प्रतिबिंब के माध्यम से एक कक्षा से दूसरे कक्षा में सभी मूल्यों को कॉपी करें


82

मेरे पास एक वर्ग है जो मूल रूप से दूसरे वर्ग की नकल है।

public class A {
  int a;
  String b;
}

public class CopyA {
  int a;
  String b;
}

क्या मैं कर रहा हूँ वर्ग से मूल्यों डाल है Aमें CopyAभेजने से पहले CopyAएक वेब सेवा कॉल के माध्यम से। अब मैं एक प्रतिबिंब-विधि बनाना चाहूंगा जो मूल रूप से उन सभी क्षेत्रों की प्रतिलिपि बनाता है जो कक्षा से कक्षा Aतक एक समान (नाम और प्रकार से) हैं CopyA

मैं यह कैसे कर सकता हूँ?

यह वही है जो मेरे पास अभी तक है, लेकिन यह काफी काम नहीं करता है। मुझे लगता है कि यहां समस्या यह है कि मैं जिस क्षेत्र से गुजर रहा हूं, उस क्षेत्र को स्थापित करने की कोशिश कर रहा हूं।

private <T extends Object, Y extends Object> void copyFields(T from, Y too) {

    Class<? extends Object> fromClass = from.getClass();
    Field[] fromFields = fromClass.getDeclaredFields();

    Class<? extends Object> tooClass = too.getClass();
    Field[] tooFields = tooClass.getDeclaredFields();

    if (fromFields != null && tooFields != null) {
        for (Field tooF : tooFields) {
            logger.debug("toofield name #0 and type #1", tooF.getName(), tooF.getType().toString());
            try {
                // Check if that fields exists in the other method
                Field fromF = fromClass.getDeclaredField(tooF.getName());
                if (fromF.getType().equals(tooF.getType())) {
                    tooF.set(tooF, fromF);
                }
            } catch (SecurityException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (NoSuchFieldException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

        }
    }

मुझे यकीन है कि कोई ऐसा व्यक्ति होना चाहिए जिसने पहले ही किसी तरह ऐसा किया हो



अपाचे जकार्ता से हाँ या बीन्यूटिल्स।
शॉन एफ

जवाबों:


102

अगर आपको थर्ड पार्टी लाइब्रेरी का उपयोग करने में कोई आपत्ति नहीं है, तो अपाचे कॉमन्स के बीन यूटिल्स इसे आसानी से उपयोग करते हुए संभाल लेंगे copyProperties(Object, Object)


13
जाहिरा तौर पर BeanUtils शून्य दिनांक फ़ील्ड के साथ काम नहीं करता है। अपाचे PropertyUtils उपयोग करता है, तो यह आपके लिए एक समस्या है: mail-archive.com/user@commons.apache.org/msg02246.html
ripper234

10
यह स्पष्ट रूप से निजी क्षेत्र के लिए काम नहीं करता है जिसमें कोई गेट्टर और बसने वाला नहीं है। कोई समाधान जो सीधे गुणों के बजाय खेतों के साथ काम करता है?
एंड्रिया रात्तो

यह न तो बिना गेटर्स के सादे सार्वजनिक क्षेत्रों के साथ काम करता है: stackoverflow.com/questions/34263122/…
Vadzim

17

आप गेसन लाइब्रेरी https://github.com/google/gson का उपयोग क्यों नहीं करते हैं

आप सिर्फ कक्षा A को json string में रूपांतरित करते हैं। फिर jsonString को आप सबक्लास (CopyA) में परिवर्तित करें। कोड के नीचे .use:

Gson gson= new Gson();
String tmp = gson.toJson(a);
CopyA myObject = gson.fromJson(tmp,CopyA.class);

एक और स्ट्रिंग क्यों उत्पन्न करें जो बड़ी भी हो सकती है? यहां उत्तर के रूप में वर्णित बेहतर विकल्प हैं। कम से कम हम (उद्योग) स्ट्रिंग प्रतिनिधित्व के लिए
एक्सएमएन से लेकर जोंस

कृपया ध्यान दें कि प्रतिबिंब का उपयोग करते समय स्ट्रिंग एक साइड उत्पाद है। आपके माध्यम से भी इसे नहीं बचाया !! यह जावा शुरुआती के लिए एक उत्तर है, और इसका उद्देश्य संक्षिप्त और स्वच्छ तरीके से होना है। @ जर्नाग
एरिक हो

आपको अभी भी स्रोत और गंतव्य दोनों वस्तुओं पर प्रतिबिंब की आवश्यकता होगी, लेकिन अब आप बाइनरी / टेक्स्ट / बाइनरी फ़ॉर्मेटिंग और पार्सिंग ओवरहेड को भी शुरू कर रहे हैं।
इववो

यदि आप कोड को बाधित करने के लिए Proguard का उपयोग करते हैं तो ध्यान दें। यदि आप इसका उपयोग करते हैं, तो यह कोड काम नहीं करेगा।
सेबस्टियाओलीनो

8

बीन्यूटिल्स केवल सार्वजनिक क्षेत्रों की नकल करेगा और थोड़ा धीमा है। इसके बजाय गेट्टर और सेटर विधियों के साथ जाएं।

public Object loadData (RideHotelsService object_a) throws Exception{

        Method[] gettersAndSetters = object_a.getClass().getMethods();

        for (int i = 0; i < gettersAndSetters.length; i++) {
                String methodName = gettersAndSetters[i].getName();
                try{
                  if(methodName.startsWith("get")){
                     this.getClass().getMethod(methodName.replaceFirst("get", "set") , gettersAndSetters[i].getReturnType() ).invoke(this, gettersAndSetters[i].invoke(object_a, null));
                        }else if(methodName.startsWith("is") ){
                            this.getClass().getMethod(methodName.replaceFirst("is", "set") ,  gettersAndSetters[i].getReturnType()  ).invoke(this, gettersAndSetters[i].invoke(object_a, null));
                        }

                }catch (NoSuchMethodException e) {
                    // TODO: handle exception
                }catch (IllegalArgumentException e) {
                    // TODO: handle exception
                }

        }

        return null;
    }

बीन्यूटिल्स निजी क्षेत्रों पर ठीक काम करता है, जब तक कि गेटर्स / सेटर सार्वजनिक होते हैं। प्रदर्शन के संबंध में, मैंने कोई बेंचमार्किंग नहीं की है, लेकिन मेरा मानना ​​है कि यह बीन के कुछ आंतरिक कैशिंग को आत्मनिरीक्षण करता है।
ग्रेग केस

2
यह तभी काम करेगा जब दोनों फलियों में एक ही प्रकार के डेटा फ़ील्ड हों।
TimeToCodeTheRoad

@Tra Kra यह तभी काम करेगा जब आपके पास उस क्षेत्र के लिए गेटटर / सेटर हो।
WoLfPwNeR

8

यहाँ एक काम कर रहा है और परीक्षण समाधान है। आप वर्ग पदानुक्रम में मानचित्रण की गहराई को नियंत्रित कर सकते हैं।

public class FieldMapper {

    public static void copy(Object from, Object to) throws Exception {
        FieldMapper.copy(from, to, Object.class);
    }

    public static void copy(Object from, Object to, Class depth) throws Exception {
        Class fromClass = from.getClass();
        Class toClass = to.getClass();
        List<Field> fromFields = collectFields(fromClass, depth);
        List<Field> toFields = collectFields(toClass, depth);
        Field target;
        for (Field source : fromFields) {
            if ((target = findAndRemove(source, toFields)) != null) {
                target.set(to, source.get(from));
            }
        }
    }

    private static List<Field> collectFields(Class c, Class depth) {
        List<Field> accessibleFields = new ArrayList<>();
        do {
            int modifiers;
            for (Field field : c.getDeclaredFields()) {
                modifiers = field.getModifiers();
                if (!Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers)) {
                    accessibleFields.add(field);
                }
            }
            c = c.getSuperclass();
        } while (c != null && c != depth);
        return accessibleFields;
    }

    private static Field findAndRemove(Field field, List<Field> fields) {
        Field actual;
        for (Iterator<Field> i = fields.iterator(); i.hasNext();) {
            actual = i.next();
            if (field.getName().equals(actual.getName())
                && field.getType().equals(actual.getType())) {
                i.remove();
                return actual;
            }
        }
        return null;
    }
}

1
मैंने एक समान समाधान बनाया है। मैंने फ़ील्ड मैप में फ़ील्ड नाम के लिए एक वर्ग को कैश किया।
Orden

समाधान अच्छा है, लेकिन आप इस पंक्ति में एक समस्या है i.remove()। आप इटरेटर बना लिया है यहां तक कि अगर आप कॉल नहीं कर सकते removeपर Listकी iterator। यह होना चाहिएArrayList
फरीद

फरीद, हटाओ एक समस्या नहीं हो सकती क्योंकि कलेक्टफील्ड्स () ArrayList ऑब्जेक्ट बनाता है।
जेहाद

5

मेरा समाधान:

public static <T > void copyAllFields(T to, T from) {
        Class<T> clazz = (Class<T>) from.getClass();
        // OR:
        // Class<T> clazz = (Class<T>) to.getClass();
        List<Field> fields = getAllModelFields(clazz);

        if (fields != null) {
            for (Field field : fields) {
                try {
                    field.setAccessible(true);
                    field.set(to,field.get(from));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

public static List<Field> getAllModelFields(Class aClass) {
    List<Field> fields = new ArrayList<>();
    do {
        Collections.addAll(fields, aClass.getDeclaredFields());
        aClass = aClass.getSuperclass();
    } while (aClass != null);
    return fields;
}

मुझे नहीं लगता कि यह कस्टम ऑब्जेक्ट्स के लिए काम नहीं करता है। केवल अगर आपके पास कोई अभिभावक और केवल आदिम क्षेत्र के साथ एक वर्ग है
शेरविन असगरी

सुपर क्लास फील्ड्स को कवर करने के लिए, मैं 'getAllModelFields' कस्टम विधि का उपयोग कर रहा हूं
मोहसिन काशी

4

पहला तर्क tooF.set()लक्ष्य वस्तु ( too) होना चाहिए , न कि फ़ील्ड, और दूसरा तर्क मान होना चाहिए , न कि वह क्षेत्र जिससे मूल्य आता है। (मान प्राप्त करने के लिए, आपको कॉल करने की आवश्यकता है fromF.get()- इस मामले में फिर से एक लक्ष्य ऑब्जेक्ट में गुजर रहा है from।)

अधिकांश प्रतिबिंब एपीआई इस तरह से काम करते हैं। आपको Fieldऑब्जेक्ट्स, ऑब्जेक्ट्स, Methodवगैरह क्लास से ही मिलते हैं, उदाहरण के लिए नहीं, इसलिए उनका उपयोग करने के लिए (स्टैटिक्स को छोड़कर) आपको आम तौर पर उन्हें एक इंस्टेंस पास करने की आवश्यकता होती है।



4

यह एक देर से पोस्ट है, लेकिन अभी भी भविष्य में लोगों के लिए प्रभावी हो सकता है।

स्प्रिंग एक उपयोगिता प्रदान करता है BeanUtils.copyProperties(srcObj, tarObj)जो स्रोत ऑब्जेक्ट से लक्षित ऑब्जेक्ट के मूल्यों को कॉपी करता है जब दोनों वर्गों के सदस्य चर के नाम समान होते हैं।

यदि कोई दिनांक रूपांतरण है, (जैसे, स्ट्रिंग टू डेट) 'नल' को लक्ष्य ऑब्जेक्ट पर कॉपी किया जाएगा। फिर, हम आवश्यक रूप से तिथि के मूल्यों को स्पष्ट रूप से निर्धारित कर सकते हैं।

Apache Commonडेटा-प्रकारों का एक बेमेल होने पर BeanUtils एक त्रुटि फेंकता है (esp। रूपांतरण और दिनांक से)

उम्मीद है की यह मदद करेगा!


यह स्वीकार किए जाते हैं की तुलना में कोई अतिरिक्त जानकारी प्रदान नहीं करता है stackoverflow.com/a/1667911/4589003 जवाब
सुदीप भंडारी

3

मुझे लगता है कि आप डोजर आज़मा सकते हैं । इसमें सेम टू बीन रूपांतरण के लिए अच्छा समर्थन है। इसका उपयोग भी आसान है। आप इसे या तो अपने स्प्रिंग एप्लीकेशन में इंजेक्ट कर सकते हैं या जार को क्लास पाथ और उसके किए में जोड़ सकते हैं।

आपके मामले के एक उदाहरण के लिए:

 DozerMapper mapper = new DozerMapper();
A a= new A();
CopyA copyA = new CopyA();
a.set... // set fields of a.
mapper.map(a,copyOfA); // will copy all fields from a to copyA

3
  1. बीन यूटिल्स या अपाचे कॉमन्स का उपयोग किए बिना

  2. public static <T1 extends Object, T2 extends Object>  void copy(T1     
    origEntity, T2 destEntity) throws IllegalAccessException, NoSuchFieldException {
        Field[] fields = origEntity.getClass().getDeclaredFields();
        for (Field field : fields){
            origFields.set(destEntity, field.get(origEntity));
         }
    }
    

यह काम करने का हल नहीं है बल्कि एक अच्छा शुरुआती बिंदु है। दोनों वर्गों में मौजूद गैर-स्थिर और सार्वजनिक क्षेत्रों को संभालने के लिए फ़ील्ड को फ़िल्टर करने की आवश्यकता है।
JHead

क्या यह माता-पिता कक्षाओं में फ़ील्ड्स की उपेक्षा नहीं करेगा?
एवोवो

2

स्प्रिंग में एक अंतर्निहित BeanUtils.copyPropertiesविधि है। लेकिन यह गेटर्स / सेटर के बिना कक्षाओं के साथ काम नहीं करता है। खेतों की नकल करने के लिए JSON सीरियलाइजेशन / डिसेरिएलाइजेशन एक और विकल्प हो सकता है। इस उद्देश्य के लिए जैक्सन का उपयोग किया जा सकता है। यदि आप स्प्रिंग का उपयोग कर रहे हैं तो ज्यादातर मामलों में जैक्सन पहले से ही आपकी निर्भरता सूची में है।

ObjectMapper mapper     = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Clazz        copyObject = mapper.readValue(mapper.writeValueAsString(sourceObject), Clazz.class);

1

ओरिका सरल बीन मैपिंग फ्रेमवर्क है क्योंकि यह बाइट कोड जनरेशन के माध्यम से होता है। यह विभिन्न नामों के साथ मैपिंग और मैपिंग को नेस्टेड करता है। अधिक जानकारी के लिए, कृपया यहाँ जाँच करें नमूना मैपिंग जटिल लग सकती है, लेकिन जटिल परिदृश्यों के लिए यह सरल होगा।

MapperFactory factory = new DefaultMapperFactory.Builder().build();
mapperFactory.registerClassMap(mapperFactory.classMap(Book.class,BookDto.class).byDefault().toClassMap());
MapperFacade mapper = factory.getMapperFacade();
BookDto bookDto = mapperFacade.map(book, BookDto.class);

यह वह नहीं करता है जो सवाल पूछ रहा है। SerializationUtils.clone()उसी वर्ग की एक नई वस्तु देने जा रहा है। इसके अतिरिक्त यह केवल क्रमबद्ध कक्षाओं पर काम करता है।
किर्बी


1

मैंने कोटलिन में उपर्युक्त समस्या को हल किया जो मेरे लिए मेरे एंड्रॉइड ऐप डेवलपमेंट के लिए ठीक काम करता है:

 object FieldMapper {

fun <T:Any> copy(to: T, from: T) {
    try {
        val fromClass = from.javaClass

        val fromFields = getAllFields(fromClass)

        fromFields?.let {
            for (field in fromFields) {
                try {
                    field.isAccessible = true
                    field.set(to, field.get(from))
                } catch (e: IllegalAccessException) {
                    e.printStackTrace()
                }

            }
        }
    } catch (e: Exception) {
        e.printStackTrace()
    }

}

private fun getAllFields(paramClass: Class<*>): List<Field> {

    var theClass:Class<*>? = paramClass
    val fields = ArrayList<Field>()
    try {
        while (theClass != null) {
            Collections.addAll(fields, *theClass?.declaredFields)
            theClass = theClass?.superclass
        }
    }catch (e:Exception){
        e.printStackTrace()
    }

    return fields
}

}


0

मैं इस वजह से एक और JAR फ़ाइल पर निर्भरता नहीं जोड़ना चाहता था, इसलिए कुछ ऐसा लिखा जो मेरी आवश्यकताओं के अनुरूप हो। मैं fjorm के सम्मेलन का अनुसरण करता हूं https://code.google.com/p/fjorm/ जिसका अर्थ है कि मेरे आम तौर पर सुलभ क्षेत्र सार्वजनिक हैं और मैं बसने और पाने के लिए परेशान नहीं करता हूं। (मेरी राय में कोड को प्रबंधित करना आसान है और वास्तव में अधिक पठनीय है)

इसलिए मैंने कुछ लिखा (यह वास्तव में बहुत कठिन नहीं है) जो मेरी जरूरतों के अनुरूप हो (मान लिया कि वर्ग में बिना आर्गन के सार्वजनिक कंस्ट्रक्टर है) और इसे उपयोगिता वर्ग में निकाला जा सकता है

  public Effect copyUsingReflection() {
    Constructor constructorToUse = null;
    for (Constructor constructor : this.getClass().getConstructors()) {
      if (constructor.getParameterTypes().length == 0) {
        constructorToUse = constructor;
        constructorToUse.setAccessible(true);
      }
    }
    if (constructorToUse != null) {
      try {
        Effect copyOfEffect = (Effect) constructorToUse.newInstance();
        for (Field field : this.getClass().getFields()) {
          try {
            Object valueToCopy = field.get(this);
            //if it has field of the same type (Effect in this case), call the method to copy it recursively
            if (valueToCopy instanceof Effect) {
              valueToCopy = ((Effect) valueToCopy).copyUsingReflection();
            }
            //TODO add here other special types of fields, like Maps, Lists, etc.
            field.set(copyOfEffect, valueToCopy);
          } catch (IllegalArgumentException | IllegalAccessException ex) {
            Logger.getLogger(Effect.class.getName()).log(Level.SEVERE, null, ex);
          }
        }
        return copyOfEffect;
      } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
        Logger.getLogger(Effect.class.getName()).log(Level.SEVERE, null, ex);
      }
    }
    return null;
  }

Antipattern: Reinvent पहिया
Spektakulatius

0

म्लाडेन के मूल विचार ने काम किया (धन्यवाद), लेकिन मजबूत होने के लिए कुछ बदलावों की आवश्यकता थी, इसलिए मैंने उन्हें यहां योगदान दिया।

एकमात्र स्थान जहां इस प्रकार के समाधान का उपयोग किया जाना चाहिए, यदि आप ऑब्जेक्ट को क्लोन करना चाहते हैं, लेकिन ऐसा नहीं कर सकते क्योंकि यह एक प्रबंधित ऑब्जेक्ट है। यदि आप उन वस्तुओं के लिए पर्याप्त भाग्यशाली हैं जिनके पास सभी क्षेत्रों के लिए 100% साइड-इफ़ेक्ट फ्री सेटर हैं, तो आपको निश्चित रूप से बीन यूटिल्स विकल्प का उपयोग करना चाहिए।

यहां, मैं कोड को सरल बनाने के लिए lang3 की उपयोगिता विधियों का उपयोग करता हूं, इसलिए यदि आप इसे पेस्ट करते हैं, तो आपको पहले Apache की lb3 लाइब्रेरी को आयात करना होगा।

कोड को कॉपी करें

static public <X extends Object> X copy(X object, String... skipFields) {
        Constructor constructorToUse = null;
        for (Constructor constructor : object.getClass().getConstructors()) {
            if (constructor.getParameterTypes().length == 0) {
                constructorToUse = constructor;
                constructorToUse.setAccessible(true);
                break;
            }
        }
        if (constructorToUse == null) {
            throw new IllegalStateException(object + " must have a zero arg constructor in order to be copied");
        }
        X copy;
        try {
            copy = (X) constructorToUse.newInstance();

            for (Field field : FieldUtils.getAllFields(object.getClass())) {
                if (Modifier.isStatic(field.getModifiers())) {
                    continue;
                }

                //Avoid the fields that you don't want to copy. Note, if you pass in "id", it will skip any field with "id" in it. So be careful.
                if (StringUtils.containsAny(field.getName(), skipFields)) {
                    continue;
                }

                field.setAccessible(true);

                Object valueToCopy = field.get(object);
                //TODO add here other special types of fields, like Maps, Lists, etc.
                field.set(copy, valueToCopy);

            }

        } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
                | InvocationTargetException e) {
            throw new IllegalStateException("Could not copy " + object, e);
        }
        return copy;
}

0
    public <T1 extends Object, T2 extends Object> void copy(T1 origEntity, T2 destEntity) {
        DozerBeanMapper mapper = new DozerBeanMapper();
        mapper.map(origEntity,destEntity);
    }

 <dependency>
            <groupId>net.sf.dozer</groupId>
            <artifactId>dozer</artifactId>
            <version>5.4.0</version>
        </dependency>

इस mapper.map में समस्याएं हैं, एक इकाई में यह प्राथमिक कुंजी की नकल नहीं कर रहा है
मोहम्मद रफीक

0
public static <T> void copyAvalableFields(@NotNull T source, @NotNull T target) throws IllegalAccessException {
    Field[] fields = source.getClass().getDeclaredFields();
    for (Field field : fields) {
        if (!Modifier.isStatic(field.getModifiers())
                && !Modifier.isFinal(field.getModifiers())) {
            field.set(target, field.get(source));
        }
    }
}

हम कक्षा के सभी क्षेत्रों को पढ़ते हैं। परिणाम से गैर-स्थिर और गैर-अंतिम फ़ील्ड फ़िल्टर करें। लेकिन गैर-सार्वजनिक क्षेत्रों तक पहुँचने में त्रुटि हो सकती है। उदाहरण के लिए, यदि यह फ़ंक्शन समान वर्ग में है, और कॉपी की जा रही कक्षा में सार्वजनिक फ़ील्ड नहीं हैं, तो एक एक्सेस त्रुटि उत्पन्न होगी। समाधान इस फ़ंक्शन को एक ही पैकेज में रखने या सार्वजनिक या इस कोड में लूप कॉल फ़ील्ड के अंदर पहुंच बदलने में हो सकता है। असफल (सत्य); खेतों को क्या उपलब्ध कराएगा


हालांकि यह कोड प्रश्न का हल प्रदान कर सकता है, इसलिए संदर्भ जोड़ना बेहतर है कि यह क्यों / कैसे काम करता है। यह भविष्य के उपयोगकर्ताओं को सीखने में मदद कर सकता है, और उस ज्ञान को अपने कोड में लागू कर सकता है। जब कोड समझाया जाता है, तो आपको उपयोगकर्ताओं से upvotes के रूप में सकारात्मक प्रतिक्रिया मिलने की संभावना होती है।
बोरवम
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.