Review: Java SE 04(异常机制和File类、IO流、多线程、网络编程、反射机制)


Java SE 04

目录
  • Java SE 04
    • 一、异常机制和File类
      • 1. 异常机制
      • 2. File类
    • 二、IO流
      • 1. IO流的框架结构
      • 2. 各种相关流
    • 三、多线程
      • 1. 线程的创建和启动
      • 2. 线程编号和名称
      • 3. Thread类中其他常用方法
      • 4. 多个线程执行不同代码
      • 5. 同步代码块、同步方法、Lock接口
      • 6. 线程之间的通信
      • 7. 使用Callable接口创建线程
      • 8. 线程池概念和使用
    • 四、网络编程
      • 1. 基于TCP协议的编程模型
      • 2. 基于UDP协议的编程模型
      • 3. URL类
    • 五、反射机制
      • 1. Class类
      • 2. Constructor类
      • 3. Field类
      • 4. Method类
      • 5. 获取其他信息

一、异常机制和File类

1. 异常机制

  • java.lang.Throwable类是Java语言中错误(Error)和异常(Exception)的超类

  • 异常分类

    检测性异常:编译阶段都能被编译器检测出来的异常,不处理就无法到运行阶段

    非检测性异常:运行阶段会发生异常,这个异常后面的代码不会执行

    public class ExceptionTest {
    
        public static void main(String[] args) {
    
            // 1.见识一下非检测性异常  运行时异常
            System.out.println(5 / 0); // 编译ok,运行阶段会发生算术异常  下面的代码不会执行
    
            // 2.检测性异常
            //Thread.sleep(1000); // 编译错误  不处理就无法到运行阶段
    
            System.out.println("程序正常结束了!");
        }
    }
    
  • 异常的避免

    开发中尽量使用if条件判断来避免异常的发生,但是过多的if条件判断会导致程序的代码加长、臃肿,可读性差

    public class ExceptionPreventTest {
    
        public static void main(String[] args) {
    
            // 会发生算术异常
            int ia = 10;
            int ib = 0;
            if (0 != ib) {
                System.out.println(ia / ib);
            }
    
            // 数组下标越界异常
            int[] arr = new int[5];
            int pos = 5;
            if (pos >= 0 && pos < 5) {
                System.out.println(arr[pos]);
            }
    
            // 发生空指针异常
            String str = null;
            if (null != str) {
                System.out.println(str.length());
            }
    
            // 类型转换异常
            Exception ex = new Exception();
            if (ex instanceof IOException) {
                IOException ie = (IOException) ex;
            }
    
            // 数字格式异常
            String str2 = "123a";
            if (str2.matches("\\d+")) {
                System.out.println(Integer.parseInt(str2));
            }
    
            System.out.println("程序总算正常结束了!");
        }
    }
    
  • 异常的捕获

    • 当程序执行过程中发生异常但又没有手动处理时,则由Java虚拟机采用默认方式处理异常,而默认处理方式就是:

      打印异常的名称、异常发生的原因、异常发生的位置以及终止程序

    • 手动处理异常和没有处理的区别:代码是否可以继续向下执行

    • 当需要编写多个catch分支时,切记小类型应该放在大类型的前面

    • 示例Code

      public class ExceptionCatchTest {
      
          public static void main(String[] args) {
      
              // 创建一个FileInputStream类型的对象与d:/a.txt文件关联,打开文件
              FileInputStream fis = null;
              try {
                  System.out.println("1");
                  // 当程序执行过程中发生了异常后直奔catch分支进行处理
                  fis = new FileInputStream("d:/a.txt");
                  System.out.println("2");
              } catch (FileNotFoundException e) {
                  System.out.println("3");
                  e.printStackTrace();
                  System.out.println("4");
              }
              // 关闭文件
              try {
                  System.out.println("5");
                  fis.close();//当文件a.txt不存在时,FileInputStream无法创建,这里调用close方法会空指针异常
                  System.out.println("6");
              } /*catch (Exception e) {//Exception是其他异常的父类,放在其他异常之前报错exception has been caught
                  e.printStackTrace();
              }*/ catch (IOException e) {
                  System.out.println("7");
                  e.printStackTrace();
                  System.out.println("8");
              } catch (NullPointerException e) {
                  e.printStackTrace();
              } catch (Exception e) {
                  e.printStackTrace();
              }
      
              System.out.println("你在try我在catch,无论我都承受并静静的处理,到那时再来期待我们finally!");
              // 当程序执行过程中没有发生异常时的执行流程:1 2  5 6  世界上...
              // 当程序执行过程中发生异常又没有手动处理空指针异常时的执行流程:1 3 4  5  空指针异常导致程序终止
              // 当程序执行过程中发生异常并且手动处理空指针异常时的执行流程: 1 3 4 5 世界上...
      
              // 手动处理异常和没有处理的区别:代码是否可以继续向下执行
          }
      }
      
  • finally的使用

    public class ExceptionFinallyTest {
    
        // 笔试考点
        public static int test() {
            try {
                int[] arr = new int[5];
                System.out.println(arr[5]);
                return 0;
            } catch (ArrayIndexOutOfBoundsException e) {
                e.printStackTrace();
                return 1;// 结束方法返回数据前会先执行finally
            } finally {
                return 2; // 提交结束方法并返回数据
            }
        }
    
        public static void main(String[] args) {
    
            try {
                int ia = 10;
                int ib = 0;
                System.out.println(ia / ib);
            } catch (ArithmeticException e) {
                e.printStackTrace();
    
                String str1 = null;
                //str1.length(); // 会发生空指针异常
    
            } finally {
                System.out.println("无论是否发生异常都记得来执行我哦!");  // 依然是执行
            }
    
            System.out.println("Over!"); //若有空指针异常且未处理这里就不执行了
    
            System.out.println("----------------------------------------");
            int test = test();
            System.out.println("test = " + test); // 2
        }
    }
    
  • 异常的抛出

    不建议在main方法中抛出异常 JVM负担很重

    //若一个方法内部又以递进方式分别调用了好几个其它方法,则建议这些方法内可以使用抛出的方法处理到最后一层进行捕获方式处理
    public class ExceptionThrowsTest {
    
        public static void show() throws IOException {
            FileInputStream fis = new FileInputStream("d:/a.txt");
            System.out.println("我想看看你抛出异常后是否继续向下执行???");
            fis.close();
        }
    
        public static void test1() throws IOException {
            show();
        }
    
        public static void test2() throws IOException {
            test1();
        }
    
        public static void test3() throws IOException {
            test2();
        }
    
        // 不建议在main方法中抛出异常   JVM负担很重
        public static void main(String[] args) /*throws IOException*/ {
            try {
                show();
            } catch (IOException e) {
                e.printStackTrace();
            }
    
            System.out.println("------------------------------------");
            try {
                test3();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    //若父类中被重写的方法没有抛出异常时,则子类中重写的方法只能进行异常的捕获处理
    public class SubExceptionMethod extends ExceptionMethod {
    
        @Override
        public void show() throws IOException {} // 子类重写的方法可以抛出和父类中方法一样的异常
        //public void show() throws FileNotFoundException {}  // 子类重写的方法可以抛出更小的异常
        //public void show() {} // 子类可以不抛出异常
        //public void show() throws ClassNotLoadedException {} // 不可以抛出平级不一样的异常
        //public void show() throws Exception {} // 不可以抛出更大的异常
    }
    
  • 自定义异常类使用异常类

    IDEA序列化版本号添加:实现Serializable接口,类名处Alt+Enter

    //自定义异常类要继承父类Exception
    public class AgeException extends Exception {
    	//自定义异常需要定义一个序列化版本号常量
        static final long serialVersionUID = 7818375828146090155L; // 序列化的版本号  与序列化操作有关系
    
        //自定义异常需要定义空参和带参构造方法
        public AgeException() {
        }
    
        public AgeException(String message) {
            super(message);
        }
    }
    
    public class Person {
        private int age;
        private String name;
    
        public Person() {
        }
    
        public Person(int age, String name) {
            setAge(age);
            setName(name);
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            if (age > 0 && age < 150) {
                this.age = age;
            } else {
                try {
                    throw new AgeException();
                } catch (AgeException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    
  • 异常使用注意事项

2. File类

  • File类文件操作

    // 1.构造File类型的对象与d:/a.txt文件关联
    File f1 = new File("d:/a.txt");
    // 2.若文件存在则获取文件的相关特征信息并打印后删除文件
    if (f1.exists()) {
        System.out.println("文件的名称是:" + f1.getName());
        System.out.println("文件的大小是:" + f1.length());
        Date d1 = new Date(f1.lastModified());
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("文件的最后一次修改时间:" + sdf.format(d1));
        // 绝对路径: 主要指以根目录开始的路径信息,如:c:/  d:/   /..
        // 相对路径: 主要指以当前目录所在位置开始的路径信息,如:./  ../   相对路径
        System.out.println("文件的绝对路径信息是:" + f1.getAbsolutePath());
        System.out.println(f1.delete()? "文件删除成功": "文件删除失败");
    } else {
        // 3.若文件不存在则创建新的空文件
        System.out.println(f1.createNewFile()? "文件创建成功": "文件创建失败!");
    }
    
    System.out.println("---------------------------------------------------------");
    
  • File类实现目录的操作

    // 4.实现目录的删除和创建
    File f2 = new File("d:/捣乱/猜猜我是谁/你猜我猜不猜/死鬼");
    if (f2.exists()) {
        System.out.println("目录名称是:" + f2.getName());
        //delete方法用于删除指定文件或指定空文件夹
        System.out.println(f2.delete()? "目录删除成功": "目录删除失败");
    } else {
        //System.out.println(f2.mkdir()? "目录创建成功": "目录创建失败");   // 创建单层目录
        System.out.println(f2.mkdirs()? "目录创建成功": "目录创建失败");   // 创建多层目录
    }
    
    System.out.println("---------------------------------------------------------");
    
  • File类实现目录中的内容遍历对目录中所有内容获取的同时过滤

    // 5.实现将指定目录中的所有内容打印出来
    File f3 = new File("d:/捣乱");
    // 获取目录f3下的所有内容并记录到一维数组中
    File[] filesArray = f3.listFiles();
    // 遍历数组
    for (File tf: filesArray) {
        String name = tf.getName();
        // 判断是否为文件,若是则直接打印文件名称
        if (tf.isFile()) {
            System.out.println(name);
        }
        // 若是目录,则使用[]将目录名称括起来
        if (tf.isDirectory()) {
            System.out.println("[" + name + "]");
        }
    }
    
    System.out.println("---------------------------------------------------------");
    // 6.实现目录中所有内容获取的同时进行过滤
    // 匿名内部类的语法格式:接口/父类类型 引用变量名 = new 接口/父类类型() { 方法的重写 };
    /*FileFilter fileFilter = new FileFilter() {
                @Override
                public boolean accept(File pathname) {
                    // 若文件名是以.avi为结尾,则返回true表示保留   否则返回false就是表示丢弃
                    return pathname.getName().endsWith(".avi");
                }
            };*/
    // Lambda表达式的格式:(参数列表) -> {方法体}
    FileFilter fileFilter = (File pathname) -> {return pathname.getName().endsWith(".avi");};
    File[] filesArray2 = f3.listFiles(fileFilter);
    for (File tf : filesArray2) {
        System.out.println(tf);
    }
    
    System.out.println("---------------------------------------------------------");
    
  • 递归实现对指定文件目录及子目录中所有内容打印

    // 自定义成员方法实现指定目录以及子目录中所有内容的打印
    public static void show(File file) {
        // 获取目录f3下的所有内容并记录到一维数组中
        File[] filesArray = file.listFiles();
        // 遍历数组
        for (File tf: filesArray) {
            String name = tf.getName();
            // 判断是否为文件,若是则直接打印文件名称
            if (tf.isFile()) {
                System.out.println(name);
            }
            // 若是目录,则使用[]将目录名称括起来
            if (tf.isDirectory()) {
                System.out.println("[" + name + "]");
                show(tf);
            }
        }
    }
    

二、IO流

1. IO流的框架结构

2. 各种相关流

  • 字符流:FileWriter类

    java.io.FileWriter类主要用于将文本内容写入到文本文件,构造方法传参的文件可以不存在

    public class FileWriterTest {
    
        public static void main(String[] args) {
            // 选中代码后可以使用 ctrl+alt+t 来生成异常的捕获代码等
            FileWriter fw = null;
    
            try {
                // 1.构造FileWrite类型的对象与d:/a.txt文件关联
                // 若文件不存在,该流会自动创建新的空文件
                // 若文件存在,该流会清空文件中的原有内容
                fw = new FileWriter("d:/a.txt");
                // 以追加的方式创建对象去关联文件
                // 若文件不存在则自动创建新的空文件,若文件存在则保留原有数据内容
                //fw = new FileWriter("d:/a.txt", true);
                // 2.通过流对象写入数据内容  每当写入一个字符后则文件中的读写位置向后移动一位
                fw.write('a');
    
                // 准备一个字符数组
                char[] cArr = new char[]{'h', 'e', 'l', 'l', 'o'};
                // 将字符数组中的一部分内容写入进去
                fw.write(cArr, 1, 3);  // ell
                // 将整个字符数组写进去
                fw.write(cArr); // hello
    
                // 刷新流
                fw.flush();//开发中如需反复大量读写,需要刷新流
                System.out.println("写入数据成功!");
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 3.关闭流对象并释放有关的资源
                if (null != fw) {
                    try {
                        fw.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
  • 字符流:FileReader类

    java.io.FileReader类主要用于从文本文件读取文本数据内容

    该类中int read()方法空参时读取单个字符的数据并返回(打印需int->char强转),带参时返回值为读取到的字符个数

    public class FileReaderTest {
    
        public static void main(String[] args) {
            FileReader fr = null;
    
            try {
                // 1.构造FileReader类型的对象与d:/a.txt文件关联
                //fr = new FileReader("d:/a.txt");
                fr = new FileReader("d:/b.txt");
                // 2.读取数据内容并打印
                /*
                int res = fr.read();
                System.out.println("读取到的单个字符是:" + (char)res); // 'a'
                 */
                int res = 0;
                while ((res = fr.read()) != -1) {
                    System.out.println("读取到的单个字符是:" + (char)res + ",对应的编号是:" + res);
                }
    
                // 准备一个字符数组来保存读取到的数据内容
    //            char[] cArr = new char[5];
                // 期望读满字符数组中的一部分空间,也就是读取3个字符放入数组cArr中下标从1开始的位置上
                /*int res = fr.read(cArr, 1, 3);
                System.out.println("实际读取到的字符个数是:" + res); // 3
                for (char cv : cArr) {
                    System.out.println("读取到的单个字符是:" + (char)cv); // 啥也没有 a e l 啥也没有
                }*/
    
                // 期望读满整个字符数组
                /*int res = fr.read(cArr);
                System.out.println("实际读取到的字符个数是:" + res); // 5
                for (char cv : cArr) {
                    System.out.println("读取到的单个字符是:" + (char)cv); // a e l l h
                }*/
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 3.关闭流对象并释放有关的资源
                if (null != fr) {
                    try {
                        fr.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
  • 使用FileReader和FileWriter进行文件的拷贝(字符流只能用于拷贝文本文件)

    public class FileCharCopyTest {
    
        public static void main(String[] args) {
            FileReader fr = null;
            FileWriter fw = null;
    
            try {
                // 1.创建FileReader类型的对象与d:/a.txt文件关联
                fr = new FileReader("d:/a.txt");
                //fr = new FileReader("d:/03 IO流的框架图.png");
                // 2.创建FileWriter类型的对象与d:/b.txt文件关联
                fw = new FileWriter("d:/b.txt");
                //fw = new FileWriter("d:/IO流的框架图.png");   拷贝图片文件失败!!!
                // 3.不断地从输入流中读取数据内容并写入到输出流中
                System.out.println("正在玩命地拷贝...");
                int res = 0;
                while ((res = fr.read()) != -1) {
                    fw.write(res);
                }
                System.out.println("拷贝文件成功!");
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 4.关闭流对象并释放有关的资源
                if (null != fw) {
                    try {
                        fw.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (null != fr) {
                    try {
                        fr.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
  • 字节流:FileOutputStream类

    java.io.FileOutputStream类主要用于将图像数据之类的原始字节流写入到输出流中,构造方法传参的文件可以不存在

  • 字节流:FileInputStream类

    java.io.FileInputStream类主要用于从输入流中以字节流的方式读取图像数据等

  • 使用FileOutputStream和FileInputStream进行文件的拷贝(3种方式)

    public class FileByteCopyTest {
    
        public static void main(String[] args) {
    
            // 获取当前系统时间距离1970年1月1日0时0分0秒的毫秒数
            long g1 = System.currentTimeMillis();
    
            FileInputStream fis = null;
            FileOutputStream fos = null;
    
            try {
                // 1.创建FileInputStream类型的对象与d:/03 IO流的框架图.png文件关联
                //fis = new FileInputStream("d:/03 IO流的框架图.png");
                fis = new FileInputStream("d:/02_IO流的框架结构.mp4");
                // 2.创建FileOutputStream类型的对象与d:/IO流的框架图.png文件关联
                //fos = new FileOutputStream("d:/IO流的框架图.png");
                fos = new FileOutputStream("d:/IO流的框架结构.mp4");
                // 3.不断地从输入流中读取数据内容并写入到输出流中
                System.out.println("正在玩命地拷贝...");
                // 方式一:以单个字节为单位进行拷贝,也就是每次读取一个字节后再写入一个字节
                // 缺点:文件稍大时,拷贝的效率很低
                /*int res = 0;
                while ((res = fis.read()) != -1) {
                    fos.write(res);
                }*/
                // 方式二:准备一个和文件大小一样的缓冲区,一次性将文件中的所有内容取出到缓冲区然后一次性写入进去
                // 缺点:若文件过大时,无法申请和文件大小一样的缓冲区,真实物理内存不足
                /*int len = fis.available();
                System.out.println("获取到的文件大小是:" + len);
                byte[] bArr = new byte[len];
                int res = fis.read(bArr);
                System.out.println("实际读取到的文件大小是:" + res);
                fos.write(bArr);*/
                // 方式三:准备一个相对适当的缓冲区,分多次将文件拷贝完成
                byte[] bArr = new byte[1024];
                int res = 0;
                while ((res = fis.read(bArr)) != -1) {
                    fos.write(bArr, 0, res);
                }
    
                System.out.println("拷贝文件成功!");
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 4.关闭流对象并释放有关的资源
                if (null != fos) {
                    try {
                        fos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (null != fis) {
                    try {
                        fis.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
    
            long g2 = System.currentTimeMillis();
            System.out.println("使用文件流拷贝视频文件消耗的时间为:" + (g2-g1));  // 165
        }
    }
    
  • 字节流:BufferedOutputStream类

    java.io.BufferedOutputStream类主要用于描述缓冲输出流,此时不用为写入的每个字节调用底层系统

  • 字节流:BufferedInputStream类

    java.io.BufferedInputStream类主要用于描述缓冲输入流

    public class BufferedByteCopyTest {
    
        public static void main(String[] args) {
    
            // 获取当前系统时间距离1970年1月1日0时0分0秒的毫秒数
            long g1 = System.currentTimeMillis();
    
            BufferedInputStream bis = null;
            BufferedOutputStream bos = null;
    
            try {
                // 1.创建BufferedInputStream类型的对象与d:/02_IO流的框架结构.mp4文件关联
                bis = new BufferedInputStream(new FileInputStream("d:/02_IO流的框架结构.mp4"));
                // 2.创建BufferedOuputStream类型的对象与d:/IO流的框架结构.mp4文件关联
                bos = new BufferedOutputStream(new FileOutputStream("d:/IO流的框架结构.mp4"));
    
                // 3.不断地从输入流中读取数据并写入到输出流中
                System.out.println("正在玩命地拷贝...");
    
                byte[] bArr = new byte[1024];
                int res = 0;
                while ((res = bis.read(bArr)) != -1) {
                    bos.write(bArr, 0, res);
                }
    
                System.out.println("拷贝文件成功!");
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 4.关闭流对象并释放有关的资源
                if (null != bos) {
                    try {
                        bos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (null != bis) {
                    try {
                        bis.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
    
            long g2 = System.currentTimeMillis();
            System.out.println("使用缓冲区拷贝视频文件消耗的时间为:" + (g2-g1)); // 44
        }
    }
    
    
  • 字符流:BufferedWriter类

    java.io.BufferedWriter类主要用于写入单个字符、字符数组以及字符串到输出流中

  • 字符流:BufferedReader类

    java.io.BufferedReader类用于从输入流中读取单个字符、字符数组以及字符串

    public class BufferedCharCopyTest {
    
        public static void main(String[] args) {
            BufferedReader br = null;
            BufferedWriter bw = null;
    
            try {
                // 1.创建BufferedReader类型的对象与d:/a.txt文件关联
                br = new BufferedReader(new FileReader("d:/a.txt"));
                // 2.创建BufferedWriter类型的对象与d:/b.txt文件关联
                bw = new BufferedWriter(new FileWriter("d:/b.txt"));
                // 3.不断地从输入流中读取一行字符串并写入到输出流中
                System.out.println("正在玩命地拷贝...");
                String str = null;
                while ((str = br.readLine()) != null) {
                    bw.write(str);
                    bw.newLine(); // 当前系统中的行分隔符是:\r\n
                }
                System.out.println("拷贝文件成功!");
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 4.关闭流对象并释放有关的资源
                if (null != bw) {
                    try {
                        bw.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (null != br) {
                    try {
                        br.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
  • 字节流(OutputStream):PrintStream类

    java.io.PrintStream类主要用于更加方便地打印各种数据内容

  • 字符流(Writer):PrintWriter类

    java.io.PrintWriter类主要用于将对象的格式化形式打印到文本输出流

  • 字符流:OutputStreamWriter类

    java.io.OutputStreamWriter类主要用于实现从字符流到字节流的转换

  • 字符流:InputStreamReader类

    java.io.InputStreamReader类主要用于实现从字节流到字符流的转换

    /*不断地提示用户输入要发送的内容,若发送的内容是"bye"则聊天结束,否则将用户输入的内容写入到文件d:/a.txt中。
    要求使用BufferedReader类来读取键盘的输入 System.in代表键盘输入
    要求使用PrintStream类负责将数据写入文件*/
    public class PrintStreamChatTest {
    
        public static void main(String[] args) {
    
            // 由手册可知:构造方法需要的是Reader类型的引用,但Reader类是个抽象类,实参只能传递子类的对象  字符流
            // 由手册可知: System.in代表键盘输入, 而且是InputStream类型的 字节流
            BufferedReader br = null;
            PrintStream ps = null;
            try {
                br = new BufferedReader(new InputStreamReader(System.in));
                ps = new PrintStream(new FileOutputStream("d:/a.txt", true));
    
                // 声明一个boolean类型的变量作为发送方的代表
                boolean flag = true;
    
                while(true) {
                    // 1.提示用户输入要发送的聊天内容并使用变量记录
                    System.out.println("请" + (flag? "张三": "李四") + "输入要发送的聊天内容:");
                    String str = br.readLine();
                    // 2.判断用户输入的内容是否为"bye",若是则聊天结束
                    if ("bye".equals(str)) {
                        System.out.println("聊天结束!");
                        break;
                    }
                    // 3.若不是则将用户输入的内容写入到文件d:/a.txt中
                    //else {
                    // 获取当前系统时间并调整格式
                    Date d1 = new Date();
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                    ps.println(sdf.format(d1) + (flag?" 张三说:":" 李四说:") + str);
                    //}
                    flag = !flag;
                }
                ps.println(); // 写入空行 与之前的聊天记录隔开
                ps.println();
                ps.println();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 4.关闭流对象并释放有关的资源
                if (null != ps) {
                    ps.close();
                }
                if (null != br) {
                    try {
                        br.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
  • 字符编码

  • 字节流:DataOutputStream类

    java.io.DataOutputStream类主要用于以适当的方式将基本数据类型写入输出流中

    public class DataOutputStreamTest {
    
        public static void main(String[] args) {
            DataOutputStream dos = null;
    
            try {
                // 1.创建DataOutputStream类型的对象与d:/a.txt文件关联
                dos = new DataOutputStream(new FileOutputStream("d:/a.txt"));
                // 2.准备一个整数数据66并写入输出流
                // 66: 0000 0000 ... 0100 0010    =>   B
                int num = 66;
                //dos.writeInt(num);  // 写入4个字节
                dos.write(num);       // 写入1个字节
                System.out.println("写入数据成功!");
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 3.关闭流对象并释放有关的资源
                if (null != dos) {
                    try {
                        dos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
  • 字节流:DataInputStream类

    java.io.DataInputStream类主要用于从输入流中读取基本数据类型的数据

    public class DataInputStreamTest {
    
        public static void main(String[] args) {
            DataInputStream dis = null;
    
            try {
                // 1.创建DataInputStream类型的对象与d:/a.txt文件关联
                dis = new DataInputStream(new FileInputStream("d:/a.txt"));
                // 2.从输入流中读取一个整数并打印
                //int res = dis.readInt(); // 读取4个字节
                int res = dis.read();      // 读取1个字节
                System.out.println("读取到的整数数据是:" + res); // 66
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 3.关闭流对象并释放有关的资源
                if (null != dis) {
                    try {
                        dis.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
  • 字节流:ObjectOutputStream类

    java.io.ObjectOutputStream类主要用于将一个对象的所有内容整体写入到输出流中

    序列化:主要指将一个对象需要存储的相关信息有效组织成字节序列的转化过程

    只能将支持 java.io.Serializable 接口的对象写入流中

    类通过实现 java.io.Serializable 接口以启用其序列化功能,同时需要在类中生成序列化版本号

    使用transient关键字可以使某些成员变量不参与序列化操作

    当希望将多个对象写入文件时,通常建议将多个对象放入一个集合中,然后将集合这个整体看做一个对象写入输出流中,此时只需要调用一次readObject方法就可以将整个集合的数据读取出来,从而避免了通过返回值进行是否达到文件末尾的判断

    public class User implements java.io.Serializable {
        private static final long serialVersionUID = -5814716593800822421L;
    
        private String userName;  // 用户名
        private String password;  // 密码
        private transient String phoneNum;  // 手机号  表示该成员变量不参与序列化操作, 此变量不会写入输出流中
    
        public User() {
        }
    
        public User(String userName, String password, String phoneNum) {
            this.userName = userName;
            this.password = password;
            this.phoneNum = phoneNum;
        }
    
        public String getUserName() {
            return userName;
        }
    
        public void setUserName(String userName) {
            this.userName = userName;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        public String getPhoneNum() {
            return phoneNum;
        }
    
        public void setPhoneNum(String phoneNum) {
            this.phoneNum = phoneNum;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "userName='" + userName + '\'' +
                    ", password='" + password + '\'' +
                    ", phoneNum='" + phoneNum + '\'' +
                    '}';
        }
    }
    
    public class ObjectOutputStreamTest {
    
        public static void main(String[] args) {
            ObjectOutputStream oos = null;
    
            try {
                // 1.创建ObjectOutputStream类型的对象与d:/a.txt文件关联
                oos = new ObjectOutputStream(new FileOutputStream("d:/a.txt"));
                // 2.准备一个User类型的对象并初始化
                User user = new User("qidian", "123456", "13511258688");
                // 3.将整个User类型的对象写入输出流
                oos.writeObject(user);
                System.out.println("写入对象成功!");
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 4.关闭流对象并释放有关的资源
                if (null != oos) {
                    try {
                        oos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
  • 字节流:ObjectInputStream类

    java.io.ObjectInputStream类主要用于从输入流中一次性将对象整体读取出来

    反序列化:指将有效组织的字节序列恢复为一个对象及相关信息的转化过程

    序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常(InvalidCastException)

    public class ObjectInputStreamTest {
    
        public static void main(String[] args) {
            ObjectInputStream ois = null;
    
            try {
                // 1.创建ObjectInputStream类型的对象与d:/a.txt文件关联
                ois = new ObjectInputStream(new FileInputStream("d:/a.txt"));
                // 2.从输入流中读取一个对象并打印
                Object obj = ois.readObject();
                System.out.println("读取到的对象是:" + obj); // qidian 123456 13511258688  null
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } finally {
                // 3.关闭流对象并释放有关的资源
                if (null != ois) {
                    try {
                        ois.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
  • RandomAccessFile类

    java.io.RandomAccessFile类主要支持对随机访问文件的读写操作

    read方法读取起始位置默认是第一个字符

    public class RandomAccessFileTest {
    
        public static void main(String[] args) {
            RandomAccessFile raf = null;
    
            try {
                // 1.创建RandomAccessFile类型的对象与d:/a.txt文件关联
                raf = new RandomAccessFile("d:/a.txt", "rw");
                // 2.对文件内容进行随机读写操作
                // 设置距离文件开头位置的偏移量,从文件开头位置向后偏移3个字节    aellhello
                raf.seek(3);
                int res = raf.read();
                System.out.println("读取到的单个字符是:" + (char)res); // a l
                res = raf.read();
                System.out.println("读取到的单个字符是:" + (char)res); // h 指向了e
                raf.write('2'); // 执行该行代码后覆盖了字符'e'
                System.out.println("写入数据成功!");
    
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 3.关闭流对象并释放有关的资源
                if (null != raf) {
                    try {
                        raf.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    

三、多线程

1. 线程的创建和启动

  • Thread类中的run方法

    public class ThreadTest {
    
        public static void main(String[] args) {
    
            // 1.使用无参方式构造Thread类型的对象
            // 由源码可知:Thread类中的成员变量target的数值为null。
            Thread t1 = new Thread();
            // 2.调用run方法进行测试
            // 由源码可知:由于成员变量target的数值为null,因此条件if (target != null)不成立,跳过{}中的代码不执行
            //  而run方法中除了上述代码再无代码,因此证明run方法确实啥也不干
            t1.run();
            // 3.打印一句话
            System.out.println("我想看看你到底是否真的啥也不干!");
        }
    }
    
  • 线程创建和启动方式(2种)

    1. 自定义类继承Thread类并重写run方法,然后创建该类的对象调用start方法

      Thread类调用run方法和start方法的区别:

      调用run方法测试,本质上就是相当于对普通成员方法的调用,因此执行流程就是run方法的代码执行完毕后才能继续向下执行;

      start方法用于启动线程,Java虚拟机会自动调用该线程类中的run方法,相当于又启动了一个线程,加上执行main方法的线程是两个线程

      public class SubThreadRun extends Thread {
      
          @Override
          public void run() {
              // 打印1 ~ 20之间的所有整数
              for (int i = 1; i <= 20; i++) {
                  System.out.println("run方法中:i = " + i); // 1 2 ... 20
              }
          }
      }
      
      public class SubThreadRunTest {
      
          public static void main(String[] args) {
      
              // 1.声明Thread类型的引用指向子类类型的对象
              Thread t1 = new SubThreadRun();
              // 2.直接调用run方法测试,本质上就是相当于对普通成员方法的调用,因此执行流程就是run方法的代码执行完毕后才能继续向下执行
              //t1.run();
              // start方法用于启动线程,Java虚拟机会自动调用该线程类中的run方法
              // 相当于又启动了一个线程,加上执行main方法的线程是两个线程
              t1.start();
      
              // 打印1 ~ 20之间的所有整数
              for (int i = 1; i <= 20; i++) {
                  System.out.println("-----------------main方法中:i = " + i); // 1 2 ... 20
              }
          }
      }
      
    2. 自定义类实现Runnable接口并重写run方法,创建该类的对象作为实参来构造Thread类型的对象,然后使用Thread类型的对象调用start方法

      public class SubRunnableRun implements Runnable {
          @Override
          public void run() {
              // 打印1 ~ 20之间的所有整数
              for (int i = 1; i <= 20; i++) {
                  System.out.println("run方法中:i = " + i); // 1 2 ... 20
              }
          }
      }
      
      public class SubRunnableRunTest {
      
          public static void main(String[] args) {
      
              // 1.创建自定义类型的对象,也就是实现Runnable接口类的对象
              SubRunnableRun srr = new SubRunnableRun();
              // 2.使用该对象作为实参构造Thread类型的对象
              // 由源码可知:经过构造方法的调用之后,Thread类中的成员变量target的数值为srr。
              Thread t1 = new Thread(srr);
              // 3.使用Thread类型的对象调用start方法
              // 若使用Runnable引用构造了线程对象,调用该方法(run)时最终调用接口中的版本
              // 由run方法的源码可知:if (target != null) {
              //                         target.run();
              //                    }
              // 此时target的数值不为空这个条件成立,执行target.run()的代码,也就是srr.run()的代码
              t1.start();
              //srr.start();  Error
      
              // 打印1 ~ 20之间的所有整数
              for (int i = 1; i <= 20; i++) {
                  System.out.println("-----------------main方法中:i = " + i); // 1 2 ... 20
              }
          }
      }
      
  • 匿名内部类方式实现线程创建和启动

    使用匿名内部类可以实现以上两种方式的线程创建,使用Lambda表达式可简化格式

    public class ThreadNoNameTest {
    
        public static void main(String[] args) {
    
            // 匿名内部类的语法格式:父类/接口类型 引用变量名 = new 父类/接口类型() { 方法的重写 };
            // 1.使用继承加匿名内部类的方式创建并启动线程
            /*Thread t1 = new Thread() {
                @Override
                public void run() {
                    System.out.println("张三说:在吗?");
                }
            };
            t1.start();*/
            new Thread() {
                @Override
                public void run() {
                    System.out.println("张三说:在吗?");
                }
            }.start();
    
            // 2.使用实现接口加匿名内部类的方式创建并启动线程
            /*Runnable ra = new Runnable() {
                @Override
                public void run() {
                    System.out.println("李四说:不在。");
                }
            };
            Thread t2 = new Thread(ra);
            t2.start();*/
            /*new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("李四说:不在。");
                }
            }).start();*/
            
            
            // Java8开始支持lambda表达式: (形参列表)->{方法体;}
            /*Runnable ra = ()-> System.out.println("李四说:不在。");
            new Thread(ra).start();*/
            
            
    		// lambda表达式省略引用变量版本
            new Thread(()-> System.out.println("李四说:不在。")).start();
        }
    }
    

2. 线程编号和名称

  • 继承方式获取线程名称和编号 与 设置线程名称

    public class ThreadIdNameTest extends Thread {
    
        public ThreadIdNameTest(String name) {
            super(name); // 表示调用父类的构造方法
        }
    
        @Override
        public void run() {
            System.out.println("子线程的编号是:" + getId() + ",名称是:" + getName()); // 14  Thread-0 guanyu
            // 修改名称为"zhangfei"
            setName("zhangfei");
            System.out.println("修改后子线程的编号是:" + getId() + ",名称是:" + getName()); // 14  zhangfei
        }
    
        public static void main(String[] args) {
    
            ThreadIdNameTest tint = new ThreadIdNameTest("guanyu");
            tint.start();
    
            // 获取当前正在执行线程的引用,当前正在执行的线程是主线程,也就是获取主线程的引用
            Thread t1 = Thread.currentThread();
            System.out.println("主线程的编号是:" + t1.getId() + ", 名称是:" + t1.getName());
        }
    }
    
  • 实现方式获取线程名称和编号 与 设置线程名称

    public class RunnableIdNameTest implements Runnable {
        @Override
        public void run() {
            // 获取当前正在执行线程的引用,也就是子线程的引用
            Thread t1 = Thread.currentThread();
            System.out.println("子线程的编号是:" + t1.getId() + ", 名称是:" + t1.getName()); // 14 guanyu
            t1.setName("zhangfei");
            System.out.println("修改后子线程的编号是:" + t1.getId() + ", 名称是:" + t1.getName()); // 14 zhangfei
        }
    
        public static void main(String[] args) {
    
            RunnableIdNameTest rint = new RunnableIdNameTest();
            //Thread t2 = new Thread(rint);
            Thread t2 = new Thread(rint, "guanyu");
            t2.start();
    
            // 获取当前正在执行线程的引用,当前正在执行的线程是主线程,也就是获取主线程的引用
            Thread t1 = Thread.currentThread();
            System.out.println("主线程的编号是:" + t1.getId() + ", 名称是:" + t1.getName());
        }
    }
    

3. Thread类中其他常用方法

  • 静态方法static void sleep(times)

    public class ThreadSleepTest extends Thread {
        // 声明一个布尔类型的变量作为循环是否执行的条件
        private boolean flag = true;
    
        // 子类中重写的方法不能抛出更大的异常
        @Override
        public void run() {
            // 每隔一秒获取一次系统时间并打印,模拟时钟的效果
            while (flag) {
                // 获取当前系统时间并调整格式打印
    //            LocalDateTime.now();
                Date d1 = new Date();
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                System.out.println(sdf.format(d1));
    
                // 睡眠1秒钟
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public static void main(String[] args) {
    
            ThreadSleepTest tst = new ThreadSleepTest();
            tst.start();
    
            // 主线程等待5秒后结束子线程
            System.out.println("主线程开始等待...");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 停止子线程  过时  不建议使用
            //tst.stop();
            tst.flag = false;
            System.out.println("主线程等待结束!");
        }
    }
    
  • 线程优先级获取和设置

    int getPriority() 获取线程的优先级

    void setPriority(int newPriority)修改线程的优先级

    优先级越高的线程不一定先执行,但该线程获取到时间片的机会会更多一些

    需要在start方法前设置线程的优先级

    public class ThreadPriorityTest extends Thread {
        @Override
        public void run() {
            //System.out.println("子线程的优先级是:" + getPriority()); // 5  10  优先级越高的线程不一定先执行。
            for (int i = 0; i < 20; i++) {
                System.out.println("子线程中:i = " + i);
            }
        }
    
        public static void main(String[] args) {
    
            ThreadPriorityTest tpt = new ThreadPriorityTest();
            // 设置子线程的优先级
            tpt.setPriority(Thread.MAX_PRIORITY);
            tpt.start();
    
            Thread t1 = Thread.currentThread();
            //System.out.println("主线程的优先级是:" + t1.getPriority()); // 5 普通的优先级
            for (int i = 0; i < 20; i++) {
                System.out.println("--主线程中:i = " + i);
            }
        }
    
    }
    
  • 子线程等待

    void join()主线程会等待该线程终止

    void join(long millis)主线程会以参数指定的毫秒数等待该线程

    public class ThreadJoinTest extends Thread {
        @Override
        public void run() {
            // 模拟倒数10个数的效果
            System.out.println("倒计时开始...");
            for (int i = 10; i > 0; i--) {
                System.out.println(i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("新年快乐!");
        }
    
        public static void main(String[] args) {
    
            ThreadJoinTest tjt = new ThreadJoinTest();
            tjt.start();
    
            // 主线程开始等待
            System.out.println("主线程开始等待...");
            try {
                // 表示当前正在执行的线程对象等待调用线程对象,也就是主线程等待子线程终止
                //tjt.join();
                tjt.join(5000); // 最多等待5秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //System.out.println("终于等到你,还好没放弃!");
            System.out.println("可惜不是你,陪我到最后!");
        }
    }
    /*
    主线程开始等待...
    倒计时开始...
    10
    9
    8
    7
    6
    可惜不是你,陪我到最后!
    5
    4
    3
    2
    1
    新年快乐!
    */
    
  • 守护线程

    boolean isDaemon()用于判断是否为守护线程

    void setDaemon(boolean on)用于设置线程为守护线程

    当子线程是守护线程时,当主线程结束后,则子线程随之结束,但子线程不是立刻结束

    public class ThreadDaemonTest extends Thread {
        @Override
        public void run() {
            //System.out.println(isDaemon()? "该线程是守护线程": "该线程不是守护线程"); // 默认不是守护线程
            // 当子线程不是守护线程时,虽然主线程先结束了,但是子线程依然会继续执行,直到打印完毕所有数据为止
            // 当子线程是守护线程时,当主线程结束后,则子线程随之结束
            for (int i = 0; i < 50; i++) {
                System.out.println("子线程中:i = " + i);
            }
        }
    
        public static void main(String[] args) {
    
            ThreadDaemonTest tdt = new ThreadDaemonTest();
            // 必须在线程启动之前设置子线程为守护线程
            tdt.setDaemon(true);
            tdt.start();
    
            for (int i = 0; i < 20; i++) {
                System.out.println("-------主线程中:i = " + i);
            }
        }
    }
    

4. 多个线程执行不同代码

  • 继承Thread类方式,创建子类对象

    public class SubThread1 extends Thread {
        @Override
        public void run() {
            // 打印1 ~ 100之间的所有奇数
            for (int i = 1; i <= 100; i += 2) {
                System.out.println("子线程一中: i = " + i);
            }
        }
    }
    
    public class SubThread2 extends Thread {
        @Override
        public void run() {
            // 打印1 ~ 100之间的所有偶数
            for (int i = 2; i <= 100; i += 2) {
                System.out.println("------子线程二中: i = " + i);
            }
        }
    }
    
    public class SubThreadTest {
    
        public static void main(String[] args) {
    
            SubThread1 st1 = new SubThread1();
            SubThread2 st2 = new SubThread2();
    
            st1.start();
            st2.start();
    
            System.out.println("主线程开始等待...");
            try {
                st1.join();
                st2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("主线程等待结束!");
        }
    }
    
  • 重写Runnable接口,将实现类对象作为参数传入Thread构造方法

    public class SubRunnable1 implements Runnable {
        @Override
        public void run() {
            // 打印1 ~ 100之间的所有奇数
            for (int i = 1; i <= 100; i += 2) {
                System.out.println("子线程一中: i = " + i);
            }
        }
    }
    
    public class SubRunnable2 implements Runnable {
        @Override
        public void run() {
            // 打印1 ~ 100之间的所有偶数
            for (int i = 2; i <= 100; i += 2) {
                System.out.println("------子线程二中: i = " + i);
            }
        }
    }
    
    public class SubRunnableTest {
    
        public static void main(String[] args) {
    
            SubRunnable1 sr1 = new SubRunnable1();
            SubRunnable2 sr2 = new SubRunnable2();
    
            Thread t1 = new Thread(sr1);
            Thread t2 = new Thread(sr2);
    
            t1.start();
            t2.start();
    
            System.out.println("主线程开始等待...");
            try {
                t1.join();
                t2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("主线程等待结束!");
        }
    }
    

5. 同步代码块、同步方法、Lock接口

  • 线程同步解决的问题:多个线程对同一个共享资源操作时会有线程不一致的问题

    解决线程同步的问题:同步代码块或同步方法

    同步代码块和同步方法的区别:同步代码块是部分的锁定,同步方法是对整个方法的锁定

    同步代码块和Lock接口实现类锁的区别:同步代码块是自动释放锁对象,Lock是手动释放锁对象;Lock可选锁的范围多,性能较好

    在Java语言中使用synchronized关键字来实现同步/对象锁机制从而保证线程执行的原子性

    原子性表示这段代码在程序运行过程中,像原子一样是最小单位 ,无法再被切割了

    静态方法可以被synchronized关键字修饰,效果等同于同步代码块中的锁对象是当前类的Class类对象(类.class)

    普通方法用synchronized关键字修饰,效果等同于同步代码块中使用this作为锁对象

  • 使用同步代码块的方式实现部分代码的锁定

     synchronized(类类型的引用) {//这里要实现代码块的同步,需要使用同一个引用变量,不能是new新的对象
     	编写所有需要锁定的代码;
     }
    

    使用Runnable接口和Thread类:

    1. Thread类,需要创建两个子类对象,此时synchronized代码块中要传入一个静态成员变量的引用

    2. Runnable接口,只需创建一个实现类对象传入Thread构造方法中,此时synchronized中只需传入普通引用变量即可

  • 示例Code

    1. 使用Runnable接口实现类创建线程及同步

      同一个java文件中只允许存在一个public class,但可以有多个class

      public class AccountRunnableTest implements Runnable {
         private int balance; // 用于描述账户的余额
         private Demo dm = new Demo();
         private ReentrantLock lock = new ReentrantLock();  // 准备了一把锁
      
         public AccountRunnableTest() {
         }
      
         public AccountRunnableTest(int balance) {
             this.balance = balance;
         }
      
         public int getBalance() {
             return balance;
         }
      
         public void setBalance(int balance) {
             this.balance = balance;
         }
      
         @Override
         public /*synchronized*/ void run() {
             // 开始加锁
             lock.lock();
      
             // 由源码可知:最终是account对象来调用run方法,因此当前正在调用的对象就是account,也就是说this就是account
             //synchronized (this) { // ok
             System.out.println("线程" + Thread.currentThread().getName() + "已启动...");
             //synchronized (dm) { // ok
             //synchronized (new Demo()) { // 锁不住  要求必须是同一个对象
             // 1.模拟从后台查询账户余额的过程
             int temp = getBalance(); // temp = 1000  temp = 1000
             // 2.模拟取款200元的过程
             if (temp >= 200) {
                 System.out.println("正在出钞,请稍后...");
                 temp -= 200;  // temp = 800   temp = 800
                 try {
                     Thread.sleep(5000);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
                 System.out.println("请取走您的钞票!");
             } else {
                 System.out.println("余额不足,请核对您的账户余额!");
             }
             // 3.模拟将最新的账户余额写入到后台
             setBalance(temp); // balance = 800  balance = 800
             //}
             lock.unlock(); // 实现解锁
         }
      
         public static void main(String[] args) {
      
             AccountRunnableTest account = new AccountRunnableTest(1000);
             //AccountRunnableTest account2 = new AccountRunnableTest(1000);
             Thread t1 = new Thread(account);
             Thread t2 = new Thread(account);
             //Thread t2 = new Thread(account2);
             t1.start();
             t2.start();
      
             System.out.println("主线程开始等待...");
             try {
                 t1.join();
                 //t2.start(); // 也就是等待线程一取款操作结束后再启动线程二
                 t2.join();
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             System.out.println("最终的账户余额为:" + account.getBalance()); // 600  800
         }
      }
      
      class Demo{}//默认的访问权限,在同一个包下可以访问
      
    2. 使用Thread子类创建线程及同步

      静态方法可以被synchronized关键字修饰,效果等同于同步代码块中的锁对象是当前类的Class类对象(类.class)

      普通方法用synchronized关键字修饰,效果等同于同步代码块中使用this作为锁对象

      由于run方法是重写自Thread类,不可改为静态方法,所以需要将run方法中的内容封装成另一个静态方法调用

      Thread子类创建线程及同步时做线程同步的两种方案

      1. 使用同步方法在继承Thread类中做线程同步,直接给run方法加synchronized关键字不可行(创建的两个Thread子类对象,会生成两份this锁对象),需要给封装后的静态方法加synchronized相当于是使用了锁对象为当前类的Class类对象(类.class)

      2. 在run方法中使用同步代码块,锁对象使用一个类中的静态变量的引用

        (可以是上述class Demo{}定义的形式,变量定义private static Demo dm = new Demo()??

      public class AccountThreadTest extends Thread {
         private int balance; // 用于描述账户的余额
         private static Demo dm = new Demo(); // 隶属于类层级,所有对象共享同一个
      
         public AccountThreadTest() {
         }
      
         public AccountThreadTest(int balance) {
             this.balance = balance;
         }
      
         public int getBalance() {
             return balance;
         }
      
         public void setBalance(int balance) {
             this.balance = balance;
         }
      
         @Override
         public /*static*/ /*synchronized*/ void run() {
             /*System.out.println("线程" + Thread.currentThread().getName() + "已启动...");
              //synchronized (dm) { // ok
                  //synchronized (new Demo()) { // 锁不住  要求必须是同一个对象
                  // 1.模拟从后台查询账户余额的过程
                  int temp = getBalance(); // temp = 1000  temp = 1000
                  // 2.模拟取款200元的过程
                  if (temp >= 200) {
                      System.out.println("正在出钞,请稍后...");
                      temp -= 200;  // temp = 800   temp = 800
                      try {
                          Thread.sleep(5000);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                      System.out.println("请取走您的钞票!");
                  } else {
                      System.out.println("余额不足,请核对您的账户余额!");
                  }
                  // 3.模拟将最新的账户余额写入到后台
                  setBalance(temp); // balance = 800  balance = 800
              //}*/
             test();
         }
      
         public /*synchronized*/ static void test() {
             synchronized (AccountThreadTest.class) { // 该类型对应的Class对象,由于类型是固定的,因此Class对象也是唯一的,因此可以实现同步
                 System.out.println("线程" + Thread.currentThread().getName() + "已启动...");
                 //synchronized (dm) { // ok
                 //synchronized (new Demo()) { // 锁不住  要求必须是同一个对象
                 // 1.模拟从后台查询账户余额的过程
                 int temp = 1000; //getBalance(); // temp = 1000  temp = 1000
                 // 2.模拟取款200元的过程
                 if (temp >= 200) {
                     System.out.println("正在出钞,请稍后...");
                     temp -= 200;  // temp = 800   temp = 800
                     try {
                         Thread.sleep(5000);
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                     System.out.println("请取走您的钞票!");
                 } else {
                     System.out.println("余额不足,请核对您的账户余额!");
                 }
                 // 3.模拟将最新的账户余额写入到后台
                 //setBalance(temp); // balance = 800  balance = 800
             }
         }
      
         public static void main(String[] args) {
      
             AccountThreadTest att1 = new AccountThreadTest(1000);
             att1.start();
      
             AccountThreadTest att2 = new AccountThreadTest(1000);
             att2.start();
      
             System.out.println("主线程开始等待...");
             try {
                 att1.join();
                 //t2.start(); // 也就是等待线程一取款操作结束后再启动线程二
                 att2.join();
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             System.out.println("最终的账户余额为:" + att1.getBalance()); // 800
      
         }
      
      }
      

6. 线程之间的通信

  • Object类中的wait和notify方法

    wait方法和notify方法必须在锁定的代码中调用

    调用wait和notify的必须是锁对象(同步代码块中的引用变量),若为this可省

    public class ThreadCommunicateTest implements Runnable {
        private int cnt = 1;
    
        @Override
        public void run() {
            while (true) {
                synchronized (this) {
                    // 每当有一个线程进来后先大喊一声,调用notify方法
                    notify();
                    if (cnt <= 100) {
                        System.out.println("线程" + Thread.currentThread().getName() + "中:cnt = " + cnt);
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        cnt++;
                        // 当前线程打印完毕一个整数后,为了防止继续打印下一个数据,则调用wait方法
                        try {
                            wait(); // 当前线程进入阻塞状态,自动释放对象锁,必须在锁定的代码中调用
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        break;
                    }
                }
            }
        }
    
        public static void main(String[] args) {
    
            ThreadCommunicateTest tct = new ThreadCommunicateTest();
            Thread t1 = new Thread(tct);
            t1.start();
    
            Thread t2 = new Thread(tct);
            t2.start();
        }
    }
    
  • 生产者和消费者模型

    /**
     * 编程实现仓库类
     */
    public class StoreHouse {
        private int cnt = 0; // 用于记录产品的数量
    
        public synchronized void produceProduct() {
            notify();
            if (cnt < 10) {
                System.out.println("线程" + Thread.currentThread().getName() + "正在生产第" + (cnt+1) + "个产品...");
                cnt++;
            } else {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public synchronized void consumerProduct() {
            notify();
            if (cnt > 0) {
                System.out.println("线程" + Thread.currentThread().getName() + "消费第" + cnt + "个产品");
                cnt--;
            } else {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    /**
     * 编程实现生产者线程,不断地生产产品
     */
    public class ProduceThread extends Thread {
        // 声明一个仓库类型的引用作为成员变量,是为了能调用调用仓库类中的生产方法   合成复用原则
        private StoreHouse storeHouse;
        // 为了确保两个线程共用同一个仓库
        public ProduceThread(StoreHouse storeHouse) {
            this.storeHouse = storeHouse;
        }
    
        @Override
        public void run() {
            while (true) {
                storeHouse.produceProduct();
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    public class ConsumerThread extends Thread {
        // 声明一个仓库类型的引用作为成员变量,是为了能调用调用仓库类中的生产方法   合成复用原则
        private StoreHouse storeHouse;
        // 为了确保两个线程共用同一个仓库
        public ConsumerThread(StoreHouse storeHouse) {
            this.storeHouse = storeHouse;
        }
    
        @Override
        public void run() {
            while (true) {
                storeHouse.consumerProduct();
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    public class StoreHouseTest {
    
        public static void main(String[] args) {
    
            // 创建仓库类的对象
            StoreHouse storeHouse = new StoreHouse();
            // 创建线程类对象并启动
            ProduceThread t1 = new ProduceThread(storeHouse);
            ConsumerThread t2 = new ConsumerThread(storeHouse);
            t1.start();
            t2.start();
        }
    }
    

7. 使用Callable接口创建线程

  • 使用Callable接口创建和启动线程

    public class ThreadCallableTest implements Callable {
    
        @Override
        public Object call() throws Exception {
            // 计算1 ~ 10000之间的累加和并打印返回
            int sum = 0;
            for (int i = 1; i <= 10000; i++) {
                sum +=i;
            }
            System.out.println("计算的累加和是:" + sum); // 50005000
            return sum;
        }
    
        public static void main(String[] args) {
    
            ThreadCallableTest tct = new ThreadCallableTest();
            FutureTask ft = new FutureTask(tct);
            Thread t1 = new Thread(ft);
            t1.start();
            Object obj = null;
            try {
                obj = ft.get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
            System.out.println("线程处理方法的返回值是:" + obj); // 50005000
        }
    }
    

8. 线程池概念和使用

  • Executors类中的方法:

    static ExecutorService newFixedThreadPool(int nThreads)

    创建一个可重用固定线程数的线程池

  • ExecutorService类中的方法:

    Future submit(Callable task)

    执行任务和命令,通常用于执行Callable

    void shutdown()

    启动有序关闭

    public class ThreadPoolTest {
    
          public static void main(String[] args) {
    
              // 1.创建一个线程池
              ExecutorService executorService = Executors.newFixedThreadPool(10);
              // 2.向线程池中布置任务
              executorService.submit(new ThreadCallableTest());
              // 3.关闭线程池
              executorService.shutdown();
          }
    }
    

四、网络编程

1. 基于TCP协议的编程模型

  • TCP三次握手、四次挥手

  • 编程模型

    服务器:

    (1)创建ServerSocket类型的对象并提供端口号;

    (2)等待客户端的连接请求,调用accept()方法;

    (3)使用输入输出流进行通信;

    (4)关闭Socket;

    客户端:

    (1)创建Socket类型的对象并提供服务器的IP地址和端口号;

    (2)使用输入输出流进行通信;

    (3)关闭Socket;

  • 示例Code

    ServerSocket类中的方法:

    Socket accept() 侦听并接收到此套接字的连接请求

    public class ServerStringTest {
        public static void main(String[] args) {
            ServerSocket ss = null;
            Socket s = null;
            BufferedReader br = null;
    
            try {
                // 1.创建ServerSocket类型的对象并提供端口号
                ss = new ServerSocket(8888);
    
                // 2.等待客户端的连接请求,调用accept方法
                while(true) {
                    System.out.println("等待客户端的连接请求...");
                    // 当没有客户端连接时,则服务器阻塞在accept方法的调用这里
                    s = ss.accept();
                    System.out.println("客户端" + s.getInetAddress() + "连接成功!");
    
                    br = new BufferedReader(new InputStreamReader(s.getInputStream()));
                    String s1 = br.readLine();
                    System.out.println("the content was sent by Client is : " + s1);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 4.关闭Socket并释放有关的资源
                if (null != br) {
                    try {
                        br.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
    
                if (null != ss) {
                    try {
                        ss.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
    public class ClientStringTest {
    
        public static void main(String[] args) {
            Socket s = null;
            PrintStream ps = null;
            Scanner sc = null;
            BufferedReader br = null;
    
            try {
                // 1.创建Socket类型的对象并提供服务器的主机名和端口号
                s = new Socket("127.0.0.1", 8888);
                System.out.println("连接服务器成功!");
    
                // 2.使用输入输出流进行通信
                sc = new Scanner(System.in);
                ps = new PrintStream(s.getOutputStream());
                br = new BufferedReader(new InputStreamReader(s.getInputStream()));
    
                while(true) {
                    //Thread.sleep(10000);
                    // 实现客户端发送的内容由用户从键盘输入
                    System.out.println("请输入要发送的数据内容:");
                    String str1 = sc.next();
                    // 实现客户端向服务器发送字符串内容"hello"
                    //ps.println("hello");
                    ps.println(str1);
                    System.out.println("客户端发送数据内容成功!");
                    // 当发送的数据内容为"bye"时,则聊天结束
                    if ("bye".equalsIgnoreCase(str1)) {
                            System.out.println("聊天结束!");
                            break;
                    }
                    // 实现接收服务器发来的字符串内容并打印
                    String str2 = br.readLine();
                    System.out.println("服务器回发的消息是:" + str2);
                }
    
            } catch (IOException /*| InterruptedException*/ e) {
                e.printStackTrace();
            } finally {
                // 3.关闭Socket并释放有关的资源
                if (null != br) {
                    try {
                        br.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (null != ps) {
                    ps.close();
                }
                if (null != sc) {
                    sc.close();
                }
                if (null != s) {
                    try {
                        s.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    

    服务器采用多线程机制的实现

    public class ServerStringTest {
    
        public static void main(String[] args) {
            ServerSocket ss = null;
            Socket s = null;
    
            try {
                // 1.创建ServerSocket类型的对象并提供端口号
                ss = new ServerSocket(8888);
    
                // 2.等待客户端的连接请求,调用accept方法
                while(true) {
                    System.out.println("等待客户端的连接请求...");
                    // 当没有客户端连接时,则服务器阻塞在accept方法的调用这里
                    s = ss.accept();
                    System.out.println("客户端" + s.getInetAddress() + "连接成功!");
                    // 每当有一个客户端连接成功,则需要启动一个新的线程为之服务
                    new ServerThread(s).start();
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 4.关闭Socket并释放有关的资源
                if (null != ss) {
                    try {
                        ss.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
    public class ServerThread extends Thread {
        private Socket s;
    
        public ServerThread(Socket s) {
            this.s = s;
        }
    
        @Override
        public void run() {
            BufferedReader br = null;
            PrintStream ps = null;
    
            try {
                // 3.使用输入输出流进行通信
                br = new BufferedReader(new InputStreamReader(s.getInputStream()));
                ps = new PrintStream(s.getOutputStream());
    
                while(true) {
                    // 实现对客户端发来字符串内容的接收并打印
                    // 当没有数据发来时,下面的方法会形成阻塞
                    String s1 = br.readLine();
                    InetAddress inetAddress = s.getInetAddress();
                    System.out.println("客户端" + inetAddress + "发来的字符串内容是:" + s1);
                    // 当客户端发来的内容为"bye"时,则聊天结束
                    if ("bye".equalsIgnoreCase(s1)) {
                        System.out.println("客户端" + inetAddress + "已下线!");
                        break;
                    }
                    // 实现服务器向客户端回发字符串内容"I received!"
                    ps.println("I received!");
                    System.out.println("服务器发送数据成功!");
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (null != ps) {
                    ps.close();
                }
                if (null != br) {
                    try {
                        br.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (null != s) {
                    try {
                        s.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
    
        }
    }
    

2. 基于UDP协议的编程模型

  • 编程模型

    接收方:

    (1)创建DatagramSocket类型的对象并提供端口号;

    (2)创建DatagramPacket类型的对象并提供缓冲区;

    (3)通过Socket接收数据内容存放到Packet中,调用receive方法;

    (4)关闭Socket;

    发送方:

    (1)创建DatagramSocket类型的对象;

    (2)创建DatagramPacket类型的对象并提供接收方的通信地址;

    (3)通过Socket将Packet中的数据内容发送出去,调用send方法;

    (4)关闭Socket;

  • java.net.DatagramSocket类主要用于描述发送和接收数据报的套接字(邮局);换句话说,该类就是包裹投递服务的发送或接收点

    java.net.DatagramPacket类主要用于描述数据报,数据报用来实现无连接包裹投递服务

  • 示例Code

    public class ReceiveTest {
    
        public static void main(String[] args) {
            DatagramSocket ds = null;
    
            try {
                // 1.创建DatagramSocket类型的对象并提供端口号
                ds = new DatagramSocket(8888);
    
                // 2.创建DatagramPacket类型的对象并提供缓冲区
                byte[] bArr = new byte[20];
                DatagramPacket dp = new DatagramPacket(bArr, bArr.length);
                // 3.通过Socket接收数据内容存放到Packet里面,调用receive方法
                System.out.println("等待数据的到来...");
                ds.receive(dp);
                System.out.println("接收到的数据内容是:" + new String(bArr, 0, dp.getLength()) + "!");
    
                // 实现将字符串内容"I received!"回发过去
                byte[] bArr2 = "I received!".getBytes();
                DatagramPacket dp2 = new DatagramPacket(bArr2, bArr2.length, dp.getAddress(), dp.getPort());
                ds.send(dp2);
                System.out.println("回发数据成功!");
    
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 4.关闭Socket并释放有关的资源
                if (null != ds) {
                    ds.close();
                }
            }
        }
    }
    
    public class SendTest {
    
        public static void main(String[] args) {
            DatagramSocket ds = null;
    
            try {
                // 1.创建DatagramSocket类型的对象
                ds = new DatagramSocket();
                // 2.创建DatagramPacket类型的对象并提供接收方的通信地址和端口号
                byte[] bArr = "hello".getBytes();
                DatagramPacket dp = new DatagramPacket(bArr, bArr.length, InetAddress.getLocalHost(), 8888);
                // 3.通过Socket发送Packet,调用send方法
                ds.send(dp);
                System.out.println("发送数据成功!");
    
                // 接收回发的数据内容
                byte[] bArr2 = new byte[20];
                DatagramPacket dp2 = new DatagramPacket(bArr2, bArr2.length);
                ds.receive(dp2);
                System.out.println("接收到的回发消息是:" + new String(bArr2, 0, dp2.getLength()));
    
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 4.关闭Socket并释放有关的资源
                if (null != ds) {
                    ds.close();
                }
            }
        }
    }
    

3. URL类

  • java.net.URL(Uniform Resource Identififier)类主要用于表示统一的资源定位器,也就是指向万维网上“资源”的指针。这个资源可以是简单的文件或目录,也可以是对复杂对象的引用

  • 通过URL可以访问万维网上的网络资源,最常见的就是www和ftp站点,浏览器通过解析给定的URL可以在网络上查找相应的资源

  • URL类的常用方法和URLConnection类

    public class URLTest {
    
        public static void main(String[] args) {
    
            try {
                // 1.使用参数指定的字符串来构造对象
                URL url = new URL("https://www.example.com/");
                // 2.获取相关信息并打印出来
                System.out.println("获取到的协议名称是:" + url.getProtocol());
                System.out.println("获取到的主机名称是:" + url.getHost());
                System.out.println("获取到的端口号是:" + url.getPort());
    
                // 3.建立连接并读取相关信息打印出来
                HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
                InputStream inputStream = urlConnection.getInputStream();
                BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
                String str = null;
                while ((str = br.readLine()) != null) {
                    System.out.println(str);
                }
                br.close();
                // 断开连接
                urlConnection.disconnect();
    
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
    
        }
    }
    

五、反射机制

1. Class类

  • 获取Class对象的方式

    • 使用数据类型.class的方式可以获取对应类型的Class对象

    • 使用引用/对象.getClass()的方式可以获取对应类型的Class对象(注意:基本数据类型的变量不能调用方法)

    • 使用包装类.TYPE的方式可以获取对应基本数据类型的Class对象

    • 使用Class.forName()的方式来获取参数指定类型的Class对象(要求写完整的名称:包名.类名)

    • 使用类加载器ClassLoader的方式获取指定类型的Class对象

    public class ClassTest {
    
        public static void main(String[] args) throws ClassNotFoundException {
    
            // 1.使用数据类型.class的方式可以获取对应类型的Class对象
            Class c1 = String.class;
            System.out.println("c1 = " + c1); // 自动调用toString方法  class java.lang.String
            c1 = int.class;
            System.out.println("c1 = " + c1); // int
            c1 = void.class;
            System.out.println("c1 = " + c1); // void
    
            System.out.println("---------------------------------------------------");
            // 2.使用对象.getClass()的方式获取对应的Class对象
            String str1 = new String("hello");
            c1 = str1.getClass();
            System.out.println("c1 = " + c1); // class java.lang.String
    
            Integer it1 = 20;
            c1 = it1.getClass();
            System.out.println("c1 = " + c1); // class java.lang.Integer
    
            int num = 5;
            //num.getClass(); Error: 基本数据类型的变量不能调用方法
    
            System.out.println("---------------------------------------------------");
            // 3.使用包装类.TYPE的方式来获取对应基本数据类型的Class对象
            c1 = Integer.TYPE;
            System.out.println("c1 = " + c1); // int
    
            c1 = Integer.class;
            System.out.println("c1 = " + c1); // class java.lang.Integer
    
            System.out.println("---------------------------------------------------");
            // 4.调用Class类中的forName方法来获取对应的Class对象
            //c1 = Class.forName("String"); // Error  要求写完整的名称:包名.类名
            c1 = Class.forName("java.lang.String");
            System.out.println("c1 = " + c1); // class java.lang.String
    
            c1 = Class.forName("java.util.Date");
            System.out.println("c1 = " + c1); // class java.util.Date
    
            //c1 = Class.forName("int");
            //System.out.println("c1 = " + c1); // 不能获取基本数据类型的Class对象
    
            System.out.println("---------------------------------------------------");
            // 5.使用类加载器的方式来获取Class对象
            ClassLoader classLoader = ClassTest.class.getClassLoader();//不可使用String.class来获取ClassLoader对象
            System.out.println("classLoader = " + classLoader); // null
            c1 = classLoader.loadClass("java.lang.String");//loadClass返回Class类型对象
            System.out.println("c1 = " + c1); // class java.lang.String
        }
    }
    

2. Constructor类

java.lang.reflflect.Constructor类主要用于描述获取到的构造方法信息

  • 无参方式创建对象

    Class类中的方法获取构造器对象

    Constructor getConstructor(Class<?>...parameterTypes)

    Constructor类中的对象创建实例对象

    T newInstance(Object...initargs)

    public class Person {
        private String name;
        //public String name;
        private int age;
    
        public Person() {
        }
    
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) throws IOException {
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    
    // 1.使用原始方式以无参形式构造Person类型的对象并打印
    Person p1 = new Person();
    System.out.println("无参方式创建的对象是:" + p1); // null 0
    
    System.out.println("---------------------------------------------------");
    // 2.使用反射机制以无参形式构造Person类型的对象并打印
    // 创建对象的类型可以从键盘输入
    //System.out.println("请输入要创建对象的类型:");
    //Scanner sc = new Scanner(System.in);
    //String str1 = sc.next();
    //Class c1 = Class.forName("com.example.task20.Person");
    // 创建对象的类型可以从配置文件中读取
    BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("d:/a.txt")));
    String str1 = br.readLine();
    Class c1 = Class.forName(str1);
    //System.out.println("无参方式创建的对象是:" + c1.newInstance()); // null 0
    // 获取Class对象对应类中的无参构造方法,也就是Person类中的无参构造方法
    Constructor constructor = c1.getConstructor();
    // 使用获取到的无参构造方法来构造对应类型的对象,也就是Person类型的对象
    System.out.println("无参方式创建的对象是:" + constructor.newInstance());
    //sc.close();
    br.close();
    
    System.out.println("---------------------------------------------------");
    
  • 有参方式创建对象

    Class类中的方法获取构造器对象

    Constructor getConstructor(Class<?>...parameterTypes)

    Constructor类中的对象创建实例对象

    T newInstance(Object...initargs)

    // 3.使用原始方式以有参方式构造Person类型的对象并打印
    Person p2 = new Person("zhangfei", 30);
    System.out.println("有参方式构造的对象是:" + p2); // zhangfei 30
    
    System.out.println("---------------------------------------------------");
    // 4.使用反射机制以有参方式构造Person类型的对象并打印
    // 获取Class对象对应类中的有参构造方法,也就是Person类中的有参构造方法
    Constructor constructor1 = c1.getConstructor(String.class, int.class);
    // 使用获取到的有参构造方法来构造对应类型的对象,也就是Person类型的对象
    // newInstance方法中的实参是用于给有参构造方法的形参进行初始化的,也就是给name和age进行初始化的
    System.out.println("有参方式构造的对象是:" + constructor1.newInstance("zhangfei", 30)); // zhangfei 30
    
    System.out.println("---------------------------------------------------");
    
  • 获取所有构造方法

    // 5.使用反射机制获取Person类中所有的公共构造方法并打印
    Constructor[] constructors = c1.getConstructors();
    for (Constructor ct : constructors) {
        System.out.println("构造方法的访问修饰符是:" + ct.getModifiers());
        System.out.println("构造方法的方法名称是:" + ct.getName());
        Class[] parameterTypes = ct.getParameterTypes();
        System.out.print("构造方法的所有参数类型是:");
        for (Class cs : parameterTypes) {
            System.out.print(cs + " ");
        }
        System.out.println();
        System.out.println("-------------------------------------------------");
    }
    

3. Field类

java.lang.reflflect.Field类主要用于描述获取到的单个成员变量信息

  • 获取成员变量数值

    // 1.使用原始方式来构造对象以及获取成员变量的数值并打印
    Person p1 = new Person("zhangfei", 30);
    //System.out.println("获取到的成员变量数值为:" + p1.name); // zhangfei
    
    System.out.println("-------------------------------------------------------");
    // 2.使用反射机制来构造对象以及获取成员变量的数值并打印
    // 2.1 获取Class对象
    Class c1 = Class.forName("com.example.task20.Person");
    // 2.2 根据Class对象获取对应的有参构造方法
    Constructor constructor = c1.getConstructor(String.class, int.class);
    // 2.3 使用有参构造方法来得到Person类型的对象
    Object object = constructor.newInstance("zhangfei", 30);
    // 2.4 根据Class对象获取对应的成员变量信息
    Field field = c1.getDeclaredField("name");
    // 设置Java语言访问检查的取消  暴力反射
    field.setAccessible(true);
    // 2.5 使用Person类型的对象来获取成员变量的数值并打印
    // 获取对象object中名字为field成员变量的数值,也就是成员变量name的数值
    System.out.println("获取到的成员变量数值为:" + field.get(object)); // zhangfei
    
    System.out.println("-------------------------------------------------------");
    
  • 修改成员变量数值

    // 3.使用原始方式修改指定对象中成员变量的数值后再次打印
    //p1.name = "guanyu";
    //System.out.println("修改后成员变量的数值为:" + p1.name); // guanyu
    
    System.out.println("-------------------------------------------------------");
    // 4.使用反射机制修改指定对象中成员变量的数值后再次打印
    // 表示修改对象object中名字为field成员变量的数值为guanyu,也就是成员变量name的数值为guanyu
    field.set(object, "guanyu");
    System.out.println("修改后成员变量的数值为:" + field.get(object)); // guanyu
    
    System.out.println("-------------------------------------------------------");
    
  • 获取所有成员变量

    // 5.获取Class对象对应类中所有的成员变量
    Field[] declaredFields = c1.getDeclaredFields();
    for (Field ft : declaredFields) {
        System.out.println("获取到的访问修饰符为:" + ft.getModifiers());
        System.out.println("获取到的数据类型为:" + ft.getType());
        System.out.println("获取到的成员变量名称是:" + ft.getName());
        System.out.println("---------------------------------------------");
    }
    

4. Method类

java.lang.reflflect.Method类主要用于描述获取到的单个成员方法信息

  • 获取成员方法和调用成员方法

    // 1.使用原始方式构造对象并调用方法打印结果
    Person p1 = new Person("zhangfei", 30);
    System.out.println("调用方法的返回值是:" + p1.getName()); // zhangfei
    
    System.out.println("------------------------------------------------------");
    // 2.使用反射机制构造对象并调用方法打印结果
    // 2.1 获取Class对象
    Class c1 = Class.forName("com.example.task20.Person");
    // 2.2 根据Class对象来获取对应的有参构造方法
    Constructor constructor = c1.getConstructor(String.class, int.class);
    // 2.3 使用有参构造方法构造对象并记录
    Object object = constructor.newInstance("zhangfei", 30);
    // 2.4 根据Class对象来获取对应的成员方法(成员方法是空参的,所以getMethod不用传参数类型的Class对象)
    Method method = c1.getMethod("getName");
    // 2.5 使用对象调用成员方法进行打印
    // 表示使用object对象调用method表示的方法,也就是调用getName方法来获取姓名
    System.out.println("调用方法的返回值是:" + method.invoke(object)); // zhangfei
    
    System.out.println("------------------------------------------------------");
    
  • 获取所有成员方法

    // 3.使用反射机制来获取类中的所有成员方法并打印
    Method[] methods = c1.getMethods();
    for (Method mt : methods) {
        System.out.println("成员方法的修饰符是:" + mt.getModifiers());
        System.out.println("成员方法的返回值类型是:" + mt.getReturnType());
        System.out.println("成员方法的名称是:" + mt.getName());
        System.out.println("成员方法形参列表的类型是:");
        Class<?>[] parameterTypes = mt.getParameterTypes();
        for (Class ct : parameterTypes) {
            System.out.print(ct + " ");
        }
        System.out.println();
        System.out.println("成员方法的异常类型列表是:");
        Class<?>[] exceptionTypes = mt.getExceptionTypes();
        for (Class ct: exceptionTypes) {
            System.out.print(ct + " ");
        }
        System.out.println();
        System.out.println("---------------------------------------------------");
    }
    

5. 获取其他信息

  • Package getPackage()

    获取所在的包信息

  • Class<? super T> getSuperclass()

    获取继承的父类信息

  • Class<?>[] getInterfaces()

    获取实现的所有接口

  • Annotation[] getAnnotations()

    获取注解信息

  • Type[] getGenericInterfaces()

    获取泛型信息

  • 示例Code

    package module04.reflection;
    
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyAnnotation {
    }
    
    package module04.reflection;
    
    import java.io.Serializable;
    
    @MyAnnotation
    public class Student extends Person implements Comparable, Serializable {
    
        @Override
        public int compareTo(Object o) {
            return 0;
        }
    }
    
    package module04.reflection;
    
    import java.lang.annotation.Annotation;
    import java.lang.reflect.Type;
    
    public class StudentTest {
    
        public static void main(String[] args) throws Exception {
    
            // 获取Student类型的Class对象
            Class c1 = Class.forName("com.example.task20.Student");
            System.out.println("获取到的包信息是:" + c1.getPackage());
            System.out.println("获取到的父类信息是:" + c1.getSuperclass());
    
            System.out.println("-------------------------------------------------");
            System.out.println("获取到的接口信息是:");
            Class[] interfaces = c1.getInterfaces();
            for (Class ct : interfaces) {
                System.out.print(ct + " ");
            }
            System.out.println();
    
            System.out.println("-------------------------------------------------");
            System.out.println("获取到的注解信息是:");
            Annotation[] annotations = c1.getAnnotations();
            for (Annotation at : annotations) {
                System.out.print(at + " ");
            }
            System.out.println();
    
            System.out.println("-------------------------------------------------");
            System.out.println("获取到的泛型信息是:");
            Type[] genericInterfaces = c1.getGenericInterfaces();
            for (Type tt : genericInterfaces) {
                System.out.print(tt + " ");
            }
            System.out.println();
        }
    }
    
    /*
    获取到的包信息是:package module04.reflection
    获取到的父类信息是:class module04.reflection.Person
    -------------------------------------------------
    获取到的接口信息是:
    interface java.lang.Comparable interface java.io.Serializable 
    -------------------------------------------------
    获取到的注解信息是:
    @module04.reflection.MyAnnotation() 
    -------------------------------------------------
    获取到的泛型信息是:
    java.lang.Comparable interface java.io.Serializable
    */