2009-10-08 15 views
12

Hãy xem xét hai lớp Java sau:vô hiệu hóa thời gian biên dịch phụ thuộc kiểm tra khi biên soạn các lớp Java

a.) class Test { void foo(Object foobar) { } } 

b.) class Test { void foo(pkg.not.in.classpath.FooBar foobar) { } } 

Hơn nữa, giả sử rằng pkg.not.in.classpath.FooBar không được tìm thấy trong classpath.

Lớp đầu tiên sẽ biên dịch tiền phạt bằng cách sử dụng javac chuẩn.

Tuy nhiên, lớp thứ hai sẽ không biên dịch và javac sẽ cung cấp cho bạn thông báo lỗi "package pkg.not.in.classpath does not exist".

Các thông báo lỗi là tốt đẹp trong trường hợp chung kể từ khi kiểm tra sự phụ thuộc của bạn cho phép trình biên dịch để cho bạn biết nếu bạn có một số tranh luận phương pháp sai vv

khi tốt đẹp và hữu ích kiểm tra này phụ thuộc vào thời gian biên dịch là AFAIK không phải nghiêm chỉnh cần thiết để tạo tệp lớp Java trong ví dụ trên.

  1. Bạn có thể cho bất kỳ ví dụ mà nó sẽ là về mặt kỹ thuật không thể tạo ra một tập tin lớp Java hợp lệ mà không thực hiện biên dịch kiểm tra phụ thuộc thời gian?

  2. Bạn có biết cách nào để hướng dẫn javac hoặc bất kỳ trình biên dịch Java nào khác bỏ qua việc kiểm tra phụ thuộc thời gian biên dịch không?

Hãy đảm bảo câu trả lời của bạn giải quyết cả hai câu hỏi.

+0

Cách duy nhất (thực hiện) là tra cứu một 'MethodHandle', lưu nó trong trường' static final' và sử dụng 'invokeExact()' với các gợi ý kiểu chính xác trên MH.Đó là khá tất nhiên khá tẻ nhạt nếu bạn cần nó cho hơn một số phương pháp. – the8472

Trả lời

19

Bạn có thể đưa ra bất kỳ ví dụ nào về kỹ thuật không thể tạo tệp lớp Java hợp lệ mà không thực hiện kiểm tra phụ thuộc thời gian biên dịch không?

xem xét mã này:

public class GotDeps { 
    public static void main(String[] args) { 
    int i = 1; 
    Dep.foo(i); 
    } 
} 

Nếu phương pháp mục tiêu có chữ ký public static void foo(int n), sau đó các hướng dẫn sẽ được tạo:

public static void main(java.lang.String[]); 
    Code: 
    0: iconst_1 
    1: istore_1 
    2: iload_1 
    3: invokestatic #16; //Method Dep.foo:(I)V 
    6: return 

Nếu phương pháp mục tiêu có chữ ký public static void foo(long n), sau đó số int sẽ được thăng cấp thành long trước lời gọi phương thức:

public static void main(java.lang.String[]); 
    Code: 
    0: iconst_1 
    1: istore_1 
    2: iload_1 
    3: i2l 
    4: invokestatic #16; //Method Dep.foo:(J)V 
    7: return 

Trong trường hợp này, nó sẽ không thể tạo ra các hướng dẫn gọi hoặc làm thế nào để cư cấu trúc CONSTANT_Methodref_info gọi trong hồ bơi liên tục lớp bằng số 16. Xem class file format trong spec VM để biết thêm chi tiết.

+0

Câu trả lời hay! Để điểm và với một bằng chứng rõ ràng (như trái ngược với vẫy tay thường thấy trên SO :-)). – knorv

+2

@knorv: bạn có thể chứng minh rằng có vẫy tay trên SO hoặc bạn chỉ cần vẫy tay? ;-) –

+0

Joachim: Không, đó là rất chủ quan và do đó không thể chứng minh .-) – knorv

2

Tôi không thể xem cách bạn có thể cho phép điều này mà không phá vỡ kiểm tra loại java. Làm thế nào bạn sẽ sử dụng đối tượng tham chiếu của bạn trong phương pháp của bạn? Để mở rộng ví dụ của bạn,

class test { 
    void foo (pkg.not.in.classpath.FooBar foobar) { 
     foobar.foobarMethod(); //what does the compiler do here? 
    } 
} 

Nếu bạn gặp phải một số trường hợp bạn phải truy cập thư viện mà bạn không có quyền truy cập gần nhất có thể đến là để có được phương pháp thông qua phản ánh, một cái gì đó tương tự (phương pháp gọi từ bộ nhớ, có thể không chính xác)

void foo(Object suspectedFoobar) 
    { 
     try{ 
     Method m = suspectedFoobar.getClass().getMethod("foobarMethod"); 
     m.invoke(suspectedFoobar); 
     } 
     ... 
    } 

tôi có thể không thực sự nhìn thấy điểm để làm điều này, mặc dù. Bạn có thể cung cấp thêm thông tin về vấn đề bạn đang cố giải quyết không?

+0

Trình biên dịch sẽ chỉ vui vẻ tạo tệp lớp. Nếu foobarMethod() tồn tại hay không là một mối quan tâm thời gian chạy. – knorv

+0

"Bạn có thể cung cấp thêm thông tin về sự cố bạn đang cố giải quyết không?" Tôi không cố gắng giải quyết bất kỳ vấn đề gì. Tôi muốn biết câu trả lời cho hai câu hỏi của mình để hiểu rõ hơn về JVM. – knorv

+0

@knorv - "Nếu foobarMethod() tồn tại hay không là một mối quan tâm thời gian chạy." - Không. Không. Ít nhất không phải trong Java. – Nate

5

Tôi không nghĩ có cách như vậy - trình biên dịch cần biết về lớp của đối số, để tạo bytecode thích hợp. Nếu nó không thể định vị lớp Foobar, nó không thể biên dịch lớp Test.

Lưu ý rằng khi hai lớp của bạn là chức năng tương đương vì bạn không thực sự sử dụng đối số, chúng không giống hệt nhau và sẽ tạo ra mã byte khác nhau khi được biên dịch.

Vì vậy, tiền đề của bạn - trình biên dịch không cần phải tìm lớp để biên dịch trong trường hợp này - là không chính xác.

Sửa - nhận xét của bạn dường như được hỏi "có thể không phải là trình biên dịch chỉ bỏ qua thực tế và tạo ra các bytecode rằng sẽ thể thích hợp không?"

Câu trả lời là không -.. Nó có thể không According to the Java Language Specification, phương pháp chữ ký phải thực hiện các loại, đó là elsewhere defined được phân giải tại thời gian biên dịch

Điều đó có nghĩa rằng trong khi nó sẽ là một cách máy móc khá đơn giản để tạo một trình biên dịch có thể thực hiện những gì bạn đang yêu cầu, nó sẽ vi phạm JLS và do đó về mặt kỹ thuật sẽ không phải là trình biên dịch Java. . :-)

+0

Làm thế nào mã byte được tạo bị ảnh hưởng nhiều hơn "java/lang/Object" được thay thế bằng "pkg/not/in/classpath/FooBar"? Hãy cụ thể hơn. – knorv

+0

Tôi nghĩ rằng mã mẫu là một trường hợp đặc biệt vì đối tượng không thực sự được sử dụng (không có phương thức nào được gọi, nó không được dùng làm đối số ở bất kỳ đâu), vì vậy trong trường hợp này trình biên dịch * có thể * biên dịch nó mà không biết về lớp học. Nhưng tôi không thể nghĩ ra cách sử dụng tốt cho trường hợp đặc biệt này. –

+0

Bạn có thể cho tôi trình biên dịch đơn giản này không phải là trình biên dịch Java về mặt kỹ thuật không? Tôi dành quá nhiều thời gian để cố gắng làm cho 'javac' hạnh phúc và không đủ thời gian làm tất cả các nhiệm vụ khác liên quan đến phát triển phần mềm. – ArtOfWarfare

0

Giao diện trích xuất

pkg.in.classpath.IFooBar 

làm cho FooBar implements IFooBar

class Test { void foo(pkg.in.classpath.IFooBar foobar) {} } 

class Test của bạn sẽ được biên soạn. Chỉ cần cắm thực hiện đúng tức là FooBar trong thời gian chạy sử dụng nhà máy và cấu hình. Hãy tìm một số thùng chứa IOC IOC.

+0

Làm thế nào bạn có thể có Foobar thực hiện IFooBar mà không cần tham chiếu đến FooBar gốc? Trình biên dịch sẽ không chấp nhận điều này ngay cả khi chữ ký phương thức giống hệt nhau. –

+0

Boris: Cảm ơn, nhưng tôi không tìm kiếm một công việc xung quanh. Bạn nghĩ gì về hai câu hỏi? – knorv

0

Về điều duy nhất bạn có thể làm là sử dụng một số thao tác bytecode để chuyển đổi nó thành loại cụ thể hơn.

package pkg.not.in.classpath; 
public class FooBar { } 

từ này::

package pkg.not.in.classpath; 
class FooBar { } 

Vì vậy, chỉ có văn bản của bạn rằng đó là pháp lý để sử dụng Foobar có

Không có gì trong ngữ pháp Java cho một sử dụng pkg.not.in.classpath.FooBar để phân biệt đây là.

Ngoài ra còn một sự nhập nhằng giữa gói scoped lớp học và các lớp bên trong mã nguồn:

class pkg { 
    static class not { 
     static class in { 
      static class classpath { 
       static class FooBar {} 
      } 
     } 
    } 
} 

Các lớp bên trong cũng được gọi pkg.not.in.classpath.FooBar trong nguồn nhưng sẽ được gọi là pkg$not$in$classpath$FooBar hơn pkg/not/in/classpath/FooBar trong tập tin lớp. Không có cách nào mà javac có thể nói bạn có ý nghĩa gì mà không cần tìm nó trong classpath.

+0

Bạn hoàn toàn đúng các lớp học bên trong wrt. Nhưng liên quan đến "công khai" so với "được bảo vệ" - điều đó không làm thay đổi mã byte và do đó về mặt lý thuyết có thể được trì hoãn để được kiểm tra trong thời gian chạy. Hay tôi đang thiếu một cái gì đó? – knorv

+0

Có, nó có thể được hoãn lại cho đến sau này - IIRC nó sẽ thất bại khi lớp được xác minh khi tải nó. –

1

Java theo thiết kế thực hiện kiểm tra độ trễ thời gian biên dịch và sử dụng nó không chỉ để xác định loại mà để xác định cuộc gọi phương thức khi chúng bị quá tải. Tôi biết không có cách nào xung quanh điều đó.

Điều có thể được thực hiện (và được thực hiện cho, các trình điều khiển JDBC) là để trì hoãn việc kiểm tra phụ thuộc thông qua việc sử dụng sự phản chiếu. Bạn có thể lấy lớp từ Class.forName mà không cần trình biên dịch biết lớp ở thời gian biên dịch. Nói chung, tuy nhiên, điều này có nghĩa là mã được viết vào một giao diện và khi chạy một lớp được nạp để thực hiện giao diện.

2

Sẽ là vi phạm JLS để biên dịch một lớp mà không cần nhìn vào chữ ký loại của các lớp tùy thuộc vào. Không có trình biên dịch Java phù hợp nào cho phép bạn làm điều này.

Tuy nhiên ... có thể thực hiện điều gì đó tương tự. Cụ thể, nếu chúng ta có một lớp A và lớp B mà phụ thuộc vào A, người ta có thể làm như sau:

  1. Compile A.java
  2. Compile B.java chống A.class.
  3. Chỉnh sửa A.java để thay đổi nó theo cách không tương thích.
  4. Biên dịch A.java, thay thế lớp A. cũ.
  5. Chạy ứng dụng Java bằng B.class và lớp A. mới không tương thích.

Nếu bạn làm điều này, ứng dụng sẽ không thành công với IncompatibleClassChangeError khi trình tải lớp thông báo tính không tương thích chữ ký.

Thực ra, điều này minh họa tại sao việc biên dịch bỏ qua các phụ thuộc sẽ là một ý tưởng tồi. Nếu bạn chạy một ứng dụng với các tập tin bytecode không nhất quán, (chỉ) phát hiện sự mâu thuẫn đầu tiên sẽ được báo cáo. Vì vậy, nếu bạn có nhiều mâu thuẫn, bạn sẽ cần phải chạy ứng dụng của bạn rất nhiều lần để "phát hiện" tất cả chúng. Thật vậy, nếu có bất kỳ tải động nào của các lớp (ví dụ: sử dụng Class.forName()) trong ứng dụng hoặc bất kỳ phụ thuộc nào của nó, thì một số vấn đề này có thể không hiển thị ngay lập tức.

Tóm lại, chi phí bỏ qua các phụ thuộc trong thời gian biên dịch sẽ chậm hơn trong việc phát triển Java và các ứng dụng Java ít tin cậy hơn.

0

Tôi tạo ra hai lớp: CallerCallee

public class Caller { 
    public void doSomething(Callee callee) { 
     callee.doSomething(); 
    } 

    public void doSame(Callee callee) { 
     callee.doSomething(); 
    } 

    public void doSomethingElse(Callee callee) { 
     callee.doSomethingElse(); 
    } 
} 

public class Callee { 
    public void doSomething() { 
    } 
    public void doSomethingElse() { 
    } 
} 

tôi biên soạn các lớp này và sau đó tháo rời chúng với javap -c Callee > Callee.bcjavap -c Caller > Caller.bc. Đây được sản xuất như sau:

Compiled from "Caller.java" 
public class Caller extends java.lang.Object{ 
public Caller(); 
Code: 
0: aload_0 
1: invokespecial #1; //Method java/lang/Object."<init>":()V 
4: return 

public void doSomething(Callee); 
Code: 
0: aload_1 
1: invokevirtual #2; //Method Callee.doSomething:()V 
4: return 

public void doSame(Callee); 
Code: 
0: aload_1 
1: invokevirtual #2; //Method Callee.doSomething:()V 
4: return 

public void doSomethingElse(Callee); 
Code: 
0: aload_1 
1: invokevirtual #3; //Method Callee.doSomethingElse:()V 
4: return 

} 

Compiled from "Callee.java" 
public class Callee extends java.lang.Object{ 
public Callee(); 
Code: 
0: aload_0 
1: invokespecial #1; //Method java/lang/Object."<init>":()V 
4: return 

public void doSomething(); 
Code: 
0: return 

public void doSomethingElse(); 
Code: 
0: return 

} 

Trình biên dịch tạo ra một chữ ký phương pháp và một cuộc gọi typesafe invokevirtual cho phương pháp các cuộc gọi đến 'callee' - nó biết những gì lớp và những phương pháp đang được viện dẫn ở đây. Nếu lớp đó không có sẵn, trình biên dịch sẽ tạo ra chữ ký phương thức hoặc `invokevirtual 'như thế nào?

Có một JSR (JSR 292) để thêm một opcode 'invokedynamic' có thể hỗ trợ lời gọi động, tuy nhiên điều này hiện không được JVM hỗ trợ.