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?
5 komentarzy to Typy generyczne od teraz są mniej anonimowe
copernic777
4 października, 2009 at 13:39
Tutaj jest fajnie omówiony temat generycznych DAO:
https://www.hibernate.org/328.html
yew
5 października, 2009 at 13:51
A czy wiesz może jak pobrać typy generyczny dla konkretnej nadklasy?
Przykład:
getTypes(C.class, A.class) – powinno zwrócić [MyObject]
Łukasz Dywicki
5 października, 2009 at 19:57
Cześć 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.
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
12 października, 2009 at 08:00
Nie 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
12 października, 2009 at 13:53
Zdanek:
„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.