mafalda
mafalda

Reputation: 1024

Correct use of co-variance in arrays

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

Upvotes: 4

Views: 311

Answers (6)

Blessed Geek
Blessed Geek

Reputation: 21664

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.

Upvotes: 0

Kowshik
Kowshik

Reputation: 1641

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

Upvotes: 0

meriton
meriton

Reputation: 70574

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.

Upvotes: 0

Matthew Cox
Matthew Cox

Reputation: 13672

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

Upvotes: 2

irreputable
irreputable

Reputation: 45443

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.

Upvotes: 2

Steve B.
Steve B.

Reputation: 57325

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

Upvotes: 2

Related Questions