Variables in Java — primitive types, String, final and casting, with Laura’s card (and the division that always comes out one short)
In Java syntax we left Laura’s enrolment card working, but with a flaw I had already warned you about: the 24 credits and the 15.50 price were typed by hand in five places. Something similar happened to me in my first Java lab: I changed the credits from 24 to 30 to try it out, forgot one of the five lines, and the receipt charged 30 credits at the price of 24. The program compiled, ran and lied.
Variables fix exactly that: each piece of data is written once, with a name, and the rest of the program uses it by that name. But in Java a variable isn’t just a name like in Python: it also has a fixed type, and the three most famous traps of the topic depend on that type: the division that comes out one short, the int that wraps round and the == that says false for two identical texts. We are going to trigger all of them with real output (Java 21, shown as it looks on a computer set to English), so that when they turn up in the lab you have already seen them.
Table of Contents
Why Java makes you declare the type of every variable
In Python a variable is a label you can stick on anything, and move whenever you like:
credits = 24 # now it's an int credits = "twenty-four" # now it's a str: Python doesn't complain
Java has static typing: when you declare a variable you say what type of data it will hold, and that type never changes. If you try to put something else in it, the program doesn’t even compile:
public class FixedType {
public static void main(String[] args) {
int credits = 24;
credits = "twenty-four"; // an int can't hold text
credits = 24.5; // nor a decimal
}
}
What javac says:
FixedType.java:4: error: incompatible types: String cannot be converted to int
credits = "twenty-four"; // an int can't hold text
^
FixedType.java:5: error: incompatible types: possible lossy conversion from double to int
credits = 24.5; // nor a decimal
^
2 errors
Notice that javac finds both errors before running anything. The first one is clear: a String can’t be converted into an int. The second is more interesting: possible lossy conversion from double to int. Putting 24.5 into an int would mean throwing away the .5, and Java won’t do that behind your back. You are going to see that sentence many times in this article.
Why so strict? Because Java compiles before running and wants to check at that moment that every operation makes sense. In Python a type error shows up when the program reaches that line (maybe in front of your teacher); in Java it shows up before the program exists. You type a bit more, but type mistakes never reach run time.
How to declare variables in Java
The pattern is always the same: type, name, equals, value and semicolon. This is Laura’s card with variables:
/** Laura's card with variables: every piece of data has a name, a type and a value (made-up data). */
public class VariablesCard {
public static void main(String[] args) {
String name = "Laura Santana Ruiz"; // text: double quotes
char initial = 'L'; // a single character: single quotes
int year = 1; // integer
int credits = 24; // integer
double creditPrice = 15.50; // decimal: in the code, always with a point
boolean scholarship = false; // only true or false
System.out.println("Student: " + name + " (" + initial + ")");
System.out.println("Year: " + year + "st");
System.out.println("Credits: " + credits);
System.out.println("Price per credit: " + creditPrice + " €");
System.out.printf("Tuition fee: %.2f €%n", credits * creditPrice);
System.out.println("Scholarship? " + scholarship);
}
}
Output:
Student: Laura Santana Ruiz (L) Year: 1st Credits: 24 Price per credit: 15.5 € Tuition fee: 372.00 € Scholarship? false
Between the lines:
String name = "Laura Santana Ruiz";: the type goes before the name.Stringhas a capital letter because it is a class (we’ll see what that means later).char initial = 'L';: single quotes for one character."L"with double quotes would be aStringand wouldn’t compile.double creditPrice = 15.50;: in the code, decimals always use a point, whatever country you are in.- Look at the output:
printlnwrites15.5(point and no trailing zero), whileprintfwith%.2fwrites the price with two decimals using the decimal separator of the computer’s language (a point in English; on a Spanish computer you’d see372.00). It’s the same thing we saw in syntax:printlnuses Java’s own format andprintf, your language’s. credits * creditPricemultiplies anintby adouble: Java turns 24 into 24.0 on its own and the result isdouble. That automatic conversion has a name (widening) and its own section further down.
If Laura now enrols for 30 credits, you change one line and the whole program knows. That’s what the syntax articles promised.
Declaring without a value
You can declare first and assign later (int credits; and then credits = 24;). What you can’t do is read it before giving it a value:
public class NoValue {
public static void main(String[] args) {
int credits; // declared, but with no value
System.out.println(credits); // what does it print?
}
}
NoValue.java:4: error: variable credits might not have been initialized
System.out.println(credits); // what does it print?
^
1 error
In Python, using a name that was never assigned gave NameError at run time. In Java, local variables (the ones inside main) have no default value and javac spots it when compiling: variable credits might not have been initialized.
Names: camelCase and no accents
The Java conventions followed in FP2 (and that some teachers mark):
| What | Style | Example |
|---|---|---|
| Variables and methods | camelCase, starts in lower case | creditPrice, averageGrade |
| Classes | PascalCase, starts in upper case | VariablesCard, String |
Constants (final) | CAPITALS with underscores | CREDIT_PRICE |
Java accepts accented letters in names (año compiles), but it is better to avoid them: the day the file is opened with another encoding you’ll get strange errors.
You can declare several variables of the same type on one line: int midterm1 = 7, midterm2 = 8;. Only do it with data that belongs together; otherwise one per line reads better.
Primitive types: what they are and why they exist
Java has two families of types: primitives and objects. There are eight primitives, they are written in lower case and they store the value as it is, with nothing around it. They are the bricks everything else is built from:
| Type | What it holds | Bits | Example |
|---|---|---|---|
byte | very small integer | 8 | byte b = 100; |
short | small integer | 16 | short s = 30000; |
int | integer (the usual one) | 32 | int credits = 24; |
long | big integer | 64 | long record = 20260004321L; |
float | single-precision decimal | 32 | float f = 7.25f; |
double | decimal (the usual one) | 64 | double grade = 7.25; |
boolean | true or false | — | boolean scholarship = false; |
char | one character | 16 | char initial = 'L'; |
Wondering whether String is a primitive data type? No: it isn’t in the table. It’s a class, and that’s why it starts with a capital letter. We’ll get to it in its own section.
You don’t need to learn the limits by heart: Java’s own classes give them to you. This program prints them:
/** The limits of each integer and decimal type, taken from their wrapper classes. */
public class Ranges {
public static void main(String[] args) {
System.out.println("byte: " + Byte.MIN_VALUE + " to " + Byte.MAX_VALUE + " (" + Byte.SIZE + " bits)");
System.out.println("short: " + Short.MIN_VALUE + " to " + Short.MAX_VALUE + " (" + Short.SIZE + " bits)");
System.out.println("int: " + Integer.MIN_VALUE + " to " + Integer.MAX_VALUE + " (" + Integer.SIZE + " bits)");
System.out.println("long: " + Long.MIN_VALUE + " to " + Long.MAX_VALUE + " (" + Long.SIZE + " bits)");
System.out.println("float: up to " + Float.MAX_VALUE + " (" + Float.SIZE + " bits)");
System.out.println("double: up to " + Double.MAX_VALUE + " (" + Double.SIZE + " bits)");
System.out.println("char: " + (int) Character.MIN_VALUE + " to " + (int) Character.MAX_VALUE + " (" + Character.SIZE + " bits)");
}
}
byte: -128 to 127 (8 bits) short: -32768 to 32767 (16 bits) int: -2147483648 to 2147483647 (32 bits) long: -9223372036854775808 to 9223372036854775807 (64 bits) float: up to 3.4028235E38 (32 bits) double: up to 1.7976931348623157E308 (64 bits) char: 0 to 65535 (16 bits)
3.4028235E38 is scientific notation: 3.4 times 10 to the 38th. Decimals go much further than integers, but in exchange they aren’t exact (you’ll see it in the exercises with cents). And char goes from 0 to 65535: it is an unsigned number, which explains why you can add to it.
The integer types: byte, short, int and long
All four do the same job: they store numbers with no decimals. The only difference is how much space they take and, therefore, how far they reach. An int goes up to 2147483647 (about 2.1 billion); a long, to over 9 quintillion.
When to use each one
intfor almost everything: credits, whole grades, counters, years. It’s the type Java assumes when you write a whole number.longwhen the number can go past 2 billion: milliseconds, a record number with the year in front, world population.byteandshortonly to save memory with huge amounts of data. You’ll hardly see them in FP2.
Literals: the L for long and the f for float
A number written directly in the code (a literal) also has a type: 24 is int and 7.25 is double. That has a consequence that catches everybody out:
long worldPopulation = 8100000000; // no L: Java reads it as int float averageGrade = 7.25; // no f: Java reads it as double
Literals.java:3: error: integer number too large
long worldPopulation = 8100000000; // no L: Java reads it as int
^
1 error
integer number too large: even though the variable is long, the number 8100000000 is read first as an int, and it doesn’t fit. javac only shows that error because it’s one of those that stops it reading the file; once you fix it, in the next round it will complain about the float (a double in a float also loses precision). The fix is to tell Java the type of the literal with a suffix:
/** The same data with their suffix: L for long and f for float. */
public class LiteralsFixed {
public static void main(String[] args) {
long worldPopulation = 8100000000L; // L: this number is long
float averageGrade = 7.25f; // f: this number is float
double gradeDouble = 7.25; // with no suffix, a decimal is double
System.out.println(worldPopulation);
System.out.println(averageGrade);
System.out.println(gradeDouble);
}
}
8100000000 7.25 7.25
The L can be lower case, but 8100000000l looks like it ends in a 1. Always write it in upper case.
The decimal types: float and double
float keeps about 7 significant digits and double, about 15. A literal with a point is double unless it has an f (or F).
When to use float and when double
In FP2, always double. float exists for graphics, sensors or huge amounts of data where memory matters. Neither of them is any good for exact money: in the exercises you’ll see why the print shop inventory adds up to 6149.599999999999 and how cents in a long fix it.
boolean: only true or false
In Python it was True and False with a capital letter; in Java, true and false in lower case. And there’s a more important difference: in Java a number is not a boolean. In Python if 1: worked; in Java, a condition has to be a real boolean, such as credits > 0. You’ll see it in flow control.
Overflow: the int that wraps round
What happens if an int goes past its maximum? In Python integers grow as much as they need to. In Java they don’t: the value wraps round and comes out at the other end. No error, no warning:
/** What happens when an int goes past its maximum, and how long avoids it. */
public class Overflow {
public static void main(String[] args) {
int maximum = Integer.MAX_VALUE;
System.out.println("Maximum int: " + maximum);
System.out.println("Maximum + 1: " + (maximum + 1)); // wraps round
System.out.println("With long: " + (maximum + 1L)); // 1L turns the sum into long
// Milliseconds in a 365-day course: the sum is done in int and overflows
int msCourseWrong = 365 * 24 * 60 * 60 * 1000;
long msCourseLate = 365 * 24 * 60 * 60 * 1000; // long on the left is not enough
long msCourseRight = 365L * 24 * 60 * 60 * 1000; // the L on the FIRST number
System.out.println("ms in a course (int): " + msCourseWrong);
System.out.println("ms in a course (long, late): " + msCourseLate);
System.out.println("ms in a course (365L): " + msCourseRight);
}
}
Maximum int: 2147483647 Maximum + 1: -2147483648 With long: 2147483648 ms in a course (int): 1471228928 ms in a course (long, late): 1471228928 ms in a course (365L): 31536000000
Three lessons in one output:
maximum + 1gives-2147483648: the minimum. Picture the odometer of an old car going from 99999 to 00000.maximum + 1Lgives the right number: theLturns that sum into alongsum.- The sneakiest one:
long msCourseLate = 365 * 24 * 60 * 60 * 1000;also gives 1471228928. The variable islong, but the sum on the right is worked out entirely inint(all the numbers areint) and overflows before it reaches the variable. Checking by hand: 365 × 24 × 60 × 60 × 1000 = 31,536,000,000, which is more than 2,147,483,647. The fix is for the first number to belong:365L * 24 * ….
Thinking it’s enough for the variable on the left to be long (or double). Java first works out the expression on the right with the types of its numbers and only then stores the result. If the overflow has already happened, the variable gets the wrong number.
char: a number shaped like a letter
byte and char: the link with ASCII
A char holds one Unicode character, but underneath it’s a 16-bit number: the character’s code in the table. The first 128 match ASCII (the good old 7-bit table, which fits in a byte): 'A' is 65, 'a' is 97, '0' is 48 and the space is 32. Since it’s a number, you can convert it and do arithmetic with it:
/** A char is a number shaped like a letter: the Unicode table (the first 128, ASCII). */
public class Characters {
public static void main(String[] args) {
char letter = 'L';
int code = letter; // char → int: automatic
System.out.println(letter + " has the code " + code);
System.out.println("'L' + 1 = " + (letter + 1)); // char + int = int
System.out.println("(char) ('L' + 1) = " + (char) (letter + 1));
System.out.println("'A' + 'B' = " + ('A' + 'B')); // two chars are added as numbers
System.out.println("\"\" + 'A' + 'B' = " + ("" + 'A' + 'B')); // with text in front, they stick together
letter++; // ++ does work with char
System.out.println("After letter++: " + letter);
letter += 2; // += too (it has a hidden cast)
System.out.println("After letter += 2: " + letter);
char digit = '7';
System.out.println("'7' as a number: " + (int) digit + ", its value: " + (digit - '0'));
}
}
L has the code 76
'L' + 1 = 77
(char) ('L' + 1) = M
'A' + 'B' = 131
"" + 'A' + 'B' = AB
After letter++: M
After letter += 2: O
'7' as a number: 55, its value: 7
Line by line:
int code = letter;works without a cast: acharfits in anint.'L'is 76.letter + 1gives77, notM:char + intgivesint. To get back to a letter you need the cast(char) (letter + 1).'A' + 'B'gives131(65 + 66): twocharvalues are added. However,"" + 'A' + 'B'givesABbecause, as we saw in syntax, concatenation goes left to right and starting with a text turns everything into text.letter++andletter += 2do work: those operators carry a hidden cast back tochar.digit - '0'turns the character'7'into the number 7 (55 − 48 = 7). It’s the classic trick for turning text digits into numbers one at a time.
What does not compile is the long version of letter++:
public class CharPlus {
public static void main(String[] args) {
char letter = 'L';
letter = letter + 1; // char + int gives int
}
}
CharPlus.java:4: error: incompatible types: possible lossy conversion from int to char
letter = letter + 1; // char + int gives int
^
1 error
letter + 1 is an int, and putting an int into a char can lose data (an int goes much further than 65535). possible lossy conversion again.
Single quotes for char ('L') and double quotes for String ("L"). In Python it didn’t matter; in Java they are different types and char c = "L"; doesn’t compile.
String: the one that isn’t primitive
String isn’t one of the eight primitives: it’s a class that comes with Java, and every text is an object of that class. That’s why it has methods (called with a dot) and why it starts with a capital letter:
/** String isn't primitive: it is an object with methods, and it can't be changed (immutable). */
public class Texts {
public static void main(String[] args) {
String name = "Laura";
String surname = "Santana";
String fullName = name + " " + surname; // creates a new String
System.out.println(fullName + " has " + fullName.length() + " characters");
System.out.println(fullName.toUpperCase());
System.out.println("Initial: " + fullName.charAt(0));
fullName.toUpperCase(); // doesn't change fullName: returns another one
System.out.println(fullName);
String empty = "";
System.out.println("Length of \"\": " + empty.length());
}
}
Laura Santana has 13 characters LAURA SANTANA Initial: L Laura Santana Length of "": 0
Two details in the output:
fullName.length()counts 13 characters: «Laura» (5) + the space (1) + «Santana» (7). With brackets, because it’s a method.- The line
fullName.toUpperCase();on its own changes nothing: the next print still saysLaura Santana.Stringobjects are immutable, just like in Python:toUpperCase()returns a new upper-case text, and if you don’t store it in a variable (fullName = fullName.toUpperCase();), it’s lost.
String methods (substring, indexOf, split, replace…) have their own topic later on. For this one, length(), charAt(), toUpperCase() and toLowerCase() are enough.
Primitives versus objects: the stack and the heap
Here is the difference that explains nearly everything odd in this topic. Java splits memory into two areas:
- The stack: the variables of each method. A primitive variable keeps its value here, inside its own box.
- The heap: objects. A variable of an object type (a
String, for example) doesn’t keep the text: it keeps a reference, an arrow pointing to the object on the heap.
That’s why copying behaves differently depending on the type. int copy = credits; copies the number: if you then change copy, credits is still 24. String b = a; copies the arrow: a and b point to the same object. With String objects you don’t notice because they are immutable (nobody can change the object both of them point to), but with the objects you’ll create in Classes and objects you will.
The difference between == and .equals(): Java’s classic trap
== compares what is inside the box. With primitives that’s the value, so 7 == 7 is true. With objects, what’s in the box is the arrow: == asks «do you point to the same object?», not «do you say the same thing?». To compare the content there’s equals():
/** == compares references; equals() compares the content. */
public class Equality {
public static void main(String[] args) {
String a = "lsantana"; // literal: goes to the String pool
String b = "lsantana"; // the same literal: the same object from the pool
String c = "l" + "santana"; // two literals: Java joins them when compiling
String initial = "l";
String d = initial + "santana"; // worked out at run time: new object
String e = new String("lsantana"); // new: always a new object
System.out.println("a == b " + (a == b));
System.out.println("a == c " + (a == c));
System.out.println("a == d " + (a == d));
System.out.println("a == e " + (a == e));
System.out.println("a.equals(d) " + a.equals(d));
System.out.println("a.equals(e) " + a.equals(e));
Integer x = 127, y = 127; // Integer keeps -128 to 127 in a cache
Integer z = 128, w = 128;
System.out.println("127 == 127 " + (x == y));
System.out.println("128 == 128 " + (z == w));
System.out.println("equals 128 " + z.equals(w));
}
}
a == b true a == c true a == d false a == e false a.equals(d) true a.equals(e) true 127 == 127 true 128 == 128 false equals 128 true
Case by case:
| Comparison | Result | Why |
|---|---|---|
a == b | true | Two identical literals: Java keeps a single one in the String pool and both point to it |
a == c | true | "l" + "santana" are two literals: javac joins them when compiling and the same literal remains |
a == d | false | initial + "santana" uses a variable: it’s worked out at run time and creates a new object |
a == e | false | new String(...) always creates a new object |
a.equals(d) and a.equals(e) | true | equals() compares the letters, one by one |
The danger is that == sometimes gets it right (the first two rows). You test with literals, it works and you relax. Then the text comes from a Scanner, from a file or from concatenating variables, and it stops working. In the practice you’ll see it with Laura’s card username.
Something similar happens with Integer: Java keeps the objects from -128 to 127 in a cache, so 127 == 127 gives true and 128 == 128 gives false. The rule is the same for all objects:
Comparing texts with ==. For String (and any object), always equals(): username.equals("lsantana"). == only for primitives.
final: Java’s constant
Some data mustn’t change in the whole program: the price per credit, the admin fee. final in front of the type turns the variable into a constant: it gets a value once and can’t be assigned again. By convention, in capitals:
/** final for what doesn't change and var so Java works out the type. */
public class Constants {
public static void main(String[] args) {
final double CREDIT_PRICE = 15.50; // final: it can't be assigned again
final int CREDITS = 24;
final double FEE = 27.30;
System.out.printf("Tuition: %d x %.2f + %.2f = %.2f €%n",
CREDITS, CREDIT_PRICE, FEE, CREDITS * CREDIT_PRICE + FEE);
var year = 1; // var: Java works out int
var degree = "GCID"; // works out String
var average = 7.5; // works out double
System.out.println(degree + ", " + year + "st, average " + average);
}
}
Tuition: 24 x 15.50 + 27.30 = 399.30 € GCID, 1st, average 7.5
Check: 24 × 15.50 = 372.00, plus 27.30 = 399.30 €. And if someone tries to change a constant:
public class BadFinal {
public static void main(String[] args) {
final double CREDIT_PRICE = 15.50;
CREDIT_PRICE = 16.00; // a price rise?
var year = 1;
year = "first"; // var isn't dynamic
}
}
BadFinal.java:4: error: cannot assign a value to final variable CREDIT_PRICE
CREDIT_PRICE = 16.00; // a price rise?
^
BadFinal.java:6: error: incompatible types: String cannot be converted to int
year = "first"; // var isn't dynamic
^
2 errors
The first error is the one we were after: cannot assign a value to final variable CREDIT_PRICE. In Python, constants in capitals were just a habit; in Java, final is enforced by the compiler. The second error belongs to the next section.
Multiple declarations and var
Since Java 10 there’s var: you write var instead of the type and Java works it out from the value (type inference). In the program above, var year = 1; makes year an int and var degree = "GCID";, a String.
But careful: var doesn’t turn Java into Python. The type is decided when you declare and stays fixed, as the second error of BadFinal shows: year is an int even though you wrote it with var, and it won’t accept "first". Also, var only works for local variables with a starting value (var x; doesn’t compile: there’s nothing to work the type out from).
Java has no typeof like JavaScript or type() like Python for primitives: you know the type because you wrote it (or because var worked it out from the value). FP2 notes and exams nearly always use the explicit type; var is valid, but better used when the type is obvious.
Converting between types: casting
There are two directions:
- Widening: from a small type to a big one. It always fits, so Java does it on its own.
- Narrowing: from a big type to a small one. Data can be lost, so Java demands that you ask for it with a cast: the type in brackets in front of the value,
(int) grade.
/** Automatic (widening) and forced (narrowing) conversions between types. */
public class Casting {
public static void main(String[] args) {
int credits = 24;
long creditsLong = credits; // int → long: automatic
double creditsDouble = credits; // int → double: automatic
System.out.println(creditsLong + " / " + creditsDouble);
double grade = 9.99;
System.out.println("(int) 9.99 = " + (int) grade); // truncates
System.out.println("(int) -9.99 = " + (int) -grade); // towards zero too
System.out.println("Math.round = " + Math.round(grade)); // rounds (returns long)
System.out.println("(int) 3000000000L = " + (int) 3000000000L); // doesn't fit: bits cut off
System.out.println("(byte) 200 = " + (byte) 200); // 200 - 256
int big = 16777217;
float f = big; // automatic... but it loses precision
System.out.println("int " + big + " → float " + (int) f);
}
}
24 / 24.0 (int) 9.99 = 9 (int) -9.99 = -9 Math.round = 10 (int) 3000000000L = -1294967296 (byte) 200 = -56 int 16777217 → float 16777216
What matters in each line:
(int) 9.99gives9and(int) -9.99,-9: the cast truncates (drops the decimals and moves towards zero), it doesn’t round. To round,Math.round(9.99)gives10(and returns along).(int) 3000000000Lgives-1294967296: the number doesn’t fit and Java keeps the bottom 32 bits. With a cast, Java obeys you even if the result is absurd: you answer for it.(byte) 200gives-56: abytegoes up to 127, and 200 − 256 = −56.int→floatis automatic, but 16777217 becomes 16777216: afloatdoesn’t have enough digits for such a long number. Automatic doesn’t always mean lossless.
Without the cast, javac won’t let the narrowing through:
public class NoCast {
public static void main(String[] args) {
double grade = 9.99;
int wholeGrade = grade; // double → int with no cast
}
}
NoCast.java:4: error: incompatible types: possible lossy conversion from double to int
int wholeGrade = grade; // double → int with no cast
^
1 error
Integer division
And now the trap that gives this article its name. When you divide two int values, Java does integer division: the result is another int and the decimals are thrown away. With Laura’s grades:
/** Integer division and where to put the cast, with Laura's grades (made-up data). */
public class Division {
public static void main(String[] args) {
int midterm1 = 7;
int midterm2 = 8;
int practical = 6;
int sum = midterm1 + midterm2 + practical; // 21
System.out.println("7 / 2 = " + (7 / 2)); // int / int = int
System.out.println("7 % 2 = " + (7 % 2)); // the remainder
System.out.println("7 / 2.0 = " + (7 / 2.0)); // with a double, real division
System.out.println("Average int: " + sum / 3); // 21 / 3 = 7 (exact, by luck)
System.out.println("Average of 7 and 8: " + (midterm1 + midterm2) / 2); // 15 / 2 = 7
System.out.println("Cast before: " + (double) (midterm1 + midterm2) / 2); // 15.0 / 2 = 7.5
System.out.println("Cast after: " + (double) ((midterm1 + midterm2) / 2)); // (double) 7 = 7.0
System.out.println("With 2.0: " + (midterm1 + midterm2) / 2.0); // 7.5
double averageWrong = (midterm1 + midterm2) / 2; // the double arrives too late
System.out.println("double = int / int: " + averageWrong);
System.out.println("1 / 3 * 3 = " + (1 / 3 * 3)); // 0 * 3
}
}
7 / 2 = 3 7 % 2 = 1 7 / 2.0 = 3.5 Average int: 7 Average of 7 and 8: 7 Cast before: 7.5 Cast after: 7.0 With 2.0: 7.5 double = int / int: 7.0 1 / 3 * 3 = 0
7 / 2gives3and7 % 2gives1: quotient and remainder, like in primary school. Very useful together (you’ll use them in the practice to split instalments).- The average of 7, 8 and 6 gives
7because 21 / 3 is exact: integer division didn’t show… by luck. - The average of 7 and 8 gives
7instead of 7.5: 15 / 2 = 7. - Cast before:
(double) (midterm1 + midterm2) / 2turns 15 into 15.0 and then divides: 7.5. - Cast after:
(double) ((midterm1 + midterm2) / 2)divides first (7) and then converts: 7.0. The cast arrives too late. double averageWrong = (midterm1 + midterm2) / 2;gives 7.0 for the same reason as the overflowlong: the variable isdouble, but the division was done between twointvalues.1 / 3 * 3gives 0: 1 / 3 = 0, and 0 × 3 = 0.
Writing the average as (a + b) / 2 with two int values. The rule that never fails: as soon as there’s a division and you want decimals, make one of the two operands a double before dividing: / 2.0 or a cast in front of the dividend.
Converting between String and numbers
A text "24" isn’t the number 24 and can’t be converted with a cast ((int) "24" doesn’t compile: a String is an object, not a number). There are methods for that:
/** From text to number and from number to text: it isn't a cast, they are methods. */
public class Convert {
public static void main(String[] args) {
String creditsTxt = "24"; // this is how data arrives from a form or a file
String priceTxt = "15.50";
int credits = Integer.parseInt(creditsTxt);
double price = Double.parseDouble(priceTxt);
System.out.println(creditsTxt + creditsTxt); // two texts: they stick together
System.out.println(credits + credits); // two ints: they are added
System.out.println(credits * price);
String total = String.valueOf(credits * price);
System.out.println("As text: " + total + " (" + total.length() + " characters)");
System.out.println("With \"\" + : " + ("" + 42));
}
}
2424 48 372.0 As text: 372.0 (5 characters) With "" + : 42
creditsTxt + creditsTxt sticks two texts together (2424); credits + credits, already converted, adds (48). The other way round, number to text: String.valueOf(...) or the trick "" + number.
And if the text isn’t shaped like a number? Something very typical in Spain happens: writing the price with a comma.
public class DecimalComma {
public static void main(String[] args) {
String priceTxt = "15,50"; // written the Spanish way
double price = Double.parseDouble(priceTxt);
System.out.println(price);
}
}
Exception in thread "main" java.lang.NumberFormatException: For input string: "15,50"
at java.base/jdk.internal.math.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2054)
at java.base/jdk.internal.math.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
at java.base/java.lang.Double.parseDouble(Double.java:792)
at DecimalComma.main(DecimalComma.java:4)
Double.parseDouble only understands the point, even on a computer set to Spanish. The program stops with a NumberFormatException and the last line of the trace (DecimalComma.java:4) tells you where. How to handle these exceptions without the program stopping is in the Exceptions topic; with the Scanner of the next topic the opposite happens (on a Spanish computer it expects the comma), and you’ll see it in its theory.
Wrapper classes: Integer, Double, Boolean…
Every primitive has a «wrapper» class that turns it into an object: int → Integer, double → Double, char → Character, boolean → Boolean, long → Long… You’ve already used their constants (Integer.MAX_VALUE) and their methods (Integer.parseInt). You need them above all for collections (ArrayList<Integer>), which only hold objects. Java converts from one to the other by itself (autoboxing and unboxing), but they are objects: they are compared with equals(), as you saw with 128.
Common mistakes with variables
- Integer division by accident:
(a + b) / 2with twointvalues. Adoubleoperand before dividing. - Cast after the operation:
(double) (a / b)arrives too late. The cast goes in front of the dividend. - Silent overflow:
365 * 24 * 60 * 60 * 1000inint. The first number withL. longwithout L orfloatwithout f:8100000000doesn’t compile as a literal;7.25isdouble.==with texts: alwaysequals()forString.- Thinking the cast rounds:
(int) 9.99is 9. To round,Math.round. - Commas in the code: 15,50 isn’t a number inside Java. In the code, a point; the comma only appears in
printfoutput on a Spanish computer.
Watch it step by step in Python Tutor
pythontutor.com runs the code line by line and draws the variables and the objects with their arrows. Choose Java in the drop-down and paste this program (a single public class with main and no keyboard input):
public class References {
public static void main(String[] args) {
int credits = 24;
int copy = credits; // copies the value
copy = 30; // credits is still 24
String a = "lsantana";
String b = a; // copies the reference
String c = new String("lsantana"); // another object with the same text
boolean sameRef = (a == c);
boolean sameText = a.equals(c);
System.out.println(credits + " " + copy);
System.out.println((a == b) + " " + sameRef + " " + sameText);
}
}
Output:
24 30 true false true
What to watch step by step:
- After
int copy = credits;you’ll see two boxes with24. Whencopy = 30;runs, only one changes: primitives are copied by value. - With
String b = a;, Python Tutor draws (if in its options you choose to show String objects instead of just their value) two arrows to the same object. It’s the stack and heap picture above. new String("lsantana")creates a second object on the heap, with the same text and a different address.- On the last line,
a == bgivestrue(same arrow),sameRefisfalseandsameTextistrue: compare it with the output above.
Quick summary
| I want… | I write | Watch out for… |
|---|---|---|
| A whole number | int credits = 24; | Over 2147483647: long and L |
| A decimal | double grade = 7.25; | Point in the code; not exact for money |
| A character | char initial = 'L'; | Single quotes; it’s a number (76) |
| A text | String name = "Laura"; | It’s an object: equals(), never == |
| A constant | final double CREDIT_PRICE = 15.50; | Can’t be reassigned |
| An average with decimals | (a + b) / 2.0 | (a + b) / 2 with int truncates |
| Drop decimals | (int) 9.99 → 9 | Truncates, doesn’t round: Math.round |
| Text → number | Integer.parseInt("24") | a text like “15,50” throws NumberFormatException |
Three ideas to keep:
- The type rules the sums: Java calculates with the type of the operands and only at the end stores the result in the variable. That’s why integer division and overflow go wrong even if the variable is
doubleorlong. - Primitives in the box, objects behind an arrow:
==compares what’s in the box; for texts,equals(). - Going up a type is free; going down needs a cast and can lose data.
Now it’s time to use all this in complete programs: in the Java variables practice we build Laura’s tuition receipt with instalments in cents, her final FP2 grade (with integer division hiding twice) and her university card, with a record number that doesn’t fit in an int. And then, in Java flow control, these variables start making decisions.
Did this article help you? Here is how you can support SergioLearns →




The hardware that holds up the operating system — theory with my IC2 assignment on the printer, an interrupt that stops everything and a cache that hits (and the infinite loop that didn’t freeze my laptop)