【问题标题】:Java: Creating a subclass object from a parent objectJava:从父对象创建子类对象
【发布时间】:2010-10-12 21:31:20
【问题描述】:

新手 Java 问题。说我有:

public class Car{
  ...
}

public class Truck extends Car{
  ...
}

假设我已经有一个 Car 对象,如何从这个 Car 对象创建一个新的 Truck 对象,以便将 Car 对象的所有值复制到我的新 Truck 对象中? 理想情况下,我可以这样做:

Car c = new Car();
/* ... c gets populated */

Truck t = new Truck(c);
/* would like t to have all of c's values */

我必须编写自己的复制构造函数吗?每次 Car 获得新字段时都必须更新它...

【问题讨论】:

    标签: java inheritance constructor


    【解决方案1】:

    我知道这是一个老问题,但当情况有所改善时,我不想留下过时的答案。

    使用 JSON 要容易得多。将其转换为 JSON 并再次作为子项返回。

    这是一个 Android Kotlin 示例。

        val gson = Gson()    
        val childClass = gson.fromJson(
            gson.toJson(parentObject), 
            object: TypeToken<ChildObject>(){}.type
        ) as ChildObject
    

    我认为在 Java 中基本上是这样的。

    Gson gson = new Gson()
    ChildObject child = (ChildObject) gson.fromJson(
        gson.toJson(parentObject),
        TypeToken<ChildObject>(){}.getType()
    ) 
    

    你已经完成了,没有混乱,只是简单的 json 输入,json 输出。 如果您没有 gson,我相信您还有其他可用的 json 选项。

    这比进行反思和所有那些疯狂的事情更清洁、更快。

    【讨论】:

      【解决方案2】:

      您应该注意上述解决方案的局限性。以下是用于将字段从一个类复制到另一个类的算法的简短摘要。

      • Tom Hawtin:如果你的超类有一个复制构造函数,请使用它。如果没有,您将需要其他解决方案。
      • Christian:如果超类不扩展任何其他类,请使用此选项。此方法不会向上递归复制字段。
      • Sean Patrick Floyd:这是一种向上递归复制所有字段的通用解决方案。请务必阅读 @jett 的评论,即必须添加一行以防止无限循环。

      我复制了 Sean Patrick Floyd 的 analyze 函数,但缺少语句:

      private static Map<String, Field> analyze(Object object) {
          if (object == null) throw new NullPointerException();
      
          Map<String, Field> map = new TreeMap<String, Field>();
      
          Class<?> current = object.getClass();
          while (current != Object.class) {
              Field[] declaredFields = current.getDeclaredFields();
              for (Field field : declaredFields) {
                  if (!Modifier.isStatic(field.getModifiers())) {
                      if (!map.containsKey(field.getName())) {
                          map.put(field.getName(), field);
                      }
                  }
              }
      
              current = current.getSuperclass();   /* The missing statement */
          }
          return map;
      }
      

      【讨论】:

        【解决方案3】:

        您始终可以使用映射框架,例如 Dozer。默认情况下(没有further configuration),它使用getter 和setter 方法将所有同名字段从一个对象映射到另一个对象。

        依赖:

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

        代码:

        import org.dozer.DozerBeanMapper;
        import org.dozer.Mapper;
        
        // ...
        
        Car c = new Car();
        /* ... c gets populated */
        
        Truck t = new Truck();
        Mapper mapper = new DozerBeanMapper();
        mapper.map(c, t);
        /* would like t to have all of c's values */
        

        【讨论】:

          【解决方案4】:

          我必须编写自己的复制构造函数吗?这必须是 每次 Car 获得新字段时都会更新...

          一点也不!

          试试这个方法:

          public class Car{
              ...
          }
          
          public class Truck extends Car{
              ...
          
              public Truck(Car car){
                  copyFields(car, this);
              }
          }
          
          
          public static void copyFields(Object source, Object target) {
                  Field[] fieldsSource = source.getClass().getFields();
                  Field[] fieldsTarget = target.getClass().getFields();
          
                  for (Field fieldTarget : fieldsTarget)
                  {
                      for (Field fieldSource : fieldsSource)
                      {
                          if (fieldTarget.getName().equals(fieldSource.getName()))
                          {
                              try
                              {
                                  fieldTarget.set(target, fieldSource.get(source));
                              }
                              catch (SecurityException e)
                              {
                              }
                              catch (IllegalArgumentException e)
                              {
                              }
                              catch (IllegalAccessException e)
                              {
                              }
                              break;
                          }
                      }
                  }
              }
          

          【讨论】:

          • 由于 OP 想要复制所有字段,因此应该对您的答案进行两个更改:使用 getDeclaredFields 而不是 getFields,并访问 Parent 类的字段,而不是类本身:Field[] fieldsTarget = target.getClass().getSuperclass().getDeclaredFields();
          【解决方案5】:

          你可以使用反射,我可以这样做并且对我来说很好:

          public Child(Parent parent){
              for (Method getMethod : parent.getClass().getMethods()) {
                  if (getMethod.getName().startsWith("get")) {
                      try {
                          Method setMethod = this.getClass().getMethod(getMethod.getName().replace("get", "set"), getMethod.getReturnType());
                          setMethod.invoke(this, getMethod.invoke(parent, (Object[]) null));
          
                      } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
                          //not found set
                      }
                  }
              }
           }
          

          【讨论】:

            【解决方案6】:

            如果您在项目中使用 Spring,则可以使用 ReflectionUtils。

            【讨论】:

            • +1 来自初始测试,这非常有效。为什么春天已经为你做了这些事情?
            • 例如,ReflectionUtils.shallowCopyFieldState(baseObj, extendedObj) 将起作用。
            【解决方案7】:

            您可以使用反射 API 循环遍历每个 Car 字段并将值分配给等效的 Truck 字段。这可以在卡车内完成。此外,它是访问 Car 私有字段的唯一方法 - 至少在自动意义上,前提是没有安全管理器并限制对私有字段的访问。

            【讨论】:

            • 是的,你可以用反射来做这种事情,或者可能用一些预先存在的框架。不过要小心反射。在某些情况下它很好,但它仍然存在一些性能问题,所以不要将它用于大容量的东西。
            【解决方案8】:

            是的,只需向 Truck 添加一个构造函数。您可能还想向 Car 添加一个构造函数,但不一定是公开的:

            public class Car {
                protected Car(Car orig) {
                ...
            }
            
            public class Truck extends Car {
                public Truck(Car orig) {
                    super(orig);
                }
                ...
            }
            

            通常,最好将类设为叶类(您可能希望将它们标记为最终类)或抽象类。

            看起来好像你想要一个Car 对象,然后让同一个实例变成一个Truck。更好的方法是将行为委托给Car (Vehicle) 中的另一个对象。所以:

            public final class Vehicle {
                private VehicleBehaviour behaviour = VehicleBehaviour.CAR;
            
                public void becomeTruck() {
                    this.behaviour =  VehicleBehaviour.TRUCK;
                } 
                ...
            }
            

            如果您实现Cloneable,那么您可以“自动”将对象复制到同一类的实例中。但是,这样做存在许多问题,包括必须复制可变对象的每个字段,这很容易出错并且禁止使用 final。

            【讨论】:

            • 实现Clonableprobably a bad idea
            • @jpaugh 您的链接已有六年历史了...您确定Clonable 在此期间没有改进吗?
            • @Mindwin 我怀疑现在有更好的 Clonable 替代品,是的。使 Clonable 变得糟糕的事情是 API 的一部分。在不破坏向后兼容性的情况下无法更改。我找到了各种参考资料,它们都与上述内容相同,日期各异。
            • @jpaugh 所以这就像孩子把牛排的粘稠部分推到盘子的角落,因为他不想处理它。
            • @Mindwin Clonable 可能更像是我妈妈经常做的牛排:焦脆 --- 一点都不粘! FWIW,我对这个主题的记忆自 10 月以来一直没有刷新。
            【解决方案9】:

            是的,您必须手动执行此操作。您还需要决定复制内容的“深度”。例如,假设 Car 有一个轮胎集合 - 您可以对集合进行 shallow 副本(这样,如果原始对象更改其集合的内容,新对象也会看到更改) 或者你可以做一个 deep 副本来创建一个新的集合。

            (这就是像 String 这样的不可变类型经常派上用场的地方 - 无需克隆它们;您只需复制引用并知道对象的内容不会改变。)

            【讨论】:

              【解决方案10】:

              您将需要一个复制构造函数,但您的复制构造函数可以使用反射来查找两个对象之间的公共字段,从“原型”对象获取它们的值,并将它们设置在子对象上。

              【讨论】:

                【解决方案11】:

                我必须编写自己的复制构造函数吗?每次 Car 获得新字段时都必须更新它...

                基本上,是的 - 您不能只转换 Java 中的对象。

                幸运的是,您不必自己编写所有代码 - 查看 commons-beanutils,特别是像 cloneBean 这样的方法。这还有一个额外的好处,即您不必每次获得新字段时都对其进行更新!

                【讨论】:

                  猜你喜欢
                  • 2017-07-14
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2015-11-17
                  • 2011-09-07
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多