Typy generyczne od teraz są mniej anonimowe

2 Paź
2009

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

Avatar

copernic777

4 października, 2009 at 13:39

Tutaj jest fajnie omówiony temat generycznych DAO:
https://www.hibernate.org/328.html

Avatar

yew

5 października, 2009 at 13:51

A czy wiesz może jak pobrać typy generyczny dla konkretnej nadklasy?

Przykład:

class A {}
class B extends A {}
class C extends B

getTypes(C.class, A.class) – powinno zwrócić [MyObject]

Avatar

Ł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.

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.

Avatar

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.

Avatar

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.

Comment Form

You must be logged in to post a comment.

top