Class/Interface Basics

This document discusses subtle class-usage issues and provides demo programs to illustrate the points. The demo programs are part of the ClassesInterfaces project. To use these, download the archive
ClassesInterfaces.zip
Install it as a Java Application with Existing Sources. See the Using NetBeans document for details.

Description

Java classes can be defined at three levels: Regarding access modifiers, there are four possibilities with these rough connotations: These keywords are also applied to classes: Without any access modifier designations, here is a depiction:

classesinterfaces.LevelStructure
package classesinterfaces;
 
class FileLevel {
 
  class ClassLevel {
    // ...
  }
 
  void memberFunction() {
    class LocalLevel {
      // ...
    }
    // other statements using LocalLevel
  }
}
Both class level and local level classes can referred to as inner classes, but I will strive to keep this terminology: These are qualifier usages according to the level: We will ignore the usage of protected, final and abstract for now. These appear when we talk about inheritance.

Java Packages

A Java package is a directory which holds a collection of Java classes and other packages. What makes it different than simply being a directory is that the classes within it are designated as being part of this package. For example we may have
mypackage/
  MyClass.java
Within MyClass.java the very first statement must be:
package mypackage;
As indicated below package names are lower-case. They are meant to be simple names. A "complex" name is expressed by a complex directory structure, e.g. the directory structure
java/
  util/
    Scanner.java
    regex/
      Pattern.java
implies that the Pattern.java class uses this package line:
package java.util.regex;
The package name is the path name java.util.regex. Likewise, Scanner.java belongs to the package java.util. A packaged class is meant to be accessed with the full package path prefix, i.e.,
java.util.regex.Pattern p = /* ... */
It is the import statements which allow us to use simple class names, i.e.,
import java.util.regex.Pattern;
...
Pattern p = /* ... */

NetBeans Experiment

As an experiment in NetBeans, select the javabasics package, and from it create a New ⇾ Package; take the default, newpackage. You'll see this entry in the Projects window:
javabasics.newpackage
Selecting this, create a New ⇾ Main Class, take the default class name NewMain. In this class you'll see the first line:
package javabasics.newpackage;
Then confirm what we're saying about the file structure by looking at the project seen from the Files window:
src/
  javabasics/
    newpackage/
      NewMain.java

JAR files and CLASSPATH

A JAR (Java ARchive) file is a essentially a zipped package structure. Java programs can access packaged classes through this archive file as well as through a normal directory structure. The means by which these directory structures or JAR files are found by a Java program is by a system environment variable called CLASSPATH, which represents a list of directories or JAR files. All these details are conveniently hidden from you when you use NetBeans.

Packaging preferred

As you know, you can create a multi-file Java program without any package use whatsoever; however, all "real" Java software is packaged, often with complex package path names. NetBeans will allow you to not use packages, but it will complain about it, leaving the unpackaged Java files in something it calls the "default package," which is actually no package at all.

Java Identifiers

Identifiers in Java (and many other languages) are mixtures of letters (both upper and lower case), digits and underscores starting with a letter or underscore. For example: x, xYZ, a23, B23_, __3_A, etc. Based on their usage within Java programs there are certain conventions which should be (but are not required to) followed. For the most part it is a good idea to use English words as the basis of identifiers in order to be more descriptive about what the identifier represents.

Identifier classifications

Common terms describing identifiers are these:

Identifier qualifiers and naming conventions

An identifier can be used to name a variable, class, package, method. Based on its location, the variable supports mixtures of the access qualifiers mentioned above. Common naming conventions for Java are these:

Java Bean method conventions

A Java Bean class is one which uses strict naming conventions for certain public methods in order to expose properties about objects of this class. For example, this would be appropriate method usage:
class SomeBeanClass {
 
  public String getMyProperty() {...}                   // a "getter"
  public void setMyProperty(String param_name) {...}    // a "setter"
 
  private String myProperty; // optional
One or both of the two public methods exposes myProperty as a read-only, write-only, or read-write property of type String. The lower camel-case usage is critical. The param_name of the setter can be anything and the underlying data member need not have the property name, and may not even exist per se. However, a common, transparent style uses the property name both as the parameter and the data member with these method definitions:
  public String getMyProperty() { return this.myProperty; }
  public void setMyProperty(String myProperty) { this.myProperty = myProperty;  } 
A variation in the getter/setter naming convention is employed for boolean properties in that the read property uses "is" instead of "get", e.g., for the boolean visible property:
  public boolean isVisible() {...}
  public void setVisible(boolean param_name) {...}
Although you do not have to conform to this standard, it is a good idea to do so when possible. Java code can be written to automatically detect these exposed properties and activate the getter/setter without explicitly using the member function.

File-level classes within Packages

In this section we discuss basic features of classes defined at the file level. The ideas to explore are what is the implication of the public qualifier compared to default (empty) qualifier with respect to package usage. The packages and classes of interest are:
javabasics/
  JavaBasics.java
  WithinPackage.java
  
outside/
  OutsidePackage.java
When you create a class, say, within NetBeans: We first want to understand these conventions.

Can we have more than one class in the same file? Yes.


javabasics.JavaBasics
package javabasics;
 
public class JavaBasics {
  public static String testPublic = "public access within JavaBasics";
  static String testDefault = "default access within JavaBasics";
 
  public static void main(String[] args) {
    System.out.println("JavaBasics: main");
  }
}
 
class ExtraClass {
  public static String testPublic = "public access within ExtraClass";
  static String testDefault = "default access within ExtraClass";
 
  public static void main(String[] args) {
    System.out.println("ExtraClass: main");
  }
}
In this case we have two main classes.

Can we activate ExtraClass.main instead of JavaBasics.main? Yes

Even though the first stands out as being the "right one," NetBeans will allow you to choose between the two when you run this class.

From the Files window, if you look in
build/classes/javabasics
you will see both JavaBasics.class and ExtraClass.class. Both are main classes, and both can be executed (from one level up).

Can both classes be non-public? Yes

Erase the public from JavaBasics to verify.

Can ExtraClass be public? No

The name of a public class must be the name of the file. This is Java's requirement to ensure that file names correspond to class names when attempting to load a class.

Verify by adding public to ExtraClass.

Are non-public features accessible to other classes? Yes, if within the package.

Consider this class within the same package as JavaBasics:

javabasics.WithinPackage
package javabasics;
 
public class WithinPackage {
 
  public static void main(String[] args) {
    System.out.println(JavaBasics.testPublic);
    System.out.println(JavaBasics.testDefault);
 
    System.out.println(ExtraClass.testPublic);
    System.out.println(ExtraClass.testPublic);
  }
}

Are non-public features accessible to other classes outside the package? No

Consider this class within a different package from JavaBasics:

outside.OutsidePackage
package outside;
 
import javabasics.JavaBasics;
//import javabasics.ExtraClass; // line 1
 
public class OutsidePackage {
 
  public static void main(String[] args) {
    //System.out.println(JavaBasics.testDefault);  // line 2
    System.out.println(JavaBasics.testPublic);
  }
}
Do these experiments:
  1. Try un-commenting line 1. It is flagged as an error because ExtraClass is not public, meaning that none of the members within are accessible.
  2. Try un-commenting line 2. It is flagged as an error because the variable is non-public.
  3. Try erasing the public in front of the class definition in JavaBasics, making it be simply:
    class JavaBasics 
    Save the changes. You'll see the line in OutsidePackage become flagged:
    import javabasics.JavaBasics;

What about other qualifiers for file level classes?

Classes defined at the file level must be public or "none" (others do apply later).

Summary

Using public versus "none" has to do with access by classes within/outside the package.

Inner Classes

Inner classes are classes within classes. There are four possibilities: The most crucial distinction is whether the class is defined at the class level or at the function level (local and anonymous). The inner classes, like data members, support all four access access modifiers: public, private, protected and "none".

For inner classes (at the class level) there is a big difference in the intended usage of public versus private access qualifiers.

Public inner class

We will illustrate an example of a public inner class usage. There are 3 demo classes involved, all within the classesinterfaces package:
Employees.java
FindEmployee.java
MakeEmployeeRecord.java

classesinterfaces.Employees
package classesinterfaces;
 
import java.util.GregorianCalendar;
 
public class Employees {
 
  public class Record{
    private int id;
    private String name;
    private GregorianCalendar bday;
 
    public Record(int id, String name, GregorianCalendar bday) {
      this.id = id;
      this.name = name;
      this.bday = bday;
    }
    public Record() { }
    public int getId() { return id; }
    public String getName() { return name; }
    public GregorianCalendar getBday() { return bday; }
 
    public void setId(int id) { this.id = id; }
    public void setName(String name) { this.name = name; }
    public void setBday(GregorianCalendar bday) { this.bday = bday; }
 
    @Override
    public String toString() {
      return String.format("(%s, %s, %s)", id, name, bday.getTime());
    }
  }
 
  private Record[] data = {
    new Record(3344, "John Smith", new GregorianCalendar(1970, 1, 22)),
    new Record(4455, "Jane Doe", new GregorianCalendar(1975, 10, 2)),
  };
 
  public Record fetch(int id) {
    for(Record r: data) {
      if (r.getId() == id) { 
        return r;
      }
    }
    return null;
  }
 
  public void addEmployee(Record employee) { 
    System.out.println("adding employee: " + employee);
    /*
    * not implemented, purpose is to make a point in "MakeEmployeeRecord"
    */
  }
}
The Record class is declared as a public non-static inner class. It could be declared external to Employees, but it would be artificial. In contrast, declared inside Employees, the Record class is an essential ingredient to represent the database; furthermore it is useful outside Employees precisely as Employees.Record, representing an "employee record", perhaps in contrast to the "student record," etc.

The following driver program illustrates the external usage of the Record class:

classesinterfaces.FindEmployee
package classesinterfaces;
 
import java.util.Scanner;
 
public class FindEmployee {
  /**
   * Prompt for an integer "id", fetch and display Employee Record
   * for that id, or indicate that there no such Employee.
   *
   * @param args the command line arguments
   * @throws java.lang.Exception
   */
  public static void main(String[] args) throws Exception {
    Employees database = new Employees();
 
    System.out.print("employee id: ");
    Scanner in = new Scanner(System.in);
    int id = in.nextInt();
 
    Employees.Record employee = database.fetch(id);
    if (employee != null) {
      System.out.println(employee);
    }
    else {
      System.out.println("no such Employee");
    }
  }
}
When you do a simple test-run, you must enter an integer to avoid input failure. Entering 3344 or 4455 gives a successful find operation.

Observe how we use:
Employees.Record
in code outside the Employees class versus simply as Record for code inside the Employees class.

Now let us imagine a program to create and add an employee record:

classesinterfaces.MakeEmployeeRecord
package classesinterfaces;
 
import java.util.GregorianCalendar;
 
public class MakeEmployeeRecord {
 
  public static void main(String[] args) {
    Employees database = new Employees();
 
    //choose this one if Employees.Record is non-static
    Employees.Record employee = database.new Record();
 
    // choose this one if Employees.Record is static
    //Employees.Record employee = new Employees.Record();
 
    employee.setId(5566);
    employee.setName("Jim Jones");
    employee.setBday(new GregorianCalendar(1980, 5, 3));
 
    database.addEmployee(employee);
  }
}
Unfortunately, the creation of an empty record is somewhat convoluted:
Employees database = new Employees();
...
Employees.Record employee = database.new Record();

Static inner classes

The keyword static can only apply to inner classes (defined at the class level). Static means that the members do not reference any non-static members from the containing class. This example illustrates the idea:

classesinterfaces.Outer
package classesinterfaces;
 
public class Outer {
  private static int stat = 55;
  private int dyn = 33;
 
  class NonStaticClass {
    private int var1 = 5 + stat;  // OK
    private int var2 = 9 + dyn;   // OK
  }
 
  static class StaticClass {
    private int var = 5 + stat;      // OK
    // private int var2 = 9 + dyn;   // not OK
  }
}

Make Employees.Record static

In our running example, the Employees class works just as well if we make Record static:
public class Employees {
  ...
  public static class Record {
    ...
  }
  ...
}
In fact, doing so actually improves the understanding of the Record class. By being static, it more accurately reflects the "self-contained" nature of the class which accesses no data members whatsoever from Employees.

Once you make the change to static, you then have to fix the code in:

classesinterfaces.MakeEmployeeRecord
package classesinterfaces;
 
import java.util.GregorianCalendar;
 
public class MakeEmployeeRecord {
 
  public static void main(String[] args) {
    Employees database = new Employees();
 
    //choose this one if Employees.Record is non-static
    //Employees.Record employee = database.new Record();
 
    // choose this one if Employees.Record is static
    Employees.Record employee = new Employees.Record();
 
    employee.setId(5566);
    employee.setName("Jim Jones");
    employee.setBday(new GregorianCalendar(1980, 5, 3));
 
    database.addEmployee(employee);
  }
}
Doing so simplifies the way we create an employee record from
Employees.Record employee = database.new Record();  // non-static Record class
to
Employees.Record employee = new Employees.Record();  // static Record class

Static is preferred for inner classes

In actuality, most inner classes are static, precisely because there is usually no need for such classes to access non-static data from the container class.

Private inner classes

Private inner classes defined at the class level are used to create some sort of internal structure hidden from the public view. Such private classes are essential for creating the internal structures used within the Java data structures classes.

When you use a private inner class, it is typical to abandon the usual conventions for keeping data private. For example, consider the class:
public ContainerClass {
  private static MyInner {
    int data;
    MyInnerClass(int data) { this.data = data; }
  }
 
  public void myMethod() {
    MyInner inner = new MyInner(15);
    inner.data += 10;
  }
}
Using a private inner class fully excludes the outside world from accessing MyInner, and so within ContainerClass you have complete control over its intended usage. In particular, there is no need for any access qualifiers as well as setter and getter access methods.

How does Java manage inner classes?

The inner class, Record, cannot be represented simply with the name Record within the class structure because it could appear within other classes as well. Java creates the class name for it:
Employees$Record
to represent inner classes. The key syntactic feature is the $ to separate the parent and inner class names. From the Files window, take a look at
build/classes/classesinterfaces/

Java Interfaces

A Java interface (see the tutorial) is, in simplest term, a set of method prototypes. It is written like this:
public interface FixSomething {
  void setName(String name);
  void setHours(int hours);
  boolean isDone();
}
An interface, like a class, can appear at all three levels: file, inner, local. The same access qualifiers used for classes (public, private, "none") apply to interfaces as well. The three body-less methods are called prototypes. These prototypes indicate features which must exist in a class which implements this interface. In particular, implements is the crucial Java keyword relating a class to an interface:
public class FixMyCar implements FixSomething {
  private String name;
  private int hours;
 
  public void setName(String name) { this.name = name; }
  public void numHours(int hours) { this.hours = hours; }
  public boolean isDone() { return false; }
  ...
}
A class which implements an interface must define public methods for each of the prototypes listed in the interface. The prototypes declarations themselves are implicitly public, whether declared that way or not.

Thus, an interface is a requirement about functionality without specifying behavior. A class can implement multiple interfaces in the obvious sense of defining all the prototypes (assuming no prototype conflicts).

Simple Example

As a simple example, the code in the demo Application contains the interface:

classesinterfaces.MyInterface
package classesinterfaces;
 
public interface MyInterface {
  boolean functionA(int test);
  int functionB();
}
as well as a simple class which implements it:

outside.ImplementMyInterface
package outside;
 
import classesinterfaces.MyInterface;
 
public class ImplementMyInterface implements MyInterface {
 
  @Override
  public boolean functionA(int test) {
    return true;
  }
 
  @Override
  public int functionB() {
    return 1;
  }
}
Because the implementation class is outside the package where the interface is defined, the interface must be declared public.

Override Annotations

The line used within class definitions
@Override
is called an annotation (see the tutorial). Annotations are a form of metadata which are not statements, but directives used when the class is compiled, loaded, or even executed. The @Override annotation is probably the most common. If you ignore it, you'll see that NetBeans will give you a "suggestion" to use it.

The significance of @Override is that it tells the compiler that your definition of the function overrides (for interfaces, defines) the member function in the interface. For example, you can add further member functions to the implementation class since nothing mandates that only the interface prototypes need be defined:
public class ImplementMyInterface implements MyInterface {
   ...
   public void foo() { }  // you can define functions not in MyInterface
}
However, if you attempt to add the override, you'll see that NetBeans flags it as an error:
public class ImplementMyInterface implements MyInterface {
   ...
   @Override
   public void foo() { }
}

Constants in Interfaces

An interface can also express constants. For example, we can add this to the previous interface:

classesinterfaces.MyInterface (with constant)
package classesinterfaces;
 
public interface MyInterface {
  boolean functionA(int test);
  int functionB();
 
  int FOOBAR = 55;
}
Any such variable added is automatically set to be static final. An implementing class will automatically have access to this constant, and so we could modify our implementing class as follows:

outside.ImplementMyInterface (with constant usage)
package outside;
 
import classesinterfaces.MyInterface;
 
public class ImplementMyInterface implements MyInterface {
  @Override
  public boolean functionA(int test) {
    return true;
  }
 
  @Override
  public int functionB() {
    return 1 + FOOBAR;
  }
 
  public static int functionC() {
    return 10 + FOOBAR;
  }
}
The functionC entry proves that the interface constant is static. You can verify that the interface constant is indeed a constant by trying to set it, e.g.:
  ...
  @Override
  public int functionB() {
    FOOBAR = 77;            // flagged as error
    return 1 + FOOBAR;
  }
  ...
The effect is as if the constant were not defined in the interface and we had done this:
package outside;
 
import classesinterfaces.MyInterface;
 
public class ImplementMyInterface implements MyInterface {
  public static final int FOOBAR = 55;
 
  @Override
  public boolean functionA(int test) {
    return true;
  }
  @Override
  public int functionB() {
    return 1 + FOOBAR;
  }
  public static int functionC() {
    return 10 + FOOBAR;
  }
}

Interfaces in event-driven programming

Interfaces play a big part in event programming for Java GUI applications. The simplest, and perhaps most common interface is java.awt.event.ActionListener:
public interface ActionListener ... {
  public void actionPerformed(ActionEvent evt);
}
A common event-driven program "attaches" an ActionListener object to a JButton object (and to others). Based on what we've talked about, it could go something like this the following which triggers a response message when the button is pressed:
class Responder implements ActionListener {
  public void actionPerformed(ActionEvent evt) {
    System.out.println("someone pressed me");
  }
}
 
JButton button = new JButton();
Responder responder = new Responder();
// or,
// ActionListener responder = new Responder();
button.addActionListener(responder);

Local and Anonymous classes

Local and anonymous classes (see the tutorials for local and anonymous) are classes which are defined within the body of a function. Our demo program refers to the interface

classesinterfaces.SampleInterface
package classesinterfaces;
 
public interface SampleInterface {
  public int foo(int arg);
}
used in this sample main class:

classesinterfaces.LocalTest
package classesinterfaces;
 
public class LocalClassTest {
 
  public static void main(String[] args) {
    function1();
    function2();
  }
 
  public static void function1() {
    class LocalClass implements SampleInterface {
      @Override
      public int foo(int arg) { return arg + 10; }
    }
    // no semi-colon afterwards
 
    SampleInterface lobj1 = new LocalClass();
 
    LocalClass lobj2 = new LocalClass();
 
    System.out.println(lobj1.foo(1));
 
    System.out.println(lobj2.foo(2));
  }
 
  public static void function2() {
    class LocalClass implements SampleInterface {
      @Override
      public int foo(int arg) { return arg + 20; }
    }
 
    SampleInterface lobj1 = new LocalClass();
 
    System.out.println(lobj1.foo(1));
 
    SampleInterface lobj3 = new SampleInterface() {
      @Override
      public int foo(int arg) { return arg + 30; }
    };
 
    SampleInterface lobj4 = new SampleInterface() {
      @Override
      public int foo(int arg) { return arg + 40; }
    };
 
    System.out.println(lobj3.foo(1));
    System.out.println(lobj4.foo(1));
  }
}
The interface SampleInterface is used as the basis of all objects created. Note that the objects created can be typed either by the interface:
SampleInterface lobj1 = new LocalClass();
or by the implementing class:
LocalClass lobj2 = new LocalClass();
Using the former, interface-typed variable, is generally preferred.

Note one of the peculiarities in that the local class is declared twice, once within each function. There is no problem with that since it is truly "local" to the function, but it causes issues with creating suitable class names; somehow java must differentiate the first LocalClass from the second one.

Note the syntax:
SampleInterface lobj3 = new SampleInterface() {
  @Override
  public int foo(int arg) { return arg + 30; }
};
Compared to the creation of lobj2, the creation of lobj3 does not stop here:
new SampleInterface();
Instead, it continues on with the braces { } which give the required definition of the interface prototype function.

The means that lobj3 is created as an object of an anonymous class which implements SampleInterface. Java must differentiate the two local classes created because, even though the implement the same interface, they are different classes.

Numbered classes

The key to seeing how Java sorts out local classes is to look at the actual class files created. Run the LocalTest program. You can see these (among others) through the Files window:
build/classes/classesinterfaces/
  LocalClassTest.class
  LocalClassTest$1LocalClass.class
  LocalClassTest$2LocalClass.class
  LocalClassTest$1.class
  LocalClassTest$2.class
Java names these local classes based on the top-level class, LocalClassTest with a number appended via $N followed by the local class name (for named local classes) of nothing for anonymous classes.

So, for example, the occurrence of LocalClass in function1 really becomes:
class LocalTest$1LocalClass implements SampleInterface {
  @Override
  public int foo(int arg) { return arg + 10; }
}
SampleInterface lobj1 = new LocalTest$1LocalClass();
The creation of lobj3 from an anonymous class is:
class LocalTest$1 implements SampleInterface {
  @Override
  public int foo(int arg) { return arg + 10; }
}
SampleInterface lobj3 = new LocalTest$1();

Revisit Event Handlers

Based on what we know now, the construction of a button event handler used in the Hello GUI application makes sense:
public class Controller {
  ...
  public Controller() {
    ...
    frame.getHelloButton().addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent ae) {
        System.out.println("Hello World");
      }
    });
  }
}
If you run the HelloGUI application and look in the NetBeans folder (from the Files window) you will see the anonymous class created:
build/classes/hellogui/Controller$1.class
What Java is doing is this:
public class Controller {
  ...
  public Controller() {
    ...
    // local class name taken from to outer class, not the constructor
    class Controller$1 implements ActionListener {
      @Override
      public void actionPerformed(ActionEvent ae) {
        System.out.println("Hello World");
      }
    }
    frame.getHelloButton().addActionListener( new Controller$1() );
  }
}
Based on what you know now, review the portion of the GUIs document which discusses event handling strategies:
GUIs:event-handler-options


© Robert M. Kline