2011-08-02 29 views
11

Tôi đã thêm tệp cấu hình có thể đọc được vào ứng dụng của mình bằng cách sử dụng java.util.Properties và đang cố gắng thêm trình bao bọc xung quanh nó để làm cho chuyển đổi loại dễ dàng hơn. Cụ thể, tôi muốn giá trị trả về "kế thừa" là loại giá trị mặc định được cung cấp. Đây là những gì tôi đã có cho đến nay:Làm thế nào tôi có thể khởi tạo một loại chung trong Java?

protected <T> T getProperty(String key, T fallback) { 
    String value = properties.getProperty(key); 

    if (value == null) { 
     return fallback; 
    } else { 
     return new T(value); 
    } 
} 

(Full example source.)

Giá trị trả về từ getProperty("foo", true) sau đó sẽ là một boolean bất kể nó được đọc từ các thuộc tính tập tin và tương tự cho các chuỗi, số nguyên, đôi, & c. Tất nhiên, đoạn mã trên không thực sự biên dịch:

PropertiesExample.java:35: unexpected type 
found : type parameter T 
required: class 
         return new T(value); 
           ^
1 error 

Tôi làm điều này sai, hay tôi chỉ đơn giản là cố gắng làm điều gì đó không thể làm được?

Edit: Cách sử dụng Ví dụ:

// I'm trying to simplify this... 
protected void func1() { 
    foobar = new Integer(properties.getProperty("foobar", "210")); 
    foobaz = new Boolean(properties.getProperty("foobaz", "true")); 
} 

// ...into this... 
protected void func2() { 
    foobar = getProperty("foobar", 210); 
    foobaz = getProperty("foobaz", true); 
} 

Trả lời

12

Do type erasure, bạn không thể khởi tạo các đối tượng chung. Thông thường, bạn có thể giữ một tham chiếu đến đối tượng Class đại diện cho loại đó và sử dụng nó để gọi newInstance(). Tuy nhiên, điều này chỉ hoạt động đối với hàm tạo mặc định. Kể từ khi bạn muốn sử dụng một constructor với các thông số, bạn sẽ cần phải tìm kiếm các đối tượng Constructor và sử dụng nó cho các instantiation:

protected <T> T getProperty(String key, T fallback, Class<T> clazz) { 
    String value = properties.getProperty(key); 

    if (value == null) { 
     return fallback; 
    } else { 

     //try getting Constructor 
     Constructor<T> constructor; 
     try { 
      constructor = clazz.getConstructor(new Class<?>[] { String.class }); 
     } 
     catch (NoSuchMethodException nsme) { 
      //handle constructor not being found 
     } 

     //try instantiating and returning 
     try { 
      return constructor.newInstance(value); 
     } 
     catch (InstantiationException ie) { 
      //handle InstantiationException 
     } 
     catch (IllegalAccessException iae) { 
      //handle IllegalAccessException 
     } 
     catch (InvocationTargetException ite) { 
      //handle InvocationTargetException 
     } 
    } 
} 

Tuy nhiên, khi nhìn thấy bao nhiêu rắc rối đó là để đạt được điều này, bao gồm cả chi phí hoạt động của việc sử dụng sự phản chiếu, nó đáng xem xét các cách tiếp cận khác trước tiên.

Nếu bạn hoàn toàn cần phải thực hiện lộ trình này, và nếu T được giới hạn trong một bộ riêng biệt của các loại được biết đến tại thời gian biên dịch, một thỏa hiệp sẽ được giữ một tĩnh Map của Constructor s, được nạp lúc khởi động - theo cách đó bạn không phải tự động tìm kiếm chúng ở mọi cuộc gọi đến phương pháp này. Ví dụ: Map<String, Constructor<?>> hoặc Map<Class<?>, Constructor<?>>, được điền bằng cách sử dụng static block.

+1

Ngay cả với một hàm tạo mặc định, tốt hơn là sử dụng đối tượng 'Constructor' hơn là sử dụng' Class.newInstance() '. Việc xử lý lỗi là khác nhau; sử dụng phương thức 'Lớp', một số ngoại lệ được báo cáo với một kiểu gây hiểu lầm. Phương thức 'Constructor' phù hợp với các lời gọi động khác. – erickson

+0

@Kublai Khan - Hoạt động! Thậm chí tốt hơn, tôi đã có thể nhận được 'klazz' là' Class klazz = (Class ) fallback.getClass(); 'để loại bỏ tham số phụ. Cám ơn rất nhiều về sự giúp đỡ của bạn! ** Edit: ** Tôi đã bắt đầu tự hỏi về bộ nhớ đệm ngay sau khi phản ánh bước vào hình ảnh; Tôi sẽ xem xét các khối tĩnh cho điều đó. –

+0

Vui vì tôi có thể giúp đỡ. Chỉ cần ghi nhớ rằng tất cả các tra cứu động bằng cách sử dụng phản ánh là khá tốn kém vì chúng không thể được tối ưu hóa tại thời gian biên dịch. –

1

Generics được thực hiện sử dụng loại tẩy xoá trong Java. Theo thuật ngữ tiếng Anh, hầu hết thông tin chung sẽ bị mất vào thời gian biên dịch và bạn không thể biết giá trị thực tế của T khi chạy. Điều này có nghĩa là bạn chỉ đơn giản là không thể instanciate loại chung.

Một giải pháp thay thế là để cung cấp lớp học của bạn với các loại trong thời gian chạy:

class Test<T> { 

    Class<T> klass; 

    Test(Class<T> klass) { 
     this.klass = klass; 
    } 

    public void test() { 
     klass.newInstance(); // With proper error handling 
    } 

} 

Edit: ví dụ mới gần gũi hơn với trường hợp của bạn

static <T> T getProperty(String key, T fallback, Class<T> klass) { 
    // ... 

    if (value == null) { 
     return fallback; 
    } 
    return (T) klass.newInstance(); // With proper error handling 
} 
+0

Tôi cần phải có một phiên bản duy nhất của lớp của tôi ('PropertiesExample', tại đây) có thể đọc các thuộc tính của các loại khác nhau từ cùng một tệp. Tôi sẽ thêm một ví dụ sử dụng cho câu hỏi của mình. :-) –

+0

Bit đầu tiên của mã không biên dịch. T trên constructor là mặt nạ đối số chung, và ở vị trí sai. Ngoài ra, kiểm tra ngoại lệ là cần thiết cho newInstance(). –

+0

Typo, đã được sửa. Kiểm tra ngoại lệ được ommited cho dễ đọc, do đó "Với xử lý lỗi đúng đắn" bình luận. –

-1

Hãy thử điều này:

protected <T> T getProperty(String key, T fallback) { 
    String value = properties.getProperty(key); 

    if (value == null) { 
     return fallback; 
    } else { 
     Class FallbackType = fallback.getClass(); 
     return (T)FallbackType.cast(value); 
    } 
} 
+0

Xem bài đăng đã chỉnh sửa. –

+0

Điều này là tốt hơn, nhưng bạn cần tạo một thể hiện mới thông qua sự phản chiếu, sử dụng giá trị làm đối số. Downvote không phải là từ tôi, bằng cách này; Tôi sẽ tự đề xuất phương pháp này, nhưng tôi đã không thể tìm ra cách để thực hiện nó một cách an toàn. Cố gắng loại bỏ các diễn viên không an toàn để 'T'. – erickson

1

Đây là điều mà bạn không thể làm.

Vì loại tẩy xóa, loại T, trong khi được biết tại thời gian biên dịch, không có sẵn cho JVM tại thời gian chạy.

Đối với vấn đề cụ thể của bạn, tôi nghĩ rằng giải pháp hợp lý nhất là để tự viết mã cho từng loại khác nhau:

protected String getProperty(String key, String fallback) { ... return new String(value); } 
protected Double getProperty(String key, Double fallback) { ... return new Double(value); } 
protected Boolean getProperty(String key, Boolean fallback) { ... return new Boolean(value); } 
protected Integer getProperty(String key, Integer fallback) { ... return new Integer(value); } 

Ghi chú:

  • Trong các tiêu chuẩn API Java, bạn sẽ tìm thấy nhiều nơi có một tập các phương thức liên quan chỉ khác nhau theo kiểu đầu vào.
  • Trong C++, có lẽ bạn có thể được giải quyết bằng cách mẫu. Nhưng C++ giới thiệu nhiều vấn đề khác ...
0

Nếu bạn muốn giữ chữ ký phương thức hiện tại của bạn làm theo cách này.

import java.lang.reflect.InvocationTargetException; 
import java.util.Properties; 

public class Main 
{ 
    private final Properties properties; 

    public Main() 
    { 
     this.properties = new Properties(); 
     this.properties.setProperty("int", "1"); 
     this.properties.setProperty("double", "1.1"); 
    } 

    public <T> T getProperty(final String key, final T fallback) 
    { 
     final String value = this.properties.getProperty(key); 
     if (value == null) 
     { 
      return fallback; 
     } 
     else 
     { 
      try 
      { 
       return (T) fallback.getClass().getConstructor(new Class<?>[] { String.class }).newInstance(value); 
      } 
      catch (final InstantiationException e) 
      { 
       throw new RuntimeException(e); 
      } 
      catch (final IllegalAccessException e) 
      { 
       throw new RuntimeException(e); 
      } 
      catch (final InvocationTargetException e) 
      { 
       throw new RuntimeException(e); 
      } 
      catch (final NoSuchMethodException e) 
      { 
       throw new RuntimeException(e); 
      } 
     } 
    } 


    public static void main(final String[] args) 
    { 
     final Main m = new Main(); 
     final Integer i = m.getProperty("int", new Integer("0")); 
     final Double d = m.getProperty("double", new Double("0")); 
     System.out.println(i); 
     System.out.println(d); 
    } 
} 
Các vấn đề liên quan