【学习笔记】java基础(下)

22 阅读1小时+

模块22.IO流2

第一章.字节缓冲流

1.为啥要学字节缓冲流
  之前所写的FileOutputStream,FileInputStream,FileReader,FileWriter这都叫做基本类,其中FileInputStream和FileOutputStream的读写方法都是本地方法(方法声明上带native),本地方法是和系统以及硬盘打交道的,也就是说这两个对象的读和写都是在硬盘之间进行读写的,效率不高;缓冲流中底层带一个长度为8192的数组(缓冲区)。
  此时读和写都是在内存中完成的(在缓冲区之间完成),内存中的读写效率非常高
    
   使用之前需要将基本流包装成缓冲流,其实就new对象时,传递基本流 
    
2.字节缓冲流
  a.BufferedOutputStream:字节缓冲输出流
    构造:BufferedOutputStream(OutputStream out)
    使用:和FileOutputStream一样
        
  b.BufferedInputStream:字节缓冲输入流
    构造:BufferedInputStream(InputStream in)
    使用:和FileInputStream一样  
 public class Demo01BufferedInputStream_OutputStream {
    public static void main(String[] args)throws Exception {
        //method01();
        method02();
    }

    //使用字节缓冲流复制文件
    private static void method02()throws Exception {
        long start = System.currentTimeMillis();

        FileInputStream fis = new FileInputStream("E:\Idea\io\1.avi");
        FileOutputStream fos = new FileOutputStream("E:\Idea\io\2.avi");

        BufferedInputStream bis = new BufferedInputStream(fis);
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        //边读编写
        int len;
        while((len = bis.read())!=-1){
            bos.write(len);
        }

        long end = System.currentTimeMillis();

        System.out.println(end-start);

        bos.close();
        bis.close();
    }

    //用基本流复制文件
    private static void method01()throws Exception {
        long start = System.currentTimeMillis();

        FileInputStream fis = new FileInputStream("E:\Idea\io\1.avi");
        FileOutputStream fos = new FileOutputStream("E:\Idea\io\2.avi");
        //边读编写
        int len;
        while((len = fis.read())!=-1){
            fos.write(len);
        }

        long end = System.currentTimeMillis();

        System.out.println(end-start);

        fos.close();
        fis.close();
    }
}
细节:

问题1:使用缓冲流的时候,为啥只需要关闭缓冲流,不用单独关闭基本流呢?

​ 原因:缓冲流的close方法底层会自动关闭基本流

问题2:缓冲流底层有数组(缓冲区),都是在内存之间进行读写,那么缓冲流读写的过程是啥样的呢?

​ 注意:先依靠基本流将数据读出来,然后交给缓冲流,由于缓冲流缓冲区是8192,所以每次读取8192个字节放到缓冲区中,然后再将输入流缓冲区中的数据交给输出流缓冲区,然后再利用基本流将数据写到硬盘上

​ 那么在操作代码时len是干啥的呢?其实主要是在两个缓冲区中倒腾数据,将输入流缓冲区中的数据读到,然后写到输出流缓冲区中,等待输出流缓冲区满了,再依靠基本流写到硬盘上;如果输入流缓冲区中的数据读不到了,重新从硬盘上读8192个字节,进入到输入流缓冲区中,继续利用len在两个缓冲区中来回倒腾数据

image.png

第二章.字符缓冲流

我们知道,字符流的基本流底层是有缓冲区的,所以在效率这一块效果不是特别明显,但是不代表不重要,因为我们应该主要学字符缓冲流的两个特有方法

1.字符缓冲输出流_BufferedWriter

1.构造:
  BufferedWriter(Writer w)
2.方法:
  用起来和FileWriter一样
3.特有方法:
  newLine() 换行
public class Demo02BufferedWriter {
    public static void main(String[] args)throws Exception {
        BufferedWriter bw = new BufferedWriter(new FileWriter("module22\1.txt",true));
        bw.write("床前明月光");
        bw.newLine();
        bw.write("疑是地上霜");
        bw.newLine();
        bw.write("举头望明月");
        bw.newLine();
        bw.write("低头思故乡");
        bw.newLine();
        bw.close();
    }
}

2.字符缓冲输入流_BufferedReader

1.构造:
  BufferedReader(Reader r)
2.方法:
  用法和FileReader一样
3.特有方法:
  String readLine()-> 一次读一行,如果读到结束标记,返回的是null
public class Demo03BufferedReader {
    public static void main(String[] args)throws Exception {
        BufferedReader br = new BufferedReader(new FileReader("module22\1.txt"));
        /*String s = br.readLine();
        System.out.println(s);

        String s1 = br.readLine();
        System.out.println(s1);

        String s2 = br.readLine();
        System.out.println(s2);

        String s3 = br.readLine();
        System.out.println(s3);

        String s4 = br.readLine();
        System.out.println(s4);*/

        //定义一个字符串,接收读取的内容
        String line = null;
        while((line = br.readLine())!=null){
            System.out.println(line);
        }
        br.close();
    }
}

第三章.转换流

1.字符编码

计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的数字、英文、标点符号、汉字等字符是二进制数转换之后的结果。[按照某种规则,将字符存储到计算机中,称为编码] 。反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码 。比如说,按照A规则存储,同样按照A规则解析,那么就能显示正确的文本f符号。反之,按照A规则存储,再按照B规则解析,就会导致乱码现象。

image.png

image.png

  • 字符编码Character Encoding : 就是一套自然语言的字符与二进制数之间的对应规则。

2.字符集

  • 字符集 Charset:也叫编码表。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等。
  • 计算机要准确的存储和识别各种字符集符号,需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集、GBK字符集、Unicode字符集等。

3.转换流_InputStreamReader

1.字节流读取中文在编码一致的情况,也不要边读边看,因为如果字节读不准,读不全,输出的内容有可能会出现乱码
2.所以,我们学了字符流,字符流读取文本文档中的内容如果编码一致,就不会出现乱码问题了
3.但是如果编码不一致,字符流读取文本文档中的内容也有可能出现乱码
1.概述:是字节流通向字符流的桥梁 -> 读数据
2.构造:
  InputStreamReader(InputStream in,String charsetName)
                                   charsetName:指定编码,不区分大小写
3.作用:
  可以直接指定编码,按照指定的编码去读内容
4.用法:
  基本用法和FileReader一样
public class Demo01InputStreamReader {
    public static void main(String[] args)throws Exception {
        InputStreamReader isr =
                new InputStreamReader(new FileInputStream("E:\Idea\io\1.txt"),"gbk");
        int data = isr.read();
        System.out.println((char)data);
        isr.close();
    }
}

4.转换流_OutputStreamWriter

1.概述:是字符流通向字节流的桥梁
2.构造:
  OutputStreamWriter(OutputStream out,String charsetName)
                   
3.作用:
  按照指定的编码规则去存数据
      
4.用法:
  和FileWriter一样
public class Demo02OutputStreamWriter {
    public static void main(String[] args)throws Exception {
        OutputStreamWriter osw =
                new OutputStreamWriter(new FileOutputStream("E:\Idea\io\1.txt"),"gbk");
        osw.write("你好");
        osw.close();
    }
}

第四章.序列化流

一.序列化流和反序列化流介绍

1.作用:读写对象
2.两个对象:
  a.ObjectOutputStream -> 序列化流 -> 写对象
  b.ObjectInputStream -> 反序列化流 -> 读对象  
3.注意:
  我们将对象序列化到文件中,我们打开文件看不懂,这就对了,很多时候,我们操作的数据不能随便让别人看懂,不然别人就随意改动了,我们只需要将这些看不懂的内容成功读回来即可
      
  应用场景:比如玩儿游戏会对英雄存档,那么退出的时候英雄变成对象,将人物的属性变成对象的成员变量值,然后存到文件中,再次打开游戏,直接从文件中将这些人物对象读回来,将对象以及对象中的属性还原    

二.序列化流_ObjectOutputStream

1.作用:写对象
2.构造:
  ObjectOutputStream(OutputStream out)
3.方法:
  writeObject(Object obj) -> 写对象
4.注意:
  想要将对象序列化到文件中,被序列化的对象需要实现Serializable接口
public class Person implements Serializable {
    private String name;
    private Integer age;

    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + ''' +
                ", age=" + age +
                '}';
    }
}
   //序列化
    private static void write()throws Exception {
        ObjectOutputStream oos =
                new ObjectOutputStream(new FileOutputStream("module22\person.txt"));
        Person p1 = new Person("yy", 12);
        oos.writeObject(p1);
        oos.close();
    }

三.反序列化_ObjectInputStream

1.作用:读对象
2.构造:
  ObjectInputStream(InputStream in)
3.方法:
  Object readObject()
   private static void read()throws Exception {
        ObjectInputStream ois =
                new ObjectInputStream(new FileInputStream("module22\person.txt"));
        Person person = (Person) ois.readObject();
        System.out.println(person);
        ois.close();
    }

四.不想被序列化操作(了解)

transient

五.反序列化时出现的问题以及分析以及解决

 问题描述:
   序列化之后,修改源码,修改完之后没有重新序列化,直接反序列化了,就会出现了序列号冲突问题:
   InvalidClassException
解决:将序列号定死,后面不管怎么修改源码,序列号都是这一个
    在被序列化的对象中加上一个public static final long 的变量,并为其赋值
public class Person implements Serializable {
    public static final long serialVersionUID = 42L;
    private String name;
    public Integer age;

    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + ''' +
                ", age=" + age +
                '}';
    }
}

将一个对象实现一个序列化接口,我们将来才能让这个对象变成二进制,在网络上传输

第五章.打印流_PrintStream(了解)

1.PrintStream打印流基本使用

 1.构造:
   PrintStream(String fileName)
 2.方法:
   a.println(): 原样输出,自带换行效果
   b.print():  原样输出,不带换行效果  
public class Demo01PrintStream {
    public static void main(String[] args)throws Exception{
        PrintStream ps = new PrintStream("module22\printstream.txt");
        ps.println("大帅哥");
        ps.println("小鲜肉");
        ps.println("xxxx");
        ps.close();
    }
}
改变流向:
  1.什么叫做改变流向:
    System.out.println()-> 本身是输出到控制台上
    改变流向:可以让输出语句从控制台上输出改变成往指定文件中输出
        
  2.方法:System中的方法:
    static void setOut(PrintStream out) -> 改变流向 ->让输出语句从控制台输出转移到指定文件中
public class Demo02PrintStream {
    public static void main(String[] args)throws Exception{
        PrintStream ps = new PrintStream("module22\log.txt");

        //改变流向
        System.setOut(ps);

        System.out.println("这个错误是今天下午2点出现的");
        System.out.println("这个错误是文件意外到达结尾异常");
        System.out.println("出现的原因是循环反序列化次数不对");
        ps.close();
    }
}

使用场景:

可以将输出的内容以及详细信息放到日志文件中,永久保存

以后我们希望将输出的内容永久保存,但是输出语句会将结果输出到控制台上,控制台是临时显示,如果有新的程序运行,新程序的运行结果会覆盖之前的结果,这样无法达到永久保存,到时候我们想看看之前的运行结果信息就看不到了,所以我们需要将输出的结果保存到日志文件中,就可以使用setOut改变流向

2.PrintStream打印流完成续写

PrintStream(OutputStream out)  -> 可以依靠OutputStream的续写功能完成打印流续写
public class Demo03PrintStream {
    public static void main(String[] args)throws Exception{
        PrintStream ps = new PrintStream(new FileOutputStream("module22\log.txt",true));

        //改变流向
        System.setOut(ps);

        System.out.println("这个错误是今天下午2点出现的");
        System.out.println("这个错误是文件意外到达结尾异常");
        System.out.println("出现的原因是循环反序列化次数不对");
        ps.close();
    }
}

第六章.Properties集合

1.Properties结合IO流使用方法

回顾:
  1.概述: Properties extends Hashtable
  2.特点:
    a.无序,无索引
    b.key唯一,value可重复
    c.线程安全
    d.key和value默认类型都是String
        
  3.特有方法:
    setProperty(String key,String value) 存键值对
    getProperty(String key) -> 根据key获取value
    stringPropertyNames()-> 获取所有的key存放到set集合中
    load(InputStream in) -> 将流中的数据加载到Properties集合中
public class Demo01Properties {
    public static void main(String[] args) {
        Properties properties = new Properties();
        properties.setProperty("username","root");
        properties.setProperty("password","1234");

        Set<String> set = properties.stringPropertyNames();
        for (String key : set) {
            System.out.println(properties.getProperty(key));
        }
    }
}
使用场景:配合配置文件使用
    
注意:
   将来我们不能将很多的硬数据放到源码中,比如用户名和密码这些数据,因为将来我们有可能换用户名或者密码,如果一换,我们就需要去源码中修改,将来我们的类和类之间都有联系,有可能牵一发动全身,所以我们需要将这些数据提取出来,放到文件中,改的时候直接去文件中改,源码不需要改动
创建配置文件:
  1.在模块下右键 -> file -> 取名为xxx.properties
  2.在xxx.properties文件中写配置数据
    a.key和value都是key=value形式
    b.key和value都是String的,但是不要加双引号
    c.每个键值对写完之后,需要换行再写下一对
    d.键值对之间最好不要有空格(空格可以有,但是不建议写)
    e.键值对中建议不要使用中文(中文可以有,但是直接读取会乱码,需要转换流转码)  
jdbc.username=root
jdbc.password=1234

image.png

public class Demo02Properties {
    public static void main(String[] args)throws Exception {
        Properties properties = new Properties();
        FileInputStream fis = new FileInputStream("module22\jdbc.properties");
        properties.load(fis);

        Set<String> set = properties.stringPropertyNames();
        for (String key : set) {
            System.out.println(key+"..."+properties.getProperty(key));
        }
    }
}

第七章.Commons-io工具包

一.介绍

IO技术开发中,代码量很大,而且代码的重复率较高。如果我们要遍历目录,拷贝目录就需要使用方法的递归调用,也增大了程序的复杂度。

Apache软件基金会,开发了IO技术的工具类`commonsIO`,大大简化IO开发。

二.添加第三方jar包

1.Apache软件基金会属于第三方(Oracle公司是第一方,我们自己是第二方,其他的都是第三方),我们使用第三方开发出来的工具,都需要添加第三方提供给我们的jar包
    
2.jar包:本身是一个压缩包,里面转的都是class文件,我们想使用jar包中的工具类,就需要将相应的jar包解压到我们的当前项目下
    
3.怎么引入jar包
  a.在当前模块下创建文件夹,取名为lib或者libs
  b.将准备好的jar包,放到此文件夹下
  c.对着jar包,右键 -> add as library (如果我们想将lib下所有的jar包一起解压,我们就直接对着lib文件夹右键)
  d.level可以选择module,此时上面叫做name的输入框会变成空的,不用管
  e.直接点ok 

三.工具包的使用

IOUtils类
- 静态方法:IOUtils.copy(InputStream in,OutputStream out)传递字节流,实现文件复制。
- 静态方法:IOUtils.closeQuietly(任意流对象)释放资源,自动处理close()方法抛出的异常。
public class Demo01IOUtils {
    public static void main(String[] args) /*throws Exception*/{
        //- 静态方法:IOUtils.copy(InputStream in,OutputStream out)传递字节流,实现文件复制。
        //IOUtils.copy(new FileInputStream("E:\Idea\io\8.jpg"),new FileOutputStream("E:\Idea\io\孝敏.jpg"));
        //- 静态方法:IOUtils.closeQuietly(任意流对象)悄悄的释放资源,自动处理close()方法抛出的异常。
        FileWriter fw = null;
        try{
            fw = new FileWriter("module22\commons.txt");
            fw.write("你好");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (fw!=null){
                IOUtils.closeQuietly(fw);
            }
        }
    }
}
FileUtils类

- 静态方法:FileUtils.copyDirectoryToDirectory(File src,File dest);
           传递File类型的目录,进行整个目录的复制,自动进行递归遍历。
           
           参数:
             src:要复制的文件夹路径
             dest:要将文件夹粘贴到哪里去
             
- 静态方法:writeStringToFile(File file,String str)写字符串到文本文件中。
- 静态方法:String readFileToString(File file)读取文本文件,返回字符串。
public class Demo02FileUtils {
    public static void main(String[] args)throws Exception {
       /* - 静态方法:FileUtils.copyDirectoryToDirectory(File src,File dest);
        传递File类型的目录,进行整个目录的复制,自动进行递归遍历。

        参数:
        src:要复制的文件夹路径
        dest:要将文件夹粘贴到哪里去*/
        //FileUtils.copyDirectoryToDirectory(new File("E:\Idea\io\aa"),new File("E:\Idea\io\cc"));

        //- 静态方法:writeStringToFile(File file,String str)写字符串到文本文件中。
        //FileUtils.writeStringToFile(new File("module22\commons.txt"),"haha");
        //- 静态方法:String readFileToString(File file)读取文本文件,返回字符串。
        String s = FileUtils.readFileToString(new File("module22\commons.txt"));
        System.out.println(s);
    }
}

第八章.如何快速记忆IO流

image.png

模块23:网络编程&正则表达式&设计模式

第一章.网络编程

概述:在网络通信协议下,不同计算机上运行的程序,进行数据传输
    比如:通信,视频通话,网游,邮件等
    只要是计算机之间通过网络进行数据传输,就有网络编程的存在

1.软件结构

  • C/S结构 :全称为Client/Server结构,是指客户端和服务器结构。常见程序有QQ、红蜘蛛、飞秋等软件。
  • B/S结构 :全称为Browser/Server结构,是指浏览器和服务器结构。常见浏览器有IE、谷歌、火狐等。
  • 两种架构各有优势,但是无论哪种架构,都离不开网络的支持。网络编程,就是在一定的协议下,实现两台计算机的通信的程序。

2.服务器概念

1.概述:安装了服务器软件的计算机
2.后面马上要见到的服务器软件:tomcat   

网络通信协议:两台计算机在做数据交互时要遵守的规则,协议会对数据的格式,速率等进行规定,只有都遵守了这个协议,才能完成数据交互

两台计算机想完成数据交互,需要遵守网络通信协议

image.png

3.通信三要素

[IP地址]:计算机的唯一标识,用于两台计算机之间的连接

      a.概述:指互联网协议地址(Internet Protocol Address),俗称IP
            计算机的唯一标识
      b.作用:可用于计算机和计算机之间的连接
      c.IPV4
        32位的二进制数,通常被分为4个字节,表示成a.b.c.d 的形式,例如192.168.65.100 。其中abcd都是0~255之间的十进制整数,那么最多可以表示42亿个。
        IPV6
        为了扩大地址空间,拟通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,表示成ABCD:EF01:2345:6789:ABCD:EF01:2345:6789->号称能给地球上的每一粒沙子分配一个IP地址
        
      d.查看ip的命令:ipconfig
        测试是否能连接其他计算机的命令:ping ip地址
        
      e:特殊的网址:代表的是本机地址,到了哪里都不会变,代表自己
        127.0.0.1 -> 固定不变
        localhost
          
        localhost(主机名,写的是服务器的IP):端口号/应用名称/资源
          
        localhost:8080/应用名称/index.html
        

[协议]
     TCP:面向连接协议
         需要先确认连接,才能进行数据交互
         三次握手:
            - 第一次握手,客户端向服务器端发出连接请求,等待服务器确认。
            - 第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求。
            - 第三次握手,客户端再次向服务器端发送确认信息,确认连接。
            
         好处:数据安全,能给数据的传输提供一个安全的传输环境
         坏处:效率低
     
     UDP:面向无连接协议
         好处:效率高
         坏处:传输的数据不安全,容易丢失数据包

[端口号]
   每一个应用程序的唯一标识
  
  用两个字节表示的整数,它的取值范围是0~65535。其中,0~1023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败。

image.png TCP协议中的三次握手和四次挥手

三次握手:
- 第一次握手,客户端向服务器端发出连接请求,等待服务器确认。
- 第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求。
- 第三次握手,客户端再次向服务器端发送确认信息,确认连接。
四次挥手:
- 第一次挥手:客户端向服务器端提出结束连接,让服务器做最后的准备工作。此时,客户端处于半关闭状态,即表示不再向服务器发送数据了,但是还可以接受数据。
    
- 第二次挥手:服务器接收到客户端释放连接的请求后,会将最后的数据发给客户端。并告知上层的应用进程不再接收数据。
    
- 第三次挥手:服务器发送完数据后,会给客户端发送一个释放连接的报文。那么客户端接收后就知道可以正式释放连接了。
    
- 第四次挥手:客户端接收到服务器最后的释放连接报文后,要回复一个彻底断开的报文。这样服务器收到后才会彻底释放连接。这里客户端,发送完最后的报文后,会等待2MSL,因为有可能服务器没有收到最后的报文,那么服务器迟迟没收到,就会再次给客户端发送释放连接的报文,此时客户端在等待时间范围内接收到,会重新发送最后的报文,并重新计时。如果等待2MSL后,没有收到,那么彻底断开。

4.UDP协议编程

1.DatagramSocket -> 好比寄快递找的快递公司
2.DatagramPacket -> 好比快递公司打包
4.1.客户端(发送端)
1.创建DatagramSocket对象(快递公司)
  a.空参:端口号从可用的端口号中随机一个使用
  b.有参:自己指定
2.创建DatagramPacket对象,将数据进行打包
  a.要发送的数据-> byte[]
  b.指定接收端的IP
  c.指定接收端的端口号
3.发送数据
4.释放资源
public class Send {
    public static void main(String[] args) throws Exception{
        /*1.创建DatagramSocket对象(快递公司)
        a.空参:端口号从可用的端口号中随机一个使用
        b.有参:自己指定*/
        DatagramSocket socket = new DatagramSocket();
       /* 2.创建DatagramPacket对象,将数据进行打包
        a.要发送的数据-> byte[]
        b.指定接收端的IP
        c.指定接收端的端口号*/
        byte[] bytes = "你好呀".getBytes();
        InetAddress ip = InetAddress.getByName("127.0.0.1");
        int port = 6666;
        DatagramPacket dp = new DatagramPacket(bytes, bytes.length, ip, port);
        //3.发送数据
        socket.send(dp);
        //4.释放资源
        socket.close();
    }
}

直接执行发现,发送端在没有接收端的情况下,不会报错,因为UDP协议是面向无连接协议,不管有没有接收端,照发不误

4.2.服务端(接收端)
1.创建DatagramSocket对象,指定服务端的端口号
2.接收数据包
3.解析数据包
4.释放资源  
public class Receive {
    public static void main(String[] args) throws Exception{
        //1.创建DatagramSocket对象,指定服务端的端口号
        DatagramSocket socket = new DatagramSocket(6666);
        //2.接收数据包
        byte[] bytes = new byte[1024];//用于保存接收过来的数据
        DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
        socket.receive(dp);
        //3.解析数据包
        byte[] data = dp.getData();//接收的数据
        int len = dp.getLength();//从数据包中获取多少个数据
        InetAddress address = dp.getAddress();//获取发送端的主机
        int port = dp.getPort();//发送端的端口号
        System.out.println(new String(data,0,len));
        System.out.println(address+"..."+port);
        //4.释放资源
        socket.close();
    }
}

5.TCP协议编程

image.png

image.png

4.1.编写客户端
1.创建Socket对象,指明服务端的ip以及端口号
2.调用socket中的getOutputStream,往服务端发送请求
3.调用socket中的getInputStream,读取服务端响应回来的数据
4.关流
public class Client {
    public static void main(String[] args)throws Exception {
        //1.创建Socket对象,指明服务端的ip以及端口号
        Socket socket = new Socket("127.0.0.1", 6666);
        //2.调用socket中的getOutputStream,往服务端发送请求
        OutputStream os = socket.getOutputStream();
        os.write("hello哇".getBytes());
        //3.调用socket中的getInputStream,读取服务端响应回来的数据
        InputStream is = socket.getInputStream();
        byte[] bytes = new byte[1024];
        int len = is.read(bytes);
        System.out.println(new String(bytes,0,len));
        //4.关流
        is.close();
        os.close();
        socket.close();
    }
}
4.2.编写服务端
1.创建ServerSocket对象,设置端口号
2.调用ServerSocket中的accept方法,等待客户端连接,返回Socket对象
3.调用socket中的getInputStream,用于读取客户端发送过来的数据
4.调用socket中的getOutputStream,用于给客户端响应数据
5.关闭资源
public class Server {
    public static void main(String[] args)throws Exception {
        //1.创建ServerSocket对象,设置端口号
        ServerSocket ss = new ServerSocket(6666);
        //2.调用ServerSocket中的accept方法,等待客户端连接,返回Socket对象
        Socket socket = ss.accept();
        //3.调用socket中的getInputStream,用于读取客户端发送过来的数据
        InputStream is = socket.getInputStream();
        byte[] bytes = new byte[1024];
        int len = is.read(bytes);
        System.out.println(new String(bytes,0,len));
        //4.调用socket中的getOutputStream,用于给客户端响应数据
        OutputStream os = socket.getOutputStream();
        os.write("你也hello哇".getBytes());
        //5.关闭资源
        os.close();
        is.close();
        socket.close();
        ss.close();
    }
}

6.文件上传

image.png

6.1.文件上传客户端以及服务端实现
public class Client {
    public static void main(String[] args)throws Exception {
        //1.创建Socket对象
        Socket socket = new Socket("127.0.0.1", 6666);
        //2.创建FileInputStream,用于读取本地上的图片
        FileInputStream fis = new FileInputStream("E:\Idea\io\24.jpg");
        //3.调用getOutputStream,用于将读取过来的图片写给服务端
        OutputStream os = socket.getOutputStream();
        //4.边读边写
        byte[] bytes = new byte[1024];
        int len;
        while((len = fis.read(bytes))!=-1){
            os.write(bytes,0,len);
        }

        //给服务端写一个结束标记
        socket.shutdownOutput();
        System.out.println("======以下代码是读取响应的结果======");

        //5.调用getInputStream,读取响应结果
        InputStream is = socket.getInputStream();
        byte[] bytes1 = new byte[1024];
        int len1 = is.read(bytes1);
        System.out.println(new String(bytes1,0,len1));

        //6.关流
        is.close();
        os.close();
        fis.close();
        socket.close();
    }
}
public class Server {
    public static void main(String[] args)throws Exception {
        //1.创建ServerSocket对象
        ServerSocket ss = new ServerSocket(6666);
        //2.调用accept方法等待客户端的连接
        Socket socket = ss.accept();
        //3.调用socket中的getInputStream,读取客户端发送过来的图片
        InputStream is = socket.getInputStream();

        /*
          UUID调用randomUUID(),再调用toString,将其转成String
         */
        String s = UUID.randomUUID().toString();
        String name = s + System.currentTimeMillis();

        //4.创建FileOutputStream,将读取过来的图片写到硬盘上
        FileOutputStream fos = new FileOutputStream("E:\Idea\io\upload\"+name+".jpg");
        //5.边读边写
        byte[] bytes = new byte[1024];
        int len;
        while((len = is.read(bytes))!=-1){
            fos.write(bytes,0,len);
        }

        System.out.println("======以下代码是给客户端的响应结果======");

        //6.调用socket中的getOutputStream,给客户端响应结果
        OutputStream os = socket.getOutputStream();
        os.write("上传成功".getBytes());
        //7.关流
        os.close();
        fos.close();
        is.close();
        socket.close();
        ss.close();
    }
}

image.png

public class Demo01UUID {
    public static void main(String[] args) {
        String string = UUID.randomUUID().toString();//生成一个十六进制的随机数
        System.out.println("string = " + string);
    }
}
6.2.文件上传服务端实现(多线程)
public class ServerThread {
    public static void main(String[] args) throws Exception {
        //1.创建ServerSocket对象
        ServerSocket ss = new ServerSocket(6666);

        while (true) {
            //2.调用accept方法等待客户端的连接
            Socket socket = ss.accept();

            new Thread(new Runnable() {
                @Override
                public void run() {
                    InputStream is = null;
                    FileOutputStream fos = null;
                    OutputStream os = null;
                    try {
                        //3.调用socket中的getInputStream,读取客户端发送过来的图片
                        is = socket.getInputStream();

        /*
          UUID调用randomUUID(),再调用toString,将其转成String
         */
                        String s = UUID.randomUUID().toString();
                        String name = s + System.currentTimeMillis();

                        //4.创建FileOutputStream,将读取过来的图片写到硬盘上
                       fos = new FileOutputStream("E:\Idea\io\upload\" + name + ".jpg");
                        //5.边读边写
                        byte[] bytes = new byte[1024];
                        int len;
                        while ((len = is.read(bytes)) != -1) {
                            fos.write(bytes, 0, len);
                        }

                        System.out.println("======以下代码是给客户端的响应结果======");

                        //6.调用socket中的getOutputStream,给客户端响应结果
                        os = socket.getOutputStream();
                        os.write("上传成功".getBytes());

                    } catch (Exception e) {
                        e.printStackTrace();
                    }finally {
                        //7.关流
                       CloseUtils.closeQ(socket,fos,is,os);
                    }

                }
            }).start();

        }

    }
}
public class CloseUtils {
    private CloseUtils(){

    }
    public static void closeQ(Socket socket, FileOutputStream fos, InputStream is, OutputStream os){
        if (os!=null){
            try {
                os.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        if (fos!= null){
            try {
                fos.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        if (is!=null){
            try {
                is.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        if (socket!=null){
            try {
                socket.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

第二章.正则表达式

1.正则表达式的概念及演示

1.概述:正则表达式是一个具有特殊规则的字符串
2.作用:校验
  比如:校验手机号,身份证号,密码,用户名,邮箱等
3.String中有一个校验正则的方法:
  boolean matches(String regex)  校验字符串是否符合指定的regex的规则
4.比如:校验QQ号(不能以0开头,必须都是数字,必须是5-15位的)      
public class Demo01Regex {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String data = scanner.next();
        //boolean result01 = method01(data);
        //System.out.println("result01 = " + result01);
        boolean result02 = method02(data);
        System.out.println("result02 = " + result02);
    }

    private static boolean method02(String data) {
        boolean result = data.matches("[1-9][0-9]{4,14}");
        return result;
    }

    private static boolean method01(String data) {
        //不能是0开头的
        if (data.startsWith("0")) {
            return false;
        }

        //必须都是数字
        char[] chars = data.toCharArray();
        for (char aChar : chars) {
            if (aChar < '0' || aChar > '9') {
                return false;
            }
        }

        //必须是5-15位
        if (data.length()<5 || data.length()>15){
            return false;
        }

        return true;
    }
}

2.正则表达式-字符类

java.util.regex.Pattern:正则表达式的编译表示形式。
    正则表达式-字符类:[]表示一个区间,范围可以自己定义
        语法示例:
        1. [abc]:代表a或者b,或者c字符中的一个。
        2. [^abc]:代表除a,b,c以外的任何字符。
        3. [a-z]:代表a-z的所有小写字符中的一个。
        4. [A-Z]:代表A-Z的所有大写字符中的一个。
        5. [0-9]:代表0-9之间的某一个数字字符。
        6. [a-zA-Z0-9]:代表a-z或者A-Z或者0-9之间的任意一个字符。
        7. [a-dm-p]admp之间的任意一个字符 

3.正则表达式-逻辑运算符

 正则表达式-逻辑运算符
        语法示例:
        1. &&:并且
        2. | :或者
    /*
      逻辑运算符
     */
    private static void method02() {
       //1.要求字符串是小写字母并且字符不能以[aeiou]开头,后面跟ad
        boolean result01 = "yad".matches("[[a-z]&&[^aeiou]][a][d]");
        System.out.println("result01 = " + result01);

        //2.要求字符是aeiou的某一个字符开头,后面跟ad
        boolean result02 = "had".matches("[a|e|i|o|u][a][d]");
        System.out.println("result02 = " + result02);
    }

4.正则表达式-预定义字符

 正则表达式-预定义字符
    语法示例:
    1. "." : 匹配任何字符。(重点)  不能加[]
    2. "\d":任何数字[0-9]的简写;(重点)
    3. "\D":任何非数字[^0-9]的简写;
    4. "\s": 空白字符:[ \t\n\x0B\f\r] 的简写
    5. "\S": 非空白字符:[^\s] 的简写
    6. "\w":单词字符:[a-zA-Z_0-9]的简写(重点)
    7. "\W":非单词字符:[^\w]
    //预定义字符
    private static void method03() {
        //1.验证字符串是否是三位数字
        //boolean result01 = "111".matches("[0-9][0-9][0-9]");
        boolean result01 = "111".matches("\d\d\d");
        System.out.println("result01 = " + result01);

        //2.验证手机号: 1开头 第二位3 5 8 剩下的都是0-9的数字
        boolean result02 = "13838381438".matches("[1][358]\d\d\d\d\d\d\d\d\d");
        System.out.println("result02 = " + result02);

        //3.验证字符串是否以h开头,d结尾,中间是任意一个字符
        boolean result03 = "had".matches("[h].[d]");
        System.out.println("result03 = " + result03);
    }

5. 正则表达式-数量词

 正则表达式-数量词
        语法示例:x代表字符
        1. X? : x出现的数量为 0次或1次
        2. X* : x出现的数量为 0次到多次 任意次
        3. X+ : x出现的数量为 1次或多次 X>=1次
        4. X{n} : x出现的数量为 恰好n次 X=n次
        5. X{n,} : x出现的数量为 至少n次 X>=n次  x{3,}
        6. X{n,m}: x出现的数量为 n到m次(n和m都是包含的)   n=<X<=m
 //数量词
    private static void method04() {
         //1.验证字符串是否是三位数字
        boolean result01 = "111".matches("\d{3}");
        System.out.println("result01 = " + result01);
        //2.验证手机号: 1开头 第二位3 5 8 剩下的都是0-9的数字
        boolean result02 = "13838381438".matches("[1][358]\d{9}");
        System.out.println("result02 = " + result02);

        //3.验证qq号:  不能是0开头,都是数字,长度为5-15
        boolean result03 = "111111".matches("[1-9][0-9]{4,14}");
        System.out.println("result03 = " + result03);

    }

6.正则表达式-分组括号( )

正则表达式-分组括号( )  (abc)
    //分组括号
    private static void method05() {
        //校验abc可以出现任意次
        boolean result = "abcabc".matches("(abc)*");
        System.out.println("result = " + result);
    }

7.String类中和正则表达式相关的方法

 String类中和正则表达式相关的方法
        boolean matches(String regex) 判断字符串是否匹配给定的正则表达式。
        String[] split(String regex) 根据给定正则表达式的匹配拆分此字符串。
        String replaceAll(String regex, String replacement)把满足正则表达式的字符串,替换为新的字符
    private static void method06() {
        //String[] split(String regex) 根据给定正则表达式的匹配拆分此字符串。
        String s1 = "abc hahah  hehe   hdhshsh";
        String[] arr1 = s1.split(" +");
        System.out.println(Arrays.toString(arr1));
        //String replaceAll(String regex, String replacement)把满足正则表达式的字符串,替换为新的字符
        String s2 = s1.replaceAll(" +", "z");
        System.out.println("s2 = " + s2);
    }

8.正则表达式生成网址:

https://www.sojson.com/regex/generate

第三章.设计模式

设计模式(Design pattern),是一套被反复使用、经过分类编目的、代码设计经验的总结,使用设计模式是为了可重用代码、保证代码可靠性、程序的重用性,稳定性。

1995 年,GoF(Gang of Four,四人组)合作出版了《设计模式:可复用面向对象软件的基础》一书,共收录了 23 种设计模式。<大话设计模式>

总体来说设计模式分为三大类:

创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。-->创建对象

结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。-->对功能进行增强

行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

1.模版方法设计模式

模板方法(Template Method)模式:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。明确了一部分功能,而另一部分功能不明确。需要延伸到子类中实现

饭店中吃饭: 点菜,吃菜和买单三个步骤。点菜和买单基本上一致的,但是吃菜不同,吃法也不同。明确了一部分功能,而另一部分功能不明确。
public abstract class Hotel {
    public void eat(){
        System.out.println("点菜");
        eatCai();
        System.out.println("买单");
    }
    public abstract void eatCai();
}
public class QuanJuDe extends Hotel{
    @Override
    public void eatCai() {
        System.out.println("薄饼");
        System.out.println("放鸭肉");
        System.out.println("酱");
        System.out.println("葱丝");
        System.out.println("黄瓜丝");
        System.out.println("卷着吃");
    }
}

public class ZhangLiang extends Hotel{
    @Override
    public void eatCai() {
        System.out.println("调麻酱");
        System.out.println("放辣椒油");
        System.out.println("倒到大碗中吃");
    }
}
public class Test01 {
    public static void main(String[] args) {
        QuanJuDe quanJuDe = new QuanJuDe();
        quanJuDe.eat();

        System.out.println("================");

        ZhangLiang zhangLiang = new ZhangLiang();
        zhangLiang.eat();
    }
}

2.单例模式

1.目的:单(一个) 例(实例,对象)
  让一个类只产生一个对象,供外界使用
  
2.分类:
  a.饿汉式:我好饥渴呀,好饥饿呀,迫不及待要这个对象,所以和对象就需要赶紧new出来
    -   **类加载时立即初始化**(`private static Singleton singleton = new Singleton();`)。
    -   **全局唯一**,因为 `singleton` 是 `static` 的,整个 JVM 进程只有一个实例。
    -   **线程安全**,因为实例在类加载时创建,不存在多线程竞争问题。
    
  b.懒汉式:我好懒呀,不着急要对象,想啥时候使用,你啥时候new给我
    -   **延迟初始化(Lazy Initialization)**:实例在第一次调用 `getInstance()` 时
        才创建,而不是在类加载时立即创建。
    -   **线程不安全(基础实现)**:因为多个线程可能同时进入 `if (instance == null)`
        判断,导致创建多个实例,需要额外的同步机制(如 `synchronized`)来保证线程安全。
    -   **可能存在性能开销**:如果使用 `synchronized` 保证线程安全,每次调用 `getInstance()` 
        都会进行同步,可能影响性能(尤其是高并发场景)。可以优化(如双重检查锁、静态
        内部类、枚举等方式)来减少同步开销。
2.1.饿汉式:
public class Singleton {
    /*
      防止外界随意使用构造方法new对象,我们需要将构造私有化
     */
    private Singleton(){

    }

    /*
       为了赶紧new对象,我们new对象的时候变成静态的,让其随着类的加载而加载
       为了不让外界随便使用类名调用此静态对象,我们将其变成private
     */
    private static Singleton singleton = new Singleton();

    /*
       为了将内部new出来的对象给外界
       我们可以定义 一个方法,将内部的对象返回给外界
     */
    public static Singleton getSingleton(){
        return singleton;
    }
}
public class Test01 {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Singleton singleton = Singleton.getSingleton();
            System.out.println(singleton);
        }
    }
}
2.2.懒汉式:
public class Singleton1 {
    /*
      防止外界随意使用构造方法new对象,我们需要将构造私有化
    */
    private Singleton1() {

    }

    /*
       懒汉式,不着急new对象
     */
    private static Singleton1 singleton1 = null;

    /*
       为了将内部new出来的对给外界
       定义一个方法,将内部new出来的对返回
     */
    public static Singleton1 getSingleton1() {
        //如果singleton1不是null就没必要抢锁了,直接返回,是null再抢锁
        if (singleton1==null){
            synchronized (Singleton1.class){
                if (singleton1 == null) {
                    singleton1 = new Singleton1();
                }
            }
        }
        return singleton1;
    }
}

第四章.Lombok

1.作用:简化javabean开发
2.使用:
  a.下插件 -> 如果是idea2022不用下载了,自带
  b.导lombokjarc.修改设置   

1.lombok介绍

Lombok通过增加一些“处理程序”,可以让javabean变得简洁、快速。

Lombok能以注解形式来简化java代码,提高开发效率。开发中经常需要写的javabean,都需要花时间去添加相应的getter/setter,也许还要去写构造器、equals等方法,而且需要维护。

Lombok能通过注解的方式,在编译时自动为属性生成构造器、getter/setter、equals、hashcode、toString方法。出现的神奇就是在源码中没有gettersetter方法,但是在编译生成的字节码文件中有gettersetter方法。这样就省去了手动重建这些代码的麻烦,使代码看起来更简洁些。

image.png

2.lombok常用注解

@Getter和@Setter
  • 作用:生成成员变量的get和set方法。
  • 写在成员变量上,指对当前成员变量有效。
  • 写在类上,对所有成员变量有效。
  • 注意:静态成员变量无效。
@ToString
  • 作用:生成toString()方法。
  • 注解只能写在类上。
@NoArgsConstructor和@AllArgsConstructor
  • @NoArgsConstructor:无参数构造方法。
  • @AllArgsConstructor:满参数构造方法。
  • 注解只能写在类上。
@EqualsAndHashCode
  • 作用:生成hashCode()和equals()方法。
  • 注解只能写在类上。
@Data
  • 作用:生成get/set,toString,hashCode,equals,无参构造方法
  • 注解只能写在类上。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
    private String name;
    private Integer age;
}

模块24:jdk新特性

第一章.Lambda表达式

1.函数式编程思想和Lambda表达式定义格式

1.面向对象思想:是Java的核心编程思想
  强调的是找对象,帮我们做事儿
  
  比如:去北京 -> 强调的是怎么去,火车,高铁,飞机,汽车,自行车,腿儿  

2.jdk8开始又了个新的思想:函数式编程思想:
  强调的是结果,不强调过程
      
  比如:去北京 -> 只强调去了还是没去
      
3.Lambda表达式:
  a.定义格式:
    ()->{}
  b.各部分解释:
    () : 重写方法的参数位置
    -> : 将参数传递到方法体中
    {} : 重写方法的方法体  
public class Demo01Lambda {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("我执行了");
            }
        }).start();

        System.out.println("============");
        
        new Thread(()-> System.out.println("我执行了")).start();

        ArrayList<String> list = new ArrayList<>();
    }
}
  new Thread(()-> System.out.println("我执行了")).start();
 // 等价于下面的代码:
 new Thread(new Runnable() { @Override public void run() { System.out.println("我执行了"); } }).start();
  • Lambda 等价形式() -> {...} → new Runnable() { public void run() {...} }
  • 核心规则:Lambda 仅适用于函数式接口(如 RunnableComparatorActionListener 等)。
  • 推荐:优先使用 Lambda,因为它更简洁,但需理解其底层仍是匿名内部类。

2.Lambda表达式使用前提

1.必须是函数式接口做方法参数传递

2.啥叫函数式接口: 有且只有一个抽象方法的接口, 用@FunctionalInterface去检测

@FunctionalInterface
public interface USB {
    void open();
}

3.Lambda表达式省略规则

1.Lambda表达式怎么写
  a.观察是否是函数式接口做方法参数传递
  b.如果是,考虑使用Lambda表达式
  c.调用方法,以匿名内部类的形式传递实参
  d.从new接口开始到重写方法的方法名结束,选中,删除,别忘记再删除一个右半个大括号
  e.在重写方法的参数后面,方法体的大括号前面加上 -> 
    
    
2.省略规则:
  a.重写方法的参数类型可以干掉
  b.如果重写方法只有一个参数,所在的小括号可以干掉
  c.如果方法体中只有一句话,那么所在的大括号以及分号可以干掉
  d.如果方法体中只有一句话并且带return的,那么所在的大括号,分号以及return 可以干掉
public class Person {
   private String name;
   private Integer age;

    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + ''' +
                ", age=" + age +
                '}';
    }
}
public class Demo03Lambda {
    public static void main(String[] args) {
        ArrayList<Person> list = new ArrayList<>();
        list.add(new Person("张三",10));
        list.add(new Person("李四",8));
        list.add(new Person("王五",9));

       /* Collections.sort(list, new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge()-o2.getAge();
            }
        });
*/
        System.out.println("=============Lambda==========");

      /*  Collections.sort(list,(Person o1, Person o2)-> {
                return o1.getAge()-o2.getAge();
        });*/

        System.out.println("===========Lambda表达式简化形式==========");

        Collections.sort(list,(o1, o2)-> o1.getAge()-o2.getAge());
        System.out.println(list);
    }
}

第二章.函数式接口

1.函数式接口:
  有且只有一个抽象方法的接口
2.检测:
  @FunctionalInterface
@FunctionalInterface
public interface USB {
    void open(String s);
    //void close();
}
public class Test01 {
    public static void main(String[] args) {
       method(new USB() {
           @Override
           public void open(String s) {
               System.out.println(s+"开启了");
           }
       });
        System.out.println("=====Lambda====");
        method((String s)->{
                System.out.println(s+"开启了");
        });

        System.out.println("=====Lambda简化版====");
        method(s-> System.out.println(s+"开启了"));
    }
    public  static void method(USB usb){
        usb.open("鼠标");
    }
}

1.Supplier

1.Supplier接口
   java.util.function.Supplier<T>接口,它意味着"供给"->我们想要什么就给什么
2.方法:
  T get() -> 我们想要什么,get方法就可以返回什么

3.需求:
   使用Supplier接口作为方法的参数
   用Lambda表达式求出int数组中的最大值
       
4.泛型:
  <引用数据类型>-> 规定了我们操作的数据是什么类型
  <>中只能写引用数据类型,不能写基本数据类型
基本类型包装类
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean
public class Demo01Supplier {
    public static void main(String[] args) {
        method(new Supplier<Integer>() {
            @Override
            public Integer get() {
                int[] arr = {4,3,4,6,7};
                Arrays.sort(arr);
                return arr[arr.length-1];
            }
        });

        System.out.println("==================");
        method(()-> {
                int[] arr = {4,3,4,6,7};
                Arrays.sort(arr);
                return arr[arr.length-1];
        });
    }

    public static void method(Supplier<Integer> supplier){
        Integer max = supplier.get();//让get方法返回一个数组最大值
        System.out.println("max = " + max);
    }
}

image.png

2.Consumer

java.util.function.Consumer<T>->消费型接口->操作
  方法:
    void accept(T t),意为消费一个指定泛型的数据
        
"消费"就是"操作",至于怎么操作,就看重写accept方法之后,方法体怎么写了
public class Demo02Consumer {
    public static void main(String[] args) {
        method(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s.length());
            }
        },"abcdefg");
        System.out.println("==================");
        method(s-> System.out.println(s.length()),"abcdefg");
    }
    public static void method(Consumer<String> consumer,String s){
        consumer.accept(s);
    }
}

image.png

3.Function

java.util.function.Function<T,R>接口用来根据一个类型的数据得到另一个类型的数据
  方法:
     R apply(T t)根据类型T参数获取类型R的结果
public class Demo03Function {
    public static void main(String[] args) {
        method(new Function<Integer, String>() {
            @Override
            public String apply(Integer integer) {
                return integer+"";
            }
        },100);

        System.out.println("===================");
        method(integer -> integer+"",200);
    }

    public static void method(Function<Integer,String> function,Integer number){
        String s = function.apply(number);
        System.out.println("s = " + (s+1));
    }
}

image.png

4.Predicate

java.util.function.Predicate<T>接口。->判断型接口
    boolean test(T t)->用于判断的方法,返回值为boolean
public class Demo04Predicate {
    public static void main(String[] args) {
        method(new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return s.length()==7;
            }
        },"abcdefg");

        System.out.println("===================");

        method(s -> s.length()==7,"abcd");
    }
    public static void method(Predicate<String> predicate,String s){
        boolean test = predicate.test(s);
        System.out.println("test = " + test);
    }
}

第三章.Stream流

1.Stream流中的"流"不是特指"IO流",它是一种"流式编程"(编程方式),可以看做是"流水线"

image.png

public class Demo01Stream {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("张三丰");
        list.add("张大彪");
        list.add("吕不韦");
        list.add("张三");
        list.add("赵姬");
        list.add("张翠山");
        list.add("嫪毐");

        //需求1:筛选出姓张的人
       /* ArrayList<String> listZhang = new ArrayList<>();
        for (String s : list) {
            if (s.startsWith("张")){
                listZhang.add(s);
            }
        }
        System.out.println(listZhang);*/

        //需求2:筛选出三个字的张姓人物
       /* ArrayList<String> listThree = new ArrayList<>();
        for (String s : listZhang) {
            if (s.length()==3){
                listThree.add(s);
            }
        }
        System.out.println(listThree);*/

        //需求3.遍历集合,将三个字姓张的打印出来
        /*for (String s : listThree) {
            System.out.println(s);
        }

        System.out.println("================");*/

        //将list转成Stream流对象
        Stream<String> stream = list.stream();
        /*stream.filter(new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return s.startsWith("张");
            }
        }).filter(new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return s.length()==3;
            }
        }).forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        });*/

        System.out.println("======================");

        stream.filter(s -> s.startsWith("张")).filter(s -> s.length()==3).forEach(s -> System.out.println(s));
    }
}

1.Stream的获取

1.针对集合:Collection中的方法
    Stream<E> stream()  
    
2.针对数组:Stream接口中的静态方法:
    static <T> Stream<T> of(T... values) 
public class Demo02Stream {
    public static void main(String[] args) {
        //1.针对集合:Collection中的方法
        //Stream<E> stream()
        ArrayList<String> list = new ArrayList<>();
        list.add("张三");
        list.add("李四");
        list.add("王五");
        Stream<String> stream = list.stream();
        System.out.println(stream);

        //2.针对数组:Stream接口中的静态方法:
        //static <T> Stream<T> of(T... values)
        Stream<String> stream1 = Stream.of("aa", "bb", "cc");
        System.out.println(stream1);
    }
}

2.Stream的方法

2.1.Stream中的forEach方法:void forEach(Consumer<? super T> action);
forEach : 逐一处理->遍历
void forEach(Consumer<? super T> action);

注意:forEach方法是一个[终结方法],使用完之后,Stream流不能用了
 /**
     * 逐一处理,可以用来遍历
     */
    private static void foreach() {
        Stream<String> stream1 = Stream.of("aa", "bb", "cc");
       /* stream1.forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        });*/
        System.out.println("==============");

        stream1.forEach(s-> System.out.println(s));
    }
2.2.Stream中的long count()方法
1.作用:统计元素个数
2.注意:count也是一个终结方法
  /**
     * 统计元素个数
     */
    private static void count() {
        Stream<String> stream1 = Stream.of("张三", "李四", "王五","赵六");
        long count = stream1.count();
        System.out.println("count = " + count);
    }
2.3.Stream中的Stream filter### (Predicate<? super T> predicate)方法
1.方法:Stream<T> filter(Predicate<? super T> predicate)方法,返回一个新的Stream流对象
2.作用:根据某个条件进行元素过滤
  private static void filter() {
        Stream<String> stream1 = Stream.of("张三", "李四", "王五","赵六","张无忌");
        /*stream1.filter(new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return s.length() == 2;
            }
        }).forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        });*/

        System.out.println("============");

        stream1.filter(s -> s.length()==2).forEach(s -> System.out.println(s));


    }
2.4.Stream limit(long maxSize):获取Stream流对象中的前n个元素,返回一个新的Stream流对象
1.Stream<T> limit(long maxSize):获取Stream流对象中的前n个元素,返回一个新的Stream流对象
    /**
     * 获取前几个元素
     */
    private static void limit() {
        Stream<String> stream1 = Stream.of("张三", "李四", "王五","赵六","张无忌");
        stream1.limit(3).forEach(s -> System.out.println(s));
    }
2.5.Stream skip(long n):跳过Stream流对象中的前n个元素,返回一个新的Stream流对象
Stream<T> skip(long n): 跳过Stream流对象中的前n个元素,返回一个新的Stream流对象
    /**
     * 跳过前n个元素
     */
    private static void skip() {
        Stream<String> stream1 = Stream.of("张三", "李四", "王五","赵六","张无忌");
        stream1.skip(2).forEach(s -> System.out.println(s));
    }
2.6.static Stream concat(Stream<? extends T> a, Stream<? extends T> b): 两个流合成一个流
1.方法:static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b):两个流合成一个流
    /**
     *  流合并,是一个静态方法
     */
    private static void concat() {
        Stream<String> stream1 = Stream.of( "三上", "松下","柳岩","张无忌");
        Stream<String> stream2 = Stream.of("yy", "yy1", "yy2","yy3");

        Stream.concat(stream1, stream2).forEach(s -> System.out.println(s));

    }
2.7.collect()将Stream流变成集合
Stream流对象转成集合对象,使用Stream接口方法collect()
  /**
     * 流转集合
     */
    private static void collect() {
        Stream<String> stream1 = Stream.of("金莲", "三上", "松下","柳岩","张无忌");
        List<String> list = stream1.collect(Collectors.toList());
        System.out.println(list);
    }
2.8.dinstinct方法
Stream<T> distinct()
元素去重复,依赖hashCode和equals方法
   /**
     * 去重复元素
     * 被去重的元素底层需要重写hashCode和equals方法
     */
    private static void distinct() {
        //Stream<String> stream1 = Stream.of("金莲", "三上", "松下","柳岩","张无忌","张无忌");
        //stream1.distinct().forEach(s -> System.out.println(s));

        Stream<Person> stream = Stream.of(new Person("张三", 10), new Person("李四", 12), new Person("张三", 10));
        stream.distinct().forEach(person -> System.out.println(person));
    }
2.9.转换流中的类型
Stream<R> map(Function<T,R> mapper)-> 转换流中的数据类型
    /**
     * 转换流中的类型
     */
    private static void map() {
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
        stream.map(new Function<Integer, String>() {
            @Override
            public String apply(Integer integer) {
                return integer+"";
            }
        }).forEach(s -> System.out.println(s+1));
    }
2.10.Stream流练习
   1. 第一个队伍只要名字为3个字的成员姓名;//filter

   2. 第一个队伍筛选之后只要前3个人;//limit

   3. 第二个队伍只要姓张的成员姓名;//filter

   4. 第二个队伍筛选之后不要前2个人;//skip

   5. 将两个队伍合并为一个队伍;//concat

   6. 打印整个队伍的姓名信息。//forEeach
public class Demo04Stream {
    public static void main(String[] args) {
        ArrayList<String> one = new ArrayList<>();
        one.add("迪丽热巴");
        one.add("宋远桥");
        one.add("苏星河");
        one.add("老子");
        one.add("庄子");
        one.add("孙子");
        one.add("洪七公");

        ArrayList<String> two = new ArrayList<>();
        two.add("古力娜扎");
        two.add("张无忌");
        two.add("张三丰");
        two.add("赵丽颖");
        two.add("张二狗");
        two.add("张天爱");
        two.add("张三");

        //将两个集合变成Stream流
        Stream<String> teamA = one.stream();
        Stream<String> teamB = two.stream();

        //Stream<String> listA = teamA.filter(s -> s.length() == 3).limit(3);
        //Stream<String> listB = teamB.filter(s -> s.startsWith("张")).skip(2);

        //合并
        Stream.concat(teamA.filter(s -> s.length() == 3).limit(3),teamB.filter(s -> s.startsWith("张")).skip(2)).forEach(s -> System.out.println(s));

    }
}

第四章.方法引用

1.方法引用的介绍

1.概述:引用方法
2.啥时候使用:
  a.被引用的方法要写在重写方法里面
  b.被引用的方法从参数上,返回值上要和所在重写方法一致,而且引用的方法最好是操作重写方法的参数值的
  c.干掉重写方法的参数;干掉->;干掉被引用方法的参数 -> 将被引用方法的.改成::  

2.方法引入的体验

public class Demo01Method {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("aa", "bb", "vv", "dd", "zz");

        /*
           accept是重写方法:  参数类型为String
                            无返回值

           accept方法里面有println方法:println参数类型为String,被引用的方法操作重写方法的参数值
                                    println没有返回值
         */
       /* stream.forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        });*/

        System.out.println("===================");
        //stream.forEach(s -> System.out.println(s));
        System.out.println("===================");
        stream.forEach(System.out::println);
    }
}

3.对象名--引用成员方法

1.使用对象名引用成员方法
  格式:
    对象::成员方法名
         
2.需求:
    函数式接口:Supplier
        java.util.function.Supplier<T>接口
    抽象方法:
        T get()用来获取一个泛型参数指定类型的对象数据
        Supplier接口使用什么泛型,就可以使用get方法获取一个什么类型的数据
public class Demo02Method {
    public static void main(String[] args) {
        method(new Supplier<String>() {
            /*
               get为重写方法:无参的,返回值为String
               trim方法在get中:无参的,返回值为String
               考虑使用方法引用
             */
            @Override
            public String get() {
                return " abc ".trim();
            }
        });

        System.out.println("================");

        method(()->" abc ".trim());

        System.out.println("================");
        method(" abc "::trim);
    }

    public static void method(Supplier<String> supplier){
        String s = supplier.get();
        System.out.println("s = " + s);
    }
}

4.类名--引用静态方法

类名--引用静态方法
    格式:
      类名::静态成员方法
public class Demo03Method {
    public static void main(String[] args) {
        method(new Supplier<Double>() {
            /*
               get:无参,返回值类型为double
               random():无参,返回值类型为double
             */
            @Override
            public Double get() {
                return Math.random();
            }
        });
        System.out.println("==================");

        method(()->Math.random());

        System.out.println("==================");
        
        method(Math::random);
    }
    public static void method(Supplier<Double> supplier){
        Double aDouble = supplier.get();
        System.out.println("aDouble = " + aDouble);
    }
}

5.类--构造引用

1. 类--构造方法引用
   格式:
     构造方法名称::new
             
2.需求:
    函数式接口:Function
        java.util.function.Function<T,R>接口
    抽象方法:
        R apply(T t),根据类型T的参数获取类型R的结果。用于数类型转换
public class Demo04Method {
    public static void main(String[] args) {
        method(new Function<String, Person>() {
            /*
              apply为重写方法:  有一个String的参数,返回值类型为Person对象
              new Person(s) : 一个String参数的构造,类型为String,返回值类型Person类型
             */
            @Override
            public Person apply(String s) {
                return new Person(s);
            }
        },"涛哥");

        method(s -> new Person(s),"金莲");

        method(Person::new,"三上");
    }
    public static void method(Function<String,Person> function,String name){
        Person person = function.apply(name);
        System.out.println(person);
    }
}

6.数组--数组引用

数组--数组引用
     格式:
          数组的数据类型[]::new
          int[]::new  创建一个int型的数组
          double[]::new  创建于一个double型的数组
public class Demo05Method {
    public static void main(String[] args) {
        method(new Function<Integer, int[]>() {
            /*
              apply:重写的方法,参数为Integer型,返回值类型为int[]
              new int[integer]: [integer]看成参数,参数为Integer型,返回值int[]
             */
            @Override
            public int[] apply(Integer integer) {
                return new int[integer];
            }
        },10);

        System.out.println("=================");

        method(integer-> new int[integer],10);

        System.out.println("==================");

        method(int[]::new,10);
    }

    public static void method(Function<Integer,int[]> function,Integer len){
        int[] arr = function.apply(len);
        System.out.println(arr.length);
    }
}

第五章.Java9-17新特性

5.1 JDK版本的选择

在Java 17正式发布之前,Java开发框架Spring率先在官博宣布,Spring Framework 6和Spring Boot 3计划在2022年第四季度实现总体可用性的高端基线:

1、Java 17+(来自 Spring Framework 5.3.x 线中的 Java 8-17)

2、Jakarta EE 9+(来自Spring框架5.3.x 线中的 Java EE 7-8)

3.Spring 官方说明:https://spring.io/blog/2022/01/20/spring-boot-3-0-0-m1-is-now-available

image.png Springboot3.0 是需要用Java17和Spring6.0为基础建设。如果从企业选型最新Springboot3.0作为架构来说,它搭配jdk17肯定是标配了。

针对于Spring 6,官网的说明会弃用java8以9为最低版本,而且兼容tomcat10+。

4.JDK17针对于GC方面作出了优化,以及做了性能的提高

a.在吞吐量方面,Parallel 中 JDK 8 和 JDK 11 差距不大,JDK 17 相较 JDK 8 提升 15% 左右;G1 中 JDK 17 比 JDK 8 提升 18%;ZGC 在 JDK 11引入,JDK 17 对比JDK 11 提升超过 20%

image.png b. 在 GC 延迟方面,JDK 17 的提升更为明显。在 Parallel 中 JDK 17 对比 JDK 8 和JDK 11 提升 40%;在 G1 中,JDK 11 对比 JDK 8 提升 26%,JDK 17 对比 JDK 8 提升接近 60%!ZGC 中 JDK 17 对比 JDK 11 提升超过 40%

image.png 从GC性能角度去看,JDK 11对比JDK 8延迟提升不到40%;反观JDK 17对比JDK 8 延迟提升 60%,吞吐量提升 18%;可以看到JDK17的提升还是非常明显的

image.png 由于JDK对性能提升方面都是自动的,所以我们可以直接学习JDK新特性中的语法和API。我们要知道的是下面的语法不都是从JDK17才开始有的,但是JDK17都支持这些语法和API。

查看jdk版本: java -version

5.2 接口的私有方法

Java8版本接口增加了两类成员:

  • 公共的默认方法
  • 公共的静态方法

Java9版本接口又新增了一类成员:

  • 私有的方法

为什么JDK1.9要允许接口定义私有方法呢?因为我们说接口是规范,规范是需要公开让大家遵守的

私有方法:因为有了默认方法和静态方法这样具有具体实现的方法,那么就可能出现多个方法由共同的代码可以抽取,而这些共同的代码抽取出来的方法又只希望在接口内部使用,所以就增加了私有方法。

public interface USB {
    private void open(){
        System.out.println("私有非静态方法");
    }

    private static void close(){
        System.out.println("私有静态方法");
    }

    //定义一个默认方法调用私有方法
    public default void methodDef(){
        open();
        close();
    }
}
public class UsbImpl implements USB{
}
public class Test01 {
    public static void main(String[] args) {
        new UsbImpl().methodDef();
    }
}

5.3 钻石操作符与匿名内部类结合

自Java 9之后我们将能够与匿名实现类共同使用钻石操作符,即匿名实现类也支持类型自动推断

public class Person {
    private String name;
    private Integer age;

    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + ''' +
                ", age=" + age +
                '}';
    }
}
public class Test02 {
    public static void main(String[] args) {
        ArrayList<Person> list = new ArrayList<>();
        list.add(new Person("张三",10));
        list.add(new Person("李四",8));
        list.add(new Person("王五",20));

        Collections.sort(list, new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge()-o2.getAge();
            }
        });

        System.out.println(list);
    }
}

Java 8的语言等级编译会报错:“'<>' cannot be used with anonymous classes。”Java 9及以上版本才能编译和运行正常。

5.4 try..catch升级

之前我们讲过JDK 1.7引入了trywith-resources的新特性,可以实现资源的自动关闭,此时要求:

  • 该资源必须实现java.io.Closeable接口
  • 在try子句中声明并初始化资源对象
  • 该资源对象必须是final的
try(IO流对象1声明和初始化;IO流对象2声明和初始化){
    可能出现异常的代码
}catch(异常类型 对象名){
	异常处理方案
}

JDK1.9又对trywith-resources的语法升级了

  • 该资源必须实现java.io.Closeable接口
  • 在try子句中声明并初始化资源对象,也可以直接使用已初始化的资源对象
  • 该资源对象必须是final的
IO流对象1声明和初始化;
IO流对象2声明和初始化;

try(IO流对象1;IO流对象2){
    可能出现异常的代码
}catch(异常类型 对象名){
	异常处理方案
}
public class Test03 {
    public static void main(String[] args) throws IOException {
        //method01();
        method02();
    }

    /**
     * jdk9开始
     * 为了减轻try的压力,可以将对象放到外面去new,然后将对象名,放到 try中
     * 而且依然能自动刷新和关流
     */
    private static void method02() throws IOException {
        FileWriter fw = new FileWriter("module24\io.txt");
        try(fw){
            fw.write("你好");
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * jdk8之前
     */
    private static void method01() {
        try(FileWriter fw = new FileWriter("module24\io.txt")){
            fw.write("你好");
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

5.5 局部变量类型自动推断

jdk10之前,我们定义局部变量都必须要明确数据的数据类型,但是到了JDK10,出现了一个最为重要的特性,就是局部变量类型推断,顾名思义,就是定义局部变量时,不用先确定具体的数据类型了,可以直接根据具体数据推断出所属的数据类型。

var 变量名 = 值;
public class Test04 {
    public static void main(String[] args) {
        var i = 10;
        System.out.println("i = " + i);

        var j = "helloworld";
        System.out.println("j = " + j);

        var arr = new int[]{1,2,3,4,5};
        for (var element : arr) {
            System.out.println(element);
        }
    }
}

5.6 switch表达式

switch表达式在Java 12中作为预览语言出现,在Java 13中进行了二次预览,得到了再次改进,最终在Java 14中确定下来。另外,在Java17中预览了switch模式匹配。

传统的switch语句在使用中有以下几个问题。

(1)匹配是自上而下的,如果忘记写break,那么后面的case语句不论匹配与否都会执行。

(2)所有的case语句共用一个块范围,在不同的case语句定义的变量名不能重复。

(3)不能在一个case语句中写多个执行结果一致的条件,即每个case语句后只能写一个常量值。

(4)整个switch语句不能作为表达式返回值。

1、Java12的switch表达式

Java 12对switch语句进行了扩展,将其作为增强版的switch语句或称为switch表达式,可以写出更加简化的代码。

  • 允许将多个case语句合并到一行,可以简洁、清晰也更加优雅地表达逻辑分支。

  • 可以使用-> 代替 :

    • ->写法默认省略break语句,避免了因少写break语句而出错的问题。
    • ->写法在标签右侧的代码段可以是表达式、代码块或 throw语句。
    • ->写法在标签右侧的代码块中定义的局部变量,其作用域就限制在代码块中,而不是蔓延到整个switch结构。
  • 同一个switch结构中不能混用“→”和“:”,否则会有编译错误。使用字符“:”,这时fall-through规则依然有效,即不能省略原有的break语句。":"的写法表示继续使用传统switch语法。

案例需求:

请使用switch-case结构实现根据月份输出对应季节名称。例如,3~5月是春季,6~8月是夏季,9~11月是秋季,12~2月是冬季。

Java12之前写法:

@Test
    public void test1() {
        int month = 3;
        switch (month) {
            case 3:
            case 4:
            case 5:
                System.out.println("春季");
                break;
            case 6:
            case 7:
            case 8:
                System.out.println("夏季");
                break;
            case 9:
            case 10:
            case 11:
                System.out.println("秋季");
                break;
            case 12:
            case 1:
            case 2:
                System.out.println("冬季");
                break;
            default:
                System.out.println("月份输入有误!");
        }
    }

Java12之后写法:

private static void method02() {
        int month = 5;
        switch (month) {
            case 12, 1, 2 -> System.out.println("冬季");
            case 3, 4, 5 -> System.out.println("春季");
            case 6, 7, 8 -> System.out.println("夏季");
            case 9, 10, 11 -> System.out.println("秋季");
            default -> System.out.println("有毛病呀,没有这个月份");

        }
    }

    /**
     * 如果用:
     * break不写依然会case穿透性
     */
    private static void method01() {
      int month = 5;
      switch (month){
          case 12,1,2:
              System.out.println("冬季");
              break;
          case 3,4,5:
              System.out.println("春季");
              break;
          case 6,7,8:
              System.out.println("夏季");
              break;
          case 9,10,11:
              System.out.println("秋季");
              break;
          default:
              System.out.println("有毛病呀,没有这个月份");
              break;
      }
    }
2、Java13的switch表达式

Java 13提出了第二个switch表达式预览,引入了yield语句,用于返回值。这意味着,switch表达式(返回值)应该使用yield语句,switch语句(不返回值)应该使用break语句。 案例需求:判断季节。

/**
     * jdk13之后
     */
    private static void method04() {
        int month = 5;
        var seson = switch (month) {
            case 12, 1, 2 -> {
                yield "冬季";
            }
            case 3, 4, 5 -> {
                yield "春季";
            }
            case 6, 7, 8 -> {
               yield "夏季";
            }
            case 9, 10, 11 -> {
               yield "秋季";
            }
            default -> {
               yield "有毛病";
            }
        };
        System.out.println("seson = " + seson);
    }

    /**
     * jdk13之前想要拿到switch结果,需要定义一个变量,然后为其赋值
     */
    private static void method03() {
        int month = 5;
        String season = "";
        switch (month) {
            case 12, 1, 2:
                season = "冬季";
                break;
            case 3, 4, 5:
                season = "春季";
                break;
            case 6, 7, 8:
                season = "夏季";
                break;
            case 9, 10, 11:
                season = "秋季";
                break;
            default:
                season = "有毛病";
                break;
        }
        System.out.println("season = " + season);
    }

5.7 文本块

预览的新特性文本块在Java 15中被最终确定下来,Java 15之后我们就可以放心使用该文本块了。

1、Java13文本块

JDK 12引入了Raw String Literals特性,但在其发布之前就放弃了这个特性。这个JEP与引入多行字符串文字(文本块)在意义上是类似的。Java 13中引入了文本块(预览特性),这个新特性跟Kotlin中的文本块是类似的。

现实问题

在Java中,通常需要使用String类型表达HTML,XML,SQL或JSON等格式的字符串,在进行字符串赋值时需要进行转义和连接操作,然后才能编译该代码,这种表达方式难以阅读并且难以维护。

文本块就是指多行字符串,例如一段格式化后的XML、JSON等。而有了文本块以后,用户不需要转义,Java能自动搞定。因此,文本块将提高Java程序的可读性和可写性。

目标

  • 简化跨越多行的字符串,避免对换行等特殊字符进行转义,简化编写Java程序。
  • 增强Java程序中字符串的可读性。

举例

会被自动转义,如有一段以下字符串:

<html>
  <body>
      <p>Hello</p>
  </body>
</html>

将其复制到Java的字符串中,会展示成以下内容:

"<html>\n" +
"    <body>\n" +
"        <p>Hello</p>\n" +
"    </body>\n" +
"</html>\n";

即被自动进行了转义,这样的字符串看起来不是很直观,在JDK 13中,就可以使用以下语法了:

"""
<html>
  <body>
      <p>Hello, world</p>
  </body>
</html>
""";

使用“”“作为文本块的开始符和结束符,在其中就可以放置多行的字符串,不需要进行任何转义。看起来就十分清爽了。 文本块是Java中的一种新形式,它可以用来表示任何字符串,并且提供更多的表现力和更少的复杂性。

(1)文本块由零个或多个字符组成,由开始和结束分隔符括起来。

  • 开始分隔符由三个双引号字符表示,后面可以跟零个或多个空格,最终以行终止符结束。
  • 文本块内容以开始分隔符的行终止符后的第一个字符开始。
  • 结束分隔符也由三个双引号字符表示,文本块内容以结束分隔符的第一个双引号之前的最后一个字符结束。

以下示例代码是错误格式的文本块:

String err1 = """""";//开始分隔符后没有行终止符,六个双引号最中间必须换行

String err2 = """  """;//开始分隔符后没有行终止符,六个双引号最中间必须换行

如果要表示空字符串需要以下示例代码表示:

String emp1 = "";//推荐
String emp2 = """
   """;//第二种需要两行,更麻烦了

(2)允许开发人员使用“\n”“\f”和“\r”来进行字符串的垂直格式化,使用“\b”“\t”进行水平格式化。如以下示例代码就是合法的。

String html = """
    <html>\n
      <body>\n
        <p>Hello, world</p>\n
      </body>\n
    </html>\n
    """;

(3)在文本块中自由使用双引号是合法的。

String story = """
Elly said,"Maybe I was a bird in another life."

Noah said,"If you're a bird , I'm a bird."
 """;

5.8 instanceof模式匹配

instanceof的模式匹配在JDK14、15中预览,在JDK16中转正。有了它就不需要编写先通过instanceof判断再强制转换的代码。

public abstract class Animal {
    public abstract void eat();
}
public class Dog extends Animal{
    @Override
    public void eat() {
        System.out.println("狗啃骨头");
    }

    //特有方法
    public void lookDoor(){
        System.out.println("狗会看门");
    }
}

从JDK14开始,我们不需要单独强转,直接省略强转的过程

public /*abstract*/ record Person(String name)/* extends Record*/{

    //int i;不能声明实例变量
    static int i;//可以声明静态变量
    
    //不能声明空参构造
   /* public Person(){

    }*/

    public static void method(){//可以声明静态方法
        System.out.println("method");
    }
    public void method01(){//可以声明实例方法(成员方法)
        System.out.println("method01");
    }
}

5.9 Record类

Record类在JDK14、15预览特性,在JDK16中转正。

record是一种全新的类型,它本质上是一个 final类,同时所有的属性都是 final修饰,它会自动编译出get、hashCode 、比较所有属性值的equals、toString 等方法,减少了代码编写量。使用 Record 可以更方便的创建一个常量类。

1.注意:

  • Record只会有一个全参构造
  • 重写的equals方法比较所有属性值
  • 可以在Record声明的类中定义静态字段、静态方法或实例方法(非静态成员方法)。
  • 不能在Record声明的类中定义实例字段(非静态成员变量);
  • 类不能声明为abstract;
  • 不能显式的声明父类,默认父类是java.lang.Record类
  • 因为Record类是一个 final类,所以也没有子类等。
public record Person(String name) {
    //int i;//不能声明实例变量

    static int i;//可以声明静态变量

//不能声明空参构造
/*    public Person(){

    }*/

    //可以声明静态方法
    public static void method(){

    }

    //可以声明非静态方法
    public void method01(){

    }
}
    public class Test01 {
        public static void main(String[] args) {
            Person person = new Person("张三");
            Person person1 = new Person("张三");
            System.out.println(person);
         System.out.println(person.equals(person1));
    }
}

5.10 密封类

其实很多语言中都有密封类的概念,在Java语言中,也早就有密封类的思想,就是final修饰的类,该类不允许被继承。而从JDK15开始,针对密封类进行了升级。

Java 15通过密封的类和接口来增强Java编程语言,这是新引入的预览功能并在Java 16中进行了二次预览,并在Java17最终确定下来。这个预览功能用于限制超类的使用,密封的类和接口限制其他可能继承或实现它们的其他类或接口。

【修饰符】 sealed class 密封类 【extends 父类】【implements 父接口】 permits 子类{
    
}
【修饰符】 sealed interface 接口 【extends 父接口们】 permits 实现类{
    
}
  • 密封类用 sealed 修饰符来描述,
  • 使用 permits 关键字来指定可以继承或实现该类的类型有哪些
  • 一个类继承密封类或实现密封接口,该类必须是sealed、non-sealed、final修饰的。
  • sealed修饰的类或接口必须有子类或实现类
public sealed class Animal permits Dog,Cat{
}

public non-sealed class Dog extends Animal{
}

public non-sealed class Cat extends Animal{
}
package com.atguigu.sealed;

import java.io.Serializable;

public class TestSealedInterface {
}
sealed interface Flyable /*extends Serializable*/ permits Bird {
    
}
non-sealed class Bird implements Flyable{
    
}