5

Ok, so here is my problem:

I have a list containing interfaces - List<Interface> a - and a list of interfaces that extend that interface: List<SubInterface> b. I want to set a = b. I do not wish to use addAll() or anything that will cost more memory as what I am doing is already very cost-intensive. I literally need to be able to say a = b. I have tried List<? extends Interface> a, but then I cannot add Interfaces to the list a, only the SubInterfaces. Any suggestions?

I want to be able to do something like this:

List<SubRecord> records = new ArrayList<SubRecord>();
//add things to records
recordKeeper.myList = records;

The class RecordKeeper is the one that contains the list of Interfaces (NOT subInterfaces)

public class RecordKeeper{
    public List<Record> myList;
}
John Kugelman
  • 349,597
  • 67
  • 533
  • 578
MirroredFate
  • 12,396
  • 14
  • 68
  • 100
  • There is no clean way to do that without modifying the definition of the lists. Imagine you did it, then this would be right and it is not: `class Record2 extends Record { ... }; recordKeeper.myList = records; recordKeeper.myList.add(new Record2());` Can you see why it is wrong? – aalku Jun 25 '11 at 00:21
  • If you use a '? extends Record' then you mean you don't know the exact type of the list so you have to use typecast (on the list) in order to add elements. Java would know that every element on the list extends Record so you can get Record objects from it but you cannot insert any Record since the List is typed with a type that Java does not know. You want to enter the party but the doorman lost the guest list. – aalku Jun 25 '11 at 00:28
  • So, eight years later, I look back at this question with the realization that the Java compiler is (was? haven't used Java in a while) just dumb, and is bad at LSP. – MirroredFate Mar 14 '19 at 17:46

6 Answers6

6

This works :

public class TestList {

    interface Record {}
    interface SubRecord extends Record {}

    public static void main(String[] args) {
        List<? extends Record> l = new ArrayList<Record>();
        List<SubRecord> l2 = new ArrayList<SubRecord>();
        Record i = new Record(){};
        SubRecord j = new SubRecord(){};

        l = l2;
        Record a = l.get( 0 );
        ((List<Record>)l).add( i );       //<--will fail at run time,see below
        ((List<SubRecord>)l).add( j );    //<--will be ok at run time

    }

}

I mean it compiles, but you will have to cast your List<? extends Record> before adding anything inside. Java will allow casting if the type you want to cast to is a subclass of Record, but it can't guess which type it will be, you have to specify it.

A List<Record> can only contain Records (including subRecords), A List<SubRecord> can only contain SubRecords.

But A List<SubRecord> is not a List<Record> has it cannot contains Records, and subclasses should always do what super classes can do. This is important as inheritance is specilisation, if List<SubRecords> would be a subclass of List<Record>, it should be able to contain ` but it'S not.

A List<Record> and a List<SubRecord> both are List<? extends Record>. But in a List<? extends Record> you can't add anything as java can't know which exact type the List is a container of. Imagine you could, then you could have the following statements :

List<? extends Record> l = l2;
l.add( new Record() );

As we just saw, this is only possible for List<Record> not for any List<Something that extends Record> such as List<SubRecord>.

Regards, Stéphane

xxfelixxx
  • 6,512
  • 3
  • 31
  • 38
Snicolas
  • 37,840
  • 15
  • 114
  • 173
  • So, as it happens there is actually a tricky way around this. List a = (List)(List extends Record) b; By double casting the list of subrecords, it is actually possible to reference b using a. – MirroredFate Jun 27 '11 at 14:26
  • I doubt you can add records at runtime using this. Moreover the second casting (right most one) doesn't seem needed. And no, in your explain, it's wrong to say that List extends Record> is a List. It is true the other way around. – Snicolas Jun 27 '11 at 16:01
  • Actually it does work at runtime. And you've got it backwards. List extends Record> IS a List because anything that extends Record is a Record, because something that extends Record contains all the properties of a Record. – MirroredFate Jun 27 '11 at 16:34
  • Ok. Inheritance is a specialization. Right ? So, when you say "everything that extends Record is a Record", this is true. BUT, if what you are saying for containers would be true : as a List can hold Records (the following statement is valid in java : List.add( new Record() );), so it should be true for classes that extend it. And youc can't add Records to a List extends Record>, the following statement is not valid : List extends Record>.add( new Record() );. Regards, Stéphane – Snicolas Jun 27 '11 at 19:37
  • http://stackoverflow.com/questions/6504208/another-java-generic-question/6504576#6504576 – Snicolas Jun 28 '11 at 09:53
3

Just to explain why Java does not permit this:

  • A List<Record> is a list in which you can put any object implementing Record, and every object you get out will implement Record.
  • A List<SubRecord> is a list in which you can put any object implementing SubRecord, and every object you get out will implement SubRecord.

If it would be allowed to simply use a List<SubRecord> as a List<Record>, then the following would be allowed:

List<SubRecord> subrecords = new ArrayList<SubRecord>();
List<Record> records = subrecords;
records.add(new Record()); // no problem here

SubRecord sr = subrecords.get(0); // bang!

You see, this would not be typesafe. A List (or any opject of a parametrized class, in fact) can not typesafely change its parameter type.

In your case, I see these solutions:

  • Use List<Record> from start. (You can add SubRecord objects to this without problems.)
    • as a variation of this, you can use List<? super Subrecord> for the method which adds stuff. List<Record> is a subtype of this.
  • copy the list:

    List<Record> records = new ArrayList<Record>(subrecords);
    

To exand a bit on th variation:

void addSubrecords(List<? super Subrecord> subrecords) {
    ...
}


List<Record> records = new ArrayList<Record>();
addSubrecords(records);
recordkeeper.records = records;
Paŭlo Ebermann
  • 73,284
  • 20
  • 146
  • 210
  • I am going to try your List super Subrecord> solution, as that may be exactly what I am looking for. I hope so, as I feel that while my current solution works, it is a bit hack-ish. – MirroredFate Jun 27 '11 at 17:57
  • Ahh, sadly I still could find no way of using this such that it would accomplish the same thing that my answer does. And I had such high hopes. :'( – MirroredFate Jun 27 '11 at 19:33
  • There is no way to do this in a typesafe way. You can cheat the compiler (and as the VM does not know the types if you don't use special [typechecking collections](http://download.oracle.com/javase/6/docs/api/java/util/Collections.html#checkedList(java.util.List,%20java.lang.Class)), it will work on runtime), but it is not typesafe. – Paŭlo Ebermann Jun 27 '11 at 20:11
2

THIS WORK, BUT YOU SHOULD BE WARN !!!!

@Override
// Patient is Interface
public List<Patient> getAllPatients() {

   // patientService.loadPatients() returns a list of subclasess of Interface Patient
   return (List<Patient>)(List<? extends Patient>)patientService.loadPatients(); 
}

You can cast from List of Objects to List of Interface in this way. But, if you get this list somewhere in your code and if you try to add something to this list, what would you add? Inteface or Subclass of this interface ? You actually loose information of the type of list, because you let it hold Interface, so you can add anything that implement this interface, but the list is holding the subclasses only, and you could easily get class cast exception if you try to do operations like add or get on this list with some other subclass. The solution is: Change The type of source list to list<Interface> instead of cast, then you are free to go :)

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Krzysiek
  • 21
  • 2
1

You can't do that and be safe because List<Interface> and List<SubInterface> are different types in Java. Even though you can add types of SubInterface to a list of Interface, you can't equate the two lists with different interfaces even if they're sub/super interfaces of eachother.

Why is it that you want to do b = a so bad? Do you just want to store a reference to the SubInterface list?

On a side note, I suggest you read this documentation on the oracle site: http://download.oracle.com/javase/tutorial/java/generics/index.html It explains and goes deep into generics very well.

peacey
  • 177
  • 1
  • 7
1

So, the rather simple solution a friend of mine found was this:

recordKeeper.myList = (List<Record>)(List<? extends Record>)records;

This works as I understand it because it takes baby steps. List<SubRecord> is a List<? extends Record>, and List<? extends Record> is a List<Record>. It might not be pretty, but it works nonetheless.

MirroredFate
  • 12,396
  • 14
  • 68
  • 100
  • No, `List extends Record>` has both `List` and `List` as subtypes. The compiler should give you a warning here. The **is a** relation is the wrong notation. – Paŭlo Ebermann Jun 27 '11 at 18:00
  • 6
    If you are happy, okay. Just make sure you are not surprised if it starts breaking at another point due to this non-typesafe cast, when you change your program somewhere else. – Paŭlo Ebermann Jun 28 '11 at 14:26
0

There's no way to do it that is type safe.

A List<SubInterface> cannot have arbitrary Interfaces added to it. It is a list of SubInterfaces after all.

If you are convinced that this is safe to do even though it is not type safe you can do

@SuppressWarnings("unchecked")
void yourMethodName() {
  ...
  List<Interface> a = (List<Interface>) b;
  ...
}
Mike Samuel
  • 118,113
  • 30
  • 216
  • 245
  • If you don't want to add then why do you write : "but then I cannot add Interfaces to the list, only the SubInterfaces" in your question ? – Snicolas Jun 24 '11 at 23:54
  • @MirroredFate, I was wrong. I don't understand. There is only one list here -- `new ArrayList()`. You want to have two variables that refer to this list, `myList` and `records`, right? – Mike Samuel Jun 24 '11 at 23:56
  • @Snicolas If I add generics to the List so that it becomes List extends Interface>, I can no longer add Interface to that list, I can only add things that implement or extend Interface. I do not want that. @Mike Samuel I edited it to make it clearer. Yes, myList and records should reference the same list. – MirroredFate Jun 25 '11 at 00:02
  • @MirroredFate, and you want to be able to do `myRecordKeeper.myList.add(anyRecord)`? – Mike Samuel Jun 25 '11 at 00:06
  • @MirroredFate, you must give code to explain how you use your generic list (super type) – Snicolas Jun 25 '11 at 02:09