Java มี generics และ C ++ ให้รูปแบบการเขียนโปรแกรมที่แข็งแกร่งมากกับtemplate
s ดังนั้นอะไรคือความแตกต่างระหว่าง C ++ และ Java generics?
Java มี generics และ C ++ ให้รูปแบบการเขียนโปรแกรมที่แข็งแกร่งมากกับtemplate
s ดังนั้นอะไรคือความแตกต่างระหว่าง C ++ และ Java generics?
คำตอบ:
มีความแตกต่างใหญ่ระหว่างพวกเขา ใน C ++ คุณไม่จำเป็นต้องระบุคลาสหรือส่วนต่อประสานสำหรับประเภททั่วไป นั่นเป็นเหตุผลที่คุณสามารถสร้างฟังก์ชั่นและชั้นเรียนทั่วไปอย่างแท้จริงด้วยการพิมพ์ที่มีข้อบกพร่อง
template <typename T> T sum(T a, T b) { return a + b; }
วิธีการข้างต้นจะเพิ่มวัตถุสองชนิดที่เหมือนกันและสามารถใช้กับประเภท T ใด ๆ ที่มีเครื่องหมาย "+" อยู่
ใน Java คุณต้องระบุประเภทหากคุณต้องการเรียกใช้เมธอดบนวัตถุที่ส่งผ่านสิ่งที่ต้องการ:
<T extends Something> T sum(T a, T b) { return a.add ( b ); }
ใน C ++ ฟังก์ชั่น / คลาสทั่วไปสามารถกำหนดได้เฉพาะในส่วนหัวเนื่องจากคอมไพเลอร์สร้างฟังก์ชั่นที่แตกต่างกันสำหรับประเภทที่แตกต่างกัน (ที่มันถูกเรียกด้วย) ดังนั้นการรวบรวมจึงช้าลง ใน Java การคอมไพล์ไม่มีการลงโทษที่สำคัญ แต่ Java ใช้เทคนิคที่เรียกว่า "การลบ" ซึ่งประเภททั่วไปจะถูกลบที่ runtime ดังนั้นที่ runtime Java กำลังเรียก ...
Something sum(Something a, Something b) { return a.add ( b ); }
ดังนั้นการเขียนโปรแกรมทั่วไปใน Java จึงไม่มีประโยชน์จริงๆมันเป็นเพียงน้ำตาล syntactic เพียงเล็กน้อยเพื่อช่วยในการสร้าง foreach ใหม่
แก้ไข:ความเห็นข้างต้นเกี่ยวกับประโยชน์ถูกเขียนโดยตัวเองที่อายุน้อยกว่า ข้อมูลทั่วไปของ Java ช่วยด้วยประเภทความปลอดภัยแน่นอน
extends
super
คำตอบไม่ถูกต้อง
Java Generics นั้นแตกต่างอย่างมากกับเทมเพลต C ++
โดยทั่วไปในเทมเพลต C ++ นั้นเป็นชุด preprocessor / มาโครที่ได้รับการยกย่อง ( หมายเหตุ:เนื่องจากบางคนดูเหมือนจะไม่สามารถเข้าใจความคล้ายคลึงได้ฉันไม่ได้บอกว่าการประมวลผลเทมเพลตเป็นมาโคร) ในจาวาพวกมันจะใช้น้ำตาลซินแทคติคเพื่อลดการหล่อของวัตถุ นี่คือที่ดีงามรู้เบื้องต้นเกี่ยวกับภาษา C ++ แม่ VS generics
หากต้องการอธิบายรายละเอียดเกี่ยวกับประเด็นนี้: เมื่อคุณใช้เทมเพลต C ++ คุณจะต้องสร้างสำเนาอีกชุดหนึ่งเหมือนกับว่าคุณใช้#define
มาโคร สิ่งนี้อนุญาตให้คุณทำสิ่งต่าง ๆ เช่นมีint
พารามิเตอร์ในข้อกำหนดเทมเพลตที่กำหนดขนาดของอาร์เรย์และเช่น
Java ไม่ทำงานอย่างนั้น ใน Java วัตถุทั้งหมดมีขอบเขตจากjava.lang.Objectดังนั้น pre-Generics คุณจะต้องเขียนโค้ดดังนี้:
public class PhoneNumbers {
private Map phoneNumbers = new HashMap();
public String getPhoneNumber(String name) {
return (String)phoneNumbers.get(name);
}
...
}
เพราะประเภทคอลเลกชัน Java ทั้งหมดใช้ Object เป็นประเภทฐานเพื่อให้คุณสามารถใส่อะไรก็ได้ Java 5 หมุนไปรอบ ๆ และเพิ่มชื่อสามัญเพื่อให้คุณสามารถทำสิ่งต่าง ๆ เช่น:
public class PhoneNumbers {
private Map<String, String> phoneNumbers = new HashMap<String, String>();
public String getPhoneNumber(String name) {
return phoneNumbers.get(name);
}
...
}
และนั่นคือ Java Generics ทั้งหมด: wrappers สำหรับการคัดเลือกวัตถุ นั่นเป็นเพราะ Java Generics ไม่ได้รับการขัดเกลา พวกเขาใช้ลบประเภท การตัดสินใจนี้เกิดขึ้นเพราะ Java Generics เข้ามาช้าในส่วนที่พวกเขาไม่ต้องการทำลายความเข้ากันได้แบบย้อนหลัง (a Map<String, String>
สามารถใช้งานได้ทุกครั้งที่มีการMap
เรียกใช้) เปรียบเทียบสิ่งนี้กับ. Net / C # โดยที่ไม่ได้ใช้การลบประเภทซึ่งนำไปสู่ความแตกต่างทุกประเภท (เช่นคุณสามารถใช้ประเภทดั้งเดิมและIEnumerable
และIEnumerable<T>
ไม่เกี่ยวข้องกัน)
และคลาสที่ใช้ generics ที่คอมไพล์ด้วยคอมไพเลอร์ Java 5+ นั้นสามารถใช้งานได้บน JDK 1.4 (สมมติว่ามันไม่ใช้ฟีเจอร์หรือคลาสอื่นที่ต้องใช้ Java 5+)
นั่นเป็นเหตุผลที่ Java Generics เรียกว่าน้ำตาลซินแทคติค
แต่การตัดสินใจเกี่ยวกับวิธีการทำ generics นี้มีผลกระทบอย่างลึกซึ้งดังนั้นคำถามที่พบบ่อยเกี่ยวกับ Java Generics (สุดยอด) ได้เด้งขึ้นมาเพื่อตอบคำถามจำนวนมากที่ผู้คนมีเกี่ยวกับ Java Generics
เท็มเพลต C ++ มีคุณสมบัติจำนวนมากที่ Java Generics ไม่มี:
การใช้อาร์กิวเมนต์ชนิดดั้งเดิม
ตัวอย่างเช่น:
template<class T, int i>
class Matrix {
int T[i][i];
...
}
Java ไม่อนุญาตให้ใช้อาร์กิวเมนต์ชนิดดั้งเดิมใน generics
ใช้อาร์กิวเมนต์ชนิดเริ่มต้นซึ่งเป็นคุณลักษณะหนึ่งที่ฉันพลาดใน Java แต่มีเหตุผลด้านความเข้ากันได้ย้อนหลังสำหรับสิ่งนี้
ตัวอย่างเช่น:
public class ObservableList<T extends List> {
...
}
จำเป็นต้องเน้นว่าการเรียกใช้เทมเพลตที่มีอาร์กิวเมนต์ต่างกันเป็นประเภทที่แตกต่างกันจริงๆ พวกเขาไม่ได้แบ่งปันสมาชิกแบบคงที่ ใน Java นี่ไม่ใช่กรณี
นอกเหนือจากความแตกต่างกับยาชื่อสามัญเพื่อความสมบูรณ์นี่คือการเปรียบเทียบพื้นฐานของ C ++ และ Java (และอีกหนึ่ง )
และฉันยังสามารถแนะนำคิดใน Java ในฐานะโปรแกรมเมอร์ C ++ แนวคิดจำนวนมากเช่นวัตถุจะเป็นลักษณะที่สองอยู่แล้ว แต่ก็มีความแตกต่างกันเล็กน้อยดังนั้นมันจึงคุ้มค่าที่จะมีข้อความเกริ่นนำแม้ว่าคุณจะอ่านส่วน
สิ่งที่คุณจะได้เรียนรู้มากมายเมื่อเรียนรู้ Java คือไลบรารีทั้งหมด (ทั้งมาตรฐาน - สิ่งที่มาใน JDK - และไม่เป็นมาตรฐานซึ่งรวมถึงสิ่งที่ใช้กันทั่วไปเช่น Spring) ไวยากรณ์ของ Java เป็น verbose มากกว่า C ++ ไวยากรณ์และไม่มีคุณลักษณะ C ++ มากมาย (เช่นตัวดำเนินการมากไป, การรับมรดกหลายครั้ง, กลไกการทำลายล้าง ฯลฯ ) แต่นั่นก็ไม่ได้ทำให้ C ++ เป็นชุดย่อยอย่างเคร่งครัด
Map map = new HashMap<String, String>
สิ่งสำคัญที่จะทราบว่าประเภทการลบปัญหาวิธีการมากกว่าเพียงแค่ความเข้ากันได้ย้อนหลังสำหรับ หมายความว่าคุณสามารถปรับใช้รหัสใหม่ใน JVM เก่าและจะทำงานเนื่องจากความคล้ายคลึงกันใน bytecode
C ++ มีเทมเพลต Java มีข้อมูลทั่วไปซึ่งดูเหมือน sorta เหมือนแม่แบบ C ++ แต่พวกเขาแตกต่างกันมาก
เทมเพลตทำงานตามชื่อที่แสดงโดยจัดเตรียมเทมเพลตคอมไพเลอร์ด้วย (รอให้มัน ... ) ที่สามารถใช้ในการสร้างรหัสประเภทที่ปลอดภัยโดยการกรอกพารามิเตอร์ของเทมเพลต
Generics อย่างที่ฉันเข้าใจพวกมันทำงานในลักษณะอื่น ๆ : คอมไพเลอร์พารามิเตอร์ถูกใช้โดยคอมไพเลอร์เพื่อตรวจสอบว่าโค้ดที่ใช้พวกเขานั้นปลอดภัยต่อการพิมพ์ แต่โค้ดผลลัพธ์ถูกสร้างขึ้นโดยไม่มีประเภทเลย
คิดว่าเทมเพลต C ++ เป็นระบบมาโครที่ดีจริงๆและ Java generics เป็นเครื่องมือสำหรับสร้าง typecasts โดยอัตโนมัติ
const
's วัตถุใน C ++ จะไม่ถูกแก้ไขผ่านconst
ตัวชี้ยกเว้นว่าconst
-ness ถูก cast ออกไป ทำนองเดียวกันการปลดเปลื้องโดยนัยที่สร้างขึ้นโดยประเภททั่วไปใน Java รับประกันว่าจะ "ปลอดภัย" เว้นแต่ว่าพารามิเตอร์ประเภทจะถูกโยนทิ้งด้วยตนเองที่ใดที่หนึ่งในรหัส
คุณสมบัติอื่นที่แม่แบบ C ++ มี Java generics ที่ไม่ได้เป็นความเชี่ยวชาญ ที่ช่วยให้คุณมีการใช้งานที่แตกต่างกันสำหรับประเภทที่เฉพาะเจาะจง ตัวอย่างเช่นคุณสามารถมีเวอร์ชันที่ปรับให้เหมาะสมสูงสุดสำหรับintในขณะที่ยังคงมีเวอร์ชันทั่วไปสำหรับประเภทที่เหลือ หรือคุณสามารถมีรุ่นที่แตกต่างกันสำหรับประเภทของตัวชี้และชนิดที่ไม่ใช่ตัวชี้ สิ่งนี้มีประโยชน์ถ้าคุณต้องการใช้งานกับวัตถุที่มีการลงทะเบียนเมื่อส่งตัวชี้
มีคำอธิบายที่ดีของหัวข้อนี้ในJava Generics and Collections โดย Maurice Naftalin, Philip Wadler ฉันขอแนะนำหนังสือเล่มนี้ อ้างถึง:
Generics ใน Java คล้ายกับแม่แบบใน C ++ ... ไวยากรณ์นั้นคล้ายกันอย่างจงใจและความหมายต่างกันอย่างจงใจ ... Semantically, Java generics ถูกกำหนดโดยการลบโดยที่ C ++ template ถูกกำหนดโดยการขยาย
โปรดอ่านคำอธิบายที่เต็มรูปแบบที่นี่
(ที่มา: oreilly.com )
โดยพื้นฐานแล้วเทมเพลต AFAIK, C ++ จะสร้างสำเนาของรหัสสำหรับแต่ละประเภทขณะที่ Java generics ใช้รหัสเดียวกันทั้งหมด
ใช่คุณสามารถพูดได้ว่าเทมเพลต C ++ เทียบเท่ากับแนวคิดทั่วไปของ Java (แม้ว่าจะเหมาะสมกว่าที่จะกล่าวว่าเจเนอร์จีของ Java นั้นเทียบเท่ากับแนวคิด C ++)
หากคุณคุ้นเคยกับกลไกแม่แบบของ C ++ คุณอาจคิดว่า generics นั้นคล้ายกัน แต่ความคล้ายคลึงกันนั้นเป็นเพียงผิวเผิน Generics ไม่ได้สร้างคลาสใหม่สำหรับแต่ละความเชี่ยวชาญและพวกเขาไม่อนุญาตให้ "เทมเพลต metaprogramming"
จาก: Java Generics
ชื่อสามัญ Java (และ C #) ดูเหมือนจะเป็นกลไกการทดแทนชนิดรันไทม์อย่างง่าย
เทมเพลต C ++ เป็นคอมไพล์เวลาคอมไพล์ซึ่งให้วิธีการแก้ไขภาษาให้เหมาะกับความต้องการของคุณ พวกเขาเป็นภาษาที่ใช้งานได้จริงซึ่งคอมไพเลอร์ทำงานในระหว่างการคอมไพล์
ข้อดีอีกประการของเทมเพลต C ++ ก็คือความเชี่ยวชาญ
template <typename T> T sum(T a, T b) { return a + b; }
template <typename T> T sum(T* a, T* b) { return (*a) + (*b); }
Special sum(const Special& a, const Special& b) { return a.plus(b); }
ตอนนี้ถ้าคุณโทรหาผลรวมด้วยพอยน์เตอร์วิธีที่สองจะถูกเรียกถ้าคุณเรียกใช้ผลรวมกับวัตถุที่ไม่ใช่ตัวชี้วิธีแรกจะถูกเรียกและถ้าคุณโทรsum
ด้วยSpecial
วัตถุวิธีที่สามจะถูกเรียก ฉันไม่คิดว่านี่เป็นไปได้กับ Java
ฉันจะรวมเป็นประโยคเดียว: เทมเพลตสร้างชนิดใหม่, ข้อมูลทั่วไป จำกัด ประเภทที่มีอยู่
@Keith:
รหัสนั้นผิดจริงและนอกเหนือจากความผิดพลาดเล็ก ๆ น้อย ๆ ( template
เว้นไว้, ความเชี่ยวชาญด้านไวยากรณ์ที่มีลักษณะแตกต่างกัน), ความเชี่ยวชาญเฉพาะบางส่วนไม่สามารถใช้งานได้กับฟังก์ชั่นเทมเพลต, เฉพาะในเทมเพลตชั้นเรียน อย่างไรก็ตามรหัสจะทำงานได้โดยไม่มีความเชี่ยวชาญเฉพาะแม่แบบบางส่วนแทนที่จะใช้การทำเกินพิกัดแบบเดิมธรรมดา:
template <typename T> T sum(T a, T b) { return a + b; }
template <typename T> T sum(T* a, T* b) { return (*a) + (*b); }
คำตอบด้านล่างมาจากหนังสือCracking The Coding Interview Solutions ถึงบทที่ 13 ซึ่งฉันคิดว่าดีมาก
การนำ Java generics มาใช้นั้นมีรากฐานมาจากแนวคิด "การลบประเภท: 'เทคนิคนี้กำจัดประเภทพารามิเตอร์เมื่อแปลซอร์สโค้ดไปเป็น bytecode Java Virtual Machine (JVM) ตัวอย่างเช่นสมมติว่าคุณมีโค้ด Java ด้านล่าง:
Vector<String> vector = new Vector<String>();
vector.add(new String("hello"));
String str = vector.get(0);
ระหว่างการรวบรวมรหัสนี้จะถูกเขียนใหม่ลงใน:
Vector vector = new Vector();
vector.add(new String("hello"));
String str = (String) vector.get(0);
การใช้ Java generics ไม่ได้เปลี่ยนความสามารถของเราไปมากนัก มันทำให้สิ่งต่าง ๆ น่าสนใจยิ่งขึ้น ด้วยเหตุนี้บางครั้ง Java generics จึงถูกเรียกว่า "syntactic sugar: '
สิ่งนี้ค่อนข้างแตกต่างจาก C ++ ใน C ++ เทมเพลตเป็นชุดแมโครที่ได้รับการยกย่องเป็นหลักโดยคอมไพเลอร์สร้างสำเนาใหม่ของรหัสเทมเพลตสำหรับแต่ละประเภท ข้อพิสูจน์เรื่องนี้คือข้อเท็จจริงที่ว่าอินสแตนซ์ของ MyClass จะไม่แชร์ตัวแปรคงที่กับ MyClass อย่างไรก็ตามอินสแตนซ์ของ MyClass จะใช้ตัวแปรแบบสแตติกร่วมกัน
/*** MyClass.h ***/
template<class T> class MyClass {
public:
static int val;
MyClass(int v) { val v;}
};
/*** MyClass.cpp ***/
template<typename T>
int MyClass<T>::bar;
template class MyClass<Foo>;
template class MyClass<Bar>;
/*** main.cpp ***/
MyClass<Foo> * fool
MyClass<Foo> * foo2
MyClass<Bar> * barl
MyClass<Bar> * bar2
new MyClass<Foo>(10);
new MyClass<Foo>(15);
new MyClass<Bar>(20);
new MyClass<Bar>(35);
int fl fool->val; // will equal 15
int f2 foo2->val; // will equal 15
int bl barl->val; // will equal 35
int b2 bar2->val; // will equal 35
ใน Java ตัวแปรแบบคงที่จะถูกแชร์ข้ามอินสแตนซ์ของ MyClass โดยไม่คำนึงถึงพารามิเตอร์ชนิดต่าง ๆ
Java generics และเท็มเพลต C ++ มีความแตกต่างอื่น ๆ อีกจำนวนหนึ่ง เหล่านี้รวมถึง:
เทมเพลตคืออะไรนอกจากระบบมาโคร น้ำตาลทราย พวกเขาจะขยายอย่างเต็มที่ก่อนที่จะรวบรวมจริง
ตัวอย่าง:
สมมติว่าเราต้องการสองฟังก์ชัน ฟังก์ชั่นหนึ่งใช้สองลำดับ (รายการ, อาร์เรย์, เวกเตอร์, อะไรก็ตามที่ไป) ของตัวเลขและส่งคืนผลิตภัณฑ์ภายในของพวกเขา ฟังก์ชั่นอื่นใช้ความยาวสร้างสองลำดับของความยาวนั้นผ่านไปยังฟังก์ชันแรกและส่งคืนผลลัพธ์ จับได้ว่าเราอาจทำผิดในฟังก์ชั่นที่สองเพื่อให้ฟังก์ชั่นทั้งสองนี้ไม่ได้มีความยาวเท่ากัน เราต้องการคอมไพเลอร์เพื่อเตือนเราในกรณีนี้ ไม่ใช่เมื่อโปรแกรมกำลังทำงาน แต่เมื่อกำลังรวบรวม
ใน Java คุณสามารถทำสิ่งนี้:
import java.io.*;
interface ScalarProduct<A> {
public Integer scalarProduct(A second);
}
class Nil implements ScalarProduct<Nil>{
Nil(){}
public Integer scalarProduct(Nil second) {
return 0;
}
}
class Cons<A implements ScalarProduct<A>> implements ScalarProduct<Cons<A>>{
public Integer value;
public A tail;
Cons(Integer _value, A _tail) {
value = _value;
tail = _tail;
}
public Integer scalarProduct(Cons<A> second){
return value * second.value + tail.scalarProduct(second.tail);
}
}
class _Test{
public static Integer main(Integer n){
return _main(n, 0, new Nil(), new Nil());
}
public static <A implements ScalarProduct<A>>
Integer _main(Integer n, Integer i, A first, A second){
if (n == 0) {
return first.scalarProduct(second);
} else {
return _main(n-1, i+1,
new Cons<A>(2*i+1,first), new Cons<A>(i*i, second));
//the following line won't compile, it produces an error:
//return _main(n-1, i+1, first, new Cons<A>(i*i, second));
}
}
}
public class Test{
public static void main(String [] args){
System.out.print("Enter a number: ");
try {
BufferedReader is =
new BufferedReader(new InputStreamReader(System.in));
String line = is.readLine();
Integer val = Integer.parseInt(line);
System.out.println(_Test.main(val));
} catch (NumberFormatException ex) {
System.err.println("Not a valid number");
} catch (IOException e) {
System.err.println("Unexpected IO ERROR");
}
}
}
ใน C # คุณสามารถเขียนได้เกือบเหมือนกัน ลองเขียนใหม่ใน C ++ และมันจะไม่คอมไพล์โดยบ่นเกี่ยวกับการขยายเท็มเพลตที่ไม่สิ้นสุด
ฉันต้องการเสนอราคาข้อแตกต่างที่นี่:
ความแตกต่างที่สำคัญระหว่าง C ++ และ Java นั้นขึ้นอยู่กับการพึ่งพาแพลตฟอร์ม ในขณะที่ C ++ เป็นภาษาที่ขึ้นกับแพลตฟอร์ม Java เป็นภาษาอิสระของแพลตฟอร์ม
ข้อความข้างต้นคือเหตุผลที่ C ++ สามารถให้บริการประเภททั่วไปที่แท้จริงได้ ในขณะที่ Java มีการตรวจสอบที่เข้มงวดและด้วยเหตุนี้พวกเขาไม่อนุญาตให้ใช้ generics วิธีที่ C ++ อนุญาต