- 进阶-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); //97
readData = fis.read();
System.out.println(readData); //98
readData = fis.read();
System.out.println(readData); //99
readData = fis.read();
System.out.println(readData); //100
readData = fis.read();
System.out.println(readData); //101
readData = fis.read();
System.out.println(readData); //102
// 已经读到文件的末尾了,再读的时候读取不到任何数据,返回-1.
readData = fis.read();
System.out.println(readData);//-1
readData = fis.read();
System.out.println(readData);//-1
readData = 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读取数据,快捷键生成catch
int readDate = 0;
while(readDate = fis.read()!= -1){
System.out.println(readDate);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally { //第三步:手写finally
if (fis!=null){ //第四步:判断fis是否为空
try {
fis.close(); //第五步:关闭流,快捷键生成try catch
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
**分析以上程序的缺点:**<br />一次读取一个字节byte,这样内存和硬盘交互太频繁,基本上时间/资源都耗费在交互上了。<br />能不能一次读取多个字节呢?可以!
**使用byte[]数组进行改写:**
```java
package 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个字节都没有读取到返回-1
System.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= null
FileInputStream fis = null;
//第二步:创建对象,选择要读取的文件。使用快捷键生成try catch
try {
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:读不到数据时,返回-1
while ((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= null
FileInputStream fis = null;
//第二步:创建对象,选择要读取的文件。使用快捷键生成try catch
try {
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>
#### 原理
![image.png](https://cdn.nlark.com/yuque/0/2021/png/2858485/1620281857883-f3d9ec35-803f-4dbe-8f5b-32fdedd1b9f7.png#clientId=uc7aad4b8-5a8a-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=340&id=u24a12d84&margin=%5Bobject%20Object%5D&name=image.png&originHeight=680&originWidth=1169&originalType=binary&ratio=1&rotation=0&showTitle=false&size=88186&status=done&style=none&taskId=u3803bce6-2f9e-4f56-b19d-ea18e2fe0bd&title=&width=585)
<a name="DlBie"></a>
#### 代码
1. 使用FileInputStream + FileOutputStream完成文件的拷贝
1. 拷贝的过程应该是一边读,一边写。
1. 使用字节流拷贝文件的时候,文件类型随意,万能的。
```java
package 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>
#### 使用步骤
```java
package 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>
#### 代码
```java
package 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>
#### 使用步骤
```java
package 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>
#### 使用步骤
```java
package 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() :**获取文件大小:单位是字节。
```java
package 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>
### 原理
![image.png](https://cdn.nlark.com/yuque/0/2021/png/2858485/1620392783750-03deb95d-80f4-4584-8e8b-9a9e83060a63.png#clientId=ucd49bffe-5737-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=339&id=u5d9db1f3&margin=%5Bobject%20Object%5D&name=image.png&originHeight=677&originWidth=1300&originalType=binary&ratio=1&rotation=0&showTitle=false&size=218966&status=done&style=none&taskId=ua0b7ba27-9677-44fc-9f51-dd5f67707e8&title=&width=650)
<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类**
```java
package 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;
}
@Override
public 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;
}
@Override
public 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=value
key2=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获取value
String 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. 分析以下程序有几个线程?
![image.png](https://cdn.nlark.com/yuque/0/2021/png/2858485/1620526711116-ccb64b72-1190-4993-9885-c7c3cd10aa13.png#clientId=u71c4058d-4614-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=517&id=uf9b2dd93&margin=%5Bobject%20Object%5D&name=image.png&originHeight=689&originWidth=529&originalType=binary&ratio=1&rotation=0&showTitle=false&size=265110&status=done&style=none&taskId=u9986ee47-243a-4474-a566-80712be4f7f&title=&width=397)![image.png](https://cdn.nlark.com/yuque/0/2021/png/2858485/1620526807138-8d8d8f71-114c-4d05-898b-b29a5d991e7e.png#clientId=u71c4058d-4614-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=226&id=rC6X5&margin=%5Bobject%20Object%5D&name=image.png&originHeight=452&originWidth=569&originalType=binary&ratio=1&rotation=0&showTitle=false&size=17394&status=done&style=none&taskId=uafe9c766-c1a0-4efd-8032-669b2809ffc&title=&width=285)<br />答:一个!只要一个主线程 主栈。没有分支线程!
<a name="SUyHY"></a>
### 图解
**堆和方法区共享,栈独立。**<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/2858485/1620525000598-18a554c9-ae9d-4c34-aab1-ba1575169717.png#clientId=u71c4058d-4614-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=506&id=u69c579e6&margin=%5Bobject%20Object%5D&name=image.png&originHeight=675&originWidth=1269&originalType=binary&ratio=1&rotation=0&showTitle=false&size=140218&status=done&style=stroke&taskId=u50c72619-171f-44fc-be1f-8111e882a2c&title=&width=952)
<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>
#### 代码
```java
package 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{
@Override
public 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{
@Override
public 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(){
@Override
public 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() {
@Override
public 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-0
MyThread2 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方法,那么这个当前线程就是t2
Thread 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()方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常。
@Override
public 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() 合并线程
```java
class 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;
}
@Override
public void run() {
//run()方法表示取款操作
//假设取款5000
double 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);
//设置name
t1.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方法的结束吗?
```java
package 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(){ //加上了 synchronized
System.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方法的结束吗? ⭐⭐⭐
```java
package 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,o2
Thread t1 = new MyThread1(o1,o2);
Thread t2 = new MyThread2(o1,o2);
t1.start();
t2.start();
}
}
class MyThread1 extends Thread{
Object o1;
Object o2;
public MyThread1(Object o1,Object o2){
this.o1 = o1;
this.o2 = o2;
}
public void run(){
synchronized (o1){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2){
}
}
}
}
class MyThread2 extends Thread {
Object o1;
Object o2;
public MyThread2(Object o1,Object o2){
this.o1 = o1;
this.o2 = o2;
}
public void run(){
synchronized (o2){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1){
}
}
}
}
在以后的开发中应该怎么解决线程安全问题
首选是线程同步吗?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{
@Override
public 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(){
@Override
public void run() {
// code....
}
} , firstTime, 1000 * 10);
}
}
}
class LogTimerTask extends TimerTask{
@Override
public 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;
}
@Override
public 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;
}
@Override
public 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:
```java
public class User{ //java.lang.Class
private int no; //java.lang.reflect.Field
//java.lang.reflect.Constructor
public User(){
}
public User(int no){
this.no=no;
}
//java.lang.reflect.Method
public 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 ssm
Spring SpringMVC MyBatis
Spring 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");
// 创建属性类对象Map
Properties pro = new Properties(); // key value都是String
// 加载
pro.load(reader);
// 关闭流
reader.close();
// 通过key获取value
String className = pro.getProperty("className");
//System.out.println(className);
// 通过反射机制实例化对象
Class c = Class.forName(className);
Object 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.properties
System.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>
### 以流的形式直接返回
```java
package 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获取value
String 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>
### ⭐通过资源绑定器-反射实例化对象
最终方法!
```java
package 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修饰的Field
Field[] fields=c1.getFields();
//取出fields中的元素,并输出名字
Field f1 = fields[0];
System.out.println(f1.getName());
System.out.println("===================");
//获取所有的Field
Field[] 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:对象car
2:name属性
3:值 领克
*/
car.name="领克";
//获取属性的值
/*
获取car对象的name属性的二要素:
1:对象car
2:name属性
*/
System.out.println(car.name);
⭐//使用反射机制,怎么样去访问一个对象的属性呢?
🏀//获取Class
Class 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:对象obj
2:name属性-namefield就代表name属性
3:值 保时捷
注意:反射机制让代码复杂了,但是为了一个“灵活”,这也是值得的。
*/
namefield.set(obj,"保时捷");
🏀//读取属性的值
/*
获取obj对象的name属性的二要素:
1:对象obj
2: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
// 遍历Method
for(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?"登陆成功":"登录失败");
//使用反射机制,调用方法
//获取Class
Class 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;
}
@Override
public 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;
// 注解修饰注解。
@MyAnnotation
public @interface OtherAnnotation {
}
🏀
package com.bjpowernode.java.annotation;
// 默认情况下,注解可以出现在任意位置。
@MyAnnotation
public class AnnotationTest01 {
@MyAnnotation
private int no;
@MyAnnotation
public AnnotationTest01(){}
@MyAnnotation
public static void m1(){
@MyAnnotation
int i = 100;
}
@MyAnnotation
public void m2(@MyAnnotation
String name,
@MyAnnotation
int k){
}
}
@MyAnnotation
interface MyInterface {
}
@MyAnnotation
enum 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;
@Override
public 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指定了默认值。)
/*@MyAnnotation
public 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
@MyAnnotation
public class MyAnnotationTest {
@MyAnnotation
public void doSome(){}
/* 不可以
@MyAnnotation
int i;*/
}
ReflectAnnotationTest
public class ReflectAnnotationTest {
public static void main(String[] args) throws ClassNotFoundException {
//获取这个类
Class c=Class.forName("Annotation.MyAnnotationTest");
//判断此类是否有@MyAnnotation
System.out.println(c.isAnnotationPresent(MyAnnotation.class)); //true
if (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
@MustHasIdPropertyAnnotation
public 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类型的id
isOk = true; // 表示合法
break;
}
}
// 判断是否合法
if(!isOk){
throw new HasNotIdPropertyException("被@MustHasIdPropertyAnnotation注解标注的类中必须要有一个int类型的id属性!");
}
}
}
}