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
are invalid. We must initialize an object of type Person using a public method. This is the role of the initialize method. We write:
The syntax p1.initialize is valid because initialize is public.
3.1.4. The new operator
The sequence of instructions
is incorrect. The statement
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:
where we explicitly indicate with the keyword null that the variable p1 does not yet reference any object.
When we then write
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:
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
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:
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:
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:
We can now run the test1.class file:
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:
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:
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:
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:
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
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:
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:
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:
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:
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:
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:
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:
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:
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.

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:
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:
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:
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:
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
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:
The Object class is the "parent" of all Java classes. So when we write:
we are implicitly writing:
Thus, every Java object contains an *Object* component. Therefore, we can write:
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:
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:
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:
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:
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:
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
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:
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
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:
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:
3.6. Packages
3.6.1. Creating classes in a package
To print a line to the screen, we use the statement
If we look at the definition of the System class, we find that it is actually called java.lang.System:

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

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:

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:

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:

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