Java · lesson 5 of 66
Beginner
17- 1 Introduction to Java
- 2 Setting Up Java
- 3 Data Types in Java
- 4 Operators in Java
- 5 Variables and Operators in Java
- 6 Control Flow in Java
- 7 Arrays in Java
- 8 Functions (Methods) in Java
- 9 Strings in Java
- 10 Methods in Java
- 11 Java OOP — Complete Guide
- 12 Introduction to Object-Oriented Programming in Java
- 13 Encapsulation in Java
- 14 Inheritance in Java
- 15 Polymorphism in Java
- 17 Abstraction in Java
- 18 Error Types in Java
Intermediate
38- 16 Regular Expressions in Java
- 19 Interfaces in Java — A Deep Dive
- 20 Abstract Classes in Java
- 21 Method Overloading in Java
- 22 Composition vs Inheritance in Java
- 23 Generics in Java
- 27 Exception Handling in Java
- 28 Java Streams API
- 29 Collections Framework in Java
- 30 Optional in Java
- 31 File Handling in Java
- 32 Records in Java
- 33 Java 8 Features
- 34 Sealed Classes and Interfaces in Java
- 35 Multithreading in Java
- 37 JDBC — Database Access in Java
- 39 Maven — Java Build and Dependency Management
- 42 Gradle — Modern Java Build Tool
- 44 Unit Testing with JUnit 5 and Mockito
- 45 Logging in Java with SLF4J and Logback
- 49 Spring Boot — Introduction
- 50 Dependency Injection in Spring Boot
- 51 Java Project Ideas
- 52 Building REST APIs with Spring Boot
- 53 Validation in Spring Boot
- 54 Spring Data JPA — Database Access
- 55 Exception Handling in Spring Boot
- 56 Spring Security — Authentication and Authorization
- 57 JWT Authentication in Spring Boot
- 58 Dockerizing Spring Boot Applications
- 59 Java Enums: Beyond Simple Constants
- 60 Inner and Anonymous Classes in Java
- 61 Java Generics: Bounded Types, Wildcards, and Generic Methods
- 62 Java Date/Time API: LocalDate, LocalDateTime, and Duration
- 63 Java NIO.2: Paths, Files, and WatchService
- 64 Java Annotations: Custom Types and Meta-Information
- 65 Java Reflection: Dynamic Type Inspection and Method Invocation
- 66 Java Localization: ResourceBundle and Locale Support
Advanced
11Variables and Operators in Java
Learn Java variables and operators by running them: types, var, final, integer division, ++, logic and bitwise operators, Scanner input and Math.
By SkillByExample · Updated
A variable is a named, typed storage location; an operator combines values into new ones.
Java checks types at compile time, so most mistakes with variables are caught before the program
runs — but a few operator rules, like integer division, only show up in the output. Every example
is a single file run with the JDK 21 source launcher (java File.java).
Declaring variables, var and final
import java.util.ArrayList;
public class VariablesDemo {
static final int MAX_ATTEMPTS = 3; // a constant
public static void main(String[] args) {
int age = 25;
double price = 19.99;
boolean active = true;
char grade = 'A';
long population = 8_100_000_000L; // too big for int: needs L
String name = "Alice";
var count = 10; // inferred as int
var items = new ArrayList<String>(); // inferred as ArrayList<String>
items.add(name);
System.out.println(name + " " + age + " " + price + " " + active + " " + grade);
System.out.println(population + " " + count + " " + items + " " + MAX_ATTEMPTS);
System.out.println(((Object) count).getClass().getSimpleName());
System.out.println(Integer.MAX_VALUE + " " + (Integer.MAX_VALUE + 1));
}
}
$ java VariablesDemo.java
Alice 25 19.99 true A
8100000000 10 [Alice] 3
Integer
2147483647 -2147483648
Every variable has a fixed type. var saves typing but not checking: count is an int forever,
and count = "ten" would not compile. Integers silently overflow: one past Integer.MAX_VALUE
wraps around to the most negative int, which is why big counts use long.
What the compiler refuses
public class CompileErrors {
public static void main(String[] args) {
final int MAX = 3;
MAX = 5;
var x;
int whole = 3.7;
}
}
$ java CompileErrors.java
CompileErrors.java:4: error: cannot assign a value to final variable MAX
MAX = 5;
^
CompileErrors.java:5: error: cannot infer type for local variable x
var x;
^
(cannot use 'var' on variable without initializer)
CompileErrors.java:6: error: incompatible types: possible lossy conversion from double to int
int whole = 3.7;
^
3 errors
error: compilation failed
final variables cannot be reassigned, var needs an initialiser to infer a type from, and a
double will not narrow into an int without an explicit cast — each is a compile error, so the
program never runs. Write int whole = (int) 3.7; when you really want the truncated 3.
Arithmetic and integer division
public class ArithDemo {
public static void main(String[] args) {
int a = 17, b = 5;
System.out.println((a + b) + " " + (a - b) + " " + (a * b) + " " + (a / b) + " " + (a % b));
System.out.println(17.0 / 5 + " " + (double) a / b + " " + (double) (a / b));
System.out.println(-17 / 5 + " " + -17 % 5);
int total = 7, count = 2;
double average = total / count;
double correct = (double) total / count;
System.out.println(average + " vs " + correct);
System.out.println(0.1 + 0.2);
System.out.println(1 + 2 + "3" + 4 + 5);
}
}
$ java ArithDemo.java
22 12 85 3 2
3.4 3.4 3.0
-3 -2
3.0 vs 3.5
0.30000000000000004
3345
With two int operands, / drops the fraction and % gives the remainder. The cast must apply
to an operand: (double) (a / b) casts after the integer division and still loses the .4.
Division truncates toward zero, so -17 / 5 is -3 and the remainder keeps the sign of the
dividend. 0.1 + 0.2 shows binary floating point cannot store most decimals exactly. And +
works left to right: 1 + 2 is arithmetic, then everything after the "3" is string joining.
Increment, decrement and compound assignment
public class IncrementDemo {
public static void main(String[] args) {
int a = 5;
int b = a++; // use, then increment
int c = ++a; // increment, then use
System.out.println("a=" + a + " b=" + b + " c=" + c);
int n = 10;
n += 5; System.out.print(n + " ");
n -= 3; System.out.print(n + " ");
n *= 2; System.out.print(n + " ");
n /= 4; System.out.print(n + " ");
n %= 4; System.out.println(n);
int small = 10;
small += 2.7; // compiles: compound assignment casts back to int
System.out.println(small);
}
}
$ java IncrementDemo.java
a=7 b=5 c=7
15 12 24 6 2
12
a++ evaluates to the old value and then increments; ++a increments first. Compound operators
read the variable’s current value, so each line builds on the last. They also hide a cast:
small += 2.7 means small = (int) (small + 2.7), which silently truncates to 12.
Comparison and logical operators
public class LogicDemo {
static boolean check(String label, boolean value) {
System.out.println(" evaluated " + label);
return value;
}
public static void main(String[] args) {
int a = 10, b = 20;
System.out.println((a == b) + " " + (a != b) + " " + (a < b) + " " + (a >= b));
System.out.println("&& result: " + (check("left", false) && check("right", true)));
System.out.println("& result: " + (check("left", false) & check("right", true)));
System.out.println("|| result: " + (check("left", true) || check("right", false)));
int x = 0;
System.out.println(x != 0 && 10 / x > 2 ? "big" : "guarded, no division by zero");
int score = 75;
System.out.println(score >= 60 ? "Pass" : "Fail");
}
}
$ java LogicDemo.java
false true true false
evaluated left
&& result: false
evaluated left
evaluated right
& result: false
evaluated left
|| result: true
guarded, no division by zero
Pass
Comparisons produce boolean. && and || short-circuit: once the left side decides the
answer, the right side is never evaluated — & always evaluates both. That is what makes
x != 0 && 10 / x > 2 safe. The ternary cond ? a : b picks one of two values.
Bitwise operators
public class BitwiseDemo {
public static void main(String[] args) {
int a = 0b1010, b = 0b1100; // 10 and 12
System.out.println((a & b) + " " + (a | b) + " " + (a ^ b) + " " + ~a);
System.out.println(Integer.toBinaryString(a & b) + " " + Integer.toBinaryString(a ^ b));
System.out.println((a << 1) + " " + (a >> 1) + " " + (-16 >> 2) + " " + (-16 >>> 28));
final int READ = 1, WRITE = 2, EXEC = 4; // flags packed in one int
int perms = READ | WRITE;
System.out.println("can write: " + ((perms & WRITE) != 0) + ", can exec: " + ((perms & EXEC) != 0));
}
}
$ java BitwiseDemo.java
8 14 6 -11
1000 110
20 5 -4 15
can write: true, can exec: false
&, | and ^ work bit by bit. ~a flips all 32 bits, which in two’s complement equals
-(a + 1), hence -11. << and >> shift (multiplying or dividing by powers of two); >>
keeps the sign while >>> shifts zeros in, turning a negative number into a large positive one.
Flags combine with | and are tested with &.
Precedence
public class PrecedenceDemo {
public static void main(String[] args) {
System.out.println(2 + 3 * 4);
System.out.println((2 + 3) * 4);
System.out.println(10 - 4 - 3);
System.out.println(true || false && false);
System.out.println(1 + 1 == 2 ? "yes" : "no");
int x = 5;
x += x++ * 2;
System.out.println(x);
}
}
$ java PrecedenceDemo.java
14
20
3
true
yes
15
* binds tighter than +, && tighter than ||, and arithmetic tighter than ==; operators of
equal precedence run left to right, so 10 - 4 - 3 is 3. The last line is legal but unreadable
— Java reads x (5) before evaluating x++ * 2 (10) — so add parentheses or split statements
whenever precedence is not obvious.
Reading input with Scanner
import java.util.Scanner;
public class InputDemo {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.print("Name: ");
String name = in.nextLine();
System.out.print("Age: ");
int age = in.nextInt();
String rest = in.nextLine(); // the newline nextInt left behind
System.out.print("City: ");
String city = in.nextLine();
System.out.printf("%nHello %s (%d) from %s; leftover was [%s]%n", name, age, city, rest);
}
}
$ printf 'Alice Smith\n25\nParis\n' | java InputDemo.java
Name: Age: City:
Hello Alice Smith (25) from Paris; leftover was []
Input is piped in, so the typed text does not appear after each prompt the way it would in an
interactive terminal. nextLine reads a whole line; nextInt reads just the number and leaves
the newline behind, which the extra nextLine consumes — it comes back empty. Without it, city
would be the empty string.
The Math class
public class MathDemo {
public static void main(String[] args) {
System.out.println(Math.abs(-42) + " " + Math.max(10, 20) + " " + Math.min(10, 20));
System.out.println(Math.pow(2, 10) + " " + Math.sqrt(144) + " " + Math.cbrt(27));
System.out.println(Math.ceil(3.2) + " " + Math.floor(3.9) + " " + Math.round(3.5) + " " + Math.round(-3.5));
System.out.println(Math.floorDiv(-17, 5) + " " + Math.floorMod(-17, 5));
System.out.printf("%.4f %.4f%n", Math.PI, Math.E);
try {
Math.addExact(Integer.MAX_VALUE, 1);
} catch (ArithmeticException e) {
System.out.println("addExact: " + e.getMessage());
}
}
}
$ java MathDemo.java
42 20 10
1024.0 12.0 3.0
4.0 3.0 4 -3
-4 3
3.1416 2.7183
addExact: integer overflow
pow, sqrt, ceil and floor return double even for whole results. Math.round rounds
half up, so -3.5 becomes -3. floorDiv/floorMod round toward negative infinity, unlike
/ and %, which is what you want for things like wrapping clock arithmetic. addExact throws
instead of silently overflowing.
Practice
- Write
safeDivide(int a, int b)returning adoubleso thatsafeDivide(7, 2)is3.5without the caller casting.
Solution
public class SafeDivide {
static double safeDivide(int a, int b) {
return (double) a / b;
}
public static void main(String[] args) {
System.out.println(safeDivide(7, 2));
System.out.println(safeDivide(1, 0));
}
}
$ java SafeDivide.java
3.5
Infinity
Floating-point division by zero does not throw — it gives Infinity.
- Detect a
NaNresult: printDouble.NaN + 5,Double.NaN == Double.NaNandDouble.isNaN(Double.NaN). Which test works?
Solution
public class NaNCheck {
public static void main(String[] args) {
double nan = 0.0 / 0.0;
System.out.println(nan + 5);
System.out.println(nan == nan);
System.out.println(Double.isNaN(nan));
}
}
$ java NaNCheck.java
NaN
false
true
NaN is not equal to anything, including itself, so == can never detect it; use
Double.isNaN.
- Read a temperature and a unit (
CorF) from standard input and print the conversion.
Solution
import java.util.Scanner;
public class TempConverter {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
double temp = in.nextDouble();
String unit = in.next().toUpperCase();
double converted = unit.equals("C") ? temp * 9 / 5 + 32 : (temp - 32) * 5 / 9;
String target = unit.equals("C") ? "F" : "C";
System.out.printf("%.1f%s = %.1f%s%n", temp, unit, converted, target);
}
}
$ echo "100 C" | java TempConverter.java
100.0C = 212.0F
$ echo "98.6 f" | java TempConverter.java
98.6F = 37.0C
nextDouble and next both stop at whitespace, so the two values can share one line.
- Predict
~0,~(-1)and5 & -5, then run them.
Solution
public class TwosComplement {
public static void main(String[] args) {
System.out.println(~0);
System.out.println(~(-1));
System.out.println(5 & -5);
System.out.println(Integer.toBinaryString(-5));
}
}
$ java TwosComplement.java
-1
0
1
11111111111111111111111111111011
-1 is all 32 bits set, so ~0 is -1 and ~(-1) is 0. n & -n isolates the lowest set bit
of n, which for 5 (101) is 1.