前言

视频:黑马程序员

1.Java入门

1.人机交互

  • 什么是cmd?

    在windows操作系统中,利用命令行的方式操作计算机。如:打开文件,打开文件夹,创建文件夹等。

  • 常用CMD命令

    操作 说明
    盘符名称: 盘符切换。E:,表示切换到E盘。
    dir 查看当前路径下的内容。
    cd 目录 进入单级目录。cd itheima
    cd .. 回退到上一级目录。
    cd 目录1\目录2... 进入多级目录。cd itheima\JavaSE
    cd \ 回退到盘符目录。
    cls 清屏。
    exit 退出命令提示符窗口。
  • 环境变量

2. Java概述

  • Java是什么?

    计算机语言:人与计算机之间进行信息交流沟通的一种特殊语言

  • JDK下载,安装与介绍(17版本)

    1. 下载:官方网站获取

    2. 安装:改路径即可

    3. 介绍

      目录名称 说明
      bin 该路径下存放了JDK的各种工具命令。javac和java就放在这个目录。
      conf 该路径下存放了JDK的相关配置文件。
      include 该路径下存放了一些平台特定的头文件。
      jmods 该路径下存放了JDK的各种模块。
      legal 该路径下存放了JDK各模块的授权文档。
      lib 该路径下存放了JDK工具的一些补充JAR包。
  • 入门HelloWorld

    1. Java程序开发运行流程

      需要三个步骤:编写程序,编译程序,运行程序。

    2. HelloWorld案例的编写(修改为.java后缀)

      javac + 文件名 + 后缀名 (就是编译java文件)

      java + 文件名(无后缀,运行编译之后的class文件)

      java文件:程序员自己编写的代码。

      class文件:交给计算机执行的文件。

      1
      2
      3
      4
      5
      public class HelloWorld {
      public static void main(String[] args) {
      System.out.println("HelloWorld");
      }
      }
    3. System.out.printf();该输出语句支持%s占位符

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      public class Test {
      public static void main(String[] args) {
      //两部分参数:
      //第一部分参数:要输出的内容%s(占位)
      //第二部分参数:填充的数据

      System.out.printf("你好啊%s","张三");//用张三填充第一个%s
      System.out.println();//换行
      System.out.printf("%s你好啊%s","张三","李四");//用张三填充第一个%s,李四填充第二个%s
      }
      }
  • 环境变量

    1. JAVA_HOME
    2. Path:
  • Java语言的发展

    1. Java5.0:Java的第一个大版本更新。
    2. Java8.0:目前绝大数公司正在使用的版本。因为这个版本最为稳定。
    3. Java17.0:学习的版本。

    向下兼容,新的版本只是在原有的基础上添加了一些新的功能。

  • Java的三大平台

    1. JavaSE(标准版):是其他两个版本的基础。

    2. JavaME(微型版):Java语言的小型版,用于嵌入式消费类电子设备或者小型移动设备的开发。

    3. JavaEE(企业版):用于Web方向的网站开发。(主要从事后台服务器的开发)

  • Java的主要特性

    1. 面向对象
    2. 安全性
    3. 多线程
    4. 简单易用
    5. 开源
    6. 跨平台
    7. 分布式

    跨平台原理:

    001

    • 操作系统本身其实是不认识Java语言的。
    • 但是针对于不同的操作系统,Java提供了不同的虚拟机。虚拟机会把Java语言翻译成对应操作系统的二进制语言。
  • JRE和JDK

    001

    1. JVM(Java Virtual Machine),Java虚拟机,java程序运行的地方。
    2. 核心类库:Java提前定义好的,如:println
    3. JRE(Java Runtime Environment),Java运行环境,包含了JVM和Java的核心类库(Java API)
    4. JDK(Java Development Kit)称为Java开发工具包(javac,java,jdb调试工具),包含了JRE和开发工具
    1
    JDK > JRE > JVM
  • Java8的安装与配置

    1. 官网下载JDK安装

    2. 环境变量:

    CLASSPATH:没设置的话,会自动设置为当前目录(不要设置)

2.Java基础概念

1.注释

注释是对代码的解释和说明文字。

  • 单行注释:

    1
    // 这是单行注释文字
  • 多行注释:

    1
    2
    3
    4
    5
    6
    /*
    这是多行注释文字
    这是多行注释文字
    这是多行注释文字
    */
    注意:多行注释不能嵌套使用。
  • 文档注释:

    1
    2
    3
    4
    5
    /**
    这是多行注释文字
    这是多行注释文字
    这是多行注释文字
    */
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class Test {
    /**
    * 该方法用于计算两个整数的和
    * @param a 第一个整数(入参说明)
    * @param b 第二个整数(入参说明)
    * @return 两个整数的算术和(返回值说明)
    */
    public static int add(int a, int b) {
    return a + b;
    }
    }

2. 关键字

  • 概念:被Java赋予了特定含义的英文单词。

    abstract assert boolean break byte
    case catch char class const
    continue default do double else
    enum extends final finally float
    for goto if implements import
    instanceof int interface long native
    new package private protected public
    return strictfp short static super
    switch synchronized this throw throws
    transient try void volatile while
  • 第一个关键字class

    表示定义一个类。创建一个类。

    1. 类:Java项目最基本的组成单元,一个完整的Java项目有可能会有成千上万个类来组成的。

    2. 类名:class后面跟随的就是这个类的名字。

    3. 在类名后面会有一对大括号,表示这个类的内容。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      public class HelloWorld{


      }
      /*
      解释:
      class表示定义类。
      类名:HelloWorld
      HelloWorld后面的大括号表示这个类的范围。
      */

3. 字面量

作用:数据在程序中的书写格式。

字面量类型 说明 程序中的写法
整数 不带小数的数字 666,-88
小数 带小数的数字 13.14,-5.21
字符 必须使用单引号,有且仅能一个字符(可中文,中文是2个字节) ‘A’,‘0’, ‘我’
字符串 必须使用双引号,内容可有可无 “HelloWorld”,“黑马程序员”
布尔值 布尔值,表示真假,只有两个值:true,false true 、false
空值 一个特殊的值,空值 值是:null

特殊字符(串):

1
'\t':打印时把前面的字符串补齐到8,或8的整数,最少一个空格,最多8个空格
1
2
3
4
5
6
7
8
9
10
public class Demo {
public static void main(String[] args) {
System.out.println(10); // 输出一个整数
System.out.println(5.5); // 输出一个小数
System.out.println('a'); // 输出一个字符
System.out.println(true); // 输出boolean值true
// System.out.println(null); 不能直接打印
System.out.println("欢迎来到黑马程序员"); // 输出字符串
}
}

区分技巧:

  1. 用双引号引起来的,是字符串类型的字面量。
  2. 字符类型的字面量,用单引号引起来,有且只能有一个。
  3. 布尔类型的字面量只有两个值:true、false。
  4. 空类型的字面量只有一个值:null。

4. 变量

变量:在程序中临时存储数据的容器,但是这个容器中只能存一个值。

  • 变量的定义格式:

    数据类型 变量名 = 数据值;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class VariableDemo{
    public static void main(String[] args){
    //定义一个整数类型的变量
    //数据类型 变量名 = 数据值;
    int a = 16;
    System.out.println(a);//16

    //定义一个小数类型的变量
    double b = 10.1;
    System.out.println(b);//10.1
    }
    }
  • 变量的注意事项

    1. 变量名不能重复
    2. 在一条语句中,可以定义多个变量。(影响代码的阅读,了解即可)
    3. 变量在使用之前必须要赋值。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class VariableDemo2{
    public static void main(String[] args){
    //1.变量名不允许重复
    //int a = 10;
    //int a = 20;
    //System.out.println(a);

    //2.一条语句可以定义多个变量
    //了解。
    //int a = 10, b = 20, c = 20,d = 20;
    //System.out.println(a);//?
    //System.out.println(b);//?


    //3.变量在使用之前必须要赋值
    int a = 30;
    System.out.println(a);
    }
    }
  • 计算机中的存储方式

    1
    2
    3
    4
    5
    6
    public static void main(String[] args) {
    System.out.println(0b1010); //二进制
    System.out.println(012); //八进制
    System.out.println(123);
    System.out.println(0x10); //十六进制
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    文字数据:
    ASCII
    GB2312 -> GBK编码(windows默认使用)
    Unicode编码 (统一码)

    图片数据:
    分辨率:1920(宽) * 1080(长)
    像素
    灰度图(黑白图):0(黑),255(白)
    彩色图:R,G,B(值越大颜色越浓0-255)

    声音数据:
    进行采样

5. 数据类型

  • Java数据类型的分类

    1. 基本数据类型
    2. 引用数据类型
  • 基本数据类型的四类八种

    数据类型 关键字 内存占用 取值范围
    整数 byte 1 负的2的7次方 ~ 2的7次方-1(-128~127)
    short 2 负的2的15次方 ~ 2的15次方-1(-32768~32767)
    int 4 负的2的31次方 ~ 2的31次方-1
    long 8 负的2的63次方 ~ 2的63次方-1
    浮点数 float 4 1.401298e-45 ~ 3.402823e+38(23位小数)
    double 8 4.9000000e-324 ~ 1.797693e+308(52位小数)
    字符 char 2 0-65535
    布尔 boolean 1 true,false
    1. 在java中整数默认是int类型,浮点数默认是double类型
    2. 需要记忆以下几点:
      • byte类型的取值范围:-128 ~ 127
      • int类型的大概取值范围:-21亿多 ~ 21亿多
      • 整数类型和小数类型的取值范围大小关系:double > float > long > int > short > byte
  • 定义8种基本数据类型变量

    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
    public class VariableDemo3{
    public static void main(String[] args){
    //1.定义byte类型的变量
    //数据类型 变量名 = 数据值;
    byte a = 10;
    System.out.println(a);

    //2.定义short类型的变量
    short b = 20;
    System.out.println(b);

    //3.定义int类型的变量
    int c = 30;
    System.out.println(c);

    //4.定义long类型的变量
    long d = 123456789123456789L;
    System.out.println(d);

    //5.定义float类型的变量
    float e = 10.1F;
    System.out.println(e);

    //6.定义double类型的变量
    double f = 20.3;
    System.out.println(f);

    //7.定义char类型的变量
    char g = 'a';
    System.out.println(g);

    //8.定义boolean类型的变量
    boolean h = true;
    System.out.println(h);

    }
    }

    注意点:

    • 定义一个long类型的变量,在数据值的后面需要加上L后缀。(大小写都可以,建议大写。)
    • 定义一个float类型的变量,在数据值的后面需要加上F后缀。(大小写都可以)

6. 标识符

大多都在遵守阿里巴巴的命名规则。

  • 硬性要求:

    1
    2
    3
    4
    1.必须由数字、字母、下划线_、美元符号$组成。
    2.数字不能开头
    3.不能是关键字
    4.区分大小写的。
  • 小驼峰命名法(适用于变量名方法名

    1. 如果是一个单词,那么全部小写,比如:name

    2. 如果是多个单词,那么从第二个单词开始,首字母大写,比如:firstName、maxAge

  • 大驼峰命名法(适用于类名接口名

    1. 如果是一个单词,那么首字母大写。比如:Demo、Test。

    2. 如果是多个单词,那么每一个单词首字母都需要大写。比如:HelloWorld

  • 阿里巴巴命名规范细节:

    1. 尽量不要用拼音。但是一些国际通用的拼音可视为英文单词。

      正确:alibaba、hangzhou、nanjing

      错误:jiage、dazhe

    2. 平时在给变量名、方法名、类名起名字的时候,不要使用下划线或美元符号。

      错误:_name

      正确:name

7. 键盘录入

  • 快速使用

    1
    2
    3
    4
    5
    6
    7
    8
    第一步:
    导包:表示先找到Scanner这个类。

    第二步:
    创建对象:表示申明一下,准备开始用Scanner这个类。

    第三步:
    接收数据:也是真正的代码。

    代码示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //导包,其实就是先找到Scanner这个类在哪
    import java.util.Scanner;
    public class ScannerDemo1{
    public static void main(String[] args){
    //2.创建对象,其实就是申明一下,我准备开始用Scanner这个类了。
    Scanner sc = new Scanner(System.in);
    //3.接收数据
    //当程序运行之后,我们在键盘输入的数据就会被变量i给接收了
    System.out.println("请输入一个数字");
    int n1 = sc.nextInt();
    int n2 = sc.nextInt();
    System.out.println(n1+n2);
    }
    }
  • 深入学习

    键盘录入涉及到的方法如下:next()、nextLine()、nextInt()、nextDouble()。

    1. next()、nextLine():

      • 可以接受任意数据,但是都会返回一个字符串。
      • 如:键盘录入abc,那么会把abc看做字符串返回。
    2. nextInt():

      • 只能接受整数。

      • 如:键盘录入123,会把123当做int类型的整数返回。

        键盘录入小数或者其他字母,就会报错。

    3. nextDouble():

      能接收整数和小数,但是都会看做小数返回。

  • 方法底层细节

    1. 第一个细节:

      next(),nextInt(),nextDouble()在接收数据的时候,会遇到空格,回车,制表符其中一个就会停止接收数据。

    2. 第二个细节:

      next(),nextInt(),nextDouble()在接收数据的时候,会遇到空格,回车,制表符其中一个就会停止接收数据。但是这些符号 + 后面的数据还在内存中并没有接收。如果后面还有其他键盘录入的方法,会自动将这些数据接收。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      Scanner sc = new Scanner(System.in);
      String s1 = sc.next();
      String s2 = sc.next();
      System.out.println(s1);
      System.out.println(s2);
      //此时值键盘录入一次a b(注意a和b之间用空格隔开)
      //那么第一个next();会接收a,a后面是空格,那么就停止,所以打印s1是a
      //但是空格+b还在内存中。
      //第二个next会去掉前面的空格,只接收b
      //所以第二个s2打印出来是b
    3. 第三个细节:

      nextLine()方法是把一整行全部接收完毕。

      1
      2
      3
      4
      5
      Scanner sc = new Scanner(System.in);
      String s = sc.nextLine();
      System.out.println(s);
      //键盘录入a b(注意a和b之间用空格隔开)
      //那么nextLine不会过滤前面和后面的空格,会把这一整行数据全部接收完毕。
  • 结论(如何使用)

    键盘录入分为两套:

    1. next()、nextInt()、nextDouble()这三个配套使用。

      如果用了这三个其中一个,则不使用nextLine()。

    2. nextLine()单独使用。

      如果想要整数,那么先接收,再使用Integer.parseInt进行类型转换。

8. IDEA

  • IDEA概述

    集成环境:把代码编写,编译,执行,调试等多种功能综合到一起的开发工具。

  • IDEA的下载、安装、设置

    1. 下载网址:https://www.jetbrains.com/idea

    2. IDEA的设置

      • 修改主题

        001

      • 修改字体和大小

        001

      • 修改注释颜色

        001

      • 自动导包

        001

      • 提示忽略大小写

        001

9.IDEA层级结构介绍

  • 结构分类
    1. project(项目、工程)
    2. module(模块)
    3. package(包)
    4. class(类):真正写代码的地方。
  • 类的相关操作
    1. 新建类文件
    2. 删除类文件
    3. 修改类文件
  • 模块的相关操作
    1. 新建模块
    2. 删除模块
    3. 修改模块
    4. 导入模块
  • 项目的相关操作
    1. 关闭项目
    2. 打开项目
    3. 修改项目
    4. 新建项目

3.运算符

1.运算符和表达式

  • 运算符:就是对常量或者变量进行操作的符号。比如:+ - * /
  • 表达式:用运算符把常量或者变量连接起来的,符合Java语法的式子就是表达式。比如:a + b 整体就是表达式。

2.算术运算符

  • 分类:

    1
    + - * / %
    1
    2
    3
    4
    5
    6
    /:
    1.整数相除结果只能得到整除,如果结果想要是小数,必须要有小数参数。
    2.小数直接参与运算,得到的结果有可能是不精确的。
    案例:
    System.out.println( 10 / 3);//3
    System.out.println(10.0 / 3);//3.3333333333333335
    1
    2
    3
    4
    5
    6
    7
    8
    %:取模、取余。

    案例:
    System.out.println(10 % 2);//0
    System.out.println(10 % 3);//1
    应用场景:
    //可以利用取模来判断一个数是奇数还是偶数
    System.out.println(15 % 2);//1 奇数

3.隐式转换

  • 概念:也叫自动类型提升。

    把取值范围小的数据或者变量,赋值给取值范围大的变量。(程序自动完成)

  • 两种提升规则:

    1. 取值范围小的和取值范围大的进行运算,小的会先提升为大的,再进行运算。

    2. byte、short、char三种类型的数据在运算的时候,都会直接先提升为int,然后再进行运算。

    3. 取值范围从小到大的关系:

      1
      byte < short < int < long < float < double

4.强制转换

  • 概念:把取值范围大的数据或者变量赋值给另一个取值范围小的变量。是不允许直接操作。

  • 书写格式:

    目标数据类型 变量名 = (目标数据类型)被强转的数据;

    1
    2
    3
    4
    5
    6
    7
    public class OperatorDemo2 {
    public static void main(String[] args) {
    double a = 12.3;
    int b = (int) a;
    System.out.println(b);//12
    }
    }

    强制转换有可能会导致数据发生错误。(数据的精度丢失)

5.字符串和字符 + 操作

  • 字符串的+操作:

    1. 当+操作中出现字符串时,此时就是字符串的连接符,会将前后的数据进行拼接,并产生一个新的字符串。
    2. 当连续进行+操作时,从左到右逐个执行的。
    1
    1 + 2 + "abc" + 2 + 1 //结果:“3abc21”
  • 字符的+操作

    1. 规则:当+操作中出现了字符,会拿着字符到计算机内置的ASCII码表中去查对应的数字,然后再进行计算。

    2. 案例:

      1
      2
      3
      char c = 'a';
      int result = c + 0;
      System.out.println(result);//97

6.自增自减运算符

  • 分类:

    1
    2
    ++  自增运算符
    -- 自减运算符
  • 使用方式:

    1. 放在变量的前面,先++。 比如:++a
    2. 放在变量的后面,后++。 比如:a++

7.赋值运算符

  • 常用赋值运算符:=

    运算过程:就是把等号右边的结果赋值给左边的变量

  • 扩展赋值运算符

    1. 分类:+=、-=、*=、/=、%=

    2. 注意点:扩展的赋值运算符中隐层还包含了一个强制转换。

      a += b ;实际上相当于 a = (byte)(a + b);

8.关系运算符

比较运算符,左边与右边进行判断。

  • 分类:

    符号 解释
    == 判断左边与右边是否相等,如果成立就是true,如果不成立就是false
    != 判断左边与右边是否不相等,如果成立就是true,如果不成立就是false
    > 判断左边是否大于右边,如果成立就是true,如果不成立就是false
    >= 判断左边是否大于等于右边,如果成立就是true,如果不成立就是false
    < 判断左边是否小于右边,如果成立就是true,如果不成立就是false
    <= 判断左边是否小于等于右边,如果成立就是true,如果不成立就是false
  • 注意点:关系运算符最终的结果一定是布尔类型的。要么是true,要么是false

9.逻辑运算符

  • & 和 | 的使用:

    1. &:逻辑与(而且)
    2. |:逻辑或(或者)
  • ^(异或)的使用:

    计算规则:如果两边相同,结果为false,如果两边不同,结果为true

  • !(取反)的使用:

    1. 计算规则:false取反就是true,true取反就是false
    2. 取反最多只用一个。
  • 短路逻辑运算符

    1. &&:
    2. ||:

10.三元运算符

又叫:三元表达式或者问号冒号表达式。

  • 格式:

    关系表达式 ? 表达式1 :表达式2 ;

  • 计算规则:

    1. 计算关系表达式的值。
    2. 值为真,执行表达式1。
    3. 值为假,执行表达式2。
  • 注意点:

    三元运算符的最终结果一定要被使用,要么赋值给一个变量,要么直接打印出来。

11.其他

  • 运算符的优先级

    小括号优先于所有。

  • 原码反码补码

    1. 原码:十进制数据的二进制表现形式,最左为符号位,0为正,1为负。
    2. 反码:解决原码不能计算负数的问题。正数反码不变,负数的反码在原码的基础上符号位不变,数值取反。
    3. 补码:正数不变,负数反码基础上+1.(-128:1000 0000)

    注:计算机中的存储和计算都是以补码的形式进行的。

4.流程控制语句

1.流程控制语句分类

  • 顺序结构:按照代码的先后顺序,依次执行。
  • 判断和选择结构(if, switch)
  • 循环结构(for, while, do…while)

2.判断和选择结构:

1.if语句

  • if语句:

    1
    2
    3
    4
    格式:
    if (关系表达式) {
    语句体;
    } //如果大括号省略,if只能控制距离他最近的那一条语句。
  • if-else:

    1
    2
    3
    4
    5
    6
    格式:
    if (关系表达式) {
    语句体1;
    } else {
    语句体2;
    }
  • if - else if - else:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    格式:
    if (关系表达式1) {
    语句体1;
    } else if (关系表达式2) {
    语句体2;
    }

    else {
    语句体n+1;
    }

2.switch语句

  • 格式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    switch (表达式) {
    case 1:
    语句体1;
    break;
    case 2:
    语句体2;
    break;
    ...
    default:
    语句体n+1;
    break;
    }
  • 执行流程:

    1. 计算出表达式的值
    2. 与case依次比较,有对应的值,执行相应的语句,在执行的过程中,遇到break结束。
    3. 如果所有的case都和表达式的值不匹配,就会执行default语句体部分,然后程序结束掉。
  • 扩展知识:

    1. default的位置和省略情况

      default可以放在任意位置,也可以省略

    2. case穿透

      不写break会引发case穿透现象

    3. switch在JDK12的新特性

      1
      2
      3
      4
      5
      6
      7
      int number = 10;
      switch (number) {
      case 1 -> System.out.println("一");
      case 2 -> System.out.println("二");
      case 3 -> System.out.println("三");
      default -> System.out.println("其他");
      }

3.循环结构

  • for循环格式:

    1
    2
    3
    for (初始化语句;条件判断语句;条件控制语句) {
    循环体语句;
    }
  • while循环格式:

    1
    2
    3
    4
    5
    初始化语句;
    while(条件判断语句){
    循环体;
    条件控制语句;
    }
  • do…while循环

    1
    2
    3
    4
    5
    初始化语句;
    do{
    循环体;
    条件控制语句;
    }while(条件判断语句);
  • 死循环

    1
    2
    3
    for(;;){
    System.out.println("循环执行一直在打印内容");
    }

4.条件控制语句

  • break:可以用在switch和循环中,表示结束
  • continue:不能单独存在的。只能存在于循环当中。表示:跳过本次循环,继续执行下次循环。

5.Random

  • 导包

    1
    2
    import java.util.Random;
    导包的动作必须出现在类定义的上边。
  • 创建对象

    1
    2
    Random r = new Random ();
    上面这个格式里面,只有r是变量名,可以变,其他的都不允许变。
  • 生成随机数

    1
    2
    3
    int number = r.nextInt(随机数的范围);
    上面这个格式里面,只有number是变量名,可以变,其他的都不允许变。
    随机数范围的特点:从0开始,不包含指定值。比如:参数为10,生成的范围[0,10)

5.数组

指的是一种容器,可以同来存储同种数据类型的多个值。

1.数组的定义

  • 格式一:
    1
    2
    数据类型 [] 数组名
    例如: int [] array
  • 格式二:
    1
    2
    数据类型  数组名 []
    例如: int array []

2.数组的静态初始化

  • 完整格式:

    1. 数据类型[] 数组名 = new 数据类型[]{元素1,元素2,元素3,元素4…};

      比如:int[] arr = new int[]{11,22,33};

    2. 注意:

      • 等号前后的数据类型必须保持一致。
      • 数组一旦创建之后,长度不能发生变化。
  • 简化格式:

    1. 数据类型[] 数组名 = {元素1,元素2,元素3,元素4…};

      比如:int[] array = {1,2,3,4,5};

3.地址值

1
2
3
4
5
int[] arr = {1,2,3,4,5};
System.out.println(arr);//[I@6d03e736

double[] arr2 = {1.1,2.2,3.3};
System.out.println(arr2);//[D@568db2f2

数组的地址值:就表示数组在内存中的位置。

1
2
3
4
5
6
7
8
9
10
11
12
以[I@6d03e736为例:

[ :表示现在打印的是一个数组。

I:表示现在打印的数组是int类型的。

@:仅仅是一个间隔符号而已。

6d03e736:就是数组在内存中真正的地址值。(十六进制的)

但是,习惯性会把[I@6d03e736整体称为数组的地址值。

4.数组元素访问

  • 格式:

    数组名[索引];

  • 索引:也叫角标、下标

    1. 索引一定是从0开始的。
    2. 连续不间断。
    3. 逐个+1增长。
  • 数组的遍历

    1. 遍历:就是把数组里面所有的内容一个一个全部取出来。
    2. 数组的长度:数组名.length;
    1
    2
    3
    4
    for(int i = 0; i < arr.length; i++){
    //在循环的过程中,i依次表示数组中的每一个索引
    sout(arr[i]);//就可以把数组里面的每一个元素都获取出来,并打印在控制台上了。
    }

5.数组的动态初始化

  • 格式:

    数据类型[] 数组名 = new 数据类型[数组的长度];

    1
    2
    3
    4
    5
    6
    //1.定义一个数组,存3个人的年龄,年龄未知
    int[] agesArr = new int[3];


    //2.定义一个数组,存班级10名学生的考试成绩,考试成绩暂时未知,考完才知道。
    int[] scoresArr = new int[10];
  • 数组的默认初始化值:

    1. 整数类型:0
    2. 小数类型:0.0
    3. 布尔类型:false
    4. 字符类型:’\u0000’
    5. 引用类型:null

6.数组的内存图

  • Java内存分配:

    1. 栈:方法运行使用的内存。
    2. 堆:存储对象或数组,new来创建的。
    3. 方法区(jdk8之后改为:元空间):存储可以运行的class文件。
    4. 本地方法栈:JVM在操作系统功能的时候使用。
    5. 寄存器:给CPU使用。
  • 两个数组指向同一个空间的内存图

    1
    2
    int[] arr1 = {1,2,3};
    int[] arr2 = arr1;

7.二维数组

  • 二维数组的初始化

    1. 静态初始化

      1
      2
      3
      4
      数据类型[][] 数组名 = new 数据类型[][] {{元素1,元素2},{元素1,元素2}};

      简化:
      数据类型[][] 数组名 = {{元素1,元素2},{元素1,元素2}};
    2. 动态初始化

      1
      数据类型[][] 数组名 = new 数据类型[m][n]
  • 二维数组的内存图

6.方法

方法(method)是程序中最小的执行单元

注意:

  • 方法必须先创建才可以使用,该过程称为方法定义
  • 方法创建后并不是直接可以运行的,需要手动使用后,才执行,该过程成为方法调用

1.方法的定义和调用

  • 方法定义和调用

    1. 定义格式

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      public static 返回值类型 方法名(参数) {
      方法体;
      return 数据 ;
      }

      /*
      public static: 修饰符,目前先记住这个格式
      返回值类型: 方法操作完毕之后返回的数据的数据类型(如果没有数据返回,写void,且方法体中不写return)
      方法名: 调用方法时候使用的标识
      参数: 由数据类型和变量名组成,多个参数之间用逗号隔开
      方法体: 完成功能的代码块
      return: 如果方法操作完毕,有数据返回,用于把数据返回给调用者
      */
    2. 调用格式

      1
      2
      方法名 ( 参数 ) ;
      数据类型 变量名 = 方法名 ( 参数 ) ;
    3. 注意:

      • 方法的返回值通常会使用变量接收,否则该返回值将无意义
      • 方法定义时return后面的返回值与方法定义上的数据类型要匹配,否则程序将报错
  • 形参和实参

    1. 形参:方法定义中的参数

    2. 实参:方法调用中的参数

  • 方法的注意事项

    1. 方法不能嵌套定义
    2. void表示无返回值,可以省略return,也可以单独的书写return,后面不加数据

2.方法重载

  • 方法重载概念

    • 多个方法在同一个类中
    • 多个方法具有相同的方法名
    • 多个方法的参数不相同,类型不同或者数量不同
  • 注意:

    • 重载仅对应方法的定义,与方法的调用无关,调用方式参照标准格式
    • 重载仅针对同一个类中方法的名称与参数进行识别,与返回值无关。

7.字符串

1.API

  • API概述
    1. 什么是API (Application Programming Interface) :应用程序编程接口

    2. java中的API:指的就是 JDK 中提供的各种功能的 Java类,这些类将底层的实现封装了起来,无需关心类如何实现,只需学习这些类如何使用即可。

2.String

  • String类概述

    String 类代表字符串,Java 程序中的所有字符串文字(例如“abc”)都被实现为此类的实例。即:Java 程序中所有的双引号字符串,都是 String 类的对象。String 类在 java.lang 包下,所以使用的时候不需要导包!

  • String类的特点

    1. 字符串不可变,其值在创建后不能被更改
    2. 虽然 String 的值是不可变的,但是它们可以被共享
    3. 字符串效果上相当于字符数组( char[] ),但是底层原理是字节数组( byte[] )
  • String类的构造方法

    1. 常用的构造方法

      方法名 说明
      public String() 创建一个空白字符串对象,不含有任何内容
      public String(char[] chs) 根据字符数组的内容,来创建字符串对象
      public String(byte[] bys) 根据字节数组的内容,来创建字符串对象
      String s = “abc”; 直接赋值的方式创建字符串对象,内容就是abc
    2. 示例代码

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      public class StringDemo01 {
      public static void main(String[] args) {
      //public String():创建一个空白字符串对象,不含有任何内容
      String s1 = new String();
      System.out.println("s1:" + s1);

      //public String(char[] chs):根据字符数组的内容,来创建字符串对象
      char[] chs = {'a', 'b', 'c'};
      String s2 = new String(chs);
      System.out.println("s2:" + s2);

      //public String(byte[] bys):根据字节数组的内容,来创建字符串对象
      byte[] bys = {97, 98, 99};
      String s3 = new String(bys);
      System.out.println("s3:" + s3);

      //String s = “abc”; 直接赋值的方式创建字符串对象,内容就是abc
      String s4 = "abc";
      System.out.println("s4:" + s4);
      }
      }
  • 创建字符串对象两种方式的区别

    1. 通过构造方法创建:

      通过 new 创建的字符串对象,每一次 new 都会申请一个内存空间,虽然内容相同,但是地址值不同

    2. 直接赋值方式创建

      ""方式给出的字符串,只要字符序列相同(顺序和大小写),无论在程序代码中出现几次,JVM 都只会建立一个 String 对象,并在字符串池中维护

  • 字符串的比较

    1. == 的作用

      • 比较基本数据类型:比较的是具体的值
      • 比较引用数据类型:比较的是对象地址值
    2. equals方法的作用

      1
      2
      public boolean equals(String s)     // 比较两个字符串内容是否相同、区分大小写
      public boolean equalslgnoreCase(String s) // 比较两个字符串内容是否相同,忽略大小写的比较
  • 遍历字符串:charAt(i)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    for (int i = 0; i < str.length(); i++) {
    //i 依次表示字符串的每一个索引
    //索引的范围:0 ~ 长度-1

    //根据索引获取字符串里面的每一个字符
    //ctrl + alt + V 自动生成左边的接受变量
    char c = str.charAt(i);
    System.out.println(c);
    }

  • 字符串截取

    1
    2
    substring(0, 3); //左闭右开
    substring(3); //从3到末尾
  • 字符串替换

    1
    replace("TMD", "***"); //替换

3.StringBuilder

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

  • 基本使用

    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
    public class StringBuilderDemo3 {
    public static void main(String[] args) {
    //1.创建对象
    StringBuilder sb = new StringBuilder("abc");

    //2.添加元素
    /*sb.append(1);
    sb.append(2.3);
    sb.append(true);*/

    //反转
    sb.reverse();

    //获取长度
    int len = sb.length();
    System.out.println(len);


    //打印
    //因为StringBuilder是Java已经写好的类
    //java在底层对他做了一些特殊处理。
    //打印对象不是地址值而是属性值。
    System.out.println(sb);

    // toString()
    String str = sb.toString();
    System.out.println(str);
    }
    }
  • 链式编程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class StringBuilderDemo4 {
    public static void main(String[] args) {
    //1.创建对象
    StringBuilder sb = new StringBuilder();

    //2.添加字符串
    sb.append("aaa").append("bbb").append("ccc").append("ddd");

    System.out.println(sb);//aaabbbcccddd

    //3.再把StringBuilder变回字符串
    String str = sb.toString();
    System.out.println(str);//aaabbbcccddd

    }
    }
  • 反转字符串

    1
    2
    //2.反转键盘录入的字符串
    String result = new StringBuilder().append(str).reverse().toString();

4.StringJoiner

  • StringJoiner跟StringBuilder一样,也可以看成是一个容器,创建之后里面的内容是可变的。
  • 作用:提高字符串的操作效率,而且代码编写特别简洁,但是目前市场上很少有人用。
  • JDK8出现的

基本使用:

1
2
3
4
5
6
//1.创建一个对象,并指定中间的间隔符号
StringJoiner sj = new StringJoiner("---");
//2.添加元素
sj.add("aaa").add("bbb").add("ccc");
//3.打印结果
System.out.println(sj);//aaa---bbb---ccc
1
2
3
4
5
6
7
8
9
10
//1.创建对象
StringJoiner sj = new StringJoiner(", ","[","]");
//2.添加元素
sj.add("aaa").add("bbb").add("ccc");
int len = sj.length();
System.out.println(len);//15
//3.打印
System.out.println(sj);//[aaa, bbb, ccc]
String str = sj.toString();
System.out.println(str);//[aaa, bbb, ccc]

5.关于字符串的小扩展:

  1. 字符串存储的内存原理

    1
    2
    3
    4
    5
    String s = “abc”;直接赋值

    特点:
    此时字符串abc是存在字符串常量池中的。
    先检查字符串常量池中有没有字符串abc,如果有,不会创建新的,而是直接复用。如果没有abc,才会创建一个新的。
  2. new出来的字符串

    1
    2
    3
    4
    5
    6
    7
    8
    9
    看到new关键字,一定是在堆里面开辟了一个小空间。

    String s1 = new String(“abc”);

    String s2 = “abc”;

    s1记录的是new出来的,在堆里面的地址值。

    s2是直接赋值的,所以记录的是字符串常量池中的地址值。
  3. ==号比较的到底是什么?

    1
    2
    3
    4
    5
    如果比较的是基本数据类型:比的是具体的数值是否相等。

    如果比较的是引用数据类型:比的是地址值是否相等。

    结论:==只能用于比较基本数据类型。不能比较引用数据类型。

8.面向对象(封装)

1.类和对象

  • 类和对象的理解

    客观存在的事物皆为对象 ,所以我们也常常说万物皆对象。

      • 类的理解
        • 类是对现实生活中一类具有共同属性和行为的事物的抽象
        • 类是对象的数据类型,类是具有相同属性和行为的一组对象的集合
        • 简单理解:类就是对现实事物的一种描述
      • 类的组成
        • 属性:指事物的特征,例如:手机事物(品牌,价格,尺寸)
        • 行为:指事物能执行的操作,例如:手机事物(打电话,发短信)
    1. 类和对象的关系
      • 类:类是对现实生活中一类具有共同属性和行为的事物的抽象
      • 对象:是能够看得到摸的着的真实存在的实体
      • 简单理解:类是对事物的一种描述,对象则为具体存在的事物
  • 类的定义

    类的组成是由属性和行为两部分组成

    • 属性:在类中通过成员变量来体现(类中方法外的变量)
    • 行为:在类中通过成员方法来体现(和前面的方法相比去掉static关键字即可)

    注:一个java文件中可以定义多个class类,且只能一个类是public修饰,修饰的类名必须成为代码的文件名。(建议一个java文件写一个class类)

    1. 类的定义步骤:

      • 定义类
      • 编写类的成员变量
      • 编写类的成员方法
      1
      2
      3
      4
      5
      6
      7
      8
      9
      public class 类名 {
      // 成员变量
      变量1的数据类型 变量1
      变量2的数据类型 变量2;

      // 成员方法
      方法1;
      方法2;
      }
    2. 对象的使用

      • 创建对象的格式:

        1
        类名 对象名 = new 类名();
      • 调用成员的格式:

        1
        2
        对象名.成员变量
        对象名.成员方法();
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      public class PhoneDemo {
      public static void main(String[] args) {
      //创建对象
      Phone p = new Phone();

      //使用成员变量
      System.out.println(p.brand);
      System.out.println(p.price);

      p.brand = "小米";
      p.price = 2999;

      System.out.println(p.brand);
      System.out.println(p.price);

      //使用成员方法
      p.call();
      p.sendMessage();
      }
      }
  • 标准JavaBean类

    javabean类:用来描述一类事物的类。(不写main方法)

    1. 类名见名知意

    2. 成员变量使用private

    3. 提供至少2个构造方法

      • 无参构造
      • 带全部参数的构造
    4. 成员方法

      • 提供每一个成员变量对应的setxxx(),getxxx()方法。

        快捷键生成:alt + instet(或alt + fn + inster)

      • 其他的行为

  • 测试类:编写main方法的类。

  • 成员变量和局部变量的区别

    1. 类中位置不同:成员变量(类中方法外)局部变量(方法内部或方法声明上)
    2. 内存中位置不同:成员变量(堆内存)局部变量(栈内存)
    3. 生命周期不同:成员变量(随着对象的存在而存在,随着对象的消失而消失)局部变量(随着方法的调用而存在,随着方法的调用完毕而消失)
    4. 初始化值不同:成员变量(有默认初始化值)局部变量(没有默认初始化值,必须先定义,赋值才能使用)

2.封装

  • 封装思想

    1. 封装概述

      对象代表什么,就得封装对应的数据,并提供数据对应的行为

    2. 封装代码实现
      将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问
      成员变量private,提供对应的getXxx()/setXxx()方法

  • private关键字

    private是一个修饰符,可以用来修饰成员(成员变量,成员方法)

    1. 被private修饰的成员,只能在本类进行访问,针对private修饰的成员变量,如果需要被其他类使用,提供相应的操作
      • 提供“get变量名()”方法,用于获取成员变量的值,方法用public修饰
      • 提供“set变量名(参数)”方法,用于设置成员变量的值,方法用public修饰
  • 就近原则和this关键字

    this修饰的变量用于指代成员变量,其主要作用是(区分局部变量和成员变量的重名问题)

    1. 方法的形参如果与成员变量同名,不带this修饰的变量指的是形参,而不是成员变量
    2. 方法的形参没有与成员变量同名,不带this修饰的变量指的是成员变量

3.构造方法

  • 构造方法概述

    构造方法(构造器,构造函数)是一种特殊的方法

    作用:创建对象时给成员变量赋值。

    1. 作用:创建对象 Student stu = new Student();

    2. 格式:

      1
      2
      3
      4
      5
      6
      7
      public class 类名{

      修饰符 类名( 参数 ) {

      }

      }
    3. 功能:主要是完成对象数据的初始化

    4. 特点:

      • 方法名与类名相同
      • 没有返回类型
      • 没有具体的返回值
    5. 执行时机:

      • 创建对象的时候由虚拟机调用(不能手动调用)
      • 每创建一次对象,就会调用一次构造方法
  • 构造方法的注意事项

    1. 构造方法的创建

      如果没有定义构造方法,系统将给出一个默认的无参数构造方法

      如果定义了构造方法,系统将不再提供默认的构造方法

    2. 构造方法的重载

      如果自定义了带参构造方法,还要使用无参数构造方法,就必须再写一个无参数构造方法

    3. 推荐的使用方式

      无论是否使用,都需要写无参数构造方法

    4. 重要功能!

      可以使用带参构造,为成员变量进行初始化

  • 标准类制作

    1. 类名需要见名知意

    2. 成员变量使用private修饰

    3. 提供至少两个构造方法

      • 无参构造方法
      • 带全部参数的构造方法
    4. get和set方法

      提供每一个成员变量对应的setXxx()/getXxx()

    5. 如果还有其他行为,也需要写上

4.基本数据类型与引用数据类型

  • 基本数据类型:数据值存储在自己的空间中。

    特点:赋值给其他变量,也是赋的真实值。

  • 引用数据类型:数据值存储在其他空间中,本身存储的是地址值。

    特点:赋值给其他变量,赋的地址值。

9.面向对象(继承)

1.static关键字

  • 概述

    static 关键字可以用来修饰的成员变量和成员方法,被static修饰的成员属于类放在静态区中,没有static修饰的成员变量和方法则属于对象的。

  • 定义格式和使用

    static是静态的意思。 static可以修饰成员变量或者修饰方法。

    1. 静态变量及其访问

      static修饰成员变量,这个成员变量属于类,称为类变量或者静态成员变量,直接用类名访问即可。类只有一个,所以静态成员变量在内存区域中也只存在一份。所有的对象都可以共享这个变量

    2. 定义格式

      1
      修饰符 static 数据类型 变量名 = 初始值;    
    3. 静态成员变量的访问:

      1
      2
      3
      4
      5
      6
      // 格式:类名.静态变量
      public static void main(String[] args){
      System.out.println(Student.schoolName); // 传智播客
      Student.schoolName = "黑马程序员";
      System.out.println(Student.schoolName); // 黑马程序员
      }
  • 静态方法及其访问

    有static修饰成员方法,说明这个成员方法是属于类的,这个成员方法称为类方法或者静态方法,直接用类名访问即可。因为类只有一个,所以静态方法在内存区域中也只存在一份。所有的对象都可以共享这个方法。

    1. 访问:静态方法通过类名.方法名称即可访问。

      1
      2
      3
      4
      5
      6
      7
      public class Student{
      public static String schoolName = "传智播客"// 属于类,只有一份。
      // .....
      public static void study(){
      System.out.println("我们都在黑马程序员学习");
      }
      }
    2. 格式:类名.静态方法

      1
      2
      3
      public static void  main(String[] args){
      Student.study();
      }
  • 工具类

    私有化构造方法

    目的:为了不让外界创建他的对象

    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
    public class ArrayUtil {

    //私有化构造方法
    //目的:为了不让外界创建他的对象
    private ArrayUtil() {
    }


    //需要定义为静态的,方便调用
    public static String printArr(int[] arr) {
    StringBuilder sb = new StringBuilder();
    sb.append("[");
    for (int i = 0; i < arr.length; i++) {
    //i 索引 arr[i] 元素
    if (i == arr.length - 1) {
    sb.append(arr[i]);
    } else {
    sb.append(arr[i]).append(", ");
    }
    }
    sb.append("]");
    return sb.toString();
    }


    public static double getAverage(double[] arr) {
    double sum = 0;
    for (int i = 0; i < arr.length; i++) {
    sum = sum + arr[i];
    }
    return sum / arr.length;
    }

    }






    public class TestDemo {
    public static void main(String[] args) {
    //测试工具类中的两个方法是否正确

    int[] arr1 = {1, 2, 3, 4, 5};
    String str = ArrayUtil.printArr(arr1);
    System.out.println(str);


    double[] arr2 = {1.5, 3.7, 4.9, 5.8, 6.6};
    double avg = ArrayUtil.getAverage(arr2);
    System.out.println(avg);
    }
    }


  • 小结

    1. static 修饰成员变量或者成员方法时,该变量称为静态变量静态方法。该类的每个对象都共享同一个类的静态变量和静态方法。更改该静态变量的值或者访问静态方法,通过类名访问即可。
    2. 无static修饰的成员变量或成员方法,称为实例变量,实例方法,实例变量和实例方法必须创建类的对象,然后通过对象来访问。
    3. static修饰的成员属于类,会存储在静态区,是随着类的加载而加载的,且只加载一次,节省内存。存储于一块固定的内存区域(静态区),所以,可以直接被类名调用。它优先于对象存在,所以,可以被所有对象共享。
    4. 无static修饰的成员,是属于对象,对象有多少个,就会出现多少份。所以必须由对象调用。
    1
    2
    3
    4
    5
    静态方法只能访问静态变量和静态方法

    非静态方法可以访问静态变量或静态方法,也可以访问非静态的成员变量和非静态的成员方法

    静态方法中是没有this关键字

2.继承

  • 继承的含义

    1. 继承描述的是事物之间的所属关系,通过继承,可以使多种事物之间形成一种关系体系。
    2. 继承:就是子类继承父类的属性行为,使得子类对象可以直接具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。
    3. 多个继承的类称为子类派生类,单独被继承的类称为父类超类(superclass)或者基类
    4. java中所有的类直接或间接继承于Object类。
  • 继承的好处

    1. 提高代码的复用性(减少代码冗余,相同代码重复利用)。
    2. 使类与类之间产生了关系。
  • 继承的格式

    通过 extends 关键字,可以声明一个子类继承另外一个父类,定义格式如下:

    1
    2
    3
    4
    5
    6
    7
    class 父类 {
    ...
    }

    class 子类 extends 父类 {
    ...
    }

    需要注意:Java是单继承的,一个类只能继承一个直接父类,跟现实世界很像,但是Java中的子类是更加强大的。

  • 子类不能继承的内容

    1. 子类不能继承父类的构造方法。
    2. 子类可以继承父类的私有成员(成员变量,方法),只是子类无法直接访问而已,可以通过getter/setter方法访问父类的private成员变量。
  • 继承后的特点—成员变量

    1. 成员变量不重名

      如果子类父类中出现不重名的成员变量,这时的访问是没有影响的

    2. 成员变量重名

      • 如果子类父类中出现重名的成员变量,子类优先访问自己对象中的成员变量。如果访问父类成员变量可使用super关键字。
    3. super访问父类成员变量

    • 子父类中出现了同名的成员变量时,在子类中需要访问中非私有成员变量时,需要使用super 关键字,修饰父类成员变量,类似于 this

    • 需要注意的是:super代表的是父类对象的引用,this代表的是当前对象的引用。

    • 使用格式:

      1
      super.父类成员变量名
  • 继承后的特点—成员方法

    1. 成员方法不重名:若子类父类中出现不重名的成员方法,此时调用是没有影响的。对象调用方法时,会先在子类中查找有没有对应的方法,若子类中存在就会执行子类中的方法,若子类中不存在就会执行父类中相应的方法。
    2. 成员方法重名:如果子类父类中出现重名的成员方法,则创建子类对象调用该方法的时候,子类对象会优先调用自己的方法。
  • 继承后的特点—构造方法

    1. 构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。
    2. 构造方法的作用是初始化对象成员变量数据的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个super() ,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。(先有爸爸,才能有儿子
    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
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    class Person {
    private String name;
    private int age;

    public Person() {
    System.out.println("父类无参");
    }

    // getter/setter省略
    }

    class Student extends Person {
    private double score;

    public Student() {
    //super(); // 调用父类无参,默认就存在,可以不写,必须再第一行
    System.out.println("子类无参");
    }

    public Student(double score) {
    //super(); // 调用父类无参,默认就存在,可以不写,必须再第一行
    this.score = score;
    System.out.println("子类有参");
    }

    }

    public class Demo07 {
    public static void main(String[] args) {
    Student s1 = new Student();
    System.out.println("----------");
    Student s2 = new Student(99.9);
    }
    }

    输出结果:
    父类无参
    子类无参
    ----------
    父类无参
    子类有参
  • 继承的特点

    1. Java只支持单继承,不支持多继承。
    2. 一个类可以有多个子类。
    3. 可以多层继承。

    顶层父类是Object类。所有的类默认继承Object,作为父类。

3.方法重写

  • 概念

    方法重写 :子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。声明不变,重新实现

  • @Override重写注解

    1. @Override:注解,重写注解校验!

    2. 这个注解标记的方法,就说明这个方法必须是重写父类的方法,否则编译阶段报错。

    3. 建议重写都加上这个注解,一方面可以提高代码的可读性,一方面可以防止重写出错!

      加上后的子类代码形式如下:

      1
      2
      3
      4
      5
      6
      7
      8
      public class Cat extends Animal {
      // 声明不变,重新实现
      // 方法名称与父类全部一样,只是方法体中的功能重写写了!
      @Override
      public void cry(){
      System.out.println("我们一起学猫叫,喵喵喵!喵的非常好听!");
      }
      }
  • 注意事项

    1. 方法重写是发生在子父类之间的关系。
    2. 子类方法覆盖父类方法,必须要保证权限大于等于父类权限。
    3. 子类方法覆盖父类方法,返回值类型、函数名和参数列表都要一模一样。

4.super(...)this(...)

  • super和this的用法格式

    1
    2
    3
    4
    5
    this.成员变量    	--    本类的
    super.成员变量 -- 父类的

    this.成员方法名() -- 本类的
    super.成员方法名() -- 父类的

    接下来我们使用调用构造方法格式:

    1
    2
    super(...) -- 调用父类的构造方法,根据参数匹配确认
    this(...) -- 调用本类的其他构造方法,根据参数匹配确认
  • super(…)用法注意:

    子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。

    super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。

    super(..)是根据参数去确定调用父类哪个构造方法的。

  • this(…)用法

    1. 默认是去找本类中的其他构造方法,根据参数来确定具体调用哪一个构造方法。
    2. 为了借用其他构造方法的功能。
  • 小结

    1. 子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。

    2. super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。

    3. super(..)和this(…)是根据参数去确定调用父类哪个构造方法的。

    4. super(..)可以调用父类构造方法初始化继承自父类的成员变量的数据。

    5. this(..)可以调用本类中的其他构造方法。

10.面向对象(多态)

1.多态

同类型的对象,表现出的不同形态。

  • 多态的形式

    1. 多态是出现在继承或实现关系中的

    2. 多态体现的格式

      1
      2
      父类类型 变量名 = new 子类/实现类构造器;
      变量名.方法名();
    3. 多态的前提:有继承关系,子类对象是可以赋值给父类类型的变量。例如Animal是一个动物类型,而Cat是一个猫类型。Cat继承了Animal,Cat对象也是Animal类型,自然可以赋值给父类类型的变量。

  • 多态的使用场景

    1. 若没有多态,在下图中register方法只能传递学生对象,其他的Teacher和administrator对象是无法传递给register方法方法的,在这种情况下,只能定义三个不同的register方法分别接收学生,老师和管理员。(有了多态之后,方法的形参就可以定义为共同的父类Person。)

      001

    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
      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
      父类:
      public class Person {
      private String name;
      private int age;

      //空参构造
      //带全部参数的构造
      //get和set方法

      public void show(){
      System.out.println(name + ", " + age);
      }
      }

      子类1
      public class Administrator extends Person {
      @Override
      public void show() {
      System.out.println("管理员的信息为:" + getName() + ", " + getAge());
      }
      }

      子类2
      public class Student extends Person{

      @Override
      public void show() {
      System.out.println("学生的信息为:" + getName() + ", " + getAge());
      }
      }

      子类3
      public class Teacher extends Person{

      @Override
      public void show() {
      System.out.println("老师的信息为:" + getName() + ", " + getAge());
      }
      }

      测试类:
      public class Test {
      public static void main(String[] args) {
      //创建三个对象,并调用register方法

      Student s = new Student();
      s.setName("张三");
      s.setAge(18);


      Teacher t = new Teacher();
      t.setName("王建国");
      t.setAge(30);

      Administrator admin = new Administrator();
      admin.setName("管理员");
      admin.setAge(35);



      register(s);
      register(t);
      register(admin);


      }



      //这个方法既能接收老师,又能接收学生,还能接收管理员
      //只能把参数写成这三个类型的父类
      public static void register(Person p){
      p.show();
      }
      }
  • 多态的定义和前提

    1. 多态: 是指同一行为,具有多个不同表现形式。
    2. 前提【重点】
    • 有继承或者实现关系
    • 方法的重写【意义体现:不重写,无意义】
    • 父类引用指向子类对象【格式体现】

    父类类型:指子类对象继承的父类类型,或者实现的父接口类型。

  • 多态的运行特点

    1. 调用成员变量时:编译看左边,运行看左边
    2. 调用成员方法时:编译看左边,运行看右边
    1
    2
    3
    4
    5
    6
    7
    Fu f = new Zi();
    //编译时,看左边的父类是否有name属性,若无则报错
    //在实际运行时,把父类name属性的值打印出来
    System.out.println(f.name);
    //编译时,看左边的父类是否有show方法,若无则报错
    //在实际运行时,运行的是子类中的show方法
    f.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
    class Animal{
    public void eat()
    System.out.println("动物吃东西!")

    }
    class Cat extends Animal {
    public void eat() {
    System.out.println("吃鱼");
    }

    public void catchMouse() {
    System.out.println("抓老鼠");
    }
    }

    class Dog extends Animal {
    public void eat() {
    System.out.println("吃骨头");
    }
    }

    class Test{
    public static void main(String[] args){
    Animal a = new Cat();
    a.eat();
    a.catchMouse();//编译报错,编译看左边,Animal没有这个方法
    }
    }
  • 引用类型转换

    1. 为什么要转型:多态的写法就无法访问子类独有功能了。多态的转型分为向上转型(自动转换)与向下转型(强制转换)两种。

    2. 向上转型(自动转换):向上转型:多态本身是子类类型向父类类型向上转换(自动转换)的过程,这个过程是默认的。

      1
      2
      父类类型  变量名 = new 子类类型();
      如:Animal a = new Cat();

      原因是:父类类型相对与子类来说是大范围的类型,Animal是动物类,是父类类型。Cat是猫类,是子类类型。Animal类型的范围当然很大,包含一切动物。所以子类范围小可以直接自动转型给父类类型的变量。

    3. 向下转型(强制转换)

      向下转型:父类类型向子类类型向下转换的过程,这个过程是强制的。

      1
      2
      3
      子类类型 变量名 = (子类类型) 父类变量名;
      如:Aniaml a = new Cat();
      Cat c =(Cat) a;
    4. 转型的异常

      这段代码可通过编译,但是运行时,报错 ClassCastException ,类型转换异常!创建了Cat类型对象,不能转换成Dog对象。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      public class Test {
      public static void main(String[] args) {
      // 向上转型
      Animal a = new Cat();
      a.eat(); // 调用的是 Cat 的 eat

      // 向下转型
      Dog d = (Dog)a;
      d.watchHouse(); // 调用的是 Dog 的 watchHouse 【运行报错】
      }
      }
  • instanceof关键字

    为了避免ClassCastExceptio的发生,Java提供了 instanceof 关键字,给引用变量做类型的校验,格式如下:

    1
    2
    3
    变量名 instanceof 数据类型 
    如果变量属于该数据类型或者其子类类型,返回true
    如果变量不属于该数据类型或者其子类类型,返回false
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class Test {
    public static void main(String[] args) {
    // 向上转型
    Animal a = new Cat();
    a.eat(); // 调用的是 Cat 的 eat

    // 向下转型
    if (a instanceof Cat){
    Cat c = (Cat)a;
    c.catchMouse(); // 调用的是 Cat 的 catchMouse
    } else if (a instanceof Dog){
    Dog d = (Dog)a;
    d.watchHouse(); // 调用的是 Dog 的 watchHouse
    }
    }
    }
  • instanceof新特性

    JDK14的时候提出了新特性,把判断和强转合并成了一行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //新特性
    //先判断a是否为Dog类型,如果是,则强转成Dog类型,转换之后变量名为d
    //如果不是,则不强转,结果直接是false
    if(a instanceof Dog d){
    d.lookHome();
    }else if(a instanceof Cat c){
    c.catchMouse();
    }else{
    System.out.println("没有这个类型,无法转换");
    }

2.包

  • 包在操作系统中其实就是一个文件夹。包是用来分门别类的管理技术,不同的技术类放在不同的包下,方便管理和维护。

    包名的命名规范

    1
    2
    路径名.路径名.xxx.xxx
    // 例如:com.itheima.oa
    • 包名一般是公司域名的倒写。
    • 包名必须用”.“连接。
    • 包名的每个路径名必须是一个合法的标识符,而且不能是Java的关键字。
  • 导包

    1
    import com.itheima.homework.demo1.Student;
    1. 什么时候需要导包?

      • 情况一:在使用Java中提供的非核心包中的类时
      • 情况二:使用自己写的其他包中的类时
    2. 什么时候不需要导包?

      • 情况一:在使用Java核心包(java.lang)中的类时
      • 情况二:在使用自己写的同一个包中的类时
    3. 使用不同包下的相同类

      假设demo1和demo2中都有一个Student该如何使用?

      1
      2
      3
      4
      5
      //使用全类名的形式即可。
      //全类名:包名 + 类名
      //拷贝全类名的快捷键:选中类名crtl + shift + alt + c 或者用鼠标点copy,再点击copy Reference
      com.itheima.homework.demo1.Student s1 = new com.itheima.homework.demo1.Student();
      com.itheima.homework.demo2.Student s2 = new com.itheima.homework.demo2.Student();

3.final关键字

  • 概述

    final 关键字,表示修饰的内容不可变。

    • final: 不可改变,最终的含义。可以用于修饰类、方法和变量。
      • 类:被修饰的类,不能被继承。
      • 方法:被修饰的方法,不能被重写。
      • 变量:被修饰的变量,有且仅能被赋值一次。
  • 使用方式

    1. 修饰类

      final修饰的类,不能被继承。提供使用,不能改变其内容。

      1
      2
      final class 类名 {
      }
      1
      2
      3
      final class Fu {
      }
      // class Zi extends Fu {} // 报错,不能继承final的类
    2. 修饰方法

      final修饰的方法,不能被重写。

      1
      2
      3
      修饰符 final 返回值类型 方法名(参数列表){
      //方法体
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      class Fu2 {
      final public void show1() {
      System.out.println("Fu2 show1");
      }
      public void show2() {
      System.out.println("Fu2 show2");
      }
      }

      class Zi2 extends Fu2 {
      // @Override
      // public void show1() {
      // System.out.println("Zi2 show1");
      // }
      @Override
      public void show2() {
      System.out.println("Zi2 show2");
      }
      }
    3. 修饰变量-局部变量

      命名规范:全部大写,用 _ 隔开

      局部变量——基本类型:基本类型的局部变量,被final修饰后,只能赋值一次,不能再更改。代码如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      public class FinalDemo1 {
      public static void main(String[] args) {
      // 声明变量,使用final修饰
      final int a;
      // 第一次赋值
      a = 10;
      // 第二次赋值
      a = 20; // 报错,不可重新赋值

      // 声明变量,直接赋值,使用final修饰
      final int b = 10;
      // 第二次赋值
      b = 20; // 报错,不可重新赋值
      }
      }
    4. 修饰变量-成员变量

      被final修饰的常量名称,一般都有书写规范,所有字母都大写

      成员变量涉及到初始化的问题,初始化方式有显示初始化和构造方法初始化,只能选择其中一个:

      • 显示初始化(在定义成员变量的时候立马赋值)(常用);

        1
        2
        3
        public class Student {
        final int num = 10;
        }
      • 构造方法初始化(在构造方法中赋值一次)(不常用,了解即可)。

        注意:每个构造方法中都要赋值一次!

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        public class Student {
        final int num = 10;
        final int num2;

        public Student() {
        this.num2 = 20;
        // this.num2 = 20;
        }

        public Student(String name) {
        this.num2 = 20;
        // this.num2 = 20;
        }
        }

4.权限修饰符

  • 权限修饰符

    可以修饰成员变量,方法,构造方法,内部类

    在Java中提供了四种访问权限,使用不同的访问权限修饰符修饰时,被修饰的内容会有不同的访问权限。

    1. public:公共的,所有地方都可以访问。

    2. protected:本类 ,本包,其他包中的子类都可以访问。

    3. 默认(没有修饰符):本类 ,本包可以访问。

      注意:默认是空着不写,不是default

    4. private:私有的,当前类可以访问。

    public > protected > 默认 > private

  • 不同权限的访问能力

    修饰符 同一个类中 同一个包中其他类 不同包下的子类 不同包下的无关类
    private True
    空着不写 True True
    protected True True True
    public True True True True

    编写代码时,如果没有特殊的考虑,建议这样使用权限:

    • 成员变量使用private ,隐藏细节。
    • 构造方法使用 public ,方便创建对象。
    • 成员方法使用public ,方便调用方法。

    小贴士:不加权限修饰符,就是默认权限

5.代码块

  • 局部代码块
  • 构造代码块
    1. 写在成员位置的代码块
    2. 可以把多个构造方法中重复的代码抽取出来
    3. 再创建本类对象时会先执行改造代码块再执行构造方法
  • 静态代码块
    1. 在构造代码块前加static
    2. 随着类的加载而加载,只执行一次

11.面向对象(抽象类&接口&内部类)

1.抽象类

  • 概述

    1. 抽象类:父类可能知道子类应该有哪个功能,但是功能具体怎么实现父类是不清楚的(由子类自己决定),父类只需要提供一个没有方法体的定义即可,具体实现交给子类去实现。把没有方法体的方法称为抽象方法,包含抽象方法的类就是抽象类
      • 抽象方法 : 没有方法体的方法。
      • 抽象类:包含抽象方法的类。
  • abstract使用格式

    abstract是抽象的意思,用于修饰方法和类,修饰的方法是抽象方法,修饰的类是抽象类。

    1. 抽象方法

      使用abstract 关键字修饰方法,该方法就成了抽象方法,抽象方法只包含一个方法名,而没有方法体。

      定义格式:

      1
      修饰符 abstract 返回值类型 方法名 (参数列表);

      代码举例:

      1
      public abstract void run()
    2. 抽象类

      如果一个类包含抽象方法,那么该类必须是抽象类。注意:抽象类不一定有抽象方法,但是有抽象方法的类必须定义成抽象类。

      定义格式:

      1
      2
      3
      abstract class 类名字 { 

      }

      代码举例:

      1
      2
      3
      public abstract class Animal {
      public abstract void run()
      }
    3. 抽象类的使用

      要求:继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类。

      此时的方法重写,是子类对父类抽象方法的完成实现,这种方法重写的操作,也叫做实现方法

  • 抽象类的特征

    1. 抽象类得到了拥有抽象方法的能力。
    2. 抽象类失去了创建对象的能力。

    其他成员(构造方法,实例方法,静态方法等)抽象类都是具备的。

  • 抽象类的细节

    1. 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。

      理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。

    2. 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。

      理解:子类的构造方法中,有默认的super(),需要访问父类构造方法。

    3. 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。

      理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。

    4. 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则子类也必须定义成抽象类,编译无法通过而报错。

      理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。

    5. 抽象类存在的意义是为了被子类继承。

      理解:抽象类中已经实现的是模板中确定的成员,抽象类不确定如何实现的定义成抽象方法,交给具体的子类去实现。

2.接口

1.接口概述

  • 接口

    1. 概述:接口是更加彻底的抽象,JDK7之前,包括JDK7,接口中全部是抽象方法。接口同样是不能创建对象的

    2. 定义格式

      1
      2
      3
      4
      5
      6
      7
      //接口的定义格式:
      interface 接口名称{
      // 抽象方法
      }

      // 接口的声明:interface
      // 接口名称:首字母大写,满足“大驼峰模式”
  • 接口成分的特点

    接口中包含:抽象方法和常量

    1. 抽象方法

      • 注意:接口中的抽象方法默认会自动加上public abstract修饰无需自己手写!!
      • 按照规范:以后接口中的抽象方法建议不要写上public abstract。因为没有必要,默认会加上。
    2. 常量

      在接口中定义的成员变量默认会加上: public static final修饰。也就是说在接口中定义的成员变量实际上是一个常量。可以直接用接口名访问,常量必须要给初始值。常量命名规范建议字母全部大写,多个单词用下划线连接。

    3. 案例演示

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      public interface InterF {
      // 抽象方法!
      // public abstract void run();
      void run();

      // public abstract String getName();
      String getName();

      // public abstract int add(int a , int b);
      int add(int a , int b);


      // 它的最终写法是:
      // public static final int AGE = 12 ;
      int AGE = 12; //常量
      String SCHOOL_NAME = "黑马程序员";

      }

2.基本的实现

  • 实现接口

    1. 概述:

      类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。实现的动作类似继承,格式相仿,只是关键字不同,实现使用 implements关键字。

    2. 实现接口的格式:

      1
      2
      3
      4
      5
      6
      /**接口的实现:
      在Java中接口是被实现的,实现接口的类称为实现类。
      实现类的格式:*/
      class 类名 implements 接口1,接口2,接口3...{

      }
    3. 类实现接口的要求和意义

      1. 必须重写实现的全部接口中所有抽象方法。
      2. 如果一个类实现了接口,但是没有重写完全部接口的全部抽象方法,这个类也必须定义成抽象类。
      3. 意义:接口体现的是一种规范,接口对实现类是一种强制性的约束,要么全部完成接口申明的功能,要么自己也定义成抽象类。这正是一种强制性的规范。
  • 类与接口基本实现案例

    1. 定义一个运动员的接口(规范),代码如下:

      1
      2
      3
      4
      5
      6
      7
      8
      /**
      接口:接口体现的是规范。
      * */
      public interface SportMan {
      void run(); // 抽象方法,跑步。
      void law(); // 抽象方法,遵守法律。
      String compittion(String project); // 抽象方法,比赛。
      }
    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
      package com.itheima._03接口的实现;
      /**
      * 接口的实现:
      * 在Java中接口是被实现的,实现接口的类称为实现类。
      * 实现类的格式:
      * class 类名 implements 接口1,接口2,接口3...{
      *
      *
      * }
      * */
      public class PingPongMan implements SportMan {
      @Override
      public void run() {
      System.out.println("乒乓球运动员稍微跑一下!!");
      }

      @Override
      public void law() {
      System.out.println("乒乓球运动员守法!");
      }

      @Override
      public String compittion(String project) {
      return "参加"+project+"得金牌!";
      }
      }
    3. 测试代码

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      public class TestMain {
      public static void main(String[] args) {
      // 创建实现类对象。
      PingPongMan zjk = new PingPongMan();
      zjk.run();
      zjk.law();
      System.out.println(zjk.compittion("全球乒乓球比赛"));

      }
      }
  • 类与接口的多实现案例

    类与接口之间的关系是多实现的,一个类可以同时实现多个接口。

    1. 定义两个接口,代码如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      /** 法律规范:接口*/
      public interface Law {
      void rule();
      }

      /** 这一个运动员的规范:接口*/
      public interface SportMan {
      void run();
      }
    2. 定义一个实现类:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      /**
      * Java中接口是可以被多实现的:
      * 一个类可以实现多个接口: Law, SportMan
      *
      * */
      public class JumpMan implements Law,SportMan {
      @Override
      public void rule() {
      System.out.println("尊长守法");
      }

      @Override
      public void run() {
      System.out.println("训练跑步!");
      }
      }

      从上面可以看出类与接口之间是可以多实现的,我们可以理解成实现多个规范,这是合理的。

  • 接口与接口的多继承

    接口与接口之间是可以多继承的:也就是一个接口可以同时继承多个接口。

    注意:

    1. 类与接口是实现关系
    2. 接口与接口是继承关系

    接口继承接口就是把其他接口的抽象方法与本接口进行了合并。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public interface Abc {
    void go();
    void test();
    }

    /** 法律规范:接口*/
    public interface Law {
    void rule();
    void test();
    }

    *
    * 总结:
    * 接口与类之间是多实现的。
    * 接口与接口之间是多继承的。
    * */
    public interface SportMan extends Law , Abc {
    void run();
    }

3.扩展:接口的细节

  1. 当两个接口中存在相同抽象方法的时候,该怎么办?

    只要重写一次即可。此时重写的方法,既表示重写1接口的,也表示重写2接口的。

  2. 实现类能不能继承A类的时候,同时实现其他接口呢?

    继承的父类,就好比是亲爸爸一样
    实现的接口,就好比是干爹一样
    可以继承一个类的同时,再实现多个接口,只不过,要把接口里面所有的抽象方法,全部实现。

  3. 实现类能不能继承一个抽象类的时候,同时实现其他接口呢?

    实现类可以继承一个抽象类的同时,再实现其他多个接口,只不过要把里面所有的抽象方法全部重写。

  4. 实现类Zi,实现了一个接口,还继承了一个Fu类。假设在接口中有一个方法,父类中也有一个相同的方法。子类如何操作呢?

    处理办法一:如果父类中的方法体,能满足当前业务的需求,在子类中可以不用重写。
    处理办法二:如果父类中的方法体,不能满足当前业务的需求,需要在子类中重写。

  5. 如果一个接口中,有10个抽象方法,但是我在实现类中,只需要用其中一个,该怎么办?

    可以在接口跟实现类中间,新建一个中间类(适配器类)
    让这个适配器类去实现接口,对接口里面的所有的方法做空重写。
    让子类继承这个适配器类,想要用到哪个方法,就重写哪个方法。
    因为中间类没有什么实际的意义,所以一般会把中间类定义为抽象的,不让外界创建对象

3.内部类

  • 概述

    什么是内部类:将类A定义在类B里面,类A就称为内部类,B则称为外部类

  • 什么时候使用内部类

    一个事物内部还有一个独立的事物,内部的事物脱离外部的事物无法独立使用

    1. 人里面有一颗心脏。
    2. 汽车内部有一个发动机。
    3. 为了实现更好的封装性。
  • 内部类的分类

    按定义的位置来分

    1. 成员内部内,类定义在了成员位置 (类中方法外称为成员位置,无static修饰的内部类)
    2. 静态内部类,类定义在了成员位置 (类中方法外称为成员位置,有static修饰的内部类)
    3. 局部内部类,类定义在方法内
    4. 匿名内部类,没有名字的内部类,可以在方法中,也可以在类中方法外。

1.成员内部类

  • 成员内部类特点

    1. 无static修饰的内部类,属于外部类对象的。
    2. 宿主:外部类对象。
  • 内部类的使用格式

    1
    外部类.内部类。 // 访问内部类的类型都是用 外部类.内部类
  • 获取成员内部类对象的两种方式

    1. 方式一:外部直接创建成员内部类的对象

      1
      外部类.内部类 变量 = new 外部类().new 内部类();
    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
    34
    35
    36
    37
    38
    39
    40
    41
    方式一:
    public class Test {
    public static void main(String[] args) {
    // 宿主:外部类对象。
    // Outer out = new Outer();
    // 创建内部类对象。
    Outer.Inner oi = new Outer().new Inner();
    oi.method();
    }
    }

    class Outer {
    // 成员内部类,属于外部类对象的。
    // 拓展:成员内部类不能定义静态成员。
    public class Inner{
    // 这里面的东西与类是完全一样的。
    public void method(){
    System.out.println("内部类中的方法被调用了");
    }
    }
    }


    方式二:
    public class Outer {
    String name;
    private class Inner{
    static int a = 10;
    }
    public Inner getInstance(){
    return new Inner();
    }
    }

    public class Test {
    public static void main(String[] args) {
    Outer o = new Outer();
    System.out.println(o.getInstance());

    }
    }
  • 成员内部类的细节

    1. 编写成员内部类的注意点:

      • 成员内部类可以被一些修饰符所修饰,比如: private,默认,protected,public,static等
      • 在成员内部类里面,JDK16之前不能定义静态变量,JDK16开始才可以定义静态变量。
      • 创建内部类对象时,对象中有一个隐含的Outer.this记录外部类对象的地址值。
    2. 详解:

      内部类被private修饰,外界无法直接获取内部类的对象,只能通过方式二获取内部类的对象

      被其他权限修饰符修饰的内部类一般用方式一直接获取内部类的对象

      内部类被static修饰是成员内部类中的特殊情况,叫做静态内部类下面单独学习。

      内部类如果想要访问外部类的成员变量,外部类的变量必须用final修饰,JDK8以前必须手动写final,JDK8之后不需要手动写,JDK默认加上。

2.静态内部类

  • 静态内部类特点

    1. 静态内部类是一种特殊的成员内部类。
    2. 有static修饰,属于外部类本身的。
    3. 总结:静态内部类与其他类的用法完全一样。只是访问的时候需要加上外部类.内部类。
    4. 拓展1:静态内部类可以直接访问外部类的静态成员。
    5. 拓展2:静态内部类不可以直接访问外部类的非静态成员,如果要访问需要创建外部类的对象。
    6. 拓展3:静态内部类中没有银行的Outer.this。
  • 内部类的使用格式

    1
    外部类.内部类。
  • 静态内部类对象的创建格式

    1
    外部类.内部类  变量 = new  外部类.内部类构造器;
  • 调用方法的格式:

    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
    // 外部类:Outer01
    class Outer01{
    private static String sc_name = "黑马程序";
    // 内部类: Inner01
    public static class Inner01{
    // 这里面的东西与类是完全一样的。
    private String name;
    public Inner01(String name) {
    this.name = name;
    }
    public void showName(){
    System.out.println(this.name);
    // 拓展:静态内部类可以直接访问外部类的静态成员。
    System.out.println(sc_name);
    }
    }
    }

    public class InnerClassDemo01 {
    public static void main(String[] args) {
    // 创建静态内部类对象。
    // 外部类.内部类 变量 = new 外部类.内部类构造器;
    Outer01.Inner01 in = new Outer01.Inner01("张三");
    in.showName();
    }
    }

3.局部内部类

  • 局部内部类 :定义在方法中的类。

    定义格式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class 外部类名 {
    数据类型 变量名;

    修饰符 返回值类型 方法名(参数列表) {
    // …
    class 内部类 {
    // 成员变量
    // 成员方法
    }
    }
    }

4.匿名内部类【重点】

  • 概述

    匿名内部类 :是内部类的简化写法。他是一个隐含了名字的内部类。开发中,最常用到的内部类就是匿名内部类了。

  • 格式

    1
    2
    3
    new 类名或者接口名() {
    重写方法;
    };

    包含了:

    1. 继承或者实现关系

    2. 方法重写

    3. 创建对象

  • 什么时候用到匿名内部类

    实际上,如果希望定义一个只要使用一次的类,就可考虑使用匿名内部类。匿名内部类的本质作用

    是为了简化代码

  • 匿名内部类前提和格式

    匿名内部类必须继承一个父类或者实现一个父接口

    匿名内部类格式

    1
    2
    3
    4
    5
    6
    7
    new 父类名或者接口名(){
    // 方法重写
    @Override
    public void method() {
    // 执行语句
    }
    };
  • 使用方式

    以接口为例,匿名内部类的使用,代码如下:

    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
    interface Swim {
    public abstract void swimming();
    }

    public class Demo07 {
    public static void main(String[] args) {
    // 使用匿名内部类
    new Swim() {
    @Override
    public void swimming() {
    System.out.println("自由泳...");
    }
    }.swimming();

    // 接口 变量 = new 实现类(); // 多态,走子类的重写方法
    Swim s2 = new Swim() {
    @Override
    public void swimming() {
    System.out.println("蛙泳...");
    }
    };

    s2.swimming();
    s2.swimming();
    }
    }
  • 匿名内部类的特点

    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
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    interface Swim {
    public abstract void swimming();
    }

    public class Demo07 {
    public static void main(String[] args) {
    // 普通方式传入对象
    // 创建实现类对象
    Student s = new Student();

    goSwimming(s);
    // 匿名内部类使用场景:作为方法参数传递
    Swim s3 = new Swim() {
    @Override
    public void swimming() {
    System.out.println("蝶泳...");
    }
    };
    // 传入匿名内部类
    goSwimming(s3);

    // 完美方案: 一步到位
    goSwimming(new Swim() {
    public void swimming() {
    System.out.println("大学生, 蛙泳...");
    }
    });

    goSwimming(new Swim() {
    public void swimming() {
    System.out.println("小学生, 自由泳...");
    }
    });
    }

    // 定义一个方法,模拟请一些人去游泳
    public static void goSwimming(Swim s) {
    s.swimming();
    }
    }

12.常用API

1.Math

  • 概述

    Math类所在包为java.lang包,因此在使用的时候不需要进行导包。并且Math类被final修饰了,因此该类是不能被继承的。

  • 常见方法

    1
    2
    3
    4
    5
    6
    7
    8
    public static int abs(int a)					// 返回参数的绝对值
    public static double ceil(double a) // 返回向上取整
    public static double floor(double a) // 返回向下取整
    public static int round(float a) // 按照四舍五入返回最接近参数的int类型的值
    public static int max(int a,int b) // 获取两个int值中的较大值
    public static int min(int a,int b) // 获取两个int值中的较小值
    public static double pow (double a,double b) // 计算a的b次幂的值
    public static double random() // 返回一个[0.0,1.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
    public class MathDemo01 {
    public static void main(String[] args) {

    // public static int abs(int a) 返回参数的绝对值
    System.out.println("-2的绝对值为:" + Math.abs(-2));
    System.out.println("2的绝对值为:" + Math.abs(2));

    // public static double ceil(double a) 返回大于或等于参数的最小整数
    System.out.println("大于或等于23.45的最小整数位:" + Math.ceil(23.45));
    System.out.println("大于或等于-23.45的最小整数位:" + Math.ceil(-23.45));

    // public static double floor(double a) 返回小于或等于参数的最大整数
    System.out.println("小于或等于23.45的最大整数位:" + Math.floor(23.45));
    System.out.println("小于或等于-23.45的最大整数位:" + Math.floor(-23.45));

    // public static int round(float a) 按照四舍五入返回最接近参数的int
    System.out.println("23.45四舍五入的结果为:" + Math.round(23.45));
    System.out.println("23.55四舍五入的结果为:" + Math.round(23.55));

    // public static int max(int a,int b) 返回两个int值中的较大值
    System.out.println("23和45的最大值为: " + Math.max(23, 45));

    // public static int min(int a,int b) 返回两个int值中的较小值
    System.out.println("12和34的最小值为: " + Math.min(12 , 34));

    // public static double pow (double a,double b)返回a的b次幂的值
    System.out.println("2的3次幂计算结果为: " + Math.pow(2,3));

    // public static double random()返回值为double的正值,[0.0,1.0)
    System.out.println("获取到的0-1之间的随机数为: " + Math.random());
    }

    }

2.System

  • 概述

System类所在包为java.lang包,因此在使用的时候不需要进行导包。并且System类被final修饰了,因此该类是不能被继承的。

System包含了系统操作的一些常用的方法。比如获取当前时间所对应的毫秒值,再比如终止当前JVM等等。

  • 常见方法

    1
    2
    3
    public static long currentTimeMillis()			// 获取当前时间所对应的毫秒值(当前时间为0时区所对应的时间即就是英国格林尼治天文台旧址所在位置)
    public static void exit(int status) // 终止当前正在运行的Java虚拟机,0表示正常退出,非零表示异常退出
    public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length); // 进行数值元素copy
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class SystemDemo01 {

    public static void main(String[] args) {

    // 获取当前时间所对应的毫秒值
    long millis = System.currentTimeMillis();

    // 输出结果
    System.out.println("当前时间所对应的毫秒值为:" + millis);

    }

    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class SystemDemo01 {

    public static void main(String[] args) {

    // 输出
    System.out.println("程序开始执行了.....");

    // 终止JVM
    System.exit(0);

    // 输出
    System.out.println("程序终止了..........");

    }

    }
    1
    2
    3
    4
    5
    6
    // src: 	 源数组
    // srcPos: 源数值的开始位置
    // dest: 目标数组
    // destPos: 目标数组开始位置
    // length: 要复制的元素个数
    public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
    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
    public class SystemDemo01 {

    public static void main(String[] args) {

    // 定义源数组
    int[] srcArray = {23 , 45 , 67 , 89 , 14 , 56 } ;

    // 定义目标数组
    int[] desArray = new int[10] ;

    // 进行数组元素的copy: 把srcArray数组中从0索引开始的3个元素,从desArray数组中的1索引开始复制过去
    System.arraycopy(srcArray , 0 , desArray , 1 , 3);

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

    }

    }

    }

    arraycopy方法底层细节:

    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
    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
    public class SystemDemo3 {
    public static void main(String[] args) {
    //public static void arraycopy(数据源数组,起始索引,目的地数组,起始索引,拷贝个数) 数组拷贝
    //细节:
    //1.如果数据源数组和目的地数组都是基本数据类型,那么两者的类型必须保持一致,否则会报错
    //2.在拷贝的时候需要考虑数组的长度,如果超出范围也会报错
    //3.如果数据源数组和目的地数组都是引用数据类型,那么子类类型可以赋值给父类类型

    Student s1 = new Student("zhangsan", 23);
    Student s2 = new Student("lisi", 24);
    Student s3 = new Student("wangwu", 25);

    Student[] arr1 = {s1, s2, s3};
    Person[] arr2 = new Person[3];
    //把arr1中对象的地址值赋值给arr2中
    System.arraycopy(arr1, 0, arr2, 0, 3);

    //遍历数组arr2
    for (int i = 0; i < arr2.length; i++) {
    Student stu = (Student) arr2[i];
    System.out.println(stu.getName() + "," + stu.getAge());
    }
    }
    }

    class Person {
    private String name;
    private int age;

    public Person() {
    }

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

    /**
    * 获取
    *
    * @return name
    */
    public String getName() {
    return name;
    }

    /**
    * 设置
    *
    * @param name
    */
    public void setName(String name) {
    this.name = name;
    }

    /**
    * 获取
    *
    * @return age
    */
    public int getAge() {
    return age;
    }

    /**
    * 设置
    *
    * @param age
    */
    public void setAge(int age) {
    this.age = age;
    }

    public String toString() {
    return "Person{name = " + name + ", age = " + age + "}";
    }
    }


    class Student extends Person {

    public Student() {
    }

    public Student(String name, int age) {
    super(name, age);
    }
    }

3.Runtime

  • 概述

    Runtime表示Java中运行时对象,可以获取到程序运行时设计到的一些信息

  • Object类中的常见方法:

    1
    2
    3
    4
    5
    6
    7
    public static Runtime getRuntime()		//当前系统的运行环境对象
    public void exit(int status) //停止虚拟机
    public int availableProcessors() //获得CPU的线程数
    public long maxMemory() //JVM能从系统中获取总内存大小(单位byte)
    public long totalMemory() //JVM已经从系统中获取总内存大小(单位byte)
    public long freeMemory() //JVM剩余内存大小(单位byte)
    public Process exec(String command) //运行cmd命令
    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
    public class RunTimeDemo1 {
    public static void main(String[] args) throws IOException {
    /*
    public static Runtime getRuntime() 当前系统的运行环境对象
    public void exit(int status) 停止虚拟机
    public int availableProcessors() 获得CPU的线程数
    public long maxMemory() JVM能从系统中获取总内存大小(单位byte)
    public long totalMemory() JVM已经从系统中获取总内存大小(单位byte)
    public long freeMemory() JVM剩余内存大小(单位byte)
    public Process exec(string command) 运行cmd命令
    */

    //1.获取Runtime的对象
    //Runtime r1 =Runtime.getRuntime();

    //2.exit 停止虚拟机
    //Runtime.getRuntime().exit(0);
    //System.out.println("看看我执行了吗?");


    //3.获得CPU的线程数
    System.out.println(Runtime.getRuntime().availableProcessors());//8
    //4.总内存大小,单位byte字节
    System.out.println(Runtime.getRuntime().maxMemory() / 1024 / 1024);//4064
    //5.已经获取的总内存大小,单位byte字节
    System.out.println(Runtime.getRuntime().totalMemory() / 1024 / 1024);//254
    //6.剩余内存大小
    System.out.println(Runtime.getRuntime().freeMemory() / 1024 / 1024);//251

    //7.运行cmd命令
    //shutdown :关机
    //加上参数才能执行
    //-s :默认在1分钟之后关机
    //-s -t 指定时间 : 指定关机时间
    //-a :取消关机操作
    //-r: 关机并重启
    Runtime.getRuntime().exec("shutdown -s -t 3600");

    }
    }

4.Object

  • 概述

    1. Object类所在包是java.lang包。
    2. Object 是类层次结构的根类,每个类都可以将 Object 作为超类。所有类都直接或者间接的继承自该类;换句话说,该类所具备的方法,其他所有类都继承了。
    3. 在Object类中提供了一个无参构造方法,但一般情况下很少主动创建Object类的对象,调用其对应的方法。更多的是创建Object类的某个子类对象,然后通过子类对象调用Object类中的方法。
  • 常见方法

    1. 常见方法:

      1
      2
      3
      public String toString()				//返回该对象的字符串表示形式(可以看做是对象的内存地址值)
      public boolean equals(Object obj) //比较两个对象地址值是否相等;true表示相同,false表示不相同
      protected Object clone() //对象克隆
  • 案例演示:

    1. 案例1:toString方法

      • ObjectDemo01测试类

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

        public static void main(String[] args) {

        // 创建学生对象
        Student s1 = new Student("itheima" , "14") ;

        // 调用toString方法获取s1对象的字符串表现形式
        String result1 = s1.toString();

        // 输出结果
        System.out.println("s1对象的字符串表现形式为:" + result1); //s1对象的字符串表现形式为:com.itheima.api.system.demo04.Student@3f3afe78

        }

        }
      • toString方法的重写:

        1
        2
        3
        4
        5
        6
        7
        @Override
        public String toString() {
        return "Student{" +
        "name='" + name + '\'' +
        ", age='" + age + '\'' +
        '}';

      • 小结:

        1. 在通过输出语句输出一个对象时,默认调用的就是toString()方法
        2. 输出地址值一般没有意义,我们可以通过重写toString方法去输出对应的成员变量信息(快捷键:atl + insert , 空白处 右键 -> Generate -> 选择toString)
        3. toString方法的作用:以良好的格式,更方便的展示对象中的属性值
        4. 一般情况下Jdk所提供的类都会重写Object类中的toString方法
    2. 案例2:equals方法

      • ObjectDemo02代码:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        public class ObjectDemo02 {

        public static void main(String[] args) {

        // 创建两个学生对象
        Student s1 = new Student("itheima" , "14") ;
        Student s2 = new Student("itheima" , "14") ;

        // 调用equals方法比较两个对象是否相等
        boolean result = s1.equals(s2);

        // 输出结果
        System.out.println(result);

        }

        }
      • 重写equals方法:

        1
        2
        3
        4
        5
        6
        7
        @Override
        public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return Objects.equals(name, student.name) && Objects.equals(age, student.age); // 比较的是对象的name属性值和age属性值
        }
      • 小结:

        1. 默认情况下equals方法比较的是对象的地址值
        2. 比较对象的地址值是没有意义的,因此一般情况下我们都会重写Object类中的equals方法
    3. 案例3:对象克隆clone()

      对象拷贝,对象复制

      对象克隆的分类:深克隆和浅克隆

      • 浅克隆:
        1. 不管对象内部的属性是基本数据类型还是引用数据类型,都完全拷贝过来
        2. 基本数据类型拷贝过来的是具体的数据,引用数据类型拷贝过来的是地址值
        3. Object类默认的是浅克隆
      • 深克隆:
        1. ​ 基本数据类型拷贝过来,字符串复用,引用数据类型会重新创建新的
      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
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      package com.itheima.a04objectdemo;

      public class ObjectDemo4 {
      public static void main(String[] args) throws CloneNotSupportedException {
      // protected object clone(int a) 对象克隆

      //1.先创建一个对象
      int[] data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0};
      User u1 = new User(1, "zhangsan", "1234qwer", "girl11", data);

      //2.克隆对象
      //细节:
      //方法在底层会帮我们创建一个对象,并把原对象中的数据拷贝过去。
      //书写细节:
      //1.重写Object中的clone方法
      //2.让javabean类实现Cloneable接口
      //3.创建原对象并调用clone就可以了
      //User u2 =(User)u1.clone();

      //验证一件事情:Object中的克隆是浅克隆
      //想要进行深克隆,就需要重写clone方法并修改里面的方法体
      //int[] arr = u1.getData();
      //arr[0] = 100;

      //System.out.println(u1);
      //System.out.println(u2);


      //以后一般会用第三方工具进行克隆
      //1.第三方写的代码导入到项目中
      //2.编写代码
      //Gson gson =new Gson();
      //把对象变成一个字符串
      //String s=gson.toJson(u1);
      //再把字符串变回对象就可以了
      //User user =gson.fromJson(s, User.class);

      //int[] arr=u1.getData();
      //arr[0] = 100;

      //打印对象
      //System.out.println(user);

      }
      }



      package com.itheima.a04objectdemo;

      import java.util.StringJoiner;

      //Cloneable
      //如果一个接口里面没有抽象方法
      //表示当前的接口是一个标记性接口
      //现在Cloneable表示一旦实现了,那么当前类的对象就可以被克降
      //如果没有实现,当前类的对象就不能克隆
      public class User implements Cloneable {
      private int id;
      private String username;
      private String password;
      private String path;
      private int[] data;

      public User() {
      }

      public User(int id, String username, String password, String path, int[] data) {
      this.id = id;
      this.username = username;
      this.password = password;
      this.path = path;
      this.data = data;
      }

      /*get and set*/


      public String toString() {
      return "角色编号为:" + id + ",用户名为:" + username + "密码为:" + password + ", 游戏图片为:" + path + ", 进度:" + arrToString();
      }


      public String arrToString() {
      StringJoiner sj = new StringJoiner(", ", "[", "]");

      for (int i = 0; i < data.length; i++) {
      sj.add(data[i] + "");
      }
      return sj.toString();
      }

      @Override
      protected Object clone() throws CloneNotSupportedException {
      //调用父类中的clone方法
      //相当于让Java帮我们克隆一个对象,并把克隆之后的对象返回出去。

      //先把被克隆对象中的数组获取出来
      int[] data = this.data;
      //创建新的数组
      int[] newData =new int[data.length];
      //拷贝数组中的数据
      for (int i = 0; i < data.length; i++) {
      newData[i] = data[i];
      }
      //调用父类中的方法克隆对象
      User u=(User)super.clone();
      //因为父类中的克隆方法是浅克隆,替换克隆出来对象中的数组地址值
      u.data =newData;
      return u;
      }
      }

5.Objects

  • 概述

    1. Objects类所在包是在java.util包下,因此在使用时需要进行导包。
    2. Objects类被final修饰,因此该类不能被继承。
    3. Objects类提供了一些对象常见操作的方法。比如判断对象是否相等,判断对象是否为null等等。
    4. Objects类中无无参构造方法,因此不能使用new关键字去创建Objects的对象。
    5. Objects类中所提供的方法都是静态的,因此可通过类名直接去调用这些方法。
  • 常见方法

    1. 常见方法介绍

      1
      2
      3
      4
      public static String toString(Object o) 					// 获取对象的字符串表现形式
      public static boolean equals(Object a, Object b) // 比较两个对象是否相等
      public static boolean isNull(Object obj) // 判断对象是否为null
      public static boolean nonNull(Object obj) // 判断对象是否不为null
      1
      2
      3
      4
      5
      了解:

      public static <T> T requireNonNull(T obj) // 检查对象是否不为null,如果为null直接抛出异常;如果不是null返回该对象;
      public static <T> T requireNonNullElse(T obj, T defaultObj) // 检查对象是否不为null,如果不为null,返回该对象;如果为null返回defaultObj值
      public static <T> T requireNonNullElseGet(T obj, Supplier<? extends T> supplier) // 检查对象是否不为null,如果不为null,返回该对象;如果 // 为null,返回由Supplier所提供的值
    2. 案例演示1

      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
      public class ObjectsDemo01 {

      public static void main(String[] args) {

      // 调用方法
      method_04() ;

      }

      // 测试nonNull方法
      public static void method_04() {

      // 创建一个学生对象
      Student s1 = new Student("itheima" , "14") ;

      // 调用Objects类中的nonNull方法
      boolean result = Objects.nonNull(s1);

      // 输出结果
      System.out.println(result);

      }

      // 测试isNull方法
      public static void method_03() {

      // 创建一个学生对象
      Student s1 = new Student("itheima" , "14") ;

      // 调用Objects类中的isNull方法
      boolean result = Objects.isNull(s1);

      // 输出结果
      System.out.println(result);

      }

      // 测试equals方法
      public static void method_02() {

      // 创建两个学生对象
      Student s1 = new Student("itheima" , "14") ;
      Student s2 = new Student("itheima" , "14") ;

      // 调用Objects类中的equals方法,比较两个对象是否相等
      boolean result = Objects.equals(s1, s2); // 如果Student没有重写Object类中的equals方法,此处比较的还是对象的地址值

      // 输出结果
      System.out.println(result);

      }

      // 测试toString方法
      public static void method_01() {

      // 创建一个学生对象
      Student s1 = new Student("itheima" , "14") ;

      // 调用Objects中的toString方法,获取s1对象的字符串表现形式
      String result = Objects.toString(s1); // 如果Student没有重写Object类中的toString方法,此处还是返回的对象的地址值

      // 输出结果
      System.out.println(result);

      }

      }
    3. 案例演示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
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      public class ObjectsDemo02 {

      public static void main(String[] args) {

      // 调用方法
      method_03();

      }

      // 演示requireNonNullElseGet
      public static void method_03() {

      // 创建一个学生对象
      Student s1 = new Student("itheima" , "14") ;

      // 调用Objects对象的requireNonNullElseGet方法,该方法的第二个参数是Supplier类型的,查看源码我们发现Supplier是一个函数式接口,
      // 那么我们就可以为其传递一个Lambda表达式,而在Supplier接口中所定义的方法是无参有返回值的方法,因此具体调用所传入的Lambda表达式如下所示
      Student student = Objects.requireNonNullElseGet(s1, () -> {
      return new Student("itcast", "14");
      });

      // 输出
      System.out.println(student);

      }

      // 演示requireNonNullElse
      public static void method_02() {

      // 创建一个学生对象
      Student s1 = new Student("itheima" , "14") ;

      // 调用Objects对象的requireNonNullElse方法
      Student student = Objects.requireNonNullElse(s1, new Student("itcast", "14"));

      // 输出
      System.out.println(student);

      }

      // 演示requireNonNull
      public static void method_01() {

      // 创建一个学生对象
      Student s1 = new Student("itheima" , "14") ;

      // 调用Objects对象的requireNonNull方法
      Student student = Objects.requireNonNull(s1);

      // 输出
      System.out.println(student);

      }

      }

6.BigInteger

  • 引入:理论上最大到42亿的21亿次方。

  • 概述:BigInteger所在包是在java.math包下,在使用时候需导包。我们可以使用BigInteger类进行大整数的计算

  • 常见方法

    1. 构造方法

      1
      2
      3
      4
      5
      6
      public BigInteger(int num, Random rnd) 		//获取随机大整数,范围:[0 ~ 2的num次方-1]
      public BigInteger(String val) //获取指定的大整数
      public BigInteger(String val, int radix) //获取指定进制的大整数

      下面这个不是构造,而是一个静态方法获取BigInteger对象
      public static BigInteger valueOf(long val) //静态方法获取BigInteger的对象,内部有优化
    2. 构造方法小结:

      • 如果BigInteger表示的数字没有超出long的范围,可以用静态方法获取。
      • 如果BigInteger表示的超出long的范围,可以用构造方法获取。
      • 对象一旦创建,BigInteger内部记录的值不能发生改变。
      • 只要进行计算都会产生一个新的BigInteger对象
    3. 常见成员方法

      BigDecimal类中使用最多的还是提供的进行四则运算的方法,如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      public BigInteger add(BigInteger val)					//加法
      public BigInteger subtract(BigInteger val) //减法
      public BigInteger multiply(BigInteger val) //乘法
      public BigInteger divide(BigInteger val) //除法
      public BigInteger[] divideAndRemainder(BigInteger val) //除法,获取商和余数
      public boolean equals(Object x) //比较是否相同
      public BigInteger pow(int exponent) //次幂、次方
      public BigInteger max/min(BigInteger val) //返回较大值/较小值
      public int intValue(BigInteger val) //转为int类型整数,超出范围数据有误
    4. 代码实现:

      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
      package com.itheima.a06bigintegerdemo;

      import java.math.BigInteger;

      public class BigIntegerDemo1 {
      public static void main(String[] args) {
      /*
      public BigInteger(int num, Random rnd) 获取随机大整数,范围:[0~ 2的num次方-11
      public BigInteger(String val) 获取指定的大整数
      public BigInteger(String val, int radix) 获取指定进制的大整数

      public static BigInteger valueOf(long val) 静态方法获取BigInteger的对象,内部有优化

      细节:
      对象一旦创建里面的数据不能发生改变。
      */


      //1.获取一个随机的大整数
      /* Random r=new Random();
      for (int i = e; i < 100; i++) {
      BigInteger bd1 = new BigInteger(4,r);
      System.out.println(bd1);//[@ ~ 15]}
      }
      */

      //2.获取一个指定的大整数,可以超出long的取值范围
      //细节:字符串中必须是整数,否则会报错
      /* BigInteger bd2 = new BigInteger("1.1");
      System.out.println(bd2);
      */

      /*
      BigInteger bd3 = new BigInteger("abc");
      System.out.println(bd3);
      */

      //3.获取指定进制的大整数
      //细节:
      //1.字符串中的数字必须是整数
      //2.字符串中的数字必须要跟进制吻合。
      //比如二进制中,那么只能写日和1,写其他的就报错。
      BigInteger bd4 = new BigInteger("123", 2);
      System.out.println(bd4);

      //4.静态方法获取BigInteger的对象,内部有优化
      //细节:
      //1.能表示范围比较小,只能在long的取值范围之内,如果超出long的范围就不行了。
      //2.在内部对常用的数字: -16 ~ 16 进行了优化。
      // 提前把-16~16 先创建好BigInteger的对象,如果多次获取不会重新创建新的。
      BigInteger bd5 = BigInteger.valueOf(16);
      BigInteger bd6 = BigInteger.valueOf(16);
      System.out.println(bd5 == bd6);//true


      BigInteger bd7 = BigInteger.valueOf(17);
      BigInteger bd8 = BigInteger.valueOf(17);
      System.out.println(bd7 == bd8);//false


      //5.对象一旦创建内部的数据不能发生改变
      BigInteger bd9 =BigInteger.valueOf(1);
      BigInteger bd10 =BigInteger.valueOf(2);
      //此时,不会修改参与计算的BigInteger对象中的借,而是产生了一个新的BigInteger对象记录
      BigInteger result=bd9.add(bd10);
      System.out.println(result);//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
      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
      package com.itheima.a06bigintegerdemo;

      import java.math.BigInteger;

      public class BigIntegerDemo2 {
      public static void main(String[] args) {
      /*
      public BigInteger add(BigInteger val) 加法
      public BigInteger subtract(BigInteger val) 减法
      public BigInteger multiply(BigInteger val) 乘法
      public BigInteger divide(BigInteger val) 除法,获取商
      public BigInteger[] divideAndRemainder(BigInteger val) 除法,获取商和余数
      public boolean equals(Object x) 比较是否相同
      public BigInteger pow(int exponent) 次幂
      public BigInteger max/min(BigInteger val) 返回较大值/较小值
      public int intValue(BigInteger val) 转为int类型整数,超出范围数据有误
      */

      //1.创建两个BigInteger对象
      BigInteger bd1 = BigInteger.valueOf(10);
      BigInteger bd2 = BigInteger.valueOf(5);

      //2.加法
      BigInteger bd3 = bd1.add(bd2);
      System.out.println(bd3);

      //3.除法,获取商和余数
      BigInteger[] arr = bd1.divideAndRemainder(bd2);
      System.out.println(arr[0]);
      System.out.println(arr[1]);

      //4.比较是否相同
      boolean result = bd1.equals(bd2);
      System.out.println(result);

      //5.次幂
      BigInteger bd4 = bd1.pow(2);
      System.out.println(bd4);

      //6.max
      BigInteger bd5 = bd1.max(bd2);


      //7.转为int类型整数,超出范围数据有误
      /* BigInteger bd6 = BigInteger.valueOf(2147483647L);
      int i = bd6.intValue();
      System.out.println(i);
      */

      BigInteger bd6 = BigInteger.valueOf(200);
      double v = bd6.doubleValue();
      System.out.println(v);//200.0
      }
      }

  • 底层存储方式:

    每32个bit为一组,存储在数组中。

7.BigDecimal

  • 引入

    在使用float或者double类型的数据在进行数学运算的时候,可能会产生精度丢失问题。

  • 概述

    BigDecimal所在包是在java.math包下,因此在使用的时候就需要进行导包。可以使用BigDecimal类进行更加精准的数据计算。

  • 常见方法

    1. 构造方法

      1
      2
      3
      BigDecimal(int val)
      BigDecimal(long val)
      BigDecimal(String val)
    2. 常见成员方法

      1
      2
      3
      4
      public BigDecimal add(BigDecimal value)				// 加法运算
      public BigDecimal subtract(BigDecimal value) // 减法运算
      public BigDecimal multiply(BigDecimal value) // 乘法运算
      public BigDecimal divide(BigDecimal value) // 触发运算
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      public class BigDecimalDemo01 {

      public static void main(String[] args) {

      // 创建两个BigDecimal对象
      BigDecimal b1 = new BigDecimal("0.3") ;
      BigDecimal b2 = new BigDecimal("4") ;

      // 调用方法进行b1和b2的四则运算,并将其运算结果在控制台进行输出
      System.out.println(b1.add(b2)); // 进行加法运算
      System.out.println(b1.subtract(b2)); // 进行减法运算
      System.out.println(b1.multiply(b2)); // 进行乘法运算
      System.out.println(b1.divide(b2)); // 进行除法运算

      }

      }

      案例2:结果是无限循环小数会报错:ArithmeticException。 需使用BigDecimal类中另外一个divide方法:

      1
      BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)
      1
      2
      3
      4
      5
      divisor:			除数对应的BigDecimal对象;
      scale: 精确的位数;
      roundingMode: 取舍模式;
      取舍模式被封装到了RoundingMode这个枚举类中(关于枚举我们后期再做重点讲解),在这个枚举类中定义了很多种取舍方式。最常见的取舍方式有如下几个:
      UP(直接进1) , FLOOR(直接删除) , HALF_UP(4舍五入),我们可以通过如下格式直接访问这些取舍模式:枚举类名.变量名
    3. 底层存储方式:

      将数据当作字符串,遍历得到每一个字符并转化为对应ASCII码表上的值,存储到数组中。

8.正则表达式

正则表达式就是用来验证各种字符串的规则。

1.基础语法

  • 字符类

    1. 语法:

      1. [abc]:代表a、b、c字符中的一个。
      2. [^abc]:代表除a,b,c以外的任何字符。
      3. [a-z]:代表a-z的所有小写字符中的一个。
      4. [A-Z]:代表A-Z的所有大写字符中的一个。
      5. [0-9]:代表0-9之间的某一个数字字符。
      6. [a-zA-Z0-9]:代表a-z或者A-Z或者0-9之间的任意一个字符。
      7. [a-dm-p]:a 到 d 或 m 到 p之间的任意一个字符。
    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
      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
      package com.itheima.a08regexdemo;

      public class RegexDemo2 {
      public static void main(String[] args) {
      //public boolean matches(String regex):判断是否与正则表达式匹配,匹配返回true
      // 只能是a b c
      System.out.println("-----------1-------------");
      System.out.println("a".matches("[abc]")); // true
      System.out.println("z".matches("[abc]")); // false

      // 不能出现a b c
      System.out.println("-----------2-------------");
      System.out.println("a".matches("[^abc]")); // false
      System.out.println("z".matches("[^abc]")); // true
      System.out.println("zz".matches("[^abc]")); //false
      System.out.println("zz".matches("[^abc][^abc]")); //true

      // a到zA到Z(包括头尾的范围)
      System.out.println("-----------3-------------");
      System.out.println("a".matches("[a-zA-z]")); // true
      System.out.println("z".matches("[a-zA-z]")); // true
      System.out.println("aa".matches("[a-zA-z]"));//false
      System.out.println("zz".matches("[a-zA-Z]")); //false
      System.out.println("zz".matches("[a-zA-Z][a-zA-Z]")); //true
      System.out.println("0".matches("[a-zA-Z]"));//false
      System.out.println("0".matches("[a-zA-Z0-9]"));//true


      // [a-d[m-p]] a到d,或m到p
      System.out.println("-----------4-------------");
      System.out.println("a".matches("[a-d[m-p]]"));//true
      System.out.println("d".matches("[a-d[m-p]]")); //true
      System.out.println("m".matches("[a-d[m-p]]")); //true
      System.out.println("p".matches("[a-d[m-p]]")); //true
      System.out.println("e".matches("[a-d[m-p]]")); //false
      System.out.println("0".matches("[a-d[m-p]]")); //false

      // [a-z&&[def]] a-z和def的交集。为:d,e,f
      System.out.println("----------5------------");
      System.out.println("a".matches("[a-z&[def]]")); //false
      System.out.println("d".matches("[a-z&&[def]]")); //true
      System.out.println("0".matches("[a-z&&[def]]")); //false

      // [a-z&&[^bc]] a-z和非bc的交集。(等同于[ad-z])
      System.out.println("-----------6------------_");
      System.out.println("a".matches("[a-z&&[^bc]]"));//true
      System.out.println("b".matches("[a-z&&[^bc]]")); //false
      System.out.println("0".matches("[a-z&&[^bc]]")); //false

      // [a-z&&[^m-p]] a到z和除了m到p的交集。(等同于[a-1q-z])
      System.out.println("-----------7-------------");
      System.out.println("a".matches("[a-z&&[^m-p]]")); //true
      System.out.println("m".matches("[a-z&&[^m-p]]")); //false
      System.out.println("0".matches("[a-z&&[^m-p]]")); //false

      }
      }

  • 逻辑运算符

    1. 语法示例:

      • &&:并且
      • |:或者
      • \:转义字符
    2. 代码示例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      public class Demo {
      public static void main(String[] args) {
      String str = "had";

      //1.要求字符串是小写辅音字符开头,后跟ad
      String regex = "[a-z&&[^aeiou]]ad";
      System.out.println("1." + str.matches(regex));

      //2.要求字符串是aeiou中的某个字符开头,后跟ad
      regex = "[a|e|i|o|u]ad";//这种写法相当于:regex = "[aeiou]ad";
      System.out.println("2." + str.matches(regex));
      }
      }

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

      public class RegexDemo3 {
      public static void main(String[] args) {
      // \ 转义字符 改变后面那个字符原本的含义
      //练习:以字符串的形式打印一个双引号
      //"在Java中表示字符串的开头或者结尾

      //此时\表示转义字符,改变了后面那个双引号原本的含义
      //把他变成了一个普普通通的双引号而已。
      System.out.println("\"");

      // \表示转义字符
      //两个\的理解方式:前面的\是一个转义字符,改变了后面\原本的含义,把他变成一个普普通通的\而已。
      System.out.println("c:Users\\moon\\IdeaProjects\\basic-code\\myapi\\src\\com\\itheima\\a08regexdemo\\RegexDemo1.java");




      }
      }

  • 预定义字符

    1. 语法示例:

      • “.” : 匹配任何字符。
      • “\d”:任何数字[0-9]的简写;
      • “\D”:任何非数字[^0-9]的简写;
      • “\s”: 空白字符:[ \t\n\x0B\f\r] 的简写
      • “\S”: 非空白字符:[^\s] 的简写
      • “\w”:单词字符:[a-zA-Z_0-9]的简写
      • “\W”:非单词字符:[^\w]
    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
      34
      35
      36
      37
      38
      public class Demo {
      public static void main(String[] args) {
      //.表示任意一个字符
      System.out.println("你".matches("..")); //false
      System.out.println("你".matches(".")); //true
      System.out.println("你a".matches(".."));//true

      // \\d 表示任意的一个数字
      // \\d只能是任意的一位数字
      // 简单来记:两个\表示一个\
      System.out.println("a".matches("\\d")); // false
      System.out.println("3".matches("\\d")); // true
      System.out.println("333".matches("\\d")); // false

      //\\w只能是一位单词字符[a-zA-Z_0-9]
      System.out.println("z".matches("\\w")); // true
      System.out.println("2".matches("\\w")); // true
      System.out.println("21".matches("\\w")); // false
      System.out.println("你".matches("\\w"));//false

      // 非单词字符
      System.out.println("你".matches("\\W")); // true
      System.out.println("---------------------------------------------");
      // 以上正则匹配只能校验单个字符。


      // 必须是数字 字母 下划线 至少 6位
      System.out.println("2442fsfsf".matches("\\w{6,}"));//true
      System.out.println("244f".matches("\\w{6,}"));//false

      // 必须是数字和字符 必须是4位
      System.out.println("23dF".matches("[a-zA-Z0-9]{4}"));//true
      System.out.println("23 F".matches("[a-zA-Z0-9]{4}"));//false
      System.out.println("23dF".matches("[\\w&&[^_]]{4}"));//true
      System.out.println("23_F".matches("[\\w&&[^_]]{4}"));//false

      }
      }
  • 数量词

    1. 语法示例:

      • X? : 0次或1次
      • X* : 0次到多次
      • X+ : 1次或多次
      • X{n} : 恰好n次
      • X{n,} : 至少n次
      • X{n,m}: n到m次(n和m都是包含的)
    2. 代码示例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      public class Demo {
      public static void main(String[] args) {
      // 必须是数字 字母 下划线 至少 6位
      System.out.println("2442fsfsf".matches("\\w{6,}"));//true
      System.out.println("244f".matches("\\w{6,}"));//false

      // 必须是数字和字符 必须是4位
      System.out.println("23dF".matches("[a-zA-Z0-9]{4}"));//true
      System.out.println("23 F".matches("[a-zA-Z0-9]{4}"));//false
      System.out.println("23dF".matches("[\\w&&[^_]]{4}"));//true
      System.out.println("23_F".matches("[\\w&&[^_]]{4}"));//false
      }
      }

2.爬虫

  • 本地数据爬取

    1. Pattern:表示正则表达式

    2. Matcher:文本匹配器,作用按照正则表达式的规则去读取字符串,从头开始读取。在大串中去找符合匹配规则的子串。

    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
      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
      package com.itheima.a08regexdemo;

      import java.util.regex.Matcher;
      import java.util.regex.Pattern;

      public class RegexDemo6 {
      public static void main(String[] args) {
      /* 有如下文本,请按照要求爬取数据。
      Java自从95年问世以来,经历了很多版本,目前企业中用的最多的是Java8和Java11,
      因为这两个是长期支持版本,下一个长期支持版本是Java17,相信在未来不久Java17也会逐渐登上历史舞台
      要求:找出里面所有的JavaXX
      */

      String str = "Java自从95年问世以来,经历了很多版本,目前企业中用的最多的是Java8和Java11," +
      "因为这两个是长期支持版本,下一个长期支持版本是Java17,相信在未来不久Java17也会逐渐登上历史舞台";


      //1.获取正则表达式的对象
      Pattern p = Pattern.compile("Java\\d{0,2}");
      //2.获取文本匹配器的对象
      //拿着m去读取str,找符合p规则的子串
      Matcher m = p.matcher(str);

      //3.利用循环获取
      while (m.find()) {
      String s = m.group();
      System.out.println(s);
      }


      }

      private static void method1(String str) {
      //Pattern:表示正则表达式
      //Matcher: 文本匹配器,作用按照正则表达式的规则去读取字符串,从头开始读取。
      // 在大串中去找符合匹配规则的子串。

      //获取正则表达式的对象
      Pattern p = Pattern.compile("Java\\d{0,2}");
      //获取文本匹配器的对象
      //m:文本匹配器的对象
      //str:大串
      //p:规则
      //m要在str中找符合p规则的小串
      Matcher m = p.matcher(str);

      //拿着文本匹配器从头开始读取,寻找是否有满足规则的子串
      //如果没有,方法返回false
      //如果有,返回true。在底层记录子串的起始索引和结束索引+1
      // 0,4
      boolean b = m.find();

      //方法底层会根据find方法记录的索引进行字符串的截取
      // substring(起始索引,结束索引);包头不包尾
      // (0,4)但是不包含4索引
      // 会把截取的小串进行返回。
      String s1 = m.group();
      System.out.println(s1);


      //第二次在调用find的时候,会继续读取后面的内容
      //读取到第二个满足要求的子串,方法会继续返回true
      //并把第二个子串的起始索引和结束索引+1,进行记录
      b = m.find();

      //第二次调用group方法的时候,会根据find方法记录的索引再次截取子串
      String s2 = m.group();
      System.out.println(s2);
      }
      }
  • 网络数据爬取(了解)

    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
    public class RegexDemo7 {
    public static void main(String[] args) throws IOException {
    /* 扩展需求2:
    把连接:https://m.sengzan.com/jiaoyu/29104.html?ivk sa=1025883i
    中所有的身份证号码都爬取出来。
    */

    //创建一个URL对象
    URL url = new URL("https://m.sengzan.com/jiaoyu/29104.html?ivk sa=1025883i");
    //连接上这个网址
    //细节:保证网络是畅通
    URLConnection conn = url.openConnection();//创建一个对象去读取网络中的数据
    BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
    String line;
    //获取正则表达式的对象pattern
    String regex = "[1-9]\\d{17}";
    Pattern pattern = Pattern.compile(regex);//在读取的时候每次读一整行
    while ((line = br.readLine()) != null) {
    //拿着文本匹配器的对象matcher按照pattern的规则去读取当前的这一行信息
    Matcher matcher = pattern.matcher(line);
    while (matcher.find()) {
    System.out.println(matcher.group());
    }
    }
    br.close();
    }
    }

  • 贪婪爬取和非贪婪爬取

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    只写+和表示贪婪匹配,如果在+和后面加问号表示非贪婪爬取
    +? 非贪婪匹配
    *? 非贪婪匹配
    贪婪爬取:在爬取数据的时候尽可能的多获取数据
    非贪婪爬取:在爬取数据的时候尽可能的少获取数据

    举例:
    如果获取数据:ab+
    贪婪爬取获取结果:abbbbbbbbbbbb
    非贪婪爬取获取结果:ab

    代码示例:

    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
    public class RegexDemo10 {
    public static void main(String[] args) {
    /*
    只写+和*表示贪婪匹配

    +? 非贪婪匹配
    *? 非贪婪匹配

    贪婪爬取:在爬取数据的时候尽可能的多获取数据
    非贪婪爬取:在爬取数据的时候尽可能的少获取数据

    ab+:
    贪婪爬取:abbbbbbbbbbbb
    非贪婪爬取:ab
    */
    String s = "Java自从95年问世以来,abbbbbbbbbbbbaaaaaaaaaaaaaaaaaa" +
    "经历了很多版木,目前企业中用的最多的是]ava8和]ava11,因为这两个是长期支持版木。" +
    "下一个长期支持版本是Java17,相信在未来不久Java17也会逐渐登上历史舞台";

    String regex = "ab+";
    Pattern p = Pattern.compile(regex);
    Matcher m = p.matcher(s);

    while (m.find()) {
    System.out.println(m.group());
    }


    }
    }

9.JDK7时间类

1.Date

  • Date概述

    1. java.util.Date类 表示特定的瞬间,精确到毫秒。

    2. 查阅Date类的构造函数。

      • public Date():从运行程序的此时此刻到时间原点经历的毫秒值,转换成Date对象,分配Date对象并初始化此对象,以表示分配它的时间(精确到毫秒)。
      • public Date(long date):将指定参数的毫秒值date,转换成Date对象,分配Date对象并初始化此对象,以表示自从标准基准时间(称为“历元(epoch)”,即1970年1月1日00:00:00 GMT)以来的指定毫秒数。

      tips: 由于中国处于东八区(GMT+08:00)是比世界协调时间/格林尼治时间(GMT)快8小时的时区,当格林尼治标准时间为0:00时,东八区的标准时间为08:00。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      import java.util.Date;

      public class Demo01Date {
      public static void main(String[] args) {
      // 创建日期对象,把当前的时间
      System.out.println(new Date()); // Tue Jan 16 14:37:35 CST 2020
      // 创建日期对象,把当前的毫秒值转成日期对象
      System.out.println(new Date(0L)); // Thu Jan 01 08:00:00 CST 1970
      //tips:在使用println方法时,会自动调用Date类中的toString方法。Date类对Object类中的toString方法进行了覆盖重写,所以结果为指定格式的字符串。
      }
      }
  • Date常用方法

    1. Date类中的多数方法已经过时,常用的方法有:

      • public long getTime() 把日期对象转换成对应的时间毫秒值。
      • public void setTime(long time) 把方法参数给定的毫秒值设置给日期对象
    2. 示例代码

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      public class DateDemo02 {
      public static void main(String[] args) {
      //创建日期对象
      Date d = new Date();

      //public long getTime():获取的是日期对象从1970年1月1日 00:00:00到现在的毫秒值
      //System.out.println(d.getTime());
      //System.out.println(d.getTime() * 1.0 / 1000 / 60 / 60 / 24 / 365 + "年");

      //public void setTime(long time):设置时间,给的是毫秒值
      //long time = 1000*60*60;
      long time = System.currentTimeMillis();
      d.setTime(time);

      System.out.println(d);
      }
      }

      小结:Date表示特定的时间瞬间,使用Date对象对时间进行操作。

2.SimpleDateFormat

java.text.SimpleDateFormat 是日期/时间格式化类,可完成Date对象与String对象之间进行来回转换。

  • 格式化:按照指定的格式,把Date对象转换为String对象。
  • 解析:按照指定的格式,把String对象转换为Date对象。
  • 构造方法

    由于DateFormat为抽象类,不能直接使用,需要常用的子类java.text.SimpleDateFormat。这个类需要一个模式(格式)指定格式化或解析的标准。构造方法为:

    1
    public SimpleDateFormat(String pattern):用给定的模式和默认语言环境的日期格式符号构造SimpleDateFormat。参数pattern是一个字符串,代表日期时间的自定义格式。
  • 格式规则

    常用的格式规则为:

    标识字母(区分大小写) 含义
    y
    M
    d
    H
    m
    s

    备注:更详细的格式规则,可以参考SimpleDateFormat类的API文档。

  • 常用方法

    DateFormat类的常用方法有:

    1. public String format(Date date):将Date对象格式化为字符串。

    2. public Date parse(String source):将字符串解析为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
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    package com.itheima.a01jdk7datedemo;

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

    public class A03_SimpleDateFormatDemo1 {
    public static void main(String[] args) throws ParseException {
    /*
    public simpleDateFormat() 默认格式
    public simpleDateFormat(String pattern) 指定格式
    public final string format(Date date) 格式化(日期对象 ->字符串)
    public Date parse(string source) 解析(字符串 ->日期对象)
    */

    //1.定义一个字符串表示时间
    String str = "2023-11-11 11:11:11";
    //2.利用空参构造创建simpleDateFormat对象
    // 细节:
    //创建对象的格式要跟字符串的格式完全一致
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    Date date = sdf.parse(str);
    //3.打印结果
    System.out.println(date.getTime());//1699672271000


    }

    private static void method1() {
    //1.利用空参构造创建simpleDateFormat对象,默认格式
    SimpleDateFormat sdf1 = new SimpleDateFormat();
    Date d1 = new Date(0L);
    String str1 = sdf1.format(d1);
    System.out.println(str1);//1970/1/1 上午8:00

    //2.利用带参构造创建simpleDateFormat对象,指定格式
    SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy年MM月dd日HH:mm:ss");
    String str2 = sdf2.format(d1);
    System.out.println(str2);//1970年01月01日 08:00:00

    //课堂练习:yyyy年MM月dd日 时:分:秒 星期
    }
    }

    小结:DateFormat可以将Date对象和字符串相互转换。

3.Calendar

  • 概述

    1. java.util.Calendar类表示一个“日历类”,可以进行日期运算。它是一个抽象类,不能创建对象,可使用其子类:java.util.GregorianCalendar类。
    2. 有两种方式可以获取GregorianCalendar对象:
      • 直接创建GregorianCalendar对象;
      • 通过Calendar的静态方法getInstance()方法获取GregorianCalendar对象。
  • 常用方法

    方法名 说明
    public static Calendar getInstance() 获取一个它的子类GregorianCalendar对象。
    public int get(int field) 获取某个字段的值。field参数表示获取哪个字段的值,
    可以使用Calender中定义的常量来表示:
    Calendar.YEAR : 年
    Calendar.MONTH :月
    Calendar.DAY_OF_MONTH:月中的日期
    Calendar.HOUR:小时
    Calendar.MINUTE:分钟
    Calendar.SECOND:秒
    Calendar.DAY_OF_WEEK:星期
    public void set(int field,int value) 设置某个字段的值
    public void add(int field,int amount) 为某个字段增加/减少指定的值
  • get方法示例

    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
    public class Demo {
    public static void main(String[] args) {
    //1.获取一个GregorianCalendar对象
    Calendar instance = Calendar.getInstance();//获取子类对象

    //2.打印子类对象
    System.out.println(instance);

    //3.获取属性
    int year = instance.get(Calendar.YEAR);
    int month = instance.get(Calendar.MONTH) + 1;//Calendar的月份值是0-11
    int day = instance.get(Calendar.DAY_OF_MONTH);

    int hour = instance.get(Calendar.HOUR);
    int minute = instance.get(Calendar.MINUTE);
    int second = instance.get(Calendar.SECOND);

    int week = instance.get(Calendar.DAY_OF_WEEK);//返回值范围:1--7,分别表示:"星期日","星期一","星期二",...,"星期六"

    System.out.println(year + "年" + month + "月" + day + "日" +
    hour + ":" + minute + ":" + second);
    System.out.println(getWeek(week));

    }

    //查表法,查询星期几
    public static String getWeek(int w) {//w = 1 --- 7
    //做一个表(数组)
    String[] weekArray = {"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"};
    // 索引 [0] [1] [2] [3] [4] [5] [6]
    //查表
    return weekArray[w - 1];
    }
    }

  • set方法示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public class Demo {
    public static void main(String[] args) {
    //设置属性——set(int field,int value):
    Calendar c1 = Calendar.getInstance();//获取当前日期

    //计算班长出生那天是星期几(假如班长出生日期为:1998年3月18日)
    c1.set(Calendar.YEAR, 1998);
    c1.set(Calendar.MONTH, 3 - 1);//转换为Calendar内部的月份值
    c1.set(Calendar.DAY_OF_MONTH, 18);

    int w = c1.get(Calendar.DAY_OF_WEEK);
    System.out.println("班长出生那天是:" + getWeek(w));


    }
    //查表法,查询星期几
    public static String getWeek(int w) {//w = 1 --- 7
    //做一个表(数组)
    String[] weekArray = {"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"};
    // 索引 [0] [1] [2] [3] [4] [5] [6]
    //查表
    return weekArray[w - 1];
    }
    }
  • add方法示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class Demo {
    public static void main(String[] args) {
    //计算200天以后是哪年哪月哪日,星期几?
    Calendar c2 = Calendar.getInstance();//获取当前日期
    c2.add(Calendar.DAY_OF_MONTH, 200);//日期加200

    int y = c2.get(Calendar.YEAR);
    int m = c2.get(Calendar.MONTH) + 1;//转换为实际的月份
    int d = c2.get(Calendar.DAY_OF_MONTH);

    int wk = c2.get(Calendar.DAY_OF_WEEK);
    System.out.println("200天后是:" + y + "年" + m + "月" + d + "日" + getWeek(wk));

    }
    //查表法,查询星期几
    public static String getWeek(int w) {//w = 1 --- 7
    //做一个表(数组)
    String[] weekArray = {"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"};
    // 索引 [0] [1] [2] [3] [4] [5] [6]
    //查表
    return weekArray[w - 1];
    }
    }

10.JDK8时间类

JDK8时间类类名 作用
ZoneId 时区
Instant 时间戳
ZoneDateTime 带时区的时间
DateTimeFormatter 用于时间的格式化和解析
LocalDate 年、月、日
LocalTime 时、分、秒
LocalDateTime 年、月、日、时、分、秒
Duration 时间间隔(秒,纳,秒)
Period 时间间隔(年,月,日)
ChronoUnit 时间间隔(所有单位)
  • ZoneId 时区

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    /*
    static Set<string> getAvailableZoneIds() 获取Java中支持的所有时区
    static ZoneId systemDefault() 获取系统默认时区
    static Zoneld of(string zoneld) 获取一个指定时区
    */

    //1.获取所有的时区名称
    Set<String> zoneIds = ZoneId.getAvailableZoneIds();
    System.out.println(zoneIds.size());//600
    System.out.println(zoneIds);// Asia/Shanghai

    //2.获取当前系统的默认时区
    ZoneId zoneId = ZoneId.systemDefault();
    System.out.println(zoneId);//Asia/Shanghai

    //3.获取指定的时区
    ZoneId zoneId1 = ZoneId.of("Asia/Pontianak");
    System.out.println(zoneId1);//Asia/Pontianak
  • Instant 时间戳

    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
    /*
    static Instant now() 获取当前时间的Instant对象(标准时间)
    static Instant ofXxxx(long epochMilli) 根据(秒/毫秒/纳秒)获取Instant对象
    ZonedDateTime atZone(ZoneIdzone) 指定时区
    boolean isxxx(Instant otherInstant) 判断系列的方法
    Instant minusXxx(long millisToSubtract) 减少时间系列的方法
    Instant plusXxx(long millisToSubtract) 增加时间系列的方法
    */
    //1.获取当前时间的Instant对象(标准时间)
    Instant now = Instant.now();
    System.out.println(now);

    //2.根据(秒/毫秒/纳秒)获取Instant对象
    Instant instant1 = Instant.ofEpochMilli(0L);
    System.out.println(instant1);//1970-01-01T00:00:00z

    Instant instant2 = Instant.ofEpochSecond(1L);
    System.out.println(instant2);//1970-01-01T00:00:01Z

    Instant instant3 = Instant.ofEpochSecond(1L, 1000000000L);
    System.out.println(instant3);//1970-01-01T00:00:027

    //3. 指定时区
    ZonedDateTime time = Instant.now().atZone(ZoneId.of("Asia/Shanghai"));
    System.out.println(time);


    //4.isXxx 判断
    Instant instant4=Instant.ofEpochMilli(0L);
    Instant instant5 =Instant.ofEpochMilli(1000L);

    //5.用于时间的判断
    //isBefore:判断调用者代表的时间是否在参数表示时间的前面
    boolean result1=instant4.isBefore(instant5);
    System.out.println(result1);//true

    //isAfter:判断调用者代表的时间是否在参数表示时间的后面
    boolean result2 = instant4.isAfter(instant5);
    System.out.println(result2);//false

    //6.Instant minusXxx(long millisToSubtract) 减少时间系列的方法
    Instant instant6 =Instant.ofEpochMilli(3000L);
    System.out.println(instant6);//1970-01-01T00:00:03Z

    Instant instant7 =instant6.minusSeconds(1);
    System.out.println(instant7);//1970-01-01T00:00:02Z

  • ZoneDateTime 带时区的时间

    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
    /*
    static ZonedDateTime now() 获取当前时间的ZonedDateTime对象
    static ZonedDateTime ofXxxx(。。。) 获取指定时间的ZonedDateTime对象
    ZonedDateTime withXxx(时间) 修改时间系列的方法
    ZonedDateTime minusXxx(时间) 减少时间系列的方法
    ZonedDateTime plusXxx(时间) 增加时间系列的方法
    */
    //1.获取当前时间对象(带时区)
    ZonedDateTime now = ZonedDateTime.now();
    System.out.println(now);

    //2.获取指定的时间对象(带时区)1/年月日时分秒纳秒方式指定
    ZonedDateTime time1 = ZonedDateTime.of(2023, 10, 1,
    11, 12, 12, 0, ZoneId.of("Asia/Shanghai"));
    System.out.println(time1);

    //通过Instant + 时区的方式指定获取时间对象
    Instant instant = Instant.ofEpochMilli(0L);
    ZoneId zoneId = ZoneId.of("Asia/Shanghai");
    ZonedDateTime time2 = ZonedDateTime.ofInstant(instant, zoneId);
    System.out.println(time2);


    //3.withXxx 修改时间系列的方法
    ZonedDateTime time3 = time2.withYear(2000);
    System.out.println(time3);

    //4. 减少时间
    ZonedDateTime time4 = time3.minusYears(1);
    System.out.println(time4);

    //5.增加时间
    ZonedDateTime time5 = time4.plusYears(1);
    System.out.println(time5);
  • DateTimeFormatter 用于时间的格式化和解析

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /*
    static DateTimeFormatter ofPattern(格式) 获取格式对象
    String format(时间对象) 按照指定方式格式化
    */
    //获取时间对象
    ZonedDateTime time = Instant.now().atZone(ZoneId.of("Asia/Shanghai"));

    // 解析/格式化器
    DateTimeFormatter dtf1=DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm;ss EE a");
    // 格式化
    System.out.println(dtf1.format(time));
  • LocalDate 年、月、日

    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
    //1.获取当前时间的日历对象(包含 年月日)
    LocalDate nowDate = LocalDate.now();
    //System.out.println("今天的日期:" + nowDate);
    //2.获取指定的时间的日历对象
    LocalDate ldDate = LocalDate.of(2023, 1, 1);
    System.out.println("指定日期:" + ldDate);

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

    //3.get系列方法获取日历中的每一个属性值//获取年
    int year = ldDate.getYear();
    System.out.println("year: " + year);
    //获取月//方式一:
    Month m = ldDate.getMonth();
    System.out.println(m);
    System.out.println(m.getValue());

    //方式二:
    int month = ldDate.getMonthValue();
    System.out.println("month: " + month);


    //获取日
    int day = ldDate.getDayOfMonth();
    System.out.println("day:" + day);

    //获取一年的第几天
    int dayofYear = ldDate.getDayOfYear();
    System.out.println("dayOfYear:" + dayofYear);

    //获取星期
    DayOfWeek dayOfWeek = ldDate.getDayOfWeek();
    System.out.println(dayOfWeek);
    System.out.println(dayOfWeek.getValue());

    //is开头的方法表示判断
    System.out.println(ldDate.isBefore(ldDate));
    System.out.println(ldDate.isAfter(ldDate));

    //with开头的方法表示修改,只能修改年月日
    LocalDate withLocalDate = ldDate.withYear(2000);
    System.out.println(withLocalDate);

    //minus开头的方法表示减少,只能减少年月日
    LocalDate minusLocalDate = ldDate.minusYears(1);
    System.out.println(minusLocalDate);


    //plus开头的方法表示增加,只能增加年月日
    LocalDate plusLocalDate = ldDate.plusDays(1);
    System.out.println(plusLocalDate);

    //-------------
    // 判断今天是否是你的生日
    LocalDate birDate = LocalDate.of(2000, 1, 1);
    LocalDate nowDate1 = LocalDate.now();

    MonthDay birMd = MonthDay.of(birDate.getMonthValue(), birDate.getDayOfMonth());
    MonthDay nowMd = MonthDay.from(nowDate1);

    System.out.println("今天是你的生日吗? " + birMd.equals(nowMd));//今天是你的生日吗?
  • LocalTime 时、分、秒

    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
    // 获取本地时间的日历对象。(包含 时分秒)
    LocalTime nowTime = LocalTime.now();
    System.out.println("今天的时间:" + nowTime);

    int hour = nowTime.getHour();//时
    System.out.println("hour: " + hour);

    int minute = nowTime.getMinute();//分
    System.out.println("minute: " + minute);

    int second = nowTime.getSecond();//秒
    System.out.println("second:" + second);

    int nano = nowTime.getNano();//纳秒
    System.out.println("nano:" + nano);
    System.out.println("------------------------------------");
    System.out.println(LocalTime.of(8, 20));//时分
    System.out.println(LocalTime.of(8, 20, 30));//时分秒
    System.out.println(LocalTime.of(8, 20, 30, 150));//时分秒纳秒
    LocalTime mTime = LocalTime.of(8, 20, 30, 150);

    //is系列的方法
    System.out.println(nowTime.isBefore(mTime));
    System.out.println(nowTime.isAfter(mTime));

    //with系列的方法,只能修改时、分、秒
    System.out.println(nowTime.withHour(10));

    //plus系列的方法,只能修改时、分、秒
    System.out.println(nowTime.plusHours(10));
  • LocalDateTime 年、月、日、时、分、秒

    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
    // 当前时间的的日历对象(包含年月日时分秒)
    LocalDateTime nowDateTime = LocalDateTime.now();

    System.out.println("今天是:" + nowDateTime);//今天是:
    System.out.println(nowDateTime.getYear());//年
    System.out.println(nowDateTime.getMonthValue());//月
    System.out.println(nowDateTime.getDayOfMonth());//日
    System.out.println(nowDateTime.getHour());//时
    System.out.println(nowDateTime.getMinute());//分
    System.out.println(nowDateTime.getSecond());//秒
    System.out.println(nowDateTime.getNano());//纳秒
    // 日:当年的第几天
    System.out.println("dayofYear:" + nowDateTime.getDayOfYear());
    //星期
    System.out.println(nowDateTime.getDayOfWeek());
    System.out.println(nowDateTime.getDayOfWeek().getValue());
    //月份
    System.out.println(nowDateTime.getMonth());
    System.out.println(nowDateTime.getMonth().getValue());

    LocalDate ld = nowDateTime.toLocalDate();
    System.out.println(ld);

    LocalTime lt = nowDateTime.toLocalTime();
    System.out.println(lt.getHour());
    System.out.println(lt.getMinute());
    System.out.println(lt.getSecond());
  • Duration 时间间隔(秒,纳,秒)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 本地日期时间对象。
    LocalDateTime today = LocalDateTime.now();
    System.out.println(today);

    // 出生的日期时间对象
    LocalDateTime birthDate = LocalDateTime.of(2000, 1, 1, 0, 0, 0);
    System.out.println(birthDate);

    Duration duration = Duration.between(birthDate, today);//第二个参数减第一个参数
    System.out.println("相差的时间间隔对象:" + duration);

    System.out.println("============================================");
    System.out.println(duration.toDays());//两个时间差的天数
    System.out.println(duration.toHours());//两个时间差的小时数
    System.out.println(duration.toMinutes());//两个时间差的分钟数
    System.out.println(duration.toMillis());//两个时间差的毫秒数
    System.out.println(duration.toNanos());//两个时间差的纳秒数
  • Period 时间间隔(年,月,日)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 当前本地 年月日
    LocalDate today = LocalDate.now();
    System.out.println(today);

    // 生日的 年月日
    LocalDate birthDate = LocalDate.of(2000, 1, 1);
    System.out.println(birthDate);

    Period period = Period.between(birthDate, today);//第二个参数减第一个参数

    System.out.println("相差的时间间隔对象:" + period);
    System.out.println(period.getYears());
    System.out.println(period.getMonths());
    System.out.println(period.getDays());

    System.out.println(period.toTotalMonths());
  • ChronoUnit 时间间隔(所有单位)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // 当前时间
    LocalDateTime today = LocalDateTime.now();
    System.out.println(today);
    // 生日时间
    LocalDateTime birthDate = LocalDateTime.of(2000, 1, 1,0, 0, 0);
    System.out.println(birthDate);

    System.out.println("相差的年数:" + ChronoUnit.YEARS.between(birthDate, today));
    System.out.println("相差的月数:" + ChronoUnit.MONTHS.between(birthDate, today));
    System.out.println("相差的周数:" + ChronoUnit.WEEKS.between(birthDate, today));
    System.out.println("相差的天数:" + ChronoUnit.DAYS.between(birthDate, today));
    System.out.println("相差的时数:" + ChronoUnit.HOURS.between(birthDate, today));
    System.out.println("相差的分数:" + ChronoUnit.MINUTES.between(birthDate, today));
    System.out.println("相差的秒数:" + ChronoUnit.SECONDS.between(birthDate, today));
    System.out.println("相差的毫秒数:" + ChronoUnit.MILLIS.between(birthDate, today));
    System.out.println("相差的微秒数:" + ChronoUnit.MICROS.between(birthDate, today));
    System.out.println("相差的纳秒数:" + ChronoUnit.NANOS.between(birthDate, today));
    System.out.println("相差的半天数:" + ChronoUnit.HALF_DAYS.between(birthDate, today));
    System.out.println("相差的十年数:" + ChronoUnit.DECADES.between(birthDate, today));
    System.out.println("相差的世纪(百年)数:" + ChronoUnit.CENTURIES.between(birthDate, today));
    System.out.println("相差的千年数:" + ChronoUnit.MILLENNIA.between(birthDate, today));
    System.out.println("相差的纪元数:" + ChronoUnit.ERAS.between(birthDate, today));

11.包装类

Java提供两种类型系统:基本类型、引用类型,使用基本类型对应的包装类,如下:

基本类型 对应的包装类(位于java.lang包中)
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

1.Integer

  • Integer类概述:包装一个对象中的原始类型 int 的值

  • Integer类构造方法及静态方法

    方法名 说明
    public Integer(int value) 根据 int 值创建 Integer 对象(过时)
    public Integer(String s) 根据 String 值创建 Integer 对象(过时)
    public static Integer valueOf(int i) 返回表示指定的 int 值的 Integer 实例
    public static Integer valueOf(String s) 返回保存指定String值的 Integer 对象
    static string tobinarystring(int i) 得到二进制
    static string tooctalstring(int i) 得到八进制
    static string toHexstring(int i) 得到十六进制
    static int parseInt(string s) 将字符串类型的整数转成int类型的整数
  • 示例代码

    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
    //public Integer(int value):根据 int 值创建 Integer 对象(过时)
    Integer i1 = new Integer(100);
    System.out.println(i1);

    //public Integer(String s):根据 String 值创建 Integer 对象(过时)
    Integer i2 = new Integer("100");
    //Integer i2 = new Integer("abc"); //NumberFormatException
    System.out.println(i2);
    System.out.println("--------");

    //public static Integer valueOf(int i):返回表示指定的 int 值的 Integer 实例
    Integer i3 = Integer.valueOf(100);
    System.out.println(i3);

    //public static Integer valueOf(String s):返回保存指定String值的Integer对象
    Integer i4 = Integer.valueOf("100");
    System.out.println(i4);

    /*
    public static string tobinarystring(int i) 得到二进制
    public static string tooctalstring(int i) 得到八进制
    public static string toHexstring(int i) 得到十六进制
    public static int parseInt(string s) 将字符串类型的整数转成int类型的整数
    */

    //1.把整数转成二进制,十六进制
    String str1 = Integer.toBinaryString(100);
    System.out.println(str1);//1100100

    //2.把整数转成八进制
    String str2 = Integer.toOctalString(100);
    System.out.println(str2);//144

    //3.把整数转成十六进制
    String str3 = Integer.toHexString(100);
    System.out.println(str3);//64

    //4.将字符串类型的整数转成int类型的整数
    //强类型语言:每种数据在java中都有各自的数据类型
    //在计算的时候,如果不是同一种数据类型,是无法直接计算的。
    int i = Integer.parseInt("123");
    System.out.println(i);
    System.out.println(i + 1);//124
    //细节1:
    //在类型转换的时候,括号中的参数只能是数字不能是其他,否则代码会报错
    //细节2:
    //8种包装类当中,除了Character都有对应的parseXxx的方法,进行类型转换
    String str = "true";
    boolean b = Boolean.parseBoolean(str);
    System.out.println(b);

2.装箱与拆箱

基本类型与对应的包装类对象之间,来回转换的过程称为”装箱“与”拆箱“:

  • 概念:

    1. 装箱:从基本类型转换为对应的包装类对象。
    2. 拆箱:从包装类对象转换为对应的基本类型。
  • 基本数值—->包装对象

    1
    2
    Integer i = new Integer(4);//使用构造函数函数
    Integer iii = Integer.valueOf(4);//使用包装类中的valueOf方法
  • 包装对象—->基本数值

    1
    int num = i.intValue();
  • 自动装箱与自动拆箱

    从Java 5(JDK 1.5)开始,基本类型与包装类的装箱、拆箱动作可以自动完成。例如:

    1
    2
    3
    Integer i = 4;//自动装箱。相当于Integer i = Integer.valueOf(4);
    i = i + 5;//等号右边:将i对象转成基本数值(自动拆箱) i.intValue() + 5;
    //加法运算完成后,再次装箱,把基本数值转成对象。

3.基本类型与字符串之间的转换

  • 基本类型转换为String

    1. 转换方式

      • 方式一:直接在数字后加一个空字符串
      • 方式二:通过String类静态方法valueOf()
    2. 示例代码

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      public class IntegerDemo {
      public static void main(String[] args) {
      //int --- String
      int number = 100;
      //方式1
      String s1 = number + "";
      System.out.println(s1);
      //方式2
      //public static String valueOf(int i)
      String s2 = String.valueOf(number);
      System.out.println(s2);
      System.out.println("--------");
      }
      }
  • String转换成基本类型

    除了Character类之外,其他所有包装类都具有parseXxx静态方法可以将字符串参数转换为对应的基本类型:

    1. 转换方法:

      • public static byte parseByte(String s):将字符串参数转换为对应的byte基本类型。
      • public static short parseShort(String s):将字符串参数转换为对应的short基本类型。
      • public static int parseInt(String s):将字符串参数转换为对应的int基本类型。
      • public static long parseLong(String s):将字符串参数转换为对应的long基本类型。
      • public static float parseFloat(String s):将字符串参数转换为对应的float基本类型。
      • public static double parseDouble(String s):将字符串参数转换为对应的double基本类型。
      • public static boolean parseBoolean(String s):将字符串参数转换为对应的boolean基本类型。
    2. 转换方式

      • 方式一:先将字符串数字转成Integer,再调用valueOf()方法
      • 方式二:通过Integer静态方法parseInt()进行转换
    3. 示例代码

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      public class IntegerDemo {
      public static void main(String[] args) {
      //String --- int
      String s = "100";
      //方式1:String --- Integer --- int
      Integer i = Integer.valueOf(s);
      //public int intValue()
      int x = i.intValue();
      System.out.println(x);
      //方式2
      //public static int parseInt(String s)
      int y = Integer.parseInt(s);
      System.out.println(y);
      }
      }

      注意:如果字符串参数的内容无法正确转换为对应的基本类型,则会抛出java.lang.NumberFormatException异常。

4.底层原理

  • 获取Integer对象时不使用new,而是采取直接赋值或者静态方法valueOf的方式
  • 在实际开发中,-128~127之间的数据使用较多。若使用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
35
36
37
38
//1.利用构造方法获取Integer的对象(JDK5以前的方式)
/*Integer i1 = new Integer(1);
Integer i2 = new Integer("1");
System.out.println(i1);
System.out.println(i2);*/

//2.利用静态方法获取Integer的对象(JDK5以前的方式)
Integer i3 = Integer.valueOf(123);
Integer i4 = Integer.valueOf("123");
Integer i5 = Integer.valueOf("123", 8);

System.out.println(i3);
System.out.println(i4);
System.out.println(i5);

//3.这两种方式获取对象的区别(掌握)
//底层原理:
//因为在实际开发中,-128~127之间的数据,用的比较多。
//如果每次使用都是new对象,那么太浪费内存了
//所以,提前把这个范围之内的每一个数据都创建好对象
//如果要用到了不会创建新的,而是返回已经创建好的对象。
Integer i6 = Integer.valueOf(127);
Integer i7 = Integer.valueOf(127);
System.out.println(i6 == i7);//true

Integer i8 = Integer.valueOf(128);
Integer i9 = Integer.valueOf(128);
System.out.println(i8 == i9);//false

//因为看到了new关键字,在Java中,每一次new都是创建了一个新的对象
//所以下面的两个对象都是new出来,地址值不一样。
/*Integer i10 = new Integer(127);
Integer i11 = new Integer(127);
System.out.println(i10 == i11);

Integer i12 = new Integer(128);
Integer i13 = new Integer(128);
System.out.println(i12 == i13);*/

12.lambda表达式

  • Lambda表达式的标准格式

    1
    2
    3
    ()->{

    }
    1. ():对应着方法的形参
    2. ->:固定格式
    3. {}:对应方法的方法体
  • 要求

    1. Lambda表达式可以用来简化匿名内部类的书写。
    2. Lambda表达式只能简化函数式接口的匿名内部类的写法。
      • 函数式接口:有且仅有一个抽象方法的接口,接口上可添加@FunctionalInterface注解
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class Main {
    public static void main(String[] args) {

    fun(() -> {
    System.out.println("蛙泳");
    });

    }


    public static void fun(Swim s) {
    s.swimming();
    }
    }


    interface Swim {
    void swimming();
    }
  • lambda省略规则:

    1. 参数类型可以省略
    2. 若只有一个参数,参数类型可省略,同时()也可省略
    3. 若Lambda表达式方法体只有一行,大括号,分号,return可省略不写,需要同时省略
    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
    public class Main {
    public static void main(String[] args) {

    Integer[] arr = {2, 1, 3, 5, 6, 4, 8, 9};

    // 匿名内部类
    Arrays.sort(arr, new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
    return o1 - o2;
    }
    });

    // 完整lambda写法
    Arrays.sort(arr, (Integer o1, Integer o2) -> {
    return o1 - o2;
    });

    // lambda 省略写法
    Arrays.sort(arr, ((o1, o2) -> o1 - o2));

    System.out.println(Arrays.toString(arr));

    }

    }

13.集合

1.ArrayList

  • 概述

    1. 什么是集合:提供一种存储空间可变的存储模型,存储的数据容量可以发生改变。
    2. ArrayList集合的特点:长度可以变化,只能存储引用数据类型。
    3. 泛型的使用:用于约束集合中存储元素的数据类型。
  • ArrayList类常用方法

    1. 构造方法

      方法名 说明
      public ArrayList() 创建一个空的集合对象
    2. 成员方法

      方法名 说明
      public boolean add(要添加的元素) 将指定的元素追加到此集合的末尾
      public boolean remove(要删除的元素) 删除指定第一个元素,返回值表示是否删除成功
      public E remove(int index) 删除指定索引处的元素,返回被删除的元素
      public E set(int index,E element) 修改指定索引处的元素,返回被修改的元素
      public E get(int index) 返回指定索引处的元素
      public int size() 返回集合中的元素的个数
    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
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      public class ArrayListDemo02 {
      public static void main(String[] args) {
      //创建集合
      // ArrayList<String> array = new ArrayList<String>();
      ArrayList<String> array = new ArrayList<>();

      //添加元素
      array.add("hello"); // 返回为true
      array.add("world");
      array.add("java");

      //public boolean remove(Object o):删除指定的元素,返回删除是否成功
      // System.out.println(array.remove("world"));
      // System.out.println(array.remove("javaee"));

      //public E remove(int index):删除指定索引处的元素,返回被删除的元素
      // System.out.println(array.remove(1));

      //IndexOutOfBoundsException
      // System.out.println(array.remove(3));

      //public E set(int index,E element):修改指定索引处的元素,返回被修改的元素
      // System.out.println(array.set(1,"javaee"));

      //IndexOutOfBoundsException
      // System.out.println(array.set(3,"javaee"));

      //public E get(int index):返回指定索引处的元素
      // System.out.println(array.get(0));
      // System.out.println(array.get(1));
      // System.out.println(array.get(2));
      //System.out.println(array.get(3)); //?????? 自己测试

      //public int size():返回集合中的元素的个数
      System.out.println(array.size());

      //输出集合
      System.out.println("array:" + array);
      }
      }
  • ArrayList存储字符串并遍历

    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
    public class ArrayListDemo3 {
    public static void main(String[] args) {
    //1.创建集合对象
    ArrayList<String> list = new ArrayList<>();

    //2.添加元素
    list.add("aaa");
    list.add("bbb");
    list.add("ccc");
    list.add("ddd");

    //3.遍历
    //快捷键: list.fori 正向遍历
    //list.forr 倒着遍历
    System.out.print("[");
    for (int i = 0; i < list.size(); i++) {
    //i 依次表示集合里面的每一个索引

    if(i == list.size() - 1){
    //最大索引
    System.out.print(list.get(i));
    }else{
    //非最大索引
    System.out.print(list.get(i) + ", ");
    }
    }
    System.out.print("]");
    }
    }

2.Collection集合

  • 数组和集合的区别

    1. 相同点

      都是容器,可以存储多个数据

    2. 不同点

      • 数组的长度是不可变的,集合的长度是可变的

      • 数组可以存基本数据类型和引用数据类型

        集合只能存引用数据类型,如果要存基本数据类型,需要存对应的包装类

  • 集合类体系结构

    001

  • Collection 集合概述和使用

    1. Collection集合概述

      • 是单列集合的顶层接口,它表示一组对象,这些对象也称为Collection的元素
      • JDK 不提供此接口的任何直接实现.它提供更具体的子接口(如Set和List)实现
    2. 创建Collection集合的对象

      • 多态的方式
      • 具体的实现类ArrayList
    3. Collection集合常用方法

      方法名 说明
      boolean add(E e) 添加元素
      boolean remove(E e) 从集合中移除指定的元素(共性方法,无法通过索引删除)
      boolean removeIf(Object o) 根据条件进行移除
      void clear() 清空集合中的元素
      boolean contains(Object o) 判断集合中是否存在指定的元素(依赖equals()方法判断,需要javaBean中重新方法)
      boolean isEmpty() 判断集合是否为空
      int size() 集合的长度,也就是集合中元素的个数
  • Collection集合的遍历

    1. 迭代器遍历

      • 迭代器介绍

        • 迭代器:集合的专用遍历方式
        • Iterator<E> iterator(): 返回此集合中元素的迭代器,通过集合对象的iterator()方法得到
      • Iterator中的常用方法

        boolean hasNext(): 判断当前位置是否有元素可以被取出

        E next(): 获取当前位置的元素,将迭代器对象移向下一个索引位置

      • Collection集合的遍历

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        public class IteratorDemo1 {
        public static void main(String[] args) {
        //创建集合对象
        Collection<String> c = new ArrayList<>();

        //添加元素
        c.add("hello");
        c.add("world");
        c.add("java");
        c.add("javaee");

        //Iterator<E> iterator():返回此集合中元素的迭代器,通过集合的iterator()方法得到
        Iterator<String> it = c.iterator();

        //用while循环改进元素的判断和获取
        while (it.hasNext()) {
        String s = it.next();
        System.out.println(s);
        }
        }
        }
    2. 迭代器中删除的方法

      void remove(): 删除迭代器对象当前指向的元素

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      public class IteratorDemo2 {
      public static void main(String[] args) {
      ArrayList<String> list = new ArrayList<>();
      list.add("a");
      list.add("b");
      list.add("b");
      list.add("c");
      list.add("d");

      Iterator<String> it = list.iterator();
      while(it.hasNext()){
      String s = it.next();
      if("b".equals(s)){
      //指向谁,那么此时就删除谁.
      it.remove();
      }
      }
      System.out.println(list);
      }
      }
    3. 细节点注意:

      • 报错NoSuchElementException
      • 迭代器遍历完毕,指针不会复位
      • 循环中只能用一次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
      40
      41
      42
      43
      44
      public class A04_CollectionDemo4 {
      public static void main(String[] args) {
      /*
      迭代器的细节注意点:
      1.报错NoSuchElementException
      2.迭代器遍历完毕,指针不会复位
      3.循环中只能用一次next方法
      4.迭代器遍历时,不能用集合的方法进行增加或者删除
      暂时当做一个结论先行记忆,在今天我们会讲解源码详细的再来分析。
      如果我实在要删除:那么可以用迭代器提供的remove方法进行删除。
      如果我要添加,暂时没有办法。(只是暂时)
      */

      //1.创建集合并添加元素
      Collection<String> coll = new ArrayList<>();
      coll.add("aaa");
      coll.add("bbb");
      coll.add("ccc");
      coll.add("ddd");

      //2.获取迭代器对象
      //迭代器就好比是一个箭头,默认指向集合的0索引处
      Iterator<String> it = coll.iterator();
      //3.利用循环不断的去获取集合中的每一个元素
      while(it.hasNext()){
      //4.next方法的两件事情:获取元素并移动指针
      String str = it.next();
      System.out.println(str);
      }

      //当上面循环结束之后,迭代器的指针已经指向了最后没有元素的位置
      //System.out.println(it.next());//NoSuchElementException

      //迭代器遍历完毕,指针不会复位
      System.out.println(it.hasNext());

      //如果我们要继续第二次遍历集合,只能再次获取一个新的迭代器对象
      Iterator<String> it2 = coll.iterator();
      while(it2.hasNext()){
      String str = it2.next();
      System.out.println(str);
      }
      }
      }
  • 增强for

    1. 介绍

      • 它是JDK5之后出现的,其内部原理是一个Iterator迭代器
      • 实现Iterable接口的类才可以使用迭代器和增强for
      • 简化数组和Collection集合的遍历
    2. 格式

      1
      2
      3
      4
      for(集合/数组中元素的数据类型 变量名 :  集合/数组名) {
      // 已经将当前遍历到的元素封装到变量中了,直接使用变量即可

      }
    3. 代码

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      public class MyCollectonDemo1 {
      public static void main(String[] args) {
      ArrayList<String> list = new ArrayList<>();
      list.add("a");
      list.add("b");
      list.add("c");
      list.add("d");
      list.add("e");
      list.add("f");

      //1,数据类型一定是集合或者数组中元素的类型
      //2,str仅仅是一个变量名而已,在循环的过程中,依次表示集合或者数组中的每一个元素
      //3,list就是要遍历的集合或者数组
      for(String str : list){
      System.out.println(str);
      }
      }
      }
  • lambda表达式

    利用forEach()方法,再结合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
    public class A07_CollectionDemo7 {
    public static void main(String[] args) {
    /*
    lambda表达式遍历:
    default void forEach(Consumer<? super T> action):
    */

    //1.创建集合并添加元素
    Collection<String> coll = new ArrayList<>();
    coll.add("zhangsan");
    coll.add("lisi");
    coll.add("wangwu");
    //2.利用匿名内部类的形式
    //底层原理:
    //其实也会自己遍历集合,依次得到每一个元素
    //把得到的每一个元素,传递给下面的accept方法
    //s依次表示集合中的每一个数据
    /* coll.forEach(new Consumer<String>() {
    @Override
    public void accept(String s) {
    System.out.println(s);
    }
    });*/

    //lambda表达式
    coll.forEach(s -> System.out.println(s));
    }
    }

1.List集合

  • List集合的概述和特点

    1. List集合的概述
      • 有序集合:指存取顺序
      • 有索引:可通过索引操作元素
      • 可重复:允许重复的元素
    2. List集合的特点
      • 存取有序
      • 可重复
      • 有索引
  • List集合的特有方法

    1. 方法介绍

      方法名 描述
      void add(int index,E element) 在此集合中的指定位置插入指定的元素
      E remove(int index) 删除指定索引处的元素,返回被删除的元素
      E set(int index,E element) 修改指定索引处的元素,返回被修改的元素
      E get(int index) 返回指定索引处的元素
    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
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      public class MyListDemo {
      public static void main(String[] args) {
      List<String> list = new ArrayList<>();
      list.add("aaa");
      list.add("bbb");
      list.add("ccc");
      //method1(list);
      //method2(list);
      //method3(list);
      //method4(list);
      }

      private static void method4(List<String> list) {
      // E get(int index) 返回指定索引处的元素
      String s = list.get(0);
      System.out.println(s);
      }

      private static void method3(List<String> list) {
      // E set(int index,E element) 修改指定索引处的元素,返回被修改的元素
      //被替换的那个元素,在集合中就不存在了.
      String result = list.set(0, "qqq");
      System.out.println(result);
      System.out.println(list);
      }

      private static void method2(List<String> list) {
      // E remove(int index) 删除指定索引处的元素,返回被删除的元素
      //在List集合中有两个删除的方法
      //第一个 删除指定的元素,返回值表示当前元素是否删除成功
      //第二个 删除指定索引的元素,返回值表示实际删除的元素
      String s = list.remove(0);
      System.out.println(s);
      System.out.println(list);
      }

      private static void method1(List<String> list) {
      // void add(int index,E element) 在此集合中的指定位置插入指定的元素
      //原来位置上的元素往后挪一个索引.
      list.add(0,"qqq");
      System.out.println(list);
      }
      }
  • List集合的五种遍历方式

    1. 迭代器
    2. 列表迭代器
    3. 增强for
    4. Lambda表达式
    5. 普通for循环

    代码示例:

    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
    //创建集合并添加元素
    List<String> list = new ArrayList<>();
    list.add("aaa");
    list.add("bbb");
    list.add("ccc");

    //1.迭代器
    /*Iterator<String> it = list.iterator();
    while(it.hasNext()){
    String str = it.next();
    System.out.println(str);
    }*/


    //2.增强for
    //下面的变量s,其实就是一个第三方的变量而已。
    //在循环的过程中,依次表示集合中的每一个元素
    /* for (String s : list) {
    System.out.println(s);
    }*/

    //3.Lambda表达式
    //forEach方法的底层其实就是一个循环遍历,依次得到集合中的每一个元素
    //并把每一个元素传递给下面的accept方法
    //accept方法的形参s,依次表示集合中的每一个元素
    //list.forEach(s->System.out.println(s) );


    //4.普通for循环
    //size方法跟get方法还有循环结合的方式,利用索引获取到集合中的每一个元素
    /*for (int i = 0; i < list.size(); i++) {
    //i:依次表示集合中的每一个索引
    String s = list.get(i);
    System.out.println(s);
    }*/

    // 5.列表迭代器
    //获取一个列表迭代器的对象,里面的指针默认也是指向0索引的

    //额外添加了一个方法:在遍历的过程中,可以添加元素
    ListIterator<String> it = list.listIterator();
    while(it.hasNext()){
    String str = it.next();
    if("bbb".equals(str)){
    //qqq
    it.add("qqq");
    }
    }
    System.out.println(list);
  • 细节点注意:

    List系列集合中的两个删除的方法

    1
    2
    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
    //List系列集合中的两个删除的方法
    //1.直接删除元素
    //2.通过索引进行删除

    //1.创建集合并添加元素
    List<Integer> list = new ArrayList<>();

    list.add(1);
    list.add(2);
    list.add(3);


    //2.删除元素
    //请问:此时删除的是1这个元素,还是1索引上的元素?
    //为什么?
    //因为在调用方法的时候,如果方法出现了重载现象
    //优先调用,实参跟形参类型一致的那个方法。

    //list.remove(1);


    //手动装箱,手动把基本数据类型的1,变成Integer类型
    Integer i = Integer.valueOf(1);

    list.remove(i);

    System.out.println(list);

2.LinkedList集合

  • List集合子类的特点【记忆】

    1. ArrayList集合

      底层是数组结构实现,查询快、增删慢

    2. LinkedList集合

      底层是链表结构实现,查询慢、增删快

  • LinkedList集合的特有功能【应用】

    1. 特有方法

      方法名 说明
      public void addFirst(E e) 在该列表开头插入指定的元素
      public void addLast(E e) 将指定的元素追加到此列表的末尾
      public E getFirst() 返回此列表中的第一个元素
      public E getLast() 返回此列表中的最后一个元素
      public E removeFirst() 从此列表中删除并返回第一个元素
      public E removeLast() 从此列表中删除并返回最后一个元素
    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
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      public class MyLinkedListDemo4 {
      public static void main(String[] args) {
      LinkedList<String> list = new LinkedList<>();
      list.add("aaa");
      list.add("bbb");
      list.add("ccc");
      // public void addFirst(E e) 在该列表开头插入指定的元素
      //method1(list);

      // public void addLast(E e) 将指定的元素追加到此列表的末尾
      //method2(list);

      // public E getFirst() 返回此列表中的第一个元素
      // public E getLast() 返回此列表中的最后一个元素
      //method3(list);

      // public E removeFirst() 从此列表中删除并返回第一个元素
      // public E removeLast() 从此列表中删除并返回最后一个元素
      //method4(list);

      }

      private static void method4(LinkedList<String> list) {
      String first = list.removeFirst();
      System.out.println(first);

      String last = list.removeLast();
      System.out.println(last);

      System.out.println(list);
      }

      private static void method3(LinkedList<String> list) {
      String first = list.getFirst();
      String last = list.getLast();
      System.out.println(first);
      System.out.println(last);
      }

      private static void method2(LinkedList<String> list) {
      list.addLast("www");
      System.out.println(list);
      }

      private static void method1(LinkedList<String> list) {
      list.addFirst("qqq");
      System.out.println(list);
      }
      }

3.源码分析

  • ArrayList源码分析:

    1. 核心步骤:

      • 创建ArrayList对象时,底层先创建了一个长度为0的数组。(添加第一个元素时,底层会创建一个新的长度为10的数组)

        数组名字:elementDate,定义变量size。

        size这个变量有两层含义:
        ①:元素的个数,也就是集合的长度
        ②:下一个元素的存入位置

      • 添加元素,添加完毕后,size++

    2. 扩容时机一:

      当存满时候,会创建一个新的数组,新数组的长度,是原来的1.5倍,也就是长度为15。再把所有的元素,全拷贝到新数组中。

    3. 扩容时机二:

      如果一次添加多个元素,超过1.5倍,新创建数组的长度以实际为准(原始10 + 实际100 = 110)。

  • LinkedList源码分析:

    底层是双向链表结构

    核心步骤如下:

    1. 刚开始创建的时候,底层创建了两个变量:一个记录头结点first,一个记录尾结点last,默认为null
    2. 添加第一个元素时,底层创建一个结点对象,first和last都记录这个结点的地址值
    3. 添加第二个元素时,底层创建一个结点对象,第一个结点会记录第二个结点的地址值,last会记录新结点的地址值
  • 迭代器源码分析:

    迭代器遍历相关的三个方法:

    1. Iterator iterator() :获取一个迭代器对象

    2. boolean hasNext() :判断当前指向的位置是否有元素

    3. E next() :获取当前指向的元素并移动指针

4.泛型

  • 泛型概述:

    1. 泛型的介绍

      泛型是JDK5中引入的特性,它提供了编译时类型安全检测机制

    2. 泛型的好处

      • 把运行时期的问题提前到了编译期间,避免了强制类型转换
      • 统一数据类型
    3. 泛型的定义格式

      • <类型>: 指定一种类型的格式.尖括号里可任写,一般只写一个字母.例如:

        注:泛型只能支持引用数据类型。

      • <类型1,类型2…>: 指定多种类型的格式,多种类型之间用逗号隔开.例如: <E,T> <K,V>

  • 泛型类

    1
    2
    3
    4
    5
    6
    7
    8
    修饰符 class 类名<类型>{

    }


    public class ArryList<E>{

    }
  • 泛型方法

    1. 方案一:使用类名后面定义的泛型(所有方法都可使用)

    2. 方案二:在方法申明上定义自己的泛型(只有本方法使用)

      1
      2
      3
      public static<E> void addAll(ArrayList<E> list){

      }
  • 泛型接口

    1
    2
    3
    public interface List<E>{

    }
    1. 实现类给出具体类型
    2. 实现类延续泛型,创建对象时在确定
  • 泛型的继承

    泛型不具备继承性,但是数据具备继承性

  • 泛型的通配符

    1. ?:表示不确定的通配符。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      ? extends E: //表示可以传递E或E所有的子类类型
      ? super E: //表示可以传递E或E所有的父类类型

      public static void method(ArraryList<? extends Ye> list){

      }

      public static void method(ArraryList<? super zi> list){

      }

5.数据结构(树)

  • 二叉树

    任意一个节点的度要小于等于2

  • 二叉查找树

    1. 二叉查找树的特点

      • 二叉查找树,又称二叉排序树或者二叉搜索树
      • 每一个节点上最多有两个子节点
      • 左子树上所有节点的值都小于根节点的值
      • 右子树上所有节点的值都大于根节点的值
    2. 二叉查找树添加节点规则

      • 小的存左边
      • 大的存右边
      • 一样的不存
  • 平衡二叉树

    1. 平衡二叉树的特点

      • 二叉树左右两个子树的高度差不超过1
      • 任意节点的左右两个子树都是一颗平衡二叉树
    2. 平衡二叉树旋转

      • 旋转触发时机

        当添加一个节点之后,该树不再是一颗平衡二叉树

      • 左旋

        就是将根节点的右侧往左拉,原先的右子节点变成新的父节点,并把多余的左子节点出让,给已经降级的根节点当右子节点

      • 右旋

        就是将根节点的左侧往右拉,左子节点变成了新的父节点,并把多余的右子节点出让,给已经降级根节点当左子节点

    3. 平衡二叉树旋转的四种情况

      • 左左

        • 左左: 当根节点左子树的左子树有节点插入,导致二叉树不平衡

        • 如何旋转: 直接对整体进行右旋即可

      • 左右

        • 左右: 当根节点左子树的右子树有节点插入,导致二叉树不平衡

        • 如何旋转: 先在左子树对应的节点位置进行左旋,在对整体进行右旋

      • 右右

        • 右右: 当根节点右子树的右子树有节点插入,导致二叉树不平衡

        • 如何旋转: 直接对整体进行左旋即可

      • 右左

        • 右左:当根节点右子树的左子树有节点插入,导致二叉树不平衡

        • 如何旋转: 先在右子树对应的节点位置进行右旋,在对整体进行左旋

  • 红黑树

    1. 红黑树的特点

      • 平衡二叉B树
      • 每一个节点可以是红或者黑
      • 红黑树不是高度平衡的,它的平衡是通过”自己的红黑规则”进行实现的
    2. 红黑树的红黑规则

      1. 每一个节点或是红色的,或者是黑色的

      2. 根节点必须是黑色

      3. 如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶节点,每个叶节点(Nil)是黑色的

      4. 如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连 的情况)

      5. 对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点

    3. 红黑树添加节点的默认颜色

      • 添加节点时,默认为红色,效率高
    4. 红黑树添加节点后如何保持红黑规则

      • 根节点位置
        • 直接变为黑色
      • 非根节点位置
        • 父节点为黑色
          • 不需要任何操作,默认红色即可
        • 父节点为红色
          • 叔叔节点为红色
            1. 将”父节点”设为黑色,将”叔叔节点”设为黑色
            2. 将”祖父节点”设为红色
            3. 如果”祖父节点”为根节点,则将根节点再次变成黑色
          • 叔叔节点为黑色
            1. 将”父节点”设为黑色
            2. 将”祖父节点”设为红色
            3. 以”祖父节点”为支点进行旋转

6.Set集合

  • Set集合概述和特点

    1. 无序
    2. 不重复:不可以存储重复元素
    3. 无索引:不能使用普通for循环遍历
  • Set集合的使用

    存储字符串并遍历

    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
    public class MySet1 {
    public static void main(String[] args) {
    //创建集合对象
    Set<String> set = new TreeSet<>();
    //添加元素
    set.add("ccc");
    set.add("aaa");
    set.add("aaa");
    set.add("bbb");

    // for (int i = 0; i < set.size(); i++) {
    // //Set集合是没有索引的,所以不能使用通过索引获取元素的方法
    // }

    //遍历集合
    Iterator<String> it = set.iterator();
    while (it.hasNext()){
    String s = it.next();
    System.out.println(s);
    }


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

    // 增强for
    for (String s : set) {
    System.out.println(s);
    }


    System.out.println("-----------------------------------");
    //Lambda表达式
    set.forEach(str -> System.out.println(str));

    }
    }

7.HashSet集合

  • HashSet集合概述和特点

    1. 底层数据结构是哈希表
    2. 有序
    3. 不重复
    4. 无索引:不能使用普通for循环遍历
  • 哈希值

    1. 根据hashCode方法算出来的int类型的整数。

    2. 该方法定义在Object类中,所有对象都可以调用,默认使用地址值进行计算。

    3. 一般情况下,会重写hashCode方法,利用对象内部的属性值计算哈希值。

    4. 获取哈希值

      Object类中的public int hashCode():返回对象的哈希码值

  • 对象的哈希值特点

    1. 若没有重写hashCode方法,不同对象计算出的哈希值是不同的。
    2. 若已经重写hashCode方法,不同的对象只要属性值相同,计算出的哈希值相同。
    3. 不同属性值或不同地址值计算出来的哈希值有可能相同。(哈希碰撞)
  • HashSet集合的基本应用

    存储字符串并遍历

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class HashSetDemo {
    public static void main(String[] args) {
    //创建集合对象
    HashSet<String> set = new HashSet<String>();

    //添加元素
    set.add("hello");
    set.add("world");
    set.add("java");
    //不包含重复元素的集合
    set.add("world");

    //遍历
    for(String s : set) {
    System.out.println(s);
    }
    }
    }
  • 哈希表结构

    1. JDK1.8以前:数组 + 链表

    2. JDK1.8以后:

      • 节点个数少于等于8个:数组 + 链表

      • 节点个数多于8个且数组长度大于等于64:数组 + 红黑树

  • HashSet集合存储学生对象并遍历

    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
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      public class Student {
      private String name;
      private int age;

      public Student() {
      }

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

      public String getName() {
      return name;
      }

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

      public int getAge() {
      return age;
      }

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

      @Override
      public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;

      Student student = (Student) o;

      if (age != student.age) return false;
      return name != null ? name.equals(student.name) : student.name == null;
      }

      @Override
      public int hashCode() {
      int result = name != null ? name.hashCode() : 0;
      result = 31 * result + age;
      return result;
      }
      }

      测试类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      public class HashSetDemo02 {
      public static void main(String[] args) {
      //创建HashSet集合对象
      HashSet<Student> hs = new HashSet<Student>();

      //创建学生对象
      Student s1 = new Student("林青霞", 30);
      Student s2 = new Student("张曼玉", 35);
      Student s3 = new Student("王祖贤", 33);

      Student s4 = new Student("王祖贤", 33);

      //把学生添加到集合
      hs.add(s1);
      hs.add(s2);
      hs.add(s3);
      hs.add(s4);

      //遍历集合(增强for)
      for (Student s : hs) {
      System.out.println(s.getName() + "," + s.getAge());
      }
      }
      }
    3. 总结

      ​ HashSet集合存储自定义类型元素,要想实现元素的唯一,要求必须重写hashCode方法和equals方法

8.LinkedHashSet集合

  • 特点:

    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
    31
    32
    33
    34
    35
    36
    37
    38
    public class LinkedHashSetStudentDemo {
    public static void main(String[] args) {
    // 1. 创建LinkedHashSet,泛型指定为Student
    LinkedHashSet<Student> studentSet = new LinkedHashSet<>();

    // 2. 添加Student对象(包含重复学号的对象,测试去重)
    Student s1 = new Student("001", "张三", 18);
    Student s2 = new Student("002", "李四", 19);
    Student s3 = new Student("001", "张三(重复)", 18); // 学号和s1相同,视为重复
    Student s4 = new Student("003", "王五", 20);

    studentSet.add(s1);
    studentSet.add(s2);
    studentSet.add(s3); // 重复元素,不会被添加
    studentSet.add(s4);

    // 3. 遍历LinkedHashSet(验证插入顺序不变,且重复元素被过滤)
    System.out.println("=== 遍历LinkedHashSet(插入顺序) ===");
    // 方式1:增强for循环
    for (Student s : studentSet) {
    System.out.println(s);
    }

    // 方式2:迭代器遍历(效果和增强for一致)
    System.out.println("\n=== 迭代器遍历 ===");
    Iterator<Student> iterator = studentSet.iterator();
    while (iterator.hasNext()) {
    System.out.println(iterator.next());
    }

    // 4. 常用方法演示
    System.out.println("\n=== 常用方法 ===");
    System.out.println("集合大小:" + studentSet.size()); // 输出3(s3未被添加)
    System.out.println("是否包含学号002的学生:" + studentSet.contains(new Student("002", "李四", 19))); // true
    System.out.println("移除学号003的学生:" + studentSet.remove(new Student("003", "王五", 20))); // true
    System.out.println("移除后集合大小:" + studentSet.size()); // 输出2
    }
    }

9.TreeSet集合

  • TreeSet集合概述和特点

    1. 不重复
    2. 无索引
    3. 有序:可以将元素按照规则进行排序(默认从小到大)
      • TreeSet():根据其元素的自然排序进行排序
      • TreeSet(Comparator comparator) :根据指定的比较器进行排序
  • TreeSet集合基本使用

    存储Integer类型的整数并遍历

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class TreeSetDemo01 {
    public static void main(String[] args) {
    //创建集合对象
    TreeSet<Integer> ts = new TreeSet<Integer>();

    //添加元素
    ts.add(10);
    ts.add(40);
    ts.add(30);
    ts.add(50);
    ts.add(20);
    ts.add(30);

    //遍历集合
    for(Integer i : ts) {
    System.out.println(i);
    }
    }
    }
  • 自然排序Comparable的使用

    1. 案例需求

      • 存储学生对象并遍历,创建TreeSet集合使用无参构造方法
      • 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
    2. 实现步骤

      • 使用空参构造创建TreeSet集合

        用TreeSet集合存储自定义对象,无参构造方法使用的是自然排序对元素进行排序的

      • 自定义的Student类实现Comparable接口

        自然排序,就是让元素所属的类实现Comparable接口,重写compareTo(T o)方法

      • 重写接口中的compareTo方法

        重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写

    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
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      public class Student implements Comparable<Student>{
      private String name;
      private int age;

      public Student() {
      }

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

      public String getName() {
      return name;
      }

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

      public int getAge() {
      return age;
      }

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

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

      @Override
      public int compareTo(Student o) {
      //按照对象的年龄进行排序
      //主要判断条件: 按照年龄从小到大排序
      int result = this.age - o.age;
      //次要判断条件: 年龄相同时,按照姓名的字母顺序排序
      result = result == 0 ? this.name.compareTo(o.getName()) : result;
      return result;
      }
      }

      测试类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      public class MyTreeSet2 {
      public static void main(String[] args) {
      //创建集合对象
      TreeSet<Student> ts = new TreeSet<>();
      //创建学生对象
      Student s1 = new Student("zhangsan",28);
      Student s2 = new Student("lisi",27);
      Student s3 = new Student("wangwu",29);
      Student s4 = new Student("zhaoliu",28);
      Student s5 = new Student("qianqi",30);
      //把学生添加到集合
      ts.add(s1);
      ts.add(s2);
      ts.add(s3);
      ts.add(s4);
      ts.add(s5);
      //遍历集合
      for (Student student : ts) {
      System.out.println(student);
      }
      }
      }
  • 比较器排序Comparator的使用

    1. 案例需求

      • 存储老师对象并遍历,创建TreeSet集合使用带参构造方法
      • 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
    2. 实现步骤

      • 用TreeSet集合存储自定义对象,带参构造方法使用的是比较器排序对元素进行排序的
      • 比较器排序,就是让集合构造方法接收Comparator的实现类对象,重写compare(T o1,T o2)方法
      • 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
    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
      31
      32
      33
      34
      35
      36
      public class Teacher {
      private String name;
      private int age;

      public Teacher() {
      }

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

      public String getName() {
      return name;
      }

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

      public int getAge() {
      return age;
      }

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

      @Override
      public String toString() {
      return "Teacher{" +
      "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
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      public class MyTreeSet4 {
      public static void main(String[] args) {
      //创建集合对象
      TreeSet<Teacher> ts = new TreeSet<>(new Comparator<Teacher>() {
      @Override
      public int compare(Teacher o1, Teacher o2) {
      //o1表示现在要存入的那个元素
      //o2表示已经存入到集合中的元素

      //主要条件
      int result = o1.getAge() - o2.getAge();
      //次要条件
      result = result == 0 ? o1.getName().compareTo(o2.getName()) : result;
      return result;
      }
      });
      //创建老师对象
      Teacher t1 = new Teacher("zhangsan",23);
      Teacher t2 = new Teacher("lisi",22);
      Teacher t3 = new Teacher("wangwu",24);
      Teacher t4 = new Teacher("zhaoliu",24);
      //把老师添加到集合
      ts.add(t1);
      ts.add(t2);
      ts.add(t3);
      ts.add(t4);
      //遍历集合
      for (Teacher teacher : ts) {
      System.out.println(teacher);
      }
      }
      }
  • 两种比较方式总结

    1. 两种比较方式小结
      • 自然排序: 自定义类实现Comparable接口,重写compareTo方法,根据返回值进行排序
      • 比较器排序: 创建TreeSet对象的时候传递Comparator的实现类对象,重写compare方法,根据返回值进行排序
      • 在使用的时候,默认使用自然排序,当自然排序不满足现在的需求时,必须使用比较器排序
    2. 两种方式中关于返回值的规则
      • 如果返回值为负数,表示当前存入的元素是较小值,存左边
      • 如果返回值为0,表示当前存入的元素跟集合中元素重复了,不存
      • 如果返回值为正数,表示当前存入的元素是较大值,存右边

3.Map集合

  • Map集合概述和特点【理解】

    1. Map集合概述

      1
      2
      3
      4
      interface Map<K,V>  K:键的类型;V:值的类型 
      // 键值对
      // 或 键值对对象
      // 或 Entry
    2. Map集合的特点

      • 双列集合,一个键对应一个值
      • 键不可以重复,值可以重复
    3. Map集合的基本使用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      public class MapDemo01 {
      public static void main(String[] args) {
      //创建集合对象
      Map<String,String> map = new HashMap<String,String>();

      //V put(K key, V value) 将指定的值与该映射中的指定键相关联
      map.put("itheima001","林青霞");
      map.put("itheima002","张曼玉");
      map.put("itheima003","王祖贤");
      map.put("itheima003","柳岩");

      //输出集合对象
      System.out.println(map);
      }
      }
  • Map集合的基本功能

    1. 方法介绍

      方法名 说明
      V put(K key,V value) 添加/覆盖元素(若覆盖,返回被覆盖的值,若空,返回null)
      V remove(Object key) 根据键删除键值对元素
      void clear() 移除所有的键值对元素
      boolean containsKey(Object key) 判断集合是否包含指定的键
      boolean containsValue(Object value) 判断集合是否包含指定的值
      boolean isEmpty() 判断集合是否为空
      int size() 集合的长度,也就是集合中键值对的个数
    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
      public class MapDemo02 {
      public static void main(String[] args) {
      //创建集合对象
      Map<String,String> map = new HashMap<String,String>();

      //V put(K key,V value):添加元素
      map.put("张无忌","赵敏");
      map.put("郭靖","黄蓉");
      map.put("杨过","小龙女");

      //V remove(Object key):根据键删除键值对元素
      // System.out.println(map.remove("郭靖"));
      // System.out.println(map.remove("郭襄"));

      //void clear():移除所有的键值对元素
      // map.clear();

      //boolean containsKey(Object key):判断集合是否包含指定的键
      // System.out.println(map.containsKey("郭靖"));
      // System.out.println(map.containsKey("郭襄"));

      //boolean isEmpty():判断集合是否为空
      // System.out.println(map.isEmpty());

      //int size():集合的长度,也就是集合中键值对的个数
      System.out.println(map.size());

      //输出集合对象
      System.out.println(map);
      }
      }
  • Map集合的获取功能

    1. 方法介绍

      方法名 说明
      V get(Object key) 根据键获取值
      Set keySet() 获取所有键的集合
      Collection values() 获取所有值的集合
      Set<Map.Entry<K,V>> entrySet() 获取所有键值对对象的集合
    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
      public class MapDemo03 {
      public static void main(String[] args) {
      //创建集合对象
      Map<String, String> map = new HashMap<String, String>();

      //添加元素
      map.put("张无忌", "赵敏");
      map.put("郭靖", "黄蓉");
      map.put("杨过", "小龙女");

      //V get(Object key):根据键获取值
      // System.out.println(map.get("张无忌"));
      // System.out.println(map.get("张三丰"));

      //Set<K> keySet():获取所有键的集合
      // Set<String> keySet = map.keySet();
      // for(String key : keySet) {
      // System.out.println(key);
      // }

      //Collection<V> values():获取所有值的集合
      Collection<String> values = map.values();
      for(String value : values) {
      System.out.println(value);
      }
      }
      }
  • Map集合的遍历(增强for)

    1. 步骤分析

      • 获取所有键的集合。用keySet()方法实现
      • 遍历键的集合,获取到每一个键。用增强for实现
      • 根据键去找值。用get(Object key)方法实现
    2. 代码实现

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      public class MapDemo01 {
      public static void main(String[] args) {
      //创建集合对象
      Map<String, String> map = new HashMap<String, String>();

      //添加元素
      map.put("张无忌", "赵敏");
      map.put("郭靖", "黄蓉");
      map.put("杨过", "小龙女");

      //获取所有键的集合。用keySet()方法实现
      Set<String> keySet = map.keySet();
      //遍历键的集合,获取到每一个键。用增强for实现
      for (String key : keySet) {
      //根据键去找值。用get(Object key)方法实现
      String value = map.get(key);
      System.out.println(key + "," + value);
      }
      }
      }
  • Map集合的遍历(对象集合)

    1. 步骤分析

      • 获取所有键值对对象的集合
        • Set<Map.Entry<K,V>> entrySet():获取所有键值对对象的集合
      • 遍历键值对对象的集合,得到每一个键值对对象
        • 用增强for实现,得到每一个Map.Entry
      • 根据键值对对象获取键和值
        • 用getKey()得到键
        • 用getValue()得到值
    2. 代码实现

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      public class MapDemo02 {
      public static void main(String[] args) {
      //创建集合对象
      Map<String, String> map = new HashMap<String, String>();

      //添加元素
      map.put("张无忌", "赵敏");
      map.put("郭靖", "黄蓉");
      map.put("杨过", "小龙女");

      //获取所有键值对对象的集合
      Set<Map.Entry<String, String>> entrySet = map.entrySet();
      //遍历键值对对象的集合,得到每一个键值对对象
      for (Map.Entry<String, String> me : entrySet) {
      //根据键值对对象获取键和值
      String key = me.getKey();
      String value = me.getValue();
      System.out.println(key + "," + value);
      }
      }
      }
    3. lambda表达式

      1
      map.forEach((key,value) -> sout(key + "=" + value))

1.HashMap

直接使用map中的方法即可。

  • HashMap集合概述和特点

    1. HashMap底层是哈希表结构的
    2. 依赖hashCode方法和equals方法保证键的唯一
    3. 如果键要存储的是自定义对象,需要重写hashCode和equals方法
    4. 键:无序,不重复,无索引
  • HashMap集合应用案例

    1. 案例需求

      • 创建一个HashMap集合,键是学生对象(Student),值是居住地 (String)。存储多个元素,并遍历。
      • 要求保证键的唯一性:如果学生对象的成员变量值相同,我们就认为是同一个对象
    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
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      public class Student {
      private String name;
      private int age;

      public Student() {
      }

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

      public String getName() {
      return name;
      }

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

      public int getAge() {
      return age;
      }

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

      @Override
      public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;

      Student student = (Student) o;

      if (age != student.age) return false;
      return name != null ? name.equals(student.name) : student.name == null;
      }

      @Override
      public int hashCode() {
      int result = name != null ? name.hashCode() : 0;
      result = 31 * result + age;
      return result;
      }
      }

      测试类

      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 HashMapDemo {
      public static void main(String[] args) {
      //创建HashMap集合对象
      HashMap<Student, String> hm = new HashMap<Student, String>();

      //创建学生对象
      Student s1 = new Student("林青霞", 30);
      Student s2 = new Student("张曼玉", 35);
      Student s3 = new Student("王祖贤", 33);
      Student s4 = new Student("王祖贤", 33);

      //把学生添加到集合
      hm.put(s1, "西安");
      hm.put(s2, "武汉");
      hm.put(s3, "郑州");
      hm.put(s4, "北京");

      //遍历集合
      Set<Student> keySet = hm.keySet();
      for (Student key : keySet) {
      String value = hm.get(key);
      System.out.println(key.getName() + "," + key.getAge() + "," + value);
      }
      }
      }

3.LinkedHashMap

  • 特点:

    1. 由键决定:有序(存取有序)、不重复、无索引。
    2. 原理是哈希表,每个键值对元素额外多一个双链表的机制
  • 代码

    1
    2
    3
    4
    LinkedHashMap<String,String> li = new LinkedHashMap<>();

    li.put("a","aaa");
    li.put("b","bbb");

3.TreeMap

  • TreeMap集合概述和特点

    1. TreeMap底层是红黑树结构
    2. 依赖自然排序或者比较器排序,对键进行排序
    3. 如果键存储的是自定义对象,需要实现Comparable接口或者在创建TreeMap对象时候给出比较器排序规则
  • TreeMap集合应用案例

    1. 案例需求

      • 创建一个TreeMap集合,键是学生对象(Student),值是籍贯(String),学生属性姓名和年龄,按照年龄进行排序并遍历
      • 要求按照学生的年龄进行排序,如果年龄相同则按照姓名进行排序
    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
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      public class Student implements Comparable<Student>{
      private String name;
      private int age;

      public Student() {
      }

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

      public String getName() {
      return name;
      }

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

      public int getAge() {
      return age;
      }

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

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

      @Override
      public int compareTo(Student o) {
      //按照年龄进行排序
      int result = o.getAge() - this.getAge();
      //次要条件,按照姓名排序。
      result = result == 0 ? o.getName().compareTo(this.getName()) : result;
      return result;
      }
      }

      测试类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      public class Test1 {
      public static void main(String[] args) {
      // 创建TreeMap集合对象
      TreeMap<Student,String> tm = new TreeMap<>();

      // 创建学生对象
      Student s1 = new Student("xiaohei",23);
      Student s2 = new Student("dapang",22);
      Student s3 = new Student("xiaomei",22);

      // 将学生对象添加到TreeMap集合中
      tm.put(s1,"江苏");
      tm.put(s2,"北京");
      tm.put(s3,"天津");

      // 遍历TreeMap集合,打印每个学生的信息
      tm.forEach(
      (Student key, String value)->{
      System.out.println(key + "---" + value);
      }
      );
      }
      }

4.可变参数

方法形参的个数可以改变。

  • 格式

    1
    类型 ...名字
  • 注意:

    1. 方法的形参中最多只能有一个可变参数。
    2. 有其他形参,可变参数要写在最后。
  • 举例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public static void main(String[] args) {

    System.out.println( getSum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
    }

    public static int getSum(int ...args){
    int sum = 0;
    for (int i = 0; i < args.length; i++) {
    sum += args[i];
    }
    return sum;
    }

5.collections

  • 概述:

    java.utils.Collections是集合工具类,用来对集合进行操作。

  • 常用方法如下:

    1. public static void shuffle(List<?> list) :打乱集合顺序。
    2. public static <T> void sort(List<T> list):将集合中元素按照默认规则排序。
    3. public static <T> void sort(List<T> list,Comparator<? super T> ):将集合中元素按照指定规则排序。
  • 代码演示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class CollectionsDemo {
    public static void main(String[] args) {
    ArrayList<Integer> list = new ArrayList<Integer>();

    list.add(100);
    list.add(300);
    list.add(200);
    list.add(50);

    // 批量添加
    Collections.addSll(list,1,2,3,4,5)

    //排序方法
    Collections.sort(list);
    System.out.println(list);
    }
    }
    结果:
    [50,100, 200, 300]

6.不可变集合

  • 不可变集合

    长度不可变,内容也无法修改的集合

  • 使用场景

    如果某个数据不能被修改,把它防御性地拷贝到不可变集合中是个很好的实践。

    当集合对象被不可信的库调用时,不可变形式是安全的。

  • 不可变集合分类

    1. 不可变的list集合
    2. 不可变的set集合
    3. 不可变的map集合

1.不可变的list集合

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
public class ImmutableDemo1 {
public static void main(String[] args) {
/*
创建不可变的List集合
"张三", "李四", "王五", "赵六"
*/

//一旦创建完毕之后,是无法进行修改的,在下面的代码中,只能进行查询操作
List<String> list = List.of("张三", "李四", "王五", "赵六");

System.out.println(list.get(0));
System.out.println(list.get(1));
System.out.println(list.get(2));
System.out.println(list.get(3));

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

for (String s : list) {
System.out.println(s);
}

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


Iterator<String> it = list.iterator();
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}
System.out.println("---------------------------");

for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
System.out.println(s);
}
System.out.println("---------------------------");

// list.remove("李四");
// list.add("aaa");
// list.set(0,"aaa");
}
}

2.不可变的Set集合

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
public class ImmutableDemo2 {
public static void main(String[] args) {
/*
创建不可变的Set集合
"张三", "李四", "王五", "赵六"


细节:
当我们要获取一个不可变的Set集合时,里面的参数一定要保证唯一性
*/

//一旦创建完毕之后,是无法进行修改的,在下面的代码中,只能进行查询操作
Set<String> set = Set.of("张三", "张三", "李四", "王五", "赵六");

for (String s : set) {
System.out.println(s);
}

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

Iterator<String> it = set.iterator();
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}

System.out.println("-----------------------");
//set.remove("王五");
}
}

3.不可变的Map集合

  • 键值对个数小于等于10

    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
    public class ImmutableDemo3 {
    public static void main(String[] args) {
    /*
    创建Map的不可变集合
    细节1:
    键是不能重复的
    细节2:
    Map里面的of方法,参数是有上限的,最多只能传递20个参数,10个键值对
    细节3:
    如果我们要传递多个键值对对象,数量大于10个,在Map接口中还有一个方法
    */

    //一旦创建完毕之后,是无法进行修改的,在下面的代码中,只能进行查询操作
    Map<String, String> map = Map.of("张三", "南京", "张三", "北京", "王五", "上海",
    "赵六", "广州", "孙七", "深圳", "周八", "杭州",
    "吴九", "宁波", "郑十", "苏州", "刘一", "无锡",
    "陈二", "嘉兴");

    Set<String> keys = map.keySet();
    for (String key : keys) {
    String value = map.get(key);
    System.out.println(key + "=" + value);
    }

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

    Set<Map.Entry<String, String>> entries = map.entrySet();
    for (Map.Entry<String, String> entry : entries) {
    String key = entry.getKey();
    String value = entry.getValue();
    System.out.println(key + "=" + value);
    }
    System.out.println("--------------------------");
    }
    }
  • 键值对个数大于10

    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
    public class ImmutableDemo4 {
    public static void main(String[] args) {

    /*
    创建Map的不可变集合,键值对的数量超过10个
    */

    //1.创建一个普通的Map集合
    HashMap<String, String> hm = new HashMap<>();
    hm.put("张三", "南京");
    hm.put("李四", "北京");
    hm.put("王五", "上海");
    hm.put("赵六", "北京");
    hm.put("孙七", "深圳");
    hm.put("周八", "杭州");
    hm.put("吴九", "宁波");
    hm.put("郑十", "苏州");
    hm.put("刘一", "无锡");
    hm.put("陈二", "嘉兴");
    hm.put("aaa", "111");

    //2.利用上面的数据来获取一个不可变的集合
    /*
    //获取到所有的键值对对象(Entry对象)
    Set<Map.Entry<String, String>> entries = hm.entrySet();
    //把entries变成一个数组
    Map.Entry[] arr1 = new Map.Entry[0];
    //toArray方法在底层会比较集合的长度跟数组的长度两者的大小
    //如果集合的长度 > 数组的长度 :数据在数组中放不下,此时会根据实际数据的个数,重新创建数组
    //如果集合的长度 <= 数组的长度:数据在数组中放的下,此时不会创建新的数组,而是直接用
    Map.Entry[] arr2 = entries.toArray(arr1);
    //不可变的map集合
    Map map = Map.ofEntries(arr2);
    map.put("bbb","222");*/


    //Map<Object, Object> map = Map.ofEntries(hm.entrySet().toArray(new Map.Entry[0]));

    Map<String, String> map = Map.copyOf(hm);
    map.put("bbb","222");
    }
    }

7.Stream

  • 体验Stream流

    1. 案例需求

      • 创建一个集合,存储多个字符串元素
      • 把集合中所有以”张”开头的元素存储到一个新的集合
      • 把”张”开头的集合中的长度为3的元素存储到一个新的集合
      • 遍历上一步得到的集合
    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
      public class MyStream1 {
      public static void main(String[] args) {
      //集合的批量添加
      ArrayList<String> list1 = new ArrayList<>(List.of("张三丰","张无忌","张翠山","王二麻子","张良","谢广坤"));
      //list.add()

      //遍历list1把以张开头的元素添加到list2中。
      ArrayList<String> list2 = new ArrayList<>();
      for (String s : list1) {
      if(s.startsWith("张")){
      list2.add(s);
      }
      }
      //遍历list2集合,把其中长度为3的元素,再添加到list3中。
      ArrayList<String> list3 = new ArrayList<>();
      for (String s : list2) {
      if(s.length() == 3){
      list3.add(s);
      }
      }
      for (String s : list3) {
      System.out.println(s);
      }
      }
      }
    3. 使用Stream流示例代码

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      public class StreamDemo {
      public static void main(String[] args) {
      //集合的批量添加
      ArrayList<String> list1 = new ArrayList<>(List.of("张三丰","张无忌","张翠山","王二麻子","张良","谢广坤"));

      //Stream流
      list1.stream().filter(s->s.startsWith("张"))
      .filter(s->s.length() == 3)
      .forEach(s-> System.out.println(s));
      }
      }
    4. Stream流的好处

      • 代码易读:获取流、过滤姓张、过滤长度为3、逐一打印
      • Stream流把真正的函数式编程风格引入到Java中
      • 代码简洁
  • Stream流的常见生成方式

    1. Stream流的三类方法

      • 获取Stream流
        • 创建一条流水线,并把数据放到流水线上准备进行操作
      • 中间方法
        • 流水线上的操作
        • 一次操作完毕之后,还可以继续进行其他操作
      • 终结方法
        • 一个Stream流只能有一个终结方法
        • 是流水线上的最后一个操作
    2. 生成Stream流的方式

      • Collection体系集合

        使用默认方法stream()生成流, default Stream stream()

      • Map体系集合

        把Map转成Set集合,间接的生成流

      • 数组

        通过Arrays中的静态方法stream生成流

      • 同种数据类型的多个数据

        通过Stream接口的静态方法of(T… values)生成流

    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
      public class StreamDemo {
      public static void main(String[] args) {
      //Collection体系的集合可以使用默认方法stream()生成流
      List<String> list = new ArrayList<String>();
      Stream<String> listStream = list.stream();

      Set<String> set = new HashSet<String>();
      Stream<String> setStream = set.stream();

      //Map体系的集合间接的生成流
      Map<String,Integer> map = new HashMap<String, Integer>();
      Stream<String> keyStream = map.keySet().stream();
      Stream<Integer> valueStream = map.values().stream();
      Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();

      //数组可以通过Arrays中的静态方法stream生成流
      String[] strArray = {"hello","world","java"};
      Stream<String> strArrayStream = Arrays.stream(strArray);

      //同种数据类型的多个数据可以通过Stream接口的静态方法of(T... values)生成流
      Stream<String> strArrayStream2 = Stream.of("hello", "world", "java");
      Stream<Integer> intStream = Stream.of(10, 20, 30);
      }
      }
  • Stream流中间操作方法

    1. 概念

      中间操作的意思是,执行完此方法之后,Stream流依然可以继续执行其他操作

    2. 常见方法

      方法名 说明
      Stream filter(Predicate predicate) 用于对流中的数据进行过滤
      Stream limit(long maxSize) 返回此流中的元素组成的流,截取前指定参数个数的数据
      Stream skip(long n) 跳过指定参数个数的数据,返回由该流的剩余元素组成的流
      static Stream concat(Stream a, Stream b) 合并a和b两个流为一个流
      Stream distinct() 返回由该流的不同元素(根据Object.equals(Object) )组成的流
      Stream map(Function<T ,R> mapper) 转换流中的数据类型

      注:

      • 中间方法,返回新的Stream流,原来的Stream流只能使用一次。
      • 修改Stream流中的数据,不会影响原来集合或数组中的数据。
    3. filter代码

      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
      public class MyStream3 {
      public static void main(String[] args) {
      // Stream<T> filter(Predicate predicate):过滤
      // Predicate接口中的方法 boolean test(T t):对给定的参数进行判断,返回一个布尔值

      ArrayList<String> list = new ArrayList<>();
      list.add("张三丰");
      list.add("张无忌");
      list.add("张翠山");
      list.add("王二麻子");
      list.add("张良");
      list.add("谢广坤");

      //filter方法获取流中的 每一个数据.
      //而test方法中的s,就依次表示流中的每一个数据.
      //我们只要在test方法中对s进行判断就可以了.
      //如果判断的结果为true,则当前的数据留下
      //如果判断的结果为false,则当前数据就不要.
      // list.stream().filter(
      // new Predicate<String>() {
      // @Override
      // public boolean test(String s) {
      // boolean result = s.startsWith("张");
      // return result;
      // }
      // }
      // ).forEach(s-> System.out.println(s));

      //因为Predicate接口中只有一个抽象方法test
      //所以我们可以使用lambda表达式来简化
      // list.stream().filter(
      // (String s)->{
      // boolean result = s.startsWith("张");
      // return result;
      // }
      // ).forEach(s-> System.out.println(s));

      list.stream().filter(s ->s.startsWith("张")).forEach(s-> System.out.println(s));

      }
      }
    4. limit&skip代码

      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
      public class StreamDemo02 {
      public static void main(String[] args) {
      //创建一个集合,存储多个字符串元素
      ArrayList<String> list = new ArrayList<String>();

      list.add("林青霞");
      list.add("张曼玉");
      list.add("王祖贤");
      list.add("柳岩");
      list.add("张敏");
      list.add("张无忌");

      //需求1:取前3个数据在控制台输出
      list.stream().limit(3)
      .forEach(s-> System.out.println(s));
      System.out.println("--------");

      //需求2:跳过3个元素,把剩下的元素在控制台输出
      list.stream().skip(3)
      .forEach(s-> System.out.println(s));
      System.out.println("--------");

      //需求3:跳过2个元素,把剩下的元素中前2个在控制台输出
      list.stream().skip(2).limit(2).forEach(s-> System.out.println(s));
      }
      }
    5. concat&distinct代码演示

      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 StreamDemo03 {
      public static void main(String[] args) {
      //创建一个集合,存储多个字符串元素
      ArrayList<String> list = new ArrayList<String>();

      list.add("林青霞");
      list.add("张曼玉");
      list.add("王祖贤");
      list.add("柳岩");
      list.add("张敏");
      list.add("张无忌");

      //需求1:取前4个数据组成一个流
      Stream<String> s1 = list.stream().limit(4);

      //需求2:跳过2个数据组成一个流
      Stream<String> s2 = list.stream().skip(2);

      //需求3:合并需求1和需求2得到的流,并把结果在控制台输出
      // Stream.concat(s1,s2).forEach(s-> System.out.println(s));

      //需求4:合并需求1和需求2得到的流,并把结果在控制台输出,要求字符串元素不能重复
      Stream.concat(s1,s2).distinct().forEach(s-> System.out.println(s));
      }
      }
    6. map

      1
      2
      // 张无忌-23
      list.stream().map(s -> Integer.parseInt(s.split("-")[1]));
  • Stream流终结操作方法

    1. 概念

      终结操作的意思是,执行完此方法之后,Stream流将不能再执行其他操作

    2. 常见方法

      方法名 说明
      void forEach(Consumer action) 对此流的每个元素执行操作
      long count() 返回此流中的元素数
      toArray() 收集流中的数据放入数组中
      collect(Collector collector) 收集流中的数据放入集合中
    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
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      public class MyStream5 {
      public static void main(String[] args) {
      ArrayList<String> list = new ArrayList<>();
      list.add("张三丰");
      list.add("张无忌");
      list.add("张翠山");
      list.add("王二麻子");
      list.add("张良");
      list.add("谢广坤");

      //method1(list);

      // long count():返回此流中的元素数
      long count = list.stream().count();
      System.out.println(count);
      }

      private static void method1(ArrayList<String> list) {
      // void forEach(Consumer action):对此流的每个元素执行操作
      // Consumer接口中的方法void accept(T t):对给定的参数执行此操作
      //在forEach方法的底层,会循环获取到流中的每一个数据.
      //并循环调用accept方法,并把每一个数据传递给accept方法
      //s就依次表示了流中的每一个数据.
      //所以,我们只要在accept方法中,写上处理的业务逻辑就可以了.
      list.stream().forEach(
      new Consumer<String>() {
      @Override
      public void accept(String s) {
      System.out.println(s);
      }
      }
      );

      System.out.println("====================");
      //lambda表达式的简化格式
      //是因为Consumer接口中,只有一个accept方法
      list.stream().forEach(
      (String s)->{
      System.out.println(s);
      }
      );
      System.out.println("====================");
      //lambda表达式还是可以进一步简化的.
      list.stream().forEach(s->System.out.println(s));
      }
      }
  • Stream流的收集操作

    1. 概念

      对数据使用Stream流的方式操作完毕后,可以把流中的数据收集到集合中

    2. 常用方法

      方法名 说明
      R collect(Collector collector) 把结果收集到集合中
    3. 工具类Collectors提供了具体的收集方式

      方法名 说明
      public static Collector toList() 把元素收集到List集合中
      public static Collector toSet() 把元素收集到Set集合中
      public static Collector toMap(Function keyMapper,Function valueMapper) 把元素收集到Map集合中
    4. 代码演示

      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
      // toList和toSet方法演示 
      public class MyStream7 {
      public static void main(String[] args) {
      ArrayList<Integer> list1 = new ArrayList<>();
      for (int i = 1; i <= 10; i++) {
      list1.add(i);
      }

      list1.add(10);
      list1.add(10);
      list1.add(10);
      list1.add(10);
      list1.add(10);

      //filter负责过滤数据的.
      //collect负责收集数据.
      //获取流中剩余的数据,但是他不负责创建容器,也不负责把数据添加到容器中.
      //Collectors.toList() : 在底层会创建一个List集合.并把所有的数据添加到List集合中.
      List<Integer> list = list1.stream().filter(number -> number % 2 == 0)
      .collect(Collectors.toList());

      System.out.println(list);

      Set<Integer> set = list1.stream().filter(number -> number % 2 == 0)
      .collect(Collectors.toSet());
      System.out.println(set);
      }
      }
      /**
      Stream流的收集方法 toMap方法演示
      创建一个ArrayList集合,并添加以下字符串。字符串中前面是姓名,后面是年龄
      "zhangsan,23"
      "lisi,24"
      "wangwu,25"
      保留年龄大于等于24岁的人,并将结果收集到Map集合中,姓名为键,年龄为值
      */
      public class MyStream8 {
      public static void main(String[] args) {
      ArrayList<String> list = new ArrayList<>();
      list.add("zhangsan,23");
      list.add("lisi,24");
      list.add("wangwu,25");

      Map<String, Integer> map = list.stream().filter(
      s -> {
      String[] split = s.split(",");
      int age = Integer.parseInt(split[1]);
      return age >= 24;
      }

      // collect方法只能获取到流中剩余的每一个数据.
      //在底层不能创建容器,也不能把数据添加到容器当中

      //Collectors.toMap 创建一个map集合并将数据添加到集合当中

      // s 依次表示流中的每一个数据

      //第一个lambda表达式就是如何获取到Map中的键
      //第二个lambda表达式就是如何获取Map中的值
      ).collect(Collectors.toMap(
      s -> s.split(",")[0],
      s -> Integer.parseInt(s.split(",")[1]) ));

      System.out.println(map);
      }
      }

8.方法引用

  • 方法引用的使用

    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
      public interface Printable {
      void printString(String s);
      }

      public class PrintableDemo {
      public static void main(String[] args) {
      //在主方法中调用usePrintable方法
      // usePrintable((String s) -> {
      // System.out.println(s);
      // });
      //Lambda简化写法
      usePrintable(s -> System.out.println(s));

      //方法引用
      usePrintable(System.out::println);

      }

      private static void usePrintable(Printable p) {
      p.printString("爱生活爱Java");
      }
      }

  • 方法引用符

    1. 方法引用符

      :: , 该符号为引用运算符,而它所在的表达式被称为方法引用

      1
      类名::静态方法
    2. 推导与省略

      • 如果使用Lambda,那么根据“可推导就是可省略”的原则,无需指定参数类型,也无需指定的重载形式,它们都将被自动推导
      • 如果使用方法引用,也是同样可以根据上下文进行推导
      • 方法引用是Lambda的孪生兄弟
  • 引用类方法

    引用类方法,其实就是引用类的静态方法

    1. 格式

      1
      类名::静态方法
    2. 范例

      1
      Integer::parseInt

      Integer类的方法:public static int parseInt(String s) 将此String转换为int类型数据

    3. 练习描述

      • 定义一个接口(Converter),里面定义一个抽象方法 int convert(String s);
      • 定义一个测试类(ConverterDemo),在测试类中提供两个方法
        • 一个方法是:useConverter(Converter c)
        • 一个方法是主方法,在主方法中调用useConverter方法
    4. 代码演示

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      public interface Converter {
      int convert(String s);
      }

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

      //Lambda写法
      useConverter(s -> Integer.parseInt(s));

      //引用类方法
      useConverter(Integer::parseInt);

      }

      private static void useConverter(Converter c) {
      int number = c.convert("666");
      System.out.println(number);
      }
      }
    5. 使用说明

      Lambda表达式被类方法替代的时候,它的形式参数全部传递给静态方法作为参数

  • 引用对象的实例方法

    引用对象的实例方法,其实就引用类中的成员方法

    1. 格式

      1
      对象::成员方法
    2. 范例

      1
      "HelloWorld"::toUpperCase

      String类中的方法:public String toUpperCase() 将此String所有字符转换为大写

    3. 练习描述

      • 定义一个类(PrintString),里面定义一个方法

        public void printUpper(String s):把字符串参数变成大写的数据,然后在控制台输出

      • 定义一个接口(Printer),里面定义一个抽象方法

        void printUpperCase(String s)

      • 定义一个测试类(PrinterDemo),在测试类中提供两个方法

        • 一个方法是:usePrinter(Printer p)
        • 一个方法是主方法,在主方法中调用usePrinter方法
    4. 代码演示

      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
      public class PrintString {
      //把字符串参数变成大写的数据,然后在控制台输出
      public void printUpper(String s) {
      String result = s.toUpperCase();
      System.out.println(result);
      }
      }

      public interface Printer {
      void printUpperCase(String s);
      }

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

      //Lambda简化写法
      usePrinter(s -> System.out.println(s.toUpperCase()));

      //引用对象的实例方法
      PrintString ps = new PrintString();
      usePrinter(ps::printUpper);

      }

      private static void usePrinter(Printer p) {
      p.printUpperCase("HelloWorld");
      }
      }

    5. 使用说明

      Lambda表达式被对象的实例方法替代的时候,它的形式参数全部传递给该方法作为参数

  • 引用类的实例方法

    引用类的实例方法,其实就是引用类中的成员方法

    1. 格式

      1
      类名::成员方法
    2. 范例

      1
      String::substring

      public String substring(int beginIndex,int endIndex)

      从beginIndex开始到endIndex结束,截取字符串。返回一个子串,子串的长度为endIndex-beginIndex

    3. 练习描述

      • 定义一个接口(MyString),里面定义一个抽象方法:

        String mySubString(String s,int x,int y);

      • 定义一个测试类(MyStringDemo),在测试类中提供两个方法

        • 一个方法是:useMyString(MyString my)
        • 一个方法是主方法,在主方法中调用useMyString方法
    4. 代码演示

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      public interface MyString {
      String mySubString(String s,int x,int y);
      }

      public class MyStringDemo {
      public static void main(String[] args) {
      //Lambda简化写法
      useMyString((s,x,y) -> s.substring(x,y));

      //引用类的实例方法
      useMyString(String::substring);

      }

      private static void useMyString(MyString my) {
      String s = my.mySubString("HelloWorld", 2, 5);
      System.out.println(s);
      }
      }
    5. 使用说明

      Lambda表达式被类的实例方法替代的时候
      ​第一个参数作为调用者
      ​后面的参数全部传递给该方法作为参数

  • 引用构造器

    引用构造器,其实就是引用构造方法

    1. l格式

      1
      类名::new
    2. 范例

      1
      Student::new
    3. 练习描述

      • 定义一个类(Student),里面有两个成员变量(name,age)

        并提供无参构造方法和带参构造方法,以及成员变量对应的get和set方法

      • 定义一个接口(StudentBuilder),里面定义一个抽象方法

        Student build(String name,int age);

      • 定义一个测试类(StudentDemo),在测试类中提供两个方法

        • 一个方法是:useStudentBuilder(StudentBuilder s)
        • 一个方法是主方法,在主方法中调用useStudentBuilder方法
    4. 代码演示

      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
      public class Student {
      private String name;
      private int age;

      public Student() {
      }

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

      public String getName() {
      return name;
      }

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

      public int getAge() {
      return age;
      }

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

      public interface StudentBuilder {
      Student build(String name,int age);
      }

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

      //Lambda简化写法
      useStudentBuilder((name,age) -> new Student(name,age));

      //引用构造器
      useStudentBuilder(Student::new);

      }

      private static void useStudentBuilder(StudentBuilder sb) {
      Student s = sb.build("林青霞", 30);
      System.out.println(s.getName() + "," + s.getAge());
      }
      }
    5. 使用说明

      Lambda表达式被构造器替代的时候,它的形式参数全部传递给构造器作为参数

  • 引用数组的构造方法

    1. 格式:

      1
      数据类型[]::new
    2. 范例:

      1
      int[]::new
    3. 例子

      1
      2
      3
      4
      5
      ArrayList<Integet> list = new ArrayList<>();
      Collections.addAll(list,1,2,3,4,5);

      Integer[] arr2 = list.stream().toArray(Integer::new);
      sout(Arrays.toString(arr2));

14.异常 & File & IO

1.异常

1.概念

  • 异常概念

    1. 异常 :指的是程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止。

    2. 在Java等面向对象编程语言,异常本身是一个类,产生异常会创建异常对象并抛出一个异常对象。Java处理异常的方式是中断处理。

      异常:不是语法错误,语法出错,编译不通过,不会产生字节码文件,而是根本不能运行.

  • 异常体系

    1. 异常机制:是帮助找到程序中的问题,异常的根类是java.lang.Throwable

      其有两个子类:java.lang.Errorjava.lang.Exception,平常说的异常指java.lang.Exception

      001

    2. Throwable体系:

      • Error:严重错误Error,无法通过处理的错误,只能事先避免。
      • Exception:表示异常,异常产生后程序员可以通过代码的方式纠正,使程序继续运行,是必须要处理的。
    3. Throwable中的常用方法:

      • public void printStackTrace():打印异常的详细信息。

        包含了异常的类型,异常的原因,还包括异常出现的位置,在开发和调试阶段,都得使用printStackTrace。

      • public String getMessage():获取发生异常的原因。

        提示给用户的时候,就提示错误原因。

      • public String toString():获取异常的类型和异常描述信息(不用)。

  • 异常分类

    1. 平常异常:指Exception,因为这类异常一旦出现,要对代码进行更正,修复程序。

    2. 异常(Exception)的分类

      • 编译时期异常:checked异常。在编译时期,就会检查,如果没有处理异常,则编译失败。(如日期格式化异常)

      • 运行时期异常:runtime异常。在运行时期,检查异常.在编译时期,运行异常不会编译器检测(不报错)。(如数学异常)

        001

2.抛出异常:throw

编写程序时,需要考虑程序出现问题的情况。如,在定义方法时,方法需要接受参数,当调用方法使用接受到的参数时,需要先对参数数据进行合法的判断,若数据不合法,则反馈调用者,传递合法的数据。此时需要使用抛出异常的方式来告诉调用者。

  • throw关键字,用来抛出一个指定的异常对象。

    1. 创建一个异常对象。封装一些提示信息(信息可以自己编写)。

    2. 需要将这个异常对象告知给调用者。throw 异常对象。

      throw用在方法内,用来抛出一个异常对象,将这个异常对象传递到调用者处,并结束当前方法的执行。

  • 使用格式:

    1
    throw new 异常类名(参数);

    例如:

    1
    2
    3
    throw new NullPointerException("要访问的arr数组不存在");

    throw new ArrayIndexOutOfBoundsException("该索引在数组中不存在,已超出范围");。
    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
    public class ThrowDemo {
    public static void main(String[] args) {
    //创建一个数组
    int[] arr = {2,4,52,2};
    //根据索引找对应的元素
    int index = 4;
    int element = getElement(arr, index);

    System.out.println(element);
    System.out.println("over");
    }
    /*
    * 根据 索引找到数组中对应的元素
    */
    public static int getElement(int[] arr,int index){
    //判断 索引是否越界
    if(index<0 || index>arr.length-1){
    /*
    判断条件如果满足,当执行完throw抛出异常对象后,方法已经无法继续运算。
    这时就会结束当前方法的执行,并将异常告知给调用者。这时就需要通过异常来解决。
    */
    throw new ArrayIndexOutOfBoundsException("哥们,角标越界了```");
    }
    int element = arr[index];
    return element;
    }
    }

    注意:如果产生了问题,我们就会throw将问题描述类即异常进行抛出,也就是将问题返回给该方法的调用者。

    处理方式:一种是进行捕获处理,另一种就是继续讲问题声明出去,使用throws声明处理。

3.声明异常:throws

  • 声明异常:将问题标识出来,报告给调用者。如果方法内通过throw抛出了编译时异常,而没有捕获处理,那么必须通过throws进行声明,让调用者去处理。

    关键字throws运用于方法声明之上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常(抛出异常).

  • 声明异常格式:

    1
    修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2…{   }	

    声明异常的代码演示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class ThrowsDemo {
    public static void main(String[] args) throws FileNotFoundException {
    read("a.txt");
    }

    // 如果定义功能时有问题发生需要报告给调用者。可以通过在方法上使用throws关键字进行声明
    public static void read(String path) throws FileNotFoundException {
    if (!path.equals("a.txt")) {//如果不是 a.txt这个文件
    // 我假设 如果不是 a.txt 认为 该文件不存在 是一个错误 也就是异常 throw
    throw new FileNotFoundException("文件不存在");
    }
    }
    }

    throws用于进行异常类的声明,若该方法可能有多种异常情况产生,那么在throws后面可以写多个异常类,用逗号隔开。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class ThrowsDemo2 {
    public static void main(String[] args) throws IOException {
    read("a.txt");
    }

    public static void read(String path)throws FileNotFoundException, IOException {
    if (!path.equals("a.txt")) {//如果不是 a.txt这个文件
    // 我假设 如果不是 a.txt 认为 该文件不存在 是一个错误 也就是异常 throw
    throw new FileNotFoundException("文件不存在");
    }
    if (!path.equals("b.txt")) {
    throw new IOException();
    }
    }
    }

4.捕获异常:try…catch...finally

如果异常出现的话,会立刻终止程序,所以得处理异常:

  1. 方法不处理,而是声明抛出,由该方法的调用者来处理(throws)。
  2. 在方法中使用try-catch的语句块来处理异常。
  • try-catch的方式就是捕获异常。

    1. 捕获异常:Java中对异常有针对性的语句进行捕获,可以对出现的异常进行指定方式的处理。

      捕获异常语法:

      1
      2
      3
      4
      5
      6
      try{
      编写可能会出现异常的代码
      }catch(异常类型 e){
      处理异常的代码
      //记录日志/打印异常信息/继续抛出异常
      }

      try:该代码块中编写可能产生异常的代码。

      catch:用来进行某种异常的捕获,实现对捕获到的异常进行处理。

      注意:try和catch都不能单独使用,必须连用。

  • 演示如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class TryCatchDemo {
    public static void main(String[] args) {
    try {// 当产生异常时,必须有处理方式。要么捕获,要么声明。
    read("b.txt");
    } catch (FileNotFoundException e) {// 括号中需要定义什么呢?
    //try中抛出的是什么异常,在括号中就定义什么异常类型
    System.out.println(e);
    }
    System.out.println("over");
    }
    /*
    *
    * 我们 当前的这个方法中 有异常 有编译期异常
    */
    public static void read(String path) throws FileNotFoundException {
    if (!path.equals("a.txt")) {//如果不是 a.txt这个文件
    // 我假设 如果不是 a.txt 认为 该文件不存在 是一个错误 也就是异常 throw
    throw new FileNotFoundException("文件不存在");
    }
    }
    }
  • 如何获取异常信息:

    1. Throwable类中定义了一些查看方法:

      • public String getMessage():获取异常的描述信息,原因(提示给用户的时候,就提示错误原因。
      • public String toString():获取异常的类型和异常描述信息(不用)。
      • public void printStackTrace():打印异常的跟踪栈信息并输出到控制台。
    2. 在catch将编译期异常转换成运行期异常处理。

      多个异常使用捕获处理:

      1. 多个异常分别处理。
      2. 多个异常一次捕获,多次处理。
      3. 多个异常一次捕获一次处理。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      try{
      编写可能会出现异常的代码
      }catch(异常类型A e){ 当try中出现A类型异常,就用该catch来捕获.
      处理异常的代码
      //记录日志/打印异常信息/继续抛出异常
      }catch(异常类型B e){ 当try中出现B类型异常,就用该catch来捕获.
      处理异常的代码
      //记录日志/打印异常信息/继续抛出异常
      }

      注意:这种异常处理方式,要求多个catch中的异常不能相同,并且若catch中的多个异常之间有子父类异常的关系,那么子类异常要求在上面的catch处理,父类异常在下面的catch处理。

  • finally

    在finally代码块中存放的代码都是一定会被执行的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class TryCatchDemo4 {
    public static void main(String[] args) {
    try {
    read("a.txt");
    } catch (FileNotFoundException e) {
    //抓取到的是编译期异常 抛出去的是运行期
    throw new RuntimeException(e);
    } finally {
    System.out.println("不管程序怎样,这里都将会被执行。");
    }
    System.out.println("over");
    }
    /*
    *
    * 我们 当前的这个方法中 有异常 有编译期异常
    */
    public static void read(String path) throws FileNotFoundException {
    if (!path.equals("a.txt")) {//如果不是 a.txt这个文件
    // 我假设 如果不是 a.txt 认为 该文件不存在 是一个错误 也就是异常 throw
    throw new FileNotFoundException("文件不存在");
    }
    }
    }

    当只有在try或者catch中调用退出JVM的相关方法,此时finally才不会执行,否则finally永远会执行。

  • 异常注意事项

    1. 运行时异常被抛出可以不处理。即不捕获也不声明抛出。
    2. 如果父类抛出了多个异常,子类覆盖父类方法时,只能抛出相同的异常或者是他的子集。
    3. 父类方法没有抛出异常,子类覆盖父类该方法时也不可抛出异常。此时子类产生该异常,只能捕获处理,不能声明抛出
    4. 当多异常处理时,捕获处理,前边的类不能是后边类的父类
    5. 在try/catch后可以追加finally代码块,其中的代码一定会被执行,通常用于资源回收。

5.自定义异常类

  • 自定义异常类:

    在开发中根据自己业务的异常情况来定义异常类.

    自定义一个业务逻辑异常: LoginException。一个登陆异常类。

  • 异常类如何定义:

    1. 自定义一个编译期异常: 自定义类 并继承于java.lang.Exception
    2. 自定义一个运行时期的异常类:自定义类 并继承于java.lang.RuntimeException
  • 自定义异常的练习

    要求:我们模拟登陆操作,如果用户名已存在,则抛出异常并提示:亲,该用户名已经被注册。

    首先定义一个登陆异常类LoginException:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 业务逻辑异常
    public class LoginException extends Exception {
    /**
    * 空参构造
    */
    public LoginException() {
    }

    /**
    *
    * @param message 表示异常提示
    */
    public LoginException(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
    public class Demo {
    // 模拟数据库中已存在账号
    private static String[] names = {"bill","hill","jill"};

    public static void main(String[] args) {
    //调用方法
    try{
    // 可能出现异常的代码
    checkUsername("nill");
    System.out.println("注册成功");//如果没有异常就是注册成功
    } catch(LoginException e) {
    //处理异常
    e.printStackTrace();
    }
    }

    //判断当前注册账号是否存在
    //因为是编译期异常,又想调用者去处理 所以声明该异常
    public static boolean checkUsername(String uname) throws LoginException {
    for (String name : names) {
    if(name.equals(uname)){//如果名字在这里面 就抛出登陆异常
    throw new LoginException("亲"+name+"已经被注册了!");
    }
    }
    return true;
    }
    }

2.File类

java.io.File 类是文件和目录路径名的抽象表示,主要用于文件和目录的创建、查找和删除等操作。

  • 构造方法

    1. public File(String pathname) :通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。
    2. public File(String parent, String child) :从父路径名字符串和子路径名字符串创建新的 File实例。
    3. public File(File parent, String child) :从父抽象路径名和子路径名字符串创建新的 File实例。
  • 构造举例,代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 文件路径名
    String pathname = "D:\\aaa.txt";
    File file1 = new File(pathname);

    // 文件路径名
    String pathname2 = "D:\\aaa\\bbb.txt";
    File file2 = new File(pathname2);

    // 通过父路径和子路径字符串
    String parent = "d:\\aaa";
    String child = "bbb.txt";
    File file3 = new File(parent, child);

    // 通过父级File对象和子路径字符串
    File parentDir = new File("d:\\aaa");
    String child = "bbb.txt";
    File file4 = new File(parentDir, child);

    注:

    1. 一个File对象代表硬盘中实际存在的一个文件或者目录。
    2. 无论该路径下是否存在文件或者目录,都不影响File对象的创建。

1.常用方法

  • 获取功能的方法

    1. public String getAbsolutePath() :返回此File的绝对路径名字符串。

    2. public String getPath() :将此File转换为路径名字符串。

    3. public String getName() :返回由此File表示的文件或目录的名称。

    4. public long length() :返回由此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
    public class FileGet {
    public static void main(String[] args) {
    File f = new File("d:/aaa/bbb.java");
    System.out.println("文件绝对路径:"+f.getAbsolutePath());
    System.out.println("文件构造路径:"+f.getPath());
    System.out.println("文件名称:"+f.getName());
    System.out.println("文件长度:"+f.length()+"字节");

    File f2 = new File("d:/aaa");
    System.out.println("目录绝对路径:"+f2.getAbsolutePath());
    System.out.println("目录构造路径:"+f2.getPath());
    System.out.println("目录名称:"+f2.getName());
    System.out.println("目录长度:"+f2.length());
    }
    }
    输出结果:
    文件绝对路径:d:\aaa\bbb.java
    文件构造路径:d:\aaa\bbb.java
    文件名称:bbb.java
    文件长度:636字节

    目录绝对路径:d:\aaa
    目录构造路径:d:\aaa
    目录名称:aaa
    目录长度:4096

    API中说明:length(),表示文件的长度。但是File对象表示目录,则返回值未指定。

  • 绝对路径和相对路径

    1. 绝对路径:从盘符开始的路径,这是一个完整的路径。
    2. 相对路径:相对于项目目录的路径,这是一个便捷的路径,开发中经常使用。
  • 判断功能的方法

    1. public boolean exists() :此File表示的文件或目录是否实际存在。
    2. public boolean isDirectory() :此File表示的是否为目录。
    3. public boolean isFile() :此File表示的是否为文件。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class FileIs {
    public static void main(String[] args) {
    File f = new File("d:\\aaa\\bbb.java");
    File f2 = new File("d:\\aaa");
    // 判断是否存在
    System.out.println("d:\\aaa\\bbb.java 是否存在:"+f.exists());
    System.out.println("d:\\aaa 是否存在:"+f2.exists());
    // 判断是文件还是目录
    System.out.println("d:\\aaa 文件?:"+f2.isFile());
    System.out.println("d:\\aaa 目录?:"+f2.isDirectory());
    }
    }
    输出结果:
    d:\aaa\bbb.java 是否存在:true
    d:\aaa 是否存在:true
    d:\aaa 文件?:false
    d:\aaa 目录?:true
  • 创建删除功能的方法

    1. public boolean createNewFile() :当且仅当具有该名称的文件尚不存在时,创建一个新的空文件(父级路径不存在报错)。
    2. public boolean delete() :删除由此File表示的文件或目录。
    3. public boolean mkdir() :创建由此File表示的目录。
    4. public boolean mkdirs() :创建由此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
    public class FileCreateDelete {
    public static void main(String[] args) throws IOException {
    // 文件的创建
    File f = new File("aaa.txt");
    System.out.println("是否存在:"+f.exists()); // false
    System.out.println("是否创建:"+f.createNewFile()); // true
    System.out.println("是否存在:"+f.exists()); // true

    // 目录的创建
    File f2= new File("newDir");
    System.out.println("是否存在:"+f2.exists());// false
    System.out.println("是否创建:"+f2.mkdir()); // true
    System.out.println("是否存在:"+f2.exists());// true

    // 创建多级目录
    File f3= new File("newDira\\newDirb");
    System.out.println(f3.mkdir());// false
    File f4= new File("newDira\\newDirb");
    System.out.println(f4.mkdirs());// true

    // 文件的删除
    System.out.println(f.delete());// true

    // 目录的删除
    System.out.println(f2.delete());// true
    System.out.println(f4.delete());// false
    }
    }

    API中说明:delete方法,如果此File表示目录,则目录必须为空才能删除。

2.目录的遍历

  • public String[] list() :返回一个String数组,表示该File目录中的所有子文件或目录。
  • public File[] listFiles() :返回一个File数组,表示该File目录中的所有的子文件或目录。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class FileFor {
public static void main(String[] args) {
File dir = new File("d:\\java_code");

//获取当前目录下的文件以及文件夹的名称。
String[] names = dir.list();
for(String name : names){
System.out.println(name);
}
//获取当前目录下的文件以及文件夹对象,只要拿到了文件对象,那么就可以获取更多信息
File[] files = dir.listFiles();
for (File file : files) {
System.out.println(file);
}
}
}

小贴士:

调用listFiles方法的File对象,表示的必须是实际存在的目录,否则返回null,无法进行遍历。

3.IO

1.IO概述

  • 什么是IO:

    1. 数据的传输:以内存为基准,分为输入input输出output ,即流向内存是输入流,流出内存的输出流。
    2. Java中I/O操作:主要是指使用java.io包下的内容,进行输入、输出操作。输入也叫做读取数据,输出也叫做作写出数据。
  • IO的分类

    1. 根据数据的流向分为:输入流输出流
      • 输入流 :把数据从其他设备上读取到内存中的流。
      • 输出流 :把数据从内存 中写出到其他设备上的流。
    2. 根据数据的类型分为:字节流字符流
      • 字节流 :以字节为单位,读写数据的流。
      • 字符流 :以字符为单位,读写数据的流。
  • 顶级父类(抽象类)

    输入流 输出流
    字节流 字节输入流
    InputStream
    字节输出流
    OutputStream
    字符流 字符输入流
    Reader
    字符输出流
    Writer

2.字节流

一切皆为字节:一切数据都是以二进制数字的形式保存,所以字节流可以传输任意文件数据。

1.OutputStream & InputStream

  • OutputStream

    java.io.OutputStream 抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。

    1. public void close() :关闭此输出流并释放与此流相关联的任何系统资源。
    2. public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出。
    3. public void write(byte[] b):将 b.length字节从指定的字节数组写入此输出流。
    4. public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。
    5. public abstract void write(int b) :将指定的字节输出流。

    注:当完成流的操作时,必须调用close方法,释放系统资源。

  • InputStream

    java.io.InputStream 抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。

    1. public void close() :关闭此输入流并释放与此流相关联的任何系统资源。
    2. public abstract int read(): 从输入流读取数据的下一个字节。
    3. public int read(byte[] b): 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。

    注:当完成流的操作时,必须调用close方法,释放系统资源。

2.FileOutputStream

OutputStream有很多子类

java.io.FileOutputStream 类是文件输出流,用于将数据写出到文件。

  • 构造方法

    1. public FileOutputStream(File file):创建文件输出流以写入由指定的 File对象表示的文件。
    2. public FileOutputStream(String name): 创建文件输出流以指定的名称写入文件。

    创建流对象时,必须传入一个文件路径。若没有该文件,则创建文件。若有该文件,则清空文件数据。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class FileOutputStreamConstructor throws IOException {
    public static void main(String[] args) {
    // 使用File对象创建流对象
    File file = new File("a.txt");
    FileOutputStream fos = new FileOutputStream(file);

    // 使用文件名称创建流对象
    FileOutputStream fos = new FileOutputStream("b.txt");
    }
    }
  • 写出字节数据

    1. 写出字节write(int b) 方法,每次可以写出一个字节数据:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      public class FOSWrite {
      public static void main(String[] args) throws IOException {
      // 使用文件名称创建流对象
      FileOutputStream fos = new FileOutputStream("fos.txt");
      // 写出数据
      fos.write(97); // 写出第1个字节
      fos.write(98); // 写出第2个字节
      fos.write(99); // 写出第3个字节
      // 关闭资源
      fos.close();
      }
      }
      输出结果:
      abc
    2. 写出字节数组write(byte[] b),每次可以写出数组中的数据:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      public class FOSWrite {
      public static void main(String[] args) throws IOException {
      // 使用文件名称创建流对象
      FileOutputStream fos = new FileOutputStream("fos.txt");
      // 字符串转换为字节数组
      byte[] b = "黑马程序员".getBytes();
      // 写出字节数组数据
      fos.write(b);
      // 关闭资源
      fos.close();
      }
      }
      输出结果:
      黑马程序员
    3. 写出指定长度字节数组write(byte[] b, int off, int len) ,每次写出从off索引开始,len个字节:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      public class FOSWrite {
      public static void main(String[] args) throws IOException {
      // 使用文件名称创建流对象
      FileOutputStream fos = new FileOutputStream("fos.txt");
      // 字符串转换为字节数组
      byte[] b = "abcde".getBytes();
      // 写出从索引2开始,2个字节。索引2是c,两个字节,也就是cd。
      fos.write(b,2,2);
      // 关闭资源
      fos.close();
      }
      }
      输出结果:
      cd
  • 数据追加续写

    1. public FileOutputStream(File file, boolean append): 创建文件输出流以写入由指定的 File对象表示的文件。
    2. public FileOutputStream(String name, boolean append): 创建文件输出流以指定的名称写入文件。

    两个构造方法中,true 表示追加数据,false 表示清空原有数据。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class FOSWrite {
    public static void main(String[] args) throws IOException {
    // 使用文件名称创建流对象
    FileOutputStream fos = new FileOutputStream("fos.txt"true);
    // 字符串转换为字节数组
    byte[] b = "abcde".getBytes();
    // 写出从索引2开始,2个字节。索引2是c,两个字节,也就是cd。
    fos.write(b);
    // 关闭资源
    fos.close();
    }
    }
    文件操作前:cd
    文件操作后:cdabcde
  • 写出换行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public class FOSWrite {
    public static void main(String[] args) throws IOException {
    // 使用文件名称创建流对象
    FileOutputStream fos = new FileOutputStream("fos.txt");
    // 定义字节数组
    byte[] words = {97,98,99,100,101};
    // 遍历数组
    for (int i = 0; i < words.length; i++) {
    // 写出一个字节
    fos.write(words[i]);
    // 写出一个换行, 换行符号转成数组写出
    fos.write("\r\n".getBytes());
    }
    // 关闭资源
    fos.close();
    }
    }

    输出结果:
    a
    b
    c
    d
    e
    • 回车符\r和换行符\n
      • 回车符:回到一行的开头(return)。
      • 换行符:下一行(newline)。
    • 系统中的换行:
      • Windows系统里,每行结尾是 回车+换行 ,即\r\n
      • Unix系统里,每行结尾只有 换行 ,即\n
      • Mac系统里,每行结尾是 回车 ,即\r。从 Mac OS X开始与Linux统一。

3.FileInputStream

java.io.FileInputStream 类是文件输入流,从文件中读取字节。

  • 构造方法

    1. FileInputStream(File file): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。
    2. FileInputStream(String name): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。

    若没有路径中的文件,会抛出FileNotFoundException

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class FileInputStreamConstructor throws IOException{
    public static void main(String[] args) {
    // 使用File对象创建流对象
    File file = new File("a.txt");
    FileInputStream fos = new FileInputStream(file);

    // 使用文件名称创建流对象
    FileInputStream fos = new FileInputStream("b.txt");
    }
    }
  • 读取字节数据

    1. 读取字节read方法,每次可读一个字节,并提升为int类型,读取到文件末尾,返回-1

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      public class FISRead {
      public static void main(String[] args) throws IOException{
      // 使用文件名称创建流对象
      FileInputStream fis = new FileInputStream("read.txt");
      // 定义变量,保存数据
      int b ;
      // 循环读取
      while ((b = fis.read())!=-1) {
      System.out.println((char)b);
      }
      // 关闭资源
      fis.close();
      }
      }
      输出结果:
      a
      b
      c
      d
      e

      注:

      1. 虽然读取了一个字节,但是会自动提升为int类型。
      2. 流操作完毕后,必须释放系统资源,调用close方法。
    2. 使用字节数组读取read(byte[] b),每次读取b的长度个字节到数组中,返回读取到的有效字节个数,读取到末尾时,返回-1

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      public class FISRead {
      public static void main(String[] args) throws IOException{
      // 使用文件名称创建流对象.
      FileInputStream fis = new FileInputStream("read.txt"); // 文件中为abcde
      // 定义变量,作为有效个数
      int len ;
      // 定义字节数组,作为装字节数据的容器
      byte[] b = new byte[2];
      // 循环读取
      while (( len= fis.read(b))!=-1) {
      // 每次读取后,把数组变成字符串打印
      System.out.println(new String(b));
      }
      // 关闭资源
      fis.close();
      }
      }

      输出结果:
      ab
      cd
      ed

      最后一行有错误数据d,在最后一次读取时,只读取一个字节e,数组中,上次读取的数据没有被完全替换,所以要通过len ,获取有效的字节:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      public class FISRead {
      public static void main(String[] args) throws IOException{
      // 使用文件名称创建流对象.
      FileInputStream fis = new FileInputStream("read.txt"); // 文件中为abcde
      // 定义变量,作为有效个数
      int len ;
      // 定义字节数组,作为装字节数据的容器
      byte[] b = new byte[2];
      // 循环读取
      while (( len= fis.read(b))!=-1) {
      // 每次读取后,把数组的有效字节部分,变成字符串打印
      System.out.println(new String(b,0,len));// len 每次读取的有效字节个数
      }
      // 关闭资源
      fis.close();
      }
      }

      输出结果:
      ab
      cd
      e

      注:使用数组读取,每次读取多个字节,减少了系统间的IO操作次数,从而提高了读写的效率,建议开发中使用。

  • 字节流练习:图片复制

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public class Copy {
    public static void main(String[] args) throws IOException {
    // 1.创建流对象
    // 1.1 指定数据源
    FileInputStream fis = new FileInputStream("D:\\test.jpg");
    // 1.2 指定目的地
    FileOutputStream fos = new FileOutputStream("test_copy.jpg");

    // 2.读写数据
    // 2.1 定义数组
    byte[] b = new byte[1024];
    // 2.2 定义长度
    int len;
    // 2.3 循环读取
    while ((len = fis.read(b))!=-1) {
    // 2.4 写出数据
    fos.write(b, 0 , len);
    }

    // 3.关闭资源
    fos.close();
    fis.close();
    }
    }

    注:

    流的关闭原则:先开后关,后开先关。

3.字符流

字节流读取文本文件时,遇到中文字符,可能不会显示完整的字符,因为一个中文字符可能占用多个字节存储。所以字符流类,以字符为单位读写数据,专门处理文本文件。

  • 字符集:

    1. ASCII:1字节
    2. GBK(系统显示ANSI):英文1字节、中文2字节。(中文1开头,英文0开头)
    3. Unicode:UTF-8编码规则(1-4可变字节保存,英文1字节、中日韩3字节)
  • 编码&解码

    1. 编码:文字 -> 二进制

      1
      2
      public byte[] getBytes()
      public byte[] getBytes(String charsetName)
    2. 解码:二进制 -> 文字

      1
      2
      String(byte[] bytes)
      String(byte[] bytes,String charsetName)

1.Reader & Writer

  • Reader:字符输入流

    java.io.Reader抽象类是表示用于读取字符流的所有类的超类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法。

    1. public void close() :关闭此流并释放与此流相关联的任何系统资源。
    2. public int read(): 从输入流读取一个字符。
    3. public int read(char[] cbuf): 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中 。
  • Writer:字符输出流

    java.io.Writer 抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。它定义了字节输出流的基本共性功能方法。

    1. void write(int c) 写入单个字符。
    2. void write(char[] cbuf) 写入字符数组。
    3. abstract void write(char[] cbuf, int off, int len) 写入字符数组的某一部分,off数组的开始索引,len写的字符个数。
    4. void write(String str) 写入字符串。
    5. void write(String str, int off, int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数。
    6. void flush() 刷新该流的缓冲。
    7. void close() 关闭此流,但要先刷新它。

2.FileReader

  • java.io.FileReader 类是读取字符文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。

  • 字符编码:字节与字符的对应规则。 Windows系统的中文编码默认是GBK编码表。 idea中UTF-8

  • 字节缓冲区:一个字节数组,用来临时存储字节数据。

  • 构造方法

    1. FileReader(File file): 创建一个新的 FileReader ,给定要读取的File对象。
    2. FileReader(String fileName): 创建一个新的 FileReader ,给定要读取的文件的名称。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class FileReaderConstructor throws IOException{
    public static void main(String[] args) {
    // 使用File对象创建流对象
    File file = new File("a.txt");
    FileReader fr = new FileReader(file);

    // 使用文件名称创建流对象
    FileReader fr = new FileReader("b.txt");
    }
    }
  • 读取字符数据

    1. 读取字符read方法,每次可读取一个字符的数据,提升为int类型,读取到文件末尾,返回-1

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      public class FRRead {
      public static void main(String[] args) throws IOException {
      // 使用文件名称创建流对象
      FileReader fr = new FileReader("read.txt");
      // 定义变量,保存数据
      int b ;
      // 循环读取
      while ((b = fr.read())!=-1) {
      System.out.println((char)b);
      }
      // 关闭资源
      fr.close();
      }
      }
      输出结果:





      注:虽然读取了一个字符,但是会自动提升为int类型。

    2. 字符数组读取read(char[] cbuf),每次读取b的长度个字符到数组中,返回读取到的有效字符个数,读取到末尾时,返回-1 ,代码使用演示:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      public class FRRead {
      public static void main(String[] args) throws IOException {
      // 使用文件名称创建流对象
      FileReader fr = new FileReader("read.txt");
      // 定义变量,保存有效字符个数
      int len ;
      // 定义字符数组,作为装字符数据的容器
      char[] cbuf = new char[2];
      // 循环读取
      while ((len = fr.read(cbuf))!=-1) {
      System.out.println(new String(cbuf));
      }
      // 关闭资源
      fr.close();
      }
      }
      输出结果:
      黑马
      程序
      员序

      获取有效的字符改进:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      public class FISRead {
      public static void main(String[] args) throws IOException {
      // 使用文件名称创建流对象
      FileReader fr = new FileReader("read.txt");
      // 定义变量,保存有效字符个数
      int len ;
      // 定义字符数组,作为装字符数据的容器
      char[] cbuf = new char[2];
      // 循环读取
      while ((len = fr.read(cbuf))!=-1) {
      System.out.println(new String(cbuf,0,len));
      }
      // 关闭资源
      fr.close();
      }
      }

      输出结果:
      黑马
      程序

3.FileWriter

java.io.FileWriter 类是写出字符到文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。

  • 构造方法

    1. FileWriter(File file): 创建一个新的 FileWriter,给定要读取的File对象。
    2. FileWriter(String fileName): 创建一个新的 FileWriter,给定要读取的文件的名称。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class FileWriterConstructor {
    public static void main(String[] args) throws IOException {
    // 使用File对象创建流对象
    File file = new File("a.txt");
    FileWriter fw = new FileWriter(file);

    // 使用文件名称创建流对象
    FileWriter fw = new FileWriter("b.txt");
    }
    }
  • 写出数据

    1. 写出字符write(int b) 方法,每次可以写出一个字符数据,代码使用演示:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      public class FWWrite {
      public static void main(String[] args) throws IOException {
      // 使用文件名称创建流对象
      FileWriter fw = new FileWriter("fw.txt");
      // 写出数据
      fw.write(97); // 写出第1个字符
      fw.write('b'); // 写出第2个字符
      fw.write('C'); // 写出第3个字符
      fw.write(30000); // 写出第4个字符,中文编码表中30000对应一个汉字。

      /*
      【注意】关闭资源时,与FileOutputStream不同。
      如果不关闭,数据只是保存到缓冲区,并未保存到文件。
      */
      // fw.close();
      }
      }
      输出结果:
      abC田

      注:

      1. 虽然参数为int类型四个字节,但是只会保留一个字符的信息写出。
      2. 未调用close方法,数据只是保存到了缓冲区,并未写出到文件中。
  • 关闭和刷新

    因为内置缓冲区的原因,若不关闭输出流,无法写出字符到文件,若关闭流对象,则无法继续写出数据。flush 方法既能写出数据,又能继续使用流。

    1. flush :刷新缓冲区,流对象可以继续使用。
    2. close :先刷新缓冲区,然后通知系统释放资源。流对象不可再被使用。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class FWWrite {
    public static void main(String[] args) throws IOException {
    // 使用文件名称创建流对象
    FileWriter fw = new FileWriter("fw.txt");
    // 写出数据,通过flush
    fw.write('刷'); // 写出第1个字符
    fw.flush();
    fw.write('新'); // 继续写出第2个字符,写出成功
    fw.flush();

    // 写出数据,通过close
    fw.write('关'); // 写出第1个字符
    fw.close();
    fw.write('闭'); // 继续写出第2个字符,【报错】java.io.IOException: Stream closed
    fw.close();
    }
    }

    注:即便是flush方法写出了数据,操作的最后还是要调用close方法,释放系统资源。

  • 写出其他数据

    1. 写出字符数组write(char[] cbuf)write(char[] cbuf, int off, int len) ,每次可写出字符数组中的数据,用法类似FileOutputStream

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      public class FWWrite {
      public static void main(String[] args) throws IOException {
      // 使用文件名称创建流对象
      FileWriter fw = new FileWriter("fw.txt");
      // 字符串转换为字节数组
      char[] chars = "黑马程序员".toCharArray();

      // 写出字符数组
      fw.write(chars); // 黑马程序员

      // 写出从索引2开始,2个字节。索引2是'程',两个字节,也就是'程序'。
      fw.write(b,2,2); // 程序

      // 关闭资源
      fos.close();
      }
      }
    2. 写出字符串write(String str)write(String str, int off, int len) ,每次可以写出字符串中的数据:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      public class FWWrite {
      public static void main(String[] args) throws IOException {
      // 使用文件名称创建流对象
      FileWriter fw = new FileWriter("fw.txt");
      // 字符串
      String msg = "黑马程序员";

      // 写出字符数组
      fw.write(msg); //黑马程序员

      // 写出从索引2开始,2个字符。索引2是'程',两个字符,也就是'程序'。
      fw.write(msg,2,2); // 程序

      // 关闭资源
      fos.close();
      }
      }
    3. 续写和换行:操作类似于FileOutputStream。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      public class FWWrite {
      public static void main(String[] args) throws IOException {
      // 使用文件名称创建流对象,可以续写数据
      FileWriter fw = new FileWriter("fw.txt"true);
      // 写出字符串
      fw.write("黑马");
      // 写出换行
      fw.write("\r\n");
      // 写出字符串
      fw.write("程序员");
      // 关闭资源
      fw.close();
      }
      }
      输出结果:
      黑马
      程序员

      注:字符流,只能操作文本文件,不能操作图片,视频等非文本文件。若单独读写文本文件时使用字符流 其他情况使用字节流。

4.IO异常的处理

只有实现了AutoCloseable接口的类才能在小括号中创建对象

  • JDK7前处理(try-catch-finally)

    实际开发中使用try-catch-finally 代码块处理异常部分:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public class HandleException1 {
    public static void main(String[] args) {
    // 声明变量
    FileWriter fw = null;
    try {
    //创建流对象
    fw = new FileWriter("fw.txt");
    // 写出数据
    fw.write("黑马程序员"); //黑马程序员
    } catch (IOException e) {
    e.printStackTrace();
    } finally {
    try {
    if (fw != null) {
    fw.close();
    }
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    }
    }
  • JDK7的处理(try-with-resource)

    JDK7优化后的try-with-resource 语句,确保了每个资源在语句结束时关闭。所谓的资源(resource)是指在程序完成后,必须关闭的对象。

    1. 格式:

      1
      2
      3
      4
      5
      try (创建流对象语句,如果多个,使用';'隔开) {
      // 读写数据
      } catch (IOException e) {
      e.printStackTrace();
      }
    2. 代码:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      public class HandleException2 {
      public static void main(String[] args) {
      // 创建流对象
      try ( FileWriter fw = new FileWriter("fw.txt"); ) {
      // 写出数据
      fw.write("黑马程序员"); //黑马程序员
      } catch (IOException e) {
      e.printStackTrace();
      }
      }
      }
  • JDK9的改进(扩展知识点了解内容)

    JDK9中try-with-resource 的改进,被引入的对象,可以自动关闭,无需手动close。

    1. 改进前格式:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      // 被final修饰的对象
      final Resource resource1 = new Resource("resource1");
      // 普通对象
      Resource resource2 = new Resource("resource2");
      // 引入方式:创建新的变量保存
      try (Resource r1 = resource1;
      Resource r2 = resource2) {
      // 使用对象
      }
    2. 改进后格式:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      // 被final修饰的对象
      final Resource resource1 = new Resource("resource1");
      // 普通对象
      Resource resource2 = new Resource("resource2");

      // 引入方式:直接引入
      try (resource1; resource2) {
      // 使用对象
      }
    3. 改进后代码:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      public class TryDemo {
      public static void main(String[] args) throws IOException {
      // 创建流对象
      final FileReader fr = new FileReader("in.txt");
      FileWriter fw = new FileWriter("out.txt");
      // 引入到try中
      try (fr; fw) {
      // 定义变量
      int b;
      // 读取数据
      while ((b = fr.read())!=-1) {
      // 写出数据
      fw.write(b);
      }
      } catch (IOException e) {
      e.printStackTrace();
      }
      }
      }

5.综合练习

  • 练习1:拷贝文件夹

    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
    public class Test01 {
    public static void main(String[] args) throws IOException {
    //拷贝一个文件夹,考虑子文件夹

    //1.创建对象表示数据源
    File src = new File("D:\\aaa\\src");
    //2.创建对象表示目的地
    File dest = new File("D:\\aaa\\dest");

    //3.调用方法开始拷贝
    copydir(src,dest);

    }

    /*
    * 作用:拷贝文件夹
    * 参数一:数据源
    * 参数二:目的地
    *
    * */
    private static void copydir(File src, File dest) throws IOException {
    dest.mkdirs();
    //递归
    //1.进入数据源
    File[] files = src.listFiles();
    //2.遍历数组
    for (File file : files) {
    if(file.isFile()){
    //3.判断文件,拷贝
    FileInputStream fis = new FileInputStream(file);
    FileOutputStream fos = new FileOutputStream(new File(dest,file.getName()));
    byte[] bytes = new byte[1024];
    int len;
    while((len = fis.read(bytes)) != -1){
    fos.write(bytes,0,len);
    }
    fos.close();
    fis.close();
    }else {
    //4.判断文件夹,递归
    copydir(file, new File(dest,file.getName()));
    }
    }
    }
    }

  • 练习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
    public class Test02 {
    public static void main(String[] args) throws IOException {
    /*
    为了保证文件的安全性,就需要对原始文件进行加密存储,再使用的时候再对其进行解密处理。
    加密原理:
    对原始文件中的每一个字节数据进行更改,然后将更改以后的数据存储到新的文件中。
    解密原理:
    读取加密之后的文件,按照加密的规则反向操作,变成原始文件。

    */
    }

    public static void encryptionAndReduction(File src, File dest) throws IOException {
    FileInputStream fis = new FileInputStream(src);
    FileOutputStream fos = new FileOutputStream(dest);
    int b;
    while ((b = fis.read()) != -1) {
    fos.write(b ^ 2);
    }
    //4.释放资源
    fos.close();
    fis.close();
    }


    }

  • 练习3:数字排序

    文本文件中有以下的数据:
    2-1-9-4-7-8
    将文件中的数据进行排序,变成以下的数据:
    1-2-4-7-8-9

    1. 方式一:

      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
      public class Test03 {
      public static void main(String[] args) throws IOException {
      /*
      文本文件中有以下的数据:
      2-1-9-4-7-8
      将文件中的数据进行排序,变成以下的数据:
      1-2-4-7-8-9
      */


      //1.读取数据
      FileReader fr = new FileReader("myio\\a.txt");
      StringBuilder sb = new StringBuilder();
      int ch;
      while((ch = fr.read()) != -1){
      sb.append((char)ch);
      }
      fr.close();
      System.out.println(sb);
      //2.排序
      String str = sb.toString();
      String[] arrStr = str.split("-");//2-1-9-4-7-8

      ArrayList<Integer> list = new ArrayList<>();
      for (String s : arrStr) {
      int i = Integer.parseInt(s);
      list.add(i);
      }
      Collections.sort(list);
      System.out.println(list);
      //3.写出
      FileWriter fw = new FileWriter("myio\\a.txt");
      for (int i = 0; i < list.size(); i++) {
      if(i == list.size() - 1){
      fw.write(list.get(i) + "");
      }else{
      fw.write(list.get(i) + "-");
      }
      }
      fw.close();
      }
      }
    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
      34
      35
      36
      37
      public class Test04 {
      public static void main(String[] args) throws IOException {
      /*
      文本文件中有以下的数据:
      2-1-9-4-7-8
      将文件中的数据进行排序,变成以下的数据:
      1-2-4-7-8-9

      细节1:
      文件中的数据不要换行

      细节2:
      bom头
      */
      //1.读取数据
      FileReader fr = new FileReader("myio\\a.txt");
      StringBuilder sb = new StringBuilder();
      int ch;
      while((ch = fr.read()) != -1){
      sb.append((char)ch);
      }
      fr.close();
      System.out.println(sb);
      //2.排序
      Integer[] arr = Arrays.stream(sb.toString()
      .split("-"))
      .map(Integer::parseInt)
      .sorted()
      .toArray(Integer[]::new);
      //3.写出
      FileWriter fw = new FileWriter("myio\\a.txt");
      String s = Arrays.toString(arr).replace(", ","-");
      String result = s.substring(1, s.length() - 1);
      fw.write(result);
      fw.close();
      }
      }

6.缓冲流

缓冲流,也叫高效流,是对4个基本的FileXxx 流的增强,所以也是4个流,按照数据类型分类:

  • 字节缓冲流BufferedInputStreamBufferedOutputStream
  • 字符缓冲流BufferedReaderBufferedWriter

缓冲流的基本原理,是在创建流对象时创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。

1.字节缓冲流

  • public BufferedInputStream(InputStream in) :创建一个新的缓冲输入流。
  • public BufferedOutputStream(OutputStream out): 创建一个新的缓冲输出流。
1
2
3
4
// 创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bis.txt"));
// 创建字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bos.txt"));

2.字符缓冲流

  • 构造方法

    1. public BufferedReader(Reader in) :创建一个 新的缓冲输入流。
    2. public BufferedWriter(Writer out): 创建一个新的缓冲输出流。
    1
    2
    3
    4
    // 创建字符缓冲输入流
    BufferedReader br = new BufferedReader(new FileReader("br.txt"));
    // 创建字符缓冲输出流
    BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));
  • 特有方法

    字符缓冲流的基本方法与普通字符流调用方式一致。

    1. BufferedReader:public String readLine(): 读一行文字(无数据返回null)。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      public class BufferedReaderDemo {
      public static void main(String[] args) throws IOException {
      // 创建流对象
      BufferedReader br = new BufferedReader(new FileReader("in.txt"));
      // 定义字符串,保存读取的一行文字
      String line = null;
      // 循环读取,读取到最后返回null
      while ((line = br.readLine())!=null) {
      System.out.print(line);
      System.out.println("------");
      }
      // 释放资源
      br.close();
      }
      }
    2. BufferedWriter:public void newLine(): 写一行行分隔符,由系统属性定义符号。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      public class BufferedWriterDemo throws IOException {
      public static void main(String[] args) throws IOException {
      // 创建流对象
      BufferedWriter bw = new BufferedWriter(new FileWriter("out.txt"));
      // 写出数据
      bw.write("黑马");
      // 写出换行
      bw.newLine();
      bw.write("程序");
      bw.newLine();
      bw.write("员");
      bw.newLine();
      // 释放资源
      bw.close();
      }
      }
      输出效果:
      黑马
      程序

3.练习:文本排序

1
2
3
4
5
6
7
8
9
3.侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯,是以先帝简拔以遗陛下。
8.愿陛下托臣以讨贼兴复之效,不效,则治臣之罪,以告先帝之灵。
4.将军向宠,性行淑均,晓畅军事,试用之于昔日,先帝称之曰能,是以众议举宠为督。
2.宫中府中,俱为一体,陟罚臧否,不宜异同。
1.先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。
9.今当远离,临表涕零,不知所言。
6.臣本布衣,躬耕于南阳,苟全性命于乱世,不求闻达于诸侯。
7.先帝知臣谨慎,故临崩寄臣以大事也。
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
28
29
30
31
32
33
34
35
36
37
38
public class Demo05Test {
public static void main(String[] args) throws IOException {
//1.创建ArrayList集合,泛型使用String
ArrayList<String> list = new ArrayList<>();
//2.创建BufferedReader对象,构造方法中传递FileReader对象
BufferedReader br = new BufferedReader(new FileReader("10_IO\\in.txt"));
//3.创建BufferedWriter对象,构造方法中传递FileWriter对象
BufferedWriter bw = new BufferedWriter(new FileWriter("10_IO\\out.txt"));
//4.使用BufferedReader对象中的方法readLine,以行的方式读取文本
String line;
while((line = br.readLine())!=null){
//5.把读取到的文本存储到ArrayList集合中
list.add(line);
}
//6.使用Collections集合工具类中的方法sort,对集合中的元素按照自定义规则排序
Collections.sort(list, new Comparator<String>() {
/*
o1-o2:升序
o2-o1:降序
*/
@Override
public int compare(String o1, String o2) {
//依次比较集合中两个元素的首字母,升序排序
return o1.charAt(0)-o2.charAt(0);
}
});
//7.遍历ArrayList集合,获取每一个元素
for (String s : list) {
//8.使用BufferedWriter对象中的方法wirte,把遍历得到的元素写入到文本中(内存缓冲区中)
bw.write(s);
//9.写换行
bw.newLine();
}
//10.释放资源
bw.close();
br.close();
}
}

7.转换流

  • 字符编码Character Encoding : 就是一套自然语言的字符与二进制数之间的对应规则。
  • **字符集 Charset**:也叫编码表。

使用FileReader 读取文本文件,若是UTF-8编码不会乱码,GBK编码,则会出现乱码。

1.InputStreamReader

转换流java.io.InputStreamReader,是Reader的子类,是从字节流到字符流的桥梁。读取字节,并指定字符集将其解码为字符。

  • 构造方法

    1. InputStreamReader(InputStream in): 创建一个使用默认字符集的字符流。
    2. InputStreamReader(InputStream in, String charsetName): 创建一个指定字符集的字符流。
    1
    2
    InputStreamReader isr = new InputStreamReader(new FileInputStream("in.txt"));
    InputStreamReader isr2 = new InputStreamReader(new FileInputStream("in.txt") , "GBK");
  • 指定编码读取

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class ReaderDemo2 {
    public static void main(String[] args) throws IOException {
    // 定义文件路径,文件为gbk编码
    String FileName = "E:\\file_gbk.txt";
    // 创建流对象,默认UTF8编码
    InputStreamReader isr = new InputStreamReader(new FileInputStream(FileName));
    // 创建流对象,指定GBK编码
    InputStreamReader isr2 = new InputStreamReader(new FileInputStream(FileName) , "GBK");
    // 定义变量,保存字符
    int read;
    // 使用默认编码字符流读取,乱码
    while ((read = isr.read()) != -1) {
    System.out.print((char)read); // ��Һ�
    }
    isr.close();

    // 使用指定编码字符流读取,正常解析
    while ((read = isr2.read()) != -1) {
    System.out.print((char)read);// 大家好
    }
    isr2.close();
    }
    }

2.OutputStreamWriter

转换流java.io.OutputStreamWriter ,是Writer的子类,是从字符流到字节流的桥梁。使用指定字符集将字符编码为字节。

  • 构造方法

    1. OutputStreamWriter(OutputStream in): 创建一个使用默认字符集的字符流。
    2. OutputStreamWriter(OutputStream in, String charsetName): 创建一个指定字符集的字符流。
    1
    2
    OutputStreamWriter isr = new OutputStreamWriter(new FileOutputStream("out.txt"));
    OutputStreamWriter isr2 = new OutputStreamWriter(new FileOutputStream("out.txt") , "GBK");
  • 指定编码写出

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class OutputDemo {
    public static void main(String[] args) throws IOException {
    // 定义文件路径
    String FileName = "E:\\out.txt";
    // 创建流对象,默认UTF8编码
    OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(FileName));
    // 写出数据
    osw.write("你好"); // 保存为6个字节
    osw.close();

    // 定义文件路径
    String FileName2 = "E:\\out2.txt";
    // 创建流对象,指定GBK编码
    OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream(FileName2),"GBK");
    // 写出数据
    osw2.write("你好");// 保存为4个字节
    osw2.close();
    }
    }

8.序列化

  • 序列化:用一个字节序列表示一个对象,该字节序列包含该对象的数据对象的类型对象中存储的属性等信息。
  • 反序列化:字节序列还从文件中读取回来,重构对象。

1.ObjectOutputStream

java.io.ObjectOutputStream 类,将Java对象的原始数据类型写出到文件,实现对象的持久存储。

  • 构造方法

    1. public ObjectOutputStream(OutputStream out) : 创建一个指定OutputStream的ObjectOutputStream。

      1
      2
      FileOutputStream fileOut = new FileOutputStream("employee.txt");
      ObjectOutputStream out = new ObjectOutputStream(fileOut);
  • 序列化操作

    1. 一个对象要想序列化,必须满足两个条件:

      • 该类必须实现java.io.Serializable 接口,Serializable 是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException
      • 类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient 关键字修饰。
      1
      2
      3
      4
      5
      6
      7
      8
      public class Employee implements java.io.Serializable {
      public String name;
      public String address;
      public transient int age; // transient瞬态修饰成员,不会被序列化
      public void addressCheck() {
      System.out.println("Address check : " + name + " -- " + address);
      }
      }
    2. 写出对象方法

      • public final void writeObject (Object obj) : 将指定的对象写出。

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        public class SerializeDemo{
        public static void main(String [] args) {
        Employee e = new Employee();
        e.name = "zhangsan";
        e.address = "beiqinglu";
        e.age = 20;
        try {
        // 创建序列化流对象
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("employee.txt"));
        // 写出对象
        out.writeObject(e);
        // 释放资源
        out.close();
        fileOut.close();
        System.out.println("Serialized data is saved"); // 姓名,地址被序列化,年龄没有被序列化。
        } catch(IOException i) {
        i.printStackTrace();
        }
        }
        }
        输出结果:
        Serialized data is saved

2.ObjectInputStream

ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象。

  • 构造方法

    1. public ObjectInputStream(InputStream in) : 创建一个指定InputStream的ObjectInputStream。
  • 反序列化操作

    1. 若能找到对象的class文件,可进行反序列化操作,调用ObjectInputStream读取对象的方法。

      public final Object readObject () : 读取一个对象。

      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
      public class DeserializeDemo {
      public static void main(String [] args) {
      Employee e = null;
      try {
      // 创建反序列化流
      FileInputStream fileIn = new FileInputStream("employee.txt");
      ObjectInputStream in = new ObjectInputStream(fileIn);
      // 读取一个对象
      e = (Employee) in.readObject();
      // 释放资源
      in.close();
      fileIn.close();
      }catch(IOException i) {
      // 捕获其他异常
      i.printStackTrace();
      return;
      }catch(ClassNotFoundException c) {
      // 捕获类找不到异常
      System.out.println("Employee class not found");
      c.printStackTrace();
      return;
      }
      // 无异常,直接打印输出
      System.out.println("Name: " + e.name); // zhangsan
      System.out.println("Address: " + e.address); // beiqinglu
      System.out.println("age: " + e.age); // 0
      }
      }

      JVM反序列化对象,必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个 ClassNotFoundException 异常。

    2. 若JVM反序列化对象能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException异常。原因如下:

      • 该类的序列版本号与从流中读取的类描述符的版本号不匹配
      • 该类包含未知数据类型
      • 该类没有可访问的无参数构造方法
      • Serializable 接口给需要序列化的类,提供了一个序列版本号。
      • serialVersionUID 该版本号目的在于验证序列化的对象和对应类是否版本匹配。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      public class Employee implements java.io.Serializable {
      // 加入序列版本号
      private static final long serialVersionUID = 1L;
      public String name;
      public String address;
      // 添加新的属性 ,重新编译, 可以反序列化,该属性赋为默认值.
      public int eid;

      public void addressCheck() {
      System.out.println("Address check : " + name + " -- " + address);
      }
      }

9.打印流

控制台打印输出是调用printprintln方法,两个方法都来自于java.io.PrintStream类,该类能打印各种数据类型的值,是一种便捷的输出方式。

1.PrintStream

  • 构造方法

    1. public PrintStream(String fileName) : 使用指定的文件名创建一个新的打印流。

      1
      PrintStream ps = new PrintStream("ps.txt");
  • 改变打印流向

    System.out就是PrintStream类型的,但它的流向是系统规定的,打印在控制台上。不过也可改变它的流向。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class PrintDemo {
    public static void main(String[] args) throws IOException {
    // 调用系统的打印流,控制台直接输出97
    System.out.println(97);

    // 创建打印流,指定文件的名称
    PrintStream ps = new PrintStream("ps.txt");

    // 设置系统的打印流流向,输出到ps.txt
    System.setOut(ps);
    // 调用系统的打印流,ps.txt中输出97
    System.out.println(97);
    }
    }

10.压缩流和解压缩流

  • 压缩流:负责压缩文件或者文件夹
  • 解压缩流:负责把压缩包中的文件和文件夹解压出来
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
/*
* 解压缩流
*
* */
public class ZipStreamDemo1 {
public static void main(String[] args) throws IOException {

//1.创建一个File表示要解压的压缩包
File src = new File("D:\\aaa.zip");
//2.创建一个File表示解压的目的地
File dest = new File("D:\\");

//调用方法
unzip(src,dest);

}

//定义一个方法用来解压
public static void unzip(File src,File dest) throws IOException {
//解压的本质:把压缩包里面的每一个文件或者文件夹读取出来,按照层级拷贝到目的地当中
//创建一个解压缩流用来读取压缩包中的数据
ZipInputStream zip = new ZipInputStream(new FileInputStream(src));
//要先获取到压缩包里面的每一个zipentry对象
//表示当前在压缩包中获取到的文件或者文件夹
ZipEntry entry;
while((entry = zip.getNextEntry()) != null){
System.out.println(entry);
if(entry.isDirectory()){
//文件夹:需要在目的地dest处创建一个同样的文件夹
File file = new File(dest,entry.toString());
file.mkdirs();
}else{
//文件:需要读取到压缩包中的文件,并把他存放到目的地dest文件夹中(按照层级目录进行存放)
FileOutputStream fos = new FileOutputStream(new File(dest,entry.toString()));
int b;
while((b = zip.read()) != -1){
//写到目的地
fos.write(b);
}
fos.close();
//表示在压缩包中的一个文件处理完毕了。
zip.closeEntry();
}
}
zip.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
public class ZipStreamDemo2 {
public static void main(String[] args) throws IOException {
/*
* 压缩流
* 需求:
* 把D:\\a.txt打包成一个压缩包
* */
//1.创建File对象表示要压缩的文件
File src = new File("D:\\a.txt");
//2.创建File对象表示压缩包的位置
File dest = new File("D:\\");
//3.调用方法用来压缩
toZip(src,dest);
}

/*
* 作用:压缩
* 参数一:表示要压缩的文件
* 参数二:表示压缩包的位置
* */
public static void toZip(File src,File dest) throws IOException {
//1.创建压缩流关联压缩包
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File(dest,"a.zip")));
//2.创建ZipEntry对象,表示压缩包里面的每一个文件和文件夹
//参数:压缩包里面的路径
ZipEntry entry = new ZipEntry("aaa\\bbb\\a.txt");
//3.把ZipEntry对象放到压缩包当中
zos.putNextEntry(entry);
//4.把src文件中的数据写到压缩包当中
FileInputStream fis = new FileInputStream(src);
int b;
while((b = fis.read()) != -1){
zos.write(b);
}
zos.closeEntry();
zos.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
40
41
42
43
44
45
46
47
48
49
50
51
52
public class ZipStreamDemo3 {
public static void main(String[] args) throws IOException {
/*
* 压缩流
* 需求:
* 把D:\\aaa文件夹压缩成一个压缩包
* */
//1.创建File对象表示要压缩的文件夹
File src = new File("D:\\aaa");
//2.创建File对象表示压缩包放在哪里(压缩包的父级路径)
File destParent = src.getParentFile();//D:\\
//3.创建File对象表示压缩包的路径
File dest = new File(destParent,src.getName() + ".zip");
//4.创建压缩流关联压缩包
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(dest));
//5.获取src里面的每一个文件,变成ZipEntry对象,放入到压缩包当中
toZip(src,zos,src.getName());//aaa
//6.释放资源
zos.close();
}

/*
* 作用:获取src里面的每一个文件,变成ZipEntry对象,放入到压缩包当中
* 参数一:数据源
* 参数二:压缩流
* 参数三:压缩包内部的路径
* */
public static void toZip(File src,ZipOutputStream zos,String name) throws IOException {
//1.进入src文件夹
File[] files = src.listFiles();
//2.遍历数组
for (File file : files) {
if(file.isFile()){
//3.判断-文件,变成ZipEntry对象,放入到压缩包当中
ZipEntry entry = new ZipEntry(name + "\\" + file.getName());//aaa\\no1\\a.txt
zos.putNextEntry(entry);
//读取文件中的数据,写到压缩包
FileInputStream fis = new FileInputStream(file);
int b;
while((b = fis.read()) != -1){
zos.write(b);
}
fis.close();
zos.closeEntry();
}else{
//4.判断-文件夹,递归
toZip(file,zos,name + "\\" + file.getName());
// no1 aaa \\ no1
}
}
}
}

11.工具包:Commons-io

Commons是apache开源基金组织提供的工具包,提供提高开发效率的API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
StringUtils   字符串工具类

NumberUtils   数字工具类

ArrayUtils   数组工具类

RandomUtils   随机数工具类

DateUtils   日期工具类

StopWatch   秒表工具类

ClassUtils   反射工具类

SystemUtils   系统工具类

MapUtils   集合工具类

Beanutils   bean工具类

Commons-io io的工具类

等等.....
  • 使用方式:

    1. 新建lib文件夹
    2. 把第三方jar包粘贴到文件夹中
    3. 右键点击add as a library
  • 代码示例:

    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
    public class CommonsIODemo1 {
    public static void main(String[] args) throws IOException {
    /*
    FileUtils类
    static void copyFile(File srcFile, File destFile) 复制文件
    static void copyDirectory(File srcDir, File destDir) 复制文件夹
    static void copyDirectoryToDirectory(File srcDir, File destDir) 复制文件夹
    static void deleteDirectory(File directory) 删除文件夹
    static void cleanDirectory(File directory) 清空文件夹
    static String readFileToString(File file, Charset encoding) 读取文件中的数据变成成字符串
    static void write(File file, CharSequence data, String encoding) 写出数据

    IOUtils类
    public static int copy(InputStream input, OutputStream output) 复制文件
    public static int copyLarge(Reader input, Writer output) 复制大文件
    public static String readLines(Reader input) 读取数据
    public static void write(String data, OutputStream output) 写出数据
    */


    /* File src = new File("myio\\a.txt");
    File dest = new File("myio\\copy.txt");
    FileUtils.copyFile(src,dest);*/


    /*File src = new File("D:\\aaa");
    File dest = new File("D:\\bbb");
    FileUtils.copyDirectoryToDirectory(src,dest);*/

    /*File src = new File("D:\\bbb");
    FileUtils.cleanDirectory(src);*/



    }
    }

15.多线程&JUC

1.概述

  • 简单了解多线程
    1. 指从软件或者硬件上实现多个线程并发执行的技术。
    2. 具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个线程,提升性能。
  • 并发和并行
    1. 并行:在同一时刻,有多个指令在多个CPU上同时执行。
    2. 并发:在同一时刻,有多个指令在单个CPU上交替执行。
  • 进程和线程
    1. 进程:是正在运行的程序
      • 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位
      • 动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的
      • 并发性:任何进程都可以同其他进程一起并发执行
    2. 线程:是进程中的单个顺序控制流,是一条执行路径
      • 单线程:一个进程如果只有一条执行路径,则称为单线程程序
      • 多线程:一个进程如果有多条执行路径,则称为多线程程序

2.实现多线程

1.继承Thread

  • 方法介绍

    方法名 说明
    void run() 在线程开启后,此方法将被调用执行
    void start() 使此线程开始执行,Java虚拟机会调用run方法()
  • 实现步骤

    1. 定义一个类MyThread继承Thread类
    2. 在MyThread类中重写run()方法
    3. 创建MyThread类的对象
    4. 启动线程
  • 代码演示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class MyThread extends Thread {
    @Override
    public void run() {
    for(int i=0; i<100; i++) {
    System.out.println(i);
    }
    }
    }
    public class MyThreadDemo {
    public static void main(String[] args) {
    MyThread my1 = new MyThread();
    MyThread my2 = new MyThread();

    // my1.run();
    // my2.run();

    //void start() 导致此线程开始执行; Java虚拟机调用此线程的run方法
    my1.start();
    my2.start();
    }
    }
  • 两个问题

    1. 为什么要重写run()方法?

      因为run()是用来封装被线程执行的代码

    2. run()方法和start()方法的区别?

      run():封装线程执行的代码,直接调用,相当于普通方法的调用

      start():启动线程;然后由JVM调用此线程的run()方法

2.实现Runnable接口

  • Thread构造方法

    方法名 说明
    Thread(Runnable target) 分配一个新的Thread对象
    Thread(Runnable target, String name) 分配一个新的Thread对象
  • 实现步骤

    1. 定义一个类MyRunnable实现Runnable接口
    2. 在MyRunnable类中重写run()方法
    3. 创建MyRunnable类的对象
    4. 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
    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
    public class MyRunnable implements Runnable {
    @Override
    public void run() {
    for(int i=0; i<100; i++) {
    System.out.println(Thread.currentThread().getName()+":"+i);
    }
    }
    }
    public class MyRunnableDemo {
    public static void main(String[] args) {
    //创建MyRunnable类的对象
    MyRunnable my = new MyRunnable();

    //创建Thread类的对象,把MyRunnable对象作为构造方法的参数
    //Thread(Runnable target)
    // Thread t1 = new Thread(my);
    // Thread t2 = new Thread(my);
    //Thread(Runnable target, String name)
    Thread t1 = new Thread(my,"坦克");
    Thread t2 = new Thread(my,"飞机");

    //启动线程
    t1.start();
    t2.start();
    }
    }

3.实现Callable接口

可获取多线程的结果

  • 方法介绍

    方法名 说明
    V call() 计算结果,如果无法计算结果,则抛出一个异常
    FutureTask(Callable callable) 创建一个 FutureTask,一旦运行就执行给定的 Callable
    V get() 如有必要,等待计算完成,然后获取其结果
  • 实现步骤

    1. 定义一个类MyCallable实现Callable接口
    2. 在MyCallable类中重写call()方法
    3. 创建MyCallable类的对象
    4. 创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数
    5. 创建Thread类的对象,把FutureTask对象作为构造方法的参数
    6. 启动线程
    7. 再调用get方法,就可以获取线程结束之后的结果。
  • 代码演示

    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
    public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
    for (int i = 0; i < 100; i++) {
    System.out.println("跟女孩表白" + i);
    }
    //返回值就表示线程运行完毕之后的结果
    return "答应";
    }
    }
    public class Demo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    //线程开启之后需要执行里面的call方法
    MyCallable mc = new MyCallable();

    //Thread t1 = new Thread(mc);

    //可以获取线程执行完毕之后的结果.也可以作为参数传递给Thread对象
    FutureTask<String> ft = new FutureTask<>(mc);

    //创建线程对象
    Thread t1 = new Thread(ft);

    String s = ft.get();
    //开启线程
    t1.start();

    //String s = ft.get();
    System.out.println(s);
    }
    }
  • 三种实现方式的对比

    1. 实现Runnable、Callable接口
      • 好处: 扩展性强,实现该接口的同时还可以继承其他的类
      • 缺点: 编程相对复杂,不能直接使用Thread类中的方法
    2. 继承Thread类
      • 好处: 编程比较简单,可以直接使用Thread类中的方法
      • 缺点: 可以扩展性较差,不能再继承其他的类

4.设置和获取线程名称

  • 方法介绍

    方法名 说明
    void setName(String name) 将此线程的名称更改为等于参数name
    String getName() 返回此线程的名称
    Thread currentThread() 返回对当前正在执行的线程对象的引用
  • 代码演示

    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
    public class MyThread extends Thread {
    public MyThread() {}
    public MyThread(String name) {
    super(name);
    }

    @Override
    public void run() {
    for (int i = 0; i < 100; i++) {
    System.out.println(getName()+":"+i);
    }
    }
    }
    public class MyThreadDemo {
    public static void main(String[] args) {
    MyThread my1 = new MyThread();
    MyThread my2 = new MyThread();

    //void setName(String name):将此线程的名称更改为等于参数 name
    my1.setName("高铁");
    my2.setName("飞机");

    //Thread(String name)
    MyThread my1 = new MyThread("高铁");
    MyThread my2 = new MyThread("飞机");

    my1.start();
    my2.start();

    //static Thread currentThread() 返回对当前正在执行的线程对象的引用
    System.out.println(Thread.currentThread().getName());
    }
    }

5.线程休眠

  • 相关方法

    方法名 说明
    static void sleep(long millis) 使当前正在执行的线程停留(暂停执行)指定的毫秒数
  • 代码演示

    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
    public class MyRunnable implements Runnable {
    @Override
    public void run() {
    for (int i = 0; i < 100; i++) {
    try {
    Thread.sleep(100);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }

    System.out.println(Thread.currentThread().getName() + "---" + i);
    }
    }
    }
    public class Demo {
    public static void main(String[] args) throws InterruptedException {
    /*System.out.println("睡觉前");
    Thread.sleep(3000);
    System.out.println("睡醒了");*/

    MyRunnable mr = new MyRunnable();

    Thread t1 = new Thread(mr);
    Thread t2 = new Thread(mr);

    t1.start();
    t2.start();
    }
    }

6.线程优先级

  • 线程调度

    1. 两种调度方式

      • 分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
      • 抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,则随机选择一个优先级高的线程获取的 CPU 时间片相对较多
    2. Java使用的是抢占式调度模型

    3. 随机性

      假如计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到CPU时间片即使用权,才可以执行指令。所以,多线程程序的执行是有随机性,谁抢到CPU的使用权是不一定的。

  • 优先级相关方法

    方法名 说明
    final int getPriority() 返回此线程的优先级
    final void setPriority(int newPriority) 更改此线程的优先级线程默认优先级是5;线程优先级的范围是:1-10(数字越大,优先级越大)
  • 代码演示

    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
    public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
    for (int i = 0; i < 100; i++) {
    System.out.println(Thread.currentThread().getName() + "---" + i);
    }
    return "线程执行完毕了";
    }
    }
    public class Demo {
    public static void main(String[] args) {
    //优先级: 1 - 10 默认值:5
    MyCallable mc = new MyCallable();

    FutureTask<String> ft = new FutureTask<>(mc);

    Thread t1 = new Thread(ft);
    t1.setName("飞机");
    t1.setPriority(10);
    //System.out.println(t1.getPriority());//5
    t1.start();

    MyCallable mc2 = new MyCallable();

    FutureTask<String> ft2 = new FutureTask<>(mc2);

    Thread t2 = new Thread(ft2);
    t2.setName("坦克");
    t2.setPriority(1);
    //System.out.println(t2.getPriority());//5
    t2.start();
    }
    }

7.守护线程

  • 相关方法

    方法名 说明
    void setDaemon(boolean on) 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出(当其他的非守护线程执行完毕,守护线程会陆续结束)
    public static void yield() (了解)出让线程/礼让线程(线程运行尽可能均匀)
    public final void join() (了解)插入线程/插队线程(该线程执行完毕在执行其他线程)
  • 代码演示

    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
    public class MyThread1 extends Thread {
    @Override
    public void run() {
    for (int i = 0; i < 10; i++) {
    System.out.println(getName() + "---" + i);
    }
    }
    }
    public class MyThread2 extends Thread {
    @Override
    public void run() {
    for (int i = 0; i < 100; i++) {
    System.out.println(getName() + "---" + i);
    }
    }
    }
    public class Demo {
    public static void main(String[] args) {
    MyThread1 t1 = new MyThread1();
    MyThread2 t2 = new MyThread2();

    t1.setName("女神");
    t2.setName("备胎");

    //把第二个线程设置为守护线程
    //当普通线程执行完之后,那么守护线程也没有继续运行下去的必要了.
    t2.setDaemon(true);

    t1.start();
    t2.start();
    }
    }

8.线程生命周期

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
1. NEW(新建)

│ 调用 start()

2. RUNNABLE(就绪/可运行)

│ 获取 CPU 时间片

3. RUNNING(正在运行)─-──┐
│ │
├──── 时间片用完 ────► │ 回到 RUNNABLE
│ │
├──── sleep(n) ──────┴──► TIMED_WAITING(限时等待)
│ │
│ │ 时间到 / 中断
│ ▼
│ 回到 RUNNABLE

├──── wait() / join() ───────► WAITING(无限等待)
│ │
│ │ notify() / 中断
│ ▼
│ 回到 RUNNABLE

├──── 等待锁(synchronized)──────► BLOCKED(阻塞)
│ │
│ │ 获得锁
│ ▼
│ 回到 RUNNABLE

└──── 执行完毕 / 异常退出 ─────────► TERMINATED(终止)

3.线程同步

1.案例

  • 案例需求

    电影院目前共有100张票,有3个窗口卖票,请设计一个程序模拟该电影院卖票

  • 实现步骤

    1. 定义一个类SellTicket实现Runnable接口,里面定义一个成员变量:private int tickets = 100;

    2. 在SellTicket类中重写run()方法实现卖票,代码步骤如下

    3. 判断票数大于0,就卖票,并告知是哪个窗口卖的

    4. 卖了票之后,总票数要减1

    5. 票卖没了,线程停止

    6. 定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下

    7. 创建SellTicket类的对象

    8. 创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称

    9. 启动线程

  • 代码实现

    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
    public class SellTicket implements Runnable {
    private int tickets = 100;
    //在SellTicket类中重写run()方法实现卖票,代码步骤如下
    @Override
    public void run() {
    while (true) {
    if(ticket <= 0){
    //卖完了
    break;
    }else{
    try {
    Thread.sleep(100);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    ticket--;
    System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");
    }
    }
    }
    }
    public class SellTicketDemo {
    public static void main(String[] args) {
    //创建SellTicket类的对象
    SellTicket st = new SellTicket();

    //创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
    Thread t1 = new Thread(st,"窗口1");
    Thread t2 = new Thread(st,"窗口2");
    Thread t3 = new Thread(st,"窗口3");

    //启动线程
    t1.start();
    t2.start();
    t3.start();
    }
    }
  • 卖票案例的问题

    1. 问题:

      • 相同的票出现了多次

      • 出现了负数的票

    2. 问题产生原因

      线程执行的随机性导致的,可能在卖票过程中丢失cpu的执行权,导致出现问题

2.同步代码块解决数据安全问题

  • 安全问题出现的条件

    1. 是多线程环境

    2. 有共享数据

    3. 有多条语句操作共享数据

  • 如何解决多线程安全问题呢?

    1. 基本思想:让程序没有安全问题的环境
  • 实现多线程安全问题:

    1. 把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可

    2. Java提供了同步代码块的方式来解决

  • 同步代码块格式:

    1
    2
    3
    synchronized(任意对象) {
    多条语句操作共享数据的代码
    }

    synchronized(任意对象):相当于给代码加锁了,任意对象就可以看成是一把锁。锁对象唯一。

  • 同步的好处和弊端

    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
    34
    35
    36
    37
    38
    39
    public class SellTicket implements Runnable {
    private int tickets = 100;
    private Object obj = new Object();

    @Override
    public void run() {
    while (true) {
    synchronized (obj) { // 对可能有安全问题的代码加锁,多个线程必须使用同一把锁
    //t1进来后,就会把这段代码给锁起来
    if (tickets > 0) {
    try {
    Thread.sleep(100);
    //t1休息100毫秒
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    //窗口1正在出售第100张票
    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
    tickets--; //tickets = 99;
    }
    }
    //t1出来了,这段代码的锁就被释放了
    }
    }
    }

    public class SellTicketDemo {
    public static void main(String[] args) {
    SellTicket st = new SellTicket();

    Thread t1 = new Thread(st, "窗口1");
    Thread t2 = new Thread(st, "窗口2");
    Thread t3 = new Thread(st, "窗口3");

    t1.start();
    t2.start();
    t3.start();
    }
    }

3.同步方法解决数据安全问题

  • 同步方法的格式

    1. 同步方法:就是把synchronized关键字加到方法上

      1
      2
      3
      修饰符 synchronized 返回值类型 方法名(方法参数) { 
      方法体;
      }
    2. 同步方法的锁对象是什么呢?

      1
      this
  • 静态同步方法

    1. 同步静态方法:就是把synchronized关键字加到静态方法上

      1
      2
      3
      修饰符 static synchronized 返回值类型 方法名(方法参数) { 
      方法体;
      }
    2. 同步静态方法的锁对象是什么呢?

      1
      类名.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
    44
    45
    46
    47
    48
    49
    public class MyRunnable implements Runnable {
    private static int ticketCount = 100;

    @Override
    public void run() {
    while(true){
    if("窗口一".equals(Thread.currentThread().getName())){
    //同步方法
    boolean result = synchronizedMthod();
    if(result){
    break;
    }
    }

    if("窗口二".equals(Thread.currentThread().getName())){
    //同步代码块
    synchronized (MyRunnable.class){
    if(ticketCount == 0){
    break;
    }else{
    try {
    Thread.sleep(10);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    ticketCount--;
    System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张票");
    }
    }
    }

    }
    }

    private static synchronized boolean synchronizedMthod() {
    if(ticketCount == 0){
    return true;
    }else{
    try {
    Thread.sleep(10);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    ticketCount--;
    System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张票");
    return false;
    }
    }
    }
    public class Demo {
      public static void main(String[] args) {
          MyRunnable mr = new MyRunnable();
          Thread t1 = new Thread(mr);
          Thread t2 = new Thread(mr);
    
          t1.setName("窗口一");
          t2.setName("窗口二");
    
          t1.start();
          t2.start();
        }
    }
    

4.Lock

  • 同步代码块和同步方法的锁对象,并没有直接展示哪里加上了锁、释放了锁。JDK5以后提供了一个新的锁对象Lock更清晰的表达如何加锁和释放锁。
  • Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
  • ReentrantLock构造方法

    方法名 说明
    ReentrantLock() 创建一个ReentrantLock的实例
  • 加锁解锁方法

    方法名 说明
    void lock() 获得锁
    void unlock() 释放锁
  • 代码演示

    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
     public class Ticket implements Runnable {
    //票的数量
    private int ticket = 100;
    private Object obj = new Object();
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
    while (true) {
    //synchronized (obj){//多个线程必须使用同一把锁.
    try {
    lock.lock();
    if (ticket <= 0) {
    //卖完了
    break;
    } else {
    Thread.sleep(100);
    ticket--;
    System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");
    }
    } catch (InterruptedException e) {
    e.printStackTrace();
    } finally {
    lock.unlock();
    }
    // }
    }
    }
    }

    public class Demo {
    public static void main(String[] args) {
    Ticket ticket = new Ticket();

    Thread t1 = new Thread(ticket);
    Thread t2 = new Thread(ticket);
    Thread t3 = new Thread(ticket);

    t1.setName("窗口一");
    t2.setName("窗口二");
    t3.setName("窗口三");

    t1.start();
    t2.start();
    t3.start();
    }
    }

5.死锁

  • 概述

    线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行

  • 什么情况下会产生死锁

    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
    public class Demo {
    public static void main(String[] args) {
    Object objA = new Object();
    Object objB = new Object();

    new Thread(()->{
    while(true){
    synchronized (objA){
    //线程一
    synchronized (objB){
    System.out.println("小康同学正在走路");
    }
    }
    }
    }).start();

    new Thread(()->{
    while(true){
    synchronized (objB){
    //线程二
    synchronized (objA){
    System.out.println("小薇同学正在走路");
    }
    }
    }
    }).start();
    }
    }

4.生产者消费者

1.生产者和消费者模式概述

  • 概述

    1. 生产者消费者问题,实际上主要包含了两类线程:
      • 一类是生产者线程用于生产数据
      • 一类是消费者线程用于消费数据
    2. 为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库
    3. 生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为
    4. 消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为
  • Object类的等待和唤醒方法

    方法名 说明
    void wait() 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法
    void notify() 随机唤醒正在等待对象监视器的单个线程
    void notifyAll() 唤醒正在等待对象监视器的所有线程

2.生产者和消费者案例

  • 案例需求

    1. 桌子类(Desk):定义表示包子数量的变量,定义锁对象变量,定义标记桌子上有无包子的变量

    2. 生产者类(Cooker):实现Runnable接口,重写run()方法,设置线程任务

      • 判断是否有包子,决定当前线程是否执行
      • 如果有包子,就进入等待状态,如果没有包子,继续执行,生产包子
      • 生产包子之后,更新桌子上包子状态,唤醒消费者消费包子
    3. 消费者类(Foodie):实现Runnable接口,重写run()方法,设置线程任务

      • 判断是否有包子,决定当前线程是否执行
      • 如果没有包子,就进入等待状态,如果有包子,就消费包子
      • 消费包子后,更新桌子上包子状态,唤醒生产者生产包子
    4. 测试类(Demo):里面有main方法,main方法中的代码步骤如下

      • 创建生产者线程和消费者线程对象
      • 分别开启两个线程
  • 代码实现

    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
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    public class Desk {

    //定义一个标记
    //true 就表示桌子上有汉堡包的,此时允许吃货执行
    //false 就表示桌子上没有汉堡包的,此时允许厨师执行
    public static boolean flag = false;

    //汉堡包的总数量
    public static int count = 10;

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

    public class Cooker extends Thread {
    // 生产者步骤:
    // 1,判断桌子上是否有汉堡包
    // 如果有就等待,如果没有才生产。
    // 2,把汉堡包放在桌子上。
    // 3,叫醒等待的消费者开吃。
    @Override
    public void run() {
    while(true){
    synchronized (Desk.lock){
    if(Desk.count == 0){
    break;
    }else{
    if(!Desk.flag){
    //生产
    System.out.println("厨师正在生产汉堡包");
    Desk.flag = true;
    Desk.lock.notifyAll();
    }else{
    try {
    Desk.lock.wait();
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }
    }
    }
    }
    }

    public class Foodie extends Thread {
    @Override
    public void run() {
    // 1,判断桌子上是否有汉堡包。
    // 2,如果没有就等待。
    // 3,如果有就开吃
    // 4,吃完之后,桌子上的汉堡包就没有了
    // 叫醒等待的生产者继续生产
    // 汉堡包的总数量减一

    //套路:
    //1. while(true)死循环
    //2. synchronized 锁,锁对象要唯一
    //3. 判断,共享数据是否结束. 结束
    //4. 判断,共享数据是否结束. 没有结束
    while(true){
    synchronized (Desk.lock){
    if(Desk.count == 0){
    break;
    }else{
    if(Desk.flag){
    //有
    System.out.println("吃货在吃汉堡包");
    Desk.flag = false;
    Desk.lock.notifyAll();
    Desk.count--;
    }else{
    //没有就等待
    //使用什么对象当做锁,那么就必须用这个对象去调用等待和唤醒的方法.
    try {
    Desk.lock.wait();
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }
    }
    }

    }
    }

    public class Demo {
    public static void main(String[] args) {
    /*消费者步骤:
    1,判断桌子上是否有汉堡包。
    2,如果没有就等待。
    3,如果有就开吃
    4,吃完之后,桌子上的汉堡包就没有了
    叫醒等待的生产者继续生产
    汉堡包的总数量减一*/

    /*生产者步骤:
    1,判断桌子上是否有汉堡包
    如果有就等待,如果没有才生产。
    2,把汉堡包放在桌子上。
    3,叫醒等待的消费者开吃。*/

    Foodie f = new Foodie();
    Cooker c = new Cooker();

    f.start();
    c.start();

    }
    }

3.生产者和消费者案例优化

  • 需求

    1. 将Desk类中的变量,采用面向对象的方式封装起来
    2. 生产者和消费者类中构造方法接收Desk类对象,之后在run方法中进行使用
    3. 创建生产者和消费者线程对象,构造方法中传入Desk类对象
    4. 开启两个线程
  • 代码实现

    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
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    public class Desk {

    //定义一个标记
    //true 就表示桌子上有汉堡包的,此时允许吃货执行
    //false 就表示桌子上没有汉堡包的,此时允许厨师执行
    //public static boolean flag = false;
    private boolean flag;

    //汉堡包的总数量
    //public static int count = 10;
    //以后我们在使用这种必须有默认值的变量
    // private int count = 10;
    private int count;

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

    public Desk() {
    this(false,10); // 在空参内部调用带参,对成员变量进行赋值,之后就可以直接使用成员变量了
    }

    public Desk(boolean flag, int count) {
    this.flag = flag;
    this.count = count;
    }

    public boolean isFlag() {
    return flag;
    }

    public void setFlag(boolean flag) {
    this.flag = flag;
    }

    public int getCount() {
    return count;
    }

    public void setCount(int count) {
    this.count = count;
    }

    public Object getLock() {
    return lock;
    }

    @Override
    public String toString() {
    return "Desk{" +
    "flag=" + flag +
    ", count=" + count +
    ", lock=" + lock +
    '}';
    }
    }

    public class Cooker extends Thread {

    private Desk desk;

    public Cooker(Desk desk) {
    this.desk = desk;
    }
    // 生产者步骤:
    // 1,判断桌子上是否有汉堡包
    // 如果有就等待,如果没有才生产。
    // 2,把汉堡包放在桌子上。
    // 3,叫醒等待的消费者开吃。

    @Override
    public void run() {
    while(true){
    synchronized (desk.getLock()){
    if(desk.getCount() == 0){
    break;
    }else{
    //System.out.println("验证一下是否执行了");
    if(!desk.isFlag()){
    //生产
    System.out.println("厨师正在生产汉堡包");
    desk.setFlag(true);
    desk.getLock().notifyAll();
    }else{
    try {
    desk.getLock().wait();
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }
    }
    }
    }
    }

    public class Foodie extends Thread {
    private Desk desk;

    public Foodie(Desk desk) {
    this.desk = desk;
    }

    @Override
    public void run() {
    // 1,判断桌子上是否有汉堡包。
    // 2,如果没有就等待。
    // 3,如果有就开吃
    // 4,吃完之后,桌子上的汉堡包就没有了
    // 叫醒等待的生产者继续生产
    // 汉堡包的总数量减一

    //套路:
    //1. while(true)死循环
    //2. synchronized 锁,锁对象要唯一
    //3. 判断,共享数据是否结束. 结束
    //4. 判断,共享数据是否结束. 没有结束
    while(true){
    synchronized (desk.getLock()){
    if(desk.getCount() == 0){
    break;
    }else{
    //System.out.println("验证一下是否执行了");
    if(desk.isFlag()){
    //有
    System.out.println("吃货在吃汉堡包");
    desk.setFlag(false);
    desk.getLock().notifyAll();
    desk.setCount(desk.getCount() - 1);
    }else{
    //没有就等待
    //使用什么对象当做锁,那么就必须用这个对象去调用等待和唤醒的方法.
    try {
    desk.getLock().wait();
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }
    }
    }

    }
    }

    public class Demo {
    public static void main(String[] args) {
    /*消费者步骤:
    1,判断桌子上是否有汉堡包。
    2,如果没有就等待。
    3,如果有就开吃
    4,吃完之后,桌子上的汉堡包就没有了
    叫醒等待的生产者继续生产
    汉堡包的总数量减一*/

    /*生产者步骤:
    1,判断桌子上是否有汉堡包
    如果有就等待,如果没有才生产。
    2,把汉堡包放在桌子上。
    3,叫醒等待的消费者开吃。*/

    Desk desk = new Desk();

    Foodie f = new Foodie(desk);
    Cooker c = new Cooker(desk);

    f.start();
    c.start();

    }
    }

4.阻塞队列基本使用

  • 阻塞队列继承结构

    001

  • 常见BlockingQueue:

    1. ArrayBlockingQueue: 底层是数组,有界
    2. LinkedBlockingQueue: 底层是链表,无界.但不是真正的无界,最大为int的最大值
  • BlockingQueue的核心方法:

    1. put(anObject): 将参数放入队列,如果放不进去会阻塞
    2. take(): 取出第一个数据,取不到会阻塞
  • 代码示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class Demo02 {
    public static void main(String[] args) throws Exception {
    // 创建阻塞队列的对象,容量为 1
    ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(1);

    // 存储元素
    arrayBlockingQueue.put("汉堡包");

    // 取元素
    System.out.println(arrayBlockingQueue.take());
    System.out.println(arrayBlockingQueue.take()); // 取不到会阻塞

    System.out.println("程序结束了");
    }
    }

5.阻塞队列实现等待唤醒机制

  • 案例需求

    1. 生产者类(Cooker):实现Runnable接口,重写run()方法,设置线程任务

      • 构造方法中接收一个阻塞队列对象
      • 在run方法中循环向阻塞队列中添加包子
      • 打印添加结果
    2. 消费者类(Foodie):实现Runnable接口,重写run()方法,设置线程任务

      • 构造方法中接收一个阻塞队列对象
      • 在run方法中循环获取阻塞队列中的包子
      • 打印获取结果
    3. 测试类(Demo):里面有main方法,main方法中的代码步骤如下

      • 创建阻塞队列对象
      • 创建生产者线程和消费者线程对象,构造方法中传入阻塞队列对象
      • 分别开启两个线程
  • 代码实现

    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
    public class Cooker extends Thread {

    private ArrayBlockingQueue<String> bd;

    public Cooker(ArrayBlockingQueue<String> bd) {
    this.bd = bd;
    }
    // 生产者步骤:
    // 1,判断桌子上是否有汉堡包
    // 如果有就等待,如果没有才生产。
    // 2,把汉堡包放在桌子上。
    // 3,叫醒等待的消费者开吃。

    @Override
    public void run() {
    while (true) {
    try {
    bd.put("汉堡包");
    System.out.println("厨师放入一个汉堡包");
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }
    }

    public class Foodie extends Thread {
    private ArrayBlockingQueue<String> bd;

    public Foodie(ArrayBlockingQueue<String> bd) {
    this.bd = bd;
    }

    @Override
    public void run() {
    // 1,判断桌子上是否有汉堡包。
    // 2,如果没有就等待。
    // 3,如果有就开吃
    // 4,吃完之后,桌子上的汉堡包就没有了
    // 叫醒等待的生产者继续生产
    // 汉堡包的总数量减一

    //套路:
    //1. while(true)死循环
    //2. synchronized 锁,锁对象要唯一
    //3. 判断,共享数据是否结束. 结束
    //4. 判断,共享数据是否结束. 没有结束
    while (true) {
    try {
    String take = bd.take();
    System.out.println("吃货将" + take + "拿出来吃了");
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }

    }
    }

    public class Demo {
    public static void main(String[] args) {
    ArrayBlockingQueue<String> bd = new ArrayBlockingQueue<>(1);

    Foodie f = new Foodie(bd);
    Cooker c = new Cooker(bd);

    f.start();
    c.start();
    }
    }

5.线程池

1.线程状态介绍

  • 当线程被创建并启动后不是一启动就进入执行状态,也不是一直处于执行状态。

  • 状态被定义在了java.lang.Thread.State枚举类中,State枚举类的源码如下:

    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
    public class Thread {

    public enum State {

    /* 新建 */
    NEW ,

    /* 可运行状态 */
    RUNNABLE ,

    /* 阻塞状态 */
    BLOCKED ,

    /* 无限等待状态 */
    WAITING ,

    /* 计时等待 */
    TIMED_WAITING ,

    /* 终止 */
    TERMINATED;

    }

    // 获取当前线程的状态
    public State getState() {
    return jdk.internal.misc.VM.toThreadState(threadStatus);
    }

    }
  • Java中的线程存在6种状态的含义

    线程状态 具体含义
    NEW 一个尚未启动的线程的状态。也称之为初始状态、开始状态。线程刚被创建,但是并未启动。还没调用start方法。MyThread t = new MyThread()只有线程象,没有线程特征。
    RUNNABLE 当我们调用线程对象的start方法,那么此时线程对象进入了RUNNABLE状态。那么此时才是真正的在JVM进程中创建了一个线程,线程一经启动并不是立即得到执行,线程的运行与否要听令与CPU的调度,那么我们把这个中间状态称之为可执行状态(RUNNABLE)也就是说它具备执行的资格,但是并没有真正的执行起来而是在等待CPU的度。
    BLOCKED 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
    WAITING 一个正在等待的线程的状态。也称之为等待状态。造成线程等待的原因有两种,分别是调用Object.wait()、join()方法。处于等待状态的线程,正在等待其他线程去执行一个特定的操作。例如:因为wait()而等待的线程正在等待另一个线程去调用notify()或notifyAll();一个因为join()而等待的线程正在等待另一个线程结束。
    TIMED_WAITING 一个在限定时间内等待的线程的状态。也称之为限时等待状态。造成线程限时等待状态的原因有三种,分别是:Thread.sleep(long),Object.wait(long)、join(long)。
    TERMINATED 一个完全运行完成的线程的状态。也称之为终止状态、结束状态
  • 各个状态的转换:

    001

2.基本原理

  • 概述 :
    1. 线程池可以看做成一个池子,该池子中存储很多线程。
    2. 线程池存在的意义:系统创建一个线程的成本比较高,其涉及到与操作系统交互,当程序中需要创建大量生存期很短暂的线程时,频繁的创建和销毁线程对系统的资源消耗有可能大于业务处理是对系统资源的消耗
    3. 线程池在启动的时,会创建大量空闲线程,当向线程池提交任务时,线程池会启动一个线程来执行该任务。等待任务执行完毕后,线程不会死亡,而是再次返回到线程池中称为空闲状态,等待下一次任务的执行。
  • 线程池的设计思路 :
    1. 准备一个任务容器
    2. 一次性启动多个(2个)消费者线程
    3. 刚开始任务容器是空的,所以线程都在wait
    4. 直到一个外部线程向这个任务容器中扔了一个”任务”,就会有一个消费者线程被唤醒
    5. 这个消费者线程取出”任务”,并且执行这个任务,执行完毕后,继续等待下一次任务的到来

3.Executors默认线程池

  • 概述 : JDK对线程池进行了相关的实现:使用Executors提供的静态方法创建线程池

    1. static ExecutorService newCachedThreadPool():创建一个默认的线程池
    2. static newFixedThreadPool(int nThreads):创建一个指定最多线程数量的线程池
  • 代码实现 :

    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
    package com.itheima.mythreadpool;


    //static ExecutorService newCachedThreadPool() 创建一个默认的线程池
    //static newFixedThreadPool(int nThreads) 创建一个指定最多线程数量的线程池

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

    public class MyThreadPoolDemo {
    public static void main(String[] args) throws InterruptedException {

    //1,创建一个默认的线程池对象.池子中默认是空的.默认最多可以容纳int类型的最大值.
    ExecutorService executorService = Executors.newCachedThreadPool();
    //Executors --- 可以帮助我们创建线程池对象
    //ExecutorService --- 可以帮助我们控制线程池

    executorService.submit(()->{
    System.out.println(Thread.currentThread().getName() + "在执行了");
    });

    //Thread.sleep(2000);

    executorService.submit(()->{
    System.out.println(Thread.currentThread().getName() + "在执行了");
    });

    executorService.shutdown();
    }
    }

4.Executors指定线程池上限

  • 使用Executors中所提供的静态方法来创建线程池

    1. static ExecutorService newFixedThreadPool(int nThreads) : 创建一个指定最多线程数量的线程池
  • 代码实现 :

    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.itheima.mythreadpool;

    //static ExecutorService newFixedThreadPool(int nThreads)
    //创建一个指定最多线程数量的线程池

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

    public class MyThreadPoolDemo2 {
    public static void main(String[] args) {
    //参数不是初始值而是最大值
    ExecutorService executorService = Executors.newFixedThreadPool(10);

    ThreadPoolExecutor pool = (ThreadPoolExecutor) executorService;
    System.out.println(pool.getPoolSize());//0

    executorService.submit(()->{
    System.out.println(Thread.currentThread().getName() + "在执行了");
    });

    executorService.submit(()->{
    System.out.println(Thread.currentThread().getName() + "在执行了");
    });

    System.out.println(pool.getPoolSize());//2
    // executorService.shutdown();
    }
    }

5.ThreadPoolExecutor

  • 创建线程池对象 :

    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(核心线程数量,最大线程数量,空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略);

  • 代码实现 :

    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.itheima.mythreadpool;

    import java.util.concurrent.ArrayBlockingQueue;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;

    public class MyThreadPoolDemo3 {
    // 参数一:核心线程数量
    // 参数二:最大线程数
    // 参数三:空闲线程最大存活时间
    // 参数四:时间单位
    // 参数五:任务队列
    // 参数六:创建线程工厂
    // 参数七:任务的拒绝策略
    public static void main(String[] args) {
    ThreadPoolExecutor pool = new ThreadPoolExecutor(2,5,2,TimeUnit.SECONDS,new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
    pool.submit(new MyRunnable());
    pool.submit(new MyRunnable());

    pool.shutdown();
    }
    }

6.参数详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)

corePoolSize: 核心线程的最大值,不能小于0
maximumPoolSize:最大线程数,不能小于等于0,maximumPoolSize >= corePoolSize
keepAliveTime: 空闲线程最大存活时间,不能小于0
unit: 时间单位
workQueue: 任务队列,不能为null
threadFactory: 创建线程工厂,不能为null
handler: 任务的拒绝策略,不能为null

7.非默认任务拒绝策略

  • RejectedExecutionHandler:是jdk提供的一个任务拒绝策略接口,它下面存在4个子类。

    1
    2
    3
    4
    ThreadPoolExecutor.AbortPolicy: 		    丢弃任务并抛出RejectedExecutionException异常。是默认的策略。
    ThreadPoolExecutor.DiscardPolicy: 丢弃任务,但是不抛出异常 这是不推荐的做法。
    ThreadPoolExecutor.DiscardOldestPolicy: 抛弃队列中等待最久的任务 然后把当前任务加入队列中。
    ThreadPoolExecutor.CallerRunsPolicy: 调用任务的run()方法绕过线程池直接执行。

    注:明确线程池对多可执行的任务数 = 队列容量 + 最大线程数

  • 案例演示1

    1. 演示ThreadPoolExecutor.AbortPolicy任务处理策略

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      public class ThreadPoolExecutorDemo01 {

      public static void main(String[] args) {

      /**
      * 核心线程数量为1 , 最大线程池数量为3, 任务容器的容量为1 ,空闲线程的最大存在时间为20s
      */
      ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,
      new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.AbortPolicy()) ;

      // 提交5个任务,而该线程池最多可以处理4个任务,当我们使用AbortPolicy这个任务处理策略的时候,就会抛出异常
      for(int x = 0 ; x < 5 ; x++) {
      threadPoolExecutor.submit(() -> {
      System.out.println(Thread.currentThread().getName() + "---->> 执行了任务");
      });
      }
      }
      }
    2. 控制台输出结果

      1
      2
      3
      4
      pool-1-thread-1---->> 执行了任务
      pool-1-thread-3---->> 执行了任务
      pool-1-thread-2---->> 执行了任务
      pool-1-thread-3---->> 执行了任务

      控制台报错,仅仅执行了4个任务,有一个任务被丢弃

  • 案例演示2

    1. 演示ThreadPoolExecutor.DiscardPolicy任务处理策略

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      public class ThreadPoolExecutorDemo02 {
      public static void main(String[] args) {
      /**
      * 核心线程数量为1 , 最大线程池数量为3, 任务容器的容量为1 ,空闲线程的最大存在时间为20s
      */
      ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,
      new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.DiscardPolicy()) ;

      // 提交5个任务,而该线程池最多可以处理4个任务,当我们使用DiscardPolicy这个任务处理策略的时候,控制台不会报错
      for(int x = 0 ; x < 5 ; x++) {
      threadPoolExecutor.submit(() -> {
      System.out.println(Thread.currentThread().getName() + "---->> 执行了任务");
      });
      }
      }
      }
    2. 控制台输出结果

      1
      2
      3
      4
      pool-1-thread-1---->> 执行了任务
      pool-1-thread-1---->> 执行了任务
      pool-1-thread-3---->> 执行了任务
      pool-1-thread-2---->> 执行了任务

      控制台没有报错,仅仅执行了4个任务,有一个任务被丢弃

  • 案例演示3

    1. 演示ThreadPoolExecutor.DiscardOldestPolicy任务处理策略

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      public class ThreadPoolExecutorDemo02 {
      public static void main(String[] args) {
      /**
      * 核心线程数量为1 , 最大线程池数量为3, 任务容器的容量为1 ,空闲线程的最大存在时间为20s
      */
      ThreadPoolExecutor threadPoolExecutor;
      threadPoolExecutor = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,
      new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.DiscardOldestPolicy());
      // 提交5个任务
      for(int x = 0 ; x < 5 ; x++) {
      // 定义一个变量,来指定指定当前执行的任务;这个变量需要被final修饰
      final int y = x ;
      threadPoolExecutor.submit(() -> {
      System.out.println(Thread.currentThread().getName() + "---->> 执行了任务" + y);
      });
      }
      }
      }
    2. 控制台输出结果

      1
      2
      3
      4
      pool-1-thread-2---->> 执行了任务2
      pool-1-thread-1---->> 执行了任务0
      pool-1-thread-3---->> 执行了任务3
      pool-1-thread-1---->> 执行了任务4

      由于任务1在线程池中等待时间最长,因此任务1被丢弃。

  • 案例演示4

    1. 演示ThreadPoolExecutor.CallerRunsPolicy任务处理策略

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      public class ThreadPoolExecutorDemo04 {
      public static void main(String[] args) {

      /**
      * 核心线程数量为1 , 最大线程池数量为3, 任务容器的容量为1 ,空闲线程的最大存在时间为20s
      */
      ThreadPoolExecutor threadPoolExecutor;
      threadPoolExecutor = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,
      new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.CallerRunsPolicy());

      // 提交5个任务
      for(int x = 0 ; x < 5 ; x++) {
      threadPoolExecutor.submit(() -> {
      System.out.println(Thread.currentThread().getName() + "---->> 执行了任务");
      });
      }
      }
      }
    2. 控制台输出结果

      1
      2
      3
      4
      5
      pool-1-thread-1---->> 执行了任务
      pool-1-thread-3---->> 执行了任务
      pool-1-thread-2---->> 执行了任务
      pool-1-thread-1---->> 执行了任务
      main---->> 执行了任务

      通过控制台的输出,可看到次策略没有通过线程池中的线程执行任务,而是直接调用任务的run()方法绕过线程池直接执行。

6.原子性

1.volatile问题

代码分析 :

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

public class Demo {
public static void main(String[] args) {
MyThread1 t1 = new MyThread1();
t1.setName("小路同学");
t1.start();

MyThread2 t2 = new MyThread2();
t2.setName("小皮同学");
t2.start();
}
}
1
2
3
4
5
package com.itheima.myvolatile;

public class Money {
public static int money = 100000;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.itheima.myvolatile;

public class MyThread1 extends Thread {
@Override
public void run() {
while(Money.money == 100000){

}

System.out.println("结婚基金已经不是十万了");
}
}

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

public class MyThread2 extends Thread {
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}

Money.money = 90000;
}
}

程序问题 : 女孩虽然知道结婚基金是十万,但是当基金的余额发生变化的时候,女孩无法知道最新的余额。

2.volatile解决

  • 以上案例出现的问题 :

    当A线程修改了共享数据时,B线程没有及时获取到最新的值,如果还在使用原先的值,就会出现问题

    1. 堆内存是唯一的,每一个线程都有自己的线程栈。
    2. 每一个线程在使用堆里面变量的时候,都会先拷贝一份到变量的副本中。
    3. 在线程中,每一次使用是从变量的副本中获取的。
  • Volatile关键字 : 强制线程每次在使用的时候,都会看一下共享区域最新的值

  • 代码实现 : 使用volatile关键字解决

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

    public class Demo {
    public static void main(String[] args) {
    MyThread1 t1 = new MyThread1();
    t1.setName("小路同学");
    t1.start();

    MyThread2 t2 = new MyThread2();
    t2.setName("小皮同学");
    t2.start();
    }
    }
    1
    2
    3
    4
    5
    package com.itheima.myvolatile;

    public class Money {
    public static volatile int money = 100000;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    package com.itheima.myvolatile;

    public class MyThread1 extends Thread {
    @Override
    public void run() {
    while(Money.money == 100000){

    }

    System.out.println("结婚基金已经不是十万了");
    }
    }

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

    public class MyThread2 extends Thread {
    @Override
    public void run() {
    try {
    Thread.sleep(10);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }

    Money.money = 90000;
    }
    }

3.synchronized解决

  • synchronized解决 :

    1. 线程获得锁
    2. 清空变量副本
    3. 拷贝共享变量最新的值到变量副本中
    4. 执行代码
    5. 将修改后变量副本中的值赋值给共享数据
    6. 释放锁
  • 代码实现 :

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

    public class Demo {
    public static void main(String[] args) {
    MyThread1 t1 = new MyThread1();
    t1.setName("小路同学");
    t1.start();

    MyThread2 t2 = new MyThread2();
    t2.setName("小皮同学");
    t2.start();
    }
    }
    1
    2
    3
    4
    5
    6
    package com.itheima.myvolatile2;

    public class Money {
    public static Object lock = new Object();
    public static volatile int money = 100000;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package com.itheima.myvolatile2;

    public class MyThread1 extends Thread {
    @Override
    public void run() {
    while(true){
    synchronized (Money.lock){
    if(Money.money != 100000){
    System.out.println("结婚基金已经不是十万了");
    break;
    }
    }
    }
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    package com.itheima.myvolatile2;

    public class MyThread2 extends Thread {
    @Override
    public void run() {
    synchronized (Money.lock) {
    try {
    Thread.sleep(10);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }

    Money.money = 90000;
    }
    }
    }

4.原子性

  • 概述 : 所谓的原子性是指在一次操作或者多次操作中,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行,多个操作是一个不可以分割的整体。

  • 代码实现 :

    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
    package com.itheima.threadatom;

    public class AtomDemo {
    public static void main(String[] args) {
    MyAtomThread atom = new MyAtomThread();

    for (int i = 0; i < 100; i++) {
    new Thread(atom).start();
    }
    }
    }
    class MyAtomThread implements Runnable {
    private volatile int count = 0; //送冰淇淋的数量

    @Override
    public void run() {
    for (int i = 0; i < 100; i++) {
    //1,从共享数据中读取数据到本线程栈中.
    //2,修改本线程栈中变量副本的值
    //3,会把本线程栈中变量副本的值赋值给共享数据.
    count++;
    System.out.println("已经送了" + count + "个冰淇淋");
    }
    }
    }
  • 代码总结 : count++ 不是一个原子性操作, 他在执行的过程中,有可能被其他线程打断

5.volatile关键字不能保证原子性

解决方案 : 可以给count++操作添加锁,那么count++操作就是临界区中的代码,临界区中的代码一次只能被一个线程去执行,所以count++就变成了原子操作。

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.itheima.threadatom2;

public class AtomDemo {
public static void main(String[] args) {
MyAtomThread atom = new MyAtomThread();

for (int i = 0; i < 100; i++) {
new Thread(atom).start();
}
}
}
class MyAtomThread implements Runnable {
private volatile int count = 0; //送冰淇淋的数量
private Object lock = new Object();

@Override
public void run() {
for (int i = 0; i < 100; i++) {
//1,从共享数据中读取数据到本线程栈中.
//2,修改本线程栈中变量副本的值
//3,会把本线程栈中变量副本的值赋值给共享数据.
synchronized (lock) {
count++;
System.out.println("已经送了" + count + "个冰淇淋");
}
}
}
}

6.原子性AtomicInteger

  • 概述:java从JDK1.5开始提供了java.util.concurrent.atomic包(简称Atomic包),这个包中的原子操作类提供了一种用法简单,性能高效,线程安全地更新一个变量的方式。因为变量的类型有很多种,所以在Atomic包里一共提供了13个类,属于4种类型的原子更新方式,分别是原子更新基本类型、原子更新数组、原子更新引用和原子更新属性(字段)。使用原子的方式更新基本类型Atomic包提供了以下3个类:

    1. AtomicBoolean:原子更新布尔类型
    2. AtomicInteger:原子更新整型
    3. AtomicLong:原子更新长整型

    以上3个类提供的方法几乎一样,以AtomicInteger为例的常用方法如下:

    1
    2
    3
    4
    5
    6
    7
    8
    public AtomicInteger():	   			    初始化一个默认值为0的原子型Integer
    public AtomicInteger(int initialValue): 初始化一个指定值的原子型Integer

    int get(): 获取值
    int getAndIncrement(): 以原子方式将当前值加1,注意,这里返回的是自增前的值。
    int incrementAndGet(): 以原子方式将当前值加1,注意,这里返回的是自增后的值。
    int addAndGet(int data): 以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。
    int getAndSet(int value): 以原子方式设置为newValue的值,并返回旧值。
  • 代码实现 :

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

    import java.util.concurrent.atomic.AtomicInteger;

    public class MyAtomIntergerDemo1 {
    // public AtomicInteger(): 初始化一个默认值为0的原子型Integer
    // public AtomicInteger(int initialValue): 初始化一个指定值的原子型Integer
    public static void main(String[] args) {
    AtomicInteger ac = new AtomicInteger();
    System.out.println(ac);

    AtomicInteger ac2 = new AtomicInteger(10);
    System.out.println(ac2);
    }

    }
    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
    package com.itheima.threadatom3;

    import java.lang.reflect.Field;
    import java.util.concurrent.atomic.AtomicInteger;

    public class MyAtomIntergerDemo2 {
    // int get(): 获取值
    // int getAndIncrement(): 以原子方式将当前值加1,注意,这里返回的是自增前的值。
    // int incrementAndGet(): 以原子方式将当前值加1,注意,这里返回的是自增后的值。
    // int addAndGet(int data): 以原子方式将参数与对象中的值相加,并返回结果。
    // int getAndSet(int value): 以原子方式设置为newValue的值,并返回旧值。
    public static void main(String[] args) {
    // AtomicInteger ac1 = new AtomicInteger(10);
    // System.out.println(ac1.get());

    // AtomicInteger ac2 = new AtomicInteger(10);
    // int andIncrement = ac2.getAndIncrement();
    // System.out.println(andIncrement);
    // System.out.println(ac2.get());

    // AtomicInteger ac3 = new AtomicInteger(10);
    // int i = ac3.incrementAndGet();
    // System.out.println(i);//自增后的值
    // System.out.println(ac3.get());

    // AtomicInteger ac4 = new AtomicInteger(10);
    // int i = ac4.addAndGet(20);
    // System.out.println(i);
    // System.out.println(ac4.get());

    AtomicInteger ac5 = new AtomicInteger(100);
    int andSet = ac5.getAndSet(20);
    System.out.println(andSet);
    System.out.println(ac5.get());
    }
    }

7.AtomicInteger内存解析

  • AtomicInteger原理 : 自旋锁 + CAS 算法
  • CAS算法:
    1. 有3个操作数(内存值V, 旧的预期值A,要修改的值B)
    2. 当旧的预期值A == 内存值 此时修改成功,将V改为B
    3. 当旧的预期值A!=内存值 此时修改失败,不做任何操作
    4. 并重新获取现在的最新值(这个重新获取的动作就是自旋)

8.AtomicInteger源码解析

  • 代码实现 :

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

    public class AtomDemo {
    public static void main(String[] args) {
    MyAtomThread atom = new MyAtomThread();

    for (int i = 0; i < 100; i++) {
    new Thread(atom).start();
    }
    }
    }
    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
    package com.itheima.threadatom4;

    import java.util.concurrent.atomic.AtomicInteger;

    public class MyAtomThread implements Runnable {
    //private volatile int count = 0; //送冰淇淋的数量
    //private Object lock = new Object();
    AtomicInteger ac = new AtomicInteger(0);

    @Override
    public void run() {
    for (int i = 0; i < 100; i++) {
    //1,从共享数据中读取数据到本线程栈中.
    //2,修改本线程栈中变量副本的值
    //3,会把本线程栈中变量副本的值赋值给共享数据.
    //synchronized (lock) {
    // count++;
    // ac++;
    int count = ac.incrementAndGet();
    System.out.println("已经送了" + count + "个冰淇淋");
    // }
    }
    }
    }

  • 源码解析 :

    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
    //先自增,然后获取自增后的结果
    public final int incrementAndGet() {
    //+ 1 自增后的结果
    //this 就表示当前的atomicInteger(值)
    //1 自增一次
    return U.getAndAddInt(this, VALUE, 1) + 1;
    }

    public final int getAndAddInt(Object o, long offset, int delta) {
    //v 旧值
    int v;
    //自旋的过程
    do {
    //不断的获取旧值
    v = getIntVolatile(o, offset);
    //如果这个方法的返回值为false,那么继续自旋
    //如果这个方法的返回值为true,那么自旋结束
    //o 表示的就是内存值
    //v 旧值
    //v + delta 修改后的值
    } while (!weakCompareAndSetInt(o, offset, v, v + delta));
    //作用:比较内存中的值,旧值是否相等,如果相等就把修改后的值写到内存中,返回true。表示修改成功。
    // 如果不相等,无法把修改后的值写到内存中,返回false。表示修改失败。
    //如果修改失败,那么继续自旋。
    return v;
    }

9.悲观锁和乐观锁

  • synchronized和CAS的区别 :
    1. 相同点:在多线程情况下,都可以保证共享数据的安全性。
    2. 不同点:
      • synchronized总是从最坏的角度出发,认为每次获取数据的时候,别人都有可能修改。所以在每次操作共享数据之前,都会上锁。(悲观锁)
      • cas是从乐观的角度出发,假设每次获取数据别人都不会修改,所以不会上锁。只不过在修改共享数据的时候,会检查一下,别人有没有修改过这个数据。
      • 如果别人修改过,那么我再次获取现在最新的值。
      • 如果别人没有修改过,那么我现在直接修改共享数据的值.(乐观锁)

7.并发工具类

1.Hashtable

  • Hashtable出现的原因 : 在集合类中HashMap是比较常用的集合对象,但是HashMap是线程不安全的(多线程环境下可能会存在问题)。为了保证数据的安全性可以使用Hashtable,但是Hashtable的效率低下。

  • 代码实现 :

    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.itheima.mymap;

    import java.util.HashMap;
    import java.util.Hashtable;

    public class MyHashtableDemo {
    public static void main(String[] args) throws InterruptedException {
    Hashtable<String, String> hm = new Hashtable<>();

    Thread t1 = new Thread(() -> {
    for (int i = 0; i < 25; i++) {
    hm.put(i + "", i + "");
    }
    });


    Thread t2 = new Thread(() -> {
    for (int i = 25; i < 51; i++) {
    hm.put(i + "", i + "");
    }
    });

    t1.start();
    t2.start();

    System.out.println("----------------------------");
    //为了t1和t2能把数据全部添加完毕
    Thread.sleep(1000);

    //0-0 1-1 ..... 50- 50

    for (int i = 0; i < 51; i++) {
    System.out.println(hm.get(i + ""));
    }//0 1 2 3 .... 50


    }
    }

2.ConcurrentHashMap基本使用

  • ConcurrentHashMap出现的原因 : 在集合类中HashMap是比较常用的集合对象,但是HashMap是线程不安全的(多线程环境下可能会存在问题)。为了保证数据的安全性可以使用Hashtable,但是Hashtable的效率低下。

  • 总结 :

    1. HashMap是线程不安全的。多线程环境下会有数据安全问题
    2. Hashtable是线程安全的,但是会将整张表锁起来,效率低下
    3. ConcurrentHashMap也是线程安全的,效率较高。
  • 代码实现 :

    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
    package com.itheima.mymap;

    import java.util.Hashtable;
    import java.util.concurrent.ConcurrentHashMap;

    public class MyConcurrentHashMapDemo {
    public static void main(String[] args) throws InterruptedException {
    ConcurrentHashMap<String, String> hm = new ConcurrentHashMap<>(100);

    Thread t1 = new Thread(() -> {
    for (int i = 0; i < 25; i++) {
    hm.put(i + "", i + "");
    }
    });


    Thread t2 = new Thread(() -> {
    for (int i = 25; i < 51; i++) {
    hm.put(i + "", i + "");
    }
    });

    t1.start();
    t2.start();

    System.out.println("----------------------------");
    //为了t1和t2能把数据全部添加完毕
    Thread.sleep(1000);

    //0-0 1-1 ..... 50- 50

    for (int i = 0; i < 51; i++) {
    System.out.println(hm.get(i + ""));
    }//0 1 2 3 .... 50
    }
    }

3.ConcurrentHashMap & ConcurrentHashMap

4.CountDownLatch

  • CountDownLatch类 :

    方法 解释
    public CountDownLatch(int count) 参数传递线程数,表示等待线程数量
    public void await() 让线程等待
    public void countDown() 当前线程执行完毕

    使用场景: 让某一条线程等待其他线程执行完毕之后再执行

  • 代码实现 :

    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.itheima.mycountdownlatch;

    import java.util.concurrent.CountDownLatch;

    public class ChileThread1 extends Thread {

    private CountDownLatch countDownLatch;
    public ChileThread1(CountDownLatch countDownLatch) {
    this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
    //1.吃饺子
    for (int i = 1; i <= 10; i++) {
    System.out.println(getName() + "在吃第" + i + "个饺子");
    }
    //2.吃完说一声
    //每一次countDown方法的时候,就让计数器-1
    countDownLatch.countDown();
    }
    }

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

    import java.util.concurrent.CountDownLatch;

    public class ChileThread2 extends Thread {

    private CountDownLatch countDownLatch;
    public ChileThread2(CountDownLatch countDownLatch) {
    this.countDownLatch = countDownLatch;
    }
    @Override
    public void run() {
    //1.吃饺子
    for (int i = 1; i <= 15; i++) {
    System.out.println(getName() + "在吃第" + i + "个饺子");
    }
    //2.吃完说一声
    //每一次countDown方法的时候,就让计数器-1
    countDownLatch.countDown();
    }
    }

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

    import java.util.concurrent.CountDownLatch;

    public class ChileThread3 extends Thread {

    private CountDownLatch countDownLatch;
    public ChileThread3(CountDownLatch countDownLatch) {
    this.countDownLatch = countDownLatch;
    }
    @Override
    public void run() {
    //1.吃饺子
    for (int i = 1; i <= 20; i++) {
    System.out.println(getName() + "在吃第" + i + "个饺子");
    }
    //2.吃完说一声
    //每一次countDown方法的时候,就让计数器-1
    countDownLatch.countDown();
    }
    }

    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.itheima.mycountdownlatch;

    import java.util.concurrent.CountDownLatch;

    public class MotherThread extends Thread {
    private CountDownLatch countDownLatch;
    public MotherThread(CountDownLatch countDownLatch) {
    this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
    //1.等待
    try {
    //当计数器变成0的时候,会自动唤醒这里等待的线程。
    countDownLatch.await();
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    //2.收拾碗筷
    System.out.println("妈妈在收拾碗筷");
    }
    }

    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.itheima.mycountdownlatch;

    import java.util.concurrent.CountDownLatch;

    public class MyCountDownLatchDemo {
    public static void main(String[] args) {
    //1.创建CountDownLatch的对象,需要传递给四个线程。
    //在底层就定义了一个计数器,此时计数器的值就是3
    CountDownLatch countDownLatch = new CountDownLatch(3);
    //2.创建四个线程对象并开启他们。
    MotherThread motherThread = new MotherThread(countDownLatch);
    motherThread.start();

    ChileThread1 t1 = new ChileThread1(countDownLatch);
    t1.setName("小明");

    ChileThread2 t2 = new ChileThread2(countDownLatch);
    t2.setName("小红");

    ChileThread3 t3 = new ChileThread3(countDownLatch);
    t3.setName("小刚");

    t1.start();
    t2.start();
    t3.start();
    }
    }
  • 总结 :

    1. CountDownLatch(int count):参数写等待线程的数量。并定义了一个计数器。
    2. await():让线程等待,当计数器为0时,会唤醒等待的线程
    3. countDown(): 线程执行完毕时调用,会将计数器-1。

5.Semaphore

  • 使用场景 :

    可以控制访问特定资源的线程数量。

  • 实现步骤 :

    1. 需要有人管理这个通道
    2. 当有车进来了,发通行许可证
    3. 当车出去了,收回通行许可证
    4. 如果通行许可证发完了,那么其他车辆只能等着
  • 代码实现 :

    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.itheima.mysemaphore;

    import java.util.concurrent.Semaphore;

    public class MyRunnable implements Runnable {
    //1.获得管理员对象,
    private Semaphore semaphore = new Semaphore(2);
    @Override
    public void run() {
    //2.获得通行证
    try {
    semaphore.acquire();
    //3.开始行驶
    System.out.println("获得了通行证开始行驶");
    Thread.sleep(2000);
    System.out.println("归还通行证");
    //4.归还通行证
    semaphore.release();
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }

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

    public class MySemaphoreDemo {
    public static void main(String[] args) {
    MyRunnable mr = new MyRunnable();

    for (int i = 0; i < 100; i++) {
    new Thread(mr).start();
    }
    }
    }

16.网络编程

1.网络编程入门

  • 网络编程概述

    1. 计算机网络

      指将地理位置不同且具有独立功能的多台计算机及其它外部设备,通过通信线路连接起来,在网络操作系统、网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统

    2. 网络编程

      在网络通信协议下,不同计算机上运行的程序,可以进行数据传输

  • 网络编程三要素

    1. IP地址:是网络中设备的唯一标识

      • IP地址分为两大类
        • IPv4:32位地址长度,“点分十进制表示法”。
        • IPv6:128位地址长度,每16个字节一组,分成8组十六进制数,“冒分十六进制表示法”。
      • 特殊IP地址:127.0.0.1:是回送地址,可以代表本机地址,一般用来测试使用
    2. 端口:设备上应用程序的唯一标识

      • 端口号:用两个字节表示的整数,它的取值范围是065535。其中,01023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。
    3. 协议:计算机网络中,连接和通信的规则被称为网络通信协议

      • UDP协议

        1. 用户数据报协议(User Datagram Protocol)
        2. UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据,一次最多发送64KB。
        3. 由于使用UDP协议消耗系统资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输
        4. 例如视频会议通常采用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议
      • TCP协议

        1. 传输控制协议 (Transmission Control Protocol)

        2. TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”

        3. 三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠

          • 第一次握手,客户端向服务器端发出连接请求,等待服务器确认
          • 第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求
          • 第三次握手,客户端再次向服务器端发送确认信息,确认连接
        4. 完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用十分广泛。例如上传文件、下载文件、浏览网页等

  • InetAddress:此类表示Internet协议(IP)地址

    1. 相关方法

      方法名 说明
      static InetAddress getByName(String host) 确定主机名称的IP地址。主机名称可以是机器名称,也可以是IP地址
      String getHostName() 获取此IP地址的主机名
      String getHostAddress() 返回文本显示中的IP地址字符串
    2. 代码演示

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      public class InetAddressDemo {
      public static void main(String[] args) throws UnknownHostException {
      //InetAddress address = InetAddress.getByName("LAPTOP-LFSA4920");
      InetAddress address = InetAddress.getByName("192.168.1.66");

      //public String getHostName():获取此IP地址的主机名
      String name = address.getHostName();
      //public String getHostAddress():返回文本显示中的IP地址字符串
      String ip = address.getHostAddress();

      System.out.println("主机名:" + name);
      System.out.println("IP地址:" + ip);
      }
      }

2.UDP通信程序

1.UDP发送数据

  • Java中的UDP通信

    1. UDP协议是一种不可靠的网络协议,它在通信的两端各建立一个Socket对象,但是这两个Socket只是发送,接收数据的对象,因此对于基于UDP协议的通信双方而言,没有所谓的客户端和服务器的概念
    2. Java提供了DatagramSocket类作为基于UDP协议的Socket
  • 构造方法

    方法名 说明
    DatagramSocket() 创建数据报套接字并将其绑定到本机地址上的任何可用端口
    DatagramPacket(byte[] buf,int len,InetAddress add,int port) 创建数据包,发送长度为len的数据包到指定主机的指定端口
  • 相关方法

    方法名 说明
    void send(DatagramPacket p) 发送数据报包
    void close() 关闭数据报套接字
    void receive(DatagramPacket p) 从此套接字接受数据报包
  • 发送数据的步骤

    1. 创建发送端的Socket对象(DatagramSocket)
    2. 创建数据,并把数据打包
    3. 调用DatagramSocket对象的方法发送数据
    4. 关闭发送端
  • 代码演示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public class SendDemo {
    public static void main(String[] args) throws IOException {
    //创建发送端的Socket对象(DatagramSocket)
    // DatagramSocket() 构造数据报套接字并将其绑定到本地主机上的任何可用端口
    DatagramSocket ds = new DatagramSocket();

    //创建数据,并把数据打包
    //DatagramPacket(byte[] buf, int length, InetAddress address, int port)
    //构造一个数据包,发送长度为 length的数据包到指定主机上的指定端口号。
    byte[] bys = "hello,udp,我来了".getBytes();

    DatagramPacket dp = new DatagramPacket(bys,bys.length,InetAddress.getByName("127.0.0.1"),10086);

    //调用DatagramSocket对象的方法发送数据
    //void send(DatagramPacket p) 从此套接字发送数据报包
    ds.send(dp);

    //关闭发送端
    //void close() 关闭此数据报套接字
    ds.close();
    }
    }

2.UDP接收数据

  • 接收数据的步骤

    1. 创建接收端的Socket对象(DatagramSocket)
    2. 创建一个数据包,用于接收数据
    3. 调用DatagramSocket对象的方法接收数据
    4. 解析数据包,并把数据在控制台显示
    5. 关闭接收端
  • 构造方法

    方法名 说明
    DatagramPacket(byte[] buf, int len) 创建一个DatagramPacket用于接收长度为len的数据包
  • 相关方法

    方法名 说明
    byte[] getData() 返回数据缓冲区
    int getLength() 返回要发送的数据的长度或接收的数据的长度
  • 示例代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class ReceiveDemo {
    public static void main(String[] args) throws IOException {
    //创建接收端的Socket对象(DatagramSocket)
    DatagramSocket ds = new DatagramSocket(10086);

    //创建一个数据包,用于接收数据
    byte[] bys = new byte[1024];
    DatagramPacket dp = new DatagramPacket(bys, bys.length);

    //调用DatagramSocket对象的方法接收数据
    ds.receive(dp);

    ds.close;

    //解析数据包,并把数据在控制台显示
    System.out.println("数据是:" + new String(dp.getData(), 0, dp.getLength()));
    }
    }
    }

3.UDP通信程序:聊天室

  • 案例需求

    UDP发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束

    UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收

  • 代码实现

    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
    /*
    UDP发送数据:
    数据来自于键盘录入,直到输入的数据是886,发送数据结束
    */
    public class SendDemo {
    public static void main(String[] args) throws IOException {
    //创建发送端的Socket对象(DatagramSocket)
    DatagramSocket ds = new DatagramSocket();
    //键盘录入数据
    Scanner sc = new Scanner(System.in);
    while (true) {
    String s = sc.nextLine();
    //输入的数据是886,发送数据结束
    if ("886".equals(s)) {
    break;
    }
    //创建数据,并把数据打包
    byte[] bys = s.getBytes();
    DatagramPacket dp = new DatagramPacket(bys, bys.length, InetAddress.getByName("192.168.1.66"), 12345);

    //调用DatagramSocket对象的方法发送数据
    ds.send(dp);
    }
    //关闭发送端
    ds.close();
    }
    }

    /*
    UDP接收数据:
    因为接收端不知道发送端什么时候停止发送,故采用死循环接收
    */
    public class ReceiveDemo {
    public static void main(String[] args) throws IOException {
    //创建接收端的Socket对象(DatagramSocket)
    DatagramSocket ds = new DatagramSocket(12345);
    while (true) {
    //创建一个数据包,用于接收数据
    byte[] bys = new byte[1024];
    DatagramPacket dp = new DatagramPacket(bys, bys.length);
    //调用DatagramSocket对象的方法接收数据
    ds.receive(dp);
    //解析数据包,并把数据在控制台显示
    System.out.println("数据是:" + new String(dp.getData(), 0, dp.getLength()));
    }
    //关闭接收端
    // ds.close();
    }
    }

4.UDP三种通讯方式

  • 单播

    单播用于两个主机之间的端对端通信

  • 组播

    组播用于对一组特定的主机进行通信

  • 广播

    广播用于一个主机对整个局域网上所有主机上的数据通信

1.UDP组播实现

  • 实现步骤

    • 发送端
      1. 创建发送端的Socket对象(DatagramSocket)
      2. 创建数据,并把数据打包(DatagramPacket)
      3. 调用DatagramSocket对象的方法发送数据(在单播中,这里是发给指定IP的电脑但是在组播当中,这里是发给组播地址)
      4. 释放资源
    • 接收端
      1. 创建接收端Socket对象(MulticastSocket)
      2. 创建一个箱子,用于接收数据
      3. 把当前计算机绑定一个组播地址
      4. 将数据接收到箱子中
      5. 解析数据包,并打印数据
      6. 释放资源
  • 代码实现

    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
    // 发送端
    public class ClinetDemo {
    public static void main(String[] args) throws IOException {
    // 1. 创建发送端的Socket对象(DatagramSocket)
    DatagramSocket ds = new DatagramSocket();
    String s = "hello 组播";
    byte[] bytes = s.getBytes();
    InetAddress address = InetAddress.getByName("224.0.1.0");
    int port = 10000;
    // 2. 创建数据,并把数据打包(DatagramPacket)
    DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
    // 3. 调用DatagramSocket对象的方法发送数据(在单播中,这里是发给指定IP的电脑但是在组播当中,这里是发给组播地址)
    ds.send(dp);
    // 4. 释放资源
    ds.close();
    }
    }
    // 接收端
    public class ServerDemo {
    public static void main(String[] args) throws IOException {
    // 1. 创建接收端Socket对象(MulticastSocket)
    MulticastSocket ms = new MulticastSocket(10000);
    // 2. 创建一个箱子,用于接收数据
    DatagramPacket dp = new DatagramPacket(new byte[1024],1024);
    // 3. 把当前计算机绑定一个组播地址,表示添加到这一组中.
    ms.joinGroup(InetAddress.getByName("224.0.1.0"));
    // 4. 将数据接收到箱子中
    ms.receive(dp);
    // 5. 解析数据包,并打印数据
    byte[] data = dp.getData();
    int length = dp.getLength();
    System.out.println(new String(data,0,length));
    // 6. 释放资源
    ms.close();
    }
    }

2.UDP广播实现

  • 实现步骤

    • 发送端
      1. 创建发送端Socket对象(DatagramSocket)
      2. 创建存储数据的箱子,将广播地址封装进去
      3. 发送数据
      4. 释放资源
    • 接收端
      1. 创建接收端的Socket对象(DatagramSocket)
      2. 创建一个数据包,用于接收数据
      3. 调用DatagramSocket对象的方法接收数据
      4. 解析数据包,并把数据在控制台显示
      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
    28
    29
    30
    31
    32
    33
    34
    35
    // 发送端
    public class ClientDemo {
    public static void main(String[] args) throws IOException {
    // 1. 创建发送端Socket对象(DatagramSocket)
    DatagramSocket ds = new DatagramSocket();
    // 2. 创建存储数据的箱子,将广播地址封装进去
    String s = "广播 hello";
    byte[] bytes = s.getBytes();
    InetAddress address = InetAddress.getByName("255.255.255.255");
    int port = 10000;
    DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
    // 3. 发送数据
    ds.send(dp);
    // 4. 释放资源
    ds.close();
    }
    }

    // 接收端
    public class ServerDemo {
    public static void main(String[] args) throws IOException {
    // 1. 创建接收端的Socket对象(DatagramSocket)
    DatagramSocket ds = new DatagramSocket(10000);
    // 2. 创建一个数据包,用于接收数据
    DatagramPacket dp = new DatagramPacket(new byte[1024],1024);
    // 3. 调用DatagramSocket对象的方法接收数据
    ds.receive(dp);
    // 4. 解析数据包,并把数据在控制台显示
    byte[] data = dp.getData();
    int length = dp.getLength();
    System.out.println(new String(data,0,length));
    // 5. 关闭接收端
    ds.close();
    }
    }

3.TCP通信程序

1.TCP发送数据

  • Java中的TCP通信

    1. Java对基于TCP协议的的网络提供了良好的封装,使用Socket对象来代表两端的通信端口,并通过Socket产生IO流来进行网络通信。
    2. Java为客户端提供了Socket类,为服务器端提供了ServerSocket类
  • 构造方法

    方法名 说明
    Socket(InetAddress address,int port) 创建流套接字并将其连接到指定IP指定端口号
    Socket(String host, int port) 创建流套接字并将其连接到指定主机上的指定端口号
  • 相关方法

    方法名 说明
    InputStream getInputStream() 返回此套接字的输入流
    OutputStream getOutputStream() 返回此套接字的输出流
  • 示例代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class Client {
    public static void main(String[] args) throws IOException {
    //TCP协议,发送数据

    //1.创建Socket对象
    //细节:在创建对象的同时会连接服务端
    // 如果连接不上,代码会报错
    Socket socket = new Socket("127.0.0.1",10000);

    //2.可以从连接通道中获取输出流
    OutputStream os = socket.getOutputStream();
    //写出数据
    os.write("aaa".getBytes());

    //3.释放资源
    os.close();
    socket.close();
    }
    }

2.TCP接收数据

  • 构造方法

    方法名 说明
    ServletSocket(int port) 创建绑定到指定端口的服务器套接字
  • 相关方法

    方法名 说明
    Socket accept() 监听要连接到此的套接字并接受它
  • 注意事项

    1. accept方法是阻塞的,作用就是等待客户端连接
    2. 客户端创建对象并连接服务器,此时是通过三次握手协议,保证跟服务器之间的连接
    3. 针对客户端来讲,是往外写的,所以是输出流
      针对服务器来讲,是往里读的,所以是输入流
    4. read方法也是阻塞的
    5. 客户端在关流的时候,还多了一个往服务器写结束标记的动作
    6. 最后一步断开连接,通过四次挥手协议保证连接终止
  • 三次握手和四次挥手

    1. 三次握手

    2. 四次挥手

  • 示例代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public class Server {
    public static void main(String[] args) throws IOException {
    //TCP协议,接收数据

    //1.创建对象ServerSocker
    ServerSocket ss = new ServerSocket(10000);

    //2.监听客户端的链接
    Socket socket = ss.accept();

    //3.从连接通道中获取输入流读取数据
    InputStream is = socket.getInputStream();
    int b;
    while ((b = is.read()) != -1){
    System.out.println((char) b);
    }

    //4.释放资源
    socket.close();
    ss.close();
    }
    }

3.TCP程序练习(传输中文)

发送端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Client {
public static void main(String[] args) throws IOException {
//TCP协议,发送数据

//1.创建Socket对象
//细节:在创建对象的同时会连接服务端
// 如果连接不上,代码会报错
Socket socket = new Socket("127.0.0.1",10000);


//2.可以从连接通道中获取输出流
OutputStream os = socket.getOutputStream();
//写出数据
os.write("你好你好".getBytes());//12字节

//3.释放资源
os.close();
socket.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
public class Server {
public static void main(String[] args) throws IOException {
//TCP协议,接收数据

//1.创建对象ServerSocker
ServerSocket ss = new ServerSocket(10000);

//2.监听客户端的链接
Socket socket = ss.accept();

//3.从连接通道中获取输入流读取数据
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);

// BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));

int b;
while ((b = br.read()) != -1){
System.out.print((char) b);
}

//4.释放资源
socket.close();
ss.close();

}
}

4. 综合练习

1.多发多收

  • 需求:

    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
    public class Client {
    public static void main(String[] args) throws IOException {
    //客户端:多次发送数据
    //服务器:接收多次接收数据,并打印

    //1. 创建Socket对象并连接服务端
    Socket socket = new Socket("127.0.0.1",10000);

    //2.写出数据
    Scanner sc = new Scanner(System.in);
    OutputStream os = socket.getOutputStream();

    while (true) {
    System.out.println("请输入您要发送的信息");
    String str = sc.nextLine();
    if("886".equals(str)){
    break;
    }
    os.write(str.getBytes());
    }
    //3.释放资源
    socket.close();
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class Server {
    public static void main(String[] args) throws IOException {
    //客户端:多次发送数据
    //服务器:接收多次接收数据,并打印

    //1.创建对象绑定10000端口
    ServerSocket ss = new ServerSocket(10000);

    //2.等待客户端来连接
    Socket socket = ss.accept();

    //3.读取数据
    InputStreamReader isr = new InputStreamReader(socket.getInputStream());
    int b;
    while ((b = isr.read()) != -1){
    System.out.print((char)b);
    }

    //4.释放资源
    socket.close();
    ss.close();
    }
    }

2.接收并反馈

  • 案例需求

    客户端:发送数据,接受服务器反馈

    服务器:收到消息后给出反馈

  • 案例分析

    1. 客户端创建对象,使用输出流输出数据
    2. 服务端创建对象,使用输入流接受数据
    3. 服务端使用输出流给出反馈数据
    4. 客户端使用输入流接受反馈数据
  • 代码实现

    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
    // 客户端
    public class ClientDemo {
    public static void main(String[] args) throws IOException {
    Socket socket = new Socket("127.0.0.1",10000);

    OutputStream os = socket.getOutputStream();
    os.write("hello".getBytes());
    // os.close();如果在这里关流,会导致整个socket都无法使用
    socket.shutdownOutput();//仅仅关闭输出流.并写一个结束标记,对socket没有任何影响

    BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    String line;
    while((line = br.readLine())!=null){
    System.out.println(line);
    }
    br.close();
    os.close();
    socket.close();
    }
    }
    // 服务器
    public class ServerDemo {
    public static void main(String[] args) throws IOException {
    ServerSocket ss = new ServerSocket(10000);

    Socket accept = ss.accept();

    InputStream is = accept.getInputStream();
    int b;
    while((b = is.read())!=-1){
    System.out.println((char) b);
    }

    System.out.println("看看我执行了吗?");

    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
    bw.write("你谁啊?");
    bw.newLine();
    bw.flush();

    bw.close();
    is.close();
    accept.close();
    ss.close();
    }
    }

3.上传练习(TCP协议)

  • 案例需求

    1. 客户端:数据来自于本地文件,接收服务器反馈
    2. 服务器:接收到的数据写入本地文件,给出反馈
  • 案例分析

    1. 创建客户端对象,创建输入流对象指向文件,每读一次数据就给服务器输出一次数据,输出结束后使用shutdownOutput()方法告知服务端传输结束
    2. 创建服务器对象,创建输出流对象指向文件,每接受一次数据就使用输出流输出到文件中,传输结束后。使用输出流给客户端反馈信息
    3. 客户端接受服务端的回馈信息
  • 相关方法

    方法名 说明
    void shutdownInput() 将此套接字的输入流放置在“流的末尾”
    void shutdownOutput() 禁止用此套接字的输出流
  • 代码实现

    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
    public class Client {
    public static void main(String[] args) throws IOException {
    //客户端:将本地文件上传到服务器。接收服务器的反馈。
    //服务器:接收客户端上传的文件,上传完毕之后给出反馈。


    //1. 创建Socket对象,并连接服务器
    Socket socket = new Socket("127.0.0.1",10000);

    //2.读取本地文件中的数据,并写到服务器当中
    BufferedInputStream bis = new BufferedInputStream(new FileInputStream("mysocketnet\\clientdir\\a.jpg"));
    BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
    byte[] bytes = new byte[1024];
    int len;
    while ((len = bis.read(bytes)) != -1){
    bos.write(bytes,0,len);
    }

    //往服务器写出结束标记
    socket.shutdownOutput();


    //3.接收服务器的回写数据
    BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    String line = br.readLine();
    System.out.println(line);


    //4.释放资源
    socket.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
    public class Server {
    public static void main(String[] args) throws IOException {
    //客户端:将本地文件上传到服务器。接收服务器的反馈。
    //服务器:接收客户端上传的文件,上传完毕之后给出反馈。


    //1.创建对象并绑定端口
    ServerSocket ss = new ServerSocket(10000);

    //2.等待客户端来连接
    Socket socket = ss.accept();

    //3.读取数据并保存到本地文件中
    BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
    BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("mysocketnet\\serverdir\\a.jpg"));
    int len;
    byte[] bytes = new byte[1024];
    while ((len = bis.read(bytes)) != -1){
    bos.write(bytes,0,len);
    }
    bos.close();
    //4.回写数据
    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
    bw.write("上传成功");
    bw.newLine();
    bw.flush();

    //5.释放资源
    socket.close();
    ss.close();
    }
    }

4.文件名重复

1
2
3
4
5
6
public class UUIDTest {
public static void main(String[] args) {
String str = UUID.randomUUID().toString().replace("-", "");
System.out.println(str);//9f15b8c356c54f55bfcb0ee3023fce8a
}
}
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
public class Client {
public static void main(String[] args) throws IOException {
//客户端:将本地文件上传到服务器。接收服务器的反馈。
//服务器:接收客户端上传的文件,上传完毕之后给出反馈。


//1. 创建Socket对象,并连接服务器
Socket socket = new Socket("127.0.0.1",10000);

//2.读取本地文件中的数据,并写到服务器当中
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("mysocketnet\\clientdir\\a.jpg"));
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
byte[] bytes = new byte[1024];
int len;
while ((len = bis.read(bytes)) != -1){
bos.write(bytes,0,len);
}

//往服务器写出结束标记
socket.shutdownOutput();


//3.接收服务器的回写数据
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = br.readLine();
System.out.println(line);


//4.释放资源
socket.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
public class Server {
public static void main(String[] args) throws IOException {
//客户端:将本地文件上传到服务器。接收服务器的反馈。
//服务器:接收客户端上传的文件,上传完毕之后给出反馈。


//1.创建对象并绑定端口
ServerSocket ss = new ServerSocket(10000);

//2.等待客户端来连接
Socket socket = ss.accept();

//3.读取数据并保存到本地文件中
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
String name = UUID.randomUUID().toString().replace("-", "");
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("mysocketnet\\serverdir\\" + name + ".jpg"));
int len;
byte[] bytes = new byte[1024];
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes, 0, len);
}
bos.close();
//4.回写数据
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write("上传成功");
bw.newLine();
bw.flush();

//5.释放资源
socket.close();
ss.close();
}
}

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
28
29
30
31
32
33
public class Client {
public static void main(String[] args) throws IOException {
//客户端:将本地文件上传到服务器。接收服务器的反馈。
//服务器:接收客户端上传的文件,上传完毕之后给出反馈。


//1. 创建Socket对象,并连接服务器
Socket socket = new Socket("127.0.0.1",10000);

//2.读取本地文件中的数据,并写到服务器当中
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("mysocketnet\\clientdir\\a.jpg"));
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
byte[] bytes = new byte[1024];
int len;
while ((len = bis.read(bytes)) != -1){
bos.write(bytes,0,len);
}

//往服务器写出结束标记
socket.shutdownOutput();


//3.接收服务器的回写数据
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = br.readLine();
System.out.println(line);


//4.释放资源
socket.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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public class Server {
public static void main(String[] args) throws IOException {
//客户端:将本地文件上传到服务器。接收服务器的反馈。
//服务器:接收客户端上传的文件,上传完毕之后给出反馈。


//1.创建对象并绑定端口
ServerSocket ss = new ServerSocket(10000);

while (true) {
//2.等待客户端来连接
Socket socket = ss.accept();

//开启一条线程
//一个用户就对应服务端的一条线程
new Thread(new MyRunnable(socket)).start();
}

}
}


public class MyRunnable implements Runnable{

Socket socket;

public MyRunnable(Socket socket){
this.socket = socket;
}

@Override
public void run() {
try {
//3.读取数据并保存到本地文件中
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
String name = UUID.randomUUID().toString().replace("-", "");
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("mysocketnet\\serverdir\\" + name + ".jpg"));
int len;
byte[] bytes = new byte[1024];
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes, 0, len);
}
bos.close();
//4.回写数据
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write("上传成功");
bw.newLine();
bw.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
//5.释放资源
if(socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

6.线程池改进

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
public class Client {
public static void main(String[] args) throws IOException {
//客户端:将本地文件上传到服务器。接收服务器的反馈。
//服务器:接收客户端上传的文件,上传完毕之后给出反馈。


//1. 创建Socket对象,并连接服务器
Socket socket = new Socket("127.0.0.1",10000);

//2.读取本地文件中的数据,并写到服务器当中
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("mysocketnet\\clientdir\\a.jpg"));
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
byte[] bytes = new byte[1024];
int len;
while ((len = bis.read(bytes)) != -1){
bos.write(bytes,0,len);
}

//往服务器写出结束标记
socket.shutdownOutput();


//3.接收服务器的回写数据
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = br.readLine();
System.out.println(line);


//4.释放资源
socket.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
public class Server {
public static void main(String[] args) throws IOException {
//客户端:将本地文件上传到服务器。接收服务器的反馈。
//服务器:接收客户端上传的文件,上传完毕之后给出反馈。


//创建线程池对象
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3,//核心线程数量
16,//线程池总大小
60,//空闲时间
TimeUnit.SECONDS,//空闲时间(单位)
new ArrayBlockingQueue<>(2),//队列
Executors.defaultThreadFactory(),//线程工厂,让线程池如何创建线程对象
new ThreadPoolExecutor.AbortPolicy()//阻塞队列
);



//1.创建对象并绑定端口
ServerSocket ss = new ServerSocket(10000);

while (true) {
//2.等待客户端来连接
Socket socket = ss.accept();

//开启一条线程
//一个用户就对应服务端的一条线程
//new Thread(new MyRunnable(socket)).start();
pool.submit(new MyRunnable(socket));
}

}
}
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
public class MyRunnable implements Runnable{

Socket socket;

public MyRunnable(Socket socket){
this.socket = socket;
}

@Override
public void run() {
try {
//3.读取数据并保存到本地文件中
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
String name = UUID.randomUUID().toString().replace("-", "");
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("mysocketnet\\serverdir\\" + name + ".jpg"));
int len;
byte[] bytes = new byte[1024];
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes, 0, len);
}
bos.close();
//4.回写数据
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write("上传成功");
bw.newLine();
bw.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
//5.释放资源
if(socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

17.反射

1.反射的概述

  • 专业解释(了解):

    1. 在运行状态中,对于任意一个类,都能知道这个类的所有属性和方法;
    2. 对于任意一个对象,都能够调用它的任意属性和方法;
    3. 这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。
  • 通俗的理解:(掌握)

    1. 利用反射创建的对象可以无视修饰符调用类里面的内容
    2. 可以跟配置文件结合起来使用,把要创建的对象信息和方法写在配置文件中。
  • 反射底学什么?

    反射都是从class字节码文件中获取的内容。

    1. 如何获取class字节码文件的对象
    2. 利用反射如何获取构造方法(创建对象)
    3. 利用反射如何获取成员变量(赋值,获取值)
    4. 利用反射如何获取成员方法(运行)
  • 获取字节码文件对象的三种方式

    1. Class这个类里面的静态方法forName(“全类名”)(最常用)
    2. 通过class属性获取
    3. 通过对象获取字节码文件对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    //1.Class这个类里面的静态方法forName
    //Class.forName("类的全类名"): 全类名 = 包名 + 类名
    Class clazz1 = Class.forName("com.itheima.reflectdemo.Student");
    //源代码阶段获取 --- 先把Student加载到内存中,再获取字节码文件的对象
    //clazz 就表示Student这个类的字节码文件对象。
    //就是当Student.class这个文件加载到内存之后,产生的字节码文件对象


    //2.通过class属性获取
    //类名.class
    Class clazz2 = Student.class;

    //因为class文件在硬盘中是唯一的,所以,当这个文件加载到内存之后产生的对象也是唯一的
    System.out.println(clazz1 == clazz2);//true


    //3.通过Student对象获取字节码文件对象
    Student s = new Student();
    Class clazz3 = s.getClass();
    System.out.println(clazz1 == clazz2);//true
    System.out.println(clazz2 == clazz3);//true
  • 字节码文件和字节码文件对象

    1. java文件:自己编写的java代码。

    2. 字节码文件:通过java文件编译之后的class文件(是在硬盘上真实存在的,用眼睛能看到的)

    3. 字节码文件对象:当class文件加载到内存之后,虚拟机自动创建出来的对象。

      这个对象里面至少包含了:构造方法,成员变量,成员方法。

2.获取构造方法

  • 规则:

    1. get表示获取
    2. Declared表示私有
    3. s表示复数形式
  • 若当前获取到的是私有的,必须临时修改访问权限,否则无法使用

    方法名 说明
    Constructor<?>[] getConstructors() 获得所有的构造(只能public修饰)
    Constructor<?>[] getDeclaredConstructors() 获得所有的构造(包含private修饰)
    Constructor getConstructor(Class<?>… parameterTypes) 获取指定构造(只能public修饰)
    Constructor getDeclaredConstructor(Class<?>… parameterTypes) 获取指定构造(包含private修饰)
  • 代码示例:

    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
    public class ReflectDemo2 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
    //1.获得整体(class字节码文件对象)
    Class clazz = Class.forName("com.itheima.reflectdemo.Student");


    //2.获取构造方法对象
    //获取所有构造方法(public)
    Constructor[] constructors1 = clazz.getConstructors();
    for (Constructor constructor : constructors1) {
    System.out.println(constructor);
    }

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

    //获取所有构造(带私有的)
    Constructor[] constructors2 = clazz.getDeclaredConstructors();

    for (Constructor constructor : constructors2) {
    System.out.println(constructor);
    }
    System.out.println("=======================");

    //获取指定的空参构造
    Constructor con1 = clazz.getConstructor();
    System.out.println(con1);

    Constructor con2 = clazz.getConstructor(String.class,int.class);
    System.out.println(con2);

    System.out.println("=======================");
    //获取指定的构造(所有构造都可以获取到,包括public包括private)
    Constructor con3 = clazz.getDeclaredConstructor();
    System.out.println(con3);
    //了解 System.out.println(con3 == con1);
    //每一次获取构造方法对象的时候,都会新new一个。

    Constructor con4 = clazz.getDeclaredConstructor(String.class);
    System.out.println(con4);
    }
    }
  • 获取构造方法并创建对象

    1. 涉及的方法:newInstance

    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
      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
      //首先要有一个javabean类
      public class Student {
      private String name;

      private int age;


      public Student() {

      }

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

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


      /**
      * 获取
      * @return name
      */
      public String getName() {
      return name;
      }

      /**
      * 设置
      * @param name
      */
      public void setName(String name) {
      this.name = name;
      }

      /**
      * 获取
      * @return age
      */
      public int getAge() {
      return age;
      }

      /**
      * 设置
      * @param age
      */
      public void setAge(int age) {
      this.age = age;
      }

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



      //测试类中的代码:
      //需求1:
      //获取空参,并创建对象

      //1.获取整体的字节码文件对象
      Class clazz = Class.forName("com.itheima.a02reflectdemo1.Student");
      //2.获取空参的构造方法
      Constructor con = clazz.getConstructor();
      //3.利用空参构造方法创建对象
      Student stu = (Student) con.newInstance();
      System.out.println(stu);


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


      //测试类中的代码:
      //需求2:
      //获取带参构造,并创建对象
      //1.获取整体的字节码文件对象
      Class clazz = Class.forName("com.itheima.a02reflectdemo1.Student");
      //2.获取有参构造方法
      Constructor con = clazz.getDeclaredConstructor(String.class, int.class);
      //3.临时修改构造方法的访问权限(暴力反射)
      con.setAccessible(true);
      //4.直接创建对象
      Student stu = (Student) con.newInstance("zhangsan", 23);
      System.out.println(stu);

3.获取成员变量

  • 规则:

    1. get表示获取
    2. Declared表示私有

    如果当前获取到的是私有的,必须要临时修改访问权限,否则无法使用

  • 方法名:

    方法名 说明
    Field[] getFields() 返回所有成员变量对象的数组(只能拿public的)
    Field[] getDeclaredFields() 返回所有成员变量对象的数组,存在就能拿到
    Field getField(String name) 返回单个成员变量对象(只能拿public的)
    Field getDeclaredField(String name) 返回单个成员变量对象,存在就能拿到
    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
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    public class ReflectDemo4 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
    //获取成员变量对象

    //1.获取class对象
    Class clazz = Class.forName("com.itheima.reflectdemo.Student");

    //2.获取成员变量的对象(Field对象)只能获取public修饰的
    Field[] fields1 = clazz.getFields();
    for (Field field : fields1) {
    System.out.println(field);
    }

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

    //获取成员变量的对象(public + private)
    Field[] fields2 = clazz.getDeclaredFields();
    for (Field field : fields2) {
    System.out.println(field);
    }

    System.out.println("===============================");
    //获得单个成员变量对象
    //如果获取的属性是不存在的,那么会报异常
    //Field field3 = clazz.getField("aaa");
    //System.out.println(field3);//NoSuchFieldException

    Field field4 = clazz.getField("gender");
    System.out.println(field4);

    System.out.println("===============================");
    //获取单个成员变量(私有)
    Field field5 = clazz.getDeclaredField("name");
    System.out.println(field5);

    }
    }



    public class Student {
    private String name;

    private int age;

    public String gender;

    public String address;


    public Student() {
    }

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


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

    /**
    * 获取
    * @return name
    */
    public String getName() {
    return name;
    }

    /**
    * 设置
    * @param name
    */
    public void setName(String name) {
    this.name = name;
    }

    /**
    * 获取
    * @return age
    */
    public int getAge() {
    return age;
    }

    /**
    * 设置
    * @param age
    */
    public void setAge(int age) {
    this.age = age;
    }

    /**
    * 获取
    * @return gender
    */
    public String getGender() {
    return gender;
    }

    /**
    * 设置
    * @param gender
    */
    public void setGender(String gender) {
    this.gender = gender;
    }

    /**
    * 获取
    * @return address
    */
    public String getAddress() {
    return address;
    }

    /**
    * 设置
    * @param address
    */
    public void setAddress(String address) {
    this.address = address;
    }

    public String toString() {
    return "Student{name = " + name + ", age = " + age + ", gender = " + gender + ", address = " + address + "}";
    }
    }

  • 获取成员变量并获取值和修改值

    方法 说明
    void set(Object obj, Object value) 赋值
    Object get(Object obj) 获取值

    代码示例:

    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
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    public class ReflectDemo5 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
    Student s = new Student("zhangsan",23,"广州");
    Student ss = new Student("lisi",24,"北京");

    //需求:
    //利用反射获取成员变量并获取值和修改值

    //1.获取class对象
    Class clazz = Class.forName("com.itheima.reflectdemo.Student");

    //2.获取name成员变量
    //field就表示name这个属性的对象
    Field field = clazz.getDeclaredField("name");
    //临时修饰他的访问权限
    field.setAccessible(true);

    //3.设置(修改)name的值
    //参数一:表示要修改哪个对象的name?
    //参数二:表示要修改为多少?
    field.set(s,"wangwu");

    //3.获取name的值
    //表示我要获取这个对象的name的值
    String result = (String)field.get(s);

    //4.打印结果
    System.out.println(result);

    System.out.println(s);
    System.out.println(ss);

    }
    }


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


    public Student() {
    }

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


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

    /**
    * 获取
    * @return name
    */
    public String getName() {
    return name;
    }

    /**
    * 设置
    * @param name
    */
    public void setName(String name) {
    this.name = name;
    }

    /**
    * 获取
    * @return age
    */
    public int getAge() {
    return age;
    }

    /**
    * 设置
    * @param age
    */
    public void setAge(int age) {
    this.age = age;
    }

    /**
    * 获取
    * @return gender
    */
    public String getGender() {
    return gender;
    }

    /**
    * 设置
    * @param gender
    */
    public void setGender(String gender) {
    this.gender = gender;
    }

    /**
    * 获取
    * @return address
    */
    public String getAddress() {
    return address;
    }

    /**
    * 设置
    * @param address
    */
    public void setAddress(String address) {
    this.address = address;
    }

    public String toString() {
    return "Student{name = " + name + ", age = " + age + ", gender = " + gender + ", address = " + address + "}";
    }
    }

4.获取成员方法

  • 方法

    方法名 说明
    Method[] getMethods() 返回所有成员方法对象的数组(只能拿public的)
    Method[] getDeclaredMethods() 返回所有成员方法对象的数组,存在就能拿到
    Method getMethod(String name, Class<?>… parameterTypes) 返回单个成员方法对象(只能拿public的)
    Method getDeclaredMethod(String name, Class<?>… parameterTypes) 返回单个成员方法对象,存在就能拿到
    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
    public class ReflectDemo6 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
    //1.获取class对象
    Class<?> clazz = Class.forName("com.itheima.reflectdemo.Student");


    //2.获取方法
    //getMethods可以获取父类中public修饰的方法
    Method[] methods1 = clazz.getMethods();
    for (Method method : methods1) {
    System.out.println(method);
    }

    System.out.println("===========================");
    //获取所有的方法(包含私有)
    //但是只能获取自己类中的方法
    Method[] methods2 = clazz.getDeclaredMethods();
    for (Method method : methods2) {
    System.out.println(method);
    }

    System.out.println("===========================");
    //获取指定的方法(空参)
    Method method3 = clazz.getMethod("sleep");
    System.out.println(method3);

    Method method4 = clazz.getMethod("eat",String.class);
    System.out.println(method4);

    //获取指定的私有方法
    Method method5 = clazz.getDeclaredMethod("playGame");
    System.out.println(method5);
    }
    }

  • 获取成员方法并运行

    1. Object invoke(Object obj, Object... args) :运行方法

      • 参数一:用obj对象调用该方法
      • 参数二:调用方法的传递的参数(若没有不写)
      • 返回值:方法的返回值(若没有不写)
      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
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      package com.itheima.a02reflectdemo1;

      import java.lang.reflect.InvocationTargetException;
      import java.lang.reflect.Method;

      public class ReflectDemo6 {
      public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
      //1.获取字节码文件对象
      Class clazz = Class.forName("com.itheima.a02reflectdemo1.Student");

      //2.获取一个对象
      //需要用这个对象去调用方法
      Student s = new Student();

      //3.获取一个指定的方法
      //参数一:方法名
      //参数二:参数列表,如果没有可以不写
      Method eatMethod = clazz.getMethod("eat",String.class);

      //运行
      //参数一:表示方法的调用对象
      //参数二:方法在运行时需要的实际参数
      //注意点:如果方法有返回值,那么需要接收invoke的结果
      //如果方法没有返回值,则不需要接收
      String result = (String) eatMethod.invoke(s, "重庆小面");
      System.out.println(result);

      }
      }



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


      public Student() {

      }

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

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

      /**
      * 获取
      * @return name
      */
      public String getName() {
      return name;
      }

      /**
      * 设置
      * @param name
      */
      public void setName(String name) {
      this.name = name;
      }

      /**
      * 获取
      * @return age
      */
      public int getAge() {
      return age;
      }

      /**
      * 设置
      * @param age
      */
      public void setAge(int age) {
      this.age = age;
      }

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

      private void study(){
      System.out.println("学生在学习");
      }

      private void sleep(){
      System.out.println("学生在睡觉");
      }

      public String eat(String something){
      System.out.println("学生在吃" + something);
      return "学生已经吃完了,非常happy";
      }
      }

5.泛型擦除

  • 理解:(掌握)

    集合中的泛型只在java文件中存在,当编译成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
    package com.itheima.reflectdemo;

    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.util.ArrayList;

    public class ReflectDemo8 {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    //1.创建集合对象
    ArrayList<Integer> list = new ArrayList<>();
    list.add(123);
    // list.add("aaa");

    //2.利用反射运行add方法去添加字符串
    //因为反射使用的是class字节码文件

    //获取class对象
    Class clazz = list.getClass();

    //获取add方法对象
    Method method = clazz.getMethod("add", Object.class);

    //运行方法
    method.invoke(list,"aaa");

    //打印集合
    System.out.println(list);
    }
    }

6.修改字符串的内容

  • 字符串:在底层是一个byte类型的字节数组,名字叫做value

    1
    private final byte[] value;
    1. 真正不能被修改的原因:final和private
    2. final修饰value表示value记录的地址值不能修改。
    3. private修饰value而且没有对外提供getvalue和setvalue的方法。所以,在外界不能获取或修改value记录的地址值。
  • 强行修改可以用反射:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    String s = "abc";
    String ss = "abc";
    // private final byte[] value= {97,98,99};
    // 没有对外提供getvalue和setvalue的方法,不能修改value记录的地址值
    // 如果我们利用反射获取了value的地址值。
    // 也是可以修改的,final修饰的value
    // 真正不可变的value数组的地址值,里面的内容利用反射还是可以修改的,比较危险

    //1.获取class对象
    Class clazz = s.getClass();

    //2.获取value成员变量(private)
    Field field = clazz.getDeclaredField("value");
    //但是这种操作非常危险
    //JDK高版本已经屏蔽了这种操作,低版本还是可以的
    //临时修改权限
    field.setAccessible(true);

    //3.获取value记录的地址值
    byte[] bytes = (byte[]) field.get(s);
    bytes[0] = 100;

    System.out.println(s);//dbc
    System.out.println(ss);//dbc

7.反射和配置文件结合动态获取

  • 需求: 利用反射根据文件中的不同类名和方法名,创建不同的对象并调用方法。

  • 分析:

    1. 通过Properties加载配置文件
    2. 得到类名和方法名
    3. 通过类名反射得到Class对象
    4. 通过Class对象创建一个对象
    5. 通过Class对象得到方法
    6. 调用方法
  • 代码示例:

    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
    public class ReflectDemo9 {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
    //1.读取配置文件的信息
    Properties prop = new Properties();
    FileInputStream fis = new FileInputStream("day14-code\\prop.properties");
    prop.load(fis);
    fis.close();
    System.out.println(prop);

    String classname = prop.get("classname") + "";
    String methodname = prop.get("methodname") + "";

    //2.获取字节码文件对象
    Class clazz = Class.forName(classname);

    //3.要先创建这个类的对象
    Constructor con = clazz.getDeclaredConstructor();
    con.setAccessible(true);
    Object o = con.newInstance();
    System.out.println(o);

    //4.获取方法的对象
    Method method = clazz.getDeclaredMethod(methodname);
    method.setAccessible(true);

    //5.运行方法
    method.invoke(o);


    }
    }

    配置文件中的信息:
    classname=com.itheima.a02reflectdemo1.Student
    methodname=sleep

8.利用反射保存对象中的信息

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
public class MyReflectDemo {
public static void main(String[] args) throws IllegalAccessException, IOException {
/*
对于任意一个对象,都可以把对象所有的字段名和值,保存到文件中去
*/
Student s = new Student("小A",23,'女',167.5,"睡觉");
Teacher t = new Teacher("播妞",10000);
saveObject(s);
}

//把对象里面所有的成员变量名和值保存到本地文件中
public static void saveObject(Object obj) throws IllegalAccessException, IOException {
//1.获取字节码文件的对象
Class clazz = obj.getClass();
//2. 创建IO流
BufferedWriter bw = new BufferedWriter(new FileWriter("myreflect\\a.txt"));
//3. 获取所有的成员变量
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
//获取成员变量的名字
String name = field.getName();
//获取成员变量的值
Object value = field.get(obj);
//写出数据
bw.write(name + "=" + value);
bw.newLine();
}

bw.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
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
public class Student {
private String name;
private int age;
private char gender;
private double height;
private String hobby;

public Student() {
}

public Student(String name, int age, char gender, double height, String hobby) {
this.name = name;
this.age = age;
this.gender = gender;
this.height = height;
this.hobby = hobby;
}

/**
* 获取
* @return name
*/
public String getName() {
return name;
}

/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}

/**
* 获取
* @return age
*/
public int getAge() {
return age;
}

/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}

/**
* 获取
* @return gender
*/
public char getGender() {
return gender;
}

/**
* 设置
* @param gender
*/
public void setGender(char gender) {
this.gender = gender;
}

/**
* 获取
* @return height
*/
public double getHeight() {
return height;
}

/**
* 设置
* @param height
*/
public void setHeight(double height) {
this.height = height;
}

/**
* 获取
* @return hobby
*/
public String getHobby() {
return hobby;
}

/**
* 设置
* @param hobby
*/
public void setHobby(String hobby) {
this.hobby = hobby;
}

public String toString() {
return "Student{name = " + name + ", age = " + age + ", gender = " + gender + ", height = " + height + ", hobby = " + hobby + "}";
}
}
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
public class Teacher {
private String name;
private double salary;

public Teacher() {
}

public Teacher(String name, double salary) {
this.name = name;
this.salary = salary;
}

/**
* 获取
* @return name
*/
public String getName() {
return name;
}

/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}

/**
* 获取
* @return salary
*/
public double getSalary() {
return salary;
}

/**
* 设置
* @param salary
*/
public void setSalary(double salary) {
this.salary = salary;
}

public String toString() {
return "Teacher{name = " + name + ", salary = " + salary + "}";
}
}

18.动态代理

好处:无侵入式的给方法增强功能

  • 动态代理三要素:

    1. 真正干活的对象
    2. 代理对象
    3. 利用代理调用方法

    切记:代理可以增强或者拦截的方法都在接口中,接口需要写在newProxyInstance的第二个参数里。

  • 代码实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class Test {
    public static void main(String[] args) {
    /*
    需求:
    外面的人想要大明星唱一首歌
    1. 获取代理的对象
    代理对象 = ProxyUtil.createProxy(大明星的对象);
    2. 再调用代理的唱歌方法
    代理对象.唱歌的方法("只因你太美");
    */
    //1. 获取代理的对象
    BigStar bigStar = new BigStar("鸡哥");
    Star proxy = ProxyUtil.createProxy(bigStar);

    //2. 调用唱歌的方法
    String result = proxy.sing("只因你太美");
    System.out.println(result);
    }
    }
    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
    /*
    *
    * 类的作用:
    * 创建一个代理
    *
    * */
    public class ProxyUtil {
    /*
    *
    * 方法的作用:
    * 给一个明星的对象,创建一个代理
    *
    * 形参:
    * 被代理的明星对象
    *
    * 返回值:
    * 给明星创建的代理
    *
    *
    *
    * 需求:
    * 外面的人想要大明星唱一首歌
    * 1. 获取代理的对象
    * 代理对象 = ProxyUtil.createProxy(大明星的对象);
    * 2. 再调用代理的唱歌方法
    * 代理对象.唱歌的方法("只因你太美");
    * */
    public static Star createProxy(BigStar bigStar){
    /* java.lang.reflect.Proxy类:提供了为对象产生代理对象的方法:

    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
    参数一:用于指定用哪个类加载器,去加载生成的代理类
    参数二:指定接口,这些接口用于指定生成的代理长什么,也就是有哪些方法
    参数三:用来指定生成的代理对象要干什么事情*/
    Star star = (Star) Proxy.newProxyInstance(
    ProxyUtil.class.getClassLoader(),//参数一:用于指定用哪个类加载器,去加载生成的代理类
    new Class[]{Star.class},//参数二:指定接口,这些接口用于指定生成的代理长什么,也就是有哪些方法
    //参数三:用来指定生成的代理对象要干什么事情
    new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    /*
    * 参数一:代理的对象
    * 参数二:要运行的方法 sing
    * 参数三:调用sing方法时,传递的实参
    * */
    if("sing".equals(method.getName())){
    System.out.println("准备话筒,收钱");
    }else if("dance".equals(method.getName())){
    System.out.println("准备场地,收钱");
    }
    //去找大明星开始唱歌或者跳舞
    //代码的表现形式:调用大明星里面唱歌或者跳舞的方法
    return method.invoke(bigStar,args);
    }
    }
    );
    return star;
    }
    }
    1
    2
    3
    4
    5
    6
    7
    public interface Star {
    //我们可以把所有想要被代理的方法定义在接口当中
    //唱歌
    public abstract String sing(String name);
    //跳舞
    public abstract void dance();
    }
    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
    public class BigStar implements Star {
    private String name;


    public BigStar() {
    }

    public BigStar(String name) {
    this.name = name;
    }

    //唱歌
    @Override
    public String sing(String name){
    System.out.println(this.name + "正在唱" + name);
    return "谢谢";
    }

    //跳舞
    @Override
    public void dance(){
    System.out.println(this.name + "正在跳舞");
    }

    /**
    * 获取
    * @return name
    */
    public String getName() {
    return name;
    }

    /**
    * 设置
    * @param name
    */
    public void setName(String name) {
    this.name = name;
    }

    public String toString() {
    return "BigStar{name = " + name + "}";
    }
    }

  • 额外扩展

    动态代理:可以拦截方法

    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
    /*
    * 类的作用:
    * 创建一个代理
    * */
    public class ProxyUtil {
    public static Star createProxy(BigStar bigStar){
    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
    Star star = (Star) Proxy.newProxyInstance(
    ProxyUtil.class.getClassLoader(),
    new Class[]{Star.class},
    new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if("cleanWC".equals(method.getName())){
    System.out.println("拦截,不调用大明星的方法");
    return null;
    }
    //如果是其他方法,正常执行
    return method.invoke(bigStar,args);
    }
    }
    );
    return star;
    }
    }
  • 动态代理的练习

    对add方法进行增强,对remove方法进行拦截,对其他方法不拦截也不增强

    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
    public class MyProxyDemo1 {
    public static void main(String[] args) {
    //动态代码可以增强也可以拦截
    //1.创建真正干活的人
    ArrayList<String> list = new ArrayList<>();

    //2.创建代理对象
    //参数一:类加载器。当前类名.class.getClassLoader()
    // 找到是谁,把当前的类,加载到内存中了,我再麻烦他帮我干一件事情,把后面的代理类,也加载到内存

    //参数二:是一个数组,在数组里面写接口的字节码文件对象。
    // 如果写了List,那么表示代理,可以代理List接口里面所有的方法,对这些方法可以增强或者拦截
    // 但是,一定要写ArrayList真实实现的接口
    // 假设在第二个参数中,写了MyInter接口,那么是错误的。
    // 因为ArrayList并没有实现这个接口,那么就无法对这个接口里面的方法,进行增强或拦截
    //参数三:用来创建代理对象的匿名内部类
    List proxyList = (List) Proxy.newProxyInstance(
    //参数一:类加载器
    MyProxyDemo1.class.getClassLoader(),
    //参数二:是一个数组,表示代理对象能代理的方法范围
    new Class[]{List.class},
    //参数三:本质就是代理对象
    new InvocationHandler() {
    @Override
    //invoke方法参数的意义
    //参数一:表示代理对象,一般不用(了解)
    //参数二:就是方法名,我们可以对方法名进行判断,是增强还是拦截
    //参数三:就是下面第三步调用方法时,传递的参数。
    //举例1:
    //list.add("阿玮好帅");
    //此时参数二就是add这个方法名
    //此时参数三 args[0] 就是 阿玮好帅
    //举例2:
    //list.set(1, "aaa");
    //此时参数二就是set这个方法名
    //此时参数三 args[0] 就是 1 args[1]"aaa"
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //对add方法做一个增强,统计耗时时间
    if (method.getName().equals("add")) {
    long start = System.currentTimeMillis();
    //调用集合的方法,真正的添加数据
    method.invoke(list, args);
    long end = System.currentTimeMillis();
    System.out.println("耗时时间:" + (end - start));
    //需要进行返回,返回值要跟真正增强或者拦截的方法保持一致
    return true;
    }else if(method.getName().equals("remove") && args[0] instanceof Integer){
    System.out.println("拦截了按照索引删除的方法");
    return null;
    }else if(method.getName().equals("remove")){
    System.out.println("拦截了按照对象删除的方法");
    return false;
    }else{
    //如果当前调用的是其他方法,我们既不增强,也不拦截
    method.invoke(list,args);
    return null;
    }
    }
    }
    );

    //3.调用方法
    //如果调用者是list,就好比绕过了第二步的代码,直接添加元素
    //如果调用者是代理对象,此时代理才能帮我们增强或者拦截

    //每次调用方法的时候,都不会直接操作集合
    //而是先调用代理里面的invoke,在invoke方法中进行判断,可以增强或者拦截
    proxyList.add("aaa");
    proxyList.add("bbb");
    proxyList.add("ccc");
    proxyList.add("ddd");

    proxyList.remove(0);
    proxyList.remove("aaa");


    //打印集合
    System.out.println(list);
    }
    }

19.log日志

  • 作用:

    log日志可以把程序在运行过程中的详细信息都打印在控制台上,还可以把详细信息保存到文件和数据库中。

  • 使用步骤:

    第三方提供的代码,所以要导入jar包。

    1. 导入第三方的代码
      • 新建lib文件夹,把jar粘贴到lib文件夹当中,全选后右键点击选择add as library….
      • 检测导入成功:导入成功后jar包可以展开。在项目重构界面可以看到导入的内容
      • 把配置文件粘贴到src文件夹下
    2. 在代码中获取日志对象
    3. 调用方法打印日志
  • 日志级别

    1
    TRACE, DEBUG, INFO, WARN, ERROR
    1. 两个特殊:

      • ALL:输出所有日志
      • OFF:关闭所有日志
    2. 日志级别从小到大的关系:

      1
      TRACE < DEBUG < INFO < WARN < ERROR
  • 配置文件

    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
    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
    <!--
    CONSOLE :表示当前的日志信息是可以输出到控制台的。
    -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <!--输出流对象 默认 System.out 改为 System.err-->
    <target>System.out</target>
    <encoder>
    <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度
    %msg:日志消息,%n是换行符-->
    <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] %c [%thread] : %msg%n</pattern>
    </encoder>
    </appender>

    <!-- File是输出的方向通向文件的 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <encoder>
    <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    <charset>utf-8</charset>
    </encoder>
    <!--日志输出路径-->
    <file>C:/code/itheima-data.log</file>
    <!--指定日志文件拆分和压缩规则-->
    <rollingPolicy
    class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
    <!--通过指定压缩文件名称,来确定分割文件方式-->
    <fileNamePattern>C:/code/itheima-data2-%d{yyyy-MMdd}.log%i.gz</fileNamePattern>
    <!--文件拆分大小-->
    <maxFileSize>1MB</maxFileSize>
    </rollingPolicy>
    </appender>

    <!--

    level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF
    , 默认debug
    <root>可以包含零个或多个<appender-ref>元素,标识这个输出位置将会被本日志级别控制。
    -->
    <root level="info">
    <appender-ref ref="CONSOLE"/>
    <appender-ref ref="FILE" />
    </root>
    </configuration>

20.类加载器

  • 类加载器作用

    1. 负责将.class文件(存储的物理文件)加载在到内存中
  • 类加载的完整过程

    1. 类加载时机

      • 创建类的实例(对象)
      • 调用类的类方法
      • 访问类或者接口的类变量,或者为该类变量赋值
      • 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
      • 初始化某个类的子类
      • 直接使用java.exe命令来运行某个主类

      总结而言:用到了就加载,不用不加载

    2. 类加载过程

      • 加载

        • 通过包名 + 类名,获取这个类,准备用流进行传输
        • 在这个类加载到内存中
        • 加载完毕创建一个class对象
      • 验证

        • 确保Class文件字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身安全(文件中的信息是否符合虚拟机规范有没有安全隐患)
      • 准备

        • 负责为类的类变量(被static修饰的变量)分配内存,并设置默认初始化值(初始化静态变量)
      • 解析

        • 将类的二进制数据流中的符号引用替换为直接引用(本类中如果用到了其他类,此时就需要找到对应的类)
      • 初始化

        • 根据程序员通过程序制定的主观计划去初始化类变量和其他资源(静态变量赋值以及初始化其他资源)
    3. 小结

      • 当一个类被使用的时候,才会加载到内存
      • 类加载的过程: 加载、验证、准备、解析、初始化
  • 类加载的分类

    1. 分类

      • Bootstrap class loader:虚拟机的内置类加载器,通常表示为null ,并且没有父null
      • Platform class loader:平台类加载器,负责加载JDK中一些特殊的模块
      • System class loader:系统类加载器,负责加载用户类路径上所指定的类库
    2. 类加载器的继承关系

      • System的父加载器为Platform
      • Platform的父加载器为Bootstrap
    3. 代码演示

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      public class ClassLoaderDemo1 {
      public static void main(String[] args) {
      //获取系统类加载器
      ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();

      //获取系统类加载器的父加载器 --- 平台类加载器
      ClassLoader classLoader1 = systemClassLoader.getParent();

      //获取平台类加载器的父加载器 --- 启动类加载器
      ClassLoader classLoader2 = classLoader1.getParent();

      System.out.println("系统类加载器" + systemClassLoader);
      System.out.println("平台类加载器" + classLoader1);
      System.out.println("启动类加载器" + classLoader2);

      }
      }
  • ClassLoader 中的两个方法

    1. 方法介绍

      方法名 说明
      public static ClassLoader getSystemClassLoader() 获取系统类加载器
      public InputStream getResourceAsStream(String name) 加载某一个资源文件
    2. 示例代码

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      public class ClassLoaderDemo2 {
      public static void main(String[] args) throws IOException {
      //static ClassLoader getSystemClassLoader() 获取系统类加载器
      //InputStream getResourceAsStream(String name) 加载某一个资源文件

      //获取系统类加载器
      ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();

      //利用加载器去加载一个指定的文件
      //参数:文件的路径(放在src的根目录下,默认去那里加载)
      //返回值:字节流。
      InputStream is = systemClassLoader.getResourceAsStream("prop.properties");

      Properties prop = new Properties();
      prop.load(is);

      System.out.println(prop);

      is.close();
      }
      }

21.xml

1.概述

  • xml概述

    1. XML的全称(EXtensible Markup Language),是一种可扩展的标记语言
    2. 标记语言: 通过标签来描述数据的一门语言(标签有时也称为元素)
    3. 可扩展:标签的名字是可以自定义的,XML文件是由很多标签组成的,而标签名是可以自定义的
  • 作用

    1. 用于进行存储数据和传输数据
    2. 作为软件的配置文件
  • 作为配置文件的优势

    1. 可读性好
    2. 可维护性高
  • 标签的规则

    1. 标签由一对尖括号和合法标识符组成

      1
      <student>
    2. 标签必须成对出现

      1
      2
      <student> </student>
      前边的是开始标签,后边的是结束标签
    3. 特殊的标签可以不成对,但是必须有结束标记

      1
      <address/>
    4. 标签中可以定义属性,属性和标签名空格隔开,属性值必须用引号引起来

      1
      <student id="1"> </student>
    5. 标签需要正确的嵌套

      1
      2
      这是正确的: <student id="1"> <name>张三</name> </student>
      这是错误的: <student id="1"><name>张三</student></name>

2.语法规则

  • 语法规则

    1. XML文件的后缀名为:xml

    2. 文档声明必须是第一行第一列

      1
      <?xml version="1.0" encoding="UTF-8" standalone="yes”?>
      • version:该属性是必须存在的
      • encoding:该属性不是必须的
      • 打开当前xml文件的时候应该是使用什么字符编码表(一般取值都是UTF-8)
      • standalone: 该属性不是必须的,描述XML文件是否依赖其他的xml文件,取值为yes/no
    3. 必须存在一个根标签,有且只能有一个

    4. XML文件中可以定义注释信息

    5. XML文件中可以存在以下特殊字符

      1
      2
      3
      4
      5
      &lt; < 小于
      &gt; > 大于
      &amp; & 和号
      &apos; ' 单引号
      &quot; " 引号
    6. XML文件中可以存在CDATA区

      1
      <![CDATA[ …内容… ]]>
  • 示例代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <?xml version="1.0" encoding="UTF-8" ?>
    <!--注释的内容-->
    <!--本xml文件用来描述多个学生信息-->
    <students>

    <!--第一个学生信息-->
    <student id="1">
    <name>张三</name>
    <age>23</age>
    <info>学生&lt; &gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;的信息</info>
    <message> <![CDATA[内容 <<<<<< >>>>>> ]]]></message>
    </student>

    <!--第二个学生信息-->
    <student id="2">
    <name>李四</name>
    <age>24</age>
    </student>

    </students>

3.xml解析

  • 概述

    xml解析就是从xml中获取到数据

  • 常见的解析思想

    DOM(Document Object Model)文档对象模型:就是把文档的各个组成部分看做成对应的对象。会把xml文件全部加载到内存,在内存中形成一个树形结构,再获取对应的值

  • 常见的解析工具

    1. JAXP: SUN公司提供的一套XML的解析的API
    2. JDOM: 开源组织提供了一套XML的解析的API-jdom
    3. DOM4J: 开源组织提供了一套XML的解析的API-dom4j,全称:Dom For Java
    4. pull: 主要应用在Android手机端解析XML
  • 解析的准备工作

    1. 可通过网站:https://dom4j.github.io/ 去下载dom4j

    2. 将提供好的dom4j-1.6.1.zip解压,找到里面的dom4j-1.6.1.jar

    3. 在idea中当前模块下新建一个libs文件夹,将jar包复制到文件夹中

    4. 选中jar包 -> 右键 -> 选择add as library即可

  • 需求

    1. 解析提供好的xml文件
    2. 将解析到的数据封装到学生对象中
    3. 并将学生对象存储到ArrayList集合中
    4. 遍历集合
  • 代码实现

    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
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    <?xml version="1.0" encoding="UTF-8" ?>
    <!--注释的内容-->
    <!--本xml文件用来描述多个学生信息-->
    <students>

    <!--第一个学生信息-->
    <student id="1">
    <name>张三</name>
    <age>23</age>
    </student>

    <!--第二个学生信息-->
    <student id="2">
    <name>李四</name>
    <age>24</age>
    </student>

    </students>

    // 上边是已经准备好的student.xml文件
    public class Student {
    private String id;
    private String name;
    private int age;

    public Student() {
    }

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

    public String getId() {
    return id;
    }

    public void setId(String id) {
    this.id = id;
    }

    public String getName() {
    return name;
    }

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

    public int getAge() {
    return age;
    }

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

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

    /**
    * 利用dom4j解析xml文件
    */
    public class XmlParse {
    public static void main(String[] args) throws DocumentException {
    //1.获取一个解析器对象
    SAXReader saxReader = new SAXReader();
    //2.利用解析器把xml文件加载到内存中,并返回一个文档对象
    Document document = saxReader.read(new File("myxml\\xml\\student.xml"));
    //3.获取到根标签
    Element rootElement = document.getRootElement();
    //4.通过根标签来获取student标签
    //elements():可以获取调用者所有的子标签.会把这些子标签放到一个集合中返回.
    //elements("标签名"):可以获取调用者所有的指定的子标签,会把这些子标签放到一个集合中并返回
    //List list = rootElement.elements();
    List<Element> studentElements = rootElement.elements("student");
    //System.out.println(list.size());

    //用来装学生对象
    ArrayList<Student> list = new ArrayList<>();

    //5.遍历集合,得到每一个student标签
    for (Element element : studentElements) {
    //element依次表示每一个student标签

    //获取id这个属性
    Attribute attribute = element.attribute("id");
    //获取id的属性值
    String id = attribute.getValue();

    //获取name标签
    //element("标签名"):获取调用者指定的子标签
    Element nameElement = element.element("name");
    //获取这个标签的标签体内容
    String name = nameElement.getText();

    //获取age标签
    Element ageElement = element.element("age");
    //获取age标签的标签体内容
    String age = ageElement.getText();

    // System.out.println(id);
    // System.out.println(name);
    // System.out.println(age);

    Student s = new Student(id,name,Integer.parseInt(age));
    list.add(s);
    }
    //遍历操作
    for (Student student : list) {
    System.out.println(student);
    }
    }
    }

4.DTD约束

  • 什么是约束

    用来限定xml文件中可使用的标签以及属性

  • 约束的分类

    1. DTD
    2. schema
  • 编写DTD约束

    1. 步骤

      • 创建一个文件,这个文件的后缀名为.dtd

      • 看xml文件中使用了哪些元素

        1
        <!ELEMENT> 可以定义元素
      • 判断元素是简单元素还是复杂元素

        • 简单元素:没有子元素。
        • 复杂元素:有子元素的元素;
    2. 代码实现

      1
      2
      3
      4
      <!ELEMENT persons (person)>
      <!ELEMENT person (name,age)>
      <!ELEMENT name (#PCDATA)>
      <!ELEMENT age (#PCDATA)>
  • 引入DTD约束

    1. 引入DTD约束的三种方法

      • 引入本地dtd

        1
        <!DOCTYPE 根元素名称 SYSTEM ‘DTD文件的路径'>
      • 在xml文件内部引入

        1
        <!DOCTYPE 根元素名称 [ dtd文件内容 ]>
      • 引入网络dtd

        1
        <!DOCTYPE 根元素的名称 PUBLIC "DTD文件名称" "DTD文档的URL">
    2. 代码实现

      • 引入本地DTD约束

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        // 这是persondtd.dtd文件中的内容,已经提前写好
        <!ELEMENT persons (person)>
        <!ELEMENT person (name,age)>
        <!ELEMENT name (#PCDATA)>
        <!ELEMENT age (#PCDATA)>

        // 在person1.xml文件中引入persondtd.dtd约束
        <?xml version="1.0" encoding="UTF-8" ?>
        <!DOCTYPE persons SYSTEM 'persondtd.dtd'>

        <persons>
        <person>
        <name>张三</name>
        <age>23</age>
        </person>

        </persons>
      • 在xml文件内部引入

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        <?xml version="1.0" encoding="UTF-8" ?>
        <!DOCTYPE persons [
        <!ELEMENT persons (person)>
        <!ELEMENT person (name,age)>
        <!ELEMENT name (#PCDATA)>
        <!ELEMENT age (#PCDATA)>
        ]>

        <persons>
        <person>
        <name>张三</name>
        <age>23</age>
        </person>

        </persons>
      • 引入网络dtd

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        <?xml version="1.0" encoding="UTF-8" ?>
        <!DOCTYPE persons PUBLIC "dtd文件的名称" "dtd文档的URL">

        <persons>
        <person>
        <name>张三</name>
        <age>23</age>
        </person>

        </persons>
  • DTD语法

    1. 定义元素

      • 格式:

        1
        <!ELEMENT 元素名 元素类型>
      • 简单元素:

        • EMPTY: 表示标签体为空
        • ANY: 表示标签体可以为空也可以不为空
        • PCDATA: 表示该元素的内容部分为字符串
      • 复杂元素:

        • 直接写子元素名称. 多个子元素可以使用”,”或者”|”隔开;
        • “,”表示定义子元素的顺序 ; “|”: 表示子元素只能出现任意一个
        • “?”零次或一次, “+”一次或多次, “*”零次或多次;如果不写则表示出现一次
    2. 定义属性

      • 格式

        • 定义一个属性的格式为:

          1
          <!ATTLIST 元素名称 属性名称 属性的类型 属性的约束>
        • 属性的类型:

          1
          CDATA类型:普通的字符串
        • 属性的约束:

          1
          2
          3
          // #REQUIRED: 必须的
          // #IMPLIED: 属性不是必需的
          // #FIXED value:属性值是固定的
    3. 代码实现

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      <!ELEMENT persons (person+)>
      <!ELEMENT person (name,age)>
      <!ELEMENT name (#PCDATA)>
      <!ELEMENT age (#PCDATA)>
      <!ATTLIST person id CDATA #REQUIRED>

      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE persons SYSTEM 'persondtd.dtd'>

      <persons>
      <person id="001">
      <name>张三</name>
      <age>23</age>
      </person>

      <person id = "002">
      <name>张三</name>
      <age>23</age>
      </person>

      </persons>
      ​```

5.schema约束

  • schema和dtd的区别

    1. schema约束文件也是一个xml文件,符合xml的语法,这个文件的后缀名.xsd
    2. 一个xml中可以引用多个schema约束文件,多个schema使用名称空间区分(名称空间类似于java包名)
    3. dtd里面元素类型的取值比较单一常见的是PCDATA类型,但是在schema里面可以支持很多个数据类型
    4. schema 语法更加的复杂
  • 编写schema约束

    1. 步骤

      • 创建一个文件,这个文件的后缀名为.xsd。

      • 定义文档声明

      • schema文件的根标签为:

      • 中定义属性:

        xmlns=http://www.w3.org/2001/XMLSchema

      • 中定义属性 :

        targetNamespace =唯一的url地址,指定当前这个schema文件的名称空间。

      • 中定义属性 :
        elementFormDefault=”qualified“,表示当前schema文件是一个质量良好的文件。

      • 通过element定义元素

      • 判断当前元素是简单元素还是复杂元素

    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
      <?xml version="1.0" encoding="UTF-8" ?>
      <schema
      xmlns="http://www.w3.org/2001/XMLSchema"
      targetNamespace="http://www.itheima.cn/javase"
      elementFormDefault="qualified"
      >

      <!--定义persons复杂元素-->
      <element name="persons">
      <complexType>
      <sequence>
      <!--定义person复杂元素-->
      <element name = "person">
      <complexType>
      <sequence>
      <!--定义name和age简单元素-->
      <element name = "name" type = "string"></element>
      <element name = "age" type = "string"></element>
      </sequence>

      </complexType>
      </element>
      </sequence>
      </complexType>

      </element>

      </schema>

  • 引入schema约束

    1. 步骤

      • 在根标签上定义属性xmlns=”http://www.w3.org/2001/XMLSchema-instance
      • 通过xmlns引入约束文件的名称空间
      • 给某一个xmlns属性添加一个标识,用于区分不同的名称空间
      • 格式为: xmlns:标识=“名称空间地址” ,标识可以是任意的,但是一般取值都是xsi
      • 通过xsi:schemaLocation指定名称空间所对应的约束文件路径
      • 格式为:xsi:schemaLocation = “名称空间url 文件路径“
    2. 代码实现

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <?xml version="1.0" encoding="UTF-8" ?>

      <persons
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns="http://www.itheima.cn/javase"
      xsi:schemaLocation="http://www.itheima.cn/javase person.xsd"
      >
      <person>
      <name>张三</name>
      <age>23</age>
      </person>

      </persons>
      ​```
  • schema约束定义属性

    1. 代码示例

      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
      <?xml version="1.0" encoding="UTF-8" ?>
      <schema
      xmlns="http://www.w3.org/2001/XMLSchema"
      targetNamespace="http://www.itheima.cn/javase"
      elementFormDefault="qualified"
      >

      <!--定义persons复杂元素-->
      <element name="persons">
      <complexType>
      <sequence>
      <!--定义person复杂元素-->
      <element name = "person">
      <complexType>
      <sequence>
      <!--定义name和age简单元素-->
      <element name = "name" type = "string"></element>
      <element name = "age" type = "string"></element>
      </sequence>

      <!--定义属性,required( 必须的)/optional( 可选的)-->
      <attribute name="id" type="string" use="required"></attribute>
      </complexType>

      </element>
      </sequence>
      </complexType>
      </element>

      </schema>

      <?xml version="1.0" encoding="UTF-8" ?>
      <persons
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns="http://www.itheima.cn/javase"
      xsi:schemaLocation="http://www.itheima.cn/javase person.xsd"
      >
      <person id="001">
      <name>张三</name>
      <age>23</age>
      </person>

      </persons>

22.单元测试Junit

  • 单元测试

    对部分代码进行测试。

  • Junit的特点

    1. 是一个第三方的工具。(导jar包)
    2. 如果运行结果显示绿色,表示运行结果是正确的。
    3. 如果运行结果显示红色,表示运行结果是错误的。
  • 基本用法:

    1. 一定要先写一个方法。
    2. 在方法的上面写@Test
    3. 鼠标点一下@Test 按alt + 回车,点击Junit4
      • 此时就可以自动导包。
      • 如果自动导包失败(连接外网,或者自己手动导包)
      • 如果导包成功在左下角就会出现Junit4的相关jar包
  • 运行测试代码

    1. 只能直接运行无参无返回值的非静态方法
    2. 右键点击方法,要运行一个类里面所有的测试方法,选择类名,右键点击即可。
  • Junit正确的打开方式

    1. 注意点:并不是直接在要测试的方法上面直接加@Test
    2. 原因:因为要测试的方法有可能是有参数的,有返回值,或者是静态的。
  • 正确的使用方式:

    1. 新建测试类
    2. 新建测试方法(要测试的方法名 + Test) methodTest
    3. 在这个方法中直接调用要测试的方法
    4. 在测试方法的上面写@Test
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //真正用来测试的类
    //测试用例(测试类)
    public class JunitTest {

    //在这个类里面再写无参无返回值的非静态方法
    //在方法中调用想要测试的方法

    @Test
    public void method2Test(){
    //调用要测试的方法
    JunitDemo1 jd = new JunitDemo1();
    jd.method2(10);
    }
    }
  • 实际开发中单元测试的使用方式

    1. 需求:测试File中的delete方法,写的是否正确(掌握)

    2. 开发中的测试原则:不污染原数据。

    3. .Before 准备数据

      1
      2
      3
      4
      准备数据
      1.创建Properties的对象
      2.put数据到集合当中
      //只不过在下面的方法中,我们也需要用到Properties的对象,所以写完之后要挪到成员位置
    4. Test 测试方法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      调用store方法,保存数据到本地文件

      断言1
      判断当前文件是否存在
      断言2
      文件的大小一定是大于0
      断言3
      再次读取文件中的数据,判断是否跟集合中一致

      结论:
      如果所有的断言都通过了,表示store方法是正确的
    5. After 还原

      1
      把本地文件给删除
    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
    public class JunitDemo3 {
    //在实际开发中,真正完整的单元测试该怎么写?
    //前提:
    //以后在工作的时候,测试代码不能污染原数据。(修改,篡改)
    //1.利用Before去对数据做一个初始化的动作
    //2.利用Test真正的去测试方法
    //3.利用After去还原数据

    //需求:测试File类中的delete方法是否书写正确???
    @Before
    public void beforemethod() throws IOException {
    //先备份
    File src = new File("C:\\Users\\moon\\Desktop\\a.txt");
    File dest = new File("C:\\Users\\moon\\Desktop\\copy.txt");

    FileInputStream fis = new FileInputStream(src);
    FileOutputStream fos = new FileOutputStream(dest);
    int b;
    while((b = fis.read()) != -1){
    fos.write(b);
    }
    fos.close();
    fis.close();
    }

    //作为一个标准的测试人员,运行完单元测试之后,不能污染原数据
    //需要达到下面两个要求:
    //1.得到结果
    //2.a.txt还在而且其他的备份文件消失
    @Test
    public void method(){
    File file = new File("C:\\Users\\moon\\Desktop\\a.txt");
    boolean delete = file.delete();

    //检查a.txt是否存在
    boolean exists = file.exists();

    //只有同时满足了下面所有的断言,才表示delete方法编写正确
    Assert.assertEquals("delete方法出错了",delete,true);
    Assert.assertEquals("delete方法出错了",exists,false);
    }


    @After
    public void aftermethod() throws IOException {
    //还要对a.txt做一个还原
    File src = new File("C:\\Users\\moon\\Desktop\\copy.txt");
    File dest = new File("C:\\Users\\moon\\Desktop\\a.txt");

    FileInputStream fis = new FileInputStream(src);
    FileOutputStream fos = new FileOutputStream(dest);
    int b;
    while((b = fis.read()) != -1){
    fos.write(b);
    }
    fos.close();
    fis.close();

    //备份数据要删除
    src.delete();

    }
    }
  • 扩展点:

    在单元测试中,相对路径是相对当前模块而言的。

    1
    2
    3
    File file = new File("aweihaoshuai.txt");
    file.createNewFile();
    //此时是把aweihaoshuai.txt这个文件新建到模块中了。

23.注解

  • 注释和注解的区别:

    1. 共同点:都可以对程序进行解释说明。
    2. 不同点:
      • 注释:是给人看的,只在Java文件中有效,在class文件中不存在注释的,当编译之后,会进行注释擦除。
      • 注解:是给虚拟机看的,当虚拟机看到注解之后,就知道要做什么事情了。
  • 使用注解

    1. 当子类重写父类方法时,在重写的方法上面写@Override。
    2. 当虚拟机看到@Override时,就知道下面的方法是重写的父类。(检查语法,如果语法正确,编译正常,如果语法错误,报错)
  • Java中已经存在的注解

    1. @Override:表示方法的重写
    2. @Deprecated:表示修饰的方法已过时
    3. @SuppressWarnings(“all”):压制警告
    4. 三方框架中提供的注解(如:Junit)
      • @Test 表示运行测试方法
      • @Before 表示在Test之前运行,进行数据的初始化
      • @After 表示在Test之后运行,进行数据的还原
  • 特殊属性

    1. value:当注解中只有”一个属性”,并且属性名是”value”,使用注解时,可以省略value属性名

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      //注解的定义
      public @interface Anno2 {
      public String value();

      public int age() default 23;
      }

      //注解的使用
      @Anno2("123")
      public class AnnoDemo2 {

      @Anno2("123")
      public void method(){

      }
      }
  • 元注解

    可写在注解上面的注解

    1. @Target :指定注解能在哪里使用
      • 作用:用来标识注解使用的位置,如果没有使用该注解标识,则自定义的注解可以使用在任意位置.
      • 可使用的值定义在ElementType枚举类中:
        1. TYPE,类,接口
        2. FIELD, 成员变量
        3. METHOD, 成员方法
        4. PARAMETER, 方法参数
        5. CONSTRUCTOR, 构造方法
        6. LOCAL_VARIABLE, 局部变量
    2. @Retention :可以理解为保留时间(生命周期)
      • 作用:用来标识注解的生命周期(有效范围)
      • 可使用的值定义在RetentionPolicy枚举类中:
        1. SOURCE:注解只作用在源码阶段,生成的字节码文件中不存在
        2. CLASS:注解作用在源码阶段,字节码文件阶段,运行阶段不存在,默认值
        3. RUNTIME:注解作用在源码阶段,字节码文件阶段,运行阶段
  • 模拟JUnit自带的@Test注解

    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
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyTest {
    }

    public class MyTestMethod {

    @MyTest
    public void method1(){
    System.out.println("method1");
    }

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

    @MyTest
    public void method3(){
    System.out.println("method3");
    }
    }

    public class MyTestDemo {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException {
    //1,获取class对象
    Class clazz = Class.forName("com.itheima.test2.MyTestMethod");

    //获取对象
    Object o = clazz.newInstance();

    //2.获取所有方法
    Method[] methods = clazz.getDeclaredMethods();
    for (Method method : methods) {
    //method依次表示类里面的每一个方法
    method.setAccessible(true);
    //判断当前方法有没有MyTest注解
    if(method.isAnnotationPresent(MyTest.class)){
    method.invoke(o);
    }
    }
    }
    }

24.枚举

  • 枚举介绍

    枚举是java中的一种特殊类型,一般用来做信息的标志和分类

  • 定义格式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    修饰符 enum 枚举类名{
    枚举项1,枚举项2...
    }

    public enum Season {
    // 枚举项:枚举类的对象
    SPRING,SUMMER,AUTUMN,WINTER
    }

  • 枚举的特点:

    1. 每一个枚举项其实就是该枚举的一个对象

    2. 通过枚举类名去访问指定的枚举项

    3. 所有枚举类都是Enum的子类

    4. 枚举也是类,可以定义成员变量。

    5. 枚举类的第一行上必须是枚举项,最后一个枚举项后的分号是可以省略的

      但如果枚举类有其他的东西,这个分号就不能省略,建议不要省略

    6. 枚举类可以有构造器,但必须是private的,它默认也是private

    7. 枚举类也可以有抽象方法,但是枚举项必须重写该方法

    1
    2
    3
    4
    5
    6
    public enum Season {
    // 枚举项:枚举类的对象
    SPRING,SUMMER,AUTUMN,WINTER


    }