Как получить фактические аргументы типа косвенно реализованному универсальному интерфейсу? - PullRequest
7 голосов
/ 23 декабря 2010

У меня есть параметризованный интерфейс, который реализован многими различными способами. Во время выполнения мне нужно выяснить, учитывая произвольный объект, который реализует этот интерфейс, каковы фактические параметры типа для интерфейса.

Вот фрагмент, иллюстрирующий проблему, и попытка ее решения на полпути ( также на ideone.com ):

import java.util.*;
import java.lang.reflect.*;

interface Awesome<X> { }
class Base<E> implements Awesome<Set<E>> { }
class Child extends Base<List<Integer>> { }

class AwesomeExample {      
    public static void main(String[] args) {
        Awesome<Set<List<Integer>>> x = new Child();

        System.out.println(
            ((ParameterizedType)
                Child.class.getGenericSuperclass()
            ).getActualTypeArguments()[0]
        );
        // prints "java.util.List<java.lang.Integer>"

        System.out.println(
            ((ParameterizedType)
                Base.class.getGenericInterfaces()[0]
            ).getActualTypeArguments()[0]
        );
        // prints "java.util.Set<E>"        

        investigate(x);
        // we want this to print "Set<List<Integer>>"
    }

    static void investigate(Awesome<?> somethingAwesome) {
        // how to do this?
    }
}

Похоже, что во время выполнения достаточно информации общего типа для вывода:

  • Child extends Base<List<Integer>>
  • Base<E> implements Awesome<Set<E>>

И, следовательно, мы можем собрать все кусочки воедино, чтобы сделать вывод:

  • Child implements Awesome<Set<List<Integer>>>

Таким образом, похоже, что проблема разрешима, но это не так просто, так как нам пришлось бы работать с произвольной иерархией классов / интерфейсов. Это единственный способ сделать это? Есть ли более простой способ? Кто-то уже написал библиотеку, чтобы сделать это?

Ответы [ 3 ]

4 голосов
/ 25 мая 2011

Редактировать: Вы можете просто захотеть использовать: http://code.google.com/p/gentyref/

Если вы можете гарантировать, что все реализации Awesome<?> не будут иметь аргументов типа, следующий код должен помочь вам начать [1]:

static void investigate(Object o) {
    final Class<?> c = o.getClass();
    System.out.println("\n" + c.getName() + " implements: ");
    investigate(c, (Type[])null);
}

static void investigate(Type t, Type...typeArgs) {
    if(t == null) return;

    if(t instanceof Class<?>) {
        investigate((Class<?>)t, typeArgs);
    } else if(t instanceof ParameterizedType) {
        investigate((ParameterizedType)t, typeArgs);
    }
}

static void investigate(Class<?> c, Type...typeArgs) {
    investigate(c.getGenericSuperclass(), typeArgs);

    for(Type i : c.getGenericInterfaces()) {
        investigate(i, typeArgs);
    }
}

static void investigate(ParameterizedType p, Type...typeArgs) {
    final Class<?> c = (Class<?>)p.getRawType();
    final StringBuilder b = new StringBuilder(c.getName());
    b.append('<');
    Type[] localArgs = p.getActualTypeArguments();
    if(typeArgs != null && typeArgs.length > 0) {
        int i = 0, nextTypeArg = 0;
        for(Type local : localArgs) {
            if(local instanceof ParameterizedType) {
                ParameterizedType localP = (ParameterizedType) local;
                b.append(localP.getRawType()).append('<');
                b.append(typeArgs[nextTypeArg++]);
                b.append('>');
            } else if(local instanceof TypeVariable) {
                // reify local type arg to instantiated one.
                localArgs[nextTypeArg] = typeArgs[nextTypeArg];
                b.append(localArgs[nextTypeArg]);
                nextTypeArg++;
            } else {
                b.append(local.toString());
            }
            b.append(", ");
            i++;
        }
        if(typeArgs.length > 0) {
            b.delete(b.length() - 2, b.length());
        }
        b.append('>');
    } else {
        String args = Arrays.toString(localArgs);
        b.append(args.substring(1, args.length()-1)).append('>');
    }
    System.out.println(b);
    investigate(c, localArgs);
}

Если, однако, будут созданы экземпляры Awesome<?> или Base<E>, эта информация о типе будет потеряна из-за удаления. Это можно обойти, как правило, примерно так:

Awesome<?> awesome = new Base<Double>() {};

Обратите внимание на {}, это создает новый анонимный класс, который реализует (или расширяет) Base<E>. Этот класс будет иметь параметры типа, доступные для отражения.

Если вы боитесь, что соблюдение этого соглашения будет проблемой, вы можете скрыть конструкторы и использовать только фабричные методы:

class Base<E> implements Awesome<Set<E>> {

    public static Base<Number> newNumberInstance() {
        return new Base<Number> () {};
    }

    protected Base() {}
}

Поскольку приведенный выше код не был полностью протестирован, вы можете сделать это. Дело в том, что вы можете найти фактические параметры типа, если ваши требования достаточно строги. Относится ли это к вашей ситуации, решать вам.

[1] Он распечатает все интерфейсы, которые реализует класс, а не только параметры типа Awesome. Это можно изменить, но я подумал, что пойду на более общий и позволю вам разобраться в специфике. Например, вы захотите проверить это, чтобы понять, что я имею в виду:

investigate(new ArrayList<Integer>());
investigate(new ArrayList<String>() {}); // new anonymous ArrayList class
investigate("");
investigate(new Awesome<Comparable<?>> () {}); // new anonymous implementation of Awesome
1 голос
/ 08 октября 2012

Следуя предложению @ oconnor0, вот как это сделать с gentyref :

static Type investigate(Awesome<?> somethingAwesome) {
    return GenericTypeReflector.getTypeParameter(somethingAwesome.getClass(), Awesome.class.getTypeParameters()[0]);
}

В случае, если somethingAwesome.getClass() также может быть общим, было бы полезно сначала пропустить его через GenericTypeReflector.addWildcardParameters.

Spring также имеет GenericTypeResolver.resolveTypeArgument (Class, Class) , который может достичь того же результата.

0 голосов
/ 23 декабря 2010

Краткий ответ - НЕТ.Я согласен, что это жаль ... :( Причина в том, что Java отбрасывает параметры типа на этапе компиляции. Они не существуют в байт-коде. Они используются только компилятором.

Для решения вашей проблемывам нужно добавить еще один «обычный» параметр типа Class и передать его конструктору при создании экземпляра Base:

class Base<E> implements Awesome<Set<E>> { 
    private E type;
    public Base(E type) {
        this.type = type;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...