JavaRush /จาวาบล็อก /Random-TH /ข้อมูลทั่วไปสำหรับแมว
Viacheslav
ระดับ

ข้อมูลทั่วไปสำหรับแมว

เผยแพร่ในกลุ่ม
ข้อมูลทั่วไปสำหรับแมว - 1

การแนะนำ

วันนี้เป็นวันที่ดีในการจดจำสิ่งที่เรารู้เกี่ยวกับ Java ตามเอกสารที่สำคัญที่สุดคือ ข้อกำหนดภาษา Java (JLS - Java Language Specifiaction) Java เป็นภาษาที่พิมพ์อย่างยิ่ง ดังที่อธิบายไว้ในบท " บทที่ 4 ประเภท ค่า และตัวแปร " สิ่งนี้หมายความว่า? สมมติว่าเรามีวิธีการหลัก:
public static void main(String[] args) {
String text = "Hello world!";
System.out.println(text);
}
การพิมพ์ที่รัดกุมช่วยให้แน่ใจว่าเมื่อมีการคอมไพล์โค้ดนี้ คอมไพลเลอร์จะตรวจสอบว่าหากเราระบุประเภทของตัวแปรข้อความเป็น String แล้วเราจะไม่พยายามใช้ตัวแปรนั้นที่ใดก็ได้เป็นตัวแปรประเภทอื่น (เช่น เป็นจำนวนเต็ม) . ตัวอย่างเช่น ถ้าเราพยายามบันทึกค่าแทนข้อความ2L(เช่น long แทนที่จะเป็น String) เราจะได้รับข้อผิดพลาดในขณะคอมไพล์:

Main.java:3: error: incompatible types: long cannot be converted to String
String text = 2L;
เหล่านั้น. การพิมพ์ขั้นสูงช่วยให้คุณมั่นใจได้ว่าการดำเนินการกับออบเจ็กต์จะดำเนินการเฉพาะเมื่อการดำเนินการเหล่านั้นถูกต้องตามกฎหมายสำหรับออบเจ็กต์เหล่านั้น สิ่งนี้เรียกว่าความปลอดภัยประเภท ตามที่ระบุไว้ใน JLS มีประเภทสองประเภทใน Java: ประเภทดั้งเดิมและประเภทการอ้างอิง คุณสามารถจำเกี่ยวกับประเภทดั้งเดิมได้จากบทความทบทวน: “ ประเภทดั้งเดิมใน Java: พวกมันไม่ดั้งเดิมมากนัก ” ประเภทการอ้างอิงสามารถแสดงด้วยคลาส อินเทอร์เฟซ หรืออาร์เรย์ และวันนี้เราจะมาสนใจประเภทอ้างอิงกัน เริ่มจากอาร์เรย์กันก่อน:
class Main {
  public static void main(String[] args) {
    String[] text = new String[5];
    text[0] = "Hello";
  }
}
รหัสนี้ทำงานโดยไม่มีข้อผิดพลาด ดังที่เราทราบ (เช่น จาก " Oracle Java Tutorial: Arrays ") อาร์เรย์คือคอนเทนเนอร์ที่เก็บข้อมูลประเภทเดียวเท่านั้น ในกรณีนี้ - เฉพาะบรรทัดเท่านั้น ลองเพิ่มความยาวให้กับอาร์เรย์แทน String:
text[1] = 4L;
มารันโค้ดนี้ (เช่นในRepl.it Online Java Compiler ) และได้รับข้อผิดพลาด:
error: incompatible types: long cannot be converted to String
อาเรย์และความปลอดภัยของประเภทของภาษาไม่อนุญาตให้เราบันทึกสิ่งที่ไม่เหมาะกับประเภทลงในอาเรย์ นี่คือการแสดงถึงความปลอดภัยประเภท เราได้รับแจ้งว่า: "แก้ไขข้อผิดพลาด แต่จนกว่าจะถึงตอนนั้น ฉันจะไม่รวบรวมโค้ด" และสิ่งที่สำคัญที่สุดเกี่ยวกับเรื่องนี้ก็คือ สิ่งนี้จะเกิดขึ้นในขณะที่ทำการคอมไพล์ ไม่ใช่เมื่อเปิดตัวโปรแกรม นั่นคือเราเห็นข้อผิดพลาดทันที ไม่ใช่ "สักวันหนึ่ง" และเนื่องจากเราจำเกี่ยวกับอาร์เรย์ได้ เราก็เลยจำเกี่ยวกับJava Collections Frameworkไป ด้วย เรามีโครงสร้างที่แตกต่างกันที่นั่น ตัวอย่างเช่น รายการ ลองเขียนตัวอย่างใหม่:
import java.util.*;
class Main {
  public static void main(String[] args) {
    List text = new ArrayList(5);
    text.add("Hello");
    text.add(4L);
    String test = text.get(0);
  }
}
เมื่อทำการคอมไพล์ เราจะได้รับtestข้อผิดพลาดในบรรทัดการเริ่มต้นตัวแปร:
incompatible types: Object cannot be converted to String
ในกรณีของเรา List สามารถจัดเก็บวัตถุใด ๆ (เช่นวัตถุประเภท Object) ดังนั้นคอมไพเลอร์จึงบอกว่าไม่สามารถรับภาระความรับผิดชอบดังกล่าวได้ ดังนั้นเราจึงต้องระบุประเภทที่เราจะได้รับจากรายการให้ชัดเจน:
String test = (String) text.get(0);
ข้อบ่งชี้นี้เรียกว่าการแปลงประเภทหรือการหล่อประเภท และทุกอย่างจะทำงานได้ดีในตอนนี้จนกว่าเราจะพยายามรับองค์ประกอบที่ดัชนี 1 เพราะ เป็นประเภทยาว และเราจะได้รับข้อผิดพลาดที่ยุติธรรม แต่ในขณะที่โปรแกรมกำลังทำงานอยู่ (ในรันไทม์):

type conversion, typecasting
Exception in thread "main" java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.String
อย่างที่เราเห็น มีข้อเสียที่สำคัญหลายประการที่นี่ ประการแรก เราถูกบังคับให้ "ส่ง" ค่าที่ได้รับจากรายการไปยังคลาส String เห็นด้วยนี่น่าเกลียด ประการที่สอง ในกรณีที่เกิดข้อผิดพลาด เราจะเห็นเฉพาะเมื่อมีการรันโปรแกรมเท่านั้น หากโค้ดของเราซับซ้อนกว่านี้ เราอาจตรวจไม่พบข้อผิดพลาดดังกล่าวในทันที และนักพัฒนาก็เริ่มคิดถึงวิธีทำให้งานในสถานการณ์ดังกล่าวง่ายขึ้นและโค้ดมีความชัดเจนมากขึ้น และพวกเขาก็เกิด - ยาสามัญ
ข้อมูลทั่วไปสำหรับแมว - 2

ยาสามัญ

ดังนั้นชื่อสามัญ มันคืออะไร? ทั่วไปเป็นวิธีพิเศษในการอธิบายประเภทที่ใช้ ซึ่งคอมไพเลอร์โค้ดสามารถใช้ในการทำงานเพื่อให้มั่นใจถึงความปลอดภัยของประเภท มีลักษณะดังนี้:
ข้อมูลทั่วไปสำหรับแมว - 3
นี่คือตัวอย่างสั้นๆ และคำอธิบาย:
import java.util.*;
class Main {
  public static void main(String[] args) {
    List<String> text = new ArrayList<String>(5);
    text.add("Hello");
    text.add(4L);
    String test = text.get(1);
  }
}
ในตัวอย่างนี้ เราบอกว่าเราไม่ได้มีแค่Listแต่Listซึ่งใช้ได้กับอ็อบเจ็กต์ประเภท String เท่านั้น และไม่มีคนอื่น อะไรแค่ระบุในวงเล็บเราก็เก็บได้ "วงเล็บ" ดังกล่าวเรียกว่า "วงเล็บมุม" เช่น วงเล็บมุม คอมไพเลอร์จะกรุณาตรวจสอบเราว่าเราทำผิดพลาดหรือไม่เมื่อทำงานกับรายการสตริง (รายการมีชื่อว่าข้อความ) คอมไพเลอร์จะเห็นว่าเรากำลังพยายามใส่ Long เข้าไปในรายการ String อย่างโจ่งแจ้ง และเมื่อถึงเวลาคอมไพล์มันจะทำให้เกิดข้อผิดพลาด:
error: no suitable method found for add(long)
คุณอาจจำได้ว่า String เป็นลูกหลานของ CharSequence และตัดสินใจทำบางสิ่งเช่น:
public static void main(String[] args) {
	ArrayList<CharSequence> text = new ArrayList<String>(5);
	text.add("Hello");
	String test = text.get(0);
}
แต่นี่เป็นไปไม่ได้และเราจะได้รับข้อผิดพลาด: error: incompatible types: ArrayList<String> cannot be converted to ArrayList<CharSequence> มันดูแปลกเพราะว่า บรรทัดCharSequence sec = "test";ไม่มีข้อผิดพลาด ลองคิดดูสิ พวกเขาพูดเกี่ยวกับพฤติกรรมนี้: “ข้อมูลทั่วไปไม่แปรเปลี่ยน” "ค่าคงที่" คืออะไร? ฉันชอบที่พูดเกี่ยวกับเรื่องนี้ในวิกิพีเดียในบทความ " ความแปรปรวนร่วมและความแปรปรวนร่วม ":
ข้อมูลทั่วไปสำหรับแมว - 4
ดังนั้นค่าคงที่คือการไม่มีมรดกระหว่างประเภทที่ได้รับ ถ้า Cat เป็นประเภทย่อยของ Animals ดังนั้น Set<Cats> ไม่ใช่ประเภทย่อยของ Set<Animals> และ Set<Animals> ไม่ใช่ประเภทย่อยของ Set<Cats> อย่างไรก็ตามเป็นเรื่องที่ควรค่าแก่การบอกว่าเริ่มต้นด้วย Java SE 7 สิ่งที่เรียกว่า " Diamond Operator " ก็ปรากฏขึ้น เพราะวงเล็บมุมทั้งสอง <> เปรียบเสมือนเพชร สิ่งนี้ทำให้เราสามารถใช้ยาชื่อสามัญเช่นนี้:
public static void main(String[] args) {
  List<String> lines = new ArrayList<>();
  lines.add("Hello world!");
  System.out.println(lines);
}
จากโค้ดนี้ คอมไพเลอร์เข้าใจว่าหากเราระบุทางด้านซ้ายว่าListจะมีอ็อบเจ็กต์ประเภท String ทางด้านขวาเราหมายความว่าเราต้องการlinesบันทึก ArrayList ใหม่ลงในตัวแปร ซึ่งจะเก็บอ็อบเจ็กต์ด้วย ของประเภทที่ระบุทางด้านซ้าย ดังนั้นคอมไพเลอร์จากด้านซ้ายจะเข้าใจหรืออนุมานประเภทของด้านขวา นี่คือสาเหตุที่ลักษณะการทำงานนี้เรียกว่าการอนุมานประเภทหรือ "การอนุมานประเภท" ในภาษาอังกฤษ สิ่งที่น่าสนใจอีกประการหนึ่งที่น่าสังเกตคือประเภท RAW หรือ "ประเภทดิบ" เพราะ Generics ไม่ได้มีมานานแล้ว และ Java พยายามรักษาความเข้ากันได้แบบย้อนหลังทุกครั้งที่เป็นไปได้ จากนั้น Generics จะถูกบังคับให้ทำงานกับโค้ดที่ไม่ได้ระบุ Generics ไว้ ลองดูตัวอย่าง:
List<CharSequence> lines = new ArrayList<String>();
ดังที่เราจำได้ บรรทัดดังกล่าวจะไม่คอมไพล์เนื่องจากค่าคงที่ของยาชื่อสามัญ
List<Object> lines = new ArrayList<String>();
และอันนี้จะไม่คอมไพล์ด้วยเหตุผลเดียวกัน
List lines = new ArrayList<String>();
List<String> lines2 = new ArrayList();
บรรทัดดังกล่าวจะคอมไพล์และใช้งานได้ อยู่ในนั้นมีการใช้ประเภท Raw เช่น ประเภทที่ไม่ระบุ เป็นอีกครั้งที่คุ้มค่าที่จะชี้ให้เห็นว่า Raw Types ไม่ควรใช้ในโค้ดสมัยใหม่
ข้อมูลทั่วไปสำหรับแมว - 5

ชั้นเรียนที่พิมพ์

ดังนั้นพิมพ์ชั้นเรียน มาดูกันว่าเราจะเขียนคลาส typed ของเราเองได้อย่างไร ตัวอย่างเช่น เรามีลำดับชั้น:
public static abstract class Animal {
  public abstract void voice();
}

public static class Cat extends Animal {
  public void voice(){
    System.out.println("Meow meow");
  }
}

public static class Dog extends Animal {
  public void voice(){
    System.out.println("Woof woof");
  }
}
เราต้องการสร้างคลาสที่ใช้คอนเทนเนอร์สัตว์ มันจะเป็นไปได้ที่จะเขียนคลาสที่จะมีไฟล์Animal. เรื่องนี้เรียบง่าย เข้าใจได้ แต่... การผสมสุนัขและแมวเข้าด้วยกันนั้นไม่ดี พวกเขาไม่ได้เป็นเพื่อนกัน นอกจากนี้หากใครได้รับภาชนะดังกล่าว เขาอาจโยนแมวจากภาชนะใส่สุนัขโดยไม่ได้ตั้งใจ...ซึ่งจะไม่เกิดผลดีแต่อย่างใด และยาชื่อสามัญจะช่วยเราที่นี่ ตัวอย่างเช่น ลองเขียนการใช้งานดังนี้:
public static class Box<T> {
  List<T> slots = new ArrayList<>();
  public List<T> getSlots() {
    return slots;
  }
}
ชั้นเรียนของเราจะทำงานกับวัตถุประเภทที่ระบุโดยชื่อทั่วไป T นี่คือนามแฝงชนิดหนึ่ง เพราะ ชื่อคลาสทั่วไประบุไว้ จากนั้นเราจะได้รับมันเมื่อประกาศคลาส:
public static void main(String[] args) {
  Box<Cat> catBox = new Box<>();
  Cat murzik = new Cat();
  catBox.getSlots().add(murzik);
}
ดังที่เราเห็น เราได้ระบุว่าเรามีBoxซึ่งใช้ได้กับCat. คอมไพเลอร์ตระหนักดีว่าcatBoxแทนที่จะเป็นประเภททั่วไปTคุณต้องแทนที่ประเภทCatทุกที่ที่มีการระบุชื่อทั่วไปT:
ข้อมูลทั่วไปสำหรับแมว - 6
เหล่านั้น. ต้องขอบคุณBox<Cat>คอมไพเลอร์ที่เข้าใจว่าslotsจริงๆ แล้วควรเป็นList<Cat>อย่างไร เพราะ ข้าง ในBox<Dog>จะslotsประกอบด้วย List<Dog>การประกาศประเภทอาจมีข้อมูลทั่วไปได้หลายอย่าง เช่น
public static class Box<T, V> {
ชื่อทั่วไปสามารถเป็นอะไรก็ได้แม้ว่าจะแนะนำให้ปฏิบัติตามกฎที่ไม่ได้พูด - "แบบแผนการตั้งชื่อพารามิเตอร์ประเภท": ประเภทองค์ประกอบ - E, ประเภทคีย์ - K, ประเภทตัวเลข - N, T - สำหรับประเภท, V - สำหรับ ประเภทค่า อย่างไรก็ตาม จำไว้ว่าเราบอกว่ายาชื่อสามัญไม่แปรผัน กล่าวคือ อย่ารักษาลำดับชั้นการสืบทอด ที่จริงแล้วเราสามารถมีอิทธิพลต่อสิ่งนี้ได้ นั่นคือเรามีโอกาสที่จะสร้างยาชื่อสามัญ COvariant เช่น รักษามรดกให้อยู่ในลำดับเดียวกัน พฤติกรรมนี้เรียกว่า "ประเภทมีขอบเขต" เช่น ประเภทที่จำกัด ตัวอย่างเช่น ชั้นเรียนของเราBoxอาจมีสัตว์ทุกตัว จากนั้นเราจะประกาศชื่อสามัญดังนี้:
public static class Box<T extends Animal> {
Animalนั่นคือเรากำหนดขีดจำกัด บนให้กับคลาส นอกจากนี้เรายังสามารถระบุหลายประเภทหลังคำหลักextendsได้ นี่จะหมายความว่าประเภทที่เราจะใช้งานจะต้องเป็นผู้สืบทอดของคลาสบางคลาสและในขณะเดียวกันก็ใช้อินเทอร์เฟซบางอย่าง ตัวอย่างเช่น:
public static class Box<T extends Animal & Comparable> {
ในกรณีนี้ หากเราพยายามใส่Boxบางสิ่งที่ไม่ใช่ผู้สืบทอดAnimalและไม่ได้นำไปใช้Comparableในระหว่างการคอมไพล์เราจะได้รับข้อผิดพลาด:
error: type argument Cat is not within bounds of type-variable T
ข้อมูลทั่วไปสำหรับแมว - 7

วิธีการพิมพ์

ข้อมูลทั่วไปไม่เพียงใช้เฉพาะในประเภทเท่านั้น แต่ยังใช้ในแต่ละวิธีด้วย สามารถดูการประยุกต์ใช้วิธีการได้ในบทช่วยสอนอย่างเป็นทางการ: " วิธีการทั่วไป "

พื้นหลัง:

ข้อมูลทั่วไปสำหรับแมว - 8
ลองดูภาพนี้ อย่างที่คุณเห็น คอมไพเลอร์จะดูที่ลายเซ็นของเมธอด และเห็นว่าเรากำลังใช้คลาสที่ไม่ได้กำหนดไว้เป็นอินพุต ไม่ได้กำหนดด้วยลายเซ็นว่าเรากำลังส่งคืนวัตถุบางประเภทเช่น วัตถุ. ดังนั้นหากเราต้องการสร้าง ArrayList เราก็ต้องทำสิ่งนี้:
ArrayList<String> object = (ArrayList<String>) createObject(ArrayList.class);
คุณต้องเขียนอย่างชัดเจนว่าเอาต์พุตจะเป็น ArrayList ซึ่งน่าเกลียดและเพิ่มโอกาสในการทำผิดพลาด ตัวอย่างเช่น เราสามารถเขียนเรื่องไร้สาระดังกล่าวได้ และมันจะรวบรวม:
ArrayList object = (ArrayList) createObject(LinkedList.class);
เราสามารถช่วยคอมไพเลอร์ได้หรือไม่? ใช่ ยาชื่อสามัญอนุญาตให้เราทำเช่นนี้ได้ ลองดูตัวอย่างเดียวกัน:
ข้อมูลทั่วไปสำหรับแมว - 9
จากนั้นเราสามารถสร้างวัตถุได้ดังนี้:
ArrayList<String> object = createObject(ArrayList.class);
ข้อมูลทั่วไปสำหรับแมว - 10

ไวด์การ์ด

ตามบทช่วยสอนของ Oracle เกี่ยวกับ Generics โดยเฉพาะส่วน " Wildcards " เราสามารถอธิบาย "ประเภทที่ไม่รู้จัก" ด้วยเครื่องหมายคำถาม Wildcard เป็นเครื่องมือที่มีประโยชน์ในการลดข้อจำกัดบางประการของยาชื่อสามัญ ตัวอย่างเช่น ดังที่เราได้กล่าวไปแล้วข้างต้น ยาชื่อสามัญจะไม่เปลี่ยนแปลง ซึ่งหมายความว่าแม้ว่าคลาสทั้งหมดจะสืบทอด (ชนิดย่อย) ของประเภท Object แต่ก็List<любой тип>ไม่ใช่ประเภทList<Object>ย่อย แต่List<любой тип>มันเป็นประเภทList<?>ย่อย ดังนั้นเราสามารถเขียนโค้ดได้ดังนี้:
public static void printList(List<?> list) {
  for (Object elem: list) {
    System.out.print(elem + " ");
  }
  System.out.println();
}
เช่นเดียวกับยาสามัญทั่วไป (เช่น โดยไม่ต้องใช้ไวด์การ์ด) ยาสามัญที่มีไวด์การ์ดก็สามารถถูกจำกัดได้ ไวด์การ์ดขอบเขตบนดูคุ้นเคย:
public static void printCatList(List<? extends Cat> list) {
  for (Cat cat: list) {
    System.out.print(cat + " ");
  }
  System.out.println();
}
แต่คุณยังสามารถจำกัดได้ด้วยไวด์การ์ดขอบเขตล่าง:
public static void printCatList(List<? super Cat> list) {
ดังนั้นวิธีการจะเริ่มยอมรับแมวทุกตัวรวมถึงลำดับชั้นที่สูงกว่า (ขึ้นอยู่กับวัตถุ)
ข้อมูลทั่วไปสำหรับแมว - 11

ประเภทการลบข้อมูล

เมื่อพูดถึงยาชื่อสามัญ เราควรรู้เกี่ยวกับ “การลบประเภท” ที่จริงแล้ว การลบประเภทเป็นเรื่องเกี่ยวกับข้อเท็จจริงที่ว่ายาชื่อสามัญเป็นข้อมูลสำหรับคอมไพเลอร์ ในระหว่างการทำงานของโปรแกรม ไม่มีข้อมูลเพิ่มเติมเกี่ยวกับยาชื่อสามัญ ซึ่งเรียกว่า "การลบ" การลบออกนี้ส่งผลให้ประเภททั่วไปถูกแทนที่ด้วยประเภทเฉพาะ หากข้อมูลทั่วไปไม่มีขอบเขต ประเภทวัตถุจะถูกแทนที่ หากมีการระบุเส้นขอบ (เช่น<T extends Comparable>) เส้นขอบนั้นจะถูกแทนที่ นี่คือตัวอย่างจากบทช่วยสอนของ Oracle: " Erasure of Generic Types ":
ข้อมูลทั่วไปสำหรับแมว - 12
ดังที่กล่าวไว้ข้างต้น ในตัวอย่างนี้ ข้อมูลทั่วไปTจะถูกลบไปที่เส้นขอบ เช่น ก่อนComparable.
ข้อมูลทั่วไปสำหรับแมว - 13

บทสรุป

ข้อมูลทั่วไปเป็นหัวข้อที่น่าสนใจมาก ฉันหวังว่าหัวข้อนี้เป็นที่สนใจของคุณ โดยสรุป เราสามารถพูดได้ว่ายาชื่อสามัญเป็นเครื่องมือที่ยอดเยี่ยมที่นักพัฒนาได้รับเพื่อแจ้งข้อมูลเพิ่มเติมแก่คอมไพเลอร์เพื่อให้แน่ใจว่าประเภทมีความปลอดภัยในด้านหนึ่งและมีความยืดหยุ่นในอีกด้านหนึ่ง และหากคุณสนใจ ฉันขอแนะนำให้คุณตรวจสอบแหล่งข้อมูลที่ฉันชอบ: #เวียเชสลาฟ
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION