C # 및 Java의 제네릭과 C ++의 템플릿의 차이점은 무엇입니까? [닫은]


203

나는 주로 Java를 사용하고 제네릭은 비교적 새롭습니다. Java가 잘못된 결정을 내렸거나 .NET이 더 나은 구현 등을 가지고 있음을 계속 읽습니다.

그렇다면 제네릭에서 C ++, C #, Java의 주요 차이점은 무엇입니까? 각각의 장단점?

답변:


364

나는 소리에 목소리를 더하고 일을 명확하게하는 데 찌를 것이다 :

C # Generics를 사용하면 이와 같은 것을 선언 할 수 있습니다.

List<Person> foo = new List<Person>();

그런 다음 컴파일러는 Person목록에 없는 것을 넣지 못하게 합니다.
뒤에서 C # 컴파일러는 List<Person>.NET dll 파일에 넣지 만 런타임에 JIT 컴파일러는 마치 사람들을 포함하기 위해 특별한 목록 클래스를 작성한 것처럼 새로운 코드 세트를 작성하고 빌드합니다 ListOfPerson.

이것의 장점은 정말 빠르다는 것입니다. 캐스팅이나 다른 것들이 없으며 dll에 이것이 List of라는 정보가 포함되어 있기 때문에 Person나중에 리플렉션을 사용하여 그것을 보는 다른 코드는 Person객체 가 포함되어 있음을 알 수 있습니다 (따라서 인텔리전스 등을 얻습니다).

이것의 단점은 오래된 C # 1.0 및 1.1 코드 (제네릭을 추가하기 전에)가 이러한 새로운 것을 이해하지 못하기 List<something>때문에 수동으로 항목을 다시 변환 List하여 상호 운용해야합니다. C # 2.0 이진 코드는 이전 버전과 호환되지 않기 때문에 큰 문제는 아닙니다. 이것이 일어날 유일한 시간은 오래된 C # 1.0 / 1.1 코드를 C # 2.0으로 업그레이드하는 경우입니다.

Java Generics를 사용하면 이와 같은 것을 선언 할 수 있습니다.

ArrayList<Person> foo = new ArrayList<Person>();

표면에서 그것은 동일하게 보이며 정렬됩니다. 컴파일러는 Person목록에 없는 것을 넣지 못하게 합니다.

차이점은 무대 뒤에서 일어나는 일입니다. C #과 달리 Java는 특별한 ListOfPerson것을 만들지 않고 ArrayList항상 Java로 사용되었던 평범한 오래된 것을 사용합니다 . 배열에서 물건을 꺼낼 때 일반적인 Person p = (Person)foo.get(1);캐스팅 댄스는 여전히 수행해야합니다. 컴파일러는 키 누름을 저장하지만 속도 적중 / 캐스팅은 항상 그렇듯이 발생합니다.
사람들이 "Type Erasure"를 언급 할 때 이것이 바로 그들이 말하는 것입니다. 컴파일러는 캐스트를 삽입 한 다음 Person단순히 목록이 아니라는 사실을 '삭제'합니다.Object

이 접근법의 장점은 제네릭을 이해하지 못하는 오래된 코드는 신경 쓸 필요가 없다는 것입니다. 그것은 여전히 ArrayList그렇듯이 여전히 낡은 것을 다루고 있습니다. Java 세계에서는 제네릭으로 Java 5를 사용하여 코드 컴파일을 지원하고 이전 1.4 또는 이전 JVM에서 실행되도록했기 때문에 이는 Microsoft에서 더 중요합니다.

단점은 앞서 언급 한 속도 적중이며 ListOfPerson, .class 파일에 들어가는 의사 클래스 또는 이와 유사한 것이 없기 때문에 나중에 볼 코드 (반사 또는 다른 컬렉션에서 가져 오는 경우) 변환 된 위치 등 Object)는 Person다른 배열 목록뿐만 아니라 단지 포함하는 목록이라는 것을 알 수 없습니다 .

C ++ 템플릿을 사용하면 이와 같은 것을 선언 할 수 있습니다

std::list<Person>* foo = new std::list<Person>();

C # 및 Java 제네릭처럼 보이며 원하는 방식으로 작동하지만 뒤에서 다른 일이 발생합니다.

C # 제네릭과 가장 공통적 인 점은 pseudo-classesJava와 같이 유형 정보를 버리는 대신 특수하게 빌드한다는 점 에서 완전히 다른 주전자입니다.

C #과 Java는 모두 가상 머신 용으로 설계된 출력을 생성합니다. Person클래스 가있는 코드를 작성하면 두 가지 경우 모두 클래스에 대한 일부 정보 Person가 .dll 또는 .class 파일에 들어가고 JVM / CLR 이이 작업을 수행합니다.

C ++은 원시 x86 이진 코드를 생성합니다. 모든 것이 객체 가 아니며Person 클래스 에 대해 알아야 할 기본 가상 머신이 없습니다 . 복싱 또는 언 박싱이 없으며 함수는 클래스에 속하거나 실제로 아무것도 필요하지 않습니다.

이 때문에 C ++ 컴파일러는 템플릿을 사용하여 수행 할 수있는 작업에 제한을 두지 않습니다. 기본적으로 수동으로 작성할 수있는 모든 코드, 템플릿을 작성할 수 있습니다.
가장 확실한 예는 다음을 추가하는 것입니다.

C # 및 Java에서 제네릭 시스템은 클래스에 사용할 수있는 메소드를 알아야하며이를 가상 머신으로 전달해야합니다. 이를 알 수있는 유일한 방법은 실제 클래스를 하드 코딩하거나 인터페이스를 사용하는 것입니다. 예를 들면 다음과 같습니다.

string addNames<T>( T first, T second ) { return first.Name() + second.Name(); }

해당 코드는 C # 또는 Java로 컴파일되지 않습니다. T 실제로 Name ()이라는 메서드를 제공 . C #에서 다음과 같이 말해야합니다.

interface IHasName{ string Name(); };
string addNames<T>( T first, T second ) where T : IHasName { .... }

그런 다음 addNames에 전달한 항목이 IHasName 인터페이스 등을 구현하는지 확인해야합니다. 자바 문법이 다릅니다 (<T extends IHasName> ) 동일한 문제가 발생합니다.

이 문제에 대한 '고전적인'사례는 이것을하는 함수를 작성하려고합니다.

string addNames<T>( T first, T second ) { return first + second; }

인터페이스를 선언 할 수있는 방법이 없기 때문에 실제로이 코드를 작성할 수 없습니다 +메소드를 . 당신은 실패합니다.

C ++에는 이러한 문제가 없습니다. 컴파일러는 유형을 VM으로 전달하는 것을 신경 쓰지 않습니다. 두 객체 모두에 .Name () 함수가 있으면 컴파일됩니다. 그렇지 않으면 그렇지 않습니다. 단순한.

그래서, 당신은 그것을 가지고 있습니다 :-)


8
C #에서 참조 유형에 대해 생성 된 의사 클래스는 동일한 구현을 공유하므로 정확히 ListOfPeople을 얻지 못합니다. 확인 blogs.msdn.com/ericlippert/archive/2009/07/30/...
표트르 Czapla

4
아니요, 제네릭을 사용하여 Java 5 코드를 컴파일 할 수 없으며 이전 1.4 VM에서 실행되도록 할 수 있습니다 (적어도 Sun JDK는이를 구현하지 않습니다. 일부 타사 도구는 수행함). 이전에 컴파일 된 1.4 JAR을 사용할 수 있습니다. 1.5 / 1.6 코드
finnw

4
int addNames<T>( T first, T second ) { return first + second; }C #으로 작성할 수 없다는 진술에 반대합니다 . 제네릭 형식은 인터페이스 대신 클래스로 제한 될 수 있으며 +연산자를 사용하여 클래스를 선언하는 방법이 있습니다.
Mashmagar

4
@AlexanderMalakhov 의도적으로 관용적이지 않습니다. 요점은 C ++의 관용구에 대해 교육하는 것이 아니라 같은 모양의 코드가 각 언어에 따라 다르게 처리되는 방법을 설명하는 것입니다. 이 목표는 코드 모양이 더 다양해지기 어려웠을 것입니다.
Orion Edwards

3
@phresnel 원칙적으로 동의하지만 관용적 C ++로 해당 스 니펫을 작성하면 C # / Java 개발자에게는 이해하기가 훨씬 어려워서 차이를 설명하는 데 더 나쁜 일을했을 것입니다. 이것에 동의하지 않기로합시다 :-)
Orion Edwards

61

C ++은 "일반"용어를 거의 사용하지 않습니다. 대신 "템플릿"이라는 단어가 사용되고보다 정확합니다. 템플릿은 하나의 기술을 설명 합니다 일반적인 디자인을 달성하기위한 을 합니다.

C ++ 템플릿은 두 가지 주요 이유로 C # 및 Java가 구현하는 것과 매우 다릅니다. 첫 번째 이유는 C ++ 템플릿이 컴파일 타임 유형 인수뿐만 아니라 컴파일 타임 const-value 인수도 허용하기 때문입니다. 템플릿은 정수 또는 함수 시그니처로 제공 될 수 있습니다. 즉, 컴파일 타임에 계산할 때 매우 펑키 한 작업을 수행 할 수 있습니다.

template <unsigned int N>
struct product {
    static unsigned int const VALUE = N * product<N - 1>::VALUE;
};

template <>
struct product<1> {
    static unsigned int const VALUE = 1;
};

// Usage:
unsigned int const p5 = product<5>::VALUE;

이 코드는 C ++ 템플릿의 다른 고유 기능인 템플릿 전문화도 사용합니다. 이 코드 product는 하나의 값 인수가있는 하나의 클래스 템플릿을 정의합니다 . 또한 인수가 1로 평가 될 때마다 사용되는 해당 템플리트의 전문화도 정의합니다.이를 통해 템플리트 정의에 대한 재귀를 정의 할 수 있습니다. 나는 이것이 Andrei Alexandrescu에 의해 처음 발견되었다고 믿는다. .

템플릿 구조화는 데이터 구조에서 구조적 차이를 허용하므로 C ++에서 중요합니다. 전체적으로 템플릿은 유형에 따라 인터페이스를 통합하는 수단입니다. 그러나 이것이 바람직하지만 모든 유형을 구현 내에서 동일하게 취급 할 수는 없습니다. C ++ 템플릿은이를 고려합니다. 이것은 OOP가 인터페이스와 구현 사이에서 가상 메서드를 재정의하는 것과 거의 같은 차이점입니다.

C ++ 템플릿은 알고리즘 프로그래밍 패러다임에 필수적입니다. 예를 들어, 컨테이너에 대한 거의 모든 알고리즘은 컨테이너 유형을 템플릿 유형으로 받아들이고 균일하게 처리하는 함수로 정의됩니다. 실제로, 그것은 옳지 않습니다 : C ++은 컨테이너에서 작동하지 않지만 컨테이너 의 시작과 끝을 가리키는 두 반복자가 정의한 범위 에서 작동합니다 . 따라서 전체 내용은 반복자에 의해 둘러싸입니다. begin <= elements <end.

컨테이너 대신 반복자를 사용하면 전체가 아닌 컨테이너의 일부에서 작동 할 수 있으므로 유용합니다.

C ++의 또 다른 특징은 클래스 템플릿에 대한 부분 특수화 가능성입니다 . 이것은 Haskell 및 기타 기능적 언어의 인수에 대한 패턴 일치와 다소 관련이 있습니다. 예를 들어 요소를 저장하는 클래스를 생각해 봅시다.

template <typename T>
class Store {  }; // (1)

이것은 모든 요소 유형에 적용됩니다. 그러나 특별한 트릭을 적용하여 포인터를 다른 유형보다 효율적으로 저장할 수 있다고 가정 해 봅시다. 모든 포인터 유형 을 부분적으로 전문화하여 이를 수행 할 수 있습니다.

template <typename T>
class Store<T*> {  }; // (2)

이제 한 유형의 컨테이너 템플릿을 인스턴스화 할 때마다 적절한 정의가 사용됩니다.

Store<int> x; // Uses (1)
Store<int*> y; // Uses (2)
Store<string**> z; // Uses (2), with T = string*.

때로는 .net의 제네릭 기능으로 유형 이외의 항목을 키로 사용할 수 있기를 바랍니다. 값 유형 배열이 프레임 워크의 일부인 경우 (구조 내에 고정 크기 배열을 포함하는 이전 API와 상호 작용할 필요가 있다는 점에서 놀랍지 않습니다)를 선언하는 것이 유용합니다. 몇 개의 개별 항목을 포함하고 그 크기가 일반 매개 변수 인 값 유형 배열을 포함하는 클래스. 그대로 가장 가까운 것은 개별 항목을 보유하고 배열을 보유하는 별도의 객체에 대한 참조를 보유하는 클래스 객체를 갖는 것입니다.
supercat

@supercat 레거시 API와 상호 작용하는 경우 마샬링 (속성을 통해 주석을 달 수 있음)을 사용하는 것이 좋습니다. CLR에는 고정 크기 배열이 없으므로 유형이 아닌 템플릿 인수를 사용하면 도움이되지 않습니다.
Konrad Rudolph

내가 수수께끼를 찾는 것은 고정 크기 값 유형 배열을 갖는 것이 어렵지 않아야하고 많은 데이터 유형을 값이 아닌 참조로 마샬링 할 수 있다는 것입니다. 값별로 마샬링하는 것은 실제로 다른 방법으로 처리 할 수없는 경우에 유용 할 수 있지만, ref-marshal-by-ref는 사용 가능한 거의 모든 경우에서 우수하다고 간주합니다. 크기가 큰 배열은 유용한 기능으로 보입니다.
supercat

비 유형 일반 매개 변수가 유용한 또 다른 상황 인 BTW는 차원 수량을 나타내는 데이터 유형이있을 수 있습니다. 수량을 나타내는 인스턴스 내에 차원 정보를 포함 할 수 있지만, 유형 내에 이러한 정보를 가짐으로써 컬렉션이 특정 차원 단위를 나타내는 객체를 보유하도록 지정 될 수 있습니다.
supercat

35

Anders Hejlsberg 자신도 여기서 " C #, Java 및 C ++의 제네릭 "의 차이점을 설명했습니다 .


나는 그 인터뷰를 정말 좋아한다. 그것은 C # 제네릭으로 무슨 일이 일어나고 있는지 나와 같은 비 C # 사람들에게 분명합니다.
Johannes Schaub-litb

18

차이점이 무엇인지에 대한 좋은 대답이 이미 많으 므로 약간 다른 관점을 제시하고 이유를 추가하겠습니다 .

이미 설명했듯이, 주요 차이점은 유형 삭제입니다 . 즉, Java 컴파일러가 일반 유형을 지우고 생성 된 바이트 코드로 끝나지 않는다는 사실입니다. 그러나 문제는 왜 누군가 그렇게 할 것입니까? 말이되지 않습니다! 아니면?

대체 대안은 무엇입니까? 당신이 언어의 제네릭을 구현하지 않는 경우, 어디 않습니다 당신이 그들을 구현? 그리고 그 대답은 가상 머신입니다. 이전 버전과의 호환성을 손상시킵니다.

반면에 유형 삭제를 사용하면 일반 클라이언트를 비 일반 라이브러리와 혼합 할 수 있습니다. 즉, Java 5에서 컴파일 된 코드는 여전히 Java 1.4에 배포 할 수 있습니다.

그러나 Microsoft는 제네릭의 이전 버전과의 호환성을 중단하기로 결정했습니다. 사용자들은 .NET 제네릭 자바 제네릭보다 "더 나은"왜.

물론 썬은 바보 나 겁쟁이가 아닙니다. 그들이 "축소"한 이유는 Java가 제네릭을 도입했을 때 .NET보다 훨씬 오래되고 더 널리 퍼져 있기 때문입니다. (두 세계에서 거의 동시에 도입되었습니다.) 이전 버전과의 호환성을 깨는 것은 큰 고통이었습니다.

또 다른 방법으로 말하면 Java에서 Generics는 언어 의 일부이며 ( 다른 언어가 아닌 Java 에만 적용됨 ) .NET에서 가상 컴퓨터의 일부입니다 (즉 , 모든 언어에 적용되며 C # 및 Visual Basic.NET).

이것을 LINQ, 람다 식, 로컬 변수 형식 유추, 익명 형식 및 식 트리와 같은 .NET 기능과 비교하십시오. 이들은 모두 언어 기능입니다. 그렇기 때문에 VB.NET과 C #간에 미묘한 차이점이 있습니다. 이러한 기능이 VM의 일부인 경우 모든 언어 에서 동일 합니다. 그러나 CLR은 변경되지 않았습니다. .NET 3.5 SP1의 .NET 2.0과 동일합니다. .NET 3.5 라이브러리와 함께 LINQ를 사용하는 C # 프로그램을 컴파일하고 .NET 3.5 라이브러리를 사용하지 않는 경우 .NET 2.0에서 계속 실행할 수 있습니다. 즉 것 없는 제네릭 및 .NET 1.1과 작동하지만 그것은 것입니다 자바와 자바 1.4와 함께 작동합니다.


3
LINQ는 주로 라이브러리 기능입니다 (C # 및 VB도 구문 설탕을 추가 함). 2.0 CLR을 대상으로하는 모든 언어는 System.Core 어셈블리를로드하기 만하면 LINQ를 최대한 활용할 수 있습니다.
Richard Berg

그래, 미안, 좀 더 분명 했어야 했어 LINQ. 나는 모나 딕 표준 쿼리 연산자, LINQ 확장 메소드 또는 IQueryable 인터페이스가 아닌 쿼리 구문을 언급하고있었습니다. 분명히 모든 .NET 언어의 언어를 사용할 수 있습니다.
Jörg W Mittag

1
Java에 대한 다른 옵션을 생각하고 있습니다. 오라클은 이전 버전과의 호환성을 유지하기를 원하지 않지만 유형 정보가 지워지지 않도록 컴파일러 트릭을 만들 수 있습니다. 예를 들어, ArrayList<T>(숨겨진) 정적 Class<T>필드를 가진 내부적으로 명명 된 새 유형으로 생성 될 수 있습니다 . 일반 lib의 새 버전이 1.5+ 바이트 코드와 함께 배포되는 한 1.4-JVM에서 실행될 수 있습니다.
Earth Engine

14

이전 게시물에 대한 후속 조치.

템플릿은 사용 된 IDE에 관계없이 인텔리전스에서 C ++가 심하게 실패하는 주된 이유 중 하나입니다. 템플릿 전문화로 인해 특정 멤버가 존재하는지 여부를 IDE에서 실제로 확인할 수 없습니다. 치다:

template <typename T>
struct X {
    void foo() { }
};

template <>
struct X<int> { };

typedef int my_int_type;

X<my_int_type> a;
a.|

이제 커서가 표시된 위치에 있고 IDE가 그 시점에서 멤버 a가 무엇을 가지고 있는지 말하기가 어렵습니다 . 다른 언어의 경우 구문 분석이 간단하지만 C ++의 경우 미리 약간의 평가가 필요합니다.

악화된다. my_int_type클래스 템플릿 내에 정의 된 경우 어떻게 합니까? 이제 해당 유형은 다른 유형 인수에 따라 다릅니다. 그리고 여기에서도 컴파일러조차 실패합니다.

template <typename T>
struct Y {
    typedef T my_type;
};

X<Y<int>::my_type> b;

: 생각의 조금 후, 프로그래머는이 코드는 상기와 동일하다고 결론을 내릴 것 Y<int>::my_type으로 확인 int하므로, b같은 타입이어야 a오른쪽?

잘못된. 컴파일러가이 문을 해결하려고 할 때 실제로는 Y<int>::my_type아직 알지 못합니다 ! 따라서 이것이 유형이라는 것을 모릅니다. 멤버 함수 또는 필드와 같은 다른 것이 될 수 있습니다. 이로 인해 모호성이 생길 수 있으며 (현재는 아니지만) 컴파일러가 실패합니다. 유형 이름을 명시 적으로 명시해야합니다.

X<typename Y<int>::my_type> b;

이제 코드가 컴파일됩니다. 이 상황에서 모호성이 어떻게 발생하는지 확인하려면 다음 코드를 고려하십시오.

Y<int>::my_type(123);

이 코드 문은 완벽하게 유효하며 C ++에게에 대한 함수 호출을 실행하도록 지시합니다 Y<int>::my_type. 그러나 my_type함수가 아니라 유형 인 경우이 명령문은 여전히 ​​유효하며 종종 생성자 호출 인 특수 캐스트 (함수 스타일 캐스트)를 수행합니다. 컴파일러는 우리가 의미하는 바를 알 수 없으므로 여기서 명확하게해야합니다.


2
동의합니다. 그래도 희망이 있습니다. 자동 완성 시스템과 C ++ 컴파일러는 매우 밀접하게 상호 작용해야합니다. Visual Studio에는 이러한 기능이 없을 것이라고 확신하지만 Eclipse / CDT 또는 GCC 기반의 다른 IDE에서 문제가 발생할 수 있습니다. 희망! :)
Benoît

6

Java와 C # 모두 첫 번째 언어 릴리스 이후에 제네릭을 도입했습니다. 그러나 제네릭이 도입 될 때 핵심 라이브러리가 어떻게 변경되었는지는 차이가 있습니다. C #의 제네릭은 컴파일러 마법되지 않습니다 할 수 없었습니다 그래서 generify 이전 버전과의 호환성을 깨지 않고 기존 라이브러리 클래스를.

예를 들어 Java에서 기존 Collections Framework완전히 일반화되었습니다 . Java에는 일반 및 레거시 비 제네릭 버전의 컬렉션 클래스가 없습니다. 어떤면에서 이것은 훨씬 깨끗합니다. C #에서 컬렉션을 사용해야하는 경우 비 일반 버전을 사용해야 할 이유는 거의 없지만 레거시 클래스는 그대로 남아있어 풍경을 어지럽 힙니다.

또 다른 눈에 띄는 차이점은 Java 및 C #의 Enum 클래스입니다. Java의 Enum은 다음과 같이 다소 구불 구불 한 정의를 가지고 있습니다.

//  java.lang.Enum Definition in Java
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {

( 이것이 왜 그런지에 대한 Angelika Langer의 매우 명확한 설명을 참조하십시오 . 본질적으로 이것은 Java가 문자열에서 Enum 값으로 유형에 안전하게 액세스 할 수 있음을 의미합니다.

//  Parsing String to Enum in Java
Colour colour = Colour.valueOf("RED");

이것을 C # 버전과 비교하십시오.

//  Parsing String to Enum in C#
Colour colour = (Colour)Enum.Parse(typeof(Colour), "RED");

generics가 언어에 도입되기 전에 Enum이 C #에 이미 존재했기 때문에 기존 코드를 손상시키지 않고 정의를 변경할 수 없었습니다. 따라서 컬렉션과 마찬가지로이 레거시 상태의 핵심 라이브러리에 남아 있습니다.


C #의 제네릭조차도 컴파일러의 마법이 아니라 컴파일러가 기존의 라이브러리를 생성하기 위해 더 많은 마법을 사용할 수 있습니다. 이름 ArrayList을 바꾸고 List<T>새 네임 스페이스에 넣어야하는 이유는 없습니다 . 실제로 소스 코드에 클래스가 있으면 ArrayList<T>IL 코드에서 다른 컴파일러 생성 클래스 이름이되므로 이름 충돌이 발생하지 않습니다.
Earth Engine

4

11 개월 늦었지만이 질문은 일부 Java Wildcard에 대한 준비가 된 것 같습니다.

이것은 Java의 구문 기능입니다. 방법이 있다고 가정하십시오.

public <T> void Foo(Collection<T> thing)

그리고 메소드 본문에서 유형 T를 참조 할 필요가 없다고 가정하십시오. 이름 T를 선언 한 다음 한 번만 사용하므로 이름을 생각해야하는 이유는 무엇입니까? 대신 다음과 같이 쓸 수 있습니다.

public void Foo(Collection<?> thing)

물음표는 컴파일러가 해당 지점에서 한 번만 나타나야하는 일반적인 명명 된 형식 매개 변수를 선언 한 것처럼 가장하도록 요청합니다.

와일드 카드로 수행 할 수있는 작업은 없습니다. 명명 된 형식 매개 변수로는 수행 할 수 없습니다 (이는 C ++ 및 C #에서 항상 수행되는 방식입니다).


2
또 다른 11 개월 늦게 ... 명명 된 유형 매개 변수로는 할 수없는 Java 와일드 카드로 수행 할 수있는 작업이 있습니다. 당신은 자바에서이 작업을 수행 할 수 있습니다 class Foo<T extends List<?>>사용 Foo<StringList>하지만 C #에서 당신은 여분의 형식 매개 변수를 추가 할 수 있습니다 class Foo<T, T2> where T : IList<T2>와 투박한을 사용합니다 Foo<StringList, String>.
R. Martinho Fernandes 2016 년



1

C ++ 템플릿은 컴파일 타임에 평가되고 전문화를 지원하므로 실제로 C # 및 Java에 비해 훨씬 강력합니다. 이를 통해 템플릿 메타 프로그래밍이 가능하며 C ++ 컴파일러를 Turing 머신과 동일하게 만듭니다 (즉, 컴파일 과정에서 Turing 머신으로 계산 가능한 모든 것을 계산할 수 있음).


1

Java에서 제네릭은 컴파일러 수준 일 뿐이므로 다음과 같은 이점이 있습니다.

a = new ArrayList<String>()
a.getClass() => ArrayList

'a'의 유형은 문자열 목록이 아닌 배열 목록입니다. 바나나 목록의 유형은 원숭이 목록과 같습니다.

말하자면.


1

매우 흥미로운 제안 중에서 제네릭을 수정하고 이전 버전과의 호환성을 깨는 방법이 있습니다.

현재 제네릭은 삭제를 사용하여 구현되므로 런타임에 제네릭 형식 정보를 사용할 수 없으므로 코드를 작성하기가 어렵습니다. 제네릭은이 방식으로 구현되어 이전의 비 제네릭 코드와의 호환성을 지원합니다. Restricted Generics는 런타임에 제네릭 형식 정보를 사용할 수있게하여 레거시 비 제네릭 코드를 손상시킵니다. 그러나 Neal Gafter는 이전 버전과의 호환성을 위반하지 않도록 지정된 경우에만 유형을 수정할 수 있도록 제안했습니다.

Java 7 Proposals에 대한 Alex Miller의 기사 에서


0

NB : 의견이 충분하지 않으므로 적절한 답변에 대한 의견으로 자유롭게 이동하십시오.

.net은 어디에서 왔는지 이해하지 못하는 대중적인 신념과 달리 이전 버전과의 호환성을 유지하지 않고 진정한 제네릭을 구현했으며 명시 적으로 노력했습니다. .net 2.0에서 사용하기 위해 제네릭이 아닌 .net 1.0 코드를 제네릭으로 변경할 필요는 없습니다. 제네릭 목록과 제네릭이 아닌 목록은 4.0 이전까지도 .Net framework 2.0에서 계속 사용할 수 있습니다. 따라서 일반이 아닌 ArrayList를 계속 사용했던 이전 코드는 계속 작동하며 이전과 동일한 ArrayList 클래스를 사용합니다. 이전 버전과의 호환성은 항상 1.0부터 지금까지 유지되고 있습니다. 따라서 .net 4.0에서도 1.0 BCL의 제네릭이 아닌 클래스를 사용하도록 선택해야합니다.

따라서 자바가 진정한 제네릭을 지원하기 위해 이전 버전과의 호환성을 깨뜨릴 필요는 없다고 생각합니다.


그것은 사람들이 말하는 이전 버전과의 호환성이 아닙니다. 아이디어는 런타임 과의 호환성입니다. .NET 2.0에서 제네릭을 사용하여 작성된 코드 는 이전 버전의 .NET 프레임 워크 / CLR에서 실행할 수 없습니다 . 마찬가지로 Java가 "true"제네릭을 도입하는 경우 최신 Java 코드는 바이트 코드를 변경해야하기 때문에 이전 JVM에서 실행할 수 없습니다.
tzaman

그것은 제네릭이 아닌 .net입니다. 특정 CLR 버전을 대상으로하려면 항상 재 컴파일해야합니다. 바이트 코드 호환성, 코드 호환성이 있습니다. 또한 이전 목록을 사용하는 이전 코드를 새 제네릭 목록을 사용하기 위해 변환해야 할 필요성에 대해 구체적으로 대답했습니다.
Sheepy

1
나는 사람들이 앞으로의 호환성 에 대해 이야기하고 있다고 생각 합니다. 즉 .net 2.0에서 실행되는 .net 2.0 코드는 1.1 런타임이 2.0 "의사 클래스"에 대해 아무것도 모르기 때문에 중단됩니다. 그렇다면 "자바는 순방향 호환성을 유지하기 위해 진정한 제네릭을 구현하지 않습니다"라고 말하면 안됩니까? (뒤로가 아니라)
Sheepy

호환성 문제는 미묘합니다. 문제는 "실제"제네릭을 Java에 추가하면 이전 버전의 Java를 사용하는 모든 프로그램에 영향을 미칠 것이지만 "새로운 개선 된"제네릭을 사용하는 코드는 이러한 객체를 이전 코드와 교환하는 데 어려움을 겪을 것이라고 생각하지 않습니다. 새로운 유형에 대해서는 아무것도 몰랐습니다. 예를 들어, 프로그램이의 인스턴스 ArrayList<Foo>로 채울 것으로 예상되는 이전 메소드에 전달하려고한다고 가정합니다 . 이 경우 가 아닌 , 어떻게 하나 그 일을합니까? ArrayListFooArrayList<foo>ArrayList
supercat
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.