programing

리스트는 리스트의 서브클래스입니까? Java 제네릭이 암묵적으로 다형성이 아닌 이유는 무엇입니까?

goodsources 2022. 8. 21. 20:02
반응형

리스트 리스트의 서브클래스입니까? Java 제네릭이 암묵적으로 다형성이 아닌 이유는 무엇입니까?

자바 제네릭스가 상속/다형성을 어떻게 다루는지 좀 헷갈리네요.

다음 계층을 가정합니다.

동물(부모)

- 고양이(어린이)

내가 해 보자.doSomething(List<Animal> animals) 법칙에 , 는 '다형성이 '다형성'이라고 List<Dog> 는 입니다.List<Animal> a. a. a.List<Cat> 는 입니다.List<Animal>둘다 이 될 수 - 이 방법으로 될 수 있습니다. - 이 방법으로 전달될 수 있습니다.그렇지 않아요.을 수락하는 방법을 . 즉, 동물의 하위 클래스 목록을 을 알려줘야 합니다.doSomething(List<? extends Animal> animals)

이것이 Java의 동작인 것을 알고 있습니다.제 질문은 왜일까요?왜 다형성은 일반적으로 암묵적이지만, 일반론에 관해서는 반드시 명시되어야 하는가?

ㅇㅇㅇㅇ, ᄋ.List<Dog> 아니다List<Animal> "로할 수 해 보십시오.List<Animal>- 아무 동물이나 추가 가능...고양이를 포함해서요.이제, 당신은 논리적으로 강아지 한 마리에 고양이를 추가할 수 있나요?절대로 그렇지 않아요.

// Illegal code - because otherwise life would be Bad
List<Dog> dogs = new ArrayList<Dog>(); // ArrayList implements List
List<Animal> animals = dogs; // Awooga awooga
animals.add(new Cat());
Dog dog = dogs.get(0); // This should be safe, right?

갑자기 당신은 매우 혼란스러운 고양이를 갖게 되었다.

이제 추가는 할 수 없습니다.Cat a까지List<? extends Animal>List<Cat>. 이 이 . 이라는 을 알 수Animal동물은 수 의 경우도 입니다.List<? super Animal>는 - 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아.Animal 수 수 은 「무엇을 수 있는지」라고 것일 도 있기 때문입니다. 왜냐하면, 그것은 안전할 수 있기 때문입니다.List<Object>.

찾고 있는 항목공변량 유형 모수라고 합니다.즉, 메서드에서 오브젝트 타입이 다른 타입으로 대체될 수 있는 경우(예를 들어Animal로 할 수 Dog오브젝트를 즉, 「」를 사용하는 List<Animal>로 할 수 List<Dog>문제는 공분산이 일반적으로 가변 리스트에 안전하지 않다는 것입니다., '우리'가 가정해 주세요.List<Dog> , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , .List<Animal>List<Animal> '아주'입니다.List<Dog>하면 타입 유형 파라미터를 자동으로 공변으로 허용하면 유형 시스템이 중단됩니다.

수에 의해, 「공변량」은 됩니다. ★★★★★★★★★★★★★★★★,? extends Foo메서드 선언에 포함되지만, 그렇게 되면 복잡성이 증가합니다.

List<Dog> 아니다List<Animal>즉, 를, 를, 를, 를, 를, 를, 를, 를, 를, 를, 를, 를, 를, ,, 를, 를, ,, ,, ,, ,,CatList<Animal>, ''에는 들어가지 List<Dog>... 를 사용하여 경우 수 를 들어, ...에서 수 와일드카드를 사용하여 가능한 한 범용 장치를 확장하기 쉽게 할 수 있습니다. ★★★★★★★★★★★★★★★★★,List<Dog>.List<Animal> 쓰지는 .---쓰지 않다.

Java 언어의 GenericsJava Tutorial의 Generics 섹션에는 다형성 또는 다형성 또는 범용성이 허용되지 않는 이유에 대해 매우 상세하게 설명되어 있습니다.

다른 답변에 덧붙여야 할 점은 다음과 같습니다.

List<Dog> 않다List<Animal>

라는 것도 사실이다

개 리스트는 영어로 된 동물 리스트입니다(합리적으로 해석하면).

OP의 직관이 작동하는 방식은 물론 완전히 타당하지만 후자의 문장이다.그러나 이 직관을 적용하면 유형 시스템에서 Java-esque가 아닌 언어를 얻을 수 있습니다.우리의 언어가 우리의 개 목록에 고양이를 추가하는 것을 허용한다고 가정해 보자.그게 무슨 뜻이죠?이것은 그 목록이 개들의 목록이 아니라 단지 동물들의 목록으로 남아있다는 것을 의미할 것이다.포유동물 목록과 사족동물 목록이요

말하면 입니다.List<Dog>자바어로 "개 목록"은 영어로 "개 목록"을 의미하지 않고 "개 목록"을 의미합니다.

보다 일반적으로 OP의 직관은 객체에 대한 연산이 객체의 유형을 변경할 수 있는 언어에 적합하거나 객체의 유형이 객체의 값의 (동적인) 함수이다.

제네릭스의 요점은 그것을 허용하지 않는다는 것입니다.이러한 유형의 공분산을 허용하는 배열의 상황을 고려해 보십시오.

  Object[] objects = new String[10];
  objects[0] = Boolean.FALSE;

「 」 「 」 「 」 「 」 「 」 )가 발생합니다.java.lang.ArrayStoreException: java.lang.Boolean.그것은 활자가 아니다.제네릭스의 요점은 컴파일 시간 유형의 안전성을 추가하는 것입니다.그렇지 않으면 제네릭스를 사용하지 않고 플레인 클래스를 그대로 사용할 수 있습니다.

는 좀 더 있게 할 , '유연성'입니다.을 사용하다? super Class ★★★★★★★★★★★★★★★★★」? extends Class를 위한 것입니다.전자는 타입에 삽입할 필요가 있는 경우입니다.Collection(예를 들어, 후자는, 타이프 세이프한 방법으로 읽어낼 필요가 있는 경우에 사용합니다.하지만 두 가지를 동시에 할 수 있는 유일한 방법은 특정한 유형을 갖는 것입니다.

문제를 이해하려면 어레이와 비교하는 것이 좋습니다.

List<Dog>의 서브클래스가 아니다.List<Animal>.
하지만 Dog[] 의 서브클래스입니다.Animal[].

배열재현 가능하고 공변적입니다.
Replyable은 실행 시 해당 유형 정보를 완전히 사용할 수 있음을 의미합니다.
따라서 어레이는 런타임 유형의 안전성은 제공하지만 컴파일 시간 유형의 안전성은 제공하지 않습니다.

    // All compiles but throws ArrayStoreException at runtime at last line
    Dog[] dogs = new Dog[10];
    Animal[] animals = dogs; // compiles
    animals[0] = new Cat(); // throws ArrayStoreException at runtime

제네릭의 경우는 그 반대입니다.
제네릭은 지워지고 불변합니다.
따라서 제네릭은 런타임 유형의 안전성은 제공할 수 없지만 컴파일 시간 유형의 안전성은 제공합니다.
아래 코드에서는 제네릭이 공변량일 경우 라인 3에서 힙 오염을 발생시킬 수 있습니다.

    List<Dog> dogs = new ArrayList<>();
    List<Animal> animals = dogs; // compile-time error, otherwise heap pollution
    animals.add(new Cat());

여기서 나온 답변은 나를 완전히 납득시키지 못했다.대신 다른 예를 들어보자.

public void passOn(Consumer<Animal> consumer, Supplier<Animal> supplier) {
    consumer.accept(supplier.get());
}

★★★★★★★★★★★★★★★★★?, 패스할 수 것은 '어느 쪽인가'입니다.Consumer §Supplier for 」(으)Animal. 가 있는 .Mammal) 단, 「」, 「」, 「」, 「」, 「」,Duck공급자는 둘 다 동물이지만 맞지 않아야 합니다.이를 허용하지 않기 위해 추가 제한이 추가되었습니다.

위의 내용 대신 사용하는 유형 간의 관계를 정의해야 합니다.

예.,

public <A extends Animal> void passOn(Consumer<A> consumer, Supplier<? extends A> supplier) {
    consumer.accept(supplier.get());
}

소비자에게 적합한 유형의 물건을 제공하는 공급업체만 사용할 수 있도록 보장합니다.

OTOH, 우리가 할 수 있는 건

public <A extends Animal> void passOn(Consumer<? super A> consumer, Supplier<A> supplier) {
    consumer.accept(supplier.get());
}

로 말하면, '는 '종류'를 합니다.Supplier 이 수 해 주세요.Consumer.

우리는 할 수 있다

public <A extends Animal> void passOn(Consumer<? super A> consumer, Supplier<? extends A> supplier) {
    consumer.accept(supplier.get());
}

서, 직관적인 관계를 Life->Animal->Mammal->Dog,Cat, ,, ,, ,, ,, ,, ,MammalLifeconsumer이지만, "consumer"(사용자용)는 .StringLife★★★★★★★★★★★★★★★★★★.

는 '먹다'입니다.Generics활자 삭제 메커니즘을 따릅니다.에 '어느 정도'의를 특정할 수 있는 이 없습니다.collection와는 달리arrays★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

따라서 다음과 같은 방법이 있다고 가정합니다.

add(List<Animal>){
    //You can add List<Dog or List<Cat> and this will compile as per rules of polymorphism
}

이제 java에서 발신자가 이 메서드에 List of type Animal을 추가할 수 있는 경우 컬렉션에 잘못된 항목을 추가할 수 있으며 실행 시 유형 삭제로 인해 실행됩니다.어레이의 경우 이러한 시나리오에서는 런타임 예외가 발생합니다.

따라서 본질적으로 이 동작은 잘못된 것을 수집에 추가할 수 없도록 구현됩니다.제네릭스를 사용하지 않고 레거시 자바와의 호환성을 제공하기 위해 타입 삭제가 존재한다고 생각합니다.

실제로 인터페이스를 사용하여 원하는 것을 달성할 수 있습니다.

public interface Animal {
    String getName();
    String getVoice();
}
public class Dog implements Animal{
    @Override 
    String getName(){return "Dog";}
    @Override
    String getVoice(){return "woof!";}

}

그런 다음 컬렉션을 사용할 수 있습니다.

List <Animal> animalGroup = new ArrayList<Animal>();
animalGroup.add(new Dog());

서브타이핑은 파라미터화된 타입에 대해 불변합니다.심지어 수업도 힘들고Dog는 의 입니다.Animal된 타입 " " " " 입니다List<Dog>의 서브타입이 아닙니다.List<Animal>반면 공변 서브타이핑은 어레이에서 사용되므로 어레이 유형은Dog[]는 의 입니다.Animal[].

불변 서브타이핑은 Java에 의해 적용되는 유형 제약 조건을 위반하지 않도록 합니다.@Jon Sket에 의해 지정된 다음 코드를 생각해 보십시오.

List<Dog> dogs = new ArrayList<Dog>(1);
List<Animal> animals = dogs;
animals.add(new Cat()); // compile-time error
Dog dog = dogs.get(0);

@John Sket에서 설명한 바와 같이 이 코드는 불법입니다.그렇게 하지 않으면 개가 예상했을 때 고양이를 반환함으로써 타입의 제약을 위반하게 되기 때문입니다.

위의 코드를 어레이의 유사한 코드와 비교하는 것이 도움이 됩니다.

Dog[] dogs = new Dog[1];
Object[] animals = dogs;
animals[0] = new Cat(); // run-time error
Dog dog = dogs[0];

코드는 합법입니다.그러나 어레이 저장소 예외를 발생시킵니다.이렇게 하면 JVM이 공변 서브타이핑의 유형 안전성을 적용할 수 있습니다.

이를 더 이해하기 위해 다음에서 생성된 바이트 코드를 살펴보겠습니다.javap다음과 같이 합니다.

import java.util.ArrayList;
import java.util.List;

public class Demonstration {
    public void normal() {
        List normal = new ArrayList(1);
        normal.add("lorem ipsum");
    }

    public void parameterized() {
        List<String> parameterized = new ArrayList<>(1);
        parameterized.add("lorem ipsum");
    }
}

사용javap -c Demonstration자바어

Compiled from "Demonstration.java"
public class Demonstration {
  public Demonstration();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public void normal();
    Code:
       0: new           #2                  // class java/util/ArrayList
       3: dup
       4: iconst_1
       5: invokespecial #3                  // Method java/util/ArrayList."<init>":(I)V
       8: astore_1
       9: aload_1
      10: ldc           #4                  // String lorem ipsum
      12: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      17: pop
      18: return

  public void parameterized();
    Code:
       0: new           #2                  // class java/util/ArrayList
       3: dup
       4: iconst_1
       5: invokespecial #3                  // Method java/util/ArrayList."<init>":(I)V
       8: astore_1
       9: aload_1
      10: ldc           #4                  // String lorem ipsum
      12: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      17: pop
      18: return
}

메서드 본문의 변환된 코드가 동일한지 확인합니다.컴파일러는 파라미터화된 각 유형을 삭제로 대체했습니다.이 속성은 하위 호환성을 손상시키지 않았다는 것을 의미합니다.

결론적으로, 컴파일러는 각 파라미터화된 타입을 삭제로 대체하기 때문에 파라미터화된 타입에 대해서는 런타임 안전성이 불가능하다.이것은 매개 변수화된 유형을 통사 설탕에 불과하게 만든다.

목록 항목이 지정된 슈퍼 유형의 하위 클래스임을 확신하는 경우 다음 방법을 사용하여 목록을 캐스트할 수 있습니다.

(List<Animal>) (List<?>) dogs

이것은 생성자 내부에서 목록을 전달하거나 목록을 반복하려는 경우에 유용합니다.

다른 답들과 마찬가지로 정답도 맞습니다.저는 도움이 될 만한 해결책으로 그 답변들을 추가하겠습니다.프로그래밍에서 자주 나오는 것 같아요.한 가지 주의할 점은 컬렉션(목록, 세트 등)에 대한 주요 이슈는 컬렉션에 추가하는 것입니다.거기서 일이 터진다.떼어내도 괜찮아요.

는 '우리'를 사용할 수 .Collection<? extends T>Collection<T> 그렇게 않은 있습니다하지만 나는 그것을 하기 쉽지 않은 경우를 찾고 있다.그것이 항상 최선인지 아닌지는 논쟁의 여지가 있다.서 'DownCast Collection'을 할 수 '을합니다.이 클래스는, 다음의 변환 프로그램을 수강할 수중에 있습니다.Collection<? extends T> a까지Collection<T>(List, Set, Navigable Set 등에 대해 유사한 클래스를 정의할 수 있음) 표준 접근 방식을 사용할 때 사용하는 것은 매우 불편합니다.할 수 ).Collection<? extends Object>DownCast Collection 이이cast을 。

/**Could use Collection<? extends Object> and that is the better choice. 
* But I am doing this to illustrate how to use DownCastCollection. **/

public static void print(Collection<Object> col){  
    for(Object obj : col){
    System.out.println(obj);
    }
}
public static void main(String[] args){
  ArrayList<String> list = new ArrayList<>();
  list.addAll(Arrays.asList("a","b","c"));
  print(new DownCastCollection<Object>(list));
}

이제 클래스:

import java.util.AbstractCollection;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;

public class DownCastCollection<E> extends AbstractCollection<E> implements Collection<E> {
private Collection<? extends E> delegate;

public DownCastCollection(Collection<? extends E> delegate) {
    super();
    this.delegate = delegate;
}

@Override
public int size() {
    return delegate ==null ? 0 : delegate.size();
}

@Override
public boolean isEmpty() {
    return delegate==null || delegate.isEmpty();
}

@Override
public boolean contains(Object o) {
    if(isEmpty()) return false;
    return delegate.contains(o);
}
private class MyIterator implements Iterator<E>{
    Iterator<? extends E> delegateIterator;

    protected MyIterator() {
        super();
        this.delegateIterator = delegate == null ? null :delegate.iterator();
    }

    @Override
    public boolean hasNext() {
        return delegateIterator != null && delegateIterator.hasNext();
    }

    @Override
    public  E next() {
        if(!hasNext()) throw new NoSuchElementException("The iterator is empty");
        return delegateIterator.next();
    }

    @Override
    public void remove() {
        delegateIterator.remove();

    }

}
@Override
public Iterator<E> iterator() {
    return new MyIterator();
}



@Override
public boolean add(E e) {
    throw new UnsupportedOperationException();
}

@Override
public boolean remove(Object o) {
    if(delegate == null) return false;
    return delegate.remove(o);
}

@Override
public boolean containsAll(Collection<?> c) {
    if(delegate==null) return false;
    return delegate.containsAll(c);
}

@Override
public boolean addAll(Collection<? extends E> c) {
    throw new UnsupportedOperationException();
}

@Override
public boolean removeAll(Collection<?> c) {
    if(delegate == null) return false;
    return delegate.removeAll(c);
}

@Override
public boolean retainAll(Collection<?> c) {
    if(delegate == null) return false;
    return delegate.retainAll(c);
}

@Override
public void clear() {
    if(delegate == null) return;
        delegate.clear();

}

}

이 문제는 분산과 관련된 것으로 올바르게 식별되었지만 자세한 내용은 올바르지 않습니다.순수 함수 리스트는 공변량 데이터 펑터입니다. 즉, Sub 유형이 Super의 하위 유형인 경우 Sub의 리스트는 Super 목록의 하위 유형입니다.

그러나 목록의 변동성은 여기서 기본적인 문제가 아닙니다.문제는 일반적으로 변동성입니다.이 문제는 잘 알려져 있고 공분산 문제라고 불리며, 카스타냐에 의해 처음 확인되었으며, 일반적인 패러다임으로서의 객체 지향성을 완전히 파괴합니다.그것은 Cardelli와 Reynolds에 의해 확립된 이전에 확립된 분산 규칙에 기초하고 있다.

다소 지나치게 단순화하면 타입 T의 객체 B를 타입 T의 객체 A에 할당하는 것을 변환으로 간주합니다.이것은 일반성을 잃지 않는다: A의 돌연변이는 A = f (A)로 쓸 수 있다.여기서 f: T - > T.물론 문제는 함수가 공변하는 반면, 그 영역에서는 반변하는 것이지만, 할당에서는 도메인과 코드 도메인이 같기 때문에 할당은 불변이라는 것입니다.

따라서, 일반론적으로, 하위 유형은 변이될 수 없습니다.그러나 객체 방향 돌연변이는 기본이므로 객체 방향은 본질적으로 결함이 있습니다.

여기 간단한 예가 있습니다.완전히 기능적인 설정에서는 대칭행렬은 분명히 행렬이며 서브타입이며 문제 없습니다.이제 매트릭스에 다른 요소는 변경되지 않는 규칙을 사용하여 좌표(x,y)에 단일 요소를 설정하는 기능을 추가합니다.이제 대칭 행렬은 더 이상 하위 유형이 아닙니다. (x,y)를 변경하면 (y,x)도 변경됩니다.기능 동작은 델타입니다.Sym -> Mat 대칭행렬의 1개의 요소를 변경하면 일반 비대칭행렬이 반환됩니다.따라서 Mat에 "하나의 요소 변경" 방법을 포함하면 Sym은 하위 유형이 아닙니다.사실..적절한 서브타입은 거의 없습니다.

쉽게 말하면, 범용성을 활용하는 다양한 변이자를 가진 일반적인 데이터 타입이 있는 경우, 적절한 서브 타입이 이러한 모든 돌연변이를 지원할 수 없다는 것을 알 수 있습니다.가능한 한, 「적절한」서브 타입의 사양과는 달리, 슈퍼 타입과 같이 일반적입니다.

자바가 서브타이핑 변경 목록을 막는 것은 진짜 문제를 해결하지 못한다: 수십 년 전 자바와 같은 객체 지향 쓰레기를 왜 사용하는가?

어떤 경우에도 여기서는 합리적인 논의가 이루어집니다.

https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)

JavaSE 튜토리얼의 예를 들어보겠습니다.

public abstract class Shape {
    public abstract void draw(Canvas c);
}

public class Circle extends Shape {
    private int x, y, radius;
    public void draw(Canvas c) {
        ...
    }
}

public class Rectangle extends Shape {
    private int x, y, width, height;
    public void draw(Canvas c) {
        ...
    }
}

따라서 개(원) 목록이 암묵적으로 동물(모양) 목록으로 간주되지 않아야 하는 이유는 다음과 같다.

// drawAll method call
drawAll(circleList);


public void drawAll(List<Shape> shapes) {
   shapes.add(new Rectangle());    
}

Java의 "아키텍트"에는 이 문제에 대처하기 위한 두 가지 옵션이 있었습니다.

  1. 서브타입이 암묵적으로 슈퍼타입이라고 생각하지 말고 지금처럼 컴파일 에러를 발생시킨다.

  2. 서브타입은 슈퍼타입으로 간주하여 컴파일 시 "add" 메서드를 제한합니다(따라서 drawAll 메서드에서는 원의 리스트, 형상의 서브타입이 전달되면 컴파일러는 이를 검출하여 컴파일 오류 발생 시 이를 제한해야 합니다).

분명한 이유로, 그것은 첫 번째 방법을 선택했다.

또한 컴파일러가 일반 클래스를 어떻게 위협하는지 고려해야 합니다. 즉, 일반 인수를 채울 때마다 다른 유형을 "인스턴스화"합니다.

thus가 있습니다.ListOfAnimal,ListOfDog,ListOfCatetc는 "계층 는 평탄한 계층 입니다).List을 이용하다)

이 안 또 으로 모든 입니다.List★★★★★★★를 전문으로 하고 있습니다.List일반 인수를 채우는 것이 클래스를 확장하지 않고 특정 일반 인수에 맞게 만들 뿐입니다.

문제는 이미 충분히 특정되었다.하지만 해결 방법이 있습니다. 범용적인 방법을 사용하세요.

<T extends Animal> void doSomething<List<T> animals) {
}

List <Dog>, List <Cat> 또는 List <Animal> 중 하나를 사용하여 doSomething을 호출할 수 있게 되었습니다.

또 다른 해결책은 새로운 목록을 작성하는 것이다.

List<Dog> dogs = new ArrayList<Dog>(); 
List<Animal> animals = new ArrayList<Animal>(dogs);
animals.add(new Cat());

Jon Sket의 답변에 덧붙여, 이 예에서는 다음의 코드를 사용하고 있습니다.

// Illegal code - because otherwise life would be Bad
List<Dog> dogs = new ArrayList<Dog>(); // ArrayList implements List
List<Animal> animals = dogs; // Awooga awooga
animals.add(new Cat());
Dog dog = dogs.get(0); // This should be safe, right?

는 가장 깊은 레벨에서 보면, 「」라고 하는 입니다.dogs ★★★★★★★★★★★★★★★★★」animals 는 목록 이것은 참조 평등입니다: , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , .

// This code is fine
List<Dog> dogs = new ArrayList<Dog>();
dogs.add(new Dog());
List<Animal> animals = new ArrayList<>(dogs); // Copy list
animals.add(new Cat());
Dog dog = dogs.get(0);   // This is fine now, because it does not return the Cat

" " " 를 List<Animal> animals = new ArrayList<>(dogs);할 수 animals 쪽인가에dogs ★★★★★★★★★★★★★★★★★」cats:

// These are both illegal
dogs = animals;
cats = animals;

안 요.Animal 있지합니다.- 중 는 서브타입의 오브젝트입니다.- 서브타입의 오브젝트입니다.? extends Animal 할 수 animals.

당연히에는 '아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아목록은animals ★★★★★★★★★★★★★★★★★」dogs되지 않기 에, 1개의 에 추가되지 의 리스트는, 1개의 리스트가 되는 ).Cat를 추가할 수 은, 「이러다」만이 입니다.Dog비효율적일 수 .또한 전체 목록을 복사하는 것은 비효율적일 수 있습니다.그러나 이렇게 하면 기준 등식을 깨는 방식으로 유형 동등성 문제가 해결됩니다.

질문에는 이미 여러 번 답변이 있었으므로, 같은 질문에 대한 제 의견을 입력해 주십시오.

단순화된 Animal 클래스 계층을 만들어 보겠습니다.

abstract class Animal {
    void eat() {
        System.out.println("animal eating");
    }
}

class Dog extends Animal {
    void bark() { }
}

class Cat extends Animal {
    void meow() { }
}

이제 오랜 친구 어레이에 대해 살펴보겠습니다.이 어레이는 다형성을 암묵적으로 지원하고 있습니다.

class TestAnimals {
    public static void main(String[] args) {
        Animal[] animals = {new Dog(), new Cat(), new Dog()};
        Dog[] dogs = {new Dog(), new Dog(), new Dog()};
        takeAnimals(animals);
        takeAnimals(dogs);
    }

    public void takeAnimals(Animal[] animals) {
        for(Animal a : animals) {
            System.out.println(a.eat());
        }
    }   
}

클래스는 컴파일 상태가 양호하며 위의 클래스를 실행하면 출력이 표시됩니다.

animal eating
animal eating
animal eating
animal eating
animal eating
animal eating

여기서 주의할 점은 take Animals() 메서드는 Animal 타입의 모든 것을 취하도록 정의되어 있으며 Animal 타입의 배열을 취할 수 있으며 Dog-is-a-Animal이기 때문에 Dog의 배열을 취할 수도 있습니다.이것이 다형성이라는 것입니다.

이제 제네릭에 대해서도 같은 접근방식을 사용합시다.

이제 코드를 조금 수정하여 어레이 대신 어레이 목록을 사용한다고 가정해 보겠습니다.

class TestAnimals {
    public static void main(String[] args) {
        ArrayList<Animal> animals = new ArrayList<Animal>();
        animals.add(new Dog());
        animals.add(new Cat());
        animals.add(new Dog());
        takeAnimals(animals);
    }

    public void takeAnimals(ArrayList<Animal> animals) {
        for(Animal a : animals) {
            System.out.println(a.eat());
        }
    }   
}

위의 클래스는 컴파일하여 출력을 생성합니다.

animal eating
animal eating
animal eating
animal eating
animal eating
animal eating

이것이 효과가 있다는 것을 알고 있습니다.이제 이 클래스를 조금 조정해서 동물 타입을 다형적으로 사용하도록 하겠습니다.

class TestAnimals {
    public static void main(String[] args) {
        ArrayList<Animal> animals = new ArrayList<Animal>();
        animals.add(new Dog());
        animals.add(new Cat());
        animals.add(new Dog());

        ArrayList<Dog> dogs = new ArrayList<Dog>();
        takeAnimals(animals);
        takeAnimals(dogs);
    }

    public void takeAnimals(ArrayList<Animal> animals) {
        for(Animal a : animals) {
            System.out.println(a.eat());
        }
    }   
}

take Animals() 메서드는 Animal 및 Dog-is-a-Animal 유형의 ArrayList를 사용하도록 설계되었기 때문에 위의 클래스를 컴파일하는 데 문제가 없을 것으로 보입니다.

그러나 불행히도 컴파일러는 오류를 발생시켜 Dog Array List를 Animal Array List를 기대하는 변수에 전달할 수 없습니다.

이유를 물으시죠?

Because just imagine, if JAVA were to allow the Dog ArrayList - dogs - to be put into the Animal ArrayList - animals - and then inside the takeAnimals() method somebody does something like -

animals.add(new Cat());

이상적으로는 Animal Array List이고 Cat-is-also-a-Animal로 고양이를 추가할 수 있는 위치에 있기 때문에 가능한 일이라고 생각합니다만, 실제로는 Dog type Array List를 전달했습니다.

따라서 어레이에서도 같은 일이 일어났어야 한다고 생각하시는 것이 틀림없습니다.네가 그렇게 생각하는 것은 옳다.

어레이에서도 같은 처리를 하려고 하면 어레이에서도 에러가 발생하지만 어레이에서는 런타임에 이 에러가 처리되지만 ArrayLists에서는 컴파일 시에 이 에러가 처리됩니다.

언급URL : https://stackoverflow.com/questions/2745265/is-listdog-a-subclass-of-listanimal-why-are-java-generics-not-implicitly-po

반응형