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(){        // m3 method
        
    }
    type4 m4(){        // m4 method
        
    }
    
}

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 personne{
  // attributes
  private String prenom;
  private String nom;
  private int age;
  
  // method
  public void initialise(String P, String N, int age){
    this.prenom=P;
    this.nom=N;
    this.age=age;
  }
 
  // method
  public void identifie(){
    System.out.println(prenom+","+nom+","+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 nomClasse{
    private  donnée ou méthode privée
    public  donnée ou méthode publique
    protected  donnée ou méthode protégée
}

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 personne{
  // attributes
  private String prenom;
  private String nom;
  private int age;
  
  // method
  public void initialise(String P, String N, int age){
    this.prenom=P;
    this.nom=N;
    this.age=age;
  }
 
  // method
  public void identifie(){
    System.out.println(prenom+","+nom+","+age);
  }
}

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

personne p1;
p1.prenom="Jean";
p1.nom="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:

personne p1;
p1.initialise("Jean","Dupont",30);

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

3.1.4. The new operator

The sequence of instructions

personne p1;
p1.initialise("Jean","Dupont",30);

is incorrect. The statement

    personne 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:

personne 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.initialise("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:

personne p1=new personne();

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.initialise("Jean","Dupont",30);

is valid.

3.1.5. The keyword this

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


public void initialise(String P, String N, int age){
    this.prenom=P;
    this.nom=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.initialise("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 initialise(String P, String N, int age){
   prenom=P;
   nom=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[]){
    personne p1=new personne();
    p1.initialise("Jean","Dupont",30);
    p1.identifie();
  }
}

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

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

E:\data\serge\JAVA\BASES\OBJETS\2>dir
10/06/2002  09:21                  473 personne.java
10/06/2002  09:22                  835 personne.class
10/06/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
10/06/2002  09:21                  473 personne.java
10/06/2002  09:22                  835 personne.class
10/06/2002  09:23                  165 test1.java
10/06/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 personne;

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 personne;
               ^
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:

// classes importées
// import personne;

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 personne{
    // attributes
  private String prenom;    // first name
  private String nom;            // its name
  private int age;                // his age

    // method
  public void initialise(String P, String N, int age){
    this.prenom=P;
    this.nom=N;
    this.age=age;
  }//initialize

    // method
  public void identifie(){
    System.out.println(prenom+","+nom+","+age);
  }//identifies
}//class
public class test2{
  public static void main(String arg[]){
    personne p1=new personne();
    p1.initialise("Jean","Dupont",30);
    p1.identifie();
  }
}

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
10/06/2002  09:36                  633 test2.java
10/06/2002  09:41                  832 personne.class
10/06/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 initialise(personne P){
    prenom=P.prenom;
    nom=P.nom;
    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 nobody;
import java.io.*;
 
public class test1{
  public static void main(String arg[]){
    personne p1=new personne();
    p1.initialise("Jean","Dupont",30);
    System.out.print("p1=");
    p1.identifie();
    personne p2=new personne();
    p2.initialise(p1);
    System.out.print("p2=");
    p2.identifie();
  }
}

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:


        classe objet =new classe(arg1,arg2, ... argn);

or


        classe objet;

        objet=new classe(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:

    personne p1;
    p1=new personne();

Let's create two constructors for our Person class:


public class personne{
  // attributes
  private String prenom;
  private String nom;
  private int age;
  
  // manufacturers
  public personne(String P, String N, int age){
    initialise(P,N,age);
  }
  public personne(personne P){
    initialise(P);
  }
 
  // method
  public void initialise(String P, String N, int age){
    this.prenom=P;
    this.nom=N;
    this.age=age;
  }
  public void initialise(personne P){
    this.prenom=P.prenom;
    this.nom=P.nom;
    this.age=P.age;
  }
 
  // method
  public void identifie(){
    System.out.println(prenom+","+nom+","+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 nobody;
import java.io.*;
 
public class test1{
  public static void main(String arg[]){
    personne p1=new personne("Jean","Dupont",30);
    System.out.print("p1=");
    p1.identifie();
    personne p2=new personne(p1);
    System.out.print("p2=");
    p2.identifie();
  }
}

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 nobody;
import java.io.*;
 
public class test1{
  public static void main(String arg[]){
    // p1
    personne p1=new personne("Jean","Dupont",30);
    System.out.print("p1=");  p1.identifie();
    // p2 references the same object as p1
    personne p2=p1;
    System.out.print("p2="); p2.identifie();
    // p3 references an object that will be a copy of the object referenced by p1
    personne p3=new personne(p1);
    System.out.print("p3=");  p3.identifie();
    // change the state of the object referenced by p1
    p1.initialise("Micheline","Benoît",67);
    System.out.print("p1=");  p1.identifie();
    // as p2=p1, the object referenced by p2 must have changed state
    System.out.print("p2=");  p2.identifie();
    // as p3 does not reference the same object as p1, the object referenced by p3 must not have changed
    System.out.print("p3=");  p3.identifie();
  }
}

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

personne p1=new personne("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:

personne 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:

personne p3=new personne(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 nobody;
 
public class test1{
  public static void main(String arg[]){
    new personne(new personne("Jean","Dupont",30)).identifie();
  }
}

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


// manufacturers
  public personne(String P, String N, int age){
    System.out.println("Constructeur personne(String, String, int)");
    initialise(P,N,age);
  }
  public personne(personne P){
    System.out.println("Constructeur personne(personne)");
    initialise(P);
  }

We get the following results:

Constructeur personne(String, String, int)
Constructeur personne(personne)
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 personne{
  private String prenom;
  private String nom;
  private int age;
  
  public personne(String P, String N, int age){
    this.prenom=P;
    this.nom=N;
    this.age=age;
  }
 
  public personne(personne P){
    this.prenom=P.prenom;
    this.nom=P.nom;
    this.age=P.age;
  }
 
  public void identifie(){
    System.out.println(prenom+","+nom+","+age);
  }
 
  // accessors
  public String getPrenom(){
    return prenom;
  }
  public String getNom(){
    return nom;
  }
  public int getAge(){
    return age;
  }
 
  //modifiers
  public void setPrenom(String P){
    this.prenom=P;
  }
  public void setNom(String N){
    this.nom=N;
  }
  public void setAge(int age){
    this.age=age;
  }
}

We test the new class with the following program:


// import nobody;
 
public class test1{
  public static void main(String[] arg){
    personne P=new personne("Jean","Michelin",34);
    System.out.println("P=("+P.getPrenom()+","+P.getNom()+","+P.getAge()+")");
    P.setAge(56);
    System.out.println("P=("+P.getPrenom()+","+P.getNom()+","+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 nbPersonnes;        // 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 getNbPersonnes(){
    return nbPersonnes;
}

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

The Person class becomes the following:


public class personne{
  
  // class attribute
  private static long nbPersonnes=0;
 
  // object attributes

 
  // manufacturers
  public personne(String P, String N, int age){
    initialise(P,N,age);
    nbPersonnes++;
  }
  public personne(personne P){
    initialise(P);
    nbPersonnes++;
  }
 
  // method

 
  // class method
  public static long getNbPersonnes(){
    return nbPersonnes;
  }
 
}// class

With the following program:


// import nobody;
 
public class test1{
  public static void main(String arg[]){
    personne p1=new personne("Jean","Dupont",30);
    personne p2=new personne(p1);
    new personne(p1);
    System.out.println("Nombre de personnes créées : "+personne.getNbPersonnes());
  }// hand
}//test1

We get the following results:

    Nombre de personnes créées : 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 nobody;
 
public class test1{
  public static void main(String arg[]){
    personne p1=new personne("Jean","Dupont",30);
    System.out.print("Paramètre effectif avant modification : ");
    p1.identifie();
    modifie(p1);
    System.out.print("Paramètre effectif après modification : ");
    p1.identifie();
  }// hand
 
  private static void modifie(personne P){
    System.out.print("Paramètre formel avant modification : ");
    P.identifie();
    P.initialise("Sylvie","Vartan",52);
    System.out.print("Paramètre formel après modification : ");
    P.identifie();
  }// modify
}// 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:

Constructeur personne(String, String, int)
Paramètre effectif avant modification : Jean,Dupont,30
Paramètre formel avant modification : Jean,Dupont,30
Paramètre formel après modification : Sylvie,Vartan,52
Paramètre effectif après 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 entieres{
  private int valeur;
 
  public entieres(int valeur){
    this.valeur=valeur;
  }
 
  public void setValue(int valeur){
    this.valeur=valeur;
  }
 
  public int getValue(){
    return valeur;
  }
}

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 integer;
 
public class test2{
  public static void main(String[] arg){
    entieres I=new entieres(12);
    System.out.println("I="+I.getValue());
    change(I);
    System.out.println("I="+I.getValue());
  }
  private static void change(entieres entier){
    entier.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 nobody;
 
public class test1{
  public static void main(String arg[]){
    personne[] amis=new personne[3];
    System.out.println("----------------");
    amis[0]=new personne("Jean","Dupont",30);
    amis[1]=new personne("Sylvie","Vartan",52);
    amis[2]=new personne("Neil","Armstrong",66);
    int i;
    for(i=0;i<amis.length;i++)
      amis[i].identifie();
  }
}

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:

----------------
Constructeur personne(String, String, int)
Constructeur personne(String, String, int)
Constructeur personne(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 enseignant extends personne

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 enseignant extends personne{
// attributes
  private int section;
 
// manufacturer
  public enseignant(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 personne{
  private String prenom;
  private String nom;
  private int age;
  
  public personne(String P, String N, int age){
    this.prenom=P;
    this.nom=N;
    this.age=age;
  }
 
  public personne(personne P){
    this.prenom=P.prenom;
    this.nom=P.nom;
    this.age=P.age;
  }
 
  public String identite(){
    return "personne("+prenom+","+nom+","+age+")";
  }
 
  // accessors
  public String getPrenom(){
    return prenom;
  }
  public String getNom(){
    return nom;
  }
  public int getAge(){
    return age;
  }
 
  //modifiers
  public void setPrenom(String P){
    this.prenom=P;
  }
  public void setNom(String N){
    this.nom=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:


// constructeur
  public enseignant(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:


// constructeur
  public enseignant(String P, String N, int age,int section){
    this.prenom=P;
        this.nom=N
        this.age=age
      this.section=section;
  }

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 nobody;
// import teacher;
 
public class test1{
  public static void main(String arg[]){
    System.out.println(new enseignant("Jean","Dupont",30,27).identite());
  }
}

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
10/06/2002  10:00                  765 personne.java
10/06/2002  10:00                  212 enseignant.java
10/06/2002  10:01                  192 test1.java

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

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

The test1.class file is executed:

E:\data\serge\JAVA\BASES\OBJETS\4>java test1
personne(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 enseignant extends personne{
  int section;
 
  public enseignant(String P, String N, int age,int section){
    super(P,N,age);
    this.section=section;
  }
 
  public String identite(){
    return "enseignant("+super.identite()+","+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 nobody;
// import teacher;
 
public class test1{
  public static void main(String arg[]){
    System.out.println(new enseignant("Jean","Dupont",30,27).identite());
  }
}

The results obtained this time are as follows:

    enseignant(personne(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=Oj avec 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 affiche(Object obj){
        .
    }

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

    public class personne

we are implicitly writing:

    public class personne extends Object

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

    enseignant e;
    affiche(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 affiche(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:

    enseignant e=new enseignant(...);
    affiche(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 nobody;
// import teacher;
 
public class test1{
  public static void main(String arg[]){
    enseignant e=new enseignant("Lucile","Dumas",56,61);
    affiche(e);
    personne p=new personne("Jean","Dupont",30);
    affiche(p);
  }
  public static void affiche(Object obj){
    System.out.println(obj.toString());
  }
}

The results are as follows:

enseignant@1ee789
personne@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 personne{
    ...
 
  public String toString(){
    return "personne("+prenom+","+nom+","+age+")";
  }
 
    ...
}
 
class enseignant extends personne{
  int section;
 

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

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

enseignant(personne(Lucile,Dumas,56),61)
personne(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{

    // internal class
    private class article{
                // we define the structure
        private String code;
        private String nom;
        private double prix;
        private int stockActuel;
        private int stockMinimum;

        // manufacturer
    public article(String code, String nom, double prix, int stockActuel, int stockMinimum){
                // attribute initialization
      this.code=code;
      this.nom=nom;
      this.prix=prix;
      this.stockActuel=stockActuel;
      this.stockMinimum=stockMinimum;
    }//manufacturer

    //toString
    public String toString(){
        return "article("+code+","+nom+","+prix+","+stockActuel+","+stockMinimum+")";
    }//toString
  }//item class

    // local data
  private article art=null;

    // manufacturer
  public test1(String code, String nom, double prix, int stockActuel, int stockMinimum){
            // attribute definition
    art=new article(code, nom, prix, stockActuel,stockMinimum);
  }//test1

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

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

}// fin 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 élève{
        // public attributes
    public String nom;
    public double note;
        // manufacturer
    public élève(String NOM, double NOTE){
        nom=NOM;
        note=NOTE;
    }//manufacturer
}//student  

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


// imported classes
// student import
 
// class notes
public class notes{
 
        // attributes
    protected String matière;
    protected élève[] élèves;
 
        // manufacturer
    public notes (String MATIERE, élève[] ELEVES){
                // student & subject memorization
        matière=MATIERE;
        élèves=ELEVES;
    }//notes
 
    // toString
    public String toString(){
        String valeur="matière="+matière +", notes=(";
        int i;
                // concatenate all the notes
        for (i=0;i<élèves.length-1;i++){
            valeur+="["+élèves[i].nom+","+élèves[i].note+"],";
        };
                //final note
        if(élèves.length!=0){ valeur+="["+élèves[i].nom+","+élèves[i].note+"]";}
        valeur+=")";
                // end
        return valeur;
    }//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 _moyenne;
    private double _écartType;

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


// an interface
public interface Istats{
    double moyenne();
    double écartType();
}//

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;
// student import;
 
public class notesStats extends notes implements Istats {
        // attributes
    private double _moyenne;
    private double _écartType;
 
        // manufacturer
    public notesStats (String MATIERE, élève[] ELEVES){
                // parent class construction
        super(MATIERE,ELEVES);
                // average score calculation
        double somme=0;
        for (int i=0;i<élèves.length;i++){
            somme+=élèves[i].note;
        }
        if(élèves.length!=0) _moyenne=somme/élèves.length;
        else _moyenne=-1;
                // standard deviation
        double carrés=0;
        for (int i=0;i<élèves.length;i++){
            carrés+=Math.pow((élèves[i].note-_moyenne),2);
        }//for
        if(élèves.length!=0) _écartType=Math.sqrt(carrés/élèves.length);
        else _écartType=-1;
    }//manufacturer
 
 
        // ToString
    public String toString(){
        return super.toString()+",moyenne="+_moyenne+",écart-type="+_écartType;
    }//ToString
 
        // istats interface methods
    public double moyenne(){
                // makes the average score
        return _moyenne;
    }//average
    public double écartType(){
                // makes the standard deviation
        return _écartType;
    }//écartType
}//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
// student import;
// import Istats;
// import notes;
// import notesStats;
 
// test class
public class test{
    public static void main(String[] args){
        // some students & notes
        élève[] ELEVES=new élève[] { new élève("paul",14),new élève("nicole",16), new élève("jacques",18)};
                // recorded in a notes object
        notes anglais=new notes("anglais",ELEVES);
                // and display
        System.out.println(""+anglais);
                // idem with mean and standard deviation
        anglais=new notesStats("anglais",ELEVES);
        System.out.println(""+anglais);
    }//hand
}//class
 

returns the results:


matière=anglais, notes=([paul,14.0],[nicole,16.0],[jacques,18.0])
matière=anglais, notes=([paul,14.0],[nicole,16.0],[jacques,18.0]),moyenne=16.0,écart-type=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 élève.java
06/06/2002  14:05                  309 élève.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 Iexample interface
public interface Iexemple{
    int ajouter(int i,int j);
    int soustraire(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 classe1 implements Iexemple{
public int ajouter(int a, int b){
        return a+b+10;
    }
    public int soustraire(int a, int b){
        return a-b+20;
    }
}//class

// imported classes
// import Iexample;
 
public class classe2 implements Iexemple{
    public int ajouter(int a, int b){
                    return a+b+100;
                }
    public int soustraire(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 calculer(int i, int j, Iexemple inter){
        System.out.println(inter.ajouter(i,j));
        System.out.println(inter.soustraire(i,j));
    }//calculate
 
        // the main function
    public static void main(String[] arg){
        // creation of two objects class1 and class2
        classe1 c1=new classe1();
        classe2 c2=new classe2();
        // static function calls calculate
        calculer(4,3,c1);
        calculer(14,13,c2);
    }//hand
}//test class

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\opérations>dir
06/06/2002  14:33                  128 Iexemple.java
06/06/2002  14:34                  218 classe1.java
06/06/2002  14:32                  220 classe2.java
06/06/2002  14:33                  144 Iexemple.class
06/06/2002  14:34                  325 classe1.class
06/06/2002  14:34                  326 classe2.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 classeDérivée extends classeDeBase 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 internal class
  private static class classe3 implements Iexemple{
        public int ajouter(int a, int b){
            return a+b+1000;
        }
        public int soustraire(int a, int b){
            return a-b+2000;
        }
    };//definition class3
 
        // a static function
    private static void calculer(int i, int j, Iexemple inter){
        System.out.println(inter.ajouter(i,j));
        System.out.println(inter.soustraire(i,j));
    }//calculate
 
        // the main function
    public static void main(String[] arg){
        // creation of two objects implementing the Iexemple interface
        Iexemple i1=new Iexemple(){
        public int ajouter(int a, int b){
            return a+b+10;
        }
        public int soustraire(int a, int b){
            return a-b+20;
        }
    };//definition i1
 
        Iexemple i2=new Iexemple(){
        public int ajouter(int a, int b){
            return a+b+100;
        }
        public int soustraire(int a, int b){
            return a-b+200;
        }
    };//definition i2
         // another object Iexample
    Iexemple i3=new classe3();
 
         // static function calls calculate
        calculer(4,3,i1);
        calculer(14,13,i2);
    calculer(24,23,i3);
    }//hand
}//test class
 

The key feature is in the code:


                // creation of two objects implementing the Iexemple interface
        Iexemple i1=new Iexemple(){
        public int ajouter(int a, int b){
            return a+b+10;
        }
        public int soustraire(int a, int b){
            return a-b+20;
        }
    };//definition 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:


        Iexemple i1=new Iexemple()

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:


        Iexemple i1=new Iexemple(){
        public int ajouter(int a, int b){
            // définition de ajouter
        }
        public int soustraire(int a, int b){
            // définition de soustraire
        }
    };//définition i1

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

public class test2{
................
// an internal class
private static class classe1 implements Iexemple{
        public int ajouter(int a, int b){
            // definition of add
        }
        public int soustraire(int a, int b){
            // definition of subtract
        }
};//definition class1
.................
    public static void main(String[] arg){
...........
        Iexemple i1=new classe1();
}//hand
}//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 classe3 implements Iexemple{
    public int ajouter(int a, int b){
        return a+b+1000;
    }
    public int soustraire(int a, int b){
        return a-b+2000;
    }
};//definition class3

public class test4{

        // a static function
    private static void calculer(int i, int j, Iexemple inter){
        System.out.println(inter.ajouter(i,j));
        System.out.println(inter.soustraire(i,j));
    }//calculate

    // hand method
    public static void main(String args[]){
            // definition of an anonymized class deriving from class3
        // to redefine subtract
    classe3 i1=new classe3(){
        public int ajouter(int a, int b){
          return a+b+10000;
      }//subtract
    };//i1
                // static function calls calculate
        calculer(4,3,i1);
  }//hand
}//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("Coucou");
    }//hand
}//class

Let's compile and run this program:

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

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

E:\data\serge\JAVA\classes\paquetages>java test1
Coucou

Why, then, can we write


        System.out.println("Coucou");

instead of


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

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 paquetage;
// 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;
 
// class person
public class personne{
    // last name, first name, age
    private String prenom;
    private String nom;
    private int age;
  
    // builder 1
    public personne(String P, String N, int age){
        this.prenom=P;
        this.nom=N;
        this.age=age;
    }
 
    // toString
    public String toString(){
        return "personne("+prenom+","+nom+","+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\paquetages\personne>dir
06/06/2002  16:28                  467 personne.java
06/06/2002  16:04       <DIR>          istia

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

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

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


    public class test{
        public static void main(String[] args){
            istia.st.personne p1=new istia.st.personne("Jean","Dupont",20);
            System.out.println("p1="+p1);
        }//hand
    }//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\paquetages\personne>dir
06/06/2002  16:28                  467 personne.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\paquetages\personne>java test
p1=personne(Jean,Dupont,20)

To avoid writing


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

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


import istia.st.personne;

We can then write


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

and the compiler will translate this to


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

The test program then becomes the following:


// imported namespaces
import istia.st.personne;
 
public class test2{
    public static void main(String[] args){
        personne p1=new personne("Jean","Dupont",20);
        System.out.println("p1="+p1);
    }//hand
}//test2 class

Let's compile and run this new program:

E:\data\serge\JAVA\classes\paquetages\personne>javac test2.java

E:\data\serge\JAVA\classes\paquetages\personne>dir
06/06/2002  16:28                  467 personne.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\paquetages\personne>java test2
p1=personne(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\paquetages\personne>dir
06/06/2002  16:28                  467 personne.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>          mesClasses

E:\data\serge\JAVA\classes\paquetages\personne>dir mesClasses
06/06/2002  16:22       <DIR>          istia

E:\data\serge\JAVA\classes\paquetages\personne>dir mesClasses\istia
06/06/2002  16:22       <DIR>          st

E:\data\serge\JAVA\classes\paquetages\personne>dir mesClasses\istia\st
06/06/2002  16:01                1 153 personne.class

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

E:\data\serge\JAVA\classes\paquetages\personne>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\paquetages\personne>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 info
  -O                        Optimize; may hinder debugging or enlarge 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 location of bootstrap class files
  -extdirs <dirs>           Override location of installed extensions
  -d <directory>            Specify where to place generated class files
  -encoding <encoding>      Specify character encoding used by source files
  -source <release>         Provide source compatibility with specified release
  -target <release>         Generate class files for 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\paquetages\personne>javac -classpath mesClasses test2.java

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

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

E:\data\serge\JAVA\classes\paquetages\personne>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\paquetages\personne>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 | -enablesystemassertions
                  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\paquetages\personne>java.bat -classpath mesClasses 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\paquetages\personne>java -classpath mesClasses;. test2
p1=personne(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\paquetages\personne>dir "e:\program files\jdk14\bin\jar.exe"
07/02/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\paquetages\personne>"e:\program files\jdk14\bin\jar.exe"
Syntaxe : jar {ctxu}[vfm0M] [fichier-jar] [fichier-manifest] [rÚp -C] fichiers ...
Options :
    -c  crÚer un nouveau fichier d'archives
    -t  gÚnÚrer la table des matiÞres du fichier d'archives
    -x  extraire les fichiers nommÚs (ou tous les fichiers) du fichier d'archives
    -u  mettre Ó jour le fichier d'existing archives
    -v  gÚnÚrer des informations verbeuses sur la sortie standard
    -f  spÚcifier le nom du fichier d'archives
    -m  inclure les informations manifest provenant du fichier manifest spÚcifiÚ
    -0  stocker seulement ; ne pas utiliser la compression ZIP
    -M  ne pas crÚer de fichier manifest pour les entrÚes
    -i  gÚnÚrer l'index for jar files spÚcifiÚs
    -C  passer au rÚpertoire spÚcifiÚ et inclure le fichier suivant
Si un rÚpertoire est spÚcifiÚ, il est traitÚ rÚcursivement.
Les noms des fichiers manifest et d'archives must be spÚcifiÚs
dans l'order of ''m'' and ''f'' indicators.

Exemple 1 : pour archiver deux fichiers de classe dans le fichier d'archives classes.jar :
       jar cvf classes.jar Foo.class Bar.class
Exemple 2 : utilisez le fichier manifest existant 'mymanifest'' to archive all files in the

           rÚpertoire foo/ dans '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\paquetages\personne>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

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

E:\data\serge\JAVA\classes\paquetages\personne>dir istia\st
06/06/2002  16:28                  675 personne.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\paquetages\personne>"e:\program files\jdk14\bin\jar" cvf istia.st.jar istia\st\*

E:\data\serge\JAVA\classes\paquetages\personne>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\paquetages\personne>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\paquetages\personne>javac -classpath istia.st.jar test2.java

E:\data\serge\JAVA\classes\paquetages\personne>java -classpath istia.st.jar;. test2
p1=personne(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 an impots class
 
public class impots{
 
    // data required for tax calculation
        // come from an external source
 
    private double[] limites, coeffR, coeffN;
 
        // manufacturer
    public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
                // check that the 3 arrays have the same size
        boolean OK=LIMITES.length==COEFFR.length && LIMITES.length==COEFFN.length;
        if (! OK) throw new Exception ("Les 3 tableaux fournis n'ont pas la même taille("+
                                LIMITES.length+","+COEFFR.length+","+COEFFN.length+")");
        // it's good
        this.limites=LIMITES;
        this.coeffR=COEFFR;
        this.coeffN=COEFFN;
    }//manufacturer
 
        // tAX CALCULATION
    public long calculer(boolean marié, int nbEnfants, int salaire){
                // calculating the number of shares
        double nbParts;
        if (marié) nbParts=(double)nbEnfants/2+2;
        else nbParts=(double)nbEnfants/2+1;
        if (nbEnfants>=3) nbParts+=0.5;
                // calculation of taxable income & family quota
        double revenu=0.72*salaire;
        double QF=revenu/nbParts;
                // tAX CALCULATION
        limites[limites.length-1]=QF+1;
        int i=0;
        while(QF>limites[i]) i++;
        // return result
        return (long)(revenu*coeffR[i]-nbParts*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 calculator
                        // the user enters three data points on the keyboard: married nbEnfants salary
                        // the program then displays the tax payable
 
            final String syntaxe="syntaxe : marié nbEnfants salaire\n"
                            +"marié : o pour marié, n pour non marié\n"
                            +"nbEnfants : nombre d'enfants\n"
                            +"salaire : salaire annuel en F";
 
                        // data tables required for tax calculation
            double[] limites=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,1290.5,2072.5,3309.5,4900,6898.5,9316.5,12106,16754.5,23147.5,30710,39312,49062};
 
            // create a reading flow
      BufferedReader IN=new BufferedReader(new InputStreamReader(System.in));
            // tax object creation
            impots objImpôt=null;
            try{
                objImpôt=new impots(limites,coeffR,coeffN);
            }catch (Exception ex){
                System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
                System.exit(1);
            }//try-catch
 
            // infinite loop
            while(true){
                                // tax calculation parameters are requested
                System.out.print("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :");
                String paramètres=IN.readLine().trim();
                // anything to do?
                if(paramètres==null || paramètres.equals("")) break;
                // check the number of arguments in the input line
                String[] args=paramètres.split("\\s+");
                int nbParamètres=args.length;
                if (nbParamètres!=3){
                    System.err.println(syntaxe);
                    continue;
                }//if
                                // checking the validity of parameters
                // married
                String marié=args[0].toLowerCase();
                if (! marié.equals("o") && ! marié.equals("n")){
                    System.err.println(syntaxe+"\nArgument marié incorrect : tapez o ou n");
                    continue;
                }//if
                                // nbEnfants
                int nbEnfants=0;
                try{
                    nbEnfants=Integer.parseInt(args[1]);
                    if(nbEnfants<0) throw new Exception();
                }catch (Exception ex){
                    System.err.println(syntaxe+"\nArgument nbEnfants incorrect : tapez un entier positif ou nul");
                    continue;
                }//if
                                // salary
                int salaire=0;
                try{
                    salaire=Integer.parseInt(args[2]);
                    if(salaire<0) throw new Exception();
                }catch (Exception ex){
                    System.err.println(syntaxe+"\nArgument salaire incorrect : tapez un entier positif ou nul");
                    continue;
                }//if
                                // parameters are correct - tax is calculated
                System.out.println("impôt="+objImpôt.calculer(marié.equals("o"),nbEnfants,salaire)+" F");
                                // next taxpayer
            }//while
        }//hand
    }//class

Here is an example of the previous program in action:

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

Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :q s d
syntaxe : marié nbEnfants salaire
marié : o pour marié, n pour non marié
nbEnfants : nombre d'enfants
salaire : salaire annuel en F
Argument marié incorrect : tapez o ou n

Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o s d
syntaxe : marié nbEnfants salaire
marié : o pour marié, n pour non marié
nbEnfants : nombre d'enfants
salaire : salaire annuel en F
Argument nbEnfants incorrect : tapez un entier positif ou nul

Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 d
syntaxe : marié nbEnfants salaire
marié : o pour marié, n pour non marié
nbEnfants : nombre d'enfants
salaire : salaire annuel en F
Argument salaire incorrect : tapez un entier positif ou nul

Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :q s d f
syntaxe : marié nbEnfants salaire
marié : o pour marié, n pour non marié
nbEnfants : nombre d'enfants
salaire : salaire annuel en F

Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 200000
impôt=22504 F

Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :