Java의 제네릭에 어떤 문제가 있습니까? [닫은]


49

이 사이트 게시물에서 Java의 제네릭 구현을 거부하는 몇 번을 보았습니다. 이제 나는 그것들을 사용하는 데 아무런 문제가 없다고 정직하게 말할 수 있습니다. 그러나 나는 일반 클래스를 직접 만들려고하지 않았습니다. 그렇다면 Java의 일반 지원과 관련하여 어떤 문제가 있습니까?




클린턴 시작 ( "iBatis 사람") 말했다 "그들은 작동하지 않습니다 ..."
gnat

답변:


54

Java의 일반 구현은 erasure 유형을 사용 합니다 . 즉, 강력한 형식의 제네릭 컬렉션은 실제로 Object런타임 에 형식 이됩니다. 이는 기본 콜렉션에 추가 될 때 기본 유형을 상자에 넣어야한다는 의미이므로 일부 성능 고려 사항이 있습니다. 물론 컴파일 타임 타입 정확성의 이점은 타입 소거의 일반적인 어리 석음을 능가하며 이전 버전과의 호환성에 대한 강박 관념에 중점을 둡니다.



43
의미new ArrayList<String>.getClass() == new ArrayList<Integer>.getClass()
자기 소개

9
@Job 아니오. 그리고 C ++은 템플릿이라고하는 메타 프로그래밍에 완전히 다른 접근법을 사용합니다. 그것은 오리 타이핑을 사용하고 유용한 것들을 할 수 template<T> T add(T v1, T v2) { return v1->add(v2); }있고 거기에서 add두 가지 일을 하는 함수를 만드는 진정한 일반적인 방법이 있습니다. 그들은 add()하나의 매개 변수로 명명 된 메소드 만 있으면됩니다 .
트리니다드

7
@Job 실제로 코드가 생성됩니다. 템플릿은 C 전처리기를 대체하기위한 것으로, 그렇게하기 위해 매우 영리한 트릭 을 만들 수 있어야하며 자체적으로 Turing-complete 언어입니다. Java 제네릭은 형식이 안전한 컨테이너를위한 설탕 일 뿐이며 C # 제네릭은 더 좋지만 여전히 사람의 C ++ 템플릿은 좋지 않습니다.
트리니다드

3
@Job AFAIK, Java 제네릭은 각각의 새로운 제네릭 유형에 대한 클래스를 생성하지 않으며 제네릭 메서드를 사용하는 코드에 타입 캐스트를 추가하기 때문에 실제로 메타 프로그래밍 IMO가 아닙니다. C # 템플릿은 각 제네릭 형식에 대해 새 코드를 생성합니다. 즉, C # 세계에서 List <int> 및 List <double>을 사용하면 각 형식에 대한 코드가 생성됩니다. C # 제네릭은 템플릿과 비교하여 어떤 유형을 공급할 수 있는지 알아야합니다. Add제가 제공 한 간단한 예를 구현할 수는 없습니다. 사전에 적용 할 수있는 클래스를 미리 알지 못하면 방법이 없습니다.
트리니다드

26

이미 제공된 답변은 Java 언어, JVM 및 Java 클래스 라이브러리의 조합에 집중되어 있습니다.

Java 언어에 관한 한 Java의 제네릭에는 아무런 문제가 없습니다. C #과 Java 제네릭에 설명 된 것처럼 Java 제네릭 은 언어 수준 1 에서 상당히 훌륭합니다 .

차선책은 JVM이 제네릭을 직접 지원하지 않기 때문에 몇 가지 중요한 결과가 발생한다는 것입니다.

  • 클래스 라이브러리의 리플렉션 관련 부분은 Java 언어에서 사용 가능한 전체 유형 정보를 노출시킬 수 없습니다.
  • 일부 성능 저하가 관련됩니다

Java 언어가 JVM을 대상으로하고 Java 클래스 라이브러리를 핵심 라이브러리로 사용하여 Java 언어를 어디서나 사용할 수 있기 때문에 내가 만들고있는 구별은 실용적인 것이라고 생각합니다.

1 와일드 카드를 제외하고는 일반적으로 유형 유추를 결정할 수없는 것으로 여겨집니다. 이것은 전혀 언급되지 않은 C #과 Java 제네릭의 주요 차이점입니다. 고마워, 안티몬


14
JVM은 제네릭을 지원할 필요가 없습니다. 이것은 ix86 실제 머신이 템플리트를 지원하지 않기 때문에 C ++에 템플리트가 없다고 말하는 것과 같습니다. 문제는 컴파일러가 제네릭 형식을 구현하기 위해 취하는 방법입니다. 그리고 다시, C ++ 템플릿은 런타임에 페널티가 없으며 전혀 반영되지 않습니다. Java에서해야 할 유일한 이유는 언어 디자인이 좋지 않은 것 같습니다.
트리니다드

2
@Trinidad 그러나 Java에는 제네릭이 없다고 말하지 않았으므로 어떻게 동일한 지 알 수 없습니다. 그리고 네, JVM은하지 않습니다 필요가 C ++이없는 것처럼, 그들을 지원하는 데 필요한 코드를 최적화 할 수 있습니다. 그러나 그것을 최적화하지 않으면 분명히 "무언가"로 간주 될 것입니다.
Roman Starkov

3
내가 주장한 것은 JVM이 리플렉션을 사용할 수 있도록 제네릭을 직접 지원할 필요가 없다는 것입니다. 다른 사람들은 그것을 할 수 있도록했습니다. 문제는 Java가 제네릭에서 새로운 클래스를 생성하지 않는다는 것입니다.
트리니다드

4
@Trinidad : C ++에서는 컴파일러에 충분한 시간과 메모리가 주어진다고 가정하면 템플릿을 사용하여 컴파일시 사용 가능한 정보 (템플릿 컴파일이 Turing-complete이므로)로 수행 할 수있는 모든 작업을 수행 할 수 있습니다. 프로그램을 실행하기 전에 사용할 수없는 정보를 사용하여 템플릿을 만들 수 없습니다. 반대로 .net에서는 프로그램이 입력을 기반으로 형식을 만들 수 있습니다. 만들어 질 수있는 다른 유형의 수가 우주의 전자 수를 초과하는 프로그램을 작성하는 것은 상당히 쉽습니다.
supercat

2
@Trinidad : 분명히, 프로그램을 한 번만 실행해도 그러한 유형을 모두 만들 수는 없지만 (실제로 무한한 부분을 넘어서는 것은 아닙니다), 요점은 그럴 필요가 없다는 것입니다. 실제로 사용되는 유형 만 작성하면됩니다. 하나는 임의의 숫자 (예 :)를 받아 들일 수있는 프로그램을 가질 수 8675309있으며 그로부터 고유 한 숫자 (예 :)를 만들어 Z8<Z6<Z7<Z5<Z3<Z0<Z9>>>>>>>다른 유형과 다른 멤버를 가질 수 있습니다. C ++에서 모든 입력에서 생성 될 수있는 모든 유형은 컴파일 타임에 생성되어야합니다.
supercat

13

일반적인 비판은 통일 부족입니다. 즉, 런타임시 객체에는 일반 인수에 대한 정보가 포함되어 있지 않습니다 (정보는 여전히 필드, 메소드, 생성자 및 확장 클래스 및 인터페이스에 존재하지만). 즉, ArrayList<String>에 캐스팅 할 수 List<File>있습니다. 컴파일러는 경고를 표시하지만 참조에 ArrayList<String>할당 Object한 다음로 캐스팅 하면 경고합니다 List<String>. 단점은 제네릭을 사용하면 캐스팅을 수행해서는 안되며, 불필요한 데이터가 없으면 성능이 향상되고 이전 버전과의 호환성이 향상된다는 것입니다.

어떤 사람들은 일반적인 인수 ( void fn(Set<String>)void fn(Set<File>))를 기준으로 과부하를 할 수 없다고 불평합니다 . 대신 더 나은 메소드 이름을 사용해야합니다. 오버로드는 정적 컴파일 시간 문제이므로이 오버로드에는 수정이 필요하지 않습니다.

기본 유형은 제네릭에서 작동하지 않습니다.

와일드 카드와 범위는 매우 복잡합니다. 그들은 매우 유용합니다. Java가 불변성 및 tell-don't-ask 인터페이스를 선호하는 경우 사용 측 제네릭이 아닌 선언 측이 더 적합합니다.


"오버로딩은 정적 컴파일 시간 문제이므로이 오버로딩에는 수정이 필요하지 않습니다." 그렇지 않습니까? 통일없이 어떻게 작동할까요? 어떤 메소드를 호출하기 위해 JVM이 런타임에 어떤 유형의 세트를 가지고 있는지 알아야합니까?
MatrixFrog

2
@MatrixFrog 아니요. 말했듯이 오버로드는 컴파일 타임 문제입니다. 컴파일러는 정적 유형의 표현식을 사용하여 특정 오버로드를 선택한 다음 클래스 파일에 기록 된 메소드입니다. 당신이 경우 Object cs = new char[] { 'H', 'i' }; System.out.println(cs);당신은 말도 안되는 인쇄 얻을. 유형 변경 cs에를 char[]당신은 얻을 것이다 Hi.
Tom Hawtin-tackline

10

Java 제네릭은 다음을 수행 할 수 없기 때문에 짜증납니다.

public class AsyncAdapter<Parser,Adapter> extends AsyncTask<String,Integer,Adapter> {
    proptected Adapter doInBackground(String... keywords) {
      Parser p = new Parser(keywords[0]); // this is an error
      /* some more stuff I was hoping for but couldn't do because
         the compiler wouldn't let me
      */
    }
}

제네릭이 실제로 제네릭 클래스 매개 변수 인 경우와 같이 작동하려면 위의 코드에서 모든 클래스를 정체 할 필요가 없습니다.


모든 수업을 도살 할 필요는 없습니다. 적절한 팩토리 클래스를 추가하고 AsyncAdapter에 주입하면됩니다. (그리고 기본적으로 이것은 제네릭에 관한 것이 아니라 생성자가 Java로 상속되지 않는다는 사실입니다).
피터 테일러

13
@PeterTaylor : 적합한 팩토리 클래스는 모든 상용구를 다른 곳으로 밀어 넣지 만 여전히 문제를 해결하지 못합니다. 이제 새 인스턴스를 인스턴스화 할 때마다 Parser팩토리 코드를 더욱 복잡하게 만들어야합니다. "일반"이 진정한 의미를 지녔다면, 디자인 패턴의 이름을 따르는 팩토리와 다른 모든 넌센스가 필요하지 않을 것입니다. 동적 언어로 작업하는 것을 선호하는 많은 이유 중 하나입니다.
davidk01

2
템플릿 + 가상을 사용할 때 생성자의 주소를 전달할 수없는 C ++의 경우와 비슷합니다. 대신 팩토리 functor를 사용할 수 있습니다.
Mark K Cowan

메소드 이름 앞에 "보호"를 작성할 수 없기 때문에 Java가 짜증나십니까? 불쌍한 너. 역동적 인 언어를 고수하십시오. 특히 Haskell에주의하십시오.
fdreger

5
  • 목적에 맞지 않는 일반 매개 변수가 누락되었다는 컴파일러 경고는 언어를 무의미하게 장황하게 만듭니다. 예 : public String getName(Class<?> klazz){ return klazz.getName();}

  • 제네릭 은 배열과 잘 어울리지 않습니다.

  • 잃어버린 유형 정보는 반사가 주조 및 덕트 테이프를 엉망으로 만듭니다.


HashMap대신에 사용하라는 경고 메시지가 표시되면 짜증이납니다 HashMap<String, String>.
Michael K

1
@Michael 그러나 실제로 generics와 함께 사용해야 합니다.
대안

배열 문제는 수정 가능성으로갑니다. 설명은 Java Generics 책 (Alligator)을 참조하십시오.
ncmathsadist

5

다른 답변이 어느 정도까지는 말했지만 명확하지는 않습니다. 제네릭의 문제 중 하나는 리플렉션 프로세스 중에 제네릭 형식을 잃는 것입니다. 예를 들어 :

List<String> arr = new ArrayList<String>();
assertTrue( ArrayList.class, arr.getClass() );
TypeVarible[] types = arr.getClass().getTypedVariables();

불행히도, 반환 된 타입은 arr의 제네릭 타입이 String이라는 것을 알 수 없습니다. 미묘한 차이이지만 중요합니다. 런타임에 arr이 작성되므로 런타임에 일반 유형이 지워 지므로이를 알 수 없습니다. 언급 한 바와 같이 , 반사 관점에서 ArrayList<Integer>와 동일하게 보인다 ArrayList<String>.

이것은 Generics 사용자에게는 중요하지 않지만 사용자가 인스턴스의 구체적인 제네릭 형식을 선언 한 방법에 대한 멋진 것을 파악하기 위해 리플렉션을 사용하는 멋진 프레임 워크를 만들고 싶다고 가정 해 봅시다.

Factory<MySpecialObject> factory = new Factory<MySpecialObject>();
MySpecialObject obj = factory.create();

일반 팩토리가 인스턴스를 생성하기를 원한다고 가정 해 봅시다 MySpecialObject.이 인스턴스에 대해 선언 한 구체적인 제네릭 유형이기 때문입니다. Well Factory 클래스는 Java가 삭제했기 때문에이 인스턴스에 선언 된 구체적인 유형을 찾기 위해 자체 조사 할 수 없습니다.

.Net의 제네릭에서는 런타임에 컴파일러가 바이너리를 준수했기 때문에 객체가 제네릭 형식임을 알기 때문에이 작업을 수행 할 수 있습니다. 삭제와 함께 Java는 이것을 할 수 없습니다.


1

제네릭에 대해 몇 가지 좋은 점을 말할 수는 있지만 문제는 아닙니다. 런타임에 사용할 수 없으며 배열에서 작동하지 않는다고 불평 할 수 있지만 언급되었습니다.

큰 심리적 성가심 : 나는 때때로 제네릭을 사용할 수없는 상황에 빠지게된다. (배열이 가장 간단한 예입니다.) 그리고 제네릭이 일을 할 수 없는지 또는 내가 바보인지 아닌지는 알 수 없습니다. 나는 그것을 싫어한다. 제네릭과 같은 것이 항상 작동해야합니다. 다른 모든 시간이 나는, 나는 자바 - 더 - 언어를 사용하여 원하는 것을 할 수 없습니다 알고 문제가 나에게, 그리고 난 그냥 계속 밀고 경우, 나는 결국 거기 것 알고있다. 제네릭을 사용하면 너무 오래 지속되면 많은 시간을 낭비 할 수 있습니다.

그러나 실제 문제 는 제네릭이 너무 적은 이득을 위해 너무 많은 복잡성을 추가한다는 것입니다. 가장 간단한 경우에는 자동차가 들어있는 목록에 사과를 추가하지 못하게 할 수 있습니다. 좋아. 그러나 제네릭이 없으면이 오류로 인해 시간이 거의 걸리지 않고 런타임에 ClassCastException이 실제로 빠르게 발생합니다. 차일드 시트가있는 차를 차에 추가 할 경우, 목록에 베이비 침팬지가 포함 된 차일드 시트가있는 차에만 해당한다는 컴파일 타임 경고가 필요합니까? 일반 객체 인스턴스 목록이 좋은 아이디어처럼 보이기 시작합니다.

제네릭 코드는 많은 단어와 문자를 가질 수 있고 추가 공간을 많이 차지하며 읽는 데 시간이 오래 걸립니다. 추가 코드를 올바르게 작동시키는 데 많은 시간을 할애 할 수 있습니다. 그럴 때, 나는 정신없이 영리하다고 느낍니다. 또한 몇 시간 이상 내 자신의 시간을 낭비했으며 다른 사람이 코드를 알아낼 수 있는지 궁금합니다. 저보다 똑똑하거나 낭비 할 시간이 적은 사람들에게 유지 보수를 위해 전달할 수 있기를 바랍니다.

반면에 (저는 약간 상처를 입었고 균형을 잡을 필요가 있다고 느꼈습니다) 간단한 컬렉션과 맵을 사용하여 쓸 때 추가, 넣기 및 확인을 할 때 좋으며 일반적으로 많이 추가하지 않습니다. 코드의 복잡성 ( 다른 사람 이 컬렉션이나 맵을 작성하는 경우 ) . 그리고 C #보다 Java가 더 좋습니다. 내가 사용하려는 C # 컬렉션 중 어느 것도 제네릭을 처리하지 않는 것 같습니다. (컬렉션에 이상한 취향이 있음을 인정합니다.)


좋은 rant. 그러나 Guice, Gson, Hibernate와 같은 제네릭없이 존재할 수있는 많은 라이브러리 / 프레임 워크가 있습니다. 그리고 일단 당신이 그것에 익숙해지면 제네릭은 그렇게 어렵지 않습니다. 형식 인수를 입력하고 읽는 것은 실제 PITA입니다. 여기서 val 은 사용할 수 있으면 도움이됩니다.
maaartinus

1
@maaartinus : 얼마 전에 작성했습니다. 또한 OP는 "문제"를 요청했습니다. 제네릭은 매우 유용하고 많은 것을 좋아합니다. 그러나 자신의 컬렉션을 작성하는 경우 배우기가 매우 어렵습니다. 컬렉션의 유형이 런타임에 결정되면 컬렉션 의 항목 클래스를 알려주 는 방법이있을 때 유용성이 사라집니다 . 이 시점에서 제네릭은 작동하지 않으며 IDE는 수백 가지의 의미없는 경고를 생성합니다. Java 프로그래밍의 99.99 %는 이것을 포함하지 않습니다. 그러나 나는 것 단지많은 내가 위를 쓸 때이 가진 문제를.
RalphChapin

Java와 C # 개발자 모두 Java 컬렉션을 선호하는 이유가 궁금합니다.
Ivaylo Slavov

Fine. But without generics this error would throw a ClassCastException really quick at run time with little time wasted.그것은 실제로 프로그램 시작과 문제의 코드 행간에 얼마나 많은 런타임이 발생하는지에 달려 있습니다. 사용자가 오류를보고하고 재현하는 데 몇 분 (또는 최악의 시나리오, 몇 시간 또는 며칠)이 걸리는 경우 컴파일 타임 확인이 더 좋아지기 시작합니다 ...
Mason Wheeler
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.