programing

C#의 Generics와 Java의 차이점은 무엇입니까?템플릿은 C++로 표시됩니까?

procenter 2022. 7. 21. 23:07
반응형

C#의 Generics와 Java의 차이점은 무엇입니까?템플릿은 C++로 표시됩니까?

저는 주로 자바를 사용하고 제네릭스는 비교적 새로운 것입니다.자바가 잘못된 결정을 했다거나 그런 걸 계속 읽어요.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항상 자바에서 사용되었습니다.배열에서 물건을 꺼낼 때, 평상시처럼Person p = (Person)foo.get(1);캐스팅 댄스는 아직 해야 해컴파일러는 키 프레스를 저장하지만 히트/캐스팅 속도는 여전히 항상 그랬던 것처럼 발생합니다.
'유형삭제'라고 하면 이렇게 얘기해요.컴파일러는 당신을 위해 깁스를 삽입하고, 그 다음에 그것이 의도된 목록이라는 사실을 '삭제'한다.Person뿐만 아니라.Object

이 접근방식의 장점은 제네릭스를 이해하지 못하는 오래된 코드는 신경 쓸 필요가 없다는 것입니다.여전히 예전 그대로의 것을 다루고 있다.ArrayList늘 그래왔듯이이는 Java 세계에서 더욱 중요한데, 이는 Java 5를 제네릭스와 함께 사용하여 코드를 컴파일하고 마이크로소프트가 의도적으로 신경 쓰지 않기로 결정한 이전 1.4 또는 이전 JVM에서 실행하기를 원했기 때문입니다.

단점은 앞서 말한 속도 히트, 그리고 또 다른 단점이 없기 때문입니다.ListOfPerson.class 파일에 들어가는 의사 클래스 등의 코드, 나중에 참조하는 코드(반사 또는 변환된 다른 컬렉션에서 꺼내는 경우)Object기타 등)는 이 리스트가 다음 리스트만을 포함하는 것임을 전혀 알 수 없습니다.Person다른 어레이 리스트뿐만이 아닙니다.

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

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

그것은 C#과 Java의 제네릭스와 비슷하고, 당신이 생각하는 대로 할 수 있지만, 이면에서는 다른 일이 일어나고 있습니다.

C# 제네릭스와 가장 공통적인 점은 특수 구축이라는 점입니다.pseudo-classesjava처럼 타입 정보를 버리는 것이 아니라 전혀 다른 문제입니다.

C#과 Java는 모두 가상 머신용으로 설계된 출력을 생성합니다.만약 당신이 어떤 코드를 쓴다면Person클래스, 두 경우 모두 A에 대한 몇 가지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++ 템플릿은 compile-time type 인수뿐만 아니라 compile-time 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로 평가될 때마다 사용되는 템플릿의 전문화도 정의합니다.이를 통해 템플릿 정의를 통한 재귀 정의를 정의할 수 있습니다.나는 이것이 안드레이 알렉산드레스쿠에 의해 처음 발견되었다고 믿는다.

템플릿 전문화는 데이터 구조의 구조적 차이를 허용하기 때문에 C++에서 중요합니다.템플릿 전체는 인터페이스를 여러 유형으로 통합하는 수단입니다.그러나 이는 바람직하지만 구현 내에서 모든 유형을 동등하게 취급할 수는 없습니다.C++ 템플릿은 이 점을 고려합니다.이것은 OOP가 가상 메서드를 덮어쓰기하여 인터페이스와 구현 간에 가져오는 차이와 거의 동일합니다.

C++ 템플릿은 알고리즘 프로그래밍 패러다임에 필수적입니다.예를 들어, 컨테이너에 대한 거의 모든 알고리즘은 컨테이너 유형을 템플릿 유형으로 받아들이고 균등하게 처리하는 함수로 정의됩니다.실제로는 그렇지 않습니다.C++는 컨테이너에서는 동작하지 않습니다.컨테이너의 시작과 끝을 가리키는2개의 반복기에 의해 정의된 범위에서는 동작합니다.따라서 전체 내용은 반복자에 의해 제한됩니다: 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 자신은 여기서의 차이점을 "Generics in C#, Java, and C++"라고 설명했다.

그 차이점에 대해서는 이미 좋은 답변이 많이 나오고 있기 때문에 조금 다른 시각으로 이유를 덧붙이겠습니다.

이미 설명한 바와 같이 주요 차이점은 유형 삭제입니다. 즉, Java 컴파일러가 범용 유형을 삭제하고 생성된 바이트 코드로 끝나지 않습니다.하지만, 문제는: 누가 왜 그렇게 하겠느냐는 것이다.말도 안 돼!아니면 그런가요?

그럼, 대안이 뭐죠?언어로 제네릭스를 구현하지 않으면 어디에 구현합니까?정답은 "가상 머신에서"입니다.이전 버전과의 호환성이 깨집니다.

한편 유형 삭제를 사용하면 일반 클라이언트를 일반 라이브러리가 아닌 라이브러리와 혼재시킬 수 있습니다.즉, Java 5에서 컴파일된 코드를 Java 1.4에 배치할 수 있습니다.

그러나 마이크로소프트는 제네릭스의 하위 호환성을 깨기로 결정했다.그래서.NET Generics는 Java Generics보다 '더 우수'합니다.

물론, 썬은 바보나 겁쟁이는 아니다.그들이 "결국"한 이유는 자바가 훨씬 더 오래되고 널리 퍼져있었기 때문이다.제네릭스를 도입했을 때의 NET.(이것들은 양쪽 세계에서 대략 동시에 도입되었습니다.이전 버전과의 호환성을 깨는 것은 큰 골칫거리였을 것입니다.

바꿔 말하면 Java에서 Generics는 의 언어(다른 언어가 아닌 Java에만 적용됨을 의미)의 일부입니다.NET 가상 머신의 일부입니다(즉, C#과 Visual Basic뿐만 아니라 모든 언어에 적용됩니다).네트워크)

와 비교해 주세요.LINQ, lamda 식, 로컬 변수 유형 추론, 익명 유형 및 식 트리와 같은 NET 기능은 모두 언어 기능입니다.그것이 VB 사이에 미묘한 차이가 있는 이유입니다.NET 및 C#: 이러한 기능이 VM의 일부일 경우 모든 언어로 동일합니다.단, CLR은 변경되지 않았습니다.에서는 그대로입니다.NET 3.5 SP1을 참조해 주세요.NET 2.0에서 LINQ를 사용하는 C# 프로그램을 컴파일 할 수 있습니다.NET 3.5 컴파일러를 계속 실행합니다.NET 2.0(아무것도 사용하지 않는 경우)NET 3.5 라이브러리이 기능은 제네릭 및 에서는 동작하지 않습니다.NET 1.1 단, Java 및 Java 1.4에서 동작합니다.

이전 투고에 대한 후속 조치입니다.

템플릿은 사용되는 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#의 제네릭스는 단순한 컴파일러 매직이 아니기 때문에 하위 호환성을 깨지 않고 기존 라이브러리 클래스를 생성할 수 없었습니다.

예를 들어 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 {

(안젤리카 랑거의 명확한 설명을 참조해 주세요).이는 기본적으로 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");

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

11개월이나 늦었지만 자바 와일드카드 관련 질문이 준비되어 있습니다.

이것은 Java의 구문 기능입니다.다음과 같은 방법이 있다고 가정합니다.

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

방법 본문에서 T형을 참조할 필요가 없다고 가정합니다.이름 T를 선언하고 한 번만 사용하는데 굳이 이름을 생각해야 하는 이유는 무엇입니까?대신 다음과 같이 쓸 수 있습니다.

public void Foo(Collection<?> thing)

물음표는 컴파일러에 대해 사용자가 해당 지점에 한 번만 표시하면 되는 일반 이름 형식 매개 변수를 선언한 것으로 간주하도록 요구합니다.

와일드카드에서는 이름 있는 타입 파라미터로도 할 수 없는 일이 없습니다(이러한 작업은 C++ 및 C#에서 항상 실행됩니다).

Wikipedia 에는 Java/C# 범용 템플릿과 Java 범용 템플릿/C++ 템플릿이 비교되어 있습니다.제네릭스에 대한 주요 기사는 약간 어수선해 보이지만 좋은 정보가 담겨 있다.

가장 큰 불만은 활자 삭제입니다.이 경우 런타임에는 제네릭이 강제되지 않습니다.이 주제에 대한 Sun 문서 링크입니다.

제네릭은 유형 삭제에 의해 구현됩니다.일반 유형 정보는 컴파일 시에만 존재하며 그 후 컴파일러에 의해 삭제됩니다.

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

Java에서는 제네릭은 컴파일러 수준이기 때문에 다음과 같은 것을 얻을 수 있습니다.

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

'a'의 유형은 문자열 목록이 아닌 배열 목록입니다.그래서 바나나 리스트의 종류는 원숭이 리스트와 같다.

말하자면.

다른 매우 흥미로운 제안들 중 하나는 제네릭을 개선하고 하위 호환성을 깨는 것입니다.

현재 제네릭은 소거를 사용하여 구현되고 있습니다.즉, 범용 타입 정보는 런타임에 사용할 수 없기 때문에 어떤 종류의 코드를 작성하기가 어렵습니다.제네릭은 오래된 비제너릭 코드와의 하위 호환성을 지원하기 위해 이러한 방식으로 구현되었습니다.재인증된 제네릭은 실행 시 범용 유형 정보를 사용할 수 있도록 하여 기존 비제너릭 코드를 해제합니다.그러나 Neal Gafter는 하위 호환성을 깨지 않도록 지정된 경우에만 유형을 다시 만들 것을 제안했습니다.

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

NB: 저는 코멘트가 부족하기 때문에, 이것을 코멘트로서 적절한 답변으로 옮겨 주세요.

어디서 왔는지 전혀 모르는 통념과는 달리, .net은 하위 호환성을 깨지 않고 진정한 제네릭스를 구현했고, 이를 위해 노골적인 노력을 기울였습니다..net 2.0에서 사용하기 위해서만 일반적이지 않은 .net 1.0 코드를 일반 코드로 변경할 필요는 없습니다.범용 리스트와 비범용 리스트는 모두 에서 사용할 수 있습니다.Net Framework 2.0을 4.0까지 사용할 수 있는 이유는 하위 호환성뿐입니다.따라서 일반적이지 않은 ArrayList를 사용한 오래된 코드는 계속 작동하며 이전과 동일한 ArrayList 클래스를 사용합니다.1.0 이후 지금까지 하위 코드 호환성이 항상 유지되고 있습니다.따라서 .net 4.0에서도 1.0 BCL 이후의 비일반 클래스를 사용할 수 있는 옵션을 선택해야 합니다.

따라서 진정한 제네릭스를 지원하기 위해 Java가 하위 호환성을 깰 필요는 없다고 생각합니다.

언급URL : https://stackoverflow.com/questions/31693/what-are-the-differences-between-generics-in-c-sharp-and-java-and-templates-i

반응형