Java Module 2 – Variables, types and operators
Variables and the eight primitive types
How to declare Java variables with a type or with var, the size and range of the eight primitive types, and how to write int, long, hex and binary literals.
What you will learn
- Declare variables with explicit types and with var, and predict the type var infers
- State the size and range of each primitive type
- Choose a primitive type for a value from its range, its fraction and how many values you keep
- Write numeric literals with underscores, hex, binary and long or float suffixes
- Explain which variables start with a default value and when the compiler refuses an unassigned local
Before you start
What you’ll learn · Before you start
What you will learn
- Declare variables with explicit types and with var, and predict the type var infers
- State the size and range of each primitive type
- Choose a primitive type for a value from its range, its fraction and how many values you keep
- Write numeric literals with underscores, hex, binary and long or float suffixes
- Explain which variables start with a default value and when the compiler refuses an unassigned local
Before you start
On this page
A variable is a named place that holds a value, and in Java every variable has a type that the compiler knows before
the program runs. The type decides which values fit and which operations make sense: int seats = 120; declares an
int called seats and stores 120 in it, while int seats = 3000000000; does not even compile, because an int
stops at 2,147,483,647. Java has eight primitive type: One of the eight value types built into Java: byte, short, int, long, float, double, char and boolean. A variable of a primitive type holds the value itself and has no methods. for plain values: four for
whole numbers (byte, short, int and long), two for numbers with a fractional part (float and double),
char for one UTF-16 code unit and boolean for true or false. Everything else, from String to the classes you
write, is a reference type: Every type that is not primitive: classes such as String, interfaces and arrays. A variable of a reference type holds a reference to an object, or null..
You will declare variables with a type and with var, step through the type the compiler gives each one, print the
limits of every primitive type on JDK 25, choose a type for a value by answering four questions, and write one number
in four bases. The last section shows why the compiler refuses to read a local variable that might have no value.
A declaration fixes the variable’s type
A declaration names the type first, then the variable, and usually gives it a first value. Assigning again replaces
the value; final forbids that, so a final variable keeps its first value for good:
Java (Declare.java) · Declare.java
// Variables with their types written out, then with var, which works the type out from the value.
void main() {
int seats = 120;
double ticketPrice = 249.50;
char row = 'K';
boolean soldOut = false;
long bytesStreamed = 7_340_032_000L;
seats = seats - 2; // assigning again changes the value
final int screens = 4; // final: this one is never assigned again
var shows = 3; // 3 is an int, so shows is an int
var halfPrice = ticketPrice / 2;
var initial = 'J';
var total = bytesStreamed * shows;
IO.println("seats = " + seats + ", row " + row + ", sold out: " + soldOut + ", screens: " + screens);
IO.println("shows is " + typeOf(shows) + ", halfPrice is " + typeOf(halfPrice) + " (" + halfPrice + ")");
IO.println("initial is " + typeOf(initial) + ", total is " + typeOf(total) + " (" + total + ")");
}
// One method per type: Java picks the one that matches the type the compiler gave the variable.
String typeOf(int x) { return "int"; }
String typeOf(long x) { return "long"; }
String typeOf(double x) { return "double"; }
String typeOf(char x) { return "char"; } Java (VarErrors.java) · VarErrors.java
void main() {
var count; // no value to work the type out from
var nothing = null; // null has no type of its own
var total = 10; // total is an int from here on
total = 9_000_000_000L; // a long value does not fit in it
} from session import run, version
version()
run("""
java Declare.java
!java VarErrors.java
""") Output
# openjdk version "25.0.4.1" LTS
$ java Declare.java
seats = 118, row K, sold out: false, screens: 4
shows is int, halfPrice is double (124.75)
initial is char, total is long (22020096000)
$ java VarErrors.java
VarErrors.java:2: error: cannot infer type for local variable count
var count; // no value to work the type out from
^
(cannot use 'var' on variable without initializer)
VarErrors.java:3: error: cannot infer type for local variable nothing
var nothing = null; // null has no type of its own
^
(variable initializer is 'null')
VarErrors.java:5: error: incompatible types: possible lossy conversion from long to int
total = 9_000_000_000L; // a long value does not fit in it
^
3 errors
error: compilation failed
[exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 types_session.py
Each run on this page is a short terminal session. The line starting with # names the JDK, a line starting with $
is a command, and the lines under it are what that command printed. java Declare.java compiles the file in memory
and runs it, so one command is enough. To try an example yourself, save the file and type the same command in a
terminal, in the folder where the file is.
var asks the compiler to take the type from the value on the right, which is called
local variable type inference: Writing var instead of a type for a local variable with an initial value: the compiler gives the variable the type of that value, so var shows = 3; declares an int. The type is fixed when the file compiles.. 3 is an int, so shows is an
int; ticketPrice / 2 is a double; 'J' is a char; and a long times an int is a long. The file proves it
with four methods called typeOf, one per type: Java calls the method whose parameter type matches the variable’s
type, so each line of output names the type the compiler chose. Step through the four declarations, then the three
lines of VarErrors.java that the compiler refuses:
The drawing shows the whole scene; its 9 steps are listed below.
Every step as text
- Four lines with var. Declare.java declares four variables with var and no type. For each one the compiler looks at the value after the = sign and gives the variable that value's type, before the program runs.
- 3 is an int. 3 is an int literal, so shows is an int. The method typeOf(int) is the one Java picked for it, and the run printed shows is int.
- A double divided by 2. ticketPrice is a double, and a double divided by the int 2 is a double, so halfPrice is a double. The run printed halfPrice is double (124.75).
- A char literal. 'J' in single quotes is a char literal, so initial is a char: the run printed initial is char.
- A long times an int. bytesStreamed is a long, and a long times an int is a long, so total is a long. The run printed 22020096000, a value no int can hold.
- No value, no type. VarErrors.java tries var count; with no value. There is nothing to take a type from, so the compiler refuses the line with cannot use 'var' on variable without initializer.
- null names no type. var nothing = null; is refused as well. null fits every reference type, so it names none of them, and the message says variable initializer is 'null'.
- The type stays fixed. var total = 10; makes total an int for good. Assigning 9_000_000_000L to it later is a possible lossy conversion from long to int, exactly as after int total = 10;.
- Chosen at compile time. Every type in this figure was chosen when the file was compiled. var changes what you type, not what the program does, which is why it is called type inference and not dynamic typing.
The type is fixed at compile time, which is why VarErrors.java does not compile. var count; has no value to take
a type from, null belongs to no single type, and once total is an int, a long value no longer fits in it. So
var is type inference, not dynamic typing: the compiled program is the same as if you had written the type
yourself. It works only for local variables that get a value where they are declared, loop variables included;
fields, method parameters and return types always name their type.
Reading var as a variable of any type
var total = 10; followed by total = 9_000_000_000L; does not compile, because the first line made total an
int. When the variable must hold larger values later, write long total = 10; or var total = 10L;.
Use var when the type is obvious from the right-hand side, as in var names = new ArrayList<String>(). Write the
type out when a reader would otherwise have to look elsewhere to learn it, as in var result = process(order).
Checkpoint
Check: Declaring with a type or with var
Each primitive type has a fixed range on every computer
Each primitive type takes a fixed number of bits. The diagram draws each type to scale, one box for every 8 bits:
The eight primitive types, drawn to scale by their size in bits
Text description of the diagram
One row per primitive type, with a box for every 8 bits.
- byte: 8 bits, from -128 to 127.
- short: 16 bits, from -32,768 to 32,767.
- char: 16 bits, from 0 to 65,535, one UTF-16 code unit.
- int: 32 bits, about plus or minus 2.1 billion.
- long: 64 bits, about plus or minus 9.2 × 10¹⁸.
- float: 32 bits, about 7 significant decimal digits.
- double: 64 bits, about 15 to 16 significant decimal digits.
- boolean: true or false; the language does not fix how many bits it takes.
The table adds the exact ranges, the value each type starts with when it is a field, and what it is usually for:
| Type and size | Range | Default in a field | Typical use |
|---|---|---|---|
byte, 8 bits |
-128 to 127 | 0 |
raw bytes of files and network data |
short, 16 bits |
-32,768 to 32,767 | 0 |
large arrays of small numbers, rarely anything else |
int, 32 bits |
-2,147,483,648 to 2,147,483,647 | 0 |
the default for whole numbers: counts, indexes, sizes |
long, 64 bits |
about ±9.2 × 10¹⁸ | 0L |
totals, IDs, times in milliseconds, amounts in cents |
float, 32 bits |
about ±3.4 × 10³⁸, 7 significant digits | 0.0f |
large arrays where memory matters, such as graphics |
double, 64 bits |
about ±1.8 × 10³⁰⁸, 15 to 16 significant digits | 0.0 |
the default for numbers with a fractional part |
char, 16 bits |
0 to 65,535 | '\u0000' |
one UTF-16 code unit, usually one character |
boolean, size not fixed |
true or false |
false |
conditions and yes-or-no facts |
The four whole-number types are signed: each stores negative and positive values in
two's complement: How Java stores signed whole numbers: the highest bit counts as minus 2 to the power n − 1, so an n-bit type runs from −2ⁿ⁻¹ to 2ⁿ⁻¹ − 1, and the byte 11111111 is -1., so a type of n bits runs from −2ⁿ⁻¹ to 2ⁿ⁻¹ − 1. char is the
only unsigned one. float and double are the IEEE 754 binary32 and binary64 formats, which
Floating point, rounding and BigDecimal explains. The Java Language
Specification fixes every one of these ranges, so they are the same on every computer and every JVM. The classes
Byte, Short, Integer, Long, Float, Double and Character hold the limits as constants, so you never need
to type them from memory:
// The size and the range of each numeric primitive type, from the constants of its wrapper class.
void main() {
IO.println("byte " + Byte.SIZE + " bits " + Byte.MIN_VALUE + " to " + Byte.MAX_VALUE);
IO.println("short " + Short.SIZE + " bits " + Short.MIN_VALUE + " to " + Short.MAX_VALUE);
IO.println("char " + Character.SIZE + " bits " + (int) Character.MIN_VALUE + " to " + (int) Character.MAX_VALUE);
IO.println("int " + Integer.SIZE + " bits " + Integer.MIN_VALUE + " to " + Integer.MAX_VALUE);
IO.println("long " + Long.SIZE + " bits " + Long.MIN_VALUE + " to " + Long.MAX_VALUE);
IO.println("float " + Float.SIZE + " bits largest " + Float.MAX_VALUE + ", smallest above zero " + Float.MIN_VALUE);
IO.println("double " + Double.SIZE + " bits largest " + Double.MAX_VALUE + ", smallest above zero " + Double.MIN_VALUE);
} from session import run, version
version()
run("""
java Ranges.java
""") Output
# openjdk version "25.0.4.1" LTS $ java Ranges.java byte 8 bits -128 to 127 short 16 bits -32768 to 32767 char 16 bits 0 to 65535 int 32 bits -2147483648 to 2147483647 long 64 bits -9223372036854775808 to 9223372036854775807 float 32 bits largest 3.4028235E38, smallest above zero 1.4E-45 double 64 bits largest 1.7976931348623157E308, smallest above zero 4.9E-324
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 ranges_session.py
Look at the last two lines. Float.MIN_VALUE and Double.MIN_VALUE are not the most negative values: they are the
smallest values above zero, 1.4 × 10⁻⁴⁵ and 4.9 × 10⁻³²⁴. The most negative double is -Double.MAX_VALUE. For the
whole-number types, MIN_VALUE really is the most negative value.
Starting a search for the largest double at Double.MIN_VALUE
double highest = Double.MIN_VALUE; starts just above zero, so when every reading is negative, highest never
changes and the program reports 4.9E-324. Start from -Double.MAX_VALUE, or from the first reading itself.
Checkpoint
Check: The eight primitive types and their ranges
Choose int, and long once a value can pass 2.1 billion
Most values need one of four types, and a few facts decide which:
intfor whole numbers, unless a value can pass about 2.1 billion (2,147,483,647, which is 2³¹ − 1). Counts, loop indexes and the sizes of strings and arrays areint.longwhen a value can grow past that. Totals, database IDs, file sizes and time stamps belong here:System.currentTimeMillis()returns along, because the milliseconds since the start of 1970 are already past 1.7 × 10¹², and anintstops at 2.1 × 10⁹.doublefor numbers with a fractional part, such as measurements and averages. Not for money: Floating point, rounding and BigDecimal shows why, and what to use instead.bytefor binary data. Mind the sign: abyteholds -128 to 127, so the byte 0xFF of a file is -1 in abyte. That is whyInputStream.read()returns anintfrom 0 to 255, with -1 meaning the end of the stream.shortandfloatsave memory and little else. An array of a millionintvalues takes about 4 MB and the same array ofshortvalues about 2 MB; that matters only for large arrays.charfor one UTF-16 code unit andbooleanfor a condition. char, boolean, casting and conversions covers both.
The table below answers back. Pick a value you want to store, such as the bytes of a photo, the milliseconds a video has played or the average mark of a class, answer its four questions, and watch which types are left and why:
Answer the questions above: the table then marks what fits your case and says why the rest does not.
| Type | Whole numbers it holds | Fractions | A million of them | In everyday code |
|---|---|---|---|---|
| byte Size: 8 bits, 1 byte | Poor -128 to 127 only | Poor none: 7.0 does not even go in without a cast | Good about 1 MB | Mixed arithmetic on it gives an int, so a result needs a cast to go back |
| short Size: 16 bits, 2 bytes | Poor -32,768 to 32,767 | Poor none | Good about 2 MB | Mixed arithmetic on it gives an int, so a result needs a cast to go back |
| int Size: 32 bits, 4 bytes | Good about ±2.1 billion, up to 2,147,483,647 | Poor none | Mixed about 4 MB | Good the type of whole-number literals, counts, indexes and lengths |
| long Size: 64 bits, 8 bytes | Good about ±9.2 × 10¹⁸ | Poor none | Poor about 8 MB | Good needs an L on literals past the int range: 12_000_000_000L |
| float Size: 32 bits, 4 bytes | Poor exact only up to 16,777,216 (2²⁴) | Mixed about 7 significant digits | Mixed about 4 MB | Mixed needs an f on literals: 19.99f |
| double Size: 64 bits, 8 bytes | Mixed exact up to 2⁵³, about 9 × 10¹⁵ | Good 15 to 16 significant digits | Poor about 8 MB | Good the type of decimal literals such as 249.50 |
How the answers decide
- “Can the value have a fractional part, such as 2.5 kg or an average?” Yes, it can have a fraction: rules out byte, short, int and long. The four whole-number types hold no fraction at all: FitErrors.java shows that even 7.0 does not go into an int without a cast.
- “Can the value have a fractional part, such as 2.5 kg or an average?” Yes, it can have a fraction and “Will you keep millions of these values in one array?” No, a few at a time: adds a caveat to float. A float keeps only about 7 significant digits and saves memory only in large arrays; double is the usual type for fractions.
- “Can the value have a fractional part, such as 2.5 kg or an average?” No, whole numbers only: rules out float. A float holds whole numbers exactly only up to 16,777,216, so larger counts and IDs change without a word.
- “Can the value have a fractional part, such as 2.5 kg or an average?” No, whole numbers only: adds a caveat to double. A double holds whole numbers exactly up to 2⁵³, but counts, indexes and IDs stay exact, compare exactly and read clearly in an integer type.
- “Can the value ever pass 2,147,483,647, about 2.1 billion, in either direction?” Yes, or I am not sure: rules out byte, short and int. An int stops at 2,147,483,647, and past it the value wraps round without an error. Totals, IDs, file sizes and times in milliseconds need a long.
- “Will you keep millions of these values in one array?” No, a few at a time: adds a caveat to byte and short. byte and short save memory only in large arrays, and arithmetic on them gives an int that needs a cast to be stored back.
- “Is it raw data, the bytes of a file or of a network message?” Yes: adds a caveat to byte. A byte is signed: the file byte 0xFF reads as -1. That is why InputStream.read() returns an int from 0 to 255, with -1 for the end.
- “Will you keep millions of these values in one array?” Yes, millions: adds a caveat to long and double. In a large array every byte of the type counts: a million long or double values take about 8 MB, twice as much as int or float.
In short: int for whole numbers, long once they can pass 2,147,483,647, double for fractions, byte for raw
data, and short and float only in large arrays where memory matters.
Checkpoint
Check: Choosing a type for a value
A literal’s form decides its base and its type
A value written in the code is a literal: A value written directly in the code, such as 120, 0xFF, 12_000_000_000L, 19.99f or true. Whole-number literals are int unless they end in L, and decimal literals are double unless they end in f.. Whole-number literals are int unless they end in
L, and numbers with a decimal point or an exponent are double unless they end in f:
Java (Literals.java) · Literals.java
// One value can be written in several ways; the compiler stores the same bits.
void main() {
int decimal = 255;
int hex = 0xFF;
int binary = 0b1111_1111;
int octal = 0377; // a leading 0 means base 8
int surprise = 010; // base 8 again: this is eight, not ten
IO.println(decimal + " " + hex + " " + binary + " " + octal + " " + surprise);
long bigCount = 12_000_000_000L; // the L makes it a long literal
long grouped = 12_00_000L; // underscores may go between any two digits
IO.println(bigCount + " " + grouped + " " + (grouped == 1200000));
float ratio = 1.5f; // the f makes it a float literal
double tiny = 1e-9; // 1 × 10 to the power -9
double large = 2.5e6; // 2,500,000
IO.println(ratio + " " + tiny + " " + large);
char letter = 'A';
char sameLetter = '\u0041'; // a Unicode escape for the same character
IO.println(letter + " " + sameLetter + " " + (letter == sameLetter));
} Java (LiteralErrors.java) · LiteralErrors.java
void main() {
int big = 3000000000; // more than an int can hold
long count = 1_000_; // an underscore at the end of a number
} Java (FitErrors.java) · FitErrors.java
void main() {
byte level = 200; // a byte holds -128 to 127
float price = 19.99; // 19.99 is a double literal
int whole = 7.0; // a double does not go into an int on its own
} from session import run, version
version()
run("""
java Literals.java
!java LiteralErrors.java
!java FitErrors.java
""") Output
# openjdk version "25.0.4.1" LTS
$ java Literals.java
255 255 255 255 8
12000000000 1200000 true
1.5 1.0E-9 2500000.0
A A true
$ java LiteralErrors.java
LiteralErrors.java:2: error: integer number too large
int big = 3000000000; // more than an int can hold
^
LiteralErrors.java:3: error: illegal underscore
long count = 1_000_; // an underscore at the end of a number
^
2 errors
error: compilation failed
[exit status 1]
$ java FitErrors.java
FitErrors.java:2: error: incompatible types: possible lossy conversion from int to byte
byte level = 200; // a byte holds -128 to 127
^
FitErrors.java:3: error: incompatible types: possible lossy conversion from double to float
float price = 19.99; // 19.99 is a double literal
^
FitErrors.java:4: error: incompatible types: possible lossy conversion from double to int
int whole = 7.0; // a double does not go into an int on its own
^
3 errors
error: compilation failed
[exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 literals_session.py
The first line of the run is one number written four ways, followed by a surprise. Read each form against the value it printed:
| In the code | Base | Value printed |
|---|---|---|
255 |
10 | 255 |
0xFF |
16 | 255 |
0b1111_1111 |
2 | 255 |
0377 |
8 | 255 |
010 |
8 | 8, not 10 |
0xstarts a hexadecimal literal and0ba binary one. A leading0alone means octal, so010is eight. Few programs need octal, and the leading zero is easy to type by mistake, so never pad numbers with zeros.- Underscores may sit between two digits to make long numbers readable. They change nothing:
12_000_000_000Lis12000000000L. They must sit between digits, so1_000_and1_000_Lare errors. - Write
Lin capitals: a smallllooks like the digit 1. 1e-9is 1 × 10⁻⁹.'A'is acharliteral, and so is'\u0041', the same character written as a Unicode escape.
The compiler refuses a literal that does not fit. 3000000000 is too large for any int, so it needs an L. A
constant that fits may go straight into a smaller variable, as in byte level = 120;, but 200 does not fit in a
byte. 19.99 is a double, so a float needs 19.99f, and a double never goes into an int without a cast.
Lakh and crore grouping
Underscores may sit between any two digits, so Indian digit grouping compiles: 12_00_000L is 12 lakh, the same
value as 1_200_000L, as the second line of the run shows. Write the grouping your readers expect; the compiler
ignores it.
Key idea
A literal’s prefix picks its base (0x, 0b, a leading 0), its suffix picks its type (L for long, f for
float), and underscores between digits change nothing.
Checkpoint
Check: Writing literals
Fields start at a default, local variables must be assigned
Fields, the variables that belong to a class or an object, start with a default value: zero for numbers, '\u0000'
for char, false for boolean and null for references. Local variables, the ones declared inside a method, get
no default. The compiler checks every path through the method and refuses to read a local variable that might not have
been assigned:
Java (Defaults.java) · Defaults.java
// Fields get a default value; this file's fields are fields of the class the compiler makes for it.
int seats;
double price;
boolean open;
char initial;
String name;
void main() {
IO.println("int " + seats + ", double " + price + ", boolean " + open);
IO.println("char code " + (int) initial + ", String " + name);
} Java (Unassigned.java) · Unassigned.java
void main() {
int total;
int items = 3;
if (items > 0) {
total = items * 10;
}
IO.println(total); // not assigned when items is 0 or less
} from session import run, version
version()
run("""
java Defaults.java
!java Unassigned.java
""") Output
# openjdk version "25.0.4.1" LTS
$ java Defaults.java
int 0, double 0.0, boolean false
char code 0, String null
$ java Unassigned.java
Unassigned.java:7: error: variable total might not have been initialized
IO.println(total); // not assigned when items is 0 or less
^
1 error
error: compilation failed
[exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 defaults_session.py
In Unassigned.java the compiler cannot know whether items > 0 will be true, so the path that skips the if leaves
total unassigned and the program does not compile. Step through the two paths the compiler follows:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- A local starts empty. int total; declares a local variable and gives it no value. A field would start at 0, but a local variable gets no default at all.
- The compiler sees two paths. The compiler does not run the program, so it cannot know whether items > 0 will be true. It follows both ways out of the if.
- The true path. When the test is true, total = items * 10 assigns total, so on this path IO.println(total) reads a value.
- The false path. When the test is false, the assignment is skipped and IO.println(total) is reached with nothing in total. One such path is enough: the compiler stops with variable total might not have been initialized.
- The fix. Give total a value on every path: a starting value, int total = 0;, or an else that assigns it. Both ways into IO.println(total) then carry a value, and the file compiles.
Give such a variable a value on every path, often with an else, or at its declaration when a sensible starting value
exists. This rule, called definite assignment: The compiler rule that a local variable must be assigned on every path through the code before it is read. Otherwise the file does not compile: variable might not have been initialized., turns a classic bug of other
languages, reading memory that was never set, into a compile-time error.
Key idea
A field starts at 0, false, '\u0000' or null; a local variable starts with nothing, and the compiler refuses to
read it until every path to that line has assigned it.
Checkpoint
Check: Default values and definite assignment
Practise: the narrowest type for a range
Write narrowest(min, max), which returns "byte", "short", "int" or "long": the narrowest whole-number type
that can hold every value from min to max. The checks in main describe eight quantities, and each must say ok:
// Exercise: narrowest(min, max) returns the name of the narrowest integer type, "byte", "short", "int" or
// "long", that holds every whole number from min to max. Write it, then run this file: every check must say ok.
String narrowest(long min, long max) {
return "long"; // replace this line
}
int failures = 0;
void check(String quantity, long min, long max, String expected) {
String got = narrowest(min, max);
if (!got.equals(expected)) failures++;
IO.println((got.equals(expected) ? "ok " : "FAILED ") + quantity + ": " + got);
}
void main() {
check("a temperature in degrees Celsius, -90 to 60", -90, 60, "byte");
check("a level from -128 to 127", -128, 127, "byte");
check("a byte read from a file, 0 to 255", 0, 255, "short");
check("a year from 1 to 9999", 1, 9999, "short");
check("a change in stock from -40000 to 40000", -40_000, 40_000, "int");
check("pixels in a 10000 by 10000 image", 0, 100_000_000, "int");
check("a balance in cents up to 50 million in either direction", -5_000_000_000L, 5_000_000_000L, "long");
check("milliseconds in 100 years", 0, 3_155_760_000_000L, "long");
IO.println(failures + " of 8 checks failed");
if (failures > 0) System.exit(1);
} As given, the method always answers "long", so six of the eight checks fail:
from session import run, version
version()
run("""
!java Narrowest.java
""") Output
# openjdk version "25.0.4.1" LTS $ java Narrowest.java FAILED a temperature in degrees Celsius, -90 to 60: long FAILED a level from -128 to 127: long FAILED a byte read from a file, 0 to 255: long FAILED a year from 1 to 9999: long FAILED a change in stock from -40000 to 40000: long FAILED pixels in a 10000 by 10000 image: long ok a balance in cents up to 50 million in either direction: long ok milliseconds in 100 years: long 6 of 8 checks failed [exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 exercise_session.py
First hint: compare min and max with Byte.MIN_VALUE and Byte.MAX_VALUE first, then with the limits of Short
and Integer; whatever is left needs a long. Notice the third check: a byte read from a file can be 255, which a
byte cannot hold.
When all eight checks say ok, check the five ideas of this lesson. The questions come one at a time, the idea you
are least sure of first, and each answer is explained at once:
Practice that adapts to you
Practise this lesson
15 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Fresher · What are Java’s eight primitive types, and why is String not one of them?
The primitive types are byte, short, int and long for whole numbers of 8, 16, 32 and 64 bits, float and
double for IEEE 754 floating-point numbers of 32 and 64 bits, char for a 16-bit UTF-16 code unit and boolean for
true or false. A variable of a primitive type holds the value itself, has a default value when it is a field, and has
no methods. String is a class: a String variable holds a reference to an object, which can be null and has
methods such as length(). The language gives strings literals and the + operator, but that does not make them
primitive.
Key takeaways
- Every Java variable has a type fixed at compile time;
varlets the compiler infer it from the initial value, for local variables only, and the type never changes afterwards. - The primitive types are
byte,short,int,long(8 to 64 bits, signed),floatanddouble(IEEE 754),char(an unsigned 16-bit UTF-16 code unit) andboolean, with the same ranges on every computer;Float.MIN_VALUEandDouble.MIN_VALUEare the smallest positive values, not the most negative ones. - Use
intfor whole numbers andlongonce a value can pass 2,147,483,647; usedoublefor fractions, but not for money, andbyte,shortorfloatonly for raw data or large arrays. - Whole-number literals are
intunless they end inL; decimal literals aredoubleunless they end inf;0x,0band a leading0mean hex, binary and octal; underscores only improve readability. - Fields start with a default value; a local variable must be assigned on every path before it is read.
Terms in this lesson
- definite assignment
- The compiler rule that a local variable must be assigned on every path through the code before it is read. Otherwise the file does not compile: variable might not have been initialized.
- literal
- A value written directly in the code, such as 120, 0xFF, 12_000_000_000L, 19.99f or true. Whole-number literals are int unless they end in L, and decimal literals are double unless they end in f.
- local variable type inference
- Writing var instead of a type for a local variable with an initial value: the compiler gives the variable the type of that value, so var shows = 3; declares an int. The type is fixed when the file compiles.
- primitive type
- One of the eight value types built into Java: byte, short, int, long, float, double, char and boolean. A variable of a primitive type holds the value itself and has no methods.
- reference type
- Every type that is not primitive: classes such as String, interfaces and arrays. A variable of a reference type holds a reference to an object, or null.
- two's complement
- How Java stores signed whole numbers: the highest bit counts as minus 2 to the power n − 1, so an n-bit type runs from −2ⁿ⁻¹ to 2ⁿ⁻¹ − 1, and the byte 11111111 is -1.
References
- The Java Language Specification, Java SE 25: chapter 4, Types, Values, and Variables (Oracle)
- The Java Language Specification, Java SE 25: chapter 3, Lexical Structure (3.10 Literals) (Oracle)
- The Java Language Specification, Java SE 25: chapter 16, Definite Assignment (Oracle)
- JEP 286: Local-Variable Type Inference (OpenJDK)
- Local Variable Type Inference (Java SE 25 language guide) (Oracle)
- Local Variable Type Inference: Style Guidelines (OpenJDK)
- JEP 512: Compact Source Files and Instance Main Methods (OpenJDK)
- Float (Java SE 25 API) (Oracle)
- Double (Java SE 25 API) (Oracle)
- System.currentTimeMillis() (Java SE 25 API) (Oracle)
- InputStream.read() (Java SE 25 API) (Oracle)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress