Java基础

第一章

跨平台性

  • 什么是跨平台性?

    通过Java 语言编写的应用程序在不同的系统平台上都可以运行

  • 原理是什么?

    java 程序的跨平台主要是指字节码文件可以在任何具有JVM 的计算机和电子设备上运 行,Java 虚拟机中的java 解释器负责将字节码文件解释成特定的机器码进行运行。

    Java 语言本质上是不能跨平台的,真正实现跨平台的是JVM,也就是Java 虚拟机。写好的Java 源文件通过Javac 命令编译生成class 文件(中间文件),然后JVM class 文件进行执行生成机器语言然后机器语言在平台中操作,Java 在不同的平台下都有对应的不同版本的JVMJVM 可以识别字节码文件从而运行。

    跨平台性
  • JDK、JRE、JVM 之间的关系

    • JDK【Java Development Kit】: 是Java 程序开发工具包,包含JRE 和开发人员使用的工具,例如java.exe 编译器javadoc.exe可以自动为 java的代码生成说明文档

    • JRE【Java Runtime Environment】:是 Java 程序的运行时环境,包含JVM 和运行时所需要的核心类库

    • JVM【Java Virtual Machine】: Java 虚拟机,是运行所有Java 程序的假象计算机,Java 程序的运行环境,Java 最具有吸引力的特性之一,我们编写的Java 代码,都运行在 JVM 之上。

    JDK 中包含JRE 和 开发工具集

    JRE 中包含JVM JavaSE 标准类库

    • 关系图

      关系图

JDK 安装

  • 进入官网下载

    https://www.oracle.com/index.html

    下载流程
  • 安装

    • 注意文件路径不要出现中文、特殊符号,空格等
  • 环境变量配置

    • JAVA_HOME: JDK 软件安装的bin 目录上一级

      JAVA_HOME
    • CLASSPATH

      1
      .;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar; 
      CLASSPATH
    • PATH

      1
      2
      %JAVA_HOME%\bin
      %JAVA_HOME%\jre\bin
      PATH
    • 检测是否安装成功

      环境检测
  • 编写HelloWorld.java

    步骤 编译 - 运行
    1. 将 java代码编写到扩展为 .java 的文件中

    2. 通过 javac 命令对该java 文件进行编译

    3. 通过 Java 命令对生成的 class 文件进行运行

注释

  • 注释分类

    • 单行注释// 单行注释

    • 多行注释/* 多行注释 */

    • 文档注释

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      /** 
      文档注释
      @author: coder-itl
      @version: 1.0.0
      */

      // 文档生成指令: javadoc -d folder-name -author -version java-file-nale.java

      // 文档注释的作用: 注释内容可以被 `JDK` 提供的`javadoc` 所解析,生成一套以网页文件形式体现的该程序的说明文档.

DOC 命令

  • 常见的doc 命令

    1
    2
    3
    4
    5
    6
    7
    cd ..
    cd /
    md 创建目录
    rd 删除目录
    del 删除文件
    exit 退出 doc 命令行
    dir 列出当前目录下的所有文件
  • 制表符\t

    • 在打印的时候,把前面字符串的长度补齐到8,或者8 的整数倍。最少补1 个空格,最多补8 个空格。
  • 编码问题

    • CMD 默认GBK,可以将文件编码格式保存时设置为GBK
    • 运行时指定参数:javac -encoding utf-8 Hello.java

内存分配

  • Java 内存分配

    • 栈:方法运行时使用的内存,比如main 方法运行,进入方法栈中执行

    • 堆:存储对象或数组,new 来创建的,都存储在堆内存

    • 方法区: 存储可以运行的class 文件

    • 本地方法栈: JVM 在使用操作系统功能的时候,和我们无关的开发

    • 寄存器: 给CPU 使用,和我们开发无关

  • 内存关系

    内存关系
  • 内存分析

    (basketball) 引用(0x001)
  • 引用传递产生垃圾分析

    垃圾产生
    1
    2
    3
    4
    5
    6
    7
    8
    9
    A {
    int price = 10;
    }
    A a = new A();
    A b = new A();

    a = b; // 引用传递
    a.price = 1; // 修改内容

    在引用的关系中,没有任何栈内存指向堆内存空间就将成为垃圾,所有的垃圾空间会由GC(Garbage Collector、GC即垃圾收集器) 回收并进行内存的释放,但是频繁的GC 会引起严重的性能,垃圾空间必须进行严格的控制,尽量减少无用内存的开辟

第二章

计算机中的数据存储

  • 计算机的存储规则
    • 在计算机中,任意数据都是以二进制的形式来存储的
  • 二进制
    • 0 1 组成,代码中以0b 开头

关键字的定义和特点

  • 关键字的定义: 是被Java 语言赋予了特殊含义,用做专门用途的字符串【单词】
  • 关键字的特点: 关键字中所有的字母都是小写

标识符

标识符的概念
  • java 对各种变量、方法和类等要素命名时使用的字符串序列称为标识符
  • 识别技巧: 凡是自己可以命名的地方都叫标识符
定义合法标识符的规则

如果不遵守如下的规则,编译不通过!需要大家严格遵守

  • 26 个英文字母大小写,0-9,_$ 组成
  • 数字不可以开头
  • 不可以使用关键字和保留字,但能包含关键字和保留字
  • Java 中严格区分大小写,长度无限制
  • 标识符不能包含空格
Java中的命名规范
  • 包名: 多单词组成时所有字母都小写

  • 类名,接口名: 多单词组成时,所有单词首字母大写

  • 变量名、方法名: 多单词组成时,第一个单词首字母小写,第二个单词开始每个首字母大写

  • 常量名: 所有字母都大写,多单词时每个单词用下划线连接

    如果不遵守如上的规范,编译可以通过!但是建议大家遵守

变量

  • 变量的作用: 用于在内存中保留数据

  • 使用: 数据类型 变量名 = 变量值;

    注意: 变量使用必须先声明后使用,变量都定义在其作用域内,在作用域内,它是有效的,出了作用域就无效了,同一个作用域内,不可以声明两个同名的变量

数据类型

数据类型分类
  • 分类

    • Java 语言的数据类型分为: 基本数类型、引用数据类型

    • 基本数据类型又分为四类八种

      • 整数
        • byte 的取值范围: -128 ~ 127
      • 浮点数
      • 字符
      • 布尔

      整数和小数取值范围大小关系: double > float > long > int > short > byte

    数据类型分类
  • 值传递与引用传递

    • 基本数据类型: 所有的数据操作都是采用值传递,相当于把数据给对方了一个副本信息,副本信息的修改不影响原始的数据内容,这些基本数据类型也是构成程序开发的基本环节
    • 引用数据类型: 是进行内存的数据操作,给的内存使用权
  • 成员变量与局部变量

    方法体外,类体内声明的变量称为成员变量

    在方法体内部声明的变量称为局部变量

    成员变量与局部变量
数据类型之间的运算规则

前提: 这里只讨论7 种基本数据类型变量间的运算,不包含boolean 类型的

  • 自动类型提升

    结论: 当容量小的数据类型的变量与容量大的数据类型的变量做运算时,结果自动提升为容量大的数据类型

    byte、char、short–> int --> long --> float --> double

    特别的: 当byte、char、short 三种类型的变量做运算时结果为int

  • 强制数据类型转换【自动提升类型的逆运算

    • 需要使用强制符()
    • 注意点: 强制类型转化可能损失数据精度
  • 字符串类型

    • String 不是基本数据类型,属于引用数据类型,翻译为: 字符串
    • 声明String 类型的变量时,使用一对双引号 " "

键盘录入

  • Java 帮我们写好了一个类叫Scanner,这个类就可以接受键盘输入的数字

  • 使用步骤

    • 导包(找到 Scanner 这个类在哪)

    • 创建对象(表示我们要开始使用这个对象了)

    • 接受输入

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      package org.example;
      // 1. 导包
      import java.util.Scanner;

      public class ScannerDemo {
      public static void main(String[] args) {
      // 2. 创建对象
      Scanner scanner = new Scanner(System.in);
      // 3. 使用 -> 接受数据
      int age = scanner.nextInt();
      System.out.println("我的年龄是: " + age);
      }
      }

算数运算符

运算符概念
  • 运算符是对字面量或者变量进行操作的符号
表达式
  • 运算符把字面量或者变量连接起来,符合Java 语法的式子就可以称为表达式。不同运算符连接的表达式体现的是不同类型的表达式。

  • Eg:

    1
    2
    3
    4
    5
    6
    public static void main(String[] args) {
    int a = 10;
    int b = 20;
    // +: 运算符,并且是算数运算符, a + b: 是表达式,由于 + 是算数运算符,所以这个表达式叫算数表达式
    int c = a + b;
    }
运算符分类
  • 算数运算符: +-x/%

    • %(取模、取余数)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      // 符合小学数据运算规则: + - *
      public static void main(String[] args) {
      int a = 30;
      int b = 10;
      int sum = a + b;
      int dif = a - b;
      int mul = a * b;
      int div = a / b;
      int mod = a % b;
      System.out.println("两数之和: " + sum); // 40
      System.out.println("两数之差: " + dif); // 20
      System.out.println("两数之积: " + mul); // 300
      System.out.println("两数之商: " + div); // 3
      System.out.println("两数取模: " + mod); // 30 /10 = 3...0【%: 0】
      }
    • 练习: 键盘录入一个三位数、将其拆分为个位、十位、百位,打印在控制台

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27

      /**
      * 分析:
      * 个位: 数值 % 10
      * 十位: 数值 / 10 % 10
      * 百位: 数值 / 100 % 10
      */
      public class ScannerDemo {
      public static void main(String[] args) {
      // 键盘录入
      Scanner scanner = new Scanner(System.in);
      // 接受数据
      System.out.println("请输入任意的三位数: ");
      int num = scanner.nextInt();
      // 表示num除以10的余数,即个位上的数字。
      int digits = num % 10;
      // 表示num除以10再整除10的余数,即十位上的数字。
      int tDigits = num / 10 % 10;
      // 表示num除以100再整除10的余数,即百位上的数字。
      int hDigits = num / 100 % 10;

      System.out.println("个位数: " + digits);
      System.out.println("十位数: " + tDigits);
      System.out.println("白位数: " + hDigits);
      }
      }

      1 2
      iuODUE.png iuOHLN.png
    • 不同数据类型之间的运算

      1
      2
      3
      4
      5
      6
      7
      8
      public static void main(String[] args) {
      int a = 10;
      double b = 20.3;
      // double: 30.3
      double c = a + b;
      // 结论;小数据类型向大数据类型看齐
      System.out.println("不同数据类型的两数相加: " + c);
      }
    • + 操作中输出字符串,这个+ 是字符串连接符,而不是算数运算符了,会将前后的数据进行拼接,并产生一个新的字符串。

      1
      2
      3
      4
      5
      6
      public static void main(String[] args) {
      int a = 20;
      String b = "23";
      // 输出: 2023
      System.out.println(a + b);
      }
      1
      2
      3
      4
      5
      // 连续进行 + 操作时,从左到右逐个执行
      public static void main(String[] args) {
      // 输出: 43java
      System.out.println(20 + 23 + "java");
      }
  • 自增自减运算符

    • ++/--无论是放在变量的前边还是后边,单独写一行结果是一样的

    • 参与计算

      • ++: 先用后加

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        public class ScannerDemo {
        public static void main(String[] args) {
        int a = 10, b = 20;
        // 1. 先用: c = a
        int c = a++;
        // 2. (用)++: a++ => 11
        System.out.println(a);
        }
        }

      • ++: 先加后用

        1
        2
        3
        4
        5
        6
        7
        8
        public class ScannerDemo {
        public static void main(String[] args) {
        // 先用: ++b => 21
        int d = ++b;
        // 21
        System.out.println(b);
        }
        }
  • 赋值运算符

    符号 作用 说明
    = 赋值 int a = 10,10 赋值给变量a
    += 加后赋值 a+=b,a+b 的值给a
    *= 乘后赋值 a*=b,a*b 的值给a

    +=、*=、/=、%= 底层都隐藏了一个强制类型转换

  • 关系运算符(比较运算符)

    符号 说明
    == a==b,判断a b 的值是否相等,成立为true、不成立为false
    != a!=b,判断a b 的值是否不相等,成立为true、不成立为 false
    > a>b,判断a 是否大于b,成立为true、不成立为 false
    >= a>=b,判断a 是否大于等于b 的值,成立为true、不成立为 false
    < a<b,判断a 是否小于b 的值,成立为true、不成立为 false
    <= a<=b,判断a 是否小于等于b 的值,成立为true、不成立为 false
  • 逻辑运算符

    符号 作用 说明
    & 逻辑与 并且,两边都为真,结果才是真
    ` ` 逻辑或
    ^ 逻辑异或 相同为false,不同为true
    ! 逻辑非 取反
  • 短路逻辑运算符

    符号 作用 说明
    && 短路与 结果和& 相同,但是有短路效果
    ` `
    • 注意事项

      • &、|无论左边true、false,右边都要执行
      • &&、||如果左边能确定整个表达式的结果,右边将不再执行
      • &&: 左边为false,右边不管是真是假,整个表达式的结果一定是false
      • ||: 左边为true,右边不管是真是假,整个表达式的结果一定是true
    • 练习

      • 需求: 数字6 是一个真正伟大的数字,键盘录入两个整数。其中一个为6,最终结果输出为true。如果他们的和为6 的倍数。最终结果输出true。其他情况都是false

        1
        2
        3
        4
        5
        6
        7
        public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int num1 = scanner.nextInt();
        int num2 = scanner.nextInt();
        boolean result = num1 == 6 || num2 == 6 || (num1 + num2) % 6 == 0;
        System.out.println(result);
        }
  • 三元运算符

    • 格式: 关系表达式?表达式1:表达式2

    • 范例: 求两个数的较大值

      1
      2
      3
      4
      5
      6
      public static void main(String[] args) {
      int num1 = 20;
      int num2 = 30;
      int result = num1 > num2 ? num1 : num2;
      System.out.println(result);
      }
      执行流程(num2 = 30)
  • 运算逻辑优先级

    • ( ) 优先于所有
++ 运算符
  • ++ 与后++

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class ReviewTest2 {
    public static void main(String[] args) {
    int a1 = 10;
    int b1 = a1++; // = 属于赋值运算,所以经历了"先运算赋值 的操作"
    // 结论: (后)++: 先运算 ,然后在自增
    System.out.println(b1); // 输出: 10

    int c1 = 10;
    int d1 = ++c1; // = 属于赋值运算,所以经历了"自增1,在运算'赋值操作' "
    // 结论: (前)++: 先自增 1,然后在运算
    System.out.println(d1); // 输出: 11
    }
    }
    • 案例练习

      案例练习
  • 逻辑运算符

    操作符 描述 例子
    && 称为逻辑运算符,当且仅当两个操作数都为真,条件才为真 (A && B) 为假
    || 称为逻辑运算符,如果任何两个操作数任何一个为真, 条件为真 (A || B)为真
    ! 称为逻辑运算符,用来反转操作数的逻辑状态,如果条件为 true,逻辑非运算将得到false !(A&&B)为真
  • 三元运算符

    三元运算符
    • 三元运算符与if-else 的联系与区别
      • 三元运算符可以简化if-else 语句
      • 三元运算符必须返回一个结果
      • if 后的代码块可以有多个语句

原码、反码、补码

  • 原码

    十进制数据的二进制表现形式,最左边是符号位,0 为正,1 为负

    原码 在线计算器(http://www.atoolbox.net/Tool.php?Id=952)
  • 反码

    正数的补码反码是其本身(正数的补码和反码与原码一致), 负数 的反码是符号位保持不变,其余取反

    负数以原码的补码形式表达,先计算正数的原码,改变符号位,就是负数的原码

    正数的反码(+5) 负数的反码(-5)
    5: 0101 -5: 1010
  • 补码

    正数的补码是其本身(原码形式),负数的补码是在其反码的基础上+1

    +5 -5
    0101
  • 其他运算符

    运算符 含义 说明
    & 逻辑与 0 false1 true
    | 逻辑或 0 false1 true
    << 左移 向左移动,低位补0
    >> 右移 向右移动,高位补0(原来是正数) / 1(原来是负数)
    • 按位与

      &,二进制为补码形式
    • 按位或

      按位或
    • <<

      左移
    • >>

      右移

流程控制语句

分类
  • 顺序结构
  • 分支结构
  • 循环结构
顺序结构
  • 顺序结构: 顺序结构是Java 程序默认的执行流程,按照代码的先后顺序,从上到下依次执行

    1
    2
    3
    4
    5
    6
    7
    // 输出依次是 1 2 3 4 顺序执行
    public static void main(String[] args) {
    System.out.println(1);
    System.out.println(2);
    System.out.println(3);
    System.out.println(4);
    }
分支结构
IF(判断)
  • 语法与执行流程

    1
    2
    3
    4
    5
    public static void main(String[] args) {
    if (关系表达式) {
    语句体;
    }
    }
    • 执行流程
      1. 首先计算关系表达式的值
      2. 如果关系表达式的值为true 就执行语句体
      3. 如果关系表达式的值为false 就不执行语句体
      4. 继续执行后面的其他语句
  • 需求:键盘录入女婿酒量,如果大于2 斤,老丈人给出回应,反之不回应

    1
    2
    3
    4
    5
    6
    7
    8
    public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    System.out.println("请输入: ");
    int num = scanner.nextInt();
    if (num > 2) {
    System.out.println("酒水喝多了对身体不好啊,小伙子.................");
    }
    }
    • 注意点
      1. 大括号的开头可以另起一行书写,但是建议写在第一行的末尾
      2. 如果语句体中,只有一句代码,大括号可以省略,但是不建议省略
      3. 如果对一个布尔类型的变量进行判断,不要用== 号,直接把变量写在小括号内
  • 闰年判断

  • 格式二

    1
    2
    3
    4
    5
    6
    7
    public static void main(String[] args) {
    if(关系表达式){
    语句体1;
    }else {
    语句体2;
    }
    }
    格式二
    • 执行流程
      1. 首先计算关系表达式的值
      2. 如果关系表达式的值为true 就执行语句体1
      3. 如果关系表达式的值为false 就执行语句体2
      4. 继续执行后面的其他语句
  • 第三种格式

    第三种格式
Switch(选择)
  • break 可以在switch- case 结构中,表示一旦执行到此关键字就跳出switch- case 结构
  • switch- case 结构中的表达式,只能执行如下六种 数据类型;如: byte、short、int、,枚举类型、String 等类型
  • case 之后只能声明常量,不能声明范围
  • JDK12 新特性写法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // >=JDK12+
    public static void main(String[] args) {
    int number = 10;
    switch (number) {
    // 没有 case 穿透
    case 1 -> {
    System.out.println(1);
    }
    case 10 -> {
    System.out.println(10);
    }
    default -> {
    System.out.println("default.....");
    }
    }
    }
循环控制结构
For
  • 语法

    1
    2
    3
    for (初始化语句; 条件判断; 条件控制语句) {
    循环体语句;
    }
    • 执行流程

      流程
    • 描述

      1. 执行初始化语句
      2. 执行条件判断语句,看其结果是true 还是false
        • 如果是false,循环结束
        • 如果是true,执行循环体语句
      3. 执行条件控制语句
      4. 回到继续执行条件判断语句
  • for 循环

    • 练习:使用 for 循环遍历100 以内的奇数, 并计算所有的奇数的和并输出

      1
      2
      3
      4
      5
      6
      7
      8
      9
      public static void main(String[] args) {
      int sum = 0;
      for (int i = 1; i <= 100; i++) {
      if (i % 2 != 0) {
      sum += i;
      }
      }
      System.out.println(sum);
      }
    • 流程

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      for (int i = 0; i < 3; i++) {
      System.out.println(i);
      }

      // 执行流程
      int i = 0; // ①
      i<3 // ②
      System.out.println(i);// ③
      i++; // ④
      ...

  • 九九乘法口诀表

    1
    2
    3
    4
    5
    6
    7
    // 已知九九乘法口诀表 9 行 9 列
    for (int i = 1; i <= 9; i++) {
    for (int j = 1; j <= i; j++) {
    System.out.print(j + "*" + i + "=" + i * j + "\t");
    }
    System.out.println();
    }
  • 打标签

    1
    2
    3
    4
    5
    6
    7
    flag:for (int i = 1; i <= 9; i++) {
    for (int j = 1; j <= i; j++) {
    if(i==2 && j==3)
    break flag;
    }
    System.out.println();
    }
While
  • 语法

    1
    2
    3
    4
    5
    6
    7
    public static void main(String[] args) {
    初始化语句;
    while (条件判断语句) {
    循环体语句;
    条件控制语句;
    }
    }
    • 执行流程

      while 流程
Do While
  • 语法

    1
    2
    3
    4
    5
    6
    7
    8
    public static void main(String[] args) {
    初始化语句;
    do {
    循环体语句;
    条件控制语句;
    }
    while (条件判断语句);
    }
  • 执行流程

    执行流程
    在这里插入图片描述
  • 特点: 先执行后判断

For 和 While 的对比
  • 相同点

    相同点: 运行规则都是一样的

  • for while 的区别

    • for 循环中,控制循环的变量,因为归属for 循环的语法结构中,for 循环结束后,就不能再次被访问到了

      控制循环的变量i
    • while 循环中,控制循环的变量,对于while 循环来说不归属其语法结构中,while 循环结束后,该变量还可以继续使用

      控制循环的变量(flag)
  • 练习

    • 需求: 世界最高山峰是珠穆朗玛峰(8844.43m = 8844430mm),加入我们有一张足够大的纸,它的厚度是0.1mm,请问,我折叠多少次,可以折成珠穆朗玛峰的高度?

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      public static void main(String[] args) {
      // 珠穆朗玛峰高度
      double height = 8844430;
      // 纸张初始厚度
      double paper = 0.1;
      // 折纸次数
      int count = 0;
      while (paper < height) {
      // 折纸一次,厚度翻倍
      paper *= 2;
      // 统计次数 ++
      count++;
      }
      // 27
      System.out.println(count);
      }
    • 回文数判断

      • 需求: 给你一个整数x,如果x 是一个回文数,打印true,否则,返回false

      • 回文数: 回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数

      • Eg: 121(true),123(false)

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        public static void main(String[] args) {
        // 1. 定义一个数字
        int x = 1213;
        // 定义一个临时变量用于记录 x 原来的值,用于最后进行比较
        int temp = x;
        // 记录倒过来的结果
        int num = 0;
        while (x != 0) {
        // 从右往左获取每一个数字
        int ge = x % 10;
        // 修改一下 x 记录的值
        x = x / 10;
        // 把当前获取到的数字拼接到最右边
        num = num * 10 + ge;
        }
        // 比较
        System.out.println(num == temp);
        }
        理解
    • 求商和余数

      • 需求: 给定两个整数,被除数和除数(都是整数,且不超过 int 的范围),将两数相除,要求不使用乘法、除法和% 运算

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        public static void main(String[] args) {
        // 定义被除数
        int diviend = 2;
        // 定义除数
        int divisor = 3;
        // 定义 商
        int count = 0;
        while (diviend >= divisor) {
        diviend -= divisor;
        count++;
        }
        System.out.println("商: " + count);
        System.out.println("余数: " + diviend);
        }
循环高级练习
  • 无限循环

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 不好会停止
    public static void main(String[] args) {
    for (; ; ) {
    System.out.println("无限循环体");
    }
    while (true) {
    System.out.println("无限循环体");
    }
    do{
    System.out.println("无限循环体");
    }while (true);
    }
  • continue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public static void main(String[] args) {
    for (int i = 1; i <= 5; i++) {
    if (i == 3) {
    // 结束本次循环,继续下次循环
    continue;
    }
    System.out.println("小老虎正在吃第" + i + "个包子");
    }
    }
    continue
  • break

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public static void main(String[] args) {
    for (int i = 1; i <= 5; i++) {
    System.out.println("小老虎正在吃第" + i + "个包子");
    if (i == 3) {
    // 结束整个循环
    break;
    }
    }
    }
    break
  • 练习

    • 7 过,continue

    • 求平方根: 键盘录入一个大于等于2 的整数X,计算并返回X 的平方根。结果只保留整数部分,小数部分将被舍去

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      public static void main(String[] args) {
      // 键盘录入一个大于等于`2`的整数`X`,计算并返回`X`的平方根。结果只保留整数部分,小数部分将被舍去
      Scanner scanner = new Scanner(System.in);
      // 测试三组数据 4 10 20
      int count = 0;
      while (count < 3) {
      System.out.print("请输入一个 2 的整数倍的数: ");
      int num = scanner.nextInt();
      for (int i = 1; i <= num; i++) {
      if (i * i == num) {
      System.out.println(i + " 就是 " + num + " 的平方根");
      break;
      } else if (i * i > num) {
      System.out.println((i - 1) + " 就是 " + num + " 平方根的整数部分");
      break;
      }
      }
      count++;
      }
      }
      求平方根
  • 需求: 键盘录入一个正整数x,判断该整数是否为一个质数

    • 质数: 如果一个整数只能被1 和本身整数,那么这个数就是质数。否则这个数叫做合数

      • 实现一

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入一个正整数: ");
        int num = scanner.nextInt();
        // 标记 num 最初为一个质数
        boolean flag = true;
        for (int i = 2; i < num; i++) {
        // i: 依次表示这个范围内的每一个数字
        if (num % i == 0) {
        flag = false;
        break;
        }
        }
        // 当循环内的数字都判断了,此时才能说明这个数是一个 质数
        if (flag) {
        System.out.println(num + " 是一个质数");
        }
        }
      • 实现二

Random

  • 生成随机数

    1
    2
    3
    4
    5
    6
    7
    8
    public static void main(String[] args) {
    Random r = new Random();
    for (int i = 0; i < 100; i++) {
    // r.nextInt(10): 0 ~ 9
    int anInt = r.nextInt(10);
    System.out.print(anInt + " ");
    }
    }
    [0,10)
  • 生成指定区间的数字

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // [a,b] => int round = b-a+1
    public static void main(String[] args) {
    Random r = new Random();
    for (int i = 0; i < 100; i++) {
    // [7 ~ 15]
    int round = 15 - 7 + 1;
    int start = 7;
    // r.nextInt(round) => 0 ~ round, +start => (0 ~ round)+start
    int anInt = r.nextInt(round) + start;
    System.out.print(anInt + " ");
    }
    }
    生成指定区间的数字

IDEA-模块+操作

  • 模块导入(ctrl+alt+shift+s)

    设置
    设置
  • 选择导入

    点击加号,选择导入
    点击加号,选择导入
  • 选择覆盖

    选择覆盖
    选择覆盖
  • 如果出现SDK,选择如下

    SDK
    SDK
  • 模块移除(也可以直接按下delete,只是项目移除,磁盘中依然存在)

    remove
    remove
  • 创建新模块

    创建新模块
    创建新模块

第三章

数组

数组的概述
  • 数组的理解: 数组(Array),是多个相同数据类型按照一定的顺序排列的集合,并使用同一个名字命名;
  • 数组的特点: 数组是有序排列的
  • 数组属于引用类型数据的变量,既可以是基本数据类型,也可以是引用数据类型
  • 创建数组对象会在内存中开辟一整块连续的空间
  • 数组长度一旦确定,就不能再修改
  • 数组的下标从 0 开始
一维数组的使用
  • 数组的遍历

    • 数组遍历:将数组中所有的内容取出来,取出来之后可以(打印、求和、判断...)

      遍历: 指的是取出数据的过程

  • 数组的声明和初始化

    1. 静态初始化: 数组的初始化和数组元素的赋值同时进行

      初始化: 就是在内存中,为数组容器开辟空间,并将数据存入容器的过程

      1
      2
      3
      4
      5
      6
      7
      public class ArrayTest {
      public static void main(String[] args) {
      // 静态初始化: 数组的声明和赋值同时进行
      int[] arrays = new int[]{100, 90, 89, 40};
      System.out.println(arrays);
      }
      }
    2. 动态初始化: 数组的初始化和数组元素的赋值分开进行

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      public class ArrayTest {
      public static void main(String[] args) {
      // 动态初始化: 数组的声明和赋值分开进行
      String[] names = new String[4];
      for (int i = 0; i < names.length; i++) {
      // 数组赋值
      names[i] = "names1" + i;
      System.out.println(names[i]);
      }
      // 调用数组指定位置的元素 4
      System.out.println("数组指定位置名称: " + names[3]);

      // 获取数组的长度
      int arrayLength = names.length;
      System.out.println("数组长度的获取: " + arrayLength);
      }
      }

数组默认元素的初始值
  • 基本数据类型

    1. 数组元素是 整型0

    2. 数组元素是 浮点型0.0

    3. 数组元素是char0

    4. 数组元素是布尔类型false

  • 引用数据类型

    数组元素是String 类型: 默认值为null

数组中的常见操作
  • 求数组中数组元素的 最大值最小值平均值 总和等

    • 最大值

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      public static void main(String[] args) {
      Random r = new Random();
      int[] arr = new int[]{0, 89, 5, 100};
      // 遍历数组求最大值
      int max = arr[0];
      // i 从 1 开始,因为 max=arr[0],自己和自己没有任何影响,影响效率
      for (int i = 1; i < arr.length; i++) {
      // i: 是索引,arr[i] 是元素值
      if (arr[i] > max) {
      max = arr[i];
      }
      }
      // 当循环结束后,记录的就是最大值
      System.out.println(max);
      }
    • 最小值

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      public static void main(String[] args) {
      Random r = new Random();
      int[] arr = new int[]{0, 89, 5, 100};
      // 遍历数组求最大值
      int min = arr[0];
      for (int i = 1; i < arr.length; i++) {
      // i: 是索引,arr[i] 是元素值
      if (arr[i] < min) {
      min = arr[i];
      }
      }
      // 当循环结束后,记录的就是最小值
      System.out.println(min);
      }
    • 遍历数组求和

      • 求出所有数据的和

      • 求出所有数据的平均数

      • 统计有多少个数据比平均值小

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        public static void main(String[] args) {
        Random r = new Random();
        int[] arr = new int[10];
        // 1. 生成 10 个随机数
        for (int i = 0; i < arr.length; i++) {
        // 1 ~ 100
        int num = r.nextInt(100) + 1;
        arr[i] = num;
        }

        // 遍历数组
        System.out.print("[");
        for (int i = 0; i < arr.length; i++) {
        if (i == arr.length - 1) {
        System.out.print(arr[i]);
        } else {
        System.out.print(arr[i] + ",");
        }
        }
        System.out.println("]");

        // 2. 求平均数
        int sum = 0, avg = 0;
        for (int i = 0; i < arr.length; i++) {
        sum += arr[i];
        }
        avg = sum / arr.length;
        System.out.println("所有数据的和: " + sum);
        System.out.println("所有数据的平均数: " + avg);

        // 3. 统计有多少个数据比平均值小
        int count = 0;
        for (int i = 0; i < arr.length; i++) {
        if (arr[i] < avg) {
        count++;
        }
        }
        System.out.println("比平均数小的个数为: " + count);
        }
        结果
  • 数组元素的反转

    • 要求不能使用新数组,就用原来唯一 一个数组

    • 解析:

      • 数组元素反转, 其实就是对称位置的元素交换

      • 两个元素的交换流程图

        数组元素的反转
    • 交换数组中的数据

      • 需求: 定义一个数组,存入1、2、3、4、5 按照要求交换索引对应的元素

      • 交换前1、2、3、4、5

      • 交换后:5、4、3、2、1

      • 分析

        定义两个变量i,j,记录索引,进行交换
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        public static void main(String[] args) {
        int[] arr = {95, 80, 70, 18, 13, 39, 70, 71, 6, 58};

        for (int i = 0, j = arr.length - 1; i < j; i++, j--) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
        }
        for (int i = 0; i < arr.length; i++) {
        System.out.print(arr[i] + " ");
        }
        }
Java的内存 【Java 的内存划分为5 个部分】
  1. (stack): 存放的都是方法中的局部变量(方法的运行一定要在栈当中运行)
    • 局部变量: 方法的参数或者是方法 {} 内部的变量
    • 作用域: 一旦超出作用域,立刻从栈的内存中消失
  2. (Heap): 凡是 new 出来的东西都在堆当中
    • 堆内存里面的东西都是地址值: 16进制
    • 堆内存里面的数据都有默认值
  3. 方法区(Method area ): 存储.class 相关信息包含放法的信息
  4. 本地方法栈: 与操作系统相关
  5. 寄存器: 与CPU相关
数组常见异常问题
  • 数组越界

  • 空指针异常

    数组必须进行 new 初始化才能使用其中的元素,如果只是赋值了一个 null,没有进行 new 创建,那么将引起空指针异常

    空指针异常
    • 引起的原因: 没有进行new 操作
    • 解决方案: 补上new

方法

  • 方法注意事项

    1. 方法定义的先后顺序无所谓
    2. 方法的定义不能产生嵌套包含关系
    3. 方法定义好了之后,不会执行的,一定要进行方法的 调用
  • 方法的重载

    • 定义: 在同一个类中, 允许存在一个以上的`同名`方法,只需要它们的参数类型不同即可

    • 判断方法是否重载: 跟方法的权限修饰符返回值类型形参变量名 方法体 都没有关系

  • 方法的重写

    • 重写: 子类继承父类以后、可以对父类中同名同参数的方法、进行覆盖操作

    • 应用: 重写以后,当创建子类对象以后,通过子类调用父子类中的同名同参数的方法时, 实际执行的是子类重写父类的方法

  • 定义方法的完整格式

    • 方法的声明

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      权限修饰符 返回值类型 方法名(形参列表){
      // 方法体
      return 返回值;
      }


      权限修饰符:
      返回值类型: 也就是方法最终产生的数据结果是什么类型
      方法名称: 方法的名字,小驼峰命名
      参数类型: 进入方法的数据类型是什么类型
      参数名称: 进入方法的数据对应的变量名称



      约定俗称:
      子类中的叫重写方法
      父类中的叫被重写方法

    • 子类中重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同
    • 子类重写的方法的权限不小于父类中被重写的方法的权限修饰符

      • 特殊情况: 子类不能重写父类中声明为 private 权限的方法
  • 返回值类型

    • 父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是 void 类型
    • 父类被重写的方法是 的返回值类型是 A 类型,则子类中重写的方法的返回值类型可以是 A 类型或者A 类的子类
    • 父类被重写的方法的返回值类型是基本数据类型则子类重写的方法的返回值类型必须是相同的。
  • return 作用:

    1. 停止当前方法
    2. 将后面的返回值还给调用处
  • 方法重载和重写的区别

    重载和重写的区别
    • 重载: 发生在同一个类中,方法名必须相同,参数类型不同,个数不同,顺序不同,方法返回值和访问修饰符可以不同,发生在编译时
    • 重写: 发生在父子类中, 方法名 %}、 参数列表 %} 必须相同 返回值范围 %} 小于等于`` 父类 %},抛出的异常范围小于等于父类,如果父类方法访问修饰符为` private %}则子类中不能重写该方法
  • 方法调用的三种格式

    1. 单独调用: 方法名称(参数)

    2. 打印调用: System.out.println(方法名称(参数))

    3. 赋值调用: 数据类型 变量名称 = 方法名称(参数)

      注意: 返回值类型固定为 void,这种方法只能单独调用, 不能进行打印调用 或者 赋值调用

      void
  • 方法执行的流程图解析

    流程图解析

方法参数的传值机制

基本数据类型和引用数据类型
  • 基本数据类型内存图

    变量中存储的是真实地数据值
  • 引用数据类型内存图

    引用数据类型的变量存储的是地址值
方法形参的传递机制
  • 参数

    1. 形参: 用来接收调用该方法时传递的参数。只有在被调用的时候才分配内存空间,一旦调用结束,就释放内存空间。因此仅仅在方法内有效。

    2. 实参: 传递给被调用方法的值,预先创建并赋予确定值。

  • 值传递机制

    值传递机制 值传递机制
    如果参数是基本数据类型,此时实参赋给形参的是真实存储的 数据值 如果变量是引用数据类型,此时赋值的变量是所保存的数据的 地址值

第四章

类和对象

  • 类: 类是一组相关属性和行为的集合,可以看作是一类事物的模板,使用事物的属性特征和行为来描述该类事物
  • 对象: 对象是类的一个实例,有状态和行为。例如,一条狗是一个对象,它的状态有:颜色、名字、品种;行为有:摇尾巴、叫、吃等。
  • 类与对象的关系:
    • 类是对一类事物的描述,是抽象的
    • 对象是一类事物的实例,是具体的
    • 类是对象的模板,对象是类的实体

Java类及类的成员

  1. 属性: 对应类中的成员变量

  2. 方法: 对应类中的成员方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class Phone {
    // 属性
    String brand;
    Double price;

    // 行为
    public static void call() {
    System.out.println("我可以打电话");
    }
    }

定义类的规范

  • 类名首字母建议大写,需要见名知意, 驼峰模式
  • 一个Java 文件中可以定义多个class 类,且只能一个类是public 修饰,而且public 修饰的类名必须成为代码的文件名
  • 成员变量的完整定义格式是: 修饰符 数据类型 变量名称 = 初始值 ;一般无需指定初始化值,存在默认值

封装

  • 封装的意义: 在于明确表识出允许外部使用的所有成员函数和数据项内部细节对外调用透明,外部调用无需修改或者关心内部实现

    JavaBen 的属性私有,提供setter getter 对外访问,因为属性的赋值或者获取逻辑只能由 javaben 本身决定,而不能由外部随意修改

    1
    2
    3
    4
    5
    private String name;
    public void setName(String name){
    // 该 name 有自己的命名规范,明显不能由外部直接赋值
    this.name = "coder-itl_"+ name;
    }
  • 详细原因说明

    • 问题引出

      1
      2
      3
      4
      5
      public class BookDemo {
      private String bookName; // 书籍名称
      private double bookPrice; // 书籍价格
      // 省略 getter setter
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12

      public class MainApplication {
      public static void main(String[] args) {
      BookDemo bookDemo = new BookDemo();
      bookDemo.setBookName("Java从入门到精通");
      // TODO: 这个价格被定义为了负数
      bookDemo.setBookPrice(-99.99);
      // 输出: 当前阅读书籍: Java从入门到精通 书籍的价格为: -99.99
      System.out.println("当前阅读书籍: " + bookDemo.getBookName() + " 书籍的价格为: " + bookDemo.getBookPrice());
      }
      }

    • private 关键字

      相当于秘密通道,目的是为了获取金库的金子,要获取到金子,就要通过通道setter、getter

this关键字的使用

  • this

    当方法的局部变量和类的成员变量重名的时候,根据就近原则,优先使用局部变量,如果需要访问本类中的成员变量,需要使用格式: this.成员变量

    使用 问题引出

    注意: 通过谁调用的方法,谁就是this

理解”万事万物皆对象”

  1. java 语言范畴中,我们都将功能结构等封装到类中,而通过类的实例化来调用具体的功能结构
  2. 涉及到 java 语言与前端HTML,后端的数据库交互时,前后端的结构在Java 层面交互时,都体现为对象

构造方法

  • 什么是构造方法

    构造方法是专门用来创建对象的方法,当我们通过关键字 new 来创建对象时,其实就是在调用构造方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // 无参
    public class Phone {
    public Phone() {
    }
    }
    // 有参
    public class Phone {
    public Phone() {
    }

    public Phone(String brand, Double price) {
    this.brand = brand;
    this.price = price;
    }

    // 属性
    private String brand;
    private Double price;

    // 行为
    public static void call() {
    System.out.println("我可以打电话");
    }
    }

  • 注意事项:

    1. 构造方法的名称和所在的名称完全一样,就连大小写都一样
    2. 构造方法不需要写返回的类型,void 都不写
    3. 构造方法不能return 一个具体的返回值
    4. 构造方法也可以重载

    这个标准类也叫做 Java Bean

匿名对象

注意事项
  1. 理解: 我们创建的时候,没有显示的赋给一个变量名,即为匿名对象

    匿名对象
  2. 特征: 匿名对象只能调用一次

使用解析
  • 使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    package it.coderitl.scannertest;

    import java.util.Scanner;

    public class Anonymous {
    // Anonymous 匿名
    public static void main(String[] args) {
    // 匿名对象的使用
    System.out.println("请输入一个整形数字: ");
    System.out.println(new Scanner(System.in).nextInt());

    // 方法调用
    testAnonymous(new Scanner(System.in));

    // 将匿名对象作为返回值
    Scanner sc = methodReturn();
    int num = sc.nextInt();
    System.out.println("输入的是: " + num);
    }

    // 将匿名对象作为参数传递
    public static void testAnonymous(Scanner sc) {
    System.out.println("请输入一个整型数值: ");
    int num = sc.nextInt();
    System.out.println("你输入的数据是: " + num);
    }

    // 将匿名对象作为返回值
    public static Scanner methodReturn() {
    return new Scanner(System.in);
    }
    }

标准类-JavaBean

  1. 所在的成员变量都要使用 private 关键字修饰
  2. 为每一个成员变量编写一对 Getter/Setter 方法
  3. 编写一个无参的构造方法
  4. 编写一个全参数的构造方法

可变个数的形参

  1. JDK-5.0 新增内容
  2. 具体使用:
    • 格式: 数据类型 ...args
    • 当调用可变个数的形参的方法时,传入的参数个数可以是012······、个
    • 可变个数形参的方法与本类中方法名相同,形参不同的方法之间构成重载
    • 可变个数形参的方法与本类中方法名相同,形参类型也相同的数组之间不构成重载,换句话说,二者不能共存
    • 可变个数形参在方法的形参中,必须声明在末尾
    • 可变个数形参在方法的形参中,最多只能声明一个可变形参

静态代码块

  • 格式

    1
    2
    3
    4
    5
    6
    7
    public class StaticArea {
    static {
    // 静态代码块内容
    // 特点: 当第一次用到本类时,静态代码块执行唯一的一次
    }
    }
    静态代码块内容总是优先于非静态,所以静态代码块比构造方法先执行
  • 静态代码块的典型用途

    用来一次性的对静态成员变量进行赋值

第五章 String

String-概述

  • 字符串的特点:

    1. 字符串的内容用不可变
    2. 正是因为字符串不可改变,所以字符串是可以共享使用的
    3. 字符串效果上相当于 char[ ] 字符数组,但是底层原理是byte[ ] 字符串数组
  • 创建字符串的方式

    • 直接创建

      1
      String name = "coder-itl";
    • new

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      public static void main(String[] args) {
      char[] chars = {'a', 'b'};
      // 字符数组
      String s1 = new String(chars);
      System.out.println(s1);

      byte[] bytes = {97, 98};
      // 字节数组
      String s2 = new String(bytes);
      System.out.println(s2);
      }
  • 字符串常量池

    • 字符串常量池: 程序当中直接写上的双引号字符串就在字符串常量池中

      字符串常量池
    • 对于基本数据类型来说: == 是进行数值的比较

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      public static void main(String[] args) {
      /************************************/
      int a = 10;
      int b = 20;
      System.out.println(a == b); // false
      System.out.println(10 == 20); // false
      /************************************/
      int c = 30;
      int d = 30;
      System.out.println(c == d); // true
      System.out.println(30 == 30); // true
      /************************************/
      }
    • 对于引用数据类型来说:== 是进行地址值的比较

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      public static void main(String[] args) {
      /*************** 常量池中 *************/
      String s1 = "abc";
      String s2 = "abc";
      System.out.println(s1 == s2); // true
      /************************************/
      String s3 = new String("def");
      String s4 = new String("def");
      System.out.println(s3 == s4); // false(s3、s4指向了不同的地址)
      /************************************/
      }
  • 字符串比较相关的方法

    1
    2
    3
    4
    5
    6
    7
    // 字符串内容比较
    public boolean equals(Object object){

    }

    // 参数可以是任何对象,只有参数是一个字符串并且内容相同的才会给true,否则返回false;

    注意事项:

    1. 任何对象都能用 Object 接收

    2. equals 方法具有对称性也就是 a.equals(b) b.equals(a) 效果一样

    3. 如果比较双方一个是常量一个是变量,推荐把常量字符串写在前面

      1
      2
      3
      推荐: "abc".equals(str)
      不推荐: str.equals("abc")
      原因: String str = null; // 会出现空指针异常
  • 键盘输入字符串问题

    键盘输入字符串问题
    • 原因: next() 底层的字符串是new 出来的,所以地址值不一样,使用== 比较结果就为false
  • 字符串的遍历

    1
    2
    3
    4
    5
    6
    7
    8
    public static void main(String[] args) {
    String word = "\"C:\\Program Files\\Zulu\\zulu-17\\bin\\re\\IntelliJ IDEA 2022.2.3\\bin\"";
    for (int i = 0; i < word.length(); i++) {
    // i: 字符串的索引
    char c = word.charAt(i);
    System.out.print(c + " ");
    }
    }
  • 字符统计

    • 键盘录入一个字符串,统计该字符串中大写字母字符,小写字母字符,数字字符出现的次数

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      public static void main(String[] args) {
      Scanner scanner = new Scanner(System.in);
      System.out.println("请输入一个字符串: ");
      String str = scanner.next();

      int bigCount = 0;
      int smallCount = 0;
      int numberCount = 0;

      for (int i = 0; i < str.length(); i++) {
      char c = str.charAt(i);
      if (c >= 'a' && c <= 'z') {
      smallCount++;
      } else if (c >= 'A' && c <= 'Z') {
      bigCount++;
      } else {
      numberCount++;
      }
      }
      System.out.println("小写字母个数为: " + smallCount);
      System.out.println("大写字母个数为: " + bigCount);
      System.out.println("数字个数为: " + numberCount);
      }
  • 将一个整数类型的数组按照指定格式输出

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public static void main(String[] args) {
    int[] arr = {1, 2, 3};
    String res = "[";
    for (int i = 0; i < arr.length; i++) {
    if (i == arr.length - 1) {
    res = res + arr[i];
    } else {
    res = res + arr[i] + ", ";
    }
    }
    res = res + "]";
    System.out.println(res);
    }
  • 字符串相关方法练习使用

    练习
  • 字符串分割方法

    练习
  • 字符串的相关练习

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    package it.coderitl.splicetest;

    public class StringSplit {
    public static void main(String[] args) {
    // 练习: 定义一个方法,把数组 {1,2,3}按照指定的格式拼接成一个字符串
    /*
    * 习题分析:
    * 1. 定义一个方法 arrayToString
    * 2. {1,2,3} --> int[] array
    * 3. 拼接 concat | split
    * 4. 定义方法的三要素
    * 4.1 权限修饰符
    * 4.2 方法的返回值
    * 4.3 方法名称
    * 格式: [world#1world#2world#3]
    * */
    int[] array = {1, 2, 3};

    // 调用方法
    String arrayStr = arrayToString(array);
    System.out.println(arrayStr);
    }


    public static String arrayToString(int[] array) {
    String str = "{";
    // 遍历数组
    for (int i = 0; i < array.length; i++) {
    // 判断: 如果在数组的最后一个元素时
    if (i == array.length - 1) {
    // 数组的每个元素 array[i]
    str += "world" + array[i] + "}";
    } else {
    // 数组的每个元素 array[i]
    str += "world" + array[i] + "#";
    }
    }
    // 该返回谁 {world1#world2#world3}
    return str;
    }
    }

StringBuilder概述

  • StringBuilder 可以看成是一个容器, 创建之后里面的内容是可变的

  • 作用: 提高字符串的操作效率

  • 常用方法

    • append:添加数据,并返回对象本身

    • reverse:反转容器中的内容

    • length:返回长度(字符出现的个数)

      1
      2
      3
      4
      5
      6
      7
      public static void main(String[] args) {
      StringBuilder sb = new StringBuilder("abbc");
      System.out.println(sb.length());
      sb.append("d");
      System.out.println(sb);
      System.out.println(sb.reverse());
      }
    • toString: 就可以实现把StringBuilder 转换为String,就可以使用String 特有的方法了

StringJoiner

  • 体验

    1
    2
    3
    4
    5
    6
    7
    8
    public static void main(String[] args) {
    int[] arr = {1, 2, 3};
    StringJoiner sj = new StringJoiner(",", "[", "]");
    for (int i = 0; i < arr.length; i++) {
    sj.add(arr[i] + "");
    }
    System.out.println(sj);
    }
  • 构造方法

    方法名 说明
    public Stringjoiner(间隔符号) 创建一个StringJoiner 对象,指定拼接时的间隔符号
    public StringJoiner(间隔符号,开始符号,结束符号) 创建一个StringJoiner 对象,指定拼接时的间隔符号、开始符号、结束符号
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class TestStream {
    public static void main(String[] args) {
    // delimiter: 分隔符
    StringJoiner sj = new StringJoiner("---");
    sj.add("aa").add("bb").add("cc");
    // aa---bb---cc
    System.out.println(sj);
    }
    }
  • 成员方法

    方法名 说明
    public StringJoiner add(添加的内容) 添加数据,并返回对象本身
    public int length() 返回长度(字符出现的个数)
    public String toString() 返回一个字符串(该字符串就是拼接之后的结果)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 说明: 没有空参构造
    package org.example;


    import java.util.StringJoiner;

    public class TestStream {
    public static void main(String[] args) {
    // delimiter: 分隔符
    StringJoiner sj = new StringJoiner(",", "[", "]");
    sj.add("aa").add("bb").add("cc");
    System.out.println(sj);
    // [aa,bb,cc] 的总体长度,包含符号
    System.out.println(sj.length());

    }
    }

字符串相关原理

  • 字符串存储的内存原理

    1. 直接赋值会复用字符串常量池中的
    2. new 出来不会复用,而是开辟一个新的空间
  • == 号比较的到底是什么

    1. 基本数据类型比较的是数据值
    2. 引用数据类型比较的是地址值
  • 字符串拼接的底层原理

    • 右边没有变量

      字符串拼接
    • 右边有变量

      • JDK8 之前的原理

        1
        2
        3
        String s1 = "a";
        // 一个加号,堆内存中两个对象 JDK7: 1. StringBuilder对象 2.String
        String s2 = s1 + "b";
        两个对象
      • JDk8 之后

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        public static void main(String[] args) {
        String s1 = "a"; // length = 1
        String s2 = "b"; // length = 1
        String s3 = "c"; // length = 1
        // JDK8: 字符串拼接原理
        // 1. 预估长度 (预估总长度: 3)
        // 2. 创建一个预估长度数组 存入对应的值 a b c
        // 3. 再将这个数组转换为字符串
        String s4 = s1 + s2 + s3;
        }
        • 总结

          • 如果没有变量参与,都是字符串直接相加,编译之后就是拼接之后的结果,会复用串池中的字符串

            1
            2
            3
            4
            5
            6
            7
            public static void main(String[] args) {
            String s1 = "abc";
            String s2 = "a" + "b" + "c";
            // true: "a"+"b"+"c"运算过程中,未出现变量,在编译的时候,就会将 "a"+"b"+"c" 拼接为 "abc"
            System.out.println(s1 == s2); // true

            }
          • 如果有变量参与,会创建新的字符串,浪费内存

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            public class TestStream {
            public static void main(String[] args) {
            // 常量池中
            String s1 = "abc";
            String s2 = "ab";

            String s3 = s2 + "c";
            // false: 主要因为 s2 + "c"中的 '+',底层出现: new String
            System.out.println(s1 == s3);

            }
            }

            如果很多字符串变量拼接,不要直接+。在底层会创建多个对象,浪费时间,浪费性能

          • 字符串拼接的时候,如果有变量

            • JDK8 以前: 系统底层会自动创建一个StringBuilder 对象,然后在调用append 方法完成拼接,拼接后,在调用其toString 方法转换为String 类型,toString 方法的底层是直接new 了一个字符串对象
            • JDK8 版本: 系统会预估要字符串拼接之后的总大小,把要拼接的内容都放在数组中,此时也是产生了一个新的字符串
  • StringBuilder 提高效率原因

    所有要拼接的内容对都会往StringBuilder 中放,不会创建很多无用的空间,节约内存

  • StringBuilder 源码分析

    • 刚开始创建StringBuilder 时,默认会创建一个容量为16 字节数组

    • 添加的内容长度小于16,直接存

    • 添加的内容大于16 会扩容((16)老容量*2+2),如果再次超出容量,则扩容以实际容量为准

      1
      2
      3
      // 存储 A ~ Z (26) 个字母
      // 默认 16 不够用,进行扩容. 16*2+2=34
      // 存储 A~Z 0~9: 26+10=36[第二次扩容是 34 还不够用,],[第三次]所以本次的容量是 36
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      public static void main(String[] args) {
      // 容量: 最多装多少
      // 长度: 已经装了多少
      StringBuilder sBuilder = new StringBuilder();
      /**************************************/
      // 获取容量
      System.out.println("默认容量: " + sBuilder.capacity());
      // 获取长度
      System.out.println("默认长度: " + sBuilder.length());

      /**************************************/
      StringBuilder s2 = new StringBuilder();
      s2.append("aa").append("bb").append("cc");
      System.out.println("添加元素后: " + s2.capacity());
      System.out.println("添加元素后: " + s2.length());
      /**************************************/
      }
      原码分析

第六章 集合

集合

  • 集合的体系结构

    单列集合
    • List 系列集合: 添加的元素是有序的、可重复、有索引
    • Set 系列的集合: 添加的元素是无序、不重复、无索引
  • Collection 是单列集合的祖宗接口,他的功能是全部单列集合都可以继承使用的

    方法名称 说明
    add(E e) 把给定的对象添加到集合中
    clear() 清空集合中所有的元素
    remove(E e) 把给定的对象在当前集合中删除
    contains(Object obj) 判断当前集合中是狗包含给定的对象
    isEmpty() 判断当前集合是否为空
    size() 返回集合中元素的个数/集合的长度
    1
    2
    3
    // 判断元素是否包含
    // 底层是依赖 equals 方法进行判断是否存在的
    // 所以,如果集合中存储的是自定义对象,也想通过 contains 方法来判断是否包含,那么在 JavaBean 中,一定要重写 equals 方法
    • 集合自定义元素类型输出

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      public static void main(String[] args) {
      ArrayList<User> coll = new ArrayList<>();
      coll.add(new User("a", "b", LocalDateTime.now(), LocalDateTime.now()));
      coll.add(new User("b", "e", LocalDateTime.now(), LocalDateTime.now()));
      coll.add(new User("c", "f", LocalDateTime.now(), LocalDateTime.now()));
      // 需要重写 ToString,否则输出 [org.example.User@b0657722],不能进行 .toString() 改变
      System.out.println(coll);

      Collection<String> str = new ArrayList<>();
      str.add("aa");
      // [aa]
      System.out.println(str);
      }
      User 中必须重写 ToString
    • 集合的遍历

      • 迭代器

        方法名称 说明
        Iterator<E> iterator() 返回迭代器对象,默认指向当前集合的0 索引
        • Iterator 中的常用方法

          方法名称 说明
          boolean hasNext() 判断当前位置是否有元素,有元素返回true,没有元素返回false
          E next() 获取当前位置的元素,并讲迭代器对象移向下一个位置
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          public static void main(String[] args) {
          ArrayList<User> coll = new ArrayList<>();
          coll.add(new User("a", "b", LocalDateTime.now(), LocalDateTime.now()));
          coll.add(new User("b", "e", LocalDateTime.now(), LocalDateTime.now()));
          coll.add(new User("c", "f", LocalDateTime.now(), LocalDateTime.now()));
          Iterator<User> iterator = coll.iterator();
          while (iterator.hasNext()) {
          System.out.println(iterator.next());
          }
          }
      • 增强for

        • 增强for 的底层就是迭代器,为了简化迭代器的代码书写的
        • 它是JDK5 之后出现的,其内部原理就是一个Iterator 迭代器
        • 所有的单列集合和数组才能用增强for 进行遍历
      • Lambda 表达式遍历

        1
        2
        3
        4
        5
        6
        7
        8
        public static void main(String[] args) {
        ArrayList<User> coll = new ArrayList<>();
        coll.add(new User("a", "b", LocalDateTime.now(), LocalDateTime.now()));
        coll.add(new User("b", "e", LocalDateTime.now(), LocalDateTime.now()));
        coll.add(new User("c", "f", LocalDateTime.now(), LocalDateTime.now()));
        // 遍历
        coll.forEach(u-> System.out.println(u));
        }
    • List

      • 特点

        • 有序: 存和取的元素顺序一致
        • 有索引: 可以通过索引操作元素
        • 可重复: 存储的元素可以重复
      • 方法

        方法名称 说明
        void add(int index,E element) 在此集合中的指定位置插入指定的元素
        E remove(int index) 删除指定索引处的元素,返回被删除的元素
        E set(int index,E element) 修改指定索引处的元素,返回被修改的元素
        E get(int index) 返回指定索引处的元素
        1
        2
        3
        4
        5
        6
        7
        8
        public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        // TODO: 索引的位置从 0 开始,不能跳跃,间隔
        // 细节: 插入指定位置,如果该位置有元素,会被后移
        list.add(0, "aa");
        list.add(1, "bb");
        System.out.println(list);
        }

ArrayList 集合

基础定义
  • 由于数组的长度不可以发生改变,ArrayList 集合的长度是可以随意变化的

  • 泛型: 也就是装在 集合当中的所有元素,全部都是 统一的某种类型

集合常用的方法
  • public boolean add(E e): 向集合中添加元素,参数的类型和泛型一致

    1
    2
    3
    4
    5
    6

    注意: 对于ArrayList 集合来说,add 添加动作一定是成功的,所以返回值可以不用,

    返回值代表添加是否成功
    但是对于今后的其他类型来说: add 添加动作不一定成功

  • public E get(int index): 从集合当中获取元素,参数是索引编号,返回值就是对应位置的元素

  • public E remove(int index):从集合中删除元素,参数是索引编号,返回值就是被删除掉的元素

  • public int size(): 获取集合的尺寸长度,返回值是集合中包含的元素个数。

  • 练习ArrayList

    ArrayList
存储基本数据类型
  • 如果希望向 ArrayList 集合中存储基本数据类型,必须使用基本类型对应的包装类
包装类
  • 基本数据类型,使用起来非常方便,但是没有对应的方法来操作这些基本类型的数据,可以使用一个类,把基本数据类型的数据装起来,在类中定义一些方法,这个类叫做包装类,我们可以使用类中这些方法来操作基本数据类型
基本数据类型 对应包装类(包装类位于Java.lang 包下,所以不需要导包)
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean
装箱与拆箱
  • 基本类型与对应包装类对象之间,来回转换的过程称为装箱与"拆箱"
  • 装箱: 从基本类型转换为对应的包装类对象
  • 拆箱: 从包装类对象转换为对应的基本类型。
案例练习
  • 使用练习

    ArrayList的增删改查

    JDK 1.5+ 开始,支持自动装箱、自动拆箱

    • 自动装箱: 基本类型 –> 包装类型
      • 自动拆箱: 包装类型 –> 基本类型
  • 随机数与ArrayList 结合练习

    | 练习 |

| :———————————————————-: |
| |

  • 自定义

    自定义对象作为ArrayList数据类型
将 ArrayList 作为方法的参数使用
练习
用一个大集合存入 20 个随机数,然后筛选其中的偶数元素,放到小集合当中
练习
  • ArrayList 底层原理

    1. 利用空参创建的集合,在底层创建一个默认长度为0 的数组

    2. 添加第一个元素时,底层会创建一个新的长度为10 的数组

    3. 存满时,会扩容1.5

    4. 如果依次添加多个元素,1.5 倍还放不下,则新创建数组的长度以实际为准

      1
      2
      3
      4
      5
      6
      // ArrayList 空参构造
      public ArrayList() {
      // 底层数组名称: elementData
      // private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; 默认空数组
      this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
      }
  • LinkedList 集合

    • 底层数据结构是双链表,查询慢,增删快,但是如果操作的是首尾元素,速度也是极快的

    • 方法说明

      方法 说明
      public void addFirst(E e) 在该列表开头插入指定的元素
      public void addLast(E e) 将指定的元素追加到此列表的末尾
      public E getFirst() 返回此列表中的第一个元素
      public E getLast() 返回此列表中的最后一个元素
      public E removeFirst() 从此列表中删除并返回第一个元素
      public E removeLast() 从此列表中删除并饭hi最后一个元素
    • 链表

      链表结构
    • 链表的基本实现

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      package org.example;

      class Node<T> { // 定义描述节点的结构类
      private T data; // 保存所有数据
      private Node next; // 描述下一个节点

      public Node(T data) { // 所有节点一定要包裹数据
      this.data = data;
      }

      public T getData() {
      return this.data;
      }

      public void setNext(Node next) { // 设置下一个节点
      this.next = next;
      }

      public Node getNext() { // 返回下一个节点
      return this.next;
      }


      }

      public class LinkApplication {
      public static void main(String[] args) {
      // 直接定义好了一个数组内容,这个时候数组需要通过索引下标进行访问
      Node<String> nodeA = new Node<>("coder-itl");
      Node<String> nodeB = new Node<>("coderitl.github.io");
      Node<String> nodeC = new Node<>("coderitl");

      // 设置关联关系
      nodeA.setNext(nodeB);
      nodeB.setNext(nodeC);
      print(nodeA);
      }

      // 获取数据
      public static void print(Node node) {
      if (node == null) {
      return;
      }
      System.out.println(node.getData());
      // 递归调用
      print(node.getNext());
      }
      }

关键字 static

  • 理解

    分析
  • static 描述

    1. 如果一个成员变量使用了 static 关键字,那么这个变量不再属于对象自己,而是属于所在类
    2. 一旦使用了 static 修饰成员方法,那么这就成为了静态方法, 静态方法不属于对象 ,而是属于
    3. 使用: 如果没有使用static 关键字,那么必须首先创建对象,然后通过对象才能使用变量或方法
    4. 对于静态方法来说,可以通过对象名进行调用,也可以直接通过类名称调用
    5. 推荐使用类.静态方法名 | 类.成员方法

    注意事项:

    • 静态不能直接访问非静态: 原因: 因为在内存当中是有静态内容, 有非静态内容
    • 静态方法不能使用 this:原因: this 代表当前对象,通过谁调用的方法谁就是this,调用过程全程使用,矛盾
  • static 内存图

    注意: 根据 类名访问静态成员变量的时候,全程和对象就没关系,只和类有关系

    分析

数组工具类

java.util.Arrays 是一个与数组相关的工具类,里面提供了大量的静态方法,用来实现数组的常见操作

  • public static String toString(数组): 将参数数组编程字符串,按照默认格式([元素1,元素2,···])

  • public static void sort(数组): 将按照默认升序从小到大对数组元素进行排序

  • 使用:

    练习
  • 练习

数学工具类

继承

基本使用
  • 继承的格式

    • 父类格式

      1
      public class 父类名称{}
    • 子类格式

      1
      public class Zi extends Fu {}
  • 继承中关于成员变量访问的特点

  • 区分子类方法中重名的三种

    1
    2
    3
    Fu: String name = "Fu";
    Zi: String name = "Zi";
    成员方法: String name = "methods name";
    • 使用
      1. 直接写: System.out.println(name) --> 方法局部变量
      2. 关键字: this.name -->本类的成员方法
      3. super 调用: Fu类的变量
  • 继承中成员方法的访问特点

    1
    2
    3
    在父子类关系中,创建子类对象访问成员方法的规则

    创建的对象是谁,就优先用谁,如果没有则直接向上找,
构造方法
  • 继承关系中: 父子类构造方法的访问特点
    1. 子类构造方法当中有一个默认隐含的super() 调用,所以一定是优先调用的父类构造,后执行的子类构造
    2. 子类构造可以通过super 关键字来调用父类构造
    3. super 的父类构造调用,必须是子类构造方法的第一句,不能一个子类构造调用多次super 构造
Super关键字的三种用法
  1. 在子类的成员方法中,访问父类的成员变量
  2. 在子类的成员方法中,访问父类的成员方法
  3. 在子类的构造方法中,访问父类的构造方法
this 关键字的三种用法
  1. 在本类中的成员方法中,访问本类的成员变量
  2. 在本类的成员方法中,访问本类的两一个成员方法
  3. 在本类的构造方法中,访问本类的另一个构造方法

注意: this super 不能同时使用

Java继承的三个特点
  1. Java 语言是单继承
  2. Java 语言可以多级继承
  3. 一个子类的直接父类是唯一的,但是一个父类可以拥有很多个子类

抽象类

基本组成
  • 抽象方法: 就是加上abstract 关键字,然后去掉大括号,直接分号结束

    1
    2
    3
    4
    5
    // 抽象类
    public abstract class Animal {
    // 抽象方法
    public abstract void run();
    }
  • 抽象类: 抽象方法所在的类,必须是抽象类才行,class 之前写上abstract 关键字即可。

    1
    2
    public abstract class Animal {
    }
基本使用
  1. 不能直接创建new 抽象类对象

  2. 必须用一个子类继承抽象父类

  3. 子类必须覆盖重写抽象父类当中的所有抽象方法

    注意: 覆盖重写的规则: 子类去掉抽象方法的abstract 关键字,然后补上方法体{ }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class Dog extends Animal {
    @Override
    public void run() {

    }

    @Override
    public void eat() {

    }
    }

  4. 创建子类对象进行使用

  • 抽象类的使用过程

    1
    2
    3
    4
    5
    6
    7
    8
    package com.coderitl.learnabstract;

    // 抽象类
    public abstract class Animal {
    // 抽象方法
    public abstract void eat();
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package com.coderitl.learnabstract;

    // 继承 Animal
    public class Cat extends Animal {
    // 重写抽象方法
    @Override
    public void eat() {
    System.out.println("猫吃鱼");
    }

    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package com.coderitl.learnabstract;

    public class Main {
    public static void main(String[] args) {
    // 实例化 Cat 对象
    Cat cat = new Cat();
    // 方法调用
    cat.eat();

    }
    }

    • 抽象类使用的注意事项:

      • 抽象类不能创建对象
      • 抽象类中,可以有构造方法,是供子类创建对象时初始化父类成员使用的
      • 抽象类中,不一定包含抽象方法,但是有抽象方法的类必须是抽象类
      • 抽象类的子类必须重写抽象父类中所有的抽象方法
    • 抽象类练习

      • 需求

        1. 某加油站推出了2 种支付卡,一种是预存了10000 的金卡,后续加油享受8折优惠,另一种是预存了5000 的银卡,后续加油享受8.5 折优惠
        2. 请分别实现2 种卡片进入收银系统后的逻辑,卡片需要包含主人名称、余额、支付功能
      • 分析实现

        • 创建一张卡片的父类: 定义属性包括主人名称、余额、支付功能(具体实现交给子类)
        • 创建一张白金卡类: 重写支付功能,按照原价的8 折计算输出
        • 创建一张银卡类: 重写支付功能,按照原价的8.5 折计算输出
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        // 父类定义
        package com.example;

        public abstract class Card {
        private String name;
        private Double balance;

        // 支付功能
        public abstract void pay(Double money);

        public String getName() {
        return name;
        }

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

        public Double getBalance() {
        return balance;
        }

        public void setBalance(Double balance) {
        this.balance = balance;
        }
        }

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        // 金卡
        package com.example;

        public class GoldCard extends Card {
        @Override
        public void pay(Double money) {
        System.out.println("您当前消费: " + money);
        System.out.println("现有余额: " + getBalance());
        Double rs = money * 0.8;
        System.out.println(getName() + ": 您实际支付: " + rs);
        // 更新账户余额
        setBalance(getBalance() - rs);
        System.out.println("您当前余额: " + getBalance());
        }
        }

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        // 测试
        package com.example;

        public class MainTest {
        public static void main(String[] args) {
        GoldCard goldCard = new GoldCard();
        goldCard.setName("coder-itl");
        goldCard.setBalance(10000.0);
        goldCard.pay(90.0);
        }
        }
        抽象类使用
特征和注意事项
  • 类有的成员(成员变量、方法、构造器)抽象类都具备

  • 抽象类中不一定有抽象方法、有抽象方法的类一定是抽象类

  • 一个类继承了抽象类必须重写抽象类的全部抽象方法,否则这个类也必须定义成抽象类

  • 不能用abstract 修饰变量、代码块、构造器

  • 最重要的特征: 得到了抽象方法,失去了创建对象的能力

    失去创建对象的能力
    • 抽象类不能创建对象,为什么

      1
      2
      3
      public abstract class Card {
      public abstract void pay(Double money);
      }
      1
      2
      3
      // 反证法: 假设抽象类可以创建对象
      Card card = new Card();
      card.pay() // 方法体都不存在
    • final abstract 是什么关系

      互斥

      • abstract 定义的抽象类作为模板让子类继承,final 定义的类不能被继承
      • 抽象方法定义通用功能让子类重写,final 定义的方法子类不能被重写
  • 设计模式-模板方法模式

    • 什么时候使用模板方法模式

      • 使用场景说明: 当系统中出现同一个功能多处在开发,而该功能中大部分代码是一样的,只有其中部分可能是不同的时候
    • 模板方法模式实现步骤

      • 把功能定义成一个所谓的模板方法,放在抽象类中,模板方法中只定义通用且能确定的代码
      • 模板方法中不能决定的功能定义成抽象方法让具体子类去实现
    • 模板方法实现

      • 需求

        • 现在又两类学生,一类是中学生,一类是小学生,他们都要写我在学习java 这篇作文

        • 要求每种类型的学生,标题第一段和最后一段,内容必须一样。正文部分自己发挥

        • 请选择最优的面向对象方案进行设计

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          package com.example;

          public abstract class Student {
          // 模板方法建议使用 final 修饰(更加专业)
          public final void write() {
          System.out.println("----------------------- 起始部分 -----------------------");
          System.out.println(writeMain());
          System.out.println("----------------------- 结束部分 -----------------------");
          }

          public abstract String writeMain();
          }

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          // 小学生
          package com.example;

          public class StudentChild extends Student {
          @Override
          public String writeMain() {
          return "我是小学生.................";
          }
          }

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          // 大学生
          package com.example;

          public class StudnetMiddle extends Student {
          @Override
          public String writeMain() {
          return "我是大学生.................";
          }
          }

          1
          2
          3
          4
          5
          6
          7
          8
          // 测试
          public class MainTest {
          public static void main(String[] args) {
          StudentChild child = new StudentChild();
          child.write();
          }
          }

          抽象类-模板模式
    • 模板方法模式解决了什么问题

      • 提高了代码的复用性
      • 模板方法已经定义了通用结构,模板方法不能确定的部分定义成抽象方法,交给子类实现,因此,使用者只需要关心自己需要实现的功能即可

接口

定义:
  • 接口就是多个类的公共规范
  • 接口是一种引用数据类型,最重要的内容就是其中的` 抽象方法 %}
如何定义一个接口的格式【大驼峰命名】
  • 格式

    1
    2
    3
    4
    5
    6
    7
    8
    public interface interfacce-name {}

    // 如果是 java7,那么接口中可以定义包含的内容有: 常量、抽象方法

    // 如果是 java8,那么接口中可以额外包含内容有: 默认方法、静态方法

    // 如果是 java9,那么接口中可以额外包含内容有: 私有方法

接口的抽象方法的定义:
  • 在任何版本中的 java 中,接口都能定义抽象方法

  • 抽象方法的定义格式:

    1
    public abstract 返回值类型 方法名称(参数列表);
    • 注意事项:
      1. 接口当中的抽象方法 修饰符必须是两个固定的关键字 public abstract
      2. 这两个关键字修饰符,可以选择性的省略
      3. 方法的三要素可以根据业务发生改变
接口的使用步骤:
  1. 接口不能直接使用,必须有一个` 实现类 %}来实现接口

    1
    public class 实现类名称 implements 接口名称 {}
  2. 接口的实现类必须覆盖重写接口中的所有的抽象方法

    1
    去掉 abstract 关键字 加上方法体的大括号
  3. 创建实现类的对象,进行使用

  4. 图示:

  5. 注意事项: 如果实现类并没有覆盖重写接口中所有的抽象方法,那么这个实现类自己就必须是抽象类

接口的默认方法
  • Java8 开始、接口里允许定义默认方法

  • 格式:

    1
    2
    3
    4
    5
    public default 返回值类型 方法名称(参数列表){
    // 方法体
    }

    备注: 接口当中的默认方法可以解决接口升级的问题
接口的静态方法的
  • Java8 开始、接口里允许定义静态方法

  • 格式:

    1
    2
    3
    4
    5
    6
    public static 返回值类型 方法名称(参数列表){
    // 方法体
    }

    提示: 就是将 abstract 或者 default 替换成 static 再带上方法体

  • 使用:

    • 注意: 不能通过接口的实现类的对象来调用静态方法
    • 正确做法: 接口名称.静态方法名(参数列表);

接口的私有方法

问题描述: 我们需要抽取一个共有的方法,用来解决两个默认方法之间重复代码的问题,但是这个共有方法不应该让实现类使用,应该是私有化的

  • 解决方案

    1
    从 java9 开始: 接口当中允许定义私有话的方法
    1. 普通私有化方法: 解决多个默认方法之间重复的代码

      1
      private 返回值类型 方法名称(参数列表){}
    2. 静态私有方法: 解决多个静态方法之间重复的代码问题

      1
      private static 返回值类型 方法名名称(参数列表){}
继承父类并实现多个接口
  • 使用接口的时候需要注意

    1. 接口是没有 静态代码块或者构造方法

    2. 一个类的直接父类是唯一的,但是一个类可以同时实现多个接口

      1
      2
      3
      public class class-name implements interface1,interface2 {
      // 覆盖重写父类所有的抽象方法
      }
    3. 如果实现类所实现的多个接口当中,存在重复的抽象方法,那么只需要覆盖重写一次即可。

    4. 如果实现类没有覆盖重写所有接口当中的抽象方法,那么实现类就必须是一个抽象类

    5. 如果实现类实现的多个接口当中,存在重复的默认方法,那么实现类一定要对冲突的默认方法进行覆盖重写

    6. 一个类如果直接父类当中的方法和接口当中的默认方法产生了冲突,优先用父类当中的方法

注意事项:
  1. 多个父接口当中的抽象方法如果重复,没关系
  2. 多个父接口当中的默认方法如果重复,那么子接口必须进行默认方法的覆盖重写,而且带着default 关键字

多态

基本使用
  • 使用

    1
    2
    3
    4
    5
    面向对象的三大特征: 封装、继承、多态

    extends 或者 implements 实现是多态性的前提

    一个对象拥有多种形态这就是对象的多态性
  • 代码当中体现多态性,其实就是一句话父类引用指向子类对象

    1
    2
    3
    4
    5
    6
    7
    8
    父类名称 对象名 = new 子类名称(); 
    Person p = new Man();

    或者

    接口名称 对象名 = new 实现类名称();
    Person person = new PersonImpl();

对象的向上转型
  • 向上转型,其实就是多态用法

    1
    2
    3
    Animal animal = new Cat();

    注意: 向上转型一定是安全的,从小范围转向了大范围,从小范围的猫,向上转型为更大范围的动物
  • 对象的向下转型

    1
    2
    3
    4
    5
    6
    格式: 子类名称 对象名 = (子类名称)父类对象;
    Cat cat = (Cat)animal;

    含义: 将父类对象,还原成为本来类的子类对象
    Animal animal = new Cat(); // 本来是 猫,向上转型为了猫
    Cat cat = (Cat)animal; // 本来是猫,已经被当作了动物了,还原成为本来的猫
    • 注意事项
      1. 必须保证对象本来创建的时候,就是猫,才能向下转型为猫
      2. 如果对象创建的时候本来不是猫,现在非要它向下转型为猫,就会报错
  • instanceof 关键字的使用

    • a instanceof A: 判断对象 a 是否是A的实例,如果返回是 true,否则返回 false 使用情景: 为了避免向下转型时出现 `classException`的异常,我们向下转型之前先进行 ` instanceof `的判断,一旦返回 `true`,就进行向下转型,如果返回了 `false`,不进行向下转型 + 如果 ` a instanceof A: `返回 `true`,则 ` a instanceof B` 也返回` true`,{% emp 其中 类 B 是类A的父类

单元测试方法的使用

  • src同级目录下,新建文件夹test

  • test文件夹右键 –> Mark Directory as –> Test Resources Root

  • 创建,进行单元测试:

    • 此类必须是 public权限
    • 此类的 返回值类型只能是void
  • 此类中声明单元测试方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import org.junit.Test;

    public class Order {
    @Test
    public void testEquals(){
    System.out.println("测试 equals");
    }
    }

    注意: 方法的权限是 public ,没有返回值(只能是 void)没有形参
  • 此单元测试方法上需要声明注解

    1
    @Test并在单元测试中导入: import org.junit.Test
  • 执行测试

final 关键字

  • final 关键字代表 最终,不可改变的

  • final 关键字修饰类:

    1
    2
    3
    public final class 类名称{}
    含义: 当前这个类不能有任何的子类,但是有父类
    注意: 一个类如果是 final 的,那么其中所有的成员方法都无法进行覆盖重写
  • final 修饰成员方法

    final 关键字用修饰一个方法的时候,这个方法就是最终方法,不能进行覆盖重写

    1
    2
    3
    修饰类 final 返回值类型 方法名称(参数列表){}

    注意事项: 对于类、方法来说,abstract 关键字和 final 关键字不能同时使用
  • final 关键字修饰局部变量

    一旦使用 final 用来修饰局部变量,那么这个变量就不能进行更改,

    对于基本数据类型来说,不可变说的是变量当中的数据不可改变

    对于引用类型来说,不可变说的是变量当中的地址值不可改变

  • final 关键字修饰成员变量

    1. 由于成员变量具有默认值,所以用了 final 之后就必须手动赋值
    2. 对于final的成员变量,要么使用直接赋值,要么通过构造方法赋值
    3. 必须保证类当中所有重载的构造方法都最终会对final的成员变量进行赋值
  • 四种权限修饰符

    是否可访问 public protected default private
    同一个类 yes yes yes yes
    同一个包 yes yes yes X
    不同包子类 yes yes X X
    不同包非子类 yes X X X

常用 API

System
  • System也是一个工具类,提供了一些与系统相关的方法

  • 常用方法

    方法名 说明
    public static void exit(int status) 终止当前运行的 Java 虚拟机
    public static long currentTimeMillis() 返回当前系统的时间毫秒值形式
    arraycopy() 拷贝数组
Runtime
  • Runtime表示当前虚拟机的运行环境

  • 常用方法

    方法 说明
    public static Runtime getRuntime() 当前系统
    public void exit(int status) 停止虚拟机
    publi int availableProcessors() 获得CPU的线程数
    publi long maxMemory() JVM能从系统中获取总内存大小(单位byte)
    public long totalMemory() JVM已经从系统中获取总内存大下(单位byte)
Object
  • ObjecttoStrinh方法

    方法名 说明
    public String toString() 默认是返回当前对象在堆内存中的地址信息:类的全限定名@内存地址
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    package com.example;

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

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

    @Override
    public String toString() {
    return "Student{" +
    "name='" + name + '\'' +
    ", age=" + age +
    '}';
    }
    }

    class Test {
    public static void main(String[] args) {
    Student student = new Student("coder-itl", 19);
    // com.example.Student@6bc7c054
    System.out.println(student);
    }
    }

  • equals

    • equals默认比较地址值

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      // == 引用类型比较地址值

      class Test {
      public static void main(String[] args) {
      Student student1 = new Student("coder-itl", 19);
      Student student2 = new Student("coder-itl", 19);
      // ==: false
      System.out.println(student1 == student2);
      // equals: false(未重写equals时)
      System.out.println(student1.equals(student2));
      }
      }

    • 存在的意义: 父类equals方法存在的意义就是为了被子类重写,以便子类自己来定制比较规则

第七章

异常

throw 关键字

throw 关键字:

  • 作用: 可以使用 throw关键字在指定的方法中抛出指定异常

  • 使用格式: throw new xxxException(“异常产生的原因”)

  • 注意:

    • throw关键字必须写在方法的内部
    • throw关键字后边的 new 的对象必须是Exception 或者 Exception的子类对象
    • throw 关键字抛出指定的异常对象,我们就必须处理这个异常对象
      • throw 关键字后边创建的是 RuntimeException或者是 RuntimeException的子类对象,我们可以不处理,默认交给JVM处理(打印异常对象,中断程序)
      • throw关键字后边创建的是编译异常(写代码时报错),我们就必须处理这个异常,要么thows,要么try...catch
  • throw基本使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    package com.coderitl;

    public class Main {

    public static void main(String[] args) {
    // int[] array = null;
    int[] array = {1, 2, 3};
    int value = exceptionArray(array, 4);
    System.out.println(value);
    }

    public static int exceptionArray(int[] array, int index) {
    /*
    * 我们可以对传递过来的参数数组,进行合法性校验
    * 如果数组值是 null
    * 我们就抛出空指针异常,告知方法的调用者 "传递的数组的值是 null"
    * */
    if (array == null) {
    throw new NullPointerException("传递的数组的值是 null");
    }
    // 下标越界异常
    if (index < 0 || index > array.length - 1) {
    throw new IndexOutOfBoundsException("数组越界");
    }
    int value = array[index];
    return value;
    }
    }

throws 关键字

throws 关键字:

作用:

  • 当方法内部抛出异常对象的时候,那么我们就必须处理这个异常对象

  • 可以使用 throws关键字处理异常对象,会把异常对象声明抛出给方法的调用者处理,最终交给 JVM处理 –> 中断处理

  • 使用格式:

    1
    2
    3
    4
    5
    修饰符 返回值类型 方法名(参数列表) throws AAAException,BBBException...{
    throw new AAAException("产生原因");
    throw new BBBException("产生原因");
    ...
    }
  • 注意:

    1. throws 关键字必须卸载方法声明出
    2. throws 关键字后变声明的异常必须是Exception或者是 Exception的子类
    3. 方法内部如果抛出了多个异常对象,那么直接声明父类异常即可,如果抛出的多个异常对象有子父类关系,那么直接声明父类异常即可
    4. 调用了一个声明抛出异常的方法,我们就必须处理声明的异常,要么继续使用 throws 声明抛出,交给方法的调用者处理,最终交给 JVM,要么 try...catch自己处理异常
  • throws基本使用

    throws.png

    try...catch.png

try…catch(捕获异常)
1
2
3
4
5
6
7
8
9
10
11
12
try{
// 可能出现异常
}
catch(异常类型 变量名){
// 处理
}
finaly{}

1. try 中可能会抛出多个异常对象,那么就可以使用多个 catch 来处理这些异常对象
2. 如果 try 中产生了异常,那么就会执行 catch 中的异常处理逻辑,执行完毕 catch 中的处理逻辑,继续执行 try..catch之后的代码
如果 try 中没有产生异常,那么就不会执行 catch 中的异常处理逻辑,执行完 try 中的处理逻辑,继续执行 try..catch之后的代码

自定义异常

自定义异常类: Java 提供的异常类,不够我们使用,需要自定义一些异常类

格式:

1
2
3
4
5
6
7
8
9
10
11
 public class XXXException extends Exception | RuntimeException{
添加一个空参数的构造方法
添加一个带异常信息的构造方法
}

注意:
1. 自定义异常类一般都是以 Exception 结尾,说明该类是一个异常类
2. 自定义异常类,必须继承 Exception 或者 RuntimeException
继承 Exception: 那么自定义的异常类就是一个编译期异常,如果方法内部抛出编译器异常,就必须处理这个异常,要么 throws 要么 try...catch

继承 RuntimeException: 那么自定义的异常类就是一个运行期异常,无需处理,交给虚拟机处理(中断处理)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.coderitl;

/**
* @Auther: coder-itl
* @Date: 2021/7/30 18:29
* @Description: com.coderitl
* @version: 1.0
*/
public class RegisterException extends Exception {
public RegisterException() {
super();
}

public RegisterException(String message) {
super(message);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

// 使用自定义异常类
package com.coderitl;


import java.util.Scanner;

public class UseLogin {
public static void main(String[] args) {
// 已经注册的用户名,用于登录使用
String[] register = new String[]{"coder-itl", "bin", "moli"};
try {
getRegisterName(register);
} catch (RegisterException e) {
e.printStackTrace();
}

}

public static void getRegisterName(String[] array) throws RegisterException {
// 使用 scanner 输入用户名
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名: ");
String userName = sc.next();

for (int i = 0; i < array.length; i++) {
if (array[i].equals(userName)) {
throw new RegisterException("亲,该用户已经被注册");
}
}
throw new RegisterException("亲,恭喜您注册成功!");
}
}

多线程

多线程的概念和优点
  • 基本概念
    • 程序: 是为了完成特定任务,用某种语言编写的一组指令的集合,即指一段静态的代码,静态对象
    • 进程: 是程序的一次执行过程,是正在进行的一个程序
    • 线程: 进程可进一步细化为线程,是一个程序内部的一条执行路径
  • 多线程的优点
    1. 提高程序的响应,对图形化界面更具有意义
    2. 提高计算机流 CPU的利用率
    3. 改善程序结构,将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
多线程使用的方式一
  • 实现步骤

    1. 创建一个继承于Thread类的子类
    2. 重写Thread 类的 run方法,主要针对方法体重写
    3. 创建Thread类的子类对象
    4. 通过此对象调用start()
  • 具体实现

    • 继承Thread

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      package com.example;

      // 1. 继承
      public class MyThread extends Thread {
      // 2. 重写 run 方法
      @Override
      public void run() {
      for (int i = 0; i < 100; i++) {
      System.out.println(getName() + ": " + i);
      }
      }
      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      package com.example;

      public class MainThread {
      public static void main(String[] args) {
      // 3. 创建 Thread 子类对象
      MyThread myThread1 = new MyThread();
      MyThread myThread2 = new MyThread();
      myThread1.setName("myThread1");
      myThread2.setName("myThread2");
      // 4. 通过子类对象调用 start 方法
      myThread1.start();
      myThread2.start();
      }
      }

    • 问题一: 我们不能通过直接调用 run()的方式启动线程

    • 问题二: 再启动一个线程,遍历100以内的偶数,不可以让已经 start() 的线程去执行,会报IllegalThreadStateException

  • 线程的调度

    • 调度策略:抢占式: 高优先级的线程抢占 CPU
    • Java的调度方法
      • 同优先级线程组成先进先出队列(先到先服务),使用时间片策略
      • 对高优先级,使用优先调度的抢占式策略
多线程使用的方式二
  • 实现Runnable接口

  • 具体实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    package com.coderitl.exer;

    import java.util.ArrayList;

    // 1. 创建 Runnable 的实现类
    public class RunnableImpl implements Runnable {
    // 2. 重写 run 方法
    @Override
    public void run() {
    // 多线程执行的任务
    ArrayList<Integer> list = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
    if (i % 2 == 0) {
    list.add(i);
    }
    }
    System.out.println(list);
    }
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    package com.coderitl.exer;

    public class RunnableMain {
    public static void main(String[] args) {
    // 3. 创建实现类的对象
    RunnableImpl impl = new RunnableImpl();
    // 4. 将此对象作为参数传递到 Thread 类的构造器中,创建Thread类的对象
    Thread t1 = new Thread(impl);
    // 5. 通过 Thread 类的对象调用 start()
    // start作用:
    // 1. 启动线程
    // 2. 调用当前线程的 run(),调用了 Runnable 类型的 target 的 run()
    t1.start();
    }
    }

多线程使用的方式三
  • 实现步骤

    • 创建一个自定义的类MyCallable实现Callable接口
    • 重写call是有返回值的,表示多线程运行的结果
    • 创建MyCallable的对象(表示多线程要执行的任务)
    • 创建FutureTask的对象(作用:管理多线程运行的结果)
    • 创建Thread类的对象,并启动(表示线程)
  • 具体实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package com.example;

    import java.util.concurrent.Callable;

    // 1. 自定义类实现 Callable
    public class MyCallable implements Callable {
    // 2. 重写 call 方法,这个返回值类型是可以自定义的
    @Override
    public Integer call() throws Exception {
    // 任务: 多线程执行的任务(求 1~100以内的和)
    int sum = 0;
    for (int i = 0; i <= 100; i++) {
    sum += i;
    }
    return sum;
    }
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    package com.example;

    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;

    public class MainThread {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    // 3. 创建 MyCallable 的对象(表示多线程要执行的任务)
    MyCallable myCallable = new MyCallable();
    // 4. 创建 FutureTask 的对象(作用: 管理多线程运行的结果)
    FutureTask<Integer> ft = new FutureTask<>(myCallable);
    // 5. 创建多线程的对象
    Thread t1 = new Thread(ft);
    t1.start();
    // 6. 获取多线程运行的结果
    Integer sum = ft.get();
    System.out.println("1~100以内的和: " + sum);
    }
    }

多线程使用的方式四
  • 背景: 经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大

  • 思路: 提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁创建销毁、实现重复利用

  • 好处

    • 提高响应速度(减少了创建线程的时间)
    • 降低资源消耗(重复利用线程池中线程,不需要内次都创建)
    • 便于线程管理
      • corePoolSize: 核心池的大小
      • maximumPoolSize: 最大线程数
      • keepAliveTime: 线程没有任务时最多保持多长时间后会终止
  • Executors工具类、线程池的工厂类,用于创建并返回不同类型的线程池

    • Executors.newCachedThreadPool(): 创建一个可根据需要创建创建新线程的线程池
    • Executors.newFixedThreadPool(int num): 创建一个可重用固定线程数的线程池
    • Executors.newSingleThreadExecutor() : 创建一个只有一个线程的线程池
    • Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期的执行
  • 实现步骤

    1. 创建线程池
    2. 提交任务
    3. 所有的任务全部执行完毕,关闭线程池
  • 线程池的基本使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    package com.coderitl.thread;

    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;

    class ThreadRunnableImpl implements Runnable {
    @Override
    public void run() {
    int sum = 0;
    for (int i = 0; i < 10; i++) {
    if (i % 2 == 0) {
    sum += i;
    }
    }
    System.out.println("10以内偶数计算总和为: " + sum);
    }
    }

    // 线程池的使用
    public class ThreadPoll {
    public static void main(String[] args) {
    // 1. 提供指定线程数量的线程池
    ExecutorService service = Executors.newFixedThreadPool(10);
    // 2. 执行指定的线程的操作 需要提供实现 Runnable 接口或 Callable接口实现类的对象
    service.execute(new ThreadRunnableImpl()); // 适合适用于 Runnable
    // 线程池的关闭
    service.shutdown();
    }
    }

  • 线程池主要核心原理

    1. 创建一个池子,池子中是空的
    2. 提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子,下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可
    3. 但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待。
  • 关于同步方法的总结

    • 同步方法仍然涉及到同步监视器,只是不需要我们显示的声明
    • 非静态的同步方法,同步监视器是:this
    • 静态的同步方法,同步监视器是: 当前类本身
前三种线程使用如何选择
  • 对比

    实现方式 优点 缺点
    继承Thread 编程比较简单,可以直接使用Thread类中的方法 可以扩展性较差,不能再继承其他的类
    实现Runnable接口 扩展性强,实现该接口的同时还可以继承其他的类 编程相对复杂,不能直接使用Thread类中的方法
    实现Callable接口 扩展性强,实现该接口的同时还可以继承其他的类 编程相对复杂,不能直接使用Thread类中的方法
  • 如何理解 Callable接口的方式创建多线程比实现 Runnable接口创建多线程方式强大?

    1. call() 可以有返回值
    2. call 可以抛出异常,被外面的操作捕获,获取异常的信息
    3. callable是支持泛型的
线程常用的成员方法
  • 线程常用方法

    1. void start(): 启动线程,并执行对象的 run()方法
    2. run(): 线程在被调度时执行的操作
    3. String getName(): 返回线程的名称
    4. void setName(String name): 设置线程名
    5. Thread currentThread() : 返回当前线程,在Thread子类中就是 this,通常用于主线程和 Runnable 实现类 xxImpl
    6. static void yield(): 设置线程名
    7. join(): 当某个程序执行流中调用其他线程的 join()方法时,调用线程将被阻塞,直到join()方法加入的join线程执行完为止
    8. static voidsleep(long millis): 指定时间:毫秒
    9. 令当前活动线程在指定时间段内放弃对 CPU 控制,使其他线程有机会被执行,时间到后重排对
    10. 抛出InterruptedException
    11. stop(): 强制线程生命期结束,不推荐使用
    12. boolean isAlive(): 返回 boolean,判断线程是否还活着
多线程的生命周期
  1. 新建:当一个 Thread 类 或其子类的对象被声明并创建时,新生的线程对象处于新建状态

  2. 就绪: 处于新建状态的线程被 start()后,将进入线程队列等待 CPU 的时间片,此时它已具备了运行的条件,只是没有分配到 CPU 资源

  3. 运行: 当就绪的线程被调度并获得 CPU 资源时,便进入运行状态, run ()方法定义了线程操作和功能

  4. 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让CPU并临时中止自己的执行,进入 阻塞状态

  5. 死亡: 线程完成了它的全部或线程被提前强制性地中止或异常导致结束

    • 疑问: sleep方法会让线程睡眠,睡眠时间到了之后,立马就会执行下面的代码吗?

      不会,因为等休眠时间到了之后会进入就绪状态,是需要抢占CPU资源的,只有抢占到CPU资源才能获得执行权

线程的安全问题
问题地题出
  • 多个线程执行地不确定性引起执行结果的不稳定
  • 多个线程对账户的共享,会造成操作的不完整性,会破坏数据
线程安全问题的解决措施
  • 同步代码块

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    /**
    *
    说明:
    1. 操作共享数据的代码,即为需要同步的代码
    2. 共享数据: 多个线程共同操作的变量,比如 ticket 就是共享数据
    3. 同步监视器,所称: 锁 任何一个类的对象,都可以充当 锁
    要求: 多个线程必须要共用同一把 锁


    同步的方式,解决了线程安全的问题 -- 好处
    操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低 --- 局限性
    */
    package com.example;

    // 1. 自定义类实现 Callable
    public class SellingTickets implements Runnable {

    int tickets = 0;
    // 锁对象必须是唯一的
    static Object object = new Object();

    @Override
    public void run() {
    while (true) {
    synchronized (SellingTickets.class) {
    if (tickets < 100) {
    try {
    Thread.sleep(100);
    } catch (InterruptedException e) {
    throw new RuntimeException(e);
    }
    ++tickets;
    System.out.println(Thread.currentThread().getName() + "正在售卖第 " + tickets + " 张票");
    } else {
    break;
    }
    }
    }
    }
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    package com.example;

    import java.util.concurrent.ExecutionException;

    public class MainThread {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    SellingTickets window = new SellingTickets();

    Thread t1 = new Thread(window);
    t1.start();
    t1.setName("windows1");

    Thread t2 = new Thread(window);
    t2.start();
    t2.setName("windows2");


    Thread t3 = new Thread(window);
    t3.start();
    t3.setName("windows3");
    }
    }

  • 同步方法

    如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的

    1
    2
    3
    4
    5
    6
    7
    /**
    * 同步方法的总结:
    * 1. 同步方法任然涉及到同步监视器,只是不需要我们显示的声明
    * 2. 非静态的同步方法,同步监视器是: this
    * 静态的同步方法,同步监视器是: 当前类本身
    */

    • 特点

      • 特点1:同步方法是锁住方法里面所有的代码
      • 特点2:锁对象不能自己指定
    • 锁对象

      • 如果方法是非静态的锁对象就是 this
      • 如果方法是静态的锁对象就是 当前类的字节码文件对象
    • 语法

      1
      修饰符 synchronized 返回值类型 方法名(方法参数) {}
    • 实现

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      package com.example;

      // 1. 自定义类实现 Callable
      public class SellingTickets implements Runnable {

      int tickets = 0;
      // 锁对象必须是唯一的
      static Object object = new Object();

      @Override
      public void run() {
      while (true) {
      if (extracted()) break;
      }
      }

      // 先使用同步代码块实现再修改为同步方法的实现
      // 非静态的就是 this
      private synchronized boolean extracted() {
      if (tickets < 100) {
      try {
      Thread.sleep(100);
      } catch (InterruptedException e) {
      throw new RuntimeException(e);
      }
      ++tickets;
      System.out.println(Thread.currentThread().getName() + "正在售卖第 " + tickets + " 张票");
      } else {
      return true;
      }
      return false;
      }
      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      package com.example;

      import java.util.concurrent.ExecutionException;

      public class MainThread {
      public static void main(String[] args) throws ExecutionException, InterruptedException {
      SellingTickets window = new SellingTickets();

      Thread t1 = new Thread(window);
      t1.start();
      t1.setName("windows1");

      Thread t2 = new Thread(window);
      t2.start();
      t2.setName("windows2");


      Thread t3 = new Thread(window);
      t3.start();
      t3.setName("windows3");
      }
      }

  • synchronizedlock的异同?

    • 相同: 二者都可以解决线程安全问题
    • 不同: synchronized 机制在执行完相应的同步代码以后,自动的释放同步监视器,lock需要手动的启动同步(lock( )),同时结束同步也需要手动的实现(unlock( ))
Lock-锁
  • 使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    package com.example;

    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;

    // 1. 自定义类实现 Callable
    public class SellingTickets implements Runnable {

    int tickets = 0;
    // 锁: Lock 是一个接口,ReentrantLock是一个实现类,使用的时候需要注意实现多线程的方式
    Lock lock = new ReentrantLock();

    @Override
    public void run() {
    while (true) {
    // 手动上锁
    lock.lock();
    try {
    if (tickets < 300) {
    Thread.sleep(10);
    tickets++;
    System.out.println(Thread.currentThread().getName() + "正在售卖第 " + tickets + " 张票");
    } else {
    break;
    }
    } catch (InterruptedException e) {
    throw new RuntimeException(e);
    } finally {
    // 手动释放锁
    lock.unlock();
    }
    }
    }
    }

死锁
  • 死锁
    • 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
    • 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
  • 解决方法
    • 专门的算法、原则
    • 尽量减少同步资源的定义
    • 尽量避免嵌套同步
线程的通信
  • 涉及到的方法

    1. wait( ): 一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
    2. notify( ): 一旦执行此方法,就会唤醒被 wait的一个线程,如果有多个线程被 wait,就唤醒优先级高的
    3. notifyAll( ): 一旦执行此方法,就会唤醒所有被 wait的线程

    注意:

    1. wait()notify()notifyAll()、三个方法必须使用在同步代码块或同步方法中
    2. wait() notify()notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器【Eg. 要么都是 this,要么就是同一对象】,否则会出现 IllegalMonitorStateException异常
    3. wait()notify()notifyAll()三个方法定义在 java.lang.Object类中
  • 面试题

    • sleep()wait()的异同?
    • 相同点: 一旦执行此方法,都可以使得线程进入阻塞状态
    • 不同点:
      1. 两个方法的声明位置不同,Thread类中声明 sleep,Object类中声明 wait( )
      2. 调用的要求不同: sleep( ) 可以在任何需要的场景下调用, wait( )方法必须在同步代码块中
      3. 关于是否释放同步监视器: 如果两个方法都使用在同步代码块或同步方法中,sleep( ) 不会释放锁,wait( ) 会释放锁
  • 生产者和消费者(等待唤醒机制)

    • 控制生产者和消费者的执行

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      package com;

      /**
      * 作用: 控制生产者和消费者的执行
      */
      public class Desk {
      // 是否有面条: 0: 没有 1: 有面条
      public static int foodFlag = 0;

      // 总个数
      public static int count = 10;

      // 锁对象
      public static Object lock = new Object();
      }

    • 消费者

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      package com;

      /**
      * 消费者
      */
      public class Foodie extends Thread {
      /**
      * 1. 循环
      * 2. 同步代码块
      * 3. 判断共享数据是否到了末尾(到了末尾)
      * 4. 判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
      */
      @Override
      public void run() {
      // 1. 循环
      while (true) {
      // 2. 同步代码块
      synchronized (Desk.lock) {
      if (Desk.count == 0) {
      break;
      } else {
      // 3. 判断共享数据是否到了末尾
      if (Desk.foodFlag == 0) {
      // 桌子上没有食物(等待),厨师准备制作食物
      try {
      // 让当前线程跟锁进行绑定
      Desk.lock.wait();
      } catch (InterruptedException e) {
      throw new RuntimeException(e);
      }
      } else {
      // 桌子上有食物,吃货准备吃饭
      Desk.count--;
      // 核心逻辑
      System.out.println("吃货还能吃" + Desk.count + "碗!!!");
      // 告诉厨师需要做饭了
      Desk.lock.notifyAll();
      // 修改桌子的状态(桌子上再次没有食物了)
      Desk.foodFlag = 0;
      }
      }
      }
      }
      }
      }

    • 生产者

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      package com;

      /**
      * 生产者
      */
      public class Cook extends Thread {
      @Override
      public void run() {
      while (true) {
      synchronized (Desk.lock) {
      if (Desk.count == 0) {
      break;
      } else {
      // 检查桌子上有食物吗
      if (Desk.foodFlag == 1) {
      try {
      // 桌子上有食物,等待
      Desk.lock.wait();
      } catch (InterruptedException e) {
      throw new RuntimeException(e);
      }
      } else {
      // 如果没有就制作食物
      System.out.println("厨师做了一碗苗条!!!");
      // 修改桌子上食物的状态
      Desk.foodFlag = 1;
      // 唤醒消费者去吃
      Desk.lock.notifyAll();
      }
      }
      }
      }
      }
      }

    • 启动

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      package com;

      public class MainThread {
      public static void main(String[] args) {
      Foodie foodie = new Foodie();
      foodie.setName("吃货一号");
      foodie.start();

      Cook cook = new Cook();
      cook.setName("厨师");
      cook.start();
      }
      }

线程的状态
  • 没有运行状态

    线程的状态
    VZNeEy.png
线程问题总结
  1. 谈谈你对程序、进程、线程的理解
  2. 代码完成继承 Thread 的方式创建分线程,并遍历 100 以内的自然数
  3. 代码完成实现 Runnable 接口的方法创建分线程,并变量 100以内的自然数
  4. 对比两种创建方式
  5. 谈谈你对 IDEA ProjectModule 的理解
多线程练习
卖电影票
  • 需求

    一共有1000张电影票,可以在两个窗口领取,假设每次领取的时间为3000毫秒

    要求: 请用多线程模拟卖票的过程并打印剩余电影票的数量

送礼品
  • 需求

    100份礼品,两人同时发送,当剩下的礼品小于10份的时候则不再送出

    利用多线程模拟该过程并将线程的名字和礼物的剩余数量打印出来

打印奇数数字
  • 需求

    同时开启两个线程,共同获取1~100之间的所有数字

    要求: 输出所有的奇数

抢红包
  • 需求

    假设:100快,分成了3个包,现在有5个人去抢,其中,红包就是共享数据,5个人就是5条线程。

    打印结果如下:

    xxx 抢到了xxx

    xxx 抢到了xxx

    xxx 抢到了xxx

    xxx没抢到

    xxx没抢到

Lambda-表达式

  • 基本使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class LambdaTest {
    public static void main(String[] args) {
    // 简化 Runnable 实现多线程
    new Thread(() -> {
    // run方法体
    System.out.println(Thread.currentThread().getName());
    }).start();
    }
    }
  • lambda解释

    1. 一些参数

    2. 一个箭头

    3. 一段代码

      1
      2
      3
      4
      5
      格式: (参数列表) -> {一些重写方法的代码}
      解释说明格式:
      (): 接口中抽象方法的参数列表,没有参数,就空着,有参数就写出参数,多个参数使用逗号分割
      ->: 传递的意思,把参数传递给方法体
      {}: 重写接口的抽象的方法体
  • 练习掌握lambda

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    // 给定一个 Cook 接口,内含唯一的抽象方法 makeFood,切无参数,无返回值

    ---------------------- 接口 ------------------------------
    package com.coderitl;

    public interface Cook {
    public abstract void makeFood();
    }

    ---------------------- 实现 ------------------------------
    package com.coderitl;


    public class CookImpl {


    public static void main(String[] args) {

    invoCook(new Cook() {
    @Override
    public void makeFood() {
    System.out.println("匿名-未做饭");
    }
    });


    // lambda 实现上述操作
    invoCook(() -> {
    System.out.println("Lambda - 做饭了?");
    });
    }

    private static void invoCook(Cook cook) {
    cook.makeFood();
    }

    }

  • 年龄排序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    前提实现 JavaBin
    package com.coderitl;


    import java.util.Arrays;
    import java.util.Comparator;

    public class SortAge {
    public static void main(String[] args) {
    PersonInfo[] person = {
    new PersonInfo("name11", 2),
    new PersonInfo("name12", 19),
    new PersonInfo("name13", 13)
    };
    // 对数组中的 PersonInfo 对象使用 Arrayssort 方法通过年龄进行降序(o2 - 01 | -(o1-o2) )排序
    Arrays.sort(person, new Comparator<PersonInfo>() {
    @Override
    public int compare(PersonInfo o1, PersonInfo o2) {
    return -(o1.getAge() - o2.getAge());
    }
    });
    // 遍历数组输出
    for (PersonInfo p : person) {
    System.out.println(p);
    }
    }
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    Arrays.sort(person,(PersonInfo o1, PersonInfo o2)->{
    return -(o1.getAge() - o2.getAge());
    })

    遍历输出:
    PersonInfo{name='name12', age=19}
    PersonInfo{name='name13', age=13}
    PersonInfo{name='name11', age=2}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    package com.coderitl;


    public class CalculatorImpl {
    public static void main(String[] args) {
    // TODO: 使用 Lambda 实现 有参有返回值

    // 1. 使用匿名内部类实现
    invokeCalc(10, 20, new Calculator() {
    @Override
    public int calc(int a, int b) {
    return a + b;
    }
    });

    // 2. 使用 Lambda 实现
    invokeCalc(12, 20, (int a, int b) -> {
    return a + b;
    });
    }

    public static void invokeCalc(int a, int b, Calculator c) {
    int result = c.calc(a, b);
    System.out.println("方法调用的结果是: " + result);
    }
    }

    Lambda.png

第八章

Java 常用类

String - 字符串补充

String: 声明为 final 的,不可被继承

String 实现了 Serializable 接口: 表示字符串是支持序列化的

实现了 Comparable 接口: 表示字符串String 可比较大小

String 内部定义了 final char[] value用于存储字符串数据

String 代表不可变的字符串序列。简称: 不可变性

String、StringBuffer、StringBuilder三者的异同?
  • String: 不可变的字符串序列

  • StringBuffer: 可变的字符串序列;线程安全的、效率低

  • StringBuilder: 可变的字符串序列;jdk5.0新增,线程不安全,效率高

  • 三者的相同点: 底层使用 char[ ] 存储

  • 可变的基础理解

    1
    2
    3
    4
    5
    6
    7
    @Test
    public void testStringBuffer() {
    StringBuffer str = new StringBuffer("abc");
    str.setCharAt(0, 'm');
    System.out.println(str); // mbc
    }

StringBuffer-StringBuilder 常用方法
  • 常用方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    /*
    * StringBuffer 常用方法:
    * append(xxx) 提供了很多的 append() 方法,用于进行字符串拼接
    * delete(int start,int end) 删除指定位置的内容
    * replace(int start,ent end,String str) 把 [start,end) 位置替换为 str
    * insert(int offset,xxx) 在指定位置 插入: xxx
    * reverse() 把当前字符串序列逆转
    *
    * */
JDK8-之前日期时间API
  • System类中地 currentTimeMillis()

    1
    2
    3
    long time = System.currentTimeMillis();
    // 返回当前时间与 1970 年 1 月 1 日0 时0分 0秒之间以毫秒为单位的时间差 ,称为时间戳
    System.out.println(time);
  • Date

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    @Test
    public void date01() {
    /* import java.util.Date;
    * java.util.Date
    * |--- java.sql.Date
    * 1. 两个构造器地使用
    * 2. 两个方法地使用
    * > toString(); 显示当前年 月 日 时 分 秒
    * > getTime(); 获取当前Date对象地毫秒数(时间戳)
    * 3. Java.sql.Date 对应数据库中地日期类型地变量
    * 怎么实例化对象?
    * 如何将 java.util.Date 对象转换为 java.sql.Date 对象
    * */
    // 构造器一: Date() 创建一个对应当前时间地 Date 对象
    Date date1 = new Date();
    System.out.println(date1.toString()); // Sat Jul 24 16:51:56 CST 2021
    System.out.println(date1.getTime());

    // sqlDate 实例化
    java.sql.Date sqlDate = new java.sql.Date(21832480385L);
    System.out.println(sqlDate);

    // 如何将 java.util.Date 对象转换为 java.sql.Date 对象
    Date datec = new Date();
    java.sql.Date dates = new java.sql.Date(datec.getTime());
    System.out.println(dates);
    }
  • SimpleDateFormat

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    package com.coderitl.date;

    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;


    public class SimpleDateFormatTest {
    public static void main(String[] args) throws ParseException {
    SimpleDateFormat sdf = new SimpleDateFormat();
    Date date = new Date();
    // 将日期转换为字符串
    String formatDate = sdf.format(date);
    System.out.println(formatDate);

    // 将字符串转换为日期
    Date parseDate = sdf.parse(formatDate);
    System.out.println(parseDate);

    // 指定格式
    SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
    String formatSelf = sdf1.format(date);
    System.out.println(formatSelf);
    }
    }

  • Calendar-日历

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    package com.coderitl.date;

    import java.util.Calendar;


    public class CalendarTest {
    public static void main(String[] args) {
    // 1. 创建其子类(GregorianCalendar)
    // 2. 调用其静态方法
    Calendar calendar = Calendar.getInstance();
    // 常用方法
    // 1. get() 参数: Calendar.DAY_OF_YEAR 一年的第几天
    int days = calendar.get(Calendar.DAY_OF_YEAR);
    System.out.println(days);
    // 2. set()
    // 3. add()
    // 4. getTime()
    // 5. setTime()
    }
    }

JDK8-中新日期时间 API
  • LocalDateLocalTimeLocalDateTime

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    import org.junit.Test;

    import java.time.LocalDate;
    import java.time.LocalDateTime;
    import java.time.LocalTime;

    /**
    * @Auther: coder-itl
    * @Date: 2021/7/25 12:36
    * @Description: PACKAGE_NAME
    * @version: 1.0
    */
    public class DateTest {
    @Test
    public void testLocalDate() {
    LocalDate localDate = LocalDate.now();
    // 获取当前日期
    System.out.println("获取当前日期: " + localDate);
    }

    @Test
    public void testLocalTime() {
    LocalTime localTime = LocalTime.now();
    // 获取当前时间
    System.out.println("获取当前时间: " + localTime);
    }

    @Test
    public void testLocalDateTime() {
    // 常用方法
    LocalDateTime localDateTime = LocalDateTime.now();
    // 获取当前日期 + 时间
    System.out.println(" 获取当前日期 + 时间: " + localDateTime);
    // of(): 设置指定的年月日时分秒没有偏移量
    LocalDateTime ofLocalDateTime = localDateTime.of(2020, 12, 10, 12, 12, 11);
    System.out.println("设置指定时间: " + ofLocalDateTime);
    // 获取当月第几天
    System.out.println(localDateTime.getDayOfMonth());
    System.out.println(localDateTime.getDayOfWeek());
    // ... 上面两个具有相同方法用例
    }
    }

Instant类的使用
  • Instant 时间线上的一个瞬时点。这可能被用来记录应用程序中的事件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    @Test
    public void testInstant() {
    // 实例化 Instant now(): 获取本初子午线对应的标准时间
    Instant instant = Instant.now();
    System.out.println(instant);

    // 根据地区时区差添加偏移量
    OffsetDateTime offsetHours = instant.atOffset(ZoneOffset.ofHours(8));
    System.out.println(offsetHours);

    // 获取子 197011000(UTC)开始的毫秒数
    long milli = instant.toEpochMilli();
    System.out.println(milli);

    }
Java比较器的使用
1
2
3
说明: Java中的对象,正常情况下,只能进行比较: == 或 != 不能使用 > 或 < 的,但是在开发场景中,我们需要对多个对象进行排序,言外之意,就需要比较对象的大小,
如何实现?
使用两个接口中的任何一个: Comparable 或 Comparator

Comparable 接口的使用举例:

  1. String,包装类等实现了 Comparable接口,重写了 compareTo(obj) 方法,给出了比较两个对象大小的方式。
  2. String、包装类等实现了 compareTo() 方法以后,进行了从小到大的排列
  3. 重写 compareTo(obj)的规则:
    • 如果当前对象 this大于形参对象obj,则返回正整数
    • 如果当前对象 this小于形参对象obj,则返回负整数
    • 如果当前对象 this等于形参对象obj,则返回正
  4. 对于自定义类来说,如果需要排序,我们可以让自定义实现类实现 comparable接口,重写 compareTo(), comparaTo(obj)方法中支名如何排序
自定义类实现 comparable 自然排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package com.coderitl.compareto;


public class RewriteCompareTo implements Comparable {
private String name;
private int price;

public RewriteCompareTo() {
}

public RewriteCompareTo(String name, int price) {
this.name = name;
this.price = price;
}

@Override
public String toString() {
return "RewriteCompareTo{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}

public String getName() {
return name;
}

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

public int getPrice() {
return price;
}

public void setPrice(int price) {
this.price = price;
}

// 实现接口 Comparable 并重写 compareTo 方法,指明比较方式(按照价格从小到大的排序)
@Override
public int compareTo(Object o) {
if (o instanceof RewriteCompareTo) {
RewriteCompareTo goods = (RewriteCompareTo) o;
// 排序的方式
if (this.price > goods.price) {
return 1;
} else if (this.price < goods.price) {
return -1;
} else {
// 再按照某顺序排列,Eg: 再根据名称从低到高排序
return -(this.name.compareTo(goods.name));
}
}
// 手动抛出异常
throw new RuntimeException("传入的数据类型不一致");
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.coderitl.compareto;


import java.util.Arrays;

public class MainCompareTo {
public static void main(String[] args) {
// 完成一个 javaben 类后做什么
// 定义一个对应数组
RewriteCompareTo[] arr = new RewriteCompareTo[5];
// 存储比较对象
arr[0] = new RewriteCompareTo("Aava编程艺术", 99);
arr[1] = new RewriteCompareTo("BavaScript编程艺术", 69);
arr[2] = new RewriteCompareTo("Cython编程艺术", 199);
arr[3] = new RewriteCompareTo("D++编程艺术", 89);
arr[4] = new RewriteCompareTo("E++编程艺术", 69);

// 先根据价格从低到高排序,再将名称按照从高到低排序
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
// 输出结果:
[RewriteCompareTo{name='E++编程艺术', price=69},
RewriteCompareTo{name='BavaScript编程艺术', price=69},
RewriteCompareTo{name='D++编程艺术', price=89},
RewriteCompareTo{name='Aava编程艺术', price=99},
RewriteCompareTo{name='Cython编程艺术', price=199}]

}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void testComparator() {
String[] str = new String[]{"AA", "EE", "VV", "DD", "BB"};
Arrays.sort(str, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof String && o2 instanceof String) {
String s1 = (String) o1;
String s2 = (String) o2;
// 实现排序
return -(s1.compareTo(s2));
}
throw new RuntimeException("传入数据类型不一致!");
}
});
System.out.println(Arrays.toString(str));

 Compareble接口与 Comparator 的使用对比:

  • Compareble接口的方式一旦一定,保证 Comparable接口实现类的对象在任何位置都可以比较大小
  • Comparator接口属于临时性的比较

枚举类

枚举类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package com.coderitl.enumdemo;


// 枚举类: enum
enum Season {
// 1. 提供当前枚举类的对象,多个对象之间用 ","隔开,末尾对象";"结束
SPRING("春天", "鸟语花香"),
SUMMER("夏天", "烈日炎炎"),
AUTUMN("秋天", "硕果累累"),
WINTER("冬天", "寒风凛冽");

// 2. 声明 season 对象的属性" private final 修饰
private final String name;
private final String desc;

// 3. 私有化类的构造器,并给对象属性赋值
private Season(String name, String desc) {
this.name = name;
this.desc = desc;
}
// 其他诉求1: 获取枚举类对象的属性 getter方法

public String getName() {
return name;
}

public String getDesc() {
return desc;
}


}

public class EnumTest {
public static void main(String[] args) {
// 使用
/*
* Enum类的主要方法:
* values() 方法: 返回枚举类型的对象数组,该方法可以很方便地遍历所有地枚举值
* valueOf(String str): 可以把一个字符串转为对应地枚举类对象。要求字符串必须是枚举类对象的名字,如不是,会有运行时异常: IllegalArgumenException
* toString(): 返回当前枚举类对象常量的名称
* */
// 调用 toString()
Season spring = Season.SPRING;
System.out.println(spring); // spring == spring.toString() 方法

// values() 方法使用
Season[] values = Season.values();
for (int i = 0; i < values.length; i++) {
System.out.println(values[i]);
}

// valueOf(String str) 返回枚举类中对象名是 objName的对象,没有则报异常
Season summer = Season.valueOf("SUMMER");
System.out.println(summer);
}


}
使用 enum 关键字定义的枚举类实现接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.coderitl.enumdemo;

// 枚举类: enum
enum Season implements Info {
// 1. 提供当前枚举类的对象,多个对象之间用 ","隔开,末尾对象";"结束
SPRING("春天", "鸟语花香"),
SUMMER("夏天", "烈日炎炎"),
AUTUMN("秋天", "硕果累累"),
WINTER("冬天", "寒风凛冽");

// 2. 声明 season 对象的属性" private final 修饰
private final String name;
private final String desc;

// 3. 私有化类的构造器,并给对象属性赋值
private Season(String name, String desc) {
this.name = name;
this.desc = desc;
}
// 其他诉求1: 获取枚举类对象的属性 getter方法

public String getName() {
return name;
}

public String getDesc() {
return desc;
}

// 重写方法 show
@Override
public void show() {
System.out.println("这是一个季节");
}
}

// 定义接口
interface Info {
void show(); // 抽象方法
}

public class EnumTest {
public static void main(String[] args) {
Season spring = Season.SPRING;
spring.show(); // 这是一个季节
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package com.coderitl.enumdemo;


// 枚举类: enum
enum Seasons implements Infos {
// 1. 提供当前枚举类的对象,多个对象之间用 ","隔开,末尾对象";"结束
SPRING("春天", "鸟语花香") {
@Override
public void show() {
System.out.println("这是春天");
}
},
SUMMER("夏天", "烈日炎炎") {
@Override
public void show() {
System.out.println("这是夏天");
}
},
AUTUMN("秋天", "硕果累累") {
@Override
public void show() {
System.out.println("这是秋天");
}
},
WINTER("冬天", "寒风凛冽") {
@Override
public void show() {
System.out.println("这是冬天");
}
};

// 2. 声明 season 对象的属性" private final 修饰
private final String name;
private final String desc;

// 3. 私有化类的构造器,并给对象属性赋值
private Seasons(String name, String desc) {
this.name = name;
this.desc = desc;
}
// 其他诉求1: 获取枚举类对象的属性 getter方法

public String getName() {
return name;
}

public String getDesc() {
return desc;
}


}

// 定义接口
interface Infos {
void show(); // 抽象方法
}

public class EnumTest2 {
public static void main(String[] args) {
Seasons spring = Seasons.SPRING;
spring.show(); // 这是春天

Seasons summer = Seasons.SUMMER;
summer.show(); // 这是夏天

// 循环调用 show()
Seasons[] values = Seasons.values();
for (int i = 0; i < values.length; i++) {
values[i].show();
}

}

}

匿名内部类

内部类
  • 说明: 内部类就是定义在一个类里面的类,里面的类可以理解成(寄生),外部类可以理解成(宿主)

    1
    2
    3
    4
    5
    6
    7

    public class TestA {
    public class TestB {

    }
    }

  • 内部类的使用场景、作用

    • 当一个事物的内部、还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构可以选择使用内部类来设计
    • 内部类通常可以方便访问外部类的成员,包括私有的成员
    • 内部类提供了更好的封装性、内部类本身就可以用pricvate protected等修饰,封装性可以做更多控制
  • 内部类的分类

    • 静态内部类

      • static修饰,属于外部类本身

      • 它的特点和使用与普通类是完全一样的,类有的成分他都有,只是位置在别人里面而已

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        package com.example;

        public class Out {
        // 静态成员内部类
        static class Inner {
        void run() {
        System.out.println("run...............");
        }
        }
        }

      • 静态内部类的访问拓展

        • 静态内部类中是否可以直接访问外部类的静态成员

          可以,外部类的静态成员只有一份可以被共享访问

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          package com.example;

          public class Out {
          static final String NAME = "coder-itl";

          // 静态成员内部类
          static class Inner {
          void run() {
          System.out.println(NAME + " run...............");
          }
          }
          }

          class Test {
          public static void main(String[] args) {
          Out.Inner inner = new Out.Inner();
          inner.run();
          }
          }
        • 静态内部类中是否可以直接访问外部类的实例成员?

          不可以,外部类的实例成员必须用外部类对象访问

          实例成员
    • 成员内部类:不能加static修饰,属于外部类对象的

      • static修饰,属于外部类的对象

      • JDK16之前,成员内部类中不能定义静态成员,JDK16开始也可以定义静态成员了

        1
        2
        3
        4
        5
        6
        7
        public class Out {
        class Inner {
        void run() {
        System.out.println("run...............");
        }
        }
        }
      • 成员内部类创建对象的格式

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        package com.example;

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

        class Test {
        public static void main(String[] args) {
        // 成员内部类的调用
        Out.Inner oIn = new Out().new Inner();
        oIn.run();
        }
        }
      • 成员内部类的访问拓展

        • 成员内部类中是否可以直接访问外部类的静态成员

          可以,外部类的静态成员只有一份可以被共享访问

        • 成员内部类的实例方法中是否可以直接访问外部类的实例成员

          可以的,因为必须先有外部类对象,才能有内部类对象,所以可以直接访问外部类对象的实例成员

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          package com.example;

          public class Out {
          static final String NAME = "coder-itl";
          int age = 20;

          class Inner {
          void run() {
          // 访问静态成员
          System.out.println(NAME + "run...............");
          // 访问外部类的实例成员
          System.out.println("年龄是: " + age);
          }
          }
          }

          class Test {
          public static void main(String[] args) {

          // 成员内部类的调用
          Out.Inner oIn = new Out().new Inner();
          oIn.run();
          }
          }
          成员内部类
      • 面试题

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        package com.example;

        public class Out {
        private int hearbeat = 150;

        class Inner {
        private int hearbeat = 110;

        public void show() {
        int hearbeat = 78;
        System.out.println(hearbeat); // 78
        System.out.println(this.hearbeat); // 110
        System.out.println(Out.this.hearbeat); // 150
        }
        }
        }

        class Test {
        public static void main(String[] args) {
        Out.Inner oIn = new Out().new Inner();
        oIn.show();
        }
        }
    • 局部内部类

    • 匿名内部类*

      • 本质上是一个没有名字的局部内部类,定义在方法中、代码块中等
      • 作用: 方便创建子类对象,最终目的是为了简化代码的编写
      • 特点
        • 匿名内部类是一个没有名字的内部类
        • 匿名内部类写出来就会产生一个匿名内部类的对象
        • 匿名内部类的对象类型相当于是当前new的那个类型的子类类型

第九章

集合

集合概述
  • 集合: 集合是 java 中提供的一种容器,可以用来存储多个数据

  • 集合和数组都是容器,他们的区别?

    • 数组的长度是固定的。集合的长度是可变的
    • 数组中存储的是同一类型元素,可以存储基本数据类型值。集合存储的都是对象,而对象的类型可以不一致。在开发中一般当对象多的时候,使用集合进行存储。
  • 图示

  • 集合特点

    • List集合: 有索引,可以存储重复元素,可以保证存取顺序

    • ArrayList:底层是数组实现的,查询快、增删慢

    • LinkList:底层是链表实现的、查询慢、增删快

  • Set集合:无索引、不可以存储重复元素、存取无序

    • HashSet: 底层是哈希表+(红黑树)实现的、无索引、不可以存储重复元素、存取无序

    • LinkHashSet: 底层是哈希表+链表实现的、无索引、不可以存储重复元素、可以保证存取顺序

    • TreeSet: 底层是二叉树实现,一般用于排序

  • 集合中的常用方法[上述集合公用特性]

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    package com.coderitl.collection;


    import java.util.ArrayList;
    import java.util.Collection;

    public class CollectionMethods {
    public static void main(String[] args) {
    /*
    * boolean add(E e); 向集合中添加元素
    * boolean remove(E e); 删除集合中的某个元素
    * void clear(); 清空集合所有的元素
    * boolean contains(E e); 判断集合中是否包含某个元素
    * boolean isEmpty(); 判断集合是否为空
    * int size(); 获取集合的长度
    * Object[] toArray(); 将集合转成一个数组
    * */
    // collection 实例化
    Collection<String> str = new ArrayList<>();
    // add 向集合中添加元素
    str.add("Hello");
    str.add("World");
    str.add("coder-itl");
    System.out.println(str);

    // 在集合中删除元素
    str.remove("Hello");
    System.out.println(str);


    Object[] objects = str.toArray();
    for (int i = 0; i < objects.length; i++) {
    System.out.println(objects[i]);
    }

    }
    }

  • 小题总结

    1
    2
    3
    ArrayList、LinkList、Vector 三者的异同?
    相同点: 三个类都是实现了 List 接口,存储数据的特点相同: 存储有序的、可重复的数据
    不同:
Iterator接口
  • 基本介绍

    1
    2
    3
    4
    5
    6
    7
    8
    java.util.Iterator接口: 迭代器(对集合进行遍历)
    常用方法:
    boolean hashNext() 判断集合中还有没有下一个元素,有就返回 true,没有就返回 false
    E next() 取出集合中的下一个元素

    Iterator 迭代器,是一个接口,我们无法直接使用,需要使用 Iterator 接口的实现类对象,
    获取实现类的方式比较特殊,Collection接口中有一个方法,叫 iterator(),这个方法返回
    的就是迭代器的实现类对象
  • Iterator使用步骤

    1
    2
    3
    4
    迭代器的使用步骤(重点):
    1. 使用集合中的方法 iterator() 获取迭代器的实现类对象,使用 iterator 接口接受(多态)
    2. 使用 Iterator 接口中的方法 hashNext() 判断还有没有下一个元素
    3. 使用 Iterator 接口中的方法 next 取出集合中的下一个元素
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    import org.junit.Test;

    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Iterator;

    public class TestIterator {
    @Test
    public void testIterator() {
    // 多态
    Collection<String> list = new ArrayList<>();
    list.add("1");
    list.add("2");
    list.add("3");
    list.add("4");
    // 获取迭代器的实现类对象,并且会把指针(索引)指向集合的 -1 索引
    Iterator<String> it = list.iterator();
    // 不确定次数的循环 使用 while
    while (it.hasNext()) { // it.hashNext 判断集合中还有没有下一个元素
    String next = it.next(); // it.next() 做了两件事: 1. 取出下一个元素 2. 会把指针向后移动一位
    System.out.println(next);
    }

    }
    }

  • 增强 for 循环

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    增强 for 循环: 底层使用的也是 迭代器,使用 for 循环的格式,简化了迭代器的书写,是 JDK1.5 
    之后出现的新特性

    Collection<E> extends Iterable(E): 所有的单列集合都可以使用增强 for

    public interface Iterable<T> 实现这个接口语序对象称为 "foreach" 语句的目标

    增强 for 循环: 用来遍历集合和数组

    格式:
    for(集合/数组的数据类型 变量名:集合名/数组名){
    System.out.println(变量名);
    }
    1
    2
    3
    4
    5
    6
    7
    @Test
    public void testForEach() {
    String[] str = {"10", "11", "20", "90"};
    for (String strValue : str) {
    System.out.println(strValue);
    }
    }
  • 习题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Test
    public void testExer() {
    List list = new ArrayList();
    list.add(1); // index: 0
    list.add(2);// index: 1
    list.add(3);// index: 2 --> remove 掉
    updateList(list);
    System.out.println(list); // [2]
    }

    private void updateList(List list) {
    list.remove(2); // 使用的是索引
    list.remove(new Integer(1)); // 根据值删除
    }
  • 集合每日一问

    1. 集合Collection中存储的如果时自定义类的对象、需要自定义类重写那个方法?为什么?
    2. ArrayList LinkList Vector三者的相同点与不同点?
    3. list接口的常用方法有那些?(增、删、改、查、插、长度、遍历)
    4. 如何使用iterator 和 增强 for 循环遍历 List
    5. Set存储数据的特点时什么?常见的实现类有什么?说明一下彼此特点。
Map集合
  • 基本方法使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    package com.coderitl.hashmap;


    import java.util.HashMap;
    import java.util.Map;

    public class HashMapTest {
    public static void main(String[] args) {
    // Map 的增删改基本使用
    MapMethods();
    System.out.println("-----------------------------");
    // Map 查询方法
    MapQuery();
    }

    public static void MapMethods() {
    /*
    * 添加、删除、修改:
    * Object put(Object key,Object value): 将指定 key-value 添加到(或修改)当前 map 对象中
    * void putAll(Map m): 将 m 中的所有 key-value 对存放到当前 map 中
    * Object remove(Object key): 移除指定 key的 key-value对,并返回 value
    * void clear(): 清空当前 map 中所有的数据
    * */
    Map map = new HashMap();
    // Object put(Object key,Object value): 将指定 key-value 添加到(或修改)当前 map 对象中
    map.put("Key1", "JavaSE");
    map.put("Key2", "红黑树");
    map.put("Key3", "数据结构");

    // put(key) 修改
    map.put("Key1", "JavaEE");
    System.out.println(map);

    // Object remove(Object key): 移除指定 key的 key-value对,并返回 value
    map.remove("key1");

    // void putAll(Map m): 将 m 中的所有 key-value 对存放到当前 map 中
    Map map1 = new HashMap();
    map1.put("key9", "JavaScript");
    map1.put("key8", "JavaScript权威指南");
    map.putAll(map1);
    System.out.println(map);

    // void clear(): 清空当前 map 中所有的数据
    map1.clear(); // map = null? 会报空指针异常吗?
    System.out.println(map1); // 没有出现空指针异常问题,反而输出: {}
    }

    public static void MapQuery() {
    /*
    * Object get(Object key): 获取指定 key 对应的 value
    * boolean containskey(Object key): 是否包含指定的 key
    * boolean containsValue(Object value): 是否包含指定的 value
    * int size(): 返回 map 中 keu-value 对的个数
    * boolean isEmpty(): 判断当前 map 是否为空
    * boolean equals(Object obj): 判断当前 map 和参数对象 obj 是否相等
    * */
    Map map = new HashMap();
    map.put("Key1", "JavaSE");
    map.put("Key2", "红黑树");
    map.put("Key3", "数据结构");
    map.put(43, 45);

    // Object get(Object key): 获取指定 key 对应的 value
    Object key2 = map.get("Key2");
    System.out.println("指定keyvalue: " + key2);

    // boolean containskey(Object key): 是否包含指定的 key
    boolean key4 = map.containsKey("key4");
    System.out.println("这个key将会是不存在的: " + key4);

    // boolean containsValue(Object value): 是否包含指定的 value
    boolean redTree = map.containsValue("红黑树");
    System.out.println("红黑树这个value存在: " + redTree);

    // int size(): 返回 map 中 keu-value 对的个数
    int mapSize = map.size();
    System.out.println("map的长度: " + mapSize);

    // boolean isEmpty(): 判断当前 map 是否为空
    boolean empty = map.isEmpty();
    System.out.println("判断map是否为空: " + empty);

    // boolean equals(Object obj): 判断当前 map 和参数对象 obj 是否相等
    // map.equals(); // 这个怎么用?
    }
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    import org.junit.Test;

    import java.util.*;


    public class TestHashMap {
    @Test
    public void testHashMap() {
    Map map = new HashMap();
    map.put("One", 1);
    map.put("Two", 2);
    map.put("Three", 3);

    // Set keySet(): 返回所有的key构成的 Set集合
    Set set = map.keySet();
    Iterator iterator = set.iterator();
    while (iterator.hasNext()) {
    // 遍历所有的 key
    System.out.println(iterator.next());
    }

    System.out.println();

    // Collection values(): 返回所有的 value 构成的 Collection集合
    Collection values = map.values();
    for (Object obj : values) {
    // 遍历所有的 value
    System.out.println(obj);
    }


    // Set entrySet(): 返回所有的 key-value 对构成的 Set集合
    Set entrySet = map.entrySet();
    Iterator entryIterator = entrySet.iterator();
    // 方式一: 遍历 key:value
    while (entryIterator.hasNext()) {
    Object obj = entryIterator.next();
    Map.Entry entry = (Map.Entry) obj;
    // 遍历 key:value
    System.out.println(entry.getKey() + ":" + entry.getValue());
    }

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

    // 方式二: 遍历 key:value
    Set set1 = map.keySet();
    Iterator iterator1 = set.iterator();
    while (iterator1.hasNext()) {
    // 遍历所有的 key
    Object next1 = iterator1.next();
    System.out.println(next1 + ":" + map.get(next1));
    }
    }
    }

    1. Map存储数据的特点是什么?并指明keyvalueentry存储数据的特点
    2. 描述HashMap的底层原理(JDK-8)
    3. Map中常用实现类有哪些?各自有什么特点?
    4. 如何遍历Map中的key-value对?代码实现
    5. CollectionCollections的区别?

可变参数

  • 基础使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package com.example;

    public class MapDemo {
    public static void main(String[] args) {
    int res = getSum(1, 2, 3, 4);
    System.out.println(res);
    }

    private static int getSum(int... num) {
    int sum = 0;
    for (int i : num) {
    sum += i;
    }
    return sum;
    }
    }

    • 注意点
      • 可变参数本质上是一个数组
      • 可变参数只能只有一个且这个参数只能放在形参列表的最后

Collections

  • java.util.Collections: 是集合工具类

  • 作用: Collections不是集合,而是集合的工具类

  • 常用API

    方法名称 说明
    addAll 只能对单列结合进行批量添加元素
    shuffle 捣乱List集合元素的顺序
  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package com.example;

    import java.util.ArrayList;
    import java.util.Collections;

    public class MapDemo {
    public static void main(String[] args) {
    ArrayList<String> list = new ArrayList<>();
    Collections.addAll(list, "1", "2", "3");
    System.out.println(list);
    }
    }

不可变集合

  • 使用场景

    • 如果某个数据不能被修改,把他防御性地拷贝到不可变集合中是个很好的实践
    • 或者当集合对象被不可信的库调用时,不可变是安全的
    • JDK8+可用
  • List

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class MapDemo {
    public static void main(String[] args) {
    // 存储的数据只能被读取
    List<Integer> integers = List.of(1, 2, 3, 4, 5);
    for (Integer num : integers) {
    System.out.println(num);
    }
    }
    }
  • Set

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    public class MapDemo {
    public static void main(String[] args) {
    Set<Integer> integers = Set.of(1, 2, 3, 4, 5);
    for (Integer num : integers) {
    System.out.println(num);
    }
    }
    }

  • Map

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    // Map 里面的 of 方法,参数是有上限的,最多只能传递 20 个参数,10个键值对

    package com.example;

    import java.util.HashMap;
    import java.util.Map;
    import java.util.Set;

    public class MapDemo {
    public static void main(String[] args) {
    Map<String, Object> map = new HashMap();
    map.put("a1", "a1");
    map.put("a2", "a2");
    map.put("a3", "a3");
    map.put("a4", "a4");
    map.put("a5", "a5");
    map.put("a6", "a6");
    map.put("a7", "a7");
    map.put("a8", "a8");
    map.put("a9", "a9");
    map.put("a10", "a10");
    map.put("a11", "a11");
    // 获取到所有键值对的 entry 对象
    Set<Map.Entry<String, Object>> entries = map.entrySet();
    // 把 entry 变成一个数组
    Map.Entry[] arr1 = new Map.Entry[0];
    // toArray(): 方法在底层会比较集合的长度跟数组长度两者的大小
    // 如果集合的长度(11) > 数组的长度(0): 数据在数组中放不下,此时会根据实际数据的个数(11),重新创建数据
    // 如果集合的长度(11) < 数组的长度(假如 20):数据在数组中放得下,此时不会创建新的数组,而是直接使用(20作为长度)
    Map.Entry[] arr2 = entries.toArray(arr1);
    // 不可变的集合
    Map ofEntries = Map.ofEntries(arr2);
    }
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // JDK10 出现了新方法简化上述

    public class MapDemo {
    public static void main(String[] args) {
    Map<String, Object> map = new HashMap();
    map.put("a1", "a1");
    map.put("a2", "a2");
    map.put("a3", "a3");
    map.put("a4", "a4");
    map.put("a5", "a5");
    map.put("a6", "a6");
    map.put("a7", "a7");
    map.put("a8", "a8");
    map.put("a9", "a9");
    map.put("a10", "a10");
    map.put("a11", "a11");
    // 创建不可变的 map
    Map<String, Object> stringObjectMap = Map.copyOf(map);
    System.out.println(stringObjectMap);
    }
    }

Stream

  • 使用步骤

    1. 先得到一条Stram(流水线),并把数据放上去

    2. 利用Stream流中的 API进行各种操作

      过滤 转换 中间方法:方法调用完毕后,还可以调用其他方法
      统计 打印 终结方法:最后一步,调用完毕之后,不能调用其他方法
    3. 示例

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      package com.example;

      import java.util.ArrayList;
      import java.util.Collections;

      public class MapDemo {
      public static void main(String[] args) {
      // 单列集合
      ArrayList<String> list = new ArrayList<>();
      Collections.addAll(list, "张三", "张三丰", "李四", "王五");
      list
      // 获取流水线
      .stream()
      // 使用中间方法
      .filter(name -> name.length() > 2)
      // 使用终结方法结束
      .forEach(res -> System.out.println(res));
      }
      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      package com.example;

      import java.util.HashMap;
      import java.util.Map;

      public class MapDemo {
      public static void main(String[] args) {
      // 双列结合使用 stream
      Map<String, Integer> map = new HashMap<>();
      map.put("aaa", 111);
      map.put("bbb", 222);
      map.put("ccc", 333);
      map.put("ddd", 444);
      // 第一种获取 stream 流的方式
      map.keySet()
      .stream()
      .forEach(word -> System.out.println(word));

      // 第二种获取 stream 流的方式
      map.entrySet().stream().forEach(word -> System.out.println(word));
      }
      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      package com.example;

      import java.util.Arrays;

      public class MapDemo {
      public static void main(String[] args) {
      // 数组获取 stream
      Integer[] arr = {1, 2, 3, 4};
      Arrays.stream(arr).filter(num -> num > 2).forEach(num -> System.out.println(num));
      }
      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      package com.example;

      import java.util.stream.Stream;

      public class MapDemo {
      public static void main(String[] args) {
      // 一堆零散的数据使用 stream: 这堆零散的数据必须是同一种数据类型
      Stream.of(1, 2, 3, 4, 5).filter(num -> num > 2).forEach(num -> System.out.println(num));
      }
      }

  • stream流的中间方法

    名称 说明
    filter 过滤
    limit 获取前几个元素
    skip 跳过前几个元素
    distinct 元素去重,依赖hashCodeequals方法
    concat 合并ab两个流为一个
    map 转换流中的数据类型
    • 注意点
      • 注意点一: 中间方法,返回新的stream流,原来的stream流只能使用一次,建议使用链式编程
      • 注意点二:修改stream流中的数据,不会影响原来集合或者数组中的数据
  • stream流的中间方法

    名称 说明
    forEach 遍历
    count 统计
    toArray 收集流中的数据,放到数组中
    collect 收集流中的数据,放到集合中

第十章

泛型

泛型概念
  • 概念

    所谓泛型、就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如: 继承或者实现这个接口,用到这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)

  • 为什么要有泛型

    1. 解决元素存储的安全性问题
    2. 解决获取数据元素时,需要类型强制转换的问题
  • 引入泛型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    package com.coderitl.generic;

    import java.util.ArrayList;

    public class GenericTest {
    public static void testGengeic() {
    // 在集合中使用泛型之前的情况
    ArrayList score = new ArrayList();
    // 需求: 存储成绩
    score.add(90);
    score.add(98);
    score.add(59);
    score.add(60);
    // 不安全问题:
    score.add("Java");
    // 循环遍历:
    for (Object obj : score) {
    // 强转时可能出现 ClassCastException 异常
    int scoreStu = (int) obj;
    System.out.println(scoreStu);
    }

    }

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 使用泛型之后
    ArrayList<Integer> score = new ArrayList<Integer>();
    score.add(89);
    score.add(98);
    score.add(59);
    score.add(99);
    // 编译时就会报错,限制了数据类型传入
    // score.add("java");
    for (Integer sc : score) {
    // 避免了数据类型转换
    System.out.println(sc);
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 使用泛型之后
    ArrayList<Integer> score = new ArrayList<Integer>();
    score.add(89);
    score.add(98);
    score.add(59);
    score.add(99);
    // 使用 Iterator 遍历输出
    Iterator<Integer> iterator = score.iterator();
    while (iterator.hasNext()) {
    Integer next = iterator.next();
    System.out.println(next);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @Test
    public void testGenHashMap() {
    // Map比较特殊: 由于 public interface Map {} K:key V:value
    Map<String, Integer> map = new HashMap<String, Integer>();
    map.put("英语", 20);
    map.put("语文", 120);
    map.put("数学", 150);
    map.put("历史", 80);

    // map 怎么使用 Iterator
    Set<Map.Entry<String, Integer>> entries = map.entrySet();
    // 泛型嵌套
    Iterator<Map.Entry<String, Integer>> iterator = entries.iterator();
    while (iterator.hasNext()) {
    Map.Entry<String, Integer> next = iterator.next();
    System.out.println(next);
    }
    }
如何自定义泛型结构
  • 泛型类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    package com.coderitl.generic;

    public class GenericDemo<T> {

    // 类的内部结构就可以使用类的泛型
    T genericT;
    // 自定义泛型类
    private String name;
    private int age;

    // 无参构造
    public GenericDemo() {
    }

    public GenericDemo(String name, int age, T genericT) {
    this.name = name;
    this.age = age;
    this.genericT = genericT;
    }

    // 对泛型提供 getter 和 setter
    public T getGenericT() {
    return genericT;
    }

    public void setGenericT(T genericT) {
    this.genericT = genericT;
    }

    @Override
    public String toString() {
    return "GenericDemo{" +
    "genericT=" + genericT +
    ", name='" + name + '\'' +
    ", age=" + age +
    '}';
    }
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    package com.coderitl.generic;

    public class GenericDemoMain {
    public static void main(String[] args) {
    // 使用自定义泛型
    GenericDemo demo = new GenericDemo(); // 未使用泛型
    demo.setGenericT("ObjectG"); // Object genericT

    // 使用泛型
    GenericDemo<String> list = new GenericDemo<String>("StringGG", 12, "NoObj");
    list.setGenericT("StringG"); // 会自动提示指定泛型类型 --> String genericT
    }
    }



    public class SubGeneric extends GenericDemo<Interge>{

    }
    // new 子类对象时,不需要再添加泛型 --> 由于子类在继承带泛型的父类时,指明了泛型类型.则实例化子类对象时,不需要再指明泛型

  • 泛型方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    public class Edemo<String> {
    public <E> List<E> ToArray(E[] arry) {
    ArrayList<E> list = new ArrayList<E>();
    // 遍历数组
    for (E e : arry) {
    // 集合添加遍历出的数据
    list.add(e);
    }
    return list;
    }
    }

    -----------------------------------------------------

    package com.coderitl.generic;

    import java.util.List;


    public class EdemoMain {
    public static void main(String[] args) {
    // 类的实例化
    Edemo<String> demo = new Edemo<>();
    // 创建一个 Integer 类型数组
    String[] list = new String[]{"Python", "java"};
    System.out.println(list);
    // 调用泛型方法: 方法是封装的循环遍历方法 <--- 认为: 规范了数据安全
    List<String> integers = demo.ToArray(list);
    System.out.println(integers);
    }
    }

    // 泛型方法: 在方法中出现了泛型结构,泛型参数与类的泛型参数没有任何关系,也就是说,泛型方法所属的类不是泛型类都没有关系
    // 泛型方法,可以声明为静态的,原因: 泛型参数是在调用时确定的,并非在实例化类时确定

File

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.coderitl.file;

import java.io.File;

/*
* 1. 如何创建 File类的实例
* File(String filePath)
* File(String parentPath,String childPath)
* File(File parentFile,String childPath)
* 2. 相对路径: 相较于某个路径下,指明的路径
* 绝对路径: 包含盘符在内的文件或文件目录的路径
* 3. 路径分隔符
* windows: \\
* unix: /
* */
public class fileDemo {
public static void main(String[] args) {
// 1. File 类的一个对象,代表一个文件或一个文件目录(俗称: 文件夹)
// 构造器一:
File file1 = new File("file.txt");
// 构造器二:
File file2 = new File("D:\\JavaCode\\src\\com\\coderitl", "File");
// 构造器三:
File file3 = new File(file2, "hello.txt");

System.out.println(file1);
System.out.println(file2);
System.out.println(file3);

}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.coderitl.file;


import java.io.File;

public class IOTest {
public static void main(String[] args) {
/*
* public String getAbsolutePath(): 获取绝对路径
* public String getPath(): 获取路径
* public String getName(): 获取名称
* public String getParent(): 获取上层文件目录路径,若无,返回 null
* public long Length(): 获取文件长度(即: 字节数),不能获取目录的长度
* public long lastModified(): 获取最后一次修改的时间,毫秒值
*
* public String[] list(): 获取指定目录的所有文件或者文件目录的名称数组
* public File[] listFiles(): 获取指定目录下的所有文件或者文件目录的 File 数组
*
* */
File file = new File("D:\\JavaCode\\Hello.txt");

// public String getAbsolutePath(): 获取绝对路径
File absoluteFile = file.getAbsoluteFile();
System.out.println("文件的绝对路径: " + absoluteFile);

// public String getPath(): 获取路径
String path = file.getPath();
System.out.println("文件路径输出: " + path);

// public String getName(): 获取名称
String name = file.getName();
System.out.println("文件名称: " + name);

// public String getParent(): 获取上层文件目录路径,若无,返回 null
String parent = file.getParent();
System.out.println("获取文件上层文件目录路径: " + parent);

// public long Length(): 获取文件长度(即: 字节数),不能获取目录的长度
long length = file.length();
System.out.println("文件字节长度: " + length);

// public long lastModified(): 获取最后一次修改的时间,毫秒值
long modified = file.lastModified();
System.out.println("文件修改的毫秒值: " + modified);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.coderitl.file;


import java.io.File;

/*
* File 类的判断功能:
* public boolean isDirector(): 判断是否是文件目录
* public boolean isFile(): 判断是否是文件
* public boolean exists(): 判断是否存在
* public boolean canRead(): 判断是否可读
* public boolean canWrite(): 判断是否可写
* public boolean isHidden(): 判断是否隐藏
*
* */
public class IsFileMethods {
public static void main(String[] args) {
File file = new File("D:\\JavaCode\\Hello.txt");

// public boolean isDirector(): 判断是否是文件目录
boolean directory = file.isDirectory();
System.out.println("判断是否是文件目录: " + directory);

// public boolean isFile(): 判断是否是文件
boolean isFile = file.isFile();
System.out.println("判断是否是文件: " + isFile);

// public boolean exists(): 判断是否存在
boolean exists = file.exists();
System.out.println("判断是否存在: " + exists);

// public boolean canRead(): 判断是否可读
boolean isCanRead = file.canRead();
System.out.println("判断是否可读: " + isCanRead);

// public boolean canWrite(): 判断是否可写
boolean isCanWrite = file.canWrite();
System.out.println("判断是否可写: " + isCanWrite);

// public boolean isHidden(): 判断是否隐藏
boolean hidden = file.isHidden();
System.out.println("判断是否隐藏: " + hidden);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.coderitl.file;

import java.io.File;
import java.io.IOException;

/*
* public boolean createNewFile(): 创建文件。若文件存在,则不创建,返回false
* public boolean mkdir(): 创建文件目录, 如果此文件目录存在,就不创建了,如果此文件目录的上层目录不存在,也不创建
* public boolean mkdirs(): 创建文件目录 如果上层文件目录不存在,一并创建
*
* public boolean delete(): 删除文件或文件夹
* 删除注意事项:
* Java 中的删除不走回收站
* */
public class FileCRUD {
public static void main(String[] args) {
File file = new File("TestCreate.java");
// public boolean createNewFile(): 创建文件。若文件存在,则不创建,返回false
boolean newFile = false;
try {
newFile = file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(newFile);

// public boolean mkdir(): 创建文件目录, 如果此文件目录存在,就不创建了,如果此文件目录的上层目录不存在,也不创建
file = new File("D:\\javaCode\\TestCreate", "Java");
boolean mkdir = file.mkdir();
System.out.println(mkdir);

// public boolean mkdirs(): 创建文件目录 如果上层文件目录不存在,一并创建
boolean mkdirs = file.mkdirs();
System.out.println(mkdirs);

File file1 = new File("D:\\JavaCode\\TestCreate\\Java.txt");
boolean delete = file1.delete();
System.out.println(delete);
}
}

  • listFiles

    • 当调用者File表示的路径不存在时,返回null
    • 当调用者File表示的路径是文件时,返回null
    • 当调用者File表示的路径是一个空文件夹时,返回一个长度为0的数组
    • 当调用者File表示的路径是一个有内容的文件夹时,将里面所有文件和文件夹的路径放在File数组中返回
    • 当调用者File表示的路径是一个有隐藏文件的文件夹时,将里面所有文件和文件夹的路径放在File数组中返回,包含隐藏文件
    • 当调用者File表示的路径是需要权限才能访问的文件夹时,返回null
  • 案例练习

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    package com.coderitl.file;

    import java.io.File;
    import java.util.Scanner;

    /*
    * 1. 利用 File 构造器,new 一个文件目录 file
    * 1.1 在其中创建多个文件和目录
    * 1.2 编写方法,实现删除 file 中指定文件的操作
    * 2. 判断指定目录下是否有后缀名未 .jpg 的文件,如果有,就输出该文件名称
    * 3. 遍历指定目录所有文件名称,包括子文件目录中的文件
    * 拓展1: 并计算指定目录占用空间的大小
    * 拓展2: 删除指定文件目录及其下的所有文件
    * */

    public class FileHomeWork {
    public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    System.out.println("请输入要操作的文件或目录路径: ");
    String filePath = sc.next();
    File file = new File(filePath);

    // 1. 利用 File 构造器,new 一个文件目录 file
    // * 1.1 在其中创建多个文件和目录
    // * 1.2 编写方法,实现删除 file 中指定文件的操作
    // 创建
    CreateFolder(file);
    DeleteFolder(file);

    // * 2. 判断指定目录下是否有后缀名为 .jpg 的文件,如果有,就输出该文件名称
    GetJpgPrint(file); // 也可以调用 endsWith() 方法
    // * 3. 遍历指定目录所有文件名称,包括子文件目录中的文件
    // * 拓展1: 并计算指定目录占用空间的大小
    // * 拓展2: 删除指定文件目录及其下的所有文件
    }

    public static void CreateFolder(File file) {
    // 怎么同时创建多个文件或目录
    try {
    boolean newFile = file.createNewFile();
    if (newFile) {
    String name = file.getName();
    System.out.println("名称为 " + name + "创建成功");
    }
    } catch (Exception e) {
    e.printStackTrace();
    }
    }

    public static void DeleteFolder(File file) {
    if (file.exists()) {
    String name = file.getName();
    boolean delete = file.delete();
    System.out.println("名称为 " + name + "删除成功");
    }
    }

    public static void GetJpgPrint(File file) {
    String name = file.getName();
    String substring = name.substring(name.length() - 4, name.length()); // .jpg
    if (substring.equals(".jpg")) {
    // 输出包含 jpg 的name
    System.out.println(name);
    } else {
    // 输出提示信息
    System.out.println("文件中不存在后缀名为: .jpg的文件");
    }
    }
    }

IO

IO-概念
  • IO流概述
  • IO流:存储和读取数据的解决方案
  • 流: 是一种抽象概念,是对数据传输的总称,也就是说数据在设备间的传输称为流、流的本质是数据传输
  • IO流就是用来处理设备间数据传输的问题的
    • 常见的应用: 文件复制文件上传文件下载
IO-分类
  • IO流分类

    分类
    • 纯文本文件(只能使用字符流操作): 使用Windwos自带的记事本打开就能读懂
    • 字节流: 可以操作所有类型的文件
  • 字节流抽象基类

    • InputStream:这个抽象类是表示字节输入流的所有类的超类
    • OutputStream: 这个抽象类是表示字节输出流的所有类的超类
    • 子类特点名称: 子类名称都是以其父类名作为子类名的后缀
IO-体系
  • IO流体系

    体系
FileReaderFileWriter
  • FileReaderFileWriter使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    // 说明:	
    // 1. read() 的理解: 返回读入的一个字符,如果达到文件末尾 返回 -1
    // 2. 异常的处理: 为了保证流资源一定可以执行关闭操作,需要使用 try-catch-finally 处理

    package com.coderitl.testio;


    import java.io.File;
    import java.io.FileReader;
    import java.io.IOException;

    public class IOTest {
    public static void main(String[] args) {
    FileReader fr = null;
    try {
    File file = new File("D:\\JavaCode\\src\\com\\coderitl\\testio\\Hello.txt");
    fr = new FileReader(file);
    // 数据的读入
    int data;
    while ((data = fr.read()) != -1) {
    System.out.print((char) data);
    }
    } catch (IOException e) {
    e.printStackTrace();
    } finally {
    try {
    if (fr != null) {
    fr.close();
    }
    } catch (IOException e) {
    e.printStackTrace();
    }
    }

    }
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @Test
    public void testFileWrite() throws IOException {
    /*
    * 说明:
    * 1. 输出操作,对应的 File 可以不存在,并不会报异常
    * 2.
    * File 对应的硬盘中的文件如果不存在,在输出的过程中,会自动创建此文件:
    * File对应的硬盘中的文件如果存在:
    * 如果流使用的构造器是: FileWriter(file,false)/ FileWrite(file): 对原有文件覆盖
    * 如果流使用的构造器是: FileWriter(file,true): 不会对原有文件覆盖,而是在原有文件后追加
    * */
    File file = new File("D:\\JavaCode\\src\\com\\coderitl\\testio\\Hello.txt");
    // 写的时候存在异常
    FileWriter fw = new FileWriter(file, true);
    fw.write("我爱学习Java1");
    fw.write("我爱学习Java2");
    fw.write("我爱学习Java3");
    fw.write("我爱学习Java4");
    fw.write("我爱学习Java5");
    fw.close();
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    @Test
    public void testCoptText() {
    FileWriter fw = null;
    FileReader fr = null;
    try {
    File writeFile = new File("D:\\JavaCode\\src\\com\\coderitl\\testio\\LearnJava.txt");
    File readFile = new File("D:\\JavaCode\\src\\com\\coderitl\\testio\\ReadJava.txt");

    // 创建写 流
    fw = new FileWriter(writeFile, true);
    fr = new FileReader(readFile);

    // 读取 字符数组
    char[] cbuf = new char[5];
    // 记录每次读入到 cbuf 数组中的字符个数
    int len;
    while ((len = fr.read(cbuf)) != -1) {
    // 每次写出 len个字符
    fw.write(cbuf, 0, len);
    }
    } catch (IOException e) {
    e.printStackTrace();
    } finally {
    if (fw != null) {
    try {
    fw.close();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    if (fr != null) {
    try {
    fr.close();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    }
    }
FileInputStreamFileOutputStream
  • FileInputStreamFileOutputStream

    操作本地文件的字节输出流,可以把程序中的数据写到本地文件中

    • 使用步骤

      1. 创建字节输出流对象

        • 细节一: 参数是字符串表示的路径或者是File对象都是可以的
        • 细节二: 如果文件不存在会创建一个新的文件,但是要保证父级路径是存在的
        • 细节三: 如果文件已经存在,则会清空文件
      2. 写数据

        • 细节: write方法的参数是整数,但是实际上写到本地文件中的是整数在ASCII上对应的字符
      3. 释放资源

        • 细节: 每次使用完流之后都要释放资源
        1
        2
        3
        FileOutputStream fos = new FileOutputStream("./aa.txt");
        fos.write(100);
        fos.close();
    • 换行写

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      public static void main(String[] args) throws IOException {
      FileOutputStream fos = new FileOutputStream("./aa.txt");
      String str1 = "Hello World";
      byte[] arr1 = str1.getBytes();
      fos.write(arr1);
      // 换行: \r\n(可以只写一个,底层会补全,但是建议写全)
      String wrap = "\r\n";
      byte[] arr2 = wrap.getBytes();
      fos.write(arr2);

      String str3 = "Java NiuBi";
      byte[] arr3 = str3.getBytes();
      fos.write(arr3);
      fos.close();
      }
    • 续写

      1
      2
      // true: 续写开关,开启即可
      FileOutputStream fos = new FileOutputStream("./aa.txt",true);

    操作本地文件的字节输入流,可以把本地文件中的数据读取到程序中来。

    • 书写步骤

      • 创建字节输入流对象

        • 细节: 如果文件不存在,就直接报错

          • Java为什么会这么设计呢?

            因为创建出来的文件是没有数据的,没有任何意义.所以就直接报错了。

            程序中最重要的是: 数据

      • 读数据

        • 细节: 一次读一个字节,读出来的就是数据在ASCII上对应的数字
        • 细节: 读取到文件末尾了,read方法返回-1
      • 释放资源

        • 细节: 每次使用完流之后都要释放资源
    • 循环读取

      1
      2
      3
      4
      FileInputStream fi = new FileInputStream("./aa.txt");
      int data = fi.read();
      // 一次只读取一个
      System.out.println(data);
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      // 读取文件的所有内容(不合理写法)
      public static void main(String[] args) throws IOException {
      FileInputStream fi = new FileInputStream("./aa.txt");
      // read: 表示读取数据,而且是读取一个数据就移动一次指针(下面总共出现两个 read )
      while (fi.read() != -1) {
      int data = fi.read();
      System.out.println((char) data);
      }
      fi.close();
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      // 正确写法,应该定义一个变量
      public static void main(String[] args) throws IOException {
      FileInputStream fi = new FileInputStream("./aa.txt");
      int data;
      while ((data = fi.read()) != -1) {
      System.out.print((char) data);
      }
      fi.close();
      }
      正确读取输出
    • 文件拷贝

      • 思路: 边读边写

      • 实现

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        public static void main(String[] args) throws IOException {
        // 文件输入流(读)
        FileInputStream fi = new FileInputStream("./aa.txt");
        // 文件输出流(写)
        FileOutputStream fos = new FileOutputStream("./bb.txt");

        int data;
        while ((data = fi.read()) != -1) {
        // 将数据写如到 bb.txt [拷贝: 边读边写]
        fos.write(data);
        }
        // 关闭原则: 先开后关
        fos.close();
        fi.close();
        }
    • 一次读取多个数据【缓冲数组(1024的整数倍)】

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      public static void main(String[] args) throws IOException {
      // 文件输入流(读)
      FileInputStream fi = new FileInputStream("./aa.txt");
      // 读取数据
      byte[] bytes = new byte[2];
      // 一次读取多个字节数据,具体读多少,根数组的长度有关
      // 返回值(len): 本地读取到了多少个字节数据
      int len = fi.read(bytes);
      // 读取 bytes,从 0 开始读取,读 len 个
      String s = new String(bytes, 0, len);
      System.out.println(s);
      fi.close();
      }
    • 文件拷贝的优化【推荐】

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      public static void main(String[] args) throws IOException {
      FileInputStream fis = new FileInputStream("./aa.txt");
      FileOutputStream fos = new FileOutputStream("./bb.txt");
      int len;
      byte[] bytes = new byte[1024];
      while ((len = fis.read(bytes)) != -1) {
      // 拷贝
      fos.write(bytes, 0, len);
      }
      // 释放资源
      fos.close();
      fis.close();
      }

高级流

缓冲流
  • 缓冲流体系

    缓冲流体系
  • 字节缓冲流

    • 常用方法

      方法名称 说明
      BufferedInputStream(InputStream is) 把基本流包装成高级流,提高读取数据的性能
      BufferedOutputStream(OutputStream os) 把基本流包装成高级流,提高写出数据的性能
    • 提升性能的原理:底层自带了长度为8192的缓冲区提高性能

转换流
  • 转换流

    • 转换流属于字符流

    • 作用: 提供字节流与字符流之间的转换

    • 编码解码

      1
      2
      解码: 字节、字节数组 转换为 字符数组、字符串
      编码: 字符数组、字符串 转换为 字节、字节数组
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    @Test
    public void testFileInputStream() {
    InputStreamReader isr = null;
    try {
    FileInputStream fis = new FileInputStream("D:\\JavaCode\\src\\com\\coderitl\\testio\\Hello.txt");
    isr = new InputStreamReader(fis,"UTF-8");
    char[] cbuf = new char[20];
    int len;
    while((len=isr.read(cbuf))!=-1){
    String str = new String(cbuf,0,len);
    System.out.println(str);
    }
    } catch (IOException e) {
    e.printStackTrace();
    } finally {
    try {
    isr.close();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    @Test
    public void testLiu() {
    InputStreamReader isr = null;
    OutputStreamWriter osw = null;
    try {
    // 1. 造文件
    File file1 = new File("D:\\JavaCode\\src\\com\\coderitl\\testio\\Hello.txt");
    File file2 = new File("D:\\JavaCode\\src\\com\\coderitl\\testio\\HelloGBK.txt");
    // 2. 流
    FileInputStream fis = new FileInputStream(file1);
    FileOutputStream fos = new FileOutputStream(file2);
    // 转换流
    isr = new InputStreamReader(fis, "UTF-8");
    osw = new OutputStreamWriter(fos, "GBK");

    char[] cbuf = new char[20];
    // 遍历
    int len;
    while ((len = isr.read(cbuf)) != -1) {
    // 写入
    osw.write(cbuf, 0, len);
    }

    } catch (IOException e) {
    e.printStackTrace();
    } finally {
    try {
    if (isr != null) {
    isr.close();
    }
    } catch (IOException e) {
    e.printStackTrace();
    }

    try {
    if (osw != null) {
    osw.close();
    }
    } catch (IOException e) {
    e.printStackTrace();
    }
    }

    }

对象流

  • ObjectInputStreamObjectOutputStream
    • 用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来
  • 序列化: 用ObjectOutputStream类保存基本类型数据或对象的机制
  • 反序列化: 用 ObjectInputStream类读取基本类型数据或对象的机制
  • ObjectOutputStreamObjectInputStream不能序列化 static %} transient %}修饰的成员变量

字符集

  • 在计算机中,任意数据都是以二进制的形式来存储的
  • 计算机中最小的存储单元是一个字节
  • ASCII 字符集中,一个英文占一个字节
  • 简体中文版Windows,默认使用GBK 字符集
  • GBK 字符集完全兼容ASCII 字符集
    • 一个英文占一个字节,二进制第一位是0
    • 一个中文占两个字节,二进制高位字节的第一位是1

第十一章

网络编程

什么是网络编程
  • 概念

    在网络通信协议下,不同计算机上运行的程序,进行数据的传输

  • BS/CS 架构的优缺点

    BS CS
    不需要开发客户端,只需要开发服务端 画面可以做的非常精美,用户体验好
    用户不需要下载,打开浏览器就能使用 需要开发客户端,也需要开发服务端
    如果应用过大,用户体验受到影响 用户需要下载和更新的时候太麻烦
  • 网络编程三要素

    • IP: 设备在网络中的地址,是唯一标识
    • 端口: 应用程序在设备中唯一的标识
    • 协议: 数据在网络中传输的规则,常见的协议有UDP、TCP、http、https、ftp
socket
基础使用
  • 服务端

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    package com.example;

    import java.io.IOException;
    import java.net.ServerSocket;
    import java.net.Socket;

    public class Server {
    public static void main(String[] args) throws IOException {
    // 启动服务器
    ServerSocket serverSocket = new ServerSocket(8888);
    System.out.println("服务器已经启动.............");
    Socket socket = serverSocket.accept();
    System.out.println("有客户端连接成功............");
    }
    }

  • 客户端

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package com.example;

    import java.io.IOException;
    import java.net.Socket;

    public class Client {
    public static void main(String[] args) {
    try {
    Socket socket = new Socket("127.0.0.1", 8888);
    System.out.println("服务器连接成功..........");
    } catch (IOException e) {
    System.out.println("服务器连接失败..............");
    throw new RuntimeException(e);
    }

    }
    }

多客户端 – 一个服务端
  • 服务端

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    package com.example;

    import java.io.IOException;
    import java.net.ServerSocket;
    import java.net.Socket;

    public class Server {
    public static void main(String[] args) throws IOException {
    // 启动服务器
    ServerSocket serverSocket = new ServerSocket(8888);
    int count = 0;
    while (true) {
    System.out.println("服务器已经启动.............");
    Socket socket = serverSocket.accept();
    count++;
    System.out.println("有" + count + "连接成功");
    }
    }
    }

  • 客户端

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package com.example;

    import java.io.IOException;
    import java.net.Socket;

    public class Client {
    public static void main(String[] args) {
    try {
    Socket socket = new Socket("127.0.0.1", 8888);
    System.out.println("服务器连接成功..........");
    } catch (IOException e) {
    System.out.println("服务器连接失败..............");
    throw new RuntimeException(e);
    }

    }
    }

通信
  • 服务端

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    package com.example;

    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.net.ServerSocket;
    import java.net.Socket;

    public class Server {
    public static void main(String[] args) throws IOException {
    // 启动服务器
    ServerSocket serverSocket = new ServerSocket(8888);
    System.out.println("服务器已经启动.............");
    Socket socket = serverSocket.accept();

    InputStream is = socket.getInputStream();
    InputStreamReader isr = new InputStreamReader(is);
    BufferedReader br = new BufferedReader(isr);
    String msg = br.readLine();
    System.out.println("客户端说: " + msg);
    }
    }

  • 客户端

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    package com.example;

    import java.io.IOException;
    import java.io.OutputStream;
    import java.io.PrintStream;
    import java.net.Socket;

    public class Client {
    public static void main(String[] args) {
    try {
    Socket socket = new Socket("127.0.0.1", 8888);
    // 流的准备
    OutputStream os = socket.getOutputStream();
    PrintStream printStream = new PrintStream(os, true);
    // 流的操作
    String msg = "吃饭了吗?";
    // 发送消息
    printStream.println(msg);
    // 释放资源
    printStream.close();
    os.close();
    } catch (IOException e) {
    System.out.println("服务器连接失败..............");
    throw new RuntimeException(e);
    }
    }
    }

    • 输出

      是否通信
配合多线程
  • 服务端

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    package com.example;

    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.net.ServerSocket;
    import java.net.Socket;

    public class Server {
    public Server() throws Exception {
    ServerSocket serverSocket = new ServerSocket(8888);
    System.out.println("服务器已经启动成功.......");
    while (true) {
    // 等待客户端连接,如果有新的连接请求则返回一个新的套接字对象
    Socket socket = serverSocket.accept();
    System.out.println("客户端连接成功...............");
    // 开始处理客户端的消息
    ServerThread thread = new ServerThread(socket);
    thread.start();
    }
    }

    public static void main(String[] args) throws Exception {
    new Server();
    }

    private class ServerThread extends Thread {
    private Socket socket;

    public ServerThread(Socket socket) {
    this.socket = socket;
    }

    @Override
    public void run() {
    try {
    InputStream is = socket.getInputStream();
    InputStreamReader isr = new InputStreamReader(is);
    BufferedReader br = new BufferedReader(isr);
    while (true) {
    // 有活就干
    if (is.available() > 0) {
    String msg = br.readLine();
    System.out.println("客户端说: " + msg);
    }
    // 没活就休眠
    try {
    this.sleep(50);
    } catch (InterruptedException e) {
    throw new RuntimeException(e);
    }
    }
    } catch (Exception e) {
    throw new RuntimeException(e);
    }

    }
    }
    }

  • 客户端

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    package com.example;

    import java.io.IOException;
    import java.io.OutputStream;
    import java.io.PrintStream;
    import java.net.Socket;
    import java.util.Scanner;

    public class Client {
    public static void main(String[] args) throws IOException {
    // 建立连接
    try {
    Socket socket = new Socket("127.0.0.1", 8888);
    System.out.println("服务器连接成功.........");
    Scanner sc = new Scanner(System.in);
    String msg = sc.next();
    OutputStream os = socket.getOutputStream();
    PrintStream ps = new PrintStream(os, true);
    ps.println(msg);
    ps.close();
    } catch (IOException e) {
    System.err.println("服务器连接失败");
    throw new RuntimeException(e);
    }
    }
    }

类加载器

  • 类加载

    当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过类的加载、类的连接、类的初始化这三个步骤来对类进行初始化。如果不出意外情况,JVM 将会连续完成这三个步骤,所以有时也把这三个步骤统称未类加载或者类初始化

    加载过程
    • 类的加载

      • 就是指将class 文件读入内存,并为之创建一个java.lang.Class 对象
      • 任何类被使用时,系统都会为之建立一个java.lang.Class 对象
    • 类的连接

      • 验证阶段: 用于校验被加载的类是否有正确的内部结构,并和其他类协调一致
      • 准备阶段: 负责为类的类变量分配内存,并设置默认初始化值
      • 解析阶段: 将类的二进制数据中的符号引用替换为直接引用
    • 类的初始化

      在给阶段,主要就是对类变量进行初始化

      • 类的初始化步骤
        • 假如类还未被加载和连接,则程序先加载并连接该类
        • 假如该类的直接父类还未被初始化,则先初始化器直接父类
        • 假如类中有初始化语句,则系统一次执行这些被初始化语句
      • 类的初始化时机
        • 创建类的实例
        • 调用类的方法
        • 访问类或者接口的类变量,或者为该类变量赋值
        • 使用反射方式来强制创建某个类或接口对应的java.lang.Class 对象
        • 初始化某个类的子类
        • 直接使用java.exe 命令来运行某个主类
  • 类加载器的作用

    • 负责将.class 文件加载到内存中,并为之生成对应的java.lang.Class 对象
  • JVM 的类加载机制

    • 全盘负责: 就是当一个类加载负责加载某个Class 时,Class 所依赖的引用的其他Class 也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入
    • 父类委托: 就是当一个类加载器负责加载某个Class 时,先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类
    • 缓存机制: 保存所有加载过的Class 都会被缓存,当程序需要使用某个Class 对象时,类加载从缓存区中搜索该Class,只由当缓存区中不存在该Class 对象时,系统才会读取该类对应的二进制数据,并将其转换成Class 对象,存储到缓存区
  • 类加载器

    ClassLoader: 是负责加载类的对象

    • Java 运行时具有以下内置类加载器

      • Bootstrap class loader: 它是虚拟机的内置类加载器,通常表示为null,并且没有父null
      • Platform class loader: 平台类加载器可以看到所有平台类,平台类包括由平台类加载器或其祖先定义的JAVASE 平台API,其实现类和JDK 特定的运行时类
      • Systemclass loader: 它也被称为应用程序类加载器,与平台类加载器不同。系统类加载器通常用于定义应用程序类路径,,模块路径和JDK 特定工具上的类

      类加载器的继承关系: System 的父加载器为Platform,Platforrm 的父加载器为Bootstrap

    • Classloader 中的两个方法

      • static ClassLoader getStstemClassLoader(): 返回用于委派的系统类加载器
      • Classloader getParent(): 返回父类加载器进行委派
  • 关于java.lang.Class 类的理解

    • 类的加载过程

      程序经过 java.exe 命令以后,会生成一个或多个字节码文件(.class 结尾)

      接着我们使用 java.exe 命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到类内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时的类,就作为Class 的一个实例

      • 换句话说,Class 的实例就对应着一个运行时类

      • 加载到内存中的运行时类,会缓存一定的时间,在此时间之内,我们可以通过不同的方式来获取此运行时类

反射

  • 反射概述

    Java 反射机制: 是指在运行时去获取一个类的变量和方法信息,然后通过获取到的信息来创建对象,调用方法的一种机制,由于这种动态性,可以极大的增强程序的灵活性,程序不用在编译期就完成确定,在运行期任然可以扩展

  • 获取Class 类的对象

    我们想要通过反射去使用一个类,首先我们要获取到该类的字节码文件对象,也就是类型为Class 类型的对象,可以通过如下三种方式获取Class 类型对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    package com.example;

    public class Student {
    private Integer stuId;
    private String stuName;

    public Student() {
    }

    public Student(Integer stuId, String stuName) {
    this.stuId = stuId;
    this.stuName = stuName;
    }

    private Student(Integer stuId) {
    this.stuId = stuId;
    }

    public Integer getStuId() {
    return stuId;
    }

    public void setStuId(Integer stuId) {
    this.stuId = stuId;
    }

    public String getStuName() {
    return stuName;
    }

    public void setStuName(String stuName) {
    this.stuName = stuName;
    }

    @Override
    public String toString() {
    return "Student{" +
    "stuId=" + stuId +
    ", stuName='" + stuName + '\'' +
    '}';
    }
    }

    • 使用类的class 属性来获取该类对应的Class 对象

      1
      2
      3
      4
      5
      6
      7
      public static void main(String[] args) {
      Class<Student> c1 = Student.class;
      System.out.println(c1);
      Class<Student> c2 = Student.class;
      // 字节码只有一份: true
      System.out.println(c2 == c1);
      }
    • 调用对象的getClass 方法,返回该对象所属类对应的Class 对象,该方法是Object 类中的方法,所有的java 对象都可以调用该方法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      public static void main(String[] args) {
      Class<Student> c1 = Student.class;
      System.out.println(c1);

      Student student = new Student();
      Class<? extends Student> c2 = student.getClass();
      // 字节码只有一份: true
      System.out.println(c2 == c1);
      }
    • 使用Class 类中的静态方法forName(String className),该方法需要传入字符串参数,该字符串参数的值是某个类的全路径,也就是完整包名的路径

      1
      2
      3
      4
      5
      6
      7
      public static void main(String[] args) throws ClassNotFoundException {
      Class<Student> c1 = Student.class;
      System.out.println(c1);

      Class<?> aClass = Class.forName("com.example.Student");
      System.out.println(c1 == aClass);
      }
  • 反射获取构造方法并使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    package com.example;

    import java.lang.reflect.Constructor;

    // 获取构造方法并使用
    public class ReflectDemop {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
    // 1. 获取 Class 对象
    Class<Student> c1 = Student.class;
    // 2. 获取构造方法并使用
    // 返回一个包含 Constructor 对象的数组,Constructor对象反映了由该 Class对象表示的类的所有公共构造函数
    Constructor<?>[] constructors = c1.getConstructors();
    for (Constructor<?> constructor : constructors) {
    /**
    * public com.example.Student()
    * public com.example.Student(java.lang.Integer,java.lang.String)
    */
    System.out.println(constructor);
    }
    System.out.println("-------------------------------------------------");
    // 返回反应由该 Class 对象表示的类声明的所有构造函数的 Constructor 对象的数组
    Constructor<?>[] declaredConstructors = c1.getDeclaredConstructors();
    for (Constructor<?> declaredConstructor : declaredConstructors) {
    System.out.println(declaredConstructor);
    }
    }
    }
    输出所有构造
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
    // 1. 获取 Class 对象
    Class<Student> c1 = Student.class;
    Constructor<Student> constructor = c1.getConstructor();
    Student student = constructor.newInstance();
    student.setStuId(1001);
    student.setStuName("aa");
    // Student{stuId=1001, stuName='aa'}
    System.out.println(student);
    }
  • 练习

    • 需求: 通过反射实现Student{stuId=1002, stuName='bb'}

    • 实现

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      package com.example;

      import java.lang.reflect.Constructor;
      import java.lang.reflect.InvocationTargetException;

      // 获取构造方法并使用
      public class ReflectDemop {
      public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
      Class<Student> studentClass = Student.class;
      // int 和 Integer的 class 不一样
      Constructor<Student> constructor = studentClass.getConstructor(Integer.class, String.class);
      Student student = constructor.newInstance(1002, "bb");
      // Student{stuId=1002, stuName='bb'}
      System.out.println(student);
      }
      }

  • 反射获取成员变量并使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    package com.example;

    public class Student {
    public String address;
    int age;
    private String name;

    public Student() {
    }

    public Student(String name) {
    this.name = name;
    }

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

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

    public String getAddress() {
    return address;
    }

    public void setAddress(String address) {
    this.address = address;
    }

    public int getAge() {
    return age;
    }

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

    public String getName() {
    return name;
    }

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

    @Override
    public String toString() {
    return "Student{" +
    "address='" + address + '\'' +
    ", age=" + age +
    ", name='" + name + '\'' +
    '}';
    }
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    package com.example;

    import java.lang.reflect.Field;

    // 获取构造方法并使用
    public class ReflectDemop {
    public static void main(String[] args) {
    Class<Student> studentClass = Student.class;
    // Field[] getFields() 返回一个包含 Field 对象的数组,Field 对象反映由该 Class 对象表示的类或接口的所有可访问的公共字段
    Field[] fields = studentClass.getFields();
    for (Field field : fields) {
    // public String address;
    System.out.println(field);
    }
    System.out.println("------------------------------");
    // Field[] 返回一个 Field 对象的数组,反映了由该 Class 对象表示的类或接口声明的所有字段
    Field[] declaredFields = studentClass.getDeclaredFields();
    for (Field declaredField : declaredFields) {
    System.out.println(declaredField);
    }
    }
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 成员变量赋值
    public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException {
    Class<?> c = Class.forName("com.example.Student");
    // 获取指定字段
    Field addressField = c.getField("address");
    // 获取无参构造
    Constructor<?> conn = c.getConstructor();
    Object obj = conn.newInstance();
    // 给 obj 的成员变量 addressField 赋值为 广东
    addressField.set(obj, "广东");
    // Student{address='广东', age=0, name='null'}
    System.out.println(obj);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 成员变量赋值
    public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException {
    Class<Student> c = Student.class;
    Constructor<Student> conn = c.getConstructor();
    Student student = conn.newInstance();
    Field addressField = c.getField("address");
    addressField.set(student, "nn");
    // Student{address='nn', age=0, name='null'}
    System.out.println(student);
    }
  • 反射获取成员方法并使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public static void main(String[] args) {
    // Method[] 返回一个包含方法对象的数组,方法对象反映由该 Class 对象表示的类或接口的所有公共方法,包括由类或接口声明的对象以及从
    // 超类和超级接口继承的类
    Class<Student> c = Student.class;
    Method[] methods = c.getMethods();
    for (Method method : methods) {
    System.out.println(method);
    }

    // Method[] 返回一个包好感方法对象的数组,方法对象反映由该 Class 对象表示的类或接口的所有声明方法,包括 public,protected,default访问和私有方法,但不包括继承方法
    Method[] declaredMethods = c.getDeclaredMethods();
    for (Method declaredMethod : declaredMethods) {
    System.out.println(declaredMethod);
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
    Class<Student> c = Student.class;
    // 获取 save 方法
    Method method = c.getMethod("save");
    // 获取无参构造创建对象
    Constructor<Student> cons = c.getConstructor();
    Student student = cons.newInstance();
    // invoke(obj,Object ...args) args方法的参数
    // save...............
    method.invoke(student);
    }