数组中协方差的正确使用

发布于 2024-10-17 12:47:45 字数 585 浏览 4 评论 0原文

基类有一个数据成员,它是一个对象数组 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
  } 
 }

其中 Client 对应于 A,Patient 对应于 B,Patient 扩展 Client。
我该如何解决这个问题?我知道数组中的所有对象都将是 B 类型,并且希望避免每次访问客户端的数组元素时都必须向下转换为 Patient

base class has a data-member which is an array of object A[] and an accessor method for that. I want to be able to over-ride the accessor to return a B[], where B is subclass of A.

In Java 5.0 on it will allow me to do that because arrays are co-variant, but I get a
ClassCastException when I try to something like the following:

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

class DoctorBusiness extends Business {
   B[] getClientList(){
      return super.clients; 
     //this line thwoes a classcastexception
  } 
 }

where Client corresponds to A and Patient corresponds to B and Patient extends Client.
How do I get round this ? I know that all the objects in the array are going to be of type B and would like to avoid having to cast down to Patient every-time I access array elements of clients

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

是伱的 2024-10-24 12:47:45

刚刚怎么样

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

What about just

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();
    }
}
雨巷深深 2024-10-24 12:47:45

impossible.

you can use List<A>, cast it to List<B>, or have a generic List<T>. The performance should be very close to array.

与之呼应 2024-10-24 12:47:45

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

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

现在,因为他们共享一个公共接口。这将按您的意愿工作。

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

See the comment I posted in your question. But if I am correct in understanding your question then the following applies:

A is not a sub type of B. That is why you are getting the exception. Based on what you are trying to do, it will not work.

I thought of a solution that will allow what you want. Enters the beautiful concept of the Interface!!! =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;
    }
}

Now since they share a common interface. This will work as you wanted.

You will need to either expose methods that will allow you to use them by that type OR, you have to resort to casting when using them. That is the drawback

救星 2024-10-24 12:47:45

如果您可以更改超类,您可能希望按照 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);
    }
}

:如果 as 包含无法分配给 B 的内容,>copyOf 将失败并出现 ArrayStoreException

If you can change the super class, you might wish to retrofit it with generics as Steve B. suggests, or at least make the array creation overridable by the subclass:

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

If you can't change the superclass, you can only do:

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

copyOf will fail with an ArrayStoreException should as contains something not assignable to B.

兔小萌 2024-10-24 12:47:45

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

查看此维基百科页面中的“C# 和 Java 中的数组”部分,了解协方差

Covariance does not hold good for arrays and generics in Java because it kills static type safety. The only way to get around this design problem is to use separate lists in classes Business and DoctorBusiness.

Check out the "Arrays in C# and Java" section in this wikipedia page on Covariance

初见终念 2024-10-24 12:47:45

在登机时,您可以升级/降级您的类,但在 Java 或任何常见的面向对象语言中,您不能将水果降级为被视为苹果,即使您可以升级苹果的视图为被视为水果。

水果是苹果和橙子的超类。

Fruit f;
Apple, Orange extend Fruit;
Apple a;
Orange b;

根据常识,如果将 f 分配给 apple,

f = apple;

则不能将 f 返回为 Orange;

然而,
如果您分配了 a:

a = apple;

您将能够返回 Fruit

Fruit get(){
 return a;
}

您可以将苹果作为 Fruit 返回,但不能将 Fruit 作为苹果返回,因为如果 f 已经被分配为橙子怎么办?

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

因此,回到OO原则的问题。 OO编程是一种态度。当你的态度正确时,你就不会遇到这样的困境,因为你根本不会考虑它。

请原谅我这么说,您遇到这个问题表明您可能是一个 Visual Basic(或 php 或 perl 程序员),试图将线性编程态度(与所有 vb 程序员一样)融入到 C# 或 Java 的维度中。当c#/java程序员和vb程序员相遇时,确实令人沮丧。

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

因此,在下面的示例中,您可以将 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,则意味着你需要了解你正在编程的业务和流程,就像经理需要了解食物一样层次结构。

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

您也可以屈服于 Steve 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);

像往常一样,我打字太快可能会产生一些打字错误和错位。如果是这样,请原谅我。

In boarding a plane you can upgrade/downgrade your class, but in Java or any usual object-oriented language you cannot downgrade a Fruit to be perceived as an Apple even though you can upgrade the view of an Apple as being viewed as a Fruit.

Fruit is a super class of apple and orange.

Fruit f;
Apple, Orange extend Fruit;
Apple a;
Orange b;

By common world sense, if you assigned f to apple,

f = apple;

you cannot return f as orange;

However,
If you had assigned a:

a = apple;

you would be able to return Fruit

Fruit get(){
 return a;
}

You can return an apple as a Fruit, but not a Fruit as an apple, because what if f had been already assigned as an orange?

However, you may say, "Well B extends A is a one-to-one relationship, so I should have no worries about an instance of A being assigned to any other class other than a B." That is not how object-oriented language compilers see it. OO compilers do not have a relational model to enable it to inspect and restrict class extensions to 1-to-1.

Therefore, back to the question of OO principles. OO programming is an attitude. When you get your attitude right, you will not encounter such a dilemma because you would not even consider it.

Pardon me for saying this, that your encountering this issue signals that you are probably a visual basic ( or php or perl programmer) trying fit the linear programming attitude (as with all vb programmers) into the dimensionality of C# or Java. It is indeed frustrating both for the c#/java programmer and the vb programmer when both of them meet.

As an OO programmer, your visualization of the object hierarchy will spontaneously (some linguistically less discerning people would use the word "automatically") dictate your programming style. Just as your view of the fruit hierarchy would not even let you think of paying for apples when you actually have oranges in your cart.

Therefore, in the below example, you may set an instance of A to an instance of B and then return an instance of A.
But you cannot return an instance of A as an instance of 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();
    }
}

If you do insist on returning B from an A instance, then both your concept of OO and your world view is broken.

Therefore, messr Cox's suggestion is correct. You have to use an interface as the contract declaration. You have to understand and have a spontaneous mental design of the program you are writing and the flow of contracts across your application. Just as you would have a spontaneous flow of contracts of fruit, vegetables, condiments, etc with the supermarket when you get your groceries.

You need to cease and desist attempting to get an instance B from its superclass instance A and redesign your attitude and mental process to spontaneously design your application that you need an interface.

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

In designing a cross-food extension world view, you need to picture yourself as being able to ask the question - "what if they put a bunch oranges in the apple bucket in the fruit section?" Your worldview would jump up spontaneously to yell "I will make an exception complaint to the manager for misrepresenting oranges and being sold as apples". Similarly, whatever you are doing, if you persist in attempting to get a B from an A instance, it means you need to gain understanding of the business and processes for which you are programming, just as the manager need to have an understanding of food hierarchies.

It is imperative for programmers and data schema designers to have an expert knowledge of the processes and business for which they are programming.

You could also succumb to Steve B's suggestion of genericising your classes. If you choose to use generics, it means that

  • once you instantiate an aisle as for apples, you cannot attempt to get oranges from that aisle.
  • you are using generics because you wish to share routines among fruit buckets but you are not attempting the impossible miracle of turning oranges into apples.

.

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

placing apples into Fruit section is OK.

fsect.set(aa);

But placing any old Fruit in the apple bucket is not acceptable:

abuck.set(ff);

As usual, my typing too fast may yield some typos and misalignment. If so, pardon me.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文