6

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;
}
3
  • 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? Commented Jun 25, 2011 at 0: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. Commented Jun 25, 2011 at 0: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. Commented Mar 14, 2019 at 17:46

6 Answers 6

7

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

Sign up to request clarification or add additional context in comments.

5 Comments

So, as it happens there is actually a tricky way around this. List<Record> a = (List<Record>)(List<? extends Record) b; By double casting the list of subrecords, it is actually possible to reference b using a.
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<Record>. It is true the other way around.
Actually it does work at runtime. And you've got it backwards. List<? extends Record> IS a List<Record> because anything that extends Record is a Record, because something that extends Record contains all the properties of a Record.
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<Record> can hold Records (the following statement is valid in java : List<Record>.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
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;

3 Comments

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.
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. :'(
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, it will work on runtime), but it is not typesafe.
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 :)

Comments

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.

Comments

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.

2 Comments

No, List<? extends Record> has both List<Record> and List<Subrecord> as subtypes. The compiler should give you a warning here. The is a relation is the wrong notation.
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.
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;
  ...
}

5 Comments

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 ?
@MirroredFate, I was wrong. I don't understand. There is only one list here -- new ArrayList<SubRecord>(). You want to have two variables that refer to this list, myList and records, right?
@Snicolas If I add generics to the List<Interface> 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, and you want to be able to do myRecordKeeper.myList.add(anyRecord)?
@MirroredFate, you must give code to explain how you use your generic list (super type)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.