แนวคิดของการลบออกใน generics ใน Java คืออะไร?
แนวคิดของการลบออกใน generics ใน Java คืออะไร?
คำตอบ:
โดยพื้นฐานแล้ววิธีการใช้งานทั่วไปใน Java ผ่านทางเครื่องมือรวบรวมคอมไพเลอร์ รหัสทั่วไปที่คอมไพล์แล้วจริง ๆ แล้วจะใช้java.lang.Object
ทุกที่ที่คุณพูดถึงT
(หรือพารามิเตอร์ชนิดอื่น) - และมีเมตาดาต้าที่จะบอกคอมไพเลอร์ว่ามันเป็นประเภททั่วไป
เมื่อคุณคอมไพล์โค้ดกับประเภทหรือเมธอดทั่วไปคอมไพเลอร์จะทำงานตามที่คุณต้องการ (เช่นอาร์กิวเมนต์ของประเภทT
คืออะไร) และตรวจสอบในเวลาที่คอมไพล์ว่าคุณกำลังทำสิ่งที่ถูกต้อง แต่โค้ดที่ปล่อยออกมาพูดอีกครั้ง ในแง่ของjava.lang.Object
- คอมไพเลอร์สร้างปลดเปลื้องพิเศษในกรณีที่จำเป็น ณ เวลาดำเนินการ a List<String>
และ a List<Date>
จะเหมือนกันทุกประการ คอมไพเลอร์ชนิดข้อมูลพิเศษถูกลบ
เปรียบเทียบสิ่งนี้กับพูด C # ที่ข้อมูลจะถูกเก็บไว้ที่เวลาดำเนินการให้รหัสที่มีการแสดงออกเช่นtypeof(T)
ซึ่งเทียบเท่าT.class
- ยกเว้นว่าหลังไม่ถูกต้อง (มีความแตกต่างเพิ่มเติมระหว่าง. NET generics และ Java generics โปรดคำนึงถึงคุณ) การลบประเภทเป็นแหล่งที่มาของข้อความเตือน / ข้อผิดพลาด "คี่" หลายข้อเมื่อจัดการกับ Java generics
แหล่งข้อมูลอื่น ๆ :
Object
(ในสถานการณ์ที่พิมพ์ที่อ่อนแอ) เป็นจริงList<String>
) ตัวอย่างเช่น ใน Java นั้นเป็นไปไม่ได้คุณจะพบว่ามันเป็นArrayList
แต่ไม่ใช่ประเภทสามัญดั้งเดิม การเรียงลำดับของสิ่งนี้สามารถเกิดขึ้นในสถานการณ์ซีเรียลไลซ์เซชั่น / การดีซีเรียลไลเซชัน อีกตัวอย่างหนึ่งคือที่ซึ่งคอนเทนเนอร์จะต้องสามารถสร้างอินสแตนซ์ของประเภททั่วไปได้ - คุณต้องผ่านประเภทนั้นใน Java (as Class<T>
)
Class<T>
พารามิเตอร์ให้กับตัวสร้าง (หรือวิธีการทั่วไป) เพียงเพราะ Java ไม่เก็บข้อมูลนั้น ดูEnumSet.allOf
ตัวอย่าง - อาร์กิวเมนต์ชนิดทั่วไปของเมธอดควรเพียงพอ เหตุใดฉันต้องระบุอาร์กิวเมนต์ "ปกติ" ด้วย คำตอบ: ลบประเภท สิ่งเหล่านี้ก่อให้เกิดมลพิษกับ API จากสิ่งที่คุณสนใจคุณใช้. NET generics มาก? (ต่อ)
มันเป็นแบบฝึกหัดที่น่าสนใจที่จะเห็นว่าคอมไพเลอร์กำลังทำอะไรเมื่อทำการลบ - ทำให้แนวคิดทั้งหมดเข้าใจง่ายขึ้นเล็กน้อย มีแฟล็กพิเศษที่คุณสามารถส่งต่อคอมไพเลอร์ไปยังไฟล์ java เอาต์พุตที่มีการลบข้อมูลทั่วไปและใส่ casts ตัวอย่าง:
javac -XD-printflat -d output_dir SomeFile.java
-printflat
เป็นธงที่ได้รับการส่งออกไปยังคอมไพเลอร์ที่สร้างไฟล์ ( -XD
ส่วนหนึ่งคือสิ่งที่บอกjavac
ให้ส่งไปยัง jar ที่ปฏิบัติการได้จริง ๆ แล้วทำการคอมไพล์มากกว่าแค่javac
แต่ฉันเชือนแช ... ) -d output_dir
สิ่งที่จำเป็นเพราะคอมไพเลอร์ต้องการที่ที่จะวางไฟล์. java ใหม่
แน่นอนว่านี่เป็นมากกว่าการลบทิ้ง ทุกสิ่งอัตโนมัติที่คอมไพเลอร์ทำได้ที่นี่ ตัวอย่างเช่นตัวสร้างเริ่มต้นจะถูกแทรกเข้าไปด้วยfor
ลูปforeach-style ใหม่จะถูกขยายเป็นfor
ลูปปกติฯลฯ มันเป็นเรื่องดีที่ได้เห็นสิ่งเล็ก ๆ น้อย ๆ ที่เกิดขึ้นโดยอัตโนมัติ
การลบอย่างแท้จริงหมายถึงข้อมูลประเภทที่มีอยู่ในซอร์สโค้ดจะถูกลบออกจากไบต์ที่รวบรวม ให้เราเข้าใจเรื่องนี้ด้วยรหัส
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class GenericsErasure {
public static void main(String args[]) {
List<String> list = new ArrayList<String>();
list.add("Hello");
Iterator<String> iter = list.iterator();
while(iter.hasNext()) {
String s = iter.next();
System.out.println(s);
}
}
}
หากคุณคอมไพล์รหัสนี้แล้วถอดรหัสมันด้วยตัวถอดรหัส Java คุณจะได้รับสิ่งนี้ ขอให้สังเกตว่ารหัสที่แปลแล้วนั้นไม่มีข้อมูลประเภทร่องรอยอยู่ในซอร์สโค้ดต้นฉบับ
import java.io.PrintStream;
import java.util.*;
public class GenericsErasure
{
public GenericsErasure()
{
}
public static void main(String args[])
{
List list = new ArrayList();
list.add("Hello");
String s;
for(Iterator iter = list.iterator(); iter.hasNext(); System.out.println(s))
s = (String)iter.next();
}
}
jigawot
พูดว่ามันใช้งานได้
ให้เสร็จสมบูรณ์คำตอบจอนสกีตอยู่แล้วที่สมบูรณ์มากคุณจะต้องตระหนักถึงแนวคิดของประเภทการลบออกมาจากความต้องการของการเข้ากันได้กับรุ่นก่อนหน้าของ Java
เริ่มแรกนำเสนอที่ EclipseCon 2007 (ไม่มีให้บริการแล้ว) ความเข้ากันได้รวมถึงคะแนนเหล่านั้น:
คำตอบเดิม:
ดังนั้น:
new ArrayList<String>() => new ArrayList()
มีข้อเสนอสำหรับมากขึ้นเป็นทำให้เป็นจริง Reify เป็น "คำนึงถึงแนวคิดนามธรรมเหมือนจริง" โดยที่ภาษาที่สร้างควรเป็นแนวคิดไม่ใช่แค่น้ำตาลประโยค
ฉันควรจะพูดถึงcheckCollection
วิธีการของ Java 6 ซึ่งจะส่งกลับมุมมองที่ปลอดภัยแบบไดนามิกของคอลเลกชันที่ระบุ ความพยายามที่จะแทรกองค์ประกอบของความผิดประเภทใด ๆ ClassCastException
จะมีผลในทันที
กลไกยาชื่อสามัญในภาษาที่ให้เวลารวบรวม (คงที่) การตรวจสอบชนิด แต่มันก็เป็นไปได้ที่จะเอาชนะกลไกนี้มีบรรยากาศไม่ถูกตรวจสอบ
โดยทั่วไปนี่ไม่ใช่ปัญหาเนื่องจากคอมไพเลอร์ออกคำเตือนในการดำเนินการที่ไม่ได้ตรวจสอบทั้งหมด
อย่างไรก็ตามมีเวลาที่การตรวจสอบชนิดคงที่เพียงอย่างเดียวไม่เพียงพอเช่น:
ClassCastException
ซึ่งระบุว่าองค์ประกอบที่พิมพ์ไม่ถูกต้องถูกใส่ลงในคอลเลกชันที่กำหนดพารามิเตอร์ น่าเสียดายที่ข้อยกเว้นสามารถเกิดขึ้นได้ตลอดเวลาหลังจากที่ใส่องค์ประกอบที่ผิดพลาดดังนั้นโดยทั่วไปจะให้ข้อมูลเพียงเล็กน้อยหรือไม่มีเลยว่าเป็นแหล่งที่มาของปัญหาจริงหรือไม่อัปเดตกรกฎาคม 2555 เกือบสี่ปีต่อมา:
ตอนนี้ (2012) มีรายละเอียดใน " กฎการเข้ากันได้ของ API การย้ายข้อมูล (การทดสอบลายเซ็น) "
ภาษาการเขียนโปรแกรมจาวาดำเนินการเรื่องข้อมูลทั่วไปโดยใช้การลบซึ่งทำให้มั่นใจได้ว่าเวอร์ชันดั้งเดิมและรุ่นทั่วไปจะสร้างไฟล์คลาสที่เหมือนกันยกเว้นข้อมูลเสริมบางอย่างเกี่ยวกับประเภท ความเข้ากันได้ของไบนารีไม่แตกเนื่องจากเป็นไปได้ที่จะแทนที่ไฟล์คลาสดั้งเดิมด้วยไฟล์คลาสทั่วไปโดยไม่ต้องเปลี่ยนหรือคอมไพล์รหัสไคลเอนต์ใหม่
เพื่ออำนวยความสะดวกในการเชื่อมต่อกับรหัสดั้งเดิมที่ไม่ใช่แบบทั่วไปคุณยังสามารถใช้การลบประเภทที่กำหนดพารามิเตอร์เป็นชนิดได้ ชนิดดังกล่าวเรียกว่าชนิดข้อมูลดิบ ( Java Language Specification 3 / 4.8 ) การอนุญาตประเภท raw ยังทำให้แน่ใจได้ถึงความเข้ากันได้ย้อนหลังสำหรับซอร์สโค้ด
ตามนี้
java.util.Iterator
คลาสรุ่นต่อไปนี้เป็นทั้งไบนารีและซอร์สโค้ดที่เข้ากันได้แบบย้อนหลัง:
Class java.util.Iterator as it is defined in Java SE version 1.4:
public interface Iterator {
boolean hasNext();
Object next();
void remove();
}
Class java.util.Iterator as it is defined in Java SE version 5.0:
public interface Iterator<E> {
boolean hasNext();
E next();
void remove();
}
การตอบสนองคำตอบของ Jon Skeet ที่ได้เสริมแล้ว ...
มีการกล่าวกันว่าการใช้ยาชื่อสามัญผ่านการลบทำให้เกิดข้อ จำกัด ที่น่ารำคาญ (เช่นไม่มีnew T[42]
) นอกจากนี้ยังได้รับการกล่าวถึงว่าเหตุผลหลักในการทำสิ่งต่าง ๆ ในลักษณะนี้คือความเข้ากันได้ย้อนหลังในรหัสไบต์ นี่ก็เป็นจริงด้วยเช่นกัน bytecode ที่สร้างขึ้น - เป้าหมาย 1.5 ค่อนข้างแตกต่างจากการคัดเลือกนักแสดง -target 1.4 ในทางเทคนิคแล้วมันเป็นไปได้ (ผ่านกลอุบายอันยิ่งใหญ่) เพื่อเข้าถึงอินสแตนซ์ประเภททั่วไปที่รันไทม์พิสูจน์ว่ามีบางสิ่งในไบต์
จุดที่น่าสนใจมากขึ้น (ซึ่งยังไม่ได้รับการเพิ่มขึ้น) ก็คือการใช้ยาชื่อสามัญโดยใช้การลบทิ้งนั้นมีความยืดหยุ่นมากกว่าเล็กน้อยในสิ่งที่ระบบระดับสูงสามารถทำได้ ตัวอย่างที่ดีของเรื่องนี้คือการนำ JVM ไปใช้กับ CLR บน JVM เป็นไปได้ที่จะใช้ชนิดที่สูงกว่าโดยตรงเนื่องจากความจริงที่ว่า JVM เองไม่มีข้อ จำกัด ในประเภททั่วไป (เนื่องจาก "ประเภท" เหล่านี้ไม่มีประสิทธิภาพ) สิ่งนี้ตรงกันข้ามกับ CLR ซึ่งมีความรู้รันไทม์ของพารามิเตอร์อินสแตนซ์ ด้วยเหตุนี้ CLR เองจึงต้องมีแนวคิดว่าควรใช้ยาสามัญทั่วไปอย่างไรและพยายามลบล้างระบบโดยใช้กฎที่ไม่คาดคิด ผลที่ตามมาก็คือชนิดที่สูงขึ้นของ Scala บน CLR นั้นจะถูกนำไปใช้งานโดยใช้รูปแบบการลบแบบจำลองที่แปลกประหลาดภายในคอมไพเลอร์เอง
การลบอาจไม่สะดวกเมื่อคุณต้องการทำสิ่งซนที่รันไทม์ แต่มันให้ความยืดหยุ่นสูงสุดแก่นักเขียนคอมไพเลอร์ ฉันเดาว่าเป็นส่วนหนึ่งของสาเหตุที่จะไม่หายไปเมื่อเร็ว ๆ นี้
ตามที่ฉันเข้าใจ (เป็น. NET guy) JVMไม่มีแนวคิดเกี่ยวกับ generics ดังนั้นคอมไพเลอร์จะแทนที่พารามิเตอร์ประเภทด้วย Object และดำเนินการร่ายทั้งหมดให้คุณ
ซึ่งหมายความว่า Java generics ไม่มีอะไรนอกจากไวยากรณ์น้ำตาลและไม่เสนอการปรับปรุงประสิทธิภาพสำหรับประเภทค่าที่ต้องใช้ Boxing / unboxing เมื่อผ่านการอ้างอิง
มีคำอธิบายที่ดี ฉันแค่เพิ่มตัวอย่างเพื่อแสดงว่าการลบประเภททำงานอย่างไรกับตัวถอดรหัส
ชั้นเรียนดั้งเดิม
import java.util.ArrayList;
import java.util.List;
public class S<T> {
T obj;
S(T o) {
obj = o;
}
T getob() {
return obj;
}
public static void main(String args[]) {
List<String> list = new ArrayList<>();
list.add("Hello");
// for-each
for(String s : list) {
String temp = s;
System.out.println(temp);
}
// stream
list.forEach(System.out::println);
}
}
โค้ดที่ถอดรหัสจากไบต์ของมัน
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Objects;
import java.util.function.Consumer;
public class S {
Object obj;
S(Object var1) {
this.obj = var1;
}
Object getob() {
return this.obj;
}
public static void main(String[] var0) {
ArrayList var1 = new ArrayList();
var1.add("Hello");
// for-each
Iterator iterator = var1.iterator();
while (iterator.hasNext()) {
String string;
String string2 = string = (String)iterator.next();
System.out.println(string2);
}
// stream
PrintStream printStream = System.out;
Objects.requireNonNull(printStream);
var1.forEach(printStream::println);
}
}
ทำไมต้องใช้ Generices
สั้น ๆ generics เปิดใช้งานประเภท (คลาสและอินเทอร์เฟซ) เป็นพารามิเตอร์เมื่อกำหนดคลาสอินเทอร์เฟซและวิธีการ เช่นเดียวกับพารามิเตอร์ทางการที่คุ้นเคยมากขึ้นที่ใช้ในการประกาศเมธอดพารามิเตอร์ชนิดเป็นวิธีการให้คุณใช้รหัสเดียวกันกับอินพุตที่ต่างกันอีกครั้ง ความแตกต่างคืออินพุตของพารามิเตอร์ที่เป็นทางการคือค่าในขณะที่อินพุตเพื่อพิมพ์พารามิเตอร์เป็นประเภท บทกวีที่ใช้ generics มีประโยชน์มากมายกว่ารหัสที่ไม่ใช่แบบทั่วไป:
การลบประเภทคืออะไร
Generics ถูกนำมาใช้กับภาษา Java เพื่อให้การตรวจสอบประเภทที่เข้มงวดมากขึ้นในเวลารวบรวมและเพื่อสนับสนุนการเขียนโปรแกรมทั่วไป ในการใช้งาน generics คอมไพเลอร์ Java ใช้การลบประเภทกับ:
[NB] - บริดจ์คืออะไร? ในไม่ช้าในกรณีของอินเทอร์เฟซแบบมีพารามิเตอร์เช่นComparable<T>
นี้อาจทำให้วิธีการเพิ่มเติมจะถูกแทรกโดยคอมไพเลอร์ วิธีการเพิ่มเติมเหล่านี้เรียกว่าบริดจ์
วิธีลบการทำงาน
การลบประเภทถูกกำหนดดังนี้: ปล่อยพารามิเตอร์ประเภททั้งหมดจากประเภทพารามิเตอร์และแทนที่ตัวแปรประเภทใด ๆ ด้วยการลบขอบเขตหรือด้วยวัตถุหากไม่มีขอบเขตหรือด้วยการลบขอบเขตซ้ายสุดหากมี หลายขอบเขต นี่คือตัวอย่างบางส่วน:
List<Integer>
, List<String>
และเป็นList<List<String>>
List
List<Integer>[]
List[]
List
ตัวเองเช่นเดียวกับประเภทดิบใด ๆInteger
ตัวเองคล้ายกันสำหรับประเภทใด ๆ โดยไม่มีพารามิเตอร์ประเภทT
ในคำจำกัดความของasList
คือObject
เพราะT
ไม่มีขอบเขตT
ในความหมายของการmax
มีที่Comparable
เพราะได้ผูกพันT
Comparable<? super T>
T
ในคำจำกัดความสุดท้ายของmax
คือObject
เพราะ
T
มีผลผูกพันObject
& Comparable<T>
และเราทำการลบทิ้งขอบเขตซ้ายสุดต้องระวังเมื่อใช้ยาชื่อสามัญ
ใน Java สองวิธีที่แตกต่างไม่สามารถมีลายเซ็นเดียวกัน เนื่องจากยาชื่อสามัญจะถูกนำมาใช้โดยการลบก็ยังตามด้วยวิธีการที่แตกต่างกันสองวิธีไม่สามารถมีลายเซ็นที่มีการลบแบบเดียวกัน คลาสไม่สามารถโหลดสองเมธอดที่มีลายเซ็นมีการลบแบบเดียวกันและคลาสไม่สามารถใช้อินเทอร์เฟซสองตัวที่มีการลบแบบเดียวกันได้
class Overloaded2 {
// compile-time error, cannot overload two methods with same erasure
public static boolean allZero(List<Integer> ints) {
for (int i : ints) if (i != 0) return false;
return true;
}
public static boolean allZero(List<String> strings) {
for (String s : strings) if (s.length() != 0) return false;
return true;
}
}
เราตั้งใจให้รหัสนี้ทำงานดังนี้:
assert allZero(Arrays.asList(0,0,0));
assert allZero(Arrays.asList("","",""));
อย่างไรก็ตามในกรณีนี้การลบลายเซ็นของทั้งสองวิธีจะเหมือนกัน:
boolean allZero(List)
ดังนั้นจึงมีการรายงานการปะทะกันของชื่อในเวลารวบรวม มันเป็นไปไม่ได้ที่จะให้ทั้งสองวิธีชื่อเดียวกันและพยายามแยกแยะระหว่างพวกเขาโดยการบรรทุกเกินพิกัดเพราะหลังจากลบแล้วมันเป็นไปไม่ได้ที่จะแยกแยะวิธีการหนึ่งเรียกจากอีกวิธีหนึ่ง
หวังว่าผู้อ่านจะเพลิดเพลินไปกับ :)