I have an abstract class called Data, with a getInstance() method which should return instances of concrete subclasses of Data.
I want to pass a String to the getInstance method, and this string will define exactly what class will be returned.
So far I have:
public abstract class Data {
private Map<String, Data> instances;
public <T extends Data> T getInstance(Class<T> type, String dataName) {
return type.cast(instances.get(dataName));
}
}
Where the getInstance() method looks up the correct instance in the instances map. I think this should be OK provided the map is populated (by Spring), but the caller must match the Class<T> type parameter with the String dataName parameter.
Is there any way I can remove the Class<T> type parameter and not have generics warnings?
No, you will always get a warning if you try to cast into a generic type without knowing the runtime class of the type. And no, there is no way to get an instance of the generic class without providing it as an argument - generics are erased at runtime.
The only way to get no warnings without an explicit cast is to return either Object or Data (depending on you Map) and require the user to make the cast instead:
public Data getInstance(String dataName) {
return instances.get(dataName);
}
// OR
public Object getInstance(String dataName) {
return instances.get(dataName);
}
In my opinion it is best, to provide both methods for convenience: Data getInstance(String) and <T extends Data> T getInstance(Class<T>, String). This is essentially also what OSGI does in the BundleContext class, so one is able to get service references, with the difference, that it is not possible to get services with arbitrary ids (the id is always the name of the class):
ServiceReference<?> BundleContext#getServiceReference(java.lang.String clazz)
ServiceReference<S> BundleContext#getServiceReference(java.lang.Class<S> clazz)
You can skip the class parameter altogether.
public <T extends Data> T getInstance(String dataName) {
return (T)instances.get(dataName);
}
This will still generate a warning (which you can suppress). Both ways will throw a runtime exception if actual class in the map differs from expected type. In my example compile type inferrence will not work in certain cases and you will need to specify type when calling like this: Data.<SubClass>getInstance("name");
It is possible to have a solution which will return null if the subtype of data for the key is not correct.
public <T extends Data> T getInstance(Class<T> clazz, String dataName) {
Data d = instances.get(dataName);
if (d != null && clazz.isAssignableFrom(d.getClass())) {
return (T)d;
} else {
return null;
}
}
This code will return null if the value in the map is not of correct class T or its subclass.
Related
I have an interface:
public interface Message<T extends Message<T>> {
}
I have a class that implements this method as
public class FulfilmentReleasedDomModel implements Message<FulfilmentReleasedDomModel> {}
And I have this method:
private <T extends Message<T>> Mono<T> getDomainModel(ConsumerRecord<String, String> record) {}
When I try to return an object of type Mono<FulfilmentReleaseDomModel> from this method, the compiler throws an error and asks me to cast it to (Mono<T>).
My question is, since I have bounded T to extends Message, and FulfilmentReleasedDomModel implements Message<FulfilmentReleasedDomModel>, why do I need to cast it to Mono<T>?
A common misconception about generics is that the callee decides what the generic type is. No, the caller does.
You, as the writer of the method, don't get to decide what T is. By returning Mono<FulfilmentReleasedDomModel>, you are saying that T must be FulfilmentReleasedDomModel. But in actuality, the caller of your method will decide what T is. They could declare a type called Foo that implements Message<Foo> and say that T is Foo. You would need to return a Mono<Foo> instead.
It seems like your method should not be generic, because the callee is deciding what type to use:
private Mono<FulfilmentReleasedDomModel> getDomainModel(ConsumerRecord<String, String> record) {}
Alternatively, if you want to make this method more flexible, so that you can change its implementation to return something else without changing its return type, you can use generic wildcards:
private Mono<? extends Message> getDomainModel(ConsumerRecord<String, String> record) {}
I'm not that familiar with java Generics (using IntelliJ).
What I want is adding generic values to collections.
Two question for the code below.
I read https://docs.oracle.com/javase/tutorial/java/generics/methods.html
and https://docs.oracle.com/javase/tutorial/extra/generics/methods.html
but don't know why the code below has an error.
Q1) I have error message in map.put(T, T); in add method such that red ripple under Ts: Expression Expected, introduce local variable
Q2) Wondering in this case, should I declare class as public class Test<T> or can I declare public class Test?
public class Test<T> {
Map<T, T> map;
public Test() {
map = new HashMap<T, T>();
}
public <T> void add(T value) throws Exception {
map.put(value, value); // Q1) red ripple under value: Expression Expected, introduce local variable
}
}
There are two problems in here.
The generic method parameter T hides the class one T. They are different types and you have no ways to refer to that T (unless you rename one of them).
The method map.put expects values of T type, not this type itself (it is not an instance of Class anyway).
The solution:
public void add(T value) throws Exception {
map.put(value, value);
}
Wondering in this case, should I declare class as public class Test<T> or can I declare public class Test?
You don't necessarily need to declare class generic types if you want few generic methods - make your methods with their own generic types. Note that there won't be any resemblance between them.
But, in your case, a generic type T is mandatory since you declared a generic field Map<T, T> map.
I have a generic method and would like to retrieve objects using the generic type. This is my method:
public static <T extends RealmObject & IndentifierModel> void storeNewData() {
...
T item = realm.where(Class<T>) // Not compiling (Expression not expected)
.equalTo("ID", data.get(i).getID())
.findFirst();
}
The above isn't working for realm.where(Class<T>). How do I pass in my generic type to Realm?
You have to supply the generic parameter like so:
public static <T extends RealmObject & IndentifierModel> void storeNewData(Class<T> clazz) {
T item = realm.where(clazz)
.equalTo("ID", 123)
.findFirst();
}
Class<T> is not valid, since that's like saying realm.where(Class<List<String>>) or realm.where(Class<String>). What you need is an actual Class<T> instance. But you cannot use T.class either since T is not available at runtime due to type-erasure. At runtime, the method basically needs a Class<T> instance to work properly. Since you cannot get that from T, you will have to explicitly supply an argument of type Class<T>.
I am sorry about putting the error directly as the title, but I couldn't find any better title.
I have an interface defined as following to be used as a blueprint for all my validator classes:
public interface Validator<T> {
public boolean validate(T item);
}
And then I have some classes that would implement it, lets say one of them is this:
public class EmptyStringValidator implements Validator<String> {
private final String _errorMessage;
public EmptyStringValidator() {
this("String cannot be empty.");
}
public EmptyStringValidator(String message) {
this._errorMessage = message;
}
#Override
public String getMessage() {
return this._errorMessage;
}
#Override
public boolean validate(String item) {
return gsi.application.core.Validation.isEmptyString(item);
}
}
I would like to put it all in an array and call it all in one loop. So this is the code I am using:
public List<Validator<? extends Object>> validators;
public FormItem<T> addValidator(Validator<? extends Object> validator) {
this.validators.add(validator);
return this;
}
public boolean validate() {
for (Validator<? extends Object> validator : this.validators)
if (!validator.validate(this.getInputValue())) {
this._errorMessage = validator.getMessage();
return false;
}
return true;
}
However, that code is giving an error at the validate() function, specifically at this part:
validator.validate(this.getInputValue())
It gives me the error that I have mentioned
The method validate(capture#2-of ? extends Object) in the type Validator<capture#2-of ? extends Object> is not applicable for the arguments (String)
which to my understanding doesn't makes sense. To my understanding <? extends Object> should accept anything that derives from the Object class, right?
Could anyone point out what am I doing wrong or point me at the right direction?
Thanks.
As an aside, ? extends Object is no different from saying ?. That isn't the root of your problem, however.
The issue is that validators is a List<Validator<? extends Object>>. In other words, each element can be any kind of Validator<T>, not necessarily a Validator<String>. So you can put a Validator<String> into it, but when you pull an element out you don't know what kind of Validator it is, and so you don't know if it is compatible with the type returned by this.getInputValue().
The simplest fix would be to specify a concrete type (eg: String) for the type parameter. A more complicated fix would be to use a type variable in place of ? extends Object, and have getInputValue()'s type signature use that same type variable. You need to constrain the types such that getInputValue()'s return type is assignable to the parameter of validate().
An even better type for collected validators generally is Validator<? super T>, where T is the input type, in this case String.
This way addValidator(Validator<? super String> validator) accepts Validator<String> but also Validator<CharSequence> and Validator<Object>.
For example:
class LongerThan10 implements Validator<CharSequence> {
#Override
public boolean validate(CharSequence item) {
return item.length() > 10;
}
}
formItem.addValidator(str -> !str.isBlank())
formItem.addValidator(new LongerThan10());
In this specific case it makes not much sense, but it is a good idea to accept validators that work with super types generally.
I am new to generics.
Having a Map like
private static Map<String, Object> map;
and a method like
public <T> T getObject(final Class<T> myClass) {
return (T)map.get(myClass);
}
How to change the map declaration in order to not have to do the cast when returning from the method ?
You would need to make a generic class, not a generic method:
public class MyClass<T> {
private Map<String, T> map;
public T getObject(final String key) {
return map.get(key);
}
}
Also, I changed the parameter from a Class to a String. It doesn't make sense to pass a Class if map.get() expects a String.
Edit: I didn't notice that map was static. If you can change it to non-static without it breaking other parts of your program, this could work. If you can't, then you cannot avoid a cast.
You can't avoid the cast operation as the get() method returns Object
see here for more info
If you're willing to drop the static modifier of your map, than you can do like so:
public class MyClass<T> {
private Map<String, T> map;
public T getObject(final Class<T> myClass) {
return map.get(myClass);
}
}
Otherwise:
It is a compile-time error to refer to a type parameter of a generic
class C anywhere in:
the declaration of a static member of C
(excerpt from the JLS), which prevents you from using parameterized class to achieve the above.
What you were trying to do, however, is to refer a parameterized method's type-parameter from another member (which happen to also be static), which also unreachable.