Kế thừa: Mã từ siêu lớp hầu như * được sao chép * sang lớp con, hay nó * được gọi bởi lớp con *?


10

Lớp Sublà một lớp con của lớp Sup. Điều đó có nghĩa thực tế là gì? Hay nói cách khác, ý nghĩa thực tế của "thừa kế" là gì?

Tùy chọn 1: Mã từ Sup hầu như được sao chép sang Sub. (như trong 'sao chép-dán', nhưng không có mã được sao chép trực quan nhìn thấy trong lớp con).

Ví dụ: methodA()là một phương thức ban đầu trong Sup. Sub mở rộng Sup, do đó methodA()(hầu như) được sao chép-dán vào Sub. Bây giờ Sub có một phương thức được đặt tên methodA(). Nó giống hệt với Sup methodA()trong mọi dòng mã, nhưng hoàn toàn thuộc về Sub - và không phụ thuộc vào Sup hoặc có liên quan đến Sup theo bất kỳ cách nào.

Tùy chọn 2: Mã từ Sup không thực sự được sao chép sang Sub. Nó vẫn chỉ trong siêu lớp. Nhưng mã đó có thể được truy cập thông qua lớp con và có thể được sử dụng bởi lớp con.

Ví dụ: methodA()là một phương thức trong Sup. Sub mở rộng Sup, vì vậy bây giờ methodA()có thể được truy cập thông qua Sub như vậy : subInstance.methodA(). Nhưng điều đó thực sự sẽ gọi methodA()trong siêu lớp. Điều đó có nghĩa là phương thứcA () sẽ hoạt động trong ngữ cảnh của siêu lớp, ngay cả khi nó được gọi bởi lớp con.

Câu hỏi: Lựa chọn nào trong hai lựa chọn thực sự là cách mọi thứ hoạt động? Nếu không ai trong số họ là, hơn xin vui lòng mô tả làm thế nào những điều này thực sự hoạt động.


Điều này rất dễ để tự kiểm tra - viết mã, kiểm tra các tệp lớp (thậm chí một tổng kiểm tra sẽ làm điều đó), sửa đổi siêu lớp, biên dịch lại, xem lại các tệp lớp. Bạn cũng có thể thấy việc đọc Chương 3. Biên dịch cho Máy ảo Java của đặc tả JVM sẽ hữu ích trong việc hiểu (đặc biệt là phần 3.7).

@MichaelT " hầu như được sao chép" là từ khóa. Ngoài ra, ngay cả khi mã được sao chép theo nghĩa đen, điều này chỉ có thể xảy ra sau khi tải lớp.

@delnan sẽ rất tò mò nếu Hotspot (hoặc các trình tối ưu hóa thời gian chạy khác) sẽ mã hóa mã tại một số điểm, nhưng điều đó trở thành một chi tiết triển khai của JVM có thể khác với JVM này và do đó không thể được trả lời chính xác. Điều tốt nhất mà có thể được thực hiện đang xem xét các bytecode biên dịch (và invokespecial sử dụng opcode trong đó mô tả những gì thực sự xảy ra)

Câu trả lời:


13

Lựa chọn 2.

Mã byte được tham chiếu động khi chạy: đây là lý do tại sao, ví dụ, LinkageErrors xảy ra.

Ví dụ: giả sử bạn biên dịch hai lớp:

public class Parent {
  public void doSomething(String x) { ... }
}

public class Child extends Parent {
  @Override
  public void doSomething(String x) {
    super.doSomething(x);
    ...
  }
}

Bây giờ sửa đổi và biên dịch lại lớp cha mà không sửa đổi hoặc biên dịch lại lớp con :

public class Parent {
  public void doSomething(Collection<?> x) { ... }
}

Cuối cùng, chạy một chương trình sử dụng lớp con. Bạn sẽ nhận được NoSuchMethodError :

Ném nếu một ứng dụng cố gắng gọi một phương thức được chỉ định của một lớp (tĩnh hoặc thể hiện) và lớp đó không còn có định nghĩa về phương thức đó nữa.

Thông thường, lỗi này được trình biên dịch bắt; lỗi này chỉ có thể xảy ra trong thời gian chạy nếu định nghĩa của một lớp đã thay đổi không thể thay đổi.


7

Hãy bắt đầu với hai lớp đơn giản:

package com.michaelt.so.supers;

public class Sup {
    int methodA(int a, int b) {
        return a + b;
    }
}

và sau đó

package com.michaelt.so.supers;

public class Sub extends Sup {
    @Override
    int methodA(int a, int b) {
        return super.methodA(a, b);
    }
}

Phương thức biên dịchA và nhìn vào mã byte người ta nhận được:

  methodA(II)I
   L0
    LINENUMBER 6 L0
    ALOAD 0
    ILOAD 1
    ILOAD 2
    INVOKESPECIAL com/michaelt/so/supers/Sup.methodA (II)I
    IRETURN
   L1
    LOCALVARIABLE this Lcom/michaelt/so/supers/Sub; L0 L1 0
    LOCALVARIABLE a I L0 L1 1
    LOCALVARIABLE b I L0 L1 2
    MAXSTACK = 3
    MAXLOCALS = 3

Và bạn có thể thấy ngay ở đó với phương thức invokespecial nó thực hiện tra cứu theo phương thức lớp SupA ().

Các invokespecial opcode có logic sau đây:

  • Nếu C chứa một khai báo cho một phương thức cá thể có cùng tên và mô tả như phương thức được giải quyết, thì phương thức này sẽ được gọi. Thủ tục tra cứu chấm dứt.
  • Mặt khác, nếu C có siêu lớp, quy trình tra cứu tương tự này được thực hiện đệ quy bằng cách sử dụng siêu lớp trực tiếp của C. Phương thức được gọi là kết quả của việc gọi đệ quy của quy trình tra cứu này.
  • Mặt khác, một AbstractMethodError được đưa ra.

Trong trường hợp này, không có phương thức cá thể nào có cùng tên và mô tả trong lớp của anh ta nên viên đạn đầu tiên sẽ không bắn. Tuy nhiên, viên đạn thứ hai sẽ - có một siêu lớp và nó gọi phương thức của siêu nhân.

Trình biên dịch không nội tuyến này và không có bản sao nguồn Sup trong lớp.

Tuy nhiên câu chuyện vẫn chưa kết thúc. Đây chỉ làmã được biên dịch . Khi mã đạt JVM, HotSpot có thể tham gia.

Thật không may, tôi không biết nhiều về nó, vì vậy tôi sẽ khiếu nại chính quyền về vấn đề này và đi đến Inlining trong Java nơi người ta nói rằng HotSpot có thể phương thức nội tuyến (ngay cả phương thức không phải là cuối cùng).

Đi đến các tài liệu cần lưu ý rằng nếu một lệnh gọi phương thức cụ thể trở thành một điểm nóng thay vì thực hiện tra cứu đó mỗi lần, thông tin này có thể được nội tuyến - sao chép mã hiệu quả từ Sup methodA () vào Sub methodA ().

Điều này được thực hiện trong thời gian chạy, trong bộ nhớ, dựa trên cách ứng dụng hoạt động và những tối ưu hóa nào là cần thiết để tăng tốc hiệu suất.

Như đã nêu trong HotSpot Internals cho OpenJDK "Các phương thức thường được nội tuyến. Các cách gọi tĩnh, riêng tư, cuối cùng và / hoặc" đặc biệt "rất dễ để nội tuyến."

Nếu bạn đào sâu vào các tùy chọn cho JVM, bạn sẽ tìm thấy một tùy chọn -XX:MaxInlineSize=35(35 là mặc định) là số byte tối đa có thể được nội tuyến. Tôi sẽ chỉ ra rằng đây là lý do tại sao Java thích có nhiều phương thức nhỏ - bởi vì chúng có thể dễ dàng được nội tuyến hóa. Những phương thức nhỏ đó trở nên nhanh hơn khi chúng được gọi nhiều hơn vì chúng có thể được nội tuyến. Và trong khi người ta có thể chơi với con số đó và làm cho nó lớn hơn, nó có thể khiến các tối ưu hóa khác kém hiệu quả hơn. (câu hỏi SO liên quan: Chiến lược nội tuyến JIT của HotSpot , trong đó chỉ ra một số tùy chọn khác để xem qua nội bộ của nội tuyến mà HotSpot đang thực hiện).

Vì vậy, không - mã không được nội tuyến tại thời gian biên dịch. Và, vâng - mã rất có thể được nội tuyến trong thời gian chạy nếu tối ưu hóa hiệu suất bảo đảm nó.

Và tất cả những gì tôi đã viết về HotSpot inlining chỉ áp dụng cho HotSpot JVM do Oracle phân phối. Nếu bạn xem danh sách các máy ảo Java của wikipedia, có nhiều thứ khác ngoài HotSpot và cách các JVM xử lý nội tuyến có thể hoàn toàn khác với những gì tôi đã mô tả ở trên. Apache Harmony, Dalvik, ART - mọi thứ có thể hoạt động khác nhau ở đó.


0

mã không được sao chép, nó được truy cập bởi tham chiếu:

  • lớp con tham chiếu các phương thức của nó và lớp cha
  • siêu lớp tham chiếu các phương thức của nó

trình biên dịch có thể tối ưu hóa cách thức này được biểu diễn / thực thi trong bộ nhớ, nhưng về cơ bản đó là cấu trúc

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.