Java-다형성 또는 경계 유형 매개 변수 사용


17

이 클래스 계층 구조가 있다고 가정하십시오 ...

public abstract class Animal {
    public abstract void eat();
    public abstract void talk();
}
class Dog extends Animal {
    @Override
    public void eat() {
    }

    @Override
    public void talk() {
    }
}

class Cat extends Animal {
    @Override
    public void eat() {
    }

    @Override
    public void talk() {
    }
}

그리고 나는 ...

public static <T extends Animal> void addAnimal(T animal) {
    animal.eat();
    animal.talk();
}

public static void addAnimalPoly(Animal animal) {
    animal.eat();
    animal.talk();
}

경계 유형 매개 변수 또는 다형성을 사용할 때의 차이점은 무엇입니까?

그리고 언제 하나를 사용해야합니까?


1
이 두 가지 정의는 유형 매개 변수에서 큰 이익을 얻지 못합니다. 그러나 addAnimals(List<Animal>)고양이 목록을 작성 하고 추가하십시오!
Kilian Foth

7
예를 들어, 위의 각 메소드가 무언가를 반환하면 제네릭을 사용하는 메소드는 T를 리턴하고 다른 메소드는 Animal 만 리턴 할 수 있습니다. 따라서 이러한 방법을 사용하는 사람의 경우 첫 번째 경우에는 원하는 것을 정확하게 다시 얻을 수 있습니다. Dod dog = addAnimal (new Dog ()); 두 번째 방법에서는 개를 얻기 위해 캐스트를 강요 받게됩니다. Dog d = (Dog) addAnimalPoly (new Dog ());
Shivan Dragon

바운딩 된 유형의 대부분의 사용은 Java의 열악한 제네릭 구현에 대해 바탕 화면입니다 (예 : List <T>는 T와 다른 분산 규칙이 있습니다). 이 경우 장점은 없지만 동일한 개념을 표현하는 두 가지 방법이 있지만 @ShivanDragon이 말했듯이 Animal 대신 컴파일 타임 에 T 있음을 의미합니다. 내부적으로 동물처럼 취급 할 수 있지만 외부 적으로 T로 제공 할 수 있습니다.
Phoshi

답변:


14

이 두 예제는 동일하며 실제로 동일한 바이트 코드로 컴파일됩니다.

첫 번째 예제에서와 같이 바인딩 된 제네릭 형식을 메서드에 추가하면 두 가지 방법이 있습니다.

유형 매개 변수를 다른 유형으로 전달

이 두 가지 메소드 서명은 바이트 코드에서 동일하지만 컴파일러는 형식 안전성을 강제합니다.

public static <T extends Animal> void addAnimals(Collection<T> animals)

public static void addAnimals(Collection<Animal> animals)

첫 번째 경우에는 Collection(또는 하위 유형) 만 Animal허용됩니다. 두 번째 경우 Collection에는 일반 유형 또는 하위 유형이 있는 (또는 하위 유형) Animal이 허용됩니다.

예를 들어, 첫 번째 방법에서는 다음이 허용되지만 두 번째 방법에서는 허용되지 않습니다.

List<Cat> cats = new ArrayList<Cat>();
cats.add(new Cat());
addAnimals(cats);

그 이유는 두 번째 것은 동물의 수집 만 허용하고 첫 번째는 동물에 할당 할 수있는 모든 개체 (예 : 하위 유형)의 수집을 허용하기 때문입니다. 이 목록이 고양이를 포함하는 동물의 목록 인 경우 두 방법 중 하나를 사용하면됩니다. 문제는 실제로 포함 된 것이 아니라 컬렉션의 일반적인 사양입니다.

객체 반환

중요한 것은 객체를 반환하는 것입니다. 다음 방법이 존재한다고 가정 해 봅시다.

public static <T extends Animal> T feed(T animal) {
  animal.eat();
  return animal;
}

그것으로 다음을 수행 할 수 있습니다.

Cat c1 = new Cat();
Cat c2 = feed(c1);

이것은 좋은 예이지만, 이해가되는 경우가 있습니다. 제네릭이 없으면 메서드가 반환 Animal되어야하고 형식 캐스팅을 추가하여 작동하게해야합니다 (컴파일러가 장면 뒤의 바이트 코드에 추가하는 것임).


" 첫 번째 경우에는 동물의 컬렉션 (또는 하위 유형) 만 허용됩니다. 두 번째 경우에는 일반 유형의 동물 또는 하위 유형을 가진 컬렉션 (또는 하위 유형)이 허용됩니다. "- 당신의 논리를 확인?
Fund Monica의 소송

3

다운 캐스팅 대신 제네릭을 사용하십시오. "다운 캐스팅"은보다 일반적인 유형에서보다 구체적인 유형으로 진행됩니다.

Animal a = hunter.captureOne();
Cat c = (Cat)a;  // ACK!!!!!! What if it's a Dog? ClassCastException!

... 당신은 a고양이 임을 믿지만 컴파일러는 그것을 보장 할 수 없습니다. 런타임에 개가 될 수 있습니다.

제네릭을 사용할 위치는 다음과 같습니다.

public class <A> Hunter() {
    public A captureOne() { ... }
}

이제 고양이 사냥꾼을 원한다고 지정할 수 있습니다.

Hunter<Cat> hunterC = new Hunter<Cat>();
Cat c = hunterC.captureOne();

Hunter<Dog> hunterD = new Hunter<Dog>();
Dog d = hunterD.captureOne();

이제 컴파일러는 hunterC가 고양이 만 캡처하고 hunterD는 개만 캡처한다는 것을 보증 할 수 있습니다 .

따라서 특정 클래스를 기본 유형으로 처리하려면 일반 다형성을 사용하십시오. 업 캐스팅은 좋은 것입니다. 그러나 특정 클래스를 자체 유형으로 처리 해야하는 상황에서는 일반적으로 제네릭을 사용하십시오.

또는 실제로 다운 캐스트해야하는 경우 제네릭을 사용하십시오.

편집 : 더 일반적인 경우는 처리 할 유형의 유형을 결정하려는 경우입니다. 따라서 유형 뿐만 아니라 값이 매개 변수가됩니다.

동물원 수업에서 고양이 나 스폰지를 처리하고 싶다고 가정 해 보겠습니다. 나는 공통 슈퍼 클래스가 없습니다. 그러나 나는 여전히 사용할 수 있습니다 :

public class <T> Zoo() { ... }

Zoo<Sponge> spongeZoo = ...
Zoo<Cat> catZoo = ...

잠그는 정도는 수행하려는 작업에 따라 다릅니다.)


2

이 질문은 구식이지만, 다형성 대 경계 유형 매개 변수를 언제 사용해야하는지 고려해야 할 중요한 요소가 사라진 것 같습니다. 이 요소는 질문에 주어진 예와 약간 접할 수도 있지만, 더 일반적인 "다형성을 사용하는 경우와 제한된 유형 매개 변수를 사용하는 경우"와 매우 관련이 있다고 생각합니다.

TL; DR

다형성 방식으로 코드에 액세스 할 수 없기 때문에 더 나은 판단에 따라 서브 클래스에서 기본 클래스로 코드를 이동하는 것을 발견 한 경우 경계 유형 매개 변수가 잠재적 솔루션이 될 수 있습니다.

전체 답변

바인드 된 유형 매개 변수는 상속 된 멤버 변수에 대해 상속되지 않은 구체적 서브 클래스 메소드를 노출 할 수 있습니다. 다형성은

예를 확장하여 정교화하려면 다음을 수행하십시오.

public abstract class AnimalOwner<T extends Animal> {
   protected T pet;
   public abstract void rewardPet();
}

// Modify the dog class
class Dog extends Animal {
   // ...
   // This method is not inherited from anywhere!
   public void scratchBelly() {
      System.out.println("Belly: Scratched");
   }
}

class DogOwner extends AnimalOwner<Dog> {
   DogOwner(Dog dog) {
     this.pet = dog;
   }

   @Override
   public void rewardPet()
   {
      // ---- Note this call ----
      pet.scratchBelly();
   }
}

추상 클래스 AnimalOwner가 a protected Animal pet;및 polymorphism을 선택하도록 정의 된 경우 컴파일러는 pet.scratchBelly();라인에 오류가 발생하여 Animal에 대해이 메소드가 정의되어 있지 않음을 알려줍니다.


1

귀하의 예에서 경계 유형을 사용하지 않아야합니다. 이해하기가 더 혼동되기 때문에 경계 유형 매개 변수 만 사용해야 합니다.

다음은 경계 유형 매개 변수를 사용하는 상황입니다.

  • 컬렉션 매개 변수

    class Zoo {
    
      private List<Animal> animals;
    
      public void add(Collection<? extends Animal> newAnimals) {
        animals.addAll(newAnimals);
      }
    }

    그럼 당신은 전화 할 수 있습니다

    List<Dog> dogs = ...
    zoo.add(dogs);

    zoo.add(dogs)<? extends Animal>제네릭은 공변량이 아니기 때문에을 사용하지 않고 컴파일하지 못합니다.

  • 서브 클래 싱

    abstract class Warrior<T extends Weapon> {
    
      public abstract T getWeapon();
    }

    서브 클래스가 제공 할 수있는 유형을 제한합니다.

여러 경계를 사용할 수도 있습니다 <T extends A1 & A2 & A3> 를 사용하여 유형이 목록에있는 모든 유형의 하위 유형이되도록 .

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