Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
Inheritance in Java: The keyword extends is used to signify inheritance in Java. Outline for Lecture 13 For example, suppose we want to subclass Point to create a class Circle. Then we might write— A. Abstract classes B. this public class Circle extends Point { protected double radius; public Circle() { super(0, 0); // Call the // superclass constructor setRadius(0); } I. Inheritance II. Predicates & control flow III. File streams IV. Exception handling V. Modularization. VI. Interfaces So far, we have seen three new features of Java: • The keyword extends is used to signify subclassing. • The radius instance variable is declared not as public or private, but as protected. This means that it is accessible to the Circle class and any of its subclasses, or other classes in its package, but not to other classes in the system. How does this compare with instance variables in Smalltalk? In declaring variables and methods to be public, protected, or private, it is good to follow the need-to-know principle: Only those classes that need to be able to access a variable or method should be able to. This prevents other classes from coming to depend upon them, and allows the programmer to change their implementation later, if needed. Lecture 13 Object-Oriented Languages and Systems 207 • In the process of creating a Point object, the superclass constructor is called. This is denoted by the call super(…). Continuing with the class definition … //Three-parameter constructor public Circle(double r, double x, double y) { super(x, y); setRadius(r); } public void setRadius(double r) { radius = r; } public double getRadius(double r) { return radius; } //Calculate area of circle public double area() { return Constants.¹ * radius * radius; } //Return a representation of circle public String printString() { return "Center = " + "(" + x + "," + y + "); Radius = " + radius; } } Circle is a subclass of Point. Any subclass of Circle is also a subclass of Point. Because Circle inherits directly from Point, it is called a direct subclass of Point. Point is called a direct superclass of Circle. © 2000 Edward F. Gehringer CSC 517/ECE 517 Lecture Notes, Fall 2000 208 In the last lecture, our definition of Point did not specify that it was extending a class. Since it did not, Object was its direct superclass. A class can extend only one class; that is, it can have only one direct superclass. Because of this, Java is said to have single inheritance. The “opposite” of single inheritance is multiple inheritance, as in C++. Since Java has single inheritance, all classes form a tree with Object at the root. For safety’s sake, Java always makes sure that a constructor calls its superclass’s constructor. (Why did I say, “For safety’s sake”?) Thus, if the first statement in a constructor is not a call to super, then Java implicitly inserts the call super(). As in Smalltalk, a subclass can override some of its superclasses’ methods. To decide which method to use, Java follows the same rule as Smalltalk: If an invoked method is not defined in a class, then the direct superclass is checked, then its direct superclass, etc. That is, the compiler searches from the object’s class up the tree toward Object until it finds a definition of the method. Unlike in Smalltalk, the compiler can determine whether the call will succeed. Why? Lecture 13 Object-Oriented Languages and Systems 209 The Winston & Narasimhan text suggests a rule for deciding where to site instance methods (and instance variables. Two criteria should be satisfied: • There should be no needless duplication of an instance method or variable. • Each instance method and variable should be useful in all the subclasses of the class in which it is defined. Abstract classes: In Smalltalk, our abstract classes were identified by having some of their methods implemented as subclassResponsibility. In Java, it is possible to declare a class to be abstract, and then any attempt to create an instance of it will be flagged by the compiler. Consider an abstract class Shape, with subclasses of Circle and Rectangle: public abstract class Shape { public abstract double area(); public abstract double circumf(); } Notice that not only the class, but also its methods are declared to be abstract. A method declared as abstract is required to be implemented in every subclass of the class declaring the abstract method. © 2000 Edward F. Gehringer CSC 517/ECE 517 Lecture Notes, Fall 2000 210 Note that an abstract method has no body; a semicolon immediately follows the signature. If a class extends Shape but does not implement both area and circumf, the compiler will complain. In essence, defining abstract classes allows us to make aspects of our design explicit in our code. Here is the code for the Circle class: public class Circle extends Shape { protected double radius; public Circle() {radius = 1.0;} public Circle(double r) {radius = r;} public double area() { return Constants.¹ * radius * radius; } public double circumf() { return 2 * Constants.¹ * radius; } public double getRadius() {return radius;} } What would happen here if we didn’t have the getRadius() access method? Here is the code for Rectangle: public class Rectangle extends Shape { protected double w, h; public Rectangle() {}; public Rectangle(double wd, double ht) { w = wd; h = ht;} public double area() {return w*h;} public double circumf() {return 2*(w+h);} public double getWidth() {return w;} Lecture 13 Object-Oriented Languages and Systems 211 public double getHeight() {return h;} } If a class is not supposed to be subclassed, you can declare it as final. Then any attempt to subclass it produces a compilation error. this: We have seen how a constructor can call a constructor of a superclass. It is also possible for a constructor to call a constructor of the same class. For example, suppose our Rectangle class above included a one-parameter constructor for specifying a zero-height rectangle (a horizontal line). It could be implemented simply by calling the two-parameter constructor: public Rectangle(double wd) { this(wd, 0.0); } In this call, we need to use the this keyword. What would happen if we tried to use Rectangle(wd, 0.0)? In general, this in Java is like self in Smalltalk; Smalltalk messages that would be sent to self are sent to this in Java. © 2000 Edward F. Gehringer CSC 517/ECE 517 Lecture Notes, Fall 2000 212 For example, suppose we redefined the area method of Circle in terms of circumf: public double area() { return this.circumf() * radius / 2; } Since the receiver of the message is the same instance as the sender, we send this.circumf(). Instead of explictly naming this, however, Java allows us to omit it. That is, if the receiver is omitted, then this is understood: public double area() { return circumf() * radius / 2; } Predicates and control flow: In general, the expressions and statements that control the flow of a Java program are very similar to those in C or C++. They are covered in ¤20–¤25 of Winston and Narasimhan. We will touch only on the highlights. =, ==, and equals: In Java, = means assignment, while == means a test for equality. As in C or C++, a common error is to write = when you mean to test for equality. In Java, == compares • values, if the objects being compared are primitive types, or • references, if the objects being compared are class types. Lecture 13 Object-Oriented Languages and Systems 213 Thus, it does not test objects for equality. However, several classes define an equals method to test objects for equality. For example, suppose we define String s1 = new String("Hello, world!"); String s2 = new String("Hello, world!"); Then s1 == s2 returns while s1.equals(s2) returns If we then define String s3 = s1; then s1 == s3 returns and s1.equals(s3) returns One caveat is that if we define String s4 = "Hello, world!"; String s5 = "Hello, world!"; then s4 == s5 and s4.equals(s5) are both true. This is because Java treats all anonymous Strings with the same contents as one anonymous String that has many references. && and || vs. & and |: The double operators && and || may not evaluate both of their operands. The left-hand side operand is evaluated. If the truth or falsity of the expression can be determined from the lh. operand, then the rh. operand is not evaluated. © 2000 Edward F. Gehringer CSC 517/ECE 517 Lecture Notes, Fall 2000 214 However, & and | always evaluate both of their operands. Ordinarily, it is more efficient to use && and || on Boolean expressions. Use the others only when you are depending on side-effects of evaluating the right-hand expression. The operators | and & can also be used on integral expressions; in this case, they perform bitwise and and or. Augmented assignment: The form operator = means that an operation is performed on the lhs. and rhs. and then assigned to the lhs. That is, result *= 2 means the same as result = result * 2 This is a useful shorthand, but sometimes obscures the fact that an assignment is being performed. The augmented assignment may be quicker when using arrays, because the index expression needs to be calculated only once. So, a[i*5] *= 2 may be faster than a[i*5] = a[i*5] * 2. Order of evaluation: Unlike C and C++, Java prescribes that expressions are evaluated left-to-right. Thus, expressions like ++x + x have well defined results in Java. Lecture 13 Object-Oriented Languages and Systems 215 Switch statement: Java has a switch statement like C or C++: public class Demo { public static int fibonacci(int n) { switch (n) { case 0: return 0; case 1: return 1; default: return fibonacci(n - 1) + fibonacci(n - 2); } } } Note that if the first two cases did not return, we’d have to insert break statements to prevent control from falling through to the next case. File streams: A stream is a sequence of data. • The stream that flows from your keyboard to your program is the standard input stream. • The stream that flows from your program to the display is the standard output stream. To read data, you create an input stream that connects an input file to your program. In order to use input (or output) streams, you need to inform Java that you want to use classes from the input-output package: import java.io.FileInputStream; import java.io.FileOutputStream; or simply import java.io.*; © 2000 Edward F. Gehringer CSC 517/ECE 517 Lecture Notes, Fall 2000 216 Then, you create an instance of FileInputStream, and assign it to a particular file. FileInputStream inputFile = new FileInputStream("/ncsu/efg/input.data"); We can use inputFile.read() to read a single byte. However, it is usually more convenient to read complete numbers and strings. To do this, one converts a FileInputStream into an instance of StreamTokenizer. StreamTokenizer tokens = new StreamTokenizer(inputFile); Tokenizers treat whitespace as delimiters. Thus, a file containing the following characters is viewed as a stream of seven numbers: 1.0 2 3.1415926536 -42 5.15e6 678 0 The nextToken method moves the tokenizer from one token to the next. Each time it moves to a token, it assigns the token it moves past to its instance variable nval. What are the values that nval takes on on successive calls to nextToken? The nval variable is of type double, so if you want to change it to any other value, you need to cast it: (int) tokens.nval Lecture 13 Object-Oriented Languages and Systems 217 When the tokenizer reaches the end of the stream, nextToken returns a special value, which is the same as the value in the tokenizer’s TT_EOF instance variable. Thus, to test for end-of-file, you can use while (tokens.nextToken() != TT_EOF) { … i = (int) tokens.nval … … } Before the end of the file is reached, nextToken returns • TT_NUMBER whenever it reads a number (and it assigns the value of the number to nval), and • TT_WORD whenever it reads a string (and it assigns the value of the string to sval). Output streams are created in a similar fashion: FileOutputStream outputFile = new FileOutputStream("output.data"); You can write a byte at a time to the output stream with the write(byte[] b) method. However, it is usually more convenient to use an instance of PrintStream. PrintStream ouput = new PrintStream(outputFile); You can write to PrintStreams using the print and println methods. After you have finished reading from or writing to a file, you should close it: © 2000 Edward F. Gehringer CSC 517/ECE 517 Lecture Notes, Fall 2000 218 inputFile.close(); outputFile.close(); Exception handling: Normally, a programmer writes code to deal with an exceptional condition where that condition is detected. For example, in Smalltalk’s lazy initialization, the programmer codes a check for a null instance variable, and if it is found to be null, then initializes it. This is a good way to handle exceptions in code that doesn’t have to deal with a lot of them. However, production code tends to contain a lot of error handling, which has the effect of “polluting” the source code. Therefore, code is more readable if the language provides a mechanism to deal with exceptions elsewhere. These exceptions include events like— • • • • array out of bounds, arithmetic overflow and zero-divide, memory exhaustion, nonexistent files or insufficient permissions. It is possible for a programmer to define additional exceptions, but it is best not to overuse exceptions. They are expected to be unusual, so the compiler may not concentrate on optimizing code for dealing with them. When an error is detected by a method, the method will throw an exception. Lecture 13 Object-Oriented Languages and Systems 219 The programmer can choose whether to write code that deals with the exception. • If (s)he chooses not to deal with the exception, (s)he writes in the procedure heading, e.g., throws IOException to indicate that the method will not deal with any exceptions involving I/O. If this is done in the main method, its header might read— public static void main(string argv[]) throws IOException { … } In this case, handling the exception is the responsibility of the code that invoked this method. • If (s)he chooses to deal with the exception, (s)he encloses the code that may generate the exception in a try block. The try block is immediately followed by one or more catch blocks. Java stops executing statements in the try block as soon as an exception is thrown. Suppose we are trying to read from a file. If an exception occurs, exception-handling code prints out the name of the exception. try { … FileInputStream inputFile = new FileInputStream("input.data"); … } catch (IOException e) { © 2000 Edward F. Gehringer CSC 517/ECE 517 Lecture Notes, Fall 2000 220 System.out.println(e); } Note that the I/O exception is actually an instance of an exception class. The exception may be an instance of FileNotFoundException, which is a subclass of IOException. IOException FileNotFoundException Multiple catch blocks permit different code to be used to handle different exceptions. For example, • one piece of code can be used to handle a FileNotFoundException (perhaps a retry, in case the file is being created “as we speak”), and • another piece of code to handle any other kind of I/O exception (perhaps just reporting the exception that occurred). public static String readInputFile(String fileName) { boolean tryAgain = true; while (tryAgain) { try { tryAgain = false; FileInputStream inputFile = new FileInputStream(fileName); … } catch (FileNotFoundException) tryAgain = true; } catch (IOException e { System.out.println(e); } } … } Lecture 13 Object-Oriented Languages and Systems 221 Occasionally, you may want to have a piece of code executed after a try statement, regardless of whether an exception occurred. In this case, Java provides a finally block: try { … } catch (exception-class name e){ … } finally { //clean-up statements } Modularization of Java programs: In Visualworks\Smalltalk, related classes are combined into the same This is the only structuring feature available in Smalltalk. Java has two different mechanisms for grouping classes. • Multiple classes may be defined in the same file. In this case, however, only one of these classes may be public—the one whose name matches the name of the file. • Classes from multiple files may be grouped into a package. In this case, an identical package statement is placed at the beginning of each file. package csc517.project.gui; public class Window { …} © 2000 Edward F. Gehringer CSC 517/ECE 517 Lecture Notes, Fall 2000 222 package csc517.project.gui; public class DialogBox extends Window { …} Package names. • The components of package names always correspond to the components at the end of the pathname where they are stored. • The beginning of the pathname is taken from the CLASSPATH environment variable. So, the full pathname could be— /ncsu/efg/csc517/project/gui/Window.java Protection of variables and methods: So far, we have seen three of Java’s visibility levels, public, Java has two more visibility levels, • default, i.e., declared without a visibility specifier int i; Here is a table that summarizes the visibility: Situation Inherited by subclass in same package? Inherited by subclass in different package? Accessible to other classes in same package? Accessible to classes in different packages? Lecture 13 public protected default private Yes Yes Yes No Yes Yes No No Yes Yes Yes No No No No Object-Oriented Languages and Systems 223 Note that it is possible to have public access methods, but private instance variables. This allows the implementor to change the implementation of the class without affecting clients of the class, as long as the signature of the methods doesn’t change. Also note the difference between “inherited” and “accessible.” Subclasses inherit private protected and protected fields, but they can’t access them in instances of the superclass. The same applies to protected fields when the subclas is not in the same package as the superclass. Interfaces: C++ is among those languages offering multiple inheritance: a class may inherit from more than one direct superclass. This adds flexibility, but it also brings problems. • A class may inherit two or more methods of a given name along different paths. Which method should take precedence? Many strategies have been developed for this, but they often produce surprising results. • Multiple inheritance imposes considerable implementation difficulties on a language, and may make it take longer to call a method. In view of these difficulties, Java adopted a different approach: © 2000 Edward F. Gehringer CSC 517/ECE 517 Lecture Notes, Fall 2000 224 • A class is allowed to inherit methods from more than one direct superclass, but • all the methods inherited must be abstract, with the exception of the methods inherited from a single class. This solves the problems with multiple inheritance: • Since only a single method is inherited, there can’t be any conflict about which method predominates. • Since the inheritance graph is a tree, implementation is comparatively simple and fast. The entirely-abstract classes inherited from are called interfaces. Let’s take a look at an interface. interface Drivable { boolean startEngine(); void stopEngine(); float accelerate(float acc); boolean turn(Direction dir); } The keyword interface replaces the word class in the class definition, and all methods must be abstract. Note that these methods, being abstract, have no body defined here. Instead, they end with a semicolon. They could be defined to be abstract, but that’s not necessary. Since interfaces name capabilities, it’s often a good idea to name them after those abilities. Lecture 13 Object-Oriented Languages and Systems 225 Any class that implements all the methods can then declare that it implements the interface. class Automobile implements Drivable { … boolean startEngine() { if (notTooCold) engineRunning = true; … } void stopEngine() { engineRunning = false; } etc. } Other classes, like lawnmower, could also implement Drivable. An interface can also serve as a type specifier. You can write procedures that take objects of the interface type as parameters: Drivable myVehicle; But the main purpose of an interface is to impose requirements on the class(es) that want to implement it. Example: The java.util.Enumeration interface. This interface can be used by any kind of set to provide serial access to its elements. An object that implements the Enumeration interface must provide two methods, • nextElement(), which returns an Object type so it can be used with any kind of collection, and • hasMoreElements(). © 2000 Edward F. Gehringer CSC 517/ECE 517 Lecture Notes, Fall 2000 226 Any kind of object can implement the Enumeration interface and then use it to provide access to its elements. This means we can write general looping code, to which we later plug in the appropriate object types. Enumeration e = … while (e.hasMoreElement()) { String s = (String) e.nextElement(); System.out.println(s); } Summary: • Java is a single-inheritance language. • Java provides linguistic mechanisms for declaring abstract superclasses, and enforcing the requirement that abstract methods be implemented in subclasses. • Control flow is similar to C and C++, except for the fact that expressions are guaranteed to be evaluated left-to-right. • File streams are used for input and output. Utility classes such as tokenizers and print streams facilitate their use. • The exception-handling mechanism of Java relies upon “throwing” exceptions from one place in the code to be dealt with by an exception-handler elsewhere. • Packages and multiple compilation units make it possible to organize a large program. • Interfaces provide many of the benefits of multiple inheritance, while avoiding most of the costs. Lecture 13 Object-Oriented Languages and Systems 227