Problems encountered with Kotlin generics

In the process of using kotlin, I encountered some generic problems, so I simply studied them together;

About Wildcard

Java has ?, extends, super;
Kotlin has *, out, in;
although the representation methods are different, they can be considered as corresponding and equivalent respectively;

What needs to be clarified is its characteristics, such as:

The wildcard type parameter ? extends E indicates that this method accepts a collection of objects of E or some subtypes of E, not just E itself. This means that we can safely read E from it (the elements of the collection are instances of subclasses of E), but not write to it, because we don't know what objects conform to that unknown subtype of E. In turn, this restriction allows Collection to be represented as a subtype of Collection<? extends Object>. In short, a wildcard type with an extends limit (upper bound) makes the type covariant.

This content comes from Kotlin Chinese Network - Generics , mainly to explain two features:

? extends E表示数据不能安全写入,但可以安全读出;(协变)
? super E表示数据可以安全写入,但无法安全读出;(逆变)

The "safety" here of course refers to type safety. Because of type erasure , all generic safety checks are performed at compile time.

The concept is too vague, you can know it by doing it:

	static void testOutType() {
    
    
		List<Integer> list1 = new ArrayList<>();
		List<? extends Number> list2 = new ArrayList<>();
		list1.add(1);
		list1.add(2);
//		list2.add(1);//不允许
		list2 = list1;
		//取出来的数据可以确定是Number的子类
		Number n1 = list2.get(0);
		Number n2 = list2.get(1);
	}

The list2 declared as ? extends Number cannot add Integer, and the compilation directly fails;
although Integer is a subclass of Number, the compiler believes that it is not sure whether the added (written) data is safe, it may be Integer or other It is a subclass of Number, so it is not safe to write;
but when reading, it can be determined that it is a subclass of Number, so it is considered safe;

	static void testInType() {
    
    
		List<Number> list1 = new ArrayList<>();
		List<? super Integer> list2 = new ArrayList<>();
		list2 = list1;
		list2.add(1);//允许
		list2.add(2);//允许
		//取出来的数据只能视为Object
		Integer a = (Integer) list2.get(0);
		Object b = list2.get(1);
	}

The list2 declared as ? super Integer can add Integer, which is more relaxed in comparison;
because it is determined that the added data must be the parent class of Integer, the compiler thinks it is safe;
on the contrary, when reading, only Treat the read data as Object and display the conversion, which is obviously unsafe, so it is considered unsafe to read;

	static void testType(){
    
    
		List<Integer> list1 = new ArrayList<>();
		List<?> list2 = new ArrayList<>();
		list1.add(1);
//		list2.add(2);//不允许
		list2 = list1;
		Object a = list1.get(0);
	}

The list2 declared as ? takes into account the above two characteristics. One is that adding data is not allowed because of uncertain type safety, and the other is that the read data can only be Object; ? is
implemented as ? extends Object by default, so it will have such characteristics ;

Then, corresponding to kotlin:

? extends E	等价于out E;
? super E等价于in E;
?等价于*;

question

Now there are 3 interfaces like this,

interface Gesture {
    
    
    fun onMove()
    fun setGestureListener(listener: GestureListener<out Gesture>)
}

interface Fling : Gesture {
    
    
    fun onFling()
}

interface GestureListener<F> {
    
    
    fun done(gesture: F)
}

And there is such an implementation class, which is written in Java like this:

public class FlingImpl implements Fling {
    
    
	private GestureListener listener;//可以规避

	@Override
	public void onMove() {
    
    
		listener.done(this);//因为没有声明F,规避了extends无法写入的问题
	}

	@Override
	public void setGestureListener(GestureListener<? extends Gesture> listener) {
    
    
		this.listener = listener;
	}

	@Override
	public void onFling() {
    
    
		System.out.println("invoke onFling");
	}
}

The GestureListener here allows not to declare type parameters, but this is not possible in Kotlin. Kotlin does not allow the use of generic types without specified type parameters ;
for example, writing like the following will report an error:

class FlingImpl : Fling {
    
    
    private var listener: GestureListener<out Gesture>? = null

    override fun onMove() {
    
    
        listener?.done(this)//编译出错
    }

    override fun setGestureListener(listener: GestureListener<out Gesture>) {
    
    
        this.listener = listener
    }

    override fun onFling() {
    
    
        println("invoke onFling")
    }
}

The GestureListener here cannot be avoided, because the type parameter must be specified;
and if the type parameter is specified, because of the characteristics of out Gesture , this listener cannot be written , so the listener?.done method cannot be compiled, and it will report like this a mistake:

Kotlin: Out-projected type 'GestureListener<*>?' prohibits the use of 'public abstract fun done(gesture: F): Unit defined in com.xter.callback.kotlin.GestureListener'

solve

The simplest and most practical solution is to cast, because the called class or interface may not be able to be changed; then the solution
is to change out to in, and use as to cast:

class FlingImpl : Fling {
    
    
    private var listener: GestureListener<in Fling>? = null

    override fun onMove() {
    
    
        listener?.done(this)
    }

    override fun setGestureListener(listener: GestureListener<out Gesture>) {
    
    
        this.listener = listener as? GestureListener<in Fling>?
    }

    override fun onFling() {
    
    
        println("invoke onFling")
    }
}

This should work fine.
The same idea is also applicable in Java, but Java is relatively loose, so in some scenarios it can be done without conversion.

Guess you like

Origin blog.csdn.net/ifmylove2011/article/details/105989476