Search code examples
javagenerics

Java Generics - complexer example with inheritance and interface


Most Java tutorials on "Generics" explain either working with Classes and Methods. That's quite simple.

Putting all together in a more complex example is not trivial. I get a number of compile errors.

Can you help getting this complex example right? It may be useful for others as well. I know also that the BaseObjectData is of course not right.

The interface:

public interface ComparatorBase<T> {
    List<Difference> isSame(T otherObject);
}

With Difference:

public record Difference(String id, String left, String right) { }

The base class is (Errors: unknow methods: otherObject.getId() or 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;
    }
}

Then an inherited class: (Error: unknown method: 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;
    }
}

Test class:

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( "BO_" + id, data1, data2),
                new InheritLevel1Data( "ID1_" + id, data1, data2, data3));
    }
}

The output should give the differences: data-2 vs data-2a AND data-3a vs data-3, etc.


Solution

  • @rzwitserloot - and others - thank you for helping! Much appreciated

    Primary goal was to perform a deep dive into Generics via a complex inheritance example.

    My mistake was that I thought that an inherited method signature with a base-class object parameter would (also) match with an inherited object. That is of course not so! That is a different method.

    So, inheritedLevel1Data.isSame( InheritedLevel1Data otherObject) is a completely different method signature than the inherited isSame( BaseObjectData otherObject).

    Since working with Java 0.92 I was taken by surprise by Java. Never too old to learn ;-)

    ComparatorBase:

    public interface ComparatorBase<T> {
        List<Difference> isSame(T left, T right);
    }
    

    Data level-0:

    @NoArgsConstructor
    @Setter
    @Getter
    public class BaseObjectData {
        private String id;
        private String objectValue1;
        private String objectValue2;
    
        private BaseComparator comparatorBase = new BaseComparator();
    
        public BaseObjectData(String id, String objectValue1, String objectValue2) {
            this.id = id;
            this.objectValue1 = objectValue1;
            this.objectValue2 = objectValue2;
        }
    
        public List<Difference> isSame(BaseObjectData otherObject) {
            if(otherObject != null) {
                return comparatorBase.isSame(this, otherObject);
            }
            return new ArrayList<>();
        }
        BaseObjectData findMatchingObject( List<BaseObjectData> otherObjects) {
            for( BaseObjectData object: otherObjects) {
               if( object.getId().equals( id)) {
                   return object;
               }
            }
            return null;
        }
    }
    

    Data level-1:

    @NoArgsConstructor
    @AllArgsConstructor
    @Getter
    @Setter
    public class InheritLevel1Data extends BaseObjectData {
        private String objectValue3;
        private InheritLevel1Comparator comparatorBase = new InheritLevel1Comparator();
    
        public InheritLevel1Data(String id, String d1, String d2, String d3) {
            super( id, d1, d2);
            this.objectValue3 = d3;
        }
        // Don't use InheritLevel1Data in the signature
        public List<Difference> isSame(BaseObjectData otherObject) {
            if( otherObject instanceof InheritLevel1Data i1) {
                return comparatorBase.isSame(this, i1);
            }
            return null;
        }
    }
    

    Data level-2: InheritLevel2Data:

    @NoArgsConstructor
    @AllArgsConstructor
    @Getter
    @Setter
    public class InheritLevel2Data extends InheritLevel1Data  {
        private String objectValue4;
        private InheritLevel2Comparator comparatorBase = new InheritLevel2Comparator();
    
        public InheritLevel2Data(String id, String d1, String d2, String d3, String d4) {
            super( id, d1, d2,d3);
            this.objectValue4 = d4;
        }
    
        // Don't use InheritLevel2Data in the signature. 
        public List<Difference> isSame(BaseObjectData otherObject) {
            if( otherObject instanceof InheritLevel2Data i2) {
                return comparatorBase.isSame(this, i2);
            }
            return null;
        }
    }
    

    Compare level-0:

    public class BaseComparator<T extends BaseObjectData> implements ComparatorBase<T> {
    
        public List<Difference> isSame(T left, T right) {
            List<Difference> differences = new ArrayList<>();
            if( ! left.getObjectValue1().equals( right.getObjectValue1())) {
                differences.add( new Difference( left.getId(), left.getObjectValue1(), right.getObjectValue1()));
            }
            if( ! left.getObjectValue2().equals( right.getObjectValue2())) {
                differences.add( new Difference( left.getId(), left.getObjectValue2(), right.getObjectValue2()));
            }
            return differences;
        }
    }
    

    Compare level 1:

    public class InheritLevel1Comparator<T extends InheritLevel1Data> extends BaseComparator<T> {
        public List<Difference> isSame(T left, T right) {
            List<Difference> differences = super.isSame(left, right);
            if (!left.getObjectValue3().equals(right.getObjectValue3())) {
                differences.add(new Difference(left.getId(), left.getObjectValue3(), right.getObjectValue3()));
            }
            return differences;
        }
    }
    

    Compare level-2:

    public class InheritLevel2Comparator<U extends InheritLevel2Data> extends InheritLevel1Comparator<U> {
        public List<Difference> isSame(U left, U right) {
            List<Difference> differences = super.isSame( left, right);
            if (!left.getObjectValue3().equals(right.getObjectValue3())) {
                differences.add(new Difference(left.getId(), left.getObjectValue3(), right.getObjectValue3()));
            }
            return differences;
        }
    }
    

    Test runner:

    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", "data-4");
            List<BaseObjectData> objectDataList2 = buildList( "id-1", "data-1", "data-2a", "data-3", "data-4c");
            List<Difference> differences = new ArrayList<>();
            for( BaseObjectData o: objectDataList1) {
              BaseObjectData o2 = 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, String data4) {
            return List.of(
                    new BaseObjectData( "B0_"+ id, data1, data2),
                    new InheritLevel1Data( "I1_"+ id, data1, data2, data3),
                    new InheritLevel2Data( "I2_"+ id, data1, data2, data3, data4));
        }
    }