java-进阶-1

day01-static-代码块-继承-权限修饰符-final

01-static关键字的特点

  • static 关键字的介绍

    • static 是静态的意思,可以修饰成员变量,成员方法。被static 修饰的成员在内存中只存储一份,可以被共享,修改
  • 特点

    1. 被类的所有对象共享
    2. 可以通过类名,直接调用,例如: Student.school
    3. 随着类的加载而加载,优先于对象存在
  • public class StudentTest {
    
        public static void main(String[] args) {
            Student.school = "黑马程序员";//随着类的加载而加载,优先于对象存在.....直接调用,例如: Student.school
            Student stu1 = new Student();
            stu1.name = "张三";
            stu1.age =18;
            //stu1.school = "黑马程序员";
            Student stu2 = new Student();
            stu2.name = "李四";
            stu2.age =24;
            //stu2.school = "传智剥壳";//被类的所有对象共享
            Student stu3 = new Student();
            stu3.name = "王五";
            stu3.age =25;
            //stu3.school = "传智剥壳";//被类的所有对象共享
    
            stu1.show();
            stu2.show();
            stu3.show();
    
        }
    }
    
    //创建学生类
    class Student{
        String name;
        int age;
        static String school;
        public void show(){
            System.out.println(name + "-----" + age + "-----" + school);
        }
    }
    

02-成员变量的分类

  • 静态成员变量(有static修饰,属于类,内存中只加载一次):常表示如在线人数信息、等需要被共享的信息,可以被共享访问

    静态成员变量的访问方式

    • 推荐:类名.静态成员变量
    • 不推荐:对象名.静态成员变量 assets\images
  • 实例成员变量(无static修饰,存在于每一个对象中):常表示姓名、年龄、等,属于每一个对象的信息

03-static内存图解

在这里插入图片描述

04-static修饰成员方法的调用方式

  • 成员方法的分类

    1. 静态成员方法(有static修饰,属于类),建议使用类名访问,也可以使用对象访问
    2. 实例成员方法(无static修饰,属于对象),只能用对象触发访问
  • public class UserTest {
        public static void main(String[] args) {
            //调用静态成员方法.通过类名调用
            UserTest.method();
            //实例成员方法,通过对象调用
            // 1、 创建对象
            UserTest user = new UserTest();
            // 2、 对象.调用
            //UserTest.show();编译报错
            user.show();
        }
        public static void method(){
            System.out.println("static修饰的成员方法,即静态成员方法,建议使用类名调用");
        }
        public void show(){
            System.out.println("无static修饰的成员方法,即实例成员方法,只能通过对象名调用");
        }
    }
    

05-static修饰成员方法的思路

  • 使用场景

    • 方法中,必须要用到非静态的成员变量,则定义为非静态方法
    • 方法要实现的功能,如果可以不用成员变量,则可以定义为静态方法(一般体现在工具类中)
    • 静态成员方法只能使用静态成员变量
  • 在这里插入图片描述

在这里插入图片描述

06-数组工具类【模拟】

  • 工具类:工具类存在的价值,只是为了给其他类提供服务的

  • 如果该类中所有的方法,都是static修饰的,通常会多做一步,私有构造方法,

    目的,为了避免他人创建对象,避免使用对象调用静态方法

  • public class ArraysTools {
    
        private ArraysTools(){}
        //打印数组[10,20,30,40,50,60]
        public static void printArray(int[] arr){
            System.out.print("[");
            for (int i = 0; i < arr.length; i++) {
                if (i == arr.length - 1){
                    System.out.println(arr[i] + "]");
                }else {
                    System.out.print(arr[i] + ", ");
                }
            }
        }
        //数组求和
        public static int getSum(int[] arr){
            int sum = 0;
            for (int i = 0; i < arr.length; i++) {
                sum += arr[i];
            }
            return sum;
        }
        //求数组中的最大值
        public static int getMax(int[] arr){
            int max = arr[0];
            for (int i = 1; i < arr.length; i++) {
                if (arr[i] > max){
                    max = arr[i];
                }
            }
            return max;
        }
        //求数组中的最小值
        public static int getMin(int[] arr){
            int min = arr[0];
            for (int i = 1; i < arr.length; i++) {
                if (arr[i] < min){
                    min = arr[i];
                }
            }
            return min;
        }
        //求数组的平均值
        public static double getAvg(int[] arr){
            double avg = (getSum(arr) - getMax(arr) - getMax(arr))*1.0 / (arr.length - 2);
            return avg;
        }
    }
    

07-static注意事项

  • 静态方法只能访问静态的成员,不可以直接访问实例成员
  • 实例方法可以访问静态的成员,也可以访问实例成员
  • 静态方法中是不可以出现 this 关键字(this指向当前对象,谁调用就指向谁,先创建对象,才有this)

08-代码块

  • 在java类下,使用{ } 括起来的代码被称为代码块

  • 代码块的分类

    • 局部代码块

      位置:方法中定义

      作用:限定变量的生命周期,及早释放,提高内存的利用率

      在这里插入图片描述

    • 构造代码块

      位置:类中方法外

      特点:每次构造方法执行时,都会执行该代码块中的代码,并且在构造方法执行前执行

      作用:将多个构造方法中相同的代码,抽取到构造代码块中,提高代码的复用性

      public class CodeBlock {
          public static void main(String[] args) {
             Student stu1 = new Student();
             Student stu2 = new Student("张三");
          }
      }
      class Student{
          private String name;
          public Student(){
              System.out.println("默认空参构造方法");
          }
          public Student(String name){
              System.out.println("带参构造方法");
              this.name = name;
          }
          //构造代码块
          {
              System.out.println("-------构造代码块在构造方法发执行前执行-------");
              System.out.println("类中方法外 { },没有static修饰,是构造代码块");
              System.out.println("创建对象,就会执行构造代码块");
          }
      }
      
    • 静态代码块

      位置:类中方法外定义

      特点:需要通过static关键字修饰,随着类的加载而加载,并且只执行一次

      作用:在类加载的时候做一些数据初始化的操作

      public class CodeBlock {
          public static void main(String[] args) {
             Student stu1 = new Student();
             Student stu2 = new Student("张三");
          }
      }
      class Student{
          private String name;
          //静态代码块
          static {
              System.out.println("类中方法外,static{},是静态代码块,随着类的加载而加载,只执行一次");
         }
          public Student() {
          }
          public Student(String name) {
              this.name = name;
          }
      }
      
      
  • 斗地主游戏,静态代码块的使用

    在这里插入图片描述

public class Poker {
    // 需求:在启动游戏房间的时候,应该提前准备好54张牌,后续才可以直接使用这些牌数据
    /**
     *  分析:
     *  1、该房间需要一副牌
     *  2、定义一个静态的ArrayList 集合存储54张牌对象,静态的集合只会加载一次
     *  3、在启动游戏房间前,应该将54张牌初始化好
     */
    public static ArrayList<String> list = new ArrayList<>();
    static {
        String[] colors = {"♥","♠","♦","♣"};
        String[] nums = {"A","1","2","3","4","5","6","7","8","9","10","J","Q","K"};
        for (int i = 0; i < colors.length; i++) {
            String color = colors[i];
            for (int j = 0; j < nums.length; j++) {
                list.add(color+nums[j]);
            }
        }
        list.add("小王");
        list.add("大王");
        //打乱集合中的顺序
        Collections.shuffle(list);

        System.out.println(list);
    }
}

09-继承介绍和入门

  • 继承的介绍

    继承:让类于类之间产生关系(之父类关系),子类可以直接使用父类中,非私有的成员

    继承的格式:

    • 格式:public class 子类名 extends 父类名{ }
    • 范例: public class Zi extends Fu { }
    • Fu: 是父类,也称为基类,超类
    • Zi:是子类,也称为派生类
  • 什么时候使用继承?

    • 当类与类之间,存在相同(共性)的内容,并且产生了 is a 的关系,就可以考虑使用继承,来优化代码
  • 继承解决的问题:

    • 共性抽取
  • 继承的入门

    猫 == 狗 =====>动物

    public class Animal {
        private String name;
        private String color;
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public String getColor() {
            return color;
        }
        public void setColor(String color) {
            this.color = color;
        }
    }
    
    
    public class Cat extends Animal{
    }
    
    public class Dog extends Animal{
    }
    
    public class AnimalTest {
        public static void main(String[] args) {
            //创建猫的对象
            Cat c = new Cat();
            c.setName("花花");
            c.setColor("灰色");
            System.out.println(c.getName() + "---" + c.getColor());
            //创建狗的对象
            Dog d = new Dog();
            d.setName("小黑");
            d.setColor("黑色");
            System.out.println(d.getName() + "---" + d.getColor());
        }
    }
    

10-继承中成员变量的访问特点

  • 当继承关系产生后,子父类中,出现了重名的成员变量,调用的时候,会根据就近原则,优先使用子类的成员变量,

    非要使用父类的成员变量的话,使用 super 进行区分

  • this :调用本类的成员

  • super : 调用父类的成员

public class Test {
    public static void main(String[] args) {
        Son son = new Son();
        son.getNum();
    }
}
class Father{
    int num = 10;
}
class Son extends Father{
    int num = 20;
    public void getNum(){
        int num = 40;
        System.out.println("当前方法中的num:" +num);  //40
        System.out.println("son类中的num:" +this.num);   //20
        System.out.println("father类中的num:" +super.num);    //10
    }
}

11-方法重写(Override)(1)

public class Test {
    public static void main(String[] args) {
        Son son = new Son();
        son.catchSheep();
    }
}
class Father{
   public void catchSheep(){
       System.out.println("灰太狼:使用弓箭捉羊");
   }
}
class Son extends Father{
    @Override
    public void catchSheep() {
        //super.catchSheep();
        System.out.println("小灰灰:使用猎枪捉羊");
    }

}
  • 问题: 当子父类关系产生后,子类定义的方法,跟父类重名,创建子类对象,调用方法,执行的是谁的逻辑?

    答案: 会执行子类的方法逻辑,这虽然是就近原则,但实际上,是子类的方法,对父类的方法,进行了重写。

  • 方法重载( Overload ):在同一个类中,方法名相同,参数不同,与返回值无关,参数不同:个数不同,类型不同,顺序不同

  • 方法重写( Override ) : 在继承关系中,子父类出现了方法声明一模一样的方法:方法名,参数,返回值( 全部相同 ),方法的重写,也叫方法的覆盖重写。

  • 什么时候需要方法重写呢?

    子类觉得父类的逻辑不好,或是想增强逻辑,实现父类没有的功能,就可以对父类的方法进行覆盖重写

    不改变父类方法的前提下,子类也有自己的实现。

  • -方法重写的注意事项

  • 父类私有的成员方法不能重写,静态的方法不能重写

  • 子类重写父类的成员方法,权限修饰符必须大于等于父类

  • @Override注解,标记一个方法是重写父类方法(语法检查)

12-权限修饰符

在这里插入图片描述

13-Java中继承的特点

  • java只支持单继承,不支持多继承,但是支持多层继承

    在这里插入图片描述

  • 任何一个类,都直接或是间接继承Object

14-继承中构造方法的访问特点

  • 子类不能继承父类的构造方法

    • 在子类初始化之前,是否需要先完成父类的初始化?

    ​ 回答:有必要先完成父类的初始化

    ​ 原因:因为子类在初始化的过程中,很有可能用到了父类的数据,如果父类没有提前完成初始化,子类访问父类的数据的时候, 就相当于使用一个变量进行运算操作,但是变量没有赋值。

    • 子类是如何完成父类的初始化操作的?

    ​ 思考:要初始化一个对象,要先执行构造方法

    ​ 回答:子类只要调用到父类的构造方法,就可以完成父类的初始化操作。

    • 子类如何调用到父类的构造方法呢?

      ​ 在所有子类的构造方法中,都会默认隐藏一句代码,super(); 来访问父类的空参构造

    class A{
        public A(){
            System.out.println("学习java基础");
        }
    }
    class B extends A{
        public B(){
            super();//默认,super();可以访问到class A的空参构造
            System.out.println("数据结构与算法分析");
        }
    }
    

15-继承案例-学生和老师

  • 测试类

    public class Test {
        /**
         *   需求:
         *          Person类:
         *                  成员变量:姓名,年龄
         *          Teacher类:
         *                  成员变量:姓名,年龄
         *           Student类:
         *                   成员变量:姓名,年龄,成绩
         *
         */
        public static void main(String[] args) {
            Teacher teacher = new Teacher("张三",30);
            System.out.println(teacher.getName() + "---" + teacher.getAge());
            Student stu = new Student("小明",18,100);
            System.out.println(stu.getName() + "---" +stu.getAge() + "---" + stu.getScore());
        }
    }
    
  • Person类,标准的javabeen类

    public class Person {
        private String name;
        private int age;
        //省略set,get,空参,带参方法
    }
    
  • Teacher类

    public class Teacher extends Person{
        public Teacher() {
        }
        public Teacher(String name, int age) {
            super(name, age);
        }
    }
    
  • Student类

    public class Student extends Person{
        private int score;
    
        public Student() {
        }
        public Student(String name, int age, int score) {
            super(name, age);
            this.score = score;
        }
        public int getScore() {
            return score;
        }
    
        public void setScore(int score) {
            this.score = score;
        }
    }
    

16-继承构造方法调用图解

在这里插入图片描述

17-this和super

  • this : 代表本类对象的引用

  • super : 代表父类存储空间的标识( 可以理解为父类对象的引用 )

    在这里插入图片描述

  • 注意:this() 和 super() 都在争夺构造方法第一行的位置,所以二者不能共存。

  • this();访问本类的构造方法

public class Test {
    public static void main(String[] args) {
        A a = new A(10,20,30,40);
    }
}
class A{
    int a;
    int b;
    int c;
    //需求:需要增加一个成员变量  int d;
    int d;
    public A() {
    }

    public A(int a, int b, int c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }
    public A(int a, int b, int c,int d) {
        //交给public A(int a, int b, int c)去初始化  a,b,c
        this(a,b,c);
        this.d = d;
    }
}

18-final关键字的作用

  • final : 修饰符( 方法, 类 ,变量 )

    • 方法: 被final 修饰的方法,不能被重写,但是子类可以继承使用

      在这里插入图片描述

    • 类: 被final 修饰的类,不能被继承,但是可以继承其他类

      • 一个类中,所有的方法,都不希望子类重写,干脆就不要有子类,直接final修饰
    • 变量: 被final 修饰的变量,只能赋值一次,无法修改

19-final修饰变量的细节补充

  • 被final修饰的基本数据类型,其数据值不能更改

在这里插入图片描述

  • 被final修饰的引用数据类型,其引用地址不能更改

    在这里插入图片描述

  • final修饰成员变量特点

    1. 定义成员变量的时候就赋值

      在这里插入图片描述

    2. 如果定义成员变量的时候没有赋值,须在构造方法中进行赋值

      在这里插入图片描述

  • final 修饰变量的命名规范

    • 如果final修饰的变量的变量名是一个单词,单词字母全部大写

      在这里插入图片描述

    • 如果final修饰的变量的变量名是多个单词组合,单词之间用 — 分隔

      在这里插入图片描述


day02-包-抽象类-接口-多态-内部类

-包

  • 包本质上是一个文件夹,用来对类文件进行分类管理,定义一个类时,需要指定类在哪个包下。

  • 定义包的语句 package com.itheima.d1_abstract.demo1;

  • 不同包下的访问,需要导包

    导包的格式: import 包名.类名

    import java.util.*;导入java.util包下的所有类
    
  • 如果多个包下有相同的类,导包的时候需要导入某一个包下的类

    import java.util.Scanner;

    import com.itheima.d1_abstract.demo2.Student;
    

01-抽象类和抽象方法

​ 抽象类的意义:限定子类的行为

  • 抽象类
    • 一个类存在抽象方法,这个类就必须定义为抽象类
    • 就是一种特殊的父类,跟普通的父类的区别,在于内部可以定义抽象方法
  • 抽象方法:
    • 在父类中,一个方法的实现,父类不知道如何具体去实现,就定义为抽象方法
    • 将共性行为(方法),抽取到父类之后,发现该方法的实现逻辑无法在父类中给出具体明确,该方法就可以定义为抽象方法
  • 定义格式:
    • 抽象类的定义格式:
      • public abstract class 类名 {}
    • 抽象方法的定义格式:
      • public abstract 返回值类型 方法名 (参数列表);

02-抽象类注意事项

  • 如果子类继承父类是抽象类,子类必须重写父类的所有的抽象方法
  • 如果子类继承父类是抽象类,子类不重写父类的抽象方法,子类可以定义为抽象类
  • 抽象类可以有构造方法,使得子类能通过super()进行访问
  • 抽象类不能创建对象
  • abstract与static不能共存,abstract修饰的方法,没有方法体,调用没有任何意义
  • abstract 与 final 不能共存,final修饰的方法,不能让子类重写
  • abstract 与private,被private修饰的方法,子类不能重写

模板方法模式解决了什么问题?

  • 模板方法已经定义了通用结构,不能确定的功能定义成抽象方法
  • 子类根据自己的需要复写方法即可

03-接口的介绍

接口:体现的思想是对规则的声明

​ Java中的接口更多体现的是对行为的抽象

  • 定义接口

    public interface 接口名{
        //常量
        public final static int num = 10;
        //抽象方法
        public abstract void show();   
    }
    

04-类和接口之间的各种关系

  1. 类与类

    单继承,一个类只能继承一个父类

  2. 类与接口

    实现关系,可以单实现,也可以多实现,甚至可以在继承一个类的同时,实现多个接口

    ​ 多个接口,如果有一模一样的方法声明,仅仅是方法声明,没有方法体,没有逻辑

    ​ 父类只能有一个,亲爹,多继承会有逻辑冲突

    class Fu{
        public void method(){
            System.out.println("学习Java程序开发。。。。");
        }
    }
    interface InterA{
        //学习
        public abstract void method();
    }
    interface InterB{
        //学习
        public abstract void method();
    }
    class InterImpl1 implements InterA,InterB{
        @Override
        public void method() {
            System.out.println("天天学习,天天快乐~");
        }
    }
    class InterImpl2 extends Fu implements InterA,InterB{
    
    }
    
  3. 接口与接口

    多继承,一个接口可以继承多个接口,相当于把多个接口的功能集成到一个接口中

  • 接口弥补了抽象类的局限性

    ​ 抽象类:只能单继承

    ​ 接口:可以多实现

    抽象类的意义:限定子类的行为

    在这里插入图片描述

  • JDK8以后,开始支持新性能

    • 默认方法

      ​ 默认被public default修饰,可选重写

    • 静态方法

      ​ 被 public static 修饰,接口名调用

    • 私有方法

      ​ 被private 修饰,只能在当前接口中被访问

在这里插入图片描述

抽象类和接口的区别

  • 抽象类是把子类的共性进行抽取,而接口是把某一个单一功能进行抽取,谁需要就去实现接口。

接口的注意事项

  • 接口不能实例化,不允许创建对象,并且接口没有构造方法
  • 接口中的成员变量是常量,默认被public static final修饰
  • 接口中成员方法,只能是抽象方法,默认被public abstract修饰
  • 接口的实现类
    1. 一个类实现接口,必须重写所有的抽象方法
      2. 将自己变成抽象类(不推荐使用)

05-多态的前提条件

  • 什么是多态?

    • 在继承父类或者实现接口的基础上,允许同一个对象具有多种表示形态

      Dog  dog = new Dog();
      Animal dog = new Dog();
      
  • 多态的表现形式

    • 父类 变量 = 子类对象;
    • 接口 变量 = 实现类对象;
  • 前提条件

    1. 要有继承关系 / 实现关系
    2. 要有方法重写
    3. 要有父类引用指向子类对象
  • 多态的好处

  • 当把方法的参数写成父类 / 接口类型时,调用方法的时候可以传递子类 / 实现子类对象

  • 原本是什么类型,才能还原成什么类型

    Dog dog = new Dog();
    Animal animal = dog;
    Dog dog1 = (Dog) animal;
    
  • ClassCastException,异常报错,类型转换异常

  • Dog dog = new Dog();
    Animal animal = dog;
    
     Cat cat = (Cat) animal;
    Feeder.feed(cat);
    
  • instanceof关键字

    • 作用:测试它左边的对象是否是它右边的类的实例

06-多态创建对象后-成员的访问特点

  • 成员变量:

    • 编译看左边(父类),运行看左边(父类)
    • 原因:当前是父类的引用,所以访问有一些局限性,只能看到堆内存,对象中,super那一小块区域
    • 所以,访问的是父类的成员变量
  • 成员方法

    • 编译看左边(父类),运行看右边(子类)

      细节:编译期检查父类中是否有这个方法

      ​ 没有:编译出错

      ​ 有:运行的时候,要走子类重写后的逻辑

      为什么一定要走子类重写后的逻辑呢?

      ​ 如果父类的方法,是抽象方法,那就没有逻辑可以执行

    • 所以访问的是子类的成员方法

  • 静态成员

    ​ 编译看左边(父类),运行看左边(父类)

    ​ 静态成员,随着类的加载而加载,有先于对象而存在

    • 所以访问的是父类的静态成员

07-多态的好处和弊端

好处:

​ 提高了代码的扩展性

​ 把方法的参数写成父类,接口类型,调用方法的时候可以传递子类对象,实现类的对象

弊端:

​ 父类引用不能直接访问子类特有的成员

​ 如果要想要调用子类特有的成员,怎么办?

		1. 直接创建子类对象
		2. 使用  instanceof  判断父类引用是否是子类的实例,然后进行向下转型

08-多态的转型问题

​ 规则:多态的写法,只能调用子父类共性的方法,不能直接调用子类特有的方法,必须转型后,才能调用

  • 向上转型

    ​ 从子到父,(父类引用指向子类对象),

    ​ Fu fu = new Zi();

    ​ Animal animal = new Dog();

    • 可以类比数据类型转换的自动类型转换,小范围的数据类型可以直接给大范围的数据类型赋值。

      ​ double b = 10;

  • 向下转型

    ​ 从父到子, (父类引用转为子类对象),将父类引用,所记录的子类对象,重新再交给子类的引用

    ​ //父类引用先指向子类对象

    ​ Fu fu = new Zi();

    ​ //重新再交给子类的引用,进行向下转型

    ​ Zi zi = (Zi) fu;

    • 可以类比数据类型转换中强制类型转换,数据范围大的转换给数据范围小的变量

      ​ int a = (int) 3.14;

09-内部类

定义:

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

public class Outer{
    //内部类
    public class Inner{
        
    }
}
  • 内部类通常可以直接访问外部类的成员,包括私有成员

10-成员内部类和静态成员内部类

  • 成员内部类,

    • 位置,和外部类的成员 平级
    public class Test {
        public static void main(String[] args) {
            //成员内部类创建对象的格式
    
            // 外部类的类名.内部类的类名  变量名  =  new  外部类().new 内部类();
    
            Outer.Inner oi = new Outer().new Inner();
            oi.show();
        }
    }
    class Outer{
        int num = 1000;
        class Inner{
            int num = 100;
            public void show(){
                int num = 10;
                System.out.println("我是成员内部类Inner....show");
                System.out.println("num:" + num);//10 访问的是方法中的show 的变量
                System.out.println("num:" + this.num);//100 访问的是内部类中的 的变量
                System.out.println("num:" + Outer.this.num);//1000 访问的是外部类中的 的变量
    
            }
        }
    }
    
    • 成员内部类可以访问外部类的成员,包括私有成员
    • 当外部类与内部类以及方法中,有变量名一样的情况,要访问外部类的成员,可以跟上外部类的类名.this进行区分
  • 静态内部类

    • 静态内部类,只是成员内部类被static关键字修饰,特点和普通类一样
    • 创建对象的格式
      • 外部类的类名.内部类的类名 变量名 = new 外部类的类名.内部类的类名();
      • Outer.Inner oi = new Outer.Inner();
    • public class Test {
          public static void main(String[] args) {
              //静态内部类创建对象的格式
      
              // 外部类的类名.内部类的类名  变量名  =  new  外部类的类名.内部类();
      
              Outer.Inner oi = new Outer.Inner();
             
          }
      }
      class Outer{
          int num = 1000;
          static  int index = 5;
          static class Inner{
            public void show(){
                System.out.println("我是静态内部类  static Inner ... show");
                
                //System.out.println(num);   静态内部类,不能访问非静态的成员
                System.out.println(index);
            }
          }
      }
      

11-局部内部类-了解

  • 局部内部类放在方法、代码块、构造器等执行体中

  • 局部内部类的类文件为:外部类$局部内部类.class

    public class Test2 {
        public static void main(String[] args) {
            Student student = new Student();
            student.show();
        }
    }
    class Student{
        public void show(){
            System.out.println("Student.....show");
            class Inner{
                int num = 10;
                public void method(){
                    System.out.println("局部内部类,鸡肋语法,了解即可");
                    System.out.println("num:" + num);
                }
            }
            Inner i = new Inner();
            i.method();
        }
    }
    

11-匿名内部类-重点!

  • 概述:匿名内部类,本质上是一个特殊的局部内部类(定义在方法内部)

    前提:需要存在一接口或类

  • 前引

    public class Test3 {
        public static void main(String[] args) {
    
            method(new InnerImpl());
            /*
                     method();它应该接收什么参数呢?  传入的参数必须是  Innter接口的实现类对象
                        1.method(new Inner());  不允许,接口没有构造方法,不允许创建对象
                        2.method(new InnerImpl());  Innter接口的实现类对象
             */
    
        }
        public static void method(Inner i){//形参是一个接口
            i.show();
        }
    }
    //定义一个接口
    interface Inner{
        //定义一个show 的抽象方法
        void show();
    }
    class InnerImpl implements Inner{
    
        @Override
        public void show() {
            System.out.println("show.......");
        }
    }
    
  • 匿名内部类:

    • 将继承 / 实现,方法重写,创建对象,放在了一步进行
    • 格式:
      • new 类名(){} ------->继承这个类
      • new 接口名(){ } --------> 实现这个接口
public class Test4 {
    public static void main(String[] args) {

        method(new Inner2(){
            @Override
            public void show() {
                System.out.println("show....");
            }
        });
        /**
         * 匿名内部类
         * new Inner2(){
         *             @Override
         *             public void show() {
         *                 System.out.println("show....");
         *             }
         *         }
         *   把接口的实现类,方法的重写集成于一体      
         */

    }
    public static void method(Inner2 i){
        i.show();
    }
}
//定义一个接口
interface Inner2{
    //定义一个show 的抽象方法
    void show();
}
  • 匿名内部类是抽象类的子类或是接口的实现类,它既是类也是对象
  • 匿名内部类的使用场景:

    一般不会主动创建匿名内部类*

    当方法的参数是一个接口,需要传入一个接口类的实现对象

    1. 可以单独写一个class,中规中矩的实现和重写,创建对象
    2. 可以直接使用匿名内部类,简化当前代码
  • 遍历集合:在JDK8版本以后,API 提供了一个forEach方法,可以遍历集合元素

    ​ forEach方法底层会自动遍历集合,得到每一个元素

    ​ 然后调用Consumer接口实现类的accept方法,把元素赋值给accpet方法的参数

    ​ 对于accept是怎么复写的,交给实现类去实现

      ArrayList<String> list = new ArrayList<>();
            list.add("abc");
            list.add("123");
            list.add("hello");
            list.forEach(new Consumer<String>() {
                @Override
                public void accept(String s) {
                    System.out.println(s);
                }
            });
    

day03-Lambda-Object-Math-System-BigDecimal-Date

01-Lambda表达式

  • Lambda表达式是 JDK8 开始后的一种新语法形式。

  • 作用:简化匿名内部类的代码写法。

    -Lambda表达式的简化

(匿名内部类被重写方法的形参列表) ->{
    //被重写方法的方法体。
}
//  ->  是语法形式,无实际含义
  • 注意:Lambda表达式只能简化函数式接口的匿名内部类的写法形式
    • 什么是函数式接口?
      1. 首先必须是接口、其次接口中有且仅有一个抽象方法的形式
      2. 通常我们会在接口上加上一个 @FunctionalInterface注解,标记该接口必须是满足函数式接口
Integer[] array = {1,4,3,6,9,7};
        //降序排序,使用匿名内部类
        Arrays.sort(array, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2 - o1;
            }
        });
        //使用Lambda表达式
        Arrays.sort(array,(Integer o1,Integer o2)-> o1 - o2);

        System.out.println(Arrays.toString(array));
  • Lamdba表达式简化格式

    ​ 1.参数类型可以直接省略

    ​ 2.如果语句体只有一条,大括号,return关键字可以省略

    ​ 3.参数只有一个,小括号可以省略

  • Lamdba表达式的简化格式有哪些要求

    1. 什么时候可以省略参数类型?

      ​ 任何时候

    2. 什么时候可以省略大括号和return关键字?

      ​ 语句体只有一条语句时

    3. 什么时候可以省略小括号?

      ​ 参数只有一个时

  • 方法引用:对Lamdba进一步简化

    ​ 如果Lamdba表达式中的代码,可以用一个方法代替,就可以使用方法引用

    ​ 格式:

    ​ 对象名 : : 方法名

    ​ list.forEach(System.out : : println)

    ArrayList<String> list = new ArrayList<>();
            list.add("hello");
            list.add("java");
            list.add("world");
            list.forEach(System.out::println);
    
  • Lambda表达式,是否是匿名内部类的语法糖?

    ​ 语法糖:

    ​ 两个东西,代码实现方式不一样,但是内部原理一样

    ​ 集合遍历:迭代器遍历,增强for循环

    匿名内部类和Lambda表达式的内部原理是不一致的

    ​ 匿名内部类,会产生字节码文件

    ​ Lambda表达式,不会产生.class字节码文件

02-Object类

Object 是类层次结构的根类。每个类都使用 Object 作为超类。所有对象(包括数组)都实现这个类的方法。

  • Objects:用于操作任意对象的工具类,可以避免空指针异常

  • Object类的toString()方法

    • public String toString() {
              return getClass().getName() + "@" + Integer.toHexString(hashCode());
          }
      
  • 一个类会产生字节码文件,Java程序把字节码也创建成一个类Class
    
    return getClass().getName() + "@" + Integer.toHexString(hashCode())
    
    getClass().getName() --->  全类名(包名 + 类名) :com.itheima.mytest.demo01.Student
    
    @  --- >  分隔符
    
    Integer.toHexString(hashCode()) :
    			hashCode() --->根据内存地址,用哈希算法,计算出的哈希值
             Integer.toHexString(十进制数) --->  把十进制数转为 16 进制   -->  776ec8df
    
  1. toString()方法

    • 子类没有重写Object的toString()方法
    • 进行简单打印对象变量名的时候,打印的是地址值

      System.out.println(stu1);//com.itheima.mytest.demo01.Student@776ec8df
      
    • 使用对象调用toString()方法,打印的也是地址值

      System.out.println(stu1.toString());//com.itheima.mytest.demo01.Student@776ec8df
      
    • 当打印一个对象变量的时候,底层实现也会调用Object的toString()

      public static String valueOf(Object obj) {
          return (obj == null) ? "null" : obj.toString();
      }
      
    • 子类重写toString()方法

    当子类没有重写toString()方法的时候,打印对象名,用户得到的地址值,没有实际意义,然而用户希望看到一个类的属性值的时候,就可以对Object的toString()方法进行重写

    • @Override
          public String toString() {
              return "Student{" +
                      "name='" + name + '\'' +
                      ", age=" + age +
                      '}';
          }
      
    • 重写后,得到对象的属性值

    • System.out.println(stu1);//Student{name='张三', age=23}
      
  2. equals方法

    • 子类没有重写,比较的是两个对象的地址值

      System.out.println(stu1 == stu2);//false
      
      System.out.println(stu1.equals(stu2));//false
      
      • 没有重写equals()方法的时候,对象调用equals()进行比较,本质就是使用 == 进行比较

        public boolean equals(Object obj) {
                return (this == obj);
        }
        
    • 子类重写equals(),可以比较两个对象的属性值

      @Override
       public boolean equals(Object o) {
           //1、使用地址比较两个对象,地址一样,肯定是一个对象,直接return true
           if (this == o) 
               return true;
           /*  2、当前对象能调用方法,this指向当前对象,当前对象不为 null
                   2.1、参数对象如果为null,没有可比性,return false
                   2.2、如果两个字节码文件不一样,说明不是同一个类, return false
            */
           if (o == null || getClass() != o.getClass())
               return false;
      
           //3、经过前两步,说明参数对象不为null,也是同一个类,且地址不一样,把参数对象向下转型
           Student student = (Student) o;
      
           if (age != student.age) 
               return false;
      
           return name != null ? name.equals(student.name) : student.name == null;
       }
      
      Student stu1 = new Student("张三",23);
      Student stu2 = new Student("张三",23);
      
      System.out.println(stu1 == stu2);//false
      
      System.out.println(stu1.equals(stu2));//true
      

03-包装类

  • 什么是基本类型包装类?

    在这里插入图片描述

  • 学习包装类干什么用呢?

    ​ 包装类中会提供一些方法,便于对数据进行操作

    ​ 比如:字符串与基本类型数据的相互转换

  • String和基本类型的相互转换

    • 基本类型转换为字符串

      String s1=Integer.toString(300)

      String s2 = Double.toString(5678.88)

      • 每个包装类都有一个toString方法
    • 字符串转换为基本类型

      int a = Integer.parseInt(“12345”)

      double b = Double.parseDouble(“6.88”)

      • 每个包装类都有一个parseXxx方法
  • NumberFormatException

    ​ 格式转换异常

    Integer num = Integer.parseInt("123a456");
    
  • 自动装箱和拆箱

    • 装箱:把基本类型转换为引用类型

      • 自动装箱
      Integer  a = 10;//把int 类型的10,自动转换为Integer类型
      
      • 手动装箱
      Integer num = Integer.valueOf(10);
      
    • 拆箱:把引用类型转换为基本类型

      • 自动拆箱
      int b = a; //a 是Integer类型,运算时会自动转换为int类型
      
      • 手动拆箱
      int b = a.intValue();
      
  • 面试题

    • 自动装箱时,会判断这个数是否在 -128 ~ 127 范围内,如果在这个范围,就不重新创建对象,否则就会重新创建对象

      • Integer a = 127;
        Integer b = 127;
        System.out.println(a == b);//true
        
        Integer c = 128;
        Integer d = 128;
        System.out.println(c==d);//false
        
      • 自动装箱的原理

        • 底层会自动调用 Integer.valueOf(数值)
        • valueOf( ):
          • 判断数值是否在:-128 ~ 127之间
            • 不会重新创建,而是从底层的数组中,取出并返回
            • 不在
              • 重新 new Integer( )对象
    • 比较

      • Integer num1 = new Integer(100);
        Integer num2 = new Integer(100);
        
        int num3 = 100;
        
        System.out.println(num1 == num2);//false,内存中有两个地址
        //Integer引用类型,参与数学运算的时候,会自动拆箱为 int 基本数据类,基本数据类型比较的是数值
        System.out.println(num3 == num2);//true
        

04-Math类(工具类)

  • Math 类包含用于执行基本数学运算的方法,如初等指数、对数、平方根和三角函数。

  • public final class Math,它是最终类,不能被继承,所有的方法都是static修饰

  • 向上取整

    ​ public static double ceil(double a)

    ​ 返回最小的(最接近负无穷大)double 值,该值大于等于参数,并等于某个整数

       double num1 = Math.ceil(3.14);
            System.out.println(num1); //4.0
    
            double num2 = Math.ceil(3.67);
            System.out.println(num2);//4.0
    
            double num3 = Math.ceil(-1.3);
            System.out.println(num3);// -1.0
    
  • 向下取整

    ​ public static double floor(double a)

    ​ 返回最大的(最接近正无穷大)double 值,该值小于等于参数,并等于某个整数

        double num1 = Math.floor(3.14);
            System.out.println(num1); //3.0
    
            double num2 = Math.floor(3.67);
            System.out.println(num2);//3.0
    
            double num3 = Math.floor(-1.3);
            System.out.println(num3);// -2.0
    

05-System类

  • System 类提供的设施中,有标准输入、标准输出和错误输出流;对外部定义的属性和环境变量的访问;加载文件和库的方法;还有快速复制数组的一部分的实用方法。

  • public static void exit(int status)

    ​ 终止当前正在运行的 Java 虚拟机。参数用作状态码;根据惯例,非 0 的状态码表示异常终止。

  • public static long currentTimeMillis()

    ​ 返回以毫秒为单位的当前时间。

    ​ 当前时间与协调世界时 1970 年 1 月 1 日午夜之间的时间差(以毫秒为单位测量)。

    可以使用该方法,测试程序运行所消耗的时间;

    可以计算 年 - 月 - 日

  • public static void arraycopy(Object src,int srcPos,Object dest,int destPos,int length)

    从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束。

  • /*
                System.arraycopy(Object src,int srcPos,Object dest,int destPos,int length)
                参数:
                    Object src   源数组
                    int srcPos   源数组中的起始位置。
                    Object dest  复制到的目标数组
                    int destPos  从复制到目标数组的起始位置开始粘贴
                    int length   要复制的数组元素的数量。
             */
    
  • int[] arr = {10,20,30,40,50};
    
            int[] arr2 = new int[5];
    
            System.arraycopy(arr,1,arr2,2,3);
    
            System.out.println(Arrays.toString(arr2)); //[0, 0, 20, 30, 40]
    

06-BigDecimal类

  • double类型小数参与运算时,会有精度损失

在这里插入图片描述

  • BigDecimal的作用是什么?

  • BigDecimal类可以用来对小数进行精确运算、控制小数的位数

  • BigDecimal的对象如何获取?

  • BigDecimal b1 = BigDecimal.valueOf(0.1);

  • public static BigDecimal valueOf(double val): 包装浮点数成为BigDecimal对象

//获取两个BigDecimal对象

        BigDecimal num1 = BigDecimal.valueOf(0.09);

        BigDecimal num2 = BigDecimal.valueOf(0.01);

        //加法运算
        BigDecimal add = num1.add(num2);
        System.out.println(add);//0.10

        //减法运算
        BigDecimal sub = num1.subtract(num2);
        System.out.println(sub);//0.08

        //乘法运算
        BigDecimal mul = num1.multiply(num2);
        System.out.println(mul);//0.0009

        //除法运算
        BigDecimal div = num1.divide(num2);
        System.out.println(div);//9
  • 当除不尽的情况

    BigDecimal num3 = BigDecimal.valueOf(10);
            BigDecimal num4 = BigDecimal.valueOf(3);
    
            BigDecimal div2 = num3.divide(num4);
            System.out.println(div2);
    
    //ArithmeticException  报错,异常
    
  • public BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode),方法,可以保留指定位数

    • RoundingMode roundingMode有三个参数:
      • 具体看API

BigDecimal num3 = BigDecimal.valueOf(10);
BigDecimal num4 = BigDecimal.valueOf(3);
BigDecimal div2 = num3.divide(num4, 2, RoundingMode.HALF_UP);
System.out.println(div2); // 3.33
  • 将BigDecimal对象转换为基本数据类型 doubleValue()

    BigDecimal bd1 = BigDecimal.valueOf(0.01);
    BigDecimal bd2 = BigDecimal.valueOf(0.09);
    
    BigDecimal add = bd1.add(bd2);
    
    double doubleValue = add.doubleValue();
    // 将此 BigDecimal 转换为 double。  public double doubleValue()
    
    System.out.println(doubleValue);//0.1
    

07-Date日期类

  • Date类的对象,用来表示任意的日期 和 时间(单位:毫秒)

  • 构造方法

    1. public Date();

      ​ 创建一个Date对象,代表的是系统当前日期和时间

    2. public Date(long time);

      ​ 创建一个Date对象,代表自1970年1月1日0时0分0秒以来的

  • 实例方法

    1. public long getTime()

      ​ 返回从1970年1月1日0时0分0秒走到此刻的总毫秒值

    2. public void setTime(long time); 设置Date对象自1970年1月1日0时0分0秒以来的毫秒值

//需求:请计算出当前时间往后走1小时26分之后的时间是多少?
        
        //1、获取Date对象
        Date date = new Date();//空参构造

        System.out.println("修改之前的date:" + date);

        //2、获取当前date对象的毫秒值
        long currentTime = date.getTime();

        //3、当前时间  +  1小时26分
        long l = currentTime + 1 * 60 * 60 * 1000 + 26 * 60 * 1000;

        //4、把修改后的时间,赋值给date对象
        date.setTime(l);

        System.out.println("修改之后的date:" + date);

08-SimpleDateformat类

  • SimpleDateFormat 是一个以与语言环境有关的方式来格式化和解析日期的具体类。它允许进行格式化(日期 -> 文本)、解析(文本 -> 日期)和规范化。

  • 构造方法

    ​ public SimpleDateFormat(String pattern)

    ​ 用给定的模式和默认语言环境的日期格式符号构造 SimpleDateFormat。

    模式:

    ​ 字母 日期或时间元素 表示 示例
    y 年 Year 1996; 96
    M 年中的月份

    d 月份的天数

    H 一天中的小时数(0-23)

    m 小时中的分钟数

    s 分钟中的秒数

  • 日期格式化, 把Date对象转换为字符串

    //创建对象
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    //日期格式化,  把Date对象转换为字符串
    String format = sdf.format(new Date());
    
    System.out.println(format);
    
  • 日期解析,返回Date日期对象,需要 throws 异常

        public static void main(String[] args) throws ParseException {
            //创建对象
            SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
    
            //日期解析,返回Date日期对象
            Date parse = sdf2.parse("1998年11月15日 20:30:40");
            
            System.out.println(parse);//Sun Nov 15 20:30:40 CST 1998
        }
    
  • 需求:请计算出“2021年08月06日11点11分11秒”,往后走2天14小时的时间是多少?

      public static void main(String[] args) throws ParseException {
            //需求:请计算出“2021年08月06日11点11分11秒”,往后走2天14小时的时间是多少?
    
            /*
                   分析
                        给的是  字符串  “2021年08月06日11点11分11秒”,
                        1、用SimpleDateFormat类的日期解析为Date对象
                        2、调用Date对象的getTime()获取毫秒值
                        3、计算出2天14小时的毫秒值
                        4、调用Date对象的setTime()设置毫秒值
                        5、用SimpleDateFormat类的日期格式化,把Date对象转为字符串,格式化输出
    
             */
            // 创建SimpleDateFormat对象
    
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
            // 1、用SimpleDateFormat类的日期解析为Date对象
            Date date = sdf.parse("2021年08月06日 11:11:11");
            //2、调用Date对象的getTime()获取毫秒值
            long currentTime = date.getTime();
            //3、计算出2天14小时的毫秒值
    
            long l = 2 * 24 * 60 * 60 *1000 + 14 *60 *60 *1000;
            //4、调用Date对象的setTime()设置毫秒值
            date.setTime(currentTime + l);
            //5、用SimpleDateFormat类的日期格式化,把Date对象转为字符串,格式化输出
    
            String s = sdf.format(date.getTime());
            //输出结果
            System.out.println(s);//2021年08月09日 01:11:11
        }
    

08-Calendar获取年月日星期

  • public abstract class Calendar,它是抽象类

    ​ Calendar表示日历,包含很多与日期时间相关的字段信息(年、月、日、时、分、秒)

    Calendar可以对每一个日历字段进行单独操作

  • 获取Calendar的对象

    ​ public static Calendar getInstance(TimeZone zone)

    使用指定时区和默认语言环境获得一个日历。返回的 Calendar 基于当前时间,使用了给定时区和默认语言环境

    • Calendar c = Calendar.getInstance();
  • 方法名说明
    public int get(int field)取日期中的某个字段信息。
    public void set(int field,int value)修改日历的某个字段信息。
    public void add(int field,int amount)为某个字段增加/减少指定的值
    public final Date getTime()拿到此刻日期对象。
    public long getTimeInMillis()拿到此刻时间毫秒值
  public static void main(String[] args) {
        //获取Calendar的子类
        Calendar c = Calendar.getInstance();
        //c.set()设置年月日

        // c.set(1998,10,15);

       /* c.set(Calendar.YEAR,1998);
        c.set(Calendar.MONTH,10);
        c.set(Calendar.DAY_OF_MONTH,15);*/

        //c.add()可进行日期加减操作
        c.add(Calendar.YEAR,-5);//往后倒退5年

        //  c.get()获取年月日
        //获取年
        int year = c.get(Calendar.YEAR);
        //获取月    注意  java程序中 ,月份是从  0 开始
        int month = c.get(Calendar.MONTH) +1;
        //获取日
        int day = c.get(Calendar.DAY_OF_MONTH);
        System.out.println(year + "年" + month + "月" + day +"日");

    }
  • ***注意:***calendar是可变日期对象,一旦修改后其对象本身表示的时间将产生变化

  • 查表法获取Calendar中的星期

    ​ 老外认为,一个星期中的第一天是星期日,星期六是一个星期中的最后一天

    星期日  星期一  星期二  星期三  星期四  星期五  星期六
    1     2       3      4      5      6      7
    
    int day = c.get(Calendar.DAY_OF_WEEK);// 返回值是 3 ,则对应的是星期二
    
    String[] weeks = {"","星期日","星期一","星期二","星期三","星期四","星期五","星期六"};
    
    				  0     1      2       3        4       5       6        7 	
    int index = c.get(Calendar.DAY_OF_WEEK);
    System.out.println(weeks[index]);//星期二                      
    

day04-JDK8日期类-正则-Arrays

1、LocalDateTime对象的创建和获取【熟悉】

  1. JDK8 开始,java.time包提供了新的日期和时间API
  • LocalDate:年,月,日,周(无时间)

  • LocalTime: 时分秒

  • LocalDateTime:包含了日期及时间

  • Instant:代表的是时间戳

  • DateTimeFormatter:用于做时间的格式化和解析的

  • Duration:用于计算两个“时间” 间隔

  • Preiod:用于计算两个“日期间隔”

  • LocalDate、LocalTime、LocalDateTime

    • 分别表示日期、时间、日期时间对象,他们的类的实例是不可变的对象
    • 他们三者构建对象和API都是通用的
  • 构建对象的方式如下:

    public static Xxx now()   根据当前时间创建对象
    

    举例:

    LocalDateTime now = LocalDateTime.now();
    
    LocalDate now1 = LocalDate.now();
    
    LocalTime now2 = LocalTime.now();
    
    public static Xxx of(...)  指定日期 / 时间创建对象
    

    举例

    LocalDateTime of = LocalDateTime.of(1998, 11, 15, 20, 30, 40);
    
    LocalDate of1 = LocalDate.of(2020, 2, 2);
    
    LocalTime now3 = LocalTime.of(23,8,8);
    
    • LocalDateTime可以转换为LocalDate和LocalTime

      举例

      LocalDateTime now = LocalDateTime.now();
      
      LocalDate localDate = now.toLocalDate();
      
      LocalTime localTime = now.toLocalTime();
      
  • 获取

    • getXxx
  • 修改

    • withXxx
  • 向前减

    • minusXxx
  • 往后加

    • plusXxx

2、Instant 时间戳【了解】

  • JDK8获取时间戳。可以通过Instant类由一个静态的工厂方法now()可以返回当前时间戳

    Instant in = Instant.now();  //返回当前时间戳
    
    Date date = Date.from(instant);    //返回当前时间戳
    
    //Date对象转为Instant对象
    Instant instant = date.toInstant();
    
  • 时间戳是包含日期和时间的,与java.util.Date很类似,事实上Instant就是类似JDK8以前的Date

  • 设置时区

    Zone时区        ZoneId.systemDefault() 可以获取当前系统的默认时区
    Instant now = Instant.now();
    ZonedDateTime zonedDateTime = now.atZone(ZoneId.systemDefault());    
    

3、DateTimeFormatter对JDK8日期类进行解析或格式化

//日期对象
LocalDateTime localDateTime = LocalDateTime.now();

//日期格式化对象,返回字符串
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒 E a");

// 1.1格式化日期对象【日期格式化对象 . format (日期对象)】
System.out.println(dateTimeFormatter.format(localDateTime));//2022年07月08日 16时21分28秒 周五 下午

// 1.2 格式化日期对象【日期对象 . format (日期格式化对象)】
System.out.println(localDateTime.format(dateTimeFormatter));

//日期解析,返回日期对象
//解析格式
DateTimeFormatter dateTimeFormatter2 = DateTimeFormatter.ofPattern("yyyy年MM月dd日");
//返回解析对象
LocalDate localDateTime1 = LocalDate.parse("1998年11月15日", dateTimeFormatter2);

4、Period计算日期间隔,用于LocalDate之间的比较,【不能比较时分秒】

//当前本地 年月日
LocalDate today = LocalDate.now();

//假设生日  1998 - 11 - 15
LocalDate birthday = LocalDate.of(1998,11,15);

//Period 对象表示时间的间隔对象
Period period = Period.between(birthday,today);//第二个参数减第一个参数

//间隔多少年
int years = period.getYears();
//间隔多少月份
int months = period.getMonths();
//间隔多少天数
int days = period.getDays();
//总月份
long allMonths = period.toTotalMonths();
System.out.println("间隔 : " + years + "年" + months + "月" + days + "天");
System.out.println("总共间隔了" + allMonths + "月");

5、Duraation计算日期间隔,用于LocalDateTime、Instant之间的比较

  • 注意:没有两个日期时间差的年数

    //本地当前日期时间对象
    LocalDateTime today = LocalDateTime.now();
    
    //出生日期时间对象
    LocalDateTime birthday = LocalDateTime.of(1998,11,15,0,0,0);
    
    //Duration表示日期时间间隔对象
    Duration duration = Duration.between(birthday,today);//第二个参数减第一个参数
    
    //两个时间差的天数
    long days = duration.toDays();
    //两个时间差的小时数
    long hours = duration.toHours();
    //两个时间差的分钟
    long minutes = duration.toMinutes();
    //两个时间差的秒数
    long seconds = duration.toSeconds();
    //两个时间差的毫秒数
    long millis = duration.toMillis();
    

6、ChronoUnit可以用于在单个时间单位内测量一段时间,可以比较所有的时间单位【比较全面】

  • //本地当前日期时间对象
    LocalDateTime today = LocalDateTime.now();
    
    //出生日期时间对象
    LocalDateTime birthday = LocalDateTime.of(1998,11,15,0,0,0);
    
    //相差的年数
    long years = ChronoUnit.YEARS.between(birthday, today);
    //相差的月数
    long months = ChronoUnit.MONTHS.between(birthday, today);
    //相差的天数
    long days = ChronoUnit.DAYS.between(birthday, today);
    //相差的小时数
    long hours = ChronoUnit.HOURS.between(birthday, today);
    //相差的分钟数
    long minutes = ChronoUnit.MINUTES.between(birthday, today);
    //相差的秒数
    long seconds = ChronoUnit.SECONDS.between(birthday, today);
    //相差的毫秒数
    long millis = ChronoUnit.MILLIS.between(birthday, today);
    

6、正则表达式

  • 正则表达式是专门用于对字符串做合法性校验的

  • String类的哪个方法可以与正则表达式进行匹配。

    public boolean matches(String regex):

    判断是否匹配正则表达式,匹配返回true,不匹配返回false

  • 字符类【默认匹配一个字符】在这里插入图片描述

    • [abc] 只能是 a,b或c

      String regex = "[abc]";
      
      String str = "d";
      System.out.println(str.matches(regex));//false
      
    • [^abc] 除了 a , b ,c之外的任何字符

      String regex = "[^abc]";
      
      String str = "d";
      System.out.println(str.matches(regex));//true
      
    • [a - zA - Z] a到z和A-Z的范围

      String regex = "[a-zA-Z]";
      
      String str = "6";
      System.out.println(str.matches(regex));//false
      
    • [a - d[m - p]] a到d,或m到p,并集

      String regex = "[a - d[m - p]]";
      
      String str = "e";
      System.out.println(str.matches(regex));//false
      
    • [a - z&&[ d e f ]] a-z 与 def 取交集 ,即只能是 d e f

      String regex = "[a-z&&[def]]";
      
      String str = "c";
      System.out.println(str.matches(regex));//false
      
    • [a-z&&[^bc]]  a-z,除了b和c
      
      String regex = "[a-z&&[^bc]]";
      
      String str = "b";
      System.out.println(str.matches(regex));//false
      
    • [a-z&&[^m-p]]   a-z,除了m-p
      
      String regex = "[a-z&&[^m-p]]";
      
      String str = "n";
      System.out.println(str.matches(regex));//false
      
  • 预定义的字符类【默认匹配一个字符】在这里插入图片描述

    • .  任何字符
      
      String regex = "\\."; //使用转义字符后,只是普通的点。
      
      String str = ".";
      System.out.println(str.matches(regex));//true
      
      String regex2 = "."; // 没有转义,可以匹配任意字符
      
      String str2 = "$";
      System.out.println(str2.matches(regex2));//true
      
    • \d 一个数字

      String regex = "\\d";
      
      String str = "3";
      System.out.println(str.matches(regex));//true
      
    • \D 非数字

      String regex = "\\D";
      
      String str = "6";
      System.out.println(str.matches(regex));//false
      
    • \s 一个空白字符

      String regex = "\\s";
      
      //String str = " ";//空格
      //String str = "\t";//制表符
      //String str = "\n";//换行符
      String str = "\r";
      System.out.println(str.matches(regex));//true
      

      举例:判断一串数字是否含有空白符

      • String regex = "[\\d&&[^\\s]]+";
        
        String str = "134\t5 4";
        System.out.println(str.matches(regex));//false
        
    • \w [a-zA-Z_0-9] 英文字母、数字、下划线

      String regex = "\\w";
      
      String str = "_";
      System.out.println(str.matches(regex));//true
      
    • \W 就是对 \w取反 ===> 不是英文字母、数字、下划线的字符

      String regex = "\\W";
      
      String str = "\t";
      System.out.println(str.matches(regex));//true
      
  • 贪婪的量词【配合匹配多个字符】在这里插入图片描述

    • X? X,一次或根本不

      // a 出现一次或者 0 次
      String regex = "a?";
      
      // String str = "";//a 出现 0 次,根本没有
      String str = "a";//a 出现一次
      System.out.println(str.matches(regex));//true
      
    • X* X, 出现 0 次或者多次

      // 前2个字符为a-zA-Z_0-9,  最后一个字符9要么出现很多次,要么不出现
      String regex = "\\w{2}9*";
      
      String str = "ab";//一次都没有出现
      System.out.println(str.matches(regex));//true
      
      String str1 = "ab9";//出现一次
      System.out.println(str1.matches(regex));//true
      String str2 = "ab99999";//出现5次
      System.out.println(str2.matches(regex));//true
      

      注意: 第三个字符可以没有出现,但是不能被其他字符占位

      • String str3 = "abT";//
        System.out.println(str3.matches(regex));//false
        
    • X+ , X出现一次或者多次

      // 前三个字符为@qq,后面为  .com,要求出现一次或者多次
      String regex = "@qq(.com)+";
      
      String str = "@qq";//一次都没出现
      System.out.println(str.matches(regex));//false
      String str1 = "@qq.com";//出现一次
      System.out.println(str1.matches(regex));//true
      String str2 = "@qq.com.com";//出现2次
      System.out.println(str2.matches(regex));//true
      
    • X{n} X 正好出现 n 次

      // 前2个字符为a-z,后三个字符6要求出现三次
      String regex = "[a-z]{2}6{3}";
      
      String str = "ab666";//一次都没出现
      System.out.println(str.matches(regex));//true
      
    • X{n ,} X 至少出现 n 次

      // whw这三个字符至少出现三次
      String regex = "(whw){3,}";
      
      String str = "whwwhwwhw";
      System.out.println(str.matches(regex));//true
      
    • X{n , m} X 至少出现 n 次,但不超过 m 次

      // cn这两个字符至少出现一次,但不能超过两次
      String regex = "(cn){1,2}";
      
      String str = "cncncn";
      System.out.println(str.matches(regex));//false
      
  • 案例:提取字符串中的手机号码

    String str = "电话1:15342337795;电话2:17876238497;电话3:19815637456";
    
    String regex = "[1][\\d&&[^\\:]]\\d{9}";
    
    //1、根据正则表达式,获取编译对象(正则对象)
    Pattern pattern = Pattern.compile(regex);
    //2、获取匹配器对象
    Matcher matcher = pattern.matcher(str);
    /*
                matcher.find()  查找是否还有匹配的,返回boolean值
                matcher.group()  查找到就把该字符串返回
             */
    while (matcher.find()){
        String group = matcher.group();
        System.out.println(group);
    }
    

7、Arrays数组工具类

  • Arrays数组工具类

    ​ Arrays是专门用于操作数组的工具类,可以对数组进行排序、查找等操作

  • 常用方法:

    方法名说明
    public static String toString(类型 [ ] a)返回数组的内容(字符串形式)
    public static void sort(类型 [ ] a)对数组进行默认升序排序
    public static void sort(类型[ ] a, Comparator<? super T> c)使用比较器对象自定义排序
    public static int binarySearch(int [] a, int key)二分搜索数组中的数据,数组必须有序,存在返回索引,不存在返负插入点 -1
  • 使用比较器对象自定义排序

  • Integer[] arr ={11,58,6,35,88,9,16};
            Arrays.sort(arr,new Comparator<Integer>(){
    
                @Override
                public int compare(Integer o1, Integer o2) {
                    /*
                        升序:  o1 -o2
                        降序    o2 -o1
    
                     */
                    //int num = o1 -o2;
                    int num = o2 -o1;
                    return num;
                }
            });
    
            System.out.println(Arrays.toString(arr));//[88, 58, 35, 16, 11, 9, 6]
    
  • 如果直接return -1 会对数组进行翻转,相当于Collections.reverse(list);, return 0 或 1 不对数组进行操作

    Integer[] arr ={44,11,33,22,55};
    
    //Arrays.sort(arr,(o1, o2) -> -1);
    System.out.println(Arrays.toString(arr));//[55, 22, 33, 11, 44]
    
    //Arrays.sort(arr,(o1, o2) -> 0);
    System.out.println(Arrays.toString(arr));//[44, 11, 33, 22, 55]
    
    Arrays.sort(arr,(o1, o2) -> 1);
    System.out.println(Arrays.toString(arr));//[44, 11, 33, 22, 55]
    
  • Student[] array = new Student[5];
          array[0] = new Student("Ken",18);
          array[1] = new Student("Jhon",18);
          array[2] = new Student("Mike",18);
          array[3] = new Student("Amiy",18);
          array[4] = new Student("Smith",18);
    
          Arrays.sort(array, new Comparator<Student>() {
              @Override
              public int compare(Student o1, Student o2) {
                  //按照年龄进行排序
                  int num = o1.getAge() - o2.getAge();
                  //如果年龄相同,则比较姓名
                  if (num == 0){
                      num = o1.getName().compareTo(o2.getName());
                  }
                  return num;
              }
          });
          System.out.println(Arrays.toString(array));
    

7.1、binarySearch(int [] a, int key)

  • binarySearch(int [] a, int key)方法的使用前提是数组元素有序。
1. 找到的情况下:返回元素在数组的索引
    
    int[] arr = {11,22,33,44,55,66};
    
    int index = binarySearch(arr, 55);   ----> index = 4
    
2. 找不到的情况下,返回负的插入点减 1   
    
    int[] arr = {11,22,33,44,55,66};
    
    int index = binarySearch(arr, 25);   ----> index = -3
    
    分析:
        
        假如查找的元素在数组中,则该数组应该是: {11,22,25,33,44,55,66};
        											
										   0   1  2  3  4  5  6	
    	那么当前的插入点是 : 2
    
    	负的插入点减 1  ------->  -2 - 1  = -3
    
            
    int index = binarySearch(arr, -100);   ----> index = -1     
    
    分析:
        
        假如查找的元素在数组中,则该数组应该是: {100,11,22,33,44,55,66};
        											
										    0   1  2  3  4  5  6	
    	那么当前的插入点是 : 0
    
    	负的插入点减 1  ------->  -0 - 1  = -1       
       
  • 案例-二分法实现数据动态插入
/*
         * 集合、泛型,都不支持基本类型
         * 用基本类型的包装类
         */
ArrayList<Integer> list = new ArrayList<>();
System.out.println("回车继续");
while(true) {
    new Scanner(System.in).nextLine();
    int n = new Random().nextInt(100);
    //二分法查找,在list中找n所在的位置
    //找不到,返回 -(插入点+1)
    int index = Collections.binarySearch(list, n);
    if(index < 0) {
        index = (-index)-1;
    }
    list.add(index, n);
    System.out.println(list.toString());
}
}

day05-Collection、数据结构

  • 集合是存储数据的容器

  • 集合只能存储引用数据类型,数组可以存储基本数据类型,也可以存储引用数据类型

在这里插入图片描述


在这里插入图片描述


在这里插入图片描述


01、单列集合collection接口

  • public interface Collection

  • Collection接口

    • 接口 List implements Collection

      ​ 实现类: ArrayList implements List

      ​ 实现类: LinkedList implements List

    • 接口 Set implements Collection

      ​ 实现类: HashSet implements Set

      ​ 子类: LinkedHashSet extends HashSet implements Set

      ​ 实现类: TreeSet implements Set

      注意:

      • List 集合下的元素是有序的,有索引,元素可以重复
      • Set 集合下的元素是无序的,没有索引,元素可以不能重复
    • Collection接口API中常用的方法,其实现类都可以使用
    1. public boolean add(E e) 把给定的对象添加到当前集合中

    2. public void clear() 清空集合中所有的元素

    3. public boolean remove(E e) 把给定的对象在当前集合中删除

    4. public boolean contains(Object obj) 判断当前集合中是否包含给定的对象

      contains:判断集合中,是否包含传入的对象
      	底层:使用equals()方法,来进行判断
      
    5. public boolean isEmpty() 判断当前集合是否为空

    6. public int size() 返回集合中元素的个数

    7. public Object [ ] toArray() 把集合中的元素,存储到Object数组

    8. <T> T[] toArray(T[] a)
      Integer[] arr = new Integer[0];    
      Integer[] array = list.toArray(arr);  //底层会判断传入的数组的长度,会自动扩容,建议new Integer[list.size()]
      
    9. JDK8以后, public boolean removeIf(参数),根据条件删除,参数是一个函数式编程接口

  • 集合的遍历方式

    • 遍历方式一

      • Collection 使用迭代器遍历

        1. Collection 获取迭代器的方法

          • Iterator <T>  iterator()   //  返回集合中迭代器对象,该迭代器对象默认指向当前集合的 0 索引
            
          • boolean hasNext(); 询问当前位置是否有元素存在,存在返回true,不存在返回false

          • E next(); 获取当前位置的元素,并同时将迭代器对象移向下一个位置,注意防止取出越界

        举例:

         ArrayList <String> list = new ArrayList<>();
                list.add("hello");
                list.add("world");
                list.add("java");
                Iterator it = list.iterator();
                
                while (it.hasNext()){
                    System.out.println(it.next());
                }
        
    • 迭代器,如果已经遍历完毕,再想遍历的话,需要重新获取迭代器。

    • 遍历方式二

      • 增强 for,本质上是迭代器

        增强for既能遍历集合、也能遍历数组

        for(元素数据类型 变量名 :  数组/集合) {
                 ....
        }
        
        
        Collection<String> list = new ArrayList<>();
        for(String ele : list) {
            System.out.println(ele);
        }
        
        
    • 并发修改异常(补充)

      1.集合每次添加或者删除元素时,底层都会记录一次修改的次数modCount

      2.在创建迭代器对象后使用expectedModCount记录已经被修改的次数

      3.如果获取元素时发现expectedModCount和modCount值不一致,就认为集合被修改过,此时会触发 ConcurrentModificationException

      while (it.hasNext()){
                  String s =(String) it.next();
                  if (s.equals("hello")){
                      //ConcurrentModificationException  并发修改异常
                      list.remove(s);//在使用迭代器或者增强for遍历集合,不允许集合本身对元素进行增删
                  }
      
              }
      
    • 如何避免并发修改异常呢?

      • 使用迭代器遍历集合,但是用迭代器自己的方法删除元素不会存在这个问题。

        while (it.hasNext()){
                    String s =(String) it.next();
                    if (s.equals("hello")){
                        //使用迭代器删除
                        it.remove();
                    }
        
                }
        
      • 使用JDK提供的removeIf(函数式接口)方法。

        list.removeIf(new Predicate<String>() {
                    @Override
                    public boolean test(String s) {
                        return s.equals("hello");
                    }
                });
        
        list.removeIf(s -> s.equals("hello"));//使用Lambda表达式
        
      • 新问题:迭代器中,删除的方法,但是普通的迭代器没有添加的方法

        • 使用List集合中,特有的迭代器,ListIterator

        • ArrayList<String> list = new ArrayList<>();
          
                  list.add("java");
                  list.add("sql");
                  list.add("python");
                  list.add("c++");
                  ListIterator<String> it = list.listIterator();
                  while (it.hasNext()){
                      String s = it.next();
                      if ("sql".equals(s)){
                          it.add("PHP");
                          //it.set("HTML");
                      }
                  }
                  System.out.println(list);//[java, sql, PHP, python, c++]
          
  • 当删除的元素是倒数第二个的时候,不会触发并发修改异常【源码的bug】

    在这里插入图片描述

    ArrayList<String> list = new ArrayList<>();
          list.add("java");
          list.add("sql");
          list.add("python");
          list.add("c++");
          Iterator<String> it = list.iterator();
          while (it.hasNext()){
              String s = it.next();
              if ("python".equals(s)){
                  list.remove(s);
              }
          }
          System.out.println(list);//[java, sql, c++]
    
    • 使用forEach()遍历

      • default void forEach(Consumer<? super T> action):

        • list.forEach(new Consumer<String>() {
                      @Override
                      public void accept(String s) {
                          System.out.println(s);
                      }
                  });
          
        • list.forEach(s -> System.out.println(s));
          

02、遍历总结

  1. 普通的for循环

    ​ 能遍历数组,Collection中List集合,必须有索引

  2. 迭代器

    Collection集合中的所有实现类和子类

  3. 增强for

    Collection集合中的所有实现类和子类,和数组

    ※※※ 迭代器和增强for需要注意并发修改异常

  4. forEach(函数式编程接口)

    可以遍历所有的Collection


03、数据结构(特点)

​ 数据结构指的是,数据组织的方式。不同的组织方式会有不同的特点

  1. 栈结构

    先进后出

  2. 队列结构

    先进先出

  3. 数组结构

    ​ 数组是一种查询快,增删慢的模型

    查询速度快

    删除效率低

    添加效率低

  4. 链表结构

    ​ 链表中的元素是在内存中不连续存储的,每个元素节点包含数据值和下一个元素的地址。

    链表特点:

    ​ 查询效率低

    ​ 增删效率相对较高

    ​ 单链表:

    ​ 从左找到右,是单方向的,所以称之为单向链表

    ​ 双向链表:

    ​ 查询速度比单向链表快,可以从前往后找,也可以从后往前找

  5. 二叉树、二叉查找树、平衡二叉树

    ​ 每一个节点最多只有两个子节点

    • 在这里插入图片描述

    • 二叉查找树

      在这里插入图片描述

    • 二叉树查找树添节点

      ​ 大的存右边,右子树

      ​ 小的存左边, 左子树

      ​ 一样的不存

    • 二叉查找树可能存在的问题?

      极端情况,左子树可能只有一个或者没有子节点,而右子树很高,导致查询的性能与单链表一样,查询速度变慢!

    • 平衡二叉树

      平衡二叉树是在满足查找二叉树的大小规则下,让树尽可能矮小,以此提高查数据的性能。

      平衡二叉树的要求:

      ​ 任意节点的左右两个子树的高度差不超过1,任意节点的左右两个子树都是一颗平衡二叉树

      • 在这里插入图片描述

5.3.1、平衡二叉添加元素

  • 平衡二叉树添加元素后可能导致不平衡

    • 基本策略是进行左旋,或者右旋保证平衡
  • 左旋:

    • 将根节点的右侧往左拉,原先的右子节点变成新的父节点,并把多余的左子节点出让,给已经降级的根节点当右子节点
  • 右旋

    • 将根节点的左侧往右拉,原先左子节点变成新的父节点,并把多余的右子节点出让,给已经降价的根节点当左子节点
  • 平衡二叉树添加元素旋转的4中情况

  1. 左左

    • 【左子树的左子树】添加新的元素导致不平衡
      • 整体做一个右旋
      • 在这里插入图片描述
  2. 左右

    • 【左子树的右子树】插入新节点导致不平衡
      • 先对左子树整体左一个左旋

      • 在这里插入图片描述

      • 再对整体做一个右旋

      • 在这里插入图片描述

  3. 右右

    • 【右子树的右子树】插入新节点

      • 整体做一个左旋

      • 在这里插入图片描述

  4. 右左

    • 【右子树的左子树】插入新节点
      • 先对右子树整体做一个右旋

      • 在这里插入图片描述

      • 再对整体做一个左旋

      • 在这里插入图片描述


  • 案例,现有一个平衡二叉树如下:
    • 在这里插入图片描述

    • 左左:【左子树的左子树】插入新节点 9

      • 在这里插入图片描述
    • 左右:【左子树的右子树】插入新节点 27

      • 在这里插入图片描述
    • 右右:【右子树的右子树】插入新节点 200

      • 在这里插入图片描述
    • 右左:【右子树的左子树】插入新节点 55 ,刚好平衡

      • 在这里插入图片描述
  1. 红黑树(红黑树增删改查的性能都很好

    ​ 红黑树是一种自平衡的二叉查找树,是计算机科学中用到的一种数据结构。

    ​ 1972年出现,当时被称之为平衡二叉B树。1978年被修改为如今的"红黑树"。

    ​ 每一个节点可以是红或者黑;红黑树不是通过高度平衡的,它的平衡是通过“红黑规则”进行实现的。

    • 红黑规则

      • 每一个节点或是红色的,或者是黑色的,根节点必须是黑色
      • 如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶节点,每个叶节点(Nil)是黑色的;
      • 如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连的情况)
      • 对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色
    • 在这里插入图片描述


  • 总结:

    • 队列:先进先出,后进后出。

    • 栈:后进先出,先进后出。

    • 数组:内存连续区域,查询快,增删慢。

    • 链表:元素是游离的,查询慢,首尾操作极快。

    • 二叉树:永远只有一个根节点, 每个结点不超过2个子节点的树。

    • 查找二叉树:小的左边,大的右边,但是可能树很高,查询性能变差

    • 平衡查找二叉树:让树的高度差不大于1,增删改查都提高了。

    • 红黑树(就是基于红黑规则实现了自平衡的排序二叉树)


04、List

  • ArrayList和LinkedList是List的实现类,通用List的add、remove、set、get方法(增删改查)。

  • List系列集合通用方法

    boolean add(E e); 在此集合的末尾添追加元素
    
    void add(int index,E element);   在此集合中的指定位置插入指定的元素
    
    E remove(int index);   删除指定索引处的元素,返回被删除的元素
    
    E set(int index,E element);  修改指定索引处的元素,返回被修改的元素
    
    E get(int index);  返回指定索引处的元素
    

1、ArrayList

  • ArrayList底层是什么结构?

    • 数组结构:查询快、增删慢
    /**
         *  往ArrayList集合中一次添加”孙悟空“、”猪八戒“、”沙和尚“、”小白龙“、”小白龙“、
         *
         *  将最后一个元素修改为”唐僧“,再将最后一个元素置顶
    */
    ArrayList<String> list = new ArrayList<>();
            list.add("孙悟空");
            list.add("猪八戒");
            list.add("沙和尚");
            list.add("小白龙");
            list.add("小白龙");
            //1、将最后一个元素修改为”唐僧“
            list.set(list.size() - 1, "唐僧");
            //2、把最后一个元素插入到 0 索引处
            list.add(0,list.get(list.size() - 1));
            //3、把最后一个元素删除
            list.remove(list.size() - 1);
    
  • ArrayList会自动创建一个长度为10的数组,当存满的时候,会再继续创建一个新的数组,新数组的长度是旧数组的 1.5 倍

    ​ 把旧数组的内容拷贝到新数组中,旧数组会被回收。

2、LinkedList (双链表)

  • LinkedList的底层原理是一个双向链表,一个元素对应一个节点

    原理:每次添加元素其实就是链表的一个节点,一次往链表的后面拼接,相对于List集合而言,多了一些针对头和尾操作的方法。

public void addFirst(E e);  在该列表开头插入指定的元素

public void addLast(E e); 将指定的元素追加到此列表的末尾
E getFirst();
E getLast();
E removeFirst();
E removeLast();
  • LinkedList list = new LinkedList();

    • list.get(1), 表面是拿着索引去找元素,底层还是去遍历一个一个节点的访问

      • 底层原码

        Node<E> node(int index) {
            // assert isElementIndex(index);
            
        	//当前索引和长度的一半进行比较
            if (index < (size >> 1)) { //当前索引离头结点近,就从头结点处开始遍历
                Node<E> x = first;
                for (int i = 0; i < index; i++)
                    x = x.next;
                return x;
            } else {//当前索引离尾结点比较近,就从尾结点开始遍历
                Node<E> x = last;
                for (int i = size - 1; i > index; i--)
                    x = x.prev;
                return x;
            }
        }
        

05、Set

  • 哈希值(hashCode方法)

    ​ hashCode()方法的含义,获取一个哈希值(与地址值相关的整数)

    ​ 在Object类中有一个hashCode()方法,任何一个对象都可以调用hashCode()方法获取哈希值

    ​ 每一个对象都有不同的地址值,地址值不同那么哈希值就不同

    如果子类复写了hashCode方法,哈希值就和属性值相关,如果属性值一样那么哈希值就一样

    //Student类复写了hashCode()方法,如果两个对象的属性值一样,那么hashCode()就一样
    System.out.println(stu1.hashCode());//24021582
    System.out.println(stu2.hashCode());//24021582
    

1、HashSet

  • HashSet底层原理

​ HashSet底层是:数组+链表组成的(哈希表结构)

  • 特点:可以对元素进行去重

创建一个默认长度16的数组,数组名table
    
根据元素的哈希值跟数组的长度求余计算出应存入的位置(哈希算法)
    
判断当前位置是否为null
    
- 如果是null,说明这个位置没有元素,可以直接存入
    
- 如果不为null,说明这个位置有元素,需要进一步使用equals判断
    
如果equals判断不同,则以链表的形式插入元素;
    
- JDK 7新元素占老元素位置,指向老元素(头插法)
    
- JDK 8中新元素挂在老元素下面(尾插法)
    
- 如果equals判断相同,认为是同一个元素(不存)

去重原理:
    1、当调用集合的添加方法时,首先会调用该对象的hashCode()方法,计算出一个哈希值
    2、拿着这个哈希值,去数组中查找,是否有相同的
    	--没有相同的 : 直接存入
    	--有相同的 : 进行equals判断
    3、调用该对象的equals方法,比较对象的内容
    	--false : 代表哈希值虽然相同,但是内容不同  --> 存入
        --true : 代表哈希值相同,内容也相同  ---> 不存
效率:
    --如果hashCode()方法的返回值是固定的,将会调用很多次equals()方法       
  • JDK8版本后HashSet底层是:数组+链表+红黑树组成的(哈希表结构)

    当链表的长度超过 8 的时候,就会把链表转为红黑树

    在这里插入图片描述

2、LinkedHashSet

  • 原理:底层数据结构是依然哈希表,只是每个元素又额外的多了一个双链表的机制记录存储的顺序。

  • 有序、不重复、无索引。

3、TreeSet

  • TreeSet可以对元素进行自动排序,底层基于红黑树实现

  • 必须指定排序规则

    • 如果没有指定排序规则,就会报错

      ClassCastException: class com.itheima.d3_Set.Student cannot be cast to class java.lang.Comparable
      

    第一种:让元素实现Comparable接口,默认排序

    • 实现Comparable接口

      public class Student implements Comparable<Student>
      
    • 复写CompartTo方法

       @Override
          public int compareTo(Student o) {
              //TreeSet集合底层会根据CompareTo方法的返回值是整数、负数、0来决定,
      
              // 将元素存储在二叉树的左边,还是右边,0就不存
              /*
                      this  :将要添加到集合中的元素
                      o :集合中已有的元素
               */
              return this.getAge() - o.getAge();//按照年龄升序排序
          }
      
    • Test类测试
    Student stu1 = new Student("张三",23);
          Student stu2 = new Student("张三",23);//重复,不会存到TreeSet集合中
          Student stu3 = new Student("李四",24);
          Student stu4 = new Student("王五",25);
          TreeSet<Student> set = new TreeSet<>();
          set.add(stu1);
          set.add(stu2);
          set.add(stu3);
          set.add(stu4);
          for (Student student : set) {
              System.out.println(student.getName() +"....." + student.getAge());
          }
    

    第二种:使用比较器Comparator接口,比较器排序

    ​ 如果元素是自定义类型、或者元素本身具备模式排序规则,但是默认排序规则并不是自己想要的,就可以使用比较器自定义排序

    • 创建TreeSet集合的时候,就给TreeSet构造方法中传入一个Comparator比较器
    TreeSet<Student> set = new TreeSet<>(new Comparator<Student>() {
                @Override
                public int compare(Student o1, Student o2) {
                    return o2.getAge() - o1.getAge();
                }
            });
    
  • TreeSet集合的特点是怎么样的?

    可排序、不重复、无索引

    ​ 底层基于红黑树实现排序,增删改查性能较好

  • TreeSet集合指定排序规则有几种方式

    默认排序规则Comparable(源码)

    集合自定义Comparator比较器对象,重写比较规则

同时指定了自然排序,还指定了比较器排序,优先按照比较器排序

day06_Map、泛型、Collections类

01、泛型

  • 泛型只在编译阶段有效,在运行阶段就会被擦除。


  • 概念

    • 什么是泛型?

    泛型允许程序员在编写代码时使用一些以后才能确定的数据类型

    定义泛型:<字母> 如:、

    在创建对象的时候才指定明确的数据类型

    ArrayList<String> list = new ArrayList<>();//list 集合只能存储String
    
    ArrayList<Student> stu = new ArrayList<>();//stu集合只能存储学生对象
    
    • 泛型的好处:
      • 在编译时期起到限定数据类型的作用
      • 将运行期的错误,提升到了编译期,避免后期进行强制类型转换
  • 自定义泛型类

    • 泛型类的格式:class 类名<泛型变量>{ }

      自定义一个MyArrayList类,简单模拟ArrayList集合

      ​ 1、类上的泛型是什么含义?

      ​ 在类中有不确定的数据类型,使用表示

      ​ 2、类上的泛型什么时候确定?

      ​ 创建该类对象确定。

    • public class MyArrayList <T>{
      
          private Object[] array = new Object[10];
          private int index;
      
          public void add(T t){
              array[index] = t;
              index++;
          }
      
          public T get(int index){
              return (T)array[index];
          }
      
          public T set(int index,T t){
              Object obj = array[index];
              array[index] = t;
              return (T)obj;
          }
      
          @Override
          public String toString() {
              return Arrays.toString(array);
          }
      }
      
  • 自定义泛型方法

    格式: public 返回值类型 方法名(T t){…}

    //需求:写一个工具方法,可以往Collection系列的任意集合中添加任意类型的三元素,并把集合返回。
    
    public static <T>Collection<T> addElement(Collection<T> coll,T t1,T t2,T t3){
         coll.add(t1);
         coll.add(t2);
         coll.add(t3);
         return coll;
     }
    
  • 自定义泛型接口

    • 格式: public interface 接口名{}

      ​ 泛型接口的作用:

      ​ 可以让实现类选择当前功能需要操作的数据类型

      public interface Dao <T>{
          //添加一个 T 类型对象
          void add(T t);
          //获取一个 T 类型对象
          T get(String id);
      }
      
    • 1、实现接口

      // 实现泛型接口,指定具体的数据类型为 Student
      public class Demo3Dao implements Dao<Student> {
          @Override
          public void add(Student student) {
      
          }
      
          @Override
          public Student get(String id) {
              return null;
          }
      }
      
    • 2、实现接口的时候,不指定具体类型,将泛型传递到自己的类当中

      class A<T> implements Dao<T>{
      
          @Override
          public void add(T t) {
      
          }
      
          @Override
          public T get(String id) {
              return null;
          }
      }
      
  • 泛型通配符

    • 泛型通配符的作用

      • 泛型通配符一般用在方法的参数位置,限定参数的数据类型

      • <?>表示任意类型
         public static void main(String[] args) {
              	//String 字符串类型
                ArrayList<String> list1 = new ArrayList<>();
             	//Integer类型
                ArrayList<Integer> list2 = new ArrayList<>();
                method(list1);
                method(list2);
         }
          public static void method(ArrayList<?> list){ }
        
      • <? extends 指定类型> 指定类型或者指定类型的子类型
        public static void main(String[] args) {
        
                ArrayList<String> list1 = new ArrayList<>();
                ArrayList<Integer> list2 = new ArrayList<>();
                //method(list1);报错
                method(list2);//class Integer extends Number,
        }
            public static void method(ArrayList<? extends Number> list){ }
        
      • <? super 指定类型> 指定类型或者指定类型的父类
        public static void main(String[] args) {
        
                ArrayList<String> list1 = new ArrayList<>();
                ArrayList<Integer> list2 = new ArrayList<>();
                //method(list1);报错
                //method(list2);报错
                ArrayList<Object> list3 = new ArrayList<>();
                method(list3);//Number extends Object
        }
            public static void method(ArrayList<? super Number> list){ }
        

02、可变参数

  • 什么是可变参数?

    需求1:计算两个整数的和
    public static void getSum1(int a ,int b){
     return a + b;
    }    
    需求2:计算三个整数的和
    public static void getSum2(int a ,int b, int c){
     return a + b +c;
    }  
    需求:计算 n 个整数的和?
    	由于需要动态传入参数,参数的个数是不确定的,就就可以使用动态参数,接受不定的参数个数
    public static void getSum2(int. . .arr){
     int sum = 0;
     for(int i = 0; i < arr.length; i++){
         sum += arr[i];
     }
     return sum;
    }     
    
    • 可变参数(int . . . arr)本质上是一个数组

      int[] array = {1,2,3};
      
      int sum = getSum2(array);//可直接传入一个数组对象
      
      public static void getSum2(int. . .arr){
          int sum = 0;
          for(int i = 0; i < arr.length; i++){
              sum += arr[i];
          }
          return sum;
      }  
      
  • 当除了可变参数还有其他参数的时候,可变参数一定要放在参数列表中的最后一个

    public static void getSum3(String str,int. . .arr){...}
    

03、Collections类

  • Collection是所有单列集合的跟接口,而Collections是对集合进行操作的工具类

  • Collections类的常用方法

    1. 给集合对象批量添加元素
    public static <T> boolean addAll(Collection<? super T> coll, T . . . t);  
    
    ArrayList<String> list = new ArrayList<>();
    
    Collections.addAll(list,"world","java","hello","abc");
    
    1. 打乱List集合元素的顺序
    public static void shuffle(List<?> list);
    
    ArrayList<String> list = new ArrayList<>();
    
    Collections.shuffle(list);
    
    1. 将集合中元素按照默认规则排序
    public static <T> void sort(List<?> list);
    
    ArrayList<String> list = new ArrayList<>();
    
    Collections.sort(list);
    
    1. 将集合中元素按照指定规则排序
    public static <T> void sort(List<T> list, Comparator<? super T> c);
    
    ArrayList<String> list = new ArrayList<>();
    
         Collections.addAll(list,"world","java","hello","abc");
    
         Collections.sort(list, new Comparator<String>() {
             @Override
             public int compare(String o1, String o2) {
                 /**
                     * String 的compareTo()方法
                     * 按照字典顺序排序,如果字符串小于参数字符串,返回负数,反之则返回正数
                     */
                    return o1.compareTo(o2);
           		 }
    		});
    

04、Map

  • Map是双列集合,双列集合的元素是成对出现的,每一个元素都包含一个键和值
  • Map的体系结构
    • 在这里插入图片描述
  1. HashMap的键是无序的
  2. LinkedHashMap的键是有序的
  3. TreeMap的键是可以进行排序
  • 键不能重复,值可以重复

  • Map集合常用API

    • Map<String,String> map = new HashMap<>();
      
    1. V put(K key, V value) 设置键值对

      map.put("贾乃亮","李小璐");
      map.put("贾乃亮","李小龙");//新值会替换旧值,但是键不变,只是值改变了
      
    2. V remove(Object key) 根据键删除键值对元素

      map.remove("贾乃亮"); //返回被移除的键所对应的值
      
    3. void clear() 移除所有键值对元素

      map.clear()
      
    4. boolean containsKey(Object obj) 判断集合是否包含指定键

      boolean obj2 = map.containsKey("贾乃亮");
      
    5. boolean containsValue(Object value) 判断集合是否包含指定的值

      boolean obj3 = map.containsValue("白百合");
      
    6. boolean isEmpty() 判断集合是否为空

      boolean bl = map.isEmpty();
      
    7. int size() 集合的长度,也就是集合中键值对的个数

      int size = map.size();
      

05、遍历Map集合

1、遍历方式一

  • 键找值,需要用到的方法: map.keySet()、map.get()

    HashMap<String,String> map = new HashMap<>();
    map.put("贾乃亮", "李小璐");
    map.put("王宝强","马蓉");
    map.put("羽泉","白百合");
    
    • 获取map集合中所有的键 map.keySet();

      Set<String> key = map.keySet();//Set集合保证元素不重复,而map中的键是不重复的
      
    • 通过键匹配值 String value = map.get(s);

       for (String s : key) {
           String value = map.get(s);
           System.out.println(s + "....." + value);
       }
      

2、直接获取【键值对】

  • 调用Map提供的entrySet()方法,获取所有的【键值对】组成的Set集合

    将键值对封装到Map.Entry类

    Set<Map.Entry<String, String>> entries = map.entrySet();
    
  • 遍历Set集合,然后提取【键值对】中的键和值

    for (Map.Entry<String, String> entry : entries) {
        //K   getKey()    获得键
        String key = entry.getKey();
        //V   getValue()  获得值
        String value = entry.getValue();
        System.out.println(key + "... " + value);
    }
    
  • Set<Map.entry<K,V>> entrySet() 获取所有键值对对象的集合

  • K getKey() 获得键

  • V getValue() 获得值

3、forEach()方法

  • JDK8提供的一个新的方法 forEach,结合Lamdba表达式使用更加方便
default void forEach(BiConsumer<? super k,? super V> action);   结合Lambda遍历Map集合
//使用forEach遍历集合
     map.forEach(new BiConsumer<String, String>() {
         @Override
         public void accept(String key, String value) {
             System.out.println(key + "..." + value);
    }
});
//使用Lambda遍历集合
map.forEach((key, value) -> System.out.println(key + "..." + value));

06、HashMap

  • HashSet的底层就是HashMap,HashSet底层原理和HashMap是一模一样的【哈希表结构】

  • HashSet可以保证元素的唯一性,HashMap可以保证键的唯一性,通过重写,equals()和hashCode()方法,可以保证唯一性

07、LinkedHashMap

  • LinkedHashSet的底层原理和LinkedHashMap 是一样的,比他们的父类多了一个双向链表可以保证元素的存储顺序。

08、TreeMap

  • TreeSet和TreeMap的底层原理是一样,都是红黑树
    • TreeSet可以对元素进行排序
    • TreeMap可以对键进行排序
    • 同时,都要指定排序规则。

day07_不可变集合,Stream,异常体系

01、不可变集合

  • 什么是不可变集合?

    • 在创建集合时就已经添加好了元素,之后不能添加、不能删除、不能修改
  • 获取不可变集合的方法

    • 这些方法是JDK9之后才有的,List、Set、Map都可以获取不可变集合

    • 方法名称说明
      static List of(E…elements)创建一个具有指定元素的List集合对象
      static Set of(E…elements)创建一个具有指定元素的Set集合对象
      static <K , V> Map<K,V> of(E…elements)创建一个具有指定元素的Map集合对象
       public static void main(String[] args) {
              List<String> list = List.of("张三", "李四", "王五");
              //list.add("赵六"); //UnsupportedOperationException
              //list.remove(1);//UnsupportedOperationException
              //list.set(0,"小二");//UnsupportedOperationException
              System.out.println(list);
      
              Set<Integer> set = Set.of(100,200, 300, 400);
              System.out.println(set);
      
              Map<String, Integer> map = Map.of("张三", 20, "李四", 24, "王五", 25);
              System.out.println(map);
          }
      

02、Stream

1、概述

  • 什么是Stream?

    Stream流指的是流式编程,用于简化集合和数组的操作

    • 需求:把存储姓名的集合中,提取姓氏是 “ 张 ”,名字是三个字的姓名
      • 1、把姓名以“张” 开头的名字筛选出来,str . startsWith(" 张 ")
      • 2、在1、的基础上,把姓名是三个字的提取出来,str.lenth() == 3
    • 使用流式编程
      • list.stream().filter(s - > s.startsWith(" 张 ")).filter(s -> s.length() == 3)
  • Stream流式思想的核心

    1. 先得到集合或者数组的Stream流(传送带)
    2. 然后用Stream流提供方法对流中的数据进行操作(加工的环节)
  • Stream流的作用是什么?结合了什么技术?

    • 简化集合、数组操作的API。结合了Lambda表达式

2、获取Stream流

  1. 数组、获取Stream流

    public static<T> Stream<T> of(T... values)   获取当前数组/可变数据的Stream

    举例:

    Stream<Integer> s1 = Stream.of(12, 54, 36, 55);
    
    Integer[] arr = {1,2,3,4,5,6};//数组的声明需要使用引用类型
    Stream<Integer> s1 = Stream.of(arr);
    //数组声明使用基本数据类型,错误示范:
    int[] arr = {1,2,3,4,5,6};
    Stream<int[]> s1 = Stream.of(arr);//Stream<int[]>  int[]类型是错误的泛型
    
  2. 集合、获取Stream流

    default  Stream<E> stream()      获取当前集合对象的Stream
    • 单列集合

      List<String> list = List.of("张三", "李四", "王五");
      
      //list获取Stream流
      Stream<String> stream1 = list.stream();
      
    • 双列集合转为单列集合,再获取Stream流

      Map<String, Integer> map = Map.of("张三", 23, "李四", 24, "王五", 25);
      
              //获取键
      Set<String> keys = map.keySet();
              //键的Stream流
      Stream<String> stream1 = keys.stream();
              //获取值
      Collection<Integer> values = map.values();
              //值的Stream流
      Stream<Integer> stream2 = values.stream();
      
              //获取键值对
      Set<Map.Entry<String, Integer>> entries = map.entrySet();
              //键值对的Stream流
      Stream<Map.Entry<String, Integer>> stream3 = entries.stream();
      

3、Stream流的常用API( 中间操作方法 )

Stream<T> filter(Predicate<? super T > predicate)  用于对流中的数据进行过滤
Stream<T> limit(long maxSize)  获取前几个元素
Stream<T> skip(long n)  跳过前几个元素
Stream <T> distinct()   去除流中重复的元素。依赖(hashCode和equals方法)
Static <T> Stream <T> concat(Stream s1,Stream s2)  合并s1和s2两个流为一个流
Stream<T> sorted()  对流中的元素进行排序,自己指定排序规则
Stream<T> mapToXxx()  把流中的元素转换为任意类型  比如:mapToDouble()
Stream<T> map()  把流中的元素转换为任意类型
  • 举例:
List<String> list1 = List.of("张三", "张无忌", "小龙女", "周伯通", "杨过", "金轮法王", "一修大师");

List<String> list2 = List.of("李逵", "宋公明", "高俅", "林冲", "小龙女", "武松", "张无忌");

Stream<String> stream1 = list1.stream();
Stream<String> stream2 = list2.stream();
//合并两个Stream流
Stream<String> stream3 = Stream.concat(stream1, stream2);
//   对流中的数据进行过滤     名字三个字、  去重、     截取前3个, 跳过第一个
stream3.filter(s -> s.length() == 3).distinct().limit(3).skip(1).forEach(s -> 		                                                                                      System.out.println(s));

4、Stream流的常见终结的操作方法

Optional<T> max(Comparator<? super T> comparator)  返回最大元本流根据提供的 Comparator
/*
     stream().max()筛选出最大
     返回值:
          Optional<Employee>是一个封装了Employee对象的对象
*/
Optional<Employee> max1 = list1.stream().max((o1, o2) -> o1.getMoney() - o2.getMoney() >= 0 ? 1 : -1);
/*
      max.get();
      返回值:返回被这个对象封装的Employee对象
*/
Employee employee1 = max1.get();
void forEach(Consumer action)  对此流的每个元素指向遍历操作
long count()  返回此流中的元素个数
  • 注意:

    1. Stream流调用完方法之后其结果还是一个Stream流,可以继续调用Stream的方法,支持链式编程。

    2. 在Stream流中无法直接修改集合、数组中的数据,数组或是集合中的数据没有任何改变

    3. 终结操作方法,调用完成之后,就无法继续使用了,原因是不会返回Stream流了。

      stream3.filter(s -> s.length() == 3).distinct().limit(3).skip(1).forEach(s -> 	    System.out.println(s));
      
      stream3.forEach(s -> System.out.println(s));//运行报错   IllegalStateException
      

    终结和非终结方法的含义是什么?

    • 终结方法后流不可以继续使用
    • 非终结方法会返回新的Stream流,支持链式编程
  • 案例

    需求:某个公司的开发部门,分为开发一部和二部,现在需要进行年中数据结算,
     要求如下:
     :每个员工信息至少包含了(姓名、性别、工资、奖金、处罚记录)
     :开发一部有4个员工、开发二部有5名员工
     :分别筛选出2个部门的最高收入的员工信息,封装成优秀员工对象Topperformer
               优秀员工包含:姓名和实际收入两个属性
     ④:分别统计出2个部门的平均月收入,要求去掉最高收入和最低收入。
     ⑤:统计2个开发部门整体的平均月收入,要求去掉最高收入和最低收入。
    

    部分代码

    //开发部门二的平均月收入,要求去掉最高收入和最低收入。
    
    /*
    	sorted(比较器)    指定排序规则 按照工资从小到大进行排序,返回值是  Stream<Employee>
    	limit(list2.size() -1)   截取到倒数第二个 ,即去掉最后一个最高工资
    	skip(1)  跳过第一最低工资
    	mapToDouble  把流中的元素从员工类转换为一个DoubleStream类型
    	average() 求流中的平均值,返回值是一个OptionanlDouble
    	getAsDouble(); 获取OptionanlDouble封装的Double值,返回基本数据类型double
    
    */
    double avg2 = list2.stream().sorted((o1, o2) -> o1.getMoney() - o2.getMoney() >= 0 ? 1 : -1)
     .limit(list2.size() -1)
     .skip(1)
     .mapToDouble(value -> value.getMoney())
     .average()
     .getAsDouble();
    System.out.println("开发部门二的平均月收入" + avg2);
    

5、收集Stream流

  • Stream流的收集方法,可以把流中的元素再收集到集合中

    R collect(Collector collector)   开始收集Stream流,指定收集器
    
  • Collectors工具类提供了具体的收集方式

    public static <T> Collector toLlist()   把元素收集到List 集合中
    
    • 举例

      List<Integer> list1 = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9);
      
      List<Integer> collect1 = list1.stream().filter(s -> s % 2 == 0).collect(Collectors.toList());
      System.out.println(collect1);//[2, 4, 6, 8]
      
    public static <T> Collector toSet()   把元素收集到Set集合中
    
    • 举例

      List<Integer> list1 = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9);
      
      Set<Integer> collect1 = list1.stream().filter(s -> s % 2 == 1).collect(Collectors.toSet());
      System.out.println(collect1);//[1, 3, 5, 7, 9]
      
    public static Collector toMap(Function keyMapper,Function valueMapper)  把元素收集到Map 集合中
    
    • 举例

      ArrayList<Employee> list1 = new ArrayList<>();
      //开发部门一、
      list1.add(new Employee("孙悟空","男",20000,5000,"大闹天宫"));
      list1.add(new Employee("猪八戒","男",15000,3000,"调戏良家少女"));
      list1.add(new Employee("沙和尚","男",16000,4000,"打碎琉璃盏"));
      list1.add(new Employee("小白龙","男",10000,2000,"吃了白马"));
      //把开发部中,员工的工资  > 15000的员工提取到一个新集合,只需要姓名和工资
      
      Map<String, Double> map = list1.stream().filter(s -> s.getSalary() > 15000).collect(Collectors.toMap(
      
          //把员工的姓名当做map集合中的键
          name -> name.getName(),
      
          //把员工的工资当做map集合中的值
          salary -> salary.getSalary()
      ));
      
      System.out.println(map);//{沙和尚=16000.0, 孙悟空=20000.0}
      

    • 把元素收集到数组中
    public static <T> Collector toArray()   把元素收集到数组中
    
    • 举例

      List<Integer> list = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9);
      //toArray()返回的是 Object的数组
      Object[] objects = list.stream().filter(s -> s % 2 == 0).toArray();
      
      List<Integer> list = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9);
      
      /*
      	new Integer[value]  把流中的元素返回到Integer数组中
      	value 表示剩余元素个数,  创建一个 Integer 数组,长度为value,
      */
      Integer[] array = list.stream().filter(s -> s % 2 == 0).toArray(value -> new Integer[value]);
      
      System.out.println(Arrays.toString(array)); //[2, 4, 6, 8]
      

03、异常体系

  • 什么是异常?

    1. 异常是程序在编译或者执行的过程可能出现的问题
    2. Java语言的设计者为了让开发者知道异常产生的原因,把异常设计成了类
  • 学习异常的目的

  • 避免异常的出现,同时处理可能出现的异常,让代码更稳健

  • 异常的体系

    在这里插入图片描述

    常见的运行时异常

    • 数组索引越界异常   ArrayIndexOutOfBoundsException
      
    • 空指针异常    NullPointerException
      
    • 数学操作异常   ArithmeticException   举例:【除数为 0
    • 类型转换异常   ClassCastException
      原因:在多态的情况下,父类 、接口转换为子类,类型转错了
      【原本是什么类型,才可以转换为什么类型】
      
    • 数字转换异常   NumberFormatException
      原因:在调用方法时,参数的数据格式有问题  
      举例:
          int num = Integer.parseInt("12abc3");
      
  • 编译时异常

    • 不管代码真的有问题,在编译阶段就会报错,目的是提醒程序员不要出错
    • 特点:
      • 继承Exception的异常或者其子类
      • 编译阶段报错,必须处理,否则代码不通过

    在这里插入图片描述

  • 如何产生异常

    • 在写一个方法时,考虑到调用者可能传递非法数据,需要对调用者传递过来的数据进行合法性的校验,如果数据校验不合法就可以通过throw new产生一个异常对象,并抛给调用者,让调用者知道为什么错了。
  • 注意:

    • 如果方法中产生的是运行时异常:不需要在方法上声明

    • 如果方法中产生的是编译时异常:需要在方法上用throws声明

      在这里插入图片描述

  • 遇到有异常的方法如何处理?

    1. 如果遇到的是运行时异常,一般不需要处理,直接改代码就行;如果改代码解决不了,也能使用try…catch处理

    2. 如果遇到编译时异常,可以继续使用throws声明,抛给下一个调用者处理,也可以使用try…catch捕获异常

  • 自定义异常类

    • 如果Java的API中提供的异常类,不足以描述你的问题;那么你就可以自定义异常类
    • 只需要让类继承Exception或者RuntimeException即可
--------------------榆木脑袋,炼气期二层--------------------2024-03-26 0:1345
  • 18
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值