文章目录
- java基础部分零碎知识总结
- 基本数据类型
- 基础知识
- 类型之间转换(int和double为例)
- 局部变量和成员变量(实例变量和类变量\静态变量)
- 包装类
- 包装类作用
- 自动拆箱、自动装箱
- 缓存机制(JDK1.5之后提供的特性)
- 包装类 == 和 equals区别
- 面向对象 的理解
- this关键字
- System.out.println()
- 三大特征 -- 封装
- 三大特征 -- 继承
- 方法覆盖和方法重载
- 三大特征 -- 多态
- 多态基础语法
- super关键字
- Object中toString()方法
- Object中的equals()方法
- Object的finalize()方法
- Object的hashCode()方法
- String类
- StringBuilder和StringBuffer
- 抽象类
- 接口
- 基础语法
- 类实现接口可以多实现(多继承)
- 接口在开发中的作用
- 抽象类和接口的关系
- 集合
- 集合概述
- 集合继承结构
- Collection集合结构
- Map集合继承结构
- 总结
- Collection集合常用方法
- Collection集合迭代
- 泛型
- 泛型概述
- 类型自动推断
- 自定义泛型
- 反射
- 反射机制概述
- 获取java.lang.Class实例三种方法
- 通过反射实例化对象
- 通过读属性文件实例化对象
- 使用forName()方法执行静态代码块
- 获取类途径下文件的绝对路径
- 直接返回流
- 资源绑定器
- 类加载器
- 双亲委派机制
- 获取Field
- 反编译field
- 通过反射机制访问对象属性
- 访问public属性
- 访问私有属性
- 可变长度参数
- 反射获取Method
- 反编译Method
- 通过反射机制调用某个对象方法☆
- 反编译Constructor
- 反射机制调用构造方法
- 获取父类和父类接口
- 注解
- 注解概述
- 自定义注解
- 注解使用方式
- JDK内置注解
- Override注解
- Deprecated注解
- 元注解
- 注解中定义属性
- 注解当中属性类型
- 反射机制获取注解
- 反射机制获取注解对象属性的值
- 注解在开发中的作用
- 异常
- finally语句
- 异常经典题
- final finally finalize区别
- 自定义异常
- 多线程
- 概述
- 进程和线程的关系
- 分析程序多少线程
- 实现线程的方式一
- 实现线程的方式二(常用方法)
- 线程生命周期
- 获取线程的名字
- 获取当前线程对象
- 线程的sleep方法
- 终止线程的休眠
- 线程调度概述
- 多线程下的安全问题
- synchronized方法
- 有安全问题的变量
- synchronized出现在实例方法
- synchronized相关题目
- 死锁
- 解决线程安全问题
- 守护线程(后台线程)
- 定时器
- 实现线程的第三种方式
- 关于Object中的wait和notify方法(生产者和消费者模式)
- 生产者和消费者模式
- IO流
- IO流概述
- IO流分类
- javaIO流的四大家族
- 注意
- 需要掌握的16个流
- FileInputStream
- 一个字节一个字节读
- 往byte数组中读
- 最终版
- FileInputStream其他常用方法
- FileOutputStream
- 文件复制(字节流)
- FileReader
- FileWriter
- 普通文本复制(字符流)
- 带缓冲区的字符输入流BufferedReader
- 带缓冲区的字符输出流BufferedWriter
- 数据专属流
- 标准输出流
- File类
- 示例代码
- 常用方法
- 目录拷贝
- 序列化和反序列化(使用专属对象流)
- 序列化单个对象
- 序列化多个对象
- transient关键字
- 序列化版本号
java基础部分零碎知识总结
基本数据类型
基础知识
- Byte int long short都可以用八进制,十进制 十六进制表示,前缀0表示八进制,前缀0x表示十六进制
- Char类型值的范围是\u0000到\uFFFF,\u表示是十六位的unicode字符,\u0000表示一个空字符,长度为1,空格符为\u0020,十进制等效值为0,\uFFFF等效值为65535
类型之间转换(int和double为例)
- int类型转double类型
- 隐式转换:double比int范围大,所以int转double可以不需要类型转换
int i = 123;
double j = i;
- 使用double.valueOf()方法
double j = Double.valueOf(i);
- double转int类型
- 使用类型转换,损失精度,不四舍五入,直接删去小数点后数字
double i = 123.88;
int j = (int) i;
- 使用Math.round()方法,四舍五入
- 使用double wrapper类的intValue()方法,损失精度,不四舍五入
Double doubleObject = new Double(i); //或者Double object = i; 自动装箱机制
int j = doubleObject.intValue();
局部变量和成员变量(实例变量和类变量\静态变量)
- 访问修饰符不能用于局部变量
- Java变量分为局部变量和成员变量,成员变量分为实例变量(没有static修饰)和类变量(有static)修饰,局部变量声明在方法内,成员变量声明在类之内,方法外;局部变量储存在栈内存中,实例变量储存在对象所在的堆内存中,类变量储存在方法区;局部变量生命周期和方法一样,实例变量生命周期和对象一样,类变量和类一样;类变量初始化后不可改变;实例变量可直接使用变量名访问,在静态方法中需要使用 对象.变量名 来访问,静态变量使用 类名.变量名 访问
包装类
包装类作用
- java面向对象的语言,但是基本数据类型不具备对象的特性,java为每个基本数据类型提供了对应的包装类,包装类创建对象的方式和其他类一样
Integer num = new Integer(0);
自动拆箱、自动装箱
- 基本数据类型向包装类转换
Integer num = new Integer(0);
- 包装类向基本数据类型转换
int num1 = num.intValue();
- 自动装箱拆箱
Integer num = 1; //自动装箱,自动装箱使用的是Integer.valueOf(1)而不是new Integer(1)
int num1 = num; //自动拆箱
缓存机制(JDK1.5之后提供的特性)
- 只有Byte Short Integer Long Character有缓存机制,Float Double Boolean没有该机制
- 只要使用Integer类, Integer静态内部类加载时会创建-128 ~ 127的Integer对象,同时创建一个数组cache来缓存这些对象,当使用valueOf方法时首先判断是否在缓存数组中,如果在就直接范围已经缓存的对象,不会在创建新对象,当使用new创建对象时,就会直接创建新对象
- 使用场景:某个方法需要的参数为包装类型参数,而我们手里只有基本数据类型参数的值,则不需要做任何特殊的处理,直接把这个值传入方法中即可
- 缓存的作用:如果在缓存池中则不需要创建新对象,节省空间开销和时间消耗,提升了性能
包装类 == 和 equals区别
- 包装类使用==判断的是对象是否为同一个对象,而equals则是判断对象的值是否相等
- java中不能重写运算符,但可以重写equals()方法,Object类中默认的equals()方法时判断是否为同一个对象,经过包装类重写后则判断对象的值是否相同,同时还会重写hashcode()方法,使得哈希值等于对象值本身
public boolean equals(Object obj) {
if(obj instance of Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
面向对象 的理解
- 面向对象思想是一种优秀的程序设计方法,他运用类 对象 封装 继承 消息等概念进行程序设计,面向对象思想从现实世界客观存在的事物出发构造软件系统,在程序设计中尽可能地使用人类的自然思维方式,将这些事物抽象成一个个类,作为系统的基本构成单元,使得软件系统的组件可以映射到现实世界中来,保持了客观事物以及相互关系的本来面貌。
- 结构化程序设计是一种按功能来进行系统需求分析的方法,他的特点有自顶向下 逐步求精 模块化等,它先使用结构化分析方法对系统需求进行分析,然后使用结构化设计方法对软件系统进行整体设计,之后在进行详细设计,最后通过结构化编程方法实现系统。函数是结构化程序设计系统的基本构成单元,每个函数负责一个功能,每个函数都会有一些数据输入,然后经过一系列处理,再输出一些数据。整体流程是作为程序入口的主函数调用其他普通函数,然后普通函数之间再相互调用,完成整个系统功能。
this关键字
- 一个对象一个this
- this是一个变量,是一个引用,this保存当前对象的内存地址,指向自身,this储存在堆内存当中对象的内部
- this只能用在实例方法中
- 哪个对象调用该实例方法,this就代表谁
- 当局部变量和实例变量名称相同时,this不能省略,用于区分二者eg:this.name = name;
- this可以用在构造方法中,也可以省略
- this()用来在一个构造方法中调用类中另一个构造方法,this(参数列表),作用是代码复用,这样使用的话this语句需要是构造器中第一个语句
System.out.println()
- system是一个类名
- out是一个静态变量(对象)
- println()带括号是一个实例方法
- system.out.println(引用);当直接输出一个引用的时候,prinln()方法会自动调用引用.toString(),然后输出引用.toString()的结果
三大特征 – 封装
- 什么是封装
封装实际上就是信息隐藏,利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体,数据被保护在抽象数据类型的内部,尽可能隐藏内部的细节,只保留一些简单的接口使其与外界发生联系。 - 封装的功能
- 封装后的代码外部人员不能随意访问,保证数据的安全
- 屏蔽复杂,暴露简单,对于外部调用的人员来说,不需要关心代码的复杂实现,只需要通过一个简单的入口就可以访问
- 封装的步骤:
- 属性私有化(使用private关键字)
- 对外提供简单的操作入口(一个属性对外提供set, get方法,不带static的实例方法,可在set方法中设立关卡)
- 封装的意义
- 封装降低了程序的耦合度
- 封装提高了程序的扩展性、复用性和重用性
三大特征 – 继承
- 继承作用
- 解决代码臃肿的问题,实现代码的复用
- 有了继承才有了方法覆盖和多态机制
- 继承特点
- java虽然不支持多继承,但又间接继承的效果
- 子类继承父类,除构造方法之外,剩余都可 以继承,但是私有属性不能直接访问,可以通过调用继承来的set get方法访问
- 子类继承父类之后,相当于父类的代码复制到子类中,构造方法除外
- 不是代码有重复就可以使用继承,需要看两个类之间是否可以使用A is a B这种形式表示
- 继承缺点
- 代码耦合度变高
- 父类修改子类受到牵连
方法覆盖和方法重载
- 覆盖overwrite或者override,重载overload
- 构成方法重载条件:
- 在同一个类中
- 方法名相同
- 参数列表不同(个数、顺序、类型)
- 构成方法覆盖条件:
- 继承的方法无法满足子类的业务需求,则需要进行方法覆盖
- 两个类有继承关系
- 有相同的方法名、返回值类型、参数列表
- 子类的方法的访问权限只能更高,不能更低(父类public 子类protected不行)
- 重写之后的方法不能比之前抛出更多异常
- 私有方法不能覆盖,构造方法不能继承也不能覆盖
- 方法覆盖只针对实例方法,静态方法覆盖没有意义
- 方法覆盖后,子类对象执行方法一定是子类重写后的方法
三大特征 – 多态
多态基础语法
- 向上转型upcasting:父类型引用指向子类型对象Animal a = new Cat();
- 向下转型downcasting:父 ----> 子,需要添加强制类型转换符Cat c = (Cat) a;
- 不管向上还是向下转型,二者必须有继承关系,编译才会不报错(对于接口不适用)
- 需要调用执行子类对象中特有的属性和方法,必须向下转型才可以调用
- instanceof运算符:引用 instanceof 类型,结果为true或者false
- 向下转型有风险,容易出现ClassCastException,为避免风险,使用instanceof运算符,可以在程序动态运行阶段判断某个引用指向的对象是否为某一种类型
- 什么是多态:多种形态,多种状态,编译和运行有两种状态,编译期静态绑定,运行期动态绑定Animal a = new Cat();编译时编译器先判断a为Animal类,去Animal类中找eat()方法,结果找到了,编译通过,运行时,底层实际的对象是什么,就自动到该实际对象对应的eat()方法上,这就是多态的使用
- 软件开发原则之一:OCP原则,对扩展开放,对修改关闭
super关键字
- super能出现在实例方法和构造方法中
- super语法同this:
- super.属性名(访问父类的属性)
- super.方法名(实参)(访问父类的方法)
- super(实参)(调用父类的构造方法,比如子类构造方法中需要对父类的私有属性进行初始化赋值,则使用super(实参)把实参通过父类的构造方法赋给父类的私有属性)
- super不能用在静态方法中
- super大部分情况可以省略
- super()只能出现在构造方法第一行,通过当前构造方法调用父类的构造方法,目的是创建子类型对象时先初始化父类型特征
- 如果子类构造方法第一行,既没有this(),又没有super(),默认会有一个super(),表示通过当前子类的构造方法调用父类的无参数构造方法,所以必须保证父类的无参数构造方法存在
- 如果父类没有无参构造方法,则必须在子类写下构造方法,并在第一句写出带有参数的super(实参)方法
- 在构造方法执行过程中虽然不断在向上调用父类的构造方法,但是对象只创建了一个,super(实参)只是为了初始化父类的特征,而不是创建对象,super关键字代表的就是当前对象的那部分父类特征
- super.不能省的情况:
- 子类和父类有同名属性,且需要在子类中访问父类的该属性
- 子类覆盖了父类的方法,如果需要在子类中访问父类的方法,则需要super.方法名来访问父类的方法
- super不是引用,不保存内存地址,super也不指向任何对象,super只代表当前对象内部的那一块父类的特征,不能直接打印super
- super.不仅可以访问属性,也可以访问方法
Object中toString()方法
- toString方法作用是通过调用此方法将一个java对象转换成字符串的形式表示
- 打印引用默认调用toString()方法
- SUN公司开发java语言时建议所有子类都重写该方法
Object中的equals()方法
- “==”用于两基本数据类型时是判断二者值是否相同,用于引用数据类型时是判断是否为同一个对象,所以判断两个对象是否相等时不能用双等号
- Object的equals源码:
- equals方法默认使用的是双等号,两个值相同的对象会输出false,故需要重写equals方法
public boolean equals(Object obj) {
return (this == obj);
}
- 重写equals方法实例:
public Mytime {
private int year;
private int month;
private int day;
public Mytime() {}
public Mytime(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof Mytime)) return false;
if (this == obj) return true;
Mytime t = (Mytime)obj;
return (this.year == t.year && this.month == t.month && this.day == t.day);
return false;
}
}
- equals()方法重新要彻底,把含有引用数据类型属性的obj类equals()方法全部重写
Object的finalize()方法
- finalize()方法只有一个方法体里面没有代码,这个方法是protected修饰的
- 这个方法不需要程序员手动调用,JVM的垃圾回收器负责调用这个方法,不像equals() toString()方法是需要写代码调用,finalize()方法只需要重写,重写完将来会有程序来调用
- finalize()方法的执行时机:当一个java对象即将被垃圾回收器回收的时候,垃圾回收器负责调用
- finalize()方法实际上是SUN公司为java程序员准备的一个时机,垃圾销毁时机,如果希望在对象销毁时机执行一段代码的话,这段代码要写到finalize()方法当中,类似于静态代码块实在类加载时刻执行,并且只执行了一次
- 给对象赋值null,可以把一个java对象变成垃圾
- java中的垃圾回收器不是轻易启动的,垃圾太少或者时间没到种种条件下有可能启动也可能不启动
- System.gc();该静态方法可以建议垃圾回收器启动,只是建议
Object的hashCode()方法
- 这个方法不是抽象方法,带有native关键字,底层调用C++代码
- 该方法返回的是哈希码,实际上是一个java对象的内存地址,经过哈希算法,得出的一个值,所以hashCode()方法的执行结果可以等同看做一个java对象的内存地址
String类
- String类的常用方法
- charAt(int index)返回指定索引的字符
- trim()删去字符串首尾的空白符
- replace(char old, char new)替换字符
- str.toLowerCase()字符串全部小写
- indexOf(String str)返回子串在字符串中首次出现的索引
- equals()判断两个字符串内容是否相同,string类重写了该方法
- toCharArray()把字符串转换为字符数组
- str1 == str2判断两个字符串地址是否相同
- str.isEmpty()判断字符串是空的
- str.contains(char c)判断字符串是否包括某个字符
- public String[] split()把字符串按某种规则分割
- startsWith(String pre)判断字符串是否以某子串开始
- endsWith()同上
- toString()返回字符串本身,因为String类重写了该方法
- String不能被继承,因为String类由final修饰,如果没有final修饰,string类就会有子类,修改string方法,修改字符串的值,违背初衷。
- String类被设计为不可变类原因:
- 字符串通常被用来储存敏感信息,如账号密码等,如果字符串可变就容易被修改,不安全
- 多线程当中只有不可变的值可对象是线程安全的,当一个线程修改了字符串的值,就会创新一个新的对象,不对其他线程的访问产生副作用
- 字符串不可变时,字符串常量池才有意义,他是为了减少同样值的字符串被反复创建,为运行时节省更多的堆内存,如果字符串可变,常量池失去意义,intern方法也没有用了,每次创建字符串时都开辟新的内存空间,占据更多内存。
- 字符串创建两种方式区别:
- String a = “abc”; 这种方式JVM会使用字符串常量池来管理字符串直接量,JVM先检查常量池中是否含有“abc”,若没有则将“abc”存入常量池,然后将其引用赋值给a
- String a = new String(“abc”); 执行时JVM会先将“abc”放入常量池中,然后再创建一个新的String对象,这个对象被保存在堆内存中,堆内存中对象的数据指向常量池中的字符串直接量
- 字符串拼接的几种方式:
- 通过 + :如果是拼接字符串直接量就用加号拼接,编译时编译器会直接优化为一个完整的字符串,和自己直接写一个完整的字符串是一样的,效率很高
- 通过StringBuilder和StringBuffer:如果字符串含有变量则可以通过二者来拼接,区别为StringBuilder不是线程安全的,另一个是
- 通过concat方法:如果是拼接两个字符串且含有变量,可以使用concat方法进行拼接
- 几种拼接方式底层原理:
- 通过加号拼接时如果字符串是变量,编译时编译器会默认采用StringBuilder对其进行优化,即自动创建StringBuilder实例并调用append方法进行拼接,但是如果拼接操作在循环当中则效率很低
- 通过StringBuilder和StringBuffer方法是使用字符串缓冲区,缓冲区的容量在创建对象时确定,并且默认为16,当拼接的字符串长度超过缓冲区容量时,会触发缓冲区的扩容机制,缓冲区扩容为2 * n + 2,频繁扩容会导致性能降低
字符串缓冲区扩容机制字符串缓冲区扩容机制2 - concat方法是先创建一个足以容纳两个字符串的字节数组,然后将这两个字符串拼接到这个数组中,最后将其传化为字符串
- String类重写了toString()和equals()方法
- 比较两个字符串不能使用==,因为字符串是一个类,不是基本数据类型
- 字符串的toString()方法是返回字符串对象本身,equals()方法是判断两个字符串对象的值是否相同
StringBuilder和StringBuffer
- 二者共同点 :代表字符序列可变的字符串对象,他们有共同的父类AbstractStringBuilder,且两个类的构造方法和成员方法也基本相同。当一个StringBuilder对象被创建后,通过StringBuilder提供的append、insert、reverse、setCharAt、setLength等方法可以改变这个字符序列,一旦通过StringBuilder生成需要的字符序列后,可通过toString()方法将其转换为一个String对象。
- 不同点:StringBuffer是线程安全的,StringBuilder是非线程安全的,所以StringBuilder效率较高。
抽象类
- 把具有共同特征的类的共同特征提取出来,形成抽象类,抽象类本身是不存在的,所以抽象类是无法创建对象,也就是无法实例化的
- 抽象类属于引用数据类型
- 抽象类语法:public abstract class 类名{类体};
- 抽象类无法实例化,无法创建对象,所以抽象类是用来被子类继承的
- final和abstract不能联合使用,两个关键字是对立的
- 抽象类的子类可以是抽象类,也可以是非抽象类
- 抽象类虽然无法实例化,但是有构造方法,是提供给子类使用,比如子类的构造方法中默认super()
- 抽象方法是没有实现的方法,没有方法体,public abstract void doSome();分号结束,修饰符列表中有abstract关键字
- 抽象类中不一定有抽象方法,抽象方法只能出现在抽象类中
- 一个非抽象类继承抽象类,必须将抽象类中的抽象方法实现(对抽象方法的覆盖或者重写,叫做对抽象的实现)
- 没有方法体的不一定是抽象方法,Object类中就有很多方法都没有方法体,都是以分号结尾的,例如public native int hashCode();这个方法底层调用了C++写的动态链接库程序,前面修饰符列表中的native代表调用JVM程序
接口
基础语法
- 接口是一种引用数据类型,接口通常提取的都是行为动作(如计算,飞翔等)
- 接口是完全抽象的,抽象类是半抽象的,不能创建对象
- 接口定义语法:【修饰符列表】interface 接口名{};
- 接口支持多继承interface A extends A, B {}
- 接口中只包含两部分:
- 常量
- 抽象方法
interface MyMath {
public static final double PI = 3.1415926;
//接口中的常量的public static final可以省略
public abstract int sum(int a, int b);
//定义抽象方法时public abstract可以省略
}
- 接口中所有元素都是public修饰
- 接口抽象方法不能带有方法体
- 类和类之间叫继承,关键字用extends,类和接口之间叫实现,可以看做是继承,实现用关键字implements完成
- 当一个非抽象的类实现接口的话,必须将接口中所有的抽象方法全部实现(覆盖重写)
public class Test01 {
public static void main (String[] args) {
//多态
MyMath mm = new MyMathImpl();
int result1 = mm.sum(10, 20);
int result2 = mm.sub(20, 10);
System.out.println(result1 + result2);
}
}
interface MyMath {
double PI = 3.1415926;
int sum(int a, int b);
int sub(int a, int b);
}
class MyMathImpl implements MyMath {
//不能省略public,重写父类的方法不能权限更低,接口中都是public方法
public int sum(int a, int b) {
return a + b;
}
public int sub(int a, int b) {
return a - b;
}
}
- 集成和实现都存在:extends关键字在前,implements关键字在后
public class Test01 {
public static void main(String[] args) {
//多态 表面Animal没用
Flyable f = new Cat();
f.fly();
Flyable f2 = new Pig();
f2.fly();
Flyable f3 = new Fish();
f3.fly();
}
}
class Animal {
}
interface Flyable {
void fly();
}
class Cat extends Animal implements Flyable {
public void fly() {
System.out.println("飞猫起飞");
}
}
class Snake extends Animal {
}
//想飞翔就插翅膀这个接口
class Pig extends Animal implements Flyable {
public void fly() {
System.out.println("我是会飞的猪");
}
}
class Fish implements Flyable {
public void fly() {
System.out.println("我是飞鱼");
}
}
类实现接口可以多实现(多继承)
- 一个类可以同时实现多个接口,这种机制弥补了java中不能多继承的出现
- 一个接口如果需要调用另一个接口的方法,需要向下转型
- 接口和接口之间进行强制类型转换时,二者之前没有继承关系也可以强转,编译器没意见,但是运行时还是可能会出现ClassCastException(是SUN公司的骚操作,记住就行)
public static void main(String[] args) {
A a = new D();
B b = new D();
C c = new D();
/*
向下转型B和A没有继承关系也可以转,编译没问题,但是运行出错,记住就行
B b2 = (B)a;
b.m2();
*/
}
if (a instanceof D) {
D d = (D)a;
d.m2();
}
}
interface A {
void m1();
}
interface B {
void m2();
}
interface C {
void m3();
}
class D implements A, B, C {
//类实现接口需要重写接口中的抽象方法,public不能省略
public void m1(){}
public void m2(){}
public void m3(){}
}
接口在开发中的作用
- 接口在开发中的作用类似于多态在开发中的作用。多态:面向抽象编程,不要面向具体编程,降低程序耦合度,提高程序扩展力
pulbic class Master {
public void feed(Dog d) {}
public void feed(Cat c) {}
//如果需要其他宠物此时就需要再加一个方法(修改代码),扩展力太差,违背OCP原则:对扩展开放,对修改关闭
}
------------------------
public class Master {
public void feed (Animal a) {}
//面向Animal父类编程,父类比子类抽象,所以我们叫面向抽象编程,不要面向具体编程,提高程序扩展力
}
- 接口是完全抽象的,面向抽象编程可以改为面向接口编程,有了接口就有了插拔,可插拔表示扩展力很强而不是焊接死的,类似主板和内存条的关系
- 凡是可以使用has a描述的,同意以属性的方法存在(实例变量,属性)
public class Test {
public static void main(String[] args) {
//创建厨师对象,如果之后需要换厨师,只需要该test类中的程序,其他不需要改,因为customer中属性使用的是接口FoodMenu
FoodMenu cooker1 = new ChinaCooker();
//创建顾客对象
Customer customer = new Customer(cooker1); //传入cooker1防止出现空指针异常
//顾客点菜
customer.order();
}
}
//顾客类,顾客has a FoodMenu
public class Customer{
private FoodMenu foodMenu; //封装好习惯
public Customer() {}
public Customer(FoodMenu fooMenu) {
this.foodMenu = foodMenu;
}
// setter getter
public void setFoodMenu(FoodMenu foodMenu) {
this.foodMenu = foodMenu;
}
public FoodMenu getFoodMenu() {
return foodMenu;
}
public void order() {
FoodMenu fm = this.getFoodMenu();
fm.shizichaodan();
fm.mayishangshu();
}
}
public interface FoodMenu {
void shizichaodan(){}
void mayishangshu(){}
}
//中餐厨师,实现餐单上的菜,厨师是接口的实现者
public class ChinaCooker implements FoodMenu{
public void shizichaodan() {
Sytstem.out.println("中餐师傅做的柿子炒蛋");
}
public void shizichaodan() {
Sytstem.out.println("中餐师傅做的蚂蚁上树");
}
}
public class AmercaCooker implements FoodMenu{
public void shizichaodan() {
Sytstem.out.println("西餐师傅做的柿子炒蛋");
}
public void mayishangshu() {
Sytstem.out.println("西餐师傅做的蚂蚁上树");
}
}
- 总结一句话:面向接口编程,可以降低程序耦合度,提高程序扩展力,符合OCP开发原则。接口的使用离不开多态机制(接口 + 多态 可以实现解耦合)。任何一个接口都有调用者和实现者,接口· 满足like a关系的,表示实现关系,比如 cooker like a menu,类实现接口
抽象类和接口的关系
- 不同点:
- 抽象类是半抽象,接口是完全抽象
- 抽象类只能有抽象方法、静态方法、默认方法和私有方法;抽象类则可以有普通方法
- 抽象类可以有普通成员变量也可以有静态常量;接口则只能有静态常量
- 抽象类中有构造方法,它的构造方法不是为了创建对象,而是为了子类调用这些构造器来完成属于抽象类的初始化操作;接口是一种规范,其中没有构造方法和初始化块java初始化块
- 一个类最多有一个直接父类,包括抽象类;一个类可以实现多个接口弥补java单继承的不足
- 共同点:
- 二者都不能被实例化,位于继承树的顶端,被其他类实现和继承
- 都可以有抽象方法,实现接口或继承抽象类的普通子类必须实现这些抽象方法
集合
集合概述
- 集合实际上是一个容器,可以来容纳其他类型的数据,数组就是一种集合
- 在实际开发中,假设连接数据库,数据库当中有十条记录,那么假设把这十条记录查询出来,在java程序中会将十条数据封装成十个java对象,然后将这些对象放到某一个集合当中,把集合传到前端,然后遍历集合,将一个一个数据展现出来
- 集合不能直接存储基本数据类型,另外集合也不能直接存储java对象,集合当中存储的都是java对象的内存地址,或者说集合中存储的是引用,
list.add(100); // 自动装箱Integer
- 注意集合在java中本身是一个容器,是一个对象,集合中任何时候存储的都是“引用”
- 在java中不同集合底层会对应不同的数据结构,往不同集合中存储元素,等于将数据放到了不同的数据结构当中。数据结构就是数据存储结构,不同数据结构,数据存储的方式不同,比如数组、二叉树、链表、哈希表…
- 集合在java JDK下的java.util.*下,所有集合类和集合接口都在java.util包下
集合继承结构
- 集合分为两大类:
- 单个方式存储元素,这一类集合中超级父接口为:java.util.Collection;
- 键值对的方式存储元素,这一类集合中超级父接口为:java.util.Map;
Collection集合结构
Map集合继承结构
总结
- ArrayList:底层是数组
- LinkedList:底层是双向链表
- Vector:底层是数组,线程安全的,效率较低,使用较少
- HashSet:底层是HashMap,放到HashSet集合中的元素等同于放到HashMap集合key部分了
- TreeSet:底层是TreeMap,放到了TreeSet集合中的元素等同于放到了TreeMap的key部分
- HashMap:底层是哈希表
- HashTable:底层也是哈希表,只不过线程安全的,效率较低,使用较少
- Properties:是线程安全的,key和value只能存储String类型
- TreeMap底层是二叉树,TreeMap集合的key可以自动按照大小顺序排序
- List集合存储元素的特点:有序可重复
- Set集合存储元素的特点:无序不可重复
- SortedSet集合储存元素特点:首先是无序不可重复,但是集合中的元素可排序
- Map集合的key就是Set集合,在Set集合中放数据,实际上放到了Map集合的key部分
Collection集合常用方法
- 不使用泛型,可以存储Object的所有子类型,使用泛型之后,Collection中只能存储某个具体的类型。Collection中什么都能存,只要是Object的子类型就行,集合中不能存储基本数据类型,也不能存储java对象,只是存储java对象的内存地址
boolean add(Object a);
int size();
void clear();
boolean contains(Object a);
boolean remove(Object a);
boolean isEmpty();
Object[] toArray(); // 调用这个方法把集合转成数组
Collection集合迭代
- 以下方法在Map集合中不可用
- 迭代三步走:
- 获取集合对象的迭代器对象Iterator
Iterator it = c.iterator();
- 通过以上获取的迭代器对象开始迭代。迭代器对象Iterator中有两个方法:(1)
boolean hasNext() // 如果仍有元素可以迭代,则返回true
(2)Object next() // 返回迭代器的下一个元素
泛型
泛型概述
- 泛型这种语法机制只在编译阶段起作用,是给编译器参考的,运行阶段没用
- 使用泛型List之后,表示List集合中只能存储Animal类型的数据
- 用泛型来指定集合中存储的数据类型
- 使用泛型之后,集合中的元素数据更加统一了
- 泛型好处:
- 集合中元素类型更统一
- 从集合中取出的元素类型是泛型指定的类型,不需要进行大量的向下转型
public class GenericTest01 {
public static void main(String[] args) {
List<Animal> mylist = new ArrayList<Animal>();
Cat c = new Cat();
Bird b = new Bird();
mylist.add(c);
mylist.add(b);
// 加上泛型表示迭代器迭代的是Animal类型
Iterator<Animal> it = mylist.iterator();
while (it.hasNext()) {
// 使用泛型之后,每次迭代返回的数据都是Animal类型
Animal a = it.next();
if (a instanceof Cat) {
Cat c2 = (Cat) a;
c2.catchMouse();;
}
a.move();
}
}
}
class Animal {
public void move() {
System.out.println("move!");
}
}
class Cat extends Animal {
public void catchMouse() {
System.out.println("catch mouse!");
}
}
class Bird extends Animal {
public void fly() {
System.out.println("flying!");
}
}
类型自动推断
- JDK8之后推出自动类型推断机制(又称为钻石表达式)
public class GenericTest02 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("http://www.baidu.com");
list.add("http://www.jingdong.com");
list.add("http://www.taobao.com");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
String newString = s.substring(7);
System.out.println(newString);
}
}
}
自定义泛型
- <>尖括号中的St为标识符,随便写就行
- 不使用泛型则默认返回的是Object类型
public class GenericTest03<St> {
public void doSome(St s) {
System.out.println(s);
}
public static void main(String[] args) {
GenericTest03<String> s = new GenericTest03<>();
s.doSome("abs");
}
}
反射
反射机制概述
- 通过hava语言中的反射机制可以直接操作字节码文件,即class文件
- 反射机制的相关类在java.lang.reflect.*
- 反射机制中的类:
- java.lang.Class 代表整个字节码
- java.lang.reflect.Method 代表字节码中的方法字节码
- java.lang.reflect.Constructor 代表字节马当中的构造方法字节码
- java.lang.reflect.Field 代表字节码中的属性字节码
- 字节码文件装载到JVM中的时候只装载一份
获取java.lang.Class实例三种方法
Class.forName(完整包名字符串);
- 该方法为静态方法
- 方法的参数是一个字符串
- 字符串需要的是一个完整类名
- 完整类名必须带有报名,java.lang包也不能省略
- 任何一个类的
getClass()
方法 - java语言中任何一种类型包括基本数据类型都有.class属性
public class ReflectTest01 {
public static void main(String[] args) {
// c1代表String.class文件,或者说c1代表String类型
Class c1 = null;
try {
c1 = Class.forName("java.lang.String");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
String s = "abc";
Class c2 = s.getClass();
System.out.println(c1 == c2);
Class c3 = String.class;
System.out.println(c2 == c3);
}
}
通过反射实例化对象
- newInstance()方法会调用类的无参数构造方法,完成对象的创建,必须保证无参数构造是存在的
public class ReflectTest02 {
public static void main(String[] args) {
User s1 = new User();
System.out.println(s1);
Class s2 = null;
Object c = null;
try {
s2 = Class.forName("bean.User");
c = s2.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} finally {
System.out.println(c);
}
}
}
通过读属性文件实例化对象
- java代码写一遍,在不改变java源代码的基础上,可以做到对不同对象的实例化,非常灵活,符合OCP原则
public class ReflectTest03 {
public static void main(String[] args) throws Exception {
// 通过IO流读取ClassInfo.properties文件
FileReader reader = new FileReader("D:/Project/src/ClassInfo.properties");
// 创建属性类对象Map
Properties pro = new Properties(); // key和value都是String
// 加载
pro.load(reader);
// 关闭流
reader.close();
// 通过key获取value
String className = pro.getProperty("className");
System.out.println(className);
//通过反射机制创建对象
Class c = Class.forName(className);
Object o = c.newInstance();
System.out.println(o);
}
}
使用forName()方法执行静态代码块
- 这个方法的执行会导致类加载,如果希望一个类的静态代码块执行,其他代码一律不执行,可以使用Class.forName(“完整类名”),类加载时,静态代码块执行
public class ReflectTest04 {
public static void main(String[] args) {
try {
Class c = Class.forName("reflect.myClass");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class myClass {
static {
// 静态代码块在类加载的时候执行,并且只执行一次
System.out.println("静态代码块执行!");
}
}
获取类途径下文件的绝对路径
- 之前的路径缺点:移植性差,在IDEA中默认当前路径是project的根,离开了IDEA就没有用了
- 以下为一种通用方式,即使换代码位置也能通用
- 前提是文件必须在类路径下,即src下
- src是类的根路径
Thread.currentThread().getContextClassLoader().getResource("").getPath();
public class AboutPath {
public static void main(String[] args) throws Exception {
// Thread.currentThread() 当前线程对象
// getContextClassLoader() 是线程对象的方法,可以获取当前线程的类加载器对象
// getResource("") 是类加载器对象的方法,默认从类的根路径下加载资源
String path = Thread.currentThread().getContextClassLoader().getResource("ClassInfo.properties").getPath();
System.out.println(path);
String path2 = Thread.currentThread().getContextClassLoader().getResource("reflect/ReflectTest01.class").getPath();
System.out.println(path2);
}
}
直接返回流
public class IoPropertiesTest {
public static void main(String[] args) throws Exception {
// 通过类路径文件获取绝对路径
// String path = Thread.currentThread().getContextClassLoader().getResource("ClassInfo.properties").getPath();
// FileReader reader = new FileReader(path);
// 直接以流的形式返回
InputStream reader = Thread.currentThread().getContextClassLoader().getResourceAsStream("ClassInfo.properties");
Properties pro = new Properties();
pro.load(reader);
reader.close();
String className = pro.getProperty("className");
System.out.println(className);
}
}
资源绑定器
- java.util包下提供了一个资源绑定器,便于获取属性配置文件的内容,使用这种方式的时候,属性配置文件xxx.properties必须放在类路径下
- 资源绑定器只能绑定xxx.properties文件,文件扩展名必须是properties,并且在写路径的时候,路径后面的扩展名不能写
public class ResourceBundleTest {
public static void main(String[] args) {
ResourceBundle bundle = ResourceBundle.getBundle("ClassInfo");
String className = bundle.getString("className");
System.out.println(className);
}
}
类加载器
- 类加载器是专门负责加载类的命令/工具
- JDK中自带三个类加载器。分别为:启动类加载器、扩展类加载器、应用类加载器
- 假设一段代码:String s = “abc”;代码在开始执行之前,会将所需要的类全部加载到JVM当中,通过类加载器加载,看到以上代码类加载器会找String.class文件,找到就加载。
- 上述加载流程为:
- 通过“启动类加载器”加载。启动类加载器专门加载“C:\Program Files\Java\jdk 1.8.0_101\jre\lib\rt.jar”,该jar包下全是最核心的类库
- 如果启动类加载器加载不到时, 会通过扩展类加载器加载。扩展类加载器专门加载“C:\Program Files\Java\jdk 1.8.0_101\jre\lib\ext/*.jar”
- 如果扩展类加载器没有加载到,那么会通过应用类加载器加载。应用类加载器专门加载classpath中的类
双亲委派机制
- java为了保证类加载的安全,使用了双亲委派机制,优先从启动类加载器中加载,这个称为“父”, “父”无法加载到,再从扩展类加载器中加载,这个称为“母”。双亲委派,如果都加载不到,才会考虑从应用类加载器中加载,直到加载到为止
获取Field
public class Student {
public int no;
private String name;
protected int age;
boolean sex;
public static final double MATH_PI = 3.1415926;
}
public class ReflectTest05 {
public static void main(String[] args) throws Exception {
Class c = Class.forName("bean.Student");
// 获取类中所有public修饰的field
String fullName = c.getName();
System.out.println("全类名:" + " " + fullName);
String simpleName = c.getSimpleName();
System.out.println("简类名:" + " " + simpleName);
System.out.println("====================");
// 只能获取public修饰的field
Field[] fields = c.getFields();
System.out.println(fields.length);
Field f = fields[0];
System.out.println(f.getName());
System.out.println("====================");
// 获取所有field名字
Field[] fs = c.getDeclaredFields();
for(Field fd : fs) {
System.out.println(fd.getName());
}
System.out.println("====================");
// 获取所有field类型
for(Field fd : fs) {
Class fType = fd.getType();
String typeName = fType.getName();
System.out.println(typeName);
}
System.out.println("====================");
// 获取所有field修饰符
for(Field fd : fs) {
// 返回的修饰符是一个数字,该数字是这个修饰符的代号!
int fdModifier = fd.getModifiers();
System.out.println(fdModifier);
// 将代号转为对应的修饰符名称
String modifier = Modifier.toString(fdModifier);
System.out.println(modifier);
}
}
}
反编译field
public class ReflectTest06 {
public static void main(String[] args) throws Exception {
StringBuilder sb = new StringBuilder();
Class StudentClass = Class.forName("bean.Student");
sb.append(Modifier.toString(StudentClass.getModifiers()) + " class " + StudentClass.getSimpleName() + " {\n");
Field[] fields = StudentClass.getDeclaredFields();
for(Field field : fields) {
sb.append("\t");
sb.append(Modifier.toString(field.getModifiers()) + " " + field.getType().getName() + " " + field.getName());
sb.append(";");
sb.append("\n");
}
sb.append("}");
System.out.println(sb.toString());
}
}
通过反射机制访问对象属性
- ClassInfo.properties内容:
访问public属性
public class ReflectTest07 {
public static void main(String[] args) throws Exception {
Student sd = new Student();
sd.no = 123;
System.out.println(sd.no);
InputStream reader = Thread.currentThread().getContextClassLoader().getResourceAsStream("ClassInfo.properties");
Properties pro = new Properties();
pro.load(reader);;
reader.close();
String className = pro.getProperty("className");
String filedName = pro.getProperty("fieldName");
String noValue = pro.getProperty("noValue");
Class studentClass = Class.forName(className);
Object obj = studentClass.newInstance();
// Student s = (Student) obj;
Field noField = studentClass.getDeclaredField(filedName);
System.out.println(noField.get(obj));
noField.set(obj, Integer.valueOf(noValue));
System.out.println(noField.get(obj));
}
}
访问私有属性
- 打破封装
Field nameField = studentClass.getDeclaredField(fieldName1);
// 反射缺点:打破封装
nameField.setAccessible(true);
nameField.set(obj, "jack");
System.out.println(nameField.get(obj));
可变长度参数
- 可变长度参数在参数列表中必须在最后一个且只能有一个
- 可以将可变长度参数当作一个数组来看待,也可以直接传一个数组
public class ArgsTest {
public static void main(String[] args) {
test();
test(1, 2);
test(1, 2, 3, 4, 5);
test1(2, "a","b", "c");
test1(2, "a","b", "c", "def");
// 直接传一个数组
String[] array = {"abc", "def", "ghi"};
test1(10, array);
}
public static void test(int... args) {
System.out.println("可变长度参数方法执行");
}
public static void test1(int a, String... args) {
for(String arg : args) {
System.out.println(arg);
}
System.out.println("双类型参数方法执行");
}
}
反射获取Method
public class ReflectTest08 {
public static void main(String[] args) throws Exception {
Class log = Class.forName("service.UserService");
Method[] methods = log.getDeclaredMethods();
for (Method method : methods) {
// 获取method的修饰符
System.out.println(Modifier.toString(method.getModifiers()));
// 获取method类型
Class type = method.getReturnType();
String typeName = type.getName();
System.out.println(typeName);
// 获取method名字
System.out.println(method.getName());
// 获取method参数列表
Class[] parameterTypes = method.getParameterTypes();
for(Class parameterType : parameterTypes) {
System.out.println(parameterType.getName());
}
}
}
}
public class UserService {
/**
* 登录方法
* @param name 用户名
* @param password 密码
* @return 返回true登陆成功,false登录失败
*/
public boolean login(String name, String password) {
return "admin".equals(name) && "123".equals(password);
}
/**
* 退出的方法
*/
public void logout() {
System.out.println("系统安全退出");
}
}
反编译Method
public class ReflectTest09 {
public static void main(String[] args) throws Exception {
Class service = Class.forName("service.UserService");
StringBuilder sb = new StringBuilder();
sb.append(Modifier.toString(service.getModifiers()) + " class " + service.getSimpleName());
sb.append(" {\n");
Method[] methods = service.getDeclaredMethods();
for (Method method : methods) {
sb.append("\t");
sb.append(Modifier.toString(method.getModifiers()) + " " + method.getReturnType().getSimpleName() + " " + method.getName());
sb.append("(");
Class[] parameterTypes = method.getParameterTypes();
for (Class parameterType : parameterTypes) {
sb.append(parameterType.getSimpleName());
sb.append(",");
}
// 防止无参方法丢失括号,需进行判断
if (sb.charAt(sb.length() - 1) == ',') {
sb.deleteCharAt(sb.length() - 1);
}
sb.append("){}");
sb.append("\n");
}
sb.append("}");
System.out.println(sb);
}
}
通过反射机制调用某个对象方法☆
public class ReflectTest10 {
public static void main(String[] args) throws Exception {
Class c = Class.forName("service.UserService");
Object obj = c.newInstance();
Method method = c.getDeclaredMethod("login", String.class, String.class);
Object retValue = method.invoke(obj, "admin", "123");
System.out.println(retValue);
}
}
反编译Constructor
public class Vip {
int no;
String name;
String birth;
boolean sex;
public Vip() {}
public Vip(int no) {
this.no = no;
}
public Vip(int no, String name) {
this.no = no;
this.name = name;
}
public Vip(int no, String name, String birth) {
this.no = no;
this.name = name;
this.birth = birth;
}
public Vip(int no, String name, String birth, boolean sex) {
this.no = no;
this.name = name;
this.birth = birth;
this.sex = sex;
}
@Override
public String toString() {
return "Vip{" +
"no=" + no +
", name='" + name + '\'' +
", birth='" + birth + '\'' +
", sex=" + sex +
'}';
}
}
public class ReflectTest11 {
public static void main(String[] args) throws Exception{
Class c = Class.forName("bean.Vip");
StringBuilder s = new StringBuilder();
s.append(Modifier.toString(c.getModifiers()));
s.append(" class ");
s.append(c.getSimpleName());
s.append(" {\n");
Constructor[] constructors = c.getDeclaredConstructors();
for (Constructor constructor : constructors) {
s.append("\t");
s.append(c.getSimpleName());
s.append("(");
Class[] parameterTypes = constructor.getParameterTypes();
for (Class parameterType : parameterTypes) {
s.append(parameterType.getSimpleName());
s.append(",");
}
if (parameterTypes.length > 0) {
s.deleteCharAt(s.length() - 1);
}
s.append(")\n");
}
s.append("}");
System.out.println(s.toString());
}
}
反射机制调用构造方法
public class ReflectTest12 {
public static void main(String[] args) throws Exception {
Class c = Class.forName("bean.Vip");
Constructor constructor = c.getDeclaredConstructor(int.class, String.class, String.class, boolean.class);
Object obj = constructor.newInstance(1, "通信一班", "1997-09-23", true);
System.out.println(obj);
}
}
获取父类和父类接口
public class ReflectTest13 {
public static void main(String[] args) throws Exception {
Class c = Class.forName("java.lang.String");
Class superClass = c.getSuperclass();
System.out.println(superClass.getName());
Class[] interfaces = c.getInterfaces();
for (Class in : interfaces) {
System.out.println(in.getName());
}
}
}
注解概述
- 注解或者叫注释,英文单词Annotation
- 注解是一种引用数据类型。编译之后也是生成xxx.class文件
自定义注解
[ 修饰符列表 ] @interface 注解类型名{ }
注解使用方式
- 可以用 @注解类型名
- 注解可以出现在类上、属性上、方法上、变量上等…注解还可以出现在注解类型上
JDK内置注解
- Deprecated 用@Deprecated注解的程序元素,不鼓励程序员使用这样的元素,通常是因为他很危险或存在更好的选择
- Override 表示一个方法声明打算重写超类的另一个方法声明
- SuppressWarning 指示应该在注释元素(以及包含在该注释元素中的所有程序元素)中取消显示指定的编译器警告
Override注解
- 该注解是给编译器参考的,和运行阶段没有关系
- 凡是java中的方法带有这个注解的,编译器都会进行编译检查,如果这个方法不是重写父类的方法,编译器报错
- 标识性注解,给编译器参考的,编译期看到方法上带有这个注解的,会自动检查该方法是否重写了父类的方法,如果没有重写则报错
Deprecated注解
- 这个注释向程序员传达信息:该方法已过时,有更好的解决方案
- 该注解java源码中默认可以被反射机制通过字节码文件读到
元注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
- 上面的注解说明了只能出现在方法上
- Target注解成为元注解(用来标注注解的注解)
- 常见元注解:Target、Retention
- Target注解用来标注“注解类型”的“注释”,这个Target注解,用来表示“被标注的注解”可以出现在哪些位置上
- Retention注解用来标注“被标注的注解”最终保存在哪里
@Retention(RetentionPolicy.SOURCE)表示该注解只保留在java源文件中,编译生成的class文件中没有
@Retention(RetentionPolicy.CLASS)表示该注解保存在class文件中
@Retention(RetentionPolicy.RUNTIME)表示该注解保存在class文件中并且可以被反射机制读取到
注解中定义属性
- 如果一个注解当中有属性,必须给该属性赋值
- 当唯一属性名叫value时,赋值时value=可以省略
/**
* 注解中的属性,看着像是方法,其实是属性
*/
public @interface MyAnnotation {
String name();
String age() default "24";
}
public class AnnotationTest01 {
@MyAnnotation(name = "ylx", age = "12")
public static void m() {
}
}
注解当中属性类型
- byte short int long float double boolean char String Class 枚举类型以及以上每一种的数组形式
- 如果数组中只有一个元素,大括号可以省略
反射机制获取注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
// 属性
String pos() default "上海宝山";
}
@MyAnnotation
public class MyAnnotationTest {
}
public class ReflectAnnotationTest {
public static void main(String[] args) throws Exception {
Class c = Class.forName("annotation.annotation2.MyAnnotationTest");
System.out.println(c.isAnnotationPresent(MyAnnotation.class)); // true
if (c.isAnnotationPresent(MyAnnotation.class)) {
MyAnnotation myAnnotation = (MyAnnotation) c.getAnnotation(MyAnnotation.class);
String value = myAnnotation.pos();
System.out.println(value); // 上海宝山
}
}
}
反射机制获取注解对象属性的值
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
// 属性
int age();
String name();
}
public class MyAnnotationTest {
@MyAnnotation(age = 23, name = "zsq")
public void doSome() {
}
}
public class ReflectAnnotationTest {
public static void main(String[] args) throws Exception {
Class c = Class.forName("annotation.annotation2.MyAnnotationTest");
Method method = c.getDeclaredMethod("doSome");
MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class);
System.out.println(myAnnotation.age());
System.out.println(myAnnotation.name());
}
}
注解在开发中的作用
- 假如有一个注解,叫做@id,这个注解只能出现在类上面,当这个类上有这个注解的时候,要求这类中必须有一个int类型的id属性,如果没有这个属性就报异常,如果有这个属性则正常执行
public class NotFoundIdException extends RuntimeException {
public NotFoundIdException() {}
public NotFoundIdException(String s) {
super(s);
}
}
@Id
public class User {
// int id;
String name;
String password;
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Id {
}
public class Test {
public static void main(String[] args) throws Exception {
Class userClass = Class.forName("annotation.annotation3.User");
// 判断该类上是否有Id注解
boolean isOk = false;
if (userClass.isAnnotationPresent(Id.class)) {
Field[] fields = userClass.getDeclaredFields();
for(Field field : fields) {
if ("id".equals(field.getName()) && field.getType().getName() == "int") {
isOk = true;
break;
}
}
if (!isOk) {
throw new NotFoundIdException("没有id属性,不合法!");
}
}
}
}
- java程序出现不正常情况,这种情况称为异,java把异常信息打印在控制台上,供程序员参考。异常的作用是增强程序健壮性
- java中异常以类的形式存在,每一个异常类都可以创建异常对象
- 异常的继承结构:
- Object
- throwable
- 2下有error和exception两个子类,不管是错误还是异常都是可以抛出的,所有错误只要发生,Java程序只有一个结果就是中止程序的执行,推出JVM,错误是不能处理的。
- (1)exception直接子类
(2)RuntimeException
分别称为编译时异常和运行时异常,常见的RuntimeException有ClassCastException、NullPointerException等等 - 编译时异常并不是在编译阶段发生的异常,二是表示必须在编写程序的时候预先对这种异常进行处理,如果不处理编译器会报错,编译时异常又称为受控异常或受检异常(CheckedException);运行时异常在编写程序阶段可以处理也可以不处理。
- 所有异常都发生在运行阶段
- java异常处理两种方式:
- 在方法声明的位置上使用throws关键字抛给上一级,如果异常一直上抛到main方法,main方法抛给JVM,则程序终止
- 使用try…catch语句进行异常的捕捉
- 下面代码中new文件流时源码抛出FileNotFoundException,该异常为编译时异常,所以需要预处理,要么在m1后使用throws抛给main方法,要么在m1中使用异常捕捉,代码中是抛给main方法,main方法中使用异常捕捉,一般不建议在main方法中使用throws关键字上抛,建议进行一场捕捉,增强程序健壮性
public class ExceptionTest {
public static void main(String[] args) {
try {
m1();
} catch (ClassNotFoundEception e) {
System.out.println("文件删除或不存在!");
}
}
public static void m1 throws FileNotFoundException() {
new FileInputStr
}
}
- 只要异常没有捕捉采用上报的方式,则此方法后续的代码不会执行,try语句块中某一行出现异常,该行后面的代码不会执行,try…catch捕捉异常之后,后续代码可以执行。
- 一个方法方法体中异常上报的话,方法就结束了
- catch后面小括号中的异常类型可以是具体的,也可以是父类,比如Exception,catch写多个的时候,从上到下必须遵循从小到大
- JDK8新特性:catch后小括号可以用“||”来同时写多种异常
- 异常两种方法:
- getMessage()获取异常的简单描述信息
- printStackTrace()打印异常堆栈信息,java后台打印时采用了异步线程的方式,异常信息追踪信息,从上往下一行一行看,SUN写的代码不用看,问题出在自己编写的代码中
finally语句
- finally子句中的代码是最后执行的,并且是一定会执行的,即使try语句块中的代码出现了异常
- finally子句必须和try一起出现,不能单独编写
- try中局部变量finally中无法使用,应该声明在try外面
public class ExceptionTest01 {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("D://XXX");
}catch (FileNotFoundException e) {
e.printStackTrace();
}finally {
if (fis != null) {
try {
fis.close();
}catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
- try和finally可以连用,没有catch
- 在try中使用System.exit()退出JVM虚拟机的话,finally语句就不执行了
异常经典题
public class ExceptionTest01 {
public static void main(String[] args) {
int result = m();
System.out.println(result);
}
public static void m() {
int i = 100;
try {
return i;
}finally {
i++;
}
}
}
final finally finalize区别
- final关键字:
- final修饰的类无法继承
- final修饰的方法无法覆盖
- final修饰的变量不能重新赋值
- finally关键字
- 和try一起联用
- finally语句块中的代码是必须执行的
- finalize标识符:是一个Object类中的方法名,这个方法是由垃圾回收器调用的
- 编写一个类继承Exception或者RuntimeException
- 编写两个构造方法,一个有参数,一个无参数
public class MyException extends RuntimeException {
public MyException() {
}
public MyException(String s) {
super(s);
}
}
public class ExceptionTest02 {
public static void main(String[] args) {
MyException me = new MyException("字符串为空");
me.printStackTrace();
String msg = me.getMessage();
System.out.println(msg);
}
}
概述
- 进程是一个应用程序,是一个软件
- 线程是一个进程中的执行场景\执行单元,一个进程可以启动多个线程
- java中之所以有多线程机制,目的就是为了提高程序的处理效率
- main方法结束说明主线程结束,其他线程可能还在执行
- 多核cpu电脑可以做到真正的多线程并发,单核cpu不能,但可以给人“多线程并发”的感觉,单核cpu频繁切换线程给人感觉多个事情在做
进程和线程的关系
- 进程A和进程B的内存独立不共享,但线程A和线程B堆内存和方法区内存共享,栈内存独立,一个线程一个栈
分析程序多少线程
- 该程序只有一个主线程主栈,没有启动分支栈,没有启动分支线程,所以这个只有一个主线程
public class ThreadTest01 {
public static void main(String[] args) {
System.out.println("main begin");
m1();
System.out.println("main end");
}
public static void m1() {
System.out.println("m1 begin");
m2();
System.out.println("m1 end");
}
public static void m2() {
System.out.println("m2 begin");
m3();
System.out.println("m2 end");
}
public static void m3() {
System.out.println("m3 execute");
}
}
实现线程的方式一
- 编写一个类,直接继承java.lang.Thread,重写run方法。new一个线程对象,调用线程对象的start()方法来启动线程
- start()方法作用:启动一个分支线程,在JVM中开辟一个新的栈空间,只要新的栈空间开出来,start()方法就结束了,线程启动成功
- 启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)。
- run方法在分支栈的栈底部,main方法在主栈的栈底部,run和main是平级的
public class ThreadTest02 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
for(int i = 0; i < 1000; ++i) {
System.out.println("main thread--->" + i);
}
}
}
class MyThread extends Thread {
@Override
public void run() {
// 在这里编写程序,这段程序运行在分支线程中
for (int i = 0; i < 1000; ++i) {
System.out.println("other thread--->" + i);
}
}
}
- 如果没有调用thread的start方法,直接调用内部的run方法,则不是多线程并发
实现线程的方式二(常用方法)
- 编写一个类,实现java.lang.Runnable接口,实现run方法
- 将该类作为参数传进去,变为线程对象
public class ThreadTest03 {
public static void main(String[] args) {
// 创建一个可运行的对象
MyRunnable r = new MyRunnable();
// 将可运行的对象分装成一个线程对象
Thread t = new Thread(r);
// 启动线程
t.start();
for (int i = 0; i < 1000; ++i) {
System.out.println("主线程--->" + i);
}
}
}
// 这并不是一个线程类,是一个可运行的类,还不是一个线程
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; ++i) {
System.out.println("分支线程--->" + i);
}
}
}
线程生命周期
- 四个状态:新建状态、就绪状态、运行状态、死亡状态
获取线程的名字
- 通过
thread.getName()
来获取名字 - 通过
thread.setName("名字")
来设置线程名字 - 默认名字为Thread-0、Thread-1等等
public class ThreadTest05 {
public static void main(String[] args) {
// 创建线程对象
MyThread2 t = new MyThread2();
// 设置线程的名字
// t.setName("tttt");
// 获取线程的名字
String tName = t.getName(); // 默认为Thread-0
System.out.println(tName);
}
}
class MyThread2 extends Thread {
@Override
public void run() {
for(int i = 0; i < 100; ++i) {
System.out.println("分支线程--->" + i);
}
}
}
获取当前线程对象
- 获取当前线程对象
Thread t = Thread.currentThread();
返回值t就是当前线程 - 上述代码出现在哪个方法里,就是哪个线程
public class ThreadTest05 {
public static void main(String[] args) {
Thread currentThread = Thread.currentThread();
System.out.println(currentThread.getName());
// 创建线程对象
MyThread2 t1 = new MyThread2();
// 设置线程的名字
t1.setName("t1线程");
// 获取线程的名字
String tName = t1.getName(); // 默认为Thread-0
System.out.println(tName);
MyThread2 t2 = new MyThread2();
t2.setName("t2线程");
System.out.println(t2.getName());
t1.start();
t2.start();
}
}
class MyThread2 extends Thread {
@Override
public void run() {
for(int i = 0; i < 100; ++i) {
Thread currentThread = Thread.currentThread();
System.out.println(currentThread.getName() + "--->" + i);
}
}
}
线程的sleep方法
static void sleep(long millis);
- 静态方法:Thread.sleep(100);
- 参数为毫秒
- 作用:让当前线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片,让给其他线程使用
- sleep方法为静态方法,不能用对象来调用,只会让所在线程休眠
public class ThreadTest07 {
public static void main(String[] args) throws InterruptedException {
Thread t = new MyThread3();
t.setName("t");
t.start();
// 出现在main方法中,就让main线程休眠
t.sleep(5000); // 在执行的时候还是会转换成:Thread.sleep(5000);
System.out.println("hello world");
}
}
class MyThread3 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; ++i) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
终止线程的休眠
- run()方法的异常不能throws只能try catch,因为run()方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常
- 通过打标记的方式
public class ThreadTest10 {
public static void main(String[] args) {
MyRunnable4 r = new MyRunnable4();
Thread t = new Thread(r);
t.setName("t1");
t.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
r.run = false;
}
}
class MyRunnable4 implements Runnable {
boolean run = true;
@Override
public void run() {
for (int i = 0; i < 10; ++i) {
if(run) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
// return之前可以做一些操作如保存等
return;
}
}
}
}
线程调度概述
- 抢占式调度模型(Java采用的方式)
哪个线程的优先级较高,抢到CPU时间片的概率就高一些 - 均分式调度模型
平均分配CPU时间片,每个线程占有的CPU时间片时间长度一样
- 线程调度实例方法
void setPriority(int newPriority);
设置线程优先级void join();
获取线程优先级,最低优先级1,最高10,默认优先级5void join();
合并线程- 线程调用静态方法
static void yield();
让位方法,使线程从运行状态到就绪状态,让位之后再抢到概率也会低一些
线程优先级代码
public class ThreadTest11 {
public static void main(String[] args) {
Thread.currentThread().setPriority(1);
// System.out.println("最高优先级" + Thread.MAX_PRIORITY);
Thread currentThread = Thread.currentThread();
// System.out.println(currentThread.getName() + "线程默认优先级" + currentThread.getPriority());
Thread t = new Thread(new Myrunnable5());
t.setPriority(10);
t.setName("t1");
t.start();
// 优先级较高的只是抢到CPU时间片相对多一些,大概率更偏向于优先级较高的
for (int i = 0; i < 1000; ++i) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
class Myrunnable5 implements Runnable {
@Override
public void run() {
// System.out.println(Thread.currentThread().getName() + "线程默认优先级" + Thread.currentThread().getPriority());
for (int i = 0; i < 1000; ++i) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
线程让位代码
public class ThreadTest12 {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable6());
t.setName("t1");
t.start();
for (int i = 0; i < 10000; ++i) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
class MyRunnable6 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10000; ++i) {
if(i % 100 == 0) {
Thread.yield(); // 当前线程让一下,让给主线程
}
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
线程合并代码
- main over总是最后一个输出
- join()方法并不是栈合并
public class ThreadTest13 {
public static void main(String[] args) {
System.out.println("main begin");
Thread t = new Thread(new MyRunnable7());
t.setName("t1");
t.start();
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main over");
}
}
class MyRunnable7 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10000; ++i) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
多线程下的安全问题
- 不安全的三个条件:
- 多线程并发
- 有共享数据
- 共享数据有修改的行为
- 解决线程安全问题:线程排队执行(不能并发),这种机制称为线程同步机制。线程同步就是线程排队,会牺牲一部分效率,但保证数据安全
- 异步编程模型:线程t1和线程t2各自执行各自的,谁也不需要等谁,其实就是多线程并发,效率较高
- 同步编程模型:两个线程之间会发生等待关系,效率较低
不安全代码
public class Account {
private String actNo;
private double balance;
public Account() {
}
public Account(String actNo, double balance) {
this.actNo = actNo;
this.balance = balance;
}
public String getActNo() {
return actNo;
}
public void setActNo(String actNo) {
this.actNo = actNo;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public void withdraw(double money) {
// 取之前的余额
double before = this.getBalance();
// 取之后余额
double after = before - money;
// 假如t1执行到这里之前,t2线程进入withdraw方法调用getBalance()显示余额仍未1w
try {
// 睡眠1s模拟网络延迟
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
}
}
public class AccountThread extends Thread{
private Account act;
public AccountThread(Account act) {
this.act = act;
}
public void run() {
// run方法表示取款操作
double money = 5000;
act.withdraw(money);
System.out.println(Thread.currentThread().getName() + "对账户" + act.getActNo() + "取款成功,余额为" + act.getBalance());
}
}
public class Test {
public static void main(String[] args) {
Account act = new Account("act-001", 10000);
Thread t1 = new AccountThread(act);
Thread t2 = new AccountThread(act);
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
}
synchronized方法
public class Account {
private String actNo;
private double balance;
public Account() {
}
public Account(String actNo, double balance) {
this.actNo = actNo;
this.balance = balance;
}
public String getActNo() {
return actNo;
}
public void setActNo(String actNo) {
this.actNo = actNo;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public void withdraw(double money) {
// 以下这几行代码必须是线程排队的,不能并发
// 一个线程把这里的代码全部执行结束之后,另一个线程才能进来
/*
线程同步机制的语法:
synchronized() {
// 线程同步代码块
}
synchronized后面小括号中传的这个数据是相当关键的,
这个数据必须是多线程共享的数据,才能达到多线程排队
()中写什么?
要看你想让那些线程同步
假设t1,t2,t3,t4,t5有五个线程
你只希望t1,t2,t3排队,t4,t5不需要排队,怎么办?
你一定要在()写一个t1,t2,t3共享的对象,而这个对象对于t4 t5是不共享的
*/
synchronized (this) {
double before = this.getBalance();
double after = before - money;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
}
}
}
public class AccountThread extends Thread{
private Account act;
public AccountThread(Account act) {
this.act = act;
}
public void run() {
// run方法表示取款操作
double money = 5000;
act.withdraw(money);
System.out.println(Thread.currentThread().getName() + "对账户" + act.getActNo() + "取款成功,余额为" + act.getBalance());
}
}
public class Test {
public static void main(String[] args) {
Account act = new Account("act-001", 10000);
Thread t1 = new AccountThread(act);
Thread t2 = new AccountThread(act);
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
}
- java中,任何一个对象都有一把锁,其实这把锁就是标记
- 上面代码执行原理:
- 假设t1和t2线程并发,一先一后执行代码
- 假设t1先执行,遇到了synchronized,这个时候自动找“后面共享对象”的对象锁,找到之后,并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直都是占有这把锁的,知道同步代码块代码结束,这把锁才会释放
- 假设t1已经占有这把锁,此时t2也遇到synchronized关键字,也会去占有后面共享对象的这个锁,结果这把锁被t1占有,t2只能在同步代码块外面等待t1的结束,直到t1代码执行结束,归还锁,t2等到这把锁然后t2占有这把锁之后,进入同步代码块,这样就达到了线程执行
- 进入锁池可以理解为一种阻塞状态,直到抢到锁为止
- 上面代码中synchronized后面括号中可以也可以写“abc”字符串也能达到线程排队的效果,但是是所有线程都同步,因为字符串放在字符串常量池中;也可以传实例变量的对象
- synchronized方法体内的代码块越小,执行效率越高
有安全问题的变量
- java三大变量:
- 实例变量:堆中
- 静态变量:在方法区
- 局部变量:在栈中
- 以上三大变量永远都不会存在线程安全问题,因为局部变量不共享,一个线程一个栈。局部变量在栈中,所以局部变量永远都不会共享
- 实例变量在堆中,堆只有一个,静态变量在方法区中,方法区也只有一个,堆和方法区是多线程共享的,存在线程 安全问题
- 局部变量和常量不会有线程安全问题
synchronized出现在实例方法
缺点:
- 出现在实例方法上,一定锁的是this,没得挑,只能是this,不能是其他对象
- 出现在实例方法上,表示整个方法都需要同步,可能会无故扩大同步的范围,导致程序执行效率降低,因此这种方式不常用
优点:
- 代码写得少了
- 如果共享对象就是this,且方法提都需要同步,则采用这种方式
- StringBuffer方法是带有synchronized关键字,如果是局部变量建议使用StringBuilder,因为局部变量不存在线程安全问题
- ArrayList是非线程安全的,Vector是线程安全的
- HashMap HashSet是非线程安全的,HashTable是线程安全的
- synchronized有两种写法:
- 同步代码块
灵活
synchronized(线程共享对象) {
同步代码块
} - 在实例方法上使用synchronized
表示共享对象一定是this
并且同步代码块是整个方法体 - 在静态方法上使用synchronized
表示找类锁
类锁永远只有一把
就算创建了100个对象,类锁也只有一把 - 对象锁:1个对象1把锁,100个对象100把锁
- 类锁:100个对象,也可能只是一把类锁
synchronized相关题目
- 线程t2执行doOther()方法时,需要等待吗?答案:不需要
public class Exam01 {
public static void main(String[] args) {
MyClass mc = new MyClass();
Thread t1 = new MyThread(mc);
Thread t2 = new MyThread(mc);
t1.setName("t1");
t2.setName("t2");
t1.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
class MyThread extends Thread {
private MyClass mc;
public MyThread(MyClass mc) {
this.mc = mc;
}
public void run() {
if (Thread.currentThread().getName().equals("t1")) {
mc.doSome();;
}
if (Thread.currentThread().getName().equals("t2")) {
mc.doOther();
}
}
}
class MyClass {
public synchronized void doSome() {
System.out.println("doSome begin");
try {
Thread.sleep(1000 * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doSome over");
}
public void doOther() {
System.out.println("doOther begin");
System.out.println("doOther over");
}
}
- doOther()方法也有synchronized,此时需要等待吗?答案:需要
- 只有线程需要锁的方法的时候,才会去找锁,当t2执行doOther()时,遇到synchronized此时去找this的锁,发现this的对象锁被t1占用,t2只能等待
- t1和t2传入两个对象,需要等待吗?答案:不需要
public class Exam01 {
public static void main(String[] args) {
MyClass mc1 = new MyClass();
MyClass mc2 = new MyClass();
Thread t1 = new MyThread(mc1);
Thread t2 = new MyThread(mc2);
t1.setName("t1");
t2.setName("t2");
t1.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
class MyThread extends Thread {
private MyClass mc;
public MyThread(MyClass mc) {
this.mc = mc;
}
public void run() {
if (Thread.currentThread().getName().equals("t1")) {
mc.doSome();;
}
if (Thread.currentThread().getName().equals("t2")) {
mc.doOther();
}
}
}
class MyClass {
public synchronized void doSome() {
System.out.println("doSome begin");
try {
Thread.sleep(1000 * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doSome over");
}
public synchronized void doOther() {
System.out.println("doOther begin");
System.out.println("doOther over");
}
}
- doSome()和doOther()都是静态方法且有synchronized,此时需要等待吗?答案:需要
- synchronized出现在静态方法上是找类锁,MyClass这个类不管创建多少对象都只有一把锁,股需要等待
public class Exam01 {
public static void main(String[] args) {
MyClass mc1 = new MyClass();
MyClass mc2 = new MyClass();
Thread t1 = new MyThread(mc1);
Thread t2 = new MyThread(mc2);
t1.setName("t1");
t2.setName("t2");
t1.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
class MyThread extends Thread {
private MyClass mc;
public MyThread(MyClass mc) {
this.mc = mc;
}
public void run() {
if (Thread.currentThread().getName().equals("t1")) {
mc.doSome();;
}
if (Thread.currentThread().getName().equals("t2")) {
mc.doOther();
}
}
}
class MyClass {
public synchronized static void doSome() {
System.out.println("doSome begin");
try {
Thread.sleep(1000 * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doSome over");
}
public synchronized static void doOther() {
System.out.println("doOther begin");
System.out.println("doOther over");
}
}
死锁
- synchronized嵌套使用有可能导致死锁
public class DeadLock {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
// t1和t2两个线程共享o1和o2
Thread t1 = new MyThread1(o1, o2);
Thread t2 = new MyThread2(o1, o2);
t1.start();
t2.start();
}
}
class MyThread1 extends Thread {
Object o1;
Object o2;
public MyThread1(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
public void run() {
synchronized (o1) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2) {
}
}
}
}
class MyThread2 extends Thread {
Object o1;
Object o2;
public MyThread2(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
public void run() {
synchronized (o2) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1) {
}
}
}
}
解决线程安全问题
- 方案一:尽量使用局部变量代替“实例变量和静态变量”
- 方案二:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了(一个线程对应一个对象,100个线程对应100个对象,对象不共享,就没有线程安全问题了)
- 方案三:如果不能使用局部变量,且对象不能创建多个,则只能选择synchronized了。线程同步机制(线程排队等待机制)
守护线程(后台线程)
- java语言中线程分为:1. 用户线程 2. 守护线程
- 其中代表性的垃圾回收线程就是守护线程
- 守护线程特点:一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束
- 主线程main方法是一个用户线程
- 守护线程应用地方:
每天00:00系统数据自动备份
这个需要使用到定时器,我们可以将定时器设置为守护线程。
t1.setDaemon(true);
*此时t1在所有用户线程结束后会自动结束
定时器
- 间隔特定的时间,执行特定的程序
- 实现方式:
- 可以使用sleep方法睡眠,设置睡眠时间,到时间点醒来执行任务(方法原始)
- java.util.Timer可以直接拿来用
- spring框架中提供的SpringTask框架,这个框架只要简单的配置,就可以完成定时器任务
public class TimerTest {
public static void main(String[] args) throws Exception {
Timer timer = new Timer();
// Timer timer = new Timer(true); // 设置为守护线程
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date firstTime = sdf.parse("2022-08-07 16:37:00");
timer.schedule(new LogTimerTask(), firstTime, 1000 * 10);
}
}
class LogTimerTask extends TimerTask {
public void run() {
SimpleDateFormat sdf = new SimpleDateFormat("yyy-MM-dd HH:mm:ss");
String strTime = sdf.format(new Date());
System.out.println(strTime + ": 完成一次数据备份!");
}
}
- 使用匿名内部类
public class TimerTest {
public static void main(String[] args) throws Exception {
Timer timer = new Timer();
// Timer timer = new Timer(true); // 设置为守护线程
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date firstTime = sdf.parse("2022-08-07 16:37:00");
timer.schedule(new TimerTask(){
@Override
public void run() {
SimpleDateFormat sdf = new SimpleDateFormat("yyy-MM-dd HH:mm:ss");
String strTime = sdf.format(new Date());
System.out.println(strTime + ": 完成一次数据备份!");
}
}, firstTime, 1000 * 10);
}
}
实现线程的第三种方式
- 之前方法实现的线程无法获取线程的返回值,run返回值为void
- 使用第三种方式实现Callable接口
优点:可以获取线程执行结果
缺点:效率较低,在获取t线程结果之前,当前线程受阻塞,效率低
public class ThreadTest15 {
public static void main(String[] args) throws Exception {
// 第一步:创建一个“未来任务类”对象
// 第二步:参数非常重要,需要给一个Callable接口实现类对象
FutureTask task = new FutureTask(new Callable() {
@Override
public Object call() throws Exception { // call方法相当于run方法,只是有返回值
System.out.println("call method begin");
Thread.sleep(1000 * 5);
System.out.println("call method end");
int a = 100;
int b = 200;
return a + b; // 自动装箱300编程Integer
}
});
// 创建线程对象
Thread t = new Thread(task);
t.start();
// 这是main方法,主线程中,如何获取t线程的返回结果
Object obj = task.get();
System.out.println(obj.toString());
//main方法剩下代码需要等待get()方法结果,而此方法可能需要很久,另一个线程需要时间
System.out.println("hello world");
}
}
关于Object中的wait和notify方法(生产者和消费者模式)
- 两个方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方式是Object类中自带的
- wait方法和notify方法不是通过线程对象调用,不是t.wait()或者t.notify()
- wait()方法作用:
Object o = new Object();
o.wait();
表示让正在o对象上活动的线程进入等待状态,无限期等待,直到被唤醒为止
o.wait()方法的调用会让“当前线程(正在o对象上活动的线程)”进入等待状态 - notify()方法让在o对象上等待的线程进入活动状态
Object o = new Object();
o.notify();
表示唤醒正在o对象上等待的线程
还有一个notifyAll()方法,唤醒所有等待状态的线程
生产者和消费者模式
- o.wait()方法会让正在o对象上活动的当前线程进入等待状态,并且释放之前占有的o对象的锁,o.notify()方法只会通知,不会释放之前占有的对象的锁
public class ThreadTest16 {
/*
1. 使用wait方法和notify方法实现生产者和消费者模式
2. 什么是生产者和消费者模式?
生产线程负责生产,消费线程负责消费
生产线程he消费线程要达到均衡
这是一种特殊的业务需求,在这种特殊需求的情况下需要使用wait和notify方法
3. 两种方法建立在线程同步的基础上,因为多线程要同时操作一个仓库,有线程安全问题
*/
/*
模拟这样一个需求:
仓库采用list集合。
list集合假设只能存储一个元素
如果list集合中元素个数为0则表示仓库空了
保证list集合中永远最多存1个元素
*/
public static void main(String[] args) {
List list = new ArrayList();
Thread t1 = new Thread(new Producer(list));
Thread t2 = new Thread(new Consumer(list));
t1.setName("生产者线程");
t2.setName("消费者线程");
t1.start();
t2.start();
}
}
// 生产线程
class Producer implements Runnable {
private List list;
public Producer(List list) {
this.list = list;
}
@Override
public void run() {
// 通过死循环模拟一直生产
synchronized (list) {
// 表示给仓库对象加锁
while(true) {
if (list.size() > 9) {
// 当前线程进入等待状态
try {
// 当前线程进入等待状态,并且释放list的锁
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Object obj = new Object();
list.add(obj);
System.out.println(Thread.currentThread().getName() + "生产了" + obj);
// 唤醒消费者进行消费
list.notify();
}
}
}
}
// 消费线程
class Consumer implements Runnable {
private List list;
public Consumer(List list) {
this.list = list;
}
@Override
public void run() {
synchronized (list) {
while (true) {
if (list.size() == 0) {
try {
// 仓库已经空了,消费者线程等待,释放掉list集合的锁
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 程序执行到此处说明仓库中有数据,应该进行消费
Object obj = list.remove(0);
System.out.println(Thread.currentThread().getName() + "消费了" + obj);
list.notify();
}
}
}
}
IO流概述
- 通过IO可以完成硬盘文件的读和写
IO流分类
- 按照流的方向分类:(以内存为参照物)
- 网内存中去叫输入,或者读
- 从内存中出来叫输出,或者写
- 按照读取数据方式不同进行分类:
- 字节流,一次读一个字节
- 字符流,一次读取一个字符,为了方便录取普通文本文件存在的,不能读取图片、声音、视频等文件,不能读取word文件
javaIO流的四大家族
- java.io.InputStream 字节输入流
- java.io.OutputStream 字节输出流
- java.io.Reader 字符输入流
- java.io.Writer 字符输出流
- 以上四个类都是抽象类
注意
- windows操作系统中文件的a字符占用一个字节,java中占用2个字节
- java中所有的流都在java.io.*下
- java中以stream结尾的类都是字节流,以reader或者writer结尾的都是字符流
- 所有的流都实现了java.io.closeable接口,都是可关闭的,都有close()方法
- 所有输出流都实现了java.io.flushable接口,都是可刷新的,都有flush()方法,养成好习惯输出流在最终输出之后一定要记得flush()刷新一下,这个刷新表示将通道/管道当中剩余未输出的数据强行输出玩,刷新的作用就是清空管道
需要掌握的16个流
- 文件专属
- java.io.FileInputStream
- java.io.FileOutputStream
- java.io.FileReader
- java.io.FileWriter
- 转换流(将字节流转换为字符流)
- java.io.InputStreamerReader
- java.io.OutputStreamWriter
- 缓冲流专属
- java.io.BufferedReader
- java.io.BufferedWriter
- java.io.BufferedInputStream
- java.io.BufferedOutputStream
- 数据流专属
- java.io.DataInputStream
- java.io.DataOutputStream
- 对象专属流
- java.io.ObjectInputStream
- java.io.ObjectOutputStream
- 标准输出流
- java.io.PrintWriter
- java.io.PrintStream
FileInputStream
一个字节一个字节读
package io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/*
1. 文件字节输入流,万能的,任何类型的文件都可以采用这个流来读
2. 字节的方式,完成输入的操作,完成度的操作(硬盘 --> 内存)
3.
*/
public class FileInputStreamTest01 {
public static void main(String[] args) {
// 在外部声明,不然在try中声明finally中无法使用
FileInputStream fis = null;
try {
// 创建字节输入流对象
// 以下采用绝对路径方式
// idea会把\变成\\
fis = new FileInputStream("D:/IOExample/temp.txt");
//开始读
// 调用一次read指针移动一个字节
int readData1 = fis.read();
System.out.println(readData1);
int readData2 = fis.read();
System.out.println(readData2);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 在finally语句中确保流关闭
if (fis != null) { // 避免空指针异常
try {
// 关闭流的前提是:流不为空,流是null的时候没必要关闭
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
package io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class FileInputStreamTest02 {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("D:\\IOExample\\temp.txt");
// while(true) {
// int readData = fis.read();
// if (readData == -1) {
// break;
// }
// System.out.println(readData);
// }
// 改造while循环
int readData = 0;
// 赋值语句和判断语句写在一起
while((readData = fis.read()) != -1) {
System.out.println(readData);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
- 以上程序缺点:一次读取一个字节,这样内存和硬盘交互太频繁,基本上时间和资源都耗费在交互上了
往byte数组中读
package com.bjpowernode.java.io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class FileInputStreamTest03 {
/*
int read(byte[] b)
一次最多读取b.length个字节
减少了硬盘和内存的交互,提高程序的执行效率
在byte数组当中读
*/
public static void main(String[] args) {
FileInputStream fis = null;
try {
// idea中默认地址为工程project的根就是idea默认当前路径
fis = new FileInputStream("tempfile.txt");
byte[] bytes = new byte[4];
// 该方法返回的是读到的字节数量
int readCount1 = fis.read(bytes);
System.out.println(readCount1);
System.out.println(new String(bytes)); // abcd
System.out.println(new String(bytes, 0, readCount1)); // abcd
// 此时读到的是ef,并将内存中的ab覆盖
int readCount2 = fis.read(bytes);
System.out.println(readCount2);
System.out.println(new String(bytes)); // efcd
System.out.println(new String(bytes, 0, readCount2)); // ef
// 此时没有字节
int readCount3 = fis.read(bytes);
System.out.println(readCount3);
System.out.println(new String(bytes)); // efcd
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
最终版
package com.bjpowernode.java.io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/*
最终版
*/
public class FileInputStreamTest04 {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("chapter15/src/tempfile3");
byte[] bytes = new byte[4];
int readCount = 0;
while((readCount = fis.read(bytes)) != -1) {
System.out.print(new String(bytes, 0, readCount));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
FileInputStream其他常用方法
- int available();
package com.bjpowernode.java.io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/*
其他常用方法:
int available();返回流当中剩余的没有读到的字节数量
long skip(long n);跳过几个字节不读
*/
public class FileInputStreamTest05 {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("tempfile.txt");
System.out.println("总字节数:" + fis.available());
// int readByte = fis.read();
// System.out.println("剩下未读字节数:" + fis.available());
// 该方法不适合大文件
byte[] bytes = new byte[fis.available()];
int readCount = fis.read(bytes);
System.out.println(new String(bytes));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
- long skip(long n);
package com.bjpowernode.java.io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class FileInputStreamTest06 {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("tempfile.txt");
fis.skip(3);
System.out.println(fis.read());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
FileOutputStream
package com.bjpowernode.java.io;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/*
文件字节输出流,负责写
从内存到硬盘
*/
public class FileOutputStreamTest01 {
public static void main(String[] args) {
FileOutputStream fos = null;
try {
// 这种方式会将源文件清空,然后重新写入
// fos = new FileOutputStream("myfile");
// FileOutputStream(String name, boolean append);使用这种构造方法可以选择是清空写还是追加写
fos = new FileOutputStream("myfile", true);
// 开始写
byte[] bytes = {97, 98, 99, 100};
// 将byte数组全部写出,没有文件将自动创建
fos.write(bytes);
fos.write(bytes, 0, 2); // abcdab
String s = "我是一个中国人";
// 将字符串转为byte数组
byte[] bs = s.getBytes();
fos.write(bs);
// 写完之后要刷新
fos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
文件复制(字节流)
package com.bjpowernode.java.io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/*
使用FileInputStream和FileOutputStream完成文件的拷贝
拷贝的过程是一边读一边写
使用以上字节流拷贝文件的时候,文件类型随意,万能
*/
public class Copy01 {
public static void main(String[] args) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("E:\\mathModel\\2021年中国研究生数学建模竞赛赛题\\1.csv");
fos = new FileOutputStream("E:\\1.csv");
byte[] bytes = new byte[1024 * 1024]; // 一次1MB
int readCount = 0;
while ((readCount = fis.read(bytes)) != -1) {
fos.write(bytes, 0, readCount);
}
fos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
FileReader
package com.bjpowernode.java.io;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
/*
文件字符输入流,只能服务普通文本
读取文本内容时,比较方便,快捷
*/
public class FileReaderTest {
public static void main(String[] args) {
FileReader reader = null;
try {
reader = new FileReader("tempfile.txt");
// 开始读
char[] chars = new char[4];
int readCount = 0;
while ((readCount = reader.read(chars)) != -1) {
System.out.print (new String(chars, 0, readCount));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
FileWriter
package com.bjpowernode.java.io;
import java.io.FileWriter;
import java.io.IOException;
/*
文件字符输出流,写
只能输出普通文本
*/
public class FileWriterTest {
public static void main(String[] args) {
FileWriter out = null;
try {
out = new FileWriter("file", true);
// 开始写
char[] chars = {'我', '是','中', '国', '人'};
// 可以是字符数组
out.write(chars);
// 可以是字符数组一部分
out.write(chars, 2, 3);
// 可以是一个字符串
out.write("我是一名java软件工程师");
// 刷新
out.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
普通文本复制(字符流)
- 能用记事本编辑的都是普通文本文件,不限制后缀,不一定是.txt结尾,java文件也是普通文件
package com.bjpowernode.java.io;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class Copy02 {
public static void main(String[] args) {
FileReader reader = null;
FileWriter writer = null;
try {
reader = new FileReader("E:\\mathModel\\2021年中国研究生数学建模竞赛赛题\\123.txt");
writer = new FileWriter("E:\\123_copy.txt");
char[] chars = new char[1024 * 1024];
int readCount = 0;
while((readCount = reader.read(chars)) != -1) {
writer.write(chars, 0, readCount);
}
// 刷新
writer.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
带缓冲区的字符输入流BufferedReader
package com.bjpowernode.java.io;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
/*
BufferedReader:
带有缓冲区的字符输入流,使用这个流的时候不需要自定义char数组,或者说不需要自定义byte数组,自带缓冲
*/
public class BufferedReaderTest01 {
public static void main(String[] args) {
// 当一个流的构造方法中需要一个流的时候,这个被传进来的流叫做:节点流
// 外部负责包装的这个流,叫做包装流,还有一个名字叫做:处理流
// 向当前这个程序来说,fileReader就是一个节点流,BufferedReader就是包装流/处理流
FileReader reader = null;
BufferedReader br = null;
try {
reader = new FileReader("Copy02_copy.java");
br = new BufferedReader(reader);
// 读一行
// String firstLine = br.readLine();
// System.out.println(firstLine);
//
// String secondLine = br.readLine();
// System.out.println(secondLine);
//
// String thirdLine = br.readLine();
// System.out.println(thirdLine);
String s = null;
// readLine()方法读取一个文本行但是不带换行符
while ((s = br.readLine()) != null) {
System.out.println(s);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null) {
// 关闭流
// 对于包装流来说,只需要关闭最外层流就行,里面的节点流会自动关闭
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
- 如果只有字节流,则需要转换为字符流之后再传进BufferedReader参数中
package com.bjpowernode.java.io;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStreamReader;
public class BufferedReaderTest02 {
public static void main(String[] args) throws Exception {
// // 字节流
// FileInputStream in = new FileInputStream("Copy02_copy.java");
// // 将字节流转换为字符流
// InputStreamReader reader = new InputStreamReader(in);
// // 这个构造方法只能传一个字符流,不能传字节流,需要转换
// BufferedReader br = new BufferedReader(reader);
//合并
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("Copy02_copy.java")));
String s = null;
while ((s = br.readLine()) != null) {
System.out.println(s);
}
br.close();
}
}
带缓冲区的字符输出流BufferedWriter
package com.bjpowernode.java.io;
import java.io.*;
public class BufferedWriterTest {
public static void main(String[] args) throws Exception {
// BufferedWriter bw = new BufferedWriter(new FileWriter("copy"));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("copy", true)));
bw.write("Hello world!");
bw.write('\n');
bw.write("Hello ylx!");
bw.flush();
bw.close();
}
}
数据专属流
- DataOutputStream
package com.bjpowernode.java.io;
import java.io.DataOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
/*
java.io.DataOutputStream:数据专属的流
这个流可以将数据连同数据的类型一并写入文档
注意:这个文件不是普通的文本文档。(这个文件使用记事本打不开)
*/
public class DataOutputStreamTest {
public static void main(String[] args) throws Exception {
// 创建数据专属的字节输出流
DataOutputStream dos = new DataOutputStream(new FileOutputStream("data"));
// 写数据
byte b = 100;
short s = 200;
int i = 1000000000;
long l = 400L;
float f = 3.0F;
double d = 3.14;
boolean sex = true;
char c = 'a';
// 写
dos.writeByte(b);
dos.writeShort(s);
dos.writeInt(i);
dos.writeLong(l);
dos.writeFloat(f);
dos.writeDouble(d);
dos.writeBoolean(sex);
dos.writeChar(c);
dos.flush();
dos.close();
}
}
- DataInputStream
package com.bjpowernode.java.io;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
/*
DataInputStream:数据字节输入流
DataOutputStream写的文件,只能使用DataInputStream去读,并且读的时候你需要提前知道写入的顺序
读的顺序需要和写的顺序一致,才可以正常取出数据
*/
public class DataInputStreamTest01 {
public static void main(String[] args) throws Exception {
DataInputStream dis = new DataInputStream(new FileInputStream("data"));
// 开始读
byte b = dis.readByte();
short s = dis.readShort();
int i = dis.readInt();
System.out.println(b);
System.out.println(s);
System.out.println(i);
}
}
标准输出流
- PrintStream
- 标准输出流不需要关闭
- 标准输出流默认输出到控制台
- System.out.println()中out返回的是一个PrintStream
- out是一个静态的标准字节输出流对象
package com.bjpowernode.java.io;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
/*
java.io.PrintStream:标准的字节输出流
*/
public class PrintStreamTest {
public static void main(String[] args) throws Exception {
// 合并写
System.out.println("Hello World!");
// 分开写
PrintStream ps = System.out; // out为静态的标准字节输出流对象
ps.println("Hello World!");
// 标准输出流不再指向控制台,指向log文件
PrintStream printStream = new PrintStream(new FileOutputStream("log", true));
// 修改输出方向,将输出方向修改到log文件
System.setOut(printStream);
// 再输出
System.out.println("hello world");
System.out.println("hello ylx");
// 标准输出流不需要手动关闭
}
}
- 通过标准输出流写一个记录日志的工具
package com.bjpowernode.java.io;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.Date;
/*
日志工具
*/
public class Logger {
public static void log(String msg) {
try {
// 指向一个日志文件
PrintStream ps = new PrintStream(new FileOutputStream("log.txt", true));
// 改变输出方向
System.setOut(ps);
// 日期当前时间
Date nowTime = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String strTime = sdf.format(nowTime);
System.out.println(strTime + ": " + msg);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
package com.bjpowernode.java.io;
/*
测试工具类是否好用
*/
public class LogTest {
public static void main(String[] args) {
Logger.log("调用了System类的gc()方法,建议启动垃圾回收");
Logger.log("调用了UserService的doSome()方法");
Logger.log("用户尝试登录,验证失败");
}
}
File类
示例代码
package com.bjpowernode.java.io;
import java.io.File;
import java.io.IOException;
/*
File
1. File类和四大家族没有关系,所以File类不能完成文件的读和写
2. File对象代表什么呢?
文件和目录路径名的抽象表示形式
一个File对象有可能对应的是目录,也可能是文件
File只是一个路径名的抽象表示形式
3. File类中常用的方法
*/
public class FileTest01 {
public static void main(String[] args) throws Exception {
// 创建File对象
File f1 = new File("D:\\file");
// 判断是否存在
System.out.println(f1.exists());
// // 如果D:/file不存在,则以文件的形式创建出来
// if (!f1.exists()) {
// f1.createNewFile();
// }
// // 如果D:/file不存在,则以目录的形式创建
// if (!f1.exists()) {
// f1.mkdir();
// }
// // 创建多重目录
// File f2 = new File("D:/a/b/c/d");
// System.out.println(f2.exists());
// if (!f2.exists()) {
// f2.mkdirs();
// }
// 获取父路径
File f3 = new File("D:\\a\\b\\c\\d\\123.docx");
String parentPath = f3.getParent();
System.out.println(parentPath);
// 第二种方法获取父路径
File parentFile = f3.getParentFile();
System.out.println("获取绝对路径:" + parentFile.getAbsolutePath());
File f4 = new File("copy");
System.out.println("绝对路径为:" + f4.getAbsolutePath());
}
}
常用方法
boolean exists()
boolean createNewFile();
boolean mkdir();
boolean mkdirs();
File f1 = new File(String pathname); // 构造方法
File getParentFile();
String getAbsolutePath();
- 以下代码中还有一些方法
package com.bjpowernode.java.io;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
/*
File类的常用方法
*/
public class FileTest02 {
public static void main(String[] args) {
File f1 = new File("copy");
// 获取文件名
System.out.println(f1.getName());
// 判断是否是一个目录
System.out.println(f1.isDirectory());
// 判断是否是一个文件
System.out.println(f1.isFile());
// 获取文件最后一个修改时间
long haoMiao = f1.lastModified();
Date time = new Date(haoMiao);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String strTime = sdf.format(time);
System.out.println(strTime);
// 获取文件大小
System.out.println(f1.length());
}
}
package com.bjpowernode.java.io;
import java.io.File;
/*
File中的listFiles()方法
*/
public class FileTest03 {
public static void main(String[] args) {
// File[] listFiles()
// 获取当前目录下所有的文件
File f = new File("E:\\ShanghaiUniversity\\QT功能实现汇总");
File[] files = f.listFiles();
for (File file : files) {
System.out.println(file.getAbsolutePath());
}
}
}
目录拷贝
package com.bjpowernode.java.io;
import java.io.*;
public class CopyAll {
public static void main(String[] args) {
File srcFile = new File("E:\\ShanghaiUniversity\\personal\\专利");
File destFile = new File("D:\\a\\b\\c\\d\\");
copyDir(srcFile, destFile);
}
private static void copyDir(File srcFile, File destFile) {
if (srcFile.isFile()) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
String path = (destFile.getAbsolutePath().endsWith("\\") ? destFile.getAbsolutePath() : destFile.getAbsolutePath()) + "\\" + srcFile.getAbsolutePath().substring(3);
// System.out.println(path);
// 读文件
fis = new FileInputStream(srcFile);
// 写到这个文件中
fos = new FileOutputStream(path);
// 一边读一边写
byte[] bytes = new byte[1024 * 1024];
int readCount = 0;
while ((readCount = fis.read(bytes)) != -1) {
fos.write(bytes,0, readCount);
}
fos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return;
}
// 获取源下面的子目录
File[] files = srcFile.listFiles();
for (File file : files) {
// 获取所有文件(包括目录和文件)的绝对路径
// System.out.println(file.getAbsolutePath());
if (file.isDirectory()) {
// 新建对应目录
String srcDir = file.getAbsolutePath();
// System.out.println(srcDir.substring(3));
String destDir = destFile.getAbsolutePath().endsWith("\\") ? destFile.getAbsolutePath() : destFile.getAbsolutePath() + "\\" + srcDir.substring(3);
System.out.println(destDir);
File newFile = new File(destDir);
if (!newFile.exists()) {
newFile.mkdirs();
}
}
copyDir(file, destFile);
}
}
}
- 序列化:Serialize,java对象存储到文件中,将java对象的状态保存下来的过程
- 反序列化:DeSerialize,将硬盘上的数据重新恢复到内存当中,恢复成java对象
序列化单个对象
package com.bjpowernode.java.bean;
import java.io.Serializable;
public class Student implements Serializable {
private int no;
private String name;
public Student() {
}
public Student(int no, String name) {
this.no = no;
this.name = name;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
}
package com.bjpowernode.java.io;
import com.bjpowernode.java.bean.Student;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
/*
1. 参与序列化和反序列化的对象,必须实现Serializable接口
2. 通过源代码发现,Serializable接口只是一个标志接口,接口中没有代码,起到标识的作用,java虚拟机看到这个类实现了这个接口,可能会对这个类进行特殊待遇
3. java虚拟机看到实现该接口的类会自动生成序列化版本号
*/
public class ObjectOutputStreamTest01 {
public static void main(String[] args) throws Exception {
Student s = new Student(1111, "zhangsan");
// 序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("students"));
//序列化对象
oos.writeObject(s);
//刷新
oos.flush();
// 关闭
oos.close();
}
}
package com.bjpowernode.java.io;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class ObjectInputStreamTest01 {
public static void main(String[] args) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("students"));
// 开始反序列化,读
Object obj = ois.readObject();
System.out.println(obj);
ois.close();
}
}
序列化多个对象
package com.bjpowernode.java.bean;
import java.io.Serializable;
public class User implements Serializable {
private int no;
private String name;
public User() {
}
public User(int no, String name) {
this.no = no;
this.name = name;
}
@Override
public String toString() {
return "User{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.bjpowernode.java.io;
import com.bjpowernode.java.bean.User;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
public class ObjectOutputStreamTest02 {
public static void main(String[] args) throws Exception {
List<User> list = new ArrayList<User>();
list.add(new User(1, "zhangsan"));
list.add(new User(2, "lisi"));
list.add(new User(3, "wangwu"));
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("users"));
// 序列化一个集合,这个集合中有多个对象
oos.writeObject(list);;
oos.flush();
oos.close();
}
}
package com.bjpowernode.java.io;
import com.bjpowernode.java.bean.User;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.util.List;
public class ObjectInputStreamTest02 {
public static void main(String[] args) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("users"));
List<User> list = (List<User>)ois.readObject();
for (User user : list) {
System.out.println(user);
}
ois.close();
}
}
transient关键字
- 加该关键字的属性不参与序列化,表示游离的
序列化版本号
- java虚拟机判断类是否相同:
- 类名不同,类一定不同
- 类名相同,靠序列化版本号区分
- 后期如果同一个类的代码修改,则会生成不同的序列化版本号,建议手动指定序列化版本号,这样后期改动后也会认为是同一个类,这样反序列化后不会报错