Reputation: 1024
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
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
.
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
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
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
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
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
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