本文章参考B站 Java入门基础视频教程,java零基础自学首选黑马程序员Java入门教程(含Java项目和Java真题),仅供个人学习使用,部分内容为本人自己见解,与黑马程序员无关。

1、常用 API

什么是API

  • API(Application Programming interface) 应用程序编程接口。
  • 简单来说:就是Java帮我们已经写好的一些方法,我们直接拿过来用就可以了。

1.1、Object

Object类的作用

  • Object类的方法是一切子类对象都可以直接使用的,所以我们要学习Object类的方法。
  • 一个类要么默认继承了Object类,要么间接继承了Object类,Object类是Java中的祖宗类。

Object类的常用方法

方法名

说明

public String toString()

默认是返回当前对象在堆内存中的地址信息:类的全限名@内存地址

public boolean equals(Object o)

默认是比较当前对象与另一个对象的地址是否相同,相同返回true,不同返回false

1.1.1、toString 方法

代码展示

import java.util.Objects;

public class Student { //extends Object{
    private String name;
    private char sex;
    private int age;

    public Student() {
    }

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

    public String getName() {
        return name;
    }

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

    public char getSex() {
        return sex;
    }

    public void setSex(char sex) {
        this.sex = sex;
    }

    public int getAge() {
        return age;
    }

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

}


/**
    目标:掌握Object类中toString方法的使用。
 */
public class Test1 {
    public static void main(String[] args) {
        Student s = new Student("周雄", '男', 19);
        // String rs = s.toString();
        // System.out.println(rs);

        // System.out.println(s.toString());

        // 直接输出对象变量,默认可以省略toString调用不写的
        System.out.println(s);
    }
}

输出结果

com.itheima.d9_api_object.Student@1b6d3586

问题引出

  • 开发中直接输出对象,默认输出对象的地址其实是毫无意义的。
  • 开发中输出对象变量,更多的时候是希望看到对象的内容数据而不是对象的地址信息。

toString() 存在的意义

  • toString 方法本身是无意义的

  • 父类 toString 方法存在的意义就是为了被子类重写,以便返回对象的内容信息,而不是地址信息!!

代码改进

Student 类重写 toString 方法

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

输出结果

Student{name='周雄', sex=男, age=19}

1.1.2、equals 方法

代码展示

/**
    目标:掌握Object类中equals方法的使用。
 */
public class Test2 {
    public static void main(String[] args) {
        Student s1 = new Student("周雄", '男', 19);
        Student s2 = new Student("周雄", '男', 19);
        // equals默认是比较2个对象的地址是否相同,子类重写后会调用子类重写的来比较内容是否相同。
        System.out.println(s1.equals(s2));
        System.out.println(s1 == s2);

        System.out.println(Objects.equals(s1, s2));
    }
}

输出结果

false
false
false

问题思考

  • 直接比较两个对象的地址是否相同完全可以用 “==” 替代 equals。

equals 存在的意义

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

代码改进

Student 类中重写 equals 方法

/**
     定制相等规则。
     两个对象的内容一样就认为是相等的
     s1.equals(s2)
     比较者:s1 == this
     被比较者: s2 ==> o
     */
    @Override
    public boolean equals(Object o) {
        // 1、判断是否是同一个对象比较,如果是返回true。
        if (this == o) return true;
        // 2、如果o是null返回false  如果o不是学生类型返回false  ...Student !=  ..Pig
        if (o == null || this.getClass() != o.getClass()) return false;
        // 3、说明o一定是学生类型而且不为null
        Student student = (Student) o;
        return sex == student.sex && age == student.age && Objects.equals(name, student.name);
    }

输出结果

true
false
true

1.2、Objects

概述

  • Objects 是一个工具类,提供了一些方法去完成一些功能。

常见方法

方法名

说明

public static boolean equals(Object a, Object b)

比较两个对象的,底层会先进行非空判断,从而可以避免空指针异常。再进行equals比较

public static boolean isNull(Object obj)

判断变量是否为null ,为null返回true ,反之

Object.equals 方法缺陷

  • 空值用 equals 比较会出 Exception in thread “main” java.lang.NullPointerException

代码演示

import java.util.Objects;

/**
    目标:掌握objects类的常用方法:equals
 */
public class Test {
    public static void main(String[] args) {
        String s1 = null;
        String s2 = new String("itheima");

        // System.out.println(s1.equals(s2));   // 留下了隐患,可能出现空指针异常。

        System.out.println(Objects.equals(s1, s2)); // 更安全,结果也是对的!

        /**
             Objects:
             public static boolean equals(Object a, Object b) {
                     return (a == b) || (a != null && a.equals(b));
             }
         */

        System.out.println(Objects.isNull(s1)); // true
        System.out.println(s1 == null); // true

        System.out.println(Objects.isNull(s2)); // false
        System.out.println(s2 == null); // false

    }
}

输出结果

false
true
true
false
false

对象进行内容比较的时候建议使用什么?为什么?

  • 建议使用Objects提供的equals方法。
  • 比较的结果是一样的,但是更安全。

源码分析

public static boolean equals(Object a, Object b) {
    return (a == b) || (a != null && a.equals(b));
}

可以发现 equals 方法已经做了非空判断。

1.3、StringBuilder

StringBuilder 概述

  • StringBuilder是一个可变的字符串类,我们可以把它看成是一个对象容器。
  • 作用:提高字符串的操作效率,如拼接、修改等。

StringBuilder 构造器

名称

说明

public StringBuilder()

创建一个空白的可变的字符串对象,不包含任何内容

public StringBuilder(String str)

创建一个指定字符串内容的可变字符串对象

StringBuilder常用方法

方法名称

说明

public StringBuilder append(任意类型)

添加数据并返回StringBuilder对象本身

public StringBuilder reverse()

将对象的内容反转

public int length()

返回对象内容长度

public String toString()

通过toString()就可以实现把StringBuilder转换为String

代码展示

/**
    目标:学会使用StringBuilder操作字符串,最终还需要知道它性能好的原因
 */
public class StringBuilderDemo1 {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder(); // ""
        sb.append("a");
        sb.append("b");
        sb.append("c");
        sb.append(1);
        sb.append(false);
        sb.append(3.3);
        sb.append("abc");
        System.out.println(sb);

        StringBuilder sb1 = new StringBuilder();
        // 支持链式编程
        sb1.append("a").append("b").append("c").append("我爱你中国");
        System.out.println(sb1);

        // 反转
        sb1.reverse().append("110");
        System.out.println(sb1);

        System.out.println(sb1.length());

        // 注意:StringBuilder只是拼接字符串的手段:效率好。
        // 最终的目的还是要恢复成String类型。
        StringBuilder sb2 = new StringBuilder();
        sb2.append("123").append("456");
        // 恢复成String类型
        String rs = sb2.toString();
        check(rs);
    }

    public static void check(String data){
        System.out.println(data);
    }
}

输出结果

abc1false3.3abc
abc我爱你中国
国中你爱我cba110
11
123456

为什么拼接、反转字符串建议使用 StringBuilder

  • String :内容是不可变的、拼接字符串性能差。
  • StringBuilder:内容是可变的、拼接字符串性能好、代码优雅。
  • 定义字符串使用 String
  • 拼接、修改等操作字符串使用 StringBuilder

原理展示


案例:打印整型数组内容

需求:

  • 设计一个方法用于输出任意整型数组的内容,要求输出成如下格式:“该数组内容为:[11, 22, 33, 44, 55]”

分析:

  1. 定义一个方法,要求该方法能够接收数组,并输出数组内容。 —> 需要参数吗?需要返回值类型申明吗?
  2. 定义一个静态初始化的数组,调用该方法,并传入该数组。

代码展示

public class StringBuilderTest2 {
    public static void main(String[] args) {
        int[] arr1 = null;
        System.out.println(toString(arr1));

        int[] arr2 = {10, 88, 99};
        System.out.println(toString(arr2));

        int[] arr3 = {};
        System.out.println(toString(arr3));
    }

    /**
       1、定义方法接收任意整型数组,返回数组内容格式
     */
    public static String toString(int[] arr){
       if(arr != null){
            // 2、开始拼接内容。
           StringBuilder sb = new StringBuilder("[");
           for (int i = 0; i < arr.length; i++) {
               sb.append(arr[i] ).append(i == arr.length - 1 ? "" : ", ");
           }
           sb.append("]");
           return sb.toString();
       }else {
           return null;
       }
    }
}

输出结果

null
[10, 88, 99]
[]

1.4、Math

Math类

  • 包含执行基本数字运算的方法,Math类没有提供公开的构造器。
  • 类的成员是否都是静态的,通过类名就可以直接调用。

Math 类的常用方法

方法名

说明

public static int abs(int a)

获取参数绝对值

public static double ceil(double a)

向上取整

public static double floor(double a)

向下取整

public static int round(float a)

四舍五入

public static int max(int a,int b)

获取两个int值中的较大值

public static double pow(double a,double b)

返回a的b次幂的值

public static double random()

返回值为double的随机值,范围[0.0,1.0)

代码展示

/**
    目标:Math类的使用。
    Math用于做数学运算。
    Math类中的方法全部是静态方法,直接用类名调用即可。
    方法:
          方法名                                          说明
          public static int abs(int a)                   获取参数a的绝对值:
          public static double ceil(double a)            向上取整
          public static double floor(double a)           向下取整
          public static double pow(double a, double b)   获取a的b次幂
          public static long round(double a)             四舍五入取整
    小结:
          记住。
 */
public class MathDemo {
    public static void main(String[] args) {
        // 1.取绝对值:返回正数
        System.out.println(Math.abs(10)); // 10
        System.out.println(Math.abs(-10.3)); // 10.3

        // 2.向上取整: 5
        System.out.println(Math.ceil(4.00000001)); // 5.0
        System.out.println(Math.ceil(4.0)); // 4.0
        // 3.向下取整:4
        System.out.println(Math.floor(4.99999999)); // 4.0
        System.out.println(Math.floor(4.0)); // 4.0

        // 4.求指数次方
        System.out.println(Math.pow(2 , 3)); // 2^3 = 8.0
        // 5.四舍五入 10
        System.out.println(Math.round(4.49999)); // 4
        System.out.println(Math.round(4.500001)); // 5

        System.out.println(Math.random());  // 0.0 - 1.0 (包前不包后)

        // 拓展: 3 - 9 之间的随机数  (0 - 6) + 3
        //  [0 - 6] + 3
        int data =  (int)(Math.random() * 7) + 3;
        System.out.println(data);


    }
}

输出结果

10
10.3
5.0
4.0
4.0
4.0
8.0
4
5
0.7219171930691749
3

1.5、System

System 类概述

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

System 类的常用方法

方法名

说明

public static void exit(int status)

终止当前运行的 Java 虚拟机,非零表示异常终止

public static long currentTimeMillis()

返回当前系统的时间毫秒值形式

public static void arraycopy(数据源数组, 起始索引, 目的地数组, 起始索引, 拷贝个数)

数组拷贝

时间毫秒值

  • 计算机认为时间是有起点的,起始时间: 1970年1月1日 00:00:00
  • 时间毫秒值:指的是从1970年1月1日 00:00:00走到此刻的总的毫秒数,应该是很大的。 1s = 1000ms。

原因

  • 1969年8月,贝尔实验室的程序员肯汤普逊利用妻儿离开一个月的机会,开始着手创造一个全新的革命性的操作系统,他使用B编译语言在老旧的PDP-7机器上开发出了Unix的一个版本。
  • 随后,汤普逊和同事丹尼斯里奇改进了B语言,开发出了C语言,重写了UNIX。
  • 1970年1月1日 算C语言的生日

代码展示

import java.util.Arrays;

/**
    目标:System系统类的使用。
    System代表当前系统。(虚拟机系统)
    静态方法:
        1.public static void exit(int status):终止JVM虚拟机,非0是异常终止。
        2.public static long currentTimeMillis():获取当前系统此刻时间毫秒值。(重点)
        3.可以做数组的拷贝。
             arraycopy(Object var0, int var1, Object var2, int var3, int var4);
             * 参数一:原数组
             * 参数二:从原数组的哪个位置开始赋值。
             * 参数三:目标数组
             * 参数四:赋值到目标数组的哪个位置
             * 参数五:赋值几个。
 */
public class SystemDemo {
    public static void main(String[] args) {
        System.out.println("程序开始。。。");

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

        // 2、计算机认为时间有起源:返回1970-1-1 00:00:00 走到此刻的总的毫秒值:时间毫秒值。
        long time = System.currentTimeMillis();
        System.out.println(time);

        long startTime = System.currentTimeMillis();
        // 进行时间的计算:性能分析
        for (int i = 0; i < 100000; i++) {
            System.out.println("输出:" + i);
        }
        long endTime = System.currentTimeMillis();
        System.out.println((endTime - startTime)/1000.0 + "s");


        // 3、做数组拷贝(了解)
        /**
         arraycopy(Object src,  int  srcPos,
         Object dest, int destPos,
         int length)
         参数一:被拷贝的数组
         参数二:从哪个索引位置开始拷贝
         参数三:复制的目标数组
         参数四:粘贴位置
         参数五:拷贝元素的个数
         */
        int[] arr1 = {10, 20, 30, 40, 50, 60, 70};
        int[] arr2 = new int[6]; // [0, 0, 0, 0, 0, 0] ==>  [0, 0, 40, 50, 60, 0]
        System.arraycopy(arr1, 3, arr2, 2, 3);
        System.out.println(Arrays.toString(arr2));

        System.out.println("-------------------");
        double i = 10.0;
        double j = 3.0;

//
//        System.out.println(k1);

        System.out.println("程序结束。。。。");
    }
}

1.6、BigDecimal

BigDecimal 作用

  • 用于解决浮点型运算精度失真的问题

使用步骤

  • 创建对象BigDecimal封装浮点型数据(最好的方式是调用方法)

    public static BigDecimal valueOf(double val); // 包装浮点数成为BigDecimal对象。

valueOf 源码还是调用了 toString

BigDecima常用API

方法名

说明

public BigDecimal add(BigDecimal b)

加法

public BigDecimal subtract(BigDecimal b)

减法

public BigDecimal multiply(BigDecimal b)

乘法

public BigDecimal divide(BigDecimal b)

除法

public BigDecimal divide (另一个BigDecimal对象,精确几位,舍入模式)

除法

代码展示

import java.math.BigDecimal;
import java.math.RoundingMode;

/**
    目标:BigDecimal大数据类。

    引入:
        浮点型运算的时候直接+  * / 可能会出现数据失真(精度问题)。
        BigDecimal可以解决浮点型运算数据失真的问题。

    BigDicimal类:
        包:java.math.
        创建对象的方式(最好的方式:)
              public static BigDecimal valueOf(double val) :包装浮点数成为大数据对象。
        方法声明
              public BigDecimal add(BigDecimal value)       加法运算
              public BigDecimal subtract(BigDecimal value)  减法运算 
              public BigDecimal multiply(BigDecimal value)  乘法运算 
              public BigDecimal divide(BigDecimal value)    除法运算
              public double doubleValue(): 把BigDecimal转换成double类型。
 */
public class BigDecimalDemo {
    public static void main(String[] args) {
        // 浮点型运算的时候直接+  * / 可能会出现数据失真(精度问题)。
        System.out.println(0.09 + 0.01);
        System.out.println(1.0 - 0.32);
        System.out.println(1.015 * 100);
        System.out.println(1.301 / 100);

        System.out.println("-------------------------");
        double a = 0.1;
        double b = 0.2;
        double c = a + b;
        System.out.println(c);
        System.out.println("--------------------------");
        // 包装浮点型数据成为大数据对象 BigDeciaml
        BigDecimal a1 = BigDecimal.valueOf(a);
        BigDecimal b1 = BigDecimal.valueOf(b);
        BigDecimal c1 = a1.add(b1);
        // BigDecimal c1 = a1.subtract(b1);
        // BigDecimal c1 = a1.multiply(b1);
        // BigDecimal c1 = a1.divide(b1);
        System.out.println(c1);

        // 目的:double
        double rs = c1.doubleValue();
        System.out.println(rs);

        // 注意事项:BigDecimal是一定要精度运算的
        BigDecimal a11 = BigDecimal.valueOf(10.0);
        BigDecimal b11 = BigDecimal.valueOf(3.0);
        /**
           参数一:除数 参数二:保留小数位数  参数三:舍入模式
         */
        BigDecimal c11 = a11.divide(b11, 2, RoundingMode.HALF_UP); // 3.3333333333
        System.out.println(c11);


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

输出结果

0.09999999999999999
0.6799999999999999
101.49999999999999
0.013009999999999999
-------------------------
0.30000000000000004
--------------------------
0.3
0.3
3.33
-------------------

除法问题

  • 除法可能会导致结果为无限循环或无限不循环小数,故需要指定小数点精确到多少位,选择什么样的舍入模式

    BigDecimal divide = bd1.divide(参与运算的对象, 小数点后精确到多少位, 舍入模式);
    参数1 ,表示参与运算的 BigDecimal 对象。
    参数2 ,表示小数点后面精确到多少位
    参数3 ,舍入模式:
    BigDecimal.ROUND_UP 进一法 BigDecimal.ROUND_FLOOR 去尾法 BigDecimal.ROUND_HALF_UP 四舍五入

2、日期与时间

2.1、Date 类

Date 类概述

  • Date类代表当前所在系统的日期时间信息。

Date的构造器

名称

说明

public Date()

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

public Date(long time)

把时间毫秒值转换成Date日期对象。

Date的常用方法

名称

说明

public long getTime()

返回从1970年1月1日 00:00:00走到此刻的总的毫秒数

public void setTime(long time)

设置日期对象的时间为当前时间毫秒值对应的时间

代码展示

import java.util.Date;

/**
    目标:学会使用Date类处理时间,获取时间的信息
 */
public class DateDemo1 {
    public static void main(String[] args) {
        // 1、创建一个Date类的对象:代表系统此刻日期时间对象
        Date d = new Date();
        System.out.println(d);

        // 2、获取时间毫秒值
        long time = d.getTime();
        System.out.println(time);
//        long time1 = System.currentTimeMillis();
//        System.out.println(time1);

        System.out.println("----------------------------");
        // 1、得到当前时间
        Date d1 = new Date();
        System.out.println(d1);

        // 2、当前时间往后走 1小时  121s
        long time2 = System.currentTimeMillis();
        time2 += (60 * 60 + 121) * 1000;

        // 3、把时间毫秒值转换成对应的日期对象。
        // Date d2 = new Date(time2);
        // System.out.println(d2);

        Date d3 = new Date();
        d3.setTime(time2);
        System.out.println(d3);

    }
}

输出结果

Sat Apr 02 21:43:24 CST 2022
1648907004470
----------------------------
Sat Apr 02 21:43:24 CST 2022
Sat Apr 02 22:45:25 CST 2022

2.2、SimpleDateFormat



SimpleDateFormat 类作用

  • 可以去完成日期时间的格式化操作

构造器

构造器

说明

public SimpleDateFormat(String pattern)

构造一个SimpleDateFormat,使用指定的格式

格式化方法

格式化方法

说明

public final String format(Date date)

将日期格式化成日期/时间字符串

public final String format(Object time)

将时间毫秒值式化成日期/时间字符串

格式化的时间形式的常用的模式对应关系

代码展示

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

/**
    目标:SimpleDateFormat简单日期格式化类的使用
    格式化时间
    解析时间

 */
public class SimpleDateFormatDemo01 {
    public static void main(String[] args) {
        // 1、日期对象
        Date d = new Date();
        System.out.println(d);

        // 2、格式化这个日期对象 (指定最终格式化的形式)
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss EEE a");
        // 3、开始格式化日期对象成为喜欢的字符串形式
        String rs = sdf.format(d);
        System.out.println(rs);

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

        // 4、格式化时间毫秒值
        // 需求:请问121秒后的时间是多少
        long time1 = System.currentTimeMillis() + 121 * 1000;
        String rs2 = sdf.format(time1);
        System.out.println(rs2);

        System.out.println("------------解析字符串时间,下个代码---------------");
    }
}

输出结果

Sat Apr 02 21:48:06 CST 2022
2022年04月02日 21:48:06 星期六 下午
----------------------------
2022年04月02日 21:50:07 星期六 下午
------------解析字符串时间,下个代码---------------

解析字符串时间成为日期对象

解析方法

说明

public Date parse(String source)

从给定字符串的开始解析文本以生成日期

案例

  • 请计算出 2021年08月06日11点11分11秒,往后走2天14小时49分06秒后的时间是多少?

代码展示

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

public class SimpleDateFormatDemo2 {
    public static void main(String[] args) throws ParseException {
        // 目标: 学会使用SimpleDateFormat解析字符串时间成为日期对象。
        // 有一个时间 2021年08月06日 11:11:11 往后 2天 14小时 49分 06秒后的时间是多少。
        // 1、把字符串时间拿到程序中来
        String dateStr = "2021年08月06日 11:11:11";

        // 2、把字符串时间解析成日期对象(本节的重点):形式必须与被解析时间的形式完全一样,否则运行时解析报错!
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
        Date d = sdf.parse(dateStr);

        // 3、往后走2天 14小时 49分 06秒
        long time = d.getTime() + (2L*24*60*60 + 14*60*60 + 49*60 + 6) * 1000;

        // 4、格式化这个时间毫秒值就是结果
        System.out.println(sdf.format(time));
    }
}

输出结果

2021年08月09日 02:00:17

案例:秒杀活动

需求:

  • 某购物网站举办秒杀活动,开始时间和结束时间如左图所示,当前活动结束后,系统记录到2位用户的付款时间分别如下:

  • 小贾下单并付款的时间为:2020年11月11日 0:03:47

  • 小皮下单并付款的时间为:2020年11月11日 0:10:11

  • 规则:顾客的付款时间必须在秒杀时间之内,请判断出两位顾客是否秒杀成功。

分析:

  1. 把4个字符串形式的时间解析成日期对象。
  2. 判断小贾和小皮的时间是否在秒杀时间范围之内,并给出相应的提示。

代码展示

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

public class SimpleDateFormatTest3 {
    public static void main(String[] args) throws ParseException {
        // 1、开始 和 结束时间
        String startTime = "2021-11-11 00:00:00";
        String endTime = "2021-11-11 00:10:00";

        // 2、小贾 小皮
        String xiaoJia =  "2021-11-11 00:03:47";
        String xiaoPi =  "2021-11-11 00:10:11";

        // 3、解析他们的时间
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date d1 = sdf.parse(startTime);
        Date d2 = sdf.parse(endTime);
        Date d3 = sdf.parse(xiaoJia);
        Date d4 = sdf.parse(xiaoPi);

        if(d3.after(d1) && d3.before(d2)){
            System.out.println("小贾秒杀成功,可以发货了!");
        }else {
            System.out.println("小贾秒杀失败!");
        }

        if(d4.after(d1) && d4.before(d2)){
            System.out.println("小皮秒杀成功,可以发货了!");
        }else {
            System.out.println("小皮秒杀失败!");
        }
    }
}

输出结果

小贾秒杀成功,可以发货了!
小皮秒杀失败!

2.3、Calendar

Calendar概述

  • Calendar代表了系统此刻日期对应的日历对象。
  • Calendar是一个抽象类,不能直接创建对象。

Calendar日历类创建日历对象的方法:

方法名

说明

public static Calendar getInstance()

获取当前日历对象

Calendar常用方法

方法名

说明

public int get(int field)

取日期中的某个字段信息。

public void set(int field,int value)

修改日历的某个字段信息。

public void add(int field,int amount)

为某个字段增加/减少指定的值

public final Date getTime()

拿到此刻日期对象。

public long getTimeInMillis()

拿到此刻时间毫秒值

注意

  • calendar是可变日期对象,一旦修改后其对象本身表示的时间将产生变化。
  • 使用 JDK8 新增日期类可以解决这个问题,后面介绍

代码展示

import java.util.Calendar;
import java.util.Date;

/**
    目标:日历类Calendar的使用,可以得到更加丰富的信息。

    Calendar代表了系统此刻日期对应的日历对象。
    Calendar是一个抽象类,不能直接创建对象。
    Calendar日历类创建日历对象的语法:
        Calendar rightNow = Calendar.getInstance();
    Calendar的方法:
        1.public static Calendar getInstance(): 返回一个日历类的对象。
        2.public int get(int field):取日期中的某个字段信息。
        3.public void set(int field,int value):修改日历的某个字段信息。
        4.public void add(int field,int amount):为某个字段增加/减少指定的值
        5.public final Date getTime(): 拿到此刻日期对象。
        6.public long getTimeInMillis(): 拿到此刻时间毫秒值
    小结:
        记住。
 */
public class CalendarDemo{
    public static void main(String[] args) {
        // 1、拿到系统此刻日历对象
        Calendar cal = Calendar.getInstance();
        System.out.println(cal);

        // 2、获取日历的信息:public int get(int field):取日期中的某个字段信息。
        int year = cal.get(Calendar.YEAR);
        System.out.println(year);

        int mm = cal.get(Calendar.MONTH) + 1;
        System.out.println(mm);

        int days = cal.get(Calendar.DAY_OF_YEAR) ;
        System.out.println(days);

        // 3、public void set(int field,int value):修改日历的某个字段信息。
        // cal.set(Calendar.HOUR , 12);
        // System.out.println(cal);

        // 4.public void add(int field,int amount):为某个字段增加/减少指定的值
        // 请问64天后是什么时间
        cal.add(Calendar.DAY_OF_YEAR , 64);
        cal.add(Calendar.MINUTE , 59);

        //  5.public final Date getTime(): 拿到此刻日期对象。
        Date d = cal.getTime();
        System.out.println(d);

        //  6.public long getTimeInMillis(): 拿到此刻时间毫秒值
        long time = cal.getTimeInMillis();
        System.out.println(time);

    }
}

输出结果

java.util.GregorianCalendar[time=1648908569874,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=31,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2022,MONTH=3,WEEK_OF_YEAR=14,WEEK_OF_MONTH=1,DAY_OF_MONTH=2,DAY_OF_YEAR=92,DAY_OF_WEEK=7,DAY_OF_WEEK_IN_MONTH=1,AM_PM=1,HOUR=10,HOUR_OF_DAY=22,MINUTE=9,SECOND=29,MILLISECOND=874,ZONE_OFFSET=28800000,DST_OFFSET=0]
2022
4
92
Sun Jun 05 23:08:29 CST 2022
1654441709874

2.4、JDK8 新增日期类

概述

  • 从Java 8开始,java.time包提供了新的日期和时间API,主要涉及的类型有:
  • 新增的API严格区分了时刻、本地日期、本地时间,并且,对日期和时间进行运算更加方便。
  • 其次,新API的类型几乎全部是不变类型(和String的使用类似),可以放心使用不必担心被修改。

2.4.1、LocalDate、LocalTime、LocalDateTime

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

构建对象的方式

LocalDate、LocalTime、LocalDateTime 获取信息的方法

方法名

说明

public int geYear()

获取年

public int getMonthValue()

获取月份(1-12)

Public int getDayOfMonth()

获取月中第几天乘法

Public int getDayOfYear()

获取年中第几天

Public DayOfWeek getDayOfWeek()

获取星期

转换相关的API

修改相关的API

  • LocalDateTime 综合了 LocalDate 和 LocalTime 里面的方法,所以下面只用 LocalDate 和 LocalTime 来举例。
  • 这些方法返回的是一个新的实例引用,因为LocalDateTime 、LocalDate 、LocalTime 都是不可变的。

方法名

说明

plusDays, plusWeeks, plusMonths, plusYears

向当前 LocalDate 对象添加几天、 几周、几个月、几年

minusDays, minusWeeks, minusMonths, minusYears

从当前 LocalDate 对象减去几天、 几周、几个月、几年

withDayOfMonth, withDayOfYear, withMonth, withYear

将月份天数、年份天数、月份、年 份 修 改 为 指 定 的 值 并 返 回 新 的 LocalDate 对象

isBefore, isAfter

比较两个 LocalDate

代码展示

LocalDate

import java.time.LocalDate;
import java.time.Month;

public class Demo01LocalDate {
    public static void main(String[] args) {
        // 1、获取本地日期对象。
        LocalDate nowDate = LocalDate.now();
        System.out.println("今天的日期:" + nowDate);//今天的日期:

        int year = nowDate.getYear();
        System.out.println("year:" + year);


        int month = nowDate.getMonthValue();
        System.out.println("month:" + month);

        int day = nowDate.getDayOfMonth();
        System.out.println("day:" + day);

        //当年的第几天
        int dayOfYear = nowDate.getDayOfYear();
        System.out.println("dayOfYear:" + dayOfYear);

        //星期
        System.out.println(nowDate.getDayOfWeek());
        System.out.println(nowDate.getDayOfWeek().getValue());

        //月份
        System.out.println(nowDate.getMonth());//AUGUST
        System.out.println(nowDate.getMonth().getValue());//8

        System.out.println("------------------------");
        LocalDate bt = LocalDate.of(1991, 11, 11);
        System.out.println(bt);//直接传入对应的年月日
        System.out.println(LocalDate.of(1991, Month.NOVEMBER, 11));//相对上面只是把月换成了枚举
    }
}

输出结果

今天的日期:2022-04-02
year:2022
month:4
day:2
dayOfYear:92
SATURDAY
6
APRIL
4
------------------------
1991-11-11
1991-11-11

LocalTime

import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;

public class Demo02LocalTime {
    public static void main(String[] args) {
        // 1、获取本地时间对象。
        LocalTime nowTime = LocalTime.now();
        System.out.println("今天的时间:" + nowTime);//今天的时间:

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

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

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

        int nano = nowTime.getNano();//纳秒
        System.out.println("nano:" + nano);//nano:

        System.out.println("-----");
        System.out.println(LocalTime.of(8, 20));//时分
        System.out.println(LocalTime.of(8, 20, 30));//时分秒
        System.out.println(LocalTime.of(8, 20, 30, 150));//时分秒纳秒
        LocalTime mTime = LocalTime.of(8, 20, 30, 150);

        System.out.println("---------------");
        System.out.println(LocalDateTime.of(1991, 11, 11, 8, 20));
        System.out.println(LocalDateTime.of(1991, Month.NOVEMBER, 11, 8, 20));
        System.out.println(LocalDateTime.of(1991, 11, 11, 8, 20, 30));
        System.out.println(LocalDateTime.of(1991, Month.NOVEMBER, 11, 8, 20, 30));
        System.out.println(LocalDateTime.of(1991, 11, 11, 8, 20, 30, 150));
        System.out.println(LocalDateTime.of(1991, Month.NOVEMBER, 11, 8, 20, 30, 150));
    }
}

输出结果

今天的时间:22:18:20.462
hour:22
minute:18
second:20
nano:462000000
-----
08:20
08:20:30
08:20:30.000000150
---------------
1991-11-11T08:20
1991-11-11T08:20
1991-11-11T08:20:30
1991-11-11T08:20:30
1991-11-11T08:20:30.000000150
1991-11-11T08:20:30.000000150

LocalDateTime

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

public class Demo03LocalDateTime {
    public static void main(String[] args) {
        // 日期 时间
        LocalDateTime nowDateTime = LocalDateTime.now();
        System.out.println("今天是:" + nowDateTime);//今天是:
        System.out.println(nowDateTime.getYear());//年
        System.out.println(nowDateTime.getMonthValue());//月
        System.out.println(nowDateTime.getDayOfMonth());//日
        System.out.println(nowDateTime.getHour());//时
        System.out.println(nowDateTime.getMinute());//分
        System.out.println(nowDateTime.getSecond());//秒
        System.out.println(nowDateTime.getNano());//纳秒
        //日:当年的第几天
        System.out.println("dayOfYear:" + nowDateTime.getDayOfYear());//dayOfYear:249
        //星期
        System.out.println(nowDateTime.getDayOfWeek());//THURSDAY
        System.out.println(nowDateTime.getDayOfWeek().getValue());//4
        //月份
        System.out.println(nowDateTime.getMonth());//SEPTEMBER
        System.out.println(nowDateTime.getMonth().getValue());//9


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

        LocalTime lt = nowDateTime.toLocalTime();
        System.out.println(lt.getHour());
        System.out.println(lt.getMinute());
        System.out.println(lt.getSecond());
    }
}

输出结果

今天是:2022-04-02T22:19:33.222
2022
4
2
22
19
33
222000000
dayOfYear:92
SATURDAY
6
APRIL
4
2022-04-02
22
19
33

UpdateTime

import java.time.LocalDate;
import java.time.LocalTime;
import java.time.MonthDay;

public class Demo04UpdateTime {
    public static void main(String[] args) {
        LocalTime nowTime = LocalTime.now();
        System.out.println(nowTime);//当前时间
        System.out.println(nowTime.minusHours(1));//一小时前
        System.out.println(nowTime.minusMinutes(1));//一分钟前
        System.out.println(nowTime.minusSeconds(1));//一秒前
        System.out.println(nowTime.minusNanos(1));//一纳秒前

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

        System.out.println(nowTime.plusHours(1));//一小时后
        System.out.println(nowTime.plusMinutes(1));//一分钟后
        System.out.println(nowTime.plusSeconds(1));//一秒后
        System.out.println(nowTime.plusNanos(1));//一纳秒后

        System.out.println("------------------");
        // 不可变对象,每次修改产生新对象!
        System.out.println(nowTime);

        System.out.println("---------------");
        LocalDate myDate = LocalDate.of(2018, 9, 5);
        LocalDate nowDate = LocalDate.now();

        System.out.println("今天是2018-09-06吗? " + nowDate.equals(myDate));//今天是2018-09-06吗? false
        System.out.println(myDate + "是否在" + nowDate + "之前? " + myDate.isBefore(nowDate));//2018-09-05是否在2018-09-06之前? true
        System.out.println(myDate + "是否在" + nowDate + "之后? " + myDate.isAfter(nowDate));//2018-09-05是否在2018-09-06之后? false

        System.out.println("---------------------------");
        // 判断今天是否是你的生日
        LocalDate birDate = LocalDate.of(1996, 8, 5);
        LocalDate nowDate1 = LocalDate.now();

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

        System.out.println("今天是你的生日吗? " + birMd.equals(nowMd));//今天是你的生日吗? false
    }
}

输出结果

22:21:42.399
21:21:42.399
22:20:42.399
22:21:41.399
22:21:42.398999999
----------------
23:21:42.399
22:22:42.399
22:21:43.399
22:21:42.399000001
------------------
22:21:42.399
---------------
今天是2018-09-06吗? false
2018-09-05是否在2022-04-02之前? true
2018-09-05是否在2022-04-02之后? false
---------------------------
今天是你的生日吗? false

2.4.2、Instant

Instant 时间戳

  • JDK8获取时间戳特别简单,且功能更丰富。Instant类由一个静态的工厂方法now()可以返回当前时间戳。

    Instant instant = Instant.now();
    System.out.println(“当前时间戳是:” + instant);

    Date date = Date.from(instant);
    System.out.println(“当前时间戳是:” + date);

    instant = date.toInstant();
    System.out.println(instant);

  • 时间戳是包含日期和时间的,与 java.util.Date 很类似,事实上 Instant 就是类似 JDK8 以前的 Date。

  • Instant 和 Date 这两个类可以进行转换。

代码展示

import java.time.Instant;
import java.time.ZoneId;
import java.util.Date;

public class Demo05Instant {
    public static void main(String[] args) {
        // 1、得到一个Instant时间戳对象
        Instant instant = Instant.now();
        System.out.println(instant);

        // 2、系统此刻的时间戳怎么办?
        Instant instant1 = Instant.now();
        System.out.println(instant1.atZone(ZoneId.systemDefault()));

        // 3、如何去返回Date对象
        Date date = Date.from(instant);
        System.out.println(date);

        Instant i2 = date.toInstant();
        System.out.println(i2);
    }
}

输出结果

2022-04-02T14:26:12.737Z
2022-04-02T22:26:12.863+08:00[Asia/Shanghai]
Sat Apr 02 22:26:12 CST 2022
2022-04-02T14:26:12.737Z

2.4.3、DateTimeFormat

  • 在JDK8中,引入了一个全新的日期与时间格式器DateTimeFormatter。

  • 正反都能调用format方法。

    LocalDateTime ldt = LocalDateTime.now();
    System.out.println(ldt);//2021-03-01T15:09:17.444190900

    DateTimeFormatter dtf = DateTimeFormatter.ofPattern(“yyyy-MM-dd HH:mm:ss”);
    String ldtStr = ldt.format(dtf);
    System.out.println(ldtStr);//2021-03-01 15:09:17

    String ldtStr1 = dtf.format(ldt);
    System.out.println(ldtStr1);//2021-03-01 15:09:17

代码展示

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class Demo06DateTimeFormat {
    public static void main(String[] args) {
        // 本地此刻  日期时间 对象
        LocalDateTime ldt = LocalDateTime.now();
        System.out.println(ldt);

        // 解析/格式化器
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss EEE a");
        // 正向格式化
        System.out.println(dtf.format(ldt));
        // 逆向格式化
        System.out.println(ldt.format(dtf));

        // 解析字符串时间
        DateTimeFormatter dtf1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        // 解析当前字符串时间成为本地日期时间对象
        LocalDateTime ldt1 = LocalDateTime.parse("2019-11-11 11:11:11" ,  dtf1);
        System.out.println(ldt1);
        System.out.println(ldt1.getDayOfYear());
    }
}

输出结果

2022-04-02T22:31:23.910
2022-04-02 22:31:23 星期六 下午
2022-04-02 22:31:23 星期六 下午
2019-11-11T11:11:11
315

2.4.4、Period

  • 在Java8中,我们可以使用以下类来计算日期间隔差异:java.time.Period
  • 主要是 Period 类方法 getYears(),getMonths() 和 getDays() 来计算,只能精确到年月日。
  • 用于 LocalDate 之间的比较

代码展示

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

public class Demo07Period {
    public static void main(String[] args) {
        // 当前本地 年月日
        LocalDate today = LocalDate.now();
        System.out.println(today);//

        // 生日的 年月日
        LocalDate birthDate = LocalDate.of(1998, 10, 13);
        System.out.println(birthDate);

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

        System.out.println(period.getYears());
        System.out.println(period.getMonths());
        System.out.println(period.getDays());
    }
}

输出结果

2022-04-02
1998-10-13
23
5
20

2.4.5、Duration

  • 在Java8中,我们可以使用以下类来计算时间间隔差异:java.time.Duration
  • 提供了使用基于时间的值测量时间量的方法。
  • 用于 LocalDateTime 之间的比较。也可用于 Instant 之间的比较。

代码展示

import java.time.Duration;
import java.time.LocalDateTime;

public class Demo08Duration {
    public static void main(String[] args) {
        // 本地日期时间对象。
        LocalDateTime today = LocalDateTime.now();
        System.out.println(today);

        // 出生的日期时间对象
        LocalDateTime birthDate = LocalDateTime.of(2021,8,06,01,00,00);

        System.out.println(birthDate);

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

        System.out.println(duration.toDays());//两个时间差的天数
        System.out.println(duration.toHours());//两个时间差的小时数
        System.out.println(duration.toMinutes());//两个时间差的分钟数
        System.out.println(duration.toMillis());//两个时间差的毫秒数
        System.out.println(duration.toNanos());//两个时间差的纳秒数
    }
}

输出结果

2022-04-02T22:36:53.207
2021-08-06T01:00
239
5757
345456
20727413207
20727413207000000

2.4.6、ChronoUnit

java.time.temporal.ChronoUnit

  • ChronoUnit类可用于在单个时间单位内测量一段时间,这个工具类是最全的了,可以用于比较所有的时间单位

代码展示

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;

public class Demo09ChronoUnit {
    public static void main(String[] args) {
        // 本地日期时间对象:此刻的
        LocalDateTime today = LocalDateTime.now();
        System.out.println(today);

        // 生日时间
        LocalDateTime birthDate = LocalDateTime.of(1990,10,1,
                10,50,59);
        System.out.println(birthDate);

        System.out.println("相差的年数:" + ChronoUnit.YEARS.between(birthDate, today));
        System.out.println("相差的月数:" + ChronoUnit.MONTHS.between(birthDate, today));
        System.out.println("相差的周数:" + ChronoUnit.WEEKS.between(birthDate, today));
        System.out.println("相差的天数:" + ChronoUnit.DAYS.between(birthDate, today));
        System.out.println("相差的时数:" + ChronoUnit.HOURS.between(birthDate, today));
        System.out.println("相差的分数:" + ChronoUnit.MINUTES.between(birthDate, today));
        System.out.println("相差的秒数:" + ChronoUnit.SECONDS.between(birthDate, today));
        System.out.println("相差的毫秒数:" + ChronoUnit.MILLIS.between(birthDate, today));
        System.out.println("相差的微秒数:" + ChronoUnit.MICROS.between(birthDate, today));
        System.out.println("相差的纳秒数:" + ChronoUnit.NANOS.between(birthDate, today));
        System.out.println("相差的半天数:" + ChronoUnit.HALF_DAYS.between(birthDate, today));
        System.out.println("相差的十年数:" + ChronoUnit.DECADES.between(birthDate, today));
        System.out.println("相差的世纪(百年)数:" + ChronoUnit.CENTURIES.between(birthDate, today));
        System.out.println("相差的千年数:" + ChronoUnit.MILLENNIA.between(birthDate, today));
        System.out.println("相差的纪元数:" + ChronoUnit.ERAS.between(birthDate, today));
    }
}

输出结果

2022-04-02T22:38:45.180
1990-10-01T10:50:59
相差的年数:31
相差的月数:378
相差的周数:1643
相差的天数:11506
相差的时数:276155
相差的分数:16569347
相差的秒数:994160866
相差的毫秒数:994160866180
相差的微秒数:994160866180000
相差的纳秒数:994160866180000000
相差的半天数:23012
相差的十年数:3
相差的世纪(百年)数:0
相差的千年数:0
相差的纪元数:0

3、包装类

包装类

  • 其实就是8种基本数据类型对应的引用类型。

基本数据类型

引用数据类型

byte

Byte

short

Short

int

Integer

long

Long

char

Character

float

Float

double

Double

boolean

Boolean

为什么提供包装类?

  • Java为了实现一切皆对象,为8种基本类型提供了对应的引用类型。
  • 后面的集合和泛型其实也只能支持包装类型,不支持基本数据类型。

自动装箱

  • 基本类型的数据和变量可以直接赋值给包装类型的变量。

自动拆箱

  • 包装类型的变量可以直接赋值给基本数据类型的变量。

包装类的特有功能

  • 包装类的变量的默认值可以是null,容错率更高。
  • 可以把基本类型的数据转换成字符串类型(用处不大)
    1. 调用toString()方法得到字符串结果。
    2. 调用Integer.toString(基本类型的数据)。
  • 可以把字符串类型的数值转换成真实的数据类型(真的很有用)
    1. Integer.parseInt(“字符串类型的整数”)。
    2. Double.parseDouble(“字符串类型的小数”)。
    3. 也可以使用 Integer.valueOf(number) 和 Double.valueOf(number) ,效果和上面的一样

代码演示

/**
    目标:明白包装类的概念,并使用。
 */
public class Test {
    public static void main(String[] args) {
        int a = 10;
        Integer a1 = 11;
        Integer a2 = a; // 自动装箱
        System.out.println(a);
        System.out.println(a1);

        Integer it = 100;
        int it1 = it; // 自动拆箱
        System.out.println(it1);
        System.out.println(it1 == it);

        double db = 99.5;
        Double db2 = db; // 自动装箱了
        double db3 = db2; // 自动拆箱
        System.out.println(db3);
        System.out.println(db == db2);

        // int age = null; // 报错了!
        Integer age1 = null;
        Integer age2 = 0;

        System.out.println("-----------------");
        // 1、包装类可以把基本类型的数据转换成字符串形式。(没啥用)
        Integer i3 = 23;
        String rs = i3.toString();
        System.out.println(rs + 1);

        String rs1 = Integer.toString(i3);
        System.out.println(rs1 + 1);

        // 可以直接+字符串得到字符串类型
        String rs2 = i3 + "";
        System.out.println(rs2 + 1);

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

        String number = "23";
        //转换成整数
        // int age = Integer.parseInt(number);
        int age = Integer.valueOf(number);
        System.out.println(age + 1);

        String number1 = "99.9";
        //转换成小数
//        double score = Double.parseDouble(number1);
        double score = Double.valueOf(number1);
        System.out.println(score + 0.1);
    }
}

输出结果

10
11
100
true
99.5
true
-----------------
231
231
231
-----------------
24
100.0

装箱拆箱后的值是相等的

System.out.println(it1 == it); // true
System.out.println(db == db2); // true

4、正则表达式

  • 正则表达式可以用一些规定的字符来制定规则,并用来校验数据格式的合法性。

4.1、正则表达式初体验

**需求:**假如现在要求校验一个qq号码是否正确,6位及20位之内,必须全部是数字 。

代码展示

public class RegexDemo1 {
    public static void main(String[] args) {
        // 需求:校验qq号码,必须全部数字 6 - 20位
        System.out.println(checkQQ("251425998"));
        System.out.println(checkQQ("2514259a98"));
        System.out.println(checkQQ(null));
        System.out.println(checkQQ("2344"));

        System.out.println("-------------------------");
        // 正则表达式的初体验:
        System.out.println(checkQQ2("251425998"));
        System.out.println(checkQQ2("2514259a98"));
        System.out.println(checkQQ2(null));
        System.out.println(checkQQ2("2344"));

    }

    public static boolean checkQQ2(String qq){
        return qq != null && qq.matches("\d{6,20}");
    }


    public static boolean checkQQ(String qq){
        // 1、判断qq号码的长度是否满足要求
        if(qq == null || qq.length() < 6 || qq.length() > 20 ) {
            return false;
        }

        // 2、判断qq中是否全部是数字,不是返回false
        //  251425a87
        for (int i = 0; i < qq.length(); i++) {
            // 获取每位字符
            char ch = qq.charAt(i);
            // 判断这个字符是否不是数字,不是数字直接返回false
            if(ch < '0' || ch > '9') {
                return false;
            }
        }

        return true; // 肯定合法了!
    }
}

输出结果

true
false
false
false
-------------------------
true
false
false
false

4.2、正则表达式的使用详解

代码展示

/**
    目标:全面、深入学习正则表达式的规则
 */
public class RegexDemo02 {
    public static void main(String[] args) {
        //public boolean matches(String regex):判断是否与正则表达式匹配,匹配返回true
        // 只能是 a  b  c
        System.out.println("a".matches("[abc]")); // true
        System.out.println("z".matches("[abc]")); // false

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

        System.out.println("a".matches("\d")); // false
        System.out.println("3".matches("\d")); // true
        System.out.println("333".matches("\d")); // false
        System.out.println("z".matches("\w")); // true
        System.out.println("2".matches("\w")); // true
        System.out.println("21".matches("\w")); // false
        System.out.println("你".matches("\w")); //false
        System.out.println("你".matches("\W")); // true
        System.out.println("---------------------------------");
        //  以上正则匹配只能校验单个字符。

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

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

    }
}

输出结果:

true
false
true
false
true
true
false
false
true
---------------------------------
true
false
true
false
true
false

4.3、正则表达式的常见案例

需求:

  1. 请编写程序模拟用户输入手机号码、验证格式正确,并给出提示,直到格式输入正确为止。
  2. 请编写程序模拟用户输入邮箱号码、验证格式正确,并给出提示,直到格式输入正确为止。
  3. 请编写程序模拟用户输入电话号码、验证格式正确,并给出提示,直到格式输入正确为止。

分析:

  • 定义方法,接收用户输入的数据,使用正则表达式完成检验,并给出提示。

代码展示

public static void checkTel(){
    Scanner sc = new Scanner(System.in);
    while (true) {
        System.out.println("请您输入您的电话号码:");
        String tel = sc.next();
        // 判断邮箱格式是否正确   027-3572457  0273572457
        if(tel.matches("0\d{2,6}-?\d{5,20}")){
            System.out.println("格式正确,注册完成!");
            break;
        }else {
            System.out.println("格式有误!");
        }
    }
}

public static void checkEmail(){
    Scanner sc = new Scanner(System.in);
    while (true) {
        System.out.println("请您输入您的注册邮箱:");
        String email = sc.next();
        // 判断邮箱格式是否正确   3268847878@qq
        // 判断邮箱格式是否正确   3268847dsda878@163
        // 判断邮箱格式是否正确   3268847dsda878@pci
        if(email.matches("\w{1,30}@[a-zA-Z0-9]{2,20}(\.[a-zA-Z0-9]{2,20}){1,2}")){
            System.out.println("邮箱格式正确,注册完成!");
            break;
        }else {
            System.out.println("格式有误!");
        }
    }
}

public static void checkPhone(){
    Scanner sc = new Scanner(System.in);
    while (true) {
        System.out.println("请您输入您的注册手机号码:");
        String phone = sc.next();
        // 判断手机号码的格式是否正确
        if(phone.matches("1[3-9]\d{9}")){
            System.out.println("手机号码格式正确,注册完成!");
            break;
        }else {
            System.out.println("格式有误!");
        }
    }

4.4、正则表达式在方法中的应用

方法名

说明

public String replaceAll(String regex,String newStr)

按照正则表达式匹配的内容进行替换

public String[] split(String regex):

按照正则表达式匹配的内容进行分割字符串,反回一个字符串数组。

代码展示

/**
    目标:正则表达式在方法中的应用。
        public String[] split(String regex):
            -- 按照正则表达式匹配的内容进行分割字符串,反回一个字符串数组。
        public String replaceAll(String regex,String newStr)
            -- 按照正则表达式匹配的内容进行替换
 */
public class RegexDemo04 {
    public static void main(String[] args) {
        String names = "小路dhdfhdf342蓉儿43fdffdfbjdfaf小何";

        String[] arrs = names.split("\w+");
        for (int i = 0; i < arrs.length; i++) {
            System.out.println(arrs[i]);
        }

        String names2 = names.replaceAll("\w+", "  ");
        System.out.println(names2);
    }
}

输出结果

小路
蓉儿
小何
小路  蓉儿  小何

4.5、正则表达式爬取信息

代码展示

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

/**
    拓展:正则表达式爬取信息中的内容。(了解)
 */
public class RegexDemo05 {
    public static void main(String[] args) {
        String rs = "来黑马程序学习Java,电话020-43422424,或者联系邮箱" +
                "itcast@itcast,电话18762832633,0203232323" +
                "邮箱bozai@itcast,400-100-3233 ,4001003232";

        // 需求:从上面的内容中爬取出 电话号码和邮箱。
        // 1、定义爬取规则,字符串形式
        String regex = "(\w{1,30}@[a-zA-Z0-9]{2,20}(\.[a-zA-Z0-9]{2,20}){1,2})|(1[3-9]\d{9})" +
                "|(0\d{2,6}-?\d{5,20})|(400-?\d{3,9}-?\d{3,9})";

        // 2、把这个爬取规则编译成匹配对象。
        Pattern pattern = Patternpile(regex);

        // 3、得到一个内容匹配器对象
        Matcher matcher = pattern.matcher(rs);

        // 4、开始找了
        while (matcher.find()) {
            String rs1 = matcher.group();
            System.out.println(rs1);
        }

    }
}

输出结果

020-43422424
itcast@itcast
18762832633
0203232323
bozai@itcast
400-100-3233
4001003232

5、Arrays 类

  • 数组操作工具类,专门用于操作数组元素的。

5.1、Arrays 类常用功能演示

Arrays类的常用API

方法名

说明

public static String toString(类型[] a)

返回数组的内容(字符串形式)

public static void sort(类型[] a)

对数组进行默认升序排序

public static void sort(类型[] a, Comparator< super T> c)

使用比较器对象自定义排序

public static int binarySearch(int[] a, int key)

二分搜索数组中的数据,存在返回索引,不存在返回-1

数组与 ArrayList 的区别

ArrayList 底层已经默认调用了 toString()。

代码展示

import java.util.ArrayList;
import java.util.Arrays;

public class ArraysDemo1 {
    public static void main(String[] args) {
        // 目标:学会使用Arrays类的常用API ,并理解其原理
        int[] arr = {10, 2, 55, 23, 24, 100};
        System.out.println(arr);

        ArrayList<String> list = new ArrayList<>();
        list.add("迪丽热巴");
        list.add("古力娜扎");
        list.add("超人迪加");
        // ArrayList<Object> 底层已经默认调用了toString()
        System.out.println(list);

        // 1、返回数组内容的 toString(数组)
//        String rs = Arrays.toString(arr);
//        System.out.println(rs);

        System.out.println(Arrays.toString(arr));

        // 2、排序的API(默认自动对数组元素进行升序排序)
        Arrays.sort(arr);
        System.out.println(Arrays.toString(arr));

        // 3、二分搜索技术(前提数组必须排好序才支持,否则出bug)
        int index = Arrays.binarySearch(arr, 55);
        System.out.println(index);

        // 返回不存在元素的规律: - (应该插入的位置索引 + 1)
        int index2 = Arrays.binarySearch(arr, 22);
        System.out.println(index2);


        // 注意:数组如果么有排好序,可能会找不到存在的元素,从而出现bug!!
        int[] arr2 = {12, 36, 34, 25 , 13,  24,  234, 100};
        System.out.println(Arrays.binarySearch(arr2 , 36));
    }

}

输出结果

[I@1b6d3586
[迪丽热巴, 古力娜扎, 超人迪加]
[10, 2, 55, 23, 24, 100]
[2, 10, 23, 24, 55, 100]
4
-3
-7

5,2、Arrays 类对于Comparator比较器的支持

Arrays类的排序方法

方法名

说明

public static void sort(类型[] a)

对数组进行默认升序排序

public static void sort(类型[] a, Comparator< super T> c)

使用比较器对象自定义排序

自定义排序规则

  • 设置Comparator接口对应的比较器对象,来定制比较规则。
  • 如果认为左边数据 大于 右边数据 返回正整数
  • 如果认为左边数据 小于 右边数据 返回负整数
  • 如果认为左边数据 等于 右边数据 返回0

代码展示

学生对象

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

    public Student() {
    }

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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    public double getHeight() {
        return height;
    }

    public void setHeight(double height) {
        this.height = height;
    }

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

测试类

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

public class ArraysDemo2 {
    public static void main(String[] args) {
        // 目标:自定义数组的排序规则:Comparator比较器对象。
        // 1、Arrays的sort方法对于有值特性的数组是默认升序排序
        int[] ages = {34, 12, 42, 23};
        Arrays.sort(ages);
        System.out.println(Arrays.toString(ages));

        // 2、需求:降序排序!(自定义比较器对象,只能支持引用类型的排序!!)
        Integer[] ages1 = {34, 12, 42, 23};
        /**
           参数一:被排序的数组 必须是引用类型的元素
           参数二:匿名内部类对象,代表了一个比较器对象。
         */
        Arrays.sort(ages1, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                // 指定比较规则。
//                if(o1 > o2){
//                    return 1;
//                }else if(o1 < o2){
//                    return -1;
//                }
//                return 0;
                // return o1 - o2; // 默认升序
                return o2 - o1; //  降序
            }
        });
        System.out.println(Arrays.toString(ages1));

        System.out.println("-------------------------");
        Student[] students = new Student[3];
        students[0] = new Student("吴磊",23 , 175.5);
        students[1] = new Student("谢鑫",18 , 185.5);
        students[2] = new Student("王亮",20 , 195.5);
        System.out.println(Arrays.toString(students));

        // Arrays.sort(students);  // 直接运行奔溃
        Arrays.sort(students, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                // 自己指定比较规则
                // return o1.getAge() - o2.getAge(); // 按照年龄升序排序!
                // return o2.getAge() - o1.getAge(); // 按照年龄降序排序!!
                // return Doublepare(o1.getHeight(), o2.getHeight()); // 比较浮点型可以这样写 升序
                return Doublepare(o2.getHeight(), o1.getHeight()); // 比较浮点型可以这样写  降序
            }
        });
        System.out.println(Arrays.toString(students));


    }
}

输出结果

[12, 23, 34, 42]
[42, 34, 23, 12]
-------------------------
[Student{name='吴磊', age=23, height=175.5}, Student{name='谢鑫', age=18, height=185.5}, Student{name='王亮', age=20, height=195.5}]
[Student{name='王亮', age=20, height=195.5}, Student{name='谢鑫', age=18, height=185.5}, Student{name='吴磊', age=23, height=175.5}]

6、常见算法

6.1、冒泡排序

6.2、选择排序

代码展示

import java.util.Arrays;

/**
    目标:学会使用选择排序的方法对数组进行排序。
 */
public class Test1 {
    public static void main(String[] args) {
        // 1、定义数组
        int[] arr = {5, 1, 3, 2};
        //           0  1  2  3

        // 2、定义一个循环控制选择几轮: arr.length - 1
        for (int i = 0; i < arr.length - 1; i++) {
            // i = 0   j =  1  2  3
            // i = 1   j =  2  3
            // i = 2   j =  3
            // 3、定义内部循环,控制选择几次
            for (int j = i + 1; j < arr.length; j++) {
                // 当前位:arr[i]
                // 如果有比当前位数据更小的,则交换
                if(arr[i] > arr[j]) {
                    int temp = arr[i];
                    arr[i] = arr[j];
                    arr[j] = temp;
                }
            }
        }
        System.out.println(Arrays.toString(arr));
    }
}

输出结果

[1, 2, 3, 5]

6.3、二分查找

代码展示

/**
    目标:理解二分搜索的原理并实现。
 */
public class Test2 {
    public static void main(String[] args) {
        // 1、定义数组
        int[] arr = {10, 14, 16, 25, 28, 30, 35, 88, 100};
        //                                            r
        //                                                l
        //
        System.out.println(binarySearch(arr , 35));
        System.out.println(binarySearch(arr , 350));
    }
    /**
     * 二分查找算法的实现
     * @param arr  排序的数组
     * @param data 要找的数据
     * @return  索引,如果元素不存在,直接返回-1
     */
    public static int binarySearch(int[] arr, int data){
        // 1、定义左边位置  和 右边位置
        int left = 0;
        int right = arr.length - 1;

        // 2、开始循环,折半查询。
        while (left <= right){
            // 取中间索引
            int middleIndex = (left + right) / 2;
            // 3、判断当前中间位置的元素和要找的元素的大小情况
            if(data > arr[middleIndex]) {
                // 往右边找,左位置更新为 = 中间索引+1
                left = middleIndex + 1;
            }else if(data < arr[middleIndex]) {
                // 往左边找,右边位置 = 中间索引 - 1
                right = middleIndex - 1;
            }else {
                return middleIndex;
            }
        }
        return -1; // 查无此元素
    }

}

输出结果

6
-1

7、Lambda 表达式

简化过程

public class LambdaDemo2 {
    public static void main(String[] args) {
        // 目标:学会使用Lambda的标准格式简化匿名内部类的代码形式
        // 注意:Lambda只能简化接口中只有一个抽象方法的匿名内部类形式(函数式接口)
//        Swimming s1 = new Swimming() {
//            @Override
//            public void swim() {
//                System.out.println("老师游泳贼溜~~~~~");
//            }
//        };

//        Swimming s1 = () -> {
//            System.out.println("老师游泳贼溜~~~~~");
//        };

        Swimming s1 = () -> System.out.println("老师游泳贼溜~~~~~");
        go(s1);

        System.out.println("---------------------");
//        go(new Swimming() {
//            @Override
//            public void swim() {
//                System.out.println("学生游泳很开心~~~");
//            }
//        });

//        go(() ->{
//                System.out.println("学生游泳很开心~~~");
//        });

        go(() -> System.out.println("学生游泳很开心~~~"));


    }

    public static void go(Swimming s){
        System.out.println("开始。。。");
        s.swim();
        System.out.println("结束。。。");
    }
}

@FunctionalInterface // 一旦加上这个注解必须是函数式接口,里面只能有一个抽象方法
interface Swimming{
    void swim();
}

Lambda表达式有什么使用前提?

必须是接口的匿名内部类,接口中只能有一个抽象方法。

Lambda表达式简化Comparator接口的匿名形式

Lambda表达式简化按钮监听器ActionListener的匿名内部类形式

简化过程

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Arrays;
import java.util.Comparator;

public class LambdaDemo3 {
    public static void main(String[] args) {
        Integer[] ages1 = {34, 12, 42, 23};
        /**
         参数一:被排序的数组 必须是引用类型的元素
         参数二:匿名内部类对象,代表了一个比较器对象。
         */
//        Arrays.sort(ages1, new Comparator<Integer>() {
//            @Override
//            public int compare(Integer o1, Integer o2) {
//                return o2 - o1; //  降序
//            }
//        });

//        Arrays.sort(ages1, (Integer o1, Integer o2) -> {
//                return o2 - o1; //  降序
//        });


//        Arrays.sort(ages1, ( o1,  o2) -> {
//            return o2 - o1; //  降序
//        });

        Arrays.sort(ages1, ( o1,  o2 ) ->  o2 - o1 );

        System.out.println(Arrays.toString(ages1));

        System.out.println("---------------------------");
        JFrame win = new JFrame("登录界面");
        JButton btn = new JButton("我是一个很大的按钮");
//        btn.addActionListener(new ActionListener() {
//            @Override
//            public void actionPerformed(ActionEvent e) {
//                System.out.println("有人点我,点我,点我!!");
//            }
//        });

//        btn.addActionListener((ActionEvent e) -> {
//                System.out.println("有人点我,点我,点我!!");
//        });

//        btn.addActionListener(( e) -> {
//            System.out.println("有人点我,点我,点我!!");
//        });

//        btn.addActionListener( e -> {
//            System.out.println("有人点我,点我,点我!!");
//        });

        btn.addActionListener( e -> System.out.println("有人点我,点我,点我!!") );



        win.add(btn);
        win.setSize(400, 300);
        win.setVisible(true);
    }
}

Lambda表达式的省略写法(进一步在Lambda表达式的基础上继续简化)

  • 参数类型可以省略不写。
  • 如果只有一个参数,参数类型可以省略,同时()也可以省略。
  • 如果Lambda表达式的方法体代码只有一行代码。可以省略大括号不写,同时要省略分号!
  • 如果Lambda表达式的方法体代码只有一行代码。可以省略大括号不写。此时,如果这行代码是return语句,必须省略return不写,同时也必须省略分号不写。

8、集合

8.1、集合的概述

集合和数组都是容器。

数组的特点

  • 数组定义完成并启动后,类型确定、长度固定。
  • 适合元素的个数和类型确定的业务场景,不适合做需要增删数据操作。

集合的特点

  • 集合的大小不固定,启动后可以动态变化,类型也可以选择不固定。集合更像气球。
  • 集合非常适合做元素的增删操作。

数组与集合的比较

1、数组和集合的元素存储的个数问题。

  • 数组定义后类型确定,长度固定
  • 集合类型可以不固定,大小是可变的。

2、数组和集合存储元素的类型问题。

  • 数组可以存储基本类型和引用类型的数据。
  • 集合只能存储引用数据类型的数据。

3、数组和集合适合的场景

  • 数组适合做数据个数和类型确定的场景。
  • 集合适合做数据个数不确定,且要做增删元素的场景。

8.2、集合的体系特点



代码演示

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

/**
    目标:明确Collection集合体系的特点
 */
public class CollectionDemo1 {
    public static void main(String[] args) {
        // 有序 可重复 有索引
        Collection list = new ArrayList();
        list.add("Java");
        list.add("Java");
        list.add("Mybatis");
        list.add(23);
        list.add(23);
        list.add(false);
        list.add(false);
        System.out.println(list);

        // 无序 不重复  无索引
        Collection list1 = new HashSet();
        list1.add("Java");
        list1.add("Java");
        list1.add("Mybatis");
        list1.add(23);
        list1.add(23);
        list1.add(false);
        list1.add(false);
        System.out.println(list1);
        
        // Collection<String> list2 = new ArrayList<String>();
        Collection<String> list2 = new ArrayList<>(); // JDK 7开始之后后面类型申明可以不写
        list2.add("Java");
        // list2.add(23);
        list2.add("黑马");

        // 集合和泛型不支持基本数据类型,只能支持引用数据类型
        // Collection<int> list3 = new ArrayList<>();
        Collection<Integer> list3 = new ArrayList<>();
        list3.add(23);
        list3.add(233);
        list3.add(2333);

        Collection<Double> list4 = new ArrayList<>();
        list4.add(23.4);
        list4.add(233.0);
        list4.add(233.3);
    }
}

输出结果

[Java, Java, Mybatis, 23, 23, false, false]
[Java, false, 23, Mybatis]

8.3、Collection 集合常用API

Collection 集合

Collection 是单列集合的祖宗接口,它的功能是全部单列集合都可以继承使用的。

Collection API

方法名称

说明

public boolean add(E e)

把给定的对象添加到当前集合中

public void clear()

清空集合中所有的元素

public boolean remove(E e)

把给定的对象在当前集合中删除

public boolean contains(Object obj)

判断当前集合中是否包含给定的对象

public boolean isEmpty()

判断当前集合是否为空

public int size()

返回集合中元素的个数。

public Object[] toArray()

把集合中的元素,存储到数组中

代码演示

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

/**
    目标:Collection集合的常用API.
    Collection是集合的祖宗类,它的功能是全部集合都可以继承使用的,所以要学习它。
    Collection API如下:
         - public boolean add(E e):  把给定的对象添加到当前集合中 。
         - public void clear() :清空集合中所有的元素。
         - public boolean remove(E e): 把给定的对象在当前集合中删除。
         - public boolean contains(Object obj): 判断当前集合中是否包含给定的对象。
         - public boolean isEmpty(): 判断当前集合是否为空。
         - public int size(): 返回集合中元素的个数。
         - public Object[] toArray(): 把集合中的元素,存储到数组中。
    小结:
        记住以上API。
 */
public class CollectionDemo {
    public static void main(String[] args) {
        // HashSet:添加的元素是无序,不重复,无索引。
        Collection<String> c = new ArrayList<>();
        // 1.添加元素, 添加成功返回true。
        c.add("Java");
        c.add("HTML");
        System.out.println(c.add("HTML"));
        c.add("MySQL");
        c.add("Java");
        System.out.println(c.add("黑马"));
        System.out.println(c); // [Java, HTML, HTML, MySQL, Java, 黑马]

        // 2.清空集合的元素。
        // c.clear();
        // System.out.println(c);

        // 3.判断集合是否为空 是空返回true,反之。
        // System.out.println(c.isEmpty());

        // 4.获取集合的大小。
        System.out.println(c.size());

        // 5.判断集合中是否包含某个元素。
        System.out.println(c.contains("Java"));  // true
        System.out.println(c.contains("java")); // false
        System.out.println(c.contains("黑马")); // true

        // 6.删除某个元素:如果有多个重复元素默认删除前面的第一个!
        System.out.println(c.remove("java")); // false
        System.out.println(c);
        System.out.println(c.remove("Java")); // true
        System.out.println(c);

        // 7.把集合转换成数组  [HTML, HTML, MySQL, Java, 黑马]
        Object[] arrs = c.toArray();
        System.out.println("数组:" + Arrays.toString(arrs));

        System.out.println("----------------------拓展----------------------");
        Collection<String> c1 = new ArrayList<>();
        c1.add("java1");
        c1.add("java2");
        Collection<String> c2 = new ArrayList<>();
        c2.add("赵敏");
        c2.add("殷素素");
        // addAll把c2集合的元素全部倒入到c1中去。
        c1.addAll(c2);
        System.out.println(c1);
        System.out.println(c2);
    }
}

输出结果

true
true
[Java, HTML, HTML, MySQL, Java, 黑马]
6
true
false
true
false
[Java, HTML, HTML, MySQL, Java, 黑马]
true
[HTML, HTML, MySQL, Java, 黑马]
数组:[HTML, HTML, MySQL, Java, 黑马]
----------------------拓展----------------------
[java1, java2, 赵敏, 殷素素]
[赵敏, 殷素素]

8.4、Collection集合的遍历方式

8.4.1、迭代器

迭代器遍历概述

  • 遍历就是一个一个的把容器中的元素访问一遍。
  • 迭代器在Java中的代表是Iterator,迭代器是集合的专用遍历方式。

Collection 集合获取迭代器

方法名称

说明

Iterator iterator()

返回集合中的迭代器对象,该迭代器对象默认指向当前集合的0索引

Iterator中的常用方法

方法名称

说明

boolean hasNext()

询问当前位置是否有元素存在,存在返回true ,不存在返回false

E next()

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

代码演示

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

/**
    目标:Collection集合的遍历方式。

    什么是遍历? 为什么开发中要遍历?
        遍历就是一个一个的把容器中的元素访问一遍。
        开发中经常要统计元素的总和,找最值,找出某个数据然后干掉等等业务都需要遍历。

    Collection集合的遍历方式是全部集合都可以直接使用的,所以我们学习它。
    Collection集合的遍历方式有三种:
        (1)迭代器。
        (2)foreach(增强for循环)。
        (3)JDK 1.8开始之后的新技术Lambda表达式(了解)

    a.迭代器遍历集合。
        -- 方法:
             public Iterator iterator(): 获取集合对应的迭代器,用来遍历集合中的元素的
             boolean hasNext():判断是否有下一个元素,有返回true ,反之。
             E next():获取下一个元素值!
        --流程:
            1.先获取当前集合的迭代器
                Iterator<String> it = lists.iterator();
            2.定义一个while循环,问一次取一次。
              通过it.hasNext()询问是否有下一个元素,有就通过
              it.next()取出下一个元素。
    小结:
        记住代码。
 */
public class CollectionDemo01 {
    public static void main(String[] args) {
        ArrayList<String> lists = new ArrayList<>();
        lists.add("赵敏");
        lists.add("小昭");
        lists.add("素素");
        lists.add("灭绝");
        System.out.println(lists);
        // [赵敏, 小昭, 素素, 灭绝]
        //   it

        // 1、得到当前集合的迭代器对象。
        Iterator<String> it = lists.iterator();
//        String ele = it.next();
//        System.out.println(ele);
//        System.out.println(it.next());
//        System.out.println(it.next());
//        System.out.println(it.next());
        // System.out.println(it.next()); // NoSuchElementException 出现无此元素异常的错误

        // 2、定义while循环
        while (it.hasNext()){
            String ele = it.next();
            System.out.println(ele);
        }

    }
}

输出结果

[赵敏, 小昭, 素素, 灭绝]
赵敏
小昭
素素
灭绝

注意点

1、迭代器的默认位置在哪里?

  • Iterator iterator():得到迭代器对象,默认指向当前集合的索引0

2、迭代器如果取元素越界会出现什么问题?

  • 会出现NoSuchElementException异常。

8.4.2、foreach/增强for循环

代码演示

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
/**
     目标:Collection集合的遍历方式。

     什么是遍历? 为什么开发中要遍历?
     遍历就是一个一个的把容器中的元素访问一遍。
     开发中经常要统计元素的总和,找最值,找出某个数据然后干掉等等业务都需要遍历。

     Collection集合的遍历方式是全部集合都可以直接使用的,所以我们学习它。
     Collection集合的遍历方式有三种:
         (1)迭代器。
         (2)foreach(增强for循环)。
         (3)JDK 1.8开始之后的新技术Lambda表达式。

     b.foreach(增强for循环)遍历集合。
         foreach是一种遍历形式,可以遍历集合或者数组。
         foreach遍历集合实际上是迭代器遍历集合的简化写法。
         foreach遍历的关键是记住格式:
            for(被遍历集合或者数组中元素的类型 变量名称 : 被遍历集合或者数组){

            }
 */
public class CollectionDemo02 {
    public static void main(String[] args) {
        Collection<String> lists = new ArrayList<>();
        lists.add("赵敏");
        lists.add("小昭");
        lists.add("殷素素");
        lists.add("周芷若");
        System.out.println(lists);
        // [赵敏, 小昭, 殷素素, 周芷若]
        //  ele

        for (String ele : lists) {
            System.out.println(ele);
        }

        System.out.println("------------------");
        double[] scores = {100, 99.5 , 59.5};
        for (double score : scores) {
            System.out.println(score);
//            if(score == 59.5){
//                score = 100.0; // 修改无意义,不会影响数组的元素值。
//            }
        }
        System.out.println(Arrays.toString(scores));

    }
}

输出结果

[赵敏, 小昭, 殷素素, 周芷若]
赵敏
小昭
殷素素
周芷若
------------------
100.0
99.5
59.5
[100.0, 99.5, 59.5]

增强for可以遍历哪些容器?

既可以遍历集合也可以遍历数组。

8.4.3、lambda表达式

简化过程

import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Consumer;

/**
     目标:Collection集合的遍历方式。

     什么是遍历? 为什么开发中要遍历?
     遍历就是一个一个的把容器中的元素访问一遍。
     开发中经常要统计元素的总和,找最值,找出某个数据然后干掉等等业务都需要遍历。

     Collection集合的遍历方式是全部集合都可以直接使用的,所以我们学习它。
     Collection集合的遍历方式有三种:
         (1)迭代器。
         (2)foreach(增强for循环)。
         (3)JDK 1.8开始之后的新技术Lambda表达式。
     c.JDK 1.8开始之后的新技术Lambda表达式。
 */
public class CollectionDemo03 {
    public static void main(String[] args) {
        Collection<String> lists = new ArrayList<>();
        lists.add("赵敏");
        lists.add("小昭");
        lists.add("殷素素");
        lists.add("周芷若");
        System.out.println(lists);
        // [赵敏, 小昭, 殷素素, 周芷若]
        //  s
        lists.forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        });

//        lists.forEach(s -> {
//                System.out.println(s);
//        });

        // lists.forEach(s ->  System.out.println(s) );

        lists.forEach(System.out::println );

    }
}

8.5、Collection 集合存储自定义类型的对象

案例:影片信息在程序中的表示

需求:

  • 某影院系统需要在后台存储上述三部电影,然后依次展示出来。

分析:

  1. 定义一个电影类,定义一个集合存储电影对象。
  2. 创建3个电影对象,封装相关数据,把3个对象存入到集合中去。
  3. 遍历集合中的3个对象,输出相关信息。

代码演示

Movie类

public class Movie {
    private String name;
    private double score;
    private String actor;

    public Movie() {
    }

    public Movie(String name, double score, String actor) {
        this.name = name;
        this.score = score;
        this.actor = actor;
    }

    public String getName() {
        return name;
    }

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

    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }

    public String getActor() {
        return actor;
    }

    public void setActor(String actor) {
        this.actor = actor;
    }

    @Override
    public String toString() {
        return "Movie{" +
                "name='" + name + ''' +
                ", score=" + score +
                ", actor='" + actor + ''' +
                '}';
    }
}

测试类

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

public class TestDemo {
    public static void main(String[] args) {
        // 1、定义一个电影类
        // 2、定义一个集合对象存储3部电影对象
        Collection<Movie> movies = new ArrayList<>();
        movies.add(new Movie("《你好,李焕英》", 9.5, "张小斐,贾玲,沈腾,陈赫"));
        movies.add(new Movie("《唐人街探案》", 8.5, "王宝强,刘昊然,美女"));
        movies.add(new Movie("《刺杀小说家》",8.6, "雷佳音,杨幂"));

        System.out.println(movies);

        // 3、遍历集合容器中的每个电影对象
        for (Movie movie : movies) {
            System.out.println("片名:" + movie.getName());
            System.out.println("得分:" + movie.getScore());
            System.out.println("主演:" + movie.getActor());
        }

    }
}

输出结果

[Movie{name='《你好,李焕英》', score=9.5, actor='张小斐,贾玲,沈腾,陈赫'}, Movie{name='《唐人街探案》', score=8.5, actor='王宝强,刘昊然,美女'}, Movie{name='《刺杀小说家》', score=8.6, actor='雷佳音,杨幂'}]
片名:《你好,李焕英》
得分:9.5
主演:张小斐,贾玲,沈腾,陈赫
片名:《唐人街探案》
得分:8.5
主演:王宝强,刘昊然,美女
片名:《刺杀小说家》
得分:8.6
主演:雷佳音,杨幂

内存原理

集合中存储的是元素的什么信息

集合中存储的是元素对象的地址。

9、常见数据结构

数据结构概述

  • 数据结构是计算机底层存储、组织数据的方式。是指数据相互之间是以什么方式排列在一起的。
  • 通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率

9.1、栈

9.2、队列

9.3、数组

9.4、链表

链表加入元素

链表删除元素

链表的种类

9.5、二叉树

二叉树概述

二叉树的特点

9.6、二叉查找树

二叉查找树对比普通二叉树

二叉树查找树添节点

9.7、平衡二叉树

二叉树查找存在的问题

平衡二叉树

平衡二叉树的要求

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

  • 基本策略是进行左旋,或者右旋保证平衡。
  • 后续会开专栏分析平衡二叉树与红黑树

9.8、红黑树

红黑树概述

红黑规则

添加节点

红黑树小结

  • 红黑树不是高度平衡的,它的平衡是通过"红黑规则"进行实现的
  1. 每一个节点或是红色的,或者是黑色的,根节点必须是黑色;
  2. 如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶节点,每个叶节点(Nil)是黑色的;
  3. 如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连的情况);
  4. 对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。
  • 后续会开专栏分析平衡二叉树与红黑树

9.9、各种数据结构的特点和作用

  • 队列:先进先出,后进后出。
  • 栈:后进先出,先进后出。
  • 数组:内存连续区域,查询快,增删慢。
  • 链表:元素是游离的,查询慢,首尾操作极快。
  • 二叉树:永远只有一个根节点, 每个结点不超过2个子节点的树。
  • 查找二叉树:小的左边,大的右边,但是可能树很高,查询性能变差。
  • 平衡查找二叉树:让树的高度差不大于1,增删改查都提高了。
  • 红黑树(就是基于红黑规则实现了自平衡的排序二叉树)

10、List系列集合

10.1、List 集合特点、特有API

List系列集合特点

  • ArrayList、LinekdList:有序,可重复,有索引。
  • 有序:存储和取出的元素顺序一致
  • 有索引:可以通过索引操作元素
  • 可重复:存储的元素可以重复

List集合特有方法

List集合因为支持索引,所以多了很多索引操作的独特api,其他Collection的功能List也都继承了。

方法名称

说明

void add(int index,E element)

在此集合中的指定位置插入指定的元素

E remove(int index)

删除指定索引处的元素,返回被删除的元素

E set(int index,E element)

修改指定索引处的元素,返回被修改的元素

E get(int index)

返回指定索引处的元素

List的实现类的底层原理

  • ArrayList底层是基于数组实现的,根据查询元素快,增删相对慢。
  • LinkedList底层基于双链表实现的,查询元素慢,增删首尾元素是非常快的。

代码演示

import java.util.ArrayList;

/**
      目标:ArrayList集合。

      Collection集合的体系
                         Collection<E>(接口)
              /                                                
       Set<E>(接口)                                         List<E>(接口)
            /                                                 /                                       
      HashSet<E>(实现类)    TreeSet<E>(实现类)          LinkedList<E>(实现类)  Vector(线程安全)     ArrayList<E>(实现类)
          /
         LinkedHashSet<E>(实现类)

      Collection集合体系的特点:
         Set系列集合: 添加的元素,是无序,不重复,无索引的。
             -- HashSet:添加的元素,是无序,不重复,无索引的。
             -- LinkedHashSet:添加的元素,是有序,不重复,无索引的。
         List系列集合:添加的元素,是有序,可重复,有索引的。
             -- LinkedList: 添加的元素,是有序,可重复,有索引的。
             -- ArrayList: 添加的元素,是有序,可重复,有索引的。
             -- Vector 是线程安全的,速度慢,工作中很少使用。

         1、List集合继承了Collection集合的全部功能,"同时因为List系列集合有索引",
         2、因为List集合多了索引,所以多了很多按照索引操作元素的功能:
         3、ArrayList实现类集合底层基于数组存储数据的,查询快,增删慢!
             - public void add(int index, E element): 将指定的元素,添加到该集合中的指定位置上。
             - public E get(int index):返回集合中指定位置的元素。
             - public E remove(int index): 移除列表中指定位置的元素, 返回的是被移除的元素。
             - public E set(int index, E element):用指定元素替换集合中指定位置的元素,返回更新前的元素值。
      小结:
            ArrayList集合的底层是基于数组存储数据。查询快,增删慢!(相对的)
 */
public class ListDemo01 {
    public static void main(String[] args) {
        // 1.创建一个ArrayList集合对象:
        // List:有序,可重复,有索引的。
        ArrayList<String> list = new ArrayList<>(); // 一行经典代码!
        list.add("Java");
        list.add("Java");
        list.add("HTML");
        list.add("HTML");
        list.add("MySQL");
        list.add("MySQL");

        // 2.在某个索引位置插入元素。
        list.add(2, "黑马");
        System.out.println(list);

        // 3.根据索引删除元素,返回被删除元素
        System.out.println(list.remove(1));
        System.out.println(list);

        // 4.根据索引获取元素:public E get(int index):返回集合中指定位置的元素。
        System.out.println(list.get(1));

        // 5.修改索引位置处的元素: public E set(int index, E element)
        System.out.println(list.set(1, "传智教育"));
        System.out.println(list);
    }
}

输出结果

[Java, Java, 黑马, HTML, HTML, MySQL, MySQL]
Java
[Java, 黑马, HTML, HTML, MySQL, MySQL]
黑马
黑马
[Java, 传智教育, HTML, HTML, MySQL, MySQL]

10.2、List 集合的遍历

List集合的遍历方式

  • 迭代器
  • 增强for循环
  • Lambda表达式
  • for循环(因为List集合存在索引)

代码演示

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

/**
    拓展:List系列集合的遍历方式有:4种。

    List系列集合多了索引,所以多了一种按照索引遍历集合的for循环。

    List遍历方式:
        (1)for循环。(独有的,因为List有索引)。
        (2)迭代器。
        (3)foreach。
        (4)JDK 1.8新技术。
 */
public class ListDemo02 {
    public static void main(String[] args) {
        List<String> lists = new ArrayList<>();
        lists.add("java1");
        lists.add("java2");
        lists.add("java3");

        /** (1)for循环。 */
        System.out.println("-----------------------");

        for (int i = 0; i < lists.size(); i++) {
            String ele = lists.get(i);
            System.out.println(ele);
        }


        /** (2)迭代器。 */
        System.out.println("-----------------------");
        Iterator<String> it = lists.iterator();
        while (it.hasNext()){
            String ele = it.next();
            System.out.println(ele);
        }

        /** (3)foreach */
        System.out.println("-----------------------");
        for (String ele : lists) {
            System.out.println(ele);
        }

        /** (4)JDK 1.8开始之后的Lambda表达式  */
        System.out.println("-----------------------");
        lists.forEach(s -> {
            System.out.println(s);
        });

    }
}

10.3、ArrayList 集合的底层原理

ArrayList集合的底层原理

  • ArrayList底层是基于数组实现的:根据索引定位元素快,增删需要做元素的移位操作。
  • 第一次创建集合并添加第一个元素的时候,在底层创建一个默认长度为10的数组。

为何ArrayList查询快、增删元素相对较慢。

因为数组在插入时需要对元素进行迁移

List集合存储的元素要超过容量怎么办?

创建一个新数组,默认为原数组的1.5倍,然后把旧元素迁移过去

10.4、LinkedList 集合特点

底层数据结构是双链表,查询慢,首尾操作的速度是极快的,所以多了很多首尾操作的特有API。

LinkedList 集合的特有功能

方法名称

说明

public void addFirst(E e)

在该列表开头插入指定的元素

public void addLast(E e)

将指定的元素追加到此列表的末尾

public E getFirst()

返回此列表中的第一个元素

public E getLast()

返回此列表中的最后一个元素

public E removeFirst()

从此列表中删除并返回第一个元素

public E removeLast()

从此列表中删除并返回最后一个元素

代码演示

import java.util.LinkedList;

/**
     目标:LinkedList集合。

     Collection集合的体系:
                Collection<E>(接口)
     /                                       
 Set<E>(接口)                             List<E>(接口)
     /                                   /                                   
 HashSet<E>(实现类)                   LinkedList<E>(实现类) Vector(实现类)  ArrayList<E>(实现类)
     /
     LinkedHashSet<E>(实现类)

     Collection集合体系的特点:
         Set系列集合: 添加的元素,是无序,不重复,无索引的。
             -- HashSet:添加的元素,是无序,不重复,无索引的。
             -- LinkedHashSet:添加的元素,是有序,不重复,无索引的。
         List系列集合:添加的元素,是有序,可重复,有索引的。
             -- LinkedList: 添加的元素,是有序,可重复,有索引的。
             -- Vector: 添加的元素,是有序,可重复,有索引的。线程安全(淘汰了)
             -- ArrayList: 添加的元素,是有序,可重复,有索引的。

     LinkedList也是List的实现类:底层是基于双链表的,增删比较快,查询慢!!
     LinkedList是支持双链表,定位前后的元素是非常快的,增删首尾的元素也是最快的
     所以LinkedList除了拥有List集合的全部功能还多了很多操作首尾元素的特殊功能:
         - public void addFirst(E e):将指定元素插入此列表的开头。
         - public void addLast(E e):将指定元素添加到此列表的结尾。
         - public E getFirst():返回此列表的第一个元素。
         - public E getLast():返回此列表的最后一个元素。
         - public E removeFirst():移除并返回此列表的第一个元素。
         - public E removeLast():移除并返回此列表的最后一个元素。
         - public E pop():从此列表所表示的堆栈处弹出一个元素。
         - public void push(E e):将元素推入此列表所表示的堆栈。

    小结:
         LinkedList是支持双链表,定位前后的元素是非常快的,增删首尾的元素也是最快的。
         所以提供了很多操作首尾元素的特殊API可的实以做栈和队列现。

         如果查询多而增删少用ArrayList集合。(用的最多的)
         如果查询少而增删首尾较多用LinkedList集合。
 */
public class ListDemo03 {
    public static void main(String[] args) {
        // LinkedList可以完成队列结构,和栈结构 (双链表)
        // 1、做一个队列:
        LinkedList<String> queue = new LinkedList<>();
        // 入队
        queue.addLast("1号");
        queue.addLast("2号");
        queue.addLast("3号");
        System.out.println(queue);
        // 出队
        // System.out.println(queue.getFirst());
        System.out.println(queue.removeFirst());
        System.out.println(queue.removeFirst());
        System.out.println(queue);

        // 2、做一个栈
        LinkedList<String> stack = new LinkedList<>();
        // 入栈 压栈 (push)
        stack.push("第1颗子弹");
        stack.push("第2颗子弹");
        stack.push("第3颗子弹");
        stack.push("第4颗子弹");
        System.out.println(stack);

        // 出栈  弹栈 pop
        System.out.println(stack.pop());
        System.out.println(stack.pop());
        System.out.println(stack.pop());
        System.out.println(stack);

    }
}

输出结果

[1号, 2号, 3号]
1号
2号
[3号]
[第4颗子弹, 第3颗子弹, 第2颗子弹, 第1颗子弹]
第4颗子弹
第3颗子弹
第2颗子弹
[第1颗子弹]

push 底层调用了 addFirst()

10.5、集合的并发修改异常问题

问题引出

当我们从集合中找出某个元素并删除的时候可能出现一种并发修改异常问题。

哪些遍历存在问题?

  • 迭代器遍历集合且直接用集合删除元素的时候可能出现。
  • 增强for循环遍历集合且直接用集合删除元素的时候可能出现。

哪种遍历且删除元素不出问题

  • 迭代器遍历集合但是用迭代器自己的删除方法操作可以解决。
  • 使用for循环遍历并删除元素不会存在这个问题。需要倒着遍历或者再每次删除元素后进行 i-- 操作

代码演示

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

/**
    目标:研究集合遍历并删除元素可能出现的:并发修改异常问题。
 */
public class Test {
    public static void main(String[] args) {
        // 1、准备数据
        ArrayList<String> list = new ArrayList<>();
        list.add("黑马");
        list.add("Java");
        list.add("Java");
        list.add("赵敏");
        list.add("赵敏");
        list.add("素素");
        System.out.println(list);
        // [黑马, Java, Java, 赵敏, 赵敏, 素素]
        //        it

        // 需求:删除全部的Java信息。
        // a、迭代器遍历删除
        Iterator<String> it = list.iterator();
//        while (it.hasNext()){
//            String ele = it.next();
//            if("Java".equals(ele)){
//                // 删除Java
//                // list.remove(ele); // 集合删除会出毛病
//                it.remove(); // 删除迭代器所在位置的元素值(没毛病)
//            }
//        }
//        System.out.println(list);

        // b、foreach遍历删除 (会出现问题,这种无法解决的,foreach不能边遍历边删除,会出bug)
//        for (String s : list) {
//            if("Java".equals(s)){
//                list.remove(s);
//            }
//        }

        // c、lambda表达式(会出现问题,这种无法解决的,Lambda遍历不能边遍历边删除,会出bug)
//        list.forEach(s -> {
//            if("Java".equals(s)){
//                list.remove(s);
//            }
//        });

        // d、for循环(边遍历边删除集合没毛病,但是必须从后面开始遍历删除才不会出现漏掉应该删除的元素)
        for (int i = list.size() - 1; i >= 0 ; i--) {
            String ele = list.get(i);
            if("Java".equals(ele)){
                list.remove(ele);
            }
        }
        System.out.println(list);
    }
}

11、泛型深入

11.1、泛型的概述和优势

泛型概述

  • 泛型:是JDK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查。
  • 泛型的格式:<数据类型>;注意:泛型只能支持引用数据类型。
  • 集合体系的全部接口和实现类都是支持泛型的使用的。

泛型的好处

  • 统一数据类型。
  • 把运行时期的问题提前到了编译期间,避免了强制类型转换可能出现的异常,因为编译阶段类型就能确定下来。

11.2、自定义泛型类

泛型类的概述

  • 定义类时同时定义了泛型的类就是泛型类。
  • 泛型类的格式:修饰符 class 类名<泛型变量>{ }
  • 此处泛型变量T可以随便写为任意标识,常见的如E、T、K、V等。
  • 作用:编译阶段可以指定数据类型,类似于集合的作用。

案例

模拟ArrayList集合自定义一个集合MyArrayList集合,完成添加和删除功能的泛型设计即可。

代码演示

MyArrayList 类

import java.util.ArrayList;

public class MyArrayList<E> {
    private ArrayList lists = new ArrayList();

    public void add(E e){
        lists.add(e);
    }

    public void remove(E e){
        lists.remove(e);
    }

    @Override
    public String toString() {
        return lists.toString();
    }
}

测试类

public class Test {
    public static void main(String[] args) {
        // 需求:模拟ArrayList定义一个MyArrayList ,关注泛型设计
        MyArrayList<String> list = new MyArrayList<>();
        list.add("Java");
        list.add("Java");
        list.add("MySQL");
        list.remove("MySQL");
        System.out.println(list);

        MyArrayList<Integer> list2 = new MyArrayList<>();
        list2.add(23);
        list2.add(24);
        list2.add(25);
        list2.remove(25);
        System.out.println(list2);
    }
}

输出结果

[Java, Java]
[23, 24]

泛型类的原理

把出现泛型变量的地方全部替换成传输的真实数据类型。

11.3、自定义泛型方法

泛型方法的概述

  • 定义方法时同时定义了泛型的方法就是泛型方法。
  • 泛型方法的格式:修饰符 <泛型变量> 方法返回值 方法名称(形参列表){}
  • 作用:方法中可以使用泛型接收一切实际类型的参数,方法更具备通用性。

案例

给你任何一个类型的数组,都能返回它的内容。也就是实现Arrays.toString(数组)的功能!

代码演示

/**
    目标:自定义泛型方法。

    什么是泛型方法?
        定义了泛型的方法就是泛型方法。

    泛型方法的定义格式:
        修饰符 <泛型变量> 返回值类型 方法名称(形参列表){

        }
        注意:方法定义了是什么泛型变量,后面就只能用什么泛型变量。
        泛型类的核心思想:是把出现泛型变量的地方全部替换成传输的真实数据类型。

    需求:给你任何一个类型的数组,都能返回它的内容。Arrays.toString(数组)的功能!

    小结:
        泛型方法可以让方法更灵活的接收数据,可以做通用技术!
 */
public class GenericDemo {
    public static void main(String[] args) {
        String[] names = {"小璐", "蓉容", "小何"};
        printArray(names);

        Integer[] ages = {10, 20, 30};
        printArray(ages);

        Integer[] ages2 = getArr(ages);
        String[]  names2 = getArr(names);
    }

    public static <T> T[] getArr(T[] arr){
        return arr;
    }

    public static <T> void printArray(T[] arr){
        if(arr != null){
            StringBuilder sb = new StringBuilder("[");
            for (int i = 0; i < arr.length; i++) {
                sb.append(arr[i]).append(i == arr.length - 1 ? "" : ", ");
            }
            sb.append("]");
            System.out.println(sb);
        }else {
            System.out.println(arr);
        }
    }
}

输出结果

[小璐, 蓉容, 小何]
[10, 20, 30]

泛型方法的原理

把出现泛型变量的地方全部替换成传输的真实数据类型。

11.4、自定义泛型接口

泛型接口的概述

  • 使用了泛型定义的接口就是泛型接口。
  • 泛型接口的格式:修饰符 interface 接口名称<泛型变量>{}
  • 作用:泛型接口可以让实现类选择当前功能需要操作的数据类型

案例

教务系统,提供一个接口可约束一定要完成数据(学生,老师)的增删改查操作

代码演示

学生类

public class Student {
}

老师类

public class Teacher {
}

泛型接口

public interface Data<E> {
    void add(E e);
    void delete(int id);
    void update(E e);
    E queryById(int id);
}

学生实现类

public class StudentData implements Data<Student>{
    @Override
    public void add(Student student) {

    }

    @Override
    public void delete(int id) {

    }

    @Override
    public void update(Student student) {

    }

    @Override
    public Student queryById(int id) {
        return null;
    }
}

老师实现类

public class TeacherData implements Data<Teacher>{
    @Override
    public void add(Teacher teacher) {

    }

    @Override
    public void delete(int id) {

    }

    @Override
    public void update(Teacher teacher) {

    }

    @Override
    public Teacher queryById(int id) {
        return null;
    }
}

泛型接口的原理

实现类可以在实现接口的时候传入自己操作的数据类型,这样重写的方法都将是针对于该类型的操作。

11.5、泛型通配符、上下限

通配符:

  • 可以在“使用泛型”的时候代表一切类型。
  • E T K V 是在定义泛型的时候使用的。

案例

开发一个极品飞车的游戏,所有的汽车都能一起参与比赛。

代码演示

import java.util.ArrayList;

/**
    目标:泛型通配符。?

    需求:开发一个极品飞车的游戏,所有的汽车都能一起参与比赛。

    注意:
        虽然BMW和BENZ都继承了Car
        但是ArrayList<BMW>和ArrayList<BENZ>与ArrayList<Car>没有关系的!!
    通配符:?
        ?可以在“使用泛型”的时候代表一切类型。
        E T K V 是在定义泛型的时候使用的。
    泛型的上下限:
        ? extends Car : ?必须是Car或者其子类  泛型上限
        ? super Car :?必须是Car或者其父类   泛型下限
    小结:
        通配符:?
        ?可以在“使用泛型”的时候代表一切类型。

 */
public class GenericDemo {
    public static void main(String[] args) {
        ArrayList<BMW> bmws = new ArrayList<>();
        bmws.add(new BMW());
        bmws.add(new BMW());
        bmws.add(new BMW());
        go(bmws);

        ArrayList<BENZ> benzs = new ArrayList<>();
        benzs.add(new BENZ());
        benzs.add(new BENZ());
        benzs.add(new BENZ());
        go(benzs);

        ArrayList<Dog> dogs = new ArrayList<>();
        dogs.add(new Dog());
        dogs.add(new Dog());
        dogs.add(new Dog());
        // go(dogs);
    }

    /**
       所有车比赛
     */
    public static void go(ArrayList<? extends Car> cars){
    }
}

class Dog{

}

class BENZ extends Car{
}

class BMW extends Car{
}

// 父类
class Car{
}

注意

虽然BMW和BENZ都继承了Car但是ArrayList和ArrayList与ArrayList没有关系的!

泛型的上下限

  • extends Car: 必须是Car或者其子类 泛型上限
  • super Car : 必须是Car或者其父类 泛型下限

12、Set 系列集合

12.1、Set 系列集合概述

Set 系列集合特点

  • 无序:存取顺序不一致
  • 不重复:可以去除重复
  • 无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素。

Set 集合实现类特点

  • HashSet : 无序、不重复、无索引。
  • LinkedHashSet:有序、不重复、无索引。
  • TreeSet:排序、不重复、无索引。

Set集合的功能上基本上与Collection的API一致。

代码演示

import java.util.HashSet;
import java.util.Set;
public class SetDemo1 {
    public static void main(String[] args) {
        // 看看Set系列集合的特点: HashSet LinkedHashSet TreeSet
        Set<String> sets = new HashSet<>(); // 一行经典代码  无序不重复,无索引
        sets.add("MySQL");
        sets.add("MySQL");
        sets.add("Java");
        sets.add("Java");
        sets.add("HTML");
        sets.add("HTML");
        sets.add("SpringBoot");
        sets.add("SpringBoot");
        System.out.println(sets);
    }
}

输出结果

[Java, MySQL, HTML, SpringBoot]

12.2、HashSet 的底层原理

哈希值

是 JDK 根据对象的地址,按照某种规则算出来的int类型的数值。

Object 类的 API

public int hashCode():返回对象的哈希值

对象的哈希值特点

  • 同一个对象多次调用hashCode()方法返回的哈希值是相同的
  • 默认情况下,不同对象的哈希值是不同的。

代码演示

public class SetDemo2 {
    public static void main(String[] args) {
        // 目标:学会获取对象的哈希值,并确认一下
        String name = "itheima";
        System.out.println(name.hashCode());
        System.out.println(name.hashCode());

        String name1 = "itheima1";
        System.out.println(name1.hashCode());
        System.out.println(name1.hashCode());

        String name3 = "itheima";
        System.out.println(name3.hashCode());
        System.out.println(name3.hashCode());
    }
}

输出结果

2118746965
2118746965
1256646524
1256646524
2118746965
2118746965

HashSet 元素无序的底层原理

JDK1.7 版本 HashSet 原理解析

1. 底层结构:哈希表(数组、链表的结合体)
  1. 创建一个默认长度16的数组,数组名table

  2. 根据元素的哈希值跟数组的长度求余计算出应存入的位置(哈希算法)

  3. 判断当前位置是否为null,如果是null直接存入

  4. 如果位置不为null,表示有元素,则调用equals方法比较

  5. 如果一样,则不存,如果不一样,则存入数组

    • JDK 7新元素占老元素位置,指向老元素

    • JDK 8中新元素挂在老元素下面

**结论:**哈希表是一种对于增删改查数据性能都较好的结构。

JDK1.8 版本开始 HashSet 原理解析

  • 底层结构:哈希表(数组、链表、红黑树的结合体)
  • 当挂在元素下面的数据过多时,查询性能降低,从JDK8开始后,当链表长度超过8的时候,自动转换为红黑树。

**结论:**JDK8开始后,哈希表对于红黑树的引入进一步提高了操作数据的性能。

哈希表的详细流程

  1. 创建一个默认长度16,默认加载因为0.75的数组,数组名table
  2. 根据元素的哈希值跟数组的长度计算出应存入的位置
  3. 判断当前位置是否为null,如果是null直接存入,如果位置不为null,表示有元素, 则调用equals方法比较属性值,如果一样,则不存,如果不一样,则存入数组。
  4. 当数组存满到16*0.75=12时,就自动扩容,每次扩容原先的两倍

HashSet 去重复原理解析

  1. 创建一个默认长度16的数组,数组名table
  2. 根据元素的哈希值跟数组的长度求余计算出应存入的位置(哈希算法)
  3. 判断当前位置是否为null,如果是null直接存入
  4. 如果位置不为null,表示有元素,则调用equals方法比较
  5. 如果一样,则不存,如果不一样,则存入数组,

**结论:**如果希望Set集合认为2个内容一样的对象是重复的,必须重写对象的hashCode()和equals()方法

案例:Set集合去重复

需求:

  • 创建一个存储学生对象的集合,存储多个学生对象,使用程序实现在控制台遍历该集合
  • 要求:学生对象的成员变量值相同,我们就认为是同一个对象

分析:

  1. 定义学生类,创建 HashSet 集合对象, 创建学生对象
  2. 把学生添加到集合
  3. 在学生类中重写两个方法,hashCode() 和 equals(),自动生成即可
  4. 遍历集合(增强for)

代码演示

学生类

import java.util.Objects;

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

    public Student() {
    }

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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    public char getSex() {
        return sex;
    }

    public void setSex(char sex) {
        this.sex = sex;
    }

    /**
       只要2个对象内容一样,结果一定是true
     * @param o
     * @return
     */
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age && sex == student.sex && Objects.equals(name, student.name);
    }

    /**
     s1 = new Student("无恙", 20, '男')
     s2 = new Student("无恙", 20, '男')
     s3 = new Student("周雄", 21, '男')
     */
    @Override
    public int hashCode() {
        return Objects.hash(name, age, sex);
    }

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

测试类

import java.util.HashSet;
import java.util.Set;

/**
    目标:让Set集合把重复内容的对象去掉一个(去重复)
 */
public class SetDemo3 {
    public static void main(String[] args) {
        // Set集合去重复原因:先判断哈希值算出来的存储位置是否相同 再判断equals
        Set<Student> sets = new HashSet<>();

        Student s1 = new Student("无恙", 20, '男');
        Student s2 = new Student("无恙", 20, '男');
        Student s3 = new Student("周雄", 21, '男');
        System.out.println(s1.hashCode());
        System.out.println(s2.hashCode());
        System.out.println(s3.hashCode());

        sets.add(s1);
        sets.add(s2);
        sets.add(s3);

        System.out.println(sets);
    }
}

输出结果

800712763
800712763
680875133
[Student{name='无恙', age=20, sex=男}, Student{name='周雄', age=21, sex=男}]

12.3、Set 系列集合实现类

LinkedHashSet集合概述和特点

  • 有序、不重复、无索引。
  • 这里的有序指的是保证存储和取出的元素顺序一致。
  • 原理:底层数据结构是依然哈希表,只是每个元素又额外的多了一个双链表的机制记录存储的顺序。

代码演示

import java.util.LinkedHashSet;
import java.util.Set;
public class SetDemo1 {
    public static void main(String[] args) {
        // 看看Set系列集合的特点: HashSet LinkedHashSet TreeSet
//        Set<String> sets = new HashSet<>(); // 一行经典代码  无序不重复,无索引
         Set<String> sets = new LinkedHashSet<>(); // 有序  不重复 无索引
        sets.add("MySQL");
        sets.add("MySQL");
        sets.add("Java");
        sets.add("Java");
        sets.add("HTML");
        sets.add("HTML");
        sets.add("SpringBoot");
        sets.add("SpringBoot");
        System.out.println(sets);
    }
}

输出结果

[MySQL, Java, HTML, SpringBoot]

TreeSet集合概述和特点

  • 不重复、无索引、可排序。
  • 可排序:按照元素的大小默认升序(有小到大)排序。
  • TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好。
  • 注意:TreeSet集合是一定要排序的,可以将元素按照指定的规则进行排序。

TreeSet集合默认的规则

  • 对于数值类型:Integer , Double,官方默认按照大小进行升序排序。
  • 对于字符串类型:默认按照首字符的编号升序排序。
  • 对于自定义类型如Student对象,TreeSet无法直接排序。

结论:想要使用TreeSet存储自定义类型,需要制定排序规则。

自定义排序规则

TreeSet集合存储对象的的时候有2种方式可以设计自定义比较规则

  1. 让自定义的类(如学生类)实现Comparable接口重写里面的compareTo方法来定制比较规则。
  2. TreeSet集合有参数构造器,可以设置Comparator接口对应的比较器对象,来定制比较规则。

补充

两种方式中,关于返回值的规则:

  • 如果认为第一个元素大于第二个元素返回正整数即可。

  • 如果认为第一个元素小于第二个元素返回负整数即可。

  • 如果认为第一个元素等于第二个元素返回0即可,此时Treeset集合只会保留一个元素,认为两者重复。

注意:如果TreeSet集合存储的对象有实现比较规则,集合也自带比较器,默认使用集合自带的比较器排序

代码演示

苹果类

public class Apple implements Comparable<Apple>{
    private String name;
    private String color;
    private double price;
    private int weight;

    public Apple() {
    }

    public Apple(String name, String color, double price, int weight) {
        this.name = name;
        this.color = color;
        this.price = price;
        this.weight = weight;
    }

    public String getName() {
        return name;
    }

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

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public double getPrice() {
        return price;
    }

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

    public int getWeight() {
        return weight;
    }

    public void setWeight(int weight) {
        this.weight = weight;
    }

    @Override
    public String toString() {
        return "Apple{" +
                "name='" + name + ''' +
                ", color='" + color + ''' +
                ", price=" + price +
                ", weight=" + weight +
                '}';
    }

    /**
      方式一:类自定义比较规则
      o1pareTo(o2)
     * @param o
     * @return
     */
    @Override
    public int compareTo(Apple o) {
        // 按照重量进行比较的
        return this.weight - o.weight ; // 去重重量重复的元素
        // return this.weight - o.weight >= 0 ? 1 : -1; // 保留重量重复的元素
    }
}

测试类

import java.util.Set;
import java.util.TreeSet;

/**
   目标:观察TreeSet对于有值特性的数据如何排序。
        学会对自定义类型的对象进行指定规则排序
 */
public class SetDemo5 {
    public static void main(String[] args) {
        Set<Integer> sets = new TreeSet<>(); // 不重复 无索引 可排序
        sets.add(23);
        sets.add(24);
        sets.add(12);
        sets.add(8);
        System.out.println(sets);

        Set<String> sets1 = new TreeSet<>(); // 不重复 无索引 可排序
        sets1.add("Java");
        sets1.add("Java");
        sets1.add("angela");
        sets1.add("黑马");
        sets1.add("Java");
        sets1.add("About");
        sets1.add("Python");
        sets1.add("UI");
        sets1.add("UI");
        System.out.println(sets1);

        System.out.println("------------------------------");
        // 方式二:集合自带比较器对象进行规则定制
        //
//        Set<Apple> apples = new TreeSet<>(new Comparator<Apple>() {
//            @Override
//            public int compare(Apple o1, Apple o2) {
//                // return o1.getWeight() - o2.getWeight(); // 升序
//                // return o2.getWeight() - o1.getWeight(); // 降序
//                // 注意:浮点型建议直接使用Doublepare进行比较
//                // return Doublepare(o1.getPrice() , o2.getPrice()); // 升序
//                return Doublepare(o2.getPrice() , o1.getPrice()); // 降序
//            }
//        });

        Set<Apple> apples = new TreeSet<>(( o1,  o2) ->  Doublepare(o2.getPrice() , o1.getPrice())  );
//        Set<Apple> apples = new TreeSet<>();
        apples.add(new Apple("红富士", "红色", 9.9, 500));
        apples.add(new Apple("青苹果", "绿色", 15.9, 300));
        apples.add(new Apple("绿苹果", "青色", 29.9, 400));
        apples.add(new Apple("黄苹果", "黄色", 9.8, 500));
        System.out.println(apples);
    }
}

输出结果

[8, 12, 23, 24]
[About, Java, Python, UI, angela, 黑马]
------------------------------
[Apple{name='绿苹果', color='青色', price=29.9, weight=400}, Apple{name='青苹果', color='绿色', price=15.9, weight=300}, Apple{name='红富士', color='红色', price=9.9, weight=500}, Apple{name='黄苹果', color='黄色', price=9.8, weight=500}]

13、Collection 体系

13.1、Collection体系的特点、使用场景总结

如果希望元素可以重复,又有索引,索引查询要快?

  • 用ArrayList集合,基于数组的。(用的最多)

如果希望元素可以重复,又有索引,增删首尾操作快?

  • 用LinkedList集合,基于链表的。

如果希望增删改查都快,但是元素不重复、无序、无索引。

  • 用HashSet集合,基于哈希表的。

如果希望增删改查都快,但是元素不重复、有序、无索引。

  • 用LinkedHashSet集合,基于哈希表和双链表。

如果要对对象进行排序。

  • 用TreeSet集合,基于红黑树。后续也可以用List集合实现排序。

13.2、可变参数

假如需要定义一个方法求和,该方法可以灵活的完成如下需求:

  • 计算1个数据的和。
  • 计算2个数据的和。
  • 计算3个数据的和。
  • 计算n个数据的和,甚至可以支持不接收参数进行调用。

可变参数

  • 可变参数用在形参中可以接收多个数据。
  • 可变参数的格式:数据类型…参数名称

可变参数的作用

  • 传输参数非常灵活,方便。可以不传输参数,可以传输1个或者多个,也可以传输一个数组。
  • 可变参数在方法内部本质上就是一个数组。

可变参数的注意事项

  • 一个形参列表中可变参数只能有一个
  • 可变参数必须放在形参列表的最后面

代码演示

import java.util.Arrays;

/**
    目标:可变参数。

    可变参数用在形参中可以接收多个数据。
    可变参数的格式:数据类型...参数名称

    可变参数的作用:
         传输参数非常灵活,方便。
         可以不传输参数。
         可以传输一个参数。
         可以传输多个参数。
         可以传输一个数组。

     可变参数在方法内部本质上就是一个数组。
     可变参数的注意事项:
         1.一个形参列表中可变参数只能有一个!!
         2.可变参数必须放在形参列表的最后面!!
     小结:
        记住。
 */
public class MethodDemo {
    public static void main(String[] args) {

        sum(); // 1、不传参数
        sum(10); // 2、可以传输一个参数
        sum(10, 20, 30); // 3、可以传输多个参数
        sum(new int[]{10, 20, 30, 40, 50}); // 4、可以传输一个数组
    }

    /**
       注意:一个形参列表中只能有一个可变参数,可变参数必须放在形参列表的最后面
     * @param nums
     */
    public static void sum(int...nums){
        // 注意:可变参数在方法内部其实就是一个数组。 nums
        System.out.println("元素个数:" + nums.length);
        System.out.println("元素内容:" + Arrays.toString(nums));
    }
}

输出结果

元素个数:0
元素内容:[]
元素个数:1
元素内容:[10]
元素个数:3
元素内容:[10, 20, 30]
元素个数:5
元素内容:[10, 20, 30, 40, 50]

13.3、集合工具类 Collections

Collections 集合工具类

  • java.utils.Collections:是集合工具类
  • 作用:Collections并不属于集合,是用来操作集合的工具类。

Collections 常用的API

方法名称

说明

public static boolean addAll(Collection< super T> c, T… elements)

给集合对象批量添加元素

public static void shuffle(List<> list)

打乱List集合元素的顺序

Collections排序相关API

使用范围:只能对于List集合的排序。

排序方式1:

方法名称

说明

public static void sort(List list)

将集合中元素按照默认规则排序

注意:本方式不可以直接对自定义类型的List集合排序,除非自定义类型实现了比较规则Comparable接口

代码演示

import java.util.*;

/**
    目标:Collections工具类的使用。

    java.utils.Collections:是集合工具类
    Collections并不属于集合,是用来操作集合的工具类。
    Collections有几个常用的API:
         - public static <T> boolean addAll(Collection<? super T> c, T... elements)
             给集合对象批量添加元素!
         - public static void shuffle(List<?> list) :打乱集合顺序。
         - public static <T> void sort(List<T> list):将集合中元素按照默认规则排序。
         - public static <T> void sort(List<T> list,Comparator<? super T> c):将集合中元素按照指定规则排序。
 */
public class CollectionsDemo01 {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        //names.add("楚留香");
        //names.add("胡铁花");
        //names.add("张无忌");
        //names.add("陆小凤");
        Collections.addAll(names, "楚留香","胡铁花", "张无忌","陆小凤");
        System.out.println(names);

        // 2、public static void shuffle(List<?> list) :打乱集合顺序。
        Collections.shuffle(names);
        System.out.println(names);

        // 3、 public static <T> void sort(List<T> list):将集合中元素按照默认规则排序。 (排值特性的元素)
        List<Integer> list = new ArrayList<>();
        Collections.addAll(list, 12, 23, 2, 4);
        System.out.println(list);
        Collections.sort(list);
        System.out.println(list);
    }
}

输出结果

[楚留香, 胡铁花, 张无忌, 陆小凤]
[陆小凤, 胡铁花, 楚留香, 张无忌]
[12, 23, 2, 4]
[2, 4, 12, 23]

排序方式2:

方法名称

说明

public static void sort(List list,Comparator< super T> c)

将集合中元素按照指定规则排序

代码演示

Apple 类

public class Apple implements Comparable<Apple>{
    private String name;
    private String color;
    private double price;
    private int weight;

    public Apple() {
    }

    public Apple(String name, String color, double price, int weight) {
        this.name = name;
        this.color = color;
        this.price = price;
        this.weight = weight;
    }

    public String getName() {
        return name;
    }

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

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public double getPrice() {
        return price;
    }

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

    public int getWeight() {
        return weight;
    }

    public void setWeight(int weight) {
        this.weight = weight;
    }

    @Override
    public String toString() {
        return "Apple{" +
                "name='" + name + ''' +
                ", color='" + color + ''' +
                ", price=" + price +
                ", weight=" + weight +
                '}';
    }

    /**
      方式一:类自定义比较规则
      o1pareTo(o2)
     * @param o
     * @return
     */
    @Override
    public int compareTo(Apple o) {
        // 按照重量进行比较的
        return this.weight - o.weight ; // List集存储相同大小的元素 会保留!
    }
}

实现类

import java.util.*;

/**
    目标:引用数据类型的排序。

    字符串按照首字符的编号升序排序!

    自定义类型的比较方法API:Collections
         - public static <T> void sort(List<T> list):
               将集合中元素按照默认规则排序。
               对于自定义的引用类型的排序人家根本不知道怎么排,直接报错!

         - public static <T> void sort(List<T> list,Comparator<? super T> c):
                将集合中元素按照指定规则排序,自带比较器
 */
public class CollectionsDemo02 {
    public static void main(String[] args) {
        List<Apple> apples = new ArrayList<>(); // 可以重复!
        apples.add(new Apple("红富士", "红色", 9.9, 500));
        apples.add(new Apple("青苹果", "绿色", 15.9, 300));
        apples.add(new Apple("绿苹果", "青色", 29.9, 400));
        apples.add(new Apple("黄苹果", "黄色", 9.8, 500));

//        Collections.sort(apples); // 方法一:可以的,Apple类已经重写了比较规则
//        System.out.println(apples);

        // 方式二:sort方法自带比较器对象
//        Collections.sort(apples, new Comparator<Apple>() {
//            @Override
//            public int compare(Apple o1, Apple o2) {
//                return Doublepare(o1.getPrice() , o2.getPrice()); // 按照价格排序!!
//            }
//        });

        Collections.sort(apples, ( o1,  o2) ->  Doublepare(o1.getPrice() , o2.getPrice()) );
        System.out.println(apples);

    }
}

13.4、Collection 体系的综合案例

案例:斗地主游戏

需求:

  • 在启动游戏房间的时候,应该提前准备好54张牌,完成洗牌、发牌、牌排序、逻辑。

分析:

  • 当系统启动的同时需要准备好数据的时候,就可以用静态代码块了。
  • 洗牌就是打乱牌的顺序。
  • 定义三个玩家、依次发出51张牌
  • 给玩家的牌进行排序(拓展)
  • 输出每个玩家的牌数据。

代码演示

Card 类

public class Card {
    private String size;
    private String color;
    private int index; // 牌的真正大小

    public Card(){
    }

    public Card(String size, String color, int index) {
        this.size = size;
        this.color = color;
        this.index = index;
    }

    public String getSize() {
        return size;
    }

    public void setSize(String size) {
        this.size = size;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public int getIndex() {
        return index;
    }

    public void setIndex(int index) {
        this.index = index;
    }

    @Override
    public String toString() {
        return size + color;
    }
}

实现类

import java.util.*;

/**
    目标:斗地主游戏的案例开发。

    业务需求分析:
        斗地主的做牌, 洗牌, 发牌, 排序(拓展知识), 看牌。
        业务: 总共有54张牌。
        点数: "3","4","5","6","7","8","9","10","J","Q","K","A","2"
        花色: "?", "?", "?", "?"
        大小王: "??" , "??"
        点数分别要组合4种花色,大小王各一张。
        斗地主:发出51张牌,剩下3张作为底牌。

    功能:
        1.做牌。
        2.洗牌。
        3.定义3个玩家
        4.发牌。
        5.排序(拓展,了解,作业)
        6.看牌
 */
public class GameDemo {
    /**
      1、定义一个静态的集合存储54张牌对象
     */
     public static List<Card> allCards = new ArrayList<>();

    /**
      2、做牌:定义静态代码块初始化牌数据
     */
    static {
        // 3、定义点数:个数确定,类型确定,使用数组
        String[] sizes = {"3","4","5","6","7","8","9","10","J","Q","K","A","2"};
        // 4、定义花色:个数确定,类型确定,使用数组
        String[] colors = {"?", "?", "?", "?"};
        // 5、组合点数和花色
        int index = 0; // 记录牌的大小
        for (String size : sizes) {
            index++;
            for (String color : colors) {
                // 6、封装成一个牌对象。
                Card c = new Card(size, color, index);
                // 7、存入到集合容器中去
                allCards.add(c);
            }
        }
        // 8 大小王存入到集合对象中去 "??" , "??"
        Card c1 = new Card("" ,  "??", ++index);
        Card c2 = new Card("" ,  "??",++index);
        Collections.addAll(allCards , c1 , c2);
        System.out.println("新牌:" + allCards);
    }

    public static void main(String[] args) {
        // 9、洗牌
        Collections.shuffle(allCards);
        System.out.println("洗牌后:" + allCards);

        // 10、发牌(定义三个玩家,每个玩家的牌也是一个集合容器)
        List<Card> linhuchong = new ArrayList<>();
        List<Card> jiumozhi = new ArrayList<>();
        List<Card> renyingying = new ArrayList<>();

        // 11、开始发牌(从牌集合中发出51张牌给三个玩家,剩余3张作为底牌)
        // allCards = [??, A?, 5?, 2?, 2?, Q?, ??, Q? ...
        //    i        0  1   2   3   4   5    6  7      %  3
        for (int i = 0; i < allCards.size() - 3; i++) {
            // 先拿到当前牌对象
            Card c = allCards.get(i);
            if(i % 3 == 0) {
                // 请阿冲接牌
                linhuchong.add(c);
            }else if(i % 3 == 1){
                // 请阿鸠
                jiumozhi.add(c);
            }else if(i % 3 == 2){
                // 请盈盈接牌
                renyingying.add(c);
            }
        }

        // 12、拿到最后三张底牌(把最后三张牌截取成一个子集合)
        List<Card> lastThreeCards = allCards.subList(allCards.size() - 3 , allCards.size());

        // 13、给玩家的牌排序(从大到小 可以自己先试试怎么实现)
        sortCards(linhuchong);
        sortCards(jiumozhi);
        sortCards(renyingying);

        // 14、输出玩家的牌:
        System.out.println("啊冲:" + linhuchong);
        System.out.println("啊鸠:" + jiumozhi);
        System.out.println("盈盈:" + renyingying);
        System.out.println("三张底牌:" + lastThreeCards);
    }

    /**
       给牌排序
     * @param cards
     */
    private static void sortCards(List<Card> cards) {
        // cards = [J?, A?, 3?, ??, 5?, Q?, 2?
        Collections.sort(cards, new Comparator<Card>() {
            @Override
            public int compare(Card o1, Card o2) {
                // o1 = J?
                // o2 = A?
                // 知道牌的大小,才可以指定规则
                return o2.getIndex() - o1.getIndex();
            }
        });
    }

}

输出结果

新牌:[3?, 3?, 3?, 3?, 4?, 4?, 4?, 4?, 5?, 5?, 5?, 5?, 6?, 6?, 6?, 6?, 7?, 7?, 7?, 7?, 8?, 8?, 8?, 8?, 9?, 9?, 9?, 9?, 10?, 10?, 10?, 10?, J?, J?, J?, J?, Q?, Q?, Q?, Q?, K?, K?, K?, K?, A?, A?, A?, A?, 2?, 2?, 2?, 2?, ??, ??]
洗牌后:[9?, A?, 4?, J?, 5?, 6?, K?, Q?, 10?, K?, 10?, 10?, 9?, 8?, 9?, 2?, A?, 4?, 5?, A?, 4?, Q?, J?, 3?, 4?, 3?, 7?, 7?, K?, K?, 2?, 8?, Q?, ??, 10?, A?, 2?, 6?, 8?, 7?, 6?, 2?, J?, 3?, Q?, 7?, 5?, 3?, 8?, 9?, 5?, J?, 6?, ??]
啊冲:[??, 2?, 2?, 2?, K?, K?, Q?, J?, J?, 9?, 9?, 8?, 7?, 7?, 7?, 5?, 4?]
啊鸠:[A?, A?, A?, K?, Q?, J?, 10?, 10?, 9?, 8?, 8?, 6?, 6?, 5?, 5?, 3?, 3?]
盈盈:[2?, A?, K?, Q?, Q?, 10?, 10?, 9?, 8?, 7?, 6?, 5?, 4?, 4?, 4?, 3?, 3?]
三张底牌:[J?, 6?, ??]

14、Map 集合体系

14.1、Map 集合的概述

Map集合概述和使用

  • Map集合是一种双列集合,每个元素包含两个数据。
  • Map集合的每个元素的格式:key=value(键值对元素)。
  • Map集合也被称为“键值对集合”。

Map集合整体格式

  • Collection集合的格式: [元素1,元素2,元素3…]
  • Map集合的完整格式:{key1=value1 , key2=value2 , key3=value3 , …}

Map集合的使用场景之一:购物车系统

分析:

  • 购物车提供的四个商品和购买的数量在后台需要容器存储。
  • 每个商品对象都一一对应一个购买数量。
  • 把商品对象看成是Map集合的建,购买数量看成Map集合的值。

{商品1=2 , 商品2=3 , 商品3 =2 , 商品4=3}

14.2、Map 集合体系特点

Map集合体系特点

  • Map集合的特点都是由键决定的。
  • Map集合的键是无序,不重复的,无索引的,值不做要求(可以重复)。
  • Map集合后面重复的键对应的值会覆盖前面重复键的值。
  • Map集合的键值对都可以为null。

Map集合实现类特点

  • HashMap:元素按照键是无序,不重复,无索引,值不做要求。(与Map体系一致)
  • LinkedHashMap:元素按照键是有序,不重复,无索引,值不做要求。
  • TreeMap:元素按照建是排序,不重复,无索引的,值不做要求。

代码演示

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

/**
    目标:认识Map体系的特点:按照键无序,不重复,无索引。值不做要求。
 */
public class MapDemo1 {
    public static void main(String[] args) {
        // 1、创建一个Map集合对象
//         Map<String, Integer> maps = new HashMap<>(); // 一行经典代码 {null=null, Java=100, 枸杞=100, 鸿星尔克=3}
        Map<String, Integer> maps = new LinkedHashMap<>(); // {鸿星尔克=3, Java=100, 枸杞=100, null=null}
        maps.put("鸿星尔克", 3);
        maps.put("Java", 1);
        maps.put("枸杞", 100);
        maps.put("Java", 100); // 覆盖前面的数据
        maps.put(null, null);
        System.out.println(maps);

    }
}

14.3、Map 集合常用API

Map是双列集合的祖宗接口,它的功能是全部双列集合都可以继承使用的。

方法名称

说明

V put(K key,V value)

添加元素

V remove(Object key)

根据键删除键值对元素

void clear()

移除所有的键值对元素

boolean containsKey(Object key)

判断集合是否包含指定的键

boolean containsValue(Object value)

判断集合是否包含指定的值

boolean isEmpty()

判断集合是否为空

int size()

集合的长度,也就是集合中键值对的个数

代码演示

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

/**
    目标:Map集合的常用API(重点中的重点)
     - public V put(K key, V value):  把指定的键与指定的值添加到Map集合中。
     - public V remove(Object key): 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。
     - public V get(Object key) 根据指定的键,在Map集合中获取对应的值。
     - public Set<K> keySet(): 获取Map集合中所有的键,存储到Set集合中。
     - public Set<Map.Entry<K,V>> entrySet(): 获取到Map集合中所有的键值对对象的集合(Set集合)。
     - public boolean containKey(Object key):判断该集合中是否有此键。
     - public boolean containValue(Object value):判断该集合中是否有此值。
 */
public class MapDemo {
    public static void main(String[] args) {
        // 1.添加元素: 无序,不重复,无索引。
        Map<String , Integer> maps = new HashMap<>();
        maps.put("iphoneX",10);
        maps.put("娃娃",20);
        maps.put("iphoneX",100);//  Map集合后面重复的键对应的元素会覆盖前面重复的整个元素!
        maps.put("huawei",100);
        maps.put("生活用品",10);
        maps.put("手表",10);
        // {huawei=100, 手表=10, 生活用品=10, iphoneX=100, 娃娃=20}
        System.out.println(maps);

        // 2.清空集合
//        maps.clear();
//        System.out.println(maps);

        // 3.判断集合是否为空,为空返回true ,反之!
        System.out.println(maps.isEmpty());

        // 4.根据键获取对应值:public V get(Object key)
        Integer key = maps.get("huawei");
        System.out.println(key);
        System.out.println(maps.get("生活用品")); // 10
        System.out.println(maps.get("生活用品2")); // null

        // 5.根据键删除整个元素。(删除键会返回键的值)
        System.out.println(maps.remove("iphoneX"));
        System.out.println(maps);

        // 6.判断是否包含某个键 ,包含返回true ,反之
        System.out.println(maps.containsKey("娃娃"));  // true
        System.out.println(maps.containsKey("娃娃2"));  // false
        System.out.println(maps.containsKey("iphoneX")); // false

        // 7.判断是否包含某个值。
        System.out.println(maps.containsValue(100));  //
        System.out.println(maps.containsValue(10));  //
        System.out.println(maps.containsValue(22)); //

        // {huawei=100, 手表=10, 生活用品=10, 娃娃=20}
        // 8.获取全部键的集合:public Set<K> keySet()
        Set<String> keys = maps.keySet();
        System.out.println(keys);

        System.out.println("------------------------------");
        // 9.获取全部值的集合:Collection<V> values();
        Collection<Integer> values = maps.values();
        System.out.println(values);

        // 10.集合的大小
        System.out.println(maps.size()); // 4

        // 11.合并其他Map集合。(拓展)
        Map<String , Integer> map1 = new HashMap<>();
        map1.put("java1", 1);
        map1.put("java2", 100);
        Map<String , Integer> map2 = new HashMap<>();
        map2.put("java2", 1);
        map2.put("java3", 100);
        map1.putAll(map2); // 把集合map2的元素拷贝一份到map1中去
        System.out.println(map1);
        System.out.println(map2);
    }
}

输出结果

{huawei=100, 手表=10, 生活用品=10, iphoneX=100, 娃娃=20}
false
100
10
null
100
{huawei=100, 手表=10, 生活用品=10, 娃娃=20}
true
false
false
true
true
false
[huawei, 手表, 生活用品, 娃娃]
------------------------------
[100, 10, 10, 20]
4
{java3=100, java2=1, java1=1}
{java3=100, java2=1}

14.4、Map 集合的遍历方式

14.4.1、Map 集合的遍历方式一:键找值

  • 先获取Map集合的全部键的Set集合。
  • 遍历键的Set集合,然后通过键提取对应值。

键找值涉及到的API

方法名称

说明

Set keySet()

获取所有键的集合

V get(Object key)

根据键获取值

代码演示

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

/**
    目标:Map集合的遍历方式一:键找值

    Map集合的遍历方式有:3种。
        (1)“键找值”的方式遍历:先获取Map集合全部的键,再根据遍历键找值。
        (2)“键值对”的方式遍历:难度较大。
        (3)JDK 1.8开始之后的新技术:Lambda表达式。(暂时了解)

    a.“键找值”的方式遍历Map集合。
        1.先获取Map集合的全部键的Set集合。
        2.遍历键的Set集合,然后通过键找值。
    小结:
        代码简单,需要记住!

 */
public class MapDemo01 {
    public static void main(String[] args) {
        Map<String , Integer> maps = new HashMap<>();
        // 1.添加元素: 无序,不重复,无索引。
        maps.put("娃娃",30);
        maps.put("iphoneX",100);
        maps.put("huawei",1000);
        maps.put("生活用品",10);
        maps.put("手表",10);
        System.out.println(maps);
        // maps = {huawei=1000, 手表=10, 生活用品=10, iphoneX=100, 娃娃=30}

        // 1、键找值:第一步:先拿到集合的全部键。
        Set<String> keys = maps.keySet();
        // 2、第二步:遍历每个键,根据键提取值
        for (String key : keys) {
            int value = maps.get(key);
            System.out.println(key + "===>" + value);
        }

    }
}

输出结果

{huawei=1000, 手表=10, 生活用品=10, iphoneX=100, 娃娃=30}
huawei===>1000
手表===>10
生活用品===>10
iphoneX===>100
娃娃===>30

14.4.2、Map集合的遍历方式二:键值对

  • 先把Map集合转换成Set集合,Set集合中每个元素都是键值对实体类型了。
  • 遍历Set集合,然后提取键以及提取值。

键值对涉及到的API

方法名称

说明

Set<Map.Entry<K,V>> entrySet()

获取所有键值对对象的集合

K getKey()

获得键

V getValue()

获取值

代码演示

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

/**
    目标:Map集合的遍历方式。

    Map集合的遍历方式有:3种。
        (1)“键找值”的方式遍历:先获取Map集合全部的键,再根据键找值。
        (2)“键值对”的方式遍历:难度较大。
        (3)JDK 1.8开始之后的新技术:Lambda表达式。

    b.“键值对”的方式遍历:
        1.把Map集合转换成一个Set集合:Set<Map.Entry<K, V>> entrySet();
        2.此时键值对元素的类型就确定了,类型是键值对实体类型:Map.Entry<K, V>
        3.接下来就可以用foreach遍历这个Set集合,类型用Map.Entry<K, V>
 */
public class MapDemo02 {
    public static void main(String[] args) {
        Map<String , Integer> maps = new HashMap<>();
        // 1.添加元素: 无序,不重复,无索引。
        maps.put("娃娃",30);
        maps.put("iphoneX",100);
        maps.put("huawei",1000);
        maps.put("生活用品",10);
        maps.put("手表",10);
        System.out.println(maps);
        // maps = {huawei=1000, 手表=10, 生活用品=10, iphoneX=100, 娃娃=30}
        /**
            maps = {huawei=1000, 手表=10, 生活用品=10, iphoneX=100, 娃娃=30}
                ??
            使用foreach遍历map集合.发现Map集合的键值对元素直接是没有类型的。所以不可以直接foreach遍历集合。
                ??
            可以通过调用Map的方法 entrySet把Map集合转换成Set集合形式  maps.entrySet();
                ??
            Set<Map.Entry<String,Integer>> entries =  maps.entrySet();
             [(huawei=1000), (手表=10), (生活用品=10), (iphoneX=100), (娃娃=30)]
                              entry
                ??
            此时可以使用foreach遍历
       */
       // 1、把Map集合转换成Set集合
        Set<Map.Entry<String, Integer>> entries = maps.entrySet();
        // 2、开始遍历
        for(Map.Entry<String, Integer> entry : entries){
            String key = entry.getKey();
            int value = entry.getValue();
            System.out.println(key + "====>" + value);
        }
    }
}

14.4.3、Map集合的遍历方式三:lambda表达式

  • 得益于JDK 8开始的新技术Lambda表达式,提供了一种更简单、更直接的遍历集合的方式。

Map结合Lambda遍历的API

方法名称

说明

default void forEach(BiConsumer< super K, super V> action)

结合lambda遍历Map集合

代码演示

import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;

/**
    目标:Map集合的遍历方式。

    Map集合的遍历方式有:3种。
        (1)“键找值”的方式遍历:先获取Map集合全部的键,再根据键找值。
        (2)“键值对”的方式遍历:难度较大。
        (3)JDK 1.8开始之后的新技术:Lambda表达式。

    c.JDK 1.8开始之后的新技术:Lambda表达式。(暂时了解)
 */
public class MapDemo03 {
    public static void main(String[] args) {
        Map<String , Integer> maps = new HashMap<>();
        // 1.添加元素: 无序,不重复,无索引。
        maps.put("娃娃",30);
        maps.put("iphoneX",100);//  Map集合后面重复的键对应的元素会覆盖前面重复的整个元素!
        maps.put("huawei",1000);
        maps.put("生活用品",10);
        maps.put("手表",10);
        System.out.println(maps);

        //  maps = {huawei=1000, 手表=10, 生活用品=10, iphoneX=100, 娃娃=30}

//        maps.forEach(new BiConsumer<String, Integer>() {
//            @Override
//            public void accept(String key, Integer value) {
//                System.out.println(key + "--->" + value);
//            }
//        });

        maps.forEach((k, v) -> {
                System.out.println(k + "--->" + v);
        });

    }
}

底层原理

14.5、Map 集合案例

统计投票人数

需求:

  • 某个班级80名学生,现在需要组成秋游活动,班长提供了四个景点依次是(A、B、C、D)。
  • 每个学生只能选择一个景点,请统计出最终哪个景点想去的人数最多。

分析:

  1. 将80个学生选择的数据拿到程序中去。
  2. 定义Map集合用于存储最终统计的结果。
  3. 遍历80个学生选择的数据,看Map集合中是否存在,不存在存入“数据=1“,存在则其对应值+1。

代码演示

import java.util.*;

/**
   需求:统计投票人数
 */
public class MapTest1 {
    public static void main(String[] args) {
         // 1、把80个学生选择的数据拿进来。
        String[] selects = {"A" , "B", "C", "D"};
        StringBuilder sb = new StringBuilder();
        Random r = new Random();
        for (int i = 0; i < 80; i++) {
            sb.append(selects[r.nextInt(selects.length)]);
        }
        System.out.println(sb);

        // 2、定义一个Map集合记录最终统计的结果: A=30 B=20 C=20 D=10  键是景点 值是选择的数量
        Map<Character, Integer> infos = new HashMap<>(); //

        // 3、遍历80个学生选择的数据
        for (int i = 0; i < sb.length(); i++) {
            // 4、提取当前选择景点字符
            char ch = sb.charAt(i);
            // 5、判断Map集合中是否存在这个键
            if(infos.containsKey(ch)){
                 // 让其值 + 1
                infos.put(ch , infos.get(ch) + 1);
            }else {
                // 说明此景点是第一次被选
                infos.put(ch , 1);
            }
        }

        // 4、输出集合
        System.out.println(infos);

    }
}

输出结果

DCBAACDACAACAABAAADAABDACCCADACABBAADBCACDBCBCACCBCDCDDBDBDAAADCABCAACCDABABBACD
{A=29, B=15, C=21, D=15}

14.6、Map 集合的实现类

14.6.1、HashMap

HashMap 的特点

  • HashMap是Map里面的一个实现类。特点都是由键决定的:无序、不重复、无索引
  • 没有额外需要学习的特有方法,直接使用Map里面的方法就可以了。
  • HashMap跟HashSet底层原理是一模一样的,都是哈希表结构,只是HashMap的每个元素包含两个值而已。

实际上:Set系列集合的底层就是Map实现的,只是Set集合中的元素只要键数据,不要值数据而已。

HashMap 的底层原理

  • 由键决定:无序、不重复、无索引。HashMap底层是哈希表结构的。
  • 依赖hashCode方法和equals方法保证键的唯一。
  • 如果键要存储的是自定义对象,需要重写hashCode和equals方法。
  • 基于哈希表。增删改查的性能都较好。

案例:HashMap 集合存储自定义对象并遍历

需求:

  • 创建一个HashMap集合,键是学生对象(Student),值是籍贯(String)。
  • 存储三个键值对元素,并遍历。

思路:

  1. 定义学生类
  2. 创建HashMap集合对象
  3. 创建学生对象
  4. 把学生添加到集合
  5. 遍历集合

代码演示

学生类

import java.util.Objects;

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

    public Student() {
    }

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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    public char getSex() {
        return sex;
    }

    public void setSex(char sex) {
        this.sex = sex;
    }

    /**
       只要2个对象内容一样,结果一定是true
     * @param o
     * @return
     */
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age && sex == student.sex && Objects.equals(name, student.name);
    }

    /**
     s1 = new Student("无恙", 20, '男')
     s2 = new Student("无恙", 20, '男')
     s3 = new Student("周雄", 21, '男')
     */
    @Override
    public int hashCode() {
        return Objects.hash(name, age, sex);
    }

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

测试类

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

public class HashMapDemo1 {
    public static void main(String[] args) {
         // Map集合是根据键去除重复元素
        Map<Student, String> maps = new HashMap<>();

        Student s1 = new Student("无恙", 20, '男');
        Student s2 = new Student("无恙", 20, '男');
        Student s3 = new Student("周雄", 21, '男');

        maps.put(s1, "北京");
        maps.put(s2, "上海");
        maps.put(s3, "广州");

        System.out.println(maps);
    }
}

输出结果

{Student{name='无恙', age=20, sex=男}=上海, Student{name='周雄', age=21, sex=男}=广州}

14.6.2、LinkedHashMap

LinkedHashMap集合概述和特点

  • 由键决定:有序、不重复、无索引。
  • 这里的有序指的是保证存储和取出的元素顺序一致
  • 原理:底层数据结构是依然哈希表,只是每个键值对元素又额外的多了一个双链表的机制记录存储的顺序。

代码演示

import java.util.LinkedHashMap;
import java.util.Map;

public class LinkedHashMapDemo2 {
    public static void main(String[] args) {
        // 1、创建一个Map集合对象
        Map<String, Integer> maps = new LinkedHashMap<>();
        maps.put("鸿星尔克", 3);
        maps.put("Java", 1);
        maps.put("枸杞", 100);
        maps.put("Java", 100); // 覆盖前面的数据
        maps.put(null, null);
        System.out.println(maps);

    }
}

输出结果

{鸿星尔克=3, Java=100, 枸杞=100, null=null}

14.6.3、TreeMap

TreeMap集合概述和特点

  • 由键决定特性:不重复、无索引、可排序。
  • 可排序:按照键数据的大小默认升序(有小到大)排序。只能对键排序。
  • 注意:TreeMap集合是一定要排序的,可以默认排序,也可以将键按照指定的规则进行排序。
  • TreeMap跟TreeSet一样底层原理是一样的。
  • 底层基于红黑树实现排序,增删改查性能较好

TreeMap集合自定义排序规则

  • 类实现Comparable接口,重写比较规则。
  • 集合自定义Comparator比较器对象,重写比较规则。

代码演示

Apple 类

public class Apple implements Comparable<Apple>{
    private String name;
    private String color;
    private double price;
    private int weight;

    public Apple() {
    }

    public Apple(String name, String color, double price, int weight) {
        this.name = name;
        this.color = color;
        this.price = price;
        this.weight = weight;
    }

    public String getName() {
        return name;
    }

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

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public double getPrice() {
        return price;
    }

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

    public int getWeight() {
        return weight;
    }

    public void setWeight(int weight) {
        this.weight = weight;
    }

    @Override
    public String toString() {
        return "Apple{" +
                "name='" + name + ''' +
                ", color='" + color + ''' +
                ", price=" + price +
                ", weight=" + weight +
                '}';
    }

    /**
      方式一:类自定义比较规则
      o1pareTo(o2)
     * @param o
     * @return
     */
    @Override
    public int compareTo(Apple o) {
        // 按照重量进行比较的
        return this.weight - o.weight ; // 去重重量重复的元素
        // return this.weight - o.weight >= 0 ? 1 : -1; // 保留重量重复的元素
    }
}

测试类

public class TreeMapDemo3 {
    public static void main(String[] args) {
        Map<Integer, String> maps1 = new TreeMap<>();
        maps1.put(13 , "王麻子");
        maps1.put(1 , "张三");
        maps1.put(3 , "县长");
        System.out.println(maps1);

        // TreeMap集合自带排序。  可排序 不重复(只要大小规则一样就认为重复)  无索引
        Map<Apple, String> maps2 = new TreeMap<>(new Comparator<Apple>() {
            @Override
            public int compare(Apple o1, Apple o2) {
                return Doublepare(o2.getPrice() , o1.getPrice()); // 按照价格降序排序!
            }
        });
        maps2.put(new Apple("红富士", "红色", 9.9, 500), "山东" );
        maps2.put(new Apple("青苹果", "绿色", 15.9, 300), "广州");
        maps2.put(new Apple("绿苹果", "青色", 29.9, 400), "江西");
        maps2.put(new Apple("黄苹果", "黄色", 9.8, 500), "湖北");

        System.out.println(maps2);
    }
}

输出结果

{1=张三, 3=县长, 13=王麻子}
{Apple{name='绿苹果', color='青色', price=29.9, weight=400}=江西, Apple{name='青苹果', color='绿色', price=15.9, weight=300}=广州, Apple{name='红富士', color='红色', price=9.9, weight=500}=山东, Apple{name='黄苹果', color='黄色', price=9.8, weight=500}=湖北}

14.7、Map 集合案例

Map集合实现类特点

  • HashMap:元素按照键是无序,不重复,无索引,值不做要求,基于哈希表(与Map体系一致)
  • LinkedHashMap:元素按照键是有序,不重复,无索引,值不做要求,基于哈希表
  • TreeMap:元素只能按照键排序,不重复,无索引的,值不做要求,可以做排序

Map集合案例-统计投票人数

需求:

  • 某个班级多名学生,现在需要组成秋游活动,班长提供了四个景点依次是(A、B、C、D)。
  • 每个学生可以选择多个景点,请统计出最终哪个景点想去的人数最多。

分析:

  • 将80个学生选择的数据拿到程序中去,需要记住每个学生选择的情况。
  • 定义Map集合用于存储最终统计的结果。

代码演示

import java.util.*;

/**
   需求:统计投票人数
 */
public class MapTest4 {
    public static void main(String[] args) {
        // 1、要求程序记录每个学生选择的情况。
        // 使用一个Map集合存储。
        Map<String, List<String>> data = new HashMap<>();

        // 2、把学生选择的数据存入进去。
        List<String> selects = new ArrayList<>();
        Collections.addAll(selects, "A", "C");
        data.put("罗勇", selects);

        List<String> selects1 = new ArrayList<>();
        Collections.addAll(selects1, "B", "C" , "D");
        data.put("胡涛", selects1);

        List<String> selects2 = new ArrayList<>();
        Collections.addAll(selects2 , "A",  "B", "C" , "D");
        data.put("刘军", selects2);

        System.out.println(data);

        // 3、统计每个景点选择的人数。
        Map<String, Integer> infos = new HashMap<>(); // {}

        // 4、提取所有人选择的景点的信息。
        Collection<List<String>> values = data.values();
        System.out.println(values);
        // values = [[A, B, C, D], [B, C, D], [A, C]]
        //             value

        for (List<String> value : values) {
            for (String s : value) {
                // 有没有包含这个景点
                if(infos.containsKey(s)){
                    infos.put(s, infos.get(s) + 1);
                }else {
                    infos.put(s , 1);
                }
            }
        }

        System.out.println(infos);
    }
}

输出结果

{刘军=[A, B, C, D], 胡涛=[B, C, D], 罗勇=[A, C]}
[[A, B, C, D], [B, C, D], [A, C]]
{A=2, B=2, C=3, D=2}

15、创建不可变集合

什么是不可变集合?

  • 不可变集合,就是不可被修改的集合。
  • 集合的数据项在创建的时候提供,并且在整个生命周期中都不可改变。否则报错。

为什么要创建不可变集合?

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

如何创建不可变集合?

  • 在List、Set、Map接口中,都存在of方法,可以创建一个不可变的集合。

方法名称

说明

static List of(E…elements)

创建一个具有指定元素的List集合对象

static Set of(E…elements)

创建一个具有指定元素的Set集合对象

static <K , V> Map<K,V> of(E…elements)

创建一个具有指定元素的Map集合对象

  • 这个集合不能添加,不能删除,不能修改。

代码演示

import java.util.List;
import java.util.Map;
import java.util.Set;

/**
     目标:不可变集合。
 */
public class CollectionDemo {
    public static void main(String[] args) {
        // 1、不可变的List集合
        List<Double> lists = List.of(569.5, 700.5, 523.0,  570.5);
        // lists.add(689.0);
        // lists.set(2, 698.5);
        // System.out.println(lists);
        double score = lists.get(1);
        System.out.println(score);

        // 2、不可变的Set集合
        Set<String> names = Set.of("迪丽热巴", "迪丽热九", "马尔扎哈", "卡尔眨巴" );
        // names.add("三少爷");
        System.out.println(names);

        // 3、不可变的Map集合
        Map<String, Integer> maps = Map.of("huawei",2, "Java开发", 1 , "手表", 1);
        // maps.put("衣服", 3);
        System.out.println(maps);
    }
}

注意:此方法 jdk9 以上才支持

先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦

更多推荐

Java进阶-常用API