문제

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

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

도움이 되었습니까?

해결책

나는 소음에 내 목소리를 추가하고 상황을 명확하게 하기 위해 노력할 것입니다.

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

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

그러면 컴파일러는 당신이 그렇지 않은 것을 넣지 못하게 할 것입니다. Person 목록에.
C# 컴파일러는 뒤에서 List<Person> .NET dll 파일에 추가하지만 런타임에 JIT 컴파일러는 마치 사람을 포함하기 위해 특수 목록 클래스를 작성한 것처럼 새로운 코드 세트를 작성합니다. ListOfPerson.

이것의 장점은 정말 빠르게 만들 수 있다는 것입니다.캐스팅이나 다른 내용이 없으며 dll에는 이것이 목록이라는 정보가 포함되어 있기 때문입니다. 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); 캐스팅 댄스는 아직 끝나지 않았습니다.컴파일러는 키 누르기를 저장하지만, 적중/시전 속도는 항상 그랬던 것처럼 여전히 발생합니다.
사람들이 "유형 삭제"를 언급할 때 이것이 바로 그들이 말하는 내용입니다.컴파일러는 캐스트를 삽입한 다음 그것이 목록이 될 것이라는 사실을 '삭제'합니다. Person 뿐만 아니라 Object

이 접근 방식의 이점은 제네릭을 이해하지 못하는 오래된 코드가 신경 쓸 필요가 없다는 것입니다.아직도 똑같은 낡은 물건을 다루고 있어 ArrayList 언제나 그렇듯이.이는 Java 5를 제네릭과 함께 사용하여 코드 컴파일을 지원하고 Microsoft가 의도적으로 신경 쓰지 않기로 결정한 이전 1.4 또는 이전 JVM에서 실행되도록 지원하기를 원했기 때문에 Java 세계에서 더 중요합니다.

단점은 아까 말씀드렸던 속도타도 있고, 역시 없기 때문에 ListOfPerson 의사 클래스 또는 .class 파일에 들어가는 것과 유사한 것, 나중에 이를 살펴보는 코드(리플렉션을 사용하거나 다음으로 변환된 다른 컬렉션에서 가져오는 경우) Object 등) 다음 항목만 포함하는 목록이라는 것을 어떤 식으로든 알 수 없습니다. Person 다른 배열 목록뿐만 아니라.

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

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 인터페이스 등을 구현하는지 확인해야 합니다.Java 구문이 다릅니다(<T extends IHasName>), 그러나 동일한 문제를 겪고 있습니다.

이 문제의 '전형적인' 사례는 이를 수행하는 함수를 작성하려고 시도하는 것입니다.

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

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

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

자, 여기 있습니다 :-)

다른 팁

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

C ++ 템플릿은 두 가지 주요 이유로 C # 및 Java가 구현하는 것과 매우 다릅니다. 첫 번째 이유는 C ++ 템플릿이 컴파일 타임 유형 인수뿐만 아니라 컴파일 타임 상수 값 인수도 허용하지 않기 때문입니다. 템플릿은 정수 또는 함수 서명으로 제공 될 수 있습니다. 이것은 컴파일 타임에 꽤 펑키 한 일을 할 수 있음을 의미합니다. 계산 : 라코 디스

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

템플릿 전문화는 데이터 구조의 구조적 차이를 허용하기 때문에 C ++에서 중요합니다. 템플릿 전체는 유형간에 인터페이스를 통합하는 수단입니다. 그러나 이것이 바람직하지만 모든 유형이 구현 내에서 동일하게 처리 될 수는 없습니다. C ++ 템플릿은이를 고려합니다. 이것은 OOP가 가상 메소드의 재정의를 통해 인터페이스와 구현간에 만드는 차이점과 매우 유사합니다.

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

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

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

모든 요소 유형에서 작동합니다. 하지만 특별한 트릭을 적용하여 다른 유형보다 더 효율적으로 포인터를 저장할 수 있다고 가정 해 보겠습니다. 모든 포인터 유형을 부분적으로 전문화하여이를 수행 할 수 있습니다. 라코 디스

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

Anders Hejlsberg는 " C #, Java 및 C ++의 제네릭 에서 차이점을 설명했습니다.".

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

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

그렇습니다. 대안은 무엇입니까? 제네릭을 언어로 구현하지 않는 경우 어디서 구현합니까 합니까 ? 그리고 대답은 가상 머신에서입니다. 이전 버전과의 호환성이 깨집니다.

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

그러나 Microsoft는 제네릭에 대한 하위 호환성을 중단하기로 결정했습니다. 이것이 .NET Generics가 Java Generics보다 "더 나은"이유입니다.

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

또 다른 방법으로 말하면 : Java에서 Generics는 .NET에서 언어 의 일부입니다 (즉, 다른 언어가 아닌 Java에만 적용됨). 가상 머신 의 일부입니다 (즉, C # 및 Visual Basic.NET뿐만 아니라 모든 언어에 적용됨).

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

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

템플릿은 사용된 IDE에 관계없이 C++가 intellisense에서 엄청나게 실패하는 주된 이유 중 하나입니다.템플릿 전문화로 인해 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 #의 제네릭은 단순한 컴파일러 마술이 아니므로 이전 버전과의 호환성을 깨지 않고 기존 라이브러리 클래스를 생성 할 수 없었습니다.

예를 들어 Java에서는 기존 컬렉션 프레임 워크 완전히 일반화 되었습니다. 자바에는 컬렉션 클래스의 일반 버전과 레거시 비 일반 버전이 모두 없습니다. 어떤면에서 이것은 훨씬 더 깔끔합니다. C #에서 컬렉션을 사용해야하는 경우 갈 이유가 거의 없습니다. 제네릭이 아닌 버전도 있지만 이러한 레거시 클래스는 그대로 유지되어 지형을 복잡하게 만듭니다.

또 다른 주목할만한 차이점은 Java 및 C #의 Enum 클래스입니다. Java의 Enum에는 다음과 같이 다소 구불 구불 한 정의가 있습니다. 라코 디스

(Angelika Langer의 매우 명확한 설명 참조 그 이유는 본질적으로 Java가 문자열에서 Enum 값으로 유형 안전 액세스를 제공 할 수 있음을 의미합니다. 라코 디스

이것을 C # 버전과 비교 : 라코 디스

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

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

이것은 Java의 구문 적 기능입니다.방법이 있다고 가정합니다. 라코 디스

그리고 메소드 본문에서 유형 T를 참조 할 필요가 없다고 가정합니다.이름 T를 선언 한 다음 한 번만 사용하고 있는데 왜 이름을 생각해야합니까?대신 다음과 같이 작성할 수 있습니다. 라코 디스

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

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

Wikipedia에는 Java / C # 제네릭 Java generics / C ++ 템플릿. Generics에 대한 주요 기사 가 약간 복잡해 보이지만 좋은 정보가 있습니다.

가장 큰 불만은 유형 삭제입니다.그 점에서 제네릭은 런타임에 적용되지 않습니다. 다음은 주제에 대한 일부 Sun 문서에 대한 링크입니다. . <인용구>

제네릭은 유형별로 구현됩니다. 삭제 : 일반 유형 정보는 컴파일 타임에만 존재 컴파일러에 의해 지워집니다.

C ++ 템플릿은 컴파일 타임에 평가되고 전문화를 지원하므로 실제로 C # 및 Java 템플릿보다 훨씬 강력합니다.이것은 템플릿 메타 프로그래밍을 허용하고 C ++ 컴파일러를 Turing 머신과 동등하게 만듭니다 (즉, 컴파일 프로세스 동안 Turing 머신으로 계산할 수있는 모든 것을 계산할 수 있습니다).

자바에서 제네릭은 컴파일러 레벨 전용이므로 다음을 얻을 수 있습니다. 라코 디스

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

말하자면

다른 매우 흥미로운 제안 중에서 제네릭을 다듬고 하위 호환성을 깨는 제안이있는 것 같습니다. <인용구>

현재 제네릭이 구현 됨 삭제를 사용합니다. 즉, 일반 유형 정보는 런타임에 사용할 수 있습니다. 작성하기 어려운 종류의 코드.제네릭 이 방식으로 구현되어 이전 버전과의 하위 호환성 일반 코드가 아닙니다.수정 된 제네릭 일반 유형을 만들 것입니다 런타임에 사용 가능한 정보, 레거시 비 제네릭을 깨는 암호.그러나 Neal Gafter는 수정 가능한 제작 유형 만 제안 지정된 경우 중단되지 않도록 이전 버전과의 호환성.

Java 7 제안에 대한 Alex Miller의 기사

주의 : 의견을 제시 할 요점이 충분하지 않으므로이 의견을 적절한 답변으로 옮기십시오.

내가 그것이 어디에서 왔는지 결코 이해하지 못하는 대중적인 믿음과는 달리 .net은 이전 버전과의 호환성을 깨지 않고 진정한 제네릭을 구현했으며이를 위해 명시적인 노력을 기울였습니다. .net 2.0에서 사용하기 위해 제네릭이 아닌 .net 1.0 코드를 제네릭으로 변경할 필요가 없습니다.제네릭 및 비 제네릭 목록은 모두 이전 버전과의 호환성을 이유로 4.0까지도 .Net 프레임 워크 2.0에서 사용할 수 있습니다.따라서 여전히 제네릭이 아닌 ArrayList를 사용하는 이전 코드는 여전히 작동하며 이전과 동일한 ArrayList 클래스를 사용합니다. 역방향 코드 호환성은 1.0부터 지금까지 항상 유지됩니다 ... 따라서 .net 4.0에서도 그렇게하려는 경우 1.0 BCL의 비 제네릭 클래스를 사용하도록 선택해야합니다.

그래서 저는 자바가 진정한 제네릭을 지원하기 위해 이전 버전과의 호환성을 깨야한다고 생각하지 않습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top