跳至主要內容

常见类和接口

wangdx大约 73 分钟GuideMarkdown

StringBuffer 类

  • 如果要是在项目之中使用字符串,90%的情况下所能够使用的字符串的类型一定是 java.lang.Suing 类,但是这个 Sting 类在使用的过程之中会存在有一个设计上的问题:字符串的内容一旦声明则不可改变,如果说现在假设要有字符串的修改要求,那么如果使用 String 的时候就可能会产生垃圾。

  • 如果此时不能够提供有方便的字符串的修改操作,那么对于整个的程序来讲,就会产生大量的垃圾空间,在高并发的系统设计访问之中永远都需要去考虑这一点点的性能优化。。

  • 为了解决 String 内容无法修改的设计性的问题,所以在 JDK 里面针对于字符串又提供了另外一个处理类:java.lang.StringBuffer 类,该类是在 JDK1.0 的时候提供的,首先来观察一下 StringBuffer 类

案例
public class Demo {
    public static void main(String[] args) {
        StringBuffer buffer = new StringBuffer();
        buffer.append("沐言科技:").append("www.yootk.com").append("\n") ; // 字符串连接
        change(buffer); // 引用传递
        System.out.println(buffer);
    }

    public static void change(StringBuffer temp) {
        temp.append("李兴华编程训练营:").append("yootk.ke.qq.com") ; // 修改StringBuffer内容
    }
}

通过此时的执行结果可以清楚的发现,程序代码已经可以正确的进行了 StringBuffer 的修改,而且最关键的问题即便调用了 change() 方法,那么最终也可以实现 StringBuffer 内容的变更,所以得出的结论就是 StringBuffer 的内容可以进行变更,如果此时同样的操作变为了 String,change()方法对字符串的修改一定无法返回。

案例
class Test{
    public static void main(String[] args) throws Exception {
        String message = "沐言科技:www.yootk.com\n" ; // 字符串
        change(message) ;
        System.out.println(message);
    }
    public static void change(String temp) {  // 接收StringBuffer引用
        temp += "李兴华编程训练营:yootk.ke.qq.com" ;
    }
}
  • StringBuffer 所谓的可以修改长度实际上都是有一个限制范围的,必须预估可能保存的字符串长度,否则一样会有产生垃圾的可能性。既然 StringBuffer 内部所采用的处理机制是字节数组的方式,那么对于字节数组在进行字符串控制的时候就会比较方便了,例如:字符串反转、指定范围的内容的删除或者是替换等等都很容易实现。
案例
package com.yootk.demo;
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        StringBuffer buffer = new StringBuffer(30) ;  // 可以容纳30个长度的字符串
        buffer.append("李兴华编程训练营:").append("yootk.ke.qq.com") ;
        System.out.println(buffer.reverse()); // 字符串反转(数组反转)
    }
}

package com.yootk.demo;
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        StringBuffer buffer = new StringBuffer(30) ;  // 可以容纳30个长度的字符串
        buffer.append("yootk.ke.qq.com").insert(0, "李兴华编程训练营:") ;
        System.out.println(buffer);
    }
}

package com.yootk.demo;
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        StringBuffer buffer = new StringBuffer(30) ;  // 可以容纳30个长度的字符串
        buffer.append("yootk.ke.qq.com").insert(0, "李兴华编程训练营:") ;
        System.out.println(buffer.replace(9,25, "www.yootk.com"));
        System.out.println(buffer.delete(0, 9));
    }
}

String 与 StringBuffer 区别?

  • String 与 StringBuffer 都可以实现字符串的保存,但是如果从开发的角度来讲比较常见的还是 String;
  • String 最大的特点是一旦声明内容则一定不可改变,所改变的只能够是字符串的引用:
  • StringBuffer 最大的特点是可以进行内容的改变,它的改变是需要进行严格的长度控制的,如果超过了指定的长度限制,也会自动进行扩容。
  • StringBufer 类中还提供有一些 String 所不具备的操作方法,例如:reverse()、delete()

StringBuilder 类

在 Java 之中除了使用 StringBuffer 类可以实现内容的修改之外,实际上也可以通过 StringBuilder 类来实现可变的字符串内容的修改操作,StringBuilder 类是在 JDK1.5 的时候提供给用户使用的,通过整个的 JavaDoc 文档中可以发现,StringBuilder 中支持的处 理方法定义和 StringBuffer 类中的方法很相似。

案例
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        StringBuilder buffer = new StringBuilder(30) ;  // 可以容纳30个长度的字符串
        buffer.append("yootk.ke.qq.com").insert(0, "李兴华编程训练营:") ;
        System.out.println(buffer.replace(9,25, "www.yootk.com"));
        System.out.println(buffer.delete(0, 9));
    }
}

经过了当前的分析之后可以发现,StringBuffer 与 StringBuilder 类的功能都是非常相似的,甚至连方法的名称也都是非常相似的,于是很多的小伙伴们说了,为什么要提供有两个不同的类?通过源代码的分析观察可以发现 StringBuffer 类中的方法全部都提供了 synchronized,而 StringBuilder 类中的方法并没有提供 synchronized 同步的处理操作,那么就可以得出一个结论:StringBuffer 类属于线程安全的类,而 StringBuilder 类属于非线程安全的类,为了更好的解释多线程的安全问题,下面通过两端代码进行对比。

案例
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        StringBuilder builder = new StringBuilder(30) ;  // 可以容纳30个长度的字符串
        for (int x = 0 ; x < 5 ; x ++) {
            new Thread(() -> {
                builder.append(Thread.currentThread().getName() + "\n") ;
            }, "线程对象-" + x).start();
        }
        Thread.sleep(1000); // 等待线程执行完毕
        System.out.println(builder);
    }
}

yix-0
yix-4
yix-3
      yix-1

class Test{
    public static void main(String[] args) throws InterruptedException {
        StringBuffer buffer = new StringBuffer(30);
        for (int x = 0; x < 5; x++) {
            new Thread(() -> {
                buffer.append(Thread.currentThread().getName() + "\n");
            }, "yix-" + x).start();
        }
        Thread.sleep(1000);
        System.out.println(buffer);
    }
}

yix-0
yix-4
yix-1
yix-2
yix-3

由于 StringBuilder 属于非线程安全的程序类,所以在通过多线程进行访问的过程之中实际上就不保证所有的数据可以正确的进行存储(StringBuilder 内部是一个字节数组,字节数组在进行保存的时候就需要根据保存的大小不断的扩容)。

StringBuffer 与 StringBuilder 区别?

  • StringBuffer 与 StringBuilder 都可以实现字符串内容的修改处理操作;
  • StringBuffer 属于线程同步的处理过程,多个线程进行修改的时候可以自动实现同步机制,保证数据修改的正确性:
  • StringBuilder 属于非线程安全的处理,没有进行同步的操作,多个线程同时修改的时候一定会有数据的修改错误。

CharSequence 接口

在之前讲解的字符串相关类:String、StringBuffer、StringBuilder,实际上这些类都和 CharSequence 接口有关系,下面首先来 观察这三个类的定义结构:

通过定义的分析可以发现,这三个字符串的类都实现了一些相同的父接口,而这些接口里面就存在有 CharSequence 父接口,该接口是在 JDK1.4 的时候提供的,而 String、StringBuffer 类都是在 JDK1.0 的时候提供的,在 JDK1.4 之后,Java 设计者发现了对于字符串需要进行一些更加规范化的定义,所以才有了 CharSequence 接口,而随着技术的发展,在 CharSequence 接口中现在的方法也比最早的时候要多。

案例
public class Demo {
    public static void main(String[] args) {
        print("沐言优拓:www.yootk.com");
        print(new StringBuffer("沐言优拓:www.yootk.com"));    // 接收字符串
        print(new StringBuilder("沐言优拓:www.yootk.com"));    // 接收字符串
    }

    public static void print(CharSequence sequence) {
        System.out.println("old str = " + sequence);
        System.out.println("mod str = " + sequence.subSequence(5, 12));
    }
}

不管未来的 Java 如何设计,只要你在项目之中见到了 CharSequence,99%的情况下的它都是和字符串几乎对等的概念,但是需要注意的是,虽然所有的 CharSequence 接口子类可以同时为 CharSequence 接口对象实例化,可是这不同的子类之间是无法直接进行对象转换的,那么可以依靠构造方法的形式完成转换。

String、StringBuilder、StringBuffer 类之间的互相转换原则

  • String 转为 StringBuffer,StringBuilder
    • public StringBuffer(String str).
    • String 转为 StringBuilder:public StringBuilder(String str)
  • 将 StringBuffer、StringBuilder 转为字符串,那么有两种做法:
    • 直接利用类中提供的 toString()方法实现转换处理(常见的做法);
    • 利用 String 类的构造方法:
      • StringBuffer 转为 String: public String(StringBuffer buffer)
      • StringBuilder 转为 String: public String(StringBuilder builder)
案例
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String str = "沐言优拓:www.yootk.com" ; // 字符串对象
        StringBuffer buffer = new StringBuffer(str) ; // String转为StringBuffer
        System.out.println(buffer.delete(0, 5));    // StringBuffer类的方法
        String message = buffer.toString() ; // toString()方法转换是最直接的做法
        System.out.println(message.toUpperCase()); // String类的方法
    }
}

AutoCloseable 接口

在实际的项目开发过程之中,一般都有可能连接到一些资源,例如:文件资源、网络资源、数据库资源,那么在实际项目之中在进行资源访问的时候一般都有如下的几个操作步骤。.

在程序设计之中所有的资源都是有访问上限的,一旦达到了这个上限,那么未来就有可能出现无法继续连接的问题了,这个时候就需要对所有的资源进行及时的释放,下面模拟这样的处理机制。

案例
public interface IMessage {
    public void send(String msg);
}
class NetMessage implements IMessage{
    public NetMessage(){
        // 当前的设计为:只要现在实例化了此类对象就表示要进行消息的发送,则就需要连接网络服务器
        System.out.println("【连接】连接远程服务器,创建消息的发送通道...");
    }
    @Override
    public void send(String msg) {
        System.out.println("【发送】" + msg); // 模拟数据的发送
    }
    public void close() {   // 关闭资源
        System.out.println("【关闭】网络消息发送完毕,断开服务器连接...");
    }
}
class Test{
    public static void main(String[] args) {
        NetMessage message = new NetMessage() ; // 创建消息发送类
        message.send("沐言优拓:www.yootk.com"); // 核心业务
        message.close();
    }
}

【连接】连接远程服务器,创建消息的发送通道...
【发送】沐言优拓:www.yootk.com
【关闭】网络消息发送完毕,断开服务器连接...

按照正常的结构设计来讲,当前的程序已经可以满足于整个设计上的开发要求了,因为有了正常的连接,同时又可以进行服务器务器断开访问,虽然这个时候已经可以正常实现网络消息的处理模型,但是当前的程序本身又存在有另外几个问题:

  • 按照正常的设计开发的结构来讲,此时的程序应该是面向接口的开发,应该在接口里面提供有所有的核心业务功能;
  • 如果此时通过子类获取了 IMessage 接口对象,那么会发现无法正常的进行 close()方法调用。
  • 既然所有的资源在操作完毕之后都需要关闭,那么能否提供有一种自动关闭的机制呢?。 那么根据以上的设计需求来讲核心就一句话:每一次手工调用 close() 关闭资源实在是过于繁琐了,所以最佳的做法是通过某种机制自动实现关闭处理,这样在 jdkK1.7 版本中提供了一个新的接口 AutoCloseable。但是这种自动关闭的处理机制是需要有其特定的运行条件:必须结合异常处理语句才可以正常触发关闭操作机制。
案例
public interface IMessage extends AutoCloseable {
    public void send(String msg);
}
class NetMessage implements IMessage{
    public NetMessage(){
        // 当前的设计为:只要现在实例化了此类对象就表示要进行消息的发送,则就需要连接网络服务器
        System.out.println("【连接】连接远程服务器,创建消息的发送通道...");
    }
    @Override
    public void send(String msg) {
        if (msg.contains("yootk")) {    // 手工异常抛出
            throw new RuntimeException("沐言优拓:yootk.com") ;
        }
        System.out.println("【发送】" + msg); // 模拟数据的发送
    }
    public void close() {   // 关闭资源
        System.out.println("【关闭】网络消息发送完毕,断开服务器连接...");
    }
}
class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        try (NetMessage message = new NetMessage()) {
            message.send("沐言优拓:www.yootk.com"); // 核心业务
        } catch (Exception e) {}
    }
}

Runtime 类

描述的是一种运行时,在 Java 程序执行过程之中,所有的 java 程序都一定要运行在 JVM 的进程之中,有了这个 JVM 进程,那么就需要有一种类型可以描述当前进程的相关的环境以及与之相关的处理操作,这样在 Java 设计的时候就设计了一个 Runtime.每一个 JVM 的进程之中都会自动包含有一个 Runtime 类的实例化对象,打开 Runtime 类之后就可以见到相关的文档。 按照传统的开发的做法来讲,每一个 JavaDoc 文档之中应该按照“成员”、“构造”、“方法”的形式进行类中的结构展示,而现在如果直接打开 Runtime 类之后可以清楚的发现,此时直接给出的是“方法”摘要,因为 Runtime 类中的构造方法被隐藏了,来观察一下 Runtime 类

private Runtime() {}

可以发现此时 Runtime 类中的构造方法已经被默认设置为了私有化的状态,所以就可以得出结论,Runtime 类应该属于单例设计模式(如果真的是多例设计,在新的开发版本之中一定会使用枚举来描述)。之所以将 Runtime 类设计为单例设计模式,主要的原因在于每一个 JVM 进程只需要提供有一个 Runtime 类的实例化对象。

通过以上的方法列表可以发现在 Runtime 类中可以直接通过 exec() 方法创建一个新的子进程,而如果要想对这个子进程进行控制,则可以依据 Process 类来完成。

案例
public class Demo {
    public static void main(String[] args) throws Exception {
        Runtime runtime = Runtime.getRuntime(); // 获取Runtime类的对象实例
        // 每当通过Runtime启动了一个进程之后实际上会返回有一个进程对象
        Process process = runtime.exec("notepad.exe"); // 执行Windows系统中的命令
        Thread.sleep(2000); // 运行2秒的进程
        process.destroy(); // 销毁进程
    }
}

在 Java 发展的鼎盛初期由于其语言的魅力很大,所以在 Java 的内部就提供有一系列的底层的处理支持,这样就方便一些程序开发人员进行可执行脚本的定义,但是慢慢人们发现好像 Java 做这种脚本的编程不是很方便,所以为什么 Pvthon 这几年比较火。原因就在于:Java 不方便干的事情由 Python 的小弟来完成。 在以后实际的项目开发过程之中,对于 Runtime 类来讲比较重要的操作就在于内存信息的动态获取,因为整个 Java 程序的正确执行都需要依靠内存的支持,如果内存不能够保证正常运行环境,那么程序就会出现严重的性能问题,甚至宕机。

案例
class Test{
    public static void main(String[] args) {
        Runtime runtime = Runtime.getRuntime(); // 获取Runtime类的对象实例
        System.out.println("【1】MaxMemory = " + runtime.maxMemory());
        System.out.println("【1】TotalMemory = " + runtime.totalMemory());
        System.out.println("【1】FreeMemory = " + runtime.freeMemory());
    }
}1MaxMemory = 10703863808  10G,默认为物理内存的四分之一
【1TotalMemory = 668991488 668M,默认物理内存的六十四分之一
【1FreeMemory = 664164952

实际上现在所给出的内存关系基本的定义:整个 JVM 进程的可用最大内存(MaxMemory)>默认的可用内存(TotalMemory,这个值是会改变的,但是最终不超过 MaxMemory)>空闲内存(FreeMemory),如果空闲内存不足则 TotalMemory 也会进行动态的扩充(这种动态的扩充机制实际上是非常伤害程序的)。

案例
class Test1{
    public static void main(String[] args) {
        Runtime runtime = Runtime.getRuntime(); // 获取Runtime类的对象实例
        System.out.println("【1】垃圾产生前的内存信息:MaxMemory = " + runtime.maxMemory());
        System.out.println("【1】垃圾产生前的内存信息:TotalMemory = " + runtime.totalMemory());
        System.out.println("【1】垃圾产生前的内存信息:FreeMemory = " + runtime.freeMemory());
        String message = "www.yootk.com" ; // 定义字符串
        for (int x = 0 ; x < Integer.MAX_VALUE ; x ++) {
            message += message + x + "\n" ; // 产生大量的垃圾
        }
        System.out.println("【2】垃圾产生后的内存信息:MaxMemory = " + runtime.maxMemory());
        System.out.println("【2】垃圾产生后的内存信息:TotalMemory = " + runtime.totalMemory());
        System.out.println("【2】垃圾产生后的内存信息:FreeMemory = " + runtime.freeMemory());
    }
}1】垃圾产生前的内存信息:MaxMemory = 107038638081】垃圾产生前的内存信息:TotalMemory = 6689914881】垃圾产生前的内存信息:FreeMemory = 664165936
Exception in thread "main" java.lang.OutOfMemoryError: Overflow: String length out of range
	at java.base/java.lang.StringConcatHelper.checkOverflow(StringConcatHelper.java:46)
	at java.base/java.lang.StringConcatHelper.mixLen(StringConcatHelper.java:118)
	at com.yix.commonClass.w05.Test1.main(Demo.java:31)

如果此时你的内存空间已经被垃圾空间严重沾满了,并且来不及进行扩充,那么就会出现如下的异常信息,这就属于内存溢出的问题,而这种 OOM 问题解决就需要通过 JVM 调优来完成。

案例
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        Runtime runtime = Runtime.getRuntime(); // 获取Runtime类的对象实例
        System.out.println("【1】垃圾产生前的内存信息:MaxMemory = " + runtime.maxMemory());
        System.out.println("【1】垃圾产生前的内存信息:TotalMemory = " + runtime.totalMemory());
        System.out.println("【1】垃圾产生前的内存信息:FreeMemory = " + runtime.freeMemory());
        String message = "www.yootk.com" ; // 定义字符串
        for (int x = 0 ; x < 20 ; x ++) {
            message += message + x + "\n" ; // 产生大量的垃圾
        }
        runtime.gc(); // 手工进行垃圾清除
        System.out.println("【2】GC调用之后的内存信息:MaxMemory = " + runtime.maxMemory());
        System.out.println("【2】GC调用之后的内存信息:TotalMemory = " + runtime.totalMemory());
        System.out.println("【2】GC调用之后的内存信息:FreeMemory = " + runtime.freeMemory());
    }
}

当使用 gc()进行垃圾清除之后可以发现空闲空间已经有了回收,这样就可以便于后续程序的高效执行。 Java 之中的所有的 GC 属于守护线程,守护线程是伴随主线程而存在的,主要的目的是进行垃圾的收集以及堆内存空间释放;如果要进行 gc 的处理在 JVM 进程中有两种处理形式:一种是自动的垃圾收集(需要清除整个的 JVM 体系结构)、另外一种属于手工的垃圾收集(Runtime 类中的 gcO 方法),而实际的开发之中基本上 GC 都建议使用自动的清除机制。

System 类

在使用 System 类的时候有一个非常重要的功能就是统计某一个操作的耗时时间,在 System 类中提供有一个 curentTimeMilis() 方法,这个方法可以获取当前的时间戳,这样就能够在某些操作执行前进行一次方法调用,而执行之后再进行一次方法调用,将两个调用的结果进行减法处理即可。

案例
class SystemTest{
    public static void main(String[] args) {
        String message = "www.yootk.com" ;
        long start = System.currentTimeMillis() ;   // 在操作开始前获取时间戳
        for (int x = 0 ; x < 99999 ; x ++) {
            message += x ; // 字符串的修改
        }
        long end = System.currentTimeMillis() ; // 在操作开始后获取时间戳
        System.out.println("本次程序执行的耗时统计:" + (end - start));
    }
}

本次程序执行的耗时统计:2405

利用以上的程序的处理形式就可以轻松的实现耗时的统计,现在的统计是直接写在了程序的内部,如果更高级的做法可以通过切面的形式来控制。 在一个 JVM 进程运行的过程之中,如果现在假设说出现了某些问题,那么就可以通过 exit() 让 JVM 直接结束,而 exit() 并不是 System 类本身所提供的方法,而是由 Runtime 类提供的。

案例
class SystemTest2{
    public static void main(String[] args) {
        if (args.length != 2) { // 通过程序接收初始化参数
            System.out.println("【错误】本程序执行时需要传递初始化的运行参数,否则无法运行!");
            System.out.println("【提示】可以按照如下的方式运行:java YootkDemo 字符串 重复次数");
            System.exit(1); // 程序退出
        }
        String message = args[0] ; // 获取一个参数内容
        int count = Integer.parseInt(args[1]) ; // 循环次数
        for (int x = 0 ; x < count ; x ++) {
            System.out.println(message);
        }
    }
}
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        System.out.println("【1】垃圾产生前的内存信息:FreeMemory = " + Runtime.getRuntime().freeMemory());
        String message = "www.yootk.com" ; // 定义字符串
        for (int x = 0 ; x < 20 ; x ++) {
            message += message + x + "\n" ; // 产生大量的垃圾
        }
        System.gc(); // 手工进行垃圾清除
        System.out.println("【2】GC调用之后的内存信息:FreeMemory = " + Runtime.getRuntime().freeMemory());
    }
}

Cleaner 类

案例
package com.yootk.demo;
class Book {
    public Book() {
        System.out.println("【构造】用心编写了一本优秀的原创技术图书。");
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("【析构】图书使用完毕了,可以销毁变为手纸了。");
        throw new Exception("这本图书还有用,不要毁灭。") ; // 手工抛出了异常
    }
}
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        Book book = new Book(); // 实例化新的类对象
        book = null ; // 断开堆内存的指向,变为垃圾空间
        // 如果不在此处手工调用gc()回收,则程序执行的时候就需要等待自动的GC回收,时间是不可控的
        System.gc(); // 垃圾回收
    }
}

以上的这种程序的做法都是在 JDK1.9 以前提供的处理形式,但是这样的做法一直以来都存在有严重的问题,如果现在假设说在 finalize()里面出现一些线程的死锁操作,那么就有可能造成垃圾回收的失败,同时也会产生严重的线程阻塞问题,那么为了解决这样的问题在 JDK1.9 之后决定,对于垃圾回收最佳的做法是启动一个专属的回收线程,这样才有了 java.lang.ref.Cleaner 类的设计结构。

案例
public class Book implements Runnable { //设计有一个回收线程
    public Book() {
        System.out.println("【构造】用心自学成才");
    }

    public void read() {
        System.out.println("【读书】认真学习李兴华老师的Java编程。");
    }

    @Override
    public void run() {//真正地回收由线程来完成
        System.out.println("【析构】图书使用完毕,可以销毁变手纸");

    }
}

class BookCleaner implements AutoCloseable { // 必须实现AutoCloseable接口
    private static final Cleaner cleaner = Cleaner.create();// 创建一个回收对象
    private Cleaner.Cleanable cleanable;

    public BookCleaner(Book book) {
        this.cleanable = cleaner.register(this, book);// 注册一个回收线程
    }

    @Override
    public void close() throws Exception {
        this.cleanable.clean(); // 释放的时候进行垃圾的清除
    }
}
class Test{
    public static void main(String[] args) {
        Book book = new Book(); // 实例化新的类对象
        try (BookCleaner bc = new BookCleaner(book)) {
            book.read(); // 可以在中间进行一些对象的处理操作
        } catch (Exception e) {}
    }
}

【构造】用心自学成才
【读书】认真学习李兴华老师的Java编程。
【析构】图书使用完毕,可以销毁变手纸

随着后续的 Java 技术的发展,很多传统类结构的设计一定要有所改变,因为现在的程序编写的越来越庞大,同时业务的繁琐程度也相当的高,以及硬件和网络通讯水平的不断发展,程序语言内部的结构升级必然是整个行业的趋势。

对象的生命周期

经过之前的分析基本上已经清除对象的创建以及回收的处理操作,那么下面就可以给出 java 之中对象的生命周期流程。

  • 创建阶段:每当使用关键字 new 就表示要开辟新的堆内存空间,同时每一个新的对象实例化的时候都一定要去执行类中的构造方法,构造方法的目的是为了类中成员属性的初始化;- 应用阶段:利用指定的对象名称可以直接进行类之中的方法的调用处理;
  • 不可见阶段:如果说现在某一个方法内部有了一个对象,则该方法执行完毕后该对象将不再使用:
  • 不可达阶段:某一块堆内存已经不再有任何的栈内存所指向,那么这块空间就将成为垃圾空间:
  • 收集阶段:JVM 会自动的进行此块垃圾空间的标记,标记之后将准备通过 GC 回收释放,JDK1.8 及以前的版本使用的都是 finalize() 方法,而 JDK1.9 及以后的版本推荐使用的 Cleaner 来完成:
  • 释放阶段:JVM 重新回收垃圾的堆内存空间,供后续的新的对象使用。

对象克隆

  • 所谓的对象克隆描述的概念就是进行对象的赋值,当一个对象创建完成之后实际上都会自动的开辟堆内存空间,在每一块堆内存空间里面都会保存有对象的相关属性内容,所谓的对象克隆它描述的就是一个属性的赋值。
  • 如果要想完成对象的克隆操作实际上它不需要由用户特别复杂的进行处理,因为在 Object 类里面提供有一个专属的对象克隆的处理方法,此方法定义如下:
protected Object clone() throws CloneNotSupportedException

通过 clone()方法的定义可以发现,这个方法的操作实质上是直接利用了 JVM 底层完成的内存空间的复制处理,如果要想复制对象就必须使用指定的方法完成,同时该方法上会抛出有一个“c1oneNotsupportedException”异常,如果要克隆对象所处的类没有实现 Cloneable 接口,那么就会出现这个异常,这个 Cloneable 接口是在 JDK1.0 的时候就提供的支持,同时该接口中并没有提供任何的处理方法,所以此接口属于一种标识性接口,表示一种能力。

案例
package com.yootk.demo;
class Emp implements Cloneable { // 该类的对象实例允许克隆
    private String ename ;
    private String job ;
    public Emp(String ename, String job) {
        this.ename = ename ;
        this.job = job ;
    }
    @Override
    public String toString() {  // 父类中的toString()可以返回一个内存编码
        return "【Emp - " + super.toString() + "】姓名:" + this.ename + "、职位:" + this.job;
    }
    @Override
    public Object clone() throws CloneNotSupportedException {   // 让此方法对其他外部的类可见
        return super.clone(); // 调用父类方法
    }
}
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        Emp empA = new Emp("李兴华", "软件编程讲师") ; // 原始对象
        Emp empB = (Emp) empA.clone() ; // 对象克隆
        System.out.println(empA);
        System.out.println(empB);
    }
}

此时两个对象的编码内容不同,所以得出的结论就是:利用 empA 对象的堆内存空间拷贝得到了 empB 对象的新的堆内存空间,同时两个空间中的属性内容完全相同。 在实际的程序开发过程之中如果要想进行对象克隆的处理一般会有两种做法:

  • 深克隆、
  • 浅克隆。
案例
package com.yootk.demo;
class Emp implements Cloneable { // 该类的对象实例允许克隆
    private String ename ;
    private String job ;
    public Emp(String ename, String job) {
        this.ename = ename ;
        this.job = job ;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    @Override
    public String toString() {  // 父类中的toString()可以返回一个内存编码
        return "【Emp - " + super.toString() + "】姓名:" + this.ename + "、职位:" + this.job;
    }
    @Override
    public Object clone() throws CloneNotSupportedException {   // 让此方法对其他外部的类可见
        return super.clone(); // 调用父类方法
    }
}
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        Emp empA = new Emp("李兴华", "软件编程讲师") ; // 原始对象
        Emp empB = (Emp) empA.clone() ; // 对象克隆
        empB.setEname("爆可爱的小李老师"); // 修改一个对象的信息
        System.out.println(empA);
        System.out.println(empB);
    }
}

3package com.yootk.demo;
class Dept {    // 部门类
    private String dname ;
    private String loc ;
    public Dept(String dname, String loc) {
        this.dname = dname ;
        this.loc = loc ;
    }
    public void setDname(String dname) {
        this.dname = dname;
    }
    @Override
    public String toString() {
        return "【Dept - "+super.toString()+"】部门名称:" + this.dname + "、部门位置:" + this.loc;
    }
}
class Emp implements Cloneable { // 该类的对象实例允许克隆
    private String ename ;
    private String job ;
    private Dept dept ; // 与部门引用
    public Emp(String ename, String job, Dept dept) {
        this.ename = ename ;
        this.job = job ;
        this.dept = dept ;
    }

    public Dept getDept() {
        return dept;
    }

    @Override
    public String toString() {  // 父类中的toString()可以返回一个内存编码
        return "【Emp - " + super.toString() + "】姓名:" + this.ename + "、职位:" + this.job;
    }
    @Override
    public Object clone() throws CloneNotSupportedException {   // 让此方法对其他外部的类可见
        return super.clone(); // 调用父类方法
    }
}
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        Dept dept = new Dept("沐言科技教学研发部", "北京") ; // 部门对象
        Emp empA = new Emp("李兴华", "软件编程讲师", dept) ; // 原始对象
        Emp empB = (Emp) empA.clone() ; // 对象克隆
        dept.setDname("李兴华编程训练营");
        System.out.println(empA);
        System.out.println("\t|- " + empA.getDept());
        System.out.println("----------------------- 避免晕眩的分割线 -----------------------");
        System.out.println(empB);
        System.out.println("\t|- " + empA.getDept());
    }
}

Math 类

java.ang.Math 类是在整个 Java 之中提供的一个数学计算的程序功能类,利用这个程序功能类可以非常方便的执行一些基础的数学计算:对数、三角函数、开方,这个类是在 JDK1.0 的时候提供给我们用户使用的,同时在这个类中不提供有构造方法。

  • 类构造方法私有化的原因有两点:.
    • 需要控制当前类中的实例化对象的个数,所谓的单例设计模式、多例设计模式;
    • 类中没有提供普通的成员属性,并且类中的成员都是 static 声明,方法也采用的 static 声明。

Math 类成员里面提供有两个公共的全局常量:E、PI。 除了给出的常量之外,在 Math 类中全部的方法几乎都是 static 类型定义的方法,那么下面来通过具体的程序观察这些计算公式的使用。

案例
public class MathTest {
    public static void main(String[] args) {
        System.out.println("【绝对值】" + Math.abs(-10.3));
        System.out.println("【最大值】" + Math.max(8, 98));
        System.out.println("【最小值】" + Math.min(8.9, 89.65));
        System.out.println("【正弦值】" + Math.sin(3.56));
        System.out.println("【对数值】" + Math.log(20));
    }
}

在使用 Math 类实现四舍五入处理的过程之中,如果要操作的数值为负数,那么这个时候如果小数点的内容超过了“0.5”,则会自动的进行进位处理。但是虽然在 Mat 类中提供有四舍五入的处理方法,可是这个四舍五入的处理方法会将全部的小数位进行整体的进位处理,这样的处理模式一定是存在有问题的。 例如:有一家公司每年的收入都是以亿为单位的,今年的收入达到了 3.467812 亿,如果说要使用了 Math.round()方法实现了四舍五入,最终就表示只有 3 亿的收入,那么请问其余的 4000 多万去了那里?如果要想解决这样的问题就需要开发者自己去定义一个新的四舍五入的处理方法。

案例
class MathUtil {
    private MathUtil() {
    }
    /**
     * 进行准确位数的四舍五入的处理操作
     * @param num 表示要进行处理的数字
     * @param scale 表示要保留的小数位数
     * @return 四舍五入处理后的结果
     */
    public static double round(double num, int scale) {
        return Math.round(num * Math.pow(10.0, scale)) / Math.pow(10.0, scale);
    }
}
class Test{
    public static void main(String[] args) {
        System.out.println("【四舍五入】"+MathUtil.round(15.3829489, 3));
    }
}

在本工具类里面可以发现最终还是依靠 Math 类实现了四舍五入的处理操作,在 Math 类中由于 round() 方法不保留有小数位数, 所以首先让数据进行一个整倍的扩充,随后在计算完成后再除以相应的倍数,就得到了正确的四舍五入结果。

Random 类

实际上各个编程语言里面都会存在有随机数的处理支持,利用随机数可以方便的生成一些临时的内容,在现实的生活之中比较常见的随机数的应用就在于:验证码,在 Java 里面如果要想实现随机数的操作,就可以通过 java.util.Random 类来完成处理操作。这个类一般在使用的时候往往都会通过无参构造方法进行对象的实例化,随后调用 nextInt 方法生成随机整数:

案例
class RandomTest {
    public static void main(String[] args) {
        Random random = new Random();
        for (int x = 0; x < 10; x++) {
            System.out.print(random.nextInt(100) + "、");
        }
    }
}

此时利用 Random 实现了随机数的生成操作,这样一来每一次执行的结果都是不同的,在现实生活中有一种“骗傻子钱”的模式,所谓的彩票业务,那么在彩票业务里面有一种随机运气,例如:早期有一种彩票称为 36 选 7,就可以通过随机数的方式来进行生成,而所生成的数据之中肯定是不能包含有 0,同时不能包含有重复的号码。考虑到面向对象的设计结构来说,最佳的做法是要有一个彩票的工具类,这个工具类,可以自动随机生成一组彩票的信息。

案例
class LotteryTicket {  // 彩票工具类
    // 因为随机数字生成的时候会存在有0,或者会存在有重复的数据,那么数组的索引必须单独控制
    private int index; // 手工控制生成索引
    private int[] data; // 保存最终生成的彩票数据
    private Random random = new Random();

    public LotteryTicket() {
        this.data = new int[17]; // 动态初始化
    }

    public void random() {  // 随机生成彩票数据
        while (this.index < this.data.length) { // 还需要持续生成
            int code = this.random.nextInt(37);// 1 ~ 36为彩票数据的生成范围
            if (this.isExists(code)) {
                this.data[this.index++] = code;
            }
        }
    }

    private boolean isExists(int code) {
        if (code == 0) {// 不判断0
            return false; // 表示该数值不可用
        }
        for (int temp : this.data) {
            if (temp == code) {
                return false;// 表示该数值不可用
            }
        }
        return true;
    }

    public int[] getData() {
        Arrays.sort(this.data);  // 数组排序
        return data;
    }
}

class Test1{
    public static void main(String[] args) {
        LotteryTicket lotteryTicket = new LotteryTicket() ;
        lotteryTicket.random(); // 随机生成彩票数据
        for (int temp : lotteryTicket.getData()) {
            System.out.print(temp + "、");
        }
    }
}

大数字处理类

如果说现在要是进行正常的程序开发,基本上程序语言里面所提供数据类型都够能够满足开发人员的需求,而且在整个的编程语言里面中的 double 数据类型可以保存的范围是非常庞大的(double 保存数据范围:4.9E-324~1.7976931348623157E308),但是如果说现在某些的程序计算里面有可能需要执行更大的数字计算操作,那么这种情况下就不可能采用常规的形式完成处理了,所以按照传统编程语言设计的模型来讲,此时一般都会利用字符串实现大数字数据的保存(利用字符的操作模拟实现各种数学计算)。为了解决这样的设计专门提供了一个 java.math 的包,而在这个包里面提供有两个程序类:BigInteger(大整数操作类)、BigDecimal(大小数操作类)。

案例
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        System.out.println(Double.MAX_VALUE);
        System.out.println(Double.MIN_VALUE);
    }
}

BigInteger

案例

class BigData{
    public static void main(String[] args) {
        BigInteger bigA = new BigInteger("2378902389023");// 大数字类
        BigInteger bigB = new BigInteger("723782378");// 大数字类
        System.out.println("【加法计算】" + bigA.add(bigB));
        System.out.println("【减法计算】" + bigA.subtract(bigB));
        System.out.println("【乘法计算】" + bigA.multiply(bigB));
        System.out.println("【除法计算】" + bigA.divide(bigB));
        // 当前给定的两个数字类对象是不可能进行整除处理的,所以可以采用余数的形式进行除法计算
        BigInteger result [] = bigA.divideAndRemainder(bigB) ; // 计算商和余数
        System.out.println("【除法计算】商:" + result[0] + "、余数:" + result[1]);
    }
}

所有的大数字类都可以完成普通计算的基本操作支持,但是如果要是想执行更加繁琐的大数字的计算操作,肯定要额外使用第三方的程序组件包来完善程序功能。

BigDecimal

BigDecimal 类所描述的是一个小数,但是这个类所具备的功能要比 BigInteger 丰富,尤其是在构造方法的定义上:

案例
class BidDecimal{
    public static void main(String[] args) {
        BigDecimal bigA = new BigDecimal("2378902389023.2323231") ; // 大数字类
        BigDecimal bigB = new BigDecimal("723782378.7237238") ; // 大数字类
        System.out.println("【加法计算】" + bigA.add(bigB));
        System.out.println("【减法计算】" + bigA.subtract(bigB));
        System.out.println("【乘法计算】" + bigA.multiply(bigB));
        System.out.println("【除法计算】" + bigA.divide(bigB, RoundingMode.HALF_UP));
    }
}

通过当前的程序代码的执行可以非常清楚的发现,在使用 BigDecimal 类的处理过程之中,需要针对于除法进行四舍五入的进位配置,所以对于四舍五入的功能处理也可以通过 BigDecimal 类的方式来完成。

案例
/**
 * 该类是一个自定义的数学工具类,可以弥补Math类的不足
 * @author 李兴华
 */
public class MathUtil {
    private MathUtil() {}   // 不存在成员属性,构造方法私有化
    /**
     * 进行准确位数的四舍五入的处理操作
     * @param num 表示要进行处理的数字
     * @param scale 表示要保留的小数位数
     * @return 四舍五入处理后的结果
     */
    public static double round(double num, int scale) {
        return Math.round(num * Math.pow(10.0, scale)) / Math.pow(10.0, scale);
    }
    public static double round2(double num, int scale) {
        return new BigDecimal(num).divide(new BigDecimal(1), scale, RoundingMode.HALF_UP).doubleValue() ;
    }
}

使用 Math 类或者使用 BigDecimal 类都可以实现四舍五入的处理,但是相比较来讲 Math 类的实现针对于基本数据类型的支持 还是比较合理的(毕竟可以减少不必要的对象产生)。

  • 经过了一系列的分析之后,基本上可以确定了大数字类的基本使用,但是问题是在实际的开发过程之中,该如何去使用这两个大数字类呢?这里面需要明确的告诉大家一个重要的原则:在以后如果面临数据的精准度很高的情况下,千万不要再去使用 double,把所有的 double 都替换为 BigDecimal。

日期 类

Data 类

案例
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        System.out.println(new Date());
    }
}

Date 里面所给出的日期时间默认格式采用的是外国人的习惯,所以中国人民肯定不习惯,但是它已经可以包含有正确的信息,如果要想进行更加详细的处理,后面会有专门的类,除了以上的构造方法之外,在整个的 Date 类里面也提供有如下的一些方法。

在之前讲解数据类型划分时候就重点强调了:在 Java 程序里面,日期时间、内存大小或者文件大小都是用 long 数据类型来进行描述(毫秒数),在 Date 类里面通过构造方法可以接收一个 long 日期时间的数字,也可以通过 Date 返回一个 long 的数据。如果要想清楚的理解 Date 类之中两个构造彼此之间的关系,最佳的做法还是需要浏览一下 Date 类的构造方法源代码。

日期时间和 long 之间的转换

案例
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        long datetime = System.currentTimeMillis() - 10000 ;    // 得到了一个long数据类型
        Date dateA = new Date(datetime) ; // 日期时间数值要小
        Date dateB = new Date() ;   // 日期时间数值要大
        System.out.println(dateA); // long转为Date类型
        System.out.println(dateB);
        System.out.println("【两个日期之间所差的毫秒数】" + (dateB.getTime() - dateA.getTime()));
        System.out.println("【先后关系】AFTER:" + (dateA.after(dateB)));
        System.out.println("【先后关系】BEFORE:" + (dateA.before(dateB)));
    }
}

Calendar 类

在整个的 Java 里面虽然 Date 可以描述日期时间,但是除了日期时间之外实际上还会有许多与日历有关的操作,例如:假设需要知道某一个日期所在月的最后一天,或者说判断某一个日期是否处于闰年,对于这些繁琐的日期的处理操作,在 Java 里面就可以采用一个 Calendar 程序类来完成相应的出来操作。

案例
class CalendarTest{
    public static void main(String[] args) throws Exception {
        Calendar calendar = Calendar.getInstance(); // 获取对象实例
        System.out.println(String.format("当前的日期时间格式:%s-%s-%s %s:%s:%s",
                calendar.get(Calendar.YEAR),
                calendar.get(Calendar.MONTH) + 1,
                calendar.get(Calendar.DAY_OF_MONTH),
                calendar.get(Calendar.HOUR_OF_DAY),
                calendar.get(Calendar.MINUTE),
                calendar.get(Calendar.SECOND)));
    }
}

非常抱歉的是在使用 Calendar 类获取日期时间数值的时候一般都需要进行一些补 0 的处理,但是获得当前的日期时间并不是 Calendar 主要作用,这个类主要使用的是日历的计算。

案例
class CalendarTest1{
    public static void main(String[] args) throws Exception {
        Calendar calendar = Calendar.getInstance(); // 获取对象实例
        calendar.add(Calendar.YEAR, 30); // 30年之后
        calendar.add(Calendar.MONTH, 6); // 计算半年之后的日期
        System.out.println(String.format("当前的日期时间格式:%s-%s-%s %s:%s:%s",
                calendar.get(Calendar.YEAR),
                calendar.get(Calendar.MONTH) + 1,
                calendar.get(Calendar.DAY_OF_MONTH),
                calendar.get(Calendar.HOUR_OF_DAY),
                calendar.get(Calendar.MINUTE),
                calendar.get(Calendar.SECOND)));
    }
}

如果此时没有采用这样的操作类进行日期的计算处理,而是使用 ong 进行计算,那么所得到的日期时间一定是不准确的。既然 Calendar 描述的是一个日期结构,就可以考虑进行一些日期上的调用。

范例:找到 8 月的最后一天。

案例
class CalendarTest2{
    public static void main(String[] args) throws Exception {
        Calendar calendar = Calendar.getInstance(); // 获取对象实例
        calendar.set(calendar.get(Calendar.YEAR), 8, 1); // 通过9月计算8月最后一天
        calendar.add(Calendar.DATE, -1); // 9月的第一天减1
        System.out.println(String.format("当前的日期时间格式:%s-%s-%s %s:%s:%s",
                calendar.get(Calendar.YEAR),
                calendar.get(Calendar.MONTH) + 1,
                calendar.get(Calendar.DAY_OF_MONTH),
                calendar.get(Calendar.HOUR_OF_DAY),
                calendar.get(Calendar.MINUTE),
                calendar.get(Calendar.SECOND)));
    }
}

SimpleDateFormat 类

通过之前的对比可以发现使用 Date 类获取日期时间要比使用 Calendar 类获取的日期时间更加的简单(Calendar 类的功能不在于数据的获取,而是在日期的计算上),可是通过 Date 获取的日期时间实在是不方便阅读“Sun Feb 16 15:45:30 CST 2020”,所以在实际项目的开发过程之中就会存在有一种格式化日期的处理操作,而这种处理操作主要依靠的 java.text.simpleDateFormat 类完成。 如果要想成功的对日期进行格式化或者将字符串转为日期,那么就必须要存在有一个日期时间的匹配表达式,而这组表达式里面最重要的几个日期时间单位:年(yyyy)、月(MM)、日(dd)、时(HH)、分(mm)、秒(ss)、亳秒(SSS)

案例
class SimpleDateFormatTest{
    public static void main(String[] args) {
        Date date = new Date() ;
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS") ; // 实例化转换类对象
        String str = sdf.format(date) ; // 将日期时间转为字符串
        System.out.println(str);
    }
}

class SimpleDateFormatTest1{
    public static void main(String[] args) throws ParseException {
        String str = "2020-02-16 15:54:36.873" ; // 所给出的日期时间字符串的组成要求符合于匹配结构
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS") ; // 实例化转换类对象
        Date date = sdf.parse(str) ; // 将字符串转为日期时间
        System.out.println(date);
    }
}

以后只要是进行项目的开发过程里面,当进行数据输入的时候全部的数据信息都是字符串类型,那么就需要依据目标的需求进行数据类型的转换,这样的转换操作机制非常常见。 本程序全部实现只后就可以针对于实际的开发做出完整的总结了,根据给定的结构图形强记下来就可以在实际的项目开发之中进行应用,而这些基本的知识是保证可以编写出项目基本功。

LocalDate 类

在 JDK1.8 之后的版本里面,Java 追加了一个新的日期时间的处理包:java.time,在这个包中提供有三个主要的类型:LocalDate、LocalTime、LocalDateTime,可以通过这些类更加简单的进行日期、时间或日期时间的处理,相比较之前的 Calendar 类来讲,这些类的使用的更加的方便。

案例
class localDateTest{
    public static void main(String[] args) {
        LocalDate localDate = LocalDate.now() ; // 获得当前的日期
        LocalTime localTime = LocalTime.now() ; // 获得当前的时间
        LocalDateTime localDateTime = LocalDateTime.now() ; // 获得当前的日期时间
        System.out.println("【LocalDate实例化对象输出】" + localDate);
        System.out.println("【LocalTime实例化对象输出】" + localTime);
        System.out.println("【LocalDateTime实例化对象输出】" + localDateTime);
    }
}

在 java.time 包中最为重要的一个类就属于 LocaleDate 类,这个类除了可以直接利用对象的 toSting()方法获取日期时间之外,也可以针对于年、月、日等数据分开获取。

案例
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        LocalDate today = LocalDate.now(); // 获得当前的日期
        System.out.println(String.format("【当前日期】%s-%s-%s", today.getYear(), today.getMonthValue(), today.getDayOfMonth()));
        System.out.println("【获取一周时间数】" + today.getDayOfWeek().getValue());
        System.out.println("【今天所处一月的周数】" + today.get(ChronoField.ALIGNED_WEEK_OF_MONTH));
        System.out.println("【今天所处一年的周数】" + today.get(ChronoField.ALIGNED_WEEK_OF_YEAR));
        System.out.println("【今天所处一年的第几天】" + today.getDayOfYear());
    }
}
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        LocalDate localDate = LocalDate.parse("1988-09-15") ; // 操作特定日期
        System.out.println("【闰年判断】" + localDate.isLeapYear());
        System.out.println("【所处的一周时间数】" + localDate.getDayOfWeek());
    }

public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        LocalDate localDate = LocalDate.parse("1988-09-15"); // 操作特定日期
        System.out.println("【所在月的第一天】" + localDate.with(TemporalAdjusters.firstDayOfMonth()));
        System.out.println("【所在月的第二天】" + localDate.withDayOfMonth(2));
        System.out.println("【所在月的最后一天】" + localDate.with(TemporalAdjusters.lastDayOfMonth()));
        System.out.println("【300年后的日期】" + localDate.plusYears(300));
        System.out.println("【300月后的日期】" + localDate.plusMonths(300));
        System.out.println("【日期所处月的第一个周一】" + localDate.with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY)));
        System.out.println("【日期所处年的第一个周一】" + LocalDate.parse("1988-01-01").with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY)));
    }
}

多线程日期

案例
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        for (int x = 0; x < 10; x++) {
            new Thread(() -> {
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                try {   // 单一线程下的实现的字符串转日期处理
                    System.out.println("【" + Thread.currentThread().getName() + "】" + sdf.parse("1998-02-17 21:15:32"));
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            }, "SDF转换线程 - " + x).start();
        }
    }
}


public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        // 日期时间的格式化类对象,定义SimpleDateFormat类中的匹配日期时间字符串
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") ;
        ZoneId zoneId = ZoneId.systemDefault() ; // 获得当前的时区ID
        for (int x = 0; x < 10; x++) {
            new Thread(() -> {
                LocalDateTime localDateTime = LocalDateTime.parse("1998-02-17 21:15:32", formatter) ;
                Instant instant = localDateTime.atZone(zoneId).toInstant();// 获得一个当前时区的实例
                Date date = Date.from(instant) ; // 字符串转为日期
                System.out.println("【" + Thread.currentThread().getName() + "】" + date);
            }, "SDF转换线程 - " + x).start();
        }
    }
}

public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        // 日期时间的格式化类对象,定义SimpleDateFormat类中的匹配日期时间字符串
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") ;
        ZoneId zoneId = ZoneId.systemDefault() ; // 获得当前的时区ID
        for (int x = 0; x < 10; x++) {
            new Thread(() -> {
                LocalDate localDate = LocalDate.parse("1998-02-17", formatter) ;
                Instant instant = localDate.atStartOfDay().atZone(zoneId).toInstant() ; // 获取当前时区实例
                Date date = Date.from(instant) ; // 字符串转为日期
                System.out.println("【" + Thread.currentThread().getName() + "】" + date);
            }, "SDF转换线程 - " + x).start();
        }
    }
}


public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        for (int x = 0; x < 10; x++) {
            new Thread(() -> {
                System.out.println("【" + Thread.currentThread().getName() + "】" + sdf.format(new Date()));
            }, "SDF转换线程 - " + x).start();
        }
    }
}

正则表达式

简介

在所有的项目实战的开发过程之中,只要存在有 Java 项目,那么就一定会存在有字符串的应用,字符串除了可以保存任意自数据信息,同时可以向所有的数据类型转换之外,还有一个最为重要的支持,那么这个支持就是正则表达式,如果要想充分的理正则表达式的作用,那么首先就需要清楚为什么需要提供有正则表达式。

案例
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String str = "12023239023" ; // 全部由数字所组成
        System.out.println(isNumber(str));
    }
    /**
     * 该方法的主要功能是验证字符串是否全部由数字所组成
     * 只要方法的返回值类型为boolean,一般都是以isXxx()的形式命名的
     * @param message 要验证的字符串信息
     * @return 如果字符串全部由数字所组成返回true,否则返回false
     */
    public static boolean isNumber(String message) {
        for (int x = 0 ; x < message.length() ; x ++) {
            if (message.charAt(x) < '0' || message.charAt(x) > '9') {   // 不是数字编码
                return false ; // 后面就不再需要进行验证了
            }
        }
        return true ; // 如果没有问题返回一个true,表示验证正确
    }
}

由于此时给出的是一个完整的字符串,那么按照传统的设计操作的结构来讲,如果要想验证其组成唯一的方式就是将字符串中每一个的字符内容进行判断,如果发现其正好为数字的字符范围,则表示是正确的,如果不是,则表示错误,并且如果有一位的内容不是数字,则整个的判断返回的结果就是 false。 但是现在不禁需要做一个思考,这种判断字符串组成结构的操作代码本质上来讲并不繁琐,但是面对于这样一个几乎不怎么简单的验证却发现要编写 7 行的代码完成,那么请问,如果说现在突然有一些更加繁琐的验证,这个时候所编写的代码一定会更多在这样的环境下,就需要采用更好的技术去解决当前设计,所以此时就可以通过正则表达式的模式来完成。

案例
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String str = "12023239023" ; // 全部由数字所组成
        System.out.println(str.matches("\\d+")); // 正则验证
    }
}

当前的程序就使用了正则表达式的形式实现了字符串的验证,不看具体是否理解这种代码,但是从直观的感受上来说这个代码的长度非常的精简。而在程序中出现的“\d+”标记就属于正则表达式的定义范畴。 正则表达式最早是 Linux 下发展起来的技术(如果你要想充分的理解正则表达式,那么就一定要认真学习大学的“离散数学”课程),在 Java 早期的时代(JDK 1.3 及以前),如果要想在项目之中去引入正则表达式,那么都需要采用 Apache 所提供的第三方组件包来完成,但是从 JDK14 开始正则表达式正式进入到了 Java 的原生环节,并且在 JDK1.4 里面修改了 String 类的定义(可以通过字符串类直接进行正则的处理操作)。

常用正则标记

  • 单个字符匹配(未加入任何的量词描述,只表示 1 位)

    • \\x:表示的是一位任意的字符;
    • \\:匹配任意的一位“\”(Java 中的转义字符);
    • \\t:匹配制表符(Java 中的转义字符);
    • \\n:匹配换行(Java 中的转义字符);
  • 字符范围(未加入任何的量词描述,只表示 1 位)

  - [abc]: 匹配的是字母“a”、“b”、“c”中的任意一位;
  - [^abc]: 匹配的字母不是“a”、“b”、“c”中的任意一位;
  - [a-zA-Z]:匹配所有的字母(大小写),也可以拆分为“[a-z]”匹配所有的小写字母、“[A-Z]”匹配所有的大写字母;
  - [0-9]: 匹配所有的数字信息:,
  • 简化表达式(未加入任何的量词描述,只表示 1 位)

    • .:表示任意的字符;
    • \d:匹配任意的一位数字,等价于“[0-9]”;
    • \D:匹配任意的一位非数字,等价于“[^0-9]”;
    • \s:匹配任意的空格,等价于“[\t\n\x0B\fr]”;
    • \S:匹配任意的非空格,等价于“[^\s]”;
    • \w:匹配字母(大小写)、数字、下划线,等价于“[a-zA-Z_0-9]”;
    • \W:匹配的是非字母(大小写)、数字、下划线;
  • 边界匹配:

    • ^:匹配开始位置;
    • $:匹配结束位置。
  • 数量表达式(所有的表达式只有与数量匹配之后才可以描述多个字符)

    • ?:表示该正则表达式出现 0 次或者 1 次;
    • *:表示该正则表达式出现 0 次、1 次或者多次;
    • +:表示该正则表达式出现 1 次或者多次;
    • {n}:表示正则表达式正好出现 n 次;
    • {n,}:表示正则表达式出现 n 次以上;
    • {n,m}:表示正则表达式出现 n~m 次;
  • 逻辑运算(可以连接多个正则表达式)

    • 正则表达式 A 正则表达式 B:在 A 正则之后紧跟 B 正则;
    • 正则表达式 A 正则表达式 B:两个正则二选一;
    • (正则):将多个正则表达式作为一组出现,这样可以为整个的正则组再继续定义量词。
案例
1package com.yootk.demo;

public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String str = "12023239023" ; // 全部由数字所组成
        System.out.println(isNumber(str));
    }
    /**
     * 该方法的主要功能是验证字符串是否全部由数字所组成
     * 只要方法的返回值类型为boolean,一般都是以isXxx()的形式命名的
     * @param message 要验证的字符串信息
     * @return 如果字符串全部由数字所组成返回true,否则返回false
     */
    public static boolean isNumber(String message) {
        for (int x = 0 ; x < message.length() ; x ++) {
            if (message.charAt(x) < '0' || message.charAt(x) > '9') {   // 不是数字编码
                return false ; // 后面就不再需要进行验证了
            }
        }
        return true ; // 如果没有问题返回一个true,表示验证正确
    }
}

2package com.yootk.demo;

public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String str = "12023239023" ; // 全部由数字所组成
        System.out.println(str.matches("\\d+")); // 正则验证
    }
}

3package com.yootk.demo;
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String str = "a" ; // 字符串
        System.out.println(str.matches("a")); // 正则验证
    }
}

4package com.yootk.demo;
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String str = "\\" ; // 字符串
        // "\\\\"正确的字符串定义描述的是“\\”,但是“\\”如果要变为正则的匹配那么最后转换为“\”
        System.out.println(str.matches("\\\\")); // 正则验证
    }
}

5package com.yootk.demo;
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String str = "a\t" ; // 字符串
        System.out.println(str.matches("a\t")); // 正则验证
    }
}

6package com.yootk.demo;
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String str = "yootk\n" ; // 字符串
        System.out.println(str.matches("yootk\n")); // 正则验证
    }
}

7package com.yootk.demo;
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String str = "k" ; // 字符串
        System.out.println(str.matches("[yotk]")); // 正则验证
    }
}

8package com.yootk.demo;
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String str = "m" ; // 字符串
        System.out.println(str.matches("[^yotk]")); // 正则验证
    }
}

9package com.yootk.demo;
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String str = "M" ; // 字符串
        System.out.println(str.matches("[a-zA-Z]")); // 正则验证
    }
}

10package com.yootk.demo;
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String str = "1" ; // 字符串
        System.out.println(str.matches("[0-9]")); // 正则验证
    }
}

11package com.yootk.demo;
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String str = "1" ; // 字符串
        System.out.println(str.matches(".")); // 正则验证
    }
}

12package com.yootk.demo;
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String str = "1" ; // 字符串
        System.out.println(str.matches("\\d")); // 正则验证
    }
}

13package com.yootk.demo;
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String str = "a" ; // 字符串
        System.out.println(str.matches("\\D")); // 正则验证
    }
}

14package com.yootk.demo;
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String str = "\n" ; // 字符串
        System.out.println(str.matches("\\s")); // 正则验证
    }
}

15package com.yootk.demo;
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String str = "1" ; // 字符串
        System.out.println(str.matches("\\w")); // 正则验证
    }
}

16package com.yootk.demo;
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String str = "" ; // 字符串
        System.out.println(str.matches("\\w?")); // 正则验证
    }
}

17package com.yootk.demo;
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String str = "aaaaa" ; // 字符串
        System.out.println(str.matches("\\w*")); // 正则验证
    }
}

18package com.yootk.demo;
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String str = "a" ; // 字符串
        System.out.println(str.matches("\\w+")); // 正则验证
    }
}

19package com.yootk.demo;
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String str = "yootk.com" ; // 字符串
        System.out.println(str.matches("\\w{5}")); // 正则验证
    }
}

20package com.yootk.demo;
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String str = "yootk_com" ; // 字符串
        System.out.println(str.matches("\\w{5,}")); // 正则验证
    }
}

21package com.yootk.demo;
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String str = "yootk.com" ; // 字符串
        System.out.println(str.matches("yootk.+")); // 正则验证
    }
}

22package com.yootk.demo;
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String str = "yootk.com" ; // 字符串
        System.out.println(str.matches("yootk|.+")); // 正则验证
    }
}

23package com.yootk.demo;
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String str = "yU*()&*()#@JKILoFFLKFSD*()*(*(o*()@#*()@#*()@#t*()#U*()@$@#@k.com" ; // 字符串
        String regex = "[^a-z]" ; // 定义正则表达式
        System.out.println(str.replaceAll(regex, "")); // 正则验证
    }
}

24package com.yootk.demo;
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String str = "yootk.com828238edu.yootk.com8923892389yootk.ke.qq.com" ; // 字符串
        String regex = "\\d+" ; // 定义正则表达式
        String result [] = str.split(regex) ; // 正则拆分
        for (String temp : result) {
            System.out.println(temp);
        }
    }
}

25package com.yootk.demo;
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String str = "9889118" ; // 字符串
        String regex = "\\d+" ; // 定义正则表达式
        if (str.matches(regex)) {   // 是否符合于正则规则
            System.out.println("【字符串转换数字】" + Integer.parseInt(str));
        } else {
            System.out.println("【ERROR】当前的字符串不符合正则规则,无法转换为int型");
        }
    }
}

26package com.yootk.demo;
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String str = "98891a18" ; // 字符串
        String regex = "\\d+" ; // 定义正则表达式
        try {
            System.out.println("【字符串转换数字】" + Integer.parseInt(str));
        } catch (Exception e) {
            System.out.println("【ERROR】当前的字符串不符合正则规则,无法转换为int型");
        }
    }
}

27package com.yootk.demo;

import java.time.LocalDateTime;
import java.util.Date;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;

public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    public static DateTimeFormatter datetimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") ;
    public static ZoneId zoneId = ZoneId.systemDefault() ; // 获取当前时区的ID
    public static void main(String[] args) throws Exception {
        String str = "1998-02-19 21:15:23" ; // 字符串
        String dateRegex = "\\d{4}-\\d{2}-\\d{2}" ; // 日期正则匹配
        String datetimeRegex = "\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}" ; // 日期时间正则匹配
        if (str.matches(dateRegex)) {
            LocalDate localDate = LocalDate.parse(str, dateFormatter) ;
            Instant instant = localDate.atStartOfDay().atZone(zoneId).toInstant() ;
            Date date = Date.from(instant) ;
            System.out.println("【字符串转日期】" + date);
        }
        if (str.matches(datetimeRegex)) {
            LocalDateTime localDateTime = LocalDateTime.parse(str, datetimeFormatter) ;
            Instant instant = localDateTime.atZone(zoneId).toInstant();
            Date date = Date.from(instant) ;// 转换
            System.out.println("【字符串转换日期时间】" + date);
        }
    }
}

28package com.yootk.demo;

public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String str = "11066789109" ; // 字符串
        String regex = "\\d{11}" ; // 日期正则匹配
        System.out.println(str.matches(regex));
    }
}

29package com.yootk.demo;

public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String str = "008611066789109" ; // 字符串
        String regex = "((\\+|00)\\d{2})?\\d{11}" ; // 日期正则匹配
        System.out.println(str.matches(regex));
    }
}

30package com.yootk.demo;

public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String str = "muyan.yootk-lixinghua@yootk.com" ; // 字符串
        String regex = "[a-zA-Z_][a-zA-Z0-9\\-_\\.]+@[a-zA-Z0-9\\-]+\\.(com|com\\.cn|net|net\\.cn|gov|edu)" ; // 日期正则匹配
        System.out.println(str.matches(regex));
    }
}

31package com.yootk.demo;

import java.util.regex.Pattern;

public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String str = "yootk.com828238edu.yootk.com8923892389yootk.ke.qq.com" ; // 字符串
        String regex = "\\d+" ; // 日期正则匹配
        Pattern pattern = Pattern.compile(regex) ; // 编译正则
        String result [] = pattern.split(str) ; // 拆分字符串
        for (String temp : result) {
            System.out.println(temp);
        }
    }
}

32package com.yootk.demo;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String str = "100.00" ; // 字符串
        String regex = "\\d+(\\.\\d+)?" ; // 日期正则匹配
        Pattern pattern = Pattern.compile(regex) ; // 编译正则
        Matcher matcher = pattern.matcher(str) ; // 创建正则匹配对象
        System.out.println(matcher.matches()); // 匹配结果
    }
}

33package com.yootk.demo;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String sql = "INSERT INTO dept(deptno,dname,loc) VALUES (#{deptno}, #{dname}, #{loc})" ; // 字符串
        String regex = "#\\{\\w+\\}" ; // 日期正则匹配
        Pattern pattern = Pattern.compile(regex) ; // 编译正则
        Matcher matcher = pattern.matcher(sql) ; // 创建正则匹配对象
        while(matcher.find()) {
            String name = matcher.group(0) ;
            System.out.print(name.replaceAll("#|\\{|\\}", "") + "、");
        }
    }
}

String 对正则支持

java.util.regex 包支持

Pattem 类

Patem 类主要功能是进行正则表达式的编译处理,所以如果要想获得本类的实例化对象一般都使用其内部提供的 compile0 方法,这个方法需要传入一个正则。

Matcher 类

Matcher:正则匹配的,如果要想获取 Matcher 类的对象实例必须依靠 Pattern 类。

  • Patter 类提供有一个获取 Matcher 类实例化对象的方法:public Matcher matcher(CharSequence input);
  • 而获得了 Matcher 对象之后就可以直接实现匹配以及替换等功能;。

程序国际化

所谓的应用程序国际化核心的含义指的就是一个程序可以被不同国家的用户同时去使用,这样的优势在于:可以极大的降低程序的开发成本以及维护成本,但是不同的国家本身是存在有不同的风土人情以及办事风格的,所以国际化的程序的开发在实际的工作上其实挺困难的(国际化程序:Google、Youtube、Facebook、Twitter、Ins)。

Locale 类

案例
public class LocaleTest {
    public static void main(String[] args) {
        Locale loc = new Locale("zh", "CN") ;
        System.out.println(loc);
        Locale loc = new Locale("en", "US") ;
        System.out.println(loc);
        Locale loc = Locale.getDefault();
        System.out.println(loc);
    }
}

资源文件

如果要想实现国际化程序除了所给定的 Locale 实现定位之外,那么最为重要的核心技术就是关于文字信息的存储,在 Java 里面国际化的文字信息都要保存在属性文件(或称资源文件)之中,所以首先需要来研究一下属性文件的定义要求:

  • 属性文件(或称资源文件)必须保存在“*.properties”后缀的文件之中;

  • 资源文件在进行保存的时候需要按照类的形式进行存储,它要放在 CLASSPATH 之中(需要考虑到包的问题);

  • 资源文件在进行定义的时候,所有的内容必须采用“key=value”的形式来进行声明;

  • 如果要想在资源文件中进行一些注释的编写,可以使用“#”开头;".

    对于当前的资源文件,其完整的名称为“com.yootk.resource.Mesage”,对于这个资源文件 JDK1.8 及以前和 JDK1.9 及以后是有差别的,同时还需要考虑所使用的开发工具的环境问题,例如:如果要使用的是 IDEA 开发环境,那么最佳的优势在于,其可以直接使用 UTF-8 进行编码。

ResourceBundle

案例
class ResourceTest{
    public static void main(String[] args) {
        // 资源文件在Java程序之中也属于一种完整的结构,如果定义在包里一定要进行包的定义
        ResourceBundle resourceBundle = ResourceBundle.getBundle("com.yix.commonClass.resource.Message") ;
        String value = resourceBundle.getString("welcome.info") ; // 资源文件中提供的key
        System.out.println(value);
    }
}

对于 ResourceBundle 类来讲,其最大的优势是可以直接读取 CLASSPATH 环境下对应的资源文件的内容,但是如果说现在给 定的资源不存在,或者要读取的 key 不存在,也会出现有异常。 在实际的项目开发过程里面,通过资源文件保存的内容可以直接通过 CLASSPATH 模式的加载进行读取,而 ResourceBundle 类是唯一一个可以这样读取资源文件的处理类,所以在日后的开发中,这个类非常常见。

国际化数据读取

Message.properties Message_en_US.properties Message_zh_CN.properties
案例
class ResourceTest{
    public static void main(String[] args) {
        // 资源文件在Java程序之中也属于一种完整的结构,如果定义在包里一定要进行包的定义
        ResourceBundle resourceBundle = ResourceBundle.getBundle("com.yix.commonClass.resource.Message") ;
        String value = resourceBundle.getString("welcome.info") ; // 资源文件中提供的key
        String value1 = resourceBundle.getString("teacher.info") ; // 资源文件中提供的key
        System.out.println(value);
        System.out.println(value1);
    }
}

沐言优拓VIP学员班:yootk.ke.qq.com
爆可爱的小李老师领衔的编程训练营。

class ResourceTest1 {
    public static void main(String[] args) {
        Locale loc = Locale.US; // 英文环境
        ResourceBundle resourceBundle = ResourceBundle.getBundle("com.yix.commonClass.resource.Message", loc);
        String value = resourceBundle.getString("welcome.info"); // 资源文件中提供的key
        String value1 = resourceBundle.getString("teacher.info"); // 资源文件中提供的key
        System.out.println(value);
        System.out.println(value1);
    }
}

此时的程序已经可以通过 Locale 设置了要读取的资源为英文的资源文件,同时如果在读取数据的时候发现指定的 key 不存在,则会自动查找公共的资源文件,如果 key 存在则会加载具体文字语言信息的资源文件。

格式化文本

现在为止在程序之中所定义的资源文件的数据内容基本上都是固定的信息,里面的内容一旦定义则不可改变,但是需要思考一种环境,假设现在实现了一个用户登录的提示资源,要求:在用户录之后可以显示出如下的内容:

登录成功,欢迎“小李”同学的光临,你也可以访问“edu.yootk.com”网站获取课程信息。。

现在希望的做法是可以根据不同的情况设置不同的信息,即:以上提示信息中的“小李”、“edu.yootk.com”的内容可以进行动态的替换,所以这就需要格式化文本的操作支持了。

  • 1.修改资源文件

  • 2.如果要想实现当前显示文字的占位符的处理,就需要通过 java.text.MessageFormat 类来完成。

    • 格式化文本方法:public static String format(String pattern, Object... arguments);
案例
class ResourceTest2 {
    public static void main(String[] args) {
        Locale loc = Locale.CHINA;
        ResourceBundle resourceBundle = ResourceBundle.getBundle("com.yix.commonClass.resource.Message", loc);
        String value = resourceBundle.getString("login.info"); // 资源文件中提供的key
        System.out.println(value);
        System.out.println(MessageFormat.format(value, "小李", "edu.yootk.com"));
    }
}

数字格式化

既然已经强调了国际化的概念,那么在整个国际化程序的实现过程之中就必须去考虑数字的国际化处理(数字的格式化以及货币国际化,包括各种计量单位),所以为了解决这种数字格式化的问题又提供有一个 NumberFormat 子类。 由于 NumberFormat 类的内部需要提供有多种的数字格式化的方式,那么即便现在提供有一个 DecimalFormat 子类,实际上也 不要直接应用,如果仅仅是进行一些基本的数字格式化是可以直接使用。

案例
class ResourceTest3 {
    public static void main(String[] args) {
        NumberFormat numberFormat = new DecimalFormat() ;
        System.out.println(numberFormat.format(28292535.928531));
    }
}

class ResourceTest4 {
    public static void main(String[] args) {
        DecimalFormat decimalFormat = (DecimalFormat) NumberFormat.getInstance();
        System.out.println(decimalFormat.format(28292535.928531));
    }
}
class ResourceTest5 {
    public static void main(String[] args) {
        DecimalFormat numberFormat = (DecimalFormat) NumberFormat.getInstance() ; // 得到一个普通的实例
        numberFormat.applyPattern("####,####,####.000"); // 保留三位小数
        numberFormat.setRoundingMode(RoundingMode.DOWN); // 不进位
        numberFormat.setPositivePrefix("当年的收入流水:");
        numberFormat.setMinimumFractionDigits(5); // 保留5位小数,之前的配置就不在了
        System.out.println(numberFormat.format(28292535.928731));
    }
}
class ResourceTest6 {
    public static void main(String[] args) {
        NumberFormat numberFormat = NumberFormat.getPercentInstance() ;
        System.out.println(numberFormat.format(0.9892367));
        // 如果需要对百分比的处理有自己的要求,那么就必须进行强制向下转型
        DecimalFormat decimalFormat = (DecimalFormat) numberFormat ;
        decimalFormat.setMinimumFractionDigits(5); // 保留5位小数
        System.out.println(decimalFormat.format(0.9892367));
    }
}
class ResourceTest7 {
    public static void main(String[] args) {
        NumberFormat numberFormat = NumberFormat.getCurrencyInstance(Locale.US) ; // 美元货币
        System.out.println(numberFormat.format(789.98));
    }
}

Base64

在进行任何语言类库的设计过程之中,除了比较大型的类库结构之外,例如:国际化、正则表达式、字符串等,还会提供有许多的小类库,简单的理解为它可以完成部分的操作功能,而本次讨论的 Base64 就属于这样的一种小的类库功能。。

案例
public class Base64Test {
    public static void main(String[] args) {
        String message = "www.yootk.com";
        Base64.Encoder encoder = Base64.getEncoder();
        byte[] encodeData = encoder.encode(message.getBytes()) ; // 对数据进行加密处理
        System.out.println("【加密后的数据】" + new String(encodeData));
        Base64.Decoder decoder = Base64.getDecoder(); // 解密工具类
        byte[] decodeData = decoder.decode(encodeData);// 字节数组进行解密
        System.out.println("【解密后的数据】" + new String(decodeData));
    }
}
class PasswordUtil {
    private static final int REPEAT = 5; // 重复加密5次
    private static final String SALT = "edu.yootk.com"; // 重复加密5次

    private PasswordUtil() {
    }   // 该类的方法为static方法

    public static String encrypt(String str) {
        String encodeData = "{" + SALT + "}" + str; // 处理要加密的数据
        Base64.Encoder encoder = Base64.getEncoder();
        for (int x = 0; x < REPEAT; x++) {   // 执行多次编码操作
            encodeData = encoder.encodeToString(encodeData.getBytes()); // 加密操作
        }
        return encodeData;
    }

    public static String decrypt(String str) {
        Base64.Decoder decoder = Base64.getDecoder();// 获得解密类
        byte data[] = str.getBytes(); // 根据字节数组进行解密操作
        for (int x = 0; x < REPEAT; x++) {
            data = decoder.decode(data); // 多次解密
        }
        String decodeData = new String(data); // 包含有盐值的数据
        return decodeData.substring(("{" + SALT + "}").length());
    }
}
class Test{
    public static void main(String[] args) {
        String message = "www.yootk.com" ; // 原始数据
        System.out.println(PasswordUtil.encrypt(message));
        System.out.println(PasswordUtil.decrypt(PasswordUtil.encrypt(message)));
    }
}

UUID 类

在程序设计过程之中 UUID 属于一种数据生成算法,主要功能就是生成不会重复的一个数据,因为时间不重复,所以 UUID 所生成的数据也就不重复,UUID 是基于时间应用的一种数据工具类。。

案例
public class UUIDTest {
    public static void main(String[] args) {
        String id = UUID.randomUUID().toString() ; // 生成UUID数据
        System.out.println(id);
    }
}

Optional 类

在整个引用数据类型的处理过程之中,一旦处理不当,就会在程序运行的时候出现有一个“NullPointerExceptioni”,而这种问 旦出现就需要进行各种代码的排查。

案例
public interface IMessage {
    public void send(String msg);
}

class Factory {
    private Factory() {
    }

    public static IMessage getInstance() {
        return null; // 假设没有返回
    }
}
class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        IMessage messageObject = Factory.getInstance() ; // 通过工厂获取IMessage接口对象
        messageObject.send("沐言优拓:www.yootk.com"); // 进行数据的发送
    }
}

此时的程序在运行的时候出现了一个 NulPointerException,随后就需要根据异常出现的位置进行错误的排查,而且由于 Factory:getnstanceO 方法可能有返回 nu 的情况出现,那么此时能够解决这种空指向问题的关键就是追加一个判断语句。

案例
class Test{
    public static void main(String[] args) {
        IMessage messageObject = Factory.getInstance() ;
        if (messageObject != null) {
            messageObject.send("沐言优拓:www.yootk.com"); // 进行数据的发送
        }
    }
}

现在的程序解决空指向问题的核心依据在于:在客户端的主方法上使用了一个 if 判断语句来判断当前的返回对象是否为空,如果不为 nul,则执行接口方法调用,如果为空则不进行调用,但是如果说此时的 Factory.getlnstance()方法确定永远不会返回 nul 这个时候就可以减少客户端调用的这种无效的判断。 现在如果要想解决当前的问题就必须在返回方法中有一个明确的声明:经过本方法处理的操作是否可以返回 nul,如果可以返 回 null 方法按照原始的方式编写,而如果确定不能够返回 null 的方法使用 Optional 进行描述。Optional 是在 JDK 1.8 之后提供的一个新的系统类,这个类可以保证存储的数据不为 null,首先来观察一下本类的使用。

案例
class Test1{
    public static void main(String[] args) {
        Optional<String> option = Optional.of("沐言优拓:www.yootk.com");// 字符串绝对不为null
        if(option.isPresent()){
            String value = option.get();
            System.out.println(value);
        }
    }
}

此时程序代码由于在 Optiona 对象里面保存的内容属于非空的数据,所以可以正常完成,但是如果此时要是保存有 nul,则在保存的时候就会直接抛出异常,

案例
class Test2{
    public static void main(String[] args) {
        Optional<String> optional = Optional.of(null) ;
    }
}

由于 Optiona.of0 方法不允许设置空数据,所以一旦使用之后就会出现空指向,通过当前的这种操作就可以得出一个结论:正常的设计结构,如果发现返回的是 Optional 类型,则一般都不会为空,因为一旦有空方法本身就有错误了,而不是调用处出错,那么就可以通过 Optional 来实现之前工厂类的修改。

案例
@FunctionalInterface
interface IMessage1 { // 定义一个接口
    public void send(String msg) ;
}
class Factory1 {
    private Factory1() {} ;
    public static Optional<IMessage1> getInstance() {
        return Optional.of((msg)->{
            System.out.println("【消息发送】" + msg);
        }) ; // 假设没有返回
    }
}
class YootkDemo1 {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        IMessage1 messageObject = Factory1.getInstance().get() ; // 可以保证返回的内容不为空
        messageObject.send("沐言优拓:www.yootk.com");
    }
}

ThreadLocal

在项目开发过程之中,ThreadLocal 是一个最为重要的引用数据类型的传递操作类,利用这个类可以非常轻松的实现数据的传输,同时也可以保证多个线程下的数据传输的正确性,由于 ThreadLocal 对于很多的初学者很难理解,所以下面就针对于当前给出的操作进行一些递进分析。

案例
class Message {  // 信息的操作类
    private String content ; // 类中的属性
    public void setContent(String content) {
        this.content = content;
    }
    public String getContent() {
        return content;
    }
}
class MessagePrint {    // 信息打印
    public static void print(Message obj) {
        System.out.println("【MessagePrint】" + obj.getContent());
    }
}
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        Message message = new Message() ;
        message.setContent("沐言科技:www.yootk.com"); // 内容的设置
        MessagePrint.print(message);
    }
}


class Message {  // 信息的操作类
    private String content ; // 类中的属性
    public void setContent(String content) {
        this.content = content;
    }
    public String getContent() {
        return content;
    }
}
class MessagePrint {    // 信息打印
    public static void print() {
        System.out.println("【MessagePrint】" + Resource.message.getContent());
    }
}
class Resource {    // 中间类
    public static Message message ;
}
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        Resource.message = new Message() ;
        Resource.message.setContent("沐言科技:www.yootk.com"); // 内容的设置
        MessagePrint.print(); // 不再传递引用对象
    }
}
package com.yootk.demo;
class Message {  // 信息的操作类
    private String content ; // 类中的属性
    public void setContent(String content) {
        this.content = content;
    }
    public String getContent() {
        return content;
    }
}
class MessagePrint {    // 信息打印
    public static void print() {
        System.out.println("【MessagePrint】" + Resource.message.getContent());
    }
}
class Resource {    // 中间类
    public static Message message ;
}
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String[] values = new String[]{
                "沐言科技:www.yootk.com",
                "李兴华编程训练营:yootk.ke.qq.com",
                "人才培养大纲:edu.yootk.com"};    // 每个线程执行各自的内容输出
        for (String msg : values) {
            new Thread(()->{
                Resource.message = new Message();
                Resource.message.setContent(msg); // 内容的设置
                MessagePrint.print(); // 不再传递引用对象
            }).start();
        }
    }
}

当前的代码已经完成了 MessagePrint.print0 方法不接收 Message 实例化对象的处理操作,从某种程度上来讲当前的问题已经解决了,但是现在的代码是在单线程的状态下进行的,如果说现在采用的是多线程模式呢?。

案例
package com.yootk.demo;
class Message {  // 信息的操作类
    private String content ; // 类中的属性
    public void setContent(String content) {
        this.content = content;
    }
    public String getContent() {
        return content;
    }
}
class MessagePrint {    // 信息打印
    public static void print() {
        // 获取当前线程所保存的message对象
        System.out.println("【MessagePrint】" + Resource.MESSAGES.get().getContent());
    }
}
class Resource {    // 中间类
    public static final ThreadLocal<Message> MESSAGES = new ThreadLocal<>() ;
}
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        String[] values = new String[]{
                "沐言科技:www.yootk.com",
                "李兴华编程训练营:yootk.ke.qq.com",
                "人才培养大纲:edu.yootk.com"};    // 每个线程执行各自的内容输出
        for (String msg : values) {
            new Thread(()->{
                Resource.MESSAGES.set(new Message());
                Resource.MESSAGES.get().setContent(msg);
                MessagePrint.print(); // 不再传递引用对象
            }).start();
        }
    }
}

定时调度

在实际项目开发之中,由于某些原因经常有可能要执行一些自动的处理操作,例如:在之前讲解多线程的时候说过一个守护线程,这个守护线程可能每 3 秒执行一次,或者说这个守护线程每 10 秒执行一个数据的清理操作,但是传统的这种守护线程的调用都需要主线程的存在,那么假设说要求你编写一个程序,这个程序除了满足有实际的功能开发要求之外,还应该可以提供有一些辅助性的做法,并且不太希望涉及到一些繁琐的守护线程的处理,就可以通过定时调度的模式来解决了。

  • Timer 类:实现线程任务调度;如果要想实现调度需要两个处理方法的支持

  • TimerTask 接口:实现定时处理任务的接口定义。

案例
public class TaskThread extends TimerTask {
    @Override
    public void run() {
        System.out.println("【定时任务】沐言优拓:www.yootk.com");
    }
}
class Test{
    public static void main(String[] args) {
        Timer timer = new Timer() ; // 调度类
        // 1秒之后开始调度,执行1次,每2秒重复调用一次
        timer.schedule(new TaskThread(), 1000, 2000);
    }
}

Arrays 类

在早期讲解 Java 数组概念的时候曾经为大家分析过类库对于数组的支持方法,重点讲解了 Amays.son0 方法,该方法的主要作用是进行数组的排序,而 java.util.Arays 类是在 Java 里面为开发者提供的一个与数组有关的功能类,里面提供有大量的方法。

Arrays 类所提供的所有方法全部都属于 static 方法,并且其构造方法也已经被 pnivate 封装了,同时由于在实际的开发之中数组的类型比较多,所以很多的方法都会进行大量的重载处理。

案例
public class ArrayTest {
    public static void main(String[] args) {
        int data[] = new int[]{1, 5, 7, 2, 90, 23, 56, 78};
        System.out.println("【原始数组内容】" + Arrays.toString(data));
        Arrays.sort(data);
        System.out.println("【排序后的数组】" + Arrays.toString(data));
    }
}

如果要想使用 Arrays 类中的 equalsO)比较两个数组的内容是否相同,那么一定要保证两个数组的内容顺序是一致的。

案例
class ArrayTest1 {
    public static void main(String[] args) {
        int dataA[] = new int[]{1, 7, 5};
        int dataB[] = new int[]{5, 7, 1};
        System.out.println("【相等判断 - 未排序】" + Arrays.equals(dataA, dataB));
        Arrays.sort(dataA);
        Arrays.sort(dataB);
        System.out.println("【相等判断 - 已排序】" + Arrays.equals(dataA, dataB));
        System.out.println("【大小关系判断】" + Arrays.compare(new int[]{2, 8, 9}, dataA));
    }
}

在使用 Arrays 类中提供的 compare()方法的时候,一般会有三种返回结果:大于(1)、等于(0)、小于(-1)。 在整个 Arays 类中提供有一个最为重要的数组的内容查找方法:二分查找法(只要牵扯到面试的问题,这类问题一定都会问到),如果要想理解二分查找法概念,那么首先就必须来分析一下关于数组内容的查找操作。

案例
class ArrayTest2 {
    public static void main(String[] args) {
        int data[] = new int[]{9, 8, 5, 19, 29, 56, 78, 3};
        int key = 9 ; // 查找的关键数据
        System.out.println(search(data, key));
    }
    /**
     * 本方法的主要作用是查找在指定的数组之中是否存在有指定的数据内容
     * 如果存在则返回相应的索引,如果不存在返回-1
     * @param data 要查找的数组内容
     * @param key 要查找的数据
     * @return 数组索引,如果没有找到返回-1
     */
    public static int search(int[] data, int key) {
        for (int x = 0; x < data.length; x++) {
            if (data[x] == key) {
                return x;
            }
        }
        return -1;
    }
}

对于以上数据的查找实际上它的实现原理非常简单:就是对数组中的每一个数据进行逐个排查,现在假设有 100 个长度的数组,那么最多排查 100 次,如果有 10W 个数组的长度,那么最多排査的是 10W 次,按照程序的设计概念来讲,此时 search()方法时间复杂度为“O(n)”(n 为数组长度),并且随着内容的增加时间复杂度还会攀升,如果要想进行性能的优化,就必须对整个的结构进行重新设计,在整个计算机的世界里面,如果按照查找来讲,最快的速度就是“O(Log2N)”,这个时候就可以采用二分查找法而这种二分查找法实现前提:需要数组做出排序,下面观察一下二分查找法具体的实现思路。

案例
class ArrayTest3 {
    public static void main(String[] args) {
        int data[] = new int[]{19, 8, 5, 19, 29, 56, 78, 3};
        int key = 9 ; // 查找的关键数据
        Arrays.sort(data);
        System.out.println("【排序数组】" + Arrays.toString(data));
        System.out.println("【数据查询】" + search(data, key));
    }
    public static int search(int[] data, int key) {
        int low = 0; // 开始索引,数组的首个元素索引
        int high = data.length - 1; // 结束索引,数组结尾的索引
        // 现代的语言设计过程之中,强调通过循环结构来代替递归结果,这样可以提升处理性能
        while (low <= high) {   // 如果还有内容没有判断到,则进行持续判断
            int mid = (low + high) >>> 1;   // 进行中间索引的确定,折半
            int midVal = data[mid]; // 获取中间值数据
            if (midVal < key) { // 判断中间值是否小于要查找的内容
                low = mid + 1; // 修改low,变为了“中间索引 + 1”;
            } else if (midVal > key) { // 判断中间值是否大于要查找的内容
                high = mid - 1; // 不再判断后面,而是修改high
            } else { // 数据匹配成功
                return mid; // 返回当前索引
            }
        }
        return -(low + 1);  // 负数,没有查询到
    }
}
案例
class Test{
    public static void main(String[] args) {
        int data[] = new int[]{3, 10, 5, 19, 1, 7};
        System.out.println("【原始数组】" + Arrays.toString(data));
        int key = 9 ; // 查找的关键数据
        Arrays.sort(data);
        System.out.println("【排序数组】" + Arrays.toString(data));
        System.out.println("【数据查询】" + Arrays.binarySearch(data, key));
    }
}

比较器

在 Java 项目开发的机制之中,比较器是一种最为常见的功能,同时在整个的 Java 类集实现架构之中,比较器都有着非常重要的地位,但是首先应该知道为什么要使用比较器?

通过之前的讲解应该已经发现在 Java 里面提供有一个所谓的 Arays 类,这个 Arays 类提供有大量的数组有关的操作方法,而其中,可以发现这样的一个方法定义:

案例
class Book {
    private String title ;
    private double price ;
    public Book(String title, double price) {
        this.title = title ;
        this.price = price ;
    }
    public String toString() {
        return "【Book】图书名称:" + this.title + "、图书价格:" + this.price + "\n" ;
    }
    // setter、getter、无参构造,略 ...
}
public class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        Book books [] = new Book[] {
                new Book("Java从入门到项目实战", 99.8) ,
                new Book("Python从入门到项目实战", 89.7) ,
                new Book("GO语言从入门到项目实战", 96.3)
        } ; // 提供有一个对象数组
        Arrays.sort(books); // 数组排序处理
        System.out.println(Arrays.toString(books)); // 实现对象数组的字符串转换
    }
}


Exception in thread "main" java.lang.ClassCastException: class com.yix.commonClass.Book cannot be cast to class java.lang.Comparable (com.yix.commonClass.Book is in unnamed module of loader 'app'; java.lang.Comparable is in module java.base of loader 'bootstrap')
at java.base/java.util.ComparableTimSort.countRunAndMakeAscending(ComparableTimSort.java:320)
at java.base/java.util.ComparableTimSort.sort(ComparableTimSort.java:188)
at java.base/java.util.Arrays.sort(Arrays.java:1249)
at com.yix.commonClass.Test1.main(ArrayTest.java:122)

在程序执行的过程之中出现有一个“ClassCastException”异常,这种异常所描述的就是对象转换异常,这里面直接提示给用 户“不能够将 Book 类的对象实例转为 Comparable”。

如果说现在给定的是一个整型数组,那么如果要想确定数组之中的元素彼此之间的大小关系,直接利用各种关系运算符即可,但是问题是此时所给出的是一个对象数组,对象数组里面所包含的内容一个个堆内存的信息,那么请问堆内存的信息如何进行大小关系的比较呢?

很明显,堆内存无法直接进行大小关系的比较,如果要的进行排序处理,严格意义上来讲应该使用是堆内存中属性的内容来进行大小关系确认,而这个属性内容的确认就必须采用比较器来支持,而在 Java 里面支持有两种比较器:Comparable、Comparator。

Comparable 接口

在比较器的应用过程之中,Comparable 是在整个 Java 开发过程之中使用最多的一种比较器,同时很多系统内部提供的类也都是基于 Comparable 实现的比较器应用,首先来观察一下该接口的基本定义:

Comparable 接口上追加泛型主要的因素在于,如果真的要进行一组对象的比较,那么肯定这组对象应该拥有相同的类型。在这个接口里面只提供有一个核心的比较方法:compareTo(),这个方法在最终实现比较的时候会有三种返回结果:大于(1,比 0 大的数值)、等于(0)、小与(-1、比 0 小的数值)。

案例
public class Book implements Comparable<Book> {
    private String title;
    private double price;

    public Book(String title, double price) {
        this.title = title;
        this.price = price;
    }

    public String toString() {
        return "【Book】图书名称:" + this.title + "、图书价格:" + this.price + "\n";
    }

    @Override
    public int compareTo(Book o) {
        if (this.price > o.price) {
            return 1;
        } else if (this.price < o.price) {
            return -1;
        } else {
            return 0;
        }
    }
}

class YootkDemo {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        Book books[] = new Book[]{
                new Book("Java从入门到项目实战", 99.8),
                new Book("Python从入门到项目实战", 89.7),
                new Book("GO语言从入门到项目实战", 96.3)
        }; // 提供有一个对象数组
        Arrays.sort(books); // 数组排序处理
        System.out.println(Arrays.toString(books)); // 实现对象数组的字符串转换
    }
}

Comparator 接口

Comparable 比较器是在类定义的时候为类设计的额外功能,但是如果说现在假设有一个类在设计之初并没有考虑到排序的需求,那么这个时候如何可以利用系统类所提供的数组操作形式进行排序呢?为了解决这样的问题,在 java.util 包中提供了一个 Comparator 比较器接口,这个接口可以实现挽救的比较操作,但是需要定义有一个专属的比较器实现类。

案例
class Book1{ // 这个类拥有排序支持
    private String title ;
    private double price ;
    public Book1(String title, double price) {
        this.title = title ;
        this.price = price ;
    }
    public String toString() {
        return "【Book】图书名称:" + this.title + "、图书价格:" + this.price + "\n" ;
    }
    public String getTitle() {
        return title;
    }
    public double getPrice() {
        return price;
    }
}
class BookComparator implements Comparator<Book1> {  // 提供有专属的比较器实现类
    @Override
    public int compare(Book1 o1, Book1 o2) {
        if (o1.getPrice() > o2.getPrice()) {
            return 1 ;
        } else if (o1.getPrice() < o2.getPrice()) {
            return -1 ;
        } else {
            return 0;
        }
    }
}
class YootkDemo1 {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        Book1 books [] = new Book1[] {
                new Book1("Java从入门到项目实战", 99.8) ,
                new Book1("Python从入门到项目实战", 89.7) ,
                new Book1("GO语言从入门到项目实战", 96.3)
        } ; // 提供有一个对象数组
        Arrays.sort(books, new BookComparator()); // 数组排序处理
        System.out.println(Arrays.toString(books)); // 实现对象数组的字符串转换
    }
}

class Book2{ // 这个类拥有排序支持
    private String title ;
    private double price ;
    public Book2(String title, double price) {
        this.title = title ;
        this.price = price ;
    }
    public String toString() {
        return "【Book】图书名称:" + this.title + "、图书价格:" + this.price + "\n" ;
    }
    public String getTitle() {
        return title;
    }
    public double getPrice() {
        return price;
    }
}
class YootkDemo2 {    // 李兴华编程训练营:yootk.ke.qq.com
    public static void main(String[] args) throws Exception {
        Book2 books [] = new Book2[] {
                new Book2("Java从入门到项目实战", 99.8) ,
                new Book2("Python从入门到项目实战", 89.7) ,
                new Book2("GO语言从入门到项目实战", 96.3)
        } ; // 提供有一个对象数组
        Comparator<Book2> comparator = (o1, o2)->{
            if (o1.getPrice() > o2.getPrice()) {
                return 1 ;
            } else if (o1.getPrice() < o2.getPrice()) {
                return -1 ;
            } else {
                return 0;
            }
        } ;
        Arrays.sort(books, comparator.reversed()); // 数组排序处理
        System.out.println(Arrays.toString(books)); // 实现对象数组的字符串转换
    }
}

Comparator 除了基本的排序支持之外,其内部实际上也存在有大量的数据排序的处理操作,例如:reversed(),如果现在使用的是 Comparable 接口实现这样的反转那么必须进行大量系统源代码的修改,但是如果使用了 Comparator 可以在外部通过具体的方法来进行配置,所以灵活度更高。

请解释两种比较器的区别?

  • java.lang.Comparable:是在类定义的时候实现的接口,该接口只存在有一个 compareTo()方法用于确定大小关系;
  • java.util.Comparator:是属于挽救的比较器,除了可以实现排序的功能之外,在 JDK1.8 之后的版本里面还提供有更多方便的数组操作的处理功能。
上次编辑于: