Using generics and Class of an object in map - java

I have following class/interface structure (can't modify the source):
public interface Car {}
public class CarA implements Car{
public String getASpecifics() {...}
}
public class CarB implements Car{
public String getBSpecifics() {...}
}
public class Summary {...}
I want to have a generic way of creating Summray for concrete implementations of Car interface which will be open to adding new implementations. My approach is following:
public CarSummarizer {
public interface SummaryGenerator<T extends Car> {
Summary generateSummary(T car);
}
static {
SummaryGenerator<CarA> aGen = c -> {... c.getASpecifics(); ...}
SummaryGenerator<CarB> bGen = c -> {... c.getBSpecifics(); ...}
}
}
Now I'd like to store aGen and bgen in a Map. I want to parametrize it so that I can offer one only public static method which accepts Car car and based on it's class object (car.getClass()) uses correct SummaryGenerator implemenation. That should look something like following:
public static Summary getSummaryForCar(Car car) {
return map.get(car.getClass()).generateSummary(car);
}
I don't know how to declare and instantiate that Map so that it's fully type-safe (i.e. doesn't allow inserting pair (CarC.class, SummaryGenerator<CarD>)). I'd like something like this:
public static <T extends Car> Map<Class<T>, SummaryGenerator<T>> map = new LinkedHashMap<>();
static {
// after instantiation
map.put(CarA.class, aGen);
map.put(CarB.class, BGen);
}
// also support following
public static <T extends Car> void addSummaryGenerator(T car, SummaryGenerator<T> sg) {
map.put(car.getClass(), sg);
}
That doesn't work because generics can't be declared on variables like they can be on functions.
I guess I could define new class public class SummarizerStorage<T extends Car> and the place map inside and just delegate calls. That seems like an overkill and ugly. I feel like it should be done somehow directly.
Declaring map like Map<Class<? extends Car>, SummaryGenerator<? extends Car>> would allow paring of Class<> and SummaryGenerator<> of sibling types. I want to allow only same type pairs.

You can do this with compile-time safety if you wrap your map and only allow to put entries of which the value SummaryGenerator<T> matches a key of Class<T>. To get the SummaryGenerator you need to cast but since you ensured that you only added entries that actually can be cast to SummaryGenerator<T>, this is safe to do. If you try to add an incompatible SummaryGenerator for a given implementation of Car, it would result in a compile error.
public class SummaryGeneratorStorage {
private static final Map<Class<? extends Car>, CarSummarizer.SummaryGenerator<? extends Car>> map = new HashMap<>();
// provides type safety to only add a SummaryGenerator<T> for a key of Class<T>
public static <T extends Car> void add(Class<T> clazz, CarSummarizer.SummaryGenerator<T> sg) {
map.put(clazz, sg);
}
#SuppressWarnings("unchecked")
public static <T extends Car> CarSummarizer.SummaryGenerator<T> get(T car) {
// this cast is safe since the add-method only
// allows a SummaryGenerator<T> to be added for a key of Class<T>
return (CarSummarizer.SummaryGenerator<T>) map.get(car.getClass());
}
private SummaryGeneratorStorage() { }
}
In order to get a Summary, you retrieve the registered SummaryGenerator for the Car and call the implemented generateSummary method. If no SummaryGenerator is found for the passed implementation of Car, just throw an exception or handle it a different way. Note that the signature of add takes a Class and not a Car object since we don't need an instance at this point to make the reference to an implementation of Car.
public class CarSummarizer {
public interface SummaryGenerator<T extends Car> {
Summary generateSummary(T car);
}
static {
// here you cannot pass incompatible implementations of Car (compile-time safety)
SummaryGenerator<CarA> aGen = c -> new Summary(c.getASpecifics());
SummaryGeneratorStorage.add(CarA.class, aGen); // with variable
SummaryGeneratorStorage.add(CarB.class, c -> new Summary(c.getBSpecifics())); // direct
}
public static <T extends Car> Summary getSummary(T car) {
SummaryGenerator<T> generator = SummaryGeneratorStorage.get(car);
if (generator != null) {
return generator.generateSummary(car);
} else {
throw new IllegalArgumentException("no summary generator found");
}
}
}
Here is a test class for the above code. A simple Summary containing a name-field was used and the implementations of Car return A, B or C in their getXSpecifics() methods:
public class CarSummarizerTest {
#Test
public void testCarA() {
CarA car = new CarA();
Summary summary = CarSummarizer.getSummary(car);
Assert.assertEquals(car.getASpecifics(), summary.getName());
}
#Test
public void testCarB() {
CarB car = new CarB();
Summary summary = CarSummarizer.getSummary(car);
Assert.assertEquals(car.getBSpecifics(), summary.getName());
}
#Test
public void testCarC() {
CarC car = new CarC();
Assert.assertThrows(IllegalArgumentException.class, () -> {
CarSummarizer.getSummary(car);
});
}
}
For completeness the other classes:
public class CarA implements Car {
public String getASpecifics() { return "A"; }
}
public class CarB implements Car {
public String getBSpecifics() { return "B"; }
}
public class CarC implements Car {
public String getCSpecifics() { return "C"; }
}
public class Summary {
private final String name;
public Summary(String name) { this.name = name; }
public String getName() { return name; }
}

Related

Write a single generics method to cover multiple methods for String to Enum value conversion

I created two Java enums,
public enum TypeEnum {
TYPE_A, TYPE_B
}
and
public enum FormatEnum{
FORMAT_X, FORMAT_Y
}
Next, I wrote two functions to convert an incoming String to an enum value:
private TypeEnum convertType(String test) {
return TypeEnum.valueOf(test);
}
private FormatEnum convertFormat(String test) {
return FormatEnum.valueOf(test);
}
Next, I wanted to unify these two conversion methods under a single method with generics. I tried this in two ways:
private <T extends Enum> Enum convertToEnumValue(T localEnum, String value) {
return T.valueOf(localEnum.getClass(), value);
}
and
private static <T extends Enum> T convertToEnumValue(Class<T> enumType, String value) {
return (T) T.valueOf(enumType, value);
}
I couldn't write a call to these methods that would compile.
Is there a way to correct them to make them work?
There is no need to declare your own method, as JDK java.lang.Enum already declares one:
FormatEnum y =Enum.valueOf(FormatEnum.class, "FORMAT_Y");
TypeEnum a = Enum.valueOf(TypeEnum.class, "TYPE_A");
This works because Enum is the base class of all enum types and so when you call TypeEnum.valueOf(s); you are calling Enum.valueOf(s)
…Is there a way to correct them to make them work?…
I got your examples to work with these very small corrections…:
class DeduperAnswer {
private <T extends Enum> T convertToEnumValue(T localEnum, String value) {
return ( T ) T.valueOf(localEnum.getClass(), value);
}
private static <T extends Enum> T convertToEnumValue(Class<T> enumType, String value) {
return ( T ) T.valueOf(enumType, value);
}
static public void main(String ...args){
DeduperAnswer da = new DeduperAnswer();
TypeEnum typB = da.convertToEnumValue(TypeEnum.TYPE_B, "TYPE_B");
FormatEnum fmtX = convertToEnumValue(FormatEnum.FORMAT_X.getClass(), "FORMAT_X");
}
}
Of course, there's more than one way to skin a cat — as the saying goes. But seeing as your solution works for you, you're good to go.
I suspect you are looking for the following method:
public static <E extends Enum<E>> E toMember(Class<E> clazz, String name) {
//TODO input validations;
for (E member : clazz.getEnumConstants()) {
if (member.name().equals(name)) {
return member;
}
}
return null; //Or throw element not found exception
}
//More elegant form of the previous one
public static <E extends Enum<E>> E toMember(Class<E> clazz, String name, E defaultMember) {
//TODO input validations;
for (E member : clazz.getEnumConstants()) {
if (member.name().equals(name)) {
return member;
}
}
return defaultMember;
}
Note the generic E extends Enum<E>

How to use Generic bounded param in method

I am trying to write some generic code and facing issue. Here is code
public abstract class AbstractService<D extends IDTO> {
public String ex(D dto) {
return null;
}
}
public class AService extends AbstractService<TestA> {
#Override
public String ex(TestA dto) {
return "a";
}
}
public class BService extends AbstractService<TestB> {
#Override
public String ex(TestB dto) {
return "b";
}
}
class TestA impements IDTO {
}
class TestB impements IDTO {
}
So as you can see, its really simple code, one AbstractService with bounded param that extends IDTO.
Two implementation of service AService and BService which uses their respective DTO.
Not there is another class that need to call ex() method on basis of runtime instance.
here I am facing the problem.
public class TestAll {
public void executeRequest(final IDTO dto){
// serviceMap contains list of all Services here A and B
serviceMap.get(type).ex(dto);
}
}
Problem on line build().
The method build(capture#5-of ? extends IDTO) in the type AbstractService is not applicable for the arguments (IDTO)
Could someone help to fix this issue?
I found the reason why it was giving me the error. It was my mistake as I was trying to build a map with the help of Spring and was using bounded approach.
It was my previous code.
#Autowired
public void setServicesList(List<AbstractService<IDTO>> abstractServices) {
serviceMap = abstractServices.stream().collect(Collectors.toMap(AbstractService::getType, Function.identity()));
}
and I had to remove the bounded approach and now it's working.
public void setServicesList(List<AbstractService> abstractServices) {
serviceMap = abstractServices.stream().collect(Collectors.toMap(AbstractService::getType, Function.identity()));
}
In case you know what type of service holds the Map, you could do following:
// define your service map
private final Map<String, AbstractService<? extends IDTO>> serviceMap = Map.of(
"a", new AService(),
"b", new BService());
// cast `AbstractServise` from the map into required type:
public void executeRequest(final TestA dto){
((AbstractService<TestA>)serviceMap.get("a")).ex(dto);
}
public void executeRequest(final TestB dto){
((AbstractService<TestB>)serviceMap.get("b")).ex(dto);
}

Why this converter needs casting?

I need to implement an enum to enum converter in java: Enum_2 > Enum_1 and I'd like to do it in generic way.
So I defined an interface:
interface LabelAware<T extends Enum> {
String getLabel();
T getObject();
}
and Enum_1:
enum Enum_1 {
A, B;
String getValue() {
return "whatever";
}
}
and Enum_2 which implements LabelAware and needs to be converted to Enum_1:
enum Enum_2 implements LabelAware<Enum_1> {
C("c", Enum_1.A), D("d", Enum_1.B);
private final String label;
private final Enum_1 object;
Enum_2(String label, Enum_1 object) {
this.label = label;
this.object = object;
}
public String getLabel() {
return label;
}
public Enum_1 getObject() {
return object;
}
}
Finally, here's a generic converter (List.ofAll() comes from javaslang):
class Converter<S extends LabelAware, D extends Enum> {
private S[] values;
Converter(S[] values) {
this.values = values;
}
D map(String label) {
return (D) List.of(values)
.find(v -> v.getLabel().equals(label))
.map(LabelAware::getObject)
.getOrElseThrow(() -> new RuntimeException(""));
}
}
And a main method:
public class Main {
public static void main(String[] args) {
System.out.println(new Converter<Enum_2, Enum_1>(Enum_2.values()).map("c").getValue());
}
}
It all compiles and runs well, however I've no idea why I need to cast the result of Converter.map method to D, since I've declared D to extend Enum. Can it be done in a generic way without any warnings?
As a general rule, all warnings related to generics should be handled to have a safer code and avoid a warning chain (the visible warning is caused by a very far warning of the dependency chain).
But in your case, you have not a warning chain problem since externally, LabelAware is safe. LabelAware has only a internal warning (in its implementation) as Enum in extends Enum is raw-declared.
Here, a single missing generic declaration explains why the cast in Converter.map() method is not safe : Converter class declaration doesn't specify the generic for LabelAware.
You declare Converter class as :
class Converter<S extends LabelAware, D extends Enum> {
with its value field of type S:
private S[] values;
and its map() method as :
D map(String label) {
return (D) List.of(values)
.find(v -> v.getLabel().equals(label))
.map(LabelAware::getObject)
.getOrElseThrow(() -> new RuntimeException(""));
}
In map(), here .find(v -> v.getLabel().equals(label)), your retrieve so a S instance and you declared that S extends LabelAware.
Therefore finally, your retrieve an instance of LabelAware or extending it.
And LabelAware is typed with Enum generic :
interface LabelAware<T extends Enum> {
String getLabel();
T getObject();
}
So, in map() method when .map(LabelAware::getObject) is called, you retrieve a Enum type .
And an Enum type is not necessarily a D type, while the reverse is true.
Therefore, if you want to avoid the cast (and the related warning) in map(), you should specify that the generic type returned by getObject() is an instance of D by typing LabelAware with D generic :
class Converter<S extends LabelAware<D>, D extends Enum> {
You have been using raw types at several places (not only the one that yshavit pointed out in the comment). Particularly, the
class Converter<S extends LabelAware, D extends Enum>
has to be
class Converter<S extends LabelAware<D>, D extends Enum<D>>
The following should compile without warnings:
import javaslang.collection.List;
interface LabelAware<T extends Enum<?>>
{
String getLabel();
T getObject();
}
enum Enum_1
{
A, B;
String getValue()
{
return "whatever";
}
}
enum Enum_2 implements LabelAware<Enum_1>
{
C("c", Enum_1.A), D("d", Enum_1.B);
private final String label;
private final Enum_1 object;
Enum_2(String label, Enum_1 object)
{
this.label = label;
this.object = object;
}
public String getLabel()
{
return label;
}
public Enum_1 getObject()
{
return object;
}
}
class Converter<S extends LabelAware<D>, D extends Enum<D>>
{
private S[] values;
Converter(S[] values)
{
this.values = values;
}
D map(String label)
{
return List.of(values)
.find(v -> v.getLabel().equals(label))
.map(LabelAware::getObject)
.getOrElseThrow(() -> new RuntimeException(""));
}
}
(EDIT: This only tells you how to fix the problem, pragmatically. See the answer by davidxxx for details about what went wrong there, and don't forget to leave a +1 there :-))

Using reflection to create an instance based on getClass of generic class

I would like to know what is the right way to declare Class<...> stateBaseClass, when my goal is to create an instance by using Reflection: state = stateBaseClass.newInstance(); without using cast.
I put some comments in the code below:
abstract class StateBase{} // It is so complex to be serializable.
class State extends StateBase{}
class StateInstanceDescription <T extends StateBase> implements Serializable{
private static final long serialVersionUID = -828114417567233018L;
transient private T stateBase;
// what is the right way to declare the line below to void that cast?
private Class<? extends StateBase> stateBaseClass;
public StateInstanceDescription(T base){
this.stateBase = base;
stateBaseClass = base.getClass();
}
public T getBase() {
return stateBase;
}
public Class<? extends StateBase> getBaseClass() {
return stateBaseClass;
}
}
public class Main {
public static void main(String ... args) throws InstantiationException, IllegalAccessException{
State state = new State();
StateInstanceDescription<State> stateInstDesc = new StateInstanceDescription<>(state);
// ... At some point, I will recreate State after deserialize stateInstDesc.
// compiler-time error. Compiler is asking for casting it to (State).
// There is a way to avoid this cast?
state = stateInstDesc.getBaseClass().newInstance();
}
}
getBaseClass() returns a Class<? extends StateBase> so the result of newInstace can be any subclass of StateBase (depending on the class returned). The compiler cannot be sure that this result is specifically of type State or from another subclass.
To fix this, work with Class<T> and pass this as argument to the constructor of StateInstanceDescription:
class StateInstanceDescription <T extends StateBase> implements Serializable {
transient private T stateBase;
private Class<T> stateBaseClass;
public StateInstanceDescription(T base, Class<T> clazzBase) {
this.stateBase = base;
stateBaseClass = clazzBase;
}
public T getBase() {
return stateBase;
}
public Class<T> getBaseClass() {
return stateBaseClass;
}
}
Then in main:
State state = new State();
StateInstanceDescription<State> stateInstDesc = new StateInstanceDescription<>(state, State.class);
state = stateInstDesc.getBaseClass().newInstance();

Java - Instantiating generic typed class

I have a class for example
public class Example<T> {...}
I would like to instantiate class Example with a specific type class which I know. Pseudocode would look something like that
public Example<T> createTypedExample(Class exampleClass, Class typeClass) {
exampleClass.newInstance(typeClass); // made-up
}
So that this would give me same result
Example<String> ex = new Example<String>();
ex = createTypedExample(Example.class, String.class);
Is it possible in Java?
Since, the return type i.e. the class of the new instance is fixed; there's no need to pass it to the method. Instead, add a static factory method to your Example class as
public class Example<T> {
private T data;
static <T> Example<T> newTypedExample(Class<T> type) {
return new Example<T>();
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
Now, here's how you would create generic Example instances.
// String
Example<String> strTypedExample = Example.newTypedExample(String.class);
strTypedExample.setData("String Data");
System.out.println(strTypedExample.getData()); // String Data
// Integer
Example<Integer> intTypedExample = Example.newTypedExample(Integer.class);
intTypedExample.setData(123);
System.out.println(intTypedExample.getData()); // 123

Categories

Resources