CS61B, Lecture #4, 6/22/00. Paul N. Hilfinger with Modifications by B. Chen Administrative -------------- 1. Make sure to login to your class account by midnight tonight. Otherwise we will presume you are not in the course. HW0 is also due by midnight. 2. Project 1 is due on Monday July 3rd. 3. Note the changes to the reading for today. Some pages inbetween 217 and 232 talk about mysterious interfaces, just ignore that and learn to work with packages. You'll need to know this for Project 1. Readings -------- For today: Friesen pp. 75-102, 217-232, 235-249 For Monday: Friesen pp. 105-132 "Object-Based" Programming -------------- ----------- In the past, one organized programs around their actions---the subprogram (function, or procedure) was the basic unit, and families of related subprograms were collected into "modules". So, the outline of a typical program might be 1. Input the data 2. Process the data 3. Print the results and each of those might represent a procedure, which is itself broken down in the same fashion until one gets to individual statements. This is known as "functional decomposition." The problem here is that the effects of certain design decisions---such as how "the data" are to be represented---tend to get spread out over the entire program. If one changes those decisions, one changes a lot of code. Therefore, there has been a movement, beginning in the early 1970s, more and more towards looking at program design "from the data's point of view." That is, the modern trend is to identify the kinds of things that are operated upon and the operations that are performed on them, grouping things by the kinds (types) of data they are chiefly concerned with. Thus, changes in the representation of "the data" (above) are limited in the amount of program text that is affected (that, at least, is the idea. We call this sort of program organization "object based." Java is clearly slanted strongly in this direction. Every definition is wrapped up in some class. Although to date we have used classes almost exclusively as containers for functions, they really come into their own when used to define new types of data. The Class as Data Type --- ----- -- ---- ---- In CS61A, you were introduced to the `define-class' special form for creating new types of data. Here is an example (slightly modified) from ``Object-Oriented Programming---Above the line view'': (define-class (account init-balance) ; A (instance-vars (balance 0)) ; B (initialize (set! balance init-balance)) ; C (method (deposit amount) ; D (set! balance (+ amount balance)) balance) (method (withdraw amount) ; E (if (< balance amount) (error "Insufficient funds") (sequence (set! balance (- balance amount)) balance)))) (The original had `balance' instead of `init-balance' and didn't bother with a separate instance variable for it. It also handled the error condition differently). After this definition, I could write (define myAccount (instantiate account 1000)) ; F The `instantiate' function creates a new account object, containing an "instance variable" called `balance', which is initialized to 1000. I can inquire as to the current balance with (ask myAccount 'balance) ; G can add $100 with (ask myAccount 'deposit 100) ; H and can withdraw $200 with (ask myAccount 'withdraw 200) ; I In Java, we write public class Account { // A // fields private int balance; // B // methods of the class public Account(int initBalance) { balance = initBalance; // C } public int balance() { // B return balance; } public int deposit(int amount) { // D balance += amount; return balance; } public int withdraw(int amount) { // E if (balance < amount) throw new Error("Insufficient funds"); else { balance -= amount; return balance; } } } and in place of the operations on my account, I write: Account myAccount = new Account(1000); // F myAccount.balance(); // G myAccount.deposit(100); // H myAccount.withdraw(200); // I I wrote `balance' without `static' because this time, there is not just one `balance', but rather there is one for each Account object that gets created. When a variable in a particular class is declared as 'static' there is only one instance of that variable in ALL instances of that class. Static variables are used when we have a variable that all the instances of some class need to share. Static methods in a class are methods that don't need an instantiated object. Some examples of static methods are Math.sin(...), and Integer.parseInt(...). In our example, the declaration of the class Account says ``There can be any number of Account objects, each of which contains a field variable (slot) named `balance', which may contain integers. With a few exceptions, this is all there is to say about it, but let's examine this example with more detail. First, as you can see, there is a method---known as a "constructor"---having the same name as the class and having no return type. The `new Account(1000)' expression causes the following things to happen: a. A new Account object is created, having a single instance variable, balance, initialized to the standard initial integer value (0). Let's call the pointer to this object `OBJ' for reference. b. The Account method is called, as if by a call OBJ.Account(1000) [WARNING: I said "as if". A direct call on a constructor like that is not legal for you to actually write.] c. The pointer OBJ is the value of the `new ...' expression (and in line F above, is promptly assigned to myAccount). Second, I designated the instance variable `balance' as private (which means that only inside the class Account can one touch it) to indicate that I don't intend for anyone to bypass the `deposit' and `withdraw' methods by writing, e.g., myAccount.balance -= 50; // WRONG, .balance is private! Had I declared instead public int balance; then the assignment above would be legal, and had I declared int balance; then the assignment would be legal in any class within the same package. (Again, the notation myAccount.balance means ``the variable balance THAT IS FOUND IN the object pointed to by myAccount.'') Java Packages ---- -------- In Java, type definitions reside in "packages". A package is nothing but a collection of classes, interfaces (we'll discuss this later), and other packages ("subpackages"). A declaration package foo.bar; at the beginning of a compilation tells us that the classes in this compilation belong to a subpackage named bar, which is itself inside a package named foo. In a bare Java system, a standard package called "java" is visible ("in scope") to any class. This particular package contains the subpackages lang, util, io, net, applet, and awt. To reference one of them, one uses the names java.lang, java.util, etc. In general, if P is the name of a package, and that package contains a type or subpackage named Q, then P.Q refers to that type or subpackage. This works recursively: java.io contains a class PrintStream, so java.io.PrintStream is the name of a class; package java.awt contains a subpackage image, which contains a class ColorModel, which one may refer to as java.awt.image.ColorModel. By the way, the meaning of `.' here is consistent with other Java practice: X.Y always means ``the thing named Y that is contained (or defined) in X.'' Thus, if L is a List, then L.head means ``the thing named `head' that is contained in the List object pointed to by L.'' Likewise, System.out means ``The static field named `out' that is defined in the class System.'' [Nitty-Gritty Implementation Note: It is up to an individual Java implementation to decide where it expects to find the .class or .java files associated with these classes and other. In our case, packages and subpackages correspond to directories. There is a set of directories, recorded in an environment variable called CLASSPATH, in which the compiler (javac) and interpreter (java) expect to find all of the highest- level packages (the ones at the left of a name, such as "java") as subdirectories.] For casual use, Java also supports "unnamed packages", which is where it puts classes from compilations that don't have a package declaration. In our case, the system looks for types in the unnamed package in the current directory. Consider the following piece of (correct) program text from the middle of a program: public void mergeFiles(java.io.FileInputStream x, java.io.FileInputStream y, java.io.FileOutputStream z) { Just how many times must we be reminded what package these classes are in? To make things easier, therefore, we are allowed to say things like this once at the beginning of a compilation. The declarations import java.io.FileInputStream; import java.io.FileOutputStream; cause the names FileInputStream and FileOutputStream to be usable within the Compilation without java.io in front---the declarations of those classes is said to be "in scope". A type name such as java.io.FileInputStream is called a "fully qualified name." It is illegal to import a type with the same name from two different packages, or to import a type from another package having the same name as a type in the current package (the one for the file containing the import). When the classes of a package are typically used together, or the package is so standard that programmers are likely to recognize the names of classes within it, one may get the effect of import declarations for every class in the package with import PACKAGENAME.*; as in import java.io.*; The types in java.lang are assumed to be in this category, so there is, in effect, an implicit import java.lang.*; at the beginning of every program. Unlike the first kind of import, it doesn't matter if some of the type names in java.io conflict with other type names---for those conflicting names, you must simply use the fully qualified name. Here is a (simplified) example of the Math class: package java.lang; import java.util.Random; public class Math { // fields public static final double PI = 3.14159265358979323846; private static Random randomNumberGenerator; ... // methods public static double sin(double a) { ... } public static double pow(double a, double b) { ... } ... } I could also have replaced the import with import java.util.*; or I could have left the import off entirely and put 'java.util' in the appropriate places. Import clauses affect only the compilation they appear in. Just because I import a class whose .java file has an 'import foo.bar;' clause in it, I can't just leave the foo.bar's off my names. IMPORTANT: the 'import' declaration does NOTHING other than what I have stated above. For example, if when you run your Java program (that is, AFTER having compiled it), you get an error message saying that class foo.Baz was missing, you are not going to fix the problem by putting an "import foo.Baz;" or "import foo.*" in your program (the error is generally caused by your having forgotten to re-compile foo.Baz after having erased its class file or by your having set the wrong CLASSPATH when you run 'java'). Import clauses are nothing more than a shorthand.