Java基础
Java基础
面向对象
- 所谓的面向对象,其实就是分析问题时,以问题所涉及到的事或物为中心的分析方式
类和对象: - 类表示归纳和整理
- 对象就表示具体的事物
类与对象
- 类是抽象的,对象是具体的
- 类是模子,对象是具体的实例
- 类是静态的,对象是动态的
- 类的创建过程
- 先声明类
- 创建对象
- 声明网性,所说的属性其实就是类中的变最变航类型变航名称 =变值属性类型性名称 = 属性値
- 声明方法
- void 方法名(参数){ 功能代码 }
- 执行方法
- 对象.属性
- 对象.方法名()
总结:
- static修饰的成员方法叫什么?如何使用?
- 类方法(静态方法)
- 属于类,可以直接用类名访问,也可以用对象访问
使用:类名.类方法
- 无static修饰的成员方法叫什么?如何使用?
- 实例方法(对象的方法)
- 属于对象,只能用对象访问
使用方法:对象.实例方法
注意事项
- 类方法中可以直接访问类的成员,不可以直接访问实例成员。
- 实例方法中既可以直接访问类成员,也可以直接访问实例成员。
- 实例方法中可以出现this关键字,类方法中不可以出现this关键字的。
代码块
代码块是类的5大成分之一(成员变量、构造器、方法、代码块、内部类)
代码块分为两种:
- 静态代码块:
格式:static{}
特点:类加载时自动执行,由于类只会加载一次,所以静态代码块也只会执行一次。
作用:完成类的初始化,例如:对类变量的初始化赋值。 - 实例代码块:
格式:{}
特点:每次创建对象时,执行实例代码块,并在构造器前执行。
作用:和构造器一样,都是用来完成对象的初始化的,例如:对实例变量进行初始化赋值。
构造器
构造器是类的5大成分之一(成员变量、构造器、方法、代码块、内部类)
构造器的作用:
- 创建对象
- 完成对象的初始化
- 构造器没有返回值,连void都不写
- 构造器的名称必须和类名完全一样
- 构造器可以有参数,因此可以发生重载
- 一旦定义了构造器,有默认的构造器,如果没有显示定义,则系统会自动创建一个无参的构造器。
- 构造器内不能使用return返回数据。
- 一旦显示定义了构造器,系统就不再提供默认的无参构造器。
构造器与代码块的区别
- 执行顺序:
- 代码块在构造器之前执行
- 构造器在代码块之后执行
- 作用:
- 代码块可以完成对象的初始化,例如:对实例变量进行初始化赋值
- 构造器可以完成对象的初始化,例如:对类变量的初始化赋值
内存模型
- 栈(stack):存放局部变量,方法参数,对象的引用
- 堆(heap):存放new出来的对象
- 方法区(method area):存放类的信息,常量,静态变量
- 程序计数器(program counter):记录当前线程执行到哪一行代码
StringAPI
继承
- 继承是面向对象三大特征之一
- 继承是类与类之间的关系
- 继承的特点:
- 子类拥有父类的所有成员(成员变量和方法)
- 子类可以拥有自己的成员
- 子类可以对父类继承的成员进行扩展,重写
- 子类可以继承父类的属性和行为,但是子类不能继承父类的构造器,
- Java是单继承模式:一个类只能继承一个直接父类。
- Java不支持多继承、但是支持多层继承
- 继承的语法:
- 子类继承父类,使用extends关键字
在子类方法中访问成员(成员变量、成员方法)满足:
- 就近原则,子类没有找子类、子类没有找父类、父类没有就报错!
如果子父类中出现了重名的成员,此时如果一定要在子类中使用父类的怎么办?
- 格式: super.父类成员变量/父类成员方法
this关键字是什么?
- 出现在构造器和成员方法中,代表当前对象的地址
this关键字在构造器中、成员方法中可以做什么?
- 可以用于指定访问当前对象的成员。
子类继承父类后构造器的特点:
- 子类中所有的构造器默认都会先访问父类中无参的构造器,再执行自己。
为什么?
- 子类在初始化的时候,有可能会使用到父类中的数据,如果父类没有完成初始化,子类将无法使用父类的数据。
- 子类初始化之前,一定要调用父类构造器先完成父类数据空间的初始化。
怎么调用父类构造器的?
- 子类构造器的第一行语句默认都是:super(),不写也存在。
this(…)和super(…)使用注意点:
- 子类通过 this (.)去调用本类的其他构造器,本类其他构造器会通过 super 去手动调用父类的构造器,最终还是会调用父类构造器的。
- 注意:this(…)super(…)都只能放在构造器的第一行,所以二者不能共存在同一个构造器中。
方法重写
- 什么是方法重写?
在继承体系中,子类出现了和父类中一模一样的方法声明,我们就称子类这个方法是重写的方法。 - 方法重写的应用场景
当子类需要父类的功能,但父类的该功能不完全满足自己的需求时
子类可以重写父类中的方法。 - 案例演示:
旧手机的功能只能是基本的打电话,发信息
新手机的功能需要能够:基本的打电话下支持视频通话。基本的发信息下支持发送语音和图片。
@Override重写注解
- @Override是放在重写后的方法上,作为重写是否正确的校验注解。
- 加上该注解后如果重写错误,编译阶段会出现错误提示。
- 建议重写方法都加@Override注解,代码安全,优雅!
方法重写注意事项和要求
- 重写方法的名称、形参列表必须与被重写方法的名称和参数列表一致。
- 私有方法不能被重写。
子类重写父类方法时,访问权限必须大于或者等于父类
暂时了解:缺省 < protected < public子类不能重写父类的静态方法,如果重写会报错的。
- 方法重写是什么样的?
- 子类写一个与父类申明一样的方法覆盖父类的方法。
- 方法重写建议加上哪个注解,有什么好处?
- @Override注解可以校验重写是否正确,同时可读性好
- 重写方法有哪些基本要求?
- 重写方法的名称和形参列表应该与被重写方法一致。
- 私有方法不能被重写。
- 子类重写父类方法时,访问权限必须大于或者等于父类被重写的方法的权限
封装
- 如何进行封装更好?
一般建议对成员变量使用private(私有、隐藏)关键字修饰进(private修饰的成员只能在当前类中访问)为每个成员变量提供配套public修饰的的getter、setter方法暴露
其取值和赋值
ArrList集合
- ArrayList是集合中的一种,它支持索引
- ArrayList集合中存放的是对象的地址
- ArrayList集合的对象获取
构造器 | 说明 |
---|---|
public ArrayList() | 创建一个新的ArrList集合 |
- ArrayList集合的常用方法
方法名 | 说明 |
---|---|
public boolean add(E e) | 向集合中添加元素 |
public E remove(int index) | 删除指定索引位置的元素 |
public E get(int index) | 获取指定索引位置的元素 |
public int size() | 获取集合中元素的个数 |
public boolean contains(E e) | 判断集合中是否包含指定元素 |
public void clear() | 清空集合中所有的元素 |
public boolean isEmpty() | 判断集合是否为空 |
public int indexOf(E e) | 获取指定元素在集合中第一次出现的索引位置 |
public int lastIndexOf(E e) | 获取指定元素在集合中最后一次出现的索引位置 |
public E set(int index, E e) | 修改指定索引位置的元素 |
public E[] toArray() | 将集合转换为数组 |
- ArrayList集合的遍历
方法名 | 说明 |
---|---|
public Iterator |
获取集合的迭代器 |
- ArrayList集合的常用遍历方式
- 迭代器遍历
- 增强for循环遍历
final
- final 修饰类,不能被继承,例如工具类
- final 修饰方法,方法不能被继重写
- final 修饰变量,变量有且只能被赋值一次
注意:
final修饰的变量是基本类型:那么变量存储的数据值不能发生改变。
final修饰的变量是引用类型:那么变量存储的地址值不能发生改变,但是地址指向的对象内容是可以发生变化的
常量
- 常量是使用了public static final修饰的变量,必须有初始值,而且执行过程中其值不发生改变
- 作用以及好处:
- 可以提高程序的可读性
- 提高程序的安全性
- 方便维护
- 命名规范:英文单词全部大写,多个单词下划线进行连接
- 执行过程:
- 编译阶段:常量名会存入到class文件中,编译阶段常量名会替换成常量值
- 运行阶段:常量值会存入到内存中,运行阶段常量值不会发生改变
枚举
1 | //修饰符 enum 枚举名{} |
- 枚举类都是继承了枚举类型:java.lang.Enum
- 枚举都是最终类,不可以被继承。
- 构造器的构造器都是私有的,枚举对外不能创建对象。
- 枚举类的第一行默认都是罗列枚举对象的名称的。
- 枚举类相当于是多例模式
抽象类
- 抽象类是使用abstract修饰的类,抽象类中可以包含抽象方法
- 抽象类不能被实例化,抽象类可以包含非抽象方法
- 抽象类可以包含抽象方法,也可以包含非抽象方法
- 抽象类可以包含构造器,抽象类的构造器是用来初始化成员变量
- 抽象类不能使用final修饰,因为抽象类是要被继承的
- 抽象类中可以包含静态方法,静态方法可以被继承
注意事项
- 抽象方法只能有方法签名不能有方法体
- 一个类中如果包含抽象方法,那么该类必须是抽象类
- 类有的成员(成员变量、方法、构造器)抽象类都具备
- 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
- 一个类继承了抽象类必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类
- 不能用abstract修饰变量、代码块、构造器。
- 抽象类、抽象方法是什么样的?
- 都是用abstract修饰的;抽象方法只有方法签名,不能写方法体。
- 一个类中定义了抽象方法,这个类必须声明成抽象类
- 抽象类基本作用是啥?
- 作为父类,用来被继承的。
- 继承抽象类有哪些要注意?
- 一个类如果继承了抽象类,那么这个类必须重写完抽象类的全部抽象方法否则这个类也必须定义成抽象类。
- final和abstract是什么关系?
- 互斥关系
- abstract定义的抽象类作为模板让子类继承,final定义的类不能被继承
- 抽象方法定义通用功能让子类重写,final定义的方法子类不能重写
模板方法模式
- 模板方法模式解决了什么问题?
- 提高了代码的复用性
- 模板方法已经定义了通用结构,模板方法不能确定的部分定义成抽象方法,交给子类实现,因此,使用者只需要关心自己需要实现的功能即可。
接口
- 接口的命名格式:
1 | public interface 接口名 { |
-
接口的用法
- 接口是用来被类实现(implements)的,实现接口的类称为实现类。实现类可以理解成所谓的子类
- 接口可以被类单实现,也可以被多实现
- 一个类实现接口,必须重写完全部接口的全部抽象方法,否则这个类需要定义成抽象类
-
小结
- 类和类的关系:单继承,
- 类和接口的关系:多实现。
- 接口和接口的关系:多继承,一个接口可以同时继承多个接口
-
接口多继承的作用
- 规范合并,整合多个接口为同一个接口,便于子类实现。
泛型
- 定义类、接口、方法时,同时声明了一个或者多个类型变量(如:
),称为泛型类、泛型接口,泛型方法、它们统称为泛型。 - 作用:泛型提供了在编译阶段约束所能操作的数据类型,并自动进行检查的能力!这样可以避免强制类型转换,及其可能出现的异常。
- 泛型的本质:把具体的数据类型作为参数传给类型变量。
- 泛型不支持基本数据类型,只能支持对象类型
API
Object
所有类都是Object类的子孙类,都可以使用Object类的方法
方法名 | 说明 |
---|---|
public boolean equals(Object obj) | 判断当前对象与指定对象是否相等 |
public String toString() | 返回当前对象的字符串表示 |
public Object clone() | 对象克隆 |
tostring存在的意义: toString()方法存在的意义就是为了被子类重写,以便返回对象具体的内容。
equals存在的意义: 直接比较两个对象的地址是否相同完全可以用“==”替代equals,equals存在的意义就是为了被子类重写,以便子类自己来定制比较规则(比如比较对象内容)
- Object中toString方法的作用是什么?存在的意义是什么?
- 基本作用:返回对象的字符串形式,
- 存在的意义:让子类重写,以便返回子类对象的内容。
- Object中equals方法的作用是什么?存在的意义是什么?
- 基本作用:默认是比较两个对象的地址是否相等,
- 存在的意义:让子类重写,以便用于比较对象的内容是否相同。
浅克隆: 拷贝出的新对象,与原对象中的数据一模一样(引用类型拷贝的只是地址)
深克隆:对象中基本类型的数据直接拷贝,对象中的字符串数据拷贝的还是地址。对象中还包含的其他对象,不会拷贝地址,会创建新对象。
Objects
Objects类是java.util包下的一个工具类,里面提供了很多静态方法,用来操作对象。
方法名 | 说明 |
---|---|
public static boolean equals(Object a, Object b) | 判断两个对象是否相等 |
public static |
判断指定对象是否为null,如果为null,抛出异常 |
public static String toString(Object obj) | 返回对象的字符串表示 |
public static boolean isNull(Object obj) | 判断指定对象是否为null |
public static boolean nonNull(Object obj) | 判断指定对象是否不为null |
public static int hashCode(Object obj) | 返回指定对象的哈希值 |
包装类
包装类就是把基本类型的数据包装成对象。
基本数据类型 | 对应的包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
自动拆箱: 基本类型的数据自动转换成包装类对象
自动装箱: 包装类对象自动转换成基本类型的数据
1 | Integer a = Interger.valueof(21) |
- 包装类的其他操作
- 可以把基本类型的数据转换成字符串类型
- 可以把字符串类型的数值转换成数值本身对应的数据类型
Stringbuilder
StringBuilder是一个可变的字符序列,可以对字符串进行拼接、插入、删除等操作。
方法名 | 说明 |
---|---|
public StringBuilder append(String str) | 拼接字符串 |
public StringBuilder insert(int offset, String str) | 在指定位置插入字符串 |
public StringBuilder delete(int start, int end) | 删除指定位置的字符串 |
public StringBuilder reverse() | 反转字符串 |
构造器 | 说明 |
---|---|
public StringBuilder() | 创建一个空的字符串构建器 |
public StringBuilder(String str) | 创建一个指定字符串初始化的字符串构建器 |
1 | StringBulider sb = new StringBulider("liujiale"); |
- 对于字符串相关的操作,如频繁的拼接、修改等,建议用StringBuidler,效率更高!
- 注意:如果操作字符串较少,或者不需要操作,以及定义字符串变量,还是建议用String。
StringBuffer
StringBuffer是一个可变的字符序列,可以对字符串进行拼接、插入、删除等操作。
方法名 | 说明 |
---|---|
public StringBuffer append(String str) | 拼接字符串 |
public StringBuffer insert(int offset, String str) | 在指定位置插入字符串 |
public StringBuffer delete(int start, int end) | 删除指定位置的字符串 |
public StringBuffer reverse() | 反转字符串 |
- StringBuffer的用法与StringBuilder是一模一样的
- 但 StringBuilder是线程不安全的 StringBuffer是线程安全的
Math
Math类是java.lang包下的一个工具类,里面提供了很多静态方法,用来操作数学相关的数据。
方法名 | 说明 |
---|---|
public static int abs(int a) | 返回指定整数的绝对值 |
public static double ceil(double a) | 返回大于或等于指定双精度型数值的最小整数 |
public static double floor(double a) | 返回小于或等于指定双精度型数值的最大整数 |
public static int max(int a, int b) | 返回两个整数中较大的值 |
public static int min(int a, int b) | 返回两个整数中较小的值 |
public static double pow(double a, double b) | 返回第一个参数的第二个参数次幂 |
public static double random() | 返回一个随机的小数,该随机数大于等于0.0,小于1.0 |
public static int round(float a) | 返回最接近指定浮点型数值的整数 |
public static long round(double a) | 返回最接近指定双精度型数值的整数 |
public static double sqrt(double a) | 返回指定双精度型数值的平方根 |
Random
Random类是java.util包下的一个工具类,里面提供了很多静态方法,用来生成随机数。
方法名 | 说明 |
---|---|
public static int nextInt() | 返回一个随机整数,该随机数介于int类型的最小值和最大值之间 |
public static int nextInt(int n) | 返回一个随机整数,该随机数介于0(包含)和指定值(不包含)之间 |
public static double nextDouble() | 返回一个随机小数,该随机数介于0.0和1.0之间 |
public static boolean nextBoolean() | 返回一个随机布尔值,该随机值是true或false |
public static void setSeed(long seed) | 设置随机数种子 |
System
System类是java.lang包下的一个工具类,里面提供了很多静态方法,用来获取与系统相关的信息。
方法名 | 说明 |
---|---|
public static long currentTimeMillis() | 返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差 |
public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length) | 将数组中指定的数据复制到另一个数组中 |
public static void exit(int status) | 终止当前正在运行的Java虚拟机 |
public static void gc() | 运行垃圾回收器 |
Runtime
Runtime类是java.lang包下的一个工具类,里面提供了很多静态方法,用来获取与运行环境相关的信息。
方法名 | 说明 |
---|---|
public static Runtime getRuntime() | 返回与当前Java程序关联的Runtime对象 |
public static long totalMemory() | 返回Java虚拟机中的内存总量 |
public static long freeMemory() | 返回Java虚拟机中的空闲内存量 |
public static void gc() | 运行垃圾回收器 |
BigDecimal
用来表示一个任意精度的小数。
方法名 | 说明 |
---|---|
public BigDecimal(String val) | 创建一个指定字符串表示的BigDecimal对象 |
public BigDecimal add(BigDecimal augend) | 将两个BigDecimal对象相加 |
public BigDecimal subtract(BigDecimal subtrahend) | 将两个BigDecimal对象相减 |
public BigDecimal multiply(BigDecimal multiplicand) | 将两个BigDecimal对象相乘 |
public BigDecimal divide(BigDecimal divisor) | 将两个BigDecimal对象相除 |
public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode) | 将两个BigDecimal对象相除,并保留指定的小数位数,可以使用指定的舍入模式 |
日期时间
Date
代表日期时间
方法名 | 说明 |
---|---|
public long getTime() | 返回从1970年1月1日00:00:00走到此刻的总的毫秒数 |
public void setTime(long time) | 设置日期对象的时间为当前时间毫秒值对应的时间 |
SimpleDateFormat
SimpleDateFormat是一个日期时间格式化类,用来格式化和解析日期时间字符串。
方法名 | 说明 |
---|---|
public SimpleDateFormat(String pattern) | 创建一个指定模式的字符串格式化器 |
public String format(Date date) | 将日期对象格式化为日期时间字符串 |
public Date parse(String source) | 将日期时间字符串解析为日期对象 |
1 | String s1 = "2023-11-11 12:12:11"; |
Calendar
- 代表的是系统此刻时间对应的日历。
- 通过它可以单独获取、修改时间中的年、月、日、时、分、秒等
方法名 | 说明 |
---|---|
public static Calendar getInstance() | 获取一个代表系统此刻时间的日历对象 |
public int get(int field) | 获取指定日历字段的值 |
public void set(int field, int value) | 设置指定日历字段的值 |
public abstract void add(int field, int amount) | 根据日历的规则,为指定日历字段添加或减去指定的时间量 |
Java8新日期时间
- LocalDate:代表本地日期(年、月、日、星期)
- LocalTime:代表本地时间(时、分、秒、纳秒)
- LocalDateTime:代表本地日期、时间(年、月、日、星期、时、分、秒、纳秒)
| 方法名 | 说明 |
| -------- | --------- |
| public static LocalDate now() | 获取当前日期 |
| public static LocalTime now(Clock clock) | 获取当前时间 |
| public static LocalDateTime now(Clock clock) | 获取当前日期和时间 |
| public static LocalDateTime now(ZoneId zone) | 获取当前日期和时间 |
| public static LocalDateTime now(ZoneId zone, Clock clock) | 获取当前日期和时间 |
| public static LocalDateTime of(int year, int month, int dayOfMonth, int hour, int minute, int second) | 获取指定日期和时间的日期时间对象 |
| public static LocalDateTime of(int year, int month, int dayOfMonth, int hour, int minute, int second, int nanoOfSecond) | 获取指定日期和时间的日期时间对象 |
| public static LocalDateTime of(LocalDate date, LocalTime time) | 获取指定日期和时间的日期时间对象 |
Arrays
Arrays类用来操作数组。
方法名 | 说明 |
---|---|
public static String toString(int[] a) | 将数组转换为字符串 |
public static void sort(int[] a) | 对数组进行排序 |
如果数组中存储的是对象,如何排序?
1 | public class Test1 { |
学生类
1 | public class Student implements Comparable<Student>{ |
JDK8新特性
Lambda
1 | public class Test { |
Lambda省略规则
- 参数类型可以省略不写。
- 如果只有一个参数,参数类型可以省略,同时()也可以省略。
- 如果Lambda表达式中的方法体代码只有一行代码,可以省略大括号不写,同时要省略分号!此时,如果这行代码是return语句,也必须去掉return不写.
方法引用
- 静态方法引用
用法:类名::静态方法。
使用场景:
如果某个Lambda表达式里只是调用一个静态方法,并且前后参数的形式一致,就可以使用静态方法引用。
1 | Arrays.sort(students , new Comparator<Student>() { |
- 实例方法的引用
用法:对象名::实例方法。
使用场景:
如果某个Lambda表达式里只是调用一个实例方法,并且前后参数的形式一致,就可以使用实例方法引用。
1 | CompareBydata compare = new CompareBydata(); |
- 特定类型的方法引用
用法:类型::方法。
使用场景:
如果某个Lambda表达式里只是调用一个实例方法,并且前面参数列表中的第一个参数是作为方法的主调后面的所有参数都是作为该实例方法的入参的,则此时就可以使用特定类型的方法引用。
1 | public class Test1 { |
- 构造方法的引用
用法:类名::new
使用场景:
如果某个Lambda表达式里只是创建一个对象,并且前面参数列表中的第一个参数是作为构造方法的主调后面的所有参数都是作为该构造方法的入参的,则此时就可以使用构造方法的引用。
1 | public class Test1 { |
正则表达式
正则表达式用于搜索替换,切割内容
方法名 | 说明 |
---|---|
public String replaceAll(String regex,String replacement) | 替换所有匹配的字符串 |
public String replaceFirst(String regex,String replacement) | 替换第一个匹配的字符串 |
public String[] split(String regex) | 切割字符串 |
public boolean matches(String regex) | 判断是否匹配正则表达式 |
public String[] split(String regex,int limit) | 切割字符串,limit限制切割次数 |
异常
自定义异常
- Java无法为这个世界上全部的问题都提供异常类来代表,如果企业自己的某种问题,想通过异常来表示
以便用异常来管理该问题,那就需要自己来定义异常类了
-
运行时异常
- 定义一个异常类继承RuntimeException,
- 重写构造器。
- 通过throw new 异常类(xxx)来创建异常对象并抛出。编译阶段不报错,提醒不强烈,运行时才可能出现!!
-
编译时异常
- 定义一个异常类继承Exception.
- 重写构造器。
- 通过throw new 异常类(xxx)来创建异常对象并抛出编译阶段就报错,提醒更加强烈!
-
处理方式
- 捕获异常,记录异常并响应合适的信息给用户
- 捕获异常,尝试重新修复
抛出异常
- 在方法上使用throws关键字,可以将方法内部出现的异常抛出去给调用者处理
- 直接捕获程序出现的异常(try-catch捕获)
集合框架
Collection集合
- Collection代表单列集合,每个元素(数据)只包含一个值
- Map代表双列集合,每个元素包含两个值(键值对)
- Collection是单列集合的祖宗,它规定的方法(功能)是全部单列集合都会继承的
- Collection常见方法
方法名 | 说明 |
---|---|
public boolean add(E e) | 添加元素 |
public void clear() | 清空集合 |
public boolean remove(E e) | 删除元素 |
public boolean contains(E e) | 判断集合中是否包含某个元素 |
public boolean isEmpty() | 判断集合是否为空 |
public int size() | 获取集合中元素的个数 |
public Object[] toArray() | 将集合转换为数组 |
- Collections
是用来操作集合的工具类
方法名 | 说明 |
---|---|
public static void reverse(List<?> list) | 反转集合中元素的顺序 |
public static void shuffle(List<?> list) | 随机排序集合中元素 |
public static |
升序排序集合中元素 |
public static |
将多个元素添加到集合中 |
注意:Collections中的sort方法,只能对List集合进行排序,不能对Set集合进行排序。
排序方式一:
方法名 | 说明 |
---|---|
public static |
升序排序集合中元素 |
- 本方法可以直接对自定义类型的List集合排序,但自定义类型必须实现了Comparable接口,指定了比较规则才可以。
排序方式二:
方法名 | 说明 |
---|---|
public static |
升序排序集合中元素 |
遍历集合
- 增强for循环
- 增强for循环,也称为for each循环,用来遍历数组和集合。
- 增强for循环的语法格式:
1 | for(元素的数据类型 变量名:数组或者集合){ |
- 迭代器
- 迭代器是用来遍历集合的专用方式(数组没有迭代器),在Java中迭代器的代表是Iterator,用来遍历集合。
- Lambda表达式
List集合
List系列集合特点:有序,可重复,有索引
- ArrayList:有序,可重复,有索引。
- LinkedList:有序,可重复,有索引。
二者底层实现不同!适合的场景不同
方法名 | 说明 |
---|---|
public boolean add(E e) | 添加元素 |
public void clear() | 清空集合 |
public boolean remove(E e) | 删除元素 |
public boolean contains(E e) | 判断集合中是否包含某个元素 |
public boolean isEmpty() | 判断集合是否为空 |
public int size() | 获取集合中元素的个数 |
public Object[] toArray() | 将集合转换为数组 |
indexof()方法根据集合中的数值来找到对应索引的位置
contains()方法判断集合中是否存在重复数值,有返回true,没有返回forse
- ArrayList
- ArrayList底层是数组,查询快,增删慢
- 数组默认长度为10,当添加元素时,会自动扩容,默认扩容为原来的1.5倍
应用场景:
1、ArrayList适合:根据索引查询数据比如根据随机索引取数据(高效)!或者数据量不是很大时!
2、ArrayList不适合:数据量大的同时又要频繁的进行增删操作!
- 查询速度快(注意:是根据索引查询数据快):查询数据通过地址值和索引定位,查询任意数据耗时相同
- 删除效率低:可能需要把后面很多的数据进行前移。
- 添加效率极低:可能需要把后面很多的数据后移,再添加元素;或者也可能需要进行数组的扩容。
- LinkedList
- LinkedList底层是链表,查询慢,增删快,但对首尾元素进行增删改查的速度是极快的
- 链表的每个元素都包含有数据和指针,指针指向下一个元素
- LinkedList新增了:很多首尾操作的特有方法。
方法名 | 说明 |
---|---|
public void addFirst(E e) | 在链表的首部添加元素 |
public void addLast(E e) | 在链表的尾部添加元素 |
public E getFirst() | 获取链表的首部元素 |
public E getLast() | 获取链表的尾部元素 |
public E removeFirst() | 删除链表的首部元素 |
public E removeLast() | 删除链表的尾部元素 |
应用场景:队列(先进先出),栈(先进后出)
1 | public static void main(String[] args) { |
Set集合
Set系列集合特点: 无序;添加数据的顺序和获取出的数据顺序不一致;不重复;无索引;
- Hashset: 无序、不重复、无索引。
- LinkedHashset: 有序、不重复、无索引。
- TreeSet:排序、不重复、无索引
哈希值
- 就是一个int类型的数值,Java中每个对象都有一个哈希值。
- Java中的所有对象,都可以调用obejct类提供的hashCode方法,返回该对象自己的哈希值
public int hashcode():返回对象的哈希码值。对象哈希值的特点:
- 同一个对象多次调用hashCode()方法返回的哈希值是相同的。
- 不同的对象,它们的哈希值一般不相同,但也有可能会相同(哈希碰撞)
- HashSet底层原理
- 基于哈希表实现。
- 哈希表是一种增删改查数据,性能都较好的数据结构。
- 哈希表
- JDK8之前,哈希表=数组+链表
- JDK8开始,哈希表=数组+链表+红黑树
- 平衡二叉树
- 平衡二叉树是二叉搜索树,又被称为AVL树。
- 它是一颗空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一颗平衡二叉树。
- 平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等。
- 在满足查找二叉树的大小规则下,让树尽可能矮小,以此提高查数据的性能
- 红黑树
- 它是一颗空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一颗红黑树。
- 它是一种特殊的二叉树,树中的每个节点都只能是红色或者黑色。
- 树中根节点是黑色。
- 每个叶子节点(NIL)是黑色。
- 不能有相邻接的两个红色节点。
- 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
- 红黑树,就是可以自平衡的二叉树
- 红黑树是一种增删改查数据性能相对都较好的结构
-
哈希表和红黑树的区别
- 哈希表查询速度快,增删速度慢。
- 红黑树查询速度慢,增删速度快。
- 哈希表适用于查询多,增删少的情况。
- 红黑树适用于查询少,增删多的情况。
-
HashSet去重复
-
LinkedHashSet底层原理
- 依然是基于哈希表(数组、链表、红黑树)实现的。
- 但是,它的每个元素都额外的多了一个双链表的机制记录它前后元素的位置。
1 | public static void main(String[] args) { |
- TreeSet
- 特点:不重复、无索引、可排序(默认升序排序,按照元素的大小,由小到大排序)
- 底层是基于红黑树实现的排序
注意
- 对于数值类型:Integer,Double,默认按照数值本身的大小进行升序排序
- 对于字符串类型:默认按照首字符的编号升序排序。
- 对于自定义类型如Student对象,Treeset默认是无法直接排序的
- 自定义排序规则
TreeSet集合存储自定义类型的对象时,必须指定排序规则,支持如下两种方式来指定比较规则。 - 方式一
让自定义的类(如学生类)实现Comparable接口,重写里面的compareTo方法来指定比较规则 - 方式二
通过调用Treeset集合有参数构造器,可以设置Comparator对象(比较器对象,用于指定比较规则
public TreeSet(Comparator<?super E>comparator)
两种方式中,关于返回值的规则:
- 如果认为第一个元素>第二个元素 返回正整数即可
- 如果认为第一个元系<第二个元表返回负整数即可。
- 如果认为第一个元素=第二个元索返回0即可,此时Treeset集合只会保留一个元素,认为两者重复。
注意:如果类本身有实现Comparable接口,TreeSet集合同时也自带比较器,默认使用集合自带的比较器排序
各种集合的应用场景
- 如果希望记住元素的添加顺序,需要存储重复的元素,又要频繁的根据索引查询数据?
- 用ArrayList集合(有序、可重复、有索引),底层基于数组的。(常用)
- 如果希望记住元素的添加顺序,且增删首尾数据的情况较多?
- 用LinkedList集合(有序、可重复、有索引),底层基于双链表实现的,
- 如果不在意元索顺序,也没有重复元素需要存储,只希望增删改查都快?
- 用Hashset集合(无序,不重复,无索引),底层基于哈希表实现的。(常用)
- 如果希望记住元素的添加顺序,也没有重复元索需要存储,且希望增删改查都快?
- 用LinkedHashset集合(有序,不重复,无索引),底层基于哈希表和双链表。
- 如果要对元素进行排序,也没有重复元素需要存储?且希望增删改查都快?
- 用TreeSet集合,基于红黑树实现
- 集合的并发修改异常
- 使用迭代器遍历集合时,又同时在删除集合中的数据,程序就会出现并发修改异常的错误。
- 由于增强for循环遍历集合就是迭代器遍历集合的简化写法,因此,使用增强for循环遍历集合,又在同时删除集合中的数据时,程序也会出现并发修改异常的错误
- 怎么保证遍历集合同时删除数据时不出bug?
- 使用迭代器遍历集合,但用迭代器自己的删除方法删除数据即可。
- 如果能用for循环遍历时:可以倒着遍历并删除;或者从前往后遍历,但删除元素后做i–操作。
Map集合
- Map集合称为双列集合,格式:{key1=value1,key2=value2,key3=value3,…},一次需要存一对数据做为一个元素
- Map集合的每个元素"key=value"称为一个键值对/键值对对象/一个Entry对象,Map集合也被叫做"键值对集合"
- Map集合的所有键是不允许重复的,但值可以重复,键和值是一一对应的,每一个键只能找到自己对应的值
Map集合体系
Map系列集合的特点都是由键决定的,值只是一个附属品,值是不做要求的
- HashMap(由键决定特点):无序、不重复、无索引;(用的最多)
- LinkedHashMap (由键决定特点):由键决定的特点:有序、不重复、无索引
- TreeMap (由键决定特点):按照大小默认升序排序、不重复、无索引。
- Map是双列集合的祖宗,它的功能是全部双列集合都可以继承过来使用的,
方法名 | 说明 |
---|---|
void clear() | 清除集合中的所有元素 |
boolean containsKey(Object key) | 判断集合中是否包含指定的键 |
boolean containsValue(Object value) | 判断集合中是否包含指定的值 |
Set<Map.Entry<K,V>> entrySet() | 返回所有键值对对象的集合 |
V get(Object key) | 根据键获取值 |
boolean isEmpty() | 判断集合是否为空 |
V put(K key,V value) | 存储键值对 |
V remove(Object key) | 根据键删除键值对 |
int size() | 获取集合中键值对的数量 |
Collection |
获取所有值 |
Map集合的遍历
1 | public static void main(String[] args) { |
案例:
1 | public class Test2 { |
LinkedHashMap
底层数据结构依然是基于哈希表实现的,只是每个键值对元素又额外的多了一个双链表的机制记录元素顺序(保证有序)。
LinkedHashMap集合的特点:有序、不重复、无索引
- LinkedHashMap集合底层是链表和哈希表结合组成的,底层哈希表的键和值都是Entry对象
- LinkedHashMap集合的Entry对象,重写了hashCode()和equals()方法,保证键不重复,且按照添加的顺序保存
TreeMap
底层数据结构是基于红黑树实现的,保证按照键的大小顺序保存键值对
TreeMap集合的特点:按照键的大小默认升序排序、不重复、无索引(只能对键排序)
- TreeMap集合的底层是红黑树,红黑树是一颗自平衡的二叉树,保证按照键的大小顺序保存键值对
- TreeMap集合的键和值都不能为null
TreeMap集合同样也支持两种方式来指定排序规则 - 让类实现Comparable接口,重写比较规则。
- TreeMap集合有一个有参数构造器,支持创建Comparator比较器对象,以便用来指定比较规则。
集合的嵌套
1 | public static void main(String[] args) { |
Stream
- 也叫Stream流,是Jdk8开始新增的一套API (java.util.stream.*),可以用于操作集合或者数组的数据。
- 优势: Stream流大量的结合了Lambda的语法风格来编程,提供了一种更加强大,更加简单的方式操作集合或者数组中的数据,代码更简洁,可读性更好
-
Stream流的使用步骤
-
Stream流中的常用方法
1 | public static void main(String[] args) { |
中间方法:
Stream流常用中间方法 | 说明 |
---|---|
Stream |
接收一个Predicate接口参数,用于对数据进行过滤 |
Stream |
用于去重,通过流中元素的hashCode和equals方法判断是否为重复元素 |
Stream |
产生一个新流,其中按自然顺序排序 |
Stream |
产生一个新流,其中按比较器顺序排序 |
Stream |
接收一个Consumer接口参数,用于对数据进行操作 |
Stream |
截断流,使其元素不超过给定数量 |
Stream |
跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit(n)互补 |
1 | public static void main(String[] args) { |
终结方法
Stream流常用终结方法 | 说明 |
---|---|
void forEach(Consumer<? super T> action) | 遍历数据 |
long count() | 返回流中元素总数 |
Optional |
返回流中第一个元素 |
Optional |
返回流中任意元素 |
Optional |
返回流中最大值 |
Optional |
返回流中最小值 |
void close() | 关闭流,释放资源 |
IO流
用于读写数据,或网络中的数据。
- 字节输入流:以内存为基准,来自磁盘文件/网络中的数据以字节的形式读入到内存中去的流
- 字节输出流:以内存为基准,把内存中的数据以字节写出到磁盘文件或者网络中去的流
- 字符输入流:以内存为基准,来自磁盘文件/网络中的数据以字符的形式读入到内存中去的流
- 字符输出流:以内存为基准,把内存中的数据以字符写出到磁盘文件或者网络介质中去的流。
字节流
- FileInputStream(文件字节输入流)
构造器 | 说明 |
---|---|
FileInputStream(File file) | 创建一个文件字节输入流,用于读取文件 |
FileInputStream(String name) | 创建一个文件字节输入流,用于读取文件 |
方法 | 说明 |
---|---|
int read() | 读取一个字节,并返回该字节所对应的ASCII码值 |
int read(byte[] b) | 读取一个字节数组,并返回该字节数组的长度 |
int read(byte[] b, int off, int len) | 读取一个字节数组,并返回该字节数组的长度 |
- FileOutputStream(文件字节输出流)
构造器 | 说明 |
---|---|
FileOutputStream(File file) | 创建一个文件字节输出流,用于写入文件 |
FileOutputStream(String name) | 创建一个文件字节输出流,用于写入文件 |
FileOutputStream(File file, boolean append) | 创建一个文件字节输出流,用于写入文件,可追加数据 |
FileOutputStream(String name, boolean append) | 创建一个文件字节输出流,用于写入文件,可追加数据 |
方法 | 说明 |
---|---|
void write(int b) | 写入一个字节 |
void write(byte[] b) | 写入一个字节数组 |
void write(byte[] b, int off, int len) | 写入一个字节数组 |
void close() throws IOException | 关闭流,释放资源 |
文件复制案例
1 | /** |
字符流
适合读写文本文件内容
- 字符输入流:以内存为基准,来自磁盘文件/网络中的数据以字符的形式读入到内存中去的流
- 字符输出流:以内存为基准,把内存中的数据以字符写出到磁盘文件或者网络介质中去的流。
- FileReader(文件字符输入流)
构造器 | 说明 |
---|---|
FileReader(File file) | 创建一个文件字符输入流,用于读取文件 |
FileReader(String name) | 创建一个文件字符输入流,用于读取文件 |
方法 | 说明 |
---|---|
int read() | 读取一个字符,并返回该字符所对应的ASCII码值 |
int read(char[] cbuf) | 读取一个字符数组,并返回该字符数组的长度 |
int read(char[] cbuf, int off, int len) | 读取一个字符数组,并返回该字符数组的长度 |
- FileWriter(文件字符输出流)
字符输出流写出数据后,必须刷新流(调用flush()方法进行刷新该字符流通道还能继续使用)
或者关闭流(调用close()方法,默认先进行刷新操作),写出去的数据才能生效
方法 | 说明 |
---|---|
public static void flush() throws IOException | 刷新流,将内存中的数据写出到文件中 |
public void close() throws IOException | 关闭流,释放资源,包含刷新 |
构造器 | 说明 |
---|---|
FileWriter(File file) | 创建一个文件字符输出流,用于写入文件 |
FileWriter(String name) | 创建一个文件字符输出流,用于写入文件 |
FileWriter(File file, boolean append) | 创建一个文件字符输出流,用于写入文件,可追加数据 |
FileWriter(String name, boolean append) | 创建一个文件字符输出流,用于写入文件,可追加数据 |
方法 | 说明 |
---|---|
void write(int c) | 写入一个字符 |
void write(char[] cbuf) | 写入一个字符数组 |
void write(char[] cbuf, int off, int len) | 写入一个字符数组 |
void close() throws IOException | 关闭流,释放资源 |
字节流、字符流的使用场景小结
- 字节流适合做一切文件数据的拷贝(音视频,文本);
- 字节流不适合读取中文内容输出
- 字符流适合做文本文件的操作(读,写)。
缓冲流
- 缓冲流:在流的基础上,添加缓冲功能,提高流的读写效率。
- 原理:字节缓冲输入流自带了8KB缓冲池;字节缓冲输出流也自带了8KB缓冲池
- BufferedReader(字符缓冲输入流)
构造器 | 说明 |
---|---|
BufferedReader(Reader in) | 创建一个字符缓冲输入流,用于读取文件 |
方法 | 说明 |
---|---|
String readLine() | 读取一行字符串,并返回该字符串 |
void close() throws IOException | 关闭流,释放资源 |
- BufferedWriter(字符缓冲输出流)
构造器 | 说明 |
---|---|
BufferedWriter(Writer out) | 创建一个字符缓冲输出流,用于写入文件 |
方法 | 说明 |
---|---|
void write(String s) | 写入一个字符串 |
void newLine() | 写入一个换行符 |
void close() throws IOException | 关闭流,释放资源 |
- 低级字节流通过改变字节数组的大小可以提高性能
- 缓冲流通过改变字节数组大小同时改变缓冲池大小也可以提高性能
- 以一个字节一个字节的方式适合小文件,大文件被淘汰。性能差
转换流
- InputStreamReader(字符输入转换流)
- 解决不同编码时,字符流读取文本内容乱码的问题。
- 解决思路:先获取文件的原始字节流,再将其按真实的字符集编码转成字符输入流,这样字符输入流中的字符就不乱码了
构造器 | 说明 |
---|---|
InputStreamReader(InputStream in) | 创建一个字符输入转换流,用于读取文件 |
InputStreamReader(InputStream in, String charsetName) | 创建一个字符输入转换流,用于读取文件 |
方法 | 说明 |
---|---|
int read() | 读取一个字符,并返回该字符所对应的ASCII码值 |
int read(char[] cbuf) | 读取一个字符数组,并返回该字符数组的长度 |
int read(char[] cbuf, int off, int len) | 读取一个字符数组,并返回该字符数组的长度 |
void close() throws IOException | 关闭流,释放资源 |
- OutputStreamWriter(字符输出转换流)
- 作用:可以控制写出去的字符使用什么字符集编码。
- 解决思路:获取字节输出流,再按照指定的字符集编码将其转换成字符输出流,以后写出去的字符就会用该字符集编码了
构造器 | 说明 |
---|---|
OutputStreamWriter(OutputStream out) | 创建一个字符输出转换流,用于写入文件 |
OutputStreamWriter(OutputStream out, String charsetName) | 创建一个字符输出转换流,用于写入文件 |
方法 | 说明 |
---|---|
void write(int c) | 写入一个字符 |
void write(char[] cbuf) | 写入一个字符数组 |
void write(char[] cbuf, int off, int len) | 写入一个字符数组 |
void close() throws IOException | 关闭流,释放资源 |
打印流
- 作用:打印流可以实现更方便、更高效的打印数据出去,能实现打印啥出去就是啥出去
- PrintStream
构造器 | 说明 |
---|---|
PrintStream(String fileName) | 创建一个打印流,用于写入文件 |
PrintStream(File file) | 创建一个打印流,用于写入文件 |
PrintStream(OutputStream out,boolean autoFlush) | 实现自动刷新,创建一个打印流,用于写入文件 |
PrintStream(OutputStream out,boolean autoFlush,String encodingName) | 实现自动刷新,指定编码方式 |
PrintStream(String fileName, String charsetName) | 创建一个打印流,用于写入文件 |
方法 | 说明 |
---|---|
void print(xxx xx) | 打印任意类型的数据 |
void write(int/byte[]/byte[]一部分) | 可以支持写字节数据 |
- PrintWriter
构造器 | 说明 |
---|---|
public PrintWriter(OutputStream/Writer/File/String) | 打印流直接通向字节输出流/文件/文件路径 |
public PrintWriter(string fileName, Charset charset) | 可以指定写出去的字符编码 |
public PrintWriter(OutputStream out/Writer,boolean autoFlush) | 实现自动刷新 |
public PrintWriter(OutputStream out,boolean autoFlush, String encoding) | 实现自动刷新,指定编码方式 |
方法 | 说明 |
---|---|
void print(xxx xx) | 打印任意类型的数据 |
void write(int/char[]/string/…) | 可以支持写字符数据 |
数据流
- DataOutputStream(数据输出流)
- 作用:可以将基本数据类型的数据写出去,并且可以控制写出去的字符使用什么字符集编码。
构造器 | 说明 |
---|---|
DataOutputStream(OutputStream out) | 创建一个数据输出流,用于写入文件 |
方法 | 说明 |
---|---|
void writeInt(int v) | 写入一个int类型的数据 |
void writeByte(byte v) | 写入一个byte类型的数据 |
void writeDouble(double v) | 写入一个double类型的数据 |
void writeUTF(String str) | 写入一个字符串 |
void close() throws IOException | 关闭流,释放资源 |
- DataInputStream(数据输入流)
- 作用:可以将基本数据类型的数据读进来,并且可以控制读进来的字符使用什么字符集编码。
构造器 | 说明 |
---|---|
DataInputStream(InputStream in) | 创建一个数据输入流,用于读取文件 |
方法 | 说明 |
---|---|
int readInt() | 读取一个int类型的数据 |
byte readByte() | 读取一个byte类型的数据 |
double readDouble() | 读取一个double类型的数据 |
String readUTF() | 读取一个字符串 |
void close() throws IOException | 关闭流,释放资源 |
- 读取和写入顺序要一致,否则会报错,及输入输出要对应
序列化流
对象序列化:把Java对象写入到文件中去
对象反序列化:把文件的Java对象读出来
对象如果要实现序列化必须要实现序列化接口(Serializable)
- ObjectOutputStream(对象输出流)
- 作用:可以将Java对象序列化,并且把Java对象存在文件中。
构造器 | 说明 |
---|---|
ObjectOutputStream(OutputStream out) | 创建一个对象输出流,用于写入文件 |
方法 | 说明 |
---|---|
void writeObject(Object obj) | 写入一个Java对象 |
void close() throws IOException | 关闭流,释放资源 |
- ObjectInputStream(对象输入流)
- 作用:可以将Java对象反序列化,并且把存在文件中的Java对象读入内存中。
构造器 | 说明 |
---|---|
ObjectInputStream(InputStream in) | 创建一个对象输入流,用于读取文件 |
方法 | 说明 |
---|---|
Object readObject() | 读取一个Java对象 |
void close() throws IOException | 关闭流,释放资源 |
transient 代表该属性不参与序列化,即不被序列化
如果一次性序列化多个对象,可以用ArrayList集合存储对象,然后直接对集合进行序列化即可
ArrayList集合已经实现了序列化接口
IO框架
Commons-IO
- 作用:对IO流进行高级封装,简化IO流编程
- 下载地址:https://commons.apache.org/proper/commons-io/download_io.cgi
- 使用步骤:
- 导入commons-io-1.3.2.jar
- 使用IOUtils类中的方法
常用方法:
FileUtils类提供的部分方法 | 说明 |
---|---|
void copyFile(File srcFile, File destFile) | 复制文件 |
void copyDirectory(File srcDir, File destDir) | 复制目录 |
void deleteDirectory(File dir) | 删除目录 |
void deleteFile(File file) | 删除文件 |
boolean isDirectory(File file) | 判断是否是目录 |
boolean isFile(File file) | 判断是否是文件 |
boolean isHidden(File file) | 判断是否是隐藏文件 |
boolean exists(File file) | 判断文件是否存在 |
IOUtils类提供的部分方法 | 说明 |
---|---|
long copy(InputStream in, OutputStream out) | 复制流中的数据 |
String readFileToString(File file, Charset charset) | 读取文件中的数据 |
void writeStringToFile(File file, String data, Charset charset) | 写数据到文件中 |
void write(byte[] data, OutputStream out) | 写字节数组到输出流中 |
void copy(Reader in, Writer out) | 复制文件 |
特殊文件,日志
-
.properties属性文件
- 作用:用于存储配置信息
- 只能存储键值对信息
- 键不能重复
- 文件后缀一般是.properties
-
xml文件
- XML( 全称EXtensible Markup Language,可扩展标记语言 )本质是一种数据的格式,可以用来存储复杂的数据结构,和数据关系。
- XML的特点
- XML中的“<标签名>” 称为一个标签或一个元素,一般是成对出现的XML中的标签名可以自己定义 (可扩展),但必须要正确的嵌套
- XML中只能有一个根标签。
- XML中的标签可以有属性。
- 如果一个文件中放置的是XML格式的数据,这个文件就是XML文件,后缀一般为.xml
XML文件语法规则:
- < : 代表小于号
- > : 代表大于号
- & : 代表和号
- " : 代表引号
- ' : 代表单引号
- : 代表空格
- 日志技术
可以将系统执行的信息,方便的记录到指定的位置(控制台、文件中、数据库中)可以随时以开关的形式控制日志的启停,无需侵入到源代码中去进行修改普遍使用slf4j日志框架,进行日志的记录
属性 | 说明 |
---|---|
trace | 最详细的日志信息,一般不会使用 |
debug | 调试信息 |
info | 运行信息 |
warn | 警告信息 |
error | 错误信息 |
fatal | 致命错误信息 |
线程
多线程
多线程是指从软硬件上实现的多条执行流程的技术(多条线程由CPU负责调度执行)
- 多线程的创建方式一:继承Thread类
- 定义一个子类MyThread继承线程类java.lang.Thread,重写run()方法
- 创建MyThread类的对象
- 调用线程对象的start()方法启动线程(启动后还是执行run方法的)
多线程的注意事项
1、启动线程必须是调用start方法,不是调用run方法
- 直接调用run方法会当成普通方法执行,此时相当于还是单线程执行
- 只有调用start方法才是启动一个新的线程执行
2、不要把主线程任务放在启动子线程之前
- 这样主线程一直是先跑完的,相当于是一个单线程的效果了
- 多线程的创建方式二:实现Runnable接口
- 定义一个线程任务类MyRunnable实现Runnable接口,重写run方法
- 创建MyRunnable任务对象
- 把MyRunnable任务对象交给Thread处理
- 调用线程对象的start()方法启动线程
方式二的优缺点
- 优点:任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强
- 缺点:需要多一个Runnable对象
- 多线程的第三种创建方式:利用Callable接口、FutureTask类来实现
- 创建任务对象
- 定义一个类实现Callable接口,重写call方法,封装要做的事情,和要返回的数据把Callable类型的对象封装成FutureTask (线程任务对象)
- 把线程任务对象交给Thread对象
- 调用Thread对象的start方法启动线程
- 线程执行完毕后、通过FutureTask对象的的get方法去获取线程任务执行的结果
注意:如果线程没有执行完毕,或有多个线程则(FutureTask对象的的get方法)会等待线程执行完毕,才会通过此方法进行输出结果
构造器 | 说明 |
---|---|
public FutureTask(Callable |
创建一个Callable类型的任务对象 |
方法 | 说明 |
---|---|
V get() | 获取线程执行完毕后的结果 |
- 线程创建方式三的优缺点
优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强,可以在 线程执行完毕后 去获取线程执行的结果.
缺点:编码复杂一点。
Thread提供的构造器 | 说明 |
---|---|
public Thread() | 创建一个新线程 |
public Thread(Runnable target) | 创建一个新线程 |
public Thread(Runnable target, String name) | 创建一个新线程并指定线程名 |
Thread提供方法 | 说明 |
---|---|
public void start() | 启动线程,线程进入就绪状态,等待CPU调度 |
public void run() | 线程执行体,线程启动后会自动调用run方法 |
public String getName() | 获取线程的名称 |
public void setName(String name) | 设置线程的名称 |
public static Thread currentThread() | 获取当前线程对象 |
public static void sleep(long time) | 让当前线程休眠指定的毫秒数 |
public void join() | 等待线程执行完毕 |
线程安全
多个线程,同时操作同一个共享资源的时候,可能会出现业务安全问题
出现原因:
- 存在多个线程在同时执行
- 同时访问一个共享资源
- 存在修改该共享资源
线程同步
目的: 解决线程安全问题的方案
思想:让多个线程实现先后依次访问共享资源,这样就解决了安全问题
解决方案:
加锁: 每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能再加锁进来
方案一:同步代码块
作用: 把访问共享资源的核心代码给上锁,以此保证线程安全
原理: 每次只允许一个线程加锁后进入,:执行完毕后自动解锁,其他线程才可以进来执行
同步锁的注意事项:
对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug。锁对象随便选择一个唯一的对象好不好呢?
不好,会影响其他无关线程的执行锁对象的使用规范
建议使用共享资源作为锁对象,对于实例方法建议使用this作为锁对象
对于静态方法建议使用字节码 (类名.class)对象作为锁对象
1 | /** |
方案二:同步方法
- 作用:把访问共享资源的核心方法给上锁,以此保证线程安全。
- 原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行
- 内部隐含一种锁,以(this)作为锁对象,如果方法是静态方法:同步方法默认用类名.class作为的锁对象。
1 | 修饰符 synchronized 返回值类型 方法名称(形参列表){ |
方案三:Lock锁
- Lock锁是DK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大
- Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象。
- 防止实例方法出现异常,并跳出方法体,不执行解锁操作,而造成资源的占用,其他线程无法访问,通常使用try-finally把方法体放在try{}块中,解锁操作放在finally{}块中,即使出现异常,也会执行解锁操作。
- 每个类的实例都会有一个锁对象,用来锁住自己,锁住共享资源,锁对象是唯一的,通常用final 来修饰锁对象,保证唯一性,不能被二次赋值。
构造器 | 说明 |
---|---|
public ReentrantLock() | 创建一个ReentrantLock对象 |
方法 | 说明 |
---|---|
public void lock() | 获取锁 |
public void unlock() | 释放锁 |
1 | public class Account { |
线程通信
-
什么是线程通信?
当多个线程共同操作共享的资源时,线程间通过某种方式互相告知自己的状态,以相互协调,并避免无效的资源争夺 -
线程通信的常见模型(生产者与消费者模型)
- 生产者线程负责生产数据
- 消费者线程负责消费生产者生产的数据
- 注意: 生产者生产完数据应该等待自己,通知消费者消费;消费者消费完数据也应该等待自己,再通知生产者生产!
-
线程通信的常用方法,Object类的等待唤醒和等待方法
方法 | 说明 |
---|---|
void wait() | 等待,线程进入等待状态,直到被唤醒 |
void notify() | 唤醒一个等待的线程 |
void notifyAll() | 唤醒所有等待的线程 |
- 线程通信的注意事项
- 等待和唤醒方法必须由同一个锁对象调用,否则会报错
- 等待和唤醒方法必须由同步代码块或者同步方法调用,否则会报错
线程池
-
什么是线程池?
- 线程池是管理线程的一个容器,可以管理多个线程,提高线程的复用率,减少线程的创建和销毁带来的开销,提高程序的性能
- 线程池是JDK1.5开始提供的一个新的线程管理工具,可以方便的创建线程池,并执行线程任务
-
线程池的创建方式
- 通过Executors工具类创建线程池
作用:复用线程
- 谁代表线程池?
JDK 5.0起提供了代表线程池的接口:ExecutorService
- 如何得到线程池对象?
方式一:使用ExecutorService
的实现类ThreadPoolExecutor
自创建一个线程池对象。
方式二:使用Executors
(线程池的工具类)调用方法返回不同特点的线程池对象
参数一: corePoolsize: 指定线程池的核心线程的数量
参数二: maximumPoolSize: 指定线程池的最大线程数量
参数三: keepAliveTime: 指定临时线程的存活时间
参数四: unit:指定临时线程存活的时间单位(秒、分、时、天)
参数五: workQueue: 指定线程池的任务队列。
参数六: threadFactory: 指定线程池的线程工厂
参数七: handler:指定线程池的任务拒绝策略(线程都在忙,任务队列也满了的时候,新任务来了该怎么处理
线程池的注意事项:
1、临时线程什么时候创建?
新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程
2、什么时候会开始拒绝新任务?
核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务
ExecutorService常用方法:
方法 | 说明 |
---|---|
void execute(Runnable command) | 执行任务 |
Future<?> submit(Runnable task) | 执行任务,并返回一个Future对象 |
void shutdown() | 等待线程池中的任务执行完毕,再关闭线程池 |
void shutdownNow() | 立即关闭线程池,不执行任务 |
1 | // 1.创建线程池 |
新任务拒绝策略:
策略 | 说明 |
---|---|
ThreadPoolExecutor.AbortPolicy() | 丢弃任务并抛出RejectedExecutionException异常 |
ThreadPoolExecutor.CallerRunsPolicy() | 由调用线程处理该任务 |
ThreadPoolExecutor.DiscardOldestPolicy() | 丢弃队列最前面的任务,然后重新尝试执行任务 |
ThreadPoolExecutor.DiscardPolicy() | 直接丢弃任务,不予任何处理也不抛出异常 |
并发,并行
- 并发:(一个核心不断切换不同线程执行)
进程中的线程是由CPU负责调度执行的,但CPU能同时处理线程的数量有限,为了保证全部线程都能往前执行,CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发。 - 并行:(多个核心同时执行多个线程)在同一个时刻上,同时有多个线程在被CPU调度执行
- 多线程:并发,并行同时进行。
- 关于线程
- 正在运行的程序(软件)就是一个独立的进程。
- 线程是属于进程的,一个进程中可以同时运行很多个线程。
- 进程中的多个线程其实是并发和并行执行的。
线程的生命周期
线程的六种状态:
线程状态 | 说明 |
---|---|
new(新建) | 线程刚被创建,但是并未启动。 |
RUNNABLE(可运行) | 线程已经调用了start(),等待CPU调度 |
BLOCKED(锁阻塞) | 线程在执行的时候未竞争到锁对象,则该线程进入Blocked状态 |
WAITING(无限等待) | 一个线程进入Waiting状态,另一个线程调用notify或者notifyAll方法才能够唤醒 |
TIME_WAITING(计时等待) | 同waiting状态,有几个方法(sleep,wait)有超时参数,调用他们将进入Timed Waiting状态 |
TERMINATED(被终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡 |
网络通信
网络编程三要素
网络通信的关键三要素
IP: 设备在网络中的地址,是唯一的标识。
端口: 应用程序在设备中唯一的标识
协议: 连接和数据在网络中传输的规则。
IP地址:
- IP (Internet Protocol): 全称”互联网协议地址”,是分配给上网设备的唯一标志。
- IP地址有两种形式:IPv4、IPv6
- IPv4:(32bit 4字节)点分十进制表示法
- IPv6:(128bit 16字节)分成8段表示,每段每四位编码成一个十六进制位表示,数之间用冒号 (:)分开。
公网IP,内网IP
- 公网IP:是可以连接互联网的IP地址;内网IP:也叫局域网P,只能组织机构内部使用
- 192.168开头的就是常见的局域网地址,范用即为192.168.0.0–192.168.255.255,专门为组织机构内部使用
特殊IP地址
- 127.0.0.1、localhost: 代表本机IP,只会寻找当前所在的主机。
IP常用命令:
- ipconfig: 查看本机IP地址
- pingIP地址:检查网络是否连通
InterAddress
:代表IP地址
方法 | 说明 |
---|---|
InetAddress(String host) | 根据主机名创建InetAddress对象 |
InetAddress(String host, int port) | 根据主机名和端口号创建InetAddress对象 |
InetAddress getLocalHost() | 获取本机InetAddress对象 |
InetAddress getByName(String host) | 根据主机名获取InetAddress对象 |
String getHostAddress() | 获取IP地址 |
String getHostName() | 获取主机名 |
1 | public static void main(String[] args) throws Exception { |
端口
- 标记正在计算机设备上运行的应用程序的,被规定为一个 16 位的二进制,范围是0~65535。
分类
周知端口:0~1023,被预先定义的知名应用占用(如: HTTP占用80,FTP占用21)
注册端口:1024~49151,分配给用户进程或某些应用程序
动态端口:49152到65535,之所以称为动态端口,是因为它一般不固定分配某种进程,而是动态分配
注意:我们自己开发的程序一般选择使用注册端口,且一个设备中不能出现两个程序的端口号一样,否则出错
传输协议
OSI网络参考模型:全球网络互联标准
TCP/IP网络模型:事实上的国际标准。
一、UDP协议
特点: 无连接、不可靠通信、传输效率高
不事先建立连接,数据按照包发,一包数据包含:自己的IP、程序端口,目的地IP、程序端口和数据(限制在64KB内)等发送方不管对方是否在线,数据在中间丢失也不管,如果接收方收到数据也不返回确认,故是不可靠的。
业务场景:语音通话,视频直播。
二、TCP协议
可靠通信特点: 面向连接、可靠通信、传输效率相对不高
TCP的最终目的:要保证在不可靠的信道上实现可靠的传输
TCP主要有三个步骤实现可靠传输:
三次握手建立连接
传输数据进行确认
四次挥手断开连接
Java高级
单元测试
就是针对最小的功能单元(方法),编写测试代码对其进行正确性测试
反射
反射就是:加载类,并允许以编程的方式解剖类中的各种成分(成员变量、方法、构造器等)
1、反射第一步:加载类,获取类的字节码: Class对象
2、获取类的构造器: Constructor对象
3、获取类的成员变量: Field对象
5、获取类的成员方法: Method对象
获取Class对象的三种方式
1、Class c1=类名.class
2、调用Class提供方法: public static Class forName(String package);
3、Object提供的方法: public Class getClass(); Class c3 = 对象.getClass();
- 获取构造器
方法 | 说明 |
---|---|
Constructor getConstructor(Class<?>… parameterTypes) | 根据参数类型获取构造器 |
Constructor[] getConstructors() | 获取所有公共构造器 |
Constructor getDeclaredConstructor(Class<?>… parameterTypes) | 根据参数类型获取构造器 |
Constructor[] getDeclaredConstructors() | 获取所有构造器 |
- 获取成员变量
方法 | 说明 |
---|---|
Field getField(String name) | 根据成员变量名获取公共成员变量 |
Field[] getFields() | 获取所有公共成员变量 |
Field getDeclaredField(String name) | 根据成员变量名获取成员变量 |
Field[] getDeclaredFields() | 获取所有成员变量 |
- 获取成员方法
方法 | 说明 |
---|---|
Method getMethod(String name, Class<?>… parameterTypes) | 根据方法名和参数类型获取公共方法 |
Method[] getMethods() | 获取所有公共方法 |
Method getDeclaredMethod(String name, Class<?>… parameterTypes) | 根据方法名和参数类型获取方法 |
Method[] getDeclaredMethods() | 获取所有方法 |
- 作用,应用场景
存任意对象的字段和其数据到文件中去
1 | public class Student { |
1 | public class Test1 { |
1 | public class ObjectFrame { |
注解
Java代码里的特殊标记,比如: @Override、@Test等
作用:让其他程序根据注解信息来决定怎么执行该程序
注意:注解可以用在类上、构造器上、方法上、成员变量上、参数上、等位置处。
- 自定义注解
就是自己定义注解。
1 | public 注解名称{ |
特殊属性名: value
如果注解中只有一个value属性,使用注解时,value名称可以不写!!
-
元注解
元注解是用于修饰注解的注解,比如: @Target、@Retention
@Target:指定注解可以修饰哪些位置
@Retention:指定注解的生命周期
-
注解解析
就是判断类上、方法上、成员变量上是否存在注解,并把注解里的内容给解析出来。
如何解析注解?
- 要解析谁上面的注解,就应该先拿到谁
- 比如要解析类上面的注解,则应该先获取该类的CLass对象,再通过Clas5对象解析其上面的注解
- 比如要解析成员方法上的注解,则应该获取到该成员方法的Method对象,再通过Method对象解析其上面的注解。
- Class、Method、Field,nstructor、都实现了AnnotatedElement接口,它们都拥有解析注解的能力
1 | public class Test1 { |