Java Generic type flexibility - java

So, lets say I have an enum, "Data".
public enum Data {
FIRSTNAME(String.class, "John");
private final Class<?> defaultClass;
private final Object defaultData;
Data(Class<?> clazz, Object data) {
this.defaultClass = clazz;
this.defaultData = data;
}
public Class<?> getDataClass() {
return this.defaultClass;
}
}
Would it be possible to create a method that gets its return type based on the passed Data enum's getDataClass() response? Ie like this:
//This code obviously won't work, it's just another way of showing this.
public [data.getDataClass()] getData(Data data) {
//Return the data.
}

Related

Java Generics: getting return type from parameter

This is an odd question. I don't think there's a solution, but I thought I'd ask anyway.
Say I have an enum:
public enum Key {
RED(String.class),
GREEN(Integer.class),
BLUE(Short.class);
private Class<?> expectedType;
Key(Class<?> expectedType) { this.expectedType = expectedType; }
public Class<?> getExpectedType() { return expectedType; }
}
I want to use the 'expectedType' field from the Key enum as the return type of a method. See:
public class Cache {
private static Map<Key, Object> cache = new HashMap<>();
public void put(Key key, Object value) {
// Easy to validate that 'value' is of type key.getExpectedType()...
}
public <T> T get(Key key) {
Object value = cache.get(key);
// TODO need to define <T> as key.getExpectedType(). How?
}
}
See that TODO? I'd like for get() to define the return type of the 'expectedType' defined by the key parameter. E.g. if the key parameter were RED, the get() method would return a String and you could write:
String s = cache.get(Key.RED);
Is there a way to do that?
I'm thinking there isn't, but I'd love to hear of a clever solution.
Enums don't support generics, but you could use a regular class as a generic pseudo-enum:
public class Key<T> {
public static final Key<String> RED = new Key<>(String.class);
public static final Key<Integer> GREEN = new Key<>(Integer.class);
public static final Key<Short> BLUE = new Key<>(Short.class);
private final Class<T> expectedType;
private Key(Class<T> expectedType) { this.expectedType = expectedType; }
public Class<T> getExpectedType() { return expectedType; }
}
public class Cache {
private Map<Key<?>, Object> cache = new HashMap<>();
public <T> void put(Key<T> key, T value) {
cache.put(key, key.getExpectedType().cast(value));
}
public <T> T get(Key<T> key) {
return key.getExpectedType().cast(cache.get(key));
}
}
shmosel's answer is almost certainly sufficient for what you need; however, it has the slight limitation that you can't store/retrieve a generic type, because you can't get a class literal for a generic type.
Instead, you can use something like Guava's TypeCapture:
abstract class GenericKey<T> {
Type getExpectedType() {
return ((ParameterizedType) getClass().getGenericSuperclass())
.getActualTypeArguments()[0];
}
}
which is a bit of reflective grossness that you shouldn't spend too much time looking at.
Notice that it's abstract, so you have to instantiate like:
new GenericKey<Integer>() {}
This is creating an anonymous subclass of GenericKey, which is part of the magic that makes it work with generic types.
Then, it's basically the same:
public class Cache {
private Map<GenericKey<?>, Object> cache = new HashMap<>();
public <T> void put(GenericKey<T> key, T value) {
cache.put(key.getExpectedType(), value);
}
public <T> T get(GenericKey<T> key) {
return (T) cache.get(key.getExpectedType());
}
}
Now you could have a GenericKey<List<Integer>>, using new new GenericKey<List<Integer>() {}, if you should so desire.
The downside of this approach is that you lose the ability to do checking on the value on the way in/out of the cache, so you could get heap pollution if you are careless with raw types.

Generic Class for a GSON LinkedHashMap

I have been working on this solution for months and I have come to the conclusion that there is no clean way to achieve what I am trying to achieve. I feel as though my education in polymorphism is failing me, so I've come to StackOverflow to get a second opinion. Sorry if this seems long and convoluted. That's been my brain for the past couple of months and at this point I'm out of ideas. I'm hoping somebody can take a look and see that I could've avoided all this mess by doing it some other way.
What I am trying to achieve is two generic classes: One that can represent any "saveable" object, and one that can represent a list of saveable objects (or what I call a "store"). A saveable object can save itself using GSON, and a store can also save itself using GSON to a JSON file. The difference being that saveable objects are generically representing any GSON object that can be saved, whereas stores are extending from saveables to become a saveable hash map of objects via IDs.
An example output I am looking for is as so:
Imagine I have an object with a uuid string field and a name string field. I want to be able to create a Store, which is a LinkedHashMap, of these objects, but also extend a Saveable to allow the objects to be saved as so:
test.json
{"dbf39199209e466ebed0061a3491ed9e":{"uuid":"dbf39199209e466ebed0061a3491ed9e","name":"Example Name"}}
I would also like to be able to load this JSON back into the objects via the Store's load method.
An example code usage would be like so:
Store<User> users = new Store<>();
users.load();
users.add(new User("dbf39199209e466ebed0061a3491ed9e", "Example Name"));
users.save();
My Attempts
Saveables
What I expect a "saveable" object to be able to do is as follows: provide a non-argumented method for saving and provide a non-argumented method for loading. A saveable object represents any object that can be saved via GSON. It contains two fields: a Gson gson object and a Path location. I provide those in the constructor of my saveable. I then want to provide two methods: a Saveable#save() method and a Saveable#load() method (or a static Saveable#load() method, I am indifferent). The way you use a Saveable object is by extending it (so it is abstract) to another object representing something, say, TestSaveable, and then the usage is as so:
TestSaveable saveable = new TestSaveable(8);
saveable.save(); // Saves data
saveable.setData(4);
saveable = saveable.load(); // Loads old data
I also would like a saveable object to be able to handle a generic, such as an integer (think of the last example but with an integer generic). This would allow me to execute my next plan for Stores.
My attempt at an implementation was the following:
public abstract class Saveable {
private transient Gson gson;
private transient Path location;
public Saveable(Gson gson, Path location) {
this.gson = gson;
this.location = location;
}
#SuppressWarnings("unchecked")
public <T extends Saveable> T save() throws IOException {
if (location.getParent() != null) {
Files.createDirectories(location.getParent());
}
Files.write(location, gson.toJson(this).getBytes(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, LinkOption.NOFOLLOW_LINKS);
return (T) this;
}
protected <T extends Saveable> T load(Class<T> clazz, #NotNull Class<?>... generics) throws IOException {
if (!Files.exists(location)) {
return this.save();
} else {
InstanceCreator<Saveable> creator = type -> this;
Type type = TypeToken.getParameterized(clazz, generics).getType();
Gson newGson = gson.newBuilder().registerTypeAdapter(type, creator).create();
return newGson.fromJson(Files.newBufferedReader(location), type);
}
}
}
Unfortunately, this attempt failed in my goal, because upon making my TestSaveable class users still had to pass the generic through for loading:
public class TestSaveable<T> extends Saveable {
public boolean testBool = false;
public T value;
public TestSaveable(T value) {
super(new Gson(), Path.of("test.json"));
this.value = value;
}
public final TestSaveable<T> load(Class<T> generic) throws IOException {
return super.load(TestSaveable.class, generic);
}
}
However, through this I did get a fairly clean implementation with the exception of little to no type checking at all and constantly having to add supressions for it:
public class Test {
public static void main(String[] args) {
try {
TestSaveable<Integer> storeB4 = new TestSaveable<>(5).save();
storeB4.value = 10;
TestSaveable<Integer> store = storeB4.load(Integer.class);
System.out.println("STORE: " + store);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Stores
Stores are an extension of saveables. A store is a LinkedHashMap which will quickly and easily save all of the objects in it as a map in GSON. Unfortunately, I'm not even sure where to start on this. I cannot extend two objects (the two being a LinkedHashMap<String, T> and a Saveable), but I also cannot use interfaces for the Saveable object.
I previously tried the following using the IStorable and ISaveable classes as an alternative to the abstract Saveable class I've shown you above, but this resulted in another very ugly and non-robust solution to my issue.
Saveable.java
public class Saveable {
// Suppress default constructor
private Saveable() {}
// Save a class to the specified location using the specified gson
public static <T extends ISaveable> T save(T instance) throws IOException {
Files.createDirectories(instance.getLocation().getParent());
Files.write(instance.getLocation(), instance.getGson().toJson(instance).getBytes(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, LinkOption.NOFOLLOW_LINKS);
return instance;
}
// Load a file from the specified location using the specified gson and cast it to the specified class using the specified generic
public static <T extends ISaveable> ISaveable load(Path location, Gson gson, Class<T> clazz, Class<?> genericClazz) throws IOException {
if (!Files.exists(location)) {
return null;
} else {
TypeToken<?> type = genericClazz == null ? TypeToken.get(clazz) : TypeToken.getParameterized(clazz, genericClazz);
ISaveable saveable = gson.fromJson(Files.newBufferedReader(location), type.getType());
saveable.setGson(gson);
saveable.setLocation(location);
return saveable;
}
}
}
ISaveable.java
public interface ISaveable {
// Gson
Gson getGson();
void setGson(Gson gson);
// Location
Path getLocation();
void setLocation(Path location);
}
IStorable.java
public interface IStoreable {
String getUuid();
}
Store.java
public class Store<T extends IStoreable> extends LinkedHashMap<String, T> implements ISaveable {
private transient Path location;
private transient Gson gson;
public Store(Path location, Gson gson) {
this.location = location;
this.gson = gson;
}
public Store() {
this.location = null;
this.gson = null;
}
public Store<T> put(T value) {
this.put(value.getUuid(), value);
return this;
}
public Store<T> remove(T value) {
this.remove(value.getUuid());
return this;
}
public Store<T> save() throws IOException {
return Saveable.save(this);
}
#SuppressWarnings("unchecked")
public static <T extends IStoreable> Store<T> load(Path location, Gson gson, Class<T> genericClazz) throws IOException {
ISaveable saveable = Saveable.load(location, gson, Store.class, genericClazz);
if (saveable == null) {
return new Store<T>(location, gson).save();
} else {
return (Store<T>) saveable;
}
}
}
This solution achieved me almost the result I was looking for, but fell short quickly on the loading process as well as just not being a robust solution, excluding the hundreds of Java practices I'm sure to have ruined at this point:
Store<ExampleStoreable> store = Store.load(Paths.get("storetest.json"), new Gson(), ExampleStoreable.class);
store.put(new ExampleStoreable("Example Name"));
store.save();
And before I get any comments saying I shouldn't be posting this on StackOverflow: if not here, where else? Please help point me in the right direction, I'd love to not be left in the dark.
Thanks if anyone is able to help and if not I understand. This isn't the easiest question by any means.
I was extremely close to the correct solution, but my logic just wasn't lining up.
The fixed load method is as follows:
default <T extends ISaveable> T load() throws IOException {
if (!Files.exists(getLocation())) {
return save();
} else {
InstanceCreator<?> creator = type -> (T) this;
Gson newGson = getGson().newBuilder().registerTypeAdapter(getType(), creator).create();
return newGson.fromJson(Files.newBufferedReader(getLocation()), getType());
}
}
Instead of attempting to prevent type erasure, and instead of passing the class every time we call the method, we just... pass it in the constructor. It was really that simple. I don't care about sending the type through the constructor, as long as .load() and .save() do not result in hundreds of lines of repetitive code.
I can't believe I was this close to the solution the whole time. It's incredible to me how simple this was. Guess that's the life of programming, right?
Here is the full class, which I determined was better as an interface called ISaveable.java:
public interface ISaveable {
Type getType();
Gson getGson();
Path getLocation();
/**
* Saves this object.
*
* #param <T> The extended object to cast to.
* #return The object after having been saved.
* #throws IOException Thrown if there was an exception while trying to save.
*/
#SuppressWarnings("unchecked")
default <T extends ISaveable> T save() throws IOException {
Path location = getLocation().toAbsolutePath();
if (location.getParent() != null) {
Files.createDirectories(location.getParent());
}
Files.write(getLocation(), getGson().toJson(this).getBytes(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, LinkOption.NOFOLLOW_LINKS);
return (T) this;
}
/**
* Loads this object.
*
* #param <T> The extended object to cast to.
* #return The object after loading the new values.
* #throws IOException Thrown if there was an exception while trying to load.
*/
#SuppressWarnings("unchecked")
default <T extends ISaveable> T load() throws IOException {
if (!Files.exists(getLocation())) {
return save();
} else {
InstanceCreator<?> creator = type -> (T) this;
Gson newGson = getGson().newBuilder().registerTypeAdapter(getType(), creator).create();
return newGson.fromJson(Files.newBufferedReader(getLocation()), getType());
}
}
}
An example implementation:
public class ExampleSaveable implements ISaveable {
private boolean testBoolean = false;
private String myString;
public ExampleSaveable(String myString) {
this.myString = myString;
}
#Override
public Gson getGson() {
return new Gson();
}
#Override
public Type getType() {
return TypeToken.get(ExampleSaveable.class).getType();
}
#Override
public Path getLocation() {
return Path.of("test.json");
}
}
And an example usage is like so:
ExampleSaveable saveable = new ExampleSaveable("My Data!").load();
saveable.myString = "This is a replacement string!";
saveable.save();
On the first run, the output is "My Data!", on the second, the output is "This is a replacement string!"
The corresponding output JSON was:
{"testBoolean":false,"myString":"This is a replacement string!"}
This allowed me to subsequently extend the class to create my Store.
IStorable.java
public interface IStorable {
String getUuid();
}
Store.java
public class Store<T extends IStorable> extends LinkedHashMap<String, T> implements ISaveable {
// GSON & Location
private transient Gson gson;
private transient Path location;
private transient Type type;
/**
* Constructs a new store.
*
* #param gson The gson to use for saving and loading.
* #param location The location of the JSON file.
* #param generic The generic that this instance of this class is using (due to type erasure).
*/
public Store(Gson gson, Path location, Class<T> generic) {
this.gson = gson;
this.location = location;
this.type = TypeToken.getParameterized(Store.class, generic).getType();
}
// Putting
public Store<T> put(T value) {
this.put(value.getUuid(), value);
return this;
}
public Store<T> putAll(T... values) {
for (T value : values) {
this.put(value.getUuid(), value);
}
return this;
}
// Replacing
public Store<T> replace(T value) {
this.replace(value.getUuid(), value);
return this;
}
// Removing
public Store<T> remove(T value) {
this.remove(value.getUuid());
return this;
}
// Implement ISaveable
#Override
public Gson getGson() {
return gson;
}
#Override
public Path getLocation() {
return location;
}
#Override
public Type getType() {
return type;
}
// Setters
public void setLocation(Path location) {
this.location = location;
}
}

Java Map with variable generics as values

So here's a slightly tricky question (for me).
I have a generic object. Call it MyObject. This object has a method which returns something of the type T:
public class MyObject<T>
{
private T _t;
public MyObject(T t)
{
_t = t;
}
//...
public T get()
{
return _t;
}
}
(Obviously my "MyObject" does a bit more but that's the gist).
Now, I want to have a map of this type:
Map<String, MyObject<?>> m = new HashMap<>();
I want to be able to fetch maps using some predefined string name, and these maps can be of any MyObject. For example, I could call:
m.put("map_1", new MyObject<String>("String"));
m.put("map_2", new MyObject<Integer>(new Integer(3));
m.put("map_3", new MyObject<Long>(new Long(5));
etc.
But - and here's the tricky part - I want the map to "remember" the parameterized type of MyObject when I fetch some value from the map. Using
m.get("map_1");
would return a
MyObject<Object>
type, since the map was defined as containing
MyObject<?>
values. Thus:
m.get("map_1").get() // <-- This is an Object, not a String!
What modification (if any) is possible, in order to be able to get the correct - full - information regarding the MyObject fetched object, such that invoking the last line (m.get("map_1")) would return a
MyObject<String>
Thanks :)
Amir.
Typesafe Heterogeneous Containers from Joshua Bloch's Effective Java might work here. Basically you add a Class object to represent the type.
public class MyObject<T>
{
private T _t;
private Class<T> type;
public MyObject( Class<T> type, T t)
{
_t = t;
this.type = type;
}
//...
public T get()
{
return _t;
}
public Class<T> getType() { return type; }
}
Then you could do something like this:
public <T> T get( Map<String, MyObject<?>> map, String key, Class<T> type ) {
return type.cast( m.get( key ).get() );
}
Which is safe and will compile, but will throw a runtime error if you get the type wrong.
(Note I didn't actually compile that, so I might have syntax errors floating around. But most folks don't know how to use Class to cast objects.)
You can get the class.
Class c = m.get("map_1").get().getClass();
if (String.class.equals(c)) {
System.out.println("its a String");
}
Here is a full test.
public class GenericsTest {
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
Map<String, MyObject<?>> map = new HashMap<>();
MyObject<String> obj = new MyObject<>("hello");
map.put("greeting", obj);
Class c = map.get("greeting").get().getClass();
if (String.class.equals(c)) {
System.out.println("its a String");
}
}
static class MyObject<T> {
T t;
public MyObject(T t) {
this.t = t;
}
T get() {
return t;
}
}
}
The type system only knows about types, not objects, and therefore can not distinguish "key1" from "key2", because both are of type String.
If keys have different types, the easiest way is to encapsulate a weakly typed map, and use reflective casts to prove to the compiler the types are correct:
class Favorites {
private Map<Class<?>,?> map = new HashMap<>();
<V> V get(Class<V> clazz) {
return clazz.cast(map.get(clazz));
}
<V> void put(Class<V> clazz, V value) {
map.put(clazz, value);
}
}
Favorites favs = new Favorites();
favs.put(String.class, "hello");
favs.put(Integer.class, 42);
favs.get(String.class).charAt(1);

How to assign assign type in a generic class with a param java.lang.reflect.Type

If I have a class with a generic like this:
public class ResponseSimple<T> {
private Map<String, Collection<String>> headers;
private int status;
private T body;
}
Then,in other class I have a method which I need to use an instance of this class, but the method passes by param a java.lang.reflect.Type and it's overrided so I can't change the any of the method (name, signature..):
public class ResponseEncoder extends GsonDecoder {
public ResponseEncoder() {
super();
}
#Override
public Object decode(Response response, Type type) throws IOException
{
//How assign type T using type param??
//¿ResponseSimple<T> responseSimple = new ResponseSimple();?
return null;
}
}
How could I assign the generic type T using the param type (java.lang.reflect.Type)?
I would suggest something like this:
#Override
public <T> T decode(Response response, Class<T> type) throws IOException
{
//How assign type T using type param??
ResponseSimple<T> response = new ResponseSimple<T>();
return response;
}
Then use decode as follows:
.decode(response, NameOfClass.class)
Edit:
If you need to extend your class you could use a static helper function:
public static <T> ResponseSimple<T> createResponse(Class<T> clazz)
{
return new ResponseSimple<>();
}
And use it like this:
public class ResponseEncoder extends GsonDecoder {
public ResponseEncoder() {
super();
}
#Override
public Object decode(Response response, Type type) throws IOException
{
Class<?> clazz = (Class<?>) type;
ResponseSimple<?> response = createResonse(clazz);
return null;
}
}
I hope I understood your question correctly.
To create a new instance of your generic class you need to infer the correct type arguments like this if you want your ResponseSimple<T> to contain java.lang.reflect.Type:
ResponseSimple<Type> response = new ResponseSimple<>();
So, inbetween the <> you need to add the name of the class you want to use.
Also have a look at this: https://docs.oracle.com/javase/tutorial/java/generics/types.html
//EDIT:
As you said you want to infer the type arguments dynamically, what you did works fine for me. The only thing is that you forgot the diamond operator:
#Override
public Object decode(Response response, T type) throws IOException
{
ResponseSimple<T> response = new ResponseSimple<>(); //<> in the second part
return null;
}

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