Java - unmodifiable key set map - java

I am looking for a way to provide a Map with pre-defined (as in runtime immutable, not compile time const) constant key set, but modifiable values.
The JDK provides Collections.unmodifiableMap factory method, which wraps a Map and provides an immutable view of it.
Is there a similar way to wrap a Map so that only it's keys are immutable? For instance, put(K,V) will replace the value of existing keys, but throw UnsupportedOperationException if the key does not exist.

Use an enum as the key. Then one needn't care if they can add a new key since the key domain is fixed and finite. In fact, that's such a standard use case that Java provides java.util.EnumMap<K extends Enum<K>,V>
http://docs.oracle.com/javase/8/docs/api/java/util/EnumMap.html

Ok, all the solutions suggested here were to wrap or extend Collections.UnmodifiableMap. Both impossible since the original implementation would not allow to override put (and replace etc.), Which is exactly what makes it secure...
I see two options:
The "smelly" option - Using reflection to get hold of the original Map<> and directly call it's methods.
The "ugly" option - Copy the source of some of the static classes in java.lang.Collections and modify them.
If anyone has a better idea, please let me know.
Here is an initial implementation of the 2'nd solution:
import java.io.Serializable;
import java.util.*;
import java.util.function.*;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import static java.util.Collections.unmodifiableCollection;
import static java.util.Collections.unmodifiableSet;
/**
* #serial include
*/
public class UnmodifiableKeySetMap<K,V> implements Map<K,V>, Serializable {
private final Map<K, V> m;
/**
* Returns a view of the specified map with unmodifiable key set. This
* method allows modules to provide users with "read-only" access to
* internal maps. Query operations on the returned map "read through"
* to the specified map, and attempts to modify the returned
* map, whether direct or via its collection views, result in an
* <tt>UnsupportedOperationException</tt>.<p>
*
* The returned map will be serializable if the specified map
* is serializable.
*
* #param <K> the class of the map keys
* #param <V> the class of the map values
* #param m the map for which an unmodifiable view is to be returned.
* #return an unmodifiable view of the specified map.
*/
public static <K,V> Map<K,V> unmodifiableKeySetMap(Map<K, V> m) {
return new UnmodifiableKeySetMap<>(m);
}
UnmodifiableKeySetMap(Map<K, V> m) {
if (m==null)
throw new NullPointerException();
this.m = m;
}
public int size() {return m.size();}
public boolean isEmpty() {return m.isEmpty();}
public boolean containsKey(Object key) {return m.containsKey(key);}
public boolean containsValue(Object val) {return m.containsValue(val);}
public V get(Object key) {return m.get(key);}
public V put(K key, V value) {
if (containsKey(key)) {
return m.put(key, value);
}
throw new UnsupportedOperationException();
}
public V remove(Object key) {
throw new UnsupportedOperationException();
}
public void putAll(Map<? extends K, ? extends V> m) {
throw new UnsupportedOperationException();
}
public void clear() {
throw new UnsupportedOperationException();
}
private transient Set<K> keySet;
private transient Set<Map.Entry<K,V>> entrySet;
private transient Collection<V> values;
public Set<K> keySet() {
if (keySet==null)
keySet = unmodifiableSet(m.keySet());
return keySet;
}
public Set<Map.Entry<K,V>> entrySet() {
if (entrySet==null)
entrySet = new UnmodifiableKeySetMap.UnmodifiableEntrySet<>(m.entrySet());
return entrySet;
}
public Collection<V> values() {
if (values==null)
values = unmodifiableCollection(m.values());
return values;
}
public boolean equals(Object o) {return o == this || m.equals(o);}
public int hashCode() {return m.hashCode();}
public String toString() {return m.toString();}
// Override default methods in Map
#Override
#SuppressWarnings("unchecked")
public V getOrDefault(Object k, V defaultValue) {
// Safe cast as we don't change the value
return ((Map<K, V>)m).getOrDefault(k, defaultValue);
}
#Override
public void forEach(BiConsumer<? super K, ? super V> action) {
m.forEach(action);
}
#Override
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
throw new UnsupportedOperationException();
}
#Override
public V putIfAbsent(K key, V value) {
throw new UnsupportedOperationException();
}
#Override
public boolean remove(Object key, Object value) {
throw new UnsupportedOperationException();
}
#Override
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
throw new UnsupportedOperationException();
}
#Override
public V computeIfPresent(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
throw new UnsupportedOperationException();
}
#Override
public V compute(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
throw new UnsupportedOperationException();
}
#Override
public V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
throw new UnsupportedOperationException();
}
/**
* #serial include
*/
static class UnmodifiableSet<E> extends UnmodifiableCollection<E>
implements Set<E>, Serializable {
private static final long serialVersionUID = -9215047833775013803L;
UnmodifiableSet(Set<? extends E> s) {super(s);}
public boolean equals(Object o) {return o == this || c.equals(o);}
public int hashCode() {return c.hashCode();}
}
/**
* #serial include
*/
static class UnmodifiableCollection<E> implements Collection<E>, Serializable {
private static final long serialVersionUID = 1820017752578914078L;
final Collection<? extends E> c;
UnmodifiableCollection(Collection<? extends E> c) {
if (c==null)
throw new NullPointerException();
this.c = c;
}
public int size() {return c.size();}
public boolean isEmpty() {return c.isEmpty();}
public boolean contains(Object o) {return c.contains(o);}
public Object[] toArray() {return c.toArray();}
public <T> T[] toArray(T[] a) {return c.toArray(a);}
public String toString() {return c.toString();}
public Iterator<E> iterator() {
return new Iterator<E>() {
private final Iterator<? extends E> i = c.iterator();
public boolean hasNext() {return i.hasNext();}
public E next() {return i.next();}
public void remove() {
throw new UnsupportedOperationException();
}
#Override
public void forEachRemaining(Consumer<? super E> action) {
// Use backing collection version
i.forEachRemaining(action);
}
};
}
public boolean add(E e) {
throw new UnsupportedOperationException();
}
public boolean remove(Object o) {
throw new UnsupportedOperationException();
}
public boolean containsAll(Collection<?> coll) {
return c.containsAll(coll);
}
public boolean addAll(Collection<? extends E> coll) {
throw new UnsupportedOperationException();
}
public boolean removeAll(Collection<?> coll) {
throw new UnsupportedOperationException();
}
public boolean retainAll(Collection<?> coll) {
throw new UnsupportedOperationException();
}
public void clear() {
throw new UnsupportedOperationException();
}
// Override default methods in Collection
#Override
public void forEach(Consumer<? super E> action) {
c.forEach(action);
}
#Override
public boolean removeIf(Predicate<? super E> filter) {
throw new UnsupportedOperationException();
}
#SuppressWarnings("unchecked")
#Override
public Spliterator<E> spliterator() {
return (Spliterator<E>)c.spliterator();
}
#SuppressWarnings("unchecked")
#Override
public Stream<E> stream() {
return (Stream<E>)c.stream();
}
#SuppressWarnings("unchecked")
#Override
public Stream<E> parallelStream() {
return (Stream<E>)c.parallelStream();
}
}
/**
* We need this class in addition to UnmodifiableSet as
* Map.Entries themselves permit modification of the backing Map
* via their setValue operation. This class is subtle: there are
* many possible attacks that must be thwarted.
*
* #serial include
*/
static class UnmodifiableEntrySet<K,V>
extends UnmodifiableSet<Entry<K,V>> {
private static final long serialVersionUID = 7854390611657943733L;
#SuppressWarnings({"unchecked", "rawtypes"})
UnmodifiableEntrySet(Set<? extends Map.Entry<? extends K, ? extends V>> s) {
// Need to cast to raw in order to work around a limitation in the type system
super((Set)s);
}
static <K, V> Consumer<Entry<K, V>> entryConsumer(Consumer<? super Entry<K, V>> action) {
return e -> action.accept(new UnmodifiableKeySetMap.UnmodifiableEntrySet.UnmodifiableEntry<>(e));
}
public void forEach(Consumer<? super Entry<K, V>> action) {
Objects.requireNonNull(action);
c.forEach(entryConsumer(action));
}
static final class UnmodifiableEntrySetSpliterator<K, V>
implements Spliterator<Entry<K,V>> {
final Spliterator<Map.Entry<K, V>> s;
UnmodifiableEntrySetSpliterator(Spliterator<Entry<K, V>> s) {
this.s = s;
}
#Override
public boolean tryAdvance(Consumer<? super Entry<K, V>> action) {
Objects.requireNonNull(action);
return s.tryAdvance(entryConsumer(action));
}
#Override
public void forEachRemaining(Consumer<? super Entry<K, V>> action) {
Objects.requireNonNull(action);
s.forEachRemaining(entryConsumer(action));
}
#Override
public Spliterator<Entry<K, V>> trySplit() {
Spliterator<Entry<K, V>> split = s.trySplit();
return split == null
? null
: new UnmodifiableKeySetMap.UnmodifiableEntrySet.UnmodifiableEntrySetSpliterator<>(split);
}
#Override
public long estimateSize() {
return s.estimateSize();
}
#Override
public long getExactSizeIfKnown() {
return s.getExactSizeIfKnown();
}
#Override
public int characteristics() {
return s.characteristics();
}
#Override
public boolean hasCharacteristics(int characteristics) {
return s.hasCharacteristics(characteristics);
}
#Override
public Comparator<? super Entry<K, V>> getComparator() {
return s.getComparator();
}
}
#SuppressWarnings("unchecked")
public Spliterator<Entry<K,V>> spliterator() {
return new UnmodifiableKeySetMap.UnmodifiableEntrySet.UnmodifiableEntrySetSpliterator<>(
(Spliterator<Map.Entry<K, V>>) c.spliterator());
}
#Override
public Stream<Entry<K,V>> stream() {
return StreamSupport.stream(spliterator(), false);
}
#Override
public Stream<Entry<K,V>> parallelStream() {
return StreamSupport.stream(spliterator(), true);
}
public Iterator<Map.Entry<K,V>> iterator() {
return new Iterator<Map.Entry<K,V>>() {
private final Iterator<? extends Map.Entry<? extends K, ? extends V>> i = c.iterator();
public boolean hasNext() {
return i.hasNext();
}
public Map.Entry<K,V> next() {
return new UnmodifiableKeySetMap.UnmodifiableEntrySet.UnmodifiableEntry<>(i.next());
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
#SuppressWarnings("unchecked")
public Object[] toArray() {
Object[] a = c.toArray();
for (int i=0; i<a.length; i++)
a[i] = new UnmodifiableKeySetMap.UnmodifiableEntrySet.UnmodifiableEntry<>((Map.Entry<? extends K, ? extends V>)a[i]);
return a;
}
#SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
// We don't pass a to c.toArray, to avoid window of
// vulnerability wherein an unscrupulous multithreaded client
// could get his hands on raw (unwrapped) Entries from c.
Object[] arr = c.toArray(a.length==0 ? a : Arrays.copyOf(a, 0));
for (int i=0; i<arr.length; i++)
arr[i] = new UnmodifiableKeySetMap.UnmodifiableEntrySet.UnmodifiableEntry<>((Map.Entry<? extends K, ? extends V>)arr[i]);
if (arr.length > a.length)
return (T[])arr;
System.arraycopy(arr, 0, a, 0, arr.length);
if (a.length > arr.length)
a[arr.length] = null;
return a;
}
/**
* This method is overridden to protect the backing set against
* an object with a nefarious equals function that senses
* that the equality-candidate is Map.Entry and calls its
* setValue method.
*/
public boolean contains(Object o) {
if (!(o instanceof Map.Entry))
return false;
return c.contains(
new UnmodifiableKeySetMap.UnmodifiableEntrySet.UnmodifiableEntry<>((Map.Entry<?,?>) o));
}
/**
* The next two methods are overridden to protect against
* an unscrupulous List whose contains(Object o) method senses
* when o is a Map.Entry, and calls o.setValue.
*/
public boolean containsAll(Collection<?> coll) {
for (Object e : coll) {
if (!contains(e)) // Invokes safe contains() above
return false;
}
return true;
}
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Set))
return false;
Set<?> s = (Set<?>) o;
if (s.size() != c.size())
return false;
return containsAll(s); // Invokes safe containsAll() above
}
/**
* This "wrapper class" serves two purposes: it prevents
* the client from modifying the backing Map, by short-circuiting
* the setValue method, and it protects the backing Map against
* an ill-behaved Map.Entry that attempts to modify another
* Map Entry when asked to perform an equality check.
*/
private static class UnmodifiableEntry<K,V> implements Map.Entry<K,V> {
private Map.Entry<? extends K, ? extends V> e;
UnmodifiableEntry(Map.Entry<? extends K, ? extends V> e)
{this.e = Objects.requireNonNull(e);}
public K getKey() {return e.getKey();}
public V getValue() {return e.getValue();}
public V setValue(V value) {
throw new UnsupportedOperationException();
}
public int hashCode() {return e.hashCode();}
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> t = (Map.Entry<?,?>)o;
return eq(e.getKey(), t.getKey()) &&
eq(e.getValue(), t.getValue());
}
public String toString() {return e.toString();}
}
}
/**
* Returns true if the specified arguments are equal, or both null.
*
* NB: Do not replace with Object.equals until JDK-8015417 is resolved.
*/
static boolean eq(Object o1, Object o2) {
return o1==null ? o2==null : o1.equals(o2);
}
}

I know this isn't strictly what was asked in the question, but I think this approach is worth considering.
Use Google Guava's ImmutableMap and make your value type mutable- with a wrapper type if necessary.
Before moving on, it's important to understand this: immutability at the map level only means you can't re-assign objects. There's nothing stopping you from calling mutator methods on the objects already in the map. With this insight, the above approach might be obvious, but let me explain via an example.
For me, the value type was AmtomicInteger- already perfectly mutable in exactly the right way I needed, so I didn't need to change anything. However, in general, let's say you have a type T, and you want a partially mutable map for this type. Well, you can almost do this via a mutable map whose values are wrapper types for that class. Here's a really basic wrapper type, which you might want to enhance with some encapsulation:
public class Wrapper<T>{
public T value;
Wrapper(T t){value = t;}
}
Then you simply create a regular ImmutableMap in the normal way, except fill it with objects of Wrapper instead of objects of T. When using the values, you need to unbox them from the wrapper.
Again, I know this isn't what was asked, and unboxing from a wrapper might be too painful for some use cases, but I'd imagine this would work in a lot of cases. Certainly for me, it helped me realise that I already had a mutable type that was basically a wrapper for an int, and so the ImmmutableMap was fine.

1) The proxy
I would reduce the scope of your SemiMutableMap to something like
interface ISemiMutableMap<U, V> {
V get(U key);
V set(U key, V value) throws Exception; //create your own maybe ?
}
This will reduce the possibilities of access but give you the full control of it.
And then implements it simply like a proxy
public class SemiMutableMap<U, V> implements ISemiMutableMap<U,V>{
private Map<U, V> map;
public SemiMutableMap(Map<U, V> map){ //get the predefine maps
this.map = map;
}
public V get(U key){
return map.get(U);
}
public V set(U key, V value) throws Exception{
if(!map.containsKey(key)){
throw new Exception();
}
return map.put(key,value);
}
}
And you can add the methods you like to it off course.
Note that this is not complety true, The constructor should clone the map instead of using the same reference but I am a bit lazy ;) and I've writen this without an IDE
2) The implementation
Nothing prevent you to simply get the code of the UnmodifiableMap from the Collections and adapt it to your needs. From this, you will see it is quite simple to create your own implementation to your need.
Trust me, this class as been tested and reviewed ;)
You will need to adapt put to be able to update an existing value (same code as above) and UnmodifiableEntry.setValue to accept an update from the entry set.

I believe you want to do something like this.
public class UnmodifiableKeyMap{
static enum MYKeySet {KEY1, KEY2, KEY3, KEY4};
private Map myUnmodifiableHashMap = new HashMap();
public boolean containsKey(Object key) {
return this.containsKey(key);
}
public Object get(Object key) {
if(this.containsKey(key)){
return this.myUnmodifiableHashMap.get(key);
}else {
return null;
}
}
public Object put(Object key, Object value) throws Exception {
if(this.containsKey(key)){
this.myUnmodifiableHashMap.put(key, value);
}
throw new Exception("UnsupportedOperationException");
}
public Set keySet() {
Set mySet = new HashSet(Arrays.asList(MYKeySet.values()));
return mySet;
}
public Collection values() {
return this.myUnmodifiableHashMap.values();
}
}

Related

Strange bug with Streams

So I have created my own Set, which is just a regular set, but has additional functions (for example my set only stores absolute values).
Here is my Code:
import java.util.*;
public class SortedByAbsoluteValueIntegerSet<E> extends HashSet<E> {
private Set<Integer> mySet;
public SortedByAbsoluteValueIntegerSet() {
mySet = new HashSet<Integer>();
}
#Override
public int size() {
return mySet.size();
}
#Override
public boolean add(E e){
return mySet.add(Math.abs((Integer) e));
}
#Override
public boolean remove(Object o) {
return mySet.remove(o);
}
#Override
public boolean contains(Object o){
return mySet.contains(o);
}
#Override
public boolean addAll(Collection<? extends E> c) {
List<Integer> myList = new ArrayList<>();
for (Object e: c) {
myList.add(Math.abs((Integer) e));
}
return mySet.addAll(myList);
}
#Override
public String toString(){
return mySet.toString();
}
}
I had a test case in JUnit, which failed. Because there was some issue with my code. For demonstration purpose, and for me to explain my issue better I have created two functions, which show the problem well.
Here is the problem:
public static void testSortedByAbsoluteValueIntegerSet() {
Set<Integer> set1 = new SortedByAbsoluteValueIntegerSet();
Set<Integer> set2 = new HashSet<>();
set1.add(5);
set1.add(3);
set2.add(5);
set2.add(3);
String x = toString(set1); //x is ""
String t = toString(set2); //t is "3 5"
}
public static String toString(final Collection<Integer> collection) {
return String.join(" ", collection.stream()
.map(i -> Integer.toString(i))
.toArray(String[]::new));
}
So the problem arises in this line:
String x = toString(set1); //x is always an empty string
String t = toString(set2); //t works correctly
When I go through debugger I see that String x is always an empty String and String t works correctly. By the way set1 is representation of my created set and set2 is just a regular hashset.
The question is: how can I fix my SortedByAbsoluteValueIntegerSet class so that the toString() method worked fine with my own created set as well.
P.S I am new to streams and I don't really understand the problem, why does it happens.
It's because you're extending HashSet but also using an internal Set.
When adding, you're adding to the internal Set but when using collection.stream() it calls the inherited HashSet (which is empty).
Easiest for you I beleive would be to remove the internal 'mySet' and call the inherited methods in your overridden methods.
For instance, your add method would be
#Override
public boolean add(E e){
return super.add(Math.abs((Integer) e));
}
(and then you don't need to override size, remove, contains of toString or spliterator)
Full example:
import java.util.*;
public class SortedByAbsoluteValueIntegerSet extends HashSet<Integer> {
#Override
public boolean add(Integer e){
return super.add(Math.abs(e));
}
#Override
public boolean addAll(Collection<? extends Integer> c) {
List<Integer> myList = new ArrayList<>();
for (Integer e: c) {
myList.add(Math.abs(e));
}
return super.addAll(myList);
}
}
I think Tomas F gave better answer
Main problem in your set is using HashSet mySet as field and extending HashSet. In java better to use (field) composition instead of extending to add some functionality to your class. Here you tried use both - it's not a good idea.
Best decision is to use just composition and extending more general class, for example AbstractSet<Integer> and Set<Integer>:
import java.util.*;
public class SortedByAbsoluteValueIntegerSet extends AbstractSet<Integer>
implements Set<Integer>, java.io.Serializable {
private final Set<Integer> mySet;
public SortedByAbsoluteValueIntegerSet() {
mySet = new HashSet<>();
}
#Override
public Iterator<Integer> iterator() {
return mySet.iterator();
}
#Override
public int size() {
return mySet.size();
}
#Override
public boolean add(Integer e) {
return mySet.add(Math.abs(e));
}
#Override
public boolean remove(Object o) {
return mySet.remove(o);
}
#Override
public boolean contains(Object o) {
return mySet.contains(o);
}
#Override
public boolean addAll(Collection<? extends Integer> c) {
List<Integer> myList = new ArrayList<>();
for (Integer e : c) {
myList.add(Math.abs(e));
}
return mySet.addAll(myList);
}
#Override
public String toString() {
return mySet.toString();
}
}
in this case you don't have to implement spliterator, because Set has default implementation using this keyword (which is refer to your set as a Collection)
but also you can implement spliterator in your class (but using such extends and internal Set fields are the bad practice. Also, it's better to get rid of type parameter E and casting elements to Integer:
import java.util.*;
public class SortedByAbsoluteValueIntegerSet extends HashSet<Integer> {
private Set<Integer> mySet;
public SortedByAbsoluteValueIntegerSet() {
mySet = new HashSet<>();
}
#Override
public int size() {
return mySet.size();
}
#Override
public boolean add(Integer e){
return mySet.add(Math.abs(e));
}
#Override
public boolean remove(Object o) {
return mySet.remove(o);
}
#Override
public boolean contains(Object o){
return mySet.contains(o);
}
#Override
public boolean addAll(Collection<? extends Integer> c) {
List<Integer> myList = new ArrayList<>();
for (Integer e: c) {
myList.add(Math.abs(e));
}
return mySet.addAll(myList);
}
#Override
public String toString(){
return mySet.toString();
}
#Override
public Spliterator<Integer> spliterator() {
return mySet.spliterator();
}
}

java generics - how to implement a class that takes a HashSet<T>?

Normally when you implement a generic, you have some type T that you want to generalize. I want to write a class that generalizes a HashSet<T>.
I'm trying to write this the following way, but it's not the correct syntax or maybe it's not supported:
public class PermutationHelper<T> implements Iterable<T> {
private HashSet<T> m_set;
private long numberOfPermutations;
private boolean includeEmptyPermutationAsOutput = false;
public PermutationHelper(HashSet<T> set) {
m_set = set;
numberOfPermutations = 2 ^ set.size();
}
public void setIncludeEmptyPermutationAsOutput(boolean value) {
includeEmptyPermutationAsOutput = value;
}
#Override
public Iterator<T> iterator() {
Iterator<T> it = new Iterator<T>() {
long currentIndex = (includeEmptyPermutationAsOutput ? 0 : 1);
#Override
public boolean hasNext() {
return currentIndex < numberOfPermutations;
}
#Override
public T next() {
HashSet<T> result = new HashSet<T>();
return result; // expects T, but is a HashSet<T>..
}
#Override
public void remove() {
throw new UnsupportedOperationException();
}
};
return it;
}
}
I want the Iterator to give me all subsets of the passed-in HashSet<T>.
You can easly do
public class PermutationHelper<T extends HashSet<T>> implements Iterable<T>
in order to 'force' the generic type to be an HashSet or a subtype of a HashSet
What you want is simply
public class PermutationHelper<T> implements Iterable<HashSet<T>>
Your class is generic. You choose to name its generic type T. And it implements Iterable<HashSet<T>>, which means it must have a method
public Iterator<HashSet<T>> iterator()

Java Generics with Composite Key and Wildcards

I have a pair interface e.g.
public interface CompositeKeyType<K1, K2> {
public K1 getKey1();
public K2 getKey2();
}
And an implementation:
package com.bcsg.creditcardrecords;
public class CompositeKeyImplementer<K1, K2> implements
CompositeKeyType<K1, K2> {
private K1 key1;
private K2 key2;
public CompositeKeyImplementer() {
this.key1 = null;
this.key2 = null;
}
public CompositeKeyImplementer(K1 key1, K2 key2) throws IllegalArgumentException {
if (key1.equals(key2)){
throw new IllegalArgumentException("both keys cannot be equal");
}
this.key1 = key1;
this.key2 = key2;
}
#Override
public K1 getKey1() {
return this.key1;
}
#Override
public K2 getKey2() {
return this.key2;
}
#Override
public boolean equals(Object obj) {
if (!(obj instanceof CompositeKeyImplementer<?, ?>)) {
return false;
}
if (!(((CompositeKeyImplementer<?, ?>) obj).getKey1().equals(this.key1))
|| !(((CompositeKeyImplementer<?, ?>) obj).getKey2()
.equals(this.key2))) {
return false;
}
return true;
}
}
Now....I also have an abstract class:
public abstract class AbstractBankCardDetailsHolder<K, V> {
private NavigableMap<K, V> cardData;
public AbstractBankCardDetailsHolder() {
cardData = new TreeMap<K, V>();
}
public AbstractBankCardDetailsHolder(K key, V value){
cardData.put(key, value);
}
public NavigableMap<K, V> getCardData(){
return this.cardData;
}
public void setCardData(NavigableMap<K,V> cadData){
this.cardData.clear();
this.cardData.putAll(cardData);
}
}
Which I am generalising here (it is coming up with errors):
public class CompositeKeyBasedCreditCardDetailsHolder<? extends K, ? extends V> extends
AbstractBankCardDetailsHolder<? extends K, ? extends V> {
private CompositeKeyImplementer<? extends K, ? extends K> numberProviderPair;
// ....... TBC
}
I was under the impression that ? wildcard means ?TypeUnknown? and it will resolve the types #Runtime. However, I notices whilsts writing this questiont that my CompositeKeyImplementer.java class has got wildcards in the equals method too. Is this something that I won't be able to achieve because JVM cannot resolve the different wildcard arrangements such as this one during runtime?
From what I can work out from your sample code:
1) Your CompositeKeyImplementer needs to be generic. It implements a generic interface, and you later refer to it as a generic type.
public class CompositeKeyImplementer<K1, K2> implements CompositeKeyType<K, V> {
...
2) You want to have a CompositeKeyImplementor<K1, K2> with any type arguments that are subtypes of K as a field in your CompositeKeyBasedCreditCardDetailsHolder class.
Therefore you use the wildcards on invocation of that CompositeKeyImplementor as type arguments. You don't use them as type parameters in the generic type declaration for CompositeKeyBasedCreditCardDetailsHolder
public class CompositeKeyBasedCreditCardDetailsHolder<K, V> extends
AbstractBankCardDetailsHolder<K, V> {
private CompositeKeyImplementer<? extends K, ? extends K> numberProviderPair;
// ....... TBC
}
With this you are saying:
I am declaring a generic CompositeKeyBasedCreditCardDetailsHolder with type parameters K, V.
That type has a field called numberProviderPair
Which itself is a generic type CompositeKeyImplementer<K1, K2>
and in fact which itself can be any CompositeKeyImplementer<K1, K2> where the type parameters K1, K2 are a subtype (inclusive) of K
i.e. they are upper bounded by the type parameter K defined by CompositeKeyBasedCreditCardDetailsHolder
Note that the type arguments for K1, K2 are not restricted to the same type. For example: this is possible:
// note the arguments for K1, K2. Both extend Number
CompositeKeyImplementer<Integer, Double> cki =
new CompositeKeyImplementer<Integer, Double>();
// note the argument for K is Number
CompositeKeyBasedCreditCardDetailsHolder<Number, String> cdh =
new CompositeKeyBasedCreditCardDetailsHolder<Number, String>(cki);
I recommend (re-)reading the java tutorials on wildcards, and possibly also Angelika Langer on wildcard type arguments as they have a lot of information on what wildcards are used for and how.

Treemap Constructor

Is the way I am trying to setup my treemap constructor correct?
import java.util.TreeMap ;
public class Table<K extends Comparable<K>, T> { //K = Key, T = Item
TreeMap<K, T> tm;
public Table<K, T> () {
tm = new TreeMap<K, T>();
}
public boolean isEmpty() {
return tm.isEmpty();
}
public int size() {
return tm.size();
}
public void tableInsert(K key, T item) throws TableException {
tm.put(key, item);
}
public boolean tableDelete(K key) {
if (tm.containsKey(key)) {
tm.remove(key);
return true;
} else {
return false;
}
}
public T tableRetreive(K key) {
return tm.get(key);
} //return null if not found
public void printTable() {
TreeMap<K, T> tmclone = (TreeMap<K, T>) tm.clone();
while (!tmclone.isEmpty()) {
System.out.println(tmclone.pollFirstEntry());
}
} //print in search key order
}
I have another class that will create students, and with the put method will insert into a new map tree.. but the compiler says it was expecting a different character. Also, is the proper way to call the constructor is to input TreeMap blah<K,T> = new TreeMap correct?
This code here:
public Table<K, T> () {
tm = new TreeMap<K, T>();
}
needs to change to this:
public Table() {
tm = new TreeMap<K, T>();
}
Otherwise all good.
If you are asking if the initialization in the constructor is functional(?), yes. You could also make an initializer above the constructor like this:
public class Table <K extends Comparable<K>, T> { //K = Key, T = Item
TreeMap<K,T> tm;
{ //Initializer
tm = new TreeMap<K,T>();
}
public Table<K,T>() {
//Your constructor here
}
//Other methods...
}
As far as the second part of your question, there is far too little based on your description to diagnose the issue without seeing some more code and getting more details.

How is having a wrapper class equals composition as described Joshua Bloch?

I am reading the book effective java by Joshua Bloch. on the item 16 of "favor composition over inheritance", he gives an example of using HashSet and querying how many elements have been added since it was created(not to be confused with current size, which goes down when an element is removed). he provided the following code and here the getAddCount return 6, which I can understand. This should return 3 actually. (this is because HashSet's addAll method is implemented on top of its add method)
import java.util.HashSet;
public class InstrumentedHashSet<E> extends HashSet<E> {
// The number of attempted element insertions
private int addCount = 0;
public InstrumentedHashSet() {
}
public InstrumentedHashSet(int initCap, float loadFactor) {
super(initCap, loadFactor);
}
#Override
public boolean add(E e) {
addCount++;
return super.add(e);
}
#Override
public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c);
}
public int getAddCount() {
return addCount;
}
public static void main(String[] args) {
InstrumentedHashSet<String> s = new InstrumentedHashSet<String>();
s.addAll(Arrays.asList("Snap", "Crackle", "Pop"));
System.out.println(s.getAddCount());
}
}
Now he explains a way to fix this, using wrapper classes (composition and forwarding). here is where I am having hard time to understand. he provides the following two classes
public class ForwardingSet<E> implements Set<E> {
private final Set<E> s;
public ForwardingSet(Set<E> s) {
this.s = s;
}
public void clear() {
s.clear();
}
public boolean contains(Object o) {
return s.contains(o);
}
public boolean isEmpty() {
return s.isEmpty();
}
public int size() {
return s.size();
}
public Iterator<E> iterator() {
return s.iterator();
}
public boolean add(E e) {
return s.add(e);
}
public boolean remove(Object o) {
return s.remove(o);
}
public boolean containsAll(Collection<?> c) {
return s.containsAll(c);
}
public boolean addAll(Collection<? extends E> c) {
return s.addAll(c);
}
public boolean removeAll(Collection<?> c) {
return s.removeAll(c);
}
public boolean retainAll(Collection<?> c) {
return s.retainAll(c);
}
public Object[] toArray() {
return s.toArray();
}
public <T> T[] toArray(T[] a) {
return s.toArray(a);
}
#Override
public boolean equals(Object o) {
return s.equals(o);
}
#Override
public int hashCode() {
return s.hashCode();
}
#Override
public String toString() {
return s.toString();
}
}
AND
import java.util.*;
public class InstrumentedSet<E> extends ForwardingSet<E> {
private int addCount = 0;
public InstrumentedSet(Set<E> s) {
super(s);
}
#Override
public boolean add(E e) {
addCount++;
return super.add(e);
}
#Override
public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c);
}
public int getAddCount() {
return addCount;
}
public static void main(String[] args) {
InstrumentedSet<String> s = new InstrumentedSet<String>(
new HashSet<String>());
s.addAll(Arrays.asList("Snap", "Crackle", "Pop"));
System.out.println(s.getAddCount());
}
}
how this works? In the main method, I create an instance of HashSet and using addAll method, I add all the elements of list. but the HashSet invokes its addAll method (which in turn uses its add method), which should be the same as in the first in correct example and I should get value of 6, however this gives me 3.
In
public class InstrumentedHashSet<E> extends HashSet<E> {
you're adding directly to the HashSet because the addAll() is delegating to the super implementation
InstrumentedHashSet<String> s = new InstrumentedHashSet<String>();
s.addAll(Arrays.asList("Snap", "Crackle", "Pop"));
System.out.println(s.getAddCount());
The addAll() internally calls add() which defers to your #Override implementation of add() because of polymorphism
#Override
public boolean add(E e) {
addCount++;
return super.add(e);
}
that increments the count and prints 6 (3 + 1 + 1 + 1).
In
public class InstrumentedSet<E> extends ForwardingSet<E> {
you are adding to
private final Set<E> s;
because the addAll() is delegating to it, so
public static void main(String[] args) {
InstrumentedSet<String> s = new InstrumentedSet<String>(
new HashSet<String>());
s.addAll(Arrays.asList("Snap", "Crackle", "Pop"));
System.out.println(s.getAddCount());
}
and prints 3. Here the add() is being called on the Set<E> s, not on your instance.
The conclusion is that if you are inheriting, you need to understand the side-effects. Do the super method calls invoke any other method calls internally? If so, you need to act appropriately.
Inheritance (start from bottom)
s.add() // s is your InstrumentedHashSet instance, because of polymorphism (inheritance), this adds to the count
this.add() // this is the internal call inside the HashSet#addAll()
super.addAll(...) // this calls the HashSet implementation of addAll which calls add() internally
s.addAll(Arrays.asList("Snap", "Crackle", "Pop")); // s is your InstrumentedHashSet instance
Composition
this.add() // this is the internal call to add() inside the Set implementation
s.addAll() // s is the Set<E> instance
super.addAll(...) // this calls the ForwardingSet implementation of addAll()
s.addAll(Arrays.asList("Snap", "Crackle", "Pop")); // s is your InstrumentedSet instance
InstrumentedSet#getAddCount() returns 6 because the size of the array (3) is added twice!
//InstrumentedSet
public boolean addAll(Collection<? extends E> c) {
addCount += c.size(); //here
return super.addAll(c); //and here!
}
super.addAll(c); calls the add() Method.
More detailed:
InstrumentedSet#addAll -> ForwardingSet#addAll (because of super.addAll) -> HashSet#addAll() (because this is what you give it in the main) -> InstrumentedSet#add (because of polymorphism)
If you want a fix: remove addCount += c.size();
InstrumentedSet#addAll returns 3 because it calls this:
InstrumentedSet#addAll() (adds 3) -> ForwardingSet#addAll (because of super) -> HashSet#addAll (because forwardingset has a field of type HashSet) -> HashSet#add

Categories

Resources