Java primitive types and operators
With all the main programming tools in place, we can start talking about Java as a language. The language syntax is defined by the Java Language Specification, which you can find at https://docs.oracle.com/javase/specs. Don’t hesitate to refer to it every time you need some clarification—it is not as daunting as many people assume.
All the values in Java are divided into two categories: reference types and primitive types. We start with primitive types and operators as the natural entry point to any programming language. In this chapter, we will also discuss one reference type called String
(see the String types and literals section).
All primitive types can be divided into two groups: Boolean types and numeric types.
Boolean types
There are only two Boolean type values in Java: true
and false
. Such a value can only be assigned to a variable of a boolean
type, as in the following example:
boolean b = true;
A boolean
variable is typically used in control flow statements, which we are going to discuss in the Java statements section. Here is one example:
boolean b = x > 2; if(b){ //do something }
In the preceding code, we assign to the b
variable the result of the evaluation of the x > 2
expression. If the value of x
is greater than 2
, the b
variable gets the assigned value, true
. Then, the code inside the braces ({}
) is executed.
Numeric types
Java numeric types form two groups: integral types (byte
, char
, short
, int
, and long
) and floating-point types (float
and double
).
Integral types
Integral types consume the following amount of memory:
byte
: 8 bitschar
: 16 bitsshort
: 16 bitsint
: 32 bitslong
: 64 bits
The char
type is an unsigned integer that can hold a value (called a code point) from 0 to 65,535 inclusive. It represents a Unicode character, which means there are 65,536 Unicode characters. Here are three records from the basic Latin list of Unicode characters:
The following code demonstrates the properties of the char
type (execute the main()
method of the com.packt.learnjava.ch01_start.PrimitiveTypes
class—see the charType()
method):
char x1 = '\u0032'; System.out.println(x1); //prints: 2 char x2 = '2'; System.out.println(x2); //prints: 2 x2 = 65; System.out.println(x2); //prints: A char y1 = '\u0041'; System.out.println(y1); //prints: A char y2 = 'A'; System.out.println(y2); //prints: A y2 = 50; System.out.println(y2); //prints: 2 System.out.println(x1 + x2); //prints: 115 System.out.println(x1 + y1); //prints: 115
The last two lines from the preceding code example explain why the char
type is considered an integral type because char
values can be used in arithmetic operations. In such a case, each char
value is represented by its code point.
The range of values of other integral types is shown here:
byte
: from -128 to 127 inclusiveshort
: from -32,768 to 32,767 inclusiveint
: from -2.147.483.648 to 2.147.483.647 inclusivelong
: from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 inclusive
You can always retrieve the maximum and minimum value of each primitive type from a corresponding Java constant, as follows (execute the main()
method of the com.packt.learnjava.ch01_start.PrimitiveTypes
class—see the minMax()
method):
System.out.println(Byte.MIN_VALUE); //prints: -128 System.out.println(Byte.MAX_VALUE); //prints: 127 System.out.println(Short.MIN_VALUE); //prints: -32768 System.out.println(Short.MAX_VALUE); //prints: 32767 System.out.println(Integer.MIN_VALUE); //prints: -2147483648 System.out.println(Integer.MAX_VALUE); //prints: 2147483647 System.out.println(Long.MIN_VALUE); //prints: -9223372036854775808 System.out.println(Long.MAX_VALUE); //prints: 9223372036854775807 System.out.println((int)Character.MIN_VALUE); //prints: 0 System.out.println((int)Character.MAX_VALUE); //prints: 65535
The construct (int
) in the last two lines is an example of cast operator usage. It forces the conversion of a value from one type to another in cases where such a conversion is not always guaranteed to be successful. As you can see from our examples, some types allow bigger values than other types. But a programmer may know that the value of a certain variable can never exceed the maximum value of the target type, and the cast operator is the way the programmer can force their opinion on the compiler. Otherwise, without a cast operator, the compiler would raise an error and would not allow the assignment. However, the programmer may be mistaken and the value may become bigger. In such a case, a runtime error will be raised during execution time.
There are types that, in principle, cannot be cast to other types, though, or at least not to all types—for example, a Boolean type value cannot be cast to an integral type value.
Floating-point types
There are two types in this group of primitive types—float
and double
. These consume the following amount of memory:
float
: 32 bitdouble
: 64 bit
Their positive maximum and minimum possible values are shown here (execute the main()
method of the com.packt.learnjava.ch01_start.PrimitiveTypes
class—see the minMax()
method):
System.out.println(Float.MIN_VALUE); //prints: 1.4E-45 System.out.println(Float.MAX_VALUE); //prints: 3.4028235E38 System.out.println(Double.MIN_VALUE); //prints: 4.9E-324 System.out.println(Double.MAX_VALUE); //prints: 1.7976931348623157E308
The maximum and minimum negative values are the same as those just shown, only with a minus sign (-
) in front of them. So, effectively, the Float.MIN_VALUE
and Double.MIN_VALUE
values are not the minimal values, but the precision of the corresponding type. A zero value can be either 0.0 or -0.0 for each of the floating-point types.
A special feature of the floating-point type is the presence of a dot (.
) that separates integer and fractional parts of the number. By default, in Java, a number with a dot is assumed to be a double
type. For example, the following is assumed to be a double
value:
42.3
This means that the following assignment causes a compilation error:
float f = 42.3;
To indicate that you would like it to be treated as a float
type, you need to add either f
or F
. For example, the following assignments do not cause an error (execute the main()
method of the com.packt.learnjava.ch01_start.PrimitiveTypes
class—see the casting()
method):
float f = 42.3f; float d = 42.3F; double a = 42.3f; double b = 42.3F; float x = (float)42.3d; float y = (float)42.3D;
As you may have noticed from the preceding example, d
and D
indicate a double
type, but we were able to cast them to the float
type because we are confident that 42.3
is well inside the range of possible float
-type values.
Default values of primitive types
In some cases, a variable has to be assigned a value even when a programmer did not want to do that. We will talk about such cases in Chapter 2, Java Object-Oriented Programming (OOP). The default primitive type value in such cases is outlined here:
byte
,short
,int
, andlong
types have a default value of 0.- The
char
type has a default value of\u0000
, with the code point 0. float
anddouble
types have a default value of 0.0.- The
boolean
type has a default value offalse
.
Literals of primitive types
The representation of a value is called a literal. The boolean
type has two literals: true
and false
. Literals of byte
, short
, int
, and long
integral types have an int
type by default, as illustrated here:
byte b = 42; short s = 42; int i = 42; long l = 42;
In addition, to indicate a literal of a long
type, you can append the letter l
or L
to the end, like this:
long l1 = 42l; long l2 = 42L;
The letter l
can be easily confused with the number 1
, so using L
(instead of l
) for this purpose is a good practice.
So far, we have expressed integral literals in a decimal number system. Meanwhile, literals of byte
, short
, int
, and long
types can also be expressed in binary (base 2, digits 0-1), octal (base 8, digits 0-7), and hexadecimal (base 16, digits 0-9, and a-f) number systems. A binary literal starts with 0b (or 0B), followed by the value expressed in a binary system. For example, the decimal 42 is expressed as 101010 = 2^0*0 + 2^1*1 + 2^2*0 + 2^3 *1 + 2^4 *0 + 2^5 *1 (we start from the right 0). An octal literal starts with 0, followed by the value expressed in an octal system, so 42 is expressed as 52 = 8^0*2+ 8^1*5. A hexadecimal literal starts with 0x (or with 0X), followed by a value expressed in a hexadecimal system. So, 42 is expressed as 2a = 16^0*a + 16^1*2 because, in the hexadecimal system, the symbols a
to f
(or A
to F
) map to the decimal values 10 to 15. Here is the demonstration code (execute the main()
method of the com.packt.learnjava.ch01_start.PrimitiveTypes
class—see the literals()
method):
int i = 42; System.out.println(Integer.toString(i, 2)); // 101010 System.out.println(Integer.toBinaryString(i)); // 101010 System.out.println(0b101010); // 42 System.out.println(Integer.toString(i, 8)); // 52 System.out.println(Integer.toOctalString(i)); // 52 System.out.println(052); // 42 System.out.println(Integer.toString(i, 10)); // 42 System.out.println(Integer.toString(i)); // 42 System.out.println(42); // 42 System.out.println(Integer.toString(i, 16)); // 2a System.out.println(Integer.toHexString(i)); // 2a System.out.println(0x2a); // 42
As you can see, Java provides methods that convert decimal system values to systems with different bases. All these expressions of numeric values are called literals.
One feature of numeric literals makes them human-friendly. If the number is large, it is possible to break it into triples separated by an underscore (_
) sign. Observe the following, for example:
int i = 354_263_654; System.out.println(i); //prints: 354263654 float f = 54_436.98f; System.out.println(f); //prints: 54436.98 long l = 55_763_948L; System.out.println(l); //prints: 55763948
The compiler ignores an embedded underscore sign.
The char
type has two kinds of literals: a single character or an escape sequence. We have seen examples of char
-type literals when discussing numeric types, and you can see some others here:
char x1 = '\u0032'; char x2 = '2'; char y1 = '\u0041'; char y2 = 'A';
As you can see, the character has to be enclosed in single quotes.
An escape sequence starts with a backslash (\
) followed by a letter or another character. Here is a full list of escape sequences:
\
b: backspace BS, Unicode escape\u0008
- \t: horizontal tab HT, Unicode escape
\u0009
- \n: line feed LF, Unicode escape
\u000a
- \f: form feed FF, Unicode escape
\u000c
- \r: carriage return CR, Unicode escape
\u000d
- \”: double quote “, Unicode escape
\u0022
- \’: single quote ‘, Unicode escape
\u0027
- \\: backslash \, Unicode escape
\u005c
From the eight escape sequences, only the last three are represented by a symbol. They are used when this symbol cannot be otherwise displayed. Observe the following, for example:
System.out.println("\""); //prints: " System.out.println('\''); //prints: ' System.out.println('\\'); //prints: \
The rest are used more as control codes that direct the output device to do something, as in the following example:
System.out.println("The back\bspace"); //prints: The backspace System.out.println("The horizontal\ttab"); //prints: The horizontal tab System.out.println("The line\nfeed"); //prints: The line feed System.out.println("The form\ffeed"); //prints: The form feed System.out.println("The carriage\rreturn");//prints: return
As you can see, \b
deletes a previous symbol, \t
inserts a tab space, \n
breaks the line and begins the new one, \f
forces the printer to eject the current page and to continue printing at the top of another, and \r
starts the current line anew.
New compact number format
The java.text.NumberFormat
class presents numbers in various formats. It also allows formats to be adjusted to those provided, including locales. A new feature added to this class in Java 12 is called a compact or short number format.
It represents a number in a locale-specific, human-readable form. Observe the following, for example (execute the main()
method of the com.packt.learnjava.ch01_start.PrimitiveTypes
class—see the newNumberFormat()
method):
NumberFormat fmt = NumberFormat.getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT); System.out.println(fmt.format(42_000)); //prints: 42K System.out.println(fmt.format(42_000_000)); //prints: 42M NumberFormat fmtP = NumberFormat.getPercentInstance(); System.out.println(fmtP.format(0.42)); //prints: 42%
As you can see, to access this capability, you have to acquire a particular instance of the NumberFormat
class, sometimes based on the locale and style provided.
Operators
There are 44 operators in Java. These are listed in the following table:
We will not describe the not-often-used &=
, |=
, ^=
, <<=
, >>=
, >>>=
assignment operators and bitwise operators, but you can read about them in the Java specification (https://docs.oracle.com/javase/specs). Arrow ( ->
) and method reference (::
) operators will be described in Chapter 14, Java Standard Streams. The new
instance creation operator, the .
field access/method invocation operator, and the instanceof
type comparison operator will be discussed in Chapter 2, Java Object-Oriented Programming (OOP). As for the cast operator, we have already described it in the Integral types section.
Arithmetic unary (+ and -) and binary (+, -, *, /, and %) operators
Most of the arithmetic operators and positive and negative signs (unary operators) are quite familiar to us. The modulus operator (%
) divides the left-hand operand by the right-hand operand and returns the remainder, as follows (execute the main()
method of the com.packt.learnjava.ch01_start.Operators
class—see the integerDivision()
method:
int x = 5; System.out.println(x % 2); //prints: 1
It is also worth mentioning that the division of two integer numbers in Java loses the fractional part because Java assumes the result should be an integer number 2
, as follows:
int x = 5; System.out.println(x / 2); //prints: 2
If you need the fractional part of the result to be preserved, convert one of the operands into a floating-point type. Here are a few ways (among many) in which to do this:
int x = 5; System.out.println(x / 2.); //prints: 2.5 System.out.println((1. * x) / 2); //prints: 2.5 System.out.println(((float)x) / 2); //prints: 2.5 System.out.println(((double) x) / 2); //prints: 2.5
Increment and decrement unary operators (++ and --)
The ++
operator increases the value of an integral type by 1, while the --
operator decreases it by 1. If placed before the variable (prefix), it changes its value by 1 before the variable value is returned. But when placed after the variable (postfix), it changes its value by 1 after the variable value is returned. Here are a few examples (execute the main()
method of the com.packt.learnjava.ch01_start.Operators
class—see the incrementDecrement()
method):
int i = 2; System.out.println(++i); //prints: 3 System.out.println(i); //prints: 3 System.out.println(--i); //prints: 2 System.out.println(i); //prints: 2 System.out.println(i++); //prints: 2 System.out.println(i); //prints: 3 System.out.println(i--); //prints: 3 System.out.println(i); //prints: 2
Equality operators (== and !=)
The ==
operator means equals, while the !=
operator means not equals. They are used to compare values of the same type and return a true
Boolean value if the operand’s values are equal
, or false
otherwise. Observe the following, for example (execute the main()
method of the com.packt.learnjava.ch01_start.Operators
, class—see the equality()
method):
int i1 = 1; int i2 = 2; System.out.println(i1 == i2); //prints: false System.out.println(i1 != i2); //prints: true System.out.println(i1 == (i2 - 1)); //prints: true System.out.println(i1 != (i2 - 1)); //prints: false
Exercise caution, though, while comparing values of floating-point types, especially when you compare the results of calculations. Using relational operators (<
, >
, <=
, and >=
) in such cases is much more reliable, because calculations such as 1/3—for example—result in a never-ending fractional part 0.33333333... and ultimately depend on precision implementation (a complex topic that is beyond the scope of this book).
Relational operators (<, >, <=, and >=)
Relational operators compare values and return a Boolean value. Observe the following, for example (execute the main()
method of the com.packt.learnjava.ch01_start.Operators
class—see the relational()
method):
int i1 = 1; int i2 = 2; System.out.println(i1 > i2); //prints: false System.out.println(i1 >= i2); //prints: false System.out.println(i1 >= (i2 - 1)); //prints: true System.out.println(i1 < i2); //prints: true System.out.println(i1 <= i2); //prints: true System.out.println(i1 <= (i2 - 1)); //prints: true float f = 1.2f; System.out.println(i1 < f); //prints: true
Logical operators (!, &, and |)
Logical operators can be defined as follows:
- The
!
binary operator returnstrue
if the operand isfalse
; otherwise, it returnsfalse
. - The
&
binary operator returnstrue
if both of the operands aretrue
. - The
|
binary operator returnstrue
if at least one of the operands istrue
.
Here is an example (execute the main()
method of the com.packt.learnjava.ch01_start.Operators
class—see the logical()
method):
boolean b = true; System.out.println(!b); //prints: false System.out.println(!!b); //prints: true boolean c = true; System.out.println(c & b); //prints: true System.out.println(c | b); //prints: true boolean d = false; System.out.println(c & d); //prints: false System.out.println(c | d); //prints: true
Conditional operators (&&, ||, and ? :)
The &&
and ||
operators produce the same results as the &
and |
logical operators we have just demonstrated, as follows (execute the main()
method of the com.packt.learnjava.ch01_start.Operators
class—see the conditional()
method):
boolean b = true; boolean c = true; System.out.println(c && b); //prints: true System.out.println(c || b); //prints: true boolean d = false; System.out.println(c && d); //prints: false System.out.println(c || d); //prints: true
The difference is that the &&
and ||
operators do not always evaluate the second operand. For example, in the case of the &&
operator, if the first operand is false
, the second operand is not evaluated because the result of the whole expression will be false
anyway. Similarly, in the case of the ||
operator, if the first operand is true
, the whole expression will be clearly evaluated to true
without evaluating the second operand. We can demonstrate this in the following code snippet:
int h = 1; System.out.println(h > 3 && h++ < 3); //prints: false System.out.println(h); //prints: 2 System.out.println(h > 3 && h++ < 3); //prints: false System.out.println(h); //prints: 2
The ? :
operator is called a ternary operator. It evaluates a condition (before the ?
sign), and if it results in true
, assigns to a variable the value calculated by the first expression (between the ?
and :
signs); otherwise, it assigns a value calculated by the second expression (after the :
sign), as illustrated in the following code snippet:
int n = 1, m = 2; float k = n > m ? (n * m + 3) : ((float)n / m); System.out.println(k); //prints: 0.5
Assignment operators (=, +=, -=, *=, /=, and %=)
The =
operator just assigns a specified value to a variable, like this:
x = 3;
Other assignment operators calculate a new value before assigning it, as follows:
x += 42
assigns tox
the result of thex = x + 42
addition operation.x -= 42
assigns tox
the result of thex = x - 42
subtraction operation.x *= 42
assigns tox
the result of thex = x * 42
multiplication operation.x /= 42
assigns tox
the result of thex = x / 42
division operation.x %= 42
assigns the remainder of thex = x + x % 42
division operation.
Here is how these operators work (execute the main()
method of the com.packt.learnjava.ch01_start.Operators
class—see the assignment()
method):
float a = 1f; a += 2; System.out.println(a); //prints: 3.0 a -= 1; System.out.println(a); //prints: 2.0 a *= 2; System.out.println(a); //prints: 4.0 a /= 2; System.out.println(a); //prints: 2.0 a %= 2; System.out.println(a); //prints: 0.0