Tüm değerleri yansıtma yoluyla bir sınıftaki alanlardan diğerine kopyalayın


82

Temelde başka bir sınıfın kopyası olan bir sınıfım var.

public class A {
  int a;
  String b;
}

public class CopyA {
  int a;
  String b;
}

Yaptığım şey, bir web servis çağrısı yoluyla göndermeden önce sınıftan değerleri Aiçine koymak . Şimdi bir yansıması-yöntemi oluşturmak istiyorum sınıftan temelde kopyalar (isim ve türe göre) aynıdır tüm alanları sınıfa .CopyACopyAACopyA

Bunu nasıl yapabilirim?

Şu ana kadar sahip olduğum şey bu, ama pek işe yaramıyor. Sanırım buradaki problem, döngüye girdiğim alanda bir alan oluşturmaya çalışıyorum.

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();
            }

        }
    }

Eminim bunu zaten bir şekilde yapmış biri olmalı



Evet veya Apache Jakarta'dan BeanUtils.
Shaun F

Yanıtlar:


102

Üçüncü parti bir kitaplık kullanmaktan çekinmiyorsanız , Apache Commons'tan BeanUtilscopyProperties(Object, Object) .


13
Görünüşe göre BeanUtils null Tarih alanlarıyla çalışmıyor. Bu sizin için bir sorunsa Apache PropertyUtils'i kullanın: mail-archive.com/user@commons.apache.org/msg02246.html
ripper234

10
Görünüşe göre bu, alıcı ve ayarlayıcı olmayan özel alanlar için çalışmıyor. Özellikler yerine doğrudan alanlarla çalışan bir çözüm var mı?
Andrea Ratto

Alıcılar olmadan düz halka açık alanlarda da
çalışmaz

17

Neden gson kitaplığını kullanmıyorsunuz https://github.com/google/gson

sadece A Sınıfını json dizesine dönüştürürsünüz. Ardından aşağıdaki kodu kullanarak jsonString'i size subClass (CopyA) 'ya dönüştürün:

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

Neden aynı zamanda büyük olabilecek başka bir String oluşturalım? Burada cevaplar olarak tanımlanan daha iyi alternatifler var. En azından biz (sektör) dize gösterimleri için XML'den json'a ilerledik, ancak yine de her şeyin herhangi bir şansla bu dizgi temsiline geçirilmesini istemiyoruz ...
arntg

Lütfen yansıma kullanılırken dizenin bir yan ürün olduğunu unutmayın. Senin aracılığın bile onu kurtarmadı !! Bu, java'ya yeni başlayanlar için bir cevaptır ve kısa ve temiz bir şekilde olmayı hedefler. @arntg
Eric Ho

Yine de hem kaynak hem de hedef nesneler üzerinde düşünmeye ihtiyacınız olacak, ancak şimdi ikili / metin / ikili biçimlendirme ve ayrıştırma ek yükünü de tanıtıyorsunuz.
Evvo

Kodu karıştırmak için Proguard kullanıyorsanız dikkatli olun. Eğer kullanırsanız, bu kod çalışmayacaktır.
SebastiaoRealino

8

BeanUtils yalnızca genel alanları kopyalar ve biraz yavaştır. Bunun yerine alıcı ve ayarlayıcı yöntemlerini kullanın.

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;
    }

BeanUtils, alıcılar / ayarlayıcılar halka açık olduğu sürece özel alanlarda gayet iyi çalışır. Performansla ilgili olarak, herhangi bir kıyaslama yapmadım, ancak iç gözlem yaptığı çekirdeklerde bazı dahili önbellekleme yaptığına inanıyorum.
Greg Case

2
bu sadece iki fasulye aynı veri tipine sahip alanlara sahipse işe yarar.
TimeToCodeTheRoad

@To Kra Sadece o alan için alıcı / ayarlayıcıya sahipseniz işe yarar.
WoLfPwNeR

8

İşte çalışan ve test edilmiş bir çözüm. Eşlemenin derinliğini sınıf hiyerarşisinde kontrol edebilirsiniz.

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
Benzer bir çözüm yarattım. Alan adlarını alan haritasına bir sınıfı önbelleğe aldım.
Orden

Çözüm güzel ama bu çizgide bir problemin olacak i.remove(). Yineleyici yaratmış olsanız bile remove, List' s'i arayamazsınız iterator. O olmalıArrayList
Farid

Farid, CollectFields () ArrayList nesneleri oluşturduğundan, kaldırma işlemi bir sorun olamaz.
JHead

5

Çözümüm:

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;
}

Bunun özel nesneler için işe yaramadığını sanmıyorum. Yalnızca ebeveyni olmayan ve yalnızca ilkel alanları olan bir sınıfınız varsa
Shervin Asgari

Süper sınıf alanlarını kaplamak için 'getAllModelFields' özel yöntemini kullanıyorum
Mohsen Kashi

4

İlk bağımsız değişken alan değil tooF.set(), hedef nesne ( too) olmalıdır ve ikinci bağımsız değişken değerin geldiği alan değil değer olmalıdır . (Değeri elde etmek için, fromF.get()bu durumda bir hedef nesneyi yeniden çağırmanız gerekir.from .)

Yansıma API'sinin çoğu bu şekilde çalışır. FieldNesneleri, Methodnesneleri vb. Bir örnekten değil, sınıftan alırsınız , bu nedenle bunları kullanmak için (statikler hariç) genellikle onlara bir örnek iletmeniz gerekir.



4

Bu geç bir gönderi, ancak gelecekte insanlar için hala etkili olabilir.

Spring, BeanUtils.copyProperties(srcObj, tarObj)her iki sınıfın üye değişkenlerinin adları aynı olduğunda kaynak nesneden hedef nesneye değerleri kopyalayan bir yardımcı program sağlar .

Bir tarih dönüşümü varsa (örneğin, Tarihe Dize) 'null' hedef nesneye kopyalanacaktır. Daha sonra, tarihin değerlerini gerektiği gibi açıkça belirleyebiliriz.

The BeanUtils Apache Common , veri türleri uyuşmazlığı olduğunda bir hata atıyor (özellikle Tarihe ve Tarihten dönüştürme)

Bu yardımcı olur umarım!


Bu, kabul edilen stackoverflow.com/a/1667911/4589003 yanıtından başka ek bilgi sağlamaz
Sudip Bhandari

3

Sanırım dozer deneyebilirsiniz . Fasulyeden fasulyeye dönüşüm için iyi bir desteğe sahiptir. Kullanımı da kolaydır. Yay uygulamanıza enjekte edebilir veya kavanozu sınıf yoluna ekleyebilir ve bitti.

Durumunuza bir örnek olarak:

 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. BeanUtils veya Apache Commons kullanmadan

  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));
         }
    }
    

Bu çalışan bir çözüm değil, iyi bir başlangıç ​​noktasıdır. Alanların, yalnızca her iki sınıfta da bulunan statik olmayan ve genel alanları işlemek için filtrelenmesi gerekir.
JHead

Bu, ebeveyn sınıflarındaki alanları yok saymaz mı?
Evvo

2

Baharın yerleşik bir BeanUtils.copyPropertiesyöntemi vardır. Ancak alıcılar / ayarlayıcılar olmadan sınıflarla çalışmaz. JSON serileştirme / seriyi kaldırma, alanları kopyalamak için başka bir seçenek olabilir. Jackson bu amaçla kullanılabilir. Spring kullanıyorsanız Çoğu durumda, Jackson zaten bağımlılık listenizdedir.

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

1

Orika, bayt kodu oluşturma yoluyla yaptığı için basit, daha hızlı fasulye haritalama çerçevesidir. Farklı adlarla iç içe geçmiş eşlemeler ve eşlemeler yapar. Daha fazla ayrıntı için lütfen burayı kontrol edin Örnek eşleme karmaşık görünebilir, ancak karmaşık senaryolar için bu basit olacaktır.

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);

Bu sorunun ne istediğini yapmaz. SerializationUtils.clone()aynı sınıftan yeni bir nesne verecek. Ek olarak, yalnızca serileştirilebilir sınıflarda çalışır.
Kirby


1

Android Uygulamaları Geliştirmem için benim için iyi çalışan yukarıdaki sorunu Kotlin'de çözdüm:

 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

Bu nedenle başka bir JAR dosyasına bağımlılık eklemek istemedim, bu yüzden ihtiyaçlarıma uygun bir şey yazdım. Ben fjorm ait uylaşımını https://code.google.com/p/fjorm/ benim genellikle erişilebilir alanlar kamu olduklarını ve ben yazma belirleyiciler ve alıcılar için zahmet etmeyin demekse. (bence kodun yönetimi daha kolay ve aslında daha okunabilir)

Bu yüzden, ihtiyaçlarıma uyan bir şey yazdım (aslında çok da zor değil) (sınıfın bağımsız kurucuya sahip olduğunu varsayar) ve bu, fayda sınıfına çıkarılabilir.

  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: Tekerleği Yeniden
Keşfedin

0

Mladen'in temel fikri işe yaradı (teşekkürler), ancak sağlam olması için birkaç değişikliğe ihtiyacı vardı, bu yüzden burada onlara katkıda bulundum.

Bu tür bir çözümün kullanılması gereken tek yer, nesneyi klonlamak istiyorsanız, ancak yönetilen bir nesne olduğu için yapamazsınız. Tüm alanlar için% 100 yan etkisiz ayarlayıcılara sahip olan nesnelere sahip olacak kadar şanslıysanız, bunun yerine kesinlikle BeanUtils seçeneğini kullanmalısınız.

Burada, kodu basitleştirmek için lang3'ün yardımcı yöntemlerini kullanıyorum, bu yüzden eğer onu yapıştırırsanız, önce Apache'nin lang3 kitaplığını içe aktarmanız gerekir.

Kodu kopyala

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>

Bu mapper.map'in sorunları var, bir varlıkta Birincil anahtarları kopyalamıyor
Mohammed Rafeeq

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));
        }
    }
}

Sınıfın tüm alanlarını okuyoruz. Sonuçtan statik olmayan ve nihai olmayan alanları filtreleyin. Ancak herkese açık olmayan alanlara erişimde bir hata olabilir. Örneğin, bu işlev aynı sınıftaysa ve kopyalanan sınıf genel alanlar içermiyorsa, bir erişim hatası ortaya çıkar. Çözüm, bu işlevi aynı pakete yerleştirmek veya halka veya bu koda erişimi döngü çağrısı alanı içinde değiştirmek olabilir. SetAccessible (true); alanları ne kullanılabilir hale getirecek


Bu kod soruya bir çözüm sunabilirken, neden / nasıl çalıştığına ilişkin bağlam eklemek daha iyidir. Bu, gelecekteki kullanıcıların öğrenmesine ve bu bilgiyi kendi kodlarına uygulamasına yardımcı olabilir. Ayrıca, kod açıklandığında kullanıcılardan olumlu oylar şeklinde olumlu geri bildirimler almanız da olasıdır.
borchvm
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.