2009-12-21 38 views
8

Tôi đang viết một thư viện cần phải có một số mã nếu một thư viện cụ thể được bao gồm. Vì mã này nằm rải rác xung quanh dự án, sẽ rất tuyệt nếu người dùng không phải tự nhận xét/bỏ ghi chú mọi thứ.Tương đương với #define trong Java?

Trong C, điều này sẽ đủ dễ dàng với một tiêu đề #define trong tiêu đề và sau đó là các khối mã được bao quanh với #ifdefs. Tất nhiên, Java không có bộ tiền xử lý C ...

Để làm rõ - một số thư viện bên ngoài sẽ được phân phối cùng với tôi. Tôi không muốn bao gồm tất cả để giảm thiểu kích thước thực thi của tôi. Nếu một nhà phát triển không bao gồm một thư viện, tôi cần để có thể sử dụng nó, và nếu không, sau đó nó chỉ có thể được bỏ qua.

Cách tốt nhất để làm điều này trong Java là gì?

Trả lời

6

Như người khác đã nói, không có điều nào như # define/# ifdef trong Java. Nhưng liên quan đến vấn đề của bạn có thư viện bên ngoài tùy chọn, mà bạn sẽ sử dụng, nếu có, và không sử dụng nếu không, sử dụng các lớp proxy có thể là một tùy chọn (nếu giao diện thư viện không quá lớn).

Tôi phải thực hiện việc này một lần cho các tiện ích mở rộng cụ thể của Mac OS X cho AWT/Swing (được tìm thấy trong com.apple.eawt. *). Các lớp học, tất nhiên, chỉ trên đường dẫn lớp nếu ứng dụng đang chạy trên Mac OS. Để có thể sử dụng chúng nhưng vẫn cho phép ứng dụng tương tự được sử dụng trên các nền tảng khác, tôi đã viết các lớp proxy đơn giản, chỉ cung cấp các phương thức tương tự như các lớp EAWT gốc. Bên trong, các proxy sử dụng một số phản ánh để xác định xem các lớp thực sự có trên đường dẫn lớp và sẽ truyền qua tất cả các cuộc gọi phương thức hay không. Bằng cách sử dụng lớp java.lang.reflect.Proxy, bạn thậm chí có thể tạo và truyền xung quanh các đối tượng thuộc loại được xác định trong thư viện bên ngoài mà không cần phải có sẵn tại thời gian biên dịch.

Ví dụ, proxy cho com.apple.eawt.ApplicationListener trông như thế này:

public class ApplicationListener { 

    private static Class<?> nativeClass; 

    static Class<?> getNativeClass() { 
     try { 
      if (ApplicationListener.nativeClass == null) { 
       ApplicationListener.nativeClass = Class.forName("com.apple.eawt.ApplicationListener"); 
      } 

      return ApplicationListener.nativeClass; 
     } catch (ClassNotFoundException ex) { 
      throw new RuntimeException("This system does not support the Apple EAWT!", ex); 
     } 
    } 

    private Object nativeObject; 

    public ApplicationListener() { 
     Class<?> nativeClass = ApplicationListener.getNativeClass(); 

     this.nativeObject = Proxy.newProxyInstance(nativeClass.getClassLoader(), new Class<?>[] { 
      nativeClass 
     }, new InvocationHandler() { 

      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
       String methodName = method.getName(); 

       ApplicationEvent event = new ApplicationEvent(args[0]); 

       if (methodName.equals("handleReOpenApplication")) { 
        ApplicationListener.this.handleReOpenApplication(event); 
       } else if (methodName.equals("handleQuit")) { 
        ApplicationListener.this.handleQuit(event); 
       } else if (methodName.equals("handlePrintFile")) { 
        ApplicationListener.this.handlePrintFile(event); 
       } else if (methodName.equals("handlePreferences")) { 
        ApplicationListener.this.handlePreferences(event); 
       } else if (methodName.equals("handleOpenFile")) { 
        ApplicationListener.this.handleOpenFile(event); 
       } else if (methodName.equals("handleOpenApplication")) { 
        ApplicationListener.this.handleOpenApplication(event); 
       } else if (methodName.equals("handleAbout")) { 
        ApplicationListener.this.handleAbout(event); 
       } 

       return null; 
      } 

     }); 
    } 

    Object getNativeObject() { 
     return this.nativeObject; 
    } 

    // followed by abstract definitions of all handle...(ApplicationEvent) methods 

} 

Tất cả điều này chỉ có ý nghĩa, nếu bạn cần chỉ là một vài lớp học từ một thư viện bên ngoài, bởi vì bạn phải làm mọi thứ thông qua sự phản chiếu trong thời gian chạy. Đối với các thư viện lớn hơn, có thể bạn sẽ cần một số cách để tự động hóa việc tạo các proxy. Nhưng sau đó, nếu bạn thực sự là phụ thuộc vào một thư viện bên ngoài lớn, bạn chỉ nên yêu cầu nó tại thời gian biên dịch.

Comment by Peter Lawrey: (Xin lỗi để chỉnh sửa, rất khó để đưa mã của nó vào một bình luận)

Các theo gương là chung chung bằng phương pháp do đó bạn không cần phải biết tất cả các phương pháp có liên quan. Bạn cũng có thể làm cho lớp chung này theo lớp, do đó bạn chỉ cần một lớp InvocationHandler được mã hóa để bao gồm tất cả các trường hợp.

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
    String methodName = method.getName(); 
    ApplicationEvent event = new ApplicationEvent(args[0]); 
    Method method = ApplicationListener.class.getMethod(methodName, ApplicationEvent.class); 
    return method.invoke(ApplicationListener.this, event); 
} 
2

Sử dụng một constant:

Tuần này chúng tôi tạo ra một số hằng rằng có tất cả những lợi ích của việc sử dụng cơ sở vật chất C preprocessor để xác định hằng số thời gian biên dịch và đang có điều kiện biên soạn.

Java đã loại bỏ toàn bộ khái niệm về bộ tiền xử lý văn bản (nếu bạn coi Java là "hậu duệ" của C/C++). Tuy nhiên, chúng tôi có thể nhận được các lợi ích tốt nhất của của ít nhất một số tính năng tiền xử lý của C trong Java: hằng số và biên dịch có điều kiện.

+0

Tôi không nghĩ rằng đây là những gì anh ta đang tìm kiếm. Anh ta không muốn các hằng số tượng trưng, ​​nhưng tương đương với việc biên dịch có điều kiện. Không biết java, tôi không có một đầu mối, mặc dù ... – dmckee

+0

những gì về trình biên dịch có điều kiện? Làm thế nào để bạn làm điều đó trong java? – Russell

+0

Bài viết tôi đã liên kết thảo luận về vấn đề này. –

0

Sử dụng thuộc tính để thực hiện loại điều này.

Sử dụng những thứ như Class.forName để xác định lớp học.

Không sử dụng câu lệnh if khi bạn có thể dịch trực tiếp một cách tầm thường trực tiếp đến một lớp học.

11

Không có cách nào để thực hiện những gì bạn muốn từ bên trong Java. Bạn có thể tiền xử lý các tệp nguồn Java, nhưng nó nằm ngoài phạm vi của Java.

Bạn có thể không trừu tượng hóa sự khác biệt và sau đó thay đổi triển khai không?

Dựa trên làm rõ, có vẻ như bạn có thể tạo phương thức nhà máy sẽ trả về đối tượng từ một trong các thư viện bên ngoài hoặc lớp "sơ khai" có chức năng sẽ thực hiện những gì bạn đã làm trong "không có sẵn" có điều kiện mã.

+0

+1: Sự cố này có vẻ như có thể (có thể) có thể sửa đổi thiết kế kiến ​​trúc giải pháp để trợ giúp việc này. – Russell

+0

Trình biên dịch Java sẽ tối ưu hóa các kiểm tra có điều kiện dựa trên các giá trị không đổi. Vì vậy, nó chắc chắn có thể. –

+0

@Anon: Nhưng nó vẫn biên dịch cả hai nhánh. nó chỉ tối ưu hóa mã không sử dụng, phải không? Ngoài ra, rất nhiều thời gian, tái cấu trúc kiến ​​trúc của bạn để loại bỏ những vấn đề này có thể nhận được thực sự lộn xộn. Nếu bạn có một loạt các địa điểm ngẫu nhiên mà bạn muốn #ifdefed ... bạn kết thúc với một lớp cơ sở, sau đó là một loạt các lớp con, với các hàm trợ giúp, và nó chỉ trở thành một mớ hỗn độn. –

1

Tôi không tin rằng có thực sự là một điều như vậy. Hầu hết người dùng Java thực sự sẽ cho bạn biết rằng đây là một điều tốt, và dựa vào việc biên dịch có điều kiện nên tránh ở hầu hết mọi chi phí.

Tôi không thực sự đồng ý với họ ...

Bạn có thể dùng hằng có thể được xác định từ đường biên dịch, và điều đó sẽ có một số tác dụng, nhưng không thực sự tất cả. (Ví dụ, bạn không thể có những thứ không biên dịch, nhưng bạn vẫn muốn, bên trong #if 0 ... (và không, bình luận không phải lúc nào cũng giải quyết vấn đề đó, bởi vì nhận xét lồng nhau có thể phức tạp ...)).

Tôi nghĩ rằng hầu hết mọi người sẽ cho bạn biết sử dụng một số hình thức thừa kế để làm điều này, nhưng điều đó có thể rất xấu xí là tốt, với rất nhiều mã lặp đi lặp lại ...

Điều đó nói rằng, bạn luôn có thể chỉ thiết lập IDE của bạn để ném java của bạn thông qua trước khi xử lý trước khi gửi nó cho javac ...

5

trong Java người ta có thể sử dụng nhiều phương pháp để đạt được kết quả tương tự:

Cách Java là đưa hành vi khác nhau vào một tập các lớp riêng biệt trừu tượng thông qua một giao diện, sau đó cắm lớp cần thiết tại thời gian chạy. Xem thêm:

+0

+1 cho Dependency Injection. Hãy tưởng tượng nối dây lên các đối tượng của bạn thông qua một tệp XML (như một ví dụ) và sau đó sử dụng một tệp XML khác nhau cho các tình huống khác nhau của bạn. –

1

"để giảm thiểu kích thước thực thi của tôi"

Ý anh là gì bằng cách "kích thước thực thi" ?

Nếu bạn có nghĩa là số lượng mã được tải trong thời gian chạy, thì bạn có thể tải các lớp học một cách có điều kiện thông qua trình nạp lớp. Vì vậy, bạn phân phối mã thay thế của bạn không có vấn đề gì, nhưng nó chỉ thực sự được tải nếu thư viện mà nó đứng ở cho là mất tích. Bạn có thể sử dụng một Adapter (hoặc tương tự) để đóng gói API, để đảm bảo rằng hầu như tất cả các mã của bạn là chính xác giống nhau một trong hai cách, và một trong hai lớp wrapper được nạp theo trường hợp của bạn. SPI bảo mật Java có thể cung cấp cho bạn một số ý tưởng về cách cấu trúc và triển khai thực hiện điều này.

Nếu bạn có nghĩa là kích thước tệp .jar, thì bạn có thể làm như trên, nhưng hãy nói cho các nhà phát triển cách loại bỏ các lớp không cần thiết ra khỏi bình, trong trường hợp họ biết chúng sẽ không cần thiết.

+0

Thư viện sẽ được sử dụng trong các ứng dụng Android, vì vậy tôi không muốn bao gồm bất kỳ thứ gì không cần thiết. – Justin

+0

Điều này không đáp ứng các thông số của câu hỏi ban đầu của bạn. Nếu bạn đang liên kết tại thời gian chạy, bạn vẫn phải có mã gọi thư viện được biên dịch để #ifdef không giúp được gì. Nếu bạn không liên kết trong thời gian chạy nhưng biên dịch cho các mục tiêu khác nhau, thì nếu (CONSTANT) sẽ đủ. Cái nào là vấn đề thực tế? –

+0

Không, hằng số sẽ không đủ. Ví dụ, 'if (false) {LibraryIDontHave.action();}' sẽ không biên dịch. – Justin

4

Vâng, cú pháp Java đủ gần với C mà bạn có thể đơn giản sử dụng bộ tiền xử lý C, thường được vận chuyển dưới dạng tệp thực thi riêng biệt.

Nhưng Java không thực sự làm mọi thứ vào thời gian biên dịch. Cách tôi đã xử lý các tình huống tương tự trước đây là với sự phản ánh. Trong trường hợp của bạn, vì các cuộc gọi của bạn đến thư viện có thể không tồn tại nằm rải rác trong toàn bộ mã, tôi sẽ tạo một lớp bao bọc, thay thế tất cả các cuộc gọi đến thư viện bằng các cuộc gọi đến lớp trình bao bọc và sau đó sử dụng sự phản chiếu bên trong lớp trình bao bọc để gọi trên thư viện nếu nó có mặt.

0

Tùy thuộc vào những gì bạn đang làm (không hoàn toàn đầy đủ thông tin), bạn có thể làm một cái gì đó như thế này:

interface Foo 
{ 
    void foo(); 
} 

class FakeFoo 
    implements Foo 
{ 
    public void foo() 
    { 
     // do nothing 
    } 
} 

class RealFoo 
{ 
    public void foo() 
    { 
     // do something 
    } 
} 

và sau đó cung cấp một lớp trừu tượng instantiation:

class FooFactory 
{ 
    public static Foo makeFoo() 
    { 
     final String name; 
     final FooClass fooClass; 
     final Foo  foo; 

     name  = System.getProperty("foo.class"); 
     fooClass = Class.forName(name); 
     foo  = (Foo)fooClass.newInstance(); 

     return (foo); 
    } 
} 

Sau đó chạy java với -Dfoo.name = RealFoo | FakeFoo

Bỏ qua xử lý ngoại lệ i n phương thức makeFoo và bạn có thể làm theo cách khác ... nhưng ý tưởng là như nhau.

Bằng cách đó bạn biên dịch cả hai phiên bản của các lớp con Foo và cho phép nhà phát triển chọn thời gian chạy mà họ muốn sử dụng.

0

Tôi thấy bạn chỉ định hai vấn đề loại trừ lẫn nhau ở đây (hoặc, nhiều khả năng, bạn đã chọn một và tôi chỉ không hiểu lựa chọn nào bạn đã thực hiện).

Bạn phải lựa chọn: Bạn có đang vận chuyển hai phiên bản mã nguồn của bạn (một nếu thư viện tồn tại và một thư viện nếu không) hoặc bạn đang gửi một phiên bản và mong muốn nó hoạt động với thư viện nếu thư viện tồn tại.

Nếu bạn muốn có một phiên bản duy nhất để phát hiện sự tồn tại của thư viện và sử dụng nó nếu có, thì bạn PHẢI có tất cả mã để truy cập nó trong mã được phân phối của mình - bạn không thể cắt nó ra. Vì bạn đang cân bằng vấn đề của mình bằng cách sử dụng #define, tôi cho rằng đây không phải là mục tiêu của bạn - bạn muốn gửi 2 phiên bản (Cách duy nhất #define có thể hoạt động)

Vì vậy, với 2 phiên bản bạn có thể xác định thư viện . Điều này có thể là một đối tượng kết thúc tốt đẹp thư viện của bạn và chuyển tiếp tất cả các cuộc gọi đến thư viện cho bạn hoặc một giao diện - trong cả hai trường hợp đối tượng này PHẢI tồn tại ở thời gian biên dịch cho cả hai chế độ.

public LibraryInterface getLibrary() 
{ 
    if(LIBRARY_EXISTS) // final boolean 
    { 
     // Instantiate your wrapper class or reflectively create an instance    
     return library; 
    } 
    return null; 
} 

Bây giờ, khi bạn muốn sử dụng thư viện của bạn (trường hợp bạn sẽ có một #ifdef trong C), bạn có điều này:

if(LIBRARY_EXISTS) 
    library.doFunc() 

Thư viện là một giao diện tồn tại trong cả hai trường hợp. Vì nó luôn được bảo vệ bởi LIBRARY_EXISTS, nó sẽ biên dịch (thậm chí không bao giờ nên nạp vào trình nạp lớp của bạn - nhưng đó là việc thực thi phụ thuộc).

Nếu thư viện của bạn là thư viện được đóng gói sẵn do bên thứ ba cung cấp, bạn có thể phải tạo cho Thư viện một lớp trình bao bọc chuyển tiếp cuộc gọi đến thư viện của bạn. Vì trình bao bọc thư viện của bạn không bao giờ được khởi tạo nếu LIBRARY_EXISTS là sai, nó thậm chí không được nạp khi chạy (Heck, nó thậm chí không được biên dịch nếu JVM đủ thông minh vì nó luôn được bảo vệ bởi một hằng số cuối cùng). rằng trình bao bọc PHẢI có sẵn tại thời gian biên dịch trong cả hai trường hợp.

0

Tôi có một cách tốt nhất để nói.

Điều bạn cần là biến cuối cùng.

public static final boolean LibraryIncluded= false; //or true - manually set this 

Sau đó bên trong mã nói như

if(LibraryIncluded){ 
    //do what you want to do if library is included 
} 
else 
{ 
    //do if you want anything to do if the library is not included 
} 

này sẽ làm việc như #ifdef. Bất kỳ một trong các khối sẽ có mặt trong mã thực thi. Người khác sẽ bị loại bỏ trong thời gian biên dịch chính nó

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