2013-08-23 54 views
8

Tôi đã thực hiện một số thử nghiệm và vô tình viết một mã, điều này rất lạ và tôi không hiểu hết. Tôi thậm chí còn ngạc nhiên rằng tôi có thể biên dịch nó. Nó trông giống như thế này:Java: Định nghĩa các phương pháp và biến bên trong hằng số của enum

enum Foo { 
    VALUE_1 { 
     public int myVariable = 1; 
    }, 
    VALUE_2 { 
     public void myMethod() { 
      // 
     } 
    }, 
    VALUE_3; 
} 

Đúng như dự đoán, nó không thể truy cập vào một yếu tố như vậy theo cách sau:

Foo.VALUE_2.myMethod(); 

Lý do là, trình biên dịch sẽ tìm kiếm phương pháp mà bên trong liệt kê bản thân .

Tôi cho rằng không thể truy cập các phương pháp và biến này từ bên ngoài điều tra. Vì lý do này, tôi đã cố gắng tạo một hàm tạo tham số và gọi nó với một số biến nội bộ:

enum Foo { 
    VALUE(internalVariable) { 
     int internalVariable = 1; 
    }; 

    private Foo(int param) { 
     // 
    } 
} 

Không thể biên dịch một công trình như vậy. Bây giờ tôi đã nghĩ điểm xác định thứ gì đó bên trong hằng số là gì nếu không có cách nào để truy cập nó.

Tôi đã cố tạo các phương thức có cùng tên trong hằng số cũng như trong liệt kê chính nó để kiểm tra xem nó có va chạm theo một cách nào đó không. Nó không được!

enum Foo { 
    VALUE_1 { 
     int myVariable = 1; 

     public int myMethod() { 
      return myVariable; 
     } 
    }, 
    VALUE_2 { 
     // 
    }; 

    public int myMethod() { 
     return 0; 
    } 
} 

Và đây là thời điểm vui nhộn! Tôi đã cố gắng thực hiện cuộc gọi của myMethod() trong liệt kê và thực sự đã tìm ra cách thức hoạt động của phép thuật Java này. Các phương thức, được định nghĩa bên trong hằng số, ghi đè các phương thức được xác định bên trong liệt kê.

Foo.VALUE_1.myMethod(); // Returns 1 
Foo.VALUE_2.myMethod(); // Returns 0 

Tuy nhiên, chúng tôi không thể ghi đè biến, phải không? Vì vậy, tôi đã tò mò, làm thế nào nó hoạt động với các biến chỉ.

enum Foo { 
    VALUE_1 { 
     public int myVariable = 1; 
    }, 
    VALUE_2 { 
     // 
    }; 

    public int myVariable = 0; 
} 

.... 

System.out.println(Foo.VALUE_1.myVariable); // Returns 0 
System.out.println(Foo.VALUE_2.myVariable); // Returns 0 

Bây giờ tôi cuối cùng nhận được những câu hỏi của tôi:

  1. Tại sao tôi không nhận được bất kỳ lỗi nếu tôi có thể tạo phương pháp nào bên trong liệt kê liên tục và trái trống mà không phương pháp này? Trong trường hợp đó, phương pháp tôi vừa xác định không thể được gọi là. Hoặc là tôi sai?

    Cập nhật: Tôi biết rằng liệt kê có thể triển khai giao diện. Tuy nhiên, nếu tôi không có cụ thể nói rằng, toàn bộ mã là vô nghĩa.

    Ai đó đã chỉ ra rằng ngay cả khi không thể truy cập phương thức từ ngôn ngữ theo cách thông thường , vẫn có thể sử dụng phản chiếu. Vâng ... Tại sao chúng tôi không thiết kế một từ khóa không thể truy cập ?

    inaccessible void magicalMethod() { 
        // 
    } 
    

    Phương thức như vậy sẽ được biên dịch thành tệp * .class. Khi bạn muốn sử dụng nó, bạn phải tự mình tải bytecode và diễn giải nó.

    Tôi không thể hiểu được, tại sao có thể xác định phương pháp không thể truy cập. Lý do duy nhất Tôi có thể nghĩ là lập trình viên đang hoạt động và chưa có định nghĩa về giao diện.Vì vậy, anh ấy chỉ đang chuẩn bị mã của các phương thức đơn lẻ và sẽ thêm từ khóa "thực hiện" sau đó. Bên cạnh điều này là vô lý, nó vẫn sẽ yêu cầu phải có một phương pháp như vậy trong tất cả các hằng số.

    Tôi nghĩ điều này sẽ kết thúc với lỗi, không chỉ cảnh báo về phương pháp không sử dụng. Bạn có thể quên để thêm mệnh đề "triển khai" hoặc để xác định phương pháp trong điều tra (sẽ là ghi đè) và sẽ nhận ra rằng ngay sau lần sử dụng đầu tiên. Java là ngôn ngữ rất nghiêm ngặt, vì vậy tôi mong đợi hành vi này.

  2. Tại sao tôi không gặp bất kỳ lỗi nào nếu tôi tạo biến công khai (hoặc trường, chính xác hơn) bên trong hằng số? Nó không thể được truy cập trong mọi trường hợp (từ bên ngoài). Do đó, công cụ sửa đổi "công khai" không có ý nghĩa gì ở đây.

    Cập nhật: Nó ít giống với điểm trước đó, ngoại trừ khả năng hiển thị công cụ sửa đổi hoàn toàn vô dụng tại đây. Nó thực sự không quan trọng nếu nó được công khai, bảo vệ hoặc riêng tư, bởi vì bạn sẽ không thể truy cập vào đó. Tôi nghi no la một con bọ.

  3. Tại sao nó có thể định nghĩa một lớp (không bổ tầm nhìn), nhưng không giao tiếp? Vâng, bạn có lẽ sẽ không muốn viết liệt kê tàn bạo đến mức bạn sẽ cần định nghĩa các lớp bên trong hằng số và thậm chí là sử dụng thừa kế ở đó. Nhưng nếu có thể định nghĩa các lớp và các lớp trừu tượng thì có vẻ hơi kỳ lạ.

    Cập nhật: Đây chắc chắn không phải là thứ bạn cần thường xuyên, nhưng tôi hiểu rằng nó có thể hữu ích. Nhưng tại sao nó chỉ giới hạn ở các lớp và giao diện không thể là cũng được xác định?

    enum Foo { 
        VALUE { 
         class MyClass { 
          // OK 
         } 
    
         abstract class MyAbstractClass { 
          // OK 
         } 
    
         interface MyInterface { 
          // FAIL. It won't compile. 
         } 
        } 
    } 
    
  4. Bạn có sử dụng chức năng như vậy ở đâu đó không? Tôi có thể tưởng tượng nó có thể hữu ích, nhưng nó hơi khó hiểu. Ngoài ra, khi tôi đang tìm kiếm một số tài nguyên về điều đó, tôi đã không tìm thấy bất cứ điều gì.

    Cập nhật: Tôi muốn xem một số ví dụ thực tế được ghi đè bằng phương pháp trong cơ thể lớp liên tục enum. Bạn đã thấy nó trong một số dự án nguồn mở chưa?

Môi trường:

$ java -version 
java version "1.7.0_21" 
OpenJDK Runtime Environment (IcedTea 2.3.9) (7u21-2.3.9-0ubuntu0.12.10.1) 
OpenJDK 64-Bit Server VM (build 23.7-b01, mixed mode) 

Cảm ơn thời gian của bạn và cho câu trả lời của bạn!

+0

Bạn có thể tìm thấy ví dụ thực tế về các phương pháp ghi đè bên trong một chi phí enum trong dự án nguồn mở của Nhà máy Maker: http://www.github.com/raspacorp/maker – raspacorp

Trả lời

2

Tại sao tôi không gặp bất kỳ lỗi nào nếu tôi tạo phương thức công khai bên trong liệt kê liên tục và để trống mà không có phương pháp này? Trong trường hợp đó, phương pháp tôi vừa định nghĩa không thể được gọi là gì cả. Hoặc là tôi sai?

Thực tế, trình biên dịch có thể thấy rằng phương thức không hiển thị bên ngoài thân của lớp hằng số và cảnh báo bạn nếu nó không được sử dụng - tôi biết chắc chắn rằng Eclipse thực hiện điều này. Như dasblinkenlight points out, một phương thức công khai thực tế có thể là một ghi đè của một phương thức được khai báo bởi một giao diện mà enum thực hiện.

Tôi không thể hiểu được, tại sao có thể xác định phương pháp không thể truy cập. Lý do duy nhất tôi có thể nghĩ là lập trình viên đang hoạt động và chưa có định nghĩa về giao diện. Vì vậy, anh ấy chỉ đang chuẩn bị mã của các phương thức đơn và sẽ thêm từ khóa "thực hiện" sau đó. Bên cạnh đó là vô lý, nó vẫn sẽ yêu cầu phải có một phương pháp như vậy trong tất cả các hằng số.

Như tôi đã lưu ý, điều này không áp dụng cụ thể cho các lớp không đổi enum. Có nhiều phạm vi - các lớp lồng nhau riêng tư, các lớp địa phương, các lớp ẩn danh - nơi nó vô nghĩa cho một thành viên được công khai.

Vấn đề với câu hỏi này là chỉ có các nhà thiết kế ngôn ngữ mới có thể trả lời được. Tôi chỉ có thể đưa ra ý kiến ​​của tôi, đó là: tại sao nó lại là một lỗi? Thông số ngôn ngữ không được cung cấp miễn phí - mọi thứ trong JLS phải được xác định cẩn thận và sau đó được triển khai và thử nghiệm. Câu hỏi thực sự là, những lợi ích là có để làm cho nó một lỗi? Thực tế chỉ là trong khi một thành viên không sử dụng có thể chỉ ra một lỗi (do đó cảnh báo), nó không phải là làm tổn thương bất cứ điều gì.

Tại sao tôi không gặp lỗi nào nếu tôi tạo biến công khai (hoặc trường, chính xác hơn) bên trong hằng số? Nó không thể được truy cập trong mọi trường hợp (từ bên ngoài). Do đó, công cụ sửa đổi "công khai" không có ý nghĩa gì ở đây.

Tương tự như trên - trình biên dịch hoặc ít nhất một số IDE sẽ cảnh báo bạn nếu biến không được sử dụng. Điều này giống như khi bạn khai báo một biến số public trong một lớp học được xếp chồng private và sau đó không tham chiếu nó ở bất kỳ đâu. Trong mọi trường hợp, nó không phải là ưu tiên của JLS để cấm các tình huống như vậy, mặc dù mắt của sự phản chiếu nhìn thấy tất cả.

Điều này cũng giống như ở điểm trước, ngoại trừ công cụ sửa đổi hiển thị hoàn toàn vô dụng ở đây. Nó thực sự không quan trọng nếu nó là công cộng, được bảo vệ hay riêng tư, bởi vì bạn sẽ không thể truy cập vào điều đó. Tôi nghi no la một con bọ.

Ở đây, bạn quên rằng các thành viên vẫn có thể được sử dụng trong nội dung lớp liên tục enum - hãy nghĩ đến phương thức trợ giúp. Nó chỉ là trong trường hợp này truy cập modifiers chỉ đơn giản là không quan trọng và có thể được trái.

Tại sao có thể xác định một lớp (không có bộ sửa đổi hiển thị), nhưng không phải là giao diện? Vâng, bạn có lẽ sẽ không muốn viết liệt kê tàn bạo đến mức bạn sẽ cần định nghĩa các lớp bên trong hằng số và thậm chí là sử dụng thừa kế ở đó. Nhưng nếu có thể định nghĩa các lớp và các lớp trừu tượng thì có vẻ hơi kỳ lạ.

Đây là một câu hỏi hay và tôi mất một lúc để hiểu ý của bạn.Để làm rõ, bạn đang nói rằng trong tình huống này chỉ lớp được phép:

VALUE_1 { 
    class Bar { } 
    interface Baz { } 
}, 

Để làm sáng tỏ về vấn đề này, hãy thử làm lớp static:

VALUE_1 { 
    static class Bar { } 
    interface Baz { } 
}, 

Bây giờ không được phép. Tại sao? Không có gì có thể được khai báo trong một cơ thể liên tục enum, vì cơ thể nằm trong ngữ cảnh của thể hiện của hằng số đó. Điều này tương tự là trong phạm vi của một bên (không tĩnh lồng nhau) lớp:

class Outer { 

    class Inner { 
     // nothing static allowed here either! 
    } 
} 

tĩnh biến, phương pháp, các lớp học, và giao diện (mà đang ngầm tĩnh khi lồng nhau) đều bị cấm trong đó phạm vi.

Bạn có sử dụng chức năng như vậy ở đâu đó không? Tôi có thể tưởng tượng nó có thể hữu ích, nhưng nó hơi khó hiểu. Ngoài ra, khi tôi đang tìm kiếm một số tài nguyên về điều đó, tôi đã không tìm thấy bất cứ điều gì.

Không rõ chức năng của bạn đề cập cụ thể ở đây là gì. Vui lòng cập nhật câu hỏi để xác định chính xác những gì bạn đang tìm kiếm - các phương thức được ghi đè trong cơ thể lớp không đổi enum? Lĩnh vực? Phương pháp trợ giúp? Lớp học trợ giúp? Vui lòng làm rõ.

+0

Cảm ơn bạn đã trả lời! Bạn nói đúng, nó thực sự tạo ra cảnh báo. Tôi muốn hỏi tại sao nó không tạo ra lỗi, nhưng chỉ cảnh báo :-). Tôi chỉ đang chỉnh sửa câu hỏi khi bạn đăng bài này. – tzima

+0

@ TomášZíma Tôi đã cập nhật câu trả lời của mình để trả lời các cập nhật của bạn. –

+0

Cảm ơn! Tôi đã cập nhật câu hỏi cuối cùng của mình. Tôi đã nghĩ về điều đó một lần nữa và có lẽ bạn không cần thiết phải tạo ra lỗi. Nhưng ví dụ trong giao diện có thể được xác định chỉ có phương pháp công cộng. Trong trường hợp khác nó sẽ kết thúc với lỗi ("loại bỏ sửa đổi không hợp lệ"). Đó là những gì tôi đã mong đợi ít nhất trong trường hợp của lĩnh vực (chỉ "tư nhân" sẽ cho phép). – tzima

2

Từ JLS

Cơ thể lớp tùy chọn của một hằng số enum ngầm định nghĩa một tuyên bố mang tính chất lớp (§15.9.5) kéo dài kiểu enum ngay kèm theo.

Tạo một cái gì đó như thế này

VALUE_2 { 
    public void myMethod() { 
     // 
    } 
}, 

khi không có myMethod() khai báo trong Enum bản thân (hoặc siêu kiểu của nó) chỉ làm cho một phương pháp scoped đến lớp trừu tượng, ví dụ. không thể được gọi bên ngoài cơ thể đó. Hành vi tương tự áp dụng cho một trường được khai báo bên trong phần thân này. Số nhận dạng public không thay đổi bất kỳ thứ gì.

Sửa

Đối với câu hỏi thứ 3, bởi vì những gì bạn đang làm là khai báo một lớp vô danh, không có thành phần khác sẽ có quyền truy cập vào (để thực hiện) giao diện. Mặt khác, một lớp cung cấp hành vi và do đó có thể được sử dụng trong lớp ẩn danh.

Kiểm tra một số restrictions trên các lớp ẩn danh.

End Sửa

Đối 4. Tôi chưa bao giờ thấy mã như vậy trong thực tế. Nó có thể hữu ích nếu bạn muốn một số phương thức trợ giúp để thực hiện một số hành vi cụ thể chỉ liên quan đến enum cụ thể đó. Điều đó sẽ rất kỳ lạ và có lẽ sẽ phù hợp hơn cho một lớp học thực sự.

Hãy xem singleton pattern implemented with an enum. Bạn có thể muốn có nhiều người độc thân với mỗi người có triển khai riêng của họ. Khai báo lớp enum ẩn danh với các phương thức overriden có thể là một cách để thực hiện điều này.

3

[...] phương pháp tôi vừa xác định không thể gọi được. Hoặc là tôi sai?

đúng, anh sai rồi: Java enum s có thể thực hiện các giao diện, như thế này:

interface Bar { 
    void myMethod(); 
} 
enum Foo implements Bar { 
    VALUE_1 { 
     public void myMethod() { 
      System.err.println("val1"); 
     } 
    }; 
} 

Bây giờ bạn có quyền truy cập vào myMethod bên VALUE_1. Tất nhiên bạn sẽ bị buộc phải thực hiện phương pháp này trong các giá trị khác hoặc trong chính bản thân số enum. Ngoài ra, bạn luôn có thể truy cập các phương thức như vậy, không thể truy cập thông qua ngôn ngữ, thông qua phản ánh.

Theo như các biến công khai có liên quan, có vẻ như phản ánh là cách duy nhất ở đây. Tuy nhiên, không có lý do gì để cấm điều này hoàn toàn (mặc dù thật khó để tưởng tượng một ứng dụng hữu ích cho họ).

Bạn có sử dụng chức năng như vậy ở đâu đó không?

Tôi đã sử dụng một enum đã triển khai giao diện, với mỗi lần thực hiện liên tục các phương pháp của giao diện theo cách cụ thể.

+0

Phần đầu tiên bạn trích dẫn đề cập đến một phương thức công khai được khai báo trong phần thân của 'enum' trong đó hàm kê khai OP _left rỗng mà không có phương thức này_. Nói cách khác, phương thức này không có trong một giao diện ở bất kỳ đâu và là cục bộ cho lớp ẩn danh. –

+0

@SotiriosDelimanolis Không, đó không phải là trong cơ thể của enum: phương pháp là trên giá trị cá nhân, không phải trên lớp. Giao diện không áp dụng cho toàn bộ enum, mặc dù. – dasblinkenlight

+0

Tôi nghĩ rằng OP có nghĩa là một phương thức được khai báo bên trong hằng số enum, nhưng không phải bất cứ nơi nào khác, không phải là enum, không phải là một số giao diện mà nó đang thực hiện. Hư không. Trong trường hợp đó nó sẽ không thể truy cập ngoại trừ thông qua sự phản chiếu, nó ẩn danh. –

4

OK, tôi đã thực sự sử dụng chức năng này! Tôi đang viết trò chơi đơn giản và muốn cung cấp hai gói âm thanh. Bởi vì trò chơi rất đơn giản và sẽ không được kéo dài trong tương lai, tôi không muốn tạo ra một số cơ chế phức tạp để đạt được một điều như vậy.

public enum SoundPack { 
    CLASSICAL { 
     @Override 
     public String getSoundPickUp() { 
      return "res/sounds/classical/pick.wav"; 
     } 

     @Override 
     public String getSoundNewLevel() { 
      return "res/sounds/classical/success.wav"; 
     } 

     @Override 
     public String getSoundFail() { 
      return "res/sounds/fail.wav"; 
     } 
    }, 
    UNHEALTHY { 
     @Override 
     public String getSoundPickUp() { 
      return "res/sounds/unhealthy/quick_fart.wav"; 
     } 

     @Override 
     public String getSoundNewLevel() { 
      return "res/sounds/unhealthy/toilet_flush.wav"; 
     } 

     @Override 
     public String getSoundFail() { 
      return "res/sounds/unhealthy/vomiting.wav"; 
     } 
    }; 

    public abstract String getSoundPickUp(); 
    public abstract String getSoundNewLevel(); 
    public abstract String getSoundFail(); 
} 

Vì vậy, tôi vừa xác định enum bạn thấy ở trên. Trong lớp học, mà đang nắm giữ tất cả các cấu hình, chỉ là một thuộc tính như thế này:

private SoundPack soundPack = SoundPack.CLASSICAL; 

Bây giờ, nếu tôi cần phải chơi một số âm thanh, tôi có thể có được con đường rất đơn giản:

configuration.getSoundPack().getSoundNewLevel(); 

Cấu hình có thể thay đổi rất dễ dàng khi chạy chỉ bằng cách gán một giá trị khác cho soundPack của trường. Nếu âm thanh chưa được tải (và có khả năng chúng sẽ không như tôi đang sử dụng tải rất nhiều), các thay đổi sẽ ảnh hưởng ngay lập tức. Không thay đổi gì khác.

Ngoài ra, nếu tôi muốn thêm gói âm thanh mới, nó sẽ được thực hiện bằng cách xác định hằng số mới trong enum đó. Eclipse sẽ hiển thị cảnh báo, tôi sẽ chỉ nhấn CTRL + 1 và tạo ra các phương thức này. Vì vậy, nó cũng rất dễ dàng.

Tôi biết rằng đây không phải là cách tốt nhất để làm điều đó. Nhưng thật dễ dàng, nhanh chóng và điều quan trọng nhất là gì: Tôi muốn thử để sử dụng điều đó trong praxis. :-)