6

I want to implement a simple Cache interface:

public interface Cache {
    Object get(Object key);
    Object put(Object key, Object value);
    void clear();
}

I realized it's part of interface java.util.Map. So objects like HashMap should be able to be be passed to functions needing a Cache object.

But on the other hand I don't want to make my own Cache class implement the whole Map interface because I don't really need other methods other than these three.

Java is not a duck-typed language, so what's the best practice in this situation?

Boann
  • 48,794
  • 16
  • 117
  • 146
Ken
  • 163
  • 1
  • 2
  • 8
  • 2
    Implement all methods, because you have to; make the methods you aren't planning to invoke throw `UnsupportedOperationException`. – Andy Turner May 08 '18 at 18:10
  • 3
    The only purpose of Interfaces is to **enforce** a class implements certain methods. – L.Spillner May 08 '18 at 18:11
  • If you only plan to support some methods only you can throw `UnsupportedOperationException`. However, usually you can do much better. How about splitting the interface into several sub-interfaces and then only implement what you really support. – Zabuzard May 08 '18 at 18:12
  • 1
    add deprecited flag to the method you are not planning to invoke – Thecarisma May 08 '18 at 18:15
  • I think this needs some clarification. If i understand your question, you are creating a the Cache interface in your snippet. Are you asking if a Map object would meet the definition? – Stephan May 08 '18 at 18:18
  • 1
    @Stephan Yes. What I can do now is to make a wrapper Cache class which accept a Map object as single parameter. And implements all there method by calling the internal Map object. – Ken May 08 '18 at 18:22
  • 2
    I would personally make my own `Cache` interface and implement that. You shouldn't have a class implement `Map` unless it's a map. – markspace May 08 '18 at 18:27
  • I think OP should reclarify his idea, or unrelated answer will be posted – Mạnh Quyết Nguyễn May 08 '18 at 18:38
  • @Ken ok take a look at my answer. Please edit your question to include a code snippet of your current approach just to help clarify the issue for future readers. – Stephan May 08 '18 at 18:41
  • @AndyTurner that's a good approach, what is a bit painful that I will only find out about that at runtime :( – Eugene May 08 '18 at 22:00
  • @Eugene yep. Maybe better just to implement that interface properly, eh? – Andy Turner May 08 '18 at 22:00
  • @AndyTurner I was more thinking into creating a private inner class that implements Map interface and only expose methods that are needed, while the others are private. Not entirely sure I make sense – Eugene May 08 '18 at 22:16

3 Answers3

2

I think you can wrapper Map inside the Implementation of Cache class

class CacheImpl<K, V> implements Cache<K, V> {
    Map<K, V> cacheMap;

    CacheImpl() {
        this(new LinkedHashMap<>());
    }

    CacheImpl(Map<K,V> cacheMap) {
        this.cacheMap = cacheMap;
    }

    @Override
    public V get(K key) {
        return cacheMap.get(key);
    }

    @Override
    public V put(K key, V value) {
        return cacheMap.put(key, value);
    }

    @Override
    public void clear() {
        cacheMap.clear();
    }
}

I have added the example with the Generic but you are always free to Use Object as the key and value.

So, in my example to make it thread safe We can we ConcurrentHashMap which is already thread safe and well implemented. I will suggest having a Factory class to create the cache object like below.

class CacheImplFactory {
    public static <K, V> Cache<K, V> newSimpleCache() {
        return new CacheImpl<K, V>();
    }

    public static <K, V> Cache<K, V> newSynchronizedCache() {
        return new CacheImpl<K, V>(new ConcurrentHashMap<>());
    }

    public static <K, V> Cache<K, V> newWeakCache() {
        return new CacheImpl<K, V>(new WeakHashMap<>());
    }
}
Amit Bera
  • 7,075
  • 1
  • 19
  • 42
  • 1
    The map should be passed in rather than instantiated in the constructor. Otherwise, great answer. – shmosel May 08 '18 at 18:32
  • 1
    Good, but no thread safety considerations at all. This class is a memory leak waiting to happen. HashMap will work, but I'd prefer something that kicked out long lived objects using LRU in LinkedHashMap. https://stackoverflow.com/questions/224868/easy-simple-to-use-lru-cache-in-java – duffymo May 08 '18 at 18:35
  • @shmosel Thanks!!! Yes, you are right it will make loosely coupled code and testable code. I will improve my answer. – Amit Bera May 08 '18 at 18:37
  • 2
    I like the idea of a wrapper class. However, personally I would implement it as a static method of `Cache` returning an anonymous class. – Boann May 08 '18 at 18:37
  • Don't use *raw* generics. Change constructor parameter to `Map`. You should also add `@Override` annotations to the methods implementing the interface. – Andreas May 08 '18 at 18:47
  • @duffymo thanks for the suggestion. I have edited my answer and added a thread-safe approach. – Amit Bera May 08 '18 at 19:05
  • @Andreas Thanks for the suggestion. I have editted my answer. – Amit Bera May 08 '18 at 19:05
2

There are no such features in java.

I think the best you can do is to create a wrapper of HashMap as you concern.

An interface is a contract. The implemented class should fullfill the contract by implement the virtual / abstract methods.

Your CacheImpl only fullfills the contract with Cache interface. Meanwhile HashMap only fullfills the contract with Map interface.

The Cache and Map interface share method with same signatures but they requires different contract / implementation.

You really can not determine if an interface contains the method Object put(Object key, Object value); is compatible with your Cache interface or not, because you don't understand which contract that interface provide.

So in Java, unless they provide semantic to create kind of "parent" interface of existing interface, you cannot do that.

Mạnh Quyết Nguyễn
  • 17,677
  • 1
  • 23
  • 51
1

If I understand your question, you're asking how to back the interface with an existing java class like HashMap.

You will have to create your own implementation that implements your interface, and extends HashMap.

The Interface methods will delegate the action to the HashMap. In this manner, your custom class will meet the interface needs for any Method with a signature of your interface.

A working example I threw together to showcase how to do what you're asking with Generics:

The Cache Interface:

package interfaces;

public interface Cache<K,V> {
    V get(K key);
    V put(K key, V value);
    void clear();
}

The Implementation:

package classes;

import java.util.HashMap;
import interfaces.Cache;

public class MyCache<K,V> extends HashMap<K,V> implements Cache<K,V> {

    private static final long serialVersionUID = 1L;

    @Override
    public V get(Object key) {
        return super.get(key);
    }

    @Override
    public V put(K key, V value) {
        return super.put(key, value);
    }

    @Override
    public void clear() {
        super.clear();
    }
}

You can then use it like so:

import classes.MyCache;
import interfaces.Cache;

public class DoStuff {

    private Cache<String,Integer> cache;

    public void initCache() {
        //This works
        //Since MyCache implements Cache
        MyCache<String,Integer> mCache=new MyCache<String,Integer>();
        setCache(mCache);

        //This will not
        //Since HashMap does not implement Cache
        HashMap<String,Integer> mMap=new HashMap<String,Integer>();
        setCache(mMap);
    }

    public void setCache(Cache<String,Integer> c) {
        cache=c;
    }

    public Cache<String,Integer> getCache() {
        return cache;
    }
}
Stephan
  • 666
  • 8
  • 23