Làm cách nào để sử dụng Lớp <T> trong Java?


247

Có một cuộc thảo luận tốt về Generics và những gì họ thực sự làm đằng sau câu hỏi này , vì vậy tất cả chúng ta đều biết đó Vector<int[]>là một vectơ của các mảng số nguyên vàHashTable<String, Person> là một bảng có các khóa là chuỗi và giá trị Persons. Tuy nhiên, những gì làm tôi bối rối là việc sử dụng Class<>.

Lớp java Classđược cho là cũng lấy một tên mẫu, (hoặc vì vậy tôi được nói bởi phần gạch chân màu vàng trong nhật thực). Tôi không hiểu những gì tôi nên đặt trong đó. Toàn bộ quan điểm của Classđối tượng là khi bạn không có đầy đủ thông tin về một đối tượng, để phản ánh và như vậy. Tại sao nó làm cho tôi chỉ định lớp nào Classđối tượng sẽ giữ? Tôi rõ ràng không biết, hoặc tôi sẽ không sử dụng Classđối tượng, tôi sẽ sử dụng đối tượng cụ thể.

Câu trả lời:


136

Sử dụng phiên bản chung của Class Class cho phép bạn, trong số những thứ khác, viết những thứ như

Class<? extends Collection> someCollectionClass = someMethod();

và sau đó bạn có thể chắc chắn rằng đối tượng Class mà bạn nhận được mở rộng Collectionvà một thể hiện của lớp này sẽ (ít nhất là) một Bộ sưu tập.


183

Tất cả những gì chúng ta biết là " Tất cả các thể hiện của một lớp bất kỳ đều có chung đối tượng java.lang.Class của loại lớp đó "

ví dụ)

Student a = new Student();
Student b = new Student();

Đó a.getClass() == b.getClass()là sự thật.

Bây giờ giả sử

Teacher t = new Teacher();

không có thuốc generic dưới đây là có thể.

Class studentClassRef = t.getClass();

Nhưng điều này là sai bây giờ ..?

ví dụ) public void printStudentClassInfo(Class studentClassRef) {}có thể được gọi vớiTeacher.class

Điều này có thể tránh được bằng cách sử dụng thuốc generic.

Class<Student> studentClassRef = t.getClass(); //Compilation error.

Bây giờ T là gì ?? T là tham số kiểu (còn gọi là biến kiểu); được phân cách bằng dấu ngoặc nhọn (<>), theo tên lớp.
T chỉ là một ký hiệu, giống như một tên biến (có thể là bất kỳ tên nào) được khai báo trong khi viết tệp lớp. Sau đó, T sẽ được thay thế bằng
tên Class hợp lệ trong quá trình khởi tạo (HashMap<String> map = new HashMap<String>(); )

ví dụ) class name<T1, T2, ..., Tn>

Vì vậy, Class<T>đại diện cho một đối tượng lớp của loại lớp cụ thể 'T '.

Giả sử rằng các phương thức lớp của bạn phải làm việc với các tham số loại không xác định như dưới đây

/**
 * Generic version of the Car class.
 * @param <T> the type of the value
 */
public class Car<T> {
    // T stands for "Type"
    private T t;

    public void set(T t) { this.t = t; }
    public T get() { return t; }
}

Ở đây T có thể được sử dụng như là Stringloại CarName

HOẶC T có thể được sử dụng làm Integerkiểu như modelNumber ,

HOẶC T có thể được sử dụng như là Objectloại ví dụ xe hợp lệ .

Bây giờ ở đây ở trên là POJO đơn giản có thể được sử dụng khác nhau trong thời gian chạy.
Các bộ sưu tập, ví dụ) List, Set, Hashmap là những ví dụ tốt nhất sẽ hoạt động với các đối tượng khác nhau theo khai báo của T, nhưng một khi chúng ta đã khai báo T là String,
ví dụ) HashMap<String> map = new HashMap<String>();Sau đó, nó sẽ chỉ chấp nhận các đối tượng thể hiện của String String.

Phương pháp chung

Các phương thức chung là các phương thức giới thiệu các tham số loại riêng của chúng. Điều này tương tự như khai báo một kiểu chung, nhưng phạm vi của tham số loại được giới hạn trong phương thức mà nó được khai báo. Các phương thức chung tĩnh và không tĩnh được cho phép, cũng như các hàm tạo lớp chung.

Cú pháp cho một phương thức chung bao gồm một tham số kiểu, bên trong dấu ngoặc nhọn và xuất hiện trước kiểu trả về của phương thức. Đối với các phương thức chung, phần tham số loại phải xuất hiện trước kiểu trả về của phương thức.

 class Util {
    // Generic static method
    public static <K, V, Z, Y> boolean compare(Pair<K, V> p1, Pair<Z, Y> p2) {
        return p1.getKey().equals(p2.getKey()) &&
               p1.getValue().equals(p2.getValue());
    }
}

 class Pair<K, V> {

    private K key;
    private V value;
}

Dưới đây <K, V, Z, Y>là khai báo các kiểu được sử dụng trong các đối số phương thức nên trước kiểu trả vềboolean ở đây.

Ở phía dưới; khai báo kiểu <T>không bắt buộc ở cấp phương thức, vì nó đã được khai báo ở cấp lớp.

class MyClass<T> {
   private  T myMethod(T a){
       return  a;
   }
}

Nhưng bên dưới là sai vì các tham số loại cấp K, V, Z và Y không thể được sử dụng trong ngữ cảnh tĩnh (phương thức tĩnh tại đây).

class Util <K, V, Z, Y>{
    // Generic static method
    public static  boolean compare(Pair<K, V> p1, Pair<Z, Y> p2) {
        return p1.getKey().equals(p2.getKey()) &&
               p1.getValue().equals(p2.getValue());
    }
}

PHONG CÁCH GIÁ TRỊ KHÁC

class MyClass<T> {

        //Type declaration <T> already done at class level
        private  T myMethod(T a){
            return  a;
        }

        //<T> is overriding the T declared at Class level;
        //So There is no ClassCastException though a is not the type of T declared at MyClass<T>. 
        private <T> T myMethod1(Object a){
                return (T) a;
        }

        //Runtime ClassCastException will be thrown if a is not the type T (MyClass<T>).  
        private T myMethod1(Object a){
                return (T) a;
        }       

        // No ClassCastException        
        // MyClass<String> obj= new MyClass<String>();
        // obj.myMethod2(Integer.valueOf("1"));
        // Since type T is redefined at this method level.
        private <T> T myMethod2(T a){
            return  a;
        }

        // No ClassCastException for the below
        // MyClass<String> o= new MyClass<String>();
        // o.myMethod3(Integer.valueOf("1").getClass())
        // Since <T> is undefined within this method; 
        // And MyClass<T> don't have impact here
        private <T> T myMethod3(Class a){
            return (T) a;
        }

        // ClassCastException for o.myMethod3(Integer.valueOf("1").getClass())
        // Should be o.myMethod3(String.valueOf("1").getClass())
    private  T myMethod3(Class a){
        return (T) a;
    }


        // Class<T> a :: a is Class object of type T
        //<T> is overriding of class level type declaration; 
        private <T> Class<T> myMethod4(Class<T> a){
            return  a;
        }
    }

Và cuối cùng phương thức tĩnh luôn cần <T>khai báo rõ ràng ; Nó sẽ không xuất phát từ cấp lớp Class<T>. Điều này là do lớp T bị ràng buộc với thể hiện.

Cũng đọc Hạn chế về Generics

Ký tự đại diện và phân loại

loại đối số cho một phương thức chung


2
Câu trả lời của tôi về giới hạn wildCards stackoverflow.com/questions/1368166/
Kanagavelu Sugumar

"Lớp <T> đại diện cho một đối tượng lớp có loại lớp cụ thể 'T'." Điều đó có ý nghĩa. Cảm ơn bạn ..
bynu022

Câu trả lời này cực kỳ khó hiểu trong cách nó sử dụng các Lớp học (từ trường học) trong một câu hỏi về Các lớp học (trong Java). Thật khó để biết tác giả đang nói gì từ câu này sang câu khác.
Echox

@Echox Tôi xin lỗi, tôi có thể cải thiện nó, nếu bạn có bất kỳ câu hỏi cụ thể.
Kanagavelu Sugumar


32

Từ Tài liệu Java:

[...] Đáng ngạc nhiên hơn, Lớp học đã được tạo ra. Lớp chữ giờ đây hoạt động như mã thông báo loại, cung cấp cả thông tin loại thời gian chạy và thời gian biên dịch. Điều này cho phép một kiểu các nhà máy tĩnh được minh họa bằng phương thức getAnnotation trong giao diện AnnotatedEuity mới:

<T extends Annotation> T getAnnotation(Class<T> annotationType); 

Đây là một phương pháp chung. Nó lấy giá trị của tham số loại T từ đối số của nó và trả về một thể hiện thích hợp của T, như được minh họa bởi đoạn mã sau:

Author a = Othello.class.getAnnotation(Author.class);

Trước khi có thuốc generic, bạn sẽ phải gửi kết quả cho Tác giả. Ngoài ra, bạn sẽ không có cách nào để làm cho trình biên dịch kiểm tra xem tham số thực sự đại diện cho một lớp con của Chú thích. [...]

Chà, tôi chưa bao giờ phải sử dụng loại công cụ này. Bất kỳ ai?


2
Tôi nghĩ rằng tôi đã làm. Một khung (loại) tôi đã làm việc với bạn yêu cầu bạn vượt qua tên lớp dịch vụ mà mô-đun của bạn phụ thuộc vào. Tôi đã xây dựng một lớp trên đó lấy các đối tượng Class, để hạn chế số lượng lựa chọn. Sử dụng Class<? extends X>ký hiệu tôi cho rằng tôi chỉ có thể giới hạn ở loại 'dịch vụ'. Ngoại trừ không có loại 'dịch vụ' chung, vì vậy tôi chỉ có thể làm điều đó với Class<?>. Than ôi.
fwielstra

9

Tôi đã tìm thấy class<T>hữu ích khi tôi tạo tra cứu đăng ký dịch vụ. Ví dụ

<T> T getService(Class<T> serviceClass)
{
    ...
}

5

Như các câu trả lời khác chỉ ra, có rất nhiều lý do chính đáng tại sao điều này classđược đưa ra chung chung. Tuy nhiên, có rất nhiều lần bạn không có cách nào để biết loại chung để sử dụng Class<T>. Trong những trường hợp này, bạn có thể chỉ cần bỏ qua các cảnh báo nhật thực màu vàng hoặc bạn có thể sử dụng Class<?>... Đó là cách tôi thực hiện;)


@SuppressWarnings("unchecked")Hãy đến giải cứu! (Chỉ cần cẩn thận để luôn luôn áp dụng nó là phạm vi nhỏ càng tốt vì nó không vấn đề tiềm năng tối nghĩa trong mã của bạn.)
Donal Fellows

4

Theo câu trả lời của @Kire Haglin, một ví dụ khác về các phương pháp tổng quát có thể được nhìn thấy trong tài liệu về JAXB không thay đổi :

public <T> T unmarshal( Class<T> docClass, InputStream inputStream )
         throws JAXBException {
  String packageName = docClass.getPackage().getName();
  JAXBContext jc = JAXBContext.newInstance( packageName );
  Unmarshaller u = jc.createUnmarshaller();
  JAXBElement<T> doc = (JAXBElement<T>)u.unmarshal( inputStream );
  return doc.getValue();
}

Điều này cho phép unmarshaltrả về một tài liệu thuộc loại cây nội dung JAXB tùy ý.


2

Bạn thường muốn sử dụng ký tự đại diện với Class. Ví dụ, Class<? extends JComponent>sẽ cho phép bạn chỉ định rằng lớp là một số lớp con của JComponent. Nếu bạn đã truy xuất Classthể hiện từ Class.forNameđó, thì bạn có thể sử dụng Class.asSubclassđể thực hiện phân vai trước khi thử, giả sử, xây dựng một thể hiện.


2

Trong java <T>có nghĩa là lớp Chung. Lớp chung là một lớp có thể hoạt động trên bất kỳ loại dữ liệu nào hoặc nói cách khác chúng ta có thể nói nó là loại dữ liệu độc lập.

public class Shape<T> {
    // T stands for "Type"
    private T t;

    public void set(T t) { this.t = t; }
    public T get() { return t; }
}

Trong đó T có nghĩa là loại. Bây giờ khi bạn tạo cá thể của lớp Shape này, bạn sẽ cần báo cho trình biên dịch biết loại dữ liệu này sẽ hoạt động.

Thí dụ:

Shape<Integer> s1 = new Shape();
Shape<String> s2 = new Shape();

Integer là một loại và String cũng là một loại.

<T>đặc biệt là viết tắt của loại chung chung. Theo Java Docs - Một loại chung là một lớp hoặc giao diện chung được tham số hóa qua các loại.


0

Chỉ cần đưa ra một ví dụ khác, phiên bản chung của Class ( Class<T>) cho phép một người viết các hàm chung như bên dưới.

public static <T extends Enum<T>>Optional<T> optionalFromString(
        @NotNull Class<T> clazz,
        String name
) {
    return Optional<T> opt = Optional.ofNullable(name)
            .map(String::trim)
            .filter(StringUtils::isNotBlank)
            .map(String::toUpperCase)
            .flatMap(n -> {
                try {
                    return Optional.of(Enum.valueOf(clazz, n));
                } catch (Exception e) {
                    return Optional.empty();
                }
            });
}

-2

Đó là khó hiểu trong đầu. Nhưng nó giúp trong các tình huống dưới đây:

class SomeAction implements Action {
}

// Later in the code.
Class<Action> actionClass = Class.forName("SomeAction"); 
Action action = actionClass.newInstance();
// Notice you get an Action instance, there was no need to cast.

4
Không phải đó chỉ là một cách cực kỳ phức tạp để nói Action a = new Action () sao?
Karl

1
Action a = new Action() ? ActionTôi là một giao diện, đó là SomeActionchúng tôi đang cố gắng lấy một ví dụ. Chúng tôi chỉ có tên củaSomeAction có sẵn trong thời gian chạy.
fastcodejava

Điều này không vượt qua lỗi đánh máy: trình biên dịch java không có cách nào để nói rằng <code> Class.forName ("someAction") </ code> sẽ thuộc loại <code> Class <Action> </ code>, vì điều này sẽ chỉ được biết đến trong thời gian chạy.
tonio

@tonio, phải, vì vậy bạn có thể phải bọc dòng đầu tiên đó trong một số loại thử / bắt. Nhưng giả sử không có ngoại lệ được ném ở đó, dòng thứ hai được đảm bảo để hoạt động.
MatrixFrog

2
Những gì bạn đang thực sự mô tả là someAction. Class phù hợp với lớp mẫu <? mở rộng Hành động> - nghĩa là, nếu bạn có một phương thức useAction (Class <? extends Action> klass), bạn có thể gọi useAction (someAction. class).
Thomas Andrew

-5

Chỉ cần sử dụng lớp thịt bò:

public <T> T beefmarshal( Class<beef> beefClass, InputBeef inputBeef )
     throws JAXBException {
     String packageName = docClass.getPackage().getBeef();
     JAXBContext beef = JAXBContext.newInstance( packageName );
     Unmarshaller u = beef.createBeef();
     JAXBElement<T> doc = (JAXBElement<T>)u.beefmarshal( inputBeef );
     return doc.getBeef();
}

4
Điều này không thực sự cung cấp một câu trả lời đầy đủ cho câu hỏi. Nếu bạn nghĩ rằng bạn có một cái gì đó để thêm, chỉnh sửa câu trả lời này.
MasterAM
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.