Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic methods returning dynamic object types

Possibly a question which has been asked before, but as usual the second you mention the word generic you get a thousand answers explaining type erasure. I went through that phase long ago and know now a lot about generics and their use, but this situation is a slightly more subtle one.

I have a container representing a cell of data in an spreadsheet, which actually stores the data in two formats: as a string for display, but also in another format, dependent on the data (stored as object). The cell also holds a transformer which converts between the type, and also does validity checks for type (e.g. an IntegerTransformer checks if the string is a valid integer, and if it is returns an Integer to store and vice versa).

The cell itself is not typed as I want to be able to change the format (e.g. change the secondary format to float instead of integer, or to raw string) without having to rebuild the cell object with a new type. a previous attempt did use generic types but unable to change the type once defined the coding got very bulky with a lot of reflection.

The question is: how do I get the data out of my Cell in a typed way? I experimented and found that using a generic type could be done with a method even though no constraint was defined

public class Cell {     private String stringVal;     private Object valVal;     private Transformer<?> trans;     private Class<?> valClass;      public String getStringVal(){         return stringVal;     }      public boolean setStringVal(){         //this not only set the value, but checks it with the transformer that it meets constraints and updates valVal too     }      public <T> T getValVal(){         return (T) valVal;         //This works, but I don't understand why     } } 

The bit that puts me off is: that is ? it can't be casting anything, there is no input of type T which constrains it to match anything, actually it doesn't really say anything anywhere. Having a return type of Object does nothing but give casting complications everywhere.

In my test I set a Double value, it stored the Double (as an object), and when i did Double testdou = testCell.getValVal(); it instantly worked, without even an unchecked cast warning. however, when i did String teststr = testCell.getValVal() I got a ClassCastException. Unsurprising really.

There are two views I see on this:

One: using an undefined Cast to seems to be nothing more than a bodge way to put the cast inside the method rather than outside after it returns. It is very neat from a user point of view, but the user of the method has to worry about using the right calls: all this is doing is hiding complex warnings and checks until runtime, but seems to work.

The second view is: I don't like this code: it isn't clean, it isn't the sort of code quality I normaly pride myself in writing. Code should be correct, not just working. Errors should be caught and handled, and anticipated, interfaces should be foolproof even if the only expecter user is myself, and I always prefer a flexible generic and reusable technique to an awkward one off. The problem is: is there any normal way to do this? Is this a sneaky way to achieve the typeless, all accepting ArrayList which returns whatever you want without casting? or is there something I'm missing here. Something tells me I shouldn't trust this code!

perhaps more of a philosophical question than I intended but I guess that's what I'm asking.

edit: further testing.

I tried the following two interesting snippets:

public <T> T getTypedElem() {     T output = (T) this.typedElem;     System.out.println(output.getClass());     return output; }  public <T> T getTypedElem() {     T output = null;     try {         output = (T) this.typedElem;         System.out.println(output.getClass());     } catch (ClassCastException e) {         System.out.println("class cast caught");         return null;     }     return output; } 

When assigning a double to typedElem and trying to put it into a String I get an exception NOT on the cast to , but on the return, and the second snippet does not protect. The output from the getClass is java.lang.Double, suggesting that is being dynamically inferred from typedElem, but that compiler level type checks are just forced out of the way.

As a note for the debate: there is also a function for getting the valClass, meaning it's possible to do an assignability check at runtime.

Edit2: result

After thinking about the options I've gone with two solutions: one the lightweight solution, but annotated the function as @depreciated, and second the solution where you pass it the class you want to try to cast it as. this way it's a choice depending on the situation.

like image 723
K.Barad Avatar asked Sep 24 '12 17:09

K.Barad


People also ask

Can dynamic type be used for generic?

Not really. You need to use reflection, basically. Generics are really aimed at static typing rather than types only known at execution time.

Can a generic can take any object data type?

Generics in Java is similar to templates in C++. The idea is to allow type (Integer, String, … etc and user defined types) to be a parameter to methods, classes and interfaces. For example, classes like HashSet, ArrayList, HashMap, etc use generics very well. We can use them for any type.

What are generic methods in oops?

Generic methods are methods that introduce their own type parameters. This is similar to declaring a generic type, but the type parameter's scope is limited to the method where it is declared. Static and non-static generic methods are allowed, as well as generic class constructors.

What are generic objects?

Generic objects are structures for storing and interacting with data in an app. They are the primary type of entity through which the engine provides access to the components of an app.


1 Answers

You could try type tokens:

public <T> T getValue(Class<T> cls) {     if (valVal == null) return null;     else {         if (cls.isInstance(valVal)) return cls.cast(valVal);         return null;     } } 

Note, that this does not do any conversion (i.e., you cannot use this method to extract a Double, if valVal is an instance of Float or Integer).

You should get, btw., a compiler warning about your definition of getValVal. This is, because the cast cannot be checked at run-time (Java generics work by "erasure", which essentially means, that the generic type parameters are forgotten after compilation), so the generated code is more like:

public Object getValVal() {     return valVal; } 
like image 69
Dirk Avatar answered Sep 23 '22 10:09

Dirk