Reputation: 400
What I'm trying to do:
I'm creating a class hierarchy that represents and has utilities for a lot of linear algebra concepts (both to practice linear algebra and to practice Java, which I'm learning right now). All went pretty well until I decided to add the different types of numbers, that is, integers, rationals, reals and complex (for now).
What I want is to be able to operate with them (for example, inside a Matrix class) without having to care about which number type I'm operating with, and implement all the operations inside different classes representing each number type.
What I thought/tried:
I thought it was a good idea to make an interface called "AnyNumber" (since Number is already taken by the Java API) on which I'd define all the necessary abstract methods, and then implement that interface on each number type class. The problem is, I can't figure out how to handle class types.
Here is what the interface declaration would look like:
public interface AnyNumber {
public AnyNumber add(AnyNumber other);
public AnyNumber subtract(AnyNumber other);
public AnyNumber multiply(AnyNumber other);
public AnyNumber divide(AnyNumber other);
}
This way, I'd be able to operate with any objects that implement the interface by simple calling methods like:
number1 = number1.add(number2)
The problems I had
The problem comes when trying to implement the interface methods on a particular class.
I thought Java understood that the type "AnyNumber" could be any type that implements the interface, so I tried declaring the methods like this:
public IntegerNumber add(IntegerNumber other) {
return new IntegerNumber(this.value + other.value);
}
But Eclipse complained that I did not not implement the following method:
AnyNumber add(AnyNumber other)
Then I though that I could just change the return and argument types to "AnyNumber", but soon realized I couldn't access the variable "value" inside "other" because the interface doesn't define it.
So I thought maybe what I needed was an abstract class that defined all the necessary fields and abstract methods for all the different types of number, but I'm not sure if that's a good approach, since integer numbers just need one field, while rationals need two, and complex need also two but I'd like to name them different. Also, something keeps telling me that an interface makes a lot of sense in this situation..
..So I though maybe what I needed were generics. But I don't yet fully understand them and all my attempts to use them to solve this problem have miserably failed.
Please help
I realized I'll not be able to solve this alone... I really want to continue working on this project and learn how to deal with this kind of situations in Java.
Your help is very much appreciated!
Upvotes: 3
Views: 854
Reputation: 31689
When you implement an interface method, the parameter types need to be exactly the same as they are in the interface. The return type doesn't have to be the same--it can be a subclass (this is called covariance). But the parameter types can't be subclasses (this is called contravariance and Java doesn't allow it).
The reason is that there's nothing preventing the types of the two AnyNumber
objects to be different.
AnyNumber n1 = new IntegerNumber(123);
AnyNumber n2 = new RealNumber(23.4);
Then later, you could say:
IntegerNumber n3 = n1.add(n2);
Or you can pass n1
and n2
as parameters to some other method that tries to do the above. The point here is that since n1
and n2
are declared as AnyNumber
, the compiler cannot tell, at that point, whether the two objects have the same class.
This means that if you require that the two objects have the same class, you'll have to enforce it yourself, at runtime.
Your implementing class needs to look like
public class IntegerNumber implements AnyNumber {
public IntegerNumber add(AnyNumber other) { // NOTE: Must be AnyNumber!!
IntegerNumber otherInteger = (IntegerNumber)other;
// will throw a ClassCastException at runtime if "other" is, say, RealNumber
...
}
}
The rest of the method can use otherInteger
, which the compiler knows will be an IntegerNumber
at that point, since you've checked. There are other things you can do; you can use instanceof
to check, and control what exception you throw (or what else you do) instead of letting the cast throw the exception. You could also add mechanisms to handle adding of different AnyNumber
types, if you choose.
Upvotes: 2