제네릭의 멋진 점은 무엇이며 왜 사용합니까?


87

나는이 소프트볼을 공원에서 치고 싶은 사람에게 줄 것이라고 생각했다. 제네릭이란 무엇이며 제네릭의 장점은 무엇이며 왜, 어디서, 어떻게 사용해야합니까? 상당히 기본적으로 유지하십시오. 감사.


1
stackoverflow.com/questions/77632/… 중복 . 그러나 간단한 대답은 컬렉션이 API의 일부가 아니더라도 내부 구현에서도 불필요한 캐스팅을 좋아하지 않습니다.
Matthew Flaschen

질문은 매우 비슷하지만 받아 들여진 답변이이 질문에 대한 답이라고 생각하지 않습니다.
Tom Hawtin-tackline


4
@MatthewFlaschen 이상합니다. 중복이이 질문으로 연결됩니다 ... 매트릭스의 결함!
jcollum

@jcollum, 나는 그 코멘트를 게시 한 원래 질문 이이 질문에 병합되었다고 생각합니다.
Matthew Flaschen

답변:


126
  • 형식이 안전한 코드 / 사용 라이브러리 메서드를 작성할 수 있습니다. 즉, List <string>은 문자열 목록이됩니다.
  • 제네릭이 사용 된 결과 컴파일러는 유형 안전성을 위해 코드에서 컴파일 시간 검사를 수행 할 수 있습니다. 즉, 문자열 목록에 int를 넣으려고합니까? ArrayList를 사용하면 덜 투명한 런타임 오류가 발생합니다.
  • boxing / unboxing (.net이 값 유형을 참조 유형으로 또는 그 반대로 변환해야하는 경우 )을 피하거나 객체에서 필수 참조 유형으로 캐스팅 하는 것을 방지하므로 객체를 사용하는 것보다 빠릅니다 .
  • 동일한 기본 동작을 가진 많은 유형에 적용 할 수있는 코드를 작성할 수 있습니다. 즉, Dictionary <string, int>는 Dictionary <DateTime, double>과 동일한 기본 코드를 사용합니다. 제네릭을 사용하여 프레임 워크 팀은 앞서 언급 한 이점과 함께 두 가지 결과를 모두 달성하기 위해 하나의 코드 만 작성하면되었습니다.

9
아, 아주 좋아요. 저는 총알 목록을 좋아해요. 나는 이것이 지금까지 가장 완전하면서도 간결한 대답이라고 생각합니다.
MrBoJangles

전체 설명은 "컬렉션"컨텍스트에 있습니다. 더 넓은 모습을 찾고 있습니다. 이견있는 사람?
jungle_mole nov.

좋은 대답 2 개의 다른 장점을 추가하고 싶습니다. 일반 제한에는 2 개의 장점이 있습니다. 1- 제약 유형의 속성을 일반 유형으로 사용할 수 있습니다 (예 : T : IComparable이 CompareTo를 사용하도록 제공하는 경우) 2- 작성하는 사람 당신이 무엇을 할 것인지 알게 된 후에 코드
Hamit YILDIRIM

52

반복하는 게 정말 싫어요. 나는 내가해야하는 것보다 더 자주 같은 것을 입력하는 것을 싫어한다. 나는 약간의 차이를 가지고 여러 번 재조정하는 것을 좋아하지 않습니다.

만드는 대신 :

class MyObjectList  {
   MyObject get(int index) {...}
}
class MyOtherObjectList  {
   MyOtherObject get(int index) {...}
}
class AnotherObjectList  {
   AnotherObject get(int index) {...}
}

재사용 가능한 클래스 하나를 만들 수 있습니다 ... (어떤 이유로 원시 컬렉션을 사용하지 않으려는 경우)

class MyList<T> {
   T get(int index) { ... }
}

저는 이제 3 배 더 효율적이고 하나의 복사본 만 유지하면됩니다. 더 적은 코드를 유지하고 싶지 않은 이유는 무엇입니까?

이것은 다른 클래스와 상호 작용해야하는 a Callable<T>또는 a 와 같은 컬렉션이 아닌 클래스의 경우에도 마찬가지입니다 Reference<T>. 유형 안전 버전을 만들기 위해 Callable<T>Future<T>기타 모든 관련 클래스를 확장 하시겠습니까?

난 안해.


1
이해가 잘 안됩니다. 나는 당신이 "MyObject"래퍼를 만드는 것을 제안하는 것이 아니다. 그것은 끔찍할 것이다. 나는 당신의 개체 컬렉션이 Cars 컬렉션이라면 Garage 개체를 작성한다고 제안합니다. 그것은 get (car)가 없을 것이고, $ 100의 연료를 사서 그것을 가장 필요로하는 차들에게 분배하는 buyFuel (100)과 같은 메소드를 가질 것입니다. 나는 단지 "포장 꾼"이 아닌 실제 비즈니스 수업에 대해 이야기하고 있습니다. 예를 들어 컬렉션 외부에서 개체를 가져 오거나 반복해서는 안됩니다. 개체에 멤버를 요청하지 않고 대신 작업을 요청합니다.
Bill K

차고 내에서도 형식 안전성이 있어야합니다. 따라서 List <Cars>, ListOfCars가 있거나 모든 곳에서 캐스팅합니다. 필자는 필요한 최소 시간 이상으로 유형을 말할 필요가 없다고 느낍니다.
James Schek 2009-06-05

유형 안전이 좋을 것이라는 데 동의하지만 차고 등급에 국한되어 있기 때문에 훨씬 덜 도움이됩니다. 캡슐화되고 쉽게 제어 할 수 있으므로 새로운 구문을 사용하여이 작은 이점을 얻을 수 있습니다. 이는 미국 헌법을 수정하여 빨간불을 불법으로 만드는 것과 같습니다. 그렇습니다. 항상 불법이어야하지만 수정을 정당화 할 수 있습니까? 나는 또한 사람들이이 경우에 캡슐화를 사용하지 않도록 장려한다고 생각하는데, 이는 실제로 비교적으로 해 롭습니다.
Bill K

Bill-- 캡슐화를 사용하지 않는 것에 대한 요점이있을 수 있지만 나머지 주장은 좋지 않은 비교입니다. 이 경우 새 구문을 배우는 데 드는 비용은 최소화됩니다 (이를 C #의 람다와 비교). 이것을 헌법 개정과 비교하는 것은 의미가 없습니다. 헌법은 교통 코드를 명시 할 의도가 없습니다. 양피지에 손으로 쓴 사본 대신 포스터 보드의 Serif-Font 인쇄 버전으로 전환하는 것과 비슷합니다. 같은 의미이지만 훨씬 더 명확하고 읽기 쉽습니다.
James Schek 2009-06-15

이 예는 개념을 더 실용적으로 만듭니다. 감사합니다.
RayLoveless

22

타입 캐스트를 할 필요가 없다는 것은 컴파일 타임에 타입 검사를 수행하기 때문에 자바 제네릭의 가장 큰 장점 중 하나입니다 . 이것은 ClassCastException런타임에 던질 수있는 s 가능성을 줄이고 더 강력한 코드로 이어질 수 있습니다.

그러나 나는 당신이 그것을 완전히 알고 있다고 생각합니다.

Generics를 볼 때마다 두통이 생깁니다. Java의 가장 좋은 부분은 단순하고 최소한의 구문과 제네릭이 단순하지 않고 상당한 양의 새로운 구문을 추가한다는 점입니다.

처음에는 제네릭의 이점도 보지 못했습니다. 1.4 구문에서 Java를 배우기 시작했습니다 (당시 Java 5가 나왔음에도 불구하고). 제네릭을 접했을 때 작성해야 할 코드가 더 많다는 느낌이 들었고 실제로 이점을 이해하지 못했습니다.

최신 IDE를 사용하면 제네릭으로 코드를 더 쉽게 작성할 수 있습니다.

대부분의 현대적이고 괜찮은 IDE는 제네릭, 특히 코드 완성으로 코드 작성을 지원할만큼 똑똑합니다.

다음 Map<String, Integer>HashMap. 입력해야하는 코드는 다음과 같습니다.

Map<String, Integer> m = new HashMap<String, Integer>();

그리고 실제로 새로운 HashMap. 그러나 실제로 Eclipse가 필요한 것을 알기 전에이 정도만 입력해야했습니다.

Map<String, Integer> m = new Ha Ctrl+Space

사실, HashMap후보 목록에서 선택해야 했지만 기본적으로 IDE는 제네릭 유형을 포함하여 추가 할 항목을 알고있었습니다. 올바른 도구를 사용하면 제네릭을 사용하는 것이 나쁘지 않습니다.

또한 유형이 알려져 있기 때문에 제네릭 컬렉션에서 요소를 검색 할 때 IDE는 해당 개체가 이미 선언 된 유형의 개체 인 것처럼 작동합니다. 개체의 유형을 알기 위해 IDE를 캐스팅 할 필요가 없습니다. 이다.

제네릭의 주요 장점은 새로운 Java 5 기능과 잘 어울리는 방식에서 비롯됩니다. 다음은 정수를 a에 던지고 Set합계를 계산 하는 예입니다 .

Set<Integer> set = new HashSet<Integer>();
set.add(10);
set.add(42);

int total = 0;
for (int i : set) {
  total += i;
}

이 코드에는 세 가지 새로운 Java 5 기능이 있습니다.

첫째, 프리미티브의 제네릭 및 오토 박싱은 다음 행을 허용합니다.

set.add(10);
set.add(42);

정수는 10으로되어 오토 박싱 Integer의 값 10. (그리고 동일 42). 그런 다음 s 를 보유하는 것으로 알려진에 Integer던져집니다 . 던지려고 하면 컴파일 오류가 발생합니다.SetIntegerString

다음으로 for-each 루프는 다음 세 가지를 모두 취합니다.

for (int i : set) {
  total += i;
}

첫째, Set포함하는 Integers는 for-each 루프에서 사용됩니다. 각 요소는으로 선언 되며은 기본적으로 다시 unboxed int되므로 허용됩니다 . 그리고이 unboxing이 발생한다는 사실은 제네릭을 사용 하여 .IntegerintIntegerSet

Generics는 Java 5에 도입 된 새로운 기능을 결합하는 접착제가 될 수 있으며 코딩을 더 간단하고 안전하게 만듭니다. 그리고 대부분의 경우 IDE는 좋은 제안을 할 수있을만큼 똑똑하므로 일반적으로 더 많이 입력하지 않아도됩니다.

솔직히 Set예제 에서 볼 수 있듯이 Java 5 기능을 사용하면 코드가 더 간결하고 강력해질 수 있다고 생각합니다.

편집-제네릭이없는 예

다음은 Set제네릭을 사용하지 않은 위의 예입니다. 가능하지만 정확히 유쾌하지는 않습니다.

Set set = new HashSet();
set.add(10);
set.add(42);

int total = 0;
for (Object o : set) {
  total += (Integer)o;
}

(참고 : 위 코드는 컴파일 타임에 확인되지 않은 변환 경고를 생성합니다.)

제네릭이 아닌 컬렉션을 사용하는 경우 컬렉션에 입력되는 유형은 유형의 개체입니다 Object. 따라서이 예에서 a Objectadd세트 로 편집되는 것입니다.

set.add(10);
set.add(42);

상기 라인에서 오토 박싱 재생에 - 프리미티브 int10과은 42으로 오토 박싱되는 Integer받는 추가되는 개체 Set. 그러나 컴파일러가 어떤 유형 을 예상 해야하는지 알 수있는 유형 정보가 없기 때문에 Integer객체가 Objects 로 처리된다는 점 을 명심 하십시오 Set.

for (Object o : set) {

이것이 중요한 부분입니다. for-each 루프가 작동하는 이유는 존재하는 경우 with 형식 정보 를 반환하는 인터페이스를 Set구현 하기 때문 입니다. ( , 즉)IterableIteratorIterator<T>

그러나 유형 정보가 없기 때문에는 as 에서 값을 Set반환하는 an Iterator을 반환 하므로 for-each 루프에서 검색되는 요소 는 유형 이어야합니다 .SetObjectObject

에서 Object가 검색 Set되었으므로 Integer추가를 수행하려면 수동으로로 캐스팅해야합니다 .

  total += (Integer)o;

여기서, 타입 변환은에서 수행 Object내지 An Integer. 이 경우, 우리는 이것이 항상 작동한다는 것을 알고 있지만, 수동 형변환은 항상 다른 곳에서 사소한 변경이 이루어지면 손상 될 수있는 취약한 코드라고 느끼게합니다. (나는 모든 타입 캐스트가 ClassCastException일어나기를 기다리고 있다고 느낀다 . 그러나 나는 빠져 나간다 ...)

Integer이제로 박싱되어 int상기에 또한 수행 할 수있는 int변수 total.

Java 5의 새로운 기능이 제네릭이 아닌 코드와 함께 사용할 수 있음을 설명 할 수 있기를 바랍니다.하지만 제네릭으로 코드를 작성하는 것만 큼 깨끗하고 간단하지는 않습니다. 그리고 제 생각에는 Java 5의 새로운 기능을 최대한 활용하려면 제네릭을 조사해야합니다. 최소한 컴파일 시간 검사를 통해 잘못된 타입 캐스트가 런타임에 예외를 발생시키는 것을 방지 할 수 있다면 말입니다.


향상된 for 루프와의 통합은 정말 훌륭합니다 (비 제네릭 컬렉션에서 똑같은 구문을 사용할 수 없기 때문에 망할 루프가 깨 졌다고 생각하지만, 캐스트 / 오토 박스를 int에 사용해야합니다. 필요한 모든 정보!) 그러나 당신이 맞습니다. IDE의 도움은 아마도 좋을 것입니다. 나는 여전히 추가 구문을 정당화하기에 충분한 지 모르겠지만 적어도 그것은 내가 이익을 얻을 수있는 것입니다 (내 질문에 답합니다).
Bill K

@Bill K : 제네릭을 사용하지 않고 Java 5 기능을 사용하는 예를 추가했습니다.
coobird 2009-06-05

그것은 좋은 점입니다. 나는 그것을 사용하지 않았습니다 (나는 1.4에서 그리고 지금까지 몇 년 동안 일찍 붙어 있다고 언급했습니다). 나는 장점이 있다는 데 동의하지만 내가 도달 한 결론은 A) OO를 올바르게 사용하지 않는 사람들에게 상당히 유리한 경향이 있고 사용하는 사람들에게는 덜 사용되는 경향이 있으며 B) 당신의 장점입니다. 나열한 것은 장점이지만 Java로 구현 된 제네릭을 실제로 grok하는 데 필요한 큰 변경은 말할 것도없고 작은 구문 변경도 정당화하기에 충분하지 않습니다. 그러나 적어도 장점이 있습니다.
Bill K

15

당신은 1.5이 출시되기 직전 자바 버그 데이터베이스를 검색한다면, 당신은 일곱 배나 더 많은 버그를 찾을 것 NullPointerException이상을 ClassCastException. 따라서 버그 나 최소한 약간의 연기 테스트 후에도 지속되는 버그를 찾는 것은 훌륭한 기능이 아닌 것 같습니다.

제네릭의 큰 장점은 코드에 중요한 유형 정보를 문서화한다는 것 입니다. 해당 유형 정보를 코드로 문서화하지 않으려면 동적으로 유형이 지정된 언어를 사용하거나 최소한 더 암시적인 유형 추론이있는 언어를 사용합니다.

객체의 컬렉션을 자체적으로 유지하는 것은 나쁜 스타일이 아닙니다 (하지만 일반적인 스타일은 캡슐화를 효과적으로 무시하는 것입니다). 오히려 당신이하는 일에 달려 있습니다. 콜렉션을 "알고리즘"으로 전달하는 것은 제네릭을 사용하여 (컴파일시 또는 전에) 확인하기가 약간 더 쉽습니다.


6
문서화되어있을뿐만 아니라 (그 자체로 큰 승리) 컴파일러에 의해 시행됩니다.
James Schek

좋은 지적이지만, "이 유형의 개체 컬렉션"을 정의하는 클래스에 컬렉션을 포함하여 수행되지 않습니까?
Bill K

1
James : 예, 그것이 코드 로 문서화된다는 점 입니다.
Tom Hawtin-tackline

나는 "알고리즘"에 대한 컬렉션을 사용하는 좋은 라이브러리를 알지 못한다고 생각합니다 (아마도 당신이 "함수"에 대해 이야기하고 있다는 것을 암시합니다. 이것은 그것들이 컬렉션의 객체에서 메소드가되어야한다고 믿게합니다). 나는 JDK가 항상 데이터의 복사 본인 멋지고 깨끗한 배열을 추출하여 엉망이 될 수 없도록한다고 생각합니다.
Bill K

또한 컬렉션이 어떻게 사용되는지 정확히 설명하는 개체가없고 훨씬 더 나은 형식의 인 코드 문서를 사용하도록 제한하지 않습니까? 나는 제네릭에 주어진 정보가 좋은 코드에서 매우 중복된다는 것을 발견했습니다.
Bill K

11

Java의 Generics는 파라 메트릭 다형성을 용이하게 합니다. 유형 매개 변수를 사용하여 유형에 인수를 전달할 수 있습니다. String foo(String s)모델 과 같은 메소드 가 특정 문자열뿐만 아니라 모든 문자열에 대한 일부 동작을 s나타내는 것처럼 List<T>모델 과 같은 유형은 특정 유형뿐만 아니라 모든 유형에 대한 일부 동작을 나타냅니다 . 모든 유형 대해 요소가 s 유형이List<T> 있다고 말합니다 . 그래서 실제로 유형 생성자 입니다. 유형을 인수로 취하고 결과로 다른 유형을 구성합니다.TListTList

다음은 내가 매일 사용하는 제네릭 유형의 몇 가지 예입니다. 첫째, 매우 유용한 일반 인터페이스 :

public interface F<A, B> {
  public B f(A a);
}

이 인터페이스는 말한다 몇 개의 유형, AB, (라는 함수있다 f소요) A과를 반환 B. 이 인터페이스를 구현하는 경우 AB같은 당신이 기능 제공으로, 당신이 원하는 어떤 종류의 수 있습니다 f전자를 소요하고 후자를 반환합니다. 다음은 인터페이스 구현의 예입니다.

F<Integer, String> intToString = new F<Integer, String>() {
  public String f(int i) {
    return String.valueOf(i);
  }
}

제네릭 이전 에는 키워드를 사용하여 서브 클래 싱 하여 다형성을 달성했습니다 extends. 제네릭을 사용하면 실제로 서브 클래 싱을 없애고 대신 파라 메트릭 다형성을 사용할 수 있습니다. 예를 들어, 모든 유형의 해시 코드를 계산하는 데 사용되는 매개 변수화 된 (일반) 클래스를 고려하십시오. Object.hashCode ()를 재정의하는 대신 다음과 같은 일반 클래스를 사용합니다.

public final class Hash<A> {
  private final F<A, Integer> hashFunction;

  public Hash(final F<A, Integer> f) {
    this.hashFunction = f;
  }

  public int hash(A a) {
    return hashFunction.f(a);
  }
}

이는 상속을 사용하는 것보다 훨씬 더 유연합니다. 부서지기 쉬운 계층 구조를 잠그지 않고도 구성 및 매개 변수 다형성을 사용하는 주제를 유지할 수 있기 때문입니다.

Java의 제네릭은 완벽하지 않습니다. 유형을 통해 추상화 할 수 있지만 예를 들어 유형 생성자를 통해 추상화 할 수는 없습니다. 즉, "모든 유형 T에 대해"라고 말할 수 있지만 "유형 매개 변수 A를 사용하는 모든 유형 T에 대해"라고 말할 수 없습니다.

여기에 Java 제네릭의 이러한 한계에 대한 기사를 썼습니다.

제네릭의 큰 장점 중 하나는 서브 클래 싱을 피할 수 있다는 것입니다. 서브 클래 싱은 확장하기 어려운 부서지기 쉬운 클래스 계층 구조와 전체 계층 구조를 보지 않고는 개별적으로 이해하기 어려운 클래스를 생성하는 경향이 있습니다.

제네릭 전에 Wereas 당신은 같은 클래스를 가질 수 Widget확장 FooWidget, BarWidget그리고 BazWidget, 제네릭 단일 제네릭 클래스 가질 수 Widget<A>소요 Foo, Bar또는 Baz생성자을 제공하기에 Widget<Foo>, Widget<Bar>등을 Widget<Baz>.


Java에 인터페이스가 없다면 서브 클래 싱에 대한 좋은 점이 될 것입니다. FooWidget, BarWidtet 및 BazWidget이 모두 위젯을 구현하는 것이 옳지 않습니까? 나는 이것에 대해 틀릴 수있다. 그러나 이것은 내가 틀렸다면 정말 좋은 지적이다.
Bill K

해시 값을 계산하기 위해 동적 제네릭 클래스가 필요한 이유는 무엇입니까? 작업에 적절한 매개 변수가있는 정적 함수가 더 효율적으로 작동하지 않을까요?
Ant_222

8

Generics는 boxing 및 unboxing의 성능 저하를 방지합니다. 기본적으로 ArrayList 대 List <T>를 살펴보십시오. 둘 다 동일한 핵심 작업을 수행하지만 List <T>는 개체로 /로부터 상자를 만들 필요가 없기 때문에 훨씬 빠릅니다.


그게 핵심이지, 그렇지? 좋은.
MrBoJangles

전적으로는 아닙니다. 타입 안전성에 유용합니다. + boxing / unboxing이 전혀 들어오지 않고 참조 타입에 대한 캐스트를 피하는 것도 좋습니다.
ljs

따라서 다음 질문은 "왜 ArrayList를 사용하고 싶습니까?"입니다. 그리고 답을 위험하게 생각하면 ArrayLists는 값을 많이 상자에 넣고 상자를 풀지 않는 한 괜찮다고 말하고 싶습니다. 코멘트?
MrBoJangles

아니요, .net 2부터는 작동하지 않습니다. 이전 버전과의 호환성에만 유용합니다. ArrayList는 속도가 느리고 형식이 안전하지 않으며 boxing / unboxing 또는 캐스팅이 필요합니다.
ljs

값 유형 (예 : int 또는 structs)을 저장하지 않는 경우 ArrayList를 사용할 때 boxing이 없습니다. 클래스는 이미 'object'입니다. 안전 유형 때문에 List <T>를 사용하는 것이 훨씬 낫습니다. 실제로 개체를 저장하려면 List <object>를 사용하는 것이 좋습니다.
Wilka

6

Generics의 가장 큰 이점은 코드 재사용입니다. 많은 비즈니스 객체가 있고 동일한 작업을 수행하기 위해 각 엔티티에 대해 매우 유사한 코드를 작성한다고 가정 해 보겠습니다. (IE Linq에서 SQL 작업으로).

제네릭을 사용하면 주어진 기본 클래스에서 상속하는 모든 유형이 주어지면 작동 할 수있는 클래스를 만들거나 다음과 같이 주어진 인터페이스를 구현할 수 있습니다.

public interface IEntity
{

}

public class Employee : IEntity
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int EmployeeID { get; set; }
}

public class Company : IEntity
{
    public string Name { get; set; }
    public string TaxID { get; set }
}

public class DataService<ENTITY, DATACONTEXT>
    where ENTITY : class, IEntity, new()
    where DATACONTEXT : DataContext, new()
{

    public void Create(List<ENTITY> entities)
    {
        using (DATACONTEXT db = new DATACONTEXT())
        {
            Table<ENTITY> table = db.GetTable<ENTITY>();

            foreach (ENTITY entity in entities)
                table.InsertOnSubmit (entity);

            db.SubmitChanges();
        }
    }
}

public class MyTest
{
    public void DoSomething()
    {
        var dataService = new DataService<Employee, MyDataContext>();
        dataService.Create(new Employee { FirstName = "Bob", LastName = "Smith", EmployeeID = 5 });
        var otherDataService = new DataService<Company, MyDataContext>();
            otherDataService.Create(new Company { Name = "ACME", TaxID = "123-111-2233" });

    }
}

위의 DoSomething 메서드에서 다른 유형이 주어 졌을 때 동일한 서비스를 재사용하는 것에 주목하십시오. 정말 우아합니다!

작업에 제네릭을 사용하는 다른 많은 이유가 있습니다. 이것이 제가 가장 좋아하는 것입니다.


5

사용자 정의 유형을 정의하는 빠른 방법을 제공하기 때문에 좋아합니다 (어쨌든 사용했듯이).

예를 들어 문자열과 정수로 구성된 구조를 정의한 다음 이러한 구조의 배열에 액세스하는 방법에 대한 전체 개체 및 메서드 집합을 구현해야하는 대신 사전을 만들 수 있습니다.

Dictionary<int, string> dictionary = new Dictionary<int, string>();

그리고 컴파일러 / IDE가 나머지 작업을 수행합니다. 특히 사전을 사용하면 첫 번째 유형을 키로 사용할 수 있습니다 (반복되는 값 없음).


5
  • 형식화 된 컬렉션-사용하고 싶지 않더라도 다른 라이브러리, 다른 소스에서 처리해야 할 가능성이 높습니다.

  • 클래스 생성시 일반 입력 :

    공개 클래스 Foo <T> {공개 T get () ...

  • 캐스팅 회피-나는 항상 다음과 같은 것을 싫어했습니다.

    new Comparator {public int compareTo (Object o) {if (o instanceof classIcareAbout) ...

인터페이스가 객체로 표현되기 때문에 존재해야하는 조건을 본질적으로 확인하는 경우.

제네릭에 대한 나의 초기 반응은 당신의 반응과 비슷했습니다. "너무 지저분하고, 너무 복잡합니다." 내 경험으로는 그것들을 조금 사용한 후에 익숙해지며, 그것들이없는 코드는 덜 명확하고 덜 편안하다고 느낍니다. 그 외에도 나머지 자바 세계에서는이를 사용하므로 결국 프로그램을 사용해야합니다.


1
캐스팅 방지와 관련하여 정확합니다. 캐스팅은 Sun 문서에 따라 발생하지만 컴파일러에 의해 수행됩니다. 코드에 캐스트를 작성하지 않아도됩니다.
Demi

아마도 저는 1.4를 넘어서고 싶지 않은 긴 회사들에 갇혀있는 것 같습니다. 그래서 누가 알겠습니까? 저는 5가되기 전에 은퇴 할 수 있습니다. 최근에 "SimpleJava"를 포크하는 것이 흥미로운 개념이 될 수 있다고 생각했습니다. -자바는 계속해서 기능을 추가 할 것이며 내가 본 많은 코드에서 건전하지 않을 것입니다. 나는 이미 루프를 코딩 할 수없는 사람들을 다루고있다. 그들이 무의미하게 펼쳐진 루프에 제네릭을 주입하려고하는 것을보고 싶지 않다.
Bill K

@Bill K : 제네릭의 모든 기능을 사용해야하는 사람은 거의 없습니다. 그것은 그 자체로 일반적인 클래스를 작성할 때만 필요합니다.
Eddie

5

좋은 예를 들어 보자. Foo라는 클래스가 있다고 상상해보십시오.

public class Foo
{
   public string Bar() { return "Bar"; }
}

예제 1 이제 Foo 개체의 컬렉션을 원합니다. LIst 또는 ArrayList의 두 가지 옵션이 있으며 둘 다 유사한 방식으로 작동합니다.

Arraylist al = new ArrayList();
List<Foo> fl = new List<Foo>();

//code to add Foos
al.Add(new Foo());
f1.Add(new Foo());

위의 코드에서 Foo 대신 FireTruck 클래스를 추가하려고하면 ArrayList가이를 추가하지만 Foo의 Generic List는 예외를 발생시킵니다.

예 2 :

이제 두 개의 배열 목록이 있고 각각에 대해 Bar () 함수를 호출하려고합니다. ArrayList는 Objects로 채워져 있으므로 bar를 호출하기 전에 캐스트해야합니다. 그러나 Foo의 일반 목록에는 Foos 만 포함될 수 있으므로 Bar ()를 직접 호출 할 수 있습니다.

foreach(object o in al)
{
    Foo f = (Foo)o;
    f.Bar();
}

foreach(Foo f in fl)
{
   f.Bar();
}

질문을 읽기 전에 대답 한 것 같습니다. 그다지 도움이되지 않는 두 가지 경우가 있습니다 (문제에서 설명 됨). 유용한 기능을하는 다른 경우가 있습니까?
Bill K

4

메서드 / 클래스의 핵심 개념이 매개 변수 / 인스턴스 변수의 특정 데이터 유형 (연결된 목록, 최대 / 최소 함수, 이진 검색)에 밀접하게 연결되지 않은 메서드 (또는 클래스)를 작성한 적이 없습니다. 등).

혹시 컷 - 앤 - 붙여 재사용이나 강한 타이핑을 손상시키지을 (내가 원하는 예를 들어, 당신이에 의지하지 않고 algorthm / 코드를 재사용 할 수 싶지 않은 적이 List하지 않은, 문자열의 List것들을 내가 희망 문자열!)?

당신이해야하는 이유의 싶지는 제네릭 (또는 뭔가 더 나은)를 사용합니다.


이런 종류의 항목에 대해 재사용을 복사 / 붙여 넣기 할 필요가 없었습니다. (어떻게 일어날 지 모르겠습니까?) 필요에 맞는 인터페이스를 구현하고 해당 인터페이스를 사용합니다. 이 작업을 수행 할 수없는 드문 경우는 순수 유틸리티 (예 : 컬렉션)를 작성할 때이며 (질문에서 설명했듯이) 실제로는 문제가되지 않았습니다. 나는 당신이 리플렉션을 사용하는 것처럼 강력한 타이핑을 타협합니다. 강력한 타이핑을 할 수 없기 때문에 리플렉션 사용을 절대 거부합니까? (반사를 사용해야하는 경우 컬렉션처럼 캡슐화합니다.)
Bill K

3

제네릭은 클래스에서만 사용되는 것이 아니라 메서드에서도 사용할 수 있다는 것을 잊지 마십시오. 예를 들어 다음 스 니펫을 사용하십시오.

private <T extends Throwable> T logAndReturn(T t) {
    logThrowable(t); // some logging method that takes a Throwable
    return t;
}

간단하지만 매우 우아하게 사용할 수 있습니다. 좋은 점은 메서드가 주어진 것이 무엇이든 반환한다는 것입니다. 이는 호출자에게 다시 던져 져야하는 예외를 처리 할 때 도움이됩니다.

    ...
} catch (MyException e) {
    throw logAndReturn(e);
}

요점은 메소드를 통해 유형을 전달함으로써 유형을 잃지 않는다는 것입니다. Throwable제네릭없이 할 수있는 모든 것 대신에 올바른 유형의 예외를 throw 할 수 있습니다 .

이것은 제네릭 메서드에 대한 간단한 사용 예입니다. 일반적인 방법으로 할 수있는 몇 가지 다른 깔끔한 작업이 있습니다. 내 생각에 가장 멋진 것은 제네릭으로 추론하는 유형입니다. 다음 예제 (Josh Bloch의 Effective Java 2nd Edition에서 발췌)를 사용하십시오.

...
Map<String, Integer> myMap = createHashMap();
...
public <K, V> Map<K, V> createHashMap() {
    return new HashMap<K, V>();
}

이것은 많은 작업을 수행하지 않지만 제네릭 유형이 길거나 중첩 될 때 약간의 혼란을 줄입니다 (예 :) Map<String, List<String>>.


예외에 대해서는 그것이 사실이라고 생각하지 않습니다. 제네릭을 전혀 사용하지 않고도 정확히 동일한 결과를 얻을 수 있다고 95 % 확신합니다 (그리고 더 명확한 코드). 유형 정보는 캐스트 대상이 아니라 객체의 일부입니다. 그래도 시도해보고 싶은 유혹이 있습니다. 아마 내일 직장에서 다시 연락 드리겠습니다. 무엇을 말씀해 주시겠습니까? 시도해 보겠습니다. 귀하의 게시물 목록과 귀하의 게시물 5 개에 투표하십시오. :) 그렇지 않다면 제네릭의 단순한 가용성으로 인해 코드를 복잡하게 만들었 기 때문에 이점이 없다고 생각할 수 있습니다.
Bill K

이것이 추가 복잡성을 추가하는 것은 사실입니다. 그러나 나는 그것이 약간의 사용이 있다고 생각합니다. 문제는 Throwable특정 선언 된 예외가있는 메서드 본문 내에서 일반 오래된 것을 던질 수 없다는 것 입니다. 대안은 각 예외 유형을 반환하기 위해 별도의 메서드를 작성하거나를 반환하는 제네릭이 아닌 메서드로 직접 캐스트를 수행하는 것입니다 Throwable. 전자는 너무 장황하고 쓸모없고 후자는 컴파일러의 도움을받지 못합니다. 제네릭을 사용하면 컴파일러가 자동으로 올바른 캐스트를 삽입합니다. 따라서 질문은 다음과 같습니다. 복잡 할 가치가 있습니까?
jigawot 2009-06-05

아, 알겠습니다. 그래서 캐스트가 나오는 것을 피합니다. 좋은 지적입니다. 5 빚을졌습니다.
Bill K

2

Mitchel이 지적했듯이 주요 이점은 여러 클래스를 정의 할 필요없이 강력한 타이핑입니다.

이렇게하면 다음과 같은 작업을 수행 할 수 있습니다.

List<SomeCustomClass> blah = new List<SomeCustomClass>();
blah[0].SomeCustomFunction();

제네릭이 없으면 blah [0]을 올바른 유형으로 캐스팅해야 해당 함수에 액세스 할 수 있습니다.


선택권이 주어지면 캐스팅보다는 캐스팅하지 않을 것입니다.
MrBoJangles

강력한 타이핑은 쉽게 제네릭 imho의 가장 좋은 측면이며, 특히 컴파일 타임에 허용되는 유형 검사를 고려할 때 더욱 그렇습니다.
ljs

2

어쨌든 jvm은 캐스트합니다. 일반 유형을 "Object"로 취급하고 원하는 인스턴스화에 대한 캐스트를 생성하는 코드를 암시 적으로 생성합니다. Java 제네릭은 구문상의 설탕 일뿐입니다.


2

나는 이것이 C # 질문이라는 것을 알고 있지만 제네릭 은 다른 언어에서도 사용되며 사용 / 목표는 매우 유사합니다.

Java 컬렉션은 Java 1.5부터 제네릭을 사용 합니다. 그래서, 그것들을 사용하기 좋은 곳은 자신 만의 컬렉션과 같은 객체를 만들 때입니다.

거의 모든 곳에서 볼 수있는 예는 두 개의 객체를 보유하지만 일반적인 방식으로 이러한 객체를 처리해야하는 Pair 클래스입니다.

class Pair<F, S> {
    public final F first;
    public final S second;

    public Pair(F f, S s)
    { 
        first = f;
        second = s;   
    }
}  

이 Pair 클래스를 사용할 때마다 처리 할 객체의 종류를 지정할 수 있으며 모든 유형 캐스트 ​​문제는 런타임이 아닌 컴파일 시간에 표시됩니다.

제네릭은 'super'및 'extends'키워드로 정의 된 경계를 가질 수도 있습니다. 예를 들어, 제네릭 타입을 다루고 싶지만 그것이 Foo라는 클래스 (setTitle 메소드가 있음)를 확장하는지 확인하고 싶다면 :

public class FooManager <F extends Foo>{
    public void setTitle(F foo, String title) {
        foo.setTitle(title);
    }
}

그 자체로는별로 흥미롭지는 않지만 FooManager를 다룰 때마다 MyClass 유형을 처리하고 MyClass가 Foo를 확장한다는 것을 알고 있으면 유용합니다.


2

Sun Java 문서에서 "왜 제네릭을 사용해야합니까?"에 대한 응답 :

"Generics는 컬렉션의 유형을 컴파일러에 전달하여 확인할 수있는 방법을 제공합니다. 컴파일러가 컬렉션의 요소 유형을 알게되면 컴파일러는 컬렉션을 일관되게 사용했는지 확인하고 삽입 할 수 있습니다. 컬렉션에서 가져온 값에 대한 올바른 캐스트 ... 제네릭을 사용하는 코드가 더 명확하고 안전합니다 .... 컴파일러는 컴파일 타임에 유형 제약 조건이 런타임에 위반되지 않았는지 확인할 수 있습니다 [강조]. 프로그램이 경고없이 컴파일되면 런타임에 ClassCastException이 발생하지 않는다고 확실하게 말할 수 있습니다. 특히 대형 프로그램에서 제네릭을 사용하는 결과는 가독성과 견고성향상된다는 것 입니다. [강조] "


컬렉션의 일반적인 타이핑이 내 코드에 도움이 될 경우를 본 적이 없습니다. 내 컬렉션의 범위가 엄격합니다. 그래서 제가 "도움이 될까요?"라고 말한 것입니다. 부수적 인 질문으로 제네릭 사용을 시작하기 전에 가끔 이런 일이 발생했다고 말씀하고 있습니까? (컬렉션에 잘못된 개체 유형을 넣음). 나에게는 진짜 문제가없는 해결책처럼 보이지만 어쩌면 나는 운이 좋을 수도 있습니다.
Bill K

@Bill K : 코드가 엄격하게 통제된다면 (즉, 귀하 만 사용하는 경우) 아마도 이것을 문제로 보지 않을 것입니다. 훌륭합니다! 가장 큰 위험은 여러 사람이 작업하는 대규모 프로젝트 인 IMO입니다. 이것은 안전망이며 "모범 사례"입니다.
Demi

@Bill K : 여기에 예가 있습니다. ArrayList를 반환하는 메서드가 있다고 가정합니다. ArrayList가 일반 컬렉션이 아니라고 가정 해 보겠습니다. 누군가의 코드는이 ArrayList를 가져 와서 항목에 대해 몇 가지 작업을 수행하려고합니다. 유일한 문제는 ArrayList를 BillTypes로 채우고 소비자가 ConsumerTypes에서 작동하려고한다는 것입니다. 이것은 컴파일되지만 런타임 중에 폭발합니다. 제네릭 컬렉션을 사용하면 대신 컴파일러 오류가 발생하므로 처리하기가 훨씬 좋습니다.
Demi

@Bill K : 저는 다양한 기술 수준을 가진 사람들과 함께 일했습니다. 프로젝트를 공유 할 사람을 선택할 수있는 사치가 없습니다. 따라서 유형 안전성은 나에게 매우 중요합니다. 예, Generics를 사용하도록 코드를 변환하여 버그를 발견 (및 수정)했습니다.
Eddie

1

제네릭을 사용하면 강력한 형식의 개체를 만들 수 있지만 특정 형식을 정의 할 필요는 없습니다. 가장 유용한 예는 List 및 유사한 클래스라고 생각합니다.

일반 목록을 사용하면 원하는 목록 목록 목록을 가질 수 있으며 항상 강력한 유형을 참조 할 수 있습니다. 변환 할 필요가 없습니다.


1

제네릭을 사용하면 모든 개체를 보유 할 수 있어야하는 개체 및 데이터 구조에 대해 강력한 유형을 사용할 수 있습니다. 또한 일반 구조 (boxing / unboxing)에서 개체를 검색 할 때 지루하고 값 비싼 typecast를 제거합니다.

둘 다 사용하는 한 가지 예는 연결 목록입니다. 연결 목록 클래스가 객체 Foo 만 사용할 수 있다면 어떤 소용이 있을까요? 모든 종류의 객체를 처리 할 수있는 연결 목록을 구현하려면 목록에 한 유형의 개체 만 포함하도록하려면 가상 노드 내부 클래스의 연결 목록과 노드가 제네릭이어야합니다.


1

컬렉션에 값 유형이 포함되어있는 경우 컬렉션에 삽입 할 때 개체에 대해 상자 / 개봉 할 필요가 없으므로 성능이 크게 향상됩니다. resharper와 같은 멋진 애드온은 foreach 루프와 같은 더 많은 코드를 생성 할 수 있습니다.


1

Generics (특히 Collections / Lists와 함께) 사용의 또 다른 이점은 컴파일 시간 유형 검사를 얻는 것입니다. 이것은 객체 목록 대신 일반 목록을 사용할 때 매우 유용합니다.


1

가장 큰 이유는 유형 안전성 을 제공하기 때문입니다.

List<Customer> custCollection = new List<Customer>;

반대로

object[] custCollection = new object[] { cust1, cust2 };

간단한 예입니다.


유형 안전성, 그 자체로 토론. 누군가가 그것에 대해 질문해야 할 수도 있습니다.
MrBoJangles

그래,하지만 당신은 무엇을 암시하고 있습니까? 유형 안전의 요점?
Vin

1

요약하면 제네릭을 사용하면 수행하려는 작업을보다 정확하게 지정할 수 있습니다 (더 강력한 입력).

다음과 같은 몇 가지 이점이 있습니다.

  • 컴파일러는 사용자가 원하는 작업에 대해 더 많이 알고 있기 때문에 형식이 호환 될 것임을 이미 알고 있기 때문에 많은 형식 캐스팅을 생략 할 수 있습니다.

  • 이것은 또한 프로그램의 수정에 대한 이전 피드백을 제공합니다. 이전에는 런타임에 실패 했었던 것 (예 : 객체를 원하는 유형으로 캐스트 할 수 없기 때문에), 이제 컴파일 타임에 실패하고 테스트 부서가 암호화 버그 보고서를 제출하기 전에 실수를 수정할 수 있습니다.

  • 컴파일러는 권투 방지 등과 같은 더 많은 최적화를 수행 할 수 있습니다.


1

추가 / 확장 할 몇 가지 사항 (.NET 관점에서 말하면) :

일반 유형을 사용하면 역할 기반 클래스 및 인터페이스를 만들 수 있습니다. 이것은 이미 더 기본적인 용어로 언급되었지만 유형에 구애받지 않는 방식으로 구현 된 클래스를 사용하여 코드를 디자인하기 시작하여 재사용 가능성이 높은 코드를 생성합니다.

메서드에 대한 일반적인 인수는 동일한 작업을 수행 할 수 있지만, 캐스팅에 "Tell Do n't Ask"원칙을 적용하는데도 도움이됩니다.


1

예를 들어 SpringORM 및 Hibernate로 구현 된 GenericDao에서 사용합니다.

public abstract class GenericDaoHibernateImpl<T> 
    extends HibernateDaoSupport {

    private Class<T> type;

    public GenericDaoHibernateImpl(Class<T> clazz) {
        type = clazz;
    }

    public void update(T object) {
        getHibernateTemplate().update(object);
    }

    @SuppressWarnings("unchecked")
    public Integer count() {
    return ((Integer) getHibernateTemplate().execute(
        new HibernateCallback() {
            public Object doInHibernate(Session session) {
                    // Code in Hibernate for getting the count
                }
        }));
    }
  .
  .
  .
}

제네릭을 사용함으로써이 DAO의 구현은 개발자가 GenericDao를 서브 클래 싱하여 설계된 엔티티 만 전달하도록합니다.

public class UserDaoHibernateImpl extends GenericDaoHibernateImpl<User> {
    public UserDaoHibernateImpl() {
        super(User.class);     // This is for giving Hibernate a .class
                               // work with, as generics disappear at runtime
    }

    // Entity specific methods here
}

내 작은 프레임 워크가 더 강력합니다 (필터링, 지연 로딩, 검색과 같은 것 포함). 예를 들어 간단히 설명하겠습니다.

나도 스티브와 너처럼 처음에는 "너무 지저분하고 복잡하다"라고 말 했지만 지금은 그 장점을 봅니다.


1

"유형 안전성"및 "주조 없음"과 같은 명백한 이점이 이미 언급되어 있으므로 도움이 되었으면하는 다른 "장점"에 대해 이야기 할 수 있습니다.

우선, 제네릭은 언어에 독립적 인 개념이며 IMO는 동시에 정규 (런타임) 다형성에 대해 생각하면 더 합리적 일 수 있습니다.

예를 들어, 객체 지향 설계에서 알 수있는 다형성은 런타임에 프로그램 실행이 진행됨에 따라 호출자 객체가 파악되고 런타임 유형에 따라 관련 메서드가 호출되는 런타임 개념이 있습니다. 제네릭에서는 아이디어가 다소 비슷하지만 모든 것이 컴파일 타임에 발생합니다. 그 의미는 무엇이며 어떻게 사용합니까?

(간단하게 유지하기 위해 제네릭 메서드를 고수합시다) 즉, 이전에 다형성 클래스에서했던 것처럼 별도의 클래스에서 동일한 메서드를 사용할 수 있지만 이번에는 컴파일러에서 자동 생성되는 형식 집합에 따라 다릅니다. 컴파일 타임에. 컴파일 타임에 제공하는 유형에 대한 메서드를 매개 변수화합니다. 따라서 런타임 다형성 (메소드 재정의) 에서처럼 모든 단일 유형에 대해 처음부터 메서드를 작성하는 대신 컴파일러가 컴파일 중에 작업을 수행하도록합니다. 시스템에서 사용할 수있는 가능한 모든 유형을 추론 할 필요가 없기 때문에 코드 변경없이 훨씬 더 확장 가능하기 때문에 이것은 명백한 이점이 있습니다.

수업은 거의 같은 방식으로 작동합니다. 유형을 매개 변수화하면 컴파일러가 코드를 생성합니다.

"컴파일 시간"이라는 아이디어를 얻으면 "제한된"유형을 사용하고 클래스 / 메소드를 통해 매개 변수 유형으로 전달할 수있는 것을 제한 할 수 있습니다. 따라서 전달할 내용을 제어 할 수 있습니다. 특히 다른 사람들이 사용하는 프레임 워크가있는 강력한 것입니다.

public interface Foo<T extends MyObject> extends Hoo<T>{
    ...
}

지금은 아무도 MyObject 이외의 sth를 설정할 수 없습니다.

또한 메서드 인수에 형식 제약을 "강제"할 수 있습니다. 즉, 두 메서드 인수가 동일한 형식에 종속되도록 할 수 있습니다.

public <T extends MyObject> foo(T t1, T t2){
    ...
}   

이 모든 것이 이해되기를 바랍니다.



0

컬렉션에 제네릭을 사용하는 것은 간단하고 깔끔합니다. 당신이 다른 곳에서 그것을 펀칭하더라도 컬렉션의 이득은 나에게 승리입니다.

List<Stuff> stuffList = getStuff();
for(Stuff stuff : stuffList) {
    stuff.do();
}

vs

List stuffList = getStuff();
Iterator i = stuffList.iterator();
while(i.hasNext()) {
    Stuff stuff = (Stuff)i.next();
    stuff.do();
}

또는

List stuffList = getStuff();
for(int i = 0; i < stuffList.size(); i++) {
    Stuff stuff = (Stuff)stuffList.get(i);
    stuff.do();
}

그것만으로도 제네릭의 한계 "비용"의 가치가 있으며, 이것을 사용하고 가치를 얻기 위해 제네릭 전문가가 될 필요는 없습니다.


1
제네릭 없이도 할 수 있습니다. List stuffList = getStuff (); for (Object stuff : stuffList) {((Stuff) stuff) .doStuff (); }, 크게 다르지 않습니다.
Tom Hawtin-tackline

제네릭없이 나는 stuffList.doAll (); 나는 항상 그런 코드를위한 장소 인 래퍼 클래스를 가지고 있다고 말했다. 그렇지 않으면이 루프를 다른 곳에 복사하지 않으려면 어떻게해야합니까? 재사용을 위해 어디에 두나요? 래퍼 클래스는 아마도 일종의 루프를 가질 것이지만, 그 클래스에서 그 클래스는 모두 "The stuff"에 관한 것이기 때문에 위에서 제안한 Tom과 같은 캐스트 된 for 루프를 사용하는 것은 매우 명확하고 자체 문서화입니다.
Bill K

@Jherico, 정말 흥미 롭습니다. 나는 당신이 그런 식으로 느끼는 이유가 있는지, 그리고 그것이 내가 이해하지 못하는 이유가 있는지 궁금합니다. 훨씬 더 복잡한 구문 추가)를 피하십시오. 왜 "물품을 캐스팅해야한다면 이미 잃어버린 것입니다."
Bill K

@Jherico : Sun 문서에 따르면 제네릭은 컴파일러에게 객체를 캐스팅 할 대상에 대한 지식을 제공합니다. 컴파일러가 사용자가 아닌 제네릭에 대한 캐스팅을 수행하므로 이로 인해 문이 변경됩니까?
Demi

Java 1.5 이전 코드에서 Generics로 코드를 변환했으며 그 과정에서 잘못된 개체 유형이 컬렉션에 포함 된 버그를 발견했습니다. 수동으로 캐스트해야한다면 ClassCastException의 위험이 있기 때문에 "if you have to cast you 've lost"캠프에 속합니다. 제네릭 컴파일러는 당신을 위해 보이지를 않지만,는 ClassCastException의 기회입니다 방법 유형 안전으로 인해 감소. 예, Generics 없이는 Object를 사용할 수 있습니다. 그러나 그것은 "예외를 던졌습니다"와 같은 끔찍한 코드 냄새입니다.
Eddie

0

Generics는 또한 유형별 지원을 계속 제공하면서 더 재사용 가능한 객체 / 메소드를 생성 할 수있는 기능을 제공합니다. 또한 어떤 경우에는 많은 성능을 얻습니다. Java Generics에 대한 전체 사양은 모르지만 .NET에서는 Implements a Interface, Constructor 및 Derivation과 같은 Type 매개 변수에 대한 제약 조건을 지정할 수 있습니다.


0
  1. 프로그래머가 제네릭 알고리즘을 구현할 수 있도록 지원-프로그래머는 제네릭을 사용하여 다양한 유형의 컬렉션에서 작동하고 사용자 정의 할 수 있으며 유형이 안전하고 읽기 쉬운 제네릭 알고리즘을 구현할 수 있습니다.

  2. 컴파일 타임에 더 강력한 유형 검사-Java 컴파일러는 강력한 유형 검사를 일반 코드에 적용하고 코드가 유형 안전성을 위반하면 오류를 발행합니다. 컴파일 타임 오류를 수정하는 것은 찾기 어려울 수있는 런타임 오류를 수정하는 것보다 쉽습니다.

  3. 캐스트 제거.

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