- 进阶-IO流
- password=123456
- key相同会覆盖
- 不建议使用冒号
- 第二种方式
- 注意
- 第三种方式(JDK8新特性)
- 线程的生命周期
- 线程的常用方法
- 线程安全⭐
- 守护线程
- 定时器
- wait()与notify()
- ⭐获取Class的三种方式
- 获取Class后能干的事
- 获取类路径下文件的绝对路径
- 资源绑定器
- 拓展-类加载器
- 获取Field(了解即可!)
- ⭐通过反射访问对象属性-Field
- 可变长度参数-插入知识点
- 获取Method(了解即可!)
- ⭐通过反射调用方法-Method
- 获取Constructor(了解即可!)
- ⭐通过反射调用构造方法-Constructor
- ⭐获取父类和父接口
- 进阶-注解
进阶-IO流
基本概念
什么是IO?
I:Input O:Output
通过IO可以完成硬盘文件的读和写。
图解:
IO流的分类?
有多种分类方式:
按照流的方向进行分类:
以内存为参照物,
向内存中去,叫做输入(Input),或者叫做读(Read)。
从内存中出来,叫做输出(Output),或者叫做写(Write)。
按照读取数据方式不同进行分类:
按照字节的方式读取数据:一次读取一个字节byte,等同于一次读取8个二进制位。
这种流是万能的,什么类型的文件都可以读取。包括:文本文件,图片,声音文件,视频文件等….
eg:假设文件file1.txt,采用字节流的话是这样读的:
a中国bc张三fe
第一次读:一个字节,正好读到’a’
第二次读:一个字节,正好读到’中’字符的一半。
第三次读:一个字节,正好读到’中’字符的另外一半。
按照字符的方式读取数据:一次读取一个字符,这种流是为了方便读取普通文本文件而存在的,
这种流不能读取:图片、声音、视频等文件。只能读取纯文本文件,连word文件都无法读取。
eg: 假设文件file1.txt,采用字符流的话是这样读的:
a中国bc张三fe
第一次读:’a’字符(’a’字符在windows系统中占用1个字节。)
第二次读:’中’字符(’中’字符在windows系统中占用2个字节。)
综上所述:流的分类
输入流,输出流
字节流,字符流
- Java中的IO流都已经写好了,我们程序员不需要关心。
我们最主要还是掌握,在java中已经提供了哪些流,每个流的特点是什么,
每个流对象上的常用方法有哪些?
java中所有的流都是在:java.io.*;下。
java中主要还是研究:怎么new流对象。
调用流对象的哪个方法是读,哪个方法是写。
JAVA IO流中的四大家族
四大家族的首领:
java.io.InputStream 字节输入流
java.io.OutputStream 字节输出流
java.io.Reader 字符输入流
java.io.Writer 字符输出流
四大家族的首领都是抽象类。(abstract class)
所有的流都实现了:
java.io.Closeable接口,都是可关闭的,都有close()方法。
流毕竟是一个管道,这个是内存和硬盘之间的通道,用完之后一定要关闭,
不然会耗费(占用)很多资源。养成好习惯,用完流一定要关闭。
所有的输出流都实现了:
java.io.Flushable接口,都是可刷新的,都有flush()方法。
养成一个好习惯,输出流在最终输出之后,一定要记得flush()刷新一下。
这个刷新表示将通道/管道当中剩余未输出的数据强行输出完(清空管道!)。
刷新的作用就是清空管道。
注意:如果没有flush()可能会导致丢失数据。
注意:在java中只要“类名”以Stream结尾的都是字节流。以“Reader/Writer”结尾的都是字符流。
java.io包下需要掌握的流有16个
- 文件专属:
字节流:byte[]
java.io.FileInputStream(掌握)
java.io.FileOutputStream(掌握)
字符流:char[]
java.io.FileReader
java.io.FileWriter
- 转换流:(将字节流转换成字符流)
java.io.InputStreamReader
java.io.OutputStreamWriter
- 缓冲流专属:
java.io.BufferedReader
java.io.BufferedWriter
java.io.BufferedInputStream
java.io.BufferedOutputStream
- 数据流专属:
java.io.DataInputStream
java.io.DataOutputStream
- 标准输出流:
java.io.PrintWriter
java.io.PrintStream(掌握)
- 对象专属流:(序列化与反序列化)
java.io.ObjectInputStream(掌握)
java.io.ObjectOutputStream(掌握)
————⭐字节流⭐————————
FileInputStream(读)⭐
知识点
- 文件字节输入流,万能的,任何类型的文件都可以采用这个流来读。
-
使用步骤
public class FileInputStreamTest01 {public static void main(String[] args) {//先创建fis,防止空指针异常。FileInputStream fis = null;try {// 创建文件字节输入流对象// 文件路径:D:\course\JavaProjects\02-JavaSE\temp (IDEA会自动把\编程\\,因为java中\表示转义)// 以下都是采用了:绝对路径的方式。//FileInputStream fis = new FileInputStream("D:\\course\\JavaProjects\\02-JavaSE\\temp");// 写成这个/也是可以的。fis = new FileInputStream("D:/course/JavaProjects/02-JavaSE/temp");// 开始读int readData = fis.read(); // 这个方法的返回值是:读取到的“字节”本身。System.out.println(readData); //97readData = fis.read();System.out.println(readData); //98readData = fis.read();System.out.println(readData); //99readData = fis.read();System.out.println(readData); //100readData = fis.read();System.out.println(readData); //101readData = fis.read();System.out.println(readData); //102// 已经读到文件的末尾了,再读的时候读取不到任何数据,返回-1.readData = fis.read();System.out.println(readData);//-1readData = fis.read();System.out.println(readData);//-1readData = fis.read();System.out.println(readData);//-1} catch (FileNotFoundException e) {e.printStackTrace();//捕获的是FileInputStream相关的异常} catch (IOException e) {e.printStackTrace();//捕获的是read相关的异常} finally {// 在finally语句块当中确保流一定关闭。if (fis != null) { // 避免空指针异常!// 关闭流的前提是:流不是空。流是null的时候没必要关闭。try {fis.close();} catch (IOException e) {e.printStackTrace();}}}}}
使用循环改写以上代码: ```java package allTest;
import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException;
public class FileInputStreamTest01 { public static void main(String[] args) { FileInputStream fis = null; //第一步 try { //第二步,然后快捷键生成try..catch fis = new FileInputStream(“C:\Users\Administrator\Desktop\JAVA笔记\Mu.txt”);
while (true){int readDate=fis.read();if (readDate==-1){break;}System.out.println(readDate);}⭐⭐//改造while循环//第六步:通过read读取数据,快捷键生成catchint readDate = 0;while(readDate = fis.read()!= -1){System.out.println(readDate);}} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} finally { //第三步:手写finallyif (fis!=null){ //第四步:判断fis是否为空try {fis.close(); //第五步:关闭流,快捷键生成try catch} catch (IOException e) {e.printStackTrace();}}}}
}
**分析以上程序的缺点:**<br />一次读取一个字节byte,这样内存和硬盘交互太频繁,基本上时间/资源都耗费在交互上了。<br />能不能一次读取多个字节呢?可以!**使用byte[]数组进行改写:**```javapackage com.bjpowernode.java.io;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;/*int read(byte[] b)一次最多读取 b.length 个字节。减少硬盘和内存的交互,提高程序的执行效率。往byte[]数组当中读。*/public class FileInputStreamTest03 {public static void main(String[] args) {FileInputStream fis = null;try {// 相对路径的话呢?相对路径一定是从当前所在的位置作为起点开始找!// IDEA默认的当前路径是哪里?工程Project的根就是IDEA的默认当前路径。//fis = new FileInputStream("tempfile3");//fis = new FileInputStream("chapter23/tempfile2");//fis = new FileInputStream("chapter23/src/tempfile3");fis = new FileInputStream("chapter23/src/com/bjpowernode/java/io/tempfile4");// 开始读,采用byte数组,一次读取多个字节。最多读取“数组.length”个字节。byte[] bytes = new byte[4]; // 准备一个4个长度的byte数组,一次最多读取4个字节。// 这个方法的返回值是:读取到的字节数量。(不是字节本身)int readCount = fis.read(bytes);System.out.println(readCount); // 第一次读到了4个字节。// 将字节数组全部转换成字符串//System.out.println(new String(bytes)); // abcd// 不应该全部都转换,应该是读取了多少个字节,转换多少个。System.out.println(new String(bytes,0, readCount));readCount = fis.read(bytes); // 第二次只能读取到2个字节。System.out.println(readCount); // 2// 将字节数组全部转换成字符串//System.out.println(new String(bytes)); // efcd// 不应该全部都转换,应该是读取了多少个字节,转换多少个。System.out.println(new String(bytes,0, readCount));readCount = fis.read(bytes); // 1个字节都没有读取到返回-1System.out.println(readCount); // -1} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} finally {if (fis != null) {try {fis.close();} catch (IOException e) {e.printStackTrace();}}}}}
⭐FileInputStream最终版
package IO;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;public class FileInputStreamTest04 {public static void main(String[] args) {//第一步:创建fis= nullFileInputStream fis = null;//第二步:创建对象,选择要读取的文件。使用快捷键生成try catchtry {fis = new FileInputStream("chapter23/src/IO/mu");//第五步:使用read读取字节。//创建byte数组byte[] bytes = new byte[4];/*while(true){int readCount = fis.read(bytes);if(readCount == -1){break;}// 把byte数组转换成字符串,读到多少个转换多少个。System.out.print(new String(bytes, 0, readCount));}*///改写以上while循环int readCount = 0;//-1:读不到数据时,返回-1while ((readCount = fis.read(bytes))!= -1){ // 这个方法的返回值是:读取到的字节数量。(不是字节本身)//将byte数组转换为String并输出。System.out.print(new String(bytes,0,readCount));}} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} finally { //第三步:写finally,用来关闭流if (fis!=null){ //第四步:使用if判断fis是否为空,不为空则关闭。try {fis.close();} catch (IOException e) {e.printStackTrace();}}}}}
FileInputStream其他常用方法
- int available(): 返回流当中剩余的没有读到的字节数量。
- 作用:可以不需要使用whlie循环直接输出数据。
- long skip(long n): 跳过几个字节不读。
使用available()改写程序:这种方法不适合太大的文件,因为byte[]不可以太大、
package IO;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;public class FileInputStreamTest05 {public static void main(String[] args) {//第一步:创建fis= nullFileInputStream fis = null;//第二步:创建对象,选择要读取的文件。使用快捷键生成try catchtry {fis = new FileInputStream("chapter23/src/IO/mu");⭐⭐//通过available()方法获取剩余的字节数。从开头获取,就是总字节数。fis.available();//第五步:使用read读取字节。//创建byte数组byte[] bytes = new byte[fis.available()];//读取数组fis.read(bytes);System.out.println(new String(bytes));⭐⭐} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} finally { //第三步:写finally,用来关闭流if (fis!=null){ //第四步:使用if判断fis是否为空,不为空则关闭。try {fis.close();} catch (IOException e) {e.printStackTrace();}}}}}
FileOutputStream(写)⭐
知识点
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 { ⭐//若myfile文件不存在,会自动创建。 //fos = new FileOutputStream(“myfile”);
⭐//若文件存在,则会将原文件清空,再写入。 谨慎使用!!!//fos = new FileOutputStream("chapter23/src/IO/mu");⭐// 以追加的方式在文件末尾写入。不会清空原文件内容。 建议使用!fos = new FileOutputStream("chapter23/src/IO/mu",true);//写byte[] bytes = {97,98,99};//将bytes数组全部写出fos.write(bytes);// 将byte数组的一部分写出!fos.write(bytes,0,1);//写字符串String s = "穆徐";//将字符串转换为数组--getBytes()byte[] bytes1 = s.getBytes();fos.write(bytes1);⭐// 写完之后,最后一定要刷新⭐fos.flush();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} finally {if (fos!=null){try {fos.close();} catch (IOException e) {e.printStackTrace();}}}}
}
<a name="RPcFt"></a>### 文件复制<a name="zEClS"></a>#### 原理<a name="DlBie"></a>#### 代码1. 使用FileInputStream + FileOutputStream完成文件的拷贝1. 拷贝的过程应该是一边读,一边写。1. 使用字节流拷贝文件的时候,文件类型随意,万能的。```javapackage IO;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;public class Copy01 {public static void main(String[] args) {FileInputStream fis = null;FileOutputStream fos = null;try {// 创建一个输入流对象fis = new FileInputStream("C:\\Users\\Administrator\\Desktop\\testmu\\test.txt");// 创建一个输出流对象fos = new FileOutputStream("C:\\Users\\Administrator\\Desktop\\testmuu\\test.txt");⭐ // 最核心的:一边读,一边写 ⭐byte[] bytes = new byte[1024*1024]; //1MB(一次最多拷贝1Mb)int readCount = 0;while ((readCount = fis.read(bytes))!= -1){fos.write(bytes,0,readCount);}//输出流,最后一定要flush()一下,记住,谢谢。fos.flush();}catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} finally {//分开try,因为如果一起try的时候,一个出现异常,可能会影响到另一个流的关闭。if (fis!=null){try {fis.close();} catch (IOException e) {e.printStackTrace();}}if (fos!=null){try {fos.close();} catch (IOException e) {e.printStackTrace();}}}}}
—————-字符流——————————-
FileReader(读)
知识点
import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException;
public class FileReaderTest01 { public static void main(String[] args) { FileReader fr = null; try { fr = new FileReader(“myfile”);
char[] chars = new char[4]; //一次最多读取4个字符。/* 读int readCount = fr.read(chars);System.out.println(new String(chars,0,readCount)); //ab穆徐*/int readCount = 0;while ((readCount=fr.read(chars))!=-1){System.out.print(new String(chars,0,readCount));}} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} finally {if (fr!=null){try {fr.close();} catch (IOException e) {e.printStackTrace();}}}}
}
<a name="zoJzA"></a>### FileWrite(写)<a name="IhVun"></a>#### 知识点1. 文件字符输出流。写。1. 只能输出普通文本。<a name="ssGG1"></a>#### 使用步骤```javapackage IO;import java.io.FileWriter;import java.io.IOException;public class FileWriteTest01 {public static void main(String[] args) {FileWriter fw = null;try {fw = new FileWriter("NBA",true);char[] chars = {'詹','姆','斯'};fw.write(chars);//优点,可以直接写字符串,不通过数组。fw.write(",科比布莱恩特");//刷新fw.flush();} catch (IOException e) {e.printStackTrace();}finally {if (fw!=null){try {fw.close();} catch (IOException e) {e.printStackTrace();}}}}}
文件复制
代码
package 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 fr = null;FileWriter fw = null;try {fr = new FileReader("C:\\Users\\Administrator\\Desktop\\testmu\\test.txt");fw = new FileWriter("C:\\Users\\Administrator\\Desktop\\testmuu\\new.txt");//一边读一边写char[] chars = new char[1024*512];int readCount = 0;while ((readCount=fr.read(chars))!=-1){fw.write(chars,0,readCount);}//刷新fw.flush();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e){e.printStackTrace();}finally {if (fr!=null){try {fr.close();} catch (IOException e) {e.printStackTrace();}}if (fw!=null){try {fw.close();} catch (IOException e) {e.printStackTrace();}}}}}
—————-缓冲流 转换流——————-
BufferedReader
知识点
import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException;
public class BufferedReaderTest01 { public static void main(String[] args) { FileReader fr = null; BufferedReader br = null;
try {fr = new FileReader("NBA");⭐//当一个流的构造方法中需要一个流的时候,这个被传进来的流叫做:节点流。//外部负责包装的这个流,叫做:包装流,还有一个名字叫做:处理流。//当前这个程序来说: 节点流: FileReader// 包装流/处理流:BufferedReader⭐br = new BufferedReader(fr);/* //readLine() 读一行String one=br.readLine();System.out.println(one);//readLine() 再读一行String two=br.readLine();System.out.println(two);*///使用while循环改写,读不到就返回Null,所以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();}}}}
}
<a name="aVbdL"></a>### InputStreamReader<a name="tFR7K"></a>#### 原理(FileInputStream(字节) -> FileReader(字符))<a name="Xq51x"></a>#### 代码```javapackage IO;import java.io.*;public class InputStreamReaderTest01 {public static void main(String[] args) {FileInputStream fis = null;InputStreamReader isr = null;BufferedReader br=null;try {//字节流fis=new FileInputStream("NBA");/*将fis转换为字符流* isr是包装流,fis是节点流。* */isr = new InputStreamReader(fis);/** 这个构造方法只能传字符流,不能传字节流。* isr是节点流,br是包装流。* */br = new BufferedReader(isr);⭐//合并以上三行代码br = new BufferedReader(new InputStreamReader(new FileInputStream("NBA")));String s =null;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();}}}}}
BufferedWriter
知识点
import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException;
public class BufferedWriterTest01 { public static void main(String[] args) { FileWriter fw = null; BufferedWriter bw = null;
try {fw = new FileWriter("LJ",true);bw = new BufferedWriter(fw);bw.newLine();//写入一个行分隔符bw.write("勒布朗");bw.newLine();bw.write("詹姆斯");bw.flush();} catch (IOException e) {e.printStackTrace();}finally {if (bw!=null){try {bw.close();} catch (IOException e) {e.printStackTrace();}}}}
}
<a name="eRSFJ"></a>## -----------数据流---------------------<a name="TOQOQ"></a>### DataOutputStream<a name="cBEb7"></a>#### 知识点1. 数据专属的流。1. 这个流可以将数据连同数据的类型一并写入文件。1. 注意:这个文件不是普通文本文档。(这个文件使用记事本打不开。)<a name="b88P0"></a>#### 使用步骤```javapackage com.bjpowernode.java.io;import java.io.DataOutputStream;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 = 300;long l = 400L;float f = 3.0F;double d = 3.14;boolean sex = false;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();}}
DateInputStream
知识点
- 数据字节输入流。
- DataOutputStream写的文件,只能使用DataInputStream去读。并且读的时候你需要提前知道写入的顺序。
- 读的顺序需要和写的顺序一致。才可以正常取出数据。
使用步骤
```java package com.bjpowernode.java.io;
import java.io.DataInputStream; import java.io.FileInputStream;
/* 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(); long l = dis.readLong(); float f = dis.readFloat(); double d = dis.readDouble(); boolean sex = dis.readBoolean(); char c = dis.readChar();
System.out.println(b);System.out.println(s);System.out.println(i + 1000);System.out.println(l);System.out.println(f);System.out.println(d);System.out.println(sex);System.out.println(c);dis.close();}
}
<a name="p1NDN"></a>## -----------标准输出流-----------------<a name="n2q54"></a>### PrintStream<a name="rGdCw"></a>#### 知识点1. 标准的字节输出流。默认输出到控制台。1. 标准输出流不需要手动close()关闭。1. 回顾之前System类使用过的方法和属性System.gc(); 建议启动垃圾回收器<br />System.currentTimeMillis(); 获取自1970年1月1日到系统当前时间的总毫秒数。<br /> PrintStream ps2 = System.out; <br /> System.exit(0); 退出JVM。<br /> System.arraycopy(....); 数组拷贝<a name="K0zSz"></a>#### 使用步骤```javapackage IO;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.PrintStream;public class PrintStreamTest01 {public static void main(String[] args) {//联合起来写System.out.println("LJ");//分开写PrintStream ps = System.out; //返回一个PrintStream类型ps.println("KB");ps.println("AD");// 可以改变标准输出流的输出方向吗? 可以try {// 标准输出流不再指向控制台,指向“log”文件。PrintStream ps2 = new PrintStream(new FileOutputStream("log"));// 修改输出方向,将输出方向修改到"log"文件。System.setOut(ps2);System.out.println("LJ");System.out.println("KB");ps2.flush();} catch (FileNotFoundException e) {e.printStackTrace();}}}
应用:日志工具
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 out = new PrintStream(new FileOutputStream("log.txt", true));// 改变输出方向System.setOut(out);// 日期当前时间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("用户尝试进行登录,验证失败");Logger.log("我非常喜欢这个记录日志的工具哦!");}}
—————-File类——————————-
知识点
- File类和四大家族没有关系,所以FIle类不能完成文件的读和写。
- File对象代表什么?
- 文件和目录路径名的抽象表示形式。
- C:\Drivers 这是一个File对象
- C:\Drivers\Lanealtekeadme.txt 也是File对象。
- 一个File对象有可能对应的是目录,也可能是文件。
- File只是一个路径名的抽象表示形式。
-
常用方法
f1.exists():判断f1文件是否存在
- createNewFile(): 以文件形式新建
- mkdir():以目录形式创建
- mkdirs():以多层目录形式创建
- getParent():获取文件的上级目录,父目录。
- getAbsolutePath():获取文件的绝对路径。 ```java package IO;
import java.io.File; import java.io.IOException;
public class FileTest01 { public static void main(String[] args) { File file = new File(“D:\笔记\mu”); 🍎//判断文件是否存在 System.out.println(file.exists());
🍎// 如果 D:\笔记\mu 不存在,则以文件的形式创建出来/* if (!file.exists()){try {// 以文件形式新建file.createNewFile();} catch (IOException e) {e.printStackTrace();}}*/🍎// 如果 D:\笔记\mu 不存在,则以目录的形式创建出来if (!file.exists()){// 以目录的形式新建。file.mkdir();}🍎//创建多层目录File f1 = new File("D:/N/B/A");if (!f1.exists()){//以多层目录的形式新建。f1.mkdirs();}File f2 = new File("D:\\笔记\\yuque-desktop\\locales");🍎//获取文件的父路径String parentPath =f2.getParent();System.out.println(parentPath); // D:\笔记\yuque-desktop🍎//返回一个File类型File parentFile=f2.getParentFile();System.out.println(parentFile.getAbsolutePath());File f3 = new File("NBA");🍎//获取绝对路径System.out.println("绝对路径:"+f3.getAbsolutePath());}
}
7. **getName():**获取文件名。7. **isDirectory():**判断是否是一个目录。7. **isFile():**// 判断是否是一个文件7. **lastModified():**获取文件最后一次修改时间:这个毫秒是从1970年到文件最后一次修改时间 的总毫秒数。1. System._currentTimeMillis_():获取从1970年到现在的总毫秒数。11. **length() :**获取文件大小:单位是字节。```javapackage IO;import java.io.File;import java.text.SimpleDateFormat;import java.util.Date;public class FileTest02 {public static void main(String[] args) {File f1 = new File("D:\\笔记\\mu\\xuyoubo.txt");//获取文件名System.out.println("文件名:"+f1.getName());// 判断是否是一个目录System.out.println(f1.isDirectory()); //false// 判断是否是一个文件System.out.println(f1.isFile()); //true// 获取文件最后一次修改时间long haoMiao=f1.lastModified();// 这个毫秒是从1970年到文件最后一次修改时间 的总毫秒数。System.out.println(haoMiao);//将毫秒数转换成日期Date time = new Date(haoMiao);// Date time2 = new Date(1000);SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");String strTime = sdf.format(time);// String t2 = sdf.format(time2);System.out.println(strTime); //2021-05-07 14:33:47// System.out.println(t2);// 获取文件大小System.out.println(f1.length()); //单位是字节 36字节}}
- listFiles(): 获取当前目录下所有的子文件。 ```java package IO;
import java.io.File;
public class FileTest03 { public static void main(String[] args) { File f1 = new File(“C:\Users\Administrator\Desktop\Study_two\chapter23\src\IO”); // 获取当前目录下所有的子文件。 File[] files=f1.listFiles(); //foreach for (File f: files) { //获取文件名 // System.out.println(f.getName()); //获取绝对路径 System.out.println(f.getAbsolutePath()); } } }
<a name="qv4ND"></a>### getPath()与getAbsolutePath()的区别**getPath与getAbsolutePath()的区别:**<br />相对路径时:<br /> getPath输出相对路径<br /> getAbsolutePath输出绝对路径绝对路径时: 都输出绝对路径<br /><a name="JBtEx"></a>## -----------目录拷贝--------------------<a name="QXcdW"></a>### 代码[目录拷贝代码](https://blog.csdn.net/weixin_44989186/article/details/116501727)<a name="GlCuh"></a>## -----------对象专属流----------------<a name="rs2ju"></a>### 原理<a name="E60Dg"></a>### 知识点1. java.io.NotSerializableException:Student对象不支持序列化!!!! 需要 implements接口.2. 参与序列化和反序列化的对象,必须实现Serializable接口。3. 注意:通过源代码发现,**Serializable接口只是一个标志接口:**public interface Serializable { <br /> }<br />这个接口当中什么代码都没有。<br />那么它起到一个什么作用呢?<br />起到标识的作用,标志的作用,java虚拟机看到这个类实现了这个接口,可能会对这个类进行特殊待遇。 <br />Serializable这个标志接口是给java虚拟机参考的,java虚拟机看到这个接口之后,<br />会为该类自动生成 一个序列化版本号。4. **序列化版本号有什么用?**java.io.InvalidClassException: <br />com.bjpowernode.java.bean.Student;<br /> local class incompatible:<br /> stream classdesc serialVersionUID = -684255398724514298(十年后),<br /> local class serialVersionUID = -3463447116624555755(十年前)java语言中是采用什么机制来区分类的?<br />第一:首先通过类名进行比对,如果类名不一样,肯定不是同一个类。<br />第二:如果类名一样,再怎么进行类的区别?靠序列化版本号进行区分。小鹏编写了一个类:com.bjpowernode.java.bean.Student implements Serializable<br />胡浪编写了一个类:com.bjpowernode.java.bean.Student implements Serializable<br />**自动生成序列化版本号的好处:**<br />不同的人编写了同一个类,但“这两个类确实不是同一个类”。这个时候序列化版本就起上作用了。<br /> 对于java虚拟机来说,java虚拟机是可以区分开这两个类的,因为这两个类都实现了Serializable接口,<br />都有默认的序列化版本号,他们的序列化版本号不一样。所以区分开了。**自动生成序列化版本号的缺陷:**<br />这种自动生成的序列化版本号缺点是:一旦代码确定之后,不能进行后续的修改,<br />因为只要修改,必然会重新编译,此时会生成全新的序列化版本号,<br />这个时候java虚拟机会认为这是一个全新的类。(这样就不好了!)<a name="snFG2"></a>#### 最终结论凡是一个类实现了Serializable接口,建议给该类提供一个固定不变的序列化版本号。<br />这样,以后这个类即使代码修改了,但是版本号不变,java虚拟机会认为是同一个类。<br /> **手写序列号**:private static final long serialVersionUID = 1L; <br /> [手写序列号快捷键](https://www.cnblogs.com/zouhong/p/12975929.html)<a name="mARxX"></a>### 序列化与反序列化一个对象**Student类**```javapackage bean;import java.io.Serializable;public class Student implements Serializable {// Java虚拟机看到Serializable接口之后,会自动生成一个序列化版本号。// 这里没有手动写出来,java虚拟机会默认提供这个序列化版本号。// 建议将序列化版本号手动的写出来。不建议自动生成private static final long serialVersionUID = 1L;private int no;//transient 表示 游离的 使name不参与序列化与反序列化private transient String name;// 过了很久,Student这个类源代码如果发生改动了。// 源代码改动之后,需要重新编译,编译之后生成了全新的字节码文件。// 并且class文件再次运行的时候,java虚拟机生成的序列化版本号也会发生相应的改变。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;}public Student() {}public Student(int no, String name) {this.no = no;this.name = name;}@Overridepublic String toString() {return "Student{" +"no=" + no +", name='" + name + '\'' +'}';}}
ObjectOutPutStream类
package IO;import bean.Student;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;public class ObjectOutPutStreamTest01 {public static void main(String[] args) throws IOException {Student s1 = new Student(23,"LJ");//序列化ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Students"));//序列化对象oos.writeObject(s1);oos.flush();oos.close();}}
ObjectInputStream类
package IO;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;import java.io.ObjectInputStream;public class ObjectInputStreamTest01 {public static void main(String[] args) throws IOException, ClassNotFoundException {ObjectInputStream ois = new ObjectInputStream(new FileInputStream("students"));//开始反序列化,读Object obj=ois.readObject();//反序列化回来是一个学生对象,所以会调用学生对象的toString方法。System.out.println(obj);ois.close();}}
序列化与反序列化多个对象
User类
package bean;import java.io.Serializable;public class User implements Serializable {private String username;private String password;public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public User() {}public User(String username, String password) {this.username = username;this.password = password;}@Overridepublic String toString() {return "User{" +"username='" + username + '\'' +", password='" + password + '\'' +'}';}}
ObjectOutputStream类
package IO;import bean.User;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;import java.util.ArrayList;import java.util.List;/*一次序列化多个对象呢?可以,可以将对象放到集合当中,序列化集合。提示:参与序列化的ArrayList集合以及集合中的元素User都需要实现 java.io.Serializable接口。ArrayList已自己实现。*/public class ObjectOutputStreamTest02 {public static void main(String[] args) throws IOException {List<User> list = new ArrayList<>();list.add(new User("23","LJ"));list.add(new User("24","KB"));list.add(new User("3","AD"));ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("users"));// 序列化一个集合,这个集合对象中放了很多其他对象。oos.writeObject(list);oos.flush();oos.close();}}
ObjectInputStream类
package IO;import bean.User;import java.io.*;import java.util.List;public class ObjectInputStreamTest02 {public static void main(String[] args) throws IOException, ClassNotFoundException {ObjectInputStream ois = new ObjectInputStream(new FileInputStream("users"));/* Object o=ois.readObject();System.out.println(o instanceof List);*/List<User> list = (List<User>) ois.readObject();for (User u:list) {System.out.println(u);}}}
———IO+Properties联合使用—————————
知识点
import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.util.Properties;
/* IO+Properties的联合应用。 非常好的一个设计理念: 以后经常改变的数据,可以单独写到一个文件中,使用程序动态读取。 将来只需要修改这个文件的内容,java代码不需要改动,不需要重新 编译,服务器也不需要重启。就可以拿到动态的信息。
类似于以上机制的这种文件被称为配置文件。并且当配置文件中的内容格式是:key1=valuekey2=value的时候,我们把这种配置文件叫做属性配置文件。java规范中有要求:属性配置文件建议以.properties结尾,但这不是必须的。这种以.properties结尾的文件在java中被称为:属性配置文件。其中Properties是专门存放属性配置文件内容的一个类。
/ public class IoPropertiesTest01 { public static void main(String[] args) throws IOException { / Properties是一个Map集合,key和value都是String类型。 想将userinfo文件中的数据加载到Properties对象当中。 */ // 新建一个输入流对象 FileReader reader = new FileReader(“chapter23/src/bean/userinfo.properties”);
//新建一个Map集合Properties p1 = new Properties();// 调用Properties对象的load方法将文件中的数据加载到Map集合中。p1.load(reader); // 文件中的数据顺着管道加载到Map集合中,其中等号=左边做key,右边做value//通过key获取valueString username=p1.getProperty("username");System.out.println(username);String password = p1.getProperty("password");System.out.println(password);String level = p1.getProperty("level");System.out.println(level);}
}
——-properties文件——— username=admin
password=123456
key相同会覆盖
password=45678
不建议使用冒号
level:vip
<a name="OQQvH"></a># 进阶-多线程<a name="fioSn"></a>## 基本概念<a name="Bpi3D"></a>### 什么是进程?什么是线程?1. 进程是一个应用程序(1个进程是一个软件)。1. 线程是一个进程中的执行场景/执行单元。1. 一个进程可以启动多个线程。1. 对于java程序来说,当在DOS命令窗口中输入:1. java HelloWorld 回车之后。1. 会先启动JVM,而JVM就是一个进程。1. JVM再启动一个主线程调用main方法。1. 同时再启动一个垃圾回收线程负责看护,回收垃圾。1. 最起码,现在的java程序中至少有两个线程并发,1. 一个是垃圾回收线程,一个是执行main方法的主线程。<a name="itygH"></a>### 进程与线程的关系1. 举个例子:阿里巴巴--->进程<br />马云--->阿里巴巴的一个线程<br />蔡崇信--->阿里巴巴的一个线程<br />京东--->进程<br />强东--->京东的一个线程<br />奶茶--->京东的一个线程**进程**可以看做是现实生活当中的公司。<br />**线程**可以看做是公司当中的某个员工。2. 进程A和进程B的内存独立不共享。阿里巴巴和京东资源不会共享的!<a name="QAdky"></a>### 多线程并发的理解3. 线程A和线程B呢?在java语言中:<br />线程A和线程B,堆内存和方法区内存共享。<br />但是栈内存独立,一个线程一个栈。<br />eg:<br />假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,<br />各自执行各自的,这就是**多线程并发**。eg:<br />火车站,可以看做是一个进程。<br />火车站中的每一个售票窗口可以看做是一个线程。<br />我在窗口1购票,你可以在窗口2购票,你不需要等我,我也不需要等你。<br />所以多线程并发可以提高效率。4. java中之所以有多线程机制,目的就是为了提高程序的处理效率。5. 使用了多线程机制之后,main方法结束,程序会结束么?main方法结束只是主线程结束了,主栈空了,其它的栈(线程)可能还在压栈弹栈6. 对于单核的CPU来说,真的可以做到真正的多线程并发吗?对于**多核**的CPU电脑来说,真正的多线程并发是没问题的。<br />4核CPU表示同一个时间点上,可以真正的有4个进程并发执行。什么是真正的多线程并发?<br />t1线程执行t1的,t2线程执行t2的。<br />t1不会影响t2,t2也不会影响t1。这叫做真正的多线程并发。单核的CPU表示只有一个大脑:<br />不能够做到真正的多线程并发,但是可以做到给人一种**“多线程并发”的感觉。**<br />对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,<br />但是由于CPU的处理速度极快,多个线程之间频繁切换执行,<br />给人的感觉是:多个事情同时在做!<br />eg:<br />线程A:播放音乐<br />线程B:运行魔兽游戏<br />线程A和线程B频繁切换执行,人类会感觉音乐一直在播放,游戏一直在运行。<br />给我们的感觉是同时并发的。7. 分析以下程序有几个线程?<br />答:一个!只要一个主线程 主栈。没有分支线程!<a name="SUyHY"></a>### 图解**堆和方法区共享,栈独立。**<br /><a name="g2fyH"></a>## 实现线程的方式java支持多线程机制。并且java已经将多线程实现了,我们只需要继承就行了。<a name="gWxDt"></a>### 第一种方式编写一个类,直接继承java.lang.Thread,重写run方法。**start()方法**:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。 <br />这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了。<br />线程就启动成功了。<br /> 启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)。<a name="VCd44"></a>#### 代码```javapackage Thread;/*** 实现线程的第一种方式:* 编写一个类,直接继承java.lang.Thread,重写run方法。* 怎么创建线程对象?* new就行了。* 怎么启动线程呢?* 调用线程对象的start()方法。* 注意:* 方法体当中的代码永远都是自上而下的顺序依次逐行执行的。**/public class ThreadTest01 {public static void main(String[] args) {// 这里是main方法,这里的代码属于主线程,在主栈中运行。// 新建一个分支线程对象MyThread m = new MyThread();//t.run(); 假的这种方法不会启动线程,不会分配新的分支栈。(用这种方法还是单线程)/*start()方法:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了。线程就启动成功了。启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)。run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的。*///启动线程m.start();// 这里的代码还是运行在主线程中。for (int i = 0; i <1000 ; i++) {System.out.println("主线程--->"+i);}}}class MyThread extends Thread{@Overridepublic void run() {// 编写程序,这段程序运行在分支线程中(分支栈)。for (int i = 0; i <1000 ; i++) {System.out.println("分支线程--->"+i);}}}
图解
run()
start()
第二种方式
代码
编写一个类,实现java.lang.Runnable接口,实现run方法。
package Thread;public class ThreadTest02 {public static void main(String[] args) {// 创建一个可运行的对象MyRunnable r = new MyRunnable();// 将可运行的对象封装成一个线程对象Thread t = new Thread(r);//合并以上代码Thread t = new Thread(new MyRunnable());//启动线程t.start();for (int i = 0; i <1000 ; i++) {System.out.println("主线程--->"+i);}}}class MyRunnable implements Runnable{@Overridepublic void run() {for (int i = 0; i <1000 ; i++) {System.out.println("分支线程--->"+i);}}}
匿名内部类实现第二种方式
代码
package com.bjpowernode.java.thread;/*采用匿名内部类可以吗?*/public class ThreadTest04 {public static void main(String[] args) {// 创建线程对象,采用匿名内部类方式。// 这是通过一个没有名字的类,new出来的对象。Thread t = new Thread(new Runnable(){@Overridepublic void run() {for(int i = 0; i < 100; i++){System.out.println("t线程---> " + i);}}});// 启动线程t.start();for(int i = 0; i < 100; i++){System.out.println("main线程---> " + i);}}}
注意
第二种方式实现接口比较常用,因为一个类实现了接口,它还可以去继承其它的类,更灵活。
第三种方式(JDK8新特性)
这种方式实现的线程可以获取线程的返回值。
之前讲解的那两种方式是无法获取线程返回值的,因为run方法返回void。
思考:
系统委派一个线程去执行一个任务,该线程执行完任务之后,
可能会有一个执行结果,我们怎么能拿到这个执行结果呢?
使用第三种方式:实现Callable接口方式。
代码
package com.bjpowernode.java.thread;import java.util.concurrent.Callable;import java.util.concurrent.FutureTask; // JUC包下的,属于java的并发包,老JDK中没有这个包。新特性。/*实现线程的第三种方式:实现Callable接口这种方式的优点:可以获取到线程的执行结果。这种方式的缺点:效率比较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低。*/public class ThreadTest15 {public static void main(String[] args) throws Exception {⭐// 第一步:创建一个“未来任务类”对象。// 参数非常重要,需要给一个Callable接口实现类对象。FutureTask task = new FutureTask(new Callable() {@Overridepublic Object call() throws Exception { // call()方法就相当于run方法。只不过这个有返回值// 线程执行一个任务,执行之后可能会有一个执行结果// 模拟执行System.out.println("call method begin");Thread.sleep(1000 * 10);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线程的返回结果?// get()方法的执行会导致“当前线程阻塞”Object obj = task.get();System.out.println("线程执行结果:" + obj);// main方法这里的程序要想执行必须等待get()方法的结束// 而get()方法可能需要很久。因为get()方法是为了拿另一个线程的执行结果// 另一个线程执行是需要时间的。System.out.println("hello world!");}}
线程的生命周期

- 新建状态:采用 new语句创建完成
- 就绪状态:执行 start 后
- 运行状态:占用 CPU 时间,执行run方法。
- 阻塞状态:执行了 wait 语句、执行了 sleep 语句和等待某个对象锁, 等待输入的场合。
-
线程的常用方法
知识点
获取当前线程对象: Thread t = Thread.currentThread(); 静态方法
返回值t就是当前线程。
获取线程对象的名字: String name = 线程对象.getName();
修改线程对象的名字:线程对象.setName(“线程名字”);
当线程没有设置名字的时候,默认的名字有什么规律?(了解一下)
Thread-0
Thread-1
Thread-2
Thread-3
…..
代码
public class ThreadTest05 {public void doSome(){// 这样就不行了//this.getName();//super.getName();// 但是这样可以String name = Thread.currentThread().getName();System.out.println("------->" + name);}public static void main(String[] args) {ThreadTest05 tt = new ThreadTest05();tt.doSome();//currentThread就是当前线程对象// 这个代码出现在main方法当中,所以当前线程就是主线程。Thread currentThread = Thread.currentThread();System.out.println(currentThread.getName()); //main// 创建线程对象MyThread2 t = new MyThread2();// 设置线程的名字t.setName("t1");// 获取线程的名字String tName = t.getName();System.out.println(tName); //Thread-0MyThread2 t2 = new MyThread2();t2.setName("t2");System.out.println(t2.getName()); //Thread-1\t2.start();// 启动线程t.start();}}class MyThread2 extends Thread {public void run(){for(int i = 0; i < 100; i++){// currentThread就是当前线程对象。当前线程是谁呢?// 当t1线程执行run方法,那么这个当前线程就是t1// 当t2线程执行run方法,那么这个当前线程就是t2Thread currentThread = Thread.currentThread();System.out.println(currentThread.getName() + "-->" + i);//System.out.println(super.getName() + "-->" + i);//System.out.println(this.getName() + "-->" + i);}}}
sleep()方法
知识点
static void sleep(long millis)
静态方法:Thread.sleep(1000);
参数是毫秒
作用:让当前线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片,让给其它线程使用。
这行代码出现在A线程中,A线程就会进入休眠。
这行代码出现在B线程中,B线程就会进入休眠。Thread.sleep()方法,可以做到这种效果:
间隔特定的时间,去执行一段特定的代码,每隔多久执行一次。
代码
public class ThreadTest06 {public static void main(String[] args) {// 让当前线程进入休眠,睡眠5秒// 当前线程是主线程!!!/*try {Thread.sleep(1000 * 5);} catch (InterruptedException e) {e.printStackTrace();}*/// 5秒之后执行这里的代码//System.out.println("hello world!");for(int i = 0; i < 10; i++){System.out.println(Thread.currentThread().getName() + "--->" + i);// 睡眠1秒try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}
面试题
package com.bjpowernode.java.thread;/*关于Thread.sleep()方法的一个面试题:*/public class ThreadTest07 {public static void main(String[] args) {// 创建线程对象Thread t = new MyThread3();t.setName("t");t.start();// 调用sleep方法try {// 问题:这行代码会让线程t进入休眠状态吗?t.sleep(1000 * 5); // 因为sleep()是一个静态方法,是类名. 调用的, 与对象无关。在执行的时候还是会转换成:Thread.sleep(1000 * 5);这行代码的作用是:让当前线程进入休眠,也就是说main线程进入休眠。这样代码出现在main方法中,main线程睡眠。} catch (InterruptedException e) {e.printStackTrace();}// 5秒之后这里才会执行。System.out.println("hello World!");}}class MyThread3 extends Thread {public void run(){for(int i = 0; i < 10000; i++){System.out.println(Thread.currentThread().getName() + "--->" + i);}}}
终止sleep(),唤醒线程
sleep睡眠太久了,如果希望半道上醒来,你应该怎么办?也就是说怎么叫醒一个正在睡眠的线程??
注意:这个不是终断线程的执行,是终止线程的睡眠。
public class ThreadTest08 {public static void main(String[] args) {Thread t = new Thread(new MyRunnable2());t.setName("t");t.start();// 希望5秒之后,t线程醒来(5秒之后主线程手里的活儿干完了。)try {Thread.sleep(1000 * 5);} catch (InterruptedException e) {e.printStackTrace();}// 终断t线程的睡眠(这种终断睡眠的方式依靠了java的异常处理机制。)t.interrupt(); // 干扰,一盆冷水过去!}}class MyRunnable2 implements Runnable {// 重点:run()当中的异常不能throws,只能try catch// 因为run()方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常。@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + "---> begin");try {// 睡眠1年Thread.sleep(1000 * 60 * 60 * 24 * 365);} catch (InterruptedException e) {// 打印异常信息e.printStackTrace();}//1年之后才会执行这里System.out.println(Thread.currentThread().getName() + "---> end");}}
终止线程的执行
stop()方法: 已弃用,容易丢失数据。 了解即可。
合理的终止一个线程的执行:打标记 ```java package Thread;
public class ThreadTest10 { public static void main(String[] args) { MyRunnable5 r = new MyRunnable5(); Thread t = new Thread(r); t.setName(“分支线程”); t.start();
// 模拟6秒try {Thread.sleep(1000*6);} catch (InterruptedException e) {e.printStackTrace();}⭐ //终止线程,你想什么时候终止,将标记改为false就终止了。//6秒后终止r.flag=false;}
}
class MyRunnable5 implements Runnable{ //打一个标记⭐ boolean flag = true; @Override public void run() {
for (int i = 0; i <10 ; i++) {if (flag){System.out.println(Thread.currentThread().getName()+"-->"+i);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}else {// return就结束了,你在结束之前还有什么没保存的。// 在这里可以保存呀。//save....//终止当前线程return;}}}
}
<a name="yCFpW"></a>## 线程的调度 (了解即可)<a name="GT3OS"></a>### 常见的线程调度模1. 抢占式调度模型:那个线程的优先级比较高,抢到的CPU时间片的概率就高一些/多一些。<br />java采用的就是抢占式调度模型。2. 均分式调度模型:平均分配CPU时间片。每个线程占有的CPU时间片时间长度一样。<br />平均分配,一切平等。<br />有一些编程语言,线程调度模型采用的是这种方式。<a name="I8dr5"></a>### 线程调度的方法实例方法:<br />void setPriority(int newPriority) 设置线程的优先级<br />int getPriority() 获取线程优先级<br />最低优先级1<br />默认优先级是5<br />最高优先级10<br />优先级比较高的获取CPU时间片可能会多一些。(但也不完全是,大概率是多的。)<br /> 静态方法:<br />static void yield() 让位方法<br />暂停当前正在执行的线程对象,并执行其他线程<br />yield()方法不是阻塞方法。让当前线程让位,让给其它线程使用。<br />yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”<br />注意:在回到就绪之后,有可能还会再次抢到。<br /> 实例方法:<br />void join() 合并线程```javaclass MyThread1 extends Thread {public void doSome(){MyThread2 t = new MyThread2();t.join();// 当前线程进入阻塞,t线程执行,直到t线程结束。当前线程才可以继续。}}class MyThread2 extends Thread{}
线程安全⭐
为什么是重点
在以后的开发中,我们的项目都是运行在服务器当中,
而服务器已经将线程的定义,线程对象的创建,线程的启动等,全都实现了。
这些代码我们都不需要编写。
最重要的是:
你要知道,你编写的程序需要放到一个多线程的环境下运行,
你更需要关注的是这些数据在多线程并发的环境下是否安全!(⭐⭐⭐)
线程不安全的条件
举例:多线程并发对同一个账户进行取款
数据在多线程并发的环境下存在安全问题的三个条件
- 多线程并发
- 有共享数据
- 共享数据有修改的行为
满足以上3个条件,就会存在线程安全问题。
如何解决线程安全问题
线程同步机制
线程排队执行(不能并发),用排队执行解决线程安全问题。这种机制被称为—线程同步机制。
线程同步就是线程排队,线程排队了就会牺牲一部分效率。
数据安全是第一位的,只有数据安全了,才可以谈效率,数据不安全,没有效率的事。
语法: synchronized(){
// 线程同步代码块。
}
同步与异步
异步编程模型(并发):
线程t1和t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁。
这种编程模型叫做—异步编程模型。
本质:多线程并发(效率较高)
同步编程模型(排队):
线程t1和t2,在一个线程执行时,另一个线程必须等其执行结束,才可以执行。
两个线程之间发生了等待关系,这种编程模型叫做—同步编程模型。
本质:线程排队执行(效率较低)
⭐JAVA中的三大变量哪些有线程安全问题?
实例变量:在堆中。
静态变量:在方法区。
局部变量:在栈中。
以上三大变量中:
局部变量永远都不会存在线程安全问题。
因为局部变量不共享。(一个线程一个栈。)
局部变量在栈中。所以局部变量永远都不会共享。
实例变量在堆中,堆只有1个。
静态变量在方法区中,方法区只有1个。
堆和方法区都是多线程共享的,所以可能存在线程安全问题。
总结:
1. 局部变量+常量: 不会有线程安全问题1. 成员变量(实例变量+静态变量):可能会有线程安全问题。
synchronized
使用同步和异步分别实现多线程对同一个账户进行取款
Accout类
package threadsafe;/*** 编写程序模拟两个线程同时对一个账户进行取款操作*/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;}/*** 取钱方法:异步编程模型,并发执行!* @param money 取钱数*/public void draw(double money){// t1和t2并发这个方法。。。。(t1和t2是两个栈。两个栈操作堆中同一个对象。)//取款之前的余额double before = getBalance(); //10000//取款后余额double after = getBalance()-money;// 在这里模拟一下网络延迟,100%会出现问题try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}//更新余额//思考:t1执行到这里,但还没来得及执行这行代码,t2就进来draw方法了,此时一定会出问题!this.setBalance(after);}⭐⭐ /*** 取钱方法:使用线程同步机制!排队执行!* @param money* 以下这几行代码必须是线程排队的,不能并发。* // 一个线程把这里的代码全部执行结束之后,另一个线程才能进来。* /** 线程同步机制的语法是:* synchronized(){* // 线程同步代码块。* }* synchronized后面小括号中传的这个“数据”是相当关键的。* 这个数据必须是多线程共享的数据。才能达到多线程排队。** ()中写什么?* 那要看你想让哪些线程同步。* 假设t1、t2、t3、t4、t5,有5个线程,* 你只希望t1 t2 t3排队,t4 t5不需要排队。怎么办?* 你一定要在()中写一个t1 t2 t3共享的对象。而这个* 对象对于t4 t5来说不是共享的。* 此时 t1 t2 t3 就是同步(排队)的* t4 t5 是异步(并发)的。** 这里的共享对象是:账户对象。* 账户对象是共享的,那么this(因为是账户调用的draw())就是账户对象吧!!!* 不一定是this,这里只要是多线程共享的那个对象就行。** 在java语言中,任何一个对象都有“一把锁”,其实这把锁就是标记。(只是把它叫做锁。)* 100个对象,100把锁。1个对象1把锁。** 以下代码的执行原理?* 1、假设t1和t2线程并发,开始执行以下代码的时候,肯定有一个先一个后。* 2、假设t1先执行了,遇到了synchronized,这个时候自动找“后面共享对象”的对象锁,* 找到之后,并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直都是占有这把锁的。* 直到同步代码块代码结束,这把锁才会释放。* 3、假设t1已经占有这把锁,此时t2也遇到synchronized关键字,也会去占有后面* 共享对象的这把锁,结果这把锁被t1占有,t2只能在同步代码块外面等待t1的结束,* 直到t1把同步代码块执行结束了,t1会归还这把锁,此时t2终于等到这把锁,* 然后t2占有这把锁之后,进入同步代码块执行程序。** 这样就达到了线程排队执行!* 这里需要注意的是:这个共享对象一定要选好了。* 这个共享对象一定是你需要排队执行的这些线程对象所共享的。**/public void draw2(double money){//Object obj2 = new Object();//synchronized (obj2) 不行,这是局部变量,不是共享对象//synchronized ("abc") 可以,因为"abc"在字符串常量池当中。但是它是所以线程的共享对象。//synchronized (null) 不行,空指针异常synchronized (this){//取款之前的余额double before = getBalance(); //10000//取款后余额double after = getBalance()-money;// 在这里模拟一下网络延迟。try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}//更新余额this.setBalance(after);}}}
AccountThread类
package threadsafe;public class AccountThread extends Thread {//两个线程必须共享一个账户private Account act;//通过构造方法传递过来账户对象public AccountThread(Account act){this.act = act;}@Overridepublic void run() {//run()方法表示取款操作//假设取款5000double money = 5000;//取款//多线程并发执行这个方法//act.draw(money);//线程同步排队执行此方法act.draw2(money);System.out.println(Thread.currentThread().getName()+"对"+act.getActno()+"取款"+money+"元成功,余额:" +act.getBalance());}}
Test类
package threadsafe;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);//设置namet1.setName("t1");t2.setName("t2");//启动线程,进行取款t1.start();t2.start();}}
()里放什么?
P626讲解!
总而言之:一句话,放的是你想要排队的线程的共享对象!
()放的范围越小,效率越高。
synchronized的三种写法
- 同步代码块:
比较灵活
synchronized(线程共享对象){
同步代码块;
}
- 在实例方法上使用synchronized
表示共享对象一定是this,
并且同步代码块是整个方法体。
public synchronized void draw(double money){
}
- 在静态方法上使用synchronized
表示找类锁。
类锁永远只有一把,
就算创建了100个对象,那类锁也只要一把。
对象锁:1个对象1把锁,100个对象100把锁。
类锁:100个对象,也可能只是1把类锁。
synchronized相关面试题
- 面试题:doOther方法执行的时候需要等待doSome方法的结束吗? ```java package com.bjpowernode.java.exam1;
// 面试题:doOther方法执行的时候需要等待doSome方法的结束吗? //不需要,因为doOther()方法没有synchronized public class Exam01 { public static void main(String[] args) throws InterruptedException { MyClass mc = new MyClass();
Thread t1 = new MyThread(mc);Thread t2 = new MyThread(mc);t1.setName("t1");t2.setName("t2");t1.start();Thread.sleep(1000); //这个睡眠的作用是:为了保证t1线程先执行。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。2. 面试题:doOther方法执行的时候需要等待doSome方法的结束吗?```javapackage com.bjpowernode.java.exam2;// 面试题:doOther方法执行的时候需要等待doSome方法的结束吗?//需要public class Exam01 {public static void main(String[] args) throws InterruptedException {MyClass mc = new MyClass();Thread t1 = new MyThread(mc);Thread t2 = new MyThread(mc);t1.setName("t1");t2.setName("t2");t1.start();Thread.sleep(1000); //这个睡眠的作用是:为了保证t1线程先执行。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(){ //加上了 synchronizedSystem.out.println("doOther begin");System.out.println("doOther over");}}
答:需要!因为doOther()方法有synchronized。
- 面试题:doOther方法执行的时候需要等待doSome方法的结束吗? ```java package com.bjpowernode.java.exam3;
// 面试题:doOther方法执行的时候需要等待doSome方法的结束吗? //不需要,因为MyClass对象是两个,两把锁。 public class Exam01 { public static void main(String[] args) throws InterruptedException { 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();Thread.sleep(1000); //这个睡眠的作用是:为了保证t1线程先执行。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”); } }
答:不需要,因为MyClass对象是两个,两把锁。4. 面试题:doOther方法执行的时候需要等待doSome方法的结束吗? ⭐⭐⭐```javapackage com.bjpowernode.java.exam4;// 面试题:doOther方法执行的时候需要等待doSome方法的结束吗?//需要,因为静态方法是类锁,不管创建了几个对象,类锁只有1把。public class Exam01 {public static void main(String[] args) throws InterruptedException {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();Thread.sleep(1000); //这个睡眠的作用是:为了保证t1线程先执行。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 {// synchronized出现在静态方法上是找类锁。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");}}
答:需要,因为静态方法是类锁,不管创建了几个对象,类锁只有1把。
死锁
死锁代码要会写。
一般面试官要求你会写。
只有会写的,才会在以后的开发中注意这个事儿。
因为死锁很难调试。
package com.bjpowernode.java.deadlock;/*死锁代码要会写。一般面试官要求你会写。只有会写的,才会在以后的开发中注意这个事儿。因为死锁很难调试。*/public class DeadLock {public static void main(String[] args) {Object o1 = new Object();Object o2 = new Object();// t1和t2两个线程共享o1,o2Thread 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){}}}}
在以后的开发中应该怎么解决线程安全问题
首选是线程同步吗?synchronized
不是,synchronized会让程序的执行效率降低,用户体验不好。
系统的用户吞吐量降低。用户体验差。在不得已的情况下再选择
线程同步机制。
第一种方案
第二种方案
如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了。
一个线程对应1个对象,100个线程对应100个对象, 对象不共享,就没有数据安全问题了
第三种方案
如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择synchronized了。线程同步机制。
守护线程
java语言中的线程分为两大类:
用户线程
守护线程(后台线程)
其中具有代表性的就是:垃圾回收线程—-是一个守护线程。
守护线程
语法:setDaemon(true);
特点:一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束。
注意: 主方法main方法是一个用户线程。
**守护线程用在什么地方呢?**<br />每天00:00的时候系统数据自动备份。<br />这个需要使用到定时器,并且我们可以将定时器设置为守护线程。<br />一直在那里看着,每到00:00的时候就备份一次。<br />所有的用户线程如果结束了,守护线程自动退出,没有必要进行数据备份了。
代码
package Thread;public class ThreadTest14 {public static void main(String[] args) {Thread t1 = new MyThread3();t1.setName("备份数据的线程");⭐// 启动线程之前,将线程设置为守护线程⭐t1.setDaemon(true);//启动线程t1.start();// 主线程:主线程是用户线程for (int i = 1; i <10 ; i++) {System.out.println(Thread.currentThread().getName()+"-->"+i);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}class MyThread3 extends Thread{@Overridepublic void run() {int i =0;// 即使是死循环,但由于该线程是守护者,当用户线程结束,守护线程自动终止。while (true){System.out.println(Thread.currentThread().getName()+"-->"+(++i));try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}
定时器
作用
间隔特定的时间,执行特定的程序。
在实际的开发中,每隔多久执行一段特定的程序,这种需求是很常见的,
例如:每周要进行银行账户的总账操作。
每天要进行数据的备份操作。
实现方式
- sleep(): 可以使用sleep方法,睡眠,设置睡眠时间,没到这个时间点醒来,执行任务。
这种方式是最原始的定时器。(比较low)
- java.util.Timer: 在java的类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用。
这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的。
- SpringTask框架:在实际的开发中,
目前使用较多的是Spring框架中提供SpringTask框架(底层是Timer)
这个框架只要进行简单的配置,就可以完成定时器的任务。
代码
package Thread;import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.Date;import java.util.Timer;import java.util.TimerTask;/*使用定时器指定定时任务。*/public class TimerTest {public static void main(String[] args) throws ParseException {//创建定时器对象Timer timer = new Timer();//Timer timer = new Timer(true); //守护线程的方式// 指定定时任务//timer.schedule(定时任务, 第一次执行时间, 间隔多久执行一次);SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");Date firstTime = sdf.parse("2021-05-11 15:57:00");//每10秒备份一次timer.schedule(new LogTimerTask(),firstTime,1000*10);//匿名内部类方式timer.schedule(new TimerTask(){@Overridepublic void run() {// code....}} , firstTime, 1000 * 10);}}}class LogTimerTask extends TimerTask{@Overridepublic void run() {//在这编写你需要执行的任务就行SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");String strTime = sdf.format(new Date());System.out.println(strTime+":成功完成了一次数据备份!");}}
wait()与notify()
概述
wait()和notify()方法不是线程对象的方法,是java中任何一个对象都有的方法,
因为这两个方法是Object类中自带的。
wait()和notify()不是通过线程对象调用的。
t.wait();✖ t.notify() ✖ 这种写法都不对。
wait()的作用
Object o = new Object();
o.wait();
表示:
让正在o对象上活动的线程进入等待状态,无限期等待,直到被唤醒为止。
o.wait();方法的调用,会让”当前线程(正在o对象上活动的线程)”进入等待状态。
notify()方法作用
Object o = new Object();
o.notify();
表示:
唤醒正在o对象上等待的线程。
还有一个notifyAll()方法:
这个方法是唤醒o对象上处于等待的所有线程。
图解
生产者与消费者模式
知识点
使用wait()和notify()实现“生产者和消费者模式”。
什么是“生产者和消费者模式”?
生产线程负责生产,消费线程负责消费。
生产线程和消费线程要达到均衡。
这是一种特殊的业务需求,在这种特殊的情况下需要使用wait()和notify()。
This is a special work demand,in the special situation need use wait() and notify() .
wait和notify方法不是线程对象的方法,是普通java对象都有的方法。
wait方法和notify方法建立在线程同步的基础之上。因为多线程要同时操作一个仓库。有线程安全问题。
图解
代码
```java package com.bjpowernode.java.thread;
import java.util.ArrayList; import java.util.List;
/* 1、使用wait方法和notify方法实现“生产者和消费者模式”
2、什么是“生产者和消费者模式”? 生产线程负责生产,消费线程负责消费。 生产线程和消费线程要达到均衡。 这是一种特殊的业务需求,在这种特殊的情况下需要使用wait方法和notify方法。
3、wait和notify方法不是线程对象的方法,是普通java对象都有的方法。
4、wait方法和notify方法建立在线程同步的基础之上。因为多线程要同时操作一个仓库。有线程安全问题。
5、wait方法作用:o.wait()让正在o对象上活动的线程t进入等待状态,并且释放掉t线程之前占有的o对象的锁。
6、notify方法作用:o.notify()让正在o对象上等待的线程唤醒,只是通知,不会释放o对象上之前占有的锁。
7、模拟这样一个需求: 仓库我们采用List集合。 List集合中假设只能存储1个元素。 1个元素就表示仓库满了。 如果List集合中元素个数是0,就表示仓库空了。 保证List集合中永远都是最多存储1个元素。
必须做到这种效果:生产1个消费1个。
*/ public class ThreadTest16 { public static void main(String[] args) { // 创建1个仓库对象,共享的。 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;}@Overridepublic void run() {// 一直生产(使用死循环来模拟一直生产)while(true){// 给仓库对象list加锁。synchronized (list){if(list.size() > 0){ // 大于0,说明仓库中已经有1个元素了。try {// 当前线程进入等待状态,并且释放Producer之前占有的list集合的锁。list.wait();} catch (InterruptedException e) {e.printStackTrace();}}// 程序能够执行到这里说明仓库是空的,可以生产Object obj = new Object();list.add(obj);System.out.println(Thread.currentThread().getName() + "--->" + obj);// 唤醒消费者进行消费list.notifyAll();}}}
}
// 消费线程 class Consumer implements Runnable { // 仓库 private List list;
public Consumer(List list) {this.list = list;}@Overridepublic void run() {// 一直消费while(true){synchronized (list) {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.notifyAll();}}}
}
<a name="tJeMi"></a># 进阶-反射<a name="NOXWl"></a>## 基本概念1. 什么是反射机制,反射机制的作用通过java语言中的反射机制可以操作字节码文件。<br />作用:可以让程序更加灵活。有点类似于黑客。(可以读和修改字节码文件。)<br />通过反射机制可以操作代码片段。(class文件。)2. 反射机制的相关类在哪个包下?java.lang.reflect.*;3. 反射机制相关的重要的类有哪些?**java.lang.Class**:代表整个字节码,代表一个类型,代表整个类。**java.lang.reflect.Method**:代表字节码中的方法字节码。代表类中的方法。**java.lang.reflect.Constructor**:代表字节码中的构造方法字节码。代表类中的构造方法**java.lang.reflect.Field**:代表字节码中的属性字节码。代表类中的成员变量(静态变量+实例变量)。eg:```javapublic class User{ //java.lang.Classprivate int no; //java.lang.reflect.Field//java.lang.reflect.Constructorpublic User(){}public User(int no){this.no=no;}//java.lang.reflect.Methodpublic int getNo(){return no;}public void setNo(int no){this.no=no;}}
要操作一个类的字节码,需要首先获取到这个类的字节码,怎么获取java.lang.Class实例?
⭐获取Class的三种方式
第一种方式-Class.forName(“完整类名”)
- 静态方法
- 方法的参数是一个字符串。
- 字符串需要是一个完整的类名。
-
代码
try {Class c1 = Class.forName("java.lang.String"); /c1代表String.class文件,或者说c1代表String类型。/Class c2 = Class.forName("java.util.Date");// c2代表Date类型} catch (ClassNotFoundException e) {e.printStackTrace();}
第二种方式-对象.getClass()
-
代码
public static void main(String[] args) {Class c1 = null;try {c1 = Class.forName("java.lang.String"); //c1代表String.class文件,或者说c1代表String类型。Class c2 = Class.forName("java.util.Date");// c2代表Date类型} catch (ClassNotFoundException e) {e.printStackTrace();}// java中任何一个对象都有一个方法:getClass()String s = "abc";Class c3 = s.getClass(); // x代表String.class字节码文件,x代表String类型。System.out.println(c1==c3);//true}
图解
第三种方式-类型.class
java中任何一种类型,包括基本数据类型,它都有.class属性。
代码
public static void main(String[] args) {Class c1 = null;第一种try {c1 = Class.forName("java.lang.String"); //c1代表String.class文件,或者说c1代表String类型。Class c2 = Class.forName("java.util.Date");// c2代表Date类型} catch (ClassNotFoundException e) {e.printStackTrace();}第二种String s = "abc";Class c3 = s.getClass();System.out.println(c1==c3);//true第三种Class a = String.class; //a代表String类型System.out.println(c1==a);//true}
获取Class后能干的事
⭐通过反射实例化对象-Class
知识点
获取了Class之后,可以调用无参数构造方法来实例化对象
- 一定要注意:newInstance()底层调用的是该类型的无参数构造方法。
代码
package com.bjpowernode.java.bean;public class User {public User(){System.out.println("无参数构造方法!");}// 定义了有参数的构造方法,无参数构造方法就没了。public User(String s){}}package com.bjpowernode.java.reflect;import com.bjpowernode.java.bean.User;/*获取到Class,能干什么?通过Class的newInstance()方法来实例化对象。注意:newInstance()方法内部实际上调用了无参数构造方法,必须保证无参构造存在才可以。*/public class ReflectTest02 {public static void main(String[] args) {// 这是不使用反射机制,创建对象User user = new User();System.out.println(user);// 下面这段代码是以反射机制的方式创建对象。try {// 通过反射机制,获取Class,通过Class来实例化对象Class c = Class.forName("com.bjpowernode.java.bean.User"); // c代表User类型。⭐// newInstance() 这个方法会调用User这个类的无参数构造方法,完成对象的创建。// 重点是:newInstance()调用的是无参构造,必须保证无参构造是存在的!⭐Object obj = c.newInstance();System.out.println(obj); // com.bjpowernode.java.bean.User@10f87f48} catch (ClassNotFoundException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();}}}
验证反射机制的灵活性
package com.bjpowernode.java.reflect;import com.bjpowernode.java.bean.User;import java.io.FileReader;import java.util.Properties;/*验证反射机制的灵活性。java代码写一遍,再不改变java源代码的基础之上,可以做到不同对象的实例化。非常之灵活。(符合OCP开闭原则:对扩展开放,对修改关闭。)后期你们要学习的是高级框架,而工作过程中,也都是使用高级框架,包括: ssh ssmSpring SpringMVC MyBatisSpring Struts Hibernate...这些高级框架底层实现原理:都采用了反射机制。所以反射机制还是重要的。学会了反射机制有利于你理解剖析框架底层的源代码。*/public class ReflectTest03 {public static void main(String[] args) throws Exception{// 这种方式代码就写死了。只能创建一个User类型的对象//User user = new User();// 以下代码是灵活的,代码不需要改动,可以修改配置文件,配置文件修改之后,可以创建出不同的实例对象。// 通过IO流读取classinfo.properties文件FileReader reader = new FileReader("chapter25/classinfo2.properties");// 创建属性类对象MapProperties pro = new Properties(); // key value都是String// 加载pro.load(reader);// 关闭流reader.close();// 通过key获取valueString className = pro.getProperty("className");//System.out.println(className);// 通过反射机制实例化对象Class c = Class.forName(className);Object obj = c.newInstance();System.out.println(obj);}}chapter25/classinfo2.properties文件中的内容:className=com.bjpowernode.java.bean.User
只让静态代码块执行
- Class.forName(“该类的类名”);
- 这样类就加载,类加载的时候,静态代码块执行!!!!
- 在这里,对该方法的返回值不感兴趣,主要是为了使用“类加载”这个动作。
代码
package com.bjpowernode.java.reflect;/*研究一下:Class.forName()发生了什么?记住,重点:如果你只是希望一个类的静态代码块执行,其它代码一律不执行,你可以使用:Class.forName("完整类名");这个方法的执行会导致类加载,类加载时,静态代码块执行。提示:后面JDBC技术的时候我们还需要使用此方法。*/public class ReflectTest04 {public static void main(String[] args) {try {// Class.forName()这个方法的执行会导致:类加载。Class.forName("com.bjpowernode.java.reflect.MyClass");} catch (ClassNotFoundException e) {e.printStackTrace();}}}package com.bjpowernode.java.reflect;public class MyClass {// 静态代码块在类加载时执行,并且只执行一次。static {System.out.println("MyClass类的静态代码块执行了!");}}
获取类路径下文件的绝对路径
- 前提是:文件需要在类路径下。才能用这种方式。
- 即使代码换位置了,这样编写仍然是通用的。
- 什么类路径下?放在src下的都是类路径下。【记住它】⭐⭐⭐
- src是类的根路径。
代码
```java package com.bjpowernode.java.reflect;
import java.io.FileReader;
/ 研究一下文件路径的问题。 怎么获取一个文件的绝对路径。以下讲解的这种方式是通用的。但前提是:文件需要在类路径下。才能用这种方式。 / public class AboutPath { public static void main(String[] args) throws Exception{ // 这种方式的路径缺点是:移植性差,在IDEA中默认的当前路径是project的根。 // 这个代码假设离开了IDEA,换到了其它位置,可能当前路径就不是project的根了,这时这个路径就无效了。 //FileReader reader = new FileReader(“chapter25/classinfo2.properties”);
// 接下来说一种比较通用的一种路径。即使代码换位置了,这样编写仍然是通用的。// 注意:使用以下通用方式的前提是:这个文件必须在类路径下。// 什么类路径下?方式在src下的都是类路径下。【记住它】// src是类的根路径。/*解释:Thread.currentThread() 当前线程对象getContextClassLoader() 是线程对象的方法,可以获取到当前线程的类加载器对象。getResource() 【获取资源】这是类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源。*/String path = Thread.currentThread().getContextClassLoader().getResource("classinfo2.properties").getPath(); // 这种方式获取文件绝对路径是通用的。// 采用以上的代码可以拿到一个文件的绝对路径。// /C:/Users/Administrator/IdeaProjects/javase/out/production/chapter25/classinfo2.propertiesSystem.out.println(path);// 获取db.properties文件的绝对路径(从类的根路径下作为起点开始)String path2 = Thread.currentThread().getContextClassLoader().getResource("com/bjpowernode/java/bean/db.properties").getPath();System.out.println(path2);}
}
<a name="WUUiO"></a>### 以流的形式直接返回```javapackage com.bjpowernode.java.reflect;import java.io.FileReader;import java.io.InputStream;import java.util.Properties;public class IoPropertiesTest {public static void main(String[] args) throws Exception{// 获取一个文件的绝对路径了!!!!!/*String path = Thread.currentThread().getContextClassLoader().getResource("classinfo2.properties").getPath();FileReader reader = new FileReader(path);*/// 直接以流的形式返回。InputStream reader = Thread.currentThread().getContextClassLoader().getResourceAsStream("classinfo2.properties");Properties pro = new Properties();pro.load(reader);reader.close();// 通过key获取valueString className = pro.getProperty("className");System.out.println(className);}}
以流的方式返回并通过反射实例化对象
package reflect;import bean.Player;import java.io.*;import java.util.Properties;//验证反射的灵活性public class Reflect03 {public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {//直接以流的形式返回InputStream reader =Thread.currentThread().getContextClassLoader().getResourceAsStream("userinfo.properties");Properties pro = new Properties();pro.load(reader);reader.close();String className = pro.getProperty("className");//通过反射实例化对象Class c = Class.forName(className);Object obj=c.newInstance();System.out.println(obj);//相当于 Player p1 = new Player();}}
资源绑定器
知识点
java.util包下提供了一个资源绑定器,便于获取属性配置文件中的内容。
使用资源绑定器的时候,属性配置文件xxx.properties必须放到类路径(src)下。
资源绑定器,只能绑定xxx.properties文件,并且这个文件必须在类路径下。
文件扩展名也必须是properties。
在写路径的时候,路径后面的扩展名不能写。
ResourceBundle bundle = ResourceBundle.getBundle(“classinfo2”);
-
代码
```java package com.bjpowernode.java.reflect;
import java.util.ResourceBundle;
/ java.util包下提供了一个资源绑定器,便于获取属性配置文件中的内容。 使用以下这种方式的时候,属性配置文件xxx.properties必须放到类路径下。 / public class ResourceBundleTest { public static void main(String[] args) {
// 资源绑定器,只能绑定xxx.properties文件。并且这个文件必须在类路径下。文件扩展名也必须是properties// 并且在写路径的时候,路径后面的扩展名不能写。//ResourceBundle bundle = ResourceBundle.getBundle("classinfo2");ResourceBundle bundle = ResourceBundle.getBundle("com/bjpowernode/java/bean/db");String className = bundle.getString("className");System.out.println(className);}
}
<a name="ZQ1rD"></a>### ⭐通过资源绑定器-反射实例化对象最终方法!```javapackage reflect;import bean.Player;import java.io.*;import java.util.Properties;import java.util.ResourceBundle;//验证反射的灵活性public class Reflect03 {public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {/* //直接以流的形式返回InputStream reader =Thread.currentThread().getContextClassLoader().getResourceAsStream("userinfo.properties");Properties pro = new Properties();pro.load(reader);reader.close();String className = pro.getProperty("className");*///以上代码可以通过资源绑定器,完美替代ResourceBundle rb = ResourceBundle.getBundle("userinfo");String className = rb.getString("className");//通过反射实例化对象Class c = Class.forName(className);Object obj=c.newInstance();System.out.println(obj);//相当于 Player p1 = new Player();}}
拓展-类加载器
聊一聊,不需要掌握,知道当然最好!
- 什么是类加载器?
专门负责加载类的命令/工具-ClassLoader
- JDK中自带了3个类加载器
启动类加载器:rt.jar
扩展类加载器:ext/*.jar
应用类加载器:classpath
- 假设有这样一段代码:
String s = “abc”;
代码在开始执行之前,会将所需要类全部加载到JVM当中。
通过类加载器加载,看到以上代码类加载器会找String.class文件,找到就加载,那么是怎么进行加载的呢?
首先通过“启动类加载器”加载。
注意:启动类加载器专门加载:C:\Program Files\Java\jdk1.8.0_101\jre\lib\rt.jar
rt.jar中都是JDK最核心的类库。
如果通过“启动类加载器”加载不到的时候,
会通过”扩展类加载器”加载。
注意:扩展类加载器专门加载:C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext*.jar
如果“扩展类加载器”没有加载到,
会通过“应用类加载器”加载。
注意:应用类加载器专门加载:classpath中的类。
双亲委派机制
java中为了保证类加载的安全,使用了双亲委派机制。
优先从启动类加载器中加载,这个称为“父”
“父”无法加载到,再从扩展类加载器中加载,
这个称为“母”。双亲委派。
如果都加载不到,才会考虑从应用类加载器中加载。直到加载到为止。
获取Field(了解即可!)
知识点
- 获取类中所有的public修饰的Field:
Field[] fields=c1.getFields();
- 获取所有的Field
Field[] fields1=c1.getDeclaredFields();
- 获取Field的名字
System.out.println(f.getName());
- 获取属性的类型
Class fieldType=f.getType();
System.out.println(fieldType.getSimpleName());
- 获取属性的修饰符列表
int i=f.getModifiers();//返回的修饰符是一个数字,每个数字是修饰符的代号!!!
将代号转换成字符串
String modifierString=Modifier.toString(i);
反射Field
package reflect;import java.lang.reflect.Field;import java.lang.reflect.Modifier;public class ReflectTest05 {public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {//获取类Class c1 = Class.forName("bean.Car");//获取小名String simpleName=c1.getSimpleName();System.out.println(simpleName);//获取全名String className=c1.getName();System.out.println(className);System.out.println("===================");//获取类中所有的Public修饰的FieldField[] fields=c1.getFields();//取出fields中的元素,并输出名字Field f1 = fields[0];System.out.println(f1.getName());System.out.println("===================");//获取所有的FieldField[] fields1=c1.getDeclaredFields();//遍历此数组,取出fields1中的元素,并输出名字for (Field f:fields1) {//获取属性的名字System.out.println(f.getName());//获取属性的类型Class fieldType=f.getType();System.out.println(fieldType.getSimpleName());// 获取属性的修饰符列表int i=f.getModifiers();//返回的修饰符是一个数字,每个数字是修饰符的代号!!!System.out.println(i);//将代号转换成字符串String modifierString=Modifier.toString(i);System.out.println(modifierString);System.out.println("===================");}}}
反编译Field
package com.bjpowernode.java.reflect;//通过反射机制,反编译一个类的属性Field(了解一下)import java.lang.reflect.Field;import java.lang.reflect.Modifier;public class ReflectTest06 {public static void main(String[] args) throws Exception{// 创建这个是为了拼接字符串。StringBuilder s = new StringBuilder();//Class studentClass = Class.forName("com.bjpowernode.java.bean.Student");Class studentClass = Class.forName("java.lang.Thread");s.append(Modifier.toString(studentClass.getModifiers()) + " class " + studentClass.getSimpleName() + " {\n");Field[] fields = studentClass.getDeclaredFields();for(Field field : fields){s.append("\t");s.append(Modifier.toString(field.getModifiers()));s.append(" ");s.append(field.getType().getSimpleName());s.append(" ");s.append(field.getName());s.append(";\n");}s.append("}");System.out.println(s);}}
⭐通过反射访问对象属性-Field
知识点
- 怎么通过反射机制访问一个java对象的属性?
给属性赋值set
获取属性的值get
代码
package reflect;import bean.Car;import java.lang.reflect.Field;public class ReflectTest07 {public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {⭐//我们不使用反射机制,怎么样去访问一个对象的属性呢?Car car = new Car();//给属性赋值/*给car对象的name属性赋值领克的三要素:1:对象car2:name属性3:值 领克*/car.name="领克";//获取属性的值/*获取car对象的name属性的二要素:1:对象car2:name属性*/System.out.println(car.name);⭐//使用反射机制,怎么样去访问一个对象的属性呢?🏀//获取ClassClass carClass=Class.forName("bean.Car");//实例化对象Object obj=carClass.newInstance();//这两步相当于完成了 Car car = new Car();🏀//获取name属性(根据属性的名称来获取Field)Field namefield =carClass.getDeclaredField("name");🏀//给obj对象(Car对象)的name属性赋值/*给obj对象的name属性赋值保时捷的三要素:1:对象obj2:name属性-namefield就代表name属性3:值 保时捷注意:反射机制让代码复杂了,但是为了一个“灵活”,这也是值得的。*/namefield.set(obj,"保时捷");🏀//读取属性的值/*获取obj对象的name属性的二要素:1:对象obj2:name属性*/System.out.println(namefield.get(obj));⭐//访问私有属性//获取no属性Field noField = carClass.getDeclaredField("no");🏀//打破封装:因为是private的,所以需要打破封装。//反射机制的缺点:打破封装,可能会给不法分子留下机会!!!noField.setAccessible(true);//赋值noField.set(obj,226);//获取值System.out.println(noField.get(obj));}}
可变长度参数-插入知识点
知识点
int… args 这就是可变长度参数
- 语法:类型… (注意:一定是3个点。)
- 可变长度参数要求的参数个数是:0~N个。
- 可变长度参数在参数列表中必须在最后一个位置上,而且只能有1个。
- 可变长度参数可以当做一个数组来看待。
代码
package com.bjpowernode.java.reflect;public class ArgsTest {public static void main(String[] args) {m();m(10);m(10, 20);// 编译报错,类型错误//m("abc");m2(100);m2(200, "abc");m2(200, "abc", "def");m2(200, "abc", "def", "xyz");m3("ab", "de", "kk", "ff");String[] strs = {"a","b","c"};// 也可以传1个数组m3(strs);// 直接传1个数组m3(new String[]{"我","是","中","国", "人"}); //没必要m3("我","是","中","国", "人");}public static void m(int... args){System.out.println("m方法执行了!");}//public static void m2(int... args2, String... args1){}// 必须在最后,只能有1个。public static void m2(int a, String... args1){}public static void m3(String... args){//args有length属性,说明args是一个数组!// 可以将可变长度参数当做一个数组来看。for(int i = 0; i < args.length; i++){System.out.println(args[i]);}}}
获取Method(了解即可!)
反射Method
UserService类package com.bjpowernode.java.service;/*** 用户业务类*/public class UserService {/*** 登录方法* @param name 用户名* @param password 密码* @return true表示登录成功,false表示登录失败!*/public boolean login(String name,String password){if("admin".equals(name) && "123".equals(password)){return true;}return false;}// 可能还有一个同名login方法// java中怎么区分一个方法,依靠方法名和参数列表。public void login(int i){}/*** 退出系统的方法*/public void logout(){System.out.println("系统已经安全退出!");}}package com.bjpowernode.java.reflect;import java.lang.reflect.Method;import java.lang.reflect.Modifier;/*作为了解内容(不需要掌握):反射Method*/public class ReflectTest08 {public static void main(String[] args) throws Exception{// 获取类了Class userServiceClass = Class.forName("com.bjpowernode.java.service.UserService");// 获取所有的Method(包括私有的!)Method[] methods = userServiceClass.getDeclaredMethods();//System.out.println(methods.length); // 2// 遍历Methodfor(Method method : methods){// 获取修饰符列表System.out.println(Modifier.toString(method.getModifiers()));// 获取方法的返回值类型System.out.println(method.getReturnType().getSimpleName());// 获取方法名System.out.println(method.getName());// 方法的修饰符列表(一个方法的参数可能会有多个。)Class[] parameterTypes = method.getParameterTypes();for(Class parameterType : parameterTypes){System.out.println(parameterType.getSimpleName());}}}}
反编译Method
package com.bjpowernode.java.reflect;import java.lang.reflect.Method;import java.lang.reflect.Modifier;/*了解一下,不需要掌握(反编译一个类的方法。)*/public class ReflectTest09 {public static void main(String[] args) throws Exception{StringBuilder s = new StringBuilder();//Class userServiceClass = Class.forName("com.bjpowernode.java.service.UserService");Class userServiceClass = Class.forName("java.lang.String");s.append(Modifier.toString(userServiceClass.getModifiers()) + " class "+userServiceClass.getSimpleName()+" {\n");Method[] methods = userServiceClass.getDeclaredMethods();for(Method method : methods){//public boolean login(String name,String password){}s.append("\t");s.append(Modifier.toString(method.getModifiers()));s.append(" ");s.append(method.getReturnType().getSimpleName());s.append(" ");s.append(method.getName());s.append("(");// 参数列表Class[] parameterTypes = method.getParameterTypes();for(Class parameterType : parameterTypes){s.append(parameterType.getSimpleName());s.append(",");}// 删除指定下标位置上的字符s.deleteCharAt(s.length() - 1);s.append("){}\n");}s.append("}");System.out.println(s);}}
⭐通过反射调用方法-Method
package reflect;import bean.UserService;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;public class Reflect10 {public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {//不使用反射机制,调用方法//创建对象UserService userService = new UserService();//调用方法/*要素分析:要素1:对象userService要素2:login方法名要素3:实际参数列表要素4:返回值*/boolean loginSuccess=userService.login("admin","123");System.out.println(loginSuccess?"登陆成功":"登录失败");//使用反射机制,调用方法//获取ClassClass usClass=Class.forName("bean.UserService");//实例化对象Object obj=usClass.newInstance();// 获取Method,重载的方法,通过参数列表进区分Method loginMethod =usClass.getDeclaredMethod("login", String.class, String.class);//想要访问public void login(int i) 这个方法Method loginMethod2=usClass.getDeclaredMethod("login", int.class);Method logoutMethod = usClass.getDeclaredMethod("logout");//调用方法// 反射机制中最最最最最重要的一个方法,必须记住。/*四要素:loginMethod方法obj对象"admin","123" 实参retValue 返回值*/Object retVaule=loginMethod.invoke(obj,"admin","123");System.out.println(retVaule);logoutMethod.invoke(obj); //此方法是void没有返回值,直接调用}}
获取Constructor(了解即可!)
反编译Constructor
package com.bjpowernode.java.bean;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;}@Overridepublic String toString() {return "Vip{" +"no=" + no +", name='" + name + '\'' +", birth='" + birth + '\'' +", sex=" + sex +'}';}}package com.bjpowernode.java.reflect;import java.lang.reflect.Constructor;import java.lang.reflect.Modifier;/*反编译一个类的Constructor构造方法。*/public class ReflectTest11 {public static void main(String[] args) throws Exception{StringBuilder s = new StringBuilder();Class vipClass = Class.forName("java.lang.String");s.append(Modifier.toString(vipClass.getModifiers()));s.append(" class ");s.append(vipClass.getSimpleName());s.append("{\n");// 拼接构造方法Constructor[] constructors = vipClass.getDeclaredConstructors();for(Constructor constructor : constructors){//public Vip(int no, String name, String birth, boolean sex) {s.append("\t");s.append(Modifier.toString(constructor.getModifiers()));s.append(" ");s.append(vipClass.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);}}
⭐通过反射调用构造方法-Constructor
package com.bjpowernode.java.reflect;import com.bjpowernode.java.bean.Vip;import java.lang.reflect.Constructor;/*比上一个例子(ReflectTest11)重要一些!!!通过反射机制调用构造方法实例化java对象。(这个不是重点)*/public class ReflectTest12 {public static void main(String[] args) throws Exception{// 不使用反射机制怎么创建对象Vip v1 = new Vip();Vip v2 = new Vip(110, "zhangsan", "2001-10-11", true);// 使用反射机制怎么创建对象呢?Class c = Class.forName("com.bjpowernode.java.bean.Vip");// 调用无参数构造方法Object obj = c.newInstance();System.out.println(obj);// 调用有参数的构造方法怎么办?// 第一步:先获取到这个有参数的构造方法Constructor con = c.getDeclaredConstructor(int.class, String.class, String.class,boolean.class);// 第二步:调用构造方法new对象Object newObj = con.newInstance(110, "jackson", "1990-10-11", true);System.out.println(newObj);// 获取无参数构造方法Constructor con2 = c.getDeclaredConstructor();Object newObj2 = con2.newInstance();System.out.println(newObj2);}}
⭐获取父类和父接口
package com.bjpowernode.java.reflect;/*重点:给你一个类,怎么获取这个类的父类,已经实现了哪些接口?*/public class ReflectTest13 {public static void main(String[] args) throws Exception{// String举例Class stringClass = Class.forName("java.lang.String");// 获取String的父类Class superClass = stringClass.getSuperclass();System.out.println(superClass.getName());// 获取String类实现的所有接口(一个类可以实现多个接口。)Class[] interfaces = stringClass.getInterfaces();for(Class in : interfaces){System.out.println(in.getName());}}}
进阶-注解
基本概念
注解,或者叫注释类型,英文单词是-Annotation
注解Annotation是一种引用数据类型。编译之后也是生成xxx.class文件。
怎么自定义注解呢?语法格式?
[修饰符列表] @interface 注解类型名{
}
- 注解怎么使用,用在什么地方?
- 注解使用时的语法格式是:
- @注解类型名
- 注解可以出现在类上、属性上、方法上、变量上等….
- 注解使用时的语法格式是:
注解还可以出现在注解类型上。
代码
package com.bjpowernode.java.annotation;/*自定义注解:MyAnnotation*/public @interface MyAnnotation {// ??????}🏀package com.bjpowernode.java.annotation;// 注解修饰注解。@MyAnnotationpublic @interface OtherAnnotation {}🏀package com.bjpowernode.java.annotation;// 默认情况下,注解可以出现在任意位置。@MyAnnotationpublic class AnnotationTest01 {@MyAnnotationprivate int no;@MyAnnotationpublic AnnotationTest01(){}@MyAnnotationpublic static void m1(){@MyAnnotationint i = 100;}@MyAnnotationpublic void m2(@MyAnnotationString name,@MyAnnotationint k){}}@MyAnnotationinterface MyInterface {}@MyAnnotationenum Season {SPRING,SUMMER,AUTUMN,WINTER}
JDK中内置的注解
java.lang包下的注释类型
- Deprecated 用 @Deprecated 注释的程序元素
不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择。
Override 表示一个方法声明打算重写超类中的另一个方法声明。
SuppressWarnings 指示应该在注释元素(以及包含在该注释元素中的所有程序元素)中
取消显示指定的编译器警告。 (不需要掌握)
⭐Override
源代码:public @interface Override { }
标识性注解,给编译器做参考的。
编译器看到方法上有这个注解的时候,编译器会自动检查该方法是否重写了父类的方法。
如果没有重写,报错。
这个注解只是在编译阶段起作用,和运行期无关!
@Override这个注解只能注解方法。
凡是java中的方法带有这个注解的,编译器都会进行编译检查,
如果这个方法不是重写父类的方法,编译器报错。
⭐Deprecated
Deprecated这个注解标注的元素已过时。
这个注解主要是向其它程序员传达一个信息,告知已过时,有更好的解决方案存在。

表示doSome方法已过时。
代码
//@Override-报错public class AnnotationTest02 {//@Override-报错private int no;@Overridepublic String toString() {return "toString";}}
元注解
知识点
- 什么是元注解?
用来标注“注解类型”的“注解”,称为元注解。
- 常见的元注解有哪些?
Target Retention
⭐Target注解
知识点
- 这是一个元注解,用来标注“注解类型”的“注解”。
这个Target注解用来标注“被标注的注解”可以出现在哪些位置上。
语法
@Target(ElementType.METHOD):表示“被标注的注解”只能出现在方法上。
- @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE})
表示该注解可以出现在:构造方法上,字段上,字段上,方法上········类上…
此注解就表示Override注解只能出现在方法上。
⭐Retention注解
知识点
- 这是一个元注解,用来标注“注解类型”的“注解”
这个Retention注解用来标注“被标注的注解”最终保存在哪里。
语法
@Retention(RetentionPolicy.SOURCE):表示该注解只被保留在java源文件中。
- @Retention(RetentionPolicy.CLASS):表示该注解被保存在class文件中。
- @Retention(RetentionPolicy.RUNTIME):表示该注解被保存在class文件中,并且可以被反射机制所读取。

此注解就表示Override注解只被保留在java源文件中。
源代码
//元注解public @interface Retention {//属性 是一个value所以代表value可以省略不写RetentionPolicy value();}RetentionPolicy的源代码: 是一个枚举类型public enum RetentionPolicy {SOURCE,CLASS,RUNTIME}@Retention(RetentionPolicy.RUNTIME) == @Retention(value=RetentionPolicy.RUNTIME)
注解中定义属性
知识点
- 如果一个注解当中有属性,那么必须给属性赋值。(除非该属性使用default指定了默认值。)
- 语法:@MyAnnotation(属性名=属性值,属性名=属性值,属性名=属性值)
- 如果一个注解的属性的名字是value,并且只有一个属性的话,在使用的时候,该属性名可以省略。
@MyAnnotation(value = “hehe”) == @MyAnnotation(“haha”)
代码
package com.bjpowernode.java.annotation2;public @interface MyAnnotation {/*** 我们通常在注解当中可以定义属性,以下这个是MyAnnotation的name属性。* 看着像1个方法,但实际上我们称之为属性name。* @return*/String name();/*颜色属性*/String color();/*年龄属性*/int age() default 25; //属性指定默认值}package com.bjpowernode.java.annotation2;public class MyAnnotationTest {// 报错的原因:如果一个注解当中有属性,那么必须给属性赋值。(除非该属性使用default指定了默认值。)/*@MyAnnotationpublic void doSome(){}*///@MyAnnotation(属性名=属性值,属性名=属性值,属性名=属性值)//指定name属性的值就好了。@MyAnnotation(name = "zhangsan", color = "红色")public void doSome(){}}
注解中的属性可以是什么类型?
属性的类型可以是:byte short int long float double boolean char
String Class 枚举类型
以及以上每一种的数组形式。 ```java public @interface MyAnnotation { /* 注解当中的属性可以是哪一种类型?属性的类型可以是:byte short int long float double boolean char String Class 枚举类型以及以上每一种的数组形式。
*/ int value1();
String value2();
int[] value3();
String[] value4();
Season value5();
Season[] value6();
Class parameterType();
Class[] parameterTypes(); }
<a name="AT1eW"></a>#### 当注解的属性是数组时1. 数组是大括号1. 如果数组中只有1个元素:大括号可以省略。```java//定义一个枚举类型public enum Season {SPRING,SUMMER,AUTUMN,WINTER}//自定义一个注解public @interface OtherAnnotation {/*年龄属性*/int age();/*邮箱地址属性,支持多个*/String[] email();/*** 季节数组,Season是枚举类型* @return*/Season[] seasonArray();}public class OtherAnnotationTest {// 数组是大括号@OtherAnnotation(age = 25, email = {"zhangsan@123.com", "zhangsan@sohu.com"}, seasonArray = Season.WINTER)public void doSome(){}// 如果数组中只有1个元素:大括号可以省略。@OtherAnnotation(age = 25, email = "zhangsan@123.com", seasonArray = {Season.SPRING, Season.SUMMER})public void doOther(){}}
反射注解
MyAnnotation
//只允许该注解可以标注类、方法@Target({ElementType.TYPE,ElementType.METHOD})// 希望这个注解可以被反射@Retention(RetentionPolicy.RUNTIME)public @interface MyAnnotation {String value() default "xyb";}
MyAnnotationTest
@MyAnnotationpublic class MyAnnotationTest {@MyAnnotationpublic void doSome(){}/* 不可以@MyAnnotationint i;*/}
ReflectAnnotationTest
public class ReflectAnnotationTest {public static void main(String[] args) throws ClassNotFoundException {//获取这个类Class c=Class.forName("Annotation.MyAnnotationTest");//判断此类是否有@MyAnnotationSystem.out.println(c.isAnnotationPresent(MyAnnotation.class)); //trueif (c.isAnnotationPresent(MyAnnotation.class)){//获取该注解对象MyAnnotation myAnnotation=(MyAnnotation) c.getAnnotation(MyAnnotation.class);//类上面的注解对象@Annotation.MyAnnotation(value=xyb)System.out.println("类上面的注解对象" + myAnnotation);//获取注解对象的属性String value=myAnnotation.value();System.out.println(value);}}}
注解在开发中的作用
知识点
- 注解在程序中等同于一种标记
实例
假设有这样一个注解,叫做:@Id
这个注解只能出现在类上面,当这个类上有这个注解的时候,
要求这个类中必须有一个int类型的id属性。
如果没有这个属性就报异常。如果有这个属性则正常执行!
MustHasIdPropertyAnnotation
// 这个注解@MustHasIdPropertyAnnotation用来标注类,被标注的类中必须有一个int类型的id属性,没有就报异常。// 表示这个注解只能出现在类上面@Target(ElementType.TYPE)// 该注解可以被反射机制读取到@Retention(RetentionPolicy.RUNTIME)public @interface MustHasIdPropertyAnnotation {}
User
@MustHasIdPropertyAnnotationpublic class User {int id;String name;String password;}
HasNotIdPropertyException
/*自定义异常*/public class HasNotIdPropertyException extends RuntimeException {public HasNotIdPropertyException(){}public HasNotIdPropertyException(String s){super(s);}}
Test
public class Test {public static void main(String[] args) throws Exception{// 获取类Class userClass = Class.forName("com.bjpowernode.java.annotation7.User");// 判断类上是否存在Id注解if(userClass.isAnnotationPresent(MustHasIdPropertyAnnotation.class)){// 当一个类上面有@MustHasIdPropertyAnnotation注解的时候,要求类中必须存在int类型的id属性// 如果没有int类型的id属性则报异常。// 获取类的属性Field[] fields = userClass.getDeclaredFields();boolean isOk = false; // 给一个默认的标记for(Field field : fields){if("id".equals(field.getName()) && "int".equals(field.getType().getSimpleName())){// 表示这个类是合法的类。有@Id注解,则这个类中必须有int类型的idisOk = true; // 表示合法break;}}// 判断是否合法if(!isOk){throw new HasNotIdPropertyException("被@MustHasIdPropertyAnnotation注解标注的类中必须要有一个int类型的id属性!");}}}}

