Java 泛型 - 具有继承和接口的更复杂示例

问题描述 投票:0回答:1

大多数关于“泛型”的 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。

java generics
1个回答
0
投票
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");
。这主要是泛型的含义:捕获诸如“返回值的类型与参数的类型相同,但调用者可以在类型系统中选择该类型”之类的二阶概念。
    

© www.soinside.com 2019 - 2024. All rights reserved.