문제

나는 공원 밖에서 공을 치고 싶은 누구에게나 이 소프트볼을 제공하겠다고 생각했습니다.제네릭이란 무엇이며, 제네릭의 장점은 무엇이며, 왜, 어디서, 어떻게 사용해야 합니까?아주 기본적인 내용을 유지하시기 바랍니다.감사해요.

도움이 되었습니까?

해결책

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

다른 팁

나는 반복하는 것을 정말 싫어합니다.나는 같은 내용을 필요한 것보다 더 자주 입력하는 것을 싫어합니다.나는 약간의 차이를 두고 여러 번 다시 설명하는 것을 좋아하지 않습니다.

만드는 대신:

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

재사용 가능한 클래스를 하나 만들 수 있습니다.(어떤 이유로 raw 컬렉션을 사용하고 싶지 않은 경우)

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

이제 효율성이 3배 향상되었으며 복사본 하나만 유지하면 됩니다.왜 더 적은 코드를 유지하고 싶지 않습니까?

이는 다음과 같은 비컬렉션 클래스에도 해당됩니다. Callable<T> 또는 Reference<T> 다른 클래스와 상호 작용해야 합니다.정말 연장하시겠습니까? Callable<T> 그리고 Future<T> 그리고 다른 모든 관련 클래스를 사용하여 유형이 안전한 버전을 만들 수 있나요?

나는 아니에요.

타입캐스트가 필요하지 않다는 것은 Java 제네릭의 가장 큰 장점 중 하나입니다., 컴파일 타임에 유형 검사를 수행하기 때문입니다.이렇게 하면 다음과 같은 가능성이 줄어듭니다. ClassCastException런타임에 발생할 수 있으며 보다 강력한 코드로 이어질 수 있습니다.

하지만 나는 당신이 그 사실을 충분히 알고 있다고 생각합니다.

제네릭을 볼 때마다 두통이 생깁니다.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 키+공간

맞아요, 선택해야 했어요 HashMap 하지만 기본적으로 IDE는 제네릭 유형을 포함하여 무엇을 추가해야 할지 알고 있었습니다.올바른 도구가 있으면 제네릭을 사용하는 것도 나쁘지 않습니다.

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

제네릭의 주요 장점은 새로운 Java 5 기능과 잘 작동한다는 점입니다. 다음은 정수를 던지는 예입니다. 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).그 Integer 에 던져진다 Set 보유하고 있는 것으로 알려져 있습니다. Integer에스.던지려고 노력 중 String 컴파일 오류가 발생합니다.

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

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

첫째, Set 포함하는 Integers는 for-each 루프에서 사용됩니다.각 요소는 다음과 같이 선언됩니다. int 그리고 그것은 다음과 같이 허용됩니다 Integer 기본으로 다시 언박싱됩니다. int.그리고 이러한 언박싱이 발생한다는 사실은 제네릭을 사용하여 다음이 있음을 지정했기 때문에 알려져 있습니다. Integer에서 개최됩니다 Set.

제네릭은 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.따라서 이 예에서는 Object 존재하는 것이 무엇인가 add세트에 들어갔습니다.

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

위의 줄에서는 오토박싱이 작동 중입니다. int10 그리고 42 자동으로 박싱되고 있습니다 Integer 에 추가되는 객체 Set.그러나 명심하십시오. Integer 객체는 다음과 같이 처리됩니다. Objects, 컴파일러가 어떤 유형인지 알 수 있도록 도와주는 유형 정보가 없기 때문입니다. Set 기대해야합니다.

for (Object o : set) {

이것이 결정적인 부분이다.for-each 루프가 작동하는 이유는 Set 구현 Iterable 인터페이스는 다음을 반환합니다. Iterator 유형 정보가 있는 경우.(Iterator<T>, 그건.)

다만, 타입정보가 없기 때문에 Set 반환합니다 Iterator 이는 Set ~처럼 Objects, 이것이 for-each 루프에서 요소가 검색되는 이유입니다. ~ 해야 하다 유형이다 Object.

이제 Object 에서 검색됩니다. Set, 로 캐스팅되어야 합니다. Integer 추가를 수행하려면 수동으로 다음을 수행하십시오.

  total += (Integer)o;

여기서는 타입캐스트가 다음에서 수행됩니다. ObjectInteger.이 경우 우리는 이것이 항상 작동한다는 것을 알고 있지만 수동 형변환을 사용하면 다른 곳에서 사소한 변경이 이루어지면 손상될 수 있는 취약한 코드라는 느낌이 항상 듭니다.(나는 모든 타입캐스트가 ClassCastException 일어나기를 기다리고 있지만 빗나갑니다...)

그만큼 Integer 이제 포장이 풀렸습니다. int 그리고 int 변하기 쉬운 total.

Java 5의 새로운 기능을 제네릭이 아닌 코드와 함께 사용할 수 있다는 점을 설명할 수 있기를 바랍니다. 하지만 제네릭을 사용하여 코드를 작성하는 것만큼 깔끔하고 간단하지는 않습니다.그리고 제 생각에는 Java 5의 새로운 기능을 최대한 활용하려면 제네릭을 조사해야 합니다. 최소한 컴파일 타임 검사를 통해 유효하지 않은 유형 변환이 런타임 시 예외를 발생시키는 것을 방지할 수 있습니다.

1.5가 출시되기 직전에 Java 버그 데이터베이스를 검색하면 7배 더 많은 버그를 찾을 수 있습니다. NullPointerException ~보다 ClassCastException.따라서 버그를 찾는 것이 훌륭한 기능은 아닌 것 같습니다. 최소한 약간의 스모크 테스트 후에도 지속되는 버그는 아닙니다.

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

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

Java의 제네릭은 파라메트릭 다형성.유형 매개변수를 통해 유형에 인수를 전달할 수 있습니다.다음과 같은 방법으로 String foo(String s) 특정 문자열뿐만 아니라 모든 문자열에 대한 일부 동작을 모델링합니다. s, 그래서 다음과 같은 유형 List<T> 특정 유형에 대한 행동뿐만 아니라 몇 가지 행동을 모델링합니다. 모든 유형에 대해. List<T> 말한다 모든 유형에 대해 T, 다음과 같은 유형이 있습니다. List 그 요소는 T에스.그래서 List 실제로는 유형 생성자.유형을 인수로 사용하고 결과로 다른 유형을 구성합니다.

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

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

이 인터페이스는 다음과 같이 말합니다. 두 가지 유형의 경우 A 그리고 B, 다음과 같은 기능이 있습니다. f) A 그리고는 B. 이 인터페이스를 구현하면 A 그리고 B 함수를 제공하는 한 원하는 모든 유형이 될 수 있습니다. 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 제네릭의 한계에 관한 기사를 썼습니다.

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

제네릭 이전에는 다음과 같은 클래스가 있을 수 있습니다. Widget 연장됨 FooWidget, BarWidget, 그리고 BazWidget, 제네릭을 사용하면 단일 제네릭 클래스를 가질 수 있습니다 Widget<A> 그건 시간이 좀 걸려 Foo, Bar 또는 Baz 생성자에서 당신에게 제공 Widget<Foo>, Widget<Bar>, 그리고 Widget<Baz>.

제네릭은 박싱 및 언박싱으로 인한 성능 저하를 방지합니다.기본적으로 ArrayList와 List<T>를 살펴보세요.둘 다 동일한 핵심 작업을 수행하지만 List<T>는 개체 간에 상자 작업을 수행할 필요가 없기 때문에 훨씬 더 빠릅니다.

나는 사용자 정의 유형을 정의하는 빠른 방법을 제공하기 때문에 그것을 좋아합니다(어차피 제가 사용하는 것처럼).

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

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

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

Generics의 가장 큰 이점은 코드 재사용입니다.많은 비즈니스 객체가 있고 각 엔터티에 대해 동일한 작업을 수행하기 위해 매우 유사한 코드를 작성한다고 가정해 보겠습니다.(즉, 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 메서드에서 다양한 유형이 주어지면 동일한 서비스를 재사용한다는 점에 유의하세요.정말 우아해요!

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

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

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

    공개 클래스 foo <t> {public t get () ...

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

    새 비교기 {public int compareto (object o) {if (o instanceof classicAreacebout) ...

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

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

좋은 예를 들자면.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의 일반 목록으로 인해 예외가 발생합니다.

예제 2.

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

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

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

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

잘라내어 붙여넣기 재사용이나 강력한 입력(예:나는 List 문자열이 아닌 List 내가 하는 것들 중 희망 문자열입니다!)?

그렇기 때문에 당신은 원하다 제네릭(또는 더 나은 것)을 사용합니다.

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

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, 이는 제네릭 없이도 할 수 있는 전부입니다.

이것은 제네릭 메소드의 한 가지 용도에 대한 간단한 예일 뿐입니다.제네릭 메소드를 사용하여 할 수 있는 다른 멋진 작업이 꽤 많이 있습니다.제 생각에는 가장 멋진 것은 제네릭을 사용한 유형 추론입니다.다음 예를 들어보세요(Josh Bloch의 Effective Java 2판에서 가져옴).

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

이는 많은 작업을 수행하지는 않지만 일반 유형이 길거나 중첩된 경우 일부 혼란을 줄여줍니다.즉. Map<String, List<String>>).

Mitchel이 지적한 것처럼 주요 이점은 여러 클래스를 정의할 필요 없이 강력한 유형을 지정할 수 있다는 것입니다.

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

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

제네릭이 없으면 해당 기능에 액세스하려면 blah[0]을 올바른 유형으로 캐스팅해야 합니다.

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

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

Java 컬렉션 사용 제네릭 자바 1.5부터.따라서 이를 사용하기 좋은 곳은 자신만의 컬렉션 같은 개체를 만들 때입니다.

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

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

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

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

Generics는 키워드 'super' 및 'extends'를 사용하여 범위를 정의할 수도 있습니다.예를 들어, 일반 유형을 처리하고 싶지만 Foo(setTitle 메소드가 있음)라는 클래스를 확장하는지 확인하려는 경우:

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

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

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

"제네릭은 컬렉션 유형을 확인할 수 있도록 컴파일러에 전달하는 방법을 제공합니다.컴파일러가 컬렉션의 요소 유형을 알고 나면 컴파일러는 컬렉션을 일관되게 사용했는지 확인하고 컬렉션에서 가져오는 값에 올바른 캐스트를 삽입할 수 있습니다.제네릭을 사용하는 코드가 더 명확하고 안전합니다.... 컴파일러는 런타임에 유형 제약 조건을 위반하지 않았는지 컴파일 타임에 확인할 수 있습니다. [강조 내 것].프로그램이 경고 없이 컴파일되기 때문에 런타임에 ClassCastException이 발생하지 않을 것이라고 확신할 수 있습니다.특히 대규모 프로그램에서 제네릭을 사용하면 얻을 수 있는 최종 효과는 다음과 같습니다. 향상된 가독성과 견고성.[강조]"

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

일반 목록을 사용하면 원하는 대로 목록 목록 목록을 가질 수 있으며 항상 강력한 입력을 참조할 수 있으므로 변환할 필요가 없으며 배열이나 표준 목록에서와 같은 작업을 수행할 필요가 없습니다.

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

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

컬렉션에 값 유형이 포함된 경우 컬렉션에 삽입할 때 개체를 상자에 넣거나 상자에서 꺼내지 않아도 되므로 성능이 크게 향상됩니다.ReSharper와 같은 멋진 추가 기능은 foreach 루프와 같은 더 많은 코드를 생성할 수 있습니다.

제네릭(특히 컬렉션/목록)을 사용하는 또 다른 이점은 컴파일 시간 유형 검사를 얻을 수 있다는 것입니다.이는 개체 목록 대신 일반 목록을 사용할 때 정말 유용합니다.

가장 큰 이유는 그들이 제공한다는 것입니다. 유형 안전

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

반대로,

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

간단한 예로.

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

이렇게 하면 다음과 같은 여러 가지 이점이 있습니다.

  • 컴파일러는 사용자가 수행하려는 작업에 대해 더 많이 알고 있으므로 유형이 호환된다는 것을 이미 알고 있으므로 많은 유형 캐스팅을 생략할 수 있습니다.

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

  • 컴파일러는 박싱 방지 등과 같은 추가 최적화를 수행할 수 있습니다.

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

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

메소드에 대한 일반적인 인수는 동일한 작업을 수행할 수 있지만 "묻지 말고 말하세요" 원칙을 캐스팅에 적용하는 데도 도움이 됩니다."내가 원하는 것을 주세요. 줄 수 없으면 이유를 말해주세요."

예를 들어 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
}

내 작은 프레임워크는 더 강력합니다(필터링, 지연 로딩, 검색 등이 있음).예를 들기 위해 여기에서는 단순화했습니다.

나도 스티브와 당신처럼 처음에 말했어요. "너무 지저분하고 복잡해요" 하지만 이제 보니 장점이 있군요

"유형 안전성" 및 "캐스팅 없음"과 같은 명백한 이점이 이미 언급되었으므로 도움이 되기를 바라는 다른 "이점"에 대해 이야기할 수 있을 것입니다.

우선, 제네릭은 언어 독립적인 개념이며, IMO에서는 일반(런타임) 다형성을 동시에 생각하면 더 의미가 있을 수 있습니다.

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

(간소하게 유지하기 위해 일반 메소드를 사용하겠습니다.) 이는 이전에 다형성 클래스에서 했던 것처럼 별도의 클래스에서 동일한 메소드를 계속 사용할 수 있지만 이번에는 설정된 유형에 따라 컴파일러에 의해 자동 생성된다는 의미입니다. 컴파일 타임에.컴파일 타임에 제공한 유형에 따라 메소드를 매개변수화합니다.따라서 메소드를 처음부터 작성하는 대신 모든 단일 유형에 대해 런타임 다형성(메서드 재정의)에서와 마찬가지로 컴파일 중에 컴파일러가 작업을 수행하도록 할 수 있습니다.이는 시스템에서 사용될 수 있는 모든 유형을 추론할 필요가 없기 때문에 코드 변경 없이 시스템의 확장성을 훨씬 높일 수 있다는 점에서 분명한 이점이 있습니다.

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

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

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

이제 MyObject 외에는 누구도 sth를 설정할 수 없습니다.

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

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

이 모든 것이 이해되길 바랍니다.

한번은 이 주제에 관해 강연을 한 적이 있습니다.내 슬라이드, 코드 및 오디오 녹음은 다음에서 찾을 수 있습니다. http://www.adventuresinsoftware.com/generics/.

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

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

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

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

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

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top