Java Generics with Composite Key and Wildcards - java

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.

Related

Mocking generics and wild card with mockito

I have a class that uses wild cards and generics to return a cache object and I'm trying to mock it but I get the following error:
Unfinished stubbing detected here:
-> at com.demo.MyTestTest.initTests(MyTest.java:232)
E.g. thenReturn() may be missing.
Examples of correct stubbing:
when(mock.isOk()).thenReturn(true);
when(mock.isOk()).thenThrow(exception);
doThrow(exception).when(mock).someVoidMethod();
my generic class is:
public class AsyncCaffeineCacheManager {
private final Map<String, AsyncCache<?, ?>> cacheMap;
#Getter
private final List<String> cacheNames;
private AsyncCaffeineCacheManager(Map<String, AsyncCache<?, ?>> cacheMap, List<String> cacheNames) {
this.cacheMap = cacheMap;
this.cacheNames = cacheNames;
}
#SuppressWarnings("unchecked")
public <K, V> AsyncCache<K, V> getCache(String cacheName) {
return (AsyncCache<K, V>) cacheMap.get(cacheName);
}
......
}
and my test class:
public class MyTest {
#BeforeEach
public void initTests() {
doReturn(new NoOpAsyncCache<Integer, MyValue>("cacheName"))
.when(asyncCaffeineCacheManager.getCache(anyString()));
}
}
I also created a NoOpAsyncCache which is default implementation:
#Getter
public class NoOpAsyncCache<K, V> implements AsyncCache<K, V> {
private final String cacheName;
public NoOpAsyncCache(String cacheName) {
this.cacheName = cacheName;
}
#Override
public #Nullable CompletableFuture<V> getIfPresent(K key) {
return null;
}
#Override
public CompletableFuture<V> get(K key, Function<? super K, ? extends V> mappingFunction) {
return null;
}
#Override
public CompletableFuture<V> get(K key, BiFunction<? super K, ? super Executor, ? extends CompletableFuture<? extends V>> mappingFunction) {
return null;
}
......
}
I also tried to create a real one, but it didn't work.
Would love to hear some ideas.
Turns out it was not an issue with generics.
the when(..) was wrong:
instead of:
.when(asyncCaffeineCacheManager.getCache(anyString()));
it should have been:
.when(asyncCaffeineCacheManager).getCache(anyString());
also NoOpAsyncCache didn't work well for me, had to use a real implementation
doReturn(Caffeine.newBuilder().buildAsync())
.when(asyncCaffeineCacheManager).getCache(anyString());

Java Comparing Generics with Comparable<? super T> from another class

I have a "Schema" and "Field" model in which a Field represents a data type and has methods on how to parse it, and the schema is a collection of fields. I am trying to implement generic comparison, however, I can't get the code to compile and I can't figure out the proper generic scopes. How can I get this to work?
class Field<T extends Comparable<? super T>> {
T parse(String val) {
...
}
}
public class Schema {
Map<Integer, Field<?>> fields;
Field<?> getField(int index){ ... }
}
public class Comparison {
public static <T extends Comparable<? super T>> boolean greaterThan(Field<T> f, String val1, String val2) {
// compiles as expected
return f.parse(val1).compareTo(f.parse(val2)) > 0;
}
public static boolean greaterThan2(Field<?> f, String val1, String val2) {
// does not compile -> required capture of ? super capture of ?, provided capture of ?
return f.parse(val2).compareTo(f.parse(val2));
}
}
public class App {
public static void main(String[] args) {
Schema s = ...
// does not compile, required type Field<T>, provided type Field<capture of ?>
Comparison.greaterThan(s.getField(0), "val1", "val2");
// compiles
Comparison.greaterThan2(s.getField(0), "val1","val2");
}
}
Below code compiles in 1.8
import java.util.Map;
class Field<T extends Comparable<? super T>> {
T parse(String val) {
return null;
}
}
class Schema {
Map<Integer, Field<?>> fields;
Field<?> getField(int index){ return null; }
}
class Comparison {
public static <T extends Comparable<? super T>> boolean greaterThan(Field<T> f, String val1, String val2) {
// compiles as expected
return f.parse(val1).compareTo(f.parse(val2)) > 0;
}
}
public class App {
public static void main(String[] args) {
Schema s = new Schema();
//compiles ok
Comparison.greaterThan(s.getField(0), "val1", "val2");
}
}

Java - unmodifiable key set map

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();
}
}

Lambda and Generics: Why Does This Compile?

This is a shortened sample of my code to prove my point:
public class Tmp {
static class X {
void setStr(String blah) {
}
String getStr() {
return null;
}
}
public void test() {
createCheck(X::getStr, ""); // supposed to compile
createCheck(X::getStr, 123); // rather not: int isn't String
}
private <T, V> BiPredicate<T, String> createCheck(Function<T, V> func, V value) {
return new BiPredicate<T, String>() {
#Override
public boolean test(T t, String ref) {
assertThat(func.apply(t))
.as(ref)
.isEqualTo(value);
return true;
}
};
}
}
IMHO, the compiler should see that V in createCheck() should be String coming from the getter-function, which is why it should complain about the int. Or the other way around.
So why does this compile?
Your signature is:
private <T, V> BiPredicate<T, String> createCheck(Function<T, V> func, V value) {
You have no bounds on T or V so absolutely any values of T or V would compile.
You return a BiPredicate<T, String> however, so the isEqualTo method takes the String ref and compares its equality to the Object value - this is also perfectly legal as I suppose isEqualTo takes an Object to mimic the behaviour of Object.equals.
In your example:
createCheck(X::getStr, 123);
V is simply Object as that is the tightest type bound that can apply to String and Integer.
Did you perhaps mean:
private <T> BiPredicate<T, String> createCheck(Function<T, String> func, String value) {

Using generics with collection of enum classes implementing same interface

I am trying to do reverse lookup on few enum classes implementing same Field interface by iterating through list of Classes using Guava's Maps.uniqueIndex:
Field valueOfSearchName = null;
for (final Class<? extends Enum<?>> clazz : ImmutableList.of(
EntityField.class,
AddressField.class,
PersonFunctionType.class)) {
valueOfSearchName = Fields.valueOfSearchName(clazz, term.field()); // error
if (valueOfSearchName != null) {
// do something...
break;
}
}
I don't want to repeat same code (for making index and doing lookup) in all enum classes, so I use helper static class Fields containing Fields.valueOfSearchName method:
public static <E extends Enum<E> & Field> Field valueOfSearchName(
final Class<E> clazz, final String searchName) {
// TODO: cache the index
final ImmutableMap<String, E> index = Maps.uniqueIndex(
EnumSet.allOf(clazz), GET_SEARCH_NAME_FUNCTION);
return index.get(searchName);
}
Unfortunately, Eclipse shows an error:
Bound mismatch:
The generic method valueOfSearchName(Class<E>, String) of type Fields is not
applicable for the arguments (Class<capture#1-of ? extends Enum<?>>, String).
The inferred type capture#1-of ? extends Enum<?> is not a valid substitute
for the bounded parameter <E extends Enum<E> & Field>
The problem is Class<? extends Enum<?>> clazz in for-each loop (not matching Field), but I don't know how to deal with this case (obviously I cannot add & Field to clazz).
Consider Class<? extends List<?>. Class<? extends List<?> has two wildcards whereas <E extends List<E>> Class<E> only has generic parameter. The former will admit Class<ArrayList<String>>. So without doing something extra special for enums, the types are not compatible.
How to fix? An extra layer of indirection!
public final class MetaEnum<E extends Enum<E>> {
private final E clazz;
public static <E extends Enum<E>> MetaEnum<E> of(E clazz) {
return clazz;
}
private MetaEnum(E clazz) {
this.clazz = clazz;
}
public E clazz() {
return clazz;
}
// ...
}
for (final MetaEnum<?> meta : ImmutableList.of(
MetaEnum.of(EntityField .class),
MetaEnum.of(AddressField .class),
MetaEnum.of(PersonFunctionType.class)
)) {
Field valueOfSearchName = Fields.valueOfSearchName(
meta.clazz(), term.field()
);
...
(Usual Stack Overflow dislaimer: Not so much as attempted to compile.)
Inspired by Tom Hawtin's answer I created wrapper class holding Classes, but only those with signature <E extends Enum<E> & Field>:
public final static class FieldEnumWrapper<E extends Enum<E> & Field> {
private final Class<E> clazz;
private final ImmutableMap<String, E> index;
public static <E extends Enum<E> & Field>
FieldEnumWrapper<E> of(final Class<E> clazz) {
return new FieldEnumWrapper<E>(clazz);
}
private FieldEnumWrapper(final Class<E> clazz) {
this.clazz = clazz;
this.index = Maps.uniqueIndex(
EnumSet.allOf(clazz), new Function<E, String>() {
#Override
public String apply(final E input) {
return input.searchName();
}
});
}
public Class<E> clazz() {
return clazz;
}
public Field valueOfSearchName(final String searchName) {
return index.get(searchName);
}
}
Now:
for (final FieldEnumWrapper<?> fieldEnum : ImmutableList.of(
FieldEnumWrapper.of(EntityField.class),
FieldEnumWrapper.of(AddressField.class),
FieldEnumWrapper.of(PersonFunctionType.class))) {
valueOfSearchName = fieldEnum.valueOfSearchName("POD_I_OS_PARTNER");
// ...
is type-safe and inappropriate usage of FieldEnumWrapper's static factory:
FieldEnumWrapper.of(NotEnumAndFieldClass.class)
generates compile error.
Moreover, valueOfSearchName is now method of FieldEnumWrapper what make more sense that helper class.
maybe something like this:
import java.util.*;
class N {
static int n;
}
interface HasField {
int getField();
}
enum Color implements HasField {
r, g, b;
public int getField() {
return field;
}
private int field = N.n++;
}
enum Day implements HasField {
m, t, w, th, f, sa, su;
public int getField() {
return field;
}
private int field = N.n++;
}
class Helper {
Helper(Set<HasField> set) {
for (HasField hasField : set)
if (hasField instanceof Enum) {
Enum<?> e = (Enum<?>) hasField;
for (Object o : e.getDeclaringClass().getEnumConstants()) {
map.put(((HasField) o).getField(), (Enum<?>) o);
}
} else
throw new RuntimeException(hasField + " is not an enum!");
}
final Map<Integer, Enum<?>> map = new TreeMap<Integer, Enum<?>>();
}
public class Main {
public static void main(String[] args) {
Set<HasField> set = new LinkedHashSet<HasField>();
set.add(Color.r);
set.add(Day.m);
Helper helper = new Helper(set);
for (int i = 0; i < N.n; i++)
System.out.println(i + " " + helper.map.get(i));
}
}

Categories

Resources