Detailed explanation of Java Basics

Introduction to Java

Java was first developed by SUN (acquired by Oracle) James Gosling (commander Gao, known as the father of Java) a programming language developed in the early 1990s, initially named Oak, aimed at embedded applications for small household appliances. As a result, there was no response in the market. Who expected that the rise of the Internet brought Oak back to life, so SUN transformed Oak and officially released it under the name of Java in 1995. The reason is that Oak has been registered, so SUN has registered the trademark of Java. With the rapid development of the Internet, Java has gradually become the most important network programming language.

Java is between compiled language and interpreted language. Compiled languages such as C and C + +, the code is directly compiled into machine code for execution, but the instruction sets of CPUs on different platforms (x86, ARM, etc.) are different. Therefore, it is necessary to compile the corresponding machine code of each platform. Interpretative languages such as Python and Ruby do not have this problem. The interpreter can directly load the source code and run it at the cost of low efficiency. Java compiles the code into a "bytecode", which is similar to abstract CPU instructions, and then writes virtual machines for different platforms, which are responsible for loading and executing bytecode. In this way, the effect of "writing once and running everywhere" is realized. Of course, this is for Java developers. For virtual machines, they need to be developed separately for each platform. In order to ensure that virtual machines developed by different platforms and companies can correctly execute Java bytecode, SUN has formulated a series of Java virtual machine specifications. From a practical point of view, the compatibility of the JVM is very good, and the low version of Java bytecode can run normally on the high version of the JVM.

With the development of Java, SUN has divided Java into three different versions:

  • Java SE: Standard Edition
  • Java EE: Enterprise Edition
  • Java ME: Micro Edition

What is the relationship between the three?

┌───────────────────────────┐
│Java EE                    │
│    ┌────────────────────┐ │
│    │Java SE             │ │
│    │    ┌─────────────┐ │ │
│    │    │   Java ME   │ │ │
│    │    └─────────────┘ │ │
│    └────────────────────┘ │
└───────────────────────────┘

In short, Java SE is the Standard Version, including standard JVM s and standard libraries, while Java EE is the enterprise version. It only adds a large number of API s and libraries on the basis of Java SE to facilitate the development of Web applications, databases, message services, etc. the virtual machine used by Java EE applications is exactly the same as Java SE.

Java ME is different from Java SE. It is a "slimming version" for embedded devices. The standard library of Java SE cannot be used on Java ME, and the virtual machine of Java ME is also a "slimming version".

There is no doubt that Java SE is the core of the whole Java platform, and Java EE is necessary for further learning Web applications. Frameworks such as Spring that we are familiar with are all part of the open source ecosystem of Java EE. Unfortunately, Java ME has never really become popular. On the contrary, Android development has become one of the standards of mobile platforms. Therefore, it is not recommended to learn Java ME without special needs.

Therefore, the Java learning roadmap we recommend is as follows:

  1. First, learn Java SE, master the Java language itself, Java core development technology and the use of Java standard library;
  2. If you continue to learn Java EE, you need to learn the Spring framework, database development and distributed architecture;
  3. If you want to learn big data development, Hadoop, Spark, Flink and other big data platforms need to learn. They are all developed based on Java or Scala;
  4. If you want to learn mobile development, go deep into the Android platform and master Android App development.

Java version

Since the release of version 1.0 in 1995, so far, the latest Java version is Java 15:

timeedition
19951.0
19981.2
20001.3
20021.4
20041.5 / 5.0
20051.6 / 6.0
20111.7 / 7.0
20141.8 / 8.0
2017/91.9 / 9.0
2018/310
2018/911
2019/312
2019/913
2020/314
2020/915

Noun interpretation

When beginners learn Java, they often hear the terms JDK and JRE. What are they?

  • JDK: Java Development Kit
  • JRE: Java Runtime Environment

Simply put, JRE is a virtual machine running Java bytecode. However, if there is only java source code, JDK is needed to compile into Java bytecode, because JDK not only includes JRE, but also provides development tools such as compiler and debugger.

The relationship between the two is as follows:

  ┌─    ┌──────────────────────────────────┐
  │     │     Compiler, debugger, etc.     │
  │     └──────────────────────────────────┘
 JDK ┌─ ┌──────────────────────────────────┐
  │  │  │                                  │
  │ JRE │      JVM + Runtime Library       │
  │  │  │                                  │
  └─ └─ └──────────────────────────────────┘
        ┌───────┐┌───────┐┌───────┐┌───────┐
        │Windows││ Linux ││ macOS ││others │
        └───────┘└───────┘└───────┘└───────┘

To learn Java development, of course, you need to install JDK.

What are JSR and JCP?

  • JSR specification: Java Specification Request
  • JCP organization: Java Community Process

In order to ensure the standardization of the Java language, SUN has established a JSR specification. If you want to add a function to the Java platform, such as the function of accessing the database, you should first create a JSR specification and define the interface. In this way, all database manufacturers write java drivers according to the specification, so that developers don't have to worry that the database code they write can run on MySQL, But you can't run on PostgreSQL.

Therefore, JSR is a series of specifications, from the memory model of the JVM to the Web program interface, all of which are standardized. The organization responsible for reviewing JSR is JCP.

When a JSR specification is released, in order to give everyone a reference, a "reference implementation" and a "compatibility test suite" should be released at the same time:

  • RI: Reference Implementation
  • TCK: Technology Compatibility Kit

For example, someone proposed to build a message server based on Java development. This proposal is very good, but it's not enough just to have a proposal. We have to post real running code, which is RI. If other people want to develop such a message server, how to ensure that these message servers have the same interfaces and functions for developers? So we have to provide TCK.

Generally speaking, RI is only the correct code that "can run". It does not pursue speed. Therefore, if you really want to choose a Java message server, generally no one uses RI, and everyone will choose a competitive commercial or open source product.

Install JDK

Because Java programs must run on the JVM, the first thing we do is install the JDK.

Search JDK 8 to ensure that Oracle's official website Download the stable JDK:

[the external chain picture transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-bjUQP8vO-1624588123743)(C:\Users644\Pictures \ caption \ jdk.png)]

Setting environment variables

Variable name: JAVA_HOME

Variable value: C: \ program files (x86) \ Java \ jdk1 8.0_ 91 / / configure according to your actual path

Variable name: CLASSPATH

Variable value:.;% JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar; // Remember there's a "."

Variable name: Path

Variable value:% JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;

Check whether the Java environment is successfully configured from the command prompt

Development tool: IDEA

First Java program

public class Hello {    public static void main(String[] args) {        System.out.println("Hello, world!");    }}

In a Java program, you can always find one similar to:

public class Hello {    ...}

This definition is called class (class). The class name here is hello, which is case sensitive. Class is used to define a class. Public means that the class is public. Public and class are Java keywords and must be lowercase. Hello is the name of the class. According to custom, the initial H should be capitalized. In the middle of curly braces {} is the definition of class.

Note that in the class definition, we define a method called main:

 public static void main(String[] args) {        ...    }

A method is an executable code block. In addition to the method name main, a method also has method parameters enclosed by (). Here, the main method has a parameter, the parameter type is String [], the parameter name is args, and public and static are used to modify the method. Here, it means that it is a public static method, void is the return type of the method, and the code of the method is in the middle of curly brackets {}.

Method for each line of code; At the end, there is only one line of code, which is:

System.out.println("Hello, world!");

It is used to print a string to the screen.

Java stipulates that the public static void main(String[] args) defined by a class is the fixed entry method of the Java program. Therefore, the Java program always starts from the main method.

Note that the indentation of Java source code is not necessary, but the format is good after indentation. It is easy to see the beginning and end of the code block. The indentation is usually 4 spaces or a tab.

Finally, when we save the code as a file, the file name must be hello Java, and the file name should also pay attention to case, because it should be completely consistent with the class name Hello defined by us.

How to run a Java program

The Java source code is essentially a text file. We need to use javac to put hello Java compiled into bytecode file hello Class, and then execute the bytecode file with the Java command:

┌──────────────────┐
│    Hello.java    │<─── source code
└──────────────────┘
          │ compile
          ▼
┌──────────────────┐
│   Hello.class    │<─── byte code
└──────────────────┘
          │ execute
          ▼
┌──────────────────┐
│    Run on JVM    │
└──────────────────┘

Therefore, the executable javac is the compiler, and the executable java is the virtual machine.

The first step is to save hello Execute the command javac hello java:

$ javac Hello.java

If the source code is correct, the above command will not have any output, and a hello. Com will be generated in the current directory Class file:

$ lsHello.class	Hello.java

Step 2, execute hello Class, use the command java Hello:

$ java HelloHello, world!

Note: the parameter Hello passed to the virtual machine is the class name defined by us. The virtual machine automatically finds the corresponding class file and executes it.

Some children's shoes may know, run Java Hello directly Java is also possible:

$ java Hello.java Hello, world!

This is a new feature of Java 11. It can directly run a single file source code!

It should be noted that in actual projects, a single Java source code that does not rely on a third-party library is very rare. Therefore, in most cases, we cannot directly run a Java source code file because it needs to rely on other libraries.

Summary

A Java source code can only define a class of public type, and the class name and file name should be exactly the same;

Using javac, you can java source code compiled into class bytecode;

Using Java, you can run a compiled Java program, and the parameter is the class name.

Java program foundation

Basic knowledge of Java programs, including:

  • Basic structure of Java program

  • Variables and data types

  • Integer operation

  • Floating point operation

  • Boolean operation

  • Characters and strings

  • Array type

  • Variables and data types

  • variable

    What are variables?

    Variable is the concept of algebra in junior high school mathematics. For example, a simple equation, x and y are variables:

    y=x^2+1y=x2+1

    In Java, variables are divided into two types: basic type variables and reference type variables.

    Let's start with basic types of variables.

    In Java, variables must be defined before use. When defining variables, you can give them an initial value. For example:

    int x = 1;
    

    The above statement defines a variable of integer int type, with the name of x and the initial value of 1.

    If you do not write the initial value, it is equivalent to assigning it a default value. The default value is always 0.

    Let's take a complete example of defining variables and then printing variable values:

public class Main {
    public static void main(String[] args) {
        int x = 100; // Define the int type variable x and assign the initial value of 100
        System.out.println(x); // Print the value of the variable
    }
}

An important feature of variables is that they can be re assigned. For example, for variable x, assign a value of 100 first and then 200, and observe the results of two printing:

// Reassign variable
public class Main {
    public static void main(String[] args) {
        int x = 100; // Define the variable x of type int and assign the initial value of 100
        System.out.println(x); // Print the value of the variable and observe whether it is 100
        x = 200; // Reassign to 200
        System.out.println(x); // Print the value of the variable and observe whether it is 200
    }
}

Note that when defining variable x for the first time, you need to specify the variable type int, so use the statement int x = 100;. During the second re assignment, the variable x already exists and cannot be defined repeatedly. Therefore, the variable type int cannot be specified. The statement x = 200; `.

Variables can not only be re assigned, but also assigned to other variables. Let's take an example:

// Assignment between variables
public class Main {
    public static void main(String[] args) {
        int n = 100; // Define the variable n and assign a value of 100
        System.out.println("n = " + n); // Print the value of n

        n = 200; // The variable n is assigned a value of 200
        System.out.println("n = " + n); // Print the value of n

        int x = n; // The variable x is assigned n (the value of n is 200, so the value of X after assignment is also 200)
        System.out.println("x = " + x); // Print the value of x

        x = x + 100; // The variable x is assigned x+100 (the value of X is 200, so the value of X after assignment is 200 + 100 = 300)
        System.out.println("x = " + x); // Print the value of x
        System.out.println("n = " + n); // Print the value of N again. Should n be 200 or 300?
   }
}

We analyze the code execution process line by line:

Execute int n = 100;, This statement defines the variable n and assigns a value of 100. Therefore, the JVM allocates a "storage unit" for the variable n in memory and fills in the value of 100:

      n
      │
      ▼
┌───┬───┬───┬───┬───┬───┬───┐
│   │100│   │   │   │   │   │
└───┴───┴───┴───┴───┴───┴───┘

Execution n = 200; When, the JVM writes 200 to the storage unit of variable n. therefore, the original value is overwritten. Now the value of n is 200:

      n
      │
      ▼
┌───┬───┬───┬───┬───┬───┬───┐
│   │200│   │   │   │   │   │
└───┴───┴───┴───┴───┴───┴───┘

Execute int x = n; A new variable x is defined and assigned to X at the same time. Therefore, the JVM needs to allocate a new storage unit to variable x and write the same value as variable n. as a result, the value of variable x also becomes 200:

      n           x
      │           │
      ▼           ▼
┌───┬───┬───┬───┬───┬───┬───┐
│   │200│   │   │200│   │   │
└───┴───┴───┴───┴───┴───┴───┘

Execute x = x + 100; When, the JVM first calculates the value x + 100 on the right side of the equation and the result is 300 (because the value of X is 200 at this time), and then writes the result 300 to the storage unit of X. therefore, the final value of variable x becomes 300:

      n           x
      │           │
      ▼           ▼
┌───┬───┬───┬───┬───┬───┬───┐
│   │200│   │   │300│   │   │
└───┴───┴───┴───┴───┴───┴───┘

It can be seen that variables can be assigned repeatedly. Note that the equal sign = is an assignment statement, not equal in the mathematical sense, otherwise x = x + 100 cannot be explained.

Basic data type

The basic data type is the type that the CPU can operate directly. Java defines the following basic data types:

  • Integer type: byte, short, int, long
  • Float type: float, double
  • Character type: char
  • boolean type: boolean

What are the differences between these basic data types defined in Java? To understand these differences, we must briefly understand the basic structure of computer memory.

The smallest storage unit of computer memory is byte. A byte is an 8-bit binary number, that is, 8 bits. Its binary representation ranges from 00000000 ` 11111111 `, which is 0255 in decimal system and 00~ff in hexadecimal system.

Memory units are numbered from 0 and are called memory addresses. Each memory unit can be regarded as a room, and the memory address is the house number.

  0   1   2   3   4   5   6  ...
┌───┬───┬───┬───┬───┬───┬───┐
│   │   │   │   │   │   │   │...
└───┴───┴───┴───┴───┴───┴───┘

One byte is 1byte, 1024 bytes is 1K, 1024K is 1M, 1024M is 1G and 1024G is 1T. The number of bytes of a computer with 4T memory is:

4T = 4 x 1024G
   = 4 x 1024 x 1024M
   = 4 x 1024 x 1024 x 1024K
   = 4 x 1024 x 1024 x 1024 x 1024
   = 4398046511104

Different data types occupy different bytes. Let's take a look at the number of bytes occupied by Java basic data types:

       ┌───┐
  byte │   │
       └───┘
       ┌───┬───┐
 short │   │   │
       └───┴───┘
       ┌───┬───┬───┬───┐
   int │   │   │   │   │
       └───┴───┴───┴───┘
       ┌───┬───┬───┬───┬───┬───┬───┬───┐
  long │   │   │   │   │   │   │   │   │
       └───┴───┴───┴───┴───┴───┴───┴───┘
       ┌───┬───┬───┬───┐
 float │   │   │   │   │
       └───┴───┴───┴───┘
       ┌───┬───┬───┬───┬───┬───┬───┬───┐
double │   │   │   │   │   │   │   │   │
       └───┴───┴───┴───┴───┴───┴───┴───┘
       ┌───┬───┐
  char │   │   │
       └───┴───┘

Byte is exactly one byte, while long and double require 8 bytes.

integer

For integer types, Java only defines signed integers, so the highest bit represents the sign bit (0 represents a positive number and 1 represents a negative number). The maximum range that various integers can represent is as follows:

  • byte: -128 ~ 127

  • short: -32768 ~ 32767

  • int: -2147483648 ~ 2147483647

  • long: -9223372036854775808 ~ 9223372036854775807

    Let's take an example of defining an integer:

//Define integer
public class Main {
    public static void main(String[] args) {
        int i = 2147483647;
        int i2 = -2147483648;
        int i3 = 2_000_000_000; // Underline is easier to identify
        int i4 = 0xff0000; // Hexadecimal 16711680
        int i5 = 0b1000000000; // Binary 512
        long l = 9000000000000000000L; // L is required at the end of long type
    }
}

float

Floating point numbers are decimals, because the decimal point can be "floating" when expressed in scientific counting method. For example, 1234.5 can be expressed as 12.345x102 or 1.2345x103, so it is called floating point numbers.

The following is an example of defining a floating point number:

float f1 = 3.14f;
float f2 = 3.14e38f; // 3.14x10^38 in scientific counting
double d = 1.79e308;
double d2 = -1.79e308;
double d3 = 4.9e-324; // 4.9x10^-324 expressed by scientific counting method

For float type, you need to add the f suffix.

Floating point numbers can be represented in a very large range. The float type can represent up to 3.4x1038, while the double type can represent up to 1.79x10308.

Boolean type

boolean type has only two values: true and false. boolean type is always the calculation result of relational operation:

boolean b1 = true;
boolean b2 = false;
boolean isGreater = 5 > 3; // The calculation result is true
int age = 12;
boolean isAdult = age >= 18; // The calculation result is false

The Java language does not specify the storage of boolean types, because in theory, it only needs 1 bit to store boolean types, but usually the JVM will express boolean as a 4-byte integer.

Character type

The character type char represents a character. In addition to the standard ASCII, the char type of Java can also represent a Unicode character:

public class Main {
    public static void main(String[] args) {
        char a = 'A';
        char zh = 'in';
        System.out.println(a);
        System.out.println(zh);
    }
}

Note that the char type uses single quotation marks' and has only one character, which should be distinguished from the string type of "double quotation marks".

reference type

In addition to the above basic types of variables, the rest are reference types. For example, the most common reference type is String:

String s = "hello";

The variable of reference type is similar to the pointer of C language. It internally stores an "address" pointing to the location of an object in memory. We will discuss the concept of class in detail later.

constant

When defining a variable, if the final modifier is added, the variable becomes a constant:

final double PI = 3.14; // PI is a constant
double r = 5.0;
double area = PI * r * r;
PI = 300; // compile error!

Constants cannot be re assigned after initialization during definition. Re assignment will lead to compilation errors.

The function of constants is to use meaningful variable names to avoid magic numbers. For example, don't write 3.14 everywhere in the code, but define a constant. If we need to improve the calculation accuracy in the future, we only need to modify the definition of the constant, for example, to 3.1416, instead of replacing 3.14 everywhere.

By convention, constant names are usually all uppercase.

var keyword

Sometimes, the name of the type is too long, which is troublesome to write. For example:

StringBuilder sb = new StringBuilder();

At this time, if you want to omit the variable type, you can use the var keyword:

var sb = new StringBuilder();

The compiler will automatically infer that the type of variable sb is StringBuilder according to the assignment statement. For the compiler, the statement:

var sb = new StringBuilder();

In fact, it will automatically become:

StringBuilder sb = new StringBuilder();

Therefore, using var to define variables is only to write less variable types.

Scope of variable

In Java, multiline statements are enclosed by {}. Many control statements, such as conditional judgment and loop, take {} as their own scope, for example:

if (...) { // if start
    ...
    while (...) { while start
        ...
        if (...) { // if start
            ...
        } // if end
        ...
    } // while end
    ...
} // if end

As long as these {} are nested correctly, the compiler can recognize the beginning and end of the statement block. The variables defined in the statement block have a scope, that is, from the definition to the end of the statement block. If you reference these variables outside the scope, the compiler will report an error. for instance:

{
    ...
    int i = 0; // The variable i is defined from here
    ...
    {
        ...
        int x = 1; // The variable x is defined from here
        ...
        {
            ...
            String s = "hello"; // The variable s is defined from here
            ...
        } // This concludes the scope of variable s
        ...
        // Note that this is a new variable s, which has the same name as the above variable,
        // However, due to different scopes, they are two different variables:
        String s = "hi";
        ...
    } // This concludes the scope of variables x and s
    ...
} // This concludes the scope of variable i

When defining variables, follow the principle of scope minimization, try to define variables in the scope as small as possible, and do not reuse variable names.

Summary

Java provides two types of variables: basic type and reference type

Basic types include integer, floating point, Boolean and character.

Variables can be re assigned. The equal sign is an assignment statement, not an equal sign in mathematical sense.

Constants cannot be re assigned after initialization. Using constants is easy to understand the program intent.

Integer operation

The integer operation of Java follows four operation rules, and any nested parentheses can be used. The four operation rules are consistent with elementary mathematics. For example:

// Four arithmetic
public class Main {
    public static void main(String[] args) {
        int i = (100 + 200) * (99 - 88); // 3300
        int n = 7 * (5 + (i - 9)); // 23072
        System.out.println(i);
        System.out.println(n);
    }
}

The numerical representation of integers is not only accurate, but also the integer operation is always accurate, even the division is accurate, because the division of two integers can only get the integer part of the result:

int x = 12345 / 67; // 184

% for remainder operation:

int y = 12345 % 67; // The remainder of 12345 ÷ 67 is 17

Special note: for integer division, when the divisor is 0, the runtime will report an error, but the compilation will not report an error.

overflow

In particular, due to the range limitation of integers, if the calculation result exceeds the range, overflow will occur, and overflow will not make an error, but a strange result will be obtained:

// Operation overflow 
public class Main {
    public static void main(String[] args) {
        int x = 2147483640;
        int y = 15;
        int sum = x + y;
        System.out.println(sum); // -2147483641
    }
}

To explain the above results, we replace the integers 2147483640 and 15 with binary for addition:

  0111 1111 1111 1111 1111 1111 1111 1000
+ 0000 0000 0000 0000 0000 0000 0000 1111
-----------------------------------------
  1000 0000 0000 0000 0000 0000 0000 0111

Since the highest order calculation result is 1, the addition result becomes a negative number.

To solve the above problem, you can replace int with long. Since long can represent a larger range of integers, the result will not overflow:

long x = 2147483640;long y = 15;long sum = x + y;System.out.println(sum); // 2147483655

There is also a short operator, namely + =, - =, * =, / =, which can be used as follows:

n += 100; // 3409, equivalent to n = n + 100;n -= 100; // 3309, equivalent to n = n - 100;

Self increasing / self decreasing

Java also provides + + operations and -- operations, which can add and subtract 1 from an integer:

// Auto increment / Auto decrement operation
public class Main {
    public static void main(String[] args) {
        int n = 3300;
        n++; // 3301, equivalent to n = n + 1;
        n--; // 3300, equivalent to n = n - 1;
        int y = 100 + (++n); // Don't write that
        System.out.println(y);
    }
}

Note that the calculation results of + + written before and after are different, + + n means to add 1 first and then reference n, and N + + means to reference n first and then add 1. It is not recommended to mix + + operations into conventional operations, which is easy to confuse yourself.

Shift operation

In a computer, integers are always represented in binary form. For example, integer 7 of type int uses 4 bytes to represent binary as follows:

00000000 0000000 0000000 00000111

Integer can be shifted. Shifting 1 bit left of integer 7 will result in integer 14, and shifting 2 bits left will result in integer 28:

int n = 7;       // 00000000 00000000 00000000 00000111 = 7int a = n << 1;  // 00000000 00000000 00000000 00001110 = 14int b = n << 2;  // 00000000 00000000 00000000 00011100 = 28int c = n << 28; // 01110000 00000000 00000000 00000000 = 1879048192int d = n << 29; // 11100000 00000000 00000000 00000000 = -536870912

When moving 29 bits to the left, the result becomes negative because the highest bit becomes 1.

Similarly, shift the integer 28 to the right, and the result is as follows:

int n = 7;       // 00000000 00000000 00000000 00000111 = 7int a = n >> 1;  // 00000000 00000000 00000000 00000011 = 3int b = n >> 2;  // 00000000 00000000 00000000 00000001 = 1int c = n >> 3;  // 00000000 00000000 00000000 00000000 = 0

If you shift a negative number to the right and the highest 1 does not move, the result is still a negative number:

int n = -536870912;int a = n >> 1;  // 11110000 00000000 00000000 00000000 = -268435456int b = n >> 2;  // 11111000 00000000 00000000 00000000 = -134217728int c = n >> 28; // 11111111 11111111 11111111 11111110 = -2int d = n >> 29; // 11111111 11111111 11111111 11111111 = -1

There is also an unsigned shift right operation, which uses > > >. Its feature is that regardless of the sign bit, the high bit is always supplemented with 0 after the shift right. Therefore, if a negative number is > > > shifted right, it will become a positive number because the highest 1 becomes 0:

int n = -536870912;int a = n >>> 1;  // 01110000 00000000 00000000 00000000 = 1879048192int b = n >>> 2;  // 00111000 00000000 00000000 00000000 = 939524096int c = n >>> 29; // 00000000 00000000 00000000 00000111 = 7int d = n >>> 31; // 00000000 00000000 00000000 00000001 = 1

When byte and short types are shifted, they are first converted to int and then shifted.

Careful observation shows that moving left is actually constantly × 2. Moving right is actually constantly ÷ 2.

Bit operation

Bit operation is the operation of and, or, non and XOR by bit.

The rule of and operation is that two numbers must be 1 at the same time before the result is 1:

n = 0 & 0; // 0n = 0 & 1; // 0n = 1 & 0; // 0n = 1 & 1; // 1

The rule of or operation is that as long as any one is 1, the result is 1:

n = 0 | 0; // 0n = 0 | 1; // 1n = 1 | 0; // 1n = 1 | 1; // 1

The rule of non operation is that 0 and 1 are interchangeable:

n = ~0; // 1n = ~1; // 0

The rule of XOR operation is that if two numbers are different, the result is 1, otherwise it is 0:

n = 0 ^ 0; // 0n = 0 ^ 1; // 1n = 1 ^ 0; // 1n = 1 ^ 1; // 0

Bit operation on two integers is actually bit alignment, and then operation on each bit in turn. For example:

// Bit operation
public class Main {
    public static void main(String[] args) {
        int i = 167776589; // 00001010 00000000 00010001 01001101
        int n = 167776512; // 00001010 00000000 00010001 00000000
        System.out.println(i & n); // 167776512
    }
}

The above bitwise sum operation can actually be regarded as IP addresses 10.0.17.77 and 10.0.17.0 represented by two integers. Through the sum operation, we can quickly judge whether an IP is in a given network segment.

Operation priority

In the calculation expression of Java, the operation priority from high to low is:

  • ()
  • ! ~ ++ --
  • * / %
  • + -
  • << >> >>>
  • &
  • |
  • += -= *= /=

It doesn't matter if you can't remember. You only need to add parentheses to ensure the correct priority of the operation.

Type automatic promotion and forced transformation

During the operation, if the two number types involved in the operation are inconsistent, the calculation result is an integer of the larger type. For example, in the calculation of short and int, the result is always int, because short is automatically transformed into int first:

// Type automatic promotion and forced transformation
public class Main {
    public static void main(String[] args) {
        short s = 1234;
        int i = 123456;
        int x = s + i; // s is automatically converted to int
        short y = s + i; // Compilation error!
    }
}

The result can also be forcibly transformed, that is, a large range of integers can be transformed into a small range of integers. Force transformation uses (type), for example, force transformation of int to short:

int i = 12345;
short s = (short) i; // 12345

Note that forced transformation beyond the range will get wrong results. The reason is that during transformation, the two high-order bytes of int are directly thrown away, and only the two low-order bytes are retained:

// Forced transformation
public class Main {
    public static void main(String[] args) {
        int i1 = 1234567;
        short s1 = (short) i1; // -10617
        System.out.println(s1);
        int i2 = 12345678;
        short s2 = (short) i2; // 24910
        System.out.println(s2);
    }
}

Summary

The result of integer operation is always accurate;

The calculation result will be automatically promoted;

Forced transformation can be, but forced transformation beyond the scope will get wrong results;

You should select an appropriate range of integers (int or long). There is no need to use byte and short for integer operation in order to save memory.

Floating point operation

Compared with integer operation, floating-point operation can only perform addition, subtraction, multiplication and division, not bit operation and shift operation.

In computers, although floating-point numbers represent a wide range, floating-point numbers have a very important feature, that is, floating-point numbers are often unable to be accurately represented.

For example, chestnuts:

Floating point number 0.1 cannot be accurately expressed in the computer, because the conversion of decimal 0.1 to binary is an infinite circular decimal. Obviously, no matter using float or double, only an approximate value of 0.1 can be stored. However, the floating-point number 0.5 can be accurately represented.

Because floating-point numbers are often not accurately represented, floating-point operations produce errors:

//Floating point arithmetic error
public class Main {
    public static void main(String[] args) {
        double x = 1.0 / 10;
        double y = 1 - 9.0 / 10;
        // Observe whether x and y are equal:
        System.out.println(x);
        System.out.println(y);
    }
}

Due to the operation error of floating-point numbers, it is often wrong to compare whether two floating-point numbers are equal. The correct comparison method is to judge whether the absolute value of the difference between two floating-point numbers is less than a small number:

// To compare whether x and y are equal, first calculate the absolute value of their difference:
double r = Math.abs(x - y);
// Then judge whether the absolute value is small enough:
if (r < 0.00001) {
    // It can be considered equal
} else {
    // Unequal
}

The representation of floating-point numbers in memory is more complex than that of integers. Java's floating-point numbers fully follow IEEE-754 Standard, which is also the standard representation of floating-point numbers supported by most computer platforms.

Type promotion

If one of the two numbers involved in the operation is an integer, the integer can be automatically promoted to a floating-point type:

// Type promotion
public class Main {
    public static void main(String[] args) {
        int n = 5;
        double d = 1.2 + 24.0 / n; // 6.0
        System.out.println(d);
    }
}

It should be noted that in a complex four arithmetic operation, the operation of two integers will not be promoted automatically. For example:

double d = 1.2 + 24 / 5; // 5.2

The calculation result is 5.2 because when the compiler calculates the sub expression 24 / 5, it operates as two integers, and the result is still integer 4.

overflow

Integer operation will report an error when the divisor is 0, while floating-point operation will not report an error when the divisor is 0, but will return several special values:

  • NaN means Not a Number
  • Infinity means infinity
  • -Infinity means negative infinity

For example:

double d1 = 0.0 / 0; // NaNdouble d2 = 1.0 / 0; // Infinitydouble d3 = -1.0 / 0; // -Infinity

These three special values are rarely encountered in practical operation. We only need to understand them.

Forced transformation

Floating point numbers can be forcibly converted to integers. During the transformation, the decimal part of the floating point number will be lost. If the transformation exceeds the maximum range that can be represented by an integer, the maximum value of the integer will be returned. For example:

int n1 = (int) 12.3; // 12int n2 = (int) 12.7; // 12int n2 = (int) -12.7; // -12int n3 = (int) (12.7 + 0.5); // 13int n4 = (int) 1.2e20; // 2147483647

If you want to round, you can add 0.5 to the floating-point number and force the transformation:

// rounding
public class Main {
    public static void main(String[] args) {
        double d = 2.6;
        int n = (int) (d + 0.5);
        System.out.println(n);
    }
}

Summary

Floating point numbers are often not accurately represented, and the operation results of floating point numbers may have errors;

Comparing two floating-point numbers usually compares whether the absolute value of their difference is less than a specific value;

During integer and floating-point operations, integer will be automatically promoted to floating-point;

You can force a floating-point type to an integer, but when it is out of range, the maximum value of the integer is always returned.

Boolean operation

For boolean type Boolean, there are always only two values: true and false.

Boolean operation is a kind of relational operation, including the following categories:

  • Comparison operators: >, > =, <, < =, = ==
  • And operation&&
  • Or operation||
  • Non arithmetic!

Here are some examples:

boolean isGreater = 5 > 3; // trueint age = 12;boolean isZero = age == 0; // falseboolean isNonZero = !isZero; // trueboolean isAdult = age >= 18; // falseboolean isTeenager = age >6 && age <18; // true

The precedence of relational operators from high to low is:

  • !
  • >,>=,<,<=
  • ==,!=
  • &&
  • ||

Short circuit operation

An important feature of Boolean operation is short circuit operation. If the expression of a Boolean operation can determine the result in advance, the subsequent calculation will not be executed and the result will be returned directly.

Because the result of false & & X is always false, whether x is true or false, the and operation will not continue to calculate after determining that the first value is false, but directly return false.

We examine the following codes:

// Short circuit operation
public class Main {
    public static void main(String[] args) {
        boolean b = 5 < 3;
        boolean result = b && (5 / 0 > 0);
        System.out.println(result);
    }
}

If there is no short-circuit operation, the expression after & & will report an error because the divisor is 0, but in fact, the statement does not report an error because the and operation is a short-circuit operator and the result false is calculated in advance.

If the value of variable b is true, the expression becomes true & & (5 / 0 > 0). Because the short-circuit operation cannot be performed, the expression must report an error due to the divisor of 0. You can test it yourself.

Similarly, for the 𞓜 operation, as long as the first value can be determined to be true, subsequent calculations will not be carried out, but will directly return true:

boolean result = true || (5 / 0 > 0); // true

Ternary operator

Java also provides a ternary operator B? X: y, which returns the calculation result of one of the following two expressions respectively according to the result of the first Boolean expression. Example:

// Ternary operation
public class Main {
    public static void main(String[] args) {
        int n = -100;
        int x = n >= 0 ? n : -n;
        System.out.println(x);
    }
}

The above statement means to judge whether n > = 0 is true. If it is true, it returns n; otherwise, it returns - n. This is actually an expression for the absolute value.

Notice the ternary operation b? X: y will calculate b first. If b is true, only x will be calculated. Otherwise, only y will be calculated. In addition, the types of X and y must be the same, because the return value is not boolean, but one of X and y.

Summary

And operation and or operation is short circuit operation;

Ternary operation B? The following types of x: y must be the same. Ternary operation is also "short circuit operation", which only calculates x or y.

Characters and strings

In Java, characters and strings are two different types.

Character type

Character type char is the basic data type, which is the abbreviation of character. A char saves a Unicode character:

char c1 = 'A';char c2 = 'in';

Because Java always uses Unicode to represent characters in memory, an English character and a Chinese character are represented by a char type, both of which occupy two bytes. To display the Unicode encoding of a character, simply assign the char type directly to the int type:

int n1 = 'A'; // The Unicode code of the letter "A" is 65int n2 = 'medium'// The Unicode code of the Chinese character "Zhong" is 20013

You can also directly use escape character \ u+Unicode encoding to represent a character:

// Note hex: char c3 = '\u0041'; // 'A ', because hexadecimal 0041 = decimal 65char C4 =' \ u4e2d '; / /' Medium ', because hex 4e2d = decimal 20013

String type

Different from char type, String type String is a reference type. We use double quotation marks "..." Represents a String. A String can store 0 to any character:

String s = ""; // Empty string, containing 0 characters String s1 = "A"// Contains a character String s2 = "ABC"// Contains 3 characters String s3 = "Chinese ABC"// Contains 6 characters, including one space

Because the string uses double quotation marks "..." If the string itself contains a "character", for example, "abc"xyz ", the compiler cannot determine whether the middle quotation mark is part of the string or indicates the end of the string. At this time, we need to use the escape character \:

String s = "abc\"xyz"; // Contains 7 characters: a, b, c, ", x, y, z

Because \ is an escape character, two \ \ represent a \ character:

String s = "abc\\xyz"; // Contains 7 characters: a, b, c, \, x, y, z

Common escape characters include:

  • \"Represents a character"
  • \'represents character '
  • \Representation character\
  • \n indicates a newline character
  • \r means carriage return
  • \t means Tab
  • \u###### represents a Unicode encoded character

For example:

String s = "ABC\n\u4e2d\u6587"; // It contains 6 characters: A, B, C, line feed, Chinese and Chinese

String connection

The Java compiler takes special care of strings. You can use + to connect any string and other data types, which greatly facilitates the processing of strings. For example:

// String connection 
public class Main {
    public static void main(String[] args) {
        String s1 = "Hello";
        String s2 = "world";
        String s = s1 + " " + s2 + "!";
        System.out.println(s);
    }
}

If + is used to connect strings and other data types, other data types will be automatically transformed into strings first, and then connected:

// String connection
public class Main {
    public static void main(String[] args) {
        int age = 25;
        String s = "age is " + age;
        System.out.println(s);
    }
}

Multiline string

If we want to represent multi line strings, it is very inconvenient to use the + sign to connect:

String s = "first line \n"         + "second line \n"         + "end";

Starting with Java 13, strings can be "..." "" indicates a multiline string (Text Blocks). for instance:

// Multiline string
public class Main {
    public static void main(String[] args) {
        String s = """
                   SELECT * FROM
                     users
                   WHERE id > 100
                   ORDER BY name DESC
                   """;
        System.out.println(s);
    }
}

The above multi line string is actually 5 lines, followed by the last DESC \ n. If we don't want to add a \ n at the end of the string, we need to write this:

String s = """            SELECT * FROM             users           WHERE id > 100           ORDER BY name DESC""";

It should also be noted that the common spaces in front of multi line strings will be removed, that is:

String s = """...........SELECT * FROM...........  users...........WHERE id > 100...........ORDER BY name DESC...........""";

Use The spaces marked will be removed.

If the layout of multiline strings is irregular, the spaces removed will become as follows:

String s = """.........  SELECT * FROM.........    users.........WHERE id > 100.........  ORDER BY name DESC.........  """;

That is, it is always based on the shortest space at the beginning of the line.

Immutable characteristic

In addition to being a reference type, Java string has another important feature, that is, the string is immutable. Consider the following codes:

// String immutable
public class Main {
    public static void main(String[] args) {
        String s = "hello";
        System.out.println(s); // Show hello
        s = "world";
        System.out.println(s); // Show world
    }
}

Observe the execution result. Has the string s changed? In fact, it is not the string that changes, but the "point" of variable s.

Execute String s = "hello"; When, the JVM virtual machine first creates the string "hello", and then points the string variable s to it:

      s
      │
      ▼
┌───┬───────────┬───┐
│   │  "hello"  │   │
└───┴───────────┴───┘

Next, execute s = "world"; When, the JVM virtual machine first creates the string "world", and then points the string variable s to it:

      s ──────────────┐
                      │
                      ▼
┌───┬───────────┬───┬───────────┬───┐
│   │  "hello"  │   │  "world"  │   │
└───┴───────────┴───┴───────────┴───┘

The original string "hello" is still there, but we can't access it through the variable s. Therefore, immutability of a string means that the content of the string is immutable.

After understanding the "pointing" of reference type, try to explain the following code output:

// String immutable
public class Main {
    public static void main(String[] args) {
        String s = "hello";
        String t = s;
        s = "world";
        System.out.println(t); // Is t "hello" or "world"?
    }
}

Null value null

A variable of reference type can point to a null value, which indicates that it does not exist, that is, the variable does not point to any object. For example:

String s1 = null; // s1 is null
String s2; // No initial value is assigned, and s2 is null
String s3 = s1; // s3 is also null
String s4 = ""; // s4 points to an empty string, not null

Note: to distinguish between null value and empty string '', empty string is a valid string object, which is not equal to null.

Array type

If we have a set of variables of the same type, for example, the grades of 5 students, we can write as follows:

public class Main {    public static void main(String[] args) {        // Results of 5 students: int N1 = 68; int n2 = 79;         int n3 = 91;         int n4 = 85;         int n5 = 62;    }}

But there is no need to define five int variables. You can use arrays to represent "a set" of int types. The code is as follows:

public class Main {
    public static void main(String[] args) {
        // Results of 5 students:
        int n1 = 68;
        int n2 = 79;
        int n3 = 91;
        int n4 = 85;
        int n5 = 62;
    }
}

Define a variable of array type, using the array type "type []", for example, int []. Unlike a single basic type variable, array variable initialization must use new int[5] to create an array that can hold 5 int elements.

Java arrays have several characteristics:

  • All elements of the array are initialized to the default value. The integer type is 0, the floating point type is 0.0, and the boolean type is false;
  • Once the array is created, its size cannot be changed.

To access an element in an array, you need to use an index. The array index starts from 0. For example, for an array of 5 elements, the index range is 0 ~ 4.

You can modify an element in the array and use an assignment statement, for example, ns[1] = 79;.

You can use array variables length get array size:

// array 
public class Main {
    public static void main(String[] args) {
        // Results of 5 students:
        int[] ns = new int[5];
        System.out.println(ns.length); // 5
    }
}

Array is a reference type. When using index to access array elements, if the index exceeds the range, an error will be reported at runtime:

// array
public class Main {
    public static void main(String[] args) {
        // Results of 5 students:
        int[] ns = new int[5];
        int n = 5;
        System.out.println(ns[n]); // Index n cannot be out of range
    }
}

You can also directly specify the initialized elements when defining an array, so that you do not have to write out the array size, but the compiler automatically calculates the array size. For example:

// array
public class Main {
    public static void main(String[] args) {
        // Results of 5 students:
        int[] ns = new int[] { 68, 79, 91, 85, 62 };
        System.out.println(ns.length); // The compiler automatically calculates the array size to 5
    }
}

It can also be further abbreviated as:

int[] ns = { 68, 79, 91, 85, 62 };

Note that the array is a reference type and the array size is immutable. Let's look at the following code:

// array
public class Main {
    public static void main(String[] args) {
        // Results of 5 students:
        int[] ns;
        ns = new int[] { 68, 79, 91, 85, 62 };
        System.out.println(ns.length); // 5
        ns = new int[] { 1, 2, 3 };
        System.out.println(ns.length); // 3
    }
}

Has the array size changed? It looks like it has changed, but it hasn't changed at all.

For array ns, execute ns = New Int [] {68, 79, 91, 85, 62}; When, it points to an array of 5 elements:

     ns
      │
      ▼
┌───┬───┬───┬───┬───┬───┬───┐
│   │68 │79 │91 │85 │62 │   │
└───┴───┴───┴───┴───┴───┴───┘

Execute ns = New Int [] {1, 2, 3}; When, it points to a new array of 3 elements:

     ns ──────────────────────┐
                              │
                              ▼
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│   │68 │79 │91 │85 │62 │   │ 1 │ 2 │ 3 │   │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘

However, the original array of five elements has not changed, but they can not be referenced through the variable ns.

String array

If the array element is not a basic type, but a reference type, how will it be different to modify the array element?

String is a reference type, so we first define a string array:

String[] names = {    "ABC", "XYZ", "zoo"};

For the array variable names of String [] type, it actually contains three elements, but each element points to a String object:

          ┌─────────────────────────┐
    names │   ┌─────────────────────┼───────────┐
      │   │   │                     │           │
      ▼   │   │                     ▼           ▼
┌───┬───┬─┴─┬─┴─┬───┬───────┬───┬───────┬───┬───────┬───┐
│   │░░░│░░░│░░░│   │ "ABC" │   │ "XYZ" │   │ "zoo" │   │
└───┴─┬─┴───┴───┴───┴───────┴───┴───────┴───┴───────┴───┘
      │                 ▲
      └─────────────────┘

Assign a value to names[1], for example, names[1] = "cat";, The effects are as follows:

          ┌─────────────────────────────────────────────────┐
    names │   ┌─────────────────────────────────┐           │
      │   │   │                                 │           │
      ▼   │   │                                 ▼           ▼
┌───┬───┬─┴─┬─┴─┬───┬───────┬───┬───────┬───┬───────┬───┬───────┬───┐
│   │░░░│░░░│░░░│   │ "ABC" │   │ "XYZ" │   │ "zoo" │   │ "cat" │   │
└───┴─┬─┴───┴───┴───┴───────┴───┴───────┴───┴───────┴───┴───────┴───┘
      │                 ▲
      └─────────────────┘

It is noted here that the string "XYZ" pointed to by the original names[1] has not changed, but the reference of names[1] has been changed from pointing to "XYZ" to pointing to "cat". As a result, the string "XYZ" can no longer be accessed through names[1].

After having a deeper understanding of "pointing", try to explain the following code:

// array
public class Main {
    public static void main(String[] args) {
        String[] names = {"ABC", "XYZ", "zoo"};
        String s = names[1];
        names[1] = "cat";
        System.out.println(s); // Is s "XYZ" or "cat"?
    }
}

Summary

An array is a collection of the same data type. Once an array is created, its size is immutable;

You can access array elements through the index, but if the index exceeds the range, an error will be reported;

The array element can be a value type (such as int) or a reference type (such as String), but the array itself is a reference type;

Process control

In Java programs, JVM s are always executed sequentially with semicolons by default; End statement. However, in the actual code, the program often needs to make conditional judgment and loop. Therefore, a variety of process control statements are needed to realize the functions of program jump and loop.

In this section, we will introduce if conditional judgment, switch multiple selection and various loop statements.

Input and output

output

In the previous code, we always used system out. Println () to output something to the screen.

println is the abbreviation of print line, which means output and line feed. Therefore, if you do not want to wrap lines after output, you can use print():

// Output public class Main{    
public static void main(String[] args) {        System.out.print("A,");       
System.out.print("B,");        
System.out.print("C.");        
System.out.println();        
System.out.println("END");    }}

Observe the execution effect of the above code.

Format output

Java also provides the ability to format the output. Why format the output? Because the data represented by the computer is not necessarily suitable for people to read:

// Format output public class Main{    
public static void main(String[] args) {       
 double d = 12900000;        
 System.out.println(d); // 1.29E7    
 }}

If we want to display the data in the desired format, we need to use the function of formatted output. Formatted output uses system out. printf(), by using the placeholder%?, printf() can format the following parameters into the specified format:

// Format output public class Main{   
 public static void main(String[] args) {        
 double d = 3.1415926;        
 System.out.printf("%.2f\n", d); // Display two decimal places 3.14  
 System.out.printf("%.4f\n", d); // Display 4 decimal places 3.1416    
 }}

The formatting function of Java provides a variety of placeholders, which can "format" various data types into specified strings:

placeholder explain
%dFormat output integer
%xFormat output hexadecimal integer
%fFormat output floating point number
%eFormat and output the floating-point number represented by scientific counting method
%sformat string

Note that since% represents a placeholder, two consecutive%% represent a% character itself.

Placeholders themselves can also have more detailed formatting parameters. The following example formats an integer into hexadecimal and complements 8 bits with 0:

// Format output public class Main{    
public static void main(String[] args) {        
int n = 12345000;        
System.out.printf("n=%d, hex=%08x", n, n); // Note that two% placeholders must pass in two numbers    
}}

Please refer to the JDK documentation for detailed formatting parameters java.util.Formatter

input

Java input is much more complex than output.

Let's take a look at an example of reading a string and an integer from the console:

import java.util.Scanner;public class Main {    
public static void main(String[] args) {        
Scanner scanner = new Scanner(System.in); // Create Scanner object        
System.out.print("Input your name: "); // Print tips        
String name = scanner.nextLine(); // Read one line of input and get the string        
System.out.print("Input your age: "); // Print prompt int 
age = scanner.nextInt(); // Read a line of input and get an integer        
System.out.printf("Hi, %s, you are %d\n", name, age); // Format output}

First, we import Java. Net through the import statement util. Scanner, import is a statement that imports a class and must be placed at the beginning of the Java source code. We will explain in detail how to use import in the java package later.

Then, create the Scanner object and pass it in to system in. System.out represents the standard output stream, while system In stands for standard input stream. Directly use system In reading user input is possible, but more complex code is required, and subsequent code can be simplified through Scanner.

After having the scanner object, to read the string entered by the user, use scanner Nextline(), to read the integer entered by the user, use scanner nextInt(). Scanner automatically converts the data type, so you don't have to convert it manually.

To test the input, we cannot run it online because the input must be read from the command line. Therefore, we need to follow the process of compilation and execution:

$ javac Main.java

If there is a warning when compiling this program, you can ignore it temporarily and explain it in detail later when learning IO. After successful compilation, execute:

$ java MainInput your name: BobInput your age: 12Hi, Bob, you are 12

After entering a string and an integer according to the prompt, we get the formatted output.

if judgment

In Java programs, if you want to decide whether to execute a piece of code according to conditions, you need an IF statement.

The basic syntax of the if statement is:

if (condition) {    // Execute}

According to the calculation result of if (true or false), the JVM decides whether to execute the if statement block (that is, all statements contained in curly braces {}).

Let's take an example:

// Condition judgment public class Main{    
public static void main(String[] args) {        
int n = 70;        
if (n >= 60) {            
System.out.println("Passed");        }        System.out.println("END");    }}

When the calculation result of condition n > = 60 is true, the if statement block is executed and "passed" will be printed. Otherwise, the if statement block will be skipped. Modify the value of n to see the execution effect.

Note that the block contained in the if statement can contain multiple statements:

// Condition judgment public class Main{    
public static void main(String[] args) {        
int n = 70;        
if (n >= 60) {            
System.out.println("Passed");            
System.out.println("congratulations");        }        
System.out.println("END");    }}

When the if statement block has only one line of statements, the curly braces {} can be omitted:

// Condition judgment public class Main{    
public static void main(String[] args) {        
int n = 70;        
if (n >= 60)            
System.out.println("Passed");        
System.out.println("END");    }}

However, omitting curly braces is not always a good idea. Suppose you suddenly want to add a statement to the if statement block at some time:

// Condition judgment public class Main{    
public static void main(String[] args) {        
int n = 50;        
if (n >= 60)            
System.out.println("Passed");            
System.out.println("congratulations"); // Note that this statement is not part of the if statement block        
System.out.println("END");    }}

Due to the use of indentation format, it is easy to regard both lines of statements as the execution block of if statement, but in fact, only the first line of statement is the execution block of if statement. When using git, these version control systems are more prone to problems when merging automatically, so ignoring curly braces is not recommended.

else

The if statement can also write an else {...}, When the condition is judged as false, the else statement block will be executed:

// Conditional judgment
public class Main {
    public static void main(String[] args) {
        int n = 70;
        if (n >= 60) {
            System.out.println("Passed");
        } else {
            System.out.println("Failed");
        }
        System.out.println("END");
    }
}

Modify the value of the above code n and observe the statement block executed by the program when the if condition is true or false.

Note that else is not required.

You can also use multiple if else if ... series connection. For example:

// Conditional judgment
public class Main {
    public static void main(String[] args) {
        int n = 70;
        if (n >= 90) {
            System.out.println("excellent");
        } else if (n >= 60) {
            System.out.println("Passed");
        } else {
            System.out.println("Failed");
        }
        System.out.println("END");
    }
}

The effect of concatenation is actually equivalent to:

if (n >= 90) {
    // N > = 90 is true:
    System.out.println("excellent");
} else {
    // N > = 90 is false:
    if (n >= 60) {
        // N > = 60 is true:
        System.out.println("Passed");
    } else {
        // N > = 60 is false:
        System.out.println("Failed");
    }
}

When using multiple if s in series, pay special attention to the judgment order. Observe the following code:

// Conditional judgment
public class Main {
    public static void main(String[] args) {
        int n = 100;
        if (n >= 60) {
            System.out.println("Passed");
        } else if (n >= 90) {
            System.out.println("excellent");
        } else {
            System.out.println("Failed");
        }
    }
}

It is found that when n = 100, the condition n > = 90 is met, but the output is not "excellent", but "passed". The reason is that when the if statement is executed from top to bottom, it is judged that n > = 60 is successful, and the subsequent else will not be executed. Therefore, if (n > = 90) has no chance to execute.

The correct way is to judge from large to small:

// Judge from large to small:
if (n >= 90) {
    // ...
} else if (n >= 60) {
    // ...
} else {
    // ...
}

Or rewrite it to judge from small to large:

// Judge from small to large:
if (n < 60) {
    // ...
} else if (n < 90) {
    // ...
} else {
    // ...
}

When using if, pay special attention to the boundary conditions. For example:

Suppose we expect 90 points or higher to be "excellent", but the output of the above code is "pass", because the effects of > and > = are different.

// Conditional judgment
public class Main {
    public static void main(String[] args) {
        int n = 90;
        if (n > 90) {
            System.out.println("excellent");
        } else if (n >= 60) {
            System.out.println("Passed");
        } else {
            System.out.println("Failed");
        }
    }
}

As mentioned earlier, floating-point numbers are often not accurately represented in computers, and calculation errors may occur. Therefore, it is not reliable to judge the equality of floating-point numbers with = =:

// Conditional judgment
public class Main {
    public static void main(String[] args) {
        double x = 1 - 9.0 / 10;
        if (x == 0.1) {
            System.out.println("x is 0.1");
        } else {
            System.out.println("x is NOT 0.1");
        }
    }
}

The correct method is to use the difference less than a critical value to judge:

// Conditional judgment
public class Main {
    public static void main(String[] args) {
        double x = 1 - 9.0 / 10;
        if (Math.abs(x - 0.1) < 0.00001) {
            System.out.println("x is 0.1");
        } else {
            System.out.println("x is NOT 0.1");
        }
    }
}

Judge whether the reference types are equal

In Java, you can use the = = operator to judge whether the variables of value types are equal. However, to judge whether the variables of reference types are equal, = = means "whether the references are equal", or whether they point to the same object. For example, the contents of the following two String types are the same, but they point to different objects respectively. Judge with = = and the result is false:

// Conditional judgment 
public class Main {
    public static void main(String[] args) {
        String s1 = "hello";
        String s2 = "HELLO".toLowerCase();
        System.out.println(s1);
        System.out.println(s2);
        if (s1 == s2) {
            System.out.println("s1 == s2");
        } else {
            System.out.println("s1 != s2");
        }
    }
}

To determine whether the contents of variables of reference types are equal, you must use the equals() method:

// Conditional judgment
public class Main {
    public static void main(String[] args) {
        String s1 = "hello";
        String s2 = "HELLO".toLowerCase();
        System.out.println(s1);
        System.out.println(s2);
        if (s1.equals(s2)) {
            System.out.println("s1 equals s2");
        } else {
            System.out.println("s1 not equals s2");
        }
    }
}

Note: execute statement s1 When equals (S2), null pointerexception will be reported if the variable s1 is null:

// Conditional judgment
public class Main {
    public static void main(String[] args) {
        String s1 = null;
        if (s1.equals("hello")) {
            System.out.println("hello");
        }
    }
}

To avoid NullPointerException errors, you can use the short circuit operator & &:

// Conditional judgment
public class Main {
    public static void main(String[] args) {
        String s1 = null;
        if (s1 != null && s1.equals("hello")) {
            System.out.println("hello");
        }
    }
}

You can also put the object "hello" that must not be null in front: for example: if ("hello".equals(s)) {...}.

switch multiple selection

In addition to the if statement, there is also a conditional judgment, which is to execute different branches according to the result of an expression.

For example, in a game, let the user choose options:

  1. single mode
  2. Multiplayer mode
  3. Exit the game

At this time, the switch statement comes in handy.

The switch statement jumps to the matching case result according to the result calculated by the switch (expression), and then continues to execute subsequent statements until a break is encountered.

Let's take an example:

// switch
public class Main {
    public static void main(String[] args) {
        int option = 1;
        switch (option) {
        case 1:
            System.out.println("Selected 1");
            break;
        case 2:
            System.out.println("Selected 2");
            break;
        case 3:
            System.out.println("Selected 3");
            break;
        }
    }
}

Modify the values of option to 1, 2 and 3 respectively, and observe the execution results.

If the value of option does not match any case, such as option = 99, the switch statement will not execute any statement. At this time, you can add a default to the switch statement. When no case is matched, execute default:

// switch
public class Main {
    public static void main(String[] args) {
        int option = 99;
        switch (option) {
        case 1:
            System.out.println("Selected 1");
            break;
        case 2:
            System.out.println("Selected 2");
            break;
        case 3:
            System.out.println("Selected 3");
            break;
        default:
            System.out.println("Not selected");
            break;
        }
    }
}

if the switch statement is translated into an if statement, the above code is equivalent to:

if (option == 1) {
    System.out.println("Selected 1");
} else if (option == 2) {
    System.out.println("Selected 2");
} else if (option == 3) {
    System.out.println("Selected 3");
} else {
    System.out.println("Not selected");
}

For multiple = = judgments, the switch structure is clearer.

At the same time, note that the above "translation" can only correspond to each case if the break statement is correctly written in the switch statement.

When using switch, note that the case statement does not have curly braces {}, and the case statement is "penetrating". Missing a break will lead to unexpected results:

// switch
public class Main {
    public static void main(String[] args) {
        int option = 2;
        switch (option) {
        case 1:
            System.out.println("Selected 1");
        case 2:
            System.out.println("Selected 2");
        case 3:
            System.out.println("Selected 3");
        default:
            System.out.println("Not selected");
        }
    }
}

When option = 2, "Selected 2", "Selected 3" and "Not selected" will be output successively because all subsequent statements will be executed from matching to case 2 until a break statement is encountered. So don't forget to write break at any time.

If several case statements execute the same set of statement blocks, it can be written as follows:

// switch
public class Main {
    public static void main(String[] args) {
        int option = 2;
        switch (option) {
        case 1:
            System.out.println("Selected 1");
            break;
        case 2:
        case 3:
            System.out.println("Selected 2, 3");
            break;
        default:
            System.out.println("Not selected");
            break;
        }
    }
}

When using the switch statement, as long as there is a break, the order of case s does not affect the program logic:

switch (option) {
case 3:
    ...
    break;
case 2:
    ...
    break;
case 1:
    ...
    break;
}

However, it is still recommended to arrange them in natural order for easy reading.

The switch statement can also match strings. When matching strings, compare "equal content". For example:

// switch
public class Main {
    public static void main(String[] args) {
        String fruit = "apple";
        switch (fruit) {
        case "apple":
            System.out.println("Selected apple");
            break;
        case "pear":
            System.out.println("Selected pear");
            break;
        case "mango":
            System.out.println("Selected mango");
            break;
        default:
            System.out.println("No fruit selected");
            break;
        }
    }
}

The switch statement can also use enumeration types, which we will explain later.

Compile check

When using the IDE, you can automatically check whether the break statement and default statement are omitted by opening the compilation check of the IDE.

In Eclipse, select Preferences - Java - Compiler - Errors/Warnings - Potential programming problems and mark the following checks as Warning:

  • 'switch' is missing 'default' case
  • 'switch' case fall-through

In Idea, select Preferences - Editor - Inspections - Java - Control flow issues and mark the following inspections as Warning:

  • Fallthrough in 'switch' statement
  • 'switch' statement without 'default' branch

When there is a problem with the switch statement, you can get a warning prompt in the IDE.

switch expression

When using switch, if break is omitted, it will cause serious logical errors, and it is not easy to find errors in the source code. Starting from Java 12, the switch statement is upgraded to a more concise expression syntax, using a method similar to Pattern Matching to ensure that only one path will be executed without a break statement:

// switch 
public class Main {
    public static void main(String[] args) {
        String fruit = "apple";
        switch (fruit) {
        case "apple" -> System.out.println("Selected apple");
        case "pear" -> System.out.println("Selected pear");
        case "mango" -> {
            System.out.println("Selected mango");
            System.out.println("Good choice!");
        }
        default -> System.out.println("No fruit selected");
        }
    }
}

Note that the new syntax uses - >. If there are multiple statements, they need to be enclosed with {}. Do not write break statements, because the new syntax will only execute matching statements without penetration effect.

Many times, we may also assign a value to a variable with a switch statement. For example:

int opt;
switch (fruit) {
case "apple":
    opt = 1;
    break;
case "pear":
case "mango":
    opt = 2;
    break;
default:
    opt = 0;
    break;
}

Using the new switch syntax, you don't need to break, but you can return the value directly. Rewrite the above code as follows:

// switch
public class Main {
    public static void main(String[] args) {
        String fruit = "apple";
        int opt = switch (fruit) {
            case "apple" -> 1;
            case "pear", "mango" -> 2;
            default -> 0;
        }; // Note that the assignment statement should be written in; end
        System.out.println("opt = " + opt);
    }
}

This allows for more concise code.

yield

Most of the time, inside the switch expression, we return a simple value.

However, if complex statements are needed, we can also write many statements and put them in {...} Then, use yield to return a value as the return value of the switch statement:

// yield 
public class Main {
    public static void main(String[] args) {
        String fruit = "orange";
        int opt = switch (fruit) {
            case "apple" -> 1;
            case "pear", "mango" -> 2;
            default -> {
                int code = fruit.hashCode();
                yield code; // Return value of switch statement
            }
        };
        System.out.println("opt = " + opt);
    }
}

Summary

The switch statement can make multiple choices, and then execute the subsequent code of the matched case statement;

The calculation result of switch must be integer, string or enumeration type;

Be careful not to miss the break. It is recommended to open the fall through warning;

Always write default. It is recommended to open the missing default warning;

Starting from Java 14, the switch statement is officially upgraded to an expression, no break is required, and the return value of yield is allowed.

while Loop

Loop statement is to let the computer do loop calculation according to the conditions, continue the loop when the conditions are met, and exit the loop when the conditions are not met.

For example, calculate the sum from 1 to 100:

1 + 2 + 3 + 4 + ... + 100 = ?

In addition to using the sequence formula, the computer can do 100 times of cyclic accumulation. Because the characteristic of the computer is that the computing speed is very fast. We let the computer cycle 100 million times in less than 1 second, so many computing tasks can not be calculated by people, but the computer can quickly get the results by using the simple and rough method of cycle.

Let's first look at the while conditional loop provided by Java. Its basic usage is:

while (Conditional expression) {    Circular statement}// Continue with subsequent code

Before the start of each cycle, the while loop first judges whether the condition is true. If the calculation result is true, execute the statement in the loop body again. If the calculation result is false, jump directly to the end of the while loop and continue to execute.

We use the while loop to accumulate 1 to 100, which can be written as follows:

// while 
public class Main {
    public static void main(String[] args) {
        int sum = 0; // Accumulated sum, initialized to 0
        int n = 1;
        while (n <= 100) { // The cycle condition is n < = 100
            sum = sum + n; // Add n to sum
            n ++; // n itself plus 1
        }
        System.out.println(sum); // 5050
    }
}

Note that the while loop is to judge the loop conditions first and recycle. Therefore, it is possible not to do a loop at all.

For the judgment of cyclic conditions and the treatment of self increasing variables, special attention should be paid to the boundary conditions. Consider why the following code did not get the correct result:

// while
public class Main {
    public static void main(String[] args) {
        int sum = 0;
        int n = 0;
        while (n <= 100) {
            n ++;
            sum = sum + n;
        }
        System.out.println(sum);
    }
}

If the loop condition is always satisfied, the loop becomes an dead loop. Dead loop will lead to 100% CPU consumption. Users will feel that the computer runs slowly, so they should avoid writing dead loop code.

If the logic of the loop condition is written incorrectly, it will also cause unexpected results:

// while
public class Main {
    public static void main(String[] args) {
        int sum = 0;
        int n = 1;
        while (n > 0) {
            sum = sum + n;
            n ++;
        }
        System.out.println(n); // -2147483648
        System.out.println(sum);
    }
}

On the surface, the while loop above is an dead loop. However, the int type of Java has a maximum value. When the maximum value is reached, adding 1 will become a negative number. As a result, the while loop unexpectedly exits.

Summary

The while loop first judges whether the loop conditions are met, and then executes the loop statement;

The while loop may not be executed once;

When writing a cycle, pay attention to the cycle conditions and avoid dead cycles.

do while loop

In Java, a while loop judges the loop condition first, and then executes the loop. The other do while loop is to execute the loop first, and then judge the conditions. When the conditions are met, continue the loop, and exit when the conditions are not met. Its usage is:

do {    Execute loop statement} while (Conditional expression);

It can be seen that the do while loop will loop at least once.

Let's rewrite the summation of 1 to 100 with the do while loop:

// do-while 
public class Main {
    public static void main(String[] args) {
        int sum = 0;
        int n = 1;
        do {
            sum = sum + n;
            n ++;
        } while (n <= 100);
        System.out.println(sum);
    }
}

When using do while loop, you should also pay attention to the judgment of loop conditions.

practice

Use the do while loop to calculate the sum from m to n.

// do while
public class Main {
	public static void main(String[] args) {
		int sum = 0;
        int m = 20;
		int n = 100;
		// Using do while to calculate m ++ N:
		do {
		} while (false);
		System.out.println(sum);
	}
}

Summary

do while loop executes the loop first, and then judges the condition;

The do while loop executes at least once.

for loop

In addition to the while and do while loops, the for loop is the most widely used in Java.

The for loop is very powerful. It uses counters to implement the loop. The for loop initializes the counter first, then detects the loop condition before each loop, and updates the counter after each loop. The counter variable is usually named i.

Let's rewrite the sum of 1 to 100 with the for loop:

// for
public class Main {
    public static void main(String[] args) {
        int sum = 0;
        for (int i=1; i<=100; i++) {
            sum = sum + i;
        }
        System.out.println(sum);
    }
}

Before the execution of the for loop, the initialization statement int i=1 will be executed first, which defines the counter variable I and assigns the initial value to 1. Then, the loop condition I < = 100 will be checked before the loop, and I + + will be executed automatically after the loop. Therefore, compared with the while loop, the for loop unifies the code for updating the counter. Inside the body of the for loop, there is no need to update the variable I.

Therefore, the usage of the for loop is:

for (initial condition ; Cycle detection conditions; Update counter after cycle) {    // Execute statement}

If we want to sum all elements of an integer array, we can use the for loop:

// for 
public class Main {
    public static void main(String[] args) {
        int[] ns = { 1, 4, 9, 16, 25 };
        int sum = 0;
        for (int i=0; i<ns.length; i++) {
            System.out.println("i = " + i + ", ns[i] = " + ns[i]);
            sum = sum + ns[i];
        }
        System.out.println("sum = " + sum);
    }
}

The loop condition of the above code is i < ns length. Because the length of the NS array is 5, when the loop is 5 times, the value of i is updated to 5, the loop condition is not satisfied, so the for loop ends.

Note that the initialization counter of the for loop is always executed, and the for loop may loop 0 times.

When using the for loop, never modify the counter in the loop body! Modifying counters in the loop body often leads to inexplicable logic errors. For the following code:

// for
public class Main {
    public static void main(String[] args) {
        int[] ns = { 1, 4, 9, 16, 25 };
        for (int i=0; i<ns.length; i++) {
            System.out.println(ns[i]);
            i = i + 1;
        }
    }
}

Although no error will be reported, only half of the array elements are printed. The reason is that i = i + 1 inside the loop causes the counter variable to actually increase by 2 each loop (because the for loop will also automatically execute i + +). Therefore, in the for loop, do not modify the value of the counter. The initialization, judgment conditions and update conditions after each cycle of the counter can be clearly put into the for() statement.

If you want to access only array elements with odd indexes, you should rewrite the for loop to:

int[] ns = { 1, 4, 9, 16, 25 };
for (int i=0; i<ns.length; i=i+2) {
    System.out.println(ns[i]);
}

This effect is achieved by updating the statement i=i+2 of the counter, so as to avoid modifying the variable i in the loop body.

When using the for loop, the counter variable i should be defined in the for loop as much as possible:

int[] ns = { 1, 4, 9, 16, 25 };
for (int i=0; i<ns.length; i++) {
    System.out.println(ns[i]);
}
// Unable to access i
int n = i; // compile error!

If the variable i is defined outside the for loop:

int[] ns = { 1, 4, 9, 16, 25 };
int i;
for (i=0; i<ns.length; i++) {
    System.out.println(ns[i]);
}
// i can still be used
int n = i;

Then, after exiting the for loop, the variable i can still be accessed, which breaks the principle that variables should minimize the access range.

Flexible use of for loops

For loops can also lack initialization statements, loop conditions, and update statements for each loop, such as:

// Do not set end condition:
for (int i=0; ; i++) {
    ...
}
// Do not set end conditions and update statements:
for (int i=0; ;) {
    ...
}
// Set nothing:
for (;;) {
    ...
}

This is generally not recommended, but in some cases, some statements of the for loop can be omitted.

for each loop

The for loop is often used to traverse an array because each element of the array can be accessed by index through the counter:

int[] ns = { 1, 4, 9, 16, 25 };
for (int i=0; i<ns.length; i++) {
    System.out.println(ns[i]);
}

However, many times, what we really want to access is the value of each element of the array. Java also provides another for each loop, which can more easily traverse arrays:

// for each 
public class Main {
    public static void main(String[] args) {
        int[] ns = { 1, 4, 9, 16, 25 };
        for (int n : ns) {
            System.out.println(n);
        }
    }
}

Compared with the for loop, the variable n of the for each loop is no longer a counter, but directly corresponds to each element of the array. The for each loop is also more concise. However, the for each loop cannot specify the traversal order or get the index of the array.

In addition to arrays, the for each loop can traverse all "iteratable" data types, including List and Map, which will be described later.

Summary

for loop can realize complex loop through counter;

The for each loop can directly traverse each element of the array;

break and continue

Whether it is a while loop or a for loop, there are two special statements that can be used, namely, the break statement and the continue statement.

break

During a loop, you can use the break statement to jump out of the current loop. Let's take an example:

// break
public class Main {
    public static void main(String[] args) {
        int sum = 0;
        for (int i=1; ; i++) {
            sum = sum + i;
            if (i == 100) {
                break;
            }
        }
        System.out.println(sum);
    }
}

When using the for loop to calculate from 1 to 100, we do not set the detection condition for loop exit in for(). However, inside the loop, we use if to judge that if i==100, we will exit the loop through break.

Therefore, break statements are usually used in conjunction with if statements. In particular, break statements always jump out of their own layer of loop. For example:

// break
public class Main {
    public static void main(String[] args) {
        for (int i=1; i<=10; i++) {
            System.out.println("i = " + i);
            for (int j=1; j<=10; j++) {
                System.out.println("j = " + j);
                if (j >= i) {
                    break;
                }
            }
            // break jump here
            System.out.println("breaked");
        }
    }
}

The above code is a nesting of two for loops. Because the break statement is located in the inner for loop, it will jump out of the inner for loop, but not out of the outer for loop.

continue

break will jump out of the current loop, that is, the whole loop will not be executed. continue ends this cycle in advance and directly continues to execute the next cycle. Let's take an example:

// continue
public class Main {
    public static void main(String[] args) {
        int sum = 0;
        for (int i=1; i<=10; i++) {
            System.out.println("begin i = " + i);
            if (i % 2 == 0) {
                continue; // The continue statement ends the loop
            }
            sum = sum + i;
            System.out.println("end i = " + i);
        }
        System.out.println(sum); // 25
    }
}

Observe the effect of the continue statement. When i is odd, the entire loop is executed completely, so begin i=1 and end i=1 are printed. When i is an even number, the continue statement will end the loop ahead of time. Therefore, begin i=2 will be printed, but end i = 2 will not be printed.

In a multi-level nested loop, the continue statement also ends the loop in which it is located this time.

Summary

break statement can jump out of the current loop;

break statements usually cooperate with if to end the whole loop in advance when the conditions are met;

The break statement always jumps out of the nearest loop;

The continue statement can end this cycle in advance;

The continue statement usually cooperates with the if statement to end the loop in advance when the conditions are met.

Array operation

In this section, we will explain the operations on arrays, including:

  • Traversal;
  • Sort.

And the concept of multidimensional array.

Traversal array

We introduced the data type of array in the basis of Java program. With an array, we also need to manipulate it. The most common operation of an array is traversal.

You can traverse the array through the for loop. Because each element of the array can be accessed through the index, the traversal of an array can be completed by using the standard for loop:

// Traversal array
public class Main {
    public static void main(String[] args) {
        int[] ns = { 1, 4, 9, 16, 25 };
        for (int i=0; i<ns.length; i++) {
            int n = ns[i];
            System.out.println(n);
        }
    }
}

In order to realize the for loop traversal, the initial condition is i=0, because the index always starts from 0, and the condition for continuing the loop is i < ns Length, because when i = ns Length, i has exceeded the index range (the index range is 0 ~ ns.length-1). After each cycle, i + +.

The second way is to use the for each loop to directly iterate over each element of the array:

// Traversal array
public class Main {
    public static void main(String[] args) {
        int[] ns = { 1, 4, 9, 16, 25 };
        for (int n : ns) {
            System.out.println(n);
        }
    }
}

Note: in the for (int n: ns) loop, the variable n gets the elements of the ns array directly, not the index.

Obviously, the for each loop is more concise. However, the for each loop cannot get the index of the array. Therefore, which for loop to use depends on our needs.

Print array contents

Print the array variable directly to get the reference address of the array in the JVM:

int[] ns = { 1, 1, 2, 3, 5, 8 };
System.out.println(ns); // Similar[ I@7852e922

This doesn't make sense because we want to print the element contents of the array. Therefore, use the for each loop to print it:

int[] ns = { 1, 1, 2, 3, 5, 8 };for (int n : ns) {    System.out.print(n + ", ");}

Using for each loop printing is also troublesome. Fortunately, the Java standard library provides arrays Tostring(), which can quickly print the contents of the array:

int[] ns = { 1, 1, 2, 3, 5, 8 };
for (int n : ns) {
    System.out.print(n + ", ");
}

Summary

The for loop can be used to traverse the array. The for loop can access the array index. The for each loop directly iterates over each array element, but cannot obtain the index;

Use arrays Tostring() can quickly get the contents of the array.

Array sorting

Sorting arrays is a very basic requirement in the program. The commonly used sorting algorithms include bubble sort, insert sort and quick sort.

Let's take a look at how to use the bubble sorting algorithm to sort an integer array from small to large:

// Bubble sorting
import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        int[] ns = { 28, 12, 89, 73, 65, 18, 96, 50, 8, 36 };
        // Before sorting:
        System.out.println(Arrays.toString(ns));
        for (int i = 0; i < ns.length - 1; i++) {
            for (int j = 0; j < ns.length - i - 1; j++) {
                if (ns[j] > ns[j+1]) {
                    // Exchange ns[j] and ns[j+1]:
                    int tmp = ns[j];
                    ns[j] = ns[j+1];
                    ns[j+1] = tmp;
                }
            }
        }
        // After sorting:
        System.out.println(Arrays.toString(ns));
    }
}

The characteristic of bubble sorting is that after each cycle, the largest number is exchanged to the end. Therefore, the last number can be "shaved" in the next cycle, and each cycle is one higher than the end of the previous cycle.

//Insert sort
public class TestInsertSort {
    public static void main(String[] args) {
        int arr[]={7,5,3,2,4};
        
        for (int i = 1; i < arr.length; i++) {
            //Outer cycle, compare from the second
            for (int j = i; j > 0; j--) {
                //The inner loop compares with the data in the front row. If the data in the back is smaller than the data in the front, it will be exchanged
                if(arr[j]<arr[j-1]){
                    int temp=arr[j-1];
                    arr[j-1]=arr[j];
                    arr[j]=temp;
                }else{
                    break;
                }
            }
            System.out.println(Arrays.toString(arr));
        }
    }
}
//Select sort
public class TestSelectSort {
    public static void main(String[] args) {
        int arr[]={5,6,3,2,4};
 
        for (int i = 0; i < arr.length-1; i++) {
            for (int j = i+1; j < arr.length; j++) {
                 if(arr[i]>arr[j]){
                     int temp=arr[i];
                     arr[i]=arr[j];
                     arr[j]=temp;
                }
            }
            System.out.println(Arrays.toString(arr));
        }
    }
}
//Hill sort (insert sort variant)
public class TestShellSort {
    public static void main(String[] args) {
        int arr[] = {7, 5, 3, 2, 4};

        for (int i = arr.length / 2; i > 0; i /= 2) {
            //Layer i cyclic control step
            for (int j = i; j < arr.length; j++) {
                //j controls the starting position of the unordered end
                for (int k = j; k > 0  && k - i >= 0; k -= i) {
                    if (arr[k] < arr[k - i]) {
                        int temp = arr[k - i];
                        arr[k - i] = arr[k];
                        arr[k] = temp;
                    } else {
                        break;
                    }
                }
            }
        }
        System.out.println(Arrays.toString(arr));
    }
}

In addition, note that exchanging the values of two variables must rely on a temporary variable. It's wrong to write like this:

int x = 1;int y = 2;x = y; // X is now 2Y = x// Y is still 2

The correct wording is:

int x = 1;int y = 2;int t = x; // Save the value of X in the temporary variable t, which is now 1x = y// X is now 2Y = t// Y is now the value 1 of T

In fact, the standard library of Java has built-in sorting function, and we only need to call arrays. XML provided by JDK Sort() to sort:

// sort
import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        int[] ns = { 28, 12, 89, 73, 65, 18, 96, 50, 8, 36 };
        Arrays.sort(ns);
        System.out.println(Arrays.toString(ns));
    }
}

It must be noted that sorting an array actually modifies the array itself. For example, the array before sorting is:

int[] ns = { 9, 3, 6, 5 };

In memory, this integer array is represented as follows:

      ┌───┬───┬───┬───┐
ns───>│ 9 │ 3 │ 6 │ 5 │
      └───┴───┴───┴───┘

When we call arrays sort(ns); After, the integer array becomes:

      ┌───┬───┬───┬───┐
ns───>│ 3 │ 5 │ 6 │ 9 │
      └───┴───┴───┴───┘

That is, the contents of the array pointed to by the variable ns have been changed.

If you sort an array of strings, for example:

String[] ns = { "banana", "apple", "pear" };

Before sorting, this array is represented in memory as follows:

                   ┌──────────────────────────────────┐
               ┌───┼──────────────────────┐           │
               │   │                      ▼           ▼
         ┌───┬─┴─┬─┴─┬───┬────────┬───┬───────┬───┬──────┬───┐
ns ─────>│░░░│░░░│░░░│   │"banana"│   │"apple"│   │"pear"│   │
         └─┬─┴───┴───┴───┴────────┴───┴───────┴───┴──────┴───┘
           │                 ▲
           └─────────────────┘

Call arrays sort(ns); After sorting, the array is represented in memory as follows:

                   ┌──────────────────────────────────┐
               ┌───┼──────────┐                       │
               │   │          ▼                       ▼
         ┌───┬─┴─┬─┴─┬───┬────────┬───┬───────┬───┬──────┬───┐
ns ─────>│░░░│░░░│░░░│   │"banana"│   │"apple"│   │"pear"│   │
         └─┬─┴───┴───┴───┴────────┴───┴───────┴───┴──────┴───┘
           │                              ▲
           └──────────────────────────────┘

The original three strings have not changed in memory, but the point of each element of the ns array has changed.

Summary

The commonly used sorting algorithms include bubble sorting, insertion sorting and quick sorting;

Bubble sorting uses two-layer for loop to realize sorting;

Exchanging the values of two variables requires the aid of a temporary variable.

You can directly use arrays. XML provided by the Java standard library Sort();

Sorting an array directly modifies the array itself.

Multidimensional array

Two dimensional array

A two-dimensional array is an array of arrays. Define a two-dimensional array as follows:

// Two dimensional array
public class Main {
    public static void main(String[] args) {
        int[][] ns = {
            { 1, 2, 3, 4 },
            { 5, 6, 7, 8 },
            { 9, 10, 11, 12 }
        };
        System.out.println(ns.length); // 3
    }
}

Because ns contains three arrays, NS Length is 3. In fact, the structure of NS in memory is as follows:

                    ┌───┬───┬───┬───┐
         ┌───┐  ┌──>│ 1 │ 2 │ 3 │ 4 │
ns ─────>│░░░│──┘   └───┴───┴───┴───┘
         ├───┤      ┌───┬───┬───┬───┐
         │░░░│─────>│ 5 │ 6 │ 7 │ 8 │
         ├───┤      └───┴───┴───┴───┘
         │░░░│──┐   ┌───┬───┬───┬───┐
         └───┘  └──>│ 9 │10 │11 │12 │
                    └───┴───┴───┴───┘

If we define an ordinary array arr0 and assign ns[0] to it:

// Two dimensional array
public class Main {
    public static void main(String[] args) {
        int[][] ns = {
            { 1, 2, 3, 4 },
            { 5, 6, 7, 8 },
            { 9, 10, 11, 12 }
        };
        int[] arr0 = ns[0];
        System.out.println(arr0.length); // 4
    }
}

In fact, arr0 gets the 0th element of the ns array. Because each element of the ns array is also an array, the array pointed to by arr0 is {1, 2, 3, 4}. In memory, the structure is as follows:

            arr0 ─────┐
                      ▼
                    ┌───┬───┬───┬───┐
         ┌───┐  ┌──>│ 1 │ 2 │ 3 │ 4 │
ns ─────>│░░░│──┘   └───┴───┴───┴───┘
         ├───┤      ┌───┬───┬───┬───┐
         │░░░│─────>│ 5 │ 6 │ 7 │ 8 │
         ├───┤      └───┴───┴───┴───┘
         │░░░│──┐   ┌───┬───┬───┬───┐
         └───┘  └──>│ 9 │10 │11 │12 │
                    └───┴───┴───┴───┘

array[row][col] is required to access an element of a two-dimensional array, for example:

System.out.println(ns[1][2]); // 7

The length of each array element of a two-dimensional array is not required to be the same. For example, an ns array can be defined as follows:

int[][] ns = {    { 1, 2, 3, 4 },    { 5, 6 },    { 7, 8, 9 }};

The structure of this two-dimensional array in memory is as follows:

                    ┌───┬───┬───┬───┐
         ┌───┐  ┌──>│ 1 │ 2 │ 3 │ 4 │
ns ─────>│░░░│──┘   └───┴───┴───┴───┘
         ├───┤      ┌───┬───┐
         │░░░│─────>│ 5 │ 6 │
         ├───┤      └───┴───┘
         │░░░│──┐   ┌───┬───┬───┐
         └───┘  └──>│ 7 │ 8 │ 9 │
                    └───┴───┴───┘

To print a two-dimensional array, you can use a two-tier nested for loop:

for (int[] arr : ns) {
    for (int n : arr) {
        System.out.print(n);
        System.out.print(', ');
    }
    System.out.println();
}

Or use arrays. Net of the Java standard library deepToString():

// Two dimensional array
import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        int[][] ns = {
            { 1, 2, 3, 4 },
            { 5, 6, 7, 8 },
            { 9, 10, 11, 12 }
        };
        System.out.println(Arrays.deepToString(ns));
    }
}

3D array

A three-dimensional array is an array of two-dimensional arrays. You can define a three-dimensional array as follows:

int[][][] ns = {
    {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    },
    {
        {10, 11},
        {12, 13}
    },
    {
        {14, 15, 16},
        {17, 18}
    }
};

Its structure in memory is as follows:

                              ┌───┬───┬───┐
                   ┌───┐  ┌──>│ 1 │ 2 │ 3 │
               ┌──>│░░░│──┘   └───┴───┴───┘
               │   ├───┤      ┌───┬───┬───┐
               │   │░░░│─────>│ 4 │ 5 │ 6 │
               │   ├───┤      └───┴───┴───┘
               │   │░░░│──┐   ┌───┬───┬───┐
        ┌───┐  │   └───┘  └──>│ 7 │ 8 │ 9 │
ns ────>│░░░│──┘              └───┴───┴───┘
        ├───┤      ┌───┐      ┌───┬───┐
        │░░░│─────>│░░░│─────>│10 │11 │
        ├───┤      ├───┤      └───┴───┘
        │░░░│──┐   │░░░│──┐   ┌───┬───┐
        └───┘  │   └───┘  └──>│12 │13 │
               │              └───┴───┘
               │   ┌───┐      ┌───┬───┬───┐
               └──>│░░░│─────>│14 │15 │16 │
                   ├───┤      └───┴───┴───┘
                   │░░░│──┐   ┌───┬───┐
                   └───┘  └──>│17 │18 │
                              └───┴───┘

If we want to access an element of the three-dimensional array, for example, ns[2][0][1], we only need to find the corresponding final element 15 along the positioning.

In theory, we can define any N-dimensional array. However, in practical applications, in addition to two-dimensional arrays, higher dimensional arrays are rarely used.

Summary

A two-dimensional array is an array of arrays, and a three-dimensional array is an array of two-dimensional arrays;

The length of each array element of multidimensional array is not required to be the same;

To print multidimensional arrays, you can use arrays deepToString();

The most common multidimensional array is a two-dimensional array. array[row][col] is used to access an element of a two-dimensional array.

Command line parameters

The entry of Java program is the main method, and the main method can accept a command line parameter, which is a String [] array.

This command line parameter is received by the JVM from the user and passed to the main method:

public class Main {
    public static void main(String[] args) {
        for (String arg : args) {
            System.out.println(arg);
        }
    }
}

We can use the received command line parameters to execute different codes according to different parameters. For example, implement a - version parameter to print the program version number:

public class Main {
    public static void main(String[] args) {
        for (String arg : args) {
            if ("-version".equals(arg)) {
                System.out.println("v 1.0");
                break;
            }
        }
    }
}

The above program must be executed on the command line. Let's compile it first:

$ javac Main.java

Then, when executing, pass it a - version parameter:

$ java Main -versionv 1.0

In this way, the program can make different responses according to the incoming command-line parameters.

Summary

The command line parameter type is String [] array;

Command line parameters are received by the JVM from the user and passed to the main method;

How to parse command line parameters needs to be implemented by the program itself.

object-oriented programming

Java is an Object-Oriented Programming language. Object oriented programming, English is object oriented programming, referred to as OOP.

So what is object-oriented programming?

Different from object-oriented programming, it is process oriented programming. Process oriented programming is to decompose the model into a step-by-step process. For example, the boss told you that to write a TODO task, you must follow the following steps step by step:

  1. Read the file;
  2. Prepare TODO;
  3. Save the file.

In object-oriented programming, as the name suggests, you must first have an object:

Once you have an object, you can interact with it:

GirlFriend gf = new GirlFriend();
gf.name = "Alice";gf.send("flowers");

Therefore, object-oriented programming is a programming method that maps the real world to the computer model by means of objects.

In this chapter, we will discuss:

The basic concepts of object-oriented include:

  • class
  • example
  • method

Object oriented implementation methods include:

  • inherit
  • polymorphic

The mechanisms provided by the Java language itself include:

  • package
  • classpath
  • jar

And the core classes provided by the Java standard library, including:

  • character string
  • Packaging type
  • JavaBean
  • enumeration
  • Common tools

Through the study of this chapter, you can fully understand and master the basic idea of object-oriented, but there is no guarantee that you can find objects.

method

A class can contain multiple fields. For example, we define two fields for the Person class:

class Person {
    public String name;
    public int age;
}

However, directly exposing the field to the outside with public may destroy the encapsulation. For example, the code can be written as follows:

Person ming = new Person();
ming.name = "Xiao Ming";
ming.age = -99; // age is set to a negative number 

Obviously, direct operation of field is easy to cause logical confusion. In order to avoid external code accessing the field directly, we can modify the field with private and refuse external access:

class Person {
    private String name;
    private int age;
}

Try the effect of private modified field:

// private field
public class Main {
    public static void main(String[] args) {
        Person ming = new Person();
        ming.name = "Xiao Ming"; // Assign a value to the field name
        ming.age = 12; // Assign value to field age
    }
}

class Person {
    private String name;
    private int age;
}

Is there a compilation error? The assignment statement accessing the field can be compiled normally.

Change the field from public to private. External code cannot access these fields. What's the use of defining these fields? How can I assign a value to it? How can I read its value?

Therefore, we need to use the method to allow external code to modify the field indirectly:

// private field
public class Main {
    public static void main(String[] args) {
        Person ming = new Person();
        ming.setName("Xiao Ming"); // Set name
        ming.setAge(12); // Set age
        System.out.println(ming.getName() + ", " + ming.getAge());
    }
}

class Person {
    private String name;
    private int age;

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return this.age;
    }

    public void setAge(int age) {
        if (age < 0 || age > 100) {
            throw new IllegalArgumentException("invalid age value");
        }
        this.age = age;
    }
}

Although external code cannot modify the private field directly, external code can call methods setName() and setAge() to modify the private field indirectly. Inside the method, we have the opportunity to check whether the parameters are correct. For example, setAge() will check the parameters passed in. If the parameters exceed the range, an error will be reported directly. In this way, external code has no chance to set age to an unreasonable value.

The setName() method can also be checked. For example, null and empty strings are not allowed to be passed in:

public void setName(String name) {
    if (name == null || name.isBlank()) {
        throw new IllegalArgumentException("invalid name");
    }
    this.name = name.strip(); // Remove the leading and trailing spaces
}

Similarly, external code cannot directly read the private field, but can indirectly obtain the value of the private field through getName() and getAge().

Therefore, a class can expose some operation interfaces to external code by defining methods, and ensure logical consistency internally.

The syntax for calling a method is an instance variable Method name (parameter);. A method call is a statement, so don't forget to add; at the end;. For example: Ming setName("Xiao Ming");.

Definition method

As can be seen from the above code, the syntax for defining methods is:

Modifier method return type method name(Method parameter list) {
    Several method statements;
    return Method return value;
}

The return value of the method is realized through the return statement. If there is no return value and the return type is set to void, return can be omitted.

private method

If there is a public method, there is naturally a private method. Like the private field, the private method does not allow external calls. What's the use of defining the private method?

The reason for defining private methods is that internal methods can call private methods. For example:

// private method
public class Main {
    public static void main(String[] args) {
        Person ming = new Person();
        ming.setBirth(2008);
        System.out.println(ming.getAge());
    }
}

class Person {
    private String name;
    private int birth;

    public void setBirth(int birth) {
        this.birth = birth;
    }

    public int getAge() {
        return calcAge(2019); // Call the private method
    }

    // private method:
    private int calcAge(int currentYear) {
        return currentYear - this.birth;
    }
}

Looking at the above code, calcAge() is a private method, which cannot be called by external code, but can be called by internal method getAge().

In addition, we also note that this Person class only defines the birth field and does not define the age field. When obtaining age, the method getAge() returns a real-time calculated value, not the value stored in a field. This shows that the method can encapsulate the external interface of a class, and the caller does not need to know or care whether the Person instance has an age field internally.

this variable

Inside the method, you can use an implicit variable this, which always points to the current instance. Therefore, through this Field to access the fields of the current instance.

If there is no naming conflict, you can omit this. For example:

class Person {
    private String name;

    public String getName() {
        return name; // Equivalent to this name
    }
}

However, if the local variable and field have the same name, the local variable has higher priority, and this must be added:

class Person {
    private String name;

    public void setName(String name) {
        this.name = name; // The previous this is indispensable. If it is missing, it will become the local variable name
    }
}

Method parameters

Method can contain 0 or any parameters. Method parameters are used to receive the value of the variable passed to the method. When calling a method, it must be passed one by one in strict accordance with the definition of parameters. For example:

class Person {
    ...
    public void setNameAndAge(String name, int age) {
        ...
    }
}

When calling this setNameAndAge() method, there must be two parameters, and the first parameter must be String and the second parameter must be int:

Person ming = new Person();
ming.setNameAndAge("Xiao Ming"); // Compilation error: wrong number of parameters
ming.setNameAndAge(12, "Xiao Ming"); // Compilation error: wrong parameter type

Variable parameters

Variable parameter type Definition, variable parameters are equivalent to array types:

class Group {
    private String[] names;

    public void setNames(String... names) {
        this.names = names;
    }
}

setNames() above defines a variable parameter. When called, it can be written as follows:

Group g = new Group();
g.setNames("Xiao Ming", "Xiao Hong", "Xiao Jun"); // Pass in 3 strings
g.setNames("Xiao Ming", "Xiao Hong"); // Pass in 2 strings
g.setNames("Xiao Ming"); // Pass in 1 String
g.setNames(); // 0 strings passed in

Variable parameters can be rewritten to String [] type:

class Group {
    private String[] names;

    public void setNames(String[] names) {
        this.names = names;
    }
}

However, the caller needs to construct String [], which is troublesome. For example:

Group g = new Group();
g.setNames(new String[] {"Xiao Ming", "Xiao Hong", "Xiao Jun"}); // Pass in 1 String []

Another problem is that the caller can pass in null:

Group g = new Group();
g.setNames(null);

Variable parameters can ensure that null cannot be passed in, because when 0 parameters are passed in, the actual value received is an empty array rather than null.

Parameter binding

When the caller passes the parameters to the instance method, the values passed during the call will be bound one by one according to the parameter position.

What is parameter binding?

Let's first observe the transfer of a basic type parameter:

// Basic type parameter binding
public class Main {
    public static void main(String[] args) {
        Person p = new Person();
        int n = 15; // The value of n is 15
        p.setAge(n); // Value passed in n
        System.out.println(p.getAge()); // 15
        n = 20; // Change the value of n to 20
        System.out.println(p.getAge()); // 15 or 20?
    }
}

class Person {
    private int age;

    public int getAge() {
        return this.age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

Run the code. It can be seen from the results that modifying the external local variable n does not affect the age field of instance P. the reason is that the parameters obtained by setAge() method copy the value of N. therefore, p.age and local variable n do not affect each other.

Conclusion: the transfer of basic type parameters is a copy of the caller's value. Subsequent amendments made by both parties shall not affect each other.

Let's take another example of passing reference parameters:

// Reference type parameter binding
public class Main {
    public static void main(String[] args) {
        Person p = new Person();
        String[] fullname = new String[] { "Homer", "Simpson" };
        p.setName(fullname); // Pass in fullname array
        System.out.println(p.getName()); // "Homer Simpson"
        fullname[0] = "Bart"; // Change the first element of the fullname array to "Bart"
        System.out.println(p.getName()); // "Homer Simpson" or "Bart Simpson"?
    }
}

class Person {
    private String[] name;

    public String getName() {
        return this.name[0] + " " + this.name[1];
    }

    public void setName(String[] name) {
        this.name = name;
    }
}

Notice that the parameter to setName() is now an array. At first, pass in the fullname array, and then modify the contents of the fullname array. It is found that the field p.name of instance P has also been modified!

Conclusion: the transfer of reference type parameters, the variables of the caller and the parameter variables of the receiver point to the same object. Any modification of this object by either party will affect the other party (because it points to the same object).

With the above conclusion, let's take another example:

// Reference type parameter binding
public class Main {
    public static void main(String[] args) {
        Person p = new Person();
        String bob = "Bob";
        p.setName(bob); // Pass in bob variable
        System.out.println(p.getName()); // "Bob"
        bob = "Alice"; // bob changed his name to Alice
        System.out.println(p.getName()); // "Bob" or "Alice"?
    }
}

class Person {
    private String name;

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Summary

  • Method allows external code to safely access instance fields;
  • A method is a set of execution statements and can execute any logic;
  • Return when return is encountered inside the method. void means that no value is returned (note that it is different from returning null);
  • The external code operates the instance through the public method, and the internal code can call the private method;
  • Understand the parameter binding of methods.

Construction method

When creating an instance, we often need to initialize the fields of the instance at the same time, for example:

Person ming = new Person();
ming.setName("Xiao Ming");
ming.setAge(12);

It takes three lines of code to initialize the object instance. Moreover, if you forget to call setName() or setAge(), the internal state of the instance is incorrect.

Can all internal fields be initialized to appropriate values when creating an object instance?

Absolutely.

At this time, we need to construct methods.

When you create an instance, you actually initialize the instance by constructing a method. Let's first define a construction method. When creating a Person instance, we can pass in name and age at one time to complete initialization:

// Construction method
public class Main {
    public static void main(String[] args) {
        Person p = new Person("Xiao Ming", 15);
        System.out.println(p.getName());
        System.out.println(p.getAge());
    }
}

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public String getName() {
        return this.name;
    }

    public int getAge() {
        return this.age;
    }
}

Because the constructor is so special, the name of the constructor is the class name. There are no restrictions on the parameters of the construction method. Any statement can also be written inside the method. However, compared with ordinary methods, the constructor has no return value (nor void). To call the constructor, you must use the new operator.

Default construction method

Does any class have a constructor? yes.

We didn't write a constructor for the Person class. Why can we call new Person()?

The reason is that if a class does not define a construction method, the compiler will automatically generate a default construction method for us. It has no parameters and no execution statements, like this:

class Person {   
  public Person() {    }
 }

It should be noted that if we customize a constructor, the compiler will no longer automatically create the default constructor:

// Construction method 
public class Main {
    public static void main(String[] args) {
        Person p = new Person(); // Compilation error: this constructor was not found
    }
}

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public String getName() {
        return this.name;
    }

    public int getAge() {
        return this.age;
    }
}

If you want to use the construction method with parameters and keep the construction method without parameters, you can only define both construction methods:

// Construction method
public class Main {
    public static void main(String[] args) {
        Person p1 = new Person("Xiao Ming", 15); // You can call the constructor with parameters
        Person p2 = new Person(); // You can also call a parameterless constructor
    }
}

class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public String getName() {
        return this.name;
    }

    public int getAge() {
        return this.age;
    }
}

When the field is not initialized in the constructor, the field of reference type is null by default, the field of numeric type uses the default value, the default value of int type is 0, and the default value of boolean type is false:

class Person {
    private String name; // Default initialization is null
    private int age; // Default initialization is 0

    public Person() {
    }
}

You can also initialize fields directly:

class Person {
    private String name = "Unamed";
    private int age = 10;
}

Then the problem arises: both initialize the field and initialize the field in the construction method:

class Person {
    private String name = "Unamed";
    private int age = 10;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

When we create an object, what is the initial value of the object instance and field obtained by new Person("Xiao Ming", 12)?

In Java, when creating an object instance, initialize it in the following order:

  1. Initialize the field first, for example, int age = 10; Indicates that the field is initialized to 10, double salary; Indicates that the field is initialized to 0 by default, String name; Indicates that the reference type field is initialized to null by default;
  2. The code that executes the construction method is initialized.

Therefore, since the code of the construction method runs later, the field value of new Person("Xiao Ming", 12) is finally determined by the code of the construction method.

Multi construction method

Multiple construction methods can be defined. When called through the new operator, the compiler will automatically distinguish by the number, position and type of parameters of the construction method:

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Person(String name) {
        this.name = name;
        this.age = 12;
    }

    public Person() {
    }
}

If you call new Person("Xiao Ming", 20);, It will be automatically matched to the constructor public Person(String, int).

If you call new Person("Xiao Ming");, It will be automatically matched to the constructor public Person(String).

If you call new Person();, It will be automatically matched to the constructor public Person().

A constructor can call other constructors. The purpose of this is to facilitate code reuse. The syntax for calling other constructor methods is this(...):

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Person(String name) {
        this(name, 18); // Call another constructor Person(String, int)
    }

    public Person() {
        this("Unnamed"); // Call another constructor Person(String)
    }
}

Summary

When an instance is created, its corresponding construction method will be called through the new operator. The construction method is used to initialize the instance;

If no construction method is defined, the compiler will automatically create a default parameterless construction method;

Multiple construction methods can be defined, and the compiler can automatically judge according to the parameters;

You can call another constructor inside one constructor to facilitate code reuse.

Method overloading

In a class, we can define multiple methods. If there are a series of methods with similar functions and different parameters, this group of method names can be made into methods with the same name. For example, in the Hello class, define multiple hello() methods:

class Hello {
    public void hello() {
        System.out.println("Hello, world!");
    }

    public void hello(String name) {
        System.out.println("Hello, " + name + "!");
    }

    public void hello(String name, int age) {
        if (age < 18) {
            System.out.println("Hi, " + name + "!");
        } else {
            System.out.println("Hello, " + name + "!");
        }
    }
}

This method has the same name but different parameters. It is called method Overload.

Note: the return value types of method overloads are usually the same.

The purpose of method overloading is that methods with similar functions use the same name, which is easier to remember, so it is easier to call.

For example, the String class provides multiple overloaded methods indexOf(), which can find substrings:

  • int indexOf(int ch): search according to the Unicode code of the character;
  • int indexOf(String str): search by string;
  • int indexOf(int ch, int fromIndex): search according to characters, but specify the starting position;
  • int indexOf(String str, int fromIndex) looks up according to the string, but specifies the starting position.

have a try:

// String.indexOf()
public class Main {
    public static void main(String[] args) {
        String s = "Test string";
        int n1 = s.indexOf('t');
        int n2 = s.indexOf("st");
        int n3 = s.indexOf("st", 4);
        System.out.println(n1);
        System.out.println(n2);
        System.out.println(n3);
    }
}

Summary

Method overloading means that multiple methods have the same method name but different parameters;

Overloaded methods should perform similar functions. Refer to indexOf() of String;

The return value type of overloaded method should be the same.

inherit

In the previous chapter, we have defined the Person class:

class Person {
    private String name;
    private int age;

    public String getName() {...}
    public void setName(String name) {...}
    public int getAge() {...}
    public void setAge(int age) {...}
}

Now, suppose you need to define a Student class, and the fields are as follows:

class Student {
    private String name;
    private int age;
    private int score;

    public String getName() {...}
    public void setName(String name) {...}
    public int getAge() {...}
    public void setAge(int age) {...}
    public int getScore() { ... }
    public void setScore(int score) { ... }
}

After careful observation, it is found that the Student class contains the existing fields and methods of the Person class, but there is an additional score field and the corresponding getScore() and setScore() methods.

Can you not write duplicate code in Student?

At this time, inheritance comes in handy.

Inheritance is a very powerful mechanism in object-oriented programming. It can reuse code first. When we let students inherit from Person, students will get all the functions of Person. We only need to write new functions for students.

Java uses the extends keyword to implement inheritance:

class Person {
    private String name;
    private int age;

    public String getName() {...}
    public void setName(String name) {...}
    public int getAge() {...}
    public void setAge(int age) {...}
}

class Student extends Person {
    // Do not repeat the name and age fields / methods,
    // You only need to define the new score field / method:
    private int score;

    public int getScore() { ... }
    public void setScore(int score) { ... }
}

It can be seen that through inheritance, students only need to write additional functions and do not need to repeat code.

Note: the subclass automatically obtains all fields of the parent class. It is strictly prohibited to define fields with the same name as the parent class!

In OOP terms, we call Person super class, parent class, base class, and Student subclass and extended class.

In OOP terms, we call Person super class, parent class, base class, and Student subclass and extended class.

Inheritance tree

Notice that we didn't write extends when defining Person. In Java, if you do not explicitly write the extends class, the compiler will automatically add the extends Object. Therefore, any class, except Object, will inherit from a class. The following figure shows the inheritance tree of Person and Student:

┌───────────┐
│  Object   │
└───────────┘
      ▲
      │
┌───────────┐
│  Person   │
└───────────┘
      ▲
      │
┌───────────┐
│  Student  │
└───────────┘

Java only allows a class to inherit from a class, so a class has and only has a parent class. Only Object is special. It has no parent class.

Similarly, if we define a Teacher inherited from Person, their inheritance tree relationship is as follows:

       ┌───────────┐
       │  Object   │
       └───────────┘
             ▲
             │
       ┌───────────┐
       │  Person   │
       └───────────┘
          ▲     ▲
          │     │
          │     │
┌───────────┐ ┌───────────┐
│  Student  │ │  Teacher  │
└───────────┘ └───────────┘

protected

Inheritance has a feature that subclasses cannot access the private field or private method of the parent class. For example, the Student class cannot access the name and age fields of the Person class:

class Person {
    private String name;
    private int age;
}

class Student extends Person {
    public String hello() {
        return "Hello, " + name; // Compilation error: unable to access the name field
    }
}

This weakens the role of inheritance. In order for the subclass to access the fields of the parent class, we need to change private to protected. Fields decorated with protected can be accessed by subclasses:

class Person {
    protected String name;
    protected int age;
}

class Student extends Person {
    public String hello() {
        return "Hello, " + name; // OK!
    }
}

Therefore, the protected keyword can control the access rights of fields and methods within the inheritance tree. A protected field and method can be accessed by its subclasses and subclasses, which will be explained in detail later.

super

Super keyword indicates a parent class (superclass). When a subclass references a field of a parent class, you can use super fieldName. For example:

class Student extends Person {
    public String hello() {
        return "Hello, " + super.name;
    }
}

In fact, super is used here Name, or this Name, or name, has the same effect. The compiler automatically navigates to the name field of the parent class.

However, in some cases, super must be used. Let's take an example:

//super
public class Main {
    public static void main(String[] args) {
        Student s = new Student("Xiao Ming", 12, 89);
    }
}

class Person {
    protected String name;
    protected int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

class Student extends Person {
    protected int score;

    public Student(String name, int age, int score) {
        this.score = score;
    }
}

Running the above code will get a compilation error to the effect that the constructor of Person cannot be called in the constructor of Student.

This is because in Java, the first line of any class constructor must call the constructor of the parent class. If the constructor of the parent class is not explicitly called, the compiler will automatically add a super();, Therefore, the construction method of Student class is actually as follows:

class Student extends Person {
    protected int score;

    public Student(String name, int age, int score) {
        super(); // Automatically call the constructor of the parent class
        this.score = score;
    }
}

However, the Person class does not have a parameterless constructor, so compilation fails.

The solution is to call a constructor that exists in the Person class. For example:

class Student extends Person {
    protected int score;

    public Student(String name, int age, int score) {
        super(name, age); // Call the constructor Person(String, int) of the parent class
        this.score = score;
    }
}

So you can compile normally!

Therefore, we conclude that if the parent class does not have a default constructor, the child class must explicitly call super() and give parameters so that the compiler can locate an appropriate constructor of the parent class.

This also leads to another problem: that is, the subclass will not inherit the constructor of any parent class. The default construction method of subclasses is automatically generated by the compiler, not inherited.

Block inheritance

Under normal circumstances, as long as a class has no final modifier, any class can inherit from that class.

Starting from Java 15, it is allowed to modify a class with sealed, and explicitly write out the subclass name that can be inherited from the class through permissions.

For example, define a Shape class:

public sealed class Shape permits Rect, Circle, Triangle {    ...}

The above Shape class is a sealed class, which only allows the specified three classes to inherit it. If write:

public final class Rect extends Shape {...}

No problem, because Rect appears in the permission list of Shape. However, if you define an Ellipse, an error will be reported:

public final class Ellipse extends Shape {...}// Compile error: class is not allowed to extend sealed class: Shape

The reason is that Ellipse does not appear in the permission list of Shape. This sealed class is mainly used in some frameworks to prevent inheritance from being abused.

The sealed class is currently in preview status in Java 15. To enable it, you must use the parameters -- enable preview and -- source 15.

Upward transformation

If the type of a reference variable is Student, it can point to an instance of Student type:

Student s = new Student();

If a variable of reference type is Person, it can point to an instance of Person type:

Person p = new Person();

Now the question arises: if Student inherits from Person, can a variable with reference type of Person point to an instance of Student type?

Person p = new Student(); // ???

Test it and you can find that this direction is allowed!

This is because Student inherits from Person, so it has all the functions of Person. If a variable of Person type points to an instance of Student type, there is no problem operating on it!

This assignment that safely changes a subclass type to a parent type is called upcasting.

The upward transformation is actually to safely change a subtype into a more abstract parent type:

Student s = new Student();Person p = s; // upcasting, okObject o1 = p; // upcasting, okObject o2 = s; // upcasting, ok

Note that the inheritance tree is Student > Person > Object, so you can convert the Student type to Person or higher-level Object.

Downward transformation

Contrary to upward transformation, if a parent type is forcibly transformed into a child type, it is downward casting. For example:

Person p1 = new Student(); // upcasting, okPerson p2 = new Person();Student s1 = (Student) p1; // okStudent s2 = (Student) p2; // runtime error! ClassCastException!

If you test the above code, you can find:

Person type p1 actually points to the Student instance, and person type variable p2 actually points to the person instance. During the downward transformation, the transformation from p1 to Student will succeed, because p1 does point to the Student instance, and the transformation from p2 to Student will fail, because the actual type of p2 is person, and the parent class cannot be changed into a child class, because the child class has more functions than the parent class, and more functions cannot be changed out of thin air.

Therefore, the downward transformation is likely to fail. In case of failure, the Java virtual opportunity reports ClassCastException.

In order to avoid downward transformation errors, Java provides the instanceof operator, which can first judge whether an instance is of a certain type:

Person p = new Person();System.out.println(p instanceof Person); // trueSystem.out.println(p instanceof Student); // falseStudent s = new Student();System.out.println(s instanceof Person); // trueSystem.out.println(s instanceof Student); // trueStudent n = null;System.out.println(n instanceof Student); // false

Instanceof actually determines whether the instance pointed to by a variable is a specified type or a subclass of this type. If a reference variable is null, the judgment of any instanceof is false.

Using instanceof, you can judge the following before the downward Transformation:

Person p = new Student();if (p instanceof Student) {    // Only when the judgment is successful will the transition be downward: Student s = (Student) p// Will succeed}

Starting from Java 14, after judging instanceof, it can be directly transformed into a specified variable to avoid forced transformation again. For example, for the following code:

Object obj = "hello";if (obj instanceof String) {    String s = (String) obj;    System.out.println(s.toUpperCase());}

It can be rewritten as follows:

// instanceof variable:
public class Main {
    public static void main(String[] args) {
        Object obj = "hello";
        if (obj instanceof String s) {
            // You can directly use the variable s:
            System.out.println(s.toUpperCase());
        }
    }
}

This way of using instanceof is more concise.

Distinguish between inheritance and composition

When using inheritance, we should pay attention to logical consistency.

Consider the following Book classes:

class Book {    protected String name;    public String getName() {...}    public void setName(String name) {...}}

This Book class also has a name field, so can we let students inherit from Book?

class Student extends Book {    protected int score;}

Obviously, logically speaking, this is unreasonable. Student s should not inherit from Book, but from Person.

The reason is that students are a kind of Person. They are an is relationship, and students are not books. In fact, the relationship between Student and Book is a has relationship.

With has relationship, inheritance should not be used, but combination should be used, that is, students can hold a Book instance:

class Student extends Person {   
 protected Book book;    
 protected int score;
 }

Therefore, inheritance is an is relationship and composition is an has relationship.

Summary

  • Inheritance is a powerful way of code reuse in object-oriented programming;
  • Java only allows single inheritance, and the final root class of all classes is Object;
  • protected allows subclasses to access fields and methods of the parent class;
  • The construction method of the subclass can call the construction method of the parent class through super();
  • Can safely transition up to more abstract types;
  • It can be forced downward transformation, and it is best to judge with the help of instanceof;
  • The relationship between subclass and parent class is is. The has relationship cannot be inherited.

polymorphic

In the inheritance relationship, if a subclass defines a method with exactly the same signature as the parent method, it is called Override.

For example, in the Person class, we define the run() method:

class Person {
    public void run() {
        System.out.println("Person.run");
    }
}

In the subclass Student, override the run() method:

class Student extends Person {
    @Override
    public void run() {
        System.out.println("Student.run");
    }
}

The difference between Override and Overload is that if the method signature is different, it is Overload, and the Overload method is a new method; If the method signature is the same and the return value is the same, it is Override.

Note: the same method name and method parameters, but different method return values are also different methods. In Java programs, if this happens, the compiler will report an error.

class Person {
    public void run() { ... }
}

class Student extends Person {
    // Not Override because the parameters are different:
    public void run(String s) { ... }
    // Not Override because the return value is different:
    public int run() { ... }
}

Adding @ Override allows the compiler to help check whether the correct Override has been made. You want to overwrite, but if you accidentally write the wrong method signature, the compiler will report an error.

// override
public class Main {
    public static void main(String[] args) {
    }
}

class Person {
    public void run() {}
}

public class Student extends Person {
    @Override // Compile error!
    public void run(String s) {}
}}

However, @ Override is not required.

In the previous section, we already know that the declared type of the reference variable may not match its actual type, for example:

Person p = new Student();

Now, let's consider a case where a subclass overrides the method of the parent class:

// override
public class Main {
    public static void main(String[] args) {
        Person p = new Student();
        p.run(); // You should print person Run or student run?
    }
}

class Person {
    public void run() {
        System.out.println("Person.run");
    }
}

class Student extends Person {
    @Override
    public void run() {
        System.out.println("Student.run");
    }
}

Then, a variable with the actual type of Student and the reference type of Person calls its run() method. Is it Person or Student's run() method?

Run the above code to know that the method actually called is the Student's run() method. Therefore, it can be concluded that:

Java instance method calls are dynamic calls based on the actual type at runtime, rather than the declared type of variables.

This very important feature is called polymorphism in object-oriented programming. Its English spelling is very complex: Polymorphic.

polymorphic

Polymorphism means that the method actually executed by a method call of a certain type depends on the method of the actual type at run time. For example:

Person p = new Student();
p.run(); // It is not possible to determine which run() method the runtime calls

Some children's shoes will ask. You can see from the above code that it must call the Student's run() method.

However, suppose we write such a method:

public void runTwice(Person p) {
    p.run();
    p.run();
}

The parameter type passed in is Person. We cannot know whether the actual type of the parameter passed in is Person, Student or other subclasses of Person. Therefore, we cannot determine whether the run() method defined by the Person class is called.

Therefore, the characteristic of polymorphism is that the runtime can dynamically determine the subclass methods to be called. When a method is called on a type, the actual method executed may be the override method of a subclass. What is the role of this uncertain method call?

Let's lift chestnuts.

Suppose we define an Income and need to declare tax for it, then first define an Income class:

class Income {
    protected double income;
    public double getTax() {
        return income * 0.1; // Tax rate 10%
    }
}

For salary Income, you can subtract a base, then we can derive SalaryIncome from Income and override getTax():

class Salary extends Income {
    @Override
    public double getTax() {
        if (income <= 5000) {
            return 0;
        }
        return (income - 5000) * 0.2;
    }
}

If you enjoy the special allowance of the State Council, you can be exempted from all taxes according to the regulations:

class StateCouncilSpecialAllowance extends Income {
    @Override
    public double getTax() {
        return 0;
    }
}

Now, we want to write a financial software for tax declaration, which can declare all one's income as follows:

public double totalTax(Income... incomes) {
    double total = 0;
    for (Income income: incomes) {
        total = total + income.getTax();
    }
    return total;
}

Let's try:

// Polymorphic
public class Main {
    public static void main(String[] args) {
        // Tax is calculated for a small partner with ordinary income, wage income and special allowance of the State Council:
        Income[] incomes = new Income[] {
            new Income(3000),
            new Salary(7500),
            new StateCouncilSpecialAllowance(15000)
        };
        System.out.println(totalTax(incomes));
    }

    public static double totalTax(Income... incomes) {
        double total = 0;
        for (Income income: incomes) {
            total = total + income.getTax();
        }
        return total;
    }
}

class Income {
    protected double income;

    public Income(double income) {
        this.income = income;
    }

    public double getTax() {
        return income * 0.1; // Tax rate 10%
    }
}

class Salary extends Income {
    public Salary(double income) {
        super(income);
    }

    @Override
    public double getTax() {
        if (income <= 5000) {
            return 0;
        }
        return (income - 5000) * 0.2;
    }
}

class StateCouncilSpecialAllowance extends Income {
    public StateCouncilSpecialAllowance(double income) {
        super(income);
    }

    @Override
    public double getTax() {
        return 0;
    }
}

Observe the totalTax() method: using polymorphism, the totalTax() method only needs to deal with Income. It does not need to know the existence of Salary and StateCouncil special allowance to correctly calculate the total tax. If we want to add a kind of royalty Income, we only need to derive from Income and correctly override the getTax() method. Pass the new type into totalTax(), without any code modification.

It can be seen that polymorphism has a very powerful function, that is, it allows to add more types of subclasses to realize function expansion without modifying the code based on the parent class.

Override Object method

Because all class es ultimately inherit from Object, and Object defines several important methods:

  • toString(): output instance as String;
  • equals(): judge whether two instance s are logically equal;
  • hashCode(): calculates the hash value of an instance.

If necessary, we can override these methods of Object. For example:

class Person {
    ...
    // Show more meaningful strings:
    @Override
    public String toString() {
        return "Person:name=" + name;
    }

    // Compare for equality:
    @Override
    public boolean equals(Object o) {
        // If and only if o is of type Person:
        if (o instanceof Person) {
            Person p = (Person) o;
            // And when the name field is the same, return true:
            return this.name.equals(p.name);
        }
        return false;
    }

    // Calculate hash:
    @Override
    public int hashCode() {
        return this.name.hashCode();
    }
}

Call super

In the overridden method of a subclass, if you want to call the overridden method of the parent class, you can call it through super. For example:

class Person {
    protected String name;
    public String hello() {
        return "Hello, " + name;
    }
}

Student extends Person {
    @Override
    public String hello() {
        // Call the hello() method of the parent class:
        return super.hello() + "!";
    }
}

final

Inheritance allows subclasses to Override the methods of the parent class. If a parent class does not allow subclasses to Override one of its methods, you can mark the method as final. Methods decorated with final cannot be overridden:

class Person {
    protected String name;
    public final String hello() {
        return "Hello, " + name;
    }
}

Student extends Person {
    // compile error: overwrite is not allowed
    @Override
    public String hello() {
    }
}

If a class does not want any other classes to inherit from it, you can mark the class itself as final. Classes decorated with final cannot be inherited:

final class Person {
    protected String name;
}

// compile error: inheritance from Person is not allowed
Student extends Person {
}

The instance field of a class can also be decorated with final. Fields decorated with final cannot be modified after initialization. For example:

cclass Person {
    public final String name = "Unamed";
}

An error will be reported if the final field is re assigned:

Person p = new Person();
p.name = "New Name"; // compile error!

The final field can be initialized in the constructor:

class Person {
    public final String name;
    public Person(String name) {
        this.name = name;
    }
}

This method is more commonly used because it can ensure that the final field of an instance cannot be modified once it is created.

Summary

  • A subclass can Override the method of the parent class, which changes the behavior of the parent method in the subclass;
  • Java method calls always act on the actual types of runtime objects. This behavior is called polymorphism;
  • The final modifier has several functions:
    • The method of final modification can prevent overwriting;
    • final modified class es can prevent inheritance;
    • The final decorated field must be initialized when the object is created and cannot be modified later.

abstract class

Due to polymorphism, each subclass can override the methods of the parent class, for example:

class Person {
    public void run() { ... }
}

class Student extends Person {
    @Override
    public void run() { ... }
}

class Teacher extends Person {
    @Override
    public void run() { ... }
}

Both Student and Teacher derived from the Person class can override the run() method.

If the run() method of the parent class Person has no practical significance, can the execution statement of the method be removed?

class Person {
    public void run(); // Compile Error!
}

The answer is no, which will lead to compilation errors, because when defining a method, the statement of the method must be implemented.

Can you remove the run() method of the parent class?

The answer is still no, because removing the run() method of the parent class will lose its polymorphism. For example, runTwice() cannot compile:

public void runTwice(Person p) {
    p.run(); // Person has no run() method, which will cause compilation errors
    p.run();
}

If the method of the parent class does not need to implement any function, but only to define the method signature so that the child class can override it, the method of the parent class can be declared as an abstract method:

class Person {
    public abstract void run();
}

Declaring a method as abstract means that it is an abstract method and does not implement any method statements. Because the abstract method itself cannot be executed, the Person class cannot be instantiated. The compiler will tell us that the Person class cannot be compiled because it contains abstract methods.

The Person class itself must also be declared abstract to compile it correctly:

abstract class Person {
    public abstract void run();
}

abstract class

If a class defines a method but does not specifically execute code, the method is an abstract method, which is decorated with abstract.

Because the abstract method cannot be executed, this class must also be declared as an abstract class.

A class decorated with abstract is an abstract class. We cannot instantiate an abstract class:

Person p = new Person(); // Compilation error

What is the use of abstract classes that cannot be instantiated?

Because the abstract class itself is designed to be inherited only, the abstract class can force its subclass to implement its defined abstract methods, otherwise the compilation will report an error. Therefore, the abstract method is actually equivalent to defining the "specification".

For example, if the Person class defines the abstract method run(), the run() method must be overridden when implementing the subclass Student:

// abstract class
public class Main {
    public static void main(String[] args) {
        Person p = new Student();
        p.run();
    }
}

abstract class Person {
    public abstract void run();
}

class Student extends Person {
    @Override
    public void run() {
        System.out.println("Student.run");
    }
}

Abstract oriented programming

When we define the abstract class Person and the specific Student and Teacher subclasses, we can refer to the specific subclass instances through the abstract class Person type:

Person s = new Student();
Person t = new Teacher();

The advantage of this kind of reference to abstract classes is that we make method calls on them and don't care about the specific subtypes of Person type variables:

// Do not care about the specific subtype of the Person variable:
s.run();
t.run();

If the same code refers to a new subclass, we still don't care about the specific type:

// Similarly, I don't care how the new subclass implements the run() method:
Person e = new Employee();
e.run();

This way of referring to high-level types as much as possible and avoiding referring to actual subtypes is called abstract oriented programming.

The essence of abstract oriented programming is:

  • The upper layer code only defines specifications (e.g. abstract class Person);
  • Business logic can be implemented without subclasses (normal compilation);
  • The specific business logic is implemented by different subclasses, and the caller does not care.

Summary

  • The method defined through abstract is an abstract method, which has only definition and no implementation. The abstract method defines the interface specification that the subclass must implement;
  • The class that defines the abstract method must be defined as an abstract class, and the subclass inherited from the abstract class must implement the abstract method;
  • If the abstract method is not implemented, the subclass is still an abstract class;
  • Abstract oriented programming makes the caller only care about the definition of abstract methods, not the specific implementation of subclasses.

Interface

In abstract classes, abstract methods essentially define interface specifications: that is, specify the interface of high-level classes, so as to ensure that all subclasses have the same interface implementation. In this way, polymorphism can exert its power.

If an abstract class has no fields, all methods are abstract methods:

abstract class Person {
    public abstract void run();
    public abstract String getName();
}

You can rewrite the abstract class as an interface: interface.

In Java, you can declare an interface by using interface:

interface Person {
    void run();
    String getName();
}

The so-called interface is a pure abstract interface more abstract than an abstract class, because it can't even have fields. Because all methods defined by the interface are public abstract by default, these two modifiers do not need to be written (the effect is the same whether they are written or not).

When a specific class implements an interface, you need to use the implements keyword. for instance:

class Student implements Person {

We know that in Java, a class can only inherit from another class, not from multiple classes. However, a class can implement multiple interface s, such as:

class Student implements Person, Hello { // Two interface s are implemented
    ...
}

term

Pay attention to distinguishing terms:

Java interface refers specifically to the definition of interface, which represents an interface type and a group of method signatures, while programming interface generally refers to interface specifications, such as method signature, data format, network protocol, etc.

The comparison between abstract classes and interfaces is as follows:

abstract classinterface
inheritOnly one class can be extendedMultiple interface s can be implemented
fieldYou can define instance fieldsInstance fields cannot be defined
Abstract methodAbstract methods can be definedAbstract methods can be defined
Non abstract methodNon abstract methods can be definedYou can define the default method

Interface inheritance

One interface can inherit from another interface. Interface inherits from interface and uses extensions, which is equivalent to extending the methods of the interface. For example:

interface Hello {
    void hello();
}

interface Person extends Hello {
    void run();
    String getName();
}

At this time, the Person interface inherits from the Hello interface. Therefore, the Person interface now actually has three abstract method signatures, one of which comes from the inherited Hello interface.

Inheritance relationship

Reasonably designing the inheritance relationship between interface and abstract class can fully reuse the code. Generally speaking, public logic is suitable to be placed in abstract class, specific logic is placed in various subclasses, and interface level represents the degree of abstraction. You can refer to the inheritance relationship of a set of interfaces, abstract classes and specific subclasses defined by Java's collection classes:

┌───────────────┐
│   Iterable    │
└───────────────┘
        ▲                ┌───────────────────┐
        │                │      Object       │
┌───────────────┐        └───────────────────┘
│  Collection   │                  ▲
└───────────────┘                  │
        ▲     ▲          ┌───────────────────┐
        │     └──────────│AbstractCollection │
┌───────────────┐        └───────────────────┘
│     List      │                  ▲
└───────────────┘                  │
              ▲          ┌───────────────────┐
              └──────────│   AbstractList    │
                         └───────────────────┘
                                ▲     ▲
                                │     │
                                │     │
                     ┌────────────┐ ┌────────────┐
                     │ ArrayList  │ │ LinkedList │
                     └────────────┘ └────────────┘

When used, the instantiated object can only be a specific subclass, but it is always referenced through the interface, because the interface is more abstract than the abstract class:

List list = new ArrayList(); // Use the List interface to reference instances of specific subclasses
Collection coll = list; // Transform upward to Collection interface
Iterable it = coll; // Upward transformation to Iterable interface

default method

In the interface, you can define the default method. For example, change the run() method of the Person interface to the default method:

// interface
public class Main {
    public static void main(String[] args) {
        Person p = new Student("Xiao Ming");
        p.run();
    }
}

interface Person {
    String getName();
    default void run() {
        System.out.println(getName() + " run");
    }
}

class Student implements Person {
    private String name;

    public Student(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }
}

The implementation class does not have to override the default method. The purpose of the default method is that when we need to add a method to the interface, it will involve modifying all subclasses. If you add a default method, you don't have to modify all the subclasses. You just need to override the new method where you need to override it.

The default method is different from the ordinary method of the abstract class. Because the interface has no field, the default method cannot access the field, while the ordinary method of the abstract class can access the instance field.

Summary

Java's interface defines a pure abstract specification, and a class can implement multiple interfaces;

The interface is also a data type, which is suitable for upward transformation and downward transformation;

All methods of the interface are abstract methods, and the interface cannot define instance fields;

The interface can define the default method (JDK > = 1.8).

Static fields and static methods

The fields defined in a class are called instance fields. The characteristic of instance field is that each instance has an independent field, and the fields with the same name of each instance do not affect each other.

There is also a field modified with static, which is called static field: static field.

The instance field has its own independent "space" in each instance, but the static field has only one shared "space", which will be shared by all instances. for instance:

class Person {
    public String name;
    public int age;
    // Define static field number:
    public static int number;
}

Let's look at the following code:

// static field
public class Main {
    public static void main(String[] args) {
        Person ming = new Person("Xiao Ming", 12);
        Person hong = new Person("Xiao Hong", 15);
        ming.number = 88;
        System.out.println(hong.number);
        hong.number = 99;
        System.out.println(ming.number);
    }
}

class Person {
    public String name;
    public int age;

    public static int number;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

For static fields, no matter which instance's static fields are modified, the effect is the same: the static fields of all instances are modified because the static fields do not belong to instances:

        ┌──────────────────┐
ming ──>│Person instance   │
        ├──────────────────┤
        │name = "Xiao Ming"│
        │age = 12          │
        │number ───────────┼──┐    ┌─────────────┐
        └──────────────────┘  │    │Person class │
                              │    ├─────────────┤
                              ├───>│number = 99  │
        ┌──────────────────┐  │    └─────────────┘
hong ──>│Person instance   │  │
        ├──────────────────┤  │
        │name = "Xiao Hong"│  │
        │age = 15          │  │
        │number ───────────┼──┘
        └──────────────────┘

Although instances can access static fields, they actually point to static fields of Person class. Therefore, all instances share a static field.

Therefore, instance variables are not recommended Static fields to access static fields, because in Java programs, instance objects have no static fields. In code, instance objects can access static fields only because the compiler can automatically convert them to class names according to the instance type Static fields to access static objects.

It is recommended to use the class name to access static fields. Static fields can be understood as fields describing the class itself (non instance fields). For the above code, the better way to write it is:

Person.number = 99;
System.out.println(Person.number);

Static method

If there are static fields, there are static methods. Methods modified with static are called static methods.

Calling an instance method must pass through an instance variable, while calling a static method does not need an instance variable and can be called through the class name. Static methods are similar to functions in other programming languages. For example:

// static method
public class Main {
    public static void main(String[] args) {
        Person.setNumber(99);
        System.out.println(Person.number);
    }
}

class Person {
    public static int number;

    public static void setNumber(int value) {
        number = value;
    }
}

Because the static method belongs to class rather than instance, it cannot access this variable or instance field inside the static method. It can only access static fields.

Static methods can also be called through instance variables, but this is just that the compiler automatically rewrites the instance into a class name.

In general, accessing static fields and static methods through instance variables will get a compilation warning.

Static methods are often used in tool classes. For example:

  • Arrays.sort()
  • Math.random()

Static methods are also often used as auxiliary methods. Notice that the Java program entry main() is also a static method.

Static field of interface

Because interface is a pure abstract class, it cannot define instance fields. However, the interface can have static fields, and the static fields must be of final type:

public interface Person {
    public static final int MALE = 1;
    public static final int FEMALE = 2;
}

In fact, because the field of interface can only be public static final, we can remove these modifiers. The above code can be abbreviated as:

public interface Person {
    // The compiler will automatically add public statc final:
    int MALE = 1;
    int FEMALE = 2;
}

The compiler will automatically change this field to public static final.

Summary

  • Static fields belong to the "shared" fields of all instances. In fact, they belong to class;
  • Calling a static method does not require an instance and cannot access this, but you can access static fields and other static methods;
  • Static methods are often used in tool classes and auxiliary methods.

package

In the previous code, we named the class and interface Person, Student, Hello and other simple names.

In reality, if Xiaoming wrote a Person class and Xiaohong also wrote a Person class, now Xiaobai wants to use both Xiaoming's Person and Xiaohong's Person, what should we do?

If Xiao Jun writes an Arrays class, and the JDK happens to have an Arrays class, how to solve the class name conflict?

In Java, we use package to resolve name conflicts.

Java defines a namespace called package. A class always belongs to a package. The class name (such as Person) is just a short name. The real complete class name is the package name Class name.

For example:

Xiao ming's person class is stored under the package ming, so the full class name is ming Person;

Xiao hong's person class is stored under the package hong, so the full class name is hong Person;

Xiaojun's Arrays class is stored under the package mr.jun, so the full class name is mr.jun.Arrays;

The arrays class of JDK is stored in the Java. Net package Util, so the full class name is Java util. Arrays.

When defining a class, we need to declare in the first line which package this class belongs to.

Xiao Ming's person Java file:

package ming; // Declare package name ming

public class Person {
}

Xiaojun's arrays Java file:

package mr.jun; // Declare package name mr.jun

public class Arrays {
}

When the Java virtual machine executes, the JVM only looks at the complete class name. Therefore, as long as the package name is different, the class is different.

The package can be a multi-layer structure, using separate. For example: Java util.

Special note: packages have no parent-child relationship. java.util and Java util. Zip is a different package, and there is no inheritance relationship between them.

There is a class with a defined package name. It uses the default package, which is very easy to cause name conflicts. Therefore, it is not recommended not to write the package name.

We also need to organize the above Java files according to the package structure. Assume package_ With sample as the root directory and src as the source directory, all file structures are:

package_sample
└─ src
    ├─ hong
    │  └─ Person.java
    │  ming
    │  └─ Person.java
    └─ mr
       └─ jun
          └─ Arrays.java

That is, the directory level corresponding to all Java files should be consistent with the package level.

Compiled Class files also need to be stored according to the package structure. If you use IDE, the compiled If the class file is placed in the bin directory, the compiled file structure is:

package_sample
└─ bin
   ├─ hong
   │  └─ Person.class
   │  ming
   │  └─ Person.class
   └─ mr
      └─ jun
         └─ Arrays.class

The compiled commands are relatively complex. We need to execute javac commands in the src Directory:

javac -d ../bin ming/Person.java hong/Person.java mr/jun/Arrays.java

In the IDE, all Java source code is automatically compiled according to the package structure, so you don't have to worry about complex commands compiled using the command line.

Package scope

Classes in the same package can access the fields and methods of the package scope. Fields and methods that do not need public, protected and private modifications are package scopes. For example, the Person class is defined under the hello package:

package hello;

public class Person {
    // Package scope:
    void hello() {
        System.out.println("Hello!");
    }
}

The Main class is also defined under the hello package:

package hello;

public class Main {
    public static void main(String[] args) {
        Person p = new Person();
        p.hello(); // Can be called because Main and Person are in the same package
    }
}

import

In one class, we always refer to other classes. For example, Xiao Ming's Ming If you want to reference Xiao Jun's mr.jun.Arrays class, there are three ways to write the person class:

First, write out the complete class name directly, for example:

// Person.java
package ming;

public class Person {
    public void run() {
        mr.jun.Arrays arrays = new mr.jun.Arrays();
    }
}

Obviously, it's painful to write the full class name every time.

Therefore, the second method is to use the import statement to import Xiaojun's Arrays, and then write a simple class name:

// Person.java
package ming;

// Import full class name:
import mr.jun.Arrays;

public class Person {
    public void run() {
        Arrays arrays = new Arrays();
    }
}

When writing import, you can use *, which means to import all classes under the package (but excluding the classes of sub packages):

// Person.java
package ming;

// Import all class es of mr.jun package:
import mr.jun.*;

public class Person {
    public void run() {
        Arrays arrays = new Arrays();
    }
}

We generally do not recommend this method, because it is difficult to see which package the Arrays class belongs to after importing multiple packages.

There is also an import static syntax, which can import static fields and static methods of a class:

package main;

// Import all static fields and methods of the System class:
import static java.lang.System.*;

public class Main {
    public static void main(String[] args) {
        // It is equivalent to calling system out. println(…)
        out.println("Hello, world!");
    }
}

import static is rarely used.

The Java compiler finally compiled it The class file only uses the full class name. Therefore, in the code, when the compiler encounters a class name:

  • If it is a complete class name, you can directly find the class according to the complete class name;
  • If it is a simple class name, find it in the following order:
    • Find out whether this class exists in the current package;
    • Find out whether the import ed package contains this class;
    • Find Java Whether the Lang package contains this class.

If the class name cannot be determined according to the above rules, the compilation will report an error.

Let's take an example:

// Main.java
package test;

import java.text.Format;

public class Main {
    public static void main(String[] args) {
        java.util.List list; // ok, use the full class name - > java util. List
        Format format = null; // ok, use the class of import - > java text. Format
        String s = "hi"; // ok, use Java String - > java.lang package lang.String
        System.out.println(s); // ok, use Java System - > java.lang package lang.System
        MessageFormat mf = null; // Compilation error: cannot find MessageFormat: MessageFormat cannot be resolved to a type
    }
}

Therefore, when writing a class, the compiler will automatically do two import actions for us:

  • By default, other class es of the current package are automatically import ed;
  • The default is to automatically import Java lang.*.

Note: automatically import Java Lang package, but similar to Java Lang.reflect these packages still need to be imported manually.

If two class names are the same, for example, mr.jun.Arrays and Java util. Arrays, then only one of them can be import ed, and the other must write the full class name.

To avoid name conflicts, we need to determine a unique package name. The recommended practice is to use inverted domain names to ensure uniqueness. For example:

  • org.apache
  • org.apache.commons.log
  • com.liaoxuefeng.sample

Sub packages can be named according to their functions.

Be careful not to interact with Java The class names of Lang package are the same, that is, do not use these names for your own class:

  • String
  • System
  • Runtime
  • ...

Be careful not to have the same name as JDK common classes:

  • java.util.List
  • java.text.Format
  • java.math.BigInteger
  • ...

Summary

The package mechanism built in Java is to avoid class naming conflicts;

The core class of JDK uses Java Lang package, the compiler will import it automatically;

Other common classes of JDK are defined in Java util.*, java.math.*, java.text.*,……;

Inverted domain names are recommended for package names, such as org apache.

Scope

In Java, we often see public, protected and private modifiers. In Java, these modifiers can be used to qualify the access scope.

public

Classes and interface s defined as public can be accessed by any other class:

package abc;

public class Hello {
    public void hi() {
    }
}

The above Hello is public, so it can be accessed by other package classes:

package xyz;

class Main {
    void foo() {
        // Main can access Hello
        Hello h = new Hello();
    }
}

Fields and method s defined as public can be accessed by other classes, provided that you have the permission to access class first:

package abc;

public class Hello {
    public void hi() {
    }
}

The above hi() method is public and can be called by other classes. The premise is to access the Hello class first:

package xyz;

class Main {
    void foo() {
        Hello h = new Hello();
        h.hi();
    }
}

private

Fields and method s defined as private cannot be accessed by other classes:

package abc;

public class Hello {
    // Cannot be called by other classes:
    private void hi() {
    }

    public void hello() {
        this.hi();
    }
}

In fact, to be exact, private access is limited within the class and is independent of the order in which the methods are declared. It is recommended to put the private method later, because the public method defines the functions provided by the class. When reading the code, you should first pay attention to the public method:

package abc;

public class Hello {
    public void hello() {
        this.hi();
    }

    private void hi() {
    }
}

Because Java supports nested classes, if a nested class is also defined inside a class, the nested class has access to private:

// private
public class Main {
    public static void main(String[] args) {
        Inner i = new Inner();
        i.hi();
    }

    // private method:
    private static void hello() {
        System.out.println("private hello!");
    }

    // Static inner class:
    static class Inner {
        public void hi() {
            Main.hello();
        }
    }
}

Classes defined within a class are called nested classes. Java supports several nested classes.

protected

Protected works on inheritance relationships. Fields and methods defined as protected can be accessed by subclasses and subclasses of subclasses:

package abc;

public class Hello {
    // protected method:
    protected void hi() {
    }
}

The above protected method can be accessed by inherited classes:

package xyz;

class Main extends Hello {
    void foo() {
        // You can access the protected method:
        hi();
    }
}

package

Finally, package scope refers to a class that allows access to the same package without public and private modifications, as well as fields and methods without public, protected and private modifications.

package abc;
// package permission class:
class Hello {
    // package permission method:
    void hi() {
    }
}

As long as you are in the same package, you can access the class, field and method of package permission:

package abc;

class Main {
    void foo() {
        // Classes that can access package permissions:
        Hello h = new Hello();
        // Methods that can call package permissions:
        h.hi();
    }
}

Note that the package name must be exactly the same. The package has no parent-child relationship, com Apache and com apache. ABC is a different package.

local variable

Variables defined inside the method are called local variables. The scope of local variables starts from the variable declaration to the end of the corresponding block. Method parameters are also local variables.

package abc;

public class Hello {
    void hi(String name) { // ①
        String s = name.toLowerCase(); // ②
        int len = s.length(); // ③
        if (len < 10) { // ④
            int p = 10 - len; // ⑤
            for (int i=0; i<10; i++) { // ⑥
                System.out.println(); // ⑦
            } // ⑧
        } // ⑨
    } // ⑩
}

Let's look at the hi() method code above:

  • The method parameter name is a local variable, and its scope is the whole method, i.e. ① ~ ⑩;
  • The scope of variable s is from the definition to the end of the method, i.e. ② ~ ⑩;
  • The scope of variable len is from the definition to the end of the method, i.e. ③ ~ ⑩;
  • The scope of variable p is from the definition to the end of if block, i.e. ⑤ ~ ⑨;
  • The scope of variable I is for loop, i.e. ⑥ ~ ⑧.

When using local variables, the scope of local variables should be reduced as much as possible and local variables should be declared as late as possible.

final

Java also provides a final modifier. Final does not conflict with access rights. It has many functions.

Modifying a class with final can prevent it from being inherited:

package abc;

// Cannot be inherited:
public final class Hello {
    private int n = 0;
    protected void hi(int t) {
        long i = t;
    }
}

Modifying method with final can prevent it from being overridden by subclasses:

package abc;

public class Hello {
    // Cannot be overridden:
    protected final void hi() {
    }
}

Modifying a field with final prevents it from being reassigned:

package abc;

public class Hello {
    private final int n = 0;
    protected void hi() {
        this.n = 1; // error!
    }
}

Modifying a local variable with final prevents it from being re assigned:

package abc;

public class Hello {
    protected void hi(final int t) {
        t = 1; // error!
    }
}

Best practices

If you are not sure whether public is required, you will not declare it as public, that is, expose as few external fields and methods as possible.

Defining a method as package permission is helpful for testing, because as long as the test class and the tested class are in the same package, the test code can access the package permission method of the tested class.

One java files can only contain one public class, but can contain multiple non-public classes. If there is a public class, the file name must be the same as the name of the public class.

Summary

The built-in access permissions of Java include public, protected, private and package permissions;

Variables defined in Java methods are local variables. The scope of local variables starts from variable declaration to the end of a block;

The final modifier is not an access right. It can modify class, field and method;

One java files can only contain one public class, but can contain multiple non-public classes.

Inner class

In Java programs, we usually organize different classes under different packages. For classes under a package, they are at the same level and have no parent-child relationship:

java.lang
├── Math
├── Runnable
├── String
└── ...

There is another kind, which is defined inside another class, so it is called Nested Class. Java's internal classes are divided into several types, which are usually not used much, but we also need to understand how they are used.

Inner Class

If a class is defined inside another class, this class is Inner Class:

class Outer {
    class Inner {
        // An Inner Class is defined
    }
}

The Outer Class defined above is an ordinary class, while the Inner Class is an Inner Class. The biggest difference between it and an ordinary class is that the instance of Inner Class cannot exist alone and must be attached to an instance of Outer Class. The example code is as follows:

// inner class
public class Main {
    public static void main(String[] args) {
        Outer outer = new Outer("Nested"); // Instantiate an Outer
        Outer.Inner inner = outer.new Inner(); // Instantiate an Inner
        inner.hello();
    }
}

class Outer {
    private String name;

    Outer(String name) {
        this.name = name;
    }

    class Inner {
        void hello() {
            System.out.println("Hello, " + Outer.this.name);
        }
    }
}

To observe the above code, to instantiate a Inner, we must first create an instance of Outer, and then invoke the new of Outer instance to create Inner instance:

Outer.Inner inner = outer.new Inner();

This is because the Inner Class not only has a this pointing to itself, but also implicitly holds an Outer Class instance. You can use Outer This accesses this instance. Therefore, instantiating an Inner Class cannot be separated from the Outer instance.

Compared with ordinary classes, Inner Class has an additional "privilege" in addition to referencing Outer instances, that is, it can modify the private field of Outer Class. Because the scope of Inner Class is within Outer Class, it can access the private field and method of Outer Class.

Observe the Java compiler after compiling The class file shows that the Outer class is compiled as Outer Class, and the Inner class is compiled as Outer $Inner class.

Anonymous Class

There is also a method to define the Inner Class. It does not need to explicitly define this Class in the Outer Class, but is defined inside the method through the Anonymous Class. The example code is as follows:

// Anonymous Class
public class Main {
    public static void main(String[] args) {
        Outer outer = new Outer("Nested");
        outer.asyncHello();
    }
}

class Outer {
    private String name;

    Outer(String name) {
        this.name = name;
    }

    void asyncHello() {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello, " + Outer.this.name);
            }
        };
        new Thread(r).start();
    }
}

Looking at the asyncHello() method, we instantiated a runnable inside the method. Runnable itself is an interface, which cannot be instantiated. Therefore, an anonymous class that implements the runnable interface is actually defined here, and the anonymous class is instantiated through new, and then transformed into runnable. When defining an anonymous class, you must instantiate it. The writing method of defining an anonymous class is as follows:

Runnable r = new Runnable() {    // Implement the necessary abstract methods...};

Anonymous classes, like inner classes, can access the private fields and methods of outer classes. The reason why we want to define an anonymous class is that we usually don't care about the class name here. We can write a lot less code than directly defining Inner Class.

Observe the Java compiler after compiling The class file shows that the Outer class is compiled as Outer Class, and the anonymous class is compiled as Outer class. If there are multiple anonymous classes, the Java compiler will name each anonymous class Outer , Outer,, Outer in turn

In addition to interfaces, anonymous classes can also inherit from ordinary classes. Observe the following codes:

// Anonymous Class 
import java.util.HashMap;

public class Main {
    public static void main(String[] args) {
        HashMap<String, String> map1 = new HashMap<>();
        HashMap<String, String> map2 = new HashMap<>() {}; // Anonymous class!
        HashMap<String, String> map3 = new HashMap<>() {
            {
                put("A", "1");
                put("B", "2");
            }
        };
        System.out.println(map3.get("A"));
    }
}

map1 is an ordinary HashMap instance, but map2 is an anonymous class instance, but the anonymous class inherits from HashMap. map3 is also an anonymous class instance inherited from HashMap, and a static code block is added to initialize the data. Observe the compiled output and find main $1 Class and main $2 Class two anonymous class files.

Static Nested Class

The last internal class is similar to the Inner Class, but is modified with static, which is called Static Nested Class:

// Static Nested Class
public class Main {
    public static void main(String[] args) {
        Outer.StaticNested sn = new Outer.StaticNested();
        sn.hello();
    }
}

class Outer {
    private static String NAME = "OUTER";

    private String name;

    Outer(String name) {
        this.name = name;
    }

    static class StaticNested {
        void hello() {
            System.out.println("Hello, " + Outer.NAME);
        }
    }
}

The Inner Class decorated with static is very different from the Inner Class. It is no longer attached to the Outer instance, but a completely independent class. Therefore, Outer cannot be referenced This, but it can access Outer's private static fields and static methods. If you move StaticNested outside of Outer, you lose access to private.

Summary

The internal classes of Java can be divided into Inner Class, Anonymous Class and Static Nested Class:

  • Inner Class and Anonymous Class are essentially the same. They must be attached to the instance of Outer Class, that is, they implicitly hold outer This instance and has private access to Outer Class;
  • Static Nested Class is a separate class, but it has private access to Outer Class.

classpath and jar

In Java, we often hear about classpath. There are many articles about "how to set classpath" on the Internet, but most of the settings are unreliable.

What exactly is classpath?

classpath is an environment variable used by the JVM to indicate how the JVM searches for class es.

Because java is a compiled language, the source code file is java, and compiled The class file is the bytecode that can really be executed by the JVM. Therefore, the JVM needs to know that if you want to load an ABC xyz. Hello class, where should I search for the corresponding hello Class file.

Therefore, classpath is a collection of directories, and the search path it sets is related to the operating system. For example, on a Windows system, use; Separated directories with spaces are enclosed by "", which may be as follows:

C:\work\project1\bin;C:\shared;"D:\My Documents\project1\bin"

On Linux systems, it is separated by: and may be as follows:

/usr/shared:/usr/local/bin:/home/liaoxuefeng/bin

Now let's assume that classpath is; C:\work\project1\bin;C:\shared, when the JVM is loading ABC xyz. When Hello this class, it will find:

  • < Current Directory > \ ABC \ XYZ \ hello class
  • C:\work\project1\bin\abc\xyz\Hello.class
  • C:\shared\abc\xyz\Hello.class

be aware. Represents the current directory. If the JVM finds the corresponding class file in a certain path, it will not continue to search in the future. If no path is found, an error is reported.

There are two methods to set classpath:

Setting classpath environment variable in system environment variable is not recommended;

Setting the classpath variable when starting the JVM is recommended.

We strongly do not recommend setting classpath in the system environment variable, which will pollute the whole system environment. Setting classpath when starting the JVM is the recommended practice. In fact, you pass in the - classpath or - cp parameter to the java command:

java -classpath .;C:\work\project1\bin;C:\shared abc.xyz.Hello

Or use the abbreviation of - cp:

java -cp .;C:\work\project1\bin;C:\shared abc.xyz.Hello

If the system environment variable is not set and the - cp parameter is not passed in, the JVM's default classpath is, Current directory:

java abc.xyz.Hello

The above command tells the JVM to search for Hello. Net only in the current directory class.

Run the Java program in the IDE. The - cp parameter automatically passed in by the IDE is the bin directory of the current project and the imported jar package.

Usually, in our own classes, we will refer to the classes of the Java core library, such as String, ArrayList, etc. Where should I find these classes?

Many articles on "how to set the classpath" will tell you to put the rt.jar provided by the JVM into the classpath, but in fact, there is no need to tell the JVM how to find the class in the Java core library. How can the JVM be so stupid that it doesn't even know where its core library is?

Do not add any Java core libraries to the classpath! The JVM doesn't rely on classpath to load the core library at all!

Better yet, don't set classpath! Default current directory It's enough for most cases.

Suppose we have a compiled hello Class, its package name is com For example, if the current directory is C:\work, the directory structure must be as follows:

C:\work
└─ com
   └─ example
      └─ Hello.class

Run this hello Class must use the following command in the current directory:

C:\work> java -cp . com.example.Hello

The JVM is set according to the classpath Find com.com in the current directory example. Hello, that is, the actual search file must be located at COM / example / Hello class. If specified If the class file does not exist, or the directory structure and package name are not correct, an error will be reported.

jar package

If there are many class files are scattered in various layers of directories, which is certainly not easy to manage. It would be much more convenient if you could package the directory and turn it into a file.

The jar package is used to do this. It can type the directory level of the package organization and all files under each directory (including. class files and other files) into a jar file. In this way, it is much easier to back up or send to customers.

The jar package is actually a zip file, and the jar package is equivalent to a directory. If we want to execute the class of a jar package, we can put the jar package in the classpath:

java -cp ./hello.jar abc.xyz.Hello

In this way, the JVM will automatically open the Search for a class in the jar file.

So the question comes: how to create jar packages?

Because the jar package is a zip package, you can directly find the correct directory in Explorer, right-click, and select "send to" and "compressed folder" in the pop-up shortcut menu to create a zip file. Then, change the suffix from Change zip to Jar, a jar package is created successfully.

Suppose the directory structure of the compiled output is as follows:

package_sample
└─ bin
   ├─ hong
   │  └─ Person.class
   │  ming
   │  └─ Person.class
   └─ mr
      └─ jun
         └─ Arrays.class

It should be noted that the first level directory in the jar package should not be bin, but hong, ming and mr.
The jar package can also contain a special / meta-inf / manifest. Jar MF file, manifest MF is plain text and can specify main class and other information. The JVM will automatically read this manifest MF file. If there is a main class, we do not need to specify the name of the class to start on the command line, but use a more convenient command:

java -jar hello.jar

The jar package can also contain other jar packages. At this time, it needs to be in manifest The classpath is configured in the MF file.

In large projects, it is not possible to manually write manifest MF file, and then manually create the zip package. The Java community provides a large number of open source construction tools, such as Maven , you can easily create jar packages.

Summary

The JVM determines the path and order of searching classes through the environment variable classpath;

Setting the system environment variable classpath is not recommended. It is always recommended to pass it in through the - cp command;

The jar package is equivalent to a directory and can contain many class file, easy to download and use;

MANIFEST.MF file can provide jar package information, such as main class, so that jar package can be run directly.

modular

Starting from Java 9, JDK introduces modules.

What is a module? This starts with the version before Java 9.

We know that Class file is the smallest executable file seen by the JVM, and a large program needs to write many classes and generate a pile Class files are not easy to manage, so jar files are containers for class files.

Before Java 9, a large Java program will generate its own jar file and reference the dependent third-party jar file. The Java standard library of the JVM is actually stored in the form of jar file. This file is called rt.jar, with a total of more than 60 M.

If it is a self-developed program, except for one of its own apps In addition to jar, you also need a bunch of third-party jar packages to run a Java program. Generally speaking, the command line reads as follows:

java -cp app.jar:a.jar:b.jar:c.jar com.liaoxuefeng.sample.Main

Note: the standard library rt.jar provided with the JVM should not be written into the classpath. If it is written, it will interfere with the normal operation of the JVM.

If you omit to write a jar that needs to be used at runtime, it is very likely to throw ClassNotFoundException at runtime.

Therefore, jar is just a container for storing classes. It doesn't care about the dependencies between classes.

The module introduced from Java 9 is mainly to solve the problem of "dependency". If a.jar must rely on another b.jar to run, we should add some instructions to a.jar so that the program can automatically locate b.jar when compiling and running. This class container with "dependency" is a module.

In order to show the determination of Java modularization, starting from Java 9, the original Java standard library has been divided into dozens of modules from a single huge rt.jar jmod extension ID, which can be found in $Java_ Find them in the home / jmods Directory:

  • java.base.jmod
  • java.compiler.jmod
  • java.datatransfer.jmod
  • java.desktop.jmod
  • ...

these Each jmod file is a module, and the module name is the file name. For example: module Java The file corresponding to base is Java base. jmod. The dependencies between modules have been written to module info Class file. All modules rely directly or indirectly on Java Base module, only Java The base module does not depend on any module. It can be regarded as a "root module". For example, all classes inherit directly or indirectly from Object.

Encapsulating a pile of classes into jar s is just a packaging process, while encapsulating a pile of classes into modules requires not only packaging, but also writing dependencies, and can also contain binary code (usually JNI extensions). In addition, modules support multiple versions, that is, different versions can be provided for different JVM s in the same module.

Write module

So, how should we write modules? Let's take a specific example. First, creating a module is exactly the same as creating a Java project. Take the OOP module project as an example. Its directory structure is as follows:

oop-module
├── bin
├── build.sh
└── src
    ├── com
    │   └── itranswarp
    │       └── sample
    │           ├── Greeting.java
    │           └── Main.java
    └── module-info.java

Among them, the bin directory stores the compiled class files, and the src directory stores the source code according to the directory structure of the package name. Only one module info is added in the src directory Java file, which is the module description file. In this module, it looks like this:

module hello.world {	requires java.base; // No need to write, any module will automatically introduce Java base 	 requires java.xml;}

Where module is the keyword, followed by Hello World is the name of the module, and its naming convention is consistent with that of the package. requires xxx in curly braces; Indicates other module names that this module needs to reference. Except Java Base can be automatically introduced. Here we introduce a Java XML module.

We can use the introduced module only after we have declared the dependency with the module. For example, main The Java code is as follows:

package com.itranswarp.sample;

// You must introduce Java XML module before using its classes:
import javax.xml.XMLConstants;

public class Main {
	public static void main(String[] args) {
		Greeting g = new Greeting();
		System.out.println(g.hello(XMLConstants.XML_NS_PREFIX));
	}
}

If you put requires Java xml; From module info Java, the compiler will report an error. It can be seen that the important role of modules is to declare dependencies.

Next, we use the command line tool provided by JDK to compile and create the module.

First, we switch the working directory to OOP module and compile all the modules in the current directory java file and store it in the bin directory. The command is as follows:

$ javac -d bin src/module-info.java src/com/itranswarp/sample/*.java

If the compilation is successful, the current project structure is as follows:

oop-module
├── bin
│   ├── com
│   │   └── itranswarp
│   │       └── sample
│   │           ├── Greeting.class
│   │           └── Main.class
│   └── module-info.class
└── src
    ├── com
    │   └── itranswarp
    │       └── sample
    │           ├── Greeting.java
    │           └── Main.java
    └── module-info.java

Notice the module info. In the src directory Java is compiled into module info. Java in the bin directory class.

Next, we need to package all class files in the bin directory into jars. When packaging, pay attention to passing in the -- main class parameter so that the jar package can locate the class where the main method is located:

$ jar --create --file hello.jar --main-class com.itranswarp.sample.Main -C bin .

Now we get hello. Com in the current directory Jar is a jar package. It is no different from an ordinary jar package. You can directly use the command Java - jar hello Jar to run it. However, our goal is to create modules, so continue to use the jmod command of JDK to convert a jar package into modules:

$ jmod create --class-path hello.jar hello.jmod

So, in the current directory, we get hello Jmod is the module file, which is the legendary module packaged at last!

Operation module

To run a jar, we use Java - jar XXX Jar command. To run a module, we only need to specify the module name. try:

$ java --module-path hello.jmod --module hello.world

The result is an error:

Error occurred during initialization of boot layerjava.lang.module.FindException: JMOD format not supported at execution time: hello.jmod

as a result of. jmod cannot be put into -- module path. Replace with jar is OK:

$ java --module-path hello.jar --module hello.worldHello, xml!

Then we worked hard to create hello What's the use of jmod? The answer is that we can use it to package JRE.

Package JRE

As mentioned earlier, in order to support modularization, Java 9 took the lead in dismantling its own huge rt.jar into dozens jmod module. The reason is that we don't actually use so many JDK modules when running Java programs. Unnecessary modules can be deleted.

In the past, a Java application was released. To run it, you must download a complete JRE and then run the jar package. The complete JRE is large, with more than 100 M. How to slim down JRE?

Now, JRE's own standard library has been divided into modules. Just bring the modules used by the program, and other modules can be cut out. How to cut JRE? It does not mean to delete some modules from the JRE installed in the system, but to "copy" a JRE, but only bring the modules used. To this end, JDK provides the jlink command to do this. The command is as follows:

$ jlink --module-path hello.jmod --add-modules java.base,java.xml,hello.world --output jre/

We specified our own module hello in the -- module path parameter Jmod, and then specify the three modules we use in the -- add modules parameter base,java.xml and hello World, separated by. Finally, specify the output directory in the -- output parameter.

Now, under the current directory, we can find the JRE directory, which is a complete one with our own hello JRE of jmod module. Try running this JRE directly:

$ jre/bin/java --module hello.worldHello, xml!

To distribute our own Java applications, we only need to package the jre directory and send it to the other party. The other party can directly run the above commands. There is no need to download and install JDK or know how to configure our own modules, which greatly facilitates distribution and deployment.

Access rights

As we mentioned earlier, Java class access permissions are divided into public, protected, private and default package access permissions. After the module is introduced, the rules of these access rights need to be adjusted slightly.

To be exact, these access permissions of class are only valid in one module. For example, if module a wants to access a class of module b, the necessary condition is that module b explicitly exports accessible packages.

For example: the module hello World uses the module Java A class of javax. XML xml. Xmlconstants, we can use this class directly because the module Java Module info.xml Several exports are declared in Java:

module java.xml {    exports java.xml;    exports javax.xml.catalog;    exports javax.xml.datatype;    ...}
```cmd

Only the exported package declared by it can be accessed by external code. In other words, if external code wants to access our`hello.world`In the module`com.itranswarp.sample.Greeting`Class, we must export it:

```cmd
module hello.world {    exports com.itranswarp.sample;    requires java.base;	requires java.xml;}

Therefore, the module further isolates the access rights of the code.

Summary

The module introduced by Java 9 is to manage dependencies;

JRE can be packaged as required by using the module;

Access to classes using modules is further restricted.

Java core classes

In this section, we will introduce the core classes of Java, including:

  • character string
  • StringBuilder
  • StringJoiner
  • Packaging type
  • JavaBean
  • enumeration
  • Common tools

String and encoding

String

In Java, String is a reference type, and it is also a class. However, the java compiler has special processing for strings, that is, you can directly use "..." To represent a String:

String s1 = "Hello!";

In fact, the String is represented by a char [] array inside the String. Therefore, it is possible to write it as follows:

String s2 = new String(new char[] {'H', 'e', 'l', 'l', 'o', '!'});

Because String is too commonly used, Java provides "..." This String literal representation.

An important feature of Java strings is that strings are immutable. This immutability is realized through the internal private final char [] field and without any method of modifying char [].

Let's take an example:

// String 
public class Main {
    public static void main(String[] args) {
        String s = "Hello";
        System.out.println(s);
        s = s.toUpperCase();
        System.out.println(s);
    }
}

string comparison

When we want to compare whether two strings are the same, we should pay special attention to that we actually want to compare whether the contents of the strings are the same. The equals() method must be used instead of = =.

Let's look at the following example:

// String
public class Main {
    public static void main(String[] args) {
        String s1 = "hello";
        String s2 = "hello";
        System.out.println(s1 == s2);
        System.out.println(s1.equals(s2));
    }
}

On the surface, both strings are true when compared with = = and equals(), but in fact, it is just that the Java compiler will automatically put all the same strings into the constant pool as an object during compilation. Naturally, the references of s1 and s2 are the same.

Therefore, it is a coincidence that this = = comparison returns true. In other words, = = the comparison will fail:

// String
public class Main {
    public static void main(String[] args) {
        String s1 = "hello";
        String s2 = "HELLO".toLowerCase();
        System.out.println(s1 == s2);
        System.out.println(s1.equals(s2));
    }
}

Conclusion: when comparing two strings, the equals() method must always be used.

To ignore the case comparison, use the equalsIgnoreCase() method.

The String class also provides a variety of methods to search and extract substrings. Common methods are:

// Include substrings:
"Hello".contains("ll"); // true

Notice that the parameter of the contains() method is CharSequence instead of String, because CharSequence is the parent class of String.

More examples of searching substrings:

"Hello".indexOf("l"); // 2
"Hello".lastIndexOf("l"); // 3
"Hello".startsWith("He"); // true
"Hello".endsWith("lo"); // true

Examples of extracting substrings:

"Hello".substring(2); // "llo"
"Hello".substring(2, 4); "ll"

Notice that the index number starts at 0.

Remove leading and trailing white space characters

Use the trim() method to remove white space characters from the beginning and end of a string. White space characters include spaces, \ t, \ r \ n:

"  \tHello\r\n ".trim(); // "Hello"

Note: trim() does not change the contents of the string, but returns a new string.

Another strip() method can also remove white space characters at the beginning and end of a string. Unlike trim(), the Chinese space character \ u3000 will also be removed:

"\u3000Hello\u3000".strip(); // "Hello"
" Hello ".stripLeading(); // "Hello "
" Hello ".stripTrailing(); // " Hello"

String also provides isEmpty() and isBlank() to judge whether the string is empty or blank:

"".isEmpty(); // true because the string length is 0
"  ".isEmpty(); // false because the string length is not 0
"  \n".isBlank(); // true because it contains only white space characters
" Hello ".isBlank(); // false because it contains non white space characters

Replace substring

There are two ways to replace substrings in a string. One is to replace according to characters or strings:

String s = "hello";
s.replace('l', 'w'); // "hewwo", all characters' l 'are replaced by' w '
s.replace("ll", "~~"); // "he~~o", all substrings "ll" are replaced with "~ ~"

The other is to replace by regular expression:

String s = "A,,B;C ,D";
s.replaceAll("[\\,\\;\\s]+", ","); // "A,B,C,D"

The above code uniformly replaces the matching substring with "," through regular expression. The usage of regular expressions will be explained in detail later.

Split string

To split a string, use the split() method and pass in a regular expression:

String s = "A,B,C,D";
String[] ss = s.split("\\,"); // {"A", "B", "C", "D"}

Splice string

Splicing strings uses the static method join(), which connects the string array with the specified string:

String[] arr = {"A", "B", "C"};
String s = String.join("***", arr); // "A***B***C"

format string

String provides formatted() method and format() static method. You can pass in other parameters, replace placeholders, and then generate a new string:

// String
public class Main {
    public static void main(String[] args) {
        String s = "Hi %s, your score is %d!";
        System.out.println(s.formatted("Alice", 80));
        System.out.println(String.format("Hi %s, your score is %.2f!", "Bob", 59.5));
    }
}

There are several placeholders, and several parameters will be passed in later. The parameter type should be consistent with the placeholder. We often use this method to format information. Common placeholders are:

  • %s: Display string;
  • %d: Display integer;
  • %x: Display hexadecimal integer;
  • %f: Displays floating point numbers.

Placeholders can also be formatted, for example%. 2f shows two decimal places. If you're not sure what placeholder to use, always use% s because% s can display any data type. To view the complete formatting syntax, refer to JDK document.

Type conversion

To convert any base or reference type to a string, you can use the static method valueOf(). This is an overloaded method. The compiler will automatically select the appropriate method according to the parameters:

String.valueOf(123); // "123"
String.valueOf(45.67); // "45.67"
String.valueOf(true); // "true"
String.valueOf(new Object()); // Similar to Java lang. Object@636be97c

To convert a string to another type, you need to change it as appropriate. For example, convert a string to type int:

int n1 = Integer.parseInt("123"); // 123
int n2 = Integer.parseInt("ff", 16); // Hexadecimal conversion, 255

Convert string to boolean type:

boolean b1 = Boolean.parseBoolean("true"); // true
boolean b2 = Boolean.parseBoolean("FALSE"); // false

In particular, Integer has a getInteger(String) method. Instead of converting a string to int, it converts the system variable corresponding to the string to Integer:

Integer.getInteger("java.version"); // Version number, 11

Convert to char []

String and char [] types can be converted to each other by:

char[] cs = "Hello".toCharArray(); // String -> char[]
String s = new String(cs); // char[] -> String

If the char [] array is modified, the String does not change:

// String <-> char[]
public class Main {
    public static void main(String[] args) {
        char[] cs = "Hello".toCharArray();
        String s = new String(cs);
        System.out.println(s);
        cs[0] = 'X';
        System.out.println(s);
    }
}

This is because when creating a new String instance through new String(char []), it will not directly reference the incoming char [] array, but will copy a copy. Therefore, modifying the external char [] array will not affect the char [] array inside the String instance, because these are two different arrays.

From the invariance design of String, we can see that if the incoming object may change, we need to copy rather than reference it directly.

For example, the following code designs a Score class to save the scores of a group of students:

// int[] import java.util.Arrays;
public class Main {
    public static void main(String[] args) {
        int[] scores = new int[] { 88, 77, 51, 66 };
        Score s = new Score(scores);
        s.printScores();
        scores[2] = 99;
        s.printScores();
    }
}

class Score {
    private int[] scores;
    public Score(int[] scores) {
        this.scores = scores;
    }

    public void printScores() {
        System.out.println(Arrays.toString(scores));
    }
}

Character encoding

In the early computer system, in order to encode characters, the American National Standard Institute (ANSI) developed A set of codes for English letters, numbers and common symbols. It occupies one byte, the coding range is from 0 to 127, and the highest bit is always 0, which is called ASCII coding. For example, the encoding of character 'A' is 0x41 and the encoding of character '1' is 0x31.

If we want to include Chinese characters in computer coding, it is obvious that one byte is not enough. GB2312 standard uses two bytes to represent a Chinese character, in which the highest bit of the first byte is always 1 to distinguish it from ASCII coding. For example, the GB2312 code of Chinese character 'Zhong' is 0xd6d0.

Similarly, Japanese has Shift_JIS codes and EUC-KR codes are available in Korean. These codes will conflict if they are used at the same time because the standards are not unified.

In order to unify the coding of all languages in the world, the global unified coding alliance has released Unicode coding, which brings all the major languages in the world into the same coding, so that Chinese, Japanese, Korean and other languages will not conflict.

Unicode encoding requires two or more bytes. We can compare the encoding of Chinese and English characters in ASCII, GB2312 and Unicode:

ASCII and Unicode encoding of English character 'A':

         ┌────┐
ASCII:   │ 41 │
         └────┘
         ┌────┬────┐
Unicode: │ 00 │ 41 │
         └────┴────┘

The Unicode encoding of English characters is simply to add a 00 byte in front.

GB2312 encoding and Unicode encoding of Chinese characters' Zhong ':

         ┌────┬────┐
GB2312:  │ d6 │ d0 │
         └────┴────┘
         ┌────┬────┐
Unicode: │ 4e │ 2d │
         └────┴────┘

What is the UTF-8 code we often use? Because the high byte of Unicode encoding of English characters is always 00, and containing A large amount of English text will waste space, UTF-8 encoding appears. It is A variable length encoding, which is used to change the fixed length Unicode encoding into A variable length encoding of 1 ~ 4 bytes. Through UTF-8 encoding, the UTF-8 encoding of English character 'A' becomes 0x41, which is exactly consistent with ASCII code, while the UTF-8 encoding of Chinese character 'Zhong' is 3 bytes 0xe4b8ad.

Another advantage of UTF-8 coding is strong fault tolerance. If some characters make mistakes during transmission, it will not affect the subsequent characters, because UTF-8 coding depends on the high byte bits to determine how many bytes a character is, which is often used as transmission coding.

In Java, the char type is actually a two byte Unicode encoding. If we want to manually convert a string to another encoding, we can do this:

byte[] b1 = "Hello".getBytes(); // Convert according to the system default code, not recommended
byte[] b2 = "Hello".getBytes("UTF-8"); // Convert according to UTF-8 encoding
byte[] b2 = "Hello".getBytes("GBK"); // Convert by GBK code
byte[] b3 = "Hello".getBytes(StandardCharsets.UTF_8); // Convert according to UTF-8 encoding

Note: after converting the encoding, it is no longer a char type, but an array represented by byte type.

If you want to convert a known encoded byte [] to a String, you can do this:

byte[] b = ...
String s1 = new String(b, "GBK"); // Convert by GBK
String s2 = new String(b, StandardCharsets.UTF_8); // Convert according to UTF-8

Always keep in mind that Java strings and char s are always encoded in Unicode in memory.

Extended reading

For different versions of JDK, the String class has different optimization methods in memory. Specifically, strings in earlier JDK versions are always stored in char [], which is defined as follows:

public final class String {
    private final char[] value;
    private final int offset;
    private final int count;
}

The String of the newer JDK version is stored in byte []: if the String contains only ASCII characters, one character is stored in each byte; otherwise, one character is stored in every two bytes. The purpose of this is to save memory, because a large number of short strings usually contain only ASCII characters:

public final class String {
    private final byte[] value;
    private final byte coder; // 0 = LATIN1, 1 = UTF16

For users, the internal optimization of String does not affect any existing code, because its public method signature is unchanged.

Summary

  • Java String string is an immutable object;
  • String operation does not change the content of the original string, but returns a new string;
  • Common string operations: extracting substrings, searching, replacing, case conversion, etc;
  • Java uses Unicode encoding to represent String and char;
  • The conversion code is to convert String and byte [], and the code needs to be specified;
  • When converting to byte [], always give priority to UTF-8 encoding.

StringBuilder

The Java compiler makes special processing for String, so that we can directly splice strings with +.

Consider the following loop code:

String s = "";
for (int i = 0; i < 1000; i++) {
    s = s + "," + i;
}

Although strings can be spliced directly, in a loop, each loop creates a new string object and then discards the old string. In this way, most strings are temporary objects, which not only wastes memory, but also affects GC efficiency.

In order to splice strings efficiently, the Java standard library provides StringBuilder, which is a variable object and can pre allocate buffers. In this way, when adding characters to StringBuilder, new temporary objects will not be created:

StringBuilder sb = new StringBuilder(1024);
for (int i = 0; i < 1000; i++) {
    sb.append(',');
    sb.append(i);
}
String s = sb.toString();

StringBuilder can also perform chain operations:

// Chain operation
public class Main {
    public static void main(String[] args) {
        var sb = new StringBuilder(1024);
        sb.append("Mr ")
          .append("Bob")
          .append("!")
          .insert(0, "Hello, ");
        System.out.println(sb.toString());
    }
}

If we look at the source code of StringBuilder, we can find that the key to chain operation is that the defined append() method will return this, so that we can constantly call other methods.

Following StringBuilder, we can also design classes that support chain operations. For example, a counter that can be incremented:

// Chain operation
public class Main {
    public static void main(String[] args) {
        Adder adder = new Adder();
        adder.add(3)
             .add(5)
             .inc()
             .add(10);
        System.out.println(adder.value());
    }
}

class Adder {
    private int sum = 0;

    public Adder add(int n) {
        sum += n;
        return this;
    }

    public Adder inc() {
        sum ++;
        return this;
    }

    public int value() {
        return sum;
    }
}

Note: for ordinary string + operations, we do not need to rewrite them into StringBuilder, because the Java compiler automatically encodes multiple consecutive + operations as StringConcatFactory operations at compile time. During runtime, StringConcatFactory will automatically optimize the string connection operation into array copy or StringBuilder operation.

You may also have heard of StringBuffer, which is a thread safe version of StringBuilder in early Java. It ensures that multiple threads operate StringBuffer safely through synchronization, but synchronization will reduce the execution speed.

The interfaces of StringBuilder and StringBuffer are exactly the same, and there is no need to use StringBuffer now.

Summary

StringBuilder is a variable object, which is used to splice strings efficiently;

StringBuilder can support chain operation. The key to realize chain operation is to return the instance itself;

StringBuffer is a thread safe version of StringBuilder and is rarely used now.

StringJoiner

To splice strings efficiently, you should use StringBuilder.

Many times, our spliced strings are like this:

// Hello Bob, Alice, Grace!
public class Main {
    public static void main(String[] args) {
        String[] names = {"Bob", "Alice", "Grace"};
        var sb = new StringBuilder();
        sb.append("Hello ");
        for (String name : names) {
            sb.append(name).append(", ");
        }
        // Note to remove the last ",":
        sb.delete(sb.length() - 2, sb.length());
        sb.append("!");
        System.out.println(sb.toString());
    }
}

The requirement of splicing arrays with separators is very common, so the Java standard library also provides a StringJoiner to do this:

import java.util.StringJoiner;
public class Main {
    public static void main(String[] args) {
        String[] names = {"Bob", "Alice", "Grace"};
        var sj = new StringJoiner(", ");
        for (String name : names) {
            sj.add(name);
        }
        System.out.println(sj.toString());
    }
}

Wait! The result with StringJoiner is less of the preceding "Hello" and the ending "!"! In this case, you need to specify "start" and "end" for StringJoiner:

import java.util.StringJoiner;
public class Main {
    public static void main(String[] args) {
        String[] names = {"Bob", "Alice", "Grace"};
        var sj = new StringJoiner(", ", "Hello ", "!");
        for (String name : names) {
            sj.add(name);
        }
        System.out.println(sj.toString());
    }
}

String.join()

String also provides a static method join (), which internally uses StringJoiner to splice strings. When it is not necessary to specify "start" and "end", string join() is more convenient:

String[] names = {"Bob", "Alice", "Grace"};
var s = String.join(", ", names);

Summary

Use StringJoiner or string. When splicing string arrays with the specified delimiter Join() is more convenient;

When you splice strings with StringJoiner, you can attach an additional "beginning" and "end".

Packaging type

We already know that there are two types of data in Java:

  • Basic types: byte, short, int, long, boolean, float, double, char
  • Reference type: all class and interface types

The reference type can be assigned null, indicating null, but the basic type cannot be assigned null:

String s = null;int n = null; // compile error!

So, how to treat a basic type as an object (reference type)?

For example, to change the basic type of int into a reference type, we can define an Integer class, which contains only one instance field int. in this way, the Integer class can be regarded as the Wrapper Class of int:

public class Integer {
    private int value;

    public Integer(int value) {
        this.value = value;
    }

    public int intValue() {
        return this.value;
    }
}

After defining the Integer class, we can convert int and Integer to each other:

Integer n = null;
Integer n2 = new Integer(99);
int n3 = n2.intValue();

In fact, because wrapper types are very useful, the Java core library provides corresponding wrapper types for each basic type:

Basic typeCorresponding reference type
booleanjava.lang.Boolean
bytejava.lang.Byte
shortjava.lang.Short
intjava.lang.Integer
longjava.lang.Long
floatjava.lang.Float
doublejava.lang.Double
charjava.lang.Character

We can use it directly without defining it ourselves:

// Integer:
public class Main {
    public static void main(String[] args) {
        int i = 100;
        // Create an Integer instance through the new operator (not recommended, there will be a compilation warning):
        Integer n1 = new Integer(i);
        // Create an Integer instance through the static method valueOf(int):
        Integer n2 = Integer.valueOf(i);
        // Create an Integer instance through the static method valueOf(String):
        Integer n3 = Integer.valueOf("100");
        System.out.println(n3.intValue());
    }
}

Auto Boxing

Because int and Integer can be converted to each other:

int i = 100;
Integer n = Integer.valueOf(i);
int x = n.intValue();

Therefore, the Java compiler can help us automatically transform between int and Integer:

Integer n = 100; // The compiler automatically uses integer valueOf(int)
int x = n; // The compiler automatically uses integer intValue()

This assignment method of directly changing int into Integer is called Auto Boxing. Conversely, the assignment method of changing Integer into int is called auto unpacking.

Note: Auto boxing and auto unpacking only occur in the compilation stage in order to write less code.

Boxing and unpacking will affect the execution efficiency of the code, because the compiled class code strictly distinguishes between basic types and reference types. In addition, NullPointerException may be reported during automatic unpacking:

// NullPointerException
public class Main {
    public static void main(String[] args) {
        Integer n = null;
        int i = n;
    }
}

Invariant class

All wrapper types are invariant classes. We can see from the source code of Integer that its core code is as follows:

public final class Integer {
    private final int value;
}

Therefore, once an Integer object is created, it remains unchanged.

When comparing two Integer instances, you should pay special attention: you must not use = = to compare, because Integer is a reference type, and you must use equals() to compare:

// == or equals?
public class Main {
    public static void main(String[] args) {
        Integer x = 127;
        Integer y = 127;
        Integer m = 99999;
        Integer n = 99999;
        System.out.println("x == y: " + (x==y)); // true
        System.out.println("m == n: " + (m==n)); // false
        System.out.println("x.equals(y): " + x.equals(y)); // true
        System.out.println("m.equals(n): " + m.equals(n)); // true
    }
}

Careful observation of the children's shoes shows that = = in comparison, the smaller two identical integers return true and the larger two identical integers return false. This is because Integer is an invariant class and the compiler sets Integer x = 127; Automatically becomes Integer x = Integer valueOf(127);, To save memory, Integer Valueof() always returns the same instance for smaller numbers. Therefore, = = comparison "just happens" is true. However, we must not use = = comparison because Integer in Java standard library has cache optimization. We must use equals() method to compare two integers.

Programming according to semantics, rather than "optimizing" for specific underlying implementations.

Because Integer Valueof() may always return the same Integer instance. Therefore, when we create an Integer ourselves, we use the following two methods:

  • Method 1: Integer n = new Integer(100);
  • Method 2: integer n = integer valueOf(100);

Method 2 is better because method 1 always creates a new Integer instance. Method 2 leaves the internal optimization to the implementer of Integer. Even if there is no optimization in the current version, it may be optimized in the next version.

We call static methods that can create "new" objects static factory methods. Integer.valueOf() is a static factory method that returns cached instances as much as possible to save memory.

When creating new objects, the static factory method is preferred over the new operator.

If we look at Byte From the source code of the valueof () method, you can see that all Byte instances returned by the standard library are cached instances, but the caller does not care how the static factory method creates a new instance or directly returns the cached instance.

Binary conversion

The Integer class itself also provides a large number of methods. For example, the most commonly used static method parseInt() can parse a string into an Integer:

int x1 = Integer.parseInt("100"); // 100int x2 = Integer.parseInt("100", 16); // 256, because it is parsed in hexadecimal

Integer can also format integers into strings of specified base numbers:

// Integer:
public class Main {
    public static void main(String[] args) {
        System.out.println(Integer.toString(100)); // "100" is represented as decimal
        System.out.println(Integer.toString(100, 36)); // "2s" is expressed in hexadecimal 36
        System.out.println(Integer.toHexString(100)); // "64" means hexadecimal
        System.out.println(Integer.toOctalString(100)); // "144", expressed as octal
        System.out.println(Integer.toBinaryString(100)); // "1100100" indicates binary
    }
}

Note: the output of the above methods is String. In the computer memory, it is only represented by binary, and there is no decimal or hexadecimal representation. int n = 100 is always represented in 4-byte binary in memory:

┌────────┬────────┬────────┬────────┐
│00000000│00000000│00000000│01100100│
└────────┴────────┴────────┴────────┘

We often use system out. println(n); It relies on the core library to automatically format integers into hexadecimal output and display them on the screen, using integer Tohexstring (n) automatically formats integers to hexadecimal through the core library.

Here we notice an important principle of program design: the storage and display of data should be separated.

Java wrapper types also define some useful static variables

// Boolean has only two values true/false, and its wrapper type only needs to reference the static fields provided by Boolean:
Boolean t = Boolean.TRUE;
Boolean f = Boolean.FALSE;
// Max / min value that int can represent:
int max = Integer.MAX_VALUE; // 2147483647
int min = Integer.MIN_VALUE; // -2147483648
// Number of bit s and byte s occupied by long type:
int sizeOfLong = Long.SIZE; // 64 (bits)
int bytesOfLong = Long.BYTES; // 8 (bytes)

Finally, the wrapper types of all integers and floating-point numbers are inherited from Number. Therefore, it is very convenient to obtain various basic types directly through the wrapper types:

// Up to Number:
Number num = new Integer(999);
// Get byte, int, long, float, double:
byte b = num.byteValue();
int n = num.intValue();
long ln = num.longValue();
float f = num.floatValue();
double d = num.doubleValue();

Handling unsigned integers

In Java, there is no Unsigned basic data type. byte, short, int and long are signed integers, and the highest bit is the sign bit. C language provides all data types supported by CPU, including Unsigned integer. The conversion between Unsigned integer and signed integer needs to be completed with the help of the static method of wrapper type in Java.

For example, byte is a signed integer with a range of - 128 ` + 127 `, but if 'byte' is regarded as an unsigned integer, its range is' 0 '255. We convert a negative byte to int as an unsigned integer:

// Byte
public class Main {
    public static void main(String[] args) {
        byte x = -1;
        byte y = 127;
        System.out.println(Byte.toUnsignedInt(x)); // 255
        System.out.println(Byte.toUnsignedInt(y)); // 127
    }
}

Because the binary representation of - 1 of byte is 11111111, the int converted by unsigned integer is 255.

Similarly, you can press unsigned to convert a short to int and unsigned to long.

Summary

The wrapper type provided by the Java core library can wrap the basic type as class;

Automatic packing and unpacking are completed during compilation (JDK > = 1.5);

Packing and unpacking will affect the execution efficiency, and NullPointerException may occur during unpacking;

The comparison of packing types must use equals();

The wrapper types of integers and floating-point numbers are inherited from Number;

Packaging types provide a number of practical methods.

JavaBean

In Java, many class definitions conform to such specifications:

  • Several private instance fields;
  • Use the public method to read and write instance fields.

For example:

public class Person {
    private String name;
    private int age;

    public String getName() { return this.name; }
    public void setName(String name) { this.name = name; }

    public int getAge() { return this.age; }
    public void setAge(int age) { this.age = age; }
}

If the read-write method complies with the following naming conventions:

// Read method:
public Type getXyz()
// Write method:
public void setXyz(Type value)

Then this class is called a JavaBean:

If the above field is XYZ, the name of the read-write method starts with get and set respectively, followed by the field name XYZ starting with an uppercase letter. Therefore, the names of the two read-write methods are getXyz() and setXyz() respectively.

The boolean field is special. Its reading method is generally named isXyz():

// Read method:
public boolean isChild()
// Write method:
public void setChild(boolean value)

We usually call a set of corresponding read methods (getter s) and write methods (setter s) as properties. For example, the name attribute:

  • The corresponding read method is String getName()
  • The corresponding write method is setName(String)

Only the getter attribute is called read-only. For example, define a read-only attribute:

  • The corresponding read method is int getAge()
  • No corresponding write method setAge(int)

Similarly, only the setter property is called write only.

Obviously, read-only attributes are common, and write only attributes are not common.

Property only needs to define getter and setter methods, not necessarily corresponding fields. For example, the child read-only attribute is defined as follows:

public class Person {
    private String name;
    private int age;

    public String getName() { return this.name; }
    public void setName(String name) { this.name = name; }

    public int getAge() { return this.age; }
    public void setAge(int age) { this.age = age; }

    public boolean isChild() {
        return age <= 6;
    }
}

It can be seen that getter and setter are also methods of data encapsulation.

The role of JavaBean s

JavaBeans are mainly used to transfer data, that is, a group of data is combined into a JavaBean for easy transmission. In addition, JavaBeans can be easily analyzed by IDE tools to generate code for reading and writing attributes, which is mainly used in the visual design of graphical interface.

Through the IDE, you can quickly generate getter s and setter s. For example, in Eclipse, enter the following code first:

public class Person {
    private String name;
    private int age;
}

Then, right-click, select "Source", "Generate Getters and Setters" in the pop-up menu, select the fields to Generate Getters and Setters in the pop-up dialog box, and click OK to complete all method codes automatically by the IDE.

Enumerating JavaBean properties

To enumerate all properties of a JavaBean, you can directly use the Introspector provided by the Java core library:

import java.beans.*;
public class Main {
    public static void main(String[] args) throws Exception {
        BeanInfo info = Introspector.getBeanInfo(Person.class);
        for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
            System.out.println(pd.getName());
            System.out.println("  " + pd.getReadMethod());
            System.out.println("  " + pd.getWriteMethod());
        }
    }
}

class Person {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

Summary

JavaBean is a class that conforms to the naming convention. It defines attributes through getter s and setter s;

Attribute is a common name, not specified in Java syntax;

You can use IDE to quickly generate getter s and setter s;

Using introspector Getbeaninfo () can get the attribute list.

Enumeration class

In Java, we can define constants through static final. For example, we want to define seven constants from Monday to Sunday, which can be represented by seven different int s:

public class Weekday {
    public static final int SUN = 0;
    public static final int MON = 1;
    public static final int TUE = 2;
    public static final int WED = 3;
    public static final int THU = 4;
    public static final int FRI = 5;
    public static final int SAT = 6;
}

When using constants, you can refer to:

if (day == Weekday.SAT || day == Weekday.SUN) {
    // TODO: work at home
}

You can also define constants as string types. For example, you can define constants of three colors:

public class Color {
    public static final String RED = "r";
    public static final String GREEN = "g";
    public static final String BLUE = "b";
}

When using constants, you can refer to:

String color = ...
if (Color.RED.equals(color)) {
    // TODO:
}

Whether it is an int constant or a String constant, when using these constants to represent a set of enumerated values, there is a serious problem that the compiler cannot check the rationality of each value. For example:

if (weekday == 6 || weekday == 7) {
    if (tasks == Weekday.MON) {
        // TODO:
    }
}

The above code compilation and operation will not report errors, but there are two problems:

  • Note that the constant range defined by Weekday is 0 ~ 6 and does not contain 7. The compiler cannot check the int value that is not in the enumeration;
  • Defined constants can still be compared with other variables, but their purpose is not to enumerate week values.

enum

In order to enable the compiler to automatically check that a value is in the set of enums, and enums for different purposes need different types to be marked and cannot be mixed, we can use enum to define the enum class:

// enum
public class Main {
    public static void main(String[] args) {
        Weekday day = Weekday.SUN;
        if (day == Weekday.SAT || day == Weekday.SUN) {
            System.out.println("Work at home!");
        } else {
            System.out.println("Work at office!");
        }
    }
}

enum Weekday {
    SUN, MON, TUE, WED, THU, FRI, SAT;
}

Note that the definition of enumeration class is implemented through the keyword enum. We only need to list the constant names of enumeration in turn.

Compared with constants defined by int, enum has the following advantages:

First, the enum constant itself carries type information, Weekday The sun type is Weekday, and the compiler will automatically check for type errors. For example, the following statement cannot be compiled:

int day = 1;
if (day == Weekday.SUN) { // Compile error: bad operand types for binary operator '=='
}

Second, it is impossible to reference non enumerated values because they cannot be compiled.

Finally, enumerations of different types cannot be compared or assigned to each other because the types do not match. For example, a variable of Weekday enumeration type cannot be assigned a value of Color enumeration type:

Weekday x = Weekday.SUN; // ok!
Weekday y = Color.RED; // Compile error: incompatible types

This allows the compiler to automatically detect all possible potential errors at compile time.

Comparison of enum

The enum class defined with enum is a reference type. As mentioned earlier, the equals() method is used for reference type comparison. If = = comparison is used, it compares whether the variables of two reference types are the same object. Therefore, for reference type comparison, always use the equals() method, with the exception of enum types.

This is because each constant of enum type has only one unique instance in the JVM, so it can be directly compared with = =:

if (day == Weekday.FRI) { // ok!
}
if (day.equals(Weekday.SUN)) { // ok, but more code!
}

enum type

What is the difference between enum classes defined by enum and other classes?

The answer is no difference. The type defined by enum is class, but it has the following characteristics:

  • The defined enum type always inherits from Java Lang. enum, and cannot be inherited;
  • Only instances of enum can be defined, but instances of enum cannot be created through the new operator;
  • Each instance defined is a unique instance of the reference type;
  • You can use the enum type for switch statements.

For example, the Color enumeration class we defined:

public enum Color {    RED, GREEN, BLUE;}

The class compiled by the compiler is like this:

public final class Color extends Enum { // Inherited from Enum and marked as final class
    // Each instance is globally unique:
    public static final Color RED = new Color();
    public static final Color GREEN = new Color();
    public static final Color BLUE = new Color();
    // private constructor to ensure that the new operator cannot be called externally:
    private Color() {}
}

Therefore, compiled enum class is no different from ordinary class. However, we can't define enum as a normal class. We must use the enum keyword, which is stipulated by Java syntax.

Because enum is a class and the value of each enumeration is a class instance, these instances have some methods:

name()

Returns the constant name, for example:

String s = Weekday.SUN.name(); // "SUN"

ordinal()

Returns the order of defined constants, counting from 0, for example:

int n = Weekday.MON.ordinal(); // 1

Changing the order of enumeration constant definitions will cause the return value of ordinal() to change. For example:

public enum Weekday {    SUN, MON, TUE, WED, THU, FRI, SAT;}

and

public enum Weekday {    MON, TUE, WED, THU, FRI, SAT, SUN;}

The ordinal is different. If a statement like if(x.ordinal()==1) is written in the code, it is necessary to ensure that the enum order of enum cannot be changed. New constants must be placed last.

Some children's shoes will think that if Weekday's enumeration constant needs to be converted to int, isn't it very convenient to use ordinal()? For example:

String task = Weekday.MON.ordinal() +"/ppt";
saveToFile(task);

However, if you accidentally change the order of enumeration, the compiler cannot detect this logical error. To write robust code, don't rely on the return value of ordinal(). Because enum itself is a class, we can define the construction method of private and add fields to each enumeration constant:

// enum
public class Main {
    public static void main(String[] args) {
        Weekday day = Weekday.SUN;
        if (day.dayValue == 6 || day.dayValue == 0) {
            System.out.println("Work at home!");
        } else {
            System.out.println("Work at office!");
        }
    }
}

enum Weekday {
    MON(1), TUE(2), WED(3), THU(4), FRI(5), SAT(6), SUN(0);

    public final int dayValue;

    private Weekday(int dayValue) {
        this.dayValue = dayValue;
    }
}

In this way, there is no need to worry about the change of order. When adding an enumeration constant, you also need to specify an int value.

Note: the fields of enumeration class can also be non final, that is, they can be modified at run time, but this is not recommended!

By default, calling toString() on an enumeration constant returns the same string as name(). However, toString () can be overridden, while name () cannot. We can add toString () method to Weekday:

// enum
public class Main {
    public static void main(String[] args) {
        Weekday day = Weekday.SUN;
        if (day.dayValue == 6 || day.dayValue == 0) {
            System.out.println("Today is " + day + ". Work at home!");
        } else {
            System.out.println("Today is " + day + ". Work at office!");
        }
    }
}

enum Weekday {
    MON(1, "Monday"), TUE(2, "Tuesday"), WED(3, "Wednesday"), THU(4, "Thursday"), FRI(5, "Friday"), SAT(6, "Saturday"), SUN(0, "Sunday");

    public final int dayValue;
    private final String chinese;

    private Weekday(int dayValue, String chinese) {
        this.dayValue = dayValue;
        this.chinese = chinese;
    }

    @Override
    public String toString() {
        return this.chinese;
    }
}

toString() is overridden to make the output more readable.

Note: to judge the name of an enumeration constant, always use the name() method, never call toString()!

switch

Finally, enumeration classes can be applied to switch statements. Because enumeration classes naturally have type information and a limited number of enumeration constants, they are more suitable for use in switch statements than int and String types:

// switch
public class Main {
    public static void main(String[] args) {
        Weekday day = Weekday.SUN;
        switch(day) {
        case MON:
        case TUE:
        case WED:
        case THU:
        case FRI:
            System.out.println("Today is " + day + ". Work at office!");
            break;
        case SAT:
        case SUN:
            System.out.println("Today is " + day + ". Work at home!");
            break;
        default:
            throw new RuntimeException("cannot process " + day);
        }
    }
}

enum Weekday {
    MON, TUE, WED, THU, FRI, SAT, SUN;
}

With the default statement, you can automatically report an error when an enumeration constant is omitted, so as to find the error in time.

Summary

Java uses enum to define enumeration types, which are compiled by the compiler as final class XXX extensions enum {...};

Get the string defined by the constant through name(). Be careful not to use toString();

Return the order of constant definitions through ordinal() (meaningless);

You can write construction methods, fields, and methods for enum

The construction method of enum should be declared as private, and the field is strongly recommended to be declared as final;

enum is suitable for use in switch statements.

Record class

When using String, Integer and other types, these types are invariant classes. An invariant class has the following characteristics:

  1. final is used when defining class, and subclasses cannot be generated;
  2. final is used for each field to ensure that no field can be modified after the instance is created.

Suppose we want to define a Point class with two variables x and y, and it is an invariant class, which can be written as follows:

public final class Point {
    private final int x;
    private final int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int x() {
        return this.x;
    }

    public int y() {
        return this.y;
    }
}

In order to ensure the comparison of invariant classes, you also need to override the equals() and hashCode() methods correctly, so that they can be used normally in collection classes. Later, we will explain in detail how to correctly override equals() and hashCode(). Here we demonstrate the writing method of Point invariant class. The purpose is that these codes are very simple but cumbersome.

record

Starting with Java 14, a new record class is introduced. When we define the record class, we use the keyword record. Rewrite the above Point class into record class, and the code is as follows:

// Record 
public class Main {
    public static void main(String[] args) {
        Point p = new Point(123, 456);
        System.out.println(p.x());
        System.out.println(p.y());
        System.out.println(p);
    }
}

public record Point(int x, int y) {
}

Carefully observe the definition of Point:

public record Point(int x, int y) {}

Rewrite the above definition to class, which is equivalent to the following code:

public final class Point extends Record {
    private final int x;
    private final int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int x() {
        return this.x;
    }

    public int y() {
        return this.y;
    }

    public String toString() {
        return String.format("Point[x=%s, y=%s]", x, y);
    }

    public boolean equals(Object o) {
        ...
    }
    public int hashCode() {
        ...
    }
}

In addition to decorating the class and each field with final, the compiler also automatically creates a constructor for us, a method with the same name as the field name, and overrides toString(), equals() and hashCode() methods.

In other words, using the record keyword, you can write out an invariant class line by line.

Similar to enum, we cannot derive directly from record, but can only be inherited by the compiler through the record keyword.

Construction method

By default, the compiler automatically creates a constructor in the order of variables declared by record, and assigns values to fields within the method. So the question is, what should we do if we want to check the parameters?

Assuming that x and y of Point class do not allow negative numbers, we have to add check logic to the construction method of Point:

public record Point(int x, int y) {
    public Point {
        if (x < 0 || y < 0) {
            throw new IllegalArgumentException();
        }
    }
}

Notice method public Point {...} It is called Compact Constructor. Its purpose is to let us write check logic. The final construction method generated by the compiler is as follows:

public final class Point extends Record {
    public Point(int x, int y) {
        // This is our Compact Constructor:
        if (x < 0 || y < 0) {
            throw new IllegalArgumentException();
        }
        // This is the assignment code that the compiler continues to generate:
        this.x = x;
        this.y = y;
    }
    ...
}

As a Point of record, you can still add static methods. A common static method is the of() method, which is used to create points:

public record Point(int x, int y) {
    public static Point of() {
        return new Point(0, 0);
    }
    public static Point of(int x, int y) {
        return new Point(x, y);
    }
}

In this way, we can write more concise code:

var z = Point.of();
var p = Point.of(123, 456);

Summary

Starting from Java 14, a new record keyword is provided to easily define the Data Class:

  • Use record to define invariant classes;
  • You can write Compact Constructor to verify the parameters;
  • You can define static methods.

BigInteger

BigInteger

In Java, the maximum range of integers provided natively by the CPU is 64 bit long integers. Using long integers can be calculated directly through CPU instructions, which is very fast.

What if the integer range we use exceeds the long type? At this time, you can only use software to simulate a large integer. java.math.BigInteger is an integer of any size. BigInteger uses an int [] array to simulate a very large integer:

BigInteger bi = new BigInteger("1234567890");
System.out.println(bi.pow(5)); // 2867971860299718107233761438093672048294900000

When performing operations on BigInteger, only instance methods can be used, such as addition:

BigInteger i1 = new BigInteger("1234567890");
BigInteger i2 = new BigInteger("12345678901234567890");
BigInteger sum = i1.add(i2); // 12345678902469135780

Compared with long type integer operation, BigInteger has no range limitation, but its disadvantage is that it is relatively slow.

BigInteger can also be converted to long type:

BigInteger i = new BigInteger("123456789000");
System.out.println(i.longValue()); // 123456789000
System.out.println(i.multiply(i).longValueExact()); // java.lang.ArithmeticException: BigInteger out of long range

When using the longValueExact() method, an ArithmeticException will be thrown if it exceeds the range of long type.

BigInteger, like Integer and Long, is an immutable class and also inherits from the Number class. Because Number defines several methods for converting to basic types:

  • Convert to byte: byteValue()
  • Convert to short: shortValue()
  • Convert to int: intValue()
  • Convert to long: longValue()
  • Convert to float: floatValue()
  • Convert to double: doubleValue()

Therefore, through the above method, BigInteger can be converted to basic type. If the range represented by BigInteger exceeds the range of the basic type, the high-order information will be lost during conversion, that is, the result may not be accurate. If you need to convert to a basic type accurately, you can use methods such as intValueExact(), longValueExact(). If the conversion is out of range, you will directly throw an ArithmeticException exception.

If the value of BigInteger even exceeds the maximum range of float (3.4x1038), what is the returned float?

// BigInteger to float import java.math.BigInteger; 
public class Main {
    public static void main(String[] args) {
        BigInteger n = new BigInteger("999999").pow(99);
        float f = n.floatValue();
        System.out.println(f);
    }
}

Summary

BigInteger is used to represent an integer of any size;

BigInteger is an invariant class and inherits from Number;

When converting BigInteger to basic type, methods such as longValueExact() can be used to ensure accurate results.

BigDecimal

Like BigInteger, BigDecimal can represent a floating-point number of any size with completely accurate precision.

BigDecimal bd = new BigDecimal("123.4567");System.out.println(bd.multiply(bd)); // 15241.55677489

BigDecimal uses scale() to represent the number of decimal places, for example:

BigDecimal d1 = new BigDecimal("123.45");BigDecimal d2 = new BigDecimal("123.4500");BigDecimal d3 = new BigDecimal("1234500");System.out.println(d1.scale()); // 2, two decimal places system out. println(d2.scale()); //  4System. out. println(d3.scale()); //  0

Through the stripTrailingZeros() method of BigDecimal, a BigDecimal can be formatted as an equal, but the BigDecimal at the end 0 is removed:

BigDecimal d1 = new BigDecimal("123.45");
BigDecimal d2 = new BigDecimal("123.4500");
BigDecimal d3 = new BigDecimal("1234500");
System.out.println(d1.scale()); // 2. Two decimal places
System.out.println(d2.scale()); // 4
System.out.println(d3.scale()); // 0

If a BigDecimal's scale() returns a negative number, for example, - 2, it means that the number is an integer with two zeros at the end.

You can set the scale of a BigDecimal. If the precision is lower than the original value, round it or truncate it directly according to the specified method:

BigDecimal d1 = new BigDecimal("123.4500");
BigDecimal d2 = d1.stripTrailingZeros();
System.out.println(d1.scale()); // 4
System.out.println(d2.scale()); // 2, because 00 is removed

BigDecimal d3 = new BigDecimal("1234500");
BigDecimal d4 = d3.stripTrailingZeros();
System.out.println(d3.scale()); // 0
System.out.println(d4.scale()); // -2

When adding, subtracting and multiplying BigDecimal, the accuracy will not be lost, but when dividing, there is a situation that cannot be divided completely. In this case, you must specify the accuracy and how to truncate:

import java.math.BigDecimal;
import java.math.RoundingMode;
public class Main {
    public static void main(String[] args) {
        BigDecimal d1 = new BigDecimal("123.456789");
        BigDecimal d2 = d1.setScale(4, RoundingMode.HALF_UP); // Rounding, 123.4568
        BigDecimal d3 = d1.setScale(4, RoundingMode.DOWN); // Direct truncation, 123.4567
        System.out.println(d2);
        System.out.println(d3);
    }
}

You can also divide BigDecimal and find the remainder at the same time:

BigDecimal d1 = new BigDecimal("123.456");
BigDecimal d2 = new BigDecimal("23.456789");
BigDecimal d3 = d1.divide(d2, 10, RoundingMode.HALF_UP); // Keep 10 decimal places and round
BigDecimal d4 = d1.divide(d2); // Error: ArithmeticException, because it cannot be divided completely

When the divideandremander () method is called, the returned array contains two BigDecimal, namely quotient and remainder, where quotient is always an integer and remainder will not be greater than divisor. We can use this method to judge whether two BigDecimal are integer multiples:

import java.math.BigDecimal;
public class Main {
    public static void main(String[] args) {
        BigDecimal n = new BigDecimal("12.345");
        BigDecimal m = new BigDecimal("0.12");
        BigDecimal[] dr = n.divideAndRemainder(m);
        System.out.println(dr[0]); // 102
        System.out.println(dr[1]); // 0.105
    }
}

Compare BigDecimal

When comparing whether the values of two BigDecimal are equal, pay special attention to that using the equals() method requires not only the values of two BigDecimal to be equal, but also their scale() to be equal:

BigDecimal n = new BigDecimal("12.75");
BigDecimal m = new BigDecimal("0.15");
BigDecimal[] dr = n.divideAndRemainder(m);
if (dr[1].signum() == 0) {
    // n is an integral multiple of m
}

The compareTo() method must be used for comparison, which returns negative, positive and 0 respectively according to the size of the two values, indicating less than, greater than and equal to respectively.

Always use compareTo() to compare the values of two BigDecimal, not equals()!

If you look at the source code of BigDecimal, you can find that a BigDecimal is actually represented by a BigInteger and a scale, that is, BigInteger represents a complete integer and scale represents the number of decimal places:

public class BigDecimal extends Number implements Comparable<BigDecimal> {
    private final BigInteger intVal;
    private final int scale;
}

BigDecimal also inherits from Number and is an immutable object.

Summary

BigDecimal is used to represent accurate decimal, which is often used in financial calculation;

To compare whether the values of BigDecimal are equal, you must use compareTo() instead of equals().

Common tools

Math

As the name suggests, Math class is used for mathematical calculation. It provides a large number of static methods to facilitate us to realize mathematical calculation:

Absolute value:

Math.abs(-100); // 100Math.abs(-7.8); // 7.8

Take the maximum or minimum value:

Math.max(100, 99); // 100Math.min(1.2, 2.3); // 1.2

Calculate xy power:

Math.pow(2, 10); // The 10th power of 2 = 1024

Calculation √ x:

Math.sqrt(2); // 1.414...

Calculate ex power:

Math.exp(2); // 7.389...

Calculate the logarithm based on e:

Math.log(4); // 1.386...

Calculate the base 10 logarithm:

Math.log10(100); // 2

Trigonometric function:

Math.sin(3.14); // 0.00159...Math.cos(3.14); // -0.9999...Math.tan(3.14); // -0.0015...Math.asin(1.0); // 1.57079...Math.acos(1.0); // 0.0

Math also provides several mathematical constants:

double pi = Math.PI; // 3.14159...double e = Math.E; // 2.7182818...Math.sin(Math.PI / 6); // sin(π/6) = 0.5

Generate a random number x, the range of X is 0 < = x < 1:

Math.random(); // 0.53907...  It's different every time

If we want to generate a random number with an interval of [min, Max], we can use Math.random() to achieve the following calculation:

// Random number with interval in [MIN, MAX)
public class Main {
    public static void main(String[] args) {
        double x = Math.random(); // The range of x is [0,1)
        double min = 10;
        double max = 50;
        double y = x * (max - min) + min; // The range of y is [10,50)
        long n = (long) y; // The range of n is an integer of [10,50]
        System.out.println(y);
        System.out.println(n);
    }
}

Some children as like as two peas may notice that the Java standard library also provides a StrictMath, which provides almost the same method as Math. The difference between the two classes is that due to errors in floating-point calculation, the calculation results of different platforms (such as x86 and ARM) may be inconsistent (referring to different errors). Therefore, strictmath ensures that the calculation results of all platforms are exactly the same, and math will try to optimize the calculation speed for the platform. Therefore, in most cases, math is enough.

Random

Random is used to create pseudo-random numbers. The so-called pseudo-random number means that as long as an initial seed is given, the generated random number sequence is exactly the same.

To generate a random number, you can use nextInt(), nextLong(), nextFloat(), nextDouble():

Random r = new Random();
r.nextInt(); // 2071575453, it's different every time
r.nextInt(10); // 5. Generate an int between [0,10]
r.nextLong(); // 881164929570369305, it's different every time
r.nextFloat(); // 0.54335... Generate a float between [0,1]
r.nextDouble(); // 0.3716... Generate a double between [0,1]

Some children's shoes asked that the random numbers generated are different every time the program is run. They don't see the characteristics of pseudo-random numbers.

This is because when we create a Random instance, if we do not give a seed, we use the current time stamp of the system as the seed. Therefore, each time we run, the seed is different, and the pseudo-Random number sequence will be different.

If we specify a seed when creating a Random instance, we will get a completely determined Random number sequence:

import java.util.Random; 
public class Main {
    public static void main(String[] args) {
        Random r = new Random(12345);
        for (int i = 0; i < 10; i++) {
            System.out.println(r.nextInt(100));
        }
        // 51, 80, 41, 28, 55...
    }
}

SecureRandom

If there is a pseudo-random number, there is a true random number. In fact, the real real random number can only be obtained through the principles of quantum mechanics, and what we want is an unpredictable and safe random number. SecureRandom is used to create safe random numbers:

SecureRandom sr = new SecureRandom();System.out.println(sr.nextInt(100));

SecureRandom cannot specify seed, it uses RNG (random number generator) algorithm. There are actually many different underlying implementations of SecureRandom in JDK. Some use secure random seeds and pseudo-random number algorithms to generate secure random numbers, and some use real random number generators. In actual use, high-strength safe random number generators can be obtained first. If not provided, ordinary safe random number generators can be used:

import java.util.Arrays; import java.security.SecureRandom; import java.security.NoSuchAlgorithmException; 
public class Main {
    public static void main(String[] args) {
        SecureRandom sr = null;
        try {
            sr = SecureRandom.getInstanceStrong(); // Get high strength safe random number generator
        } catch (NoSuchAlgorithmException e) {
            sr = new SecureRandom(); // Gets the normal secure random number generator
        }
        byte[] buffer = new byte[16];
        sr.nextBytes(buffer); // Fill the buffer with safe random numbers
        System.out.println(Arrays.toString(buffer));
    }
}

The security of SecureRandom is to generate random numbers through secure random seeds provided by the operating system. This seed is "entropy" generated by various random events such as CPU thermal noise, bytes of read-write disk, network traffic and so on.

In cryptography, secure random numbers are very important. If insecure pseudo-random numbers are used, all encryption systems will be broken. Therefore, always remember that you must use SecureRandom to generate secure random numbers.

Summary

Common tool classes provided by Java include:

  • Math: mathematical calculations
  • Random: generate pseudo-random numbers
  • SecureRandom: generate secure random numbers

Keywords: Java

Added by mattheww on Tue, 25 Jan 2022 05:30:49 +0200