Java核心基础学习笔记

Java核心基础学习笔记

参考 malajava 《Java核心基础》课程学习

Java开发环境:OpenJDK11

Java开发工具:IDEA 、VSCode、Eclipse、Cmder

1、从简单的Java程序入手

1.1、创建一个Java程序

Hello.java 为例

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

利用 cmd命令 运行 Hello.java 文件

java Hello.java

注:Java11 支持 java命令 直接编译和运行 Java程序,且不会产生 .class 字节码文件

1.2、分析Java程序结构

以 Hello.java 为例做分析:

  • 第一行

    • public 是 修饰符,可以修饰 类(class) 、方法( method ) 、字段( field )
    • class 是用来声明 类 的关键字,在 class 之后紧跟的 Hello 就是类的名称
    • 类名命名要求 被 public 修饰的 class 的名称 必须与相应的 .java 文件名称保持一致
  • 第二行

    • public

    是个修饰符,被 public 修饰的方法表示方法是公开的

    • 修饰符 可以理解成 汉语 中的形容词,比如 一个 温柔的、漂亮的 小女孩

    • static

    是个修饰符,被 static 修饰的方法是属于类的

    • 这里的 static 和 public 谁在前谁在后,跟 温柔的、漂亮的 谁在前在后没什么区别

    • void 是 方法的返回类型,表示方法不需要返回任何数据

    • main 是方法名称,在 JVM 运行一个类中的代码时,这个 main 方法就是一个入口

    • main 之后的 ( ) 内部

    是参数列表,其中的 args 是参数名称,String[] 是参数的类型

    • main 之后的 ( ) 内部 String[] 表示参数类型,canShu 是参数名称 ( 名称可以是任意的符合命名规则的名称 )

    • String

    是 java.lang 包中的一个非常常用的类(笔试、面试也经常遇到)

    • void main( String[] ) 表示一个方法的 返回类型、方法名称、参数类型,必须按照顺序书写
  • 第三行

    • 在 void main () 之后的 { } 内部所书写的就是这个方法的方法体(也就是这个方法所要完成的操作)
    • System 也是 java.lang 包中的一个常用类
    • out 是 System 类中的一个属于类的变量(称作类变量)
    • println 是 out 对应的类中的一个方法,用于输出数据并换行

1.3、关于类的命名规则

  • 类名可以以 $_英文字符 为前缀,其后可以跟 $_英文字符数字
  • 类名中每个单词首字母一律大写,比如 Mouse
public class Mouse  {
      public static void main(String[] args) {
          System.out.println("我是一只饥饿的小老鼠");
      }
}

1.4、Java相关文件

以 Ox.java 为例做分析:

  • 文件 Ox.java 是程序员编写的【源代码】
  • 编译: javac Ox.java
    • 源代码经过 javac 命令编译后产生的 Ox.class 文件被称作【字节码】
  • 运行: java Ox
  • 通过 java 命令可以启动一个 JVM ,在 JVM 中运行的是 字节码
  • 另外在 Java 11 中支持采用 java Ox.java 的形式完成 编译、运行 (但不会产生字节码文件)
public class Ox {
      public static void main( String[] args ) {
          System.out.println( "当前时间是:" );
          // 在控制台中输出当前时间 ( 是一个数字,这个数字表示从 历元( Epoch ) 到 当前时刻 所经历的毫秒数 )
          System.out.println( System.currentTimeMillis() );
      }
}

2、Java数据类型

在 Java语言中,数据类型分为基本数据类型、引用数据类型

2.1、命名规则

  • 可以以 $_英文字符 为前缀,其后可以跟 $ 、_ 、英文字符 、阿拉伯数字
  • 如果变量名称中只有一个单词,则该单词所有字母全部小写
  • 如果变量名称中包含多个单词,则第一个单词全部小写,从第二个单词开始每个单词首字母大写(驼峰命名法)

2.2、int 、long 、double举例

int 、long 、double 是不同的数据类型,它们所表示的数据范围是不同的

等号 ( = ) 表示赋值运算符,运算规则是 从右向左,表示将 等号右边的结果 赋值 给 等号左边的变量

public class Tiger {
    public static void main(String[] args) {
        // 获取当前时间(从历元开始至现在所经历的毫秒值)
        var millis = System.currentTimeMillis(); // 尽管支持 var 但不建议使用
        System.out.println( millis );
        // int time = System.currentTimeMillis(); // 不兼容的类型: 从long转换到int可能会有损失
        long time = System.currentTimeMillis();
        System.out.println( time );
        double ms = System.currentTimeMillis();
        System.out.println( ms ); 
    }
}

2.3、Java数组的简单应用

public class Rabbit {
    public static void main( String[] args ) {
        // args 是一个 String 类型的数组 ( String[] 表示 String 数组 )
        System.out.println( "参数个数:" );
        System.out.println( args.length ); // 使用 length 来获取数组长度
        for( int i = 0 ; i < args.length ; i++ ) {
            System.out.println( args[ i ] ); // 与 C / C++ 相同,通过数组名称和下标来获取元素
        }
    }
}

2.4.1、基本数据类型

基本数据类型又称原始数据类型/原生类型,包含8种基本数据类型

  • byte 字节型数据类型
  • short 也可以像 byte 那样节省空间,一个short变量是int型变量所占空间的二分之一
  • int 在Java源代码中直接书写的整数值默认是 int 类型
  • long 需要在后面加 L/l
  • float 需要在后面加 F/f
  • double 在Java源代码中直接书写的浮点数默认是 double 类型
  • boolean 在Java语言中使用 boolean 类型表示逻辑值,其取值只有 true 、false
  • char 对于 char 类型变量取值来说,可以使用 单引号直接将字符引起来,也可以使用 Unicode 编码
public class Loong {

   public static void main( String[] args ) {

      // 基本数据类型(也称作原始数据类型或原生类型)
      byte first = 100 ;
      System.out.println( first );

      short second = 30000 ;
      System.out.println( second );

      int third = 66666 ;
      System.out.println( third );
 
      // long fourth = 12345678901234 ; // 错误: 整数太大
      long fourth = 12345678901234L ; // long 类型的数值之后加上 L 或 l 
      System.out.println( fourth );

      // float fifth = 3.141592653589793 ; // 不兼容的类型: 从double转换到float可能会有损失
      float fifth = 3.141592653589793F ; // float 类型的数值之后加上 F 或 f
      System.out.println( fifth );

      // double 类型的数值之后 可以加上 D 或 d ,也可以不加
      double sixth = 3.141592653589793238462643383279D ;
      System.out.println( sixth );

      // 在 C / C++ 有 非零即真 的说法,在Java只能使用 true 或 false 来表示
      boolean seventh = false ; // boolean 类型的取值只有两个 true 、false
      System.out.println( seventh );

      char ch1 = '中' ; // char 类型的取值 可以是 单引号 引起来的 单个字符
      System.out.println( ch1 );

      // 直接将某个范围内的整数赋值给 char 类型的变量,这个整数会根据 Unicode 编码转换为相应的字符 ( ASC II 码 是 Unicode 编码的子集 )
      char ch2 = 100 ;
      System.out.println( ch2 );

      char ch3 = '\u8fa3' ; // 在单引号中可以使用 Unicode 编码来表示单个字符
      System.out.println( ch3 );
   }

}

2.4.2、引用数据类型

  • 与 基本数据类型 对应的是 引用类型
  • 在基本数据类型的变量中存储的就是相应类型的数值
  • 在引用类型的变量中存储的是一个内存地址(通过这个地址可以找到真正的数据)
public class Snake {

   public static void main( String[] args ) {

      int first = 100 ; // int 类型的变量 first 中直接存储了数值 100
      System.out.println( first );

      // 对于 main 方法的参数 args 来说就是一个 引用类型 的变量 (引用变量) 
      System.out.println( args ); // 所有引用变量中都存储了一个内存地址

      // 通过这个内存地址可以找到真正的数据所在的位置
      int n = args.length ; // 获取数组长度
      for( int i = 0 ; i < n ; i++ ) {
         System.out.println( args[ i ] ) ;
      }

   }

2.4.3、基本数据类型的分类

2.4.3.1、整数类型
  • 基本数据类型包含:byte 、short 、int 、long
  • Java 语言中为 8 中基本数据类型提供了相应的包装类:
    • byte <===> Byte
    • short <===> Short
    • int <===> Integer
    • long <===> Long
  • 基本数据类型中整数类型对应包装类都可以通过 MAX_VALUE 获取最大值,通过 MIN_VALUE 获取最小值
  • 从 JDK 1.7 开始允许在 Java 源代码中使用 二进制形式 来书写整数值 ( 以 0b 或 0B 为前缀 )
  • 在 Java 源代码中可以使用 十六进制 形式来书写整数值 ( 以 0x 或 0X 为前缀 )
public class Horse {

    public static void main(String[] args) {

        // 在 Java 语言中,byte 类型的数值 占 8 Bit (二进制位)
        // byte first = 200 ; // 不兼容的类型: 从int转换到byte可能会有损失
        byte first = 100; // 在 Java 源代码中直接书写的 整数都是 int 类型
        System.out.println(first);
        // 在 "" 内部直接书写的内容就是 字符串 ( 对应 String 类型 )
        System.out.print("byte min value : "); // 注意 print 方法可以输出数据但不换行
        System.out.println(Byte.MIN_VALUE); // 通过 Byte 类的 MIN_VALUE 来获取 byte 最小值

        // 在 Java 语言中,与基本数据类型 byte 对应的 包装类类型是 Byte 类 ( 以后会讲 )

        System.out.print("byte max value : ");
        System.out.println(Byte.MAX_VALUE); // 通过 Byte 类的 MAX_VALUE 来获取 byte 最大值

        // 在 Java 语言中 short 类型的数值 占 16 Bit (二进制位)
        short second = 30000;
        System.out.println(second);

        System.out.print("short min value : ");
        System.out.println(Short.MIN_VALUE); // 通过 Short 类的 MIN_VALUE 来获取 short 最小值

        System.out.print("short max value : ");
        System.out.println(Short.MAX_VALUE); // 通过 Short 类的 MAX_VALUE 来获取 short 最大值

        // 在 Java 语言中 int 类型的数值 占 32 Bit (二进制位)
        // int third = 0b0000_0000_0000_0000_0000_0000_0000_0000 ; // 以 0b 或 0B 为前缀
        int third = 0b1000_0000_0000_0000_0000_0000_0000_0000;
        // int third = 0b0111_1111_1111_1111_1111_1111_1111_1111 ;
        System.out.println(third);

        System.out.print("int min value : ");
        System.out.println(Integer.MIN_VALUE); // 通过 Integer 类的 MIN_VALUE 来获取 int 最小值

        System.out.print("int max value : ");
        System.out.println(Integer.MAX_VALUE); // 通过 Integer 类的 MAX_VALUE 来获取 int 最大值

        // 在 Java 语言中 long 类型的数值 占 64 Bit (二进制位)
        long fourth = 0b01111111_11111111_11111111_11111111_11111111_11111111_11111111_11111111L;
        System.out.println(fourth); // Long.MAX_VALUE

        long fifth = 0b10000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000L;
        System.out.println(fifth); // Long.MIN_VALUE

        long sixth = 0x8000_0000_0000_0000L; // 以十六进制形式表示整数,以 0x 或 0X 为前缀
        System.out.println(sixth);

    }

}
2.4.3.2、浮点数类型
  • 不论是 float 还是 double 都遵循 IEEE 754 标准
  • float 类型的数值占 32 个二进制位
  • double 类型的数值占 64 个二进制位
  • 在 Java 源代码中直接书写的 浮点数 默认都是 double 类型
public class Sheep {
    public static void main(String[] args) {

        // 在 Java 语言中 double 类型的数值 占 64 Bit ( 二进制位 )
        double first = 3.14;
        System.out.println(first);

        // 这里使用的 + 运算符表示 拼接字符串
        System.out.println("double min value : " + Double.MIN_VALUE);
        System.out.println("double max value : " + Double.MAX_VALUE);

        // 在 Java 语言中 float 类型的数值 占 32 Bit ( 二进制位 )
        float second = 3.14F; // 在直接书写的浮点数末尾增加 F 或 f 后缀来表示 float 类型数值
        System.out.println(second);

        System.out.println("float min value : " + Float.MIN_VALUE);
        System.out.println("float max value : " + Float.MAX_VALUE);

    }
}
2.4.3.3、字符、布尔类型
  • 字符类型:Char
    • char 类型的数值 占 16 Bit (二进制位)
    • char 类型的取值 可以使用 '' 引起来的 字符、Unicode 编码、转义字符,也可以是 符合 char 数据范围的 正整数
    • char 类型的取值范围是 \u0000 ~ \uFFFF 之间 ( 对应的正整数范围是 0x0000 ~ 0xffff ),其中 \u0000 表示一个空白字符
  • 布尔(逻辑)类型:boolean
    • boolean 类型的数值占 1 Bit (二进制位) 【这是规范要求的】
    • boolean 类型包含 true 、false
    • 在 C / C++ 有 非零即真 的说法,在Java只能使用 true 或 false 来表示
public class Monkey {

    public static void main( String[] args ) {

        // 在 Java 语言中 boolean 类型的数值 占 1 Bit (二进制位) 【这是规范要求的,但是JVM可能会有自己的实现】
        boolean exists ; // 仅声明一个 boolean 类型的变量,但没有为它赋值

        // 与 C / C++ 语言不同,Java 中的 变量必须 在 赋值之后才能使用 ( 首次为某个变量赋值被称作 初始化 )
        // System.out.println( exists ); // 【编译失败】错误: 可能尚未初始化变量exists
        
        exists = true ; // 为 已经声明过的 exists 变量赋值
        System.out.println( exists );

        exists = false ; // 为 已经声明过的 exists 变量赋值
        System.out.println( exists );

        // exists = 0 ; // 【编译失败】: 不兼容的类型: int无法转换为boolean ( Java 中没有 零表示假、非零即真 这一说)

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        char ch1 = 'a' ;
        System.out.println( ch1 ); // a

        char ch2 = 97 ;
        System.out.println( ch2 ); // ?

        char ch3 = '\u8fa3' ;
        System.out.println( ch3 ); // 辣

        char ch4 = '\n' ; // 转义字符
        System.out.println( ch4 ); 

        System.out.println( "end" );

        // 在 Java 语言中 char 类型的数值 占 16 Bit (二进制位)
        char ch5 = '\u0000' ;  // \u0000 ~ \uFFFF
        System.out.println( ch5 ); // \u0000 表示一个空白字符,这个空白字符不是 空格 也不是 Tab 键产生的空白

        ch5 = '\uFFFF';
        System.out.println( ch5 ); 

    }

}

2.4.4、基本数据类型的转换

关于数值型的数据类型(数字范围从小到大排列):

8bit 16bit 32bit 64bit 32bit 64bit
byte short int long float double

注:将 数字范围较小的类型的变量的值 赋值给 数字范围较大的类型的变量 时,会发生【自动类型提升】

下面来展示一下几种数据类型的转换:

2.4.4.1、short 和 char 转换
  • short类型 转化为 char类型时,可能会有损失
  • short 和 char 之间不能实现【自动类型转换】,转换是通过 int类型 进行的
  • short 和 char 都可以【自动类型提升】为 数字范围较大 的类型 ( int 、long 、float 、double )
public class Chicken {

    public static void main( String[] args ) {
        // 声明一个 byte 类型的、名称为 first 的 变量并为其赋值
        byte first = 100 ; // 100 对应的二进制形式刚好是用 8 个二进制位所能表示的
        System.out.println( first );

        short second = first ; // 本来是 byte 类型的 first 变量中存储的数值 会提升为 short 类型
        System.out.println( second );

        // int third = second ; // 本来是 short 类型的 second 变量中存储的数值 会提升为 int 类型
        int third = first ; // 本来是 byte 类型的 first 变量中存储的数值 会提升为 int 类型
        System.out.println( third );

        long fourth = first ; // 本来是 byte 类型的 first 变量中存储的数值 会提升为 long 类型
        System.out.println( fourth );

        float fifth = first ; // 本来是 byte 类型的 first 变量中存储的数值 会提升为 float 类型
        System.out.println( fifth );

        double sixth = first ; // 本来是 byte 类型的 first 变量中存储的数值 会提升为 double 类型
        System.out.println( sixth );

    }

}
public class Dog {

    public static void main( String[] args ) {

        // short : 16bit 
        short first = 97 ;
        System.out.println( first );
        // char second = first ; // 【编译失败】错误: 不兼容的类型: 从short转换到char可能会有损失
        int second = first ; // 本来是 short 类型的 first 变量中存储的数值会自动提升为 int 类型后再赋值给 second 变量
        System.out.println( second );

        // char : 16bit
        char third = 'a' ;
        System.out.println( third );
        // short fourth = third ; // 【编译失败】错误: 不兼容的类型: 从short转换到char可能会有损失
        int fourth = third ; // 本来是 char 类型的 third 变量中存储的数值会自动提升为 int 类型后再赋值给 fourth 变量
        System.out.println( fourth );

    }

}
2.4.4.2、浮点数类型的转换
  • 在 Java 语言中,如果遇到 范围比 int 小的类型发生运算时,首先需要提升为 int 类型后再运算
  • 在 Java 语言中,两个 float 类型的变量相加后仍然是 float 类型 ( Java 8 和 Java 11 测试通过 )
  • 在 Java 语言中,一个 float 类型的数值 与 一个 double 类型的数值 发生运算时,float 数值首先提升为 double 类型后再运算
public class Pig {

    public static void main( String[] args ) {

        byte first = 100 ;
        byte second = 50 ;

        // byte third = first + second ; // 【编译失败】错误: 不兼容的类型: 从int转换到byte可能会有损失
        // 两个 byte 类型的 变量 相加时,会首先自动类型提升为 int 类型,再运算
        int third = first + second ; 
        System.out.println( third );

        short fourth = 100 ;
        short fifth = 200 ;

        // short sixth = fourth + fifth ; // 【编译失败】错误: 不兼容的类型: 从int转换到short可能会有损失
        // 两个 short 类型的 变量 相加时,会首先自动类型提升为 int 类型,再运算
        int sixth = fourth + fifth ; 
        System.out.println( sixth );

        // byte result = first + fourth ; // ???
        // short result = first + fourth ; // ???
        // int result = first + fourth ; // ???
        // long result = first + fourth ; // ???

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        float one = 3.14F ;
        float two = 3.14F ;

        float three = one + two ; // 注意: 这里至少在 Java 8 中测试是通过的
        System.out.println( three );

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        double four = 3.1415926 ;

        // float five = one + four ; // 【编译失败】错误: 不兼容的类型: 从double转换到float可能会有损失
        double five = one + four ; 
        System.out.println( five );

    }

}
2.4.4.3、char类型的转换

两个 char 类型的取值 是可以相加的,加完之后是个 正整数

案例代码:

public class Test1 {

    public static void main ( String[] args ) {

        char first = 'A' ;
        char second = 'B' ;
        
        // // 错误: 不兼容的类型: 从int转换到char可能会有损失
        // char result1 = first + second ; 
        // System.out.println( result1 );

         // 自动类型提升
        int result2 = first + second ;
        System.out.println( result2 );

    }
    
}
2.4.4.4、boolean类型转换

boolean 类型取值 不会自动类型提升,也不参与类型转换

public class Test2 {

    public static void main ( String[] args ) {

        boolean first = true ;
        boolean second = false ;

        boolean restlt1 = first + second ; // 错误: 二元运算符 '+' 的操作数类型错误

        byte restlt2 = first  ; // 错误: 不兼容的类型: boolean无法转换为byte

    }
    
}

从运行结果可以看出:

  • 错误: 二元运算符 '+' 的操作数类型错误
  • 错误: 不兼容的类型: boolean无法转换为byte
2.4.4.5、byte short char 混合运算

byte 、short 、char 如果混合运算则会首先提升为 int

public class Test2 {

    public static void main ( String[] args )  {

        short first = 197 ;
        byte second = 5;    
        char third = 'A' ;
        int result = first + second + third ;
        System.out.println( result );

    }
    
}

2.4.5、强制类型转换

  1. 强制类型转换的使用方法: 目标类型 变量名称 = (目标类型) 源变量名或数值 ;
  2. 整数类型的强制类型转换会舍弃高位、留下低位 (舍弃多少高位、留下多少低位,取决于源类型和目标类型)
  3. 反码和补码 : 补码 = 反码 + 1 ========> 反码 = 补码 - 1
  4. 原码和反码 : 原码按位取反得到反码 ,反码按位取反得到原码
  5. 原码和补码 : 原码 + 补码 = 0
2.4.5.1、第一种情况
public class Test3 {

    public static void main ( String[] args ) {

        int first = 100 ; // 0000_0000_0000_0000_0000_0000_0110_0100
        System.out.println( first );

        // 强制类型转换:int ----> byte
        // 0110_0100
        byte second = (byte)  first ;
        System.out.println( second );

        // 强制类型转换:130 ----> byte
        // 130 : 0000_0000_0000_0000_0000_0000_1000_0010 【32bit】
        // (byte)130 : 1000_0010 【8bit】
        byte third = (byte)  130;
        System.out.println( third );

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        //  (byte)130 : 1000_0010 【8bit】 -126
        // 得到某个数的反码 :  1000_0001 【8bit】-127
        // 按位取反得到某个数:  0111_1110  【8bit】  126
        System.out.println( (byte)0b1000_0010 );
        System.out.println( (byte)0b1000_0001 );
        System.out.println( (byte)0b0111_1110 );

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        System.out.println( (byte)0b0111_1111 ); // 将一个正数的二进制形式当做 【原码】
        System.out.println( (byte)0b1000_0000 ); // 将正数的原码按位取反得到 【反码】
        System.out.println( (byte)0b1000_0001 ); // 反码加一得到【补码】
    } 
    
}
2.4.5.2、第二种情况
public class Test4 {

    public static void main( String[] args ) {

        double first =1234567.89 ;
        System.out.println( first );

        int second = (int) first ;
        System.out.println( second );

        short third = (short) first ;
        System.out.println( third );

         /*
        // float first = Float.MAX_VALUE ;
        float first = 3.14F ;
        System.out.println( first );

        long second =  (long)first ;
        System.out.println( second );
        */
    }
}

3、Java运算符

3.1、赋值运算符

赋值运算符 ( = ) 的运算规则是 从右到左

代码案例:

public class Assign {

    public static void main(String[] args) {
        
            int x; // 声明一个名称为 x 的变量

            x = 0b0111_1000 ;// 以 二进制 形式书写直接量
            System.out.println( x );

            x = 077 ; // 以 八进制 形式书写直接量
            System.out.println( x );

            x = 100 ; // 将 100 赋值给 x 变量
            System.out.println( x );

            x = 0xFF ; // 以 十六进制 形式书写直接量
            System.out.println( x );

    }
    
}

3.2、算术运算符

1、+ 加法

2、- 减法

3、* 乘法

4、/ 除法

5、% 取模 ( 整除取余数 ) 【 注意 整数 和 浮点数 都可以实现 模运算】

6、++ 自增 【 注意 i++ 与 ++i 的区别 】

7、-- 自减 【 注意 i-- 与 --i 的区别 】

代码案例:

public class Arithmetic {

    public static void main(String[] args) {
        
            long first = 0x7FFF_FFFF_FFFF_FFFFL ; // 取 long 类型的最大值
            long second = 10000L ;
            // 不论两数相加后是多大,都从最低位开始取够64位
            long result1 = first + second ; // 两个 long 类型的数值相加后如果超出 long 取值范围 ( -9223372036854765809 )
            System.out.println( result1 );

            System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

            int third = 0x8000_0000 ; // byte third = 100 ;
            short fourth = 300 ;
            int result2 = third - fourth ; // fourth 变量中的值自动提升为 int 然后再参与减法运算( 两数相减的差可能超出int的取值范围 )
            System.out.println( result2 );

            System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

            int fifth = 0x7FFF_FFFF ;
            long sixth = 9 ; // int sixth = 9 ;

            long result3 = fifth * sixth ;
            System.out.println( result3 );

            System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

            System.out.print( "5 / 2 = " ) ;
            System.out.println( 5 / 2 ); // 整除

            System.out.print( "5 / 2.0 = " ) ; 
            System.out.println( 5 / 2.0 ); // 除尽 ( 与数学中的除法相同 )

            // System.out.print( "5 / 0 = " ) ; 
            // System.out.println( 5 / 0 ); // 运行时出错: java.lang.ArithmeticException

            System.out.print( "5 / 0.0 = " ) ; 
            System.out.println( 5 / 0.0 ); // Infinity ( 正无穷大 )

            System.out.print( "-5 / 0.0 = " ) ; 
            System.out.println( -5 / 0.0 ); // -Infinity ( 负无穷大 )

            System.out.print( "5 / ( 5 / 0.0 ) = " ) ; 
            System.out.println( 5 / ( 5 / 0.0 ) ); // 0.0

            System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

            System.out.print( "5 % 2 = " ) ;
            System.out.println( 5 % 2 ); // 取模 : 整除取余数 ( 1 )

            System.out.print( "5.45 % 2.2 = " ) ;
            System.out.println( 5.45 % 2.2 ); // 取模 : 整除取余数 ( 1.05 )

            System.out.print( "5 % 0.0 = " ) ;
            // System.out.println( 5 % 0 ); // 运行时错误 : java.lang.ArithmeticException
            System.out.println( 5 % 0.0 );  // NaN : Not a Number , 不是一个数字

            System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );
        
            int x = 100 ;
            System.out.println( x ); // 100
            System.out.println( x++ ); // 100【先输出原来的值、再执行自增操作】
            System.out.println( x ); // 101

            short s = 100 ;
            System.out.println( s ); // 100
            System.out.println( ++s ); // 101 【先执行自增操作、再执行输出操作】
            System.out.println( s ); // 101

            byte b = 100 ;
            byte y = b-- ; // 【先将b变量原来的值赋值给y变量、再执行b变量的自减操作】
            System.out.println( "y = " + y + " , b = " + b ); // y = 100 , b = 99

            long c = 1000L ;
            long z = --c ; // 【先执行c变量的自减操作,再将b变量的值赋值给z变量】
            System.out.println( "z = " + z + " , c = " + c ); // z = 999 , c = 999

    }
    
}

3.3、关系运算符

关系运算符 又称 比较运算符 ,有 : ==!=(不等于) 、><>=<=

代码案例:

public class Relation {
    public static void main(String[] args) {
        
        double x = 3.14 ;
        int y = 100 ;
        double z = 3.14 ;

        System.out.println( x == y ); // false
        System.out.println( x == z ); // true

        System.out.println( "~ ~ ~ ~ ~ ~" );

        System.out.println( x != y ); // true
        System.out.println( x != z ); // false

        System.out.println( "~ ~ ~ ~ ~ ~" );

        System.out.println( x < y ); // true
        System.out.println( x > z ); // false

        System.out.println( "~ ~ ~ ~ ~ ~" );

        System.out.println( x <= y ); // true
        System.out.println( x >= z ); // true

        System.out.println( "~ ~ ~ ~ ~ ~" );

        System.out.println( 3.14 == 3.14F ); // false 【double 和 float 精度不同】

    }
}

3.4、逻辑运算符

3.4.1、逻辑 与、或、非

  • 逻辑与 && (两个值中都为 true 时结果才是 true)
  • 逻辑或 || (两个值中只要有一个为 true 结果就为 true)
  • 逻辑非 ! (该值的相反)

代码案例:

public class Logic1 {

    public static void main(String[] args) {
        
        boolean first = true ;
        boolean second = false ;
        boolean third = true ;
        boolean fourth = false ;

        System.out.println( first || second ) ; // 两个值中只要有一个为 true 结果就为 true
        System.out.println( first || third ) ; 
        System.out.println( second || fourth ) ; 

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        System.out.println( first && second ) ; 
        System.out.println( first && third ) ; // 两个值中都为 true 时结果才是 true
        System.out.println( second && fourth ) ; 

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );
        System.out.println( !first ) ; 
        System.out.println( !second ) ; 

    }
    
}

3.4.2、逻辑 与、或、异或

  • 逻辑与 & (两个值中都为 true 时结果才是 true)
  • 逻辑或 | (两个值中只要有一个为 true 结果就为 true)
  • 逻辑异或 ^ (两个逻辑值不相同时结果为 true)

代码案例:

public class Logic2 {

    public static void main(String[] args) {
        
        boolean first = true ;
        boolean second = false ;
        boolean third = true ;
        boolean fourth = false ;

        System.out.println( first | second ) ; // true 两个值中只要有一个为 true 结果就为 true
        System.out.println( first | third ) ; // true
        System.out.println( second | fourth ) ; // false

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        System.out.println( first & second ) ; // false 
        System.out.println( first & third ) ; // true 两个值中都为 true 时结果才是 true
        System.out.println( second & fourth ) ; // false

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );
        System.out.println( first ^ second ) ; // true 两个逻辑值不相同时结果为 true
        System.out.println( first ^ third ) ; // false
        System.out.println( second ^ fourth ) ; // false

    }
    
}

3.4.3、对比区别

  • 对比 逻辑运算符 中 & 和 && 的区别 、| 和 || 的区别
  • 逻辑或: || (短路)、|(不短路)
  • 逻辑与: && (短路)、&(不短路)

代码案例:

public class Logic3 {

    public static void main(String[] args) {
        
        int a = 100 ;
        int b = 100 ;
        System.out.println( "a = " + a + " , b = " + b );
        System.out.println( ++a > 99 | ++b > 99 ); // 不短路
        System.out.println( "a = " + a + " , b = " + b );

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        int c = 100 ;
        int d = 100 ;
        System.out.println( "c = " + c + " , d = " + d ); // c = 100 , d = 100
        System.out.println( ++c > 99 || ++d > 99 ); // 短路
        System.out.println( "c = " + c + " , d = " + d ); // c = 101 , d = 100

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        int m = 100 ;
        int n = 100 ;

        System.out.println( "m = " + m + " , n = " + n ); // m = 100 , n = 100
        System.out.println( ++m < 99 & ++n < 99 ); // 不短路
        System.out.println( "m = " + m + " , n = " + n );// m = 101 , n = 101

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        int x = 100 ;
        int y = 100 ;

        System.out.println( "x = " + x + " , y = " + y ); // x = 100 , y = 100
        System.out.println( ++x < 99 && ++y < 99 ); // 短路
        System.out.println( "x = " + x + " , y = " + y ); // x = 101 , y = 100


    }
    
}

3.5、特殊的赋值运算符

3.5.1、与 算术运算 有关

特殊的赋值运算符 类似于
a += b a = a + b
a -= b a = a - b
a *= b a = a * b
a /= b a = a / b
public class Assignment {
    public static void main(String[] args) {

        int first = 5;
        System.out.println( "first:" + first );

        int second = -5;
        System.out.println( "second:"  + second );

        int third = +5;
        System.out.println( "third:" + third );

        int fourth = 6;
        System.out.println( "fourth:" + fourth );
        System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");

        first += 2;
        System.out.println ( "first:" +  first );

        System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");

        second -= 2;
        System.out.println( "second:" + second );

        System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");

        third *= 2;
        System.out.println( "third:"  + third  );

        System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");

        fourth *= 2;
        System.out.println( "fourth:" + fourth );
    }
}

3.5.2、与 位运算移位 有关

特殊的赋值运算符 类似于
a >>= b a = a >> b
a >>>= b a = a >>> b
a <<= b a = a << b
public class Assignment1 {

    public static void main(String[] args) {
        
        int x = 5;
        System.out.println( "x:" + x );

        int y = -5;
        System.out.println( "y:" + y );

        System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~");

        x >>= 2;
        System.out.println( "x >>= 2:" + x );

        y >>=2;
        System.out.println( "y >>=2:" + y );

        System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~");

        x <<= 2;
        System.out.println( "x <<= 2:" + x );

        y <<= 2;
        System.out.println( "y <<= 2:" + y );

        System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~");

        x >>>= 2;
        System.out.println( "x >>>= 2:" + x );

        y >>>= 2;
        System.out.println( "y >>>= 2:" + y);


    }
    
}

3.5.3、与 逻辑运算 有关

特殊的赋值运算符 类似于
a | = b
a &= b a = a & b
a ^= b a = a ^ b
public class Assignment2 {

    public static void main(String[] args) {
        
        int x = 5;
        System.out.println( "x:" + x );

        int y = -5;
        System.out.println( "y:" + y );

        System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~");

        x |= 2;
        System.out.println( "x |= 2:" + x );

        y |=2;
        System.out.println( "y |= 2:" + y );

        System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~");

        x &= 2;
        System.out.println( "x &= 2:" + x );

        y &= 2;
        System.out.println( "y &= 2:" + y );

        System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~");

        x ^= 2;
        System.out.println( "x ^= 2:" + x );

        y ^= 2;
        System.out.println( "y ^= 2:" + y);


    }
    
}

3.6、位运算符

3.6.1、移位

位运算符(移位) 作用 使用方法 正负数情况
>> 向右移位 x >> n 表示 x 向右移动 n 对于正数来说,向右移位时,高位补0,低位被挤掉;对于负数来说,向右移位时,高位补1,低位被挤掉
<< 向左移位 x << n 表示 x 向左 移动 n 不论是正数还是负数,向左移位时,都是挤掉高位,低位补0
>>> 无符号右移 x >>> n 表示 x 向右 移动 n 不论是正数还是负数,向右移位时,高位一律补0,低位被挤掉
public class BitOperator1 {

    public static void main(String[] args) {
        
        // 被 final 修饰的变量被称作【最终变量】,它是最终的、不可更改的变量 【不要当做"常量"对待】
        final int x = 5 ; // 0b00000000_00000000_00000000_00000101
        System.out.println( x );
        // 尝试再次为 final 修饰的变量赋值
        // x = 6 ; // 错误: 无法为最终变量x分配值

        // 将 最终变量 x 中存储的数值向右移动1位后赋值给 y 变量
        int y = x >> 1 ; // 0b0_00000000_00000000_00000000_0000010
        System.out.println( y );

        int z = x << 1 ; // 0b0000000_00000000_00000000_00000101_0
        System.out.println( z );

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        // -5【原码】: 1000_0000_0000_0000_0000_0000_0000_0101
        // -5【反码】: 1111_1111_1111_1111_1111_1111_1111_1010
        // -5【补码】: 1111_1111_1111_1111_1111_1111_1111_1011
        final int m = -5 ; // 0b1111_1111_1111_1111_1111_1111_1111_1011
        System.out.println( m );

        int n = m >> 1 ; // // 0b1_1111_1111_1111_1111_1111_1111_1111_101
        //【补码】1_1111_1111_1111_1111_1111_1111_1111_101
        //【反码】1_1111_1111_1111_1111_1111_1111_1111_100
        //【原码】1_0000_0000_0000_0000_0000_0000_0000_011
        System.out.println( n );

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        int p = m << 1 ; //  0b111_1111_1111_1111_1111_1111_1111_1011_0
        //【补码】111_1111_1111_1111_1111_1111_1111_1011_0
        //【反码】111_1111_1111_1111_1111_1111_1111_1010_1
        //【原码】100_0000_0000_0000_0000_0000_0000_0101_0
        System.out.println( p );

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        int r = 0x7FFFFFFF ;
        System.out.println( r );
        int s = r << 1 ;
        System.out.println( s );

    }

}

3.6.2、按位

位运算符(按位) 作用
|
& 按位与
^ 按位异或
~ 按位取反
public class BitOperator4 {

    public static void main(String[] args) {
        
        final int x = 5 ; // 0b00000000_00000000_00000000_00000101
        final int y = 7 ; // 0b00000000_00000000_00000000_00000111

        //【 5 】0b00000000_00000000_00000000_00000101
        //【 7 】0b00000000_00000000_00000000_00000111
        int a = x | y ; // 按位或: 0b00000000_00000000_00000000_00000111
        System.out.println( a );

        //【 5 】0b00000000_00000000_00000000_00000101
        //【 7 】0b00000000_00000000_00000000_00000111
        int b = x & y ; // 按位与: 0b00000000_00000000_00000000_00000101
        System.out.println( b );

        //【 5 】0b00000000_00000000_00000000_00000101
        //【 7 】0b00000000_00000000_00000000_00000111
        int c = x ^ y ; // 按位异或: 0b00000000_00000000_00000000_00000010
        System.out.println( c );

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        int r = 5 ; // 0b00000000_00000000_00000000_00000101
        int s = 7 ; // 0b00000000_00000000_00000000_00000111
        System.out.println( "r = " + r + " , s = " + s );

        // int temp = s ; s = r ; r = temp ;
        r = r ^ s ; // 0b00000000_00000000_00000000_00000010
        s = r ^ s ; // 0b00000000_00000000_00000000_00000101
        r = r ^ s ; // 0b00000000_00000000_00000000_00000111

        System.out.println( "r = " + r + " , s = " + s );

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        int j = 5 ; // 0b00000000_00000000_00000000_00000101
        // 注意使用 ~ 按位取反时,会对整数的符号位也取反
        int k = ~j ; //0b11111111_11111111_11111111_11111010
        //【补码】 11111111_11111111_11111111_11111010
        //【反码】 11111111_11111111_11111111_11111001
        //【原码】 10000000_00000000_00000000_00000110
        System.out.println( "j = " + j + " , k = " + k );

    }

}

3.7、三目运算符

也称作 三元运算符条件运算符

expression  ? first : second 

如果 expression 返回结果为 true 则取 first,如果 expression 返回结果为 false 则取 second

import java.util.Random;

public class Condition {

    public static void main(String[] args) {

        // 创建一个 Random 类的实例并将该实例的内存地址赋值给 rand 变量
        Random rand = new Random(); // 这个先用起来,暂不详细研究

        // 通过 Random 类中定义的实例方法 nextBoolean 来产生伪随机数
        boolean x = rand.nextBoolean() ; // 每次调用 nextBoolean 都会产生一个随机数
        System.out.println( x );
        System.out.println( x ? "真的" : "假的" ) ;

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        byte a = 100 ;
        short b = 200 ;
        // variable = expression  ? first : second ;
        // 如果期望通过一个变量接受三目运算符产生的结果,则这个变量 variable 的类型 必须跟 first 和 second 两个值的类型兼容
        short c = a < b ? a : b ;
        System.out.println( c );

    }

}

3.8、运算符的优先级

1、对于运算符优先级来说,我们建议大致掌握几个大的分类的优先级,比如先乘除后加减

2、但是不建议在运算符优先级这里花费太多时间,不划算

3、当无法准确确定运算符优先级时,可以使用 ( ) 提升某个表达式的优先级

public class OperatorPriority {

    public static void main(String[] args) {
        
        int a = 5 - 2 * 3 ;
        System.out.println( a );

        int b = 5 >> 1 * 2 ;
        System.out.println( b );

        int c = ( 5 >> 1 ) * 2 ; // 当无法准确确定运算符优先级时,可以使用 ( ) 提升某个表达式的优先级
        System.out.println( c );

        int d = 100 ;
        System.out.println( d++ + ++d + d-- + --d + --d+ ++d + d++ - --d - d-- ); // 无聊

    }

}

4、Java 流程控制

4.1、选择语句

1、if ( condition ) { ... }

2、if ( condition ) { ... } else { .... }

3、if ( condition1 )...else if ( condition2 ) ... else

import java.util.Random;

public class Statement1 {

    public static void main(String[] args) {
        
        // 在 Java 语言中凡是使用 "" 引起来的内容 都是 字符串 ( 对应 String 类型 )
        String name = "张三丰" ; 
        int age = 22 ;
        char gender = '男' ;

        if( age >= 22 && gender == '男' ) {
            System.out.println( name + " , 你可以结婚了" ); // 注意,这里的 + 是字符串连接符,不是算术运算符
        } else {
            System.out.println( name + " , 你还未到结婚年龄" );
        }

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        Random rand = new Random() ; // 创建一个用于产生随机数的对象

        int score = rand.nextInt( 100 ) ; // 随机产生一个 [ 0 , 100 ) 之间的整数

        System.out.println( "score : " + score );

        if( score >= 90 ) {
            System.out.println( "优秀" );
        } else if( score >= 80 ){ // 为嘛不写 score >= 80 && score < 90 呢? 
            System.out.println( "优良" );
        } else if( score >= 70 ){
            System.out.println( "良好" );
        } else if( score >= 60 ){
            System.out.println( "及格" );
        } else if( score >= 0 ){
            System.out.println( "进步空间巨大" );
        } else {
            System.out.println( "不是有效的成绩" );
        }

    }

}

4.2、开关语句

在 switch 关键字之后的 ( ) 中可以使用那些类型:

1、JDK 1.5 之前只能使用 与 int 兼容的类型,比如 int 、char 、short 、byte

2、从 JDK 1.5 开始允许在 switch 语句中使用 枚举类型 ( enum ) 【 以后讲 enum 】

3、从 JDK 1.7 开始允许在 switch 语句中使用 String 类型

import java.util.Random ;

public class Statement2 {

    public static void main(String[] args) {

        Random rand = new Random() ;

        int month = rand.nextInt( 12 ) ; // 随机产生一个 [ 0 , 12 ) 之间的整数
        month += 1 ; // month = month + 1 ; // 在中国,月份是从 1 开始的
        System.out.println( "month : " + month ); // [ 1 , 12 ]

        /*
        if( month == 2  ) {
            System.out.println( "平年28天、闰年29天" ) ;
        } else if ( month == 4 || month == 6 || month == 9 || month == 11 ){
            System.out.println( "30天" ) ;
        } else if ( month == 1 || month == 3 || month == 5 || month == 7  || month == 8 || month == 10 || month == 12 ){
            System.out.println( "30天" ) ;
        } else {
            System.out.println( "31天" ) ;
        }
        */

        char m = (char) month ;
        switch (  m ) { // switch 语句块开始
                case 2:
                    System.out.println( "平年28天、闰年29天" ) ;
                    break;
                case 4:
                case 6:
                case 9:
                case 11:
                    System.out.println( "30天" ) ;
                    break; // bread 会跳出当前的 switch 语句块
                case 1:
                case 3:
                case 5:
                case 7:
                case 8:
                case 10:
                case 12:
                    System.out.println( "31天" ) ;
                    break; // bread 会跳出当前的 switch 语句块
                default:
                    System.out.println( "无效的月份" ) ;
                    break;
        } // switch 语句块结束

        // 月份:1 、2、3、4、5、6、7、8、9、10、11、12
        // 星期: 一、二、三、四、五、六、日



    }

}
import java.util.Scanner ;

public class Statement3 {

    public static void main(String[] args) {

        // 创建一个可以读取用户在控制台中输入内容的扫描器
        Scanner sc = new Scanner( System.in ); // 暂时只使用不详细研究

        System.out.println( "请输入一个月份名称,比如 一月、二月、正月、腊月" );
        String month = sc.nextLine(); // 读取用户在控制台中输入的内容(读取到换行符为止)

        switch( month ) {
            default :
                System.out.println( "这个月份我判断不了" ); 
                break ;
            case "一月" :
            case "正月" :
            case "十二月" :
            case "腊月" :
                System.out.println( "三十一天" );  // 注意: 这里不考虑农历
                break ;
            case "二月" :
                System.out.println( "平年二十八天、闰年二十九天" ); // 注意: 这里不考虑农历
                break ;
        }


         // 关闭扫描器
        sc.close();
    }

}

4.3、循环语句

4.3.1、for 循环语句

1、基本结构:

for( A ; B ; C ) {    
    D    
}

A 用来声明并初始化变量

B 是一个逻辑表达式,用于决定循环是否执行

C 通常被称作 迭代部分 或 递进部分,用于实现对控制循环的变量的增加或减少等操作

D 表示循环体中要执行的语句 ( 就是要完成什么任务 )

2、执行顺序:

A -> B -> D -> C

B -> D -> C

..............

B

public class Loop1 {

    public static void main(String[] args) {
        
        for ( int i = 0; i < 10 ; i++ ) {
            System.out.println( i );
        }

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        // 在 for 语句中声明并初始化的变量只能在 for 语句块内部使用
        for ( int i = 0 , j = 10 ; i < 10 && j > 0 ; i++  , j-- ) {
            System.out.println( "i = " + i + " , j = " + j );
        }

    }

}

4.3.2、while 循环语句

1、基本结构:

 while ( 判断 ) {
     循环语句
     迭代
}

2、代码示例:

public class Loop2 {

    public static void main(String[] args) {

        
        // while ( 判断 ) {
        //      循环语句
        //      迭代
        // }

        int i = 10 ; // 声明变量并初始化
        while ( i > 0 ) { // 在 while 语句中之后的 ( ) 内执行判断
            System.out.println( "i = " + i ); // 这个输出语句相当于循环要完成的操作
            i-- ; // 迭代部分 ( 实现变量的变化,比如 变量增长 或 减少 )
        }

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        int j = 0 ; // 初始化
        while( j++ < 10 ){ // 迭代 和 判断
            System.out.println( "j = " + j );  // 循环要完成的操作
        }

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        int a = 100 ;
        int b = 200 ;
        System.out.println( "a = " + a + " , b = " + b );
        while( a++ < b-- ); // 没有循环体
        System.out.println( "a = " + a + " , b = " + b );
        
    }

}

4.3.3、do while 循环语句

1、基本结构:

do { } while () ; // 注意最后的分号

2、执行顺序:

while 循环 和 do...while 循环都可以在不明确循环次数的前提下完成循环操作

while 循环会先执行 判断 ( 因为 while 关键字之后就是 逻辑判断表达式 )

do...while 循环会首先执行 do 和 while 之间的代码,再执行之后才进行判断

3、代码示例:

import java.util.Scanner;

public class Loop3 {

    public static void main(String[] args) {

        // 创建一个用于读取用户输入数据的扫描器
        Scanner s = new Scanner( System.in );

        int x = -1 ; // 初始化
        do {
            System.out.println( "请输入 0 到 100 之间的数字:" );
            x = s.nextInt(); // 读取用户在控制台中输入的整数
            System.out.println( "您输入的是 " + x );
         } while ( x < 0 || x > 100 ) ;

        System.out.println( "输入正确" );

         s.close();

    }

}

4.4、continue 和 break 语句

1、continue 语句:

① continue 语句用于实现 略过 循环语句中 本轮操作的后续语句,直接进入下一轮操作

② continue 默认针对它所在的循环有效 ( 就是 离 continue 语句最近的循环 )

2、break 语句:

① break 语句用于跳出循环语句块 ( 整个循环会结束 )

② break 默认针对它所在的循环有效 ( 就是 离 break 语句最近的循环 )

3、代码案例:

public class Loop6 {

    public static void main(String[] args) {

        for( int i = 1 ; i < 10 ; i++ ) {
            if( i == 2 ) {
                // 略过本次循环后续语句直接进入下次
                continue ; // 整个循环并没结束,而是略过本轮中后续操作直接进入下一轮操作
            }
            System.out.print( i + " " ); // 循环当中输出的内容不换行
        }
        System.out.println(); // 等到循环执行结束后再输出换行符

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        for( int i = 1 ; i < 10 ; i++ ) {
            if( i == 2 ) {
                break ; // 跳出循环语句块,整个循环结束
            }
            System.out.print( i + " " ); // 循环当中输出的内容不换行
        }
        System.out.println(); // 等到循环执行结束后再输出换行符

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        for( int i = 1 ; i < 10 ; i++ ){
            System.out.print( i + ": " );
            for( int j = 1 ; j < 10 ; j++ ) {
                if( i == 5 && j == 5 ) {
                    continue ;
                }
                System.out.print( j + " " );
            }
            System.out.println();
        }

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        for( int i = 1 ; i < 10 ; i++ ){
            System.out.print( i + ": " );
            for( int j = 1 ; j < 10 ; j++ ) {
                if( i == 5 && j == 5 ) {
                    break ;
                }
                System.out.print( j + " " );
            }
            System.out.println();
        }

    }

}

4.5、使用标签标识循环语句块

在 continue 语句 和 break 语句中可以使用 标签( label ) 来指定针对那层循环

public class Loop7 {

    public static void main(String[] args) {

        outer:for( int i = 1 ; i <= 5 ; i++ ){
            System.out.print( i + ": " );
            inner:for( int j = 1 ; j <= 5 ; j++ ) {
                if( i == 3 && j == 3 ) {
                    continue inner ; // 在 continue 语句中可以使用 标签
                }
                System.out.print( j + " " );
            }
            System.out.println();
        }

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        outer:for( int i = 1 ; i <= 5 ; i++ ){
            System.out.print( i + ": " );
            inner:for( int j = 1 ; j <= 5 ; j++ ) {
                if( i == 3 && j == 3 ) {
                    continue outer ; // 在 continue 语句中可以使用 标签
                }
                System.out.print( j + " " );
            }
            System.out.println();
        }

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        outer:for( int i = 1 ; i <= 5 ; i++ ){
            System.out.print( i + ": " );
            inner:for( int j = 1 ; j <= 5 ; j++ ) {
                if( i == 3 && j == 3 ) {
                    break inner ; // 在 break 语句中可以使用 标签
                }
                System.out.print( j + " " );
            }
            System.out.println();
        }

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        outer:for( int i = 1 ; i <= 5 ; i++ ){
            System.out.print( i + ": " );
            inner:for( int j = 1 ; j <= 5 ; j++ ) {
                if( i == 3 && j == 3 ) {
                    break outer ; // 在 break 语句中可以使用 标签
                }
                System.out.print( j + " " );
            }
            System.out.println();
        }

    }

}

5、Java 数组(array)

5.1、什么是数组

1、数组:是具有确定顺序的若干个相同类型的变量的集合,用于批量声明同种类型的多个变量

数组中的每个变量被称作数组【元素】( element )

2、数组相关概念:

① 数组变量 :类型 变量名称 ;

② 数组类型 :类型

③ 数组长度 :数组变量名称.length

④ 下标(索引) :用来访问 数组中指定位置(次序)的数字 就是所谓的 下标 ,也称作 索引

⑤ 数组常量 :类型 变量名称 = 数组常量 ;

注:数组变量 不是 基本数据类型的变量

数组变量中存储的不是数值本身而是一个地址,通过这个地址可以找到真正的存放数据的内存区域

3、通过 数组变量名称[ 下标 ] 可以对 数组中指定位置的元素进行 取值 或 赋值操作

4、遍历数组

代码示例:

public class Array1 {

    public static void main(String[] args) {
        
        // first 是一个数组类型的变量,即【数组变量】
        // int[] 是 first 变量的类型,即【数组类型】
        // 数组中所存放的每个数值,比如 10 , 20 , 30 , 40 , 50 等 都是数组中的【元素】
        int[] first = { 10 , 20 , 30 , 40 , 50 }; // Java 建议采用的风格
        // Java 语言中 原点运算符 可以理解为 汉语 中的 "的" ,比如 first.length 可以读成 first 的 length
        System.out.println( first.length ); // 通过 length 属性可以获取 【数组长度】 ( 数组中可以容纳的最大元素个数 )
        System.out.println( first[ 0 ] ); // 数组【下标(索引)】从零开始 
        System.out.println( first[ 1 ] );  // 可以通过【 数组变量名称[ 下标 ] 】来获取指定位置的元素
        System.out.println( first[ 2 ] );
        System.out.println( first[ 3 ] );
        System.out.println( first[ 4 ] );

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        first[ 3 ] = 100 ; // 可以通过【 数组变量名称[ 下标 ] 】来为指定位置的元素赋值
        first[ 2 ] = 200 ;

        // 【遍历数组】: 将数组中的每个元素按照一定顺序依次访问一遍
        for (int i = 0 ; i < first.length; i++) {
            System.out.print( first[ i ] + "\t" ); // 可以通过【 数组变量名称[ 下标 ] 】来获取指定位置的元素
        }
        System.out.println();

        // 在 声明数组后,可以通过 { } 形式直接为 数组变量 赋值,这里以 { } 形式书写的内容就是【数组常量】
        // { 10 , 20 , 30 , 40 , 50 , 60 } 就是所谓的数组常量
        int second[] = { 10 , 20 , 30 , 40 , 50 , 60 }; // C/C++ 惯用的风格
        System.out.println( second.length );

        for (int i = 0 ; i < second.length; i++) {
            System.out.print( second[ i ] + "\t" );
        }
        System.out.println();

    }

}
public class Array2 {

    public static void main(String[] args) {
        
        // first 是变量名称,int 是变量的类型
        int first = 100 ; // 基本数据类型的变量中直接存储相应的取值
        System.out.println( first ); 

        // second 是变量名称,也就是 数组变量名称 ,简称 数组名称
        // int[] 是 second 变量的类型,int[] 既确定了 second 变量的类型,也确定了 second 所对应数组中各个元素的类型
        int[] second = { 10 , 20 , 30 }; // 数组常量 { 10 , 20 , 30 } 确定了 second 对应的数组中每个变量的值 和 second 数组的长度
        System.out.println( second ); 

        // 对于非基本数据类型的变量来说,可以通过 System.identityHashCode( reference ) 方法来获取该变量中所存储的 "地址"
        int address = System.identityHashCode( second ); // 这个方法 死记硬背 也要背下来,将来可以帮很多忙
        System.out.println( address );

        // 通过 for 循环 遍历数组
        for ( int i = 0; i < second.length; i++ ) {
            int element = second[ i ] ; // 通过 数组变量名称[ 下标 ] 来获取相应的元素
            System.out.print( element + "\t");
        }
        System.out.println();

        System.out.println(  "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        // 对比 C/C++
        // int third[ 5 ] ; // 与 C 语言不同,Java 中不支持这种写法
        int[] third = new int[ 5 ]; // Java 语言中创建数组的方法
        System.out.println( third.length ); 
        for ( int i = 0; i < third.length; i++ ) {
            int element = third[ i ] ;
            System.out.print( element + "\t");
        }
        System.out.println();

        System.out.println( System.identityHashCode( third ) );

    }

}

5.2、数组的静态、动态初始化

5.2.1、数组的静态初始化

声明数组时直接完成对数组的初始化操作就是静态初始化

静态初始化时可以使用 数组常量 ( 只有在 声明数组 时才可以使用 )

例如:

int[] first = { 10 , 20 , 30 };

5.2.2、数组的动态初始化

声明数组时为数组中各个变量开辟内存空间并赋予【默认值】

例如:

int[] array = new int[ 5 ] ;

5.3.3、关于数组中的变量

1、用来存放 原生类型数据 的数组中各个变量的默认值都是 :

byte : 0 short : 0 int : 0 char : \u0000

long : 0 float : 0.0f double : 0.0 boolean : false

2、 用来存放 引用类型数据 的数组中各个变量的默认值都是 null

3、如果期望将一组数据放入到数组中,需要在声明数组之后显式为各个变量赋值,第一次显式为数组中各个变

量赋的值就是【初始值】

5.3.4、数组初始化案例

import java.util.Random;

public class Array3 {

    public static void main(String[] args) {

        // 数组常量 只有在 声明数组 时才可以使用
        int[] first = { 10 , 20 , 30 }; //【静态初始化】
        for (int i = 0; i < first.length; i++) {
            System.out.print( first[ i ] + "\t" );
        }
        System.out.println();

        // 对于已经声明过的 数组变量 不可以再使用 数组常量 为其赋值
        // first = { 100 , 200 , 300 }; // 错误: 非法的表达式开始

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        int[] second = new int[] { 1 , 3 , 5 , 7 , 9 } ; //【静态初始化】
        for (int i = 0; i < second.length; i++) {
            System.out.print( second[ i ] + "\t" );
        }
        System.out.println();

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        // 声明数组
        int[] third = new int[ 5 ];  // 开辟内存空间并赋予默认值
        for (int i = 0; i < third.length; i++) {
            System.out.print( third[ i ] + "\t" );
        }
        System.out.println();

        Random rand = new Random();
        // 通过循环完成对数组的初始化
        for (int i = 0; i < third.length; i++) {
            third[ i ] = rand.nextInt( 100 );
        }
        
        for (int i = 0; i < third.length; i++) {
            System.out.print( third[ i ] + "\t" );
        }
        System.out.println();

    }

}

5.3、数组中 new 的 操作

1、通过 array 获取数组长度、访问数组元素都要首先通过 array 变量中存储的"地址"找到真正的数组

2、通过 new 关键字 在 堆内存 中 创建的数组 ,就是一个特殊的 Java 对象,很多时候将其称作 数组对象

数组实例
import java.util.Random ;

/**
 * 1、array = new int[ 5 ] ; 到底完成了哪些操作
 * 2、通过 array 获取数组长度、访问数组元素都要首先通过 array 变量中存储的"地址"找到真正的数组
 * 3、通过 new 关键字 在 堆内存 中 创建的数组 ,就是一个特殊的 Java 对象,很多时候将其称作 数组对象  或 数组实例
 */

public class Array4 {

    public static void main(String[] args) {

        int[] array ; // 声明一个数组变量
        // System.out.println( array ); //【编译失败】错误: 可能尚未初始化 变量 array

        // 只有在声明数组时才可以将 数组常量 赋值给 数组变量
        // array = { 100 , 200 , 300 }; //【编译失败】错误: 非法的表达式开始

        // 1、new 负责在 堆内存 中开辟空间
        // 2、类型 ( int ) 和 数组长度 ( 5 ) 确定 new 所开辟的内存大小 ( 一个 int 变量占 4 字节,5 个就是 20 字节 ) 
        // 3、为已经创建好的 5 个 int 变量赋予默认值 ( int 类型默认值都是 0 )
        // 4、最后将 堆内存中 数组的首地址 赋值给 数组变量 ( array )
        array = new int[ 5 ];
        System.out.println( "数组变量 array 中存储的 '地址' 是: " + System.identityHashCode( array ) );

        // 通过 数组变量 array 中存储的地址找到 堆内存 中的 数组 后,再获取数组长度并输出
        System.out.println( "数组长度: " + array.length ); // 输出 array 数组 的 长度

        for (int i = 0; i < array.length; i++) {
            System.out.print( array[ i ] + "\t" ); // 通过 array 变量所存储的地址找到 堆内存中的数组后再根据 索引 访问相应位置的变量
        }
        System.out.println();

        Random rand = new Random();
        // 通过循环完成对数组的初始化
        for (int i = 0; i < array.length; i++) {
            // 通过 array 变量所存储的地址找到 堆内存中的数组后再根据 索引 为相应位置的变量赋值
            array[ i ] = rand.nextInt( 100 );
        }

        for (int i = 0; i < array.length; i++) {
            System.out.print( array[ i ] + "\t" ); 
        }
        System.out.println();

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        int[] another = array ;
        System.out.println( "数组变量 another 中存储的 '地址' 是: " + System.identityHashCode( another ) );

        System.out.println( another == array ); // 比较两个数组变量中存储的值是否相等 ( 数组变量中存储的是地址 )

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        int[] first = { 10 , 20 , 30 }; // int[] first = new int[] { 10 , 20 , 30 };
        int[] second = { 10 , 20 , 30 }; // int[] second = new int[] { 10 , 20 , 30 };

        System.out.println( first == second );
        System.out.println( "数组变量 first 中存储的 '地址' 是: " + System.identityHashCode( first ) );
        System.out.println( "数组变量 second 中存储的 '地址' 是: " + System.identityHashCode( second ) );

    }

}

5.4、数组的冒泡排序

import java.util.Random;

/**
 * 1、能够区分 基本数据类型的变量 和 引用变量
 * 2、掌握冒泡排序
 */
public class Array5 {

    public static void main(String[] args) {
        
        // x 是基本数据类型 (原生类型)的变量
        int x = 100 ; // 基本数据类型的变量中可以直接存储数值本身
        System.out.println( x );

        // a 是一个引用类型的变量
        int[] a = { 2 , 4 , 6 , 8 , 10 } ;  // 引用类型的变量中存储的是地址
        System.out.println( a ); // 类型@哈希码
        // 对于引用类型变量来说,可以通过 identityHashCode 方法来获取变量中存储的地址
        System.out.println( System.identityHashCode( a ) );

        System.out.println( "= = = = = = = = = = = = = = = = = = = =" );

        // 数组下标的有效范围是 [ 0 , array.length - 1 ] ,如果超出这个范围就会抛出 ArrayIndexOutOfBoundsException
        // 对于 length 为 10 的数组来说,其下标取值范围是 [ 0 , 9 ] 
        int[] array = new int[ 10 ] ;

        Random rand = new Random();

        for( int i = 0 ; i < array.length ; i++ ) {
            array[ i ] = rand.nextInt( 100 ) ; // 随机产生 [ 0 , 100 ) 之间的整数并将该整数赋值给 array 数组中下标为 i 的变量
        }

        /* 在初始化数组之后遍历数组,将数组中每个元素逐个输出 */
        for( int i = 0 ; i < array.length ; i++ ){
            System.out.print( array[ i ] + "  " );
        }
        System.out.println();

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        // 冒泡排序 ( Bubble Sort )
        for( int r = 0 ; r < array.length ; r++ ){
            // 为什么 在 内层循环的 判断语句中 要有  array.length 减去 1 ,再减去 r
            for( int i = 0 ; i < array.length - 1 - r ; i++ ){
                if( array[ i ] > array[ i + 1 ] ) {
                    // 对于整数类型变量来说可以使用 按位异或 实现交换
                    array[ i ] ^= array[ i + 1 ] ;
                    array[ i + 1 ] ^= array[ i ] ;
                    array[ i ] ^= array[ i + 1 ] ;
                }
            }
        }


        /* 在对数组中的元素排序之后再次遍历数组,将数组中每个元素逐个输出 */
        for( int i = 0 ; i < array.length ; i++ ){
            System.out.print( array[ i ] + "  " );
        }
        System.out.println();

    }

5.5、多维数组

1、数组就是用来容纳一组相同类型的变量的

2、数组内部容纳的变量是基本数据类型还是引用类型,取决于实际需要

3、如果某个数组内部容纳的变量又是数组类型,则这个数组是个多维数组

public class Array6 {

    public static void main(String[] args) {

        char[] first = { '青' , '海' , '长' , '云' , '暗' , '雪' , '山' } ;
        System.out.println( first ); // 不是输出 "类型@哈希码" 形式 而是将 char 数组中的所有字符直接输出
        System.out.println( System.identityHashCode( first ) );

        char[] second = { '孤' , '城' , '遥' , '望' , '玉' , '门' , '关' };
        System.out.println( second ); 
        System.out.println( System.identityHashCode( second ) );

        char[] third = { '黄' , '沙' , '百' , '战' , '穿' , '金' , '甲' } ;
        System.out.println( third ); 
        System.out.println( System.identityHashCode( third ) );

        char[] fourth = { '不' , '破' , '楼' , '兰' , '终' , '不' , '还' } ;
        System.out.println( fourth ); 
        System.out.println( System.identityHashCode( fourth ) );

        // 数组常量 { first , second , third , fourth } 是由 四个 地址 组成的数组
        // 变量 x 所对应的数组是一个存放 数组 的 数组
        char[][] x = { first , second , third , fourth } ;

        /*
        char[][] x =  {
                                { '青' , '海' , '长' , '云' , '暗' , '雪' , '山' } ,
                                { '孤' , '城' , '遥' , '望' , '玉' , '门' , '关' } ,
                                { '黄' , '沙' , '百' , '战' , '穿' , '金' , '甲' } ,
                                { '不' , '破' , '楼' , '兰' , '终' , '不' , '还' }
                            };
        */

        // 遍历二维数组
        for( int i = 0 ; i < x.length ; i++ ) {
            // 取 x 对应的数组中的元素
            char[] e = x[ i ];
            // 处理获取到的元素(是个数组)
            for( int j = 0 ; j < e.length ; j++ ) {
                char ch = e[ j ] ;
                System.out.print( ch + "\t" );
            }
            System.out.println();
        }

        /*
        // 遍历二维数组
        for( int i = 0 ; i < x.length ; i++ ) {
            // 处理获取到的元素(是个数组)
            for( int j = 0 ; j < x[ i ].length ; j++ ) {
                char ch = x[ i ][ j ] ;
                System.out.print( ch + "\t" );
            }
            System.out.println();
        }
        */

    }

}

5.6、二维数组的分类

1、将二维数组分为 规则二维数组 和 不规则二维数组

2、如果某个数组内部的 所有数组长度都相同,则这个二维数组就是规则二维数组,否则就是 不规则二维数组

3、使用 两重 for 循环 遍历二维数组

public class Array7 {

    public static void main(String[] args) {

        // 不规则二维数组
        char[][] first = { 
                                    { '狼' , '烟' , '起' } , 
                                    { '江' , '山' , '北' , '望' } ,
                                    { '龙' , '起' , '卷' , '马' , '长' , '嘶' , '剑' , '气' , '如' , '霜' } , 
                                    { '心' , '似' , '黄' , '河' , '水' , '茫' , '茫' } 
                                } ;

        for( int i = 0 ; i < first.length ; i++ ) {
            for( int j = 0 ; j < first[ i ] .length  ;j++ ) {
                // i 表示外部数组中下标是 i 的元素(是个数组), j 表示 内部数组中下标是 j 的元素(是个字符)
                char ch = first[ i ] [ j ] ; 
                System.out.print( ch + " " );
            }
            System.out.println();
        }

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        // 规则二维数组
         char[][] second =  {
                                        { '青' , '海' , '长' , '云' , '暗' , '雪' , '山' } ,
                                        { '孤' , '城' , '遥' , '望' , '玉' , '门' , '关' } ,
                                        { '黄' , '沙' , '百' , '战' , '穿' , '金' , '甲' } ,
                                        { '不' , '破' , '楼' , '兰' , '终' , '不' , '还' }
                                    };     
                                    
        for( int i = 0 ; i < second.length ; i++ ) {
            // for( int j = 0 ; j < second[ i ] .length  ;j++ ) {
            for( int j = 0 ; j < second[ 0 ] .length  ;j++ ) {
                char ch = second[ i ] [ j ] ;
                System.out.print( ch + " " );
            }
            System.out.println();
        }                          

    }

}

6、类与对象

以下面的代码为例:

public class Person {

    public String name ;
    public int age ;
    public char gender ;
    public boolean married ;
    public static String home = "地球" ;

    public void introduce()  {
        // 注意此处的三元运算符使用
        System.out.println("我叫" + name + "," + "性别是" + gender + "," + "今年是" + age + "," + "婚姻状况是"  + ( married ? "已婚" : "未婚" ));
    }

    public static void main(String[] args) {

        // 类型   变量名  =  new 类名() ;
        Person first = new Person(); // 使用 new Person() 在堆内存中创建对象的过程被称作实例化
        first.name = "杨过" ;
        first.age = 18 ;
        first.gender = '男' ;
        first.married = false ;

        first.introduce();
        System.out.println( first.home );
        System.out.println( Person.home );

        Person second = new Person();
        second.name = "欧阳锋" ;
        second.age = 50 ;
        second.gender = '男' ;
        second.married = true ;

        second.introduce();
        System.out.println( second.home ); // 不建议这样使用
        System.out.println( Person.home );

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        Person.home = "Earth" ;
        System.out.println( first.home );
        System.out.println( second.home );
        System.out.println( Person.home );
        

    }
    
}

6.1、什么是类和对象

6.1.1、类

是某一种事物的抽象,比如 人 、动物 、西瓜 等

6.1.2、对象

对象 是 某种事物的一个具体的 个体,比如 一个名字叫做张三丰的人 、你女朋友给你买的西瓜 、一个敢于大闹

天宫的猴子

6.2、什么是字段

字段( field )本质上就是一个 变量 ,是属于类中的一个成员( member ),因此也称作 【成员变量】

某一种事物的每个个体都具有的特征可以使用类中的 字段 来表示,比如 人 的 姓名、年龄、性别 等

6.3、变量类型

6.3.1、实例变量(字段)

1、属于 [个体] 的字段被称作 实例变量 ( 也可以称作 实例字段 )

2、在一个类中可以声明属于其实例的字段,比如: 在 Person 类中声明的 name 变量

3、实例变量 是 没有 static 修饰符修饰的字段,它们是伴随实例存在

4、当一个对象被实例化后,实例变量就已经存在,直到该对象被回收

6.3.2、类变量(字段)

1、属于 [类] 的字段被称作 类变量 ( 也称作 类字段 或者 静态变量 )

2、在一个类中可以声明属于该类的字段,比如: 在 Person 类中声明的 home 变量

类变量 可以通过 类名.字段名称"】 来访问,比如 Person.home 【推荐使用】

类变量 也可以通过 "对象名.字段名称" 来访问,比如 first.home 、second.home 【不建议使用】

3、类变量是被 static 修饰符修饰 的字段,它们是伴随类存在

4、当一个类被加载成功后,类变量就已经存在,直到该类被卸载时

不论是 类变量 还是 实例变量,JVM 都会为它们赋予相应的默认值

  • 基本数据类型都是 :

byte : 0short : 0int : 0long : 0float : 0.0fdouble : 0.0char : \u0000

boolean : false
  • 引用类型都是 null

6.4、方法类型

public class Student {

    public static String school ; // 类变量 ( 有 static 修饰的 )

    public String name ; // 实例变量 ( 没有 static 修饰的 )

    public static void showSchool(){ // 类方法 ( 有 static 修饰的 )
        System.out.println( school ) ;
    }

    public void showName(){  // 实例方法 ( 没有 static 修饰的 )
        System.out.println( name ) ;
    }

    public static void main(String[] args) {

        System.out.println( Student.school ) ; // null
        // System.out.println( Student.name ) ; //【编译失败】错误: 无法从静态上下文中引用非静态 变量 name
        Student.school = "大肥羊学校" ;
        System.out.println( Student.school ) ;

        Student s = new Student();
        s.name = "懒羊羊" ;
        System.out.println( s.name ) ; // null
        System.out.println( s.school ) ; // null

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        // 通过 "类名.方法名" 来调用【类方法】
        Student.showSchool();
        // 通过 "对象名.方法名" 来调用【类方法】 ( 但是不赞成使用这种方式 )
        s.showSchool(); // The static method showSchool() from the type Student should be accessed in a static way

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        // Student.showName(); // showName 方法 是 实例方法,不能通过 类名 来访问
        s.showName(); // 通过 "对象名.方法名" 来调用【实例方法】 

    }

}

6.4.1、类方法

static 修饰的方法 是 类方法

类方法 既可以通过 类名 来调用,也可以通过 对象名 来调用(不赞成使用)

例如:Student.showSchool(); s.showSchool();(不赞成使用)

6.4.2、实例方法

没有 static 修饰的方法 是 实例方法

实例方法 只能通过 实例 来调用,不能通过 类名 来调用

例如:s.showName();

6.5、类的初始化

6.5.1、什么是类的初始化

在声明 类变量 时,直接使用 赋值表达式 可以为 类变量指定 初始值 ( initial value )

public class Goat {

    // 在声明 类变量时,直接使用 赋值表达式 可以为 类变量指定 初始值 ( initial value )
    public static String school = "大肥羊学校" ; // 类变量 ( static 表示属于类的、与类相关的 )

    static {
        // System.out.println( Goat.school ) ; // 在 static 代码块中输出已经初始化的 school 变量的值
        System.out.println( school ) ; // 同一个类中可以省略类名
    }

    public static void main(String[] args) { // 一个带有 main 方法类是可以直接运行的,这种类被称作 启动类
        // main 方法中什么都不写
    }

}

6.5.2、启动类

1、一个带有 main 方法类是可以直接运行的,这种类被称作 启动类

2、首次 主动使用 一个类时,将导致该类被初始化,类中的类变量也将初始化

3、所谓 主动使用 一个类包括很多情况,比如这里直接运行一个启动类 或者 创建一个类的对象

4、当一个类被初始化后即可反复使用,不会再次执行初始化操作,除非该类被卸载后重新加载

5、static 代码块 仅在首次主动使用一个类时才执行一次

public class Sheep {

    public static String school ; // 类变量 ( static 表示属于类的、与类相关的 )

    // 在 static 修饰的代码块中可以完成对 类变量 的初始化
    static {
        System.out.println( "默认值: " + school ); // 因为在此之前从来没有为 school 主动赋值
        // Sheep.school = "大肥羊学校" ; // 首次为 school 主动赋值就是对该变量的初始化
        school = "大肥羊学校" ; // 同一个类中可以省略类名
        System.out.println( "初始值: " + school );
    }

    public static void main(String[] args) { 
        // main 方法中什么都不写
    }

}
public class SheepTest {

    public static void main(String[] args) {

        // null 是所有的引用类型的变量都可以使用的取值
        Sheep s = null ; // 声明一个 Sheep 类型的引用变量 s 并为其赋值
        System.out.println( s );

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        s = new Sheep(); // 创建 Sheep 类的对象就是主动使用 Sheep 类
        System.out.println( s ); // 类型@哈希码
        System.out.println( System.identityHashCode( s ) );

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        Sheep h = new Sheep();
        System.out.println( h ); // 类型@哈希码
        System.out.println( System.identityHashCode( h ) );
        
    }

}

此处需要用虚拟机运行,VSCode可能会报错( 没有Sheep这种Java变量类型 )

6.6、对象的初始化

1、类体括号中 可以出现 字段、构造、方法、代码块,但不能直接出现 语句

2、实例的初始化过程:通过 new 关键字创建对象时,首先为实例变量分配空间并为实例变量赋予默认值,然

后执行 实例代码块 ,最后执行 构造方法中的代码

public class Bull {

    public String name ; // 实例变量 ( 没有 static 修饰的 、属于 实例的 )

    { // 实例代码块 ( 用来完成对 实例变量的初始化 )
        System.out.println( "代码块" );
        System.out.println( "默认值: " + name );
    }

    // System.out.println( "" ); // 类体括号中不能出现语句

    // 构造方法 : 与类名同名 、没有返回类型
    public Bull(){
        System.out.println( "构造方法" );
        name = "牛魔王" ;
        System.out.println( "初始值: " + name );
    }

    public static void main(String[] args) {
        Bull b = new Bull();
        System.out.println( b ); // 类型@哈希码

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        Bull u = new Bull();
        System.out.println( u ); // 类型@哈希码
    }

}

同时,对于实例变量来说,赋值表达式 比 实例代码块 先执行,实例代码块 比 构造方法 先执行

public class Calf {

    // 可以通过 赋值表达式 为 实例变量直接赋值
    public String name = "圣婴岱王" ; // 实例变量 ( 没有 static 修饰的 、属于 实例的 )

    { // 实例代码块 ( 用来完成对 实例变量的初始化 )
        System.out.println( "代码块: " + name );
    }

    // 构造方法 : 与类名同名 、没有返回类型
    public Calf(){
        System.out.println( "构造方法: " + name );
    }

    public static void main(String[] args) {

        Calf b = new Calf();
        System.out.println( b ); // 类型@哈希码

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        Calf u = new Calf();
        System.out.println( u ); // 类型@哈希码

    }

}

6.7、方法(method)

6.7.1、声明方法

1、掌握方法的声明格式:

修饰符 返回类型 名称( [参数类型 参数名称 ,...... ] ) [throws 异常类型列表] {
    
    方法体
    
}

2、只有返回类型不是 void 的方法,才可以将方法执行后的返回值赋值给另外一个变量

3、方法可以 有参数 也可以 没有参数 ,可以声明抛出异常也可以不声明

public class Monkey {

    private String name ;

    public void show(){ // 不带参数
        System.out.println( "我叫" + name );
    }

    public void add( int a , int b ) {
        int c = a + b ;
        System.out.println( c );
    }

    // 方法声明
    // 修饰符 返回类型  名称 ( [ 参数类型  参数名称 , ...... ] ) [ throws 异常类型列表 ] { }

    public int addition ( int a , int b ) { // 方法体
        int c = a + b ;
        return c ;
    } // 方法体

    public static void main(String[] args) {
        
        Monkey m = new Monkey();
        m.add( 5 , 6 ); 
        // int x = m.add( 5 ,  6 ); // cannot convert from void to int

        int x = m.addition( 100 , 200 );
        System.out.println( x );

    }

}

6.7.2、基本类型值的传递

本质:将一个方法中的栈帧(stack frame)中的某些变量的值拷贝到另一个方法的栈帧(stack frame)中

public class Elephant {

    public void increment( int x ) {
        System.out.println( "【 increment 】x = " + x );
        x++ ;
        System.out.println( "【 increment 】x = " + x );
    }

    public static void main(String[] args) { 
        
        int x = 100 ;

        Elephant e = new Elephant();

        System.out.println( "【 main 】x = " + x ); // 100

        // 调用任意的一个方法,都会导致产生于该方法本次执行对应的 栈桢
        e.increment( x ); // main 调用 e 的 increment 方法

        System.out.println( "【 main 】x = " + x ); // 100
        
    }

}

6.7.3、引用类型的值传递

以数组为例:

public class Arab {

    public void sort( int[] array ) {
        for (int i = 0 ; i < array.length - 1 ; i++) {
            for (int j = 0; j < array.length - 1 - i ; j++) {
                if( array[ j ] > array[ j + 1 ] ) {
                    array[ j ] ^= array[ j + 1 ] ;
                    array[ j + 1 ] ^= array[ j ] ;
                    array[ j ] ^= array[ j + 1 ] ;
                }
            }
        }
    }

    public void traversal( int[] array ) {
        for (int i = 0; i < array.length; i++) {
            System.out.print( array[ i ] + "  ");
        }
        System.out.println();
    }

    public static void main(String[] args) {

        Arab a = new Arab();

        // 静态初始化 : 声明数组时使用 数组常量 直接对 数组进行初始化
        int[] array = { 2 , 1 , 4 , 7 , 8 , 0 , 3, 5 , 6 , 9 } ;
        System.out.println( array ); // 类型@哈希码
        System.out.println( System.identityHashCode( array ) ) ;

        // 排序之前遍历数组
        a.traversal( array ); // 参数传递

        // 对数组排序
        a.sort( array ); // 参数传递

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        // 排序之后遍历数组
        a.traversal( array ); // 参数传递

    }

}

6.7.4、方法的返回类型

1、方法的 返回类型 必须跟方法所返回的 值的类型 相匹配

2、使用 return 语句可以导致一个正在执行的方法立即结束

3、使用 return 语句可以将 一个方法的栈桢 中的数据 带出到 方法的调用者

public class Dog {

    // 修饰符  返回类型  方法名称 ( 参数列表 )
    public char upper( char x ){
        char ch = x ;
        if( x >= 'a' && x <= 'z' ) {
            int y = x ^ 32 ; // int y = x - 32 ;
            ch = (char) y ;
        }
        return ch ;
    }

    // 判断一个整数是否是质数
    public boolean isPrime( int n ) {
        if( n <= 1 ){
            System.out.println( "你的数学是体育老师的媳妇的表妹教的?" );
            return false ; // 直接使用 return 语句返回 false 导致方法立即结束
        }

        boolean result = true ; // 假设参数 n 是个质数

        for (int i = 2 ; i < n ; i++ ) {
            if( n % i == 0 ) { // 尝试推翻假设
                result = false ; // 推翻假设
                break ; // 跳出整个循环
            }
        }
        // return 可以导致一个正在执行的方法立即结束 ( 该方法的 栈桢 从 栈 中弹出 )
        // 使用 return 语句 可以将 数据 ( 可能是变量的值 或 直接量 ) 从 当前方法的 栈桢 中 带出给 方法的调用者
        return result ; // 返回判断结果
    }

    public static void main(String[] args) {

        Dog d = new Dog();

        char x = 'a' ;
        System.out.println( "x : " + x );

        char y = d.upper( x );
        System.out.println( "y : " + y );

        boolean z = d.isPrime( 12 ); // 【 main 方法】 调用 【 d 】 的 【 isPrime方法 】 【判断 12 是否是个质数】 【返回判断结果】
        System.out.println( "z : " + z );

    }

}

6.7.5、返回引用类型

class Cellphone {
    public String brand ;
    public int memory ;
}

class Factory {
    public Cellphone produce(){
        Cellphone c = null ; // 声明一个 Cellphone 类型的变量并为其赋值
        c = new Cellphone(); // 创建 Cellphone 对象并将其在堆内存中的地址赋值给引用变量 c
        c.brand = "小米手机" ; // 通过引用变量 c 找到堆内存中的 Cellphone 对象 并为其 实例变量 brand 赋值
        c.memory = 1 << 30 ;
        System.out.println( "produce : " + System.identityHashCode( c ) ) ;  
        return c ; // 返回引用变量 c 中的 值 ( 实际上是个地址 )
    }
}

// 同一个 源文件中 可以存在多个 class ,但至多有一个 public 修饰的 class
// 如果存在 public 修饰的 class  ,则源文件的名称 必须与 public 修饰的 class 的名称相同
 public class CellphoneTest {
    public static void main(String[] args) {
        Factory f = new Factory();
        Cellphone c = f.produce();
        System.out.println( "main : " + System.identityHashCode( c ) ) ;  
    }
}

6.7.6、传入引用类型的参数

public class Undergraduate {

    public String name ;
    public char gender ;

    // 判断 自己 ( 我 ) 和 对方 ( partner ) 是否可以结婚
    // 判断 当前对象 ( this ) 和 参数传入的对象 ( partner ) 是否可以结婚
    public boolean marry( Undergraduate partner ) {
        // 暂时只考虑 性别,异性可以结婚
        if( ( this.gender == '男' && partner.gender == '女' ) || ( this.gender == '女' && partner.gender == '男' ) ) {
            return true ;
        } 
        return false ;
    }

    public static void main(String[] args) {

        Undergraduate first = new Undergraduate();
        first.name = "杨过" ;
        first.gender = '男' ;

        Undergraduate second = new Undergraduate();
        second.name = "郭芙" ;
        second.gender = '女' ;

        boolean x = second.marry( first ); // 此时,在 marry 内部,this 就表示 second 对应的对象,partner 就表示 first 对应的对象
        System.out.println( x );

        Undergraduate third = new Undergraduate();
        third.name = "小武" ;
        third.gender = '男' ;

        x = third.marry( first );
        System.out.println( x );
        
    }

}

6.8、方法的重载

6.8.1、什么是方法的重载

同一个类中 存在多个 同名不同参 的方法时,它们之间构成重载 ( 重载是相互的 )

其中,同名 是指 方法名称 完全相同

不同参 是指 构成重载的多个方法 的 参数类型参数个数参数类型顺序 不相同

但是,重载 对 方法的 修饰符返回类型异常列表 等没有要求

例如,定义一个 SikaDeer 类,并对该类中的 add() 方法进行重载(计算各种类型的数相加)

public class SikaDeer {

    public int add( int a , int b ) {
        System.out.println( "add( int , int )" );
        return a + b ;
    }

    protected double add( int a , double b ) {
        System.out.println( "add( int , double )" );
        return a + b ;
    }

    // 没有 显式 指定 访问修饰符 时即为 默认 ( 默认是 package-private )
    double add( double a , int b ) {
        System.out.println( "add( double , int )" );
        return a + b ;
    }

    private double add( double a , double b ) {
        System.out.println( "add( double , double )" );
        return a + b ;
    }

    public double add( double a , double b , double c ) {
        System.out.println( "add( double , double , double )" );
        return a + b + c ;
    }

    public static void main( String[] args ) {

        SikaDeer s = new SikaDeer();

        int x = s.add( 100 , 200 ); // add( int , int )
        System.out.println( x );

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        double y = s.add( 100D , 200.0 );  // add( double , double )
        System.out.println( y );

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        double z = s.add( 100 , 200.0 );  // add( int , double )
        System.out.println( z );

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        double u = s.add( 100.0 , 200 );  // add( double , int )
        System.out.println( u );

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        double v = s.add( 100 , 200 , 300 );  // add( double , double , double )
        System.out.println( v );

    }

}

2、构造方法的重载

① 如果没有指定任何构造方法,则编译会为当前类添加一个默认构造方法( 公开的、无参的 、空的 )

public  类名() {   }

② 如果显式指定了带参数的构造方法,则编译器不再为当前类添加默认构造方法

为了将来能够使用无参数的构造方法,就需要显式书写无参数构造方法

③ 对于构造方法来说,多个构造方法之间可以构成重载

一个类中所有的构造方法都是与类名同名的,因此它们的名称一定是相同的

当多个构造方法的参数不相同( 类型、个数、顺序 ) 时,它们之间就构成重载

④ 当一个类中存在多个重载的构造方法时,创建该类的对象时会根据参数来确定到底调用哪一个构造方法

例如,创建一个 Human 类,并在其内部写 带参数构造方法

public class Human {

    protected String name ;

    // 构造方法与类名同名、不能显式指定返回类型
    public Human(){ 
    }

    public Human( String name ){ // 为构造方法指定参数
        this.name = name ; // 通过构造方法完成对实例变量的初始化
    }

    public static void main(String[] args) {
        
        Human h = new Human( "张三丰" );
        System.out.println( h.name ) ;

        Human u = new Human();
        System.out.println( u.name ) ;

    }

}

3、super 关键字

通过 super 关键字 调用父类构造方法

使用格式:

super( [实参列表] ) 

1、在任意类的构造方法中,都可以通过 super( [实参列表] ) 形式来调用父类中的指定构造方法

2、如果某个构造方法中没有显示书写 super( [实参列表] )编译器 会为构造方法添加 super() 从而调用父类中的无参构造

3、通过指定不同的参数可以调用父类中不同的构造方法

例如,在 Human 父类中写一个方法,随后再创建 Chinese 子类进行继承后使用 super 调用父类中的方法

    // 构造方法与类名同名、不能显式指定返回类型
    public Human(){ 
        System.out.println( "Constructor : Human()" );
    }

    public Human( String name ){ // 为构造方法指定参数
        System.out.println( "Constructor : Human(java.lang.String)" );
        this.name = name ; // 通过构造方法完成对实例变量的初始化
    }
public class Chinese extends Human {

    public Chinese(){
        super(); // 任意构造方法内部执行的第一行代码永远是 super( [参数] )
        System.out.println( "Constructor : Chinese()" );
    }

    public Chinese( String name ) {
        super( name ); // 调用父类中带有 java.lang.String 类型参数的构造方法
        System.out.println( "Constructor : Chinese(java.lang.String)" );
    }

    public static void main(String[] args) {
        
        Chinese c = new Chinese();
        System.out.println( c.name );

        Chinese h = new Chinese( "钟大爷" );
        System.out.println( h.name );

    }

}

4、特殊情况

4.1、如果某个类的所有构造方法都是私有的则该类不可能有子类

例如,创建 Shape 父类,其中 它的所有构造方法是私有的

class Shape {

    private String type ;

    private Shape() { 
    }

    public Shape( String type ) {
        this.type = type ;
    }

    public void show(){
        System.out.println( this.type );
    }

}

再创建 Triangle 子类去继承 Shape 父类,使用 super 关键字 调用父类中的 构造方法

class Triangle extends Shape {

    public Triangle(){
        super( "三角形" );
    }

}

最后创建 ShapeTest 测试类,输出 show() 方法中的内容

public class ShapeTest {

    public static void main(String[] args) {

        Triangle t = new Triangle();
        t.show();
        
    }

}

4.2、子类可以重新声明父类中的成员

例如,先创建 Botany 父类,并声明一个受保护的字段,并为其赋值

public class Botany {

    protected String name = "植物" ;

}

再创建 Tree 子类 去继承 Botany父类,并通过 super关键字 访问父类中已声明的字段,并重新声明该字段

public class Tree extends Botany {

    // 如果子类中声明了与父类中同名的字段( 并且父类中的同名字段对子类是可见的 )
    // 此时在子类中使用 该名称对应的字段时,会使用 子类中的字段 而不是从父类继承的、可见的字段
    protected String name = "树" ; // 子类可以重新声明父类中的成员

    public void show() {
        System.out.println( "super.name : " + super.name ); // 通过 super 关键字访问 从父类继承的、可见的 字段 或 方法
        System.out.println( "this.name : " + this.name ); // 输出本类中声明的 name 字段的值
        System.out.println( "name : " + name );
    }

    public static void main(String[] args) {
        Tree t = new Tree();
        t.show();
    }

}

7、封装

img

7.1、访问修饰符

img

7.1.1、public 和 private

1、被 public 修饰的 字段 或 方法 可以在 任意位置 使用

2、被 private 修饰的 字段 或 方法 只能在本类中使用

public class Human {

    // 被 public 修饰的 字段 或 方法 可以在 任意位置 使用
    public String name ; // 实例变量
    // 被 private 修饰的 字段 或 方法 只能在本类中使用
    private boolean married ; // 实例变量

    public void show(){
        System.out.println( "我是 " + name + " ,目前 " + ( married ? "已婚" : "未婚" ) );
    }

    public static void main(String[] args) {

        Human h = new Human();
        h.name = "令狐冲" ;
        h.married = false ;

        h.show();

    }

}
public class HumanTest {

    public static void main(String[] args) {

        Human h = new Human();
        h.name = "令狐冲" ;
        // h.married = true ; //【编译失败】错误: married 在 Human 中是 private 访问控制

        h.show();

    }

}

7.1.2、property(属性)

属性 ( property ) 和 字段 ( field ) 不是一个概念,可以通过非私有的方法来访问私有化的字段

public class Chinese {

    private String name ; // name 字段本质上是个变量,用于存储数据
    private int age ;
    private char gender ;
    private boolean married ;

    // 提供用来访问数据的方法 ( 就是读写字段中所持有数据的方法 )
    public String getXingMing(){ // 获取数据的方法 使用 getXxx 命名
        return name ; // 用来获取某个字段取值的方法直接返回相应的字段
    }

    public void setXingMing( String xm ){ // 为字段赋值的方法使用 setXxx 形式命名
        name = xm ;
    }

    //【以下注释混个眼熟,不必深入】
    // getNianLing() ==== 去掉 get 和 参数部分 ====>  NianLing ------首字母变小写、其它字母不变-----> nianLing 【框架】
    // 我们将最后得到的 nianLing 称作 属性 ,英文中称作 property ,这与 字段 是不同的
    public int getNianLing(){
        return age ;
    }

    public void setNianLing( int nl ){
        if( nl >= 0 ) {
            age = nl ;
        } else {
            System.out.println( "年龄设置错误" );
        }
    }

    public char getGender(){
        return gender ;
    }

    public void setGender( char gender ){ // 参数名称是 gender ,与本类中的 gender 字段同名
        // 在 Java 语言中 this 可以理解为 汉语中的 "我" ,它表示当前对象本身
        this.gender = gender ; // 为了区分两个不同的 gender 变量,在 gender 字段之前使用了 this 关键字
    }

    public boolean getMarried(){
        return married ;
    }

    public void setMarried( boolean married ){ // 参数名称是 married ,与本类中的 married 字段同名
        this.married = married ; // 为了区分两个不同的 married 变量,在 married 字段之前使用了 this 关键字
    }

    public void show(){
        System.out.println( "我叫" + name + " , 今年" + age + "岁,目前" +  ( married ? "已婚" : "未婚" )  );
    }

}
public class ChineseTest {


    public static void main(String[] args) {

        Chinese c = new Chinese();
        // c.name = "张翠山" ; // 【编译】错误: name 在 Sinaean 中是 private 访问控制
        c.setXingMing( "张翠山" );
        // c.age = 28 ;
        c.setNianLing( 28 );
        // c.gender = '男' ;
        c.setGender( '男' );
        // c.married = true ;
        c.setMarried( true );

        c.show();

        System.out.println( c.getXingMing() + " , " + c.getNianLing() + " , " + c.getMarried() );

    }


}

7.2、封装的过程

7.2.1、什么是封装

  • 将抽象的数据和行为组合成一个有机整体
  • 将需要向外暴露的部分暴露出来,让外部程序有机会进行访问
  • 将需要向外隐藏的部分隐藏起来,让外部程序无法访问

7.2.2、如何实现封装

  • 需要隐藏起来的部分使用 private 修饰符修饰
  • 期望对外暴露的部分使用 非 private 修饰符修饰
    • 默认的(也就是什么修饰符都不写)
    • protected(受保护的)
    • public(公开的)

7.2.3、封装的特点

  • 只能通过规定的方法访问数据
  • 隐藏类的细节,方便修改和实现

7.2.4、this

在 Java 语言中 this 可以理解为 汉语中的 "我" ,它表示当前对象本身

8、继承

继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类

8.1、什么是继承

继承就是子类继承父类的特征和行为,使得 子类对象(实例)具有父类的 实例域 和 方法 ,或 子类从父类继承方法,使得子类具有父类相同的行为

8.2、类的继承格式

在 Java 中通过 extends 关键字可以申明一个类是从另外一个类继承而来的,一般形式如下:

class 父类 {
    
}
 
class 子类 extends 父类 {

}

示例:

class Human {

    public String name ;

    public void show(){
        System.out.println( this.name );
    }

}

public class Sinaean extends Human {

    public int age ;

    public void introduce(){
        System.out.println( "我叫" + this.name + " , 今年" + this.age );
    }

    public static void main(String[] args) {
        
        Sinaean s = new Sinaean();
        s.name = "欧阳锋" ;
        s.age = 40 ;
        s.show();
        s.introduce();

    }

}

8.3、类与类之间继承的特点

  1. 当一个 class 没有通过 extends 关键字显式指定父类时,其直接父类是 java.lang.Object
  2. 子类可以继承父类中的一切实例成员,但 能不能访问要看修饰符
  3. 当 创建 子类类型 的对象时,也会为 父类中声明的 实例字段 开辟空间 并赋予默认值
  4. 子类可以在继承父类原有实例成员的基础上,对父类进行扩展
  5. 一个类只能直接继承一个父类 ( 在 extends 之后指定多个父类的做法是错误的 )
class Vehicle {

    private String frame ; // 架子
    private String wheel ; // 轮子

    public String getFrame(){
        return frame ; // 等同于 return this.frame ;
    }

    public void setFrame( String frame ){
        this.frame = frame ;
    }

    public String getWheel(){
        return wheel ; // 等同于 return this.wheel ;
    }

    public void setWhell( String wheel ){
        this.wheel = wheel ;
    }
    
}

/** 子类可以继承父类中的一切实例成员,但能不能访问要看修饰符 */
class Bicycle extends Vehicle {
    // 当 创建 子类类型 的对象时,也会为 父类中声明的 实例字段 开辟空间 并赋予默认值
}

/** 子类可以在继承父类原有实例成员的基础上,对父类进行扩展 */
class Car extends Vehicle {
    public String locator ; // 定位器,比如 使用 北斗定位 、使用 GPS 定位
    public String sofa ;

    public void info() {
        System.out.println( this.getFrame() + " , " + this.getWheel() + " , " + this.locator + " , " + this.sofa );
        // System.out.println( this.frame + " , " + this.wheel + " , " + this.locator + " , " + this.sofa );
    }
}

// 在 Java 语言中,一个类只能直接继承一个父类 ( 在 extends 之后指定多个父类的做法是错误的 )
// class X extends Bicycle , Car { }

public class VehicleTest {

    public static void main(String[] args) {
        
        Bicycle b = new Bicycle();
        b.setFrame( "钢铁" );
        System.out.println( b.getFrame() );

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        Car c = new Car(); // frame / wheel / locator / sofa
        c.setFrame( "钛合金" );
        c.setWhell( "四个轮子" );

        c.locator = "北斗定位系统" ;
        c.sofa = "坐上去贼爽的沙发座位" ;

        c.info();

    }

}

但是,对于类变量和类方法来说,子类也许可以访问,但不属于继承范畴

class Animal {
    // 被 static 修饰的 字段 或 方法 是属于当前类的 ( 如果子类能继承不就矛盾了吗 )
    public static String type ;
    public String name ;

    static {
        System.out.println( "Animal Initialization" );
        type = "动物" ;
    }
}

class Monkey extends Animal {

    static {
        System.out.println( "Monkey Initialization" );
    }
    
}

public class AnimalTest {

    public static void main(String[] args) {

        System.out.println( Monkey.type ); // 仅仅完成了对 Animal 的初始化,并没有初始化 Monkey

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        Monkey m = new Monkey(); // 创建 Monkey 类的对象时导致 Monkey 被初始化
        m.name = "孙悟空" ;
        System.out.println( m.name ) ;
        
    }

}

8.4、理解继承的执行顺序

  1. 首先第一部分执行的是父类的静态代码块—子类的静态代码块—主程序。这一部分都是执行一次,与建立多少

    对象没有关系。

  2. 第二部分new了一个父类对象,并调用了方法。执行了它的非静态代码块—构造函数—一般方法

  3. 第三部分new了一个子类的对象,并调用了方法。执行顺序为父类的非静态代码块—父类的无参构造函数,然

    后是子类的非静态代码块—子类构造函数—子类的方法。

public class Father {
    
    protected String familyName ; // 姓
    private String name ;
    
    {
        System.out.println( "Father : Instantiation" );
        this.familyName = "张" ;
        this.name = "三丰" ;
        System.out.println( "Father : name = " + this.name );
    }

}
public class Child extends Father {
    
    int age ;
    
    {
        System.out.println( "Child : Instantiation" );
        this.age = 18 ;
    }

}
public class Test {

    public static void main(String[] args) {

        Child c =  new Child();
        System.out.println( c.familyName );
        // System.out.println( c.name ); // The field Father.name is not visible
        System.out.println( c.age );
        
    }

}

9、Object 类

Java在java.lang包中有一个Object类

9.1、Object 类的意义

  • Class Object is the root of the class hierarchy.(所有Java类都直接或间接扩展Object类)
  • Every class has a Object as a superClass.(所有Java类都是Object类的子类Object类是所有类的超类)
  • All objects , including array,implement the methods of the class.(Object类的方法可以在Java中的所有类中使用)

9.2、Object 的方法

9.2.1、toStringhashCode

1、Object 类中定义了 toString() 方法用于获取对象的字符串表示形式

2、Object 类中定义了 hashCode() 方法用于获取对象的 哈希码 ( 或称作 哈希值 / 哈希码值 )

public class Hamster extends Object { // java.lang 包中的所有的类都可以不显式 import

    public static void main(String[] args) {
        
        Object o = new Object();
        System.out.println( o ); // 类型@哈希码

        String s = o.toString() ; // 获取 o 所引用的对象的字符串表示形式
        System.out.println( s ); // 类型@哈希码

        int hash = o.hashCode(); // 获取 哈希码 ( 以十进制形式返回 )
        System.out.println( "十进制形式: " + hash );
        System.out.println( "十六进制形式: " + Integer.toHexString( hash ) );

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        Hamster h = new Hamster();
        System.out.println( h );
        System.out.println( h.toString() );
        System.out.println( h.hashCode() );
        System.out.println( Integer.toHexString( h.hashCode() ) );

    }

}

但是我们在 重写 hashCode() 后如何获取原来由 ObjecthashCode() 返回的哈希码值呢?

**即使某个类重写了 hashCode 方法,仍然可以通过 identityHashCode 方法来获取原本由 Object 提供的 **

hashCode 所返回的 哈希码值

package Object;

public class Sheep {

    private int age ;
    private int weight ; // kg

    public Sheep( int age , int weight ) {
        this.age = age ;
        this.weight = weight ;
    } 

    @Override
    public int hashCode(){
        System.out.println( super.hashCode() );
        // 暂时不考虑实现细节一律返回 零
        return 0 ;
    }

    public static void main(String[] args) {
        
        Sheep s = new Sheep( 5 , 30 );
        System.out.println( s.hashCode() );
        // 即使某个类重写了 hashCode 方法,仍然可以通过 identityHashCode 方法来获取原本由  Object 提供的 hashCode 所返回的 哈希码值
        System.out.println( System.identityHashCode( s ) );

        Sheep h = new Sheep( 6 , 35 );
        System.out.println( h.hashCode() );
        System.out.println( System.identityHashCode( h ) );

        System.out.println( s == h );

    }

}

理解 hashCode 的常规协定

1、所谓的重写后的 equals 方法要与 hashCode 一致,实际上就是遵守 hashCode 常规协定 ( 在 Java API 中找常规协定 )

2、简单来说,equals 方法中通过什么字段判断两个对象是否相等,则在 hashCode 中就应该使用什么样的字段来产生哈希码值

3、当两个对象使用 equals 方法比较后返回 true 时,它们的 哈希码值 必须相同

4、Objects.hash( Object... value ) 可以根据一组数据产生一个哈希码值

注意,这个类没有重写 hashCode 和 equals 方法,因此仍然是在使用 Object 类中的 hashCode 和 equals 方法

package Object;

public class Rooster {

    private String name ;
    private int age ;

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

    public static void main(String[] args) {

        Rooster r = null ; // 声明一个 Rooster 类型的变量并将其赋值为 null

        r = new Rooster( "昴日星官" , 12500 );

        System.out.println( r.hashCode() ); // 第 1 次

        r.name = "昴日星官他哥" ;
        System.out.println( r.hashCode() ); // 第 2 次

        r.age = 13500 ;
        System.out.println( r.hashCode() ); // 第 3 次

        r.name = "昴日星官他弟" ;
        r.age = 12000 ;
        System.out.println( r.hashCode() ); // 第 4 次
    
    }

}

然后,在下面这个类中,重写了 hashCode 和 equals 方法

package Object;

import java.util.Objects;

public class MagicRooster {

    // 实例变量
    private String name ;
    private int age ;

    // 构造方法
    public MagicRooster( String name , int age ){
        super();
        this.name = name ;
        this.age = age ;
    }

    // 重写父类中的 equals 方法,并通过比较 name 和 age 变量来判断两个对象是否相等
    @Override
    public boolean equals ( Object x ) {

        if( x == null ) {
            return false ;
        }

        if( this == x ) { // 如果两个对象地址相同则返回 true
            return true ;
        }

        // 判断两个对象的类型是否相同
        if( this.getClass() != x.getClass() ) {
            // 如果两个对象的类型是相同的,则通过 各自的 getClass() 方法所返回的 类型 是同一个对象
            // 如果不相同,则一定不是同一个对象
            return false ;
        }

        MagicRooster another = (MagicRooster) x ;

        // return this.name.equals( another.name ) && this.age == another.age ;
        // 比较字符串是否相等要使用 equals 方法而不是使用 == 运算符
        return name.equals( another.name ) && age == another.age ;
    }

    @Override
    public int hashCode() {
        
        // 为了与 equals 保持一致,可以在 hashCode 方法中根据 equals 方法中所比较的字段来产生哈希码值
        return Objects.hash( name , age ); // 使用 java.util 包中的 Objects 类 的 hash 方法产生 哈希码值

        // int result = 1 ;
        // result = 31 * result + ( name == null ? 0 : name.hashCode() );
        // result = 31 * result + ( age == 0 ? 0 : age );
        // return result ;
    }

    public static void main(String[] args) {

        MagicRooster a = new MagicRooster( "昴日星官" , 12500 );
        MagicRooster b = new MagicRooster( "昴日星官" , 12500 );

        System.out.println( a == b ); // 比较 变量 a 和 变量 b 中存储的值 ( 两者存储的都是地址 )
        System.out.println( a.equals( b ) ); // 比较 变量 a 所引用的对象 和 变量 b 所引用的对象

        System.out.println( "~~~~~~~~~~~~~" );

        MagicRooster x = a ;
        System.out.println( a.equals( x ) ) ;

        System.out.println( "~~~~~~~~~~~~~" );

        System.out.println( a.hashCode() );
        System.out.println( b.hashCode() );
    
    }

}

9.2.2、测试 getClass() 方法

1、在 Java 语言中,一切都可以当做对象来对待。比如:

① 一个类可以当做一对象来对待

② 一个类的实例可以当做对象来对待

2、在 Java 中使用 java.lang.Class 类来表示 类类型的对象 ,就是将 类 当做对象来对待,每个类都对应一

个 对象 ,比如 Object 类 对应的 对象 就可以使用一个 Class 实例 来表示

例如:类(Class)

鼠类 : 舒克 、贝塔 、杰瑞

牛类 : 牛魔王 、青牛怪(兕大王)

虎类 : 虎先锋 、虎妞

package Object;

public class Buffalo {

    public static void main(String[] args) {
        
        Object o = new Object() ;
        System.out.println( o ) ; // o.toString()
        System.out.println( o.toString() ) ;

        System.out.println("~~~~~~~~~~~~~~~~~~~~") ;

        // 获取 某个引用变量 所引用的 对象 的 类型 对应的 对象 【将类当做对象,获取某个实例对应的类的对象】
        Class c = o.getClass() ;
        System.out.println( c ) ; // c.toString() 
        System.out.println( c.toString()  ) ;

        System.out.println("~~~~~~~~~~~~~~~~~~~~") ;


        int[][] array = { {1,3} , {5,7} , {9} } ;
        System.out.println( array ) ;
        System.out.println( array.toString() ) ;
        System.out.println( "十六进制的哈希码是:" + Integer.toHexString( array.hashCode() ) ) ;

        Class x = array.getClass() ;
        System.out.println( x ) ; // x.toString() 

        System.out.println("~~~~~~~~~~~~~~~~~~~~") ;

        Buffalo b = new Buffalo() ;
        System.out.println( b ) ;
        System.out.println( b.toString() ) ;
        System.out.println( Integer.toHexString( b.hashCode() ) ) ;
        Class y = b.getClass() ;
        System.out.println( y ) ; // y.toString()

    }
    
}

9.2.3、测试 getName() 方法

1、任意一个类型都可以通过 .class 来获取该类型对应的 Class 实例

2、基本数据类型 和 数组 都可以 通过 .class 来获取其相应的 Class 实例

3、所有的引用类型也都可以 通过 .class 来获取其相应的 Class 实例

4、全限定名称

① 对于 基本数据类型 来说就是 类型名称

② 对于 数组类型 来说,都是 [相应类型的字母组成

③ 对于 接口 来说 就是 包名.类名包名.接口名 【不针对内部类和内部接口】

例如,创建一个 Smilodo类

public class Smilodon { 

    public static void main(String[] args) {
        
        Class ic = int.class ;
        // 由 java.lang.Class 类定义的 getName() 方法 可以获得相应类型的全限定名称
        System.out.println( ic.getName() ); 

        Class oc = Object.class ;
        System.out.println( oc.getName() ); 

        Class iac1 = byte[].class ;
        System.out.println( iac1.getName() ); 

        Class iac2 = boolean[][].class ;
        System.out.println( iac2.getName() ); 

        Class cc = Smilodon.class ;
        System.out.println( cc.getName() ); 

        Class interClass = java.util.List.class ; // List 还没讲到
        System.out.println( interClass.getName() ); 


    }

}

9.3、类的重写(覆盖)

9.3.1、重写(覆盖)的概念

1、当子类中声明了与从 父类中继承的(可见的)方法 同名同参同返回 的方法时,就说 子类中的 同名方法 重写了 ( 覆盖了 ) 父类中的同名方法

同名:子类中重新声明的方法的名称与从父类中继承的、可见的方法名称完全相同

同参:子类中重新声明的方法的名称与从父类中继承的、可见的同名方法参数相同

同返回:父类方法如果返回 基本类型 或 void,则子类中重写后的方法返回类型必须与父类方法的返回类型相同

2、当通过子类类型的对象 调用 该名称的方法时,会执行子类中声明的方法

例如,创建一个 Shape 父类:

public class Shape {

    protected double area ;  // 存储面积的实例变量

    private String type ; // 图形类型

    public Shape( String type ) {
        this.type = type ;
    }

    public void calculate() {
        System.out.println( "计算" + this.type + "的面积" );
    }

    public void show(){
        System.out.println( this.type + "的面积为" + this.area );
    }

}

再创建 Trapezoid子类去继承 shape 父类:

public class Trapezoid extends Shape {

    private double top;
    private double bottom;
    private double height;

    public Trapezoid(double top, double bottom, double height) {
        super("梯形"); // 通过 super 调用父类带参数构造
        this.top = top;
        this.bottom = bottom;
        this.height = height;
    }

    public void calculate() {
        System.out.println( "计算梯形面积" );
    }

    public void show() {
        System.out.println( "输出梯形信息和梯形面积" );
    }

}

最后创建 ShapeTest 测试类:

public class ShapeTest {

    public static void main(String[] args) {
        
        Trapezoid t = new Trapezoid( 10 , 20 , 5 );
        t.calculate();
        t.show();

    }

}

9.4、final 修饰符

9.4.1、没有子类

1、通常,将不是为了继承而设计的类定义为 最终类 ( final 修饰的类 )

2、如果某个类不期望有子类可以为该类增加 final 修饰符 ,比如 java.lang.String 就是这种类

例如,创建一个 Person类,并使用 final 修饰符修饰,然后会发现 Sinaean类 无法继承它

package Object;

final class Person {

}

// 编译错误 : 错误: 无法从最终Person进行继承
class Sinaean extends Person {
    
}

public class PersonTest {

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

}

9.4.2、不能被子类重写

例如,创建 Monkey类,并用 final 修饰,发现其不能被子类重写

img

package Object;

public class Monkey {

    // 子类重写的父类方法是被 fianl 修饰符修饰的 ( 被覆盖的方法为 final )
    public final Class<?> getClass() { // 【编译错误】错误: Monkey中的getClass()无法覆盖Object中的getClass()
        return null ;
    }

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

}

10、多态

10.1、什么是多态

1、多态是同一个行为具有多个不同表现形式或形态的能力

2、多态就是同一个接口,使用不同的实例而执行不同操作

3、多态性是对象多种表现形式的体现

例如:现实中,比如我们按下 F1 键这个动作:

  • 如果当前在 Flash 界面下弹出的就是 AS 3 的帮助文档;
  • 如果当前在 Word 下弹出的就是 Word 帮助;
  • 在 Windows 下弹出的就是 Windows 帮助和支持。

同一个事件发生在不同的对象上会产生不同的结果

10.2、使用多态的好处

  • 消除类型之间的耦合关系
  • 可替换性
  • 可扩充性
  • 接口性
  • 灵活性
  • 简化性

用韩老师的话来讲,就是用起来爽!!!

10.3、多态的使用场景

  • 继承
  • 重写
  • 父类引用指向父类对象

10.4、 编译时多态

编写一个 Dog类,并实现其中 add() 方法的重写,展示不同的功能

package Polymorphism;

public class Dog {

    public int add( int a , int b) {
        System.out.println( "int add( int , int )" );
        return a + b ;
    }

    public int add( int a , int b , int c ) {
        System.out.println( "int add( int , int , int)" );
        return a + b + c ;
    }

    public long add( long a , long b ) {
        System.out.println( "long add( long , long )" );
        return a + b ;
    }

    public static void main(String[] args) {
        
        Dog d = new Dog();

        d.add( 100 , 200 );

        d.add( 100L , 200 );

    }
    
}

10.5、运行时多态

1、体验运行时多态: 在运行期间,引用变量 引用了 (指向了) 哪一个子类类型的实例,将来调用方法时就调用哪个实例的方法

2、编译时类型决定了可以通过 引用变量 来访问哪些字段、调用哪些方法,因为子类重写了父类中的方法,因此父类类型的引用变量可以调用的方法在子类实例中是存在的

3、了解 动态绑定

例如,先创建一个 Human父类

package Polymorphism;

public class Human {

    public void eat( String foodName ) {
        System.out.println( "人可以吃" + foodName ) ;
    }
    
}

再创建 Sinaean子类、British子类、Indian子类

package Polymorphism;

public class Sinaean extends Human { 

    public void eat( String foodName ) {
        
        System.out.println( "中国人用筷子吃" + foodName ) ; 

    }
    
}
package Polymorphism;

public class British extends Human { 

    public void eat( String foodName ) {
        System.out.println( "英国人用刀和叉吃" + foodName  ) ;
    }
    
}
package Polymorphism;

public class Indian extends Human {
    
    public void eat( String foodName ) {
        System.out.println( "印度人用手吃"  + foodName ) ;
    } 

}

最后创建 HumanTest 测试类

package Polymorphism;

public class HumanTest {

    public static void main(String[] args) {
        
        Human h = null ; // 声明一个 Human 类型的引用变量 h 并为其赋值为 null
        System.out.println( "h=> " + h ) ;

        h = new Human(); // Human 类型的引用变量 引用了 (指向了) 一个 Human 实例
        System.out.println( "h=> " + h ) ;
        h.eat( "美食" ) ;

        // 父类类型的引用变量 引用了 (指向了)  子类类型的对象
        h = new Sinaean(); // Human 类型的引用变量 引用了 (指向了) 一个 Sinaean 实例
        System.out.println( "h=> " + h ) ;
        h.eat( "米饭" ) ;

        // 父类类型的引用变量 引用了 (指向了)  子类类型的对象
        h = new British() ; // Human 类型的引用变量 引用了 (指向了) 一个 British 实例
        System.out.println( "h=> " + h ) ;
        h.eat( "包子" ) ;

        // 父类类型的引用变量 引用了 (指向了)  子类类型的对象
        h = new Indian() ; // Human 类型的引用变量 引用了 (指向了) 一个 India 实例
        System.out.println( "h=> " + h ) ;
        h.eat( "火锅" ) ;

    }
    
}

10.6、静态绑定和动态绑定

10.6.1、探究问题

1、当子类和父类存在同一个方法,子类重写了父类的方法,程序在运行时调用方法是调用父类的方法还是子类的重写方法呢?

2、当一个类中存在方法名相同但参数不同(重载)的方法,程序在执行的时候该如何辨别区分使用哪个方法呢? 在Java中我们使用静态绑定(static binding)和动态绑定(Dynamic binding)来解决,那么什么是绑定?什么是静态绑定?什么又是动态绑定?有什么区别?

10.6.2、引进几个概念

绑定:指的是一个方法的调用与方法所在的类(方法主体)关联起来。对java来说,绑定分为静态绑定和动态绑定;或者叫做前期绑定和后期绑定。

静态绑定(前期绑定):在程序执行前方法已经被绑定,针对java简单的可以理解为程序编译期的绑定; java当中的方法只有final,static,private和构造方法是前期绑定

动态绑定(后期绑定):在运行时根据具体对象的类型进行绑定。提供了一些机制,可在运行期间判断对象的类型,并分别调用适当的方法。也就是说,编译器此时依然不知道对象的类型,但方法调用机制能自己去调查,找到正确的方法主体。

10.6.3、比较一下

静态绑定 动态绑定
发生在编译阶段 发生在运行阶段
privatefinal and static 方法 、变量使用 虚函数(非 final 的方法)
使用的是类信息 使用的是对象信息
重载方法(overloaded methods) 重写方法(overridden methods)

10.6.4、动态绑定的过程

1、虚拟机提取对象的实际类型的方法表 2、虚拟机搜索方法签名 3、调用方法

10.6.5、代码分析

下面从代码中分析

静态绑定

public class StaticBindingTest {

    public static void main(String args[])  {
        StaticBindingTest sbt = new StaticBindingTest();
        sbt.test("This is a String");
        sbt.test(10);
     }

    public void test(String s) {
        System.out.println(s);
    }

    public void test(int a) {
        System.out.println(a);
    }
}

在两个同名但参数不同的方法(重载),在调用方法sbt.test(param)时,程序会自动根据输入的参数类型来选择具体调用哪个方法,其后的原理就是静态绑定,即在编译期根据参数类型进行静态绑定

但java当中的向上转型或者说多态是借助于动态绑定实现的,所以理解了动态绑定,也就搞定了向上转型和多态

动态绑定的典型发生在父类和子类的转换声明之下:

Parent p = new Child();
// 其具体过程细节如下:
// 1:编译器检查对象的声明类型和方法名。假设我们调用p.method()方法,并且p已经被声明为Child类的对象,那么编译器会列举出Child类中所有的名称为method的方法和从Child类的父类继承过来的method方法
// 2:接下来编译器检查方法调用中提供的参数类型。如果在所有名称为method 的方法中有一个参数类型和调用提供的参数类型最为匹配,那么就调用这个方法,这个过程叫做“重载解析” 
// 3:当程序运行并且使用动态绑定调用方法时,虚拟机必须调用同p所指向的对象的实际类型相匹配的方法版本。假设child类定义了mehod()那么该方法被调用,否则就在child的父类(Parent类)中搜寻方法method()

动态绑定

首先,当子类中有父类重写的方法

//父类
public class Parent {

    protected String name = "ParentName";

    public void method() {
        System.out.println("ParentMethod");
    }
}

//子类
public class Child extends Parent {
    protected String name = "ChildName";

    public void method() {
        System.out.println("ChildMethod");
    }

    public static void main(String[] args) {
        Parent p = new Child();
        System.out.println(p.name);
        p.method();
    }
}

然后,当子类中没有父类的重写方法

//测试类
public class DynamicBindingTest {

    public static void main(String[] args) {
        Parent p = new Child();
        System.out.println(p.name);
        p.method();
    }
}

//父类
class Parent {

    protected String name = "ParentName";
    
    public void method() {
        System.out.println("ParentMethod");
    }
}

//子类
class Child extends Parent {
    protected String name = "ChildName";
    
}    

从上面的结果中可以看出:

1、子类的对象(由父类的引用handle)调用到的是父类的成员变量,运行时(动态)绑定针对的范畴只是对象的方法,而属性要采取静态绑定方法

2、执行p.method()时会先去调用子类的method方法执行,若子类没有则向上转型去父类中寻找

所以 在向上转型的情况下,对象的方法可以找到子类,而对象的属性还是父类的属性

10.6.7、引用类型的强制类型转换

创建 Sinaean 类

package Polymorphism;

public class Sinaean extends Human { 

    public void eat( String foodName ) {
        
        System.out.println( "中国人用筷子吃" + foodName ) ; 

    }

    public void taiChi(){
        System.out.println( "许多中国人喜欢打太极拳" );
    }
    
}

再创建 HumanTest 测试类,并进行引用类型的强制类型转换

public class HumanTest3 {

    public static void main(String[] args) {

        // 父类类型的引用变量 指向 子类类型的对象
        Object o = new Sinaean();
        // o.eat( "藜蒿炒腊肉" ); //【错误】变量 o 的编译时类型是 Object ,其中并没有声明 eat 方法

        if( o instanceof Human ) {
            Human h = (Human) o ;
            h.eat( "藜蒿炒腊肉" ); // 变量 h 的编译时类型是 Human ,其中是包含被调用的 eat 方法
            System.out.println( o == h ); // true
        }

        if( o instanceof Sinaean ) {
            Sinaean s = (Sinaean) o ;
            s.eat( "宫爆鸡丁" );
            s.taiChi(); // 变量 s 的编译时类型是 Sinaean ,其中是包含被调用的 taiChi 方法
            System.out.println( o == s ); // true
        }

    }

}

11、抽象类

11.1、为什么会有抽象类

1、在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类

2、抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样

3、由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。也是因为这个原因,通常在设计阶段决定要不要设计抽象类

4、父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法

在Java中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口

11.2、理解抽象类和抽象方法

1、抽象类具体类 的区别是 抽象类不可以被实例化,而 具体类 可以被实例化

2、抽象类 中不一定要有 抽象方法,为了让某个类不能被 实例化 也可以将该类设计为 抽象类

3、抽象类 可以继承 抽象类具体类(非抽象类) 可以继承 抽象类抽象类可以继承 具体类

5、抽象方法由 abstract 修饰的、没有方法体 的方法

6、有 抽象方法 的类必须是 抽象类

7、如果 具体类 继承了 抽象类,则必须实现 抽象类 中的所有 抽象方法

先创建一个 继承 Object类 的 Human抽象类,所以 Human类 不能被 实例化

public abstract class Human extends Object { // 抽象类 可以继承 具体类 ( 非抽象类 )

    protected String name ;

    static {
        System.out.println( "Human : 类初始化块" );
    }

    {
        System.out.println( "Human : 实例初始化块" );
    }

    public Human(){
        super();
        System.out.println( "Human()" );
    }

    public Human( String name ){
        super();
        this.name = name ;
        System.out.println( "Human(String)" );
    }

    public void show() {
        System.out.println( this.name );
    }

    public static void main(String[] args) {
        // Human h = new Human(); //【错误】Human是抽象的; 无法实例化
    }

}

再创建 Sinaean抽象类 去继承 Human抽象类,里面的方法是抽象方法

public abstract class Sinaean extends Human {

    public Sinaean(){
        super(); // 调用父类构造 ( 不管父类是 抽象类 还是 具体类 )
        System.out.println( "Sinaean()" );
    }

    // 声明一个抽象方法
    public abstract void eat( String foodName ) ; // 没有方法体、由 abstract 修饰符修饰

}

最后创建 Han具体类 去继承 Sinaean抽象类,必须实现 Sinaean抽象类 的所有抽象方法

public class Han extends Sinaean {

    public Han(){
        super(); // 调用父类构造 ( 不管父类是 抽象类 还是 具体类 )
        System.out.println( "Han()" );
    }

    @Override
    public void eat( String foodName ) { // 如果有方法体就一定不能有 abstract 修饰符
        System.out.println( "在中国汉族人基本都使用筷子吃" + foodName + ",比如我" + this.name + "就是这样的");
    }

    public static void main(String[] args) {

        Han h = new Han();
        h.name = "罗文康" ;
        h.eat( "火锅" );

    }

}

12、包装类

12.1、什么是包装类

Java中的基本类型功能简单,不具备对象的特性,为了使基本类型具备对象的特性,所以出现了包装类,就可

以像操作对象一样操作基本类型数据

12.2、手动实现包装类

1、最终类:被 final 修饰的类就是所谓的 最终类 ( 或者称作 不可变类 / 不变类 )

而且被 final 修饰的类是没有子类的

2、最终变量:被 final 修饰的变量被称作 最终变量 ( 可以是 类变量、实例变量 、局部变量 、参数 )

3、常量:被 final 修饰的 类变量 被称作 常量

4、不可变对象:在 堆内存中所创建的 Decimal 对象是不可变的 ( 因为其内部 实例变量的 值不可更改 )

public final class Decimal {

    // 被 final 修饰的 类变量 被称作【常量】
    public final static long MAX_VALUE = 0x7fffffffffffffffL ; 
    // 常量名称中所有单词一律大写,多个单词之间使用 下划线 隔开
    public final static long MIN_VALUE = 0x8000000000000000L ; 

    // 被 final 修饰的 实例变量 在初始化之后不能再次赋值
    private final long value ;

    {
        // System.out.println( "this.value : " + this.value ); // 错误: 可能尚未初始化变量value
    }

    public Decimal ( long value ) {
        // System.out.println( "this.value : " + this.value ); // 错误: 可能尚未初始化变量value
        this.value = value ;
        System.out.println( "this.value : " + this.value );
    }

    public long getValue(){
        return this.value ;
    }

    public void setValue( long value ) {
        // this.value = value ; // 错误: 无法为最终变量value分配值
    }

    public static void main(String[] args) {
        // 在 堆内存中所创建的 Decimal 对象是不可变的 ( 因为其内部 实例变量的 值不可更改 ) 【不可变对象】
        Decimal d = new Decimal( 100L );
        System.out.println( d.getValue() );
        System.out.println( System.identityHashCode( d ) );

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        // 但是 Decimal 类型的变量 d 的值 是可以改变的,也就是 d 可以引用不同的 Decimal 实例
        d = new Decimal( 200L );
        System.out.println( d.getValue() );
        System.out.println( System.identityHashCode( d ) );

    }

}

12.3、基本类型对应的包装类

基本类型 包装类型
byte Byte
short Short
int Integer
long Long
float Float
double Double
boolean Boolean
char Character

我们可以通过 valueOf 方法来获取实例,但是在 Java9 之前使用 包装类的 构造方法创建实例

例如:八种基本类型对应的包装类都有一个将相应的基本类型数据封装成该类对象的 valueOf 方法

package Wrapper;

public class WrapperTest1 {

    public static void main(String[] args) {

        // Java 9 之前使用 构造方法创建 相应的实例
        byte b = 100 ;
        Byte first = new Byte(b)  ; // 但是从 Java 9 开始 包装类的 构造方法均已废弃
        System.out.println( first ) ; // first.toString()

        Short s = 9527 ;
        Short second = new Short(s) ;
        System.out.println( second ) ; // second.toString()

        // 从 Java 9 开始 建议使用 工厂方法来创建 包装类的实例
        Integer third = Integer.valueOf( 999 ) ;
        System.out.println( third ) ;

        Long fourth = Long.valueOf(999L) ;
        System.out.println( fourth ) ;

        Float fifth = Float.valueOf(13.2454f) ;
        System.out.println( fifth ) ;
        
        Double sixth = Double.valueOf(13213.02) ;
        System.out.println( sixth ) ;
        
        Boolean seventh = Boolean.valueOf( false ) ;
        System.out.println( seventh ) ;

        Character eighth = Character.valueOf( '汉' ) ;
        System.out.println( eighth ) ;

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        System.out.println( fourth.getClass() ); // 获得运行时类型
        System.out.println( fourth.hashCode() ); // 获得哈希码值
        System.out.println( fourth.toString() ); // 获取字符串表示形式

        // 即使变量 fourth 指向的 对象对应的类 重写 hashCode 方法,
        // 我们仍然可以通过 identityHashCode 来获取 由 Object 提供的 hashCode 方法所返回的值
        System.out.println( System.identityHashCode( fourth ) );


    }
 
}

但是,除了 Character 外,八种基本类型对应的包装类都有一个将字符串解析并封装成该类对象的 valueOf 方法

使用格式:【 字符串 ===> 基本数据类型的数值 】

public static Xxx valueOf( String  value )

例如:

package Wrapper;

public class WrapperTest2 {

    public static void main(String[] args) {
        
        // valueOf 接受一个与之对应的基本数据类型的数值做参数,返回一个当前类型的对象
        Byte b = Byte.valueOf( "100" ) ;
        System.out.println( b ) ;

        Short s = Short.valueOf(  "1000" ) ;
        System.out.println( s ) ;

        Integer i = Integer.valueOf( "11321" ) ;
        System.out.println( i ) ;

        Long l = Long.valueOf( "13214546" ) ;
        System.out.println( l ) ;

        Float f = Float.valueOf( "12654.12" ) ;
        System.out.println( f ) ;

        Double d = Double.valueOf( "1354564.1245" ) ;
        System.out.println( d ) ;

        Boolean bl = Boolean.valueOf( "false" ) ; // 输入其他字符串是 false ,输入 true 才是 true
        System.out.println( bl ) ;

        // Character c = Character.valueOf( "汉" ); // Character 没有定义 valueOf( String ) 方法

    }
    
}

当然,Java 也提供了 将 基本数据类型的数值 转换为 字符形式 表示

Integer 类中的 toString 方法,其它包装类的 toBinaryString 、toOctalString 、toHexString 等方法

package Wrapper;

public class WrapperTest3 {

    public static void main(String[] args) {

        final int x = 12 ;

        String s = x + "" ; // 【近"串"者"串"】
        System.out.println( s + " , " + s.getClass().getName() ) ;

        String ss = Integer.toString( x ); // 返回 整数的 十进制 字符串 形式
        System.out.println( ss + " , " + ss.getClass().getName() ) ;

        String binary = Integer.toString( x, 2 ) ; // 返回 整数的 二进制 字符串 形式
        System.out.println( binary + " , " + binary.getClass().getName() ) ;

        String hex = Integer.toString( x, 16 ) ; // 返回 整数的 十六进制 字符串 形式
        System.out.println( hex + " , " + hex.getClass().getName() ) ;

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" ) ;


        String b1 = Integer.toBinaryString( x ) ; // 返回 整数的 二进制 字符串 形式
        System.out.println( b1 ) ;

        String o1= Integer.toOctalString( x ) ; // 返回 整数的 八进制 字符串 形式
        System.out.println( o1 ) ;

        String h1 = Integer.toHexString( x ) ; // 返回 整数的 十六进制 字符串 形式
        System.out.println( h1 ) ;

    }
    
}

12.4、子类不能重写父类中的类方法

class Super {
    public static String getType() {
        return "Super";
    }
}

class Sub extends Super {
    // 对于 类方法 来说,一般不认为是 重写
    public static String getType() {
        return "Sub";
    }
}

public class StaticMethodTest {

    public static void main(String[] args) {

        String s = null;

        // 编译阶段就 确定 是 哪个类的 getType 方法
        s = Super.getType(); // 被 final 、static 、private 修饰的方法都属于 静态绑定
        System.out.println(s);

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~" );

        // 编译阶段就 确定 是 哪个类的 getType 方法
        s = Sub.getType();
        System.out.println(s); // Sub

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~" );

        // 实例化 Super
        Super su = new Sub();
        // 编译阶段就 确定 getType() 方法是 Super 类中的方法
        s =  su.getType(); // 不推荐的使用方式
        System.out.println(s); // Super

        Sub sub = new Sub();
        // 编译阶段就 确定 getType() 方法是 Sub 类中的方法
        s =  sub.getType(); // 不推荐的使用方式
        System.out.println(s); // Sub

    }

}

12.5、通过包装类实例用数据

1、Byte 、Short 、Integer 、Long 、Float 、Double 都继承了 Number 类

2、将 基本数据类型的数值 封装到 其相应包装类类型的实例中后,可以将这个数值当做一个对象来对待

package Wrapper;

public class WrapperTest4 {

    public static void main(String[] args) {
        
        Double d = null ;

        d = Double.valueOf( 12132.121 ) ;
        System.out.println( d ) ;   // d == null ? "null" : d.toString()

        byte b = d.byteValue() ;
        System.out.println( b ) ;

        int i = d.intValue() ;
        System.out.println( i ) ;

        float f = d.floatValue() ;
        System.out.println( f ) ;

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~" ) ; 

        boolean x = d.isNaN() ; // isNaN : is Not a Number
        System.out.println( x ) ;

        boolean y = d.isInfinite() ;
        System.out.println( y ) ;

        System.out.println( "= = = = = = = = =" ) ;

        Character ch = Character.valueOf( 'A' ) ;
        char c = ch.charValue() ;
        System.out.println( c ) ;



    }
    
}

12.6、自动装箱和自动拆箱

12.6.1、自动装箱

1、所谓自动装箱(Auto-Boxing),就是将 基本类型 的数值 "自动包装" 到一个 包装类类型的实例中

2、将 基本类型的值 赋值给 包装类类型的 引用变量 时,就会发生 自动装箱 ( auto-boxing )

package Wrapper;

public class AutoBoxingTest {

    public static void main(String[] args) {

        int x = 100; // 基本类型 的变量中直接存储数值本身
        System.out.println("x : " + x);

        // 将 基本类型的值 赋值给 包装类类型的 引用变量 时,就会发生 自动装箱 ( auto-boxing )
        Integer o = x; // 引用类型 的变量中存储的是 堆内存 中某个对象的 地址
        System.out.println("o : " + o);
        System.out.println(System.identityHashCode(o));
        System.out.println(Integer.toHexString(System.identityHashCode(o)));

        System.out.println("~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~");

        Integer n = Integer.valueOf(x); // 手动装箱
        System.out.println("n : " + n);

        System.out.println("= = = = = = = = = = = = = = =");

        Object object = 100L; // auto-boxing
        System.out.println("object : " + object);
        System.out.println("运行时类型 : " + object.getClass().getName());

        System.out.println("= = = = = = = = = = = = = = =");

        // JVM 会为引用类型的数组的每个元素赋予的默认值都是 null
        Object[] array = new Object[5];
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i]);
            System.out.print(i < array.length - 1 ? " , " : "\n");
        }

        array[0] = 100; // auto-boxing
        array[1] = 200D; // auto-boxing
        array[2] = 300F; // auto-boxing
        array[3] = 400L; // auto-boxing
        array[4] = 500; // auto-boxing

        for (int i = 0; i < array.length; i++) {
            Object t = array[i];
            System.out.println(t + " , " + t.getClass().getName());
        }

    }

}

12.6.2、自动拆箱

1、所谓自动拆箱(Auto-Unboxing),就是将 包装类类型的实例中所封装的 基本类型的值 取出来,仍然以基本类型数值的方式来运算

2、当将一个包装类类型的引用变量的值 "赋值" 给一个基本数据类型的变量时,会发生 自动拆箱

3、用一个包装类类型的引用变量 参与 运算时,会发生自动拆箱

package Wrapper;

public class AutoUnboxing {

    public static void main(String[] args) {
        
        Integer x = Integer.valueOf( 9527 ); 

        // 当将一个包装类类型的引用变量的值 "赋值" 给一个基本数据类型的变量时,会发生 自动拆箱 ( auto-boxing )
        int i = x ; // auto-unboxing 
        System.out.println( i );

        int n = x.intValue() ; // JDK 1.5 之前需要 手动拆箱
        System.out.println( n );

        Long ox = Long.valueOf( 10000L );
        long o = ox ; // 这里对应的 手动拆箱 代码是 ox.longValue()
        System.out.println( o );

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        Double d = Double.valueOf( 3.14 );
        // 使用 包装类 类型的引用变量 参与数学运算时,会首先自动拆箱,然后再运算
        double u = d + 1 ; // auto-unboxing
        System.out.println( u );

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        Integer y = Integer.valueOf( 999 ); 
        System.out.println( "y : " + y );
        System.out.println( Integer.toHexString( System.identityHashCode( y ) ) );
        y++ ; // 1、auto-unboxing ( 999 )     2、++ ( 999 --> 1000 )     3、auto-boxing ( 1000 )
        System.out.println( "y : " + y );
        System.out.println( Integer.toHexString( System.identityHashCode( y ) ) );

    }

    }
    
}

12.6.3、测试Ineger类的缓存

1、所谓的自动装箱,实际上就是由编译器将 相应的代码替换为 Xxx.valueOf( xxx value )

2、让一个 包装类类型的变量 与 基本类型的变量比较时,会发生自动拆箱

3、Integer.valueOf( int ) 方法中会判断 参数对应的数字是否是 [ -128 , 127 ] 之间

4、了解:

① 在 Ineger 类内部有一个 IntegerCache 内部类

② 在 IntegerCache 中有一个 cache 数组,其中默认缓存了 [ -128 , 127 ] 之间 的数字对应的 Integer 实例

5、自行测试 其它的 包装类中的 缓存

package Wrapper;

public class IntegerCacheTest {

    public static void main(String[] args) {
        
        int x = 100 ; // 基本类型
        Integer a = 100 ; // 引用类型 ( auto-boxing : Integer.valueOf( 100 ) )
        Integer b = 100 ; // 引用类型 ( auto-boxing : Integer.valueOf( 100 )  )
        
        // 但凡是使用 == 比较两个变量,一定是比较变量中存储的值
        System.out.println( a == b ); // true : 说明两者存储的地址是【相同】的
        System.out.println( "a : " + Integer.toHexString( System.identityHashCode( a ) ) );
        System.out.println( "b : " + Integer.toHexString( System.identityHashCode( b ) ) );

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        // 自动拆箱 ( auto-unboxing ) : 手动拆箱后的代码是 a.intValue() == x 
        System.out.println( a == x ); // true : 说明包装类类型的实例中封装的数值 与 基本类型变量中存储的数值 是相等的

        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );

        // Object o = 100 ; // auto-boxing
        // System.out.println( o == x ); // 错误: 二元运算符 '==' 的操作数类型错误

        Integer u = 1000 ; // Integer.valueOf( 1000 ) 
        
        // Integer.valueOf( int ) 方法中会判断 参数对应的数字是否是 [ -128 , 127 ] 之间,
        // 如果是该范围内的数字就直接从 缓存数组中获取已经创建好的 Integer 实例 ,否则就创建新的 Integer 实例
        Integer w =  1000  ; // Integer.valueOf( 1000 ) 
        System.out.println( u == w ); // false : 说明两者存储的地址是【不相同】的
        System.out.println( "u : " + Integer.toHexString( System.identityHashCode( u ) ) );
        System.out.println( "w : " + Integer.toHexString( System.identityHashCode( w ) ) );
        System.out.println( u.equals( w ) ); // 比较两个 Integer 实例中封装的 数值 是否相等

    }
    
}

13、接口

13.1、什么是接口

接口( interface ),在JAVA编程语言中是一个抽象类型,是 抽象方法的集合,接口通常以 interface 来声明

一个类通过继承接口的方式,从而来继承接口的抽象方法

接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法,接口则包含类要实现的方法,除非实现接口的类是抽象类,否则该类要定义接口中的所有方法

接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象

13.2、接口 和 类 的区别

13.2.1、相似点

1、一个接口可以有多个方法

2、接口文件可以保存在 .java 结尾的文件中,文件名使用接口名

3、接口的字节码文件保存在 .class 结尾的文件中

4、接口相应的 字节码文件 必须保存在 与其包名称相同的目录 中

13.2.2、区别

1、接口不能用于实例化对象

2、接口没有构造方法

3、接口中的方法必须是抽象方法

4、接口中不能包含 成员变量 ,除了 static 和 final 修饰的变量

5、接口要被类实现,而不是继承

6、接口支持单继承

13.3、接口的特性

1、接口中的每一个方法都是隐式抽象的,且其方法都是被隐式指定为 private abstract(只能是这个)

2、接口中也可以含有变量,且其变量会被隐式指定为 public static final ( 只能是 public )

3、接口中的方法 是不能在接口中实现的,只能 由实现接口的类 来实现接口中的方法

13.4、抽象类 与 接口 的区别

1、抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行

2、抽象类的成员变量可以是各种类型的,但接口的变量只能是 public static final 修饰

3、抽象类是可以有静态代码块和静态方法,但是接口没有

4、一个类只能继承一个抽象类,而一个类却可以实现多个接口

13.5、编写接口

13.5.1、接口的声明

接口的声明语法格式如下:

[可见度] interface 接口名称 [extends 其他的接口名] {
        // 声明变量
        // 抽象方法
}

Interface关键字用来声明一个接口

13.5.2、接口的实现

当类实现接口的时候,类要实现接口中所有的方法。否则,类必须声明为抽象的类

类使用implements关键字实现接口。在类声明中,Implements关键字放在class声明后面

实现一个接口的语法,可以使用这个公式:

...implements 接口名称[, 其他接口名称, 其他接口名称..., ...] ...

重写接口中声明的方法时,需要注意以下规则

  • 类在实现接口的方法时,不能抛出强制性异常,只能在接口中,或者继承接口的抽象类中抛出该强制性异常
  • 类在重写方法时要保持一致的方法名,并且应该保持相同或者相兼容的返回值类型。
  • 如果实现接口的类是抽象类,那么就没必要实现该接口的方法

    在实现接口的时候,也要注意一些规则:

  • 一个类可以同时实现多个接口

  • 一个类只能继承一个类,但是能实现多个接口

  • 一个接口能继承另一个接口,这和类之间的继承比较相似

13.5.3、接口的继承

一个接口能继承另一个接口,和类之间的继承方式比较相似。接口的继承使用 extends关键字,子接口继承父接口的方法

13.5.4、接口的多继承

在Java中,类的多继承是不合法,但接口允许多继承

在接口的多继承中 extends关键字 只需要使用一次,在其后跟着继承接口

例如:编写 Hockey接口 继承 Sports类 和 Event类

public interface Hockey extends Sports, Event

13.5.5、标记接口

标记接口是没有任何方法和属性的接口,是最常用的继承接口,它仅仅表明它的类属于一个特定的类型,供其他代码来测试允许做一些事情

标记接口作用简单形象的说就是给某个对象打个标(盖个戳),使对象拥有某个或某些特权

使用标记接口的好处:

  • 建立一个公共的父接口:

    正如 EventListener 接口,这是由几十个其他接口扩展的 Java API,你可以使用一个标记接口来建立一组接口的父接口

    例如:当一个接口继承了 EventListener 接口,Java虚拟机(JVM)就知道该接口将要被用于一个事件的代理方案

  • 向一个类添加数据类型:

    这种情况是标记接口最初的目的,实现标记接口的类不需要定义任何接口方法(因为标记接口根本就没有方法),但是该类通过多态性变成一个接口类型

14、字符序列

14.1、字符接口

14.1.1、java.lang.CharSeqence

  • CharSequencechar 值的可读序列
  • 该接口提供对许多不同类型的char序列的统一,只读访问
  • char值表示基本多语言平面(BMP)中的字符或代理项

14.1.2、java.lang.Comparable

  • 此接口对实现它的每个类的对象强加一个总排序

14.1.3、java.lang.Appendable

  • 可以追加char序列和值的对象
  • Appendable 接口必须由其实例用于接收来自 Formatter 的格式化输出的任何类实现
  • 要附加的字符应该是有效的 Unicode字符,如 Unicode Character Representation中所述

14.2、字符编码

14.2.1、编码解码

  1. 编码(encode)

    将字符集 按照某种字符集 转换成 字符序列

    比如:“中国” 按照 UTF-8 转换成 { -28 , -72 , -83 , -27 , -101 , -67 }

  2. 解码(decode)

    将 字节序列 按照某种字符集 转换成 字符串

    比如: { -28 , -72 , -83 , -27 , -101 , -67 } 转换成 “ 中国”

代码示例:

package xyz.wenkang.CharSequence;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;

public class Code {

    public static void main(String[] args) throws UnsupportedEncodingException {
        
        String str1 = URLEncoder.encode( "中国" , "UTF-8");
        System.out.println( str1 ); // %E4%B8%AD%E5%9B%BD
        
        String str2 = URLDecoder.decode( str1 , "UTF-8");
        System.out.println( str2 ); // 中国

    }

}

14.2.2、字符集

  1. 常用字符集
    • ISO-8859-1
    • Latin1
    • Big5:繁体中文
    • GBK:简体中文,每个字符占两个字节
    • UTF-8:每个字符占 1~4 个字节
  2. Unicode 编码
    • UTF-8
    • UTF-16
      • UTF-16
      • UTF-16BE
      • UTF-16LE
    • UTF-32
      • UTF-32
      • UTF-32BE
      • UTF-32LE

14.3、类

14.3.1、java.lang.String

1、在 Java 语言中直接使用 "" 引起来的多个字符就是 字符串 ( character strings )

2、在 Java 源代码中直接使用 "" 引起来的 字符串 ( character strings ) 都是 java.lang.String 类的实例

3、java.lang.String 类是个 不可变类,将来创建的每个 String 实例也都是 不可变对象 【 Strings are constant 】

注意:

在 Java8 之前,使用 char 数组保存字符序列

public final class String {
    private final char[] value ; // 使用 char 数组保存字符序列
}

在Java 9 及以后,使用 byte 数组保存字符序列

public final class String {
    private final byte[] value ; // 使用 byte 数组保存字节序列 ( 字节序列是字符序列根据某种编码转换而来 )
}

同时,在 java.lang.String 类,重写了 equals 方法

package xyz.wenkang.CharSequence.String;

public class StringTest1 {
    
    public static void main(String[] args) {
        
        String str1 = "南风知我意" ; // 指向常量池
        
        String str2 = "南风知我意" ; // 指向常量池
        
        // 使用 == 运算符 比较两个变量时,一定是比较两个变量的值
        System.out.println( str1 == str2 ); // true : 说明 变量 s 和 变量 x 存储的值是相同的
        System.out.println( str1.equals(str2) ); //true : String 重写后的 equals 方法比较的是两个字符串的内容
        
        System.out.println( System.identityHashCode(str1) ); // 1509514333
        System.out.println( System.identityHashCode(str2) ); // 1509514333
        System.out.println( "~~~~~~~~~~~~~~~~~~~~~~~~~~~~" );
        
        String str3 = new String("南风知我意"); // 指向堆
        System.out.println( str3 == str1 ); // false : 说明 变量 s 和 变量 t 存储的值是不同的 
        System.out.println( str3.equals(str1) ); // true : String 类重写后的 equals 会比较两个字符串的内容
        
        System.out.println( System.identityHashCode(str1) ); // 1509514333
        System.out.println( System.identityHashCode(str3) ); // 1556956098
        
        
    }

}

利用工具类透视String实例的内部结构(了解)

StringHelper 类:

package xyz.wenkang.CharSequence.String;

import java.lang.reflect.Array;
import java.lang.reflect.Field;

/*这个类是一个工具,用于帮助我们认识 Stirng 内部存储结构,大家不需要懂*/

public class StringHelper {
    
    private static Field valueField ; // value 字段
    private static Field coderField ; // coder 字段
    private static Field hashField ; // hash 字段
    
    static {
        Class<String> sc = String.class ;
        try {
            valueField = sc.getDeclaredField( "value" ); // 获取 value 字段
            valueField.setAccessible( true );
            coderField = sc.getDeclaredField( "coder" ); // 获取 coder 字段
            coderField.setAccessible( true );
            hashField = sc.getDeclaredField( "hash" ); // 获取 hash 字段
            hashField.setAccessible( true );
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 透视一个字符串实例,将其中的 value 、coder 、hash 输出到控制台中
     * @param s 被透视的字符串实例
     */
    public static void perspective( final String s ) {
        try {
            System.out.println( "字符串实例内容: " + s );
            System.out.println( "字符串实例中字符个数: " + s.length() );
            final Object value = valueField.get( s );
            int length = Array.getLength( value );
            System.out.println( "字符串实例内部字节数组长度: " + length );
            System.out.println( "字符串实例内部字节数组\"地址\": " + System.identityHashCode( value )  );
            System.out.print( "字符串实例内部字节数组: " );
            for ( int i = 0 ; i < length ; i++ ) {
                System.out.print( Array.get( value , i ) );
                System.out.print( i < length - 1 ? " , " : "\n" );
            }
            System.out.println( "字符串实例的 coder 变量: " + coderField.getByte( s ) );
            System.out.println( "字符串实例的 hash 变量: " + hashField.getInt( s ) );
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

}

测试一下:

package xyz.wenkang.CharSequence.String;

public class StringTest3 {

    public static void main(String[] args) {
        
        String str1 = "泡芙"  ;
        str1.hashCode(); // 首次调用 hashCode 方法会将 相应 字符串 的 哈希码值 缓存在 实例变量 hash 中
        StringHelper.perspective(str1);// 透视某个字符串实例
//      字符串实例内容: 泡芙
//      字符串实例中字符个数: 2
//      字符串实例内部字节数组长度: 4
//      字符串实例内部字节数组"地址": 1689843956
//      字符串实例内部字节数组: -31 , 108 , -103 , -126
//      字符串实例的 coder 变量: 1
//      字符串实例的 hash 变量: 897496
        
        System.out.println("~~~~~~~~~~~~~~~~~~");
        
        String str2 = new String("泡芙") ;
        str2.hashCode();
        StringHelper.perspective(str2);
//      字符串实例内容: 泡芙
//      字符串实例中字符个数: 2
//      字符串实例内部字节数组长度: 4
//      字符串实例内部字节数组"地址": 1689843956
//      字符串实例内部字节数组: -31 , 108 , -103 , -126
//      字符串实例的 coder 变量: 1
//      字符串实例的 hash 变量: 897496
//      字符串实例内容: 泡芙
//      字符串实例中字符个数: 2
//      字符串实例内部字节数组长度: 4
//      字符串实例内部字节数组"地址": 1689843956
//      字符串实例内部字节数组: -31 , 108 , -103 , -126
//      字符串实例的 coder 变量: 1
//      字符串实例的 hash 变量: 897496
    }
    
}

String类中的类方法: valueOf 、format 、join

1、将 基本数据类型的值 转换为 字符串形式 : valueOf ( boolean | char | int | long | float | double )

2、public static String valueOf( char[] data )

3、public static String valueOf( char[] data , int offset , int count )

4、public static String valueOf( Object o )

5、public static String format( String format , Object... args )

6、public static String join( CharSequence delimiter , CharSequence... elements ) 【 JDK 1.8 开始提供 】

package xyz.wenkang.CharSequence.String;

public class StringTest4 {

    public static void main(String[] args) {
        
        boolean z = true ;
        String s = String.valueOf(z);
        StringHelper.perspective(s);
        s.hashCode();
        StringHelper.perspective(s);
        
        System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~");
        
        char[] chars = {  '南' , '风' , '知' , '我' , '意' , '吹' , '梦' , '至' , '西' , '洲' };
        
        String x = String.valueOf( chars );
        System.out.println(x);
        
        String t = String.valueOf(chars , 7 , 3 );// 如果超出范围会抛出 StringIndexOutOfBoundsException
        System.out.println(t);
        
        Object o = Integer.valueOf( 9527 );
        String u = String.valueOf( o ) ;
        System.out.println( u );
        
        
    }

}
package xyz.wenkang.CharSequence.String;

public class StringTest5 {
    
    public static void main(String[] args) {
        
        int one = 123456789 ;
        double two = 1234567.89 ;
        String s = String.format( "第一个参数:%,d 第二个参数:%,.2f", one , two );
        System.out.println(s); // 第一个参数:123,456,789 第二个参数:1,234,567.89
        
        System.out.println("~~~~~~~~~~~~~~~~~~~~~");
        
        // JDK 1.8 开始定义了 join 方法
        String x = String.join( "|" , "对抗路" , "中路" , "发育路" , "打野" , "辅助" );
        System.out.println(x); // 对抗路|中路|发育路|打野|辅助
        
        // 注意 split 是个实例方法,一定要通过 String 实例来调用
        String[] array = x.split("===");  // 将一个 String 实例中的 字符串 按照指定的 表达式 拆分成 String 数组
        for (int i = 0; i < array.length; i++) {
            System.out.println( array[i] ); // 对抗路|中路|发育路|打野|辅助
        }
        
        
    }

}

理解字符编码对字符串的影响

1、获取指定字符串对应的字节序列: getBytes()

2、将一组字节序列编码为一个字符串: String( byte[] )String( byte[] , int , int )

3、编码 : 将人类易读的字符串 转换为 一组字节序列 的过程

例如:"中国" ----> { -28 , -72 , -83 , -27 , -101 , -67 }

4、解码 : 将一组字节序列 转换为 人类易读的字符串 的过程

例如:{ -28 , -72 , -83 , -27 , -101 , -67 } ----> "中国"

5、字符集: 从 JDK 1.4 开始 增加了 java.nio.charset.Charset 类表示字符集

package xyz.wenkang.CharSequence.String;

import java.nio.charset.Charset;
import java.util.Arrays;


public class StringTest6 {

    public static void main(String[] args) {
        
byte[] bytes = { 97 , 98 , 99 , 100 , 101 , 102 , 48 , 49 , 50 , 51 , 52 , 53 };
        
        // 通过使用 平台的默认字符集 解码 指定的 byte 数组,构造一个新的 String 实例
        String s = new String( bytes );
        System.out.println( s );
        
        String x = new String( bytes , 0 , 6 );
        System.out.println( x );
        
        bytes[ 0 ] = 65 ;
        
        System.out.println( s );
        System.out.println( x );
        
        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );
        
        String t = "中国" ; 
        System.out.println( t );
        
        // 使用 平台的默认字符集 将此 String 编码 为 byte 序列,并将结果存储到一个新的 byte 数组中
        byte[] bs = t.getBytes();
        // java.util 包 中的 Arrays 类的 toString 方法可以将 数组中的元素 链接到一个字符串中
        String u = Arrays.toString( bs ); 
        System.out.println( u );
        
        String n = new String( bs );
        System.out.println( n );
        
        // String m = new String( new byte[]{ -28, -72, -83, -27, -101, -67 } )  ;
        
        byte[] b = new byte[]{ -28, -72, -83, -27, -101, -67 } ;
        String m = new String( b )  ;
        System.out.println( m );
        
        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );
        
        // 获得 平台的默认字符集
        Charset defaultCharset = Charset.defaultCharset() ;
        System.out.println( defaultCharset );
        
    }

}

1、常用字符集

  • GBK : 简体中文,全称《汉字内码扩展规范》(GBK即“国标”、“扩展”汉语拼音的第一个字母,英文名称:Chinese Internal Code Specification)
  • UTF-8 : 可以表示 西欧诸国字符 、东亚诸国字符 (但是不是全部) 8位元,Universal Character Set/Unicode Transformation Format)是针对 Unicode 的一种可变长度字符编码
  • ISO-8859-1 : 编码是单字节编码,向下兼容ASCII,其编码范围是0x00-0xFF,0x00-0x7F之间完全和ASCII一致,0x80-0x9F之间是控制字符,0xA0-0xFF之间是文字符号
  • Latin1 : Latin1 是ISO-8859-1的别名,有些环境下写作 Latin-1
  • Big 5 : 繁体中文(正体中文)

2、通过 Charset.forName( name ) 方法可以获得指定名称的字符集对应的 Charset 实例

3、根据指定的 字符集 将 字符串 编码为 字节序列

4、同一个字符串(尤其是东亚诸国字符)通过不同的字符集编码后的字节序列是不相同的

package xyz.wenkang.CharSequence.String;

import java.nio.charset.Charset;
import java.util.Arrays;

public class StringTest7 {

    public static void main(String[] args) {
        
        // 指定为 Charset.forName( name ) 指定的参数(一个字符编码的名称)是不被JVM支持的
        // 则会抛出 ava.nio.charset.UnsupportedCharsetException
        Charset first = Charset.forName("GBK");
        System.out.println( first + first.getClass().getName() ); // GBKsun.nio.cs.GBK
        
        Charset second = Charset.forName("UTF-8");
        System.out.println( second + second.getClass().getName() ); // UTF-8sun.nio.cs.UTF_8
        
        Charset third = Charset.forName("Big5");
        System.out.println( third + third.getClass().getName() ); // Big5sun.nio.cs.ext.Big5
        
        Charset fourth = Charset.forName("ISO-8859-1");
        System.out.println( fourth + fourth.getClass().getName() ); // ISO-8859-1sun.nio.cs.ISO_8859_1
        
        Charset fifth = Charset.forName("Latin1");
        System.out.println( fifth + fifth.getClass().getName() ); // ISO-8859-1sun.nio.cs.ISO_8859_1
        
        System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
        
        String str = "中国" ;
        
        // 使用 平台默认的字符集 将此 String 编码 为 byte 序列,并将结果存储在一个新的 byte 数组
        byte[] defaultBytes = str.getBytes();
        System.out.println( "默认的:" + Arrays.toString(defaultBytes)); // 默认的:[-28, -72, -83, -27, -101, -67]
        
        // 使用给定的 charset 实例 将此 String 编码 为 byte 序列,并将结果存储到新的 byte 数组
        byte[] firstBytes = str.getBytes( first );  // GBK
        System.out.println( first + " : " + Arrays.toString( firstBytes ) ); // GBK : [-42, -48, -71, -6]
        
        byte[] secondBytes = str.getBytes( second ); // UTF-8
        System.out.println( second + " : " + Arrays.toString( secondBytes ) ); // UTF-8 : [-28, -72, -83, -27, -101, -67]
        
        byte[] thirdBytes = str.getBytes( third ); // Big5
        System.out.println( third + " : " + Arrays.toString( thirdBytes ) ); // Big5 : [-92, -92, 63]
        
        byte[] fourthBytes = str.getBytes( fourth ); // ISO-8859-1
        System.out.println( fourth + " : " + Arrays.toString( fourthBytes ) ); // ISO-8859-1 : [63, 63]
        
        byte[] fifthBytes = str.getBytes( fifth );
        System.out.println( fifth + ":" + Arrays.toString( fifthBytes ) ); // ISO-8859-1:[63, 63]
        
        
    }
    
}

举一个例子,同一个字符串 通过 不同的字符集 编码后的字节序列 是不相同的

package xyz.wenkang.CharSequence.String;

import java.nio.charset.Charset;
import java.util.Arrays;



public class StringTest8 {

    public static void main(String[] args) {
        
        final String str1 = "我来抓人了" ;
        System.out.println( str1 ); // 输出字符正常:我来抓人了
        
        Charset first = Charset.forName("GBK"); // 每个汉字占两个字节
        Charset second = Charset.forName("UTF-8"); 
        
        byte[] firstBytes = str1.getBytes(first); // 使用 GBK 字符集 将 字符串 编码 为 字节序列
        System.out.println( Arrays.toString( firstBytes ) ); // [-50, -46, -64, -76, -41, -91, -56, -53, -63, -53]
        
        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );
        
        String str2 = new String( firstBytes , second ); // 使用 指定的字符集 将 字节序列 解码 为 字符串
        System.out.println( str2 ); // 输出乱码:����ץ����
        
        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );
        
        String str3 = new String( firstBytes , first ); // 使用 指定的字符集 将 字节序列 解码 为 字符串
        System.out.println( str3 ); // 输出正常:我来抓人了

    }

}

String实例内部的byte数组中存储是字符串按照UTF-16LE编码后的字节序列

1、在 String 实例内部封装的 byte 数组中存储 是 字符串 按照 UTF-16LE字符集 编码后的字节序列

2、在通过 String 实例 的 getBytes 方法获取字节序列时,可以指定 任意字符集

3、将一组字节序列构成为 String 实例时,必须指定 将字符串编码为字节序列时 所采用的 字符集 才能保证不乱码

package xyz.wenkang.CharSequence.String;

import java.nio.charset.Charset;
import java.util.Arrays;

public class StringTest9 {
    
    public static void main(String[] args) {
        
        final String str = "发起进攻" ;
        
        StringHelper.perspective(str);
//      字符串实例内容: 发起进攻
//      字符串实例中字符个数: 4
//      字符串实例内部字节数组长度: 8
//      字符串实例内部字节数组"地址": 1689843956
//      字符串实例内部字节数组: -47 , 83 , 119 , -115 , -37 , -113 , 59 , 101
//      字符串实例的 coder 变量: 1
//      字符串实例的 hash 变量: 0
        
        System.out.println("~~~~~~~~~~~~~~~~~~~~~~");
        
        Charset utf = Charset.forName( "UTF-16LE" ); // 16-bit 
        
        byte[] bytes = str.getBytes( utf ) ; // 使用 UTF-16LE 字符集 将 字符串中的字符 编码为 字节序列
        System.out.println( Arrays.toString( bytes ) ); // [-47, 83, 119, -115, -37, -113, 59, 101]
        
        byte[] array = { -47 , 83 , 119 , -115 , -37 , -113 , 59 , 101 } ;
        String str2 = new String( array , utf ) ; // 使用 UTF-16LE 字符集 将 字节序列 解码为 字符串中的字符
        System.out.println( str2 ); // 发起进攻
        
    }

}

String类中常用实例方法:length/charAt/ontains/indexOf/lastIndexOf

1、int length() 获取字符串长度 ( 字符串中所包含的字符个数 )

2、char charAt( int ) 获取指定索引处的单个字符

3、boolean contains( CharSequence ) 判断指定的 字符序列 是否存在于 当前字符串中,若存在即返回 true 否则返回 false

4、int indexOf( int ch ) 查询 指定字符 在 当前字符串中首次出现的位置,若存在即返回该索引,否则返回 -1

5、int indexOf( String s ) 查询 指定字符串 在 当前字符串中首次出现的位置,若存在即返回该索引,否则返回 -1

6、int lastIndexOf( int ch ) 查询 指定字符 在 当前字符串中最后一次出现的位置,若存在即返回该索引,否则返回 -1

7、int lastIndexOf( String s ) 查询 指定字符串 在 当前字符串中最后一次出现的位置,若存在即返回该索引,否则返回 -1

package xyz.wenkang.CharSequence.String;

public class StringTestA {

    public static void main(String[] args) {
        
        final String str1 = "干得漂亮" ;
        
        // 输出: 干 , 得 , 漂 , 亮
        for( int i = 0 , n = str1.length() ; i < n ; i++ ) { // 【 int length() 】
            char ch = str1.charAt( i ); // 【 char charAt( int ) 】
            System.out.print( ch );
            System.out.print( i < n - 1 ? " , " : "\n" );
        }
        
        // 因为 String 类实现了 CharSequence 接口
        CharSequence cs = "漂亮" ; // 所以 用 CharSequence 类型的引用变量 指向 String 实例是可以的
        
        boolean z = str1.contains( cs ) ; // 【 boolean contains( CharSequence ) 】
        System.out.println( z ); // true
        
        // 整个字符串中的每个字符的索引 一律 从 "左" 开始统计 ( 与数组相同 )
        int index = str1.indexOf( '干' ) ; // 获取 '干' 在变量 str1 所指向的字符串中首次出现的位置( 索引 )
        System.out.println( index ); // 0
        
        index = str1.indexOf( "的" ) ; // 获取 "的" 在变量 str1 所指向的字符串中首次出现的位置( 索引 )
        System.out.println( index ); // -1
        
        System.out.println( str1.indexOf( '啊' ) ); // 若不存在即返回 -1 
        
        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );
        
        int last = str1.lastIndexOf( '亮' ); //  获取 '亮' 在变量 str1 所指向的字符串中最后一次出现的位置( 索引 )
        System.out.println( last ); // 3
        
        last = str1.lastIndexOf( "漂亮" ); // 获取 "漂亮" 在变量 str1 所指向的字符串中最后一次出现的位置( 索引 )
        System.out.println( last ); // 2
        
        System.out.println( str1.indexOf( "下雨" ) ); // 若不存在即返回 -1 

    }

}

String类中常用实例方法:indexOf/lastIndexOf

1、 int indexOf( int ch , int from )

从 当前字符串中 指定位置(from) 开始寻找 指定字符 ( ch ) 首次出现的位置,若存在即返回该索引,否则返回 -1

2、int indexOf( String s , int from )

从 当前字符串中 指定位置(from) 开始寻找 指定字符串 ( s ) 首次出现的位置,若存在即返回该索引,否则返回 -1

3、int lastIndexOf( int ch , int from )

从 当前字符串中 指定位置(from) 开始反向寻找 指定字符 ( ch ) 最后一次出现的位置,若存在即返回该索引,否则返回 -1 ,即获取在 from 处及其之前 ch 最后一次出现的位置是什么( 反向寻找的顺序是 from 、from - 1 、from - 2 、........ )

4、int lastIndexOf( String s , int from )

从 当前字符串中 指定位置(from) 开始反向寻找 指定字符串 ( s ) 最后一次出现的位置,若存在即返回该索引,否则返回 -1 ,即获取在 from 处及其之前 s 最后一次出现的位置是什么

package xyz.wenkang.CharSequence.String;

public class StringTestB {

    public static void main(String[] args) {
        
        final String str = "别团我单带" ;
        int index ;
        int last ;
        
        index = str.indexOf(0);
        last = str.lastIndexOf(3);
        System.out.println( index + ","+ last ); // -1,-1
        
        System.out.println("~~~~~~~~~~~~~~~~~~~~~~~");
        
        index = str.indexOf( '单' , 1 ); // 从 索引 1 开始 向后寻找 '/' 字符首次出现的位置
        last = str.lastIndexOf( '我' , 4 ); // 从 索引 4 处开始 反向寻找 '/' 字符 最后一次出现的位置
        System.out.println( index + " , " + last ); // 3 , 2

    }

}

String类中常用实例方法:concat/toUpperCase/toLowerCase/equals/equalsIgnoreCase/…

1、String concat( String )

将当前 字符串 与 参数给定的字符串 拼接到一个新的字符串中并返回新字符串实例

2、String toUpperCase()

将当前 字符串 中所有的小写英文字符转换为大写后返回新的字符串实例

3、String toLowerCase()

将当前 字符串 中所有的大写英文字符转换为小写后返回新的字符串实例

4、boolean equals( Object )

String 类重写了 equals 方法用于比较 两个 String 实例中所包含的字符串是否相等

5、boolean equalsIgnoreCase( String )

比较两个 String 实例中所包含的字符串是否相等 ( 忽略英文字母大小写 )

6、boolean contentEquals( CharSequence )

用于比较参数指定的 字符序列 是否与 当前 String 实例所包含的 字符串内容 相等

7、int compareTo( String )

用于比较两个字符串的 "大小" ( 当两个字符串相等时返回 零 ( 英文字母区分大小写 ) )

8、int compareToIgnoreCase( String )

用于比较两个字符串的 "大小" ( 当两个字符串相等时返回 零 ( 英文字母不区分大小写 ) )

package xyz.wenkang.CharSequence.String;

public class StringTestC {

    public static void main(String[] args) {
        
        String s = "abc" ;
        
        String x = s.concat( "123" );
        System.out.println( s ); // "abc"
        System.out.println( x ); // "abc123"
        
        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );    
        
        String upper = x.toUpperCase(); 
        System.out.println( upper ); // "ABC123"
        
        String lower = upper.toLowerCase() ;
        System.out.println( lower ); // "abc123"
        
        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );    
        
        final String a = "hello" ;
        final String b = "Hello" ;
        
        System.out.println( a.equals( b ) ); // 比较 字符串 内容时 英文字母区分大小写
        System.out.println( a.equalsIgnoreCase( b ) ) ; // 忽略英文字母区分大小写
        System.out.println( a.contentEquals( b ) ); // 区分大小写
        
        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );    
        
        // compareTo 是一个比较方法,用于比较两个字符串的 "大小"
        System.out.println( a.compareTo( b ) ); // 考虑大小写
        // 当 两个 String 实例中所包含的 字符串内容 相同时 返回 零 ,否则返回 非零 的整数
        System.out.println( a.compareToIgnoreCase( b ) ); // 不考虑大小写

    }

}

String类中常用实例方法:length/isEmpty/isBlank/trim

1、int length()

2、boolean isEmpty()

用于判断 当前 String 实例 所包含 字符串 是否为 空 ( 即 value.length 为 零 )

3、boolean isBlank() 【 JDK 11 新增的方法 】

用于 当前 String 实例 所包含 字符串 是否为 空白 ( 仅包含了 空格 、制表符 等字符 )

4、String trim()

剔除 当前 String 实例 所包含 字符串 中的首尾空白后返回新的 String 实例

package xyz.wenkang.CharSequence.String;

public class StringTestD {

    public static void main(String[] args) {
        
        String str1 = null ;
        System.out.println(str1); // null
        
        String str2 = "" ; // 空串 不是 一个空格 或 一个制表符(Tab) ,也不是 \u0000  或 null
        System.out.println(str2); // 
        System.out.println( "length: " + str2.length() + ", empty: " + str2.isEmpty() ); // length: 0, empty: true
        
        System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~");
        
        String str3 = "  " ; // 有 空格 、 有 Tab
        System.out.println( "length : " + str3.length() + " , is empty : " + str3.isEmpty() + " , is blank : " + str3.isBlank() ); // length : 2 , is empty : false , is blank : true
        
        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );    
        
        String str4 = "     hello ecuter.    ";
        System.out.println( str4.length() ); // 含空白字符 【输出:22】
        String str5 = str4.trim() ; // 剔除首尾空白后返回新的 String 实例
        System.out.println( str5.length() ); // 不含空白字符【输出:13】
        System.out.println( str5 ); // 不剔除中间空格【输出:hello ecuter.】

    }

}

String类中常用实例方法:startsWith/endsWith/getChars/substring/subSequence

1、boolean startsWith( String )

判断 当前 String 实例 中所包含的字符串 是否以指定的 字符串 为【前缀】

2、boolean endsWith( String )

判断 当前 String 实例 中所包含的字符串 是否以指定的 字符串 为【后缀】

3、boolean startsWith( String s , int from )

判断 当前 字符串 从指定位置开始 是否以指定的 字符串 为前缀

4、void getChars( int begin , int end , char[] array , int start )

将 当前 String 实例 中所包含的字符串 中 [ begin , end ) 之间的字符 拷贝到 array 数组的 start 位置

5、String substring( int begin )

从当前字符串中的 begin 位置处开到字符串末尾截取并返回新字符串

6、String substring( int begin , int end )

从当前字符串中截取[ begin , end ) 之间的内容并返回新字符串

7、CharSequence subSequence( int begin , int end )

从当前字符串中截取[ begin , end ) 之间的内容并返回 CharSequence 实例

package xyz.wenkang.CharSequence.String;

import java.util.Arrays;

public class StringTestE {

    public static void main(String[] args) {
        
        final String path = "D:/java-beginner/char-sequence/StringHelper.java" ;
        
        System.out.println( path.startsWith( "D:" ) ); // true
        System.out.println( path.startsWith( "C:" ) );  // false
        
        System.out.println( path.endsWith( ".java" ) ); // true
        System.out.println( path.endsWith( ".class" ) );  // false
        
        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );    
        
        // 判断 从索引 17 处开始 是否是 已 "char" 为前缀
        System.out.println( path.startsWith( "char" , 17 ) ); // true
        
        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );    
        
        char[] array = new char[ 10 ];
        System.out.println( Arrays.toString( array ) ); // [, , , , , , , , ,]
        String str = "沧海一声笑滔滔两岸潮" ;
        
        // 将 str 所指向的 String 实例中所包含的 字符串 中 [ 5 , 10 ) 之间的字符 拷贝到 array 数组中
        // 并在 array 数组的 索引 0 处 开始存放拷贝的字符
        str.getChars( 5 , 10, array , 0 );
        System.out.println( Arrays.toString( array ) ); // [滔, 滔, 两, 岸, 潮, , , , , ] 
        
        str.getChars( 0 , 5, array , 5 );
        System.out.println( Arrays.toString( array ) ); // [滔, 滔, 两, 岸, 潮, 沧, 海, 一, 声, 笑]
        
        String u = str.substring( 5 ); // [ 5 , length() - 1 ]
        System.out.println( u ); // 滔滔两岸潮
        
        String v = str.substring( 3 , 7 ) ; // [ 3 , 7 )
        System.out.println( v ); // 声笑滔滔
        
        CharSequence cs = str.subSequence( 2 ,  8 ) ; // [ 2 , 8 )
        System.out.println( cs ); // 一声笑滔滔两
        System.out.println( cs.getClass() ); // class java.lang.String

    }

}

getChars/toCharArray/使用+运算符连接字符串

1、测试 getChars( int , int , char[] , int ) 和 toCharArray()

2、理解使用 + 运算符 直接 连接 两个 字符串 字面量 所执行的操作

3、理解使用 + 运算符 连接 一个 字符串 字面量 和 另一个 String 类型的引用变量 所执行的操作(向 面试官 回答时,主要讲 Java 8 中的实现过程)

4、在 Java 11 中所完成的操作暂时仅做了解

package xyz.wenkang.CharSequence.String;

import java.util.Arrays;

public class StringTestF {

    public static void main(String[] args) {
        
        char[] chars = new char[10] ;
        final String str1  = "黄沙百战穿金甲,不破楼兰终不还" ;
        str1.getChars( 8 , 15,  chars , 0 ); 
        System.out.println( Arrays.toString(chars) ); // [不, 破, 楼, 兰, 终, 不, 还, , , ]
        
        char[] array = str1.toCharArray() ;
        System.out.println( Arrays.toString( array ) ); // [黄, 沙, 百, 战, 穿, 金, 甲, ,, 不, 破, 楼, 兰, 终, 不, 还]
        
        System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~");
        
        String str2 = "集合" ; // 1 character strings ( String constant pool )
        String str3 = "反蓝" ; // 1 character strings ( String constant pool )
        String str4 = "集合反蓝" ; // 1 character strings ( String constant pool )
        
        // 当使用 + 连接 两个 字符串 直接量时,在 String 常量池中完成 字符串 的 连接操作,
        // 如果 新字符串 在 String常量池 中不存在,则首先将 新字符串 添加到 String 常量池 中并返回该串的地址
        // 如果 新字符串 在 String常量池 中已经存在 则直接返回已经存在的 字符串 的地址 ( 不再添加新拼接的字符串 )
        String str5 = "集合" + "反蓝" ;  // 至少在 JDK 11 中已经是在编译阶段就将两个字符串合并了
        System.out.println( str4 == str5 ); // true
        System.out.println( System.identityHashCode(str4) ); // 1509514333
        System.out.println( System.identityHashCode(str5) ); // 1509514333
        
        // 以下 三步 是 Java 8 中的做法
        // 1、StringBuilder sb = new StringBuilder( a ) ; // 创建一个字符缓冲区,并在其中存放  '集' , '合' 
        // 2、sb.append( "反蓝" ) ; // 在 字符缓冲区 中 追加 '穿' , '金' , '甲' ,此时在 字符缓冲区中存在 7 个字符
        // 3、sb.toString() ; // 在 toString 方法内部创建并返回一个新的 String 实例,其中封装了 "几何反蓝"
        String str6 = str2 + "反蓝" ;
        System.out.println( str4 == str6 ); // false
        
        String str7 = "撤退" + str3 ; 
        System.out.println( str7 == str4 ); // false 
        System.out.println( System.identityHashCode(str4) ); // 1509514333
        System.out.println( System.identityHashCode(str7) ); // 1556956098
        
    }

}

String类中常用实例方法:intern

当通过 某个String实例 来调用其 intern 方法时,如果 String常量池 中已经包含一个 与该String实例 所包含内容相等 ( 用 equals(Object)确定 ) 的 String实例,就直接返回在 String常量池 中已经存在的 String实例 ( 不再将当前的 String实例 添加到 String常量池 ) 。否则,将 此 String实例 添加到 String常量池 中,并返回 此String实例 的引用 ( 即返回 String常量池中新增的 String实例 的地址)

package xyz.wenkang.CharSequence.String;

/**
 * 1、测试 intern 方法
 * 2、String常量池 中存放的全部是 String实例
 */

public class StringTestG {

    public static void main(String[] args) {
        
        String str1 = "撤退" ;
        String str2 = "别打主宰" ;
        String str3 = "撤退别打主宰" ;
        String str4 = str1 + str2 ;
        System.out.println( str3 == str4 ); // false
        
        System.out.println( "~~~~~~~~~~~~~~~~~~~~~~~~" );
        
        String str5 = str4.intern() ; 
        System.out.println( str3 == str5 ); // true
        
        System.out.println("~~~~~~~~~~~~~~~~~~~");
        
        String str6 = new String("撤退别打暴君") ;
        String str7 = new String("撤退别打暴君") ;  
        System.out.println( str6 == str7 ); // false
        System.out.println( str6.equals(str7) ); // true
        
        String str8 = str1.intern() ;
        String str9 = str1.intern() ;
        System.out.println( str8 == str9 ); // true 
        
    }

}

使用CharacterStrings实现CharSequence

package xyz.wenkang.CharSequence.String;

public final class CharacterStrings extends Object implements CharSequence {
    
    private final  char[] value ;
    private final int size ;
    
    public CharacterStrings( String s ) {
        if( s == null ) {
            this.value = null ;
            this.size = -1 ;
        } else {
            this.size = s.length() ;
            this.value = new char[ this.size ];
            s.getChars( 0 ,  size , value , 0 );
        }
    }

    @Override
    public int length() {
        return size ;
    }

    @Override
    public char charAt(int index) {
        if( index>=0 && index<=size ) {
            return value[ index ] ;
        }
        return 0 ;
    }

    @Override
    public CharSequence subSequence(int start, int end) {
        if( start >= 0 && end <= size && start < end ) {
            String s = new String( value , start , end );
            CharacterStrings cs = new CharacterStrings( s );
            return cs ;
        }
        return null;
    }
    
    @Override
    public String toString() {
        return new String( this.value );
    }
    
    public static void main(String[] args) {
        
        CharacterStrings s = new CharacterStrings( "hello,world" );
        System.out.println( s.length() ); // 11
        System.out.println( s ); // hello,world
        
        CharSequence c = s.subSequence( 0 , 5 ); 
        System.out.println( c.length() ); // 5
        System.out.println( c ); // hello
        
    }
    
}

理解 String 类的 compareTo 是怎么比较字符串内容的

package xyz.wenkang.String;

import java.util.Arrays;

/**
 * 理解 String 类的 compareTo 在比较什么
 * 1、纯 英文 时,直接比较 String 实例内部的 字节数组 [ 大致思路 ]
 * 2、非 纯英文 时,需要比较 String 实例 中的 字符  [ 大致思路 ]
 */

public class StringSort {
    
    public static void main(String[] args) {
        
        String[] numbers = {  "九" , "一" , "八" , "四" , "七" , "三" , "六" , "二" , "五" } ;
        System.out.println( Arrays.toString(numbers) ); // [九, 一, 八, 四, 七, 三, 六, 二, 五]
        
        System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~");
        
        String str = String.join( "" , numbers ) ;
        char[] array = new char[ str.length() ] ;
        str.getChars( 0, str.length() , array , 0 );
        System.out.println( Arrays.toString(array) ); // [九, 一, 八, 四, 七, 三, 六, 二, 五]
        
        Arrays.sort( array ) ;
        System.out.println( Arrays.toString(array) ); // [一, 七, 三, 九, 二, 五, 八, 六, 四]
        
        for (int i = 0; i < array.length; i++) {
            System.out.println( array[i] + 0 );
            System.out.println( i < array.length-1 ? "," : "\n");
            /*
             * 19968 , 19971 , 19977 , 20061 , 20108 , 20116 , 20843 , 20845 , 22235
             */
        }
        
        System.out.println("~~~~~~~~~~~~~~~~~~~~~~~");
        
        Arrays.sort( numbers );
        System.out.println( Arrays.toString( numbers ) ); // [一, 七, 三, 九, 二, 五, 八, 六, 四]
        
        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );
        
        String[] sa = { "hello" , "abd" , "abc" , "abcd" , "helloword" };
        System.out.println( Arrays.toString( sa ) ); // [hello, abd, abc, abcd, helloword]
        Arrays.sort( sa );
        System.out.println( Arrays.toString( sa ) ); // [abc, abcd, abd, hello, helloword]
        
    }

}

14.3.2、java.lang.AbstractStringBuilder

14.3.2.1、java.lang.StringBuffer

理解StringBuffer实例的可变性、测试常用的方法:append / insert / replace / deleteCharAt / delete

1、字符串 ( character strings ) 缓冲区 ( buffer ) : string buffer 根据 Java 中类的命名规范命名为 StringBuffer

2、与 String 类的实例一样,StringBuffer 类的实例 也用于 封装 字符串 ( character strings )

3、与 String 类的实例 不可变 不同,StringBuffer 类的实例 是 可变的,因此每次操作 都会影响 StringBuffer 实例

4、StringBuffer 类中定义了大量的用来操作 字符串缓冲区 的方法: append / insert / replace / deleteCharAt / delete

package xyz.wenkang.StringBuffer;

public class StringBufferTest1 {
    
    public static void main(String[] args) {
        
        // 使用 StringBuffer 无参数构造创建一个 空的 字符串缓冲区
        StringBuffer buffer = new StringBuffer() ;
        System.out.println( "length : " + buffer.length() + " , capacity : " + buffer.capacity() + " , toString : " + buffer ); // length : 0 , capacity : 16 , toString : 

        buffer.append(false); // 向字符缓冲区中追加内容
        System.out.println( "length : " + buffer.length() + " , capacity : " + buffer.capacity() + " , toString : " + buffer ); // length : 5 , capacity : 16 , toString : false
        
buffer.insert( 0 , "hello," );
        
        System.out.println( "length : " + buffer.length() + " , capacity : " + buffer.capacity() + " , toString : " + buffer ); // length : 11 , capacity : 16 , toString : hello,false
        
        buffer.replace( 5 , 6 , "," );
        
        System.out.println( "length : " + buffer.length() + " , capacity : " + buffer.capacity() + " , toString : " + buffer ); // length : 11 , capacity : 16 , toString : hello,false
        
        int index = buffer.indexOf( "," );
        
        buffer.deleteCharAt( index );
        
        System.out.println( "length : " + buffer.length() + " , capacity : " + buffer.capacity() + " , toString : " + buffer ); // length : 10 , capacity : 16 , toString : hellofalse
        
        buffer.delete( 5 , 10 );
        
        System.out.println( "length : " + buffer.length() + " , capacity : " + buffer.capacity() + " , toString : " + buffer ); // length : 5 , capacity : 16 , toString : hello

    }
}

了解StringBuffer内部的一些方法是返回当前的StringBuffer对象的

package xyz.wenkang.StringBuffer;

public class StringBufferTest2 {

    public static void main(String[] args) {
        
        // 使用 StringBuffer 有参数构造创建一个 包含了指定字符串 str 中所有字符 的 字符串缓冲区
        StringBuffer buffer = new StringBuffer( "hello" ); // capacity  : super( str.length() + 16 );
        System.out.println( "length : " + buffer.length() + " , capacity : " + buffer.capacity() + " , toString : " + buffer ); // length : 5 , capacity : 21 , toString : hello
        
        buffer.append( true ).append( 100 ).append( 'A' ).append( "你好" ).append( 3.14 );
        
        System.out.println( "length : " + buffer.length() + " , capacity : " + buffer.capacity() + " , toString : " + buffer ); // length : 19 , capacity : 21 , toString : hellotrue100A你好3.14
        
        buffer.insert( 5 , "X" ).insert( 10 ,  'Y' );
        
        System.out.println( "length : " + buffer.length() + " , capacity : " + buffer.capacity() + " , toString : " + buffer ); // length : 21 , capacity : 21 , toString : helloXtrueY100A你好3.14
        
        buffer.reverse(); // 翻转
        
        System.out.println( "length : " + buffer.length() + " , capacity : " + buffer.capacity() + " , toString : " + buffer ); // length : 21 , capacity : 21 , toString : 41.3好你A001YeurtXolleh
        
        buffer.reverse();

        System.out.println( "length : " + buffer.length() + " , capacity : " + buffer.capacity() + " , toString : " + buffer ); // length : 21 , capacity : 21 , toString : helloXtrueY100A你好3.14
        
        buffer.append( "world" );
        
        System.out.println( "length : " + buffer.length() + " , capacity : " + buffer.capacity() + " , toString : " + buffer ); // length : 26 , capacity : 44 , toString : helloXtrueY100A你好3.14world

    }

}
14.3.2.2、java.lang.StringBuilder

1、StringBuilder 类的设计 与 StringBuffer 基本一致 ( 父类 、实现接口 、构造 、方法 )

2、StringBuilder 类中的 方法都没有 synchronized 修饰的,而 StringBuffer 中所有 public 方法都是有 synchronized 修饰的

通过StringBuilder实例测试setLength和trimToSize

不论是 StringBuilder 还是 StringBuffer ,都可以使用 setLength 方法有效字符数 、通过 trimToSize 来调整容量

package xyz.wenkang.StringBuilder;

public class StringBuilderTest1 {
    
    public static void main(String[] args) {
        
        StringBuilder builder = new StringBuilder("hello");
        System.out.println( "length : " + builder.length() + " , capacity : " + builder.capacity() + " , toString : " + builder ); // length : 5 , capacity : 21 , toString : hello
        
        builder.setLength( 0 ); // 设置 length 为 零 ( 有效字符数 为 零 )
        System.out.println( "length : " + builder.length() + " , capacity : " + builder.capacity() + " , toString : " + builder ); // length : 0 , capacity : 21 , toString : 
        
        builder.append( true );
        System.out.println( "length : " + builder.length() + " , capacity : " + builder.capacity() + " , toString : " + builder ); // length : 4 , capacity : 21 , toString : true
        
        builder.trimToSize();
        System.out.println( "length : " + builder.length() + " , capacity : " + builder.capacity() + " , toString : " + builder ); // length : 4 , capacity : 4 , toString : true
        
    }
    
}

14、时间和日期

14.1、java.tuil.Date

  1. 字段:private trainsient long fastTime;利用fastTime字段来存储毫秒值
  2. 构造:public Date() 和public Date(lomg date)
  3. 方法:
    • public boolean equals( Object o )
    • public boolean before( Date when )
    • public boolean after( Date when )
  4. 缺点:
    • Date类不是最终类
    • Date类的实例时可变的
    • 不是线程安全的
    • 设计不友好;比如:无法快速获得指定的年月日对应的Date实例

14.1.1、时间与日期的获取

  • 类 Date 表示特定的 瞬间 ,精确到毫秒 。

  • Date 类的实例内部封装了一个 毫秒值 ( 从 历元 至 某一瞬间 所经历的 毫秒值 )。

  • 历元( Epoch ) : 1970 年 1 月 1 日 00:00:00 GMT ( GMT : 格林尼治标准时间 )。

  • CST : China Standard Time , 中国标准时间。

  • 获得系统的时间:变量类型 变量名 = System.currentTimeMillis() ;

    // 使用 Date 类的 无参构造 创建 Date 实例
        Date now = new Date(); 
    System.out.println( now );//获取运行该代码时的时间
    long millis = System.currentTimeMillis() ; // 获取 从 历元 至 现在所经历的 毫秒数 ( 毫秒值 )
    System.out.println( millis );
    // 使用 Date 类带有一个 long 类型参数的构造 创建 Date 实例
    Date date = new Date( millis );
    System.out.println( date ); // date.toString()
    //System.out.println(date.toString());
    long ms = date.getTime(); // 获取在 Date 实例内部封装的 毫秒值
    System.out.println( ms );
    date.setTime( 1000000L );
    System.out.println( date );
    long t = date.getTime(); // 获取在 Date 实例内部封装的 毫秒值
    System.out.println( t );
    Mon Jun 01 19:53:00 CST 2020
    1591012380923
    Mon Jun 01 19:53:00 CST 2020//Date date = new Date( millis );
    1591012380923
    Thu Jan 01 08:16:40 CST 1970
    1000000

14.1.2、两个date实例的比较

  • 比较两个 Date 实例是否相等: public boolean equals( Object o )

  • 实际上在Date重写后的equals方法内部比较的是两个Date实例中保存的毫秒值

    long millis = System.currentTimeMillis(); // 该行代码执行时对应的毫秒值
        Date now = new Date( millis );
    System.out.println( now );
    long day = 1000L * 60 * 60 *24 ; // 统计 24 小时对应的毫秒值
    Date first = new Date( millis - day );//一天前系统时间
    System.out.println( first );
    Date second = new Date( millis );//当前系统时间
    System.out.println( second );
    Date third = new Date( millis + day );//一天后系统时间
    System.out.println( third );
    System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );
    System.out.println( first.equals( now ) ); // false
    System.out.println( second.equals( now ) ); // true
    System.out.println( third.equals( now ) ); // false
  • 判断 当前Date实例 所表示瞬间 是否 早于 另一个Date实例所表示的瞬间: boolean before( Date when )

  • 当当前Date实例早于参数指定的Date实例时返回true

          System.out.println( first.before( now ) ); // true
        System.out.println( second.before( now ) ); // false
    System.out.println( third.before( now ) ); // false
  • 判断 当前Date实例 所表示瞬间 是否 晚于 另一个Date实例所表示的瞬间: boolean after( Date when )

  • 当当前Date实例晚于参数指定的Date实例时返回true

        System.out.println( first.after( now ) ); // false
        System.out.println( second.after( now ) ); // false
    System.out.println( third.after( now ) ); // true
    System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );
    // 以下比较方法就不赞成使用了
    System.out.println( first.getTime() < now.getTime() );
    System.out.println( second.getTime() < now.getTime() );
    System.out.println( third.getTime() < now.getTime() );
  • "闰秒" : 对于 UTC,大约每一两年出现一次额外的一秒,称为 "闰秒"

14.2、java.util.Calendar

  • java.util.Calendar 类是个抽象类
  • java.util.GregorianCalendar 类是 Calendar 的子类
  • Calendar 为特定瞬间与一组日历字段之间的转换提供了一些方法,并为操作日历字段提供了一些方法。
  • 日历字段 ( calendar field ) 就是表示 特定瞬间 中某个部分的 字段 ,比如:
    • Calendar.YEAR 表示年
    • Calendar.MONTH 表示月(在java中月 是从0开始,因此需要加1)
    • Calendar.DATE 表示天
    • Calendar.HOUR 表示小时 ( 12 小时进制 ,分上午和下午 )
    • Calendar.HOUR_OF_DAY 表示小时 ( 24 小时进制 )
    • Calendar.MINUTE 表示分钟
    • Calendar.SECOND 表示秒
    • Calendar.MILLISECOND 表示毫秒
  • 设置 Calendar 实例所表示的 瞬间 :
    • public void set( int field , int value ) 为 指定的日历字段 设置 指定的值,比如 c.set( Calendar.YEAR , 1999 )
    • public final void set( int year , int month , int date )
    • public final void set( int year , int month , int date , int hourOfDay , int minute )
    • public final void set( int year , int month , int date , int hourOfDay , int minute , int second )
  • 通过 Calendar 实例 调用 get( int) 方法可以获得指定 日历字段 的值
  • 缺点:
    • Calendar类的实例是可变的
    • 不是线程安全的
    • 通过日历字段来设置Calendar实例所表示的瞬间比较繁琐
    • 相对于Date类来说设计上较好
// 父类类型的 引用变量 指向了 子类类型的对象
        Calendar calendar = new GregorianCalendar(); // 默认地区 、默认时区
        System.out.println( calendar );
        
        StringBuilder builder = new StringBuilder();
    
        
        int year = calendar.get( Calendar.YEAR );
        builder.append( year + "年" );
        
        // 鬼子那边的月份是从 零 开始计数的
        int month = calendar.get( Calendar.MONTH ) + 1 ;
        builder.append( month + "月" );
        
        int date = calendar.get( Calendar.DATE );
        builder.append( date + "日" );
        
        int hours = calendar.get( Calendar.HOUR_OF_DAY );
        builder.append( hours + ":" );
        
        int minutes = calendar.get( Calendar.MINUTE );
        builder.append( minutes + ":" );
        
        int seconds = calendar.get( Calendar.SECOND );
        builder.append( seconds + "." );
        
        int millis = calendar.get( Calendar.MILLISECOND );
        builder.append( millis );
        
        String s = builder.toString();
        System.out.println( s );
    }
java.util.GregorianCalendar[time=1591016233426,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=19,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2020,MONTH=5,WEEK_OF_YEAR=23,WEEK_OF_MONTH=1,DAY_OF_MONTH=1,DAY_OF_YEAR=153,DAY_OF_WEEK=2,DAY_OF_WEEK_IN_MONTH=1,AM_PM=1,HOUR=8,HOUR_OF_DAY=20,MINUTE=57,SECOND=13,MILLISECOND=426,ZONE_OFFSET=28800000,DST_OFFSET=0]
2020年6月1日20:57:13.426

14.2.1、静态工厂方法来获取 Calendar 类的实例

  • 这里的 静态工厂方法 就是 Calendar 类中的 返回 Calendar实例 的 类方法

    public class CalendarTest2 {
    public static void main(String[] args) {
    String s ;
    // 父类类型的 引用变量 指向了 子类类型的对象
    Calendar calendar = Calendar.getInstance(); // 默认地区 、 默认时区
    System.out.println( calendar );
    System.out.println( calendar.getClass() ); // 运行时类型
    // 在 同一个类中使用 本类声明的 类方法时可以省略类名
    s = toString( calendar ) ; // s = CalendarTest2.toString( calendar ) ;
    System.out.println( s );
    /*
    calendar.set( Calendar.YEAR , 1999 );
    calendar.set( Calendar.MONTH , 5 ); // 注意: 鬼子那边月份从零开始计数
    calendar.set( Calendar.DATE , 10 );
    calendar.set( Calendar.HOUR_OF_DAY , 7 );
    calendar.set( Calendar.MINUTE , 50 );
    calendar.set( Calendar.SECOND , 0);
    calendar.set( Calendar.MILLISECOND , 0 );
    */
    calendar.set( 1999 , 5 , 10, 7, 50 , 0 );
    calendar.set( Calendar.MILLISECOND , 0 );
    System.out.println( calendar );
    s = toString( calendar ) ;
    System.out.println( s );
    }
    public static String toString( Calendar calendar ) {
    System.out.println();
    StringBuilder builder = new StringBuilder();
    int year = calendar.get( Calendar.YEAR );
    builder.append( year + "年" );
    // 鬼子那边的月份是从 零 开始计数的
    int month = calendar.get( Calendar.MONTH ) + 1 ;
    builder.append( month + "月" );
    int date = calendar.get( Calendar.DATE );
    builder.append( date + "日" );
    int hours = calendar.get( Calendar.HOUR_OF_DAY );
    if( hours < 10 ) {
    builder.append( '0' );
    }
    builder.append( hours + ":" );
    int minutes = calendar.get( Calendar.MINUTE );
    if( minutes < 10 ) {
    builder.append( '0' );
    }
    builder.append( minutes + ":" );
    int seconds = calendar.get( Calendar.SECOND );
    if( seconds < 10 ) {
    builder.append( '0' );
    }
    builder.append( seconds + "." );
    int millis = calendar.get( Calendar.MILLISECOND );
    builder.append( millis );
    String s = builder.toString();
    return s ;
    }
    }

14.2.2、其他

  • 如果不是使用 JDK 1.8 提供的 日期/时间 支持 ( 不是使用 java.time 包 提供的支持 ),但需要获得 指定 年、月、日、时、分、秒 、毫秒 对应的 java.util.Date 实例 ,则可以借助于 Calendar 实例来实现

  • 通过 clear 方法可以 清空 Calendar 实例的所有 日历字段值 和 时间值

  • 例子:

    public class CalendarTest3 {
    public static void main(String[] args) {
    // 1、获得一个 Calendar 实例
    final Calendar calendar = Calendar.getInstance(); // 默认地区、默认时区
    System.out.println( calendar );
    // 2、清空 Calendar 实例的所有 日历字段值 和 时间值
    calendar.clear(); // 将 当前Calendar实例 的所日历字段值和时间值(从历元至现在的毫秒偏移量)设置成未定义
    System.out.println( calendar );
    // 3、根据实际需要设置 年、月、日、时、分、秒 、毫秒
    calendar.set( 1999 , 9 , 10 , 16, 30, 40 ); // 注意月份从零开始计数
    calendar.set( Calendar.MILLISECOND , 123 );// 如有必要可以单独设置毫秒
    System.out.println( calendar );
    // 4、通过 Calendar 实例来获得 Date 对象
    Date date = calendar.getTime(); // 通过 Calendar实例 获得该实例所表示瞬间对应的 Date 对象
    System.out.println( date );
    long millis = calendar.getTimeInMillis(); // 通过 Calendar实例 获得该实例所表示瞬间 对应的毫秒值
    System.out.println( millis );
    }
    }

14.3、java.text.DateFormat

  • 通过 DateFormat 提供的 静态工厂方法 可以获得 DateFormat 实例,但是通常不会这样用

  • 通过 DateFormat 实例的 format 方法可以将一个 Date实例 格式化为 特定模式 的字符串

         final Calendar c = Calendar.getInstance();
        c.clear();
    c.set( 1999 , 9, 30, 19, 30 , 50 );
    final Date d = c.getTime();
    String s = df.format( d ) ; // 将 Date实例 格式化 为特定的字符串 形式
    System.out.println( s );
  • 获取默认语言环境的默认格式化风格:DateFormat.getInstance()

    DateFormat df = DateFormat.getInstance(); // 获取默认语言环境的默认格式化风格
    
  • 仅处理日期:DateFormat.getDateInstance()

    df = DateFormat.getDateInstance(); // 仅处理日期
    
  • 仅处理时,分,秒: DateFormat.getTimeInstance()

    df = DateFormat.getTimeInstance(); // 仅处理时间
    
  • 可通过创建 SimpleDateFormat 类的实例来完成 日期格式化操作(在创建 SimpleDateFormat 对象时,一定要注意 日期模式)

    //创建日期格式
    final String pattern = "Gyyyy年MM月dd日 EEEE HH:mm:ss.SSS" ;
    // 父类类型的引用 变量 指向了 子类类型的对象
    DateFormat df = new SimpleDateFormat( pattern );
    String s = df.format( d ); // 将 Date实例 格式化为 特定模式 的 字符串

14.4、java.time.LocalDate

  • 了解 LocalDate 中的常量: MIN / MAX / EPOCH

        System.out.println( LocalDate.MIN ); 
        System.out.println( LocalDate.EPOCH ); // 历元
    System.out.println( LocalDate.MAX );
  • LocalDate 类中的 静态工厂方法 :

    • now():
    LocalDate today = LocalDate.now(); // 获得当前日期
    
    • 通过LocalDate 对象来增加或减少天数

      System.out.println( today.plusDays( 15 ) ); // 增加 N 天
      
    • 通过LocalDate 对象来增加或减少几周

      System.out.println( today.plusWeeks( 3 ) ); // 增加 N 周
      
    • 通过LocalDate 对象来增加或减少几月

      System.out.println( today.plusMonths( -5 ) );
      
    • 通过LocalDate 对象来增加或减少几年

      System.out.println( today.plusYears( -10 ) );
      
    • of( int , int , int )

    LocalDate birthdate1 = LocalDate.of( 1999 ,  5 , 10 ); // 注意这里的月份从一开始
    
    • ofYearDay( int , int )
    LocalDate date = LocalDate.ofYearDay( 2020 , 200 ); // 获取 指定年份 的 第 N 天对应的 日期
    
    • 判断 指定日期 所在年份是否是闰年

      System.out.println( date.isLeapYear() ); 
      
    • 获取 当前日期 所在月份的总天数

      System.out.println( date.lengthOfMonth() );
      
    • 获取 当前日期 所在年份的总天数

      System.out.println( date.lengthOfYear() ); 
      
  • 理解 LocalDate 内部设计:

    • LocalDate 类是 final 修饰的

    • LocalDate 类的构造方法都是私有的

    • LocalDate 类中表示 年、月、日的 实例变量全部都是 私有 且 不可更改的:

    • private final int year ;

    • private final short month ;

    • private final short date

    • LocalDate 类是一个最终类,同时 LocalDate 类的实例 也是不可变的

    • 设置默认的国家/地区:

    Locale.setDefault( new Locale( "en" , "US") );
    

14.5、java.time.LocalTime

  • java.time.LocalTime 表示某一天当中的时间

  • 通过 LocalTime 类的 类方法 来获取 LocalTime 实例:

    • now()
    System.out.println( ZoneId.systemDefault() ); // 输出操作系统使用的默认时区
         LocalTime now = LocalTime.now(); // 根据默认时区获取当地的当前时间
    System.out.println( now );
    • now(ZoneId)
              ZoneId zone = ZoneId.of( "US/Alaska" );
             now = LocalTime.now( zone ); // 根据 指定时区 获取该地当前时间
    System.out.println( now );
    • of( int , int )
             LocalTime first = LocalTime.of( 15 ,  35 );
             System.out.println( first );//15:35
    
    • of( int , int , int )
             LocalTime second = LocalTime.of( 15 ,  35 , 40 );
             System.out.println( second );//15:35:40
    
    • of ( int , int , int , int)
         LocalTime third = LocalTime.of( 22 , 44 ,  55 , 100200300 ); // 时、分、秒、纳秒
             System.out.println( third );//22:44:55.100200300
    
  • 秒( s ) 、毫秒 ( ms )、微秒(μs)、纳秒(ns) 、皮秒(ps)之间的换算

        1s = 1000ms 。[ 毫秒 ( millisecond ) ]
          1ms = 1000μs 。[ 微秒 ( microsecond ) ]
    1μs = 1000ns 。[ 纳秒 也叫 毫微秒 ( nanosecond ) ]
    1ns = 1000ps 。[ 皮秒 ( picosecond ) ]
  • java.time.LocalTime的MIN,MAX ,MIDNIGHT,NOON

           System.out.println( LocalTime.MIN );//00:00
         System.out.println( LocalTime.MAX );//23:59:59.999999999
    System.out.println( LocalTime.MIDNIGHT );//00:00
    System.out.println( LocalTime.NOON );//12:00

14.6、java.time.LocalDateTime

  • java.time.LocalDateTime 表示 日期时间,其中既有日期又有时间

  • LocalDateTime类中的类方法:

    • now()
            LocalDateTime now = null;
            // 使用操作系统使用默认时区获得 LocalDateTime 实例
    now = LocalDateTime.now();
    System.out.println(now); // now.toString()
    • now(ZoneId)
    // 根据 指定时区 获取该时区对应的 日期时间
            now = LocalDateTime.now(ZoneId.of("US/Alaska"));
    System.out.println(now);
    • of( ... )
            LocalDate date = LocalDate.of(1998, 6, 10);
            LocalTime time = LocalTime.of(14, 20, 30, 111222333);
    LocalDateTime first = LocalDateTime.of(date, time);
    //1998-06-10T14:20:30.111222333

14.7、java.time.ZoneId

  • java.time.ZoneId 表示 时区

  • ZoneId.systemDefault() 获取当前操作系统使用的时区

    // 使用 ZoneId 类中的 systemDefault 方法获取当前操作系统使用的时区
        ZoneId zid = ZoneId.systemDefault() ;
    
  • ZoneId.getAvailableZoneIds() 获得当前JVM所支持的所有的 时区编号

    // 获得当前JVM所支持的所有 时区编号( zoneId )
        Set<String>  set = ZoneId.getAvailableZoneIds();        
    Object[] array = set.toArray(); // 将 Set 集合转换为 数组
    for( int i = 0 , n = array.length ; i < n ; i ++ ) {
    String id = (String)array[ i ] ;
    ZoneId zoneId = ZoneId.of( id );
    System.out.println( id + " : " + zoneId.getDisplayName( TextStyle.FULL , defaultLocale ) );
    }
  • ZoneId.of( String zoneId ) 根据 指定的 时区编号 获得相应的 ZoneId实例

    // 根据指定的 时区编号( zoneId ) 来获取 相应的 ZoneId实例
        ZoneId zone = ZoneId.of( "US/Alaska" );
    
  • 对于 LocalDate 来说,时区不同,所表示的 日期也可能不同

    ZoneId zone = ZoneId.of( "US/Alaska" );
    LocalDate date = LocalDate.now( zone );
    System.out.println( date );
    date = LocalDate.now();
    System.out.println( date );

14.8、转换

14.8.1、将LocalDateTime转换为LocalDate或LocalTime

LocalDateTime now = LocalDateTime.now(); // 默认时区 2020-06-02T21:29:08.531
LocalDate date = now.toLocalDate();//2020-06-02
LocalTime time = now.toLocalTime();//21:29:08.531

14.8.2、将LocalDate转换为LocalDateTime

LocalDate date = LocalDate.ofYearDay( 1999 ,  200 );//1999-07-19
// 获得 date 所表示日期的 00:00:00.000000000 对应的 LocalDateTime实例
LocalDateTime first = date.atStartOfDay(); //1999-07-19T00:00
LocalDateTime second = date.atTime( 5 , 30 );//1999-07-19T05:30
LocalDateTime thrid = date.atTime( 5 , 30 , 40 );//1999-07-19T05:30:40
LocalDateTime fourth = date.atTime( 5 , 30 , 40 , 100200300 );//1999-07-19T05:30:40.100200300

14.8.3、将LocalTime转换为LocalDateTime

LocalTime time = LocalTime.of( 16 ,  50 , 30, 400500600 );//16:50:30.400500600
LocalDate date = LocalDate.now(); // 默认时区
javaLocalDateTime first = time.atDate( date );//2020-06-02T16:50:30.400500600

14.8.4、将java.util,Date转换成LocalDateTime/LocalDate/LocalTime

  1. java.util.Date ===> java.time.LocalDateTime:

    java.util.Date ---> java.time.Instant ---> java.time.ZonedDateTime ---> java.time.LocalDateTime

    Calendar c = Calendar.getInstance();
        c.clear();
    c.set( 1999 , 9 , 10 , 20 , 30 , 40 );
    final Date utilDate = c.getTime();//Sun Oct 10 20:30:40 CST 1999
    // 根据 java.util.Date 实例 获得 与之对应的 java.time.Instant 实例
    Instant instant = utilDate.toInstant(); //1999-10-10T12:30:40Z
    ZoneId zone = ZoneId.systemDefault();
    // 根据 Instant 实例获得 带有时区的 日期时间对象 ( ZonedDateTime实例 )
    ZonedDateTime zdt = instant.atZone( zone );//1999-10-10T20:30:40+08:00[Asia/Shanghai]
    // ZonedDateTime ===> LocalDateTime
    LocalDateTime datetime = zdt.toLocalDateTime() ;//1999-10-10T20:30:40
  2. java.util.Date ===> java.time.LocalDate

    java.util.Date ---> java.time.Instant ---> java.time.ZonedDateTime ---> java.time.LocalDate

    // ZonedDateTime ===> LocalDate
        LocalDate date = zdt.toLocalDate() ;//1999-10-10
    
  3. java.util.Date ===> java.time.LocalTime

    java.util.Date ---> java.time.Instant ---> java.time.ZonedDateTime ---> java.time.LocalTime

    // ZonedDateTime ===> LocalTime
        LocalTime time = zdt.toLocalTime() ;//20:30:40
    

14.8.5、将LocalDateTime/LocalDate/LocalTime转换成java.util,Date

  • java.time.LocalDateTime ===> java.util.Date
    • java.time.LocalDateTime ---> java.time.ZonedDateTime ---> java.time.Instant ---> java.util.Date
       LocalDate date = LocalDate.of( 1949 , 10 , 1 );
        LocalTime time = LocalTime.of( 10 , 0 );
        LocalDateTime datetime = LocalDateTime.of( date , time );
        //1949-10-01T10:00
        ZoneId zone = ZoneId.systemDefault() ;
        
        // ZonedDateTime zonedDateTime = ZonedDateTime.of( date , time , zone );
        ZonedDateTime zonedDateTime = ZonedDateTime.of( datetime , zone );
        
        // ZonedDateTime 类 实现了 TemporalAccessor 接口
        Instant instant = Instant.from( zonedDateTime ) ;
        
        // 将一个 java.time.Instant 实例 转换为 java.util.Date 实例
        Date utilDate = Date.from( instant ) ;
        //Sat Oct 01 10:00:00 CST 1949

15、内部类

  • 意义:为了更好的封装。

15.1、 分类

15.1.1、按是否为成员划分

  1. 嵌套类(Nested Classes):
    • 静态嵌套类
    • 实例嵌套类
  2. 局部类(Local Classes):
    • 局部内部类
    • 匿名内部类

15.1.2、java语言规范中的分类

  1. 嵌套类(Nested Classes)【被static修饰的成员内部类】

  2. 内部类(Inner Classes):

    • 实例内部类
      • 【没有被static修饰的成员内部类】
      • 除了常量外,不能声明static修饰的字段
  • 局部类(Local Classes)
    • 在宿主类类体括号内部的某个局部区域中声明的内部类
    • 除了常量外,不能声明static修饰的字段
    • 匿名类(Anonymous Classes)
    • 不能显示指定类名
    • 不能显示声明构造方法
    • 除了常量外,不能声明static修饰的字段
    • 创造匿名类额实例时,会调用父类的指定构造方法

15.2 、声明与创建实例

15.2.1、嵌套类

  • 直接在 外部类 的 类体括号中声明的 类,就是 嵌套类 ( Nested Classes )

  • 嵌套类 也被称作 成员内部类

    public class Computer {
    static class Brand { 
    }//Brand就是一个静态嵌套类
    }
  • 有 static 修饰的 嵌套类,也被称作 【静态嵌套类】 或【 静态内部类】

  • 没有 static 修饰的 嵌套类,也被称作【 非静态嵌套类 】或【 非静态内部类 】或 【实例嵌套类 】或【 实例内部类】

  • 创建静态嵌套类【 静态内部类】的实例

    1. 声明 静态嵌套类 【静态内部类 】 的 引用变量

      Computer.Brand b =  null ;
      
    2. 创建 静态嵌套类【静态内部类 】 的实例

      b = new Computer.Brand();//new 外部类类名。嵌套类类名()
      
  • 创建非静态嵌套类【 实例内部类】的实例

    1. 声明 非静态嵌套类 【 实例内部类 】 的 引用变量

      外部类类名.内部类类名 变量=....

      Computer.Mainboard m = null ;//Mainboard时非静态嵌套类
      
    2. 创建 非静态嵌套类【 实例内部类 】的实例

      1. 首先创建外部类的实例
      Computer c = new Computer() ;
      
      1. 在 外部类实例 基础之上创建 实例内部类 实例
      m = c.new Mainboard();
      
  • 实例内部类【调用】实例

    1. 实例内部类 中 使用 this 可以引用 该类的当前实例

      
      
  • 获取嵌套类的全限定名称:嵌套类对象.getClass().getName()

    System.out.println( b.getClass().getName() );
    
  • 获取嵌套类的规范化名称:嵌套类对象.getClass().getCanonicalName()

    System.out.println( b.getClass().getCanonicalName() )
    

15.2.2、 局部内部类

  • 在外部类的类方法中创建的类叫做局部内部类(个人总结)
public class Computer{
    public void show(){
        class Printer{          
        }
    }
}
  • 创建局部内部类的实例:直接在其有效作用域中使用new关键字创建

15.2.3 、匿名内部类

16、枚举

16.1、 单例模式

16.1.1、 懒汉式

  • 不一开始就创建类的实例,而是在首次执行时才创建实例

    public class Moon {
    private static Moon moon ; // 没有直接创建 Moon 类的实例
    private Moon() {
    }
    public static Moon getInstance() {
    // 我们期望 getInstance 方法首次执行时才创建 Moon 实例
    if( moon == null ) {
    moon = new Moon();
    }
    return moon ;
    }
    public static void main(String[] args) {
    // Moon m = new Moon();
    // The constructor Moon() is not visible
    Moon m = Moon.getInstance();//enumeration.Moon@15db9742
    Moon o = Moon.getInstance();//enumeration.Moon@15db9742
    System.out.println( m == o );//true
    }
    }

16.1.2、饿汉式

  • 将所有的构造方法私有化,以避免在该类之外创建其实例

  • 提供一个 类变量 用来缓存本类唯一的实例

  • 提供一个可以返回当前类的实例的类方法 ( 静态工厂方法 : static factory method )

    public class Sun {
    private static final Sun SUN = new Sun(); // 直接创建 Sun 实例
    // 构造被私有化
    private Sun() {
    }
    // 提供一个用来获取本类的实例的类方法
    public static Sun getInstance() {
    return SUN ;
    }
    public static void main(String[] args) {
    // Sun s = new Sun(); // 不能直接实例化
    Sun s = Sun.getInstance() ; // 通过 静态工厂方法 来获取 Sun 类的实例
    System.out.println( s );//enumeration.Sun@15db9742
    Sun u = Sun.getInstance() ;
    System.out.println( u );//enumeration.Sun@15db9742
    Sun n = Sun.getInstance() ;
    System.out.println( n );//enumeration.Sun@15db9742
    }
    }

16.2、 多例模式

16.2.1、懒汉式

  • 不一开始就创建类的实例,而是在首次执行时才创建实例

    public class Gender {
    private static Gender female ;  // 女
    private static Gender male ; // 男
    private int index ;
    private String name ;
    private Gender(int index, String name) {
    super();
    this.index = index;
    this.name = name;
    }
    @Override
    public String toString() {
    return "Gender [index=" + index + ", name=" + name + "]";
    }
    public static Gender of( int index ) {
    switch ( index ) {
    case 1 : return of( "女" );
    case 2 : return of( "男" );
    default:
    return null ;
    }
    }
    public static Gender of( String name ) {
    switch ( name ) {
    case "女":
    if( female == null ) {
    female = new Gender( 1 , "女" );
    }
    return female ;
    case "男":
    if( male == null ) {
    male = new Gender( 2 , "男" );
    }
    return male ;
    default:
    return null ;
    }
    }
    }

16.2.2、饿汉式

  • 将所有构造方法私有化

    private int index; // 第几个季度
    private String name; // 季度名称
    private String description; // 季度描述
  • 使用 类变量 将 有限的几个实例缓存起来

      public static final Season SPRING = new Season( 1 , "春" , "春暖花开" ) ;
    public static final Season SUMMER = new Season( 2 , "夏" , "炎炎夏日" ) ;
    public static final Season AUTUMN = new Season( 3 , "秋" , "秋高气爽" ) ;
    // Eclipse 快捷键: ctrl + shift + y 变小写 , ctrl + shift + x 变大写
    public static final Season WINTER = new Season( 4 , "冬" , "银装素裹" ) ;
  • 提供 类方法 用以获取指定的 实例

    private Season(int index, String name, String description) {
        super();
    this.index = index;
    this.name = name;
    this.description = description;
    }
    // 静态工厂方法 ( static factory method )
    public static Season of( int index ){
    switch ( index ) {
    case 1: return SPRING ;
    case 2: return SUMMER ;
    case 3: return AUTUMN ;
    case 4: return WINTER ;
    default: return null ;
    }
    }
    // 静态工厂方法 ( static factory method )
    public static Season of( String name ){
    // JDK 1.7 开始 支持在 switch 中使用 String
    switch ( name ) {
    case "春" : return SPRING ;
    case "夏": return SUMMER ;
    case "秋": return AUTUMN ;
    case "冬": return WINTER ;
    default: return null ;
    }
    }
    public int getIndex() {
    return index;
    }
    public String getName() {
    return name;
    }
    public String getDescription() {
    return description;
    }
    public static void main(String[] args) {
        // Season s = new Season(); // The constructor Season() is undefined
    // is undefined 与 is not visible 是不同的
    // Season s = new Season( 5 , "第五季" , "朕的季节朕做主" ); // The constructor Season(int, String, String) is not visible
    Season s = Season.of( 1 );
    Season e = Season.of( "春" );
    System.out.println( s == e ); //true
    Season a = Season.SPRING ;
    System.out.println( e == a );//true
    System.out.println( a.getIndex() + " , " + a.getName() + " , " + a.getDescription() );//1 , 春 , 春暖花开
    }

16.3、枚举

16.3.1、意义

  • 使用枚举可以使用单例或多例

16.3.2、定义

  • 枚举使用enum关键字来定义
  • 枚举名称的命名规则与类名相同
  • 枚举是从JDk1.5开始新增的类型

16.3.3、父类

  • 所有的枚举类型都继承了java.lang.Enum类

  • 枚举可以重写从 java.lang.Enum 类中继承的 toString(),java.lang.Enum 类重写了从 java.lang.Object 继承的 toString 方法

    @Override
    public String toString() { 
    // 枚举可以重写从 java.lang.Enum 类中继承的 toString()
    // java.lang.Enum 类重写了从 java.lang.Object 继承的 toString 方法
    return super.toString() + " : " + description ;
    }
  • 所有的枚举类都不能功过entends显式继承java.lang.Enum类

16.3.4、组成

16.3.4.1、枚举常量

  • 将来定义枚举时,不要在 名称中 加 Enum 后缀 。

  • 枚举常量必须声明在枚举类的首行代码声明(第一个分号之前)

    SPRING( "春暖花开" ) , SUMMER , AUTUMN , WINTER ;
    
  • 所有的枚举常量的修饰符都是public static final

  • 声明枚举常量时可以调用枚举类的只id了构造方法(通过参数确定)

16.3.4.2、成员变量

  • 可以在枚举类定义类变量

  • 可以在枚举类定义实例变量

    private String description ; // 实例变量 ( 成员变量 )
    

16.3.4.3、成员方法

  • 可以在枚举类定义类方法

    // 成员方法
    public String getDescription() {
    return description ;
    }
  • 可以在枚举类定义实例方法

16.3.4.4、构造方法

  • 枚举中所有的构造方法都是私有的

  • 即使部位构造方法显式指定private修饰符也是私有的

  • 编译器会自动生成构造方法中的super(...)代码

  • 不要在枚举的构造方法中显式使用super(...)来调用父类构造

    SeasonEnum() {
        // 编译器会自动生成这里的 super( ... ) 代码
    System.out.println( "private SeasonEnum()" );
    }
    private SeasonEnum( String description ) {
    // 编译器会自动生成这里的 super( ... ) 代码
    this.description = description ;
    System.out.println( "private SeasonEnum( String )" );
    }

16.3.4.5、编译器生成的方法

  • public sttaic X valueOf(String name)

    • 编译器 为 每一个 枚举类增加了一个 valueOf( String ) 方法,用于根据 枚举常量名称 获取 枚举常量
    GenderEnum g = GenderEnum.valueOf( "FEMALE" ); 
    // 参数是 枚举常量名称 ( 英文字母严格区分大小写 )
    System.out.println( g );
    System.out.println( g == GenderEnum.FEMALE );
    • 参数是 枚举常量名称 ( 英文字母严格区分大小写 )

    • java.lang.Enum 类中的 类方法 valueOf 可以通过 Enum 类来调用

    GenderEnum x = Enum.valueOf( GenderEnum.class ,  "MALE" );
            System.out.println( x );
    
    • java.lang.Enum 类中的 类方法 valueOf 可以通过其 子类 来调用
    GenderEnum y = GenderEnum.valueOf( GenderEnum.class ,  "MALE" );
    
  • public static X[] values()

    SeasonEnum[] array = SeasonEnum.values();
        for( int i = 0 , n = array.length ; i < n ; i++ ) {
    System.out.println( array[ i ] ); // toString()
    }
    GenderEnum[] array = GenderEnum.values();
    System.out.println( Arrays.toString( array ) );

16.4、java.lang.Enum类

16.4.1、特点

  • 是所有java语言枚举类型的公共基本类
  • 支持自然排序(实现了java.lang.Comparable接口)
  • 支持序列化和反序列化(实现了java.io.Serializable)

16.4.2、构造

  • protected Enum(String name,int oridinal)
    • 程序员无法调用此方法
    • 该构造方法用于由响应枚举类型声明的编译器发出的代码
    • 参数name边上枚举常量的名称,它是用来声明该常量的标识符
    • 参数index表示常量的序数(它在枚举声明的位置,其中初始常量序数为0)

16.4.3、实例方法

  • public final String name()
  • public final int oridinal()
  • public final boolean equals(Object other)
  • public final int hashCode()
  • public String toString()

16.4.4、类方法

  • public static >T valueOf(ClassenumType,String name)

17、集合框架

17.1、Iterator

17.1.1、作用

  • 实现这个接口允许对象成为"for-each loop"语句的目标

17.1.2、抽象方法

  • Iterator iterator()

17.1.3、子接口

  • java.util.Collection
  • java.util.ListIterator

17.1.4、理解方法

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * java.util.Iterator 接口中的抽象方法:  hasNext() 、next() 、remove()
 */
public class IteratorTest1 {

    public static void main(String[] args) {
        
        List<String> x = new ArrayList<String>();
        x.add( "周勃" );
        x.add( "卫青" );
        x.add( "霍去病" );
        x.add( "岳飞" );
        x.add( "文天祥" );
        x.add( "辛弃疾" );
        
        Iterator<String> t = x.iterator(); // 获得一个可以迭代 x 的 迭代器
        
        while( t.hasNext() ) { // 判断是否存在下一个元素
            String s = t.next(); // 获取下一个元素
            // 判断目前已经获取到的元素是否是 "霍去病"
            if( s.equals( "霍去病" ) ) {
                // x.remove( "霍去病" ); // [ 快速失败 ] JVM 会尽可能抛出 java.util.ConcurrentModificationException 
                // 当使用 迭代器 或 "for-each loop" 语句 迭代集合时,
                // 如果需要删除集合中的元素,应该使用 迭代器的 remove 方法,而不是集合本身的方法 ( 比如 x.remove( "霍去病" ) )
                t.remove(); // 删除目前已经获取到的元素 ( 即刚刚调用 next 方法获取到谁就删除谁 )
            }
        }

    }

}

17.1.5、关于ListIterator

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

/**
 * 1、java.util.ListIterator 接口是 java.util.Iterator 接口的子接口
 * 2、ListIterator 接口中的抽象方法:
 *          boolean hasNext() ;
 *          E next();
 *          int nextIndex();
 *          void remove();
 *          void set( E e );
 *          void add( E e );
 *          int previousIndex();
 *          E previous();
 *          boolean hasPrevious();   
 */
public class IteratorTest2 {

    public static void main(String[] args) {
        
        List<String> x = new ArrayList<String>();
        x.add( "周勃" );
        x.add( "卫青" );
        x.add( "霍去病" );
        x.add( "岳飞" );
        x.add( "文天祥" );
        x.add( "辛弃疾" );
        
        ListIterator<String> t = x.listIterator();
        
        while( t.hasNext() ) { // 是否有下一个元素
            String s = t.next(); // 获取下一个元素
            if( s.contentEquals( "霍去病" ) ) { // contentEquals 是 String 类中的方法
                // 使用参数指定的对象 替换 目前已经获取到的元素
                t.set( "霍光" );  // ( 即刚刚调用 next 方法获取到谁就替换谁,集合中的数据会因此而发生改变 )
            }
        }
        
        System.out.println( x );
        
        while( t.hasPrevious() ) { // 是否有前一个元素
            String s = t.previous() ; // 获取前一个元素
            if( s.contentEquals( "霍光" ) ) {
                // 将参数指定的对象 添加到 目前已经获取到的元素 之前 ( List是有顺序存放的 )
                t.add( "霍去病" );
            }
        }
        
        System.out.println( x );
        
        // List 接口中提供的 listIterator( int beginIndex ) 方法用于获取可以迭代 beginIndex 及之后的元素的迭代器
        ListIterator<String> itor = x.listIterator( 4 ); // 获取新的迭代器对象,用于迭代 索引 4 及其之后的元素
        while( itor.hasNext() ) {
            System.out.println( itor.next() );
        }

    }

}

17.2、java.util.Collection

17.2.1、特点

  • 有序存放
  • 可重复
  • 可排序

17.2.2、子接口

  • java.util.List
  • java.util.Set
  • java.ytil.Queue

17.2.3、实例方法

  • size / isEmpty / contains / remove / toArray / clear
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;

/**
 * 测试 Collection 接口中的 size / isEmpty / contains / remove / toArray / clear
 */
public class CollectionTest1 {

    public static void main(String[] args) {
        
        // Eclipse 快捷键 : ctrl + shift + o 快速修复代码
        Collection<Object> collection = new ArrayList<Object>();
        System.out.println( "size : " + collection.size() + " , is empty : " + collection.isEmpty() );
        
        collection.add( "吴老二" );
        collection.add( 9527 );
        collection.add( '中' );
        collection.add( null );
        
        System.out.println( "size : " + collection.size() + " , is empty : " + collection.isEmpty() );
        
        System.out.println( collection ); // toString()
        
        System.out.println( collection.contains( "吴老二" ) ); // true
        System.out.println( collection.contains( "null" ) ); // false
        
        System.out.println( collection.remove( "null" ) ); // false
        System.out.println( collection );
        
        System.out.println( collection.remove( null ) ); // true
        System.out.println( collection );
        
        Object[] array = collection.toArray();
        System.out.println( Arrays.toString( array ) );
        
        collection.clear();
        System.out.println( collection );
        
        System.out.println( "size : " + collection.size() + " , is empty : " + collection.isEmpty() );

    }

}
  • addAll / containsAll / removeAll
import java.util.ArrayList;
import java.util.Collection;

/**
 * 测试 Collection 接口中的 addAll / containsAll / removeAll
 */
public class CollectionTest2 {

    public static void main(String[] args) {
        
        // 注意这里在 < > 中间使用的是 String
        Collection<String> first = new ArrayList<String>();
        first.add( "唐三藏" );
        first.add( "孙悟空" );
        first.add( "宋江" );
        first.add( "花荣" );
        System.out.println( "first => " + first );
        
        Collection<String> second = new ArrayList<String>();
        second.add( "李广" );
        second.add( "李敢" );
        second.add( "李凌" );
        System.out.println( "second => " + second );
        
        // addAll( Collection ) 将 参数对应的集合中的元素全部添加到 当前集合中
        System.out.println( first.addAll( second ) );  // true
        System.out.println( "first => " + first );
        
        System.out.println( first.containsAll( second ) ); // true
        
        first.remove( "李凌" ); // 删除 "李凌"
        System.out.println( "first => " + first );
        System.out.println( "second => " + second );
        
        // 当 containsAll 参数指定的集合 中包含的元素 全部都在 当前集合 中存在时返回 true  ,否则返回 false
        System.out.println( first.containsAll( second ) ); // false
        
        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );
        
        // 当 removeAll 参数指定的集合 中所包含的元素 存在于 当前集合 时,就从 当前集合 中删除该元素
        // 如果因为调用 removeAll 导致 当前集合 发生变化就返回 true, 否则返回 false
        System.out.println( first.removeAll( second ) ) ; // true
        
        System.out.println( "first => " + first );
        System.out.println( "second => " + second );
        

    }

}
  • retainAll / hashCode / equals
import java.util.ArrayList;
import java.util.Collection;

/**
 * 测试 Collection 接口中的 retainAll / hashCode / equals
 */
public class CollectionTest3 {

    public static void main(String[] args) {
        
        // 注意这里在 < > 中间使用的是 String
        Collection<String> first = new ArrayList<String>();
        first.add( "李广" );
        first.add( "唐三藏" );
        first.add( "孙悟空" );
        first.add( "宋江" );
        first.add( "花荣" );
        
        Collection<String> second = new ArrayList<String>();
        second.add( "李广" );
        second.add( "李敢" );
        second.add( "李凌" );
        
        System.out.println( "first => " + first );
        System.out.println( "second => " + second );
        
        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );
        
        // 顺"参"者昌逆"参"者亡
        // 如果 当前 collection  中所包含的元素 在 参数指定的 collection 中也存在,则保留下来,否则统统删除 
        System.out.println( first.retainAll( second ) ); // true : 如果当前 collection 由于调用 retainAll 而发生更改,则返回 true
        
        System.out.println( "first => " + first );
        System.out.println( "second => " + second );
        
        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );
        
        Collection<String> third = new ArrayList<String>( second );
        System.out.println( "third => " + third );
        System.out.println( "second => " + second );
        
        System.out.println( second.hashCode() );
        System.out.println( third.hashCode() );
        
        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );
        System.out.println( second == third ); // false
        System.out.println( System.identityHashCode( second ) );
        System.out.println( System.identityHashCode( third ) );
        System.out.println( second.equals( third ) ); // true

    }

}
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

/**
 * 1、测试 Collection 接口中的 iterator
 * 2、什么是 迭代器 ( Iterator )
 */
public class CollectionTest4 {

    public static void main(String[] args) {
        
        // 注意这里在 < > 中间使用的是 String
        Collection<String> c = new ArrayList<String>();
        c.add( "李广" );
        c.add( "唐三藏" );
        c.add( "孙悟空" );
        c.add( "宋江" );
        c.add( "花荣" );
        c.add( "李广" );
        c.add( "李敢" );
        c.add( "李凌" );
        
        System.out.println( "for-each loop : " );
        
        // 对于 Iterable 实例来说,根本就没有什么 增强的 for 循环,本质上还是使用 迭代器完成迭代
        for( String s : c ) {
            System.out.println( s ); 
        }
    
        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );
        
        // 用来完成 安全检查操作 的 机器 就是 安检设备
        // 用来完成 迭代集合操作 的 对象 就是 迭代器
        Iterator<String> itor = c.iterator(); // 返回一个可以 迭代 集合 c 中所包含元素的 迭代器 
        
        while( itor.hasNext() ) { // 判断是否存在下一个元素
            String e = itor.next() ; // 获取下一个元素
            System.out.println( e );
        }
        
        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );
        
        for( Iterator<String> it = c.iterator(); it.hasNext() ; ) {
            String e = it.next();
            System.out.println( e );
        }
        
    }

}

17.3、 java.util.List

17.3.1、 特点

  • 有序存放:list 集合中的 元素的存放顺序 与 当时的添加顺序 是相同的
  • 可重复
  • 可排序

17.3.2、实例方法

  • List 接口中声明的方法 : add(int,E) / remove(int) / set(int, E) / subList(int, int)
import java.util.ArrayList;
import java.util.List;

/**
 * List 接口中声明的方法 : add(int,E) / remove(int) / set(int, E) / subList(int, int)
 */
public class ListTest2 {

    public static void main(String[] args) {
        
        List<String> list = new ArrayList<String>();
        
        list.add( "卫青" );
        list.add( "霍去病" );
        list.add( "岳飞" );
        list.add( "文天祥" );
        
        // list 集合中的 元素的存放顺序 与 当时的添加顺序 是相同的
        System.out.println( list );
        
        list.add( 2 , "辛弃疾" ); // void add( int index , E element )
        System.out.println( list );
        
        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );
        
        list.remove( 1 ); // 删除索引 1 处的元素
        
        System.out.println( list );
        //把索引为2处的元素内容替换为 霍去病
        String old = list.set( 2 ,  "霍去病" ); // E set( int index , E element )
        System.out.println( old );
        
        System.out.println( list );
        //输出索引为1,2的两个元素
        List<String> sub = list.subList( 1 ,  3 ) ; // List<E> subList( int from , int to )
        System.out.println( sub );

    }

}

输出结果:

[卫青, 霍去病, 岳飞, 文天祥]
[卫青, 霍去病, 辛弃疾, 岳飞, 文天祥]
~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
[卫青, 辛弃疾, 岳飞, 文天祥]
岳飞
[卫青, 辛弃疾, 霍去病, 文天祥]
[辛弃疾, 霍去病]
[卫青, 辛弃疾, 霍去病, 文天祥]
  • 迭代List的三种方式
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class ListTest3 {

    public static void main(String[] args) {
        
        List<String> list1 = new ArrayList<String>();
        list1.add( "周勃" );
        list1.add( "卫青" );
        list1.add( "霍去病" );
        list1.add( "岳飞" );
        
        List<String> list2 = new ArrayList<String>();
        list2.add( "文天祥" );
        list2.add( "辛弃疾" );
        
        list1.addAll( 3 , list2 ) ; // boolean  addAll( int index, Collection<? extends E> c )
        
        System.out.println( list1 );
        
        System.out.println( "~ ~ ~ ~ ~ ~" );

        for( int i = 0 ; i < list1.size() ; i++ ) {
            String x = list1.get( i );
            System.out.println( x );
        }
        
        System.out.println( "~ ~ ~ ~ ~ ~" );
        
        Iterator<String> itor = list1.iterator();
        
        while( itor.hasNext() ) {
            String x = itor.next();
            System.out.println( x );
        }
        
        System.out.println( "~ ~ ~ ~ ~ ~" );
        
        for( String x : list1 ) {
            System.out.println( x );
        }

    }

}
  • 从 Java 9 开始 List 接口中声明了一批 of 方法,用于返回 【定长】的 List 集合
import java.util.List;
import java.util.ListIterator;

/**
 * 1、List 接口中的类方法: of
 * 2、java.util.ListIterator
 */
public class ListTest4 {

    public static void main(String[] args) {
        
        // 从 Java 9 开始 List 接口中声明了一批 of 方法,用于返回 【定长】的 List 集合
        List<String> list = List.of( "韩信" , "晁错" , "窦婴" , "李广" , "李敢" , "李凌" );
        System.out.println( list );
        // list.add( "李敢" ); // java.lang.UnsupportedOperationException
        
        for( int i = 0 , n = list.size() ; i < n ; i++ ) {
            System.out.println( list.get( i ) );
        }
        
        System.out.println( "~ ~ ~ ~ ~ ~" );
        
        for( int i = list.size() - 1 ; i >= 0 ; i-- ) {
            System.out.println( list.get( i ) );
        }
        
        System.out.println( "~ ~ ~ ~ ~ ~" );
        
        ListIterator<String> listIterator = list.listIterator();
        
        while( listIterator.hasNext() ) {
            int index = listIterator.nextIndex() ;
            String element = listIterator.next();
            System.out.println( index + " - " + element );
        }
        
        System.out.println( "~ ~ ~ ~ ~ ~" );
        
        while( listIterator.hasPrevious() ) {
            int index = listIterator.previousIndex() ;
            String element = listIterator.previous();
            System.out.println( index + " - " + element );
        }

    }

}

输出结果:

[韩信, 晁错, 窦婴, 李广, 李敢, 李凌]
韩信
晁错
窦婴
李广
李敢
李凌
~ ~ ~ ~ ~ ~
李凌
李敢
李广
窦婴
晁错
韩信
~ ~ ~ ~ ~ ~
0 - 韩信
1 - 晁错
2 - 窦婴
3 - 李广
4 - 李敢
5 - 李凌
~ ~ ~ ~ ~ ~
5 - 李凌
4 - 李敢
3 - 李广
2 - 窦婴
1 - 晁错
0 - 韩信

17.3.3、实现类

17.3.3.1、java.util.ArrayList

17.3.3.1.1、特点
  • 内部采用 数组存储数据
  • 元素可以为null
  • 不是线程安全的
  • 支持随机访问(RandomAccess)
17.3.3.1.2、父类
  • 直接父类:java.util.AbstractList
  • 间接父类:java.util.AbstractCollection
17.3.3.1.3、实现接口
  • java.util.List
  • java.util.RandomAccess
  • java.lang.Cloneable
  • java.io.Serializable
17.3.3.1.4、构造
  • public ArrayList()
  • public ArrayList(Collection<? extends E> c)
  • public ArrayList(int initialCapacity)
17.3.3.1.5、实例方法
1.泛型方法的声明
/*
     * 泛型方法
     * 
     * [修饰符]  <类型参数>  返回类型  方法名称( 形参列表 )  [ throws 异常类型列表 ] {
     * }
     * 
     * 泛型方法仅适用于 引用类型,不适用于 基本数据类型 
     * (所有的类型参数都仅适用于 引用类型 ,不适用于 基本类型 )
     */
        public static <T> T[] build( Class<T>  clazz , int length ) {
                // 以 clazz 对应的类 为 组件类型 创建一个长度 为 length 的新数组
                @SuppressWarnings("unchecked")
                T[] newArray = (T[])Array.newInstance( clazz , length );
                return newArray;
         }
2.ArrayList重新声明的方法
  • void trimToSize(); 把ArrayList实例的容量调整为当前列表的大小
  • ensureCapacity( );设置容量

17.3.3.2、java.util.LinkedList

17.3.3.2.1、特点
  • 内部采用链表存储数据
  • 元素可以为null
  • 不是线程安全的
  • 不支持随机访问(RandomAccess)
17.3.3.2.2、父类
  • 直接父类:java.util.AbstractSequentialList
  • 间接父类:java.util.AbstractCollection、java.util.AbstractList
17.3.3.2.3、实现接口
  • java.util.List
  • java.util.Deque
  • java.lang.Cloneable
  • java.io.Serializable
17.3.3.2.4、构造
  • public LinkedList()
  • public LinkedList(Collection<? extends E> c)
17.3.3.2.5、实例方法

参考队列

17.3.3.2.6、设计
  • 字段

transient Node first

transient Node last

  • 内部类

private static class Node

17.3.3.3、java.util.Vector

17.3.3.3.1、特点
  • 内部采用数组存储数据
  • 线程安全
17.3.3.3.2、子类
  • java.util.Stack

17.3.4、数组、ArrayList、LinkedList、Vector区别

  • 数组与链表的区别

    A:数组

    • 查询快修改也快
    • 增删慢

    B:链表

    • 查询慢,修改也慢
    • 增删快
  • List的三个子类的特点
    ArrayList:
    底层数据结构是数组,查询快,增删慢。
    线程不安全,效率高。
    Vector:
    底层数据结构是数组,查询快,增删慢。
    线程安全,效率低。
    Vector相对ArrayList查询慢(线程安全的)
    Vector相对LinkedList增删慢(数组结构)
    LinkedList:
    底层数据结构是链表,查询慢,增删快。
    线程不安全,效率高。

  • Vector和ArrayList的区别
    Vector是线程安全的,效率低
    ArrayList是线程不安全的,效率高
    共同点:都是数组实现的

  • ArrayList和LinkedList的区别
    ArrayList底层是数组结果,查询和修改快
    LinkedList底层是链表结构的,增和删比较快,查询和修改比较慢
    共同点:都是线程不安全的

  • List有三个儿子,我们到底使用谁呢?
    查询多用ArrayList
    增删多用LinkedList
    两者都多用ArrayList

17.4、java.util.Set

17.4.1、特点

  • 不可重复
  • 最多包含一个null元素

17.4.2、实现类 java.util.HashSet

17.4.2.1、特点
  • 内部采用哈希表来存放数据(本质上是借助于HashMap实现的)
  • 允许存在null元素
  • 不保证集合中元素的顺序(特别是它不保证该顺序恒久不变)
  • 不支持排序
  • 不是线程安全的
17.4.2.2、接口
  • java.util.Set
  • java.lang.Cloneable
  • java.io.Serializable
17.4.2.3、父类

java.util.AbstractSet

17.4.2.4、成员变量
  • private transient HashMap map;
  • private static final Object PRESENT = new Object();
17.4.2.5、构造方法
  • public HashSet()
  • public HashSet(Collection<? extends E> c)
  • public HashSet(int initialCapacity)
  • public HashSet(int initialCapacity,float loadFactor)
17.4.2.6、实例方法
  • boolean add(E e)

  • boolean remova(Object o)

17.4.3、子接口 SortedSet

17.4.3.1、特点
  • 可排序(自然顺序、比较器)

测试:

import java.util.SortedSet;
import java.util.TreeSet;

public class SortedSetTest {

    public static void main(String[] args) {
        
        SortedSet<Integer> set = new TreeSet<>();
        
        set.add( 10 );
        System.out.println( set );
        
        set.add( 7 );
        System.out.println( set );
        
        set.add( 1 );
        System.out.println( set );
        
        set.add( 2 );
        System.out.println( set );
        
        set.add( 5 );
        System.out.println( set );
        
        System.out.println( set.first() );
        
        System.out.println( set.last() );
        
        System.out.println( set.headSet( 5 ) );
        
        System.out.println( set.tailSet( 5 ) );
        
        System.out.println( set.subSet( 2 , 7 ) );

    }

}

输出结果:

[10]
[7, 10]
[1, 7, 10]
[1, 2, 7, 10]
[1, 2, 5, 7, 10]
1
10
[1, 2]
[5, 7, 10]
[2, 5]
17.4.3.2、子接口 java.util.NavigableSet
17.4.3.2.1、作用

提供了为给定搜索目标报告最接近匹配项的导航方法

17.4.3.2.2、实现类 java.util.TreeSet

特点:

  • 内部基于红黑树(Red-Black-tree)存储元素
  • 不允许存在null元素
  • 支持排序(自然排序、比较器排序)
  • 不是线程安全的

17.5、java.util.Queue

17.5.1、特点

  • Queue表示队列
  • 队列的特点是 先进先出(FIFO:First in,First out)

17.5.2、抽象方法

  • boolean offer(E e) // 在队列尾部添加元素
  • E peek() // 获取队列头部元素
  • E poll() // 移除队列头部元素
  • boolean add(E e) // 在队列尾部添加元素
  • E element // 获取队列头部元素
  • E remove() // 移除队列头部元素
import java.util.LinkedList;
import java.util.Queue;

/**
 * 单端队列 : add( E e ) / element() / remove()
 */
public class QueueTest1 {

    public static void main(String[] args) {
        
        // 创建一个队列 ( 接口类型的引用变量 指向了 实现类类型的对象 )
        Queue<String> queue = new LinkedList<>(); // "菱形语法"
        
        queue.add( "唐三藏" ) ; // 在队列尾部添加元素
        queue.add( "孙悟空" ) ; 
        queue.add( "小白龙" );
        queue.add( "猪悟能" );
        queue.add( "沙悟净" );
        
        System.out.println( queue );
        
        String head = queue.element() ; // 获取队列头部元素
        System.out.println( head );
        System.out.println( queue );
        
        head = queue.remove(); // 移除队列头部元素
        System.out.println( head );
        System.out.println( queue );
        
        queue.clear(); // 因为 Queue 接口继承了 Collection 接口,所以也是 collection
        // queue.element() ; // java.lang.NoSuchElementException
        // queue.remove(); // java.lang.NoSuchElementException

    }

}
import java.util.LinkedList;
import java.util.Queue;

/**
 * 单端队列 : offer( E e ) / peek() / poll()
 */
public class QueueTest2 {

    public static void main(String[] args) {
        
        // 创建一个队列 ( 接口类型的引用变量 指向了 实现类类型的对象 )
        Queue<String> queue = new LinkedList<>(); // "菱形语法"
        
        queue.offer( "唐三藏" ) ; // 在队列尾部添加元素
        queue.offer( "孙悟空" ) ; 
        System.out.println( "queue => " + queue );
        queue.offer( "小白龙" );
        queue.offer( "猪悟能" );
        System.out.println( "queue => " + queue );
        queue.offer( "沙悟净" );
        System.out.println( "queue => " + queue );
        
        String head = queue.peek() ; // 获取队列头部元素
        System.out.println( head );
        System.out.println( queue );
        
        head = queue.poll(); // 移除队列头部元素
        System.out.println( head );
        System.out.println( queue );
        
        queue.clear(); // 因为 Queue 接口继承了 Collection 接口,所以也是 collection
        
        System.out.println( queue.peek() ) ; // null
        System.out.println( queue.poll() ) ;  // null

    }

}

17.5.3、子接口 java.util.Deque

17.5.3.1、特点
  • Deque 表示双端队列( double ended queue)
17.5.3.2、实现类
  • java.util.LinkedList
17.5.3.3、抽象方法
  • boolean offerLast(E e)
  • E peekFirst()
  • E pollFirst()
  • void addLast(E e)
  • E getFirst()
  • E removeFirst()
  • boolean offerFirst(E e)
  • E peekLast()
  • E pollLast()
  • void addFirst(E e)
  • E getLast()
  • E removeLast()
  • void push(E e)
  • E peek()
  • E pop()
/**
 * 1、接口 java.util.Deque 是 java.util.Queue 接口的子接口
 * 2、java.util.Deque 表示双端队列 ( double ended queue )
 * 3、为了方便区分队列头和队列为,建议使用 队列对象的字符串形式 来区分
 *       比如: 
 *             以 "字符串形式" 左侧为头部,以 "字符串形式" 右侧为尾部
 *             以 "字符串形式" 右侧为头部,以 "字符串形式" 左侧为尾部
 * 4、如果 以 "字符串形式" 左侧为头部,以 "字符串形式" 右侧为尾部 则
 *          offerLast / peekFirst / pollFirst 分别表示 在队列尾部添加元素 、检查队列头部元素、移除并返回队列头部元素
 *          addLast / getFirst / removeFirst
 */
/**
 * 1、如果 以 "字符串形式" 右侧为头部,以 "字符串形式" 左侧为尾部 则
 *          offerFirst / peekLast / pollLast 分别表示 在队列尾部添加元素 、检查队列头部元素、移除并返回队列头部元素
 *          addFirst / getLast / removeLast
 */
// java.util.Deque 接口中直接定义了 "栈" 操作相关的方法:  push( E item ) / peek() / pop() 
        
        d.push( "武则天" ); // 将元素压入栈顶
        d.push( "李治" );
        d.push( "李世民" );
        d.push( "李渊" );
        
        System.out.println( d );
        
        System.out.println( d.peek() ) ; // 检查栈顶元素
        System.out.println( d );
        
        System.out.println( d.pop() ) ; // 弹出栈顶元素
        System.out.println( d );
        
        System.out.println( d.pop() ) ; // 弹出栈顶元素
        System.out.println( d );
        
        System.out.println( d.pop() ) ; // 弹出栈顶元素
        System.out.println( d );

17.6、Map

17.6.1、特点

  • 将键映射到值的对象
  • 一个映射不能包含重复的键
  • 每个键最多只能映射到一个值

17.6.2、内部接口

  • public static interface Entry

17.6.3、实现类

  • java.util.HashMap
  • java.util.HashTable

17.6.4、子接口

  • java.util.SortedMap
  • java.util.concurrent.ConcurrentMap

17.6.5、声明

1、声明 Map 变量时,可以分别指定 映射项 中 键 和 值 的类型,

​ 比如 Map< String , Integer > 中 String 是 键的类型,Integer 是 值的类型

17.6.6、常用方法

  • V put( K key , V value ) : 将 指定的值 ( value ) 与 Map 中的指定键 ( key ) 关联
  • V remove( K key ) : 根据 key 从 Map 中删除指定 映射项(键值对) 并返回该 映射项(键值对) 的值
  • boolean containsKey( Object key ): 如果此映射包含指定键的映射,则返回true
  • boolean containsValue( Object value ): 如果此映射将一个或多个键映射到指定的值,则返回true
  • V get( Object key) : 根据 key 获取 映射项 的值
  • void clear() : 清空集合

17.6.7、键集、值集、键-值映射关系集

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * 1、键集 : 所有的 映射项 的 键 组成的 Set 集合 ,通过 keySet() 方法获取
 * 2、值集 : 所有的 映射项 的 值 组成的 Collection 集合,通过 values() 方法来获取
 * 3、键-值映射关系集 : 就是 映射项 集合,所有的 映射项 组成的 Set 集合,通过 entrySet() 来获取
 * 4、本例中,对 三种 集合 都采用了迭代器来迭代
 */
public class MapTest3 {

    public static void main(String[] args) {

        Map<String, Integer> map = new HashMap<>();

        System.out.println("size : " + map.size() + " , is empty : " + map.isEmpty());

        map.put("藜蒿炒腊肉", 10);
        map.put("蒜苗回锅肉", 12);
        map.put("土豆片回锅肉", 12);
        map.put("毛血旺", 10);

        System.out.println(map); // { key = value , key = value , .... }

        // 获得 Map 中 所有 映射项 的 键 组成的 Set 集合
        Set<String> keySet = map.keySet(); // Collection<String> keySet = map.keySet();
        // Set 是 Collection 的子接口,所以可以将 Set 实例当做 Collection 来处理
        Iterator<String> iterator = keySet.iterator(); // 获取 Collection 的 迭代器
        // 使用迭代器迭代集合
        while (iterator.hasNext()) {
            String key = iterator.next();
            System.out.println( key ); // System.out.println( key + " : " + map.get( key ) );
        }

        System.out.println("~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~");

        Collection<Integer> values = map.values();
        Iterator<Integer> itor = values.iterator();
        // 使用迭代器迭代集合
        while (itor.hasNext()) {
            Integer value = itor.next();
            System.out.println(value);
        }
        
        System.out.println("~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~");
        
        // 从 Map 中获取 所有的 映射项 ( Map.Entry ) 组成的 Set 集合
        // 每个 映射项 就是一个 Map.Entry 实例,其中包括了 键 ( key ) 和 值 ( value ) 
        Set< Map.Entry< String , Integer > > entrySet = map.entrySet();
        // Set 集合中的元素类型是 Map.Entry 类型,对于 Map.Entry 来说,其 键的类型是 String ,值的类型是 Integer
        
        Iterator< Map.Entry< String , Integer > > it = entrySet.iterator();
        
        while( it.hasNext() ) {
            Map.Entry< String , Integer > entry = it.next() ;
            System.out.println( entry.getKey() + " : " + entry.getValue() );
        }

    }

}

输出结果:

size : 0 , is empty : true
{蒜苗回锅肉=12, 毛血旺=10, 土豆片回锅肉=12, 藜蒿炒腊肉=10}
蒜苗回锅肉
毛血旺
土豆片回锅肉
藜蒿炒腊肉
~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
12
10
12
10
~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
蒜苗回锅肉 : 12
毛血旺 : 10
土豆片回锅肉 : 12
藜蒿炒腊肉 : 10

17.6.8、理解哈希表

  • 如果某个数组中的所有元素 都可以 根据同一个的规则计算出其索引,也就是,向数组中添加新元素时,可以根据 元素 计算出 该元素 应该放在数组中哪个位置,则这种数组就是所谓的 "哈希表"
public static int hash( int value , int length ) {
        // 根据 某个值 和 数组 长度 计算 该值应该在数组的哪个位置存放
        return value % length ;
}

17.7、HashMap类

17.7.1、特点

  • 内部采用哈希表来存储数据

    • 当多个元素都位于同一个桶时采用链表存放
    • 从JDK1.8开始 采用 哈希表+二叉树 来存放数据
  • 允许存在 键(key) 为null的 映射项

  • 允许存在 值(vlaue)为null的 映射项

  • 不保证映射中映射项的顺序(特别是它不保证该顺序恒久不变)

  • 不支持排序

  • 不是线程安全的

17.7.2、父类

  • java.util.AbstractMap

17.7.3、接口

  • java.util.Map
  • java.lang.Cloneable
  • java.io.Serializable

17.7.4、内部类

static class Node<k,v> implements Map.Entry<k,v>{
    final int hash;
    final K key;
    V value;
    Node<k,v> next;
}
static final class TreeNode<k,v> extends LinkedHashMap.Entry<k,v>{
    TreeNode<k,v> parent;
    TreeNode<k,v> left;
    TreeNode<k,v> right;
    TreeNode<k,v> prev;
    boolean red;    
}

17.7.5、实例变量

  • transient Node[] table
  • transient int size
  • final float loadFactor

17.7.6、构造

  • public HashMap()
  • public HashMap( Map<? extends k,?extends V> m )
  • public HashMap(int initialCapacity)
  • public HashMap(int initialCapacity,float loadFactor)

17.7.7、实例方法

  • 继承了Map的方法

17.7.8、子类

  • java.util.LinkedHashMap

17.7.9、重写equals和hashCode方法

  • 理解HashMap中的put/remove/containsKey需要依赖于key的equals和hashCode方法
@Override
    public int hashCode() {
        /*
        final int prime = 31; // 为什么是 31 ?
        int result = 1;
        result = prime * result + ((birthdate == null) ? 0 : birthdate.hashCode());
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        result = prime * result + weight;
        return result;
        */
        return Objects.hash( birthdate , name , weight );
    }

    @Override
    public boolean equals(Object o ) {
        if( this == o ) {
            return true ;
        }       
        // null instanceof 任意类型 都返回 false
        if( o instanceof Panda ) { // 参数传入的 o 可能是 Panda 的子类类型的实例
            // 判断两者是否是同一个类的实例
            if( this.getClass() == o.getClass() ) {
                Panda p = ( Panda ) o ;
                return name.equals( p.name ) && birthdate.equals( p.birthdate ) && weight == p.weight ;
            }
        }       
        return false ;
    }

17.8、HashTable类

17.8.1、特点

  • 内部采用哈希表来存储数据

    • 当多个元素都位于同一个桶时采用链表存放
    • 从JDK1.8开始 采用 哈希表+二叉树 来存放数据
  • 允许存在 键(key) 为null的 映射项

  • 允许存在 值(vlaue)为null的 映射项

  • 不保证映射中映射项的顺序

  • 不支持排序

  • 线程安全的

17.8.2、父类

  • java.util.Dictionary

17.8.3、接口

  • java.util.Map
  • java.lang.Cloneable
  • java.io.Serializable

17.8.4、子类

  • java.util.Properties

17.8.5、HashMap与HashTable的区别

1.(同步性)HashTable的方法是同步的,HashMap不能同步。

2.(继承的父类不同)HashTable是继承自Dictionary类,而HashMap是继承自AbstractMap类。不过它们都实现了同时实现了map、Cloneable(可复制)、Serializable(可序列化)这三个接口。

3.(对null key和null value的支持不同).HashTable不允许null值(key和value都不可以),HashMap允许使用null值(key和value)都可以。这样的键只有一个,可以有一个或多个键所对应的值为null。

4.(遍历方法不同HashTable使用Enumeration遍历,HashMap使用Iterator进行遍历。

5.(初始化和扩容方式不同)HashTable中hash数组初始化大小及扩容方式不同。

6.(计算hash值的方法不同)

Hashtable直接使用key对象的hashCode。hashCode是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值。然后再使用除留余数法来获得最终的位置。

7.(对外提供的接口不同 )Hashtable比HashMap多提供了elements() 和contains() 两个方法。

17.9、java.util.SortedMap

17.9.1、特点

  • 根据Map集合中映射项 的 键 来排序
  • 可排序
    • 自然顺序(java.lang.Comparable)
    • 比较器顺序(java.util.Comparator)

17.9.2、子接口

  • java.util.NavigableMap
    • 提供了针对给定搜索目标返回最接近匹配项的导航方法
    • 实现类:java.util.TreeMap

17.9.3、抽象方法

  • K firstKey() //获取 Map 中第一个 key
  • K lastKey() //获取 Map 中最后一个 key
  • SortedMap headMap( K toKey ) //用于截取 从头开始 到 toKey 前的 "子集"
  • SortedMap tailMap( K fromKey ) //用于截取 从 fromKey 开始 到 末尾 的 "子集"
  • SortedMap subMap(K fromKey , K toKey ) //截取从 fromKey 到 toKey 之前的 "子集"
import java.util.SortedMap;
import java.util.TreeMap;

/**
 * 1、SortedMap 表示一种整体上排好顺序的 映射项 集合
 * 2、SortedMap 中提供了以 有顺序存放的 映射项为基础的操作方法
 *          firstKey() / lastKey() / headMap( toKey ) / tailMap( fromKey ) / subMap( fromKey , toKey )
 */
public class SortedMapTest {

    public static void main(String[] args) {
        
        SortedMap<Integer,String> map = new TreeMap<>();
        System.out.println( map );
        
        map.put( 100 ,  "百" );
        System.out.println( map );
        
        map.put( 10 ,  "十" );
        System.out.println( map );
        
        map.put( 50 ,  "半百" );
        System.out.println( map );
        
        map.put( 1000 ,  "千" );
        System.out.println( map );
        
        map.put( 0 ,  "个" );
        System.out.println( map );
        
        System.out.println( map.firstKey() ); // SortedMap 中 firstKey 获取 Map 中第一个 key
        System.out.println( map.lastKey() ); // SortedMap 中 firstKey 获取 Map 中最后一个 key
        
        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );
        
        SortedMap<Integer,String> hm = map.headMap( 100 ); // headMap( toKey ) 用于截取 从头开始 到 toKey 前的 "子集"
        System.out.println( hm );
        
        SortedMap<Integer,String> tm = map.tailMap( 50 ); // tailMap( fromKey ) 用于截取 从 fromKey 开始 到 末尾 的 "子集"
        System.out.println( tm );
        
        SortedMap<Integer,String> sm = map.subMap( 10 ,  100 ); // 截取从 fromKey 到 toKey 之前的 "子集"
        System.out.println( sm );
        
    }

}

输出结果:

{}
{100=百}
{10=十, 100=百}
{10=十, 50=半百, 100=百}
{10=十, 50=半百, 100=百, 1000=千}
{0=个, 10=十, 50=半百, 100=百, 1000=千}
0
1000
~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
{0=个, 10=十, 50=半百}
{50=半百, 100=百, 1000=千}
{10=十, 50=半百}

17.10、java.util.TreeMap

17.10.1、特点

  • 内部基于 红黑树(Red-Black tree)存储 映射项
  • 不允许存在键(key)为null的映射项
  • 支持排序
    • 自然排序
    • 比较器排序
  • 不是线程安全的

17.10.2、接口

  • java.util.NavigableMap
  • java.lang.Cloneable
  • java.io.Serializable

17.10.3、父类

  • java.util.AbstractMap

17.10.4、构造方法

  • public TreeMap()
  • public TreeMap( Map<? extends K,? extends V> m)
  • public TreeMap( SortedMap m)
  • public TreeMap( Comparator<? super K> comparator)

17.10.5、使用比较器对映射项提供排序支持

import java.time.LocalDate;
import java.util.Comparator;
import java.util.TreeMap;

public class TreeMapTest3 {

    public static void main(String[] args) {
        
        // 使用 匿名类 实现 Comparator 接口并实现其中的 compare 方法
        Comparator<Rabbit> comparator = new Comparator<Rabbit>() { 
            // 在 Java 8 中仍然需要书写为 new Comparator<Rabbit>() { ...... }
            // 从 Java 9 开始可以书写为 new Comparator<>() { ...... }
            @Override
            public int compare( Rabbit first , Rabbit second ) {
                LocalDate fd = null ; // LocalDate firstDate = null ;
                LocalDate sd = null ;// LocalDate secondDate = null ;
                // 如果 first 大于 、等于 、 小于 second 则 返回 大于零 、零 、小于零 的整数
                if( first != null && second != null && ( fd = first.getBirthdate() ) != null && ( sd = second.getBirthdate() ) != null ) {
                    // 这里仍然是借助于 java.time.LocalDate 实例的 compareTo 方法实现比较
                    return fd.compareTo( sd );
                }
                return 0 ;
            }
        } ;
        
        TreeMap<Rabbit,String> ts = new TreeMap<>( comparator );
        
        Rabbit r = new Rabbit( "玉兔" ,  LocalDate.of( 1996 , 10 , 10 ) );
        System.out.println( r );
        ts.put( r , "月宫捣药" );
        
        Rabbit a = new Rabbit( "流氓兔" ,  LocalDate.of( 1990 , 6 , 6 ) );
        System.out.println( a );
        ts.put( a ,  "流氓" );
        
        System.out.println( ts );

    }

}

输出结果:

( name='玉兔' , birthdate='1996-10-10' )
( name='流氓兔' , birthdate='1990-06-06' )
{( name='流氓兔' , birthdate='1990-06-06' )=流氓, ( name='玉兔' , birthdate='1996-10-10' )=月宫捣药}

17.11、java.util.concurrent.ConcurrentMap

17.11.1、实现类

  • java.util.concurrent.ConcurrentHashMap
    • 多线程并发时可以考虑使用

17.12、如何选择集合框架

1. 到底使用那种集合。

看需求

是否是键值对象形式:

  是:Map
    键是否需要排序:
      是:TreeMap
      否:HashMap
    不知道,就使用HashMap。

  否:Collection
    元素是否唯一:
        是:Set
           元素是否需要排序:
             是:TreeSet
            否:HashSet
         不知道,就使用HashSet

        否:List
            要安全吗:
              是:Vector(基本不用)
            否:ArrayList或者LinkedList
                 增删多:LinkedList
                 查询多:ArrayList
             不知道,就使用ArrayList
不知道,就使用ArrayList

18、垃圾回收机制

1、在 Windows 命令提示符 或 Linux 终端 中输入 jconsole 可以启动 "Java监视和管理控制台"

2、在 "Java监视和管理控制台" 中 可以连接指定的 JVM 进程,从而了解 JVM 的运行状况

18.1、开辟内存

  • 启动一个Java程序就会启动一个JVM进程
  • 在Java程序中需要开辟内存空间时,需要向JVM申请
  • 当JVM预先划分的内存尚未使用完时,就直接从JVM所预先划分的内存中开辟内存空间
  • 如果JVM申请的内存达到上限,则会通知Java程序内存溢出
    • 溢出 是指“容器”不足以容纳足够多的数据
    • 内存溢出 与 内存泄漏 不是一个概念

18.2、垃圾回收

  • 当JVM管理的内存中存在“无用”的对象时,该对象占用的内存应该被回收
  • 如果“无用”的对象一直不被回收,则该对象会一致驻留内存。导致该内存区域无法被重复使用
  • JVM中提供了专门用于回收“无用”对象 所占用内存的 线程,被称作 垃圾回收线程(也被称作垃圾回收器)
  • 垃圾回收器 将“无用”对象所占用的内存释放出来的过程,就是所谓的 垃圾回收
    • 垃圾回收的本质是在回收内存
  • 如果因为某些原因导致无法回收“无用”对象所占用的内存,则会导致 内存泄漏
  • 程序员可以在Java程序中向JVM建议 执行垃圾回收 操作
    • 程序员只有建议权,没有让GC立即执行的权限
    • 通过Runtime.getRuntime().gc() 实现
    • 通过System.gc() 实现

18.3、回收依据

  • 当某个对象不再被 任意一个引用变量 所引用时,该对象所占用的内存就可以被回收了
  • 当某个类不再被使用时,该类就可以卸载了,它所占用的内存就可以被回收了

18.4、Runtime

18.4.1、作用

  • 每个Java应用程序都有一个Runtime类实例
  • 通过Runtime类的实例使应用程序能够与其运行的环境相连接
  • Java应用程序中不能创建自己的Runtime类实例

18.4.2、类方法

  • public static Runtime getRuntime()

18.4.3、实例方法

  • public int avaliableProcessors() // 注意是 "虚拟处理器" 个数
  • public long totalMemory() // 返回 Java 虚拟机中的内存总量
  • public long freeMemory() // 返回 Java 虚拟机中的空闲内存量
  • public long maxMemory() // 返回 Java 虚拟机试图使用的最大内存量
  • public void exit(int status)
    • 根据惯例,非零的状态码表示非正常终止
  • public Process exec(String command) throws IOException //使用 Runtime 实例的 exec 放可以执行指定的命令

测试:

/**
 * 1、获取 java.lang.Runtime 类的实例并测试 availableProcessors / totalMemory / freeMemory / maxMemory
 */
public class GarbageCollectionTest2 {

    public static void main(String[] args) {
        
        // 获取 与 当前 Java 应用程序相关的 运行时 ( Runtime ) 对象
        Runtime runtime = Runtime.getRuntime();
        
        // availableProcessors 方法用于 向 Java 虚拟机返回可用处理器的数目
        int p = runtime.availableProcessors(); // 注意是 "虚拟处理器" 个数
        System.out.println( p );
        
        long total = runtime.totalMemory();// 返回 Java 虚拟机中的内存总量
        long max = runtime.maxMemory() ; // 返回 Java 虚拟机试图使用的最大内存量
        long free = runtime.freeMemory() ; // 返回 Java 虚拟机中的空闲内存量
        System.out.println( total + "Bytes , " + max + "Bytes , " + free + "Bytes");
        
        Runtime rt = Runtime.getRuntime();
        System.out.println( runtime == rt ); // true
        
    }

}

输出结果:

8
128974848Bytes , 1881145344Bytes , 126248232Bytes
true
/**
 * 1、使用 Runtime 实例的 exit 方法可以导致 JVM 退出
 * 2、根据惯例,非零的状态码表示非正常终止
 */
public class GarbageCollectionTest3 {

    public static void main(String[] args) {
        
        System.out.println( "main begin" );
        
        // 获取 与 当前 Java 应用程序相关的 运行时 ( Runtime ) 对象
        Runtime runtime = Runtime.getRuntime();
        
        // 通过启动虚拟机的关闭序列,终止当前正在运行的 Java 虚拟机
        runtime.exit( 0 ); // 根据惯例,非零的状态码表示非正常终止
        
        System.out.println( "main end" );
        
    }

}
import java.io.IOException;

/**
 * 1、使用 Runtime 实例的 exec 放可以执行指定的命令
 * 2、可以尝试使用 Java 程序 让自己的 Windows 系统 注销/关闭/重启
 */
public class GarbageCollectionTest4 {

    public static void main(String[] args) throws IOException {
        
        System.out.println( "main begin" );
        
        // 获取 与 当前 Java 应用程序相关的 运行时 ( Runtime ) 对象
        Runtime runtime = Runtime.getRuntime();
        
        //runtime.exec( "notepad" );// 在 Windows 环境下会执行 C:\Windows\notepad.exe 
        //runtime.exec( "mspaint" );// 在 Windows 环境下会执行 C:\Windows\System32\mspaint.exe 
        runtime.exec( "jconsole" );// 运行 JAVA_HOME 下 bin 目录中的 jconsole 命令
        
        System.out.println( "main end" );
        
    }

}

18.5、引用类型

18.5.1、强引用

  • 形式
    • List list = new ArrayList<>();
    • array[xxx] = "hello";
  • 回收
    • 当某个对象存在 强引用 时,垃圾回收器 不会回收该对象占用的内存
    • 即使 内存十分紧张或不够使用,也不会回收 强引用所关联的对象
  • 类型
    • 没有与之对应的类型

18.5.2、软引用

  • 形式
    • SoftReference> softRef = new SoftReference< new (ArrayList>() );
    • softRef.get();
  • 回收
    • 软引用适用于内存敏感的环境下
    • 当有充足的内存可供使用时,垃圾回收器 不会回收拥有 软引用的对象所占用的内存
    • 当内存紧张时,垃圾回收器 会回收 软引用关联的对象
  • 类型
    • java.lang.ref.SoftReference

18.5.3、弱引用

  • 形式
    • WeakReference> weakRef = new WeakReference< new (ArrayList>() );
    • weakRef.get();
  • 回收
    • 弱引用适用于内存敏感的环境下
    • 不论当前的内存是否足够使用,垃圾回收器 都会回收拥有 弱引用的对象所占用的内存
  • 类型
    • java.lang.ref.WeakReference

18.5.4、虚引用

  • 作用
    • 虚引用也称作幽灵引用(基本等同于没有引用)
    • 用于追踪垃圾回收的过程
    • 通常是结合 ReferenceQueue 使用
  • 类型
    • java.lang.ref.PhantomReference

19、异常处理

19.1、什么是 Java异常

异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的

比如说,你的代码少了一个分号,那么运行出来结果是提示是错误 java.lang.Error;如果你用 System.out.println(11/0),那么你是因为你用 0 做了除数,会抛出 java.lang.ArithmeticException 的异常

异常发生的原因有很多,通常包含以下几大类:

  • 用户输入了非法数据
  • 要打开的文件不存在
  • 网络通信时连接中断,或者JVM内存溢出

这些异常有的是因为用户错误引起,有的是程序错误引起的,还有其它一些是因为物理错误引起的。-

要理解 Java异常处理 是如何工作的,你需要掌握以下三种类型的异常:

  • 检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略
  • 运行时异常: 运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略
  • 错误: 错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的

19.2、异常的层次

java.lang.Throwable

  • java.lang.Exception
  • java.lang.Error
    • java.lang.RuntimeException
    • java.io.IOException

19.3、感受 Error

​ 通过 StackOverflowError 感受 Error

package cn.ecut.exception;

public class ErrorTest {
    
    public static void main(String[] args) {
        hello(); // StackOverflowError
    }
    
    public static void hello() {
        hello();
    }

}

19.4、创建 Exception

​ 理解异常可以由 程序主动创建 也可以由 JVM隐式 创建

19.4.1、程序主动 创建异常

package cn.ecut.exception;

/**
 * 由 程序 创建的异常对象
 */

public class ExceptionHandlingTest2 {
    
    public static void main(String[] args) {
        
        int a = 100 ;
        int b = 0 ;
        if( b == 0 ) {
            // 主动创建 异常对象 ( 即 创建 Exception 类的实例 或 其子类类型的实例 )
            RuntimeException e = new RuntimeException( "除数不能为零" );
            // 抛出异常对象
            throw e ; // throw 用于抛出 异常实例
        }
        int c = a / b ;
        System.out.println( a + " / " + b + " = " + c  );
        
    }

}

19.4.2、JVM隐式 创建异常

package cn.ecut.exception;

/**
 * 由 JVM 创建的异常对象
 */

public class ExceptionHandlingTest1 {
    
    public static void main(String[] args) {
        
        int a = 100 ;
        int b = 0 ;
        int c = a / b ; // java.lang.ArithmeticException
        System.out.println( a + " / " + b + " = " + c  );
        
    }

}

19.5、测试 try catch finally

19.5.1、try{} catch{} catch{}

package cn.ecut.exception;

public class ExceptionHandlingTest3 {
    
    public static void main(String[] args) {
        
        System.out.println( "main begin ..." ); 
        
        int a = 100 ;
        int b = 0 ;
        
        try {
            int c = a / b ; // 至此会触发异常,JVM创建表示该异常的对象,将该对象交给运行时系统处理
            System.out.println( a + " / " + b + " = " + c  );
            
            // 运行时系统 会寻找 try 之后的 catch 语句块,
            // 如果 遇到 与异常对象的类型 匹配的语句块,则将该异常对象 传递 给该 catch 语句块
        } catch( NullPointerException e ) { // 注意,catch 后的 ( ) 中指定的实际上是个 形参
            System.out.println( e ); // 输出异常对象的字符串形式
            System.out.println( e.getClass() ); // 运行时类型【java.lang.ArithmeticException: / by zero】
        } catch( ArithmeticException e ) {
            System.out.println( e ); // 输出异常对象的字符串形式
            System.out.println( "message : " + e.getMessage() ); // 【message : / by zero】
            System.out.println( "cause : " + e.getCause() ); // 【cause : null】
            System.out.println( e.getClass() ); // 运行时类型 【class java.lang.ArithmeticException】
        }
        
        System.out.println( "main end ." );
    }

}

19.5.2、try{} catch{} finally{}

package cn.ecut.exception;

public class ExceptionTest4 {
    
    public static void main(String[] args) {
        
        System.out.println( "main begin ..." );
        
        int a = 100 ;
        int b = 10 ;
        
        try {
            System.out.println( "- - - try - - -" );
            int c = a / b ; // 至此会触发异常,JVM创建表示该异常的对象,将该对象交给运行时系统处理
            System.out.println( a + " / " + b + " = " + c  ); // 100 / 10 = 10
            
            // 运行时系统 会寻找 try 之后的 catch 语句块,
            // 如果 遇到 与异常对象的类型 匹配的语句块,则将该异常对象 传递 给该 catch 语句块
        } catch( Exception e ) {
            System.out.println( "- - - catch - - -" );
            System.out.println( e ); // 输出异常对象的字符串形式
        } finally {
            System.out.println( "- - - finally - - -" );
        }
        
        System.out.println( "main end ." );
        
    }

}

19.5.3、try{} finally{}

package cn.ecut.exception;

public class ExceptionHandlingTest4 {
    
    public static void main(String[] args) {
        
        System.out.println( "main begin ..." );
        
        Integer a = 100 ;
        // Integer b = null ;
        Integer b = 0 ;
        
        try {
            System.out.println( "- - - try - - -" );
            Integer c = a / b ; // 至此会触发异常,JVM创建表示该异常的对象,将该对象交给运行时系统处理
            System.out.println( a + " / " + b + " = " + c  );
        } catch( NullPointerException e ) { 
            System.out.println( "空指针异常 : " + e.getMessage() );
            e.printStackTrace(); // 打印 栈 轨迹
        } catch( ArithmeticException e ) { 
            System.out.println( "算术异常 : " + e.getMessage() ); // 算术异常 : / by zero 【java.lang.ArithmeticException: / by zero】 
            e.printStackTrace(); // 打印 栈 轨迹
        }  finally {
            System.out.println( "- - - finally - - -" );
        }
        
        System.out.println( "main end ." );
        
    }

}

测试 JDK 1.7 开始提供的 catch 支持

package cn.ecut.exception;

public class ExceptionHandlingTest6 {
    
    public static void main(String[] args) {
        
    System.out.println( "main begin ..." );
        
        Integer a = 100 ;
        // Integer b = null ;
        Integer b = 0 ;
        
        try {
            System.out.println( "- - - try - - -" );
            Integer c = a / b ; // 至此会触发异常,JVM创建表示该异常的对象,将该对象交给运行时系统处理
            System.out.println( a + " / " + b + " = " + c  );
        // 从 JDK 1.7 开始 允许在 catch 之后的 ( ) 中指定多个异常类型 ( 它们之间使用 | 隔开 ) 
        } catch( NullPointerException | ArithmeticException e ) { 
            System.out.println( e.getMessage() ); // 打印异常信息
            if( e instanceof NullPointerException ) {
                System.out.println( "空指针异常" );
            }
            if( e instanceof ArithmeticException ) {
                System.out.println( "算术异常" );
            }
            e.printStackTrace(); // 打印异常堆栈
        }  finally {
            System.out.println( "- - - finally - - -" );
        }
        
        System.out.println( "main end ." );
        
    }

}

19.5.4、执行顺序

​ 当 return 遇到 finally 时,仍然是 finally 先执行后再执行 return

package cn.ecut.exception;

public class ExceptionHandlingTestC {
    
    public static void main(String[] args) {
        
        System.out.println( "main begin ..." );
        int x = test2();
        System.out.println( x ); // 101 [是 finally 先执行后再执行 return]
        System.out.println( "main end ." );
        
    }
    
    @SuppressWarnings("finally")
    public static int test1() {
        try {
            System.out.println( 100 / 0 );
            System.out.println( "- - - try - - -" );
            return 1  ; // try
        } catch( Exception cause ) {
            System.out.println( "- - - catch - - -" );
            return -1 ; // catch
        } finally {
            System.out.println( "- - - finally - - -" );
            return 0 ; // finally
        }
    }
    
    @SuppressWarnings("finally")
    public static int test2(){
        int a = 100 ;
        try {
            System.out.println( "- - - try - - -" );
            return a + 11 ;
        } catch( Exception cause ) {
            System.out.println( "- - - catch - - -" );
            return a - 10 ;
        } finally {
            System.out.println( "- - - finally - - -" );
            return ++a ;
        }
    }

}

19.6、声明异常

在方法的声明中使用 throws 关键字声明抛出运行时异常

package cn.ecut.exception;

public class ExceptionHandlingTest7 {
    
    public static void main(String[] args) {
        
        Integer  a = 100 ;
        Integer b = 0 ;
        
        Integer r = divide​( a , b );
        System.out.println( r );
        
    }
    
    /**
     * 实现 除法运算
     * @param dividend 被除数
     * @param divisor 除数
     * @return 返回 第一个参数 除以 第二个参数 的 商
     * @throws NullPointerException 声明方法可能抛出 空指针异常 ( NullPointerException )
     * @throws ArithmeticException 声明方法可能抛出 算术异常 ( ArithmeticException )
     */
    public static Integer divide​( Integer dividend , Integer divisor ) throws NullPointerException , ArithmeticException {
        Integer result = dividend / divisor ; // 商  =  被除数 / 除数 ;
        return result ;
    }

}

在方法的声明中使用 throws 关键字声明抛出【受检查异常】

package cn.ecut.exception;

/**
 * 实现 除法运算
 * @param dividend 被除数
 * @param divisor 除数
 * @return 返回 第一个参数 除以 第二个参数 的 商
 * @throws Exception 抛出一个 受检查异常 ( 受 编译器 检查 )
 */

public class ExceptionHandlingTest8 {
    
    public static Integer divide( Integer devidend , Integer divisor ) throws Exception {
        Integer result =  devidend / divisor ;  // 商  =  被除数 / 除数 ;
        return result ;
    }
    
    // 在 main 方法的声明中声明需要抛出的异常类型
    public static void main(String[] args) throws Exception {
        
        Integer a = 100 ;
        Integer b = 0 ;
        
        // 作为 divide 方法的调用者,
        // 要么 main 方法继续声明抛出 ( throws )
        // 要么在 main 方法内部把 divide 方法所抛出的异常捕获( try...catch )
        Integer r = divide(a, b);
        System.out.println( r );
        
    }

}
package cn.ecut.exception;

public class ExceptionHandlingTest9 {
    
    public static void main(String[] args) {
        
        Integer  a = 100 ;
        Integer b = 0 ;
        
        try {
            // 作为 divide 方法的调用者,
            // 要么 main 方法继续声明抛出 ( throws )
            // 要么在 main 方法内部把 divide 方法所抛出的异常捕获( try...catch ),
            Integer r = divide​( a , b );
            System.out.println( r );
        } catch (Exception e) {
            System.out.println( "出错了:" );
            e.printStackTrace();
        }
        
        System.out.println( "main end ." );

    }
    
    /**
     * 实现 除法运算
     * @param dividend 被除数
     * @param divisor 除数
     * @return 返回 第一个参数 除以 第二个参数 的 商
     * @throws Exception 抛出一个 受检查异常 ( 受 编译器 检查 )
     */
    public static Integer divide​( Integer dividend , Integer divisor ) throws Exception {
        Integer result = dividend / divisor ; // 商  =  被除数 / 除数 ;
        return result ; // 出错了:java.lang.ArithmeticException: / by zero
    }
        
}

19.7、抛出异常

package cn.ecut.exception;

import java.util.Arrays;

public class ExceptionHandlingTestA {
    
    static Object[] elements = new Object[ 10 ];

    public static void main(String[] args) {
        
        add( 2 , "二五零" );
        show();
        add( 250 , 250 );
        show(); // [null, null, 二五零, null, null, null, null, null, null, null] 【Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 索引必须是 [ 0 , 10 ) 之间】

    }
    
    public static void add( int index , Object element ) {
        if( index < 0 || index >= elements.length ) {
            // 指定异常实例中封装的 详细信息 ( 关于异常的描述信息 )
            String message = "索引必须是 [ 0 , " + elements.length + " ) 之间" ;
            // 创建异常实例
            ArrayIndexOutOfBoundsException e = new ArrayIndexOutOfBoundsException( message );
            // 注意,与 throws 关键字不同,throw 关键字之后跟的是 单个异常实例
            throw e ; // 抛出异常实例
        }
        elements[ index ] = element ;
    }
    
    public static void show() {
        System.out.println( Arrays.toString( elements ) );
    }

}

**在方法中使用自定义 异常类型 / 异常转译 **

1、通过 直接 或 间接 继承 RuntimeException 类来定义 运行时异常 类型

2、通过 直接 或 间接 继承 Exception 类来定义 受检查异常 类型 ( 不能继承 RuntimeException 类 )

3、建议 参照 父类中的构造方法 生成 自定义异常类型 的构造方法

package cn.ecut.exception;

public class SuanShuException extends RuntimeException  {
    
    public SuanShuException() {
        super();
    }

    public SuanShuException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }

    public SuanShuException(String message, Throwable cause) {
        super(message, cause);
    }

    public SuanShuException(String message) {
        super(message);
    }

    public SuanShuException(Throwable cause) {
        super(cause);
    }

}
package cn.ecut.exception;

public class ExceptionHandlingTestB {
    
    public static void main(String[] args) {
        System.out.println( "main begin ..." );
        Integer  a = 100 ;
        Integer b = 0 ;
        divide​( a ,  b );
        System.out.println( "main end ." );
    }
    
    /**
     * 实现 除法运算
     * @param dividend 被除数
     * @param divisor 除数
     * @return 返回 第一个参数 除以 第二个参数 的 商
     * @throws Exception 抛出一个 受检查异常 ( 受 编译器 检查 )
     */
    public static Integer divide​( Integer dividend , Integer divisor ){
        Integer result = null ;
        try {
            result = dividend / divisor ; // 商  =  被除数 / 除数 ;
        } catch( NullPointerException | ArithmeticException cause ) {
            String message = "执行除法运算时发生错误" ;
            //【异常转译】以已经捕获到的异常为原因创建另外一个异常实例
            SuanShuException sse = new SuanShuException( message , cause ); // 创建新的异常实例
            throw sse ; // 抛出异常实例
        }
        return result ;
    }

}

20、Decimal

1、使用 浮点数 完成的 算术运算 都是不精确的

public class DecimalTest1 {

    public static void main(String[] args) {
        
        double a = 3.1 ;
        double b = 2.7 ;
        
        double c = a + b ;
        System.out.println( c ); //5.800000000000001
        
        double d = a - b ;
        System.out.println( d ); //0.3999999999999999
        
        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );
        
        System.out.println( Double.MAX_VALUE ); //1.7976931348623157E308
        
        double x = 1.8 * Math.pow( 10 , 308 ); // 超出 基本数据类型所表示的数值的 最大范围
        System.out.println( x ); //Infinity
        
        double y = 1.8 * Math.pow( 10 , 1000 ); // 超出 基本数据类型所表示的数值的 最大范围
        System.out.println( y ); //Infinity

    }

}
  • 借助于 java.math.BigDecimal 类中提供的方法可以实现 精确计算

  • 通过 java.math.BigDecimal 类提供的方法可以完成 "大数据" 的计算

20.1、java.math.BigInteger

20.1.1、作用

  • BigInteger 的实例表示不可变的、任意精度的整数

20.1.2、类方法

  • public static BigInteger valueOf( long value )
  • public static BigInteger probablePrime( int bitLength,Random r )

20.1.3、实例方法

  • public BigInteger add( BigInteger value )
  • public BigInteger subtract( BigInteger value )
  • public BigInteger multiply( BigInteger value )
  • public BigInteger divide( BigInteger value )
  • public BigInteger pow( int exponent )
  • public BigInteger and( BigInteger value )
  • public BigInteger or( BigInteger value )
  • public BigInteger xor( BigInteger value )
  • public BigInteger not()
  • public BigInteger shiftLeft( int n )
  • public BigInteger shiftRight( int n )
  • public byte[] toByteArray()

20.1.4、父类

  • java.lang.Number

20.1.5、测试

  • 将一个 十六进制的、字符串形式的 整数解析为 数字后创建与之相应的 BigInteger 实例
  • BigInteger 的 toByteArray 返回一个包含了当前的 BigInteger 的二进制补码表示形式的 byte 数组
import java.math.BigInteger;
import java.util.Arrays;

public class BigIntegerTest1 {

    public static void main(String[] args) {
        
        String value = "8fa3";
        int radix = 16 ;
        
        BigInteger b = new BigInteger( value , radix );
        System.out.println( b ); // b.toString()
        
        // 返回一个 byte 数组,该数组包含此 BigInteger 的二进制补码表示形式
        byte[] bytes = b.toByteArray();
        System.out.println( Arrays.toString( bytes ) );
        
        for( int i = 0 ; i < bytes.length ; i++ ) {
            byte t = bytes[ i ];
            String s = Integer.toBinaryString( t );
            if( s.length() > 24 ) {
                s = s.substring( 24 );
            }
            System.out.println( s );
        }
        
        int y = 0b0000_0000_0000_0000_1000_1111_1010_0011;
        System.out.println( y );

    }

}

输出结果:

36771
[0, -113, -93]
0
10001111
10100011
36771
  • 使用 BigInteger( byte[] ) 根据一个 byte 数组构造一个与之对应的 BigInteger 实例
import java.math.BigInteger;

/**
 * 使用 BigInteger( byte[] ) 根据一个 byte 数组构造一个与之对应的  BigInteger 实例
 */
public class BigIntegerTest2 {

    public static void main(String[] args) {
        
        byte[] bytes = { -1 , 98 , -113 , 99 };
        
        String x = "" ;
        for( int i = 0 ; i < bytes.length ; i++ ) {
            byte t = bytes[ i ];
            String s = Integer.toBinaryString( t );
            if( t > 0 ) {
                // 正数 补够8位
                for( int j = 8 , n = s.length() ; j > n ; j-- ) {
                    s = "0" + s ;
                }
            } else if( t == 0 ){
                s = "00000000";
            } else {
                // 负数 截取 低8位
                if( s.length() > 24 ) {
                    s = s.substring( 24 );
                }
            }
            System.out.println( s );
            x += s ;// x = x + s ;
        }
        
        System.out.println( x );
        
        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );
        
        System.out.println( 0b11111111_01100010_10001111_01100011 ); // -10317981
        
        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );
        
        BigInteger h = new BigInteger( bytes ) ;
        System.out.println( h );
        
    }

}

输出结果:

11111111
01100010
10001111
01100011
11111111011000101000111101100011
~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
-10317981
~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
-10317981
  • 理解BigInteger实例是不可变的
import java.math.BigInteger;

/**
 */
public class BigIntegerTest3 {

    public static void main(String[] args) {
        
        final byte[] bytes = { -1 , 99 };
        
        BigInteger x = new BigInteger( "9527" ); // 按照十进制解析字符串
        System.out.println( x );//9527
        
        BigInteger y = new BigInteger( bytes );
        System.out.println( y );//-157
        
        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );
        
        BigInteger z = x.add( y );
        
        System.out.println( "z = " + z );//z = 9370
        System.out.println( "x = " + x );//x = 9527
        
        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );
        
        BigInteger a = x.not(); //返回其值为 (~this) 的 BigInteger
        System.out.println( "a = " + a );//a = -9528
        System.out.println( "x = " + x );//x = 9527
        
        BigInteger b = x.negate();
        System.out.println( "b = " + b );//返回其值为 (-this) 的 BigInteger b = -9527
        System.out.println( "x = " + x );//x = 9527
        
        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" );
        
        BigInteger c = x.shiftLeft( 1 ); // x << 1 
        System.out.println( "c = " + c + " , x = " + x );//c = 19054 , x = 9527
        
        BigInteger d = y.shiftLeft( 1 ); // y >> 1
        System.out.println( "d = " + d + " , y = " + y );//d = -314 , y = -157

    }

}

20.2、java.math.RoundingMode

20.2.1、作用

  • 为可能丢弃精度的数值操作指定一种舍入行为
  • 每种舍入模式都指示如何计算返回舍入结果位数的最低有效位

20.2.2、枚举常量

  • public static final RoundingMode HALF_UP
  • public static final RoundingMode HALF_DOWN
  • ......

20.3、java.math.MathContext

20.3.1、作用

  • MathContext 实例用来封装 上下文设置 的不可变对象
  • 通过 上下文设置 来描述 数字运算符 的某些规则

20.3.2、设计

  • precision(精度)
  • roundingMode(舍入模式)

20.3.3、构造方法

  • public MathContext( int precision )
  • public MathContext( int precision ,RoundingMode roundingMode )
  • public MathContext( String value )

20.3.4、实例方法

  • public int getPrecision()
  • public RoundingMode getRoundingMode()

20.4、Java.mathBigDecimal

20.4.1、作用

  • BigDecimal 实例 表示不可变的、任意精度的、有符号的十进制数
  • BigDeciaml 实例 由 任意精度的整数非标度值 和 32位的整数标度(scale)组成

20.4.2、常量

  • public static final BigDecimal ZERO ( 值为0,标度为0 )
  • public static final BigDecimal ONE ( 值为1,标度为0 )
  • public static final BigDecimal TEN ( 值为10,标度为0 )

20.4.3、构造方法

  • public BigDecimal( int|long |double value )
  • public BigDecimal( int|long |double value ,MathContext mc )
  • public BigDecimal( String value )
  • public BigDecimal( String value, MathContext mc )
  • public BigDecimal( BigInteger unscaleValue, int scale )
  • public BigDecimal( BigInteger unscaleValue, int scale ,MathContext mc )
  • 其它构造方法查阅API file:///H:/Utility/docs/api/java.base/java/math/BigDecimal.html

测试:

import java.math.BigDecimal;
import java.math.BigInteger;

public class BigDecimalTest1 {

    public static void main(String[] args) {
        
        BigDecimal a = new BigDecimal( 100 ); // int
        System.out.println( a ); //100
        
        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~" );
        
        BigDecimal b = new BigDecimal( "200300400500600700800900" ); // String
        System.out.println( b ); //200300400500600700800900
        
        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~" );
        
        BigDecimal c = new BigDecimal( 3.14 ); // double
        System.out.println( c ); //3.140000000000000124344978758017532527446746826171875
        
        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~" );

        BigDecimal d = new BigDecimal( "3.14" ); // String
        System.out.println( d ); //3.14
        
        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~" );
        
        BigInteger unscaledValue = new BigInteger( "FFFFFFFFFFFFFFFF" , 16 );
        int scale = -1000 ;
        
        BigDecimal f = new BigDecimal( unscaledValue , scale ); // ( BigInteger , int )
        System.out.println( f ); //1.8446744073709551615E+1019
        
        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~" );

    }

}

20.4.4、类方法

  • public static BigDecimal valueOf( long value )
  • public static BigDecimal valueOf( double value )
  • public static BigDecimal valueOf( long unscaleValue , int scale )

测试:

import java.math.BigDecimal;
import java.math.BigInteger;

public class BigDecimalTest2 {

    public static void main(String[] args) {
        
        BigDecimal a = BigDecimal.valueOf( 100200300 ); // ( long )
        System.out.println( a ); //100200300
        
        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~" );
        
        BigDecimal b = BigDecimal.valueOf( 3.14 ); // ( double )
        System.out.println( b ); //3.14
        
        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~" );
        
        BigDecimal c = BigDecimal.valueOf( 3.14159265358979323846 ); // ( double )
        System.out.println( c ); //3.141592653589793
        
        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~" );
        
        final long uv = 3141592653589793238L ;
        final int s = 18 ;
        BigDecimal d = BigDecimal.valueOf( uv , s ); // ( long , int )
        System.out.println( d ); //3.141592653589793238
        
        System.out.println( "~ ~ ~ ~ ~ ~ ~ ~" );
        
        final String value = "3141592653589793238462643383279";
        // 采用 BigInteger 实例所表示的不可变的任意精度的整数 来充当 非标度值 ( unscaledValue )
        final BigInteger unscaledValue = new BigInteger( value );
        // 根据实际需要来指定 标度 ( scale ) 值
        final int scale = value.length() - 1 ; // 因为为圆周率的小数点之前只有一位数字
        
        // 根据 非标度值 和 标度值 来创建一个 BigDecimal 实例,用来表示 不可变的、任意进度的十进制数
        BigDecimal f = new BigDecimal( unscaledValue , scale ); // ( BigInteger , int )
        System.out.println( f ); //3.141592653589793238462643383279

    }

}

20.4.5、实例方法

  • public BigDecimal divide( BigDecimal divisor )
  • public BigDecimal divide( BigDecimal divisor ,int scale ,RoundingMode mode)
  • public BigDecimal divide( BigDecimal divisor ,MathContext mc )
  • public BigDecimal negate()
  • public BigDecimal negate( MathContext mc)
  • public BigDecimal plus()
  • public BigDecimal plus( MathContext mc)
  • public BigDecimal round( MathContext mc)
  • public BigDeciaml movePointLeft( int n )
  • public BigDecimal movePointRight( int n )

20.4.6、父类

  • java.lang.Number

标签


© 2021 成都云创动力科技有限公司 蜀ICP备20006351号-1