Java 数组中协方差的正确使用

Java 数组中协方差的正确使用,java,arrays,covariance,Java,Arrays,Covariance,基类有一个数据成员,它是一个objecta[]数组和一个访问器方法。我希望能够越过访问器返回aB[],其中B是a的子类 在Java5.0中,它将允许我这样做,因为数组是共变的,但我得到一个 ClassCastException当我尝试执行以下操作时: class Business { A[] clients; A[] getClientList() { return clients; } } class DoctorBusiness extends Business {

基类有一个数据成员,它是一个object
a[]
数组和一个访问器方法。我希望能够越过访问器返回a
B[]
,其中
B
a
的子类

在Java5.0中,它将允许我这样做,因为数组是共变的,但我得到一个 ClassCastException当我尝试执行以下操作时:

class Business {
    A[] clients;
    A[] getClientList() { return clients; } 
}  

class DoctorBusiness extends Business {
   B[] getClientList(){
      return super.clients; 
     //this line thwoes a classcastexception
  } 
 }
其中客户机对应于A,患者对应于B,患者扩展客户机。
我怎样才能避开这个?我知道数组中的所有对象都将是
B
类型,并且希望避免每次访问客户端的数组元素时都必须向下转换到Patient上

class A{}
class B extends A{}

public class Test<T extends A> {
    T[] t;

    public T[] get()
    {
        return t;
    }

    public static void main(String args[])
    {
        Test<B> t2 = new Test<B>();
        B[] b = t2.get();
    }
}
class A{}
类B扩展了{}
公开课考试{
T[]T;
公共T[]get()
{
返回t;
}
公共静态void main(字符串参数[])
{
测试t2=新测试();
B[]B=t2.get();
}
}
不可能


您可以使用
列表
,将其强制转换为
列表
,或者使用通用的
列表
。性能应该非常接近array。

请参阅我在您的问题中发表的评论。但如果我对你的问题理解正确,那么以下内容适用:

A不是B的子类型。这就是为什么会出现异常。基于你正在尝试做的事情,它将不起作用

我想到了一个解决方案,可以满足你的需求。进入界面的美丽概念=D

public interface ICommon 
{}

public class B extends A 
{
    protected B[] b;

    public ICommon[] Get()
    {
        return b;
    }

    public ICommon[] GetAncestor()
    {
        return a;
    }
}

public class A implements ICommon  
{
    protected A[] a;

    public ICommon[] Get()
    {
    return a;
    }
}
现在,因为它们共享一个公共接口。这将按照您的要求工作


您需要公开允许按该类型使用它们的方法,或者在使用它们时必须求助于强制转换。这就是缺点

如果您可以更改超类,您可能希望按照Steve B.的建议使用泛型对其进行改造,或者至少使数组创建可由子类覆盖:

class Super {
    private A[] as;

    protected A[] newDataArray(int length) {
        return new A[length];
    }

    public A[] get() {
        return as;
    }
}

class Sub {
    @Override protected B[] newDataArray(int length) {
        return new B[length];
    }

    @Override public B[] get() {
        return (B[]) super.get(); // we know it's a B[] because we created it
    }
}
如果无法更改超类,则只能执行以下操作:

class Sub extends Super {
    @Override public B[] get() {
        A[] as = super.get();
        return Arrays.copyOf(as, as.length, B[].class);
    }
}

copyOf
将因
ArrayStoreException
而失败,因为
包含不可分配给
B

的内容,协方差对Java中的数组和泛型无效,因为它会破坏静态类型安全。解决这个设计问题的唯一方法是在类Business和DoctorBusiness中使用单独的列表


查看维基百科页面上的“C#and Java中的数组”部分,在登机时你可以升级/降级你的类,但在Java或任何常用的面向对象语言中,你不能将一个水果降级为被视为苹果,即使你可以将苹果的视图升级为被视为水果

水果是苹果和橙子的高级品种

Fruit f;
Apple, Orange extend Fruit;
Apple a;
Orange b;
根据世界常识,如果你把f分配给苹果

f = apple;
不能将f返回为橙色

但是,, 如果您已分配了一个:

a = apple;
你可以退货

Fruit get(){
 return a;
}
您可以将苹果作为水果返回,但不能将水果作为苹果返回,因为如果f已被指定为橙色,该怎么办

然而,您可能会说,“B扩展了A是一对一的关系,所以我不必担心A的实例被分配给B以外的任何其他类。”这不是面向对象语言编译器的看法。OO编译器没有关系模型使其能够检查并限制类扩展为1对1

因此,回到OO原则的问题上来。面向对象编程是一种态度。当你正确地对待你的态度时,你不会遇到这样的两难处境,因为你甚至不会考虑它。p> 请原谅我这么说,您遇到这个问题表明您可能是一名visual basic(或php或perl程序员)试图将线性编程的态度(与所有vb程序员一样)融入C#或Java的维度。对于c#/java程序员和vb程序员来说,当他们相遇时,这确实是令人沮丧的

作为一个OO程序员,您对对象层次结构的可视化将自发地(一些语言不那么敏锐的人会使用“自动”一词)决定您的编程风格。正如你对水果等级的看法,当你的购物车里有橙子时,你甚至不会想到为苹果付钱

因此,在下面的示例中,您可以将A的实例设置为B的实例,然后返回A的实例。 但不能将A的实例作为B的实例返回

class A{}
class B extends A{}

public class Test {
    A[] a;
    B[] b;

    public A[] get()
    {
        return a;
    }

    public void set(A[] a){
        this.a = a;
    }


  // Illegal
    public B[] getB(){
      return a;
    }

    public static void main(String args[])
    {
        Test t2 = new Test();
        B[] b = new B[0];
        t2.set(b);
        A[] a = t2.get();
    }
}
如果您坚持从A实例返回B,那么您的OO概念和世界视图都将被破坏

因此,考克斯先生的建议是正确的。您必须使用接口作为合同声明。您必须理解并对正在编写的程序以及应用程序中的合同流进行自发的心理设计。就像当你拿到食品杂货时,你会与超市自发地签订水果、蔬菜、调味品等合同一样

您需要停止尝试从其超类实例A中获取实例B,并重新设计您的态度和思维过程,以自发地设计您需要接口的应用程序

interface Edible{};
class Fruit implements Edible{...}
class Apple extends Fruit {...}

Interface FoodAisle{
  Edible get();
  void set(Edible e)throws WrongFoodException;
}

class FruitSection implements FoodAisle{
  Edible e;
  public Edible get(){
  }
}    

class AppleBucket extends FruitSection{
  Apple a;
  public Apple get(){
    return a;
  }

  public set(Edible e)
  throws WrongFoodException{
    if (!(e instanceof Apple)) throw WrongFoodException
    e = e;
  }
}
在设计一个跨食物扩展世界观时,你需要想象自己能够提出这样一个问题——“如果他们把一束橘子放在水果区的苹果桶里怎么办?”你的世界观会自发地跳起来大喊“我会破例向经理投诉,因为他误报了橘子并被当作苹果出售。”. 类似地,无论您在做什么,如果您坚持尝试从a实例中获得B,这意味着您需要了解您正在编程的业务和流程,就像经理需要了解食品层次结构一样

程序员和数据模式设计人员必须对他们正在编程的流程和业务具有专业知识
class FruitSection<E extends Edible>{
  E[] e;
  public Edible get(){
  }

  void set(E e){
  }
}


FruitSection<Fruit> fsect = new FruitSection<Fruit>();
Fruit[] ff = { .....};
fsect.set(ff);

AppleBucket<Apple> abuck = new FruitSection<Apple>();
Apple[] aa = { /* commalist of apples */};
abuck.set(aa);
fsect.set(aa);
abuck.set(ff);