Table of Contents
- Introduction and objectives
- Creation with IntelliJ IDEA
- IDE, JDK, JRE — the basic acronyms
- Running from command line
- Instruction structure and white spaces
- Comments
- Introduction to packages
- Use packages
- Summary
- Introduction
- Variables
- Primitive data types
- Storage by value of primitive types
- Arithmetic operators
- Prefix, postfix and compound assignment operators
- Operator Precedence
- Type Conversion
- Variable type inference — the
varkeyword - Summary
- Introduction
- Conditional logic and relational operators
if-elsestatement- The CalcEngine project
- Logical operators
- Logical operators vs conditional operators
- Block Statements
switchinstruction and conditional assignment- Summary
- Introduction
- The
whileloop - The
do-whileloop - The
forloop - Arrays
- CalcEngine and parallel tables
- The
for-eachloop - Summary
- Introduction
- Declare and call methods
- Settings
- Parameter passing behavior
- Exit a method
- Return a value
- CalcEngine with methods
- Command-line Arguments
- Summary
- Introduction
- The
Stringclass - String Equality
- Methods of the
Stringclass and conversions - Added String support in CalcEngine
- Make CalcEngine interactive
- The
StringBuilderclass - Summary
- Declare classes
- The
MathEquationclass - Use classes
- Create class array
- Encapsulation and access modifiers
- Special references:
thisandnull - Accessors and Getters/Setters
- Summary
- Introduction
- Initial class state
- Field Initializers
- Builders
- Constructor Chaining
- Constructor visibility
- Initialization Blocks
- Summary
- Introduction
- Static fields and static methods
- Use static members
- Improve
MathEquationwith static members - Static imports
- Static initialization blocks
- Summary
- Passing objects as parameters
- Modifications on objects passed as parameters
- Method Overloading
- Overload Resolution
- The
Objectclass and its methods - Redefinition of object equality (
equals) - Custom implementation of
toString - Summary
- Primitive Wrappers Introduction and Classes
- Use Primitive Wrappers in CalcEngine
- Understanding Enums
- Enums and relative comparisons
- Converting CalcEngine to Enums
- Enums as classes
- Working with Records
- Summary
- Introduction
- Apply Annotations
- Annotations in Java code
- The
@Overrideannotation in the method overload - Final application cleanup
- Summary
1. Course Overview
Java is one of the most sought-after programming languages in the job market. It is used to develop all kinds of applications: smartphone applications, websites, server-side processes, and many smart devices that make up the Internet of Things (IoT). A recent survey also ranked Java as one of the most important skills for data scientists and big data processing.
What you will learn in this course
- Variables, data types* and operators*
- Language constructs and control flow
- Methods (methods) and parameter handling (parameter handling)
- Classes (classes), records and enums
- Using a professional IDE (IntelliJ IDEA)
- Running applications from the command line
This course is intended for beginners and does not assume any prior programming experience.
2. Create your first Java application
2.1 Introduction and objectives
In this module, we build the fundamental skills needed to work effectively as a Java application developer. The objectives are:
- Create and run an application with the IntelliJ IDEA IDE
- Understand how an end user runs the application from the command line
- Master the basic structure of Java code
2.2 Creating with IntelliJ IDEA
IntelliJ IDEA (commonly referred to as “IntelliJ”) is the development tool used throughout this course. To get started, we create a simple application to see how everything comes together.
First Java application:
public class Main {
/*
This is the first comment line
This is the second comment line
// This is a line comment
*/
public static void main(String[] args) {
// This is a standalone line comment
System.out.println("First line from app"); // This is a line comment
// System.out.println("Second line from app");
System.out.println(/*"Third line from app"*/"This is different text");
}
}
- The
mainmethod is the entry point of any Java application. System.out.println()prints text to standard output.- The
Mainclass contains all the code for the starting application.
2.3 IDE, JDK, JRE — the basic acronyms
When you start working with Java, you come across many acronyms. Here are the main ones:
| Acronym | Meaning | Role |
|---|---|---|
| JRE | Java Runtime Environment | Allows execution of Java code |
| JDK | Java Development Kit | Toolset for developing in Java (includes JRE) |
| IDE | Integrated Development Environment | Development environment (e.g. IntelliJ IDEA) |
Why Java is cross-platform: Java is a language created to work on many different platforms. Java code is compiled not into direct machine instructions, but into bytecode which executes on the JVM (Java Virtual Machine). The JVM is included in the JRE, and it is what allows a Java program to run on any system that has a compatible JRE.
Code source Java (.java)
↓ [compilation par javac]
Bytecode (.class)
↓ [exécution par la JVM]
Résultat
Key points:
- The IDE provides the development experience: code editing, compilation, debugging.
- Even if the IDE launches the application during development, end users do not need an IDE. They only need the JRE to run the application.
2.4 Running from the command line
To run a Java application without an IDE, the end user needs:
- A copy of the compiled file (
Main.class) - JRE installed on his machine
Command line commands:
# Compilation (génère Main.class)
javac Main.java
# Exécution
java Main
When the application is organized with packages, the run command must include the fully qualified name:
java com.pluralsight.organized.Main
2.5 Instruction structure and white spaces
A Java program is made up of a series of instructions (statements). Each statement must end with a semicolon (;).
System.out.println("Hello"); // instruction valide
System.out.println("World"); // instruction valide
Whitespace rules (whitespace):
- Java ignores extra whitespace in code.
- This means that we can format the code in a readable way without affecting its behavior.
- On the other hand, indentation and spacing are not optional from a readability point of view — they are essential to good practice.
2.6 Comments
Java allows you to insert comments in the source code. A comment is text ignored by the compiler.
Three types of comments:
// Commentaire de ligne (line comment) — tout ce qui suit // est ignoré jusqu'à la fin de la ligne
/* Commentaire de bloc (block comment)
Ce commentaire peut s'étendre sur plusieurs lignes */
/**
* Commentaire Javadoc — utilisé pour générer la documentation
* @param args les arguments de ligne de commande
*/
Why use comments:
- Add human readable notes in source code
- Remember the intent of the code to return to it later
- Document the code so other developers understand it
- Temporarily disable code without deleting it
2.7 Package Introduction
In Java, types are normally qualified by the package that contains them. Without a package, a source class looks like:
class Main {
// code
}
Normally, source files are qualified with a package name:
package com.pluralsight.organized;
public class Main {
// code
}
Why use packages:
- Organization: group related classes together
- Avoid name conflicts: two classes can have the same name if they are in different packages
- Visibility Control: Packages play a role in access modifiers
2.8 Use packages
When creating a new project in IntelliJ:
- Create a new Java project (New Project)
- Create the project from a template (Create project from template)
- Specify a package name, for example
com.pluralsight.organized
Application with package:
package com.pluralsight.organized;
public class Main {
public static void main(String[] args) {
System.out.println("We got organized");
}
}
The directory structure matches the package hierarchy:
src/
com/
pluralsight/
organized/
Main.java
2.9 Summary
Key takeaways from this module:
- As developers, we use an IDE (Integrated Development Environment) to edit code, run builds, and debug applications.
- The IDE provides a development experience, but end users only need the JRE (Java Runtime Environment) to run the program.
- The Java source file (
.java) is compiled into bytecode (.class), which is then executed by the JVM. - Packages help organize code and avoid name conflicts between classes.
- The
mainmethod with the signaturepublic static void main(String[] args)is the entry point of any Java application.
3. Variables, data types and mathematical operators
3.1 Introduction
In this module, we explore the capabilities that Java provides for managing and representing data in our applications. Topics covered:
- Variables: how to represent data values in an application
- Primitive data types: the fundamental types integrated into the language
- Arithmetic operators: perform calculations
- Operator precedence: the order in which operations are performed
- Type conversion: switch from one type to another
- Type inference with
var
3.2 Variables
To do anything interesting in our programs, we need a way to store and manipulate values. This is where variables come into play. A variable is simply a named area of storage.
*Java is a strongly typed language: when we declare a variable, we must indicate its type.
int dataValue; // déclaration sans initialisation
int dataValue = 100; // déclaration avec initialisation
Variable naming rules:
- May contain letters, numbers, the
_character and the$character - Cannot start with a number
- Case sensitive (case-sensitive)
- Convention: use the camelCase style (e.g.
myVariable,dataValue)
3.3 Primitive data types
primitive types (primitive data types) are the fundamental types built directly into the Java language. The word “primitive” does not mean that they are inferior, but simply that they are the basic types of language.
Integer types (Integer types)
| Type | Size | Range of values |
|---|---|---|
byte | 8-bit | -128 to 127 |
short | 16-bit | -32,768 to 32,767 |
int | 32-bit | -2,147,483,648 to 2,147,483,647 |
long | 64-bit | -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 |
Floating-point types
| Type | Size | Accuracy |
|---|---|---|
float | 32-bit | ~7 significant figures |
double | 64-bit | ~15-16 significant figures |
Other primitive types
| Type | Description | Example values |
|---|---|---|
char | Single Unicode character (16-bit) | 'A', 'z', '9' |
boolean | Value true/false | true, false |
Declaration and initialization:
int count = 25;
long bigNumber = 1_000_000L; // suffixe L pour long
float temperature = 98.6f; // suffixe f pour float
double pi = 3.14159d; // suffixe d pour double (optionnel)
char initial = 'J';
boolean isValid = true;
Note: Java uses literals to express values directly in code.
3.4 Storage by value of primitive types
When working with primitive types in Java, these types are *stored by value. This means that each variable gets its own independent copy of the value.
int firstValue = 100;
int otherValue = firstValue; // otherValue obtient une copie de la valeur 100
firstValue = 50;
// otherValue est toujours 100, car c'est une copie indépendante
System.out.println(otherValue); // Affiche : 100
Difference with reference types: Unlike primitive types, objects (reference types) do not store their value directly — they store a reference to the object in memory. This concept is important for understanding class behavior.
3.5 Arithmetic operators
Java offers several categories of operators for performing calculations.
Basic operators
| Operator | Description | Example |
|---|---|---|
+ | Addition | a + b |
- | Subtraction | a - b |
* | Multiplication | a * b |
/ | Division | a/b |
% | Modulo (rest of division) | a % b |
int a = 10, b = 3;
System.out.println(a + b); // 13
System.out.println(a - b); // 7
System.out.println(a * b); // 30
System.out.println(a / b); // 3 (division entière)
System.out.println(a % b); // 1 (reste)
Warning: Integer division (
int / int) truncates the decimal part. To obtain a decimal result, at least one of the operands must be of typedoubleorfloat.
3.6 Prefix, postfix and compound assignment operators
Increment and decrement operators
- Increment (
++): increases the value by 1 - Decrement (
--): decreases the value by 1
The position of the operator determines the order of application:
int myVal = 5;
// Postfixe : utilise la valeur d'abord, puis incrémente
int result1 = myVal++; // result1 = 5, myVal devient 6
// Préfixe : incrémente d'abord, puis utilise la valeur
int result2 = ++myVal; // myVal devient 7, result2 = 7
Compound Assignment Operators (Compound Assignment Operators)
| Operator | Equivalent |
|---|---|
a += b | a = a + b |
a -= b | a = a - b |
a *= b | a = a * b |
a /= b | a = a / b |
a %= b | a = a % b |
int value = 10;
value += 5; // value = 15
value *= 2; // value = 30
value -= 8; // value = 22
3.7 Operator Precedence
When an expression contains multiple operators, the compiler must know in what order to evaluate them. This is the concept of operator precedence.
Priority order (highest to lowest):
- Postfix operators (
i++,i--) - Prefix operators (
++i,--i) - Multiplication, division, modulo (
*,/,%) - Addition, subtraction (
+,-)
Practical example:
package com.pluralsight.operatorprecedence;
public class Main {
public static void main(String[] args) {
int valA = 21;
int valB = 6;
int valC = 3;
int valD = 1;
// Sans parenthèses : la division est effectuée avant la soustraction
int result1 = valA - valB / valC; // 21 - (6/3) = 21 - 2 = 19
// Avec parenthèses : force l'ordre d'évaluation
int result2 = (valA - valB) / valC; // (21-6)/3 = 15/3 = 5
System.out.println(result1); // 19
System.out.println(result2); // 5
int result3 = valA / valC * valD + valB; // (21/3*1)+6 = 7+6 = 13
int result4 = valA / (valC * (valD + valB)); // 21/(3*(1+6)) = 21/21 = 1
System.out.println(result3); // 13
System.out.println(result4); // 1
}
}
Best practice: Use parentheses to make the intention explicit and avoid any ambiguity, even when operator precedence would give the expected result.
3.8 Type Conversion
Sometimes we need to convert from one data type to another. Java distinguishes two categories of conversions:
Implicit conversions (Implicit conversions)
Performed automatically by the compiler when there is no risk of data loss (transition from a “smaller” type to a “larger” type):
int intVal = 100;
long longVal = intVal; // conversion implicite int -> long (pas de perte)
double dblVal = intVal; // conversion implicite int -> double (pas de perte)
Explicit conversions — cast (Explicit conversions)
Necessary when there is a risk of data loss (moving from a “larger” type to a “smaller” type). We use the cast operator:
double doubleVal = 9.99;
int intVal = (int) doubleVal; // cast explicite : résultat = 9 (la partie décimale est tronquée)
Code example — TypeConversion:
package com.pluralsight.typeconversion;
public class Main {
public static void main(String[] args) {
float floatVal = 1.0f;
double doubleVal = 4.0d;
byte byteVal = 7;
short shortVal = 7;
long longVal = 5;
short result1 = (short) longVal; // cast long -> short
short result2 = (short) (byteVal - longVal); // cast du résultat de la soustraction
float result3 = longVal - floatVal; // conversion implicite long -> float
System.out.println("Success");
}
}
Table of implicit conversions (widening conversions):
byte → short → int → long → float → double
↑
char
3.9 Variable type inference — the var keyword
The var keyword (introduced in Java 10) provides new options for declaring variables. With var, there is no need to explicitly specify the type of the variable — the compiler infers it from the initialization value.
// Déclaration classique
int myValue = 42;
String myName = "Java";
// Avec var (type inféré)
var myValue = 42; // inféré comme int
var myName = "Java"; // inféré comme String
var pi = 3.14159d; // inféré comme double
Important rules on var:
- Variable must be initialized at declaration time (compiler needs the value to infer the type)
varcannot be used for instance fields, parameters, or method return types- Type is fixed at compile time —
vardoes not make Java dynamically typed
var result = 100; // inféré comme int
result = 3.14; // ERREUR : on ne peut pas assigner un double à un int inféré
3.10 Summary
Key takeaways from this module:
- In our applications we use variables to store data values. Java is a strongly typed language — every variable has a defined type.
- Java generally provides default values for variables declared in classes, but local variables (in methods) must be explicitly initialized before being used.
- Primitive types (
byte,short,int,long,float,double,char,boolean) are stored by value — each variable gets its own independent copy. - Java supports various arithmetic operators and respects an order of operator precedence.
- implicit type conversion (widening conversion) is performed automatically when no data loss is possible.
- Explicit conversion (cast) is necessary when data loss is possible.
- The
varkeyword allows type inference by the compiler.
4. Conditional logic and block instructions
4.1 Introduction
Conditional logic is essential for creating interesting applications. Without it, a program would have only one linear execution path. In this module we cover:
- relational operators to perform comparisons
- The
if-elsestatement - logical operators (logical operators)
- *block statements
- The
switchinstruction - Conditional assignment
4.2 Conditional logic and relational operators
Conditional logic is based on two elements:
- Perform a test: a comparison which will be
trueorfalse - Act depending on the result: execute different code depending on the result
Relational operators:
| Operator | Meaning | Example |
|---|---|---|
> | Greater than | a > b |
< | Less than | a < b |
>= | Greater than or equal to | a >= b |
<= | Less than or equal to | a <= b |
== | Equal to | a == b |
!= | Different from | a != b |
These operators return a boolean value (true or false).
int a = 5, b = 10;
boolean result = a > b; // false
boolean isEqual = a == b; // false
boolean isDiff = a != b; // true
The ternary conditional operator (ternary operator):
A compact form of conditional assignment:
// Syntaxe : condition ? valeurSiVrai : valeurSiFaux
result = value2 != 0 ? value1 / value2 : 0.0d;
4.3 if-else statement
The if-else statement is one of the most common ways to include conditional logic. It allows you to conditionally execute an instruction or a block of code.
// if simple
if (condition) {
// code exécuté si condition est true
}
// if-else
if (condition) {
// code si true
} else {
// code si false
}
// if-else chaîné
if (condition1) {
// code si condition1 est true
} else if (condition2) {
// code si condition2 est true
} else {
// code si aucune condition n'est true
}
Example with CalcEngine opCode:
double value1 = 100.0d;
double value2 = 50.0d;
double result;
char opCode = 'd';
if (opCode == 'a')
result = value1 + value2;
else if (opCode == 's')
result = value1 - value2;
else if (opCode == 'm')
result = value1 * value2;
else if (opCode == 'd')
result = value2 != 0 ? value1 / value2 : 0.0d;
else {
System.out.println("Invalid opCode: " + opCode);
result = 0.0d;
}
System.out.println(result);
4.4 The CalcEngine project
The CalcEngine project is a recurring demonstration that evolves throughout the course. It is a simple calculation engine that performs arithmetic operations according to an opCode (operation code).
The package used is com.pluralsight.calcengine.
4.5 Logical operators
logical operators allow two true/false values to be combined to produce a single true/false value. This allows you to include more complex conditions in the code.
| Operator | Description | Result |
|---|---|---|
& | logical AND | true if both operands are true |
| | logical OR | true if at least one operand is true |
^ | Logical XOR | true if exactly one operand is true |
! | NOT logical | Invert boolean value |
boolean a = true, b = false;
System.out.println(a & b); // false
System.out.println(a | b); // true
System.out.println(a ^ b); // true
System.out.println(!a); // false
4.6 Logical operators vs conditional operators
Java also offers conditional operators (also called “short-circuit operators”):
| Operator | Description |
|---|---|
&& | AND conditional (short-circuit AND) |
|| | Conditional OR (short-circuit OR) |
The difference is important: with && and ||, the second operand is only evaluated if necessary.
package com.pluralsight.letsgetlogical;
public class Main {
public static void main(String[] args) {
int students = 150;
int rooms = 0;
// Avec && : si rooms != 0 est false, le second test n'est PAS évalué
// Cela évite une division par zéro !
if (rooms != 0 && students / rooms > 30)
System.out.println("Crowded");
System.out.println("");
System.out.println("*** end of program ***");
}
}
Key points:
&&and||use short-circuit evaluation: if the first operand is sufficient to determine the result, the second is not evaluated.&and|always evaluate both operands, even if the first one is sufficient.- In practice,
&&and||are generally preferred for performance and security.
4.7 Block Statements
A block statement (block statement) groups multiple statements into a single compound statement, surrounding them with braces {}.
// Sans bloc : seule la prochaine ligne est associée au if
if (condition)
doSomething(); // seule cette ligne est conditionnelle
// Avec bloc : tout le bloc est conditionnel
if (condition) {
doSomething();
doSomethingElse();
andOneMoreThing();
}
Importance of blocks:
- Some Java constructs (like
if,while,for) only operate on a single statement. - To execute several instructions conditionally, they must be grouped in a block.
- Blocks also define the scope (scope) of the variables declared inside.
// CalcEngine avec bloc switch amélioré
switch (opCode) {
case 'a':
result = value1 + value2;
break;
case 's':
result = value1 - value2;
break;
case 'm':
result = value1 * value2;
break;
case 'd':
result = value2 != 0 ? value1 / value2 : 0.0d;
break;
default:
System.out.println("Invalid opCode: " + opCode);
result = 0.0d;
break;
}
4.8 switch instruction and conditional assignment
The switch instruction allows you to test a single value against several possible matches. It is often more readable than a long string of if-else when you always check the same variable.
package com.pluralsight.calcengine;
public class Main {
public static void main(String[] args) {
double value1 = 100.0d;
double value2 = 0.0d;
double result = 0.0d;
char opCode = 'd';
switch (opCode) {
case 'a':
result = value1 + value2;
break;
case 's':
result = value1 - value2;
break;
case 'm':
result = value1 * value2;
break;
case 'd':
result = value2 != 0 ? value1 / value2 : 0.0d;
break;
default:
System.out.println("Invalid opCode: " + opCode);
result = 0.0d;
break;
}
System.out.println(result);
}
}
Rules of the switch instruction:
- Each
casestarts with the keywordcasefollowed by the value to compare and:. - The
breakinstruction is necessary to avoid fall-through (execution continues in the next case). - The
defaultis executed when nocasematches (optional but recommended).
The ternary operator for conditional assignment:
// Forme longue
double result;
if (value2 != 0)
result = value1 / value2;
else
result = 0.0d;
// Forme courte avec l'opérateur ternaire
double result = value2 != 0 ? value1 / value2 : 0.0d;
4.9 Summary
Key takeaways:
- The ternary operator (conditional assignment) allows you to return a specific value based on a condition.
- The
if-elsestatement allows you to execute statements conditionally. - Relational operators (
>,<,==, etc.) perform comparisons and return abooleanvalue. - logical operators (
&,|,^,!) combinebooleanvalues. - Conditional operators (
&&,||) use short-circuit evaluation for better performance. - Block instructions allow multiple instructions to be grouped into one.
- The
switchinstruction allows you to test a value against several cases.
5. Looping and Arrays
5.1 Introduction
When building a sophisticated application, it is important to be able to perform repeated blocks of work on groups of linked data. This requires the use of loops and often the management of data in arrays.
5.2 The while loop
The while loop is the most basic type of loop. It continues to execute as long as its *control condition remains true. The condition is checked at the start of the loop (before the body is executed).
// Syntaxe
while (condition) {
// corps de la boucle
}
// Exemple
int i = 1;
while (i <= 5) {
System.out.println("Valeur : " + i);
i++;
}
// Affiche : Valeur : 1, Valeur : 2, Valeur : 3, Valeur : 4, Valeur : 5
Beware of infinite loops: If the condition never becomes false, the loop executes indefinitely.
5.3 The do-while loop
The do-while loop is similar to while, except that the check condition is checked at the end of the loop. This ensures that the loop body executes at least once.
// Syntaxe
do {
// corps de la boucle
} while (condition);
// Exemple
int value;
Scanner scanner = new Scanner(System.in);
do {
System.out.print("Entrez un nombre positif : ");
value = scanner.nextInt();
} while (value <= 0);
System.out.println("Vous avez entré : " + value);
Key difference: In while, if the condition is false from the start, the body never executes. In do-while, the body always executes at least once.
5.4 The for loop
The for loop also checks the condition at the beginning. It is particularly suitable when we know in advance the number of iterations.
// Syntaxe
for (initialisation; condition; mise_à_jour) {
// corps de la boucle
}
// Exemple
for (int i = 0; i < 5; i++) {
System.out.println("Itération " + i);
}
The structure of the for loop has three parts in the parentheses:
- Initialization: executed only once before the start of the loop
- Condition: checked before each iteration
- Update: executed after each iteration
5.5 Arrays
arrays are used to store an ordered collection of values. Each individual value is called an element.
// Déclaration d'un tableau
double[] valeurs;
// Création d'un tableau de 4 éléments
double[] valeurs = new double[4];
// Initialisation directe avec des valeurs (littéral de tableau)
double[] valeurs = {1.5, 2.5, 3.5, 4.5};
Access to elements:
- Arrays are indexed from 0 (zero-based)
- Access to an element:
array[index] - The size of the array:
array.length
double[] numbers = {10.0, 20.0, 30.0};
System.out.println(numbers[0]); // 10.0 (premier élément)
System.out.println(numbers[2]); // 30.0 (dernier élément)
System.out.println(numbers.length); // 3
5.6 CalcEngine and parallel tables
The CalcEngine project evolves to use tables to perform several calculations:
package com.pluralsight.calcengine;
public class Main {
public static void main(String[] args) {
double[] leftVals = {100.0d, 25.0d, 225.0d, 11.0d};
double[] rightVals = {50.0d, 92.0d, 17.0d, 3.0d};
char[] opCodes = {'d', 'a', 's', 'm'};
double[] results = new double[opCodes.length];
for (int i = 0; i < opCodes.length; i++) {
switch (opCodes[i]) {
case 'a':
results[i] = leftVals[i] + rightVals[i];
break;
case 's':
results[i] = leftVals[i] - rightVals[i];
break;
case 'm':
results[i] = leftVals[i] * rightVals[i];
break;
case 'd':
results[i] = rightVals[i] != 0 ? leftVals[i] / rightVals[i] : 0.0d;
break;
default:
System.out.println("Invalid opCode: " + opCodes[i]);
results[i] = 0.0d;
break;
}
}
for (double currentResult : results)
System.out.println(currentResult);
}
}
Parallel arrays:* Several arrays of the same size where elements with the same indices are linked together. In this example, leftVals[0], rightVals[0] and opCodes[0] together represent an operation: 100.0 / 50.0.
5.7 The for-each loop
The for-each loop simplifies traversing the members of an array. It executes the body of the loop once for each member of the array, without having to deal with indices.
// Syntaxe
for (Type variable : tableau) {
// corps de la boucle
}
// Exemple
double[] results = {2.0, 117.0, 208.0, 33.0};
for (double currentResult : results) {
System.out.println(currentResult);
}
Advantages of for-each:
- More readable and concise code
- No indexes to manage → less risk of errors
- Ideal when you just want to browse all elements
Limitation: The for-each does not give access to the index. If you need the index, you must use a classic for.
5.8 Summary
Key takeaways:
- Java offers several types of loops:
while: condition checked at start — may never executedo-while: condition checked at the end — executes at least oncefor: ideal when the number of iterations is known in advancefor-each: simplifies array traversal- Arrays store an ordered collection of values, indexed from 0.
- The
lengthproperty gives the size of an array. - Arrays have a fixed size (fixed-size) — once created, their size cannot change.
6. Understanding Methods
6.1 Introduction
methods are an important part of Java application development. They help organize the code so that it is more maintainable and make it easier to reuse the code within the application.
6.2 Declaring and calling methods
Basically, a method is a mechanism for organizing code. We create a block of code accessible from other parts of the application.
// Syntaxe de déclaration
visibilité typeRetour nomMéthode(paramètres) {
// corps de la méthode
return valeur; // si typeRetour != void
}
// Exemples
public static void afficherMessage() {
System.out.println("Hello from method!");
}
private static int additionner(int a, int b) {
return a + b;
}
Calling a method:
afficherMessage(); // appel sans paramètre
int somme = additionner(5, 3); // appel avec paramètres
6.3 Parameters
Variables in a method have a scope limited to that method. To pass data into a method, we use parameters.
// Méthode avec paramètres
static double execute(char opCode, double leftVal, double rightVal) {
double result;
switch (opCode) {
case 'a': result = leftVal + rightVal; break;
case 's': result = leftVal - rightVal; break;
case 'm': result = leftVal * rightVal; break;
case 'd': result = rightVal != 0 ? leftVal / rightVal : 0.0d; break;
default:
System.out.println("Invalid opCode: " + opCode);
result = 0.0d;
}
return result;
}
Terminology:
- Parameters (parameters): variables in the method declaration
- Arguments (arguments): the values passed when calling the method
// 'a', 100.0, 50.0 sont des arguments
double result = execute('a', 100.0, 50.0);
6.4 Behavior of parameter passing
In Java, parameters are passed by value. This means that the parameter receives a copy of the original value, not the original value itself.
static void doubler(int valeur) {
valeur = valeur * 2; // modifie la copie locale, pas l'original
System.out.println("Dans la méthode: " + valeur); // 20
}
int maValeur = 10;
doubler(maValeur);
System.out.println("Après l'appel: " + maValeur); // toujours 10
Impact: Changes made to a primitive type parameter inside a method are not visible outside the method.
Note: For objects (reference types), the behavior is different — see Module 11.
6.5 Exit a method
There are three reasons for exiting a method:
- We reach the end of the method: no more code to execute
- A
returnis reached: explicit exit from the method - An exception is thrown: exit in error
// return sans valeur (méthode void)
static void valider(int valeur) {
if (valeur < 0) {
System.out.println("Valeur invalide");
return; // sortie anticipée
}
System.out.println("Valeur valide : " + valeur);
}
// return avec valeur
static int calculer(int a, int b) {
return a + b; // retourne une valeur
}
6.6 Return a value
A method can return a value using the return statement. The return type can be a primitive type or a complex type like an array.
// Retourner un tableau
static int[] obtenirValeurs() {
return new int[]{1, 2, 3, 4, 5};
}
// Exemple plus complet
static double calculerInteret(double montant, double taux, int annees) {
return montant * Math.pow(1 + taux, annees);
}
If a method returns nothing, its return type is void.
6.7 CalcEngine with methods
The CalcEngine project is refactored to use separate methods:
package com.pluralsight.calcengine;
public class Main {
public static void main(String[] args) {
double[] leftVals = {100.0d, 25.0d, 225.0d, 11.0d};
double[] rightVals = {50.0d, 92.0d, 17.0d, 3.0d};
char[] opCodes = {'d', 'a', 's', 'm'};
double[] results = new double[opCodes.length];
if (args.length == 0) {
for (int i = 0; i < opCodes.length; i++) {
results[i] = execute(opCodes[i], leftVals[i], rightVals[i]);
}
for (double currentResult : results)
System.out.println(currentResult);
} else if (args.length == 3)
handleCommandLine(args);
else
System.out.println("Please provide an operation code and 2 numeric values");
}
private static void handleCommandLine(String[] args) {
char opCode = args[0].charAt(0);
double leftVal = Double.parseDouble(args[1]);
double rightVal = Double.parseDouble(args[2]);
double result = execute(opCode, leftVal, rightVal);
System.out.println(result);
}
static double execute(char opCode, double leftVal, double rightVal) {
double result;
switch (opCode) {
case 'a': result = leftVal + rightVal; break;
case 's': result = leftVal - rightVal; break;
case 'm': result = leftVal * rightVal; break;
case 'd': result = rightVal != 0 ? leftVal / rightVal : 0.0d; break;
default:
System.out.println("Invalid opCode: " + opCode);
result = 0.0d;
}
return result;
}
}
Benefits of organizing methods:
- The
execute()method centralizes the calculation logic, avoiding duplication handleCommandLine()isolates command line argument processing- Code is more readable, maintainable and testable
6.8 Command-line Arguments
command-line arguments (command-line arguments) are used to pass information into a program when it is launched.
In the main method, the String[] args parameter contains the arguments passed:
# Exécution avec arguments
java com.pluralsight.calcengine.Main d 100 50
# args[0] = "d"
# args[1] = "100"
# args[2] = "50"
public static void main(String[] args) {
if (args.length == 0) {
// Mode tableau par défaut
performDefaultCalculations();
} else if (args.length == 3) {
// Mode ligne de commande : opCode leftVal rightVal
handleCommandLine(args);
} else {
System.out.println("Please provide an operation code and 2 numeric values");
}
}
In IntelliJ IDEA, one can configure command line arguments via Run → Edit Configurations → Program arguments.
6.9 Summary
Key takeaways:
- Methods are a key part of Java development because they organize code and facilitate reuse.
- A variable declared in a method is only visible in this method (variable scope).
- Parameters are variables local to a method, initialized with the values passed during the call.
- Java passes parameters by value — method receives a copy, not the original.
- A method can return a value with
return, or return nothing (void). - Every Java application starts with the
mainmethod:public static void main(String[] args). - The
argsparameter contains the command line arguments passed to the program.
7. Working with Strings
7.1 Introduction
Virtually any application needs to work with text. In Java, it is the String class which manages character strings. In this module:
- The
Stringclass and its characteristics - String equality (string equality)
- Methods of the
Stringclass - The
StringBuilderclass to build strings efficiently - Make CalcEngine interactive with a
Scanner
7.2 The String class
The String class stores a sequence of characters. These characters are Unicode characters, stored in UTF-16 format. This means that Java strings can store virtually any character from any language.
// Création d'une String avec un littéral
String message = "Hello, Java!";
// Création avec new (moins courant)
String message2 = new String("Hello, Java!");
Important feature — immutability:
String objects in Java are immutable. This means that once created, a channel cannot be edited. Any apparent modification operation actually creates a new string.
String s = "Hello";
s = s + " World"; // crée une NOUVELLE chaîne "Hello World"
// l'ancienne "Hello" n'est pas modifiée, juste abandonnée
String pool:* Java maintains a pool of string literals in memory. When we create a string with a literal, Java checks if this string already exists in the pool. If so, it reuses the same instance.
7.3 String Equality
String comparison is an important topic in Java.
String s1 = "I love Java";
String s2 = "I love Java";
// L'opérateur == compare les RÉFÉRENCES, pas les contenus
System.out.println(s1 == s2); // peut être true (si même instance du pool)
// .equals() compare les CONTENUS
System.out.println(s1.equals(s2)); // toujours true si mêmes caractères
Fundamental rule: To compare the contents of two strings, always use .equals() (or .equalsIgnoreCase() if case doesn’t matter), never ==.
// Mauvaise pratique
if (opCode == "add") { ... } // compare les références
// Bonne pratique
if (opCode.equals("add")) { ... } // compare les contenus
if (opCode.equalsIgnoreCase("ADD")) { ... } // insensible à la casse
7.4 Methods of the String class
The String class is a class (class) that provides many methods. Here are the most common:
| Method | Description | Example |
|---|---|---|
length() | Returns length | "Hello".length() → 5 |
charAt(index) | Returns the character at index | "Hello".charAt(0) → 'H' |
substring(start, end) | Extract a substring | "Hello".substring(1, 3) → "el" |
contains(str) | Checks if contains a substring | "Hello".contains("ell") → true |
startsWith(prefix) | Check prefix | "Hello".startsWith("He") → true |
endsWith(suffix) | Check the suffix | "Hello".endsWith("lo") → true |
toLowerCase() | Converts to lowercase | "HELLO".toLowerCase() → "hello" |
toUpperCase() | Converts to uppercase | "hello".toUpperCase() → "HELLO" |
trim() | Remove spaces before/after | "hello".trim() → "hello" |
replace(old, new) | Replace characters | "Hello".replace('l', 'r') → "Herro" |
split(delimiter) | Divide into array | "a b c".split(" ") → ["a","b","c"] |
indexOf(str) | Find the first occurrence | "Hello".indexOf("l") → 2 |
equals(str) | Compare content | "abc".equals("abc") → true |
valueOf(primitive) | Convert to String | String.valueOf(42) → "42" |
Conversion between types:
// int → String
String str = String.valueOf(42); // "42"
String str2 = Integer.toString(42); // "42"
// String → int
int val = Integer.parseInt("42"); // 42
// String → double
double dbl = Double.parseDouble("3.14"); // 3.14
7.5 Added String support in CalcEngine
static void executeInteractively() {
System.out.println("Enter an operation and two numbers");
Scanner scanner = new Scanner(System.in);
String userInput = scanner.nextLine();
String[] parts = userInput.split(" ");
performOperation(parts);
}
private static void performOperation(String[] parts) {
char opCode = opCodeFromString(parts[0]);
double leftVal = valueFromWord(parts[1]);
double rightVal = valueFromWord(parts[2]);
double result = execute(opCode, leftVal, rightVal);
displayResult(opCode, leftVal, rightVal, result);
}
The valueFromWord method converts English words (“one”, “two”, etc.) into numeric values, using the equals() method for comparison.
7.6 Make CalcEngine interactive
By adding java.util.Scanner, the application can read user input:
import java.util.Scanner;
// ...
Scanner scanner = new Scanner(System.in);
String userInput = scanner.nextLine(); // lit une ligne complète
String[] parts = userInput.split(" "); // divise par les espaces
The CalcEngine application now supports three modes:
- Default mode (without arguments): calculates predefined tables
- Interactive mode (
interactiveas argument): reads user input - Command line mode (3 arguments): performs a single calculation
7.7 The StringBuilder class
The StringBuilder class provides a mutable string buffer. Unlike String, it can be modified without creating new instances.
When to use StringBuilder:
- When you have to build a chain piece by piece in a loop
- For better performance when concatenating many strings
// Mauvaise pratique (crée beaucoup d'objets String temporaires)
String result = "";
for (int i = 0; i < 1000; i++) {
result += i; // crée un nouvel objet String à chaque fois
}
// Bonne pratique (utilise StringBuilder)
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 1000; i++) {
builder.append(i);
}
String result = builder.toString();
Main methods of StringBuilder:
StringBuilder builder = new StringBuilder(20); // capacité initiale (optionnelle)
builder.append("Hello"); // ajoute une chaîne
builder.append(" "); // ajoute un espace
builder.append(42); // ajoute un nombre
builder.insert(5, " World"); // insère à une position
builder.delete(5, 10); // supprime des caractères
builder.replace(0, 5, "Hi"); // remplace une portion
builder.reverse(); // inverse la chaîne
int len = builder.length(); // longueur actuelle
String result = builder.toString(); // convertit en String
Application in CalcEngine — displayResult method:
private static void displayResult(char opCode, double leftVal,
double rightVal, double result) {
char symbol = symbolFromOpCode(opCode);
StringBuilder builder = new StringBuilder(20);
builder.append(leftVal);
builder.append(" ");
builder.append(symbol);
builder.append(" ");
builder.append(rightVal);
builder.append(" = ");
builder.append(result);
String output = builder.toString();
System.out.println(output);
// Exemple : "100.0 / 50.0 = 2.0"
}
7.8 Summary
Key takeaways:
Stringstrings store a sequence of characters and can store virtually any sequence of characters from any language.Stringvariables do not contain the string directly — they contain a reference to aStringobject in memory.Stringobjects are immutable — any modification creates a new string.- To compare the contents of two strings, always use
.equals(), never==. - The
Stringclass provides many useful methods for manipulating strings. - The
StringBuilderclass allows you to build strings in a mutable and efficient way.
8. Understanding Classes and Objects
8.1 Declaring classes
A class (class) is a template (template) for creating objects. An object is simply an instance of a class.
// Déclaration d'une classe
public class Flight {
// Champs (fields)
private int passengers;
private int seats = 150;
// Méthodes
public void add1Passenger() {
if (passengers < seats)
passengers++;
}
public int getPassengers() { return passengers; }
public int getSeats() { return seats; }
}
Types of members of a class:
- Fields: variables that store the state of the object
- Methods (methods): functions that define the behavior of the object
- Constructors (constructors): special code to initialize the object (see Module 9)
8.2 The MathEquation class
The CalcEngine project introduces a MathEquation class which encapsulates a mathematical equation:
package com.pluralsight.calcengine;
public class MathEquation {
private double leftVal;
private double rightVal;
private char opCode;
private double result;
void execute() {
switch (opCode) {
case 'a': result = leftVal + rightVal; break;
case 's': result = leftVal - rightVal; break;
case 'm': result = leftVal * rightVal; break;
case 'd': result = rightVal != 0 ? leftVal / rightVal : 0.0d; break;
default:
System.out.println("Invalid opCode: " + opCode);
result = 0.0d;
}
}
// Getters et Setters
public double getLeftVal() { return leftVal; }
public void setLeftVal(double v) { this.leftVal = v; }
public double getRightVal() { return rightVal; }
public void setRightVal(double v) { this.rightVal = v; }
public char getOpCode() { return opCode; }
public void setOpCode(char c) { this.opCode = c; }
public double getResult() { return result; }
public void setResult(double r) { this.result = r; }
}
8.3 Using classes
Declare a class type variable:
Declaring a variable of type Flight does not create a Flight object. It creates a variable that can hold a reference to a Flight object.
Create an object with new:
To actually create the object, we use the new keyword:
Flight laxToSlc; // variable de type Flight (pas encore d'objet)
laxToSlc = new Flight(); // crée un objet Flight, laxToSlc pointe vers lui
// En une seule ligne
Flight laxToSlc = new Flight();
Use object members:
Flight vol = new Flight();
vol.add1Passenger(); // appel d'une méthode
int nb = vol.getPassengers(); // accès à un champ via getter
Class types are reference types:
- The variable does not contain the object itself, but a reference to it in memory.
- Assigning one class variable to another does not copy the object — both variables point to the same object.
Flight vol1 = new Flight();
Flight vol2 = vol1; // vol2 pointe vers le MÊME objet que vol1
vol2.add1Passenger();
System.out.println(vol1.getPassengers()); // affiche 1 (le même objet a été modifié)
8.4 Create a class table
You can create arrays of objects:
static void performCalculations() {
MathEquation[] equations = new MathEquation[4];
equations[0] = create(100.0d, 50.0d, 'd');
equations[1] = create(25.0d, 92.0d, 'a');
equations[2] = create(225.0d, 17.0d, 's');
equations[3] = create(11.0d, 3.0d, 'm');
for (MathEquation equation : equations) {
equation.execute();
System.out.println("result = " + equation.getResult());
}
}
private static MathEquation create(double leftVal, double rightVal, char opCode) {
MathEquation equation = new MathEquation();
equation.setLeftVal(leftVal);
equation.setRightVal(rightVal);
equation.setOpCode(opCode);
return equation;
}
8.5 Encapsulation and access modifiers
Encapsulation is the idea of hiding the implementation details of a class. In Java, access modifiers control the visibility of members.
| Modifier | Visibility |
|---|---|
public | Visible everywhere |
private | Visible only in the classroom |
protected | Visible in the class and its subclasses (inheritance) |
| (none) | Package visibility (package-private) |
Encapsulation best practice:
- fields should generally be
private— they are an implementation detail. - Access to fields is via public getters and setters.
- Methods that are part of the public API are
public.
public class MathEquation {
private double leftVal; // champ privé
private double rightVal;
private char opCode;
private double result;
// API publique
public double getLeftVal() { return leftVal; }
public void setLeftVal(double leftVal) { this.leftVal = leftVal; }
// ...
}
8.6 Special references: this and null
The reference this
this is a reference to the current instance of the object. It is mainly used for:
- Remove ambiguity when a parameter has the same name as a field:
public void setLeftVal(double leftVal) {
this.leftVal = leftVal; // this.leftVal = champ, leftVal = paramètre
}
- Pass the current object as an argument to another method:
public void register() {
Registry.add(this); // passe l'objet courant à la méthode register
}
The value null
null represents the absence of a reference. An uninitialized reference type variable is null.
Flight vol = null; // vol ne pointe vers aucun objet
// Vérifier avant d'utiliser
if (vol != null) {
vol.add1Passenger(); // sûr
}
NullPointerException: If we try to call a method on a null reference, Java throws a NullPointerException.
8.7 Accessors and setters (Getters/Setters)
As fields are generally private, they are accessed via special methods:
- Getter (accessor): method to read the value of a field
- Setter (mutator): method to modify the value of a field
// Getter
public double getLeftVal() {
return leftVal;
}
// Setter avec validation
public void setOpCode(char opCode) {
// On pourrait valider que opCode est dans {'a', 's', 'm', 'd'}
this.opCode = opCode;
}
Advantages:
- Validation possible in setters before accepting a value
- Ability to change internal implementation without breaking user code
- Fine control over access rights (e.g. public getter but no setter → read-only field)
8.8 Summary
Key takeaways:
- A class is a template for creating objects. An object is an instance of a class.
- Classes are reference types — class variables do not contain the object itself, but a reference to it.
- To create an object, we use the keyword
new. - encapsulation hides implementation details. Fields are generally
private, public methods form the API. thisis a reference to the current instance.nullindicates no reference — using anullreference causes aNullPointerException.- getters and setters provide controlled access to private fields.
9. Class constructors and initializers
9.1 Introduction
In this module, we explore how to control the initial state of a class when it is created. Topics covered:
- Default initial state of fields
- Field initializers (field initializers)
- Constructors (constructors): single and multiple
- *Constructor chaining
- Initialization blocks (initialization blocks)
9.2 Initial state of classes
When you create a new instance of a class, Java assigns default values to the fields:
| Type | Default |
|---|---|
byte, short, int, long | 0 |
float, double | 0.0 |
char | '\u0000' (null character) |
boolean | false |
| Reference type (objects) | null |
public class Exemple {
int count; // défaut : 0
double ratio; // défaut : 0.0
String name; // défaut : null
boolean flag; // défaut : false
}
These default values are often not sufficient — we need to define specific initial states.
9.3 Field Initializers
Field initializers allow you to specify the initial value of a field directly in its declaration:
public class Earth {
// Initialiseur de champ
double circumferenceInMiles = 24_901.0;
// Sans initialiseur, circumferenceInMiles serait 0.0
}
public class Flight {
private int seats = 150; // toujours initialisé à 150
private int passengers; // initialisé à 0 par défaut
}
Advantage: Simple and clear when the initial value is a known constant.
9.4 Manufacturers
A constructor (constructor) contains code that executes each time a new instance of the class is created.
Manufacturers’ rules:
- Must have the same name as the class
- Have no return type (not even
void) - May have parameters
public class MathEquation {
private double leftVal;
private double rightVal;
private char opCode;
private double result;
// Constructeur sans paramètre (no-arg constructor)
public MathEquation() {}
// Constructeur avec un paramètre
public MathEquation(char opCode) {
this.opCode = opCode;
}
// Constructeur avec trois paramètres
public MathEquation(char opCode, double leftVal, double rightVal) {
this.opCode = opCode;
this.leftVal = leftVal;
this.rightVal = rightVal;
}
}
Usage:
MathEquation eq1 = new MathEquation(); // constructeur no-arg
MathEquation eq2 = new MathEquation('a'); // constructeur 1 param
MathEquation eq3 = new MathEquation('a', 10.0, 5.0); // constructeur 3 params
Default constructor: If no constructor is declared, Java automatically provides one without parameters (default constructor). If we declare at least one constructor, the default constructor is no longer provided automatically.
9.5 Constructor Chaining
When a class has multiple constructors, a constructor can call another constructor using the keyword this() followed by the appropriate arguments. This is called constructor chaining.
public class MathEquation {
private double leftVal;
private double rightVal;
private char opCode;
public MathEquation() {}
public MathEquation(char opCode) {
this.opCode = opCode;
}
// Ce constructeur CHAÎNE vers le constructeur à 1 paramètre
public MathEquation(char opCode, double leftVal, double rightVal) {
this(opCode); // appelle MathEquation(char opCode)
this.leftVal = leftVal;
this.rightVal = rightVal;
}
}
Advantage: Avoids duplication of initialization code between manufacturers.
Rule: The this() call must be the first instruction of the constructor.
9.6 Visibility of constructors
When designing constructors, one must think about their visibility because some constructors should not be publicly accessible.
public class Passenger {
private Passenger() {} // constructeur privé : empêche l'instanciation directe
public Passenger(int frequentFlyerNumber) {
// Seul les passagers avec un numéro sont autorisés
}
}
Use case:
privateconstructor: prevents direct instantiation from outside (Singleton pattern, Factory)- Constructor without modifier: accessible only in the same package
9.7 Initialization Blocks
Initialization blocks contain code shared between all constructors. This code runs automatically, no matter which constructor is used.
public class Exemple {
private int value;
private String name;
// Bloc d'initialisation — s'exécute avant TOUT constructeur
{
value = 10;
name = "Default";
System.out.println("Bloc d'initialisation exécuté");
}
public Exemple() {}
public Exemple(int value) {
this.value = value;
}
}
Execution order when creating an object:
- Field initializers are applied
- Initialization block is executed
- The constructor body is executed
Difference with field initializers: Initialization blocks can contain more complex logic (loops, conditions) unlike simple field initializers.
9.8 Summary
Key takeaways:
- When creating an instance, fields receive default values (0, 0.0, false, null).
- Field initializers allow you to define the initial value during declaration.
- Constructors contain code that runs when an instance is created. They have the same name as the class and have no return type.
- Classes can have multiple constructors with different signatures (overloaded constructors).
- Constructor chaining with
this()avoids duplication of initialization code. - Initialization blocks allow code to be shared between all constructors.
10. Static Members
10.1 Introduction
There are times in class design where we need values and actions associated with the class itself (not a particular instance). This is where *static members come in.
10.2 Static fields and static methods
Static fields (Static fields)
A static field is a value that is not associated with a specific instance of the class. This value is associated with the class itself. All instances share the same value.
public class Flight {
private int passengers;
private int seats = 150;
// Champ statique partagé par toutes les instances
private static int numberOfFlights;
public Flight() {
numberOfFlights++; // incrémente à chaque nouvelle instance
}
public static int getNumberOfFlights() {
return numberOfFlights;
}
}
There is only one copy of the static field, regardless of the number of instances created.
Static methods (Static methods)
A static method belongs to the class, not to an instance. We call it directly to the class.
// Déclaration
public static double getAverageResult() {
return sumOfResults / numberOfCalculations;
}
// Appel (sur la classe, pas une instance)
double avg = MathEquation.getAverageResult();
Important rule: A static method can only access static members (static fields and methods). It does not have access to instance members because there is no associated instance.
10.3 Using static members
Flight laxToSlc = new Flight();
Flight laxToSlc2 = new Flight();
System.out.println(Flight.getNumberOfFlights()); // 2 — accessible via la classe
Difference between static fields and instance fields:
| Instance field | Static field | |
|---|---|---|
| Association | Each instance has its own copy | One copy for the whole class |
| Access | Via an instance | Via class (or instance — not recommended) |
| Example | flight.passengers | Flight.numberOfFlights |
10.4 Improve MathEquation with static members
The CalcEngine project uses static members to average results:
public class MathEquation {
private double leftVal;
private double rightVal;
private char opCode;
private double result;
// Membres statiques pour la moyenne
private static int numberOfCalculations;
private static double sumOfResults;
public void execute() {
switch (opCode) {
case 'a': result = leftVal + rightVal; break;
case 's': result = leftVal - rightVal; break;
case 'm': result = leftVal * rightVal; break;
case 'd': result = rightVal != 0 ? leftVal / rightVal : 0.0d; break;
default:
System.out.println("Invalid opCode: " + opCode);
result = 0.0d;
}
// Mise à jour des compteurs statiques après chaque calcul
numberOfCalculations++;
sumOfResults += result;
}
// Méthode statique pour obtenir la moyenne
public static double getAverageResult() {
return sumOfResults / numberOfCalculations;
}
// ... getters et setters ...
}
Use in Main:
for (MathEquation equation : equations) {
equation.execute();
System.out.println("result = " + equation.getResult());
}
System.out.println("Average result = " + MathEquation.getAverageResult());
10.5 Static imports (static import)
Static import statements (static import statements) allow direct access to the static members of a class without qualifying it with the class name.
// Sans static import
Math.PI
Math.sqrt(4.0)
Math.abs(-5)
// Avec static import
import static java.lang.Math.PI;
import static java.lang.Math.sqrt;
import static java.lang.Math.*; // importe tous les membres statiques
// Utilisation directe
double area = PI * r * r;
double racine = sqrt(16.0);
When to use static imports:
Mainly useful when an external class provides very frequently used constants or utility functions (like Math). Use sparingly to avoid compromising readability.
10.6 Static initialization blocks
static initialization blocks allow you to perform a one-time initialization for the type, before its first use. They can only access static members.
public class Exemple {
private static final double[] FACTORS;
private static int maxValue;
// Bloc d'initialisation statique
static {
FACTORS = new double[]{1.0, 1.5, 2.0, 3.0};
maxValue = calculateMaxValue();
System.out.println("Type initialisé");
}
private static int calculateMaxValue() {
return 1000;
}
}
Execution order:
- Static field initializers are applied
- Static initialization block is executed
- These two steps occur before the first use of the type
10.7 Summary
Key takeaways:
- Static members are shared class-wide — there is only one copy.
- We indicate that a member is static with the keyword
static. - Static fields are values that are not tied to a specific instance.
- Static methods do not have access to instance members.
- Static imports allow using static members without class name qualification.
- Static initialization blocks allow a one-time initialization before the first use of the type.
11. A Closer Look at Methods
11.1 Passing objects into parameters
When passing an object as a parameter to a method, the method receives a copy of the reference to the original object (not a copy of the object itself). We say that objects are passed by reference.
static void processFlight(Flight f) {
f.add1Passenger(); // modifie l'objet original via la référence
}
Flight vol = new Flight();
processFlight(vol);
System.out.println(vol.getPassengers()); // 1 — l'objet a été modifié
11.2 Modifications to objects passed as parameters
Since the method receives a copy of the reference, it can modify the members of the object and these modifications will be visible externally. On the other hand, if we change which object the reference points to inside the method, this change is not visible outside.
static void modifier(Flight f) {
f.add1Passenger(); // visible à l'extérieur
f = new Flight(); // change la RÉFÉRENCE LOCALE — pas l'original
f.add1Passenger(); // ne modifie pas l'objet original
}
Flight vol = new Flight();
modifier(vol);
System.out.println(vol.getPassengers()); // 1, pas 2
Parameter passing summary:
- primitive types: passed by value → modifications not visible externally
- Types reference (objects): the reference is copied → modifications of visible members, but reassignment of the reference not visible
11.3 Method overloading
Java supports *overloading: within a single class, you can have several versions of a method (or a constructor) with the same name but different signatures (different number or types of parameters).
public class MathEquation {
// ...
public void execute() {
// version sans paramètre : utilise les champs internes
// ... (code de calcul)
numberOfCalculations++;
sumOfResults += result;
}
// Surcharge 1 : avec des doubles
public void execute(double leftVal, double rightVal) {
this.leftVal = leftVal;
this.rightVal = rightVal;
execute(); // appelle la version sans paramètre
}
// Surcharge 2 : avec des entiers
public void execute(int leftVal, int rightVal) {
this.leftVal = leftVal;
this.rightVal = rightVal;
execute();
result = (int) result; // tronque le résultat à un entier
}
}
Overload rules:
- Overloaded methods must have different signatures (different parameters).
- Return type alone is not enough to differentiate overloads.
- The compiler determines which overload to use based on the arguments provided.
11.4 Resolving overloads
MathEquation eq = new MathEquation(MathOperation.DIVIDE);
// Le compilateur choisit la bonne surcharge selon les arguments
eq.execute(9.0d, 4.0d); // appelle execute(double, double)
eq.execute(9, 4); // appelle execute(int, int)
Type match: If no overload matches exactly, the compiler searches for the closest overload by applying implicit conversions.
11.5 The Object class and its methods
Java supports a concept called inheritance: a class can be declared with the characteristics of another class.
The Object class is at the root of the entire class hierarchy in Java. All classes implicitly inherit from Object. This means that all objects have access to the methods defined in Object:
| Method | Description |
|---|---|
toString() | Returns a textual representation of the object |
equals(Object obj) | Compare equality with another object |
hashCode() | Returns an integer hash code |
getClass() | Returns the object class |
clone() | Creates a copy of the object |
11.6 Redefining object equality (equals)
By default, the equals() method inherited from Object compares references (like ==). To compare the contents of two objects, we must redefine (override) this method.
Flight vol1 = new Flight(100); // vol numéro 100
Flight vol2 = new Flight(100); // aussi vol numéro 100
// Sans @Override d'equals : compare les références
System.out.println(vol1.equals(vol2)); // false (deux objets différents)
// Avec @Override d'equals : compare le numéro de vol
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Flight)) return false;
Flight other = (Flight) obj;
return this.flightNumber == other.flightNumber;
}
System.out.println(vol1.equals(vol2)); // true (même numéro)
11.7 Custom implementation of toString
The toString() method is called automatically when passing an object to System.out.println() or when concatenating strings.
public class MathEquation {
// ...
@Override
public String toString() {
char symbol = opCode.getSymbol();
StringBuilder builder = new StringBuilder(20);
builder.append(leftVal);
builder.append(" ");
builder.append(symbol);
builder.append(" ");
builder.append(rightVal);
builder.append(" = ");
builder.append(result);
return builder.toString();
}
}
// Utilisation
MathEquation eq = new MathEquation(MathOperation.ADD, 10.0, 5.0);
eq.execute();
System.out.println(eq); // appelle toString() automatiquement
// Affiche : "10.0 + 5.0 = 15.0"
11.8 Summary
Key takeaways:
- Objects are always passed by reference — the method receives a copy of the reference, not the object itself.
- Changes made to object members via a parameter are visible externally.
- Changes to the reference itself (reassignment) are not visible externally.
- overloading allows you to have several methods with the same name but different parameters.
- All classes inherit from the
Objectclass which provides common methods. - We can redefine (
@Override)equals()andtoString()to customize their behavior.
12. Wrapper classes, Enums and Records
12.1 Introduction and primitive wrappers classes
In this module, we explore specialized classes commonly used in Java.
Primitive wrapper classes are classes that wrap primitive types to give them object-oriented functionality.
| Primitive type | Wrapper class |
|---|---|
byte | Byte |
short | Shorts |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
Added value of Wrapper classes:
- Utility methods (parsing, conversion)
- Useful constants (e.g.
Integer.MAX_VALUE,Integer.MIN_VALUE) - Ability to use primitives where objects are required (collections)
// Parsing String → type primitif
int intVal = Integer.parseInt("42");
double dblVal = Double.parseDouble("3.14");
// Conversion primitif → String
String str = Integer.toString(42);
String str2 = String.valueOf(42);
// Constantes
System.out.println(Integer.MAX_VALUE); // 2147483647
System.out.println(Double.MAX_VALUE); // 1.7976931348623157E308
// Autoboxing : conversion automatique primitif ↔ wrapper
Integer wrapped = 42; // autoboxing : int → Integer
int unwrapped = wrapped; // unboxing : Integer → int
12.2 Using Primitive Wrappers in CalcEngine
static double valueFromWord(String word) {
String[] numberWords = {
"zero", "one", "two", "three", "four",
"five", "six", "seven", "eight", "nine"
};
boolean isValueSet = false;
double value = 0d;
for (int index = 0; index < numberWords.length; index++) {
if (word.equals(numberWords[index])) {
value = index;
isValueSet = true;
break;
}
}
if (!isValueSet)
value = Double.parseDouble(word); // utilise Double.parseDouble
return value;
}
12.3 Understanding Enums
*enumeration types, enum, allow you to define a type with a finite list of valid values.
// Déclaration d'un enum
public enum FlightCrewJob {
CAPTAIN,
FIRST_OFFICER,
FLIGHT_ENGINEER,
LEAD_FLIGHT_ATTENDANT,
FLIGHT_ATTENDANT
}
// Utilisation
FlightCrewJob job = FlightCrewJob.CAPTAIN;
Advantages of enums over constants:
- The compiler checks the validity of the values
- Code is more readable and self-documenting
- Cannot use invalid value (compile-time safety)
12.4 Enums and relative comparisons
The values of an enum are ordered — the first value has the lowest order, the last the highest. This allows relative comparisons.
FlightCrewJob job = FlightCrewJob.FLIGHT_ATTENDANT;
// Méthodes disponibles sur les valeurs d'enum
int position = job.ordinal(); // position dans la liste (0-based)
String name = job.name(); // nom de la constante ("FLIGHT_ATTENDANT")
// Comparaison relative
boolean isFirst = (job.compareTo(FlightCrewJob.CAPTAIN) == 0);
boolean isBefore = (job.compareTo(FlightCrewJob.CAPTAIN) > 0); // après CAPTAIN dans la liste
// Obtenir toutes les valeurs
FlightCrewJob[] allJobs = FlightCrewJob.values();
12.5 Converting CalcEngine to Enums
The MathOperation enum replaces the opCode char in the CalcEngine project:
package com.pluralsight.calcengine;
public enum MathOperation {
ADD('+'),
SUBTRACT('-'),
MULTIPLY('*'),
DIVIDE('/');
private char symbol;
public char getSymbol() { return symbol; }
// Constructeur privé de l'enum
private MathOperation(char symbol) {
this.symbol = symbol;
}
}
Use in MathEquation:
public class MathEquation {
private MathOperation opCode; // utilise l'enum au lieu de char
public void execute() {
switch (opCode) {
case ADD: result = leftVal + rightVal; break;
case SUBTRACT: result = leftVal - rightVal; break;
case MULTIPLY: result = leftVal * rightVal; break;
case DIVIDE: result = rightVal != 0 ? leftVal / rightVal : 0.0d; break;
default:
System.out.println("Invalid opCode: " + opCode);
result = 0.0d;
}
numberOfCalculations++;
sumOfResults += result;
}
}
In Main:
MathEquation[] equations = new MathEquation[4];
equations[0] = new MathEquation(MathOperation.DIVIDE, 100.0d, 50.0d);
equations[1] = new MathEquation(MathOperation.ADD, 25.0d, 92.0d);
equations[2] = new MathEquation(MathOperation.SUBTRACT, 225.0d, 17.0d);
equations[3] = new MathEquation(MathOperation.MULTIPLY, 11.0d, 3.0d);
12.6 Enums as classes
Although it may not seem like it, when we declare an enum type, we are actually declaring a class. Enums implicitly inherit from the java.lang.Enum class.
Characteristics of enums as classes:
- Can have fields and methods
- Can have constructors (always private or without modifier)
- Each enum value is actually an instance of the enum class
- Can implement interfaces
// Enum avec méthode abstraite (chaque valeur implémente son propre comportement)
public enum MathOperation {
ADD('+') {
@Override
public double apply(double a, double b) { return a + b; }
},
SUBTRACT('-') {
@Override
public double apply(double a, double b) { return a - b; }
};
private char symbol;
public char getSymbol() { return symbol; }
private MathOperation(char symbol) { this.symbol = symbol; }
public abstract double apply(double a, double b);
}
Methods available through java.lang.Enum:
values(): returns an array of all valuesvalueOf(String name): returns the value corresponding to the nameordinal(): returns the positionname(): returns the name of the constant
// valueOf() utilisé dans CalcEngine pour l'entrée interactive
MathOperation op = MathOperation.valueOf("ADD"); // MathOperation.ADD
MathOperation op2 = MathOperation.valueOf(parts[0].toUpperCase()); // depuis l'entrée utilisateur
12.7 Working with Records
It is common in applications to access and transport data. We often create classes just to transport this data — called *data-carrier classes, data transfer objects, DTOs.
Records (introduced in Java 16 as a stable feature) greatly simplifies the creation of these classes.
// Classe traditionnelle pour transporter des données (verbeux)
public class PersonInfo {
private String firstName;
private String lastName;
private int age;
public PersonInfo(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
public String getFirstName() { return firstName; }
public String getLastName() { return lastName; }
public int getAge() { return age; }
@Override public boolean equals(Object o) { /* ... */ }
@Override public int hashCode() { /* ... */ }
@Override public String toString() { /* ... */ }
}
// Équivalent avec un Record (très concis !)
public record PersonInfo(String firstName, String lastName, int age) {}
The compiler automatically generates for a Record:
- A canonical constructor with all fields
- accessors for each field (e.g.
firstName(), notgetFirstName()) - The
equals(),hashCode(), andtoString()methods
Record Features:
- The fields are immutable (immutable) — they cannot be modified after creation
- Ideal for DTOs, configuration values, data read from a database
// Utilisation d'un Record
PersonInfo person = new PersonInfo("John", "Doe", 30);
System.out.println(person.firstName()); // "John" (accesseur, pas getter)
System.out.println(person.age()); // 30
System.out.println(person); // PersonInfo[firstName=John, lastName=Doe, age=30]
12.8 Summary
Key takeaways:
- Primitive Wrapper classes wrap primitive types and provide utility methods.
- autoboxing and unboxing automatically convert between primitives and wrappers.
- enums define a type with a finite list of valid values — the compiler checks their use.
- Enum values are ordered and can be compared.
- Enums are actually classes that inherit from
java.lang.Enumand can have fields, methods and constructors. - Records simplify the creation of classes carrying immutable data by automatically generating constructor, accessors,
equals(),hashCode()andtoString().
13. Introduction to Annotations
13.1 Introduction
In this module, we explore the role of annotations (annotations) in Java:
- What annotations do: extend the type system with additional information
- How to use annotations to embed metadata (metadata)
- Some common Java platform annotations
13.2 Applying Annotations
When the type system is not enough, we try to complete it. Traditional approaches include:
- A priori knowledge (a priori knowledge): the creator of the code knows his intention
- Comments in code: explain the intent with comments
- Annotations: encode the intent directly in the type system
annotations allow you to associate metadata with a type. They extend the type system to incorporate information such as context and intent.
Fundamental point: When you apply an annotation to a type, it has no direct effect on that type. Annotations are used by tools, frameworks, and processors to make decisions.
// Syntaxe générale d'une annotation
@NomAnnotation
public class MaClasse { ... }
@NomAnnotation(parametre = "valeur")
public void maMethode() { ... }
13.3 Annotations in Java code
Annotations are integrated into the core of the Java platform. Although the platform itself only includes a few, other tools and environments use them very extensively:
Examples of how tools use annotations:
| Context | Examples of annotations |
|---|---|
| XML/JSON serialization | @XmlElement, @JsonProperty |
| Dependency Injection | @Inject, @Autowired, @Component |
| Unit Testing | @Test, @BeforeEach, @AfterAll |
| Persistence (JPA/Hibernate) | @Entity, @Table, @Column |
| REST mapping | @GetMapping, @PostMapping |
| Validation | @NotNull, @Size, @Email |
13.4 The @Override annotation in method overloading
The @Override annotation is one of the most common annotations. It tells the compiler that the annotated method is supposed to override a method of a parent class or interface.
public class MathEquation {
@Override
public String toString() {
char symbol = opCode.getSymbol();
StringBuilder builder = new StringBuilder(20);
builder.append(leftVal);
builder.append(" ");
builder.append(symbol);
builder.append(" ");
builder.append(rightVal);
builder.append(" = ");
builder.append(result);
return builder.toString();
}
}
Why @Override is useful:
- If you make a typo in the method name, the compiler reports an error (without
@Override, it would silently create a new method) - Make the intent explicit to code readers
- Protect against refactoring errors
// Sans @Override : le compilateur ne détecte pas la faute de frappe
public String tostring() { ... } // nouvelle méthode, toString() non redéfinie
// Avec @Override : le compilateur génère une erreur
@Override
public String tostring() { ... } // ERREUR : "tostring" ne redéfinit pas une méthode de Object
Other standard Java annotations:
| Annotation | Description |
|---|---|
@Override | Indicates that a method overrides a method of a parent class |
@Deprecated | Indicates that an element is deprecated (deprecated) |
@SuppressWarnings | Suppresses compiler warnings |
@FunctionalInterface | Indicates that an interface is a functional interface |
@SafeVarargs | Suppress warnings related to generic varargs |
13.5 Final application cleanup
The final version of the CalcEngine project incorporates all the improvements made throughout the course:
package com.pluralsight.calcengine;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
if (args.length == 0) {
performCalculations();
} else if (args.length == 1 && args[0].equals("interactive"))
executeInteractively();
else if (args.length == 3)
performOperation(args);
else
System.out.println("Please provide an operation code and 2 numeric values");
}
static void performCalculations() {
MathEquation[] equations = new MathEquation[4];
equations[0] = new MathEquation(MathOperation.DIVIDE, 100.0d, 50.0d);
equations[1] = new MathEquation(MathOperation.ADD, 25.0d, 92.0d);
equations[2] = new MathEquation(MathOperation.SUBTRACT, 225.0d, 17.0d);
equations[3] = new MathEquation(MathOperation.MULTIPLY, 11.0d, 3.0d);
for (MathEquation equation : equations) {
equation.execute();
System.out.println(equation); // appelle toString()
}
System.out.println("Average result = " + MathEquation.getAverageResult());
}
static void executeInteractively() {
System.out.println("Enter an operation and two numbers");
Scanner scanner = new Scanner(System.in);
String userInput = scanner.nextLine();
String[] parts = userInput.split(" ");
performOperation(parts);
}
private static void performOperation(String[] parts) {
MathOperation opCode = MathOperation.valueOf(parts[0].toUpperCase());
double leftVal = valueFromWord(parts[1]);
double rightVal = valueFromWord(parts[2]);
MathEquation equation = new MathEquation(opCode, leftVal, rightVal);
equation.execute();
System.out.println(equation);
}
static double valueFromWord(String word) {
String[] numberWords = {
"zero", "one", "two", "three", "four",
"five", "six", "seven", "eight", "nine"
};
boolean isValueSet = false;
double value = 0d;
for (int index = 0; index < numberWords.length; index++) {
if (word.equals(numberWords[index])) {
value = index;
isValueSet = true;
break;
}
}
if (!isValueSet)
value = Double.parseDouble(word);
return value;
}
}
Final version of MathEquation with @Override:
package com.pluralsight.calcengine;
public class MathEquation {
private double leftVal;
private double rightVal;
private MathOperation opCode;
private double result;
private static int numberOfCalculations;
private static double sumOfResults;
public MathEquation() {}
public MathEquation(MathOperation opCode) {
this.opCode = opCode;
}
public MathEquation(MathOperation opCode, double leftVal, double rightVal) {
this(opCode);
this.leftVal = leftVal;
this.rightVal = rightVal;
}
public void execute() {
switch (opCode) {
case ADD: result = leftVal + rightVal; break;
case SUBTRACT: result = leftVal - rightVal; break;
case MULTIPLY: result = leftVal * rightVal; break;
case DIVIDE: result = rightVal != 0 ? leftVal / rightVal : 0.0d; break;
default:
System.out.println("Invalid opCode: " + opCode);
result = 0.0d;
}
numberOfCalculations++;
sumOfResults += result;
}
public void execute(double leftVal, double rightVal) {
this.leftVal = leftVal;
this.rightVal = rightVal;
execute();
}
public void execute(int leftVal, int rightVal) {
this.leftVal = leftVal;
this.rightVal = rightVal;
execute();
result = (int) result;
}
@Override
public String toString() {
char symbol = opCode.getSymbol();
StringBuilder builder = new StringBuilder(20);
builder.append(leftVal);
builder.append(" ");
builder.append(symbol);
builder.append(" ");
builder.append(rightVal);
builder.append(" = ");
builder.append(result);
return builder.toString();
}
public static double getAverageResult() {
return sumOfResults / numberOfCalculations;
}
// Getters et Setters
public double getLeftVal() { return leftVal; }
public void setLeftVal(double leftVal) { this.leftVal = leftVal; }
public double getRightVal() { return rightVal; }
public void setRightVal(double v) { this.rightVal = v; }
public MathOperation getOpCode() { return opCode; }
public void setOpCode(MathOperation c) { this.opCode = c; }
public double getResult() { return result; }
}
Final version of MathOperation:
package com.pluralsight.calcengine;
public enum MathOperation {
ADD('+'),
SUBTRACT('-'),
MULTIPLY('*'),
DIVIDE('/');
private char symbol;
public char getSymbol() { return symbol; }
private MathOperation(char symbol) {
this.symbol = symbol;
}
}
13.6 Summary
Key takeaways:
- annotations allow metadata to be associated with a type without changing its direct behavior.
- The annotation is applied to a type (class, method, field) and is used by external tools (compiler, frameworks, processors).
- The
@Overrideannotation indicates that a method overrides a method of a parent class. It protects against typos and refactoring errors. - Other standard annotations include
@Deprecated,@SuppressWarnings, and@FunctionalInterface. - Tools like persistence (JPA), dependency injection (Spring), and testing (JUnit) frameworks make extensive use of annotations.
14. Evolution of the CalcEngine project — summary
The CalcEngine project is the common thread of the entire training. Here is a summary of its evolution module by module:
| Module | Evolution |
|---|---|
| Module 4 | Introducing the project with chained if-else then switch |
| Module 5 | Added parallel arrays (leftVals, rightVals, opCodes) and loops |
| Module 6 | Refactoring to methods (execute, handleCommandLine) + command line arguments |
| Module 7 | Interactive mode with Scanner, String.split(), StringBuilder for display |
| Module 8 | Introduction of MathEquation class with encapsulation and accessors |
| Module 9 | Adding multiple constructors and constructor chaining in MathEquation |
| Module 10 | Static members (numberOfCalculations, sumOfResults, getAverageResult()) |
| Module 11 | execute() overload (doubles and integers), toString() implementation |
| Module 12 | Introduction of the MathOperation enum, use of Double.parseDouble, Records |
| Module 13 | Added @Override to toString(), final cleanup |
15. Java concepts covered — quick reference index
| Concept | Module | Description |
|---|---|---|
class | 8 | Template to create objects |
enum | 12 | Type with finite list of values |
record | 12 | Data-carrying immutable class |
interface | (mentioned) | Contract that classes must implement |
static | 10 | Member associated with the class, not with the instances |
final | (implicit) | Non-modifiable value |
this | 8 | Reference to current instance |
null | 8 | Absence of reference |
new | 8 | Creating an object |
var | 3 | Type inference by the compiler |
@Override | 13 | Method Redefinition Annotation |
break | 4 | Exiting a switch or a loop |
return | 6 | Exiting a method with/without value |
| Autoboxing | 12 | Auto primitive conversion ↔ wrapper |
| Overloading (overloading) | 11 | Same name methods, different parameters |
| Encapsulation | 8 | Hide implementation details |
| Inheritance (inheritance) | 11 | Class that inherits characteristics from another |
| Immutability (immutability) | 7, 12 | Object that cannot be modified after creation |
| Short-circuit evaluation | 4 | && and ` |
| Passing by value | 6 | Parameters receive a copy |
| Passage by reference | 11 | Passed objects allow their members to be modified |
| String pool (String pool) | 7 | Memory optimization for String literals |
| Bytecode | 2 | Compiled code executed by the JVM |
| JVM | 2 | Java Virtual Machine |
Search Terms
java · se · fundamentals · backend · architecture · full-stack · web · operators · calcengine · methods · static · class · classes · types · enums · primitive · conditional · loop · string · annotations · assignment · members · method · objects