이 사이트 게시물에서 Java의 제네릭 구현을 거부하는 몇 번을 보았습니다. 이제 나는 그것들을 사용하는 데 아무런 문제가 없다고 정직하게 말할 수 있습니다. 그러나 나는 일반 클래스를 직접 만들려고하지 않았습니다. 그렇다면 Java의 일반 지원과 관련하여 어떤 문제가 있습니까?
이 사이트 게시물에서 Java의 제네릭 구현을 거부하는 몇 번을 보았습니다. 이제 나는 그것들을 사용하는 데 아무런 문제가 없다고 정직하게 말할 수 있습니다. 그러나 나는 일반 클래스를 직접 만들려고하지 않았습니다. 그렇다면 Java의 일반 지원과 관련하여 어떤 문제가 있습니까?
답변:
Java의 일반 구현은 erasure 유형을 사용 합니다 . 즉, 강력한 형식의 제네릭 컬렉션은 실제로 Object
런타임 에 형식 이됩니다. 이는 기본 콜렉션에 추가 될 때 기본 유형을 상자에 넣어야한다는 의미이므로 일부 성능 고려 사항이 있습니다. 물론 컴파일 타임 타입 정확성의 이점은 타입 소거의 일반적인 어리 석음을 능가하며 이전 버전과의 호환성에 대한 강박 관념에 중점을 둡니다.
TypeLiteral
google-guice.googlecode.com/svn/trunk/javadoc/com/google/inject/…
new ArrayList<String>.getClass() == new ArrayList<Integer>.getClass()
template<T> T add(T v1, T v2) { return v1->add(v2); }
있고 거기에서 add
두 가지 일을 하는 함수를 만드는 진정한 일반적인 방법이 있습니다. 그들은 add()
하나의 매개 변수로 명명 된 메소드 만 있으면됩니다 .
Add
제가 제공 한 간단한 예를 구현할 수는 없습니다. 사전에 적용 할 수있는 클래스를 미리 알지 못하면 방법이 없습니다.
이미 제공된 답변은 Java 언어, JVM 및 Java 클래스 라이브러리의 조합에 집중되어 있습니다.
Java 언어에 관한 한 Java의 제네릭에는 아무런 문제가 없습니다. C #과 Java 제네릭에 설명 된 것처럼 Java 제네릭 은 언어 수준 1 에서 상당히 훌륭합니다 .
차선책은 JVM이 제네릭을 직접 지원하지 않기 때문에 몇 가지 중요한 결과가 발생한다는 것입니다.
Java 언어가 JVM을 대상으로하고 Java 클래스 라이브러리를 핵심 라이브러리로 사용하여 Java 언어를 어디서나 사용할 수 있기 때문에 내가 만들고있는 구별은 실용적인 것이라고 생각합니다.
1 와일드 카드를 제외하고는 일반적으로 유형 유추를 결정할 수없는 것으로 여겨집니다. 이것은 전혀 언급되지 않은 C #과 Java 제네릭의 주요 차이점입니다. 고마워, 안티몬
8675309
있으며 그로부터 고유 한 숫자 (예 :)를 만들어 Z8<Z6<Z7<Z5<Z3<Z0<Z9>>>>>>>
다른 유형과 다른 멤버를 가질 수 있습니다. C ++에서 모든 입력에서 생성 될 수있는 모든 유형은 컴파일 타임에 생성되어야합니다.
일반적인 비판은 통일 부족입니다. 즉, 런타임시 객체에는 일반 인수에 대한 정보가 포함되어 있지 않습니다 (정보는 여전히 필드, 메소드, 생성자 및 확장 클래스 및 인터페이스에 존재하지만). 즉, ArrayList<String>
에 캐스팅 할 수 List<File>
있습니다. 컴파일러는 경고를 표시하지만 참조에 ArrayList<String>
할당 Object
한 다음로 캐스팅 하면 경고합니다 List<String>
. 단점은 제네릭을 사용하면 캐스팅을 수행해서는 안되며, 불필요한 데이터가 없으면 성능이 향상되고 이전 버전과의 호환성이 향상된다는 것입니다.
어떤 사람들은 일반적인 인수 ( void fn(Set<String>)
및 void fn(Set<File>)
)를 기준으로 과부하를 할 수 없다고 불평합니다 . 대신 더 나은 메소드 이름을 사용해야합니다. 오버로드는 정적 컴파일 시간 문제이므로이 오버로드에는 수정이 필요하지 않습니다.
기본 유형은 제네릭에서 작동하지 않습니다.
와일드 카드와 범위는 매우 복잡합니다. 그들은 매우 유용합니다. Java가 불변성 및 tell-don't-ask 인터페이스를 선호하는 경우 사용 측 제네릭이 아닌 선언 측이 더 적합합니다.
Object cs = new char[] { 'H', 'i' }; System.out.println(cs);
당신은 말도 안되는 인쇄 얻을. 유형 변경 cs
에를 char[]
당신은 얻을 것이다 Hi
.
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
*/
}
}
제네릭이 실제로 제네릭 클래스 매개 변수 인 경우와 같이 작동하려면 위의 코드에서 모든 클래스를 정체 할 필요가 없습니다.
Parser
팩토리 코드를 더욱 복잡하게 만들어야합니다. "일반"이 진정한 의미를 지녔다면, 디자인 패턴의 이름을 따르는 팩토리와 다른 모든 넌센스가 필요하지 않을 것입니다. 동적 언어로 작업하는 것을 선호하는 많은 이유 중 하나입니다.
HashMap
대신에 사용하라는 경고 메시지가 표시되면 짜증이납니다 HashMap<String, String>
.
다른 답변이 어느 정도까지는 말했지만 명확하지는 않습니다. 제네릭의 문제 중 하나는 리플렉션 프로세스 중에 제네릭 형식을 잃는 것입니다. 예를 들어 :
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는 이것을 할 수 없습니다.
제네릭에 대해 몇 가지 좋은 점을 말할 수는 있지만 문제는 아닙니다. 런타임에 사용할 수 없으며 배열에서 작동하지 않는다고 불평 할 수 있지만 언급되었습니다.
큰 심리적 성가심 : 나는 때때로 제네릭을 사용할 수없는 상황에 빠지게된다. (배열이 가장 간단한 예입니다.) 그리고 제네릭이 일을 할 수 없는지 또는 내가 바보인지 아닌지는 알 수 없습니다. 나는 그것을 싫어한다. 제네릭과 같은 것이 항상 작동해야합니다. 다른 모든 시간이 나는, 나는 자바 - 더 - 언어를 사용하여 원하는 것을 할 수 없습니다 알고 문제가 나에게, 그리고 난 그냥 계속 밀고 경우, 나는 결국 거기 것 알고있다. 제네릭을 사용하면 너무 오래 지속되면 많은 시간을 낭비 할 수 있습니다.
그러나 실제 문제 는 제네릭이 너무 적은 이득을 위해 너무 많은 복잡성을 추가한다는 것입니다. 가장 간단한 경우에는 자동차가 들어있는 목록에 사과를 추가하지 못하게 할 수 있습니다. 좋아. 그러나 제네릭이 없으면이 오류로 인해 시간이 거의 걸리지 않고 런타임에 ClassCastException이 실제로 빠르게 발생합니다. 차일드 시트가있는 차를 차에 추가 할 경우, 목록에 베이비 침팬지가 포함 된 차일드 시트가있는 차에만 해당한다는 컴파일 타임 경고가 필요합니까? 일반 객체 인스턴스 목록이 좋은 아이디어처럼 보이기 시작합니다.
제네릭 코드는 많은 단어와 문자를 가질 수 있고 추가 공간을 많이 차지하며 읽는 데 시간이 오래 걸립니다. 추가 코드를 올바르게 작동시키는 데 많은 시간을 할애 할 수 있습니다. 그럴 때, 나는 정신없이 영리하다고 느낍니다. 또한 몇 시간 이상 내 자신의 시간을 낭비했으며 다른 사람이 코드를 알아낼 수 있는지 궁금합니다. 저보다 똑똑하거나 낭비 할 시간이 적은 사람들에게 유지 보수를 위해 전달할 수 있기를 바랍니다.
반면에 (저는 약간 상처를 입었고 균형을 잡을 필요가 있다고 느꼈습니다) 간단한 컬렉션과 맵을 사용하여 쓸 때 추가, 넣기 및 확인을 할 때 좋으며 일반적으로 많이 추가하지 않습니다. 코드의 복잡성 ( 다른 사람 이 컬렉션이나 맵을 작성하는 경우 ) . 그리고 C #보다 Java가 더 좋습니다. 내가 사용하려는 C # 컬렉션 중 어느 것도 제네릭을 처리하지 않는 것 같습니다. (컬렉션에 이상한 취향이 있음을 인정합니다.)
Fine. But without generics this error would throw a ClassCastException really quick at run time with little time wasted.
그것은 실제로 프로그램 시작과 문제의 코드 행간에 얼마나 많은 런타임이 발생하는지에 달려 있습니다. 사용자가 오류를보고하고 재현하는 데 몇 분 (또는 최악의 시나리오, 몇 시간 또는 며칠)이 걸리는 경우 컴파일 타임 확인이 더 좋아지기 시작합니다 ...