Java Objects Reed, CS340, Fall 2004 Examples and notes taken in large part from: http://6170.lcs.mit.edu/www-archive/Old-Fall03/lectures/handout/lec02_semantics.pdf Variables, References, Binding What is output of code below? 1. String a = "zeeb"; 2. String b = a.toUpperCase (); 3. System.out.println (b); Output is ZEEB. a is a reference to the String with “zeeb” in it, and is not the object itself. As a picture, this looks like the diagram at right above. Note that strings are immutable. Now consider this code: 1. String a = "zeeb"; 2. a.toUpperCase (); 3. System.out.println (a); It prints “zeeb”, since line 2 returns a new String (an object) which is ignored. On the other hand we could have: String a = "zeeb"; a = a.toUpperCase (); System.out.println (a); Here a points to the new object. Aliasing, Mutability, and Reference Equality 1. Vector v = new Vector (); 2. Vector k = v; 3. String a = "zeeb"; 4. v.add (a); 5. k.add (a.toUpperCase()); 6. System.out.println (v.lastElement ()); Note that the method add() in line 4 does not return an object, but rather mutates its receiver, which is Vector v. The call to a.toUpperCase() in line 5 does return an object, since Strings are immutable. v and k are references to the same object, which makes them aliases. Testing for equality: Vector v = new Vector (); Vector k = v; if (v == k) System.out.println ("same"); else System.out.println ("different"); The code above prints out “same”, since both references point to same object. One problem with aliases is that they can cause “side-effects”, where a change done with one version of an alias ends up reflected in another alias. Vector v = new Vector (); Vector k = new Vector (); if (v == k) System.out.println ("same"); else System.out.println ("different"); Prints out “different”, since references point to different objects. String a = "zeeb"; String b = "zeeb"; if (a == b) System.out.println ("same"); else System.out.println ("different"); Prints out “same”. This is unusual, and is a Java compiler performance optimization. Changing the second line to String b = new String(a); makes them different, however. Strings (and other immutable objects) should actually be compared with the equals method. It is considered bad form to test reference equality for immutable objects (e.g. Strings). String a = "zeeb"; String b = a.toUpperCase (); if (b.equals ("ZEEB")) System.out.println ("same characters"); else System.out.println ("different characters"); This code prints: “same characters”. Would you expect that generally x = = y implies x.equals (y) ? Yes, it should. Immutable types arise because aliasing makes things complicated. If aliasing makes things complicated, then why have mutable types at all? Real-world problems can be modeled more easily with mutable types. A transaction on a bank account changes it (it is mutable); it doesn't produce a new bank account. Null References The code String a = null; System.out.println (a); Prints out null, meaning there is no object to be printed. If we try to use a null object, however, we can have problems: 1. String a = null; 2. String b = a.toUpperCase (); 3. System.out.println (b); // Problem here where the a.toUpperCase() tries to call a method on a null object, throwing a NullPointerException on line 2. It is good to avoid building null references in the first place. You would expect a.equals( b) to be equivalent to b.equals( a), however if a is null and b is not, then the first would throw an exception and the second would not. We would expect these to be the same, putting this issue of nulls aside. User-defined Classes and Fields Here is our own class for a bank transaction: class Trans { int amount; Date date; } where amount is of a primitive type int, and date is of type Date. Primitives sometimes need to be converted to classes, and visa-versa. To do this with an int we could use: int_i = 5; Integer obj_i = new Integer (i); And to extract the int from the field of the Integer class object we could use int i = obj_i.intValue(); We could create a Transaction using: 1. Trans t = new Trans (); 2. t.amount = 20; 3. t.date = new Date (); This creates a fresh object and sets the fields, as shown at right above. User-defined Constructors In the example above we could have set the initial transaction date to be anything, including a date in the future or past. To restrict this we would want to implement invariants to enforce certain constraints. We can do this by providing our own constructors, which enforce the conditions we would like, such as a non-zero transaction amount. class Trans { int amount; Date date; Trans (int a, Date d) { //constructor amount = a; date = d; } } Creating our own constructor has the effect of making the default constructor go away, which is the one used in line 1 in the sample code by the diagram immediately above. We can replace those 3 lines of code with Trans t = new Trans (20, new Date ()); Method Overloading [http://www.cs.sunysb.edu/~cse219/lectures/5 ] The proper method to execute is identified by its name and signature (i.e. its parameter list) that matches the arguments sent What if no perfect match exists? The most specific match is called. Method m()1 is more specific than another method m2() if any legal call of m1() would also be a legal call of m2() if more conversions were done. What if there is no most specific match? Consider the following example: public class Junk { public static void main(String[] args) { int x = 2; comp(x, x); // which method does this match? } static int comp(int a, long b) {} static int comp(long a, int b) {} static int comp(long a, long b) {} } This gives the following compiler error: reference to comp is ambiguous, both method comp(int,long) in Junk and method comp(long,int) in Junk match comp(x, x);