JAVA 基础

1 .1 基础语法

1.1.1 数据类型

1.1.2 流程控制

顺序结构:同一方法中,从上往下,顺序执行

分支结构

分支语句练习
import java.util.Scanner;
public class inputClass {
  public static void main(String[] args) {
    // 键盘录入一个星期数,用一个变量接收
    Scanner sc = new Scanner(System.in);
    System.out.println("请输入一个星期数");
    int week = sc.nextInt(); // 获取控制多台键盘输入值
    // 对星期数进尽心判断
    if (week < 1 || week > 7) {
      System.out.println("输入有误");
    } else if (week == 1) {
      System.out.println("星期二一鸡腿饭");
    } else if (week == 2) {
      System.out.println("星期二排骨汤和红烧肉");
    }
  }
}

循环结构

跳转、中断

1 .2 数组

数组介绍:数组可以存放多个同一类型的数据。数组也是数据类型,是引用类型

1.2.1 一维数组

动态初始化

  • 语法:数组名[] = new 数据类型[大小]
  • 示例:int a[] = new int[5]
public static void main(String[] args) {
  // 创建 double 数组大小为5
  // 第一种方式
  // double[] scores = new double[5];
  // 第二种方式
  double[] scores;
  scores = new double[5];
  // 循环输入
  Scanner myScanner = new Scanner(System.in);
  for (int i = 0; i < scores.length; i++) {
    System.out.println("请输入第"+ (i+1) + "个元素");
    scores[i] = myScanner.nextDouble();
  }

  System.out.println("输入数据,输出结果");
  for (int i = 0; i < scores.length; i++) {
    System.out.println("请输入第"+ (i+1) + "个元素=" + scores[i]);
  }
}

静态初始化

  • 语法:数据类型 数组名[] = {元素值,元素值...}
  • 示例:int a[] = {2,5,6,8,9,10,1}
public static void main(String[] args) {
  int[] arr = {2,5,6,8,9,10,1};
  for (int i = 0; i < arr.length; i++) {
    System.out.println("当前数据值"+arr[i]);
  }
}

数组使用注意事项

  • 数组是多个相同数据的组合,实现对这些数据的统一管理
  • 数组中的元素可以是任意数据类型,包括基本类型和引用类型,但是不能混用
  • 数组创建后,如果没有赋值,会被赋予默认值
  • 使用输入值步骤:1.声明数组并开辟内存空间 2.给数组各个元素赋值 3.使用数组
  • 数组的下标是用 0 开始的
  • 数组长度的获取 数组名.length
  • 数组下标必须在指定范围内使用,否在会出现下标越界异常
  • 数组属于引用类型。数组型数据是对象(object)

数组的算法练习

  1. 求数组的最大值和最小值并得到对应下标 int[] arr = {10,30,50,15,90,23}
public static void main(String[] args) {
  int[] arr = {10,30,50,15,90,23};
  int max = 0;
  int maxIndex = 0;
  int min = 0;
  int minIndex = 0;
  for (int i = 0; i < arr.length; i++) {
    if (max < arr[i]) {
      max = arr[i];
      maxIndex = i;
    } else if (max > arr[i]) {
      min = arr[i];
      minIndex = i;
    }
  }
  System.out.println("最大值为:"+ max + "对应标为:" + maxIndex);
  System.out.println("最小值为:"+ min + "对应标为:" + minIndex);
}
  1. 求数组的平均值和和 int[] arr = {10,30,50,15,90,23}
 public static void main(String[] args) {
  int[] arr = {10,30,50,15,90,23};
  int total = 0;
  for (int i = 0; i < arr.length; i++) {
    total += arr[i];
  }
  System.out.println("总计" + total);
  System.out.println("平均值" + total / arr.length);
}
  1. 数组反转
  2. 数组复制
  3. 数组排序之冒泡排序
  4. 数组排序之直接选择排序

1.2.2 二维数组

如何声明

  • 数组类型 数组名; 数组类型是 xx[][]
  • 元素的类型[][] 数组名;

动态初始化

  • 语法:数组名 = new 元素的数组类型[行长度][每一行的列长度]
  • 示例:String[][] str = new String[2][1]; or String[][] str2 = new String[3][];

静态初始化

  • 语法:数组名 = new 元素的数据类型[行长度][]
  • 示例:int[][] arr = new int[][]{{125, 145}, {652,522}}; or int[][] array = {{1215,55},{54,78}};

二维数组使用注意事项

  • 二维数组长度,即行数 二维数组名.length
  • 二维数组的行对象 二维数组名[行下标] 行下标的范围[0, 二维数组名称.length - 1]
  • 二维数组获取元素 数组名[行下标][列下标] 的形式进行获取

二维数组遍历

public static void main(String[] args) {
  // 静态初始化
  int[][] arr = new int[][]{{125, 145, 155}, {652,522,855}};
  for (int i = 0; i < arr.length; i++) {
    for (int j = 0; j < arr.length; j++) {
        System.out.println("静态初始化" + arr[i][j]);
    }
  }
}

1.3 面向对象

1.3.1 方法

方法(Method):有称为函数(Function),代表一个独立功能,目的为了代码重用

方法声明格式

修饰符列表 返回值类型 方法名(形参列表, 抛出异常列表) {
  // 方法体,方法功能的实现代码
  return 返回值
}
1. 无参无返回值
  • 语法:
public static void 方法名(抛出异常列表) {
  方法体;
}
  • 调用格式

    • 本类中方法名()
    • 其他类中对象名.方法名()
  • 示例:

/**
 * Number 处理
 */
class numberUtil {

  /**
   * 获取最大值
   */
  public static void maxNumber() {
    int max = 0;
    int[] arr = {1,3,5, 20};
    for (int i = 0; i < arr.length; i++) {
      if (max < arr[i]) {
        max = arr[i];
      }
    }
    System.out.println("最大值为" + max);
  }

}

public class arr01 {
  public static void main(String[] args) {
    // 其他类中调用
    numberUtil.maxNumber();
    // 本类中调用
    minNumber();
  }

  public static void minNumber() {
    double min = 0;
    double[] arr = {12, 23, 56, 22,2};
    for (int i = 0; i < arr.length; i++) {
      if (min < arr[i]) {
        min = arr[i];
      }
    }
    System.out.println("最小值为:" + min);
  }
}
2. 有参无返回值
  • 语法:
public static void 方法名(形参列表) {
  方法体;
}
  • 调用格式
    • 本类中方法名(实参列表)
    • 其他类中对象名.方法名(实参列表)
  • 示例:
/**
 * Number 处理
 */
class numberUtil {

  /**
   * 获取最大值
   */
  public static void maxNumber(int[] arr) {
    int max = 0;
    for (int i = 0; i < arr.length; i++) {
      if (max < arr[i]) {
        max = arr[i];
      }
    }
    System.out.println("最大值为" + max);
  }

}

public class arr01 {
  public static void main(String[] args) {
    // 其他类中调用
    int[] arr = {1,5,8,7,2};
    numberUtil.maxNumber(arr);
    // 本类中调用
    double[] arr1 = {10.2, 20.5, 12.0};
    minNumber(arr1);
  }

  public static void minNumber(double[] arr) {
    double min = 0;
    for (int i = 0; i < arr.length; i++) {
      if (min < arr[i]) {
        min = arr[i];
      }
    }
    System.out.println("最小值为:" + min);
  }
}

3. 无参有返回值
  • 语法:
public static 返回值类型 方法名(抛出异常列表) {
  方法体;
  return 返回值;
}
  • 调用格式
    • 本类中变量 = 方法名() 方法调用作为表达式,把方法调用的返回值赋值给变量
    • 其他类中变量 = 对象名.方法名() or 变量 = 类名.方法名() 方法调用作为表达式,把方法调用的返回值赋值给变量
  • 示例:
class numberUtil {

  /**
   * 获取最大值
   */
  public static int maxNumber() {
    int max = 0;
    int[] arr = {10, 20, 5, 14, 23};
    for (int i = 0; i < arr.length; i++) {
      if (max < arr[i]) {
        max = arr[i];
      }
    }
    return max;
  }

}

public class arr01 {
  public static void main(String[] args) {
    // 其他类中调用
    int result1 = numberUtil.maxNumber();
    System.out.println("无参有返回最大值" + result1);
    // 本类中调用
    double result = minNumber();
    System.out.println("无参数有返回最小值:" + result);
  }

  public static double minNumber() {
    double min = 0;
    double[] arr = {10.2, 20.5, 12.0};
    for (int i = 0; i < arr.length; i++) {
      if (min < arr[i]) {
        min = arr[i];
      }
    }
   return min;
  }
}
4. 有参有返回值
  • 语法:
public static 返回值类型 方法名(形参列表) {
  方法体;
  return 返回值;
}
  • 调用格式
    • 本类中变量 = 方法名(实参列表) 方法调用作为表达式,把方法调用的返回值赋值给变量
    • 其他类中变量 = 对象名.方法名(实参列表) or 变量 = 类名.方法名(实参列表) 方法调用作为表达式,把方法调用的返回值赋值给变量
  • 示例:
/**
 * Number 处理
 */
class numberUtil {

  /**
   * 获取最大值
   */
  public static int maxNumber(int[] arr) {
    int max = 0;
    for (int i = 0; i < arr.length; i++) {
      if (max < arr[i]) {
        max = arr[i];
      }
    }
    return max;
  }

}

public class arr01 {
  public static void main(String[] args) {
    // 其他类中调用
    int[] arr = {1,5,8,7,2};
    int result1 = numberUtil.maxNumber(arr);
    System.out.println("有参有返回最大值" + result1);
    // 本类中调用
    double[] arr1 = {10.2, 20.5, 12.0};
    double result = minNumber(arr1);
    System.out.println("有参数有返回最小值:" + result);
  }

  public static double minNumber(double[] arr) {
    double min = 0;
    for (int i = 0; i < arr.length; i++) {
      if (min < arr[i]) {
        min = arr[i];
      }
    }
   return min;
  }
}

方法的注意事项

  • 方法不能嵌套定义
  • void 表示没有返回值,可以省略 return
  • public static 修饰符
  • 方法的参数传递机制
    • 形参的类型是基本数据类型,形参的修改不会影响实际参数
    • 形参的类型是引用数据类型
public class arr {
    public static void main(String[] args) {
        // 调用方法
        isEvenNumber(100);

        // 获取最大数
        getMaxNumber(10, 60);

        // 判断数值是否大于60
        boolean isTrue = isNumberTag(50);
        System.out.println(isTrue);
    }

    // 方法:判断该数据是否为偶数
    public static void isEvenNumber(int number){
        if (number % 2 == 0) {
            System.out.println("偶数");
        } else {
            System.out.println("奇数");
        }
    }
    // 获取两数中的最大数值
    public static void getMaxNumber(int a, int b) {
        if (a > b) {
            System.out.println("最大数为"+ a);
        } else {
            System.out.println("最大数为"+ b);
        }
    }
    // 判断数值是否大于60
    public static boolean isNumberTag(int number) {
        if (number > 60) {
            return true;
        }
        return false;
    }
}

1.3.2 方法重载

方法重载 Overload,在同一个类中,方法名相同,形参列表不同的两个或者多个方法构成方法的重载。和返回值类型无关,调用的时候 java 虚拟机通过参数不同来区分同名的方法
满足重载的几个条件:

  • 多个方法再同一个类中
  • 多个方法具有相同的方法名
  • 多个方法的参数不同,类型不同或者数量不同
public class arr {
  public static void main(String[] args) {
    // 通过传参不同,区分调用同名方法
    int result1 = sum(10, 20);
    System.out.println(result1);

    double result2 = sum(10.0, 20.0);
    System.out.println(result2);

    int result3 = sum(10,20,30);
    System.out.println(result3);
  }

  // 求两个 int 类型数据和方法
  public static int sum(int a, int b) {
    return  a + b;
  }
  // 求两个 double 类型数据和方法
  public static double sum(double a, double b) {
    return a+b;
  }
  // 求三个 int 类型数据和方法
  public static int sum(int a, int b, int c) {
    return  a + b + c;
  }
}

1.3.3 构造方法

  • 概述:构造方法是一种特殊的方法,修饰符一般使用 public
  • 作用:创建对象
  • 语法
public class 类名 {
  修饰符 类名(参数) {}
}
  • 示例
// 创建构造方法
public class Student {
   private String name;
   private int age;

   // 构造方法
   public Student () {
       System.out.println("无参数构造方法");
   }

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

}
// 使用
public class StudentDemo {
  public static void main(String[] args) {
    // 创建对象
    Student s = new Student();
    s.show();

  }
}

1.3.4 类成员对象定义和使用

构造方法制作标准准类

  1. 成员变量
    • 使用 private 修饰
  2. 构造方法
    • 提供一个无参构造方法
    • 提供一个带多个参数的构造方法
  3. 成员方法
    • 提供每一个成员变量对应的 set()/get() 方法
    • 提供一个显示对象信息的 show() 方法

定义对象

  • 定义对象语法:
class 类名称 {
  属性(成员变量);
  行为(成员方法);
}
  • 定义对象示例:
public class Phone {
  // 成员变量
  String brand;
  int price;
  // 成员方法
  public void call() {
    System.out.println("打电话");
  }
  public void sendMessage() {
    System.out.println("发短信");
  }
}

使用对象

  • 对象使用语法 创建对象:

    • 创建:类名 对象名 = new 类名();
    • 示例:Phone p = new Phone();

    使用对象:

    • 1.使用成员变量
      • 格式:对象名.变量名
      • 示例:p.brand
    • 2.使用成员方法
      • 格式:对象名.方法名()
      • 范例:p.call()
  • 使用对象示例:

public class PhoneDemo {
  public static void main(String[] args) {
    // 创建对象
    Phone p = new Phone();
    // 使用成员变量
    System.out.println(p.brand);
    System.out.println(p.price);

    // 变量赋值
    p.price = 6200;
    p.brand = "华为";

    System.out.println(p.brand);
    System.out.println(p.price);
    // 使用成员方法
    p.call();
    p.sendMessage();
  }
}

成员变量局部变量的区别

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

1.3.5 封装

封装通过 private 关键字修饰权限,private 是一个权限修饰符

  • 可以修饰成员(成员变量和成员方法)
  • 作用是保护成员不被别的类使用,被 private 修饰的成员只在本类中才能访问

针对 private 修饰的成员变量,如果需要被其他类使用,提供响应的操作

  1. 提供 get 变量名() 方法,用于获取成员变量的值,方法用pubilc修饰

  2. 提供 set 变量名()方法,用于设置成员变量的值,方法用pubilc修饰

  3. 创建成员变量,添加修饰符

/*学生类*/
public class Student {
  // 成员变量
  String name;
  int age;
  private int sex; // 修饰为成员变量,可通过set 和get 方法获取和设置值
  // 设置性别
  public void setSex(int a) {
    if(a!= 0) {
      System.out.println("男性");
    } else {
      System.out.println("女性");
    }
    this.sex = a;
  }
  // 获取性别
  public int getSex() {
    return sex;
  }

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

}
  1. 使用成员变量
public class StudentDemo {
  /*
   * 学生测试类
   *
  */
  public static void main(String[] args) {
    // 创建对象
    Student s = new Student();
    // 对象赋值
    s.name = "张三";
    s.age = 24;
    s.setSex(1);
    // 使用成员方法
    s.getSex();
    s.show();

  }
}

封装总结

封装概述

  1. 封装是面向对象三大特征之一(封装、继承、多态)
  2. 是面向对象编程语言对客观世界的模拟,客观世界里面成员变量都是隐藏在对象内部的外界无法直接进行操作

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

封装好处

  1. 通过方法类控制成员变量的操作,提高了代码的安全性
  2. 把代码进行了封装提高了代码的复用性

1.3.6 继承

继承介绍

多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。 多个类可以称为子类,单独这个类称为父类、超类或者基类。 子类可以直接访问父类中的非私有的属性和行为。 通过 extendsimplements 关键字来实现继承,而且所有类都继承于 java.lang.Object 当一个类没有继承的两个关键字,则默认继承 object 这个类

继承好处

  • 提高代码复用性
  • 让类和类之前产生关系,是多态的前提

继承的语法和示例

  • 语法:
class 父类 {}
class 子类 extends 父类 {}
  • 示例:
/**
 * 公共父类
 */
class Animals {
  private String name;
  private int id;
  /**
   * 无参构造方法
   */
  public  Animals() {};
  /**
   * 多参构造方法
   */
  public Animals(String myMame, int myid) {
    this.name = myMame;
    this.id = myid;
  }

  public void eat() {
    System.out.println(name + "正在吃");
  }
  public void sleep() {
    System.out.println(name + "正在睡觉");
  }
  public void introdcount() {
    System.out.println(id + "号" + name + "正在吃");
  }
}

/**
 * 企鹅构造器
 */
class Penguin extends Animals {
  public Penguin(String myMame, int myid) {
    super(myMame, myid);
  }
}
/**
 * 老鼠构造器
 */
class Mouse extends Animals {
  public Mouse(String myMame, int myid) {
    // super关键字:可以通过 super 关键字来实现对父类成员的访问,用来引用当前对象的父类
    // this关键字:指向在自己的引用
    super(myMame, myid);
  }
}

public class arr01 {
  public static void main(String[] args) {
    // 使用老鼠类继承公共类
    Mouse mou = new Mouse("杰瑞", 5445);
    mou.eat();
    mou.sleep();
    mou.introdcount();
    // 使用企鹅类继承公共类
    Penguin pen = new Penguin("阿拉斯加", 1245);
    pen.eat();
    pen.sleep();
    pen.introdcount();

  }
}

继承后对成员的影响

1. 属性

  • 子类继承父类,一定会继承父类的所有的属性,包括私有的,但是由于私有的关键字 private 的原因,在子类中无法直接操作它,但是可以通过 get/set 方法进行获取设置值
  • 当子类的属性与父类的属性重名时,而且父类属性没有私有化,如果要访问父类属性那么通过 super.属性 访问。如果子类中没有通过 super.属性 访问,那么这个属性就表示是子类自己的

2. 方法

  • 子类继承父类时,一定会继承父类所有的方法,包括私有的,但是由于 private 在子类中无法直接操作,但是可以间接操作
  • 当父类的方法实现不适用于子类时,子类可以对父类的方法进行重写

3. 构造器

  • 子类继承父类时,不会继承父类的构造器
  • 子类继承父类时,一定会调用父类的构造器
    • 如果父类有无参构造器,子类默认会去调用父类的无参构造,调用父类无参构造器使用 super()
    • 如果父类没有无参构造器,只有有参构造器,那么子类必须在子类构造器中手动调用父类的有参构造器,调用父类有参构造器使用 super(实参列表)
    • 无论是调用父级有参构造器 super(实参列表)还是父级无参构造器super(),这两个语句必须在子类构造器的首行

继承的原则

  1. 单继承:在 java 中只支持单继承,也就是说一个类只能有一个父类
  2. 多层继承
  • 子类对象在寻找一个方法、属性时,如果本类中找不到,会去直接去父类中查找,如果父类也没有,会一直追溯到 java.lang.Object 根父类中
  • 通过super.属性方法时,先从直接父类中查找,如果没有,再往上找,一直可以到 java.lang.Object
  1. 一个父类可以有多个子类,子类还可以拥有子类

1.3.7 多态

多态的表现形式

  1. 方法的重载:同一类中,功能的多种实现形式
  2. 方法的重写:父子类中,功能的不同实现形式
  3. 对象的多态性:编译时类型与运行时的类型不一致,编译时看“左边”,运行时看“右边”。编译时从“父类”中寻找方法,运行时执行的是“子类”重写过的代码
  4. 对象多态性的前提:
  • 继承/实现关系
  • 方法的重写
  • 多态引用
    • 多态引用:Person p = new Person()
    • 本态引用:Person p = new Person() or Student s = new Student()
// 父类定义
public class Animal {
  public int age = 40;
  public void eat() {
    System.out.println("动物吃东西");
  }
}

// 子类继承
/**
 * 设置继承关系
 */
public class Cat extends Animal{
  public int age = 20;
  public int weight =10;
  /**
   * @Override 标识方法重写
   */
  @Override
  public void eat() {
    System.out.println("猫吃鱼");
  }

  public void playGame() {
    System.out.println("捉迷藏");
  }
}

// 访问,成员属性和成员方法,访问成员方法看左边
/**
 * 多态:同一个对象,在不同时刻表现胡来的不同形态
 * 举例:猫
 *    猫就是猫:猫 cat = new 猫()
 *    猫是动物: 动物 animal = new 猫()
 *   这里的猫在不同的时刻表现出来的不同的形态,这就是多态
 */
public class AnimalDemo {
  public static void main(String[] args) {
    // 父类引用指向子类对象
    Animal a = new Cat();
    /*
    * 多态的访问属性和成员方法都是看左边 Animal a 这个父类中是否存在该成员属性和成员方法
    * */
    System.out.println(a.age);
    a.eat();

    // 以下属性和方法左侧类中没有,所以不能访问
    //    a.playGame();
    //    System.out.println(a.weight);

  }
}

多态的应用

  1. 多态参数
  2. 多态数组

类型转换

  1. 向上转换
  • 子类的对象赋值给父类的变量
  • 自动完成
  1. 向下转换
  • 把父类的变量赋值给子类的变量
  • 强制类型转换
  • 如果想要向下转型成功,父类的变量本省指向的就是该子类的对象
  • 如何避免 ClassCastException 向下转型之前加判断
if (变量 instanceof 子类类型) {
  子类类型 temp = (子类类型)变量;
}
  • 什么情况下需要向下转型
    • 由于是一对一向上转型后,那么就无法访问该子类对象中特有的方法,只能访问父类有的方法
    • 如果需要通过该对象,访问子类特有的方法等,那么就需要向下转型

1.父类定义

public class Animal {
  public void eat() {
    System.out.println("打印");
  }
}

2.子类继承

// 猫-重写父类
public class Cat extends Animal{
  @Override
  public void eat() {
    System.out.println("小猫咪吃鱼");
  }

  public void play(){
    System.out.println("玩毛线球");
  }
}
// 狗-重写父类
public class Dog extends Animal{
  @Override
  public void eat() {
    System.out.println("狗吃骨头");
  }
}

3.实例化类,实现向上转型和向下转型

/**
 * 向上转型
 * 从子到父
 * 父类引用指向子类对象
 * 向下转型
 * 从子到父
 * 父类引用转为子类对象
 */
public class AnimalDemo {
  public static void main(String[] args) {
    // 多态-向上转型
    Animal a = new Cat();
    a.eat();

    // 创建 Cat 类的对象 这种创建方式会在内存中放 两个 Cat 对象,所以使用向下转型
   /* Cat c = new Cat();
    c.eat();
    c.play();*/

    //多态-向下转型:解决不能访问子类的弊端
    Cat c = (Cat)a;
    c.eat();
    c.play();

    // 向上转型
    a = new Dog();
    a.eat();

    // 向下转型 ClassCastException 类型转换异常
//    Cat cc = (Cat) a;
//    cc.eat();
//    cc.play();
  }
}

多态练习:猫和狗

// 创建动物父类
public class Animal {
  private int age;
  private String name;
  // 无参构造器
  public Animal(){}
  // 多参构造器
  public Animal(int age, String name) {
    this.name = name;
    this.age = age;
  }

  public void eat() {
    System.out.println("吃饭");
  }

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

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

  public int getAge() {
    return age;
  }

  public String getName() {
    return name;
  }
}


// 子类继承动物父类

public class Cat extends Animal {
  public Cat() {}
  public Cat(int age, String name) {
    super(age, name);
  }

  @Override
  public void eat() {
    System.out.println("小猫爱吃鱼");
  }
}
public class Dog extends Animal {
  public Dog() {}
  public Dog(int age, String name) {
    super(age, name);
  }

  @Override
  public void eat() {
    System.out.println("狗吃骨头");
  }
}

// 多态实例化类,使用
public class AnimalDemo {

  public static void main(String[] args) {
    Animal a = new Cat();
    a.setName("加菲猫");
    a.setAge(20);
    a.eat();

    a = new Dog();
    a.eat();
  }
}

1.38 包

包的作用

  1. 避免类的重名
  2. 访问权限控制
  3. 便于管理

包的声明

  1. 声明方式:package
  2. 要求
    • 必须在源文件的首航,一个源文件只能有一句
    • 遵循命名规范,所哟有字母都小写,单词之间使用.一般以公司的域名倒置

如何使用其他的包

  1. 引入方式:需要 import 包名.类名;

  2. 要求

    • packageclass 声明之间,可以多句
    • 被使用的类必须是 public 或者 protected(父子类)
  3. 形式

    • 一一列举

      • import java.util.Random;
      • import java.util.Scanner;
    • 某个包的类 import java.util.*;

    • 静态导入

      • import static java.lang.Math.*;
      • System.out.printIn(PI)
      • System.out.printIn(sqrt(4))

1.39 Overload(重载)和 Override(重写)的区别

  • Overload:方法的重载,在同一类中方法名称相同,形参列表不同的两个或者多个方法称为重载
  • Override:方法的重写,在子类继承父类时,如果父类的方法实现不适用于子类,子类就可以对父类的方法进行重写,覆盖
    名词Overload 方法的重载Override 方法的重写
    位置同一个类中在父子类中
    方法名必须相同必须相同
    形参列表必须相同必须相同
    返回值类型无关<=,如果返回值类型是 void 和基本数据类型,那么必须一直,如果是引用数据类型,重写方法的返回值类型可以和被重写的方法返回值类型一样或是他的子类
    抛出异常的列表无关<=,重写方法抛出的异常类型要么和被重写方法一样,要么是被重写方法抛出异常的子类
    权限修饰符无关(建议一致)>=,重写方法的权限修饰符要么和被重写方法一样,要么比被重写方法的权限范围大

1.4 抽象类

1.4.1 为什么会有抽象类

  1. 当子类中都有一个共同的方法,每一个子类都有不同的实现,在父类中又要体现所有子类的共同特点,所有要体现有这个方法,但是父类中又无法给出具体的实现,那么这个时候就需要把这个方法声明为抽象的,而包含抽象方法的类,必须是抽象类
  2. 某个父类仅仅是标识一个抽象的概念,不希望它被实例化,这个时候父类中可能没有抽象方法,但是我们也可以把它声明为抽象类

1.4.2 如何声明抽象类

抽象类关键字 abstract 语法格式

[public/缺省] abstract class 类名 {
}

1.4.3 如何声明抽象方法

抽象方法关键字 abstract抽象方法是不能使用 private,static,final 修饰的 语法格式

[public/protected/缺省] abstract 返回值类型 方法名([形参列表]) {
}

1.4.4 抽象类的特点

  1. 抽象类不能实例化
  2. 抽象类可以包含抽象方法,也可以没有抽象方法。如果一个抽象类没有抽象方法,那么它的用意是不想实例化,仅仅用它表示一个抽象的概念。
  3. 抽象类生来就是用来被继承来的,那么子类继承它的时候,必须重写(实现)抽象父类的抽象方法,否则必须子类也必须是抽象类
  4. 抽象类的变量与子类的对象构成多态引用
  5. 抽象类不能实例化,可以包含抽象方法,其他的和非抽象类一样,可以拥有成员变量(类常量、示例变量)、构造器、代码块(静态代码块、非静态代码块)、方法(静态方法、动态方法)
// 抽象类定义
public abstract class Animal {
/**
 * 定义抽象方法
 * abstract 关键字修饰 抽象类和抽象方法
 * 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
 * 抽象
 */
  public abstract void eat();

  public void sleep() {
    System.out.println("睡觉");
  }

}

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

// 子类继承了抽象父类没有重写方法将子类设置为抽象类
public abstract class Dog extends Animal {
}


// 实例化重写抽象类方法,类似于多态的用法
public class AnimalDemo {
  public static void main(String[] args) {
    Animal a = new Cat();
    a.eat();
    a.sleep();
  }
}

1.4.5 案例:猫和狗

// 定义抽象类
public abstract class Animal {
  private String name;
  private int age;

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

  public Animal() {}


  public int getAge() {
    return age;
  }

  public String getName() {
    return name;
  }

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

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

  public abstract void eat();
}
// 子类继承抽象类,重写父类中的抽象方法
public class Cat extends Animal{
  public Cat() {}
  public Cat(String name, int age) {
    super(name, age);
  }

  @Override
  public void eat() {
    System.out.println("猫吃鱼");
  }
}

// 创建对象,采用多态的方式
public class AnimalDemo {
  public static void main(String[] args) {
    // 创建对象,按照多态的方式
    Animal a = new Cat();
    a.setName("橘猫");
    a.setAge(2);
    System.out.println(a.getName() + ',' + a.getAge());
    a.eat();

    a = new Cat("加菲", 5);
    System.out.println(a.getName() + ',' + a.getAge());
    a.eat();
  }
}

1.5 接口

接口代表行为标准、功能标准

1.5.1 如何声明

  • 语法
[public/缺省] interface 接口名 {}
  • 示例
/**
 * 定义一个接口
 */
public interface Jumpping {
  public abstract void jump();
}

1.5.2 如何实现

  • 语法
[public/缺省] class 子类名 [extends 父类名] implements 接口名1,接口名2...{
  // 要实现接口的所有抽象方法
}
  • 示例
/**
 * 使用 implements 关键字来继承接口内容
 */
public class Cat implements Jumpping{
  @Override
  public void jump() {
    System.out.println("重写方法");
  }
}

// 接口的实例化采用多态的方式
public class JumppingDemo{
  public static void main(String[] args) {
      // 采用多态的方式进行实例化
      Jumpping J = new Cat();
      J.jump();
  }
}

1.5.3 接口特点

JDK1.8 之后:接口中除了全局静态的常量和公共抽象方法以外,可以有静态方法和默认方法

  1. 接口中的静态方法

    • 当接口所有实现类,对这个方法的实现是一样的,这个方法就设计在接口中,设计为静态方法。采用接口名.方法的形式调用
  2. 接口中的默认方法

    • 当接口的大多数实现类,对这个方法的实现一样,那么这个方法的实现就可以在接口中提供默认实现,如果某个实现类觉得它不合适,只需重写它即可

    • 如何调用

      • 实现类外实现类对象.方法
      • 实现类中,如果实现类要重写该默认方法,但是又想调用接口中的默认实现采用接口名.super.方法 的形式调用
    • 什么情况下需要重写

      • 接口中的默认实现不适合该实现类
      • 必须重写一个类同时实现了多个接口,而多个接口中都相同的默认方法(方法名和形参列表都相同)这个时候实现类必须做出选择,要重写,如果需要保留其中一个的话,通过接口名.super.方法保留它的默认实现
    • 优先原则:当一个类继承了父类,又实现了接口,而父类中的某个方法与接口中的默认方法一样(方法名和形参列表),默认保留的是父类中的方法实现

  3. 接口用关键字 interface 修饰 public interface 接口名{}

  4. 类实现接口用 implements 表示 public class 类名 implements 接口名{}

  5. 接口不能直接实例化

    • 接口的实例化参考多态形式,通过实现类对象实例化,这个操作叫做接口多态
    • 多态的形式:具体类多态,抽象类多态,接口多态
    • 多态的前提:有继承或者实现关系;有方法重写;有父(类/接口)引用指向(子/实现)类对象
  6. 接口实现类

    • 要么重写接口中所有抽象方法
    • 要么抽象类

1.5.4 接口案例

猫和狗

  1. 定义抽象动物类
public abstract class Animal {
  private String name;
  private int age;
  public  Animal() {}
  public  Animal(String name, int age) {
    this.name = name;
    this.age = age;
  }

  public String getName() {
    return name;
  }

  public int getAge() {
    return age;
  }

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

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

  public abstract void  eat();
}
  1. 定义动物动作接口
public interface Jumpping {
  public abstract void jump();
}

  1. 猫类(具体实现类)继承抽象动物类(父类)和动物动作接口(接口)
public class Cat extends Animal implements Jumpping{
  public Cat() {}

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

  @Override
  public void eat() {
    System.out.println("猫吃鱼");
  }

  @Override
  public void jump() {
    System.out.println("猫跳高");
  }
}
  1. 实例化使用
public class Demo {
  public static void main(String[] args) {
    // 创建对象,调用方法
    Jumpping j = new Cat();
    j.jump();

    Animal a = new Cat();
    a.setName("富贵");
    a.setAge(20);
    System.out.println(a.getName() + a.getAge());
    a.eat();

    a = new Cat("张三", 8);
    a.eat();

    Cat c = new Cat();
    c.setName("加菲");
    c.setAge(2);
    System.out.println(c.getName() + c.getAge());
    c.eat();
    c.jump();
  }
}

教练和学员

  1. 定义抽象类
/**
 * 抽象人类类
 */
public abstract class Person {
  private String name;
  private int age;

  public Person() {}

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

  public String getName() {
    return name;
  }

  public int getAge() {
    return age;
  }

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

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

  public abstract void eat();
}
  1. 定义抽象接口
/**
 * 学习语言接口
 */
public interface SpeakEnglish {
  public abstract void speak();
}

  1. 继承抽象类和抽象接口
/**
 * 乒乓球学员类
 */
public class PingpangPlayer extends Player implements SpeakEnglish{
  public PingpangPlayer() {}

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

  @Override
  public void eat() {
    System.out.println("乒乓球学员吃苹果和红烧肉米饭");
  }

  @Override
  public void study() {
    System.out.println("乒乓球学员学习乒乓球和练习跑步");
  }

  @Override
  public void speak() {
    System.out.println("乒乓球学员学习英语");
  }
}
  1. 实例化使用
public static void main(String[] args) {
  //创建对象
  PingpangPlayer pp = new PingpangPlayer();
  pp.setName("王浩");
  pp.setAge(20);
  System.out.println(pp.getName() + pp.getAge());
  pp.eat();
  pp.study();
  pp.speak();
}

1.5.5 类和接口的关系

  1. 类和类的关系:继承关系,只能单继承,但是可以多层继承
  2. 类和接口的关系:实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口
  3. 接口和接口的关系:继承关系、可以单继承,也可以多继承

1.5.6 抽象类和接口的关系

  1. 成员区别

    • 抽象类:变量、常量;有构造方法;有抽象方法,也有非抽象方法
    • 接口:常量;抽象方法
  2. 关系区别

    • 类与类:继承,单继承
    • 类与接口:实现,可以单实现,也可以多实现
    • 接口与接口:继承,单继承、多继承
  3. 设计理念区别

    • 抽象类:对类抽象,包括属性、行为
    • 接口:对行为抽象,主要是行为

1.6 内部类

一个内的内部又完整嵌套了另一个类结构,被嵌套额类称为内部类(inner class)嵌套的其他类的类称为外部类(otuer class),内部类的最大特点可以直接访问私有属性,并且可以体现类和类之间的包含关系
类的五大成员

  • 属性
  • 方法
  • 构造器
  • 代码块
  • 内部类

基本语法

class Outer { // 外部类
  class Inner{ // 内部类

  }
}
class Other { // 外部其他类

}

示例

/**
 * 内部类
 */
public class innerclass { // 外部其他类
  public static void main(String[] args) {

  }
}

class Outer { // 外部内
  private int n = 100; // 属性
  public Outer() {} // 无参构造器
  public Outer(int n) { // 含参构造器
    this.n = n;
  } // 无参构造器
  public void m() { // 方法
    System.out.println("m");
  }
  { // 代码块
    System.out.println("代码块");
  }

  /**
   * 内部类在 Outer 类的内部
   */
  class Inner{

  }
}

1.6.1 内部类的分类

定义在外部类局部位置上(比如方法内)

1. 局部内部类(有类名)

记住:a).局部内部类定义在方法/代码块 b).作用域在方法体或者代码块中 c).本质仍然是一个类 示例:

public class inner {
  public static void main(String[] args) {
    Outer o = new Outer();
    o.m1();
    o.m2();
  }
}

/**
 * 外部类
 */
class Outer {
  private int age = 20;
  public void m2 () {
    System.out.println("局部方法");
  }
  // 方法
  public void m1() {
    //1.局部内部类是定义在外部类的局部位置,通常在方法
    //3.不能添加访问修饰符,但是可以使用final修饰(修饰后不可以继承),因为局部变量也可以使用 final修饰
    //4.作用域:仅仅在定义它的方法代码块中
    final class Inner02 { // 局部内部类(本质仍然是一个类)
      //2.可以直接访问外部内的所有成员,包含私有的
      public void f1 () {
        //5.局部内部类可以直接访问外部类成员,比如下面,外部类 age 和m2()
        System.out.println("age="+age);
        m2();
      }
    }
    //6.外部类在方法中,可以创建 Inner02对象,然后调用方法即可
    Inner02 inner02 = new Inner02();
    inner02.f1();
  }
}
2. 重要:匿名内部类(没有类名)

a).本质是类 b).内部类 c).该类没有名字 d).同时还是一个对象
说明:匿名内部类是在当以在外部类的局部位置,比如方法中并且没有类名
语法:

new/接口(参数列表){
  类体
}

示例 1:

package com.innerclass;

public class AnonymousInnerClass {
  public static void main(String[] args) {
    Outer1 o = new Outer1();
    o.method();
  }
}

class Outer1 {
  private int age = 10;
  public void  method() {
    /**
     * 1.需求:基于接口的匿名类
     * 2.传统方式,写一个类实现接口,并创建对象
     *  Tiger t = new Tiger();
     *  t.cry();
     * 3.Tiger 类只只用一次,后面不再进行使用可以使用匿名内部类来简化开发
     * 4.tiger 的编译类型 ? IA
     * 5.tiger 的运行类型? 就是匿名内部类 Outer1$1
     *
    */
    /**
     匿名内部类相当于如下代码:会自动分配类名 Outer1$1
     class Outer1$1 implements IA{
        @Override
        public void cry() {
          System.out.println("老虎叫唤。。。");
        }
     }
     */
    //6.jdk底层在创建匿名内部类 Outer1$1,立即创建了 Outer1$1实例,并且吧地址返回给 tiger
    //7. 匿名内部类只能使用一次,不能在使用
    //3. Tiger 类只只用一次,后面不再进行使用可以使用匿名内部类来简化开发,如下
    // 一、基于接口的匿名内部类
    IA Tiger = new IA() {
      @Override
      public void cry() {
        System.out.println("老虎叫唤。。。");
      }
    };
    System.out.println("tiger接口的运行类型="+ Tiger.getClass());
    Tiger.cry();
    Tiger.cry();
    Tiger.cry();

    // 二、基于类的匿名内部类
    /**
     * 分析:
     * 1. father 编译类型
     * 2. father 运行类型  Outer1$2
     * ("jack") 参数会传递给Father类的构造器
     */
    /*
    class Outer1$2 extends Father {
      @Override
      public void test() {
        System.out.println("匿名内部类重写 test 方法");
      }
    }
    * */
    Father father = new Father("jack") {
      @Override
      public void test() {
        System.out.println("匿名内部类重写 test 方法");
      }
    };
    System.out.println("father对象的运行类型=" + father.getClass());
    father.test();

    // 三、基于抽象类的匿名内部类
    Animal a = new Animal() {
      @Override
      void eat() {
        System.out.println("狗吃骨头...");
      }
    };
    a.eat();
  }
}


/**class Tiger implements IA {
  @Override
  public void cry() {
    System.out.println("老虎");
  }
}*/
/**
 * 接口
 */
interface IA {
  public void cry();
}

/**
 * 类
 */
class Father{
  public Father (String name) {
    System.out.println("name=" + name);
  }
  public void test() {}
}

abstract class Animal {
  abstract void eat();
}

实例 2:

package com.innerclass;

public class AnonymousInnerClassDemo {
  public static void main(String[] args) {
    OuterOther ou = new OuterOther();
    ou.fan();
    // 外部其他成员,不能访问-匿名内部类
    System.out.println("main OuterOther hashcode=" + ou);
  }
}

class OuterOther {
  private int age = 60;
  public void  fan() {
    // 创建一个基于类的匿名内部类
    // 不能添加访问修饰符,因为他的地位就是一个局部变量
    Person p = new Person() {
      private int age = 30;
      @Override
      public void hi() {
        //可以直接访问外部类的所有成员,包含私有的
        //如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则,如果想要访问外部类成员,则可以使用(外部类名.this.成员)去访问
        System.out.println("重写了hi()方法 age=" + age + "外部类的age=" + OuterOther.this.age);
        //OuterOther.this 就是调用 fan的对象
        System.out.println("OuterOther.this hashcode=" + OuterOther.this);

      }
    };
    p.hi(); // 动态绑定,运行类型是 OuterOther$1

    // 也可以直接调用,匿名内部类本身也是返回对象
    // class匿名内部类是 extends Person{}
    new Person() {
      @Override
      public void hi() {
        System.out.println("也可以直接调用hi()方法");
      }

      @Override
      public void ok(String name) {
        super.ok(name);
      }
    }.ok("张三");
  }
}

class Person {
  public void  hi() {
    System.out.println("打印hi()");
  }
  public void ok(String name) {
    System.out.println("Person ok()=" + name);
  }
}

匿名内部类应用场景:当作实参直接传递,简洁高效

package com.innerclass;

public class InnerClassExercise {
  public static void main(String[] args) {
    //当作实参直接传递,简洁高效,推荐使用更加灵活
    f1(new IL() {
      @Override
      public void show() {
        System.out.println("吃西瓜");
      }
    });
    // 传统方式: 实例化对象,多次调用场景
    f1(new Picture());
  }

  //静态方法,形参是接口类型
  public static void  f1(IL il) {
    il.show();
  };
}

//类
interface IL {
  void show();
}

//类->接口 => 编程领域(硬编码)
class Picture implements IL{
  @Override
  public void show() {
    System.out.println("接口继承类,并重写类内部方法");
  }
}

接口作为内部类参数使用

package com.innerclass;

public class aramClass {
  public static void main(String[] args) {
    CellPhone cell = new CellPhone();

  /**
   * 1.传递了实现了 Bell接口的匿名内部类 aramClass$1
   * 2.重写了 ring 方法
   * 3.Bell bell = new Bell() {
   *         @Override
   *         public void ring() {
   *             System.out.println("上课了");
   *         }
   *     }
   */
    cell.alramClock(new Bell() {
        @Override
        public void ring() {
            System.out.println("起床了");
        }
    });

    cell.alramClock(new Bell() {
        @Override
        public void ring() {
            System.out.println("上课了");
        }
    });
  }
}


// 抽象接口
interface Bell {
    // 抽象方法
    void ring();
}

// 类
class CellPhone{
    // 形参是 Bell 接口类型
    public void alramClock(Bell bell) {
        System.out.println(bell.getClass());
        bell.ring(); // 动态绑定,会重新周到内部类
    }
}

1.6.2 定义在外部类的成员位置上

1. 成员内部类(没有 static 修饰)

成员内部类是定义在外部类的成员位置,并且没有 static 修饰

  1. 成员内部类可以直接访问外部类所有成员包含私有的
  2. 成员内部类可以添加任意访问修饰符(public、portectd、默认、private)因为它的地位就是一个成员
  3. 成员内部类,访问外部类成员(属性/方法)可以直接访问
  4. 外部类访问内部类,访问方式:创建对象,再访问
  5. 其他内部类范围成员内部类
public class memberInnerClass {
  public static void main(String[] args) {
    OuterMenber om = new OuterMenber();
    om.t1();
    // 外部其他类,使用成员内部类的三种方式
    // 第一种方法:只是一种语法
    OuterMenber.InnterJGJ inn = om.new InnterJGJ();
    // 第二种方法:再外部类中编写一个方法,可以返回 InnterJGJ 对象
    OuterMenber.InnterJGJ inner02 = om.new InnterJGJ();
    inner02.say();
  }
}

class OuterMenber {
  private int age = 20;
  public String name = "张三";
  // 成员内部类
  public class InnterJGJ {
    private String sex = "男";
    private int age = 22;
    public void say() {
      // 如果成员内部类的成员和外部类的成员重名,会遵守就近原则,类似于js中的作用域。范文外部类可以使用(外部类.this.属性名称) 的语法进行访问
      System.out.println("age=" + age + "name=" + name);
      System.out.println("外部类的年龄=" + OuterMenber.this.age);
    }
  }
  //该方法返回一个 InnterJGJ 实例
  public InnterJGJ getInnerInnterJGJ () {
    return new InnterJGJ();
  }
  //方法-使用成员内部类
  public void t1() {
    InnterJGJ Innter2 = new InnterJGJ();
    Innter2.say();
    System.out.println("性别" + Innter2.sex);
  }
}

2. 静态内部类(使用 static 修饰)

  1. 放在外部类的成员位置
  2. 可以使用 static 修饰
  3. 可以直接访问外部类的所有静态成员,包含私有的,但是不能直接访问非静态成员
  4. 可以添加任意访问修饰符(public、protected、默认、private)因为他的地位就是一个成员
  5. 作用域:同其他的成员,为整体类
  6. 如果成员内部类的成员和外部类的成员重名,会遵守就近原则,类似于 js 中的作用域。范文外部类可以使用(外部类.this.属性名称) 的语法进行访问
public class staticStateClass {
  public static void main(String[] args) {
    stateOuter so = new stateOuter();
    so.show();
    // 外部其他类 使用静态内部类
    // 方式一:因为静态内部类,是可以通过类名直接访问(前提是满足访问权限)
    stateOuter.Inner inner = new stateOuter.Inner();
    inner.say();
    // 方式二:编写一个方法,可以返回静态内部类的对象实例
    stateOuter.Inner inner10 = so.getInner();
  }
}


class stateOuter {
  private int age = 20;
  private static String name = "张三";
  // Inner 就是静态内部类
  static class Inner {
    public void say() {
      System.out.println(name);
    }
  }
  // 外部类访问静态内部类,访问方式:创建对象再访问
  public void show() {
    new Inner().say();
    //
//    Inner iner = new Inner();
//    iner.say();
  }

  public Inner getInner() {
    return new Inner();
  }
}

1.7 枚举

枚举是指某个类型的对象是有限个,在类型中一一创建并列举它的对象
JDK1.5 之前解决方案

  1. 构造器私有化
  2. 通过常量的方式创建好所有对象
class Week{
	public static final Week MONDAY = new Week();
	public static final Week TUESDAY = new Week();
	public static final Week WEDNESDAY = new Week();
	public static final Week THURSDAY = new Week();
	public static final Week FRIDAY = new Week();
	public static final Week SATURDAY = new Week();
	public static final Week SUNDAY = new Week();

	private Week(){

	}
}
Week w = Week.MONDAY;

1.7.1 枚举如何声明

[修饰符] enum 枚举类型名{
  常量对象列表;
  其他成员;
}

1.7.2 枚举枚举的实现方式

  1. 自定义实现枚举
  2. 通过关键字 enum 实现枚举

自定义实现枚举

  1. 将构造器私有化,防止 new 实例化类
  2. 去掉 setXXX 方法,方式属性被修改
  3. 在 Season 内部,直接创建固定的对象
  4. 优化,可以加入 final 修饰符
public class enumeration02 {
  public static void main(String[] args) {
    System.out.println(Season.AUTUMN);
    System.out.println(Season.SPRING);
    System.out.println(Season.WINTER);
    System.out.println(Season.SUMMER);
  }
}

// 演示自定义枚举
class Season {
  private String name;
  private String desc;

  //定义了四个对象
  public static final Season SPRING = new Season("春", "温暖");
  public static final Season WINTER = new Season("冬", "寒冷");
  public static final Season AUTUMN = new Season("秋", "凉爽");
  public static final Season SUMMER = new Season("夏", "炎热");
  /**
   * 1.将构造器私有化,防止 new 实例化类
   * 2.去掉setXXX方法,方式属性被修改
   * 3.在Season内部,直接创建固定的对象
   * 4.优化,可以加入 final 修饰符
   */
  private Season(String name, String desc) {
    this.name = name;
    this.desc = desc;
  }

  public String getName() {
    return name;
  }

  public String getDesc() {
    return desc;
  }

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

通过关键字 enum 实现枚举

  1. 使用关键 enum 代替 class
  2. public static final Season SPRING = new Season("春", "温暖"); 直接使用 SPRING("春", "温暖"); 表达。解读:常量名(实参列表)
  3. 如果有多常量(对象),使用,号间隔
  4. 如果使用 enum 来实现枚举,要求将定义的常量对象,写在前面
public class enumeration03 {
  public static void main(String[] args) {
    System.out.println(Season2.AUTUMN);
  }
}

enum Season2 {
  /**
   * 使用 enum 关键字来实现枚举类
   * 1.使用关键 enum 代替 class
   * 2. public static final Season SPRING = new Season("春", "温暖"); 直接使用 SPRING("春", "温暖"); 表达。解读:常量名(实参列表)
   * 3.如果有多常量(对象),使用,号间隔
   * 4.如果使用 enum 来实现枚举,要求将定义的常量对象,写在前面
   * 5.如果我们使用的是无参构造器,创建常量对象,则可以省略()
   */
  SPRING("春", "温暖"),
  WINTER("冬", "寒冷"),
  AUTUMN("秋", "凉爽"),
  SUMMER("夏", "炎热");

  private String name;
  private String desc;

  private Season2(String name, String desc) {
    this.name = name;
    this.desc = desc;
  }

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

1.7.3 枚举的特点

  1. 枚举类型中的构造器都是私有化

  2. 常量对象列表必须在首行,如果常量对象列表后面面还有其他代码,那么要用;结束

  3. 枚举类型不能继承别的类型,因为默认继承 java.lang.Enum

  4. 常用方法

    • name() 返回常量名
    • ordinal() 返回常量对象的序号,从 0 开始
    • 实现了 java.lang.Comparable 接口,重写 compareTo(),按照常量对象的顺序排序,如自己的枚举类中不合适,可以重写
    • toString() 返回常量对象名,可以重写
  5. API 中没有的方法

    • 枚举类型名.values() 返回枚举常量对象组成的数据
    • 枚举类型名.valueOf() (常量对象的名称),返回某一个指定的对象
  6. 实例:Enum 类的各种方法的使用

public class enumeration04 {
  public static void main(String[] args) {
    Season02 autumn = Season02.AUTUMN;
    // 输出枚举对象名称
    System.out.println(autumn.name());
    // 输出枚举对象的次序、编号
    System.out.println(autumn.ordinal());
    // 定义所有的枚举对象,数组形式
    Season02[] values = Season02.values();
    // 循环获取values() 获取的所有值
    for (Season02 value : values) {
      System.out.println(value);
    }
    // 将字符串转换为枚举对象
    Season02 s = Season02.valueOf("SUMMER");
    System.out.println(s);

  }
}


enum Season02 {
  //定义了四个对象
  SPRING ("春", "温暖"),
  WINTER ("冬", "寒冷"),
  AUTUMN ("秋", "凉爽"),
  SUMMER ("夏", "炎热");
  private String name;
  private String desc;

  private Season02(String name, String desc) {
    this.name = name;
    this.desc = desc;
  }

  public String getName() {
    return name;
  }

  public String getDesc() {
    return desc;
  }

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

}

1.8 常用类

1.8.1 包装类

1. 包装类(WrapperType)的分类:

  • 针对八种基本数据类型相应的引用类型-包装类

  • 有了类的特点,就可以调用类的方法

    基本数据类型包装类父类
    booleanBooleanObject
    charCharacterObject
    byteByteNumber
    shortShortNumber
    intIntegerNumber
    longLongNumber
    floatFloatNumber
    doubleDoubleNumber

    Numberboolean-char

2. 包装类和基本数据类型转换

以 int 和 Interger 为例

public class Integer01 {
  public static void main(String[] args) {
    //演示 int <--> Integer 的装箱和拆箱
    //jdk5前是手动装箱和拆箱
    //手动装箱 int -> Integer
    int n = 100;
    Integer integer = new Integer(n);
    Integer integer1 = Integer.valueOf(n);
    //手动拆箱 Integer -> int
    int i = integer.intValue();

    //jdk5后,自动装箱和自动拆箱
    int n2 = 200;
    //自动装箱 int->Integer
    Integer integer2 = n2; //底层使用的是 Integer.valueOf(n2)
    //自动拆箱 Integer->int
    int n3 = integer2;
  }
}

String 和 Interger 转换

public class WrapperVsString {
  public static void main(String[] args) {
    // 包装类(Integer)->String
    Integer i =100;//自动装箱
    // 方式一
    String str = i+"";
    //方式二
    String str2 = i.toString();
    //方式三
    String str3 = String.valueOf(i);

    // String -> 包装类(Integer)
    String str4 = "4545";
    Integer i1 = Integer.parseInt(str4);
    Integer i2 = new Integer(str4);
  }
}

1.8.2 String

1.8.2.1 字符串类型的特点

  1. 不能被继承,因为Stringfinal修饰的类

  2. 字符串对象是常量对象,一旦被创建就不能修改,一旦修改了就是新对象

  3. 因为字符串对象是常量对象,那么可以共享,字符篡改常量对象是再常量池中,常量池所在位置

    • JDK1.6 方法区
    • JDK1.7
    • JDK1.8 元空间
  4. 任何字符串字面量都是String的对象

  5. 字符串底层使用字符串数组存储

  6. 字符串数组是private final修饰符

api-String

1.8.2.2 拼接和比较

  1. 创建对象个数
// 一个,在常量池
  String str1 = "hello";
  String str2 = "hello";
// 三个,一个在常量池,两个在堆中
String str3 = new String("hello");
String str3 = new String("hello");
  1. 拼接和比较原则:+两边都是常量,结果也是常量,+两边有一个是变量,结果就不是常量。在堆中,如果结果使用了 intern(),那么是常量
public class String01 {
  public static void main(String[] args) {
    test3();
  }

  public static void test3(){
    String str1 = "hello";
    String str2 = "java";
    String str3 = "hellojava";
    String str4 = "hello" + "java";//常量与常量拼接,还是常量
    String str5 = "hello" + str2;//常量与变量拼接,结果在堆中
    String str6 = str1 + str2;//变量与变量拼接,结果也在堆中

    System.out.println("str3 == str4  " + (str3 == str4));//true
    System.out.println("str3 == str5  " + (str3 == str5));//false
    System.out.println("str3 == str6  " + (str3 == str6));//false

    final String str7 = "hello";
    final String str8 = "java";
    String str9 = str7 + str8;//常量与常量拼接
    System.out.println("str3 == str9   " + (str3 == str9));//true

    String str10 = (str1 + str2).intern();//intern()的结果放常量池
    System.out.println(str3 == str10);//true
  }
}

1.8.2.3 常用的方法

基础方法
  1. int length() 返回字符串的长度,即字符的个数
String str = "SAGHIsfa";
System.out.println("字符串长高度=" + str.length());
  1. 字符串的比较

    • boolean equals(String other)

      • this 和 other 进行内容比较
      • 对 Object 的 equals 进行重写
      • 严格区分大小写
    • boolean equalsIgnoreCase(String anotherString)

      • this 和 anotherString 进行内容比较
      • 不区分大小写
    • 自然排序 public int compareTo(String anotherString) 如果是 ASCII 范围内,按照 ASCII 值的顺序

    • 定制排序 java.text.Collator

  2. String trim() 去掉前后空格

  3. 转大小写

    • String toUpperCase()
    • String toLowerCase()
String str = "SAGHIsfa";
System.out.println("toUpperCase()转换大写=" + str.toUpperCase());
System.out.println("toLowerCase()转换小写=" + str.toLowerCase());
  1. 是否是空字符串

    • "".equals(字符串)
    • isEmpty()
String str = "";
String Str1 = new String("runoob");
String Str2 = Str1;
System.out.println("isEmpty=" + str.isEmpty());
System.out.println("isEmpty=" + Str1.equals(Str2));
和字节相关
  1. 编码:把字符串转成字节数组

    • byte[] getBytes() 平台默认编码方式进行编码
    • byte[] getBytes(字符编码方式) 按照指定的编码方式
  2. 解码:把字节数组转成字符串

    • new String(byte[])
    • new String(byte[],int offset, int length)
    • new String(byte[], 字符解码方式)
  3. 乱码

    • 编码方式与解码方式不一致
    • 缺字节
和字符相关
  1. 把字符串转字符数组
    • char[] toCharArray()
  2. 把字符数组转字符串
    • new String(char[])
    • new String(char[],int offset, int count )
  3. 取指定位置的字符
    • char charAt(index)
是否以 xxx 开头和结尾
  1. boolean startsWith(xx)
  2. boolean endsWith(xx)
String str = "15078s";
System.out.println(str.startsWith("18"));
System.out.println(str.endsWith("8s"));
字符串截取
  1. String subString(int start) 从[start,end]
  2. String subString(int start, int end) 从[start,end)
String Str = new String("This is text");
System.out.println(Str.substring(4) );
System.out.println(Str.substring(4, 10) );
字符串拆分

String[] split(支持正则)

String address="上海@上海市@闵行区@吴中路";
String[] splitAdd = address.split("@");
System.out.println(splitAdd[0]);
字符串查找
  1. 是否包含
    • boolean contains(子串)
  2. 查找索引位置
    • int indexOf(xxx) 如果存在返回索引,如果不存在返回-1
    • int lastIndexOf(xx)
String str = "185787878";
//是否包含
System.out.println(str.contains("1"));
//查找索引位置
if (str.indexOf(4) > -1) {
  System.out.println("存在");
} else {
  System.out.println("不存在");
}
System.out.println("lastIndexOf=" + str.lastIndexOf(6));
字符串替换
  • String replace(目标子串, 新子串)
  • String replaceAll(目标子串, 新子串) 支持正则
  • String replaceFirst(目标子串, 新子串)

1.8.2.4 字符串反转

public class StringReverseDemo {
  public static void main(String[] args) {
    String str = "abcdefg";
    String res = null;
    try {
      res = reverse(str, 0, 3);
    } catch (Exception e) {
      System.out.println(e.getMessage());
      return;
    }
    System.out.println(res);
  }

  public static String reverse(String str, int start, int end) {
    //对输入的参数做校验
    if (!(str != null && start >= 0 && end > start && end < str.length())) {
      throw new RuntimeException("参数不正确");
    }
    char[] chars = str.toCharArray();
    char temp = ' ';//交换辅助变量
    for (int i = start, j = end; i < j; i++, j--) {
      temp = chars[i];
      chars[i] = chars[j];
      chars[j] = temp;
    }
    //使用 chars 重新构建 String 返回
    return new String(chars);
  }
}

1.8.3 StringBufferStringBuilder

  • StringBufferJDK1.0 就有,是线程安全的
  • StringBuilderJDK1.5 引入,是线程不安全

api-stringbuffer

和 String 的区别

  1. String 对象是常量对象,是不能修改的,StringBufferStringBuilder 是字符串缓冲区,可变字符序列,是可以修改的
    • String 一旦涉及修改就会产生新的 String 对象
    • StringBufferStringBuilder 不会产生新的 StringBufferStringBuilder 对象
  2. 赋值方式:只有 String 支持,String str = "xxx";
  3. 拼接
    • String 支持+每一次拼接产生新对象,浪费时间和空间
    • append 不会产生新对象

常用方法

  1. 拼接
    • append(xx)
    • 支持连写(类似于 js 的原型链) sBuilder.append(xx).append(yyy).append(zzz)...
  2. 插入 insert(index,xx)
  3. 删除 delete(start, end)
  4. 替换 setCharAt(index, char)
  5. 反转 reverse()
StringBuilder sb = new StringBuilder(10);
//拼接
sb.append("Java,").append("javaScript,").append("MySQl,").append("CSS,").append("HTML");
System.out.println(sb);
//插入
sb.insert(0, "Spring,");
System.out.println(sb);
//替换
sb.setCharAt(0,'1');
//反转
sb.reverse();
System.out.println(sb);

1.8.4 Math

Math 类用于执行基本数学运算的方法,如初等指数、对数、平方根和三角函数 方法一般为静态方法

public class Mathethod {
  public static void main(String[] args) {
    //1.abs绝对值
    int abs = Math.abs(-9);
    System.out.println(abs);
    //2.pow求幂 4的2次方
    double pow = Math.pow(4, 2);
    System.out.println(pow);
    //3.ceil向上取整,返回>=该参数的最小整数
    double ceil = Math.ceil(-3.5255);
    System.out.println(ceil);
    //4.floor向下取整,返回<=该参数的最大整数
    double floor = Math.floor(-4.999);
    System.out.println(floor);
    //5.round四舍五入, Math.floor(该参数+0.5)
    long round = Math.round(-50.001);
    System.out.println(round);
    //6.sqrt求开方
    double sqrt = Math.sqrt(10);
    System.out.println(sqrt);
    //7.random求随机数,然会的是 0<= x < 1之间的一个随机小数
    //Math.random() * (b - a) 返回的就是 a <= 数 < b-a
    //求a-b之间的随机数:(int)(a) <= x <= (int)(a + Math.random() * (b - a + 1))
    for (int i = 0; i< 10; i++) {
      System.out.println(Math.random());
      System.out.println((int)(2+Math.random() * (7-2 + 1)));
    }
    //8.max/min返回最大值和最小是
    int max = Math.max(1,9);
    int min = Math.min(45,89);
    System.out.println("min=" + min + "max=" + max);
  }
}

1.8.5 Date

JDK1.8 之前的时间

java.util.Date
  1. 两个构造器
  • new Date() 获取当前系统日期时间
  • new Date(long 毫秒) 根据毫秒数获取日期时间对象
  1. 把某个日期对象转换成毫秒数 long getTime()
import java.util.Date;

Date d1 =  new Date();
System.out.println("获取当前系统时间:" + d1);
System.out.println("根据毫秒数获取日期时间对象:" + new Date(9234567));

System.out.println("把某个日期对象转换成毫秒数"+ d1.getTime());
java.lang.System
// long System.currentTimeMillis()
System.out.println(System.currentTimeMillis());
java.util.Calendar

相关连接:https://blog.csdn.net/weixin_42472040/article/details/100108434

  1. 获取实例对象
    • Calendar.getInstance() 获取平台默认
    • Calendar.getInstance(时区,语言环境)
  2. get(常量字段)
    • YEAR
    • MONTH
import java.text.SimpleDateFormat;
import java.util.Calendar;

public class CalendarDemo {
  public static void main(String[] args) {
    Calendar ca = Calendar.getInstance();

    //get(int field) 返回指定日历字段的值
    //获得年、月、日、时、分、秒、毫秒
    ca.get(Calendar.YEAR);
    ca.get(Calendar.MONTH);
    ca.get(Calendar.DATE);
    ca.get(Calendar.HOUR_OF_DAY);
    ca.get(Calendar.MINUTE);
    ca.get(Calendar.SECOND);
    ca.get(Calendar.MILLISECOND);
    //当前时间是所在当前月的第几个星期(日历式的第几周)
    ca.get(Calendar.WEEK_OF_MONTH);

    //当前时间是所在当前年的第几个星期(日历式的第几周)
    ca.get(Calendar.WEEK_OF_YEAR);

    //当前时间是所在当前月的第几个星期,以月份天数为标准,一个月的1号为第一周,8号为第二周
    ca.get(Calendar.DAY_OF_WEEK_IN_MONTH);
    //一周7天当中,当前时间是星期几, 返回结果为1-7
    ca.get(Calendar.DAY_OF_WEEK);

    //一年中的第几天
    ca.get(Calendar.DAY_OF_YEAR);
    //判断当前时间是AM,还是PM,若是AM返回结果为0,若是PM返回结果为1
    ca.get(Calendar.AM_PM);
    // set方法设置日历字段的值
    ca.set(Calendar.YEAR,2019);
    ca.set(Calendar.MONTH,0);
    ca.set(Calendar.DATE,1);
    ca.set(Calendar.HOUR_OF_DAY,0);
    ca.set(Calendar.MINUTE,0);
    ca.set(Calendar.SECOND,0);
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    System.out.println(sdf.format(ca.getTime()));
    System.out.println("获取平台默认=" + ca);
  }
}
java.text.DateForamt 及其 java.text.SimpleDateFormat
  1. 构造器 SimpleDateFormat sf = new SimpleDateFormat("模式");
  2. 把日期转成字符串 public final String format(Date date)
  3. 把字符串转成日期 public Date parse(String source)

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

public class DateDemo {
  public static void main(String[] args) throws ParseException {
    Date d1 =  new Date();
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss E");
    String format  = sdf.format(d1);
    String format1  = sdf1.format(d1);
    System.out.println("日期格式化:" + format);
    System.out.println("日期格式化:" + format1);

    //将 String 格式的时间转换成 Date
    String s = "2019年05月20日 12:41:23 星期五";
    Date parse = sdf1.parse(s);
    System.out.println("parse=" + parse);
  }
}

JDK1.8 日期时间

相关包
  1. java.time.*
  2. java.time.chrono
  3. java.time.format
  4. java.time.temporal
  5. java.time.zone
本地日期时间

对应旧版本的 java.util.Calendar

  1. java.time.LoaclDate 日期/年月日
  2. java.time.LocalTime 时间/时分秒
  3. java.time.LocalDateTime 日期时间/年月日时分秒
  4. 方法列表 date
public static void main(String[] args) {

    LocalDateTime ldt = LocalDateTime.now();
    System.out.println(ldt);
    System.out.println("年" + ldt.getYear());
    System.out.println("月" + ldt.getMonth());
    System.out.println("月" + ldt.getMonthValue());
    System.out.println("日" + ldt.getDayOfMonth());
    System.out.println("时" + ldt.getHour());
    System.out.println("分" + ldt.getMinute());
    System.out.println("秒" + ldt.getSecond());

    //可以获取年月日
    LocalDate now = LocalDate.now();
    System.out.println("可以获取年月日" + now);
    //可以获取时分秒
    LocalDateTime now2 = LocalDateTime.now();
    System.out.println("可以获取时分秒" + now2);
  }
日期时间格式化
  1. java.time.format.DateTimeFormatter

    • format 把日期时间对象转字符串
    • parse 把字符串转日期时间对象
  2. 三种形式获取 DateTimeFormatter 对象

    • 标准格式的常量对象
    LocalDate date = LocalDate.now();
     System.out.println(date.format(DateTimeFormatter.ISO_DATE));
    
     DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE;
     String format = formatter.format(LocalDate.now());
     System.out.println(format);
    
    • style
    //FormatStyle.FULL 日期
    //FormatStyle.LONG 日期、时间、日期时间
    //FormatStyle.MEDIUM 日期、时间、日期时间
    //FormatStyle.SHORT 日期、时间、日期时间
    DateTimeFormatter dt = DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL);
    
    • 自定义格式
    DateTimeFormatter op = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    System.out.println(op.format(LocalDateTime.now()));
    
日期的间隔 Period

具体相差的年月日

import java.time.Period;
import java.time.LocalDate;

LocalDate ld = LocalDate.parse("2019-11-17");
LocalDate ld2 = LocalDate.parse("2022-01-05");
Period p = Period.between(ld, ld2);
System.out.println("相差"+p.getYears()+ "年" + p.getMonths() + "月" + p.getDays() + "天");
时间的间隔 Duration

Duration 这个类以秒和纳秒为单位建模时间的数量或数量

Instant inst1 = Instant.now();
System.out.println("当前时间戳 : " + inst1);
Instant inst2 = inst1.plus(Duration.ofSeconds(10));
System.out.println("增加之后的时间 : " + inst2);
System.out.println("相差毫秒 : " + Duration.between(inst1, inst2).toMillis());
System.out.println("相毫秒 : " + Duration.between(inst1, inst2).getSeconds());

1.8.6 System

  1. exit 退出当前程序
  2. arraycopy 复制数组元素,比较适合底层调用,一般使用 Arrays.copyOf 完成数组复制
  3. currentTimeMillens 返回当前时间距离 1970-1-1 的毫秒数
  4. gc 运行垃圾回收机制 System.gc()open in new window
public class SystemDemo {
  public static void main(String[] args) {
    int[] arr = {10,5,1,8,6,2,4};
    int[] dest = new int[3];
    /**
     *2.arraycopy 复制数据
     src –  源数组
     srcPos – 从源数组的哪个索引位置开始
     dest – 目标数组,即把源数组的数据,拷贝到哪个数组
     destPos – 把源数组的数据拷贝到目标数组的哪个索引
     length – 从源数组拷贝多少数据到目标数组
     */
    System.arraycopy(arr, 0, dest, 0,3);
    System.out.println(Arrays.toString(dest));
    //1.退出当前程序 0 表示程序正常退出状态
    System.exit(0);
    //3.currentTimeMillis 获取距离1970-1-1时间戳
    System.out.println(System.currentTimeMillis());
  }
}

1.8.7 Arrays

Arrays 类位于 java.util 包中,主要包含了操纵数组的各种方法,使用时导包:import java.util.Arrays 方法一般都是静态的

常用方法

  1. Arrays.toString(Object[] array) 返回数组的字符串形式
int[] number = {2,4,5,7,8,10,1};
System.out.println(Arrays.toString(number)); //[2, 4, 5, 7, 8, 10, 1]
  1. Arrays.sort(Object[] array) 对数组按照升序排序
int[] number = {2,4,5,7,8,10,1};
Arrays.sort(number);
for (int i:number) {
  System.out.println(i+"");
}
  1. Arrays.sort(Object[] array, int from, int to) 对数组元素指定范围进行排序(排序范围是从元素下标为 from,到下标为 to-1 的元素进行排序
int[] number = {7,6,3,5,9,8,10,1};
//对前四位元素进行排序
Arrays.sort(number, 0, 4);
for (int i:number) {
  System.out.println(i+"");
}
  1. Arrays.fill(Object[] array, Object object) 可以为数组元素填充相同的值
int[] number = {7,6,3,5,9,8,10,1};
//替换数组中全部元素
Arrays.fill(number, 0);
for (int i:number) {
  System.out.println(i+"");
}
  1. Arrays.fill(Object[] array,int from,int to,Object object) 对数组的部分元素填充一个值,从起始位置到结束位置,取头不取尾
int[] number = {7,6,3,5,9,8,10,1};
//替换数组中索引值0-3的数值替换成12
Arrays.fill(number, 0,3,12);
for (int i:number) {
  System.out.println(i+"");
}
  1. Arrays.binarySearch(Object[] array,int value) 通过二分搜索法进行查找,要求必须排好序,存在返回索引,不存在 return -(low(数值应该在的位置) + 1)
Integer[] arr= {1,4,45,788,1585};
/**
  * binarySearch 通过二分搜索法进行查找,要求必须排好序
  * 1.使用 binarySearch 二叉查找法
  * 2.要求该数组是有序的,如果该数组是无序的,不能使用 binarySearch
  * 3.如果数组中不存在该元素,则返回 return -(low(数值应该在的位置) + 1)
  */
int index= Arrays.binarySearch(arr, 1);
System.out.println(index);
  1. Arrays.copyOf(Object[], int arr.length) 数组元素复制
/**
  * copyOf 数组元素复制
  * 1.从数组 arr 中拷贝 arr.length 个元素到 newArray 数组中
  * 2.如果拷贝的长度 > arr.length 就会在新数组后面增加 null
  * 3.如果拷贝的长度 < arr.length 就会抛出异常 java.lang.NegativeArraySizeException
  * 4.该方法的底层使用的是 System.arraycopy()
  */
Integer[] arr= {1,4,45,788,1585};
Integer[] newArray = Arrays.copyOf(arr, arr.length);
System.out.println("拷贝结束的数组="+Arrays.toString(newArray));
  1. Arrays.equals(Object[], Object1[]) 比较两个数组是否完全一致,一致返回 true 不一致返回 false
Integer[] arr= {1,4,45,788,1585};
Integer[] newArray2 = {1,4,45,788,185};
System.out.println(Arrays.equals(arr, newArray2));
  1. Arrays.asList 将一组值,转换成 list
/**
  * asList 将一组值,转换成list
  * 1.asList方法会将 (4,7,2,1,5, 9, 10)数据转换成一个List集合
  * 2.返回的 asList 编译类型 List(接口)
  * 3.asList 运行类型 java.util.Arrays#Arraylist 是 Arrays 的内部类
  */
List asList = Arrays.asList(4,7,2,1,5, 9, 10);
System.out.println("asList=" + asList);

结合冒泡+定制示例

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

public class ArraysSortCustom {
  public static void main(String[] args) {
    int[] arr = {12,54,8,81,1,2,7,9};
    int[] arr2 = {0,5,2,1,8,9,3,6};
    int[] result = bubbleSort(arr2);
    System.out.println("普通冒泡排序=" + Arrays.toString(result));

    //使用匿名内部类
    bubbleSort2(arr, new Comparator() {
      @Override
      public int compare(Object o1, Object o2) {
        int i1 = (Integer) o1;
        int i2 = (Integer) o2;
        return i2-i1; //i2-i1 or i1 - i2
      }
    });
    System.out.println("定制排序=" + Arrays.toString(arr));
  }

  /**
   * 使用冒泡排序
   * @param arr
   */
  public static int[] bubbleSort(int[] arr) {
    int temp = 0;
    for (int i = 0; i < arr.length-1; i++) {
      for (int j = 0; j < arr.length-1-i; j++) {
        //从小到大
        if (arr[j] > arr[j+1]){
          temp = arr[j];
          arr[j] = arr[j+1];
          arr[j+1] = temp;
        }
      }
    }
    return arr;
  }

  /**
   * 结合冒泡+定制
   * @param arr
   * @param c
   */
  public static void bubbleSort2(int[] arr, Comparator c) {
    int temp = 0;
    for (int i = 0; i < arr.length-1; i++) {
      for (int j = 0; j < arr.length-1-i; j++) {
        //数组的排序由c.compare(arr[j], arr[j + 1])的返回值决定
        if (c.compare(arr[j], arr[j + 1]) > 0){
          temp = arr[j];
          arr[j] = arr[j+1];
          arr[j+1] = temp;
        }
      }
    }
  }
}

1.8.8 BigInteger

应用场景:BigInteger适合保存比较大的整型,Java中可以使用BigInteger操作大整数,也可以转换进制。如果在操作的时候一个整型数据已经超过了整数的最大类型长度long的话,则此数据就无法装入,所以,此时要使用BigInteger类进行操作。这些大数都会以字符串的形式传入

构造器

BigInteger(数值,需要转换的进制)

//在构造将函数时,把radix进制的字符串转化为BigInteger
String str = "1011100111";
int radix = 2;  //radix代表二进制,为下一行代码中的参数radix赋值
BigInteger interNum1 = new BigInteger(str,radix);//743
System.out.println("二进制转换成十进制" + interNum1);

//743的十进制转换成2进制
String string1 = new BigInteger("743", 10).toString(2);
System.out.println("十进制的743转换成二进制=" + string1);//1011100111

String string2 = new BigInteger("66", 16).toString(8);
System.out.println("66的16进制转换成8进制=" + string2);//146

方法描述

BigInteger abs() 返回大整数的绝对值 BigInteger add(BigInteger val) 返回两个大整数的和 BigInteger and(BigInteger val) 返回两个大整数的按位与的结果 BigInteger andNot(BigInteger val) 返回两个大整数与非的结果 BigInteger divide(BigInteger val) 返回两个大整数的商 double doubleValue() 返回大整数的 double 类型的值 float floatValue() 返回大整数的 float 类型的值 BigInteger gcd(BigInteger val) 返回大整数的最大公约数 int intValue() 返回大整数的整型值 long longValue() 返回大整数的 long 型值 BigInteger max(BigInteger val) 返回两个大整数的最大者 BigInteger min(BigInteger val) 返回两个大整数的最小者 BigInteger mod(BigInteger val) 用当前大整数对 val 求模 BigInteger multiply(BigInteger val) 返回两个大整数的积 BigInteger negate() 返回当前大整数的相反数 BigInteger not() 返回当前大整数的非 BigInteger or(BigInteger val) 返回两个大整数的按位或 BigInteger pow(int exponent) 返回当前大整数的 exponent 次方 BigInteger remainder(BigInteger val) 返回当前大整数除以 val 的余数 BigInteger leftShift(int n) 将当前大整数左移 n 位后返回 BigInteger rightShift(int n) 将当前大整数右移 n 位后返回 BigInteger subtract(BigInteger val)返回两个大整数相减的结果 byte[] toByteArray(BigInteger val)将大整数转换成二进制反码保存在 byte 数组中 String toString() 将当前大整数转换成十进制的字符串形式 BigInteger xor(BigInteger val) 返回两个大整数的异或

BigInteger a = new BigInteger("6457878");
BigInteger b = new BigInteger("123");
//加减乘除-------------------------------
System.out.println("加 a+b=" + a.add(b));
System.out.println("减 a-b=" + a.subtract(b));
System.out.println("乘 a*b=" + a.multiply(b));
System.out.println("除 a/b=" + a.divide(b));
//取余-------------------------------
BigInteger c = b.remainder(a);
System.out.println("取余=" + c);
//最大公约数-------------------------------
System.out.println("最大公约数=" + a.gcd(b));
//绝对值---------------------------------
BigInteger d = new BigInteger("-12");
System.out.println("绝对值=" + d.abs());
//取反---------------------------------
System.out.println("取反=" + d.negate());
//幂
System.out.println("幂=" + d.pow(2));

1.8.9 BigDecimal

应用场景:BigDecimal适合保存比精度比较高的浮点型,Javajava.math包中提供的 API 类BigDecimal,用来对超过 16 位有效位的数进行精确的运算。双精度浮点型变量 double 可以处理 16 位有效数。在实际应用中,需要对更大或者更小的数进行运算和处理。floatdouble只能用来做科学计算或者是工程计算,在商业计算中要用java.math.BigDecimal

构造器

  1. BigDecimal(int)创建一个具有参数所指定整数值的对象。
  2. BigDecimal(double)创建一个具有参数所指定双精度值的对象。 //不推荐使用
// 不推荐使用BigDecimal(double)创建双精度值对象,原因是会出现计算缺失精度
public static void main(String[] args) {
  BigDecimal inStr = new BigDecimal("22");
  BigDecimal doubleStr = new BigDecimal(1.1444411);
  System.out.println(inStr); //22
  System.out.println(doubleStr); //1.1444411000000000999676785795600153505802154541015625
}
  1. BigDecimal(long)创建一个具有参数所指定长整数值的对象。
  2. BigDecimal(String)创建一个具有参数所指定以字符串表示的数值的对象。//推荐使用
//String 构造方法是完全可预知的:写入 newBigDecimal("0.1") 将创建一个 BigDecimal,它正好等于预期的 0.1。因此,比较而言,通常建议优先使用String构造方法
public static void main(String[] args) {
  BigDecimal inStr = BigDecimal.valueOf(3.66666);
  BigDecimal doubleStr = new BigDecimal(Double.toString(1.1444411));
  System.out.println(inStr); //3.66666
  System.out.println(doubleStr); //1.1444411
}

方法描述

  1. add(BigDecimal)BigDecimal 对象中的值相加,然后返回这个对象。
  2. subtract(BigDecimal)BigDecimal 对象中的值相减,然后返回这个对象。
  3. multiply(BigDecimal)BigDecimal 对象中的值相乘,然后返回这个对象。
  4. divide(BigDecimal)BigDecimal 对象中的值相除,然后返回这个对象。
  5. toString() 将 BigDecimal 对象的数值转换成字符串。
  6. doubleValue() 将 BigDecimal 对象中的值以双精度数返回。
  7. floatValue() 将 BigDecimal 对象中的值以单精度数返回。
  8. longValue() 将 BigDecimal 对象中的值以长整数返回。
  9. intValue() 将 BigDecimal 对象中的值以整数返回。
public static void main(String[] args) {
    //加减乘除--------------------------
    BigDecimal first = new BigDecimal("36");
    BigDecimal second = new BigDecimal("12");
    System.out.println("加add  a+b= " + first.add(second));
    System.out.println("减subtract  a-b= " + first.subtract(second));
    System.out.println("乘multiply  a*b= " + first.multiply(second));
    System.out.println("除divide  a/b= " + first.divide(second));

    double a = 1.223;
    float b = 3.3f;
    //double 类型的数据,使用Double.toString(a) 将 double类型转成String类型
    BigDecimal firsts = new BigDecimal(Double.toString(a));
    //float 同理
    BigDecimal seconds = new BigDecimal(Double.toString(b));
    System.out.println("乘法" + firsts.multiply(seconds));
    //保留两位小数,四舍五入:divide(BigDecimal, 保留小数点后几位, 舍入模式)
    //果进行除法运算的时候,结果不能整除,有余数,这个时候会报java.lang.ArithmeticException: 这边我们要避免这个错误产生,在进行除法运算的时候,针对可能出现的小数产生的计算,必须要多传两个参数  divide(BigDecimal,保留小数点后几位小数,舍入模式)
    System.out.println("保留两位小数,四舍五入:" + firsts.divide(seconds, 2, BigDecimal.ROUND_HALF_UP));

  }

需要对 BigDecimal 进行截断和四舍五入可用 setScale 方法,例:

BigDecimal a1 = new BigDecimal("2.33661");
a1 = a1.setScale(2, RoundingMode.HALF_UP);
System.out.println(a1);

舍入模式

  1. ROUND_CEILING向正无穷方向舍入
  2. ROUND_DOWN向零方向舍入
  3. ROUND_FLOOR向负无穷方向舍入
  4. ROUND_HALF_DOWN向(距离)最近的一边舍入,除非两边(的距离)是相等,如果是这样,向下舍入, 例如 1.55 保留一位小数结果为 1.5
  5. ROUND_HALF_EVEN向(距离)最近的一边舍入,除非两边(的距离)是相等,如果是这样,如果保留位数是奇数,使用 ROUND_HALF_UP,如果是偶数,使用 ROUND_HALF_DOWN
  6. ROUND_HALF_UP向(距离)最近的一边舍入,除非两边(的距离)是相等,如果是这样,向上舍入, 1.55 保留一位小数结果为 1.6,也就是我们常说的“四舍五入”
  7. ROUND_UNNECESSARY计算结果是精确的,不需要舍入模式
  8. ROUND_UP向远离 0 的方向舍入

1.9 集合类

1.9.1 概念

集合是一个容器,用来封装对象的容器,集合主要分为两组(单列集合、双列集合)

  1. Collection 接口有两个重要的子接口 List Set 他们的实现子类都是单列集合
  2. Map 接口实现的子类 是双列结合,存放值为 key:value

数据结构

物理结构
  1. 数组也是一个容器
    • 缺点:长度固定、无法直接获取有效元素的个数
    • 在实际开发中,基本数据类型一般用数组,引用数据类型一般用集合
    • 数组是依据“数组名+下标”来确定某个元素,数组名中存储的是数组的首地址
  2. 链表:不仅仅存储数据,还有存储前/后元素的引用
逻辑结构
  1. 动态数组:底层是数组,可以通过扩容的方式实现动态数组

  2. 链表:结合 Node

    • 双向链表
     class Node{
       Node pre;
       Object data;
       Node next;
     }
    
    • 单向链表
     class Node{
       Object data;
       Node next;
     }
    
  3. 树: 经典的代表-二叉树

class Node{
	Node left;
	Object data;
	Node right;
}
  1. 栈:先进后出、添加的顺序、出栈的顺序
  2. 队列:先进先出、添加的顺序、出队列的顺序

1.9.2Collection

java.util.Collection 是一个接口,是个根接口,Collection没有直接的实现类,它有两个子接口,分别是java.util.Listjava.util.Set

java.util.List

有序的(添加顺序),可重复的

java.util.Vector动态数组
  1. JDK1.0 就有,最早,支持 Enumeration 迭代方式,当然也支持 Iterator,foreach

  2. 线程安全的

  3. 扩容算法(默认初始容量是 10) 如果没有指定扩容参数,那么默认扩大为原来的 2 倍 如果指定了扩容参数,那么就按照指定参数值进行扩容

  4. 区别 Vector 和 ArrayList 的区别 image-20220521153709258

  5. 源码分析


import java.util.Vector;

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

    Vector vector = new Vector();
    for (int i = 0; i <10; i++) {
      vector.add(i);
    }
    vector.add(100);

    /**
     * 无参构造器
     * 1. new Vector() 底层源码
     *  public Vector() {
     *      this(10);
     *  }
     * 补充:如果是有参构造 Vector vector = new Vector(8)
     * 走的方法是:
     * public Vector(int initialCapacity) {
     *         this(initialCapacity, 0);
     *     }
     *
     *  2. vector.add(i)
     *  2.1 下面这个方法就是添加数据到 vector 集合
     *  public synchronized boolean add(E e) {
     *         modCount++;
     *         // 2.2 确认是否需要扩容
     *         ensureCapacityHelper(elementCount + 1);
     *         elementData[elementCount++] = e;
     *         return true;
     *     }
     *   2.2确认是否需要扩容 条件:minCapacity - elementData.length > 0
     *   private void ensureCapacityHelper(int minCapacity) {
     *         // overflow-conscious code
     *         if (minCapacity - elementData.length > 0)
     *             grow(minCapacity);
     *     }
     *    2.3 如果需要的数组大小不够用,就扩容,扩容的算法 ,如下:(扩容两倍的算法)
     *    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?  capacityIncrement : oldCapacity);

     *      private void grow(int minCapacity) {
     *         // overflow-conscious code
     *         int oldCapacity = elementData.length;
     *         int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
     *                                          capacityIncrement : oldCapacity);
     *         if (newCapacity - minCapacity < 0)
     *             newCapacity = minCapacity;
     *         if (newCapacity - MAX_ARRAY_SIZE > 0)
     *             newCapacity = hugeCapacity(minCapacity);
     *         elementData = Arrays.copyOf(elementData, newCapacity);
     *     }
     */
  }
}
java.util.ArrayList 动态数组

java.util.ArrayList 动态数组,相对 Vector 来说新一点、只支持 Iteratorforeach,线程是不安全的,扩容算法可以扩大为原来的 1.5 倍 ArrayList 特征

  • 可调整大小的数组实现

  • <E>: 是一种特殊的数据类型、泛型

    方法名说明
    pubilc ArrayList()创建一个空的集合对象
    add(E element)将指定的元素追加到刺激和的末尾
    add(int index, E element)在此集合中的指定位置插入指定的元素
    addAll(int index, Collection<?extends E> c)
    remove(Object o)删除指定的元素,返回删除是否成功
    remove(int index)删除指定索引位置的元素,返回被删除的元素
    indexOf(Object o)查找元素
    lastIndexOf(Object o)查找集合最后一个元素
    get(int index)返回指定索引的元素
    set(int index, E element)修改指定索引处的元素,返回被修改的元素
    size()返回集合中的元素个数
    subList(int fromIndex, int toIndex)截取集合长度,返回从索引 formIndex(包括) 到 索引 toIndex(不包括)元素的 List 合集
import java.util.ArrayList;

public static void main(String[] args) {
  ArrayList<String> array = new ArrayList<>();
  // 集合添加
  array.add("李四");
  array.add("学习");
  // 在集合指定索引位置添加指定元素
  array.add(1, "java");

  // 根据索引删除,返回被删除的元素
  array.remove(1);

  // 修改指定索引位置的元素,返回被修改的元素
  array.set(0, "张三");
  // 获取指定索引位置的元素
  array.get(0);

  System.out.println(array);
}
  • 集合练习
package com.arr;
import java.util.ArrayList;

class Student {
  private String name;
  private int age;

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

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

  public int getAge() {
    return age;
  }

  public String getName() {
    return name;
  }
}

public class arr01 {
  public static void main(String[] args) {
    // 创建集合对象
    ArrayList<Student> students = new ArrayList<Student>();
    // 创建学生对象
    Student s1 = new Student("张三", 30);
    Student s2 = new Student("李四", 22);
    // 添加学生对象到集中中
    students.add(s1);
    students.add(s2);
    // 循环遍历
    for (int i = 0; i < students.size(); i++) {
      Student s = students.get(i);
      System.out.println(s.getName() + s.getAge());
    }
  }
}
ArrayList 注意事项
  1. permits all elements,includiing null, ArrayList 可以加入 null 并且可以是多个
  2. ArrayList 是由数组来实现数存储的
  3. ArrayList 基本等同于 Vector 除了 ArrayList 是线程不安全(执行效率高),在多线程情况下不建议使用 ArrayList
⭐ArrayList 底层结构和源码分析
  1. ArrayList 中维护了一个 Object 类型的数组 elemenetData.transient Object[] elemetnData
  2. 当创建对象时,如果使用的是无参构造器,则初始 elemenetData 容量为 0(JDK7 是 10)
  3. 当添加元素时,先判断是否扩容,如果需要扩容,则调用 grow 方法,否则直接添加元素到合适位置
  4. 如果使用的是无参构造器,如果第一次添加,需要扩容的化,则扩容 elemenetData 为 10,如果需要再次扩容 elemenetData 为 1.5 倍
  5. 如果使用的是指定容量 capacity 的构造器,则初始 elemenetData 容量为 capactiy
  6. 如果使用的是指定容量 capacity 的构造器,如需要扩容,则直接扩容 elemenetData 为 1.5 倍
java.util.LinkedList 双向链表
  1. 相对于动态数组来说的优势,在插入和删除操作比较频繁时,链表的方式效率更高
  2. 相对于动态数组来说的劣势,如果根据索引信息来查找的话,每次都要现统计
LinkedList 的底层操作机制
  1. LinkedList 底层维护了一个双向链表
  2. LinkedList 中为维护了两个属性 first 和 last 分别执行首节点和尾节点
  3. 每个节点(Node 对象)里面油维护了 prev、next、item 三个属性,其中通过 prev 指向前一个,next 指向后一个节点,最终实现双向链表 双向链表
  4. 所以 LinkedList 的元素的删除和添加,不是通过数组完成的,相对效率较高
  5. 模拟一个简单的双向链表
public class LinkedList01 {
  public static void main(String[] args) {
    //模拟一个简单的双向链表
    Node ww = new Node("王五");
    Node ls = new Node("李四");
    Node zs = new Node("张三");
    //连接三个节点,形成双向链表
    //ww->ls->zs
    ww.next = ls;
    ls.next = zs;
    //zs->ls->ww
    zs.prev = ls;
    ls.prev = ww;
    // 让 first 引用指向 ww,就是双向链表的头节点
    Node first = ww;
    // 让 last 引用指向 zs,就是双向链表的尾节点
    Node last = zs;
    // 演示,从头到尾遍历
    System.out.println("从头到尾遍历========");
    while(true) {
      if (first == null) {
        break;
      }
      //输出first信息
      System.out.println(first);
      first = first.next;
    }

    System.out.println("从尾到头遍历========");
    while(true) {
      if (last == null) {
        break;
      }
      //输出last信息
      System.out.println(last);
      last = last.prev;
    }
    // 演示链表的添加对象/数据
    //在ls 和 zs 之间插入一个对象 smith
    //1.创建 Node 节点
    Node smith = new Node("smith");
    smith.next = ls;
    smith.prev = zs;
    ls.prev = smith;
    zs.next = smith;

  }
}
class Node{
  public Object item;//真正存放数据
  public Node next;//指向后一个
  public Node prev;//指向前一个
  public Node(Object name) {
    this.item = name;
  }

  @Override
  public String toString() {
    return "Node" + item;
  }
}
ArrayList 和 LinkedList 的比较
底层结构增删效率改查的效率
ArrayList可变数组较低、数组扩容较高
LinkedList双向链表较高、通过链表追加较低

如何选择 ArrayList 和 LinkedList

  1. 如果改查操作多,选择 ArrayList
  2. 如果增删操作多,选择 LinkedList
  3. 一般来说,程序中 80%~90%都是查询,因此大部分情况下选择 ArrayList
  4. 在项目中,根据业务灵活选择
LinkedList 增删改查案例
public static void main(String[] args) {
  LinkedList list = new LinkedList();
  //添加节点
  list.add(1);
  list.add(2);
  list.add(3);
  list.add(4);
  //删除节点,默认删除第一个节点
  list.remove();
  //修改某个节点对象
  list.set(2, "修改节点");
  //获取某个节点
  System.out.println(list.get(1));
  System.out.println(list.size());
  System.out.println(list);

  for (Object o : list) {
    System.out.println(o);
  }
}
java.util.Stack

Stack 是 Vector 的子类,Stack 是一种后进先出(LIFO)的结构,其继承了 Vector 的基础上拓展 5 个方法 push()、pop()、peek()、empty()、search()而来

  1. 特征的方法 peek() 查看栈顶的元素,但不移除 pop() 获取栈顶的元素,并移除 push() 压入栈,添加的位置在栈顶 search(Object) 返回位置,以 1 为基数 empty() 判断该栈是否为空
  2. 底层 数组 每次添加到后面,栈顶是数组的后面[size-1]号元素,栈底是数组的[0]元素
补充 Collection 的方法,和 index 相关的方法
  1. 添加 add(int index, E element)addAll(int index, Collection<? extends E> c)
  2. 删除 remove(int index)
  3. 查找 indexOf(Object o)lastIndexOf(Object o)get(int index)
  4. 替换 set(int index, E element)
  5. 截取 subList(int fromIndex, int toIndex)

java.util.Set

Set 是无序的(添加顺序),不可重复的,Set 的元素其实也是一对,只不过它的 value 是共享同一个常量对象 Object 对象

java.util.HashSet
HashSet 特点
  1. new HashSet() 构造器源码如下,底层使用了 HashMap
public HashSet() {
  map = new HashMap<>();
}
  1. HashSet 实现了 Set 接口,无序不可重复 3.可以存放 null 值,但是只能存放一个 null
  2. HashSet 不保证元素是有序的,取决于 hash 后,再确定索引结果(即:不保证数据的存放顺序和取出顺序一致)
  3. HashSet 不能由重复元素/对象
  4. equals 和 hashCode hash 值不同,这俩个对象不一定不同,可以不调用 equals equals 如果相同,hashCode 一定相同 hash 值想通过,这两个对象不一定相等,所以一定要调用 equals 方法进行确认
import java.util.HashSet;
import java.util.Objects;

public class HashSetExercise {
  public static void main(String[] args) {
    HashSet hs = new HashSet();
    hs.add(new Employee("张三", 25));
    hs.add(new Employee("张三", 25));
    hs.add(new Employee("李四", 28));

    System.out.println(hs);
  }
}

class Employee {
  private String name;
  private int age;

  public Employee(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 "Employee{" +
      "name='" + name + '\'' +
      ", age=" + age +
      '}';
  }

  //如果name 和 age 值相同,则返回相同的hash值

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    Employee employee = (Employee) o;
    return age == employee.age && Objects.equals(name, employee.name);
  }

  @Override
  public int hashCode() {
    return Objects.hash(name, age);
  }
}

快捷键:Alt+Insert 快速重写 hashCode 方法和 equals 方法,验证 hashCode 值和 equalsimage-20220521210539989

HashSet 示例
public class HashSet_ {
  public static void main(String[] args) {

    Set hashset = new HashSet();
    //说明
    //1.在执行add方法后,会返回一个 boolean值,如果添加成功返回 true,添加失败返回false
    //2.可以通过 remove 指定删除哪个对象
    hashset.add("tom");
    hashset.add("tom");
    hashset.add("john");

    hashset.remove("john");
    System.out.println("hashset=" + hashset);

    hashset = new HashSet();
    hashset.add("lucy");
    hashset.add("lucy");
    hashset.add(new Dog("tom"));
    hashset.add(new Dog("tom"));

    System.out.println("hashset=" + hashset);
  }
}
class Dog {
  private String name;

  public Dog(String name) {
    this.name = name;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }
}
java.util.TreeSet
  1. 不按添加顺序,但是按照元素“大小”顺序存储,不可重复
  2. 不可重复,依据元素是否“大小相等”调用元素的 compareTo 或定制比较器的 compare
  3. 添加到 TreeSet 的元素一定要支持可比较大小,可排序 自然排序:要求元素类型本身要实现 java.lang.Comparable 接口,并重写 int compareTo(Object)方法 定制排序:要为 TreeSet 指定一个定制比较器对象 TreeSet set = new TreeSet(定制比较器对象);
public static void main(String[] args) {
  /*
  1.当我们使用无参构造器 new TreeSet() 创建 TreeSet时,仍然是无序的
  2.按照字母表进行排序,使用 TreeSet 提供的构造器,可以传入一个比较器(匿名内部类)并执行排序规则
  3.构造器把传入的比较器对象,给 TreeSet的底层的 TreeMap的属性 this.comparator
  public TreeMap(Comparator<? super K> comparator) {
      this.comparator = comparator;
  }
  4.在 treeSet.add("jack");添加时,在底层会执行到 TreeMap 中的
  if (cpr != null) { //cpr就是我们的匿名内部类(对象)
          do {
              parent = t;
              //动态绑定到我们匿名内部类(对象)compare
              cmp = cpr.compare(key, t.key);
              if (cmp < 0)
                  t = t.left;
              else if (cmp > 0)
                  t = t.right;
              else //如果相等,即返回0,这个key就不加入
                  return t.setValue(value);
          } while (t != null);
      }
  */

  //TreeSet treeSet = new TreeSet();
  TreeSet treeSet = new TreeSet(new Comparator() {
    @Override
    public int compare(Object o1, Object o2) {
      //下面调用 String 的 compareTo 方法进行字符串大小比较
      //return ((String) o1).compareTo((String) o2);
      //按照长度大小排序
      return ((String) o1).length() - ((String) o2).length();
    }
  });
  treeSet.add("jack");
  treeSet.add("tom");
  treeSet.add("a");
  treeSet.add("b");
  treeSet.add("tom");

  System.out.println(treeSet);
}
java.util.LinkedHashSet
  1. LinkedHashSet 是 HashSet 的子类
  2. LinkedHashSet 底层是一个 LinkedHashMap,底层维护了一个数组+双向链表
  3. LinkedHashSet 根据的 hashCode 值来决定元素的存储位置,同时使用链表维护元素的次序(图),这使得元素看起来是以插入顺序保存的
  4. LinkedHashSet 不允许添加重复元素
  5. 第一次天添加时,直接将数组 table 扩容到 16,存放的节点类型是 LinkedHashMap$Entry
  6. 数据 HashMap$Node[] 存放的元素/数据是 LinkedHanshMap$entry 类型,源码如下:
static class Entry<K,V> extends HashMap.Node<K,V> {
  Entry<K,V> before, after;
  Entry(int hash, K key, V value, Node<K,V> next) {
    super(hash, key, value, next);
  }
}
  1. LinkedHashSet 示例:重写 equals 方法和 hashCode,当 hashCode 相同 和 equals 为 true 时不添加
public class LinkedHashSetExercise {
  public static void main(String[] args) {
    LinkedHashSet linked = new LinkedHashSet();
    linked.add(new Car("大众帕萨特", 150000));
    linked.add(new Car("保时捷", 11520));
    linked.add(new Car("法拉利", 30000));
    linked.add(new Car("大众帕萨特", 150000));

    System.out.println(linked);
  }
}

class Car {
    private String name;
    private double price;

  public Car(String name, double price) {
    this.name = name;
    this.price = price;
  }

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

  /**
   * 重写 equals方法和 hashCode,当name和price相同时则返回相同的 hasCode 值, equals 返回true
   */
  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    Car car = (Car) o;
    return Double.compare(car.price, price) == 0 && Objects.equals(name, car.name);
  }

  @Override
  public int hashCode() {
    return Objects.hash(name, price);
  }
}
遍历
  1. foreach
  2. Iterator 迭代器
public static void main(String[] args) {
  //以Set接口的实现类 HashSet 来讲解 Set 接口方法
  //1.set接口的实现类的对象(Set接口对象)不能存放重复的元素,可以添加null
  //2.set接口对象存放数据是无序的(即添加的顺序和取出的数据不一致)
  //3.注意:取出的顺序虽然不是添加的顺序,但是它是固定的
  Set set = new HashSet();
  set.add("john");
  set.add("lucy");
  set.add("john");
  set.add("jack");
  set.add(null);
  set.add(null);

  set.remove(null);

  System.out.println("set" + set);
  //遍历方式
  //方式1:迭代器
  Iterator iterator = set.iterator();
  while (iterator.hasNext()) {
    Object next =  iterator.next();
    System.out.println("迭代器=" + next);
  }

  //方式2:foreach 增强
  for (Object o : set) {
    System.out.println("foreach=" + o);
  }

}
底层实现
  1. HashSet-HashMap

  2. TreeSet-TreeMap

  3. LinkedHashSet-LinkedHashMap

  4. 继承关系

    image-20220521180401637

遍历

1. foreach

在遍历时,效率高,但是不适用于遍历的同时对集合进行修改,特别是影响集合元素个数的操作 1.1 语法结构

for(集合的元素类型  element : 集合名){
}

1.2 foreach 示例


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

public class CollectionForDemo {
  public static void main(String[] args) {
    Collection list = new ArrayList();
    list.add(new Book("三国演义", "罗贯中", 62));
    list.add(new Book("小李飞刀", "古龙", 42));
    list.add(new Book("红楼梦", "曹雪芹", 52));

    // 增强for
    // 增强for 底层仍然是迭代器,可以理解成简化版本的迭代器
    // 快捷键大写I

    for (Object book : list) {
      System.out.println("book=" + book);
    }
    //增强for 也可以直接在数组上使用
    int[] arr = {1,8,5,6,7};
    for (int i:arr) {
      System.out.println(i);
    }
  }
}

class Book{
  private String name;
  private String author;
  private double price;
}
2. Iterator 迭代器

Iterator 是一个接口,在每一类集合中,都有自己的实现类,通过内部类的形式来实现 Iterator 接口 2.1 语法结构:

//得到一个集合的迭代器
Iterator iter = 集合对象.iterator();
// hasNext() 判断是否还有下一个元素
while(iter.hasNext()){
  //next() 作用:1.下移2.将下移以后集合位置上的元素返回
	Object element = iter.next();
	//可以使用iter.remove()进行移除
}

2.2 迭代器示例:

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

public class IteratorDemo {
  public static void main(String[] args) {
    Collection list = new ArrayList();
    list.add(new Book("三国演义", "罗贯中", 62));
    list.add(new Book("小李飞刀", "古龙", 42));
    list.add(new Book("红楼梦", "曹雪芹", 52));

    System.out.println(list);

    //1.创建 list 对应的迭代器
    Iterator iterator = list.iterator();
    //2.使用 while 循环遍历 iterator.hasNext() 判断是否还有数据
    while (iterator.hasNext()) {
      //返回下一个元素,类型是Object
      Object element = iterator.next();
      System.out.println(element.toString());
    }

    //while -> 快捷键 itit
    //显示所有快捷键的方式 ctrl + j

    //3.当退出 while 循环后,这时 iterator 迭代器,指向最后的元素 在执行 iterator.next() 会变成 NoSuchElementException
    //4.如果需要再次遍历,需要重置迭代器
    iterator = list.iterator();
    System.out.println("=======第二次迭代打印=======");
    while (iterator.hasNext()) {
      //返回下一个元素,类型是Object
      Object element = iterator.next();
      System.out.println(element.toString());
    }
  }
}

class Book{
  private String name;
  private String author;
  private double price;
}

继承关系图

list-set

1.9.3Collection 常用方法

  1. 添加 add(Object obj) 添加一个元素到集合中 addAll(Collection other)other 集合中的元素一一添加到当前集合中,一次添加多个
  2. 删除 remove(Object obj) 删除一个元素 removeAll(Collection other) 从当前集合中删除它俩的交集 this-this ∩ other
  3. 查找 contains(Object obj) 从当前集合中查找一个元素 containsAll(Collenction c) 判断 c 是否是当前集合的子集
  4. 其他 size() 获取有效元素的个数 retainsAll(Collection other)this ∩ other 赋值给当前集合 this=this ∩ other
  5. 遍历 Iterator iterator() Object[] toArray()
List list = new ArrayList();
//add 添加单个元素
list.add("jack");
//list.add(new Integer(10))
list.add(10);
list.add("tom");
list.add(true);
System.out.println(list);
// remove 移除元素
list.remove("jack");
list.remove(0);
System.out.println(list);
// contains 查找元素是否存在 存在则返回 true 不存在返回false
System.out.println(list.contains("tom"));
//size 获取元素个数
System.out.println(list.size());
// isEmpty 判断是否为空
System.out.println(list.isEmpty());
//clear 清空
list.clear();
System.out.println(list);
ArrayList list2 = new ArrayList();
list2.add("红楼梦");
list2.add("水浒传");
//addAll 添加多个元素
list.addAll(list2);
// containsAll 查找多个元素是否都存在
System.out.println(list.containsAll(list2));
// 移除多个元素
list.removeAll(list2);

1.9.4Map

⭐Map 的特点

  1. Map 的元素,即存储的映射关系(key,value),其类型是 Entry 类型,它是 Map 的内部子接口,在各种 Map 的实现类中,都用内部类的方式来实现 Entry 接口

  2. Map 的 key 不可重复,而且一旦添加到 map 中,key 不建议修改,特别是参与 hashCode 和 equals 方法的属性,或参与 compareTo 或 compare 方法比较的属性

  3. Map 接口实现类的特点,使用实现类 HashMap 3.1. Map 与 Collection 并存,用于保存具有映射关系的数据:Key-Value(双列元素) 3.2. Map 中的 key 与 value 可以是任何引用类型的数据,会封装到 HashMap$Node 对象中 3.3. Map 中的 Key 不允许重复,原因是和 HashSet 一样 3.4. Map 的 Key 可以是 null,value 也可以是 null,注意 key 为 null 只能有一个, value 为 null 可以多个 3.5. Map 中的 value 可以重复 3.6. 常用 String 类作为 Map 的 key 3.7. Key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到对应的 value 3.8. Map 存放数据的 key-value 示意图,一对 Key-Value 是放在一个 Node 中,因为有 Node 实现了 Entry 接口,示意图(可以结合源码)如下:

    image-20220522211654620

public static void main(String[] args) {
    Map map = new HashMap();
    map.put("age", 22);
    map.put("name", "张三");

    Map map2 = new HashMap();
    map2.put("name", "李四");
    map2.put("book", "水浒传");
    map2.put("salary", "6500");

    map.putAll(map2);
    System.out.println("map" + map);
  }

Map 常用方法

  1. 添加 put(key,value)putAll(Map)
  2. 有效键值对数 size
  3. 根据 key 获取 value get(key)
  4. 是否包含某个 key/value containsKey()containsValue()
  5. 删除 remove(key)
  6. 判断个数是否为 0 isEmpty()
  7. 和迭代相关 遍历所有的 key Set keySet() 遍历查按照键是否存在 containsKey 遍历所有的 value Collection values() 遍历所有的映射关系 Set entrySet() Set 的元素是 Entry 类型
public class MapMethod {
  public static void main(String[] args) {

    Map map = new HashMap();
    //put(key,value) 添加多个
    map.put("age", new Book("三国演义", 120));
    map.put("name", "张三");

    Map map2 = new HashMap();
    map2.put("userName", "李四");
    map2.put("book", "水浒传");
    map2.put("salary", "6500");
    //putAll(Map) 添加多个
    map.putAll(map2);
    System.out.println(map);
    //remove(key) 根据键删除映射关系
    map.remove("name");
    System.out.println(map);
    // get(key) 根据键获取值
    Object o = map.get("book");
    System.out.println(o);
    //size() 获取元素个数
    System.out.println(map.size());
    // 是否包含某个Key/Value
    System.out.println(map.containsKey("userName"));
    System.out.println(map.containsValue("李四"));
    //isEmpty 判断个数是否为0
    System.out.println(map.isEmpty());

    //6. 和迭代相关---------------------------------------------
    //遍历所有的 key `Set keySet()`
    System.out.println("keySet()------------------第一组-------------");
    Set keys = map.keySet();
    //1. 迭代器
    Iterator iter = keys.iterator();
    while (iter.hasNext()) {
      Object key = iter.next();
      Object value = iter.next();
      System.out.println(key + ":" + value);
    }
    //遍历所有的 value `Collection values()`
    //2.增强for
    for (Object key: keys) {
      System.out.println(key + ":" + map.get(key));
    }

    //遍历所有的映射关系 `Set entrySet()` Set 的元素是 Entry 类型
    System.out.println("entrySet()----------------第二组---------------");
    //entrySet<Map.Entry<K,V>>
    Set entrys = map.entrySet();
    //1.增强for
    for (Object entry: entrys) {
      //将entry 转成 Map.Entry
      Map.Entry e = (Map.Entry)entry;
      Object key = e.getKey();
      Object value = e.getValue();
      System.out.println(key + ":" + value);
    }
    //2.迭代器
    System.out.println("---使用EntrySet的迭代器-------------");
    Iterator iterator3 = entrys.iterator();
    while (iterator3.hasNext()) {
      Object next =  iterator3.next();
      System.out.println(next.getClass());
      //向下转型 Map.Entry
      Map.Entry m = (Map.Entry) next;
      System.out.println(m.getKey() + ":" + m.getValue());
    }


    System.out.println("Collections-------------第三组---------------");
    //将所有 value值取出
    Collection values = map.values();
    //可以使用所有的 Collections 使用的遍历方法
    //1.增强for
    for (Object value: values) {
      System.out.println(value);
    }
    //2.迭代器
    Iterator iterator2 = values.iterator();
    while (iterator2.hasNext()) {
      Object next =  iterator2.next();
      System.out.println(next);
    }
  }
}
class Book{
  private String name;
  private double price;
}

Map 常见实现类

1. Hashtable
  1. 存放的元素是键值对:即 key-value
  2. Hashtable 使用方法基本上和 HashMap 一样
  3. Hashtable 的键和值都不能为 null,否则会抛出 NullPointerException
  4. JDK1.0 就有的,属于旧版 HashMap,线程安全的
Hashtable 底层
  1. 底层有数组 Hashtable$Entry[] 初始化大小为 11
  2. 临界值 threshold 8 = 11 * 0.75
  3. 扩容:按照自己的扩容机制进行扩容
  4. 执行方法 addEntry(hash, key, value, index); 添加 key-value 封装到 Entry
  5. if(count >= threshlod) 满足时,就进行扩容按照 int newCapacity = (oldCapacity < 1) +1; 的大小扩容
public static void main(String[] args) {
  Hashtable hashtable = new Hashtable();
  hashtable.put("kkk", "111");
  hashtable.put("kkk1", "111");
  hashtable.put("kkk2", "111");
}
Hashtable 和 HahsMap 对比
版本线程安全(同步)效率允许 null 键 null 值
HashMap1.2不安全可以
Hashtable1.0安全较低不可以
2. HashMap

它的 key 不可重复的,依据 key 的 hashCode()和 equals()方法,线程不安全的 JDK1.7 时底层实现是数组+链表 JDK1.8 时底层实现是数组+链表+红黑树

HashMap 总结
  1. Map 接口的常用类型:HashMap、Hashtable 和 Properties
  2. HashMap 是 Map 接口使用频率最高的实现类
  3. HashMap 是以 key-value 对的方式来存储的数据(HashMap$Node 类型)
  4. key 不能重复,但是值是可以重复的,允许使用 null 键 和 null 值
  5. 如果添加相同的 key,则会覆盖原来的 key-value,等同于修改(.key 不会替换,value 会替换)
  6. 与 HashSet 一样,不保证映射的顺序,因为底层是以 hash 表的方式来存储的
  7. HashMap 没有实现同步,因此是线程不安全的,方法没有做同步互斥的操作,没有 synchronized

image-20220523144147418

HashMap 底层机制及源码刨析
  1. (k,v)是一个 Node 实现了 Map.Entry<K,V> 查看 HashMap 的源码可以查看到
  2. JDK7.0 的 HashMap 底层实现[数组+链表],JDK8.0 底层[数组+链表+红黑树]
  3. 如果我们在 HashMap 中 put(存)进去 key 一个,而这个 key 是已经存在的,即 key.hash 以及 key 的值是相同时,则会把新的 key-value 存到 HashMap 中,同时也会 key 对应的旧的 value 返回
  4. 源码刨析
public static void main(String[] args) {
  HashMap hashMap = new HashMap();
  hashMap.put("java", 10);
  hashMap.put("php", 10);
  hashMap.put("java", 20);
  System.out.println("map" + hashMap);

  /*
  1.执行构造器 new HashMap() 初始化加载因子 loadfactor = 0.75 HashMap$Node[] table = null
  2.执行 put 源码如下:
    public V put(K key, V value) {
      return putVal(hash(key), key, value, false, true);
    }
    3.执行 putVal 源码如下:
    实现了Map,put相关方法
    @param hash key的hash值
    @param key key
    @param value put值
    @param onlyIfAbsent 如果为 true不改变存在的value
    @param evict 如果为false,则该表处于创建模式
    @return 前一个值,如果没有则为空

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
      Node<K,V>[] tab; //临时节点
      Node<K,V> p; int n, i; //辅助变量
      //如果table为null,则通过resize()方法对table数组进行初始化,resize()也是扩容的方法,将扩容到16
      if ((tab = table) == null || (n = tab.length) == 0)
          n = (tab = resize()).length;
      //通过hash方式,找到key所对应的数组的节点,然后把该数组节点赋值给p
      //如果通过hash所算出来的节点为null,则通过newNode(int hash, K key, V value, Node<K,V> node) 初始化
      //一个新的node节点,然后把该值放到对应的数组中
      if ((p = tab[i = (n - 1) & hash]) == null)
          tab[i] = newNode(hash, key, value, null);
      else {
          //如果hash算出来的所对应的节点不为空,则需要通过链表的方式存储该Node
          Node<K,V> e; K k;
          //如果通过hash算出来的数组不为空,即table[i = (n - 1) & hash] 不为空
          //如果 table 索引位置的key的hash值相同和新的key的hash值相同,并满足(table现有的节点的key和准备田间的key是同一个对象 ||
          // equals 返回 true)就认为该节点的key是否与我们即将要存的key相同,如果相同,则获获取该节点即:e = p
          if (p.hash == hash &&
              ((k = p.key) == key || (key != null && key.equals(k))))
              e = p;
          //如果该节点的key与我们的不相等,则看该节点是红黑树,还是链表
          else if (p instanceof TreeNode)
          //如果是红黑树,则通过红黑树的方式,把key-value存放到红黑树中
              e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
          else {
              //如果是链表的,则把key-value插入到链表尾,该方式是链表的尾插入法
              //注意:JDK1.7 的链表是头插入法,但是这样会使 HashMap 重 hash 的时候造成死循环
              //因此在JDK1.8 就把头插入法换成了尾插入法,虽然效率上有所减低,但是也可以大概率避免多线程的时候冲哈希造成的死循环
              for (int binCount = 0; ; ++binCount) {
                  //遍历该链表,找到尾部,然后把尾部的next指向新生成的对象,即添加到尾部
                  if ((e = p.next) == null) {
                      p.next = newNode(hash, key, value, null);
                      //如果链表的长度大于等于8,则链表转化成为红黑树 TREEIFY_THRESHOLD=8;
                      if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                          //链表转为红黑树
                          treeifyBin(tab, hash);
                      break;
                  }
                  //如果该节点上的key与我们想要put进去的值相同跳出循环
                  if (e.hash == hash &&
                      ((k = e.key) == key || (key != null && key.equals(k))))
                      break;
                  p = e;
              }
          }
          //如果e不等于null,则说明HashMap中存在与我们即将要存进去的key相同,
          // 然后把节点中的值进行替换,即e.value=value,并放回旧的value
          if (e != null) {
              V oldValue = e.value;//当前节点的值赋值给oldValue
              if (!onlyIfAbsent || oldValue == null)
                  e.value = value;//将要插入的value替换当前节点的value
              afterNodeAccess(e);
              return oldValue;
          }
      }
      //modCount 是值我们修改HashMap的次数,用来快速失败的,即fast-fail
      ++modCount;//增加长度
      //threshold=capacity * loadfactor,即数组初始化长度*负载因子,如果
      // this.loadFactor = DEFAULT_LOAD_FACTOR; 默认DEFAULT_LOAD_FACTOR=0.75
      //如果HashMap中的存的数据,大于数组长度的四分之三,就要进行扩容
      if (++size > threshold)
          resize();//检测Hash表是否超过当前Hash需要增加的长度,进行扩容
      afterNodeInsertion(evict);
      return null;
  }
  4.关于树化(转成红黑树)源码如下:
  //如果 table 为null或大小还没有到64,暂时不进行树化,而是进行扩容
  final void treeifyBin(Node<K,V>[] tab, int hash) {
      int n, index; Node<K,V> e;
      if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
          resize();
    }
  */
}
  1. HashMap 源码流程图

    image-20220523164507871

3. TreeMap

它的 key 不可重复的,按照 key 的“大小”顺序进行排列, 依据 key 的自然排序 Comparable(compareTo())或定制排序 Comparator(compare())规则进行排序

public static void main(String[] args) {
  /*
  1.使用默认构造器 new TreeMap() 创建TreeMap,是无序的(也没有顺序)
  2.构造器,把传入的实现了 Comparator接口的匿名内部类(对象),传给 TreeMap 的 comparator 源码如下
  public TreeMap(Comparator<? super K> comparator) {
      this.comparator = comparator;
  }
  3.调用put方法
  3.1第一次添加,把k-v封装到 Entry对象,放入root
  Entry<K,V> t = root;
  if (t == null) {
      compare(key, key); // type (and possibly null) check

      root = new Entry<>(key, value, null);
      size = 1;
      modCount++;
      return null;
  }
  3.2第二次及以后添加
  Comparator<? super K> cpr = comparator;
  if (cpr != null) {
      do {//遍历所有的key,给当前的key找适当位置
          parent = t;
          cmp = cpr.compare(key, t.key);//动态绑定到我们的匿名内部类compare
          if (cmp < 0)
              t = t.left;
          else if (cmp > 0)
              t = t.right;
          else //如果遍历过程中,发现准备添加的key和当前已有的key相等,就不添加
              return t.setValue(value);
      } while (t != null);
  }
  */

  //TreeMap tree = new TreeMap();
  //使用匿名内部类,进行排序
  TreeMap tree = new TreeMap(new Comparator() {
    @Override
    public int compare(Object o1, Object o2) {
      //按照key(String) 的大小进行排序
      //return ((String)o1).compareTo((String)o2);
      //按照key(String) 的长度进行排序
      return ((String)o1).length() - ((String)o2).length();
    }
  });
  tree.put("jack", "杰克");
  tree.put("tom", "汤姆");
  tree.put("smith", "斯密斯");
  tree.put("ass", "测试"); // 因为按照key长度进行排序所以添加不进去

  System.out.println(tree);
}
4. LinkedHashMap

它是 HashMap 的子类,在 HashMap 的基础上同时要维护添加的顺序

5. Properties

Properties 是 Hashtable 的子类,它的 key 和 value 的类型都是 String 类型

Map 继承关系图

java-map

⭐Map 高频面试题:HashMap 的底层实现过程

  1. JDK1.7 时底层实现是数组+链表 当我们要添加一个新的映射关系时: 1.1 先取出 key,算出它的 hash 值 1.2 如果数组是空的,会先建立一个长度为 16 的数组 table 1.3 如果数组不为空,这个时候要判断数组的长度是否达到临界点(数组长度*0.75),如果已经达到临界点,应该先对数组进行扩容,扩大为 2 倍 一旦扩容,要重头开始(以前元素要重新排序位置,对新添加的映射关系也要重写计算 key 的 hash 值,和 index) 1.4 会根据 key 的 hash 值与 table 数组的长度做一个“按位与&”的运算,计算出要存储的下标 index 1.5 先判断 table[index]是否为空,如果为空,直接放进去,放进去之前会构建一个 Entry 类型对象 1.6 如果 table[index]不是空的,那么调用 key 的 equals 方法与 table[index]的 key 做比较,如果 table[index]下面还有链表, 可能需要与 table[index]下面的链表的元素一一比较,直到遇到了 equals 为 true 或都不相同 1.7 如果有一个 equals 返回 true,那么就把 value 给替换 1.8 如果 equals 都不相等,那么把当前映射关系构建的 Entry 对象,放在此链表的表头,把原来的对象作为我的 next
  2. JDK1.8 时底层实现是数组+链表+红黑树 当我们要添加一个新的映射关系时: 2.1 先取出 key,算出它的 hash 值 2.2 如果数组是空的,会先建立一个长度为 16 的数组 table 2.3 如果数组不为空,这个时候要判断数组的长度是否达到临界点(数组长度*0.75),如果已经达到临界点,应该先对数组进行扩容,扩大为 2 倍 一旦扩容,要重头开始(以前元素要重新排序位置,对新添加的映射关系也要重写计算 key 的 hash 值,和 index) 2.4 会根据 key 的 hash 值与 table 数组的长度做一个“按位与&”的运算,计算出要存储的下标 index 2.5 先判断 table[index]是否为空,如果为空,直接放进去,放进去之前会构建一个 Entry 类型对象 2.6 如果 table[index]不是空的,那么调用 key 的 equals 方法与 table[index]的 key 做比较,如果 table[index]下面有树或者链表, 可能需要与 table[index]下面的链表或树的元素一一比较,直到遇到了 equals 为 true 或都不相同 2.7 如果有一个 equals 返回 true,那么就把 value 给替换 2.8 如果都不相等,如果现在已经是树,就直接添加到该树的叶子节点上。 2.9 如果都不相等,如果现在不是树,而是链表,看当前链表的长度是否达到 8 个,如果没有达到 8 个,直接添加到链表的尾部 2.10 如果已经达到 8 个,此时要检查数组 table 的长度是否达到 64,如果没有达到 64,先扩容,一旦扩容,一切从头开始 2.11 如果达到 64,把该链表变成一颗红黑树 什么时候树会变回链表? 每次进行 resize(),会检查树的叶子节点的总数是否<6 个,如果<6 个,会把这个红黑树变回链表
  3. k-v 最后是 HashMap$Node node = newNode(hash, key, value, null);
  4. k-v 为了方便遍历,还会创建 EntrySet 集合,该集合存放的元素类型 Entry,而一个 Entry 对象就有 k,v EntrySet<Map.Entry<K,V>> 即:transient Set<Map.Entry<K,V>> entrySet;
  5. entrySet 中,定义类型是 Map.Entry,但是实际上存放的还是 HashMap$Node 这是 因为 HashMap$Node implements Map.Entry 即:static class Node<K,V> implements Map.Entry<K,V>
  6. 当把 HashMap$Node 对象存放到 entrySet 就方便我们遍历,因为 Map.Entry 提供了重要方法 K getKey();`` V getValue();
public static void main(String[] args) {
    Map map = new HashMap();
    map.put("name", "张三");
    map.put("age", 26);
    map.put(new Car(), new Person());
    Set set = map.entrySet();
    System.out.println(set.getClass()); //HashMap$EntrySet
    for (Object obj : set) {
      System.out.println(obj.getClass());
      //为了从 HashMap$Node 取出k-v
      //1.先做一个向下转型
      Map.Entry entry = (Map.Entry) obj;
      System.out.println(entry.getKey() + "-" +  entry.getValue());
    }
  }

1.9.5 集合框架图

list

1.9.6 集合工具类 java.util.Collections

操作集合的静态方法,Collections 是一个操作 Set 、List 、Map 等集合的工具类,提供了一系列静态的方法对集合元素进行排序、查询和修改等操作

  1. Collections.addAll(Collection, T... elements)
  2. binarySearch(List, T target)List的元素类型有要求,必须支持可比较大小
  3. max/min(Collection)Collection的元素类型有要求,必须支持可比较大小
  4. sort(List) 元素必须实现Comparable
  5. sort(List,Comparator) 按照指定比较器进行排序
  6. 如果想要获得线程安全的集合对象 synchronizedXXX(集合)
  7. swap(List, int, int) 将指定 list 集合中的 i 出元素和 j 处元素进行交换
public static void main(String[] args) {
  ArrayList list = new ArrayList();
  list.add("tom");
  list.add("smith");
  list.add("king");
  list.add("milan");
  // reverse(List) 反转list中的元素顺序
  Collections.reverse(list);
  System.out.println("reverse=" + list);
  //shuffle(List) 对list集合元素进行随机排序
  Collections.shuffle(list);
  System.out.println("shuffle=" + list);
  //sort(List) 根据元素的自然顺序对指定 list 集合元素按照升序排序
  Collections.sort(list);
  System.out.println("sort="+list);
  //sort(List,Comparator) 按照指定比较器进行排序
  Collections.sort(list, new Comparator() {
    @Override
    public int compare(Object o1,Object o2) {
      return ((String)o1).length() - ((String)o2).length();
    }
  });
  System.out.println("sort="+list);
  // swap(List,int,int)指定索引交换元素位置
  Collections.swap(list, 0, 1);
  System.out.println("swap="+list);
  //max(List) 根据元素自然排序,返回集合中最大的元素
  System.out.println("max=" + Collections.max(list));
  //max(List, Comparator) 根据Comparator指定的排序,返回集合中最大的元素
  Object result = Collections.max(list, new Comparator(){
    @Override
    public int compare(Object o1, Object o2) {
      return ((String)o1).length() - ((String)o2).length();
    }
  });
  System.out.println("max=" + result);
  //void copy(List dest, List src) 将scr中的内容复制到dest中
  ArrayList dest = new ArrayList();
  //拷贝前需要将空数组长度赋值到和拷贝数组长度一样,否则汇报IndexOutOfBoundsException 数组越界异常
  for (int i = 0; i < list.size(); i++) {
    dest.add("");
  }
  //拷贝
  Collections.copy(dest, list);
  System.out.println("dest-copy=" + dest);

  //boolean replaceAll(List<T> list, T oldVal, T newVal) 使用新值替换 List 对象所有的旧值
  //如果list中有tom就替换成汤姆
  Collections.replaceAll(list, "tom", "汤姆");
  System.out.println("replaceAll=" + list);
}

1.9.7 集合选型规则

  1. 先判断村塾的类型(一组对象或一组键值对)
  2. 一组对象:Collection 接口
    • 允许重复:List
      • 增删多: LinkedList[底层维护了一个双向链表]
      • 改查多:ArrayList[底层维护 Object 类型的可变数组]
    • 不允许重复:Set
      • 无序:HashSet [底层是 HashMap,维护了一个 Hash 表,即:数组+链表+红黑树]
      • 排序:TreeSet
      • 插入和取出顺序一致:LinkedHashSet,维护数组+双向链表
  3. 一组键值对:Map
    • 键无序:HashMap[底层是 JDK7.0 的 HashMap 底层实现[数组+链表],JDK8.0 底层[数组+链表+红黑树]]
    • 键排序:TreeMap
    • 键插入和取出顺序一致:LinkedHashMap
    • 读取文件 Properties

1.10 泛型

泛型是类型参数、参数的的类型,在 JDK1.7 的简写方法:ArrayList<String> list = new ArrayList<>();

泛型引入

  1. 不能对加入到集合 ArrayList 中的数据类型进行约束(不安全)
  2. 遍历的时候,需要进行类型转换,如果集合中的数据量较大,对效率有影响
public class Generic01 {
  public static void main(String[] args) {
    //使用传统方法解决,使用泛型
    //1.当我们 ArrayList<Dog> 表示存在到 ArrayList 集合中的元素是Dog类型
    //2.如果编辑器添加类型不满足要求,就会报错
    //3.遍历时可以直接取出 Dog 类型而不是 Object
    //4.public class ArrayList<E> {} E表示一个泛型 Dog -> E
    ArrayList<Dog> arrayList = new ArrayList<Dog>();
    arrayList.add(new Dog("小黑", 1));
    arrayList.add(new Dog("小黄", 4));
    arrayList.add(new Dog("小白", 2));

    //不小心添加了一只猫,使用泛型后,编辑器添加猫时会报错
    arrayList.add(new Cat("招财猫", 8));

    for (Object o : arrayList) {
      //向下转型Object->Dog
      Dog dog = (Dog)o;
      System.out.println(dog.getName() + "-" + dog.getAge());
    }
  }
}

class Dog{
  public String name;
  public int age;

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

泛型说明

public class Generic02 {
  public static void main(String[] args) {
    //注意:E具体的数据类型在定义Person对象的时候指定,即在编译期间,就确定E是什么类型
    Person<String> person = new Person<String>("JAVA");
    /**
    你可以这样理解,上面的Person类
    class Person<String> {
       //E表示 s的数据类型,该数据类型在定义Person对象的时候指定,即在编译期间,就确定E是什么类型
        String name;
        public Person(String name) {//E也可以是参数类型
          this.name = name;
        }
        public String f() {//返回时使用E
          return name;
        }
      }
     **/
  }
}

class Person<E> {
  /**
   * E表示 s的数据类型,该数据类型在定义Person对象的时候指定,即在编译期间,就确定E是什么类型
   */
  E name;

  public Person(E name) {//E也可以是参数类型
    this.name = name;
  }

  public E f() {//返回时使用E
    return name;
  }
}

泛型应用示例

public class Generic03 {
  public static void main(String[] args) {
    HashSet<Student> students = new HashSet<Student>();
    students.add(new Student("张三", 42));
    students.add(new Student("李四", 32));
    students.add(new Student("王五", 23));
    students.add(new Student("麻子", 29));

    for (Student student : students) {
      System.out.println(student.getName() + ":" + student.getAge());
    }

    Iterator iterator = students.iterator();
    while (iterator.hasNext()) {
      Object next =  iterator.next();
      //向下转型
      Student st = (Student)next;
      System.out.println(st.getName() + ":" + st.getAge());
    }

    //泛型方式给HashMap 放入三个学生对象
    // K -> String  V -> Student
    HashMap<String, Student> stringStudentHashMap = new HashMap<String, Student>();
    // public class HashMap<K,V> {}
    stringStudentHashMap.put("2019141245", new Student("mary", 23));
    stringStudentHashMap.put("2019151345", new Student("tom", 28));
    stringStudentHashMap.put("2019551323", new Student("jack", 18));
    /*
     迭代器 EntrySet
     public Set<Map.Entry<K,V>> entrySet() {
        Set<Map.Entry<K,V>> es;
        return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
    }
     */
    Set<Map.Entry<String, Student>> entries = stringStudentHashMap.entrySet();
    /*
      public final Iterator<Map.Entry<K,V>> iterator() {
         return new EntryIterator();
      }
    */
    Iterator<Map.Entry<String, Student>> it = entries.iterator();
    while (it.hasNext()) {
      Map.Entry<String, Student> next =  it.next();
      System.out.println(next.getKey() + "=" + next.getValue());
    }
  }
}

class Student{
  public String name;
  public int age;

  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 +
      '}';
  }
}

泛型的形式

  1. 泛型类、泛型接口
  2. 泛型方法

泛型类、泛型接口

语法格式
[修饰符]  class/interface  类名/接口名<类型参数列表>{
}
  1. 多个之间使用,分割

  2. 类型参数习惯命名,原则:尽量见名知意,尽量是一个大写字母,或大写字母加数字

    • E Element
    • K Key
    • V Value
    • T Type
    • T1,T2
    • U1,U2
    • R ReturnType

解析一:


public class Generic02 {
  public static void main(String[] args) {
    //注意:E具体的数据类型在定义Person对象的时候指定,即在编译期间,就确定E是什么类型
    Person<String> person = new Person<String>("JAVA");
    /**
    你可以这样理解,上面的Person类
    class Person<String> {
       //E表示 s的数据类型,该数据类型在定义Person对象的时候指定,即在编译期间,就确定E是什么类型
        String name;
        public Person(String name) {//E也可以是参数类型
          this.name = name;
        }
        public String f() {//返回时使用E
          return name;
        }
      }
     **/
  }
}

class Person<E> {
  /**
   * E表示 s的数据类型,该数据类型在定义Person对象的时候指定,即在编译期间,就确定E是什么类型
   */
  E name;

  public Person(E name) {//E也可以是参数类型
    this.name = name;
  }

  public E f() {//返回时使用E
    return name;
  }
}

解析二:

public class Generic03 {
  public static void main(String[] args) {
    HashSet<Student> students = new HashSet<Student>();
    students.add(new Student("张三", 42));
    students.add(new Student("李四", 32));
    students.add(new Student("王五", 23));
    students.add(new Student("麻子", 29));

    for (Student student : students) {
      System.out.println(student.getName() + ":" + student.getAge());
    }

    Iterator iterator = students.iterator();
    while (iterator.hasNext()) {
      Object next =  iterator.next();
      //向下转型
      Student st = (Student)next;
      System.out.println(st.getName() + ":" + st.getAge());
    }

    //泛型方式给HashMap 放入三个学生对象
    // K -> String  V -> Student
    HashMap<String, Student> stringStudentHashMap = new HashMap<String, Student>();
    // public class HashMap<K,V> {}
    stringStudentHashMap.put("2019141245", new Student("mary", 23));
    stringStudentHashMap.put("2019151345", new Student("tom", 28));
    stringStudentHashMap.put("2019551323", new Student("jack", 18));
    /*
     迭代器 EntrySet
     public Set<Map.Entry<K,V>> entrySet() {
        Set<Map.Entry<K,V>> es;
        return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
    }
     */
    Set<Map.Entry<String, Student>> entries = stringStudentHashMap.entrySet();
    /*
      public final Iterator<Map.Entry<K,V>> iterator() {
         return new EntryIterator();
      }
    */
    Iterator<Map.Entry<String, Student>> it = entries.iterator();
    while (it.hasNext()) {
      Map.Entry<String, Student> next =  it.next();
      System.out.println(next.getKey() + "=" + next.getValue());
    }
  }
}

class Student{
  public String name;
  public int age;

  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 +
      '}';
  }
}
注意
  1. 泛型形参由泛型实参决定,在使用这个泛型时
    • 创建对象 ArrayList<Student> list = new ArrayList<Student>();
    • 继承类或实现接口 class Student implements Comparable<Student>
  2. 泛型实参必须指定为引用数据类型,不能是基本数据类型
  3. 泛型形参在声明它的类或接口中,当做某种已知的类型来使用的,可以用它声明属性、方法的形参类型,方法的返回值类型,方法局部变量类型等
  4. 泛型形参不能用于,作为异常的类型,不能使用在静态成员上面
  5. 泛型不能创建数组对象
public class Generic04 {
  public static void main(String[] args) {
    //泛型使用的注意事项和细节

    //1.给泛型指向数据类型时,要用是引用类型,不能是基本数据类型
    List<Integer> list = new ArrayList<Integer>();
//    List<int> list2 = new ArrayList<int>();

    //2.说明:因为E指定了A类型,构造器传入了 new A()
    //在给泛型指定具体类型后,可以传入该类型或者其子类型
    Pig<A> aPig = new Pig<A>(new A());
    aPig.f();
    Pig<A> aPig2 = new Pig<A>(new B());
    aPig2.f();

    //3.泛型的使用形式
    ArrayList<Integer> list1 = new ArrayList<Integer>();
    List<Integer> list2 = new ArrayList<Integer>();
    //实际开发中,我们往往简写,推荐写法,编译器会进行类型推断
    ArrayList<Integer> list3 = new ArrayList<>();
    List<Integer> list4 = new ArrayList<>();

    ArrayList<Pig> pigs = new ArrayList<>();


    //4.如果是下面的写法,泛型默认是Object E 等价 ArrayList<Object> arrayList = new ArrayList();
    ArrayList arrayList = new ArrayList();



    /**
    class Tiger<E> {
      Object e;

      public Tiger() {}
      public Tiger(Object e) {
        this.e = e;
      }
    }
    */
    Tiger tiger = new Tiger();
  }
}

class Tiger<E> {
  E e;

  public Tiger() {}
  public Tiger(E e) {
    this.e = e;
  }
}

class A{}
class B extends A{}

class Pig<E>{
  E e;

  public Pig(E e) {
    this.e = e;
  }

  public void  f() {
    //运行类型
    System.out.println(e.getClass());
  }
}

泛型方法

  1. 语法格式:[修饰符] <类型参数列表> 返回值类型 方法名(形参列表)
  2. 泛型方法可以是静态方法,也可以是非静态方法
  3. 静态方法如果要用泛型,只能使用泛型方法的形式
  4. 泛型方法的类型形参只适用于当前方法,和别的方法无关
  5. 泛型方法的泛型形参由调用该方法时实参的类型决定,此时实参,即决定了泛型方法形参的值,又决定了泛型方法形参的类型
  6. 泛型方法的泛型形参也不能是指定为基本数据类型,可以用它的包装类,也不能用于异常类型

⭐ 泛型课堂练习题

public class Generic05 {
  public static void main(String[] args) {
    ArrayList<Employee> employee = new ArrayList<>();
    employee.add(new Employee("tom", 2632, new MyDate(2000,12,13)));
    employee.add(new Employee("jack", 1635, new MyDate(1952,10,15)));
    employee.add(new Employee("张三", 35260, new MyDate(1995,11,18)));

    System.out.println("employee=" + employee);
    //使用匿名内部类,实现排序
    employee.sort(new Comparator<Employee>() {
      @Override
      public int compare(Employee emp1, Employee emp2) {
        //先对传入数据的参数进行验证
        if(!(emp1 instanceof Employee && emp2 instanceof Employee)) {
          //throw new RuntimeException("类型不正确...");
          System.out.println("类型不正确...");
          return 0;
        }
        //比较name
        int i = emp1.getName().compareTo(emp2.getName());
        if (i != 0) {
          return i;
        }
        //下面是对 birthday的比较,因此我们最好把这个比较,放在MyDate类完成
        return emp1.getBirthdays().compareTo(emp2.getBirthdays());
      }
    });

    System.out.println("排序后的结果=====================================");
    System.out.println(employee);
  }
}

class Employee {
  private String name;
  private double salary;
  private MyDate birthdays;

  public Employee(String name, double salary, MyDate birthdays) {
    this.name = name;
    this.salary = salary;
    this.birthdays = birthdays;
  }

  public String getName() {
    return name;
  }

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

  public double getSalary() {
    return salary;
  }

  public void setSalary(double salary) {
    this.salary = salary;
  }

  public MyDate getBirthdays() {
    return birthdays;
  }

  public void setBirthdays(MyDate birthdays) {
    this.birthdays = birthdays;
  }

  @Override
  public String toString() {
    return "\nEmployee{" +
      "name='" + name + '\'' +
      ", salary=" + salary +
      ", birthdays=" + birthdays +
      '}';
  }


}

class MyDate implements Comparable<MyDate> {
  private int year;
  private int month;
  private int day;

  public MyDate(int year, int month, int day) {
    this.year = year;
    this.month = month;
    this.day = day;
  }

  public int getYear() {
    return year;
  }

  public void setYear(int year) {
    this.year = year;
  }

  public int getMonth() {
    return month;
  }

  public void setMonth(int month) {
    this.month = month;
  }

  public int getDay() {
    return day;
  }

  public void setDay(int day) {
    this.day = day;
  }

  @Override
  public String toString() {
    return "MyDate{" +
      "year=" + year +
      ", month=" + month +
      ", day=" + day +
      '}';
  }


  @Override
  /**
   * 实现year-month-day比较
   */
  public int compareTo(MyDate o) {
    //如果name相同,就比较 birthdays - year
    int yearMinus = year - o.getYear();
    if (yearMinus != 0) {
      return yearMinus;
    }
    //如果year相同就比较 month
    int monthMinus = month - o.getMonth();
    if (monthMinus != 0) {
      return monthMinus;
    }
    return day - o.getDay();
  }
}

自定义泛型

基本语法
class 类名<T,R...> {//...表示可以定义多个泛型
  成员
}
注意细节
  1. 普通成员可以使用泛型(属性、方法)
  2. 使用泛型的数组,不能初始化
  3. 静态方法中不能使用类的泛型
  4. 泛型的类型,是在创建对象时确定的(因为创建对象时,需要指定确定类型)
  5. 如果在创建对象时,没有指定类型,默认为 Object
package com.generic;

import org.junit.jupiter.api.Test;

import java.util.*;

public class HomeWork01 {
    public void main(String[] args) {

    }

    @Test
    public void testList() {
       DAO<User> dao = new DAO<>();
       dao.save("001", new User(1, 18, "jack"));
       dao.save("002", new User(2, 23, "king"));
       dao.save("003", new User(3, 26, "smith"));

       List<User> list = dao.list();
       System.out.println("user=" + list);

       dao.update("003", new User(1, 18, "milan"));

        List<User> list1 = dao.list();
        System.out.println("更新后=" + list1);

        dao.delete("001");
        List<User> list2 = dao.list();
        System.out.println("删除后=" + list2);

        System.out.println("获取方法=" + dao.get("002"));
    }
}

/**
 * 编写泛型类
 * @param <T> 自定义泛型
 */
class DAO<T> {
    private Map<String, T> map = new HashMap<>();

    public T get(String id) {
        return map.get(id);
    }
    public void save(String id, T entity) {
        map.put(id, entity);
    }
   public void update(String id, T entity) {
        map.put(id, entity);
   }
   public void delete(String id){
        map.remove(id);
   }
    /**
     * 遍历 map,将map的所有的value(entity)封装到 ArrayList 返回即可
     * @return map 中所有存放的T对象
     */
   public List<T> list() {
       // 创建ArrayList
       List <T> list = new ArrayList<>();
       //遍历 map
       Set<String> keySet = map.keySet();
       for (String key : keySet) {
           //也可以直接使用类的get方法
           list.add(map.get(key));
       }
       return list;
   }
}

/**
 * 定义用户类
 */
class User{
    private int id;
    private int age;
    private String name;

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

    public int getAge() {
        return age;
    }

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

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

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

泛型的通配符

  1. ? 代表任意类型,如果是集合,例如ArrayList<?>,这样的集合不能添加元素
  2. ? extends 父类 上限
    • ?代表父类本身或父类的子类类型可以
    • 如果是集合,例如ArrayList<? extends 父类>,这样的集合不可以添加
  3. ? super 子类 下限
    • ?代表子类本身或子类的父类类型可以
    • 如果是集合,例如ArrayList<? super 子类>,这样的集合,可以添加,仅限于添加子类或子类的子类对象
package com.generic;

import java.util.ArrayList;
import java.util.List;

public class GenericExtends {

    public static void main(String[] args) {
        // 泛型没有继承性
//        List<Object> list = new ArrayList<String>();

        //举例下面三个方法的使用
        List<Object> list1 = new ArrayList<>();
        List<String> list2 = new ArrayList<>();
        List<AA> list3 = new ArrayList<>();
        List<BB> list4 = new ArrayList<>();
        List<CC> list5 = new ArrayList<>();
        //List<?> 表示任意的泛型类型都可以接收
        printCollection1(list1);
        printCollection1(list2);
        printCollection1(list3);
        printCollection1(list4);
        printCollection1(list5);
        //List<? extends AA> c 可以接收AA类型或他的子类
        //printCollection2(list1); //错误
        //printCollection2(list2); //错误
        printCollection2(list3);
        printCollection2(list4);
        printCollection2(list5);
        //List<? super AA> c  ?super 子类类名AA:支持AA类以及AA类的父类,不限于直接父类,规定了泛型的下限
        printCollection3(list1);
//        printCollection3(list2);
        printCollection3(list3);
//        printCollection3(list4);
//        printCollection3(list5);

    }

    /**
     * List<?> 表示任意的泛型类型都可以接收
     */
    public static void printCollection1(List<?>c) {
        for (Object object : c) { //通配符,取出时,就是 Object
            System.out.println(object);
        }
    }

    /**
     * ? extends AA 表示上限,可以接收AA或者AA的子类
     */
    public static void printCollection2(List<? extends AA> c) {
        for (Object object : c) { //通配符,取出时,就是 Object
            System.out.println(object);
        }
    }

    /**
     * ?super 子类类名AA:支持AA类以及AA类的父类,不限于直接父类,规定了泛型的下限
     */
    public static void printCollection3(List<? super AA> c) {
        for (Object object : c) { //通配符,取出时,就是 Object
            System.out.println(object);
        }
    }
}

class AA{

}
class BB extends AA{

}

class CC extends BB{

}

1.11 注解

概念

  • 代码级别的注释
  • 给代码读取的注释,是一种特殊的元数据

注解的四种方式

1. 编译器的格式检查

  • @Override :告知编译器对该方法按照“重写”的要求进行格式检查
  • @SuppressWarnings :告知编译抑制警告
import java.util.ArrayList;
import java.util.List;
public class annotation {
  //  1. 当我们不希望看到这些警告信息的时候可以使用 @SuppressWarnings 注解来抑制警告信息
  //  2.在{""} 中,写入希望不显示警告的信息
  //  3. 可以指定的警告类型
  //  4. 关于@SuppressWarnings 作用范围和你放置位置有关
  @SuppressWarnings({"all","rawtypes", "unchecked", "unused"})
  public static void main(String[] args) {
    List list = new ArrayList();
    list.add("tom");
    list.add("jack");
    list.add("mary");
    System.out.println(list.get(0));
  }
}
  • @Deprecated :告知编译器某个元素已过时,有人使用了就弹出警告
@Deprecated
class A {

}

2. 文档注释,可以结合 javadoc.exe 进行查看

  • @version 指定当前版本
  • @author 指定作者
  • @since 指定从哪个版本开始
  • @see 另请参阅
  • param 指定当前方法的形参信息,可以多个,只有方法有形参才能标记。格式:@param 形参名 形参类型 形参的描述信息
  • @retrun 指定当前方法的返回值,一个方法只能有一个,如果方法是 void 就不能标记 @return 格式:@return 返回值的类型 返回值的描述
  • @exception 指定当前方法抛出异常的信息,可以是多个,只有方法抛出异常才能标记,格式:@exception 异常类型 异常的描述

3. JUnit 的单元测试

  • 白盒测试,程序员自己的测试,在程序员知道当前的代码的功能
  • @Test 加在方法上,这个方法必须是公共的、无参、无返回值,不能是 static
  • @Before 在 @Test 标记的方法之前运行
  • @After 在 @Test 标记的方法之后运行

4. 各大框架等替代配置文件

注解的三个部分

  1. 声明:一般都是别人声明的

  2. 使用:暂无

  3. 读取

    • 例如:@Override 等,由 javac.exe
    • 例如:@author,@param 等,由 javadoc.exe
    • 例如:@Test 等,由 JUnit 相关的类读取
    • 例如:@WebServlet 等,由 Tomcat 读取

如果自己要读取,通过反射,而且只能读取@Retention(RetentionPolicy.RUNTIME)

注解的声明

1. 有参

声明格式:

@元注解
[修饰符] @interface 注解名{}

使用格式:@注解名

2. 无参

声明格式

@元注解
[修饰符] @interface 注解名{
  配置参数
}

配置参数

  • 格式:数据类型 参数名();
  • 一个注解可以有多个配置参数
  • 配置参数可以有默认值,数据类型 参数名() default 默认值
  • 配置参数的类型要求:类型只能是八种基础数据类型,String 类型、Class 类型、enum 类型、Annotation 类型、以上所有类型的数组

使用格式

@注解(参数赋值)
  • 如果配置参数有默认值,那么可以在 使用时不需要赋值
  • 如果配置参数只有一个,而且名称是 value,那么可以在赋值时省略 value=
  • 参数赋值的格式
    • 参数名 = 参数值
    • 如果多个使用,分割
    • 如果配置参数的类型是数组类型
      • 如果只有一个元素,那么可以省略{}
      • 如果是多个元素,那么需要{}

元注解

修饰注解的注解称为元注解,在 java.lang.annotation 包

  1. @Target

    • 指定某个注解他的使用目标位置
    • 如何指定它
      • 它的配置参数的类型是一个枚举数据 ElementType 枚举类型,常量对象有:TYPE、FLELD、METHOD
      • 配置参数的名称是 value
      • 如果只有一个 @Target(ElementType.METHOD)
      • 如果是多个 @Target({ElementType.METHOD,ElementType.METHOD,...})
  2. @Retention

    • 指定某个注解的声明周期,可以保留到什么阶段
    • 如何指定它
      • 它的配置参数的类型是一个枚举类型 RetentionPolicy 类型,常量对象有三个:SOURCE、CLASS、RUNTIME
      • 配置参数的名称是 value
      • @Rentention(RetentionPolicy.RUNBTIME)
  3. @Documented 表示是否 javadoc 读取

  4. @Inherited 是否被子类继承

TODO:

  • IO 流
  • 多线程
  • 反射
  • 网络编程
  • Lambda 表达式与 StramAPI
  • Optional