Skip to content

3. Classes and interfaces

3.1. The object explained with examples

3.1.1. Overview

We will now explore object-oriented programming through examples. An object is an entity that contains data defining its state (called attributes or properties) and functions (called methods). An object is created based on a template called a class:

public class C1{
    type1 p1;            // property p1
    type2 p2;            // property p2
    type3 m3(…){        // method m3
    }
    type4 m4(…){        // method m4
    }
}

From the previous class C1, we can create many objects O1, O2, … All will have the properties p1, p2, … and the methods m3, m4, … They will have different values for their properties pi, thus each having its own state.

If O1 is an object of type C1, then O1.p1 refers to the property p1 of O1, and O1.m1 refers to the method m1 of O1.

Let’s consider a first object model: the Person class.

3.1.2. Definition of the Person class

The definition of the Person class is as follows:


import java.io.*;

public class Person{
  // attributes
  private String firstName;
  private String lastName;
  private int age;
  
  // method
  public void initialize(String firstName, String lastName, int age){
    this.firstName = P;
    this.lastName = N;
    this.age = age;
  }

  // method
  public void identify(){
    System.out.println(firstName + "," + lastName + "," + age);
  }
}

Here we have the definition of a class, which is a data type. When we create variables of this type, we call them objects. A class is therefore a template from which objects are constructed.

The members or fields of a class can be data or methods (functions). These fields can have one of the following three attributes:

private: A private field is accessible only by the class’s internal methods

public: A public field is accessible by any function, whether or not it is defined within the class

protected: A protected field is accessible only by the class’s internal methods or by a derived object (see the concept of inheritance later).

Generally, a class’s data is declared private, while its methods are declared public. This means that the user of an object (the programmer)

a: will not have direct access to the object’s private data

b: will be able to call the object's public methods, including those that provide access to its private data.

The syntax for declaring an object is as follows:


public class className{
    private data or private method
    public data or public method
    protected data or protected method
}

Notes

  • The order in which private, protected, and public attributes are declared is arbitrary.

3.1.3. The initialize method

Let's return to our Person class declared as:


import java.io.*;

public class Person{
  // attributes
  private String firstName;
  private String lastName;
  private int age;
  
  // method
  public void initialize(String firstName, String lastName, int age) {
    this.firstName = P;
    this.lastName = N;
    this.age = age;
  }

  // method
  public void identify(){
    System.out.println(firstName + "," + lastName + "," + age);
  }
}

What is the role of the initialize method? Because lastName, firstName, and age are private members of the Person class, the statements

Person p1;
p1.firstName="Jean";
p1.lastName = "Dupont";
p1.age=30;

are invalid. We must initialize an object of type Person using a public method. This is the role of the initialize method. We write:

person p1;
p1.initialize("Jean", "Dupont", 30);

The syntax p1.initialize is valid because initialize is public.

3.1.4. The new operator

The sequence of instructions

person p1;
p1.initialize("Jean", "Dupont", 30);

is incorrect. The statement

    person p1;

declares p1 as a reference to an object of type person. This object does not yet exist, so p1 is not initialized. It is as if we had written:

person p1 = null;

where we explicitly indicate with the keyword null that the variable p1 does not yet reference any object.

When we then write

p1.initialize("Jean", "Dupont", 30);

we are calling the initialize method of the object referenced by p1. However, this object does not yet exist, and the compiler will report an error. For p1 to reference an object, we must write:

person p1 = new person();

This creates an uninitialized Person object: the name and first_name attributes, which are references to String objects, will have the value null, and age will have the value 0. There is therefore a default initialization. Now that p1 references an object, the initialization statement for this object

p1.initialize("Jean", "Dupont", 30);

is valid.

3.1.5. The keyword this

Let’s look at the code for the initialize method:


public void initialize(String P, String N, int age){
    this.firstName = P;
    this.lastName = N;
    this.age = age;
  }

The statement this.firstName = P means that the firstName attribute of the current object (this) is assigned the value P. The keyword this refers to the current object: the one in which the method being executed resides. How do we know this? Let’s look at how the object referenced by p1 is initialized in the calling program:

p1.initialize("Jean", "Dupont", 30);

It is the initialize method of the p1 object that is called. When we reference the this object within this method, we are actually referencing the p1 object. The initialize method could also have been written as follows:


public void initialize(String P, String N, int age) {
   firstName = P;
   lastName = N;
   this.age = age;
  }

When a method of an object references an attribute A of that object, the notation this.A is implied. It must be used explicitly when there is a conflict of identifiers. This is the case with the statement:


this.age=age;

where age refers to both an attribute of the current object and the age parameter received by the method. The ambiguity must then be resolved by referring to the age attribute as this.age.

3.1.6. A test program

Here is a test program:


public class test1{
  public static void main(String arg[]){
    person p1 = new person();
    p1.initialize("Jean", "Dupont", 30);
    p1.identify();
  }
}

The Person class is defined in the source file person.java and is compiled:

E:\data\serge\JAVA\BASES\OBJETS\2>javac person.java

E:\data\serge\JAVA\BASES\OBJETS\2>dir
06/10/2002  09:21                  473 person.java
06/10/2002  09:22                  835 person.class
06/10/2002  09:23                  165 test1.java

We do the same for the test program:

E:\data\serge\JAVA\BASES\OBJETS\2>javac test1.java

E:\data\serge\JAVA\BASES\OBJETS\2>dir
06/10/2002  09:21                  473 person.java
06/10/2002  09:22                  835 person.class
06/10/2002  09:23                  165 test1.java
06/10/2002  09:25                  418 test1.class

It may seem surprising that the test1.java program does not import the person class with a statement:

import Person;

When the compiler encounters a class reference in the source code that is not defined in that same source file, it searches for the class in various locations:

  • in the packages imported by the import statements
  • in the directory from which the compiler was launched

In our example, the compiler was launched from the directory containing the personne.class file, which explains why it found the definition of the personne class. In this scenario, adding an import statement causes a compilation error:

E:\data\serge\JAVA\BASES\OBJETS\2>javac test1.java
test1.java:1: '.' expected
import Person;
               ^
1 error

To avoid this error but to ensure that the Person class is imported, we will write the following at the beginning of the program in the future:

// imported classes
// import Person;

We can now run the test1.class file:

E:\data\serge\JAVA\BASES\OBJETS\2>java test1
Jean,Dupont,30

It is possible to combine multiple classes into a single source file. Let’s combine the classes person* and test1 into the source file test2.java*. The test1 class is renamed test2 to reflect the change in the source file name:

// imported packages
import java.io.*;

class Person{
  // attributes
  private String firstName;    // first name of my person
  private String lastName;            // their last name
  private int age;                // their age

  // method
  public void initialize(String P, String N, int age){
    this.firstName = P;
    this.lastName = N;
    this.age = age;
  }//initialize

  // method
  public void identify(){
    System.out.println(firstName + "," + lastName + "," + age);
  }//identify
}//class
public class test2{
  public static void main(String arg[]){
    person p1 = new person();
    p1.initialize("Jean", "Dupont", 30);
    p1.identify();
  }
}

Note that the Person class no longer has the public attribute. In fact, in a Java source file, only one class can have the public attribute. This is the class that contains the main method. Furthermore, the source file must be named after this class. Let’s compile the test2.java file:

E:\data\serge\JAVA\BASES\OBJETS\3>dir
10/06/2002  09:36                  633 test2.java

E:\data\serge\JAVA\BASES\OBJETS\3>javac test2.java

E:\data\serge\JAVA\BASES\OBJETS\3>dir
06/10/2002  09:36                  633 test2.java
06/10/2002  09:41                  832 person.class
06/10/2002  09:41                  418 test2.class

Note that a .class file has been generated for each of the classes present in the source file. Now let’s run the test2.class file:

E:\data\serge\JAVA\BASES\OBJETS\2>java test2
Jean,Dupont,30

From now on, we will use both methods interchangeably:

  • classes grouped into a single source file
  • one class per source file

3.1.7. Another method initializes

Let’s continue with the Person class and add the following method to it:


public void initialize(Person P){
    firstName = P.firstName;
    lastName = P.lastName;
    this.age = P.age;
  }

We now have two methods named *initialize*: this is allowed as long as they take different parameters. That is the case here. The parameter is now a reference P to a person. The attributes of the person P are then assigned to the current object (this). Note that the initialize method has direct access to the attributes of the object P even though they are of type private. This is always true: methods of an object O1 of a class C always have access to the private attributes of other objects of the same class C.

Here is a test of the new Person class:


// import Person;
import java.io.*;

public class test1{
  public static void main(String arg[]){
    Person p1 = new Person();
    p1.initialize("Jean", "Dupont", 30);
    System.out.print("p1=");
    p1.identify();
    Person p2 = new Person();
    p2.initialize(p1);
    System.out.print("p2=");
    p2.identify();
  }
}

and its results:

p1=Jean,Dupont,30
p2=Jean,Dupont,30

3.1.8. Constructors of the Person class

A constructor is a method that bears the name of the class and is called when the object is created. It is generally used to initialize the object. It is a method that can accept arguments but returns no result. Its prototype or definition is not preceded by any type (not even void).

If a class has a constructor that accepts n arguments args, the declaration and initialization of an object of that class can be done as follows:


        class object = new class(arg1, arg2, ..., argn);

or


        class object;

        object = new class(arg1, arg2, ... argn);

When a class has one or more constructors, one of these constructors must be used to create an object of that class. If a class C has no constructors, it has a default constructor, which is the constructor without parameters: public C(). The object’s attributes are then initialized with default values. This is what happened when, in the previous programs, we wrote:

    person p1;
    p1 = new Person();

Let's create two constructors for our Person class:


public class Person{
  // attributes
  private String firstName;
  private String lastName;
  private int age;
  
  // constructors
  public Person(String firstName, String lastName, int age) {
    initialize(P, N, age);
  }
  public Person(Person P) {
    initialize(P);
  }

  // method
  public void initialize(String P, String N, int age){
    this.firstName = P;
    this.lastName = N;
    this.age = age;
  }
  public void initialize(person P){
    this.firstName = P.firstName;
    this.lastName = P.lastName;
    this.age = P.age;
  }

  // method
  public void identify(){
    System.out.println(firstName + "," + lastName + "," + age);
  }
}

Our two constructors simply call the corresponding initialize methods. Recall that when, in a constructor, we find the notation initialize(P), for example, the compiler translates it to this.initialize(P). In the constructor, the initialize method is therefore called to operate on the object referenced by this, that is, the current object, the one being constructed.

Here is a test program:


// import Person;
import java.io.*;

public class test1{
  public static void main(String arg[]){
    Person p1 = new Person("Jean", "Dupont", 30);
    System.out.print("p1=");
    p1.identify();
    Person p2 = new Person(p1);
    System.out.print("p2=");
    p2.identify();
  }
}

and the results obtained:

p1=Jean,Dupont,30
p2=Jean,Dupont,30

3.1.9. Object references

We are still using the same Person class. The test program becomes the following:


// import Person;
import java.io.*;

public class test1{
  public static void main(String arg[]){
    // p1
    Person p1 = new Person("Jean", "Dupont", 30);
    System.out.print("p1=");  p1.identify();
    // p2 references the same object as p1
    Person p2 = p1;
    System.out.print("p2="); p2.identify();
    // p3 references an object that will be a copy of the object referenced by p1
    Person p3 = new Person(p1);
    System.out.print("p3=");  p3.identify();
    // we change the state of the object referenced by p1
    p1.initialize("Micheline", "Benoît", 67);
    System.out.print("p1=");  p1.identify();
    // since p2=p1, the object referenced by p2 must have changed state
    System.out.print("p2=");  p2.identify();
    // since p3 does not reference the same object as p1, the object referenced by p3 must not have changed
    System.out.print("p3=");  p3.identify();
  }
}

The results are as follows:

p1=Jean,Dupont,30
p2=Jean,Dupont,30
p3=Jean,Dupont,30
p1=Micheline,Benoît,67
p2=Micheline,Benoît,67
p3=Jean,Dupont,30

When declaring the variable p1 using

person p1 = new person("Jean", "Dupont", 30);

p1 references the object person("Jean","Dupont",30) but is not the object itself. In C, we would say that it is a pointer, i.e., the address of the created object. If we then write:

    p1 = null

It is not the object person("Jean","Dupont",30) that is modified; rather, it is the reference p1 that changes its value. The object person("Jean","Dupont",30) will be "lost" if it is not referenced by any other variable.

When we write:

person p2 = p1;

we initialize the pointer p2: it "points" to the same object (it refers to the same object) as the pointer p1. Thus, if we modify the object "pointed to" (or referenced) by p1, we modify the one referenced by p2.

When we write:

person p3 = new person(p1);

a new object is created, which is a copy of the object referenced by p1. This new object will be referenced by p3. If you modify the object "pointed to" (or referenced) by p1, you do not modify the one referenced by p3 in any way. This is what the results show.

3.1.10. Temporary objects

In an expression, you can explicitly call an object’s constructor: the object is created, but you cannot access it (to modify it, for example). This temporary object is created for the purpose of evaluating the expression and then discarded. The memory space it occupied will be automatically reclaimed later by a program called a "garbage collector," whose role is to reclaim memory space occupied by objects that are no longer referenced by program data.

Consider the following example:


// import person;

public class test1{
  public static void main(String arg[]){
    new Person(new Person("Jean", "Dupont", 30)).identify();
  }
}

and let's modify the constructors of the Person class so that they display a message:


// constructors
  public Person(String P, String N, int age){
    System.out.println("Person constructor (String, String, int)");
    initialize(P, N, age);
  }
  public Person(Person P) {
    System.out.println("Person constructor (Person)");
    initialize(P);
  }

We get the following results:

Person constructor(String, String, int)
Person constructor(person)
Jean, Dupont, 30

showing the successive construction of the two temporary objects.

3.1.11. Methods for reading and writing private attributes

We add the necessary methods to the Person class to read or modify the state of the objects' attributes:


public class Person {
  private String firstName;
  private String lastName;
  private int age;
  
  public Person(String FirstName, String LastName, int age){
    this.firstName = P;
    this.lastName = N;
    this.age = age;
  }

  public Person(Person P){
    this.firstName = P.firstName;
    this.lastName = P.lastName;
    this.age = P.age;
  }

  public void identify(){
    System.out.println(firstName + "," + lastName + "," + age);
  }

  // accessors
  public String getFirstName(){
    return firstName;
  }
  public String getLastName(){
    return lastName;
  }
  public int getAge(){
    return age;
  }

  //accessors
  public void setFirstName(String P){
    this.firstName = P;
  }
  public void setLastName(String L){
    this.lastName = N;
  }
  public void setAge(int age){
    this.age = age;
  }
}

We test the new class with the following program:


// import Person;

public class test1{
  public static void main(String[] arg){
    Person P = new Person("Jean", "Michelin", 34);
    System.out.println("P=(" + P.getFirstName() + "," + P.getLastName() + "," + P.getAge() + ""));
    P.setAge(56);
    System.out.println("P=(" + P.getFirstName() + "," + P.getLastName() + "," + P.getAge() + ""));
  }
}

and we get the following results:

P=(Jean,Michelin,34)
P=(Jean,Michelin,56)

3.1.12. Class methods and attributes

Suppose we want to count the number of Person objects created in an application. We could manage a counter ourselves, but we risk forgetting temporary objects that are created here and there. It would seem safer to include an instruction in the Person class constructors that increments a counter. The problem is passing a reference to this counter so that the constructor can increment it: we need to pass a new parameter to them. We can also include the counter in the class definition. Since it is an attribute of the class itself and not of a particular object of that class, we declare it differently using the static keyword:

    private static long nbPeople;        // number of people created

To reference it, we write person.nbPeople to show that it is an attribute of the Person class itself. Here, we have created a private attribute that cannot be accessed directly from outside the class. We therefore create a public method to provide access to the class attribute nbPersonnes. To return the value of nbPersonnes, the method does not need a specific object: indeed, nbPersonnes is not the attribute of a specific object; it is the attribute of the entire class. Therefore, we need a class method that is also declared static:

public static long getNbPeople(){
    return nbPeople;
}

which will be called from outside using the syntax person.getNbPeople(). Here is an example.

The Person class becomes the following:


public class Person{
  
  // class attribute
  private static long numberOfPeople = 0;

  // object attributes

  
  // constructors
  public Person(String P, String N, int age) {
    initialize(P, N, age);
    nbPeople++;
  }
  public Person(Person P) {
    initialize(P);
    nbPeople++;
  }

  // method


  // class method
  public static long getNumberOfPeople(){
    return nbPeople;
  }

}// class

With the following program:


// import Person;

public class test1{
  public static void main(String arg[]){
    person p1 = new person("Jean", "Dupont", 30);
    person p2 = new person(p1);
    new Person(p1);
    System.out.println("Number of people created: " + person.getNbPeople());
  }// main
}//test1

We get the following results:

    Number of people created: 3

3.1.13. Passing an object to a function

We have already mentioned that Java passes actual function parameters by value: the values of the actual parameters are copied into the formal parameters. A function cannot therefore modify the actual parameters.

In the case of an object, one must not be misled by the common misuse of language that occurs when people systematically refer to an "object" instead of an "object reference." An object is manipulated only via a reference (a pointer) to it. What is passed to a function, therefore, is not the object itself but a reference to that object. It is thus the value of the reference—not the value of the object itself—that is copied into the formal parameter: no new object is created.

If an object reference R1 is passed to a function, it will be copied into the corresponding formal parameter R2. Thus, the references R2 and R1 point to the same object. If the function modifies the object pointed to by R2, it obviously modifies the one referenced by R1 since they are the same.

Image

This is illustrated by the following example:


// import person;

public class test1{
  public static void main(String arg[]){
    person p1 = new person("Jean", "Dupont", 30);
    System.out.print("Actual parameter before modification: ");
    p1.identify();
    modify(p1);
    System.out.print("Actual parameter after modification: ");
    p1.identify();
  }// main

  private static void modify(Person P) {
    System.out.print("Formal parameter before modification: ");
    P.identify();
    P.initialize("Sylvie", "Vartan", 52);
    System.out.print("Formal parameter after modification: ");
    P.identify();
  }// modifies
}// class

The modify method is declared static because it is a class method: you do not need to prefix it with an object to call it. The results obtained are as follows:

Person constructor(String, String, int)
Actual parameters before modification: Jean,Dupont,30
Formal parameter before modification: Jean,Dupont,30
Formal parameter after modification: Sylvie,Vartan,52
Actual parameter after modification: Sylvie,Vartan,52

We can see that only one object is constructed: that of the person p1 in the main function, and that the object has indeed been modified by the modify function.

3.1.14. Encapsulating a function’s output parameters in an object

Because parameters are passed by value, it is not possible to write a Java function with output parameters of type int, for example, since we cannot pass a reference to an int type that is not an object. We can therefore create a class that encapsulates the int type:


public class integers{
  private int value;

  public Integers(int value){
    this.value = value;
  }

  public void setValue(int value) {
    this.value = value;
  }

  public int getValue() {
    return value;
  }
}

The previous class has a constructor for initializing an integer and two methods for reading and modifying the value of that integer. We test this class with the following program:


// import integers;

public class test2{
  public static void main(String[] arg){
    Integer I = new Integer(12);
    System.out.println("I=" + I.getValue());
    change(I);
    System.out.println("I=" + I.getValue());
  }
  private static void change(Integer integer){
    integer.setValue(15);
  }
}

and we get the following results:

I=12
I=15

3.1.15. An array of people

An object is a piece of data like any other, and as such, multiple objects can be grouped together in an array:


// import Person;

public class test1{
  public static void main(String arg[]){
    person[] friends = new person[3];
    System.out.println("----------------");
    friends[0] = new Person("Jean", "Dupont", 30);
    friends[1] = new Person("Sylvie", "Vartan", 52);
    friends[2] = new Person("Neil", "Armstrong", 66);
    int i;
    for (i = 0; i < friends.length; i++)
      friends[i].identify();
  }
}

The statement person[] friends = new person[3]; creates an array of 3 elements of type person. These 3 elements are initialized here with the value null, meaning they do not reference any objects. Again, in a technical sense, we refer to an "array of objects" when it is actually just an array of object references. The creation of the object array—an array that is itself an object (as indicated by the use of new)—does not, in and of itself, create any objects of the type of its elements: this must be done subsequently.

The following results are obtained:

----------------
Person constructor(String, String, int)
Person constructor(String, String, int)
Person constructor(String, String, int)
Jean Dupont, 30
Sylvie Vartan, 52
Neil Armstrong, 66

3.2. Inheritance Through Examples

3.2.1. Overview

Here we discuss the concept of inheritance. The purpose of inheritance is to "customize" an existing class so that it meets our needs. Suppose we want to create a Teacher class: a teacher is a specific type of person. They have attributes that other people do not have: the subject they teach, for example. But they also have the attributes of any person: first name, last name, and age. A teacher is therefore a full member of the Person class but has additional attributes. Rather than writing a Teacher class from scratch, we would prefer to build upon the existing Person class and adapt it to the specific characteristics of teachers. It is the concept of inheritance that allows us to do this.

To express that the Teacher class inherits the properties of the Person class, we would write:


    public class Teacher extends Person

Person is called the parent (or base) class, and Teacher is the derived (or child) class. A Teacher object has all the qualities of a Person object: it has the same attributes and methods. These attributes and methods of the parent class are not repeated in the definition of the child class; we simply specify the attributes and methods added by the child class:


class Teacher extends Person{
// attributes
  private int section;

// constructor
  public Teacher(String P, String N, int age, int section){
    super(P, N, age);
    this.section = section;
  }
}

We assume that the Person class is defined as follows:


public class Person{
  private String firstName;
  private String lastName;
  private int age;
  
  public Person(String firstName, String lastName, int age){
    this.firstName = P;
    this.lastName = N;
    this.age = age;
  }

  public Person(Person P) {
    this.firstName = P.firstName;
    this.lastName = P.lastName;
    this.age = P.age;
  }

  public String identity(){
    return "person(" + firstName + "," + lastName + "," + age + ")";
  }

  // accessors
  public String getFirstName(){
    return firstName;
  }
  public String getLastName(){
    return lastName;
  }
  public int getAge(){
    return age;
  }

  //accessors
  public void setFirstName(String P){
    this.firstName = P;
  }
  public void setLastName(String L){
    this.lastName = N;
  }
  public void setAge(int age){
    this.age = age;
  }
}

The identifiant method has been slightly modified to return a string identifying the person and is now named identite. Here, the Teacher class adds to the methods and attributes of the Person class:

  • a `section` attribute, which is the section number to which the teacher belongs within the teaching staff (basically one section per subject)
  • a new constructor that initializes all of a teacher’s attributes

3.2.2. Creating a Teacher Object

The constructor for the Teacher class is as follows:


// constructor
  public Teacher(String P, String N, int age, int section){
    super(P, N, age);
    this.section = section;
  }

The statement super(P,N,age) is a call to the constructor of the parent class, in this case the Person class. We know that this constructor initializes the first_name, last_name, and age fields of the Person object contained within the Student object. This seems quite complicated, and we might prefer to write:


// constructor
  public Teacher(String P, String N, int age, int section){
    this.firstName = P;
        this.lastName = N
        this.age = age
      this.class = class;
  }

That's impossible. The Person class has declared its three fields—first_name, last_name, and age—as private. Only objects of the same class have direct access to these fields. All other objects, including child objects as in this case, must use public methods to access them. This would have been different if the Person class had declared the three fields as protected: it would then have allowed derived classes to have direct access to the three fields. In our example, using the parent class’s constructor was therefore the correct solution, and this is the standard approach: when constructing a child object, we first call the parent object’s constructor and then complete the initializations specific to the child object (section in our example).

Let’s try a first program:


// import person;
// import teacher;

public class test1{
  public static void main(String arg[]){
    System.out.println(new Teacher("Jean", "Dupont", 30, 27).identity());
  }
}

This program simply creates a Teacher object (new) and identifies it. The Teacher class does not have an identity method, but its parent class does have one, which is also public: through inheritance, it becomes a public method of the Teacher class.

The source files for the classes are placed in the same directory and then compiled:

E:\data\serge\JAVA\BASES\OBJETS\4>dir
06/10/2002  10:00                  765 person.java
06/10/2002  10:00                  212 teacher.java
06/10/2002  10:01                  192 test1.java

E:\data\serge\JAVA\BASES\OBJETS\4>javac *.java

E:\data\serge\JAVA\BASES\OBJETS\4>dir
06/10/2002  10:00                  765 person.java
06/10/2002  10:00                  212 teacher.java
06/10/2002  10:01                  192 test1.java
06/10/2002  10:02                  316 teacher.class
06/10/2002  10:02                1,146 person.class
06/10/2002  10:02                  550 test1.class

The test1.class file is executed:

E:\data\serge\JAVA\BASES\OBJETS\4>java test1
person(Jean,Dupont,30)

3.2.3. Method Overloading

In the previous example, we had the identity of the person part of the teacher, but some information specific to the Teacher class (the section) is missing. We therefore need to write a method to identify the teacher:


class Teacher extends Person {
  int section;

  public Teacher(String P, String N, int age, int section){
    super(P, N, age);
    this.section = section;
  }

  public String getName(){
    return "teacher(" + super.identity() + "," + section + ")";    
  }    
}

The identity method of the Teacher class relies on the identity method of its parent class (super.identity) to display its "person" part, then adds the section field, which is specific to the Teacher class.

The Teacher class now has two identity methods:

  • the one inherited from the parent Person class
  • its own

If E is a Teacher object, E.identity refers to the identity method of the Teacher class. We say that the identity method of the parent class is "overridden" by the identity method of the child class. In general, if O is an object and M is a method, to execute the method O.M, the system searches for a method M in the following order:

  • in the class of object O
  • in its parent class, if it has one
  • in the parent class of its parent class, if it exists
  • and so on…

Inheritance therefore allows methods with the same name in the parent class to be overridden in the child class. This is what allows the child class to be adapted to its own needs. Combined with polymorphism, which we will discuss shortly, method overriding is the primary benefit of inheritance.

Let’s consider the same example as before:


// import Person;
// import Teacher;

public class test1{
  public static void main(String arg[]){
    System.out.println(new Teacher("Jean", "Dupont", 30, 27).identity());
  }
}

The results obtained this time are as follows:

    teacher(person(Jean, Dupont, 30), 27)

3.2.4. Polymorphism

Consider a class hierarchy: C0  C1  C2  … Cn

where Ci Cj indicates that class Cj is derived from class Ci . This implies that class Cj has all the characteristics of class Ci plus others. Let Oi be objects of type Ci . It is valid to write:

    Oi= Ojwith j > i

Indeed, by inheritance, class Cjhas all the characteristics of class Ciplus additional ones. Therefore, an object Ojof type Cjcontains within itself an object of type Ci. The operation

    Oi= Oj

means that Oiis a reference to the object of type Cicontained within the object Oj.

The fact that a variable Oi of class Ci can in fact refer not only to an object of class Ci but to any object derived from class Ci is called polymorphism: the ability of a variable to refer to different types of objects.

Let’s take an example and consider the following function, which is independent of any class:

    public static void display(Object obj){
        .
    }

The Object class is the "parent" of all Java classes. So when we write:

    public class Person

we are implicitly writing:

    public class Person extends Object

Thus, every Java object contains an *Object* component. Therefore, we can write:

    teacher e;
    display(e);

The formal parameter of type Object in the display function will receive a value of type Teacher. Since Teacher derives from Object, this is valid.

3.2.5. Overloading and Polymorphism

Let’s complete our display function:

    public static void display(Object obj){
        System.out.println(obj.toString());
    }

The obj.toString() method returns a string identifying the obj object in the form class_name@object_address. What happens in the case of our previous example:

    teacher e = new teacher(...);
    display(e);

The system must execute the statement System.out.println(e.toString()), where e is a Teacher object. It searches for a toString method in the class hierarchy leading to the Teacher class, starting with the last one:

  • in the Teacher class, it does not find a toString() method
  • in the parent class Person, it does not find a toString() method
  • in the parent class Object, it finds the toString() method and executes it

This is what the following program demonstrates:


// import Person;
// import Teacher;

public class test1{
  public static void main(String arg[]){
    teacher e = new teacher("Lucile", "Dumas", 56, 61);
    display(e);
    person p = new person("Jean", "Dupont", 30);
    display(p);
  }
  public static void display(Object obj) {
    System.out.println(obj.toString());
  }
}

The results are as follows:

teacher@1ee789
person@1ee770

That is, class_name@object_address. Since this isn’t very clear, we might be tempted to define a toString method for the Person and Student classes that would override the toString method of the parent Object class. Rather than writing methods that would be similar to the identity methods already existing in the Person and Teacher classes, let’s simply rename those identity methods to toString:


public class Person{
    ...

  public String toString(){
    return "person(" + firstName + "," + lastName + "," + age + ")";
  }

    ...
}

class Teacher extends Person {
  int section;



  public String toString(){
    return "teacher(" + super.toString() + "," + section + ")";    
  }    
}

Using the same test program as before, the results are as follows:

teacher(person(Lucile,Dumas,56),61)
person(Jean,Dupont,30)

3.3. Internal classes

A class can contain the definition of another class. Consider the following example:

// imported classes
import java.io.*;

public class test1{

    // inner class
    private class article{
        // defining the structure
        private String code;
        private String name;
        private double price;
        private int currentStock;
        private int minimumStock;

    // constructor
    public Article(String code, String name, double price, int currentStock, int minimumStock){
        // Initialize attributes
      this.code = code;
      this.name = name;
      this.price = price;
      this.currentStock = currentStock;
      this.minStock = minStock;
    }//constructor

    //toString
    public String toString(){
        return "item(" + code + "," + name + "," + price + "," + currentStock + "," + minimumStock + ")";
    }//toString
  }//Article class

  // local data
  private Article art = null;

  // constructor
  public test1(String code, String name, double price, int currentStock, int minimumStock){
      // attribute definition
    art = new article(code, name, price, currentStock, minimumStock);
  }//test1

  // accessor
  public Article getArticle(){
      return art;
  }//getArticle

    public static void main(String arg[]){
      // Create a test1 instance
      test1 t1 = new test1("a100", "bike", 1000, 10, 5);
    // display test1.art
    System.out.println("art="+t1.getArticle());
  }//main

}// end of class

The test1 class contains the definition of another class, the article class. We say that article is an inner class of the test1 class. This can be useful when the inner class is only needed within the class that contains it. When compiling the test1.java source code above, we get two .class files:

E:\data\serge\JAVA\classes\interne>dir
05/06/2002  17:26                1 362 test1.java
05/06/2002  17:26                  941 test1$article.class
05/06/2002  17:26                1,020 test1.class

A test1$article.class file was generated for the article class, which is internal to the test1 class. If we run the program above, we get the following results:

E:\data\serge\JAVA\classes\interne>java test1
art=article(a100,velo,1000.0,10,5)

3.4. Interfaces

An interface is a set of method or property prototypes that forms a contract. A class that decides to implement an interface commits to providing an implementation of all the methods defined in the interface. The compiler verifies this implementation.

Here is an example of the definition of the java.util.Enumeration interface:

Method Summary
boolean
hasMoreElements()
          Checks if this enumeration contains more elements.
Object
nextElement()
          Returns the next element of this enumeration if this enumeration object has at least one more element to provide.

Any class implementing this interface will be declared as

public class C : Enumeration{
    ...
    boolean hasMoreElements(){....}
    Object nextElement(){...}
}

The methods hasMoreElements() and nextElement() must be defined in class C.

Consider the following code defining a student class that defines a student's name and their grade in a subject:


    // a Student class
public class Student{
    // public attributes
    public String name;
    public double grade;
    // constructor
    public Student(String name, double grade) {
        name=NAME;
        note=NOTE;
    }//constructor
}//student  

We define a notes class that collects the grades of all students in a subject:


// imported classes
// import student

// notes class
public class grades{

    // attributes
    protected String subject;
    protected Student[] students;

    // constructor
    public notes(String subject, Student[] students) {
        // storing students & subject
        subject = SUBJECT;
        students = students;
    }//grades

    // toString
    public String toString(){
        String value = "subject=" + subject + ", grades=(";
        int i;
        // concatenate all grades
        for (i=0;i<students.length-1;i++){
            value+="["+students[i].name+","+students[i].grade+"],";
        };
        //last grade
        if(students.length!=0){ value+="["+students[i].name+","+students[i].grade+"]";}
        value+=")";
        // end
        return value;
    }//toString
}//class

The subject and students attributes are declared protected so they can be accessed from a derived class. We decide to derive the notes class into a notesStats class that would have two additional attributes: the average and the standard deviation of the grades:


public class notesStats extends notes implements Istats {
    // attributes
    private double _average;
    private double _standardDeviation;

The notesStats class derives from the notes class and implements the following Istats interface:


// an interface
public interface Istats{
    double average();
    double standardDev();
}//

This means that the notesStats class must have two methods named average and standardDeviation with the signatures specified in the Istats interface. The notesStats class is as follows:


// imported classes
// import notes;
// import Istats;
// import Student;

public class notesStats extends notes implements Istats {
    // attributes
    private double _average;
    private double _standardDeviation;

    // constructor
    public notesStats (String SUBJECT, Student[] STUDENTS){
        // Constructing the parent class
        super(SUBJECT, STUDENTS);
        // calculate average of grades
        double sum = 0;
        for (int i = 0; i < students.length; i++) {
            sum += students[i].grade;
        }
        if (students.length != 0) _average = sum / students.length;
        else _average=-1;
        // standard deviation
        double sum_squares = 0;
        for (int i=0;i<students.length;i++){
            squares += Math.pow((students[i].score - _average), 2);
        }//for
        if(students.length!=0) _standardDeviation=Math.sqrt(squares/students.length);
        else standardDeviation=-1;
    }//constructor

    
    // ToString
    public String toString(){
        return super.toString() + ",average=" + _average + ",standard deviation=" + _standardDev;
    }//ToString

    // methods of the Istats interface
    public double average(){
        // returns the average of the scores
        return _average;
    }//average
    public double standardDev(){
        // returns the standard deviation
        return _standardDev;
    }//standard deviation
}//class

The mean _mean and standard deviation _standardDev are calculated when the object is created. Therefore, the mean and standardDev methods simply return the values of the _mean and _standardDev attributes. Both methods return -1 if the student array is empty.

The following test class:


// imported classes
// import student;
// import Istats;
// import grades;
// import notesStats;

// test class
public class test{
    public static void main(String[] args){
        // some students & grades
        Student[] STUDENTS = new Student[] { new Student("Paul", 14), new Student("Nicole", 16), new Student("Jacques", 18)};
        // which we store in a grades object
        grades English = new grades("English", STUDENTS);
        // and display
        System.out.println("" + English);
        // same with average and standard deviation
        english = new notesStats("english", STUDENTS);
        System.out.println("" + English);
    }//main
}//class

returns the results:


subject=English, grades=([Paul,14.0],[Nicole,16.0],[Jacques,18.0])
subject=English, grades=([Paul,14.0],[Nicole,16.0],[Jacques,18.0]),average=16.0,standard deviation=1.632993161855452

The different classes in this example are all contained in separate source files:

E:\data\serge\JAVA\interfaces\notes>dir
06/06/2002  14:06                  707 notes.java
06/06/2002  14:06                  878 notes.class
06/06/2002  14:07                1 160 notesStats.java
06/06/2002  14:02                  101 Istats.java
06/06/2002  14:02                  138 Istats.class
06/06/2002  14:05                  247 student.java
06/06/2002  14:05                  309 student.class
06/06/2002  14:07                1,103 notesStats.class
06/06/2002  14:10                  597 test.java
06/06/2002  14:10                  931 test.class

The notesStats class could very well have implemented the average and standardDev methods on its own without indicating that it implemented the Istats interface. So what is the point of interfaces? It is this: a function can accept as a parameter a value of type I. Any object of class C that implements interface I can then be a parameter of that function. Consider the following interface:


// an interface Iexample
public interface Iexample{
    int add(int i, int j);
    int subtract(int i, int j);
}//interface

The Iexample interface defines two methods: add and subtract. The following classes, class1 and class2, implement this interface.


// imported classes
// import Iexample;

public class class1 implements Iexample{
public int add(int a, int b){
        return a + b + 10;
    }
    public int subtract(int a, int b){
        return a - b + 20;
    }
}//class

// imported classes
// import IExample;

public class Class2 implements IExample{
    public int add(int a, int b){
                    return a + b + 100;
                }
    public int subtract(int a, int b){
        return a - b + 200;
    }
}//class

To simplify the example, the classes do nothing other than implement the Iexample interface. Now consider the following example:


// imported classes
// import class1;
// import class2;

// test class
public class test{
    // a static function
    private static void calculate(int i, int j, IExample inter) {
        System.out.println(inter.add(i, j));
        System.out.println(inter.subtract(i, j));
    }//calculate

    // the main function
    public static void main(String[] arg){
        // Create two objects, class1 and class2
        class1 c1 = new class1();
        class2 c2 = new class2();
        // Call the static function calculate
        calculate(4, 3, c1);
        calculate(14, 13, c2);
    }//main
}//class test

The static function calculate accepts an element of type Iexample as a parameter. It can therefore receive either an object of type class1 or of type class2 for this parameter. This is what is done in the main function with the following results:

17
21
127
201

We can see, therefore, that this property is similar to the polymorphism seen with classes. Thus, if a set of classes Ci that are not related to each other through inheritance (and therefore cannot use inheritance-based polymorphism) have a set of methods with the same signature, it may be useful to group these methods into an interface I from which all the relevant classes would inherit. Instances of these classes Ci can then be used as parameters for functions that accept a parameter of type I, i.e., functions that use only the methods of the Ci objects defined in the interface I and not the specific attributes and methods of the various Ci classes.

In the previous example, each class or interface was the subject of a separate source file:

E:\data\serge\JAVA\interfaces\operations>dir
06/06/2002  14:33                  128 Iexample.java
06/06/2002  14:34                  218 class1.java
06/06/2002  14:32                  220 class2.java
06/06/2002  14:33                  144 Iexample.class
06/06/2002  14:34                  325 class1.class
06/06/2002  14:34                  326 class2.class
06/06/2002  14:36                  583 test.java
06/06/2002  14:36                  628 test.class

Finally, note that interface inheritance can be multiple, i.e., one can write

public class DerivedClass extends BaseClass implements i1, i2, ..., in{
...
}

where the ij are interfaces.

3.5. Anonymous classes

In the previous example, the classes class1 and class2 could have been omitted. Consider the following program, which does essentially the same thing as the previous one but without explicitly defining the classes class1 and class2:


// imported classes
// import Iexample;

// test class
public class test2{

  // an inner class
  private static class class3 implements IExample{
        public int add(int a, int b) {
            return a + b + 1000;
        }
        public int subtract(int a, int b) {
            return a - b + 2000;
        }
    };//class3 definition

    // a static function
    private static void calculate(int i, int j, IExample inter) {
        System.out.println(inter.add(i, j));
        System.out.println(inter.subtract(i, j));
    }//calculate

    // the main function
    public static void main(String[] arg){
        // create two objects implementing the Iexample interface
        Iexample i1 = new Iexample() {
        public int add(int a, int b){
            return a + b + 10;
        }
        public int subtract(int a, int b){
            return a - b + 20;
        }
    };//definition i1

        IExample i2 = new IExample() {
        public int add(int a, int b) {
            return a + b + 100;
        }
        public int subtract(int a, int b) {
            return a - b + 200;
        }
    };//definition of i2
         // another IExample object
    Iexample i3 = new Class3();

         // calls to the static function calculate
        calculate(4, 3, i1);
        calculate(14, 13, i2);
    calculate(24, 23, i3);
    }//main
}//test class

The key feature is in the code:


        // creation of two objects implementing the Iexample interface
        Iexample i1 = new Iexample() {
        public int add(int a, int b){
            return a + b + 10;
        }
        public int subtract(int a, int b){
            return a - b + 20;
        }
    };//definition of i1

We create an object i1 whose sole purpose is to implement the Iexample interface. This object is of type Iexample. We can therefore create objects of interface type. Many Java class methods return objects of interface type, i.e., objects whose sole purpose is to implement the methods of an interface. To create the object i1, one might be tempted to write:


        IExample i1 = new IExample()

However, an interface cannot be instantiated. Only a class that implements that interface can be instantiated. Here, we define such a class "on the fly" within the body of the definition of the object i1:


        IExample i1 = new IExample() {
        public int add(int a, int b){
            // definition of add
        }
        public int subtract(int a, int b){
            // definition of subtract
        }
    };//definition i1

The meaning of such a statement is analogous to the sequence:

public class test2{
................
// an inner class
private static class class1 implements Iexample{
        public int add(int a, int b){
            // definition of add
        }
        public int subtract(int a, int b){
            // definition of subtract
        }
};//definition of class1
.................
    public static void main(String[] arg){
...........
        Example i1 = new Class1();
}//main
}//class

In the example above, we are indeed instantiating a class, not an interface. A class defined "on the fly" is called an anonymous class. This is a method often used to instantiate objects whose sole purpose is to implement an interface.

Executing the previous program yields the following results:

17
21
127
201
1047
2001

The previous example used anonymous classes to implement an interface. These can also be used to derive classes that do not have parameterized constructors. Consider the following example:

// imported classes
// import Iexample;

class class3 implements Iexample{
    public int add(int a, int b){
        return a + b + 1000;
    }
    public int subtract(int a, int b) {
        return a - b + 2000;
    }
};//class3 definition

public class test4{

    // a static function
    private static void calculate(int i, int j, IExample inter){
        System.out.println(inter.add(i, j));
        System.out.println(inter.subtract(i, j));
    }//calculate

  // main method
    public static void main(String args[]){
      // definition of an anonymous class deriving from class3
    // to redefine subtract
    class3 i1 = new class3() {
        public int add(int a, int b){
          return a + b + 10000;
      }//subtract
    };//i1
         // calls to the static function calculate
        calculate(4, 3, i1);
  }//main
}//class 

Here we have a class classe3 that implements the Iexemple interface. In the main function, we define a variable i1 of a type derived from classe3. This derived class is defined "on the fly" in an anonymous class and overrides the ajouter method of the classe3 class. The syntax is identical to that of the anonymous class implementing an interface. However, here the compiler detects that classe3 is not an interface but a class. To the compiler, this is therefore a class derivation. All methods found within the body of the anonymous class will override the methods of the same name in the base class.

Running the previous program yields the following results:

E:\data\serge\JAVA\classes\anonyme>java test4
10007
2001

3.6. Packages

3.6.1. Creating classes in a package

To print a line to the screen, we use the statement

System.out.println(...)

If we look at the definition of the System class, we find that it is actually called java.lang.System:

Image

Let's check this with an example:


public class test1{
    public static void main(String[] args){
        java.lang.System.out.println("Hello");
    }//main
}//class

Let's compile and run this program:

E:\data\serge\JAVA\classes\packages>javac test1.java

E:\data\serge\JAVA\classes\packages>dir
06/06/2002  15:40                  127 test1.java
06/06/2002  15:40                  410 test1.class

E:\data\serge\JAVA\classes\packages>java test1
Hello

Why, then, can we write


        System.out.println("Hello");

instead of


        java.lang.System.out.println("Hello");

Because, by default, every Java program automatically imports the java.lang package. So it’s as if we had the following statement at the beginning of every program:

import java.lang.*;

What does this statement mean? It provides access to all classes in the java.lang package. The compiler will find the System.class file there, which defines the System class. We don’t yet know where the compiler will find the java.lang package or what a package looks like. We’ll come back to that. To create a class in a package, we write:

package package;
// class definition
...

For this example, let’s create our previously studied Person class within a package. We’ll choose istia.st as the package name. The Person class becomes:


// name of the package in which the Person class will be created
package istia.st;

// Person class
public class Person{
    // last name, first name, age
    private String firstName;
    private String name;
    private int age;
  
    // constructor 1
    public Person(String P, String N, int age) {
        this.firstName = P;
        this.lastName = N;
        this.age = age;
    }

    // toString
    public String toString(){
        return "person(" + firstName + "," + lastName + "," + age + ")";
    }

}//class

This class is compiled and then placed in the istia\st directory of the current directory. Why istia\st? Because the package is named istia.st.

E:\data\serge\JAVA\classes\packages\person>dir
06/06/2002  16:28                  467 person.java
06/06/2002  16:04       <DIR>          istia

E:\data\serge\JAVA\classes\packages\person>dir istia
06/06/2002  16:04       <DIR>          st

E:\data\serge\JAVA\classes\packages\person>dir istia\st
06/06/2002  16:28                  675 person.class

Now let's use the person class in a first test class:


    public class test{
        public static void main(String[] args){
            istia.st.person p1 = new istia.st.person("Jean", "Dupont", 20);
            System.out.println("p1="+p1);
        }//main
    }//test class

Note that the Person class is now prefixed with the name of its package, istia.st. Where will the compiler find the istia.st.Person class? The compiler searches for the classes it needs in a predefined list of directories and in a directory tree starting from the current directory. Here, it will look for the class istia.st.personne in a file named istia\st\personne.class. That is why we placed the file personne.class in the istia\st directory. Let’s compile and then run the test program:

E:\data\serge\JAVA\classes\packages\person>dir
06/06/2002  16:28                  467 person.java
06/06/2002  16:06                  246 test.java
06/06/2002  16:04       <DIR>          istia
06/06/2002  16:06                  738 test.class

E:\data\serge\JAVA\classes\packages\person>java test
p1 = person(Jean, Dupont, 20)

To avoid writing


            istia.st.person p1=new istia.st.person("Jean","Dupont",20);

you can import the istia.st.personne class using an import statement:


import istia.st.personne;

We can then write


            person p1 = new person("Jean", "Dupont", 20);

and the compiler will translate this to


            istia.st.person p1 = new istia.st.person("Jean", "Dupont", 20);

The test program then becomes the following:


// imported namespaces
import istia.st.Person;

public class test2{
    public static void main(String[] args){
        Person p1 = new Person("Jean", "Dupont", 20);
        System.out.println("p1=" + p1);
    }//main
}//class test2

Let's compile and run this new program:

E:\data\serge\JAVA\classes\packages\person>javac test2.java

E:\data\serge\JAVA\classes\packages\person>dir
06/06/2002  16:28                  467 person.java
06/06/2002  16:06                  246 test.java
06/06/2002  16:04       <DIR>          istia
06/06/2002  16:06                  738 test.class
06/06/2002  16:47                  236 test2.java
06/06/2002  16:50                  740 test2.class

E:\data\serge\JAVA\classes\packages\person>java test2
p1=person(Jean,Dupont,20)

We have placed the istia.st package in the current directory. This is not mandatory. Let’s put it in a folder named mesClasses, still in the current directory. Recall that the classes in the istia.st package are located in a folder named istia\st. The directory tree of the current directory is as follows:

E:\data\serge\JAVA\classes\packages\person>dir
06/06/2002  16:28                  467 person.java
06/06/2002  16:06                  246 test.java
06/06/2002  16:06                  738 test.class
06/06/2002  16:47                  236 test2.java
06/06/2002  16:50                  740 test2.class
06/06/2002  16:21       <DIR>          myClasses

E:\data\serge\JAVA\classes\packages\person>dir myClasses
06/06/2002  16:22       <DIR>          istia

E:\data\serge\JAVA\classes\packages\person>dir myClasses\istia
06/06/2002  16:22       <DIR>          st

E:\data\serge\JAVA\classes\packages\person>dir myClasses\istia\st
06/06/2002  16:01                1,153 person.class

Now let's compile the test2.java program again:

E:\data\serge\JAVA\classes\packages\person>javac test2.java
test2.java:2: package istia.st does not exist
import istia.st.personne;

The compiler can no longer find the istia.st package since we moved it. Note that it looks for it because of the import statement. By default, it looks for it in the current directory in a folder called istia\st, which no longer exists. Let’s examine the compiler options:

E:\data\serge\JAVA\classes\packages\person>javac
Usage: javac <options> <source files>
where possible options include:
  -g                        Generate all debugging info
  -g:none                   Generate no debugging info
  -g:{lines,vars,source}    Generate only some debugging information
  -O                        Optimize; may hinder debugging or increase the size of the class file
  -nowarn                   Generate no warnings
  -verbose                  Output messages about what the compiler is doing
  -deprecation              Output source locations where deprecated APIs are used
  -classpath <path>         Specify where to find user class files
  -sourcepath <path>        Specify where to find input source files
  -bootclasspath <path>     Override the location of bootstrap class files
  -extdirs <dirs>           Override the location of installed extensions
  -d <directory>            Specify where to place generated class files
  -encoding <encoding>      Specify the character encoding used by source files
  -source <release>         Provide source compatibility with the specified release
  -target <release>         Generate class files for a specific VM version
  -help                     Print a synopsis of standard options

Here, the -classpath option can be useful. It allows you to tell the compiler where to look for its classes and packages. Let’s try it. Let’s compile by telling the compiler that the istia.st package is now in the mesClasses folder:

E:\data\serge\JAVA\classes\packages\person>javac -classpath myClasses test2.java

E:\data\serge\JAVA\classes\packages\person>dir
06/06/2002  16:47                  236 test2.java
06/06/2002  17:03                  740 test2.class
06/06/2002  16:21       <DIR>          myClasses

This time, the compilation completes without any issues. Let’s run the test2.class program:

E:\data\serge\JAVA\classes\packages\person>java test2
Exception in thread "main" java.lang.NoClassDefFoundError: istia/st/personne
        at test2.main(test2.java:6)

Now it is the Java Virtual Machine's turn to fail to find the class istia/st/personne. It is looking for it in the current directory, whereas it is now in the mesClasses directory. Let's look at the Java Virtual Machine options:

E:\data\serge\JAVA\classes\packages\person>java
Usage: java [-options] class [args...]
           (to execute a class)
   or  java -jar [-options] jarfile [args...]
           (to execute a jar file)

where options include:
    -client       to select the "client" VM
    -server       to select the "server" VM
    -hotspot      is a synonym for the "client" VM  [deprecated]
                  The default VM is client.

    -cp -classpath <directories and zip/jar files separated by ;>
                  set search path for application classes and resources
    -D<name>=<value>
                  set a system property
    -verbose[:class|gc|jni]
                  enable verbose output
    -version      print product version and exit
    -showversion  print product version and continue
    -? -help      print this help message
    -X            print help on non-standard options
    -ea[:<packagename>...|:<classname>]
    -enableassertions[:<packagename>...|:<classname>]
                  enable assertions
    -da[:<packagename>...|:<classname>]
    -disableassertions[:<packagename>...|:<classname>]
                  disable assertions
    -esa | -enable system assertions
                  enable system assertions
    -dsa | -disablesystemassertions
                  disable system assertions

We can see that the JVM also has a classpath option, just like the compiler. Let’s use it to tell the JVM where the istia.st package is located:

E:\data\serge\JAVA\classes\packages\person>java.bat -classpath myClasses test2
Exception in thread "main" java.lang.NoClassDefFoundError: test2

We haven’t made much progress. Now the test2 class itself cannot be found. Here’s why: when the classpath keyword is absent, the current directory is systematically searched for classes, but not when it is present. As a result, the test2.class file located in the current directory is not found. The solution? Add the current directory to the classpath. The current directory is represented by the symbol .

E:\data\serge\JAVA\classes\packages\person>java -classpath myClasses;. test2
p1=person(Jean,Dupont,20)

Why all this complexity? The purpose of packages is to avoid name conflicts between classes. Consider two companies, E1 and E2, that distribute packaged classes in the com.e1 and com.e2 packages, respectively. Suppose a client, C, purchases both sets of classes, in which both companies have defined a class named Person. Client C will reference the Person class from company E1 as com.e1.Person and the one from company E2 as com.e2.Person, thereby avoiding a naming conflict.

3.6.2. Searching for Packages

When we write in a program

import java.util.*;

to access all classes in the java.util package, where is it found? We mentioned that packages are searched for by default in the current directory or in the list of directories specified in the compiler’s or JVM’s classpath option, if that option is present. They are also searched for in the lib directories of the JDK installation directory. Consider this directory:

Image

In this example, the jdk14\lib and jdk14\jre\lib directories will be searched for either .class files or .jar or .zip files, which are class archives. For example, let’s search for .jar files located in the jdk14 directory mentioned above:

Image

There are several dozen of them. A .jar file can be opened with the WinZip utility. Let’s open the rt.jar file above (rt = RunTime). It contains several hundred .class files, including those belonging to the java.util package:

Image

A simple way to manage packages is to place them in the <jdk>\jre\lib directory, where <jdk> is the JDK installation directory. Generally, a package contains several classes, and it is convenient to bundle them into a single .jar file (JAR = Java ARchive file). The jar.exe executable is located in the <jdk>\bin folder:

E:\data\serge\JAVA\classes\packages\person>dir "e:\program files\jdk14\bin\jar.exe"
02/07/2002  12:52               28,752 jar.exe

Help on using the jar program can be obtained by calling it without any parameters:

E:\data\serge\JAVA\classes\packages\person>"e:\program files\jdk14\bin\jar.exe"
Syntax: jar {ctxu}[vfm0M] [jar-file] [manifest-file] [-C directory] files ...
Options:
    -c  create a new archive file
    -t  generate the archive's table of contents
    -x  extract specified files (or all files) from the archive
    -u  update the existing archive file
    -v  generate verbose information on standard output
    -f  specify the archive file name
    -m  include manifest information from the specified manifest file
    -0  store only; do not use ZIP compression
    -M  do not create a manifest file for the entries
    -i  generate an index for the specified JAR files
    -C  change to the specified directory and include the following file
If a directory is specified, it is processed recursively.
The names of the manifest and archive files must be specified
in the order of the ''m'' and ''f'' flags.

Example 1: to archive two class files into the classes.jar archive file:
       jar cvf classes.jar Foo.class Bar.class
Example 2: use the existing manifest file "mymanifest" to archive all files from the

           foo/ directory into "classes.jar":
       jar cvfm classes.jar monmanifest -C foo/ .

Let's return to the *person.class class created earlier in the *istia.st package:

E:\data\serge\JAVA\classes\packages\person>dir
06/06/2002  16:28                  467 personne.java
06/06/2002  17:36                  195 test.java
06/06/2002  4:04 PM       <DIR>          istia
06/06/2002  16:06                  738 test.class
06/06/2002  16:47                  236 test2.java
06/06/2002  18:15                  740 test2.class

E:\data\serge\JAVA\classes\packages\person>dir istia
06/06/2002  16:04       <DIR>          st

E:\data\serge\JAVA\classes\packages\person>dir istia\st
06/06/2002  16:28                  675 person.class

Let's create a file named istia.st.jar that archives all the classes in the istia.st package, i.e., all the classes in the istia\st directory tree shown above:

E:\data\serge\JAVA\classes\packages\person>"e:\program files\jdk14\bin\jar" cvf istia.st.jar istia\st\*

E:\data\serge\JAVA\classes\packages\person>dir
06/06/2002  16:28                  467 personne.java
06/06/2002  17:36                  195 test.java
06/06/2002  16:04       <DIR>          istia
06/06/2002  16:06                  738 test.class
06/06/2002  16:47                  236 test2.java
06/06/2002  18:15                  740 test2.class
06/06/2002  18:08                  874 istia.st.jar

Let's examine the contents of the istia.st.jar file using WinZip:

Image

Let’s place the istia.st.jar file in the <jdk>\jre\lib\perso directory:

E:\data\serge\JAVA\classes\packages\person>dir "e:\program files\jdk14\jre\lib\perso"
06/06/2002  18:08                  874 istia.st.jar

Now let’s compile the test2.java program and then run it:

E:\data\serge\JAVA\classes\packages\person>javac -classpath istia.st.jar test2.java

E:\data\serge\JAVA\classes\packages\person>java -classpath istia.st.jar;. test2
p1=person(Jean,Dupont,20)

Note that we only had to specify the name of the archive to search without having to explicitly state its location. All directories in the <jdk>\jre\lib tree are searched to find the requested .jar file.

3.7. The tax calculation Example

We revisit the tax calculation already covered in the previous chapter and process it using a class. Let’s recall the problem:

We consider the simplified case of a taxpayer who has only their salary to report:

  • we calculate the employee’s number of tax brackets nbParts = nbEnfants/2 + 1 if they are unmarried, nbEnfants/2 + 2 if they are married, where nbEnfants is the number of children.
  • If they have at least three children, they receive an additional half-share
  • We calculate their taxable income R = 0.72 * S, where S is their annual salary
  • We calculate their family coefficient QF = R / nbParts
  • we calculate his tax I. Consider the following table:
12620.0
0
0
13,190
0.05
631
15,640
0.1
1,290.5
24,740
0.15
2,072.5
31,810
0.2
3,309.5
39,970
0.25
4,900
48,360
0.3
6,898.5
55,790
0.35
9,316.5
92,970
0.4
12,106
127,860
0.45
16,754.5
151,250
0.50
23,147.5
172,040
0.55
30,710
195,000
0.60
39,312
0
0.65
49,062

Each row has 3 fields. To calculate tax I, find the first row where QF <= field1. For example, if QF = 23,000, the row found will be

    24740        0.15        2072.5

Tax I is then equal to 0.15*R - 2072.5*nbParts. If QF is such that the condition QF<=field1 is never met, then the coefficients from the last row are used. Here:

    0                0.65        49062

which gives tax I = 0.65*R - 49062*nbParts.

The impots class will be defined as follows:


// creation of a tax class

public class taxes{

    // the data needed to calculate the tax
    // comes from an external source

    private double[] limits, taxRate, taxN;

    // constructor
    public Taxes(double[] LIMITS, double[] COEFFR, double[] COEFFN) throws Exception{
        // we check that the 3 arrays are the same size
        boolean OK = LIMITS.length == COEFFR.length && LIMITS.length == COEFFN.length;
        if (!OK) throw new Exception("The three arrays provided do not have the same size("+
                                LIMITES.length + "," + COEFFR.length + "," + COEFFN.length + ""));
        // All good
        this.limits = LIMITS;
        this.coeffR = COEFFR;
        this.coeffN = COEFFN;
    }//constructor

    // tax calculation
    public long calculate(boolean married, int numChildren, int salary){
        // calculate the number of shares
        double nbParts;
        if (married) nbParts = (double)nbChildren / 2 + 2;
        else nbParts = (double)nbChildren / 2 + 1;
        if (numberOfChildren >= 3) numberOfShares += 0.5;
        // Calculate taxable income & family quotient
        double income = 0.72 * salary;
        double QF = income / nbParts;
        // calculate tax
        limits[limits.length-1] = QF + 1;
        int i = 0;
        while(QF > limits[i]) i++;
        // return result
        return (long)(income * coeffR[i] - numShares * coeffN[i]);
    }//calculate
}//class

A tax object is created with the data needed to calculate a taxpayer's tax. This is the stable part of the object. Once this object is created, its calculate method can be called repeatedly to calculate the taxpayer's tax based on their marital status (married or not), number of children, and annual salary.

A test program might look like this:


//imported classes
// import impots;
import java.io.*;

    public class test
    {
        public static void main(String[] arg) throws IOException
        {
            // interactive tax calculation program
            // the user enters three pieces of data via the keyboard: married, number of children, salary
            // the program then displays the tax due

            final String syntax = "syntax: married numberOfChildren salary\n"
                            +"married: o for married, n for unmarried\n"
                            +"nbChildren: number of children\n"
                            +"salary: annual salary in F";

            // data arrays needed to calculate the tax
            double[] limits = new double[] {12620, 13190, 15640, 24740, 31810, 39970, 48360, 55790, 92970, 127860, 151250, 172040, 195000, 0};
            double[] coeffR = new double[] {0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65};
            double[] coeffN = new double[] {0, 631.12905, 2072.5, 3309.5, 4900.68985, 9316.5, 12106.167545, 23147.5, 30710.39312, 49062};

      // Create a read stream
      BufferedReader IN = new BufferedReader(new InputStreamReader(System.in));
            // Create a tax object
            taxes taxObject = null;
            try{
                taxObject = new TaxObject(limits, taxRate, taxN);
            } catch (Exception ex) {
                System.err.println("The following error occurred: " + ex.getMessage());
                System.exit(1);
            }//try-catch

            // infinite loop
            while(true){
                // Request tax calculation parameters
                System.out.print("Tax calculation parameters in the format: married, number of children, salary, or 'nothing' to stop:");
                String parameters = IN.readLine().trim();
                // anything to do?
                if(parameters == null || parameters.equals("")) break;
                // Check the number of arguments in the entered line
                String[] args = parameters.split("\\s+");
                int numParameters = args.length;
                if (numberOfParameters != 3) {
                    System.err.println(syntax);
                    continue;
                }//if
                // Checking parameter validity
                // groom
                String married = args[0].toLowerCase();
                if (!married.equals("o") && !married.equals("n")){
                    System.err.println(syntax + "\nIncorrect 'married' argument: enter 'o' or 'n'");
                    continue;
                }//if
                // nbEnfants
                int nbEnfants = 0;
                try{
                    nbChildren = Integer.parseInt(args[1]);
                    if(nbChildren < 0) throw new Exception();
                } catch (Exception ex) {
                    System.err.println(syntax + "\nInvalid numberOfChildren argument: enter a positive integer or zero");
                    continue;
                }//if
                // salary
                int salary = 0;
                try{
                    salary = Integer.parseInt(args[2]);
                    if(salary < 0) throw new Exception();
                } catch (Exception ex) {
                    System.err.println(syntax + "\nInvalid salary argument: enter a positive integer or zero");
                    continue;
                }//if
                // parameters are correct - calculate the tax
                System.out.println("tax=" + objTax.calculate(married.equals("o"), nbChildren, salary) + " F");
                // next taxpayer
            }//while
        }//main
    }//class

Here is an example of the previous program in action:

E:\data\serge\MSNET\c#\impots\3>java test

Tax calculation parameters in the format married nbChildren salary or nothing to stop:q s d
syntax: married nbChildren salary
married: o for married, n for unmarried
nbChildren: number of children
salary: annual salary in F
Incorrect married argument: enter o or n

Tax calculation parameters in the format married numberOfChildren salary or nothing to stop: o s d
syntax: married nbChildren salary
married: o for married, n for unmarried
nbChildren: number of children
salary: annual salary in F
Invalid nbChildren argument: enter a positive integer or zero

Tax calculation parameters in the format married nbChildren salary or nothing to stop: o 2 d
syntax: married nbChildren salary
married: o for married, n for unmarried
nbChildren: number of children
salary: annual salary in F
Invalid salary argument: enter a positive integer or zero

Tax calculation parameters in the format married numChildren salary or nothing to stop: q s d f
syntax: married nbChildren salary
married: o for married, n for unmarried
nbChildren: number of children
salary: annual salary in F

Tax calculation parameters in married format: nbChildren salary or nothing to stop: o 2 200000
tax=22,504 F

Tax calculation parameters for married individuals: number of children, salary, or nothing to stop: