In Java, all non-primitives follow an inheritance structure. We will cover this in more detail when we specifically talk about inheritance, but we'll cover the idea of "overriding" a method here.
All classes in Java inherit from the Object class. Thus, all nonprimitives ARE Object objects, so to speak. In this class, a toString() method is defined. Thus, if you write your own class, but don't define a toString() method, then when you try to print out your object, technically, the toString() method from the Object class gets called.
Consider the fraction class. Let us omit its toString() method, compile and rerun the code. Instead of our fractions printing like 2/3, we'll get something to the effect of fraction@10b62c9.
The former represents the output of the toString() method defined in the fraction class, and the latter represents the output of the toString() method in the Object class.
Whenever one class (class B) inherits from another class (class
A) in Java, if we omit a definition of a method in class B and one exists in class A, then the method in class A will be executed on an object of type class B.
In some instances, this is desirable. In others, like the example above, it makes sense for us to "override" the toString() method in the Object class when we create the fraction class, since no one would understand what fraction@10b62c9 really means!
Often times, it makes sense to produce a duplicate of an object instead of having two references to the same object.
For example, the lines of code: fraction f = new fraction(1,2); fraction copy = f;
Actually produces the following picture: f ---------------> [ num = 1, den = 2] <------------------ copy
Thus, no copy of the ACTUAL object is made, rather, copy is references the same object that f does.
In Java, programmers typically produce a copy through the clone() method. Here's the method in the fraction class: public Object clone() { return new fraction(numerator, denominator);
}
Couple key things to note here:
1) The method signature indicates that an Object is returned.
2) Inside the method we see that a fraction is returned.
The reason for #1 is that this is the signature delineated in the
Object class, so to override the method, technically we must use the same signature.
The reason for #2, is that we know we want a fraction object.
Since the clone method returns an Object, here's one way to call it:
Object copy = f.clone();
What's interesting about this picture is that although copy is an Object reference, the object that it is referencing is
REALLY a fraction!
Our picture looks like this now: f ---------------> [ num = 1, den = 2] copy-----------> [ num = 1, den = 2]
Since copy is just an Object, you can ONLY call methods that are defined for the Object class.
The surprise, however, is that if you DO call a method that resides in the Object class, AND one that resides in the fraction class on copy, it will ACTUALLY run the method in the fraction class instead of the Object class.
But, we know that copy is a full-fledged fraction object, and it would be nice if we could call regular fraction methods on it.
We can ONLY do that if we create a fraction reference instead of an Object reference. This can be done using a cast: fraction copy = (fraction)f.clone();
The picture here is the same, but NOW, we are able to call any method from the fraction class on copy.
Due to inheritance, the type of object a reference is referencing
CAN BE different than the type of the reference, in some sense.
For example, a Object reference can refer to a fraction Object.
This sort of situation is ONLY allowed, if the object itself is of a type that inherits from the type of the reference. So it's okay for an Object reference to refer to a fraction object, but it's
NOT okay for a fraction reference to refer to an Object object.
This is because all fractions are Objects, but NOT all Objects are fractions.
Thus, when we have a reference, it might be handy to check to see whether or not the object it is referencing is a particular type.
Consider writing a method with the following signature in the fraction class: public boolean equals(Object f);
This overrides the equals method in the Object class.
When we write this method, we know that the only way that a f can equal the current object is if f refers to a fraction object.
Here's how we can check that: if (f instanceof fraction) { … } instanceof is an operator. Its first operand should be a reference and its second operand should be a class. It returns true if and only if the reference is referencing an object of the type of the second operand.
The equals method in the Object class checks to see whether or not the references of the two objects are the same.
However, when most people design their own class, they typically want equality to indicate that two separate objects have equivalent value.
Utilizing this idea, here's the code from the fraction class for the equals method: final private static double EPSILON = 0.0000001; public boolean equals(Object f) { if (f instanceof fraction) { fraction tmp = (fraction)f; return (Math.abs((double)numerator/denominator –
(double)tmp.numerator/tmp.denominator)
< EPSILON);
} return false;
}
We automatically return false if f is not a fraction. If f IS a fraction, then we need to compare the values of the current object with f.
BUT, since f is technically an object reference, we are not allowed to access f.numerator or f.denominator. To get this access, we need a fraction reference to the same object. In this case, tmp is the reference and it's referencing the same exact object that f is referencing.
In general, a regular local variable is valid in between the closest set of matching parentheses within which it is declared.
{
…
int x;
..
}
In the case above, x is declared as shown in the middle of the block. It will continue to exist until the end brace shown is hit.
Then it is destroyed. If the block enclosed by the parentheses were in a loop, x would again be created on the next iteration in the appropriate location.
Clearly, a local variable declared in one method is NOT valid in another.
Also, you are not allowed to have two local variables of the same name valid in the same scope, so the following would be illegal: int x; for (int i=0; i<10; i++) {
int x;
…
}
Class variables are allowed to be used in any static or instance method of the class.
Instance variables are allowed to be used in any instance method of a class.
What can be confusing is that technically, one is allowed to use a local variable in an instance method with the SAME NAME as an instance variable. In order to resolve the ambiguity, the key word this must be used. Consider rewriting the add method in the fraction class: public fraction add(fraction f) { int numerator = this.numerator*f.denominator +
this.denominator*f.numerator; int denominator = this.denominator*f.denominator; return new fraction(numerator, denominator);
}
In this situation, this.numerator refers to the instance variable of the current object, while numerator refers to the local variable created in the method.
When you "in-line" the declaration of a variable in a for loop: for (int i=0; i<10; i++) { … }
The scoping works as follows:
{
int i;
for (i=0; i<10; i++) { … }
}
An overloaded method is one where there are multiple definitions for a method with the same name within a class.
For example, imagine adding a fraction to an int. Currently, there is no functionality in the fraction class to do this.
However, we could add a method similar to the add method that takes in an int instead of a fraction.
It seems silly to give this method a different name because in reality, it is ALSO performing the function of adding. Thus, we can declare a second add method as follows: public fraction add(int n) { int num = this.numerator+this.denominator*n; return new fraction(num, denominator);
}
It seems much more natural to also call this add than to call it add2, or something of that nature.
Now, the key idea is that at compile time, your program can determine which method to run. If you are calling add with an integer, then the add method above will get called. If you are calling add with a fraction, then the original add will get called.
Note that if we try to make a call like the following: fraction ans = f.add(3); without the definition of add above, we'll get a syntax error telling us that the add method expects a fraction as a parameter. With it, this code will run just fine!
We'll use this idea more later, but here's a very brief introduction. Sometimes, it makes sense to make a class that stores works on "any type," so to speak. We can implement this through generics. Here's a very basic class that maintains adding elements and searching for elements of any type: public class MyCollection<T> { private Object[] values; int numvals; public MyCollection(int size) { values = new Object[size]; numvals = 0;
} public void add(T element) { if (numvals < values.length) { values[numvals] = element; numvals++;
}
} else {
Object[] temp = new Object[2*values.length]; for (int i=0; i<values.length; i++) temp[i] = values[i]; temp[values.length] = element; values = temp;
}
public boolean search(T element) { for (int i=0; i<numvals; i++) { if (element.equals(values[i])) return true;
}
} return false; public void printAll() {
System.out.print("Here are all the fractions: ");
}
} for (int i=0; i<numvals-1; i++)
System.out.print(values[i]+", ");
System.out.println(values[numvals-1]+".");
This implementation seems a bit fishy because the array ought to be of type T, but it turns out that Java doesn't allow Generic arrays. Thus, in order to get this idea to work, we need to use an array of Object. This is fine because all types (T) inherit from Object.
Now, the cool thing is that we can use this class with whatever type of Object we want. Let's take a look at the posted code.