Book Image

Learn Java 17 Programming - Second Edition

By : Nick Samoylov
4 (1)
Book Image

Learn Java 17 Programming - Second Edition

4 (1)
By: Nick Samoylov

Overview of this book

Java is one of the most preferred languages among developers. It is used in everything right from smartphones and game consoles to even supercomputers, and its new features simply add to the richness of the language. This book on Java programming begins by helping you learn how to install the Java Development Kit. You’ll then focus on understanding object-oriented programming (OOP), with exclusive insights into concepts such as abstraction, encapsulation, inheritance, and polymorphism, which will help you when programming for real-world apps. Next, you’ll cover fundamental programming structures of Java such as data structures and algorithms that will serve as the building blocks for your apps with the help of sample programs and practice examples. You’ll also delve into core programming topics that will assist you with error handling, debugging, and testing your apps. As you progress, you’ll move on to advanced topics such as Java libraries, database management, and network programming and also build a sample project to help you understand the applications of these concepts. By the end of this Java book, you’ll not only have become well-versed with Java 17 but also gained a perspective into the future of this language and have the skills to code efficiently with best practices.
Table of Contents (23 chapters)
1
Part 1: Overview of Java Programming
5
Part 2: Building Blocks of Java
15
Part 3: Advanced Java

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 bits
  • char: 16 bits
  • short: 16 bits
  • int: 32 bits
  • long: 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 inclusive
  • short: from -32,768 to 32,767 inclusive
  • int: from -2.147.483.648 to 2.147.483.647 inclusive
  • long: 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 bit
  • double: 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, and long types have a default value of 0.
  • The char type has a default value of \u0000, with the code point 0.
  • float and double types have a default value of 0.0.
  • The boolean type has a default value of false.

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 returns true if the operand is false; otherwise, it returns false.
  • The & binary operator returns true if both of the operands are true.
  • The | binary operator returns true if at least one of the operands is true.

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 to x the result of the x = x + 42 addition operation.
  • x -= 42 assigns to x the result of the x = x - 42 subtraction operation.
  • x *= 42 assigns to x the result of the x = x * 42 multiplication operation.
  • x /= 42 assigns to x the result of the x = x / 42 division operation.
  • x %= 42 assigns the remainder of the x = 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