大多数关于“泛型”的 Java 教程都解释了如何使用类和方法。这很简单。
将所有内容放在一个更复杂的示例中并非易事。我收到许多编译错误。
你能帮忙把这个复杂的例子弄对吗?它可能对其他人也有用。我也知道 BaseObjectData 当然是不对的。
界面:
public interface ComparatorBase<T> {
List<Difference> isSame(T otherObject);
}
有区别:
public record Difference(String id, String left, String right) { }
基类是(Errors:未知方法:otherObject.getId()或getObjectValue1()):
@NoArgsConstructor
@AllArgsConstructor
@Setter
@Getter
@Builder
public class BaseObjectData<T> implements ComparatorBase<T> {
private String id;
private String objectValue1;
private String objectValue2;
@Override
public List<Difference> isSame(T otherObject) {
List<Difference> differences = new ArrayList<>();
if( ! getObjectValue1().equals( otherObject.getObjectValue1())) {
differences.add( new Difference( getId(), getObjectValue1(), otherObject.getObjectValue1()));
}
if( ! getObjectValue2().equals( otherObject.getObjectValue2())) {
differences.add( new Difference( getId(), getObjectValue2(), otherObject.getObjectValue2()));
}
return differences;
}
T findMatchingObject( List<T> otherObjects) {
for( T object: otherObjects) {
if( object.getId().equals( id)) {
return object;
}
}
return null;
}
}
然后是一个继承类:(Error: 未知方法: otherObject.getObjectValue3())
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
public class InheritLevel1Data<T> extends BaseObjectData implements ComparatorBase<T> {
private String objectValue3;
public InheritLevel1Data( String id, String d1, String d2, String d3) {
super( id, d1, d2);
this.objectValue3 = d3;
}
@Override
public List<Difference> isSame(T otherObject) {
List<Difference> differences = super.isSame( otherObject);
if( ! getObjectValue3().equals( otherObject.getObjectValue3())) {
differences.add( new Difference( getId(), getObjectValue3(), otherObject.getObjectValue3()));
}
return differences;
}
}
测试班:
public class RunnerTest {
private static final Logger logger = Logger.getLogger( "My logger");
public static void main(String[] args) {
List<BaseObjectData> objectDataList1 = buildList( "id-1", "data-1", "data-2", "data-3b");
List<BaseObjectData> objectDataList2 = buildList( "id-1", "data-1", "data-2a", "data-3");
List<Difference> differences = new ArrayList<>();
for( BaseObjectData o: objectDataList1) {
BaseObjectData o2 = (BaseObjectData) o.findMatchingObject( objectDataList2);
differences.addAll( o.isSame( o2));
}
differences.forEach( v -> logger.info( "Verschil: " + v));
}
private static List<BaseObjectData> buildList( String id, String data1, String data2, String data3) {
return List.of(
new BaseObjectData( id, data1, data2),
new InheritLevel1Data( id, data1, data2, data3));
}
}
输出应给出差异:data-2 vs data-2a AND data-3a vs data-3。
public class BaseObjectData<T> implements ComparatorBase<T> {
好吧,对于每个调用 new BaseObjectData
的
caller,it 选择一种类型。该代码不知道做出了什么选择,并且需要编写这样的代码,以便无论做出什么选择,代码都可以工作并且有意义。毕竟,可能有数百万来电者,他们都可以选择不同的东西。提供的约束是
<T extends Object>
(<T>
是它的缩写)——换句话说,几乎没有约束。 T 绝对可以是任何东西。该代码必须“适用于”所有可能的选择。
public List<Difference> isSame(T otherObject) { List<Difference> differences = new ArrayList<>(); if( !getObjectValue1().equals( > otherObject.getObjectValue1())) {
不。您不能在
getObjectValue1()
上调用 otherObject
,因为 otherObject
的类型为 T
,并且 T
可以是此处的任何引用类型。 所有引用类型唯一共同的方法是java.lang.Object
定义的方法,因此,.hashCode()
是您可以在此处调用的方法。 getObjectValue1()
不是,事实上,这让你感到困惑,这强烈表明你完全误解了泛型,最好花点时间理清思绪,从头开始。
从那里你会犯下更严重的错误,例如转换为泛型类型 - 这是你永远不应该做的事情,在泛型中使用原始类型(
List<BaseObjectData>
是不对的,它应该是list<BaseObjectData<SomethingGoesHere>>
),等等。但这些也是需要解释的更复杂的事情。与假设所有 T 都有该方法的微不足道的错误相反。
大多数关于“泛型”的 Java 教程都解释了如何使用类和方法。这很简单。
你犯了一个错误。他们并不简单。如果您认为是这样,您可能需要寻找另一个教程或更加关注。
泛型捕获更高级别的类型信息。他们试图捕获涉及完全不同的方差规则集的新维度(Java 类型本身是协变的;每当需要
Integer
时,Number
就足够了,因为 class Integer extends Number
;相反,泛型是不变的:List<Integer>
当需要 List<Number>
时,效果不好。但是,如果您需要的话,它们有可选的变体:当需要 List<Integer>
时,List<? extends Number>
就可以了。这本身就已经相当复杂,但却是概念所固有的高阶类型。你不能“仅仅”设计一种没有这种复杂性的语言;不能不放弃在类型系统中捕获高阶类型的概念。
泛型完全是编译器想象力的虚构:在运行时几乎没有留下什么,剩下的就像对 JVM 执行的注释一样。这是另一件需要扭转你的想法的事情。它们用于链接事物:
public static <T> T printAndReturn(T in) {
System.out.println(in);
return in;
}
说:调用者可以选择一种类型,任何类型,而这段代码不知道它选择了什么。每个电话都可以做出自己的选择。这里发生的唯一一件事是 T 是链接的:无论调用者为参数选择什么类型,这也是该方法返回的内容。因此,这个:
String x = printAndReturn("hello");
将是有效的(T绑定到String,所以所有T都是这样的 -caller
做出了这个选择,printAndReturn
方法的作者只是命令调用者选择一个类型,它不知道选择了什么) ,而如果你这样写:
public Object printAndReturn(Object in) {
System.out.println(in);
return in;
}
然后
String x = printAndReturn("hello");
不会编译 - 因为虽然你的眼睛可以清楚地看到它会“工作”,但该方法的签名实际上并没有表明返回的值与
in
相同,因此与 具有相同的类型in
。 Javac 不会考虑方法体来处理这些事情,因此,您必须编写 String x = (String) printAndReturn("hello");
。这主要是泛型的含义:捕获诸如“返回值的类型与参数的类型相同,但调用者可以在类型系统中选择该类型”之类的二阶概念。