2015-07-15 2 views
2

Есть ли способ использовать какой-либо общий метод для унификации ниже аналогичных методов?Как использовать дженерики для подобных методов с разными классами

public ClassA getInstanceA(String key) 
{ 
    if (instanceAMap.contains(key)) 
    { 
     return instanceAMap.get(key); 
    } 

    ClassA result = new ClassA(); 
    instanceAMap.put(key, result); 
    return result; 
} 

public ClassB getInstanceB(String key) 
{ 
    if (instanceBMap.contains(key)) 
    { 
     return instanceBMap.get(key); 
    } 

    ClassB result = new ClassB(); 
    instanceBMap.put(key, result); 
    return result; 
} 

public ClassC getInstanceC(String key) 
{ 
    if (instanceCMap.contains(key)) 
    { 
     return instanceCMap.get(key); 
    } 

    ClassC result = new ClassC(); 
    instanceCMap.put(key, result); 
    return result; 
} 

Итак, я хотел бы иметь только один метод для всех классов. В C++ он может быть завернут в макрос, но как его можно изящно сделать на Java.

+1

Уверен, что все эти классы имеют доступный конструктор no-arg? – Seelenvirtuose

+1

любая причина, почему ваш класс имеет 3 разных карты? можете ли вы реорганизовать это и иметь одну карту? – user902383

+0

@Seelenvirtuose Нет, классы могут иметь любые конструкторы, это просто пример. –

ответ

1

Из-за стирания типов общих типов вы не можете создать новый экземпляр родового типа во время выполнения.

Если вы используете Java 8, вы можете использовать метод поставщика, который даст вам новый объект. Это делает его ответственным за создание объекта. Вы можете сделать это:

Map<String, Object> map = new HashMap<>(); 

public <T> T getInstance(String key, Supplier<T> objectSupplier) { 
    if (instanceAMap.containsKey(key)) { 
     return (T) map.get(key); 
    } 

    T result = objectSupplier.get(); 
    map.put(key, result); 
    return result; 
} 

Вы можете вызвать метод как это:

String s = getInstance("String",() -> "Hello World!"); 

Из-за неконтролируемого гипсе, не делают это впоследствии:

Integer i = getInstance("String",() -> 5); 

Это будет вызвать ClassCastException.

Редактировать: Вот версия, которая создает новую карту для каждого типа.

Map<Class<?>, Map<String, ?>> maps = new HashMap<>(); 

public <T> T getInstance(String key, Class<T> type, Supplier<T> objectSupplier) { 

    Map<String, T> map; 
    if (maps.containsKey(type)) { 
     map = (Map<String, T>) maps.get(type); 
    } else { 
     map = new HashMap<>(); 
     maps.put(type, map); 
    } 

    if (map.containsKey(key)) { 
     return map.get(key); 
    } 

    T result = objectSupplier.get(); 
    map.put(key, result); 
    return result; 
} 
+0

К сожалению, каждый из этих методов работает и на другой карте. Это также необходимо решить. – Seelenvirtuose

+0

О, не видел этого. Думаю, нам нужна карта как параметр, а затем. – marstran

+0

Кроме того, в пред-Java-8 вы можете использовать токен типа: ' T getInstance (String key, Class type)'. Реализация метода также может использовать этот тип для создания экземпляров: 'type.newInstance()'. – Seelenvirtuose

0

Один метод решения требует типа должны быть переданы в:

private Map<Class<?>, Map<String, Object>> instanceMaps; 

public <T> T getInstance(String key, 
         Class<T> instanceType) { 
    if (instanceMaps == null) { 
     instanceMaps = new HashMap<>(); 
     instanceMaps.put(ClassA.class, instanceAMap); 
     instanceMaps.put(ClassB.class, instanceBMap); 
     instanceMaps.put(ClassC.class, instanceCMap); 
    } 

    Map<String, Object> instanceMap = instanceMaps.get(instanceType); 
    if (instanceMap == null) { 
     throw new IllegalArgumentException("Unknown type: " + instanceType); 
    } 

    Object value = instanceMap.get(key); 
    if (value == null) { 
     try { 
      instanceMap.put(key, value = instanceType.newInstance()); 
     } catch (ReflectiveOperationException e) { 
      throw new IllegalArgumentException(e); 
     } 
    } 
    return instanceType.cast(value); 
} 

instanceAMap, instanceBMap и instanceCMap все должны быть объявлены как Map<String, Object>. Невозможно извлечь разные конкретные типы из одной карты или коллекции.

Лично я бы не пытался принудительно использовать один метод, но использовал бы перегруженные методы, которые можно было бы реорганизовать в один частный общий метод. Это позволяет instanceAMap быть объявлен как Map<String, ClassA> и т. Д. Для примераBMap и instanceCMap.

private <T> T getInstance(String key, 
          Map<String, T> instanceMap, 
          Supplier<T> constructor) { 
    return instanceMap.computeIfAbsent(key, k -> constructor.get()); 
} 

public ClassA getInstanceA(String key) { 
    return getInstance(key, instanceAMap, ClassA::new); 
} 

public ClassB getInstanceB(String key) { 
    return getInstance(key, instanceBMap, ClassB::new); 
} 

public ClassC getInstanceC(String key) { 
    return getInstance(key, instanceCMap, ClassC::new); 
} 

В версиях старше Java 8, выше, можно записать в виде:

private <T> T getInstance(String key, 
          Map<String, T> instanceMap, 
          Class<T> instanceType) { 
    T value = instanceMap.get(key); 
    if (value == null) { 
     try { 
      instanceMap.put(key, value = instanceType.newInstance()); 
     } catch (ReflectiveOperationException e) { 
      throw new IllegalArgumentException(e); 
     } 
    } 
    return value; 
} 

public ClassA getInstanceA(String key) { 
    return getInstance(key, instanceAMap, ClassA.class); 
} 

public ClassB getInstanceB(String key) { 
    return getInstance(key, instanceBMap, ClassB.class); 
} 

public ClassC getInstanceC(String key) { 
    return getInstance(key, instanceCMap, ClassC.class); 
} 

Как @ user902383 сказал, что было бы полезно, если бы вы могли использовать одну карту, чтобы держать все объекты и ключи , но, очевидно, это потребует гарантии того, что ни один из двух классов никогда не будет использовать один и тот же ключ:

public <T> T getInstance(String key, 
         Class<T> instanceType) { 
    Object value = instanceMap.get(key); 
    if (value != null) { 
     return instanceType.cast(value); 
    } 

    try { 
     T newValue = instanceType.newInstance(); 
     instanceMap.put(key, newValue); 
     return newValue; 
    } catch (ReflectiveOperationException e) { 
     throw new IllegalArgumentException(e); 
    } 
} 
+0

В первом примере вы теряете информацию о типе, имея экземплярную карту с 'Object' как тип значений. В последнем примере вы используете 'instanceType.cast (value)', чтобы выдать значение. Это не безопаснее, чем отличать значение с помощью значения '(T). Вы теряете предупреждение-компилятор, но он все равно может вызывать ClassCastExceptions. Однако пример с перегрузкой выглядит неплохо. – marstran

+0

@marstran Да, некоторая кастинг неизбежна без перегрузки метода. Но таким образом нет никаких сюрпризов: ClassCastException происходит в очевидном месте, если это вообще происходит. С другой стороны, когда вы делаете непроверенные броски, вы получаете ClassCastExceptions * позже * в коде, в неожиданных местах. Например, приведение исходного списка, содержащего целые числа в «Список », приведет к неожиданному исключению позже, когда вы будете перебирать его или вызвать get (int). – VGR

Смежные вопросы