Skip to main content
Java · lesson 5 of 66
Open the Java page →
Java beginner Lesson 5 of 66

Variables 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

  1. Write safeDivide(int a, int b) returning a double so that safeDivide(7, 2) is 3.5 without 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.

  1. Detect a NaN result: print Double.NaN + 5, Double.NaN == Double.NaN and Double.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.

  1. Read a temperature and a unit (C or F) 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.

  1. Predict ~0, ~(-1) and 5 & -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.

Frequently Asked Questions

What is the difference between = and == in Java?
A single = assigns a value to a variable, while == compares two values and produces a boolean. For primitives such as int, == compares the values; for objects such as String it compares whether both references point to the same object, so use equals() to compare object contents.
What does final mean on a Java variable?
A final variable can be assigned exactly once, and any later assignment is a compile error. Constants are usually declared static final and named in UPPER_SNAKE_CASE, such as MAX_ATTEMPTS. For an object, final fixes the reference, not the contents: a final List can still have elements added.
Why does 7 / 2 equal 3 in Java?
When both operands are integers, Java performs integer division and discards the fractional part, truncating toward zero. Storing the result in a double does not help because the division has already happened. Make at least one operand a double, for example (double) 7 / 2 or 7.0 / 2, to get 3.5.
How do I read user input in Java?
Create a Scanner on System.in and call nextLine() for a whole line, next() for one word, or nextInt() and nextDouble() for numbers. Beware that nextInt() leaves the newline in the buffer, so a following nextLine() returns an empty string unless you call nextLine() once to consume it.
Does var make Java dynamically typed?
No. var, added in Java 10, only lets the compiler infer the type of a local variable from its initialiser. The type is fixed at compile time exactly as if you had written it, so assigning a value of a different type later is still a compile error. var cannot be used without an initialiser or for fields.