疯狂Java讲义 第七章 Java基础类库

jefxff 153,583 2020-03-30

1. 与用户互动

1.1 使用Scanner获取键盘输入

  • Scanner 类了解

    1. Scanner类是一个基于正则表达式的文本扫描器, 可以从文件,输入流, 字符串中解析出基本类型值和字符串值
    2. Scanner默认使用空白(包含空格, Tab空白, 回车)作为多个输入项之间的分隔符; 但是可为Scanner指定分割符, 通过使用: useDelimiter(String prtten) 方法指定, patten 参数是一个正则表达式
    3. Scanner的读取操作可能被堵塞(当前执行顺序流暂停)来等待信息输入; 如果输入源没有结束, Scanner又读取不到更多的输入项时(尤其在键盘输入时比较常见), Scanner 的 hasNext() 和 next() 方法都有可能阻塞, (hasNext() 方法是否阻塞与其相关的 next() 方法是否阻塞无关)
  • Scanner 两个扫描输入方法:(例子1)

    1. hasNextXxx() : 是否还有下一个输入项, 其中Xxx可以是Int,Long等基本数据类型的字符串; 如果只是判断是
      否包含下一项字符串, 则直接使用hasNext()
    2. nextXxx() : 获取下一个输入项, Xxx的含义与前一个方法中Xxx相同
  • Scanner 逐行读取的方法:(例子2)

    1. boolean hasNextLine() : 返回输入源中是否还有下一行
    2. String nextLine() : 返回输入源中下一行的字符串

示例代码

// 例子1:
// ScannerKeyBoardTest.java 
// 获取到键盘标准输入, 然后将输入的内容输出到屏幕
import java.util.*;
public class ScannerKeyBoardTest {
    public static void main(String[] args) {
        // System.in 代表标准输入, 就是键盘输入
        Scanner sc = new Scanner(System.in);
        // 下面代码将只把回车作为换行符
        sc.useDelimiter("\n");
        // 判断是否还有下一个输入项
        while (sc.hasNext()){
            System.out.println("Input is: " + sc.next());
        }
        sc.close();
    }
}
// 例子2:
import java.io.File;
import java.util.*;
public class ScannerFileTest{
    public static void main(String[] args) throws Exception {
        // 将一个File对象作为Scanner的构造器的参数, Scanner读取到文件内容
        Scanner sc = new Scanner(new File(
            "C:\\Users\\Administrator\\Desktop\\studyfile\\java\\src\\chapter06\\ScannerKeyBoardTest.java"));
        System.out.println("扫描到的文件内容如下: ");
        // 判断是否还有下一行
        while (sc.hasNextLine()){
            // 输出文件中的下一行
            System.out.println(sc.nextLine());
        }
        sc.close();
    }
}

2. 系统相关

2.1 System 类

  • System 类说明(例子1)

    1. System类代表当前Java程序的运行平台, 程序不能创建System的对象, System类提供了一些类变量和类方法, 允许直接通过System类来调用这些类变量和类方法
    2. System 类提供了代表标准输入, 标准输出和错误输出的类变量, 并提供了一些静态方法用于访问环境变量, 系统变量的方法, 还提供了加载文件和动态链接库的方法, 加载文件和动态连接库主要对 native 方法有用, 对于一些特殊的功能(如访问操作系统底层硬件设备等)Java无法实现, 必须借助C语言来完成 (例子1)
    3. System类的 in, out和err分别代表系统的标准输入(通常是键盘), 标准输出(通常是显示器)和错误输出流,还有 setIn(), setOut()和setErr()方法来改变系统的标准输入, 标准输出和标准错误流输出
    4. System类提供 gc() 方法进行垃圾回收, runFinalization() 方法进行资源清理
  • identityHashCode(object x) 方法 (例子2)

    1. 该方法返回对象的精确hashCode值, 也就是根据该对象的地址计算得到的 hashCode 值,
    2. 当某个类的hashCode()方法被重写后, 该类实例的 hashCode()方法就不能唯一标识该对象; 但通过identityHashCode()方法返回的hashCode 值, 依然是根据该对象的地址计算得到的 hashCode 值
    3. 如果两个对象的 identityHashCode 值相同, 则两个对象绝对是同一个对象
  • System 类获取当前时间的方法currentTimeMillis()和nanoTime()

    1. 这两个方法都返回一个long型整数, 实际上这两个方法返回的是当前时间与UTC1790年1月1日午夜的时间差; 这两个方法返回的时间精度取决于操作系统
    2. currentTimeMillis()是以毫秒作为单位, nanoTime()是以纳秒作为单位

示例代码

// 例子1:
import java.io.FileOutputStream;
import java.util.Map;
import java.util.Properties;
public class SystemTest {
    public static void main(String[] args) throws Exception {
        // 获取系统所有的环境变量
        Map<String, String> env = System.getenv();
        for (String name : env.keySet()){
            System.out.println(name + "--->" + env.get(name));
        }
        // 获取指定环境变量的值
        System.out.println(System.getenv("JAVA_HOME"));
        // 获取所有系统属性
        Properties props = System.getProperties();
        // 将所有的系统属性保存到 prop.txt文件中
        props.store(new FileOutputStream("props.txt"), "System Properties");
        // 输出特定的系统属性
        System.out.println(System.getProperty("os.name"));
    }
}
// 例子:
public class identityHashCodeTest{
    public static void main(String[] args){
        // 下面程序中 s1 和 s2 是两个不同的对象
        String s1 = new String("Hello");
        String s2 = new String("Hello");
        // String 重写了 hashCode()方法---改为根据字符串序列计算hashCode值
        // 因为 s1 和 s2 的字符序列相同, 所以他们的hashCode()方法返回值相同
        System.out.println(s1.hashCode() + "----" + s2.hashCode());
        // s1 和 s2 是不同的字符串对象, 所以他们的 identityHashCode 值不同
        System.out.println(System.identityHashCode(s1) + "---"
                + System.identityHashCode(s2));
        String s3 = "java";
        String s4 = "java";
        // s3 和 s4 是相同的字符串对象, 所以他们的 identityHashCode值相同
        System.out.println(System.identityHashCode(s3) + "---"
                + System.identityHashCode(s4));
    }
}

2.2 Runtime 类

  • runtime类说明
    1. Runtime类代表了Java程序的运行时环境, 每个Java程序都有一个与之对应的Runtime实例, 应用程序通过该对象与其运行时环境相连, 应用程序不能创建自己的Runtime实例,但可通过 getRuntime()方法获取与之关联的 Runtime 对象
    2. Runtime 类 也提供 gc() 方法进行垃圾回收, runFinalization() 方法进行资源清理; 并提供了load(String filename) 和 loadLibrary(String libname) 方法来加载文件和动态连接库
    3. Runtime 类代表了Java程序的运行时环境, 可以访问JVM的相关信息(例子1)
    4. Runtime 提供了一系列 exex()方法来运行操作系统命令(例子2)

示例代码

// 例子1:
// RuntimeTest.java
// 获取JVM运行时环境
public class RuntimeTest{
    public static void main(String[] args) {
        // 获取Java程序关联的运行时对象
        Runtime rt = Runtime.getRuntime();
        System.out.println("处理器数量: " + rt.availableProcessors());
        System.out.println("空闲内存数: " + rt.freeMemory());
        System.out.println("总内存数: " + rt.totalMemory());
        System.out.println("可用最大内存数: " + rt.maxMemory());
    }
}
// 例子2:
// ExecTest.java
// 通过Java程序调用Runtime类的 getRuntime方法获取关联的Runtime对象, 调用 exec()方法运行记事本
public class ExecTest{
    public static void main(String[] args) throws Exception{
        Runtime rt = Runtime.getRuntime();
        // 运行记事本程序
        rt.exec("notepad.exe");
    }
}

3. 常用类

3.1 Object类

Object 类是所有类, 数组, 枚举类(继承Enum类, Enum类又继承Object类)的父类, 即Java允许把任何类型的对象赋给 Object类型的变量; 所以任何Java对象都可以调用 Object类的方法

Object类的常用方法

  1. boolean equals(Object obj)
    说明: 判断指定对象与该对象是否相等;(此处的相等标准是: 两个对象是同一个对象)

  2. protected void finalize()
    说明: 当系统中没有引用变量引用到该对象时, 垃圾回收器调用此方法来清理对象的资源

  3. Class<?> getClass()
    说明: 返回该对象的运行时类

  4. int hashCode()
    说明: 返回该对象的hashCode值, 在默认情况下, Object类的hashCode()方法根据该对象的地址计算(即与System.identityHashCode(Object x)方法计算的结果相同),但很多类重写了hashCode方法

  5. String toString()
    说明: 这个方法前一章学过了, toString()方法返回该对象的字符串表示

  6. wait() notify() notifyAll()
    说明: 这几个方法可以控制线程的暂停和运行


3.2 Java7 新增的 Objects类

Objects类是一个工具类, 它提供了一些工具方法来操作对象, 这些工具方法大多数"空指针"安全的(例子1)

示例代码

// 例子1:
public class ObjectsTest{
    // 定义一个ojb变量, 它的默认值是null
    Static ObjectsTest obj;
    public static void main(String[] args){
        // 输出一个null对象的hashCode值, 输出0
        System.out.println(Objects.hashCode(obj));
        // 输出一个null对象的toString, 输出 null
        System.out.println(Objects.toString(obj));
        // 要求 obj 不能为 null, 如果obj为null则引发异常
            System.out.println(Objects.requireNonNull(obj));
    }
}
// 程序说明:Objects提供的requireNonNull()方法, 当传入的参数不为null时, 该方法返回参数本身; 否则将会引
//         发 NullPointerException 异常, 该方法主要用来对方法形参进行输入校验, 如例子2
// 
// 
// 例子2:
public Foo(Bar bar){
    // 校验 bar 参数, 如果 bar 参数为null将会引发异常, 否则this.bar被赋值为bar参数
    this.bar = Objects.requireNonNull(bar);
}

3.3 String, StringBuffer 和 StringBuilder 类

String 类

  1. String 类是不可变类, 即一旦一个String对象被创建以后, 包含在这个对象中的字符序列是不可改变的, 直
    至这个对象被销毁

  2. String()
    说明: 创建一个包含0个字符串序列的String对象(并不是返回null)

  3. String(byte[] bytes, Charset charset)
    说明: 使用指定的字符集将指定的byte[]数组解码成一个新的String对象

  4. String(byte[] bytes, int offset, int length)
    说明: 使用平台的默认字符集将指定的 byte[] 数组从 offset开始, 长度为length的子数组解码成一个新的String对象

  5. String(byte[] bytes, int offset, String charsetName)
    说明: 使用指定的字符集将指定的byte[] 数组从 offset 开始, 长度为length 的子数组解码成一个新的的String对象

  6. String(byte[] bytes, String charsetName)
    说明: 使用指定的字符集将指定的byte[]数组解码成一个新的String对象

  7. String(char[] value, int offset, int count)
    说明: 将指定的字符数字从offset开始, 长度为count的字符元素连缀成字符串

  8. String(String original)
    说明: 根据字符串直接量来创建一个String对象, 也就是说, 新创建的String对象是该参数字符串的副本

  9. String(StringBuffer buffer)
    说明: 根据StringBuffer对象来创建对应的String对象

  10. String(StringBuilder builder)
    说明: 根据StringBuilder 对象里创建对应的String对象

String类操作字符串对象的方法

  1. char charAt(int index)
    说明: 获取字符串中指定位置的字符, 其中index指的是字符串的序数(序数从0开始到length()-1)

  2. int compareTo(String anotherString)
    说明: 比较两个字符串的大小, 如果两个字符串的字符序列相等, 则返回0; 不相等时, 从两个字符串第0个字符开始比较, 返回第一个不相等的字符差; 另一种情况, 较长字符串的前面部分恰巧是较短的字符串,则返回他们的长度差

  3. String concat(String str)
    说明: 将该String对象与str连接一起, 与Java提供的字符串连接运算符"+"的功能相同

  4. boolean contentEquals(StringNuffer sb)
    说明: 将该String对象与Stringbuffer 对象sb进行比较, 当它们包含的字符序列相同时返回true

  5. 等等等等 记住书上P250-P251页的内容

StringBuffer 类

  1. StringBuffer 对象则代表一个字符序列可变的字符串, 当一个StringBuffer被创建以后,通过StringBuffer提供的 append(), insert(), reverse(), setCharAt(), setLength()等方法可以改变这个字符串对象的字符序列,
  2. 一旦通过StringBuffer生成了最终想要的字符串, 就可以调用它的toString()方法将其转换为一个String类型的对象

StringBuilder 类

  1. StringBuilder类也代表字符串对象, 构造器及方法和StringBuffer类似, 区别是: StringBuffer是线程安全的, 而StringBuilder 则没有实现线程安全功能, 所以性能略高
  2. 通常情况下, 如果需要创建一个内容可变的字符串对象, 则应该优先考虑使用StringBuilder类
  3. String, StringBuffer, StringBuilder 都实现了 CharSequence接口, 因此CharSequence可认为是一个字符串的协议接口
  4. StringBuilder, StringBuffer 有两个属性: length和capacity, 其中length属性表示其包含的字符序列的长度, 与String对象的length不同的是, Stringbuffer, StringBuilder 的length是可以改变的, 可以通过 length(), setLength(int len)方法来访问和修改其字符串序列的长度; capacity属性表示StringBuilder 的容量, capacity 通过比length 大, 了解了解把

示例代码

// 例子:
// StringBuilderTest.java 
public class StringBuilderTest {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();
        // 追加字符串
        sb.append("java"); // sb = "java"
        System.out.println(sb);
        // 插入
        sb.insert(0, "hello ");  // sb = "hello java"
        System.out.println(sb);
        // 替换
        sb.replace(5, 6, ",");   // sb = "hello,java"
        System.out.println(sb);
        // 删除
        sb.delete(5, 6);  // sb = "hellojava"
        System.out.println(sb);
        // 反转
        sb.reverse();  // sb = "avajolleh"
        // 再反转回来
        sb.reverse();
        System.out.println(sb);
        System.out.println(sb.length());  // 输出9
        System.out.println(sb.capacity());  // 输出16
        // 改变StringBuilder的长度, 将只保留前面部分
        sb.setLength(5);
        System.out.println(sb);
    }
}  

3.4 Math 类

  1. 通过例子来了解Math提供的各种方法
  2. Java提供了Math工具类来完成这些复杂的运算, Math类是一个工具类, 他的构造器是private访问权限, 因此无法创建Math类的对象,Math类的所有方法都是类方法, 可以直接通过类名(Math)来调用他们
  3. Math类还有两个类变量: PI和E 他们的值等于π和e

示例代码

// 例子:
// MathTest.java
public class MathTest{
    public static void main(String[] args) {
        /*--------下面是三角运算------*/
        // 将弧度转换为角度
        System.out.println("Math.toDegrees(1.57): " + Math.toDegrees(1.57));
        // 将角度转换为弧度
        System.out.println("Math.toRadians(90): " + Math.toRadians(90));
        // 计算反余弦, 返回的角度范围在0.0 到 pi 之间
        System.out.println("Math.acos(1.2): " + Math.acos(1.2));
        // 计算反正弦, 返回的角度范围在 -pi/2 到 pi/2 之间
        System.out.println("Math.asin(0.8): " + Math.asin(0.8));
        // 计算反正切, 返回的角度范围在 -pi/2 到 pi/2 之间
        System.out.println("Math.atan(2.3): " + Math.atan(2.3));
        // 计算三角余弦
        System.out.println("Math.cos(1.57): " + Math.cos(1.57));
        // 计算双曲余弦
        System.out.println("Math.cosh(1.2): " + Math.cosh(1.2));
        // 计算正弦
        System.out.println("Math.sin(1.2): " + Math.sin(1.2));
        // 计算双曲正弦
        System.out.println("Math.sinh(0.8): " + Math.sinh(0.8));
        // 计算三角正切
        System.out.println("Math.tan(0.8): " + Math.tan(0.8));
        // 计算双曲正切
        System.out.println("Math.tanh(2.1): " + Math.tanh(2.1));
        // 将矩形坐标(x, y)转换成极坐标(r, thet)
        System.out.println("Math.atan2(0.1, 0.2): " + Math.atan2(0.1, 0.2));
        // 
        /*--------下面是取整运算------*/
        // 取整, 返回小于目标数的最大整数
        System.out.println("Math.floor(1.2): " + Math.floor(1.2));
        // 取整, 返回大于目标数的最小整数
        System.out.println("Math.ceil(1.2): " + Math.ceil(1.2));
        // 四舍五入取整
        System.out.println("Math.round(2.3): " + Math.round(2.3));
        //
        /*--------下面是乘方, 开方,指数运算------*/
        // 计算平方根
        System.out.println("Math.sqrt(2.3): " + Math.sqrt(2.3));
        // 计算立方根
        System.out.println("Math.cbrt(9): " + Math.cbrt(9));
        // 返回欧拉数 e 的 n 次幂
        System.out.println("Math.exp(2): " + Math.exp(2));
        // 返回sqrt(x2 + y2), 没有中间溢出或下溢
        System.out.println("Math.hypot(4, 4): " + Math.hypot(4, 4));
        // 按照IEEE 754标准的规定, 对两个参数进行余数运算
        System.out.println("Math.IEEEremainder(5, 2): " + Math.IEEEremainder(5, 2));
        // 计算乘方
        System.out.println("Math.pow(3, 2): " + Math.pow(3, 2));
        // 计算自然对数
        System.out.println("Math.log(12): " + Math.log(12));
        // 计算底数为10的对数
        System.out.println("Math.log10(12): " + Math.log10(12));
        // 返回参数与1之和的自然对数
        System.out.println("Math.log1p(12): " + Math.log1p(12));
        // 
        /*--------下面是符号相关的运算------*/
        // 计算绝对值
        System.out.println("Math.abs(-4.5): " + Math.abs(-4.5));
        // 符号赋值, 返回带有第二个浮点数符号的第一个浮点参数
        System.out.println("Math.copySign(1.2, -1.0): " + Math.copySign(1.2, -1.0));
        // 符号函数, 如果参数为0, 则返回0, 如果参数大于0
        // 则返回1.0; 如果参数小数0, 则返回-1.0
        System.out.println("Math.signum(2.3): " + Math.signum(2.3));
        //
        /*--------下面是大小相关的运算------*/
        // 找出最大值
        System.out.println("Math.max(2.3, 4.5): "+ Math.max(2.3, 4.5));
        // 计算最小值
        System.out.println("Math.min(2.3, 4.5): "+ Math.min(2.3, 4.5));
        // 返回第一个参数和第二个参数之间与第一个参数相邻的浮点数
        System.out.println("Math.nextAfter(1.2, 1.0): "+ Math.nextAfter(1.2, 1.0));
        // 返回比目标数略大的浮点数
        System.out.println("Math.nextUp(1.2): " + Math.nextUp(1.2));
        // 返回一个伪随机数, 该值大于等于0.0 且小于 1.0 
        System.out.println("Math.random(): " + Math.random());
        System.out.println("Math.PI : " + Math.PI);
        System.out.println("Math.E : " + Math.E);
    }
} 

3.5 Java7 的 ThreadLocalRandom 和 Random

  1. Random类专门用于生成一个伪随机数, 他有两个构造器: 一个构造器使用默认的种子(以当前时间作为种子), 另一个构造器需要程序员显式传入一个long型整数的种子
  2. ThreadLocalRandom 是Java7新增的, 是Random的增强版; 在并发访问的环境下, 使用ThreadLocalRandom来代替Random可以减少多线程资源的竞争, 最终保证系统具有更好的线程安全性
  3. ThreadLocalRandom 类用法类似与Random, 提供一个静态的 current()方法来获取ThreadLocalRandom对象, 获取该对象之后即可调用各种nextXxx()方法来获取伪随机数
  4. Random产生的数字并不是真正随机的, 而是一种伪随机, 可能产生两个Random对象产生相同的数字序列, 通常通过使用当前时间作为Random对象的种子, 如:Random rand = new Random(System.currentTimeMillis());
  5. ThreadLocalRandom 与 Random 都比Math类提供的Random()方法提供更多的方式来生成各种伪随机数, 可以生成浮点数类型的伪随机数, 也可以生成整数类型的伪随机数, 还可以指定生成随机数的范围(例子2)

示例代码

// 例子1:
// 生成伪随机数的demo
// RandomTest.java
import java.util.Arrays;
import java.util.Random;
// 
public class RandomTest {
    public static void main(String[] args) {
        Random rand = new Random();
        System.out.println("rand.nextBoolean(): " + rand.nextBoolean());
        byte[] buffer = new byte[16];
        rand.nextBytes(buffer);
        System.out.println(Arrays.toString(buffer));
        // 生成 0.0~1.0之间的伪随机double数
        System.out.println("rand.nextDouble(): " + rand.nextDouble());
        // 生成 0.0~1.0之间的伪随机float数
        System.out.println("rand.nextFloat(): " + rand.nextFloat());
        // 生成平均值是0.0, 标准差是1.0的伪高斯数
        System.out.println("rand.nextGaussian(): " + rand.nextGaussian());
        // 生成一个处于int整形取值范围的伪随机数
        System.out.println("rand.nextInt(): " + rand.nextInt());
        // 生成一个0~26之间的伪随机整数
        System.out.println("rand.nextInt(26): " + rand.nextInt(26));
        // 生成一个处于long整数取值范围的伪随机整数
        System.out.println("rand.nextLong(): " + rand.nextLong());
    }
}
// 例子2:
// ThreadLocalRandom 生成伪随机的用法
ThreadLocalRandom rand = ThreadLocalRandom.current();
// 生成一个4-20之间的伪随机整数
int val1 = tlr.nextInt(5, 16);
System.out.println(val1);
// 生成一个2.0-10.0之间的伪随机浮点数
double val2 = tlr.nextDouble(5.0, 16.0);
System.out.println(val2);

3.6 BigDecimal 类

  1. float, double两种基本浮点数容易引起精度丢失, 尤其在进行算术运算时更容易发生(例子1)
  2. BigDecimal 类提供了大量构造器用于创建BigDecimal对象, 包括把所有的基本数值型变量转换成一个BigDecimal对象, 也包括利用数字字符串, 数字字符数组来创建BigDecimal对象
  3. BigDecimal 类不推荐使用BigDecimal(double val);因为使用该构造器时有一定的不可预知性
  4. 使用 BigDecimal(String val) 构造器的结果是可预知的, 写入一个 new BigDecimal("0.1")将创建一个BigDecimal, 它正好等于预期的 0.1; 所以优先使用基于String的构造器
  5. 如果必须使用double浮点数作为BigDecimal构造器的参数, 不要直接将该double浮点数作为构造器的参数来创建BigDecimal对象, 而是通过 BigDecimal.valueOf(double value)静态方法来创建BigDecimal对象
  6. BigDecimal类提供了add(), subtract(), multiply(), pow()等方法对精确浮点数进行常规算术运算(例2)
  7. 如果程序中要求对double浮点数进行加, 减, 乘, 除基本运算, 则需要先将double类型的数值包装成BigDecimal 对象, 调用 BigDecimal 对象的方法执行算数运算后再将结果转换成double型变量, (如例子3, 以 BigDecimal 为基础定义一个 Arith 工具类,)

示例代码

// 例子1:
// DoubleTest.java 
// 普通的double类型的算术运算容易造成精度丢失
public class DoubleTest{
    public static void main(String[] args){
        System.out.println("0.05 + 0.01 = " + (0.05 + 0.01));
        System.out.println("1.0 - 0.42 = " + (1.0 - 0.42));
        System.out.println("4.015 * 100 = " + (4.015 * 100));
        System.out.println("123.3 / 100 = " + (123.3 / 100));
    }
}
// 例子2:
// BigDecimalTest.java
import java.math.BigDecimal;
public class BigDecimalTest {
    public static void main(String[] args) {
        BigDecimal f1 = new BigDecimal("0.05");
        BigDecimal f2 = BigDecimal.valueOf(0.01);
        BigDecimal f3 = new BigDecimal(0.05);
        // 计算结果可预期
        System.out.println("使用String作为BigDecimal构造器参数:");
        System.out.println("0.05 + 0.01 = " + f1.add(f2));
        System.out.println("0.05 - 0.01 = " + f1.subtract(f2));
        System.out.println("0.05 * 0.01 = " + f1.multiply(f2));
        System.out.println("0.05 / 0.01 = " + f1.divide(f2));
        // 计算结果不可预期
        System.out.println("使用double作为BigDecimal构造器参数:");
        System.out.println("0.05 + 0.01 = " + f3.add(f2));
        System.out.println("0.05 - 0.01 = " + f3.subtract(f2));
        System.out.println("0.05 * 0.01 = " + f3.multiply(f2));
        System.out.println("0.05 / 0.01 = " + f3.divide(f2));
    }
}
// 输出:
//     使用String作为BigDecimal构造器参数:
//     0.05 + 0.01 = 0.06
//     0.05 - 0.01 = 0.04
//     0.05 * 0.01 = 0.0005
//     0.05 / 0.01 = 5
//     使用double作为BigDecimal构造器参数:
//     0.05 + 0.01 = 0.06000000000000000277555756156289135105907917022705078125
//     0.05 - 0.01 = 0.04000000000000000277555756156289135105907917022705078125
//     0.05 * 0.01 = 0.0005000000000000000277555756156289135105907917022705078125
//     0.05 / 0.01 = 5.000000000000000277555756156289135105907917022705078125
// 说明: 从运行结果看出BigDecimal进行算数运算时, 一定要使用String对象作为构造器的参数, 而不是直接使用
//         double类型的数字
// 例子3:
// Arith.java 
// 以 BigDecimal 为基础定义一个 Arith 工具类
import java.math.BigDecimal;
public class Arith{
    // 默认的除法运算精度
    private static final int DEF_DIV_SCALE = 10;
    // 构造器私有化, 让这个类不能实例化
    private Arith(){}
    // 提供精确的加法运算
    public static double add(double v1, double v2){
        BigDecimal b1 = BigDecimal.valueOf(v1);
        BigDecimal b2 = BigDecimal.valueOf(v2);
        return b1.add(b2).doubleValue();
    }
    // 提供精确的减法运算
    public static double sub(double v1, double v2){
        BigDecimal b1 = BigDecimal.valueOf(v1);
        BigDecimal b2 = BigDecimal.valueOf(v2);
        return b1.subtract(b2).doubleValue();
    }
    // 提供精确的乘法运算
    public static double mul(double v1, double v2){
        BigDecimal b1 = BigDecimal.valueOf(v1);
        BigDecimal b2 = BigDecimal.valueOf(v2);
        return b1.multiply(b2).doubleValue();
    }
    // 提供相对精确的除法运算, 当发生除不尽的情况时, 精确到小数点以后10位的数字四舍五入
    public static double div(double v1, double v2){
        BigDecimal b1 = BigDecimal.valueOf(v1);
        BigDecimal b2 = BigDecimal.valueOf(v2);
        return b1.divide(b2, DEF_DIV_SCALE, BigDecimal.ROUND_HALF_UP).doubleValue();
    }
    public static void main(String[] args) {
        System.out.println("0.05 + 0.01 = " + Arith.add(0.05, 0.01));
        System.out.println("1.0 - 0.42 = " + Arith.sub(1.0, 0.42));
        System.out.println("4.015 * 100 = " + Arith.mul(4.015, 100));
        System.out.println("123.3 / 100 = " + Arith.div(123.3, 100));
        System.out.println("123.3 / 99 = " + Arith.div(123.3, 99));
    }
}  

4. Java 8 的日期, 时间类

4.1 Date 类(了解, Java官网都不推荐使用)

Date类的大部分构造器, 方法都已经过时, 了解剩下的能用的两个

  • 剩下的两个可以用的构造器

    1. Date()
      说明: 生成一个代表当前日期时间的Date对象, 该构造器在底层调用System.cunnentTimeMillis()获得 long 整数作为日期参数
    2. Date(long date)
      说明: 根据指定的long型整数来生成一个Date对象; 该构造器的参数表示创建的Date对象和 GMT 1970年1月1日00:00:00之间的时间差, 以毫秒作为计时单位
  • Date对象的方法

    1. Boolean after(Date when)
      说明: 测试该日期是否在指定日期when之后
    2. boolean before(Date when)
      说明: 测试该日期是否在指定日期when之前
    3. long getTime()
      说明: 返回long型整数, 即从 GMT 1970年1月1日00:00:00之间的时间差, 以毫秒作为计时单位
    4. void setTime(long time)
      说明: 设置该Date对象的时间

4.2 Calendar 类

  1. Calendar 类是一个抽象类, 更好地处理日期和时间
  2. 因为是一个抽象类, 所以不能使用构造器来创建 Calendar 对象, 但是可通过 getInstance()方法来获取 Calendar 对象,
  3. Calendar 与 Date 都是表示日期的工具类, 他们之间可以自由转换

Calendar 类常用方法

  1. void add(int field, int amount)
    说明: 根据日历的规则, 为给定的日历字段添加或减去指定的时间量
  2. int get(int field)
    说明: 返回指定日历字段的值
  3. int getActualMaximum(int field)
    说明: 返回指定日历字段可能拥有的最大值, 月最大值为11
  4. int getActualMinimum(int field)
    说明: 返回指定日历字段可能拥有的最小值, 月最小值为0
  5. void roll(int field, int amount)
    说明: 与 add()方法类似, 区别在于加上amount后超过了该字段所能表示的最大范围时, 也不会向上一个字段进位
  6. void set(int field, int value)
    说明: 将给定的日历字段设置为给定值
  7. void set(int year, int month, int date)
    说明: 设置 Calendar 对象的年, 月, 日三个字段值
  8. void set(int year, int month, int date, int hourOfDay, int minute, int second)
    说明: 设置 Calendar 对象的年, 月, 日, 时, 分, 秒6个字段值
  • 注意:
    1. 上面的很多方法都需要一个int类型的field参数, field是Calendar类的类变量,如Calendar.YEAR Calendar.MONTH 等分别代表了年月日小时分钟秒等时间字段
    2. Calendar.MONTH 的字段是从0开始的

示例代码

// 例子1:
public calss CalendarTest{
    public static void main(String[] args){
        Calendar c = Calendar.getInstance();
        // 取出年
        System.out.println(c.get(YEAR));
        // 取出月份
        System.out.println(c.get(MONTH));
        // 取出日
        System.out.println(c.get(DATE));
        // 分别设置年, 月, 日, 时, 分, 秒 
        c.set(2020, 09, 07, 20, 00, 00);
        System.out.println(c.getTime());
        // 将Calendar 的年前推一年
        c.add(YEAR, -1);
        System.out.println(c.getTime());
        // 将Calendar的月份前推8个月
        c.roll(MONTH, -8);
        System.out.println(c.getTime());
    }
}        

add() 和 roll() 方法的区别

  1. add(int field, int amount)的功能非常强大, add 主要的用于改变 Calendar的特定字段的值, 如果需要增加某字段值, 则让amount为正数, 如果需要减少某字段的值, 则让amount为附负数即可
  2. 当被修改的字段超出允许的范围时, 会发生进位, 即上一级字段也会增大(例子1)
  3. 如果下一级字段也需要改变, 那么该字段会修正到变化最小的值(例子2)
  4. roll()的规则与add()的处理规则不同: 当被修改的字段超出它允许的范围时, 上一级字段不会增大(例子3)
  5. 下一级字段的处理规则与add()相似(例子4)

示例代码

//例子1:
Calendar cal1 = Calendar.getInstance();
cal1.set(2003, 7, 23, 0, 0, 0);  // 2003-8-23
cal1.add(MONTH, 6);   // 2003-2-23
// 例子2:
Calendar cal2 = Calendar.getInstance();
cal2.set(2003, 7, 31, 0, 0, 0);  // 2003-8-31
// 因为进位后月份改为2月, 2月没有31日, 自动变成29日
cal2.add(MONTH, 6);   // 2003-8-21 => 2004-2-29
// 例子3:
Calendar cal3 = Calendar.getInstance();
cal3.set(2003, 7, 23, 0, 0, 0);  // 2003-8-23
// MONTH 字段 "进位", 但YEAR字段并没有增加
cal3.roll(MONTH, 6); // 2003-8-23 => 2003-2-23
// 例子4:
Calendar cal4 = Calendar.getInstance();
cal4.set(2003, 7, 31, 0, 0, 0);  // 2003-8-31
// MONTH 字段 "进位"后变成2, 2月没有31日
// YEAR 字段不会改变, 2003年的2月份只有28天
cal4.roll(MONTH, 6);  // 2003-8-31 => 2003-2-28

设置 Calendar 的容错性

  1. 因为 Calendar 对象的 set() 方法来改变指定时间字段的值时, 有可能传入一个不合法的参数, 所以需要通过容错性来指定是否检查输入的值符合规定
  2. Calendar 通过setLenient()方法来设置它的容错性, Calendar 默认支持较好的容错性, 可通过setLenient(false) 关闭 Calendar 的容错性, 让它进行更严格的参数检查
  3. Calendar 由两种解释日历字段的模式: lenient 模式和 non_lenient 模式:lenient 模式时每个时间字段可接受超出它允许范围的值;non_lenient 模式时如果为某个时间字段设置的值超出程序允许范围时, 程序将抛出异常

示例代码

// 例子:
public class LenientTest{
    public static void main(String[] args){
        Calendar cal = Calendar.getInstance();
        // 结果是 YEAR 字段加1, MONTH字段为1(2月)
        cal.set(MONTH, 13); 
        System.out.println(cal.getTime());
        // 关闭容错性
        cal.setLenient(false);
        // 导致运行时异常, 因为关闭容错性, 执行更严格的时间参数检查
        cal.set(MONTH, 13); 
        System.out.println(cal.getTime());
    }
}   

set()方法的延迟修改:

  1. 含义: set(f, value)方法将日历字段f更改为value, 此外他还设置了一个内部成员变量, 以指示日历字段f已经被更改; 尽管日历字段f是立即更改的, 但该Calendar所代表的时间却不会立即修改直到下次调用get(), getTime(), getTimeMillis(), add()或roll()时才会重新计算日历时间,这被称之为 set()方法的延迟修改 即: 调用 set(f, value) 方法后不会立即修改, 直到调用获取时间的方法时才修改
  2. 优势: 延迟修改的优势是多次调用set()不会触发多次不必要的计算(需要计算出一个代表实际时间的long型整数)

示例代码

// 例子:
ublic class LazyTest{
    public static void main(String[] args){
        Calendar cal = Calendar.getInstance();
        cal.set(2003, 7, 31);  // 2003-8-31
        // 将月份设置为9, 但9月31不存在
        // 如果立即修改, 系统将会把cal自动调整为10月1日
        cal.set(MONTH, 8);
        // 下面的代码输出10月1日
        System.out.prntln(cal.getTime());    // ①
        // 设置 DATE 字段为 5 
        cal.set(DATE, 5);     // ②
        System.out.prntln(cal.getTime());    // ③
    } 
}       
// 程序说明:
//     1. 上面程序中创建了代表2003-8-31的Calendar对象, 当把这个对象的MONTH字段加1后得到
//         2003-10-1(因为9月没有31日), 如果程序在①处代码输出当前Calendar里的日期, 也会看到
//         输出的是2003-10-1, ③处的代码将输出2003-10-5
//     2. 如果将①处的代码注释起来, 因为Calendar的set()方法具有延迟修改的特性, 即调用set()方
//         法后Calendar实际上并未计算真实的日期, 他只是使用内部成员变量表记录MONTH字段被修改
//         为8, 接着程序设置DATE字段的值为5, 程序内部再次记录DATE字段为5---也就是9月5日, 因
//         次这是的③处将输出 2003-9-5

Java8新增的日期, 时间java.time包类及方法

  1. Clock
    说明: 该类用于获取指定时区的当前日期, 时间; 可代替 System类的 currentTimeMillis()方法, 而且提供了更多的方法来获取当前日期, 时间, 该类提供了大量的静态方法来获取Clock对象

  2. Duration
    说明: 该类代表持续时间, 可以很方便的获取一段时间

  3. Instant
    说明: 代表一个具体的时刻, 可以精确到纳秒. 该类提供 now() 方法用来获取当前时刻; 也提供了静态的 now(Clock clock) 方法来获取 clock 对应的时刻; 还提供了 minusXxx()方法在当前时刻的基础上减去一段时间; 也提供了 plusXxx()方法在当前时刻的基础上加上一段时间

  4. LocalDate
    说明: 该类代表不带时区的日期:如2019-10-01; 提供静态 now() 方法获取当前日期; 提供静态的now(Clock clock) 方法来获取clock对应的日期; 提供 minusXxx()方法在当前年份基础上减去几年几月几周或几日; 提供 plusXxx() 方法在当前年份的基础是加上几年几月几周或几日

  5. LocalTime
    说明: 该类代表了不带时区的时间:如 10:15:30; 提供静态 now() 方法获取当前时间; 提供静态的now(Clock clock) 方法来获取clock对应的时间; 提供 minusXxx() 方法在当前年份基础上减去几小时,几分,几秒; 提供 plusXxx() 方法在当前年份基础上加上及小时, 几分, 几秒

  6. LocalDateTime
    说明: 该类代表不带时区的日期, 时间, 如 2017-12-03T10:15:33; 也提供了 now(), now(Clock clock), minusXxx(), plusXxx() 等方法

  7. MonthDay
    说明: 该类代表月日, 如04-12; 提供now()方法获取当前月日, 也提供静态的now(Clock clock)方法来获取 clock 对应的月日

  8. Year
    说明: 该类仅代表年. 如2014; 提供 now(), now(Clock clock), minusXxx(), plusXxx() 等方法

  9. YearMonth
    说明: 该类仅代表年月, 如2014-08; 提供 now(), now(Clock clock), minusXxx(), plusXxx() 等方法

  10. ZonedDateTime
    说明: 该类代表一个时区化的日期, 时间

  11. Zoneld
    说明: 该类代表一个时区

  12. DayOfWeek
    说明: 这是一个枚举类, 顶一颗周日到周六的枚举值

  13. Month
    说明: 这也是一个枚举类, 定义了一月到十二月的枚举值

示例代码

// 例子: 
// NewDatePackageTest.java
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;
import java.time.MonthDay;
import java.time.Year;
import java.time.YearMonth;
public class NewDatePackageTest {
    public static void main(String[] args) {
        System.out.println("----下面是关于Clock的用法----");
        // ----下面是关于Clock的用法----
        // 获取当前Clock
        Clock clock = Clock.systemUTC();
        // 通过 Clock 获取当前时刻
        System.out.println("当前时刻为: " + clock.instant());
        // 获取 clock 对应的毫秒数, 与 System.currentTimeMillis()输出相同
        System.out.println(clock.millis());
        System.out.println(System.currentTimeMillis());
        // 
        System.out.println("----下面是关于Duration的用法----");
        // ----下面是关于Duration的用法----
        Duration d = Duration.ofSeconds(6000);
        System.out.println("6000秒相当于 " + d.toMinutes() + " 分钟");
        System.out.println("6000秒相当于 " + d.toHours() + " 小时");
        System.out.println("6000秒相当于 " + d.toDays() + " 天");
        // 在 clock 基础上增加 6000 秒, 返回新的 Clock
        Clock clock2 = Clock.offset(clock, d);
        // 可以看到clock2和clock相差1小时40分
        System.out.println("当前时刻加6000秒为: " + clock2.instant());
        // 
        System.out.println("----下面是关于Instant的用法----");
        // ----下面是关于Instant的用法----
        // 获取当前时间
        Instant instant = Instant.now();
        System.out.println(instant);
        // instant 添加6000秒(100分钟), 返回新的Instant
        Instant instant2 = instant.plusSeconds(6000);
        System.out.println(instant2);
        // 根据字符串解析Instant对象
        Instant instant3 = Instant.parse("2014-02-23T10:12:35.342Z");
        System.out.println(instant3);
        // 在instant3的基础上添加5小时4分钟
        Instant instant4 = instant3.plus(Duration.ofHours(5).plusMinutes(4));
        System.out.println(instant4);
        // 获取 instant4 的5天以前的时刻
        Instant instant5 = instant4.minus(Duration.ofDays(5));
        System.out.println(instant5);
        // 
        System.out.println("---下面是关于LocalDate的用法----");
        // ----下面是关于LocalDate的用法----
        LocalDate localDate = LocalDate.now();
        System.out.println(localDate);
        // 获取2014年的第146天
        localDate = LocalDate.ofYearDay(2014, 146);
        System.out.println(localDate); // 2014-05-26
        // 设置为 2014年5月21日, 月份调用MONTH的枚举实例
        localDate = LocalDate.of(2014, Month.MAY, 21);
        System.out.println(localDate);  // 2014-05-21
        // 
        System.out.println("----下面是关于LocalTime的用法----");
        // ----下面是关于LocalTime的用法----
        // 获取当前时间
        LocalTime localTime = LocalTime.now();
        // 设置时间为 22点33分
        localTime = LocalTime.of(22, 33);
        System.out.println(localTime);  // 22:33
        // 返回一天中的9070秒
        localTime = LocalTime.ofSecondOfDay(9070);
        System.out.println("一天中的9070秒是: " + localTime);
        //
        System.out.println("-----下面是LocalDateTime的用法----");
        // ----下面是LocalDateTime的用法----
        // 获取当前的日期时间
        LocalDateTime localDateTime = LocalDateTime.now();
        // 当前的日期, 时间加上25小时3分钟
        LocalDateTime future = localDateTime.plusHours(25).plusMinutes(3);
        System.out.println("当前的日期,时间的25小时3分钟之后是: " + future);
        //
        System.out.println("----下面是关于Year, YearMonth, MonthDay的用法----");
        // ----下面是关于Year, YearMonth, MonthDay的用法----
        // 获取当前的年份
        Year year = Year.now();  
        System.out.println("当前年份是: " + year);
        // 当前的年份再加5年
        year = year.plusYears(5);
        System.out.println("当前年份再过5年是: " + year);
        // 根据指定月份获取YearMonth
        YearMonth ym = year.atMonth(10);
        System.out.println("year年的10月: " + ym);
        // 当前的年份再加5年, 减去3个月
        ym = ym.plusYears(5).minusMonths(3);
        System.out.println("year年10月再加5年,减去3个月是: " + ym);
        MonthDay md = MonthDay.now();
        System.out.println("当前月日: " + md);
        // 设置为5月23日
        MonthDay md2 = md.with(Month.MAY).withDayOfMonth(23);
        System.out.println("5月23日为: " + md2);
    }
}

5. 正则表达式

String 类中正则的几个方法

  1. boolean matches(String regex)
    说明: 判断该字符串是否匹配指定的正则表达式

  2. String replaceAll(String regex, String replacement)
    说明: 将该字符串中所有匹配 regex 的字串替换成 replacement

  3. String replaceFirst(String regex, String replacement)
    说明: 将该字符串中第一个匹配 regex 的字串替换成 replacement

  4. String[] split(String regex)
    说明: 以regex作为分隔符, 把该字符串分割成多个字串

创建正则表达式

正则表达式支持的合法字符

字符            解释
x字符x(x可以是任何合法的字符)
\0mnn八进制数0mnn所表示的字符
\xhh十六进制 0xhh 所表示的字符
\uhhh十六进制 0xhhh 所表示的 Unicode 字符
\t制表符("\u0009")
\n换行(新行)符 ("\u000A")
\r回车符 ("\u000D")
\f换页符 ("\u000C")
\a报警(bell)符 ("\u0007")
\eEscape符 ("\u001B")

正则表达式中的特殊字符

字符            解释
$匹配一行的行尾, 要匹配 $ 字符本身, 使用 $
^匹配一行的开头. 要匹配 ^ 字符本身, 使用 ^
()标记自表达式的开始和结束位置, 要匹配这些字符, 使用 \( 和 \)
[]用于确定中括号表达式的开始和结束位置, 要匹配字符本身, 使用 \[ 和 \]
{}用于标记前面子表达式出现的频度, 要匹配字符本身, 使用 \{和 \}
\*指定前面的子表达式可以出现0次或多次, 要匹配字符本身, 使用 \\
+指定前面的自表达式可以出现1次或多次, 要匹配+字符本身, 使用 \+
?指定前面的自表达式可以出现0次或1次, 要匹配?字符本身, 使用 ?
.匹配除换行符("\n")之外的任何单个字符, 要匹配 . 本身, 使用 \\.
\用于转义下一个字符, 或指定八进制, 十六进制字符, 要匹配 \ 本身. 使用 \\
|指定两项之间任选一项, 如果要匹配 | 本身, 使用 \|

预定义字符

字符            解释
.可以匹配任何单个字符
\d匹配 0~9 的所有数字
\D匹配非数字
\s匹配所有的空白符, 包括空格, 制表符, 回车符, 换页符, 换行符等
\S匹配所有的非空白字符
\w匹配所有的单词字符, 包括 0~9 所有数字, 26个英文字母和下划线(_)
\W匹配所有的非单词字符

方括号表达式

方括号         解释 
枚举例如[abc],表示a,b,c其中任意一个字符; [gz], 表示g, z其中任意一个字符
范围 ~[a~cx~z],表示a~c, x~z范围内的任意字符
求否 ^例如[^abc], 表示非 a,b,c的任意字符; [^a~f],表示不是a~f范围内的任意字符
"与"运算 &&例如 [a-z&&[def]], 求 a~z 和[def]的交集, 表示d, e 或 f [a-z&&[^bc]], a~z 范围内除了b和c之外的字符, 即 [ad-z] [a-z&&[^m-p]], a~z范围内除去m~p范围的所有字符 即[a-lq-z]
"并"运算并运算与前面的枚举类似, 例如[a-d[m-p]] 等价于 [a-dm-p]

边界匹配符

边界匹配符         解释 
^行的开头
$行的结尾
\b单词的边界
\B非单词的边界
\A输入的开头
\G前一个匹配的结尾
\Z输入的行尾, 仅用于最后的结束符
\z输入的行尾

正则表达式数量标识符的几种模式

  • Greedy(贪婪模式):

    1. 数量标识符默认采用贪婪模式, 除非另有表示
    2. 贪婪模式的表达式会一直匹配下去, 直到无法匹配为止
  • Reluctant(勉强模式):

    1. 用问号后缀(?)表示, 他只会匹配最少的字符, 也称为最小匹配模式
  • possessive(占有模式):

    1. 占有模式目前只有Java支持
    2. 用加号后缀(+)表示, 通常比较少用

三种模式的数量表示符

贪婪模式   勉强模式    占有模式      说明
X?X??X??X表达式出现0次或1次
X*X*?X*?X表达式出现0次或多次
X+X+?X+?X表达式出现1次或多次
XX?X?X表达式出现n次
X{n,}X{n,}?X{n,}?X表达式最少出现n次
X{n,m}X{n,m}?X{n,m}?X表达式最少出现n次, 最多出现m次

使用正则表达式:

  1. 熟悉 Pattern 和 Matcher 类
  2. Pattern 对象是正则表达式编译后再内存中的表示形式, 因此正则表达式字符串必须先被编译为 Pattern对象, 然后再利用该Pattern对象创建对应的Matcher对象. 执行匹配所涉及的状态保留再Mather对象中, 多个Matcher对象可以共享他同一个Pattern对象
  3. pattern 类是不可变类, 可供多个并发的线程安全使用

Matcher类的几个常用方法

  1. find()
    说明: 返回目标字符串中是否包含与Pattern匹配的字串
  2. group()
    说明: 返回上一次与Pattern匹配的字串
  3. start()
    说明: 返回上一次与Pattern匹配的字串在目标字符串中的开始位置
  4. end()
    说明: 返回上一次与Pattern匹配的字串在目标字符串中的结束位置加1
  5. lookingAt()
    说明: 返回目标字符串前面部分与Pattern是否匹配
  6. matches()
    说明: 返回整个目标字符串与Pattern是否匹配
  7. reset()
    说明: 将现有的 Matcher 对象应用于一个新的字符序列

示例代码

// 例子1:
// 最经典的正则表达式调用顺序
// 将一个字符串编译成 Patern 对象, 这个 Pattern 对象可以重复使用
Pattern p = Pattern.compile("a*b");
// 使用 Pattern 对象创建 Matcher 对象
Matcher m = p.matcher("aaaaaab");
boolean b = m.matches();  // out: true
// 如果不需要重复使用, 即这个正则表达式只使用一次, 可使用Pattern类的静态方法matches()
// 此方法自动把指定字符串编译v黑人女孩匿名的Pattern对象, 并执行匹配
boolean b = Pattern.matches("a*b", "aaaaab");  // out: true 
// 例子2:
// FindGroup.java
// 从大段的字符串中找出电话号码
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class FindGroup{
    public static void main(String[] args) {
        // 使用字符串模拟从网路上得到的网页源码
        String str = "我想求购一本<疯狂 Java 讲义>, 尽快联系我 12345678912" 
            + "交朋友, 电话号码: 12934568715"
            + "出售二手电脑, 电话号码: 12456783324";
        // 创建一个 Pattern 对象, 并用它建立一个Matcher对象
        // 该正则表达式中抓取12开头的手机号码
        // 可根据需求改变匹配规则
        Matcher m = Pattern.compile("12\\d{9}").matcher(str);
        while (m.find()){
            System.out.println(m.group());
        }
    }
}
// 例子3:
// StartaEnd.java
// start() 和 end() 方法确定字串在目标字符串中出现的位置
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class StartEnd{
    public static void main(String[] args) {
        // 创建一个 Pattern 对象, 并用它建立一个 Matcher 对象
        String regStr = "Java is very easy! I think Not";
        System.out.println("目标字符串是: " + regStr);
        Matcher m = Pattern.compile("\\w+").matcher(regStr);
        while(m.find()){
            System.out.println(m.group() + " 字串的起始位置: " 
                + m.start() + ";  字串的结束位置: " + m.end() 
                + "; 字符长度为:" + (m.end() - m.start()));
        }
    }
}
// 例子4:
// MatchesTest.java
// matches() 和 lookingAt() 方法有点类似, 只是 matches() 方法要求整个字符串和Pattern完全匹配
// 时才返回true, 而 lookingAt() 只要字符串以 pattern 开头就会返回 true
// reset()方法可将现有的Matcher对象应用于新的字符序列
public class MatchesTest{
    public static void main(String[] args) {
        String[] mails = {
            "kongeni@163.com",
            "jefxff@outlook.com",
            "jerry@gmail.com",
            "waaw@abc.xx",
            "aaa@wwwwwwww.gov"
        };
        String mailRegEx = "\\w{2,8}@\\w{2,5}\\.(com|xx|gov)";
        Pattern mailPattern = Pattern.compile(mailRegEx);
        Matcher matcher = null;
        for(String mail : mails){
            if(matcher == null){
                matcher = mailPattern.matcher(mail);
            }
            else{
                matcher.reset(mail);
            }
            String result = mail + (matcher.matches() ? " 是" : " 不是") 
                + "一个有效的地址!";
            System.out.println(result);
        }
    }
}
// 例子5:
// ReplaceTest.java
// 利用正则表达式对目标字符串进行分割, 查找, 替换等操作
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ReplaceTest {
    public static void main(String[] args) {
        String[] msgs = {
            "java has regular expressions in 1.4",
            "regular expressions noe expressing in java",
            "java represses oracular expressions"
        };
        Pattern p = Pattern.compile("re\\w*");
        Matcher matcher = null;
        for (int i = 0; i < msgs.length; i++){
            if (matcher == null){
                matcher = p.matcher(msgs[i]);
            }
            else{
                matcher.reset(msgs[i]);
            }
            System.out.println(matcher.replaceAll("哈哈"));
        }
    }
}
// 例子6:
// StringReg.java
// String 类中提供的 replaceAll(), replaceFirst(), split() 等方法应用
import java.util.Arrays;
public class StringReg {
    public static void main(String[] args) {
        String[] msgs = {
            "java has regular expressions in 1.4",
            "regular expressions noe expressing in java",
            "java represses oracular expressions"
        };
        for (String msg : msgs){
            System.out.println(msg.replaceFirst("re\\w*", "哈哈:)"));
            System.out.println(Arrays.toString(msg.split(" ")));
        }
    }
}

6 DateFormat 抽象类

  1. java.text.DateFormat 是一个抽象类,不能直接使用, 需要通过java.text.SimpleDateFormat子类来进行Date类型和String类型的互相转换
  2. 格式化: 通过 String format(Date date) 方法, 将 Date 对象转化为 String 对象
  3. 解析: 通过 Date parse(String source) 方法, 将 String 对象转换为 Date 对象

示例代码

import java.text.SimpleDateFormat;
import java.util.Date;
//
public class DateFormatTest {
    public static void main(String[] args) throws Exception {
        System.out.println("将Date格式转换为String格式");
        // 先创建一个Date对象, 在创建一个SimpleDateFormat对象并传入日期格式, 通过 SimpleDateFormat
        // 对象调用format(Date d) 方法, 进行格式化, 并返回 String 类型的对象
        Date d = new Date();
        // SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        String s = sdf.format(d);
        System.out.println(s + "\n");
        //
        System.out.println("将String格式转换为Date格式");
        // 先有String类型的日期时间字符串, 在创建一个SimpleDateFormat对象并传入日期格式, 通过 SimpleDateFormat
        // 对象调用 parse(String ss) 方法, 进行解析, 并返回 Date 类型的对象
        String ss = "1991-09-07 08:08:08";
        SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date d2 = sdf2.parse(ss);
        System.out.println(d2);
    }
}

7. Java8新增的日期, 时间格式

  1. 在java.time.format包下提供了一个DateTimeFormatter格式器类, 该类相当于前面学习的 DateFormat 和 SimpleDateFormat 的合体
  2. DateTimeFormatter能将日期, 时间对象格式化成字符串, 也可以将特定格式的字符串解析成日期, 时间对象
  3. 使用 DateTimeFormatter 进行格式化或解析之前, 必须先获取 DateTimeFormatter 对象

获取 DateTimeFormatter 对象的三种方式

  1. 直接使用静态常量创建 DateTimeFormatter 格式器 (DateTimeFormatter 类中包含了大量形如ISO_LOCAL_DATE, ISO_LOCAL_TIME, ISLO_LOCAL_DATE_TIME等静态常量, 这些常量本身就是 DateTimeFormatter 实例)
  2. 使用代表不同风格的枚举值来创建 DateTimeFormatter 格式器; 在 FormatStyle 枚举类中定义了 FULL, LONG, MEDIUM, SHORT四个枚举值, 他们代表日期, 时间的不同格式
  3. 根据模式字符串来创建 DateTimeFormatter 对象, 类似于 SimpleDateFormat, 可以采用模式字符串来创建DateTimeFormatter

♥使用 DateTimeFormatter 将日期, 时间格式化成字符串(两种方式)

  1. 调用 DateTimeFormatter 的 format(TemporaAccessor temporal)方法执行格式化, 其中LocalDate, LocalDateTime, LocalTime 等类都是 TemporaAccessor 接口的实现
  2. 调用 中LocalDate, LocalDateTime, LocalTime 等日期, 时间对象的 format(DateTimeFormatter)方法执行格式化

示例代码

// 例子:
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
public class NewFormatterTest {
    public static void main(String[] args){
        DateTimeFormatter[] formatters = new DateTimeFormatter[]{
            // 直接使用常量创建 DateTimeFormatter格式器
            DateTimeFormatter.ISO_LOCAL_DATE,
            DateTimeFormatter.ISO_LOCAL_TIME,
            DateTimeFormatter.ISO_LOCAL_DATE_TIME,
            // 使用本地化的不同风格来创建 DateTimeFormatter 格式器
            DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL, FormatStyle.MEDIUM),
            // DateTimeFormatter.ofLocalizedTime(FormatStyle.LONG),
            // 根据模式字符串来创建 DateTimeFormatter格式器
            DateTimeFormatter.ofPattern("Gyyyy%%MMM%%dd HH:mm:ss"),
            DateTimeFormatter.ofPattern("Gyyyy-MMM-d HH:mm:ss")
        };
        LocalDateTime date = LocalDateTime.now();
        // 一次使用不同的格式器对 LocalDateTime 进行格式化 
        for (int i = 0; i < formatters.length; i++){
            // 这两行代码的作用相同
            System.out.println(date.format(formatters[i]));
            System.out.println(formatters[i].format(date));
        }
    }
}            

♥使用 DateTimeFormatter 字符串解析成日期,时间对象

DateTimeFormatter将指定格式的字符串解析成日期, 时间对象(LocalDate, LocalDateTime, LocalTime等实例), 可通过日期, 时间对象提供的 parse(CharSequence text, DateTimeFormatter formatter)方法进行.

示例代码

// 例子:
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class NewFormatterParse {
    public static void main(String[] args){
        // 定义一个任格式的日期, 时间字符串
        String str1 = "2014==09==12 01时06分09秒";
        // 根据需要解析的日期, 时间字符串定义解析所用的格式器
        DateTimeFormatter formatter1 = DateTimeFormatter.ofPattern(
            "yyyy==MM==dd HH时mm分ss秒");
        // 执行解析
        LocalDateTime dt1 = LocalDateTime.parse(str1, formatter1);
        System.out.println(dt1);    // out: 2014-09-12T01:06:09
        // 再次解析另外一个字符串
        String str2 = "2014$$$04$$$13 20 小时";
        DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyy$$$MM$$$dd HH 小时");
        LocalDateTime dt2 = LocalDateTime.parse(str2, formatter2);
        System.out.println(dt2);    // out: 2014-04-13T20:00
    }
}

8. Calendar 类

  1. calendar 类是一个表示日历类的抽象类, 可以进行日期运算, 不能创建对象
  2. 可以通过两种方式来获取GregorianCalendar对象:
    • 直接创建GregorianCalendar对象;
    • 通过 Calendar 类的静态方法 getInstance()方法获取GregorianCalendar对象

常用方法

  1. public static Calendar getInstance()

    • 获取一个它的子类 GregorianCalendar 对象
  2. public int get(int field)

    • 获取某个字段的值, field 参数表示获取那个字段的值
    • 可以使用Calendar中定义的常量来表示:
      • Calendar.YEAR : 年
      • Calendar.MONTH :月
      • Calendar.DAY_OF_MONTH:月中的日期
      • Calendar.HOUR:小时
      • Calendar.MINUTE:分钟
      • Calendar.SECOND:秒
      • Calendar.DAY_OF_WEEK:星期
  3. public void set(int field, int value)

    • 设置某个字段的值
  4. public void add(int field, int amount)

    • 为某个字段增加/减少指定的值

常用方法代码示例

import java.util.Calendar;
//
public class CalendarTest {
    // 根据int类型1~7的值, 返回String类型的对应的字符串
    public static String getWeek(int w) {
        String[] week = {"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"};
        return week[w-1];
    }
// 
    public static void main(String[] args) throws Exception {
    // 通过Calendar获取执行代码的此刻的日期及时间
        // 通过 Calendar 的getInstance() 方法来获取Calendar对象
        Calendar d = Calendar.getInstance();
        int year = d.get(Calendar.YEAR);
        int month = d.get(Calendar.MONTH) + 1;
        int day = d.get(Calendar.DAY_OF_MONTH);
        int hour = d.get(Calendar.HOUR_OF_DAY);
        int minute = d.get(Calendar.MINUTE);
        int second = d.get(Calendar.SECOND);
        //返回值范围:1--7,分别表示:"星期日","星期一","星期二",...,"星期六"
        int week = d.get(Calendar.DAY_OF_WEEK);
        System.out.println(year + "年 " + month + "月 " + day + "日 " + 
                               hour + " : " + minute + " : " + second);
        System.out.println(week);
        System.out.println(getWeek(week));
        // 
    // 通过set方法指定Calendar的日期
        Calendar c = Calendar.getInstance();
        c.set(Calendar.YEAR, 1991);
        c.set(Calendar.MONTH, 9-1);
        c.set(Calendar.DAY_OF_MONTH, 07);
        // 
        int w = c.get(Calendar.DAY_OF_WEEK);
        System.out.println(getWeek(w));
        // 
    // 通过add()方法增加或减少指定天数
        Calendar c2 = Calendar.getInstance();
        c2.add(Calendar.DAY_OF_MONTH, 200);
        int y = c2.get(Calendar.YEAR);
        int m = c2.get(Calendar.MONTH) + 1;//转换为实际的月份
        int d = c2.get(Calendar.DAY_OF_MONTH);
        int wk = c2.get(Calendar.DAY_OF_WEEK);
        System.out.println("200天后是:" + y + "年" + m + "月" + d + "日" + getWeek(wk));
    }
}

# Java