Điều gì được viện dẫn và làm thế nào để tôi sử dụng nó?


159

Tôi tiếp tục nghe về tất cả các tính năng thú vị mới đang được thêm vào JVM và một trong những tính năng thú vị đó là inv invocate. Tôi muốn biết nó là gì và làm thế nào để lập trình phản chiếu trong Java dễ dàng hơn hoặc tốt hơn?

Câu trả lời:


165

Đó là một lệnh JVM mới cho phép trình biên dịch tạo mã gọi các phương thức với đặc tả lỏng hơn so với trước đây - nếu bạn biết " gõ vịt " là gì, về cơ bản inv invocate cho phép gõ vịt. Không có quá nhiều bạn là một lập trình viên Java có thể làm với nó; Tuy nhiên, nếu bạn là người tạo công cụ, bạn có thể sử dụng nó để xây dựng các ngôn ngữ dựa trên JVM linh hoạt hơn, hiệu quả hơn. Dưới đây là một bài viết blog thực sự ngọt ngào cung cấp rất nhiều chi tiết.


3
Trong lập trình Java hàng ngày, không có gì lạ khi thấy sự phản chiếu được sử dụng để gọi các phương thức một cách linh hoạt meth.invoke(args). Vậy làm thế nào để invokedynamicphù hợp với meth.invoke?
David K.

1
Bài đăng trên blog tôi đề cập đến nói về MethodHandle, đây thực sự là một loại điều tương tự nhưng với sự linh hoạt hơn nhiều. Nhưng sức mạnh thực sự trong tất cả những điều này không chỉ bổ sung cho ngôn ngữ Java, mà ở khả năng của chính JVM trong việc hỗ trợ các ngôn ngữ khác thực chất năng động hơn.
Ernest Friedman-Hill


1
Có vẻ như Java 8 dịch một số lambdas bằng cách sử dụng invokedynamicnó làm cho nó hoạt động (so với việc bọc chúng trong một lớp bên trong ẩn danh gần như là lựa chọn duy nhất trước khi giới thiệu invokedynamic). Hầu hết có lẽ rất nhiều ngôn ngữ lập trình chức năng trên JVM sẽ chọn để biên dịch sang ngôn ngữ này thay vì các lớp bên trong.
Nader Ghanbari

2
Chỉ cần một cảnh báo nhỏ, bài đăng trên blog từ năm 2008 đã lỗi thời và không phản ánh trạng thái phát hành thực tế (2011).
Holger

9

Cách đây một thời gian, C # đã thêm một tính năng thú vị, cú pháp động trong C #

Object obj = ...; // no static type available 
dynamic duck = obj;
duck.quack(); // or any method. no compiler checking.

Hãy nghĩ về nó như cú pháp đường cho các cuộc gọi phương thức phản chiếu. Nó có thể có các ứng dụng rất thú vị. xem http://www.infoq.com/presentations/Statically-Docate-Typing-Neal-Gafter

Neal Gafter, người chịu trách nhiệm cho loại động của C #, vừa chuyển từ SUN sang MS. Vì vậy, không có lý khi nghĩ rằng những điều tương tự đã được thảo luận trong SUN.

Tôi nhớ ngay sau đó, một số anh chàng Java đã thông báo một cái gì đó tương tự

InvokeDynamic duck = obj;
duck.quack(); 

Thật không may, tính năng này không có ở đâu trong Java 7. Rất thất vọng. Đối với các lập trình viên Java, họ không có cách nào dễ dàng để tận dụng lợi thế invokedynamictrong các chương trình của họ.


41
invokedynamickhông bao giờ có ý định được sử dụng cho các lập trình viên Java. IMO nó hoàn toàn không phù hợp với triết lý Java. Nó đã được thêm vào như một tính năng JVM cho các ngôn ngữ không phải Java.
Mark Peters

5
@Mark Không bao giờ có ý định của ai? Nó không giống như có một cấu trúc quyền lực rõ ràng trong những người nổi tiếng ngôn ngữ Java hoặc có một "ý định" tập thể được xác định rõ. Đối với triết lý ngôn ngữ - đó là hoàn toàn khả thi, xem Neal Gafter (kẻ phản bội!) Giải thích: infoq.com/presentations/Statically-Dynamic-Typing-Neal-Gafter
irreputable

3
@mark peters: invokeocate thực sự cũng chỉ dành cho các lập trình viên java không thể truy cập trực tiếp. Đó là cơ sở cho việc đóng cửa của Java 8.
M Platvoet

2
@irreputable: Không bao giờ có ý định của những người đóng góp JSR. Điều đó nói lên rằng tên của JSR là "Hỗ trợ các ngôn ngữ được gõ động trên nền tảng Java". Java không phải là một ngôn ngữ được gõ động.
Mark Peters

5
@M Platvoet: Tôi đã không cập nhật với việc đóng cửa, nhưng chắc chắn đó sẽ không phải là một yêu cầu tuyệt đối cho việc đóng cửa. Một tùy chọn khác mà họ đã thảo luận chỉ đơn giản là đóng đường cú pháp cho các lớp bên trong ẩn danh có thể được thực hiện mà không cần thay đổi thông số VM. Nhưng quan điểm của tôi là JSR không bao giờ có ý định đưa kiểu gõ động vào ngôn ngữ Java, điều đó rất rõ ràng nếu bạn đọc JSR.
Mark Peters

4

Có hai khái niệm cần hiểu trước khi tiếp tục phát sinh.

1. Nhập tĩnh so với nhập dữ liệu

Tĩnh - kiểm tra kiểu phôi ở thời gian biên dịch (ví dụ Java)

Dynamic - kiểm tra kiểu phôi trong thời gian chạy (ví dụ: JavaScript)

Kiểm tra kiểu là một quá trình xác minh rằng một chương trình là loại an toàn, đây là, kiểm tra thông tin đã nhập cho các biến lớp và thể hiện, tham số phương thức, giá trị trả về và các biến khác. Ví dụ: Java biết về int, String, .. tại thời gian biên dịch, trong khi loại đối tượng trong JavaScript chỉ có thể được xác định khi chạy

2. Đánh máy mạnh so với yếu

Mạnh - chỉ định các hạn chế đối với các loại giá trị được cung cấp cho các hoạt động của nó (ví dụ: Java)

Yếu - chuyển đổi (phôi) đối số của một hoạt động nếu các đối số đó có loại không tương thích (ví dụ: Visual Basic)

Biết rằng Java là một kiểu gõ tĩnh và yếu, làm thế nào để bạn triển khai các ngôn ngữ được gõ một cách linh hoạt và mạnh mẽ trên JVM?

Inv invocate thực hiện một hệ thống thời gian chạy có thể chọn triển khai phương thức hoặc hàm thích hợp nhất - sau khi chương trình được biên dịch.

Ví dụ: Có (a + b) và không biết gì về các biến a, b tại thời gian biên dịch, inv invocate ánh xạ thao tác này sang phương thức thích hợp nhất trong Java khi chạy. Ví dụ: nếu hóa ra a, b là Chuỗi, thì gọi phương thức (Chuỗi a, Chuỗi b). Nếu hóa ra a, b là ints, thì gọi phương thức (int a, int b).

invokeocate đã được giới thiệu với Java 7.


4

Là một phần của Hồ sơ Java của tôi bài viết , tôi đã nói rõ về động lực đằng sau Inoke Dynamic. Hãy bắt đầu với một định nghĩa sơ bộ về Indy.

Giới thiệu Indy

Invoke Dynamic (Còn được gọi là Indy ) là một phần của JSR 292 có ý định tăng cường hỗ trợ JVM cho Ngôn ngữ loại động. Sau lần phát hành đầu tiên trong Java 7, invokedynamicOpcode cùng với nójava.lang.invoke hành lý được sử dụng khá nhiều bởi các ngôn ngữ dựa trên JVM động như JRuby.

Mặc dù indy được thiết kế đặc biệt để tăng cường hỗ trợ ngôn ngữ động, nhưng nó cung cấp nhiều hơn thế. Thực tế, nó phù hợp để sử dụng bất cứ nơi nào một nhà thiết kế ngôn ngữ cần bất kỳ hình thức năng động nào, từ nhào lộn kiểu động đến chiến lược năng động!

Chẳng hạn, các biểu thức Lambda của Java 8 thực sự được triển khai bằng cách sử dụng invokedynamic, mặc dù Java là một ngôn ngữ được gõ tĩnh!

Mã byte do người dùng xác định

Trong một thời gian, JVM đã hỗ trợ bốn kiểu gọi phương thức: invokestaticgọi phương thức tĩnh, invokeinterfacegọi phương thức giao diện, invokespecialgọi hàm tạo super()hoặc phương thức riêng tư vàinvokevirtual gọi phương thức cá thể.

Bất chấp sự khác biệt của chúng, các kiểu gọi này có chung một đặc điểm: chúng ta không thể làm phong phú chúng bằng logic riêng của chúng ta . Ngược lại, invokedynamic cho phép chúng tôi Bootstrap quá trình gọi theo bất kỳ cách nào chúng tôi muốn. Sau đó, JVM sẽ gọi trực tiếp Phương thức Bootstrapping.

Indy làm việc như thế nào?

Lần đầu tiên JVM nhìn thấy một invokedynamiclệnh, nó gọi một phương thức tĩnh đặc biệt gọi là Phương thức Bootstrap . Phương thức bootstrap là một đoạn mã Java mà chúng ta đã viết để chuẩn bị logic thực tế được gọi:

nhập mô tả hình ảnh ở đây

Sau đó, phương thức bootstrap trả về một thể hiện của java.lang.invoke.CallSite. Điều này CallSitegiữ một tham chiếu đến phương thức thực tế, tức làMethodHandle .

Từ giờ trở đi, mỗi khi JVM nhìn thấy invokedynamichướng dẫn này một lần nữa, nó sẽ bỏ qua Đường dẫn chậm và gọi trực tiếp tệp thực thi cơ bản. JVM tiếp tục bỏ qua đường dẫn chậm trừ khi có gì đó thay đổi.

Ví dụ: Bản ghi Java 14

Java 14 Recordsđang cung cấp một cú pháp nhỏ gọn đẹp mắt để khai báo các lớp được cho là chủ sở hữu dữ liệu câm.

Xem xét hồ sơ đơn giản này:

public record Range(int min, int max) {}

Mã byte cho ví dụ này sẽ là một cái gì đó như:

Compiled from "Range.java"
public java.lang.String toString();
    descriptor: ()Ljava/lang/String;
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokedynamic #18,  0 // InvokeDynamic #0:toString:(LRange;)Ljava/lang/String;
         6: areturn

Trong Bảng Phương thức Bootstrap của nó :

BootstrapMethods:
  0: #41 REF_invokeStatic java/lang/runtime/ObjectMethods.bootstrap:
     (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;
     Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;
     Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
    Method arguments:
      #8 Range
      #48 min;max
      #50 REF_getField Range.min:I
      #51 REF_getField Range.max:I

Vì vậy, phương thức bootstrap cho Records được gọi là bootstrapnằm trong java.lang.runtime.ObjectMethodslớp. Như bạn có thể thấy, phương thức bootstrap này mong đợi các tham số sau:

  • Một ví dụ MethodHandles.Lookupđại diện cho bối cảnh tra cứu (Phần Ljava/lang/invoke/MethodHandles$Lookup).
  • Tên phương pháp (ví dụ toString, equals, hashCode, vv) bootstrap sẽ liên kết. Ví dụ: khi giá trị là toString, bootstrap sẽ trả về một ConstantCallSite( CallSitekhông bao giờ thay đổi) chỉ ra việc toStringtriển khai thực tế cho Bản ghi cụ thể này.
  • Các TypeDescriptorcho phương pháp này (Ljava/lang/invoke/TypeDescriptor một phần).
  • Một loại mã thông báo, nghĩa là Class<?>, đại diện cho loại lớp Ghi. nó là Class<Range> trong trường hợp này.
  • Một danh sách được phân tách bằng dấu hai chấm của tất cả các tên thành phần, nghĩa là min;max .
  • Một MethodHandlecho mỗi thành phần. Bằng cách này, phương thức bootstrap có thể tạo ra MethodHandledựa trên các thành phần để thực hiện phương thức cụ thể này.

Lệnh này invokedynamicchuyển tất cả các đối số đó sang phương thức bootstrap. Phương thức Bootstrap, trả về một thể hiện của ConstantCallSite. Điều này ConstantCallSiteđang giữ một tài liệu tham khảo để thực hiện phương pháp được yêu cầu, ví dụ toString.

Tại sao lại là Indy?

Trái ngược với API phản chiếu, java.lang.invoke API khá hiệu quả do JVM hoàn toàn có thể nhìn thấy qua tất cả các yêu cầu. Do đó, JVM có thể áp dụng tất cả các loại tối ưu hóa miễn là chúng ta tránh được đường dẫn chậm nhất có thể!

Ngoài các đối số hiệu quả, invokedynamiccách tiếp cận đáng tin cậy hơn và ít giòn hơn vì tính đơn giản của nó .

Hơn nữa, mã byte được tạo cho Bản ghi Java độc lập với số lượng thuộc tính. Vì vậy, ít mã byte hơn và thời gian khởi động nhanh hơn.

Cuối cùng, giả sử một phiên bản Java mới bao gồm triển khai phương thức bootstrap mới và hiệu quả hơn. Với invokedynamic, ứng dụng của chúng tôi có thể tận dụng sự cải tiến này mà không cần biên dịch lại. Bằng cách này, chúng tôi có một số loại tương thích nhị phân chuyển tiếp . Ngoài ra, đó là chiến lược năng động mà chúng ta đang nói đến!

Những ví dụ khác

Ngoài Java Records, động gọi ra đã được sử dụng để triển khai các tính năng như:

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.