| Polymorphism Applies To Instance Methods Only! | |||||
|---|---|---|---|---|---|
Polymorphism applies only to instance methods, not to instance variables OR static methods!
Example:
class Animal3 {
private String name = "Animal";
public void eat(String food) {
System.out.printf(" %s eats %s.\n", this.name, food);}
public void speak(String words) {
System.out.printf(" %s says nothing.\n", this.name);}
public static void play() {
System.out.printf(" Animal playtime!");}
}
class Cat3 extends Animal3 {
private String name = "Cat";
public void speak(String words) {
System.out.printf(" %s says \"%s\".\n", this.name, words);}
public static void play() {
System.out.printf(" Cat playtime!");}
}
public class Polymorphism {
public static void main(String[] args) {
Animal3[] animals = {new Animal3(), new Cat3()};
for (Animal3 animal : animals){
System.out.printf("\n\n%s output:\n",
animal.getClass().getSimpleName());
animal.eat("grass");
animal.speak("I can speak!");
animal.play(); }
}
}
/*
Animal3 output:
Animal eats grass. // OK
Animal says nothing. // OK
Animal playtime! // All as expected
Cat3 output:
Animal eats grass. // Instance variables aren't polymorphic!
Cat says "I can speak!". // Instance methods ARE polymorphic.
Animal playtime! // Static methods aren't polymorphic!
*/
However, polymorphism can be attained for static methods and instance variables, using reflection at runtime. To do this, the fields and static methods you wish to call must not be private! Here's an example of how you could accomplish polymorphism for all:
import java.lang.reflect.*;
class Animal4 {
String name = "Animal";
public void eat(String food) {
String name = this.getFieldByName("name");
System.out.printf(" %s eats %s.\n", name, food);
}
public void speak(String words) {
System.out.printf(" %s says nothing.\n", this.name);
}
public static void play() {
System.out.printf(" Animal playtime!");
}
private <T> T getFieldByName(String fieldname) {
Field f;
Object value;
// Look up the field by name
try {
// Look at fields this class declares.
f = this.getClass().getDeclaredField(fieldname);
} catch (Exception e) {
try {
// Look at fields this class inherits.
f = this.getClass().getField(fieldname);
} catch (Exception e1) {
throw new RuntimeException(e);
}
}
// Obtain the value of that field for this object.
try {
value = (T) f.get(this);
} catch (Exception e) {
throw new RuntimeException(e);
}
// Return the value.
return (T) value;
}
public <T> T invokeByName(String methodname, Object... args) {
Method m;
Object value;
Class[] parameterTypes = new Class[args.length];
for (int i = 0; i < args.length; i++)
parameterTypes[i] = args[i].getClass();
try {
// Look at methods this class declares
m = this.getClass().getDeclaredMethod(methodname, parameterTypes);
} catch (Exception e) {
try {
// Look at methods this class inherits
m = this.getClass().getMethod(methodname, parameterTypes);
} catch (Exception e1) {
throw new RuntimeException(e);
}
}
// Invoke that method on this object
try {
value = m.invoke(this, args);
} catch (Exception e) {
throw new RuntimeException(e);
}
return (T) value;
}
}
class Cat4 extends Animal4 {
String name = "Cat";
public void speak(String words) {
System.out.printf(" %s says \"%s\".\n", this.name, words);
}
public static void play() {
System.out.printf(" Cat playtime!");
}
}
public class PolymorphismFixed {
public static void main(String[] args) {
Animal4[] animals = {new Animal4(), new Cat4()};
for (Animal4 animal : animals){
System.out.printf("\n\n%s output:\n",
animal.getClass().getSimpleName());
animal.eat("grass");
animal.speak("I can speak!");
animal.invokeByName("play");
}
}
}
/*
Animal4 output:
Animal eats grass. // OK
Animal says nothing. // OK
Animal playtime! // All as expected.
Cat4 output:
Cat eats grass. // Now has cat's name.
Cat says "I can speak!". // Instance methods are polymorphic.
Cat playtime! // Now has cat's play method.
/*
However, this method has its disadvantages.
In every instance you want to get the cat's name, not the animal's name, you must remember to call 'getFieldByName' instead.
Everywhere you want to call the cat's 'play' method, and not the animal's 'play' method, you must remember to call 'invokeByName' instead.
As you can imagine, this would be prone to error, if someone forgot to do that.
| |||||
| String Weirdness (Operands Associate Left to Right) | |||||
|---|---|---|---|---|---|
Take a look at the following code:
public class StringWeirdness {
public static void main(String[] args) {
System.out.printf("%s %s %s\n",
"String" + 1 + 2,
1 + 2 + "String",
1 + "2" + "String");
}
}
It might surprise you to know the output is "String12 3String 12String"!
This is because operands associate from left to right.
| |||||
| Arithmetic Operations Overflow Silently | |||||
|---|---|---|---|---|---|
It's very simple to create an endless loop in Java. Run the following, and see if you can spot why you had to manually stop it:
public class OverflowSilently {
public static void main(String[] args) {
for (byte i = -128; i < 128; i++)
System.out.println(i);
}
}
It's because the variable i overflows when it goes over 127.
In Java, the range of a byte is -128 to 127.
When it increments over 127, it wraps around back to -128, and the loop now has the starting value again. It does this silently, without either a compile or run-time error.
This happens whether you use a primitive byte, or its wrapper class Byte.
You can't get around it by using an unsigned byte, because there ARE no unsigned numbers in Java.
It's tempting to always use a 'long' to get around this problem, but even that presents problems, because long's can't be used to index an array. This is because the maximum number of entries in an array (even an ArrayList) is Integer.MAX_VALUE (2,147,483,647), which causes the compiler to complain if you use anything larger than an int to index it. If you suspect an int won't do ya, go ahead and use a long, but keep it mind it won't be able to be used as an index into an array. Just for kicks and giggles, let's figure out just how much memory an array of maximum size would consume. Let's say we want to create an array of integers, each of which takes 4 bytes memory. The array should have the maximum number of entries possible, or 2,147,483,647 entries. 2,147,483,647 * 4 = 8,589,934,588 bytes
So, you'd pretty much max out the server memory anyway just using an array of ints. The moral of this story is, don't be parsimonious with your data types - always choose a size that will accommodate future growth. In general, it's OK to use an int without thinking in most cases, but don't try to save space by using a byte or short, because it could backfire on you in the future. | |||||
| Autoboxing and Unboxing Gotchas | |||||
|---|---|---|---|---|---|
Here's an interesting tidbit.
public class NumbersDontCompare {
public static void main(String[] args) {
int primitiveInt = 128;
Integer wrapperInt = 128;
Integer wrapperInt1a = 128;
Integer wrapperInt1b = 128;
long primitiveLong = 128;
Long wrapperLong = 128L;// must have L suffix because
// int can't autobox to Long
// ****************************************************
// These are all true
// ****************************************************
System.out.println(primitiveInt == primitiveLong);
System.out.println(primitiveInt == wrapperInt);
System.out.println(primitiveInt == wrapperLong);
System.out.println(primitiveInt == 128);
System.out.println(wrapperInt == primitiveLong);
System.out.println(wrapperInt.equals(128));
System.out.println(wrapperLong.equals(128L));
System.out.println(wrapperInt1a.equals(wrapperInt1b));
System.out.println("");
// ****************************************************
// These are all false
// ****************************************************
// This won't compile, because you can't compare
// wrappers of different types using ==.
// System.out.println(wrapperInt == wrapperLong);
// Using .equals with a primitive won't always save you
// - a Long value autoboxes to a Long type, which is
// still a different type than Integer.
// It would have been true had the primitive been
// an int (128), instead of a long (128L).
System.out.println(wrapperInt.equals(128L));
// Though both are 128, they compare false because
// they're different types. Equals silently returns
// false if they're of different types, even if
// the values are the same.
System.out.println(wrapperInt.equals(wrapperLong));
// This one fails because == compares whether the
// variables point to the same object, and doesn't
// check their values. wrapperInt1a is a different
// object than wrapperInt1b, though their values
// are the same.
System.out.println(wrapperInt1a == wrapperInt1b);
System.out.println("");
// ****************************************************
// This one's stupid.
// Even though the last example above was false,
// this one is true, because for small values
// from -128 to 127, Java caches an object wrapper
// class with that value to optimize performance.
// Therefore, they DO point to the same object. :-P
// ****************************************************
wrapperInt1a = 1;
wrapperInt1b = 1;
System.out.println(wrapperInt1a == wrapperInt1b); // true!
wrapperInt1a = 128;
wrapperInt1b = 128;
System.out.println(wrapperInt1a == wrapperInt1b); // false!
}
}
Wrapper classes are immutable, meaning their value can't change once set. What this means, is every time you increment a wrapper class, it secretly throws away the old object, and creates a new object containing the new value, and points the variable to that new object instead. Because of this, using wrapper classes can introduce significant overhead, especially in tight inner loops. Simply incrementing an Integer from 0 to 1,000,000 creates and throws away 1,000,000 objects! The moral of this example is, be very aware of when boxing and unboxing occur. Though there's no way in Java to disable autoboxing and autounboxing, there IS a way to do it, in Eclipse. Go to 'Window', 'Preferences', 'Java', 'Compiler', 'Errors/Warnings', and where it says 'Boxing and unboxing conversions', make that 'Warning.' If you were to make it an error, you'd have too many errors to fix, but setting it to warning lets you be very aware of when it's happening. | |||||