简体   繁体   中英

Java generics in List return type on a method inherited from multiple interfaces

I'm currently working at a company that has a diverse set of modules. In that company if you want to provide module internals you provide it via a java interface, that hides the actual implementing type and gives an interface for the requesting module. Now I want to have one provider to be able to provide data for multiple modules that expose different fields or methods of the actual internal data.

Therefore I have an internal Object, which has some data and I have an interface for each module that needs access to some but not strictly all fields. Finally I have an external object that implements all those interfaces and holds an instance of the internal object to delegate the method calls:

public class InternalObject {
    public int getA() { return 0; }
    public int getB() { return 0; }
}

public interface ModuleXObject {
   int getA();
}

public interface ModuleYObject {
    int getA();
    int getB();
}

public class ExternalObject implements ModuleXObject, ModuleYObject {
    private InternalObject _internal;

    public int getA() { return _internal.getA(); }
    public int getB() { return _internal.getB(); }
}

Now that is all fine and dandy, but if I want to provide - lets say - repository methods for finding a list of said objects typed for the correct module, I run into problems with how I can achieve that. I would wish for something like the following:

public interface ModuleXObjectRepository {
    List<ModuleXObject> loadAllObjects();
}

public interface ModuleYObjectRepository {
    List<ModuleYObject> loadAllObjects();
}

public class ExternalObjectRepository implements ModuleXObjectRepository, ModuleYObjectRepository {
    public List<ExternalObject> loadAllObjects() {
        // ...
    }
}

This doesn't compile saying the return type is incompatible. So my question is, if it is possible to achieve something like that and if, how?


I should note that I tried some different approaches which I want to include for completeness and to portray their downsides (in my eyes).

Approach 1 :

public interface ModuleXObjectRepository {
    List<? extends ModuleXObject> loadAllObjects();
}

public interface ModuleYObjectRepository {
    List<? extends ModuleYObject> loadAllObjects();
}

public class ExternalObjectRepository implements ModuleXObjectRepository, ModuleYObjectRepository {
    public List<ExternalObject> loadAllObjects() {
        // ...
    }
}

This approach is quite close to the solution I would prefer, but results in code like this:

List<? extends ModuleXObject> objects = repository.loadAllObjects();

Therefore requiring the user to include the "? extends" into each List-Declaration regarding to an invocation of loadAllObjects().

Approach 2 :

public interface ModuleXObjectRepository {
    List<ModuleXObject> loadAllObjects();
}

public interface ModuleYObjectRepository {
    List<ModuleYObject> loadAllObjects();
}

public class ExternalObjectRepository implements ModuleXObjectRepository, ModuleYObjectRepository {
    public List loadAllObjects() {
        // ...
    }
}

This approach just omits the generic in the ExternalObjectRepository and therefore reduces the type safety too much in my opinion. Also I haven't tested if this actually works.


Just to reharse, is there any possible way to define the loadAllObjects-method in a way that enables users to get lists that are typed with the objects for their respective module without

  • requiring "? extends" in the users code
  • degrading type safety in the repository implementation
  • using class/interface level generics

The challenge with allowing it to be typed as List<ModuleXObject> is that other code may hold is as a List<ExternalObject> .

All ExternalObject instances are ModuleXObject instances but the inverse is not true.

Consider the following additional class:

public class MonkeyWrench implements ModuleXObject{
    //STUFF
}

MonkeyWrench instances are NOT ExternalObject instances but if one could cast a List<ExternalObject> to a List<ModuleXObject> one could add MonkeyWrench instances to this collection, and this causes a risk of run time class cast exceptions and ruins type safety.

Other code could very easily have:

for(ExternalObject externalObject:externalObjectRepository.loadAllObjects())

If one of those instances is a MonkeyWrench instance, run time class cast, which is what generics are meant to avoid.

The implication of ? extends ModuleXObject ? extends ModuleXObject is that you can read any object from the collection as a ModuleXObject but you can't add anything to the collection as other code may have additional constraints on the collection that are not obvious/available at compile time.

I'd suggest in your case to use ? extends ModuleXObject ? extends ModuleXObject as its semantics seem to align with what you want, namely pulling out ModuleXObject instances, eg

ModuleXObjectRepository repo = //get repo however
for(ModuleXObject obj : repo.loadAllObjects()){
    //do stuff with obj
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM