Приведение к классу, который определяется во время выполнения - PullRequest
5 голосов
/ 19 декабря 2008

У меня есть метод fetchObjects(String), который должен вернуть массив Contract бизнес-объектов. Параметр className говорит мне, какие бизнес-объекты мне следует возвращать (конечно, в данном случае это не имеет смысла, потому что я уже сказал, что верну Contract s, но в основном это ситуация, с которой я столкнулся в моем реальном сценарий). Поэтому я откуда-то получаю набор записей и загружаю класс записей коллекции (тип которых указан className).

Теперь мне нужно создать массив для возврата, поэтому я использую метод Set toArray(T[]). Используя рефлексию, я создаю пустой массив Contracts. Но, это дает мне значение статического типа Object! Итак, затем мне нужно привести его к соответствующему типу, который в данном случае равен Contract[] (см. Часть с подчеркиванием звездочкой в ​​листинге ниже).

У меня вопрос: есть ли способ и как привести к Contract[], как я делаю в листинге, , но определить тип элементов массива (Contract) только через className (или entriesType)? Другими словами, то, что я хотел бы сделать, это в основном приведение типа: (entriesType[]) valueWithStaticTypeObject, где recordsType должен быть заменен на класс, указанный в параметре classname, т.е. Contract.

Это как-то по своей сути невозможно, или это можно сделать как-то? Может быть, с использованием дженериков?

package xx.testcode;

import java.util.HashSet;
import java.util.Set;

class TypedArrayReflection {

    public static void main(String[] args) {
        try {
            Contract[] contracts = fetchObjects("Contract");
            System.out.println(contracts.length);
        } catch (ClassNotFoundException e) {}
    }

    static Contract[] fetchObjects(String className) throws ClassNotFoundException {
        Class<?> entriesType = Class.forName("xx.testcode."+className);
        Set<?> entries = ObjectManager.getEntrySet(className); 
        return entries.toArray( 
                (Contract[]) java.lang.reflect.Array.newInstance(
                /********/          entriesType, entries.size()) );
    }
}

class Contract { } // business object

class ObjectManager {
    static Set<?> getEntrySet(String className) {
        if (className.equals("Contract"))
            return new HashSet<Contract>();
        return null; // Error
    }
}

Спасибо.


Обновление: Используя типобезопасный метод toArray, взятый из CodeIdol , я обновил свой fetchObjects метод таким образом:
static Contract[] fetchObjects(String className) throws ClassNotFoundException {
    Class<?> entriesType = Class.forName("xx.testcode."+className);
    Set<?> entries = ObjectManager.getEntrySet(className);
    return toArray(entries, entriesType); // compile error
    // -> "method not applicable for (Set<capture#3-of ?>, Class<capture#4-of ?>)"
}

public static <T> T[] toArray(Collection<T> c, Class<T> k) {
    T[] a = (T[]) java.lang.reflect.Array.newInstance(k, c.size());
    int i = 0;
    for (T x : c)
        a[i++] = x;
    return a;
}

Что мне нужно сделать, чтобы избавиться от ошибки компилятора, указанной в комментарии? Обязательно ли указывать Set<Contract> в типе возврата моего getEntrySet метода, чтобы это могло работать? Спасибо за любые указатели.

Ответы [ 2 ]

7 голосов
/ 19 декабря 2008

Вы можете использовать класс в качестве параметра, а не имя класса.

   static <T extends Contract> T[] buildArray(Class<T> clazz){
     ArrayList<T> l=new ArrayList<T>();
     return l.toArray((T[]) java.lang.reflect.Array.newInstance(clazz, l.size()));
   }

РЕДАКТИРОВАТЬ: (после прочтения комментария Ян)

Нет, универсальный тип нельзя использовать со значением переменной.

1 голос
/ 19 декабря 2008

Так почему бы не использовать

static <T> T[] buildArray(Class<T> clazz){
ArrayList<T> l=new ArrayList<T>();
return l.toArray((T[]) java.lang.reflect.Array.newInstance(clazz, l.size()));
}

Примечание. Модифицированный код сверху.

...