Java using generics with lists and interfaces

2020-03-22 06:33发布

问题:

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;
}

回答1:

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



回答2:

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:

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.



回答4:

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 :)



回答5:

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;
  ...
}


回答6:

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.