Sự khác biệt giữa 'E', 'T' và '?' cho Java chung chung?


261

Tôi bắt gặp mã Java như thế này:

public interface Foo<E> {}

public interface Bar<T> {}

public interface Zar<?> {}

Sự khác biệt giữa cả ba điều trên và họ gọi loại khai báo lớp hoặc giao diện này trong Java là gì?


1
Tôi nghi ngờ có sự khác biệt. Tôi đoán nó chỉ là một tên cho tham số loại đó. Và cái cuối cùng có hợp lệ không?
CodeInChaos

Câu trả lời:


231

Chà, không có sự khác biệt giữa hai cái đầu tiên - chúng chỉ sử dụng các tên khác nhau cho tham số loại ( Ehoặc T).

Thứ ba không phải là một khai báo hợp lệ - ?được sử dụng làm ký tự đại diện được sử dụng khi cung cấp một đối số loại , ví dụ: List<?> foo = ...có nghĩa là foođề cập đến một danh sách một số loại, nhưng chúng ta không biết gì.

Tất cả điều này là khái quát , đó là một chủ đề khá lớn. Bạn có thể muốn tìm hiểu về nó thông qua các tài nguyên sau, mặc dù tất nhiên có sẵn nhiều hơn:


1
Có vẻ như liên kết đến PDF bị hỏng. Tôi đã tìm thấy những gì dường như là một bản sao ở đây , nhưng tôi không thể chắc chắn 100% vì tôi không biết bản gốc trông như thế nào.
Giăng

2
@ John: Yup, đó là một. Sẽ chỉnh sửa một liên kết trong, cho dù đó là một hay một ...
Jon Skeet

Có gì khác ngoài T, E và không? dùng trong thuốc generic? Nếu vậy chúng là gì và chúng có nghĩa là gì?
sofs1

1
@ sofs1: Không có gì đặc biệt TE- chúng chỉ là định danh. Bạn có thể viết KeyValuePair<K, V>ví dụ. ?có ý nghĩa đặc biệt mặc dù.
Jon Skeet

215

Đó là quy ước hơn bất cứ điều gì khác.

  • T có nghĩa là một loại
  • Ecó nghĩa là một yếu tố ( List<E>: danh sách các yếu tố)
  • Klà khóa (trong một Map<K,V>)
  • V là Giá trị (dưới dạng giá trị trả về hoặc giá trị được ánh xạ)

Chúng hoàn toàn có thể hoán đổi cho nhau (xung đột trong cùng một tuyên bố mặc dù).


20
Chữ cái giữa <> chỉ là một cái tên. Những gì bạn mô tả trong câu trả lời của bạn chỉ là quy ước. Nó thậm chí không phải là một lá thư viết thường; bạn có thể sử dụng bất kỳ tên nào bạn thích, giống như bạn có thể đưa ra các lớp, biến, v.v. bất kỳ tên nào bạn thích.
Jesper

Một mô tả chi tiết và rõ ràng hơn có sẵn trong bài viết này oracle.com/technetwork/articles/java/iêu
fgul

6
Bạn đã không giải thích cho các dấu hỏi. Bị hạ bệ.
shinzou

129

Các câu trả lời trước giải thích các tham số loại (T, E, v.v.), nhưng không giải thích ký tự đại diện, "?" Hoặc sự khác biệt giữa chúng, vì vậy tôi sẽ giải quyết điều đó.

Đầu tiên, chỉ cần rõ ràng: các tham số ký tự đại diện và loại không giống nhau. Trong đó các tham số loại xác định một loại biến (ví dụ: T) đại diện cho loại cho phạm vi, ký tự đại diện không: ký tự đại diện chỉ xác định một tập hợp các loại cho phép mà bạn có thể sử dụng cho loại chung. Không có bất kỳ giới hạn ( extendshoặc super) nào, ký tự đại diện có nghĩa là "sử dụng bất kỳ loại nào ở đây".

Ký tự đại diện luôn nằm giữa các dấu ngoặc nhọn và nó chỉ có ý nghĩa trong ngữ cảnh của một loại chung:

public void foo(List<?> listOfAnyType) {...}  // pass a List of any type

không bao giờ

public <?> ? bar(? someType) {...}  // error. Must use type params here

hoặc là

public class MyGeneric ? {      // error
    public ? getFoo() { ... }   // error
    ...
}

Nó trở nên khó hiểu hơn khi chúng chồng lên nhau. Ví dụ:

List<T> fooList;  // A list which will be of type T, when T is chosen.
                  // Requires T was defined above in this scope
List<?> barList;  // A list of some type, decided elsewhere. You can do
                  // this anywhere, no T required.

Có rất nhiều sự trùng lặp về những gì có thể với các định nghĩa phương thức. Sau đây là, về mặt chức năng, giống hệt nhau:

public <T> void foo(List<T> listOfT) {...}
public void bar(List<?> listOfSomething)  {...}

Vì vậy, nếu có sự chồng chéo, tại sao sử dụng cái này hay cái kia? Đôi khi, đó thực sự chỉ là phong cách: một số người nói rằng nếu bạn không cần một loại param, bạn nên sử dụng ký tự đại diện chỉ để làm cho mã đơn giản hơn / dễ đọc hơn. Một sự khác biệt chính mà tôi đã giải thích ở trên: params type xác định một biến loại (ví dụ: T) mà bạn có thể sử dụng ở nơi khác trong phạm vi; ký tự đại diện không. Mặt khác, có hai sự khác biệt lớn giữa các thông số loại và ký tự đại diện:

Loại params có thể có nhiều lớp giới hạn; ký tự đại diện không thể:

public class Foo <T extends Comparable<T> & Cloneable> {...}

Ký tự đại diện có thể có giới hạn thấp hơn; loại params không thể:

public void bar(List<? super Integer> list) {...}

Trong phần trên, List<? super Integer>định nghĩa Integerlà giới hạn dưới của ký tự đại diện, có nghĩa là loại Danh sách phải là Số nguyên hoặc siêu loại Số nguyên. Giới hạn loại chung là vượt quá những gì tôi muốn đề cập chi tiết. Nói tóm lại, nó cho phép bạn xác định loại chung có thể là loại nào. Điều này làm cho nó có thể điều trị tổng quát đa hình. Ví dụ:

public void foo(List<? extends Number> numbers) {...}

Bạn có thể vượt qua một List<Integer>, List<Float>, List<Byte>, vv cho numbers. Không có giới hạn loại, điều này sẽ không hoạt động - đó chỉ là cách tạo ra thuốc generic.

Cuối cùng, đây là một định nghĩa phương thức sử dụng ký tự đại diện để làm điều gì đó mà tôi không nghĩ rằng bạn có thể làm bất kỳ cách nào khác:

public static <T extends Number> void adder(T elem, List<? super Number> numberSuper) {
    numberSuper.add(elem);
}

numberSupercó thể là Danh sách số hoặc bất kỳ siêu số nào (ví dụ List<Object>:) và elemphải là Số hoặc bất kỳ loại phụ nào. Với tất cả các giới hạn, trình biên dịch có thể chắc chắn rằng đó .add()là loại an toàn.


"công khai void foo (Danh sách <? số mở rộng> số) {...}" có nên "mở rộng" thành "siêu" không?
1a1a11a

1
Không. Điểm của ví dụ đó là hiển thị chữ ký hỗ trợ đa dạng Danh sách số và các loại phụ của Số. Đối với điều này, bạn sử dụng "mở rộng". Tức là, "đưa cho tôi Danh sách số hoặc bất cứ thứ gì mở rộng Số" (Danh sách <Số nguyên>, Danh sách <Float>, bất cứ điều gì). Một phương thức như thế này sau đó có thể lặp qua danh sách và, đối với mỗi phần tử, "e", thực thi, ví dụ: e.floatValue (). Không quan trọng loại phụ (số mở rộng) của Số bạn vượt qua - bạn sẽ luôn có thể ".floatValue ()", bởi vì .floatValue () là một phương thức của Số.
Hawkeye Parker

Trong ví dụ cuối cùng của bạn, "Danh sách <? Super Number>" có thể chỉ đơn giản là "Danh sách <Số>" vì phương thức này không cho phép bất cứ điều gì chung chung hơn.
jessarah

@jessarah không. Có thể ví dụ của tôi không rõ ràng, nhưng tôi đề cập đến trong ví dụ rằng adder () có thể lấy Danh sách <Object> (Object là siêu lớp của Số). Nếu bạn muốn nó có thể làm điều này, nó phải có chữ ký "Danh sách <? Super Number>". Đó chính xác là điểm "siêu" ở đây.
Hawkeye Parker

2
Câu trả lời này rất rất tốt trong việc giải thích sự khác biệt giữa các ký tự đại diện và các tham số loại, nên có một câu hỏi dành riêng với câu trả lời này. Gần đây tôi sẽ đi sâu hơn về khái quát và câu trả lời này đã giúp tôi rất nhiều trong việc sắp xếp mọi thứ lại với nhau, rất nhiều thông tin chính xác trong thời gian ngắn, cảm ơn!
Testo Testini

27

Một biến loại, <T>, có thể là bất kỳ loại không nguyên thủy nào bạn chỉ định: bất kỳ loại lớp, bất kỳ loại giao diện, bất kỳ loại mảng hoặc thậm chí một biến loại khác.

Các tên tham số loại thường được sử dụng là:

  • Phần tử điện tử (được sử dụng rộng rãi bởi Khung sưu tập Java)
  • K - Chìa khóa
  • N - Số
  • T - Loại
  • V - Giá trị

Trong Java 7, nó được phép khởi tạo như thế này:

Foo<String, Integer> foo = new Foo<>(); // Java 7
Foo<String, Integer> foo = new Foo<String, Integer>(); // Java 6

3

Các tên tham số loại thường được sử dụng là:

E - Element (used extensively by the Java Collections Framework)
K - Key
N - Number
T - Type
V - Value
S,U,V etc. - 2nd, 3rd, 4th types

Bạn sẽ thấy những tên này được sử dụng trong suốt API Java SE


2

trình biên dịch sẽ tạo một bản chụp cho mỗi ký tự đại diện (ví dụ: dấu hỏi trong Danh sách) khi nó tạo thành một hàm như:

foo(List<?> list) {
    list.put(list.get()) // ERROR: capture and Object are not identical type.
}

Tuy nhiên, một kiểu chung như V sẽ ổn và biến nó thành một phương thức chung :

<V>void foo(List<V> list) {
    list.put(list.get())
}
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.