2009-12-24 38 views
13

Dưới đây là một Java generic mẫu:Java hàm tổng quát: làm thế nào để trả về kiểu Generic

public <T> T getResultData(Class<T> resultClass, other_args) { 
    ... 
    return resultClass.cast(T-thing); 
} 

Một cuộc gọi thông thường trông giống như:

DoubleBuffer buffer; 
    buffer = thing.getResultData(DoubleBuffer.class, args); 

Tôi chưa bao giờ có thể tìm ra cách để sử dụng mẫu này một cách sạch sẽ khi kiểu trả về mong muốn là chính nó. Để được 'cụ thể', nếu một chức năng như thế này muốn trả về Map<String,String> thì sao? Vì bạn không thể có được một đối tượng lớp cho một generic, tất nhiên, lựa chọn duy nhất là để vượt qua Map.class, và sau đó bạn cần một dàn diễn viên và @SuppressWarning sau khi tất cả.

Một kết thúc với một cuộc gọi như:

Map<String, String> returnedMap; 
returnedMap = thing.getResultData(Map.class, some_other_args); 

Bây giờ một là trở lại cần phôi và đàn áp một cảnh báo.

Tôi cho rằng người ta có thể lấy thứ gì đó từ gia đình java.lang.reflect.Type thay vì số Class, nhưng những thứ đó không đặc biệt dễ pha trộn. Có vẻ như:

class Dummy { 
Map<String, String> field; 
} 

... 

Type typeObject = Dummy.class.getField("field").getGenericType(); 

Với điều này, hàm được gọi có thể trích xuất loại cơ sở và sử dụng để đúc hoặc mớiCung cấp, nhưng trường kinh doanh giả mạo trông có vẻ xấu xí.

Lưu ý rằng các chức năng như thế này không phải lúc nào cũng gọi newInstance. Rõ ràng, nếu họ làm, họ không cần phải gọi resultClass.cast.

Trả lời

7

Bạn không thể làm điều đó trong API Java chuẩn. Tuy nhiên, có các lớp tiện ích có sẵn để có được Type trong một lớp chung. Ví dụ: Google Gson (chuyển đổi JSON thành Javabeans đầy đủ và ngược lại) có một lớp học TypeToken. Bạn có thể sử dụng nó như sau:

List<Person> persons = new Gson().fromJson(json, new TypeToken<List<Person>>() {}.getType()); 

Cấu trúc như vậy chính xác là những gì bạn cần. Bạn có thể tìm thấy here mã nguồn, bạn cũng có thể thấy nó hữu ích. Nó chỉ yêu cầu phương thức getResultData() bổ sung có thể chấp nhận Type.

1

Bạn có thể sử dụng TypeLiteral nếu bạn muốn sử dụng các loại vùng chứa an toàn. Chúng được sử dụng trong Guice để cung cấp các ràng buộc an toàn. Xem mã nguồn Guice để biết thêm ví dụ và TypeLiteral lớp học.

Bằng cách này, bạn không cần truyền, nếu bạn gọi số resultClass.newInstance() chẳng hạn.

public <T> T getResultData(Class<T> resultClass, other_args) {  
    ... 
    return resultClass.newInstance(); 
} 
+2

Ngoại trừ bạn không thể có được và do đó vượt qua thể hiện Class cho lớp chung như 'Bản đồ ' do đó điều này sẽ không giúp gì cả với câu hỏi của OP. – ChssPly76

+0

Yup. Đó chính xác là cách chúng tôi đến đây. – bmargulies

+0

Chúng không được hỗ trợ trong Ngôn ngữ nhưng bạn sử dụng TypeLiteral, được phát minh bởi Neal Gafter. http://gafter.blogspot.com/2006/12/type-literals.html –

1

Vì vậy, nếu tôi hiểu đúng những gì bạn thực sự cố gắng làm là (bất hợp pháp pseudo-cú pháp):

public <T, S, V> T<S, V> getResultData(Class<T<S, V>> resultClass, other_args) { 
    ... 
    return resultClass.cast(T-thing); 
} 

Bạn có thể không phải vì bạn không thể tiếp tục tham số chung loại T. Lộn xộn về với java.lang.reflect.* sẽ không giúp ích:

  1. Không có điều nào như Class<Map<String, String>> và do đó không có cách nào để tự động tạo ví dụ của nó.
  2. Không có cách nào để thực sự tuyên bố phương pháp trong câu hỏi như trở T<S, V>
+0

@ ChssPly76: Khi nó xảy ra, có một đối tượng GenericType cho Bản đồ . Nếu bạn có một trường kiểu Map và bạn gọi Field.getGenericType, bạn sẽ nhận được nó. Tuy nhiên, về cách duy nhất để có được một trong số đó là, thực sự, để có được nó từ sự phản chiếu trên một trường hoặc kiểu trả về. – bmargulies

+0

Ý bạn là 'ParameterizedType'.Có, có và có, bạn có thể lấy nó từ khai báo trường/phương thức. Và không, nó ** không ** giống như 'Class' và do đó ** sẽ không ** giúp bạn bằng bất cứ cách nào: bạn không thể bỏ qua nó và bạn không thể tạo một thể hiện của lớp tương ứng chỉ bằng' ParameterizedType'. – ChssPly76

+0

Vâng, thực sự, điều này là không thể như nó có vẻ. Hàm được gọi có thể gọi hàm getRawType() trên một ParameterizedType cho các mục đích newInstance/cast. Nó chỉ là xấu xí, nhưng có vẻ như đó là tốt như nó được. – bmargulies

1

Vâng ,. đây là một giải pháp xấu xí (một phần). Bạn không thể tham số chung một thông số chung, vì vậy bạn không thể viết

public <T<U,V>> T getResultData ... 

nhưng bạn có thể trả lại tập hợp tham số, ví dụ:cho một bộ

public < T extends Set<U>, U > T getResultData(Class<T> resultClass, Class<U> paramClass) throws IllegalAccessException, InstantiationException { 
     return resultClass.newInstance(); 
    } 

nơi bạn có thể loại bỏ U-param nếu nó tồn tại trong chữ ký lớp.

+0

Thú vị, và nó cũng sẽ hoạt động với hai tham số (bằng cách mở rộng 'Bản đồ'). Nhưng, thành thật mà nói, lựa chọn giữa điều này và '@ SuppressWarnings' tôi muốn sau này mỗi lần :-) – ChssPly76

+1

Tôi đồng ý - Tôi đã nói" xấu xí ";) –

1

Vâng tôi không nghĩ điều này là có thể.

Hãy tưởng tượng kịch bản này. Bạn có một tệp với một đối tượng được tuần tự hóa mà bạn muốn đọc nó. Nếu bạn thực hiện các chức năng chung chung, bạn sẽ có thể sử dụng nó mà không có một diễn viên. Nhưng hãy nói rằng bạn có một Bản đồ được tuần tự hóa thành một tệp.

Khi bạn deserialize nó, có không có cách nào để biết loại bản đồ chung ban đầu là gì. Theo như java là có liên quan, nó chỉ là một bản đồ

Tôi chạy vào điều này với danh sách, và nó đã gây phiền nhiễu. Nhưng những gì tôi thực sự đã kết thúc làm là tạo ra một "bộ sưu tập chuyển đổi" lớp chung mà có một bộ sưu tập và một "chuyển đổi" (mà có thể là một loại bên trong vô danh).

Sau đó, khi bạn lặp qua bộ sưu tập chuyển đổi, mọi mục sẽ được truyền. Và điều đó đã giải quyết được vấn đề cảnh báo. Đó là rất nhiều công việc chỉ để thoát khỏi cảnh báo, nhưng tôi không nghĩ rằng đó là lý do chính tôi đã đưa ra khuôn khổ đó. Bạn cũng có thể làm những việc mạnh mẽ hơn như lấy một bộ sưu tập tên tệp và sau đó viết trình chuyển đổi tải dữ liệu trong tệp hoặc thực hiện truy vấn hoặc bất kỳ điều gì.

1

Nếu bạn không thực sự cần loại generic chính xác của bản đồ và mã của bạn không phụ thuộc vào loại đó, nhưng bạn chỉ đơn giản là khó chịu bởi những lời cảnh báo bạn có thể viết:

Map<?, ?> returnedMap; 
returnedMap = thing.getResultData(Map.class, some_other_args); 

sau đó bạn có thể sử dụng bất kỳ phương thức bản đồ nào không được loại-parametrized như containsKey, clear, iterator và cứ như vậy hoặc lặp lại trên entrySet hoặc chuyển trả vềMap cho bất kỳ phương thức generic nào và mọi thứ sẽ được an toàn.

Map<?, ?> returnedMap; 
returnedMap = thing.getResultData(Map.class, some_other_args); 

Object myKey = ...; 

Object someValue = returnedMap.get(myKey); 

for (Iterator<? extends Map.Entry<?,?>> it = returnedMap.entrySet().iterator(); it.hasNext();) 
    if (it.next().equals(myKey)) 
    it.remove(); 

for (Map.Entry<?,?> e : returnedMap.entrySet()) { 
    if (e.getKey().equals(myKey)) 
    e.setValue(null); // if the map supports null values 
} 

doSomething(returnedMap); 

... 

<K,V> void doSomething(Map<K,V> map) { ... } 

thực tế có nhiều điều bạn có thể làm với Bản đồ một cách an toàn loại mà không biết khóa hoặc giá trị của nó.

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