JavaSE学习文档(下)

发布于:2024-04-25 ⋅ 阅读:(24) ⋅ 点赞:(0)

JavaSE学习文档(下)

第九章 异常

9.1 异常概述

概述
异常 : 指的是在程序运行的过程中,出现非正常的情况导致程序终止
        注意:异常不是指的语法错误或逻辑代码错误
系统如何处理异常
JVM(系统)是如何处理异常的Java中把不同的异常用不同的类表示。
一旦发生某种异常。系统就会创建对应异常类的对象并抛出。
该异常是可以被catch到如果catch到后可以做相应的处理。
如果没有catch动作程序终止运行。
异常体系结构
  |------Throwable
            |-------Error: 表示严重错误。无法catch只能程序挂掉。
            |-------Exception :表示普通异常是可以被catch到的。
				catch到后可以做相应的处理程序不会挂掉。没有catch到程序一样终止运行。
                |------编译时异常(受检异常):在程序编译的时候发生的异常-
                	不一定会真正的发生异常只是有可能会发生异常
                     例:有一个程序该程序需要读取配置文件,
                     但该配置文件有可能找不到不是说一定找不到。
                     那这个读取文件的操作(方法)就会在编译时发生异常-
                     所以需要处理该异常-万一找不到了怎么办。
                |------运行时异常(非受检异常)(RuntimeException及子类):
                	在程序运行的时候发生的异常。
                     这类异常是由程序员的代码编写错误导致
                     (比如:空指针  下角标越界 类型转换异常)

9.2 异常演示

error演示
public static void error(){
         
        //演示 error : OutOfMemoryError
        //int[] n = new int[Integer.MAX_VALUE];//Integer.MAX_VALUE int的最大值

        //StackOverflowError
}
编译时异常演示
 /*
        编译时异常
 */
    public static void exception(){
        //受检异常-编译时异常
        //FileNotFoundException
        //new FileInputStream("a.txt");

        //InterruptedException
        //Thread.sleep(10000);
    }
运行时异常演示
  /*
        非受检异常:运行时异常
   */
    public static void runtimeException(){
        //ArithmeticException
        //System.out.println(1 / 0);

        //NullPointerException
//        String s = null;
//        System.out.println(s.length());

        //ClassCastException
//        Object o = new Integer("1");//多态
//        String s = (String) o;

        //ArrayIndexOutOfBoundsException
//        int[] ns = new int[2];
//        ns[3] = 10;
    }

9.3 try-catch-finally

格式1
 		  try{
                可能会发生异常的代码
                代码1
                代码2 --> 发生异常。代码3就不再执行了
                代码3
            }catch(异常类型1){
                异常类型1的处理
            }catch(异常类型2){
                异常类型2的处理
            }
            .......
            finally{
                一定要执行的代码
            }
说明:
1.如果try中的代码没有发生异常 那么会执行完try中所有的代码后跳到finally中执行finally中的代码。然后跳出try-catch-finally结果继续向下执行其它代码

2.如果try中的代码发生了异常 那么在try中发生异常的代码后面的try中代码不再执行。
系统会根据相应的异常创建异常类的对象并抛出。因为我们会catch异常所以会和catch后面的异常类型匹配。如果和第一个catch后面的异常类型匹配失败继承向下匹配直到匹配成功并执行相应catch中的代码执行完后跳到finally中并执行finally中的代码执行完后跳出try-catch-finally并继续向下执行其它代码。如果向下匹配失败那么会进入finally中执行finally中的代码然后终止程序的运行(因为没抓到异常-没有匹配成功)。

3.注意:finally可以省略不写 ,如果有finally无论什么情况finally中的代码一定会执行。
格式2
try{
    可能会发生异常的代码
    代码1
    代码2 --> 发生异常。代码3就不再执行了
    代码3
}catch(异常类型1){
	异常类型1的处理
}catch(异常类型2){
	异常类型2的处理
}
.......
格式3
try{
	可能会发生异常的代码
}finally{
	一定要执行的代码
}

/*
 try-catch : 有异常要处理 没有一定要执行的代码
 try-catch-finally : 有异常要处理 而且有一定要执行的代码
 	(比如 ①关闭资源 ②保存某些数据 ③保存某些状态)
 try-finally : 有异常不处理  有一定要执行的代码
 	(比如 ①关闭资源 ②保存某些数据 ③保存某些状态)
*/
jdk1.7开始catch的变化
 		  try{
                可能会发生异常的代码
                代码1
                代码2 --> 发生异常。代码3就不再执行了
                代码3
            }catch(异常类型1 | 异常类型2 | 异常类型3 |...... e){
                异常类型1的处理
            }catch(异常类型2){
                异常类型2的处理
            }
            .......
            finally{
                一定要执行的代码
            }
API
 /*
        编译时异常

        异常的API :
            getMessage() : 获取异常信息 -- 将异常信息写到日志文件
            printStackTrace() : 将异常信息打印到控制台
*/
    public static void demo3(){

        try {
            new FileInputStream("a.txt");//编译时异常 - 必须处理。不处理没办法运行。
        } catch (FileNotFoundException e) {
            //实际生产中-要收集异常信息 -- 将异常信息写到日志文件
            //比如:在Hadoop中集群(程序)崩了后需要去日志文件查看错误信息
            System.out.println(e.getMessage());
            e.printStackTrace();//将异常信息打印到控制台
        }
        System.out.println("over");
        System.out.println("over");
    }
catch后面的异常类型
 /*
        异常的类型写父类还是具体的类型呢?
            场景1(异常类型写父类) : 无论什么异常全部是统一处理方式
            场景2(异常类型要写具体的类型 最后用父类兜底) :不同的异常不同的处理方法
        注意:如果要匹配多种异常类型 小的(异常的子类)在前面大的(异常的父类)在后面。
     */
    public static void demo2(){
        try{
            System.out.println("开始执行代码");
            //System.out.println(1 / 0); //运行时异常

            String s = null;
            System.out.println(s.length());//运行时异常
            System.out.println("执行完代码");
        }catch (ArithmeticException e){ // 写父类
            System.out.println("处理方式1");
        }catch (NullPointerException e){ // 写父类
            System.out.println("处理方式2");
        }catch (Exception e){ // 写父类
            System.out.println("统一处理");
        }
        System.out.println("over");
        System.out.println("over");
    }

9.4 finally的再说明

/*
    finally : finally中的代码一定会执行(除非System.exit(0) 关闭JVM).

    1.就算在try-catch中有return finally中的代码也一定要执行
    2.如果finally中出现return那么try-catch中的return就失效了 -- (其实发生覆盖)
 */
public class FinallyTest {

    /*
        finally不会执行的情况
     */
    public static void demo3(){
        try{
            System.exit(0); //关闭JVM --- System.exit(0) :0是JVM退出的状态码。0是正常退出
        }catch (Exception e){
            System.out.println("发生了异常");
        }finally {
            System.out.println("finally一定会执行");
        }
    }

    /*
        就算在try-catch中发生了return,那么finally中的return仍然会执行。
     */
    public static int demo2(){
        try{
            return 1;
        }catch (Exception e){
            return 2;
        }finally {
            return 3; //如果finally中出现return那么try-catch中的return就失效了 -- (其实发生覆盖)
        }
    }

    /*
        就算在try-catch中有return finally中的代码也一定要执行
     */
    public static int demo1(){
        try{
            return 1;
        }catch (Exception e){
            return 2;
        }finally {
            System.out.println("finally中的代码一定要执行");
        }
    }
}

9.5 throws

 异常五个关键字: try-catch-finally  throw throws

    制造异常
        1.系统制造异常
        2.手动制造异常 :throw
    处理异常
        1.try-catch-finally : 真正的处理异常
        2.throws :将异常进行转移。转移给调用者

    throws格式:
        修饰符 返回值类型  方法名([形参列表]) throws 异常类型1,异常类型2,.......

    处理异常:throws
        出现异常后将异常抛给调用者并不是真正的将异常处理掉。

注意:

/*
注意
    当子类重写父类中的方法时。
       1. 父类throws的异常类型 >= 子类throws的异常类型(无论是编译时异常还是运行时异常)

       2. 如果父类没有thorws 编译时异常类型 那么子类不可以throws 编译时异常类型
             throws运行时异常不受影响。

     如果在方法中throws了多个异常类型那么调用者(调用该方法的地方)也需要throws多个异常类型
        除非调用者throws的是多个异常类型的父类那么就可以只写这个父类即可。

 */
public void test2() throws Exception{
        test();
}

public void test() throws FileNotFoundException,InterruptedException{

}

9.6 throws和try-catch-finally的选择

 try-catch-finally : 真正要处理异常选 try-catch-finally
 throws : 如果是因为调用者传入的数据造成的异常那么中间所有的环节都不能处理该异常
 只能将异常向外抛一直抛到最初的调用者(输入数据)由最初的调用者处理该异常。

9.6 throw

/*
    throw :人为的制造异常

    格式 :throw new 异常类型

    说明:
        throw 编译时异常类的对象 :在提醒调用者调用我这个方法是有可能会出现问题的必须在编译前就处理掉这个有可能发生的问题
        throw 运行时异常类的对象 : 只要不满足需求程序直接崩溃掉。
 */

9.10 自定义异常类

/*
    自定义异常类 : 运行时异常 vs 编译时异常

    1.继承类
        运算时异常继承RuntimeException
        编译时异常继承Exception

    2.声明构造器(最好是两个 一个空参 一个有参)
        在本类构造器中调用父类构造器
 */
public class BuNengXiaoYuDengYu0Exception extends RuntimeException{

    public BuNengXiaoYuDengYu0Exception(){
        super();
    }

    public BuNengXiaoYuDengYu0Exception(String info){
        super(info);
    }
}

第十章 常用类

10.1 Object类

/*
    Object类

    1.Object类是所有类的父类。
    2.一个类如果没有显示继承其它类默认继承Object类
 */
public class ObjectTest {
    public static void main(String[] args) {

        int[] n = new int[2];

        //下面的写法不对:Object[]中的元素是Object类型  int[]中的元素是int类型
        //Object[] o = new int[2];

        //将数组赋值给Object (数组本身是引用数据类型)
        Object o = new int[2];

        //Object[]中的元素是Object类型  Integer[]中的元素是Integer类型
        Object[] o2 = new Integer[2];
    }
}

10.2 Object中的API

toString
/*
    一 Object中的toString方法
        public String toString() {
            return getClass().getName() + "@" + Integer.toHexString(hashCode());
        }

        getClass().getName() :获取类的全类名
        Integer.toHexString() :将数值转成十六进制
        hashCode() : 获取哈希值(哈希码)-- 每个对象都有属于自己的哈希码(可以通过哈希码区分对象)

    二 我们发现核心类库中的类基本上都重写了Object中的toString方法。用来输出内容而非地址值。
         如果是自定义的类建议也重写toString方法输出内容而非地址值。
 */
class A{
    public int id;
    public String name;

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

    @Override
    public String toString() {
        return "id=" + id + " name=" + name;
    }
}
public class ObjectTest {
    public static void main(String[] args) {
        A a = new A(1,"a");
        //当我们输出对象时 实际上输出的是该对象调用toString方法后的返回值
        System.out.println(a); //输出的是对象的地址值
        System.out.println(a.toString());//输出的是对象的地址值


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

        //核心类库的类
        //重写toString方法 - 输出的是内容(属性的值)
        String s = new String("aaa");
        System.out.println(s);
        System.out.println(s.toString());

        //重写toString方法- 输出的是内容(属性的值)
        Date date = new Date();
        System.out.println(date);
        System.out.println(date.toString());

        //重写toString方法- 输出的是内容(属性的值)
        Integer intVar = new Integer(1);
        System.out.println(intVar);
        System.out.println(intVar.toString());
    }
}
equals
  /*
        equals方法

        1.Object中的equals方法 - 用来比较地址值
             public boolean equals(Object obj) {
                return (this == obj);
            }

       2.像核心类库中的许多类都重写了equals方法(比如:String)
            重写后用来比较内容

       3.自定义的类最好也重写equals方法用来比较内容。我们调用对象的equals目的一般都是比内容。
            比地址值用 == 就可以。


       [面试题] : equals和==的区别?
           == :
                如果运算符两边是基本数据类型比较的是具体的值
                如果运算符两边是引用数据类型比较的是地址值。
           equals :
                如果类中不重写equals方法那么调用的是Object中的equals方法那么比较的是地址值。
                如果类中重写了equals方法其本上都是比较内容(看重写方法中做了什么)。
     */
clone
  /*
        知道即可(根本不用)
        1.使用Object中的克隆方法是用来创建一个新的对象只不过内容一样。
        2.克隆的对象所在的类必须实现Cloneable接口。
        3.克隆的对象所在的类必须重写clone方法。在重写的方法中调用父类的clone方法即可
     */
    @Test
    public void test() throws CloneNotSupportedException {

        Person p = new Person("aaa");

        //克隆 - 创建一个新的对象--内容一样
        //理解成 : Person p2 = new Person("aaa");
        //克隆一个对象
        Person p2 = (Person) p.clone();
        System.out.println(p2.name);

    }

class Person implements Cloneable{
    String name;

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

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();//调用父类的克隆方法
    }
}

final,finally,finalize
[面试题] finalfinally、finalize的区别
final是一个修饰符可以修饰类,方法,变量。
finallytry-catch-finally中的一个关键字。finally中的代码一定会执行。
finalize :finalize是一个方法名,它是Object类中声明的一个方法,
        它是由GC垃圾回收器在回收垃圾对象之前用于释放该对象在JVM以外占用的内存和资源。
        (	
    		比如调用了native方法 native方法调用的是c程序。
    		c程序用的不是JVM的内存GC无法对该内存区域进行管理
        	再通过代码去清理JVM以外的内存(不清理就会导致内存泄露-该内存一直在被占用)
        )

10.3 包装类

什么是包装类
包装类 : Java给每种基本数据类型都创建了一个对应的类 该类叫作包装类。
包装类的作用: 包装类可以弥补基本数据类型在面向对象的环境中的局限性 
		增加了便利性(类中有属性方法)。
包装类有哪些

在这里插入图片描述

自动拆箱和装箱
 /*
        自动拆箱 :将包装类的类型转成基本数据类型。
        自动装箱 :将基本数据类型转成包装类的类型
     */
    @Test
    public void test2(){
        //创建Integer类的对象
        Integer var = new Integer(1);

        int a = var;//自动拆箱 -- 只有包装类可以这么做

        Integer var2 = 220;//自动装箱 -- 只有包装类可以这么做
    }
使用包装类时注意的点
  @Test
    public void test3(){
        set(1);//自动装箱了 - 先把1放到Integer中再将Integer传给Object
    }

    public void set(Object o){
        if (o instanceof Integer){
            System.out.println("我是Integer");
        }
    }   

	/*
        ==和 !=在使用时一定要注意两边的类型
        注意:两边的类型要一致或存在子父类关系
     */
    @Test
    public void test4(){
        Integer a = 10;//自动装箱
        Double b = 20.0;//自动装箱 - 注意下字面量的类型

        //System.out.println(a == b); //这样写是错的 == 两边的类型要一致。就算不一致也得存在子父类关系

        N1 n1 = new N1();
        N2 n2 = new N2();
        System.out.println(n1 == n2); // ==两边的类型存在子父类关系
    }

    /*
        > < >= <= : 左右两边是不同的包装类型是可以的(先拆箱 再自动类型提升)
     */
    @Test
    public void test5(){
        Integer a = 10;//自动装箱
        Double b = 20.0;//自动装箱
        System.out.println(a <= b);//自动拆箱了  int > double 可以自动类型提升

        Boolean c = true;
        //System.out.println(a > c);//是错的 - 基本数据类型的自动提升不包括boolean
    }

    @Test
    public void test6(){
        /*
            数值范围在-128 到 127 之间都是从数组中直接获取提前已经创建好的对象。
            如果数值相同取的是同一个对象。

            如果不在同一个范围那么就会new一个新的Integer对象。
         */
        Integer n1 = 50; //从数组中获取对象
        Integer n2 = 50; //从数组中获取对象
        System.out.println(n1 == n2);//true

        Integer n3 = 150; //创建新对象
        Integer n4 = 150; //创建新对象
        System.out.println(n3 == n4);//false
    }

在这里插入图片描述

包装类,基本数据类型,String相互转换
/*
        包装类 -> 基本数据类型 :自动拆箱
     */
    @Test
    public void test(){
       //方式一 :自动拆箱
       int a = new Integer(2);

       //方式二(知道就可以):
        Integer var = new Integer(2);
        int i = var.intValue();
        System.out.println(i);
    }

    /*
        基本数据类型 -> 包装类 :自动装箱
     */
    @Test
    public void test2(){
        //方式一 :自动装箱
        Integer a = 10;

        //方式二(知道即可): 通过构造器
        int b = 10;
        Integer var = new Integer(b);
    }

    /*
        基本数据,包装类 -> String
        包装类 -> String   : 调用包装类的toString方法
        基本数据 -> String : ①字符串拼接 ②String.valueOf()
     */
    @Test
    public void test3(){
        //基本数据类型 -> String
        //方式一 : 字符串拼接
        int a = 10;
        String s = a + "";
        //方式二 : String.valueOf()
        String s2 = String.valueOf(a);

        //包装类 -> String : 调用包装类的toString方法
        Integer i = new Integer(10);
        String s3 = i.toString();
    }

    /*
        String -> 基本数据类型,包装类
        String -> 基本数据类型 : 对应的包装类.parseXxxx
        String -> 包装类 :通过包装类的构造器
     */
    @Test
    public void test4(){
        //String -> 基本数据类型 : 对应的包装类.parseXxxx
        int i = Integer.parseInt("1");
        boolean b = Boolean.parseBoolean("true");
        //NumberFormatException - 下面的写法是错误的
        //double d = Double.parseDouble("xiaolongge");
        System.out.println(i + " " + b);


        //String ->包装类 : 通过包装类的构造器
        Integer a = new Integer("1");
        Boolean bo = new Boolean("true");
        System.out.println(a + " " + bo);
    }

10.4 常用类

Math
* public static double abs(double a)  :返回 double 值的绝对值。 
* public static double ceil(double a):返回大于等于参数的最小的整数。
* public static double floor(double a)  :返回小于等于参数最大的整数。
* public static long round(double a) :返回最接近参数的 long。(相当于四舍五入方法)  
* public static double pow(double a,double b):返回a的b幂次方法
* public static double sqrt(double a):返回a的平方根
* public static double random():返回[0,1)的随机值
* public static final double PI:返回圆周率
* public static double max(double x, double y):返回x,y中的最大值
* public static double min(double x, double y):返回x,y中的最小值
BigInteger
   /*
        BigInteger : 大整型

        用来解决整型数值范围不够的问题。
        * BigInteger(String val) : 构造器
        * BigInteger add(BigInteger val)  :求和
        * BigInteger subtract(BigInteger val) :求差
        * BigInteger multiply(BigInteger val) :求乘积
        * BigInteger divide(BigInteger val) :求除
        * BigInteger remainder(BigInteger val) :取模(求余数)
     */
    @Test
    public void test(){
        int a = Integer.MAX_VALUE;//取int的最大值
        int b = 1;
        System.out.println(a + b);

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

        BigInteger a2 = new BigInteger(String.valueOf(a));
        BigInteger b2 = new BigInteger(String.valueOf(b));
        BigInteger sum = a2.add(b2);

        System.out.println("a2 :" + a2);
        System.out.println("b2 :" + b2);
        System.out.println("sum:" + sum);
    }
BigDecimal
    /*
        BigDecimal : 用来解决浮点型计算不精确的问题
        * BigDecimal(String val) 
        * BigDecimal add(BigDecimal val) 
        * BigDecimal subtract(BigDecimal val)
        * BigDecimal multiply(BigDecimal val) 
        * BigDecimal divide(BigDecimal val) 
        * BigDecimal divide(BigDecimal divisor, int roundingMode) 
        * BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode) 
        * BigDecimal remainder(BigDecimal val) 
     */
    @Test
    public void test3(){
        double a = 6.6 - 6.3;
        System.out.println(a);

        BigDecimal b = BigDecimal.valueOf(6.6);
        BigDecimal b2 = BigDecimal.valueOf(6.3);
        BigDecimal b3 = b.subtract(b2);

        System.out.println(b3);
    }

  @Test
    public void test4(){
        double a = 6.6 - 6.3;
        System.out.println(a);

        BigDecimal b = BigDecimal.valueOf(10);
        BigDecimal b2 = BigDecimal.valueOf(3);
        //RoundingMode.HALF_UP : 四舍五入
        // 2 : 保留两位小数
        BigDecimal b3 = b.divide(b2,2, RoundingMode.HALF_UP);

        System.out.println(b3);
    }
Random
    @Test
    public void test(){

        Random r = new Random();

        for (int i = 0; i < 50; i++) {
            int v = r.nextInt(100);//获取随机数的范围是0~99 (不包含边界)
            System.out.println(v);
        }
    }

    @Test
    public void test2(){
        Random r = new Random(1)//设置种子值 -- 再计算随机数时以该种子为最基础的值
        System.out.println(r.nextInt());
        System.out.println(r.nextInt());

        Random r2 = new Random(1);//设置种子值 -- 再计算随机数时以该种子为最基础的值
        System.out.println(r2.nextInt());
        System.out.println(r2.nextInt());
    }
System
/*
     * static long currentTimeMillis() :返回当前系统时间距离1970-1-1 0:0:0的毫秒值
     * static void exit(int status) :退出当前系统
     * static void gc() :运行垃圾回收器。
     * static String getProperty(String key):获取某个系统属性,例如:java.version、					user.language、user.country、
     *              file.encoding、user.name、os.version、os.name等等
     */
Runtime
 /*
    public static Runtime getRuntime(): 返回与当前 Java 应用程序相关的运行时对象。
	public long totalMemory():返回 Java 虚拟机中的内存总量。
		此方法返回的值可能随时间的推移而变化,这取决于主机环境。
	public long freeMemory():回 Java 虚拟机中的空闲内存量。
		调用 gc 方法可能导致 freeMemory 返回值的增加。
*/
Date
/*
    包是: java.util.Date
*/
Date date = new Date();//获取当前的日期时间
System.out.println(date);

//获取当前时间所对应的毫秒数
long time = date.getTime();//毫秒数 - 1712888846161   Fri Apr 12 10:27:26 CST 2024
System.out.println(time);


Date date2 = new Date(1712888846161L);//获取的是毫秒数所对应的日期时间
System.out.println(date2);
SimpleDateFormat
	/*
        SimpleDateFormat : 对日期时间进行格式化(格式化成我们想要的日期时间格式)
     */
    @Test
    public void test2() throws ParseException {
        //构造器传日期时间格式
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        //将日期时间转成字符串(按照指定格式)
        String format = sdf.format(new Date());
        System.out.println(format);

        //2024-04-12 10:34:06
        //将字符串转成Date类型
        //注意:字符串的日期时间格式一定要和格造器中指定的格式匹配
        Date date = sdf.parse("2024-04-12 10:34:06");
        System.out.println(date);
    }
TimeZone
		//获取默认的时区
        TimeZone aDefault = TimeZone.getDefault();
        System.out.println(aDefault);

        //获取指定的时区
        TimeZone timeZone = TimeZone.getTimeZone("America/Los_Angeles");
        System.out.println(timeZone);

        //获取所有时区的名字
        String[] availableIDs = TimeZone.getAvailableIDs();
        for (int i = 0; i < availableIDs.length; i++) {
            System.out.println(availableIDs[i]);
        }
Locale
 /*
    Locale用来获取语言 国家 地区代码:
    语言参数是一个有效的 **ISO 语言代码**。这些代码是由 ISO-639 定义的小写两字母代码。
    国家/地区参数是一个有效的 **ISO 国家/地区代码**。这些代码是由 ISO-3166 定义的大写两字母代码。
     */
    @Test
    public void test(){

        //获取所有的ISO代码信息
        Locale[] availableLocales = Locale.getAvailableLocales();
        for (int i = 0; i < availableLocales.length; i++) {
            //System.out.println(availableLocales[i]);
        }

        //获取指定国家的ISO代码 - zh_CN
        Locale china = Locale.CHINA;
        System.out.println(china);
    }
Calendar
 	   //获取Calendar类的对象
        Calendar c = Calendar.getInstance();
        //获取对象运行时类的名字
        System.out.println(c.getClass().getName());

        //在当前的日期上加3天 --- 修改日历
        c.add(Calendar.DAY_OF_MONTH,3);

        //获取当天是当月的第几天 --- 修改后的日历
        int day = c.get(Calendar.DAY_OF_MONTH);
        System.out.println(day);

        //当天是当周的第几天--周几 --- 修改后的日历
        System.out.println(c.get(Calendar.DAY_OF_WEEK));
LocalDate,LocalTime,LocalDateTime
/*
    LocalDate : 日期
    LocalTime : 时间
    LocalDateTime :日期时间
 */
public class LocalDateTimeTest {
    @Test
    public void test(){

        LocalDate dateNow = LocalDate.now(); //当前日期
        LocalTime timeNow = LocalTime.now(); //当前时间
        LocalDateTime dateTimeNow = LocalDateTime.now();//当前日期时间

        System.out.println(dateNow);
        System.out.println(timeNow);
        System.out.println(dateTimeNow);


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

        //创建指定的日期对象
        LocalDate dateOf = LocalDate.of(2022, 10, 10);
        System.out.println(dateOf);

        //创建指定的时间对象
        LocalTime timeOf = LocalTime.of(12, 10, 10, 200);
        System.out.println(timeOf);

    }

    @Test
    public void test2(){

        LocalDateTime now = LocalDateTime.now();

        //当月的第几天
        int dayOfMonth = now.getDayOfMonth();
        //获取当前月份
        Month month = now.getMonth();

        System.out.println(dayOfMonth);
        System.out.println(month);

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

        //在当前日期加1天 --- 不会修改原日期时间(获取一个新的日期时间)
        LocalDateTime localDateTime = now.plusDays(1);
        System.out.println(localDateTime);

        //在当前日期加2天 --- 不会修改原日期时间(获取一个新的日期时间)
        LocalDateTime localDateTime1 = now.plusDays(2);
        System.out.println(localDateTime1);

    }

    @Test
    public void test3(){
        /*
        | isBefore()/isAfter() | 比较两个 LocalDate |
        isLeapYear()       | 判断是否是闰年(在LocalDate类中声明) |
         */
        LocalDate now = LocalDate.now();
        System.out.println(now.getYear() + " : " + (now.isLeapYear()? "是闰年" : "是平年"));

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

        LocalDate of1 = LocalDate.of(2022, 10, 3);
        LocalDate of2 = LocalDate.of(2021, 8, 9);

        System.out.println(of1.isAfter(of2));

    }
}
Instant
//获取UTC(全世界统一时间 不是各地区的时间)时间 -日期和时间
Instant now = Instant.now();
System.out.println(now);
ZoneID
//获取所有地方时区的名称
Set availableZoneIds = ZoneId.getAvailableZoneIds();

//增强for循环 --- 后面讲
//        for (Object availableZoneId : availableZoneIds) {
//            System.out.println(availableZoneId);
//        }

System.out.println(availableZoneIds);
ZoneDateTime
 /*
2024-04-12T11:41:08.207+08:00[Asia/Shanghai]
获取当前时区的日期和时间
*/
ZonedDateTime now = ZonedDateTime.now();
System.out.println(now);


/*
获取指定时区的日期和时间
*/
ZonedDateTime now1 = ZonedDateTime.now(ZoneId.of("America/New_York"));
System.out.println(now1);
Period和Duration
/*
        Period:用于计算两个“日期”间隔
*/
    @Test
    public void test(){

        LocalDate of1 = LocalDate.of(2020, 10, 15);
        LocalDate of2 = LocalDate.of(2020, 10, 20);
        //计算两个日期的差值
        Period p = Period.between(of1, of2);
        System.out.println(p.getDays());//获取相差的总天数
    }


/*
      Duration:用于计算两个“时间”间隔
*/
    @Test
    public void test2(){
        LocalTime of1 = LocalTime.of(10, 20, 16);
        LocalTime of2 = LocalTime.of(10, 20, 50);
        //计算两个时间的差值
        Duration between = Duration.between(of1, of2);
        System.out.println(between.getSeconds());//获取相差的总秒数

    }
DateTimeFormatter
	   //获取当前的日期时间
        LocalDateTime now = LocalDateTime.now();

        //第一种转换方式
        DateTimeFormatter dtf = DateTimeFormatter.ISO_DATE;
        String s = dtf.format(now);//对日期时间进行格式化
        System.out.println(s);


        System.out.println("==================================");
        //第二种转换方式
        DateTimeFormatter dtf2 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);
        String s2 = dtf2.format(now);//对日期时间进行格式化
        System.out.println(s2);


        System.out.println("==================================");
        //第三种转换方式 :自定义
        DateTimeFormatter dtf3 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        //将日期时间转成字符串
        String s3 = dtf3.format(now);
        //将字符串转成日期时间
        TemporalAccessor parse = dtf3.parse("2024-04-12 14:58:58");
        System.out.println(s3);

10.5 数组

Arrays工具类
/*
    数组的工具类 : Arrays
 */
public class ArraysTest {

    /*
        toString :将数组中的元素拼接成一个字符串
     */
    @Test
    public void test(){
        int[] ns = {1,2,3,4,5};
        //将数组中的元素拼接成一个字符串
        String s = Arrays.toString(ns);
        System.out.println(s);
    }
    /*
        sort : 对数组中的元素排序
     */
    @Test
    public void test2(){
        int[] ns = {5,2,1,3,9};
        // 对数组中的元素排序 : 因为是引用数类型所以不需要返回值
        Arrays.sort(ns);

        System.out.println(Arrays.toString(ns));
    }
    @Test
    public void test22(){
        int[] ns = {5,2,1,3,9,4,8,6};
        //static void sort(int[] a, int fromIndex, int toIndex) :将a数组的[fromIndex, toIndex)部分按照升序排列
        //注意:不包括 toIndex
        Arrays.sort(ns,0,3);
        System.out.println(Arrays.toString(ns));
    }

    /*
    数组元素的二分法查找
        static int binarySearch(int[] a, int key)
    注意:前提是必须有序
     */
    @Test
    public void test3(){
        int[] ns = {5,2,1,3,9,4,8,6,7};
        //排序
        Arrays.sort(ns);
        System.out.println(Arrays.toString(ns));
        //查找 - 查找的结果是排完序后的位置。如果找不到返回负数
        System.out.println(Arrays.binarySearch(ns,4));
    }

    /*
     * static int[] copyOf(int[] original, int newLength)  :根据original原数组复制一个长度为newLength的新数组,并返回新数组
     *
     * //下面的是一个泛型方法 --  后面讲(在这理解成T是任意类型--但不是基本数据类型)
     * static <T> T[] copyOf(T[] original,int newLength):根据original原数组复制一个长度为newLength的新数组,并返回新数组
     *
     */
    @Test
    public void test4(){
        int[] ns = {1,2,3};
        //扩容 - ①创建新数组 ②将原数组中的内容copy到新数组中
        int[] newArray = Arrays.copyOf(ns, 6);

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

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

        String[] d = {"aa","bbb"};
        Arrays.copyOf(d,20);
    }
    /*
     * static int[] copyOfRange(int[] original, int from, int to) :复制original原数组的[from,to)构成新数组,并返回新数组
     *      注意:不包括to的位置
     * static <T> T[] copyOfRange(T[] original,int from,int to):复制original原数组的[from,to)构成新数组,并返回新数组
     */
    @Test
    public void test5(){
        int[] ns = {5,2,1,3,9,4,8,6,7};
        int[] newNs = Arrays.copyOfRange(ns, 0, 3);
        System.out.println(Arrays.toString(newNs));
    }

    /*
     * static boolean equals(int[] a, int[] a2) :比较两个数组的长度、元素是否完全相同
     *      比较的是内容(内容个数,内容本身)
     * static boolean equals(Object[] a,Object[] a2):比较两个数组的长度、元素是否完全相同
     */
    @Test
    public void test6(){
        int[] ns = {5,2,1,3,9,4,8,6};
        int[] ns2 = {5,2,1,3,9,4,8,5};

        System.out.println(Arrays.equals(ns,ns2));
    }
    /*
     * static void fill(int[] a, int val) :用val值填充整个a数组
     * static void fill(Object[] a,Object val):用val对象填充整个a数组
     * static void fill(int[] a, int fromIndex, int toIndex, int val):将a数组[fromIndex,toIndex)部分填充为val值
     * static void fill(Object[] a, int fromIndex, int toIndex, Object val) :将a数组[fromIndex,toIndex)部分填充为val对象
     */
    @Test
    public void test7(){
        int[] ns = {5,2,1,3,9,4,8,6};
        Arrays.fill(ns,100);

        System.out.println(Arrays.toString(ns));
    }
}
System
/*
 static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length):
    src: 源数组 (从源数组copy数组)
    srcPos: 源数组copy的位置
    dest: 目标数组(将源数组中的内容copy到目标数组中)
    destPos: 目标数组copy到的位置
    length: copy的内容的长度
从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束。常用于数组的插入和删除
 */
public class SystemArrayTest {
    @Test
    public void test(){
         int[] ns = {1,2,3,4,5,6};
         int[] newNs = new int[10];
        System.arraycopy(ns,2,newNs,2,3);

        System.out.println(Arrays.toString(newNs));
    }
}

10.6 排序

自然排序
/*
    自然排序 : Comparable
    
    说明:
    	1.要排序的对象所属的类要实现Comparable接口
    	2.重写compareTo方法
    	3.在compareTo方法中实现要排序的方式(比如:按价格排序  按年纪排序)

     好处 :排序的方法不用修改。
            想要对某个对象进行排序只需调用那个对象的compareTo方法排序即可
 */
public class ComparableTest {
    public static void main(String[] args) {

        Book[] books = new Book[4];
        books[0] = new Book("《如何让富婆爱上我》",100,20);
        books[1] = new Book("《我的霸道总裁》",80,13);
        books[2] = new Book("《消失的新娘》",90,23);
        books[3] = new Book("《我的100个女朋友》",135,21);


        compare(books);


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

    }

    public static void compare(Book[] books){
        //校验 -- 类型
        if (!(books[0] instanceof Comparable)){
            //说明数组中的对象没有实现Comparable接口
            throw new ClassCastException("该对象没有实现Comparable接口");
        }

        //校验 -- 是否为null
        if (books == null){
            throw new NullPointerException("数组不能为null");
        }

        for (int i = 0; i < books.length - 1; i++) {
            for (int j = 0; j < books.length - i - 1; j++) {
                if (books[j].compareTo(books[j + 1]) > 0){
                    Book temp = books[j];
                    books[j] = books[j+1];
                    books[j+1] = temp;
                }
            }
        }
    }
}

class Book implements Comparable{
    String name;
    int price;

    //每本书都有人签名 - 可以签很多
    int number;


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

    @Override
    public String toString() {
        return name + " " + price + " " + number;
    }

    /*
        比较内容

        返回值:
            正数: 当前对象的价格 大于 传进来的对象的价格
            负数:当前对象的价格 小于 传进来的对象的价格
            0 :当前对象的价格 等于 传进来的对象的价格
     */
    @Override
    public int compareTo(Object o) {
        if (!(o instanceof Book) || o == null){
            throw new RuntimeException("类型不匹配");
        }

        //向下转型
        Book b = (Book) o;

        return this.price - b.price;
//        return this.number - b.number;
    }
}

/**
 * Demo练习
 * Author: Liang_Xinyu
 * Date: 24/04/17
 * Time: 20:36
 * &#064;Des
 * 1.定义一个接口MyComparable接口中提供一个方法
 * int compare(int a);//用来比较内容
 * 2.自定义一个类Student
 * 提供属性,名字,学号,年纪。要求有比较年纪的功能
 * 3.自定义一个类Employee
 * 提供属性:名字,工种,年纪。要求有比较年纪的功能
 * 4.创建一个测试类。在main方法中创建一个数组放入两个学生两个员工。
 * 要求对数组中的对象按照年纪排序(从小到大)。
 */
interface MyComparable {
    public int compare(int a);
}

class Student implements MyComparable{
    String name;
    int sid;
    int age;
    public Student(String name, int sid, int age) {
        this.name = name;
        this.sid = sid;
        this.age = age;
    }

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

    @Override
    public int compare(int a) {
        return this.age - a;
    }
}

class Employee implements MyComparable{
    String name;
    String job;
    int age;

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

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

    @Override
    public int compare(int a) {
        return this.age - a;
    }
}

public class ComparableDemo {
    public static void main(String[] args) {
        MyComparable[] mc = new MyComparable[3];
        mc[0] = new Student("aaa",123,15);
        mc[1] = new Student("bbb",456,60);
        mc[2] = new Employee("ccc","996",45);
        for (int i = 0; i < mc.length - 1; i++) {
            for (int j = 0; j < mc.length - i - 1; j++) {
                if (mc[j + 1] instanceof Student){
                    Student s = (Student) mc[j + 1];
                    if(mc[j].compare(s.age) > 0){
                        MyComparable temp = mc[j];
                        mc[j] = mc[j + 1];
                        mc[j + 1] = temp;
                    }
                }else if (mc[j + 1] instanceof Employee){
                    Employee e = (Employee) mc[j + 1];
                    if(mc[j].compare(e.age) > 0){
                        MyComparable temp = mc[j];
                        mc[j] = mc[j + 1];
                        mc[j + 1] = temp;
                    }
                }
            }
        }
        for (int i = 0; i < mc.length; i++) {
            System.out.println(mc[i].toString());
        }
    }
}

定制排序
/*
    定制排序 : Comparator
    
    说明:
    	1.调用者(调用排序方法的人)要去实现Comparator接口
    		(	Comparator接口匿名内部类的对象,
    			局部内部类的对象-Comparator接口,
    			普通的类的对象-要实现Comparator接口)
    	2.重写compare方法
    	3.在compare方法中实现要排序的方式(比如:按价格排序  按年纪排序)

     好处 :排序的方法不用修改。
     	    排序的对象所属的类的代码也不用修改。
            想要对某个对象进行排序只需要创建一个Comparator接口实现类的对象即可。
 */

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

        Book[] books = new Book[4];
        books[0] = new Book("《如何让富婆爱上我》",100,20);
        books[1] = new Book("《我的霸道总裁》",80,13);
        books[2] = new Book("《消失的新娘》",90,23);
        books[3] = new Book("《我的100个女朋友》",135,21);


        /*
        compare(books, new Comparator() { //创建匿名内部类的对象
            @Override
            public int compare(Object o1, Object o2) {
                if (!(o1 instanceof Book) || !(o2 instanceof Book)){
                    throw new RuntimeException("类型不对");
                }
                //向下转型
                Book b1 = (Book) o1;
                Book b2 = (Book) o2;
                return b1.price - b2.price;//按照价格排序
            }
        });

         */

        compare(books, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                //向下转型
                Book b1 = (Book) o1;
                Book b2 = (Book) o2;
                return b1.number - b2.number;//按照签名数量排序
            }
        });

        System.out.println(Arrays.toString(books));
    }

    /*
         public interface Comparator<T> {
            int compare(T o1, T o2);
         }
     */
    //将Comparator接口实现类的对象赋值给Comparator接口  多态
    public static void compare(Book[] books, Comparator c){
        for (int i = 0; i < books.length - 1; i++) {
            for (int j = 0; j < books.length - i - 1; j++) {
                //compare方法调用的是接口的实现类中实现该接口的方法
                if (c.compare(books[j],books[j+1]) > 0){
                    Book temp = books[j];
                    books[j] = books[j+1];
                    books[j+1] = temp;
                }
            }
        }
    }
}

class Book{
    String name;
    int price;

    //每本书都有人签名 - 可以签很多
    int number;


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

    @Override
    public String toString() {
        return name + " " + price + " " + number;
    }

}

10.7 字符串-String

String概述
String :

说明:
   1.String被final所修饰那么String这个类不能被其它类继承
   2.String实现了Comparable接口 那么可以比较字符串内容。
   3.String的底层是一个char类型的数组(JDK9开始变成了byte[]--原因是为了节省内存)
       JDK9之前                      JDK9开始
       char[] 编码UTF-16             byte[](编码 latin1 - UTF-16)
       a -> char                    a - 1byte-> char- 2byte
       a中 -> 2char                a中 - 4byte
   4.String是一个不可变的字符序列
  	 ①String不能被子类继承--那么就没有了子类可以对父类的操作
   	 ②char[]是被final修饰 - char[]的对象(字符串)不能被修改
    	-- 只看到这还无法保证数组的内容被修改
  	 ③char[]是被private修饰 - 
  	 	类的名部不能操作该数组同时类的内部不提供对数组内容本身修改的方法

5.为什么String要设置成不可变呢? 因为不可变才能被共享,因为共享才能节省内存。
因为字符串在内存存储的区域是一个比较特殊的区域 - 字符串常量池(相同的内容只能有一份)
 
String对象的创建和内存图
String s3 = "abc";
String s4 = "abc";

String s = new String("abc");
String s2 = new String("abc");

System.out.println(s == s2);//false
System.out.println(s3 == s4);//true

在这里插入图片描述

String案例

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

String构造器
 /*
        构造器

* `public String() ` :初始化新创建的 String对象,以使其表示空字符序列。
* ` String(String original)`: 初始化一个新创建的 `String` 对象,使其表示一个与参数相同的字符序列;
            换句话说,新创建的字符串是该参数字符串的副本。
* `public String(char[] value)  :通过当前参数中的字符数组来构造新的String。
* `public String(char[] value,int offset, int count) :通过字符数组的一部分来构造新的String。
* `public String(byte[] bytes)  :通过使用平台的默认字符集解码当前参数中的字节数组来构造新的String。
* `public String(byte[] bytes,String charsetName)  :通过使用指定的字符集解码当前参数中的字节数组来构造新的String。
     */
    @Test
    public void test() throws UnsupportedEncodingException {
        String s = new String(); //相当于 ""
        System.out.println("a" + s + "b");

        String s2 = new String("longge");

        System.out.println("======================================");
        char[] c = {'a','b','c','d','e'};
        String s3 = new String(c);
        System.out.println(s3);

        //public String(char[] value,int offset, int count) :
        //通过字符数组的一部分来构造新的String。
        String s4 = new String(c, 2, 2);// 第一个2是索引位置  第二个2是长度
        System.out.println(s4);

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

        /*
        public String(byte[] bytes)  :
        	通过使用平台的默认字符集解码当前参数中的字节数组来构造新的String。

         */
        byte[] b = {97,98,99}; //整数
        String s5 = new String(b);
        System.out.println(s5);

        System.out.println("======================================");
        
    }
字符串拼接
方式一 : 字符串连接符 
    	"a" + "b";//在编译时就是"ab"
    
    	String s1 = new String("a");
		String s2 = new String("b");
		String s3 = s1 + s2; //底层是new
方式二 : concat方法
    	"a".concat("b");//底层就是new
==============================================================================
	   String s = "hello";
        String s2 = "hel" + "lo"; //编译的时候就是 "hello"
        String s3 = "hel";
        String s4 = "lo";

        /*
            总结:只要有变量参与字符串拼接就会用StringBuilder中的
            	toString方法在该方法中new String 并返回
         */
//有变量参与字符串拼接 -- 底层调用StringBuilder中的toString方法在该方法中new String 并返回
        String s5 = "hel" + s4;
//有变量参与字符串拼接 -- 底层调用StringBuilder中的toString方法在该方法中new String 并返回
        String s6 = s3 + "lo";
 //有变量参与字符串拼接-- 底层调用StringBuilder中的toString方法在该方法中new String 并返回
        String s7 = s3 +  s4;

        /*
            intern() : 调用该方法时会直接去字符串常量池中寻找有没有该内容。如果有直接返回。
                如果没有往字符串常量池中放一份并返回。

            注意:返回值是从字符串常量池中寻找后返回的结果。
         */
        String s8 = (s3 + s4).intern(); //优化 - 节省了内存(从字符串常量池中获取的)

        System.out.println(s == s2); //true
        System.out.println(s == s5); //false
        System.out.println(s2 == s6); //false
        System.out.println(s == s7); //false
        System.out.println(s5 == s6);//false
        System.out.println(s2 == s7);//false


        System.out.println(s == s8);//true
StringAPI
/*
boolean equals(Object obj)方法:比较是两个字符串对象的内容,因为String类型重写equals,equals方法比较字符串内容时严格区分大小写。

boolean equalsIgnoreCase(String str)方法:比较是两个字符串对象的内容,并且不区分大小写。

int compareTo(String str)方法:String类型实现了java.lang.Comparable接口,重写了Comparable接口的抽象方法,即String对象支持自然排序,该方法按照字符的Unicode编码值进行比较大小的,严格区分大小写。

int compareToIgnoreCase(String str):String类型支持不区分大小写比较字符串大小。具体原理是先统一大小写再比较大小。

int length():返回字符串的长度

String toLowerCase():将字符串中大写字母转为小写

String toUpperCase():将字符串中小写字母转为大写

boolean startsWith(xx):是否以xx开头

boolean endsWith(xx):是否以xx结尾

String trim():去掉字符串前后空白符

boolean contains(xx):是否包含xx

int indexOf(xx):从前往后找当前字符串中xx,即如果有返回第一次出现的下标,要是没有返回-1

int lastIndexOf(xx):从后往前找当前字符串中xx,即如果有返回最后一次出现的下标,要是没有返回-1

String substring(int beginIndex) :返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串。 

String substring(int beginIndex, int endIndex) :返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。 

char charAt(index):返回[index]位置的字符

char[] toCharArray(): 将此字符串转换为一个新的字符数组返回

将字符数组转为String对象,可以使用之前介绍过的构造器和静态方法valueOf或copyValueOf等

String(char[] value):返回指定数组中表示该字符序列的 String。 

String(char[] value, int offset, int count):返回指定数组中表示该字符序列的 String。

static String copyValueOf(char[] data): 返回指定数组中表示该字符序列的 String

static String copyValueOf(char[] data, int offset, int count):返回指定数组中表示该字符序列的 String

static String valueOf(char[] data, int offset, int count) : 返回指定数组中表示该字符序列的 String

static String valueOf(char[] data)  :返回指定数组中表示该字符序列的 String

byte[] getBytes():编码,把字符串变为字节数组,按照平台默认的字符编码方式进行编码

​	        byte[] getBytes(字符编码方式):按照指定的编码方式进行编码

new String(byte[] ) 或 new String(byte[], int, int):解码,按照平台默认的字符编码进行解码

​           new String(byte[],字符编码方式 ) 或 new String(byte[], int, int,字符编码方式):解码,按照指定的编码方式进行解码
*/

        byte[] b2 = {97,98,99}; //整数
        String s6 = new String(b,"utf-8");
        System.out.println(s6);

        //将字符串转成byte[]
        byte[] bytes = "小鬼子".getBytes("gbk"); //[-48, -95, -71, -19, -41, -45]
        System.out.println(Arrays.toString(bytes));
        bytes = "小鬼子".getBytes("utf-8");
        //[-27, -80, -113, -23, -84, -68, -27, -83, -112]
        System.out.println(Arrays.toString(bytes)); 
		//因为bytes中放的是UTF-8的编码集的内容  然后非得转成gbk编码 肯定乱码
        String ss2 = new String(bytes, "gbk");
        System.out.println(ss2);
StringAPI
/*
        正则表达式: 正则表达式是使用单个字符串用来描述和匹配一系列符合某个语法规则的字符串
     */
    @Test
    public void test(){
        // \d表示一个数字
        // + 表示前面的字符出现一次或多次
        System.out.println("123".matches("\\d+"));
        System.out.println("123".matches("\\d+"));

        //^ : 以什么开头
        //\D	匹配一个非数字字符。等价于[^0-9]。
        System.out.println("longgeheihei".matches("^long\\D+"));
    }

    /*
    (17)String replace(xx,xx):不支持正则

    (18)String replaceFirst(正则,value):替换第一个匹配部分

    (19)String replaceAll(正则, value):替换所有匹配部分
     */
    @Test
    public void test2(){
        System.out.println("abcdaaa".replace('a','A'));
        System.out.println("abcdaaa".replaceFirst("a","A"));
        System.out.println("1abcdaaa".replaceFirst("\\d","f"));
        System.out.println("abcdaaa".replaceAll("a","A"));
    }
    /*
    String[] split(正则):按照某种规则进行拆分
     */
    @Test
    public void test3(){
        String[] split = "a-b-c-d".split("-");
        System.out.println(Arrays.toString(split));
    }
面试题
/*
        [面试题] String s = "a" + "b" + "c" 创建了几个对象 ? 1个-字符串常量池

        [面试题] String s = new String("abc") 创建了几个对象 ?
                如果字符串常量池中没有abc 那么会创建2个对象 1个在字符串常量池 1个在堆中
                如果字符串常量池中已经存在abc 那么会创建1个对象在堆中

		[面试题]String s1 = new String("hello");
				String s2 = s1.intern();
*/

10.8 StringBuffer和StringBuilder

StringBuffer和StringBuilder的构造器
/*
        当我们使用StringBuilder的空参构造器创建对象时底层会创建一个长度为16的char类型数组。
        当我们向该数组中存放第17个元素时底层会进行扩容
        	(创建一个新的数组该数组的长度为原来的2倍+2
                并且将原数组中的内容copy到新数组中)
     */
StringBuffer和StringBuilderAPI
(1)StringBuffer append(xx):拼接,追加 (注意:可以将null值放入)
(2)StringBuffer insert(int index, xx):在[index]位置插入xx
(3)StringBuffer delete(int start, int end):删除[start,end)之间字符
(3)StringBuffer deleteCharAt(int index):删除[index]位置字符
(5)void setCharAt(int index, xx):替换[index]位置字符
(6)StringBuffer reverse():反转
(7)void setLength(int newLength) :设置当前字符序列长度为newLength
(8)StringBuffer replace(int start, int end, String str):
		替换[start,end)范围的字符序列为str
StringBuffer,String,StringBuilder区别
/*
    [面试题]StringBuffer和StringBuilder的区别?

    String:         不可变的字符序列   线程安全
    StringBuffer:   可变的字符序列     线程安全
    StringBuilder:  可变的字符序列     线程不安全
 */
StringBuffer,String,StringBuilder三者效率测试
StringBuilder > StringBuffer > String

结论:如果需要频繁进行字符串的拼接可以使用StringBuilder和StringBuffer

第十一章集合

11.1 集合概述

 集合:
        1.集合是Java提供的一种容器.集合是用来存储对象.
        2.集合和数组的区别
            ①数组的长度是不可变的,集合的长度可变.
            ②数组可以存放基本数据类型的数据和对象  集合只能存储对象.
        3.集合主要分Collection(单列集合)和Map(双列集合-键值对)
        4.Collection分成List和Set
            |------Collection
                |-------List : 存储的元素是有序的并且可以重复
                    |--------ArrayList
                    |--------LinkedList
                    |--------Vector
                |-------Set :  存储的元素是无序的并且不可以重复
                    |--------HashSet
                    |--------LinkedHashSet
                    |--------TreeSet

11.2 CollectionAPI

  CollectionAPI:
          注意:如果集合中存放的是自定义的类的对象那么该类必须重写equals方法 
              (重写hashCode方法-以后再说)
              (因为对集合中元素的操作底层都可能会用到equals方法).
           如果使用的是List那么重写equals方法 就可以
           如果使用的是Set那么必须重写equals和hashCode方法
 ===================================================================1add(E obj):添加元素对象到当前集合中
(2addAll(Collection<? extends E> other):添加other集合中的所有元素对象到当前集合中,
                即this = this ∪ other
(1boolean remove(Object obj) :
 		从当前集合中删除第一个找到的与obj对象equals返回true的元素。
(2boolean removeAll(Collection<?> coll):
 		从当前集合中删除所有与coll集合中相同的元素。即this = this - this ∩ coll
(3boolean retainAll(Collection<?> coll):
		从当前集合中删除两个集合中不同的元素,使得当前集合仅保留与c集合中的元素相同的元素,
		即当前集合中仅保留两个集合的交集,即this  = this ∩ coll;
(4void clear():清空集合
(5boolean removeIf(Predicate<? super E> filter) :
		删除满足给定条件的此集合的所有元素。removeIf方法是**Java8**引入的。 
        
(1boolean isEmpty():判断当前集合是否为空集合。
(2boolean contains(Object obj):判断当前集合中是否存在一个与obj对象equals返回true的元素。
(3boolean containsAll(Collection<?> c):
    	判断c集合中的元素是否在当前集合中都存在。即c集合是否是当前集合的“子集”。
(4int size():获取当前集合中实际存储的元素个数
(5Object[] toArray():返回包含当前集合中所有元素的数组        
 ==============================
 
 	c.removeIf(new Predicate() {
            @Override
            public boolean test(Object o) {
                //向下转型
                String s = (String) o;
                //需求:只要集合中的元素的长度大于等于2就删除
                return s.length() >= 2; //如果返回值是true就删除当前元素
            }
        });

11.3 迭代器

		迭代器 : 用来遍历集合中的元素

 		//获取迭代器对象
        Iterator iterator = c.iterator();

        /*
            hasNext() : 是否有下一个元素。如果有返回true没有返回false
            next() : ①获取指针指向的元素  ②指针下移
         */
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
/*
    Iterator迭代器的快速失败(fail-fast)机制 :
        该机制是用来确保在多线程的操作下。
        当一个线程正在遍历集合时其它线程不能对该集合操作 如果操作了
        那么遍历集合的地方就会抛异常。

    结论 :在迭代器对集合进行遍历时不能使用集合的方法操作集合中的对象(增,删,改)
            如果非要在遍历时删除集合中的数据那么得使用迭代器的remove方法进行删除
 */
 	  Collection c = new ArrayList();
        /*
            只要对集进行增,删,改 那么modCount就会+1
         */
        c.add("a");
        c.add("b");
        c.add("c");
        c.add("d");

        //会创建Iterator对象---在Iterator对象中有属性int expectedModCount = modCount;
        //expectedModCount =  4
        Iterator iterator = c.iterator();
        //在遍历集合时 不能对集合中的元素做操作 否则就抛异常
        while (iterator.hasNext()){
            //获取元素
            String str = (String) iterator.next();
            /*
             在遍历集合时 不能对集合中的元素做操作 否则就抛异常
            if ("a".equals(str)){
                //从集合中删除该元素 --- 会导致modCount++
                c.remove(str);//调用的是集合的remove方法
            }

             */

            //如果非要删除数据就使用迭代器的remove
            if ("a".equals(str)) {
                iterator.remove();
            }
        }

        System.out.println(c);

11.4 增强for循环

/*
    增强for循环

    作用: 用来遍历数组和集合

    格式:
        for(数据类型 变量名: 数组名/集合名){
            循环体;
        }
 */
	/*
        遍历数组
     */
    @Test
    public void test(){
        String[] names = {"哥哥","姐姐","妹妹","弟弟"};

        //快捷方式 : names.for

        //底层就是普通for循环 for(int i = 0; i < names.length; i++)
        for(String name : names){
            System.out.println(name);
        }
    }

    /*
        遍历集合
     */
    @Test
    public void test2(){
        Collection c = new ArrayList();
        c.add("a");
        c.add("b");
        c.add("c");
        c.add("d");

        //底层是迭代器
        for(Object o : c){
            System.out.println(o);
        }
    }

11.5 List

List概述
1.List是单列集合
2.List继承了Collection接口
3.List中存放的元素是有序可重复的
4.List常用的实现类有 : ArrayList LinkedList Vector
ListAPI
 /*
 void add(int index, E ele):把元素添加到指定位置
 boolean addAll(int index, Collection<? extends E> eles):把一组元素添加到指定位置
 2、删除元素
 - E remove(int index):删除指定位置的元素
 3、修改元素
 - E set(int index, E ele):替换[index]位置的元素
 - default void replaceAll(UnaryOperator<E> operator):按指定操作的要求替换元素
 E get(int index):返回[index]位置的元素
 List subList(int fromIndex, int toIndex):返回[fromIndex, toIndex)范围的元素
 int indexOf(Object obj):查询obj在列表中的位置,如果有重复,返回第1个
 int lastIndexOf(Object obj):查询obj在列表中的位置,如果有重复,返回最后1个
*/

11.6 List的实现类

ArrayList
/*
    List的实现类: ArrayList

    说明:
        1.ArrayList的底层是一个Object[]

    [面试题]ArrayList的底实现?
        当我们通过ArrayList的空参构造器创建对象时底层会创建一个长度为0的Object[]。
        当我们第一次向该对象中添加元素时底层会扩容 扩容的长度为10(原来的长度是0).
        当我们向该对象中添加第11个元素时底层会再次扩容扩容为原来的1.5倍。
        并且将原来的数组中的元素copy到当前数组中。
        
    构造器
    	ArrayList list = new ArrayList(100);//可以指定初始化时的数组的长度。
    	ArrayList list = new ArrayList();//底层是一个长度为的数组
 */
LinkedList

在这里插入图片描述

在这里插入图片描述

/*
 说明:
    1.LinkedList底层是一个双向链表


    ArrayList和LinkedList如何选择?
        如果对集合中的数据只是查询或修改比较多那么可以选择ArrayList
        如果对集合中的数据增,删的操作比较多那么可以使用LinkedList

    [面试题] ArrayList和LinkedList的区别?
        ArrayList底层是数组,LinkedList底层是双向链表
        ArrayList和LinkedList都不是线程安全的

*/
 /*
        LinkedList的API
     */
    @Test
    public void test2(){
        LinkedList list = new LinkedList();
        list.add("a");
        list.add("b");
        list.add("c");

        //在链表的第一个位置添加元素
        list.addFirst("e");
        System.out.println(list);

        //在链表的最后一个位置添加元素
        list.addLast("f");
        System.out.println(list);

        //删除链表中的最后一个元素
        list.removeLast();
        System.out.println(list);

        //删除链表中的第一个元素
        list.removeFirst();
        System.out.println(list);
    }
Vector
/*
    Vector:
        1.Vector是List接口的实现类
        2.Vector的底层是数组(Object[])

    [面试题] ArrayList和Vector的区别?
        1.ArrayList和Vector底层都是Object[]
        2.ArrayList是线程不安全的,Vector是线程安全的。
        3.ArrayList空参构造器创建的是一个长度为0的数组,
        	Vector空参构造器创建的是一个长度为10的数组
            ArrayList扩容为原来的1.5倍。Vector默认扩容为原来的2倍(也可以指定扩容大小)
 */
Stack
/*
    Stack存储的数据的特点: 先进后出

    说明 :Stack是Vector的子类
 */
public class StackTest {
    public static void main(String[] args) {

        Stack s = new Stack();
        //压栈
        s.push("a");
        s.push("b");
        s.push("c");

        //出栈
        // System.out.println(s.peek()); // peek() : 获取栈顶元素
        // System.out.println(s.peek());

        System.out.println(s.pop());//pop() : 将栈顶元素移出栈
        System.out.println(s.pop());
    }
}
Queue
 /*
        Queue
            |-------Queue接口
                |------Dqueue接口
                    |-----LinkedList
     */
    @Test
    public void test(){
        Queue queue = new LinkedList();
        //将数据放入到队列中
        queue.add("a");
        queue.add("b");
        queue.add("c");

        //从队列中取出数据
//        System.out.println(queue.peek());
        System.out.println(queue.poll());//从队列中取出数据
        System.out.println(queue.poll());

    }

Dqueue
   /*
        Dqueue : 双端队列
            可以向头部添加数据和移除头部数据
            也可以向尾部添加数据和移除尾部数据
     */
    @Test
    public void test2(){
        Deque d = new LinkedList();
        d.add("a");
        d.add("b");
        d.add("c");

        //查看队列中头部或尾部元素
//        System.out.println(d.getFirst());
//        System.out.println(d.getLast());

        //入队列
//        d.addFirst("a");
//        d.addLast("c");

        //从队列取出
        System.out.println(d.removeLast());
        System.out.println(d.removeFirst());

        System.out.println("=============================");
        System.out.println(d);
    }
ListIterator
/*
    List中的元素遍历方式:
        1.Iterator迭代器
        2.增强for循环
        3.ListIterator(可以指定从哪个位置开始遍历,可以倒序遍历)
 */
public class ListIteratorTest {
    @Test
    public void test(){
        List list = new ArrayList();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");

        //获取ListIterator对象
        ListIterator listIterator = list.listIterator(1);//从索引值为1的地方开始遍历
        //遍历
        while (listIterator.hasNext()){
            System.out.println(listIterator.next());
        }
    }

    @Test
    public void test2(){
        List list = new ArrayList();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");

        //获取ListIterator对象
        ListIterator listIterator = list.listIterator(list.size());
        //遍历
        while (listIterator.hasPrevious()){//是否有前一个 -- 对索引值做处理了
            System.out.println(listIterator.previous());//获取前一个元素 -- 对索引值做处理了
        }
    }
}
面试题
[面试题] ArrayList,LinkedList,Vector的区别?
1.ArrayList和Vector底层都是数组。LinkedList底层是双向链表
2.ArrayList和LinkedList是线程不安全的。Vector是线程安全的。

11.7 泛型

泛型概述
  /*
        没有使用泛型的场景 :
            1.数据类型不安全
            2.就算是同一类型仍然要向下转型 -- 因为都是Object类型

        使用了泛型后:
            1.集合使用了泛型后集合中的数据类型全部统一了(约束)
            2.使用了泛型后集合中的数据再进行处理就不需要向下转型了
     */
泛型类
/*
    泛型类 - 在类上声明泛型

    1.泛型的名字要大写如果有多个泛型用逗号分开
    2.创建Employee类的对象并指定了泛型的类型。当我们指定泛型的类型后类中使用了泛型的地方都被具体化了。
    3.new后面的泛型类型可以不写 但是<>不能省略。可以通过类型推断-推出后面的类型
    4.如果在创建泛型类的对象时不指定泛型的类型默认是Object
 */
class A<K,T,V>{
    K k;
    T t;
    V v;
}
class Employee<E>{
    E e;//属性

    /*
    构造器上不能声明泛型
    public Employee<E>(){

    }
     */
    public Employee(E e){//在构造器上可以使用泛型类型的形参

    }
    public Employee(){
        /*
        try-catch中不能使用泛型类型
        try{

        }catch (E e){

        }

         */
    }

    /*
    静态方法不能使用泛型的类型 -- 加载时机的原因(给类上的泛型指定类型是在创建对象的时候)
    public static void set(E e){

    }
     */


    public E getE() {
        return e;
    }

    public void setE(E e) {
        this.e = e;
    }
}
class SuperClass<T>{
    T t;
    public void setT(T t){

    }
}
/*
如果给父类的泛型类型指定类型
1.在继承父类时直接指定父类的泛型类型-如果不指定默认是Object
2.在继承父类时不直接指定父类的泛型类型但子类也需要声明成泛型类 这样就可以将子类的泛型传父类的泛型
*/

//1.在继承父类时直接指定父类的泛型类型-如果不指定默认是Object
class SubClass extends SuperClass<String>{
}

//2.在继承父类时不直接指定父类的泛型类型但子类也需要声明成泛型类 这样就可以将子类的泛型传父类的泛型
class SubClass2<T> extends SuperClass<T>{ //子类的泛型和父类的泛型名称必须一致
}
泛型接口
/*
    泛型接口 :接口上声明泛型
 */
interface MyInterface<K>{
    void setK(K k);
}
泛型方法
class Demo<T>{

    public void set(T t){//这不是泛型方法 -- 直接使用类上的泛型

    }

    /*
        泛型方法 : 在声明的方法上加 <E>
     */
    public <E> void setDemo(E e){

    }

    /*
        方法上的泛型类型由传递给泛型类型的形参的实参类型决定
     */
    public static <K> K getK(K k,Integer i){//不依赖对象 - 不依赖类上的泛型
        return k;
    }
}
通配符
/*
    通配符(无界通配符) : ?
 */
public class GenericityTest {
    @Test
    public void test(){

        /*
            Object <- Number <- Integer
         */
        List<Number> numberList = new ArrayList<>();
        List<Object> objectList = new ArrayList<>();
        List<Integer> integerList = new ArrayList<>();

//        numberList = integerList;//不对的
        /*
            注意:Number和Integer存在子父类关系但是
                泛型<Number>和泛型<Integer>不存在子父关系。
                可以认为泛型<?> 是所有泛型的"父类"

         */
        //List<Object> list = new ArrayList<Integer>();//不对 泛型类型不一致

        set(numberList);
        set(objectList);
        set(integerList);

        List<?> list1 = objectList;
        List<?> list2 = numberList;
        List<?> list3 = integerList;
    }

    public void set(List<?> objList){

    }


    //==========================================================

    @Test
    public void test2(){
        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        set2(list);
    }
    /*
        使用了通配符的集合:
            存:只能存null -- 
            	因为不清楚给使用了通配符的集合赋值的那个集合中的数据类型到底放的是什么
            取:都可以遍历
     */
    public void set2(List<?> list){
        list.add(null);//使用了通配符的集合只能存null值
        for (Object o : list) {
            System.out.println(o);
        }
    }
}

通配符的上界和下界
  /*
      给通配符加限制
       通配符的上界:  
       			<? extends 类/接口> : 那么赋值的对象的泛型类型只能是该类的子类及本类的类型
                如果是接口类型 那么赋值的对象的泛型类型只能是该接口的实现类

       通配符的下界:  <? super 类> :那么赋值的对象的泛型类型只能是该类及该类的父类
     */

	/*
    List<Animal> list = new ArrayList<>();
    List<Dog> list = new ArrayList<>();
    List<HaShiQi> list = new ArrayList<>();
     */
    public void set22(List<? extends Animal> list){
        //只能放null - 因为传给list的对象中的泛型可能无限小
        list.add(null);
    }

    /*
    List<Animal> list = new ArrayList<>();
    List<Biology> list = new ArrayList<>();
     */
    public void set33(List<? super Animal> list){
        //只能放Animal及Animal的子类 -- 因为传给list对象的泛型的类型最小是Animal
        list.add(new Animal());
        list.add(new Dog());
    }
    
    
    class Biology{

    }
    class Animal extends Biology{

    }
    class Dog extends Animal{

    }

    class HaShiQi extends Dog{

    }
泛型的上界约束
//对泛型类型进行限制 (泛型类型的限制只有上界没有下界)
/*
    T extends 接口 :泛型的类型必须是该接口的实现类
    T extends 类 : 泛型的类型必须是该类的类型及子类的类型
    T extends 类 & Comparable :泛型的类型必须是类及类的子类并且实现了Comparable接口
 */
class Person<T extends A2 & Comparable>{ //T表示任意类型
    T t;

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }
}
类型擦除
泛型在字节码文件中是没有的。泛型是为了告诉编译器在编译时进行类型检查。

11.8 Set

Set概述
/*
    |------Collection
        |------List : 存储的元素是有序并可重复
        |------Set :存储的元素无序并且不可以重复
            |------HashSet
                |------LinkedHashSet
            |------TreeSet

   说明:
    1.Set继承了Collection接口
    2.Set是单列集合
    3.Set存储的元素无序并且不可以重复
        无序性 :是因为存放的元素的位置是根据hash值计算出的-计算出的值(0 到 数组的长度-1)没有序
        不可重复 :是通过equals方法进行比较的结果。如果为true说明重复如果为false说明不重复。
    4.Set的元素的遍历Iterator和增强for循环
    5.Set中并没有再增加其它方法
    5.Set的主要实现类 :HashSet LinkedHashSet TreeSet
 */
HashSet
 /*
        HashSet的底层是Hash表(数组 + 链表 + 红黑树)
        注意:HashSet中如果存放的是自定义类的对象那么该对象所属的类必须重写
        		equals和hashCode方法。
     */
    @Test
    public void test2(){
        /*
            思考? 如何要数据不重复得怎么做 - HashSet?
                当我们向集合中存放数据a时会先调用a的hashCode方法得出一个在数组中存放的位置
             	如果该位置没有其它元素则直接放入到该位置。如果该位置已经存在了其它元素b时。
             	那么会调用 a的equals方法和b进行比较,如果返回的结果为false
             	说明a和b不一样则以链表的形式存式。如果返回的结果为true则说明
             	a和b是一样的则不能放入。
         */
        /*
            当我们向Set中放数据  第1步是找位置  第2步是比较内容
         */
        Person p1 = new Person("aaa", 1);
        Person p2 = new Person("bbb", 2);
        Person p3 = new Person("ccc", 3);
        Person p4 = new Person("ddd", 4);
        Person p5 = new Person("ddd", 4);

        Set<Person> set = new HashSet<>();
        set.add(p1);
        set.add(p2);
        set.add(p3);
        set.add(p4);
        set.add(p5);

        System.out.println("p1:" + p1.hashCode());
        System.out.println("p2:" + p2.hashCode());
        System.out.println("p3:" + p3.hashCode());
        System.out.println("p4:" + p4.hashCode());
        System.out.println("p5:" + p5.hashCode());

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

        for (Person person : set) {
            System.out.println(person);
        }
    }
LinkedHashSet
/*
    LinkedHashSet :
        LinkedHashSet是HashSet的子类。LinkedHashSet和HashSet的底层实现是一样的。
     只不过LinkedHashSet的底层维护了一张链表(双向链表) 通过该链表就可以按照添加的元素顺序进行遍历
 */
TreeSet
/*
    TreeSet :
        1.TreeSet底层是红黑树
        2.TreeSet的作用是用来对元素进行排序
        3.TreeSet中存放的元素的类型必须是同一类型
        4.TreeSet的排序-自然排序 vs 定制排序
 */
public class SetTest3 {

    @Test
    public void test(){
        TreeSet<String> set = new TreeSet<>();
        set.add("c");
        set.add("a");
        set.add("b");
        set.add("d");

        System.out.println(set);
    }

    @Test
    public void test2(){
        TreeSet set = new TreeSet();
        set.add("c");
        set.add(1);
        set.add(2);
        set.add("d");

        System.out.println(set);
    }

    /*
        自然排序 - 自定义的类实现Comparable接口
     */
    @Test
    public void test3(){
        TreeSet<Student> set = new TreeSet<>();
        set.add(new Student("c",1));
        set.add(new Student("d",5));
        set.add(new Student("a",3));
        set.add(new Student("b",2));
        set.add(new Student("k",2));
        set.add(new Student("f",2));

        System.out.println(set);
    }

    /*
        定制排序 - 将Comparator接口的实现类的对象传到TreeSet的构造器即可
     */
    @Test
    public void test4(){
        //如果使用定制排序那么自定义类就不需要实现Comparable接口
        //如果两种都有 --- 定制排序生效
        TreeSet<Student> set = new TreeSet<>(new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return o1.name.compareTo(o2.name);
            }
        });
        set.add(new Student("c",1));
        set.add(new Student("d",5));
        set.add(new Student("a",3));
        set.add(new Student("b",2));
        set.add(new Student("k",2));
        set.add(new Student("f",2));

        System.out.println(set);
    }
}

class Student implements Comparable<Student>{
    String name;
    int id;

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

    @Override
    public String toString() {
        return id + " " + name;
    }

    /*
        按照id排序 如果id相同再按照name排序。

    @Override
    public int compareTo(Student o) {
        int cid = this.id - o.id;
        if (cid == 0){
            return this.name.compareTo(o.name);
        }
        return cid;
    }

     */

    @Override
    public int compareTo(Student o) {
        return this.id - o.id;
    }
}

11.9 Map

概述
/*
    集合主要分两类 : Collection(单列集合)   Map(双列集合)

    |-------Map
        |---------HashMap(主要实现类,底层是hash表 - 线程不安全的)
            |-----------LinkedHashMap(可以按照添加元素的顺序进行排序)
        |---------Hashtable(底层也是hash表 不过是线程安全的)
            |-----------Properties(主要读取配置文件-后面讲)
        |---------TreeMap(用来对key进行排序)

    说明:
        1.Map中存放的是键值对
        2.Map中常用的实现类有 :HashMap  LinkedHashMap Hashtable TreeMap Properties
        3.Map中的元素是无序的
        4.key不可重复(如果重复value覆盖-后面的覆盖前面的) value可重复
        5.如果map中的key是自定义类的对象那么必须重写hashCode方法和equals方法。
              因为往map中存放的对象会通过hashCode方法得到哈希值再通过该哈希值计算存储的位置。
              equasl方法-如果同一个位置已经存在对象那么需要通过equals判断是否一样
                    (如果为true说明key值相同-key值相同会value覆盖 否则说明key值不同)
        6.如果map中的value是自定义类的对象必须重写equals方法
             为什么不用重写value的hashCode方法 - 在map中是通过key确定位置的 和 value没有关系
 */
API
/*
1、添加操作
* V put(K key,V value):添加一对键值对
* void putAll(Map<? extends K,? extends V> m):添加一组键值对

2、删除
* void clear():清空map
* V remove(Object key):根据key删除一对键值对
* default boolean remove(Object key,Object value):删除匹配的(key,value)

3.修改value(JDK1.8新增)
- default V replace(K key, V value):找到目标key,替换value
- default boolean replace(K key,V oldValue,V newValue):找到目标(key,value),替换value
- default void replaceAll(BiFunction<? super K,? super V,? extends V> function):按照指定要求替换value

4.
V get(Object key):根据key返回value
boolean containsKey(Object key):判断key是否存在
boolean containsValue(Object value):判断value是否存在
boolean isEmpty():判断map是否为空
int size():获取键值对的数量
*/
HashMap
/*
1.HashMap的底层是Hash表(数组 + 链表 + 红黑树)

2.HashMap的构造器 :通过空参构造器创建对象时给加载因子赋值0.75

3.第一次添加数据时 : 当第一次添加时底层会创建一个长度为16的数组(Node类型的数组)
阈值是12 = 加载因子 * 数组的长度(当数组中的元素的个数超过阈值就会扩容)

4.底层扩容(第1次) :当我们向集合中添加第13个元素时底层会进行扩容。
数组的长度为原来的2倍阈值为原来的2倍。同时将原来的hash表中的数据copy到新的hash表中。

5.向集合中存放数据流程
当我们向集合中存放数据(k1,v1)时首先会先通过k1的hashCode方法算出哈希值通过哈希值计算出
要在数组中存放的位置。如果该位置没有其它元素直接放入。
如果该位置已经存在其它元素(K,V)时那么就会调用
K1的equals方法和K进行比较如果返回值为true说明key值相同V1覆盖V。
如果返回值为false
(
    要和链表中每一个值equals对比直到最后
    如果在和链表上的数据比较时返回true那么value则直接覆盖 ,
    如果比到最后都是false则以链表的形式连上去
)
则以链表的形式直接连上去

6.链表什么时候转成红黑树?
当链表上的元素个数达到9时并且数组的长度要>=64那么会将链表转成红黑树
*/
LinkedHashMap
/*
    LinkedHashMap :LinkedHashMap是HashMap的子类。
    LinkedHashMap的底层实现和HashMap的底层实现是一样的。
    LinkedHashMap中维护一张链表用来记录元素添加的顺序
    (HashMap的底层是Node[]  LinkedHashMap底层是Entry[]  -  Entry是Node的子类)。

 */
Hashtable
/*
    [面试题]HashMap和Hashtable的区别?
        1.HashMap是线程不安全的 Hashtable是线程安全的 (重要)
        2.HashMap和Hashtable底层都是Hash表
        3.HashMap的空参构造器中只给加载因子赋值0.75 
        	(第1次创建的数组长度为16-Node[] 阈值是12)
          Hashtable的空参构造器中创建了一个长度为11的Entry[]  加载因子0.75  阈值是8
        4.Hashtable中的key,value不能是null (重要)
          HashMap中的key,value可以是nulll
        5.HashMap扩容为原来的2倍。Hashtable扩容为原来的2倍+1
 */
Properties
/*
    Properties:
        1.Properties是Hashtable的子类
        2.Properties是一个Map
        3.Properties中的key和value都是String类型
        4.Properties可以用来读取配置文件(IO流再说)
 */
public class MapTest5 {
    public static void main(String[] args) {

        Properties properties = new Properties();
        //存入数据
        properties.setProperty("aaa","111");

        //获取数据
        String s = properties.getProperty("aaa");
        System.out.println(s);

    }
}
TreeMap
/*
    TreeMap :
        说明:
            1.TreeMap的底层是红黑树(TreeSet)
            2.TreeMap用来对数据排序 --  只能对key排序
            3.TreeMap对key的排序方式 : 自然排序 vs 定制排序
            4.TreeMap中key的类型要保持一致。
 */

11.10 Collections工具类

[面试题]CollectionCollections的区别?
			Collections工具类 : 用来对集合做一些操作
			Collection是一个接口

    /*
     * public static <T> boolean addAll(Collection<? super T> c,T... elements)
     *      将所有指定元素添加到指定 collection 中。
     * public static <T> int binarySearch(List<? extends Comparable<? super T>> list,T key)
     *      在List集合中查找某个元素的下标,但是List的元素必须是T或T的子类对象,
     		而且必须是可比较大小的,
     *      即支持自然排序的。而且集合也事先必须是有序的,否则结果不确定。
     * public static <T> int binarySearch(List<? extends T> list,T key,Comparator<? super T> c)
     *      在List集合中查找某个元素的下标,但是List的元素必须是T或T的子类对象,而且集合也事先必须是按照c比较器规则进行排序过的,
     *      否则结果不确定。
     */
    
    
    /*
     * public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)
     *      在coll集合中找出最大的元素,集合中的对象必须是T或T的子类对象,而且支持自然排序
     * public static <T> T max(Collection<? extends T> coll,Comparator<? super T> comp)
     *      在coll集合中找出最大的元素,集合中的对象必须是T或T的子类对象,按照比较器comp找出最大者
     * public static void reverse(List<?> list)反转指定列表List中元素的顺序。
     * public static void shuffle(List<?> list) List 集合元素进行随机排序,类似洗牌
     */
    
     /*
     * public static <T extends Comparable<? super T>> void sort(List<T> list)
     *      根据元素的自然顺序对指定 List 集合元素按升序排序
     * public static <T> void sort(List<T> list,Comparator<? super T> c)
     *      根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
     * public static void swap(List<?> list,int i,int j)
     *      将指定 list 集合中的 i 处元素和 j 处元素进行交换
     * public static int frequency(Collection<?> c,Object o)
     *      返回指定集合中指定元素的出现次数
     */
    
     /*
     * public static <T> void copy(List<? super T> dest,List<? extends T> src)
     *      将src中的内容复制到dest中
     * public static <T> boolean replaceAll(List<T> list,T oldVal,T newVal):
     *      使用新值替换 List 对象的所有旧值
     * Collections 类中提供了多个 synchronizedXxx()
     *      方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题
     * Collections类中提供了多个unmodifiableXxx()方法,该方法返回指定 Xxx的不可修改的视图。
     */

第十二章 多线程

12.1 进程,线程,程序概述

程序 : 一种编程语言编写的一组指令的集合。
进程:运行起来的程序就是一个进程
线程:一个进程中可以有多个线程 多个线程可以同时执行。

12.2 创建多线程方式一

格式1
/*
    在java中创建多线程的方式:
        方式一 :继承Thread
        方式二 :实现Runnable接口


    方式一 :继承Thread
        1.自定义一个类并继承Thread类
        2.重写run方法
        3.在run方法中实现需要在分线程执行的功能
        4.创建自定义类的对象
 */
public class ThreadTest2 {
    /*
        调用main方法的线程叫作主线程或main线程
     */
    public static void main(String[] args) {
        //4.创建自定义类的对象
        MyThread mt = new MyThread();
        //5.调用start方法 开启分线程
        mt.start();//做了两件事①开启分线程 ②调用run方法


        System.out.println("============================开启第二个分线程=======================");
        //再创建一次Thread子类的对象
        MyThread mt2 = new MyThread();
        mt2.start(); //mt2和mt 这两个线程调用的MyThread中的run方法。

        System.out.println("============================开启第二个分线程=======================");
        //再创建一次Thread子类的对象
        MyThread2 mt3 = new MyThread2();
        mt3.start(); // mt的分线程调用的是MyThread中的run方法  mt3的分线程调用的是MyThread2中的run方法

        for (int i = 0; i < 50; i++) {
            /*
                Thread.currentThread() : 获取当前线程
                getName() :获取线程的名字
             */

            System.out.println(Thread.currentThread().getName() + "===在main线程中......" + i);
        }
    }
}
//1.自定义一个类并继承Thread类
class MyThread extends Thread{
    public MyThread(){
        super();
    }
    //2.重写run方法
    @Override
    public void run() {
        //3.在run方法中实现需要在分线程执行的功能
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName() + "===清理垃圾中......" + i);
        }
    }
}

class MyThread2 extends Thread{
    @Override
    public void run() {

    }
}
格式2
 		new Thread(){ //new的是Thread的子类的对象
            @Override
            public void run() {
                for (int i = 0; i < 50; i++) {
                    System.out.println(Thread.currentThread().getName() + "====" + i);
                }
            }
        }.start();

        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 50; i++) {
                    System.out.println(Thread.currentThread().getName() + "-----" + i);
                }
            }
        }.start();

12.3 创建多线程方式二

格式1
/*
    创建线程的第二种方式
        1.自定义一个类并实现Runnable接口
        2.实现run方法
        3.在run方法中去实现需要在分线程中去实现的功能
        4.去创建Runnable接口的实现类的对象
        5.创建Thread类的对象并将Runnable接口的实现类的对象作为实参传到Thread的构造器中
 */
public class RunnableTest {
    public static void main(String[] args) {
        //4.去创建Runnable接口的实现类的对象
        MyRunnable mr = new MyRunnable();
        //5.创建Thread类的对象并将Runnable接口的实现类的对象作为实参传到Thread的构造器中
        new Thread(mr).start();

        System.out.println("==============再开启一个分线程==============");

        new Thread(mr).start();//该线程调用的和上面的线程调用的是同一个run方法

        System.out.println("==============再开启一个分线程==============");
        new Thread(new MyRunnable2()).start();//该线程调用的是MyRunnable2中的run方法
    }
}

//1.自定义一个类并实现Runnable接口
class MyRunnable implements Runnable{
    //2.实现run方法
    @Override
    public void run() {
        //3.在run方法中去实现需要在分线程中去实现的功能
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName() + "======" + i);
        }
    }
}

class MyRunnable2 implements Runnable{
    //2.实现run方法
    @Override
    public void run() {
        //3.在run方法中去实现需要在分线程中去实现的功能
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName() + "======" + i);
        }
    }
}
格式2
new Thread(new Runnable() {
    @Override
    public void run() {
        while (true){
            System.out.println(Thread.currentThread().getName() + "清理垃圾中.......");
        }
    }
},"longge---").start();

new Thread(new Runnable() {
    @Override
    public void run() {
        while (true) {
            System.out.println(Thread.currentThread().getName() + "杀毒中==============");
        }
    }
},"weige---").start();

12.4 线程API

/* 
    - public Thread() :分配一个新的线程对象。
    - public Thread(String name) :分配一个指定名字的新线程对象。
    - public Thread(Runnable target) :分配一个带有指定目标新的线程对象。
    - public Thread(Runnable target,String name) :
    		分配一个带有指定目标新的线程对象并指定名字。
 */
/*
     * public void run() :此线程要执行的任务在此处定义代码。
     * public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。
     * public String getName() :获取当前线程名称。
     * public static Thread currentThread() :返回当前正在执行的线程对象的引用。
     * public final boolean isAlive():测试线程是否处于活动状态。
     	如果线程已经启动且尚未终止,则为活动状态。
*/
 /*
     * public final int getPriority() :返回线程优先级
     * public final void setPriority(int newPriority) :改变线程的优先级

     * 每个线程都有一定的优先级,优先级高的线程将获得较多的执行机会。每个线程默认的优先级都与创建它的父线程具有相同的优先级。Thread类提供了setPriority(int newPriority)和getPriority()方法类设置和获取线程的优先级,其中setPriority方法需要一个整数,并且范围在[1,10]之间,通常推荐设置Thread类的三个优先级常量:
     * MAX_PRIORITY(10):最高优先级
     * MIN _PRIORITY (1):最低优先级
     * NORM_PRIORITY (5):普通优先级,默认情况下main线程具有普通优先级。
     */

  /*
    public static void sleep(long millis) :
        使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。

     */

/*
     public static void yield():
        yield只是让当前线程暂停一下,让系统的线程调度器重新调度一次,
        希望优先级与当前线程相同或更高的其他线程能够获得执行机会,但是这个不能保证,
        完全有可能的情况是,当某个线程调用了yield方法暂停之后,
        线程调度器又将其调度出来重新执行。
*/

/*
    void join() :等待该线程终止。
    void join(long millis) :等待该线程终止的时间最长为 millis 毫秒。
    	如果millis时间到,将不再等待。
    void join(long millis, int nanos) :
    	等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。
     */
public static void main(String[] args) throws InterruptedException {

    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            for (int i = 0; i < 50000; i++) {
                System.out.println(Thread.currentThread().getName() + "====" + i);
            }
        }
    });
    t1.start();


    for (int i = 0; i < 50; i++) {
        if (i == 5){
            //让t1线程执行完再执行当前线程
            t1.join();
            //当前线程让cpu资源让1毫秒 1毫秒(当这1毫秒中只有t1获取cpu资源)
            //后当前线程仍然要获取cpu资源
            t1.join(1);
        }
        System.out.println(Thread.currentThread().getName() + "====" + i);
    }
守护线程
/*
    守护线程 : 当程序中如果没有其它线程执行 守护线程死亡
    setDaemon(true) : 将线程设置守护线程
 */
public class DeamonTest {
    public static void main(String[] args) {
        /*
            GC就是守护线程
         */
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("守护线程在运行");
                }
            }
        });
        //将t1线程变成守护线程(在程序中只要没有其它线程还在运行 守护线程就会死亡)
        t1.setDaemon(true);
        t1.start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){

                }
            }
        }).start();


        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName() + "====" + i);
        }
    }
}

12.5 线程安全

线程安全问题概述
/*
    线程安全:
    发现问题:当三个窗口同时卖票时(三个线程操作共享数据时)会发生重票,错票问题(0票 负票)
    发生问题的原因 :当线程在操作共享数据时还没有操作完另一个线程也进来开始操作共享数据就会发生线程安全问题
    如何解决 :当一个线程在操作共享数据时不允许其它线程也进来操作共享数据(上锁)

 */

线程安全问题案例
public class ThreadSafeTest {
    public static void main(String[] args) {

        MyRunnable mr = new MyRunnable();
        new Thread(mr,"窗口1:").start();
        new Thread(mr,"窗口2:").start();
        new Thread(mr,"窗口3:").start();
     
    }
}

class MyRunnable implements Runnable{
    int ticketNumber = 50;

    @Override
    public void run() {

        while (true) {
            if (ticketNumber > 0) {
                System.out.println(Thread.currentThread().getName() + "====" + ticketNumber);
                ticketNumber--;
            } else {
                return;//结束方法 - 结束线程
            }
        }

    }
}

12.6 同步代码块

格式
/*
  方案一 :同步代码块
        格式: synchornized(监视器/锁){
                 操作共享数据的代码
             }
        说明:
            1.监视器/锁是任意类的对象。但是所有的线程用的必须是同一把锁。
            2.多个线程需要抢锁/监视器 谁抢到谁就执行同步代码块中的内容。
                其它线程需要等待 等待操作共享数据的线程执行完毕
 */
继承Thread
/*
    线程安全

    方案一 : 同步代码块

    使用继承Thread方式开启线程
        注意:
        	 1.共享数据只能有一份。共享数据如果是在类中声明的那么必须用static修饰。
             2.同步监视器/锁 三个线程必须使用的是同一把。
             在这如果写this那么this就是三个对象就不对了。
 */
public class ThreadSafeTest2 {
    public static void main(String[] args) {
        //创建三个线程
        new MyThread("窗口1").start();
        new MyThread("窗口2").start();
        new MyThread("窗口3").start();
    }
}
/*
    使用继承Thread方式开启线程
        注意:1.共享数据只能有一份。共享数据如果是在类中声明的那么必须用static修饰。
             2.同步监视器/锁 三个线程必须使用的是同一把。在这如果写this那么this就是三个对象就不对了。
 */
class MyThread extends Thread{
    private static int ticketNumber = 100;//如果不加static三个对象 三个属性

    private static Object obj = new Object();//如果不加static三个对象 三个属性

    public MyThread(String name){
        super(name);
    }

    @Override
    public void run() {
        while (true){
            //同步代码块
            //不可以写this因为是三个对象 --- MyThread.class当前类的运行时类的对象(类信息)
            synchronized (MyThread.class) {
                if (ticketNumber > 0) {
                    System.out.println(Thread.currentThread().getName() + "==" + ticketNumber);
                    ticketNumber--;
                } else {
                    return;
                }
            }
        }
    }
}
实现Runnable
public class ThreadSafeTest {
    public static void main(String[] args) {

        MyRunnable mr = new MyRunnable();
        new Thread(mr,"窗口1:").start();
        new Thread(mr,"窗口2:").start();
        new Thread(mr,"窗口3:").start();
    }
}

class MyRunnable implements Runnable{
    int ticketNumber = 100;
    Integer i = 99;

    @Override
    public void run() {
        /*
        下面的代码有问题 :同步代码块包多了 (同步代码块中的代码是串行-降低效率)
        synchronized (this) { //发生的问题 :同时只有一个窗口在卖
            while (true) {//三个线程用的是同一把锁
                if (ticketNumber > 0) {
                    System.out.println(Thread.currentThread().getName() + "====" + ticketNumber);
                    ticketNumber--;
                } else {
                    return;//结束方法 - 结束线程
                }
            }
        }
         */

        /*
        下面的代码不对 因为三个线程拿的不是同一把锁
        while (true) {
            synchronized (i--) {  //三个线程用的是同一把锁
                if (ticketNumber > 0) {
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }


                    System.out.println(Thread.currentThread().getName() + "====" + ticketNumber);
                    ticketNumber--;
                } else {
                    return;//结束方法 - 结束线程
                }
            }

         */

        while (true) {
            synchronized (this) {  //三个线程用的是同一把锁
                if (ticketNumber > 0) {
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }

                    System.out.println(Thread.currentThread().getName() + "====" + ticketNumber);
                    ticketNumber--;
                } else {
                    return;//结束方法 - 结束线程
                }
            }
        }
    }
}

12.7 同步方法

继承Thread
/*
    同步方法:
 */
public class ThreadSafeTest2 {
    public static void main(String[] args) {
        //创建三个线程
        new MyThread("窗口1").start();
        new MyThread("窗口2").start();
        new MyThread("窗口3").start();
    }

}

class MyThread extends Thread{
    private static int ticketNumber = 100;//如果不加static三个对象 三个属性
    public MyThread(String name){
        super(name);
    }
    @Override
    public void run() {
        while (true){
            if (!get()){
                return;//结束线程
            }
        }
    }

    /*
        同步方法的锁:
            静态方法 : 所属类的运行时类 MyThread.class
            非静态主就去 : this
     */
    public static synchronized boolean get(){
        if (ticketNumber > 0) {
            System.out.println(Thread.currentThread().getName() + "==" + ticketNumber);
            ticketNumber--;
            return true;
        } else {
            return false;
        }
    }
}
实现Runnable
public class ThreadSafeTest {
    public static void main(String[] args) {

        MyRunnable mr = new MyRunnable();
        new Thread(mr,"窗口1:").start();
        new Thread(mr,"窗口2:").start();
        new Thread(mr,"窗口3:").start();
    }
}

class MyRunnable implements Runnable{
    int ticketNumber = 100;
    Integer i = 99;

    @Override
    public void run() {
        while (true) {
            if(!get()){//get方法的返回值为true说明有票  返回值为false说明没有票了
                return;//结束线程
            }
        }
    }

    /*

        同步方法的锁:
            静态方法 : 当前类的运行时类 MyThread.class

     */
    public synchronized boolean get(){
        //操作共享数据的代码
        if (ticketNumber > 0) {
            System.out.println(Thread.currentThread().getName() + "====" + ticketNumber);
            ticketNumber--;
            return true;
        } else {
            return false;//说明票卖完了
        }
    }
}

12.8继承 Thread和实现Runnable的区别?

 [面试题]继承Thread和实现Runnable有什么区别?
        1.实现接口和继承类 - 实现接口更灵活因为可以多实现。
        2.线程安全
            同步代码块 :
                继承Thread : 锁不可以是this
                实现Runnable : 锁可以是this
            同步方法
                继承Thread : 同步方法要使用静态同步方法--锁是当前类的运行时类(类信息)
                实现Runnable : 同步方法不是静态的 - 锁是this
        3.共享数据
             继承Thread : 如果共享数据是在Thread子类中声明的该共享数据需要使用static修饰
             实现Runnable : 如果共享数据是在Runnable接口的实现类中声明的
             	共享数据不需要使用static修饰

12.9 锁的案例

class Demo{
    public synchronized void run(){//锁 - this
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName() + "====" + i);
        }
    }

    public synchronized void run2(){//锁 - this
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName() + "=========" + i);
        }
    }

    public static synchronized void staticRun(){//锁:Demo.class
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName() + "--------" + i);
        }
    }

    public static synchronized void staticRun2(){//锁:Demo.class
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName() + "--------------" + i);
        }
    }
}
public class ThreadTest {
    public static void main(String[] args) {
        /*
        Demo demo = new Demo();

        new Thread(new Runnable() {
            @Override
            public void run() {
                demo.run();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                demo.run2();
            }
        }).start();

         */

        /*
        new Thread(new Runnable() {
            @Override
            public void run() {
                new Demo().run();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                new Demo().run2();
            }
        }).start();

         */

        new Thread(new Runnable() {
            @Override
            public void run() {
                new Demo().staticRun();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                new Demo().staticRun2();
            }
        }).start();

    }
}

12.10 死锁

		//两把锁
        StringBuilder s1 = new StringBuilder();
        StringBuilder s2 = new StringBuilder();

        //两个线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (s1){
                    //做需要做的事情
                    System.out.println("线程1拿到了s1锁");
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    synchronized (s2){
                        //做需要做的事情
                        System.out.println("线程1拿到了s2锁");
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (s2){
                    //做需要做的事情
                    System.out.println("线程2拿到了s2锁");
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    synchronized (s1){
                        //做需要做的事情
                        System.out.println("线程2拿到了s1锁");
                    }
                }
            }
        }).start();

12.11 线程通信

概述
1. wait:线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源,
    也不会去竞争锁了,这时的线程状态即是 WAITINGTIMED_WAITING。
    它还要等着别的线程执行一个**特别的动作**,也即是“**通知(notify)**”或者等待时间到,
    在这个对象上等待的线程从wait set 中释放出来,重新进入到调度队列(ready queue)中
2. notify:则选取所通知对象的 wait set 中的一个线程释放;
3. notifyAll:则释放所通知对象的 wait set 上的全部线程。

注意:①上面的三个方法只能在同步代码块 和 同步方法中使用
     ②上面的三个方法是通过锁调用的
案例一
class MyRun implements Runnable{
    int i = 0;

    private Object obj = new Object();

    @Override
    public void run() {
        //同步代码块
        synchronized (obj){
            System.out.println(Thread.currentThread().getName() + "进来了");
            i++;
            System.out.println("====" + i);
            if (i == 3){
                //obj.notify();//唤醒第一个wait的线程
                obj.notifyAll();//唤醒所有在睡眠的线程
            }

            try {
                obj.wait();//当前线程睡觉 - 只能被唤醒
                //obj.wait(2000);//让当前线程睡觉 - 时间到自己就唤醒了 - 会释放锁
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            System.out.println(Thread.currentThread().getName() + "出去了");
        }
    }
}
案例二:两个线程交替打印
public class WaitTest2 {
    /*
        需求: 创建两个线程  依次交替输出 100 99 98 97
            线1 : 100
            线2 : 99
            线1 : 98
            线2 :97
     */
    public static void main(String[] args) {
        MyRunnable mr = new MyRunnable();
        new Thread(mr,"线程1:").start();
        new Thread(mr,"线程2:").start();
    }
}
class MyRunnable implements Runnable{
    int number = 100;
    @Override
    public void run() {
        while (true) {
            synchronized (this) {

                notify();

                if (number > 0){
                    System.out.println(Thread.currentThread().getName() + number);
                    number--;
                }else{
                    return;
                }

                try {
                    wait();//让当前线程睡觉  同时释放锁 让另一个线程进来
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

12.12 生产者和消费者

单个生产者和消费者
/*
    生产者和消费者 (一个生产者 和 一个消费者)
 */
class Clerk{
    public int number = 0;//现有包子的数量
    public int maxNumber = 20;//包子最大数量
    /*
        生产者 - 生产
     */
    public synchronized void put(){//锁默认是this
        if (number >= maxNumber){
            try {
                wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

        //生产包子
        number++;
        System.out.println(Thread.currentThread().getName() + "====" + number);
        notify();//唤醒正在睡觉的消费者
    }

    /*
        消费者 - 消费
     */
    public synchronized void get(){
        if (number <= 0){//没有包子 - 睡觉
            try {
                wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

        number--;
        System.out.println(Thread.currentThread().getName() + "--------" + number);
        notify();//唤醒正在睡觉的生产者
    }
}

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

        Clerk c = new Clerk();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {//不停的生产包子
                    c.put();
                    try {
                        Thread.sleep((int)(Math.random()*10));
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        },"生产者").start();


        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {//不停的吃包子
                    c.get();
                    try {
                        Thread.sleep((int)(Math.random()*10));
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        },"消费者").start();
    }
}

多个生产者和消费者
/*
    生产者和消费者 (多个生产者 和 多个消费者)

    1.将notify改成notifyAll
    2.将if判断变量while循环-每次唤醒后需要再判断一次
 */
class Clerk2{
    public int number = 0;//现有包子的数量
    public int maxNumber = 20;//包子最大数量
    /*
        生产者 - 生产
     */
    public synchronized void put(){//锁默认是this
        while (number >= maxNumber){
            try {
                wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

        //生产包子
        number++;
        System.out.println(Thread.currentThread().getName() + "====" + number);
        notifyAll();//唤醒正在睡觉的消费者
    }

    /*
        消费者 - 消费
     */
    public synchronized void get(){
        while (number <= 0){//没有包子 - 睡觉
            try {
                wait();//无论唤醒哪个线程 还是几个线程 都要被唤醒后再做一次判断
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

        number--;
        System.out.println(Thread.currentThread().getName() + "##########" + number);
        notifyAll();;//唤醒正在睡觉的生产者
    }
}

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

        Clerk2 c = new Clerk2();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {//不停的生产包子
                    c.put();
                    try {
                        Thread.sleep((int)(Math.random()*10));
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        },"生产者").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {//不停的生产包子
                    c.put();
                    try {
                        Thread.sleep((int)(Math.random()*10));
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        },"生产者2").start();


        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {//不停的吃包子
                    c.get();
                    try {
                        Thread.sleep((int)(Math.random()*10));
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        },"消费者").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {//不停的吃包子
                    c.get();
                    try {
                        Thread.sleep((int)(Math.random()*10));
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        },"消费者2").start();
    }
}

12.13 线程的生命周期

请添加图片描述

请添加图片描述

12.14 单例设计模式

public class LazyOne {
    private static LazyOne instance;

    private LazyOne(){}

    //有指令重排问题
    public static LazyOne getInstance(){
        if(instance == null){
            synchronized (LazyOne.class) {
                try {
                    Thread.sleep(10);//加这个代码,暴露问题
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(instance == null){
                    instance = new LazyOne();
                }
            }
        }

        return instance;
    }
}
public class LazySingle {
    private LazySingle instance;
    private LazySingle(){}
    private static class Inner{
        static final LazySingle INSTANCE = new LazySingle();
    }
    public static LazySingle getInstance(){
        return Inner.INSTANCE;
    }
}

12.15 [面试题]sleep和wait的区别

/*
[面试题]sleep和wait的区别?
(1)sleep()不释放锁,wait()释放锁
(2)sleep(时间)指定休眠的时间,wait()/wait(时间)可以指定时间也可以无限等待直到notify或notifyAll
(3)sleep()在Thread类中声明的静态方法,wait方法在Object类中声明
*/

第13章File类和IO流

13.1 File类概述

    1.Java程序中一个文件(a.txt b.png c.mp4 d.avi)或一个目录(文件夹)就是一个File类的对象
    2.File类只能对文件或目录进行操作(比如:创建,删除,修改,查找......)不能对内容做操作
    3.如果要读取文件中的内容或向文件中写内容需要用到IO

13.2 FileAPI

相对路径和绝对路径
 绝对路径 :包含盘符在内的完整路径
 相对路径 :相对于当前工程(module)
API案例
 /*
        File类的构造器 :
     */
    @Test
    public void test(){
        //在这这个File用来表示一个文件
        File file = new File("D:\\io\\hdfs\\longge.txt");
        File file2 = new File("D:/io/hdfs\\longge.txt");
        //在这这个File用来表示一个目录
        File file3 = new File("D:\\io\\hdfs");

        
        new File("D:\\io\\hdfs","longge.txt");//相当于D:\io\hdfs\longge.txt
    }
    /*
       绝对路径 :包含盘符在内的完整路径
       相对路径 :相对于当前工程(module)
     */
    @Test
    public void test3(){
        //绝对路径 :包含盘符在内的完整路径
        File f = new File("D:\\class_video\\240318\\01-JavaSE\\尚硅谷_01_JavaSE课程资料_最新(上)\\07_code\\JavaSE\\day23\\longge.txt");

        //相对路径 :相对于当前工程(module)
        File f2 = new File("longge.txt");//默认就是在当前module下
    }

    @Test
    public void test4(){
        File file = new File("longge.txt");
        System.out.println(file.exists());//文件或目录是否存在

//        File file2 = new File("longge2.txt");
//        System.out.println(file2.exists());//文件或目录是否存在

        System.out.println(file.getName());//名字
        System.out.println(file.length());//大小
        System.out.println(file.getAbsolutePath());//绝对路径

        File f3 = new File("D:/io/a.txt");
        System.out.println(f3.getParent());//父目录
    }

    @Test
    public void test5() throws IOException {
        File file = new File("longge2.txt");
        if (!file.exists()){//文件、目录是否存在
            file.createNewFile();//创建文件 - 不是创建目录
        }else{//文件、目录存在
            //删除
            file.delete();
        }
    }
    
    @Test
    public void test6(){
        File file = new File("D:\\io\\hdfs\\a\\sanguo");
        file.mkdir();//创建目录 --- 如果父目录不存在则无法创建

        File file2 = new File("D:\\io\\hdfs\\a\\xiyouji");
        file2.mkdirs();//创建目录 ---- 如果父目录不存在则连父目录一并创建
    }

    @Test
    public void test7(){
        File file = new File("D:\\io\\hdfs");

        String[] list = file.list();//获取当前目录下所有内容(目录,文件)的名字
        for (String s : list) {
            System.out.println(s);
        }

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

        File[] files = file.listFiles();//以File类型的方式返回当前目录下所有内容(目录,文件)
        for (File f : files) {
            if (f.isFile()) System.out.println(f.getName() + "是一个文件");
            if (f.isDirectory()) System.out.println(f.getName() + "是一个目录");
            System.out.println(f.toString());
        }


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

        String[] list1 = file.list(new FilenameFilter() {//可以对遍历的内容进行过滤
            @Override
            public boolean accept(File dir, String name) {
                return name.length() >= 3;//返回true的内容才要
            }
        });
        for (String s : list1) {
            System.out.println(s);
        }
    }

    @Test
    public void test8() throws IOException {
        File f = new File("D:/io/a/b/../a.txt");
        String s = f.getCanonicalPath();//可以解析 ../
        System.out.println(s);

        System.out.println(f.getPath());
    }

13.3 IO流概述

/*
    IO流:
    流的分类:
        流的流向 :输入流,输出流
        数据的类型 :字节流和字符流。
        根据IO流的角色不同分为:节点流和处理流。

    四个流的抽象基类:
        字节流: InputStream,OutputStream
        字符流: Reader,Writer

   =========================节点流==========================
    字节流(节点流):
          输入流:FileInputStream
          输出流:FileOutputStream

    字符流(节点流):
          输入流:FileReader
          输出流:FileWriter
 */

13.4 FileInputStream

 /*
     字节流(节点流):
          输入流:FileInputStream
          输出流:FileOutputStream
     */
    @Test
    public void test() throws Exception {
        //1.创建File类的对象
        File file = new File("longge.txt");
        //2.创建流的对象--读数据
        FileInputStream fis = new FileInputStream(file);
        //3.读数据
        /*
            read() : 返回读取的数据 如果读到文件的最后返回-1
         */
        /*
        int read = fis.read();
        while (read != -1){
            System.out.println((char)(read));
            read = fis.read();
        }
         */
        int read = -1;
        while ((read  = fis.read()) != -1){ //读取的次数和编码集和换行符有关
            System.out.println((char)(read));
            System.out.println("============");
        }

        //4.关流
        fis.close();
    }

    @Test
    public void test2() throws Exception {
        //1.创建File类的对象
        File file = new File("longge.txt");
        //2.创建流的对象--读数据
        FileInputStream fis = new FileInputStream(file);
        //3.读数据
        /*
            read(byte[] b) : 返回读取的数据的长度 如果读到文件的最后返回-1
                将数据读取到数组中
         */
        byte[] bytes = new byte[5];
        int len = -1;
        while ((len = fis.read(bytes)) != -1){
            System.out.println(len);
            System.out.println(new String(bytes,0,len));
        }

        //4.关流
        fis.close();
    }

    @Test
    public void test3() {
        FileInputStream fis = null;//局部变量没有默认值

        try {
            //1.创建File类的对象
            File file = new File("longge.txt");
            //2.创建流的对象--读数据
            fis = new FileInputStream(file);
            //3.读数据
            /*
                read(byte[] b) : 返回读取的数据的长度 如果读到文件的最后返回-1
                    将数据读取到数组中
                //byte数组大小一般就是1024 (到底多大?要看文件大小  和  内存大小)
             */
            byte[] bytes = new byte[1024];
            int len = -1;
            while ((len = fis.read(bytes)) != -1) {
                System.out.println(len);
                System.out.println(new String(bytes, 0, len));
            }

        }catch (Exception e){
            e.printStackTrace();//打印异常信息到控制台
        }finally {
            //4.关流
            try {
                //因为有可能对象就没有创建成功就发生了异常 直接close会发NullPointerException
                if (fis != null) {
                    fis.close();
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

    }

13.5FileOutputStream

 /*
        字节流的输出流(节点流)
            FileOutputStream
     */
    @Test
    public void test() throws Exception {
        //1.创建File类的对象
        File file = new File("a.txt");//写的这个文件可以不存在 -- 在执行写操作时该文件不存在会自动创建
        //2.创建流的对象
        /*
        FileOutputStream(File file, boolean append)
        append : 如果为false表示覆盖(这一次 覆盖 上一次)。如果为true表示追加
            默认是false
         */
        FileOutputStream fos = new FileOutputStream(file,false);
        //3.写数据
        fos.write("111".getBytes());//getBytes() : 将字符串转成byte[]
        fos.write("222".getBytes());//getBytes() : 将字符串转成byte[]
        //4.关资源
        fos.close();
    }

    @Test
    public void test2() {
        FileOutputStream fos = null;
        try {
            //1.创建File类的对象
            //写的这个文件可以不存在 -- 在执行写操作时该文件不存在会自动创建
            File file = new File("a.txt");
            //2.创建流的对象
            /*
            FileOutputStream(File file, boolean append)
            append : 如果为false表示覆盖(这一次 覆盖 上一次)。如果为true表示追加
                默认是false
             */
            fos = new FileOutputStream(file, false);
            //3.写数据
            fos.write("111".getBytes());//getBytes() : 将字符串转成byte[]
            fos.write("222".getBytes());//getBytes() : 将字符串转成byte[]
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //4.关资源
            try {
                if (fos != null) {
                    fos.close();
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
案例copy
 @Test
    public void test2() throws IOException {

        copy("C:\\Users\\wangfeilong\\Desktop\\a.wmv",
                "C:\\Users\\wangfeilong\\Desktop\\aaaa.wmv");
    }

    public void copy(String readPath,String writePath){
        long start = System.currentTimeMillis();

        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            //1.创建File类的对象
            File readFile = new File(readPath);
            File writeFile = new File(writePath);

            //2.创建流的对象
            fis = new FileInputStream(readFile);
            fos = new FileOutputStream(writeFile);

            //3.一边读一边写
            byte[] b = new byte[1024];//数组中存放的是读取的数据
            int len = -1;
            while ((len = fis.read(b)) != -1) {//读
                //写
                fos.write(b, 0, len);
            }

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //4.关流
            try {
                if (fis != null) {
                    fis.close();
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            try {
                if (fos != null) {
                    fos.close();
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        long end = System.currentTimeMillis();
        System.out.println("time=" + (end -  start));


    }

13.6 字节缓冲流

BufferedInputStream
 /*
        BufferedInputStream
     */
    @Test
    public void test() throws Exception {
        //1.创建File类的对象
        File file = new File("a.txt");

        //2.创建流的对象
        //2.1创建节点流的对象
        FileInputStream fis = new FileInputStream(file);
        //2.2创建处理流的对象
        BufferedInputStream bis = new BufferedInputStream(fis);

        //3.读取数据
        byte[] b = new byte[1024];
        int len = -1;
        while ((len = bis.read(b)) != -1){
            System.out.println(new String(b,0,len));
        }

        //4.关流 :如果有处理流的情况下记住先关外面的再关里面的
        bis.close();
        fis.close();
    }
BufferedOutputStream
 @Test
    public void test2() throws Exception {
        //1.创建File类的对象
        File file = new File("b2.txt");
        //2.创建流的对象
        //2.1创建节点流的对象
        FileOutputStream fos = new FileOutputStream(file);
        //2.2创建处理流的对象
        BufferedOutputStream bos = new BufferedOutputStream(fos);

        //3.写数据
        bos.write("aaa".getBytes());

        bos.flush();//刷新 -- 将内存中的数据写到磁盘中(就相当于 ctrl+s)

        //4.关流 :如果有处理流的情况下记住先关外面的再关里面的
        bos.close();//关流中会flush
        fos.close();
    }
一边读一边写
  public void copy(String readPath,String writePath){
        long start = System.currentTimeMillis();

        FileInputStream fis = null;
        FileOutputStream fos = null;
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;

        try {
            //1.创建File类的对象
            File readFile = new File(readPath);
            File writeFile = new File(writePath);

            //2.创建流的对象
            fis = new FileInputStream(readFile);
            fos = new FileOutputStream(writeFile);

            //创建字节缓冲流
            bis = new BufferedInputStream(fis);
            bos = new BufferedOutputStream(fos);

            //3.一边读一边写
            byte[] b = new byte[1024];//数组中存放的是读取的数据
            int len = -1;
            while ((len = bis.read(b)) != -1) {//读
                //写
                bos.write(b, 0, len);
            }

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //先关外面再关里面
            if (bis != null){
                try {
                    bis.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }

            if (bos != null){
                try {
                    bos.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            //4.关流
            try {
                if (fis != null) {
                    fis.close();
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            try {
                if (fos != null) {
                    fos.close();
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        long end = System.currentTimeMillis();
        System.out.println("time=" + (end -  start));

    }

13.7 字符流-节点流

FileReader
/*
    用字符流copy
    1.copy文本是可以的  copy非文本文件会损坏
    	(因为有字符的编码解码过程 -- 非文本数据就不应该这么做)
    2.只要copy文件(无论是非是文本文件)都用字节流进行copy(因为没有字符的编码解码过程)
 */
    /*
    FileReader
     */
    @Test
    public void test() throws IOException {
        //1.创建流的对象
        FileReader fr = new FileReader("c.txt");//构造器中会创建File对象--将字符串转成File
        //2.读取数据
        char[] c = new char[1024];
        int len = -1;
        /*
        read(char[] c) : 将内容读取到char数组中 并返回读取的内容的长度 如果返回-1表示已经读完了
         */
        while ((len = fr.read(c)) != -1){
            System.out.println(new String(c,0,len));
        }

        //3.关流
        fr.close();
    }

FileWriter
    /*
    FileWriter
     */
    @Test
    public void test2() throws IOException {
        /*
        FileWriter(String fileName, boolean append)
        append : 追加还是覆盖(true追加 false覆盖)
         */
        //1.创建流的对象
        FileWriter fw = new FileWriter("c2.txt",true);
        //2.将数据写出去
        fw.write("中国我爱你");
        //3.关流
        fw.close();
    }

13.8 字符流-缓冲流

BufferedReader
    /*
     缓冲流
            输入流:BufferedReader
                1.提高读取数据的速度  2.可以一次读一行数据(非常重要 - 后面用的多)
            输出流:BufferedWriter
     */
    @Test
    public void test() throws Exception {
        //1.创建流的对象
        //创建节点流的对象
        FileReader fr = new FileReader("a.txt");
        //创建字符缓冲流的对象
        BufferedReader br = new BufferedReader(fr);

        //2.读取数据
        /*
        char[] c = new char[1024];
        int len = -1;
        while ((len = br.read(c)) != -1){
            System.out.println(new String(c,0,len));
        }
         */

        //String s = br.readLine();
        //一次读一行(因为大数据中文件中的数据大多数都是一行是一条数据)
        String s = "";
        while ((s = br.readLine()) != null){
            System.out.println(s);
        }


        //3.关闭资源
        br.close();
        fr.close();
    }
BufferedWriter
 /*
        BufferedWriter
     */
    @Test
    public void test2() throws IOException {
        //创建处理流的对象
        BufferedWriter bw = new BufferedWriter(new FileWriter("d.txt",true));
        //写数据
        bw.write("aaa");
        bw.flush();//刷新--将内存中的数据刷到磁盘中
        //关闭资源
        bw.close();//只关外面即可
    }

13.9 转换流

案例一:字节和字符的转换
 /*
    1.读取内容时可以将字节流转成字符流,写内容时可以将字符流转成字节流
     */
    @Test
    public void test() throws Exception {
        //1.创建流
        //创建节点流-字节流
        FileInputStream fis = new FileInputStream("a.txt");
        FileOutputStream fos = new FileOutputStream("a3.txt");
        //创建处理流-转换流
        InputStreamReader isr = new InputStreamReader(fis);//将字节流转成了字符流
        OutputStreamWriter osw = new OutputStreamWriter(fos);

        //2.一边读一边写
        char[] c = new char[1024];
        int len = -1;
        while ((len = isr.read(c)) != -1){//读 --- isr读的
            //写数据 - osw
            osw.write(c,0,len);
        }

        //3.关闭资源
        isr.close();
        osw.close();
        fis.close();
        fos.close();
    }
案例二 编码集的转换
 /*
     2.转换编码集(可将读取的内容的编码集(UTF-8)
     	在写到另一个文件时内容转换成另一种编码集(GBK))
        注意:读取的文件的编码集如果本身就是UTF-8那么在InputStreamReader中编码集只能是UTF-8

     */
    @Test
    public void test2() throws Exception {
        //1.创建流
        //创建节点流-字节流
        FileInputStream fis = new FileInputStream("a.txt");
        FileOutputStream fos = new FileOutputStream("a3.txt");
        //创建处理流-转换流
        InputStreamReader isr = new InputStreamReader(fis,"UTF-8");//将字节流转成了字符流
        OutputStreamWriter osw = new OutputStreamWriter(fos,"GBK");//写的编码集随意-必须有的编码集

        //2.一边读一边写
        char[] c = new char[1024];
        int len = -1;
        while ((len = isr.read(c)) != -1){//读 --- isr读的
            //写数据 - osw
            osw.write(c,0,len);
        }

        //3.关闭资源
        isr.close();
        osw.close();
        fis.close();
        fos.close();
    }

13.10 将字节流转成字符缓冲流

	//需要一行一行读取数据
    @Override
    public void read(InputStream is) {
        InputStreamReader isr = new InputStreamReader(is);//转成字符流
        //字符缓冲流-才可以一行一行读
        BufferedReader br = new BufferedReader(isr);
        //读取数据
        try {
            String s = br.readLine();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

13.11 对象流

/*
    序列化:将内存中的对象写到磁盘中
        如果对象要序列化或通过网络传输
    反序列化:将磁盘中对象恢复到内存中
        如果要读取磁盘上的对象或者通过网络接收对象那么就需要反序列化

    注意:如果对象要序列化那么对象所属性的类及类中的属性都必须实现Serializable接口。基本数据类型除外。
 */

public class Person implements Serializable {

    //当前类只要实现了Serializable接口 那么就算不声明serialVersionUID 会有一个默认的
    //自己指定的
    //private static final long serialVersionUID = 213211122221L;

    int age;
    transient String name;//该属性就不可以序列化 - 类变量不可以序列化
    Address address;


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

    @Override
    public String toString() {
        return name + " " + age + " " + address.toString();
    }
}

class Address implements Serializable{
    String phoneNumber;

    public Address(String phoneNumber){
        this.phoneNumber = phoneNumber;
    }

    @Override
    public String toString() {
        return phoneNumber;
    }
}
 /*
        ObjectOutputStream
     */
    @Test
    public void test() throws Exception {
        //1.创建流的对象
        ObjectOutputStream oos =
                new ObjectOutputStream(new FileOutputStream("aa.txt"));
        //2.创建对象
        Person p = new Person(20, "longge",new Address("13333333333"));
        //3.写数据
        oos.writeObject(p);
        //4.关闭资源
        oos.close();
    }
    /*
        ObjectInputStream

        反序列化时的错误:
            stream classdesc serialVersionUID = -1159203326177187056,
                local class serialVersionUID = 4819562835854727119
        发生错误的原因 :将对象序列化后存储的SUID值和反序列化后类中的SUID的值不匹配。
        如何解决此问题:
          1.SUID是如何变的 如何不让SUID的值发生变化?
            当自定义的类实现Serializable后会有默认SUID值这个值不会发生改变除非类发生改变。
            那么我们可以通过显示声明SUID的方式让SUID不变
            (本质上序列化对象所属性的类 在对象序列化后不要修改)。
          2.如何在反序列化时保证不失败(不发生上面的错误)?
                序列化时的SUID和反序列化时的SUID要保持一致
     */
    @Test
    public void test2() throws Exception {
        //1.创建流的对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("aa.txt"));
        //2.读数据
        Person p = (Person)ois.readObject();
        System.out.println(p.toString());
        //3.关闭资源
        ois.close();
    }

13.12 打印流

 /*
        PrintStream : 打印流
     */
    @Test
    public void test() throws FileNotFoundException {
        System.out.println("aaaa");

        System.out.println("========================================");
        PrintStream ps = new PrintStream("a.txt");
        ps.print("a");
        ps.print("b");
        ps.println("c");

        //关闭资源
        ps.close();
    }

/*
	给System.out重新赋值 - 让输出的内容从控制台变成输出到某个文件
*/
    @Test
    public void test2() throws FileNotFoundException {
        //创建打印流的对象
        PrintStream ps = new PrintStream("b.txt");
        //给out重新赋值
        System.setOut(ps);//注意:只能赋值一次
        //让内容输出到文件中
        System.out.println("aaaa");
        System.out.println("bbbb");
        System.out.println("cccc");
    }

13.13 标准输入输出流

System.in : 标准输入流
System.out : 标准输出流

13.14 JDK1.7以后try-catch的新格式

  	  try(
            //创建的流对象 --- 不需要去处理关闭流的操作了
            // (正常执行还是出现异常都会在最后(try-catch结束)的时候)关闭资源
            FileInputStream fis = new FileInputStream("a.txt");
         ){
            int i = fis.read();
            System.out.println(i);
        }catch (Exception e){
            e.printStackTrace();
        }

13.15 注意

编译时异常转运行时异常
		if (bis != null){
                try {
                    bis.close();
                } catch (IOException e) {
                    //让程序直接终止运行 --- 将编译时异常转换成运行时异常
                    throw new RuntimeException(e);
                }
            }

不要将多个关流的操作放在一个try-catch中
		 try {
                if (fos != null) {//一旦出异常下面的代码就不执行了
                    fos.close();
                }
                if (bos != null) {
                    bos.close();
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

第14章 网络编程

14.0 网络通信三要素

IP地址  端口号 通信协议

14.1 IP地址

IP地址:指互联网协议地址(Internet Protocol Address),俗称IPIP地址用来给一个网络中的计算机设备做唯一的编号。假如我们把“个人电脑”比作“一台电话”的话,那么“IP地址”就相当于“电话号码”。
    
IP地址的分类:
    IP地址长度 :IPV4 VS IPV6
    IP类型 :公网IP vs 私网IP192.168.xxx.xxx)
 
查看IP地址的命令: 命令提示符 -> ipconfig
    
测试两台电脑之间是否可以通信 -> ping ip地址或域名
    
本地回环地址(表示本机):127.0.0.1  (名字:localhost)

14.2 端口号

如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的进程(应用程序)了。

端口号:用两个字节表示的整数,它的取值范围是0~65535。
    
注意:如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败。

14.3 协议

UDP :用户数据报协议(User Datagram Protocol)1.它是非面向连接的,不可靠的无连接通信协议,数据的发送端和接收端不建立逻辑连接
    2.使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频
    3.由于UDP的面向无连接性,不能保证数据的完整性(可能会丢包)
    4.大小限制的:数据被限制在64kb以内,超出这个范围就不能发送了

TCP:传输控制协议 
    1.它是面向连接的,可靠的通信协议即传输数据之前,在发送端和接收端建立逻辑连接,
    	然后再传输数据
    2.UDP比起来TCP效率低
    3.需要三次握手
    	- 第一次握手,客户端向服务器端发出连接请求,等待服务器确认。
        - 第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求。
        - 第三次握手,客户端再次向服务器端发送确认信息,确认连接。

14.4 InetAddress

        InetAddress byName = InetAddress.getByName("www.baidu.com");
        System.out.println(byName.getHostAddress());//IP
        System.out.println(byName.getHostName());//名字

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

        InetAddress localHost = InetAddress.getLocalHost();//获取本机的IP和名字
        System.out.println(localHost.getHostAddress());//IP
        System.out.println(localHost.getHostName());//名字

14.5 基于TCP协议的Socket

案例一

客户端
/*
    客户端
 */
public class TCPClient {
    public static void main(String[] args) throws IOException {

        //创建对象
        /*
        "127.0.0.1" : 服务器的IP地址
        8899 : 服务器的端口号
         */
        Socket socket = new Socket("127.0.0.1", 8899);
        //使用流进行通信
        OutputStream os = socket.getOutputStream();
        //写数据
        os.write("abc".getBytes());
        //通知对方不写了
        socket.shutdownOutput();


        //接收服务器发过来的数据
        InputStream is = socket.getInputStream();
        byte[] b = new byte[1024];
        int len = -1;
        while ((len = is.read(b)) != -1){
            System.out.println(new String(b,0,len));
        }

        //关闭资源
        os.close();
        socket.close();
    }
}
服务器
/*
    服务器端

    先运行服务器
 */
public class TCPServer {
    public static void main(String[] args) throws IOException {
        //创建对象
        //8899 : 服务器的端口号
        ServerSocket ss = new ServerSocket(8899);

        while (true) {
            //接收请求
            Socket s = ss.accept();
            //输入流
            InputStream is = s.getInputStream();
            byte[] b = new byte[1024];
            int len = -1;
            while ((len = is.read(b)) != -1){
                System.out.println(new String(b,0,len));
            }

            //输出流
            OutputStream os = s.getOutputStream();
            os.write("shou dao!!!".getBytes());
            s.shutdownOutput();//通知客户端不写了

            //关闭资源
            is.close();
            os.close();
            s.close();
        }
    }
}

案例二(多客户端)

在这里插入图片描述

客户端
public class TCPClient {
    public static void main(String[] args) throws Exception {

        //===========创建客户端对象============================
        //创建客户端的对象
        Socket s = new Socket("127.0.0.1", 8899);

        //============创建写的流=========================
        //一行一行写
        PrintStream ps = new PrintStream(s.getOutputStream());
        //===========创建读取数据的流=====================
        //因为要一行一行读取数据
        BufferedReader br
                = new BufferedReader(new InputStreamReader(s.getInputStream()));


        //2.获取控制台数据并传给服务器
        //2.1获取控台数据
        Scanner scanner = new Scanner(System.in);
        while (true){//从控制台要读取很多次
            System.out.print("请输入内容:");
            String info = scanner.next();

            if ("exit".equals(info)){
                //关闭资源
                return;//main方法结束-程序就结束了
            }

            //2.2将数据写到服务器
            ps.println(info);//println方法中输出的内容最后是换行符

            //3读取服务器发送过来的数据
            String reverseStr = br.readLine();
            System.out.println(reverseStr);//写到控制台
        }
    }
}
服务器
public class TCPServer {
    public static void main(String[] args) throws Exception {
        //创建服务器端的对象
        ServerSocket ss = new ServerSocket(8899);
        //接收客户端请求
        while (true){
            Socket s = ss.accept();

            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        //接收数据
                        //======输入流==========
                        //一行一行读取数据(因为要一行一行读所以用字符缓冲流 但是通过socket获取的是字节流 所以要套转换流)
                        BufferedReader br
                                = new BufferedReader(new InputStreamReader(s.getInputStream()));
                        String str = "";


                        //======输出流==============
                        //写数据要用到PrintStream流
                        PrintStream ps = new PrintStream(s.getOutputStream());

                        while ((str = br.readLine()) != null){//======读取数据====
                            //============反转数据=============
                            //创建StringBuilder对象
                            StringBuilder sb = new StringBuilder(str);
                            //调用reverse方法反转数据
                            String reverse = sb.reverse().toString();
                            //===========写数据================
                            //返回数据
                            ps.println(reverse);
                        }

                    }catch (Exception e){
                        return;//如果发生异常线程结束(和客户端连接断开)
                    }
                }
            }).start();

        }
    }
}

14.6 基于UDP协议的Socket

发送端
public class UDPSend {
    public static void main(String[] args) throws Exception {
        //1.创建对象
        DatagramSocket ds = new DatagramSocket();


        //2.发送数据
        //创建数据包对象
        /*
         DatagramPacket(byte buf[], int offset, int length,InetAddress address, int port)
         buf[] : 数据
         offset : 数组的起始位置
         length : 数据的长度
         address :接收端的地址
         port : 接收端的端口号
         */
        //2.1创建数据包
        String str = "abcdef";
        DatagramPacket packet =
                new DatagramPacket(str.getBytes(), 0, str.length(),
                        InetAddress.getLocalHost(), 9988);
        //2.2发送数据
        ds.send(packet);


        //3.关闭资源
        ds.close();

    }
}
接收端
public class UDPReceive {
    public static void main(String[] args) throws Exception {

        //1.创建对象
        DatagramSocket ds = new DatagramSocket(9988);

        //2.接收数据
        //2.1创建数据包对象
        byte[] bs = new byte[1024];
        DatagramPacket dp = new DatagramPacket(bs, 0, bs.length);//接收的数据放在了数组中
        //2.2接收数据
        ds.receive(dp);

        //验证数据
        System.out.println(new String(bs,0,dp.getLength()));//dp.getLength() : 接收的数据长度

        //3.关闭资源
        ds.close();
    }
}

第15章 反射

15.1 类在内存的生命周期

加载-->使用-->卸载

15.2 类的加载过程

在这里插入图片描述

15.3 哪些操作会导致类的初始化

1)运行主方法所在的类,要先完成类初始化,再执行main方法
(2)第一次使用某个类型就是在new它的对象,此时这个类没有初始化的话,
    先完成类初始化再做实例初始化
(3)调用某个类的静态成员(类变量和类方法),此时这个类没有初始化的话,先完成类初始化
(4)子类初始化时,发现它的父类还没有初始化的话,那么先初始化父类
(5)通过反射操作某个类时,如果这个类没有初始化,也会导致该类先初始化
        Class.forName("com.atguigu.java6.SupA");

15.4 哪些操作不会导致类的初始化

1)使用某个类的静态的常量(static  final)
(2)通过子类调用父类的静态变量,静态方法,只会导致父类初始化,
    不会导致子类初始化,即只有声明静态成员的类才会初始化
(3)用某个类型声明数组并创建数组对象时,不会导致这个类初始化

15.5 获取运行时类的四种方法

/*
    获取Class对象的四种方式
    
    Class说明:
    	1.Class是反射的源头
    	2.当我们使用某一个类时会先加载该类的字节码文件。一个字节码文件对应一个Class对象。
    	3.加载到内存中的字节码文件就是一个Class对象
    	4.一个类的Classs对象在内存中只有一份
 */
    @Test
    public void test() throws ClassNotFoundException {
        Person p = new Person();

        //方式一:类名.class
        Class clazz = Person.class;

        //方式二:对象名.getClass();
        Class clazz2 = p.getClass();

        //方式三 :Class.forName("全类名") - 最多 - 更灵活
        Class clazz3 = Class.forName("com.atguigu.java4.Person");

        //方式四 :类加载器.loadClass("全类名") - 了解
        ClassLoader classLoader = this.getClass().getClassLoader();
        Class clazz4 = classLoader.loadClass("com.atguigu.java4.Person");


        System.out.println(clazz == clazz2 && clazz3 == clazz4);
    }

15.6 通过反射获取类中的结构

通过反射获取属性
public class ReflectTest {
    /*
        通过反射获取属性 - 获取所有属性
     */
    @Test
    public void test(){
        //获取Class对象
        Class clazz = Student.class;
        //获取属性---获取的是本类及父类中所有public修饰的属性
        Field[] fields = clazz.getFields();
        //遍历
        for (Field field : fields) {
            System.out.println(field);
        }

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

        //获取本类中所有属性
        Field[] declaredFields = clazz.getDeclaredFields();
        //遍历
        for (Field declaredField : declaredFields) {
            System.out.println(declaredField);
        }
    }

    /*
        通过反射获取属性 - 获取指定的属性
     */
    @Test
    public void test2() throws Exception {
        Student s = new Student();

        //1.获取Class对象
        Class clazz = Student.class;
        //2.获取指定属性 - public修饰的
        Field sidPublic = clazz.getField("sidPublic");
        //给属性赋值
        /*
        set(Object obj, Object value)
        obj : 给哪个对象中的该属性赋值
        value : 赋值的内容
         */
        sidPublic.set(s,20);//给s对象中的sidPublic赋值

        s.studentShow();
    }

    /*
     通过反射获取属性 - 获取指定的属性
  */
    @Test
    public void test3() throws Exception {
        Student s = new Student();

        //1.获取Class对象
        Class clazz = Student.class;
        //2.获取指定属性 - 可以获取任意权限修饰符修饰的属性
        Field sidPrivate = clazz.getDeclaredField("sidPrivate");
        //授权 - 允许访问
        sidPrivate.setAccessible(true);
        //给属性赋值
        /*
        set(Object obj, Object value)
        obj : 给哪个对象中的该属性赋值
        value : 赋值的内容
         */
        sidPrivate.set(s,30);//给s对象中的sidPrivate赋值

        s.studentShow();
    }


}
通过反射获取方法
ublic class ReflectTest2 {
     /*
        通过反射获取方法 - 获取所有的
     */
    @Test
    public void test(){
        //1.获取Class对象
        Class clazz = Student.class;
        //2.获取方法-获取本类及父类中所有public修饰的方法
        Method[] methods = clazz.getMethods();
        //3.遍历
        for (Method method : methods) {
            System.out.println(method);
        }

        System.out.println("======================================");
        //获取本类中所有的方法
        Method[] declaredMethods = clazz.getDeclaredMethods();
        //遍历
        for (Method declaredMethod : declaredMethods) {
            System.out.println(declaredMethod);
        }
    }
    /*
        通过反射获取方法 - 获取指定的方法
     */
    @Test
    public void test2() throws Exception {
        Student s = new Student();

        //1.获Class对象
        Class clazz = Student.class;
        //2.获取指定的方法 - public修饰的方法
        Method stestPublic = clazz.getMethod("stestPublic");
        //3.调用方法
        /*
            invoke(Object obj, Object... args)
            obj : 对象名-通过哪个对象调用该方法
            args : 实参
         */
        stestPublic.invoke(s);

        System.out.println("======================================");
        //获取指定的方法- public修饰的方法
        /*
        getMethod(String name, Class<?>... parameterTypes)
        name : 方法的名字
        parameterTypes : 形参类型的运行时类
         */
        Method sdemoPublic = clazz.getMethod("sdemoPublic", String.class,int.class);
        //调用方法
         /*
            invoke(Object obj, Object... args)
            obj : 对象名-通过哪个对象调用该方法
            args : 实参
         */
        sdemoPublic.invoke(s,"xiaolongge",30);

    }

    @Test
    public void test3() throws Exception {
        Student s = new Student();

        //1.获取Class对象
        Class clazz = Student.class;
        //2.获取指定的方法 - 任意权限修饰符修饰的方法
        Method declaredMethod = clazz.getDeclaredMethod("srunPrivate");
        //3.授权-允许访问
        declaredMethod.setAccessible(true);
        //4.调用方法
        declaredMethod.invoke(s);
    }



}
通过反射获取构造器
public class ReflectTest3 {
     /*
        通过反射获取构造器
     */
    @Test
    public void test() throws Exception {
        Class clazz = Employee.class;
        //获取构造器
        /*
        getDeclaredConstructor(Class<?>... parameterTypes) :获取指定构造器(任意权限修饰符修饰的)
        parameterTypes : 形参的类型
         */
        Constructor declaredConstructor = clazz.getDeclaredConstructor(int.class);
        //授权
        declaredConstructor.setAccessible(true);
        //创建对象
        /*
        newInstance(Object ... initargs) : 创建对象
        initargs :实参
         */
        Employee e = (Employee) declaredConstructor.newInstance(20);
    }




    
}
通过反射创建对象
    /*
        通过反射创建对象 :没有获取构造器直接造对象
     */
    @Test
    public void test2() throws InstantiationException, IllegalAccessException {
        Class clazz = Employee.class;
        //直接通过空参构造器造对象 --- 如果该类没有空参构造器就不能这样子造对象(需要先获取构造器再造对象)
        clazz.newInstance();
    }

通过反射获取父类和接口
/*
        通过反射获取父类,接口
     */
    @Test
    public void test3(){
        Class clazz = Employee.class;

        //获取父类 -- 有了父类就可以获取父类中的方法 属性 构造器 父类的父类
        Class superclass = clazz.getSuperclass();
        System.out.println(superclass);

        //获取接口
        Class[] interfaces = clazz.getInterfaces();
        for (Class anInterface : interfaces) {
            System.out.println(anInterface);
        }

    }
通过反射获取注解
@MyAnn2("com.atguigu.java8.Person")
public class Person {
    String name;
    int id;

    @MyAnn2("com.atguigu.java8.Address")
    Address address;
}

//==================================================================================
public class AnnotationTest {

    @Test
    public void test() throws NoSuchFieldException, NoSuchMethodException {

        Class clazz = Student.class;

        //拿的是类上的注解
        Annotation[] annotations = clazz.getAnnotations();
        System.out.println(Arrays.toString(annotations));

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

        //拿属性上的注解
        //获取那个属性
        Field id = clazz.getField("id");
        //获取属性上的注解
        Annotation[] annotations1 = id.getAnnotations();
        System.out.println(Arrays.toString(annotations1));

        System.out.println("====================================");
        //获取方法上的注解
        //获取方法
        Method show = clazz.getMethod("show");
        //获取方法上的注解
        Annotation[] annotations2 = show.getAnnotations();
        System.out.println(Arrays.toString(annotations2));
    }
}


15.7 类加载器的分类

在这里插入图片描述

双亲委派 :当类加载时应用程序类加载器先调用父类-扩展类加载器进行调用
    扩展类加载器再调用父类-引导类加载器加载
    引导类加载器只加载核心类库的类,如果该类是核心类库的类则加载否则不加载由儿子加载。
    扩展类加载器只加载jre/lib/ext中的类。如果该类是jre/lib/ext中的类则加载否则由儿子加载。
    应用程序类加载器直接加载。
public class ClassLoaderTest {

    @Test
    public void test() throws ClassNotFoundException {

        //获取类的运行时类的对象(Class对象)
        //自定义的类用的类加载器 - AppClassLoader(应用程序类加载器)
        Class clazz = Class.forName("com.atguigu.java6.A");
        ClassLoader classLoader = clazz.getClassLoader();
        System.out.println(classLoader);


        classLoader = classLoader.getParent();//ExtClassLoader(扩展类加载器)
        System.out.println(classLoader);


        classLoader = classLoader.getParent();
        System.out.println(classLoader);//null -- 引导类加载器(不是Java语言实现的)
    }

    @Test
    public void test2() throws ClassNotFoundException {

        //获取类的运行时类的对象(Class对象)
        //null -- 引导类加载器(不是Java语言实现的)
        Class clazz = Class.forName("java.lang.String");
        ClassLoader classLoader = clazz.getClassLoader();
        System.out.println(classLoader);
    }
}

15.8 注解

注解的概述
注解:注解用来对类中的一些结构进行补充说明 并不会改变原有的结构
系统中的注解
系统中的注解:
    @Override :用来说明当前方法是一个重写的方法
    @Deprecated : 用来说明当前结构(比如:方法,属性)已经过时
    @SuppressWarnings : 抑制警告(警告不是错误可以忽略)
自定义注解
格式 :
[元注解]
权限修饰符 @interface 注解名{
	["属性"]
}


说明:
    "属性" : 数据类型  属性名()  [default 默认值]
    注意:注解中一旦声明了"属性"那么在使用注解的时候必须给该"属性"赋值除非该"属性"有默认值
        
例:
    @interface MyAnn{
        String value() default "aaa";
    }


@MyAnn(value="aaa") //如果属性的名字叫作value那么在给value赋值时value可以省略不写
@MyANN"aaa";
class A{
    
}
元注解
元注解 :注解上面使用的注解(用来描述注解的注解)
    @Target :用来限制自定义的注解可以用在哪些结构上
    @Retention : 用来描述自定义类的注解的生命周期
        SOURCE :该注解只能用在源码上---字节码文件中就没有该注解了
        CLASS : 在编译阶段到运行阶段 --- 运行以后就没有该注解了(运行时拿不到此注解)
        RUNTIME : 在运行阶段 (如果想要通过反射获取此注解的信息--该注解必须是RUNTIME)
        注意:只要是自定义注解一定是RUNTIME
    //================================================================================
    @Documented:表明这个注解应该被 javadoc工具记录。
	@Inherited:允许子类继承父类中的注解

第16章 Java8新特性

16.1 案例

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

/*
    案例:演示
 */
public class LambdaTest {
    public static void main(String[] args) {

        List<Integer> list = new ArrayList<>();
        list.add(50);
        list.add(20);
        list.add(30);
        list.add(18);
        list.add(56);

        /*
        //需求:获取集合中大于等于20的值  并排序
        Iterator<Integer> iterator = list.iterator();
        while (iterator.hasNext()){
            Integer next = iterator.next();
            if (next < 20){
                iterator.remove();//要用迭代器去删除
            }
        }

        Collections.sort(list);
        System.out.println(list);
*/

        //StreamAPI结合Lambda表达式
        list.stream().filter(x -> x >= 20).sorted().map(x -> x + " ").forEach(System.out::print);
    }
}

16.2 Lambda表达式

import org.junit.Test;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/*
    lambda表达式 :对匿名内部类的对象的一种简写

    lambda表达式的格式 : (形参列表) -> {方法体}

    说明 :
        -> : lambda操作符

 */
interface MyInterface<T>{
    int test(T t1,T t2);
}

public class LambdaTest2 {


    @Test
    public void test3(){

        //创建匿名内部类的对象
        Comparator<Integer> c = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return 0;
            }
        };
        List<Integer> list = new ArrayList<>();
        Collections.sort(list,c);//传了一个list集合和匿名内部类的对象


        //===================

        Collections.sort(list, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return 0;
            }
        });

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

        //lambda表达式(本质就是匿名内部类的对象 - 只不过是对匿名内部类的对象的一种简写)
        Comparator<Integer> c2  = (Integer o1, Integer o2) -> {
            return 0;
        };

        Collections.sort(list,c2);

        //=====================

        Collections.sort(list,(Integer o1, Integer o2) -> {
            return 0;
        });

    }

    @Test
    public void test2(){
        //创建匿名内部类的对象
        MyInterface<Integer> mi = new MyInterface<Integer>() {
            @Override
            public int test(Integer t1, Integer t2) {
                return 0;
            }
        };

        //使用lambda表达式-创建匿名内部类的对象
        MyInterface<Integer> mi2 = (Integer t1, Integer t2) ->{
            return 0;
        };


        //=====================案例==================================
        demo((Integer t1, Integer t2) -> {
            return t1 - t2;
        });
    }
    /*
        演示 :匿名内部类的对象和lambda表达式的比较
     */
    @Test
    public void test(){
        //调用方法demo
        demo(new MyInterface<Integer>() {
            @Override
            public int test(Integer t1, Integer t2) {
                return t1 - t2;
            }
        });

        //使用lambda表达式
        demo((t1,t2) -> t1 -  t2);
    }




    public void demo(MyInterface<Integer> mi){

    }
}

16.3 Lambda表达式的简写

import org.junit.Test;

import java.util.Comparator;
import java.util.function.Consumer;
import java.util.function.Supplier;

public class LambdaTest3 {
    /*
     * 当{Lambda体}中只有一句语句时,可以省略{}和{;}
     * 当{Lambda体}中只有一句语句时,并且这个语句还是一个return语句,
     *      那么{、return、;}三者可以省略。它们三要么一起省略,要么都不省略。
     * 当Lambda表达式(形参列表)的类型已知,获取根据泛型规则可以自动推断,
     *      那么(形参列表)的数据类型可以省略。
     * 当Lambda表达式(形参列表)的形参个数只有一个,并且类型已知或可以自动推断,
     *      则形参的数据类型和()可以一起省略,但是形参名不能省略。
     * 当Lambda表达式(形参列表)是空参时,()不能省略
     */
    //如果lambda体只有一条执行语句 那么大括号和return都可以省略不写
    @Test
    public void test(){
        //匿名内部类的对象
        Comparator<Integer> c = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1 - o2;
            }
        };

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

        //如果lambda体只有一条执行语句 那么大括号和return都可以省略不写
        Comparator<Integer> c2 = (Integer o1, Integer o2) ->  o1 - o2;

    }

    /*
    如果lambda体中只有一条执行语句那么大括号可以省略不写
     */
    @Test
    public void test2(){
        //匿名内部类的对象
        Consumer<String> c = new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        };
        System.out.println("====================lamdba==================");

        //如果lambda体中只有一条执行语句那么大括号可以省略不写
        Consumer<String> c2 = (String s) -> System.out.println(s);

    }

    /*
    当Lambda表达式(形参列表)的类型已知,获取根据泛型规则可以自动推断,那么(形参列表)的数据类型可以省略。
     */
    @Test
    public void test3(){
        /*
        方法的形参是泛型的类型
        public interface Consumer<T> {
            void accept(T t);
         }
         */
        //匿名内部类的对象
        Consumer<String> c = new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        };

        //当Lambda表达式(形参列表)的类型已知,获取根据泛型规则可以自动推断,那么(形参列表)的数据类型可以省略。
        //lambda表达式
        Consumer<String> c2 = (s) -> System.out.println(s);


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

        Comparator<Integer> c3 = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1 - o2;
            }
        };
        //当Lambda表达式(形参列表)的类型已知,获取根据泛型规则可以自动推断,那么(形参列表)的数据类型可以省略。
        //lambda
        Comparator<Integer> c4 = (o1,o2) -> o1 - o2;

    }

    /*
    当Lambda表达式(形参列表)的形参个数只有一个,并且类型已知或可以自动推断,则形参的数据类型和()可以一起省略,但是形参名不能省略。
     */
    @Test
    public void test4(){
        //匿名内部类的对象
        Consumer<String> c = new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        };

        //lambda
        //当Lambda表达式(形参列表)的形参个数只有一个,并且类型已知或可以自动推断,则形参的数据类型和()可以一起省略,但是形参名不能省略。
        Consumer<String> c2 = s -> System.out.println(s);

    }

    /*
    当Lambda表达式(形参列表)是空参时,()不能省略
     */
    @Test
    public void test5(){
        //创建匿名内部类的对象
        Supplier<String> s = new Supplier<String>() {
            @Override
            public String get() {
                return "a";
            }
        };

        //lambda表达式
        //当Lambda表达式(形参列表)是空参时,()不能省略
        Supplier<String> s2 = () -> "a";

    }
}

16.4 函数式接口

初识
import org.junit.Test;

import java.util.Comparator;
import java.util.function.Consumer;
import java.util.function.Supplier;

/*
    函数式接口:只有一个抽象方法(如果有和Object类中一样的方法的抽象方法也可以)的接口叫作函数式接口

    说明:
        1.lambda表达式只能是函数式接口
        2.函数式接口中只能一个抽象方法(如果有和Object类中一样的方法的抽象方法也可以)
        3.函数式接口中可以有默认方法和静态方法(也可以有常量)
        4.可以在接口上使用注解@FunctionalInterface判断接口是否为函数式接口

 */
//下面的接口不能使用lambda表达式 因为不是一个函数式接口
//@FunctionalInterface - 报错因为不是函数式接口
interface MyInterface<T>{
    void say(T t,T t2);
    void show(T t);
}

@FunctionalInterface //用来说明该接口是函数式接口
interface MyInterface2{
    int ID = 10;

    void test();

    default void say(){

    }

    static void demo(){

    }

}

public class FunctionInterfaceTest {
    @Test
    public void test(){
        new Supplier<String>() {
            @Override
            public String get() {
                return null;
            }
        };

        new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {

            }
        };

        new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return 0;
            }
        };
    }
}

//因为A类的父类是Object类 而Object类中有equals方法 A继承了equals方法所以不用实现接口中的equals方法
class A implements Comparator<A>{
    @Override
    public int compare(A o1, A o2) {
        return 0;
    }
}

类型
import org.junit.Test;

import java.util.function.Function;
import java.util.function.IntConsumer;
import java.util.function.Predicate;
import java.util.function.Supplier;

public class FunctionInterfaceTest2 {

    /*
        消费型接口 : 没有返回值
     */
    @Test
    public void test(){
        new IntConsumer() {
            @Override
            public void accept(int value) {

            }
        };

        //lambda
        IntConsumer a =  value -> System.out.println(value);
    }

    /*
        供给型接口 : 有返回值但是没有形参列表
     */
    @Test
    public void test2(){
        new Supplier<String>() {
            @Override
            public String get() {
                return null;
            }
        };

        //lambda表达式
        Supplier<String>  s = () -> "a";
    }
    /*
        判断型接口 : 返回值类型为boolean 有形参
     */
    @Test
    public void test3(){
        new Predicate<Integer>() {
            @Override
            public boolean test(Integer a) {
                return a > 20;
            }
        };

        //lambda
        Predicate<Integer> p = a -> a > 20;
    }
    
    /*
        功能型接口 : 有返回值有形参
     */
    @Test
    public void test4(){
        new Function<String, Integer>() {
            @Override
            public Integer apply(String s) {
                return Integer.parseInt(s);
            }
        };

        //lambda表达式
        Function<String,Integer> f = s -> Integer.parseInt(s);
    }
}

16.5 Lambla表达式的引用

API
序号 语法格式 场景
1 实例对象名::实例方法 Lambda体是由Lambda表达式之外的某个实例对象调用实例方法完成的,并且Lambda表达式的形参正好依次按顺序作为该方法的实参
2 类名::静态方法 Lambda体是通过调用某个类的静态方法来完成的,并且Lambda表达式的形参正好依次按顺序作为该方法的实参
3 类名::实例方法 Lambda体是通过Lambda形参列表的第一个形参调用实例方法来完成的,并且其余形参正好依次按顺序作为该方法的实参
4 类名::new Lambda体是一个new表达式,并且Lambda表达式形参正好依次按顺序作为该构造器的实参
5 数组类型名::new Lambda体是一个创建数组对象的new表达式,Lambda表达式的形参正好作为数组的长度
案例之类的引用
/*
        类的引用:
         类名::new
     */
     public class LamblaTest{
	    @Test
	    public void test(){
	        //匿名内部类的对象
	        new Function<Integer, Person>() {
	            @Override
	            public Person apply(Integer integer) {
	                //1.new的对象正好是抽象方法的返回值类型  2.抽象方法的形参正好是构造器要传的实参
	                return new Person(integer);
	            }
	        };
	        //lambda表达式
	        Function<Integer, Person> f = i -> new Person(i);
	        //类的引用
	        Function<Integer, Person> f2 = Person::new;
	    }
	}
	class Person{
	    public Person(int a){
	        System.out.println("public Person(int a)");
	    }
	}
案例之数组的引用
/*
        数组的引用:
            数组的类型::new
     */
    @Test
    public void test2(){
        //匿名内部类的对象
        new Function<Integer, int[]>() {
            @Override
            public int[] apply(Integer integer) {
                //1.创建的数组正好是抽象方法的返回值类型  2.抽象方法的形参正好是数组的长度
                return new int[integer];
            }
        };

        //lambda表达式
        Function<Integer, int[]> f = i -> new int[i];
        //数组的引用
        Function<Integer, int[]> f2 = int[] :: new;
    }

16.6 Stream API

16.6.1 创建StreamAPI

创建 Stream方式一:通过集合

Java8 中的 Collection 接口被扩展,提供了两个获取流的方法:

  • public default Stream stream() : 返回一个顺序流
  • public default Stream parallelStream() : 返回一个并行流
创建 Stream方式二:通过数组

Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:

  • public static Stream stream(T[] array): 返回一个流

重载形式,能够处理对应基本类型的数组:

  • public static IntStream stream(int[] array):返回一个整型数据流
  • public static LongStream stream(long[] array):返回一个长整型数据流
  • public static DoubleStream stream(double[] array):返回一个浮点型数据流
创建 Stream方式三:通过Stream的of()

可以调用Stream类静态方法 of(), 通过显示值创建一个流。它可以接收任意数量的参数。

  • public static Stream of(T… values) : 返回一个顺序流
创建 Stream方式四:创建无限流

可以使用静态方法 Stream.iterate() 和 Stream.generate(), 创建无限流。

  • public static Stream iterate(final T seed, final UnaryOperator f):返回一个无限流
  • public static Stream generate(Supplier s) :返回一个无限流
案例
import org.junit.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.stream.Stream;

public class TestCreateStream {
    @Test
    public void test1(){
        ArrayList<String> list = new ArrayList<>();
        list.add("hello");
        list.add("java");
        list.add("hi");
        list.add("heihei");
        Stream<String> stream = list.stream();
    }

    @Test
    public void test2(){
        String[] arr = {"hello","world","java"};
        Stream<String> stream = Arrays.stream(arr);
    }

    @Test
    public void test3(){
        Stream<String> stringStream = Stream.of("hello", "world", "java");
    }

    @Test
    public void test4(){
        //Supplier<T> 的抽象方法  T get()
        Stream<Double> stream = Stream.generate(() -> Math.random());

        //结束Stream
        //Consumer<T> 的抽象方法  void accept(T t)
        stream.forEach(t-> System.out.println(t));
    }

    @Test
    public void test5(){
        //Stream<T> iterate(final T seed, final UnaryOperator<T> f)
        //seed:种子
        //UnaryOperator<T>: T apply(T t)
//       Stream<Integer> stream =  Stream.iterate(1, t -> t+2);
       Stream<Integer> stream =  Stream.iterate(1, t -> {
           try {
               Thread.sleep(10);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           return t+2;});

        //结束Stream
        //Consumer<T> 的抽象方法  void accept(T t)
        stream.forEach(t-> System.out.println(t));
    }
}

16.6.2 中间操作API

“惰性求值”

API
序号 方 法 描 述
1 Stream filter(Predicate p) 接收 Lambda , 从流中排除某些元素
2 Stream distinct() 筛选,通过流所生成元素的equals() 去除重复元素
3 Stream limit(long maxSize) 截断流,使其元素不超过给定数量
4 Stream skip(long n) 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
5 Stream peek(Consumer action) 接收Lambda,对流中的每个数据执行Lambda体操作
6 Stream sorted() 产生一个新流,其中按自然顺序排序
7 Stream sorted(Comparator com) 产生一个新流,其中按比较器顺序排序
8 Stream map(Function f) 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
9 Stream mapToDouble(ToDoubleFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream。
10 Stream mapToInt(ToIntFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream。
11 Stream mapToLong(ToLongFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream。
12 Stream flatMap(Function f) 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
案例
import org.junit.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;

/*
    中间操作
 */
public class StreamAPI2 {
    static List<Person> list = new ArrayList<>();
    static {
        list.add(new Person("aaa",2,100));
        list.add(new Person("longge",3,98));
        list.add(new Person("canglaoshi",9,60));
        list.add(new Person("hei",8,30));
        list.add(new Person("ha",6,59));
        list.add(new Person("hei",8,30));
        list.add(new Person("ha",6,59));
    }

    @Test
    public void test(){

        /*
        Stream<Person> stream = list.stream();

        //Stream filter(Predicate p)接收 Lambda , 从流中排除某些元素
        Stream<Person> strem2 = stream.filter(new Predicate<Person>() {
            @Override
            public boolean test(Person person) {//只要分数及格的
                return person.score >= 60;
            }
        });

        strem2.forEach(System.out::println);
         */

        list.stream().filter(p -> p.score >= 60).forEach(System.out::println);
    }

    /*
    Stream distinct()筛选,通过流所生成元素的equals() 去除重复元素
        注意:集合中的对象所属的类必须重写equasl和hashCode方法
     */
    @Test
    public void test2(){
        list.stream()
                .distinct() //重写equals和hashCode方法后是按照内容去重
                .forEach(System.out::println);
    }

    /*
    Stream limit(long maxSize)截断流,使其元素不超过给定数量
     */
    @Test
    public void test3(){
        list.stream()
                .distinct()
                .limit(2) //获取前2个元素
                .forEach(System.out::println);
    }

    /*
    Stream skip(long n)跳过元素,返回一个扔掉了前 n 个元素的流。
            若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
     */
    @Test
    public void test4(){
        int[] numbers = {1,2,3,4,5,6};
        Arrays.stream(numbers)
                .skip(3)
                .forEach(System.out::println);
    }

    /*
    Stream peek(Consumer action)接收Lambda,对流中的每个数据执行Lambda体操作
     */
    @Test
    public void test5(){
        list.stream()
                .peek(new Consumer<Person>() {
                    @Override
                    public void accept(Person person) {
                        System.out.println("=======" + person.toString());
                    }
                })
                .forEach(System.out::println);


        list.stream()
                .peek(p -> System.out.println(p))
                .forEach(System.out::println);
    }

    /*
        Stream sorted()产生一个新流,其中按自然顺序排序
     */
    @Test
    public void test6(){
        /*
        list.stream()
                //.sorted()//自然排序
                .sorted(new Comparator<Person>() {//定制排序
                    @Override
                    public int compare(Person o1, Person o2) {
                        return o1.score - o2.score;
                    }
                })
                .forEach(System.out::println);

         */

        list.stream()
                .sorted((o1,o2) -> o1.score - o2.score)
                .forEach(System.out::println);
    }

    /*
    Stream map(Function f)接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
     */
    @Test
    public void test7(){
        /*
        list.stream()
                .filter(new Predicate<Person>() {
                    @Override
                    public boolean test(Person person) {
                        return person.name.length() > 3;
                    }
                })
                .map(new Function<Person, Integer>() {
                    @Override
                    public Integer apply(Person person) {
                        return person.id;
                    }
                })
                .filter(new Predicate<Integer>() {
                    @Override
                    public boolean test(Integer integer) {
                        return integer > 5;
                    }
                })
                .forEach(System.out::println);

         */

        list.stream()
                .filter(p -> p.name.length() > 3)
                .map(p -> p.id)
                .filter(id -> id > 5)
                .forEach(System.out::println);
    }

    /*
    Stream flatMap(Function f)接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
     */
    @Test
    public void test8(){
        list.stream()
                .map(p -> p.name)
                .flatMap(new Function<String, Stream<?>>() {
                    @Override
                    public Stream<?> apply(String s) {
                        //"xiaocang" -> {x,i,a,o,c,a,n,g}
                        String[] split = s.split("");
                        //数组转成流
                        return Arrays.stream(split);
                    }
                })
                .forEach(System.out::println);
    }
}
import java.util.Objects;

public class Person implements Comparable<Person> {
    String name;
    int id;
    int score;

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

    @Override
    public String toString() {
        return id + " " + name + " " + score;
    }

    /*
        没有重写equals比的是对象的地址值
        重写equals比的是内容
     */
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return id == person.id && score == person.score && Objects.equals(name, person.name);
    }

    /*
        没有重写hashCode 获取的是对象的地址值
        重写hashCode 按照内容获取对应的哈希值(如果内容一样获取的哈希值是一样的)
     */
    @Override
    public int hashCode() {
        return Objects.hash(name, id, score);
    }

    @Override
    public int compareTo(Person o) {
        return this.id - o.id;
    }
}

终结操作API

终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void。流进行了终止操作后,不能再次使用。

API
序号 方法的返回值类型 方法 描述
1 boolean allMatch(Predicate p) 检查是否匹配所有元素
2 boolean anyMatch(Predicate p) 检查是否至少匹配一个元素
3 boolean noneMatch(Predicate p) 检查是否没有匹配所有元素
4 Optional findFirst() 返回第一个元素
5 Optional findAny() 返回当前流中的任意元素
6 long count() 返回流中元素总数
7 Optional max(Comparator c) 返回流中最大值
8 Optional min(Comparator c) 返回流中最小值
9 void forEach(Consumer c) 迭代
10 T reduce(T iden, BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回 T
11 U reduce(BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回 Optional
12 R collect(Collector c) 将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法
案例
import org.junit.Test;

import java.util.*;
import java.util.function.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/*
    终结操作API
 */
public class StreamAPI3 {
    static List<String> list = new ArrayList<>();
    static {
        list.add("aaa");
        list.add("eee");
        list.add("ddd");
        list.add("ccc");
        list.add("bbb");
    }

    /*
    boolean**allMatch(Predicate p)**检查是否匹配所有元素
     */
    @Test
    public void test(){
        boolean b = list.stream().allMatch(new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return s.equals("aaa");
            }
        });
        System.out.println(b);


        System.out.println( list.stream().allMatch(s -> s.equals("aaa")));
    }
    /*
           | boolean                     | **anyMatch**(**Predicate p**) | 检查是否至少匹配一个元素    |
| ------- | ----------------------------- | --------------------------- | ---------------------- |
| 3       | boolean                       | **noneMatch(Predicate  p)** | 检查是否没有匹配所有元素 |
| 4       | Optional<T>                   | **findFirst()**             | 返回第一个元素         |
| 5       | Optional<T>                   | **findAny()**               | 返回当前流中的任意元素 |
| 6       | long                          | **count()**                 | 返回流中元素总数       |
     */
    @Test
    public void test2(){
        System.out.println(list.stream().noneMatch(p -> p.equals("longge")));
        System.out.println(list.stream().anyMatch(p -> p.equals("ccc")));

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

        Optional<String> first = list.stream().findFirst();
        System.out.println(first.get());//first.get() : 获取Optional中的值

        Optional<String> any = list.stream().findAny();
        System.out.println(any.get());

        System.out.println(list.stream().count());
    }

    /*
    |      | Optional<T> | **max(Comparator c)**                | 返回流中最大值                                               |
| ---- | ----------- | ------------------------------------ | ------------------------------------------------------------ |
| 8    | Optional<T> | **min(Comparator c)**                | 返回流中最小值                                               |
| 9    | void        | **forEach(Consumer c)**              | 迭代                                                         |

     */
    @Test
    public void test3(){
        Optional<String> max = list.stream().max((o1, o2) -> o1.compareTo(o2));
        System.out.println(max.get());

        Optional<String> min = list.stream().min(String::compareTo);
        System.out.println(min.get());

    }

    /*
| 10   | T           | **reduce(T iden, BinaryOperator b)** | 可以将流中元素反复结合起来,得到一个值。返回 T               |
| 11   | U           | **reduce(BinaryOperator b)**         | 可以将流中元素反复结合起来,得到一个值。返回 Optional<T>     |

     */
    @Test
    public void test4(){
        int[] ns = {1,2,3,4,5};

        //有初始值
        int reduce = Arrays.stream(ns).reduce(0, new IntBinaryOperator() {
            @Override
            public int applyAsInt(int left, int right) {
                System.out.println("left:" + left + "====right:" + right);
                return left + right;
            }
        });

        System.out.println(reduce);

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

        //没有初始值
        OptionalInt reduce1 = Arrays.stream(ns).reduce(new IntBinaryOperator() {
            @Override
            public int applyAsInt(int left, int right) {
                return left + right;
            }
        });

        System.out.println(reduce1.getAsInt());
    }

    /*、
    collect(Collector c)** | 将流转换为其他形式。接收一个 Collector接口的实现,
            用于给Stream中元素做汇总的方法 |
     */
    @Test
    public void test5(){
        int[] ns = {1,2,3,4,5};

        IntStream stream = Arrays.stream(ns);
        Stream<Integer> boxed = stream.boxed();//转换成对应包装类的流 -- 调用此方法返回一个带有泛型的流
        //将流中的数据进行收集
        List<Integer> collect1 = boxed.collect(Collectors.toList());

        //将数组转换的Stream 用collect进行数据的收集
        List<Integer> collect = Arrays.stream(ns).boxed().collect(Collectors.toList());

        List<String> list = StreamAPI3.list.stream().collect(Collectors.toList());
        System.out.println(list);
    }
}

16.7 Optional类

Optional实际上是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。

API
序号 构造器或方法 描述
1 static Optional empty() 用来创建一个空的Optional
2 static Optional of(T value) 用来创建一个非空的Optional
3 static Optional ofNullable(T value) 用来创建一个可能是空,也可能非空的Optional
4 T get() 返回Optional容器中的对象。要求Optional容器必须非空。T get()与of(T value)使用是安全的
5 T orElse(T other) 如果Optional容器中非空,就返回所包装值,如果为空,就用orElse(T other)other指定的默认值(备胎)代替。一般orElse(T other) 与ofNullable(T value)配合使用
6 T orElseGet(Supplier<? extends T> other) 如果Optional容器中非空,就返回所包装值,如果为空,就用Supplier接口的Lambda表达式提供的值代替
7 T orElseThrow(Supplier<? extends X> exceptionSupplier) 如果Optional容器中非空,就返回所包装值,如果为空,就抛出你指定的异常类型代替原来的NoSuchElementException
8 boolean isPresent() 判断Optional容器中的值是否存在
9 void ifPresent(Consumer<? super T> consumer) 判断Optional容器中的值是否存在,如果存在,就对它进行Consumer指定的操作,如果不存在就不做
10 Optional map(Function<? super T,? extends U> mapper) 判断Optional容器中的值是否存在,如果存在,就对它进行Function接口指定的操作,如果不存在就不做
案例
import org.junit.Test;

import java.util.Optional;
import java.util.function.Supplier;

/*
    Optional类用来处理空指针的问题。Optional可以理解成是一个容器
 */
public class OptionalTest {

    @Test
    public void test(){
        String s = "abc";
        s = null;
        //创建Optional类的对象
        Optional<String> s1 = Optional.ofNullable(s);

        //获取s的值 --- 通过Optional获取
        //orElse() : 如果获取的值是null就返回默认值(ccc) 如果不为null就返回对应的值
        String s2 = s1.orElse("ccc");
        System.out.println(s2);

    }

    @Test
    public void test2(){
        String s = "abc";
        s = null;
        //创建Optional对象
        Optional<String> o = Optional.ofNullable(s);

        if (o.isPresent()){//isPresent() :判断容器中的值是否为null
            //get() : 直接获取optional中的数据 (注意:如果数据为null直接抛异常)
            String s2 = o.get();
            System.out.println(s2);
        }else{
            System.out.println("空指针");
        }

    }


    @Test
    public void test3(){
        String s = null;
        //创建Optional对象
        Optional<String> o = Optional.ofNullable(s);

        String s2 = o.orElseGet(new Supplier<String>() {
            @Override
            public String get() {
                //业务逻辑代码
                //比如 :如果为null 连接数据库  从数据库中获取对应的值  将值返回
                return "longge";
            }
        });

        System.out.println(s2);
    }

    @Test
    public void test4() throws Throwable {
        String s = null;
        //创建Optional对象
        Optional<String> o = Optional.ofNullable(s);

        //orElseThrow() : 如果数据不为null返回对应的值  为null抛异常
        String s2 = o.orElseThrow(new Supplier<Throwable>() {
            @Override
            public Throwable get() {
                return new RuntimeException("aaa");
            }
        });

        System.out.println(s2);
    }

    @Test
    public void test5(){
        String s = null;
        //创建Optional对象 -- 通过of(s)方法创建Optional对象无法处理空指针
        Optional<String> o = Optional.of(s);

        String s2 = o.orElse("ccc");
        System.out.println(s2);
    }
}