Java Hashmap хранит только значение определенного типа в ключе - PullRequest
0 голосов
/ 20 декабря 2018

Я смотрю на создание класса Hashmap, который позволяет мне хранить ключи и значения.Однако значение может быть сохранено только в том случае, если оно соответствует определенному типу, а этот тип зависит от значения времени выполнения ключа.Например, если ключ EMAIL(String.class), то сохраненное значение должно иметь тип String.

. У меня есть следующий пользовательский ENUM:

public enum TestEnum {
    TDD,
    BDD,
    SHIFT_RIGHT,
    SHIFT_LEFT;
}

Я создал следующий класс:

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

public class test {

    private static final Map<ValidKeys, Object> sessionData = new HashMap<>();

    public enum ValidKeys {
        EMAIL(String.class),
        PASSWORD(String.class),
        FIRST_NAME(String.class),
        LAST_NAME(String.class),
        CONDITION(TestEnum.class);

        private Class type;
        private boolean isList;
        private Pattern pattern;

        ValidKeys(Class<?> type, boolean isList) {
            this.type = type;
            this.isList = isList;
        }

        ValidKeys(Class<?> type) {
            this.type = type;
        }
    }

    public <T> void setData(ValidKeys key, T value) {
        sessionData.put(key,value);
    }


    public Object getData(ValidKeys key) {
        return key.type.cast(sessionData.get(key));
    }


    public static void main(String[] args) {
        test t = new test();
        t.setData(ValidKeys.CONDITION,TestEnum.TDD);
        System.out.println(t.getData(ValidKeys.CONDITION));
    }
}

Я хотел бы использовать такие методы, как setData и getData и сохранять значения в sessionData.Кроме того, я хочу убедиться, что значение является списком объектов, тогда они также хранятся правильно.

Я также изо всех сил стараюсь избегать toString, в основном мне нужен универсальный getData, который может работать без приведения типов.

1 Ответ

0 голосов
/ 20 декабря 2018

Существует определенный паттерн, который я видел, использованный для такого рода вещей, который является вариантом паттерна Bloch's Typesafe Heterogenous Container.Я не знаю, есть ли у него имя само по себе или нет, но из-за отсутствия лучшего имени я назову его Typesafe Enumerated Lookup Keys.

В принципе, проблема, с которой я столкнулся, возникает вВ различных контекстах вам нужен динамический набор пар ключ / значение, где определенное подмножество ключей «хорошо известно» с предопределенной семантикой.Кроме того, каждый ключ связан с определенным типом.

«Очевидным» решением является использование перечисления.Например, вы могли бы сделать:

public enum LookupKey { FOO, BAR }

public final class Repository {
    private final Map<LookupKey, Object> data = new HashMap<>();

    public void put(LookupKey key, Object value) {
        data.put(key, value);
    }

    public Object get(LookupKey key) {
        return data.get(key);
    }
}

Это работает просто отлично, но очевидным недостатком является то, что теперь вам нужно разыгрывать повсюду.Например, предположим, что вы знаете, что LookupKey.FOO всегда имеет значение String, а LookupKey.BAR всегда имеет значение Integer.Как вы применяете это?В этой реализации вы не можете.

Также: в этой реализации набор ключей фиксируется перечислением.Вы не можете добавлять новые во время выполнения.Для некоторых приложений это преимущество, но в других случаях вы действительно хотите разрешить новые ключи в некоторых случаях.

Решение обеих этих проблем в основном одно и то же: сделать LookupKey первоклассным объектом, а не просто перечисление.Например:

/**
 * A key that knows its own name and type.
 */
public final class LookupKey<T> {
    // These are the "enumerated" keys:
    public static final LookupKey<String> FOO = new LookupKey<>("FOO", String.class);
    public static final LookupKey<Integer> BAR = new LookupKey<>("BAR", Integer.class);

    private final String name;
    private final Class<T> type;

    public LookupKey(String name, Class<T> type) {
        this.name = name;
        this.type = type;
    }

    /**
     * Returns the name of this key.
     */
    public String name() {
        return name;
    }

    @Override
    public String toString() {
        return name;
    }

    /**
     * Cast an arbitrary object to the type of this key.
     * 
     * @param object an arbitrary object, retrieved from a Map for example.
     * @throws ClassCastException if the argument is the wrong type.
     */
    public T cast(Object object) {
        return type.cast(object);
    }

    // not shown: equals() and hashCode() implementations
}

Это дает нам большую часть пути уже там.Вы можете ссылаться на LookupKey.FOO и LookupKey.BAR, и они ведут себя так, как вы ожидаете, но они также знают соответствующий тип поиска.И вы также можете определить свои собственные ключи, создав новые экземпляры LookupKey.

. Если мы хотим реализовать некоторые приятные перечислимые возможности, такие как статический метод values(), нам просто нужно добавить реестр.В качестве бонуса нам даже не нужны equals() и hashCode(), если мы добавляем реестр, поскольку теперь мы можем просто сравнивать ключи поиска по идентификатору.

Вот как в итоге выглядит класс:

/**
 * A key that knows its own name and type.
 */
public final class LookupKey<T> {
    // This is the registry of all known keys.
    // (It needs to be declared first because the create() function needs it.)
    private static final Map<String, LookupKey<?>> knownKeys = new HashMap<>();

    // These are the "enumerated" keys:
    public static final LookupKey<String> FOO = create("FOO", String.class);
    public static final LookupKey<Integer> BAR = create("BAR", Integer.class);


    /**
     * Create and register a new key. If a key with the same name and type
     * already exists, it is returned instead (Flywheel Pattern).
     *
     * @param name A name to uniquely identify this key.
     * @param type The type of data associated with this key.
     * @throws IllegalStateException if a key with the same name but a different
     *     type was already registered.
     */
    public static <T> LookupKey<T> create(String name, Class<T> type) {
        synchronized (knownKeys) {
            LookupKey<?> existing = knownKeys.get(name);
            if (existing != null) {
                if (existing.type != type) {
                    throw new IllegalStateException(
                            "Incompatible definition of " + name);
                }
                @SuppressWarnings("unchecked")  // our invariant ensures this is safe
                LookupKey<T> uncheckedCast = (LookupKey<T>) existing;
                return uncheckedCast;
            }
            LookupKey<T> key = new LookupKey<>(name, type);
            knownKeys.put(name, key);
            return key;
        }
    }

    /**
     * Returns a list of all the currently known lookup keys.
     */
    public static List<LookupKey<?>> values() {
        synchronized (knownKeys) {
            return Collections.unmodifiableList(
                    new ArrayList<>(knownKeys.values()));
        }
    }

    private final String name;
    private final Class<T> type;

    // Private constructor. Only the create method should call this.
    private LookupKey(String name, Class<T> type) {
        this.name = name;
        this.type = type;
    }

    /**
     * Returns the name of this key.
     */
    public String name() {
        return name;
    }

    @Override
    public String toString() {
        return name;
    }

    /**
     * Cast an arbitrary object to the type of this key.
     * 
     * @param object an arbitrary object, retrieved from a Map for example.
     * @throws ClassCastException if the argument is the wrong type.
     */
    public T cast(Object object) {
        return type.cast(object);
    }
}

Теперь LookupKey.values() работает более или менее так же, как перечисление.Вы также можете добавить свои собственные ключи, и values() вернет их позже:

LookupKey<Double> myKey = LookupKey.create("CUSTOM_DATA", Double.class);

Как только у вас появится этот класс LookupKey, вы теперь можете реализовать типизированное хранилище, которое использует эти ключи для поиска:

/**
 * A repository of data that can be looked up using a {@link LookupKey}.
 */
public final class Repository {
    private final Map<LookupKey<?>, Object> data = new HashMap<>();

    /**
     * Set a value in the repository.
     *
     * @param <T> The type of data that is being stored.
     * @param key The key that identifies the value.
     * @param value The corresponding value.
     */
    public <T> void put(LookupKey<T> key, T value) {
        data.put(key, value);
    }

    /**
     * Gets a value from this repository.
     *
     * @param <T> The type of the value identified by the key.
     * @param key The key that identifies the desired value.
     */
    public <T> T get(LookupKey<T> key) {
        return key.cast(data.get(key));
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...