Notes on Java

Bruce Eckel, Thinking in Java 3rd edition

Table of Contents

4 Chapter 4 - Initialization & Cleanup 4.1 Constructors 4.2 this keyword 4.3 The meaning of static 4.4 Cleanup: finalization and garbage collection 4.5 How a garbage collector works 4.6 Order of initialization 4.7 Array initialization 4.8 Summary 5 Chapter 5 - Hiding the Implementation 5.1 package: the library unit 5.2 Creating unique package names 5.3 Collisions 5.4 A custom tool library 5.5 Java access specifiers 5.6 Interface and Implementation 5.7 Class access 5.8 Summary 6 Chapter 6 - Reusing Classes 6.1 Composition syntax 6.2 Inheritance syntax

Chapter 4 - Initialization & Cleanup

Constructors

  • Many C bugs occur when the programmer forgets to initialize a variable.
  • Java has:
    • Constructor: a special method automatically called when an object is created
    • Garbage Collector: automatically release memory
  • if a class has a constructor -> Java automatically calls that constructor when an object is created -> initialization is guaranteed
  • the name of the constructor is the same as the name of the class
  • Style: 1st letter of all methods lowercase. 1st letter of all classes Uppercase

Workflow:

  1. A new object is created:
new Rock();
  1. Storage is allocated and the constructor is called.
  2. Like any method, the constructor can have args to allow you to specify how an obj is created.
    class Rock{
      Rock(int i) {
     System.out.println("Creating Rock number " + i);
      }
    }
    
    • Constructors eliminate a large class of problems and make the code easier to read.
    • The constructor is an unusual type of method because it has no return value -> != from a void return value.
    • Constructors returns nothing.

Method Overloading

  • When you create an object, you give a name to a region of storage.
  • A method is a name for an action.
  • Well-chosen names make it easier to understand your code.
  • Human language can express a number of different meanings = it’s overloaded.
  • Because the constructor’s name is predetermined by the name of the class, there can be only one constructor name.
  • Method overloading is essential to allow the same method name to be used with different argument types.
  • Each overloaded method must take a unique list of argument types.
  • Even differences in the ordering of arguments are sufficient to distinguish two methods: (Although you don’t normally want to take this approach, as it produces difficult-to-maintain code).
    class Tree {
    Tree (String s, int i) {
        System.out.println("String: " + s + ", int: " + i);
    }
    Tree (int i, String s) {
        System.out.println("int: " + i + ", String: " + s);
    }
    }
    

    Watch-out

  1. A primitive can be automatically promoted from a smaller type to a larger one and this can be slightly confusing in combination with overloading. You’ll see that the constant value 5 is treated as an int, so if an overloaded method is available that takes an int it is used. In all other cases, if you have a data type that is smaller than the argument in the method, that data type is promoted. char produces a slightly different effect, since if it doesn’t find an exact char match, it is promoted to int. What happens if your argument is bigger than the argument expected by the overloaded method? You must cast to the necessary type using the type name in parentheses. If you don’t do this, the compiler will issue an error message. This is a narrowing conversion, which means you might lose information during the cast.

  2. You cannot use return value types to distinguish overloaded methods.

    void f() {}
    int f() {}
    

    This works fine when the compiler can unequivocally determine the meaning from the context however you can also call a method and ignore the return value -> calling a method for its side effect:

    f();
    

    Java can’t determine which f() should be called.

Default constructors

  • Default constructor is one without args, used to create a basic object.
  • If you create a class that has no constructors, the compiler will automatically create a default constructor for you.
  • If you define any constructors (with or without arguments), the compiler will not synthesize one for you
    class Hat {
    Hat (int i) {}
    Hat (double d) {}
    }
    new Hat(); //The compiler will complain that it cannot find a constructor that matches.
    

this keyword

  • You have 2 objects of the same type called a and b. How can you call a method f() for both?
    class Banana {
    void f(int i) {
      /* ... */
    }
    }
    Banana a = new Banana();
    Banana b = new Banana();
    a.f(1);
    a.f(2);
    
  • There’s a secret first argument passed to the method f() and that argument is the reference to the object that’s being manipulated.
    Banana.f(a,1);
    Banana.f(b,2);
    

    This is internal and you can’t write these expressions and get the compiler to accept them. Suppose you’re inside a method and you’d like to get the reference to the current object:

  • Since that reference is passed secretly by the compiler, there’s no identifier for it.
  • The this keyword produces the reference to the object the method has been called for.
  • The this keyword can be used only inside a method.
  • if you’re calling a method of your class from within another method of your class, you don’t need to use this. You simply call the method. The current this reference is automatically used for the other method:
    class Apricot {
    void pick() {
    /* ... */
    }
    void pit() {
    pick();
    /* ... */
    }
    }
    

    inside pit() you could say this.pick() but the compiler does it for you automatically. The this keyword it’s often used in return statements when you want to return the reference to the current object.

    public class Leaf {
    int i = 0;
    Leaf increment() {
        i++;
        return this;
    }
    }
    

    Because increment() returns the reference to the current object via the this keyword, multiple operations can easily be performed on the same object.

Calling constructors from constructors

When you write several constructors for a class, there are times when you’d like to call one constructor from another to avoid duplicating code. You can make such a call using the this keyword.

In a constructor, the this keyword takes on a different meaning when you give it an argument list: it makes an explicit call to the constructor that matches that argument list.

public class Flower {
    int petalCount = 0;
    String s = new String("null");
    Flower (int petals) {
        petalCount = petals;
    }
    Flower (String ss) {
        s = ss;
    }
    Flower (strinig s, int petals) {
    this(petals);
    //! this(s); // Can't call two!
    this.s = s; // Another use of "this"
    }
    Flower() {
    this("hi", 47);
    }
}
  • The constructor Flower(String s, int petals) shows that, while you can call one constructor using this, you cannot call two.
  • The constructor call must be the first thing you do or you’ll get a compiler error message.
  • Since the name of the argument s and the name of the member data s are the same, there’s an ambiguity. You can resolve it by saying this.s to refer to the member data.

The meaning of static

  • It means that there is no this for that particular method.
  • You cannot call non-static methods from inside static methods (although the reverse is possible). The one case in which this is possible occurs if you pass a reference to an object into the static method. Then, via the reference (which is now effectively this), you can call non-static methods and access non-static fields. But typically if you want to do something like this you’ll just make an ordinary, non-static method.
  • You can call a static method for the class itself without any object.
  • With a static method you don’t send a message to an object, since there’s no this.

Cleanup: finalization and garbage collection

  • Java has the garbage collector to reclaim the memory of objects that are no longer used.
  • Simply letting go of an object once you are done with it is not always safe.
  • Java has the garbage collector to reclaim the memory of objects that are no longer used. Consider an unusual case:
  • your object allocates “special” memory without using new
  • the garbage collector knows only how to release memory allocated with new -> it won’t know how to release the object’s special memory To handle this case Java provides a method called finalize() that you can define for your class. When the garbage collector is ready to release the storage it will first call finalize() and only on the next garbage-ollection pass will it reclaim the object’s memory. Put another way:
    1. Your objects might not get garbage-collected.
    2. Garbage collection is not destruction. If there is some activity that must be performed before you no longer need an object, you must perform that yourself.
  • Java has no destructor or similar concept -> you must create an ordinary method to perform this clanuo.
    1. Garbage collection is only about memory.
  • The sole reason of garbage collection is to recover memory that your program is no longer using.
  • finalize( ) is in place because of the possibility that you’ll do something C-like by allocating memory using a mechanism other than the normal one in Java. This can happen primarily through native methods, which are a way to call non-Java code from Java.
  • To clean up an object, the user of that object must call a cleanup method at the point the cleanup is desired.
  • In C++, all objects are destroyed. Or rather, all objects should be destroyed.
  • Java doesn’t allow you to create local objects — You must always use new. But in Java, there’s no “delete” to call to release the object since the garbage collector releases the storage for you. In general, you can’t rely on finalize( ) being called, and you must create separate “cleanup” methods and call them explicitly. So it appears that finalize( ) is only useful for obscure memory cleanup that most programmers will never use.

There is a very interesting use of finalize( ) which does not rely on it being called every time. This is the verification of the termination condition of an object. At the point that you’re no longer interested in an object—when it’s ready to be cleaned up—that object should be in a state whereby its memory can be safely released. For example, if the object represents an open file, that file should be closed by the programmer before the object is garbagecollected.

How a garbage collector works

  • The garbage collector can have a significant impact on increasing the speed of object creation.
  • This might sound a bit odd at first: storage release affects storage allocation: allocating storage for heap objects in Java can be nearly as fast as creating storage on the stack in other languages.
  • In some JVMs, the Java heap is quite different; it’s more like a conveyor belt that moves forward every time you allocate a new object. This means that object storage allocation is remarkably rapid.
  • You might observe that the heap isn’t in fact a conveyor belt: The trick is that the garbage collector steps in and while it collects the garbage it compacts all the objects in the heap so that you’ve effectively moved the “heap pointer” closer to the beginning of the conveyor belt and further away from a page fault. Garbage collector (GC) schemes:
    1. Reference counting
  • Each object contains a reference counter, and every time a reference is attached to an object the reference count is increased.
  • Every time a reference goes out of scope or is set to null, the reference count is decreased.
  • The garbage collector moves through the entire list of objects and when it finds one with a reference count of zero it releases that storage.
  • The one drawback is that if objects circularly refer to each other they can have nonzero reference counts while still being garbage. Reference counting is commonly used to explain one kind of garbage collection but it doesn’t seem to be used in any JVM implementations.
    1. Adaptive garbage collection scheme -> faster
  • Any nondead object must ultimately be traceable back to a reference that lives either on the stack or in static storage.
  • If you start in the stack and the static storage area and walk through all the references you’ll find all the live objects.
  • There is no problem with detached self-referential groups—these are simply not found, and are therefore automatically garbage.

2.1 One of these variants is stop-and-copy.

  • The program is first stopped
  • Then, each live object that is found is copied from one heap to another, leaving behind all the garbage.
  • As the objects are copied into the new heap they are packed end-to-end, thus compacting the new heap There are two issues that make these so-called “copy collectors” inefficient:
  • You have two heaps and you slosh all the memory back and forth between these two separate heaps, maintaining twice as much memory as you actually need.
  • The second issue is the copying. Once your program becomes stable it might be generating little or no garbage. Despite that, a copy collector will still copy all the memory from one place to another, which is wasteful.

2.2 Mark and Sweep

  • It’s what earlier versions of Sun’s JVM used all the time.
  • For general use, mark and sweep is fairly slow, but when you know you’re generating little or no garbage it’s fast
  • Each time it finds a live object that object is marked by setting a flag in it, but the object isn’t collected yet.
  • Only when the marking process is finished does the sweep occur.
  • During the sweep, the dead objects are released.
  • No copying happens, so if the collector chooses to compact a fragmented heap it does so by shuffling objects around
  • Mark-and-sweep requires that the program be stopped

Adaptive generational stop-and-copy mark-and-sweep: the JVM monitors the efficiency of GC and if it becomes a waste of time because all objects are long-lived then it switches to mark-and-sweep. Similarly, the JVM keeps track of how successful mark-and-sweep is, and if the heap starts to become fragmented it switches back to stop-and-copy.

There are a number of additional speedups possible in a JVM.

  1. just-in-time (JIT) compiler: partially or fully converts a program into native machine code, so it doesn’t need to be interpreted by the JVM and thus runs much faster. One approach is to simply JIT all the code:
    • it takes a little more time
    • it increases the size of the executable (byte codes are significantly more compact than expanded JIT code) and this might cause paging, which definitely slows down a program.
  2. lazy evaluation: the code is not JIT compiled until necessary. Thus, code that never gets executed might never get JIT compiled. The Java HotSpot technologies in recent JDKs take a similar approach by increasingly optimizing a piece of code each time it is executed, so the more the code is executed, the faster it gets.

Member initialization

Java goes out of its way to guarantee that variables are properly initialized before they are used.

  1. variables that are defined locally to a method
    void f() {
    int i;
    i++; // Error -- i not initialized
    }
    

    Of course, the compiler could have given i a default value, but it’s more likely that this is a programmer error and a default value would have covered that up. Forcing the programmer to provide an initialization value is more likely to catch a bug.

  2. If a primitive is a field in a class Since any method can initialize or use that data, it might not be practical to force the user to initialize it to its appropriate value before the data is used. However, it’s unsafe to leave it with a garbage value, so each primitive field of a class is guaranteed to get an initial value.

Specifying initialization

Assign the value at the point you define the variable in the class.

class InitialValues {
boolean b = true;
char c = 'x';
byte B = 47;
short s = 0xff;
int i = 999;
long l = 1;
float f = 3.14f;
double d = 3.14159;
//. . .

You can also initialize nonprimitive objects in this same way.

class Measurement {
Depth d = new Depth();
// . . .

You can even call a method to provide an initialization value:

class CInit {
int i = f();
//...
}

This method can have arguments, of course, but those arguments cannot be other class members that haven’t been initialized yet.

Constructor initialization

  • The constructor can be used to perform initialization
  • You aren’t precluding the automatic initialization, which happens before the constructor is entered
    class Counter {
    int i;
    Counter() {i - 7}
    // ...
    

    then i will first be initialized to 0, then to 7.

Order of initialization

  • Within a class, the order of initialization is determined by the order that the variables are defined within the class
  • The variables are initialized before any methods can be called, even the constructor.
  • The order of initialization is statics first, if they haven’t already been initialized by a previous object creation, and then the non-static objects. Summarize the process of creating an object:
    1. The first time an object of type Dog is created (the constructor is actually a static method), or the first time a static method or static field of class Dog is accessed, the Java interpreter must locate Dog.class, which it does by searching through the classpath.
    2. As Dog.class is loaded, all of its static initializers are run. Thus, static initialization takes place only once, as the Class object is loaded for the first time.
    3. When you create a new Dog( ), the construction process for a Dog object first allocates enough storage for a Dog object on the heap.
    4. This storage is wiped to zero, automatically setting all the primitives in that Dog object to their default values (zero for numbers and the equivalent for boolean and char) and the references to null.
    5. Any initializations that occur at the point of field definition are executed.
    6. Constructors are executed.

Explicit static initialization

Java allows you to group other static initializations inside a special “static clause” (sometimes called a static block) in a class.

class Spoon {
static int i;
static {
i = 47;
}
// . . .

It appears to be a method, but it’s just the static keyword followed by a block of code. This code, like other static initializations, is executed only once, the first time you make an object of that class or the first time you access a static member of that class (even if you never make an object of that class).

Non-static instance initialization

public class Mugs {
static Test monitor = new Test();
Mug c1;
Mug c2;
{
c1 = new Mug(1);
c2 = new Mug(2);
System.out.println("c1 & c2 initialized");
}

Looks exactly like the static initialization clause except for the missing static keyword.

Array initialization

  • An array is simply a sequence of either objects or primitives, all the same type and packaged together under one identifier name.
    int[] a1;
    
  • The compiler doesn’t allow you to tell it how big the array is.
  • All that you have at this point is a reference to an array, and there’s been no space allocated for the array
  • To create storage for the array you must write an initialization expression:
    int[] a1 = { 1, 2, 3, 4, 5 };
    
  • length = array’s intrinsic member that you can query—but not change—to tell you how many elements there are in the array.
  • What if you don’t know how many elements you’re going to need in your array while you’re writing the program? You simply use new to create the elements in the array.
    int[] a = new int[rand.nextInt(20)];
    
  • Array elements of primitive types are automatically initialized to “empty” values. (For numerics and char, this is zero, and for boolean, it’s false.)
  • If you’re dealing with an array of nonprimitive objects, you must always use new.

Multidimensional arrays

int[][] a1 = {
{ 1, 2, 3, },
{ 4, 5, 6, },
};

Summary

  • Improper initialization of variables causes a significant portion of programming problems.
  • similar issues apply to improper cleanup
  • Because constructors allow you to guarantee proper initialization and cleanup, you get complete control and safety.
  • In Java, the garbage collector automatically releases the memory for all objects
  • The garbage collector does add a run-time cost, the expense of which is difficult to put into perspective because of the historical slowness of Java interpreters

Chapter 5 - Hiding the Implementation

  • Separate the things that change from the things that stay the same.
  • Prticularly important for libraries:
    • the user won’t need to rewrite code if a new version comes out.
    • the library creator must have the freedom to make modifications and improvements. To solve this problem Java provides access specifiers:
    • public
    • protected
    • private As a library designer you want to keep everything as private as possible.
  • package keyword: controls how components are bundled together into a cohesive unit (a library)

package: the library unit

import java.util.*

This brings in the entire utility library.

import java.util.ArrayList

This brings in a single class.

  • Importing provides a mechanism to manage name spaces: the names of all your class members are insulated from each other. But what about the class names? Suppose you create a Stack class that is installed on a machine which already has a Stack class that’s written by someone else? -> see Dynamo issues.
  • If you’re planning to create libraries or programs that are friendly to other Java programs on the same machine, you must think about preventing class name clashes.
  • compilation unit: source-code file for Java:
    • must have a name ending in .java
    • inside there can be only one public class that must have the same name as the file (excluding .java)
    • additional classes are hidden (not public) and they comprise support classes for the main public class
  • When you compile a .java file you get an output for each class in the .java file.
    • each output file has the name of a class in the .java file but with an extension of .class
  • A working program is a bunch of .class files which can be packaged and compressed into a JAR file. The Java interpreter is responsible for finding, loading and interpreting these files.
  • A library is a group of these class files.
    package mypackage
    
  • this compilation unit is part of a library named mypackage.
  • naming convention: all lowercase letters, even for intermediate words.
    package mypackage;
    public class MyClass {
    // . . .
    
  • the name of the file is MyClass.java (the only public class in that file)
  • to use MyClass the user must use the import keyword or to give the fully qualified name
    mypackage.MyClass m = new mypackage.MyClass();
    

    The import keyword can make this much cleaner:

    import mypackage.*
    // ...
    MyClass m = new MyClass();
    

Creating unique package names

  • Since a package never really gets packaged into a single file, a package could be made up of many .class files
  • Place all the .class files for a particular package into a single directory:
    • package names are unique
    • finds those classes that might be buried in a directory structure someplace: accomplished by encoding the path of the location of the .class file into the name of the package.
    • The first part of the package name is the Internet domain name of the creator of the .class, reversed
    • Since Internet domain names are guaranteed to be unique, if you follow this convention your package name will be unique
    • The second part of this trick is resolving the package name into a directory on your machine, so when the Java program runs and it needs to load the .class file it can locate the directory where the .class file resides. The Java intepreter proceeds as follows: 1. finds the environment variable CLASSPATH 2. Starting at that root, the interpreter will take the package name and replace each dot with a slash to generate a path name from the CLASSPATH root (so package foo.bar.baz becomes foo\bar\baz) 3. This is then concatenated to the various entries in the CLASSPATH. That’s where it looks for the .class file with the name corresponding to the class you’re trying to create.
      package com.bruceeckel.simple;
      public class Vector {
      public Vector() {
      /.../
      }
      }
      
  • com.bruceeckel is the name of the package and establishes my unique global name for my classes.
  • a library named simple will be created.
  • a class (therefore a .class file) Vector will be created.
    package com.bruceeckel.simple;
    public class List {
    public List() {
    /.../
    }
    }
    
  • a class (therefore a .class file) List will be created. Both of these files are placed in the subdirectory: C:\DOC\JavaT\com\bruceeckel\simple where CLASSPATH=.;D:\JAVA\LIB;C:\DOC\JavaT
  • CLASSPATH can contain a number of alternative search paths.
  • when using JAR files the name (not just the path) must be in the classpath: CLASSPATH=.;D:\JAVA\LIB;C:\flavors\grape.jar

Collisions

import com.bruceeckel.simple.*;
import java.util.*;

What happens if two libraries are imported via * and they include the same names?

  • Sice java.util.* also contains a Vector class, this causes a potential collision.
  • As long as you don’t write the code that actually causes the collision, everything is OK.
  • The collision does occur if you now try to make a Vector.
    Vector v = new Vector();
    

    Which Vector class does this refer to? The compiler complains and forces you to be explicit:

    java.util.Vector v = new java.util.Vector();
    

    Since this (along with the CLASSPATH) completely specifies the location of that Vector, there’s no need for the import java.util.* statement unless I’m using something else from java.util.

A custom tool library

Consider, for example, creating an alias for System.out.println( ) to reduce typing.

package com.bruceeckel.tools;
  public class P {
    public static void rint(String s) {
    System.out.print(s);
    }
    public static void rintln(String s) {
    System.out.println(s);
    }
  } ///:~
  • You can use this shorthand to print a String either with a newline (P.rintln( )) or without a newline (P.rint( )).
  • The location of this file must be in a directory that starts at one of the CLASSPATH locations, then continues com/bruceeckel/tools

Using imports to change behavior

  • A feature that is missing from Java is C’s conditional compilation, which allows you to change a switch and get different behavior without changing any other code.
  • The reason such a feature was left out of Java is probably because it is most often used in C to solve cross-platform issues:
  • A very common use is for debugging code:
    • The debugging features are enabled during development, and disabled in the shipping product.
    • You can accomplish this by changing the package that’s imported to change the code that’s used in your program from the debug version to the production version.

Caveat

The package must live in the directory indicated by its name, which must be a directory that is searchable starting from the CLASSPATH.

Java access specifiers

  • Placed in front of each definition for each member in the class (field or method).
  • Package access
    • If you give no access specifier at all, all the other classes in the current package have access to that member, but to all the classes outside of this package the member appears to be private.
    • Allows to group related classes together in a package so that they can easily interact with each other.
  • The only way to grant access to a member is to:
    • Make the member public.
    • An inherited class can access a protected member as well as a public membet (but not private members).
    • Provide accessor/mutator methods (get/set methods) that read and change the value.

      public: interface access

      package c05.dessert;
      public class Cookie {
      public Cookie() {
      System.out.println("Cookie constructor");
      }
      void bite() { System.out.println("bite"); }
      } ///:
      
  • Cookie.java must reside in a subdirectory called dessert in a directory under c05 that must be under one of the CLASSPATH directories
  • If you create a program that uses Cookie, you can create a Cookie object.
    import c05.dessert.*;
    public class Dinner {
      public Dinner() {
      System.out.println("Dinner constructor");
      }
     public static void main(String[] args) {
     Cookie x = new Cookie();
     //! x.bite(); // Can't access
     });
      }
    } ///:~
    
  • The bite() member is inaccessible inside Dinner.java The default package
    class Cake {
      static Test monitor = new Test();
      public static void main(String[] args) {
     Pie x = new Pie();
     x.f();
     monitor.expect(new String[] {
     "Pie.f()"
     });
      }
    } ///:~
    

    In a second file, in the same directory:

    class Pie {
      void f() { System.out.println("Pie.f()"); }
    } ///:~
    

    Cake is able to create a Pie object and call its f( ) method.

  • The reason that they are available in Cake.java is because they are in the same directory and have no explicit package name.
  • Java treats files like this as implicitly part of the “default package” for that directory.

private: no access

  • No one can access that member except the class that contains that member, inside methods of that class.
  • Other classes in the same package cannot access private members.
  • private allows you to freely change that member without concern that it will affect another class in the same package.
  • The default package access often provides an adequate amount of hiding:
    • package-access member is inaccessible to the client programmer using the class
    • you might not initially think you’ll use the private keyword often since it’s tolerable to get away without it. The consistent use of private is very important, especially where multithreading is concerned.
      class Sundae {
      private Sundae() {}
      static Sundae makeASundae() {
      return new Sundae();
      }
      }
      public class IceCream {
      public static void main(String[] args) {
       //! Sundae x = new Sundae();
       Sundae x = Sundae.makeASundae();
      }
      } ///:~
      
  • you cannot create a Sundae object via its constructor
  • you must call the makeASundae( ) method to do it for you
  • Unless you must expose the underlying implementation (which is a much rarer situation than you might think), you should make all fields private.
  • However, just because a reference to an object is private inside a class doesn’t mean that some other object can’t have a public reference to the same object.

protected: inheritance access

  • deals with a concept called inheritance, which takes an existing class—which we refer to as the base class—and adds new members to that class without touching the existing class.
  • You can also change the behavior of existing members of the class.
  • To inherit from an existing class (base class), you say that your new class extends an existing class
    class Foo extends Bar {
    

    If you create a new package and inherit from a class in another package, the only members you have access to are the public members of the original package.

  • Sometimes the creator of the base class would like to take a particular member and grant access to derived classes but not the world in general.
  • protected also gives package access

Interface and implementation

implementation hiding : access control encapsulation : wrapping data and methods within classes with implementation hiding The result is a data type with characteristics and behaviors.

  • Access control puts boundaries within a data type for two important reasons;
    • establish what the client programmers can and can’t use.
    • separate the interface from the implementation.
  • In the world of object-oriented programming, where a class is actually describing “a class of objects,” as you would describe a class of fishes or a class of birds.
  • Any object belonging to this class will share these characteristics and behaviors.
  • In the original OOP language, Simula-67, the keyword class was used to describe a new data type. This is the focal point of the whole language: the creation of new data types that are more than just boxes containing data and methods.
  • For clarity, you might prefer a style of creating classes that puts the public members at the beginning, followed by the protected, package access, and private members.
  • This will make it only partially easier to read because the interface and implementation are still mixed together.
  • Class browser : look at all the available classess and show you what you can do with them (i.e. what members are available)

Class access

  1. There can be only one public class per compilation unit (file). The idea is that each compilation unit has a single public interface represented by that public class. It can have as many supporting package-access classes as you want. If you have more than one public class inside a compilation unit, the compiler will give you an error message.
  2. The name of the public class must exactly match the name of the file containing the compilation unit, including capitalization. So for Widget, the name of the file must be Widget.java. Again, you’ll get a compile-time error if they don’t agree.
  3. It is possible, though not typical, to have a compilation unit with no public class at all. In this case, you can name the file whatever you like.
    • A class cannot be private (that would make it accessible to no one but the class), or protected.
    • You have only two choices for class access: package access or public.
    • If you don’t want anyone else to have access to that class, you can make all the constructors private, thereby preventing anyone but you, inside a static member of the class, from creating an object of that class.
      class Soup {
      private Soup() {
      public static Soup makeSoup() {   // (1) Allow creation via static method:
       return new Soup();
      }
      private static Soup ps1 = new Soup();  // (2) Create a static object and return a reference upon request (Singleton pattern)
      public static Soup access() {
       return ps1;
      }
      public void f() {}
      }
      class Sandwich { 
      void f() { new Lunch();
      }
      }
      public class Lunch {
      void test() {
      Soup priv2 = Soup.makeSoup();
      Sandwich f1 = new Sandwich();
      Soup.access().f();
      }
      }
      

      The word before the method name (access) tells what the method returns:

      public static Soup access() {
      return ps1;
      }
      

      In this case it returns a reference to an object (a new data type).

    • The class Soup shows how to prevent direct creation of a class by making all the constructors private.
    • By writing the default constructor, it won’t be created automatically.
    • By making it private, no one can create an object of that class. How does anyone use this class?
  4. a static method is created that creates a new Soup and returns a reference to it (if you want to keep count of how many Soup objects to create).
  5. use a design pattern. In this case it is called “singleton” because it allows only a single object to ever be created.
    • The object of class Soup is created as a static private member of Soup, so there’s one and only one, and you can’t get at it except through the public method access().
    • If a static member of that class is public, the client programmer can still access that static member even though they cannot create an object of that class.

Summary

  • A C programming project begins to break down somewhere between 50K and 100K lines of code because C has a single “name space,” so names begin to collide, causing an extra management overhead.
  • In Java, the package keyword, the package naming scheme, and the import keyword give you complete control over names, so the issue of name collision is easily avoided.
  • There are two reasons for controlling access to members:
    1. keep users’ hands off tools that they shouldn’t touch; tools that are necessary for the internal machinations of the data type
    2. to allow the library designer to change the internal workings of the class without worrying about how it will affect the client programmer.
  • The public interface to a class is what the user does see, so that is the most important part of the class to get “right” during analysis and design.

Chapter 6 - Reusing Classes

To be revolutionary, you’ve got to be able to do a lot more than copy code and change it. Instead of creating them from scratch, you use existing classes that someone has already built and debugged. Two ways to accomplish this:

  1. Composition : Create objects of your existing class inside the new class.
  2. Inheritance : Create a new class as a type of an existing class. Take the form of the existing class andd add code to it without modifying the existing class. They are both ways of making new types from existing ones.

Composition syntax

Suppose you’d like an object that holds several String objects, a couple of primitives, and an object of another class. For the nonprimitive objects, you put references inside your new class, but you define the primitives directly: class WaterSource

class WaterSource {
  private String s;
  WaterSource() {
  System.out.println("WaterSource()");
  s = new String("Constructed");
  }
  public String toString() { return s; }
}

class SprinklerSystem

public class SprinklerSystem {
	private String valve1, valve2, valve3, valve4;
	private WaterSource source;
	private int i;
	private float f;
	public String toString() {
	return
	"valve1 = " + valve1 + "\n" +
	"valve2 = " + valve2 + "\n" +
	"valve3 = " + valve3 + "\n" +
	"valve4 = " + valve4 + "\n" +
	"i = " + i + "\n" +
	"f = " + f + "\n" +
	"source = " + source;
	}
}

main program

public class MainProgram {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		SprinklerSystem sprinklers = new SprinklerSystem();
		System.out.println(sprinklers);
	}
}
  • One of the methods defined in both classes is special: toString( ).
  • Every nonprimitive object has a toString( ) method, and it’s called in special situations when the compiler wants a String but it’s got an object.
  • You can add a toString() method in a class to manipulate the output:
    public static void main(String[] args) {
      // TODO Auto-generated method stub
      SprinklerSystem sprinklers = new SprinklerSystem();
      System.out.println(sprinklers);
    }
    

    Returns

    water.SprinklerSystem@15db9742
    

    as output. If you add a toString() method to the SprinklerSystem class:

    public class SprinklerSystem {
     private String valve1, valve2, valve3, valve4;
     private WaterSource source;
     private int i;
     private float f;
     public String toString() {
      return
      "valve1 = " + valve1 + "\n" +
      "valve2 = " + valve2 + "\n" +
      "valve3 = " + valve3 + "\n" +
      "valve4 = " + valve4 + "\n" +
      "i = " + i + "\n" +
      "f = " + f + "\n" +
      "source = " + source;
      }
    }
    

    the output becomes:

    valve1 = null
    valve2 = null
    valve3 = null
    valve4 = null
    i = 0
    f = 0.0
    source = null
    
  • Primitives that are fields in a class are automatically initialized to zero.
  • But the object references are initialized to null, and if you try to call methods for any of them you’ll get an exception.
  • It makes sense that the compiler doesn’t just create a default object for every reference because that would incur unnecessary overhead in many cases.
  • If you want the references initialized, you can do it:
    • At the point the objects are defined. This means that they’ll always be initialized before the constructor is called
    • In the constructor for that class.
    • lazy initialization : right before you actually need to use the object. It can reduce overhead in situations where object creation is expensive and the object does not need to be created every time.
      public class Bath {
        private String
        s1 = new String("Happy"),
        s2 = "Happy",
        s3,s4;
        private Soap castille;
        private int i;
        private float toy;
        public Bath() {
        System.out.println("Inside Bath()");
        s3 = new String ("Joy");
        i = 47;
        toy = 3.14f;
        castille = new Soap();
        }
        public String toString() {
        if(s4 == null) // Delayed initialisation:
        s4 = new String("Joy");
        return
        "s1 = " + s1 + "\n" +
        "s2 = " + s2 + "\n" +
        "s3 = " + s3 + "\n" +
        "s4 = " + s4 + "\n" +
        "i = " + i + "\n" +
        "toy = " + toy + "\n" +
        "castille = " + castille;
        }
      }
      
  • Note that in the Bath constructor a statement is executed before any of the initializations take place.
  • When you don’t initialize at the point of definition, there’s still no guarantee that you’ll perform any initialization before you send a message to an object reference—except for the inevitable run-time exception.

Inheritance syntax

You’re always doing inheritance when you create a class, because unless you explicitly inherit from some other class, you implicitly inherit from Java’s standard root class Object.

  • Keyword extends before the opening brace of the class body followed by the name of the base class.
  • Automatically get all the fields and methods in the base class.
class Cleanser {
    private String s = new String("Cleanser");
    public void append(String a) { s += a; }
    public void dilute() { append(" dilute()"); }
    public void apply() { append(" apply()"); }
    public void scrub() { append(" scrub()"); }
    public String toString() { return s; }
    // Test the new class:
    public static void main(String[] args) {
    Cleanser x = new Cleanser();
    x.dilute(); x.apply(); x.scrub();
    System.out.println(x);
    monitor.expect(new String[] {
    "Cleanser dilute() apply() scrub()"
    });
    }
}
public class Detergent extends Cleanser {
    // Change a method:
    public void scrub() {
    append(" Detergent.scrub()");
    super.scrub(); // Call base-class version
    }
    // Add methods to the interface:
    public void foam() { append(" foam()"); }
    }
    // Test the new class:
    public static void main(String[] args) {
    Detergent x = new Detergent();
    x.dilute();
    x.apply();
    x.scrub();
    x.foam();
    System.out.println(x);
    System.out.println("Testing base class:");
    monitor.expect(new String[] {
    "Cleanser dilute() apply() " +
    "Detergent.scrub() scrub() foam()",
    "Testing base class:",
    });
    Cleanser.main(args);
}
  • both Cleanser and Detergent contain a main( ) method. You can create a main( ) for each one of your classes, and it’s often recommended to code this way so that your test code is wrapped in with the class.
  • even if you have a lot of classes in a program, only the main( ) for the class invoked on the command line will be called.
  • as long as main() is public it doesn’t matter whether the class that it’s part of is public.
  • It’s important that all of the methods in Cleanser are public.
  • if you leave off any access specifier the member defaults to package access, which allows access only to package members:
    • Detergent would have no trouble
    • However, if a class from some other package were to inherit from Cleanser it could access only public members.

So to plan for inheritance, as a general rule make all fields private and all methods public.

  • it’s possible to take a method that’s been defined in the base class and modify it: But inside scrub( ) you cannot simply call scrub( ), since that would produce a recursive call, which isn’t what you want. To solve this problem Java has the keyword super that refers to the “superclass” that the current class has been inherited from. Thus the expression super.scrub( ) calls the baseclass version of the method scrub( ).
  • You can also add new methods to the derived class exactly the way you put any method in a class.

Initializing the base class

Inheritance doesn’t just copy the interface of the base class: When you create an object of the derived class, it contains within it a subobject of the base class. This subobject is the same as if you had created an object of the base class by itself. It’s just that, from the outside, the subobject of the base class is wrapped within the derived-class object.

  • it’s essential that the base-class subobject be initialized correctly, and there’s only one way to guarantee this: perform the initialization in the constructor.
  • Java automatically inserts calls to the base-class constructor in the derived-class constructor
    class Art {
      Art() {
      System.out.println("Art constructor");
      }
    }
    class Drawing extends Art {
      Drawing() {
      System.out.println("Drawing constructor");
      }
    public class Cartoon extends Drawing {
      public Cartoon() {
      System.out.println("Cartoon constructor");
      }
      public static void main(String[] args) {
          Cartoon x = new Cartoon();
      }
    }    
    

    Which produces as output:

    "Art constructor",
    "Drawing constructor",
    "Cartoon constructor"
    
  • The construction happens from the base “outward,” so the base class is initialized before the derived-class constructors can access it.
  • Even if you don’t create a constructor for Cartoon( ), the compiler will synthesize a default constructor for you that calls the base class constructor.

Constructors with arguments

If your class doesn’t have default arguments, or if you want to call a base-class constructor that has an argument, you must explicitly write the calls to the base-class constructor using the super keyword and the appropriate argument list:

class Game {
    Game(int i) {
    System.out.println("Game constructor");
    }
}
class BoardGame extends Game {
    BoardGame(int i) {
    super(i);
    System.out.println("BoardGame constructor");
    }
}
public class Chess extends BoardGame {
    private static Test monitor = new Test();
    Chess() {
        super(11);
        System.out.println("Chess constructor");
        }
        public static void main(String[] args) {
        Chess x = new Chess();
        }
}

Output:

"Game constructor",
"BoardGame constructor",
"Chess constructor"
  • If you don’t call the base-class constructor in BoardGame( ), the compiler will complain that it can’t find a constructor of the form Game( ).
  • The call to the base-class constructor must be the first thing you do in the derived-class constructor

Catching base constructor exceptions

The compiler forces you to place the base-class constructor call first in the body of the derived-class constructor. This also prevents a derived-class constructor from catching any exceptions that come from a base class.

Combining composition and inheritance

It is very common to use composition and inheritance together.

//: c06:PlaceSetting.java
// Combining composition & inheritance.
import com.bruceeckel.simpletest.*;
class Plate {
    Plate(int i) {
    System.out.println("Plate constructor");
    }
}
class DinnerPlate extends Plate {
    DinnerPlate(int i) {
    super(i);
    System.out.println("DinnerPlate constructor");
    }
}
class Utensil {
    Utensil(int i) {
    System.out.println("Utensil constructor");
    }
}
class Spoon extends Utensil {
    Spoon(int i) {
    super(i);
    System.out.println("Spoon constructor");
    }
}
class Fork extends Utensil {
    Fork(int i) {
    super(i);
    System.out.println("Fork constructor");
    }
}
class Knife extends Utensil {
    Knife(int i) {
    super(i);
    System.out.println("Knife constructor");
    }
}
// A cultural way of doing something:
class Custom {
    Custom(int i) {
    System.out.println("Custom constructor");
    }
}
public class PlaceSetting extends Custom {
    private static Test monitor = new Test();
    private Spoon sp;
    private Fork frk;
    private Knife kn;
    private DinnerPlate pl;
    public PlaceSetting(int i) {
    super(i + 1);
    sp = new Spoon(i + 2);
    frk = new Fork(i + 3);
    kn = new Knife(i + 4);
    pl = new DinnerPlate(i + 5);
    System.out.println("PlaceSetting constructor");
    }
public static void main(String[] args) {
    PlaceSetting x = new PlaceSetting(9);
    }
} ///:~

Output:

"Custom constructor",
"Utensil constructor",
"Spoon constructor",
"Utensil constructor",
"Fork constructor",
"Utensil constructor",
"Knife constructor",
"Plate constructor",
"DinnerPlate constructor",
"PlaceSetting constructor"

Guaranteeing proper cleanup

  • Java doesn’t have the C++ concept of a destructor
  • in Java the practice is simply to forget about objects rather than to destroy them, allowing the garbage collector to reclaim the memory as necessary.
  • you can’t know when the garbage collector will be called, or if it will be called.
  • So if you want something cleaned up for a class, you must explicitly write a special method to do it, and make sure that the client programmer knows that they must call this method
  • On top of this you must guard against an exception by putting such cleanup in a finally clause.
    //: c06:CADSystem.java
    // Ensuring proper cleanup.
    package c06;
    import com.bruceeckel.simpletest.*;
    import java.util.*;
    class Shape {
      Shape(int i) {
      System.out.println("Shape constructor");
      }
      void dispose() {
      System.out.println("Shape dispose");
      }
    }
    class Circle extends Shape {
      Circle(int i) {
      super(i);
      System.out.println("Drawing Circle");
      }
      void dispose() {
      System.out.println("Erasing Circle");
      super.dispose();
      }
    }
    class Triangle extends Shape {
      Triangle(int i) {
      super(i);
      System.out.println("Drawing Triangle");
      }
      void dispose() {
      System.out.println("Erasing Triangle");
      super.dispose();
      }
    }
    class Line extends Shape {
      private int start, end;
      Line(int start, int end) {
      super(start);
      this.start = start;
      this.end = end;
      System.out.println("Drawing Line: "+ start+ ", "+ end);
      }
      void dispose() {
      System.out.println("Erasing Line: "+ start+ ", "+ end);
      super.dispose();
      }
    }
    public class CADSystem extends Shape {
      private static Test monitor = new Test();
      private Circle c;
      private Triangle t;
      private Line[] lines = new Line[5];
      public CADSystem(int i) {
          super(i + 1);
          for(int j = 0; j < lines.length; j++)
          lines[j] = new Line(j, j*j);
          c = new Circle(1);
          t = new Triangle(1);
          System.out.println("Combined constructor");
      }
      public void dispose() {
          System.out.println("CADSystem.dispose()");
          // The order of cleanup is the reverse
          // of the order of initialization
          t.dispose();
          c.dispose();
          for(int i = lines.length - 1; i >= 0; i--)
          lines[i].dispose();
          super.dispose();
      }
      public static void main(String[] args) {
          CADSystem x = new CADSystem(47);
          try {
          // Code and exception handling...
          } 
          finally {
          x.dispose();
          }}
    } ///:~
    

    Output:

    "Shape constructor",
    "Shape constructor",
    "Drawing Line: 0, 0",
    "Shape constructor",
    "Drawing Line: 1, 1",
    "Shape constructor",
    "Drawing Line: 2, 4",
    "Shape constructor",
    "Drawing Line: 3, 9",
    "Shape constructor",
    "Drawing Line: 4, 16",
    "Shape constructor",
    "Drawing Circle",
    "Shape constructor",
    "Drawing Triangle",
    "Combined constructor",
    "CADSystem.dispose()",
    "Erasing Triangle",
    "Shape dispose",
    "Erasing Circle",
    "Shape dispose",
    "Erasing Line: 4, 16",
    "Shape dispose",
    "Erasing Line: 3, 9",
    "Shape dispose",
    "Erasing Line: 2, 4",
    "Shape dispose",
    "Erasing Line: 1, 1",
    "Shape dispose",
    "Erasing Line: 0, 0",
    "Shape dispose",
    "Shape dispose"
    
  • Everything in this system is some kind of Shape
  • Each class overrides Shape’s dispose( ) method in addition to calling the baseclass version of that method using super.
  • The specific Shape classes-Circle, Triangle and Line-all have constructors that “draw,” although any method called during the lifetime of the object could be responsible for doing something that needs cleanup.
  • Each class has its own dispose( ) method to restore nonmemory things back to the way they were before the object existed
  • Main has 2 keywords:
    • The try keyword indicates that the block that follows (delimited by curly braces) is a guarded region, which means that it is given special treatment.
    • the code in the finally clause following this guarded region is always executed, no matter how the try block exits.
  • in your cleanup method you must also pay attention to the calling order for the base-class and member-object cleanup methods in case one subobject depends on another:
    • perform all of the cleanup work specific to your class, in the reverse order of creation.

Name hiding

Written on December 24, 2017