Appearance
Variables, Primitive Types, and Type Casting
A program is mostly about data: a price, an age, a name, a yes or no answer. Before you can do anything with data, you need somewhere to keep it. That place is a variable.
What a variable is
A variable is a named box that holds one value. In Java, every box also has a fixed type that says what kind of value may go inside.
java
int age = 25;This line does three things. int is the type (whole numbers). age is the name. 25 is the value you put in. From now on, writing age in your code means "the value currently inside that box".
Statically typed and strongly typed
You will hear Java described with two terms. They mean different things.
Statically typed: every variable's type is fixed when you write the code, and the compiler checks it before the program runs.
java
int x = 10;
x = "hello"; // compile error: a String does not fit in an int boxStrongly typed: each type has a fixed range of values, and Java restricts what you can assign. A value that does not fit the type is rejected unless you convert it explicitly.
java
byte small = 200; // compile error: possible lossy conversion from int to byte (200 > 127)Naming rules
The compiler enforces a few rules: names are case sensitive (count and Count are different), may contain Unicode letters, digits, $ and _, must start with a letter, $ or _ (never a digit), and cannot be a reserved word such as int, class or new.
On top of that, Java developers follow conventions:
java
int studentCount = 3; // variables and methods: camelCase
static final int MAX_SPEED = 200; // constants: UPPER_SNAKE_CASEThe eight primitive types
Java has exactly eight primitive types. A primitive variable holds the raw value itself, not a reference to an object.
| Type | Size | Range | Default (for fields) |
|---|---|---|---|
byte | 8 bits | -128 to 127 | 0 |
short | 16 bits | -32,768 to 32,767 | 0 |
int | 32 bits | about -2.1 billion to 2.1 billion (-2^31 to 2^31 - 1) | 0 |
long | 64 bits | -2^63 to 2^63 - 1 (about ±9.2 × 10^18) | 0L |
float | 32 bits | about ±3.4 × 10^38, ~7 significant digits | 0.0f |
double | 64 bits | about ±1.8 × 10^308, ~15 to 16 significant digits | 0.0 |
char | 16 bits | 0 to 65,535 (unsigned) | '\u0000' |
boolean | not specified by the JVM spec | true or false | false |
byte, short, int, long and char hold whole numbers. float and double hold fractional numbers. boolean holds only true or false.
Which one should you use? int for whole numbers and double for fractional numbers, unless you have a reason not to.
char
A char holds one UTF-16 code unit, which is a number from 0 to 65,535. You can assign a character or its number:
java
char a = 'A';
char b = 65; // also 'A'char is Java's only unsigned numeric type. Codes 0 to 127 are the same as ASCII (65 is 'A', 97 is 'a').
long and the L suffix
A whole number written in code (a literal) is an int by default. If it is too big for int, add L:
java
long worldPopulation = 8_100_000_000L; // underscores are only for readability
long bad = 8_100_000_000; // compile error: integer number too largeUse uppercase L; lowercase l looks like the digit 1.
float and the f suffix
A fractional literal such as 63.2 is a double by default. To store it in a float, add f. For a double you may add d, but it is optional:
java
float f = 63.2f; // OK
double d = 63.2d; // OK, same as 63.2
float g = 63.2; // compile error: possible lossy conversion from double to floatHow negative numbers are stored: two's complement
Computers store integers as bits. Java uses two's complement for negative numbers. To negate a number: flip every bit, then add 1. With 4 bits:
3 = 0011
flip 1100
+1 1101 = -3Check it: 0011 + 1101 = 10000. The fifth bit does not fit in 4 bits and falls off, leaving 0000. So 3 + (-3) = 0, using the same adder circuit as ordinary addition.
The leftmost bit acts as the sign: 0 means zero or positive, 1 means negative. 4 bits therefore cover -8 (1000) to 7 (0111). A byte has 8 bits, so it covers -128 (1000 0000) to 127 (0111 1111). The range has one more negative number because zero uses one of the "sign bit 0" patterns.
Overflow wraps around silently
If a result does not fit, Java does not throw an error. It keeps the low bits and wraps around, like an odometer rolling over.
java
byte b = 127;
b++;
System.out.println(b); // -128
System.out.println((byte) 130); // -126
System.out.println((byte) 256); // 0Never use float or double for money
float and double store numbers in binary, and most decimal fractions (such as 0.1) have no exact binary form. You get the nearest approximation:
java
float a = 0.3f;
float b = 0.1f;
System.out.println(a - b); // 0.20000002, not 0.2The same happens with double.
For money, use BigDecimal, created from a String:
java
import java.math.BigDecimal;
BigDecimal price = new BigDecimal("0.10");
BigDecimal tax = new BigDecimal("0.20");
System.out.println(price.add(tax)); // 0.30, exactlyThe next chapter shows exactly how floating point numbers are stored and why this happens.
Converting between types
Widening: automatic
Moving a value into a bigger type happens automatically:
java
int score = 95;
long big = score; // automatic
double d = score; // automaticThe smaller type always fits into the bigger one, so no cast is needed.
Narrowing: you must cast
Going into a smaller type can lose data, so Java requires an explicit cast:
java
int i = 10;
byte b = (byte) i; // 10: fits, no problem
int big = 128;
byte wrapped = (byte) big; // -128: past 127 it wraps around to the start of the range
byte next = (byte) 129; // -127Arithmetic promotes small types to int
In any arithmetic expression, byte, short and char values are promoted to int first. So the result of adding two bytes is an int:
java
byte a = 10, b = 20;
byte sum = a + b; // compile error: a + b is an int
byte ok = (byte) (a + b); // cast the whole expression
int sum2 = a + b; // or store it in an intWhen an expression mixes types, the whole expression is promoted to the largest type in it:
java
int i = 34;
double d = 20.0;
double r = i + d; // 54.0
int bad = i + d; // compile error: the expression is a double
int ok = (int) (i + d); // 54Where variables live, and whether they start with a value
Where you declare a variable decides its kind:
java
public class Demo {
int count; // instance variable: one per object, starts at 0
static int total; // static variable: one per class, starts at 0
Demo(int start) { // constructor parameter
count = start;
}
void add(int amount) { // method parameter
int doubled = amount * 2; // local variable: no default value
count += doubled;
}
}The rule to remember: fields get default values, local variables do not. Reading a local variable before assigning it is a compile error:
java
void check() {
int x;
System.out.println(x); // compile error: variable x might not have been initialized
}A static variable is shared by every object of the class. Change it through one object and all of them see the new value, so access it through the class name: Demo.total.
Check yourself
- What is the difference between statically typed and strongly typed?
- Why is the range of
byte-128 to 127? - What does
(byte) 128produce, and why? - Why does
byte c = a + b;fail to compile whenaandbare bytes? - Which variables get default values: member, static, or local?
- Why should you not use
floatordoublefor money?
Answers
- Statically typed: every variable declares a type, checked at compile time. Strongly typed: each type has a fixed range and Java restricts what you may assign to it.
- Two's complement: 8 bits give 256 patterns; 128 have sign bit 0 (zero and 1 to 127) and 128 have sign bit 1 (-1 to -128).
-128. 128 does not fit in a byte, so the value wraps around past 127 to the start of the range.a + bis promoted toint, and anintcannot be stored in abytewithout a cast.- Member and static variables get defaults (
0,false,null,'\u0000'). Local variables do not; you must assign them before use. - Binary floating point cannot store most decimal fractions exactly, so results like
0.3f - 0.1fcome out as0.20000002. UseBigDecimalinstead.