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 #과 비교하면 모든 종류의 차이점이 발생합니다 (예 : 기본 유형 IEnumerable및IEnumerable<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 ++의 객체 const는 const-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.