在数组中正确使用协方差

时间:2011-02-13 02:06:37

标签: java arrays covariance

基类有一个数据成员,它是一个对象A[]的数组,并且是一个访问器方法。我希望能够覆盖访问者以返回B[],其中BA的子类。

在Java 5.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类型,并且我希望每次访问客户端的数组元素时都不得不向患者投降

6 个答案:

答案 0 :(得分:2)

怎么样只是

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

答案 1 :(得分:2)

不可能。

您可以使用List<A>,将其投放到List<B>,或使用通用List<T>。性能应该非常接近阵列。

答案 2 :(得分:2)

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

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

现在,因为他们共享一个共同的界面。这将按你的意愿工作。

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

答案 3 :(得分:0)

如果您可以更改超级类,您可能希望像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的内容,则

as将失败并显示B

答案 4 :(得分:0)

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

查看Covariance

上此维基百科页面中的“C#和Java中的数组”部分

答案 5 :(得分:0)

登机时你可以升级/降级你的课程,但是用Java或任何常用的面向对象语言你都不能将Fruit降级为Apple,即使你可以将Apple的视图升级为被视为Apple一个水果。

水果是苹果和橘子的超级类。

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原则的问题。 OO编程是一种态度。当你的态度正确时,你就不会遇到这样的困境,因为你甚至都不会考虑它。

请原谅我说,你遇到这个问题表明你可能是一个视觉基础(或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概念和您的世界观都会被破坏。

因此,Messr Cox的建议是正确的。您必须使用接口作为合同声明。您必须了解并对您正在编写的程序以及整个应用程序中的合同流程进行自发的心理设计。正如您在购买杂货时,您可以与超市自发地签订水果,蔬菜,调味品等合同。

您需要停止并停止尝试从其超类实例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,这意味着你需要了解你正在编程的业务和流程,就像经理需要了解食物一样的层次结构。

程序员和数据模式设计人员必须具备他们编程的流程和业务的专业知识。

你也可能屈服于史蒂夫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);

像往常一样,我打字过快可能会产生一些错别字和错位。如果是的话,原谅我。