I am trying to write a class that has a Map as a field. The field is as follows:
Map<String, Collection<String>> courses;
In the constructor, I have to have the field in the form:
Map<String, Set<String>;
without changing the field at all.
I am getting an error when I try to initialize the field with the set. Can someone tell me why or what to do without altering the original field?
Despite Set<String> is actually a subtype of Collection<String>, a Map<String, Set<String>> is not a subtype of Map<String, Collection<String>>. In fact, they are completely different types and you can't assign one to the other.
Luckily, the Map interface defines the putAll method, which has the following signature:
void putAll(Map<? extends K,? extends V> m)
This means that the putAll method accepts a map whose keys and values might be of types that are subtypes of its own key and value types, respectively.
So, in your example, you could do as follows:
public class YourClass {
private final Map<String, Collection<String>> courses = new HashMap<>();
public YourClass(Map<String, Set<String>> courses) {
this.courses.putAll(courses);
}
}
You only have to make sure that the courses attribute has been instantiated before invoking putAll on it.
I'm not sure what actual question is about, but...
code below is working because of Type erasure at Runtime
public class SimpleTest {
protected Map<String, ? extends Collection<String>> courses;
public SimpleTest(Map<String,Set<String>> setsMap)
{
courses = setsMap;
}
public static void main(String... args) {
Map<String, ? extends Collection<String>> setMap = new HashMap<String, Set<String>>();
SimpleTest stInstance = new SimpleTest((Map<String, Set<String>>) setMap);
String str1 = "Hi";
String str2 = "Hello";
Set<String> stringSet = new HashSet<>();
stringSet.add(str1);
List<String> stringList = new ArrayList<>();
stringList.add(str2);
((Map<String, Collection<String>>)setMap).put("set1", stringSet);
((Map<String, Collection<String>>)setMap).put("list1", stringList);
System.out.println("set1 class: " + stInstance.courses.get("set1").getClass().getName());
System.out.println("list1 class: " + stInstance.courses.get("list1").getClass().getName());
System.out.println("map content: " + stInstance.courses);
}
}
output is:
set1 class:java.util.HashSet
list1 class:java.util.ArrayList
map content:{list1=[Hello], set1=[Hi]}
PS. I do not recommend to use such "technique", at all.
But as experiment it is interesting and funny :-)
Related
I've been working on a class where i encountered Maps and List with very huge Type values.
for e.g.
Map<String, Map<String, Map<String, ...>>> map = new HashMap<>();
and
List<List<Map<String,...>>> list = new ArrayList<>();
looping over such datatypes makes boilerplate code looks very ugly!
for(Map<String, Map<String, ...>> e : map.entrySet()){
//do something...
for(Map<String, ..> e1 : e.entrySet()){
..more such loops
}
}
I came up with a solution to reduce the size of the template using '?' keyword
below is my solution
Map<String, Map<String, Map<String, Long>>> map = ....
for(Entry<String, ?> e : map.entrySet()){
Map<String , ?> mapLevel1 = (Map<String, ?>)e.getValue();
for(Entry<String, ?> e1 : mapLevel1.entrySet()){
Map<String, ?> mapLevel2Map = (Map<String, ?>) e1.getValue();
for(Entry<String, ?> e2 : mapLevel2Map.entrySet()){
Long data = (Long)e2.getValue();
.....
}
}
}
could there be any potential problems in going with this approach ?
Thanks in Anticipation !
? means "some type, but I don't know which" (approximately). Since you do know what the type is, it isn't really suitable.
could there be any potential problems in going with this approach
The casts you need everywhere after getValue (and don't need without ?) are a very significant problem and I wouldn't even call it "potential". If any part of the type changes, good luck finding which casts you need to change and to what.
EDIT: since Java 10, you can just do
for(var e : map.entrySet()){
var mapLevel1 = e.getValue();
for(var e1 : mapLevel1.entrySet()){
var mapLevel2Map = e1.getValue();
for(var e2 : mapLevel2Map.entrySet()){
// or var again
Long data = e2.getValue();
.....
}
}
}
and let the compiler deduce types and check everything makes sense.
As others have pointed out, using ? is worse. Don’t do it.
You should enable all compiler warnings in your IDE (or use -Xlint, if building on the command line). That will inform you that casting to generic types is an unsafe operation.
A good way to keep things clean is to create actual data classes which encapsulate those Maps.
For instance, you might replace this:
Map<String, Map<String, Map<String, Boolean>>> map = new HashMap<>();
with this:
Person person = new Person();
supported by these three classes:
public class Person {
private final Map<String, Address> addressesByType = new HashMap<>();
public Set<String> getAddressTypes() {
return new HashSet<>(addressesByType.keySet());
}
public Address getAddress(String type) {
return addressesByType.get(type);
}
public void addAddress(String type,
Address address) {
Objects.requireNonNull(type);
Objects.requireNonNull(address);
addressesByType.put(type, address);
}
}
and:
public class Address {
public static final String TYPE_HOME = "Home";
public static final String TYPE_WORK = "Work";
private final Map<String, Vehicle> vehiclesByType = new HashMap<>();
public Set<String> getVehicleTypes() {
return new HashSet<>(vehiclesByType.keySet());
}
public Vehicle getVehicle(String type) {
return vehiclesByType.get(type);
}
public void addVehicle(String type,
Vehicle vehicle) {
Objects.requireNonNull(type);
Objects.requireNonNull(vehicle);
vehiclesByType.put(type, vehicle);
}
}
and finally:
public class Vehicle {
public static final String TYPE_PERSONAL = "Personal";
public static final String TYPE_BUSINESS = "Business";
private final Map<String, Boolean> inspectionsByDate = new HashMap<>();
public Set<String> getInspectionDates() {
return inspectionsByDate.keySet();
}
public Boolean getInspectionOutcome(String date) {
return inspectionsByDate.get(date);
}
public void addInspection(String date,
boolean outcome) {
Objects.requireNonNull(date);
inspectionsByDate.put(date, outcome);
}
}
Your loops would then look like this:
for (String addressType : person.getAddressTypes()) {
Address address = person.getAddress(addressType);
for (String vehicleType : address.getVehicleTypes()) {
Vehicle vehicle = address.getVehicle(vehicleType);
for (String date : vehicle.getInspectionDates()) {
boolean outcome = vehicle.getInspectionOutcome(date);
// ...
}
}
}
(The above is just an example. Obviously, in real life, the keys would be enum values, the dates would be LocalDate or Date objects, and people can have more than one address and more than one vehicle for a particular purpose.)
You can encapsulate Lists in a similar manner; see, for example, NodeList.
could there be any potential problems in going with this approach ?
Thanks in Anticipation !
A not readable code and a losing of the generics benefit as you have to cast values returned by Map methods, as if you used a raw type.
In any case, having a so important deep structure may create runtime errors because of potential instantiation missing and makes code complex to read and maintain.
You should improve your design and introduce custom classes that wrap the maps and provide logic methods to add and retrieve data.
You should also consider library as Guava.
Table is for example a good candidate to bring a some abstraction in your manipulated types.
public interface A {
int getA();
}
public class MyObj implements A {
public int getA(){
return 1;
}
}
If have a Map : Map<? extends A, String> aMap = new HashMap<>();
How can I add an MyObj to this aMap ? Or how should be the class MyObj so that it can work in this map
How can i add an MyObj to this aMap ?
You can't, because of the upper bound on the key type.
The reason is that ? extends A could be MyOtherObj implements A. In this case, it would be type-unsafe to be able to put a key of type MyObj into the map:
Map<MyOtherObj, String> anotherMap = new HashMap<>();
Map<? extends A, String> aMap = anotherMap;
aMap.put(new MyObj(), ""); // Can't do this; but if you could...
MyOtherObj obj = anotherMap.keySet().iterator().next(); // ClassCastException!
Remember the acronym PECS (see this question for a lot more detail):
Producer extends
Consumer super
In other words, Map<? extends A, String> can only be used to produce instances of A, it can't consume/accept instances of A.
For example, you can iterate the keys ("producing" the keys):
for (A a : aMap.keySet()) { ... }
The map can only "consume" a literal null:
aMap.put(null, "");
because null can be cast to any type without exception. But there's not much use in a map which only has a single key - you may as well just store the value directly.
The only way to do this type-safely is to put the instance of MyObj into the map via a reference which you know accepts MyObj instances:
Map<MyObj, String> safeMap = new HashMap<>();
safeMap.put(new MyObj(), "");
Map<? extends A, String> aMap = safeMap;
or
Map<A, String> safeMap = new HashMap<>();
safeMap.put(new MyObj(), "");
Map<? extends A, String> aMap = safeMap;
But you should consider not having the wildcard-typed map at all; Map<MyObj, String> or Map<A, String> is easier.
This isn't possible. Your compiler won't allow it.
You have to change your Map to:
Map<A, String> aMap = new HashMap<>();
After this you can use put to add an element to it:
aMap.put(new MyObj(), "myObject");
I would like to have a method that maps a List to a NavigableMap. The method call expects an parameter that is used as map key. This parameter is an attribute of the list objects.
Something like this, so both calls are ok:
List<MyObject> list = new ArrayList<>();
NavigableMap<String, MyObject> stringKeyMap = asNavMap(list, MyObject:.getString());
NavigableMap<Date, MyObject> dateKeyMap = asNavMap(list, MyObject::getDate());
I dont know how to define the second parameter (MyObject::getDate()). Do I have to use a lambda expression (p -> p.getDate()) or something like Predicate or Function?
I've tried to derive a solution from Approach 8 (or simular) from http://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html, but I don't know how to do.
This is what I have done so far:
The concrete implementation:
public class ConcreteConverter {
public static NavigableMap<Integer, Pair<Integer, String>> asNavMap(List<Pair<Integer, String>> pairs) {
NavigableMap<Integer, Pair<Integer, String>> navMap = new TreeMap<>();
for (Pair<Integer, String> pair : pairs) {
navMap.put(pair.getKey(), pair);
}
return navMap;
}
public static void main(String[] args) {
List<Pair<Integer, String>> pairs = new ArrayList<>();
pairs.add(new Pair<Integer, String>(1, "one"));
NavigableMap<Integer, Pair<Integer, String>> map = ConcreteConverter.asNavMap(pairs);
}
}
class Pair<K, V> {
K key;
V val;
// constructor, getter, setter
}
Here I stuck (??? is an attribute of the Pair object):
public static <K, V> NavigableMap<K, V> asNavMap(List<V> items, ???) {
NavigableMap<K, V> navMap = new TreeMap<>();
for (V item : items) {
navMap.put(???, item);
}
return navMap;
}
Please notice I have barely experiences writing generic methods or using lambda functions/interfaces.
Any help is appreciated.
Edit 1
As Nick Vanderhofen mentioned I didn't clarify the search for a generic solution.
You can do that with a Function. You keep the code you wanted:
List<MyObject> list = new ArrayList<>();
NavigableMap<String, MyObject> stringKeyMap = asNavMap(list, MyObject::getKey);
The method asNavMap can then take a Function:
private NavigableMap<String,MyObject> asNavMap(List<MyObject> list, Function<MyObject, String> getKey) {
//the actual mapping goes here
}
The getKey method you are specifying can either be a simple getter on the MyObject:
public String getKey(){
return key;
}
Or you could create a static method to get the same result:
public static String getKey(MyObject myObject){
return myObject.getKey();
}
To apply the function you can just use the apply method:
String key = getKey.apply(someObject);
For the actual mapping implementation you can keep your for loop, or you could rewrite it using java 8 and re-use the Function that you got as a parameter in the collector. However, since you want a TreeMap, the syntax is quite verbose:
items.stream().collect(Collectors.toMap(mapper, Function.identity(), (a,b) -> a, TreeMap::new));
Just figured out a working solution!
Still reading http://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html#approach7 I've tried to use Function, and now this is my solution:
public static <K, V> NavigableMap<K, V> asNavigableMap(List<V> items, Function<V, K> mapper) {
NavigableMap<K, V> navMap = new TreeMap<>();
for (V item : items)
navMap.put(mapper.apply(item), item);
return navMap;
}
And these calls work:
List<Pair<Integer, String>> pairs = new ArrayList<>();
pairs.add(new Pair<Integer, String>(1, "one"));
NavigableMap<Integer, Pair<Integer, String>> navI2P1 = GenericConverter.asNavigableMap(pairs, Pair::getKey);
NavigableMap<String, Pair<Integer, String>> navI2P2 = GenericConverter.asNavigableMap(pairs, Pair::getVal);
It was hard for me to understand the Function functional interface and the apply method.
Thanks to anyone!
I am trying to make a method call like this,
public class GenericsTest<T> {
public static <T> Map<String, T> createMap(Class<? extends Map<String, T>> clazz) {
return null;
}
public static void main(String[] argv) {
Map<String, Integer> result = createMap(TreeMap.class);
}
}
But I am getting this error,
<T>createMap(java.lang.Class<? extends java.util.Map<java.lang.String,T>>) in test.GenericsTest<T> cannot be applied to (java.lang.Class<java.util.TreeMap>)
How to fix this problem?
Map<String, Integer> instance = new TreeMap<String, Integer>();
#SuppressWarnings("unchecked")
Map<String, Integer> map =
createMap((Class<? extends Map<String, Integer>>)instance.getClass());
map.put("x", 1);
System.out.println("THIS IS x: " + map.get("x"));
This will appropriately print out 1. The implementation of the method is most likely
try
{
return clazz.newInstance();
}
catch (Exception e)
{
throw new RuntimeException(e);
}
A better implementation of their API would be for them to ask you for the type, T, and for them to give back a Map of their choosing instead of asking you for all of the details. Otherwise, as long as they are not filling in the Map with any data, you can instantiate a Map with the generic type argument yourself like so:
public static <T> Map<String, T> getMap()
{
return new TreeMap<String, T>();
}
You can then access that without a warning:
// note the lack of type arguments, which are inferred
Map<String, Integer> instance = getMap();
// alternatively, you could do it more explicitly:
// Map<String, Integer> instance = ClassName.<Integer>getMap();
There's really no reason for them to ask you for the Class type of your Map except to give you back an exact match to the implementation (e.g., if you stick in a HashMap, then you will get back a HashMap, and if you stick in a TreeMap, then you will get back a TreeMap). However, I suspect that the TreeMap will lose any Comparator that it was constructed with, and since that is an immutable (final) field of TreeMap, then you cannot fix that; that means that the Map is not the same in that case, nor is it likely to be what you want.
If they are filling in the Map with data, then it makes even less sense. You could always pass in an instance of a Map to fill, or have them return a Map that you can simply wrap (e.g., new TreeMap<String, Integer>(instance);), and they should know which Map offers the most utility to the data.
I have a method that returns an instance of
Map<String, List<Foo>> x();
and another method that returns an instance of
Map<String, Collection<Foo>> y();
Now if I want to dynamically add one of this Maps in my field, how can I write the generics for it to work?
ie:
public class Bar {
private Map<String, ? extends Collection<Foo>> myMap;
public void initializer() {
if(notImportant) myMap = x(); //OK
else myMap = y(); // !OK (Need cast to (Map<String, ? extends Collection<Foo>>)
}
}
Now is it ok that I cast to the signature even though the y() is declared as being Collection?
If it is not ok to cast, can I somehow write this (Collection OR List)
I mean, List is a Collection, so it should somehow be possible.
private Map<String, Collection<Foo> | List<Foo>>> myMap;
The way you did it with ? extends Collection is fine. You can't have something like OR since if you did you wouldn't know what it is you're getting back if you do myMap.get("someString"); you can't do List|Collection someVariable = myMap.get("someString"), you have to choose one, and if you choose Collection it's the same as using ? extends, if you choose List, you'll end up in all sort of trouble if the object in the map is actually a Set (which is also a collection), not a list, and you try calling methods that only List has (like indexOf). As for the reason you need to use ? extends is because Map<String, List> does not extend Map<String, Collection> even though List extends Collection.
You should take note though, that using ? extends Collection will only let you get values from the map, since then it's sure that what you get is a Collection (or child of Collection), but if you try to put something in the map, you won't be able to (since myMap may be Map<String, Set>, but since you only see it as Map<String, ? extends Collection> you might try to put a List in it which wouldn't be ok)
I'm not sure what your problem is. This code (essentially your code) compiles just fine.
import java.util.*;
public class Generic {
static class Foo {};
static Map<String, List<Foo>> x() {
return null;
}
static Map<String, Collection<Foo>> y() {
return null;
}
static Map<String, ? extends Collection<Foo>> myMap;
public static void main(String[] args) {
myMap = x();
myMap = y();
myMap = new HashMap<String,SortedSet<Foo>>();
for (Collection<Foo> value : myMap.values());
}
}
You can NOT, however, do something like List<Integer|String>. Java generics type bounds just doesn't work like that.