variables and primitives
eight primitive types, how many bits each one gets, and what that means.
- chapter
- chapter 02.01
- reading time
- 8 min read
a variable is a named slot with a fixed type, and the type decides how many bits the slot has and what they mean. java has exactly 8 primitive types: byte, short, int, long, float, double, char, boolean. everything else (strings, arrays, your classes) is an object, and a variable can only hold a reference to it.
the picture
think of variables as labelled boxes in a row of lockers. the label is the name. the type is the size and shape of the box: an int box holds exactly 32 bits, a long box holds 64, and a boolean box only takes true or false. you can change what's in a box, never its size or shape.
void main() {
int age = 21;
boolean ready = true;
char grade = 'A';
double weight = 72.5;
String name = "ada";
IO.println(age + " " + ready + " " + grade + " " + weight);
}21 true A 72.5
main's frame. the four primitives sit right in their slots. name is the odd one out: its slot holds a reference, and the actual text lives on the heap.that split (values in the slot vs arrows to objects) is the most important picture in java. it gets its own chapter: stack and heap. this one is about the boxes themselves.
the eight primitives
four whole-number types of increasing size, two decimal types, one for characters, one for yes/no. the sizes are fixed by the language spec: an int is 32 bits on every machine java runs on, which isn't true in C.
you don't have to memorize the ranges. java will tell you:
void main() {
IO.println("byte " + Byte.MIN_VALUE + " .. " + Byte.MAX_VALUE);
IO.println("short " + Short.MIN_VALUE + " .. " + Short.MAX_VALUE);
IO.println("int " + Integer.MIN_VALUE + " .. " + Integer.MAX_VALUE);
IO.println("long " + Long.MIN_VALUE + " .. " + Long.MAX_VALUE);
IO.println("char " + (int) Character.MIN_VALUE + " .. " + (int) Character.MAX_VALUE);
IO.println("float " + Float.MAX_VALUE + ", smallest " + Float.MIN_VALUE);
IO.println("double " + Double.MAX_VALUE + ", smallest " + Double.MIN_VALUE);
}byte -128 .. 127 short -32768 .. 32767 int -2147483648 .. 2147483647 long -9223372036854775808 .. 9223372036854775807 char 0 .. 65535 float 3.4028235E38, smallest 1.4E-45 double 1.7976931348623157E308, smallest 4.9E-324
notice the whole-number types are lopsided: one more negative value than positive (-128 .. 127). that's two's complement, the bit trick every CPU uses for negative numbers. and Float.MIN_VALUE is not the most negative float, it's the smallest positive one. classic trap.
in practice you'll use three of them most of the time: int for counting, double for measuring, boolean for deciding. long when numbers get big, char when you're poking at text, the rest when you have a specific reason.
literals
a literal is a value written directly in the code. its spelling decides its type:
| you write | type | value |
|---|---|---|
42 | int | 42 |
42L | long | 42 (always use capital L, a lowercase l looks like a 1) |
0x2A, 0b10_1010 | int | 42, in hex and binary |
1_000_000 | int | a million. underscores are just for your eyes |
3.14 | double | any decimal point means double |
3.14f | float | f makes it a float |
'A', '\n', 'A' | char | single quotes, one character (A is also 'A') |
true, false | boolean | that's the whole list |
void main() {
int dec = 42, hex = 0x2A, bin = 0b10_1010, oct = 052;
IO.println(dec + " " + hex + " " + bin + " " + oct);
long big = 3_000_000_000L;
char c = 'A';
IO.println(big + " " + c + " " + (int) c + " " + (char) (c + 1));
}42 42 42 42 3000000000 A 65 B
see oct = 052? that's 42 too. a leading zero means octal (base 8). nobody does that on purpose.
int x = 010;
IO.println(x + 1);char is a number in disguise
a char is a 16-bit unsigned number that java prints as a character. 'A' is 65, 'a' is 97, '0' is 48. do arithmetic on it and you get an int back, which is why (char) (c + 1) above needed the cast to print B.
and one char is one UTF-16 code unit, not one "character" as a human counts them. anything outside the first 65,536 code points (most emoji, for example) needs two chars:
void main() {
String s = "😀";
IO.println(s.length() + " " + s.codePointCount(0, s.length()) + " " + (int) s.charAt(0) + " " + (int) s.charAt(1));
}2 1 55357 56832
length() is 2, there's 1 code point, and the two halves are called a surrogate pair. more in strings, the basics.
declaring and assigning
int count; // declared: the slot exists, no value yet
count = 3; // assigned
int total = count; // declared and assigned. copies the 3
count = 10; // total is still 3
final int max = 5; // final: assigned once, never again= always copies what's in the slot. for a primitive that's the value itself, so total and count are independent from then on. (for objects, what's in the slot is a reference, which is why = behaves so differently there. see pass-by-value.)
names start with a letter, _ or $, can contain digits after that, and can't be a keyword. convention: camelCase for variables, UPPER_SNAKE for constants. since java 22, a lone _ means "a variable i don't need", like catch (Exception _).
| type of slot | gets a default? |
|---|---|
| fields and array elements | yes: 0, 0.0, false, '\u0000', null |
| local variables | no. reading one before assigning it is a compile error |
why have primitives at all, if everything else is an object?+
speed and size. an int is 4 bytes, sitting right in its slot. an Integer object is a 4-byte value plus a 12-byte header, rounded up to 16 bytes, plus a 4-byte reference to find it (on a typical 64-bit HotSpot with compressed pointers), and the CPU has to follow that reference to read it. a million ints in an int[] is 4 MB. a million Integers in a list is closer to 20 MB, scattered around the heap. java was designed in the 90s for machines where that mattered a lot, and it still matters. the long-running project to get the best of both (objects that behave like primitives) is valhalla. the boxing side of the story is in wrappers and autoboxing.
- a variable is a named slot with a fixed type. the type fixes its size and meaning forever.
- 8 primitives: byte 8, short 16, char 16, int 32, long 64, float 32, double 64 bits, plus boolean.
intanddoubleare the defaults. a whole-number literal is anintuntil you addL.charis an unsigned 16-bit number, one UTF-16 code unit, not always one visible character.=copies what's in the slot. for primitives, that's the value.