이 기사는 Java 배열 공분산 및 일반 불변성(코드 포함)에 대한 지식을 소개합니다. 필요한 친구가 참고할 수 있기를 바랍니다.
가변성은 OOP 언어 불변성의 큰 함정이며, Java의 배열 공분산은 오래된 함정 중 하나입니다. 최근에 밟았기 때문에 메모를 했습니다. 그런데 패러다임의 퇴보에 대해서도 언급해 봅시다.
배열 공분산을 설명하기 전에 먼저 공분산, 불변성, 반공분산이라는 세 가지 관련 개념을 명확히 하세요.
1. 공분산, 불변성, 반공변성
레스토랑을 위한 코드를 작성했다고 가정해 보겠습니다.
class Soup<T> { public void add(T t) {} } class Vegetable { } class Carrot extends Vegetable { }
다음으로 만든 수프에 대한 일반 클래스인 Soup
그렇다면 수프<야채>와 수프<당근>의 관계는 무엇일까요?
첫 번째 반응은 당근 수프가 분명히 야채 수프이기 때문에 Soup
Soup<Vegetable> soup = new Soup<Carrot>(); soup.add(new Tomato());
의 또 다른 하위 클래스인 토마토를 의미합니다. 첫 번째 문장은 괜찮습니다. Soup
그런데 두 문장을 합치면 문제가 발생합니다. 실제 수프 유형은 Soup
그럼 수프<당근>과 수프<야채>는 어떤 관계인가요? 언어마다 이해와 구현이 다릅니다. 정리하자면 세 가지 상황이 있습니다.
(1) Soup
(2) Soup
2. 배열 공분산
Java에서 배열은 제네릭이 아닌 기본 유형이며 Array공변성입니다. 즉, Carrot[]는 야채[]의 하위 클래스입니다. 이전 섹션의 예에서는 공분산이 때때로 문제를 일으킬 수 있음을 보여주었습니다. 예를 들어, 다음 코드
Vegetable[] vegetables = new Carrot[10]; vegetables[0] = new Tomato(); // 运行期错误
ArrayList<Vegetable> vegetables = new ArrayList<Carrot>(); // 编译期错误 vegetables.add(new Tomato());
3. 제네릭도 공변 및 반공변이 되기를 원하는 경우
제네릭은 변경할 수 없지만 일부 시나리오에서는 여전히 공변이 되기를 원합니다. 예를 들어, 살을 빼기 위해 매일 야채 수프를 마시는 젊은 여성이 있습니다class Girl { public void drink(Soup<Vegetable> soup) {} }
我们希望drink方法可以接受各种不同的蔬菜汤,包括Soup
要实现这一点,应该采用一种类似于协变性的写法
public void drink(Soup<? extends Vegetable> soup) {}
意思是,参数soup的类型是泛型类Soup
但是,这种方法有一个限制。编译器只知道泛型参数是Vegetable的子类,却不知道它具体是什么。所以,所有非null的泛型类型参数均被视为不安全的。说起来很拗口,其实很简单。直接上代码
public void drink(Soup<? extends Vegetable> soup) { soup.add(new Tomato()); // 错误 soup.add(null); // 正确}
方法内的第一句会在编译期报错。因为编译器只知道add方法的参数是Vegetable的子类,却不知道它具体是Carrot、Tomato、或者其他的什么类型。这时,传递一个具体类型的实例一律被视为不安全的。即使soup真的是Soup
但是方法内的第二句是正确的。因为参数是null,它可以是任何合法的类型。编译器认为它是安全的。
同样,也有一种类似于逆变的方法
public void drink(Soup<? super Vegetable> soup) {}
这时,Soup
这种情况就不存在上面的限制了,下面的代码毫无问题
public void drink(Soup<? super Vegetable> soup) { soup.add(new Tomato()); }
Tomato是Vegetable的子类,自然也是Vegetable父类的子类。所以,编译期就可以确定类型是安全的。
위 내용은 Java 배열 공분산 및 일반 불변성에 대한 지식 소개(코드 포함)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!