srh
srh

Reputation: 1661

Generics: How to get values of a Map without casting

I have an abstract class named BaseCode and 2 concrete classes named Location and Department:

public abstract class BaseCode {
   private Integer id;
   ...
   public Integer getId() { return id; }
   public void setId(Integer id) { this.id = id; }
   ...
}

public class Location extends BaseCode {
   ...
}

public class Department extends BaseCode {
   ...
}

I have an abstract class named BaseCodeCache and 2 concrete classes named LocationCache and DepartmentCache. LocationCache and DepartmentCache will be using Singleton.

public abstract class BaseCodeCache {
   ...
}

public class LocationCache extends BaseCodeCache {
   ...
}

public class DepartmentCache extends BaseCodeCache {
   ...
}

  1. In BaseCodeCache, I want to have a java.util.Map whose value can be any type of BaseCode i.e. Location objects or Department objects.
    In LocationCache, I want the java.util.Map to store Location objects.
    In DepartmentCache, I want the java.util.Map to store Department objects.

To accomplish this, I put this code in BaseCodeCache:

private Map<Integer, BaseCode> idMap = new HashMap<Integer, BaseCode>();

  1. I want to have a method to store a value in the java.util.Map.

To accomplish this, I put this code in BaseCodeCache:

public void add(BaseCode baseCode) {
   if (baseCode != null) {
      idMap.put(baseCode.getId(), baseCode);
   }
}

This is how I would use it for Location:

Location location = new Location(); ...
LocationCache.getInstance().add(location);

This is how I would use it for Department:

Department department = new Department(); ...
DepartmentCache.getInstance().add(department);

  1. I want to have a method to get all values in the java.util.Map as a java.util.List.
    In LocationCache this method should return List<Location>.
    In DepartmentCache this method should return List<Department>.

Thats where I am stuck. I want to create this method in BaseCodeCache but when this method is called via LocationCache then it returns List<Location> and when this same method is called via DepartmentCache then it returns List<Department>. Is it possible?

I put this code in BaseCodeCache:

public List<BaseCode> getList() {
   return new ArrayList<BaseCode>(idMap.values());
}

But the above code returns List<BaseCode>. When I would call it this way:

List<Location> allLocations = LocationCache.getInstance().getList();

Then java will not let it compile and give this error message:

Type mismatch: cannot convert from List<BaseCode> to List<Location>

I can get it fixed by getting List<BaseCode> and then converting it to List<Location> by looping but thats does not look right.

Can it be done?

Upvotes: 3

Views: 1365

Answers (1)

Robby Cornelissen
Robby Cornelissen

Reputation: 97130

Implement using generics as follows:

public abstract class BaseCodeCache<T extends BaseCode> {
    private Map<Integer, T> idMap = new HashMap<>();

    public void add(T baseCode) {
        if (baseCode != null) {
            idMap.put(baseCode.getId(), baseCode);
        }
    }

    public List<T> getList() {
        return new ArrayList<>(idMap.values());
    }
}

public class LocationCache extends BaseCodeCache<Location> {}

public class DepartmentCache extends BaseCodeCache<Department> {}

This will then enable you to do the following without any compilation errors:

LocationCache locationCache = new LocationCache();
locationCache.add(new Location());

List<Location> locations = locationCache.getList();

Even better, you will get compilation errors if you try to add or retrieve the wrong type of objects:

locationCache.add(new Department()); // won't compile
List<Department> departments = locationCache.getList(); // won't compile

Upvotes: 4

Related Questions