进阶-IO流

基本概念

什么是IO?

I:Input O:Output
通过IO可以完成硬盘文件的读和写。
图解:
image.png

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个字节。)

综上所述:流的分类
输入流,输出流
字节流,字符流

  1. 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个

  1. 文件专属:

字节流:byte[]
java.io.FileInputStream(掌握)
java.io.FileOutputStream(掌握)

字符流:char[]
java.io.FileReader
java.io.FileWriter

  1. 转换流:(将字节流转换成字符流)

java.io.InputStreamReader
java.io.OutputStreamWriter

  1. 缓冲流专属:

java.io.BufferedReader
java.io.BufferedWriter
java.io.BufferedInputStream
java.io.BufferedOutputStream

  1. 数据流专属:

java.io.DataInputStream
java.io.DataOutputStream

  1. 标准输出流:

java.io.PrintWriter
java.io.PrintStream(掌握)

  1. 对象专属流:(序列化与反序列化)

java.io.ObjectInputStream(掌握)
java.io.ObjectOutputStream(掌握)

————⭐字节流⭐————————

FileInputStream(读)⭐

知识点

  1. 文件字节输入流,万能的,任何类型的文件都可以采用这个流来读。
  2. 字节的方式,完成输入的操作,完成读的操作(硬盘—>内存)。

    使用步骤

    1. public class FileInputStreamTest01 {
    2. public static void main(String[] args) {
    3. //先创建fis,防止空指针异常。
    4. FileInputStream fis = null;
    5. try {
    6. // 创建文件字节输入流对象
    7. // 文件路径:D:\course\JavaProjects\02-JavaSE\temp (IDEA会自动把\编程\\,因为java中\表示转义)
    8. // 以下都是采用了:绝对路径的方式。
    9. //FileInputStream fis = new FileInputStream("D:\\course\\JavaProjects\\02-JavaSE\\temp");
    10. // 写成这个/也是可以的。
    11. fis = new FileInputStream("D:/course/JavaProjects/02-JavaSE/temp");
    12. // 开始读
    13. int readData = fis.read(); // 这个方法的返回值是:读取到的“字节”本身。
    14. System.out.println(readData); //97
    15. readData = fis.read();
    16. System.out.println(readData); //98
    17. readData = fis.read();
    18. System.out.println(readData); //99
    19. readData = fis.read();
    20. System.out.println(readData); //100
    21. readData = fis.read();
    22. System.out.println(readData); //101
    23. readData = fis.read();
    24. System.out.println(readData); //102
    25. // 已经读到文件的末尾了,再读的时候读取不到任何数据,返回-1.
    26. readData = fis.read();
    27. System.out.println(readData);//-1
    28. readData = fis.read();
    29. System.out.println(readData);//-1
    30. readData = fis.read();
    31. System.out.println(readData);//-1
    32. } catch (FileNotFoundException e) {
    33. e.printStackTrace();//捕获的是FileInputStream相关的异常
    34. } catch (IOException e) {
    35. e.printStackTrace();//捕获的是read相关的异常
    36. } finally {
    37. // 在finally语句块当中确保流一定关闭。
    38. if (fis != null) { // 避免空指针异常!
    39. // 关闭流的前提是:流不是空。流是null的时候没必要关闭。
    40. try {
    41. fis.close();
    42. } catch (IOException e) {
    43. e.printStackTrace();
    44. }
    45. }
    46. }
    47. }
    48. }

    使用循环改写以上代码: ```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”);

  1. while (true){
  2. int readDate=fis.read();
  3. if (readDate==-1){
  4. break;
  5. }
  6. System.out.println(readDate);
  7. }
  8. ⭐⭐//改造while循环
  9. //第六步:通过read读取数据,快捷键生成catch
  10. int readDate = 0;
  11. while(readDate = fis.read()!= -1){
  12. System.out.println(readDate);
  13. }
  14. } catch (FileNotFoundException e) {
  15. e.printStackTrace();
  16. } catch (IOException e) {
  17. e.printStackTrace();
  18. } finally { //第三步:手写finally
  19. if (fis!=null){ //第四步:判断fis是否为空
  20. try {
  21. fis.close(); //第五步:关闭流,快捷键生成try catch
  22. } catch (IOException e) {
  23. e.printStackTrace();
  24. }
  25. }
  26. }
  27. }

}

  1. **分析以上程序的缺点:**<br />一次读取一个字节byte,这样内存和硬盘交互太频繁,基本上时间/资源都耗费在交互上了。<br />能不能一次读取多个字节呢?可以!
  2. **使用byte[]数组进行改写:**
  3. ```java
  4. package com.bjpowernode.java.io;
  5. import java.io.FileInputStream;
  6. import java.io.FileNotFoundException;
  7. import java.io.IOException;
  8. /*
  9. int read(byte[] b)
  10. 一次最多读取 b.length 个字节。
  11. 减少硬盘和内存的交互,提高程序的执行效率。
  12. 往byte[]数组当中读。
  13. */
  14. public class FileInputStreamTest03 {
  15. public static void main(String[] args) {
  16. FileInputStream fis = null;
  17. try {
  18. // 相对路径的话呢?相对路径一定是从当前所在的位置作为起点开始找!
  19. // IDEA默认的当前路径是哪里?工程Project的根就是IDEA的默认当前路径。
  20. //fis = new FileInputStream("tempfile3");
  21. //fis = new FileInputStream("chapter23/tempfile2");
  22. //fis = new FileInputStream("chapter23/src/tempfile3");
  23. fis = new FileInputStream("chapter23/src/com/bjpowernode/java/io/tempfile4");
  24. // 开始读,采用byte数组,一次读取多个字节。最多读取“数组.length”个字节。
  25. byte[] bytes = new byte[4]; // 准备一个4个长度的byte数组,一次最多读取4个字节。
  26. // 这个方法的返回值是:读取到的字节数量。(不是字节本身)
  27. int readCount = fis.read(bytes);
  28. System.out.println(readCount); // 第一次读到了4个字节。
  29. // 将字节数组全部转换成字符串
  30. //System.out.println(new String(bytes)); // abcd
  31. // 不应该全部都转换,应该是读取了多少个字节,转换多少个。
  32. System.out.println(new String(bytes,0, readCount));
  33. readCount = fis.read(bytes); // 第二次只能读取到2个字节。
  34. System.out.println(readCount); // 2
  35. // 将字节数组全部转换成字符串
  36. //System.out.println(new String(bytes)); // efcd
  37. // 不应该全部都转换,应该是读取了多少个字节,转换多少个。
  38. System.out.println(new String(bytes,0, readCount));
  39. readCount = fis.read(bytes); // 1个字节都没有读取到返回-1
  40. System.out.println(readCount); // -1
  41. } catch (FileNotFoundException e) {
  42. e.printStackTrace();
  43. } catch (IOException e) {
  44. e.printStackTrace();
  45. } finally {
  46. if (fis != null) {
  47. try {
  48. fis.close();
  49. } catch (IOException e) {
  50. e.printStackTrace();
  51. }
  52. }
  53. }
  54. }
  55. }

⭐FileInputStream最终版

  1. package IO;
  2. import java.io.FileInputStream;
  3. import java.io.FileNotFoundException;
  4. import java.io.IOException;
  5. public class FileInputStreamTest04 {
  6. public static void main(String[] args) {
  7. //第一步:创建fis= null
  8. FileInputStream fis = null;
  9. //第二步:创建对象,选择要读取的文件。使用快捷键生成try catch
  10. try {
  11. fis = new FileInputStream("chapter23/src/IO/mu");
  12. //第五步:使用read读取字节。
  13. //创建byte数组
  14. byte[] bytes = new byte[4];
  15. /*while(true){
  16. int readCount = fis.read(bytes);
  17. if(readCount == -1){
  18. break;
  19. }
  20. // 把byte数组转换成字符串,读到多少个转换多少个。
  21. System.out.print(new String(bytes, 0, readCount));
  22. }*/
  23. //改写以上while循环
  24. int readCount = 0;
  25. //-1:读不到数据时,返回-1
  26. while ((readCount = fis.read(bytes))!= -1){ // 这个方法的返回值是:读取到的字节数量。(不是字节本身)
  27. //将byte数组转换为String并输出。
  28. System.out.print(new String(bytes,0,readCount));
  29. }
  30. } catch (FileNotFoundException e) {
  31. e.printStackTrace();
  32. } catch (IOException e) {
  33. e.printStackTrace();
  34. } finally { //第三步:写finally,用来关闭流
  35. if (fis!=null){ //第四步:使用if判断fis是否为空,不为空则关闭。
  36. try {
  37. fis.close();
  38. } catch (IOException e) {
  39. e.printStackTrace();
  40. }
  41. }
  42. }
  43. }
  44. }

FileInputStream其他常用方法

  1. int available(): 返回流当中剩余的没有读到的字节数量。
    1. 作用:可以不需要使用whlie循环直接输出数据。
  2. long skip(long n): 跳过几个字节不读。

使用available()改写程序:这种方法不适合太大的文件,因为byte[]不可以太大、

  1. package IO;
  2. import java.io.FileInputStream;
  3. import java.io.FileNotFoundException;
  4. import java.io.IOException;
  5. public class FileInputStreamTest05 {
  6. public static void main(String[] args) {
  7. //第一步:创建fis= null
  8. FileInputStream fis = null;
  9. //第二步:创建对象,选择要读取的文件。使用快捷键生成try catch
  10. try {
  11. fis = new FileInputStream("chapter23/src/IO/mu");
  12. ⭐⭐
  13. //通过available()方法获取剩余的字节数。从开头获取,就是总字节数。
  14. fis.available();
  15. //第五步:使用read读取字节。
  16. //创建byte数组
  17. byte[] bytes = new byte[fis.available()];
  18. //读取数组
  19. fis.read(bytes);
  20. System.out.println(new String(bytes));
  21. ⭐⭐
  22. } catch (FileNotFoundException e) {
  23. e.printStackTrace();
  24. } catch (IOException e) {
  25. e.printStackTrace();
  26. } finally { //第三步:写finally,用来关闭流
  27. if (fis!=null){ //第四步:使用if判断fis是否为空,不为空则关闭。
  28. try {
  29. fis.close();
  30. } catch (IOException e) {
  31. e.printStackTrace();
  32. }
  33. }
  34. }
  35. }
  36. }

FileOutputStream(写)⭐

知识点

  1. 文件字节输出流,负责写。
  2. 字节的方式,完成输出的操作,完成写的操作(内存—>硬盘)。

    使用步骤

    ```java package IO;

import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException;

public class FileOutputStreamTest01 { public static void main(String[] args) { FileOutputStream fos = null; try { ⭐//若myfile文件不存在,会自动创建。 //fos = new FileOutputStream(“myfile”);

  1. //若文件存在,则会将原文件清空,再写入。 谨慎使用!!!
  2. //fos = new FileOutputStream("chapter23/src/IO/mu");
  3. // 以追加的方式在文件末尾写入。不会清空原文件内容。 建议使用!
  4. fos = new FileOutputStream("chapter23/src/IO/mu",true);
  5. //写
  6. byte[] bytes = {97,98,99};
  7. //将bytes数组全部写出
  8. fos.write(bytes);
  9. // 将byte数组的一部分写出!
  10. fos.write(bytes,0,1);
  11. //写字符串
  12. String s = "穆徐";
  13. //将字符串转换为数组--getBytes()
  14. byte[] bytes1 = s.getBytes();
  15. fos.write(bytes1);
  16. // 写完之后,最后一定要刷新⭐
  17. fos.flush();
  18. } catch (FileNotFoundException e) {
  19. e.printStackTrace();
  20. } catch (IOException e) {
  21. e.printStackTrace();
  22. } finally {
  23. if (fos!=null){
  24. try {
  25. fos.close();
  26. } catch (IOException e) {
  27. e.printStackTrace();
  28. }
  29. }
  30. }
  31. }

}

  1. <a name="RPcFt"></a>
  2. ### 文件复制
  3. <a name="zEClS"></a>
  4. #### 原理
  5. ![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)
  6. <a name="DlBie"></a>
  7. #### 代码
  8. 1. 使用FileInputStream + FileOutputStream完成文件的拷贝
  9. 1. 拷贝的过程应该是一边读,一边写。
  10. 1. 使用字节流拷贝文件的时候,文件类型随意,万能的。
  11. ```java
  12. package IO;
  13. import java.io.FileInputStream;
  14. import java.io.FileNotFoundException;
  15. import java.io.FileOutputStream;
  16. import java.io.IOException;
  17. public class Copy01 {
  18. public static void main(String[] args) {
  19. FileInputStream fis = null;
  20. FileOutputStream fos = null;
  21. try {
  22. // 创建一个输入流对象
  23. fis = new FileInputStream("C:\\Users\\Administrator\\Desktop\\testmu\\test.txt");
  24. // 创建一个输出流对象
  25. fos = new FileOutputStream("C:\\Users\\Administrator\\Desktop\\testmuu\\test.txt");
  26. ⭐ // 最核心的:一边读,一边写 ⭐
  27. byte[] bytes = new byte[1024*1024]; //1MB(一次最多拷贝1Mb)
  28. int readCount = 0;
  29. while ((readCount = fis.read(bytes))!= -1){
  30. fos.write(bytes,0,readCount);
  31. }
  32. //输出流,最后一定要flush()一下,记住,谢谢。
  33. fos.flush();
  34. }
  35. catch (FileNotFoundException e) {
  36. e.printStackTrace();
  37. } catch (IOException e) {
  38. e.printStackTrace();
  39. } finally {
  40. //分开try,因为如果一起try的时候,一个出现异常,可能会影响到另一个流的关闭。
  41. if (fis!=null){
  42. try {
  43. fis.close();
  44. } catch (IOException e) {
  45. e.printStackTrace();
  46. }
  47. }
  48. if (fos!=null){
  49. try {
  50. fos.close();
  51. } catch (IOException e) {
  52. e.printStackTrace();
  53. }
  54. }
  55. }
  56. }
  57. }

—————-字符流——————————-

FileReader(读)

知识点

  1. 文件字符输入流,只能读取普通文本。
  2. 读取文本内容时,比较方便,快捷。

    使用步骤

    ```java package IO;

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”);

  1. char[] chars = new char[4]; //一次最多读取4个字符。
  2. /* 读
  3. int readCount = fr.read(chars);
  4. System.out.println(new String(chars,0,readCount)); //ab穆徐
  5. */
  6. int readCount = 0;
  7. while ((readCount=fr.read(chars))!=-1){
  8. System.out.print(new String(chars,0,readCount));
  9. }
  10. } catch (FileNotFoundException e) {
  11. e.printStackTrace();
  12. } catch (IOException e) {
  13. e.printStackTrace();
  14. } finally {
  15. if (fr!=null){
  16. try {
  17. fr.close();
  18. } catch (IOException e) {
  19. e.printStackTrace();
  20. }
  21. }
  22. }
  23. }

}

  1. <a name="zoJzA"></a>
  2. ### FileWrite(写)
  3. <a name="IhVun"></a>
  4. #### 知识点
  5. 1. 文件字符输出流。写。
  6. 1. 只能输出普通文本。
  7. <a name="ssGG1"></a>
  8. #### 使用步骤
  9. ```java
  10. package IO;
  11. import java.io.FileWriter;
  12. import java.io.IOException;
  13. public class FileWriteTest01 {
  14. public static void main(String[] args) {
  15. FileWriter fw = null;
  16. try {
  17. fw = new FileWriter("NBA",true);
  18. char[] chars = {'詹','姆','斯'};
  19. fw.write(chars);
  20. //优点,可以直接写字符串,不通过数组。
  21. fw.write(",科比布莱恩特");
  22. //刷新
  23. fw.flush();
  24. } catch (IOException e) {
  25. e.printStackTrace();
  26. }finally {
  27. if (fw!=null){
  28. try {
  29. fw.close();
  30. } catch (IOException e) {
  31. e.printStackTrace();
  32. }
  33. }
  34. }
  35. }
  36. }

文件复制

代码

  1. package IO;
  2. import java.io.FileNotFoundException;
  3. import java.io.FileReader;
  4. import java.io.FileWriter;
  5. import java.io.IOException;
  6. public class Copy02 {
  7. public static void main(String[] args) {
  8. FileReader fr = null;
  9. FileWriter fw = null;
  10. try {
  11. fr = new FileReader("C:\\Users\\Administrator\\Desktop\\testmu\\test.txt");
  12. fw = new FileWriter("C:\\Users\\Administrator\\Desktop\\testmuu\\new.txt");
  13. //一边读一边写
  14. char[] chars = new char[1024*512];
  15. int readCount = 0;
  16. while ((readCount=fr.read(chars))!=-1){
  17. fw.write(chars,0,readCount);
  18. }
  19. //刷新
  20. fw.flush();
  21. } catch (FileNotFoundException e) {
  22. e.printStackTrace();
  23. } catch (IOException e){
  24. e.printStackTrace();
  25. }finally {
  26. if (fr!=null){
  27. try {
  28. fr.close();
  29. } catch (IOException e) {
  30. e.printStackTrace();
  31. }
  32. }
  33. if (fw!=null){
  34. try {
  35. fw.close();
  36. } catch (IOException e) {
  37. e.printStackTrace();
  38. }
  39. }
  40. }
  41. }
  42. }

—————-缓冲流 转换流——————-

BufferedReader

知识点

  1. 带有缓冲区的字符输入流。
  2. 使用这个流的时候不需要自定义char[]数组或者byte[]数组。自带缓冲

    使用步骤

    readLine()的使用 ```java package IO;

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;

  1. try {
  2. fr = new FileReader("NBA");
  3. //当一个流的构造方法中需要一个流的时候,这个被传进来的流叫做:节点流。
  4. //外部负责包装的这个流,叫做:包装流,还有一个名字叫做:处理流。
  5. //当前这个程序来说: 节点流: FileReader
  6. // 包装流/处理流:BufferedReader
  7. br = new BufferedReader(fr);
  8. /* //readLine() 读一行
  9. String one=br.readLine();
  10. System.out.println(one);
  11. //readLine() 再读一行
  12. String two=br.readLine();
  13. System.out.println(two);
  14. */
  15. //使用while循环改写,读不到就返回Null,所以
  16. String s = null;
  17. // readLine()读取一个文本行,但不带换行符。
  18. while ((s=br.readLine())!=null){
  19. System.out.println(s);
  20. }
  21. } catch (FileNotFoundException e) {
  22. e.printStackTrace();
  23. } catch (IOException e) {
  24. e.printStackTrace();
  25. } finally {
  26. if (br != null){
  27. try {
  28. //对于包装流来说,只需要关闭最外层流就行,里面的节点流会自动关闭。
  29. br.close();
  30. } catch (IOException e) {
  31. e.printStackTrace();
  32. }
  33. }
  34. }
  35. }

}

  1. <a name="aVbdL"></a>
  2. ### InputStreamReader
  3. <a name="tFR7K"></a>
  4. #### 原理
  5. (FileInputStream(字节) -> FileReader(字符))
  6. <a name="Xq51x"></a>
  7. #### 代码
  8. ```java
  9. package IO;
  10. import java.io.*;
  11. public class InputStreamReaderTest01 {
  12. public static void main(String[] args) {
  13. FileInputStream fis = null;
  14. InputStreamReader isr = null;
  15. BufferedReader br=null;
  16. try {
  17. //字节流
  18. fis=new FileInputStream("NBA");
  19. /*将fis转换为字符流
  20. * isr是包装流,fis是节点流。
  21. * */
  22. isr = new InputStreamReader(fis);
  23. /*
  24. * 这个构造方法只能传字符流,不能传字节流。
  25. * isr是节点流,br是包装流。
  26. * */
  27. br = new BufferedReader(isr);
  28. ⭐//合并以上三行代码
  29. br = new BufferedReader(new InputStreamReader(new FileInputStream("NBA")));
  30. String s =null;
  31. while ((s=br.readLine())!=null){
  32. System.out.println(s);
  33. }
  34. } catch (FileNotFoundException e) {
  35. e.printStackTrace();
  36. } catch (IOException e) {
  37. e.printStackTrace();
  38. } finally {
  39. if (br!=null){
  40. try {
  41. br.close();
  42. } catch (IOException e) {
  43. e.printStackTrace();
  44. }
  45. }
  46. }
  47. }
  48. }

BufferedWriter

知识点

  1. 带有缓冲的字符输出流。

    使用步骤

    ```java package IO;

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;

  1. try {
  2. fw = new FileWriter("LJ",true);
  3. bw = new BufferedWriter(fw);
  4. bw.newLine();//写入一个行分隔符
  5. bw.write("勒布朗");
  6. bw.newLine();
  7. bw.write("詹姆斯");
  8. bw.flush();
  9. } catch (IOException e) {
  10. e.printStackTrace();
  11. }finally {
  12. if (bw!=null){
  13. try {
  14. bw.close();
  15. } catch (IOException e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. }
  20. }

}

  1. <a name="eRSFJ"></a>
  2. ## -----------数据流---------------------
  3. <a name="TOQOQ"></a>
  4. ### DataOutputStream
  5. <a name="cBEb7"></a>
  6. #### 知识点
  7. 1. 数据专属的流。
  8. 1. 这个流可以将数据连同数据的类型一并写入文件。
  9. 1. 注意:这个文件不是普通文本文档。(这个文件使用记事本打不开。)
  10. <a name="b88P0"></a>
  11. #### 使用步骤
  12. ```java
  13. package com.bjpowernode.java.io;
  14. import java.io.DataOutputStream;
  15. import java.io.FileOutputStream;
  16. /*
  17. java.io.DataOutputStream:数据专属的流。
  18. 这个流可以将数据连同数据的类型一并写入文件。
  19. 注意:这个文件不是普通文本文档。(这个文件使用记事本打不开。)
  20. */
  21. public class DataOutputStreamTest {
  22. public static void main(String[] args) throws Exception{
  23. // 创建数据专属的字节输出流
  24. DataOutputStream dos = new DataOutputStream(new FileOutputStream("data"));
  25. // 写数据
  26. byte b = 100;
  27. short s = 200;
  28. int i = 300;
  29. long l = 400L;
  30. float f = 3.0F;
  31. double d = 3.14;
  32. boolean sex = false;
  33. char c = 'a';
  34. // 写
  35. dos.writeByte(b); // 把数据以及数据的类型一并写入到文件当中。
  36. dos.writeShort(s);
  37. dos.writeInt(i);
  38. dos.writeLong(l);
  39. dos.writeFloat(f);
  40. dos.writeDouble(d);
  41. dos.writeBoolean(sex);
  42. dos.writeChar(c);
  43. // 刷新
  44. dos.flush();
  45. // 关闭最外层
  46. dos.close();
  47. }
  48. }

DateInputStream

知识点

  1. 数据字节输入流。
  2. DataOutputStream写的文件,只能使用DataInputStream去读。并且读的时候你需要提前知道写入的顺序。
  3. 读的顺序需要和写的顺序一致。才可以正常取出数据。

    使用步骤

    ```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();

  1. System.out.println(b);
  2. System.out.println(s);
  3. System.out.println(i + 1000);
  4. System.out.println(l);
  5. System.out.println(f);
  6. System.out.println(d);
  7. System.out.println(sex);
  8. System.out.println(c);
  9. dis.close();
  10. }

}

  1. <a name="p1NDN"></a>
  2. ## -----------标准输出流-----------------
  3. <a name="n2q54"></a>
  4. ### PrintStream
  5. <a name="rGdCw"></a>
  6. #### 知识点
  7. 1. 标准的字节输出流。默认输出到控制台。
  8. 1. 标准输出流不需要手动close()关闭。
  9. 1. 回顾之前System类使用过的方法和属性
  10. System.gc(); 建议启动垃圾回收器<br />System.currentTimeMillis(); 获取自1970年1月1日到系统当前时间的总毫秒数。<br /> PrintStream ps2 = System.out; <br /> System.exit(0); 退出JVM。<br /> System.arraycopy(....); 数组拷贝
  11. <a name="K0zSz"></a>
  12. #### 使用步骤
  13. ```java
  14. package IO;
  15. import java.io.FileNotFoundException;
  16. import java.io.FileOutputStream;
  17. import java.io.PrintStream;
  18. public class PrintStreamTest01 {
  19. public static void main(String[] args) {
  20. //联合起来写
  21. System.out.println("LJ");
  22. //分开写
  23. PrintStream ps = System.out; //返回一个PrintStream类型
  24. ps.println("KB");
  25. ps.println("AD");
  26. // 可以改变标准输出流的输出方向吗? 可以
  27. try {
  28. // 标准输出流不再指向控制台,指向“log”文件。
  29. PrintStream ps2 = new PrintStream(new FileOutputStream("log"));
  30. // 修改输出方向,将输出方向修改到"log"文件。
  31. System.setOut(ps2);
  32. System.out.println("LJ");
  33. System.out.println("KB");
  34. ps2.flush();
  35. } catch (FileNotFoundException e) {
  36. e.printStackTrace();
  37. }
  38. }
  39. }

应用:日志工具

  1. package com.bjpowernode.java.io;
  2. import java.io.FileNotFoundException;
  3. import java.io.FileOutputStream;
  4. import java.io.PrintStream;
  5. import java.text.SimpleDateFormat;
  6. import java.util.Date;
  7. /*
  8. 日志工具
  9. */
  10. public class Logger {
  11. /*
  12. 记录日志的方法。
  13. */
  14. public static void log(String msg) {
  15. try {
  16. // 指向一个日志文件
  17. PrintStream out = new PrintStream(new FileOutputStream("log.txt", true));
  18. // 改变输出方向
  19. System.setOut(out);
  20. // 日期当前时间
  21. Date nowTime = new Date();
  22. SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
  23. String strTime = sdf.format(nowTime);
  24. System.out.println(strTime + ": " + msg);
  25. } catch (FileNotFoundException e) {
  26. e.printStackTrace();
  27. }
  28. }
  29. }
  30. package com.bjpowernode.java.io;
  31. public class LogTest {
  32. public static void main(String[] args) {
  33. //测试工具类是否好用
  34. Logger.log("调用了System类的gc()方法,建议启动垃圾回收");
  35. Logger.log("调用了UserService的doSome()方法");
  36. Logger.log("用户尝试进行登录,验证失败");
  37. Logger.log("我非常喜欢这个记录日志的工具哦!");
  38. }
  39. }

—————-File类——————————-

知识点

  1. File类和四大家族没有关系,所以FIle类不能完成文件的读和写。
  2. File对象代表什么?
    1. 文件和目录路径名的抽象表示形式。
    2. C:\Drivers 这是一个File对象
    3. C:\Drivers\Lanealtekeadme.txt 也是File对象。
    4. 一个File对象有可能对应的是目录,也可能是文件。
    5. File只是一个路径名的抽象表示形式。
  3. 需要掌握File类中常用的方法

    常用方法

  4. f1.exists():判断f1文件是否存在

  5. createNewFile(): 以文件形式新建
  6. mkdir():以目录形式创建
  7. mkdirs():以多层目录形式创建
  8. getParent():获取文件的上级目录,父目录。
  9. 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());

  1. 🍎// 如果 D:\笔记\mu 不存在,则以文件的形式创建出来
  2. /* if (!file.exists()){
  3. try {
  4. // 以文件形式新建
  5. file.createNewFile();
  6. } catch (IOException e) {
  7. e.printStackTrace();
  8. }
  9. }*/
  10. 🍎// 如果 D:\笔记\mu 不存在,则以目录的形式创建出来
  11. if (!file.exists()){
  12. // 以目录的形式新建。
  13. file.mkdir();
  14. }
  15. 🍎//创建多层目录
  16. File f1 = new File("D:/N/B/A");
  17. if (!f1.exists()){
  18. //以多层目录的形式新建。
  19. f1.mkdirs();
  20. }
  21. File f2 = new File("D:\\笔记\\yuque-desktop\\locales");
  22. 🍎//获取文件的父路径
  23. String parentPath =f2.getParent();
  24. System.out.println(parentPath); // D:\笔记\yuque-desktop
  25. 🍎//返回一个File类型
  26. File parentFile=f2.getParentFile();
  27. System.out.println(parentFile.getAbsolutePath());
  28. File f3 = new File("NBA");
  29. 🍎//获取绝对路径
  30. System.out.println("绝对路径:"+f3.getAbsolutePath());
  31. }

}

  1. 7. **getName():**获取文件名。
  2. 7. **isDirectory():**判断是否是一个目录。
  3. 7. **isFile():**// 判断是否是一个文件
  4. 7. **lastModified():**获取文件最后一次修改时间:这个毫秒是从1970年到文件最后一次修改时间 的总毫秒数。
  5. 1. System._currentTimeMillis_():获取从1970年到现在的总毫秒数。
  6. 11. **length() :**获取文件大小:单位是字节。
  7. ```java
  8. package IO;
  9. import java.io.File;
  10. import java.text.SimpleDateFormat;
  11. import java.util.Date;
  12. public class FileTest02 {
  13. public static void main(String[] args) {
  14. File f1 = new File("D:\\笔记\\mu\\xuyoubo.txt");
  15. //获取文件名
  16. System.out.println("文件名:"+f1.getName());
  17. // 判断是否是一个目录
  18. System.out.println(f1.isDirectory()); //false
  19. // 判断是否是一个文件
  20. System.out.println(f1.isFile()); //true
  21. // 获取文件最后一次修改时间
  22. long haoMiao=f1.lastModified();// 这个毫秒是从1970年到文件最后一次修改时间 的总毫秒数。
  23. System.out.println(haoMiao);
  24. //将毫秒数转换成日期
  25. Date time = new Date(haoMiao);
  26. // Date time2 = new Date(1000);
  27. SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  28. String strTime = sdf.format(time);
  29. // String t2 = sdf.format(time2);
  30. System.out.println(strTime); //2021-05-07 14:33:47
  31. // System.out.println(t2);
  32. // 获取文件大小
  33. System.out.println(f1.length()); //单位是字节 36字节
  34. }
  35. }
  1. 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()); } } }

  1. <a name="qv4ND"></a>
  2. ### getPath()与getAbsolutePath()的区别
  3. **getPath与getAbsolutePath()的区别:**<br />相对路径时:<br /> getPath输出相对路径<br /> getAbsolutePath输出绝对路径
  4. 绝对路径时: 都输出绝对路径<br />
  5. <a name="JBtEx"></a>
  6. ## -----------目录拷贝--------------------
  7. <a name="QXcdW"></a>
  8. ### 代码
  9. [目录拷贝代码](https://blog.csdn.net/weixin_44989186/article/details/116501727)
  10. <a name="GlCuh"></a>
  11. ## -----------对象专属流----------------
  12. <a name="rs2ju"></a>
  13. ### 原理
  14. ![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)
  15. <a name="E60Dg"></a>
  16. ### 知识点
  17. 1. java.io.NotSerializableException:Student对象不支持序列化!!!! 需要 implements接口.
  18. 2. 参与序列化和反序列化的对象,必须实现Serializable接口。
  19. 3. 注意:通过源代码发现,**Serializable接口只是一个标志接口:**
  20. public interface Serializable { <br /> }<br />这个接口当中什么代码都没有。<br />那么它起到一个什么作用呢?<br />起到标识的作用,标志的作用,java虚拟机看到这个类实现了这个接口,可能会对这个类进行特殊待遇。 <br />Serializable这个标志接口是给java虚拟机参考的,java虚拟机看到这个接口之后,<br />会为该类自动生成 一个序列化版本号。
  21. 4. **序列化版本号有什么用?**
  22. java.io.InvalidClassException: <br />com.bjpowernode.java.bean.Student;<br /> local class incompatible:<br /> stream classdesc serialVersionUID = -684255398724514298(十年后),<br /> local class serialVersionUID = -3463447116624555755(十年前)
  23. java语言中是采用什么机制来区分类的?<br />第一:首先通过类名进行比对,如果类名不一样,肯定不是同一个类。<br />第二:如果类名一样,再怎么进行类的区别?靠序列化版本号进行区分。
  24. 小鹏编写了一个类:com.bjpowernode.java.bean.Student implements Serializable<br />胡浪编写了一个类:com.bjpowernode.java.bean.Student implements Serializable<br />**自动生成序列化版本号的好处:**<br />不同的人编写了同一个类,但“这两个类确实不是同一个类”。这个时候序列化版本就起上作用了。<br /> 对于java虚拟机来说,java虚拟机是可以区分开这两个类的,因为这两个类都实现了Serializable接口,<br />都有默认的序列化版本号,他们的序列化版本号不一样。所以区分开了。
  25. **自动生成序列化版本号的缺陷:**<br />这种自动生成的序列化版本号缺点是:一旦代码确定之后,不能进行后续的修改,<br />因为只要修改,必然会重新编译,此时会生成全新的序列化版本号,<br />这个时候java虚拟机会认为这是一个全新的类。(这样就不好了!)
  26. <a name="snFG2"></a>
  27. #### 最终结论
  28. 凡是一个类实现了Serializable接口,建议给该类提供一个固定不变的序列化版本号。<br />这样,以后这个类即使代码修改了,但是版本号不变,java虚拟机会认为是同一个类。<br /> **手写序列号**:private static final long serialVersionUID = 1L; <br /> [手写序列号快捷键](https://www.cnblogs.com/zouhong/p/12975929.html)
  29. <a name="mARxX"></a>
  30. ### 序列化与反序列化一个对象
  31. **Student类**
  32. ```java
  33. package bean;
  34. import java.io.Serializable;
  35. public class Student implements Serializable {
  36. // Java虚拟机看到Serializable接口之后,会自动生成一个序列化版本号。
  37. // 这里没有手动写出来,java虚拟机会默认提供这个序列化版本号。
  38. // 建议将序列化版本号手动的写出来。不建议自动生成
  39. private static final long serialVersionUID = 1L;
  40. private int no;
  41. //transient 表示 游离的 使name不参与序列化与反序列化
  42. private transient String name;
  43. // 过了很久,Student这个类源代码如果发生改动了。
  44. // 源代码改动之后,需要重新编译,编译之后生成了全新的字节码文件。
  45. // 并且class文件再次运行的时候,java虚拟机生成的序列化版本号也会发生相应的改变。
  46. public int getNo() {
  47. return no;
  48. }
  49. public void setNo(int no) {
  50. this.no = no;
  51. }
  52. public String getName() {
  53. return name;
  54. }
  55. public void setName(String name) {
  56. this.name = name;
  57. }
  58. public Student() {
  59. }
  60. public Student(int no, String name) {
  61. this.no = no;
  62. this.name = name;
  63. }
  64. @Override
  65. public String toString() {
  66. return "Student{" +
  67. "no=" + no +
  68. ", name='" + name + '\'' +
  69. '}';
  70. }
  71. }

ObjectOutPutStream类

  1. package IO;
  2. import bean.Student;
  3. import java.io.FileOutputStream;
  4. import java.io.IOException;
  5. import java.io.ObjectOutputStream;
  6. public class ObjectOutPutStreamTest01 {
  7. public static void main(String[] args) throws IOException {
  8. Student s1 = new Student(23,"LJ");
  9. //序列化
  10. ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Students"));
  11. //序列化对象
  12. oos.writeObject(s1);
  13. oos.flush();
  14. oos.close();
  15. }
  16. }

ObjectInputStream类

  1. package IO;
  2. import java.io.FileInputStream;
  3. import java.io.FileNotFoundException;
  4. import java.io.IOException;
  5. import java.io.ObjectInputStream;
  6. public class ObjectInputStreamTest01 {
  7. public static void main(String[] args) throws IOException, ClassNotFoundException {
  8. ObjectInputStream ois = new ObjectInputStream(new FileInputStream("students"));
  9. //开始反序列化,读
  10. Object obj=ois.readObject();
  11. //反序列化回来是一个学生对象,所以会调用学生对象的toString方法。
  12. System.out.println(obj);
  13. ois.close();
  14. }
  15. }

序列化与反序列化多个对象

User类

  1. package bean;
  2. import java.io.Serializable;
  3. public class User implements Serializable {
  4. private String username;
  5. private String password;
  6. public String getUsername() {
  7. return username;
  8. }
  9. public void setUsername(String username) {
  10. this.username = username;
  11. }
  12. public String getPassword() {
  13. return password;
  14. }
  15. public void setPassword(String password) {
  16. this.password = password;
  17. }
  18. public User() {
  19. }
  20. public User(String username, String password) {
  21. this.username = username;
  22. this.password = password;
  23. }
  24. @Override
  25. public String toString() {
  26. return "User{" +
  27. "username='" + username + '\'' +
  28. ", password='" + password + '\'' +
  29. '}';
  30. }
  31. }

ObjectOutputStream类

  1. package IO;
  2. import bean.User;
  3. import java.io.FileNotFoundException;
  4. import java.io.FileOutputStream;
  5. import java.io.IOException;
  6. import java.io.ObjectOutputStream;
  7. import java.util.ArrayList;
  8. import java.util.List;
  9. /*
  10. 一次序列化多个对象呢?
  11. 可以,可以将对象放到集合当中,序列化集合。
  12. 提示:
  13. 参与序列化的ArrayList集合以及集合中的元素User都需要实现 java.io.Serializable接口。
  14. ArrayList已自己实现。
  15. */
  16. public class ObjectOutputStreamTest02 {
  17. public static void main(String[] args) throws IOException {
  18. List<User> list = new ArrayList<>();
  19. list.add(new User("23","LJ"));
  20. list.add(new User("24","KB"));
  21. list.add(new User("3","AD"));
  22. ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("users"));
  23. // 序列化一个集合,这个集合对象中放了很多其他对象。
  24. oos.writeObject(list);
  25. oos.flush();
  26. oos.close();
  27. }
  28. }

ObjectInputStream类

  1. package IO;
  2. import bean.User;
  3. import java.io.*;
  4. import java.util.List;
  5. public class ObjectInputStreamTest02 {
  6. public static void main(String[] args) throws IOException, ClassNotFoundException {
  7. ObjectInputStream ois = new ObjectInputStream(new FileInputStream("users"));
  8. /* Object o=ois.readObject();
  9. System.out.println(o instanceof List);*/
  10. List<User> list = (List<User>) ois.readObject();
  11. for (User u:
  12. list) {
  13. System.out.println(u);
  14. }
  15. }
  16. }

———IO+Properties联合使用—————————

知识点

  1. IO流:文件的读和写。
  2. Properties:是一个Map集合,key和value都是String类型。

    代码

    ```java package bean;

import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.util.Properties;

/* IO+Properties的联合应用。 非常好的一个设计理念: 以后经常改变的数据,可以单独写到一个文件中,使用程序动态读取。 将来只需要修改这个文件的内容,java代码不需要改动,不需要重新 编译,服务器也不需要重启。就可以拿到动态的信息。

  1. 类似于以上机制的这种文件被称为配置文件。
  2. 并且当配置文件中的内容格式是:
  3. key1=value
  4. key2=value
  5. 的时候,我们把这种配置文件叫做属性配置文件。
  6. java规范中有要求:属性配置文件建议以.properties结尾,但这不是必须的。
  7. 这种以.properties结尾的文件在java中被称为:属性配置文件。
  8. 其中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”);

  1. //新建一个Map集合
  2. Properties p1 = new Properties();
  3. // 调用Properties对象的load方法将文件中的数据加载到Map集合中。
  4. p1.load(reader); // 文件中的数据顺着管道加载到Map集合中,其中等号=左边做key,右边做value
  5. //通过key获取value
  6. String username=p1.getProperty("username");
  7. System.out.println(username);
  8. String password = p1.getProperty("password");
  9. System.out.println(password);
  10. String level = p1.getProperty("level");
  11. System.out.println(level);
  12. }

}

——-properties文件——— username=admin

password=123456

key相同会覆盖

password=45678

不建议使用冒号

level:vip

  1. <a name="OQQvH"></a>
  2. # 进阶-多线程
  3. <a name="fioSn"></a>
  4. ## 基本概念
  5. <a name="Bpi3D"></a>
  6. ### 什么是进程?什么是线程?
  7. 1. 进程是一个应用程序(1个进程是一个软件)。
  8. 1. 线程是一个进程中的执行场景/执行单元。
  9. 1. 一个进程可以启动多个线程。
  10. 1. 对于java程序来说,当在DOS命令窗口中输入:
  11. 1. java HelloWorld 回车之后。
  12. 1. 会先启动JVM,而JVM就是一个进程。
  13. 1. JVM再启动一个主线程调用main方法。
  14. 1. 同时再启动一个垃圾回收线程负责看护,回收垃圾。
  15. 1. 最起码,现在的java程序中至少有两个线程并发,
  16. 1. 一个是垃圾回收线程,一个是执行main方法的主线程。
  17. <a name="itygH"></a>
  18. ### 进程与线程的关系
  19. 1. 举个例子:
  20. 阿里巴巴--->进程<br />马云--->阿里巴巴的一个线程<br />蔡崇信--->阿里巴巴的一个线程<br />京东--->进程<br />强东--->京东的一个线程<br />奶茶--->京东的一个线程
  21. **进程**可以看做是现实生活当中的公司。<br />**线程**可以看做是公司当中的某个员工。
  22. 2. 进程A和进程B的内存独立不共享。
  23. 阿里巴巴和京东资源不会共享的!
  24. <a name="QAdky"></a>
  25. ### 多线程并发的理解
  26. 3. 线程A和线程B呢?
  27. 在java语言中:<br />线程A和线程B,堆内存和方法区内存共享。<br />但是栈内存独立,一个线程一个栈。<br />eg:<br />假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,<br />各自执行各自的,这就是**多线程并发**。
  28. eg:<br />火车站,可以看做是一个进程。<br />火车站中的每一个售票窗口可以看做是一个线程。<br />我在窗口1购票,你可以在窗口2购票,你不需要等我,我也不需要等你。<br />所以多线程并发可以提高效率。
  29. 4. java中之所以有多线程机制,目的就是为了提高程序的处理效率。
  30. 5. 使用了多线程机制之后,main方法结束,程序会结束么?
  31. main方法结束只是主线程结束了,主栈空了,其它的栈(线程)可能还在压栈弹栈
  32. 6. 对于单核的CPU来说,真的可以做到真正的多线程并发吗?
  33. 对于**多核**的CPU电脑来说,真正的多线程并发是没问题的。<br />4核CPU表示同一个时间点上,可以真正的有4个进程并发执行。
  34. 什么是真正的多线程并发?<br />t1线程执行t1的,t2线程执行t2的。<br />t1不会影响t2,t2也不会影响t1。这叫做真正的多线程并发。
  35. 单核的CPU表示只有一个大脑:<br />不能够做到真正的多线程并发,但是可以做到给人一种**“多线程并发”的感觉。**<br />对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,<br />但是由于CPU的处理速度极快,多个线程之间频繁切换执行,<br />给人的感觉是:多个事情同时在做!<br />eg:<br />线程A:播放音乐<br />线程B:运行魔兽游戏<br />线程A和线程B频繁切换执行,人类会感觉音乐一直在播放,游戏一直在运行。<br />给我们的感觉是同时并发的。
  36. 7. 分析以下程序有几个线程?
  37. ![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 />答:一个!只要一个主线程 主栈。没有分支线程!
  38. <a name="SUyHY"></a>
  39. ### 图解
  40. **堆和方法区共享,栈独立。**<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)
  41. <a name="g2fyH"></a>
  42. ## 实现线程的方式
  43. java支持多线程机制。并且java已经将多线程实现了,我们只需要继承就行了。
  44. <a name="gWxDt"></a>
  45. ### 第一种方式
  46. 编写一个类,直接继承java.lang.Thread,重写run方法。
  47. **start()方法**:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。 <br />这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了。<br />线程就启动成功了。<br /> 启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)。
  48. <a name="VCd44"></a>
  49. #### 代码
  50. ```java
  51. package Thread;
  52. /**
  53. * 实现线程的第一种方式:
  54. * 编写一个类,直接继承java.lang.Thread,重写run方法。
  55. * 怎么创建线程对象?
  56. * new就行了。
  57. * 怎么启动线程呢?
  58. * 调用线程对象的start()方法。
  59. * 注意:
  60. * 方法体当中的代码永远都是自上而下的顺序依次逐行执行的。
  61. *
  62. */
  63. public class ThreadTest01 {
  64. public static void main(String[] args) {
  65. // 这里是main方法,这里的代码属于主线程,在主栈中运行。
  66. // 新建一个分支线程对象
  67. MyThread m = new MyThread();
  68. //t.run(); 假的这种方法不会启动线程,不会分配新的分支栈。(用这种方法还是单线程)
  69. /*
  70. start()方法:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。
  71. 这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了。线程就启动成功了。
  72. 启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)。
  73. run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的。
  74. */
  75. //启动线程
  76. m.start();
  77. // 这里的代码还是运行在主线程中。
  78. for (int i = 0; i <1000 ; i++) {
  79. System.out.println("主线程--->"+i);
  80. }
  81. }
  82. }
  83. class MyThread extends Thread{
  84. @Override
  85. public void run() {
  86. // 编写程序,这段程序运行在分支线程中(分支栈)。
  87. for (int i = 0; i <1000 ; i++) {
  88. System.out.println("分支线程--->"+i);
  89. }
  90. }
  91. }

图解

run()image.png

start()image.png

第二种方式

代码

编写一个类,实现java.lang.Runnable接口,实现run方法。

  1. package Thread;
  2. public class ThreadTest02 {
  3. public static void main(String[] args) {
  4. // 创建一个可运行的对象
  5. MyRunnable r = new MyRunnable();
  6. // 将可运行的对象封装成一个线程对象
  7. Thread t = new Thread(r);
  8. //合并以上代码
  9. Thread t = new Thread(new MyRunnable());
  10. //启动线程
  11. t.start();
  12. for (int i = 0; i <1000 ; i++) {
  13. System.out.println("主线程--->"+i);
  14. }
  15. }
  16. }
  17. class MyRunnable implements Runnable{
  18. @Override
  19. public void run() {
  20. for (int i = 0; i <1000 ; i++) {
  21. System.out.println("分支线程--->"+i);
  22. }
  23. }
  24. }

匿名内部类实现第二种方式

代码

  1. package com.bjpowernode.java.thread;
  2. /*
  3. 采用匿名内部类可以吗?
  4. */
  5. public class ThreadTest04 {
  6. public static void main(String[] args) {
  7. // 创建线程对象,采用匿名内部类方式。
  8. // 这是通过一个没有名字的类,new出来的对象。
  9. Thread t = new Thread(new Runnable(){
  10. @Override
  11. public void run() {
  12. for(int i = 0; i < 100; i++){
  13. System.out.println("t线程---> " + i);
  14. }
  15. }
  16. });
  17. // 启动线程
  18. t.start();
  19. for(int i = 0; i < 100; i++){
  20. System.out.println("main线程---> " + i);
  21. }
  22. }
  23. }

注意

第二种方式实现接口比较常用,因为一个类实现了接口,它还可以去继承其它的类,更灵活。

第三种方式(JDK8新特性)

这种方式实现的线程可以获取线程的返回值。
之前讲解的那两种方式是无法获取线程返回值的,因为run方法返回void。

思考:
系统委派一个线程去执行一个任务,该线程执行完任务之后,
可能会有一个执行结果,我们怎么能拿到这个执行结果呢?
使用第三种方式:实现Callable接口方式。

代码

  1. package com.bjpowernode.java.thread;
  2. import java.util.concurrent.Callable;
  3. import java.util.concurrent.FutureTask; // JUC包下的,属于java的并发包,老JDK中没有这个包。新特性。
  4. /*
  5. 实现线程的第三种方式:
  6. 实现Callable接口
  7. 这种方式的优点:可以获取到线程的执行结果。
  8. 这种方式的缺点:效率比较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低。
  9. */
  10. public class ThreadTest15 {
  11. public static void main(String[] args) throws Exception {
  12. // 第一步:创建一个“未来任务类”对象。
  13. // 参数非常重要,需要给一个Callable接口实现类对象。
  14. FutureTask task = new FutureTask(new Callable() {
  15. @Override
  16. public Object call() throws Exception { // call()方法就相当于run方法。只不过这个有返回值
  17. // 线程执行一个任务,执行之后可能会有一个执行结果
  18. // 模拟执行
  19. System.out.println("call method begin");
  20. Thread.sleep(1000 * 10);
  21. System.out.println("call method end!");
  22. int a = 100;
  23. int b = 200;
  24. return a + b; //自动装箱(300结果变成Integer)
  25. }
  26. });
  27. // 创建线程对象
  28. Thread t = new Thread(task);
  29. // 启动线程
  30. t.start();
  31. // 这里是main方法,这是在主线程中。
  32. // 在主线程中,怎么获取t线程的返回结果?
  33. // get()方法的执行会导致“当前线程阻塞”
  34. Object obj = task.get();
  35. System.out.println("线程执行结果:" + obj);
  36. // main方法这里的程序要想执行必须等待get()方法的结束
  37. // 而get()方法可能需要很久。因为get()方法是为了拿另一个线程的执行结果
  38. // 另一个线程执行是需要时间的。
  39. System.out.println("hello world!");
  40. }
  41. }

线程的生命周期

image.png

  1. 新建状态:采用 new语句创建完成
  2. 就绪状态:执行 start 后
  3. 运行状态:占用 CPU 时间,执行run方法。
  4. 阻塞状态:执行了 wait 语句、执行了 sleep 语句和等待某个对象锁, 等待输入的场合。
  5. 死亡状态:run()方法结束。

    线程的常用方法

    知识点

  6. 获取当前线程对象: Thread t = Thread.currentThread(); 静态方法

    返回值t就是当前线程。

  7. 获取线程对象的名字: String name = 线程对象.getName();

  8. 修改线程对象的名字:线程对象.setName(“线程名字”);

  9. 当线程没有设置名字的时候,默认的名字有什么规律?(了解一下)

Thread-0
Thread-1
Thread-2
Thread-3
…..

代码

  1. public class ThreadTest05 {
  2. public void doSome(){
  3. // 这样就不行了
  4. //this.getName();
  5. //super.getName();
  6. // 但是这样可以
  7. String name = Thread.currentThread().getName();
  8. System.out.println("------->" + name);
  9. }
  10. public static void main(String[] args) {
  11. ThreadTest05 tt = new ThreadTest05();
  12. tt.doSome();
  13. //currentThread就是当前线程对象
  14. // 这个代码出现在main方法当中,所以当前线程就是主线程。
  15. Thread currentThread = Thread.currentThread();
  16. System.out.println(currentThread.getName()); //main
  17. // 创建线程对象
  18. MyThread2 t = new MyThread2();
  19. // 设置线程的名字
  20. t.setName("t1");
  21. // 获取线程的名字
  22. String tName = t.getName();
  23. System.out.println(tName); //Thread-0
  24. MyThread2 t2 = new MyThread2();
  25. t2.setName("t2");
  26. System.out.println(t2.getName()); //Thread-1\
  27. t2.start();
  28. // 启动线程
  29. t.start();
  30. }
  31. }
  32. class MyThread2 extends Thread {
  33. public void run(){
  34. for(int i = 0; i < 100; i++){
  35. // currentThread就是当前线程对象。当前线程是谁呢?
  36. // 当t1线程执行run方法,那么这个当前线程就是t1
  37. // 当t2线程执行run方法,那么这个当前线程就是t2
  38. Thread currentThread = Thread.currentThread();
  39. System.out.println(currentThread.getName() + "-->" + i);
  40. //System.out.println(super.getName() + "-->" + i);
  41. //System.out.println(this.getName() + "-->" + i);
  42. }
  43. }
  44. }

sleep()方法

知识点

static void sleep(long millis)

  1. 静态方法:Thread.sleep(1000);

  2. 参数是毫秒

  3. 作用:让当前线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片,让给其它线程使用。

    这行代码出现在A线程中,A线程就会进入休眠。
    这行代码出现在B线程中,B线程就会进入休眠。

  4. Thread.sleep()方法,可以做到这种效果:

间隔特定的时间,去执行一段特定的代码,每隔多久执行一次。

代码

  1. public class ThreadTest06 {
  2. public static void main(String[] args) {
  3. // 让当前线程进入休眠,睡眠5秒
  4. // 当前线程是主线程!!!
  5. /*try {
  6. Thread.sleep(1000 * 5);
  7. } catch (InterruptedException e) {
  8. e.printStackTrace();
  9. }*/
  10. // 5秒之后执行这里的代码
  11. //System.out.println("hello world!");
  12. for(int i = 0; i < 10; i++){
  13. System.out.println(Thread.currentThread().getName() + "--->" + i);
  14. // 睡眠1秒
  15. try {
  16. Thread.sleep(1000);
  17. } catch (InterruptedException e) {
  18. e.printStackTrace();
  19. }
  20. }
  21. }
  22. }

面试题

  1. package com.bjpowernode.java.thread;
  2. /*
  3. 关于Thread.sleep()方法的一个面试题:
  4. */
  5. public class ThreadTest07 {
  6. public static void main(String[] args) {
  7. // 创建线程对象
  8. Thread t = new MyThread3();
  9. t.setName("t");
  10. t.start();
  11. // 调用sleep方法
  12. try {
  13. // 问题:这行代码会让线程t进入休眠状态吗?
  14. t.sleep(1000 * 5); // 因为sleep()是一个静态方法,是类名. 调用的, 与对象无关。
  15. 在执行的时候还是会转换成:Thread.sleep(1000 * 5);
  16. 这行代码的作用是:让当前线程进入休眠,也就是说main线程进入休眠。
  17. 这样代码出现在main方法中,main线程睡眠。
  18. } catch (InterruptedException e) {
  19. e.printStackTrace();
  20. }
  21. // 5秒之后这里才会执行。
  22. System.out.println("hello World!");
  23. }
  24. }
  25. class MyThread3 extends Thread {
  26. public void run(){
  27. for(int i = 0; i < 10000; i++){
  28. System.out.println(Thread.currentThread().getName() + "--->" + i);
  29. }
  30. }
  31. }

终止sleep(),唤醒线程

sleep睡眠太久了,如果希望半道上醒来,你应该怎么办?也就是说怎么叫醒一个正在睡眠的线程??
注意:这个不是终断线程的执行,是终止线程的睡眠。

  1. public class ThreadTest08 {
  2. public static void main(String[] args) {
  3. Thread t = new Thread(new MyRunnable2());
  4. t.setName("t");
  5. t.start();
  6. // 希望5秒之后,t线程醒来(5秒之后主线程手里的活儿干完了。)
  7. try {
  8. Thread.sleep(1000 * 5);
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. // 终断t线程的睡眠(这种终断睡眠的方式依靠了java的异常处理机制。)
  13. t.interrupt(); // 干扰,一盆冷水过去!
  14. }
  15. }
  16. class MyRunnable2 implements Runnable {
  17. // 重点:run()当中的异常不能throws,只能try catch
  18. // 因为run()方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常。
  19. @Override
  20. public void run() {
  21. System.out.println(Thread.currentThread().getName() + "---> begin");
  22. try {
  23. // 睡眠1年
  24. Thread.sleep(1000 * 60 * 60 * 24 * 365);
  25. } catch (InterruptedException e) {
  26. // 打印异常信息
  27. e.printStackTrace();
  28. }
  29. //1年之后才会执行这里
  30. System.out.println(Thread.currentThread().getName() + "---> end");
  31. }
  32. }

终止线程的执行

  1. stop()方法: 已弃用,容易丢失数据。 了解即可。

  2. 合理的终止一个线程的执行:打标记 ```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();

  1. // 模拟6秒
  2. try {
  3. Thread.sleep(1000*6);
  4. } catch (InterruptedException e) {
  5. e.printStackTrace();
  6. }
  7. //终止线程,你想什么时候终止,将标记改为false就终止了。
  8. //6秒后终止
  9. r.flag=false;
  10. }

}

class MyRunnable5 implements Runnable{ //打一个标记⭐ boolean flag = true; @Override public void run() {

  1. for (int i = 0; i <10 ; i++) {
  2. if (flag){
  3. System.out.println(Thread.currentThread().getName()+"-->"+i);
  4. try {
  5. Thread.sleep(1000);
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. }else {
  10. // return就结束了,你在结束之前还有什么没保存的。
  11. // 在这里可以保存呀。
  12. //save....
  13. //终止当前线程
  14. return;
  15. }
  16. }
  17. }

}

  1. <a name="yCFpW"></a>
  2. ## 线程的调度 (了解即可)
  3. <a name="GT3OS"></a>
  4. ### 常见的线程调度模
  5. 1. 抢占式调度模型:
  6. 那个线程的优先级比较高,抢到的CPU时间片的概率就高一些/多一些。<br />java采用的就是抢占式调度模型。
  7. 2. 均分式调度模型:
  8. 平均分配CPU时间片。每个线程占有的CPU时间片时间长度一样。<br />平均分配,一切平等。<br />有一些编程语言,线程调度模型采用的是这种方式。
  9. <a name="I8dr5"></a>
  10. ### 线程调度的方法
  11. 实例方法:<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() 合并线程
  12. ```java
  13. class MyThread1 extends Thread {
  14. public void doSome(){
  15. MyThread2 t = new MyThread2();
  16. t.join();// 当前线程进入阻塞,t线程执行,直到t线程结束。当前线程才可以继续。
  17. }
  18. }
  19. class MyThread2 extends Thread{}

线程安全⭐

为什么是重点

在以后的开发中,我们的项目都是运行在服务器当中,
而服务器已经将线程的定义,线程对象的创建,线程的启动等,全都实现了。
这些代码我们都不需要编写。
最重要的是:
你要知道,你编写的程序需要放到一个多线程的环境下运行,
你更需要关注的是这些数据在多线程并发的环境下是否安全!(⭐⭐⭐)

线程不安全的条件

举例:多线程并发对同一个账户进行取款

image.png

数据在多线程并发的环境下存在安全问题的三个条件

  1. 多线程并发
  2. 有共享数据
  3. 共享数据有修改的行为

满足以上3个条件,就会存在线程安全问题。

如何解决线程安全问题

线程同步机制

线程排队执行(不能并发),用排队执行解决线程安全问题。这种机制被称为—线程同步机制

线程同步就是线程排队,线程排队了就会牺牲一部分效率
数据安全是第一位的,只有数据安全了,才可以谈效率,数据不安全,没有效率的事。

语法: synchronized(){
// 线程同步代码块。
}

同步与异步

异步编程模型(并发):
线程t1和t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁。
这种编程模型叫做—异步编程模型。
本质:多线程并发(效率较高)

同步编程模型(排队):
线程t1和t2,在一个线程执行时,另一个线程必须等其执行结束,才可以执行。
两个线程之间发生了等待关系,这种编程模型叫做—同步编程模型。
本质:线程排队执行(效率较低)

⭐JAVA中的三大变量哪些有线程安全问题?

实例变量:在堆中。
静态变量:在方法区。
局部变量:在栈中。

以上三大变量中:
局部变量永远都不会存在线程安全问题。
因为局部变量不共享。(一个线程一个栈。)
局部变量在栈中。所以局部变量永远都不会共享。

实例变量在堆中,堆只有1个。
静态变量在方法区中,方法区只有1个。
堆和方法区都是多线程共享的,所以可能存在线程安全问题。

总结:

  1. 1. 局部变量+常量: 不会有线程安全问题
  2. 1. 成员变量(实例变量+静态变量):可能会有线程安全问题。

synchronized

使用同步和异步分别实现多线程对同一个账户进行取款

Accout类

  1. package threadsafe;
  2. /**
  3. * 编写程序模拟两个线程同时对一个账户进行取款操作
  4. */
  5. public class Account {
  6. //账号
  7. private String actno;
  8. //余额
  9. private double balance;
  10. public Account() {
  11. }
  12. public Account(String actno, double balance) {
  13. this.actno = actno;
  14. this.balance = balance;
  15. }
  16. public String getActno() {
  17. return actno;
  18. }
  19. public void setActno(String actno) {
  20. this.actno = actno;
  21. }
  22. public double getBalance() {
  23. return balance;
  24. }
  25. public void setBalance(double balance) {
  26. this.balance = balance;
  27. }
  28. /**
  29. * 取钱方法:异步编程模型,并发执行!
  30. * @param money 取钱数
  31. */
  32. public void draw(double money){
  33. // t1和t2并发这个方法。。。。(t1和t2是两个栈。两个栈操作堆中同一个对象。)
  34. //取款之前的余额
  35. double before = getBalance(); //10000
  36. //取款后余额
  37. double after = getBalance()-money;
  38. // 在这里模拟一下网络延迟,100%会出现问题
  39. try {
  40. Thread.sleep(1000);
  41. } catch (InterruptedException e) {
  42. e.printStackTrace();
  43. }
  44. //更新余额
  45. //思考:t1执行到这里,但还没来得及执行这行代码,t2就进来draw方法了,此时一定会出问题!
  46. this.setBalance(after);
  47. }
  48. ⭐⭐ /**
  49. * 取钱方法:使用线程同步机制!排队执行!
  50. * @param money
  51. * 以下这几行代码必须是线程排队的,不能并发。
  52. * // 一个线程把这里的代码全部执行结束之后,另一个线程才能进来。
  53. * /*
  54. * 线程同步机制的语法是:
  55. * synchronized(){
  56. * // 线程同步代码块。
  57. * }
  58. * synchronized后面小括号中传的这个“数据”是相当关键的。
  59. * 这个数据必须是多线程共享的数据。才能达到多线程排队。
  60. *
  61. * ()中写什么?
  62. * 那要看你想让哪些线程同步。
  63. * 假设t1、t2、t3、t4、t5,有5个线程,
  64. * 你只希望t1 t2 t3排队,t4 t5不需要排队。怎么办?
  65. * 你一定要在()中写一个t1 t2 t3共享的对象。而这个
  66. * 对象对于t4 t5来说不是共享的。
  67. * 此时 t1 t2 t3 就是同步(排队)的
  68. * t4 t5 是异步(并发)的。
  69. *
  70. * 这里的共享对象是:账户对象。
  71. * 账户对象是共享的,那么this(因为是账户调用的draw())就是账户对象吧!!!
  72. * 不一定是this,这里只要是多线程共享的那个对象就行。
  73. *
  74. * 在java语言中,任何一个对象都有“一把锁”,其实这把锁就是标记。(只是把它叫做锁。)
  75. * 100个对象,100把锁。1个对象1把锁。
  76. *
  77. * 以下代码的执行原理?
  78. * 1、假设t1和t2线程并发,开始执行以下代码的时候,肯定有一个先一个后。
  79. * 2、假设t1先执行了,遇到了synchronized,这个时候自动找“后面共享对象”的对象锁,
  80. * 找到之后,并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直都是占有这把锁的。
  81. * 直到同步代码块代码结束,这把锁才会释放。
  82. * 3、假设t1已经占有这把锁,此时t2也遇到synchronized关键字,也会去占有后面
  83. * 共享对象的这把锁,结果这把锁被t1占有,t2只能在同步代码块外面等待t1的结束,
  84. * 直到t1把同步代码块执行结束了,t1会归还这把锁,此时t2终于等到这把锁,
  85. * 然后t2占有这把锁之后,进入同步代码块执行程序。
  86. *
  87. * 这样就达到了线程排队执行!
  88. * 这里需要注意的是:这个共享对象一定要选好了。
  89. * 这个共享对象一定是你需要排队执行的这些线程对象所共享的。
  90. *
  91. */
  92. public void draw2(double money){
  93. //Object obj2 = new Object();
  94. //synchronized (obj2) 不行,这是局部变量,不是共享对象
  95. //synchronized ("abc") 可以,因为"abc"在字符串常量池当中。但是它是所以线程的共享对象。
  96. //synchronized (null) 不行,空指针异常
  97. synchronized (this){
  98. //取款之前的余额
  99. double before = getBalance(); //10000
  100. //取款后余额
  101. double after = getBalance()-money;
  102. // 在这里模拟一下网络延迟。
  103. try {
  104. Thread.sleep(1000);
  105. } catch (InterruptedException e) {
  106. e.printStackTrace();
  107. }
  108. //更新余额
  109. this.setBalance(after);
  110. }
  111. }
  112. }

AccountThread类

  1. package threadsafe;
  2. public class AccountThread extends Thread {
  3. //两个线程必须共享一个账户
  4. private Account act;
  5. //通过构造方法传递过来账户对象
  6. public AccountThread(Account act){
  7. this.act = act;
  8. }
  9. @Override
  10. public void run() {
  11. //run()方法表示取款操作
  12. //假设取款5000
  13. double money = 5000;
  14. //取款
  15. //多线程并发执行这个方法
  16. //act.draw(money);
  17. //线程同步排队执行此方法
  18. act.draw2(money);
  19. System.out.println(Thread.currentThread().getName()
  20. +"对"+act.getActno()+"取款"+money
  21. +"元成功,余额:" +act.getBalance()
  22. );
  23. }
  24. }

Test类

  1. package threadsafe;
  2. public class Test {
  3. public static void main(String[] args) {
  4. //创建一个账户
  5. Account act = new Account("act-001",10000);
  6. //创建两个线程
  7. Thread t1 = new AccountThread(act);
  8. Thread t2 = new AccountThread(act);
  9. //设置name
  10. t1.setName("t1");
  11. t2.setName("t2");
  12. //启动线程,进行取款
  13. t1.start();
  14. t2.start();
  15. }
  16. }

()里放什么?

P626讲解!
总而言之:一句话,放的是你想要排队的线程的共享对象!
()放的范围越小,效率越高。

synchronized的三种写法

  1. 同步代码块:

比较灵活
synchronized(线程共享对象){
同步代码块;
}

  1. 在实例方法上使用synchronized

表示共享对象一定是this,
并且同步代码块是整个方法体。

public synchronized void draw(double money){

  1. 在静态方法上使用synchronized

表示找类锁。
类锁永远只有一把,
就算创建了100个对象,那类锁也只要一把。

对象锁:1个对象1把锁,100个对象100把锁。
类锁:100个对象,也可能只是1把类锁。

synchronized相关面试题

  1. 面试题: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();

  1. Thread t1 = new MyThread(mc);
  2. Thread t2 = new MyThread(mc);
  3. t1.setName("t1");
  4. t2.setName("t2");
  5. t1.start();
  6. Thread.sleep(1000); //这个睡眠的作用是:为了保证t1线程先执行。
  7. t2.start();
  8. }

}

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”); } }

  1. 答:不需要,因为doOther()方法没有synchronized
  2. 2. 面试题:doOther方法执行的时候需要等待doSome方法的结束吗?
  3. ```java
  4. package com.bjpowernode.java.exam2;
  5. // 面试题:doOther方法执行的时候需要等待doSome方法的结束吗?
  6. //需要
  7. public class Exam01 {
  8. public static void main(String[] args) throws InterruptedException {
  9. MyClass mc = new MyClass();
  10. Thread t1 = new MyThread(mc);
  11. Thread t2 = new MyThread(mc);
  12. t1.setName("t1");
  13. t2.setName("t2");
  14. t1.start();
  15. Thread.sleep(1000); //这个睡眠的作用是:为了保证t1线程先执行。
  16. t2.start();
  17. }
  18. }
  19. class MyThread extends Thread {
  20. private MyClass mc;
  21. public MyThread(MyClass mc){
  22. this.mc = mc;
  23. }
  24. public void run(){
  25. if(Thread.currentThread().getName().equals("t1")){
  26. mc.doSome();
  27. }
  28. if(Thread.currentThread().getName().equals("t2")){
  29. mc.doOther();
  30. }
  31. }
  32. }
  33. class MyClass {
  34. public synchronized void doSome(){
  35. System.out.println("doSome begin");
  36. try {
  37. Thread.sleep(1000 * 10);
  38. } catch (InterruptedException e) {
  39. e.printStackTrace();
  40. }
  41. System.out.println("doSome over");
  42. }
  43. public synchronized void doOther(){ //加上了 synchronized
  44. System.out.println("doOther begin");
  45. System.out.println("doOther over");
  46. }
  47. }

答:需要!因为doOther()方法有synchronized。

  1. 面试题: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();

  1. Thread t1 = new MyThread(mc1);
  2. Thread t2 = new MyThread(mc2);
  3. t1.setName("t1");
  4. t2.setName("t2");
  5. t1.start();
  6. Thread.sleep(1000); //这个睡眠的作用是:为了保证t1线程先执行。
  7. t2.start();
  8. }

}

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”); } }

  1. 答:不需要,因为MyClass对象是两个,两把锁。
  2. 4. 面试题:doOther方法执行的时候需要等待doSome方法的结束吗? ⭐⭐⭐
  3. ```java
  4. package com.bjpowernode.java.exam4;
  5. // 面试题:doOther方法执行的时候需要等待doSome方法的结束吗?
  6. //需要,因为静态方法是类锁,不管创建了几个对象,类锁只有1把。
  7. public class Exam01 {
  8. public static void main(String[] args) throws InterruptedException {
  9. MyClass mc1 = new MyClass();
  10. MyClass mc2 = new MyClass();
  11. Thread t1 = new MyThread(mc1);
  12. Thread t2 = new MyThread(mc2);
  13. t1.setName("t1");
  14. t2.setName("t2");
  15. t1.start();
  16. Thread.sleep(1000); //这个睡眠的作用是:为了保证t1线程先执行。
  17. t2.start();
  18. }
  19. }
  20. class MyThread extends Thread {
  21. private MyClass mc;
  22. public MyThread(MyClass mc){
  23. this.mc = mc;
  24. }
  25. public void run(){
  26. if(Thread.currentThread().getName().equals("t1")){
  27. mc.doSome();
  28. }
  29. if(Thread.currentThread().getName().equals("t2")){
  30. mc.doOther();
  31. }
  32. }
  33. }
  34. class MyClass {
  35. // synchronized出现在静态方法上是找类锁。
  36. public synchronized static void doSome(){
  37. System.out.println("doSome begin");
  38. try {
  39. Thread.sleep(1000 * 10);
  40. } catch (InterruptedException e) {
  41. e.printStackTrace();
  42. }
  43. System.out.println("doSome over");
  44. }
  45. public synchronized static void doOther(){
  46. System.out.println("doOther begin");
  47. System.out.println("doOther over");
  48. }
  49. }

答:需要,因为静态方法是类锁,不管创建了几个对象,类锁只有1把。

死锁

死锁代码要会写。
一般面试官要求你会写。
只有会写的,才会在以后的开发中注意这个事儿。
因为死锁很难调试。

  1. package com.bjpowernode.java.deadlock;
  2. /*
  3. 死锁代码要会写。
  4. 一般面试官要求你会写。
  5. 只有会写的,才会在以后的开发中注意这个事儿。
  6. 因为死锁很难调试。
  7. */
  8. public class DeadLock {
  9. public static void main(String[] args) {
  10. Object o1 = new Object();
  11. Object o2 = new Object();
  12. // t1和t2两个线程共享o1,o2
  13. Thread t1 = new MyThread1(o1,o2);
  14. Thread t2 = new MyThread2(o1,o2);
  15. t1.start();
  16. t2.start();
  17. }
  18. }
  19. class MyThread1 extends Thread{
  20. Object o1;
  21. Object o2;
  22. public MyThread1(Object o1,Object o2){
  23. this.o1 = o1;
  24. this.o2 = o2;
  25. }
  26. public void run(){
  27. synchronized (o1){
  28. try {
  29. Thread.sleep(1000);
  30. } catch (InterruptedException e) {
  31. e.printStackTrace();
  32. }
  33. synchronized (o2){
  34. }
  35. }
  36. }
  37. }
  38. class MyThread2 extends Thread {
  39. Object o1;
  40. Object o2;
  41. public MyThread2(Object o1,Object o2){
  42. this.o1 = o1;
  43. this.o2 = o2;
  44. }
  45. public void run(){
  46. synchronized (o2){
  47. try {
  48. Thread.sleep(1000);
  49. } catch (InterruptedException e) {
  50. e.printStackTrace();
  51. }
  52. synchronized (o1){
  53. }
  54. }
  55. }
  56. }

在以后的开发中应该怎么解决线程安全问题

首选是线程同步吗?synchronized
不是,synchronized会让程序的执行效率降低,用户体验不好。
系统的用户吞吐量降低。用户体验差。在不得已的情况下再选择
线程同步机制。

第一种方案

尽量使用局部变量代替“实例变量和静态变量”。

第二种方案

如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了。
一个线程对应1个对象,100个线程对应100个对象, 对象不共享,就没有数据安全问题了

第三种方案

如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择synchronized了。线程同步机制。

守护线程

java语言中的线程分为两大类:

  1. 用户线程

  2. 守护线程(后台线程)

其中具有代表性的就是:垃圾回收线程—-是一个守护线程。

守护线程

语法:setDaemon(true);
特点:一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束。

注意: 主方法main方法是一个用户线程。

  1. **守护线程用在什么地方呢?**<br />每天00:00的时候系统数据自动备份。<br />这个需要使用到定时器,并且我们可以将定时器设置为守护线程。<br />一直在那里看着,每到00:00的时候就备份一次。<br />所有的用户线程如果结束了,守护线程自动退出,没有必要进行数据备份了。

代码

  1. package Thread;
  2. public class ThreadTest14 {
  3. public static void main(String[] args) {
  4. Thread t1 = new MyThread3();
  5. t1.setName("备份数据的线程");
  6. // 启动线程之前,将线程设置为守护线程⭐
  7. t1.setDaemon(true);
  8. //启动线程
  9. t1.start();
  10. // 主线程:主线程是用户线程
  11. for (int i = 1; i <10 ; i++) {
  12. System.out.println(Thread.currentThread().getName()+"-->"+i);
  13. try {
  14. Thread.sleep(1000);
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. }
  20. }
  21. class MyThread3 extends Thread{
  22. @Override
  23. public void run() {
  24. int i =0;
  25. // 即使是死循环,但由于该线程是守护者,当用户线程结束,守护线程自动终止。
  26. while (true){
  27. System.out.println(Thread.currentThread().getName()+"-->"+(++i));
  28. try {
  29. Thread.sleep(1000);
  30. } catch (InterruptedException e) {
  31. e.printStackTrace();
  32. }
  33. }
  34. }
  35. }

定时器

作用

间隔特定的时间,执行特定的程序。
在实际的开发中,每隔多久执行一段特定的程序,这种需求是很常见的,

例如:每周要进行银行账户的总账操作。
每天要进行数据的备份操作。

实现方式

  1. sleep(): 可以使用sleep方法,睡眠,设置睡眠时间,没到这个时间点醒来,执行任务。

这种方式是最原始的定时器。(比较low)

  1. java.util.Timer: 在java的类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用。

这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的。

  1. SpringTask框架:在实际的开发中,

目前使用较多的是Spring框架中提供SpringTask框架(底层是Timer)
这个框架只要进行简单的配置,就可以完成定时器的任务。

代码

  1. package Thread;
  2. import java.text.ParseException;
  3. import java.text.SimpleDateFormat;
  4. import java.util.Date;
  5. import java.util.Timer;
  6. import java.util.TimerTask;
  7. /*
  8. 使用定时器指定定时任务。
  9. */
  10. public class TimerTest {
  11. public static void main(String[] args) throws ParseException {
  12. //创建定时器对象
  13. Timer timer = new Timer();
  14. //Timer timer = new Timer(true); //守护线程的方式
  15. // 指定定时任务
  16. //timer.schedule(定时任务, 第一次执行时间, 间隔多久执行一次);
  17. SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  18. Date firstTime = sdf.parse("2021-05-11 15:57:00");
  19. //每10秒备份一次
  20. timer.schedule(new LogTimerTask(),firstTime,1000*10);
  21. //匿名内部类方式
  22. timer.schedule(new TimerTask(){
  23. @Override
  24. public void run() {
  25. // code....
  26. }
  27. } , firstTime, 1000 * 10);
  28. }
  29. }
  30. }
  31. class LogTimerTask extends TimerTask{
  32. @Override
  33. public void run() {
  34. //在这编写你需要执行的任务就行
  35. SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  36. String strTime = sdf.format(new Date());
  37. System.out.println(strTime+":成功完成了一次数据备份!");
  38. }
  39. }

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对象上处于等待的所有线程。

图解

image.png

生产者与消费者模式

知识点

  1. 使用wait()和notify()实现“生产者和消费者模式”。

  2. 什么是“生产者和消费者模式”?

生产线程负责生产,消费线程负责消费。
生产线程和消费线程要达到均衡。
这是一种特殊的业务需求,在这种特殊的情况下需要使用wait()和notify()。
This is a special work demand,in the special situation need use wait() and notify() .

  1. wait和notify方法不是线程对象的方法,是普通java对象都有的方法。

  2. wait方法和notify方法建立在线程同步的基础之上。因为多线程要同时操作一个仓库。有线程安全问题。

    图解

    image.png

    代码

    ```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个消费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));

  1. t1.setName("生产者线程");
  2. t2.setName("消费者线程");
  3. t1.start();
  4. t2.start();
  5. }

}

// 生产线程 class Producer implements Runnable { // 仓库 private List list;

  1. public Producer(List list) {
  2. this.list = list;
  3. }
  4. @Override
  5. public void run() {
  6. // 一直生产(使用死循环来模拟一直生产)
  7. while(true){
  8. // 给仓库对象list加锁。
  9. synchronized (list){
  10. if(list.size() > 0){ // 大于0,说明仓库中已经有1个元素了。
  11. try {
  12. // 当前线程进入等待状态,并且释放Producer之前占有的list集合的锁。
  13. list.wait();
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. }
  18. // 程序能够执行到这里说明仓库是空的,可以生产
  19. Object obj = new Object();
  20. list.add(obj);
  21. System.out.println(Thread.currentThread().getName() + "--->" + obj);
  22. // 唤醒消费者进行消费
  23. list.notifyAll();
  24. }
  25. }
  26. }

}

// 消费线程 class Consumer implements Runnable { // 仓库 private List list;

  1. public Consumer(List list) {
  2. this.list = list;
  3. }
  4. @Override
  5. public void run() {
  6. // 一直消费
  7. while(true){
  8. synchronized (list) {
  9. if(list.size() == 0){
  10. try {
  11. // 仓库已经空了。
  12. // 消费者线程等待,释放掉list集合的锁
  13. list.wait();
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. }
  18. // 程序能够执行到此处说明仓库中有数据,进行消费。
  19. Object obj = list.remove(0);
  20. System.out.println(Thread.currentThread().getName() + "--->" + obj);
  21. // 唤醒生产者生产。 唤醒等待中的线程。
  22. list.notifyAll();
  23. }
  24. }
  25. }

}

  1. <a name="tJeMi"></a>
  2. # 进阶-反射
  3. <a name="NOXWl"></a>
  4. ## 基本概念
  5. 1. 什么是反射机制,反射机制的作用
  6. 通过java语言中的反射机制可以操作字节码文件。<br />作用:可以让程序更加灵活。
  7. 有点类似于黑客。(可以读和修改字节码文件。)<br />通过反射机制可以操作代码片段。(class文件。)
  8. 2. 反射机制的相关类在哪个包下?
  9. java.lang.reflect.*;
  10. 3. 反射机制相关的重要的类有哪些?
  11. **java.lang.Class**:代表整个字节码,代表一个类型,代表整个类。
  12. **java.lang.reflect.Method**:代表字节码中的方法字节码。代表类中的方法。
  13. **java.lang.reflect.Constructor**:代表字节码中的构造方法字节码。代表类中的构造方法
  14. **java.lang.reflect.Field**:代表字节码中的属性字节码。代表类中的成员变量(静态变量+实例变量)。
  15. eg:
  16. ```java
  17. public class User{ //java.lang.Class
  18. private int no; //java.lang.reflect.Field
  19. //java.lang.reflect.Constructor
  20. public User(){
  21. }
  22. public User(int no){
  23. this.no=no;
  24. }
  25. //java.lang.reflect.Method
  26. public int getNo(){
  27. return no;
  28. }
  29. public void setNo(int no){
  30. this.no=no;
  31. }
  32. }

要操作一个类的字节码,需要首先获取到这个类的字节码,怎么获取java.lang.Class实例?

⭐获取Class的三种方式

第一种方式-Class.forName(“完整类名”)

  1. 静态方法
  2. 方法的参数是一个字符串。
  3. 字符串需要是一个完整的类名。
  4. 完成类名必须带有包名,java.lang包也不能省略。

    代码

    1. try {
    2. Class c1 = Class.forName("java.lang.String"); /c1代表String.class文件,
    3. 或者说c1代表String类型。/
    4. Class c2 = Class.forName("java.util.Date");// c2代表Date类型
    5. } catch (ClassNotFoundException e) {
    6. e.printStackTrace();
    7. }

    第二种方式-对象.getClass()

  5. java中任何一个对象都有一个方法:getClass()

    代码

    1. public static void main(String[] args) {
    2. Class c1 = null;
    3. try {
    4. c1 = Class.forName("java.lang.String"); //c1代表String.class文件,或者说c1代表String类型。
    5. Class c2 = Class.forName("java.util.Date");// c2代表Date类型
    6. } catch (ClassNotFoundException e) {
    7. e.printStackTrace();
    8. }
    9. // java中任何一个对象都有一个方法:getClass()
    10. String s = "abc";
    11. Class c3 = s.getClass(); // x代表String.class字节码文件,x代表String类型。
    12. System.out.println(c1==c3);//true
    13. }

    图解

    image.png

    第三种方式-类型.class

  6. java中任何一种类型,包括基本数据类型,它都有.class属性。

    代码

    1. public static void main(String[] args) {
    2. Class c1 = null;
    3. 第一种
    4. try {
    5. c1 = Class.forName("java.lang.String"); //c1代表String.class文件,或者说c1代表String类型。
    6. Class c2 = Class.forName("java.util.Date");// c2代表Date类型
    7. } catch (ClassNotFoundException e) {
    8. e.printStackTrace();
    9. }
    10. 第二种
    11. String s = "abc";
    12. Class c3 = s.getClass();
    13. System.out.println(c1==c3);//true
    14. 第三种
    15. Class a = String.class; //a代表String类型
    16. System.out.println(c1==a);//true
    17. }

    获取Class后能干的事

    ⭐通过反射实例化对象-Class

    知识点

  7. 获取了Class之后,可以调用无参数构造方法来实例化对象

  8. 一定要注意:newInstance()底层调用的是该类型的无参数构造方法。

如果没有这个无参数构造方法会出现”实例化”异常。

代码

  1. package com.bjpowernode.java.bean;
  2. public class User {
  3. public User(){
  4. System.out.println("无参数构造方法!");
  5. }
  6. // 定义了有参数的构造方法,无参数构造方法就没了。
  7. public User(String s){
  8. }
  9. }
  10. package com.bjpowernode.java.reflect;
  11. import com.bjpowernode.java.bean.User;
  12. /*
  13. 获取到Class,能干什么?
  14. 通过Class的newInstance()方法来实例化对象。
  15. 注意:newInstance()方法内部实际上调用了无参数构造方法,必须保证无参构造存在才可以。
  16. */
  17. public class ReflectTest02 {
  18. public static void main(String[] args) {
  19. // 这是不使用反射机制,创建对象
  20. User user = new User();
  21. System.out.println(user);
  22. // 下面这段代码是以反射机制的方式创建对象。
  23. try {
  24. // 通过反射机制,获取Class,通过Class来实例化对象
  25. Class c = Class.forName("com.bjpowernode.java.bean.User"); // c代表User类型。
  26. // newInstance() 这个方法会调用User这个类的无参数构造方法,完成对象的创建。
  27. // 重点是:newInstance()调用的是无参构造,必须保证无参构造是存在的!⭐
  28. Object obj = c.newInstance();
  29. System.out.println(obj); // com.bjpowernode.java.bean.User@10f87f48
  30. } catch (ClassNotFoundException e) {
  31. e.printStackTrace();
  32. } catch (IllegalAccessException e) {
  33. e.printStackTrace();
  34. } catch (InstantiationException e) {
  35. e.printStackTrace();
  36. }
  37. }
  38. }

验证反射机制的灵活性

  1. package com.bjpowernode.java.reflect;
  2. import com.bjpowernode.java.bean.User;
  3. import java.io.FileReader;
  4. import java.util.Properties;
  5. /*
  6. 验证反射机制的灵活性。
  7. java代码写一遍,再不改变java源代码的基础之上,可以做到不同对象的实例化。
  8. 非常之灵活。(符合OCP开闭原则:对扩展开放,对修改关闭。)
  9. 后期你们要学习的是高级框架,而工作过程中,也都是使用高级框架,
  10. 包括: ssh ssm
  11. Spring SpringMVC MyBatis
  12. Spring Struts Hibernate
  13. ...
  14. 这些高级框架底层实现原理:都采用了反射机制。所以反射机制还是重要的。
  15. 学会了反射机制有利于你理解剖析框架底层的源代码。
  16. */
  17. public class ReflectTest03 {
  18. public static void main(String[] args) throws Exception{
  19. // 这种方式代码就写死了。只能创建一个User类型的对象
  20. //User user = new User();
  21. // 以下代码是灵活的,代码不需要改动,可以修改配置文件,配置文件修改之后,可以创建出不同的实例对象。
  22. // 通过IO流读取classinfo.properties文件
  23. FileReader reader = new FileReader("chapter25/classinfo2.properties");
  24. // 创建属性类对象Map
  25. Properties pro = new Properties(); // key value都是String
  26. // 加载
  27. pro.load(reader);
  28. // 关闭流
  29. reader.close();
  30. // 通过key获取value
  31. String className = pro.getProperty("className");
  32. //System.out.println(className);
  33. // 通过反射机制实例化对象
  34. Class c = Class.forName(className);
  35. Object obj = c.newInstance();
  36. System.out.println(obj);
  37. }
  38. }
  39. chapter25/classinfo2.properties文件中的内容:
  40. className=com.bjpowernode.java.bean.User

只让静态代码块执行

  1. Class.forName(“该类的类名”);
  2. 这样类就加载,类加载的时候,静态代码块执行!!!!
  3. 在这里,对该方法的返回值不感兴趣,主要是为了使用“类加载”这个动作。

代码

  1. package com.bjpowernode.java.reflect;
  2. /*
  3. 研究一下:Class.forName()发生了什么?
  4. 记住,重点:
  5. 如果你只是希望一个类的静态代码块执行,其它代码一律不执行,
  6. 你可以使用:
  7. Class.forName("完整类名");
  8. 这个方法的执行会导致类加载,类加载时,静态代码块执行。
  9. 提示:
  10. 后面JDBC技术的时候我们还需要使用此方法。
  11. */
  12. public class ReflectTest04 {
  13. public static void main(String[] args) {
  14. try {
  15. // Class.forName()这个方法的执行会导致:类加载。
  16. Class.forName("com.bjpowernode.java.reflect.MyClass");
  17. } catch (ClassNotFoundException e) {
  18. e.printStackTrace();
  19. }
  20. }
  21. }
  22. package com.bjpowernode.java.reflect;
  23. public class MyClass {
  24. // 静态代码块在类加载时执行,并且只执行一次。
  25. static {
  26. System.out.println("MyClass类的静态代码块执行了!");
  27. }
  28. }

获取类路径下文件的绝对路径

  1. 前提是:文件需要在类路径下。才能用这种方式。
  2. 即使代码换位置了,这样编写仍然是通用的。
  3. 什么类路径下?放在src下的都是类路径下。【记住它】⭐⭐⭐
  4. 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”);

  1. // 接下来说一种比较通用的一种路径。即使代码换位置了,这样编写仍然是通用的。
  2. // 注意:使用以下通用方式的前提是:这个文件必须在类路径下。
  3. // 什么类路径下?方式在src下的都是类路径下。【记住它】
  4. // src是类的根路径。
  5. /*
  6. 解释:
  7. Thread.currentThread() 当前线程对象
  8. getContextClassLoader() 是线程对象的方法,可以获取到当前线程的类加载器对象。
  9. getResource() 【获取资源】这是类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源。
  10. */
  11. String path = Thread.currentThread().getContextClassLoader()
  12. .getResource("classinfo2.properties").getPath(); // 这种方式获取文件绝对路径是通用的。
  13. // 采用以上的代码可以拿到一个文件的绝对路径。
  14. // /C:/Users/Administrator/IdeaProjects/javase/out/production/chapter25/classinfo2.properties
  15. System.out.println(path);
  16. // 获取db.properties文件的绝对路径(从类的根路径下作为起点开始)
  17. String path2 = Thread.currentThread().getContextClassLoader()
  18. .getResource("com/bjpowernode/java/bean/db.properties").getPath();
  19. System.out.println(path2);
  20. }

}

  1. <a name="WUUiO"></a>
  2. ### 以流的形式直接返回
  3. ```java
  4. package com.bjpowernode.java.reflect;
  5. import java.io.FileReader;
  6. import java.io.InputStream;
  7. import java.util.Properties;
  8. public class IoPropertiesTest {
  9. public static void main(String[] args) throws Exception{
  10. // 获取一个文件的绝对路径了!!!!!
  11. /*String path = Thread.currentThread().getContextClassLoader()
  12. .getResource("classinfo2.properties").getPath();
  13. FileReader reader = new FileReader(path);*/
  14. // 直接以流的形式返回。
  15. InputStream reader = Thread.currentThread().getContextClassLoader()
  16. .getResourceAsStream("classinfo2.properties");
  17. Properties pro = new Properties();
  18. pro.load(reader);
  19. reader.close();
  20. // 通过key获取value
  21. String className = pro.getProperty("className");
  22. System.out.println(className);
  23. }
  24. }

以流的方式返回并通过反射实例化对象

  1. package reflect;
  2. import bean.Player;
  3. import java.io.*;
  4. import java.util.Properties;
  5. //验证反射的灵活性
  6. public class Reflect03 {
  7. public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {
  8. //直接以流的形式返回
  9. InputStream reader =Thread.currentThread().getContextClassLoader().getResourceAsStream("userinfo.properties");
  10. Properties pro = new Properties();
  11. pro.load(reader);
  12. reader.close();
  13. String className = pro.getProperty("className");
  14. //通过反射实例化对象
  15. Class c = Class.forName(className);
  16. Object obj=c.newInstance();
  17. System.out.println(obj);
  18. //相当于 Player p1 = new Player();
  19. }
  20. }

资源绑定器

知识点

  1. java.util包下提供了一个资源绑定器,便于获取属性配置文件中的内容。

  2. 使用资源绑定器的时候,属性配置文件xxx.properties必须放到类路径(src)下。

  3. 资源绑定器,只能绑定xxx.properties文件,并且这个文件必须在类路径下。

文件扩展名也必须是properties。

  1. 在写路径的时候,路径后面的扩展名不能写。

  2. ResourceBundle bundle = ResourceBundle.getBundle(“classinfo2”);

  3. 使用此方法可以完美替代-IO+Properties方法!

    代码

    ```java package com.bjpowernode.java.reflect;

import java.util.ResourceBundle;

/ java.util包下提供了一个资源绑定器,便于获取属性配置文件中的内容。 使用以下这种方式的时候,属性配置文件xxx.properties必须放到类路径下。 / public class ResourceBundleTest { public static void main(String[] args) {

  1. // 资源绑定器,只能绑定xxx.properties文件。并且这个文件必须在类路径下。文件扩展名也必须是properties
  2. // 并且在写路径的时候,路径后面的扩展名不能写。
  3. //ResourceBundle bundle = ResourceBundle.getBundle("classinfo2");
  4. ResourceBundle bundle = ResourceBundle.getBundle("com/bjpowernode/java/bean/db");
  5. String className = bundle.getString("className");
  6. System.out.println(className);
  7. }

}

  1. <a name="ZQ1rD"></a>
  2. ### ⭐通过资源绑定器-反射实例化对象
  3. 最终方法!
  4. ```java
  5. package reflect;
  6. import bean.Player;
  7. import java.io.*;
  8. import java.util.Properties;
  9. import java.util.ResourceBundle;
  10. //验证反射的灵活性
  11. public class Reflect03 {
  12. public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {
  13. /* //直接以流的形式返回
  14. InputStream reader =Thread.currentThread().getContextClassLoader().getResourceAsStream("userinfo.properties");
  15. Properties pro = new Properties();
  16. pro.load(reader);
  17. reader.close();
  18. String className = pro.getProperty("className");*/
  19. //以上代码可以通过资源绑定器,完美替代
  20. ResourceBundle rb = ResourceBundle.getBundle("userinfo");
  21. String className = rb.getString("className");
  22. //通过反射实例化对象
  23. Class c = Class.forName(className);
  24. Object obj=c.newInstance();
  25. System.out.println(obj);
  26. //相当于 Player p1 = new Player();
  27. }
  28. }

拓展-类加载器

聊一聊,不需要掌握,知道当然最好!

  1. 什么是类加载器?

专门负责加载类的命令/工具-ClassLoader

  1. JDK中自带了3个类加载器

启动类加载器:rt.jar
扩展类加载器:ext/*.jar
应用类加载器:classpath

  1. 假设有这样一段代码:

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中的类。

双亲委派机制

  1. java中为了保证类加载的安全,使用了双亲委派机制。

  2. 优先从启动类加载器中加载,这个称为“父”

“父”无法加载到,再从扩展类加载器中加载,
这个称为“母”。双亲委派。
如果都加载不到,才会考虑从应用类加载器中加载。直到加载到为止。

获取Field(了解即可!)

前提:要先获取Class

知识点

  1. 获取类中所有的public修饰的Field:

Field[] fields=c1.getFields();

  1. 获取所有的Field

Field[] fields1=c1.getDeclaredFields();

  1. 获取Field的名字

System.out.println(f.getName());

  1. 获取属性的类型

Class fieldType=f.getType();
System.out.println(fieldType.getSimpleName());

  1. 获取属性的修饰符列表

int i=f.getModifiers();//返回的修饰符是一个数字,每个数字是修饰符的代号!!!
将代号转换成字符串
String modifierString=Modifier.toString(i);

反射Field

  1. package reflect;
  2. import java.lang.reflect.Field;
  3. import java.lang.reflect.Modifier;
  4. public class ReflectTest05 {
  5. public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
  6. //获取类
  7. Class c1 = Class.forName("bean.Car");
  8. //获取小名
  9. String simpleName=c1.getSimpleName();
  10. System.out.println(simpleName);
  11. //获取全名
  12. String className=c1.getName();
  13. System.out.println(className);
  14. System.out.println("===================");
  15. //获取类中所有的Public修饰的Field
  16. Field[] fields=c1.getFields();
  17. //取出fields中的元素,并输出名字
  18. Field f1 = fields[0];
  19. System.out.println(f1.getName());
  20. System.out.println("===================");
  21. //获取所有的Field
  22. Field[] fields1=c1.getDeclaredFields();
  23. //遍历此数组,取出fields1中的元素,并输出名字
  24. for (Field f:
  25. fields1) {
  26. //获取属性的名字
  27. System.out.println(f.getName());
  28. //获取属性的类型
  29. Class fieldType=f.getType();
  30. System.out.println(fieldType.getSimpleName());
  31. // 获取属性的修饰符列表
  32. int i=f.getModifiers();//返回的修饰符是一个数字,每个数字是修饰符的代号!!!
  33. System.out.println(i);
  34. //将代号转换成字符串
  35. String modifierString=Modifier.toString(i);
  36. System.out.println(modifierString);
  37. System.out.println("===================");
  38. }
  39. }
  40. }

反编译Field

  1. package com.bjpowernode.java.reflect;
  2. //通过反射机制,反编译一个类的属性Field(了解一下)
  3. import java.lang.reflect.Field;
  4. import java.lang.reflect.Modifier;
  5. public class ReflectTest06 {
  6. public static void main(String[] args) throws Exception{
  7. // 创建这个是为了拼接字符串。
  8. StringBuilder s = new StringBuilder();
  9. //Class studentClass = Class.forName("com.bjpowernode.java.bean.Student");
  10. Class studentClass = Class.forName("java.lang.Thread");
  11. s.append(Modifier.toString(studentClass.getModifiers()) + " class " + studentClass.getSimpleName() + " {\n");
  12. Field[] fields = studentClass.getDeclaredFields();
  13. for(Field field : fields){
  14. s.append("\t");
  15. s.append(Modifier.toString(field.getModifiers()));
  16. s.append(" ");
  17. s.append(field.getType().getSimpleName());
  18. s.append(" ");
  19. s.append(field.getName());
  20. s.append(";\n");
  21. }
  22. s.append("}");
  23. System.out.println(s);
  24. }
  25. }

⭐通过反射访问对象属性-Field

知识点

  1. 怎么通过反射机制访问一个java对象的属性?

给属性赋值set
获取属性的值get

代码

  1. package reflect;
  2. import bean.Car;
  3. import java.lang.reflect.Field;
  4. public class ReflectTest07 {
  5. public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {
  6. //我们不使用反射机制,怎么样去访问一个对象的属性呢?
  7. Car car = new Car();
  8. //给属性赋值
  9. /*
  10. 给car对象的name属性赋值领克的三要素:
  11. 1:对象car
  12. 2:name属性
  13. 3:值 领克
  14. */
  15. car.name="领克";
  16. //获取属性的值
  17. /*
  18. 获取car对象的name属性的二要素:
  19. 1:对象car
  20. 2:name属性
  21. */
  22. System.out.println(car.name);
  23. //使用反射机制,怎么样去访问一个对象的属性呢?
  24. 🏀//获取Class
  25. Class carClass=Class.forName("bean.Car");
  26. //实例化对象
  27. Object obj=carClass.newInstance();
  28. //这两步相当于完成了 Car car = new Car();
  29. 🏀//获取name属性(根据属性的名称来获取Field)
  30. Field namefield =carClass.getDeclaredField("name");
  31. 🏀//给obj对象(Car对象)的name属性赋值
  32. /*
  33. 给obj对象的name属性赋值保时捷的三要素:
  34. 1:对象obj
  35. 2:name属性-namefield就代表name属性
  36. 3:值 保时捷
  37. 注意:反射机制让代码复杂了,但是为了一个“灵活”,这也是值得的。
  38. */
  39. namefield.set(obj,"保时捷");
  40. 🏀//读取属性的值
  41. /*
  42. 获取obj对象的name属性的二要素:
  43. 1:对象obj
  44. 2:name属性
  45. */
  46. System.out.println(namefield.get(obj));
  47. //访问私有属性
  48. //获取no属性
  49. Field noField = carClass.getDeclaredField("no");
  50. 🏀//打破封装:因为是private的,所以需要打破封装。
  51. //反射机制的缺点:打破封装,可能会给不法分子留下机会!!!
  52. noField.setAccessible(true);
  53. //赋值
  54. noField.set(obj,226);
  55. //获取值
  56. System.out.println(noField.get(obj));
  57. }
  58. }

可变长度参数-插入知识点

知识点

int… args 这就是可变长度参数

  1. 语法:类型… (注意:一定是3个点。)
  2. 可变长度参数要求的参数个数是:0~N个。
  3. 可变长度参数在参数列表中必须在最后一个位置上,而且只能有1个。
  4. 可变长度参数可以当做一个数组来看待。

代码

  1. package com.bjpowernode.java.reflect;
  2. public class ArgsTest {
  3. public static void main(String[] args) {
  4. m();
  5. m(10);
  6. m(10, 20);
  7. // 编译报错,类型错误
  8. //m("abc");
  9. m2(100);
  10. m2(200, "abc");
  11. m2(200, "abc", "def");
  12. m2(200, "abc", "def", "xyz");
  13. m3("ab", "de", "kk", "ff");
  14. String[] strs = {"a","b","c"};
  15. // 也可以传1个数组
  16. m3(strs);
  17. // 直接传1个数组
  18. m3(new String[]{"我","是","中","国", "人"}); //没必要
  19. m3("我","是","中","国", "人");
  20. }
  21. public static void m(int... args){
  22. System.out.println("m方法执行了!");
  23. }
  24. //public static void m2(int... args2, String... args1){}
  25. // 必须在最后,只能有1个。
  26. public static void m2(int a, String... args1){
  27. }
  28. public static void m3(String... args){
  29. //args有length属性,说明args是一个数组!
  30. // 可以将可变长度参数当做一个数组来看。
  31. for(int i = 0; i < args.length; i++){
  32. System.out.println(args[i]);
  33. }
  34. }
  35. }

获取Method(了解即可!)

反射Method

  1. UserService
  2. package com.bjpowernode.java.service;
  3. /**
  4. * 用户业务类
  5. */
  6. public class UserService {
  7. /**
  8. * 登录方法
  9. * @param name 用户名
  10. * @param password 密码
  11. * @return true表示登录成功,false表示登录失败!
  12. */
  13. public boolean login(String name,String password){
  14. if("admin".equals(name) && "123".equals(password)){
  15. return true;
  16. }
  17. return false;
  18. }
  19. // 可能还有一个同名login方法
  20. // java中怎么区分一个方法,依靠方法名和参数列表。
  21. public void login(int i){
  22. }
  23. /**
  24. * 退出系统的方法
  25. */
  26. public void logout(){
  27. System.out.println("系统已经安全退出!");
  28. }
  29. }
  30. package com.bjpowernode.java.reflect;
  31. import java.lang.reflect.Method;
  32. import java.lang.reflect.Modifier;
  33. /*
  34. 作为了解内容(不需要掌握):
  35. 反射Method
  36. */
  37. public class ReflectTest08 {
  38. public static void main(String[] args) throws Exception{
  39. // 获取类了
  40. Class userServiceClass = Class.forName("com.bjpowernode.java.service.UserService");
  41. // 获取所有的Method(包括私有的!)
  42. Method[] methods = userServiceClass.getDeclaredMethods();
  43. //System.out.println(methods.length); // 2
  44. // 遍历Method
  45. for(Method method : methods){
  46. // 获取修饰符列表
  47. System.out.println(Modifier.toString(method.getModifiers()));
  48. // 获取方法的返回值类型
  49. System.out.println(method.getReturnType().getSimpleName());
  50. // 获取方法名
  51. System.out.println(method.getName());
  52. // 方法的修饰符列表(一个方法的参数可能会有多个。)
  53. Class[] parameterTypes = method.getParameterTypes();
  54. for(Class parameterType : parameterTypes){
  55. System.out.println(parameterType.getSimpleName());
  56. }
  57. }
  58. }
  59. }

反编译Method

  1. package com.bjpowernode.java.reflect;
  2. import java.lang.reflect.Method;
  3. import java.lang.reflect.Modifier;
  4. /*
  5. 了解一下,不需要掌握(反编译一个类的方法。)
  6. */
  7. public class ReflectTest09 {
  8. public static void main(String[] args) throws Exception{
  9. StringBuilder s = new StringBuilder();
  10. //Class userServiceClass = Class.forName("com.bjpowernode.java.service.UserService");
  11. Class userServiceClass = Class.forName("java.lang.String");
  12. s.append(Modifier.toString(userServiceClass.getModifiers()) + " class "+userServiceClass.getSimpleName()+" {\n");
  13. Method[] methods = userServiceClass.getDeclaredMethods();
  14. for(Method method : methods){
  15. //public boolean login(String name,String password){}
  16. s.append("\t");
  17. s.append(Modifier.toString(method.getModifiers()));
  18. s.append(" ");
  19. s.append(method.getReturnType().getSimpleName());
  20. s.append(" ");
  21. s.append(method.getName());
  22. s.append("(");
  23. // 参数列表
  24. Class[] parameterTypes = method.getParameterTypes();
  25. for(Class parameterType : parameterTypes){
  26. s.append(parameterType.getSimpleName());
  27. s.append(",");
  28. }
  29. // 删除指定下标位置上的字符
  30. s.deleteCharAt(s.length() - 1);
  31. s.append("){}\n");
  32. }
  33. s.append("}");
  34. System.out.println(s);
  35. }
  36. }

⭐通过反射调用方法-Method

  1. package reflect;
  2. import bean.UserService;
  3. import java.lang.reflect.InvocationTargetException;
  4. import java.lang.reflect.Method;
  5. public class Reflect10 {
  6. public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
  7. //不使用反射机制,调用方法
  8. //创建对象
  9. UserService userService = new UserService();
  10. //调用方法
  11. /*
  12. 要素分析:
  13. 要素1:对象userService
  14. 要素2:login方法名
  15. 要素3:实际参数列表
  16. 要素4:返回值
  17. */
  18. boolean loginSuccess=userService.login("admin","123");
  19. System.out.println(loginSuccess?"登陆成功":"登录失败");
  20. //使用反射机制,调用方法
  21. //获取Class
  22. Class usClass=Class.forName("bean.UserService");
  23. //实例化对象
  24. Object obj=usClass.newInstance();
  25. // 获取Method,重载的方法,通过参数列表进区分
  26. Method loginMethod =usClass.getDeclaredMethod("login", String.class, String.class);
  27. //想要访问public void login(int i) 这个方法
  28. Method loginMethod2=usClass.getDeclaredMethod("login", int.class);
  29. Method logoutMethod = usClass.getDeclaredMethod("logout");
  30. //调用方法
  31. // 反射机制中最最最最最重要的一个方法,必须记住。
  32. /*
  33. 四要素:
  34. loginMethod方法
  35. obj对象
  36. "admin","123" 实参
  37. retValue 返回值
  38. */
  39. Object retVaule=loginMethod.invoke(obj,"admin","123");
  40. System.out.println(retVaule);
  41. logoutMethod.invoke(obj); //此方法是void没有返回值,直接调用
  42. }
  43. }

获取Constructor(了解即可!)

反编译Constructor

  1. package com.bjpowernode.java.bean;
  2. public class Vip {
  3. int no;
  4. String name;
  5. String birth;
  6. boolean sex;
  7. public Vip() {
  8. }
  9. public Vip(int no) {
  10. this.no = no;
  11. }
  12. public Vip(int no, String name) {
  13. this.no = no;
  14. this.name = name;
  15. }
  16. public Vip(int no, String name, String birth) {
  17. this.no = no;
  18. this.name = name;
  19. this.birth = birth;
  20. }
  21. public Vip(int no, String name, String birth, boolean sex) {
  22. this.no = no;
  23. this.name = name;
  24. this.birth = birth;
  25. this.sex = sex;
  26. }
  27. @Override
  28. public String toString() {
  29. return "Vip{" +
  30. "no=" + no +
  31. ", name='" + name + '\'' +
  32. ", birth='" + birth + '\'' +
  33. ", sex=" + sex +
  34. '}';
  35. }
  36. }
  37. package com.bjpowernode.java.reflect;
  38. import java.lang.reflect.Constructor;
  39. import java.lang.reflect.Modifier;
  40. /*
  41. 反编译一个类的Constructor构造方法。
  42. */
  43. public class ReflectTest11 {
  44. public static void main(String[] args) throws Exception{
  45. StringBuilder s = new StringBuilder();
  46. Class vipClass = Class.forName("java.lang.String");
  47. s.append(Modifier.toString(vipClass.getModifiers()));
  48. s.append(" class ");
  49. s.append(vipClass.getSimpleName());
  50. s.append("{\n");
  51. // 拼接构造方法
  52. Constructor[] constructors = vipClass.getDeclaredConstructors();
  53. for(Constructor constructor : constructors){
  54. //public Vip(int no, String name, String birth, boolean sex) {
  55. s.append("\t");
  56. s.append(Modifier.toString(constructor.getModifiers()));
  57. s.append(" ");
  58. s.append(vipClass.getSimpleName());
  59. s.append("(");
  60. // 拼接参数
  61. Class[] parameterTypes = constructor.getParameterTypes();
  62. for(Class parameterType : parameterTypes){
  63. s.append(parameterType.getSimpleName());
  64. s.append(",");
  65. }
  66. // 删除最后下标位置上的字符
  67. if(parameterTypes.length > 0){
  68. s.deleteCharAt(s.length() - 1);
  69. }
  70. s.append("){}\n");
  71. }
  72. s.append("}");
  73. System.out.println(s);
  74. }
  75. }

⭐通过反射调用构造方法-Constructor

  1. package com.bjpowernode.java.reflect;
  2. import com.bjpowernode.java.bean.Vip;
  3. import java.lang.reflect.Constructor;
  4. /*
  5. 比上一个例子(ReflectTest11)重要一些!!!
  6. 通过反射机制调用构造方法实例化java对象。(这个不是重点)
  7. */
  8. public class ReflectTest12 {
  9. public static void main(String[] args) throws Exception{
  10. // 不使用反射机制怎么创建对象
  11. Vip v1 = new Vip();
  12. Vip v2 = new Vip(110, "zhangsan", "2001-10-11", true);
  13. // 使用反射机制怎么创建对象呢?
  14. Class c = Class.forName("com.bjpowernode.java.bean.Vip");
  15. // 调用无参数构造方法
  16. Object obj = c.newInstance();
  17. System.out.println(obj);
  18. // 调用有参数的构造方法怎么办?
  19. // 第一步:先获取到这个有参数的构造方法
  20. Constructor con = c.getDeclaredConstructor(int.class, String.class, String.class,boolean.class);
  21. // 第二步:调用构造方法new对象
  22. Object newObj = con.newInstance(110, "jackson", "1990-10-11", true);
  23. System.out.println(newObj);
  24. // 获取无参数构造方法
  25. Constructor con2 = c.getDeclaredConstructor();
  26. Object newObj2 = con2.newInstance();
  27. System.out.println(newObj2);
  28. }
  29. }

⭐获取父类和父接口

  1. package com.bjpowernode.java.reflect;
  2. /*
  3. 重点:给你一个类,怎么获取这个类的父类,已经实现了哪些接口?
  4. */
  5. public class ReflectTest13 {
  6. public static void main(String[] args) throws Exception{
  7. // String举例
  8. Class stringClass = Class.forName("java.lang.String");
  9. // 获取String的父类
  10. Class superClass = stringClass.getSuperclass();
  11. System.out.println(superClass.getName());
  12. // 获取String类实现的所有接口(一个类可以实现多个接口。)
  13. Class[] interfaces = stringClass.getInterfaces();
  14. for(Class in : interfaces){
  15. System.out.println(in.getName());
  16. }
  17. }
  18. }

进阶-注解

基本概念

  1. 注解,或者叫注释类型,英文单词是-Annotation

  2. 注解Annotation是一种引用数据类型。编译之后也是生成xxx.class文件。

  3. 怎么自定义注解呢?语法格式?

[修饰符列表] @interface 注解类型名{

  1. 注解怎么使用,用在什么地方?
    1. 注解使用时的语法格式是:
      1. @注解类型名
    2. 注解可以出现在类上、属性上、方法上、变量上等….

注解还可以出现在注解类型上。

代码

  1. package com.bjpowernode.java.annotation;
  2. /*
  3. 自定义注解:MyAnnotation
  4. */
  5. public @interface MyAnnotation {
  6. // ??????
  7. }
  8. 🏀
  9. package com.bjpowernode.java.annotation;
  10. // 注解修饰注解。
  11. @MyAnnotation
  12. public @interface OtherAnnotation {
  13. }
  14. 🏀
  15. package com.bjpowernode.java.annotation;
  16. // 默认情况下,注解可以出现在任意位置。
  17. @MyAnnotation
  18. public class AnnotationTest01 {
  19. @MyAnnotation
  20. private int no;
  21. @MyAnnotation
  22. public AnnotationTest01(){}
  23. @MyAnnotation
  24. public static void m1(){
  25. @MyAnnotation
  26. int i = 100;
  27. }
  28. @MyAnnotation
  29. public void m2(@MyAnnotation
  30. String name,
  31. @MyAnnotation
  32. int k){
  33. }
  34. }
  35. @MyAnnotation
  36. interface MyInterface {
  37. }
  38. @MyAnnotation
  39. enum Season {
  40. SPRING,SUMMER,AUTUMN,WINTER
  41. }

JDK中内置的注解

java.lang包下的注释类型

  1. Deprecated 用 @Deprecated 注释的程序元素

不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择。

  1. Override 表示一个方法声明打算重写超类中的另一个方法声明。

  2. SuppressWarnings 指示应该在注释元素(以及包含在该注释元素中的所有程序元素)中

取消显示指定的编译器警告。 (不需要掌握)

⭐Override

  1. 源代码:public @interface Override { }

  2. 标识性注解,给编译器做参考的。

编译器看到方法上有这个注解的时候,编译器会自动检查该方法是否重写了父类的方法。
如果没有重写,报错。

  1. 这个注解只是在编译阶段起作用,和运行期无关!

  2. @Override这个注解只能注解方法。

  3. 凡是java中的方法带有这个注解的,编译器都会进行编译检查,

如果这个方法不是重写父类的方法,编译器报错。

⭐Deprecated

  1. Deprecated这个注解标注的元素已过时。

  2. 这个注解主要是向其它程序员传达一个信息,告知已过时,有更好的解决方案存在。

  3. image.png

表示doSome方法已过时。

代码

  1. //@Override-报错
  2. public class AnnotationTest02 {
  3. //@Override-报错
  4. private int no;
  5. @Override
  6. public String toString() {
  7. return "toString";
  8. }
  9. }

元注解

知识点

  1. 什么是元注解?

用来标注“注解类型”的“注解”,称为元注解。

  1. 常见的元注解有哪些?

Target Retention

⭐Target注解

知识点

  1. 这是一个元注解,用来标注“注解类型”的“注解”。
  2. 这个Target注解用来标注“被标注的注解”可以出现在哪些位置上。

    语法

  3. @Target(ElementType.METHOD):表示“被标注的注解”只能出现在方法上。

  4. @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE})

表示该注解可以出现在:构造方法上,字段上,字段上,方法上········类上…

  1. image.png

此注解就表示Override注解只能出现在方法上。

⭐Retention注解

知识点

  1. 这是一个元注解,用来标注“注解类型”的“注解”
  2. 这个Retention注解用来标注“被标注的注解”最终保存在哪里。

    语法

  3. @Retention(RetentionPolicy.SOURCE):表示该注解只被保留在java源文件中。

  4. @Retention(RetentionPolicy.CLASS):表示该注解被保存在class文件中。
  5. @Retention(RetentionPolicy.RUNTIME):表示该注解被保存在class文件中,并且可以被反射机制所读取。
  6. JAVA笔记(下) - 图11

此注解就表示Override注解只被保留在java源文件中。

源代码

  1. //元注解
  2. public @interface Retention {
  3. //属性 是一个value所以代表value可以省略不写
  4. RetentionPolicy value();
  5. }
  6. RetentionPolicy的源代码: 是一个枚举类型
  7. public enum RetentionPolicy {
  8. SOURCE,
  9. CLASS,
  10. RUNTIME
  11. }
  12. @Retention(RetentionPolicy.RUNTIME) == @Retention(value=RetentionPolicy.RUNTIME)

注解中定义属性

知识点

  1. 如果一个注解当中有属性,那么必须给属性赋值。(除非该属性使用default指定了默认值。)
  2. 语法:@MyAnnotation(属性名=属性值,属性名=属性值,属性名=属性值)
  3. 如果一个注解的属性的名字是value,并且只有一个属性的话,在使用的时候,该属性名可以省略。

@MyAnnotation(value = “hehe”) == @MyAnnotation(“haha”)

代码

  1. package com.bjpowernode.java.annotation2;
  2. public @interface MyAnnotation {
  3. /**
  4. * 我们通常在注解当中可以定义属性,以下这个是MyAnnotation的name属性。
  5. * 看着像1个方法,但实际上我们称之为属性name。
  6. * @return
  7. */
  8. String name();
  9. /*
  10. 颜色属性
  11. */
  12. String color();
  13. /*
  14. 年龄属性
  15. */
  16. int age() default 25; //属性指定默认值
  17. }
  18. package com.bjpowernode.java.annotation2;
  19. public class MyAnnotationTest {
  20. // 报错的原因:如果一个注解当中有属性,那么必须给属性赋值。(除非该属性使用default指定了默认值。)
  21. /*@MyAnnotation
  22. public void doSome(){
  23. }*/
  24. //@MyAnnotation(属性名=属性值,属性名=属性值,属性名=属性值)
  25. //指定name属性的值就好了。
  26. @MyAnnotation(name = "zhangsan", color = "红色")
  27. public void doSome(){
  28. }
  29. }

注解中的属性可以是什么类型?

  1. 属性的类型可以是:byte short int long float double boolean char

    String Class 枚举类型
    以及以上每一种的数组形式。 ```java public @interface MyAnnotation { /* 注解当中的属性可以是哪一种类型?

    1. 属性的类型可以是:
    2. byte short int long float double boolean char String Class 枚举类型
    3. 以及以上每一种的数组形式。

    */ int value1();

    String value2();

    int[] value3();

    String[] value4();

    Season value5();

    Season[] value6();

    Class parameterType();

    Class[] parameterTypes(); }

  1. <a name="AT1eW"></a>
  2. #### 当注解的属性是数组时
  3. 1. 数组是大括号
  4. 1. 如果数组中只有1个元素:大括号可以省略。
  5. ```java
  6. //定义一个枚举类型
  7. public enum Season {
  8. SPRING,SUMMER,AUTUMN,WINTER
  9. }
  10. //自定义一个注解
  11. public @interface OtherAnnotation {
  12. /*
  13. 年龄属性
  14. */
  15. int age();
  16. /*
  17. 邮箱地址属性,支持多个
  18. */
  19. String[] email();
  20. /**
  21. * 季节数组,Season是枚举类型
  22. * @return
  23. */
  24. Season[] seasonArray();
  25. }
  26. public class OtherAnnotationTest {
  27. // 数组是大括号
  28. @OtherAnnotation(age = 25, email = {"zhangsan@123.com", "zhangsan@sohu.com"}, seasonArray = Season.WINTER)
  29. public void doSome(){
  30. }
  31. // 如果数组中只有1个元素:大括号可以省略。
  32. @OtherAnnotation(age = 25, email = "zhangsan@123.com", seasonArray = {Season.SPRING, Season.SUMMER})
  33. public void doOther(){
  34. }
  35. }

反射注解

MyAnnotation

  1. //只允许该注解可以标注类、方法
  2. @Target({ElementType.TYPE,ElementType.METHOD})
  3. // 希望这个注解可以被反射
  4. @Retention(RetentionPolicy.RUNTIME)
  5. public @interface MyAnnotation {
  6. String value() default "xyb";
  7. }

MyAnnotationTest

  1. @MyAnnotation
  2. public class MyAnnotationTest {
  3. @MyAnnotation
  4. public void doSome(){}
  5. /* 不可以
  6. @MyAnnotation
  7. int i;*/
  8. }

ReflectAnnotationTest

  1. public class ReflectAnnotationTest {
  2. public static void main(String[] args) throws ClassNotFoundException {
  3. //获取这个类
  4. Class c=Class.forName("Annotation.MyAnnotationTest");
  5. //判断此类是否有@MyAnnotation
  6. System.out.println(c.isAnnotationPresent(MyAnnotation.class)); //true
  7. if (c.isAnnotationPresent(MyAnnotation.class)){
  8. //获取该注解对象
  9. MyAnnotation myAnnotation=(MyAnnotation) c.getAnnotation(MyAnnotation.class);
  10. //类上面的注解对象@Annotation.MyAnnotation(value=xyb)
  11. System.out.println("类上面的注解对象" + myAnnotation);
  12. //获取注解对象的属性
  13. String value=myAnnotation.value();
  14. System.out.println(value);
  15. }
  16. }
  17. }

注解在开发中的作用

知识点

  1. 注解在程序中等同于一种标记

如果这个元素上有这个注解怎么办?没有这个注解怎么办?

实例

假设有这样一个注解,叫做:@Id
这个注解只能出现在类上面,当这个类上有这个注解的时候,
要求这个类中必须有一个int类型的id属性。
如果没有这个属性就报异常。如果有这个属性则正常执行!

MustHasIdPropertyAnnotation

  1. // 这个注解@MustHasIdPropertyAnnotation用来标注类,被标注的类中必须有一个int类型的id属性,没有就报异常。
  2. // 表示这个注解只能出现在类上面
  3. @Target(ElementType.TYPE)
  4. // 该注解可以被反射机制读取到
  5. @Retention(RetentionPolicy.RUNTIME)
  6. public @interface MustHasIdPropertyAnnotation {
  7. }

User

  1. @MustHasIdPropertyAnnotation
  2. public class User {
  3. int id;
  4. String name;
  5. String password;
  6. }

HasNotIdPropertyException

  1. /*
  2. 自定义异常
  3. */
  4. public class HasNotIdPropertyException extends RuntimeException {
  5. public HasNotIdPropertyException(){
  6. }
  7. public HasNotIdPropertyException(String s){
  8. super(s);
  9. }
  10. }

Test

  1. public class Test {
  2. public static void main(String[] args) throws Exception{
  3. // 获取类
  4. Class userClass = Class.forName("com.bjpowernode.java.annotation7.User");
  5. // 判断类上是否存在Id注解
  6. if(userClass.isAnnotationPresent(MustHasIdPropertyAnnotation.class)){
  7. // 当一个类上面有@MustHasIdPropertyAnnotation注解的时候,要求类中必须存在int类型的id属性
  8. // 如果没有int类型的id属性则报异常。
  9. // 获取类的属性
  10. Field[] fields = userClass.getDeclaredFields();
  11. boolean isOk = false; // 给一个默认的标记
  12. for(Field field : fields){
  13. if("id".equals(field.getName()) && "int".equals(field.getType().getSimpleName())){
  14. // 表示这个类是合法的类。有@Id注解,则这个类中必须有int类型的id
  15. isOk = true; // 表示合法
  16. break;
  17. }
  18. }
  19. // 判断是否合法
  20. if(!isOk){
  21. throw new HasNotIdPropertyException("被@MustHasIdPropertyAnnotation注解标注的类中必须要有一个int类型的id属性!");
  22. }
  23. }
  24. }
  25. }