More About Objects Static Methods Let's start with two classes, a Driver class, containing method main, and a Calc class, containing method sum. // file Calc.java public class Calc { private int tmp; public int sum(int a, int b) { tmp = a + b; return tmp; } } // file Driver.java public class Driver { public static void main(String[] args) { Calc c = new Calc(); int x = c.sum(2, 3); // x = 5 int y = c.sum(4, 5); // y = 9 } } The driver instantiates a Calc class with the new operator, and computes two sums. Let's make the sum method static and see what happens. // file Calc.java public class Calc { private int tmp; public static int sum(int a, int b) { tmp = a + b; // can't reference instance variable return tmp; } } This causes a compiler error, as sum is unable to reference tmp. The reason is that static methods have no this pointer, so it is impossible for sum to determine which tmp to process. One solution is to make tmp a local variable in sum. // file Calc.java public class Calc { public static int sum(int a, int b) { int tmp; tmp = a + b; return tmp; } } The program now compiles and runs successfully. Since sum is now static, no this pointer is required for sum, and therefore we don't need to allocate the Calc class to execute sum. ///////////////// // old version // ///////////////// public class Calc { private int tmp; public int sum(int a, int b) { tmp = a + b; return tmp; } } public class Driver { public static void main(String[] args) { Calc c = new Calc(); int x = c.sum(2, 3); int y = c.sum(4, 5); } } ///////////////// // new version // ///////////////// public class Calc { public static int sum(int a, int b) { int tmp; tmp = a + b; return tmp; } } public class Driver { public static void main(String[] args) { int x = Calc.sum(2, 3); int y = Calc.sum(4, 5); } } This is why you can reference methods in some classes without allocating the class. For example, int x = Math.round(y); does not require allocating the Math class. That's because the Math.round method is a static method and does not reference any instance variables in the Math class. You can also include static member functions in the Driver class. Let's move our sum method to the Driver class. public class Driver { private static int sum(int a, int b) { int tmp; tmp = a + b; return tmp; } public static void main(String[] args) { int x = sum(2, 3); int y = sum(4, 5); } } We now have a single file, Driver.java, with two static methods: main and sum. If you have repetitive code and would like to place it in a method, static methods are often an attractive choice. We could also define tmp as a static data member. public class Driver { private static int tmp; private static int sum(int a, int b) { tmp = a + b; return tmp; } public static void main(String[] args) { int x = sum(2, 3); int y = sum(4, 5); } } Suppose sum referenced an instance variable. public class Driver { private int tmp; private static int sum(int a, int b) { tmp = a + b; // can't reference instance variable return tmp; } public static void main(String[] args) { int x = sum(2, 3); int y = sum(4, 5); } } Obviously, sum can't reference tmp because sum is a static method, and static methods can't reference instance variables. So let's remove the static attribute on sum. public class Driver { private int tmp; private int sum(int a, int b) { tmp = a + b; return tmp; } public static void main(String[] args) { int x = sum(2, 3); // error, sum is not static int y = sum(4, 5); } } Now sum compiles okay, but references to sum fail. That's because sum is not a static method, so we must allocate the class that contains sum. This will, in turn, allocate an instance of the variable tmp that sum uses. public class Driver { private int tmp; private int sum(int a, int b) { tmp = a + b; return tmp; } public static void main(String[] args) { Driver d = new Driver(); int x = d.sum(2, 3); int y = d.sum(4, 5); } } This logic may look a bit strange at first. We're in the Driver class and we allocate ourselves! However, remember that when you instantiate a class, you allocate memory for data, not code. Method main is static, so it compiles okay as long as we don't reference variable tmp from main. Method sum does reference tmp, so we need to allocate Driver before we invoke sum. This is exactly what we do in the main method. Then, all references to sum must go via the allocated class, and a this pointer is passed to sum indicating the location of the classes' data. Math Class The predefined Math class contains several static methods and constants. For example, Math.PI accesses a public constant that contains 3.141592653..., or the value for π. The interface for several methods is listed below: // square root double sqrt(double x) // power double pow(double x, double y) // absolute value int abs(int x) long abs(long x) float abs(float x) double abs(double x) // minimum int min(int a, int b) long min(long a, long b) float min(float a, float b) double min(double a, double b) // maximum int max(int a, int b) long max(long a, long b) float max(float a, float b) double max(double a, double b) // rounding int round(float x) long round(double x) Since these are static mehods, there's no need to allocate an instance of the Math class before calling a function. e.g., int i = Math.max(3, 5); returns 5, the maximum number. Overloading If two methods in the same class have the same name, but different parameter types, we say the function is overloaded. The compiler can determine which function to call based on the actual parameter type. For example: class Math { public static int min(int i, int j) { return i < j ? i : j; } public static double min(double i, double j) { return i < j ? i : j; } } class Test { public static void main(String[] args) { int i, j, k; double a, b, c; ... i = Math.min(j, k); // call int version a = Math.min(b, c); // call double version } } The compiler does not make its decision based on the return type. Consider the following: int f(int); int f(double); int g(); double g(); f(g()); Which version of g will be called? Which version of f ? For this reason it's illegal to have two functions with identical parameter types in the same class. Constructors Recall we had two ways to establish data in the Employee class. We could make data members public, and assign directly to the members as follows: class Employee { public String name; public double pay; } Employee e = new Employee(); e.name = "John"; e.pay = 100.00; Alternatively, we could protect our data by designating it private, and code accessor methods to assign values to the data members: class Employee { private String name; private double pay; public void setName(String name) { this.name = name; } public void setPay(double pay) { this.pay = pay; } } A third possibility exists. We could establish values while we're allocating the class using the classes' constructor. class Employee { private String name; private double pay; public Employee(String name, double pay) { this.name = name; this.pay = pay; } } Employee e = new Employee("John", 100.00); If you don't include a constructor then Java generates one for you that simply does nothing. And, just as for any other method, constructors can be overloaded. Consider the following: class Employee { private String name; private double pay; public Employee(String name, double pay) { this.name = name; this.pay = pay; } public Employee(String name) { this.name = name; this.pay = 0.00; } Employee e1 = new Employee("John", 100.00); Employee e2 = new Employee("Mary"); Employee e3 = new Employee(); // error If you have a constructor, then you must explicitly define the default constructor if you wish to support this call. Comparing Classes What output does the following program produce? public class Number { public int value; } public class Driver { public static void main(String[] args) { Number a = new Number(); a.value = 1; Number b = new Number(); b.value = 1; if (a == b) System.out.println("equal"); else System.out.println("not equal"); } } What output does the following program produce? public class Number { public int value; boolean equals(Number t) { return t.value == value; } } public class Driver { public static void main(String[] args) { Number a = new Number(); a.value = 1; Number b = new Number(); b.value = 1; if (a.equals(b)) System.out.println("equal"); else System.out.println("not equal"); } } What output does the following program produce? public class Driver { public static void main(String[] args) { String a, b; a = "123"; b = "123"; System.out.println("a=" + a + ", b=" + b + ", " + (a == b)); } } Assigning Classes What output does the following program produce? public class Number { public int value; } public class Driver { public static void main(String[] args) { Number a = new Number(); a.value = 1; Number b = new Number(); b.value = 2; System.out.println("a = " + a.value + ", b = " + b.value); a = b; System.out.println("a = " + a.value + ", b = " + b.value); a.value = 3; System.out.println("a = " + a.value + ", b = " + b.value); } } Java supports a Cloneable interface that does a bitwise copy of a class. The following is code for the Cloneable interface included in the Java library: public interface Cloneable { protected Object clone() throws CloneNotSupportedException; } The phrase implements Cloneable, in the Number class shown below, specifies that this class has implemented the Cloneable interface. public class Number implements Cloneable { public int value; public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException) { return null; } } } public class Driver { public static void main(String[] args) { Number a = new Number(); a.value = 5; Number b = (Number)a.clone(); System.out.println("a = " + a.value + ", b = " + b.value); } } In the Driver class we call the Number's clone method. The clone method in Number calls the protected method in Object. All classes are derived, directly or indirectly, from the Object class. The clone method in Object determines the size of the class, allocates memory for a new object, and does a bitwise copy, and returns a reference to the new object. C++ supports multiple inheritance. That is, we can inherit from several base classes. e.g., class derived: public base1, public base2 { ... } This is a controversial feature that some feel is confusing an non-intuitive. Java does not support multiple inheritance. However a Java class can implement multiple interfaces. Wrapper Classes Wrapper classes are provided for the primitive types as follows: byte char int long float double Byte Character Integer Long Float Double A wrapper is used on a primitive type when it's desirable to associate methods with the value. For example, if you wish to convert integer 35 to a string, it would be nice to say 35.toString(). Of course this is illegal as there is no class named 35. However there is an Integer class and you can accomplish this conversion as follows: String s = Integer.toString(35); This simply invokes the toString static method of the Integer class that converts an integer to a string and returns a String. Here's another way to do the same thing: Integer x = new Integer(35); String s = x.toString(); In this case we're allocating an instance of the Integer class and invoking a constructor that expects an integer as an argument. Code for the Integer wrapper may look similar to the following: class Integer { int v; public Integer(int v) { this.v = v; } public static String toString(int v) { // convert v to string and return string } public String toString() { return toString(v); } } There is only one constructor, so you must pass a value to instantiate the class. Then call the toString method, without parameters, to obtain a string value. Alternatively, skip instantiating the class and simply call the static method to do the conversion. // method 1 Integer x = new Integer(35); String s = x.toString(); // method 2 uses static method String s = Integer.toString(35); All the primitive wrappers include a toString method. There's also a valueOf method to obtain the value stored in the wrapper. Integer k = new Integer(50); System.out.println(Integer.toString(k.valueOf())); Here we called k's valueOf method that returns 50, the value stored in k, and then convert it to a String. Wrapper classes also include a parse method that converts strings to a primitive type. For the Integer class, Integer.parseInt("12") will return an integer 12, and Double.parseDouble("12.3") will return a double (see p. 114). Several useful methods are supported by the Character wrapper class that convert between lower and upper case and test for digits or letters (see p. 322). Wrappers are immutable. Once established they may not be changed. For example, Integer k = new Integer(25); Integer k now contains 25. There is no way you can change the value stored in variable k. That's because this value is private to Integer, and there are no accessor methods that will allow you to change its value. Well, there's almost no way to change the value. Consider the following: Integer k = new Integer(25); .... k = new Integer(35); Here we allocated a new Integer, so k now points to a new value, 35. The old value is garbagecollected by Java. The String class is also immutable. Once a value is stored in a String, the only way you can change it is to allocate another String.