Sự khác biệt giữa các loại hình chung chung trong C ++ và Java là gì?


Câu trả lời:


144

Có một sự khác biệt lớn giữa chúng. Trong C ++, bạn không phải chỉ định một lớp hoặc giao diện cho loại chung. Đó là lý do tại sao bạn có thể tạo các hàm và lớp thực sự chung chung, với sự cảnh báo của việc gõ lỏng hơn.

template <typename T> T sum(T a, T b) { return a + b; }

Phương thức trên thêm hai đối tượng cùng loại và có thể được sử dụng cho bất kỳ loại T nào có toán tử "+".

Trong Java, bạn phải chỉ định một loại nếu bạn muốn gọi các phương thức trên các đối tượng được truyền, đại loại như:

<T extends Something> T sum(T a, T b) { return a.add ( b ); }

Trong C ++, các hàm / lớp chung chỉ có thể được định nghĩa trong các tiêu đề, vì trình biên dịch tạo ra các hàm khác nhau cho các loại khác nhau (mà nó được gọi với). Vì vậy, việc biên dịch chậm hơn. Trong Java, quá trình biên dịch không có một hình phạt chính, nhưng Java sử dụng một kỹ thuật gọi là "xóa" trong đó kiểu chung bị xóa trong thời gian chạy, do đó, trong thời gian chạy Java thực sự đang gọi ...

Something sum(Something a, Something b) { return a.add ( b ); }

Vì vậy, lập trình chung trong Java không thực sự hữu ích, nó chỉ là một chút cú pháp để trợ giúp cho cấu trúc foreach mới.

EDIT: ý kiến ​​trên về tính hữu ích được viết bởi một người trẻ hơn. Tất nhiên, sự giúp đỡ của Java giúp loại an toàn.


27
Ông hoàn toàn chính xác rằng nó chỉ là một đường cú pháp phức tạp.
alphazero

31
Nó không hoàn toàn là cú pháp đường. Trình biên dịch sử dụng thông tin này để kiểm tra các loại. Mặc dù thông tin không có sẵn trong thời gian chạy, tôi sẽ không gọi thứ gì đó được biên dịch sử dụng đơn giản là "đường cú pháp". Nếu bạn gọi nó là vậy, thì C chỉ là đường cú pháp để lắp ráp, và đó chỉ là đường cú pháp cho mã máy :)
dtech

42
Tôi nghĩ rằng cú pháp đường là hữu ích.
poitroae

5
Bạn đã bỏ lỡ một điểm khác biệt lớn, những gì bạn có thể sử dụng để khởi tạo một cái chung. Trong c ++, có thể sử dụng mẫu <int N> và nhận kết quả khác cho bất kỳ số nào được sử dụng để khởi tạo nó. Nó được sử dụng để biên dịch meta thời gian. Giống như câu trả lời trong: stackoverflow.com/questions/189172/c-temsheet-turing-complete
stonemetal

2
Bạn không phải 'chỉ định một loại', dưới dạng extendshoặc super. Câu trả lời không chính xác,
Hầu tước Lorne

124

Java Generics là ồ ạt khác nhau để C ++ mẫu.

Về cơ bản trong các mẫu C ++ về cơ bản là một bộ tiền xử lý / macro được tôn vinh ( Lưu ý: vì một số người dường như không thể hiểu được một sự tương tự, tôi không nói rằng xử lý mẫu là một macro). Trong Java, về cơ bản, chúng là đường cú pháp để giảm thiểu việc đúc đối tượng. Đây là một giới thiệu khá hay về các mẫu C ++ so với Java chung .

Để giải thích về điểm này: khi bạn sử dụng mẫu C ++, về cơ bản bạn đang tạo một bản sao khác của mã, giống như khi bạn sử dụng #definemacro. Điều này cho phép bạn thực hiện những việc như có intcác tham số trong định nghĩa mẫu xác định kích thước của mảng và như vậy.

Java không hoạt động như vậy. Trong Java tất cả các đối tượng phạm vi từ java.lang.Object vì vậy, Pre-Generics, bạn sẽ viết mã như thế này:

public class PhoneNumbers {
  private Map phoneNumbers = new HashMap();

  public String getPhoneNumber(String name) {
    return (String)phoneNumbers.get(name);
  }

  ...
}

bởi vì tất cả các loại bộ sưu tập Java đã sử dụng Object làm loại cơ sở của chúng để bạn có thể đặt bất cứ thứ gì vào chúng. Java 5 cuộn xung quanh và thêm các tổng quát để bạn có thể làm những việc như:

public class PhoneNumbers {
  private Map<String, String> phoneNumbers = new HashMap<String, String>();

  public String getPhoneNumber(String name) {
    return phoneNumbers.get(name);
  }

  ...
}

Và đó là tất cả các Generics Java là: trình bao bọc để truyền đối tượng. Đó là vì Java Generics không được tinh chỉnh. Họ sử dụng loại tẩy. Quyết định này được đưa ra vì Java Generics xuất hiện quá muộn đến mức họ không muốn phá vỡ tính tương thích ngược ( Map<String, String>có thể sử dụng bất cứ khi nào aMap được yêu cầu). So sánh điều này với .Net / C # trong đó loại xóa không được sử dụng, điều này dẫn đến tất cả các loại khác biệt (ví dụ: bạn có thể sử dụng các loại nguyên thủy IEnumerableIEnumerable<T>không có mối quan hệ nào với nhau).

Và một lớp sử dụng các tổng quát được biên dịch bằng trình biên dịch Java 5+ có thể sử dụng được trên JDK 1.4 (giả sử nó không sử dụng bất kỳ tính năng hoặc các lớp nào khác yêu cầu Java 5+).

Đó là lý do tại sao Java Generics được gọi là đường cú pháp .

Nhưng quyết định này về cách làm thuốc generic có tác dụng sâu sắc đến mức Câu hỏi thường gặp về Generics Java (tuyệt vời) đã xuất hiện để trả lời cho rất nhiều câu hỏi mà mọi người có về Generics Java.

Các mẫu C ++ có một số tính năng mà Java Generics không có:

  • Sử dụng các đối số kiểu nguyên thủy.

    Ví dụ:

    template<class T, int i>
    class Matrix {
      int T[i][i];
      ...
    }

    Java không cho phép sử dụng các đối số kiểu nguyên thủy trong tổng quát.

  • Sử dụng các đối số kiểu mặc định , là một tính năng tôi bỏ lỡ trong Java nhưng có những lý do tương thích ngược cho việc này;

  • Java cho phép giới hạn các đối số.

Ví dụ:

public class ObservableList<T extends List> {
  ...
}

Nó thực sự cần phải được nhấn mạnh rằng các yêu cầu mẫu với các đối số khác nhau thực sự là các loại khác nhau. Họ thậm chí không chia sẻ các thành viên tĩnh. Trong Java đây không phải là trường hợp.

Bên cạnh sự khác biệt với khái quát, về tính đầy đủ, đây là so sánh cơ bản về C ++ và Java (và một số khác ).

Và tôi cũng có thể đề xuất Tư duy trong Java . Là một lập trình viên C ++, rất nhiều khái niệm như các đối tượng sẽ có bản chất thứ hai nhưng có những khác biệt tinh tế để có thể có một văn bản giới thiệu ngay cả khi bạn đọc lướt các phần.

Rất nhiều thứ bạn sẽ học khi học Java là tất cả các thư viện (cả hai tiêu chuẩn - những gì có trong JDK - và không chuẩn, bao gồm những thứ thường được sử dụng như Spring). Cú pháp Java dài dòng hơn cú pháp C ++ và không có nhiều tính năng C ++ (ví dụ: quá tải toán tử, nhiều kế thừa, cơ chế hủy, v.v.) nhưng điều đó cũng không hoàn toàn biến nó thành một tập hợp con của C ++.


1
Chúng không tương đương trong khái niệm. Ví dụ tốt nhất là mẫu mẫu định kỳ tò mò. Thứ hai tốt nhất là thiết kế theo định hướng chính sách. Điều tốt nhất thứ ba là thực tế là C ++ cho phép các số nguyên được truyền trong dấu ngoặc nhọn (myArray <5>).
Max Lybbert

1
Không, chúng không tương đương trong khái niệm. Có một số chồng chéo trong khái niệm, nhưng không nhiều. Cả hai đều cho phép bạn tạo Danh sách <T>, nhưng đó là khoảng cách. Các mẫu C ++ đi xa hơn nhiều.
jalf

5
Điều quan trọng cần lưu ý rằng vấn đề xóa kiểu có ý nghĩa nhiều hơn là khả năng tương thích ngược Map map = new HashMap<String, String>. Điều đó có nghĩa là bạn có thể triển khai mã mới trên một JVM cũ và nó sẽ chạy do sự tương đồng trong mã byte.
Yuval Adam

1
Bạn sẽ lưu ý rằng tôi đã nói "về cơ bản là một bộ tiền xử lý / macro được tôn vinh". Đó là một sự tương tự vì mỗi khai báo mẫu sẽ tạo ra nhiều mã hơn (trái ngược với Java / C #).
cletus

4
Mã mẫu rất khác so với sao chép và dán. Nếu bạn nghĩ về việc mở rộng vĩ mô, sớm hay muộn bạn sẽ gặp phải các lỗi tinh vi như lỗi này: womble.decadentplace.org.uk/c++/ Lỗi
Nemanja Trifunovic

86

C ++ có các mẫu. Java có các tổng quát, trông giống như các mẫu C ++, nhưng chúng rất, rất khác nhau.

Các mẫu hoạt động, như tên ngụ ý, bằng cách cung cấp cho trình biên dịch một mẫu (chờ nó ...) mà nó có thể sử dụng để tạo mã an toàn loại bằng cách điền vào các tham số mẫu.

Theo tôi hiểu thì các Generics hoạt động theo cách khác: các tham số loại được trình biên dịch sử dụng để xác minh rằng mã sử dụng chúng là loại an toàn, nhưng mã kết quả được tạo ra không có loại nào cả.

Hãy nghĩ về các mẫu C ++ như một hệ thống macro thực sự tốt và Java generic như một công cụ để tự động tạo ra các kiểu chữ.

 


4
Đây là một lời giải thích khá tốt, súc tích. Một điều chỉnh mà tôi muốn thực hiện là Java genericics là một công cụ để tự động tạo ra các kiểu chữ được đảm bảo an toàn (với một số điều kiện). Trong một số cách, chúng có liên quan đến C ++ const. Một đối tượng trong C ++ sẽ không được sửa đổi thông qua một constcon trỏ trừ khi độ sáng constđược bỏ đi. Tương tự, các phôi ẩn được tạo bởi các loại chung trong Java được đảm bảo là "an toàn" trừ khi các tham số loại được bỏ thủ công ở đâu đó trong mã.
Laurence Gonsalves

16

Một tính năng khác mà các mẫu C ++ có mà các thế hệ Java không có là chuyên môn hóa. Điều đó cho phép bạn có một triển khai khác nhau cho các loại cụ thể. Vì vậy, bạn có thể, ví dụ, có một phiên bản được tối ưu hóa cao cho một int , trong khi vẫn có một phiên bản chung cho các loại còn lại. Hoặc bạn có thể có các phiên bản khác nhau cho các loại con trỏ và không con trỏ. Điều này rất hữu ích nếu bạn muốn thao tác trên đối tượng bị hủy đăng ký khi đưa một con trỏ.


1
Chuyên môn hóa mẫu +1 là cực kỳ quan trọng đối với siêu lập trình thời gian biên dịch - chính sự khác biệt này làm cho các khái niệm java kém mạnh mẽ hơn nhiều
Faisal Vali

13

Có một lời giải thích tuyệt vời về chủ đề này trong Java Generics and Collection Collection của Maurice Naftalin, Philip Wadler. Tôi thực sự khuyên bạn nên tham khảo cuôn sach nay. Để trích:

Generics trong Java giống với các mẫu trong C ++. ... Cú pháp tương tự có chủ ý và ngữ nghĩa là khác nhau có chủ ý. ... Về mặt ngữ nghĩa, các tổng quát Java được định nghĩa bằng cách xóa, trong đó các mẫu C ++ được xác định bằng cách mở rộng.

Xin vui lòng đọc giải thích đầy đủ ở đây .

văn bản thay thế
(nguồn: oreilly.com )


5

Về cơ bản, các mẫu AFAIK, C ++ tạo một bản sao của mã cho từng loại, trong khi các mẫu chung của Java sử dụng chính xác cùng một mã.

Có, bạn có thể nói rằng mẫu C ++ tương đương với khái niệm chung về Java (mặc dù nói đúng hơn là nói chung về Java là tương đương với C ++ trong khái niệm)

Nếu bạn đã quen thuộc với cơ chế mẫu của C ++, bạn có thể nghĩ rằng thuốc generic là tương tự nhau, nhưng sự giống nhau là bề ngoài. Generics không tạo ra một lớp mới cho mỗi chuyên ngành, cũng như không cho phép lập trình siêu mẫu khuôn mẫu.

từ: Java Generics


3

Các thế hệ Java (và C #) dường như là một cơ chế thay thế kiểu thời gian chạy đơn giản.
Các mẫu C ++ là một cấu trúc thời gian biên dịch cung cấp cho bạn một cách để sửa đổi ngôn ngữ cho phù hợp với nhu cầu của bạn. Chúng thực sự là một ngôn ngữ có chức năng thuần túy mà trình biên dịch thực thi trong quá trình biên dịch.


3

Một ưu điểm khác của các mẫu C ++ là chuyên môn hóa.

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); }

Bây giờ, nếu bạn gọi sum bằng con trỏ, phương thức thứ hai sẽ được gọi, nếu bạn gọi sum bằng các đối tượng không phải con trỏ, phương thức đầu tiên sẽ được gọi và nếu bạn gọi sumbằng Specialđối tượng, phương thức thứ ba sẽ được gọi. Tôi không nghĩ rằng điều này là có thể với Java.


2
Có thể là do Java không có con trỏ .. !! bạn có thể giải thích với một ví dụ tốt hơn?
Bhavuk Mathur

2

Tôi sẽ tổng hợp nó trong một câu duy nhất: các mẫu tạo ra các loại mới, khái quát hạn chế các loại hiện có.


2
Lời giải thích của bạn rất ngắn gọn! Và có ý nghĩa hoàn hảo cho những người hiểu rõ chủ đề. Nhưng đối với những người chưa hiểu nó, nó không giúp được gì nhiều. (Đó là trường hợp của bất kỳ ai đặt câu hỏi về SO, hiểu chưa?)
Jakub

1

@Keith:

Mã đó thực sự sai và ngoài các trục trặc nhỏ hơn ( templatebỏ qua, cú pháp chuyên môn hóa trông khác nhau), chuyên môn hóa một phần không hoạt động trên các mẫu hàm, chỉ trên các mẫu lớp. Tuy nhiên, mã sẽ hoạt động mà không cần chuyên môn hóa một phần mẫu, thay vào đó sử dụng quá tải cũ đơn giản:

template <typename T> T sum(T a, T b) { return a + b; }
template <typename T> T sum(T* a, T* b) { return (*a) + (*b); }

2
Tại sao đây là một câu trả lời và không phải là một bình luận?
Laurence Gonsalves

3
@Laurence: lần đầu tiên, vì nó đã được đăng rất lâu trước khi các bình luận được triển khai trên Stack Overflow. Đối với một người khác, bởi vì đó không chỉ là một nhận xét - đó còn là một câu trả lời cho câu hỏi: một cái gì đó giống như đoạn mã trên không thể có trong Java.
Konrad Rudolph

1

Câu trả lời dưới đây là từ cuốn sách Cracking The Coding Interview Solutions đến Chương 13, mà tôi nghĩ là rất hay.

Việc triển khai các tổng quát Java bắt nguồn từ một ý tưởng về "loại xóa: 'Kỹ thuật này loại bỏ các loại được tham số hóa khi mã nguồn được dịch sang mã byte của Máy ảo Java (JVM). Ví dụ: giả sử bạn có mã Java bên dưới:

Vector<String> vector = new Vector<String>();
vector.add(new String("hello"));
String str = vector.get(0);

Trong quá trình biên dịch, mã này được viết lại thành:

Vector vector = new Vector();
vector.add(new String("hello"));
String str = (String) vector.get(0);

Việc sử dụng các khái niệm Java không thực sự thay đổi nhiều về khả năng của chúng tôi; nó chỉ làm cho mọi thứ đẹp hơn một chút Vì lý do này, các khái quát về Java đôi khi được gọi là "đường cú pháp: '.

Điều này khá khác với C ++. Trong C ++, các mẫu về cơ bản là một bộ macro được tôn vinh, với trình biên dịch tạo ra một bản sao mới của mã mẫu cho từng loại. Bằng chứng về điều này là trong thực tế là một phiên bản của MyClass sẽ không chia sẻ một biến tĩnh vớiMyClass. Tuy nhiên, các phiên bản của MyClass sẽ chia sẻ một biến tĩnh.

/*** 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

Trong Java, các biến tĩnh được chia sẻ trên các phiên bản của MyClass, bất kể các tham số loại khác nhau.

Các mẫu chung của Java và các mẫu C ++ có một số khác biệt khác. Bao gồm các:

  • Các mẫu C ++ có thể sử dụng các kiểu nguyên thủy, như int. Java không thể và thay vào đó phải sử dụng Integer.
  • Trong Java, bạn có thể giới hạn các tham số loại của mẫu là một loại nhất định. Chẳng hạn, bạn có thể sử dụng thuốc generic để triển khai CardDeck và chỉ định rằng tham số loại phải mở rộng từ CardGame.
  • Trong C ++, tham số loại có thể được khởi tạo, trong khi Java không hỗ trợ điều này.
  • Trong Java, tham số loại (nghĩa là Foo trong MyClass) không thể được sử dụng cho các phương thức và biến tĩnh, vì chúng sẽ được chia sẻ giữa MyClass và MyClass. Trong C ++, các lớp này là khác nhau, vì vậy tham số loại có thể được sử dụng cho các phương thức và biến tĩnh.
  • Trong Java, tất cả các phiên bản của MyClass, bất kể tham số loại của chúng là cùng loại. Các tham số loại được xóa trong thời gian chạy. Trong C ++, các thể hiện với các tham số kiểu khác nhau là các kiểu khác nhau.

0

Mẫu không là gì ngoài một hệ thống vĩ mô. Cú pháp đường. Chúng được mở rộng hoàn toàn trước khi biên dịch thực tế (hoặc, ít nhất, các trình biên dịch hoạt động như thể nó là trường hợp).

Thí dụ:

Hãy nói rằng chúng tôi muốn hai chức năng. Một hàm có hai chuỗi (danh sách, mảng, vectơ, bất cứ thứ gì đi) của các số và trả về sản phẩm bên trong của chúng. Một hàm khác có độ dài, tạo hai chuỗi có độ dài đó, chuyển chúng đến hàm đầu tiên và trả về kết quả của nó. Điều đáng chú ý là chúng ta có thể mắc lỗi trong hàm thứ hai, do đó hai hàm này không thực sự có cùng độ dài. Chúng tôi cần trình biên dịch để cảnh báo chúng tôi trong trường hợp này. Không phải khi chương trình đang chạy, mà là khi nó biên dịch.

Trong Java, bạn có thể làm một cái gì đó như thế này:

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");
        }
    }
}

Trong C # bạn có thể viết gần như cùng một thứ. Cố gắng viết lại nó trong C ++ và nó sẽ không biên dịch, phàn nàn về việc mở rộng vô hạn các mẫu.


Được rồi, đây là 3 tuổi nhưng tôi đang trả lời dù sao đi nữa. Tôi không thấy quan điểm của bạn. Toàn bộ lý do Java tạo ra Lỗi cho Dòng nhận xét đó là vì bạn sẽ gọi một hàm mong đợi hai A có các Đối số khác nhau (A và Nhược điểm <A>) và điều này thực sự cơ bản và cũng xảy ra khi không có chung chung. C ++ cũng vậy. Ngoài ra, mã này đã cho tôi ung thư vì nó thực sự khủng khiếp. Tuy nhiên, bạn vẫn sẽ làm như vậy trong C ++, tất nhiên bạn phải sửa đổi vì C ++ không phải là Java, nhưng đó không phải là nhược điểm của Mẫu của C ++.
clocktown

@clocktown không, bạn KHÔNG THỂ làm điều đó trong C ++. Không có số lượng sửa đổi sẽ cho phép điều đó. Và đó là nhược điểm của các mẫu C ++.
MigMit

Mã của bạn phải làm gì - cảnh báo về độ dài khác nhau - nó không làm. Trong ví dụ nhận xét của bạn, nó chỉ tạo ra lỗi do các đối số không khớp. Điều đó cũng hoạt động trong C ++. Bạn có thể nhập Mã tương đương về mặt ngữ nghĩa và tốt hơn so với mớ hỗn độn này trong C ++ và trong Java.
clocktown

Nó làm. Các đối số không khớp chính xác vì độ dài khác nhau. Bạn không thể làm điều đó trong C ++.
MigMit

0

Tôi muốn trích dẫn câu hỏi tại đây:

Sự khác biệt chính giữa C ++ và Java nằm ở sự phụ thuộc của chúng vào nền tảng. Trong khi, C ++ là ngôn ngữ phụ thuộc nền tảng, Java là ngôn ngữ độc lập với nền tảng.

Tuyên bố trên là lý do tại sao C ++ có thể cung cấp các loại chung chung thực sự. Mặc dù Java có kiểm tra nghiêm ngặt và do đó họ không cho phép sử dụng thuốc generic theo cách mà C ++ cho phép.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.