Java Object-Oriented Approach
- Declare and instantiate Java objects including nested class objects, and explain objects' lifecycles (including creation, dereferencing by reassignment, and garbage collection)
- Define and use fields and methods, including instance, static and overloaded methods
- Initialize objects and their members using instance and static initialiser statements and constructors
- Understand variable scopes, apply encapsulation and make objects immutable
- Create and use subclasses and superclasses, including abstract classes
- Utilize polymorphism and casting to call methods, differentiate object type versus reference type
- Create and use interfaces, identify functional interfaces, and utilize private, static, and default methods
- Create and use enumerations
Rule(s)
- OO programming relies on the class/instance duality: a class is a data structure plus functions (operations or “methods” in Java), which act on local data (attributes or fields) only; an instance is a class incarnation (a.k.a. “object”) at execution time. In short, a class is a “mold” for to-be-created instances.
- OO programming in Java mainly leads us to reuse existing classes in the JVM or external libraries. In contrast, user-defined classes are built from the
class
keyword when the functionality required by an app. is absent.Example (reuse)
java.time.LocalDate first_of_January_1970 = java.time.LocalDate.EPOCH; // January 1, 1970 java.time.LocalDateTime now = java.time.LocalDateTime.now();
Example (user-defined class) Programmable thermostat app. here…
final class Program { Temperature _target_temperature; java.time.LocalDateTime _time; } public class Programmable_thermostat … { private Program[] _program = new Program[8]; // An attribute named '_program' as an array of 8 values, each having 'Program' as type …
Attributes (a.k.a. fields) and methods (a.k.a. functions or operations) are foundational content of classes Rule(s)
- In OO programming, attributes aim at being hidden (Parnas' information hiding principle) to avoid any direct (unsafe) access from any code outside owners, typically third-party classes. Instead, methods are generally exposed. Only internal (reusable) computations are hidden, leading to inherent (“private”) methods.
- The Smalltalk programming language philosophy is interesting: attributes are by default not accessible while functions are. To create some access (read and/or write), getters and setters are required. Moreover, in Smalltalk, instance attribute names must start with a low-case letter while class attribute (
static
keyword in Java) names must start with a capital letter.Example (Smalltalk)
get_x "return of _x" ^_x. set_x: x "assign _x with x" _x := x.
Rule(s)
- Java supports the encapsulation principle with the
private
andprotected
keywords. There is no reason at all to set attributes to thepublic
visibility except constant attributes (final
keyword).Example
public class My_class { public float a; // To be avoided, absolutely! public final float b = 1963.F; private double _c; … } … My_class mc = new My_class(); mc.a = 1.F; // OK because 'public' // mc.b = 2.F; // Compilation error because 'final' System.out.println(mc.b); // OK because 'public' // mc._c = 3.; // Compilation error because 'private' // System.out.println(mc._c); // Compilation error because 'private'
Rule(s)
- In OO programming, operations (methods in Java) are either visible from outside or for internal use only (
private
modifier).Example
public class My_class { … private void _f() {…}; public void g() {…}; // '_f' is probably called inside 'g' and/or in other methods… } … My_class mc = new My_class(); // mc._f(); // Compilation error because 'private' mc.g(); // OK because 'public'
Rule(s)
- Parameter passing may be ruled by the
final
keyword.Example Parameter_passing.Java.zip
public class Parameter_passing { public int i = 0; // To be avoided! public static void increment(int j) { // 'Integer j' is the same j++; } public static void f(Object o) { o = new Object(); } public static void g(final Parameter_passing x) { x.i++; } public static void main(String[] args) { int j = 0; Parameter_passing.increment(j); // Copy of 'j'... System.out.println(j); // '0' Object o = new Object(), temp = o; Parameter_passing.f(o); // Copy of 'o'... assert (o == temp); // Parameter_passing x = new Parameter_passing(); assert (x.i == 0); Parameter_passing.g(x); assert (x.i == 1); } }
Rule(s)
- As many other languages (C++, JavaScript, Python…), Java allows the possibility of having a variable number of arguments for methods.
Example Covariance.Java.zip
public class Family { java.util.Set<Individual> _members = new java.util.HashSet<>(); … public void births(Individual... children) { for (Individual c : children) { _members.add(c); } } … } … // Usage: family.births(new Individual(6, 12, 1993), new Individual(15, 4, 1996), new Individual(22, 2, 2001)); // Alternative usage: Individual kids[] = {new Individual(6, 12, 1993), new Individual(15, 4, 1996), new Individual(22, 2, 2001)}; family.births(kids);
Rule(s)
native
methods are written in C/C++ and next incorporated into Java.strictfp
methods allow computations (for real numbers) that are independent of real number representation on machines (see also here…).Class attributes and methods
Rule(s)
- Class attributes are identified by the
static
keyword. Instead of instance attributes, class attributes are shared among instances. In other words, one may also consider that class attributes belong to the class itself and the class' instances just get an access.Example
public class Temperature { public static final float Min = -273.15F; // In Celsius … if(_value < Min) … // Or 'Temperature.Min' when one accesses this (public) attribute from outside the 'Temperature' class
Rule(s)
- Class methods are also identified by the
static
keyword. Instead of instance methods, class methods deal with class attributes only.Example
// See inheritance section to understand why both 'final' are here used: final public class Leap_year_UTILITY { final public static boolean Leap_year(final java.util.Calendar date) { return ((date.get(java.util.Calendar.YEAR) % 4 == 0) && (date.get(java.util.Calendar.YEAR) % 100 != 0)) || (date.get(java.util.Calendar.YEAR) % 400 == 0); } }
Static initialiser
Example
public class Control_center { final static Terminal[] _Terminals; … static { // This code is launched before any statement in the 'main' program: int number_of_terminals = …; _Terminals = new Terminal[number_of_terminals]; } …
Bad object-oriented programming
Example Bad_OO.Java.zip
class Cookie { … } public class Bad_OO { public static final java.util.Set<Cookie> Cookie_pack = new java.util.HashSet<>(); public static void Eat_cookie(java.util.Set<Cookie> cookie_pack, Cookie cookie) { cookie_pack.remove(cookie); // It returns 'false' if 'cookie' is not in the pack... } public static void main(String[] args) { // Bad_OO o = new Bad_OO(); Bad_OO.Eat_cookie(Bad_OO.Cookie_pack, new Cookie()); } }
Example
public class Individual { private java.time.LocalDate _birth_date; // Implementation is by definition hidden, i.e., 'private' here public static java.time.LocalDate Now() { return java.time.LocalDate.now(); } public int age() { // Interface is by definition visible, i.e., 'public' here return Now().getYear() - _birth_date.getYear(); } …
Variations on visibility
Rule(s)
- Visibility possibilities are
private
, “package” (default),protected
andpublic
. Note that the “package” visibility is not associated with any keyword.Example Covariance.Java.zip
public class Individual { java.time.LocalDate _birth_date; // "package" visibility … public class Family { // The 'Individual.java' and 'Family.java' files must be in the same directory java.util.Set<Individual> _members = new java.util.HashSet<>(); … public int last_child_birth_year() { int result = 0; for (Individual member : _members) { if (result < member._birth_date.getYear()) { // Access to '_birth_date' succeeds because 'Individual' and 'Family' are in the same package result = member._birth_date.getYear(); // Access to '_birth_date' succeeds because 'Individual' and 'Family' are in the same package } } return result; } }
Rule(s)
- Contrary to C++,
protected
also implies “package”! So,protected
in Java has implications for both descendant classes (inheritance) and classes in the same package, i.e., the same directory.- Contrary to Eiffel,
private
in Java is not “really” private…Example
public class Prisoner { private String _file_number; public boolean equals(Object p) { if(p instanceof Prisoner) { return (this._file_number.equals(((Prisoner)p)._file_number)); // 'this' accesses to '_file_number' of 'p'; this is not really private! } return false; } …
Rule(s)
- Contrary to C++, Java classes have a visibility which is most of the time
public
. At most one class may be declaredpublic
in a file. Other visibility possibilities for classes are “package” andprivate
.Example
// This entire code is included in the 'My_class.java' file class Class_with_package_visibility { // "package" visibility ('My_class' is probably a user class, but other classes in the same package may use it!) … } public class My_class { private class Class_with_private_visibility { // 'private' visibility (such a declaration is allowed inside the user class only!) … } … }
import
clause
Example
package my_code; … public class X { … package my_code.my_extra_code; // Absolute path from 'classpath' OS variable … import my_code.X; // 'import my_code.*;' -> all is imported … public class Y extends X { … // 'X' has been previously imported public class Y extends my_code.X { … // Alternative: no import, absolute path to 'X'
Rule(s)
- The
classpath
OS variable plays a central role. Accordingly, it must be set to values (directory paths); this may occur within UNIX for example in relation with figure above.Example
export classpath = $classpath:/usr/FranckBarbier
Package import using
static
Example (without
static
import)double cosinus = Math.cos(Math.PI); // Cosinus of 180 deg.
Rule(s)
- The
static
import construct allows unqualified access to static members.Example
import static java.lang.Math.*; // 'cos', 'PI' and others… All static members… … double cosinus = cos(PI); // Shorter, instead of 'double cosinus = Math.cos(Math.PI);'
Constructor Rule(s)
- Java supports the notion of block initialization along with that of constructor for a class. The constructor's name is that of the class; it is, by default, a “classical” function without arguments. Contrary to C++, Java has no destructor notion apart from garbage collection, which periodically releases unused (i.e. “no longer pointed”) objects. So, Java releases memory in a deferred way. Namely, collectable objects become free memory in the Java Virtual Machine -JVM-. Garbage collection in Java is associated with the
finalize
method injava.lang.Object
.- The Java compiler (like the C++ compiler) generates a default constructor for a class. However, one often requires constructors with arguments for initializations typically. Any user-defined constructor (with or without arguments) cancels the default one. For this reason, if one intends to keep a no-argument constructor then one needs its re-introduction!
Example
public class Student { { // This code is executed before every constructor: System.out.println("Block initialiser of 'Student'"); } public Student() { … // The no-argument constructor is re-introduced, cancelling the initial compiler's no-argument constructor public Student(String student_id) { this(); // Calling a constructor (the prior one) inside another! … // Additional stuff here… } …
Rule(s)
- Having several constructors in the same class is named constructor overloading. Note that overloading also applies for common methods that have the same name and a variation on their arguments : number and/or types.
Rule(s)
- Inheritance is a foundation of OO programming. The principle behind inheritance is the fact that data structures are extended from existing ones. This amounts to adding attributes (structural inheritance) and/or methods (behavioral inheritance). The way of dealing with inheritance in Java is the use of the
extends
keyword. Note that Java supports single inheritance between classes and multiple inheritance between classes and interfaces, between interfaces as well.java.lang.Object
is the single root of Java inheritance graph, thus leading to an inheritance tree.Example of inheritance tree (in French)
Structural inheritance
Example Polymorphism.Java.zip
abstract public class Compte_bancaire { private int _id; protected float _solde; protected float _cumul_interets; … public class Compte_cheque extends Compte_bancaire { protected float _taux_interet; protected float _seuil; …
Behavioral inheritance
Example Polymorphism.Java.zip
abstract public class Compte_bancaire { … public int id() { // This method is usable by 'Compte_cheque' objects! return _id; } …
Property extension
Example Polymorphism.Java.zip
public class Compte_epargne_logement extends Compte_epargne { public final static float Taux_interet = Livret_A.Taux_interet * 2.F / 3.F; // Fixed rate in the French law public float taux_interet() { return Taux_interet; } …
Inheritance and constructors
Example Polymorphism.Java.zip
abstract public class Compte_epargne extends Compte_bancaire { protected Compte_epargne(int id,float solde) { super(id,solde); } }
Overriding (redefinition)
Example Polymorphism.Java.zip
abstract public class Compte_bancaire { … abstract public float taux_interet(); // Abstract method public void appliquer_taux_interet() { _cumul_interets = _solde * (1.F + (taux_interet() / 100.F)); } } public class Compte_cheque extends Compte_bancaire { … public float taux_interet() { // Abstract nature is removed when overriding return _taux_interet; } public void appliquer_taux_interet() { // Overriding as well if(_solde > _seuil) super.appliquer_taux_interet(); } }
Rule(s)
- Accessing to properties of ancestor classes in Java obeys precise rules.
Example
// Assumption: 'Daughter' inherits from 'Mother', which inherits from 'Grandmother'. One wants in 'Daughter' to access to 'jewel' (not 'private') in 'Mother': super.jewel; // One now wants in 'Daughter' to access to 'other_jewel' (not 'private') in 'Grandmother': // super.super.other_jewel; // Compilation error! Contrary to C++, Java prevents such an access
Rule(s)
- Overriding private methods is impossible in Java.
Example
public class Note { private static int _Increment = 10; private final java.io.StringReader _source; public final static String Default_header = "Note: "; protected StringBuffer _text; private void initialization() { // 'private' prevents overriding -> to allow overriding, possibly use 'protected' instead _text = new StringBuffer(Default_header); } public Note(java.io.StringReader source) { _source = source; initialization(); // Polymorphism does not apply since it is indicated 'private' above } … public class Confidential_note extends Note { public final static String Default_header = "Confidential note: "; // private void initialization() { // Overriding does not work because 'private' in 'Note' // _text = new StringBuffer(Default_header); // } public Confidential_note(java.io.StringReader source) { super(source); } … Note cn = new Confidential_note(new java.io.StringReader("Franck Barbier"));
final
and inheritanceRule(s)
final
declared classes are not inheritable, e.g.,java.lang.Integer
.final
declared methods cannot be redefined.private
methods are implicitlyfinal
.Initialization protocol
Rule(s)
- The inheritance-based chaining of constructors must be under control to avoid undesired side effects.
Example Initialization_protocol.Java.zip
public class A { String a = null; protected void init() { a = "a - A"; } public A() { init(); // Overridable method call in constructor... } } … public class B extends A { String b = null; // Occurs *AFTER* 'init' in 'B'! @Override // Allowed overriding since 'init' is 'protected' in 'A'! protected void init() { // No effect on 'b' attribute because it occurs *BEFORE* 'String b = null;' a = "a - B"; b = "b - B"; } } … public class Main { public static void main(String[] args) { System.out.println(new B().b); // 'null' is displayed... } }
Rule(s)
- Anonymous classes have no name. They are created on the fly. Their main purpose is avoiding excessive inheritance, i.e., systematically creating descendant classes from other classes or interfaces.
- As an illustration,
java.util.TimerTask
is an abstract class. It has a singlerun
method that requires a definition through an inheriting class. Timers accept timer task objects as arguments for later executingrun
at key instants, e.g., each second. To avoid the creation of such a class (that involves the creation of an associated.java
file) for only definingrun
, an anonymous class may be useful.Example
public class Anonymous_class_test { public static void main(String[] args) { java.util.TimerTask timer_task = new java.util.TimerTask() { @Override public void run() { System.out.println("A behavior…"); } }; System.out.println(timer_task.getClass().getName()); // 'Anonymous_class_test$1' is displayed! …
Rule(s)
Anonymous_class_test$1
is the registered name of the anonymous class in the Java Virtual Machine -JVM-. The excessive use of anonymous classes leads to code, which might be maintainable with much difficulty.Double brace initialization
Rule(s)
- Anonymous classes may be created from double brace initialization as well.
Example
// Creation of an anonymous inner class, which extends 'java.util.HashSet': java.util.Set<Mammutidae> presidents = new java.util.HashSet<>() { { // <- Block initialiser add(new Mammutidae("Macron")); add(new Mammutidae("Poutine")); add(new Mammutidae("Trump")); } }; System.out.println(presidents.getClass().getName()); // 'Main$1' is displayed!
Rule(s)
- Polymorphism is intimately associated with inheritance. Polymorphism relies on the testing of objects' type at run-time to choose the “right” code.
Example Polymorphism.Java.zip
Compte_cheque cc = new Compte_cheque(1963,10500.F,2.F,10000.F); Compte_bancaire cb = cc; // 'cb' points to 'cc' since their mutual types are compatible through inheritance (see inheritance tree above) cb.appliquer_taux_interet(); // 'appliquer_taux_interet' in 'Compte_cheque' is run since the runtime type of 'cb' is 'Compte_cheque' cb = cel; // Assumption: 'cel' is a direct instance of 'Compte_epargne_logement' cb.appliquer_taux_interet(); // Polymorphism again
Rule(s)
- Polymorphism is strongly grounded on the fact that objects from “higher” types in the inheritance tree may dynamically “point/refer” to objects of lower ones (contravariance, line 2).
- The contrary does not apply apart from casting (line 5). In this scope, runtime errors may appear when types are incompatible.
Example
// Assumption: 'Cat' directly or indirectly inherits from 'Animal': Animal a = new Cat(); // Cat c = a; // Incompatible types: 'a' may point to an elephant, a mouse... try { Cat c = (Cat) a; } catch (ClassCastException cce) { // Runtime exception! System.err.println(cce.getMessage()); }
Covariance (on return types)
Rule(s)
- From Java 5, Java supports covariance (on return types). As a result, the following example shows that
cloning
in bothFemale
andMale
is really is considered as an overriding ofcloning
inIndividual
.Example Covariance.Java.zip
public class Individual { java.time.LocalDate _birth_date; … public Individual cloning() { System.out.print("cloning in Individual...\n"); return new Individual(_birth_date.getDayOfMonth(), _birth_date.getMonthValue(), _birth_date.getYear()); } } … public class Female extends Individual { … @Override public Female cloning() { System.out.print("cloning in Female...\n"); return new Female(_birth_date.getDayOfMonth(), _birth_date.getMonthValue(), _birth_date.getYear()); } } … public class Male extends Individual { … @Override public Male cloning() { System.out.print("cloning in Male...\n"); return new Male(_birth_date.getDayOfMonth(), _birth_date.getMonthValue(), _birth_date.getYear()); } } … Individual FranckBarbier = new Male(11, 1, 1963); Individual clone = FranckBarbier.cloning(); // 'cloning in Male...' is displayed => covariance applies in Java!
Rule(s)
- Covariance on return types is an original principle of the Eiffel OO programming language, which offers a graceful style compared to Java.
Example
class Individual feature cloning : like Current is do Result := Current end; … end x,y : Female -- As direct descendant of 'Individual' !!x -- Instantiation y := x.cloning -- OK, 'cloning' returns a 'Female' object
Static method hiding using
final
Example Final_and_static_methods.Java.zip
public class My_class_mother { final static void My_method() { System.out.println("I'm both a 'final' and 'static' method in " + My_class_mother.class.getSimpleName() + ", what does it mean?"); } } public class My_class extends My_class_mother { // 'static' methods can be 'hidden' instead of 'overridden', but 'final' in 'My_class_mother' prevents hidding here: // static void My_method() { // } public static void main(String[] args) { My_class.My_method(); // Normal style, 'final' in 'My_class_mother' prevents any hidding in 'My_class' My_class mc = new My_class(); mc.My_method(); // Weird Java style through instances! } }
Rule(s)
- The notion of interface is native from Java birth. Creating an interface creates a (reference) type.
- Up to Java 7, methods in interfaces are
abstract
andpublic
while attributes arepublic
,static
, andfinal
.- Interfaces are close to abstract classes having, up to Java 7, no concrete methods at all (declared or inherited). As an illustration,
java.util.Comparator<T>
is a famous predefined interface for comparing (e.g., sorting) “compliant” objects.Example
public class Task implements java.util.Comparator<Task> { private int _priority; public Task(int priority) { _priority = priority; } @Override public int compare(Task t1, Task t2) { if (t1._priority < t2._priority) { return -1; // '-1' (or any negative) is a convention/contract imposed by 'java.util.Comparator<T>' } if (t1._priority == t2._priority) { return 0; // '0' is a convention/contract imposed by 'java.util.Comparator<T>' } return 1; // '1' (or any positive) is a convention/contract imposed by 'java.util.Comparator<T>' } }
Interfaces: key issues
Rule(s)
- Interfaces are types. For example, the
java.lang.Integer
class implements thejava.lang.Comparable<T>
interface withT
equals tojava.lang.Integer
.Example
// Compilation error, attempt to (directly) instantiate an interface: // Comparable<Integer> i = new Comparable<>(); // Instead, 'i' has for (design) type 'Comparable<Integer>' while it has 'Integer' as (runtime) compatible type: Comparable<Integer> i = new Integer(0);
Rule(s)
- The key advantage of interfaces is the fact that predefined code may be written based on an anonymous
T
type. Later on,T
may be replaced by a known type provided that this type (e.g.,T
) complies withjava.lang.Comparable<T>
.Example Sort_illustration.Java.zip
public class Sort_illustration<T extends Comparable<T>> { java.util.ArrayList<T> _representation; … void sort(int left, int right) { assert (left < right); for (int i = left; i < right; i++) { for (int j = i + 1; j <= right; j++) { _cost++; if (_representation.get(i).compareTo(_representation.get(j)) > 0) { T temp = _representation.get(i); _representation.set(i, _representation.get(j)); _representation.set(j, temp); } } } } … } … public class Elephant implements Comparable<Elephant> { private float _weight; public int compareTo(Elephant e) { if(this._weight < e. _weight) return -1; … } … Sort_illustration<Elephant> elephant_zoo = new Sort_illustration<>(…); // 'elephant_zoo' is sortable
Multiple interface inheritance
Example
public interface I { String S = "S in I"; void f(); } public interface J { String S = "S in J"; void f(); } public interface K extends I, J { … public class C implements K { …
Conflict about multiple interface inheritance
Example Multiple_inheritance.Java.zip
public interface I { String S = "S in I"; void f(); void g(I i); I h(); } public interface J { String S = "S in J"; void f(); void g(J j); J h(); } public interface K extends I, J { … // Compilation error about 'h' public class C implements K { public void f() { // Compilation error about 'g': 'g' from 'I' or 'g' from 'J'? g(this); } public void g(I i) { } public void g(J j) { } public static void main(String[] args) { System.out.println(I.S); } }
interface
changes from Java 8 and 9Rule(s)
- From Java 8, interfaces, like classes, may have
static
methods.- The other issue in Java 8 is the introduction of default methods using the
default
keyword as prefix. Due to the fact that default methods make interfaces and abstract classes very similar, a remaining difference is the fact that abstract classes have constructors while interfaces have not.- As of Java 9, methods may be declared
private
.Example (with misconception on line 41) From_Java_9.Java.zip
enum Size { S, M, L, XL, XXL }; interface Proboscidea { // From Java 8 (i.e., 'static' and 'default'): static String Reign() { return _French_name() + " in Animalia reign"; } default String description() { // 'default' => 'public' return "Animal owning a trunk, whose size is " + trunk_size(); // Body is mandatory for default methods... } default boolean still_exists_() { // 'default' => 'public' return true; // Body is mandatory for default methods... } default String trunk_size() { return _trunk_size() == Size.S ? "small" : ""; // To be enhanced: test of values of 'Size' enumerated type } // From Java 9 (i.e., 'private'): private static String _French_name() { return "Proboscidien"; } private Size _trunk_size() { // 'private' methods *CANNOT* be used in descendants... return Size.S; // 'private' methods cannot be abstract... } } public class Mammutidae implements Proboscidea { @Override public boolean still_exists_() { return false; // Overriding occurs here... } private Size _trunk_size() { // Arrrgggglll... Overriding *FAILS*! return Size.XXL; } }
Example (old-school style)
public static final byte Celsius = 0; public static final byte Fahrenheit = 1; public static final byte Kelvin = 2; public Temperature(float value,byte unit) {… … Temperature t = new Temperature(18.F,3); // Error comes from the fact that '3' does not embody a meaningful value for temperature units!
Rule(s)
- From Java 5, enumerated types have a direct support based on
enum
keyword.Example
public enum Temperature_unit {Celsius, Fahrenheit, Kelvin} … public Temperature(float value,Temperature_unit unit) { … // 'Temperature_unit' is simply used as a type … Temperature t = new Temperature(18.F,Temperature_unit.Celsius); // Later on, values of 'Temperature_unit' may be passed as arguments … for(Temperature_unit unit : Temperature_unit.values()) System.out.print(“\t” + unit); // Java enumerated types have nice facilities like iterating
Rule(s)
- Enumerated types and sets are closely related in Java by starting from the
Enum<E extends Enum<E>>
interface and thejava.util.EnumSet<E extends Enum<E>>
class.Example Exception_management.Java.zip
public enum Temperature_unit { Celsius("°C"), Fahrenheit("°F"), Kelvin("°K"); final String _literal; private Temperature_unit(String literal) { _literal = literal; } public String toString() { return _literal; } } … Temperature_unit tu = Temperature_unit.Celsius; System.out.println(tu.toString()); // '°C' is displayed
Example
enum Decision_type {Conviction, Shortened_sentence, Final_discharge} Enum<Decision_type> decision_type; // <=> 'Decision_type decision_type;'