Skip to main content

Variables, Types & Operators

Java is a statically typed language -- every variable has a declared type, and the compiler enforces it. This chapter covers all the primitive types, strings, type casting, and operators.

Declaring variables

int age = 25;
String name = "Ada";
double price = 9.99;
boolean isActive = true;

System.out.println(age);
System.out.println(name);
System.out.println(price);
System.out.println(isActive);

Result:

25
Ada
9.99
true

A variable declaration has three parts: type, name, and (optionally) an initial value.

int count;          // declaration without initialization
count = 10; // assignment
int total = 100; // declaration with initialization

final -- constants

final prevents reassignment (similar to const in JavaScript):

final int MAX_RETRIES = 3;
System.out.println(MAX_RETRIES);

// MAX_RETRIES = 5; // error: cannot assign a value to final variable

Result:

3

Convention: constants use UPPER_SNAKE_CASE.

var -- type inference (Java 10+)

The compiler can infer the type from the right-hand side:

var message = "Hello";   // inferred as String
var count = 42; // inferred as int
var pi = 3.14; // inferred as double

System.out.println(message.getClass().getSimpleName());
System.out.println(((Object) count).getClass().getSimpleName());

Result:

String
Integer

var is just syntactic sugar -- the variable is still statically typed. You cannot reassign it to a different type:

var x = 10;
// x = "hello"; // error: incompatible types

Use var when the type is obvious from the right side. Prefer explicit types when clarity matters.

Primitive types

Java has eight primitive types. They store values directly (not as objects):

TypeSizeRangeDefaultExample
byte8 bits-128 to 1270byte b = 100;
short16 bits-32,768 to 32,7670short s = 30000;
int32 bits~-2.1 billion to ~2.1 billion0int i = 42;
long64 bits~-9.2 quintillion to ~9.2 quintillion0Llong l = 123456789L;
float32 bits~7 decimal digits0.0ffloat f = 3.14f;
double64 bits~15 decimal digits0.0double d = 3.14;
boolean1 bit*true or falsefalseboolean b = true;
char16 bitsUnicode character'\u0000'char c = 'A';

*boolean actual size depends on the JVM implementation.

int and long

int population = 8_000_000;         // underscores for readability
long worldPopulation = 8_000_000_000L; // 'L' suffix for long literals

System.out.println(population);
System.out.println(worldPopulation);

Result:

8000000
8000000000

Without the L suffix, the compiler treats a number literal as int. If the value exceeds int range, you get a compile error.

double and float

double precise = 3.141592653589793;
float less = 3.14f; // 'f' suffix required for float literals

System.out.println(precise);
System.out.println(less);
System.out.println(0.1 + 0.2); // same floating-point quirk as any language

Result:

3.141592653589793
3.14
0.30000000000000004

Use double by default. Use float only when memory is a concern (e.g., graphics, large arrays).

boolean

boolean isJavaFun = true;
boolean isColdOutside = false;

System.out.println(isJavaFun);
System.out.println(isColdOutside);

Result:

true
false

Unlike JavaScript, Java does not coerce other types to booleans. if (1) is a compile error -- only boolean expressions are allowed in conditions.

char

A single Unicode character, wrapped in single quotes:

char letter = 'A';
char digit = '7';
char emoji = '★';

System.out.println(letter);
System.out.println(digit);
System.out.println(emoji);
System.out.println((int) letter); // numeric value

Result:

A
7

65

Strings

String is not a primitive -- it is a class. But it is so fundamental that it has special support:

String greeting = "Hello, world!";
System.out.println(greeting);
System.out.println(greeting.length());

Result:

Hello, world!
13

Strings are immutable

Once created, a String cannot be changed. Methods like toUpperCase() return a new string:

String original = "hello";
String upper = original.toUpperCase();

System.out.println(original); // unchanged
System.out.println(upper);

Result:

hello
HELLO

Common string methods

String s = "Hello, World!";

System.out.println(s.length()); // 13
System.out.println(s.charAt(0)); // H
System.out.println(s.substring(7)); // World!
System.out.println(s.substring(0, 5)); // Hello
System.out.println(s.toLowerCase()); // hello, world!
System.out.println(s.toUpperCase()); // HELLO, WORLD!
System.out.println(s.contains("World")); // true
System.out.println(s.startsWith("Hello")); // true
System.out.println(s.indexOf("World")); // 7
System.out.println(s.replace("World", "Java")); // Hello, Java!
System.out.println(s.trim()); // trims whitespace
System.out.println(s.isEmpty()); // false
System.out.println(s.isBlank()); // false (Java 11+)

Result:

13
H
World!
Hello
hello, world!
HELLO, WORLD!
true
true
7
Hello, Java!
Hello, World!
false
false

String comparison -- use equals, not ==

String a = "hello";
String b = "hello";
String c = new String("hello");

System.out.println(a == b); // true (same string pool reference)
System.out.println(a == c); // false (different objects!)
System.out.println(a.equals(c)); // true (same content)

Result:

true
false
true

Always use .equals() to compare strings. The == operator compares object references, not content. It sometimes works due to string interning, but it is unreliable.

String concatenation

String first = "Hello";
String second = "World";

// + operator
String combined = first + ", " + second + "!";
System.out.println(combined);

// String.format
String formatted = String.format("%s is %d years old", "Ada", 36);
System.out.println(formatted);

// printf (prints directly, no newline by default)
System.out.printf("Price: $%.2f%n", 9.99);

Result:

Hello, World!
Ada is 36 years old
Price: $9.99

Common format specifiers: %s (string), %d (integer), %f (float/double), %.2f (2 decimal places), %n (newline).

Text blocks (Java 15+)

Multi-line strings with """:

String json = """
{
"name": "Ada",
"age": 36
}
""";
System.out.println(json);

Result:

{
"name": "Ada",
"age": 36
}

The indentation of the closing """ determines the left margin.

Type casting

Widening (automatic)

Going from a smaller type to a larger type is automatic and safe:

int intValue = 42;
long longValue = intValue; // int → long (automatic)
double doubleValue = intValue; // int → double (automatic)

System.out.println(longValue);
System.out.println(doubleValue);

Result:

42
42.0

Widening order: byteshortintlongfloatdouble

Narrowing (explicit cast required)

Going from a larger type to a smaller type requires an explicit cast and may lose data:

double pi = 3.14159;
int truncated = (int) pi; // double → int (truncates, does not round)

System.out.println(truncated);

long big = 130;
byte small = (byte) big; // 130 overflows byte range (-128 to 127)
System.out.println(small);

Result:

3
-126

The 130 overflows byte and wraps around. Be careful with narrowing casts.

Parsing strings to numbers

int num = Integer.parseInt("42");
double dec = Double.parseDouble("3.14");
long big = Long.parseLong("1000000");

System.out.println(num);
System.out.println(dec);
System.out.println(big);

Result:

42
3.14
1000000

Invalid input throws NumberFormatException:

try {
int bad = Integer.parseInt("abc");
} catch (NumberFormatException e) {
System.out.println("Cannot parse: " + e.getMessage());
}

Result:

Cannot parse: For input string: "abc"

Numbers to strings

String s1 = String.valueOf(42);
String s2 = Integer.toString(42);
String s3 = "" + 42; // concatenation trick

System.out.println(s1);
System.out.println(s2);
System.out.println(s3);

Result:

42
42
42

Wrapper classes

Each primitive has a corresponding wrapper class that turns it into an object:

PrimitiveWrapper
intInteger
longLong
doubleDouble
floatFloat
booleanBoolean
charCharacter
byteByte
shortShort

Java autoboxes -- it automatically converts between primitives and wrappers:

Integer boxed = 42;        // autoboxing: int → Integer
int unboxed = boxed; // unboxing: Integer → int

System.out.println(boxed);
System.out.println(unboxed);

Result:

42
42

Wrapper classes are needed when working with collections (chapter 7) because collections cannot hold primitives.

Arithmetic operators

System.out.println(10 + 3);  // 13  -- addition
System.out.println(10 - 3); // 7 -- subtraction
System.out.println(10 * 3); // 30 -- multiplication
System.out.println(10 / 3); // 3 -- integer division (truncates!)
System.out.println(10 % 3); // 1 -- remainder (modulo)
System.out.println(10.0 / 3); // 3.3333... -- double division

Result:

13
7
30
3
1
3.3333333333333335

Integer division truncates. 10 / 3 is 3, not 3.33. If you want decimal division, at least one operand must be a double or float.

Increment and decrement

int count = 5;
count++;
System.out.println(count); // 6

count--;
System.out.println(count); // 5

// Prefix vs postfix
int a = 5;
System.out.println(a++); // prints 5, then increments
System.out.println(a); // 6

int b = 5;
System.out.println(++b); // increments first, then prints 6

Result:

6
5
5
6
6

Compound assignment

int x = 10;
x += 5; System.out.println(x); // 15
x -= 3; System.out.println(x); // 12
x *= 2; System.out.println(x); // 24
x /= 4; System.out.println(x); // 6
x %= 4; System.out.println(x); // 2

Result:

15
12
24
6
2

Comparison operators

All comparisons return boolean:

System.out.println(5 > 3);   // true
System.out.println(5 < 3); // false
System.out.println(5 >= 5); // true
System.out.println(5 <= 4); // false
System.out.println(5 == 5); // true
System.out.println(5 != 3); // true

Result:

true
false
true
false
true
true

For primitives, == compares values and works correctly. For objects (including String), use .equals() to compare content.

Logical operators

System.out.println(true && true);   // true  -- AND
System.out.println(true && false); // false
System.out.println(false || true); // true -- OR
System.out.println(false || false); // false
System.out.println(!true); // false -- NOT
System.out.println(!false); // true

Result:

true
false
true
false
false
true

Short-circuit evaluation

&& stops at the first false; || stops at the first true:

String name = null;

// Without short-circuit -- would throw NullPointerException
// if (name.length() > 0) { ... }

// With short-circuit -- safe, because the first condition is false
if (name != null && name.length() > 0) {
System.out.println("Name: " + name);
} else {
System.out.println("No name provided");
}

Result:

No name provided

The Math class

Common math operations:

System.out.println(Math.abs(-42));       // 42
System.out.println(Math.max(10, 20)); // 20
System.out.println(Math.min(10, 20)); // 10
System.out.println(Math.pow(2, 10)); // 1024.0
System.out.println(Math.sqrt(144)); // 12.0
System.out.println(Math.round(3.7)); // 4
System.out.println(Math.floor(3.9)); // 3.0
System.out.println(Math.ceil(3.1)); // 4.0
System.out.println(Math.random()); // random double between 0.0 and 1.0

Result:

42
20
10
1024.0
12.0
4
3.0
4.0
0.7231... (varies)

Random integers in a range

// Random int between 1 and 6 (inclusive) -- like a dice roll
int dice = (int) (Math.random() * 6) + 1;
System.out.println("Dice: " + dice);

Operator precedence

From highest to lowest (simplified):

PriorityOperators
1() (parentheses)
2++ -- ! (unary)
3* / %
4+ -
5< <= > >=
6== !=
7&&
8||
9= += -= etc.

When in doubt, use parentheses to make your intent explicit:

// Unclear
int result = 2 + 3 * 4; // 14 (multiplication first)

// Clear
int result2 = 2 + (3 * 4); // 14 -- same but intent is obvious
int result3 = (2 + 3) * 4; // 20 -- different result

Summary

  • Java has 8 primitive types -- int and double are the most common.
  • String is a class, not a primitive. It is immutable. Always compare with .equals().
  • final makes a variable constant. var lets the compiler infer the type.
  • Integer division truncates -- use double for decimal results.
  • Widening casts are automatic; narrowing casts require (type) and may lose data.
  • Wrapper classes (Integer, Double, etc.) autobox between primitives and objects.
  • Use && and || for short-circuit logic.
  • Use parentheses when operator precedence is unclear.

Next up: Control Flow -- decisions and loops.