Các khái niệm chung về Java được giải thích dưới dạng ngắn gọn kèm theo các ví dụ

Vui lòng xem Câu hỏi thường gặp này để biết thêm thông tin:

http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html#SECTION1

Dưới đây là một số điểm quan trọng từ Câu hỏi thường gặp này.

Ví dụ (thuộc loại chung):

lớp Pair<X,Y> { 
  riêng X trước;
  riêng Y giây;   Cặp công khai(X a1, Y a2) {
    đầu tiên = a1;
    giây = a2;
  }
  công khai X getFirst() { trả về trước; }
  công khai Y getSecond() { return thứ hai; }
  public void setFirst(X arg)  { first = arg; }
  public void setSecond(Y arg) { giây = arg; }
}

lớp học Đôi có hai tham số loại X Và Y .  Chúng được thay thế bằng các đối số kiểu khi kiểu chung Đôi được khởi tạo. Ví dụ, trong khai báo Cặp<String, Date> tham số kiểu X được thay thế bằng đối số kiểu Sợi dây Và Y được thay thế bởi  Ngày .
Phạm vi của số nhận dạng X Và Y là toàn bộ định nghĩa của lớp.  Trong phạm vi này, hai tham số loại X Và Y được sử dụng giống như chúng là loại (với một số hạn chế). Trong ví dụ trên, các tham số kiểu được sử dụng làm đối số và kiểu trả về của các phương thức cá thể và các kiểu trường cá thể.
Các tham số kiểu có thể được khai báo với giới hạn. Các giới hạn cung cấp quyền truy cập vào các phương thức thuộc loại không xác định mà tham số loại đại diện. Trong ví dụ của chúng tôi, chúng tôi không gọi bất kỳ phương thức nào thuộc loại không xác định X Và Y . Vì lý do này, hai tham số loại không bị giới hạn.

Ví dụ (về loại tham số cụ thể):

public void printPair( Cặp<String,Long> cặp) {
  System.out.println("("+pair.getFirst()+","+pair.getSecond()+")");
} Cặp<String,Long> giới hạn = mới Cặp<String,Long> ("tối đa", 1024L);
printPair(giới hạn);

Ví dụ (về loại tham số ký tự đại diện):

public void printPair( Cặp<?,?> cặp) {
  System.out.println("("+pair.getFirst()+","+pair.getSecond()+")");
} Cặp<?,?> giới hạn = mới Cặp<String,Long> ("tối đa", 1024L);
printPair(giới hạn);

Tất cả các phiên bản của một kiểu chung đều có chung cách biểu diễn kiểu thời gian chạy, cụ thể là cách biểu diễn kiểu thô. Ví dụ, sự khởi tạo của một kiểu generic Danh sách , chẳng hạn như Danh sách<Date> , Danh sách<String> , Danh sách<Long> , v.v. có các kiểu tĩnh khác nhau tại thời điểm biên dịch, nhưng có cùng kiểu động Danh sách vào thời gian chạy.

Một dàn diễn viên bao gồm hai phần:

  • kiểm tra kiểu tĩnh được thực hiện bởi trình biên dịch tại thời điểm biên dịch và
  • kiểm tra kiểu động được thực hiện bởi máy ảo khi chạy.

Phần tĩnh sắp xếp các phôi vô nghĩa, không thể thành công, chẳng hạn như dàn diễn viên từ Sợi dây cái đó Ngày hoặc từ Danh sách<String> cái đó Danh sách<Date> .

Phần động sử dụng thông tin loại thời gian chạy và thực hiện kiểm tra loại khi chạy.  Nó làm tăng một ClassCastException nếu loại động của đối tượng không phải là loại mục tiêu (hoặc loại con của loại mục tiêu) của dàn diễn viên. Ví dụ về các vật đúc có phần động là vật đúc từ Sự vật cái đó Sợi dây hoặc từ Sự vật cái đó Danh sách<String> .  Đây được gọi là downcasts, từ siêu kiểu xuống đến kiểu phụ.

Ví dụ về các loại tham số cụ thể là Danh sách<String> , Map<String,Date> , nhưng không Danh sách<? mở rộng Number> hoặc Map<String,?> .

Đôi khi người ta mong đợi rằng một Danh sách<Object> sẽ là một siêu kiểu của một Danh sách<String> , bởi vì Sự vật là một siêu kiểu của Sợi dây .  Kỳ vọng này xuất phát từ thực tế là loại mối quan hệ như vậy tồn tại đối với mảng:  Sự vật[] là một siêu kiểu của Sợi dây[] , bởi vì Sự vật là một siêu kiểu của Sợi dây . (Loại quan hệ này được gọi là hiệp phương sai .) Mối quan hệ siêu kiểu con của các kiểu thành phần mở rộng sang các kiểu mảng tương ứng. Không có mối quan hệ kiểu như vậy tồn tại đối với việc khởi tạo các kiểu chung. (Các loại tham số là không hiệp biến.)

Việc thiếu mối quan hệ siêu loại con giữa các phiên bản của cùng một loại chung gây ra nhiều hậu quả khác nhau.  Đây là một ví dụ.

Ví dụ:

void printAll(ArrayList<Object> c) {
  cho (Đối tượng o : c) 
    System.out.println(o);
} Danh sách ArrayList<String> = mới ArrayList<String>();
... điền vào danh sách ...
printAll(danh sách);   // lỗi

MỘT Danh sách mảng<String> đối tượng không thể được truyền làm đối số cho một phương thức yêu cầu ArrayList<Object> bởi vì hai loại này là sự thể hiện của cùng một loại chung, nhưng đối với khác biệt loại đối số và vì lý do này, chúng không tương thích với nhau.

Các kiểu được tham số hóa cụ thể là sự thể hiện cụ thể của một kiểu chung.  Chúng gần giống như các loại; chỉ có một vài hạn chế.  Chúng KHÔNG thể được sử dụng cho các mục đích sau:

  • để tạo mảng
  • trong xử lý ngoại lệ
  • trong một lớp theo nghĩa đen
  • trong một ví dụ sự biểu lộ

Mảng chứa các phần tử có kiểu được tham số hóa cụ thể là bất hợp pháp.

Ví dụ (về loại mảng bất hợp pháp):

kiểm tra khoảng trống tĩnh () {
  Pair<Số nguyên,Số nguyên>[] intPairArr = mới Cặp<Số nguyên,Số nguyên>[10] ; // lỗi
  addElements(intPairArr); 
  Cặp Pair<Integer,Integer> = intPairArr[1];
  Số nguyên i = pair.getFirst();
  pair.setSecond(i);
}
tĩnh void addElements( Sự vật[] objArr) {
  objArr[0] = Pair<Integer mới,Integer>(0,0);
  objArr[1] = Pair<String mới,String>("","");      // sẽ thất bại với ArrayStoreException
}

Trình biên dịch cấm tạo các mảng có kiểu thành phần là kiểu được tham số hóa cụ thể, như Cặp<Số nguyên,Số nguyên> trong ví dụ của chúng tôi.  Chúng ta đã thảo luận ở mục trước tại sao việc trình biên dịch đủ tiêu chuẩn một Pair<Số nguyên,Số nguyên>[] như bất hợp pháp.  Vấn đề chính là trình biên dịch và hệ thống thời gian chạy phải đảm bảo rằng một mảng là một đồng nhất dãy các phần tử cùng loại.  Một trong những kiểm tra kiểu, cụ thể là kiểm tra cửa hàng mảng được thực hiện bởi máy ảo trong thời gian chạy, không phát hiện được việc chèn phần tử lạ vào vi phạm. Trong ví dụ, lần chèn thứ hai vào thêm phần tử phương pháp này sẽ thất bại vì họ đã thêm một cặp chuỗi vào một mảng các giá trị nguyên, nhưng nó không thất bại như mong đợi. Những lý do đã được thảo luận trong mục trước.

Nếu chúng ta không thể sử dụng mảng chứa các phần tử có loại là loại được tham số hóa cụ thể thì chúng ta sẽ sử dụng giải pháp nào?

Chúng ta hãy xem xét 3 cách giải quyết có thể hiểu được:

  • mảng các loại thô
  • mảng kiểu tham số ký tự đại diện không giới hạn
  • bộ sưu tập thay vì mảng

Mặc dù mảng các kiểu được tham số hóa cụ thể là bất hợp pháp nhưng việc tập hợp các kiểu được tham số hóa cụ thể được cho phép.

Loại được tham số hóa ký tự đại diện là sự khởi tạo của loại chung trong đó ít nhất một đối số loại là ký tự đại diện.  Ví dụ về các loại tham số ký tự đại diện là Bộ sưu tập<?> , Danh sách<? mở rộng Number> , Bộ so sánh<? siêu String> Và Pair<String,?> . Loại được tham số hóa bằng ký tự đại diện biểu thị một nhóm loại bao gồm các phần thể hiện cụ thể của một loại chung.  Loại ký tự đại diện đang được sử dụng sẽ xác định loại tham số cụ thể nào thuộc về họ.  Ví dụ: kiểu tham số ký tự đại diện Bộ sưu tập<?> biểu thị họ của tất cả các hiện tượng của Bộ sưu tập giao diện bất kể đối số loại.  Loại tham số ký tự đại diện Danh sách<? mở rộng Number> biểu thị họ của tất cả các loại danh sách trong đó loại phần tử là kiểu con của  Con số .  Loại tham số ký tự đại diện Bộ so sánh<? siêu String> là họ của tất cả các hiện tượng của Bộ so sánh giao diện cho các loại đối số kiểu là siêu kiểu của Sợi dây.

Loại được tham số hóa ký tự đại diện không phải là loại theo nghĩa thông thường (khác với lớp/giao diện không được tham số hóa hoặc loại thô).

Các loại tham số ký tự đại diện có thể được sử dụng để gõ (như các lớp và giao diện không được tham số hóa):

  • làm đối số và trả về các kiểu phương thức
  • dưới dạng loại trường hoặc biến tham chiếu cục bộ
  • là kiểu thành phần của đối số kiểu arrayas của các kiểu được tham số hóa khác
  • làm loại mục tiêu trong dàn diễn viên

Không chỉ các kiểu có thể chung chung mà các phương thức cũng có thể chung chung. Các phương thức tĩnh và không tĩnh cũng như các hàm tạo có thể có các tham số kiểu. Cú pháp khai báo các tham số kiểu chính thức tương tự như cú pháp cho kiểu generic. Phần tham số loại được phân cách bằng dấu ngoặc nhọn và xuất hiện trước kiểu trả về của phương thức. Cú pháp và ý nghĩa của nó giống hệt với danh sách tham số loại của loại chung.

Đây là ví dụ về một cái chung tối đa phương thức tính giá trị lớn nhất trong tập hợp các phần tử thuộc loại không xác định MỘT .

Ví dụ (của một phương pháp chung):

Bộ sưu tập lớp {
  công cộng tĩnh <A mở rộng Có thể so sánh<A>> A max(Bộ sưu tập< MỘT > xs) {
    Trình lặp< MỘT > xi = xs.iterator();
    MỘT w = xi.next();
    trong khi (xi.hasNext()) {
      MỘT x = xi.next();
      if (w.compareTo(x) < 0) w = x;
    }
    trở lại w;
  }
} 

các tối đa phương thức có một tham số kiểu, được đặt tên MỘT .  Nó là một trình giữ chỗ cho loại phần tử của bộ sưu tập mà phương thức này hoạt động. Tham số loại có giới hạn; nó phải là một loại là một kiểu con của Có thể so sánh<A>

Ví dụ (cùng loại chung, lần này với giới hạn):

giao diện công cộng Có thể so sánh<T> {
  int công khai so sánhTo(T arg);
}
lớp công khai TreeMap<Key mở rộng So sánh<Key> ,Dữ liệu>{
  lớp tĩnh riêng Entry<K,V> {
     phím K;
     giá trị V;
     Entry<K,V> left = null;
     Entry<K,V> phải = null;
     Entry<K, cha mẹ V>;
  }
  thoáng qua riêng tư Entry<Key,Data> root = null;
  ...
  riêng Entry<Key,Data> getEntry(Khóa khóa) {
     Entry<Key,Data> p = root;
     Phím k = phím;
     trong khi (p != null) {
       int cmp = k. so sánh(p.key) ; 
       nếu (cmp == 0)
         trả lại p;
       khác nếu (cmp < 0)
         p = p.left;
       khác 
         p = p.right; 
     }
     trả về giá trị rỗng;
  }
  boolean công khai chứaKey(Khóa khóa) {
     trả về getEntry(key) != null;
  }
  ...
}

Trong ví dụ trên, tham số kiểu Chìa khóa có giới hạn Có thể so sánh<Key> .  Việc xác định giới hạn có hai tác dụng:

  • Nó cho phép truy cập vào các phương thức mà giới hạn chỉ định .  Trong ví dụ, ràng buộc Có thể so sánh<Key> cấp quyền truy cập vào so sánhTo phương thức mà chúng tôi muốn gọi trong quá trình triển khai Bản đồ cây lớp học.
  • Chỉ các loại “trong giới hạn” mới có thể được sử dụng để khởi tạo loại chung.   Trong ví dụ, một loại tham số hóa như CâyBản đồ<Số,Chuỗi> sẽ bị từ chối, bởi vì loại Con số không phải là một kiểu con của Có thể so sánh<Số> .  Một loại tham số như TreeMap<Chuỗi,Chuỗi> sẽ được chấp nhận, vì kiểu Chuỗi nằm trong giới hạn, tức là là kiểu con của Có thể so sánh<String> .

Tất cả các lớp, giao diện và kiểu enum có thể được sử dụng làm giới hạn tham số kiểu, bao gồm cả kiểu lồng nhau và kiểu bên trong.  Cả kiểu nguyên thủy và kiểu mảng đều không được sử dụng làm giới hạn tham số kiểu.

Ví dụ (về giới hạn tham số loại):

lớp X0 <T mở rộng int > { ... }      // lỗi
lớp X1 <T mở rộng Sự vật[] > { ... } // lỗi
lớp X2 <T mở rộng Con số > { ... }
lớp X3 <T mở rộng Sợi dây > { ... }
lớp X4 <T mở rộng Có thể chạy được > { ... }
lớp X5 <T mở rộng Chủ đề.State > { ... }
lớp X6 <T mở rộng Danh sách > { ... }
lớp X7 <T mở rộng Danh sách<String> > { ... }
lớp X8 <T mở rộng Danh sách<? mở rộng Number> > { ... }
lớp X9 <T mở rộng Có thể so sánh<? siêu Số> > { ... }
lớp X10<T mở rộng Map.Entry<?,?> > { ... }

Mẫu mã cho thấy các kiểu nguyên thủy như int và các kiểu mảng như Sự vật[] không được phép làm tham số loại bị ràng buộc.

Các loại lớp, chẳng hạn như Con số hoặc Sợi dây và các loại giao diện, chẳng hạn như Có thể chạy được , được phép làm tham số loại bị ràng buộc.

Các loại Enum, chẳng hạn như Chủ đề.State cũng được phép làm tham số loại bị ràng buộc. Chủ đề.State là một ví dụ về kiểu lồng nhau được sử dụng làm giới hạn tham số kiểu.  Các loại bên trong không tĩnh cũng được cho phép.

Các loại thô được cho phép dưới dạng giới hạn tham số loại; Danh sách là một ví dụ

Các loại được tham số hóa được cho phép dưới dạng giới hạn tham số loại, bao gồm các loại được tham số hóa cụ thể như Danh sách<String> , các loại tham số ký tự đại diện giới hạn như Danh sách<? mở rộng Number> Và Có thể so sánh<? siêu dài> và các loại tham số ký tự đại diện không giới hạn, chẳng hạn như Map.Entry<?,?> .  Giới hạn là loại được tham số ký tự đại diện cho phép làm đối số loại tất cả các loại thuộc về họ loại mà ký tự đại diện biểu thị.  Giới hạn loại tham số ký tự đại diện chỉ cung cấp quyền truy cập hạn chế vào các trường và phương thức; các hạn chế phụ thuộc vào loại ký tự đại diện.

Ví dụ (về loại tham số ký tự đại diện là giới hạn tham số loại):

lớp X< T mở rộng List<? mở rộng Number> > {
  public void someMethod(T t) {
    t.add(dài mới(0L));    // lỗi
    Số n = t.remove(0);
  }
}
Kiểm tra lớp {
  public static void main(String[] args) {
     X<ArrayList< Dài >> x1 = X<ArrayList<Long>> mới(); 
     X<ArrayList< Sợi dây >> x2 = X<ArrayList<String>> mới(); // lỗi
  }
}

Các biến tham chiếu thuộc loại T (tham số loại) được xử lý giống như các biến tham chiếu của loại ký tự đại diện (tham số loại bị ràng buộc).  Trong ví dụ của chúng ta, hậu quả là trình biên dịch từ chối việc gọi các phương thức lấy đối số thuộc loại “không xác định” mà tham số loại đại diện, chẳng hạn như Danh sách.add , vì giới hạn là loại tham số ký tự đại diện có giới hạn trên.
Đồng thời sự ràng buộc Danh sách<? mở rộng Number> xác định các loại có thể được sử dụng làm đối số loại. Trình biên dịch chấp nhận tất cả các đối số kiểu thuộc họ kiểu Danh sách<? mở rộng Number> , nghĩa là, tất cả các kiểu con của Danh sách với một đối số kiểu là một kiểu con của Con số .

Một tham số loại có thể được sử dụng làm giới hạn của một tham số loại khác.

Ví dụ (về tham số loại được sử dụng làm giới hạn tham số loại):

lớp ba <T> {
  tin T fst, snd, trd;
  < công khai bạn mở rộng T , V. mở rộng T , W mở rộng T > Bộ ba(U arg1, V arg2, W arg3) {
    fst = arg1;
    snd = arg2;
    trd = arg3;
  }
}

Trong ví dụ này, tham số kiểu T của lớp được tham số hóa được sử dụng làm giới hạn của các tham số kiểu bạn ,  V. Và W của một phương thức cá thể được tham số hóa của lớp đó.

Các cơ hội khác để sử dụng các tham số kiểu làm giới hạn của các tham số kiểu khác bao gồm các tình huống trong đó kiểu lồng nhau được xác định bên trong một kiểu chung hoặc một lớp cục bộ được định nghĩa bên trong một phương thức chung.  Nó thậm chí còn được phép sử dụng một tham số loại làm giới hạn của một tham số loại khác trong cùng phần tham số loại.

Một ràng buộc là một lớp cung cấp quyền truy cập vào tất cả các thành viên công khai của nó, nghĩa là các trường công khai, phương thức và kiểu lồng nhau. Chỉ các hàm tạo là không thể truy cập được vì không có gì đảm bảo rằng một lớp con của liên kết có cùng các hàm tạo như liên kết.

Ví dụ (về một lớp được sử dụng làm giới hạn của tham số loại):

lớp công cộng siêu lớp {     // thành viên tĩnh
    public enum EnumType {ĐÂY, ĐÓ}
    đối tượng tĩnh công khai staticField;
    public static void staticMethod() { ... }

    // thành viên không tĩnh
    lớp công khai InnerClass { ... }
    Đối tượng công khai nonStaticField;
    public void nonStaticMethod() { ... }

    // hàm tạo
    SuperClass công khai() { ... }

    // thành viên riêng tư
    Đối tượng riêng tưField; 

    ...
}

lớp cuối cùng công khai SomeClass<T mở rộng siêu lớp > {
    đối tượng T riêng tư;
    công khai SomeClass(T t) { object = t; } 

    Chuỗi công khai toString() {
        trở lại
         "kiểu lồng tĩnh    : "+T.EnumType.class+"\n" 
        +"trường tĩnh          : "+T.staticField+"\n"
        +"phương thức tĩnh         : "+T.staticMethod()+"\n"
        +"kiểu lồng nhau không tĩnh: "+T.InnerClass.class+"\n"
        +"trường không tĩnh      : "+object.nonStaticField+"\n"
        +"phương thức không tĩnh     : "+object.nonStaticMethod()+"\n"
        +"constructor           : "+(new T())+"\n"                   // lỗi
        +"thành viên riêng tư        : "+object.privateField+"\n"          // lỗi 
        ;
    }
}

Sự ràng buộc siêu lớp cấp quyền truy cập vào các kiểu lồng nhau, các trường và phương thức tĩnh cũng như các trường và phương thức không tĩnh.  Chỉ có hàm tạo là không thể truy cập được.  Điều này là do hàm tạo không được kế thừa. Mỗi lớp con định nghĩa các hàm tạo riêng của nó và không cần hỗ trợ các hàm tạo của lớp cha của nó.  Do đó không có gì đảm bảo rằng một lớp con của siêu lớp sẽ có cùng hàm tạo với siêu lớp của nó.

Bối cảnh trong đó Enum<E mở rộng Enum<E>> ” xuất hiện là lời tuyên bố của liệt kê lớp trong gói java.lang :

lớp trừu tượng công cộng Enum<E mở rộng Enum<E>> {
  ...
}

Loại liệt kê là lớp cơ sở chung của tất cả các kiểu liệt kê.  Trong Java một kiểu liệt kê như Màu sắc được dịch thành một lớp Màu sắc điều đó mở rộng Enum<Color> . Mục đích của siêu lớp liệt kê là cung cấp chức năng chung cho tất cả các kiểu liệt kê.

Đây là bản phác thảo của lớp liệt kê :

lớp trừu tượng công cộng Enum< E mở rộng Enum<E>> thực hiện So sánh< E >, có thể tuần tự hóa {
  tên chuỗi cuối cùng riêng tư;
  tên chuỗi cuối cùng công khai() { ... }   thứ tự int cuối cùng riêng tư;
  trận chung kết công khai int thứ tự() { ... }

  protected Enum(Tên chuỗi, thứ tự int) { ... }

  chuỗi công khai           toString() { ... }
  boolean cuối cùng công khai bằng(Đối tượng khác) { ... }
  trận chung kết công khai int hashCode() { ... }
  đối tượng cuối cùng được bảo vệ clone() ném CloneNotSupportedException { ... }
  trận chung kết công khai int soTo( E o) { ... }

  trận chung kết công khai Class< E > getDeclaringClass() { ... }
  public static <T mở rộng Enum<T>> T valueOf(Class<T> enumType, String name) { ... }
}

Điểm bất ngờ trong tuyên bố “ Enum<E mở rộng Enum<E>> ” thực tế là lớp mới được định nghĩa liệt kê và tham số kiểu mới được xác định của nó E xuất hiện trong giới hạn của tham số cùng loại đó.  Nó có nghĩa là liệt kê loại phải được khởi tạo cho một trong các kiểu con của nó.  Để hiểu lý do tại sao điều này có ý nghĩa, hãy xem xét rằng mọi loại enum đều được dịch thành một kiểu con của liệt kê .

Đây là loại enum giả tạo Màu sắc :

liệt kê Màu sắc {ĐỎ, XANH, XANH}

Trình biên dịch dịch nó sang lớp sau:

lớp cuối cùng công khai Màu sắc mở rộng Enum<Color> {
  public static Final Color[] value() { return (Color[])$VALUES.clone(); }
  giá trị màu tĩnh công khaiOf(Tên chuỗi) { ... }   Màu riêng tư(Chuỗi s, int i) { super(s, i); }

  công tĩnh cuối cùng Màu ĐỎ;
  công tĩnh cuối cùng Màu XANH;
  công tĩnh cuối cùng Màu XANH;

  Màu cuối cùng tĩnh riêng tư $VALUES[];

  tĩnh {
    ĐỎ = Màu mới ("ĐỎ", 0);
    XANH = Màu mới ("XANH", 1);
    XANH = Màu mới ("XANH", 2);
    $VALUES = (Màu mới[] { RED, BLUE, GREEN });
  }
}

Việc thừa kế có tác dụng Màu sắc type kế thừa tất cả các phương thức được triển khai trong Enum<Color> .  Trong số đó có so sánhTo phương pháp.  các Màu sắc. so sánhTo phương pháp có lẽ nên mất một Màu sắc như một lý lẽ.  Để thực hiện được điều này lớp liệt kê là chung chung và Enum.compareTo phương pháp mất liệt kê tham số kiểu của E như một lý lẽ.  Kết quả là, gõ Màu sắc bắt nguồn từ Enum<Color> kế thừa một so sánhTo phương pháp đó cần một Màu sắc như và lập luận, chính xác như nó cần.
Nếu chúng ta mổ xẻ tuyên bố “ Enum<E mở rộng Enum<E>> ” chúng ta có thể thấy rằng mô hình này có một số khía cạnh.

Đầu tiên, có một thực tế là tham số loại bị ràng buộc chính là loại đó: “ liệt kê <E mở rộng liệt kê <E>> “. Nó đảm bảo rằng chỉ các kiểu con của kiểu liệt kê được phép làm đối số kiểu. (Về mặt lý thuyết, loại liệt kê có thể được khởi tạo trên chính nó, giống như trong Enum<Enum> , nhưng điều này chắc chắn không có chủ ý và thật khó để tưởng tượng một tình huống trong đó việc khởi tạo như vậy sẽ hữu ích.)

Thứ hai, có một thực tế là kiểu liên kết tham số là kiểu được tham số hóa liệt kê <E> , sử dụng tham số loại E làm đối số kiểu của giới hạn. Tuyên bố này đảm bảo rằng mối quan hệ kế thừa giữa một kiểu con và một sự thể hiện của liệt kê có dạng X mở rộng Enum<X> “. Một kiểu con như “ X mở rộng Enum<Y> ” không thể khai báo vì đối số kiểu Y sẽ không nằm trong giới hạn; chỉ các kiểu con của Enum<X> nằm trong giới hạn.

Thứ ba, có một thực tế là liệt kê là chung chung ở nơi đầu tiên.  Nó có nghĩa là một số phương thức của lớp liệt kê lấy một đối số hoặc trả về một giá trị thuộc loại không xác định (hoặc nói cách khác phụ thuộc vào loại không xác định). Như chúng ta đã biết, loại chưa biết này sau này sẽ là một loại phụ X của Enum<X> .  Do đó, trong kiểu tham số hóa Enum<X> , những phương thức này liên quan đến kiểu con X và chúng được kế thừa vào kiểu con X .  các so sánhTo phương pháp là một ví dụ về phương pháp đó; nó được kế thừa từ lớp cha vào từng lớp con và có một chữ ký cụ thể của lớp con trong từng trường hợp.

Tóm lại, tuyên bố “ Enum<E mở rộng Enum<E>> ” có thể được giải mã là: liệt kê là một loại chung chỉ có thể được khởi tạo cho các kiểu con của nó và các kiểu con đó sẽ kế thừa một số phương thức hữu ích, một số trong đó lấy các đối số cụ thể của kiểu con (hoặc nói cách khác là phụ thuộc vào kiểu con).

Tham số loại giới hạn dưới của lớp

Các tham số kiểu có thể có nhiều giới hạn, như trong lớp Box<T mở rộng Number> {...} . Nhưng một tham số kiểu không thể có giới hạn dưới, nghĩa là một cấu trúc như lớp Box<T Tuyệt Số> {...} không được phép.  Tại sao không?  Câu trả lời là: điều đó là vô nghĩa vì nó sẽ không mua được gì cho bạn nếu nó được phép.  Chúng ta hãy xem tại sao các tham số loại giới hạn dưới của các lớp lại gây nhầm lẫn bằng cách khám phá giới hạn trên của tham số loại có nghĩa là gì.

Giới hạn trên của tham số loại có ba tác dụng:

    1. Khởi tạo bị hạn chế. Giới hạn trên giới hạn tập hợp các loại có thể được sử dụng để khởi tạo loại chung.  Nếu chúng ta khai báo một lớp Box<T mở rộng Number> {...} thì trình biên dịch sẽ đảm bảo rằng chỉ các kiểu con của Con số có thể được sử dụng làm đối số kiểu.  Đó là, một Hộp<Số> hoặc một Hộp<Dài> được cho phép, nhưng một Hộp<Object> hoặc Hộp<Chuỗi> sẽ bị từ chối

Ví dụ (về việc khởi tạo bị hạn chế do giới hạn trên của tham số loại):
lớp Box<T mở rộng Number> { 
   giá trị T riêng tư; 
   Hộp công khai(T t) { value = t; } 
   ...
}  Kiểm tra lớp { 
   public static void main(String[] args) { 
       Box<Long> boxOfLong = Box<Long>(0L) mới;   // khỏe
       Box<String>  boxOfString = Box<String> mới(""); // lỗi: Chuỗi không nằm trong giới hạn
   }
}

  1. Truy cập vào các thành viên không tĩnh. Giới hạn trên cho phép truy cập vào tất cả các phương thức và trường không tĩnh công khai của giới hạn trên.  Trong việc thực hiện của chúng tôi lớp Box<T mở rộng Number> {...} chúng ta có thể gọi tất cả các phương thức không tĩnh công khai được xác định trong lớp Con số , chẳng hạn như intValue() chẳng hạn.  Nếu không có giới hạn trên, trình biên dịch sẽ từ chối bất kỳ lệnh gọi nào như vậy. Ví dụ (về quyền truy cập vào các thành viên không tĩnh do giới hạn trên của tham số loại):

lớp Box<T mở rộng Number> { 
   giá trị T riêng tư; 
   Hộp công khai(T t) { value = t; } 
   công khai int tăng() { return value.intValue()+1; } // <= sẽ là một lỗi nếu không có giới hạn Số
   ...
} 

    1. Loại Xóa. Giới hạn trên cùng bên trái được sử dụng để xóa kiểu và thay thế tham số kiểu trong mã byte.  Trong của chúng tôi lớp Box<T mở rộng Number> {...} tất cả các lần xuất hiện của T sẽ được thay thế bằng giới hạn trên Con số .  Ví dụ, nếu lớp Hộp có một trường riêng thuộc loại T và một phương pháp tập void(nội dung T) để thiết lập trường riêng tư này thì trường đó sẽ thuộc loại Con số sau khi xóa kiểu và phương thức sẽ được dịch sang một phương thức bộ trống (Nội dung số) .

Ví dụ (về việc sử dụng giới hạn trên của tham số loại trong việc xóa kiểu – trước gõ xóa):
lớp Box< T mở rộng Số > { 
   riêng tư T giá trị; 
   hộp công cộng( T t) { giá trị = t; } 
   ...
} 

      Ví dụ (về việc sử dụng giới hạn trên của tham số loại trong việc xóa kiểu –

sau đó

      gõ xóa):

 

Hộp lớp { 
   riêng tư Con số giá trị; 
   hộp công cộng( Con số t) { giá trị = t; } 
   ...
}

    Ngoài ra, giới hạn trên ngoài cùng bên trái xuất hiện ở các vị trí khác, chẳng hạn như các phương pháp đúc và cầu nối được chèn tự động.

Nếu giới hạn dưới được cho phép đối với các tham số loại, thì chúng sẽ hoặc nên có những tác dụng phụ nào? Nếu một cấu trúc như lớp Box<T Tuyệt Số> {...} được cho phép, điều đó có nghĩa là gì?  3 tác dụng phụ của tham số loại trên bị ràng buộc – khởi tạo bị hạn chế, quyền truy cập vào thành viên không tĩnh, xóa kiểu – có ý nghĩa gì đối với giới hạn dưới?

Hiện thực bị hạn chế. Trình biên dịch có thể hạn chế tập hợp các loại có thể được sử dụng để khởi tạo loại chung với tham số loại giới hạn dưới.  Ví dụ, trình biên dịch có thể cho phép khởi tạo như Hộp<Số> Và Hộp<Object> từ một Hộp<T Tuyệt Số> và từ chối ngay lập tức như Hộp<Dài> hoặc Hộp<Ngắn> .  Đây sẽ là một hiệu ứng phù hợp với tác dụng phụ hạn chế được mô tả cho các giới hạn tham số loại trên.

Truy cập vào các thành viên không tĩnh. Giới hạn tham số loại thấp hơn không cấp quyền truy cập vào bất kỳ phương thức cụ thể nào ngoài các phương thức được kế thừa từ lớp Sự vật . Trong ví dụ về Hộp<T Tuyệt Số> siêu kiểu của Con số không có gì chung, ngoại trừ việc chúng là kiểu tham chiếu và do đó là kiểu con của Sự vật .  Trình biên dịch không thể giả định rằng trường kiểu T thuộc loại Con số hoặc một kiểu con của nó.  Thay vào đó, trường loại T có thể thuộc bất kỳ siêu kiểu nào của Con số , chẳng hạn như Có thể tuần tự hóa hoặc Sự vật .  Việc gọi một phương thức như intValue() không còn an toàn về kiểu và trình biên dịch sẽ phải từ chối nó. Kết quả là, giới hạn tham số loại thấp hơn sẽ không cấp quyền truy cập vào các thành viên không tĩnh ngoài các thành viên được xác định trong lớp Sự vật và do đó có tác dụng tương tự như “không bị ràng buộc”.

Loại Xóa .  Theo dòng logic này, sẽ không có ý nghĩa gì khi thay thế các lần xuất hiện của tham số loại bằng giới hạn dưới ngoài cùng bên trái của nó.  Khai báo một phương thức giống như hàm tạo Hộp( T t) với tư cách là người xây dựng Hộp( Con số t) không có ý nghĩa, xem xét rằng T được thay thế bằng siêu kiểu của Con số .  MỘT Sự vật có thể được chuyển đúng cho hàm tạo ngay lập tức Hộp<Object> và hàm tạo sẽ từ chối nó.  Điều này sẽ là sai lầm chết người.   Vì vậy, việc xóa kiểu sẽ thay thế tất cả các lần xuất hiện của biến kiểu T theo loại Sự vật , và không phải bởi giới hạn dưới của nó.  Một lần nữa, giới hạn dưới sẽ có tác dụng tương tự như “không giới hạn”.

Bạn có muốn tìm hiểu xem điều đó có nghĩa là gì nếu cả hai đều thấp hơn _Và_ giới hạn trên có được phép không?   Cá nhân tôi thậm chí không muốn nghĩ về nó và muốn xếp nó vào mục “không thể quản lý được” nếu bạn cho phép.

Điểm mấu chốt là: tất cả những điều đó là “ Tuyệt ” ràng buộc sẽ mua bạn là hạn chế mà chỉ có siêu kiểu của Con số có thể được sử dụng làm đối số kiểu.  Và thậm chí điều đó thường xuyên bị hiểu lầm.  Nó KHÔNG có nghĩa là lớp Box<T siêu Số> {...} chỉ chứa các trường hợp siêu kiểu của Con số .  Hoàn toàn ngược lại – như ví dụ dưới đây chứng minh!

Ví dụ (về việc sử dụng giới hạn trên của tham số loại trong việc xóa kiểu – trước gõ xóa):

lớp Box< T siêu số > {
   riêng tư T giá trị;
   hộp công cộng( T t) { giá trị = t; }
   ...
}

Ví dụ (về việc sử dụng giới hạn trên của tham số loại trong việc xóa kiểu – sau đó gõ xóa):

Hộp lớp {
   riêng tư Sự vật giá trị;
   hộp công cộng( Sự vật t) { giá trị = t; }
   ...
}

MỘT lớp Box<T Tuyệt Số> {...} sẽ được dịch bằng cách xóa thành một Hộp chứa một Sự vật trường và hàm tạo của nó sẽ được dịch sang Hộp(Đối tượng t) . Điều đó về cơ bản khác với một lớp Box<T mở rộng Số> {...} , sẽ được dịch sang một Hộp chứa một Con số trường và hàm tạo của nó sẽ được dịch sang Hộp (Số t) . Do đó, một Hộp<Số> được khởi tạo từ một lớp Box<T mở rộng Number> {...} sẽ khác với một Hộp<Số> khởi tạo từ một lớp Box<T siêu Số> {...} , điều này có thể gây nhầm lẫn.  Vì lý do này, giới hạn dưới không có ý nghĩa đối với các tham số kiểu của các lớp.

 


 

Thông số loại giới hạn dưới của phương pháp

Cùng với các phương thức và kiểu đối số của chúng, một tham số kiểu có giới hạn dưới đôi khi có thể hữu ích.

Ví dụ (về một phương thức sẽ được hưởng lợi từ tham số loại có giới hạn dưới):

lớp Pair<X,Y> {
   riêng X trước;
   riêng Y giây;
   ...
   < công khai Một siêu X , B siêu Y > B addToMap(Map<A,B> map) { // lỗi: tham số loại không thể có giới hạn dưới
      return map.put(thứ nhất, thứ hai);
   }
}
Kiểm tra lớp { 
   public static void main(String[] args) { 
     Cặp Pair<String, Cặp Long> = Cặp <> mới ("ABC", 42L);
     Bản đồ Map<CharSequence, Number> = HashMap<CharSequence, Number>();
     Số số = pair.addToMap(map);
   }
}

các addToMap() phương thức thêm nội dung của cặp vào bản đồ.  Bất kỳ bản đồ nào có thể chứa siêu kiểu của X Và Y sẽ làm được.  Bản đồ của đặt() phương thức trả về giá trị được tìm thấy trong bản đồ cho khóa đã cho, nếu đã có mục nhập khóa-giá trị cho khóa trong bản đồ.  Giá trị trả về của bản đồ đặt() phương thức sẽ được trả về từ addToMap() phương pháp.  Trong những trường hợp này, người ta muốn khai báo phương thức như được hiển thị ở trên: Bản đồ được tham số hóa với các siêu kiểu của tham số loại của cặp và addToMap() Kiểu trả về của phương thức là kiểu giá trị của bản đồ.

Vì trình biên dịch không cho phép giới hạn dưới đối với các tham số loại nên chúng tôi cần giải quyết.

Một cách giải quyết mà tôi nghĩ đến là sử dụng ký tự đại diện, vì ký tự đại diện có thể có giới hạn dưới.  Đây là cách giải quyết bằng cách sử dụng ký tự đại diện.

Ví dụ (cách giải quyết cho ví dụ trước bằng cách sử dụng ký tự đại diện):

lớp Pair<X,Y> {
   riêng X trước;
   riêng Y giây;
   ...
   công cộng Sự vật addToMap(Map< ? siêu X , ? siêu Y Bản đồ >) { 
      return map.put(thứ nhất, thứ hai);
   }
}
Kiểm tra lớp { 
   public static void main(String[] args) { 
     Cặp Pair<String, Cặp Long> = Cặp <> mới ("ABC", 42L);
     Bản đồ Map<CharSequence, Number> = HashMap<CharSequence, Number>();
     Số số = (Con số) pair.addToMap(map);
   }
}

Nó hoạt động, ngoại trừ việc không có cách nào để khai báo kiểu trả về như mong muốn.  Nó sẽ là siêu kiểu của Y mà trình biên dịch thu thập từ loại bản đồ, nhưng không có cú pháp để chỉ định nó.  Chúng ta không được khai báo kiểu trả về là “ ? siêu Y “, bởi vì “ ? siêu Y ” là ký tự đại diện chứ không phải một loại và do đó không được phép làm loại trả về. Chúng tôi không có lựa chọn nào khác và phải sử dụng Sự vật thay vào đó là kiểu trả về của phương thức của chúng tôi.  Kiểu trả về khá không cụ thể này lần lượt buộc người gọi addToMap() phương pháp truyền giá trị trả về từ Sự vật với loại thực tế của nó.  Đây không phải là chính xác những gì chúng tôi đã nghĩ đến.

Một cách giải quyết khác là sử dụng các phương thức tĩnh.  Đây là cách giải quyết bằng phương thức tĩnh thay vì phương thức không tĩnh.

Ví dụ (về cách giải quyết cho ví dụ trước bằng phương pháp tĩnh):

lớp Pair<X,Y> {
   riêng X trước;
   riêng Y giây;
   ...
   công cộng tĩnh < MỘT , B ,X mở rộng A , Y mở rộng B > B addToMap(Pair<X,cặp Y>, Map< MỘT , B Bản đồ >) {
      return map.put(pair.first, pair.second);
   }
}
Kiểm tra lớp { 
   public static void main(String[] args) { 
     Cặp Pair<String, Cặp Long> = Cặp <> mới ("ABC", 42L);
     Bản đồ Map<CharSequence, Number> = HashMap<CharSequence, Number>();
     Số số = Đôi .addToMap( đôi ,bản đồ);
   }
}

Cái chung addToMap() phương thức có bốn tham số loại: hai phần giữ chỗ X Và Y cho loại cặp và hai phần giữ chỗ MỘT Và B cho loại bản đồ. MỘT Và B là siêu kiểu của X Và Y , bởi vì X Và Y   được khai báo với MỘT Và B như giới hạn trên của họ. (Lưu ý rằng các tham số kiểu của phương thức chung X Và Y không có gì để làm với Đôi lớp học X Và Y các thông số.  Những cái tên X Và Y được sử dụng lại cho phương thức chung để làm cho chúng dễ dàng được nhận biết dưới dạng tham số loại của cặp. Sử dụng bốn tham số loại, chúng ta có thể khai báo kiểu trả về chính xác như mong muốn: nó cùng loại với loại giá trị của bản đồ.

Ví dụ (về việc sử dụng bất hợp pháp tham số loại trong ngữ cảnh tĩnh của một lớp chung):

lớp SomeClass <T> {
  // bộ khởi tạo tĩnh, trường tĩnh, phương thức tĩnh
  tĩnh {
    Một sốLớp< T Kiểm tra > = SomeClass< mới T >(); // lỗi
  }
  riêng tư tĩnh T thông tin toàn cầu;          // lỗi
  công cộng  tĩnh T getGlobalInfo() {        // lỗi
    trả về GlobalInfo;
  }
  // trình khởi tạo không tĩnh, trường không tĩnh, phương thức không tĩnh
  { 
    Một sốLớp< T Kiểm tra > = SomeClass< mới T >();
  }
  riêng tư        T thông tin địa phương;
  công cộng         T getLocalInfo() { 
    trả về localInfo;
  }
  // các kiểu lồng nhau tĩnh
  công cộng tĩnh lớp học Thất bại mở rộng Ngoại lệ {
    trận chung kết riêng T thông tin;           // lỗi
    Thất bại công khai( T t) { thông tin = t; }      // lỗi
    công cộng T getInfo() { trả về thông tin; }    // lỗi
   }
  riêng tư giao diện Có thể sao chép {
    T sao chép();                       // lỗi
  }
  riêng tư liệt kê Tình trạng {
    HỢP LỆ, KHÔNG HỢP LỆ;
    riêng tư T thông tin; // lỗi
    public void setInfo( T t) { thông tin = t; } // lỗi
    công cộng T getInfo() { trả về thông tin; }    // lỗi
  }
  // các kiểu lồng nhau không tĩnh
  công cộng lớp học Người truy cập {
    công cộng T getInfo() { trả về localInfo; }
  }
}

Ví dụ minh họa rằng tham số kiểu không thể được sử dụng trong ngữ cảnh tĩnh của một lớp chung.  Nó cũng cho thấy các giao diện lồng nhau và các kiểu enum được coi là thành viên kiểu tĩnh của lớp.  Chỉ các lớp bên trong, tức là các lớp lồng nhau không tĩnh, mới có thể sử dụng tham số kiểu của lớp chung kèm theo.

Phạm vi tham số loại của giao diện là toàn bộ định nghĩa của giao diện, ngoại trừ mọi trường hoặc kiểu lồng nhau.  Điều này là do các trường và kiểu lồng nhau được xác định trong giao diện hoàn toàn tĩnh.

Ví dụ (về việc sử dụng bất hợp pháp tham số loại trong giao diện chung):

giao diện SomeInterface <T> {
  // cánh đồng
  Một sốLớp< T Giá trị > = SomeClass< mới T >();  // lỗi
  // kiểu lồng nhau
  lớp học Người truy cập {
    công cộng T getInfo() {            // lỗi
      giá trị trả về.getGlobalInfo();
    }
  }
  // phương pháp
  T getValue();
}

Ví dụ cho thấy các trường của một giao diện hoàn toàn tĩnh, do đó tham số loại không thể được sử dụng ở bất kỳ đâu trong phần khai báo trường của giao diện chung.  Tương tự, lớp lồng nhau được coi là lớp lồng tĩnh, không phải lớp bên trong và vì lý do này, việc sử dụng tham số loại ở bất kỳ đâu trong lớp lồng nhau là bất hợp pháp.

Cách giải quyết cho các lớp và giao diện tĩnh lồng nhau:

Trong trường hợp các lớp và giao diện lồng nhau tĩnh thì đây không phải là một tai họa lớn.  Để giải quyết vấn đề này, chúng ta có thể tự tạo lớp hoặc giao diện tĩnh.

Ví dụ (cách giải quyết – tạo kiểu tĩnh lồng nhau):

trình bao bọc lớp <T> {
  trận chung kết riêng T đối tượng;

  trình bao bọc công khai( T t) { theObject = t; }
  công cộng T getWrapper() { trả vềObject; }   công khai bất biến <T> makeImmutable() {
        trả lại mới Bất biến <T> (Đối tượng);
  }
  công cộng có thể thay đổi <T> makeMutable() {
        trả lại mới Có thể thay đổi <T> (Đối tượng);
  }
  tĩnh riêng <A> A makeClone(A theObject) { ... }

  lớp cuối cùng tĩnh công khai Bất biến <A> { // bây giờ là một lớp chung
        trận chung kết riêng MỘT đối tượng;

        công khai bất biến( MỘT tranh luận) {
            theObject = makeClone(arg);
        }
        công cộng MỘT getWrappedItem() {
            trả về makeClone(theObject);
        }
  }
  lớp tĩnh công khai Có thể thay đổi <A> {
        ... tương tự ...
  }
}

Không có cách giải quyết nào như vậy đối với các kiểu enum lồng nhau vì chúng không thể chung chung.

Cách giải quyết cho các lớp tĩnh lồng nhau:

Nếu kiểu tĩnh lồng nhau là một lớp, thì một cách giải quyết khác là biến lớp tĩnh thành lớp bên trong không tĩnh.

Ví dụ (cách giải quyết – sử dụng lớp bên trong):

trình bao bọc lớp <T> {
  trận chung kết riêng T đối tượng;

  trình bao bọc công khai( T t) { theObject = t; }
  công cộng T getWrapper() { trả vềObject; }   công khai có thể thay đổi makeMutable() {
      trở lại cái này. Có thể thay đổi mới (theObject);
  }
  công khai Bất biến makeImmutable() {
      trở lại cái này. Bất biến mới (theObject);
  }
  tĩnh riêng <A> A makeClone(A theObject) { ... }

  lớp cuối cùng công khai Bất biến {  // không còn là lớp tĩnh nữa
      trận chung kết riêng T đối tượng;

      công khai bất biến( T tranh luận) {
          theObject = makeClone(arg);
      }
      công cộng T getWrappedItem() {
          trả về makeClone(theObject);
      }
  }
  lớp công khai Có thể thay đổi 
        ... tương tự ...
  }
}

Cách giải quyết này đi kèm với một lượng chi phí nhất định vì tất cả các lớp bên trong đều có một tham chiếu ẩn đến một thể hiện của loại bên ngoài, mà trong ví dụ này chúng không cần cũng như không tận dụng được; tài liệu tham khảo ẩn chỉ là hành lý.  Thông thường, các lớp bên trong được kết hợp với các giao diện để giữ cho lớp bên trong là một chi tiết triển khai riêng tư của lớp kèm theo.  Chúng ta có thể làm điều tương tự ở đây.

Ví dụ (cách giải quyết trước đó đã được tinh chỉnh):

trình bao bọc lớp <T> {
  trận chung kết riêng T đối tượng;

  trình bao bọc công khai( T t) { theObject = t; }
  công cộng T getWrapper() { trả vềObject; }   công cộng có thể thay đổi <T> makeMutable() {
      trả về this.new Mutable(theObject);
  }
  công khai bất biến <T> makeImmutable() {
      trả về this.new Immutable(theObject);
  }
  tĩnh riêng <A> A makeClone(A theObject) { ... }

  giao diện công cộng Bất biến<S> {
      S getWrappedItem();
  }
  giao diện công cộng Có thể thay đổi<S> {
      S getWrappedItem();
      void setWrappedItem(S arg);
  }
  riêng tư lớp cuối cùng ImmutableImplementation thực hiện Immutable<T> { 

      trận chung kết riêng tư T theObject;

      public ImmutableImplementation(T arg) {
          theObject = makeClone(arg);
      }
      công khai T getWrappedItem() {
          trả về makeClone(theObject);
      }
  }
  riêng tư lớp thực hiện có thể thay đổi thực hiện Mutable<T> { 
        ... tương tự ...
  }
}

Như bạn có thể thấy, các giao diện công cộng lồng nhau cần phải chung chung trong khi các lớp riêng bên trong có thể sử dụng tham số loại của lớp kèm theo.

 

 

 

 

 

Để lại một câu trả lời