2.1
Variables, Types, Literals, Keywords
Give a value a name, declare what that name can hold, and let the compiler check each use before the program runs.
Previously on
In Section 1.2 you compiled source code and ran the resulting program on a JVM.
Those programs printed fixed text. This section adds named values, so a program can calculate with data and report a result.
The problem
Write a program that adds 2 and 3. The calculation needs both values, and the result needs somewhere to go.
public class AddTwoNumbers {
public static void main(String[] args) {
int firstNumber = 2;
int secondNumber = 3;
int sum = firstNumber + secondNumber;
System.out.println(sum);
}
}Output:
5firstNumber, secondNumber, and sum are variables. Each declaration gives Java three facts:
- the variable’s type
- the variable’s name
- its initial value
The compiler uses the type to reject operations that cannot be valid. It also knows how much numeric range and precision the primitive type provides.
The idea
Read a declaration from left to right
int score = 91;| Part | Meaning |
|---|---|
int |
the declared type |
score |
the identifier that names the variable |
= |
assignment |
91 |
an integer literal |
; |
the end of the statement |
A declaration introduces the variable. An initializer supplies its first value.
int attempts; // declaration
attempts = 1; // assignment
int score = 91; // declaration with an initializerThe declared type does not change later:
int score = 91;
score = 95; // valid: another int value
// score = true; // compile-time errorThis is static typing. Java checks types during compilation, before these statements run.
Identifiers and keywords
An identifier is a name chosen by the programmer. Use names that state what a value means.
double unitPrice = 12.50;
int quantity = 4;
double total = unitPrice * quantity;Identifiers are case-sensitive. total, Total, and TOTAL are different names.
Common identifiers use letters, digits, _, and $, and cannot start with a digit. Java also accepts many Unicode letters and currency symbols. It reserves keywords such as class, int, public, and return for the language grammar.
int classSize = 30; // valid
// int class = 30; // invalid: class is a keywordThe eight primitive types
Java defines exactly eight primitive types.
| Family | Type | Language-defined value set |
|---|---|---|
| signed integers | byte |
8 bits, -128 through 127 |
| signed integers | short |
16 bits, -32,768 through 32,767 |
| signed integers | int |
32 bits, -2,147,483,648 through 2,147,483,647 |
| signed integers | long |
64 bits, -2^63 through 2^63 - 1 |
| binary floating point | float |
32-bit IEEE 754, about 6 to 9 significant decimal digits |
| binary floating point | double |
64-bit IEEE 754, about 15 to 17 significant decimal digits |
| UTF-16 code unit | char |
unsigned 16 bits, \u0000 through \uFFFF |
| logical | boolean |
true or false; storage size is unspecified |
The integer widths and ranges are fixed by the Java language. They do not change between a 32-bit laptop and a 64-bit server.
char stores a code unit
This program shows both the useful case and the boundary:
public class CharDemo {
public static void main(String[] args) {
char latin = 'A';
char devanagari = '\u0915';
String rocket = "🚀";
System.out.println(latin);
System.out.println(devanagari);
System.out.println(rocket.length());
System.out.println(rocket.codePointCount(0, rocket.length()));
}
}Output:
A
क
2
1The rocket is one Unicode code point represented by two UTF-16 code units. String.length() counts code units, so it returns 2 here.
Choose a default, then justify exceptions
Use int for ordinary whole-number calculations and double for ordinary binary floating-point calculations. Choose another type when its range, precision, API contract, storage density, or domain meaning gives a concrete benefit.
float is useful in some graphics and large numeric arrays. byte is useful for encoded binary data. long is required when int cannot cover the range. Money and other exact decimal quantities need a decimal representation such as BigDecimal, not double.
Under the hood
Going deeperBits determine the integer ranges
One bit has two patterns. With n bits there are 2^n patterns.
For a signed byte, the same 256 patterns are interpreted as -128 through 127. Section 2.2 explains the two’s complement rule that makes that mapping work.
Literal spelling has a type
Values written directly in source are literals. Their spelling controls their type.
int decimal = 42;
int binary = 0b0010_1010;
int octal = 052;
int hexadecimal = 0x2A;
long worldPopulation = 8_100_000_000L;
float load = 0.75f;
double avogadro = 6.022_140_76e23;
char grade = 'A';
boolean ready = true;All four int variables above contain 42. The prefixes change how the source digits are read. They do not create different runtime integer types.
An unsuffixed decimal integer literal normally has type int. Add L when the value needs long. An unsuffixed floating-point literal has type double. Add f or F for float.
Single quotes create a char literal. Double quotes create a String, which is not a primitive type.
Local variables must be initialized before use
Java does not let a method read an uninitialized local variable.
public class Initialization {
public static void main(String[] args) {
int result;
// System.out.println(result); // compile-time error
result = 12;
System.out.println(result);
}
}This rule is called definite assignment. Fields receive default values, but local variables do not. Fields arrive with classes in Phase VI.
Fixed-width integer arithmetic can overflow
An int always has 32 bits. Java does not add a 33rd bit when a result needs one.
public class OverflowDemo {
public static void main(String[] args) {
int largest = Integer.MAX_VALUE;
int wrapped = largest + 1;
System.out.println(largest);
System.out.println(wrapped);
try {
System.out.println(Math.addExact(largest, 1));
} catch (ArithmeticException ex) {
System.out.println("overflow detected");
}
}
}Output:
2147483647
-2147483648
overflow detectedOrdinary integer operators wrap modulo 2^32; they do not report overflow. Methods such as Math.addExact are available when overflow must become an error.
What it costs
Types let the compiler reject invalid assignments and document the shape of data. The constraint also means you must choose ranges and conversions before running the program.
Narrow integer types do not make arithmetic narrow. Java promotes byte, short, and char before most arithmetic, which Section 2.3 covers. Use them when their storage or API meaning matters, not as a default calculation type.
Binary floating-point types trade exact decimal representation for a wide range and fast hardware operations. That trade becomes visible in the next section.
Check yourself
Answer from memory before opening the explanation. Then compare your rule with the answer and revisit the example if they differ.
Why does `byte` stop at 127 when eight bits provide 256 patterns?
Show the answer
byteis signed. Its 256 patterns represent -128 through 127: 128 negative values, zero, and 127 positive values. The exact interpretation is fixed-width two's complement, which Section 2.2 derives.Why does `float price = 3.99;` fail even though 3.99 is inside the range of `float`?
Show the answer
The literal
3.99has typedouble. Assigning it tofloatis a narrowing conversion, so Java requires an explicit cast or anfsuffix. Writefloat price = 3.99f;, or keep the greater precision withdouble price = 3.99;.Does `char` always hold one complete visible character?
Show the answer
No. A
charis one unsigned 16-bit UTF-16 code unit. Many common characters use one code unit, but some Unicode characters use a surrogate pair and therefore need twocharvalues. Text normally belongs in aString.How many bytes does a Java `boolean` occupy?
Show the answer
The Java language does not specify a storage size for
boolean. It specifies only the valuestrueandfalse. Object layout, fields, arrays, and JVM implementations can represent booleans differently, so do not derive memory totals from a claimed one-bit or one-byte size.
Exercises
Write each solution in VS Code, predict its result, and then run the checks. Reading an example does not replace implementing it.
5 exercises105 pointsabout 110 minutes
All Eight, Printed
ex-2-1-aDeclare one variable of every primitive type, then print them all.
This is deliberately mechanical. Do it without looking at the tables in the section if you can, then check yourself against them.
Two of the eight will refuse to compile on your first try. That is not you failing. That is the literal type rule from this section catching you, exactly as it will catch you in real code. Read the error, work out which rule it is, fix it.
What to write
These exact signatures. The checks call them by name, so a different name or a different parameter type will not build.
class Primitives {
static byte aByte()
static short aShort()
static int anInt()
static long aLong()
static float aFloat()
static double aDouble()
static char aChar()
static boolean aBoolean()
}Eight methods, one per type, each returning a sensible value. The return types are the exercise: the checks assign each answer to a variable of that exact type, so a wrong type will not build.
What your program must do
- Return a value of each of the eight primitive types
- Make the short too big for a byte and the long too big for an int
- Give the float and the long the suffix each one needs
- Print all eight and say which two needed a suffix and why
public class Primitives {
// One method per primitive type. Return a sensible value from each.
// Two of them need a suffix on the literal. Two of them need particular quotes.
static byte aByte() { return 0; } // TODO
static short aShort() { return 0; } // TODO: big enough that a byte could not hold it
static int anInt() { return 0; } // TODO
static long aLong() { return 0; } // TODO: big enough that an int could not hold it
static float aFloat() { return 0; } // TODO
static double aDouble() { return 0; } // TODO
static char aChar() { return 0; } // TODO
static boolean aBoolean() { return false; } // TODO
public static void main(String[] args) {
// TODO: print all eight, one per line
}
}
Sample run
byte smallCount = 100
short mediumCount = 30000
int population = 1400000000
long distanceInKm = 9460730472580800
float roughPrice = 3.99
double preciseValue = 3.14159265358979
char grade = A
boolean isActive = true
Hint 1
3.99 is a double, so a float needs 3.99f. A plain whole number is an int, so a long past 2.1 billion needs 9_000_000_000L.Hint 2
char takes single quotes. Double quotes make a String, which is not a primitive at all.Hint 3almost the answer
9_000_000_000L reads better than the same digits run together.Watch an int Overflow
ex-2-1-bThe section said that overflow is silent. Go and see the silence for yourself.
Take the largest number each type can hold, add one, and print the result. Write down what you expect first. Most people guess zero, or an error. It is neither.
Then fix the int case so it gives the right answer, and pay attention to where the fix has to go. Putting the cast in the wrong place is a real bug that looks like a fix, and it appears in production code more often than anyone would like.
What to write
These exact signatures. The checks call them by name, so a different name or a different parameter type will not build.
class Overflow {
static int intOverflow()
static byte byteOverflow()
static short shortOverflow()
static long longOverflow()
static boolean threwAnything()
}Each of the first four adds one to that type's maximum and returns the result. threwAnything reports whether adding one to Integer.MAX_VALUE raised anything at all.
What your program must do
- Predict all four results before running, then implement them
- Report whether anything was thrown
- Say why the byte version needs a cast and the int version does not
- Say what this means for a program that silently passes the maximum
public class Overflow {
// Add one to each type's maximum and return what you get.
// PREDICT all four before running.
static int intOverflow() { return 0; } // TODO
static byte byteOverflow() { return 0; } // TODO: this one needs a cast. Work out why
static short shortOverflow() { return 0; } // TODO
static long longOverflow() { return 0; } // TODO
// Did anything get thrown? Try it and find out.
static boolean threwAnything() {
return true; // TODO
}
public static void main(String[] args) {
// TODO: print all four, plus whether anything was thrown
}
}
Hint 1
Hint 2
Byte.MAX_VALUE + 1 is an int expression, because Java promotes small types before arithmetic. So it has to be narrowed back with a cast. Integer.MAX_VALUE + 1 is already an int, so it wraps on its own.Hint 3almost the answer
+ operations in this exercise throw nothing. There is no warning and no crash. Use Math.addExact when overflow must raise ArithmeticException.Six Broken Declarations
ex-2-1-cSix declarations. All six are broken. Each one is broken for a different reason.
Fix them so the file compiles, without changing what any line is trying to do.
The point is not the fixing, which is easy once you see it. The point is the error messages. Read each one properly before you fix it, and connect it to a rule from this section. Do that now, deliberately, and these errors will cost you seconds instead of minutes for the rest of your life.
What to write
These exact signatures. The checks call them by name, so a different name or a different parameter type will not build.
class Broken {
static long distance()
static float price()
static char grade()
static int classSize()
static byte count()
static boolean ready()
}Six methods holding the six broken declarations, each fixed. The values stay the same wherever the value was legal. One of them cannot keep its value, and finding out which is part of the exercise.
What your program must do
- Fix all six and name the rule behind each error before changing it
- Say which one cannot keep its original value, and what it becomes
- Say which two are about literals and which one is about a keyword
- Print all six
public class Broken {
// Six declarations, six different rules broken. Fix each one.
// Read the compiler error first, and name the rule before you change anything.
static long distance() { return 9460730472580800; } // TODO
static float price() { return 3.99; } // TODO
static char grade() { return "A"; } // TODO
static int classSize() { return class; } // TODO
static byte count() { return 200; } // TODO
static boolean ready() { return 1; } // TODO
public static void main(String[] args) {
// TODO: print all six once they compile
}
}
Hint 1
int and a plain decimal is a double, so a big long needs L and a float needs f.Hint 2
class is a reserved word, so it can never be a variable name. Rename it. "A" in double quotes is a String, and a char needs single quotes.Hint 3almost the answer
The Registry Begins
ex-2-1-dThis is the first version of The Registry, a system you will keep coming back to across the whole course.
Right now it is nothing but a handful of loose variables in main, describing one unit. That is deliberate. It should feel clumsy.
In Phase VI you will bundle these into an object and see why. In Phase VII you will hide them behind methods. In Phase VIII you will give the whole thing proper identity, so two units can be compared correctly. Each time, you will come back to this file and see how far you have moved.
For now, one job: pick the right type for each fact, and be able to say why. Do not use int for everything. The exercise is the choosing.
What to write
These exact signatures. The checks call them by name, so a different name or a different parameter type will not build.
class Registry {
static String unitName()
static int unitId()
static double readiness()
static boolean active()
static char classification()
static long serialNumber()
static byte crewSize()
}Seven fields of the first Registry unit, each with a type you chose on purpose. The serial number must be past 2,147,483,647 and the readiness must carry a fraction, so both choices mean something.
What your program must do
- Give every field a value that fits its type
- Make the serial number past 2,147,483,647 so the long is a real decision
- Give readiness a fraction so the double is a real decision
- Write one line per field saying why that type and not the next one up
public class Registry {
// One unit in the registry. Choose each type deliberately and be ready to defend it.
// The serial number is past 2,147,483,647. The readiness has a fraction.
static String unitName() { return ""; } // TODO
static int unitId() { return 0; } // TODO
static double readiness() { return 0; } // TODO
static boolean active() { return false; } // TODO
static char classification() { return 0; } // TODO
static long serialNumber() { return 0; } // TODO
static byte crewSize() { return 0; } // TODO
public static void main(String[] args) {
// TODO: print a tidy record of this unit
}
}
Hint 1
classification is one letter, so char beats String. crewSize is never above 127, so byte says something a plain int would not.Hint 2
L suffix once it goes past 2.1 billion, or the literal will not compile.Hint 3almost the answer
Work Out the Ranges Yourself
ex-2-1-eYou have been given four ranges in a table. Tables get forgotten. Formulas do not.
Work out the minimum and maximum for byte, short, int and long on paper, starting only from how many bits each one has. Then run the program and see whether you were right.
If your derivation is off by one somewhere, that is the most useful outcome you could have. The off-by-one is where zero lives, and understanding that is the difference between reciting a range and knowing it.
Finish by writing the general formula and its representation limit in your own words. A method returning
long cannot return the positive counts 2^63 or 2^64. That limit belongs to the return type,
not to the mathematics.
What to write
These exact signatures. The checks call them by name, so a different name or a different parameter type will not build.
class Ranges {
static long minOf(int bits)
static long maxOf(int bits)
static long unsignedMaxOf(int bits)
static long countOf(int bits)
}Derive the range from the bit count rather than looking it up. The checks call them with 4 and 12 as well as 8, 16, 32 and 64, so a table of remembered constants will not pass.
What your program must do
- Work each range out on paper from the bit count before writing any code
- Derive them in code, so 4 bits and 12 bits also come out right
- Check signed ranges against Byte.MIN_VALUE through Long.MIN_VALUE and their maxima
- State why countOf cannot return 2^63 or 2^64 in a signed long
- Say why the top of the range is one smaller than the bottom in size
public class Ranges {
// Derive these from the bit count. Do NOT return remembered constants:
// the checks call these with 4 bits and 12 bits as well.
// Signed, so one bit is the sign.
static long minOf(int bits) { return 0; } // TODO
static long maxOf(int bits) { return 0; } // TODO
// An unsigned n-bit pattern. Java's char is the only unsigned integral
// primitive, and it represents one UTF-16 code unit.
static long unsignedMaxOf(int bits) { return 0; } // TODO
// How many values fit. The long return type limits this method to 1..62 bits:
// 2^63 and 2^64 are positive counts that do not fit in a signed long.
static long countOf(int bits) { return 0; } // TODO
public static void main(String[] args) {
// TODO: print signed ranges for 8, 16, 32 and 64 bits
// TODO: print counts only where the positive result fits in long
}
}
Hint 1
Hint 2
1L << (bits - 1) gives the sign-bit pattern. At 64 bits that pattern is Long.MIN_VALUE; fixed-width overflow also makes subtracting one produce Long.MAX_VALUE. Validate the supported width before shifting because Java masks long shift distances modulo 64.Hint 3almost the answer
After the credits
Section 2.2 derives the bit rules behind signed integers and binary floating point. Section 2.3 then follows values as Java widens, narrows, and promotes their types.
The distinction between a UTF-16 code unit and a Unicode code point returns in Phase IX when strings become the main subject.
Threads you opened in this section
- Primitive typesBoxing converts primitive values to wrapper references and can add allocation and null costs.7.3 - Wrapper Types, Autoboxing, and POJOs
- Primitive typesA List cannot hold an int at all. Now you know why.Phase X. The Collections Framework
Primitive types will return in 6.3 - Objects in Memory: Size, Copying, Passing