Sao chép tất cả các giá trị từ các trường trong lớp này sang lớp khác thông qua phản chiếu


82

Tôi có một lớp về cơ bản là bản sao của một lớp khác.

public class A {
  int a;
  String b;
}

public class CopyA {
  int a;
  String b;
}

Những gì tôi đang làm là đặt các giá trị từ lớp Avào CopyAtrước khi gửi CopyAqua một cuộc gọi dịch vụ web. Bây giờ tôi muốn tạo một phương thức phản chiếu về cơ bản sao chép tất cả các trường giống hệt nhau (theo tên và kiểu) từ lớp này Asang lớp khác CopyA.

Tôi có thể làm cái này như thế nào?

Đây là những gì tôi có cho đến nay, nhưng nó không hoàn toàn hoạt động. Tôi nghĩ vấn đề ở đây là tôi đang cố gắng thiết lập một trường trên sân mà tôi đang lặp lại.

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

        }
    }

Tôi chắc chắn phải có ai đó đã làm điều này bằng cách nào đó



Yeah hoặc BeanUtils từ Apache Jakarta.
Shaun F

Câu trả lời:


102

Nếu bạn không ngại sử dụng thư viện của bên thứ ba, BeanUtils từ Apache Commons sẽ xử lý điều này khá dễ dàng bằng cách sử dụng copyProperties(Object, Object).


13
Rõ ràng BeanUtils không hoạt động với các trường Ngày rỗng. Sử dụng Apache PropertyUtils nếu điều này là một vấn đề cho bạn: mail-archive.com/user@commons.apache.org/msg02246.html
ripper234

10
Điều này dường như không hoạt động đối với các trường riêng tư không có getter và setters. Bất kỳ giải pháp nào hoạt động trực tiếp với các trường, thay vì các thuộc tính?
Andrea Ratto

Nó không phải làm việc với các lĩnh vực công cộng đơn giản mà không thu khí: stackoverflow.com/questions/34263122/...
Vadzim

17

Tại sao bạn không sử dụng thư viện gson https://github.com/google/gson

bạn chỉ cần chuyển đổi Class A thành chuỗi json. Sau đó, chuyển đổi jsonString thành subClass của bạn (CopyA). Sử dụng mã bên dưới:

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

Tại sao lại tạo một Chuỗi khác cũng có thể lớn? Có những lựa chọn thay thế tốt hơn được mô tả dưới dạng câu trả lời ở đây. Ít nhất chúng ta (ngành) tiến triển từ XML để json cho cơ quan đại diện chuỗi, nhưng chúng tôi vẫn không muốn tất cả mọi thứ trôi qua với chuỗi đại diện tại bất kỳ cơ hội cho ...
arntg

Xin lưu ý rằng chuỗi là một sản phẩm phụ khi sử dụng phản chiếu. Ngay cả thông qua bạn đã không lưu nó !! Đây là câu trả lời cho những người mới bắt đầu sử dụng java và hướng đến mục tiêu ngắn gọn và rõ ràng. @arntg
Eric Ho

Bạn vẫn cần phản ánh về cả đối tượng nguồn và đích, nhưng bây giờ bạn cũng đang giới thiệu định dạng nhị phân / văn bản / nhị phân và phân tích cú pháp.
Evvo

Hãy chú ý nếu bạn sử dụng Proguard để làm xáo trộn mã. Nếu bạn sử dụng nó, mã này sẽ không hoạt động.
SebastiaoRealino

8

BeanUtils sẽ chỉ sao chép các trường công khai và hơi chậm. Thay vào đó, hãy sử dụng các phương thức getter và setter.

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 hoạt động tốt trên các trường riêng tư, miễn là getters / setters là công khai. Về hiệu suất, tôi chưa thực hiện bất kỳ điểm chuẩn nào, nhưng tôi tin rằng nó thực hiện một số bộ nhớ đệm bên trong của bean mà nó đã xem xét bên trong.
Greg Case

2
điều này sẽ chỉ hoạt động nếu hai bean có cùng kiểu dữ liệu của các trường.
TimeToCodeTheRoad vào

@To Kra Nó sẽ chỉ hoạt động nếu bạn có getter / setter cho trường đó.
WoLfPwNeR

8

Đây là một giải pháp đang hoạt động và đã được thử nghiệm. Bạn có thể kiểm soát độ sâu của ánh xạ trong phân cấp lớp.

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
Tôi đã tạo ra một giải pháp tương tự. Tôi đã lưu vào bộ nhớ đệm một lớp cho các tên trường vào bản đồ trường.
Orden

Giải pháp là tốt nhưng bạn sẽ gặp vấn đề trong dòng này i.remove(). Ngay cả khi bạn đã tạo trình lặp, bạn không thể gọi removevào List's iterator. Nó phải làArrayList
Farid

Farid, loại bỏ không phải là một vấn đề vì collectFields () tạo các đối tượng ArrayList.
JHead

5

Giải pháp của tôi:

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

Tôi không nghĩ rằng điều này không hiệu quả với các đối tượng tùy chỉnh. Chỉ khi bạn có một lớp không có cha mẹ và chỉ có các trường nguyên thủy
Shervin Asgari

Đối với các lĩnh vực bao gồm lớp siêu Tôi đang sử dụng phương pháp tùy chỉnh 'getAllModelFields'
Mohsen Kashi

4

Đối số đầu tiên tooF.set()phải là đối tượng đích ( too), không phải trường và đối số thứ hai phải là giá trị , không phải trường mà giá trị đến từ. (Để nhận giá trị, bạn cần gọi fromF.get()- truyền lại một đối tượng đích, trong trường hợp này from.)

Hầu hết các API phản chiếu hoạt động theo cách này. Bạn lấy Fieldcác đối tượng, Methodđối tượng, v.v. từ lớp, không phải từ một cá thể, vì vậy để sử dụng chúng (ngoại trừ các đối tượng tĩnh), bạn thường cần chuyển chúng một thể hiện.



4

Đây là một bài đăng muộn, nhưng vẫn có thể hiệu quả cho mọi người trong tương lai.

Spring cung cấp một tiện ích BeanUtils.copyProperties(srcObj, tarObj)sao chép các giá trị từ đối tượng nguồn sang đối tượng đích khi tên của các biến thành viên của cả hai lớp đều giống nhau.

Nếu có một chuyển đổi ngày tháng, (ví dụ: Chuỗi thành ngày) 'null' sẽ được sao chép vào đối tượng đích. Sau đó, chúng ta có thể thiết lập rõ ràng các giá trị của ngày theo yêu cầu.

BeanUtils từ Apache Commonđưa ra lỗi khi có sự không khớp về kiểu dữ liệu (đặc biệt là chuyển đổi sang và từ Ngày)

Hi vọng điêu nay co ich!


Điều này không cung cấp bất kỳ thông tin bổ sung nào ngoài câu trả lời stackoverflow.com/a/1667911/4589003 được chấp nhận
Sudip Bhandari

3

Tôi nghĩ bạn có thể thử dozer . Nó hỗ trợ tốt cho quá trình chuyển đổi từ đậu sang đậu. Nó cũng dễ sử dụng. Bạn có thể đưa nó vào ứng dụng mùa xuân của mình hoặc thêm jar trong đường dẫn lớp và hoàn tất.

Ví dụ về trường hợp của bạn:

 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. Không sử dụng BeanUtils hoặc Apache Commons

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

Đây không phải là một giải pháp hiệu quả mà là một điểm khởi đầu tốt. Các trường cần được lọc để chỉ xử lý các trường không tĩnh và trường công cộng có trong cả hai lớp.
JHead

Điều này sẽ không bỏ qua các trường trong các lớp cha?
Evvo

2

Spring có một BeanUtils.copyPropertiesphương pháp tích hợp sẵn . Nhưng nó không hoạt động với các lớp không có getters / setters. Tuần tự hóa / giải mã JSON có thể là một tùy chọn khác để sao chép các trường. Jackson có thể được sử dụng cho mục đích này. Nếu bạn đang sử dụng Spring Trong hầu hết các trường hợp, Jackson đã có trong danh sách phụ thuộc của bạn.

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

1

Orika's là khung lập bản đồ bean đơn giản nhanh hơn vì nó thực hiện thông qua việc tạo mã byte. Nó thực hiện các ánh xạ lồng nhau và các ánh xạ với các tên khác nhau. Để biết thêm chi tiết, vui lòng kiểm tra tại đây Lập bản đồ mẫu có thể trông phức tạp, nhưng đối với các tình huống phức tạp thì sẽ đơn giản.

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

Điều này không làm những gì câu hỏi đang yêu cầu. SerializationUtils.clone()sẽ đưa ra một đối tượng mới của cùng một lớp. Ngoài ra, nó chỉ hoạt động trên các lớp có thể tuần tự hóa.
Kirby


1

Tôi đã giải quyết vấn đề trên trong Kotlin hoạt động tốt cho tôi cho Phát triển ứng dụng Android của tôi:

 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

Tôi không muốn thêm phụ thuộc vào tệp JAR khác vì điều này, vì vậy đã viết một cái gì đó phù hợp với nhu cầu của tôi. Tôi tuân theo quy ước của fjorm https://code.google.com/p/fjorm/ , điều đó có nghĩa là các trường thường có thể truy cập của tôi là công khai và tôi không bận tâm đến việc viết setters và getters. (theo ý kiến ​​của tôi, mã dễ quản lý hơn và thực sự dễ đọc hơn)

Vì vậy, tôi đã viết một cái gì đó (nó thực sự không khó lắm) phù hợp với nhu cầu của tôi (giả sử rằng lớp có hàm tạo công khai không có args) và nó có thể được trích xuất vào lớp tiện ích

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

Phản vật chất: Phát minh lại bánh xe
Spektakulatius

0

Ý tưởng cơ bản của Mladen đã hoạt động (cảm ơn), nhưng cần một vài thay đổi để trở nên mạnh mẽ, vì vậy tôi đã đóng góp chúng ở đây.

Nơi duy nhất mà loại giải pháp này nên được sử dụng là nếu bạn muốn sao chép đối tượng, nhưng không thể vì nó là đối tượng được quản lý. Nếu bạn đủ may mắn để có các đối tượng mà tất cả đều có bộ cài đặt 100% không có tác dụng phụ cho tất cả các trường, bạn chắc chắn nên sử dụng tùy chọn BeanUtils để thay thế.

Ở đây, tôi sử dụng các phương thức tiện ích của lang3 để đơn giản hóa mã, vì vậy nếu bạn dán nó, trước tiên bạn phải nhập thư viện lang3 của Apache.

Sao chép mã

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>

Bản đồ mapper.map này có vấn đề, trong một thực thể, nó không sao chép các Khóa chính
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));
        }
    }
}

Chúng tôi đọc tất cả các lĩnh vực của lớp. Lọc các trường không tĩnh và không phải trường cuối cùng khỏi kết quả. Nhưng có thể xảy ra lỗi khi truy cập các trường không công khai. Ví dụ, nếu hàm này nằm trong cùng một lớp và lớp được sao chép không chứa các trường công khai, thì một lỗi truy cập sẽ xảy ra. Giải pháp có thể là đặt chức năng này trong cùng một gói hoặc thay đổi quyền truy cập thành công khai hoặc trong mã này bên trong trường gọi vòng lặp.setAccessible (true); điều gì sẽ làm cho các trường có sẵn


Mặc dù mã này có thể cung cấp giải pháp cho câu hỏi, nhưng tốt hơn là bạn nên thêm ngữ cảnh về lý do / cách nó hoạt động. Điều này có thể giúp người dùng trong tương lai học và áp dụng kiến ​​thức đó vào mã của riêng họ. Bạn cũng có khả năng nhận được phản hồi tích cực từ người dùng dưới dạng phiếu ủng hộ, khi mã được giải thích.
borchvm
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.