static
static修饰成员变量
static
- 叫静态,可以修饰成员变量、成员方法。
成员变量按照有无static修饰,分为两种:
- 类变量:有static修饰,属于类,在计算机里只有一份,会被类的全部对象共享。
- 实例变量(对象的变量):无static修饰,属于每个对象的。
static修饰成员变量的应用场景
static修饰成员方法
static修饰成员方法的应用场景
static的注意事项
static的应用知识:代码块
static的应用知识:单例设计模式(开发框架)(面试笔试)(看源码)
什么是设计模式?(具体问题的最优解法)
- 一个问题通常有n种解法,其中肯定有一种解法是最优的,这个最优的解法被人总结出来了,称为设计模式。
- 设计模式有20多种,对应20多种软件开发种会遇到的问题。
关于设计模式的学习,主要学什么?
- 解决什么问题
- 怎么写?
单例设计模式
- 确保一个类只有一个对象。
写法
- 把类的构造器私有。
- 定义一个类变量记住类的一个对象。
- 定义一个类方法,返回对象。
单例模式的应用场场景和好处?
- 任务管理器对象、获取运行时对象。
- 在这些业务场景下,使用单例模式,可以避免浪费内存。
饿汉式单例设计模式
在创建类式,就创建好了对象,上面就是饿汉式单例模式
懒汉式单例设计模式
- 拿对象时,才开始创建对象。
写法
接口
认识接口
- Java提供了一个关键字interface,\,用这个关键字我们可以定义一个特殊的结构:接口。
- 注意:接口不能创建对象;接口时用来被类实现(implements)的,实现接口的类称为实现类。
- 一个类可以实现多个接口,实现类实现多个接口(可以堪称干爹),必须重写完全部接口的全部抽象方法,否则实现类需要定义成抽象类。
public interface A {
//成员变量(常量)
public static final String name = "黑马程序员";
//成员方法(抽象方法)
public abstract void run();
}
修饰符 class 实现类 implements 接口1,接口2,接口3,…{}
接口的好处(重点)
- 弥补了类单继承的不足,一个类同时可以实现多个接口。
- 为什么通过接口,也就是去找干爹来扩展自己的功能呢?因为通过接口去找干爹,别人通过你implements的接口,就可以显性的知道你是谁,从而也就可以放心的把你当作谁来用了。
- 让程序可以面向接口编程,这样程序员就可以灵活方便的切换各种业务实现。
//类
public Class A extends Student implements Driver{
public void drive(){}
}
//类
public class B implements Driver{
public void drive(){}
}
//接口
piublic interface Driver{
void drive();
}
// 面向接口编程,如果有一天想换司机,只需要new B()就行了。
public class Test{
public static void main(){
Driver s = new A();
s.drive();
}
}
接口的应用案例
1.定义学生类
2.定义班级管理类:创建学生列表,添加学生对象,实现两个功能。
3.定义接口:接口中定义两个功能。
4.通过两个类分别按照两个方案实现接口中的两个功能:类A,类B。
5.班级管理类中面向接口编程:private 接口名 接口变量名 = new 类A();
private 接口名 接口变量名 = new 类B();//与上面选一个代表选的不同功能。
在管理类中定义两个功能的方法调用 接口变量名的两个功能。
LocalDate:代表本地日期(年、月、日、星期)
LocalTime:代表本地时间(时、分、秒、纳秒)
LocalDateTime:代表本地日期、时间(年、月、日、星期、时、分、秒、纳秒)
获取对象的方案
方法名 | 示例 |
---|---|
public static Xxxx now():获取系统当前时间对应的该对象 | LocalDate ld = LocalDate.now(); LocalTime lt = LocalTime.now(); LocalDateTime ldt = LocalDateTime.now(); |
原本的日期都是不可变日期,ld.withYear(2099) 直接修改年信息返回新的对象。
Duration
// 计算两个时间之间相差的具体天数、小时、分钟、秒
Duration duration = Duration.between(now,startTime);
System.out.println(duration.toDays()+"天 "+duration.toHoursPart()+"时 "+duration.toMinutesPart()+"分 "+duration.toSecondsPart()+"秒 ");
泛型
- 定义类、接口、方法时,同时声明了一个或者多个类型变量(如:),称为泛型类、泛型接口,泛型方法、它们统称为泛型。
public class ArrayList{…} E可以换成A,B,a,但是按照规范时E。 - 作用:泛型提供了在编译阶段约束所能操作的数据类型,并自动进行检测的能力!这样可以避免强制类型转换,及其可能出现的异常。
- 泛型的本质:把具体的数据类型作为参数传给类型变量。
自定义泛型类
public class MyArrayList<E>{
public boolean add(E e)
return true;
}
public class MyClass2<E,T>{
public void put(E e, T t)
}
public class MyClass3<E extends Animal>{}
自定义泛型接口
目标:掌握泛型接口的定义和使用
场景:系统需要处理老师和学生的数据,需要提供两个功能:保存对象数据,根据名称查询数据。
public interface Data<T>{
void add(T t)
}
//老师对象
public Class TeacherData implements Data<Teacher>{}
//学生对象
public Class StudentData implements Data<Student>{}
自定义泛型方法
修饰符 <类型变量,类型变量,…> 返回值类型 方法名(形参列表){}
public static <T> T test<T t>{}
通配符
- 就是”?“,可以在”使用泛型“的时候代表一切类型;E T K V在定义泛型的时候使用。
泛型的上下限
- 泛型上限:?extend Car: ?能接收的必须是Car或者其子类。
- 泛型下限: ?super Car: ?能接收的必须是Car或者其父类。
泛型擦除问题和基本数据类型问题
- 泛型工作在编译阶段的,一旦程序编译成class文件,class文件中就不存在泛型了
- 泛型不支持基本数据类型,只能支持对象类型(引用数据类型)。
如ArrayList,如果ArrayList不使用泛型,使用集合添加元素,可以添加Object类型(一切类型)的数据。get遍历集合的数据时返回的数据类型时Object类型,当需要确定类型时需要进行强转,强转的时候可能报出异常。所以加上泛型List list1 =new ArrayList<>(); 只能加入String类型,约束能够操作的数据类型。相当于贴上标签,约束操作值,这样需要确定类型的时候就不需要强转了。String、Cat等这些类型作为参数传给类型变量E。
File
创建File类的对象
构造器 | 说明 |
---|---|
public File(String pathname) | 根据文件路径创建文件对象 |
public File(String parent, String child) | 根据父路径和子路径名字创建文件对象 |
public File(File parent, String child) | 根据父路径对应文件对象和子路径名字创建文件对象 |
注意
- File对象既可以代表文件、也可以代表文件夹。
- File封装的对象仅仅试一个路径名,这个路径可以是存在的,也允许是不存在的。
绝对路径、相对路径
- 绝对路径:从盘符开始
- 相对路径:不带盘符,默认直接到当前工程下的目录寻找文件。
File file3 = new File(‘模块名\a.txt’);
File的方法
// 1.创建文件对象,指代某个文件
File fl = new File("NextStage\\FileDirectiory\\itheima.txt");
// 2.public boolean exit():判断当前文件对象,对应的文件路径是否存在,存在返回true。
System.out.println(fl.exists());
// 3.public boolean isFile():判断当前文件对象指代的是否是文件,是文件返回true,反之。
System.out.println(fl.isFile());
// 4.public boolean isDirectory():判断当前文件对象指代的是否是文件夹,是文件夹返回true,反之。
System.out.println(fl.isDirectory());
// 5.public String getName():获取文件的名称(包含后缀)
System.out.println(fl.getName());
// 6.public long length():获取文件的大小,返回字节个数
System.out.println(fl.length());
// 7.public long lastModified():获取文件的最后修改时间。
long l = fl.lastModified();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(l));
// 8.public String getPath():获取创建文件对象时,使用的路径。
System.out.println(fl.getPath());
// 9.public String getAbsolutePath():获取绝对路径
System.out.println(fl.getAbsolutePath());
File类创建文件的功能
方法名称 | 说明 |
---|---|
public boolean createNewFile() | 创建一个新的空的文件 |
public boolean mkdir() | 只创建一级文件夹 |
public boolean mkdirs() | 可以创建多级文件夹 |
File类删除文件的功能
方法名称 | 说明 |
---|---|
public boolean delete() | 删除文件、空文件夹 |
注意:
delete方法默认只能删除文件和空文件夹,删除后的文件不会进入回收站。
File类提供的遍历文件夹的功能
方法名称 | 说明 |
---|---|
public String[] list() | 根据文件路径创建文件对象 |
public File[] listFiles() | 根据父路径和子路径名字创建文件对象 |
使用listFiles方法时的注意事项:
- 当主调是文件,或者路径不存在时,返回null
- 当主调是空文件夹时,返回一个长度为0的数组
- 当主调是一个有内容的文件夹时,将里面所有一级文件和文件夹路径放在File数组中返回
- 当主调是一个文件夹,且里面有隐藏文件时,将里面所有文件和文件夹的路径放在File数组中返回,包含隐藏文件
- 当主调是一个文件夹,但是没有权限访问该文件夹时,返回null
什么是方法递归?
- 递归是一种算法,在程序设计语言中广泛应用。
- 从形式上说:方法调用自身的形式称为方法递归。
递归的形式
- 直接递归:方法自己调用自己。
- 间接递归:方法调用其他方法,其他方法又回调方法自己。
使用方法递归时需要注意的问题:
- 递归如果没有控制好终止,会出现递归死循环,导致栈内存溢出错误。
递归找文件代码
package File_IO;
import java.io.File;
import java.io.IOException;
public class searchFile_digui {
public static void main(String[] args) throws Exception {
File dir = new File("C:/");
String fileName = "QQ.exe";
search(dir,fileName);
}
public static void search(File dir, String fileName) throws Exception {
if(dir.isFile()||dir==null||!dir.exists()){
return;
}
File[] files = dir.listFiles();
if (files != null && files.length > 0) {
for (File file:files) {
if (file.isFile()){
if(file.getName().contains(fileName)){
System.out.println(file.getAbsolutePath());
Runtime runtime = Runtime.getRuntime();
runtime.exec(file.getAbsolutePath());
}
}else {
search(file,fileName);
}
}
}
}
}
标准ASCLL字符集
- ASCll:美国信息交换标准代码,包括了英文、符号等。
- 标准ASCll使用1个字节存储一个字符,首位是0,总共可表示128个字符,对美国佬说完全够用。
GBK(汉字内码扩展标准,国标)
- 汉字编码字符集,包含了2万多个汉字等字符,GBK中的一个中文字符编码成两个字节的形式存储。
- 注意“GBK兼容了ASCll字符集。
GBK规定:汉字的第一一个字节的第一位必须是1
Unicode字符集(统一码,也叫万国码)
- Unicode是国际组织制定的,可以容纳世界上所有文字、符号的字符集。
奢侈!占存储空间,通信效率变低!
UTF-8
- 是Unicode字符集的一种编码方案,采取可变长编码方案,共分为四个长度区:1个字节,2个字节,3个字节,4个字节
- 英文字符、数字等只占1个字节(兼容标准ASCll编码),汉字字符占用3个字节。
97 25105 109是Unicode编码
注意:技术人员在开发时都应该使用UTF-8编码
注意1:字符编码时使用的字符集,和解码时使用的字符集必须一致,否则出现乱码。
注意2:英文,数字一般不会乱码,因为很多字符集都兼容ASCll编码。
字符解码编码
String str = "a我b";
// 编码
byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
System.out.println(Arrays.toString(bytes));
byte[] bytes1 = str.getBytes("GBK");
System.out.println(Arrays.toString(bytes1));
//解码
String s1 = new String(bytes);
System.out.println(s1);
String s2 = new String(bytes1,"GBK");
System.out.println(s2);
IO流概述
IO流的分类
IO流总体四大类
- 字节输入流:以内存为基准,来自磁盘文件/网络中的数据以字节的形式读入到内存中去的流。
- 字节输出流:以内存为基准,把内存中的数据以字节写出到磁盘文件或者网络中去的流。
- 字符输入流:以内存为基准,来自磁盘文件/网络中的数据以字符的形式读入到内存中去的流。
- 字符输出流:以内存为基准,把内存中的数据以字符写出到磁盘文件或者网络介质中去的流。
IO流的体系
FileInputStream每次读取一个字节流
// 创建字节输入流管道与源文件截图
FileInputStream fileInputStream = new FileInputStream(new File("D:\\IdeaProject\\javasepromax\\NextStage\\FileDirectiory\\itheima.txt"));
int b;
// 使用循环读取文件的字节数据
// read()每次读取一个字节返回,如果发现没有数据可读会返回-1
while ((b = fileInputStream.read())!=-1){
System.out.print((char) b);
}
fileInputStream.close()
注意事项
- 使用FileInputStream每次读取一个字节,读取性能较差,并且读取汉字输出会乱码。
FileInputStream每次读取多个字节流
方法名称 | 说明 |
---|---|
public int read(byte[] buffer) | 每次用一个字节数组去读取,返回字节数组读取了多少个字节,如果发现没有数据可读会返回-1。 |
InputStream is = new FileInputStream(new File("D:\\IdeaProject\\javasepromax\\NextStage\\FileDirectiory\\itheima.txt"));
int len;
// 每次读取三个字节存入bytes
byte[] bytes = new byte[3];
while ((len = is.read(bytes))!=-1){
String s = new String(bytes,0,len);
System.out.print(s);
}
注意事项
- 使用FileInputStream每次读取多个字节,读取性能得到了提升,但读取汉字输出还是会乱码。
FileInputStream一次读取完全部字节
- 方式一: 自己定义一个字节数组与被读取的文件大小一样大,然后使用该字节数组,一次读完文件的全部字节。
方法名称 | 说明 |
---|---|
public int read(byte[] buffer) | 每次用一个字节数组去读取,返回字节数组读取了多少个字节,如果发现没有数据可读会返回-1。 |
InputStream is = new FileInputStream(new File("D:\\IdeaProject\\javasepromax\\NextStage\\FileDirectiory\\itheima.txt"));
File f =new File("NextStage\\FileDirectiory\\itheima.txt");
long size = f.length();
int len;
// 每次读取三个字节存入bytes
byte[] bytes = new byte[(int) size];
while ((len = is.read(bytes))!=-1){
String s = new String(bytes,0,len);
System.out.print(s);
}
- 方式二: Java官方为InputStream提供了如下方法,可以直接把文件的全部字节输入流的文件对象的字节数据装到一个字节数组返回
方法名称 | 说明 |
---|---|
public byte[] readAllBytes() throw IOException) | 直接将当前字节流对应的文件对象的字节数据撞到一个字节数组返回 |
InputStream is = new FileInputStream("NextStage\\FileDirectiory\\itheima.txt");
byte[] buffer = is.readAllBytes();
System.out.println(new String(buffer));
FileOutputStream(文件字节输出流) 写字节出去
public static void main(String[] args) throws Exception {
// true表示追加
OutputStream wt1 = new FileOutputStream(new File("NextStage\\FileDirectiory\\output.txt"),true);
wt1.write(97);// 97就是一个字节,代表a
wt1.write('b'); // 'b'也是一个字节
wt1.write('类'); // 乱码,每次写一个字节,汉字是3个字节。
byte[] bytes = "我爱你中国abc".getBytes(StandardCharsets.UTF_8);
wt1.write(bytes);
// 一个汉字占3个字符,5个汉字15个字符
wt1.write(bytes,0,15);
wt1.close();
}
换行符 “\r\n” 在所有平台都支持
文件复制
public static void main(String[] args) throws IOException {
InputStream is = new FileInputStream("D:\\侯诗梦\\文件图片\\1686836573834.jpg");
OutputStream os = new FileOutputStream("NextStage\\FileDirectiory\\2.jpg");
byte[] pic = new byte[1024];
int len;
while ((len=is.read(pic))!=-1){
os.write(pic,0,len);
}
os.close();
is.close();
}
字节流非常适合做一切文件的复制操作
任何文件的底层都是字节,字节流做复制,是一字不漏的转移完全部字节,只要复制后的文件格式一致就没问题!
释放资源的方式
try-catch-finally
public class copyFile {
public static void main(String[] args) throws IOException {
InputStream is = null;
OutputStream os = null;
try {
is = new FileInputStream("D:\\侯诗梦\\文件图片\\1686836573834.jpg");
os = new FileOutputStream("NextStage\\FileDirectiory\\2.jpg");
byte[] pic = new byte[1024];
int len;
while ((len=is.read(pic))!=-1){
os.write(pic,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(os!=null) os.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if(is!=null) is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
- finally代码区的特点:无论try中的程序时正常执行了,还是出现了异常,最后都一定会执行finally区,除非JVM终止。
- 作用:一般用于在程序执行完成后进行资源的释放操作(专业级做法)。
JDK7开始提供了简单的资源释放方案:try-with-resource
try-catch-finally代码臃肿
public class try_with_resource {
public static void main(String[] args) {
// 注意:资源这里只能放置资源对象
// 什么是资源呢?资源都是会实现AutoCloseable接口,资源都会有一个close方法
// 永外之后,会被自动调用其close方法。
try(InputStream is = new FileInputStream("D:\\侯诗梦\\文件图片\\1686836573834.jpg");
OutputStream os = new FileOutputStream("NextStage\\FileDirectiory\\2.jpg"))
{
byte[] pic = new byte[1024];
int len;
while ((len=is.read(pic))!=-1){
os.write(pic,0,len);
}
}
catch (Exception e){
e.printStackTrace();
}
}
}
FileReader(文件字符输入流)
- 作用:以内存为准,可以把文件中的数据以字符的形式读入到内存中去。
构造器 | 说明 |
---|---|
public FileReader(File file) | 创建字符输入流管道与源文件接通 |
public FileReader(String pathname) | 创建字符输入流管道与源文件接通 |
方法名称 | 说明 |
---|---|
public int read() | 每次读取一个字符返回,如果发现没有数据可读会返回-1。 |
public int read(char[] buffer) | 每次用一个字符数组去读取数据,返回字符数组读取了多少个字符,如果发现没有数据可读返回-1. |
try (Reader reader1 = new FileReader("NextStage\\FileDirectiory\\itheima.txt")) {
int len;
while ((len = reader1.read())!=-1){
System.out.print((char) len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
try(FileReader rd = new FileReader("NextStage\\\\FileDirectiory\\\\itheima.txt")){
int len;
char[] bytes = new char[3];
while ((len=rd.read(bytes))!=-1){
System.out.print(new String(bytes,0,len));
}
}catch (Exception e){
e.printStackTrace();
}
}
FileWriter(文件字符输出流)
- 作用:以内存为基准,把内存中的数据以字符的形式写出到文件中去。
注意:字符输出流写出数据后,必须刷新流,或者关闭流,写出去的数据才能生效:内存中的数据写到缓冲区中,刷新后才能把数据写到硬盘里去。关闭包含刷新操作,书信操作,fw.flush()。缓冲区写满自动刷新。
字符流、字节流的使用场景小结
- 字节流适合做一切文件数据的拷贝(音视频,文本);字节流不适合读取中文内容输出。
- 字符流适合做文本文件的操作(读,写)。
IO流-缓冲流
字节缓冲流
字节缓冲流的呢作用:提高字节流读写数据的性能。
字节流复制数据
缓冲流复制数据:
原理:字节缓冲输入流自带了8KB缓冲池;字节缓冲输出流也自带了8KB缓冲池。
public static void main(String[] args) {
try (
InputStream is = new FileInputStream("NextStage\\FileDirectiory\\itheima.txt");
InputStream bis = new BufferedInputStream(is);
OutputStream os = new FileOutputStream("NextStage\\FileDirectiory\\copy.txt");
OutputStream bos = new BufferedOutputStream(os);
){
int len;
byte[] buffer = new byte[1024];
while ((len = bis.read(buffer))!=-1){
bos.write(buffer,0,len);
}
} catch (Exception e) {
e.printStackTrace();
}
}
BufferReader 字符缓冲输入流
BufferedWriter (字符缓冲输出流)
案例,把文件排序后复制到新的文件。
public class recoverFileWithReaderWriter {
public static void main(String[] args) {
try (
Reader r = new FileReader("NextStage\\FileDirectiory\\recoverFile.txt");
BufferedReader br = new BufferedReader(r);
Writer w = new FileWriter("NextStage\\FileDirectiory\\copyRecoverFile.txt");
BufferedWriter bw = new BufferedWriter(w);
){
String line;
List<String> l = new ArrayList<>();
while ((line=br.readLine())!=null){
l.add(line);
}
Collections.sort(l);
for (String ml:l
) {
bw.write(ml);
bw.newLine();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
原始流、缓冲流的性能分析【重点】
System.currentTimeMillis();
缓冲流按照一个一个字节数组的形式复制,速度极快,推荐使用!
并不一定缓冲流最好,看字节数组的大小。
IO流-转换流
引出问题:不同编码去读时会乱码
public static void main(String[] args) {
try (
// 1.得到文件的原始字节流
InputStream is = new FileInputStream("D:\\IdeaProject\\javasepromax\\NextStage\\FileDirectiory\\copy.txt");
// 2.把原始的字节输入流按照指定的字符集编码转换成字符输入流
Reader ios = new InputStreamReader(is,"GBK");
){
int len;
while ((len=ios.read())!=-1){
System.out.print((char) len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
OutputStreamWriter字符输出转换流
- 作用:可以控制写出去的字符使用什么字符集编码。
- 解决思路:获取字节输出流,再按照指定的字符集编码将其转换成字符输出流,以后写出去的字符就会用该字符集编码了。
public static void main(String[] args) {
try(
// 1.创建一个文件字节输出流
OutputStream os = new FileOutputStream("D:\\IdeaProject\\javasepromax\\NextStage\\FileDirectiory\\outputstreamwriter.txt");
// 2.把原始的字节输出流,按照规定的字符集编码转换成字符输出转换流。
OutputStreamWriter osw = new OutputStreamWriter(os,"GBK");
// 3.把字符输出流包装成缓冲字符输出流
BufferedWriter bw = new BufferedWriter(osw);
){
bw.write("窗前明w2月光");
}
catch (Exception e){
e.printStackTrace();
}
}
IO流-打印流
PrintStream / PrintWriter(打印流)
- 作用:打印流可以实现更方便、更高效的打印数据出去,能实现打印啥出去就是啥出去。
打印流不支持直接追加数据,想要追加数据先调用输出流去,再追加
PrintWriter ps = new PrintWriter(new FileOutputStream("路径地址"));
ps.println()
ps.write(97) //打印出是a
应用:可以把输出语句的打印位置改到某个文件中去。
文件中是乱码,输入的是数据类型。
如果需要用户密码隐藏不被看到:在对象类里面 private transient String password;
对象输出流
package ObjectOutputStreamArrayList;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
public class ObjectOutputStreamTest {
public static void main(String[] args) throws IOException {
List<User> users = new ArrayList<>();
users.add(new User("张三",23,"123456"));
users.add(new User("李四",21,"abc456"));
users.add(new User("王五",20,"123cde"));
try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("NextStage\\src\\ObjectOutputStreamArrayList\\object.txt"));) {
users.forEach(user -> {
try {
oos.writeObject(user);
} catch (Exception e) {
e.printStackTrace();
}
});
// oos.writeObject(new User("q1",23,"2e"));
// for (int i = 0; i < users.size(); i++) {
// oos.writeObject(users.get(i));
// }
} catch (Exception e) {
e.printStackTrace();
}
}
}
对象输入流
package ObjectOutputStreamArrayList;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.List;
public class ObjectInpuStreamTest {
public static void main(String[] args) {
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("NextStage\\src\\ObjectOutputStreamArrayList\\object.txt"))){
Object o;
while (true){
try {
o = ois.readObject();
System.out.println(o);
}catch (Exception e){
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
对象
package ObjectOutputStreamArrayList;
import java.io.Serializable;
public class User implements Serializable {
private String name;
private int age;
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", password='" + password + '\'' +
'}';
}
private transient String password;
public User(String name, int age, String password) {
this.name = name;
this.age = age;
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
IO框架
什么是框架?
- 解决某类问题,编写的一套类、接口等,可以理解成一个半成品,大多框架都是第三方研发的。
- 好处,在框架的基础上开发,可以得到优秀的软件架构,并能提高开发效率。
- 框架的形式:一般把类、接口等编译成class形式,再压缩成一个.jar结尾的文件发行出去。
什么是IO框架?
- 封装了Java提供的对文件、数据进行操作的代码,对外提供了更简单的方式来对文件进行操作,对文件进行读写。
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/d585a17b7ded7adc34ffd713847c8511.png
特殊文件
特殊文件: 普通文件.txt:内容随便写,格式随意。
属性文件.properites:属性文件的内容都是一些键值对信息,每行都是一个键值对,键不能重复
XML.xml:开始标签结尾标签组成。
为什么要用这些特殊文件?
存储多个用户的:用户名、密码 (属性文件可以区分用户名和密码)(XML也可以把用户名和密码的关系存进去)
存储多个用户的:用户名、密码、家乡、性别。(用XML文件存储非常清晰)
存储有关系的数据,作为系统的配置文件,作为信息进行传输。
特殊文件:
- 了解他们的特点、作用
- 学习使用程序读取它们里面的数据
- 学习使用程序把数据存储到这些文件中。
属性文件
- 键只能是键值对
- 键不能重复
- 文件后缀一般是.properties结尾的
public static void main(String[] args) throws Exception {
try (Reader r = new FileReader("NextStage\\src\\usernam.properties");
Writer w = new FileWriter("NextStage\\src\\usernam.properties");)
{
Properties properties = new Properties();
properties.load(r);
if (properties.containsKey("李方")){
properties.setProperty("李方","18");}
properties.store(w,"success");
}
catch (Exception e){
e.printStackTrace();
}
}
XML(全称EXtensible Markupp Languafe,可扩展标记语言)
- 本质是一种数据格式,可以用来存储复杂的数据结构和数据关系。
XML的特点
- XML中的“<标签名>”称为一个标签或一个元素,一般是成对出现的。
- XML中的标签名可以自己定义(可扩展),但必须要正确的嵌套。
- XML中只能有一个根标签。
- XML中的标签可以有属性
- 如果一个文件中放置的是XML格式的数据,这个文件就是XML文件,后缀一般要写成.XML 。
XML语法规则
XML的作用和应用场景
- 本质是一种数据格式,可以存储复杂的数据结构,和数据关系。
- 应用场景:经常用来做为系统的配置文件,或者作为一种特殊的数据结构,在网络中进行传输。(顺丰速运把快递信息以XML格式文件传给淘宝,让淘宝界面显示出来)
解析XML文件
- 使用程序读取XML文件中的数据。
注意:程序员并不需要自己写原始的IO流代码来解析XML,难度较大!也相当繁琐
其实,有很多开源的,好用的,解析XML的框架,最知名的是:Dom4j(第三方研发的)
如何使用程序把数据写出到XML文件中去?
不建议用dom4j做,推荐直接把程序里的数据拼接成XML格式,然后用IO流写出去!
补充知识:约束XML文件的编写
- 就是限制XML文件只能按照某种格式进行书写。
不把标签名写成英文,因为这样再读取就会出现错误
约束文档
- 专门用来限制XML书写格式的文档,比如:限制标签、属性应该怎么写。
约束文档的分类
- DTD文档
- Schema文档
XML文件中导入DTD约束文档:
日志
把程序运行的信息,记录到文件中去,方便程序员定位bug,并了解程序的执行情况等。
日志技术
- 可以将系统执行的信息,方便的记录到指定的位置(控制台、文件中、数据库中)。
- 可以随时以开关的形式控制日志的启停,无需侵入到源代码中去进行修改。
package logback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LogbackTest {
// 创建一个Logger日志对象 一个项目只需要一个日志对象
public static final Logger LOGER= LoggerFactory.getLogger("Logback.txt");
public static void main(String[] args) {
try {
LOGER.info("divide方法开始执行了~~~");
divide(10,0);
LOGER.info("divide方法执行完毕了~~~");
}catch (Exception e){
LOGER.error("divide方法出错了~~~");
}
}
private static void divide(int a,int b){
double c = a/b;
LOGER.info("执行结束了!!!!");
}
}
核心配置文件logback.xml
- 对Logback日志框架进行控制的。
Logback设置日志级别
什么是日志级别?
多线程
什么是线程?
- 线程(Thread)是一个程序内部的一条执行流程。
- 程序中如果只有一条执行流程,那这个程序就是单线程的程序。
多线程是什么?
- 多线程是指从软硬件上实现的多条执行流程的技术(多线程由CPU负责调度执行)。
如何在程序中创建出多条线程?
- Java是通过java.lang.Thread 类的对象来代表线程的。
多线程的创建方式之一:继承Thread类
- 定义一个子类MyThread继承线程类java.lang.Thread,重写run()方法
- 创建MyThread类的对象
- 调用线程对象的start方法启动线程(启动后还是执行run方法的)
方法一优缺点:
- 优点:编码简单
- 缺点:线程类已经继承Thread,无法继承其他类,不利于功能的扩展。
多线程的注意事项
- 启动线程必须是调用start方法,不是调用run方法。
- 不要把主线程任务放在启动子线程之前。
多线程的创建方式二:实现Runnable接口
- 定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法
- 创建MyRunnable对象
- 把MyRunnable任务对象交给Thread处理
- 调用线程对象的start()方法启动线程
package ThreadTest;
public class RunnableTest {
public static void main(String[] args) {
// 匿名内部类写法
Runnable r = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("这是子线程1:"+i);
}
}
};
new Thread(r).start();
// @FunctionalInterface,因此可以用lambdb简化
new Thread(()-> {
for (int i = 0; i < 5; i++) {
System.out.println("这是子线程2:"+i);
}
}).start();
// 主线程
for (int i = 0; i < 5; i++) {
System.out.println("主线程main"+i);
}
}
}
方式二的优缺点
- 优点:任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强。
- 缺点:需要多一个Runnable对象。
多线程创建方式三:利用Collable接口、FutureTask类来实现
package ThreadTest;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadTest3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 3、创建一个Callable的对象
Callable callable = new MyCallable(10);
// 4、把Callable的对象封装成一个FutureTask对象
// 未来任务对象的作用?
// 1、是一个任务对象,实现了Runnable对象
// 2、可以在线程执行完毕之后,用未来任务对象调用get方法获取线程执行完毕后返回结果
FutureTask<String> t = new FutureTask<String>(callable);
// 5、把任务对象交给Thread对象
Thread thread = new Thread(t);
thread.start();
// 6、获取线程执行完毕后返回的结果
// 注意:如果执行到这,假如上面的线程还没有执行完毕
System.out.println(t.get());
}
}
package ThreadTest;
import java.util.concurrent.Callable;
/**
* 1、让这个类实现Callable接口
* */
public class MyCallable implements Callable<String> {
private int n;
public MyCallable(int n) {
this.n = n;
}
// 2、重写cakk方法
@Override
public String call() throws Exception {
int sum = 0;
for (int i = 1; i <= n; i++) {
sum+=i;
}
return "从1到"+n+"的和是:"+sum;
}
}
Thread的常用方法
线程安全
什么是线程安全问题?
- 多个线程,同时操作同一个共享资源的时候,可能会出现业务安全问题。
- 线程安全问题出现的原因?
- 存在多个线程在同时执行
- 同时访问一个共享资源
- 存在修改该共享资源
用程序模拟线程安全问题
package thread_safe;
public class ThreadTest {
public static void main(String[] args) {
Account account = new Account("IC-BC-110",100000);
new DrawThread(account,"小明").start();
new DrawThread(account,"小红").start();
}
}
package thread_safe;
public class DrawThread extends Thread{
private Account acc;
public DrawThread(Account acc, String name){
super(name);
this.acc = acc;
}
public void run(){
acc.drawmoney(100000);
}
}
package thread_safe;
public class Account {
private String cardID;
private double money;
public Account() {
}
public Account(String cardID, double money) {
this.cardID = cardID;
this.money = money;
}
public String getCardID() {
return cardID;
}
public void setCardID(String cardID) {
this.cardID = cardID;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public void drawmoney(double money) {
Thread thread = Thread.currentThread();
String name = thread.getName();
if(this.money>=money) {
System.out.println(name+"来取钱"+money+"成功!");
this.money-=money;
System.out.println(name+"取钱后,余额剩余:"+this.money);
}else {
System.out.println(name+"来取钱:余额不足~");
}
}
}
线程同步
- 解决线程安全问题的方案
认识线程同步
- 让多个线程实现先后一次访问共享资源,这样就解决了安全问题。
方式一:同步代码块
同步锁可以用(this) this正好代表线程的共享资源。
静态方法使用 类名.class作为锁
方式二:同步方法
方式三:Lock锁
什么是线程通信?
- 当多个线程共同操作共享的资源时,线程间通过某种方式互相告知自己的状态,以相互协调,并避免无效的资源争夺。
线程通信的常见模型(生产者与消费者模型)
- 生产者线程负责生产数据
- 消费者线程负责消费生产者生产的数据。
- 注意:生产者生产完数据应该等待自己,通知消费者消费;消费者消费完数据也应该等待自己,再通知生产者生产!
package thread_communication;
public class ThreadTest {
public static void main(String[] args) {
Desk desk = new Desk();
new Thread(()->{
while (true) {desk.put();}
},"厨师1").start();
new Thread(()->{
while (true) {desk.put();}
},"厨师2").start();
new Thread(()->{
while (true) {desk.put();}
},"厨师3").start();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
desk.get();
}
}
},"吃货1").start();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
desk.get();
}
}
},"吃货2").start();
}
}
package thread_communication;
import ch.qos.logback.core.encoder.EchoEncoder;
import java.sql.Time;
import java.util.ArrayList;
public class Desk {
private ArrayList<String> list = new ArrayList<>();
public synchronized void put(){
String name = Thread.currentThread().getName();
try {
if(list.size()<1){
System.out.println(name+"做了一个包子");
list.add(name+"做的包子");
Thread.sleep(2000);
this.notifyAll();
this.wait();
}else {
this.notifyAll();
this.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void get(){
String name = Thread.currentThread().getName();
try {
if (list.size()==1){
System.out.println(name+"吃了"+list.get(0));
list.clear();
Thread.sleep(1000);
this.notifyAll();
this.wait();
}
}catch (Exception e){
e.printStackTrace();
}
}
}
线程池
什么是线程池?
- 线程池是一个可以复用线程的技术。
不适用线程池的问题
- 用户每发起一个请求,后台就需要创建一个新线程来处理,下次新任务来了肯定又要创建新线程处理的,而创建新线程的开销是很大的,并且请求过多时,肯定会产生大量的线程出来,这样会严重影响系统的性能。
线程池的工作原理
谁代表线程池?
- JDK5.0起提供了代表线程池的接口:ExecutorService。
如何得到线程池对象?
- 方式一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象。
- 方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象。
开了一个餐厅,不会来个客人招一个服务员,这样会造成资源浪费。招正式工。
线程池的注意事项
1、临时线程什么时候创建?
- 新任务提交时发现核心线程都在,任务队列也满了,并且还可以创建 临时线程,此时才会创建临时线程。
public class ThreadPoolTest1 {
public static void main(String[] args){
ExecutorService pool = new ThreadPoolExecutor(3,5,
8, TimeUnit.SECONDS,new ArrayBlockingQueue<>(4),
Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
Runnable r = new MyRunnable();
pool.execute(r); // 线程池会自动创建一个新线程,自动处理这个任务,会自动执行的
pool.execute(r); // 线程池会自动创建一个新线程,自动处理这个任务,会自动执行的
pool.execute(r); // 线程池会自动创建一个新线程,自动处理这个任务,会自动执行的
pool.execute(r); // 三个核心线程已经出来了,复用前面的核心线程
pool.execute(r); // 复用前面的核心线程
}
}
2、什么时候开始拒绝新任务?
- 核心线程和临时线程都在忙,任务队列也满了,新的任务过来时才会开始拒绝任务。
线程池处理Runnable任务
ExecutorService的常用方法。
Executors工具类实现线程池
实现类:ThreadPoolExecutor
工具类:Executors
Executore
计算密集型的任务:核心线程数量 = CPU的核数+1
IO密集型任务:读取文件,做通信比较。核心线程数量=CPU核数*2
Executors使用可能存在的陷阱
- 大型并发系统环境(京东、淘宝)中使用Executors如果不注意可能会出现系统风险
其他细节知识:并发、并行
进程:
- 正在运行的程序(软件)就是一个独立的进程
- 进程时属于进程的,一个进程中可以同时运行很多个线程。
- 进程中的多个线程其实时并发和并行执行的。
并发的含义
- 进程中的线程是由CPU负责调度执行的,但CPU能同时处理线程的数量有限,为了保证全部线程都能前往执行,CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发。
并行的理解
- 在同一个时刻上,同时有多个线程在呗CPU调度执行。
多线程到底是在怎么执行的?
并发和并行同时进行的。
线程的生命周期
- 也就是线程从生到死的过程中,经历的各种状态及状态转换。
- 理解线程这些状态有利于提升并发编程的理解能力。
Java线程的状态
- Jca总共定义了6种状态
- 6种状态都定义在Thread类的内部枚举类种。
悲观锁和乐观锁
- 悲观锁:一上来就加锁,没有安全感。每次只能一个线程进入访问完毕后,再解锁。线程安全,性能较差!
- 乐观锁:一开始不上锁,认为是没有问题的,大家一起跑,等要出现线程安全问题的时候才开始控制。线程安全,性能较好。
乐观锁:CAS算法
网络编程
- 可以让设备中的程序与网络上其他设备种的程序进行数据交互(实现网络通信的)。
UDP通信
多个客户端发送数据
package udp1;
import java.io.IOException;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
public class Client {
public static void main(String[] args) throws IOException {
// 1、创建客户端对象(发韭菜出去的人)
DatagramSocket socket = new DatagramSocket();
Scanner scanner = new Scanner(System.in);
while (true) {
String s = scanner.nextLine();
byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
if("exit".equals(s)){
break;
}
// 2、创建数据包对象封装要发出去的数据(创建一个韭菜盘子)
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getLocalHost(), 6666);
// 3、开始正式发送这个数据包的数据出去了。
socket.send(packet);
}
}
}
package udp1;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class Server {
public static void main(String[] args) throws IOException {
System.out.println("------服务端启动------");
// 1、创建一个服务器对象(创建一个接韭菜的人) 注册端口
DatagramSocket socket = new DatagramSocket(6666);
byte[] bytes = new byte[1024*64]; // 64KB
// 2、创建一个数据包对象,用于接收数据(创建一个韭菜盘子)。
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
while (true) {
// 3、开始正式使用数据包来接收客户端发来的数据
socket.receive(packet);
// 获取本次数据包接收了多少数据。
int len = packet.getLength();
// 4、从字节数组,爸接收到的数据直接打印出来。
String rs = new String(bytes,0,len);
System.out.println(rs);
System.out.println(packet.getPort());
System.out.println(packet.getAddress().getHostAddress());
}
}
}
TCP通信-快速入门
package tcp1;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
public class Client {
public static void main(String[] args) throws Exception {
// 1、创建Socket对象,并同时请求与服务端程序的连接。
Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
// 2、从socket通信管道中得到一个字节输出流,用来发数据给服务端程序。
OutputStream os = socket.getOutputStream();
// 3、把低级的字节输出流包装成数据输出流
DataOutputStream dos = new DataOutputStream(os);
// 4、开始写数据出去
dos.writeUTF("hello,你好啊");
dos.close();
socket.close(); //释放连接资源
}
}
package tcp1;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Service {
public static void main(String[] args) throws Exception {
System.out.println("-------服务端启动成功------");
// 1、创建ServerSocket对象,同时为服务端注册端口。
ServerSocket serverSocket = new ServerSocket(8888);
// 2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
Socket socket = serverSocket.accept();
// 3、从socket通信管道中得到一个字节输出流。
InputStream is = socket.getInputStream();
// 4、把原始的字节输入流包装成数据输入流
DataInputStream dis = new DataInputStream(is);
// 5、使用数据输入流读取客户端发送过来的消息
String rs = dis.readUTF();
System.out.println(rs);
dis.close();
serverSocket.close();
}
}
TCP-多发多收
package tcp1;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
public class Client {
public static void main(String[] args) throws Exception {
// 1、创建Socket对象,并同时请求与服务端程序的连接。
Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
// 2、从socket通信管道中得到一个字节输出流,用来发数据给服务端程序。
OutputStream os = socket.getOutputStream();
// 3、把低级的字节输出流包装成数据输出流
DataOutputStream dos = new DataOutputStream(os);
Scanner sc = new Scanner(System.in);
// 4、开始写数据出去
while (true) {
System.out.println("请输入你的字符串:");
String s = sc.nextLine();
dos.writeUTF(s);
dos.flush();
if("exit".equals(s)) {
dos.close();
socket.close(); //释放连接资源
break;
}
}
}
}
package tcp1;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Service {
public static void main(String[] args) throws Exception {
System.out.println("-------服务端启动成功------");
// 1、创建ServerSocket对象,同时为服务端注册端口。
ServerSocket serverSocket = new ServerSocket(8888);
// 2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
Socket socket = serverSocket.accept();
// 3、从socket通信管道中得到一个字节输出流。
InputStream is = socket.getInputStream();
// 4、把原始的字节输入流包装成数据输入流
DataInputStream dis = new DataInputStream(is);
while (true) {
try {
// 5、使用数据输入流读取客户端发送过来的消息
String rs = dis.readUTF();
System.out.println(rs);
} catch (IOException e) {
System.out.println(socket.getRemoteSocketAddress()+"离线了");
dis.close();
socket.close();
break;
}
}
}
}
TCP通信-支持与多个客户端同时通信
目前上面的服务端程序,是否可以支持与多个客户端同时通信?
- 不可以的
- 因为服务端现在只有一个主线程,只能处理一个客户端的消息。
主线程负责接收客户端的连接: Socket socket = serverSocket.accept(); //使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
然后把客户端对应的socket通信管道,交给一个独立的线程负责处理。
TCP通信 端口转发,群聊
代码在grop_chat中
TCP实现一个简易版的BS架构
Java高级技术
单元测试
定义
- 就是针对最小的功能单元(方法),编写测试代码对其进行正确性测试。
咱们之前是如何进行单元测试的?有啥问题?
- 只能在main方法编写测试代码,去调用其他方法进行测试。
- 无法实现自动化测试,一个方法测试失败,可能影响其他方法测试。
- 无法得到测试的报告,需要程序员自己去观察测试是否成功。
Junit单元测试框架
- 可以用来对方法进行测试,它是第三方公司开源出来的(很多开发工具已经集成了Junit框架,比如IDEA)
优点
- 可以灵活的编写测试代码,可以针对某个方法执行测试,也支持一键完成对全部方法的自动化测试,且各自独立。
- 不需要程序员去分析测试的结果,会自动生成测试报告出来。
Junit单元测试-快速入门
需求
- 某个系统,有多个业务方法,请使用Junit单元测试框架,编写测试代码,完成对这些方法的正确性测试。
具体步骤
- 将Junit框架的jar包导入到项目中(注意:IDEA集成了Junit框架,不需要我们自己手工导入了)
- 为需要测试的业务类,定义对应的测试类,并为每个业务方法,编写对应的测试方法(必须:公共、无参、无返回值)
- 测试方法上必须声明@Test注解,然后在测试方法中,编写代码调用被测试的业务方法进行测试;
- 开始测试:选中测试方法,右键选择“JUnit”运行,如果测试通过则是绿色;如果测试失败,则是红色。
package d1_junit;
public class StringUtil {
public static void printNumber(String name){
if(name==null){
return;
}
System.out.println("名字长度是:"+name.length());
}
public static int getMaxIndex(String data){
if(data==null){
return -1;
}
return data.length()-1;
}
}
package d1_junit;
import org.junit.*;
import java.io.PrintStream;
import java.net.PortUnreachableException;
public class StringUtilTest {
@Before
public void test1(){
System.out.println("test1");
}
@After
public void test2(){
System.out.println("test2");
}
@BeforeClass
public static void test11(){
System.out.println("test11");
}
@AfterClass
public static void test22(){
System.out.println("test22");
}
@Test
public void testPrintNumber(){
StringUtil.printNumber("admin");
StringUtil.printNumber(null);
}
@Test
public void testGetMaxIndex(){
int index = StringUtil.getMaxIndex("hello");
System.out.println(index);
Assert.assertEquals("长度输出错误",4,index);
}
}
反射
认识反射、获取类
- 反射就是:加载类,并允许以编程的方式解剖类中的各种成分(成员变量、方法、构造器等)
反射主要用于框架
获取Class对象的三种方式
package d2_reflect;
public class TestClass {
public static void main(String[] args) throws Exception {
Class<Student> c1 = Student.class;
System.out.println(c1.getName());
System.out.println(c1.getSimpleName());
Class<?> c2 = Class.forName("d2_reflect.Student");
System.out.println(c2.getSimpleName());
Student s = new Student();
Class c3 = s.getClass();
System.out.println(c2==c3);
}
}
获取类的构造器
package d2_reflect;
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
public class ClassTest2 {
@Test
public void testGetConstrucors() throws Exception {
Class c1 = Cat.class;
Constructor[] constructors = c1.getConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor.getName()+"->"+constructor.getParameterCount());
}
Constructor[] constructors1 = c1.getDeclaredConstructors();
for (Constructor constructor : constructors1) {
System.out.println(constructor.getName()+"->"+constructor.getParameterCount());
}
Constructor constructor = c1.getDeclaredConstructor();
System.out.println(constructor.getName()+"->"+constructor.getParameterCount());
Constructor constructor2 = c1.getDeclaredConstructor(String.class,int.class);
System.out.println(constructor2.getName()+"->"+constructor2.getParameterCount());
}
}
package d2_reflect;
import org.junit.Test;
import java.lang.reflect.Constructor;
public class Test2Constructor {
@Test
public void testGetCatConstructor() throws Exception {
Class c = Cat.class;
Constructor constructor = c.getDeclaredConstructor();
constructor.setAccessible(true); //禁止检查访问权限
Cat cat = (Cat) constructor.newInstance();
System.out.println(cat);
Constructor constructor1 = c.getDeclaredConstructor(String.class,int.class);
constructor1.setAccessible(true);
Cat cat1 = (Cat) constructor1.newInstance("卡迪猫",3);
System.out.println(cat1);
}
}
获取类的成员变量
package d2_reflect;
import java.lang.reflect.Field;
public class Test3Field {
public static void main(String[] args) throws Exception {
// 1、反射第一步:必须是先得到类的Class对象
Class cat = Cat.class;
// 2、获取类的全部成员变量
Field[] filelds = cat.getDeclaredFields();
// 3、遍历成员变量数组
for (Field fileld : filelds) {
System.out.println(fileld.getName()+":"+fileld.getType());
}
// 4、定位某个成员变量
Field fname = cat.getDeclaredField("name");
System.out.println(fname.getName()+"--->"+fname.getType());
Field fage = cat.getDeclaredField("age");
System.out.println(fage.getName()+"--->"+fname.getType());
// 赋值
Cat c1 = new Cat();
fname.setAccessible(true);
fname.set(c1,"咖啡猫");
System.out.println(c1);
// 取值
String catname = (String) fname.get(c1);
System.out.println(catname);
}
}
获取类的成员方法
package d2_reflect;
import org.junit.Test;
import java.lang.reflect.Method;
public class Test3Method {
@Test
public void testGetMethods() throws Exception {
// 1、反射第一步:先得到Class对象
Class c= Cat.class;
// 2、获取类的全部成员方法
Method[] methods = c.getDeclaredMethods();
// 3、遍历这个数组中的每个方法对象
for (Method method : methods) {
System.out.println(method.getName()+"--->"+
method.getParameterCount()+"--->"+method.getReturnType());
}
// 4、获取某个方法对象
Method run = c.getDeclaredMethod("run");
System.out.println(run.getName()+"--->"
+run.getParameterCount()+"--->"+ run.getReturnType());
Method eat = c.getDeclaredMethod("eat",String.class);
System.out.println(eat.getName()+"-->"+eat.getParameterCount()+"-->"+eat.getReturnType());
Cat cat = new Cat();
run.setAccessible(true); // 禁止检查访问权限
Object os = run.invoke(cat); //调用无参数的run方法,用cat对象触发调用的。
System.out.println(os);
eat.setAccessible(true);
Object os1 = eat.invoke(cat,"小鱼儿");
System.out.println(os1);
}
}
作用、应用场景
反射的作用?
- 基本作用:可以得到一个类的全部成分然后操作。
- 可以破环封装性
- 最重要的用途:适合做Java的框架,基本上,主流的框架都会基于反射设计出一些通用的功能。
使用反射做一个简易版的框架
注解
- 就是Java代码里的特殊标记,比如:@Override,@Test等,作用是:让其他程序根据注解信息来决定怎么执行该程序。
- 注意:注解可以用在类上、构造器上、方法上、成员变量上、参数上、等位置处。
自定义注解
- 就是自己定义注解
元注解
- 指的是:修饰注解的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest1 {
String aaa();
boolean bbb() default true;
String[] ccc();
}
注解的解析
什么是注解的解析?
- 就是判断类上、方法上、成员变量上是否存在注解,并把注解的内容给解析出来。
如何解析注解 - 指导思想:要解析谁上面的注解,就应该先拿到谁。
- 比如要解析类上面的注解,则应该先获取该类的Class对象,再通过Class对象解析其上面的注解。
- 比如要解析成员方法上的注解,则应该获取到该成员方法的Method对象,再通过Method对象解析去其上面的注解。
- Class、Method、Field,Constructor、都实现了AnnotatedElement接口,它们都拥有解析注解的能力。
解析注解的案例
package d3_annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest4 {
String value();
double aaa() default 100;
String[] bbb();
}
package d3_annotation;
@MyTest4(value = "蜘蛛侠",aaa = 100.5, bbb={"1","2"})
public class Demo {
@MyTest4(value = "孙悟空",aaa = 102.5, bbb={"3","4"})
public void test1(){
}
}
package d3_annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
public class AnnotationTest3 {
public static void main(String[] args) throws Exception {
Class c = Demo.class;
if(c.isAnnotationPresent(MyTest4.class)){
MyTest4 myTest4 = (MyTest4) c.getDeclaredAnnotation(MyTest4.class);
System.out.println(myTest4.value());
System.out.println(myTest4.aaa());
System.out.println(Arrays.toString(myTest4.bbb()));
}
Method method = (Method) c.getMethod("test1");
if(method.isAnnotationPresent(MyTest4.class)){
MyTest4 myTest4 = method.getDeclaredAnnotation(MyTest4.class);
System.out.println(myTest4.value());
}
}
}
模拟Junit框架
package d3_annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyTest {
}
package d3_annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class AnnotationTest4 {
@MyTest
public void test1(){
System.out.println("===test1===");
}
// @MyTest
public void test2(){
System.out.println("===test2===");
}
@MyTest
public void test3(){
System.out.println("===test3===");
}
@MyTest
public void test4(){
System.out.println("===test4===");
}
public static void main(String[] args) throws Exception {
AnnotationTest4 a = new AnnotationTest4();
Class c = AnnotationTest4.class;
Method[] methods = c.getDeclaredMethods();
for (Method method : methods) {
if(method.isAnnotationPresent(MyTest.class)){
method.invoke(a);
}
}
}
}
动态代理
程序为什么需要代理?代理长什么样?
package d4_proxy;
public class BigStar implements Strar{
private String name;
public BigStar(String name) {
this.name = name;
}
public String sing(String geming){
System.out.println(name+"开始唱"+geming);
return "谢谢~谢谢~~";
}
public void dance(){
System.out.println(name+"跳舞");
}
}
package d4_proxy;
public interface Strar {
String sing(String geming);
void dance();
}
package d4_proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyUtil {
public static Strar creatProxy(BigStar bigStar){
/*
newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
* 参数1:用于指定一个类加载器
参数2:指定生成的代理长什么样子,也就是哪些方法
参数3:用来指定生成的代理对象要干什么事情
**/
Strar starProxy = (Strar) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),
new Class[]{Strar.class}, new InvocationHandler() {
@Override //回调方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().equals("sing")){
System.out.println("准备话筒,收钱20w");
}else if(method.getName().equals("dance")){
System.out.println("准备场地,收钱100w");
}
return method.invoke(bigStar,args);
}
});
return starProxy;
}
}
package d4_proxy;
public class Test {
public static void main(String[] args) {
BigStar bigStar = new BigStar("杨超越");
Strar strarProxy = ProxyUtil.creatProxy(bigStar);
String rs = strarProxy.sing("好日子");
System.out.println(rs);
}
}