HiveBrain v1.2.0
Get Started
← Back to all entries
snippetjavaMinor

Can we now `implement Singleton` using Java 8?

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
canimplementjavanowsingletonusing

Problem

After my success with extending enum - I thought I would try to make Singleton creation as simple as possible since Java-8 makes Multitons comparatively easy.

My aim was to create a class which is a Singleton solely by the simple fact that it implements a Singleton interface. Unfortunately there is a little baggage required but it doesn't look too bad. I don't think I would use it in a real setting as it still doesn't provide the level of simplicity I am looking for but as a prototype it seems to work.

Can anyone see any ways of simplifying it further? I would like to aim for as little as possible in the TestSingleton class.

I would also like to see a way of forcing, or even pre-defining the getInstance() method but that looks impossible to me.

Here's the Multiton it uses:

```
/**
* A classic Multiton making use of lambdas to delay the create.
*
*
* @author OldCurmudgeon
* @param - The type that can create.
*/
public class Multiton {

/**
* The storage.
*
* Store only Object because they must all be different types.
*/
private final ConcurrentMap multitons = new ConcurrentHashMap<>();

/**
* The keys must be capable of creating their values.
*/
public interface Creator {

public abstract Object create();
}

/**
* The getter.
*
* @param - The type of the value that should be returned.
* @param key - The unique key behind which the value is to be stored.
* @param type - The class of the value returned.
* @return - The value stored (and perhaps created) behind the key.
*/
public V get(final K key, Class type) {
// Has it run yet?
Object o = multitons.get(key);
// A null value means not yet.
if (o == null) {
// Use a lambda to only do the create if it is still absent.
o = multitons.computeIfAbsent(key, k -> k.create());
}
return type.cast(o);
}

// Some simple test

Solution

ConcurrentMap

As was pointed out in the comments: in the Multiton.get() method:

public  V get(final K key, Class type) {
    // Has it run yet?
    Object o = multitons.get(key);
    // A null value means not yet.
    if (o == null) {
        // Use a lambda to only do the create if it is still absent.
        o = multitons.computeIfAbsent(key, k -> k.create());
    }
    return type.cast(o);
}


There's no reason to have the double-check. The computeIfAbsent already does that:

public  V get(final K key, Class type) {
    return type.cast(multitons.computeIfAbsent(key, k -> k.create()));
}


Generics

I have taken, and played with your code. It became apparent that there is no reason to have a generic type on the Multiton class. You only really use it as Multiton, and that's the broadest sense of the Generics anyway. The more I played with it, though, the more I realized that your logic is in the wrong place. The right place would be to have more in the Multiton, and to get rid of the Singleton class entirely.... There is no reason for the Singleton interface. It creates a lot of ugliness anyway, because the interface cannot have private static members, so the Multiton instance needs to be non-private, making it a resource leak.

Consider the following Multiton:

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Supplier;

/**
 * A classic Multiton making use of lambdas to delay the create.
 *
 *
 * @author OldCurmudgeon
 * @param  - The type that can create.
 */
public class Multiton {

    /**
     * The keys must be capable of creating their values.
     */
    public interface Creator {
        public Object create();
        public V cast(Object value);
    }

    public static abstract class TypedCreator implements Creator {
        private final Class tclass;
        public TypedCreator(Class tclass) {
            this.tclass = tclass;
        }

        @Override
        public final A cast(Object value) {
            return tclass.cast(value);
        }
    }

    public static final class SuppliedCreator extends TypedCreator {
        private final Supplier supplier;
        public SuppliedCreator(Class tclass, Supplier supplier) {
            super(tclass);
            this.supplier = supplier;
        }

        @Override
        public Object create() {
            return supplier.get();
        }
    }

    /**
     * The storage.
     *
     * Store only Object because they must all be different types.
     */
    private final ConcurrentMap, Object> multitons = new ConcurrentHashMap<>();

    /**
     * The getter.
     *
     * @param  - The type of the value that should be returned.
     * @param key - The unique key behind which the value is to be stored.
     * @return - The value stored (and perhaps created) behind the key.
     */
    public > V get(final C key) {
        return key.cast(multitons.computeIfAbsent(key, k -> k.create()));
    }
}


Of particular importance, note:

  • there's no generic type for the Multiton. This is expected. All it needs is the public get() method, and the generics for that method are determined by the call arguments.... which is point 2....



  • the generic type of the values are declared on the Creator (which is also the key). In other words, each key knows what the type of the value should be. This means that the generic type is 'recorded' at create time, rather than retrieve time. There's no need to pass a class in to the get() call since the class is actually part of the Creator.



  • I created 2 'helper' classes that are Creator instances. These classes TypedCreator and SuppliedCreator are simpler ways to actually create anonymous, or Java-8 based Creators.



Note that I can't see a convenient way to use a Singleton interface. The interface depends on a shared instance of the Multiton, so as a result, there needs to be some static data in the Singleton. As a consequence, an interface is the wrong language structure to use. An abstract class may be better. Still, the usage examples for the Multiton above are simple (in some common 'utility' class, or somewhere shared):

public static final Multiton MULTITON = new Multiton();


Then, whenever you have a need for a Singleton:

public static final Multiton.Creator HARDWORK = new Multiton.SuppliedCreator<>(String.class, () -> initializeHardWork());


and then:

String hardwork = MULTITON.get(HARDWORK);


This can be encapsulated in to an abstract Singleton class as:

public abstract class Singleton {
    protected static final Multiton MULTITON = new Multiton();        
}


The Singleton class can then be used in various ways, here's an example:

```
public class TestSingleton extends Singleton {

private static final Multiton.Creator KEY = new Multiton.SuppliedCreator<>(TestSingleton.class, TestSingleton::new);

public static final TestSingleton getInstance() {

Code Snippets

public <V> V get(final K key, Class<V> type) {
    // Has it run yet?
    Object o = multitons.get(key);
    // A null value means not yet.
    if (o == null) {
        // Use a lambda to only do the create if it is still absent.
        o = multitons.computeIfAbsent(key, k -> k.create());
    }
    return type.cast(o);
}
public <V> V get(final K key, Class<V> type) {
    return type.cast(multitons.computeIfAbsent(key, k -> k.create()));
}
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Supplier;

/**
 * A classic Multiton making use of lambdas to delay the create.
 *
 *
 * @author OldCurmudgeon
 * @param <K> - The type that can create.
 */
public class Multiton {

    /**
     * The keys must be capable of creating their values.
     */
    public interface Creator<V> {
        public Object create();
        public V cast(Object value);
    }

    public static abstract class TypedCreator<A> implements Creator<A> {
        private final Class<A> tclass;
        public TypedCreator(Class<A> tclass) {
            this.tclass = tclass;
        }

        @Override
        public final A cast(Object value) {
            return tclass.cast(value);
        }
    }

    public static final class SuppliedCreator<A> extends TypedCreator<A> {
        private final Supplier<A> supplier;
        public SuppliedCreator(Class<A> tclass, Supplier<A> supplier) {
            super(tclass);
            this.supplier = supplier;
        }

        @Override
        public Object create() {
            return supplier.get();
        }
    }

    /**
     * The storage.
     *
     * Store only Object because they must all be different types.
     */
    private final ConcurrentMap<Creator<?>, Object> multitons = new ConcurrentHashMap<>();

    /**
     * The getter.
     *
     * @param <V> - The type of the value that should be returned.
     * @param key - The unique key behind which the value is to be stored.
     * @return - The value stored (and perhaps created) behind the key.
     */
    public <V, C extends Multiton.Creator<V>> V get(final C key) {
        return key.cast(multitons.computeIfAbsent(key, k -> k.create()));
    }
}
public static final Multiton MULTITON = new Multiton();
public static final Multiton.Creator<String> HARDWORK = new Multiton.SuppliedCreator<>(String.class, () -> initializeHardWork());

Context

StackExchange Code Review Q#69594, answer score: 8

Revisions (0)

No revisions yet.