Currently have code that initializes a Function<String, Object> lookup variable:
Map<String, String> map = new HashMap<>();
...
lookup = map::get;
Is it possible to derive the HashMap from this lookup variable? According to (https://docs.oracle.com/javase/8/docs/api/java/util/function/Function.html) there may not be. Possibly a domain getter and an output getter for Function<String, Object> types?
This might or might not feasible: when Java generate the lambda, it creates an implementation of Function which reference variables used by the lambda as synthetic fields. Since the lambda is map::get, there is a synthetic field for the map.
Using this code and jdoodle, or any java 11 compiler:
import java.util.*;
import java.util.function.*;
import java.lang.reflect.*;
public class MyClass {
public static void main(String args[]) {
Map<String, String> map = new HashMap<>();
Function<String, String> lookup = map::get;
System.out.println(lookup);
System.out.println(lookup.getClass());
for (Field field : lookup.getClass().getDeclaredFields()) {
System.out.println(field);
}
}
}
If you execute the code, you should see a field:
MyClass$$Lambda$1/0x0000000100060c40#4c3e4790
class MyClass$$Lambda$1/0x0000000100060c40
private final java.util.Map MyClass$$Lambda$1/0x0000000100060c40.arg$1
The MyClass$$Lambda$1/0x0000000100060c40.arg$1 is the reference to map.
As you can see, the name is generated and not very easy to predict: you could certainly assume "somewhere" that if there is one field of type Map, then it may be the map used in map::get.
The field is private and with Java 11, you don't know the module of the generated class: you may not even access it using reflection (or java.lang.invoke.MethodHandle).
The short answer is no, it is not reasonably possible. Neither I think you should try to do it unless you want to know how it works.
Related
Small question regarding a Spotbug finding I am having a hard time to fix please.
On this super simple POJO:
import java.util.Map;
public class QuestionPojo {
private final Map<String, String> map;
public QuestionPojo(Map<String, String> map) {
this.map = map;
}
public Map<String, String> getMap() {
return map;
}
#Override
public String toString() {
return "QuestionPojo{" +
"map=" + map +
'}';
}
}
I am getting flag on the map with may expose internal representation by storing an externally mutable object into QuestionPojo.map
One time on this.map = map;
Another one on the getter return map.
I tried invoking a possible clone() method, but it seems it is not supported in Map.
How do I fix this please?
Thank you
may expose internal representation by storing an externally mutable object into QuestionPojo.map
What this is telling you is that the internal state of an instance of this class can be changed outside the class.
For example:
Map<String, String> map = new HashMap<>();
map.put("hello", "world");
QuestionPojo qp = new QuestionPojo(map);
// Both of these lines change the map stored inside qp.
map.clear();
qp.getMap().put("silly", "silly");
As to whether this external change of state is important depends on the semantics of your class - in general it is undesirable, however, because it makes it hard to reason about its behavior, because the map can be changed from afar (i.e. anywhere that has a reference to the QuestionPojo, or the map).
The solution to this is defensive copying: take a copy of the map in the constructor:
public QuestionPojo(Map<String, String> map) {
this.map = new HashMap<>(map);
}
and return a copy of the map in the getter:
public Map<String, String> getMap() {
return new HashMap<>(map);
}
This means that nothing outside the class has access to the map stored inside the class.
I have a line of code:
private final Map<MyClassA<?>, MyClassB<?>> myMap = new HashMap<>();
Is there any way to define that map in a way that would tell the compiler that the ? in each case must be the same class?
Something like this?
private final <T> Map<MyClassA<T>, MyClassB<T>> myMap = new HashMap<>();
... which is not legal syntax?
It's just a self-learning question at this point.
FWIW, I want to add a method
public <T> MyClassB<T> getForA(MyClassA<T> a) {
return this.myMap.get(a);
}
But I get a compile error unless I can define myMap to insist that both the key and the value wrap the same type.
As you already figured out, you can't do that if key and value are different for different entries:
map.put(new MyClassA<Foo>(), new MyClassB<Foo>());
map.put(new MyClassA<Bar>(), new MyClassB<Bar>());
(I've taken this requirement from your comment)
What you can do is to write some helper methods, which enforce this constraint:
public <T> void put(MyClassA<T> key, MyClass<B> value) {
// Maybe check at runtime if the constraint is not validated?
map.put(key, value);
}
public <T> MyClassB<T> get(MyClassA<T> key) {
// This will produce an unchecked warning.
return (T) map.get(key);
}
As long as you only access the map through such helper methods (and don't use raw types), the constraint on the map will not be violated, which allows you to write type safe code.
The only part that is not typesafe are those helper methods, and that's where you have to be careful.
You can do something similar if you introduce one static inner class for the type you need. For example:
public class DoubleGenericTest<T> {
public static class MapHolder<Z> {
private final Map<MyClassA<Z>, MyClassB<Z>> myMap = new HashMap<>();
}
private final MapHolder<String> stringMap = new MapHolder<>();
private final MapHolder<Integer> integerMap = new MapHolder<>();
}
class MyClassA<X> {}
class MyClassB<Y> {}
This gives you the class you need to hang the type parameter onto. Maybe not ideal in every situation but it's the only thing I can think of.
I am getting the below error for HashMap in the Java code.
Error - "The type HashMap is not generic; it cannot be parameterized with arguments <>"
package com.example.map;
import java.util.Map;
import java.util.HashMap;
public class HashMap {
public static void main(String[] args) {
// compilation error here vvvvvvv
Map<Integer, String> mapHttpErrors = new HashMap<>();
mapHttpErrors.put(200, "OK");
mapHttpErrors.put(303, "See Other");
mapHttpErrors.put(404, "Not Found");
mapHttpErrors.put(500, "Internal Server Error");
System.out.println(mapHttpErrors);
}
}
You have named your own class HashMap as well.
When you write new HashMap the compiler thinks you're referring to your own class which indeed does not specify any generic parameters.
You can (and in fact: should) either change the name of your class to something else or explicitly refer to java.util.HashMap in your code:
Map<Integer, String> mapHttpErrors = new java.util.HashMap<>();
As the error is telling you, your HashMap class isn't generic, so your code makes no sense.
You should not make classes with the same names as built-in classes.
In the following line HashMap refers to the public class you created:
Map<Integer, String> mapHttpErrors = new **HashMap**<>();
Naming your class with exact the same name as classes from official Java API is often a very bad idea. But if you're sure that instead of that, you still want to keep the old name, here is the way you can use the HashMap from java.util package:
Map<Integer, String> mapHttpErrors = new java.util.HashMap<>();
But again, remember that naming your classes as you did in your program is rather a bad idea.
In the below class I am declaring myMap
public class AllMap {
public static final Map<String, String> myMap= new HashMap<>();
static {
Map.put("yy", "AA");
Map.put("xx", "BB");
}
}
I need to access map in other class.
public class Test {
FieldMap.Map;
}
Everything is working fine,but sonar is giving warning on 1st class:
Make this member "protected".
on the line
public static final Map<String, String> myMap = new HashMap<>();
Should I ignore this warning or should I change it to protected?
If you need to access the map in other classes, then you should protect it against modifications:
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class AllMap {
public static final Map<String, String> myMap;
static {
final Map<String, String> tmpMap = new HashMap<>();
tmpMap.put("yy", "AA");
tmpMap.put("xx", "BB");
myMap = Collections.unmodifiableMap(tmpMap);
}
}
You can still use it and SonarQube won't mark this as error (because the map is read-only).
Read more about Collections#unmodifiableMap(Map).
Sonar is giving you suggestion that your "member" which is:
public final Map<String, String> myMap = new HashMap<>();
should not be public.
Why?
Leaving this as public makes it available from any other package - so you are exposing the member to everybody. Below code is accessing the member directly:
AllMap allMap = new AllMap();
allMap.myMap.put("X", "Y");
In most cases members should be private and accessed by getters and setters, which could prevent with returning the same reference - so you can implement some logic before you get the reference or set it.
If you need to make it static, make static getters and setters.
Sonar lint issue because you are exposing references to mutable objects to client code.Here you are exposing a Map though it is final the final object allows clients to modify the contents of the object.
Never initialize such a field to a client-provided object reference or return the object reference from an accessor.
private static final SomeType [] THE_THINGS = { ... };
public static final List<SomeType> SOMETHINGS =
Collections.unmodifiableList(Arrays.asList(THE_THINGS));
Reference link
Reference link2
The following code doesn't compile
import com.google.common.collect.LinkedHashMultimap;
public class Test {
public static void main(String[] args) {
LinkedHashMultimap<String, String> p = new LinkedHashMultimap<String, String>();
}
}
the error is: The constructor LinkedHashMultimap() is not visible
I have imported the google Java libraries, they are in the build path
and
LinkedHashMultimap<String, String> p;
alone doesn't cause compile error... weird
Use the static create method to get a new instance of LinkedHashMultimap.
LinkedHashMultimap<String, String> p = LinkedHashMultimap.create();
LinkedHashMultimap has no public constructors and the way to get an instance is by using the factory method create. The reason for this is the loophole in Java Generics due to which type inference works only for methods and not for constructors. This means that instead of for example
LinkedHashMultimap<String, Map<String, List<String>>> =
new LinkedHashMultimap<String, Map<String, List<String>>>();
you can write
LinkedHashMultimap<String, Map<String, List<String>>> =
LinkedHashMultimap.create();
With Java 7 the pressure has subsided due to the "diamond operator".