C ++과 Java에서 "일반"유형의 차이점은 무엇입니까?


답변:


144

그들 사이에는 큰 차이가 있습니다. C ++에서는 제네릭 형식에 대한 클래스 나 인터페이스를 지정할 필요가 없습니다. 그렇기 때문에 타이핑이 느슨해지면 진정한 일반 함수와 클래스를 만들 수 있습니다.

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

위의 방법은 동일한 유형의 두 객체를 추가하며 "+"연산자를 사용할 수있는 모든 유형 T에 사용할 수 있습니다.

Java에서는 전달 된 객체에서 메소드를 호출하려면 다음과 같이 유형을 지정해야합니다.

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

C ++에서 일반 함수 / 클래스는 헤더에서만 정의 할 수 있습니다. 컴파일러는 서로 다른 유형 (함수 호출)에 대해 다른 함수를 생성하기 때문입니다. 따라서 컴파일 속도가 느려집니다. Java에서 컴파일에는 큰 페널티가 없지만 Java는 런타임에 일반 유형이 지워지는 "삭제"라는 기술을 사용하므로 런타임에 Java가 실제로 호출됩니다 ...

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

따라서 Java의 일반적인 프로그래밍은 실제로 유용하지 않으며 새로운 foreach 구문을 돕는 것은 약간의 구문 설탕 일뿐입니다.

편집 : 유용성에 대한 위의 의견은 젊은 자아에 의해 작성되었습니다. Java의 제네릭은 물론 형식 안전성을 지원합니다.


27
그는 그것이 정교한 구문 설탕이라는 것을 완벽하게 정확합니다.
alphazero

31
순전히 구문 설탕이 아닙니다. 컴파일러는이 정보를 사용하여 유형을 확인합니다. 런타임에 정보를 사용할 수 없지만 컴파일 된 "단순한 설탕"을 사용하는 것을 호출하지는 않습니다. 이를 C라고하면 조립 용 구문 설탕 일 뿐이며, 기계 코드 용 구문 설탕
일뿐

42
나는 구문 설탕 이 유용 하다고 생각 합니다.
poitroae

5
제네릭을 인스턴스화하는 데 사용할 수있는 주요 차이점을 놓쳤습니다. C ++에서는 템플릿 <int N>을 사용하여 인스턴스화하는 데 사용되는 숫자에 대해 다른 결과를 얻을 수 있습니다. 컴파일 타임 메타 프로그램에 사용됩니다. 답변 : stackoverflow.com/questions/189172/c-templates-turing-complete
stonemetal

2
당신은 할 수 없습니다 중 하나의 형태로, '유형을 지정'해야 extends하거나 super. 답변이 잘못되었습니다
Marquis of Lorne

124

Java Generics는 C ++ 템플릿 과 크게 다릅니다.

기본적으로 C ++ 템플릿에서는 기본적으로 영광스러운 전 처리기 / 매크로 세트입니다 ( 참고 : 일부 사람들은 유추를 이해할 수 없기 때문에 템플릿 처리가 매크로라고 말하지 않습니다). Java에서는 기본적으로 객체의 상용구 캐스팅을 최소화하기 위해 구문 설탕입니다. 다음은 C ++ 템플릿과 Java 제네릭에 대한 꽤 괜찮은 소개 입니다.

이 점을 자세히 설명하려면 C ++ 템플릿을 사용할 때 기본적으로 #define매크로 를 사용하는 것처럼 다른 코드 사본을 작성 합니다. 이를 통해 int배열의 크기 등을 결정하는 템플릿 정의에 매개 변수 가있는 것과 같은 작업을 수행 할 수 있습니다 .

Java는 그런 식으로 작동하지 않습니다. Java에서 모든 객체는 java.lang.Object 에서 확장됩니다. 되므로 pre-Generics는 다음과 같은 코드를 작성합니다.

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

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

  ...
}

모든 Java 콜렉션 유형이 Object를 기본 유형으로 사용했기 때문에 무엇이든 넣을 수 있기 때문입니다. Java 5는 다음과 같은 작업을 수행 할 수 있도록 제네릭을 추가하고 추가합니다.

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

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

  ...
}

그리고 그것은 모든 Java Generics입니다 : 객체를 캐스팅하기위한 래퍼. Java Generics가 정제되지 않았기 때문입니다. 그들은 유형 삭제를 사용합니다. 이 결정은 Java Generics가 너무 늦게 나왔기 때문에 이전 버전과의 호환성을 깨뜨리고 싶지 않았습니다 (a Map<String, String>가 필요할 때마다 사용할 수 있음 Map). 유형 삭제가 사용되지 않는 .Net / C #과 비교하면 모든 종류의 차이점이 발생합니다 (예 : 기본 유형 IEnumerableIEnumerable<T> 서로 아무 관계도지지 않습니다).

Java 5+ 컴파일러로 컴파일 된 제네릭을 사용하는 클래스는 JDK 1.4에서 사용할 수 있습니다 (Java 5+가 필요한 다른 기능이나 클래스를 사용하지 않는다고 가정).

그렇기 때문에 Java Generics를 구문 설탕 이라고합니다. 합니다.

그러나 제네릭을 수행하는 방법에 대한이 결정은 많은 영향을 미치므로 (최상의) Java 제네릭 FAQ 가 사람들이 Java 제네릭에 대해 가지고있는 많은 질문에 대답하기 위해 생겨났습니다.

C ++ 템플릿에는 Java Generics에없는 많은 기능이 있습니다.

  • 기본 유형 인수 사용

    예를 들면 다음과 같습니다.

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

    Java는 제네릭 형식에서 기본 형식 인수를 사용할 수 없습니다.

  • 기본 유형 인수를 사용합니다 . Java에서 놓칠 수있는 기능 중 하나이지만 이전 버전과의 호환성 이유가 있습니다.

  • Java는 인수의 경계를 허용합니다.

예를 들면 다음과 같습니다.

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

다른 인수를 가진 템플릿 호출은 실제로 다른 유형이라는 점을 강조해야합니다. 정적 멤버도 공유하지 않습니다. Java에서는 그렇지 않습니다.

제네릭과의 차이점 외에도 완전성을 위해 C ++과 Java (및 다른 것 )의 기본 비교가 있습니다. ) 있습니다.

또한 Thinking in Java를 제안 할 수도 있습니다 . C ++ 프로그래머로서 객체와 같은 많은 개념은 이미 제 2의 특성이지만 미묘한 차이점이 있으므로 부품을 훑어 보더라도 소개 텍스트를 갖는 것이 좋습니다.

Java를 배울 때 배우게 될 많은 것들이 모든 라이브러리 (JDK에 포함 된 표준 라이브러리)와 스프링과 같이 일반적으로 사용되는 것을 포함하는 비표준 라이브러리입니다. Java 구문은 C ++ 구문보다 더 장황하며 많은 C ++ 기능 (예 : 연산자 오버로드, 다중 상속, 소멸자 메커니즘 등)을 갖지 않지만 C ++의 하위 집합으로 만들어지는 것은 아닙니다.


1
그것들은 개념 상 동등하지 않습니다. 가장 좋은 예는 흥미롭게 반복되는 템플릿 패턴입니다. 두 번째로 뛰어난 정책 지향 디자인. 세 번째로 좋은 점은 C ++에서 꺾쇠 괄호 (myArray <5>)로 정수를 전달할 수 있다는 사실입니다.
Max Lybbert 2018 년

1
아닙니다. 그것들은 개념 상 동등하지 않습니다. 개념에는 겹치는 부분이 있지만 많지는 않습니다. 둘 다 List <T>를 만들 수 있지만 그 정도는됩니다. C ++ 템플릿은 훨씬 더 발전했습니다.
jalf

5
유형 삭제 문제는 이전 버전과의 호환성을 의미합니다 Map map = new HashMap<String, String>. 이전 JVM에 새 코드를 배포 할 수 있으며 바이트 코드의 유사성으로 인해 실행됩니다.
유발 아담

1
내가 "기본적으로 영광스러운 전 처리기 / 매크로"라고 말한 것을 알 수 있습니다. 각 템플릿 선언이 더 많은 코드를 생성하기 때문에 유추했습니다 (Java / C #과 반대).
cletus

4
템플릿 코드는 복사하여 붙여 넣기와 매우 다릅니다. : 당신이 매크로 확장의 측면에서 생각한다면, 조만간 당신은이 하나 미묘한 버그에 의해 타격을받을 것 womble.decadentplace.org.uk/c++/...
네마냐 Trifunovic

86

C ++에는 템플릿이 있습니다. Java에는 제네릭이 있는데 C ++ 템플릿과 비슷하지만 매우 다릅니다.

템플릿은 이름에서 알 수 있듯이 템플릿 매개 변수를 입력하여 형식이 안전한 코드를 생성하는 데 사용할 수있는 (기다리는 중 ...) 템플릿을 컴파일러에 제공하여 작동합니다.

제네릭은 내가 이해하는 것처럼 다른 방법으로 작동합니다. 유형 매개 변수는 컴파일러에서 사용하여 코드를 사용하여 코드가 안전한지 확인하지만 결과 코드는 전혀 유형없이 생성됩니다.

C ++ 템플릿은 정말 좋은 매크로 시스템으로, Java 제네릭은 자동으로 타입 캐스트를 생성하는 도구로 생각하십시오 .

 


4
이것은 아주 좋고 간결한 설명입니다. 내가 유혹하고 싶은 한 가지 조정은 Java generics가 (일부 조건에서) 안전하다는 보장 된 타입 캐스트를 자동으로 생성하는 도구 라는 것 입니다. 어떤면에서는 C ++와 관련이 있습니다 const. C ++의 객체 constconst-ness가 캐스트 되지 않는 한 포인터를 통해 수정되지 않습니다 . 마찬가지로 Java에서 일반 유형으로 작성된 내재 된 캐스트는 유형 매개 변수가 코드의 어딘가에서 수동으로 캐스트되지 않는 한 "안전한"것으로 보장됩니다.
Laurence Gonsalves

16

C ++ 템플릿의 또 다른 기능으로는 Java 제네릭에는없는 특수화 기능이 있습니다. 이를 통해 특정 유형에 대해 다른 구현을 가질 수 있습니다. 예를 들어 int에 대해 고도로 최적화 된 버전을 유지하면서도 나머지 유형에 대한 일반 버전을 유지할 수 있습니다. 또는 포인터 및 비 포인터 유형에 대해 다른 버전을 가질 수 있습니다. 포인터를 넘길 때 역 참조 된 객체를 조작하려는 경우에 유용합니다.


1
+1 템플릿 전문화는 컴파일 타임 메타 프로그래밍에 매우 중요합니다. 이러한 차이로 인해 Java 제네릭이 훨씬 덜 강력 해집니다.
Faisal Vali

13

필립와 들러 (Philip Wadler)의 Maurice Naftalin의 Java Generics and Collections 에이 주제에 대한 훌륭한 설명이 있습니다. 나는이 책을 강력히 추천한다. 인용 :

Java의 제네릭은 C ++의 템플릿과 유사합니다. ... 구문은 의도적으로 유사하며 시맨틱은 의도적으로 다릅니다. ... 의미 적으로 Java 제네릭은 삭제에 의해 정의되며 C ++ 템플릿은 확장에 의해 정의됩니다.

여기 에서 전체 설명을 읽으 십시오 .

대체 텍스트
(출처 : oreilly.com )


5

기본적으로 AFAIK, C ++ 템플릿은 각 유형에 대한 코드 사본을 생성하는 반면 Java 제네릭은 정확히 동일한 코드를 사용합니다.

예, 당신은 말할 수있는 C ++ 템플릿이 자바 일반에 해당한다는 개념 (더 제대로 자바 제네릭 개념 ++ C에 해당합니다 말을하는 것입니다하지만)

C ++의 템플릿 메커니즘에 익숙하다면 제네릭은 비슷하지만 유사성은 피상적이라고 생각할 수 있습니다. 제네릭은 각 전문 분야마다 새로운 클래스를 생성하지 않으며 "템플릿 메타 프로그래밍"을 허용하지도 않습니다.

보낸 사람 : Java Generics


3

Java (및 C #) 제네릭은 간단한 런타임 형식 대체 메커니즘 인 것 같습니다.
C ++ 템플릿은 필요에 맞게 언어를 수정할 수있는 컴파일 타임 구문입니다. 그것들은 실제로 컴파일러가 컴파일하는 동안 실행하는 순전히 기능적인 언어입니다.


3

C ++ 템플릿의 또 다른 장점은 전문화입니다.

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

이제 포인터로 sum을 호출하면 두 번째 메소드가 호출되고 포인터가 아닌 객체로 sum을 호출하면 첫 번째 메소드가 호출 sum되고 Special오브젝트를 사용하여 호출 하면 세 번째 메소드가 호출됩니다. 나는 이것이 Java로 가능하다고 생각하지 않습니다.


2
Java에는 포인터가 없기 때문일 수 있습니다 .. !! 더 나은 예를 들어 설명해 주시겠습니까?
Bhavuk Mathur

2

한 문장으로 요약하겠습니다. 템플릿은 새로운 유형을 생성하고 제네릭은 기존 유형을 제한합니다.


2
당신의 설명은 너무 간단합니다! 주제를 잘 이해하는 사람들에게는 완벽하게 이해됩니다. 그러나 아직 이해하지 못하는 사람들에게는별로 도움이되지 않습니다. (SO에 대한 질문을하는 사람은 누구입니까?)
Jakub

1

@Keith :

이 코드는 실제로 잘못되었으며 더 작은 글리치 ( template생략, 특수화 구문이 다르게 보임)와는 별도로 , 부분 특수화 함수 템플리트에서 작동 하지 않으며 클래스 템플리트에서만 작동합니다. 그러나 코드는 일반 템플릿 오버로드를 사용하는 대신 부분 템플릿 전문화없이 작동합니다.

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
이것이 왜 대답이 아닌 의견입니까?
Laurence Gonsalves

3
@Laurence : 코멘트가 스택 오버플로에 구현되기 오래 전에 게시 되었기 때문에 한 번만. 또 다른 이유는 주석 일뿐 아니라 질문에 대한 답변이기도합니다. 위의 코드와 같은 것은 Java에서는 불가능합니다.
Konrad Rudolph

1

아래의 답변은 Cracking The Coding Interview Solutions 책에서 13 장까지입니다.

Java 제네릭의 구현은 "type erasure : '라는 아이디어에 기반을두고 있습니다.이 기술은 소스 코드가 JVM (Java Virtual Machine) 바이트 코드로 변환 될 때 매개 변수화 된 유형을 제거합니다. 예를 들어, 다음과 같은 Java 코드가 있다고 가정하십시오.

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

컴파일하는 동안이 코드는 다음과 같이 다시 작성됩니다.

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

Java 제네릭의 사용은 실제로 우리의 기능에 대해 크게 변경되지 않았습니다. 그것은 조금 더 예쁘게 만들었습니다. 이러한 이유로, 자바 제네릭은 때때로 "구문 설탕 :"으로 불린다.

이것은 C ++과는 상당히 다릅니다. C ++에서 템플릿은 본질적으로 영광스러운 매크로 세트이며 컴파일러는 각 유형에 대한 템플릿 코드의 새 복사본을 만듭니다. 이것에 대한 증거는 MyClass 인스턴스가 MyClass와 정적 변수를 공유하지 않는다는 사실입니다. 그러나 MyClass의 Tow 인스턴스는 정적 변수를 공유합니다.

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

Java에서 정적 변수는 다른 유형 매개 변수에 관계없이 MyClass 인스턴스간에 공유됩니다.

Java 제네릭과 C ++ 템플릿에는 다른 많은 차이점이 있습니다. 여기에는 다음이 포함됩니다.

  • C ++ 템플릿은 int와 같은 기본 유형을 사용할 수 있습니다. Java는 Integer를 사용할 수 없으며 대신 사용해야합니다.
  • Java에서는 템플리트의 유형 매개 변수를 특정 유형으로 제한 할 수 있습니다. 예를 들어 제네릭을 사용하여 CardDeck을 구현하고 type 매개 변수가 CardGame에서 확장되도록 지정할 수 있습니다.
  • C ++에서는 type 매개 변수를 인스턴스화 할 수 있지만 Java는이를 지원하지 않습니다.
  • Java에서 유형 매개 변수 (예 : MyClass의 Foo)는 정적 메소드 및 변수에 사용할 수 없습니다. MyClass와 MyClass간에 공유되기 때문입니다. C ++에서 이러한 클래스는 다양하므로 정적 매개 변수 및 변수에 type 매개 변수를 사용할 수 있습니다.
  • Java에서 유형 매개 변수에 관계없이 MyClass의 모든 인스턴스는 동일한 유형입니다. 유형 매개 변수는 런타임에 지워집니다. C ++에서 다른 유형 매개 변수를 가진 인스턴스는 다른 유형입니다.

0

템플릿은 매크로 시스템 일뿐입니다. 구문 설탕. 실제 컴파일 전에 완전히 확장됩니다 (또는 적어도 컴파일러는 마치 마치 것처럼 작동합니다).

예:

우리가 두 가지 기능을 원한다고 가정 해 봅시다. 하나의 함수는 두 개의 시퀀스 (목록, 배열, 벡터 등)를 취하고 내부 곱을 반환합니다. 다른 함수는 길이를 가져 와서 해당 길이의 두 시퀀스를 생성하여 첫 번째 함수에 전달하고 결과를 반환합니다. 문제는 두 번째 함수에서 실수를해서이 두 함수의 길이가 같지 않다는 것입니다. 이 경우 경고를 위해 컴파일러가 필요합니다. 프로그램이 실행될 때가 아니라 컴파일 할 때가 아닙니다.

Java에서는 다음과 같이 할 수 있습니다.

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

C #에서는 거의 같은 것을 쓸 수 있습니다. C ++로 다시 작성하면 템플릿의 무한 확장에 대해 불평하면서 컴파일되지 않습니다.


좋아, 이것은 3 살이지만 어쨌든 응답하고 있습니다. 나는 당신의 요점이 보이지 않습니다. Java가 주석 처리 된 행에 대해 오류를 생성하는 전체 이유는 인수 (A 및 Cons <A>)가 다른 두 개의 A를 기대하는 함수를 호출하기 때문에 실제로는 기본이며 제네릭이 포함되지 않은 경우에도 발생합니다. C ++도 그렇게합니다. 그 외에도이 코드는 실제로 끔찍하기 때문에 암에 걸렸습니다. 그러나 C ++에서는 여전히 그렇게 할 것입니다 .C ++은 Java가 아니지만 C ++의 템플릿의 이점이 아니기 때문에 물론 수정해야합니다.
clocktown

@clocktown 아니오, C ++에서는 그렇게 할 수 없습니다. 많은 수정이 허용되지 않습니다. 그리고 그것은 C ++ 템플릿의 단점입니다.
MigMit

다른 길이에 대해 경고하는 코드는 무엇을해야합니까? 주석 처리 된 예제에서는 일치하지 않는 인수로 인해 오류 만 생성합니다. 그것은 C ++에서도 작동합니다. 의미 론적으로 C ++ 및 Java 의이 엉망보다 더 나은 코드를 입력 할 수 있습니다.
clocktown

그렇습니다. 길이가 다르기 때문에 인수가 정확히 일치하지 않습니다. C ++에서는 그렇게 할 수 없습니다.
MigMit

0

나는 askanydifference 를 인용하고 싶습니다 여기에 :

C ++과 Java의 주요 차이점은 플랫폼에 대한 의존성에 있습니다. C ++은 플랫폼 의존 언어 인 반면 Java는 플랫폼 독립적 언어입니다.

위의 진술은 C ++이 진정한 제네릭 형식을 제공 할 수있는 이유입니다. Java에는 엄격한 검사가 있으므로 C ++에서 허용하는 방식으로 제네릭을 사용할 수 없습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.