Java中,如何使用超类初始化子类?

14
public class Base {
  //long list of attributes
  // no Constructor using fields
  // no init methode 
  // i cannot change this class
}

现在我扩展了基类,如下所示:

public class subClass extends Base{
   private boolean selected;

   ...
   getter und setter
   ...
}

我成为了一个 Base 对象的列表 List<Base>

但是我需要同样的列表,但作为 List<SubClass>

有没有一种方法可以从 Base 类初始化 Subclass?

示例:

for(Base b: list){
   SubClass sub = (SubClass)b; // Thats wrong i know
   if(...){
       sub.setSelected(true);
   }
   newList.add(sub);
}
我试图避免手动初始化Base Class每个属性到SubClass。
如评论中所要求,我更新了我的问题: 上面的设计只是一个例子。我确切的问题是:
为什么将BaseClass转换为SubClass (因为Subclass扩展自BaseClass) 是不可能的?为什么Java不允许我做以下操作:
例如:
Class Base{
   private String name;
.....
}
Class SubClass extends Base{
   private String title;
}

然后

Base b = DBController.getById(...);
SubClass sub = (SubClass)b; 

之后sub对象应该拥有来自b对象的属性名,并且title属性为空。

为什么在Java中不是这种情况?

对我的糟糕英语表示抱歉,谢谢。


2
为什么要从超类初始化子类?这听起来最好是不明智的,最坏的情况下可能会很危险。 - Hovercraft Full Of Eels
List<Base> 字段定义在哪里?是在 Base 类本身中吗?您是否可以控制它的声明位置和初始化方式? - Perception
1
我强烈建议你去了解“优先使用组合而非继承”——在谷歌上搜索一下。你会发现关于这个问题有一些有趣的讨论。我之所以这么说是因为看起来你正在拼命滥用特权。 - Bill K
如果Base类没有构造函数或初始化方法,你如何避免手动初始化Base类属性?你是试图将Base类转换为子类吗? - Jake Greene
是的,@Jake,那正是我想做的。 - Rami.Q
显示剩余2条评论
3个回答

7

如果你有一个 List<Base>,那么你不能将它转换成一个List<SubClass>。主要原因是列表可能不包含SubClass的实例。你能做的最好的事情是:

List<SubClass> newList = new List<SubClass>();
for(Base b: list){
    if (b instanceof SubClass) {
        SubClass sub = (SubClass)b;
        . . .
        newList.add(sub);
    }
}

然而,通常情况下,当您发现自己在做这种事情时,说明您的设计存在问题。您可能希望避免子类化 Base 并使用组合

编辑 根据您的评论,似乎您想要使用 Base 实例列表作为起点来构建 SubClass 实例列表。一种方法是为 SubClass 定义一个构造函数,该函数以 Base 作为参数。

public class SubClass extends Base{
    private boolean selected;

    public SubClass() {
        // default constructor
    }

    public SubClass(Base original) {
        // copy constructor -- initialize some fields from
        // values in original, others with default values
    }
    ...
    getter und setter
    ...
}

然后,您可以使用以下代码构建新列表:

List<SubClass> newList = new List<SubClass>();
for(Base b: list){
    SubClass sub = new SubClass(b);
    . . .
    newList.add(sub);
}

感谢您的回答。该列表仅具有Base类型的对象,因此列表中没有Subclass实例。但是为什么强制转换类是错误的?在强制转换后,子类中的属性可能为空,我可以像上面的示例一样手动设置它。您对此有何看法? - Rami.Q
我的意思是,在强制转换后,子类应该具有基类的所有属性,而子类中其余的属性将为null。 - Rami.Q
@Rami.Q - 好的,你的评论让我明白了你想要什么。我修改了我的答案来解决这个问题。 - Ted Hopp
谢谢你的回答。我接受了它,因为我这样做。但这仍然不是最好的解决方案。Oracle应该为我们提供从基类自动转换为子类的功能。类似于属性联合。 - Rami.Q

3
有一种方法:使用基于Java Beans规范的各种操作。
例如: Commons BeanUtils
for( Base base: list ){
   SubClass sub = new SubClass();
   PropertyUtilsBean.copyProperties( sub, base );
   if(...){
       sub.setSelected(true);
   }
   newList.add(sub);
}

这个功能基于相同名称的get/set方法实现,不会复制内部字段。

如果你需要复制内部字段,使用javax.lang.reflect实际上并不难实现。


2

您似乎有一个具有许多属性且没有简单设置所有属性的类。现在,您遇到了需要具有附加属性的类的问题,但必须处理基类的混乱。

我建议,不要创建子类和强制转换,而是在丑陋的类周围创建一个包装器类:

public class BigDumbClass {
    // A lot of attributes
    // No Constructor
    // No init method
}

public class Wrapper {
    private BigDumbClass base;
    private boolean selected;

    public Wrapper(BigDumbClass base) {
        this.base = base;
        this.selected = false;
    }

    //getters and setters
}

现在,当你需要创建一个新列表时,你可以将所有内容包含在旧列表中。
List<BigDumbClass> oldList = someData();
List<Wrapper> wraps = aNewList();
for (BigDumbClass bigDumb : oldList) {
    Wrapper wrap = new Wrapper(bigDumb);
    if (someCondition()) {
        wrap.setSelected(true);
    }
    wraps.add(wrap);
}

理想情况下,BigDumbClass应该实现一个接口,Wrapper也可以实现该接口,从而允许包装器将所有调用推迟到其所包装的实例。
public class BigDumbClass implements SharedInterface {
    // All the stuff outlined above
}

public class Wrapper implements SharedInterface {
    // All the stuff outlined above

    // Methods defined in SharedInterface
    public void doSomething() {
        base.doSomething();
    }
}

否则,您可以为实例提供一个getter并直接访问它。
BigDumbClass base = wrapper.getBase();
base.doSomething();

仅仅因为你有一个超类的引用并不意味着你指向的是超类。'Base baseRef' 可能指向 SubClass1,所以 'SubClass2 subRef = (SubClass2)baseRef' 不是一个有效的转换。 - Jake Greene

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接