Como posso fazer esse tipo de coisa funcionar? Posso verificar se, (obj instanceof List<?>)mas não se (obj instanceof List<MyType>). Existe uma maneira de fazer isso?
Isso não é possível porque o tipo de dados é apagado em tempo de compilação dos genéricos. A única maneira possível de fazer isso é escrever algum tipo de wrapper que contenha o tipo que a lista contém:
This answer is not safe, because even if the 0 element is a MyType, the other elements could be other types. For example, maybe the list was declared as ArrayList<Object>, then a MyType was added, and then a String was added.
Adam Gawne-Cain
@AdamGawne-Cain It's not safe, but unfortunatelly the only solution for people NOT knowing much about the list. For example - I have a local variable value which returns Object, and I need to check - if it's a list, if it is, check if the list type instanceof my interface. No wrapper or parametrized type is useful here.
As far as I know, that only works for fields, but +1 for mentioning it.
Tim Pote
6
This could be used if you want to check that object is instance of List<T>, which is not empty:
if(object instanceof List){
if(((List)object).size()>0 && (((List)object).get(0) instanceof MyObject)){
// The object is of List<MyObject> and is not empty. Do something with it.
}
}
If you are verifying if a reference of a List or Map value of Object is an instance of a Collection, just create an instance of required List and get its class...
Set<Object> setOfIntegers = new HashSet(Arrays.asList(2, 4, 5));
assetThat(setOfIntegers).instanceOf(new ArrayList<Integer>().getClass());
Set<Object> setOfStrings = new HashSet(Arrays.asList("my", "name", "is"));
assetThat(setOfStrings).instanceOf(new ArrayList<String>().getClass());
What is the point of your setOfIntegers and setOfStrings?
DanielM
@DanielM just updated the sample. It must be using those references! Thanks!
Marcello de Sales
1
If this can't be wrapped with generics (@Martijn's answer) it's better to pass it without casting to avoid redundant list iteration (checking the first element's type guarantees nothing).
We can cast each element in the piece of code where we iterate the list.
Object attVal = jsonMap.get("attName");
List<Object> ls = new ArrayList<>();
if (attVal instanceof List) {
ls.addAll((List) attVal);
} else {
ls.add(attVal);
}
// far, far away ;)for (Object item : ls) {
if (item instanceof String) {
System.out.println(item);
} else {
thrownew RuntimeException("Wrong class ("+item .getClass()+") of "+item );
}
}
The major concern here is that the collections don't keep the type in the definition. The types are only available in runtime. I came up with a function to test complex collections (it has one constraint though).
Check if the object is an instance of a generic collection. In order to represent a collection,
No classes, always false
One class, it is not a collection and returns the result of instanceof evaluation
To represent a List or Set, the type of the list comes next e.g. {List, Integer} for List<Integer>
To represent a Map, the key and value types come next e.g. {Map, String, Integer} for Map<String, Integer>
More complex use cases could be generated using the same rules. For example in order to represent List<Map<String, GenericRecord>>, it can be called as
Note that this implementation doesn't support nested types in the Map. Hence, the type of key and value should be a class and not a collection. But it shouldn't be hard to add it.
publicstaticbooleanisInstanceOfGenericCollection(Object object, Class<?>... classes){
if (classes.length == 0) returnfalse;
if (classes.length == 1) return classes[0].isInstance(object);
if (classes[0].equals(List.class))
return object instanceof List && ((List<?>) object).stream().allMatch(item -> isInstanceOfGenericCollection(item, Arrays.copyOfRange(classes, 1, classes.length)));
if (classes[0].equals(Set.class))
return object instanceof Set && ((Set<?>) object).stream().allMatch(item -> isInstanceOfGenericCollection(item, Arrays.copyOfRange(classes, 1, classes.length)));
if (classes[0].equals(Map.class))
return object instanceof Map &&
((Map<?, ?>) object).keySet().stream().allMatch(classes[classes.length - 2]::isInstance) &&
((Map<?, ?>) object).values().stream().allMatch(classes[classes.length - 1]::isInstance);
returnfalse;
}
Respostas:
Isso não é possível porque o tipo de dados é apagado em tempo de compilação dos genéricos. A única maneira possível de fazer isso é escrever algum tipo de wrapper que contenha o tipo que a lista contém:
public class GenericList <T> extends ArrayList<T> { private Class<T> genericType; public GenericList(Class<T> c) { this.genericType = c; } public Class<T> getGenericType() { return genericType; } }
fonte
if(!myList.isEmpty() && myList.get(0) instanceof MyType){ // MyType object }
fonte
value
which returnsObject
, and I need to check - if it's a list, if it is, check if the list type instanceof my interface. No wrapper or parametrized type is useful here.You probably need to use reflection to get the types of them to check. To get the type of the List: Get generic type of java.util.List
fonte
This could be used if you want to check that
object
is instance ofList<T>
, which is not empty:if(object instanceof List){ if(((List)object).size()>0 && (((List)object).get(0) instanceof MyObject)){ // The object is of List<MyObject> and is not empty. Do something with it. } }
fonte
if (list instanceof List && ((List) list).stream() .noneMatch((o -> !(o instanceof MyType)))) {}
fonte
If you are verifying if a reference of a List or Map value of Object is an instance of a Collection, just create an instance of required List and get its class...
Set<Object> setOfIntegers = new HashSet(Arrays.asList(2, 4, 5)); assetThat(setOfIntegers).instanceOf(new ArrayList<Integer>().getClass()); Set<Object> setOfStrings = new HashSet(Arrays.asList("my", "name", "is")); assetThat(setOfStrings).instanceOf(new ArrayList<String>().getClass());
fonte
setOfIntegers
andsetOfStrings
?If this can't be wrapped with generics (@Martijn's answer) it's better to pass it without casting to avoid redundant list iteration (checking the first element's type guarantees nothing). We can cast each element in the piece of code where we iterate the list.
Object attVal = jsonMap.get("attName"); List<Object> ls = new ArrayList<>(); if (attVal instanceof List) { ls.addAll((List) attVal); } else { ls.add(attVal); } // far, far away ;) for (Object item : ls) { if (item instanceof String) { System.out.println(item); } else { throw new RuntimeException("Wrong class ("+item .getClass()+") of "+item ); } }
fonte
You can use a fake factory to include many methods instead of using instanceof:
public class Message1 implements YourInterface { List<YourObject1> list; Message1(List<YourObject1> l) { list = l; } } public class Message2 implements YourInterface { List<YourObject2> list; Message2(List<YourObject2> l) { list = l; } } public class FactoryMessage { public static List<YourInterface> getMessage(List<YourObject1> list) { return (List<YourInterface>) new Message1(list); } public static List<YourInterface> getMessage(List<YourObject2> list) { return (List<YourInterface>) new Message2(list); } }
fonte
The major concern here is that the collections don't keep the type in the definition. The types are only available in runtime. I came up with a function to test complex collections (it has one constraint though).
Check if the object is an instance of a generic collection. In order to represent a collection,
false
instanceof
evaluationList
orSet
, the type of the list comes next e.g. {List, Integer} forList<Integer>
Map
, the key and value types come next e.g. {Map, String, Integer} forMap<String, Integer>
More complex use cases could be generated using the same rules. For example in order to represent
List<Map<String, GenericRecord>>
, it can be called asMap<String, Integer> map = new HashMap<>(); map.put("S1", 1); map.put("S2", 2); List<Map<String, Integer> obj = new ArrayList<>(); obj.add(map); isInstanceOfGenericCollection(obj, List.class, List.class, Map.class, String.class, GenericRecord.class);
Note that this implementation doesn't support nested types in the Map. Hence, the type of key and value should be a class and not a collection. But it shouldn't be hard to add it.
public static boolean isInstanceOfGenericCollection(Object object, Class<?>... classes) { if (classes.length == 0) return false; if (classes.length == 1) return classes[0].isInstance(object); if (classes[0].equals(List.class)) return object instanceof List && ((List<?>) object).stream().allMatch(item -> isInstanceOfGenericCollection(item, Arrays.copyOfRange(classes, 1, classes.length))); if (classes[0].equals(Set.class)) return object instanceof Set && ((Set<?>) object).stream().allMatch(item -> isInstanceOfGenericCollection(item, Arrays.copyOfRange(classes, 1, classes.length))); if (classes[0].equals(Map.class)) return object instanceof Map && ((Map<?, ?>) object).keySet().stream().allMatch(classes[classes.length - 2]::isInstance) && ((Map<?, ?>) object).values().stream().allMatch(classes[classes.length - 1]::isInstance); return false; }
fonte