JavaRush /จาวาบล็อก /Random-TH /การลบประเภท

การลบประเภท

เผยแพร่ในกลุ่ม
สวัสดี! เราดำเนินชุดการบรรยายเกี่ยวกับยาชื่อสามัญต่อไป ก่อนหน้านี้เราเข้าใจในแง่ทั่วไปว่ามันคืออะไรและทำไมจึงจำเป็น วันนี้เราจะพูดถึงคุณลักษณะบางอย่างของยาชื่อสามัญและดูข้อผิดพลาดบางประการเมื่อใช้งาน ไป! ประเภทการลบ - 1ในการบรรยายครั้งล่าสุดเราได้พูดถึงความแตกต่างระหว่างGeneric TypesและRaw Types ในกรณีที่คุณลืม Raw Type คือคลาสทั่วไปที่ถูกลบประเภทออกไปแล้ว
List list = new ArrayList();
นี่คือตัวอย่าง ในที่นี้เราไม่ได้ระบุประเภทของออบเจ็กต์ที่จะวางในList. หากเราพยายามสร้างListและเพิ่มวัตถุเข้าไป เราจะเห็นคำเตือนใน IDa:

“Unchecked call to add(E) as a member of raw type of java.util.List”.
แต่เรายังพูดถึงข้อเท็จจริงที่ว่ายาชื่อสามัญปรากฏเฉพาะในภาษาเวอร์ชัน Java 5 เท่านั้น เมื่อถึงเวลาเปิดตัวโปรแกรมเมอร์ได้เขียนโค้ดจำนวนมากโดยใช้ Raw Types และเพื่อที่จะไม่หยุดทำงานความสามารถในการ การสร้างและทำงานกับ Raw Types ใน Java ยังคงอยู่ อย่างไรก็ตาม ปัญหานี้กลับกลายเป็นว่ากว้างขึ้นมาก ดังที่คุณทราบโค้ด Java จะถูกแปลงเป็น bytecode พิเศษ ซึ่งจะถูกเรียกใช้งานโดยเครื่องเสมือน Java และหากในระหว่างกระบวนการแปล เราใส่ข้อมูลเกี่ยวกับประเภทพารามิเตอร์ลงใน bytecode มันจะทำลายโค้ดที่เขียนก่อนหน้านี้ทั้งหมด เพราะก่อน Java 5 ไม่มีประเภทพารามิเตอร์อยู่! เมื่อทำงานกับยาชื่อสามัญ มีคุณลักษณะที่สำคัญอย่างหนึ่งที่คุณต้องจำไว้ เรียกว่าการลบแบบ สาระสำคัญอยู่ที่ความจริงที่ว่าไม่มีข้อมูลเกี่ยวกับประเภทพารามิเตอร์ถูกเก็บไว้ในชั้นเรียน ข้อมูลนี้มีเฉพาะในขั้นตอนการคอมไพล์เท่านั้น และจะถูกลบ (ไม่สามารถเข้าถึงได้) เมื่อรันไทม์ หากคุณพยายามใส่วัตถุผิดประเภทลงในList<String>คอมไพลเลอร์จะส่งข้อผิดพลาด นี่คือสิ่งที่ผู้สร้างภาษาทำได้โดยการสร้างยาชื่อสามัญ - ตรวจสอบในขั้นตอนการคอมไพล์ แต่เมื่อโค้ด Java ทั้งหมดที่คุณเขียนกลายเป็น bytecode จะไม่มีข้อมูลเกี่ยวกับประเภทพารามิเตอร์ ภายในไบต์โค้ด รายชื่อList<Cat>แมวของคุณจะไม่แตกต่างจากList<String>สตริง ไม่มีสิ่งใดใน bytecode ที่จะบอกว่าcatsนี่คือรายการของCatวัตถุ ข้อมูลเกี่ยวกับสิ่งนี้จะถูกลบระหว่างการคอมไพล์ และเฉพาะข้อมูลที่คุณมีรายการในโปรแกรมของคุณเท่านั้นที่จะเข้าสู่โค้ดList<Object> catsไบต์ มาดูกันว่ามันทำงานอย่างไร:
public class TestClass<T> {

   private T value1;
   private T value2;

   public void printValues() {
       System.out.println(value1);
       System.out.println(value2);
   }

   public static <T> TestClass<T> createAndAdd2Values(Object o1, Object o2) {
       TestClass<T> result = new TestClass<>();
       result.value1 = (T) o1;
       result.value2 = (T) o2;
       return result;
   }

   public static void main(String[] args) {
       Double d = 22.111;
       String s = "Test String";
       TestClass<Integer> test = createAndAdd2Values(d, s);
       test.printValues();
   }
}
เราสร้างคลาสทั่วไปของเราTestClassเอง มันค่อนข้างง่าย โดยพื้นฐานแล้วมันคือ "คอลเลกชัน" เล็กๆ ของวัตถุ 2 ชิ้น ซึ่งจะถูกวางไว้ตรงนั้นทันทีเมื่อวัตถุถูกสร้างขึ้น มันมี 2 วัตถุเป็นเขตTข้อมูล เมื่อดำเนินการเมธอดแล้วcreateAndAdd2Values()ควรส่งอ็อบเจ็กต์ทั้งสองที่ส่งผ่านObject aไปยังObject bประเภทของเรา หลังจากนั้น จะTถูกเพิ่มลงในอ็อบเจ็กต์ TestClassในวิธีการmain()ที่เราสร้างTestClass<Integer>นั่นคือคุณภาพTเราจะมีInteger. แต่ในขณะเดียวกันcreateAndAdd2Values()เราก็ส่งผ่านตัวเลขDoubleและวัตถุ ไปยัง Stringเมธอด คุณคิดว่าโปรแกรมของเราจะใช้งานได้หรือไม่? ท้ายที่สุดแล้ว เราได้ระบุเป็นประเภทพารามิเตอร์Integerแต่Stringแน่นอนว่าไม่สามารถส่งไปที่Integer! ลองใช้วิธีการmain()และตรวจสอบ เอาต์พุตคอนโซล: 22.111 สตริงทดสอบ ผลลัพธ์ที่ไม่คาดคิด! ทำไมสิ่งนี้ถึงเกิดขึ้น? แม่นยำเพราะการลบประเภท ในระหว่างการคอมไพล์โค้ด ข้อมูลเกี่ยวกับประเภทพารามิเตอร์Integerของออบเจ็กต์ของเราTestClass<Integer> testถูกลบ เขากลายเป็นTestClass<Object> test. พารามิเตอร์ของเราถูกแปลงเป็นโดยไม่มีปัญหาใดๆDouble( และไม่เป็นอย่างที่เราคาดไว้!) และถูกเพิ่มลงใน นี่เป็นอีกตัวอย่างที่เรียบง่ายแต่แสดงให้เห็นชัดเจนมากของการลบประเภท: StringObjectIntegerTestClass
import java.util.ArrayList;
import java.util.List;

public class Main {

   private class Cat {

   }

   public static void main(String[] args) {

       List<String> strings = new ArrayList<>();
       List<Integer> numbers = new ArrayList<>();
       List<Cat> cats = new ArrayList<>();

       System.out.println(strings.getClass() == numbers.getClass());
       System.out.println(numbers.getClass() == cats.getClass());

   }
}
เอาต์พุตคอนโซล: true true ดูเหมือนว่าเราได้สร้างคอลเลกชันที่มีพารามิเตอร์ที่แตกต่างกันสามประเภท - String, Integerและคลาสที่เราสร้างCatขึ้น แต่ระหว่างการแปลงเป็น bytecode ทั้งสามรายการกลายเป็นList<Object>ดังนั้นเมื่อดำเนินการ โปรแกรมจะบอกเราว่าในทั้งสามกรณีเราใช้คลาสเดียวกัน

พิมพ์การลบเมื่อทำงานกับอาร์เรย์และข้อมูลทั่วไป

มีจุดสำคัญจุดหนึ่งที่ต้องเข้าใจอย่างชัดเจนเมื่อทำงานกับอาร์เรย์และข้อมูลทั่วไป (เช่นList) นอกจากนี้ยังควรพิจารณาเมื่อเลือกโครงสร้างข้อมูลสำหรับโปรแกรมของคุณ ข้อมูลทั่วไปอาจมีการลบประเภท ไม่มีข้อมูลเกี่ยวกับประเภทพารามิเตอร์ระหว่างการทำงานของโปรแกรม ในทางตรงกันข้าม อาร์เรย์รู้และสามารถใช้ข้อมูลเกี่ยวกับประเภทข้อมูลระหว่างการทำงานของโปรแกรมได้ การพยายามใส่ค่าผิดประเภทลงในอาร์เรย์จะทำให้เกิดข้อยกเว้น:
public class Main2 {

   public static void main(String[] args) {

       Object x[] = new String[3];
       x[0] = new Integer(222);
   }
}
เอาต์พุตคอนโซล:

Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer
เนื่องจากมีความแตกต่างอย่างมากระหว่างอาร์เรย์และข้อมูลทั่วไป จึงอาจมีปัญหาความเข้ากันได้ ประการแรก คุณไม่สามารถสร้างอาร์เรย์ของออบเจ็กต์ทั่วไปหรือแม้แต่อาร์เรย์ที่พิมพ์ได้ ฟังดูสับสนเล็กน้อยใช่ไหม? มาดูกันดีกว่า ตัวอย่างเช่น คุณไม่สามารถทำสิ่งนี้ใน Java:
new List<T>[]
new List<String>[]
new T[]
หากเราพยายามสร้างอาร์เรย์ของรายการList<String>เราจะพบข้อผิดพลาดในการรวบรวมการสร้างอาร์เรย์ทั่วไป:
import java.util.List;

public class Main2 {

   public static void main(String[] args) {

       //ошибка компиляции! Generic array creation
       List<String>[] stringLists = new List<String>[1];
   }
}
แต่ทำไมถึงทำเช่นนี้? เหตุใดการสร้างอาร์เรย์ดังกล่าวจึงถูกห้าม? ทั้งหมดนี้เพื่อความปลอดภัยของประเภท หากคอมไพลเลอร์อนุญาตให้เราสร้างอาร์เรย์ดังกล่าวจากวัตถุทั่วไป เราอาจประสบปัญหามากมาย นี่เป็นตัวอย่างง่ายๆ จากหนังสือ “Effective Java” ของ Joshua Bloch:
public static void main(String[] args) {

   List<String>[] stringLists = new List<String>[1];  //  (1)
   List<Integer> intList = Arrays.asList(42, 65, 44);  //  (2)
   Object[] objects = stringLists;  //  (3)
   objects[0] = intList;  //  (4)
   String s = stringLists[0].get(0);  //  (5)
}
ลองจินตนาการว่าการสร้างอาร์เรย์List<String>[] stringListsจะได้รับอนุญาต และคอมไพลเลอร์จะไม่บ่น ในกรณีนี้คือสิ่งที่เราสามารถทำได้: ในบรรทัดที่ 1 เราสร้างอาร์เรย์ของชีList<String>[] stringListsต อาร์เรย์ของเรามีหนึ่งList<String>รายการ ในบรรทัดที่ 2 เราสร้างรายการตัวเลขList<Integer>. ในบรรทัดที่ 3 เรากำหนดอาร์เรย์ของเราให้List<String>[]กับตัวแปร Object[] objectsภาษา Java ช่วยให้คุณทำสิ่งนี้ได้: Xคุณสามารถใส่ทั้งอ็อบเจ็กต์Xและอ็อบเจ็กต์ของคลาสลูกทั้งหมด ลงในอาร์เรย์ของอ็อบเจ็กต์ Хได้ ดังนั้นObjectsคุณสามารถใส่อะไรก็ได้ลงในอาร์เรย์ได้ ในบรรทัดที่ 4 เราจะแทนที่องค์ประกอบ เดี่ยวของอาร์เรย์objects (List<String>)ด้วยรายการ List<Integer>ด้วยเหตุนี้ เราจึงใส่List<Integer>อาร์เรย์ของเราซึ่งมีไว้เพื่อจัดเก็บList<String>! เราจะพบข้อผิดพลาดเฉพาะเมื่อโค้ดถึงบรรทัดที่ 5 เท่านั้น ข้อยกเว้นจะเกิดขึ้นระหว่างการทำงานของClassCastExceptionโปรแกรม ดังนั้นจึงมีการห้ามการสร้างอาร์เรย์ดังกล่าวในภาษา Java ซึ่งช่วยให้เราหลีกเลี่ยงสถานการณ์ดังกล่าวได้

ฉันจะข้ามการลบประเภทได้อย่างไร

เราได้เรียนรู้เกี่ยวกับการลบประเภทแล้ว มาลองโกงระบบกัน! :) งาน: เรามีคลาสTestClass<T>ทั่วไป เราจำเป็นต้องสร้างวิธีการcreateNewT()ที่จะสร้างและส่งคืนวัตถุประเภทТใหม่ แต่นี่เป็นไปไม่ได้ใช่ไหม? ข้อมูลเกี่ยวกับประเภททั้งหมดТจะถูกลบในระหว่างการคอมไพล์ และในขณะที่โปรแกรมกำลังทำงานอยู่ เราจะไม่สามารถค้นหาได้ว่าเราต้องสร้างออบเจ็กต์ประเภทใด ในความเป็นจริงมีวิธีที่ยุ่งยากวิธีหนึ่ง คุณอาจจำได้ว่ามีคลาสในClassJava เมื่อใช้มัน เราจะได้คลาสของอ็อบเจ็กต์ใดๆ ของเรา:
public class Main2 {

   public static void main(String[] args) {

       Class classInt = Integer.class;
       Class classString = String.class;

       System.out.println(classInt);
       System.out.println(classString);
   }
}
เอาต์พุตคอนโซล:

class java.lang.Integer
class java.lang.String
แต่นี่คือคุณลักษณะหนึ่งที่เราไม่ได้พูดถึง ในเอกสารของ Oracle คุณจะเห็นว่า Class เป็นคลาสทั่วไป! ประเภทการลบ - 3เอกสารระบุว่า: “T คือประเภทของคลาสที่สร้างแบบจำลองโดยอ็อบเจ็กต์คลาสนี้” หากเราแปลสิ่งนี้จากภาษาเอกสารเป็นภาษามนุษย์ นั่นหมายความว่าคลาสสำหรับอ็อบเจ็กต์Integer.classไม่ได้เป็นเพียงClassแต่Class<Integer>เป็น ประเภทของวัตถุstring.classไม่ใช่แค่Class, Class<String>ฯลฯ หากยังไม่ชัดเจน ให้ลองเพิ่มพารามิเตอร์ type ให้กับตัวอย่างก่อนหน้า:
public class Main2 {

   public static void main(String[] args) {

       Class<Integer> classInt = Integer.class;
       //ошибка компиляции!
       Class<String> classInt2 = Integer.class;


       Class<String> classString = String.class;
       //ошибка компиляции!
       Class<Double> classString2 = String.class;
   }
}
และตอนนี้ด้วยการใช้ความรู้นี้ เราสามารถหลีกเลี่ยงการลบประเภทและแก้ไขปัญหาของเราได้! ลองรับข้อมูลเกี่ยวกับประเภทพารามิเตอร์กัน ชั้นเรียนจะมีบทบาทMySecretClass:
public class MySecretClass {

   public MySecretClass() {

       System.out.println("Объект секретного класса успешно создан!");
   }
}
ต่อไปนี้คือวิธีที่เราใช้โซลูชันของเราในทางปฏิบัติ:
public class TestClass<T> {

   Class<T> typeParameterClass;

   public TestClass(Class<T> typeParameterClass) {
       this.typeParameterClass = typeParameterClass;
   }

   public T createNewT() throws IllegalAccessException, InstantiationException {
       T t = typeParameterClass.newInstance();
       return t;
   }

   public static void main(String[] args) throws InstantiationException, IllegalAccessException {

       TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
       MySecretClass secret = testString.createNewT();

   }
}
เอาต์พุตคอนโซล:

Объект секретного класса успешно создан!
เราเพียงแค่ส่งพารามิเตอร์คลาสที่ต้องการไปยังตัวสร้างคลาสทั่วไปของเรา:
TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
ด้วยเหตุนี้ เราจึงบันทึกข้อมูลเกี่ยวกับประเภทพารามิเตอร์และป้องกันไม่ให้ถูกลบ เป็นผลให้เราสามารถสร้างวัตถุได้T! :) นี่เป็นการสรุปการบรรยายของวันนี้ การลบประเภทเป็นสิ่งที่ควรคำนึงถึงเสมอเมื่อทำงานกับข้อมูลทั่วไป สิ่งนี้ดูไม่สะดวกนัก แต่คุณต้องเข้าใจว่ายาชื่อสามัญไม่ได้เป็นส่วนหนึ่งของภาษา Java เมื่อถูกสร้างขึ้น นี่เป็นคุณสมบัติที่เพิ่มเข้ามาในภายหลังซึ่งช่วยเราสร้างคอลเลกชันที่พิมพ์และตรวจจับข้อผิดพลาดในขั้นตอนการคอมไพล์ ภาษาอื่นบางภาษาที่มีข้อมูลทั่วไปตั้งแต่เวอร์ชัน 1 ไม่มีการลบประเภท (เช่น C#) อย่างไรก็ตาม เรายังศึกษายาชื่อสามัญไม่จบ! ในการบรรยายครั้งต่อไป คุณจะได้ทำความคุ้นเคยกับคุณสมบัติต่างๆ มากมายในการทำงานกับสิ่งเหล่านี้ ในระหว่างนี้ คงจะดีไม่น้อยหากได้แก้ไขปัญหาสองสามข้อ! :)
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION