我是靠谱客的博主 机智白昼,最近开发中收集的这篇文章主要介绍JAVA基础及高级知识系统梳理面向对象——上面向对象——中面向对象——下JAVA高级部分JAVA常用类枚举类及注解集合,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

文章目录

  • 面向对象——上
    • 类结构——属性
    • 类结构——方法
    • 类结构——构造器
    • 封装性
    • Scanner类的使用
    • this关键字
    • import关键字
  • 面向对象——中
    • 继承性
    • 方法的重写
    • super关键字
    • 多态性
    • 包装类
  • 面向对象——下
    • static关键字
    • 单例模式
    • 代码块
    • final关键字
    • abstract关键字
    • interface关键字
    • 内部类
    • 异常
    • 匿名对象
  • JAVA高级部分
    • 多线程
  • JAVA常用类
  • 枚举类及注解
  • 集合
    • 泛型(不太懂)
    • IO流
    • 网络编程
    • 反射

面向对象——上

类结构——属性

1.对比:属性 vs 局部变量
相同点:
定义变量的格式:数据类型 变量名 = 变量值

先声明,后使用

变量都其对应的作用域

不同点:
在类中声明的位置的不同
属性:直接定义在类的一对{}内

局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内部的变量

关于权限修饰符的不同

属性:可以在声明属性时,指明其权限,使用权限修饰符。

局部变量:不可以使用权限修饰符。

默认初始化值的情况

属性:类的属性,根据其类型,都默认初始化值。

整型(byte、short、int、long:0)

浮点型(float、double:0.0)

字符型(char:0 (或’u0000’))

布尔型(boolean:false)

引用数据类型(类、数组、接口:null)

局部变量:没默认初始化值,意味着,我们在调用局部变量之前,一定要显式赋值,特别地:形参在调用时,我们赋值即可。

在内存中加载的位置

属性:加载到堆空间中 (非static)

局部变量:加载到栈空间
属性 = 成员变量 = field = 域、字段
方法 = 成员方法 = 函数 = method
2.属性赋值的先后顺序
默认初始化——显式初始化——构造器中初始化——对象.属性方式赋值——代码块
在这里插入图片描述

类结构——方法

1.方法的重载
1.1定义:在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
1.2总结:“两同一不同”:同一个类、相同方法名
参数列表不同:参数个数不同,参数类型不同

2.可变个数形参
① 可变个数形参的格式:数据类型 … 变量名
② 当调用可变个数形参的方法时,传入的参数个数可以是:0个,1个,2个…
③ 可变个数形参的方法与本类中方法名相同,形参不同的方法之间构成重载
④ 可变个数形参的方法与本类中方法名相同,形参类型也相同的数组之间不构成重载。换句话说,二者不能共存。
⑤ 可变个数形参在方法的形参中,必须声明在末尾
⑥ 可变个数形参在方法的形参中,最多只能声明一个可变形参。

类结构——构造器

1.构造器的作用
创建对象、初始化对象的信息

2.使用说明
①如果没显式的定义类的构造器的话,则系统默认提供一个空参的构造器
②定义构造器的格式:权限修饰符 类名(形参列表){}
③一个类中定义的多个构造器,彼此构成重载
④一旦我们显式的定义了类的构造器之后,系统就不再提供默认的空参构造器
⑤一个类中,至少会有一个构造器。

3.JavaBean
①类是公共的
②一个无参的公共的构造器
③属性,且对应的get、set方法

封装性

1.为什么要引入封装性?
①我们程序设计追求“高内聚,低耦合”。
②高内聚 :类的内部数据操作细节自己完成,不允许外部干涉;
③低耦合 :仅对外暴露少量的方法用于使用。
④隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。通俗的说,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。

2.封装性的几种体现
①将类的属性xxx私化(private),同时,提供公共的(public)方法来获取(getXxx)和设置(setXxx)此属性的值
②不对外暴露的私有的方法
③单例模式(将构造器私有化),分为懒汉式和饿汉式
④如果不希望类在包外被调用,可以将类设置为缺省的。

3.权限修饰符
3.1 权限从小到大顺序为:private < 缺省 < protected < public
3.2 特殊说明:4种权限都可以用来修饰类的内部结构:属性、方法、构造器、内部类,修饰类的话,只能使用:缺省、public

Scanner类的使用

Scanner作用:从键盘获取不同类型的变量

1.导包:import java.util.Scanner;
2.Scanner的实例化:Scanner scan = new Scanner(System.in);
3.调用Scanner类的相关方法(next() / nextXxx()),来获取指定类型的变量
注意:
需要根据相应的方法,来输入指定类型的值。如果输入的数据类型与要求的类型不匹配时,会报异常:InputMisMatchException
导致程序终止。

this关键字

当前对象或当前正在创建的对象
1.使用方法
①类的方法中,我们可以使用"this.属性"或"this.方法"的方式,调用当前对象属性或方法。但是,通常情况下,我们都择省略"this.“。特殊情况下,如果方法的形参和类的属性同名时,我们必须显式的使用"this.变量"的方式,表明此变量是属性,而非形参。
②在类的构造器中,我们可以使用"this.属性"或"this.方法"的方式,调用当前正在创建的对象属性或方法。但是,通常情况下,我们都择省略"this.”。特殊情况下,如果构造器的形参和类的属性同名时,我们必须显式的使用"this.变量"的方式,表明此变量是属性,而非形参。

2.注意事项
① 我们在类的构造器中,可以显式的使用"this(形参列表)"方式,调用本类中指定的其他构造器
② 构造器中不能通过"this(形参列表)“方式调用自己
③ 如果一个类中有n个构造器,则最多有 n - 1构造器中使用了"this(形参列表)”
④ 规定:"this(形参列表)“必须声明在当前构造器的首行
⑤ 构造器内部,最多只能声明一个"this(形参列表)”,用来调用其他的构造器

import关键字

1.使用说明
① 在源文件中显式的使用import结构导入指定包下的类、接口
②声明在包的声明和类的声明之间
③如果需要导入多个结构,则并列写出即可
④可以使用"xxx.*"的方式,表示可以导入xxx包下的所结构
⑤如果使用的类或接口是java.lang包下定义的,则可以省略import结构
⑥如果使用的类或接口是本包下定义的,则可以省略import结构
⑦如果在源文件中,使用了不同包下的同名的类,则必须至少一个类需要以全类名的方式显示。
⑧使用"xxx.*"方式表明可以调用xxx包下的所结构。但是如果使用的是xxx子包下的结构,则仍需要显式导入
⑨import static:导入指定类或接口中的静态结构:属性或方法。

面向对象——中

继承性

1.为什么有继承性
① 减少了代码的冗余,提高了代码的复用性
② 便于功能的扩展
③ 为之后多态性的使用,提供了前提

2.子类继承后的变化
①一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性和方法。特别的,父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私的结构。只因为封装性的影响,使得子类不能直接调用父类的结构而已。
②子类继承父类以后,还可以声明自己特有的属性或方法:实现功能的拓展。

3.继承的说明
①一个类可以被多个子类继承。
②Java中类的单继承性:一个类只能有一个父类
③子父类是相对的概念。
④子类直接继承的父类,称为:直接父类。间接继承的父类称为:间接父类
⑤子类继承父类以后,就获取了直接父类以及所间接父类中声明的属性和方法

4.Object类
①如果我们没显式的声明一个类的父类的话,则此类继承于java.lang.Object类
②所有的java类(除java.lang.Object类之外都直接或间接的继承于java.lang.Object类
③意味着,所的java类具有java.lang.Object类声明的功能。

方法的重写

1.定义:子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作,重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名同参数的方法时,实际执行的是子类重写父类的方法。

2.方法重写的规则
约定俗称:子类中的叫重写的方法,父类中的叫被重写的方法
①子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同
② 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符
特殊情况:子类不能重写父类中声明为private权限的方法
③ 返回值类型:
父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void
父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类
父类被重写的方法的返回值类型是基本数据类型(比如:double),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须也是double)
④ 子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型
⑤子类和父类中的同名同参数的方法要么都声明为非static的(考虑重写,要么都声明为static的(不是重写)。

super关键字

1.定义:理解为父类的xxx,可调用属性、方法和构造器

2.super调用属性、方法
① 我们可以在子类的方法或构造器中。通过使用"super.属性"或"super.方法"的方式,显式的调用父类中声明的属性或方法。但是,通常情况下,我们习惯省略"super."
② 特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须显式的使用"super.属性"的方式,表明调用的是父类中声明的属性。
③特殊情况:当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显式的使用"super.方法"的方式,表明调用的是父类中被重写的方法。

3.super调用构造器
①我们可以在子类的构造器中显式的使用"super(形参列表)"的方式,调用父类中声明的指定的构造器
② "super(形参列表)"的使用,必须声明在子类构造器的首行!
③ 我们在类的构造器中,针对于"this(形参列表)"或"super(形参列表)“只能二一,不能同时出现
④ 在构造器的首行,没显式的声明"this(形参列表)“或"super(形参列表)”,则默认调用的是父类中空参的构造器:super()
⑤在类的多个构造器中,至少一个类的构造器中使用了"super(形参列表)”,调用父类中的构造器

多态性

1.定义:父类的引用指向子类的对象(或子类的对象赋给父类的引用)

2.具体使用场景:有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法,总结:编译看左边,运行看右边

3.使用前提:①类的继承关系②方法的重写

4.向下转型:有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用,就有了向下转型。

5.注意事项
①对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)
②使用强转时,可能出现ClassCastException的异常,为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,先进行instanceof的判断,一旦返回true,就进行向下转型。如果返回false,不进行向下转型。

6.instanceof的使用
① a instanceof A:判断对象a是否是类A的实例。如果是,返回true;如果不是,返回false。
② 如果 a instanceof A返回true,则 a instanceof B也返回true.其中,类B是类A的父类。
③ 要求a所属的类与类A必须是子类和父类的关系,否则编译错误。
在这里插入图片描述

包装类

在这里插入图片描述

/**
 * @author Cserjie
 * @create 2022-03-23-9:13
 */
public class PacketClass {
    public static void main(String[] args) {
        //String类 → 基本数据类型:包装类的parsexxx方法
        String str = "123";
        System.out.println(Integer.parseInt(str));
        
        //String类 → 包装类:包装类的parsexxx方法
        String str1 = "true";
        Boolean b = Boolean.parseBoolean(str1);
        System.out.println(b.booleanValue());
        
        //基本数据类型 → 包装类:自动装箱
        Integer p = new Integer(123);
        System.out.println(p.intValue());
        
        //基本数据类型 →String
        int a = 99;
        String p = String.valueOf(a);
        
        //包装类 → 基本数据类型:自动拆箱
        Integer pp = new Integer(99);
        System.out.println(pp.intValue());
        
        //包装类 → String类:包装类的toString方法
        System.out.println(p.toString());
    }
}

面向对象——下

static关键字

1.可以修饰的结构:属性、方法、代码块、内部类

2.static修饰的属性(类变量、静态变量)
属性,是否使用static修饰,又分为:静态属性 vs 非静态属性(实例变量)

实例变量:我们创建了类的多个对象,每个对象都独立的拥一套类中的非静态属性。当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值的修改。
静态变量:我们创建了类的多个对象,多个对象共享同一个静态变量。当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改过了的。
static修饰属性的其他说明
① 静态变量随着类的加载而加载。可以通过"类.静态变量"的方式进行调用
② 静态变量的加载要早于对象的创建。
③ 由于类只会加载一次,则静态变量在内存中也只会存在一份:存在方法区的静态域中。
④ 类变量 实例变量
类 yes no
对象 yes yes
⑤在静态的方法内,不能使用this关键字、super关键字

3.static修饰方法(静态方法、类方法)
① 随着类的加载而加载,可以通过"类.静态方法"的方式进行调用
② 静态方法 非静态方法
类 yes no
对象 yes yes

静态方法中,只能调用静态的方法或属性,非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性

4.如何判定是否需要static?
关于属性:属性是可以被多个对象所共享的,不会随着对象的不同而不同的,类中的常量也常常声明为static

关于方法:操作静态属性的方法,通常设置为static的,工具类中的方法,习惯上声明为static的。 比如:Math、Arrays、Collections

单例模式

1.定义:所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例。

2.两种类型
饿汉式:它是线程安全的,因为instance被static修饰,使得在外部只会在类加载时创建一个对象,不会重复创建对象。
在这里插入图片描述
懒汉式:是线程不安全的
在这里插入图片描述

代码块

1.作用:用来初始化类、对象的信息,若用修饰符只能用static,在类中执行顺序:由父及子,静态先行。

2.静态代码块VS非静态代码块
静态代码块
内部可以输出语句
随着类的加载而执行,而且只执行一次
作用:初始化类的信息
如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行
静态代码块的执行要优先于非静态代码块的执行
静态代码块内只能调用静态的属性、静态的方法,不能调用非静态的结构

非静态代码块
内部可以输出语句
随着对象的创建而执行
每创建一个对象,就执行一次非静态代码块
作用:可以在创建对象时,对对象的属性等进行初始化
如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行
非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法

final关键字

1.定义:表示最终的,后面不能再改变的,可以用来修饰:类、方法、变量

2.具体情况
final 用来修饰一个类:此类不能被其他类所继承, 比如:String类、System类、StringBuffer类。
final 用来修饰方法:表明此方法不可以被重写 ,比如:Object类中getClass()。
final 用来修饰变量:此时的"变量"就称为是一个常量,若final修饰属性则可以考虑赋值的位置:显式初始化、代码块中初始化、构造器中初始化。
static final 用来修饰属性:全局常量。

abstract关键字

1.定义:抽象的,可以用来修饰:类、方法

2.具体情况
abstract修饰类:抽象类,此类不能实例化
抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程),开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作 。
抽象的使用前提:继承性

①abstract修饰方法:抽象方法
②抽象方法只有方法的声明,没方法体
③包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法的。
④若子类重写了父类中的所的抽象方法后,此子类方可实例化
⑤若子类没重写父类中的所的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰

3.注意点:
abstract不能用来修饰:

属性、构造器等结构
abstract不能用来修饰私方法、静态方法、final的方法、final的类

interface关键字

1.定义:接口与类为并列两个结构,子类对父类叫继承,而实现类对接口叫实现,接口中不能定义构造器,所以接口无法实例化

2.接口的理解与注意事项
Java开发中,接口通过让类去实现(implements)的方式来使用.
如果实现类覆盖了接口中的所抽象方法,则此实现类就可以实例化
如果实现类没覆盖接口中所的抽象方法,则此实现类仍为一个抽象类
Java类可以实现多个接口 —>弥补了Java单继承性的局限性
接口与接口之间可以继承,而且可以多继承
接口的具体使用,体现多态性
接口,实际上可以看做是一种规范

3.java8中关于接口的新规范
①接口中定义的静态方法,只能通过接口来调用。

②通过实现类的对象,可以调用接口中的默认方法,如果实现类重写了接口中的默认方法,调用时,调用的是重写以后的方法

③如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法,那么子类在没重写此方法的情况下,默认调用的是父类中的同名同参数的方法。–>类优先原则

④如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,那么在实现类没重写此方法的情况下,报错。–>接口冲突,这就需要我们必须在实现类中重写此方法

⑤如何在子类(或实现类)的方法中调用父类、接口中被重写的方法
在这里插入图片描述
4.接口的应用:代理模式

package com.atguigu.java1;
/*
 * 接口的应用:代理模式
 * 
 */
public class NetWorkTest {
   public static void main(String[] args) {
      Server server = new Server();
//    server.browse();
      ProxyServer proxyServer = new ProxyServer(server);
      
      proxyServer.browse();//
      
   }
}

interface NetWork{//接口中放入代理要帮被代理做的事
   
   public void browse();
   
}

//被代理类
class Server implements NetWork{//作为Network的实现类

   @Override
   public void browse() {
      System.out.println("真实的服务器访问网络");
   }

}
//代理类
class ProxyServer implements NetWork{
   
   private NetWork work;
   
   public ProxyServer(NetWork work){//Network work = new server(),使得代理类“指向了”被代理类,好为他做事
      this.work = work;
   }
   

   public void check(){//代理类额外添加的工作
      System.out.println("联网之前的检查工作");
   }
   
   @Override
   public void browse() {
      check();//做代理类自己额外的事
      
      work.browse();//帮被代理类做完他要做的事
      
   }
   
}

内部类

1.定义:Java中允许将一个类A声明在另一个类B中,则类A就是内部类,类B称为外部类。

2.分类:成员内部类(静态、非静态 ) vs 局部内部类(方法内、代码块内、构造器内)

3.成员内部类的理解
一方面,作为外部类的成员:
调用外部类的结构

可以被static修饰

可以被4种不同的权限修饰

另一方面,作为一个类:
类内可以定义属性、方法、构造器等

可以被final修饰,表示此类不能被继承。言外之意,不使用final,就可以被继承

可以被abstract修饰
4.如何创建成员内部类的对象
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

异常

1.异常类型
在这里插入图片描述
2.异常处理方式
2.1 try-catch模型处理:将可能发生异常的代码块用try-catch包住,一旦这些代码出现异常,则会被catch(前提是catch参数定义的异常范围要大),其次输出异常提醒,实际是将异常处理了

2.2 throws + 异常类型处理:抛"throws + 异常类型"写在方法的声明处,指明此方法执行时,可能会抛出的异常类型,一旦当方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足throws后异常类型时,就会被抛出。异常代码后续的代码,就不再执行,实际并没有处理,只是给上一级了

3.具体说明
finally是可选的。
使用try将可能出现异常代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,去catch中进行匹配一旦try中的异常对象匹配到某一个catch时,就进入catch中进行异常的处理。一旦处理完成,就跳出当前的try-catch结构(在没写finally的情况)。继续执行其后的代码
catch中的异常类型如果没子父类关系,则谁声明在上,谁声明在下无所谓。
catch中的异常类型如果满足子父类关系,则要求子类一定声明在父类的上面。否则,报错
常用的异常对象处理的方式: ① String getMessage() ② printStackTrace()
在try结构中声明的变量,再出了try结构以后,就不能再被调用
try-catch-finally结构可以嵌套

4.体会
使用try-catch-finally处理编译时异常,使得程序在编译时就不再报错,但是运行时仍可能报错。相当于我们使用try-catch-finally将一个编译时可能出现的异常,延迟到运行时出现
开发中,由于运行时异常比较常见,所以我们通常就不针对运行时异常编写try-catch-finally了。针对于编译时异常,我们说一定要考虑异常的处理。

5.finally的说明
finally是可选的
finally中声明的是一定会被执行的代码。即使catch中又出现异常了,try中return语句,catch中return语句等情况。
像数据库连接、输入输出流、网络编程Socket等资源,JVM是不能自动的回收的,我们需要自己手动的进行资源的释放。此时的资源释放,就需要声明在finally中。

6.如何选择两种异常处理方式
如果父类中被重写的方法没throws方式处理异常,则子类重写的方法也不能使用throws,意味着如果子类重写的方法中异常,必须使用try-catch-finally方式处理。
执行的方法a中,先后又调用了另外的几个方法,这几个方法是递进关系执行的。我们建议这几个方法使用throws的方式进行处理。而执行的方法a可以考虑使用try-catch-finally方式进行处理。
子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型

7.手动抛出异常:throw关键字,同时不要忘记方法处继承异常类

8.自定义异常类
继承于现的异常结构:RuntimeException 、Exception
提供全局常量:serialVersionUID
提供重载的构造器
在这里插入图片描述

匿名对象

//类的匿名子类的匿名对象
new Thread(){
    @Override
    public void run() {
        synchronized (o2){
            System.out.print("我讨厌");
            synchronized (o1){
                System.out.println("你!!!");
            }
        }
    }
}.start();
详解如上Thread类的匿名子类的匿名对象:
规范写法为;                            解释上述写法:
class A extend Thread{                        首先我不知道此时是哪个类继承了Thread类,但我一定知道有个类确实继承了
    public void run(){                因为它重写方法了,而这个匿名类就为A,而{}中则为被匿名的类A中的内容
            
    }
}
A a1 = new A1();
a1.start();
----------------------------------
//接口的匿名实现类的匿名对象
new Thread(new Runnable() {           
    @Override                            
    public void run() {
        synchronized (s2){

            s1.append("c");
            s2.append("3");

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            synchronized (s1){
                s1.append("d");
                s2.append("4");

                System.out.println(s1);
                System.out.println(s2);
            }


        }
    }
}).start();
规范写法:                                        解释如下:
class A implement Runnable{                    我最终是需要58行代码,而此时a1是一个实现了Runnable接口的实现类的
    public void run(){                        对象,而这个对象的创建方法又跟第3行代码类似了(将接口看做父类),创建出来的就是   
                                                该类的子类的对象或者该接口的实现类的对象
    }
}
A a1 = new A();
Thread t1 = new Thread(a1);

JAVA高级部分

多线程

1.常识
每个线程,拥有自己独立的:栈、程序计数器
多个线程,共享同一个进程中的结构:方法区、堆。
2.创建多线程的四种方式
2.1继承Thread类的方式
创建一个继承于Thread类的子类
重写Thread类的run() --> 将此线程执行的操作声明在run()中
创建Thread类的子类的对象
通过此对象调用start():①启动当前线程 ② 调用当前线程的run()

2.2实现Runnable接口的方式
创建一个实现了Runnable接口的类
实现类去实现Runnable中的抽象方法:run()
创建实现类的对象
将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
通过Thread类的对象调用start()

2.3实现callable接口
创建一个实现Callable的实现类
实现call方法,将此线程需要执行的操作声明在call()中
创建Callable接口实现类的对象
创建futuretask的对象,参数为callable接口实现类的对象
将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
(可选)若对call()返回值感兴趣,采用futuretask的get(),get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值,使用object类型变量接收,因为get()返回值为包装类类型

2.4线程池
提供指定线程数量的线程池
(可选)设置线程池的属性
执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
关闭连接池

3.注意事项
我们启动一个线程,必须调用start(),不能调用run()的方式启动线程。
如果再启动一个线程,必须重新创建一个Thread子类的对象,调用此对象的start().

4.Thread和Runnable方式的对比
4.1:相同点:两种方法都需要重写run方法,并将需要执行的逻辑放在run方法体内;都是通过调用Thread类中的start()方法来启动线程的(Runnable接口的方法是利用多态的特性最终达到调用Thread中的run方法的)
4.2:开发中:优先选择:实现Runnable接口的方式,原因如下:
实现的方式没类的单继承性的局限性
实现的方式更适合来处理多个线程共享数据的情况,因为只创建了一个对象(案例中的MyThread类的对象)

5.Thread类中的常用方法
start():启动当前线程;调用当前线程的run()
run(): 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
currentThread():静态方法,返回执行当前代码的线程
getName():获取当前线程的名字
setName():设置当前线程的名字
yield():释放当前cpu的执行权
join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态。
stop():已过时。当执行此方法时,强制结束当前线程。
sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前线程是阻塞状态。
isAlive():判断当前线程是否存活

6.线程优先级
MAX_PRIORITY:10
MIN _PRIORITY:1
NORM_PRIORITY:5 -->默认优先级
6.1 获取和设置当前线程的优先级
getPriority():获取线程的优先级
setPriority(int p):设置线程的优先级
说明:高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下被执行。并不意味着只当高优先级的线程执行完以后,低优先级的线程才执行。
额外知识:①线程通信:wait() / notify() / notifyAll() :此三个方法定义在Object类中的。
②线程的分类:一种是守护线程,一种是用户线程。

7.线程的生命周期
在这里插入图片描述
重点关注上图:
生命周期关注两个概念:状态、相应的方法
状态a–>状态b:哪些方法执行了(回调方法)
某个方法主动调用:状态a–>状态b
阻塞:临时状态,不可以作为最终状态
死亡:最终状态。

8.线程的同步机制
8.1 大意:类似于一个人上厕所的时候有另外一个人闯了进来,使得原来里面那个人“线程不安全了”,我们要做的就是避免这种情况发生,也就是当一个线程进入到目标代码块时,在此线程没执行完此代码块之前,其余线程不可进入(即使此线程在内发生阻塞等一系列行为),利用同步机制解决此问题。
8.2 几种方式
方式一:同步代码块

synchronized(同步监视器){
      //需要被同步的代码
 }
 说明:1.操作共享数据的代码,即为需要被同步的代码。  -->不能包含代码多了,也不能包含代码少了。
      2.共享数据:多个线程共同操作的变量。
      3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
      要求:多个线程必须要共用同一把锁。
      4.次此关键字只是解决不出现重票和错票,但完全有可能某个线程一直都“抢到了位置”,最后结果都是某个窗口卖票

方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。

关于同步方法的总结:
同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
非静态的同步方法,同步监视器是:this
静态的同步方法,同步监视器是:当前类本身

方式三:Lock锁

class Window implements Runnable{

    private int ticket = 100;
    //1.实例化ReentrantLock
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while(true){

                //2.调用锁定方法lock()
                lock.lock();

                if(ticket > 0){

                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket);
                    ticket--;
                }else{
                    break;
                }
                lock.unlock();

        }
    }
}

8.3注意事项
在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器。
在未见到关键字Synchronized前,可能会有多个线程进入,Synchronized相当于一扇门,这些线程被挡在门外

9.线程通信
9.1涉及的三个方法
wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。

9.2注意事项
如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
call()可以返回值的。
call()可以抛出异常,被外面的操作捕获,获取异常的信息
Callable是支持泛型的
wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。
否则,会出现IllegalMonitorStateException异常
wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中。
在这里插入图片描述
在这里插入图片描述

JAVA常用类

1.String类
1.1概述
String声明为final的,不可被继承,也就是不再需要功能的扩充了
String实现了Serializable接口:表示字符串是支持序列化的。
实现了Comparable接口:表示String可以比较大小
String内部定义了final char[] value用于存储字符串数据
通过字面量的方式(区别于new给一个字符串赋值,此时的字符串值声明在字符串常量池中)。
字符串常量池中是不会存储相同内容(使用String类的equals()比较,返回true)的字符串的。

1.2不可变性的说明
当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。
当对现的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。

1.3 String两种不同方式的实例化
方式一:通过字面量定义的方式

String s1 = "javaEE";
String s2 = "javaEE";
此时 s1 == s2返回true

方式二:通过new + 构造器的方式

String s3 = new String("javaEE");
String s4 = new String("javaEE");
此时s3 == s4 或者 s1 == s3均为false

在这里插入图片描述
1.4特殊说明
常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
只要其中一个是变量,结果就在堆中。
如果拼接的结果调用intern()方法,返回值就在常量池中
在这里插入图片描述
1.5 常用方法

int length():返回字符串的长度: return value.length
char charAt(int index): 返回某索引处的字符return value[index]
boolean isEmpty():判断是否是空字符串:return value.length == 0
String toLowerCase():使用默认语言环境,将 String 中的所字符转换为小写
String toUpperCase():使用默认语言环境,将 String 中的所字符转换为大写
String trim():返回字符串的副本,忽略前导空白和尾部空白
boolean equals(Object obj):比较字符串的内容是否相同
boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大小写
String concat(String str):将指定字符串连接到此字符串的结尾。 等价于用“+int compareTo(String anotherString):比较两个字符串的大小
String substring(int beginIndex):返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串。
String substring(int beginIndex, int endIndex) :返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。

boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始

boolean contains(CharSequence s):当且仅当此字符串包含指定的 char 值序列时,返回 true
int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引
int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引
int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索

注:indexOf和lastIndexOf方法如果未找到都是返回-1

替换:
String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所 oldChar 得到的。
String replace(CharSequence target, CharSequence replacement):使用指定的字面值替换序列替换此字符串所匹配字面值目标序列的子字符串。
String replaceAll(String regex, String replacement):使用给定的 replacement 替换此字符串所匹配给定的正则表达式的子字符串。
String replaceFirst(String regex, String replacement):使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
匹配:
boolean matches(String regex):告知此字符串是否匹配给定的正则表达式。
切片:
String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。
String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。

1.6 String与基本数据类型、包装类之间的转换
1.6.1 String 与 byte[]之间的转换
编码:String --> byte[]:调用String的getBytes()
解码:byte[] --> String:调用String的构造器
编码:字符串 -->字节 (看得懂 —>看不懂的二进制数据)
解码:编码的逆过程,字节 --> 字符串 (看不懂的二进制数据 —> 看得懂)
说明:解码时,要求解码使用的字符集必须与编码时使用的字符集一致,否则会出现乱码。

String str1 = "abc123中国";
byte[] bytes = str1.getBytes();//使用默认的字符集(UTF-8,每个汉字占3个字符),进行编码,byte中每个元素为对应字符的ASCII
System.out.println(Arrays.toString(bytes));

byte[] gbks = str1.getBytes("gbk");//使用gbk字符集进行编码(每个汉字占2个字节)
System.out.println(Arrays.toString(gbks));

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

String str2 = new String(bytes);//使用默认的字符集,进行解码。
System.out.println(str2);

String str3 = new String(gbks);
System.out.println(str3);//出现乱码。原因:编码集和解码集不一致!


String str4 = new String(gbks, "gbk");
System.out.println(str4);//没有出现乱码。原因:编码集和解码集一致!

1.6.2 String与char[] 之间的转换
String --> char[]:调用String的toCharArray()
char[] --> String:调用String的构造器

1.7 StringBuffer和StringBuilder
1.7.1 String、StringBuffer、StringBuilder三者的异同?
String:不可变的字符序列;底层使用char[]存储
StringBuffer:可变的字符序列;线程安全的,效率低;底层使用char[]存储
StringBuilder:可变的字符序列;jdk5.0新增的,线程不安全的,效率高;底层使用char[]存储

1.7.2 对比String、StringBuffer、StringBuilder三者的效率:
从高到低排列:StringBuilder > StringBuffer > String

1.7.3 StringBuffer常用方法(StringBuilder也是一样)

StringBuffer的常用方法:
StringBuffer append(xxx):提供了很多的append()方法,用于进行字符串拼接
StringBuffer delete(int start,int end):删除指定位置的内容
StringBuffer replace(int start, int end, String str):把[start,end)位置替换为str,注意是左闭右开区间
StringBuffer insert(int offset, xxx):在指定位置插入xxx
StringBuffer reverse() :把当前字符序列逆转
public int indexOf(String str)
public String substring(int start,int end):返回一个从start开始到end索引结束的左闭右开区间的子字符串
public int length()
public char charAt(int n )
public void setCharAt(int n ,char ch)

        总结:
        增:append(xxx)
        删:delete(int start,int end)
        改:setCharAt(int n ,char ch) / replace(int start, int end, String str)
        查:charAt(int n )
        插:insert(int offset, xxx)
        长度:length();
        *遍历:for() + charAt() / toString()

1.7.4 说明
StringBuffer和StringBuilder效率高的原因是因为可变的,例如:StringBuilder str = new(“abc”);此时底层分配的数组大小为3+16=19个,它会为用户预留16个位置,若后期不断通过append添加字符串超过16个位置时才会采取扩容,扩容大小为原数组大小*2+2,再将原数组元素复制到新数组,因此开发中尽量避免系统自动扩容,一般通过StringBuffer(int capacity) 或者StringBuilder(int capacity)一开始就制定好容量大小

2. Date类
2.1 java.util.Date下的Date
两个构造器的使用
构造器一:Date():创建一个对应当前时间的Date对象
构造器二:创建指定毫秒数的Date对象

//构造器一:Date():创建一个对应当前时间的Date对象
Date date1 = new Date();
System.out.println(date1.toString());//Sat Feb 16 16:35:31 GMT+08:00 2019

System.out.println(date1.getTime());//1550306204104

//构造器二:创建指定毫秒数的Date对象
Date date2 = new Date(155030620410L);
System.out.println(date2.toString());

两个方法的使用
toString():显示当前的年、月、日、时、分、秒
getTime():获取当前Date对象对应的毫秒数。(时间戳)
2.2 java.sql.Date下的Date:对应着数据库中的日期类型的变量

//创建java.sql.Date对象
        java.sql.Date date3 = new java.sql.Date(35235325345L);
        System.out.println(date3);//1971-02-13

        //如何将java.util.Date对象转换为java.sql.Date对象
        //情况一:
        Date date4 = new java.sql.Date(2343243242323L);
        java.sql.Date date5 = (java.sql.Date) date4;
//       //情况二:
        Date date6 = new Date();
        java.sql.Date Date7 = (java.sql.Date)date6;//肯定是错误的,这是错误的多态使用,Date6和Date7毫无关系,相当于前期的例子:Person转换成Woman,除非Woman是Person的子类才可以转换
        java.sql.Date date7 = new java.sql.Date(date6.getTime());

3.SimpleDateFormat类
3.1 SimpleDateFormat对日期Date类的格式化和解析
3.1.1默认构造器创建SimpleDateFormat对象

//实例化SimpleDateFormat:使用默认的构造器
SimpleDateFormat sdf = new SimpleDateFormat();

//格式化:日期 --->字符串
Date date = new Date();
System.out.println(date);
String format = sdf.format(date);
System.out.println(format);

//格式化:字符串 ---> 日期
String str = "19-12-18 上午11:43";//注意此时str必须为左边这样的格式,因为此时sdf用的默认构造器构造的,并不能使用我们想要的某种格式
Date date1 = sdf.parse(str);
System.out.println(date1);

3.1.2 按照指定的方式格式化和解析:调用带参的构造器

SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");//这次规定了字符串的格式,意味着解析时必须为此种格式才行
//格式化
String format1 = sdf1.format(date);
System.out.println(format1);//2019-02-18 11:48:27
//解析:要求字符串必须是符合SimpleDateFormat识别的格式(通过构造器参数体现),
Date date2 = sdf1.parse("2020-02-18 11:48:27");
System.out.println(date2);

4. calendr类(不建议用)
4.1计算某一天是当年第几天

String s = new String("2022-03-29");
SimpleDateFormat s1 = new SimpleDateFormat("yyyy-MM-dd");//使用SimpleDateFormat类
Date date2 = s1.parse(s);//将所需计算的时间转换成Date类型
calendar.setTime(date2);//设置系统calendar时间
System.out.println(calendar.get(Calendar.DAY_OF_YEAR));//得到结果,四步走战略

4.2LocalDate、LocalTime、LocalDateTime 的使用(类似calendar)

public void test1(){
    //now():获取当前的日期、时间、日期+时间
    LocalDate localDate = LocalDate.now();//获取当前年月日
    LocalTime localTime = LocalTime.now();//获取当前时分秒
    LocalDateTime localDateTime = LocalDateTime.now();//获取前两个的综合,用的较多

    System.out.println(localDate);
    System.out.println(localTime);
    System.out.println(localDateTime);

    //of():设置指定的年、月、日、时、分、秒。没有偏移量
    LocalDateTime localDateTime1 = LocalDateTime.of(2020, 10, 6, 13, 23, 43);
    System.out.println(localDateTime1);


    //getXxx():获取相关的属性
    System.out.println(localDateTime.getDayOfMonth());
    System.out.println(localDateTime.getDayOfWeek());
    System.out.println(localDateTime.getMonth());
    System.out.println(localDateTime.getMonthValue());
    System.out.println(localDateTime.getMinute());

    //体现不可变性
    //withXxx():设置相关的属性
    LocalDate localDate1 = localDate.withDayOfMonth(22);//此时底层的日期没有变,只是给你了你修改后的返回值而已
    System.out.println(localDate);
    System.out.println(localDate1);


    LocalDateTime localDateTime2 = localDateTime.withHour(4);
    System.out.println(localDateTime);
    System.out.println(localDateTime2);

    //不可变性
    LocalDateTime localDateTime3 = localDateTime.plusMonths(3);
    System.out.println(localDateTime);
    System.out.println(localDateTime3);

    LocalDateTime localDateTime4 = localDateTime.minusDays(6);
    System.out.println(localDateTime);
    System.out.println(localDateTime4);
}

5.instance类的使用(类似Date)

public void test2(){
    //now():获取本初子午线对应的标准时间
    Instant instant = Instant.now();
    System.out.println(instant);//2019-02-18T07:29:41.719Z,与本地(东八区)慢8小时

    //添加时间的偏移量,获取你所在位置的时区的时间
    OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
    System.out.println(offsetDateTime);//2019-02-18T15:32:50.611+08:00

    //toEpochMilli():获取自1970年1月1日0时0分0秒(UTC)开始的毫秒数  ---> Date类的getTime()
    long milli = instant.toEpochMilli();
    System.out.println(milli);

    //ofEpochMilli():通过给定的毫秒数,获取Instant实例  -->Date(long millis)
    Instant instant1 = Instant.ofEpochMilli(1550475314878L);
    System.out.println(instant1);
}

6.DateTimeFormatter类(类似SimpleDateFormat)

public void test3(){
            //三种实例化的方式
//        方式一:预定义的标准格式。如:ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME
        DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
        //格式化:日期-->字符串
        LocalDateTime localDateTime = LocalDateTime.now();
        String str1 = formatter.format(localDateTime);
        System.out.println(localDateTime);
        System.out.println(str1);//2019-02-18T15:42:18.797

        //解析:字符串 -->日期
        TemporalAccessor parse = formatter.parse("2019-02-18T15:42:18.797");
        System.out.println(parse);

//        方式二:
//        本地化相关的格式。如:ofLocalizedDateTime()
//        FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT :适用于LocalDateTime,这三种表示出来的格式不一样
        DateTimeFormatter formatter1 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);
        //格式化
        String str2 = formatter1.format(localDateTime);
        System.out.println(str2);//2019年2月18日 下午03时47分16秒


//      本地化相关的格式。如:ofLocalizedDate()
//      FormatStyle.FULL / FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT : 适用于LocalDate
        DateTimeFormatter formatter2 = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM);
        //格式化
        String str3 = formatter2.format(LocalDate.now());
        System.out.println(str3);//2019-2-18


//       重点: 方式三:自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)
        DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
        //格式化
        String str4 = formatter3.format(LocalDateTime.now());
        System.out.println(str4);//2019-02-18 03:52:09

        //解析
        TemporalAccessor accessor = formatter3.parse("2019-02-18 03:52:09");
        System.out.println(accessor);

    }

7.comparable和Comparator类的使用
7.1自然排序(comparable的应用)
7.1.1若对String类型数组进行排序,可直接调用Arrays.sort()方法即可

String[] arr = new String[]{"AA","CC","KK","MM","GG","JJ","DD"};
//
Arrays.sort(arr);//sort最底层使用了compareTo方法,具体看浏览器《java疑难》书签文章

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

7.1.2 若对自定义类型数组进行排序,需要先对自定义类中重写toString()和compareTo方法后在调用Arrays.sort()即可

//此方法所属类为GOODS
public int compareTo(Object o) {
        if(o instanceof Goods){
            Goods goods = (Goods)o;
            //方式一:
            if(this.price > goods.price){
                return 1;
            }else if(this.price < goods.price){
                return -1;
            }else{
//                return 0;
            //方式一进行二级排序
               return this.name.compareTo(goods.name);//二次排序:在有相同价格时再对name排序,默认升序,若要降序则在this前加‘-’
            }
            //方式二进行二级排序
//           return Double.compare(this.price,goods.price);
        }
//        return 0;
        throw new RuntimeException("传入的数据类型不一致!");
    }

7.2 定制排序(comparator的应用)

public void test3(){
        String[] arr = new String[]{"AA","CC","KK","MM","GG","JJ","DD"};
        Arrays.sort(arr,new Comparator(){

            //按照字符串从大到小的顺序排列
            @Override
            public int compare(Object o1, Object o2) {
                if(o1 instanceof String && o2 instanceof  String){
                    String s1 = (String) o1;
                    String s2 = (String) o2;
                    return -s1.compareTo(s2);
                }
//                return 0;
                throw new RuntimeException("输入的数据类型不一致");
            }
        });
        System.out.println(Arrays.toString(arr));
    }

    @Test
    public void test4(){
        com.atguigu.java.Goods[] arr = new com.atguigu.java.Goods[6];
        arr[0] = new com.atguigu.java.Goods("lenovoMouse",34);
        arr[1] = new com.atguigu.java.Goods("dellMouse",43);
        arr[2] = new com.atguigu.java.Goods("xiaomiMouse",12);
        arr[3] = new com.atguigu.java.Goods("huaweiMouse",65);
        arr[4] = new com.atguigu.java.Goods("huaweiMouse",224);
        arr[5] = new com.atguigu.java.Goods("microsoftMouse",43);

        Arrays.sort(arr, new Comparator() {
            //指明商品比较大小的方式:按照产品名称从低到高排序,再按照价格从高到低排序
            @Override
            public int compare(Object o1, Object o2) {
                if(o1 instanceof com.atguigu.java.Goods && o2 instanceof com.atguigu.java.Goods){
                    com.atguigu.java.Goods g1 = (com.atguigu.java.Goods)o1;
                    com.atguigu.java.Goods g2 = (com.atguigu.java.Goods)o2;
                    if(g1.getName().equals(g2.getName())){
                        return Double.compare(g1.getPrice(),g2.getPrice());
                    }else{
                        return g1.getName().compareTo(g2.getName());
                    }
                }
                throw new RuntimeException("输入的数据类型不一致");
            }
        });

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

7.3 compare与comparator的比较
二者其实几乎原理一样,只不过前者属于是直接对你需要比较大小的对象所属类(GOODS)内部进行了compareTo方法的重写,这样做的后果是,从此以后每次调用Sort()方法都会执行你规定的这种方式排序,具有永久性。而后者属于是在调用Sort()方法时在参数处采用comparator实现类的匿名对象的方式声明你需要以何种方式排序,这样做的后果是GOODS类内部的compareTo方法不会被执行,执行的是comparator实现类中你定义的排序方式,具有临时性特点,不是基于GOODS类内部的某一排序方法执行的操作

8.其他类(System、Math、BigInteger 和 BigDecimal等等)

枚举类及注解

1.枚举类
1.1 使用场景及说明
枚举类的理解:类的对象只有有限个,确定的。我们称此类为枚举类
当需要定义一组常量时,强烈建议使用枚举类
如果枚举类中只有一个对象,则可以作为单例模式的实现方式。

1.2定义枚举类
1.2.1 方法一(jdk5.0前)

//自定义枚举类
class Season{
    //1.声明Season对象的属性:private final修饰,因为对象都是常量,所以属性也设置为常量
    private final String seasonName;
    private final String seasonDesc;

    //2.私有化类的构造器,并给对象属性赋值,因为对象必须有限个,如果不私有化则在外部还可以创建无限多个对象
    private Season(String seasonName,String seasonDesc){
        this.seasonName = seasonName;
        this.seasonDesc = seasonDesc;
    }

    //3.提供当前枚举类的多个对象:public static final的,外部不能造对象所以这里设置为public好让外部使用这些对象
    public static final Season SPRING = new Season("春天","春暖花开");
    public static final Season SUMMER = new Season("夏天","夏日炎炎");
    public static final Season AUTUMN = new Season("秋天","秋高气爽");
    public static final Season WINTER = new Season("冬天","冰天雪地");

    //4.其他诉求1:获取枚举类对象的属性
    public String getSeasonName() {
        return seasonName;
    }

    public String getSeasonDesc() {
        return seasonDesc;
    }
    //4.其他诉求1:提供toString()
    @Override
    public String toString() {
        return "Season{" +
                "seasonName='" + seasonName + ''' +
                ", seasonDesc='" + seasonDesc + ''' +
                '}';
    }
}

1.2.2 方法二:使用Enum关键字定义枚举类

Enum Season{ //会默认继承java.lang.enum父类,若不重写toString方法,则输出常量的名称:SPRING
    //1.提供当前枚举类对象,对象之间用逗号隔开,这里替换了方式一的繁琐操作:public static final Season SPRING = new Season("春天","春暖花开");
    //其余不变
    SPRING("春天""春暖花开"),
    SUMMER("夏天""夏日炎炎");

    //2.私有化类的构造器,并给对象属性赋值,因为对象必须有限个,如果不私有化则在外部还可以创建无限多个对象
    private Season(String seasonName,String seasonDesc){
        this.seasonName = seasonName;
        this.seasonDesc = seasonDesc;
    }

    //3.其他诉求1:获取枚举类对象的属性
    public String getSeasonName() {
        return seasonName;
    }

    public String getSeasonDesc() {
        return seasonDesc;
    }
    //4.其他诉求1:提供toString()
    @Override
    public String toString() {
        return "Season{" +
                "seasonName='" + seasonName + ''' +
                ", seasonDesc='" + seasonDesc + ''' +
                '}';
    }
}

1.2.3 几种常用方法

//values():返回所有的枚举类对象构成的数组
Season1[] values = Season1.values();
for(int i = 0;i < values.length;i++){
    System.out.println(values[i]);
    //values[i].show();
}

//valueOf(String objName):返回枚举类中对象名是objName的对象。
        Season1 winter = Season1.valueOf("WINTER");
        //如果没有objName的枚举类对象,则抛异常:IllegalArgumentException
//        Season1 winter = Season1.valueOf("WINTER1");
        System.out.println(winter);

//toString方法:返回当前对象所属常量的名称

1.2.4 枚举类实现info接口

enum Season1 implements Info{
    //1.提供当前枚举类的对象,多个对象之间用","隔开,末尾对象";"结束
    SPRING("春天","春暖花开"){
        @Override
        public void show() {
            System.out.println("春天在哪里?");
        }
    },
    SUMMER("夏天","夏日炎炎"){
        @Override
        public void show() {
            System.out.println("宁夏");
        }
    },
    AUTUMN("秋天","秋高气爽"){
        @Override
        public void show() {
            System.out.println("秋天不回来");
        }
    },
    WINTER("冬天","冰天雪地"){
        @Override
        public void show() {
            System.out.println("大约在冬季");
        }
    };
    //这种实现接口的方式的好处是,每一个常量都有自己独有的show方法,而不是所有变量在调用show方法时输出的都一样

2.注解(没有理解)
2.1 注解的作用
jdk 5.0 新增的功能
Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。通过使用 Annotation, 程序员可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息。
在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/Android中注解占据了更重要

2.2 注解的使用
生成文档相关的注解
在编译时进行格式检查(JDK内置的三个基本注解)
@Override: 限定重写父类方法, 该注解只能用于方法,防止你以为你重写方法了,但其实没有重写,加了此标记后会提醒你并没有重写
@Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择
@SuppressWarnings: 抑制编译器警告,比如定义某个变量但你还没有使用过这个变量他就会有提示(idea中没有)
跟踪代码依赖性,实现替代配置文件功能

2.3 自定义注解
参照@SuppressWarnings定义
注解声明为:@interface
内部定义成员,通常使用value表示
可以指定成员的默认值,使用default定义
如果自定义注解没有成员,表明是一个标识作用。
注意事项
如果注解有成员,在使用注解时,需要指明成员的值。
自定义注解必须配上注解的信息处理流程(使用反射)才有意义。
自定义注解通过都会指明两个元注解:Retention、Target

2.4. jdk 提供的4种元注解
元注解:对现有的注解进行解释说明的注解
Retention:指定所修饰的 Annotation 的生命周期:SOURCECLASS(默认行为)RUNTIME,只有声明为RUNTIME生命周期的注解,才能通过反射获取。

Target:用于指定被修饰的 Annotation 能用于修饰哪些程序元素
Documented:表示所修饰的注解在被javadoc解析时,保留下来。

Inherited:被它修饰的 Annotation 将具有继承性。

2.5 通过反射获取注解信息 —到反射内容时系统讲解
2.6 jdk 8 中注解的新特性:可重复注解、类型注解
2.6.1 可重复注解
在MyAnnotation上声明@Repeatable,成员值为MyAnnotations.class
MyAnnotation的Target和Retention等元注解与MyAnnotations相同。
2.6.2 类型注解
ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中(如:泛型声明)。
ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。

集合

1.概述
集合、数组都是对多个数据进行存储操作的结构,简称Java容器。
说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt,.jpg,.avi,数据库中)。

2.传统数组的特点及缺点
特点:
一旦初始化以后,其长度就确定了。
数组一旦定义好,其元素的类型也就确定了。我们也就只能操作指定类型的数据了,比如:String[] arr;int[] arr1;Object[] arr2。
缺点:
一旦初始化以后,其长度就不可修改。
数组中提供的方法非常有限,对于添加、删除、插入数据等操作,非常不便,同时效率不高。
获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用
数组存储数据的特点:有序、可重复。对于无序、不可重复的需求,不能满足。

3.集合框架
3.1Collection接口:单列集合,用来存储一个一个的对象
3.1.1List接口:存储有序的、可重复的数据。 -->“动态”数组,为list长度是可以修改的
3.1.1.1ArrayList、LinkedList、Vector

3.1.2Set接口:存储无序的、不可重复的数据 -->高中讲的“集合”
3.1.2.1:HashSet、LinkedHashSet、TreeSet
3.2 Map接口:双列集合,用来存储一对(key - value)一对的数据 -->高中函数:y = f(x)
3.2.1:HashMap、LinkedHashMap、TreeMap、Hashtable、Properties
在这里插入图片描述
4.collection接口下方法

add(Object obj):往当前集合添加元素
addAll(Collection coll):将coll集合整体添加至当前集合
size():返回当前集合大小
isEmpty():返回当前集合是否为空
clear():清空集合元素
//以下方法均需要重写元素所在类的equals方法,否则比较的非元素本身的值,而是地址 
contains(Object obj):查询当前集合是否包含obj元素
containsAll(Collection coll):查询当前集合是否包含obj这个集合
remove(Object obj):删除obj元素
removeAll(Collection coll):删除obj集合
retainsAll(Collection coll):求当前集合和coll的交集,直接在当前集合上进行修改
equals(Object obj):判断两集合是否相等,顺序和大小都必须一样
hasCode():返回当前集合hash值
toArray():将当前集合转换为数组,返回这个数组
asList():将当前数组转换为集合,特别的
List arr1 = Arrays.asList(new int[]{123, 456});
System.out.println(arr1.size());//最终集合元素只有1,原因是将123,456看成一个元素了,因为int非引用类型,应采取如下方法
List arr2 = Arrays.asList(new Integer[]{123, 456});
System.out.println(arr2.size());//输出2
iterator():涉及集合的遍历
    遍历操作
    Iterator iterator = coll.iterator();
            //方式一:
    //        System.out.println(iterator.next());
    //        System.out.println(iterator.next());
    //        System.out.println(iterator.next());
    //        System.out.println(iterator.next());
    //        System.out.println(iterator.next());
    //        //报异常:NoSuchElementException
    //        System.out.println(iterator.next());
    
            //方式二:不推荐
    //        for(int i = 0;i < coll.size();i++){
    //            System.out.println(iterator.next());
    //        }
    
            //方式三:推荐
            hasNext():判断是否还有下一个元素
            while(iterator.hasNext()){ //hasnext方法将查看后一位是否有元素,但指针不会移动
                //next():①指针下移 ②将下移以后集合位置上的元素返回
                System.out.println(iterator.next());//输出下一位的元素,搭配hasnext使用
            }
            
            //错误方式一:
    //        Iterator iterator = coll.iterator();
    //        while((iterator.next()) != null){
    //            System.out.println(iterator.next());
    //        }
    
            //错误方式二:
            //集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
            while (coll.iterator().hasNext()){
                System.out.println(coll.iterator().next());
            }
    删除指定元素操作
    Iterator iterator = coll.iterator();
        while (iterator.hasNext()){
//            iterator.remove();
            Object obj = iterator.next();
            if("Tom".equals(obj)){
                iterator.remove();
//                iterator.remove();
            }

        }
增强for循环
//for(集合元素的类型 局部变量 : 集合对象)
//内部仍然调用了迭代器。
for(Object obj : coll){
    System.out.println(obj);
}
 //笔试题
 public void test3(){

        String[] arr = new String[]{"MM","MM","MM"};

//        //方式一:普通for赋值,会输出三个GG,因为arr[i]是一个个对象,这里相当于让arr[i]重新指向一个堆中变量,这个变量在指向常量池GG
//        for(int i = 0;i < arr.length;i++){
//            arr[i] = "GG";
//        }

        //方式二:增强for循环,会输出三个MM,因为这里是把arr[i]的值拿出来赋给s,相当于String s = "MM",而数组元素未变
        for(String s : arr){
            s = "GG";
        }

        for(int i = 0;i < arr.length;i++){
            System.out.println(arr[i]);
        }


    }

5.List接口:存储有序的、可重复的数据。 →“动态”数组,替换原有的数组
5.1ArrayList实现类:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[] elementData存储

2. ArrayList的源码分析:
*   2.1 jdk 7情况下
*      ArrayList list = new ArrayList();//底层创建了长度是10的Object[]数组elementData
*      list.add(123);//elementData[0] = new Integer(123);
*      ...
*      list.add(11);//如果此次的添加导致底层elementData数组容量不够,则扩容。
*      默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
*
*      结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)
*
*   2.2 jdk 8ArrayList的变化:
*      ArrayList list = new ArrayList();//底层Object[] elementData初始化为{}.并没有创建长度为10的数组
*
*      list.add(123);//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0]
*      ...
*      后续的添加和扩容操作与jdk 7 无异。
*   2.3 小结:jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象
*            的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。
    2.4 排序
        方法一:使用Collections.sort(),并重写自定义类的compareTo(),或者Collections.sort()中指定compator排序
        方法二:list(),参数填入compator,定制排序

5.2 LinkedList实现类:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储

LinkedList list = new LinkedList(); 内部声明了Node类型的first和last属性,默认值为null
*      list.add(123);//将123封装到Node中,创建了Node对象。
*
*      其中,Node定义为:体现了LinkedList的双向链表的说法
*      private static class Node<E> {
            E item;
            Node<E> next;
            Node<E> prev;

            Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
            }
        }

5.3 vector实现类:作为List接口的古老实现类;线程安全的,效率低;底层使用Object[] elementData存储

Vector的源码分析:jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。
 在扩容方面,默认扩容为原来的数组长度的2倍。

5.4 List接口常用方法

void add(int index, Object ele):在index位置插入ele元素
boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
Object get(int index):获取指定index位置的元素
int indexOf(Object obj):返回obj在集合中首次出现的位置
int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
Object remove(int index):移除指定index位置的元素,并返回此元素
Object set(int index, Object ele):设置指定index位置的元素为ele
List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合

总结:常用方法
增:add(Object obj)
删:remove(int index) / remove(Object obj),这个是collection父接口中的,只有List子接口才有索引之说
改:set(int index, Object ele)
查:get(int index)
插:add(int index, Object ele)
长度:size()
遍历:① Iterator迭代器方式
     ② 增强for循环
     ③ 普通的循环

6.Set接口:存储无序的、不可重复的数据 -->高中讲的“集合”,除了只有TreeSet可以排序,HashSet可以通过转为List或者转为TreeSet进行排序
无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的。
不可重复性:保证添加的元素按照equals()判断时,不能返回true.即:相同的元素只能添加一个。
6.1 HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值,需要重写hashcode()和equals()

添加元素的过程:以HashSet为例:
    我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,
    此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置),判断
    数组此位置上是否已经有元素:
        如果此位置上没有其他元素,则元素a添加成功。 --->情况1
        如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
            如果hash值不相同,则元素a添加成功。--->情况2
            如果hash值相同,进而需要调用元素a所在类的equals()方法:
                   equals()返回true,元素a添加失败
                   equals()返回false,则元素a添加成功。--->情况2

    对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储。
    jdk 7 :元素a放到数组中,指向原来的元素。
    jdk 8 :原来的元素在数组中,指向元素a
    总结:七上八下

    HashSet底层:数组+链表的结构。

6.2 LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历,对于频繁的遍历操作(只针对遍历提供了方便而已)有益,LinkedHashSet效率高于HashSet,需要重写hashcode()和equals()

6.3 TreeSet:可以按照添加对象的指定属性,进行排序,不需要重写hashcode()和equals(),向TreeSet中添加的数据,要求是相同类的对象,并且添加后自动升序排序了.
两种排序方式:自然排序(实现Comparable接口) 和 定制排序(Comparator),自然排序中,比较两个对象是否相同的标准为:compareTo()返回0.不再是equals(),add方法默认调用compareTo方法(在没有comparator的前提下)定制排序中,比较两个对象是否相同的标准为:compare()返回0.不再是equals()。

6.4 Set接口要求

 1.Set接口中没有额外定义新的方法,使用的都是Collection中声明过的方法。
 2. 要求:向Set(主要指:HashSetLinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()equals()
 为什么有上面这个要求?
    答:因为如果不重写hashcode方法,则会调用object中的hashcode方法,而object中的这个方法返回的hash值是一个随机数,这将导致明明两个
    数据一样,但因为这个随机的hash值,将导致二者都被存储进数组了,所以我们需要重写hashcode,使得保证只有当这两个元素的属性完全不同
    时才会算出不同的hash值,这样就大大减少了存入重复数据的概率了
 3.要求:重写的hashCode()equals()尽可能保持一致性:相等的对象必须具有相等的散列码
 4.重写两个方法的小技巧:对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。

7.Map接口
Map:双列数据,存储key-value对的数据 —类似于高中的函数:y = f(x)
HashMap:作为Map的主要实现类;线程不安全的,效率高;存储null的key和value
LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历,原因是在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。
对于频繁的遍历操作,此类执行效率高于HashMap。
TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序,底层使用红黑树
Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value
Properties:常用来处理配置文件。key和value都是String类型

HashMap的底层:数组+链表 (jdk7及之前)
数组+链表+红黑树 (jdk 8)

7.1 底层存储方式
Map中的key:无序的、不可重复的,使用Set存储所的key —> key所在的类要重写equals()和hashCode() (以HashMap为例)
Map中的value:无序的、可重复的,使用Collection存储所的value —>value所在的类要重写equals(),不用重写hashcode()的原因是比较value时一定是当两元素key的hash值一样时才会比较value,具体看下面底层解释
一个键值对:key-value构成了一个Entry对象;
Map中的entry:无序的、不可重复的,使用Set存储所的entry

7.2 常用方法

* 添加:put(Object key,Object value)
* 删除:remove(Object key)
* 修改:put(Object key,Object value)
* 查询:get(Object key)
* 长度:size()
* 遍历:keySet() / values() / entrySet()

添加、删除、修改操作:
Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
void putAll(Map m):将m中的所有key-value对存放到当前map中
Object remove(Object key):移除指定key的key-value对,并返回value
void clear():清空当前map中的所有数据

元素查询的操作:
Object get(Object key):获取指定key对应的value
boolean containsKey(Object key):是否包含指定的key
boolean containsValue(Object value):是否包含指定的value
int size():返回map中key-value对的个数
boolean isEmpty():判断当前map是否为空
boolean equals(Object obj):判断当前map和参数对象obj是否相等
元视图操作的方法:
Set keySet():返回所有key构成的Set集合
Collection values():返回所有value构成的Collection集合
Set entrySet():返回所有key-value对构成的Set集合

public void test1(){
    Map map = new HashMap();
    map.put("柴朕杰",22);
    map.put("韩玉",48);
    map.put("柴红江",52);
    System.out.println(map);
    
    Map map1 = new HashMap();
    map1.putAll(map);
    System.out.println(map1);
    
    map1.remove("柴朕杰");
    System.out.println(map1);
    
    map1.put("柴红江",50);//会覆盖前面的柴红江 52
    map1.put("柴进",50);
    System.out.println(map1);
    
    Object c = map1.get("柴红江");
    System.out.println(c);//返回对应key值的value
    System.out.println("***********");
    
    Set keySet = map1.keySet();
    System.out.println(keySet);//返回所有key值
    Collection collection = map1.values();
    System.out.println(collection);//返回所有value值
    Set set = map1.entrySet();
    System.out.println(set);//返回所有键值对
    System.out.println("#####################");

    //遍历所有key
    Set set1 = map1.keySet();
    Iterator iterator = set1.iterator();
    while (iterator.hasNext()){
        System.out.println(iterator.next());
    }

    //遍历所有value:法一
    Collection collection1 = map1.values();
    Iterator iterator1 = collection1.iterator();
    while (iterator1.hasNext()){
        System.out.println(iterator1.next());
    }
    //法二
    Collection collection2 = map1.values();
    for (Object obj:collection2) {
        System.out.println(obj);
    }
    System.out.println("%%%%%%%%%%%%%%%%%%%%%%%%%");
    //遍历所有键值对:法一
    Set set2 = map1.entrySet();
    Iterator iterator2 = set2.iterator();
    while (iterator2.hasNext()){
        Object next = iterator2.next();//迭代器返回的是下一个元素,不确定是什么类型元素,但一定是object子类类型的,多态
        Map.Entry entry = (Map.Entry)next;//下一个元素一定是entry类型的,这里也可以不强制转换
        Object key = entry.getKey();
        Object value = entry.getValue();
        System.out.println(key + "," + value);
    }
    //法二
    Set set3 = map1.entrySet();
    for (Object obj1: set3) {
        System.out.println(set3);
    }
    System.out.println("&&&&&&&&&&&");
    //法三
    Set set4 = map1.keySet();
    Iterator iterator3 = set4.iterator();
    while (iterator3.hasNext()){
        Object key = iterator3.next();
        Object values = map1.get(key);
        System.out.println("key:" + key + "-----------values:" + values);
    }
}

7.3 内存结构说明

HashMap在jdk7中实现原理:
HashMap map = new HashMap():
*      在实例化以后,底层创建了长度是16的一维数组Entry[] table。
*      ...可能已经执行过多次put...
*      map.put(key1,value1):
*      首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。
*      如果此位置上的数据为空,此时的key1-value1添加成功。 ----情况1
*      如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:
*              如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。----情况2
*              如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)方法,比较:
*                      如果equals()返回false:此时key1-value1添加成功。----情况3
*                      如果equals()返回true:使用value1替换value2。
*
*      补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。
*
*     在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原的数据复制过来。
HashMap在jdk8中相较于jdk7在底层实现方面的不同:
1. new HashMap():底层没创建一个长度为16的数组
2. jdk 8底层的数组是:Node[],而非Entry[]
3. 首次调用put()方法时,底层创建长度为16的数组
4. jdk7底层结构只:数组+链表。jdk8中底层结构:数组+链表+红黑树。
4.1 形成链表时,七上八下(jdk7:新的元素指向旧的元素。jdk8:旧的元素指向新的元素)
4.2 当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的所数据改为使用红黑树存储。

HashMap底层典型属性的属性的说明:
DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75
threshold:扩容的临界值,=容量*填充因子:16 * 0.75 => 12
TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8
MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64
4.4 LinkedHashMap的底层实现原理(了解)
LinkedHashMap底层使用的结构与HashMap相同,因为LinkedHashMap继承于HashMap.
区别就在于:LinkedHashMap内部提供了Entry,替换HashMap中的Node.

在这里插入图片描述
7.4 TreeMap的使用
向TreeMap中添加key-value,要求key必须是由同一个类创建的对象
因为要照key进行排序:自然排序 、定制排序,对于自定义类对象可以类比TreeSet,不用重写equals方法,是根据compareTo方法判断是否相同的(无compator方法下)

7.5 properties

public class PropertiesTest {

    //Properties:常用来处理配置文件。key和value都是String类型
    public static void main(String[] args)  {
        FileInputStream fis = null;
        try {
            Properties pros = new Properties();

            fis = new FileInputStream("jdbc.properties");
            pros.load(fis);//加载流对应的文件

            String name = pros.getProperty("name");
            String password = pros.getProperty("password");

            System.out.println("name = " + name + ", password = " + password);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(fis != null){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }

    }
}

8.collections工具类常用方法

reverse(List):反转 List 中元素的顺序
shuffle(List):对 List 集合元素进行随机排序
sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
sort(ListComparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
swap(Listintint):将指定 list 集合中的 i 处元素和 j 处元素进行交换

Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
Object max(CollectionComparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
Object min(Collection)
Object min(CollectionComparator)
int frequency(CollectionObject):返回指定集合中指定元素的出现次数
void copy(List dest,List src):将src中的内容复制到dest中
boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
//报异常:IndexOutOfBoundsException("Source does not fit in dest"),因为desc当前元素个数不能小于list的大小
//        List dest = new ArrayList();
//        Collections.copy(dest,list);
        //正确的:
        List dest = Arrays.asList(new Object[list.size()]);
        System.out.println(dest.size());//list.size();
        Collections.copy(dest,list);

        System.out.println(dest);
        
Collections 类中提供了多个 synchronizedXxx() 方法,
该方法可使将指定集合包装成线程同步的集合,从而可以解决
多线程并发访问集合时的线程安全问题
//返回的list1即为线程安全的List
List list1 = Collections.synchronizedList(list);

泛型(不太懂)

1.泛型概述

总结:
① 集合接口或集合类在jdk5.0时都修改为带泛型的结构。
② 在实例化集合类时,可以指明具体的泛型类型
③ 指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型。
 比如:add(E e)  --->实例化以后:add(Integer e)
④ 注意点:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换
⑤ 如果实例化时,没有指明泛型的类型。默认类型为java.lang.Object类型。

2.如何自定义泛型结构:泛型类、泛型接口;泛型方法

为什么会有泛型?
ArrayList list = new ArrayList();
        需求:存放学生的成绩
        list.add(78);
        list.add(76);
        list.add(89);
        list.add(88);
        //问题一:类型不安全
        list.add("Tom");

        for(Object score : list){
            //问题二:强转时,可能出现ClassCastException,因为混入个Tom
            int stuScore = (Integer) score;//Integer自动装箱

            System.out.println(stuScore);

        }
        
使用泛型的情况举例
  //在集合中使用泛型的情况:以ArrayList为例
    @Test
    public void test2(){
       ArrayList<Integer> list =  new ArrayList<Integer>();//将泛型具体化为Integer

        list.add(78);//add方法原型:add(E e)这里add方法中的参数就被固定了,就可以在编译时检查了
        list.add(87);
        list.add(99);
        list.add(65);
        list.add("Tom");

//遍历操作用到泛型
        for(Integer score : list){
            //避免了强转操作
            int stuScore = score;

            System.out.println(stuScore);

        }
        //原来遍历写法
        Iterator iterator = list.iterator();
        while (iterator.hasNext()){
            Object next = iterator.next();
            Integer integer = (Integer)next;
            System.out.println(integer);
        }
        //现在写法
        Iterator<Integer> iterator = list.iterator();
        while(iterator.hasNext()){
            int stuScore = iterator.next();
            System.out.println(stuScore);
        }

    }

在集合中使用泛型的情况:以HashMap为例
    @Test
    public void test3(){
        //Map<String,Integer> map = new HashMap<String,Integer>();
        //jdk7新特性:类型推断
        Map<String,Integer> map = new HashMap<>();

        map.put("Tom",87);
        map.put("Jerry",87);
        map.put("Jack",67);

        // map.put(123,"ABC");
        //泛型的嵌套
        Set<Map.Entry<String,Integer>> entry = map.entrySet();
        Iterator<Map.Entry<String, Integer>> iterator = entry.iterator();

        while(iterator.hasNext()){
            Map.Entry<String, Integer> e = iterator.next();
            String key = e.getKey();
            Integer value = e.getValue();
            System.out.println(key + "----" + value);
        }
    }
}

**********************************
 如何自定义泛型结构:泛型类、泛型接口;泛型方法。
 
  1. 关于自定义泛型类、泛型接口:

public class GenericTest1 {

    @Test
    public void test1(){
        //如果定义了泛型类,实例化没有指明类的泛型,则认为此泛型类型为Object类型
        //要求:如果大家定义了类是带泛型的,建议在实例化时要指明类的泛型。
        Order order = new Order();
        order.setOrderT(123);
        order.setOrderT("ABC");

        //建议:实例化时指明类的泛型
        Order<String> order1 = new Order<String>("orderAA",1001,"order:AA");

        order1.setOrderT("AA:hello");

    }

    @Test
    public void test2(){
        SubOrder sub1 = new SubOrder();
        //由于子类在继承带泛型的父类时,指明了泛型类型。则实例化子类对象时,不再需要指明泛型。
        sub1.setOrderT(1122);

        SubOrder1<String> sub2 = new SubOrder1<>();
        sub2.setOrderT("order2...");
    }

    @Test
    public void test3(){

        ArrayList<String> list1 = null;
        ArrayList<Integer> list2 = new ArrayList<Integer>();
        //泛型不同的引用不能相互赋值。
        //list1 = list2;

        Person p1 = null;
        Person p2 = null;
        p1 = p2;


    }

测试泛型方法
    @Test
    public void test4(){
        Order<String> order = new Order<>();
        Integer[] arr = new Integer[]{1,2,3,4};
        //泛型方法在调用时,指明泛型参数的类型。
        List<Integer> list = order.copyFromArrayToList(arr);

        System.out.println(list);
    }
}
************************************************************
package src.com.atguigu.java;

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

自定义泛型类
public class Order<T> {

    String orderName;
    int orderId;

    //类的内部结构就可以使用类的泛型

    T orderT;

    public Order(){
        //编译不通过
        //T[] arr = new T[10];
        //编译通过
        T[] arr = (T[]) new Object[10];
    }

    public Order(String orderName,int orderId,T orderT){
        this.orderName = orderName;
        this.orderId = orderId;
        this.orderT = orderT;
    }

    //如下的三个方法都不是泛型方法
    public T getOrderT(){
        return orderT;
    }

    public void setOrderT(T orderT){
        this.orderT = orderT;
    }

    @Override
    public String toString() {
        return "Order{" +
                "orderName='" + orderName + ''' +
                ", orderId=" + orderId +
                ", orderT=" + orderT +
                '}';
    }
    //静态方法中不能使用类的泛型。
   public static void show(T orderT){
        System.out.println(orderT);
    }

    public void show(){
        //编译不通过
       try{

      }catch(T t){
      }

    }

    //泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系。
    //换句话说,泛型方法所属的类是不是泛型类都没有关系。
    //泛型方法,可以声明为静态的。原因:泛型参数是在调用方法时确定的。并非在实例化类时确定。
    public static <E>  List<E> copyFromArrayToList(E[] arr){

        ArrayList<E> list = new ArrayList<>();

        for(E e : arr){
            list.add(e);
        }
        return list;

    }
}

3.泛型注意点

1. 泛型在继承方面的体现
 虽然类A是类B的父类,但是G<A>G<B>二者不具备子父类关系,二者是并列关系。
 补充:类A是类B的父类或接口,则A<G>B<G> 的父类

2. 通配符的使用
   通配符:?A是类B的父类,G<A>G<B>是没有关系的,二者共同的父类是:G<?>
  List<String> list3 = new ArrayList<>();
        list3.add("AA");
        list3.add("BB");
        list3.add("CC");
        list = list3;
        //添加(写入):对于List<?>就不能向其内部添加数据。
        //除了添加null之外。
//        list.add("DD");
//        list.add('?');

        list.add(null);

        //获取(读取):允许读取数据,读取的数据类型为Object。
        Object o = list.get(0);
        System.out.println(o);
3.有限制条件的通配符的使用。
        ? extends A:
                G<? extends A> 可以作为G<A>G<B>的父类,其中BA的子类

        ? super A:
                G<? super A> 可以作为G<A>G<B>的父类,其中BA的父类

     */
    @Test
    public void test4(){

        List<? extends Person> list1 = null;
        List<? super Person> list2 = null;

        List<Student> list3 = null;
        List<Person> list4 = null;
        List<Object> list5 = null;

        list1 = list3;//?可以小于等于Person
        list1 = list4;
//        list1 = list5;

//        list2 = list3;
        list2 = list4;//?可以大于等于Person
        list2 = list5;

        //读取数据:
        list1 = list3;
        Person p = list1.get(0);
        //编译不通过
        //Student s = list1.get(0);

        list2 = list4;
        Object obj = list2.get(0);
        编译不通过
//        Person obj = list2.get(0);

        //写入数据:
        //编译不通过
//        list1.add(new Student());

        //编译通过
        list2.add(new Person());
        list2.add(new Student());

    }

}

IO流

1.初识File
File类的一个对象,代表一个文件或一个文件目录(俗称:文件夹)
File类声明在java.io包下
File类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法,并未涉及到写入或读取文件内容的操作。如果需要读取或写入文件内容,必须使用IO流来完成。
后续File类的对象常会作为参数传递到流的构造器中,指明读取或写入的"终点".
如何看一个流的父类:看后缀

在这里插入图片描述
2.创建FIle实例

1.如何创建File类的实例
    File(String filePath)
    File(String parentPath,String childPath)
    File(File parentFile,String childPath)

2.
相对路径:相较于某个路径下,指明的路径。
绝对路径:包含盘符在内的文件或文件目录的路径

3.路径分隔符
 windows:\
 unix:/
 
 
 步骤:
 ① 创建File类的对象,指明读取的数据的来源。(要求此文件一定要存在)
② 创建相应的输入流,将File类的对象作为参数,传入流的构造器中
③ 具体的读入过程:
    创建相应的byte[]char[]。
④ 关闭流资源
说明:程序中出现的异常需要使用try-catch-finally处理。

3.File常用方法

public String getAbsolutePath():获取绝对路径
public String getPath() :获取路径
public String getName() :获取名称
public String getParent():获取上层文件目录路径。若无,返回null
public long length() :获取文件长度(即:字节数)。不能获取目录的长度。
public long lastModified() :获取最后一次的修改时间,毫秒值

如下的两个方法适用于文件目录:
public String[] list() :获取指定目录下的所有文件或者文件目录的名称数组,相对路径
public File[] listFiles() :获取指定目录下的所有文件或者文件目录的File数组,绝对路径
public boolean renameTo(File dest):把文件重命名为指定的文件路径
 比如:file1.renameTo(file2)为例:
    要想保证返回true,需要file1在硬盘中是存在的,且file2不能在硬盘中存在。
public boolean isDirectory():判断是否是文件目录
public boolean isFile() :判断是否是文件
public boolean exists() :判断是否存在
public boolean canRead() :判断是否可读
public boolean canWrite() :判断是否可写
public boolean isHidden() :判断是否隐藏
    创建硬盘中对应的文件或文件目录
public boolean createNewFile() :创建文件。若文件存在,则不创建,返回false
public boolean mkdir() :创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。
public boolean mkdirs() :创建文件目录。如果此文件目录存在,就不创建了。如果上层文件目录不存在,一并创建

    删除磁盘中的文件或文件目录
public boolean delete():删除文件或者文件夹
    删除注意事项:Java中的删除不走回收站。
    
public void test6() throws IOException {
        File file1 = new File("hi.txt");
        if(!file1.exists()){
            //文件的创建
            file1.createNewFile();
            System.out.println("创建成功");
        }else{//文件存在
            file1.delete();
            System.out.println("删除成功");
        }


    }
    @Test
    public void test7(){
        //文件目录的创建
        File file1 = new File("d:\io\io1\io3");

        boolean mkdir = file1.mkdir();
        if(mkdir){
            System.out.println("创建成功1");
        }

        File file2 = new File("d:\io\io1\io4");

        boolean mkdir1 = file2.mkdirs();
        if(mkdir1){
            System.out.println("创建成功2");
        }
        //要想删除成功,io4文件目录下不能有子目录或文件
        File file3 = new File("D:\io\io1\io4");
        file3 = new File("D:\io\io1");
        System.out.println(file3.delete());
    }
}

4.易错实例

/**
 * 演示文件的创建以及制定文件的删除操作
 * @author Cserjie
 * @create 2022-04-05-9:58
 */
public class FileDemo {
    @Test
    public void Demo1() throws IOException {
        File file = new File("D:\file");//D:\file
        boolean mkdir = file.mkdir();
        if(mkdir){
            System.out.println("文件目录创建成功");
        }
        File file1 = new File(file.getAbsolutePath(),"hh");//D:\file\hh
        if(file1.mkdir()){
            System.out.println("创建成功");
        }
        File file2 = new File(file1.getParentFile(),"嘿嘿");//D:\file\嘿嘿
        if(file2.createNewFile()){
            System.out.println("创建文件成功");
        }
        File[] files = file.listFiles();//获取了file下的所有文件的绝对路径
        System.out.println("***************");
        for (File file3:files) {
            if(file3.getName().equals("嘿嘿")){
                System.out.println("删除成功");
                file3.delete();//注意:此时files里面依然有嘿嘿的绝对路径,只不过嘿嘿在物理层面上被删除了
            }
        }
        for (File file3:file.listFiles()) {//这是更新一下files,files里面就没有嘿嘿了
            System.out.println(file3.getName());
        }
        System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
        File file3 = new File(file.getAbsolutePath(),"chaizhenjie.txt");//添加文件应该用绝对路径添加,错误写法(file.getname(),"chaizhenjie.txt")
        if (file3.createNewFile()) {
            System.out.println("文件有有有创建成功了");
        }
        for (File file5:file.listFiles()) {
            System.out.println(file5.getName());
        }
        File[] files1 = file.listFiles();
        for (File file4:files1) {
            if(file4.getName().endsWith("txt")){
                file4.delete();
                System.out.println("文件" + file4.getName() + "删除成功");
            }
        }
        System.out.println("file下现存文件:" );
        for (File file5:file.listFiles()) {
            System.out.println(file5.getName());
        }

    }
}

/**
 * 递归输出指定目录下的所有文件(包括子文件)
 * @author Cserjie
 * @create 2022-04-05-11:24
 */
public class seniorDemo {
    public  File file = new File("D:\Download");
    public  File file1;
    public static void main(String[] args) {
        seniorDemo seniorDemo = new seniorDemo();
        seniorDemo.dir();
    }
    public void dir(){
        File[] files = file.listFiles();
        if(files != null){
        for (int i = 0;i < files.length;++i) {
            file1 = files[i];
            if (file1!= null){
                file = file1;
                dir();
            }
        }
        }
        System.out.println(file.getName());
    }

}

使用字节流FileInputStream处理文本文件,可能出现乱码,因为在utf-8和gbk汉字是拿3个字节存储的,字符则是1个字节
但是如果只是复制而不再内存层面读取则没事

在这里插入图片描述
5.转换流

/**
 * 处理流之二:转换流的使用
 * 1.转换流:属于字符流
 *   InputStreamReader:将一个字节的输入流转换为字符的输入流
 *   OutputStreamWriter:将一个字符的输出流转换为字节的输出流
 *
 * 2.作用:提供字节流与字符流之间的转换
 *
 * 3. 解码:字节、字节数组  --->字符数组、字符串
 *    编码:字符数组、字符串 ---> 字节、字节数组
 *
 *
 * 4.字符集
 *ASCII:美国标准信息交换码。
    用一个字节的7位可以表示。
 ISO8859-1:拉丁码表。欧洲码表
    用一个字节的8位表示。
 GB2312:中国的中文编码表。最多两个字节编码所有字符
 GBK:中国的中文编码表升级,融合了更多的中文文字符号。最多两个字节编码
 Unicode:国际标准码,融合了目前人类使用的所有字符。为每个字符分配唯一的字符码。所有的文字都用两个字节来表示。
 UTF-8:变长的编码方式,可用1-4个字节来表示一个字符。

 *
 *
 * @author shkstart
 * @create 2019 下午 4:25
 */
public class InputStreamReaderTest {

    /*
    此时处理异常的话,仍然应该使用try-catch-finally
    InputStreamReader的使用,实现字节的输入流到字符的输入流的转换
     */
    @Test
    public void test1() throws IOException {

        FileInputStream fis = new FileInputStream("dbcp.txt");
//        InputStreamReader isr = new InputStreamReader(fis);//使用系统默认的字符集
        //参数2指明了字符集,具体使用哪个字符集,取决于文件dbcp.txt保存时使用的字符集
        InputStreamReader isr = new InputStreamReader(fis,"UTF-8");//使用系统默认的字符集

        char[] cbuf = new char[20];
        int len;
        while((len = isr.read(cbuf)) != -1){
            String str = new String(cbuf,0,len);
            System.out.print(str);
        }

        isr.close();

    }

    /*
    此时处理异常的话,仍然应该使用try-catch-finally

    综合使用InputStreamReader和OutputStreamWriter,将文本文件利用字节流输入输出流+转换流转换为gbk编码的字符流文件
     */
    @Test
    public void test2() throws Exception {
        //1.造文件、造流
        File file1 = new File("dbcp.txt");
        File file2 = new File("dbcp_gbk.txt");

        FileInputStream fis = new FileInputStream(file1);
        FileOutputStream fos = new FileOutputStream(file2);

        InputStreamReader isr = new InputStreamReader(fis,"utf-8");
        OutputStreamWriter osw = new OutputStreamWriter(fos,"gbk");

        //2.读写过程
        char[] cbuf = new char[20];
        int len;
        while((len = isr.read(cbuf)) != -1){
            osw.write(cbuf,0,len);
        }

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


    }
}

6.其他流
6.1 标准输入输出流(解决从用户从控制台输入数据的问题)

public static void main(String[] args) {
    BufferedReader br = null;
    try {
        InputStreamReader isr = new InputStreamReader(System.in);//将标准字节输入流printstream转换为字符输入流
        br = new BufferedReader(isr);

        while (true) {
            System.out.println("请输入字符串:");
            String data = br.readLine();
            if ("e".equalsIgnoreCase(data) || "exit".equalsIgnoreCase(data)) {
                System.out.println("程序结束");
                break;
            }

            String upperCase = data.toUpperCase();
            System.out.println(upperCase);

        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (br != null) {
            try {
                br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }
}

6.2打印流

//实现把标准输出流(控制台输出)改成输出到文件
public void test2() {
    PrintStream ps = null;
    try {
        FileOutputStream fos = new FileOutputStream(new File("D:\IO\text.txt"));
        // 创建打印输出流,设置为自动刷新模式(写入换行符或字节 'n' 时都会刷新输出缓冲区)
        ps = new PrintStream(fos, true);
        if (ps != null) {// 把标准输出流(控制台输出)改成文件
            System.setOut(ps);
        }


        for (int i = 0; i <= 255; i++) { // 输出ASCII字符
            System.out.print((char) i);
            if (i % 50 == 0) { // 每50个数据一行
                System.out.println(); // 换行
            }
        }


    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } finally {
        if (ps != null) {
            ps.close();
        }
    }

}

6.3 数据流

 /*
    3. 数据流
    3.1 DataInputStream 和 DataOutputStream
    3.2 作用:用于读取或写出基本数据类型的变量或字符串

    练习:将内存中的字符串、基本数据类型的变量写出到文件中。

    注意:处理异常的话,仍然应该使用try-catch-finally.
     */
    @Test
    public void test3() throws IOException {
        //1.
        DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
        //2.
        dos.writeUTF("刘建辰");
        dos.flush();//刷新操作,将内存中的数据写入文件
        dos.writeInt(23);
        dos.flush();
        dos.writeBoolean(true);
        dos.flush();
        //3.
        dos.close();


    }
    /*
    将文件中存储的基本数据类型变量和字符串读取到内存中,保存在变量中。

    注意点:读取不同类型的数据的顺序要与当初写入文件时,保存的数据的顺序一致!

     */
    @Test
    public void test4() throws IOException {
        //1.
        DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));
        //2.
        String name = dis.readUTF();
        int age = dis.readInt();
        boolean isMale = dis.readBoolean();

        System.out.println("name = " + name);
        System.out.println("age = " + age);
        System.out.println("isMale = " + isMale);

        //3.
        dis.close();

    }

}

7.对象流(没弄懂)

/**
 * 对象流的使用
 * 1.ObjectInputStream 和 ObjectOutputStream
 * 2.作用:用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
 *
 * 3.要想一个java对象是可序列化的,需要满足相应的要求。见Person.java
 *
 * 4.序列化机制:
 * 对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种
 * 二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。
 * 当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。

 *
 * @author shkstart
 * @create 2019 上午 10:27
 */
public class ObjectInputOutputStreamTest {

    /*
    序列化过程:将内存中的java对象保存到磁盘中或通过网络传输出去
    使用ObjectOutputStream实现
     */
    @Test
    public void testObjectOutputStream(){
        ObjectOutputStream oos = null;

        try {
            //1.
            oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
            //2.
            oos.writeObject(new String("我爱北京天安门"));//此操作将这行文字写入了object.dat文件中,是看不到具体内容的,必须通过反序列化才可以看
            oos.flush();//刷新操作

            oos.writeObject(new Person("王铭",23));
            oos.flush();

            oos.writeObject(new Person("张学良",23,1001,new Account(5000)));
            oos.flush();

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(oos != null){
                //3.
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }

    }

    /*
    反序列化:将磁盘文件中的对象还原为内存中的一个java对象
    使用ObjectInputStream来实现
     */
    @Test
    public void testObjectInputStream(){
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream("object.dat"));

            Object obj = ois.readObject();//将“我爱北京天安门”读到obj中
            String str = (String) obj;

            Person p = (Person) ois.readObject();
            Person p1 = (Person) ois.readObject();

            System.out.println(str);
            System.out.println(p);
            System.out.println(p1);

        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if(ois != null){
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }



    }

}

7.1 自定义类实现序列化的要求(没弄懂)

/**
 * Person需要满足如下的要求,方可序列化
 * 1.需要实现接口:Serializable
 * 2.当前类提供一个全局常量:serialVersionUID
 * 3.除了当前Person类需要实现Serializable接口之外,还必须保证其内部所有属性
 *   也必须是可序列化的。(默认情况下,基本数据类型可序列化)
 *
 *
 * 补充:ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量
 *
 *
 * @author shkstart
 * @create 2019 上午 10:38
 */
public class Person implements Serializable{

    public static final long serialVersionUID = 475463534532L;//若不加这一行,则当Person类修改后你在反序列化将会报异常

    private String name;
    private int age;
    private int id;
    private Account acct;

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

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

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

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    public Person(String name, int age) {

        this.name = name;
        this.age = age;
    }

    public Person() {

    }
}

class Account implements Serializable{
    public static final long serialVersionUID = 4754534532L;
    private double balance;

    @Override
    public String toString() {
        return "Account{" +
                "balance=" + balance +
                '}';
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    public Account(double balance) {

        this.balance = balance;
    }
}

8.随机存取文件流

网络编程

1.初识网络编程

一、网络编程中有两个主要的问题:
* 1.如何准确地定位网络上一台或多台主机;定位主机上的特定的应用
* 2.找到主机后如何可靠高效地进行数据传输
*
* 二、网络编程中的两个要素:
* 1.对应问题一:IP和端口号
* 2.对应问题二:提供网络通信协议:TCP/IP参考模型(应用层、传输层、网络层、物理+数据链路层)
*
*
* 三、通信要素一:IP和端口号
*
* 1. IP:唯一的标识 Internet 上的计算机(通信实体)
* 2.Java中使用InetAddress类代表IP
* 3. IP分类:IPv4IPv6 ; 万维网 和 局域网
* 4. 域名:   www.baidu.com   www.mi.com  www.sina.com  www.jd.com
*            www.vip.com
* 5. 本地回路地址:127.0.0.1 对应着:localhost
*
* 6. 如何实例化InetAddress:两个方法:getByName(String host)getLocalHost()
*        两个常用方法:getHostName() / getHostAddress()
*
* 7. 端口号:正在计算机上运行的进程。
* 要求:不同的进程有不同的端口号
* 范围:被规定为一个 16 位的整数 0~65535*
* 8. 端口号与IP地址的组合得出一个网络套接字:Socket

2.实现简单的TCP网络编程

//客户端:用来向服务端发送数据
@Test
public void Clinet() throws IOException {
    Socket socket = null;
    OutputStream outputStream = null;
    try {
        InetAddress inetAddress = InetAddress.getByName("192.168.1.1");
        socket = new Socket(inetAddress,8899);

        outputStream = socket.getOutputStream();
        outputStream.write("你好,我是客户端的柴朕杰!".getBytes());
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(outputStream != null){

            try {
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        if(socket != null){

            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}


//服务端:用来接收客户端的数据
@Test
public void Server() throws IOException {
    ServerSocket ss = null;//制定自己的端口号
    Socket socket = null;//用以接收客户端的socket
    InputStream inputStream = null;//获取输入流
    ByteArrayOutputStream byteArrayOutputStream = null;
    try {
        ss = new ServerSocket(8899);
        socket = ss.accept();
        /*InputStream inputStream = accept.getInputStream();*/
        inputStream = socket.getInputStream();
        byteArrayOutputStream = new ByteArrayOutputStream();//使用这一句用来处理客户端数据中有中文的情况(功能类似转换流)

        byte[] bytes = new byte[5];
        int flag = 0;
        while ((flag = inputStream.read()) != -1){
            byteArrayOutputStream.write(bytes,0,flag);
        }

        System.out.println(byteArrayOutputStream.toString());
        System.out.println("收到了来自客户端:" + socket.getInetAddress().getHostAddress() + "的数据:" + bytes);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(byteArrayOutputStream != null){

            try {
                byteArrayOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(inputStream != null){
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        if(socket != null){
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        if(ss != null){

            try {
                ss.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }



}

3.案例:服务端接收来自客户端的数据并保存到本地

@Test
public void client() throws IOException {
    Socket socket = null;//发送给指定服务端
    OutputStream outputStream = null;
    InputStream inputStream = null;
    try {
        InetAddress inetAddress = InetAddress.getByName(InetAddress.getLocalHost().getHostAddress());//获取本机ip地址
        socket = new Socket(inetAddress,8899);//从inerAddress发送给端口8899
        outputStream = socket.getOutputStream();//为这个socket创建一个输出流,用以传输输出的数据
        FileInputStream fis = new FileInputStream("ocp.png");//用以读入需要传输的数据
        byte[] bytes = new byte[5];
        int flag;
        while ((flag = fis.read(bytes)) != -1){
            outputStream.write(bytes,0,flag);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(inputStream != null){

            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(outputStream != null){

            try {
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(socket != null){

            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}


@Test void server() throws IOException {
    ServerSocket s = null;
    Socket socket = null;
    InputStream inputStream = null;
    FileOutputStream fos = null;
    try {
        s = new ServerSocket(8899);//指定端口号,好让客户端识别它
        socket = s.accept();//此socket用以接受来及客户端的数据
        inputStream = socket.getInputStream();//需要将客户端返回的数据读取,创建一个输入流
        fos = new FileOutputStream("copy.png");//输出到你的指定位置(保存到本地)
        byte[] bytes = new byte[5];
        int flag;
        while ((flag = inputStream.read(bytes)) != -1){
            fos.write(bytes,0,flag);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(fos != null){

            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(inputStream != null){
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        if(socket != null){
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        if(s != null){

            try {
                s.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    System.out.println("数据保存完成");
}

4.案例:服务端接收来自客户端的数据并保存到本地并返回给客户端一句:图片我已收到!!!(阻塞问题的理解)

@Test
public void client() throws IOException {
    Socket socket = null;//发送给指定服务端
    OutputStream outputStream = null;
    InputStream inputStream = null;
    try {
        InetAddress inetAddress = InetAddress.getByName(InetAddress.getLocalHost().getHostAddress());//获取本机ip地址
        socket = new Socket(inetAddress,8899);//从inerAddress发送给端口8899
        outputStream = socket.getOutputStream();//为这个socket创建一个输出流,用以传输输出的数据
        FileInputStream fis = new FileInputStream("ocp.png");//用以读入需要传输的数据
        byte[] bytes = new byte[5];
        int flag;
        while ((flag = fis.read(bytes)) != -1){
            outputStream.write(bytes,0,flag);
        }
        socket.shutdownOutput();
        InputStream inputStream1 = socket.getInputStream();//从它的连接对象处获取输入流,将连接对象传输过来的数据传入此输入流
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();//为了解决传输的数据为中文的问题,因为此时用的是字节流,类似转换流的作用
        byte[] bytes1 = new byte[10];
        int flag1;
        while ((flag1 = inputStream1.read(bytes1)) != -1){//将连接对象传输过来的数据读取到数组中
            byteArrayOutputStream.write(bytes1,0,flag1);
        }
        System.out.println(byteArrayOutputStream.toString());
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(inputStream != null){

            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(outputStream != null){

            try {
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(socket != null){

            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}


@Test void server() throws IOException {
    ServerSocket s = null;
    Socket socket = null;
    InputStream inputStream = null;
    FileOutputStream fos = null;
    try {
        s = new ServerSocket(8899);//指定端口号,好让客户端识别它
        socket = s.accept();//此socket用以接受来及客户端的数据,使得客户端与服务端建立了一层连接
        inputStream = socket.getInputStream();//需要将客户端返回的数据读取,创建一个输入流
        fos = new FileOutputStream("copy.png");//输出到你的指定位置(保存到本地)
        byte[] bytes = new byte[5];
        int flag;
        while ((flag = inputStream.read(bytes)) != -1){
            fos.write(bytes,0,flag);
        }
        OutputStream outputStream = socket.getOutputStream();//获取一个输出流,目标位置是给它的连接对象(客户端)
        outputStream.write("图片我已收到!!!".getBytes());
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(fos != null){

            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(inputStream != null){
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        if(socket != null){
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        if(s != null){

            try {
                s.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    System.out.println("数据保存完成");

}

阻塞的理解:client的16~24和server的71~72为新加代码,若我们将16行去除,则两个程序永远在运行不会退出(阻塞了),原因是客户端会不停的传输数据,而服务端
会不停的接收数据,第三个案例可以成功的原因是,客户端读到-1后,服务端没读到数据进行等待,然后客户端就进行关闭操作了,然后服务端发现客户端都关闭了
那么一定就不会传输数据了,所以二者正常退出。但此案例如果不加16行,那么客户端读到-1后,服务端没读到数据进行等待,客户端继续进行下面的语句,服务端继续
等待客户端发来的数据......,终于到了客户端执行21行read循环,此时服务端依旧在等客户端,但是客户端也依旧在等你服务端传过来的数据它好读取,就这样
二者陷入僵持无法退出,而有了16行则明确告诉了服务端客户端数据已经传输完毕,服务端就不会一直等待了

5.udp网络编程(上面都是tcp网络编程,udp无脑给服务端发数据,不用握手,效率高,内容可能有缺失,考虑不要求数据精度时使用)

/**
 * 简单的客户端发送数据,用户端接收数据的实例
 * @author Cserjie
 * @create 2022-04-09-10:23
 */
public class udpTest {
    //发送端
    @Test
    public void send() throws IOException {
        DatagramSocket datagramSocket = new DatagramSocket();
        String str = new String("我是来自UDP的导弹!!!");
        byte[] bytes = str.getBytes();
        //指明接收端端口号以及要接受的数据
        DatagramPacket datagramPacket = new DatagramPacket(bytes,bytes.length, InetAddress.getLocalHost(),8899);
        datagramSocket.send(datagramPacket);
        datagramSocket.close();
    }

    //接收端
    @Test
    public void receive() throws IOException {
        DatagramSocket datagramSocket = new DatagramSocket(8899);//设置自己的端口号
        byte[] bytes = new byte[100];
        DatagramPacket datagramPacket = new DatagramPacket(bytes,0,bytes.length);
        datagramSocket.receive(datagramPacket);
        System.out.println(new String(datagramPacket.getData(),0,datagramPacket.getLength()));
        datagramSocket.close();
    }
}


/**
 * url各类方法的使用及url各参数的说明
 * @author Cserjie
 * @create 2022-04-09-19:11
 */
public class urltest {
    public static void main(String[] args) throws MalformedURLException {
        URL url = new URL("https://cn.bing.com/images/search?view=detailV2&ccid=nfC2tVNM&id=49E031AAC7715C25D8E03215367A7B4A50E14354&thid=OIP.nfC2tVNM9TgwQ5QuqECd6wHaFj&mediaurl=https%3a%2f%2fimg.mianfeiwendang.com%2fpic%2f65133e4129b6446aa22c9f9f%2f1-810-jpg_6-1080-0-0-1080.jpg&exph=810&expw=1080&q=%e5%9b%be%e7%89%87&simid=608035324929930883&FORM=IRPRST&ck=80CEE6C1EA35720EF9BEBABB8A76BE85&selectedIndex=0&ajaxhist=0&ajaxserp=0");
        System.out.println(url.getProtocol());//该url的协议名
        System.out.println(url.getPort());//端口号
        System.out.println(url.getHost());//主机名
        System.out.println(url.getPath());//文件路径
        System.out.println(url.getFile());//文件名
        System.out.println(url.getQuery());//该url查询名
    }
}


/**
 * @author Cserjie
 * @create 2022-04-09-19:16
 * 将某一url上的图片下载下来
 */
public class urltest1 {
    public static void main(String[] args) {
        HttpURLConnection httpURLConnection = null;//返回一个URLConnection实例,该实例表示到该URL引用的远程对象的连接(但还未连接),通过此实例的连接去操作这个url上的图片
        InputStream inputStream = null;//读取url上的数据,也就是你要操作的数据
        FileOutputStream fileOutputStream = null;//利用输出流下载到此位置
        try {
            URL url = new URL("file:///D:/Desktop/OIP-C.png");
            httpURLConnection = (HttpURLConnection) url.openConnection();
            httpURLConnection.connect();//将此实例与url进行连接
            inputStream = httpURLConnection.getInputStream();
            fileOutputStream = new FileOutputStream("Day10\ooo.png");
            byte[] bytes = new byte[10];
            int flag = 0;
            while ((inputStream.read(bytes)) != -1) {
                fileOutputStream.write(bytes, 0, flag);
            }
            System.out.println("传输完成");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fileOutputStream != null) {

                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (httpURLConnection != null) {

                try {
                    httpURLConnection.disconnect();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if (inputStream != null) {

                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
    }
}

反射

定义:在封装这个功能中,我们之所以把某些属性、构造器、方法等设置为私有的,是因为想要告诉外界,你们不要用这些东西了,要么是你们可以通过其他方法间接使用,要么就是可以用我提供的其他方法代替这些私有的东西,封装讲究一个“建议使用”,而反射则不一样了,我明知道你不建议我用但我还必须用,讲究的是一个“能不能用的问题”
1.关于java.lang.Class类的理解
类的加载过程
首先通过javac.exe命令会生成此程序中的各个类的字节码文件xxx.class,然后下面则为类的加载过程:接着我们通过java.exe命令将某一类(一般为包含main函数的类)加载到内存中,这就是类的加载的过程,在内存中的这个类我们称为运行时类,这个类就充当了Class类的一个实例了,以此来解释诸如为什么可以 类名.属性方法调用属性的问题,因为这个类本身也是一个对象罢了,换句话说Class的实例就对应一个运行时类。

/**
 * 反射解除私有限制的基本用法
 * @author Cserjie
 * @create 2022-04-09-20:16
 */
public class reflectionTest {
    public static void main(String[] args) throws Exception {
        //不用反射前,私有构造器、私有成员变量、私有方法都无法调用
        person p = new person(15,"柴朕杰");
        p.age = 20;
        System.out.println(p.toString());
        p.show();

        //运用反射后,如何实现以前的操作,并且解除上述限制?
        //以前操作
        Class clazz = person.class;
        //通过clazz获取一个你现在想要的对象的构造器,也可以是私有的,不过需要制定参数,他就可以识别是哪个构造器
        Constructor constructors = clazz.getConstructor(int.class,String.class);
        //通过构造器创建一个对应的实例,这里用到了多态
        Object tom = constructors.newInstance(15, "TOM");
        System.out.println(tom.toString());

        Method show1 = clazz.getDeclaredMethod("show");//填入方法名
        show1.invoke(p);
        System.out.println("******************************");
        //解除私有限制,访问私有的结构
        Field name = clazz.getDeclaredField("name");//获取定义为私有的属性
        name.setAccessible(true);//将其设置为可访问状态:解除了私有化限制
        name.set(p,"阿杰");//修改哪个对象的这个私有属性
        System.out.println(p);

        Method show = clazz.getDeclaredMethod("shownation");//填写方法名及参数,这里是空参方法
        show.setAccessible(true);//解除限制
        show.invoke(p);//哪个对象的这个方法,并在后面写上此方法的参数,这里是空参

        Constructor constructor = clazz.getConstructor(String.class);//获取待解除限制的构造器,参数为构造器参数
        constructor.setAccessible(true);//解除限制
        Object obj = constructor.newInstance("韩玉");//成功获取了一个实例
        System.out.println(obj.toString());


    }
}
class person{
    int age;
    private String name;

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

    private person(int age) {
        this.age = age;
    }

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

    public void show(){
        System.out.println("展示我的态度!!!");
    }

    private void shownation(){
        System.out.println("展示我的国家");
    }

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

2.获取Class的实例的四种方式

//方式一:调用运行时类的属性:.class
        Class clazz1 = Person.class;
        System.out.println(clazz1);
//方式二:通过运行时类的对象,调用getClass()
        Person p1 = new Person();
        Class clazz2 = p1.getClass();
        System.out.println(clazz2);

//方式三:调用Class的静态方法:forName(String classPath)
        Class clazz3 = Class.forName("com.atguigu.java.Person");
//        clazz3 = Class.forName("java.lang.String");
        System.out.println(clazz3);

        System.out.println(clazz1 == clazz2);
        System.out.println(clazz1 == clazz3);

//方式四:使用类的加载器:ClassLoader  (了解)
        ClassLoader classLoader = ReflectionTest.class.getClassLoader();
        Class clazz4 = classLoader.loadClass("com.atguigu.java.Person");
        System.out.println(clazz4);
        
注:
1.接口、数组、string等等都可以作为Class的实例,也就是Class clazz = int[][].class,一样获取了一个int[][]的实例
2.加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式
来获取此运行时类,所以不是创建一个运行时类,而是获取

3.类的加载器与properties

/**
 * 了解三种类的加载器
 * @author Cserjie
 * @create 2022-04-10-7:34
 */
public class ClassLoaderTest {
    @Test
    public void test1(){
        //系统类加载器。最常用的加载器
        ClassLoader cl1 = ClassLoaderTest.class.getClassLoader();
        System.out.println(cl1);
        //扩展类加载器
        ClassLoader cl2 = cl1.getParent();
        System.out.println(cl2);
        //引导类加载器:不可获得的,用来加载java核心类库
        System.out.println(cl2.getParent());

    }
    @Test
    //proporties的读取
    public void test2() throws Exception{
        Properties pros = new Properties();
        //没学反射之前
        FileInputStream fis = new FileInputStream("test2.properties");
        pros.load(fis);
        String user = pros.getProperty("user");
        String password = pros.getProperty("password");
        System.out.println("user = " + user + ",password = " + password);

        //反射的方法实现
        ClassLoader cl =ClassLoaderTest.class.getClassLoader();//获取当前测试类的加载器
        InputStream resourceAsStream = cl.getResourceAsStream("hhh.properties");//通过加载器加载对应的文件,返回这个文件的输入流,此文件默认在当前module的src下
        pros.load(resourceAsStream);//让pros与这个流建立起连接
        System.out.println("user = " + pros.getProperty("user") + ",password = " + pros.getProperty("password"));


    }

}

4.通过反射创建运行时类的对象

@Test
public void test1() throws InstantiationException, IllegalAccessException {
    Class clazz = Person1.class;//获取当前运行时类
    Person1 o = (Person1)clazz.newInstance();
    
    /*
    newInstance():调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参的构造器。

    要想此方法正常的创建运行时类的对象,要求:
    1.运行时类必须提供空参的构造器
    2.空参的构造器的访问权限得够。通常,设置为public。


    在javabean中要求提供一个public的空参构造器。原因:
    1.便于通过反射,创建运行时类的对象
    2.便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器

     */
    System.out.println(o);
}

4.获取当前运行时类的属性(获取方法、构造器也相似)

/**
 * 获取当前运行时类的属性结构
 *
 * @author shkstart
 * @create 2019 下午 3:23
 */
public class FieldTest {

    @Test
    public void test1(){

        Class clazz = Person.class;

        //获取属性结构
        //getFields():获取当前运行时类及其父类中声明为public访问权限的属性
        Field[] fields = clazz.getFields();
        for(Field f : fields){
            System.out.println(f);
        }
        System.out.println();

        //getDeclaredFields():获取当前运行时类中声明的所有属性。(不包含父类中声明的属性)
        Field[] declaredFields = clazz.getDeclaredFields();
        for(Field f : declaredFields){
            System.out.println(f);
        }
    }
    
    
    也可以获取当前运行时类的属性的修饰符、名字、类型,用的不多,这里不做赘述
    也可以获取当前运行时类中的所有方法的修饰符 返回值 方法名 参数列表 注解 异常,这里不做赘述

总结:这是笔者初学JAVA的学习笔记,也许内容不够饱满、也许学习方法不够得当、也许理解不够深刻,但理论学的再多终究是理论,真正的进步还是要投身到具体实践中去实现,祝阅读本文的各位读者心态放稳、稳步前进,大家一起加油!!!
:本文大部分笔记来自于尚硅谷宋红康老师的课程笔记,其次就是本人对笔记的自我理解,如有侵权,速与我联系!!!

最后

以上就是机智白昼为你收集整理的JAVA基础及高级知识系统梳理面向对象——上面向对象——中面向对象——下JAVA高级部分JAVA常用类枚举类及注解集合的全部内容,希望文章能够帮你解决JAVA基础及高级知识系统梳理面向对象——上面向对象——中面向对象——下JAVA高级部分JAVA常用类枚举类及注解集合所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(44)

评论列表共有 0 条评论

立即
投稿
返回
顶部