Types, Values and Variables
Primitive Types, Literals and Variable Definitions
PREREQUISITES — You should already…
- have access to a JDK 8, and be able to create and run simple Java Programs.
- know how to use the command line (Command Prompt, PowerShell or a POSIX shell).
Some Groundwork
We cannot have a consistently meaningful discussion about a typed language like Java, without some understanding of ‹type› as an abstraction, and which types are available in Java. Since types are at the core of every part of the language, you cannot expect to understand everything all at once, so we will gradually add more and more information about types.
Literals
Java™ has literals to represent anonymous immediate values, and each literal has a type. Some abstract literals have names, like null
, true
and false
in Java. Every literal is an ‹expr›ession. Character literals and string literals are distinct in Java, while class literals are also possible. With certain limitations, functions can also be represented literally as anonymous functions, called lambdas.
Expression Concepts
An expression is any language construct that results in, or is, a value. Since all values have types, it follows that every expression has a type. An expression can be very simple, or very complex, since operators can appear (only) in expressions.
It is important to understand that every expression:
a) has a ‹type›, and
b) has a ‹value›.
You cannot just think of an expression as a value. To ignore the type, means you ignore the validity of the value in the context where it is used, and the operations which can and cannot be performed on the value.
In formal discussion and syntax, we will indicate expressions with: ‹expr›.
Comments
You can use single-line comments, starting with //
, anywhere in Java source code. All text starting with the two forward slash characters is ignored, up to the end of the line.
Block comments start with the /*
token, and end with the first */
token seen. This means block comments can span lines, but cannot nest.
Special comments, which are the only ones processed by the javadoc
tool, are called, unsurprisingly, Javadocs. They must appear directly before a class or method, and must start with: /**
. We shall discuss these in more detail later.
Inside string literals, comment characters are not tokenised. This means they are not processed as meaningful, and will then not be treated as comment start or termination sequences.
Identifiers
Before long, you will need to create names for classes, methods and variables. These names are formally called ‹ident›ifiers, which we will represent as ‹ident› in syntax specifications to keep it short. Programming languages have rules about the characters that may be used in identifiers.
Syntax — Identifiers
- Cannot be a keyword.
- Are case-sensitive.
- First character must be:
_
,a
…z
,A
…Z
.- Subsequent characters can be:
_
,a
…z
,A
…Z
,0
…9
.- Single underscore on its own is not allowed (JDK 9).
Apart from the last point, these are the same rules employed in many C-like languages. To be completely honest, you can also use $
, but you should not; and the alphabetic characters can actually be any Unicode alphabetic character, but you should not.
Without going into the detail, you create your own identifiers in the following situations:
Syntax — Identifier Locations
☆
The final
is often used as static final
. In Java, unlike C, C++, C# and some other similar languages, the static
modifier is never legal inside a function body; final
is allowed however.
Conventions
To save space, we do not usually include boilerplate code. So all examples where there is no main
function, assume you understand that the code will only work within the following structure in some .java
file:
Example.java
— Example Boilerplate
import static java.lang.System.*; //←simplifies `System.out` use,
// can say: `out.print…` instead.
public class Example { //←any name; but must match filename.
public static void main (String[] args) {
// ──────────────────────────────────────────────────────────
// This is where example code goes. Lines above and below are the
// “boilerplate” lines, which are the same for all examples.
// ──────────────────────────────────────────────────────────
exit(0); //←good convention, not “required”.
}
}//class
We often use the format()
function (or method) of System.out
in examples. You may use printf()
instead… it simply calls format
. It works just like the format()
function of the String
class. The basic principle for both methods is that the first argument to pass, is a format string, potentially containing format specifiers, all starting with %
. These format specifiers act like placeholders where formatted arguments following the first format string argument, are placed in the output. To format an actual percentage sign, you have to use two: %%
.
String s = String.format(
"1) string: %s, int: %d, float: %f",
"ABC", 123, 456.789);
out.println(s);
out.format(
"2) string: %s, int: %d, float: %f",
"ABC", 123, 456.789);
1) string: ABC, int: 123, float: 456.789000
2) string: ABC, int: 123, float: 456.789000
Notation for Types and Values
To shorten the sentence: “the expression ‹expr› has type ‹type›”, we shall use the notation: ‹expr› ─t→
‹type›. Similarly, for the value of the ‹expr›ession, we will use: ‹expr› ─v→
‹value›. For example, consider a variable V
of type int
, having the value 123
:
In discussions about V
, we will represent its ‹type› as : V
─t→
int
and its value as : V
─v→
123
. On rare occasions, as part of an expression, we may write V
t, or V
v to represent the ‹type› and ‹value› respectively.
Summary
- ‹expr›
-t→
‹type› short for “‹expr› has type ‹type›”; also: ‹expr›t. - ‹expr›
-v→
‹value› short for “‹expr› has value ‹value›”; also: ‹expr›v. - ‹ident› short for ‹ident›ifier.
- ‹obj› short for ‹obj›ect.
- ‹type› represents any type.
- ‹param› syntax to define special method variables called ‹param›eters.
- ‹arg› ‹arg›uments passed to function (initialise parameters).
Fundamental Types
Even for simple programs, we have to be aware of types, since every value, every variable, every function return — in other words, every expression — has a type, even if it is an abstract type called void
. A type determines the storage space, and the operations that can be performed on the type, including which methods the type may or may not expose.
Syntax — Uses for Types
- ‹type› ‹ident›
;
define member or local variable.- ‹type› ‹ident›
=
‹expr›;
define member or local variable and initialise.- [
static
]final
‹type› ‹ident›=
‹expr›;
define symbolic constant;static
only usable at class level.(
‹type›)
‹expr› or(
‹type›)(
‹expr›)
type conversion (cast) operator.new
‹type›(
‹arg›)
allocate and initialise new object (constructor call).- ‹type› ‹ident›
(
‹param›) {
⋯}
define function ‹ident›, returning ‹type›.- ‹arg› ‹arg›uments passed to function
Even when you define ‹param>eters for a method, types come into play. Types are literally everywhere — almost every line in Java either has a type name, or contains an expression, which has a type.
TIP — String Representation of a Type
You can get the string representation for the type of any ‹obj›ect or ‹expr›ession with: ‹obj›.getClass().getName()
, or (
‹expr›).getClass().getName()
— as long as it is not a primitive type. IDEs normally do this automatically for you, and represent it in popups. You can also use getSimpleName()
instead of getName()
.
Primitive Types
The primitive types correspond closely to types you will find in C/C++, i.e., native types. They are not objects, and thus have no methods or properties. They are also not dynamically allocated. The primitive types are:
boolean
(true
/false
)
short
(16-bit signed integer)
char
(16-bit unsigned integer used for 16-bit Unicode characters)
int
(32-bit signed integer)
long
(64-bit signed integer)
float
(32-bit IEEE 754 float)
double
(64-bit IEEE 754 float).
Here are a few examples where we use the previous syntax examples, replacing ‹type› with one of these primitive types:
Example.java
— Working Example
public class Example { //←user-defined type `Example`.
long f (int parm) { //←define method `f` returning `long`,
return parm * 123L; // having one parameter of type `int`.
}
static void g () { //←shared function `g`, returning `void`.
}
public static int main (String[] args) {
int v; //←define variable `v` -t→ `int`.
long l; //←define variable `l` -t→ `long`.
boolean b = false; //←define & init. `b` -t→ `boolean`.
Integer w = new Integer(12); //←define `w` -t→ `Integer`, and allocate
// new `Integer` object, passing `12` arg.
}
}//class
The last line cheated a bit, because it used a primitive wrapper, which is not a primitive type. We are representing patterns here, where you can use ‹type›s; it is not (yet) about variables or functions. You should practice the patterns we used.
Primitive Wrappers
To make the primitive types act more object-oriented, you can box a primitive value in a corresponding wrapper class. This means a variable with the wrapper type can also be assigned null
— we say it is nullable, when this is possible.
The fully-qualified names of all the following wrapper classes start with java.lang
, which is always automatically import
ed when compiling Java programs, which is why we almost never write, for example, java.lang.Integer
.
boolean
Boolean
char
Character
short
Short
int
Integer
long
Long
float
Float
double
Double
Long L = Long.valueOf(123); //←recommended way to get a new `Long`.
L = new Long(123); //←deprecated in JDK 9.
L = Long.parseLong("123"); //←`parseLong` returns `long`.
L = Long.valueOf("123"); //←`valueOf` returns `Long`.
String s = L.toString(); //←all classes have `toString()`.
L = null; //←`Long` is nullable.
long l = Long.valueOf(123); //←implicitly convert `Long` to `long`.
l = Long.parseLong("123"); //←`parseLong` returns `long`.
l = Long.valueOf("123"); //←implicitly convert `Long` to `long`.
l = null; //←ERROR.
We could (should) have used 123L
(a long
) instead of 123
(an int
), but Java will automatically promote an int
value to a long
value. We can also see that Java will implicitly convert Long
to long
, and vice versa. The only real impact is that L = null
is legal, but not l = null
.
Object
All classes inherit from the java.lang.Object
class, or just Object
for short. Practically, this means that all objects, of any type, have the same methods you find in Object
. The other characteristic to be aware of, is that a variable of type Object
can store a reference to any type of object, without an explicit cast. This is an OOP feature that we will consider at a later stage.
Inspecting the list of methods that Object
provides, you should notice toString()
, which most classes override when they inherit from Object
. Again, the detail of inheritance is not particularly relevant here now, but you must understand the implication: all objects, of all types, have a toString()
method. This means that any type will produce some string representation of itself, when you call toString()
on it.
Object obj = 123;
out.format("1) obj = %s\n", obj);
obj = "ABC";
out.format("2) obj = %s\n", obj);
obj = 123.456;
out.format("3) obj = %s\n", obj);
double dbl = (double)obj;
out.format("4) dbl = %f\n", obj);
out.format("5) dbl = %s\n", obj.toString());
1) obj = 123 4) dbl = 123.456000
2) obj = ABC 5) dbl = 123.456
3) obj = 123.456
The other important method is equals()
. This is the only sure way to compare two objects for value equality. Although much of the following syntax we have not yet discussed, understand the spirit of the code, which shows that the equality operator does not always work the way you expect, but that equals()
is always safe:
Long l1 = new Long(123); Long l2 = new Long(123);
if (l1 == l2)
out.println("1) l1 == l2");
else
out.println("1) l1 != l2");
if (l1.equals(l2))
out.println("2) l1 == l2");
else
out.println("2) l1 != l2");
l1 = Long.valueOf(234); l2 = Long.valueOf(234);
if (l1 == l2)
out.println("3) l1 == l2");
else
out.println("3) l1 != l2");
if (l1.equals(l2))
out.println("4) l1 == l2");
else
out.println("4) l1 != l2");
1) l1 != l2 3) l1 != l2
2) l1 == l2 4) l1 == l2
Remember, the main point here is that using ‹obj›.equals(
‹expr›)
, which all types inherit from Object
, is the safest way to compare the values of objects. The equality operator may or may not compare the values, and is more likely to compare the references to the values. Comparing references is useful, but not if you think you are comparing values.
String Type
The String
class is not a primitive type, but it is almost impossible to write a Java program without using String
, since even string literals have type String
. Java provides some convenient shorthand syntax, so it acts in many ways, like a primitive type. Always remember that String
is an immutable type.
It is possible to “convert” any type to some String
representation of its value, with the overridden toString()
available on all types, since all types inherit it from Object
. This makes String
, and strings in general, ubiquitous. All strings have type String
, so we will not always remind you of that fact from here on.
Although we discuss string literals later, here are some examples of using the String
class, so that you have some initial patterns to experiment with:
char[] arr = {'G', 'H', 'I'}; //←array of `char`acters,
String str = "ABC"; out.format("1) str = \"%s\"\n", str);
str = new String("DEF"); out.format("2) str = \"%s\"\n", str);
str = new String(arr); out.format("3) str = \"%s\"\n", str);
str = str + "JKL"; out.format("4) str = \"%s\"\n", str);
str += "MNO"; out.format("5) str = \"%s\"\n", str);
out.println("str len = " + str.length());
1) str = "ABC"
2) str = "DEF"
3) str = "GHI"
4) str = "GHIJKL"
5) str = "GHIJKLMNO"
str len = 9
Most types are implicitly convertible to String
, which is why we did not have to explicitly cast the result of the str.length()
expression, which results in an int
. The length()
method is one of many instance methods of type String
. No instance or static method of String
can modify the original value — they all return modified copies of the string, because of String
’s immutability.
It is not necessary to use: new String("ABC")
, since just using "ABC"
will suffice for convenience. Nor is it common to place characters in an array, but it is a way to create a new String
.
Strings can be concatenated with the +
operator, while the compound assignment: +=
is more convenient. Because String
s are immutable, concatenation can be expensive. You should thus rather use the java.lang.StringBuilder
class, which is very efficient for concatenation, especially if you concatenate long strings, or concatenate multiple times.
String Literals
As you may have seen from the examples, a string literal is an immediate value of type String
, and uses a set of paired double quotes to delimit a sequence of 0
or more characters. A literal string with no characters is an empty string: ""
.
At a more fundamental level, a literal string becomes an array of char
values, which is implicitly convertible to type String
. Just like character literals, escape sequences may appear inside a string literal.
Escape Sequences
To represent special characters as a literal, they must be escaped. The start of the escape sequence is the backslash character (\
). This is applicbable to string literals, as well as character literals. And so, to represent a single quote as a character literal, you cannot write: '''
, but must write: '\''
instead. Similarly, to represent a backslash character as a literal, you must write: '\\'
. For string literals, you similarly cannot write """
to represent a double quote character; instead, you must write: "\""
.
The following special escape sequences represent unprintable characters:
\b
(back space)
\t
(tab)
\n
(new line)
\r
(carriage return)
\f
(form feed)
Although it is possible to represent any character code between 0
to 255
with octal (\000
…\377
), e.g.: '\033'
for ASCII 27
(1B
in hex), you should generally use the Unicode escape sequence. The Unicode escape sequence is a \u
token, followed by exactly 4 hexadecimal digits, e.g. \u2500
(Unicode horizontal line-drawing character), or \u001B
for ASCII ESC (27
).
The escape sequence: '\"'
is legal with a character literal, but not necessary; using '"'
is also legal. But it is required in string literals to represent a double quote character.
JDK API Documentation
Due to the large amount of information available, you will forever remain dependent on the JDK SE 8 Documentation, whether you peruse it online, download the documentation for offline use, or use API viewers like Dash (MacOS only), or Zeal (Windows and Linux); or even the same documentation in your favourite IDE.
You have to start using the formal documentation as soon as possible, so that you can get used to the terminology and layout. Using the java.lang.String
class as an example, there are some important points to note.
base class
The base class provides members that are also available in a derived class, but not documented in the derived class (although the Javadoc does provide a link). To fully understand what a class is capable of, you have to know what it inherited from its base class, the java.lang.Object
class in this case. The other benefit is that you can pass this class to a parameter (or store it in a variable) of the base class, without an explicit cast.
capabilities
Most classes in the JDK SE framework implement a number of interfaces. Practically, for you, this means you can pass String
, for example, to any parameter that expects one of the interface types shown, without any explicit type conversion — as if they are base classes. For that to work, String
has to implement certain methods and behaviour, and you can see interfaces almost as a category, or set, of methods that are guaranteed to be available.
For example, the java.lang.Comparable<String>
interface means that Strings
can be compared by value, and passed to any method that expects a Comparable
type. It also implies that String
s have a compareTo(String)
method.
initialisers
The constructors initialise new objects. They are methods, which mean they can be overloaded, which is why the documentation will list a number of them, all having different types and numbers of parameters. Constructors are special methods that can only be called by new
(directly or indirectly), and have the same name as the class (String
in our example case).
From the String
class’ Constructor Summary, we can see that we can initialise a String
object in a number of ways, simply by passing the correct number and type of parameters. The first one in the list, for example, indicates that we can even create an empty string by calling: ‘new String()
’ with no parameters.
behaviour
Methods of a class like String
determine what you can do with an object of type String
. The methods will either be static
methods, in which case you can only call them via the class name: String.
‹method›(
‹args›)
; or instance methods, in which case you must call them via an object: ‹obj›.
‹method›(
‹args›)
.
For example, the charAt(int)
method is an instance method, which means we first need an object of type String
, before we can call it. On the other hand, the format(String, Object…)
method is a static
method, so we only have to call: String.format(
‹args›)
to use it.
Numeric Literals
Numbers are obviously ubiquitous in programs. Java allows us to specify integer literals and floating point literals. To simplify the language, Java does not provide a syntax for unsigned integer types, although a char
is a 16-bit unsigned value supporting all integer operators. There are methods on Long
and Integer
wrapper classes that can help, if you really need an unsigned representation.
IMPORTANT — Avoid Magic Numbers
You should understand that randomly sprinkling your code with anonymous numeric literals, called “magic numbers”, should be avoided — rather create a symbolic constant with that value. Then it has meaning and can easily be changed.
Character Literals
This is always a bone of contention, but there is no escaping the fact that char
is demonstrably a numeric type; to be more precise, a 16-bit unsigned numeric type. We seldom use it as a number, however; we more commonly use it to represent a single 16-bit Unicode character.
A character literal is a token enclosed in single quotes, e.g.: 'A'
, having the value 65
in memory.
char c1 = 'A'; char c2 = 65;
out.format("a.1) c1 = %c, c2 = %c\n", c1, c2);
out.format("a.2) c1 = %d, c2 = %d\n", (int)c1, (int)c2);
c1 = (char)(c1 + 2); c2 = (char)(c2 + 2);
out.format("b.1) c1 = %c, c2 = %c\n", c1, c2);
out.format("b.2) c1 = %s, c2 = %s\n", c1, c2);
out.format("b.3) c1 = %d, c2 = %d\n", (int)c1, (int)c2);
a.1) c1 = A, c2 = A
a.2) c1 = 65, c2 = 65
b.1) c1 = C, c2 = C
b.2) c1 = C, c2 = C
b.3) c1 = 67, c2 = 67
Arithmetic on char
type values always results in int
, which is why we had to cast the int
results back to char
for assignment. Integer literals are implicitly cast to char
, although you could cast it: char c2 = (char)65;
. Notice that format
’s %s
formatting specifier will also print a char
as a string with one character.
Character literals may contain escape sequences, just like string literals. The only required sequences are: \'
to represent a single quote char
, and of course \\
to represent a single backslash character.
Integer Literals
Unadorned integer literals are presumed to be in decimal notation (base 10), and have type int
, a signed 32-bit number, which can range from -2147483648
or -231 to 2147483647
or 231-1. An underscore character can be used to visually separate groups of digits, but cannot be the first character.
An L
suffix can be applied to an integer literal, in which case it will have type long
, which is a signed value ranging from -263 to 263-1.
long i = 123L; long j = 123_456L; long k = 1_2_3_4_5_6L;
long x = 123; long y = 123_456; long y = 1_2_3_4_5_6;
As you can see, integer literals of type int
can be assigned without a cast to variables of type long
. This is called a promotion. The reverse would mean a demotion, and can only be explicitly performed with the cast operator:
If you want to use octal notation (base 8) for your integer literal, simply start it with the digit 0
. This is a brain-dead legacy inherited from C. Notation does not affect the type of the literal. The only valid digits in octal are: 0
-7
.
For hexadecimal notation (base 16), you can prefix 0x
or 0X
, and the valid digits must be in the range 0
-9
, a
-f
(or A
-F
). Again, the notation does not affect the type of literal.
For binary notation (base 2), simply prefix the literal with 0b
or 0B
. The range of digits can only be 0
or 1
.
int i = 0b0001_1011; //← `27` in decimal (type `int`).
long l = 0b0001_1011L; //← `27L` in decimal (type `long`).
To convert an internal number to a string representation using decimal, octal, hexadecimal, and binary notation, you can use the following pattern:
int i = 27;
out.format("%04d\t", i); //←`d` means `int` in decimal.
out.format("%04X\t", i); //←`X` or `x` means `int` in hex.
out.format("%04o\t", i); //←`o` means `int` in octal.
out.format("%s", //←no formatting specifier for
Integer.toBinaryString(i)); // converting to a binary value.
0027 001B 0033 11011
You can experiment, and change the 04
parts to other values. Start by removing the 0
and inspect the output. To print long
values, you use exactly the same format specifiers. Experiment by only changing int
to long
in the snippet above. The rest of the code will still work.
Floating Point Literals
When a decimal literal contains a decimal point, it has type double
(64-bit IEEE 754 float), unless suffixed with an f
or F
, in which case it will have type float
(32-bit IEEE 754 float). This resembles at binary level the formats natively supported by modern CPUs.
Floating point literals can use fixed point notation, or exponential notation (also known as scientific notation).
double d = 123.456; //←fixed point notation.
d = 1.23456e2; //←exponential notation.
float f = 123.456F; //←fixed point notation.
f = 1.23456e2F; //←exponential notation.
Never use float
— it has much lower precision, and round-off errors accumulate much faster, while its only benefit is that it uses less memory, which is rarely a concern.
You can format floating point output with System.out.format
:
double d = 123.456;
out.format("Value is: |%.8f|\n", d);
out.format("Value is: |%.4f|\n", d);
out.format("Value is: |%.2f|\n", d);
out.format("Value is: |%.0f|\n", d);
out.format("Value is: |%12.8f|, |%-12.8f|\n", d, d);
out.format("Value is: |%12.4f|, |%-12.4f|\n", d, d);
out.format("Value is: |%12.2f|, |%-12.2f|\n", d, d);
out.format("Value is: |%12.0f|, |%-12.0f|\n", d, d);
Value is: |123.45600000|
Value is: |123.4560|
Value is: |123.46|
Value is: |123|
Value is: |123.45600000|, |123.45600000|
Value is: | 123.4560|, |123.4560 |
Value is: | 123.46|, |123.46 |
Value is: | 123|, |123 |
With a little experimentation, you can see how the ‘.
‹num›f
’ part influences the output, and similarly the value of 12
determines the total output width, filling with leading spaces if the conversion is too short. Also notice the effect of -12
, in which case the spaces go after the conversion.
Syntax — Format Specifiers
%
[‹arg-index›$
][‹flags›][‹width›][.
‹precision›]‹specifier›- ‹arg-index›
$
offset of argument to format after the format string.- ‹flags›
0
to zero-fill numbers;+
to always include sign;-
to left-align.- ‹width› total output width; fill left with spaces or
0
if output too short..
‹precision› number of decimal digits to format (rounds).- ‹specifier› one of:
d
,c
,b
,s
,f
,e
,x
,h
or%
(to get a%
sign).
The output will not trunctate, so if the formatted item is wider than ‹width›, it will simply overflow.
The decimals part, if present, must start with a decimal point, and is only allowed when a floating point arguments is passed (and the specifier is an f
, of course). At minimum, there must be a percentage signed, followed by one of the specifiers.
If the sequence after the %
starts with a +
, and the format specifier is for a number, the number will always have a sign (+
/-
). Alternatively, in this position, you can use 0
, which only works for the integer format specifiers, and instead of filling the remaining output field width with spaces, will fill it with 0
digits.
out.format("%4d ", 12); //⇒ ` 12`
out.format("%04d ", 12); //⇒ `0012`
out.format("%4X ", 0xABC); //⇒ ` ABC`
out.format("%04X ", 0xABC); //⇒ `0ABC`
The x
specificier can be a capital letter, in which case the alphabetic digits (A
-F
) will be formatted in upper case, which normally is output as lowercase digits (a
-f
).
TIP — Primitive Type Names in String Representation
You must get a bit sneaky to print the name (the wrapper class name, to be exact), of a primitive type variable or expression. You first have to box it, by casting it to an Object
type, then you can use getClass()
and getName()
or getSimpleName()
:
int i = 123;
Object o = (Object)i;
System.out.println(o.getClass().getName());
System.out.println(((Object)i).getClass().getName());
In the last statement, we did not use an intermediate variable, but simply used the result of the cast operator (boxing) directly.
Variables
Although we have seen variables, particularly local variables, in several examples above, we now have enough background to describe variables and options more specifically. Variables store different values at different times, as long as the values are of the correct ‹type›.
Except for primitive types, variables store references to the actual data their type represents. The data is called an object, and is formally “an instance of a class”. We repeat, a variable generally has a name, has a ‹type›, and stores a reference to an object of that ‹type›. Apart from convenience syntax, objects are dynamically created with new
, directly or indirectly (like calling a method that calls new
for you).
Syntax — Local Variable Definitions
Primitive Type Variables
The simplest kind of variable is one that stores a value of a primitive type. In this case, the variable itself stores the actual value, since values of primitive types (which are not classes) are not objects.
Assignment
If a variable has been defined, but not initialised, you cannot use it in an expression. You must first assign a value to it. This is accomplished with the assignment operator. Because it is an operator, and because it associates with its operands from right to left, we can chain assignments.
int i;
int j = 123, k = j;
out.format("j=%d, k=%d\n", j, k); //⇒`j=123, k=123`.
i = j = k = 234; //←`k`←`234`, `j`←`234`, `i`←`234`.
i = (j = (k = 234)); //←same as above; superfluous parentheses.
out.format("i=%d, j=%d\n", i, j); //⇒`i=234, j=234`.
If we tried to use i
before it was assigned a value, the compilation would have failed with a message like:
…error: variable i might not have been initialized
Very polite, but still an error.
Arithmetic
All the numeric primitive types support the normal arithmetic operators:
+
(addition)
-
(subtraction)
*
(multiplication)
/
(division)
They have the expected precedence. When you mix the types of their operands, their results will be the type of the operand with the largest type. In the example below, the value of i
, which is an int
, is first converted to double
before the multiplication is performed, so the result is a double
:
A fair number of static
mathematical functions are available in the system.lang.Math
class. Two mathematical constants (final fields) PI
and E
are also found there.
Be careful with floating point arithmetic, since floating point numbers in a computer are more often than not approximations of a value, which is much coarser for float
than double
. Either way, every operation on floating point values produces round-off errors as a result, which may accumulate. This is one reason why you should never use double
to represent currency — rather use the standard java.util.Currency
class, or the java.math.BigDecimal
class.
Here are some simple examples of the approximate nature of floating point; keep in mind that in real code, with real numbers, this gets worse fast.
out.println(100000.0 + 1.2 - 100000.0);
double num = 0.0;
for (int i = 0; i < 10; ++i)
num = num + 0.1;
out.println(num);
out.println(0.3 + 0.3 + 0.3);
1.1999999999970896 0.9999999999999999 0.8999999999999999
For scientific and engineering applications, the margin of error might be sufficient, if kept under control. All programmers working with serious floating point applications are always encouraged to read “What Every Computer Scientist Should Know About Floating Point”.
Integer Operations
The integer primitive types, i.e.: short
, int
and long
, support a number of additional operations that are exclusive to the integer types.
Modulus
The modulus or remainder operator: %
, is only available for integer types. It produces the remainder of an integer division.
Bitwise Operators
The bitwise operators perform low-level logical operations, where each bit in an integer value is treated as “false” (0
) or “true” (1
). This means each bit in the result is determined from the corresponding bit in the operand(s).
~
(complement - NOT)
|
(bitwise OR)
&
(bitwise AND)
^
(bitwise XOR)
<<
(bitwise left shift)
>>
(bitwise right shift)
BitWise.java
— Bitwise Operator Example
import java.lang.*;
import static java.lang.System.*;
public class BitWise {
public static void main (String[] args) {
int lhs = 0b1111_0000_1010_0011,
rhs = 0b1101_0101_0101_0101;
show(lhs, "&", rhs, lhs & rhs);
show(lhs, "|", rhs, lhs | rhs);
show(lhs, "^", rhs, lhs ^ rhs);
exit(0);
}
public static void show(int lhs, String op, int rhs, int res) {
final String fmt = " %s\n%s %s\n= %s\n\n";
out.format(fmt, bin(lhs, 16), op, bin(rhs, 16), bin(res, 16));
}
public static String bin(int val, int fix) {
final String zero = "00000000000000000000000000000000";
String result = zero + Integer.toBinaryString(val);
return result.substring(result.length() - fix);
}
}//class
1111000010100011 1111000010100011 1111000010100011
& 1101010101010101 | 1101010101010101 ^ 1101010101010101
= 1101000000000001 = 1111010111110111 = 0010010111110110
With the bitwise operators, you can set, clear, or check any bit, or pattern of bits, in integer values.
Increment/Decrement
For integer types only, we also have the pre/post-increment and pre/post-decrement operators. It is important to understand that the pre/postfix versions both perform the same task; they only differ in the result they return.
- The prefix increment/decrement operators return the modified value.
- The postfix increment/decrement operators return the original value.
If you have no need for the return value, it does not matter in any way which one you use, but people tend to have a preference.
Comparisons
We can perform comparison operations on all these types, all of which produce boolean
results:
==
(equal to)
!=
(not equal to)
<
(less than)
>
(greater than)
<=
(less than or equal)
>=
(greater than or equal)
These operators are all on the same level of precedence, but at a lower level than the logical operators below.
Keep in mind that you should never ever compare floating point values for equality. Rather take the absolute value of their difference, and see if the value is small enough for your application.
final double delta = 0.00001;
double a = 12.000012, b = 12.000011976;
if (Math.abs(a - b) < delta)
out.println("Numbers are equal (enough)");
else
out.println("Numbers are not equal (enough)");
Logical Operators
On boolean
expressions, we have the logical operators, where !
has the highest precedence, followed by &&
and then ||
.
!
(logical NOT)
||
(logical OR)
&&
(logical AND)
The ||
and &&
operators perform short-circuit evaluation. This means they do not evaluate their right-hand operand, if the answer can be obtained by evaluation of the left-hand operand. For example, in the expression: ‘false && X
’, X
is never evaluated, because “false AND ‹anything›” will always be “false”.
out.println("LOGICAL AND (&&)");
out.println("false && false: " + (false && false));
out.println("false && true : " + (false && true ));
out.println("true && false: " + (true && false));
out.println("true && true : " + (true && true ));
out.println("LOGICAL OR (||)");
out.println("false || false: " + (false || false));
out.println("false || true : " + (false || true ));
out.println("true || false: " + (true || false));
out.println("true || true : " + (true || true ));
LOGICAL AND (&&) │ LOGICAL OR (||)
false && false: false │ false || false: false
false && true : false │ false || true : true
true && false: false │ true || false: true
true && true : true │ true || true : true
Exercise: See if you can use the following function to print the above tables in colour.
static String TF(boolean check) {
return check
? "\033[32;1m" + check + "\033[0m"
: "\033[31;1m" + check + "\033[0m";
}
Conditional Operator
The comparison and logical operators can be combined with the conditional operator, which is an operator that takes three operands. It will only evaluate two, of which the first operand is one. The first operand must be a boolean
expression, and depending on its value, either the second, or the third operand will be evaluated, and whichever one is evaluated, becomes the result of the operator.
Syntax — Conditional Operator
- ‹bool-expr›
?
‹exprT›:
‹exprF›- ‹bool-expr› any expression resulting in a
boolean
value.- ‹exprT› evaluated only if ‹bool-expr› returned
true
.- ‹exprF› evaluated only if ‹bool-expr› returned
false
.
Since all the operands can be complex expressions, and can even contain other operators, the conditional operator should be used with care. Here is an example of how not to use it:
for (int yr = 1999; yr <= 2012; ++yr)
out.format("Year %d was %sa leapyear\n", yr,
((yr % 4 == 0) && (yr % 100 != 0) || (yr % 400 == 0)
? true : false) ? "" : "not ");
The above code snippet will determine which of the years between 1999
and 2012
inclusive, are leap years or not. It is, at least, an exercise in using a fair number of operators! Just to set the record straight, the following is a better solution. Even though it uses some statements you have not yet learned about, it should still be more readable:
leapyear.java
— Determine Leap Years
import java.lang.*;
import static java.lang.System.*;
public class leapyear {
public static void main (String[] args) {
for (int year = 1999; year <= 2012; ++year) {
if (isLeap(year))
out.format("Year %d was a leapyear\n", year);
else
out.format("Year %d was not a leapyear\n", year);
}
exit(0);
}
static boolean isLeap (int year) {
boolean leap = false;
if ((year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0))
leap = true;
return leap;
}
}//class
You can check the leap year algorithm at Wikipedia, and learn more than you wanted to know about calendars and leap years in the process. Or you can just use the isLeapYear
static
method from java.util.GregorianCalendar
, and save yourself some pain.
Compound Assignment
In programming, we often find the pattern: ‘‹variable› =
‹variable› ‹op› ‹expr›;
’, where ‹op› is a binary operator (taking two operands). For example:
result = result + 2; answer = answer * Math.PI;
repeat = repeat | def; volume = volume / (3.2 * height);
This pattern can be simplified to: ‘‹variable› ‹op›=
‹expr›;
’:
Clearly, this is much more succinct, and furthermore, avoids naming redundancy, which is good programming practice in general. Consequently, you are encouraged to use these compound assignment operators.
Reference Type Variables
The syntax for defining variables that are not primitive types, is exactly the same as for primitive variables. The difference lies in the values used to initialise or assign to reference type variables; in other words, the nature of the ‹expr›essions in the syntax.
Apart from some shorthand, and implicit conversions, you have to call new
to:
a) dynamically allocate memory, and
b) initialise the object by choosing an appropriate constructor.
Double d = new Double(12.3), //←traditional, very OOPy.
e = 12.3; //←effective shorthand.
String s = new String(d.toString()), //←traditional.
t = d.toString(), //←`toString()` calls `new`.
u = new String("ABC"), //←traditional.
v = "DEF"; //←shorthand.
Object o = d, //←`Object` variables can store
p = s, // a reference to anything, even
q = 23.45; // primitive types (boxing).
The type of the value stored in q
is not a double
, even though 23.45
does have type double
. Java first converts the double
to Double
, and then assigns the reference to q
.
For fundamental types, this does not seem to be a problem: “it just works”. And because String
values are immutable, we cannot even show the one potential problem with reference types (and the reason why String
values are immutable). So we will illustrate with another type: the very useful and mutable java.lang.StringBuilder
class. This class is very efficient when you either concatenate long strings, or concatenate multiple times.
java.lang.StringBuilder
sb = new java.lang.StringBuilder("ABC"),
sc = sb;
sc.append("DEF"); sc.append("GHI");
out.println("sc = " + sc); out.println("sb = " + sb);
out.println(sc == sb ? "sc == sb" : "sc != sb");
sc = ABCDEFGHI
sb = ABCDEFGHI
sc == sb
When you run this code, you will see that both sc.toString()
, and sb.toString()
, return the same value (the toString()
is automatically called when we concatenate these variables to a String
). The point is that both sb
and sc
reference the same object in memory. The initialisation: ‘sc = sb
’ only copied the reference to the object, from sb
to sc
. If you want a copy, you have to allocate more memory (continuing from above code):
sb = new StringBuilder(sc);
sc.append("JKL"); sc.append("MNO");
out.println("sc = " + sc); out.println("sb = " + sb);
out.println(sc == sb ? "sc == sb" : "sc != sb");
sc = ABCDEFGHIJKLMNO
sb = ABCDEFGHI
sc != sb
Now, you will see that only the object that sc
references has changed. The new object stored in sb
, created from the original sc
, remains untouched. This feature of mutable reference types is not a problem, and in fact is quite efficient, but it is something you must be aware of. If a type is immutable, you are still copying only references, but it is never a problem, because if you want to modify the referenced immutable object, you are forced to create a new object (with potential modifications).
String s = "ABC", //←stores reference to `"ABC"`.
t = s; //←copies reference to `t`.
t = t.toLowerCase(); //←only way to modify a `String`,
// is to create a new one.
out.format("s=%s, t=%s", s, t); //←output different values.
Not a single one of the instance methods of the String
class can modify the object they are called on, even if the method name seems to suggest that it does. All methods that return a String
, return a copy. That means that immutable types are safer than mutable types, but sometimes a bit of a pain.
Shared Class Variables
Since local variables are only visible inside the method body block, or compound statement block, in which they have been defined, and since they only live as long as the function has not returned, they are somewhat limited if we require a longer lifetime, or a larger scope.
To solve that problem, Java allows you to define, at class level, two kinds of fields:
- instance variables have the same definition syntax as local variables, and can also be initialised;
static
or “shared” variables have a similar syntax, except they havestatic
in front.
We will revisit instance fields later, when we cover encapsulation. For now, we will discuss static
fields. Any member is visible right through the class, regardless of where in the class you have defined the member. You can thus organise the order of the members as you see fit, but you should group fields together.
Fields that are static
have a global lifetime, which means they exist as long as your program is running. They are automatically initialised before your code in main
starts running. You can use a special static
initialiser construct for non-trivial initialisation of static
fields. It, too, is called before main
.
SharedFields.java
— Example of Shared Fields
import java.lang.*;
import static java.lang.System.*;
public class SharedFields {
final static double PI = java.lang.Math.PI;
static String Message = "Hello World".toUpperCase();
static int SomeValue;
public static void main (String[] args) {
out.println("In main() now");
out.print(Message + ", ");
out.println(SomeValue);
exit(0);
}
static {
out.println("In static initialiser");
SomeValue = 1234;
}
}//class
In static initialiser
In main() now
HELLO WORLD, 1234
If you were to move the static
fields to the bottom of the class, it would make no difference. And if we had more functions in the class, they all would have been able to access and modify the same static
fields (except for the final
one, of course).
Final Variables
We have been using final
“variables” in a number of examples now, so this is really just a summary. We use the term “variables”, because that is what they are called, but they do not live up to the term, since their values are fixed after initialisation, and can thus not “vary” (change).
Only class-level final
variables can also be static
. Inside a method, you can use final
, but never static
.
The initialisation of final
, and static final
variables, can be run-time expressions. This means you can even call functions. However, a static
initialiser method cannot be used to initialise final
variables, even if they are static
.
Private Static Variables
Ex-C, or ex-C++, ex-C#, or ex-several-other-languages programmers may be surprised that you cannot create variables in a local scope, with a global lifetime; meaning a variable that can only be used by one function, just like a local variable, but remains in memory when the function returns, unlike local variables. You have to use a pattern for that.
The only way to get what you have been used to (a locally scoped variable with a global lifetime), is to wrap it in a class, and mark it private
:
PrivGlobal.java
— Private Global Variables Pattern
import static java.lang.System.*;
public class PrivGlobal {
public static void main (String[] args) {
int i = Counter.Next(),
j = Counter.Next(),
k = Counter.Next();
out.format("i=%d, j=%d, k=%d\n", i, j, k);
exit(0);
}
}//class
class Counter {
private static int myvar = 0;
static int Next() { return ++myvar; }
}//class
i=1, j=2, k=3
It seems a bit of overkill, but we have achieved what we wanted: only the Next()
function can access and modify the myvar
variable, and it lives for as long as the program runs (global lifetime). According to your Java overlords, this is a better abstraction.
Just remember, if you want to make the Counter
class public
, you have to move it to a separate source file. You may then probably also want to make the Next
method public
.
Practical Code
Once we have some understanding of literals, variables, and “finals”, we can write some relatively useful programs. We will start with a basic problem statement:
Problem Statement: Write a program that calculates the circumference and area of a circle with a radius of 12.34
units.
Circle formulæ: \(A = \pi r^{2}\); \(C = 2\pi r\); \(r = \sqrt{{{A}\div{\pi}}}\); \(r = C\div 2\pi\).
Where \(r\) is radius, \(A\) is area, and \(C\) is circumference.
Using Literals, Variables and Finals
In the example below, we use increasingly more Java features to provide several solutions to the problem statement. Only the first four, marked: a)
…d)
, are important at this stage. The others are to show you that there is more to learn, and to pique your curiosity.
circ00.java
— Circle Calculator Version 00
/* Circle Calculator 00.
*
* Pedagogical example program that shows how increasing knowledge makes
* programs increasingly “better”, where “better” means more readable,
* more maintainable, and more reusable.
*/
import java.lang.*; //←default, not really needed.
import static java.lang.System.*; //←use `out` & `exit` directly.
public class circ00 {
/* Circle Calculator's entry point.
*
* The code in `main` shows 5 (6th is a bonus) ways to produce an answer
* for the problem: “calculate the circumference and area of a circle given
* a `radius` of `12.34`”. Compound statement blocks are used to separate
* the steps, and limit the scope of variables.
*
* If you have not learned about functions yet, you can skip the fifth
* set of code (e), and obviously also then the sixth (f).
*/
public static void main (String[] args) {
out.println("CIRCLE CALCULATOR 00");
/* a) Absolutely minimal Java knowledge required: only literals,
* arithmetic, and `System.out.println`, and that everything is
* convertible `toString()`, even when concatenating to a string.
*/ {
out.println("a) Radius :" + 12.34);
out.println(" Circum. :" + (2.0 * 3.14159265359 * 12.34));
out.println(" Area :" + (3.14159265359 * 12.34 * 12.34));
}
/* b) Knowledge about `format` allows formatting for neater output.
* We still have a lot of magic numbers though, and some are even
* repeated, neither of which is a good sign in programs.
*/ {
out.format("b) Radius : %10.4f\n", 12.34);
out.format(" Circum. : %10.4f\n", 2.0 * 3.14159265359 * 12.34);
out.format(" Area : %10.4f\n", 3.14159265359 * 12.34 * 12.34);
}
/* c) Add `final` (symbolic constant, sort of), and it gets better,
* except we cannot make it `static` inside a method, so we have to
* create the `static` version at class level (see below), although
* we do not use it in this block (later we do).
*/ {
final double pi = 3.14159265359;
out.format("c) Radius : %10.4f\n", 12.34);
out.format(" Circum. : %10.4f\n", 2.0 * pi * 12.34);
out.format(" Area : %10.4f\n", pi * 12.34 * 12.34);
}
/* d) Add variables and we have more structured and cleaner code. The
* variables will only be visible in this compound statement block.
* We do not often define multiple variables at the same time, but it
* is possible, as you see below.
*/ {
double //←local variable definitions.
radius = 12.34, //←they are also initialised in
circum = 2.0 * PI * radius, // sequence of definition, so
area = PI * radius * radius; // this is well-defined code.
out.format("d) Radius : %10.4f\n", radius);
out.format(" Circum. : %10.4f\n", circum);
out.format(" Area : %10.4f\n", area );
}
/* e) Add static functions, since the formulae are reusable. See the
* function definitions below this function. We can pass the `radius`
* to the functions, so it can be used for any radii.
*/ {
double radius = 12.34;
out.format("e) Radius : %10.4f\n", radius );
out.format(" Circum. : %10.4f\n", circum(radius) );
out.format(" Area : %10.4f\n", area(radius) );
}
/* f) Bonus. You are really not required to understand lambdas at
* this point, but we employ the syntax here to abstractly create
* “local functions”. It is a little bit clumsy in Java, because we
* first have to create an `interface` with one method (see below).
*/ {
double radius = 12.34;
Circle circum = r -> 2.0 * PI * r;
Circle area = r -> PI * r* r;
out.format("f) Radius : %10.4f\n", radius );
out.format(" Circum. : %10.4f\n", circum(radius) );
out.format(" Area : %10.4f\n", area(radius) );
}
exit(0);
}
// c) Add `final` (symbolic constant), but get from `java.lang.Math`:
//
static final double PI = java.lang.Math.PI;
// e) Static functions to calculate area and circumference of a circle.
// The parameter `radius` is initialised by the caller passing an argu-
// ment of the right type (`double`). In all other respects, the para-
// meter acts like a local variable to each of these functions, and has
// no scope or syntactical relationship with other `radius` variables.
//
static double circum (double radius) { //←simplest and best solution.
return 2.0 * PI * radius; //←return result of expression.
}
static double area (double radius) { //←more traditional pattern.
double result; //←variable to store result.
result = PI * radius * radius; //←operations/calculations.
return result; //←return result.
}
// f) To use lambdas, you need an `interface` with *one* method. The
// name of the method is useful only from a readability perspective.
// The name of the parameter is required, but again is mostly useful
// as self-documenting code. The return type, and types of any number
// of parameters, are important, since Java uses that to infer the
// types in the lambda expression where used.
//
interface Circle { double formula (double radius); }
}//class
Classic Modularisation
We can improve on the above code slightly, by reorganising our code. This does not really require much new in the line of syntax, simply the observation that if you can write a static
member in some class (like above), you can write those same members in another class, and thus use more than one class in the same program — the classes do not even have to be in separate files, as long as only one class is marked with the public
access specifier.
circ01.java
— Circle Calculator 01
/* Circle Calculator 01.
*
* Pedagogical example program that shows how `static` members, like
* “finals” and functions, can be in a separate class, resembling some
* modularity. To avoid having multiple files, the `Circle` class is
* “package-private”. This is the “best” solution for the problem state-
* ment that requires no OO features, except that we *organise* members
* in classes.
*
* The `Circle` class could have been defined with `public` access, and
* placed in `Circle.java`, and we would not have to change any of the
* code in `main`. This would provide a simple mechanism to group related
* code together in a class, as long as all members to be used from
* outside the class, are `public` and `static`. This does not involve
* OOP knowledge, and is simply one way to organise code.
*/
import java.lang.*; //←default, not really needed.
import static java.lang.System.*; //←use `out` & `exit` directly.
public class circ01 {
/* Circle Calculator's entry point.
*
* The code in `main` simply uses `static` members from the `Circle` class
* defined below, in the same file.
*/
public static void main (String[] args) {
out.println("CIRCLE CALCULATOR 01");
/* a) Call static functions that reside in another class. We could also
* have accessed `Circle.PI`, but there is no need for it here.
*/ {
double radius = 12.34;
out.format("a) Radius : %10.4f\n", radius );
out.format(" Circum. : %10.4f\n", Circle.circum(radius));
out.format(" Area : %10.4f\n", Circle.area(radius) );
}
/* b) Exactly the same code as a) above, except that some colour was
* added, using VT100/ANSI escape sequences. If you cannot deal with
* that, remove and ignore this code — only a problem on Windows.
*/ {
double radius = 12.34;
final String GRN = "\033[32;1m";
final String DFT = "\033[0m";
final String FMT = " : " + GRN + "%10.4f" + DFT + "\n";
out.format("b) Radius " + FMT, radius );
out.format(" Circum." + FMT, Circle.circum(radius));
out.format(" Area " + FMT, Circle.area(radius) );
}
exit(0);
}
}//class
class Circle { //←“package-private” class, accessible in package only;
// this is the default access for the members here too.
static final double PI = java.lang.Math.PI;
static double circum (double radius) { //←simplest and best solution.
return 2.0 * PI * radius; //←return result of expression.
}
static double area (double radius) { //←more traditional pattern.
double result; //←variable to store result.
result = PI * radius * radius; //←operations/calculations.
return result; //←return result.
}
}//class
We can improve on the above example solution in one way only (unless we want to use OOP to model a Circle
class using encapsulation). This improvement would involve giving class Circle
public
access, which in turn means it must be placed in a separate Circle.java
file. In line with that, we also should put public
in front of all the members, to make it even more widely usable.
The Java compiler will automatically look for classes referenced in all the .java
files in the directory of the class being compiled, which means we do not have to modify any of the code in the main class (circ01
), or even change the compiler invocation command.
Some Standard Input
Although the java.lang.System
class provides an in
(for standard input) field, and we have been using the out
(standard output) field incessantly, in
does not provide useful buffered (keyboard) input. You have to wrap in
with one of several candidate classes; the two most likely are:
java.io.InputStreamReader cin = new java.io.InputStreamReader(System.in);
java.io.BufferedReader bin = new java.io.BufferedReader(cin);
Alternatively, you can use java.util.Scanner
:
java.util.Scanner scan = new java.util.Scanner(System.in);
For us, the problem is that to use these classes, we must teach you about exception handling, but this is not a good time for it; though soon… very soon. At this point, we rather show you a more immediately useful class: the java.io.Console
class.
SimpleConsole.java
— Simple Console Input Example
import static java.lang.System.*;
public class SimpleConsole {
public static void main (String[] args) {
java.io.Console con = System.console();
if (con == null) {
err.println("No console!");
exit(1);
}
String inp;
Double dbl;
out.print("1) Enter something: ");
inp = con.readLine();
out.println("1) You entered: |" + inp + "|");
out.print("2) Enter a double : ");
dbl = Double.parseDouble(con.readLine()); // or `Double.valueOf(…)`
out.format("2) Your number is : %.4f\n", dbl);
exit(0);
}
}//class
Apart from the fact the we have to create a java.io.Console
type variable, and initialise it, and check if it was successful, it is very easy to use. If you are going to use it a lot, you may want to make it a static
field, and initialise it in a static
initialiser method:
StaticConsole.java
— Console as Static Member
import java.lang.*;
import static java.lang.System.*;
public class StaticConsole {
public static void main (String[] args) {
String inp;
Double dbl;
out.print("1) Enter something: ");
inp = con.readLine();
out.println("1) You entered: |" + inp + "|");
out.print("2) Enter a double : ");
dbl = Double.parseDouble(con.readLine()); // or `Double.valueOf(…)`
out.format("2) Your number is : %.4f\n", dbl);
exit(0);
}
public static java.io.Console con = console();
static {
if (con == null) {
err.println("No console. Bailing");
exit(1);
}
}
}//class
There you have it: the ability to read strings from standard input. And from the example, you can deduce how to convert an input String
to an int
, for example. If you want, you can omit the static
initialiser method — your program will just fail at a different point if System.console()
returned null
(could not allocate console).
2017-12-24: Created. [brx]