Java面向对象高级
static(静态)修饰符(用处:架构师 框架 面试笔试 看原码)
类变量:被static修饰, 属于类 ,与类一起只加载一次,在内存中只有一份,被类的所有对象共享(推荐:通过类名调用)例如:判断一个类被实例化多少个对象可用类变量
类名调用可不用实例化对象即可使用,可以节约内存
实例变量:没被static修饰, 属于每个对象的
类方法中不能直接用实例变量及实例方法(可通过实例化调用)(原因:类变量与类方法在类加载时创建并赋值,即方法区中创建,而实例方法与实例变量在实例化对象后才被创建,即堆内存( 中创建,类变量在前,实例变量后)
类变量的赋值在堆内存中赋值,堆内存中有一块静态区,专门放静态变量的值
类方法的用处(工具类):
一般用于工具类(Utils),可使代码的复用性,不需要创建对象可节省空间,调用方便提高了开发效率
例如验证码小模块,可设置成工具类,之后在登录和注册区中使用验证码便可直接用 类名.方法名() 使代码使用更加方便而且可节约内存
工具类中的成员方法均为类方法,而类方法不需要通过实例化调用,为让其他人不创建工具类的对象,从而节省空间,一般将工具类的构造器私有化
类方法中不能使用this:
原因:this用于调用当前对象
方法中的隐式参数this:
下方参数为默认隐藏,所以方法中可使用this,代表调用当前方法的对象
public void aaa(当前类类名 this){ 方法体 }
代码块:
**静态代码块:static { } **----------在类中,方法外使用
随着类的加载而执行,并且无论创建多少个实例都只执行一次,通过类名调用类方法或类变量也是如此
**实例代码块:{ } **----------在类中,方法外使用
在每次创建对象时执行,且在构造器前执行
局部代码块:方法体内的 { } ------------方法体中使用
可用于提前结束方法内的部分代码,从而释放内存
设计模式(了解):
一个问题多种解法的最优解的总结,称为设计模式
**1.单例设计模式:**确保一个类在程序当中只有一个对象(如:任务管理器中,只会出现唯一对象):
构造器私有化 -----------所有种类方法都需要
私有实例化当前对象,并用静态方法返回当前实例化对象(方法一:饿汉式 在类创建时(加载.class字节码时)即创建对象)
(懒汉式 初次调用方法获取对象时创建对象)
package com.company.note.day1;
public class Student_SingleCase {
private static Student_SingleCase stud = new Student_SingleCase();
private Student_SingleCase(){}
public static Student_SingleCase getStudent(){
return stud;
}
}
package com.company.note.day1;
public class MyDay1Home {
public static void main(String[] args) {
System.out.println(Student_SingleCase.getStudent());
System.out.println(Student_SingleCase.getStudent());
System.out.println(Student_SingleCase.getStudent());
System.out.println(Student_SingleCase.getStudent()); //均为同样一个地址
}
}
继承:子类能继承父类的非私有成员(成员变量 成员方法) 单继承,多层继承
(子类共有成员在父类定义,在子类的构造方法中对父类的该变量赋值)
1.子类对象是与父类共同创建的,即子类对象中存在父类的非私有成员(相当于将父类中的非私有成员都搬到了子类中,所以子类中不能出现与父类 实例方法名 相同的 静态方法 )
(疑问:子类是通过地址值访问父类对象中的成员 还是在子类对象创建时将父类非私有成员重写到自身空间中 还是子类对象创建的同时创建一个父类对象)
答:子类通过地址值访问父类对象中的成员,当一个子类对象创建时,它会包含一个父类对象,但这个父类对象会存在于子类对象的内存空间之外,并通过子类对象中的父类地址值访问。
2.super -----------调用父类成员,但父类私有无法调用 ----------** super()** 调用父类构造方法(只能放于子类构造器首行,否则报错)---------运用场景:一般用于初始化父类成员变量
子类 _所有的构造器 _默认会 调用父类的空参构造器(在构造器最前面隐含调用super( ) 方法)
this的使用类似于super,this() 为调用本类构造
方法重写(覆写)
子类中与父类方法名,参数列表,**返回值类型(返回值类型可以更小)**相同时为重写(注意:调用时符合就近原则调用,重写只有实例方法,静态方法不会被重写,因为静态属于类)重写的修饰符访问权限需大于等于父类的该方法
(方法重载:一个类中存在多个同名但形参列表不同的方法,只与方法名和参数列表有关)
可用 @Override 来验证是否是重写方法(放于方法前若不编译报错则为重写正确)
Object类中有个toString() 方法,在控制台中打印的便是toString() 方法中的返回值
4.所有类都直接继承或间接继承 Object 根类
修饰符访问权限
当前类 同一个包 不同包子类 不同包非子类
public(公共) v v v v
protected(保护) v v v /
default(默认,接口中) v v / /
private(私有) v / / /
匿名对象
new Cat ( ) .call ( ) --------------匿名对象的使用
当对象仅使用一次时,可将其定为匿名对象
多态(继承/实现情况下,有对象多态、行为多态)
对象多态,子类对象赋给父类变量:
People p1= new Student( );
行为多态,run( )方法的行为与父类不一样:
p1.run( );
方法重载属于编译时多态
多态情况下成员的访问特点:
成员中只有 实例方法(即:非静态方法)编译找父类,运行找子类,其他的成员都先找父类(即先看父类中是否有该方法,无则编译报错,有则在运行时访问子类的该方法)--------向上转型时出现
多态的好处:
多态形式下,右边对象的解耦合的,更便于扩展和维护(解耦合:系统中多个不同部分之间的依赖关系降至最低甚至消除 如:子类对象可用父类类型变量接收 即降低了耦合度)
在使用参数时可将不同子类对象传入,而不需要更改参数类型,从而 使用子类的实例方法,不仅便利而且还简单易懂,同时还更好的更换不同子类的对应方法
多态的弊端:
多态下不能使用子类的独有方法,或成员变量
解决方法:将子类转父类多态下的对象 向下转型 回 对应子类对象,即可使用子类的 独有方法 或 成员变量
_可先用 instanceof 来判断两边对象是否类型一致 避免报错(boolean= people instanceof student ;)
_
当子类转为父父类后,再向下转型为父类对象…? 可行
final(常量:被final修饰的变量):
- **final修饰类(最终类):**无法被继承(无子类)
- **final修饰方法:**无法被子类重写
- **final修饰变量(常量):**一般配合static使用(可直接用类名.使用,方便使用,命名规则一般为全大写,单词之间用下划线连接)
在编译时常量会被“宏替换”,即 会直接将该变量换成字面量,从而节省内存
常量案例如:
抽象类 (abstract修饰)-----------不能创建对象,只能用来做父类
- **abstract修饰类:**抽象类中可以有抽象方法(子类必须重写的方法)
- **abstract修饰方法:**抽象方法(没有方法体) 用法:系统中所有子类共同需要的方法 可定义为抽象方法
- 其中的属性和普通类一样
模板方法设计模式(面试笔试 看原码)
解决方法中存在其他代码一致但部分代码不一致:在父类中实现该方法(建议_该方法用final修饰_),同时在父类中创建一个抽象方法,将该抽象方法放于实现类方法对应位置,在子类重写该抽象方法时可写自己需要的内容
如:Fu{
final conf(){ 重复内容 special() 重复内容 } -------------共有方法
abstract special( );
}
Zi{
@Override
special(){ 特殊内容 } ---------------------------重写特殊内容
}
Main{ new Zi(). conf( ); } ----------------------- 创建子类对象调用共有方法
(执行原理:子类调用 继承的父类 conf( ) 方法,当运行到 special() 方法时运行子类 special() 方法 )
- 当父类与子类都为抽象类时,抽象方法会让“孙类”去实现
### 接口(interface) -----------与抽象类类似,不能创建对象,实现接口的类称为实现类,接口是可以多继承的(一个接口可以继承多个接口)
- 成员变量默认有 public static final ,静态常量
- 成员方法默认有 public abstract ,抽象方法
接口的好处
弥补了单继承的不足,一个类可以同时实现多个接口
让程序可以面向接口编程,可以灵活方便的切换各种实现(接口中写所有实现类共有的需求,而实现类中去实现该方法的特有方案)
例:接口中写打印出全班平均成绩,定义两个实现类,一个实现打印全班平均成绩,一个实现打印全班成绩但去除最高分与最低分,在运用中只需利用多态即可实现轻松换方案
在JDK11中接口的功能很像抽象类-------其可以用普通方法(被default修饰)和被其他修饰的方法
同一类事物共同拥有的属性和方法通常将其放于抽象类中,不同类事物的共同功能可用接口(如:宝马摩托、其他摩托 和 宝马汽车、其他汽车 但宝马都有GPS定位系统 则定义一个汽车类 和 一个摩托车类,宝马品牌用接口)
接口的注意事项:
父类权级比接口大,当几个接口中都含有一个同名的方法,实现类同时实现这几个接口则会报错,因为实现类不知道找哪个接口的方法,但有父类则调用父类方法
内部类
成员内部类: ------------------了解
1. 调用:外部类名.内部类名 (先创建外部类对象 再通过外部类对象创建内部类对象) new 外部类().new 内部类();
1. 外部类不能**直接**访问内部类
2. 内部类可直接访问外部类的所有非私有成员(当内部类成员名与外部类成员名一致时可用 外部类名.this.外部类成员 调用外部类成员)
静态内部类(JDK16前不可用静态内部类): ------------------了解
3. 调用:由于内部类为静态成员,所以可以通过 外部类名. new 内部类() 的方式调用内部类,但这种调用,外部类没有被实例
局部内部类 :------------------了解
4. 方法体重创建的类,无法在方法外使用
匿名内部类:---------------------掌握
5. **定义:**匿名内部类是 **局部内部类 的一种简化形式,本质上是一个 子类/实现类 对象 **(如监听器中会传一个匿名内部类实参) 便于创建子类对象
6. **匿名内部类的适用场景:**当一个子类只需使用一次时,通常作为一个实参通过多态传递给方法,匿名内部类的父类通常为抽象类,或是接口的实现类,用来重写抽象方法(当看到一个方法的形参参数类型为接口或抽象类则一般实例一个匿名内部类)
7. **实现方法:new 父类名/接口名( ){ 方法体 } 或 ()->{ 方法体 }**
new Thread(new Runnable(){
@Override
public void run(){
System.out.println("一条线程启动!");
}
}).start();
类名.this ---------------调用外部类的实例
接口名.super.方法名() -------------在接口中调用父接口的方法,或在实现类中调用接口方法
public class MyMapImp implements MyHashMap{
@Override
public Object show(Object o) {
return MyHashMap.super.show(o);
}
}
public interface MyHashMap<E, V> {
default E show(E e){
System.out.println("e = " + e);
return e;
}
}
枚举
定义枚举:
修饰符 enum 枚举类名{
枚举值1, 枚举值2,…; // 和常量命名一样
其他成员
}
枚举使用:枚举名.枚举值
枚举也是一种引用数据类型
枚举类型的变量,只能赋值该枚举类型中的枚举值
枚举值不能为带有其他数据类型的值(如:基本数据类型,字符串 等)
枚举一般用来描述只有固定个数值且有分类的类型
如:性别,季节,方向,…
public enum Sex{
BOY, GIRL
}
public static void main(String[] args){
Sex sex= Sex.BOY;
System.out,println("sex = " + sex); //输出 sex = BOY
}
用枚举与用常量的区别:
- 枚举值通常用于表示一组相关的数值常量,而常量则是一个具体的数值常量,不易理解其含义
- **枚举值通常用于定义一组有限的选项或状态,**常量则可以用于任何需要用到常量的地方
- 枚举值不能为除枚举类型外的数据类型,常量可为任意数据类型(好处如:当用含switch的方法时,若通过传参判断switch,当方法形参为字符串时,其参数可为随意输入的字符串,但用枚举就一定是为枚举)
- 枚举值通常需要占用更多的内存空间,因为每个成员都需要分配一个独立的数值。常量则可以共享同一块内存空间。
枚举本质为一个最终类(被final修饰),不能被继承,枚举值实际上是一个静态常量对象(被static final修饰 默认)
枚举中的构造器必须 私有(默认私有),否则报错
枚举中的其他成员,用 枚举类型枚举值.成员 调用 如下为解译后的枚举类
一般用于switch(在switch中括号中放枚举类型,case后可直接写枚举值)
枚举实现单例最为简单,让其中只有一个枚举值即为一个单例(理解)
泛型:------ArrayList 中的
泛型中的含义:
8. <T>只是一个占位符,其本身不能作为返回值类型,可以想象成是类、接口或方法架构中的一个泛型变量声明,表示当前架构主体中可使用该泛型变量申明(即:方法、类、接口框架中 <T> ==申明==> T,而在方法体或成员中 T ==声明==> t)---------具体在错题总结中
- 表示一种未知的数据类型,使用的时候确定其数据类型< A-Z,A-Z,… > 一般为大写字母,可定义多个泛型(如:map<K, V>
作用:
1. 在编译阶段可以避免出现一些非法数据,可以避免强制类型转换,及其可能出现的异常(如:没有定义泛型集合可以存入任意数据类型,加大使用难度 需要先判断每个元素类型是否正确再去使用)
2. 用于类、接口、方法中 (可用泛型类型代替成员数据类型 用泛型占位)
泛型擦除:泛型只存在于编译阶段,一旦程序编译成字节码文件时便更改为相应数据类型
- 注意:泛型传入只能为引用数据类型
方法中泛型的使用
泛型类
//泛型类 及使用
public class MyArrayList<E>{
E num;
}
public class MyHashMap<K, V>{
K id;
V name;
}
public static main(String[] args){
MyArrayList<String> list = new MyArrayList<>();
MyHashMap<String, String> map= new MyHashMap<>();
}
泛型接口
实现类中定义泛型
//泛型接口 及使用(其一)
//接口
public interface MyHashMap<E>{
void add(E e);
default E show(E e){
System.out.println("e = " + e);
}
}
//实现接口
public class MyMapImp implements MyHashMap<String>{
@Override
public void add(String o) {
}
@Override
public String show(String o) {
return MyHashMap.super.show(o);
}
}
//调用
public static void main(String[] args) {
MyMapImp myMapImp= new MyMapImp();
System.out.println("myMapImp.show(\"a\") = " + myMapImp.show("a"));
}
使用时定义泛型
//实现接口 (其二)
public class MyMapImp<E> implements MyHashMap<E>{
@Override
public void add(E e) {
}
@Override
public String show(E e) {
return MyHashMap.super.show(e);
}
}
//调用
public static void main(String[] args) {
MyMapImp<String> myMapImp= new MyMapImp<>();
System.out.println("myMapImp.show(\"a\") = " + myMapImp.show("a"));
}
泛型方法
有返回值
public <T> T show(T t){
return (T)("sss"+t);
}
无返回值
public <T> void show(T t){
System.out.println(t);
}
public void show(T t){
System.out.println(t);
}
泛型通配符:“?”可以在“使用泛型”的时候代表一切类型(泛型没有多态 即:定义Object类型,不能传一个String类型)
//参数类型为ArrayList<?> 则该list只能遍历,不能添加等操作
public static <T> void test(ArrayList<?> list){
//list.add(12345); 报错
System.out.println(list);
}
//若没有<?> 则可以任何操作 此时集合元素类型默认为Object类型此时也可接收list1、list2
//但此时不是多态
public static <T> void test2(ArrayList list){
//list.add(12345); 不报错
System.out.println(list);
}
public static void main(String[] args){
ArrayList<String> list1= new ArrayList<>();
ArrayList<Integer> list2= new ArrayList<>();
list1.add("aaa");
list2.add(123);
test(list1);
test(list2);
}
泛型受限: 为解决泛型没有多态的问题
1. 泛型上限:<? extends 数据类型>,如:? extends Car 其中 ? 能接收的必须是Car或其子类(谁是XXX子类?是就可以进,XXX是最大的 而子类可以有无数个 所以是上限)
2. 泛型下限:<? super 数据类型>,如:? super Car 其中 ?能接收的必须是Car或其父类
如:
public static <T> void test(ArrayList<? extends Car> list){
}
public static void main(String[] args){
ArrayList<Car> list1= new ArrayList<>();
test(list1); //只能传<>中为Car或其子类 的对象
}
异常(出现异常包装成异常对象)----------编译异常,运行异常
异常出现的运行流程(程序报错 与 自定义 均是如此)
3. 遇到异常,创建异常对象来封装异常信息
4. 若不在主方法中的异常,则将该异常对象向上抛出,一值抛到主方法中(若该异常没有被处理情况下)
5. 若主方法中还没处理该异常,则主方法将该异常抛向主方法调用者(JVM)
6. JVM接收到了该异常,则JVM将终止程序运行,打印异常信息到控制台
异常的体系
编译异常:如 IO流中语法正确,但报错,需要手动处理异常,会出现报红
运行异常:不会报错,但运行后会出现错误,如 除数为0, 不会报红
try{
int i= 2/0;
}catch(Exception e){
//return 是先返回元素再结束方法
return 10;
}finally{
//其中代码无论怎样都会执行,除非终止程序 一般用于结束
return 20;
}
自定义异常:
编译异常:继承Exception类,重写父类构造方法,在自定义异常判断处 throw 实例自定义异常类 ( “异常提示” );
运行异常:继承RuntimeException类,重写父类构造方法(调用父类构造方法传参)
public class RunException extends RuntimeException {
public RunException(String message) {
super(message);
}
}
public class Main {
public static void main(String[] args) {
int num = -1;
if (num <= 0) {
throw new RunException("Number should be greater than 0");
}
}
}
API(克隆与StringBuffer):
API:应用程序编程接口,java已经帮我们写好的类、接口和方法等
克隆:-------------- .clone( ) 浅克隆
- 浅克隆:拷贝出新的对象,与原对象的数据一样(引用数据类型拷贝地址值)
- 深克隆:其他和浅克隆一样,就是对象中的其他对象不再是拷贝地址值,而是再创建一个该其他对象,将对象中的实际内容拷贝过去(如 数组会创建一个新数组,该数组里的元素内容一致)(处理方法将该其他对象再浅克隆一次,然后将该其他对象替换)
Objects工具类(末尾加s的类一般为工具类)
可变字符串处理:
StringBuilder表示可变的字符串,String为不可变的,用StringBuilder操作字符串 节约时间与内存,但StringBuilder内的方法比String中的方法少很多
StringBuilder sb= new StringBuilder(字符串对象);
sb.append(添加的元素);
StringBuffer与StringBuilder一模一样,方法和用处都一样,不同之处就是在线程中,StringBuffer更为安全,StringBuilder不安全,但安全便要使速度更慢
StringJoiner与StringBuilder一样 是处理字符串的,更好格式拼接
StringJoiner sj= new StringJoiner(“间隔符”,“开始符”, “结束符” );
sj.add(“添加的字符串”); -------------只能为字符串类型
API(时间日期等):
Math: --------工具类 与数学运算相关类 三角函数,圆锥曲线 等
- abs() 取绝对值
- ceil() 向上取整 3.2 ==> 4.0
- floor() 向下取整
- round() 四舍五入 返回long类型
- max()和 min()
- pow() 取次方值
System: --------工具类 与系统有关 终止当前虚拟机(0为正常运行) 等
- exit() 终止当前虚拟机
- currentTimeMillis() 1970年1月1日 00:00:00(北京时间8:00) 到现在的总毫秒数
- Runtime: ------单例类通过类名点getRuntime获取其对象 获取当前运行对象
BigDecimal小数失真: ---------处理小数运算失真问题,计算机中的浮点数都是一个无限趋近的数
- 构造方法中传入要处理的double,用构造器传建议用字符串传入
- valueOf(double): 与构造器传入字符串一样
- add(): 加
- subtract(): 减
- multiple(): 乘
- divide(): 除 若为无限小数则会报错 此时需要精确
divide(处理值,保留精度,获取精度方式_放枚举值)
获取日期、时间: ----------均为工具类
JDK8之前传统的:
-
Date(util包下的): 构造器Date(long time)将time传入,不传则为当前毫秒值
1. getTime(): 返回从1970年1月1日00:00:00 经过 传入毫秒数 后的时间对象
2. setTime(long time): 设置毫秒值相当于构造器传参 -
SimpleDateFormat: 格式化Date
1. Date=>String(格式化fom…) String=>Date(解析)
2. new SimpleDateFormat( “yyyy年MM月dd日 HH时mm分ss秒” );
| yyyy | 年 |
| — | — |
| MM | 月 |
| dd | 日 |
| HH | 时 |
| mm | 分 |
| ss | 秒 |
| EEE | 星期几 |
| a | 上午/下午 | -
**Calendar: ** -------用于处理对象中的时间日期等
1. 将date转化成日期,Calendar.getInstance().setTime(date);
弊端:可变对象,会随着修改而修改
JDK8开始新增的:(有Date的含 年 月 日 星期 有Time的含 时-纳秒) 方法会返回一个新对象
- LocalDate、LocalTime(最低为纳秒)、LocalDateTime
1. now() 表示本地日期
2. **of() ** 设置当前时间对象的内容
3. 以下方法都返回一个新对象
| | |
| — | — |
| 修改 | withXXX() |
| 增加多少 | plusXXX() |
| 减少多少 | minusXXX |
| 比较判断 | equals |
| 比较判断一个时间是否在前/后 | isXXX() |
| 类型转换 | toXXX() |
| 获取 | getXXX() |
LocalDateTime 中forme()方法可以将日期转字符串
-
ZoneId、ZonedDateTime类: 与LocalDate类似,只不过这个包含时区
| | |
| — | — |
| 获取指定时区 | fo(“America/New_York”) |
| 获取当前/指定时区的日期对象 | ZonedDateTime.now() |
| 获取默认时区 | systemDefault() | -
Instant: 时间线上的某个时刻/时间戳
1. 代替Date类 Date类为可变对象 Instant为不可变对象
2. Instant精确到纳秒 获取得到的是到现在的毫秒数+不足一秒的纳秒数 -
Period: 计算年月日
1. between() 两个时间的差 返回Period对象 只会返回对应的时间差,需要自己计算(如:相差1年2月,则get年为1,get月为2)
2. 获取用getXXX() -
Duration: 计算相差天数=>纳秒数
1. between() 会每个独立计算(用来算相差的时间会更好)
2. 获取用toXXX()
String dateSc= "1988-6-4";
SimpleDateFormat simpleDateFormat= new SimpleDateFormat("yyyy-MM-dd");
SimpleDateFormat simpleDateFormat2= new SimpleDateFormat("yyyy年-MM月-dd 是EEE,是 yyyy年的第");
Date dateTime = null;
try {
dateTime= simpleDateFormat.parse(dateSc);
}catch (Exception e){
System.out.println("数据转化异常");
}
//将date转化成Instant
Instant insTime1= Instant.ofEpochMilli(dateTime.getTime());
//将获取到的毫秒转化为时区
ZonedDateTime zonedDateTime1= insTime1.atZone(ZoneId.systemDefault());
//将时区格式化到LocalDate
LocalDate localDate= zonedDateTime1.toLocalDate();
System.out.println(simpleDateFormat2.format(dateTime) + localDate.getDayOfYear() + "天");
两种时间类的区别:
Date和Calendar类都是可变对象(即:是单一对象,修改处理时是修改本对象的数据)这种可变性使得在多线程环境下使用日期类变得非常困难。
在JDK8之后引入了一组新的日期和时间API,这些类都是不可变的(即:一旦创建了就无法修改它,我们所修改的是一个新的对象,其每个修改方法都是返回一个新的对象)
不可变日期对象的好处包括:
- **线程安全:**因为对象不可变,所以多线程环境下使用更加安全。
- **易于缓存:**因为对象不可变,所以可以将其缓存并重用,而不必担心副作用。
- **易于保持一致性:**如果你将一个不可变日期对象存储在多个数据结构中,你可以放心地认为这些对象始终具有相同的值。
public class DateTest {
public static void main(String[] args) {
LocalDate date = LocalDate.of(2022, 10, 31);
System.out.println(date); // 2022-10-31
LocalDate nextDay = date.plusDays(1); //增加一天
System.out.println(nextDay); // 2022-11-01 新对象
// 注意:对日期进行“加、减”操作时,
//会返回一个新的日期对象,原始日期对象不变
System.out.println(date); // 2022-10-31
}
}
Arrays: --------------工具类
- 自定义排序(该方式可用于集合排序)
1. 通过接口
2. 通过抽象类的子类
Lambda表达式:-----------------JDK8新特性,表达式中只能有一个方法
其中setAll() 方法的第二个参数是传入一个函数式接口,传入接口即 可写入匿名内部类实现接口,而Lambda表达式便是简化这个匿名内部类的实现
- 格式--------作用简化 函数式接口 的匿名内部类(函数式接口:有且仅有一个抽象方法的接口 )
- 实际上就是创建函数式接口的实现类,在此调用实现类的方法
- Lambda中调用方法时,接口的匿名内部类方法的形参要与调用方法的形参一致
- 静态方法引用(实例方法与其类似)
- 特定类型方法引用:
- 什么特定类型
- 其中方法中第一个参数为方法体的主调(用其调用其对象中的方法)
- 而后面的方法参数作为主调方法的实参
- 类型 : : 方法名(例:String : : comoareTo )
- 什么特定类型
- 构造器方法引用:类名 : : new
-
- 静态方法引用(实例方法与其类似)
算法:
冒泡排序:
2. 双重循环每一次将对比序列中的最大值放于最后,之后该元素不再参与比较,即对比序列长度一直在减少(有n个元素那就要循环n-1次)
- 选择排序:
二分查找:
正则表达式:
- 或者可省略不写
| [ ] | 只能为其中的单个字符 |
| — | — |
| [^ ] | 除这些外的任何单个字符 |
| [ a-d ] | a到d中单个字符(包括) |
| [ a-d [ def ] ] | a到d 或 m到p |
| | |
只匹配单个
预定字符
\ . 等于 [ . ] 表示判断字符“点”
数量词
分组后会先以外括号开始,然后再是括号内的组,然后才是下一个组
其为贪婪匹配,若文本数据没有换行,会尽可能长的匹配“asdrttedaa”用(a.d)进行匹配则会输出“asdrtted”即找到该行最后一个d,若要取消贪婪匹配,则(a.?d)
修饰组号
集合:蓝色为接口,橙色为实现类,层级为继承关系
Collection (接口): ----------单列集合( 三个接口五个类 ) Collection是一种不带索引操作的常规接口,所以其没有set,get方法
Arrays.asList(arr) 将一个数组转化成一个List集合对象,此时泛型为Object类型
部分共有方法:
- contains( 查找元素 ) -----------查找集合中是否存在某元素
- isEmpty(); --------------判断集合是否为空
- clear() ; -------------清空集合
- toArray(); -----------将集合内容存入Object[] 数组
迭代器(Iterator<>):用来遍历集合的专用方式(数组没有迭代器 集合中有些集合没有索引) ---------快捷键itit
- 调用:集合对象.iterator( ); --------返回当前集合的迭代器对象
- 使用:
while(当前迭代器对象.hasNext( ) ){
next(); //返回当前元素,并将迭代器对象指向下一个元素
}
增强for循环 --------底层便是迭代器,但数组也可使用 (常用) -----------快捷键 .for
for(接收元素的类型 s : 集合/数组对象){
System.out.println(s); //打印集合或数组中的所有值
}
Lambda表达式遍历集合 ( 易忘 )
- 使用Lambda表达式的原因:
- forEach()方法是Iterable接口的一个普通方法,而Collection接口继承于Iterable接口(所以可用集合调用方法,不需要创建Iterator对象)forEach(Consumer<? super T> action ) 方法其中传入参数为一个函数式接口
Collection<String> list = new ArrayList<>();
list.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
}
});
//Lambda表达式
list.forEach(s->System.out.println(s))
并发修改异常
在获取迭代器的时候,即告诉了迭代器要迭代多少个元素,而在while循环中是通过集合删除当前元素,此时迭代器并不知道元素被删除了,仍然按照之前方式迭代,所以会报并发修改异常 (增强for循环也是如此,其底层也是用的迭代器,但因为增强for循环获取不到当前集合迭代器,所以无法解决该问题)
处理:在while循环中用迭代器的remove() 方法删除元素
List(接口):[ 特点: 有序添加元素(按存入顺序存入)、可重复、有索引 ]
- ArrayList
- LinekdList
Set (接口):(特点:不重复、无索引)
- HashSet:无序(无序添加元素)、不重复、无索引
1. LinekdHashSet(特殊):有序(有序添加元素)、不重复、无索引 - TreeSet:元素会进行排序(为无序添加元素)、不重复、无索引
- map: ------------双列集合
集合的数据结构
ArrayList集合的底层是基于 数组的原理
特点
- 查询、修改快,按照索引直接找(不需要改变数组长度,即不需要创建新数组)
- 增、删效率低(会改变数组长度,从而需要创建新数组)
底层创建过程:
- new ArrayList<>(); 创建了一个集合对象(构造器),此时底层创建一个默认长度为0的数组( new E[]{} )
- 当添加第一个元素时创建长度为10的数组
- 当前数组 长度存满时会扩容1.5倍,若一次添加多个元素(如 用addAll()方法)且超过扩容1.5倍时,会以实际长度进行扩容
LinkedList集合的底层基于 双链表 链式储存原理
链表:
- 链表中的节点是独立的对象
- **双链表:**上节点<-- 指针域 值 指针域 -->下节点,头节点与尾节点的对应指针域为空
- 添加一个元素时,便将添加元素的地址值给前后对应元素的指针域,其自身也是添加对应地址,从而双向链接
特点:
- 查询慢,无论查询哪个数据都要重头开始找(索引是数组中的特有,所以该集合没有索引的概念,而他之所以可以通过传入的数查找到指定目标,是因为传入来的数控制着for循环的循环次数,方法体中循环next)
- 增、删快,其从循环找到该位置,然后改变指针域地址即可,且首尾 增、删 极快,因为空指针域很好找
- 符合队列结构数据储存,也可设计为符合堆栈结构数据储存(但在并发操作时是线程不安全的)
特有方法
- addFirst()、addLast()
- getFirst()、getLast
- removeFirst()、removeLast()
- pop() 队列中出队(使内存中的对象 先进先出)
应用场景
- 设计栈结构数据储存:
1. 先进后出,用push()压栈、pop()弹栈(或使用first相关方法) - 设计对类结构数据储存:
1. 先进先出,用addFirst()压栈,pop() / removeLast()弹栈
集合中常用于设计数据储存的类(了解)
栈结构类:
- Stack:是java.util包中的一员,Stack类继承于Vector(Vector类实现了List接口),Stack是线程安全的类,提供了常用的入栈、出栈、查看栈顶元素等操作。
- ArrayStack:自定义的基于数组实现的栈结构类,具有高效的入栈、出栈操作,但容量有限,需要提前定义栈的大小。
- LinkedStack:自定义的基于链表实现的栈结构类,没有容量限制,入栈、出栈效率较高,但相对于ArrayStack会有一些额外的内存开销。
三者都实现了「java.util.Deque」接口
队列结构类:
- Queue(接口):LinkedList是其一个实现类,以下三个类均是该接口的实现类
- ArrayQueue:自定义的基于数组实现的队列结构类,具有高效的入队、出队操作,但容量有限,需要提前定义队列的大小。
- LinkedQueue:自定义的基于链表实现的队列结构类,没有容量限制,入队、出队效率较高,但相对于ArrayQueue会有一些额外的内存开销。
- PriorityQueue:Java中提供的优先队列结构类,可以按照元素的优先级进行排序,提供了常用的插入、删除最大/最小元素等操作。
Set集合
---------Set常用方法基本上是Collection提供的,增删改查都较快,Set集合的三种实现类的底层均是对应的Map,Set的值存于Map的Key中
数据结构(树)
- 二叉树 根节点与父节点只能有一个,而子节点有两个及以下
- 度:每个节点的子节点数量
- 树高:树的总层数
- 普通二叉查找树会出现单一链式结构(即:存入数据一直放于一边)
- 解决:平衡二分查找树,将单链中中间的元素作为根节点(即:将中间元素提起来)
- 红黑树:一个特殊的自我平衡的二分查找树,黑色为根节点
- **哈希值 ** -----------一个int类型的数值
- Java中每个对象都有一个哈希值,都提供hashCode()方法
- 一个对象多次调用hashCode()方法,返回的哈希值是相同的,不同对象的哈希值一般不同(但也有可能相同,此时称为:哈希碰撞)
HashSet集合的底层原理 -------哈希表
- 哈希表 ---------增删改查性能都较好的数据结构
- JDK8之前: 数组+链表
- JDK8开始: 数组+链表+红黑树
- HashSet的底层(底层是个HashMap): ---------实现不重复
- **当添加第一个元素时,**创建一个默认长度为16的数组,加载因子为0.75(数组在第一次添加元素时创建)
- 每次添加都会计算当前添加对象的哈希值(可重写hashCode()使哈希值自定义),使元素的哈希值对数组长度取余计算出应存入的位置
- 判断存入位置是否为null,如果不为空,则调用equals方法比较(链式中的数据也比较)
- 注意:当对比两个对象时(非String),equals方法只对比两个对象的地址,并不会对比对象的内容,所以此时不会去重,此时应在该对象中重写equals方法和hashCode方法,将该对象的属性进行对比(主要在存入自定义)
- 若相等则不存,若不相等:
- **JDK8之前:**新元素替换老元素,老元素挂下面(链式结构) --------也无红黑树
- JDK8开始:新元素挂老元素下面 ---------有红黑树
- 当列表长度超过8,且数组长度>=64时,当刚好达到条件时存入下一个元素(相当于通过下一个元素的添加进行刷新),将自动将链表转成红黑树(当列表长度超过8的时候,每添加一次都会对数组进行扩容,直到数组长度大于等于64)
- 扩容机制
- 当集合中总元素大于等于 数组长度*0.75(加载因子) 时则,对数组扩容两倍
- 数组扩容后,所有数据将重新编序存储(因为把原数据存入新数组中仍然符合存入规则)
LinkedHashSet ----------哈希表+双链式结构(在HashSet的基础上 实现有序)
- 相比于HashSet的底层,LinkedHashSet的每个元素都额外会 以双链式机制将存入的元素按存入顺序连接起来,以实现有序
TreeSet --------实现 元素唯一与排序 主要依据红黑树
- 排序:
- 对于数值类型:默认升序排序
- 对于字符串类型:默认按照每个字符的ASCII码排序,优先级依次降低
(若是字符串中含有中文则默认会按照Unicode字符编码顺序,在Unicode编码中大写英文字母在小写英文字母前面,所有英文字母在中文前面)
扩展:
Comparator接口用于定义两个对象之间的比较规则,可使用其.comparing(HanziSort::toPinYin)或
.comparingInt(HanziSort::getStrokeCount)使汉族字符串以 拼音 或 笔画 排序
TreeSet<String> set = newTreeSet<>(Comparator.comparing(HanziSort::toPinYin));
3. **若存自定义类型(如:Student对象,默认是无法排序的,会报错):**
1. **方法一:**让Student类中实现Comparable接口,重写里面的compareTo方法(前减后升序)
2. **方法二:**在创建TreeSet集合对象时,传入Comparator(比较器接口的匿名对象),重写compare方法来指定规则(在Arrays.sort方法中用过)
可变参数 ----------只能定义在方法的形参位置
- 格式: 类型… 参数名(如:int… i)
- 可变参数:可传参,也可不传参,也可传多个该类型参数,也可传该类型数组
- 可变参数在方法中是个数组( i[ ] )
- 注意:
1. 可变参数在形参列表中只能有一个,且必须放在形参列表末尾
Collections工具类中常用方法
shuffle(集合对象) | 打乱List集合中的信息 |
sort(集合对象) | 对List集合进行升序排序 |
.unmodifiableList( 集合对象 ) | 将list集合对象变为不可变集合 |
addAll(集合对象, 添加的元素) | 给集合添加元素 |
注意:不可变集合的数据类型也不能变化,向下、向上转型也不行;
Map集合 -----------双列式结构的“行头”
Set的三个实现类的底层均是对应的Map,Map中的这三个实现类与Set中的特点一样
map.values() | 获取所有值,返回一个Collection的实现类 |
map.keySet() | 获取所有键,返回一个Set集合 |
遍历:
- 先通过.keySet()方法获取所有的键,再用键的增强for循环遍历
- 使用Map中的entrySet()方法,将map中的键值对封装成一个对象存入Set集合中,即将map封装存入Set集合中(Set<Map Entry<K, V>>),在通过增强for循环遍历,遍历获取的元素通过getKey()方法和getValue()方法,获取键和值 -------- 一般不用
- 利用集合的 .forEach()传入一个函数式接口的实现类(Lambda表达式) (k, v)->{Systrm.out.println(k+"= "+v);}
//增强for循环
Set<Integer> keySet = nameMap.keySet();
for (Integer integer : keySet) {
System.out.println(integer+"= "+nameMap.get(integer));
}
//entrySet()方法
Set<Map.Entry<Integer, String>> entries = nameMap.entrySet();
for (Map.Entry<Integer, String> entry : entries) {
System.out.println(entry.getKey()+"= "+entry.getValue());
}
//forEach()方法
nameMap.forEach((k, v)-> System.out.println(k+"= "+v));
- HashMap ------无序
- LinkedHashMap ------有序
- TreeMap -------排序
Stream流:( jdk8新特性,是jdk8新增的一套API)
用于操作 **集合 **和 数组 的数据
注意:Stream流不能真实的、直接的删除流中元素(只能通过跳过该元素后,获取新流返回)
优势:
- Stream流中大量结合了Lambda语法风格编程,提供更强大更简单的方式操作集合或数组的数据(使代码更简洁)
//查询姓“张”的名字,且名字长度为三的
List<String> list= new ArrayList<>();
Collections.addAll(list, "刘备","关羽","张飞","张飞飞");
List<String> list2= list.stream().filter(name->name.startsWith("张") && name.length()==3)
.collect(Collectors.toList());
System.out.println(list2);
中间方法: ---------- 仍然返回一个steam流,可继续操作
sorted( 自定义排序接口 ) | 对元素进行排序,不传参默认升序 |
limit( long maxSize ) | 获取前几个元素 |
skip( long n ) | 跳过前几个元素 |
distinct() | 去除流中重复元素 |
map() | 对元素进行加工,并返回对应的新流(如:将流中元素加1) |
concat(Stream a, Stream b) | 将a、b合并为一个流 |
filter( 函数式接口 放查找条件 ) | 对流中数据进行过滤 |
终结方法: ------------- 结束一个steam流
count() | 统计此流运算后的元素个数(即 最终流的元素个数) |
max( 排序规则 可无参 ) | 获取最终流的最大值元素,只返回满足的第一个 |
min( 排序规则 可无参 ) | 获取最终流的最小值元素 |
collect( 处理最终流接口 ) | 按规则收集最终流元素到指定的集合中去 |
toArray() | 将流中数据收集元素到一个数组中去(返回 Object[ ]) |
forEach( 函数式接口 ) | 对最终流数据进行遍历 |
get() 获取值所有值
注意:
- 一个stream流只能操作一次,不能重复操作(可以想象成流中数据已经流向下一个流(末尾流)里了,所以不能再对初始流进行操作)
- stream流中如果没有终结方法,则中间方法不会执行
File类: --------- 目录操作类
File类只能对文件本身进行操作,不能读写文件里面存储的数据
file.length()只能获取文件字节大小,需要获取目录字节大小需要用递归来对该目录下的所有文件字节大小相加
目录名也可以有后缀(aaa.txt),文件名也可没后缀(aaa),所以需要用file中的方法进行区别
File封装的对象仅仅是一个路径名,这个路径可以是存在的,也允许是不存在的。
地址路径:
- 相对路径:不带盘符,从当前目录( 项目 )下开始,相对于当前目录
- 绝对路径:带盘符,从根路径开始
获取:
获取文件信息
length() | 获取文件大小,返回long类型字节数 |
lastModified() | 获取文件最后修改的时间,返回long |
getPath() | 获取创建文件对象时,使用的路径 |
getAbsolutePath() | 获取绝对路径 |
getName() | 获取文件名称,含后缀 |
遍历获取文件
- **遍历获取 **
| | |
| — | — |
| 主调.listFile() | 获取当前目录下的子文件和文件地址(返回 File[] 数组) |
| 主调.list() | 获取当前目录下的子文件和文件夹名字(返回String[] 数组) |
主调为 非空 文件夹,该类方法会返回该文件夹中对应的所有元素
(包含隐藏文件,但不能访问没有权限的文件夹)
- 遍历获取的 特殊返回值
| | |
| — | — |
| 返回长度为 0 的数组 | 主调为 空文件夹 |
| 返回值为: null | 主调为文件、路径不存在、没有权限访问 |
判断
isFile() | 判断当前文件对象,是否为文件 |
isDirecctory() | 判断当前文件对象,是否为文件夹 |
exists() | 判断当前文件对象,对应文件路径是否存在 (即:判断文件是否存在),存在则为true |
**扩展:**exists()与下面两个的区别: -------------- 一般在 Linux 环境中比较明显
exists()方法可以判断任何类型文件或目录,而下面两种不能判断一些特殊文件或目录是否存在(即使存在也会返回false)
特殊文件:符号链接、管道、套接字等等;
创建:
mkdir() | 创建单级文件夹 |
mkdirs() | 创建多级文件夹(包含单级) |
createNewFile() | 创建文件 |
删除
delete() | 删除文件 或 空文件夹(直接删除,不进回收站) |
IO流: -----------输入、输出流都是以内存为基准
编码格式:
**UTF-8:**汉字占3个字符,兼容ASCLL码( 输入输出流一般是平台默认字符集,如:idea默认为 UTF-8,大部分平台都是使用的UTF-8 )
**GBK(国内码):**汉字占2个字符,兼容ASCLL码
**ASCLL:**英文、字符、符号、操作符等 均为一个字节
编码前使用的字符集与解码使用的字符集要一致,不然会乱码
//编码 字符-->字节
byte[] data= "v我50".getByte(); //idea默认字符集UTF-8
byte[] data1= "v我50".getByte("gbk"); //国标码编码
//解码
String dataStr= new String(data);
String dataStr1= new String(data1, "gbk");
IO流体系:
字节流:适合操作所有类型的文件,一般用于文件移动复制,如处理音频、图片、视频等文件 ----------以字节的形式输入输出
输入流:inputStream ( 所有字节输入流的超类 抽象类 )
- new FileInputStream(“文件地址”); ** 输入流 地址有误会报错**
| | |
| — | — |
|
read( byte[ ] ) | 读取一个字节组(效率高),并使指标移向下一个字节组的开始,当指标开始位未读到数据则返回-1(读多字节字符时也可能乱码,即:在末尾或开始只读到多节字符的其中一部分字节) --------此时推荐用字符流,除非定义足够大的字节组或用readAllBytes(),一次存入整个文件 |
| readAllBytes() | 读取该文件的所有字节内容,返回一个byte[ ](如果文件太大超出数组长度类型(int),则会使内存溢出) |
| read() | 从文件中只读一个字节(效率低),并使指标移向下一个字节,如果当前指标未读到则返回-1(读到多字节字符会乱码) |
输出流:OutputStream ( 所有字节输出流的超类 抽像类)
- **new FileOutputStream(“文件路径”); --------------也需要包含文件名 **父路径必须真实存在,不然报错(即:文件名之前的路径必须是正确的)
- 如果不存在该路径下的文件,则创建一个
- 若存在则会覆盖文件原有内容(即:先清空原有文件内容,再编入)
- new FileOutputStream( “文件路径”,true/false );
- true:在原有文件的基础上添加,无该文件则创建,图片等
- false:与无参一样
| | |
| — | — |
| write( byte[ ] ) | 写入一个字节组 |
| write( byte[ ], int 开始, int结束 ) | 写入一个字节组的部分 |
| write( byte b ) | 写入单个字节数据 |
处理异常
- try–catch–finally [ 在IO资源释放 ( 即:close() ) 太过繁杂,需要层层嵌套来处理异常后,关闭流 ]
- try–with–resource:-------自动释放资源
- try( ) 中只能放资源,否则报错 ** 注意变量申明也要放在一起**
- 资源一般指的是最终实现了AutoCloseable接口的类,进行实例
- 如:FileOutputStream fos= new FileOutputStream(“地址值”);
- 扩展:除了IO的 字符字节 的 输入、输出流 外,还有: ------了解
- 资源一般指的是最终实现了AutoCloseable接口的类,进行实例
package inClassTest;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class PngCopy {
public static void main(String[] args) {
//优化终止
try(
//获取源文件地址 并创建输入流对象
FileInputStream fis= new FileInputStream("myHomeWork\\src\\inClassTest\\img\\mm.jpg");
//获取拷贝地址(包含文件名) 并创建输出流对象
FileOutputStream fos= new FileOutputStream("myHomeWork\\src\\inClassTest\\img\\mmCopy.jpg", true);
){
//创建临时储存器 储存输入流中的数据,传到输出流中
byte[] bytes= new byte[8192];
//用于获取本次循环传入数组中的字节长度
int len= 0;
//fis.read(bytes) 直接读取一个数组长度的数据放于数组中
//末尾则读取相应数据长度
while ((len= fis.read(bytes)) != -1){
//写入对应地址 0->len 是为了让最后一组bytes[]
//字节取到相应的长度,而不会往后取,从而避免数据异常
//len是返回当次循环read()取出的字节个数
fos.write(bytes, 0, len);
}
} catch (IOException e) {
System.out.println(e);
}
}
}
字符流:适合操作纯文本文件 -------以字符的形式输入输出
输入流:Reader ( 所有字符输入流的超类 抽象类 )
输出流:Writer ( 所有字符输出流的超类 抽线类 )
- flush() --------刷新流(关闭流 的底层其实是调用了一次刷新方法后再关闭流)
- **注意:**字符输出流是一个缓冲流,输出的数据在流 刷新或关闭前,不会流向储存文件中,而是缓存在流中,所有继承与Writer下的流都是如此
String data= "哇哦ad13;篇";
byte[] bytes= data.getBytes("GBK");
缓冲流(Buffered+…):
分类: ----------共四种
字节输入、输出缓冲流(BufferedInputStream、BufferedOutputStream)
- 构造器中需存入字节原始流对象(即:FileInputStream、FileOutputStream)
字符输入、输出缓冲流 (BufferedReader、BufferedWriter)
- 构造器需传入字符原始流对象
- 特有方法:
- **输入流:**readLine()方法 ----------读取一行数据,每调用一次,则读取该行并将指标指向下一行,返回该行的内容(若未读到数据则返回null)
- **输出流:**newLine()方法 ------根据系统进行换行
特点: --------将原始流(数组方式存取)包装起来,但多一个1KB的中间数组
- 比原始流的速度更快,其将原始字符字节输入输出流包装,从而提高性能
底层原理:
- 缓冲流的输入、输出流各有个专门存放数据的数组(大小为8KB),并将收到的数据存入其中(其中数组在流对象创建时创建)
- 以数组的方式传递出去,从而减少与外界存储的交互次数
- 其中有个1KB的中间数组,用于将输入、输出流中的数组数据相互转换
转换流 ------构造器(原始流对象,“指定字符集”)
- 解决读取数据时 字符集 (编码格式) 类型转换问题,从而防止乱码
字符输入转换流:(IntputStreamReader)
- 将字节流按指定编码格式获取数据
字符转换输出流:(OutputStreamWrite)
- 将字符数据按指定编码格式存储
打印流:将 字符串 或 文件内容 打印到指定文件中 (可指定编码格式) -----输出流
分类( 二者没太大区别,只是继承不一样,处理相应数据更有优势)
- PrintStream; --------字节 打印流
- PrintWriter; ---------字符 打印流
构造器: ------------当只是传入字符串时,只能按照默认写入模式(即:覆盖)
PrintStream( url , “编码格式” ) | 指定编码格式 |
PrintStream( OutputStream , boolean , [“编码格式”]) | 获取数据,指定是否自动刷新,可指定编码格式 |
PrintStream( OutputStream/File/url ) | 获取数据 |
方法:
write(int/byte[]/byte[]一部分) | 可支持将字节数据写出去 |
println( ) | 可将任意类型的数据打印 |
重写System.out的打印地址
- **System.setOut( File文件地址 ) ** ----------修改系统打印地址
- 修改后 System.out.println( ) 将直接打印至文件中
数据流(DataInputStream、DataOutputStream):
- 数据流获取数据时,定义的数据类型必须与传出时一 一对应,否则会乱码(即:读写所用的方法要与数据一 一对应)
- readUTF( String str ) 与 writeUTF( String str ) --------将字符串以UTF-8的编码格式输入输出
序列化流:
序列化(ObjectOutputStream):
- 序列化的对象必须实现Serializable接口(JDK自带的API中都实现了这个接口,但若是通过集合将对象存储,集合中的该对象也需要实现该接口)
_ Serializable接口相当于一个标识,其中没有任何内容 _
- 若对象中的某个成员变量不想被序列化,则可以用transient修饰该属性
- writeObject( Object ) -------将对象写出去
- 序列流的输入输出流需一 一对应(即:不能一个写,然后一个循环读)
反序列化(ObjectInputStream):
- 若对象序列化后,修改的对象所属的类,则反序列化会出现版本异常
- 为解决这个问题,则可以在该类中加一个版本号属性:static final long serialVersionUID = 当前版本号;(在接口的注释中可找到)
- 反序列化的包名和类名要一致,否则会报错(若要解决该问题则需要重写ObjectInputStream中的resolveClass方法)
- readObject( ) ----------将储存在文件中的java对象读出来
IO框架(commons-io架包)
- FileUtils类 --------操作文件
- IOUtils类 ----------操作流
不同系统下的文件换行:
**windows系统:**文件中的换行每行结尾都有 “\r\n”
- 在windows系统中,虽然 “\n” 可以达到换行的效果,但仍推荐输出流 用"\r\n",有些软件在读取文本文件时,如果遇到了不同的换行符,可能会出现不兼容的情况,从而不会换行
**Unix系统:**每行结尾都只有 “\n” (如:Linux系统)
**Msc系统:**每行结尾都只有 “\r”(从Mac OS开始于Linux统一,即:“\n” )
特殊文件(Properties、XML文件)
Properties类是Map集合下的一个子类,专门用来操作Properties文件(存储键值对),没有泛型,键值对默认为String类型
- kry1= value1
- #注释
读取文件中的属性:
getProperty( String key ) | 根据键获取值 |
stringPropertyNames( ) | 获取所有键(即:keySet方法) |
load( 输入流对象 ) | 读取文件中的键值对数据(字符/字节) |
store( 输出流对象, 注释 ) | 将Property对象中的键值对存到文件中 |
setProperty( key, value ) | 保存键值对到Property对象中 |
若写入的键值对在原文件中存在,则会覆盖原文件的值
**XML:**以标签的形式存储数据
- 文档声明:<?xml version="1.0" encoding="utf-8" ?> -------可有可无,但写了就必须第一行顶格写
- **用处:**一般用来配置文件,作为程序的配置文件
- 注意事项
- 跟标签只能有一个
- 转义字符:
< | < 小于号(xml中属于非法判断符) |
---|---|
> | > 大于号 |
& | & 与(xml中属于非法判断符) |
' | ’ 单引号 |
" | " 双引号 |
5. 字符区(文本域):
1. <![CDATA[ 文本域 ] ]>(CD快捷键)
6. **解析XML文件(DOM4J架包 )**(每个标签均是一个对象)
1. 创建SAXReader对象
2. 通过SAXReader对象获取解析XML文件(read方法获取文件)获取Document对象
3. 获取文件中的跟标签对象
4. 通过跟标签对象获取子标签对象
5. 通过子标签对象获取标签属性
7. **XML文件约束(需要导入约束文件) ** --------约束标签名与标签格式
1. DTD文档
2. Schema文档(约束更加全面,功能更加强大)
日志技术(slf4j架包、logback-classic、logback-core配置文件)
创建lib包导入jar架包,项目src下导入配置文件
获取日志工厂,传入此处使用日志的类名
private static final Logger LOGGER= LoggerFactory.getLogger("LogTest");
通过日志工厂的各种方法,输出
线程(抢占式执行)
线程启动必须要调用start方法,才会开启
start方法就只是用来启动线程的,在线程启动后start方法便出栈
若将子线程放于主线程任务前则不会多线程
同一线程不能同时执行,否则会报线程并发异常
实现方法
- 继承Thread类
- 实现Runnable接口
- Callable接口、FutureTask类、call方法----------可通过FutureTask对象的get方法获取线程的执行结果
public class Test {
public static void main(String[] args) {
MyCallable callable= new MyCallable("累加和:", 1, 100);
FutureTask futureTask= new FutureTask(callable);
Thread thread= new Thread(futureTask);
thread.start();
int sum= 0;
try {
sum= (Integer) futureTask.get();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(sum);
}
}
//任务类
public class MyCallable implements Callable {
private String name;
private int begin;
private int end;
public MyCallable(String name, int begin, int end) {
this.name = name;
this.begin = begin;
this.end = end;
}
public MyCallable() {
}
@Override
public Object call() throws Exception {
int sum= 0;
Thread.currentThread().setName(name);
for (int i = begin; i <= end; i++) {
sum+= i;
}
return sum;
}
}
多线程
setName( String name ) | 设置线程名称,线程可重名 |
currentThread() | 静态方法,获取当前执行线程对象 |
sleep() | 静态方法,线程休眠 |
join() | 让调用者线程先执行完 |
getName() | 获取当前线程的名称,默认为Thread-索引 |
数据安全:
当多个线程操作同一个数据时容易出现线程安全问题
- 如:两个账户线程取一个共享账户可能出现负值情况
线程同步(解决线程安全问题)
**加锁:**使当钱进入方法的线程操作共享资源(独占),执行完后释放锁,使其他线程可操作共同资源(相当于锁上门,然后进去拿东西,然后再开门让下一个人进来)
**同步锁:**相当于钥匙(钥匙应唯一),若直接在同步锁中创建一个对象(即:访问该方法拿到的钥匙不唯一),则此时依然线程不安全)----------一般用共享资源对象作为同步锁
**同步方法锁:**用锁修饰该方法,谁调用则该锁对象就是谁,相当于同步锁中传入this(同步方法),此时锁不唯一(所以该方法需要为静态,此时传入的锁对象为 方法所在类 类名.class,即:使锁对象唯一)
Lock锁
线程通信(利用线程同步使线程顺序输出)---------线程通信是在线程同步的基础上的
线程通信并没有指代关系(即:不能指定哪条线程等待或唤醒)
- 所以当有三条线程,两条是等待状态时,任务线程是随机唤醒两条中的一条线程
线程通信是利用同步锁对象确定线程关系(即:通过线程锁对象来判断是否是同一类线程)
注意:在任务线程等待后,后面代码不再执行,除非被唤醒(或定时结束)才会继续向下执行
notify() | 唤醒正在等待的单个线程 |
notifyAll() | 唤醒正在等待的所有线程 |
wait() | 让当前线程等待 并释放锁 |
线程池
使用ExecutorService接口下的ThreadPoolExecutor实现类
public ThreadPoolExecutor(
int corePoolSize, 参数一:核心线程数量
int maximumPoolSize, 参数二:线程池中最大线程数量
long keepAliveTime, 参数三:临时线程空闲存活时间
TimeUnit unit, 参数四:参数三的时间单位
BlockingQueue workQueue, 参数五:任务列最大数量(任务等待区域)
ThreadFactory threadFactory, 参数六:线程工厂(创建临时线程的模式)
RejectedExecutionHandler handler 参数七:任务拒绝策略(任务列表满后)
);
参数六线程工厂的获取,通过Executors工具类获取(Executors.defaultThreadFactory() )
使用Executors工具类(不推荐,当请求任务过多时容易出现内存溢出)
- 使线程可以复用,当线程执行完当前任务后,可执行下一个任务
并发和并行:
- 并发:
- 并行:
线程的生命周期
网络编程
UPD --------无连接,不可靠通信(不需要判断判断是否连接,直接发送)
特点:效率高,速度快,但数据易丢失 ---------语音通信、视频直播
DatagramPacket( byte[ ], 传输的长度, 对方IP, 对方端口号 ) | 创建数据包 |
DatagramPacket( byte[ ], 开始下标, 读取长度 ) | 创建接收数据包(只是创建,并未接收) |
datagramSocket对象.send( 数据包对象 ) | 将数据包发送 |
datagramSocket对象.receive( 接收数据包对象 ) | 接收数据包 |
DatagramSocket( 端口号 ) | 无参为随机端口号 |
//客户发送端Socket
DatagramSocket datagramSocket= new DatagramSocket();
//创建数据包
byte[] data= new String("Hello Server, Is this UPD?").getBytes();
DatagramPacket packet= new DatagramPacket
(data, data.length, InetAddress.getLocalHost(), 8888);
//将数据包发送出去
datagramSocket.send(packet);
//关闭
datagramSocket.close();
//服务接收端Socket
DatagramSocket datagramSocket= new DatagramSocket(8888);
//创建数据包
byte[] data= new byte[1024*64];
DatagramPacket packet= new DatagramPacket(data, data.length);
//接收信息
datagramSocket.receive(packet);
//输出信息
System.out.println(new String(packet.getData(), 0, packet.getLength()));
TCP --------面向连接,可靠通信
特点:效率不高,但数据不易丢失(常用)
ServerSocket( 端口号 ) | 设置服务器端口号 |
ServerSocket对象.accept() | 服务器端等待用户端请求 |
Socket( 服务器地址,服务器端口号 ) | 客户端与服务器连接 |
public static void main(String[] args) {
//try-with-resources类型用于当异常断开连接时,客户端释放资源
try(Socket socket= new Socket(InetAddress.getLoopbackAddress(), 9999);) {
System.out.println("服务器连接成功!");
//开辟线程 执行用户输入
new UserOutThread(socket).start();
//循环接收服务器信息
while (true) {
//用户接收
InputStream inputStream = socket.getInputStream();
DataInputStream dataInputStream= new DataInputStream(inputStream);
System.out.println("服务器:"+dataInputStream.readUTF());
}
} catch (IOException e) {
System.out.println("服务器已断开连接!");
}
}
public static void main(String[] args) {
try {
ServerSocket serverSocket= new ServerSocket(9999);
System.out.println("服务器已启动,等待用户连接...");
//获取用户请求
Socket accept= serverSocket.accept();
System.out.println("客户端已连接,IP:"+accept.getInetAddress());
//创建线程回应客户端
new ServerOutThread(accept).start();
//服务器接收客户端请求
while (true) {
InputStream inputStream = accept.getInputStream();
DataInputStream dataInputStream= new DataInputStream(inputStream);
System.out.println("客户: "+dataInputStream.readUTF());
}
} catch (IOException e) {
System.out.println("用户已断开连接...");
}
}
//客户端
public class UserOutThread extends Thread {
private Socket socket;
public UserOutThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
while (true) {
OutputStream outputStream = socket.getOutputStream();
DataOutputStream dataOutputStream= new DataOutputStream(outputStream);
Scanner sc= new Scanner(System.in);
String str = sc.next();
if (str.equals("exit")){
System.out.println("正在断开连接...");
socket.close();
break;
}
dataOutputStream.writeUTF(str);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//服务器端
public class ServerOutThread extends Thread{
private Socket accept;
public ServerOutThread(Socket accept) {
this.accept = accept;
}
@Override
public void run() {
try {
Scanner sc= new Scanner(System.in);
while (true) {
//服务器回应
OutputStream outputStream = accept.getOutputStream();
DataOutputStream dataOutputStream= new DataOutputStream(outputStream);
String str = sc.next();
dataOutputStream.writeUTF(str);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
cmd控制台相关指令
netstat指令 ------cmd控制台
作用:查看当前主机网络连接和端口监听情况
netstat -an ------基本指令
netstat -anb ------显示运用当前端口的程序
metstat -an | more 分页查看
LISTENING ------端口正在监听 ESTABLISHED ------连接成功
ctrl+c 结束查看
反射
由于类只加载一次,所以类的字节码对象只有一个,无论通过什么方式获取的字节码对象都是同一个
可通过字节码文件解析获取类的所有内容
获取字节码对象:
- 类名.class
- 对象名.class
- Class.forClass( 该类所在包路径[全类名] ) (即:导包)
通过类字节码对象获取:
- *****Declared*表均可获取,无则表只能获取public修饰的
- *+s表获取所有,无则表获取单个
- 字节码对象.getClassLoader() 获取类加载器
- 构造器:
getDeclaredConstructors() | 获取所有构造器 |
构造器字节码对象.newInstance( [Object…] “实参列表” ) | 通过构造器创建对象,参数为实参(初始化) |
getDeclaredConstructor( 构造器参数类型字节码对象 ) | 获取构造器,无参则获取空参构造(只是获取,并非创建对象) |
属性:
getDeclaredFields() | 获取所有属性,包含非public |
属性字节码对象.get( 对象 ) | 获取该属性值,其中的对象为属性所在类的实例化对象 |
属性字节码对象.set( 对象, 替换值 ) | 修改属性值 |
getDeclaredField( “该属性名” ) | 获取单个对应属性 |
方法:
getDeclaredMethods() | 获取所有方法 |
方法字节码对象.invoke( 对象,[Object…] “实参列表” ) | 执行对象的该方法,其中的对象为属性所在类的实例化对象 |
方法字节码对象.invoke( null ) | 执行静态方法 |
getDeclaredMethod( “方法名”, 参数类型字节码对象 ) | 获取单个对应方法 |
破解访问权限(暴力反射):
对应字节码对象.setAccessible( true ) | 设置为true,表示禁止检查访问控制(暴力反射) |
注解(Annotation)
- 注解的本质是一个接口,注解使用的本质是注解的实现类
- 所有注解都是继承了Annotation接口
自定义注解
public @interface 注解名{
public 属性类型 属性名() default 默认值;
}
注解的使用: --------注解可使用在任何位置
有属性的(有返回值的方法):
- **@注解名( 属性名=属性值,… ) **
- 若属性只有一个,且属性名为"value"可直接写属性值
- 数组类型:如 arr[]={ 1,2,3 },当属性只有一个时,可直接写{ 1,2,3 }
元注解: ---------修饰注解的注解
-
@Target(ElementType.TYPE) -----------设置注解的权限,可叠加(共六个)
| TYPE | 只能用于 类、接口 |
| — | — |
| FIELD | 成员变量 |
| METHOD | 成员方法 |
| PARAMETER | 方法参数 |
| CONSTRUCTOR | 构造器 |
| LOCAL_VARIABLE | 局部变量 | -
@Retention(RetentionPolicy.RUNTIME) ------------设置注解的生命周期
| SOURCE | 只作用在源码阶段 |
| — | — |
| CLASS(默认值) | 保留到字节码阶段,运行阶段不存在 |
| RUNTIME(常用) | 一直保留到运行阶段 |
源码与字节码的区别:
源码是程序员编写的Java代码,字节码是已经编译好的Java代码,编译器先将源码编译成字节码后再运行,字节码是程序员无法直接阅读的机器码,需要通过反编译工具才能将其转换为可读的Java代码
动态代理
- 动态代理的底层是发射
- 应用场景:
类中一个或多个功能方法不能更改,但需要额外功能时
- 实现:(java.lang.reflect.Proxy)
使用Proxy类中的静态方法 newProxyInstance( )
public static Object newProxyInstance
(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
//参数一:用于指定用哪个类加载器,去加载生成的代理类
//参数二:指定接口,这些接口用于指定生成的代理长什么,也就是有哪些方法
//参数三:用来指定生成的代理对象要干什么事情
2. 创建一个接口,该接口中定义需要扩展功能的方法名(被代理类需要实现该接口)
3. 在方法使用处,通过反射获取被代理对象的 类加载器
4. 通过反射获取被代理对象实现的接口(内含需要代理的方法)
Class<RealBankAccount> realAccount = RealBankAccount.class;
//被代理类加载器
ClassLoader classLoader = realAccount.getClassLoader();
//获取被代理类实现的所有接口
Class<?>[] interfaces = realAccount.getInterfaces();
//创建代理对象
proxyAccount= (BankAccount) Proxy.newProxyInstance(classLoader, interfaces, new BankAccountProxy(account));