Использование необработанных типов для динамического применения параметров типа для отраженного подкласса

ВАЖНЫЙ:

код, который у меня сейчас есть, работает в соответствии с моими ожиданиями. Он делает то, что я хочу. Мой вопрос о том, является ли СПОСОБ, которым я заставил его работать, неправильным. Причина, по которой я спрашиваю об этом, заключается в том, что я видел много результатов переполнения стека о необработанных типах и о том, как их НИКОГДА не следует использовать.

Что я делаю и почему я использовал необработанные типы

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

Загрузка файла свойств

Properties prop = new Properties();
    try {
    prop.load(ObjectFactory.class.getResourceAsStream("config.properties"));

Это анализатор файлов, который реализует FileParserImplementation, принимает данные и помещает их в массив. Этот код получает тип класса, а затем динамически создает экземпляр этого типа.

Class<? extends FileParserImplementation> parser = null;
parser = Class.forName(prop.getProperty("FileParserImplementation")).asSubclass(FileParserImplementation.class);
FileParserImplementation ParserInstance = (FileParserImplementation) parser.getDeclaredConstructors()[0].newInstance();

Эти два класса и их экземпляры являются двумя отдельными DataParsers, реализующими DataParserImplementation. Они принимают массив Strings, который дает FileParser, и создают объекты/манипулируют данными во все, что необходимо. Он выпускает коллекцию этих данных. Зависимость Fileparser передается через внедрение конструктора. Это можно настроить через файл свойств во время выполнения.

Class<? extends DataParserImplementation> dataset1 = Class.forName(prop.getProperty("DataParserImplementation_1")).asSubclass(DataParserImplementation.class);
Class<? extends DataParserImplementation> dataset2 = Class.forName(prop.getProperty("DataParserImplementation_2")).asSubclass(DataParserImplementation.class);
DataParserImplementation Dataset1Instance = (DataParserImplementation) dataset1.getDeclaredConstructors()[0].newInstance(ParserInstance);
DataParserImplementation Dataset2Instance = (DataParserImplementation) dataset2.getDeclaredConstructors()[0].newInstance(ParserInstance);

Это класс Crossreferencer, который реализует CrossReferencerImplementation. Он принимает два набора данных и перекрестно ссылается на них любым способом, который требуется фактическому конкретному отраженному классу. Это также можно настроить во время выполнения. Он выводит карту в этом файле main. Карта служит окончательным набором данных (я могу изменить это позже).

Class<? extends CrossReferenceImplementation> crossreferencer = Class.forName(prop.getProperty("CrossReferenceImplementation")).asSubclass(CrossReferenceImplementation.class);
CrossReferenceImplementation crossReferencerInstance = 
(CrossReferenceImplementation) crossreferencer.getDeclaredConstructors()[0].newInstance();

Получение результата Map от вызова метода в нашем отраженном экземпляре. Затем содержимое этой карты распечатывается. в настоящее время кажется, что параметры карты также получены, потому что объекты, находящиеся внутри карты, правильно используют свои методы toString при вызове reflectiveFinalMap.get(key).toString(). Это заставляет меня поверить, что это работает так, как я задумал.

Map reflectiveFinalMap = (Map) 
crossReferencerInstance.CrossReference(Dataset1Instance.Parse(), Dataset2Instance.Parse());
for (Object key:reflectiveFinalMap.keySet()) {
            System.out.println(key + " { " + 
reflectiveFinalMap.get(key).toString() + " }");
        }
    return reflectiveFinalMap;
}
//catch block goes here

Обратите внимание, что каждый раз, когда я рефлективно создаю экземпляр класса, реализующего один из моих интерфейсов, я использую этот интерфейс как необработанный тип. Я надеюсь, что отражение затем увидит параметризованный тип этого необработанного типа при создании конкретного подкласса, потому что именно там фактически указаны типы параметров. Смысл в том, чтобы позволить любому классу, реализующему эти интерфейсы, быть универсальным до такой степени, что он может принимать что угодно и возвращать что угодно.

Вещи, которые я старался не использовать в необработанных типах.

Я попытался фактически получить параметризованный тип CrossReferenceImplementation в отраженном crossreferencer Class, который я получаю прямо сейчас, вызывая

Class arrayparametertype = (Class)((ParameterizedType)crossreferencer.getClass().getGenericSuperclass()).getActualTypeArguments()[0];

И затем я попытался передать это arrayparameter при создании экземпляра crossreferencer следующим образом:

CrossReferenceImplementation crossReferencer = (CrossReferenceImplementation<<arrayparametertype>>) crossreferencer.getDeclaredConstructors()[0].newInstance();

Это не сработало, поскольку типы переменных параметров, по-видимому, не имеют значения. Я попытался вручную указать конкретный параметр конкретного отраженного класса (я все равно НЕ хочу этого, потому что здесь нарушается вся точка отражения, отделяя классы друг от друга, имея возможность использовать все, что реализует соответствующий интерфейс). Это привело к появлению этого предупреждения, и код фактически не запускал методы, которые он должен был выполнять:

//how the parameters were specified. Messy and breaks the reflection.
CrossReferenceImplementation<Map<String, SalesRep>,Map<String, SalesRep>,Map<String, SalesRep>> crossReferencer = (CrossReferenceImplementation) crossreferencer.getDeclaredConstructors()[0].newInstance();

//where the warning occured
Map reflectiveFinalMap = (Map) crossReferencer.CrossReference(Dataset1.Parse(), Dataset2.Parse());

Предупреждение: «Набор данных1 имеет необработанный тип, поэтому результат анализа стирается». Обратите внимание, что SalesRep здесь — это объект, в котором данные хранятся в виде полей этого объекта. Этот объект обрабатывается и помещается в различные коллекции. Доступ к нему также осуществляется через отражение во многих методах DataParserImplementations.

Аналогичное сообщение об ошибке и проблема возникли при указании типа параметра карты (ОПЯТЬ, я НЕ хочу этого, потому что это делает отражение бессмысленным, я хочу, чтобы результат возврата карты был универсальным и определялся классом реализации).

//where the parameterized type was specified
Map reflectiveFinalMap = (Map<String,SalesRep>) crossReferencer.CrossReference(Dataset1.Parse(), Dataset2.Parse());

При указании фактического параметризованного типа результата карты сообщение об ошибке было: «crossReferencer имеет необработанный тип, поэтому результат CrossReference стирается».

Запуск кода действительно подтвердил для меня, что результаты метода .CrossReference были стерты, в то время как все остальное работало нормально.

Какие интернет-поиски я пробовал, прежде чем спросить здесь

Поэтому я использовал необработанные типы для обеих операций. Как видно из основного кода, все работало нормально. Но я видел так много "Не используйте необработанные типы". Вот почему я спрашиваю: уместно ли это использование необработанных типов? Должен ли я сделать это по-другому, чтобы НЕ нарушать отражение? Это нарушает отражение, потому что ручное указание параметра типа не только не запускает мой код, но также означает, что можно использовать ТОЛЬКО конкретный класс. Я задумался, чтобы можно было использовать все, что реализует универсальный интерфейс. Я не хочу иметь возможность использовать только конкретные конкретные экземпляры. Я пробовал искать переполнение стека для того, что в моем заголовке и других подобных вещах. Я думаю, что это может быть связано со стиранием шрифта, но я, честно говоря, не уверен в этом. Ничто другое на самом деле не решало эту проблему, потому что ничто не говорило о дженериках, параметризованных типах и отражении сразу (суть моей проблемы). Мне сказали, что дженерики и отражение плохо сочетаются друг с другом, но этот код все равно работает и работает так, как я хочу. Это работает хорошо. Я просто хочу убедиться, что я не делаю что-то УЖАСНО неправильное.

Цель.

Чтобы получить представление о моем текущем использовании необработанных типов, чтобы я знал, что делаю это правильно. Под «правильным» я подразумеваю противоположность тому, что я определяю ниже как «неправильный» путь. Пример того, что я ищу «Понимание», это:

Чтобы понять, почему код пуэсдо выглядит следующим образом:

ConcreteClass forname(myPropertiesFileObject.get(ConcreteClassname)) as subClass of (MyGenericInterface);
MyRAWGenericInterfaceType ConcreteClassInstance = (MyRAWGenericInterfaceType) ConcreteClass.newInstance( Insert generic Type constructor arguments here);
RAWCollectionType someCollection = RAWCollectionType concreteClassInstance.CallingAMethod(Insert generic Type method arguments here);

Использует необработанные типы, где RAW содержится в имени типа интерфейса или коллекции. Это в отличие от того, чтобы делать это каким-то образом, который не использует необработанные типы, но не нарушает точку отражения, чтобы отделить взаимодействия между этими классами. В этом случае указание параметров с помощью жесткого кода «сломит отражение». Кроме того, я хотел бы понять, почему указание параметров (даже если я знаю, что это не то, что я собираюсь делать) для этих типов RAW в pusedocode выше вызывает ошибки, перечисленные выше в вопросе, а именно, почему результат CallingAMethod стерт при подаче фактических параметров в RAWCollectionType, которые возвращает метод? Основная проблема заключается в том, что когда я задаю параметры типа для RAWCollectionType при его объявлении, он отказывается обновляться тем, что возвращает CallingAMethod, и я не понимаю, почему. Он принимает возвращаемое значение, но если тело метода CallingAMethod имеет возвращаемое значение, переданное в качестве аргумента, обновленное внутри метода, а затем возвращенное, возвращаемое значение, которое я получаю, не содержит обновлений. CallingAMethod в этом примере было бы похоже, если бы у меня был список вроде:

[1,2,3]

и внутри метода у меня было что-то вроде:

foreach(thing in list){
    thing += 1
}

а затем я вернул список, возврат, который я получил бы при указании параметров, был бы [1,2,3], а при использовании необработанных типов это было бы [2,3,4], как я и хотел. Я спрашиваю об этом, потому что слышал плохие вещи об использовании необработанных типов.

Кроме того, я хочу убедиться, что мое использование необработанных типов не является ужасно неправильным и что оно работает, потому что оно ДОЛЖНО работать. Может быть, я только что освоился во всей этой штуке с отражением и дженериками и нашел правильное применение необработанным типам, или я мог делать что-то настолько ужасное, что это заслуживает моего ареста. Вот что я намерен выяснить. Чтобы уточнить, под неправильным я имею в виду:

плохой дизайн (следует использовать другой способ рефлексивного вызова моих методов, а также использовать рефлексивные классы, использующие универсальные интерфейсы)

неэффективный дизайн (с точки зрения временной сложности, строки кода или удобства обслуживания)

есть лучший способ, вы даже не должны делать это в первую очередь

Если какая-либо из этих причин или что-то, что я пропустил, всплыло, когда вы читали этот код, СКАЖИТЕ МНЕ. В противном случае, пожалуйста, объясните, почему мое использование необработанных типов действительно и не является нарушением этого вопроса: [ссылка] Что такое необработанный тип и почему мы не должны его использовать?


person Redacted    schedule 31.07.2018    source источник
comment
Можете ли вы опубликовать псевдокод того, чего вы хотите достичь? Поскольку я до сих пор не понимаю вашей цели здесь. Просто некоторый код, который не является допустимым java, но вы хотели бы, чтобы он работал нормально. Обратите внимание, что общие типы в java в основном находятся на стороне компилятора (см. Удаление типов), поэтому Map‹A,B› во время выполнения в любом случае является просто Map, а отражения являются динамическими, поэтому они всегда работают только с объектом. Вы можете только написать какую-нибудь утилиту, которая преобразует поле/метод в какой-то общий интерфейс, такой как FieldAccessor<T>, но для этого вам все равно нужно будет использовать небезопасное приведение. (но вы все равно можете сделать его типобезопасным во время выполнения)   -  person GotoFinal    schedule 01.08.2018
comment
@GotoFinal обновил мой вопрос, чтобы он содержал псевдокод сути проблемы. Он содержится прямо под заголовком «ЦЕЛЬ». К сожалению, у меня нет особой цели кода, потому что я уже достиг своей цели, мне просто нужно лучше понять, как я это сделал, чтобы знать, что я буду делать в будущем. «Понимать» лучше определено в вопросе, потому что просто просить о понимании без конкретной вещи, которую нужно понять, было бы слишком широко.   -  person Redacted    schedule 01.08.2018


Ответы (1)


В Java есть стирание типов, поэтому Map<A,B> во время выполнения — это просто Map, то же самое для CrossReferenceImplementation<Map<String, SalesRep>,Map<String, SalesRep>,Map<String, SalesRep>> — это просто CrossReferenceImplementation.
Это также означает, что вы можете преобразовать любую карту в Map и просто поместить в нее любые объекты, так что вы можете иметь Map<String, Long>, который на самом деле хранит объекты типа Map<Cookie, Fish>, и поэтому вам нужно быть осторожным с необработанными типами и отражениями.

Вы не можете нормально использовать отражение и дженерики - тогда у вас всегда будет какой-то непроверенный код, но вы можете ограничить его до минимума и в любом случае сделать его типобезопасным.

Например, вы можете создать собственный метод для получения поля: (это немного псевдокода, я пропущу все возможные исключения и т. д.)

public class FieldAccessor<O, T> { 
    final Field field; // + private constructor
    public T get(O object) { return (T) field.get(object); } // unsafe, bu we validated this before constructing this accessor
    public static <O, T> FieldAccessor<O, T> create(Class<? super O> definingClass, Class<? super T> fieldClass, String fieldName) {
        Field field = definingClass.getDeclaredField(fieldName);
        if (field.getType() != fieldClass) {
            throw some exception;
        }
        return new FieldAccessor<>(field);
    }

Тогда у вас есть все необходимые проверки, прежде чем вам нужно будет использовать это поле, и оно уже вернет допустимый тип. Таким образом, вы можете получить некоторое значение допустимого типа и добавить его к обычному универсальному экземпляру Map.

FieldAccessor<X, A> keyAccessor = FieldAccessor.create(X.class, A.class, "someProperty");
FieldAccessor<Y, B> valueAccessor = FieldAccessor.create(Y.class, B.class, "someOtherProperty");
Map<A, B> myMap = new HashMap<>();
mapMap.put(keyAccessor.get(myXValue), valueAccessor.get(myYValue));

Таким образом, у вас есть типобезопасный код, который по-прежнему работает с отражениями — он все равно может дать сбой во время выполнения, если вы укажете недопустимые типы, но, по крайней мере, вы всегда знаете, где он потерпит неудачу — так как здесь FieldAccessor уже проверяет все типы во время выполнения, чтобы убедиться что вы не будете делать что-то глупое, например, добавлять Integer к Map<String, Long>, так как позже это может быть сложно отладить. (если кто-то не будет использовать этот метод доступа как исходный тип, поскольку .get не проверяется, но вы можете добавить это, передав definingClass конструктору и проверив экземпляр объекта в методах получения)

Вы можете делать аналогичные вещи для методов и полей, которые используют универсальные типы (например, поле типа Map<X, Y>, этот FieldAccessor позволит вам только проверить, является ли он каким-то Map), но это будет намного сложнее, поскольку API для дженериков все еще бит «пустой» - нет возможности создавать собственные экземпляры универсальных типов или проверять, можно ли их назначать. (такие библиотеки, как gson, делают это, поэтому они могут десериализовать карты и другие универсальные типы, у них есть собственная реализация интерфейсов представления универсальных типов java, таких как ParameterizedType, и реализован собственный метод для проверки, можно ли назначать данные типы)

Просто когда вы используете отражения, вам нужно всегда помнить и понимать, что именно вы несете ответственность за проверку типов, поскольку компилятор здесь вам не поможет, так что небезопасный и необработанный типизированный код в порядке, если у вас есть логика, которая проверяет, если этот код никогда не будет делать что-то действительно небезопасное (например, передавать неверный тип в универсальный метод, например, Integer в карту Long).
Просто не бросайте необработанные типы и отражения в середине обычного кода, добавьте некоторую абстракцию к это, поэтому будет легче поддерживать такой код и проект.

Я надеюсь, что это несколько отвечает на ваш вопрос.

person GotoFinal    schedule 01.08.2018