Typy generyczne od teraz są mniej anonimowe
Do tej pory tworząc kod z użyciem typów generycznych zawsze borykałem się z problemem – jak dobrać się do klasy na podstawie któregoś parametru. Rozwiązanie znalazłem przeglądając w internecie kod jakieś przykładowej aplikacji. Ot znowu kilka linii które uporządkują kod i zmniejszą ilość powtórzeń. :-)
Dbanie o porządek w kodzie to obowiązek każdego programisty. Stąd podział aplikacji na warstwy, wyodrębnianie wspólnego kodu do typów abstrakcyjnych i tak dalej. Są jednak miejsca, których do tej pory nie potrafiłem uporządkować dobrze – były to wszelkiego rodzaju DAO, które zawsze wyglądały podobnie. Interfejs, klasa abstrakcyjna, pochodna wywołująca chroniony konstruktor. Wszystko po to by w klasie abstrakcyjnej móc zdefiniować metodę readById. Oto przykład takiego kodu:
// Ogólny interfejs dostępu do danych
// T to typ encji, K to typ klucza w danej encji.
public interface GenericDAO<T, K> {
void save(T instance);
void create(T newInstance);
void update(T existingInstance);
boolean delete(T instance);
T readById(K id);
List<T> getAll();
}
// Implementacja oparta o mechanizmy Springa
public abstract class AbstractDAO<T extends BaseEntity, K> extends
JpaDaoSupport implements GenericDAO<T, K> {
private Class<T> typeClass;
protected AbstractDAO(Class typeClass) {
this.typeClass = typeClass;
}
// pobranie obiektu na bazie id
public T readById(K id) {
return getJpaTemplate().find(typeClass, id);
}
}
// Przekazanie User.class do AbstractDAO by te mogło
// użyć go do odpytywania bazy danych
public class UserJpaDAO extends AbstractDAO<User, Long> implements UserDAO {
public UserJpaDAO() {
super(User.class);
}
}
W tym wypadku typ generyczny zamiast uprościć życie nieco je komplikuje – w ostatniej klasie musimy pisać dwa razy User – raz jako parametr typu, dwa przekazując go do konstruktora klasy abstrakcyjnej. Logicznie rzecz biorąc. Znany jest typ generyczny zatem to nazwa klasy też powinna być do wykrycia. I tak rzeczywiście jest. Od JDK 1.5 istnieje interfejs ParameterizedType, który zawiera metodę getActualTypeArguments, Wystarczy dobrać się do tego obiektu i będzie z górki. Oto jak ja to rozwiązałem:
// Copyright (C) 2009 Code-House
// All rights reserved
package org.code_house.manager.dataaccess.jpa.util;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
/**
* Klasa pomocnicza do operowania na typach generycznych.
*
* @author Łukasz Dywicki luke@code-house.org
*
* $Id$
*/
public class GenericTypeUtil {
/**
* Konstruktor prywatny, na wypadek gdyby ktoś chciał stworzyć instancję
* utila. :)
*/
private GenericTypeUtil() {}
/**
* Pobranie argumentów dla danego typu na bazie klasy.
*/
public static Class[] getGenericTypes(Class type) {
Type superclass = type.getGenericSuperclass();
if (superclass instanceof ParameterizedType) {
ParameterizedType genericTypeClass = (ParameterizedType) superclass;
return cast(genericTypeClass.getActualTypeArguments());
}
return new Class[0];
}
// Rzutowanie w dół z Type do Class.
public static Class[] cast(Type[] types) {
Class[] classTypes = new Class[types.length];
for (int i = 0, j = types.length; i < j; i++) {
classTypes[i] = (Class) types[i];
}
return classTypes;
}
}
Co zwróci nam wywołanie GenericTypeUtil.getGenericTypes(UserJpaDAO.class)? To czego potrzebujemy! :)
[class org.code_house.manager.domain.User, class java.lang.Long]
Czy znaliście ten sposób? A może tylko ja byłem nieświadom tego rozwiązania?
Modified

copernic777 says:
Added on Październik 4th, 2009 at 13:39Tutaj jest fajnie omówiony temat generycznych DAO:
https://www.hibernate.org/328.html
yew says:
Added on Październik 5th, 2009 at 13:51A czy wiesz może jak pobrać typy generyczny dla konkretnej nadklasy?
Przykład:
class A {} class B extends A {} class C extends BgetTypes(C.class, A.class) – powinno zwrócić [MyObject]
Łukasz Dywicki (blog author) says:
Added on Październik 5th, 2009 at 19:57Cześć yew!
Na pytanie, które zadałeś nie ma dobrej odpowiedzi. Jedyne co w tym wypadku można uzyskać to informacje o nazwie aliasu generyka (np. T, K) i jego granice w określonej hierarchii.
W tym przypadku Reflection API zwróci java.lang.reflect.TypeVariable zamiast java.lang.Class.
class GenericSecondExt<T> extends GenericExt<T, CharSequence> {}Lecąc w kolejności GenericSecondExt.class.getGenericSuperclass() zwróci TypeVariable z granicami [Object.class, Object.class] dla T a następnie Class dla CharSequence.
Zdanek says:
Added on Październik 12th, 2009 at 08:00Nie znałem, a właśnie tego “ogniwa” mi brakowało :) Dzięki za publikację.
A czy mógłbyś się pokusić o test wydajnościowy? Tzn. zrobić porównanie prędkości pracy obu DAO, wywalając logikę biznesową naturalnie. Refleksje są dość kosztowne, to wiemy, ale w tym wypadku może nie jest tak źle. Z drugiej strony, jeśli narzut jest zbyt duży, to pewnie znajdą się osoby, które wolą skorzystać z rozwiązania pierwszego.
Oczywiście jeśli narzut w ogóle jest dziesiątki razy mniejszy od późniejszego czasu dostępu do bazy, to lepiej być wygodnym i użyć rozwiązania drugiego.
yew says:
Added on Październik 12th, 2009 at 13:53Zdanek:
“Refleksje są dość kosztowne, to wiemy, ale w tym wypadku może nie jest tak źle. Z drugiej strony, jeśli narzut jest zbyt duży, to pewnie znajdą się osoby, które wolą skorzystać z rozwiązania pierwszego.”
Tak, lecz popatrz, że DAO raczej powinieneś tworzyć przy starcie aplikacji, więc jedyne co będzie wolniejsze to start a nie samo działanie.