답변:
나는 소리에 목소리를 더하고 일을 명확하게하는 데 찌를 것이다 :
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으로 업그레이드하는 경우입니다.
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
다른 배열 목록뿐만 아니라 단지 포함하는 목록이라는 것을 알 수 없습니다 .
std::list<Person>* foo = new std::list<Person>();
C # 및 Java 제네릭처럼 보이며 원하는 방식으로 작동하지만 뒤에서 다른 일이 발생합니다.
C # 제네릭과 가장 공통적 인 점은 pseudo-classes
Java와 같이 유형 정보를 버리는 대신 특수하게 빌드한다는 점 에서 완전히 다른 주전자입니다.
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 () 함수가 있으면 컴파일됩니다. 그렇지 않으면 그렇지 않습니다. 단순한.
그래서, 당신은 그것을 가지고 있습니다 :-)
int addNames<T>( T first, T second ) { return first + second; }
C #으로 작성할 수 없다는 진술에 반대합니다 . 제네릭 형식은 인터페이스 대신 클래스로 제한 될 수 있으며 +
연산자를 사용하여 클래스를 선언하는 방법이 있습니다.
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*.
Anders Hejlsberg 자신도 여기서 " C #, Java 및 C ++의 제네릭 "의 차이점을 설명했습니다 .
차이점이 무엇인지에 대한 좋은 대답이 이미 많으 므로 약간 다른 관점을 제시하고 이유를 추가하겠습니다 .
이미 설명했듯이, 주요 차이점은 유형 삭제입니다 . 즉, 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와 함께 작동합니다.
ArrayList<T>
(숨겨진) 정적 Class<T>
필드를 가진 내부적으로 명명 된 새 유형으로 생성 될 수 있습니다 . 일반 lib의 새 버전이 1.5+ 바이트 코드와 함께 배포되는 한 1.4-JVM에서 실행될 수 있습니다.
이전 게시물에 대한 후속 조치.
템플릿은 사용 된 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
함수가 아니라 유형 인 경우이 명령문은 여전히 유효하며 종종 생성자 호출 인 특수 캐스트 (함수 스타일 캐스트)를 수행합니다. 컴파일러는 우리가 의미하는 바를 알 수 없으므로 여기서 명확하게해야합니다.
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 #에 이미 존재했기 때문에 기존 코드를 손상시키지 않고 정의를 변경할 수 없었습니다. 따라서 컬렉션과 마찬가지로이 레거시 상태의 핵심 라이브러리에 남아 있습니다.
ArrayList
을 바꾸고 List<T>
새 네임 스페이스에 넣어야하는 이유는 없습니다 . 실제로 소스 코드에 클래스가 있으면 ArrayList<T>
IL 코드에서 다른 컴파일러 생성 클래스 이름이되므로 이름 충돌이 발생하지 않습니다.
11 개월 늦었지만이 질문은 일부 Java Wildcard에 대한 준비가 된 것 같습니다.
이것은 Java의 구문 기능입니다. 방법이 있다고 가정하십시오.
public <T> void Foo(Collection<T> thing)
그리고 메소드 본문에서 유형 T를 참조 할 필요가 없다고 가정하십시오. 이름 T를 선언 한 다음 한 번만 사용하므로 이름을 생각해야하는 이유는 무엇입니까? 대신 다음과 같이 쓸 수 있습니다.
public void Foo(Collection<?> thing)
물음표는 컴파일러가 해당 지점에서 한 번만 나타나야하는 일반적인 명명 된 형식 매개 변수를 선언 한 것처럼 가장하도록 요청합니다.
와일드 카드로 수행 할 수있는 작업은 없습니다. 명명 된 형식 매개 변수로는 수행 할 수 없습니다 (이는 C ++ 및 C #에서 항상 수행되는 방식입니다).
class Foo<T extends List<?>>
사용 Foo<StringList>
하지만 C #에서 당신은 여분의 형식 매개 변수를 추가 할 수 있습니다 class Foo<T, T2> where T : IList<T2>
와 투박한을 사용합니다 Foo<StringList, String>
.
Wikipedia에는 Java / C # 제네릭 과 Java 제네릭 / C ++ 템플릿을 모두 비교 한 훌륭한 글이 있습니다 . Generics 의 주요 기사 는 약간 복잡해 보이지만 좋은 정보가 있습니다.
가장 큰 불만은 유형 삭제입니다. 즉, 제네릭은 런타임에 적용되지 않습니다. 다음은 주제에 대한 일부 Sun 문서에 대한 링크 입니다.
제네릭은 유형 삭제로 구현됩니다. 제네릭 유형 정보는 컴파일 타임에만 존재하며, 그 후에는 컴파일러가 지 웁니다.
매우 흥미로운 제안 중에서 제네릭을 수정하고 이전 버전과의 호환성을 깨는 방법이 있습니다.
현재 제네릭은 삭제를 사용하여 구현되므로 런타임에 제네릭 형식 정보를 사용할 수 없으므로 코드를 작성하기가 어렵습니다. 제네릭은이 방식으로 구현되어 이전의 비 제네릭 코드와의 호환성을 지원합니다. Restricted Generics는 런타임에 제네릭 형식 정보를 사용할 수있게하여 레거시 비 제네릭 코드를 손상시킵니다. 그러나 Neal Gafter는 이전 버전과의 호환성을 위반하지 않도록 지정된 경우에만 유형을 수정할 수 있도록 제안했습니다.
NB : 의견이 충분하지 않으므로 적절한 답변에 대한 의견으로 자유롭게 이동하십시오.
.net은 어디에서 왔는지 이해하지 못하는 대중적인 신념과 달리 이전 버전과의 호환성을 유지하지 않고 진정한 제네릭을 구현했으며 명시 적으로 노력했습니다. .net 2.0에서 사용하기 위해 제네릭이 아닌 .net 1.0 코드를 제네릭으로 변경할 필요는 없습니다. 제네릭 목록과 제네릭이 아닌 목록은 4.0 이전까지도 .Net framework 2.0에서 계속 사용할 수 있습니다. 따라서 일반이 아닌 ArrayList를 계속 사용했던 이전 코드는 계속 작동하며 이전과 동일한 ArrayList 클래스를 사용합니다. 이전 버전과의 호환성은 항상 1.0부터 지금까지 유지되고 있습니다. 따라서 .net 4.0에서도 1.0 BCL의 제네릭이 아닌 클래스를 사용하도록 선택해야합니다.
따라서 자바가 진정한 제네릭을 지원하기 위해 이전 버전과의 호환성을 깨뜨릴 필요는 없다고 생각합니다.
ArrayList<Foo>
로 채울 것으로 예상되는 이전 메소드에 전달하려고한다고 가정합니다 . 이 경우 가 아닌 , 어떻게 하나 그 일을합니까? ArrayList
Foo
ArrayList<foo>
ArrayList