Как быстро заполнить объект Java данными из другого, не связанного с этим объекта? - PullRequest
5 голосов
/ 22 декабря 2009

Как я могу заполнить простой Java-объект данными из любого другого произвольного объекта?

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

Я работаю в основном на динамических языках, и мне кажется, что я зациклен на том, как это будет работать в perl или javascript, и не могу выкинуть голову из динамического желоба достаточно долго, чтобы ясно это увидеть.

Я знаю, что мог бы сделать что-то вроде (псевдокод)

while (key = nextKey) {
    if (key.name == "fooBar") {
        object.setFooBar(key.value);
    } else if (key.name == "bazQux") {
        object.setBazQux(key.value);
    }
    ...etc...
}

Но это просто нехорошо и ужасно, когда увеличивается количество свойств или сложности.

На динамическом языке я бы сделал что-то вроде:

while (key = nextKey) {
    object.setField(key.name, key.value);
    // or even
    object.[key.name] = key.value;
}

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

Переключатель / регистр был бы немного лучше, но Java жалуется на то, что в таких выражениях не используются строки. Будет ли перечисления решение? </ вслепую за ответы>

Я рассмотрел использование отражения для реализации какой-то таблицы автоматической отправки, но мне кажется, что на меня смотрят свысока и что должен быть лучший способ.

Спасибо за понимание.

Ответы [ 10 ]

4 голосов
/ 22 декабря 2009

Эта тема обсуждалась ранее: есть ли какой-либо инструмент для сопоставления объекта java с объектом? В особенности сообщение о Pascal Thivent содержит lot API. Конечно, между ними должен быть один, который соответствует вашим потребностям.

Вы также можете выбрать один из них на основе Reflection API или, возможно, JavaBeans API (с каждым PropertyEditor). Но я думаю, что это займет больше времени, чем вы могли бы ожидать, чтобы все это было надежным.

3 голосов
/ 22 декабря 2009

Еще один голос за размышления. Здесь вы пытаетесь автоматически связать нетипизированные данные String с типизированными переменными Java. Это просто потребует отражения.

Причина, по которой рефлексия "смотрит вниз", заключается в том, что, вообще говоря, слепое связывание нетипизированных данных String с типизированными переменными Java "смотрит сверху вниз". Во многих случаях лучше (например), чтобы ваш объект явно извлекал только те данные, которые ему нужны. Например:

this.fooBar = doc.get("fooBar");
this.bazQux = doc.get("bazQux");
// ... etc.

Вы можете сказать: «Но теперь, если я добавлю запись sysBiz в свой документ, я должен не забыть добавить дополнительную строку в мой конструктор. На динамическом языке это будет просто работа [tm]!» Это правда, но в Java вы все равно должны добавить свою sysBiz переменную-член, ваши методы получения и установки и всю другую логику вокруг sysBiz, так что дополнительная строка в вашем конструкторе или разделе десериализации не так уж много неприятность. Кроме того, он четко указывает, какие части документа вам нужны, поэтому, если кто-то добавит в документ запись zigBlog, это не вызовет проблем с вашим автоматическим связыванием (например, что-то, что выглядело как object.setField(key, val)), вызывая время выполнения. ошибка об отсутствии zigBlog существующего поля.

Все это говорит о том, что бывают случаи, когда автоматически связывать нетипизированные данные String с типизированными переменными Java - это правильный путь, и отражение - это правильный инструмент для использования в этом сценарии. Убедитесь, что автоматическое связывание действительно является лучшим выбором для структур данных, которые у вас есть, но как только вы будете уверены, вам не нужно будет стесняться использовать рефлексию только потому, что на нее "смотрят свысока" или это немного более сложный раздел язык. Это именно то, для чего это.

2 голосов
/ 22 декабря 2009

Решит ли ваша проблема сериализация объекта A в XML и десериализация XML в объект B?

Если это так, взгляните на Простые рамки . Превратит вашу сериализацию в XML. Если нет, то не обращайте внимания, пропустите мой ответ:)

1 голос
/ 22 декабря 2009

Spring BeanUtils также может использоваться для копирования данных из одного объекта в другой.

1 голос
/ 22 декабря 2009

Мне бы хотелось, чтобы в библиотеке Apache Beans (http://commons.apache.org/beanutils/), это, вероятно, ваш лучший выбор. Вероятно, вам нужен метод [copyProperties] [1].

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

  1. Объекты, которые не совместимы между собой. Метод попытается преобразовать собственные типы, такие как int, в String и т. Д., Но он не будет преобразовывать сложные типы. Класс BeanUtilsBean - это способ предоставления пользовательских конвертеров;
  2. Я не думаю, что вы можете управлять тем, что можно копировать или нет (проблема с подходом отражения заключается в том, что это происходит под капотом - вы можете добавить параметр A в класс и тот же параметр A в другой класс, они несовместимы, и ваш код внезапно перестанет работать).

Другой вариант, все еще использующий отражение, - это использование аннотаций в исходном коде для контроля того, что можно копировать или нет. Вы можете добавить аннотацию к своему методу getter, чтобы указать, что его можно скопировать, и вы можете предоставить класс для аннотации, который будет выполнять преобразование типа при необходимости. То же самое относится и к вашему методу установки, вы можете разрешить его установку только в том случае, если там есть аннотация.

Если вы действительно хотите пойти далеко и инициализировать скопированный класс в его конструкторах, вы можете использовать аннотации параметров в параметрах конструкторов, чтобы сделать что-то вроде этого:

public class YourClass {
    public YourClass(
        @DynamicParameter(name = "id") String id,
        @DynamicParameter(name = "name") String name
    )
}

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

Все зависит от того, как далеко вы хотите зайти.

[1]: http://commons.apache.org/beanutils/v1.8.2/apidocs/org/apache/commons/beanutils/BeanUtils.html#copyProperties(java.lang.Object, java.lang.Object)

1 голос
/ 22 декабря 2009

В Apache Commons BeanUtils есть что-то похожее для перемещения данных между компонентами (учтите, на основе отражений!)

Вы могли бы легко сделать что-то, подходящее для вашей проблемы с отражением, протестировать модуль и затем - если производительность удовлетворительная, оставьте это. Если нет, вы можете подумать об использовании генерации кода на основе XML-схем или использовать что-то вроде JAXB.

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

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

1 голос
/ 22 декабря 2009

Посмотрите на классы в java.lang.reflect. Инструменты отражения позволяют создавать имена (классов,) методов и полей, а затем совершать необходимые вызовы. Убедитесь, что если вы вызываете "setFoo (...)", у вас есть правильный тип данных. Сожалею. Добро пожаловать в Java: -)

1 голос
/ 22 декабря 2009

Путь Java будет выглядеть примерно так:

public interface Trasnformer<F, T>
{
    T transformFrom(F original);
}

тогда есть классы вроде:

public class IntegerStringTransofromer<Integer, String>
{
    public String transformFrom(Integer original)
    {
        // code
    }
}

Я уверен, что вы можете придумать что-то более похожее на то, что вы действительно пытаетесь сделать, но это не совсем естественный способ сделать это в Java. Как правило, когда вы переключаетесь на разные стили языков, вам нужно переключиться на подход к решению проблем. Я уверен, что Perl-программа, написанная как Java-программа, была бы странной вещью ... так же, как Java-программа, написанная как Perl-программа.

Вздох ... несмотря на мое, лучшее суждение ... вот решение, которое делает то, что вы просите (ну, не совсем, но это начало) ... все, что я прошу, это чтобы вы его не использовали!

class Copier
{
    public static void copyFromTo(final Object source,
                                  final Object dest)
    {
        final Class   sourceClass;
        final Class   destClass;
        final Field[] sourceFields;

        sourceClass  = source.getClass();
        destClass    = dest.getClass();
        sourceFields = sourceClass.getDeclaredFields();

        for(final Field field : sourceFields)
        {
            copyField(field, source, dest, destClass);
        }
    }

    private static void copyField(final Field field,
                                  final Object source,
                                  final Object dest,
                                  final Class  destClass)
    {
        final String fieldName;
        final String methodName;

        fieldName  = field.getName();
        methodName = "set" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1);

        try
        {
            final Method method;

            method = destClass.getMethod(methodName, field.getType());
            field.setAccessible(true);
            method.invoke(dest, field.get(source));
        }
        catch(final NoSuchMethodException ex)
        {
            // ignore it
            ex.printStackTrace();
        }
        catch(final IllegalAccessException ex)
        {
            // ignore it
            ex.printStackTrace();
        }
        catch(final IllegalArgumentException ex)
        {
            // ignore it
            ex.printStackTrace();
        }
        catch(final InvocationTargetException ex)
        {
            // ignore it
            ex.printStackTrace();
        }
    }
}

и вот как это использовать (опять же, пожалуйста, не надо).

public class Main
{
    public static void main(String[] args)
    {
        final B b;

        b = new B();
        Copier.copyFromTo(new A(), b);
        System.out.println("b.a = " + b.getA());
        System.out.println("b.b = " + b.getB());
        System.out.println("b.c = " + b.getC());
        System.out.println("b.d = " + b.getD());
        System.out.println("b.e = " + b.getE());
    }
}

class A
{
    private String a = "This";
    private String b = "Is";
    private String c = "A";
    private String d = "Bad";
    private String e = "Idea";
}

class B
{
    private String a;
    private String b;
    private String c;
    private String d;
    private String e;

    public void setA(final String val)
    {
        a = val;
    }

    public void setB(final String val)
    {
        b = val;
    }

    public void setC(final String val)
    {
        c = val;
    }

    public void setD(final String val)
    {
        d = val;
    }

    public void setE(final String val)
    {
        e = val;
    }

    public String getA()
    {
        return (a);
    }

    public String getB()
    {
        return (b);
    }

    public String getC()
    {
        return (c);
    }

    public String getD()
    {
        return (d);
    }

    public String getE()
    {
        return (e);
    }
}
1 голос
/ 22 декабря 2009

Позвольте мне объяснить, что единственный способ сделать что-то похожее на это во время выполнения Java - это использовать отражение. Это не значит, что вы должны использовать язык Java для достижения этой цели! Я считаю, что Groovy позволяет вам писать код, очень похожий на второй, и делать так, чтобы он компилировался в байт-код, используя вместо этого отражение.

РЕДАКТИРОВАТЬ: То есть, чтобы сделать это в общем . Можно придумать несколько шаблонов для преобразования одного класса в другой. Но для общего сопоставления полей в одном объекте с полями с совпадающими именами в другом, это совсем другое дело. Это можно сделать на Java. Я делал это раньше в Java. Это очень больно. Пусть что-то еще сделает большую часть этой тяжелой работы за вас.

Это было давно, но вот попытка в Groovy:

// Assuming document implements Map.
document.each {

    // At this point, 'it' is a Map.Entry.
    // Make sure the property exists on the target object.
    if (object.properties.keySet().contains(it.key)) {

        // Set the property to the value from the map entry.
        object."${it.key}" = it.value
    }
}
1 голос
/ 22 декабря 2009

Я думаю, что библиотека MethodUtils из apache - это именно то, что вы ищете

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...