Java 211, Lecture 5 Mendelsohn References References An object reference and an object are two separate things. See Powerpoint presentation, slides 3-9 Recall that when we declare an object with the syntax: ObjectType objectName; we have only created a reference to an object. It is only when we use the ‘new’ operator and call the constructor that we actually have created a valid object to work with. Well, what exactly do we mean by a ‘reference to an object’? The answer is that a reference variable holds the address of an object. In other words, the reference variable holds a location in memory where the information about the object is stored. When you use the dot operator to refer to a field or method, what you are doing is using the reference to determine the location of the object in memory. Once the location of the object is determined, we can refer to any field or call any method available to that object. Null Reference When a reference is created, but does not contain the address to a valid object, we say that it is a null reference. If you try to access some information of a null reference, a NullPointerException is thrown. ( “Exceptions” will be discussed in CSC-212). String name; System.out.println(name.length()); //throws a NullPointerException ‘null’ is a reserved word in Java. You can test for it: if (name != null) System.out.println(name.length()); //avoids possibility of NullPointerException //being thrown Aliases Two or more references that refer to the same object are called ‘aliases’ of each other. If an object has multiple references to it, you can change the state of that object via any of its references. Assignment statements assign slightly different things if they are operating on primitive data types as opposed to objects. For primitive data types, the assignment statement places the value of the right-side variable into the left hand side. int n1 = 5; int n2 = 12; n2 = n1; //puts a ‘5’ into n2 For objects, the assignment statement puts the address of the right-side object into the left hand side. ChessPiece cp1 = new ChessPiece(); ChessPiece cp2 = new ChessPiece(); cp2 = cp1; //assigns the address of cp1 to cp2 cp1 and cp2 are now aliases to the same object. Making a change to the object using the cp1 reference will also result in changes to the object being pointed to by cp2. Why? Because cp1 and cp2 are both aliases to the same object. When you think about it, the assignment statement is not technically doing anything different between the above two situations. In the first, n1 and n2 are storing integers, and in the second situation, cp1 and cp2 are storing addresses. In both cases, the type on the right-side of the assignment statement matches the type on the left-side of the assignment statement. Comparing Objects/References ChessPiece cp1 = new ChessPiece(); ChessPiece cp2 = new ChessPiece(); // cp1==cp2 would return false cp2 = cp1; // cp1==cp2 would return true since both // cp1 and cp2 hold the same value // (that is, the same address) ‘equals(Object o)’ method v.s. ‘==’ Unless we have overridden one of these methods, they typically do the same thing. Specifically, they compare the addresses (for objects) or the values (for primitive data types) and return true if they are the same, and false if they are different. - ‘==’ for references always returns its value based on the addresses stored inside those references. In other words, it will only return true if the references are aliases of each other. - ‘equals()’ can be altered by the programmer to have a different definition. By default, however, it compares the addresses stored inside the references. For the String class, the equals() method has been “overridden” to return true based on the sequence of characters stored inside the object. Question: Why do we compare two strings to see if they are the same using the ‘equals(String s)’ method as opposed to using ‘==’? Question: How might we override ‘equals()’ method to compare two ChessPiece objects? Garbage Collection Question: Consider the assignment cp2 = cp1; above. What happens to the old object being referenced by cp2? Prior to the statement, cp2 was pointing at some object in memory, but after the assignment, cp2 is now pointing to a different object. (The same object that cp1 is pointing to). - Is there any way to get cp2 to point back to its original object if we wanted it to? - What about all the memory being used by the object that was previously being referenced by cp2? Answer: Unless we create a new reference that can point to cp2’s original object, that object is lost to us forever. This also means that all the memory being used by that object is just sitting there hogging up space. In C++, numerous objects “lost” in memory could eventually present a problem. However, the Java runtime has a tool called automatic garbage collection whereby any objects that do not have references pointing to them have their memory reclaimed by the system. - The programmer can NOT control when the Java runtime will carry out garbage collection. Every object can have a method called ‘finalize()’ which the programmer can define if they wish. This method allows the programmer to carry out some final tasks prior to garbage collection taking place (e.g. closing files). Representing real-life scenarios as objects In object-oriented (“OO”) programming, part of the trick is learning to represent real-live objects by programming objects. This is not necessarily as difficult as it may sound. - For example, to categorize a dice object, you really only need one field: to represent the number of dots (i.e., the side). You can then use your Math.random() method to simulate rolling of the dice. See Die.java - To simulate a chess piece, you need only two fields: One to represent the color (black or white), and another to represent which piece (e.g. bishop, v.s. rook, v.s. pawn, etc). How you choose to store this information is up to the programmer. For example, you may store the color as a string, or a char. Alternatively, you could store the color as an int with 0 = white and 1 = black. It’s up to you. - To take our chess analogy further, you could have a field representing the current location on the board. In chess, each square is represented by a letter and a number. These range from A1 to H8. So you could store the position of each chess piece as a string. - You could have a method called void move(String location) which moves the piece to the location you specify. The move method could examine the object that called the method and validate that the piece is allowed to be moved to that location. For example, a pawn could not move halfway across the board. The method could either refuse to change the position, or it could require the user to keep reentering positions until a valid one is provided. Passing Objects as Arguments (Parameters) When you pass an argument to a method, we say that the argument is passed by value. This means that the value of the argument (e.g. an integer, a double, a char, an address) is passed down to the method. The corresponding formal parameter of the method is then initialized to that value. public void doStuff(int number) { … } //a method with one formal parameter int x=5; doStuff(x); //inside doStuff, the formal parameter ‘number’ is //initialized to the value of ‘x’ //It is as if there is an invisible ‘number=5’ //statement inside the method. //What would be output by the following? System.out.println(x); It is also possible for a method to accept an object as a parameter instead of a plain old primitive data type. In this case, it is not entirely accurate to say that you are passing an actual object to the method, since what you are really passing is a reference to the object. public void doStuff(ChessPiece cp) { … } //a method with one formal parameter that is //a reference to an object ChessPiece c = new ChessPiece(); doStuff(c); //inside doStuff, the formal parameter ‘cp’ is //initialized to the value of ‘c’ //Since ‘c’ is a reference, it stores an address //and that is what the formal parameter ‘cp’ is //initialized to. The key here is that when you pass an object (or more accurately, a reference to an object) to a method, you are creating an alias to that object. The formal parameter becomes an alias to the object which was passed to the method. In the above example, the formal parameter ‘cp’ is an alias to the object ‘c’ that was passed to the method. Carefully study ParameterPassing.java and be sure that you understand what is taking place. In this example, we make use of a class called ‘Num’. Objects of type Num have only one field, an integer. This makes ‘Num’ a pretty useless class, but it is useful in that we can use it to observe what happens when you pass objects (references) to methods. Incidentally, notice the use of the ‘toString()’ method in the Num class. The toString() method is invoked by the println() method when we try to output a Num object in line 24 of ParameterPassing.java . ‘this’ Reference ‘this’ is a reserved word in Java. It is used when an object needs to refer to itself. For example, if you are inside a method and you need to refer to the calling object (the object that invoked the method), you can refer to that object by using the keyword ‘this’. We will discuss ‘this’ further as we progress. public boolean move(ChessPiece cp) { if (this.position == cp.position) // ‘this’ refers to the calling object return false; … more code… } More on the ‘static’ modifier Recall that if a method is declared static, this means that we can invoke the method without even having to have an instantiation of the class. Ordinarily to invoke a method, we must first specify an object. However, with a static method, we do not need to instantiate, and can invoke by simply qualifying the method name with the class (not object) name. System.out.println(“The square root of 27 is: “ + Math.sqrt(27) ); Here, the sqrt() method of the class Math is a static method. Therefore we can invoke it by simply providing the class name. In fact, all methods of the Math class are static. Note how we have always created main() with a static modifier. This is so that main()can be instantiated by the interpreter without having to instantiate an object from whatever class contains main(). Static Variables We’ve already discussed the ‘scope’ of variables. For example, a variable defined inside a method is “local” to that method. That is, the variable does not exist outside of that method. If you attempt to refer to variable outside of its scope, the compiler will give you an error. We’ve also seen variables that are tied to a class. For example, the StudentRecord class we discussed earlier in the quarter has a number of “instance variables” (fields) including: lastName, firstName, totalHwGrade, etc. These are called instance variables since the only way you can refer to them is via an instance (object) of the class. This makes sense, since, for example, the ‘lastName’ field only makes sense in the context of a StudentRecord object. For instance variables, each object of the class reserves memory for its own copy of that variable. However, there are times when you might want a variable to belong to all objects of the class. That is, you might want the variable to have the same value for all objects of the class. In this case, each object of the class shares the same memory space for the variable. If any one object changes the value of the variable, the value is changed for all objects of that class. These are called static variables. In the StudentRecord class, there is one such variable, ‘section’ that stores the section number for that class. All objects of type StudentRecord will have the same value for ‘section’. Question: Can you remember what the ‘final’ modifier stands for? Memory space for a static variable is reserved the first time the class is referenced by the program. (E.g. The first time the class is instantiated). Note that static methods can not refer to non-static variables. This is because non-static variables only exist in the context of an object. However, static methods do not exist in the context of an object, and therefore, know nothing about non-static (“instance”) variables. Static methods can, of course, access static variables. See CountInstances.java , and MyClass.java .