การแนะนำ
เริ่มต้นด้วย JSE 5.0 ข้อมูลทั่วไปถูกเพิ่มเข้าไปในคลังแสงภาษา Javaยาชื่อสามัญใน Java คืออะไร?
Generics (ทั่วไป) เป็นวิธีพิเศษของภาษา Java สำหรับการนำการเขียนโปรแกรมทั่วไปไปใช้: วิธีพิเศษในการอธิบายข้อมูลและอัลกอริธึมที่ช่วยให้คุณทำงานกับข้อมูลประเภทต่างๆ ได้โดยไม่ต้องเปลี่ยนคำอธิบาย บนเว็บไซต์ Oracle มีบทช่วยสอนแยกต่างหากเกี่ยวกับข้อมูลทั่วไป: “ บทเรียน: ข้อมูลทั่วไป ”

import java.util.*;
public class HelloWorld{
public static void main(String []args){
List list = new ArrayList();
list.add("Hello");
String text = list.get(0) + ", world!";
System.out.print(text);
}
}
รหัสนี้จะทำงานได้ดี แต่ถ้าพวกเขามาหาเราแล้วพูดว่า "สวัสดีชาวโลก!" พ่ายแพ้แล้วคุณสามารถกลับมาได้เท่านั้น สวัสดี? ลองลบการต่อข้อมูลด้วยสตริงออกจาก", world!"
โค้ด ดูเหมือนว่าอะไรจะเป็นอันตรายได้มากกว่านี้? แต่ในความเป็นจริงแล้ว เราจะได้รับข้อผิดพลาดระหว่างการรวบรวม : error: incompatible types: Object cannot be converted to String
ในกรณีของเรา List จะเก็บรายการของอ็อบเจ็กต์ประเภท Object เนื่องจาก String เป็นลูกหลานของ Object (เนื่องจากคลาสทั้งหมดได้รับการสืบทอดโดยปริยายจาก Object ใน Java) จึงจำเป็นต้องมีการคัดเลือกนักแสดงที่ชัดเจน ซึ่งเราไม่ได้ทำ และเมื่อทำการต่อเข้าด้วยกัน เมธอดคงที่ String.valueOf(obj) จะถูกเรียกบนอ็อบเจ็กต์ ซึ่งในที่สุดจะเรียกเมธอด toString บนอ็อบเจ็กต์ นั่นคือรายการของเรามีวัตถุ ปรากฎว่าในกรณีที่เราต้องการประเภทเฉพาะ ไม่ใช่ Object เราจะต้องทำการคัดเลือกประเภทเอง:
import java.util.*;
public class HelloWorld{
public static void main(String []args){
List list = new ArrayList();
list.add("Hello!");
list.add(123);
for (Object str : list) {
System.out.println((String)str);
}
}
}
แต่ในกรณีนี้เพราะว่า List ยอมรับรายการของอ็อบเจ็กต์ ซึ่งไม่เพียงแต่เก็บ String เท่านั้น แต่ยังเก็บ Integer อีกด้วย แต่สิ่งที่แย่ที่สุดคือในกรณีนี้คอมไพเลอร์จะไม่เห็นสิ่งผิดปกติ และที่นี่เราจะได้รับข้อผิดพลาดระหว่างการดำเนินการ (พวกเขายังบอกด้วยว่าได้รับข้อผิดพลาด "ที่รันไทม์") ข้อผิดพลาดจะเป็น: java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
เห็นด้วย ไม่น่าพอใจที่สุด และทั้งหมดนี้เป็นเพราะคอมไพเลอร์ไม่ใช่ปัญญาประดิษฐ์และไม่สามารถเดาทุกสิ่งที่โปรแกรมเมอร์หมายถึงได้ เพื่อบอกคอมไพเลอร์เพิ่มเติมเกี่ยวกับประเภทที่เราจะใช้ Java SE 5 ได้แนะนำgenerics มาแก้ไขเวอร์ชันของเราโดยบอกคอมไพเลอร์ว่าเราต้องการอะไร:
import java.util.*;
public class HelloWorld {
public static void main(String []args){
List<String> list = new ArrayList<>();
list.add("Hello!");
list.add(123);
for (Object str : list) {
System.out.println(str);
}
}
}
ดังที่เราเห็นแล้ว เราไม่ต้องการการร่ายแบบ String อีกต่อไป นอกจากนี้ ขณะนี้เรายังมีวงเล็บมุมที่ใช้ใส่ข้อมูลทั่วไป ตอนนี้คอมไพเลอร์จะไม่อนุญาตให้คอมไพล์คลาสจนกว่าเราจะลบการบวก 123 ออกจากรายการ เนื่องจาก นี่คือจำนวนเต็ม เขาจะบอกเราอย่างนั้น หลายคนเรียกยาชื่อสามัญว่า "น้ำตาลวากยสัมพันธ์" และพวกมันก็พูดถูก เนื่องจากยาชื่อสามัญจะกลายเป็นวรรณะเดียวกันเมื่อรวบรวม มาดูไบต์โค้ดของคลาสที่คอมไพล์แล้ว: ด้วยการแคสต์แบบแมนนวลและการใช้ข้อมูลทั่วไป:


ประเภทดิบหรือประเภทดิบ
เมื่อพูดถึงยาชื่อสามัญ เราจะมีสองหมวดหมู่เสมอ: ประเภทประเภท (ประเภททั่วไป) และประเภท “ดิบ” (ประเภทดิบ) ประเภท Rawคือประเภทที่ไม่ได้ระบุ "ตัวระบุ" ในวงเล็บมุม:

<>
ไวยากรณ์ของเพชรยังเกี่ยวข้องกับแนวคิดของ " การอนุมานประเภท " หรือการอนุมานประเภท ท้ายที่สุดแล้วคอมไพเลอร์เมื่อเห็น <> ทางด้านขวาจะมองที่ด้านซ้ายซึ่งมีการประกาศประเภทของตัวแปรที่กำหนดค่าไว้ และจากส่วนนี้เขาจะเข้าใจว่าค่าทางด้านขวาพิมพ์ประเภทใด ที่จริงแล้ว หากมีการระบุข้อมูลทั่วไปทางด้านซ้ายและไม่ได้ระบุไว้ทางด้านขวา คอมไพเลอร์จะสามารถอนุมานประเภทได้:
import java.util.*;
public class HelloWorld{
public static void main(String []args) {
List<String> list = new ArrayList();
list.add("Hello World");
String data = list.get(0);
System.out.println(data);
}
}
อย่างไรก็ตาม นี่จะเป็นการผสมผสานระหว่างรูปแบบใหม่ที่มียาสามัญและรูปแบบเก่าที่ไม่มีพวกมัน และนี่เป็นสิ่งที่ไม่พึงปรารถนาอย่างยิ่ง เมื่อคอมไพล์โค้ดด้านบนเราจะได้รับข้อความ: Note: HelloWorld.java uses unchecked or unsafe operations
. จริงๆ แล้วยังไม่ชัดเจนว่าทำไมเราต้องเติมเพชรตรงนี้ด้วย แต่นี่คือตัวอย่าง:
import java.util.*;
public class HelloWorld{
public static void main(String []args) {
List<String> list = Arrays.asList("Hello", "World");
List<Integer> data = new ArrayList(list);
Integer intNumber = data.get(0);
System.out.println(data);
}
}
อย่างที่เราจำได้ ArrayList ยังมี Constructor ตัวที่สองที่รับคอลเลกชันเป็นอินพุต และนี่คือที่ที่การหลอกลวงอยู่ หากไม่มีไวยากรณ์ของเพชร คอมไพเลอร์จะไม่เข้าใจว่ามันถูกหลอก แต่สำหรับเพชรแล้ว ดังนั้นกฎ #1 : ใช้ไวยากรณ์เพชรเสมอหากเราใช้ประเภทที่พิมพ์ มิฉะนั้นเราเสี่ยงที่จะพลาดตรงที่เราใช้แบบดิบ เพื่อหลีกเลี่ยงคำเตือนในบันทึกที่ “ใช้การดำเนินการที่ไม่ได้ตรวจสอบหรือไม่ปลอดภัย” คุณสามารถระบุคำอธิบายประกอบพิเศษเกี่ยวกับวิธีการหรือคลาสที่กำลังใช้อยู่ได้: @SuppressWarnings("unchecked")
การระงับถูกแปลเป็น การระงับ กล่าวคือ เพื่อระงับคำเตือนอย่างแท้จริง แต่ลองคิดดูว่าทำไมคุณถึงตัดสินใจระบุมัน? จำกฎข้อที่หนึ่งและบางทีคุณอาจต้องเพิ่มการพิมพ์

วิธีการทั่วไป
ข้อมูลทั่วไปอนุญาตให้คุณพิมพ์วิธีการ มีส่วนแยกต่างหากสำหรับคุณลักษณะนี้ในบทช่วยสอนของ Oracle: “ วิธีการทั่วไป ” จากบทช่วยสอนนี้ สิ่งสำคัญคือต้องจำไวยากรณ์:- รวมรายการพารามิเตอร์ที่พิมพ์ไว้ในวงเล็บมุม
- รายการพารามิเตอร์ที่พิมพ์จะอยู่ก่อนวิธีที่ส่งคืน
import java.util.*;
public class HelloWorld{
public static class Util {
public static <T> T getValue(Object obj, Class<T> clazz) {
return (T) obj;
}
public static <T> T getValue(Object obj) {
return (T) obj;
}
}
public static void main(String []args) {
List list = Arrays.asList("Author", "Book");
for (Object element : list) {
String data = Util.getValue(element, String.class);
System.out.println(data);
System.out.println(Util.<String>getValue(element));
}
}
}
หากคุณดูที่คลาส Util เราจะเห็นวิธีการพิมพ์สองวิธีในนั้น ด้วยการอนุมานประเภท เราสามารถจัดเตรียมคำจำกัดความประเภทให้กับคอมไพลเลอร์ได้โดยตรง หรือเราจะระบุเองก็ได้ ทั้งสองตัวเลือกจะแสดงอยู่ในตัวอย่าง อย่างไรก็ตาม ไวยากรณ์ค่อนข้างสมเหตุสมผลหากคุณลองคิดดู เมื่อพิมพ์ method เราจะระบุ generic ก่อน method เพราะถ้าเราใช้ generic หลัง method Java จะไม่สามารถระบุได้ว่าจะใช้ type ใด ดังนั้นเราจึงประกาศก่อนว่าเราจะใช้ T ทั่วไป แล้วจึงบอกว่าเราจะคืนยาสามัญนี้ โดยธรรมชาติแล้วUtil.<Integer>getValue(element, String.class)
มันจะล้มเหลวโดยมีข้อผิดincompatible types: Class<String> cannot be converted to Class<Integer>
พลาด เมื่อใช้วิธีการพิมพ์ คุณควรจำไว้เสมอเกี่ยวกับการลบประเภท ลองดูตัวอย่าง:
import java.util.*;
public class HelloWorld {
public static class Util {
public static <T> T getValue(Object obj) {
return (T) obj;
}
}
public static void main(String []args) {
List list = Arrays.asList(2, 3);
for (Object element : list) {
System.out.println(Util.<Integer>getValue(element) + 1);
}
}
}
มันจะทำงานได้ดี แต่ตราบใดที่คอมไพเลอร์เข้าใจว่าวิธีการที่เรียกว่ามีประเภทจำนวนเต็ม มาแทนที่เอาต์พุตคอนโซลด้วยบรรทัดต่อไปนี้: System.out.println(Util.getValue(element) + 1);
และเราได้รับข้อผิดพลาด: ประเภทตัวถูกดำเนินการที่ไม่ถูกต้องสำหรับตัวดำเนินการไบนารี '+' ประเภทแรก: Object ประเภทที่สอง: int นั่นคือประเภทถูกลบไปแล้ว คอมไพเลอร์เห็นว่าไม่มีใครระบุประเภท ประเภทถูกระบุเป็นวัตถุ และการเรียกใช้โค้ดล้มเหลวโดยมีข้อผิดพลาด

ประเภททั่วไป
คุณสามารถพิมพ์ไม่เพียงแต่วิธีการเท่านั้น แต่ยังรวมถึงคลาสด้วย Oracle มีส่วน " ประเภททั่วไป " สำหรับสิ่งนี้ในคำแนะนำโดยเฉพาะ ลองดูตัวอย่าง:public static class SomeType<T> {
public <E> void test(Collection<E> collection) {
for (E element : collection) {
System.out.println(element);
}
}
public void test(List<Integer> collection) {
for (Integer element : collection) {
System.out.println(element);
}
}
}
ทุกอย่างเรียบง่ายที่นี่ หากเราใช้คลาส ข้อมูลทั่วไปจะแสดงอยู่หลังชื่อคลาส ตอนนี้เรามาสร้างอินสแตนซ์ของคลาสนี้ด้วยวิธีการหลัก:
public static void main(String []args) {
SomeType<String> st = new SomeType<>();
List<String> list = Arrays.asList("test");
st.test(list);
}
มันจะทำงานได้ดี คอมไพลเลอร์เห็นว่ามีรายการตัวเลขและคอลเลกชันประเภทสตริง แต่ถ้าเราลบข้อมูลชื่อสามัญออกแล้วทำสิ่งนี้:
SomeType st = new SomeType();
List<String> list = Arrays.asList("test");
st.test(list);
เราจะได้รับข้อผิดพลาด: java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
พิมพ์ Erasure อีกครั้ง เนื่องจากคลาสไม่มีแบบทั่วไปอีกต่อไป คอมไพลเลอร์จึงตัดสินใจว่าเนื่องจากเราส่ง List แล้ว วิธีการที่มี List<Integer> จึงเหมาะสมกว่า และเราก็ล้มลงด้วยความผิดพลาด ดังนั้น กฎ #2: หากมีการพิมพ์คลาส ให้ ระบุประเภทในรูปแบบทั่วไปเสมอ
ข้อ จำกัด
เราสามารถใช้ข้อจำกัดกับประเภทที่ระบุในข้อมูลทั่วไปได้ ตัวอย่างเช่น เราต้องการให้คอนเทนเนอร์ยอมรับเฉพาะ Number เป็นอินพุต คุณลักษณะนี้มีอธิบายไว้ในบทช่วยสอนของ Oracle ในส่วนพารามิเตอร์ประเภทที่มีขอบเขต ลองดูตัวอย่าง:import java.util.*;
public class HelloWorld{
public static class NumberContainer<T extends Number> {
private T number;
public NumberContainer(T number) { this.number = number; }
public void print() {
System.out.println(number);
}
}
public static void main(String []args) {
NumberContainer number1 = new NumberContainer(2L);
NumberContainer number2 = new NumberContainer(1);
NumberContainer number3 = new NumberContainer("f");
}
}
อย่างที่คุณเห็น เราได้จำกัดประเภททั่วไปให้เป็นคลาส/อินเทอร์เฟซ Number และรายการสืบทอด ที่น่าสนใจคือคุณสามารถระบุได้ไม่เพียงแต่คลาสเท่านั้น แต่ยังรวมถึงอินเทอร์เฟซด้วย ตัวอย่างเช่น: public static class NumberContainer<T extends Number & Comparable> {
Generics ยังมีแนวคิดของ Wildcard https://docs.oracle.com/javase/tutorial/java/generics/wildcards.html ในทางกลับกัน จะแบ่งออกเป็นสามประเภท:
- Wildcards ขอบเขตบน - < ? ขยายจำนวน >
- ไวด์การ์ดไม่จำกัด - < ? >
- Wildcard ขอบเขตล่าง - < ? จำนวนเต็มซุปเปอร์ >


public static class TestClass {
public static void print(List<? extends String> list) {
list.add("Hello World!");
System.out.println(list.get(0));
}
}
public static void main(String []args) {
List<String> list = new ArrayList<>();
TestClass.print(list);
}
แต่ถ้าคุณแทนที่ส่วนขยายด้วย super ทุกอย่างจะเรียบร้อยดี เนื่องจากเราเติมรายการด้วยค่าก่อนส่งออก รายการจึงเป็นผู้บริโภคสำหรับเรา นั่นคือผู้บริโภค ดังนั้นเราจึงใช้ซุปเปอร์
มรดก
มีคุณสมบัติที่ผิดปกติอีกประการหนึ่งของยาชื่อสามัญนั่นคือการสืบทอด การสืบทอดของยาชื่อสามัญอธิบายไว้ในบทช่วยสอนของ Oracle ในส่วน " Generics, Inheritance และ Subtypes " สิ่งสำคัญคือการจำและตระหนักถึงสิ่งต่อไปนี้ เราไม่สามารถทำเช่นนี้ได้:List<CharSequence> list1 = new ArrayList<String>();
เนื่องจากการสืบทอดทำงานแตกต่างกับยาชื่อสามัญ:

List<String> list1 = new ArrayList<>();
List<Object> list2 = list1;
ทุกอย่างก็เรียบง่ายที่นี่เช่นกัน List<String> ไม่ใช่ลูกหลานของ List<Object> แม้ว่า String จะเป็นผู้สืบทอดของ Object
สุดท้าย
ดังนั้นเราจึงรีเฟรชความทรงจำเกี่ยวกับยาสามัญของเรา หากไม่ค่อยได้ใช้พลังทั้งหมดที่มี รายละเอียดบางอย่างจะหลุดออกจากความทรงจำ ฉันหวังว่าบทวิจารณ์สั้น ๆ นี้จะช่วยฟื้นฟูความจำของคุณ และเพื่อให้ได้ผลลัพธ์ที่ดียิ่งขึ้น ฉันขอแนะนำอย่างยิ่งให้คุณทำความคุ้นเคยกับสื่อต่อไปนี้:- Yuri Tkach: ประเภท Raw - Generics #1 - Java ขั้นสูง
- การสืบทอดและส่วนขยายทั่วไป - Generics #2 - Java ขั้นสูง
- ส่วนขยายประเภทแบบเรียกซ้ำ - Generics #3 - Java ขั้นสูง
- Alexander Matorin - ข้อมูลทั่วไปที่ไม่ชัดเจน
- ความรู้เบื้องต้นเกี่ยวกับจาวา ยาสามัญ. สัญลักษณ์แทน | เทคโนสตรีม
- O'Reilly: Java Generics และคอลเลกชัน
GO TO FULL VERSION