covariant

Java programming ideas study notes

covariant

Covariance and Arrays

class Fruit {} class Apple extends Fruit {} class Jonathan extends Apple {} class Orange extends Fruit {}

public class CovariantArrays { public static void main(String[] args) { Fruit[] fruit = new Apple[10]; fruit[0] = new Apple(); // OK fruit[1] = new Jonathan(); // OK // Runtime type is Apple[], not Fruit[] or Orange[] try { // compiler allows you to add Fruit; fruit[0] = new Fruit(); // ArrayStoreException } catch(Exception e) { System.out.println(e); } try { // compiler allows you to add Fruit; fruit[0] = new Orange(); // ArrayStoreException } catch(Exception e) { System.out.println(e); } } }

The code creates a new Apple array and assigns it to a Fruit array reference. Because Apple inherits from Fruit, an array of Apples should also be an array of Fruit, so there is no problem with this assignment. But the actual type of the array is still Apple[], so you can only put Apple or a subtype of Apple in it. When placing the Fruit or Orange type in the array, the compiler does not report an error, because the array Apple[] is manually up-converted to Fruit[]. Therefore, from the compiler's point of view, it is natural to receive Fruit or Fruit. subtype of . However, at runtime, the mechanism of the array knows it's dealing with Apple[], so it throws an exception when putting other types.

An array object can retain information about the type of the objects it contains. As a comparison, look at the behavior of generic containers

public class NonCovariantGenerics {
	// Compile Error: incompatible types;
	List<Fruit> flist = new ArrayList<Apple>();
}

The above code fails to compile due to "Cannot assign a generic type involving Apple to a generic type involving Fruit". Although Apple is a subclass of Fruit, here we are comparing the "type of the container", not the "type held by the container", so it is not possible to assign an Apple List to a Fruit List. (Apple inherits from Fruit, but List<Apple> does not inherit from List<Fruit>)

Covariance and Generics

Unlike arrays, generics have no built-in covariant type. If you want to establish some type of upcast relationship between two types, you need to use wildcards, as follows:

public class GenericsAndCovariance {
	public static void main(String[] args) {
		// Wildcards allow convariance
		List<? extends Fruit> flist = new ArrayList<Apple>();
		// compile Error: can't add any type of object
		// flist.add(new Apple());
		// flist.add(new Fruit());
		// flist.add(new Object());
		flist.add(null);				// legal but uninteresting
		Fruit f = flist.get(0);
	}
}

The type of flist is List<? extends Fruit>, which can be read as "a list with any type that inherits from Fruit". However, although the assignment is successful at this time, it cannot add any type of Fruit to this List. For flist, the compiler knows that a certain type of Fruit is stored in it, but it does not know which type of Fruit it is. So the compiler specifically adds any object to it. However, calling a method that returns a Fruit is allowed, because the compiler knows that the objects in this List have at least the type of Fruit, so it is safe to do so

Take a closer look with a simple Holder class:

public class Holder<T> {
	private T value;
	public Holder() {}
	public Holder(T val) { value = val; }
	public void set(T val) { value = val; }
	public T get() { return value; }
	public boolean equals(object obj) {
		return value.equals(obj);
	}

	public static void main(String[] args) {
		Holder<Apple> apple = new Holder<Apple>(new Apple());
		Apple d = apple.get();
		Apple.set(d);
		// Holder<Fruit> fruit = apple;		// can't upcast
		Holder<? extends Fruit> fruit = apple;	// OK
		Fruit p = fruit.get();
		d = (Apple)fruit.get();					// return Ojbect
		try {
			Orange c = (Orange)fruit.get();		// no warning
		} catch(Exception e) { System.out.println(e); }
		// fruit.set(new Apple());				// cannot call set()
		// fruit.set(new Fruit());				// cannot call set();
		System.out.Println(fruit.equals(d));	// OK
	}
}

As above, if a Holder<Apple> is created, it cannot be upcast to Holder<Fruit>, but it can be upcast to Holder<? extends Fruit>. If get() is called, it just returns a Fruit - that's all it can know, given the boundary "any object that extends from Fruit". You can cast the return result of get() to any specific Fruit type and not get a warning, but you may get a ClassCastException at runtime.

The set method can't work on Apple or Fruit, because the parameter of set is also "? extends Fruit", which means it can be anything that inherits from Fruit, in which case the compiler cannot verify its safety, so calling the set() method will cause it to fail to compile.

The equals() method works well because it accepts a parameter of type Object instead of type T and does not need to judge its validity.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324515166&siteId=291194637
Recommended