Operators are the verbs of the language: what turns inert data into calculations. You have already used + and - intuitively, but Java has a broad catalogue and several traps that catch everyone at least once: integer division discarding the decimals, the difference between i++ and ++i, string comparison with == that sometimes works and sometimes does not, and the treacherous precision of double when money is involved. This lesson goes through them all calmly, always on the same BiblioTech case: calculating the late fine for a loan from the Nexus Software library.
Contents
- Arithmetic operators and the integer-division trap
- The modulo operator
- Compound assignment operators
- Increment and decrement: pre and post
- Relational operators
- Logical operators and short-circuit evaluation
- The ternary operator
- Bitwise and shift operators
- The
instanceofoperator - Precedence and associativity
- Comparing strings:
==versusequals - The precision of
doubleand whyBigDecimalexists - Common Mistakes and Tips
- Exercises
- Arithmetic operators and the integer-division trap
The five basic arithmetic operators:
| Operator | Name | Example | Result |
|---|---|---|---|
+ |
Addition | 12 + 3 |
15 |
- |
Subtraction | 27 - 15 |
12 |
* |
Multiplication | 12 * 2 |
24 |
/ |
Division | 12 / 5 |
2 (careful!) |
% |
Modulo (remainder) | 12 % 5 |
2 |
The 5 / 2 trap
It is the number one mistake of anyone starting out in Java:
The rule is this: if both operands are integers, the division is an integer division and the result is truncated towards zero, discarding the decimals. Java does not round, it cuts.
And this is not fixed by storing the result in a double:
Why? Because the operation total / parts is evaluated first, between integers, giving 2; only afterwards is that 2 converted to a double. The conversion arrives too late.
The three correct solutions:
// 1. Cast one of the operands BEFORE dividing
double result = (double) total / parts; // 2.5
// 2. Declare the variables as double from the start
double totalD = 5;
double partsD = 2;
double result2 = totalD / partsD; // 2.5
// 3. Multiply by 1.0 (a less readable trick, but you will see it in real code)
double result3 = total * 1.0 / parts; // 2.5Watch the order in the cast: (double)(total / parts) does not work. The parentheses make the integer division happen first and the conversion afterwards: it gives 2.0. The cast has to be applied to one of the operands.
Division by zero
It depends on the type, and the difference matters:
int a = 5 / 0; // ArithmeticException: / by zero -> the program stops
double b = 5.0 / 0; // Infinity -> the program CONTINUES
double c = 0.0 / 0; // NaN (Not a Number) -> the program CONTINUESInteger division by zero throws a runtime error. Decimal division produces special values (Infinity, NaN) that propagate silently through every later calculation, which can be worse: you end up printing NaN EUR on a receipt without ever having seen an error.
Applied to BiblioTech
final int LOAN_DAYS = 15;
int elapsedDays = 27;
int daysLate = elapsedDays - LOAN_DAYS; // 12 days late
final double DAILY_RATE = 0.25;
double fine = daysLate * DAILY_RATE; // 12 * 0.25 = 3.0
System.out.println("Days late: " + daysLate);
System.out.println("Fine: " + fine + " EUR");Here daysLate is an int and DAILY_RATE is a double: Java promotes the integer to a decimal before multiplying and the result is correct. Automatic promotion saves us; division is the only one to watch out for.
- The modulo operator
% returns the remainder of an integer division. It is far more useful than it looks at first sight.
System.out.println(17 % 5); // 2 (17 = 5*3 + 2)
System.out.println(12 % 4); // 0 (exact division)
System.out.println(3 % 5); // 3 (3 = 5*0 + 3)
System.out.println(-7 % 3); // -1 (in Java the sign comes from the DIVIDEND)
System.out.println(7.5 % 2); // 1.5 (it also works with decimals)Common uses:
| Goal | Expression | Explanation |
|---|---|---|
| Is it even? | n % 2 == 0 |
Remainder zero when divided by 2 |
| Is it a multiple of 7? | n % 7 == 0 |
Generalisation of the previous one |
| Last digit | n % 10 |
Remainder of dividing by 10 |
| Cycle within a range 0..n−1 | i % n |
It never leaves the range |
| Break down time | minutes % 60 |
Minutes left over from the whole hours |
Applied to BiblioTech, to express the delay in weeks and leftover days:
int daysLate = 27;
int weeksLate = daysLate / 7; // integer division: 3 weeks
int leftoverDays = daysLate % 7; // remainder: 6 days
System.out.println("Late by: " + weeksLate + " weeks and " + leftoverDays + " days");
// Late by: 3 weeks and 6 daysNotice how / and % work as a pair: one gives the quotient, the other what is left over. It is a pattern that will come back many times.
- Compound assignment operators
They are shortcuts for operating on a variable and storing the result back in it:
| Operator | Equivalent to | Example |
|---|---|---|
+= |
x = x + y |
fine += 0.25; |
-= |
x = x - y |
daysLate -= 3; |
*= |
x = x * y |
fine *= 2; |
/= |
x = x / y |
total /= 4; |
%= |
x = x % y |
counter %= 7; |
There is a little-known detail: compound assignment includes an implicit cast. This compiles:
int daysLate = 12;
daysLate += 0.75; // compiles: equivalent to daysLate = (int)(daysLate + 0.75)
System.out.println(daysLate); // 12 <- the 0.75 is lost in the truncationwhereas the equivalent "long" form does not compile:
It is an asymmetry in the language that can hide precision loss with no warning at all. It is worth knowing about.
- Increment and decrement: pre and post
++ adds 1 and -- subtracts 1. What is interesting is that they can be written before or after the variable, and that changes the value of the expression:
- Post-increment
x++: uses the current value, and then increments. - Pre-increment
++x: increments first, and then uses the new value.
int a = 5;
int resultA = a++; // resultA receives 5, and then a becomes 6
System.out.println("a = " + a + ", resultA = " + resultA);
// a = 6, resultA = 5
int b = 5;
int resultB = ++b; // b becomes 6 first, and resultB receives 6
System.out.println("b = " + b + ", resultB = " + resultB);
// b = 6, resultB = 6The table sums up the difference, always starting from x = 5:
| Expression | Value of the expression | Final value of x |
|---|---|---|
x++ |
5 | 6 |
++x |
6 | 6 |
x-- |
5 | 4 |
--x |
4 | 4 |
When it really matters: if the ++ is alone on its own line (counter++;), pre and post are identical and it makes no difference which you use. The difference only shows up when the value of the expression is used for something, such as an assignment, an index or an argument.
Practical advice: do not write expressions that depend on that subtlety. Code like int x = i++ + ++i; is a riddle, not a program. When it matters, split it into two clear lines.
- Relational operators
They compare two values and always produce a boolean:
| Operator | Meaning | Example | Result |
|---|---|---|---|
== |
Equal to | daysLate == 0 |
false if there is a delay |
!= |
Not equal to | daysLate != 0 |
true if there is a delay |
> |
Greater than | 27 > 15 |
true |
< |
Less than | 12 < 15 |
true |
>= |
Greater than or equal | 15 >= 15 |
true |
<= |
Less than or equal | 20 <= 15 |
false |
final int LOAN_DAYS = 15;
int elapsedDays = 27;
boolean isLate = elapsedDays > LOAN_DAYS;
System.out.println("Is late: " + isLate); // trueIt repeats the idea from the previous lesson: a comparison is an expression that is worth true or false on its own. You do not need an if to obtain it; the if (module 2) is for deciding what to do with it.
Two warnings:
==compares values for primitives, but references for objects. That is the topic of section 11.- The most expensive typo in programming is writing
=(assignment) where you meant==(comparison). In Java, fortunately,if (x = 5)does not compile unlessxis aboolean, because anintassignment does not produce aboolean. The compiler protects you.
- Logical operators and short-circuit evaluation
They combine boolean values:
| Operator | Name | Result |
|---|---|---|
&& |
Logical AND | true only if both are true |
|| |
Logical OR | true if at least one is true |
! |
Negation (NOT) | Inverts the value |
Truth tables:
a |
b |
a && b |
a || b |
!a |
|---|---|---|---|---|
true |
true |
true |
true |
false |
true |
false |
false |
true |
false |
false |
true |
false |
true |
true |
false |
false |
false |
false |
true |
Applied to BiblioTech:
boolean available = false;
int daysLate = 12;
boolean isActiveEmployee = true;
boolean onLoanAndLate = !available && daysLate > 0; // true
boolean needsNotice = daysLate > 0 || !available; // true
boolean canReserve = available && isActiveEmployee; // falseShort-circuit evaluation
This is the important part. && and || are lazy: they stop evaluating as soon as the result is already decided.
- In
a && b, ifaisfalse, the result isfalsewhatever happens withb.bis not evaluated. - In
a || b, ifaistrue, the result istruewhatever happens withb.bis not evaluated.
flowchart TD
A["evaluate a"] --> B{"which operator?"}
B -- "logical AND" --> C{"is a false?"}
C -- "Yes" --> D["result false<br/><b>b is NOT evaluated</b>"]
C -- "No" --> E["evaluate b<br/>result = b"]
B -- "logical OR" --> F{"is a true?"}
F -- "Yes" --> G["result true<br/><b>b is NOT evaluated</b>"]
F -- "No" --> H["evaluate b<br/>result = b"]
Why does it matter? Because it lets you protect dangerous operations by putting the safety check first:
String employee = null;
// This FAILS with NullPointerException: it tries to call length() on null
boolean valid = employee.length() > 0 && employee != null;
// This WORKS: if employee is null, the first condition is false
// and the second one is not even evaluated
boolean valid = employee != null && employee.length() > 0;The order of the conditions is not cosmetic: it is functional. Remember it, because it is a pattern you will use constantly from module 6 onwards.
The non-short-circuit versions: & and |
There are & and | applied to booleans, which always evaluate both sides:
boolean r1 = false && methodThatPrints(); // the method does NOT run
boolean r2 = false & methodThatPrints(); // the method DOES runIn practice && and || are used almost always. Remember that & and | also have a completely different meaning on numbers, which is the next section.
- The ternary operator
It is Java's only operator with three operands. Its form is:
It reads: "if the condition is true, the result is the first thing; otherwise, the second".
int elapsedDays = 27;
final int LOAN_DAYS = 15;
int daysLate = elapsedDays > LOAN_DAYS
? elapsedDays - LOAN_DAYS
: 0;
System.out.println("Days late: " + daysLate); // 12This line does something very valuable for us: it stops the delay from being negative. If the book is returned after 10 days, 10 - 15 would give -5, and a negative fine would be absurd (the library paying the employee!). The ternary lets us fix it without using if, which we have not seen yet.
The most common use is adapting a piece of text:
double fine = daysLate * 0.25;
String status = daysLate > 0 ? "OVERDUE" : "ON TIME";
String currency = fine == 1.0 ? "euro" : "euros";
System.out.println("Loan status: " + status);
System.out.println("Amount: " + fine + " " + currency);They can be nested, but readability degrades very quickly:
// Readable, only just
String category = daysLate == 0 ? "ON TIME"
: daysLate <= 7 ? "MINOR"
: "SEVERE";Practical rule: the ternary is excellent for choosing a value; as soon as you want to run several different actions, you need an if, which arrives in lesson 02-01.
- Bitwise and shift operators
These operators work on the binary representation of integers. At first they look esoteric, but they show up in permissions, flags, cryptography, image processing and network protocols, and in technical interviews.
A reminder: an int is 32 bits. The number 12 is 00000000 00000000 00000000 00001100, and 10 is ...00001010.
The bitwise operators
| Operator | Name | Rule per bit | 12 op 10 |
Result |
|---|---|---|---|---|
& |
AND | 1 only if both are 1 | 1100 & 1010 |
1000 = 8 |
| |
OR | 1 if either is 1 | 1100 | 1010 |
1110 = 14 |
^ |
XOR | 1 if they are different | 1100 ^ 1010 |
0110 = 6 |
~ |
NOT | Inverts every bit | ~12 |
-13 |
int a = 12; // 1100
int b = 10; // 1010
System.out.println(a & b); // 8 -> 1000
System.out.println(a | b); // 14 -> 1110
System.out.println(a ^ b); // 6 -> 0110
System.out.println(~a); // -13
// Seeing the binary representation is the best way to understand it
System.out.println(Integer.toBinaryString(a)); // 1100
System.out.println(Integer.toBinaryString(a & b)); // 1000The result ~12 = -13 is surprising. The reason is that Java represents negative integers in two's complement, where ~x is always equivalent to -x - 1.
The shift operators
| Operator | Name | What it does | Example | Result |
|---|---|---|---|---|
<< |
Left shift | Moves the bits left, fills with zeros | 12 << 2 |
48 |
>> |
Signed right shift | Moves right, preserves the sign | 12 >> 2 |
3 |
>>> |
Unsigned right shift | Moves right, fills with zeros | -12 >>> 28 |
15 |
The relationship with arithmetic is direct and very easy to remember:
x << nis equivalent to multiplying by 2ⁿx >> nis equivalent to dividing by 2ⁿ (integer division)
System.out.println(12 << 1); // 24 (12 * 2)
System.out.println(12 << 2); // 48 (12 * 4)
System.out.println(12 >> 1); // 6 (12 / 2)
System.out.println(12 >> 2); // 3 (12 / 4)
System.out.println(-12 >> 2); // -3 preserves the sign
System.out.println(-12 >>> 2); // 1073741821 fills with zeros: the sign is lostA realistic use: permission flags
Where these operators really shine is packing several boolean options into a single integer. In BiblioTech we could encode an employee's permissions over the catalog:
// Each permission occupies a different bit
final int PERMISSION_VIEW = 1; // 0001
final int PERMISSION_LEND = 2; // 0010
final int PERMISSION_RESERVE = 4; // 0100
final int PERMISSION_ADMIN = 8; // 1000
// Marta Ruiz can view, borrow and reserve
int martaPermissions = PERMISSION_VIEW | PERMISSION_LEND | PERMISSION_RESERVE; // 0111 = 7
// Checking one specific permission: AND with the flag
boolean canReserve = (martaPermissions & PERMISSION_RESERVE) != 0; // true
boolean canAdmin = (martaPermissions & PERMISSION_ADMIN) != 0; // false
System.out.println("Marta Ruiz's permissions: " + martaPermissions);
System.out.println("Can reserve: " + canReserve);
System.out.println("Can administer: " + canAdmin);Four booleans in a single int, with instant checks. It is the mechanism behind Unix file permissions and a great many APIs.
- The
instanceof operator
instanceof operatorinstanceof checks whether an object is of a given type and returns a boolean:
It is mentioned here only to complete the catalogue of operators. Its real usefulness appears when there are type hierarchies and polymorphism, that is, from module 3 onwards. Since Java 16 it also supports pattern matching (if (obj instanceof String s)), which is studied in module 10.
- Precedence and associativity
When an expression combines several operators, Java applies a fixed order. From highest to lowest priority:
| Level | Operators | Associativity |
|---|---|---|
| 1 | () [] . |
Left to right |
| 2 | ++ -- (postfix) |
Left to right |
| 3 | ++ -- (prefix), + - (unary), !, ~, casts |
Right to left |
| 4 | * / % |
Left to right |
| 5 | + - |
Left to right |
| 6 | << >> >>> |
Left to right |
| 7 | < <= > >= instanceof |
Left to right |
| 8 | == != |
Left to right |
| 9 | & |
Left to right |
| 10 | ^ |
Left to right |
| 11 | | |
Left to right |
| 12 | && |
Left to right |
| 13 | || |
Left to right |
| 14 | ? : (ternary) |
Right to left |
| 15 | = += -= *= /= %= |
Right to left |
Associativity decides what happens when there are operators of the same level: 10 - 4 - 3 groups as (10 - 4) - 3 = 3, not as 10 - (4 - 3) = 9.
Examples where precedence changes the result:
System.out.println(2 + 3 * 4); // 14, not 20: * comes before +
System.out.println((2 + 3) * 4); // 20
System.out.println(10 - 4 - 3); // 3: left-associative
// Classic trap: & has LOWER priority than !=
// (a & b) != 0 is the correct form; a & b != 0 groups as a & (b != 0) and does not even compileAnd a real BiblioTech case where parentheses are missing:
int elapsedDays = 27;
final int LOAN_DAYS = 15;
final double DAILY_RATE = 0.25;
// WRONG: it evaluates (elapsedDays) - (LOAN_DAYS * DAILY_RATE)
double fine = elapsedDays - LOAN_DAYS * DAILY_RATE; // 23.25, absurd
// RIGHT: the parentheses express the intent
double fine = (elapsedDays - LOAN_DAYS) * DAILY_RATE; // 3.0The definitive advice: do not memorise the table. Use parentheses. Nobody has ever failed a code review for adding extra parentheses, and plenty of people have lost hours trusting precedence. Parentheses document your intent.
- Comparing strings:
== versus equals
== versus equalsIt is the classic Java mistake par excellence, and the worst part is that sometimes it works, which delays the diagnosis for weeks.
String a = "Effective Java";
String b = "Effective Java";
System.out.println(a == b); // true (!)
String c = new String("Effective Java");
System.out.println(a == c); // false (!)
System.out.println(a.equals(c)); // trueWhy it happens: the string pool
Remember from the previous lesson that a String variable does not contain the text, but a reference to an object on the heap. And == on references compares whether they point to the same object, not whether the content is equal.
The JVM maintains a string pool: a special area where it keeps literals. When it compiles String a = "Effective Java"; and then String b = "Effective Java";, it sees that the literal is identical and makes both variables point to the same object in the pool. That is why a == b gives true. It is a memory optimisation, not a promise of the language.
But new String("Effective Java") forces the creation of a new object, outside the pool. Now there are two objects with the same text at different addresses, and == gives false.
flowchart LR
subgraph Stack
A["a"]
B["b"]
C["c"]
end
subgraph Pool["String pool"]
P["#p1<br/>'Effective Java'"]
end
subgraph Heap["Heap"]
H["#h9<br/>'Effective Java'"]
end
A --> P
B --> P
C --> H
a == b compares #p1 == #p1 → true. a == c compares #p1 == #h9 → false. In both cases the text is the same.
The rule, without exceptions
To compare the content of two
Strings, always useequals(). Never==.
String enteredTitle = "effective java";
String catalogTitle = "Effective Java";
System.out.println(enteredTitle.equals(catalogTitle)); // false
System.out.println(enteredTitle.equalsIgnoreCase(catalogTitle)); // trueWhy the danger is real: strings arriving from a Scanner, from a file or from the network are not in the pool. So your == comparison will work perfectly in tests with literals and fail with real data.
A common defensive technique is putting the literal on the left, because a literal is never null:
// If isbn were null, this would throw NullPointerException
if (isbn.equals("978-0000000001")) { ... }
// This is safe even if isbn is null
if ("978-0000000001".equals(isbn)) { ... }Summary table:
| Comparison | With primitives | With objects (String) |
|---|---|---|
== |
Compares values. Correct | Compares references. Almost always wrong |
equals() |
Does not exist (primitives are not objects) | Compares content. Correct |
- The precision of
double and why BigDecimal exists
double and why BigDecimal existsOne last warning, particularly relevant because BiblioTech handles money:
System.out.println(0.1 + 0.2); // 0.30000000000000004
System.out.println(0.1 + 0.2 == 0.3); // false
System.out.println(1.03 - 0.42); // 0.6100000000000001This is not a Java bug: it happens in Python, JavaScript, C and practically every language. The cause is that float and double follow the IEEE 754 standard, which represents numbers in binary. Just as in decimal you cannot write 1/3 exactly (0.3333…), in binary you cannot write 0.1 exactly. The stored value is an excellent approximation, but an approximation.
Practical consequences:
- Never compare two
doubles with==. Compare whether their difference is smaller than a tolerance:Math.abs(a - b) < 0.0001. - Never use
doublefor money in a real system. The errors accumulate over thousands of operations and end up throwing the accounts off.
Java's solution is the BigDecimal class, which represents decimals with exact precision and lets you control rounding:
import java.math.BigDecimal;
BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.2");
System.out.println(a.add(b)); // exactly 0.3It has a cost: it is more verbose (you cannot use +, you have to call add()) and slower. It is studied in module 10; in this course we will keep using double for BiblioTech fines because the focus is on learning the language, but you have been warned: in a real billing system, BigDecimal.
Common Mistakes and Tips
5 / 2gives2. Integer division. Apply a cast to one of the operands:(double) 5 / 2.(double)(5 / 2)still gives2.0. The cast arrives after the division. It must be applied to an operand, not to the result.- Comparing
Strings with==. It works with literals and fails with input data. Always useequals(). - Comparing
doubles with==. Use a tolerance. - Forgetting the parentheses in
(a - b) * c. Multiplication has higher priority than subtraction. - Confusing
&with&&. On booleans,&&avoids evaluating the second operand;&does not. On integers,&is a completely different bitwise operation. - Getting the order of guarded conditions wrong.
x != null && x.length() > 0, never the other way round. - Writing acrobatic expressions with
++. If you have to stop and think, refactor it into two lines. - Tip: use parentheses whenever an expression has more than two operators. There is no performance penalty and the gain in clarity is enormous.
- Tip:
Integer.toBinaryString(n)is the best tool for understanding the bitwise operators. Try it in JShell. - Tip: the ternary is perfect for clamping values to zero (
x > 0 ? x : 0), a pattern you will use a lot in BiblioTech so that the days late are never negative.
Exercises
Exercise 1: Predict the output
Without running the code, write the output of each line and justify why.
public class OperatorTest {
public static void main(String[] args) {
System.out.println(7 / 2);
System.out.println(7 % 2);
System.out.println(7.0 / 2);
System.out.println((double)(7 / 2));
int x = 5;
System.out.println(x++);
System.out.println(x);
System.out.println(++x);
System.out.println(2 + 3 * 4);
System.out.println(10 - 4 - 3);
String a = "Refactoring";
String b = "Refactoring";
String c = new String("Refactoring");
System.out.println(a == b);
System.out.println(a == c);
System.out.println(a.equals(c));
System.out.println(0.1 + 0.2 == 0.3);
System.out.println(12 & 10);
System.out.println(12 << 2);
}
}Exercise 2: The BiblioTech fine calculator
Write the class FineCalculator that, from the following constants and data, calculates and displays the settlement of a loan:
- Constants:
LOAN_DAYS = 15,DAILY_RATE = 0.25,MAX_FINE = 20.0,SEVERE_SURCHARGE = 1.5(a multiplier applied if the delay exceeds 30 days). - Loan data: employee
"Diego Alonso", book"Refactoring",elapsedDays = 52.
It must calculate and display:
- The days late, which can never be negative (use the ternary operator).
- The delay expressed in whole weeks and leftover days (use
/and%). - The base fine (
daysLate * DAILY_RATE). - The fine with the surcharge if the delay exceeds 30 days (use the ternary).
- The final fine, never exceeding
MAX_FINE(use the ternary). - A severity label:
"ON TIME","MINOR"(1-7 days) or"SEVERE"(more than 7), with nested ternaries.
All of it without using if.
Exercise 3: Permission flags
Using the bitwise operators, write the class BiblioTechPermissions that:
- Defines four permission constants with values 1, 2, 4 and 8: view, lend, reserve and administer.
- Composes the permissions of three Nexus Software employees: Marta Ruiz (view + lend), Diego Alonso (view + lend + reserve) and Nuria Vidal (all four).
- Shows for each one the integer value of their permissions, its binary representation and whether they can administer.
- Adds the reserve permission to Marta Ruiz using
|=and shows the result.
Solutions
Solution 1
Justifications:
7 / 2→ 3. Both operands areint: truncated integer division.7 % 2→ 1. The remainder of dividing 7 by 2.7.0 / 2→ 3.5. Since7.0is adouble, the2is promoted and the division is a decimal one.(double)(7 / 2)→ 3.0. The parentheses force the integer division first (3) and the cast arrives afterwards.x++→ 5, and immediately afterxis 6. Post-increment returns the previous value.x→ 6. Confirms the earlier increment.++x→ 7. Pre-increment increments first (7) and returns the new value.2 + 3 * 4→ 14.*has higher precedence than+:3*4=12is computed and then2+12.10 - 4 - 3→ 3. Left associativity:(10-4)-3.a == b→ true. Both are identical literals, so the JVM shares them in the string pool and the two references point to the same object.a == c→ false.new String(...)creates a different object outside the pool; the references differ even though the text is the same.a.equals(c)→ true.equalscompares the content, which is indeed identical.0.1 + 0.2 == 0.3→ false. The IEEE 754 representation of0.1and0.2is approximate; the sum gives0.30000000000000004.12 & 10→ 8. In binary,1100 & 1010 = 1000, which is 8.12 << 2→ 48. Shifting 2 bits to the left is equivalent to multiplying by 2² = 4.
Solution 2
public class FineCalculator {
public static void main(String[] args) {
// === BiblioTech business rules (Nexus Software) ===
final int LOAN_DAYS = 15; // loan days with no surcharge
final double DAILY_RATE = 0.25; // euros per day late
final double MAX_FINE = 20.0; // absolute cap per loan
final double SEVERE_SURCHARGE = 1.5; // multiplier if the delay passes 30 days
final int SURCHARGE_THRESHOLD = 30;
final int MINOR_THRESHOLD = 7;
// === Loan data ===
String employee = "Diego Alonso";
String title = "Refactoring";
String isbn = "978-0000000003";
int elapsedDays = 52;
// 1. Days late, clamped to zero with the ternary operator.
// If it were returned early, the subtraction would be negative and
// would produce a negative fine, which makes no sense.
int daysLate = elapsedDays > LOAN_DAYS
? elapsedDays - LOAN_DAYS
: 0; // 52 - 15 = 37
// 2. Breakdown into weeks and days: / gives the quotient, % the remainder.
int weeks = daysLate / 7; // 5
int leftoverDays = daysLate % 7; // 2
// 3. Base fine. int * double -> Java promotes the int to a double.
double baseFine = daysLate * DAILY_RATE; // 37 * 0.25 = 9.25
// 4. Surcharge for a severe delay, again with the ternary.
double surchargedFine = daysLate > SURCHARGE_THRESHOLD
? baseFine * SEVERE_SURCHARGE
: baseFine; // 9.25 * 1.5 = 13.875
// 5. Cap: the fine never exceeds MAX_FINE.
double finalFine = surchargedFine > MAX_FINE
? MAX_FINE
: surchargedFine; // 13.875 < 20 -> 13.875
// 6. Severity label with nested ternaries.
// They are evaluated in a chain, top to bottom, like a ladder.
String severity = daysLate == 0 ? "ON TIME"
: daysLate <= MINOR_THRESHOLD ? "MINOR"
: "SEVERE";
// === Output ===
System.out.println("=== BiblioTech - Loan settlement ===");
System.out.println("Employee: " + employee);
System.out.println("Book: " + title);
System.out.println("ISBN: " + isbn);
System.out.println("Elapsed days: " + elapsedDays
+ " (standard loan: " + LOAN_DAYS + " days)");
System.out.println();
System.out.println("Days late: " + daysLate
+ " (" + weeks + " weeks and " + leftoverDays + " days)");
System.out.println("Severity: " + severity);
System.out.println("Base fine: " + baseFine + " EUR");
System.out.println("With surcharge: " + surchargedFine + " EUR");
System.out.println("FINAL FINE: " + finalFine + " EUR");
}
}Output:
=== BiblioTech - Loan settlement === Employee: Diego Alonso Book: Refactoring ISBN: 978-0000000003 Elapsed days: 52 (standard loan: 15 days) Days late: 37 (5 weeks and 2 days) Severity: SEVERE Base fine: 9.25 EUR With surcharge: 13.875 EUR FINAL FINE: 13.875 EUR
Look at the 13.875: three decimals for an amount in euros. It is exactly the kind of result that in a real system would demand BigDecimal or, at the very least, formatting to two decimals. We will solve that last part in the next lesson with printf.
Solution 3
public class BiblioTechPermissions {
public static void main(String[] args) {
// 1. Each permission occupies ONE distinct bit: powers of 2.
// That way none interferes with the others when combined.
final int VIEW = 1; // 0001
final int LEND = 2; // 0010
final int RESERVE = 4; // 0100
final int ADMINISTER = 8; // 1000
// 2. They are combined with OR: it turns on the bits of both operands.
int martaPermissions = VIEW | LEND; // 0011 = 3
int diegoPermissions = VIEW | LEND | RESERVE; // 0111 = 7
int nuriaPermissions = VIEW | LEND | RESERVE | ADMINISTER; // 1111 = 15
// 3. It is checked with AND: if the bit is on, the result is not zero.
boolean martaAdmin = (martaPermissions & ADMINISTER) != 0; // false
boolean diegoAdmin = (diegoPermissions & ADMINISTER) != 0; // false
boolean nuriaAdmin = (nuriaPermissions & ADMINISTER) != 0; // true
System.out.println("=== BiblioTech - Catalog permissions ===");
System.out.println("Marta Ruiz: " + martaPermissions
+ " (" + Integer.toBinaryString(martaPermissions) + ")"
+ " admin=" + martaAdmin);
System.out.println("Diego Alonso: " + diegoPermissions
+ " (" + Integer.toBinaryString(diegoPermissions) + ")"
+ " admin=" + diegoAdmin);
System.out.println("Nuria Vidal: " + nuriaPermissions
+ " (" + Integer.toBinaryString(nuriaPermissions) + ")"
+ " admin=" + nuriaAdmin);
// 4. Adding a permission with |= : turns that bit on without touching the rest.
martaPermissions |= RESERVE; // 0011 | 0100 = 0111 = 7
System.out.println();
System.out.println("After granting RESERVE to Marta Ruiz:");
System.out.println("Marta Ruiz: " + martaPermissions
+ " (" + Integer.toBinaryString(martaPermissions) + ")"
+ " can reserve=" + ((martaPermissions & RESERVE) != 0));
}
}Output:
=== BiblioTech - Catalog permissions === Marta Ruiz: 3 (11) admin=false Diego Alonso: 7 (111) admin=false Nuria Vidal: 15 (1111) admin=true After granting RESERVE to Marta Ruiz: Marta Ruiz: 7 (111) can reserve=true
Two details worth attention:
- The parentheses in
(martaPermissions & ADMINISTER) != 0are mandatory, not optional.&has lower precedence than!=, so without them Java would try to evaluatemartaPermissions & (ADMINISTER != 0), which does not even compile because it mixesintandboolean. Integer.toBinaryStringdoes not show leading zeros: 3 appears as11, not as0011. That is normal; it represents the same value.
Conclusion
You now have a grip on Java's full operator catalogue: arithmetic with its integer-division trap, the modulo and its natural pairing with /, compound assignment with its hidden implicit cast, pre- and post-increment and when the difference matters, comparisons that produce booleans, the logical operators with the short-circuit evaluation that lets you guard dangerous expressions, the ternary for choosing values without if, bitwise operations applied to permission flags, precedence and the advice to use parentheses, the critical difference between == and equals explained by the string pool, and the precision limits of double that will one day lead you to BigDecimal. With all of that, BiblioTech can already calculate a complete fine: days late clamped to zero, a severity surcharge and a maximum cap.
In the next lesson, Console Input and Output, we will fix what has been left ugly in these examples: we will stop printing amounts like 13.875 and learn to format output with printf (%.2f, widths, alignment), and above all we will stop having the data hard-coded so we can ask the user for it with Scanner. It is the last ingredient before building the first complete version of BiblioTechApp.
Java Programming Course
Module 1: Introduction to Java
- Introduction to Java
- Setting Up the Development Environment
- Basic Syntax and Structure
- Variables and Data Types
- Operators
- Console Input and Output
- Your First Complete Program: BiblioTech
Module 2: Control Flow
- Conditional Statements
- Loops
- Switch Statements
- Break and Continue
- Debugging and Execution Traces
- Project: The BiblioTech Interactive Menu
Module 3: Object-Oriented Programming
- Introduction to OOP
- Classes and Objects
- Methods
- Constructors
- Inheritance
- Polymorphism
- Encapsulation
- Abstraction
- The Object Class: equals, hashCode and toString
Module 4: Advanced Object-Oriented Programming
- Interfaces
- Abstract Classes
- Inner Classes
- Anonymous Classes
- Lambda Expressions
- Functional Interfaces and Method References
- Enums and Records
Module 5: Data Structures and Collections
- Arrays
- The Collections Framework
- ArrayList
- LinkedList
- HashMap
- HashSet
- Queue and Deque
- Stack
- Sorting and Searching Collections
Module 6: Exception Handling
- Introduction to Exceptions
- The Try-Catch Block
- Throw and Throws
- Custom Exceptions
- The Finally Block
- Try-with-resources and AutoCloseable
- Error Handling Strategies and Logging
Module 7: File Input/Output
- Reading Files
- Writing Files
- File Streams
- BufferedReader and BufferedWriter
- Serialization
- The NIO.2 API: Path and Files
- Interchange Formats: CSV and Properties
Module 8: Multithreading and Concurrency
- Introduction to Multithreading
- Creating Threads
- Thread Lifecycle
- Synchronization
- Concurrency Utilities
- Concurrent Collections and Atomic Variables
- Asynchronous Tasks with CompletableFuture
Module 9: Networking
- Introduction to Networking
- Sockets
- ServerSocket
- DatagramSocket and DatagramPacket
- URL and HttpURLConnection
- The Modern HTTP Client
Module 10: Advanced Topics
- Generics
- Annotations
- Reflection
- Java 8 Features: Streams and Optional
- Dates and Times with java.time
- Java 9 and Beyond
- Memory, Garbage Collection and Performance
Module 11: Java Frameworks and Libraries
- Introduction to Java Frameworks
- Spring Framework
- Hibernate
- JUnit
- Maven
- Advanced Testing with Mockito
- Essential Ecosystem Libraries
