Because final marks the reference, not the object. You can't make that reference point to a different hash table. But you can do anything to that object, including adding and removing things.
Your example of an int is a primitive type, not a reference. Final means you cannot change the value of the variable. So, with an int you cannot change the value of the variable, e.g. make the value of the int different. With an object reference, you cannot change the value of the reference, i.e. which object it points to.
Because final marks the reference, not the object. You can't make that reference point to a different hash table. But you can do anything to that object, including adding and removing things.
Your example of an int is a primitive type, not a reference. Final means you cannot change the value of the variable. So, with an int you cannot change the value of the variable, e.g. make the value of the int different. With an object reference, you cannot change the value of the reference, i.e. which object it points to.
You can use Collections.unmodifiableMap to get an unmodifiable wrapper over your hash table.
Example:
import java.util.*;
class Test{
public static final Map<String,Integer> MYHASH;
static{
Hashtable<String,Integer> tmp =
new Hashtable<String,Integer>();
tmp.put("A",65);
tmp.put("C",67);
MYHASH = Collections.unmodifiableMap(tmp);
}
public static void main(String[] args){
System.out.println(MYHASH.get("A"));
//this will throw
//java.lang.UnsupportedOperationException
MYHASH.put("B",66);
}
}
You can implement a new HashMap
public class CoolMap<K, V> extends HashMap<K, V> {
@Override
public V put(K key, V value) {
if (size() == 7) {
throw new IllegalStateException("Size is at max!");
} else {
// If there is something already with that key
if (containsKey(value)) {
// do nothing
return value;
} else {
// put inside
return super.put(key, value);
}
}
}
@Override
public void putAll(Map<? extends K, ? extends V> collection) {
if (collection.size() > 7) {
throw new IllegalStateException("Size is at max!");
} else {
super.putAll(collection);
}
}
@Override
public V remove(Object key) {
return null;// doesn't remove anything
}
Points 2 and 3 are covered by Collections.unmodifiableMap. To cover the first point, you can add an hand written test.
The instance initialiser is just syntactic sugar in this case, right? I don't see why you need an extra anonymous class just to initialize. And it won't work if the class being created is final.
You can create an immutable map using a static initialiser too:
public class Test {
private static final Map<Integer, String> myMap;
static {
Map<Integer, String> aMap = ....;
aMap.put(1, "one");
aMap.put(2, "two");
myMap = Collections.unmodifiableMap(aMap);
}
}
I like the Guava way of initialising a static, immutable map:
static final Map<Integer, String> MY_MAP = ImmutableMap.of(
1, "one",
2, "two"
);
As you can see, it's very concise (because of the convenient factory methods in ImmutableMap).
If you want the map to have more than 5 entries, you can no longer use ImmutableMap.of(). Instead, try ImmutableMap.builder() along these lines:
static final Map<Integer, String> MY_MAP = ImmutableMap.<Integer, String>builder()
.put(1, "one")
.put(2, "two")
// ...
.put(15, "fifteen")
.build();
To learn more about the benefits of Guava's immutable collection utilities, see Immutable Collections Explained in Guava User Guide.
(A subset of) Guava used to be called Google Collections. If you aren't using this library in your Java project yet, I strongly recommend trying it out! Guava has quickly become one of the most popular and useful free 3rd party libs for Java, as fellow SO users agree. (If you are new to it, there are some excellent learning resources behind that link.)
Update (2015): As for Java 8, well, I would still use the Guava approach because it is way cleaner than anything else. If you don't want Guava dependency, consider a plain old init method. The hack with two-dimensional array and Stream API is pretty ugly if you ask me, and gets uglier if you need to create a Map whose keys and values are not the same type (like Map<Integer, String> in the question).
As for future of Guava in general, with regards to Java 8, Louis Wasserman said this back in 2014, and [update] in 2016 it was announced that Guava 21 will require and properly support Java 8.
Update (2016): As Tagir Valeev points out, Java 9 will finally make this clean to do using nothing but pure JDK, by adding convenience factory methods for collections:
static final Map<Integer, String> MY_MAP = Map.of(
1, "one",
2, "two"
);
All Versions
In case you happen to need just a single entry: There is Collections.singletonMap("key", "value").
For Java Version 9 or higher:
Yes, this is possible now. In Java 9 a couple of factory methods have been added that simplify the creation of maps :
// this works for up to 10 elements:
Map<String, String> test1 = Map.of(
"a", "b",
"c", "d"
);
// this works for any number of elements:
import static java.util.Map.entry;
Map<String, String> test2 = Map.ofEntries(
entry("a", "b"),
entry("c", "d")
);
In the example above both test and test2 will be the same, just with different ways of expressing the Map. The Map.of method is defined for up to ten elements in the map, while the Map.ofEntries method will have no such limit.
Note that in this case the resulting map will be an immutable map. If you want the map to be mutable, you could copy it again, e.g. using mutableMap = new HashMap<>(Map.of("a", "b"));. Also note that in this case keys and values must not be null.
(See also JEP 269 and the Javadoc)
For up to Java Version 8:
No, you will have to add all the elements manually. You can use an initializer in an anonymous subclass to make the syntax a little bit shorter:
Map<String, String> myMap = new HashMap<String, String>() {{
put("a", "b");
put("c", "d");
}};
However, the anonymous subclass might introduce unwanted behavior in some cases. This includes for example:
- It generates an additional class which increases memory consumption, disk space consumption and startup-time
- In case of a non-static method: It holds a reference to the object the creating method was called upon. That means the object of the outer class cannot be garbage collected while the created map object is still referenced, thus blocking additional memory
Using a function for initialization will also enable you to generate a map in an initializer, but avoids nasty side-effects:
Map<String, String> myMap = createMap();
private static Map<String, String> createMap() {
Map<String,String> myMap = new HashMap<String,String>();
myMap.put("a", "b");
myMap.put("c", "d");
return myMap;
}
This is one way.
Map<String, String> h = new HashMap<String, String>() {{
put("a","b");
}};
However, you should be careful and make sure that you understand the above code (it creates a new class that inherits from HashMap). Therefore, you should read more here: http://www.c2.com/cgi/wiki?DoubleBraceInitialization , or simply use Guava:
Map<String, Integer> left = ImmutableMap.of("a", 1, "b", 2, "c", 3);
ImmutableMap.of works for up to 5 entries. Otherwise, use the builder: source.
Map<String, String> map = new ImmutableMap.Builder<String, String>()
.put("a", "a")
.put("b", "b")
.build();
The getter does not add anything - I would just keep it public.
What may make sense is to have a method that returns the value directly:
public MyUtilityClass {
private static final Map<String, Integer> MAX_LENGTHS = ImmutableMap.of(
"title", 256,
"text", 512);
public static final getMaxLength(String item) {return MAX_LENGTHS.get(item);}
}
public MyAnotherClass {
public void someMethod() {
//accessing the map directly
getMaxLength("title");
}
}
This also allows you to easily modify the underlying implementation later on. For example you could return a default value for items not in the map etc.
You are using ImmutableMap from guava project; thus a true immutable Map.
Making it public would not hurt anyone - as no one can really alter that Map in any way.