08【Java核心API-02】

08【Java核心API-02】

一、Object类

1.1 Object类概述

java.lang.Object类是Java语言中的根类,即所有类的父类。也就是说,Java允许把任何类型的对象赋给Object类型的变量(多态)。 当我们定义一个类时,如果没有指定继承的父类,那么该类就会隐式地继承自Object类。

此外,由于所有的类都直接或间接地继承了Object类,因此我们可以在任何类的对象上调用Object类的方法。这为我们提供了很大的便利性,因为我们可以将一些通用的操作统一放在Object类中实现,然后在其他需要的地方调用即可。

如果一个类没有特别指定父类,那么默认则继承自Object类。例如:

public class MyClass /*extends Object*/ {
  	// ...
}

因为Object类是Java中所有类的根类,在Java中所有类都直接或间接继承与Object类,因此Object类型可以接收任意的Java对象:

package com.dfbz.demo01_Object类简介;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_Object类简介 {
    public static void main(String[] args) {
        method(new B());
        method(new A());
    }

    /**
     * 如果一个方法的形参为Object类型,那么调用这个方法可以传递任意的Java对象
     * Object obj = new A();
     * Object obj = new B();
     *
     * @param obj
     */
    public static void method(Object obj) {

       System.out.println("传递成功!");
       
    }
}

class A {

}

class B extends A {
}

1.2 toString方法

1.2.1 方法的特点

方法名 介绍
public String toString() 返回该对象的字符串表示。我们在输出对象时,默认会调用该对象的toString()方法;

toString方法返回该对象的字符串表示,其实该字符串内容就是对象的类型+@+内存地址值。由于toString方法返回的结果是内存地址,而在开发中,经常需要按照对象的属性得到相应的字符串表现形式,因此也需要重写它。

  • 定义一个Person类:
package com.dfbz.demo02_toString方法;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Person {

    private String name;
    private int age;

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

    public Person() {
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
  • 编写测试类:
package com.dfbz.demo02_toString方法;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {

    public static void main(String[] args) {
        Person person = new Person("小灰", 20);

        // 输出一个该类的全类名(所在包名+类名)@内存地址值
        System.out.println(person);			
    }
}

输出结果:

Tips:toString方法默认输出的是:类的全类名@内存地址值

1.2.2 重写方法

如果不希望使用toString方法的默认行为,则可以对它进行覆盖重写。例如自定义的Person类:

public class Person {

    private String name;
    private int age;

    @Override
    public String toString() {
        return "我是一个person类,姓名为【" + name + "】,年龄是【" + age + "】";
    }
}

重新执行测试程序:

在IntelliJ IDEA中,可以点击菜单栏中的Code菜单中的Generate...,也可以使用快捷键alt+insert,点击toString()选项。选择需要包含的成员变量并确定。如下图所示:

1.3 equals方法

1.3.1 方法的特点

方法名 作用
public boolean equals(Object obj) 指示其他某个对象是否与此对象“相等”。

调用成员方法equals并指定参数为另一个对象,则可以判断这两个对象是否是相同的。这里的“相同”有默认和自定义两种方式。

在默认情况下(Object类中的equals实现),比较的是两个对象的内存地址值,即equals方法和 == 比较的结果是一致的,只要不是同一个对象,结果必然为false。

以下是Object类中对equals方法的实现:

public boolean equals(Object obj) {
    return (this == obj);
}
  • 测试equals方法,示例代码:
package com.dfbz.demo03_equals方法;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {
        Person p1 = new Person("小灰", 20);
        Person p2 = new Person("小灰", 20);

        // false
        System.out.println(p1.equals(p2));      // equals默认的实现为比较两个对象的内存地址值,结果为false

        // false
        System.out.println(p1 == p2);
    }
}

1.3.3 重写方法

在Java中,只要通过new关键字来对象,那么必定会在堆内存中开辟一块新的内存空间,因此在比较两个对象是否相等时,大多数情况下内存地址值并不是我们比较两个对象是否相同的依据。在实际开发中我们认为,如果两个对象的内容(属性值)相同,我们就认为两个对象是相等的。

如果希望进行对象的内容比较,则可以覆盖重写equals方法。例如:

@Override
public boolean equals(Object obj) {

    Person p = (Person) obj;

    // 注意: 字符串的对比要使用equals,不能使用 ==
    boolean attr_1 = p.getName().equals(this.name);
    boolean attr_2 = p.getAge() == this.age;

    // 要两个属性都相同才认为两个对象是一样的
    return attr_1 && attr_2;
}

重新执行测试代码,发现执行结果为true;

同样的,idea开发也为我们提供了重写equals方法的快捷键,在IntelliJ IDEA中,可以使用Code菜单中的Generate…选项,也可以使用快捷键alt+insert,并选择equals() and hashCode()进行自动代码生成。如下图所示:

Tips:在IDEA快捷键中,生成equals和hashCode方法是绑定在一起的关于什么是hashCode方法我们后面再了解

生成的代码如下:

import java.util.Objects;

public class Person {	
	private String name;
	private int age;
	
    @Override
    public boolean equals(Object o) {
        // 如果对象地址一样,则认为相同
        if (this == o)
            return true;
        // 如果参数为空,或者类型信息不一样,则认为不同
        if (o == null || getClass() != o.getClass())
            return false;
        // 转换为当前类型
        Person person = (Person) o;
        // 要求基本类型相等,并且将引用类型交给java.util.Objects类的equals静态方法取用结果
        return age == person.age && Objects.equals(name, person.name);
    }
    
    // 生成的hashCode方法
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

这段代码充分考虑了对象为空、类型一致等问题,但方法内容并不唯一。大多数IDE都可以自动生成equals方法的代码内容。

1.4 hashCode方法

1.4.1 方法的特点

方法名 作用
public native int hashCode() 返回该对象的hashCode值

用于计算对象的哈希值(hash值)。hash值是一个整数,它代表了对象在内存中的地址或者标识符,它可以用来快速比较两个对象是否相等。通常情况下,不同对象的hash值是不一样的。在Object类中,对象的hash值的计算依据是对象的内存地址值,简言之,如果对象的内存地址值不同,那么hash值就不同。

但hash值并不是绝对的,因为hash算法本身的问题,可能会导致两个对象的内存地址值不同计算出来的hash值也相同,我们把这种情况称为hash冲突,尽管这种情况是绝少数的,但依旧存在。

Tips:Object中的hashCode方法被native关键字修饰,代表该方法属于本地方法,不是采用Java语言来实现的,并且本地方法的调用消耗的是本地方法栈内存;

  • hashCode方法,代码示例:
package com.dfbz.demo04_hashCode方法;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_hashCode方法测试 {
    public static void main(String[] args) {

        Student s1 = new Student();
        Student s2 = new Student();

        System.out.println(s1.hashCode());      // 356573597
        System.out.println(s2.hashCode());      // 1735600054

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

        // 注意: 默认情况下,hashCode的计算依据是两个对象的内存地址值,跟对象中的属性值无关
        Student s3 = new Student("小灰",20);
        Student s4 = new Student("小灰",20);

        System.out.println(s3.hashCode());      // 21685669
        System.out.println(s4.hashCode());      // 2133927002
    }
}

hashCode的计算依据是对象的内存地址值,可以看到当对象的内存地址值不同时,计算出来的hash值也不同;

1.4.2 hash冲突

前面说到,hash算法并不是特别完美,有时候不同的两个对象(内存地址值不一致)计算出来的hash值可能会一样,我们把这种情况称为hash冲突。尽管这种情况非常少,但依旧存在;

  • 演示hash冲突现象:
package com.dfbz.demo04_hashCode方法;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo03_演示hash冲突 {
    public static void main(String[] args) {
        String s1 = "Aa";
        String s2 = "BB";

        System.out.println(s1.hashCode());          // 2112
        System.out.println(s2.hashCode());          // 2112
    }
}

下面是String类对hashCode代码的实现(并不是根据内存地址值来计算hashCode):

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

String默认情况下是获取每一个字符的ASCII码来参与hashCode的计算,在上面算法中,ABBA得出的hashCode是不一致的;有效的解决了一定情况下的hash冲突问题,但是hash算法不能保证在所有情况下hash都能唯一

例如AaBB的hashCode却是一样的,这种造成了Hash冲突问题;

Aa:
h = 31*0+65
h = 31*65+97
h = 2112

BB:
h = 31*0+66
h = 31*66+66
h = 2112

通过String类对hashCode的实现可以看出来:hash算法并不能绝对的保证两个不同的对象算出来的hash值不一样

需要注意的是:

  • 1)hashCode一致的两个对象不能说明两个对象一定相同,因为可能会造成hash冲突(例如上面的Aa和BB)
  • 2)但是如果hashCode不同,则一定能说明这两个对象肯定不同,因为同一个对象计算的hashCode永远一样;

1.4.3 重写方法

hashCode方法的功能也是用来对比两个对象是否一致的,在大多数情况下,我们应该采用hashCode来对比两个对象是否一致,因为计算一次hashCode远比内存寻址要快得多;

但Object类中的hashCode方法的计算依据也是内存地址值,也就是说,当两个对象的内存地址值不一样时,hashCode通常也不一样(这并不是绝对的,因为可能造成hash冲突),因此大多数情况下会重写对象的hashCode方法,让其计算依据改为对象的属性值。即我们希望如果两个对象的属性值不同,那么两个对象的hashCode也应该不同;

重写Student的hashCode方法:

@Override
public int hashCode() {
    // 使用属性值来作为hashCode的计算依据
    return Objects.hash(name,age);
}

测试代码:

package com.dfbz.demo04_hashCode方法;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo03_重写hashCode方法 {
    public static void main(String[] args) {
        Student s1 = new Student("小黄", 20);
        Student s2 = new Student("小灰", 20);

        System.out.println(s1.hashCode());      // 23541172
        System.out.println(s2.hashCode());      // 23541172

        if(s1.hashCode() == s2.hashCode()){
            // 有可能是hash冲突,不能确定两个对象是否是一样的,需要调用equals来决定
            if(s1.equals(s2)){
                System.out.println("我敢肯定这两个对象绝对是一样的!");
            }else{
                // hash冲突了!
                System.out.println("出现hash冲突了!这两个对象其实不是一样的!");
            }
        }else{
            System.out.println("这两个对象我敢肯定不一样!");
        }
    }
}

IDEA同样也支持一键重写hashCode方法,和之前生成equals方法是一样的:

1.5 clone方法

方法名 作用
protected native Object clone() 返回一个该对象的clone对象,clone对象和源对象属性值一样,但内存地址值不一样

clone()方法可以用来复制对象。它创建并返回一个新的对象,这个新的对象与原对象的内容完全相同(但内存地址值不同),就像用同一个模子印出来的一样。但该方法被protected修饰,也就是默认情况下,该方法只允许子类来访问,但是我们可以在子类中重写clone方法;

需要注意的是:对象要被克隆必须要实现java.lang.Cloneable接口,代表该类是可以被克隆的,否则出现java.lang.CloneNotSupportedException异常;

  • 准备一个Cat对象:
package com.dfbz.demo05_clone方法;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Cat implements Cloneable{      // 实现Cloneable接口,代表可以被克隆
    private String name;
    private int age;

    /**
     * 重写clone方法,将权限修饰符改为public
     * @return
     * @throws CloneNotSupportedException
     */
    @Override
    public Object clone() throws CloneNotSupportedException {
        // 调用父类(Object)类的clone方法
        return super.clone();
    }

    public Cat() {
    }

    public Cat(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
  • 使用clone方法:
package com.dfbz.demo05_clone方法;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args)throws Exception {

        Cat c1 = new Cat("小黄", 20);
        Cat c2 = (Cat) c1.clone();

        // 内存地址值不一样
        System.out.println(c1);     // com.dfbz.demo05_clone方法.Cat@1540e19d
        System.out.println(c2);     // com.dfbz.demo05_clone方法.Cat@1540e19d

        // 属性值一样
        System.out.println(c1.getName());       // 小黄   
        System.out.println(c2.getName());       // 小黄
    }
}

1.6 finalize方法

方法名 作用
protected void finalize() 当对象被GC回收时调用该方法

finalize()方法是在对象被垃圾回收之前由JVM自动调用的一个特殊方法,它的主要作用是释放资源和做一些其他的工作。

需要注意的是,finalize()方法并不是用来替代正常的资源管理策略的,相反,它应该作为一种补充手段,用于处理一些无法预见的情况,例如当发生异常时忘记关闭资源等情况。此外,finalize()方法并不总是会被调用,而且其调用顺序也是不确定的。因此,不应该依赖于finalize()方法来确保资源的安全性,而应该始终遵循正常的资源管理策略。

  • 准备一个Dog类,重写finalize方法:
package com.dfbz.demo06_finalize方法;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Dog {
    private String name;

    public Dog(String name) {
        this.name = name;
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println(name + "被回收了");
    }
}
  • 测试类:
package com.dfbz.demo06_finalize方法;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) throws Exception{

        for (int i = 1; i <= 10; i++) {
            // 创建对象
            Dog dog = new Dog("小黄" + i);

            // 指向空(让dog对象变成垃圾)
            dog = null;
        }
        Runtime runtime = Runtime.getRuntime();
        runtime.gc();           // 启动垃圾回收器
    }
}

执行结果:

Tips:

  • 1)尽管gc()方法是用来启动Java虚拟机的垃圾回收器的,但并不能保证JVM每次都会立即响应并执行垃圾回收操作。这是因为垃圾回收是由Java虚拟机自行控制的,而不是由gc()方法直接控制的。
  • 2)建议不要使用gc方法来进行垃圾回收,而是让虚拟机自己决定何时进行垃圾回收。这是因为虚拟机会根据系统的状态和需求来优化垃圾回收的时间和次数,而直接调用gc()方法可能会干扰这种优化过程,导致系统性能下降。

1.7 Objects类

在刚才IDEA自动重写equals代码中,使用到了java.util.Objects类,那么这个类是什么呢?

JDK7添加了一个Objects工具类,它提供了一些方法来操作对象,它由一些静态的实用方法组成,用于计算对象的hashcode、返回对象的字符串表示形式、比较两个对象。

在比较两个对象的时候,Object的equals方法容易抛出空指针异常,而Objects类中的equals方法就优化了这个问题。方法如下:

  • public static boolean equals(Object a, Object b):判断两个对象是否相等。

我们可以查看一下源码,学习一下:

public static boolean equals(Object a, Object b) {  
    return (a == b) || (a != null && a.equals(b));  
}
  • 准备一个Person类:
package com.dfbz.demo07_objects类;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Person {

    private String name;

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

测试代码:

package com.dfbz.demo07_objects类;

import java.util.Objects;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {

        Person p1 = new Person("小灰");
        Person p2 = new Person("小灰");

        boolean flag = Objects.equals(p1, p2);

        System.out.println(flag);           // false
    }
}

重写Person类的equals方法:

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Person person = (Person) o;
    return Objects.equals(name, person.name);
}

@Override
public int hashCode() {
    return Objects.hash(name);
}

再次运行测试程序,发现输出true;

Tips:Objects里面的equals的逻辑是,先比较两个对象的内存地址值,然后a不能为null,然后最终的核心还是使用a本身的equals逻辑

二、Date类

2.1 Date类概述

2.1.1 Date的构造方法

java.util.Date类 表示特定的瞬间,精确到毫秒。

继续查阅Date类的描述,发现Date拥有多个构造函数,只是部分已经过时,但是其中有未过时的构造函数可以把毫秒值转成日期对象。

  • public Date():分配Date对象并初始化此对象,以表示分配它的时间(精确到毫秒)。
  • public Date(long date):分配Date对象并初始化此对象,以表示自从标准基准时间(称为“历元(epoch)”,即1970年1月1日00:00:00 GMT)以来的指定毫秒数。

Tips:由于我们处于东八区,所以我们的基准时间为1970年1月1日8时0分0秒。

简单来说:使用无参构造,可以自动设置当前系统时间的毫秒时刻;指定long类型的构造参数,可以自定义毫秒时刻。例如:

package com.dfbz.demo01;

import java.util.Date;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_构造方法 {
    public static void main(String[] args) {
        // 描述的是当前时间
        System.out.println(new Date());

        // 基于一个毫秒值来构建一个Date
        System.out.println(new Date(1261332586828L));       // Mon Dec 21 02:09:46 CST 2009

        // 默认为1970年的1月1日0时0分0秒,由于我们处于东八区,因此时间为1970年的1月1日8时0分0秒
        System.out.println(new Date(0L));       // Thu Jan 01 08:00:00 CST 1970
    }
}

2.1.2 时区的概念

我们都知道在地理中存在经线和纬线,其中东西方向为经线,南北方向为纬线。纬线和气温有关系,经线则跟时区有关系。

世界各地的时间并不是一致的,这是因为地球每24个小时就会围绕地轴旋转一周。当地球的某个地点享受着阳光的沐浴时,遥远的另一侧则笼罩在黑暗中,处于夜晚。世界上共有24个时区,每两个相邻时区的时间相差1小时。美国和加拿大疆域辽阔,横跨6个时区。

从上图可以看出,我们国家所处的大多地区城市都在东八区,包括北京、上海、广州、深圳、武汉、长沙、南昌、郑州、合肥、南京、天津、福州...

我国横跨五个时区:东五区、东六区、东七区、东八区、东九区;分别为:

昆仑时区 GMT+5.5 新疆西部与部分西藏

新藏时区 GMT+6 新疆及西藏

陇蜀时区 GMT+7 中国中部

中原标准时区 GMT+8 中国海岸

长白时区 GMT+8.5 中国东北

1949年之后,中国将“中原标准时间”改称“北京时间”,并在全国(大陆、港澳、台湾)统一使用改时间作为标准时间。

2.2 Date常用方法

2.2.1 获取相关

  • int getDate():获取一个月中的日期;
  • int getDay():获取一个星期中的星期几,取值范围:0-6;0代表星期天、1代表星期1、2代表星期2;
  • int getHours():获取小时;
  • int getMinutes():获取分钟;
  • int getMonth():获取0-11的月份,0代表1月,1代表2月以此类推;因此我们获取月份一般使用:getMonth()+1
  • int getSeconds():获取秒;
  • long getTime():返回自1970年1月1日以来到当前日期的毫秒数;
  • int getYear():获取1900年到当前年的年份,如121代表2021年,120代表2020年以此类推...

使用示例:

package com.dfbz.demo01;

import java.util.Date;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02_常用方法_获取 {
    public static void main(String[] args) {

        Date date = new Date();

        // 返回1900到现在的年份  如:2021 -> 121; 2020 -> 120
        System.out.println(date.getYear());

        // 返回月份  0-11  0代表1月,1代表2月...
        System.out.println(date.getMonth());

        // 返回当月的第几天
        System.out.println(date.getDate());

        // 小时
        System.out.println(date.getHours());

        // 分钟
        System.out.println(date.getMinutes());

        // 秒
        System.out.println(date.getSeconds());

        // 星期几
        System.out.println(date.getDay());
    }
}

2.2.2 设置相关

  • void setYear(int year)  设置年,从1900为0年,设置1为1901年,设置20为1920年,设置120为2020年...
  • void setMonth(int month):设置月,范围0-11,0为1月,1为2月,11为12月...
  • void setDate(int date):设置一个月的某天;
  • void setHours(int hours):设置小时
  • void setMinutes(int minutes):设置分钟;
  • void setSeconds(int seconds) :设置秒;
  • void setTime(long time):设置1970年1月1日到现在的毫秒值;

使用示例:

package com.dfbz.demo01;

import java.util.Date;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo03_常用方法_设置 {
    public static void main(String[] args) {

        Date date=new Date();
        date.setYear(120);          // 2020年
        date.setMonth(10);          // 11月
        date.setDate(8);           // 8号
        date.setHours(20);          // 20时
        date.setMinutes(50);        // 50分
        date.setSeconds(40);        // 40秒

        System.out.println(date);       // Sun Nov 08 20:50:40 CST 2020
    }
}

2.3 DateFormat类

java.text.DateFormat 是日期/时间格式化子类的抽象类,我们通过这个类可以帮我们完成日期和文本之间的转换,也就是可以在Date对象与String对象之间进行来回转换。

  • 格式化:按照指定的格式,从Date对象转换为String对象。
  • 解析:按照指定的格式,从String对象转换为Date对象。

2.3.1 构造方法

由于DateFormat为抽象类,不能直接使用,所以需要常用的子类java.text.SimpleDateFormat。这个类需要一个模式(格式)来指定格式化或解析的标准。构造方法为:

  • public SimpleDateFormat(String pattern):用给定的模式和默认语言环境的日期格式符号构造SimpleDateFormat。

参数pattern是一个字符串,代表日期时间的自定义格式。

2.3.2 格式规则

  • 常用的格式规则为:
标识字母(区分大小写) 含义
y
M
d
H
m
s

创建SimpleDateFormat对象的代码如:

package com.dfbz.demo01;

import java.text.DateFormat;
import java.text.SimpleDateFormat;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */

public class Demo01 {
    public static void main(String[] args) {
        // 对应的日期格式如:2021-06-02 20:04:25
        DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    }
}

2.3.3 常用方法

DateFormat类的常用方法有:

  • public String format(Date date):将Date对象格式化为字符串。
  • public Date parse(String source):将字符串解析为Date对象。

1) format方法

使用示例:

package com.dfbz.demo01;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo04_DateFormat_格式化日期 {
    public static void main(String[] args) {

        Date date = new Date();
        System.out.println(date);
        System.out.println("--------------");

        // 格式化器
        DateFormat format= new SimpleDateFormat("yyyy年MM月dd号 HH时mm分ss秒");

        String formatStr = format.format(date);			// 2023年11月15号 17时52分57秒
        System.out.println(formatStr);
    }
}

2) parse方法

使用示例:

package com.dfbz.demo01;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo05_DateFormat_解析日期 {
    public static void main(String[] args) throws ParseException {          // 可能会出现ParseException异常
        // 创建日期格式化对象
        DateFormat df = new SimpleDateFormat("yyyy年MM月dd日");

        // 准备一个日期字符串
        String str = "2020年12月11日";

        // 使用格式化对象将字符串变为日期对象
        Date date = df.parse(str);
        System.out.println(date);           // Fri Dec 11 00:00:00 CST 2020
    }
}

2.3.4 练习

请使用日期时间相关的API,计算出一个人已经出生了多少天。

思路:

1.获取当前时间对应的毫秒值

2.获取自己出生日期对应的毫秒值

3.两个时间相减(当前时间– 出生日期)

  • 代码实现:
package com.dfbz.demo01;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo06_小练习 {
    public static void main(String[] args) throws ParseException {
        System.out.println("请输入出生日期 格式 YYYY-MM-dd");

        // 获取出生日期,键盘输入
        String birthdayString = new Scanner(System.in).next();

        // 将字符串日期,转成Date对象
        // 创建SimpleDateFormat对象,写日期模式
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

        // 调用方法parse,字符串转成日期对象
        Date birthdayDate = sdf.parse(birthdayString);

        // 获取今天的日期对象
        Date todayDate = new Date();

        // 将两个日期转成毫秒值,Date类的方法getTime
        long birthdaySecond = birthdayDate.getTime();
        long todaySecond = todayDate.getTime();
        long liveSecond = todaySecond - birthdaySecond;

        if (liveSecond < 0) {
            System.out.println("还没出生呢");
        } else {
            System.out.println("您活了: " + (liveSecond / 1000 / 60 / 60 / 24) + "天");
        }
    }
}

运行结果:

三、Calendar类

3.1 Calendar类概述

java.util.Calendar是日历类,在Date后出现,替换掉了许多Date的方法。该类将所有可能用到的时间信息封装为静态成员变量,方便获取。日历类就是方便获取各个时间属性的。

3.2 获取方式

Calendar为抽象类,Calendar类在创建对象时并非直接创建,而是通过静态方法创建,返回子类对象,如下:

  • public static Calendar getInstance():使用默认时区和语言环境获得一个日历
package com.dfbz.demo01;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */

import java.util.Calendar;

public class Demo01 {
    public static void main(String[] args) {
        // 获取日历对象
        Calendar cal = Calendar.getInstance();
    }
}

3.3 常用方法

根据Calendar类的API文档,常用方法有:

  • public int get(int field):返回给定日历字段的值。
  • public void set(int field, int value):将给定的日历字段设置为给定值。
  • public abstract void add(int field, int amount):根据日历的规则,为给定的日历字段添加或减去指定的时间量。
  • public Date getTime():返回一个表示此Calendar时间值(从历元到现在的毫秒偏移量)的Date对象。
  • public long getTimeInMillis():获取1970年到当前时间的毫秒值;

Calendar类中提供很多成员常量,代表给定的日历字段:

字段值 含义
YEAR
MONTH 月(从0开始)
DATE、DAY_OF_MONTH 月中的天(几号)
HOUR 时(12小时制)
HOUR_OF_DAY 时(24小时制)
MINUTE
SECOND
DAY_OF_WEEK 周中的天(周几,0为星期6,1为星期天,2为星期1,3为星期2,这里和Date不一样)
WEEK_OF_MONTH 一个月的第几周

需要注意的是,Calendar类和Date关于星期几的描述是不一样的:

  • 1)Date类:取值范围:0~6,0代表星期天,1代表星期1,2代表星期2......,6代表星期六
  • 2)Calendar类:取值范围:0~6,0代表星期6,1代表星期天,2代表星期1....,6代表星期5

1)get方法

get方法主要是获取日期的信息:

package com.dfbz.demo01;

import java.util.Calendar;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_Calendar_获取 {
    public static void main(String[] args) {
        Calendar calendar = Calendar.getInstance();

        System.out.println("年: " + calendar.get(Calendar.YEAR));
        System.out.println("月: " + calendar.get(Calendar.MONTH));       // 0-11,4代表5月,5代表6月
        System.out.println("日: " + calendar.get(Calendar.DAY_OF_MONTH));  // 0-6,0代表星期六,1代表星期天,2代表星期一
        System.out.println("星期几: " + calendar.get(Calendar.DAY_OF_WEEK));
        System.out.println("一个月第几周: " + calendar.get(Calendar.WEEK_OF_MONTH));
        System.out.println("时: " + calendar.get(Calendar.HOUR));
        System.out.println("分: " + calendar.get(Calendar.MINUTE));
        System.out.println("秒: " + calendar.get(Calendar.SECOND));
    }
}

2)set方法

set方法主要是设置日期的值:

package com.dfbz.demo01;

import java.util.Calendar;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02_Calendar_设置 {
    public static void main(String[] args) {
        Calendar calendar = Calendar.getInstance();

        calendar.set(Calendar.YEAR, 2020);
        calendar.set(Calendar.MONTH, 10);            // 10代表11月
        calendar.set(Calendar.DAY_OF_MONTH, 8);        // 范围1-7,1代表星期天,2代表星期1,3代表星期2...
        calendar.set(Calendar.HOUR, 10);
        calendar.set(Calendar.MINUTE, 18);
        calendar.set(Calendar.SECOND, 50);

        System.out.println("年: " + calendar.get(Calendar.YEAR));
        System.out.println("月: " + calendar.get(Calendar.MONTH));       		// 0-11,10代表11月,11代表12月
        System.out.println("日: " + calendar.get(Calendar.DAY_OF_MONTH));
        System.out.println("星期几: " + calendar.get(Calendar.DAY_OF_WEEK));     	// 0-6,0代表星期天,1代表星期1,2代表星期2...
        System.out.println("时: " + calendar.get(Calendar.HOUR));
        System.out.println("分: " + calendar.get(Calendar.MINUTE));
        System.out.println("秒: " + calendar.get(Calendar.SECOND));
    }
}

3)add方法

add方法可以对指定日历字段的值进行加减操作,如果第二个参数为正数则加上偏移量,如果为负数则减去偏移量。代码如:

package com.dfbz.demo01;

import java.util.Calendar;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo03_Calendar_add方法 {
    public static void main(String[] args) {
        Calendar cal = Calendar.getInstance();

        System.out.println(cal.get(Calendar.YEAR) + "年" + (cal.get(Calendar.MONTH) + 1) + "月" + cal.get(Calendar.DAY_OF_MONTH) + "日");

        // 使用add方法
        cal.add(Calendar.DAY_OF_MONTH, 2); // 加2天

        cal.add(Calendar.YEAR, -3); // 减3年

        System.out.println(cal.get(Calendar.YEAR) + "年" + (cal.get(Calendar.MONTH) + 1) + "月" + cal.get(Calendar.DAY_OF_MONTH) + "日");
    }
}

4)getTime方法

Calendar中的getTime方法并不是获取毫秒时刻,而是拿到对应的Date对象。

package com.dfbz.demo01;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo04_getTime方法 {
    public static void main(String[] args) {

        // 创建一个日历
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.YEAR,1999);
        calendar.set(Calendar.MONTH,7);     // 8月

        // 将日历转换为Date对象
        Date date = calendar.getTime();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        String strDate = sdf.format(date);
        System.out.println(strDate);
    }
}

Tips:

  • 1)西方星期的开始为周日,中国为周一;
  • 2)在Calendar类中,月份的表示是以0-11代表1-12月;
  • 3)日期是有大小关系的,时间靠后,时间越大;

四、SpringBuilder类

4.1 StringBuilder简介

之前在学习String类的时候我们就说到过:String类是不可变类,即一个String对象被创建以后,包含在这个对象中的字符序列是不可改变的,直至这个对象被销毁。

回顾String类的字符串拼接:

String s1 = "1";
s1 = "2";
String s3 = s1 + "3";

java.lang.StringBuilder又称为可变字符序列,是个字符的缓冲区,即它是一个容器,容器中可以装很多字符串。并且能够对其中的字符串进行各种操作。它的内部拥有一个数组用来存放字符串内容,进行字符串拼接时,直接在数组中加入新内容。StringBuilder会自动维护数组的扩容。原理如下图所示:(默认16字符空间,超过自动扩充)

查看String拼接:

4.2 StringBuilder的使用

4.2.1 构造方法

根据StringBuilder的API文档,常用构造方法有2个:

  • public StringBuilder():构造一个空的StringBuilder容器。
  • public StringBuilder(String str):构造一个StringBuilder容器,并将字符串添加进去。

示例代码:

package com.dfbz.demo01;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_构造方法 {
    public static void main(String[] args) {
        StringBuilder sb1 = new StringBuilder();
        System.out.println(sb1); // (空白)

        // 使用带参构造
        StringBuilder sb2 = new StringBuilder("abc");
        System.out.println(sb2); // abc
    }
}

4.2.3 常用方法

StringBuilder常用的方法有2个:

  • public StringBuilder append(...):添加任意类型数据的字符串形式,并返回当前对象自身。
  • public StringBuilder reverse():将当前对象的字符串反转,并返回当前对象自身。
  • public String toString():将当前StringBuilder对象转换为String对象。

1)append方法:

package com.dfbz.demo01;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02_append方法 {
    public static void main(String[] args) {
        //创建对象
        StringBuilder builder = new StringBuilder();

        //public StringBuilder append(任意类型)
        StringBuilder builder2 = builder.append("hello");

        //对比一下
        System.out.println("builder:" + builder);
        System.out.println("builder2:" + builder2);

        System.out.println(builder == builder2); //true

        // 可以添加 任何类型
        builder.append("hello");
        builder.append("world");
        builder.append(true);
        builder.append(100);

        // 在我们开发中,会遇到调用一个方法后,返回一个对象的情况。然后使用返回的对象继续调用方法。
        // 这种时候,我们就可以把代码现在一起,如append方法一样,代码如下
        // 链式编程
        builder.append("hello").append("world").append(true).append(100);
        System.out.println("builder:" + builder);
    }
    
}

2)reverse方法:

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo03_reverse方法 {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder("abc");

        // 将字符串反转,然后返回对象本身
        StringBuilder sb2 = sb.reverse();

        System.out.println(sb);         // cba
        System.out.println(sb == sb2);  // true
    }
}

3)toString方法:

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo04_toString方法 {
    public static void main(String[] args) {
        // 链式创建
        StringBuilder sb = new StringBuilder("Hello").append("World");
        // 调用方法
        String str = sb.toString();
        System.out.println(str); // HelloWorld
    }
}

4.2.3 性能测试

在使用String对象字符串拼接无论从时间上还是空间上都要大幅度弱于StringBuilder,我们在进行大量字符串拼接应该避免采用String拼接,尽量采用SpringBuilder来进行字符串拼接。

  • 示例代码:
package com.dfbz.demo01;

import java.util.Date;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo05_字符串拼接性能对比 {
    public static void main(String[] args) {
        long startTime = new Date().getTime();

        String str = "";
        for (int i = 0; i < 100000; i++) {
            str += i;
        }

        long endTime = new Date().getTime();

        System.out.println((endTime - startTime));          // 29245
    }


    public static void test(String[] args) {
        long startTime = new Date().getTime();
        StringBuilder sb = new StringBuilder();

        for (int i = 0; i < 1000000; i++) {
            sb.append(i);
        }

        long endTime = new Date().getTime();
        System.out.println((endTime - startTime));          // 41
    }
}

五、System类

5.1 常用方法

java.lang.System是Java中的系统类,主要用于获取系统的属性数据,没有构造方法。类中提供了大量的静态方法,可以获取与系统相关的信息或系统级操作,在System类的API文档中,常用的方法有:

  • public static long currentTimeMillis():返回以毫秒为单位的当前时间。
  • public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length):将数组中指定的数据拷贝到另一个数组中。
  • public static exit(int status):该方法用于退出jvm,如果参数是0表示正常退出jvm,非0表示异常退出jvm。
  • public static getProperties():该方法用于获取系统的所有属性。属性分为键和值两部分,它的返回值是Properties。
  • public static gc():该方法用来建议jvm赶快启动垃圾回收器回收垃圾。只是建议启动,但是Jvm是否启动又是另外一回事。

5.2 System类的使用

5.2.1 currentTimeMillis

通过System的currentTimeMillis()方法我们可以获取当前时间的毫秒值,有了毫秒值我们可以将其转换为Date对象、Calendar对象等进行日期的运算;

package com.dfbz.demo01;

import java.util.Calendar;
import java.util.Date;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_获取毫秒值 {
    public static void main(String[] args) {

        //  获取当前时间毫秒值
        long currTime = System.currentTimeMillis();

        // 有了毫秒值之后可以将其转换为Date
        Date date = new Date(currTime);

        // 获取日历类
        Calendar calendar = Calendar.getInstance();

        // 设置毫秒值
        calendar.setTimeInMillis(currTime);

        System.out.println(date);
        System.out.println(calendar.get(Calendar.YEAR));
        System.out.println(calendar.get(Calendar.MONTH) + 1);
        System.out.println(calendar.get(Calendar.DAY_OF_MONTH));
    }
}

5.2.2 arraycopy

  • public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length):将数组中指定的数据拷贝到另一个数组中。

数组的拷贝动作是系统级的,性能很高。System.arraycopy方法具有5个参数,含义分别为:

参数序号 参数名称 参数类型 参数含义
1 src Object 源数组
2 srcPos int 源数组索引起始位置
3 dest Object 目标数组
4 destPos int 目标数组索引起始位置
5 length int 复制元素个数
  • 使用示例:

将src数组中前3个元素,复制到dest数组的前3个位置上复制元素前:src数组元素[1,2,3,4,5],dest数组元素[11,22,33,44,55]复制元素后:src数组元素[1,2,3,4,5],dest数组元素[1,2,3,44,55];

package com.dfbz.demo01;

import java.util.Arrays;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02_arrayCopy方法 {

    public static void main(String[] args) {

        int[] src = new int[]{1, 2, 3, 4, 5};
        int[] dest = new int[]{11, 22, 33, 44, 55};

        System.arraycopy(src, 0, dest, 0, 3);

        /*
            代码运行后:两个数组中的元素发生了变化
                src数组元素[1,2,3,4,5]
                dest数组元素[1,2,3,44,55]
        */

        // 使用字符串形式打印数组
        System.out.println(Arrays.toString(src));           // [1, 2, 3, 4, 5]
        System.out.println(Arrays.toString(dest));          // [1, 2, 3, 44, 55]

    }
}

图解:

5.2.3 exit

exit用于退出Jvm,需要注意的是:0或者非0的数据都可以退出Jvm,对于用户而言没有任何区别,对于windows是有作用的,因为如果传非0对于windows而言是异常终止的,如果是正版的操作系统,对于异常退出的软件,需要把这些异常退出的软件信息做成报告发送给微软,微软就可以针对这些问题对系统做出一些修改。

package com.dfbz.demo01;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo03_exit方法 {

    public static void main(String[] args) {

        for (int i = 0; i < 10; i++) {
            System.out.println(i);
            if (i == 5) {
                System.exit(0);         // 程序退出
            }
        }

        System.out.println("我这句代码是不会执行的..");
    }
}

5.2.4 gc

与Runtime对象的gc()方法一致,System类也可以调用gc()告诉Jvm垃圾回收器,叫他来清除一下垃圾;

package com.dfbz.demo01;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo04_gc {
    public static void main(String[] args) {


        for (int i = 0; i < 10; i++) {
            // 使用匿名对象(没有对象保存返回值)
            new Student("小灰" + i);
        }
        // 触发垃圾回收器
        System.gc();

        // 等价于runtime调用gc()
//        Runtime runtime = Runtime.getRuntime();
//        runtime.gc();
    }
}

class Student {

    private String name;

    public Student(String name) {
        this.name = name;
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("【" + name + "】被回收啦!");
    }
}

运行效果:

六、包装类

6.1 包装类概述

Java提供了两个类型系统,基本类型引用类型,使用基本类型在于效率,然而很多情况,会创建对象使用,因为对象可以做更多的功能,如果想要我们的基本类型像对象一样操作,就可以使用基本类型对应的包装类,如下:

基本类型 对应的包装类(位于java.lang包中)
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

6.2 装箱与拆箱

6.2.1 手动拆箱与装箱

基本类型与对应的包装类对象之间,来回转换的过程称为”装箱“与”拆箱“:

  • 装箱:从基本类型转换为对应的包装类对象。
  • 拆箱:从包装类对象转换为对应的基本类型。

【装箱的示例代码】

package com.dfbz.demo01;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_手动装箱 {
    public static void main(String[] args) {

        // 手动装箱: 将基本数据类型转换为对应的引用数据类型

        Integer aaa = Integer.valueOf(1);       // 使用静态方法来装箱
//        Integer aaa = new Integer(1);           // 使用构造方法来装箱

        Long bbb = Long.valueOf(20L);
//        Long bbb = new Long(20L);

        Double ccc = Double.valueOf(20.8D);
//        Double ccc =new Double(20.8D);

        Float ddd = Float.valueOf(20.9F);
//        Float ddd = new Float(20.9F);

        // 这些包装类都重写了toString方法,在输出对象的时候输出的不是内存地址值,而是具体的数值
        System.out.println(aaa);            // 1
        System.out.println(bbb);            // 20
        System.out.println(ccc);            // 20.8
        System.out.println(ddd);            // 20.9
    }
}

【拆箱的示例代码】

package com.dfbz.demo01;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02_手动拆箱 {
    public static void main(String[] args) {

        // 装箱: 将基本数据类型转换为引用数据类型
        Integer aaa = new Integer(1);
        Long bbb = new Long(20L);
        Double ccc =new Double(20.8D);
        Float ddd = new Float(20.9F);

        // 拆箱: 将引用数据类型转换为基本数据类型
        int aaa_I = aaa.intValue();
        long bbb_L = bbb.longValue();
        double ccc_D = ccc.doubleValue();
        float ddd_F = ddd.floatValue();

        System.out.println(aaa_I);      // 1
        System.out.println(bbb_L);      // 20
        System.out.println(ccc_D);      // 20.8
        System.out.println(ddd_F);      // 20.9
    }
}

6.2.2 自动拆箱与装箱

由于我们经常要做基本类型与包装类之间的转换,从Java 5(JDK 1.5)开始,基本类型与包装类的装箱、拆箱动作可以自动完成。

【自动装箱】

package com.dfbz.demo01;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo03_自动装箱 {
    public static void main(String[] args) {
        // 自动装箱: 自动的将基本数据类型包装为引用数据类型
        Integer aaa = 1;
        Long bbb = 20L;
        Double ccc= 20.8D;
        Float ddd = 20.9F;

        System.out.println(aaa);            // 1
        System.out.println(bbb);            // 20
        System.out.println(ccc);            // 20.8
        System.out.println(ddd);            // 20.9
    }
}

【自动拆箱】

package com.dfbz.demo01;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo04_自动拆箱 {
    public static void main(String[] args) {
        // 自动装箱: 自动的将基本数据类型包装为引用数据类型
        Integer aaa = 1;
        Long bbb = 20L;
        Double ccc = 20.8D;
        Float ddd = 20.9F;

        // 自动拆箱: 自动的将引用数据类型转换为基本数据类型
        int aaa_I = aaa;
        long bbb_L = bbb;
        double ccc_D = ccc;
        float ddd_F = ddd;

        System.out.println(aaa);            // 1
        System.out.println(bbb);            // 20
        System.out.println(ccc);            // 20.8
        System.out.println(ddd);            // 20.9
    }
}

6.3 基本类型与String的转换

6.3.1 基本类型转String

【基本类型转成String类型】

package com.dfbz.demo01;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo05_基本类型转成String类型 {
    public static void main(String[] args) {
        int aaa = 10;

        // 直接拼接字符串即可(任何的数据类型与String拼接都会产生一个新的字符串)
        String str = aaa + "";

        System.out.println(str);            // 10
    }
}

6.3.2 String转基本类型

除了Character类之外,其他所有包装类都具有parseXxx静态方法可以将字符串参数转换为对应的基本类型:

  • public static byte parseByte(String s):将字符串参数转换为对应的byte基本类型。
  • public static short parseShort(String s):将字符串参数转换为对应的short基本类型。
  • public static int parseInt(String s):将字符串参数转换为对应的int基本类型。
  • public static long parseLong(String s):将字符串参数转换为对应的long基本类型。
  • public static float parseFloat(String s):将字符串参数转换为对应的float基本类型。
  • public static double parseDouble(String s):将字符串参数转换为对应的double基本类型。
  • public static boolean parseBoolean(String s):将字符串参数转换为对应的boolean基本类型。

代码使用(仅以Integer类的静态方法parseXxx为例)如:

package com.dfbz.demo01;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo06_String转成基本类型 {
    public static void main(String[] args) {
        // 字符串转byte
        byte b = Byte.parseByte("100");

        // 字符串转short
        short s = Short.parseShort("100");

        // 字符串转num
        int num = Integer.parseInt("100");

        // 字符串转long
        long l = Long.parseLong("100");

        // 字符串转float
        float f = Float.parseFloat("100.8");

        // 字符串转double
        double d = Double.parseDouble("100.60");

        // 字符串转boolean
        boolean boo = Boolean.parseBoolean("true");

        // 字符串转char
        char c = "a".charAt(0);
    }
}

Tips:如果字符串参数的内容无法正确转换为对应的基本类型,则会抛出java.lang.NumberFormatException异常。

七、数值类

Java中的八大基本数据类型如下:

数据类型 关键字 内存占用 取值范围
字节型 byte 1个字节 -128~127
短整型 short 2个字节 -32768~32767
整型 int(默认) 4个字节 -2147483648~2147483647
长整型 long 8个字节 -2的63次方~2的63次方-1
单精度浮点数 float 4个字节 1.4013E-45~3.4028E+38
双精度浮点数 double(默认) 8个字节 4.9E-324~1.7977E+308
字符型 char 2个字节 0-65535
布尔类型 boolean 1个字节 true,false

8个基本数据类型都是有大小的,例如一个byte类型变量只能存储-128~127之间的数字,多了就存不下,这样在面对大数据量的计算时(如科学计算)基本数据类型会出现很大的问题(存不下);

如果基本的整数和浮点数精度不能够满足需求,那么可以使用java.math包中两个很有用的类:BigIntegerBigDecimal。这两个类可以处理包含任意长度数字序列的数值;BigInteger类实现任意精度的整数运算,BigDecimal实现任意精度的浮点数运算;

7.1 BigInteger

可以通过一个字符串来构建一个BigInteger类,BigInteger类封装了大量对数字的加减乘除等操作的方法,而字符串可以任意长度,也就是说BigInteger支持任意长度的数字进行加减乘除等运算;

7.1.1 获取BigInteger对象

  • 构造方法:
    • public BigInteger(String val):通过一个字符串来构建BigInteger对象;
  • 普通方法:
    • public static BigInteger valueOf(long val):通过一个Long数值来构建一个BigInteger对象,由于BigInteger对象都是针对于非常庞大的数据(远远超过了Long的取值范围)进行运算,因此很少使用这个方法来构建BigInteger对象;

示例代码:

package com.dfbz.demo01_BigInterger;

import java.math.BigInteger;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_BigInteger的获取 {
    public static void main(String[] args) {
        // 基于一个Long数值来创建一个BigInteger对象
        BigInteger big1 = BigInteger.valueOf(1230020200L);

        // 基于一个字符串来创建一个BigInteger对象
        BigInteger big2 = new BigInteger("234234234231213123123123123213");
    }
}

7.1.2 BigInteger的常用方法

  • public BigInteger add(BigInteger val):返回值为 (this + val)
  • public BigInteger subtract(BigInteger val):返回值为 (this - val)
  • public BigInteger multiply(BigInteger val):返回值为 (this * val)
  • public BigInteger divide(BigInteger val):返回值为 (this / val)
  • public BigInteger mod(BigInteger m):返回值为(this % m)
  • public int compareTo(BigInteger val):如果this的数值比val的大,返回1,否则返回-1,如果相等则返回0

示例代码:

package com.dfbz.demo01_BigInterger;

import java.math.BigInteger;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02_BigInteger的常用方法 {
    public static void main(String[] args) {
        BigInteger num1 = new BigInteger("100");
        BigInteger num2 = new BigInteger("10");

        System.out.println(num1.add(num2));                 // 110
        System.out.println(num1.subtract(num2));            // 90
        System.out.println(num1.multiply(num2));            // 1000
        System.out.println(num1.divide(num2));              // 10
        System.out.println(num1.mod(num2));                 // 0
        System.out.println(num1.compareTo(num2));           // 1
    }
}

7.1.3 BigInteger转换为基本数据类型

  • 示例代码:
package com.dfbz.demo01_BigInterger;

import java.math.BigInteger;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo03_转换为基本数据类型 {
    public static void main(String[] args) {
        BigInteger num = new BigInteger("10");

        // BigInteger转换为对应的基本数据类型
        byte b = num.byteValue();
        short s = num.shortValue();
        int i = num.intValue();
        long l = num.longValue();
        float f = num.floatValue();
        double d = num.doubleValue();

        // BigInteger转换为字符串
        String str = num.toString();
    }
}

7.1.4 BigInteger注意事项

BigInteger只能构建整数,不能构建小数,否则会出现程序异常现象;另外,如果两个BigInteger计算的值出现小数时则会出现丢失精度现象;

  • 示例代码:
package com.dfbz.demo01_BigInterger;

import java.math.BigInteger;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo04_浮点数运算 {
    public static void main(String[] args) {
        BigInteger num1 = new BigInteger("10");
        BigInteger num2 = new BigInteger("3");

        System.out.println(num1.divide(num2));          // 结果为: 3; 出现丢失精度现象
    }

    public static void test(){
        // Exception in thread "main" java.lang.NumberFormatException: For input string: "9.3"
        BigInteger num1 = new BigInteger("9.3");
    }
}

7.2 BigDecimal

BigInteger是针对大整数的运算,BigDecimal则是针对大型浮点数的运算。值得注意的是,BigDecimal也可以针对大型整数进行运算,也就是包含了BigInteger的绝大部分功能,在大部分场景下我们使用BigDecimal会比较多,他与BigInteger其他特性几乎一样;

7.2.1 获取BigDecimal对象

  • 静态方法:
    • public static BigDecimal valueOf(long val):基于一个Long类型的数值来构建BigDecimal对象
    • public static BigDecimal valueOf(double val):基于一个Double类型的数值来构建BigDecimal对象
  • 构造方法:
    • public BigDecimal(String val):基于一个字符串来构建BigDecimal对象

示例代码:

package com.dfbz.demo02_BigDecimal;

import java.math.BigDecimal;
import java.math.BigInteger;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_BigDecimal的获取 {
    public static void main(String[] args) {

        // 基于一个Long类型的数值来构建BigDecimal对象
        BigDecimal big1 = BigDecimal.valueOf(1000);

        // 基于一个Double类型的数值来构建BigDecimal对象
        BigDecimal big2 = BigDecimal.valueOf(399.38);

        // 基于一个字符串来构建BigDecimal对象
        BigDecimal big3 = new BigDecimal("99999");

        // 基于一个字符串来构建BigDecimal对象
        BigDecimal big4 = new BigDecimal("99999.99");
    }
}

7.2.2 BigDecimal的常用方法

BigDecimal的常用方法和BigInteger基本一致;不过BigDecimal对除法(divide)进行了优化;

  • public BigDecimal divide(BigDecimal divisor, int roundingMode):除法运算,默认情况下不允许出现无限循环小数,但可以通过以下的一些参数来调整:
    • BigDecimal.ROUND_UP:最后一位如果大于0,则向前进一位,如果是正数则向上取整,如果是负数则向下取整。
      • 例如:3.3->4;-3.3->-4
    • BigDecimal.ROUND_DOWN:最后一位不管是什么都会被舍弃。
      • 例如:3.3->3;-3.3->-3
    • BigDecimal.ROUND_CEILING:向上取整。
      • 例如:3.3->4;-3.3->-3。
    • BigDecimal.ROUND_FLOOR:向下取整。
      • 例如:3.3->3;-3.3->-4。
  • 示例代码-1(整数运算):
package com.dfbz.demo02_BigDecimal;

import java.math.BigDecimal;
import java.math.BigInteger;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */

public class Demo02_BigDecimal的常用方法 {
    public static void main(String[] args) {

        BigDecimal num1 = BigDecimal.valueOf(100);
        BigDecimal num2 = BigDecimal.valueOf(10);

        System.out.println(num1.add(num2));                 // 110
        System.out.println(num1.subtract(num2));            // 90
        System.out.println(num1.multiply(num2));            // 1000
        System.out.println(num1.divide(num2));              // 10
    }
}
  • 示例代码-2(结果为小数):
package com.dfbz.demo02_BigDecimal;


import java.math.BigDecimal;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo03_BigDecimal的小数运算 {
    public static void main(String[] args) {
        BigDecimal num1 = BigDecimal.valueOf(10);
        BigDecimal num2 = BigDecimal.valueOf(3);

        // 结果有循环小数的,需要指定规则来调整
        System.out.println(num1.divide(num2, BigDecimal.ROUND_UP));                      // 4
        System.out.println(num1.divide(num2, BigDecimal.ROUND_DOWN));                    // 3
        System.out.println(num1.divide(num2, BigDecimal.ROUND_CEILING));                 // 4
        System.out.println(num1.divide(num2, BigDecimal.ROUND_FLOOR));                   // 3
    }

    public static void test4() {
        BigDecimal num1 = BigDecimal.valueOf(10);
        BigDecimal num2 = BigDecimal.valueOf(3);

        /*
         出现异常: Exception in thread "main" java.lang.ArithmeticException:
         Non-terminating decimal expansion; no exact representable decimal result.
         结果不能出现无限循环小数
         */
        System.out.println(num1.divide(num2));
    }

    public static void test3() {

        // 小数参与运算也没有问题
        BigDecimal num1 = BigDecimal.valueOf(2.53);
        BigDecimal num2 = BigDecimal.valueOf(3.69);

        System.out.println(num1.add(num2));                 // 6.22
        System.out.println(num1.subtract(num2));            // -1.16
        System.out.println(num1.multiply(num2));            // 9.3357
    }

    public static void test2() {

        // 结果为小数,也没有什么问题
        BigDecimal num1 = BigDecimal.valueOf(5);
        BigDecimal num2 = BigDecimal.valueOf(2);

        System.out.println(num1.divide(num2));              // 2.5
    }

    public static void test() {

        // 普通运算和BigInteger基本类似
        BigDecimal num1 = BigDecimal.valueOf(100);
        BigDecimal num2 = BigDecimal.valueOf(10);

        System.out.println(num1.add(num2));                 // 110
        System.out.println(num1.subtract(num2));            // 90
        System.out.println(num1.multiply(num2));            // 1000
        System.out.println(num1.divide(num2));              // 10
    }
}
posted @ 2023-02-09 13:43  绿水长流*z  阅读(111)  评论(0)    收藏  举报