语言:java
一个奇怪的要求:
class Foo<A> {
public <B> Foo<B> foo(Foo<A> a) {...}
}
class Bar<A> {
public <B> Bar<B> foo(Bar<A> a) {...}
}
要求为Foo和Bar抽出一个接口
这个来源于想要在Kotlin里实现Functor,Applicative和Monad的接口,却发现很难写
直接来说方法
interface IGeneric<ValType, ClassType extends IGeneric<?, ClassType>> {
<R> IGeneric<R, ClassType> foo(IGeneric<ValType, ClassType> val);
}
class Foo<A> implements IGeneric<A, Foo<?>> {
public <R> Foo<R> foo(IGeneric<A, Foo<?>> val) {...}
}
class Bar<A> implements IGeneric<A, Bar<?>> {
public <R> Bar<R> foo(IGeneric<A, Bar<?>> val) {...}
}
首先一个常规技巧,接口方法要求子类方法返回子类
interface IExample<T extends IExample> {
T foo();
}
class Foo implements IExample<Foo> {
Foo foo();
}
但是这里我们不能这么干,因为这样会变成:
class Foo<T> implements IExample<Foo<T>> {
Foo<T> foo(...);
}
返回值的类型参数不应该是T
而如果写成implements IExample<Foo<*>>
,返回值就是Foo<?>
,调用者需要强转。或者返回类型改为Foo<R>
(协变),在实现内部强转,还是会警告,不优雅
所以思路就是把子类中的类型参数拿出来,接口的类型参数中既有值的类型参数,又有子类的类型参数。所以叫扁平化类型参数。
这样一来,子类实现时实际上的签名是public <R> IGeneric<R, Foo<?>> foo(IGeneric<A, Foo<?>> val)
。返回类型协变一下,就可以变成Foo<R>
了。
一般来说会有这种要求的接口,只是为了给子类加一些约束而不是为了依赖接口(其实也没差啦),所以会直接使用子类。这样一来就不用调用方强转了。而在实现里强转也很神秘地不会报警告。可以亲自验证一下。(但其实这个强转并不保证成功才对,因为完全可以写class Bar implements IGeneric<A, Foo<?>>
。完全不知道为什么没警告)
附上Monad的示例
interface Monad<ValType, MonadType : Monad<*, MonadType>> {
fun <B> flatMap(func: (ValType) -> Monad<B, MonadType>): Monad<B, MonadType>
}
class Maybe<T>(val value: T?) : Monad<T, Maybe<*>>, GenericClass<T> {
override fun <R> flatMap(func: (T) -> Monad<R, Maybe<*>>): Maybe<R> =
Maybe((value?.let(func) as? Maybe<R>)?.value)
}