2014-11-03 17 views
12

Vấn đề của tôi là tôi không thể hiểu cách phân giải phương thức hoạt động trong trường hợp sau: Giả sử, chúng tôi có hai gói, AB. Có hai lớp, A được đặt trong số A, B trong phạm vi B.Cách ghi đè phương thức với phạm vi hiển thị (gói) mặc định?

A:

package com.eka.IO.a; 
import com.eka.IO.b.B; 

public class A { 

    void foo() { 
     System.out.println("parent"); 
    } 

    public static void main(String... args) { 
     B obj = new B(); 
     obj.foo(); 
    } 

} 

B:

package com.eka.IO.b; 
import com.eka.IO.a.A; 

public class B extends A { 

    public void foo() { 
     System.out.println("child"); 
    } 

} 

Đoạn mã trên bản in "trẻ em", mà là hoàn toàn OK. Nhưng nếu tôi thay đổi phương thức theo cách sau:

public static void main(String... args) { 
    A obj = new B(); 
    obj.foo(); 
} 

mã in "gốc" và tôi không hiểu tại sao. (obj có kiểu runtime B, B có một phương pháp nào foo)

Tiếp theo, tôi thay đổi tầm nhìn foo của công chúng,

public class A { 

    public void foo() { 

và in mã "con" một lần nữa.

Theo như tôi biết, phương pháp dụ được giải quyết trong thời gian chạy, sử dụng theo nguyên tắc sau đây:

  1. JVM kiểm tra lớp thời gian chạy của đối tượng.
  2. JVM tìm phương pháp của lớp thời gian chạy
  3. Nếu phương pháp được tìm thấy, JVM gọi nó, nếu không di chuyển đến lớp thời gian chạy cha mẹ.

Trong ví dụ của tôi, trong bất kỳ trường hợp nào, lớp thời gian chạy cho obj luôn là B. Phương phápfoo luôn công khai. Tại sao trong trường hợp thứ hai JVM gọi phương thức của A?

Lên: Câu trả lời hay, nhưng vẫn còn một số điều không rõ ràng đối với tôi. a) Trình biên dịch kiểm tra xem phương thức ghi đè phương thức khác hay không. (Hy vọng, tôi đúng). b) trong trường hợp A obj = new B(); trình biên dịch tạo ra các mã sau:

INVOKEVIRTUAL com/eka/IO/a/A.foo()V 

b1) nếu A của foo được khai báo mà không sửa đổi (tầm nhìn gói), sau đó JVM gọi phương thức của một. b2) nếu foo của A được khai báo công khai, thì JVM gọi phương thức B.

Điều không rõ ràng là lý do tại sao trong trường hợp thứ hai INVOKEVIRTUAL thực sự gọi B.foo. Làm thế nào để nó biết, rằng B ghi đè phương pháp?

+0

Tôi đã xóa nhận xét đó và chuyển vào câu trả lời. Thuật ngữ thích hợp là bóng. –

Trả lời

10

Quy trình này hơi khác so với mô tả của bạn. Đầu tiên, Java sẽ chỉ thực hiện các phương thức tồn tại trong lớp được khai báo và có thể nhìn thấy ở phạm vi hiện tại có sẵn. Điều này đã được thực hiện tại thời gian biên dịch.

Khi chạy,

  • JVM kiểm tra lớp thời gian chạy của đối tượng.
  • JVM kiểm tra xem lớp thời gian chạy của đối tượng có ghi đè phương thức của lớp được khai báo hay không.
  • Nếu có, đó là phương pháp được gọi. Nếu không, phương thức của lớp được khai báo sẽ được gọi.

Bây giờ, phần khó hiểu là "đã bị ghi đè" chưa?

Lớp học không thể ghi đè phương thức không hiển thị với nó. Nó có thể khai báo một phương thức bằng cùng tên và với cùng một đối số, nhưng phương pháp này không được coi là ghi đè phương thức gốc. Nó chỉ đơn giản là một phương pháp mới, giống như bất kỳ phương pháp nào khác được xác định trong B nhưng không phải trong A.

Nếu điều này không như vậy, bạn có thể phá vỡ hợp đồng lớp của cha mẹ tại một nơi mà tác giả cho rằng nó không bị hỏng và do đó không cho phép truy cập vào nó. Vì vậy, vì lớp không ghi đè lên phương thức, bạn chỉ có thể tham chiếu phương thức đó giống như cách bạn có thể tham chiếu bất kỳ phương thức nào được khai báo trong B không nằm trong A - chỉ thông qua tham chiếu B.

Tại sao trình biên dịch không cho phép bạn sử dụng tên của các phương thức đã có trong lớp cha, sau đó? Vâng, nếu bạn nhận được một gói, và thông tin duy nhất bạn có về nó là những gì trong hợp đồng của các lớp học, như được viết trong Javadoc của nó, thậm chí bạn sẽ không biết về sự tồn tại của phương thức đó. Đột nhiên, bạn viết một phương pháp, theo như bạn biết, là duy nhất, và bạn nhận được một lỗi biên dịch.

Không có lý do gì để làm điều đó. Những gì không hiển thị với bạn không nên ngăn bạn tự do đặt tên cho các phương thức của riêng mình. Vì vậy nó được cho phép. Nhưng nếu bạn muốn trình biên dịch ngăn cản bạn phạm sai lầm như vậy, hãy sử dụng chú thích @Override bất cứ khi nào bạn đang viết một phương thức là nghĩa là để ghi đè phương thức của lớp cha. Bằng cách này, trình biên dịch sẽ cảnh báo bạn nếu bạn đang cố gắng ghi đè lên một phương thức không phải là một phần của hợp đồng của lớp.

5

Bạn đang gặp phải Shadowing Method.Từ Java Language Specification. Chapter 6. Names. 6.4. Shadowing and Obscuring. 6.4.1. Shadowing (mỏ emphasys):

Một số tờ khai có thể được che phủ một phần của phạm vi của họ bằng cách khác tuyên bố cùng tên, trong trường hợp một tên đơn giản không thể được sử dụng để tham khảo các thực thể tuyên bố

(...)

Tuyên bố d được cho là hiển thị tại điểm p trong chương trình nếu phạm vi của d bao gồm p và d không bị che khuất bởi bất kỳ khai báo nào khác tại p.

(...)

Tờ khai d của một phương thức có tên n bóng tối tờ khai của bất kỳ phương pháp khác có tên n rằng đang ở trong một phạm vi bao quanh tại điểm đó d xảy ra trong suốt phạm vi d.

Hãy kiểm tra xem B#foo ghi đè A#foo. Từ 8.4.8.1. Overriding (by Instance Methods):

Một phương pháp dụ MC tuyên bố trong hoặc được thừa kế bởi lớp C, ghi đè từ C phương pháp khác mA khai báo trong lớp A, khi và chỉ khi tất cả những điều sau đây là đúng:

  • A là một lớp cha của C.
  • C không kế thừa mA.
  • Chữ ký của mC là một phần tử con (§8.4.2) chữ ký của mA.
  • Một trong những điều sau đây là đúng:
    • mA là công khai. (không phải trường hợp của bạn)
    • mA được bảo vệ. (không phải trường hợp của bạn)
    • mA được khai báo với quyền truy cập gói trong cùng một gói như C (không phải trường hợp của bạn vì các lớp nằm trong các gói khác nhau) và C tuyên bố mC hoặc mA là thành viên của siêu lớp trực tiếp C.
    • mA được khai báo với quyền truy cập gói và mC ghi đè mA từ một số siêu lớp C (không phải trường hợp của bạn vì phải có một lớp khác giữa C và A để bạn ghi đè mA).
    • mA được khai báo với quyền truy cập gói và mC ghi đè phương thức m 'từ C (m' khác với mC và mA), sao cho m 'ghi đè mA từ một số siêu lớp C (không phải trường hợp của bạn lớp giữa C và A cho phép bạn ghi đè mA).

Vì vậy, B#fookhông override A#foo bởi bất kỳ bình. Với điều này trong tâm trí, khi bạn gọi obj.foo() thì foo sẽ được lấy dựa trên lớp obj được chỉ định tại thời gian biên dịch. Lời giải thích của phần này được giải thích tại 15.12. Method Invocation Expressions

Nếu bạn muốn tránh điều này, hãy đánh dấu phương pháp của bạn với chú thích @Override trong phân lớp để đảm bảo bạn ghi đè cụ thể phương pháp mong muốn và không ẩn nó.Nếu bạn gặp lỗi trình biên dịch khi chú thích phương thức của bạn, thì bạn sẽ biết rằng bạn không ghi đè phương thức như vậy mà che đậy nó.

Do đó, bạn không thể ghi đè phương thức với phạm vi mặc định từ lớp con trong gói khác với lớp cha. Đánh dấu phương thức là protected trong lớp cha hoặc thiết kế lại các lớp của bạn sao cho phù hợp để tránh trường hợp này.

+0

Tuy nhiên, tôi không thể hiểu sự khác biệt giữa trường hợp thứ nhất và thứ 2. Trong cả hai trường hợp, phương thức 'foo' có cùng khả năng hiển thị, trong cả hai trường hợp obj đều đề cập đến một cá thể B. Làm thế nào để JVM xác định khi chạy, trong trường hợp đầu tiên nó nên gọi phương thức B, trong trường hợp thứ hai - phương thức A? – ekaerovets

+1

Đây là một trường hợp tuyệt vời cho việc sử dụng '@ Override' là một ý tưởng hay. – azurefrog

+0

@ekaerovets vì loại biến. –

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