CS61B, Lecture #7 & 8, 28-29 June 2000. P. N. Hilfinger. Modifications and some sections by M. Brudno Access to Classes ------ -- ------- Two purposes of the whole package idea are (1) to allow programmers to produce related families of type definitions without the names interfering with each other (e.g., ucb.io.Table would be distinct from foo.Table), and (2) to specify which names from these families of definitions are meant to be mentioned outside the package. In order for a type (class or interface), say A, to be referred to from within a type that is not in the same package, A must be a "public class", as in public class A { /* etc. */ } On the other hand, the definition of any type may refer to any other type in the SAME package, whether or not it is public. There is no special relationship between a package and its subpackages. Thus, the class foo.Bar may only access public classes of foo.thud.Bar. [Nitty-Gritty Detail: This puts a certain burden on compilers (like javac). Pity the poor compiler, working on file Prog.java, when it sees a reference to a type Foo. How is it to know that the definition of class Foo resides in file Glorp.java? In the case of our system (the JDK), it doesn't always. The JDK requires that if a public class is named Glorp, it must reside in a file named Glorp.java, but it says nothing about non- public classes. In fact, for the JDK to "see" a non-public class, Foo, whose name does not match its file name, you must either have previously compiled the file that contains the definition of Foo (so that there is a Foo.class file lying around that the compiler will find) or you must compile that file in the same javac command as the file that references Foo. To avoid trouble, you should usually put the definition of class Foo in a file named Foo.java. There are a few obvious exceptions: if the only uses of class A are in class B, then they may as well go into the same source file. Likewise, if one never uses class A without using class B, they may as well reside in the same class file.] Access to Fields and Members ------ -- ------ --- ------- Especially now that I've introduced packages, I am going to have to get more specific about some of these modifiers that I've been using rather casually. Part of the philosophy behind the use of classes and object-based programming in general is that of the "separation of concerns". The implementor of any given class undertakes to meet a certain "specification", which is expressed as a set of method "signatures" (function names, return types, and argument types) and fields, together with some sort of commentary about what these methods and fields are supposed to do or represent. The user of a class (the "client") is supposed to be concerned only with the specification, not with the bodies of the functions. The implementor of a class may introduce auxiliary methods, fields, and even classes that are outside the specification. Clients are not intended to use these auxiliary items, only those mentioned in the specification. Ever since programming-language designers started concentrating on language features for defining new types, it has been traditional to provide some distinction in the language between definitions that are intended to be used by clients and those that are intended for use only by implementors. This distinction adds no power to the language, but does allow implementors of classes to make their intentions clear. We've seen that classes can be public (available to any other class) or non-public (available only to classes within the package containing them). Members and fields of a class have a richer set of options, specified by adding one (or none) of the modifiers "public", "private", or "protected" to the declaration of a field or method (before the specification of the type or return type). When none of these modifiers is present, we say that the method or field has "default access". Let's define the meanings of all these types of access by showing what is accessible where in a set of generic class definitions. In this example, I use only methods; the rules for fields, however, are the same. package P1; | package P2; | public class A { | public class D { public void pub_f() { } | void func(P1.A x) { private void priv_f() { } | x.pub_f(); protected void prot_f() { } | x.priv_f(); // ILLEGAL void deflt_f() { } | x.prot_f(); // ILLEGAL }; | x.deflt_f(); // ILLEGAL | } class B { | } void func(A x) { | x.pub_f(); | class E extends P1.A { x.priv_f(); // ILLEGAL | void func(P1.A x) { x.prot_f(); | x.pub_f(); x.deflt_f(); | x.priv_f(); // ILLEGAL } | x.prot_f(); // ILLEGAL } | x.deflt_f(); // ILLEGAL | pub_f(); class C extends A { | priv_f(); // ILLEGAL void func(A x) { | prot_f(); x.pub_f(); | deflt_f(); // ILLEGAL x.priv_f(); // ILLEGAL | } x.prot_f(); | } x.deflt_f(); | pub_f(); | priv_f(); // ILLEGAL | prot_f(); | deflt_f(); | } | } | In other words, a member M (method or field) that * is public is directly accessible in any class; * is private is directly accessible only in the class defining it; * has default access is directly accessible in classes in the same package as the defining class; * is protected is directly accessible in classes in the same package as the defining class; additionally, this.M is accessible within any subtype of the defining type (that is, any type that extends the defining type). As an example of at least some of this, let's suppose that I am putting together a package, mytools, of various utility classes. One of them is a kind of array-like thing that, unlike ordinary Java arrays, can grow. I might write: package mytools; public class ExpandableArray { /** An array of size N (elements initially null). */ public ExpandableArray (int N) { size = N; elts = new Object[N]; } /** Current size of THIS. */ public int size () { return size; } /** Element #K of THIS. Requires 0<=K= size) throw new ArrayIndexOutOfBoundsException(); return elts[k]; } /** Change elt(K) to be X, where 0 <= K <= size(). Increases * size by one if K == size(). */ public void setElt (int k, Object x) { if (k < 0 || k > size) throw new ArrayIndexOutOfBoundsException(); if (k == size) setSize (size+1); elts[k] = x; } /** Cause size() to be N>=0. If this increases size(), set new * elements to null. */ protected void setSize (int n) { if (n < 0) return; if (n > elts.length) { Object[] elts0 = new Object[n]; System.arraycopy (elts, 0, elts0, 0, elts.length); elts = elts0; } for (int k = size; k < n; k += 1) elts[k] = null; size = n; } private int size; private Object[] elts; } Here, I have made size and elts private. The only way any other class (including subclasses) can access or modify these is by using the methods elt, setElt, setSize, and size. Therefore, we can guarantee that 0<=size [ 42 ] ^ k: [ -]------| so when we execute j[3]++ we get: i: [ -]-> [ 43 ] ^ k: [ -]------| as a result i.j changes. if we were to change our code to read: void foo(IntCell j) { j = new IntCell(43); } void glorp() { IntCell i = new IntCell(42); foo(i); System.out.println(i.j); // this will print out 42 } i.j is unaffected because if we look at the box and pointers, we will see that right after the call everything is the same, i: [ -]-> [ 42 ] ^ k: [ -]------| but when we execute j = new IntCell(43) we get i: [ -]-> [ 42 ] k: [ -]-> [ 43 ] thus i.j is unaffected by changes to the value of k.j. Remember, when in doubt about what certain references do, always draw box and pointer diagrams. Static Initializers (Section written by M. Brudno) ------ ------------ Lets say you want to pre-load into one of your classes an array with the first N primes. You can't do this using a constructor because the array should be static. You also can't make sure that a method making this array will be called before your class is used because someone else is writing the main method. Luckily, Java provides you with static initializers: static int N=40 static int[] primes = new int[N] static{ for (int i = 0, p = 2; i < primes.length; p++) if (isPrime(p)) { primes[i] = p; i++; } } This will cause the array to be properly initialized. The initializer must come after the variable it is initializing. Nested and Inner Classes ------ --- ----- ------- It sometimes happens that one class is inextricably bound up with another and only used by or in association with that other class. In such cases, it sometimes makes sense to nest one of the classes in the other. For one thing, it eases the naming problem. For example, suppose you have a method in one class that returns more than one value at once, specifically, suppose we'd like to recast the factor method from the homework so that it has one method for delivering factors: public class LetterStream { /** The stream of letters of s. */ public LetterStream(String s) { ... } /** Return the next letter of s and its count, or null if * there are no more. Successive calls give successive * letters. */ Letter next() { ... } /** Restart the stream of letters of s. */ void reset () { ... } } public class Letter { char letter; int count; Letter(char letter, int count) { this.letter = letter; this.count = count; } } This way, Letter is on the same level as LetterStream, even though it really isn't good for much else than carrying next()'s results. What's worse, "Letter" is a rather nice name, and you can be sure someone else will eventually want to use it for something else. So, let's put Letter inside LetterStream: public class LetterStream { /** The stream of letters of s. */ public LetterStream(String s) { ... } /** Return the next letter of s and its count, or null if * there are no more. Successive calls give successive * letters. */ Letter next() { ... } /** Restart the stream of letters of s. */ void reset () { ... } static public class Letter { char letter; int count; Letter(char letter, int count) { this.letter = letter; this.count = count; } } } Now, next() returns LetterStream.Letter. As usual, the "static" designation here means that there is precisely one version of Factor common to all LetterStreams. It's just like an ordinary "outside" class, but (1) it's referred to from outside the enclosing class with a dotted name (like other members of the enclosing class) and (2) being as part of the enclosing class, it is allowed to access all the static fields of the enclosing class, even private ones. Private, protected, and default-access nested classes are possible too: they might be used if you needed a special data structure tailored specifically to the implementation of a class, and used nowhere else. The "import" clause extends to these nested classes, as in import LetterStream.Letter; or import LetterStream.*; which allow us to write just Letter rather than LetterStream.Letter. As usual with "imports", this merely introduces shorthand. It doesn't allow you to access private classes, or any other such bending of the scoping and accessibility rules. It might not be obvious what a NON-static nested class might mean, and to be sure, it makes things a bit complicated. Basically, you use a non-static nested class when every object of that class is associated somehow with a specific instance of the enclosing class. For example, banks have ATMs and branch offices associated with them. Without nested classes, I might define class Bank { ... void hire(...) { ... } void deposit(...) { ... } } class ATM { /** An ATM hooked up to ASSOCIATEDBANK. */ public ATM(Bank associatedBank) { myBank = associatedBank; ... } void depositInto (int amount, ... ) { ... myBank.deposit(amount, ...); } private Bank myBank; } class BranchOffice { /** A branch of mainBank. */ public BranchOffice(Bank mainBank) { myBank = mainBank; ... } // (continued) void hireTeller (...) { ... myBank.hire(...) ... private Bank myBank; } But what's a branch office or ATM without a bank? With non-static nested classes, I can put them together explicitly, and get the effect of the 'myBank' field in the bargain: class Bank { public class ATM { public ATM () { ... } public void depositInto (int amount, ...) { ... Bank.this.deposit(amount,...); ... // or ... deposit(amount,...); ... } } public class BranchOffice { public BranchOffice () { ... } public void hireTeller (...) { ... Bank.this.hire(...) ... // or ... hire(...) ... } } } Since ATM and BranchOffice are non-static, one can only reference them through some Bank object. We can create a bank and associated ATM with: Bank B = new Bank(); Bank.ATM A = B.new ATM(); and now we can write A.depositInto(...); Here is how we use "box models" to depict the resulting situation: +----------------------------+ V | +---+ +----------------+ +--------------+--+ B: | *-+------->| (Bank object) | |Bank.this: | * | +---+ | | | +-----| | | | | +----------------+ | (ATM object) | +---+ | | A: | *-+------------------------------>| | +---+ | | +-----------------+ As with ordinary "this", Java allows leaving out "Bank.this." in front of things in the body of ATM when the meaning is unambiguous. Nesting can be more than two deep, but I agree with Arnold&Gosling: Don't. It is also possible to have non-static classes nested inside BLOCKS---in methods particularly. Arnold and Gosling provide an excellent example on page 54. Generically: class Encloser { int x; SomeType someName(int arg1, final int arg2) { AnotherType u; final SomeOtherType v = ...; class Inner { void aMethod () { // Access to v, arg2, x (or Encloser.this.x) legal. // Access to arg1, u illegal. } ... } Inner C = new Inner (); ... } } The model to have for C during a call anEncloser.someName(...) is something like this: +---+ anEncloser: | *-+------------------+ +---+ | | | V +------------------+ +---------------+ | this: | *--+----->| x: | | | +------+ | +--------+ | arg1: | | |(an Encloser) | | +------+ +---------------+ | arg2: | | | +------+ | u: | | | +------+ | v: | | | +------+ +--------------------+ | C: | *---+------->| : | *----+--+ +-----------+------+ | +-------+ | ^ | | | | | (an Inner) | | | +--------------------+ | | | +-------------------------------------------+ The box on the left is the "frame" for a call on method someName (see "A Model for Memory, Names, and Types" from the Course Reader.) You saw these in CS61A. Each Inner has a pointer to this frame (one you can't directly name, so I've given it the bogus name "") that allows access to its final variables, including "this" (which acts like a final variable for all practical purposes). The homework provides some opportunities for using these local inner classes. In addition, Arnold&Gosling's example on page 54 illustrates an important point. Their Enum class is invisible outside the method walkThrough, and yet they return an Enum object pointer! How can this be useful? Well, the class Enum is itself invisible, but an Enum "is an" Enumeration. The walkThrough method has a return type of Enumeration. When I call Enumeration e = walkThrough (someArray); all I know about the result (being outside walkThrough) is that it "is an" Enumeration, which means that I am entitled to call e.hasMoreElements and e.nextElement. When I do, it is the dynamic type of e that is used to determine where to jump. It is irrelevant that I cannot directly reference the type definition for e's dynamic type---it implements these methods, so the calls work. The compiler decides on the legality of e.hasMoreElements() based on the STATIC type of e, which is visible and which has an appropriate definition for that method. At execution, visibility of the names no longer matters. SIDE EXCURSION. The restriction to final variables (those that are constant once set) is for various technical reasons. It is less limiting than you might suppose. If I really need to, I can allow Inner (above) to access u by replacing u with final AnotherTypeWrapper uw = new AnotherTypeWrapper(); where we somewhere define class AnotherTypeWrapper { AnotherType u; } Now uw IS final, so Enum's methods can access it. Where before we would touch u, now we touch uw.u. This is one more example of an ancient Computer Science maxim: All problems can be solved by introducing a sufficient number of levels of indirection. END OF SIDE EXCURSION. Anonymous Classes --------- ------- Where there is only one use of a nested or local class, namely an object creation, it's somewhat of a bother to come up with a name for it---a name that will be referred to only once. That was the inspiration behind anonymous classes (see Arnold&Gosling, section 3.7), which combine the definition of a class with a 'new' operation. I won't say much about this shorthand here---you'll see its use when we talk about graphical user interfaces. ------------------------------------------------------------------- Specifying a Class ---------- - ----- In Java, a class provides both * A specification of the members of the class and how to call or access them, sometimes called a "syntactic specification" * Implementations for some or all methods. A "specification" in this context is a description of what something is suppose to accomplish and how it is used to accomplish that thing. So the header int f(String[] A) ... tells me that f is supposed to produce an integer and that I must give it an array of Strings. But this is a syntactic specification only; what it doesn't directly provide is a "semantic specification," an independent description of exactly what f is supposed to do. Now, I could use the body of f for this purpose, but that would mean that users of the class containing this definition would have to understand how f works, not just what it does (understanding java.io.SerializablePermission is difficult enough without having to read sun's code), and I (the implementor of f) would suddenly incur obligations not to change the innards of f, lest I upset my clients. Therefore, it is generally considered a good idea to provide a separate semantic specification for one's methods, in the form of comments. A typical set might look like this: /** A sequence of Objects, added to and deleted from at * one end. */ public class Stack { /** A Stack that is initially an empty sequence. */ public Stack() { ... } /** True iff this is empty. */ public boolean empty() { ... } /** The first element of the sequence. Requires * that !empty(). */ public Object peek() { ... } /** Return the first item of the sequence and remove * it from the sequence. Requires that !empty(). */ public Object pop() { ... } /** Put ITEM at the beginning of the sequence. */ public Object push(Object item) { ... } } Here, I state that a Stack is a sequence, without saying how this sequence is kept (the java.util.Stack class is more specific on this point). Each method has a comment written as if the Stack were simply a sequence. The comment gives both the result of the executing the method (the "post-condition") and the assumptions or requirements that the calling program must satisfy for the method to work (the pre-conditions). For a simple type like Stack, most programmers understand the significance of `push' and `pop', and an elaborate comment like this is perhaps not necessary. Nevertheless, it can't hurt. Exceptions ---------- The member functions of the type Stack, above, contain pre-conditions, which the caller is supposed to satisfy. Of course, saying ``the Stack must not be empty when pop() is called'' is somewhat like telling children not to put beans in their ears---one must eventually confront the question ``what if the Stack IS empty when I call pop()?'' One answer (a purist answer) is that in that case, your program is wrong, period, and that the Stack class is under no obligation to thereafter cleave to the rules of civilized behavior. Your program may crash; your files may be erased; pornography may be mailed to the Chancellor in your name; unutterable chaos may ensue---for you have violated the precondition. This answer says that the implementor of Stack is entitled to write the body of pop as if the Stack could not possibly be empty when it is called. Most of us recognize, however, that it is very questionable engineering to assume that errors will not happen. This is especially true when the error is something like ``this file is in the wrong format,'' since files (which are, after all, a kind of parameter to our program) are often created by fallible humans and their contents are outside the control of the programs that read them. It follows that we write our programs to assume the worst, where appropriate, and to check for fatal errors in the input (i.e., method parameters and input files). This raises the obvious question, ``if something goes wrong, what happens?'' One solution is simply to halt the program. Sometimes that is appropriate. Another solution is to have methods that might encounter such errors return a value indicating whether an error occurred. This can get clumsy and really clutter up one's program. Also, it is always possible to forget to check the value returned. Java provides a mechanism for signaling "exceptional conditions" (principally errors) and allowing them either to halt a program or be intercepted and handled otherwise. The construct try { Statements0 } catch (E1 x) { Statements1 } catch (E2 x) { Statements2 } ... executes Statements0 (a bunch of statements) and then normally just continues (with ...). However, if, during the execution of Statements0, an "exception" is "thrown", either by an explicit statement throw E; or by certain built-in checks (such as the one that causes an error in X.F if X is a null pointer) then execution of Statements0 halts (even if this happens deep within some method that Statements0 calls). Here, E is an expression that evaluates to some subclass of type java.lang.Throwable. What happens next depends on the dynamic type of E. Specifically, we execute Statements1 if E instanceof E1, and Statements2 if E instanceof E2. In both these cases, we then continue execution with the `...'. The argument of the catch clause (`x' in both of the ones above) is bound to E while the statements execute. If E belongs to neither of these types, it is as if the entire `try' statement were replaced by `throw E' all over again---the exception is "propagated" out of the try statement. Typically, the exception expression `E' will be something like throw new EmptyStackException(); However, an exception is simply an instance of a type, and as long as that type is a subclass of Throwable, it may have an arbitrary definition. The class Throwable itself also allows one to write, e.g., throw new Throwable("argument negative"); and the exception that gets thrown (picked up by the argument of the catch phrase) will contain a helpful message further identifying the problem (and retrievable by the getMessage() method). In some sense, an exception is a kind of output of a method, and just as methods have return types, so it is also useful to be able to say the sorts of exceptions they may throw. This is the purpose of the throws clause: /** Return the first item of the sequence and remove * it from the sequence. Requires that !empty(); * otherwise throws EmptyStackException. */ public Object pop() throws EmptyStackException { ... } In fact, a method is REQUIRED to list many of the exceptions that it might throw. For the benefit of the programmer, however, there are (ahem) exceptions to this rule. Specifically, two of the subclasses of Throwable---Error and RuntimeException---and all their subclasses are "unchecked exceptions". They do not have to be listed in throws clauses, although they may be. Errors are intended to be exceptions from which a program is not expected to recover. RuntimeExceptions include things like array-bounds violations. All other exceptions are "checked exceptions" and the compiler requires that you list all of those that a method might throw in its header. Creating a new type of exception is quite easy, since there isn't much to them. Here's java.lang.Throwable (the top of the exception class hierarchy) and its major subtypes: public class Throwable { public Throwable() { } public Throwable(String message) { } /** Returns the message argument to the constructor */ public String getMessage() {...} /** A printed form for this Throwable object. */ public String toString() {...} /* ... other stuff ... */ } /** Indicates exceptions that are probably fatal. */ public class Error extends Throwable { { public Error() { super(); } public Error(String s) { super(s); } } /** Indicates exceptions that might be caught. */ public class Exception extends Throwable { public Exception() { super(); } public Exception(String s) { super(s); } } /** Indicates exceptions that might get thrown by the * normal operation of the Java interpreter. */ public class RuntimeException extends Exception { public RuntimeException() { super(); } public RuntimeException(String s) { super(s); } } Your exceptions can simply extend one of these in the same way.