第一天:面向对象进阶一
一、static静态 关键字
1、static是什么?
- static是静态的意思,可以修饰成员变量和成员方法
- static修饰成员变量表示该成员变量只在内存中只存储一份,可以被共享访问、修改。
2、成员变量可以分为2类
-
静态成员变量(有static修饰,属于类,内存中加载一次):常表示如在线人数信息、等需要被共享的信息,可以被共享访问。
格式:类名.变量名
对象.变量名(不推荐)
-
实例成员变量(无static修饰,存在于每个对象中):常表示姓名name、年龄age、等属于每个对象的信息。
格式:类名.变量名
3、static修饰成员变量的内存原理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X5ieQfpX-1684550850442)(.\img\static修饰成员变量的内存原理.png)]
4、成员方法的分类
- 静态成员方法(有static修饰,归属于类),建议用类名访问,也可以用对象访问。
- 实例成员方法(无static修饰,归属于对象),只能用对象触发访问
使用场景:
- 表示对象自己的行为的,且方法中需要访问实例成员的,则该方法必须声明成实例方法。
- 当该方法是以执行一个公用功能为目的,则可以申明成静态方法。
5、static修饰成员方法的内存原理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Dh6tkz6F-1684550850443)(.\img\static修饰成员方法的内存原理.png)]
6、static访问注意事项
- 静态方法只能访问静态的成员,不可以直接访问实例成员。
- 实例方法可以访问静态的成员,也可以访问实例成员。
- 静态方法中是不可以出现this关键字的
二、static应用知识:工具类
1、工具类是什么?
- 类中都是一些静态方法,每个方法都是以完成一个共用的功能为目的,这个类用来给系统开发人员共同使用的
2、使用工具类的好处
- 一是调用方便,二是提高代码复用
3、为什么工具类中的方法不用实例方法做
- 实例方法需要创建对象调用
- 此时用对象只是为了调用方法,这样只会浪费内存
4、工具栏定义时的其他要求
- 由于工具里面都是静态方法,直接用类名即可访问,因此,工具类无需创建对象,建议将工具类的构造器进行私有。
三、static应用知识:代码块
1、代码块概述
- 代码块是类的5大成分之一(成员变量、构造器、方法、代码块、内部类),定义在类中方法外
- 在java类下,使用{}括起来的代码被称为代码块
2、代码块分为
- 静态代码块:
- 格式:static{}
- 特点:需要通过static关键字修饰,随着类的加载而加载,并且自动触发,只执行一次
- 使用场景:在类加载的时候做一些静态数据初始化的操作,以便后续使用。
- 构造器代码块(了解,见得少):
- 格式:{}
- 每次创建对象,调用构造器执行时,都会执行该代码块中的代码,并且在构造器执行前执行
- 使用场景:初始化实例资源
三、static应用知识:单例设计模式
1、什么是设计模式(Design pattern)
- 开发中经常遇到一些问题,一个问题通常有n种解法的,但其中肯定有一种解法是最优的,这个最优的解法被人总结出来了,称之为设计模式。
- 设计模式有20多种,对应20多种软件开发中会遇到的问题。
- 学设计模式主要是学2点:
- 第一:这种模式用来解决什么问题
- 第二:遇到这种问题了,该模式是怎么写的,他是如何解决这个问题的
2、单例模式
- ·可以保证系统中,应用该模式的这个类永远只有一个实例,即一个类永远只能创建一个对象。
- 例如任务管理器对象我们只需要一个就可以解决问题了,这样可以节省内存空间。
3、饿汉单例设计模式
在用类获取对象的时候,对象已经提前为你准备好了
设计步骤:
- 定义一个类,把构造器私有
- 定义一个静态变量存储一个对象
4、懒汉单例模式
- 在真正需要该对象的时候,才去创建一个对象(延迟加载)
- 设计步骤:
- 定义一个类,把构造器私有。
- 定义一个静态变量存储一个对象。
- 提供一个返回单例对象的方法
四、面向对象的三大特征:继承
1、什么是继承
- Java中提供了一个关键字extends,用这个关键字,我们可以让一个类和另一个类建立起父子关系
- 作用:当子类继承父类后,就可以直接使用父类公共的属性和方法了
- 格式:子类 extends 父类
2、使用继承的好处
提高代码复用性,减少代码冗余,增强类的功能扩展性。
3、继承后子类的特点
- 子类继承父类,子类可以得到父类的属性和行为,子类可以使用。
- Java中子类更加强大
4、继承设计规范
- 子类们相同特征(共性属性,共性方法)放在父类中定义,子类独有的的属性和行为应该定义在子类自己里面。
- 为什么
- 如果子类的独有属性、行为定义在父类中,会导致其它子类也会得到这些属性和行为,这不符合面向对象逻辑。
5、继承的内存原理
6、继承的特点
- 子类可以继承父类的属性和行为,但是子类不能继承父类的构造器。
- 子类有自己的构造器,父类构造器用于初始化父类对象。
- 子类不能直接访问父类的私有成员,但是可以暴力访问
- 子类可以直接使用父类的静态成员(共享),但个人认为:子类不能继承父类的静态成员。(共享并非继承)
- Java是单继承模式:一个类只能继承一个直接父类。
- Java不支持多继承、但是支持多层继承。
- Java中所有的类都是Object类的子类。
- ]ava中所有类,要么直接继承了Object,要么默认继承了Object,要么间接继承了Object,Object是祖宗类。
7、在子类中访问成员(成员变量、成员方法)满足就近原则
- 先子类局部范围找
- 然后子类成员范围找
- 然后父类成员范围找,如果父类范围还没有找到则报错。
- 如果子父类中,出现了重名的成员,会优先使用子类的,此时如果一定要在子类中使用父类的成员可以通过super关键字,指定访问父类的成员。
8、什么是方法重写
在继承体系中,子类出现了和父类中一模一样的方法声明,我们就称子类这个方法是重写的方法。
应用场景:
- 当子类需要父类的功能,但父类的该功能不完全满足自己的需求时。
- 子类可以重写父类中的方法。
9、@Override重写注解
- @Override是放在重写后的方法上,作为重写是否正确的校验注解
- 加上该注解后如果重写错误,编译阶段会出现错误提示
- 建议重写方法都加@Override注解,代码安全,优雅
10、方法重写的注意事项和要求
- 重写方法的名称、形参列表必须与被重写方法的名称和参数列表一致。
- 私有方法不能被重写。
- 子类重写父类方法时,访问权限必须大于或者等于父类(暂时了解:缺省<protected<public)
- 子类不能重写父类的静态方法,如果重写会报错的。
11、子类继承父类后构造器的特点:
子类中所有的构造器默认都会先访问父类中无参的构造器,再执行自己。
为什么?
- 子类在初始化的时候,有可能会使用到父类中的数据,如果父类没有完成初始化,子类将无法使用父类的数据。
- 子类初始化之前,一定要调用父类构造器先完成父类数据空间的初始化。
怎么调用父类构造器的?
子类构造器的第一行语句默认都是:super() 不写也存在。
12、super调用父类有参数构造器的作用
-
初始化继承自父类的数据。
-
如果父类中没有无参数构造器,只有有参构造器,会报错。因为子类默认是调用父类无参构造器的。
-
子类构造器中可以通过书写super(…),手动调用父类的有参数构造器
13、this和super详情
- this:代表本类对象的引用;
- super:代表父类存储空间的标识。
this(…)和super(…)使用注意点:
-
子类通过this(…)去调用本类的其他构造器,本类其他构造器会通过super(…)去手动调用父类的构造器,最终还是会调用父类构造器的。
-
注意:this(…)和super(…)都只能放在构造器的第一行,所以二者不能共存在同一个构造器中。
第二天:面向对象进阶二
一、包
1、什么是包
- 包是用来分门别类的管理各种不同类的,类似于文件夹、建包利于程序的管理和维护
- 建包的语法格式:package公司域名倒写.技术名称。报名建议全部英文小写,且具备意义
- 建包语句必须在第一行,一般IDEA工具会帮助创建
2、导包
- 相同包下的类可以直接访问,不同包下的类必须导包,才可以使用!导包格式:import包名.类名;
- 假如一个类中需要用到不同类,而这个两个类的名称是一样的,那么默认只能导入一个类,另一个类要带包名访问。
二、权限修饰符
1、什么是权限修饰符
- 权限修饰符:是用来控制一个成员能够被访问的范围。
- 可以修饰成员变量,方法,构造器,内部类,不同权限修饰符修饰的成员能够被访问的范围将受到限制。
2、权限修饰符的分类和具体作用范围
- 权限修饰符:有四种作用范围由小到大(private->缺省->protected->public)
3、学完权限修饰符需要具备如下能力
- 能够识别别人定义的成员的访问范围。
- 自己定义成员(方法,变量,构造器等)一般需要满足以下条件
- 成员变量一般私有
- 方法一般公开
- 如果该成员只希望本类访问,使用private修饰
- 如果该成员只希望本类,同一个包下的其他类和子类访问,使用orotected修饰。
三、final关键字
1、final的作用
- final关键字是最终的意思,可以修饰(类、方法、变量)
- 修饰类 :表面该类是最终类,不能被继承
- 修饰方法:表明该方法是最终方法,不能被重写
- 修饰变量:表示该变量第一次赋值后,不能再次被赋值(有且仅能被赋值一次)
2、final修饰变量的注意
- final修饰的变量是基本类型:那么变量存储的数据值不能发生改变。
- final修饰的变量是引用类型:那么变量存储的地址值不能发生改变,但是地址指向的对象内容是可以发生变化的。
四、常量
1、常量
- 常量是使用了public static final修饰的成员变量,必须有初始化值,而且执行的过程中其值不能被改变。
- 常量的作用和好处:可以用于做系统的配置信息,方便程序的维护,同时也能提高可读性。
- 常量命名规范:英文单词全部大写,
多个单词下划线连接起来。
2、常量的执行原理
- 在编译阶段会进行“宏替换”,把使用常量的地方全部替换成真实的字面量
- 这样做的好处是让使用常量的程序的执行性能与直接使用字面量是一样的
3、选择常量做信息标志和分类
- 代码可读性好,实现了软编码形式
- 虽然可以实现可读性,但是入参值不受约束,代码相对不够严谨。(推荐使用枚举)
五、枚举
1、枚举的概念
- 枚举是Java中的一种特殊类型
- 枚举的作用:是为了做信息标志和信息分类
2、定义枚举的格式
修饰符 enum 枚举名称{
第一行是罗列枚举类实例的名称
}
enum Season{
SPRING , SUMMER , AUTUMN , WINTER
}
3、枚举的特征
- 枚举类都是继承了枚举类型:java.lang.Enum
- 枚举都是最终类,不可以被继承
- 枚举类的构造器都是私有的,枚举类对外不能创建对象
- 枚举类的第一行默认都是罗列枚举对象的名称的
- 枚举类相当于是多例模式
4、枚举做信息标志和分类
- 代码可读性好,入参约束严谨,代码优雅,是最好的信息分类技术!建议使用
六、抽象类
1、抽象类
- 在Java中abstract是抽象的意思,可以修饰类,成员方法。
- abstract修饰类,这个类就是抽象类;修饰方法,这个方法就是抽象方法。
2、注意事项
- 抽象方法只有方法名,不能声明方法体
- 一个类中如果定义了抽象方法,这个类必须声明成抽象类,否则报错
3、使用场景
- 抽象类可以理解成不完整的设计图,一般作为父类,让子类来继承。
- 当父类知道子类一定要完成某些行为,但是每个子类该行为的实现又不同,于是该父类就把该行为定义成抽象方法的形
式,具体实现交给子类去完成。此时这个类就可以声明成抽象类。
4、特征和注意事项
- 类有的成员(成员变量、方法、构造器)抽象类都具备
- 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
- 一个类继承了抽象类必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类
- 不能用abstract修饰变量、代码块、构造器。
- 最重要的特征:得到了抽象方法,失去了创建对象的能力(有得有失)
5、final和abstract是什么关系
- 互斥关系(不能同时出现)
- abstract定义的抽象类作为模板让子类继承,final定义的类不能被继承。
- 抽象方法定义通用功能让子类重写,final定义的方法子类不能重写。
6、模板方法模式
-
使用场景说明:
当系统中出现同一个功能多处在开发,而该功能中大部分代码是一样的,只有其中部分可能不同的时候
-
模板方法模式实现步骤
-
把功能定义成一个所谓的模板方法,放在抽象类中,模板方法中只定义通用且能确定的代码
-
模板方法中不能决定的功能定义成抽象方法让子类去实现
-
-
模板方法建议用final修饰,防止子类重写,是直接给子类使用的,不让子类重写。一旦重写,模板方法就失效了。
七、接口
1、接口的定义与特点
-
接口的格式
//接口用关键字interface来定义 public interface 接口名{ //常量 //抽象方法 }
-
JDK8之前接口中只能是抽象方法和常量,没有其他成分了
2、什么是接口
- 接口是一种规范(规范一定是公开的)
3、接口的用法
-
接口是用来被类实现(implements)的,实现接口的类称为实现类。实现类可以理解成所谓的子类
-
修饰符 class 实现类 implements 接口1,接口2,接口3...{ } //实现的关键字implements
-
从上面可以看出,接口可以被类实现,也可以被类多实现
4、接口实现的注意事项
- 一个类实现接口,必须重写全部接口的全部抽象方法,否则这个类需要定义成抽象类。
回顾之前的内容:
- 类和类的关系:单继承。
- 类和接口的关系:多实现。
- 接口和接口的关系:多继承,一个接口可以同时继承多个接口。
5、接口多继承的作用
- 规范合并,整合多个接口为同一个接口,便于子类实现。
6、JDK8开始接口新增方法
JDK8版本之后,Java只对接口的成员方法进行了新增,允许接口中直接定义带有方法体的代码
- 第一种:默认方法
- 类似之前写的普通实例方法:必须用default修饰
- 默认会public修饰。需要用接口的实现类的对象来调用
- 第二种:静态方法
- 默认会public修饰,必须static修饰
- 注意:接口的静态方法必须用本身的接口名来调用
- 第三种:私有方法
- 就是私有的实例方法,必须用private修饰,从JDK9开始有
- 只能在本类中被其他的默认方法或者私有方法访问
JDK8新增的3种方法我们自己在开发中很少使用,通常是
Java源码涉及到。现阶段需要理解、识别语法,明白调用关
系即可。
7、使用接口的注意事项
- 接口不能创建对象
- 一个类实现多个接口,多个接口中有同样的静态方法不冲突。
- 一个类继承了父类,同时又实现了接口,父类中和接口中有同名方法,默认用父类的。
- 一个类实现了多个接口,多个接口中存在同名的默认方法,不冲突,这个类重写该方法即可。
- 一个接口继承多个接口,是没有问题的,如果多个接口中存在规范冲突则不能多继承。
第三天,面向对象进阶三
一、面向对象的三大特征:多态
1、什么是多态
-
同类型的对象,执行同一个行为,会表现出不同的行为特征。
-
多态的常见形式
父类类型 对象名称 = new 子类构造器 接口 对象名称 = new 实现类构造器
-
多态中成员访问特点
- 方法调用:编译看左边,运行看右边。
- 变量调用:编译看左边,运行也看左边(多态侧重行为多态)
-
多态的前提
- 有继承/实现关系
- 有父类引用指向子类对象
- 有方法重写
2、多态的优势
- 在多态形式下,右边对象可以实现解耦合,便于扩展和维护。
- 定义方法的时候,使用父类型作为参数,该方法就可以接收这父类的一切子类对象,体现出多态的扩展性与便利。
3、多态下回产生一个问题
- 多态下不能使用子类的独有功能
4、多态下引用数据类型的类型转换
自动类型转换(从子到父):
子类对象赋值给父类类型的变量指向
强制类型转换(从父到子)
- 此时必须进行强制类型转换:子类对象变量 =(子类)父类类型的变量
- 作用:可以解决多态下的劣势,可以实现调用子类独有的功能。
- 注意:如果转型后的类型和对象真实类型不是同一种类型,那么在转换的时候就会出现ClassCastException
- Java建议强转转换前使用instanceof判断当前对象的真实类型,再进行转换
- 变量名 instanceof 真实类型
- 判断关键字左边的变量指向的对象的真实类型,是否是右边的类型或者是其子类类型,是则返回true,反之。
二、内部类
1、内部类
-
内部类就是定义在一个类里面的类,里面的类可以理解成(寄生),外部类可以理解成(宿主)
-
public class People{ //内部类 public class Heart{ } }
2、内部类的使用场景、作用
- 当一个事物的内部,还有一个部分需要完整的结构进行描述,而这个内部的完整结构又只为外部事物提供服务,那么整个内部的完整结构可以选择使用内部类来设计
- 内部类通常可以方便访问外部类的成员,包括私有的成员
- 内部类提供了更好的封装性,内部类本身就可以用private protectecd等修饰,封装性可以做更多控制
3、内部类的分类
-
静态内部类
-
什么是静态内部类
-
有static修饰,属于外部类本身
-
它的特点和使用与普通类是完全一样的,类有的成分它都有,只是位置在别人里面而已
-
public class Outer{ //静态成员内部类 public static class Inner{ } }
-
-
静态内部类创建对象的格式
-
格式:外部类名.内部类名 对象名 = new 外部类名.内部类构造器;
-
-
静态内部类的访问拓展
- 静态内部类中可以直接访问外部类的静态成员,外部类的静态成员只有一份可以被共享访问
- 静态内部类中不能直接访问外部类的实例成员。
-
-
成员内部类
-
什么是成员内部类
-
无static修饰,属于外部类的对象
-
JDK16之前,成员内部类中不能定义静态成员,JDK16开始也可以定义静态成员了
-
public class Outer{ //成员内部类 public class Inner{ } }
-
-
内部类创建对象的格式
-
格式:外部类名.内部类名 对象名 = new 外部类构造器.new内部类构造器;
-
成员内部类中可以直接访问外部类的静态成员,外部类的静态成员只有一份可以被共享访问
-
成员内部类中可以直接访问外部类的实例成员。因为必须先有外部类对象,才能有成员内部类对象。
-
-
注意:在成员内部类中访问所在外部类对象,格式:外部类名.this
-
-
局部内部类(鸡肋)
- 什么是局部内部类
- 局部内部类放在方法、代码块、构造器等执行体中。
- 局部内部类的类文件名为:外部类$N内部类.class。
- 什么是局部内部类
-
匿名内部类(重点)
-
本质上是一个没有名字的局部内部类,定义在方法中、代码块中、等。
-
作用:方便创建子类对象,最终目的为了简化代码。
-
格式:
-
new 类|抽象类名|接口名(){ 重写方法; }
-
Employee a = new Employee(){ public void work(){ } }
-
-
特点:
- 匿名内部类是一个没有名字的内部类
- 匿名内部类写出来就会产生一个匿名内部类的对象
- 匿名内部类的对象类型相当于是当前new的那个的类型的子类类型
-
匿名内部类在开发中的使用形式
- 匿名内部类可以作为方法的时机参数进行传输
- 开发中不是我们主动去定义匿名内部类的,而是别人需要我们写或者我们可以写的时候才会使用。匿名内部类的代码可以实现代码进一步的简化(回扣主题)
-
三、常用API
1、什么是API
- API(Application Programming interface)应用程序编程接口
- 简单来说:就是Java帮我们已经写好的一些方法,我们直接拿过来就可以了
2、Object
作用:
- 一个类要么默认继承了Object类,要么间接继承了Object类,Object类是Java中的祖宗类
- Object类的方法是一切子类都可以直接使用的,所以我们要学习Object类的方法
常用方法:
方法名 | 说明 |
---|---|
public Sting toString() | 默认返回当前对象在堆内存中的地址信息:类的全限名@内存地址 |
public Boolean equals(Object o) | 默认是比较当前对象与另一个对象的地址是否相同,相同放回true,不同返回false |
toString方法
- 开发中直接输出对象,默认输出对象的地址其实是毫无意义的
- 开发中输出对象变量,更多的时候是希望看到对象的内容数据而不是对象的地址信息
- 父类toString()方法存在的意义就是为了被子类重写,以便返回对象的内容信息,而不是地址信息
equals方法
- 直接比较两个对象的地址是否相同完全可以用==替代equals
- 父类equals方法存在的意义就是为了被子类重写,以便子类自己来定制比较规则
3、Objects
方法名 | 说明 |
---|---|
public static boolean equals(Object a, Object b) | 比较两个对象的,底层会先进行非空判断,从而可以避免空指针异常,再进行equals比较 |
public static boolean isNull(Object obj) | 判断变量是否为null,为null返回true否则返回false |
Objects概述
- Objects类与Object还是继承关系,Objects类是用JDK1.7开始才有的
- 在重写equals方法时官方在进行字符串笔记时,没有对象自己的equlas方法,而是选择了Objects的equals方法来比较两个对象
- Objects的equals方法比较的结果是一样的,但是更安全
4、StringBuilder类
构造器名称 | 说明 |
---|---|
public StringBuilder() | 创建一个空白的可变的字符串对象,不包含任何内容 |
public StringBuilder(String str) | 创建一个指定字符串内容的可变字符串对象 |
常用方法
方法名称 | 说明 |
---|---|
public String Builder append(任意类型) | 添加数据并返回StringBuilder对象本身 |
public StringBuilder reverse() | 将对象的内容反转 |
public int length() | 返回对象内容长度 |
public String toString() | 通过toString()就可以实现把StringBuilder转换为String |
StringBuilder概述
- StringBuilder是一个可变的字符串类,我们可以把它看成是一个对象的容器
- 作用:提高字符串的操作效率,如拼接、修改等
StringBuilder内存原理
5、Math类
- 包含执行基本数字运算的方法,Math类没有提供公开的构造器
- 如果类的成员都是静态,通过类名就可以直接调用
常用方法
方法名 | 说明 |
---|---|
public static int abs(int a) | 获取参数绝对值 |
public static double ceil(double a) | 向上取整 |
public static double floor(double a) | 向下取整 |
public static int round(float a) | 四舍五入 |
public static int max(int a, int b) | 获取两个int中的较大值 |
public static double pow(double a,double b) | 返回a的b次方 |
public static double random() | 返回值为double的随机数,范围[0.0,1.0) |
6、System类
概述
- System的功能是通用的,都是直接用类名调用即可,所以System不能被实例化
常用方法
方法名 | 说明 |
---|---|
public static void exit(int status) | 终止当前运行的Java虚拟机,非0表示异常终止 |
public static long currentTimeMillis() | 返回当前系统的时间毫秒值类型 |
public static void arraycopy(数据源数组,起始索引,目的地数组,起始索引,拷贝个数) | 数组拷贝 |
时间毫秒值
- 计算机认为时间是有起点的,起始时间:1970年1月1日 00:00:00
- 时间毫秒值:指的是从1970年1月1日 00:00:00走到此刻的总毫秒数,应该是很大的。1s==1000ms
- **原因:**1969年8月,贝尔实验室的程序员肯汤普逊利用妻儿离开一个月的机会,开始着手创造一个全新的革命性的操作系统,他使用B编译语言在老旧的PDP-7机器上开发出了Unix的一个版本。随后,汤普逊和同事丹尼斯里奇改进了B语言,开发出了C语言,重写了UNX。
- 1970年1月1日算C语言的生日
7、BigDecimal
作用
- 用于解决浮点型运算精度失真的问题
使用步骤
-
创建对象BigDecimal封装浮点型数据(最好的方式是调用方法)
-
public static BigDecimal valueOf(double val) //包装浮点数成为BigDecimal对象
常用API
方法名 | 说明 |
---|---|
public BigDecimal add(GigDecimal b) | 加法 |
public BigDecimal subtract(GigDecimal b) | 减法 |
public BigDecimal multiply(GigDecimal b) | 乘法 |
public BigDecimal divide(GigDecimal b) | 除法 |
public BigDecimal divide(另一个BigDecimaly对象,精确几位,舍入模式) | 除法 |
**注意:**BigDecimal只做精度运算,如果给的值不能精度运算,就会报错.可以使用参数来限制精确位数
第四天:常用API
一、日期与时间的处理
1、Date类
概述:
- Date类的对象在Java中代表的是当前所在系统的此刻日期时间
构造器:
名称 | 说明 |
---|---|
public Date() | 创建一个Data对象,代表的是系统当前此刻日期时间 |
public Date(long time) | 把时间毫秒值转换成Date日期对象(年月日形式) |
常用方法:
名称 | 说明 |
---|---|
public long getTime() | 获取时间对象的毫秒值 |
public void setTime(long time) | 设置日期对象的时间为当期时间毫秒值对应的时间 |
Date类记录时间的2种形式
- 日期对象(创建一个日期对象代表了当前此刻日期时间对象【年,月,日】)
- 时间毫秒值(指的是从1970年1月1日00:00:00走到此刻的总的毫秒数)
2、SimpleDateFormat
作用:
- 可以对Date对象或时间毫秒值格式化成我们喜欢的时间形式
- 也可以吧字符串的时间形式解析成日期对象
构造器:
构造器 | 方法 |
---|---|
public SimpleDateFormat() | 构造一个SimpleDateFormat,使用默认格式 |
public SimpleDateFormat(String pattern) | 构造一个SimpleDateFormat使用指定格式 |
常用方法:
名称 | 说明 |
---|---|
public final String format(Date date) | 将日期格式转化成日期/时间字符串 |
public final String format(Object date) | 将时间毫秒值转化成日期/时间字符串 |
public Date parse(String source) | 从给定字符串的开始解析文本以生成日期 |
格式化的时间形式的常用的模式对应关系如下:
y—— 年,M——月,d——日,H——时,m——分,s——秒,E——星期,a——上午/下午
例如:
2020-11-1113:27:06——yyyy-MM-dd HH:mm:ss
2020年11月11日13:27:06——
yyyy年MM月dd日HH:mm:ss
注意:使用parse时输入的字符串格式和创建对象的时候的格式必须相同
3、Calendar
概述:
- Calendar代表了系统此刻日期对应的日历对象
- Calendar是一个抽象类,不能直接创建对象(要创建日历对象需要调方法)
创建对象的方法:
方法名 | 说明 |
---|---|
public static Calendar getInstance() | 获取当前日历对象 |
常用方法:
方法名 | 说明 |
---|---|
public int get(int field) | 取日期中的某个字段信息 |
public void set(int field,int value) | 修改日历的某个字段信息 |
public void add(int field,int amount) | 为某个字段增加或减少指定的值 |
public final Date getTimr() | 拿到此刻日期对象 |
public long getTimeInMillis() | 拿到此刻时间毫秒值 |
注意:日历是可变日期对象,一旦修改后其对象本身表示的时间将产生变化
二、JDK8新增日期类
1、概述:
- 从JDK8开始,Java。time包提供了新的日期和时间API,主要涉及的类型有:
- LocalDate:不包含具体时间的日期
- LocalTime:不包含日期的时间
- LocalDateTime:包含了日期和时间
- Instant:代表的是时间戳
- DateTimeFormatter:用于做时间的格式化和解析的
- Duration:用于计算两个“时间”间隔
- Period:用于计算两个“日期”间隔
- 新增的API严格区分了时刻,本地日期,本地时间,并且,对日期和时间进行运算更加方便
- 其次,新API的类型几乎全部是不变类型(和String的使用类似),可以放心 使用不必担心被修改
2、LocalDate、LocalTime、LocalDateTime
创建对象
方法名 | 说明 | |
---|---|---|
public static Xxxx now(); | 静态方法,根据当前时间创建对象 | LocaDate localDate = LocalDate.now();LocalTime llocalTime = LocalTime.now();LocalDateTime localDateTime = LocalDateTime.now(); |
public static Xxxx of(…); | 静态方法,指定日期/时间创建对象 | LocalDate localDate1 = LocalDate.of(2099 , 11,11);LocalTime localTime1 = LocalTime.of(11, 11, 11);LocalDateTime localDateTime1 = LocalDateTime.of(2020, 10, 6, 13, 23, 43); |
LocalDate、LocalTime、LocalDateTime获取信息的API
方法名 | 说明 |
---|---|
public int geYear() | 获取年 |
public int getMonthValue() | 获取月份(1-12) |
Public int getDayOfMonth() | 获取月中第几天乘法 |
Public int getDayOfYear() | 获取年中第几天 |
Public DayOfWeek getDayOfWeek() | 获取星期 |
LocalDateTime的转换API
方法名 | 说明 |
---|---|
public LocalDate toLocalDate() | 转换成一个LocalDate对象 |
public LocalTime toLocalTime() | 转换成一个LocalTime对象 |
修改相关API
- LocalDateTime 综合了 LocalDate 和 LocalTime 里面的方法,所以下面只用 LocalDate 和 LocalTime 来举例。
- 这些方法返回的是一个新的实例引用,因为LocalDateTime 、LocalDate 、LocalTime 都是不可变的。
方法名 | 说明 |
---|---|
plusDays, plusWeeks, plusMonths, plusYears | 向当前 LocalDate 对象添加几天、 几周、几个月、几年 |
minusDays, minusWeeks, minusMonths, minusYears | 从当前 LocalDate 对象减去几天、 几周、几个月、几年 |
withDayOfMonth, withDayOfYear, withMonth, withYear | 将月份天数、年份天数、月份、年 份 修 改 为 指 定 的 值 并 返 回 新 的 LocalDate 对象 |
isBefore, isAfter | 比较两个 LocalDate |
2、instant
instant时间戳
- JDK8获取时间戳特别简单,且功能更丰富。Instant类由一个静态的工厂方法now()可以返回当前时间戳
- 时间戳是包含日期和时间的,与java.util.Date很相似,事实上Instant就是类似JDK8以前的Date
- Instant和Date这两个类可以进行转换
Instant instant = Instant.now();
System.out.println("当前时间戳是:" + instant);
Date date = Date.from(instant);
System.out.println("当前时间戳是:" + date);
instant = date.toInstant();
System.out.println(instant);
3、DateTimeFormatter
- 在JDK8中,引入了一个全新的日期与时间格式器DateTimeFormatter
- 正反都能调用format方法
LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt);//2021-03-01T15:09:17.444190900
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String ldtStr = ldt.format(dtf);
System.out.println(ldtStr);//2021-03-01 15:09:17
String ldtStr1 = dtf.format(ldt);
System.out.println(ldtStr1);//2021-03-01 15:09:17
4、Duration/Period
Period
- 在ava8中,我们可以使用以下类来计算日期间隔差异:java.time.Period
- 主要是Period类方法getYears(),getMonths()和getDays()来计算,只能精确到年月日。
- 用于LocalDate之间的比较。
LocalDate today = LocalDate.now();
System.out.println(today); // 2021-03-01
LocalDate birthDate = LocalDate.of(1995, 1, 11);
System.out.println(birthDate); // 1995-01-11
Period period = Period.between(birthDate, today);
System.out.printf("年龄 : %d 年 %d 月 %d 日", period.getYears(), period.getMonths(), period.getDays());
Duration
- 在ava8中,我们可以使用以下类来计算时间间隔差异:java.time.Duration
- 提供了使用基于时间的值测量时间量的方法。
- 用于LocalDateTime之间的比较。也可用于Instant之间的比较。
LocalDateTime today = LocalDateTime.now();
System.out.println(today);
LocalDateTime birthDate = LocalDateTime.of(1990,10,1,10,50,30);
System.out.println(birthDate);
Duration duration = Duration.between(birthDate, today);//第二个参数减第一个参数System.out.println(duration.toDays());//两个时间差的天数System.out.println(duration.toHours());//两个时间差的小时数System.out.println(duration.toMinutes());//两个时间差的分钟数System.out.println(duration.toMillis());//两个时间差的毫秒数System.out.println(duration.toNanos());//两个时间差的纳秒数
5、ChronoUnit
- ChronoUnit类可用于在单个时间单位内测量一段时间,这个工具类是最全的了,可以用于比较所有的时间单位
LocalDateTime today = LocalDateTime.now();
System.out.println(today);
LocalDateTime birthDate = LocalDateTime.of(1990,10,1,10,50,30);
System.out.println(birthDate);
System.out.println("相差的年数:" + ChronoUnit.YEARS.between(birthDate, today));
System.out.println("相差的月数:" + ChronoUnit.MONTHS.between(birthDate, today));
System.out.println("相差的周数:" + ChronoUnit.WEEKS.between(birthDate, today));
System.out.println("相差的天数:" + ChronoUnit.DAYS.between(birthDate, today));
System.out.println("相差的时数:" + ChronoUnit.HOURS.between(birthDate, today));
System.out.println("相差的分数:" + ChronoUnit.MINUTES.between(birthDate, today));
System.out.println("相差的秒数:" + ChronoUnit.SECONDS.between(birthDate, today));
System.out.println("相差的毫秒数:" + ChronoUnit.MILLIS.between(birthDate, today));
System.out.println("相差的微秒数:" + ChronoUnit.MICROS.between(birthDate, today));
System.out.println("相差的纳秒数:" + ChronoUnit.NANOS.between(birthDate, today));
System.out.println("相差的半天数:" + ChronoUnit.HALF_DAYS.between(birthDate, today));
System.out.println("相差的十年数:" + ChronoUnit.DECADES.between(birthDate, today));
System.out.println("相差的世纪(百年)数:" + ChronoUnit.CENTURIES.between(birthDate, today));
System.out.println("相差的千年数:" + ChronoUnit.MILLENNIA.between(birthDate, today));
System.out.println("相差的纪元数:" + ChronoUnit.ERAS.between(birthDate, today));
三、包装类
1、包装类
- 其实就是8种基本数据类型对应的引用类型
基本数据类型 | 引用数据类型 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
char | Character |
float | Float |
double | Double |
boolean | Boolean |
2、为什么提供包装类
- Java为了实现一切皆对象,为8种基本数据类型提供了对应的引用类型
- 后面的集合和泛型其实也只能支持包装类型,不支持基本类型
**自动装箱:**基本类型数据和变量可以直接赋值给包装类型的变量
**自动拆箱:**包装类型的变量可以直接赋值给基本数据类型的变量
3、包装类的特有功能
- 包装类的变量默认值可以是null,容错率更高
- 可以把基本类型的数据转换成字符串类型(用处不大)
- 可以把字符串类型的数值转换成真实的数据类型
四、正则表达式
1、概述
- 正则表达式可以用一些规定的字符来制定规则,并用来校验数据格式的合法性。
2、正则表达式规则
-
字符串对象提供了匹配正则表达式规则的API
public 博哦了按matches(String regex)//判断是否匹配正则表达式,匹配返回true,不匹配返回false
-
字符类(默认匹配一个字符)
[abc] 只能是a, b, 或c [^abc] 除了a, b, c之外的任何字符 [a-zA-Z] a到z A到Z,包括(范围) [a-d[m-p]] a到d,或m通过p:([a-dm-p]联合) [a-z&&[def]] d, e, 或f(交集) [a-z&&[^bc]] a到z,除了b和c:([ad-z]减法)
-
预定义的字符类(默认匹配一个字符)
. 任何字符 \d 一个数字: [0-9] \D 非数字: [^0-9] \s 一个空白字符: [ \t\n\x0B\f\r] \S 非空白字符: [^\s] \w [a-zA-Z_0-9] 英文、数字、下划线 \W [^\w] 一个非单词字符
-
贪婪的量词
X? X , 一次或根本不 X* X,零次或多次 X+ X , 一次或多次 X {n} X,正好n次 X {n, } X,至少n次 X {n,m} X,至少n但不超过m次
3、正则表达式在方法中的使用
方法名 | 说明 |
---|---|
public String replaceAll(String regex,String newStr) | 按照正则表达式匹配的内容进行替换 |
public String[] split(String regex): | 按照正则表达式匹配的内容进行分割字符串,反回一个字符串数组。 |
五、Arrays类
1、概述
- 数组操作工具类,专门用于操作数组元素的。
2、常用API
方法名 | 说明 |
---|---|
public static String toString(类型[] a) | 返回数组的内容(字符串形式) |
public static void sort(类型[] a) | 对数组进行默认升序排序 |
public static void sort(类型[] a, Comparator<? super T> c) | 使用比较器对象自定义排序 |
public static int binarySearch(int[] a, int key) | 二分搜索数组中的数据,存在返回索引,不存在返回-1 |
3、Arrays类的排序方法
方法名 | 说明 |
---|---|
public static void sort(类型[] a) | 对数组进行默认升序排序 |
public static void sort(类型[] a, Comparator<? super T> c) | 使用比较器对象自定义排序 |
自定义排序规则:
- 设置Comparator接口对应的比较器对象,来定制比较规则。
如果认为左边数据 大于 右边数据 返回正整
如果认为左边数据 小于 右边数据 返回负整数
如果认为左边数据 等于 右边数据 返回0
六、常见算法
1、选择排序
选择排序的思想:
- 每轮选择当前位置,开始找出后面的较小值与该位置交换
选择排序的关键:
- 确定总共需要几轮:数组的长度-1
- 控制每轮从以前位置为基准,与后面元素选择几次。
package com.zhl.d8_binarysezrch;
import java.util.Arrays;
public class Test1 {
public static void main(String[] args) {
//选择排序
int[] arr = {2,5,3,1,4};
for (int i = 0; i < arr.length-1; i++) {
for (int j = i+1;j < arr.length ; j++){
if (arr[i]>arr[j]){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
System.out.println(Arrays.toString(arr));
}
}
2、二分查找
基本查找:
在数据量特别大的时候,基本查找从前往后查找的性能是很差的
二分查找:
- 二分查询性能好,二分查找的前提是必须是排好序的数据
- 二分查找相当于每次去掉一半的查找范围
- 二分查找正常的检索条件应该是开始位置min<=结束位置max
package com.zhl.d8_binarysezrch;
public class Test2 {
public static void main(String[] args) {
//二分查找
int[] arr = {1,3,5,7,9,11,13,15,17,19};
// 0,1,2,3,4,5 ,6 ,7 ,8 , 9
System.out.println(binarySearch(arr, 19));
}
/**
* 二分查找法
* @param arr 数组
* @param data 查找数据
* @return 索引/-1(找不到元素)
*/
public static int binarySearch(int[] arr,int data){
int left = 0;
int right = arr.length-1;
while (left<=right){
int midIndex = (left+right)/2;
if (data > arr[midIndex]){
left = midIndex+1;
}else if (data < arr[midIndex]){
right = midIndex-1;
} else {
return midIndex;
}
}
return -1;
}
}
七、Lambda表达式
1、概述
-
Lambda表达式是JDK8开始后的一种新语法形式
-
作用:简化函数式接口的匿名内部类的代码写法
-
格式:
(匿名内部类被重写方法的形参列表) ->{ 被重写方法的方法体代码。 } 注:-> 是语法形式,无实际含义
-
注意:Lambda表达式只能简化函数式接口的匿名内部类的写法形式
-
什么是函数式接口?
- 首先必须是接口、其次接口中有且只有一个抽象方法的形式;
- 通常我们会在接口上加一个@FunctionalInterfaceh注解,标记该接口必须是满足函数式接口
2、Lamdba表达式的省略写法(在基础上继续简化)
- 参数类型可以省略不写
- 如果只有一个参数,参数类型可以省略不写,同事()也可以省略
- 如果Lambda表达式的方法体只有一行代码。可以省略大括号不写,同时要省略分号;
- 如果Lamdba表达式的方法体只有一行代码。可以省略大括号不写。此时,如果这行代码是return语句,必须省略return不写,同事也必须省略分号不写
第五天:集合
一、集合概述
- 集合和数组都是容器
1、数组的特点
- 数组定义完成并启动后,类型确定,长度固定。
- 在进行增删数据操作的时候,数组是不太合适的,增删数据都需要放弃原有数组或者移位
2、数组适合的场景
- 当业务数据的个数是固定的,且同一批数据类型的时候,可以采取定义数组存储
3、集合的特点:
集合是java中存储对象的一种容器
- 集合的大小不固定,启动后可以动态变化,类型也可以不固定。集合更像气球。
- 集合非常时候做元素的增删操作
- 注意:集合中只能存储引用类型的数据,如果要存储基本数据类型数据可以选用包装类。
4、集合适合的场景
- 数据个数不确定,需要进行增删元素的时候。
二、Collection集合的体系特点
1、集合体系结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hvhY4zlk-1684550850446)(.\img\集合体系结构.png)]
- Collection单例集合,每个元素(数据)只包含一个值
- Map双列集合,每个元素包含两个值(键值对)
- 注意:前期先掌握Collection集合体系的使用
2、Collection集合体系
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L6oHpoKy-1684550850447)(.\img\Collection集合体系.png)]
3、Collection集合特点
- **List系列集合:**添加的元素是有序,可重复,有索引。
- **ArrayList、LinekList:**有序,可重复,有索引
- **Set系列集合:**添加的元素是无序、不重复、无索引
- **HashSet:**无序、不重复、无索引
- **LinkdeHashSet:**有序、不重复、无索引
- **TreeSet:**按照大小默认升序排序、不重复、无索引
4、集合对于泛型的支持
- 集合都是支持泛型的,可以在编译阶段约束集合只能操作某种数据类型。
- 注意:集合和泛型都只能支持引用数据类型,不支持基本数据类型,所以集合中存储的元素都认为是对象
- 如果要存储基本数据类型要使用包装类
三、Collection集合常用API
1、Collection集合
- Collection是单列集合的祖宗接口,它的功能是全部单列集合都可以继承使用的。
2、常用API
方法名称 | 说明 |
---|---|
public boolean add(E e) | 把给定的对象添加到当前集合中 |
public void clear() | 清空集合中所有的元素 |
public boolean remove(E e) | 把给定的对象在当前集合中删除 |
public boolean contains(Object obj) | 判断当前集合中是否包含给定的对象 |
public boolean isEmpty() | 判断当前集合是否为空 |
public int size() | 返回集合中元素的个数。 |
public Object[] toArray() | 把集合中的元素,存储到数组中 |
四、Collection集合的遍历方式
1、迭代器
概述:
- 遍历就是一个一个把容器中的元素访问一遍
- 迭代器在Java中的代表是Iterator,迭代器是结合的专用遍历方式
Collection集合获取迭代器
方法名称 | 说明 |
---|---|
Iterator iterator() | 返回集合中的迭代器对象,该迭代器对象默认指向当前集合的0索引 |
常用方法:
方法名称 | 说明 |
---|---|
boolean hasNext() | 询问当前位置是否有元素存在,存在返回true ,不存在返回false |
E next() | 获取当前位置的元素,并同时将迭代器对象移向下一个位置,注意防止取出越界。 |
2、foreach、增强for循环
-
增强for循环:既可以遍历集合也可以遍历数组
-
它是JDK5之后出现的,其内部原理是一个Iterator迭代器,遍历集合相当于是迭代器的简化写法
-
实现Iterable接口的类才可以使用迭代器和增强for,Collection接口已经实现了Iterable接口。
-
格式
for(元素数据类型 变量名:数组或者Collection集合){ //在此处使用变量即可,该元素就是元素 }
3、lambda表达式
- 得益于JDK8开始的新技术Lambda表达式,提供了一种更简单、更直接的遍历集合的方式
方法名称 | 说明 |
---|---|
default void forEach(Consumer<? super T> action): | 结合lambda遍历集合 |
五、常见数据结构
1、数据结构概述、栈、队列
数据结构概述
- 数据结构是计算机底层存储、组织数据的方式。是指数据相互之间是以什么方式排列在一起的
- 通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率
常见的数据结构
- 队列:先进先出,后进后出。
- 栈:后进先出,先进后出。
- 数组:内存连续区域,查询快,增删慢。
- 链表:元素是游离的,查询慢,首尾操作极快。
- 二叉树:永远只有一个根节点,每个结点不超过2个子节点的树。
- 查找二叉树:小的左边,大的右边,但是可能树很高,查询性能变差。
- 平衡查找二叉树:让树的高度差不大于1,增删改查都提高了。
- 红黑树(就是基于红黑规则实现了自平衡的排序二叉树)
栈数据结构的执行特点
- 后进先出,先进后出
- 数据进入栈模型的过程称为:压/进栈
- 数据离开栈模型的过程称为:顶/出栈
队列
- 先进先出,后进后出
- 数据从后端进入队列模型的过程称为:入队列
- 数据从前端离开队列模型的过程称为:出队列
2、数组
-
数组是一种添加快,增删慢的模型
-
==查询速度快:==查询数据通过地址值和索引定位,查询任意数据耗时相同。(元素在内存中是连续存储的)
-
**删除效率低:**要将原始数据删除,同事后面每个数据前移
-
**添加效率极低:**添加位置后的每个数据后移,载添加元素
3、链表
-
链表中的元素是在内存中不连续存储的,每个元素节点包含数据值和下一个元素地址
-
**链表查询慢:**无论查找哪个数据都要从头开始找(对比数组)
-
链表增删相对快(对比数组)
-
种类
- 单向链表
- 双向链表
- 增删首位数据较快
4、二叉树、二叉查找树
二叉树概念:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V5ipg8Hh-1684550850448)(.\img\二叉树概念.png)]
二叉树特点:
- 只有一个根节点,每个节点最对支持2个直接子节点
- ==节点的度:==节点拥有的子树个数,二叉树的度不大于2叶子节点度为0的节点,也称之为终端节点
- ==高度:==叶子节点的高度为1,叶子节点的父节点高度为2,依次类推,根节点的高度最高
- ==层:==根节点在第一层,依次类推
- ==兄弟节点:==拥有共同父节点的节点称为兄弟节点
二叉查找树:
二叉查找树又称二叉排序树或者二叉搜索树。
二叉查找树的特点:
- 每一个节点上最多有两个子节点
- 左子树上所有节点的值小于根节点的值
- 右子树上所有节点的值大于根节点的值
- 目的:提高检索数据的性能
二叉查找树存储规则:
- 小的存左边
- 大的存右边
- 相等的不存
5、平衡二叉树
二叉查找树存在的问题:
- 出现瘸子现象,导致查询的性能与单链表一样,查询速度变慢
平衡二叉树:
- 平衡二叉树是在满足查找二叉树的大小规则下,让树尽可能矮小,以此提高查询数据的性能
平衡二叉树的要求:
- 任意节点的左右两个子树的高度差不超过1,任意节点的左右两个子树都是一颗平衡二叉树
平衡二叉树在添加元素后肯导致不平衡:
- 基本策略是进行左旋,或者右旋保证平衡
平衡二叉树-旋转的四种情况:
-
左左
当根节点左子树的左子树有节点插入,导致二叉树不平衡
-
左右
当根节点左子树的右子树有节点插入,导致二叉树不平衡
-
右右
当根节点右子树的右子树有节点插入,导致二叉树不平衡
-
右左
当根节点右子树的左子树有节点插入导致二叉树不平衡
6、红黑树
红黑树概述:
- 红黑树是一种自平衡的二叉树,是计算机科学中用到的一种数据结构
- 1972年出现,当时被称为平衡二叉B树。1978年被修改为如今的“红黑树”
- 每个节点可以是红或者黑;红黑树不是通过高度平衡的,它的平衡是通过“红黑规则”进行实现的
红黑规则:
- 每一个节点或者是红色的,或者是黑色的,根节点必须是黑色。
- 如果一个节点没有子节点或者父节点,则该节点相应的指针属性为Nil,这些Nil视为叶节点,叶节点是黑色的
- 如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连的情况)
- 对每一个节点,从该节点到其所有后代节点的简单路径上,均包含相同数目的黑色节点。
添加节点:
- 添加节点的颜色可以是红色的,也可以是黑色的
- 默认用红色效率高
红黑树增删改查的性能都很好
六、List系列集合
1、List集合特点、特有API
list系列集合特点:
- ArrayList、LinekdList:有序,可重复,有索引
- 有序:存储和取出的元素顺序一致
- 有索引:可以通过索引操作元素
- 可重复:存储的元素可以重复
List系列特有方法:
-
List集合因为支持索引,所以多了很多索引操作的独特API,其他Collection的功能List也继承了
-
方法名称 说明 void add(int index,E element) 在此集合中的指定位置插入指定的元素 E remove(int index) 删除指定索引处的元素,返回被删除的元素 E set(int index,E element) 修改指定索引处的元素,返回被修改的元素 E get(int index) 返回指定索引处的元素
2、List集合的遍历方式小结
List集合的遍历方式:
- 迭代器
- 增强for循环
- Lambda表达式
- for循环(因为List集合存在索引)
package com.zhl.d5_collection_list;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
public class Test2 {
public static void main(String[] args) {
List<Integer> list1 = new LinkedList<>();
list1.add(11);
list1.add(22);
list1.add(33);
list1.add(44);
list1.add(66);
System.out.println(list1);
System.out.println("--------------------");
//- 迭代器
Iterator it = list1.iterator();
System.out.println(it.next());
System.out.println(it.next());
System.out.println(it.next());
System.out.println(it.next());
System.out.println(it.next());
System.out.println("--------------------");
//- 增强for循环
for (Integer e : list1) {
System.out.println(e);
}
System.out.println("--------------------");
//- Lambda表达式
list1.forEach(e-> System.out.println(e));
System.out.println("--------------------");
//- for循环(因为List集合存在索引)
for (int i = 0; i < list1.size(); i++) {
System.out.println(list1.get(i));
}
}
}
3、ArrayList集合的底层原理
- ArrayList/底层是基于数组实现的,根据查询元素快,增删需要做元素的位移操作。
- 第一次创建集合并添加第一个元素的时候,在底层创建一个默认长度为10的数组
4、LinkedList集合的底层原理
- LinkedList底层基于双链表实现的,查询元素慢,增删首尾元素是非常快的,所以多了很多首位操作的特有API
特有API
方法名称 | 说明 |
---|---|
public void addFirst(E e) | 在该列表开头插入指定的元素 |
public void addLast(E e) | 将指定的元素追加到此列表的末尾 |
public E getFirst() | 返回此列表中的第一个元素 |
public E getLast() | 返回此列表中的最后一个元素 |
public E removeFirst() | 从此列表中删除并返回第一个元素 |
public E removeLast() | 从此列表中删除并返回最后一个元素 |
七、补充知识:集合的并发修改异常问题
问题引出:
- 当我们从集合中找出某个元素并删除时,肯会出现一种并发修改异常
哪些遍历方式存在问题:
- 迭代器遍历集合直接用集合删除元素时会出现
- 增强for循环遍历集合且直接用集合删除元素会出现
那种遍历不会出现问题:
- 迭代器遍历集合但是使用迭代器自己的方法删除
- 使用for循环遍历并删除元素不会存在这个问题
八、补充知识:泛型深入
1、泛型的概述和优势
泛型的概述:
- 泛型:是JDK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查
- 泛型的格式:<数据类型>
- 注意:泛型只能支持引用数据类型
- 集合体系的全部接口和实现类都是支持泛型的使用的
泛型的好处:
- 统一数据类型
- 把运行时期的问题提前到编译期间,避免了强制类型转换可能出现的异常,因为编译阶段类型就能确定下来
- 泛型可以在很多地方进行定义
- 类后面——泛型类
- 方法后面——泛型方法
- 接口后面——泛型接口
2、自定义泛型类
泛型类的概述:
- 定义类的同时定义了泛型的类就是泛型类
- 泛型类的格式:修饰符 class 类名<泛型变量>{}
- 此处泛型变量T可以随便写为任意标识,场景的如E、T、K、V等;
- 作用:编译阶段可以指定数据类型,类似于集合的作用
泛型类原理:
- 把出现泛型变量的地方全部换成传输的真实数据类型
3、自定义泛型方法
泛型方法的概述:
- 定义方法的同时定义了泛型的方法就是泛型方法
- 泛型方法的格式:修饰符<泛型变量>方法返回值 方法名(形参列表){}
- 作用:方法中可以使用泛型接受一切实际类型参数,方法更具备通用性
泛型方法原理:
- 把出现泛型变量的地方全部换成传输的真实数据类型
4、自定义泛型接口
泛型接口的概述:
- 使用了泛型定义的接口就是泛型接口
- 泛型方法的格式:修饰符 interface 接口名称<泛型变量>{}
- 作用:泛型接口可以让实现类选择当前功能需要操作的数据类型
泛型接口的原理:
- 实现类可以在实现接口的时候传入自己操作的数据类型,这样重写的方法都将是针对该类型的操作
5、泛型通配符、上下限
通配符:
- ?可以在“使用泛型”的时候代表一切类型
- E T K V是在定义泛型的时候使用一切类型
泛型的上下限:
- ?extends Car:?必须是Car或者其子类 泛型上限
- ?super Car:?必须是Car或者其父类 泛型下线
第六天:Set系列集合、Map系列集合
一、Set系列集合
1、Set系列集合概述
Set系列集合特点:
- 无序:存取顺序不一致
- 不重复:可以去重复
- 无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素
Set集合实现类特点:
- HashSet:无序、不重复、无索引
- LinkedHashSet:有序、不重复、无索引
- TreeSet:排序、不重复、无索引
Set集合的功能上基本与Collection的API一致
2、HashSet元素无序底层原理:哈希表
HashSet底层原理:
- HeashSet集合底层采取哈希表存储的数据
- 哈希表是一种对于增删改查数据性能都较好的结构
哈希表的组成:
- JDK8之前,底层使用数组+链表组成
- JDK8之后,底层采用数组+链表+红黑树组成
- 哈希值:
- 是JDK根据对象的地址,按照某种规则算出来的int类型的数值
- Object类的API
- public int hashCode():返回对象的哈希值
- 对象的哈希值特点:
- 同一个对象多次调用hashCode()方法返回的哈希值是相同的
- 默认情况下,不同对象的哈希值是不同的。
HashSet1.7版本原理解析:数组+链表+(结合哈希算法)
- 创建一个长度为16的数组,数组名table
- 根据元素的哈希值跟数组长度求余计算出应存入的位置(哈希算法)
- 判断当前位置是否为null,如果为null直接存入
- 如果不为null,表示有元素,则调用equals方法比较
- 如果一样则不存,如果不一样,则存入数组
- JDK7新元素占老元素位置,指向老元素
- JDK8中新元素挂在老元素下面
JDK1.8版本开始HashSet原理解析:
- 底层结构:哈希表(数组、链表、红黑树的结合体)
- 当挂在元素下面的数组过多时,查询性能降低,从JDK8开始后,当链表长度超过8的时候,自动转换为红黑树。
从JDK8开始后,哈希表对于红黑树的引入进一步提高了操作数据的性能
3、HashSet元素去重复的底层原理
如果希望Set集合认为两个内容一样的对象是重复的,必须重写对象的hashCode()和equals()方法
4、实现类:LinkedHashSet
LinkedHashSet集合概述和特点:
- 有序、不重复、无索引
- 这里的有序指的是保证存储和取出的元素顺序一致
- 原理:底层数据结构依然是哈希表,只是每个元素又额外多了一个双链表的机制记录存储的顺序
5、实现类:TreeSet
TreeSet集合概述和特点:
- 不重复,无索引,可排序
- 可排序:按照元素的大小默认升序
- TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都很好。
- 注意:TreeSet集合是一定要排序的,可以将元素按指定的规则进行排序
TreeSet默认排序规则:
- 对于数值类型:Integer,Double,官方按照大小进行升序
- 对于字符串类型:默认按照首字符的编号进行升序排序
- 对应自定义类型如Student对象,TreeSet无法直接排序
如果想要使用TreeSet存储自定义类型,需要制定排序规则
自定义排序规则:
- 方式一:让自定义类(如学生)实现Comparable接口重写里面的compareTo方法来制定比较规则
- 方式二:TreeSet集合有参构造器,可以设置Comparator接口对应的比较器对象,来定制比较规则
- 两种方式中,关于返回值的规则:
- 如果认为第一个元素大于第二个元素返回正整数即可。
- 如果认为第一个元素小于第二个元素返回负整数即可。
- 如果认为第一个元素等于第二个元素返回0即可,此时Treeset集合只会保留一个元素,认为两者重复。
- 如果TreeSet集合存储的对象有实现比较规则,集合也自带比较器,默认使用集合自导的比较器排序
二、Collection体系的特点、使用场景
- 如果希望元素可以重复,又有索引,索引查询要快?
- 用ArrayList?集合,基于数组的。(用的最多)
- 如果希望元素可以重复,又有索引,增删首尾操作快?
- 用LinkedList集合,基于链表的。
- 如果希望增删改查都快,但是元素不重复、无序、无索引。
- 用HashSet集合,基于哈希表的。
- 如果希望增删改查都快,但是元素不重复、有序、无索引。
- 用LinkedHashSet:集合,基于哈希表和双链表。
- 如果要对对象进行排序。
- 用TreeSet:集合,基于红黑树。后续也可以用List集合实现排序。
三、补充知识:可变参数
可变参数:
- 可变参数用在形参中可以接受多个数据
- 可变参数的格式:数据类型…参数名称
可变参数的作用:
- 传输参数非常灵活,方便。可以不传参数,可以传一个或多个,也可以传输一个数组
- 可变参数在方法内部本质上就是一个数组
可变参数的注意事项:
- 一个形参列表中可变参数只能有一个
- 可变参数必须放在形参列表的最后面
四、补充知识:集合工具类Collections
Collections集合工具类:
- java.utils.Collections:是集合工具类
- 作用:Collections并不属于集合,是用来操作集合的工具类。
Collections常用API:
方法名称 | 说明 |
---|---|
public static boolean addAll(Collection<? super T> c, T… elements) | 给集合对象批量添加元素 |
public static void shuffle(List<?> list) | 打乱List集合元素的顺序 |
public static void sort(List list) | 将集合中元素按照默认规则排序 |
public static void sort(List list,Comparator<? super T> c) | 将集合中元素按照指定规则排序 |
collections排序相关API:
- 使用范围:只能对于List集合排序
- public static void sort(List list) 方式不可以直接对自定义类型的List集合排序,除非自定义类型实现了比较规则Comparable接口。
五、Map集合体系
1、Map集合的概述
Map集合概述和使用:
- Map集合是一种双列集合,每个元素包含两个数据
- Map集合的每个元素的格式:key=value(键值对元素)
- Map集合也称为“键值对集合”
Map集合整体格式:
- Collection集合的格式:[元素1,元素2,元素3…]
- Map集合的完整格式:{key1=value1,key2=value2,key3=value3…}
2、Map集合体系特点
Map集合体系:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fC7ogyhw-1684550850449)(.\img\Map集合体系.png)]
说明:
- 使用最多的Map集合是HashMap
- 重点掌握HashMap,LinkdeHashMap,TreeMap。
Map集合的特点:
- Map集合的特点都是由键决定的。
- Map集合的键是无序,不重复的,无索引的,值不做要求(可以重复)。
- Map集合后面重复的键对应的值会覆盖前面重复键的值。
- Map集合的键值对都可以为null:
Map集合的特点:
- HashMap:元素按照键是无序,不重复,无索引,值不做要求。(与Map体系一致)
- LinkedHashMap:元素按照键是有序,不重复,无索引,值不做要求
- TreeMap:元素按照键排序,不重复,无索引,值不做要求。
3、Map集合常用API
Map集合:
- Map是双列集合的祖宗接口,它的功能是全部双列集合都可以继承使用的。
常用API:
方法名称 | 说明 |
---|---|
V put(K key,V value) | 添加元素 |
V remove(Object key) | 根据键删除键值对元素 |
void clear() | 移除所有的键值对元素 |
boolean containsKey(Object key) | 判断集合是否包含指定的键 |
boolean containsValue(Object value) | 判断集合是否包含指定的值 |
boolean isEmpty() | 判断集合是否为空 |
int size() | 集合的长度,也就是集合中键值对的个数 |
4、Map集合的遍历方式一:键找值
- 先获取Map集合的全部的Set集合
- 遍历键的Set集合,然后通过键提取对应值
方法名称 | 说明 |
---|---|
Set keySet() | 获取所有键的集合 |
V get(Object key) | 根据键获取值 |
5、Map集合的遍历方式二:键值对
- 先把Map集合转换成Set集合,Set集合中每个元素都是键值对实体类型了
- 遍历Set集合,然后提取键以及提取值
方法名称 | 说明 |
---|---|
Set<Map.Entry<K,V>> entrySet() | 获取所有键值对对象的集合 |
K getKey() | 获得键 |
V getValue() | 获取值 |
6、Map集合的遍历方式三:lambda表达式
- 得益于JDK8开始的新技术Lambda表达式,提供了一种更简单、更直接的遍历集合的方式
方法名称 | 说明 |
---|---|
default void forEach(BiConsumer<? super K, ? super V> action) | 结合lambda遍历Map集合 |
7、Map集合的实现类HashMap
- 使用最多的Map集合是HashMap。
- 重点掌握HashMap , LinkedHashMap ,TreeMap。其他的后续理解。
HashMap的特点:
- HashMap是Map里面的一个实现类。特点都是由键决定的:无序、不重复、无索引
- 没有额外需要学习的特有方法,直接用Map里面的方法就可以了
- 依赖hashCode方法和equals方法保证键的唯一。
- HashMap跟HashSet底层原理是一模一样的,都是哈希表结构,只是HashMap的每个元素包含两个值而已
实际上:Set系列集合的底层就是Map实现的,只是Set集合中的元素只要键数据,不要值数据而已。
8、Map集合的实现类LinkedHashMap
LinkedHashMap集合概述和特点
- 由键决定:有序、不重复、无索引。
- 这里的有序指的是保证存储和取出的元素顺序一致
- 原理:底层数据结构是依然哈希表,只是每个键值对元素又额外的多了一个双链表的机制记录存储的顺序。
9、Map集合的实现类TreeMap
TreeMap集合概述和特点:
- 由键决定特性:不重复、无索引、可排序
- 可排序:按照键数据的大小默认升序(有小到大)排序。只能对键排序。
- 注意:TreeMap集合是一定要排序的,可以默认排序,也可以将键按照指定的规则进行排序
- TreeMap跟TreeSet底层原理是一样的。
TreeMap集合自定义排序规则有2种:
- 类实现Comparable接口,重写比较规则。
- 集合自定义Comparatorl比较器对象,重写比较规则。
Map集合实现类特点:
- HashMap:元素按照键是无序,不重复,无索引,值不做要求,基于哈希表(与Map体系一致)
- LinkedHashMap:元素按照键是有序,不重复,无索引,值不做要求,基于哈希表
- TreeMap:元素只能按照键排序,不重复,无索引的,值不做要求,可以做排序
六、补充知识:集合的嵌套
案例:
l某个班级多名学生,现在需要组成秋游活动,班长提供了四个景点依次是(A、B、C、D),每个学生可以选择多个景点,请统计出最终哪个景点想去的人数最多。
分析
l将80个学生选择的数据拿到程序中去,需要记住每个学生选择的情况。
l定义Map集合用于存储最终统计的结果。
package com.zhl.d12_map_nested;
import java.util.*;
public class Test {
public static void main(String[] args) {
Map<String, List<String>> rs = new LinkedHashMap<>();
String[] attractions = {"A", "B", "C", "D"};
Random r = new Random();
for (int i = 0; i < 80; i++) {
List<String> list= new ArrayList<>();
String name = "学生"+(i+1);
for (int j = 0; j < r.nextInt(attractions.length)+1; j++) {
list.add(attractions[r.nextInt(attractions.length)]);
}
rs.put(name,list);
}
System.out.println(rs);
Map<String,Integer> rsNum = new HashMap<>();
Collection<List<String>> values = rs.values();
for (List<String> value : values) {
for (String s : value) {
if (rsNum.containsKey(s)){
rsNum.put(s, rsNum.get(s)+1);
}else {
rsNum.put(s, 1);
}
}
}
System.out.println(rsNum);
}
}
第七天:Stream、异常体系
一、创建不可变集合
1、什么是不可变集合
- 不可变集合,就是不可被修改的集合
- 集合的数据项在创建的时候提供,并且在整个声明周期中都不可改变。否则报错
2、为什么要创建不可变集合?
- 如果某个数据不能被修改,把它防御性地拷贝到不可变集合中是很好的实践
- 或者当集合对象被不可信的库调用时,不可变形式是安全的
3、如何创建不可变集合?
-
在List,Set,Map接口中,都存在of方法,可以创建一个不可变集合。
方法名称 说明 static List of(E…elements) 创建一个具有指定元素的List集合对象 static Set of(E…elements) 创建一个具有指定元素的Set集合对象 static <K , V> Map<K,V> of(E…elements) 创建一个具有指定元素的Map集合对象 -
这个集合不能添加,不能删除,不能修改
二、Stream流
1、Stream流的概述
什么是Stream流:
- 在Java8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream流概念
- 目的:用于简化集合和数组操作的API
Stream流的思想:
- 先得到集合或者数组的Stream流(就是一根传送带)。
- 把元素放上去。
- 然后就用这个Stream流简化的API来方便的操作元素。
2、Stream流的获取
Stream流的三类方法:
- 获取Stream流
- 创建一条流水线,并把数据放到流水线上准备进行操作
- 中间方法
- 流水线上的操作。一次操作完毕之后,还可以继续其他操作
- 终结方法
- 一个Stream流只能有一个终结方法,是流水线上的最后一个操作
Stream操作集合或者数组的第一步是先得到Stream流,然后才能使用流的功能。
集合获取Stream流的方式
- 可以使用Collection接口中的默认方法stream()生成流
名称 | 说明 |
---|---|
default Stream stream() | 获取当前集合对象的Stream流 |
数组获取Stream流的方式:
名称 | 说明 |
---|---|
public static Stream stream(T[] array) | 获取当前数组的Stream流 |
public static Stream of(T… values) | 获取当前数组/可变数据的Stream流 |
3、Stream流的常用API
中间操作方法:
名称 | 说明 |
---|---|
Stream filter(Predicate<? super T> predicate) | 用于对流中的数据进行过滤。 |
Stream limit(long maxSize) | 获取前几个元素 |
Stream skip(long n) | 跳过前几个元素 |
Stream distinct() | 去除流中重复的元素。依赖(hashCode和equals方法) |
static Stream concat(Stream a, Stream b) | 合并a和b两个流为一个流 |
注意:
- 中间方法也称为非终结方法,调用完成后返回新的Stream流可以继续使用,支持链式编程。
- 在Stream流中无法直接修改集合、数组中的数据。
终结操作方法:
名称 | 说明 |
---|---|
void forEach(Consumer action) | 对此流的每个元素执行遍历操作 |
long count() | 返回此流中的元素数 |
注意:终结操作方法,调用完成后流就无法继续使用了,原因是不会返回Stream了
4、Stream流的综合应用
5、收集Stream流
Stream流的收集:
- 收集Stream流的含义:就是把Stream流操作后的结果数据转回到集合或者数组中去。
- Stream流:方便操作集合/数组的手段。
- 集合/数组:才是开发中的目的。
Stream流的收集方法
名称 | 说明 |
---|---|
R collect(Collector collector) | 开始收集Stream流,指定收集器 |
Collectors工具类提供了具体的收集方式
名称 | 说明 |
---|---|
public static Collector toList() | 把元素收集到List集合中 |
public static Collector toSet() | 把元素收集到Set集合中 |
public static Collector toMap(Function keyMapper , Function valueMapper) | 把元素收集到Map集合中 |
三、异常处理
1、异常概述、体系
什么是异常:
- 异常是程序在“编译”或“执行”的过程中可能出现的问题
- 注意:语法错误不算在异常体系中
- 比如:数组索引越界、空指针异常、日期格式化异常等
为什么要学习异常
- 异常一旦出现了,如果没有提前处理,程序就会退出JVM虚拟机而终止
- 研究异常并且避免异常,然后提前处理异常,体现的是程序的安全,健壮性
异常体系:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JTDXPGc9-1684550850449)(.\img\异常体系.png)]
Error:
- 系统级别问题,JVM退出等,代码无法控制
Exception:
java.lang包下,称为异常类,它表示程序本身可以处理问题
- RuntimeException及其子类:运行时异常,编译阶段不会报错
- 除RuntimeException之外所有的异常:编译时异常,编译期必须处理,否则不能通过程序
编译时异常和运行时异常:
- 编译时异常就是在编译的时候出现的异常
- 运行时异常就是在运行时出现的异常。
2、常见运行时异常
- 直接继承自RuntimeException或者其子类,编译阶段不会报错,运行时可能出现的错误。
运行时异常实例:
- 数组索引越界异常:ArraylndexOutOfBoundsException
- 空指针异常:NullPointerException,直接输出没有问题,但是调用空指针的变量的功能就会报错。
- 数学操作异常:ArithmeticException
- 类型转换异常:ClassCastException
- 数字转换异常:NumberFormatException
运行时异常:一般是程序员业务没有考虑好或者是编程逻辑不严谨引起的程序错误,自己的水平有问题!
3、常见编译时异常
- 不是RuntimeException或者其子类的异常,编译阶就报错,必须处理,否则代码不通过,
编译时异常的作用是什么:
-
编译时异常:继承自Exception的异常或者其子类
-
是担心程序员的技术不行,在编译阶段就爆出一个错误,目的在于提醒不要出错!
-
编译时异常时可遇不可求。遇到就遇到了
4、异常的默认处理流程
-
默认会在出现异常的代码那里自动的创建一个异常对象:ArithmeticException。
-
异常会从方法中出现的点这里抛出给调用者,调用者最终抛出给JVM虚拟机。
-
虚拟机接收到异常对象后,先在控制台直接输出异常栈信息数据。
-
直接从当前执行的异常点干掉当前程序。
后续代码没有机会执行了,因为程序已经死亡。
5、编译时异常的处理机制
- 编译时异常是编译阶段就出错的,所以必须处理,否则代码根本无法通过
编译时异常的处理形式有三种:
- 出现异常直接抛出去给调用者,调用者也继续抛出去。
- 出现异常自己捕获处理,不麻烦别人。
- 前两者结合,出现异常直接抛出去给调用者,调用者捕获处理。
异常处理方式1一throws:
-
throws:用在方法上,可以将方法内部出现的异常抛出去给本方法的调用者处理。
-
这种方式并不好,发生异常的方法自己不处理异常,如果异常最终抛出去给虚拟机将引起程序死亡。
-
格式:
方法 throws 异常1,异常2,异常3,..{ }
规范做法:
方法 throws Exception{ }
异常处理方式2一一try…catch
-
监视捕获异常,用在方法内部,可以将方法内部出现的异常直接捕获处理。
-
这种方式还可以,发生异常的方法自己独立完成异常的处理,程序可以继续往下执行。
-
try{ //监视可能出现异常的代码 }eactch(异常类型1 变量){ //处理异常 }eactch(异常类型1 变量){ //处理异常 }...
异常处理方式3一一前两者结合:
- 方法直接将异通过throws:抛出去给调用者
- 调用者收到异常后直接捕获处理。
6、运行时异常的处理机制
运行时异常的处理形式
- 运行时异常编译阶段不会出错,是运行时才可能出错的,所以编译阶段不处理也可以。
- 按照规范建议还是处理:建议在最外层调用处集中捕获处理即可。
7、异常处理使代码更稳健的案例
package com.zhl.d12_exception_handle_test;
import java.util.Random;
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
// 需求
// 键盘录入一个合理的价格为止(必须是数值,值必须大于0)。
Scanner sc = new Scanner(System.in);
System.out.println("请输入价格:");
while (true){
int price=0;
String s = sc.nextLine();
try {
price = Integer.valueOf(s);
if (price>0){
System.out.println("输入成功"+price);
break;
}else {
System.out.println("必须大于0");
}
} catch (Exception e) {
System.out.println("输入有误,请重新输入");
}
}
}
}
8、自定义异常
自定义异常的必要:
- Java无法为这个世界上全部的问题提供异常类。
- 如果企业想通过异常的方式来管理自己的某个业务问题,就需要自定义异常类了
自定义异常的好处:
可以使用异常的机制管理业务问题,如提醒程序员注意。
自定义异常的分类:
-
自定义编译时异常
- 定义一个异常类继承Exception.
- 重写构造器。
- 在出现异常的地方用throw new自定义对象抛出
作用:编译时异常是编译阶段就报错,提醒更加强烈,必须要处理
-
自定义运行时异常
- 定义一个异常类继承RuntimeException.
重写构造器。 - 在出现异常的地方用throw new自定义对象抛出!
作用:提醒不强烈,编译阶段不报错!!运行时才可能出现!!
- 定义一个异常类继承RuntimeException.
第八天、日志框架
一、日志技术的概述
1、以前记录日志的方式:将信息打印在控制台。
弊端:
- 信息只能展示在控制台
- 不能将其记录到其他的位置
- 想取消记录的信息需要修改代码才可以完成
2、日志技术具备的优势
- 可以将系统执行的信息选择性的记录到指定的位置(控制台、文件、数据库中)
- 可以随时以开关的形式控制是否记录日志,无需修改源代码
二、日志技术体系
1、体系结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GwGMK6KW-1684550850450)(.\img\日志体系结构.png)]
- 日志规范:一些接口,提供日志的实现矿建设计的标准
- 日志框架:牛人或者第三方公司已经做好的日志记录实现代码,后来者直接可以拿去使用
- 应为对Commons Logging的接口不满意,有人就搞了SLF4J。因为对Log4j的性能不满意,有人就搞了Logback
三、Logback概述
1、Logback日志框架
- Logback是由log4j创始人设计的另一个开源日志组件,性能比log4j要好
- 官网:https://logback.qos.ch/index.html
- Logback是基于slf4j的日志规范实现的框架
2、Logback主要分为三个技术模块
- logback-core:logback-core模块为其他两个模块奠定了基础,必须有。
- logback-classic:它是log4j的一个改良版本,同时它完整实现了slf4j APl。
- logback-access模块与Tomcat和Jetty等Servlet容器集成,以提供HTTP诱问日志功能。
四、Logback快速入门
步骤:
-
在项目下新建文件夹lib,导入Logback的相关jar包到该文件夹下,并添加到项目依赖库中去。
-
将Logback的核心配置文件logback。xml直接拷贝到src目录下
-
在代码中获取日志对象
public static final Logger LOGGER = LoggerFaction.getLogger("类对象");
五、Logback配置详解-输出位置、格式设置
Logback日志系统的特新都是通过核心配置文件logback.xml控制的。
Logback日志输出位置、格式设置:
- 通过logback.xml中的<append>标签可以设置输出位置和日志信息的详细格式。
- 通常可以设置2个日志输出位置:一个是控制台、一个是系统文件中
输出到控制台的配置标志
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
输出到系统文件的配置标志
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
六、Logback配置详解-日志级别设置
日志级别:
- 级别:TRACE<DEBUG<INFO<WARN<ERROR,默认级别是debug(忽略大小写),对应其方法
- 作用:用于控制系统中那些日志级别是可以输出的,只输出级别不低于设定级别的日志信息
- ALL和OFF分别是打开全部日志信息,及关闭全部日志信息。
具体在<root level= “INFO">标签的level属性中设置日志级别
第九天、File、方法递归、IO流
一、File类概述
1、File类概述
- File类在包java.io.File下、代表操作系统的文件对象(文件、文件夹)
- File类提供了诸如:定位文件,获取文件本身的信息、删除文件、创建文件(文件夹)等功能
2、File类创建对象
方法名称 | 说明 |
---|---|
public File(String pathname) | 根据文件路径创建文件对象 |
public File(String parent, String child) | 根据父路径名字符串和子路径名字符串创建文件对象 |
public File(File parent, String child) | 根据父路径对应文件对象和子路径名字符串创建文件对象 |
- File对象可以定位文件和文件夹
- File封装的对象仅仅是一个路径,这个路径可以层、是存在的,也可以是不存在的
3、绝对路径和相对路径
- 绝对路径:从盘符开始
File file1 = new File(“D:\\itheima\\a.txt”);
- 相对路径:不带盘符,默认直接到当前工程下的目录寻找文件。
File file3 = new File(“模块名\\a.txt”);
二、File类的常用API
1、判断文件类型、获取文件信息功能
方法名称 | 说明 |
---|---|
public boolean isDirectory() | 判断此路径名表示的File是否为文件夹 |
public boolean isFile() | 判断此路径名表示的File是否为文件 |
public boolean exists() | 判断此路径名表示的File是否存在 |
public long length() | 返回文件的大小(字节数量) |
public String getAbsolutePath() | 返回文件的绝对路径 |
public String getPath() | 返回定义文件时使用的路径 |
public String getName() | 返回文件的名称,带后缀 |
public long lastModified() | 返回文件的最后修改时间(时间毫秒值) |
2、创建文件或删除文件的功能
方法名称 | 说明 |
---|---|
public boolean createNewFile() | 创建一个新的空的文件 |
public boolean mkdir() | 只能创建一级文件夹 |
public boolean mkdirs() | 可以创建多级文件夹 |
public boolean delete() | 删除由此抽象路径名表示的文件或空文件夹 |
- delete方法直接删除不走回收站;如果删除的是一个文件,且文件没有被占用则直接删除
- delete方法默认只能删除空文件夹
3、遍历文件夹
方法名称 | 说明 |
---|---|
public String[] list() | 获取当前目录下所有的"一级文件名称"到一个字符串数组中去返回。 |
public File[] listFiles()(常用) | 获取当前目录下所有的"一级文件对象"到一个文件对象数组中去返回(重点) |
listFiles方法注意事项:
- 当调用者不存在时返回null
- 当调用者时一个文件时返回null
- 当调用者是一个空文件夹时返回长度为0的空数组
- 当文件对象是一个有内容的文件夹时,将里面所有文件和文件夹的路径放在File数组中返回
- 当文件对象是一个有隐藏文件的文件夹时,将里面所有文件和文件夹的路径放在File数组中返回,包含隐藏文件
- 当没有权限访问该文件夹时,返回null
三、方法递归
1、递归的形式和特点
什么是方法递归
- 方法直接或间接调用自己的形式称为方法递归。
- 递归做为一种算法在程序语言设计中广泛应用
递归的形式
- 直接递归:方法自己调用自己
- 间接递归:方法调用其他方法,其他方法又回调方法自己。
递归死循环
- 递归的方法无限调用自己,无法终止,出现栈内存溢出
2、递归的算法流程、核心要素
递归解决问题的思路:
- 把一个复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。
递归算法三大要素:
- 递归的公式:f(n) = f(n-1)*n
- 递归的终结点:f(n) = 1
- 递归的方向必须走向终结点
四、字符集
1、字符集基础知识
- 计算机底层不可以直接存储字符的。计算机中底层只能存储二进制(0、1)
- 二进制是可以转换成十进制的
- 计算机底层可以表示十进制编号。计算机可以给人类字符进行编号存储,这套编号的规则就是字符集。
2、ASCII字符集:
- ASCII(American Standard Code for Information Interchange,美国信息交换标准代码):包括了数字、英文、符号。
- ASCII使用1字节存储一个字符,一个字节是8位,总共可以表示128个字符信息,对于英文,数字来说是够用的。
3、GBK
- window系统默认的码表。兼容ASCII码表,也包含了几万个汉字,并支持繁体汉字以及部分日韩文字
- 注意:GBK是中国的码表,一个中文以两个字节的形式存储。但不包括世界上所有国家的文字
4、Unicode码表:
- 统一码,也叫万国码。是计算机科学领域里的一项业界标准。
- 容纳世界上大多数国家的所有常见文字和符号
- 由于Unicode会先通过UTF-8,UTF-16,以及UTF-32的编码成二进制后再存储到计算机,其中最为常见的就是
UTF-8。
注意:
- Unicode是万国码,以UTF-8编码后一个中文一般以三个字节的形式存储。
- UTF-8也要兼容ASCII编码表。
- 技术人员都应该使用UTF-8的字符集编码。
- 编码前和编码后的字符集需要一致,否则会出现中文乱码。
5、汉字存储和展示过程解析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9pbfOMPK-1684550850450)(.\img\汉字存储和展示过程解析.png)]
6、String编码
方法名称 | 说明 |
---|---|
byte[] getBytes() | 使用平台的默认字符集将该 String编码为一系列字节,将结果存储到新的字节数组中 |
byte[] getBytes(String charsetName) | 使用指定的字符集将该 String编码为一系列字节,将结果存储到新的字节数组中 |
7、String解码
构造器 | 说明 |
---|---|
String(byte[] bytes) | 通过使用平台的默认字符集解码指定的字节数组来构造新的 String |
String(byte[] bytes, String charsetName) | 通过指定的字符集解码指定的字节数组来构造新的 String |
五、IO流概述
IO流也称为输入、输出流,就是用来读写数据的。
1、IO流概述
- I表示intput,是数据从硬盘文件读入到内存的过程,称之输入,负责读
- O表示output,是内存程序的数据从内存写出到硬盘文件的过程,称之输出,负责写
2、IO流的分类
-
按流的方向分
- 输入流
- 输出流
-
按流中的数据最小单位分为
-
字节流
操作所有类型的文件
-
字符流
只操作纯文本文件
-
3、总结流的四大类
- 字节输入流:以内存为基准,来自磁盘文件/网络中的数据以字节的形式读入到内存中去的流称为字节输入流。
- 字节输出流:以内存为基准,把内存中的数据以字节写出到磁盘文件或者网络中去的流称为字节输出流。
- 字符输入流:以内存为基准,来自磁盘文件/网络中的数据以字符的形式读入到内存中去的流称为字符输入流。
- 字符输出流:以内存为基准,把内存中的数据以字符写出到磁盘文件或者网络介质中去的流称为字符输出流。
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ygt3sMNi-1684550850450)(.\img\IO流体系.png)]
六、字节流的使用
1、文件字节输入流:FileInputStream
- 作用:以内存为基准,把磁盘文件中的数据以字节的形式读取到内存中去
构造器 | 说明 |
---|---|
public FileInputStream(File file) | 创建字节输入流管道与源文件对象接通 |
public FileInputStream(String pathname) | 创建字节输入流管道与源文件路径接通 |
方法名称 | 说明 |
---|---|
public int read() | 每次读取一个字节返回,如果字节已经没有可读的返回-1 |
public int read(byte[] buffer) | 每次读取一个字节数组返回,如果字节已经没有可读的返回-1 |
2、文件字节输入流:FileInputStream
- 作用:以内存为基准,把磁盘文件中的数据以字节的形式读取到内存中去
方法名称 | 说明 |
---|---|
public int read() | 每次读取一个字节返回,如果字节已经没有可读的返回-1 |
public int read(byte[] buffer) | 每次使用字节数组来读取数据,返回读取的字节个数,如果没有可读返回-1 |
3、解决乱码的方法
方式一
- 自己定义一个字节数组与文件的大小一样大,然后使用读取字节数组的方法,一次性读取完成。
方法名称 | 说明 |
---|---|
public int read(byte[] buffer) | 每次读取一个字节数组返回,如果字节已经没有可读的返回-1 |
方式二
- 官方为字节输入流InputStream提供了如下API可以直接把文件的全部数据读取到一个字节数组中
方法名称 | 说明 |
---|---|
public byte[] readAllBytes() throws IOException | 直接将当前字节输入流对应的文件对象的字节数据装到一个字节数组返回 |
4、文件字节输出流:FileOutputStream
- 作用:以内存为基准,把内存中的数据以字节的形式写出到磁盘文件中去的流
构造器 | 说明 |
---|---|
public FileOutputStream(File file) | 创建字节输出流管道与源文件对象接通 |
public FileOutputStream(File file,boolean append) | 创建字节输出流管道与源文件对象接通,可追加数据 |
public FileOutputStream(String filepath) | 创建字节输出流管道与源文件路径接通 |
public FileOutputStream(String filepath,boolean append) | 创建字节输出流管道与源文件路径接通,可追加数据 |
文件字节输出流(FileOutputStream)写数据出去的API
方法名称 | 说明 |
---|---|
public void write(int a) | 写一个字节出去 |
public void write(byte[] buffer) | 写一个字节数组出去 |
public void write(byte[] buffer , int pos , int len) | 写一个字节数组的一部分出去。 |
流的关闭与刷新
方法 | 说明 |
---|---|
flush() | 刷新流,还可以继续写数据 |
close() | 关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据 |
七、资源释放的方式
1、try-catch-finally
-
finally:在异常处理时提供finally块来执行所有清除操作,比如IO流中释放资源
-
特点:被finally控制的语句最终一定会执行,除非JVM退出
-
异常处理标准格式:try…catch…finally
-
格式:
try { FileOutputStream fos = new FileOutputStream("a.txt"); fos.write(97); } catch (IOException e) { e.printStackTrace(); }finally{ }
2、try-catch-resource
- JDK 7 以及 JDK 9的()中只能放置资源对象,否则报错
- 什么是资源呢?
- 资源都是实现了Closeable/AutoCloseable接口的类对象
八、字符流的使用
1、文件字符输入流:Reader
- 作用:以内存为基准,把磁盘文件中的数据以字符的形式读取到内存中去
构造器 | 说明 |
---|---|
public FileReader(File file) | 创建字符输入流管道与源文件对象接通 |
public FileReader(String pathname) | 创建字符输入流管道与源文件路径接通 |
方法名称 | 说明 |
---|---|
public int read() | 每次读取一个字符返回,如果字符已经没有可读的返回-1 |
public int read(char[] buffer) | 每次读取一个字符数组,返回读取的字符个数,如果字符已经没有可读的返回-1 |
2、文件字符输出流:Writer
- 作用:以内存为基准,把内存中的数据以字符的形式写出到磁盘文件中去的流。
构造器 | 说明 |
---|---|
public FileWriter(File file) | 创建字符输出流管道与源文件对象接通 |
public FileWriter(File file,boolean append) | 创建字符输出流管道与源文件对象接通,可追加数据 |
public FileWriter(String filepath) | 创建字符输出流管道与源文件路径接通 |
public FileWriter(String filepath,boolean append) | 创建字符输出流管道与源文件路径接通,可追加数据 |
文件字符输出流FileWriter写数据出去的API:
方法名称 | 说明 |
---|---|
void write(int c) | 写一个字符 |
void write(char[] cbuf) | 写入一个字符数组 |
void write(char[] cbuf, int off, int len) | 写入字符数组的一部分 |
void write(String str) | 写一个字符串 |
void write(String str, int off, int len) | 写一个字符串的一部分 |
流的关闭与刷新:
方法 | 说明 |
---|---|
flush() | 刷新流,还可以继续写数据 |
close() | 关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据 |
3、字节流、字符流如何选择使用
- 字节流适合做一切文件数据的拷贝(音视频,文本)
- 字节流不适合读取中文内容输出
- 字符流适合做文本文件的操作(读,写)
第十天、IO流(2)
一、缓冲流
1、缓冲流概述
- 缓冲流也称为高效流、或者高级流。之前学习的字节流可以称为原始流
- 作用:缓冲流自带缓冲区,可以提高原始字节流、字符流读写数据的性能
- 字节缓冲流
- 字节缓冲输入流:ufferedInputStream
- 字节缓冲输出流:ufferedOutputStream
- 字符缓冲流
- 字符缓冲输入流:BufferedReader
- 字符缓冲输出流:BufferedWriter
2、字节缓冲流性能优化原理
- 字节缓冲输入流自带了8KB缓冲池,以后我们直接从缓冲池读取数据,所以性能较好
- 字节缓冲输入流自带了8KB缓冲池,数据就直接写入到缓冲池中去,写数据性能提高了
3、字节缓冲流
- 字节缓冲输入流:BufferedInputStream,提高字节输入流读取数据的性能。
- 字节缓冲输出流:BufferedOutputStream,提高字节输出流读取数据的性能。
构造器 | 说明 |
---|---|
public BufferedInputStream(InputStream is) | 可以把低级的字节输入流包装成一个高级的缓冲字节输入流管道,从而提高字节输入流读数据的性能 |
public BufferedOutputStream(OutputStream os) | 可以把低级的字节输出流包装成一个高级的缓冲字节输出流,从而提高写数据的性能 |
4、字符缓冲输入流
- 字符缓冲输入流:BufferedReader
- 作用:提高字符输入流读取数据的性能,除此之外多了按照行读取数据的功能
构造器 | 说明 |
---|---|
public BufferedReader(Reader r) | 可以把低级的字符输入流包装成一个高级的缓冲字符输入流管道,从而提高字符输入流读数据的性能 |
字符缓冲输入流新增功能:
方法 | 说明 |
---|---|
public String readLine() | 读取一行数据返回,如果读取没有完毕,无行可读返回null |
5、字符缓冲输出流
- 字符缓冲输出流:BufferedWriter
- 作用:提高字符输出流写取数据的性能,除此之外多了换行功能
构造器 | 说明 |
---|---|
public BufferedWriter(Writer w) | 可以把低级的字符输出流包装成一个高级的缓冲字符输出流管道,从而提高字符输出流写数据的性能 |
字符缓冲输出流新增功能:
方法 | 说明 |
---|---|
public void newLine() | 换行操作 |
二、转换流
1、字符类直接读取文本内容
- 必须文件和代码编码一致才不会乱码
- 如果文件和代码编码不一致,读取将会出现乱码
2、字符输入转换流
- 字符输入转换流:InputStreamReader,可以吧原始的字节流按照指定编码转换成字符输入流
构造器 | 说明 |
---|---|
public InputStreamReader(InputStream is) | 可以把原始的字节流按照代码默认编码转换成字符输入流。几乎不用,与默认的FileReader一样。 |
public InputStreamReader(InputStream is ,String charset) | 可以把原始的字节流按照指定编码转换成字符输入流,这样字符流中的字符就不乱码了(重点) |
3、字符输出转换流
- 字符输出转换流:OutputStreamWriter,可以把字节输出流按照指定编码转换成字符输出流。
构造器 | 说明 |
---|---|
public OutputStreamWriter(OutputStream os) | 可以把原始的字节输出流按照代码默认编码转换成字符输出流。几乎不用。 |
public OutputStreamWriter(OutputStream os,String charset) | 可以把原始的字节输出流按照指定编码转换成字符输出流(重点) |
三、序列化对象
1、对象序列化
- 作用:以内存为基准,把内存中的对象存储到磁盘中去,称为对象序列化
- 使用到的流是对象字节输出流:ObjectOutputStream
构造器 | 说明 |
---|---|
public ObjectOutputStream(OutputStream out) | 把低级字节输出流包装成高级的对象字节输出流 |
ObjectOutputStream序列化方法:
方法名称 | 说明 |
---|---|
public final void writeObject(Object obj) | 把对象写出去到对象序列化流的文件中去 |
注意:对象要序列化必须实现Serializable接口
2、对象反序列化
- 使用到的流是对象字节输入流:ObjectInputStream
- 作用:以内存为基准,把存储到磁盘文件中去的对象数据恢复成内存中的对象,称为对象反序列化。
构造器 | 说明 |
---|---|
public ObjectInputStream(InputStream out) | 把低级字节输如流包装成高级的对象字节输入流 |
ObjectInputStream序列化方法:
方法名称 | 说明 |
---|---|
public Object readObject() | 把存储到磁盘文件中去的对象数据恢复成内存中的对象返回 |
注意:transient修饰的变量不参与序列化,可以用它来修饰一些敏感字段
四、打印流
1、打印流
- 作用:打印流可以实现方便、高效的打印数据到文件中去。打印流一般是指:PrintStream,PrintWriter两个类
- 可以实现打印什么数据就是什么数据,例如打印整数97写出去就是97,打印boolean的true,写出去就是true
PrintStream
构造器 | 说明 |
---|---|
public PrintStream(OutputStream os) | 打印流直接通向字节输出流管道 |
public PrintStream(File f) | 打印流直接通向文件对象 |
public PrintStream(String filepath) | 打印流直接通向文件路径 |
方法 | 说明 |
---|---|
public void print(Xxx xx) | 打印任意类型的数据出去 |
PrintWriter
构造器 | 说明 |
---|---|
public PrintWriter(OutputStream os) | 打印流直接通向字节输出流管道 |
public PrintWriter (Writer w) | 打印流直接通向字符输出流管道 |
public PrintWriter (File f) | 打印流直接通向文件对象 |
public PrintWriter (String filepath) | 打印流直接通向文件路径 |
方法 | 说明 |
---|---|
public void print(Xxx xx) | 打印任意类型的数据出去 |
2、PrintStream和PrintWriter的区别
- 打印数据功能上是一模一样的,都是使用方便,性能高效
- PrintStream继承自字节输出流OutputStream,支持写字节数据的方法
- PrintWriter继承自字符输出流Writer,支持写字符数据出去
3、输出语句重定向
- 属于打印流的一种应用,可以把输出语句打印位置改到文件。
- 使用System的setOut方法将系统打印流改成我们自己的打印流
五、补充知识:Properties
1、Properties属性集对象
- 其实就是一个Map集合,但是我们一般不会当集合使用,因为HashMap更好用
2、Properties核心作用
- Properties代表的是一个属性文件,可以吧自己对象中的键值对信息存入到一个属性文件中去
- 属性文件:后缀是.properties结尾的文件,里面的内容都是key=value,后续做系统配置信息的
3、Properties的API
- Properties和IO流结合的方法
构造器 | 说明 |
---|---|
void load(InputStream inStream) | 从输入字节流读取属性列表(键和元素对) |
void load(Reader reader) | 从输入字符流读取属性列表(键和元素对) |
void store(OutputStream out, String comments) | 将此属性列表(键和元素对)写入此 Properties表中,以适合于使用 load(InputStream)方法的格式写入输出字节流 |
void store(Writer writer, String comments) | 将此属性列表(键和元素对)写入此 Properties表中,以适合使用 load(Reader)方法的格式写入输出字符流 |
public Object setProperty(String key, String value) | 保存键值对(put) |
public String getProperty(String key) | 使用此属性列表中指定的键搜索属性值 (get) |
public Set stringPropertyNames() | 所有键的名称的集合 (keySet()) |
六、补充知识:IO框架
1、commons-io概述
- commons-io是apache开源基金组织提供的一组有关IO操作的类库,可以提高IO功能开发的效率
- commons-io工具包提供了很多有关io操作的类。有两个主要的类FileUtils, IOUtils
FileUtils主要有如下方法:
方法名 | 说明 |
---|---|
String readFileToString(File file, String encoding) | 读取文件中的数据, 返回字符串 |
void copyFile(File srcFile, File destFile) | 复制文件。 |
void copyDirectoryToDirectory(File srcDir, File destDir) | 复制文件夹。 |
JDK1.7开始,也做了一些一行代码完成复制的操作:New IO的技术
第十一天、多线程
什么是线程
-
线程(thread)是一个程序内部的一条执行路径
-
我们之前启动程序执行后,main方法的执行其实就是一条单独的执行路径
-
程序中如果只有一条执行路径,那么这个程序就是单线程的程序
-
多线程是指从软硬件上实现多条执行流程的技术
一、多线程的创建
1、Thread类
- Java是通过java.lang.Thread类来代表线程的。
- 按照面向对象的思想,Thread类应该提供了实现多线程的方式。
2、多线程的实现方法
方案一:继承Thread类
- 定义一个子类MyThread继承线程类java.lang.Thread,重写run()方法
- 创建MyThread类的对象
- 调用线程对象的start()方法启动线程(启动后还是执行run方法的)
优缺点:
- 优点:编码简单
- 缺点:线程类已经继承了Thread,无法继承其他类,不利于扩展
方案二:实现Runable接口
- 定义线程任务类MyRunable实现Runnable接口,重写run()方法
- 创建MyRunable任务对象
- 把MyRunnable任务对象交给 Thread处理
- 调用线程对象的start()方法启动线程
构造器 | 说明 |
---|---|
public Thread(String name) | 可以为当前线程指定名称 |
public Thread(Runnable target) | 封装Runnable对象成为线程对象 |
public Thread(Runnable target ,String name ) | 封装Runnable对象成为线程对象,并指定线程名称 |
优缺点:
- 优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强
- 缺点:编程多一层对象包装,如果线程有执行结果是不可以直接返回的。
方案二:实现Runnable接口(匿名内部类形式)
- 可以创建Runnable的匿名内部类对象
- 交给Thread处理
- 调用线程对象的start()启动线程
方案三:利用Callable、FutureTask接口实现
- 得到任务对象
- 定义类实现Callable接口,重写call方法,封装要做的事情
- 用FutureTask把Callable对象封装成线程任务对象
- 把线程任务对象交给Thread处理
- 调用Thread的start方法启动线程,执行任务。
- 线程执行完毕后、通过FutureTask的get方法去获取任务执行的结果
FutureTask的API
方法名称 | 说明 |
---|---|
public FutureTask<>(Callable call) | 把Callable对象封装成FutureTask对象。 |
public V get() throws Exception | 获取线程执行call方法返回的结果。 |
优缺点:
- 优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强
- 可以在线程执行完毕后去获取线程执行的结果
- 缺点:编码复杂一点
3、三种方案的对比
方式 | 优点 | 缺点 |
---|---|---|
继承Thread类 | 编程比较简单,可以直接使用Thread类中的方法 | 扩展性较差,不能再继承其他的类,不能返回线程执行的结果 |
实现Runnable接口 | 扩展性强,实现该接口的同时还可以继承其他的类。 | 编程相对复杂,不能返回线程执行的结果 |
实现Callable接口 | 扩展性强,实现该接口的同时还可以继承其他的类。可以得到线程执行的结果 | 编程相对复杂 |
二、Thread常用方法
1、Thread常用API说明
- Thread常用方法:获取线程名称getName()、设置名称setName()、获取当前线程对象currentThread()。
- 至于Thread类提供的诸如:yield、join、interrupt、不推荐的方法 stop 、守护线程、线程优先级等线程的控制方法,在开发中很少使用,这些方法会在高级篇以及后续需要用到的时候再为大家讲解。
2、Thread获取和设置线程名称
方法名称 | 说明 |
---|---|
String getName() | 获取当前线程的名称,默认线程名称是Thread-索引 |
void setName(String name) | 将此线程的名称更改为指定的名称,通过构造器也可以设置线程名称 |
3、Thread类获得当前线程的对象
方法名称 | 说明 |
---|---|
public static Thread currentThread(): | 返回对当前正在执行的线程对象的引用 |
注意:
- 此方法是Thread类的静态方法,可以直接使用Thread类调用
- 这个方法是在哪个线程执行中调用的,就会得到哪个线程对象
4、Thread的构造器
方法名称 | 说明 |
---|---|
public Thread(String name) | 可以为当前线程指定名称 |
public Thread(Runnable target) | 封装Runnable对象成为线程对象 |
public Thread(Runnable target ,String name ) | 封装Runnable对象成为线程对象,并指定线程名称 |
5、Thread类的线程休眠方法
方法名称 | 说明 |
---|---|
public static void sleep(long time) | 让当前线程休眠指定的时间后再继续执行,单位为毫秒。 |
三、线程安全
1、线程安全问题
- 多个线程同时操作同一个共享资源的时候可能会出现业务安全问题,称为线程安全问题
2、线程安全问题出现的原因
- 存在多线程并发
- 同时访问共享资源
- 存在修改共享资源
四、线程同步
1、线程同步
- 为了解决线程安全问题
- 核心思想:加锁,把共享资源进行上锁,每次只能一个线程进入访问完毕后解锁,然后其他线程才能进来
2、方式一:同步代码块
-
作用:把出现线程安全问题的核心代码给上锁
-
原理:每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行
synchronized(同步锁对象) { 操作共享资源的代码(核心代码) }
-
锁对象要求:
- 理论上:锁对象只要对于当前同时执行的线程来说是统一对象即可
- 规范上:建议使用共享资源作为锁对象
- 对于实例方法建议使用this作为锁对象
- 对于静态方法建议使用字节码(类名.class)对象作为锁对象
3、方式二:同步方法
-
作用:把出现线程安全问题的核心方法给上锁
-
原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行
-
格式:
修饰符 synchronized 返回值类型 方法名称(形参列表) { 操作共享资源的代码 }
-
底层原理:
- 同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码
- 如果方法是实例方法:同步方法默认用this作为的锁对象。但是代码要高度面向对象
- 如果方法是静态方法:同步方法默认用类名.class作为锁对象
4、方式三:Lock锁
- 为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock,更加灵活、方便
- Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作
- Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock锁对象
方法名称 | 说明 |
---|---|
public ReentrantLock() | 获得Lock锁的实现类对象 |
Lock的API
方法名称 | 说明 |
---|---|
void lock() | 获得锁 |
void unlock() | 释放锁 |
五、线程通信【了解】
1、什么是线程通信、如何实现?
- 所谓线程通信就是线程间相互发送数据,线程间共享一个资源即可实现线程通信。
- 线程间会根据共享数据的情况决定自己该怎么做,以及通知其他线程怎么做
2、线程通信常见模型
- 生产者与消费者模型:生产者线程负责生产数据,消费者线程负责消费数据
- 要求:生产者线程生产完数据后唤醒消费者,然后等待自己,消费者消费完该数据后唤醒生产者,然后等待自己。
3、线程通信的前提
线程通信通常是在多个线程操作同一个共享资源的时候需要进行通信,且要保证线程安全
4、Object类的等待和唤醒方法
方法名称 | 说明 |
---|---|
void wait() | 让当前线程等待并释放所占锁,直到另一个线程调用notify()方法或 notifyAll()方法 |
void notify() | 唤醒正在等待的单个线程 |
void notifyAll() | 唤醒正在等待的所有线程 |
注意:上述方法应该使用当前同步锁对象进行调用
六、线程池【重点】
什么是线程池
线程池就是一个可以复用线程的技术
不适用线程池的问题
如果用户每发起一个请求,后台就创建一个新线程来处理,下次新任务来了又要创建新线程,而创建新线程的开销是很大的,这样会严重影响系统的性能
1、线程池实现的API、参数说明
谁代表线程池
- JDK5.0起提供了代表线程池的接口:ExectorService
如何得到线程池对象
- 方式一:使用ExcutorService的实现类ThreadPoolExcutor自创建一个线程池对象
- 方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象
ThreadPoolExecutor构造器的参数说明
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- 参数一:指定线程池的线程数量(核心线程): corePoolSize 不能小于0
- 参数二:指定线程池可支持的最大线程数: maximumPoolSize 最大数量 >= 核心线程数量
- 参数三:指定临时线程的最大存活时间: keepAliveTime 不能小于0
- 参数四:指定存活时间的单位(秒、分、时、天): unit 时间单位
- 参数五:指定任务队列: workQueue 不能为null
- 参数六:指定用哪个线程工厂创建线程: threadFactory 不能为null
- 参数七:指定线程忙,任务满的时候,新任务来了怎么办: handler 不能为null
线程池常见面试题
- 临时线程什么时候创建?
- 新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程
- 什么时候会开始拒绝任务?
- 核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务
2、线程池处理Runnable任务
ThreadPoolExecutor创建线程池对象实例
ExecutorService pools = new ThreadPoolExecutor(3,5,8,TimeUnit.SECONDS,new ArrayBlockingQueue<>(6),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
ExecutorService常用方法
方法名称 | 说明 |
---|---|
void execute(Runnable command) | 执行任务/命令,没有返回值,一般用来执行 Runnable 任务 |
Future submit(Callable task) | 执行任务,返回未来任务对象获取线程结果,一般拿来执行 Callable 任务 |
void shutdown() | 等任务执行完毕后关闭线程池 |
List<Runnable> shutdownNow() | 立刻关闭,停止正在执行的任务,并返回队列中未执行的任务 |
新任务拒绝策略
策略 | 详解 |
---|---|
ThreadPoolExecutor.AbortPolicy | 丢弃任务并抛出RejectedExecutionException异常。是默认的策略 |
ThreadPoolExecutor.DiscardPolicy: | 丢弃任务,但是不抛出异常 这是不推荐的做法 |
ThreadPoolExecutor.DiscardOldestPolicy | 抛弃队列中等待最久的任务 然后把当前任务加入队列中 |
ThreadPoolExecutor.CallerRunsPolicy | 由主线程负责调用任务的run()方法从而绕过线程池直接执行 |
3、线程池处理Callable任务
ExecutorService的常用方法
方法名称 | 说明 |
---|---|
void execute(Runnable command) | 执行任务/命令,没有返回值,一般用来执行 Runnable 任务 |
Future submit(Callable task) | 执行任务,返回未来任务对象获取线程结果,一般拿来执行 Callable 任务 |
void shutdown() | 等任务执行完毕后关闭线程池 |
List<Runnable> shutdownNow() | 立刻关闭,停止正在执行的任务,并返回队列中未执行的任务 |
4、Executors工具类实现线程池
Executors得到线程池对象的常用方法
- Executors:线程池的工具类通过调用方法返回不同类型的线程池对象
方法名称 | 说明 |
---|---|
public static ExecutorService newCachedThreadPool() | 线程数量随着任务增加而增加,如果线程任务执行完毕且空闲了一段时间则会被回收掉。 |
public static ExecutorService newFixedThreadPool(int nThreads) | 创建固定线程数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代它。 |
public static ExecutorService newSingleThreadExecutor () | 创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池会补充一个新线程。 |
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) | 创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务。 |
注意:Excutors的底层其实也是基于线程池的实现类ThreadPoolExcutor创建线程池对象的
Executors使用可能存在的陷阱
- 大型并发系统环境中使用Executors如果不注意可能会出现系统风险
方法名称 | 存在问题 |
---|---|
public static ExecutorService newFixedThreadPool(int nThreads) | 允许请求的任务队列长度是Integer.MAX_VALUE,可能出现OOM错误( java.lang.OutOfMemoryError ) |
public static ExecutorService newSingleThreadExecutor() | |
public static ExecutorService newCachedThreadPool() | 创建的线程数量最大上限是Integer.MAX_VALUE, 线程数可能会随着任务1:1增长,也可能出现OOM错误( java.lang.OutOfMemoryError ) |
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4juzAzWN-1684550850451)(.\img\线程池工具类陷阱.png)]
七、补充知识:定时器
1、定时器
- 定时器是一种控制任务延时调用,或者周期调用的技术
- 作用:闹钟、定式邮件发送
2、定时器的实现方式
- 方式一:Timer
- 方式二:ScheduledExecutorService
3、Timer定时器
构造器 | 说明 |
---|---|
public Timer() | 创建Timer定时器对象 |
方法 | 说明 |
---|---|
public void schedule(TimerTask task, long delay, long period) | 开启一个定时器,按照计划处理TimerTask任务 |
Timer定时器的特点和存在的问题
1、Timer是单线程,处理多个任务按照顺序执行,存在延时与设置定时器的时间有出入
2、可能因为其中的某个任务的异常使Timer线程死掉,从而影响后续任务执行
4、ScheduledExecutorService定时器
- ScheduledExecutorService是JDK1.5中引入了并发包,目的是为了弥补Timer的缺陷,ScheduledExecutorService内部为线程池
Executors的方法 | 说明 |
---|---|
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) | 得到线程池对象 |
ScheduledExecutorService的方法 | 说明 |
---|---|
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) | 周期调度方法 |
ScheduledExecutorService的优点
- 基于线程池,某个任务的执行情况不会影响其他定时任务的执行
八、补充知识:并发,并行
1、并发与并行
- 正在运行的程序(软件)就是一个独立的进程,线程是属于进程的,多个线程其实是并发与并行同时进行的
2、并发的理解
- CPU同时处理线程的数量有限
- CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们感觉这些线程在同时执行,这就是并发
3、并行的理解
- 在同一时刻上,同时有多个线程在被CPU处理并执行
九、补充知识:线程的生命周期
]
1、线程的状态
- 线程的状态:也就是线程从生到死的过程,以及中间经历的各种状态及状态转换
- 理解线程的状态有利于提升并发编程的理解能力
2、Java线程的状态
- Java总共定义了6种状态
- 6种状态都定义在Thread类的内部枚举中
public enum State {
NEW,//新建状态
RUNNABLE,//就绪状态
BLOCKED,//阻塞状态
WAITING,//等待状态
TIMED_WAITING,//计时状态
TERMINATED;//结束状态
}
3、线程的6种状态间的转换
线程状态 | 描述 |
---|---|
NEW(新建) | 线程刚被创建,但是并未启动。 |
Runnable(可运行) | 线程已经调用了start()等待CPU调度 |
Blocked(锁阻塞) | 线程在执行的时候未竞争到锁对象,则该线程进入Blocked状态;。 |
Waiting(无限等待) | 一个线程进入Waiting状态,另一个线程调用notify或者notifyAll方法才能够唤醒 |
Timed Waiting(计时等待) | 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。带有超时参数的常用方法有Thread.sleep 、Object.wait。 |
Teminated(被终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。 |
第十二天:网络编程
什么是网络编程?
- 网络编程可以让程序与网络上的其他设备中的程序进行数据交互
网络通信基本模式
- 常见的通信模式
- Client-Server(CS)
- Browser/Server(BS)
一、网络通信三要素
1、实现网络编程关键的三要素
- **IP地址:**设备在网络中的地址,是唯一的标识
- **端口:**应用程序在设备中唯一标识
- **协议:**数据在网络中传输的规则,常见的协议有UDP协议和TCP协议
2、IP地址
-
IP(Internet Protocol):全称“互联网协议地址”,是分配给上网设备的唯一标识
-
IPv6:128位(16个字节),号称可以为地球每一粒沙子编号
-
IPv6分成8个整数,每个整数用四个十六进制表示,数之间用冒号(:)分开
IP地址形式:
- 公网地址、和私有地址(局域网使用)
- 192.168.开头的就是常见的局域网地址,范围即为192.168.0.0–192.168.255.255专门为组织机构内部使用
IP常用命令:
- ipconfig:查看本机IP地址
- ping IP地址:检查网络是否连通
特殊IP地址:
- 本机IP:127.0.0.1或者localhost:称为回送地址也可称本地回环地址,只会寻找当前所在本机
3、IP地址操作类-InetAddress
InetAddress的使用
- 此类表示Internet协议(IP)地址
InetAddressAPI如下
名称 | 说明 |
---|---|
public static InetAddress getLocalHost() | 返回本主机的地址对象 |
public static InetAddress getByName(String host) | 得到指定主机的IP地址对象,参数是域名或者IP地址 |
public String getHostName() | 获取此IP地址的主机名 |
public String getHostAddress() | 返回IP地址字符串 |
public boolean isReachable(int timeout) | 在指定毫秒内连通该IP地址对应的主机,连通返回true |
4、端口号
- 端口号:表示正在计算机设备上运行的进程(程序),被规定为一个16位的二进制,范围是0~65535
端口类型:
- 周知端口:0-1023,被预先定义的知名应用占用(如:HTTp占用80,FTP占用21)
- 注册端口:1024-49151.分配给用户进程或某些应用程序。(如:Tomcat占用8080,MySQL占用3306)
- 动态端口:49152到65535,之所以称为动态端口,是因为它一般不固定分配某种进程,而是动态分配
注意:我们自己开发程序选择注册端口,且一个设备中不能出现两个程序的端口号一样,否则出错
5、协议:
通信协议:
- 连接和通信数据的规则被称为网络通信协议
网络通信协议有两套参考模型:
- OSI参考模型:时间互联协议标准,全球通信规范,由于此模型过于理想化,未能在因特网上进行广泛推广
- TCP/IP参考模型(或TCP/IP协议:事实上的国际标准)
OSI参考模型 | 各层对应 | 面向操作 |
---|---|---|
应用层 | HTTP、FTP、DNS、SMTP… | 应用程序需要关注的:浏览器,邮箱。程序员一般在这一层开发 |
表示层 | ||
会话层 | ||
传输层 | TCP、UDP… | 选择使用的TCP , UDP协议 |
网络层 | IP、ICMP… | 封装源和目标IP,进行路径选择 |
数据链路层 | 物理寻址、比特流… | 物理设备中传输 |
传输层的2个常见协议
- TCP:传输控制协议
- UDP:用户数据报协议
TCP协议特点
- 使用TCP协议,必须双方先建立连接,它是一种面向连接的可靠通信协议
- 传输前,采用“三次握手”方式建立连接,所以是可靠地
- 在连接中可进行大数据量的传输
- 连接、发送数据都要确认,且传输完毕后,还需释放已建立的连接,通信效率低
TCP协议通信场景
- 对信息安全要求较高的场景,例如:文件下载,金融等数据通信
TCP三次握手确立连接:
TCP四次挥手断开连接
UDP协议:
- UDP是一种无连接,不可靠传输的协议
- 将数据源IP、目的地IP和端口封装成数据包,不需要建立连接
- 每个数据包的大小限制在64KB内
- 发送不管对方是否准备好,接收方收到也不确认,故事不可靠的
- 可以广播发送,发送数据结束时无需释放资源,开销小,速度快
UDP协议通信场景
- 语音通话,视频会话等
二、UDP通信
1、UDP协议的特点
- UDP是一种无连接、不可靠传输的协议
- 将数据源IP、目的地IP和端口以及数据封装成数据包,大小限制在64kb内,直接发出去即可
DatagraPacket:数据包对象
构造器 | 说明 |
---|---|
public DatagramPacket(byte[] buf, int length, InetAddress address, int port) | 创建发送端数据包对象 buf:要发送的内容,字节数组 length:要发送内容的字节长度 address:接收端的IP地址对象 port:接收端的端口号 |
public DatagramPacket(byte[] buf, int length) | 创建接收端的数据包对象 buf:用来存储接收的内容 length:能够接收内容的长度 |
DatagramPacket常用方法
方法 | 说明 |
---|---|
public int getLength() | 获得实际接收到的字节个数 |
DatagramSocket:发送端和接受端对象
构造器 | 说明 |
---|---|
public DatagramSocket() | 创建发送端的Socket对象,系统会随机分配一个端口号。 |
public DatagramSocket(int port) | 创建接收端的Socket对象并指定端口号 |
DatagramSocket类成员方法
方法 | 说明 |
---|---|
public void send(DatagramPacket dp) | 发送数据包 |
public void receive(DatagramPacket p) | 接收数据包 |
三、UDP通信——广播、组播
1、UDP的三种通信方式
-
单播:单台主机与单台主机之间的通信
-
广播:当前主机与所在网络中的所有主机通信
-
组播:当前主机与选定的一组主机的通信
2、UDP如何实现广播
- 使用广播地址:255.255.255.255
- 具体操作:
- 发送端发送的数据包的目的地写的是广播地址、且指定端口。
- 本机所在网段的其他主机的程序只要匹配端口成功即就可以收到消息了。
3、UDP如何实现组播
- 使用组播地址:224.0.0.0~239.255.255.255
- 具体操作:
- 发送端的数据包的目的地址是组播IP(例如:224.0.1.1,端口:9999)
- 接收端必须绑定该组播IP(224.0.1.1),端口还要对应发送端的目的端口9999,这样既可以接受该组播消息
- DatagramSocket的子类MulticastSocket可以在接受端绑定组播IP
四、TCP通信
1、TCP协议回顾:
- TCP是一种面向连接,安全、可靠的传输数据的协议
- 传输前,采用“三次握手”方式,点对点通信,是可靠的
- 在连接中可进行大数据量的传输
2、TCP通信模式
注意:在java中只是使用java.net.Socket类实现通信,底层即是使用了TCP协议
Socket
构造器 | 说明 |
---|---|
public Socket(String host , int port) | 创建发送端的Socket对象与服务端连接,参数为服务端程序的ip和端口。 |
Socket类成员方法
方法 | 说明 |
---|---|
OutputStream getOutputStream() | 获得字节输出流对象 |
InputStream getInputStream() | 获得字节输入流对象 |
ServerSocket
构造器 | 说明 |
---|---|
public ServerSocket(int port) | 注册服务端端口 |
ServicerSocket类成员方法
方法 | 说明 |
---|---|
public Socket accept() | 等待接收客户端的Socket通信连接 连接成功返回Socket对象与客户端建立端到端通信 |
4、同时处理多个客户端消息
5、引入线程池处理多个客户端消息
优势:
- 服务端可以复用线程处理多个客户端,可以避免系统瘫痪
- 适合客户端通信时长较短的场景
五、TCP通信实战案例-即使通信
即使通信
- 即时通信,是指一个客户端的消息发出去,其他客户端可以接收到。
- 之前我们的消息都是发给服务端的。
- 即时通信需要进行端口转发的设计思想。
即使通信-端口转发
六、TCP通信实战案例——模拟BS系统
实现BS开发
注意:服务器必须给浏览器响应HTTP协议格式的数据,否则浏览器不识别
HTTP响应数据的协议格式:就是给浏览器显示的网页信息
第十三天、单元测试、反射、注解、动态代理
一、单元测试
1、单元测试
- 单元测试就是针对最小的功能单元编写测试代码,Java程序最小的功能单元是方法,因此,单元测试就是针对Java方法的测试,进而检查方法的正确性
目前测试方法是怎样进行的,存在什么问题
- 只有一个main方法,如果一个方法的测试失败了,其他方法测试会受到影响
- 无法得到测试的结果报告,需要程序员自己去观察测试是否成功
- 无法实现自动化测试
2、Junit单元测试框架
- JUnit是使用Java语言实现的单元测试框架,它是开源的,Java开发者都应当学习并使用JUnit编写单元测试
- 此外,几乎所有的IDE工具都集成了JUnit,这样我们就可以直接在IDE中编写并运行JUnit测试,Junit目前最新版本是5
优点:
- JUnit可以灵活的选择执行那些方法,可以一键执行全部测试方法
- JUnit可以生成全部方法的测试报告
- 单元测试中的某个方法测试失败了,不会影响其他测试方法的测试
3、JUnit单元测试的实现过程
- 必须导入Junit框架的jar包
- 定义的测试方法必须是无参数无返回值,且公开的方法
- 测试方法使用@Test注解标记
Junit测试某个方法,测试全部方法怎么处理?成功的标志是什么
- 测试某个方法直接右键该方法启动测试
- 测试全部方法,可以选择类或者模块启动
- 红色失败,绿色通过
4、Junit常用注解
Junit4.xxxx版本
注解 | 说明 |
---|---|
@Test | 测试方法 |
@Before | 用来修饰实例方法,该方法会在每一个测试方法执行之前执行一次。 |
@After | 用来修饰实例方法,该方法会在每一个测试方法执行之后执行一次。 |
@BeforeClass | 用来静态修饰方法,该方法会在所有测试方法之前只执行一次。 |
@AfterClass | 用来静态修饰方法,该方法会在所有测试方法之后只执行一次。 |
- 开始执行的方法:初始化资源
- 执行完成后的方法:释放资源
Junit5.xxxx版本
注解 | 说明 |
---|---|
@Test | 测试方法 |
@BeforeEach | 用来修饰实例方法,该方法会在每一个测试方法执行之前执行一次。 |
@AfterEach | 用来修饰实例方法,该方法会在每一个测试方法执行之后执行一次。 |
@BeforeAll | 用来静态修饰方法,该方法会在所有测试方法之前只执行一次。 |
@AfterAll | 用来静态修饰方法,该方法会在所有测试方法之后只执行一次。 |
- 开始执行的方法:初始化资源
- 执行之后的方法:释放资源
二、反射
1、概述
- 反射是指对于任何一个Class类,在“运行的时候”都可以直接得到这个类全部成分。
- 在运行时,可以直接得到这个类的构造器对象:Constructor
- 在运行时,可以直接得到这个类的成员变量对象:Field
- 在运行时,可以直接得到这个类的成员方法对象:Method
- 这种运行时动态获取类信息以及动态调用类中成分的能力称为Java语言的反射机制
反射的关键:
- 反射的第一步都是先得到编译后的Class类对象,然后就可以得到Class的全部成分
HelloWorld.java -> javac -> HelloWorld.class
Class c = HelloWorld.class;
2、反射获取类对象
方式一:Class c1 = Class.forName(“全类名”);
方式二:Class c2 = 类名.class
方式三:Class c3 =对象.getClass();
3、反射获取类的成分对象
Class类中用于获取构造器的方法
方法 | 说明 |
---|---|
Constructor<?>[] getConstructors() | 返回所有构造器对象的数组(只能拿public的) |
Constructor<?>[] getDeclaredConstructors() | 返回所有构造器对象的数组,存在就能拿到 |
Constructor getConstructor(Class<?>… parameterTypes) | 返回单个构造器对象(只能拿public的) |
Constructor getDeclaredConstructor(Class<?>… parameterTypes) | 返回单个构造器对象,存在就能拿到 |
使用反射技术获取构造器对象并使用
- 获取构造器的作用依然是初始化一个对象
Constructor类中用于创建对象的方法
符号 | 说明 |
---|---|
T newInstance(Object… initargs) | 根据指定的构造器创建对象 |
public void setAccessible(boolean flag) | 设置为true,表示取消访问检查,进行暴力反射 |
注意:反射可以破坏封装性,私有的也可以访问
class类中用于获取成员变量的方法
方法 | 说明 |
---|---|
Field[] getFields() | 返回所有成员变量对象的数组(只能拿public的) |
Field[] getDeclaredFields() | 返回所有成员变量对象的数组,存在就能拿到 |
Field getField(String name) | 返回单个成员变量对象(只能拿public的) |
Field getDeclaredField(String name) | 返回单个成员变量对象,存在就能拿到 |
- 获取成员变量的作用依然是在某个对象中取值、赋值
Field类中用于取值、赋值的方法
符号 | 说明 |
---|---|
void set(Object obj, Object value): | 赋值 |
Object get(Object obj) | 获取值。 |
class类中用于获取成员方法
方法 | 说明 |
---|---|
Method[] getMethods() | 返回所有成员方法对象的数组(只能拿public的) |
Method[] getDeclaredMethods() | 返回所有成员方法对象的数组,存在就能拿到 |
Method getMethod(String name, Class<?>… parameterTypes) | 返回单个成员方法对象(只能拿public的) |
Method getDeclaredMethod(String name, Class<?>… parameterTypes) | 返回单个成员方法对象,存在就能拿到 |
Method类中用于触发执行的方法
符号 | 说明 |
---|---|
Object invoke(Object obj, Object… args) | 运行方法 参数一:用obj对象调用该方法 参数二:调用方法的传递的参数(如果没有就不写) 返回值:方法的返回值(如果没有就不写) |
注意:如果某个方法是非public的,需要打开权限,然后再触发执行
4、反射的作用
绕过编译阶段为集合添加数据
- 反射是作用在运行时的技术,此集合的泛型将不能产生约束了,此时是可以为集合存入其他任意类型的元素的。
ArrayList<Integer> list = new ArrayList<>();
list.add(100);
// list.add(“黑马"); // 报错list.add(99);
- 泛型只是在编译阶段可以约束集合只能操作某种数据类型,在编译成Class文件进入运行阶段的时候,其真实类型都是ArrayList了,泛型相当于被擦除了。
三、注解
1、注解的概述、作用
- Java注解(Annotation)又称Java标注,是JDK5.0引入的一种注释机制
- Java语言中的类、构造器、方法、成员变量、参数等都可以被注解进行标注
- 对Java中类、方法、成员变量做标记,然后进行特殊处理,至于到底做何种处理由业务需求来决定
2、自定义注解
- 自定义注解就是自己做一个注解来使用
格式
public @interface 注解名称 {
public 属性类型 属性名() default 默认值 ;
}
特殊属性:
- value属性,如果只有一个value属性的情况下,使用value属性的时候可以省略value名称不写
- 但是如果有多个属性,且多个属性没有默认值,那么value名称是不能省略的
3、元注解
- 元注解:就是注解注解的注解(放在注解上的注解)
常见的元注解有两个:
- @Target:约束自定义注解只能在那些地方使用
- TYPE,类,接口
- FIELD, 成员变量
- METHOD, 成员方法
- PARAMETER, 方法参数
- CONSTRUCTOR, 构造器
- LOCAL_VARIABLE, 局部变量
- @Retention:声明注解的生命周期
- SOURCE: 注解只作用在源码阶段,生成的字节码文件中不存在
- CLASS: 注解作用在源码阶段,字节码文件阶段,运行阶段不存在,默认值.
- RUNTIME:注解作用在源码阶段,字节码文件阶段,运行阶段(开发常用)
4、注解解析
- 注解的操作中经常需要进行解析,注解的解析就是判断是否存在注解,存在注解就解析出内容
与注解解析相关的接口
- Annotation:注解的顶级接口,注解都是Annotation类型的对象
- AnnotatedElement:该接口定义了与注解解析相关的解析方法
方法 | 说明 |
---|---|
Annotation[] getDeclaredAnnotations() | 获得当前对象上使用的所有注解,返回注解数组。 |
T getDeclaredAnnotation(Class annotationClass) | 根据注解类型获得对应注解对象 |
boolean isAnnotationPresent(Class annotationClass) | 判断当前对象是否使用了指定的注解,如果使用了则返回true,否则false |
- 所有的类成分Class,Method,Field,Constructor,都实现了AnnotatedElement接口他们都有解析注解的能力
解析注解的技巧
- 注解在那个成分上,我们就先拿那个成分对象
- 比如注解作用成员方法,则要获得该成员方法对应的Method对象,再来拿上面的注解
- 比如注解作用在类上,则要该类的Class对象,再来拿上面的注解
- 比如注解作用在成员变量上,则要获得该成员变量对应的Field对象,再来拿上面的注解
四、动态代理
什么是代理
- 代理:某些场景下对象会找一个代理对象,来辅助自己完成一些工作。
- 代理主要是对对象的行为额外做一些辅助操作。
如何创建代理对象
- Java中代理的代表类是:java.lang.reflect.Proxy
- Proxy提供了一个静态方法,用于为对象产生一个代理对象返回
Java中如何生成代理,并指定代理干什么事
通过代理对象调用方法的流程
- 先走向代理
- 代理可以为方法额外做一些辅助工作。
- 开发真正触发对象的方法的执行。
- 回到代理中,由代理负责返回结果给方法的调用者。
动态代理的优点
- 非常的灵活,支持任意接口类型的实现类对象做代理,也可以直接为接本身做代理。
- 可以为被代理对象的所有方法做代理。
- 可以在不改变方法源码的情况下,实现对方法功能的增强。
- 不仅简化了编程工作、提高了软件系统的可扩展性,同时也提高了开发效率。
第十四天、XML、XML解析、设计模式
一、XML
概述
- XML是可扩展标记语言(eXtensible Markup Language)的缩写,它是一种数据表示格式,可以描述非常复杂的数据结构,常用于传输和存储数据
XML的特点和使用场景:
- 一是纯文本,默认使用UTF-8编码;二是可嵌套
- 如果把XML内容存为文件,那么他就是一个XML文件
- XML内容经常被当成消息进行网络传输,或者作为配置文件用于存储系统的信息
XML的创建
- 就是创建一个XML类型文件,要求文件的后缀必须是xml
XML的语法规则:
- XML文件的后缀名为:xml
- 文档声明必须是第一行
<?xml version="1.0" encoding="UTF-8" ?>
version:XML默认的版本号码、该属性是必须存在的
encoding:本XML文件的编码
XML的标签(元素)规则:
- 标签由一堆尖括号和合法标识符组成:<name></name>,必须存在一个根标签,有且只有一个
- 标签必须成对出现,有开始,有结束
- 特殊的标签可以不成对,但是必须有结束标记
- 标签可以定义属性,属性和标签名空格隔开,属性值必须用引号引起来
- 标签需要正确嵌套
XML的其他组成:
-
XML文件可以定义注释信息:<!- 注释内容 -->
-
XML文件中可以存在以下特殊字符
< < 小于 > > 大于 & & 和号 ' ' 单引号 " " 引号
-
XML文件可以存在CDATA区:<![CDATA[…内容…]]>
XML的文档约束
什么是文档约束:
- 文档约束:是用来限定xml文件中的标签以及属性应该怎么写
文档约束的分类:
- DTD
- schema
DTD文档约束:
- 可以约束XML文件的编写
- 不可以约束具体的数据类型
schema文档约束:
- schema可以约束具体的数据类型,约束能力上更强大
- scheam本身也是一个xml文件,本身也受到其他约束文件的要求,所以编写的更加严谨
二、XML解析
什么是XML解析
- 使用程序读取XML中的数据
两种常见的解析方式
- SAX解析
- DOM解析
Dom常见的解析工具:
名称 | 说明 |
---|---|
JAXP | SUN公司提供的一套XML的解析的API |
JDOM | JDOM是一个开源项目,它基于树型结构,利用纯JAVA的技术对XML文档实现解析、生成、序列化以及多种操作。 |
dom4j | 是JDOM的升级品,用来读写XML文件的。具有性能优异、功能强大和极其易使用的特点,它的性能超过sun公司官方的dom 技术,同时它也是一个开放源代码的软件,Hibernate也用它来读写配置文件。 |
jsoup | 功能强大DOM方式的XML解析开发包,尤其对HTML解析更加方便 |
DOM解析文档对象模型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UkWxal4L-1684550850458)(.\img\DOM解析文档对象模型.png)]
Dom4j解析XML-得到document对象
SAXReader类
构造器/方法 | 说明 |
---|---|
public SAXReader() | 创建Dom4J的解析器对象 |
Document read(String url) | 加载XML文件成为Document对象 |
Document类
方法名 | 说明 |
---|---|
Element getRootElement() | 获得根元素对象 |
Dom4j解析XML的元素、属性、文本
方法名 | 说明 |
---|---|
List elements() | 得到当前元素下所有子元素 |
List elements(String name) | 得到当前元素下指定名字的子元素返回集合 |
Element element(String name) | 得到当前元素下指定名字的子元素,如果有很多名字相同的返回第一个 |
String getName() | 得到元素名字 |
String attributeValue(String name) | 通过属性名直接得到属性值 |
String elementText(子元素名) | 得到指定名称的子元素的文本 |
String getText() | 得到文本 |
三、XML检索技术:XPath
XPath介绍
- XPath在解析XML文档方面提供了一独树一帜的路径思想,更加优雅,高效
- XPath使用路径表达式来定位XML文档中的元素节点或者属性节点
Document中与XPath相关API
方法名 | 说明 |
---|---|
Node selectSingleNode(“表达式”) | 获取符合表达式的唯一元素 |
List selectNodes(“表达式”) | 获取符合表达式的元素集合 |
XPath的四大检索方案
- 相对路径
- 绝对路径
- 全文检索
- 属性查找
绝对路径路径:
- 采用绝对路径获取从根节点开始逐层查找节点列表并打印信息
方法名 | 说明 |
---|---|
/根元素/子元素/孙元素 | 从根元素开始,一级一级向下查找,不能跨级 |
相对路径
- 先得到根节点
- 再采用相对路径获取下一级节点的子节点并打印信息
方法名 | 说明 |
---|---|
./子元素/孙元素 | 从当前元素开始,一级一级向下查找,不能跨级 |
全文搜索:
- 直接全文搜索所有的name元素并打印
方法名 | 说明 |
---|---|
//contact | 找contact元素,无论元素在哪里 |
//contact/name | 找contact,无论在哪一级,但name一定是contact的子节点 |
//contact//name | contact无论在哪一种,name只要是contact的子孙元素都可以找到 |
属性查找:
- 在全文搜索属性,或带属性的元素
方法名 | 说明 |
---|---|
//@属性名 | 查找属性对象,无论是哪个元素,只要有这个属性即可。 |
//元素[@属性名] | 查找元素对象,全文搜索指定元素名和属性名。 |
//元素//[@属性名=‘值’] | 查找元素对象,全文搜索指定元素名和属性名,并且属性值相等。 |
四、设计模式:工厂模式
什么是工厂设计模式
- 之前我们创建类对象时,都是使用new对象的形式创建,在很多业务场景下也提供了不直接new的方式
- 工厂模式是Java中常用的设计模式之一,这种类型的设计模式属于创建型模式,它提供了一种获取对象的方式
工厂设计模式的作用
- 工厂的方法可以封装对象的创建细节,比如:为该对象进行加工和数据注入
- 可以实现类与类之间的解耦操作(核心)
五、设计模式:装饰模式
什么是装饰模式
- 创建一个新类,包装原始类,从而在新类中提升原来类的功能
- 作用:装饰模式指的是在不改变原来类的基础上,动态的扩展一个类的功能
InputStream(抽象父类)
FileInputStream(实现子类,读写性能较差)
BufferedInputStream(实现子类,装饰类,读写性能高)
- schema可以约束具体的数据类型,约束能力上更强大
- scheam本身也是一个xml文件,本身也受到其他约束文件的要求,所以编写的更加严谨
二、XML解析
什么是XML解析
- 使用程序读取XML中的数据
两种常见的解析方式
- SAX解析
- DOM解析
Dom常见的解析工具:
名称 | 说明 |
---|---|
JAXP | SUN公司提供的一套XML的解析的API |
JDOM | JDOM是一个开源项目,它基于树型结构,利用纯JAVA的技术对XML文档实现解析、生成、序列化以及多种操作。 |
dom4j | 是JDOM的升级品,用来读写XML文件的。具有性能优异、功能强大和极其易使用的特点,它的性能超过sun公司官方的dom 技术,同时它也是一个开放源代码的软件,Hibernate也用它来读写配置文件。 |
jsoup | 功能强大DOM方式的XML解析开发包,尤其对HTML解析更加方便 |
Dom4j解析XML-得到document对象
SAXReader类
构造器/方法 | 说明 |
---|---|
public SAXReader() | 创建Dom4J的解析器对象 |
Document read(String url) | 加载XML文件成为Document对象 |
Document类
方法名 | 说明 |
---|---|
Element getRootElement() | 获得根元素对象 |
Dom4j解析XML的元素、属性、文本
方法名 | 说明 |
---|---|
List elements() | 得到当前元素下所有子元素 |
List elements(String name) | 得到当前元素下指定名字的子元素返回集合 |
Element element(String name) | 得到当前元素下指定名字的子元素,如果有很多名字相同的返回第一个 |
String getName() | 得到元素名字 |
String attributeValue(String name) | 通过属性名直接得到属性值 |
String elementText(子元素名) | 得到指定名称的子元素的文本 |
String getText() | 得到文本 |
三、XML检索技术:XPath
XPath介绍
- XPath在解析XML文档方面提供了一独树一帜的路径思想,更加优雅,高效
- XPath使用路径表达式来定位XML文档中的元素节点或者属性节点
Document中与XPath相关API
方法名 | 说明 |
---|---|
Node selectSingleNode(“表达式”) | 获取符合表达式的唯一元素 |
List selectNodes(“表达式”) | 获取符合表达式的元素集合 |
XPath的四大检索方案
- 相对路径
- 绝对路径
- 全文检索
- 属性查找
绝对路径路径:
- 采用绝对路径获取从根节点开始逐层查找节点列表并打印信息
方法名 | 说明 |
---|---|
/根元素/子元素/孙元素 | 从根元素开始,一级一级向下查找,不能跨级 |
相对路径
- 先得到根节点
- 再采用相对路径获取下一级节点的子节点并打印信息
方法名 | 说明 |
---|---|
./子元素/孙元素 | 从当前元素开始,一级一级向下查找,不能跨级 |
全文搜索:
- 直接全文搜索所有的name元素并打印
方法名 | 说明 |
---|---|
//contact | 找contact元素,无论元素在哪里 |
//contact/name | 找contact,无论在哪一级,但name一定是contact的子节点 |
//contact//name | contact无论在哪一种,name只要是contact的子孙元素都可以找到 |
属性查找:
- 在全文搜索属性,或带属性的元素
方法名 | 说明 |
---|---|
//@属性名 | 查找属性对象,无论是哪个元素,只要有这个属性即可。 |
//元素[@属性名] | 查找元素对象,全文搜索指定元素名和属性名。 |
//元素//[@属性名=‘值’] | 查找元素对象,全文搜索指定元素名和属性名,并且属性值相等。 |
四、设计模式:工厂模式
什么是工厂设计模式
- 之前我们创建类对象时,都是使用new对象的形式创建,在很多业务场景下也提供了不直接new的方式
- 工厂模式是Java中常用的设计模式之一,这种类型的设计模式属于创建型模式,它提供了一种获取对象的方式
工厂设计模式的作用
- 工厂的方法可以封装对象的创建细节,比如:为该对象进行加工和数据注入
- 可以实现类与类之间的解耦操作(核心)
五、设计模式:装饰模式
什么是装饰模式
- 创建一个新类,包装原始类,从而在新类中提升原来类的功能
- 作用:装饰模式指的是在不改变原来类的基础上,动态的扩展一个类的功能
InputStream(抽象父类)
FileInputStream(实现子类,读写性能较差)
BufferedInputStream(实现子类,装饰类,读写性能高)