Java程序辅导

C C++ Java Python Processing编程在线培训 程序编写 软件开发 视频讲解

客服在线QQ:2653320439 微信:ittutor Email:itutor@qq.com
wx: cjtutor
QQ: 2653320439
Algorithms and Data Structures Module 5: A First Look at Objects Supplemental material Static objects: encapsulation What is a static object? This is a little-used term for Java classes with only static components. In the "old" days, we would call this encapsulation. Let's look at an example: // A simple static object. Note: all members (data and methods) are declared // with the "static" keyword. class ObjX { static int i; static void print () { System.out.println ("i=" + i); } } //end-ObjX // The class that has "main" public class StaticExample { public static void main (String[] argv) { // Refer to a member of ObjX using the class name. ObjX.i = 5; // Call a method in the class using the class name and dot-operator. ObjX.print(); } } Note: There are two class'es in the same file above: ObjX and StaticExample. StaticExample is the one that contains main() and therefore, the file has to have the name StaticExample.java. Rule: the class that has main must be declared public. The one we created, ObjX has two members: a piece of data i (an integer) and a method print(). Both members are declared using the keyword static. To access members from another class, we use the class name (ObjX) and the . (dot) operator, as in: // Refer to a member of ObjX using the class name. ObjX.i = 5; // Call a method in the class using the class name and dot-operator. ObjX.print(); The main purpose of static objects is encapsulation. What is encapsulation? In early programming languages, data and methods were strewn all over the place, allowing "global" data (outside methods) to be accessed by any method.      ⇒ This invites name clashes, illegal accesses, bad coding practices Later languages promoted the idea of encapsulation      ⇒ Keep related data and methods together in a "package" In Java, that "package" is a static object      ⇒ a class with only static members. In-Class Exercise 1: Download StaticExample.java and add a second data member (say, another int) to the class ObjX. Then, in main, assign a value to the new variable and print it. Let's now examine the "memory picture" for a static object: Static members are assigned space in the global area. Important: This allocation of space is done before the program starts running. In general, when studying the "memory picture", we'll need to understand both where and when space is allocated. More about encapsulation: It is unusual to allow static variables to be accessed from another class.      ⇒ we just used this as an illustration. More commonly, static objects are used to put a bunch of related methods in one place. Here's an example of placing a bunch of related methods together: public class MathStuff { // Compute a^b public static int power (int a, int b) { if (b == 0) { return 1; } return (a * power (a, b-1)); } // Compute n! static int factorial (int n) { if (n == 1) { return 1; } return ( n * factorial(n-1) ); } // Compute f_n, the n-th Fibonacci number. static int fibonacci (int n) { // Base cases rolled into one: if (n <= 2) { return (n-1); } return ( fibonacci(n-1) + fibonacci(n-2) ); } } Note: The class MathStuff contains only static methods. There is no main() method. So, how are these methods used?      ⇒ From another class, e.g. public class MathStuffUsageExample { public static void main (String[] argv) { int p = MathStuff.power (3, 4); System.out.println ("3^4 = " + p); int m = MathStuff.factorial (5); System.out.println ("5! = " + m); int f = MathStuff.fibonacci (6); System.out.println ("6-th fibonacci number is: " + f); } } In this case, the class'es MathStuff and MathStuffUsageExample are in separate files. As you might expect, the Java library has its own collection of useful math functions, all put together in a class called Math. Example: public class MathExample { public static void main (String[] argv) { // The constant "e" double e = Math.E; // Compute e^2. double y = Math.exp (2.0); // Compute log_e(y) double z = Math.log (y); // ... } } About class'es and files: Each class is usually written in a separate file. With only one class per file, the class is declared public. However, for some applications, we'll need a "secondary" class that shouldn't be used outside the file      ⇒ These "secondary" classes will be in the same file (and not public). To simplify presentation and downloading, we will strive to put all relevant classes in a single file, even if this breaks with Java convention. Dynamic objects Let's look at an example with a dynamic object: // Dynamic object definition class ObjX { // No "static" keyword for either member. int i; void print () { System.out.println ("i=" + i); } } public class DynamicExample { public static void main (String[] argv) { // First create an instance, which allocates space from the heap. ObjX x = new ObjX (); // Now access members via the variable and the dot-operator. x.i = 5; x.print(); } } Note: The definition of ObjX now does not have the static keyword in front of any member.      ⇒ All members are dynamic. There is some data (the int i) and one method (print()). Before accessing the members, an instance needs to be created using the new operator: ObjX x = new ObjX (); In-Class Exercise 2: Download DynamicExample.java and add a second data member (say, another int) to the class ObjX. Then, in main, assign a value to the new variable and print it. Next, we'll make a small modification: class ObjX { // No "static" keyword for either member. int i; void print () { System.out.println ("i=" + i); } } public class DynamicExample2 { // A simple variable declaration. static ObjX x; public static void main (String[] argv) { // First create an instance, which allocates space from the heap. x = new ObjX (); // Now access members via the variable and the dot-operator. x.i = 5; x.print(); } } Note: The only change we've made is to declare the variable x as a "global" static variable in the class DynamicExample2: // A simple variable declaration. static ObjX x; In main, we simply create an instance since the variable has been already declared: x = new ObjX (); We'll now examine what memory looks like The memory picture for the above program before execution starts in main(): After the new operator is executed in the first line of main(): After the execution of the second line (which assigns a value to i): In-Class Exercise 3: Go back to the first example, DynamicExample.java, and draw the memory pictures for this example at various points in the execution of main(). A few words about nomenclature: Java books refer to dynamic objects as instances. Thus, variables in a dynamic object are called instance variables. Variables in a static object are called class variables. Similarly, there are instance methods and class methods. In other languages, the use of the term object always refers to dynamic objects. For Java programmers, when you get used to the differences between the types of objects, you will also get used to their nomenclature. Dynamic objects: more examples Consider this example: class ObjX { int i; void print () { System.out.println ("i=" + i); } } //end-ObjX public class DynamicExample3 { public static void main (String[] argv) { // Create an instance and do stuff with it. ObjX x = new ObjX (); x.i = 5; x.print(); // Create another instance assigned to the same variable. x = new ObjX (); x.i = 6; x.print(); } } Note: After the first three lines of main(), memory looks like this: The memory picture after the execution of the lines // Create another instance assigned to the same variable. x = new ObjX (); x.i = 6; What happens to the "floating" instance containing "5"?      ⇒ It gets garbage-collected. Finally, after main() completes, all the heap and stack memory is freed -up. About garbage collection: This is a process that runs invisibly in the background. The garbage collector looks for "unattached" chunks of heap memory (nothing points to these chunks) and scoops them up.      ⇒ Puts the memory back into the "available pool" of heap memory. The garbage collector always runs in the background, every few milliseconds. In-Class Exercise 4: Consider the example below and draw the memory picture just after the execution of x2.i=6: class ObjX { // ... same as before } public class DynamicExample4 { public static void main (String[] argv) { // Create an instance and do stuff with it. ObjX x = new ObjX (); x.i = 5; x.print(); // Create another instance assigned to the same variable. ObjX x2 = new ObjX (); x2.i = 6; x2.print(); } } An example with an array of objects: class ObjX { int i; void print () { System.out.println ("i=" + i); } } //end-ObjX public class DynamicExample5 { public static void main (String[] argv) { // Make space for 4 ObjX pointers. ObjX[] xArray = new ObjX [4]; // Make each of the 4 pointers point to ObjX instances. for (int k=0; k < 4; k++) { xArray[k] = new ObjX (); } // Now assign data to some of them. xArray[0].i = 5; xArray[1].i = 6; // Print all. for (int k=0; k < 4; k++) { xArray[k].print(); } } } Note: Observe how an array variable (for ObjX instances) is declared: ObjX[] xArray = new ObjX [4]; We could have, alternatively, split the declaration and space allocation: ObjX[] xArray; xArray = new ObjX [4]; This creation of space only makes space for 4 ObjX pointers: We have to create four instances of ObjX ourselves with the code: // Make each of the 4 pointers point to ObjX instances. for (int k=0; k < 4; k++) { xArray[k] = new ObjX (); } The memory picture after this is: The memory picture after the two assignments: // Now assign data to some of them. xArray[0].i = 5; xArray[1].i = 6; In-Class Exercise 5: Consider the example below and draw the memory picture just after the execution of x2.i=6: class ObjX { // ... same ... } public class DynamicExample6 { public static void main (String[] argv) { ObjX x = new ObjX (); x.i = 5; // Assignment between object variables. ObjX x2 = x; x2.i = 6; x.print(); x2.print(); } } What gets printed out? Object instances can be passed as parameters to methods and can be returned from methods: class ObjX { // ... same ... } //end-ObjX public class DynamicExample7 { public static void main (String[] argv) { // Call a method that returns an instance of ObjX. ObjX x = makeAnObject(); // Pass the instance as parameter to a method. printTheObject (x); } // Note: return type is ObjX static ObjX makeAnObject () { ObjX obj = new ObjX (); obj.i = 5; return obj; } // Note: parameter type is ObjX static void printTheObject (ObjX obj) { obj.print(); } } Note: Consider the state of memory just before the return statement is executed in makeAnObject(): After makeAnObject() returns to main(): In-Class Exercise 6: What is the memory picture just after executing the only line in printTheObject(), but before the method returns? In-Class Exercise 7: Download DynamicExample8.java and draw the memory picture just after the last statement in main(). Objects that contain objects Consider this example: class ObjX { int i; ObjY y; } class ObjY { String name; } public class DynamicExample9 { public static void main (String[] argv) { // Make an ObjX instance. ObjX x = new ObjX (); x.i = 5; // The y instance variable in x is assigned an instance of ObjY x.y = new ObjY (); // Note the repeated use of the dot-operator. x.y.name = "Mr. Y"; } } Note: The memory picture after the first two lines: After the third line executes, the memory picture is: In-Class Exercise 8: Draw the memory picture after the last line executes in main(). Now for an interesting variation: class ObjX { int i; // A variable of the same type: ObjX anotherX; } public class DynamicExample10 { public static void main (String[] argv) { // Make an ObjX instance. ObjX x = new ObjX (); x.i = 5; // Make the anotherX variable point to a new ObjX instance. x.anotherX = new ObjX (); x.anotherX.i = 6; } } In-Class Exercise 9: Draw the memory picture after the last line executes in main(). The special method toString() The method toString(), if written for any object as shown below, has a special use in Java: class ObjX { int i; public String toString () { String s = "i=" + i; return s; } } public class DynamicExample11 { public static void main (String[] argv) { // Make an ObjX instance and assign something to its variable i. ObjX x = new ObjX (); x.i = 5; // Note how "x" is directly given to println() System.out.println (x); // Another use: String outputStr = "Object x: " + x; System.out.println (outputStr); } } Note: The method toString() must be defined exactly as shown above, with the signature public String toString () { } As the return type indicates, it must return a String. Whenever the Java compiler sees an object variable in the context of a String, as in String outputStr = "Object x: " + x; The compiler places a call to the object's toString() method. The call returns a String, as we would expect. This String is then used in place of the object. Thus, in the above example, the string "i=5" gets concatenated with the string "Object x:" to give "Object x: i=5". In-Class Exercise 10: Download the above program and remove the toString() method from ObjX. Then compile and run. What do you see? Constructors Recall how we create object instances with the new operator: // Make an ObjX instance. ObjX x = new ObjX (); You might wonder why there are parentheses at the end. Before we answer that question, we will enhance our ObjX example and add a few so-called constructors: class ObjX { int i; String name; // This is the first constructor: only takes in one parameter for the name. public ObjX (String initialName) { name = initialName; i = 0; } // This is the second constructor: both name and ID are initialized. public ObjX (String initialName, int ID) { name = initialName; i = ID; } public String toString () { return "My name is " + name + " and my ID number is " + i; } } public class DynamicExample12 { public static void main (String[] argv) { // The old way: without using constructors. Will not compile ObjX x = new ObjX (); x.i = 5; x.name = "Mr. X"; System.out.println (x); // This compiles fine: // Use the first constructor. x = new ObjX ("Mr. X"); System.out.println (x); // Use the second one. x = new ObjX ("Mr. X", 5); System.out.println (x); } } First, let's fix the compilation problem by removing the "old way": class ObjX { int i; String name; // This is the first constructor: only takes in one parameter for the name. public ObjX (String initialName) { name = initialName; i = 0; } // This is the second constructor: both name and ID are initialized. public ObjX (String initialName, int ID) { name = initialName; i = ID; } public String toString () { return "My name is " + name + " and my ID number is " + i; } } public class DynamicExample13 { public static void main (String[] argv) { // Use the first constructor. ObjX x = new ObjX ("Mr. X"); System.out.println (x); // Use the second one. x = new ObjX ("Mr. X", 5); System.out.println (x); } } Note: A constructor executes just once when an object instance is created. Thus, when the first instance is created above: ObjX x = new ObjX ("Mr. X"); the first (one-parameter) constructor executes. Similarly, when the second instance is created above x = new ObjX ("Mr. X", 5); the second constructor (with two parameters) executes. Constructors are similar to methods in that they take parameters and execute like code. But constructors are unlike methods in that: They are declared differently: they must have the same name as the class and they cannot declare a return type. They cannot be called as a regular method. This fails to compile: ObjX x = new ObjX ("Mr. X"); x.ObjX ("Mr. Y"); // Will not compile. They execute only once for each instance, and execute at the time of creation. Thus, the constructor finishes execution by the time we get to the second line below: // Use the first constructor. ObjX x = new ObjX ("Mr. X"); System.out.println (x); Other facts about constructors: Any number of them can be defined, as long as they have different signatures. So, now let's get back to some nagging questions: Why were there parentheses earlier when we didn't define any constructor? Why, after we defined constructors, didn't the "old way" work? And, finally, why bother with constructors at all? Answers: When you don't define your own constructors, Java puts one in for you (but you don't see the code, of course).      ⇒ This is the "default no-parameter" constructor.      ⇒ This is why the parentheses are part of the syntax. When a programmer defines constructors, the Java compiler takes away the default no-parameter constructor      ⇒ It now becomes a compiler error to try and use one. You can of course, define your own no-parameter constructor: class ObjX { int i; String name; // This is the first constructor: only takes in one parameter for the name. public ObjX (String initialName) { name = initialName; i = 0; } // This is the second constructor: both name and ID are initialized. public ObjX (String initialName, int ID) { name = initialName; i = ID; } // No-parameter constructor that we added public ObjX () { name = "X"; // Default name i = 0; // Default value of i } public String toString () { return "My name is " + name + " and my ID number is " + i; } } public class DynamicExample14 { public static void main (String[] argv) { // Use the no-parameter constructor. ObjX x = new ObjX (); x.i = 5; System.out.println (x); } } Why use constructors at all, especially if you can initialize values directly? ObjX x = new ObjX (); x.i = 5; First, it's bad practice to place initialization code like this outside the class. The right place for initialization code is the class itself, in the constructor. Most often a class is written by one programmer, and used by another.      ⇒ The creator of the class shouldn't allow others to modify data      ⇒ Data should be declared private Thus, the right way to declare data in ObjX is: class ObjX { private int i; private String name; // ... constructors, methods ... etc } Then, something like this in main() ObjX x = new ObjX (); x.i = 5; // Compiler error! won't compile. More about objects There's a lot more to objects than we have described here      ⇒ Our purpose was to introduce objects in enough detail for the data structures we will examine soon. What we haven't covered: Gory details about constructors. Objects that have both static and dynamic members. Inheritance. Method overriding and callbacks. Abstract classes. Interfaces. Casting in objects. Java's Object class and its implications. Esoteric Java topics like: shadowed variables, the this operator, the use of instanceof, static initializers, and inner classes. To read more: See Modules 4, 5, 6, 7 and 10 of CS-143 See the two short overviews of objects in the CS-143 Appendix entitled "Why use objects?" and "More examples on objects". © 2006-2020, Rahul Simha & James Taylor (revised 2020)