2012-02-24 31 views
29

Mỗi this answerthis answer, các phương pháp tĩnh Java không phải là ảo và không thể bị ghi đè. Bằng trực giác, do đó, điều này sẽ làm việc (thậm chí nếu trong 99% các trường hợp đó là chương trình nguy hiểm):Tại sao Java thực thi khả năng tương thích kiểu trả về cho các phương thức tĩnh bị ghi đè?

class Foo 
{ 
    public static String frob() { 
     return "Foo"; 
    } 
} 

class Bar extends Foo 
{ 
    public static Number frob() { 
     return 123; 
    } 
} 

Tuy nhiên, trong thực tế điều này giúp bạn:

Foo.java:10: frob() in Bar cannot override frob() in Foo; attempting to use incompatible return type 
found : java.lang.Number 
required: java.lang.String 
    public static Number frob() { 
         ^

ngây thơ, nó có vẻ như Foo.frob()Bar.frob() không nên làm gì với nhau; nhưng Java khẳng định rằng họ làm. Tại sao?

(Nb: Tôi không muốn nghe lý do tại sao nên viết mã theo cách này, tôi muốn nghe những gì trong Java và/hoặc thiết kế JVM làm cho hạn chế này cần thiết.)


Đã cập nhật để thêm: Đối với những người nghĩ rằng trình biên dịch sẽ bị nhầm lẫn bằng cách gọi phương thức tĩnh trên các trường hợp, nếu bạn cho phép điều này: nó sẽ không. Nó đã có con số này ra trong trường hợp các phương pháp chữ ký tương thích:

class Foo 
{ 
    static String frob() { 
     return "Foo"; 
    } 
} 

class Bar extends Foo 
{ 
    static String frob() { 
     return "Bar"; 
    } 
} 

class Qux { 
    public static void main(String[] args) { 
     Foo f = new Foo(); 
     Foo b = new Bar(); 
     Bar b2 = new Bar(); 

     System.out.println(f.frob()); 
     System.out.println(b.frob()); 
     System.out.println(b2.frob()); 
    } 
} 

giúp bạn:

Foo 
Foo 
Bar 

Câu hỏi đặt ra là, lý do cụ thể là những gì lý do tại sao nó không thể như một cách dễ dàng (trong trường hợp không tương thích chữ ký) giúp bạn có được:

Foo 
Foo 
123 

Trả lời

8

xem xét như sau:

public class Foo { 
    static class A { 
    public static void doThing() { 
     System.out.println("the thing"); 
    } 
    } 

    static class B extends A { 

    } 

    static class C extends B { 
    public static void doThing() { 
     System.out.println("other thing"); 
    } 
    } 

    public static void main(String[] args) { 
    A.doThing(); 
    B.doThing(); 
    C.doThing(); 
    } 
} 

Chạy! Nó biên dịch và in ra

the thing 
the thing 
other thing 

phương pháp tĩnh loại kế thừa - theo nghĩa là B.doThing được dịch sang một cuộc gọi đến A.doThing - và loại có thể được ghi đè.

Điều này có vẻ như nó chủ yếu là một cuộc gọi phán đoán cho JLS. Cách cụ thể nhất mà JLS dường như giải quyết vấn đề này, mặc dù, là section 8.2, mà chỉ đơn giản là không nói rằng phương pháp tĩnh không phải là được kế thừa.

+0

Woah, dude, man. – Tom

+0

Tôi không tích cực điều này nhất thiết phải là quyết định tốt nhất có thể - nó đã dẫn đến những vụng về như [mã này trong tiếng ổi] (http://code.google.com/p/guava-libraries/source/browse/guava/ src/com/google/common/collect/ImmutableSortedSetFauxverideShim.java) - nhưng nó không phải là một quyết định không hợp lý, và tôi cho rằng họ có lý do cho cuộc gọi phán xét này mà tôi chưa từng nghĩ đến. –

+2

'myB' sẽ gọi' doThing() 'tắt của kiểu' myB'. –

2

Đó là vì trong Java, một phương pháp cụ thể được gọi dựa trên loại thời gian chạy của đối tượng chứ không phải trên loại thời gian biên dịch của nó. Tuy nhiên, các phương thức tĩnh là các phương thức lớp và do đó truy cập chúng luôn được giải quyết trong thời gian biên dịch chỉ bằng cách sử dụng thông tin kiểu thời gian biên dịch. Đó là, điều gì sẽ xảy ra nếu bạn có thể biên dịch mã trên và sử dụng như thế này

Foo bar = new Bar(); 
bar.frob(); 
+2

Điều này không trả lời được câu hỏi. Trong ví dụ của bạn, tôi mong đợi 'Foo.frob()' được gọi là 'bar' là một' Foo'. –

+0

@SteveKuo Nhưng các quy tắc chữ ký Java bình thường được đưa vào để chơi trong trường hợp này; các phương thức kế thừa không thể có cùng các tham số và các kiểu trả về khác nhau. –

3

JLS 8.4.2 Method Signature, ngắn gọn:

Hai phương pháp có chữ ký tương tự nếu họ có tên và lập luận kiểu như vậy.

Không có gì được nói về tính tĩnh.Các phương thức tĩnh có thể được gọi thông qua một cá thể (hoặc tham chiếu null) - phương thức này sẽ giải quyết như thế nào nếu một lớp con được tham chiếu thông qua khai báo lớp cha?

+2

Trình biên dịch đã có khả năng giải quyết vấn đề này - xem nhận xét của tôi về câu trả lời của [Ryan Shillington] (http://stackoverflow.com/a/9439532/27358). –

+1

@DavidMoles Sau đó, nó đơn giản là "vì Java không cho phép các phương thức có cùng chữ ký nhưng các kiểu trả về khác nhau." Đối với lý trí, bạn cần phải hỏi Gosling/etc. nhưng hầu hết nó giải quyết "đó là một cách dễ dàng để loại bỏ một loại lỗi người dùng cụ thể." –

1

Vâng, JVM có thể được thực hiện để cho phép điều đó, nhưng chúng ta hãy nói về lý do tại sao đó là một ý tưởng khá tồi từ góc độ trình biên dịch.

Dữ liệu cá thể (bao gồm loại cá thể nào) không phải là vấn đề đơn giản để giải quyết lúc biên dịch. Rõ ràng nó cũng được biết đến trong thời gian chạy. Nếu tôi có một thanh biến kiểu Bar, và tôi gọi s = bar.frob(), trình biên dịch sẽ cần phải đảo ngược kỹ sư thanh loại nào để xem liệu giá trị trả về có được chấp nhận hay không. Nếu xác định loại tại thời gian biên dịch là một vấn đề siêu cứng, điều này làm cho trình biên dịch không hiệu quả ở mức tốt nhất. Tại tồi tệ nhất câu trả lời là sai và bạn nhận được lỗi thời gian chạy nên đã bị bắt tại thời gian biên dịch.

+3

Tôi không mua đối số này. Trình biên dịch đã có một quy tắc cho việc này. Nếu 'b' được khai báo là' Bar', thì 'b.frob()' là 'Bar.frob()' và nếu 'b' được khai báo là' Foo', thì ngay cả khi 'b' là * thực sự * một thể hiện của 'Bar',' b.frob() 'là' Foo.frob() '. Điều này rất dễ dàng để chứng minh nếu bạn tạo một ví dụ trong đó các loại tương thích. –

+0

(Câu hỏi được chỉnh sửa để thêm một ví dụ.) –

+0

Theo như JVM có liên quan, nó sẽ tìm kiếm hệ thống phân cấp thừa kế cho một kết hợp chính xác bao gồm cả kiểu trả về. Nó bỏ qua "các loại trả về biến đổi" và tương tự. –

Các vấn đề liên quan