我正在尝试在泛型方法中初始化泛型类型。从那以后,我了解到你不能在接口中要求构造函数,所以我想出了如何使用泛型来大致伪造相同的行为:
interface Stack<V, T>{
public V getEmptyStack(); //V is supposed to be the subclass
public V copy(V s);
public void push(T e);
public T pop();
public boolean isEmpty();
}
class ArrayStack<T> implements Stack<ArrayStack<T>, T>{ //Have to pass subclass
public ArrayStack<T> getEmptyStack(){ //Constructor returns type of the class.
return new ArrayStack<T>();
}
...
有了上面的定义,我可以定义一个泛型方法“reverse”:
public static <V, S extends Stack<S,V> > S reverse(S s){
s = s.copy();
S out = s.getEmptyStack(); //Trying to initialize out as simply as possible.
while(!s.isEmpty()){
out.push(s.pop());
}
return out;
}
我希望接口要求构造函数方法返回实现接口的子类型。如果正确实现了当前接口,则它会这样做;但是它有点复杂。
如果Java不能以更简单的方式做到这一点,我想知道初始化泛型类型的惯用模式。人们对getDeclaredConstructor
和newInstance
的困惑会比我上面写的少吗?还是我写的很好?
2条答案
按热度按时间nhn9ugyo1#
反射是个错误的选择。一般来说,将
Class<?>
引用作为“类型范围操作”的位置是错误的。听起来很诱人,不是吗?让我们讨论一下为什么它感觉是正确的答案,这样我们就知道我们需要找到什么替代方案,因为我会跟进为什么它是错误的。
为什么
Class<?>
/ static方法感觉不错类本身 * 是 * 这样的东西应该存在的地方(在arraylist上有意义的操作应该存在于arraylist类型中-这是一个得到 * 非常 * 广泛同意的声明)。
在Java术语中,有两种不同的东西都充当“class-global”函数:构造函数和静态方法。在更务实的层面上,这两个结构之间实际上没有任何区别:它们都不需要接收器(
x.foo()
-x
是接收器),并且它们都完全在类型层次结构之外操作。静态方法不能被继承。语言让它看起来像是你做的,但这是假的--它们不参与动态分派,任何对静态方法的调用都是由javac
“链接”的,并且在运行时不会改变(而不是...对示例方法的任何调用,其中JVM将应用动态分派并调用最具体的覆盖)。构造函数也不能。然而,对于调用任何特定
ArrayStack
没有意义的操作,但调用“数组堆栈”的概念是有意义的,我们所有的教科书都说:这应该是ArrayStack
内部的静态方法。如果我们想要执行的特定操作导致创建一个新的操作,我们需要一个构造函数(这只是一个奇怪的静态方法)。但是,正如我提到的,静态方法只是不“做”继承或类型层次结构-你不能在接口中定义静态方法(或构造函数),句号。因此,当你想要泛型和类型层次结构的力量时,你只需要去别处看看。
理论上,你确实可以只传递
Class<?>
示例,并使用.getConstructor().newInstance()
作为替代“类型层次结构参与,泛化构造函数”,甚至使用例如。.getDeclaredMethod().invoke(null, params-go-here)
声明静态方法。但是我们现在已经完全抛弃了打字系统。不可能使用类型系统来强制无参数构造函数或静态方法的存在。反射是很难处理的(例如,各种各样的异常都会出现,现在的东西都是“字符串类型的”--如果你输入了文档中规定的静态方法,编译器不会告诉你。与此相反,你必须实现一个方法的名称,因为你
implements
一些接口,在这种情况下,javac
会立即告诉你你做错了。该工具还有助于:我可以只输入接口Iimplements
中定义的方法的前几个字母,在eclipse中点击CMD+SPACE,eclipse立即填充所有空白(全名,public
,参数类型和合理的名称,甚至是throws
子句(如果相关)和@Override
注解)。如果您试图使用反射来调用静态方法或构造函数,则这些都不可用。Class<?>
也是‘破碎的泛型’--它们只能深入一个层次。你可以有一个Class<String>
,它的.getConstructor().newInstance()
方法的类型是String
。但是你不能有一个Class<List<String>>
--所有字符串只有一个类对象,它不能被泛化。这是一个相当有害的负面影响,甚至尝试。那你怎么办?
工厂!
我知道我知道这是个迷因哦,那些疯狂的java程序员和他们的
BeanFactoryCreatorMachineDoohickeyFactory
疯狂。但是,我想,这只是误导。至少,不要让这阻止你使用这个概念,因为它正是你在这里想要的:它是类型系统参与的,泛型能力的类范围操作**。
这个想法很简单。让我们采用这个堆栈概念,并规定任何
Stack
概念的实现都必须提供一些类型范围的操作,这包括创建一个新的空操作,但也可能包括更多的操作。例如,考虑一个广义的数字系统(其中您有一些数字概念,并且您有'复数','向量数','真实的','存储为除数和除数的数字'等。你可能需要类型范围的'construct me a new instance of you,with this int value',但也需要一个'construct me a multiplicative unit value of yourself,例如。0表示真实的,0+ 01表示复数,除1对角矩阵外全零,等等。工厂就是这样做的。您可以创建第二个类型层次结构,该层次结构表示示例(通常每个类型只有一个示例),该示例表示能够执行以下操作的对象:
这是你想要的:
给定'某种StackFactory',您可以在其上调用
create()
并返回Stack<T>
。太好了但是,如果你知道stackfactory的具体类型,你会得到一个更具体的类型。这是可行的:这里的一个缺点是java不允许你“编程”你的类型变量。因此,你不能有一个表示堆栈类型的typevar(比如
ArrayStack
),然后有一个方法将该typevar与一个元素typevar组合,从而从单独的typevarArrayStack
和String
生成类型ArrayStack<String>
。你的反向方法不能完全完成这项工作。这将是非常整洁的:
但这是一个Java限制,你不能解决,除了'铸造'到泛型(这没有做任何事情,除了使编译器停止抱怨。它实际上并不输入test),而
@SuppressWarnings
则是当你这样做时编译器向你抛出的警告。编辑:根据要求,
reverse
将如何工作。此外,作为奖励,当您的“工厂”只需要暴露一件事(例如,接口将只有一个方法),相反,您可以只使用Supplier<X>
(或Function<K, V>
,如果操作需要一个参数,等等-来自java.util.function包的东西)。这实际上正是Collection.toArray()
所采用的(它的签名是public <T> T[] toArray(IntFunction<T[]> arrayMaker)
-其中arraymaker将请求的大小转换为该大小的数组,并且可以像String[]::new
一样简单,这是一个IntFunction<String[]>
):这假设
Stack
接口更新为要求所有堆栈都能够返回其出厂。或者,您可以要求工厂是参数的一部分。这就是toArray
的工作方式(以IntFunction<T[]>
的形式传入工厂)。我们可以“一气呵成”,可以说,和完全摆脱我们的“util”类(util类有点丑,它们真的不应该存在)。Java现在有默认方法:
或者,使用传入工厂:
使用最后一个:
这是整洁的,因为它甚至让我们分配给一个变量的类型
ArrayStack
,其余的这些选项不能管理。4uqofj5v2#
对于初学者来说,Stack不需要在类型上声明第二个泛型类型参数。
可以成为
这应该会简化很多事情。
有了上面的定义,我可以定义一个泛型方法“reverse”:
您可以通过选择不同的实现来实现这一点,而无需使用这种奇怪的方法。您已经在修改输入堆栈。您也可以重用该示例。
人们对
getDeclaredConstructor
和newInstance
的困惑会比我上面写的少吗?是的,可能吧。
我希望接口要求构造函数方法返回实现接口的子类型
如果我真实的的需要这样做--我过去就有过--那么我会在界面上放一个JavaDoc来说明这个需求。