JavaSE 学习笔记02丨对象与类

Chapter 4. 对象与类

4.1 面向对象程序设计概述

面向对象程序设计(简称OOP),是当今主流程序设计范型。面向对象的程序是由对象组成的,每个对象(来自于标准库或自定义的)包含对用户公开的特定功能和隐藏的实现部分。在OOP中,不必关心对象的具体实现,只要能够满足用户的需求即可。面向对象的语言中,包含了三大基本特征:封装、继承、多态。

4.1.1 类

类(class),是构造对象的模板或蓝图。由类构造(construct)对象的过程,成为类的实例(instance)。

封装(encapsulation),给予对象“黑盒”特征,将数据与行为组合在一个包中,并对对象的使用者隐藏了数据的实现方式。对象中的数据成为实例域(instance fields),操纵数据的过程成为方法(method)。对于每个特定的类实例(对象)都有一组特定的实例域值,这些值的集合就是这个对象的当前状态。

类之间,最常用的关系:

  • 依赖("uses-a")——一个类的方法操纵另一个类的对象

    应尽可能将相互依赖的类减至最小。若类A不知道B的存在,它就不关心B的任何改变(即不会导致A产生BUG)。用软件工程的术语来说,就是让类之间的耦合度最小

  • 聚合("has-a")——意味着类A的对象包含类B的对象

  • 继承("is-a")——若类A拓展类B,类A不但包含从类B继承的方法,还会拥有一些额外的功能。

4.1.2 对象

对象,是类的实体。它是一类事物的具体体现,它必然具备某类事物的属性和行为。

要想使用OOP,需清楚对象的三个主要特性

  • 对象的行为——可对对象施加哪些操作或哪些方法?
  • 对象的状态——当施加那些方法时,对象如何响应?
  • 对象标识——如何辨识具有相同行为与状态的不同对象?

同一个类的所有对象实例,由于支持相同行为而具有家族相似性。对象的行为是用可调用的方法定义的。

此外,每个对象都保存着描述当前特征的信息,即对象的状态。对象状态的改变必须通过调用方法实现。(若不经方法调用就改变对象状态,说明封装性遭破坏)

但是,对象的状态并不能完全描述一个对象,每个对象均有一个唯一的身份。需注意,作为一个类的实例,每个对象的标识永远不同,状态常常存在差异

4.2 对象与类的相关操作

我们接下来编写的主力类(workhorse class),没有main方法而却有自定义的实例域和实例方法。若要创建一个完整的程序,应将若干类组合在一起,其中只有一个类有main方法。

Java中,一个源文件只能有一个公有类。但可以有任意个非公有类。许多程序员习惯将每一个类存在一个单独的源文件中。

4.2.1 最简单的类的定义

//在Phone.java文件中
public class Phone{
    //成员变量
    String brand, color;
    double price;
    //成员方法
    public void call(String sb){
        System.out.println("给" + sb + "打电话");
    }
    public void sendMessage(){
        System.out.println("群发短信");
    }
}

注意事项:

  1. 成员变量是直接定义在类内部,在方法外部。若成员变量没有进行复制,那么会有默认值(规则与数组相同)
  2. 成员方法不用写static关键字。
  3. 在Java中,所有方法都必须在类的内部定义,但并不表示它们是内联方法(这交给Java虚拟机去解决)。

局部变量与成员变量的区别:

局部变量 成员变量
定义位置 方法内部 方法外部、写于类内部
内存位置 栈内存 堆内存
作用域 只有在方法当中可以使用 在整个类均可使用
生命周期 随方法进栈而诞生(随方法出栈而消失) 随对象创建而诞生(随对象被垃圾回收而消失)
默认值 无默认值,若要使用则必须赋值。 若没有赋值,则会自带默认值

4.2.2 标准类的定义

标准的类,也称Java Bean。它需满足:

  • 都要使用private关键字修饰,相应地,需为每一成员变量编写一对Getter/Setter方法。(详见下方的4.3)

  • 类需要编写一个无参数的构造方法与一个全参数的构造方法。

public class Student{
    //成员方法
    //1. 构造方法
    public Student() { //无参构造方法
        this.name = "张三";
        this.age = 19;
    }
    public Student(String name, int age) { //全参构造方法
        this.name = name;
        this.age = age;
    }
    //2. Getter和Setter
    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;
    }
    //2. 成员变量
    private String name;
    private int age;
}

利用IDEA来生成相应方法:(以Getter/Setter为例,若要生成构造方法同理)

选择你需要生成的方法类型,此处选中Getter和Setter,得到下面窗口。

下图即为生成成功。

TIPS:有些程序员会为每个参数前面加上一个前缀"a"。如:public Employee(String aName, double aSalary),十分清晰。

C++中一般用下划线作为实例域的前缀(确实),如salary域会被命名为_salary。但Java程序员通常不这样做。

4.2.3 对象的构造方法

构造方法(构造器)就是专门用于创建对象的方法,当我们通过关键字new来创建对象时,实际上就是在调用构造方法。其格式为:

public 类名称(参数类型 参数名称, ... ){
    方法体
}

注意事项:

  1. 仅当类没有提供任何构造方法的时候,系统才会提供一个默认的构造方法。(一旦编写了至少一个构造方法,系统则不再提供)
  2. 构造方法的名称必须与所在的类名称完全一样(包括大小写)。
  3. 构造方法不要写返回值类型(连void都不写),当然也不能return一个具体的返回值。
  4. 构造方法总是伴随new操作一起调用。

4.2.4 对象的创建与使用

我们知道,一个类并不能直接使用,需要根据类创建一个对象,方能使用。

  1. 导包:指出需要使用的类在哪里

    格式:import 包名称.类名称;

    import day03.demo1.Student; //别漏分号
    

    注:对于和当前类属于同一个包的情况,可省略不写导包语句

    在Java中,packageimport语句,类似于C++中namespaceusing 指令

  2. 创建

    格式:类名称 对象名 = new 类名称() 或者 类名称 对象名 = new 类名称(参数1, 参数2, ...)

    Student stu = new Student();
    
  3. 使用

    (在没有Setter&Getter的情况下)使用成员变量:对象名.成员变量名

    使用成员方法:对象名.成员方法名(参数1, 参数2, ...)

//在Demo01PhoneOne.java文件中
public class Demo01PhoneOne{
    public static void main(String[] args){
        Phone one = new Phone();
        System.out.println(one.brand);
        one.brand = "苹果";
        one.call("Cook");
        one.sendMessage();
    }
}

[附]几个对象的内存图

在Java中,任何对象变量的值都是对存储在另外一个地方的一个对象的引用new操作符的返回值也是一个引用。

可以将Java对象变量看作C++的对象指针。

Date birthday; //Java;
等同于
Date* birthday; //C++

4.2.5 匿名对象

格式:new 类名称();

注意事项:匿名对象只能使用唯一一次,下次再使用时必须创建一个新对象。

使用建议:若确定有一个对象只需要使用唯一的一次,便可用匿名对象。

int num = new Scanner(System.in).nextInt();
methodParam(new Scanner(System.in)); //使用匿名对象来进行传参

4.2.6 静态static关键字

若类中的某个成员变量或方法用static关键字修饰,则以后创建的对象中的static修饰的变量或方法不再属于对象自己,而是属于类,也就说凡是本类的对象,都共享同一份。

无论是成员变量,还是成员方法。若有了static修饰,都推荐使用类名称进行调用。

  • static关键字修饰成员变量

    /* Student.java文件中 */
    public class Student {
        private int id;
        private String name;
        static String room;
        private static int idCounter = 0;//学号计数器,每new一个新对象便自增
        public Student(){
            this.id = ++idCounter;
        }
        public Student(String name){
            this.name = name;
            this.id = ++idCounter;
        }
        public int getId() {
            return id;
        }
        public String getName() {
            return name;
        }
    }
    
    /* JustTest.java文件中 */
    public class JustTest {
        public static void main(String[] args) {
            Student one = new Student("Luffy");
            one.room = "101教室";
    
            System.out.printf("%s的学号为:%d,教室为:%s\n", one.getName(), one.getId(), one.getRoom());
            Student two = new Student("Nami");
            System.out.printf("%s的学号为:%d,教室为:%s\n", two.getName(), two.getId(), two.getRoom());
        }
    }
    
  • static关键字修饰成员方法

    /* MyClass.java文件中 */
    public class MyClass {
        int num; //成员变量
        static int numStatic; //静态变量
        public void method(){ //成员方法
            System.out.println("我是成员方法!");
            System.out.println(num); //成员方法可访问成员变量
            System.out.println(numStatic); //成员方法可访问静态变量
        }
        public static void methodStatic(){ //静态方法
            System.out.println("我是静态方法!");
            //System.out.println(num); 错误写法!静态方法不能访问非静态变量
            System.out.println(numStatic); //静态方法可访问静态变量
            //System.out.println(this); 错误写法!静态方法不能使用this关键字
        }
    }
    
    /* JustTest.java文件中 */
    public class JustTest {
        public static void main(String[] args) {
            MyClass obj = new MyClass(); //创建对象方能使用非静态的成员方法
            obj.method();
            obj.methodStatic(); //正确,但不推荐
            MyClass.methodStatic(); //对于静态方法,可直接通过类名称来调用
    
            myMethod(); //本类当中的静态方法,可直接省略类名称
            JustTest.myMethod(); //完全等效
        }
        public static void myMethod(){
            System.out.println("这是我的方法!");
        }
    }
    
    
  • 静态代码块

    格式为:

    public class 类名称{
        static {
            //静态代码块中的内容
        }
    }
    

    特点:

    1. 一旦第一次使用了该类的静态代码块,该静态代码块以后都不再执行

      静态代码块的典型用途:用于一次性地对静态成员变量进行赋值

    2. 静态内容总是优先于非静态,故静态代码块比构造方法更早执行。

4.3 封装

封装性在Java中的体现:

  • 方法是一种封装;
  • 关键字private也是一种封装。

一旦使用private进行修饰,那么本类当中仍然可以随意访问。但是,超出本类范围之外,不能再直接访问了。要间接访问private成员变量,就是定义一对Getter/Setter方法。

  • 仅访问实例域而不进行修改的方法,称之为访问器方法accessor method)。在Java中,对应为Getter,不能有参数,返回值类型和成员变量相对应。命令规则一般为getXXX(若为boolean类型,则应命名为isXXX)。eg: getTime

  • 对实例域做出修改的方法,称之为更改器方法mutator method)。在Java中,对应为Setter,不能有返回值,参数类型与成员变量相对应。命令规则一般为setXXX。eg:setTime

如此封装的好处:

  1. 可改变内部实现,除了该类的方法之外,不会影响其他代码。
  2. 更改器方法可以执行错误检查,然而直接对域进行赋值将不会进行这些处理。

4.3.1 this

this关键字可以用来访问本类的内容。

一、在本类的成员方法中,访问本类的成员变量

当方法的局部变量和类的成员变量重名的时候,根据“就近原则”,优先使用局部变量。

public class Person{
    String name;
    public void sayHello(String name){
        System.out.println(name + ", 你好。我是" + name);
    }
}

为了解决上方产生的“二义性”,希望在本类的某个方法中访问本类当中的成员变量,需使用this.成员变量名。在每一个方法中,关键字this表示隐式参数。

即将上面代码块的第四行改为:System.out.println(name + ",你好。我是" + this.name);

二、在本类的成员方法中,访问本类的另一个成员方法

public class Person{
    String name;
    public void methodA(){
        System.out.println("A!");
    }
    public void methodB(){
        this.methodA();
        System.out.println("B!");
    }
}

三、在本类的构造方法中,访问本类的另一个构造方法。

注意:this()调用必须是构造方法的第一个语句,且是唯一一个。

public class Person{
    String name;
    public Person(){
        this("张三"); //本类的无参构造,调用本类的有参构造方法。
    }
    public Person(String name){
        System.out.println(name);
    }
}

4.4 Scanner类

通过Scanner类的学习,我们能够熟悉如何查询API,如何使用现有的类。

4.4.1 引用类型的使用步骤

  1. 导包

    格式:import 包路径.类名称;

    只有java.lang包下的内容不需要导包,其他的包均需要import语句。

  2. 创建

    格式:对象名 = new 类名称(参数1,...);

  3. 使用

    格式:对象名.成员方法名()

4.4.2 概述

Scanner类,是一个可以解析基本类型和字符串的简单文本扫描器。它可以实现键盘输入数据到程序当中。

关于Scanner类的导包语句为:

import java.util.Scanner;

使用实例:

package MyPackages;
import java.util.Scanner; //1. 导包

public class Demo01Scanner {
    public static void main(String[] args) {
        //2. 创建对象。Note: System.in代表从键盘进行输入
        Scanner sc = new Scanner(System.in); 
        //3. 获取键盘输入的字符串
        String str = sc.next();
        //4. 获取键盘输入的任何字符串,同时转换为int类型
        int a = sc.nextInt();
        
        int num = new Scanner(System.in).nextInt(); //简写 
    }
}

4.5 Random类

Random类用于生成随机数字。

Random导包语句为:

import java.util.Random;

创建:

Random r = new Random(); 

使用:

  1. 获取一个随机int型数字,其范围为int所有范围,含正负

    int num = r.nextInt();
    
  2. 获取一个随机int型数字,传入参数代表右区间界线。范围为左闭右开区间。

    int num = r.nextInt(3); //实际上范围为0,1,2
    

    由于是左闭右开区间,如果我们想要得到\([a,b]\)的随机数,可以这样写:

    r.nextInt(b + 1);
    r.nextInt(b + 1) + a;
    //本来范围为[0, b],现在整体加a,范围变为[a, b + a];
    //为了得到[a,b],则对传入参数-a即可。
    r.nextInt(b - a + 1) + a;
    

4.6 泛型数组列表:ArrayList集合

4.6.1 引入——对象数组

//Person类的代码就不放了,现在InstanceOfArray.java文件中,
public class InstanceOfArray {
    public static void main(String[] args) {
        Person[] a = new Person[3];
        System.out.println(a);
        System.out.println(a[0]); //输出为null
        //由于a[0],...未分配相应的空间,默认值为null。不能直接对a[0]的成员进行修改,故11行与12行的代码会发生报错!
        //a[0].name = "张三";
        //a[0].age = 18;
        //正确应有:
        a[0] = new Person("Luffy", 19);
        System.out.println(a[0].getAge());
        System.out.println(a[0]);
    }
}

由于数组无法在运行过程中改变其本身的规模,此处即引入下一节要介绍的ArrayList类。

4.6.2 概述

ArrayList:一种可以动态增长和缩减的索引序列,称为数组列表。在Java SE5.0中,它是一个采用类型参数的泛型类。为了指定数组列表保存的元素对象类型,需要用<>将类名括起来加在后面。例如ArrayList <Employee>

泛型——装在集合当中所有元素,全部是统一的类型。但注意,泛型只能是引用类型,不能是基本类型(因为集合保存的均是引用类型的值)

ArrayList类似于C++的vector模板,它与vector都是泛型类型。但由于Java没有运算符重载,所以它不像vector模板为了便于访问元素重载[]运算符。此外,vector向量是值拷贝,而在Java中,a=b的赋值语句的操作结果,是让ab引用同一个数组列表。

另外,java.util.List正是ArrayList所实现的接口。

4.6.3 数组列表的相关操作

一、创建某个类型的ArrayList集合:

ArrayList<String> listA = new ArrayList<String>();
ArrayList<String> listB = new ArrayList<>();

对于ArrayList而言,直接打印得到的不是地址值,而是其内容,若内容为空,则得到空的中括号。

二、向集合添加元素:

public boolean add(E e):向集合当中添加元素,参数类型与泛型一致,返回值为boolean表示是否添加成功。

listA.add("ONEPIECE");
listA.add("I got it");
//运行结果为
//[ONEPIECE, I got it] 
//两边的中括号是输出时它本身自带的,输出中元素之间的','号也是本身自带的。

三、通过索引值从集合中获取元素:

String str = listA.get(4); //索引值从0开始,返回值即为编号对应的元素

四、从集合当中删除元素,参数为索引编号,返回值为被删除的元素:

String whoRemoved = listA.remove(3);

五、遍历集合中的元素:

for(int i = 0; i < listA.size(); i++){
    //do something;
}

六、集合作为参数:

public static ArrayList<String> getSmallList(ArrayList<String> list);

4.6.4 数组列表存储基本类型数据 与 包装类

如果希望向集合ArrayList当中存储基本类型数据,则必须使用基本类型所对应的包装类

基本类型 包装类
int Integer
char Character
byte Byte
long Long
short short
double Double
boolean Boolean
float Float

举例使用:

public class Demo05{
    public static void main(String[] args){
        ArrayList<Integer> listA = new ArrayList<>();
        listA.add(100);
        listA.add(200);
        int cur = listA.get(1);
    }
}

ArrayList<Integer>的效率远远低于int[]数组,因此应该用它构造小型集合。

从JDK 1.5+开始,支持自动打包(autoboxing)(即 基本类型 –> 包装类型)、自动拆包(即 包装类型 –> 基本类型)。

Integer i = 233; //自动打包,相当于Integer i = Integer.valueOf(233);
i = i + 66; //等号右边是将i对象转换成基本数据类型(自动拆包),i.intValue() + 66;

4.7 String类

Java没有内置的字符串类型,而是在标准Java类库中提供了一个预定义类String,它与C++中的字符串有很大的差异。

每个用双引号括起来的字符串都是(如"abcd"String类的一个实例。

特点:

  1. 字符串是常量,它们的值在创建之后不能更改!

    String strA = "Hello";
    strA = "Java";
    //上面的写法中,字符串的内容仍然没有改变,这样的写法是修改字符串变量strA,让它应用另外一个字符串。相当于将存放3的数值变量改成存放4一样
    
  2. 字符串是共享使用的。

4.7.1 字符串的构造方法和直接创建

创建字符串的几种方式:

  • 直接创建(无需使用new):String str = "Hello!";

  • 创建一个空白字符串,不含任何内容:public String();

  • 根据字符数组的内容,来创建对应的字符串:public String(char[] array);

  • 根据字节数组的内容,来创建对应的字符串:public String(byte[] array);

public class DemoString{
    public static void main(String[] args){
        String str0 = "Hello";
        
        String str1 = new String();
        
        char[] charArray = {'A', 'B', 'C'};
        String str2 = new String(charArray);
     
        byte[] byteArray = {97, 98, 99};
        String str3 = new String(byteArray);
    }
}

4.7.2 字符串常量池、检测字符串是否相等

String类没有提供用于修改字符串的方法。

如果要将下面的str中后面的"key"修改为"ey!",在Java中,要先提取出需要的字符,然后再拼接上替换的字符串。

String str = "Monkey";
str = str.substring(0, 3) + "ey!"; //Money!

C++的字符串可以修改,即能够修改字符串中的单个字符,但Java不行。

不可变的字符串有一个优点:编译器可以让字符串共享

字符串常量池存放在方法区❓,而字符串变量指向常量池中相应的位置。

检测字符串是否相等:

由于==,对于引用类型而言,是根据地址值比较的。这个运算符只能够确定两个字符串是否放置在同一位置上。而双引号括住的字符串在常量池当中,而new出来的字符串不在常量池中。

若确实需要字符串的内容进行比较可使用equals方法:

public boolean equals(Object obj); :参数可以为任何对象,但只有参数为一个字符串并且内容相同(严格区分英文大小写)的才会返回true,否则返回false

尽管equals方法是可交换的(即a.equals(b)b.equals(a)效果基本一致),但是,若要对一个常量与一个变量进行比较时,推荐常量字符串写在前面。原因见下:

"abc".equals(str); //推荐写法
str.equals("abc"); //不推荐写法
//因为,当字符串变量为null时,
String tmp = null;
System.out.println("abc".equals(tmp)); //返回false
System.out.println(tmp.equals("abc")); //报错!空指针异常

【附】:int compareTo(String other):按照字典序,若字符串位于other之前,返回负数;若字符串位于other之后返回正数;若两个字符串相等,返回0

4.7.3 字符串相关API

一、字符串获取:

  • 返回字符串的长度:int length()

    注意,String类获取长度是方法,即s.length();而数组的长度为成员,即a.length

  • 获取index位置(从0开始)的单个字符char charAt(int index)

  • 查找参数字符串str在本字符串当中首次出现的索引位置,若找不到则返回-1int indexOf(String str)

    char ch = "Hello".charAt(1); //e
    int idx = "HelloHelloHelloHello".indexOf("llo"); //2
    

二、字符串截取:

  • String substring(int index)

    截取从index位置(包括index)一直到字符串末尾的子串并将该子串返回。

  • String substring(int begin, int end)

    begin位置开始,到达不想复制的第一个位置end,即[begin, end)。显然,s.substring(a, b)的长度即为\(b -a\)

    String cur = "MonkeyDLuffy";
    System.out.println(cur.substring(6)); //DLuffy
    System.out.println(cur.substring(3, 6)); //key
    

三、字符串的拼接

Java语言允许使用+号拼接两个字符串。

String str = "Monkey";
str += "Luffy";
System.out.println(str); //MonkeyLuffy

int ans = 233;
System.out.println("The answer is " + ans); //The answer is 233

当将一个字符串与一个非字符串的值进行拼接时,后者被转换成字符串。(任何一个Java对象都可以转换成字符串)

四、字符串转换:

  • 将当前字符串拆分为字符数组并作为返回值:char[] toCharArray

  • 获得当前字符串底层的字节数组(IO流会使用):byte[] getBytes()

  • 将所有出现的目标字符串target替换为新的字符串,返回替换之后的结果新字符串:String replace(CharSequence target, CharSequence replacement)

    char[] c = "Hello".toCharArray();
    byte[] b = "Hello".getBytes();
    String str1 = "How do you do?";
    String str2 = str1.replace("o", "*"); //H*w d* y*u d*?
    

五、字符串的分割:

  • 根据字符串regex,将当前字符串切分成若干子串,并返回由这些子串组成的字符串数组:String[] split(String regex)

    String text = "not to be";
    String[] words = text.split(" ");
    for(int i = 0; i < words.length; i++)
        System.out.print(words[i]+" ");
    /*
    not
    to
    be
    */
    

    注意:split方法的参数为正则表达式,若要按照字符'.'进行切分,必须写\\.

4.7.4 字符串转换为基本类型 与 包装类

除了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基本类型。

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

4.8 StringBuilder 类

由于String类的对象内容不可改变,每当进行字符串拼接时,总是需要在内存中创建一个新对象,例如:

public class StringDemo{
    public static void main(String[] args){
        String s = "Hello"; //创建"Hello"字符串
        s += "World!"; //创建"World!"字符串,后再创建"HelloWorld!"
        System.out.println(s);
    }
}

如果要进行大量次拼接操作时,需要创建大量新的String对象,耗时且空间开销大,为解决此问题,可使用java.lang.StringBuilder类。

StringBuilder类为可变字符序列,它类似于String的字符串缓冲区,内部拥有一个数组用于存放字符串内容。当进行字符串拼接时,直接在该数组中加入新内容,同时StringBuilder本身也会自动维护数组的扩容。

常用方法有:

  • public StringBuilder append(...):添加任意类型数据的字符串形式,并返回当前对象自身(源码中就是返回this)。

  • public String toString():将当前StringBuilder对象转换为不可变的String对象,并将该String对象进行返回。

    public class JustTest {
        public static void main(String[] args) {
            StringBuilder curstr = new StringBuilder("Hello ").append("World").append("!");
            String str = curstr.toString();
            System.out.println(str);
        }
    }
    

    StringBuilder已经覆写了Object类的toString方法。

4.9 大数值类

java.math包中有两个有用的类,可以处理包含任意长度数字序列的数值。

  • BigInteger:实现任意精度的整数运算。
  • BigDecimal:实现任意精度的浮点数运算。

由于Java没有提供运算符重载的功能,无法重定义+*运算符,所以BigInteger类运算时需用其addmultiply

public class BigIntegerTest{
    public static void main(String[] args){
        int n = 10, k = 10;
        BigInteger ans = BigInteger.valueOf(1); //大整数初始化
        for(int i = 1; i <= k; i++)
            ans = ans.multiply(BigInteger.valueOf(n - i + 1)).divide(BigInteger.valueOf(i));
    }
} //组合公式计算

4.9.1大数值相关API

返回该大整数与另一大整数other的和、差、积、商及余数:

  • BigInteger add(BigInteger other)
  • BigInteger substact(BigInteger other)
  • BigInteger multiply(BigInteger other)
  • BigInteger divide(BigInteger other)
  • BigInteger mod(BigInteger other)

返回值等于x的大整数:

  • static BigInteger valueOf(long x)

若该大整数与另一个大整数other相等,返回0;若小于other,返回负数;否则,返回正数:

  • int compareTo(BigInteger other)

4.10 数学工具类Math

  • 获取绝对值,有多种重载:public static double abs(double num);
  • 向上取整:public static double ceil(double num);
  • 向下取整:public static double floor(double num);
  • 四舍五入,返回long型:public static long round(double num);

4.11 Object类

java.lang.Object类是Java中所有类的最终祖先,即在Java中每个类(包括数组类型)都是由它拓展而来的。

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

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

4.11.1 toString 方法

  • public String toString() :返回该对象的字符串表示,在Object类中该方法输出为:对象类型@内存地址值

当我们直接使用输出语句输出对象名时,其实通过该对象调用了其toString()方法。

然而在开发中需要根据对象属性得到相应的字符串表现形式,接下来便需要对该方法进行覆写。(以自定义的 Person 类)一般打印格式为:类的名字,随后是一对括号括起来的域值。

public class Person{
    private String name;
    private int age;
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    //....
}

4.11.2 equals 方法

  • public boolean equals(Object obj):检测一个对象是否等于另一对象,在Object类中该方法判断两个对象是否具有相同的引用。(相当于==运算符的运算结果)

然而,对于大多数类中,更经常地需要检测两个对象状态的相等性,比如所有成员变量相同就判定两个对象相同。因而,可以如下覆写:

import java.util.Objects;
public class Person{
    private String name;
    private int age;
    @Override
    public boolean equals(Object o){ //使用Object用于覆写超类的方法
        if(this == 0) return true;
        if(o == null || getClass() != o.getClass()) return false; //getClass()返回一个对象所属的类
        Person person = (Person)o; //向下转型,以使用子类特有的成员。
        return age == person.age && Object.equals(name, person.name);
        //基本类型相等,且将引用类型交给java.util.Objects类的equals静态方法取用结果。
    }
}

TIPS:IDEA中的“代码”(Code)菜单——“生成”(Generate)可以快速生成上面的toString方法和equals方法的覆写。

4.11.3 Objects 工具类

JDK7添加Objects工具类(注意,不是Object类),它由一些静态的实用方法组成,分为null-save(空指针安全)和null-tolerant(容忍空指针),用于计算对象的hashcode、返回对象的字符串表现形式、比较两个对象。

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

    public static boolean equals(Object a, Object b){
        return (a == b) || (a != null && a.equals(b));
    }
    

4.12 日期时间类

4.12.1 Date 类

java.util.Date类 的实例有一个状态,即特定的时间点。这个时间 是 用距离一个固定时间点的毫秒数(可正可负),而这个固定时间点为纪元(epoch)——UTC 时间 1971 年 1 月 1 日 00 : 00 : 00 。

由于我们身处东八区,则基准时间为1970年1月1日8时0分0秒

用类描述时间的好处在于,若类的设计不够完善,则可以编写自己的类以便增强或替代系统提供的类

构造方法:

  • public Date():分配Date对象并初始化此对象,以表示分配它的时间(精确到毫秒),自动设置当前系统时间的毫秒时刻;

  • public Date(long date):分配Date对象并初始化此对象以表示自从纪元以来的指定毫秒数。

    import java.util.Date;
    public class Hello {
        public static void main(String[] args) {
            System.out.println(new Date());
            //Thu Oct 29 21:03:14 CST 2020
            System.out.println(new Date(0L)); //将毫秒值转成日期对象
            //Thu Jan 01 08:00:00 CST 1970
        }
    }
    

4.12.2 DateFormat 类

java.text.DateFormat 为日期/时间格式化子类的抽象类,通过该类可以实现日期与文本间的转换(即Date对象与String对象间转换)

  • 格式化:按指定格式,Date对象 -> String对象。
  • 解析:按指定格式,String对象 -> Date对象。

一、构造方法

由于DateFormat为抽象类,需要常用的子类java.text.SimpleDateFormat(需要一个模式/格式来指定格式化或解析的标准),构造方法为:

  • public SimpleDateFormat(String pattern):用给定模式和默认语言环境的日期格式符号构造SimpleDateFormat。其中pattern为 代表日期时间的自定义格式 的字符串。

  • 格式规则

    标识字母(区分大小写) 含义
    y
    M
    d
    H
    m
    s
import java.text.DateFormat;
import java.text.SimpleDateFormat;
public class Hello {
    public static void main(String[] args) {
        DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        //对应日期格式为:2020-10-29 21:33:11
    }
}

二、常用方法

  • public String format(Date date):将Date对象格式化为字符串;

    import java.text.DateFormat;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    public class Hello {
        public static void main(String[] args) {
            Date date = new Date();
            DateFormat df = new SimpleDateFormat("yyyy年MM月dd日"); //创建日期格式化对象,指定风格
            String str = df.format(date);
            System.out.println(str); //2020年10月29日
        }
    }
    
  • public Date parse(String source):将字符串解析为Date对象;

    import java.text.DateFormat;
    import java.text.ParseException; //导包
    import java.text.SimpleDateFormat;
    import java.util.Date;
    public class Hello {
        public static void main(String[] args) throws ParseException { //异常处理
            DateFormat df = new SimpleDateFormat("yyyy年MM月dd日"); //创建日期格式化对象,指定风格
            String str = "2001年1月7日";
            Date bir = df.parse(str);
            System.out.println(bir); //2020年10月29日
        }
    }
    
  • public long getTime():将日期对象转换成对应的时间毫秒值。(可能已过时,但在计算某些时间差值时比较有用)

计算一个人从出生到此刻的天数:

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;
public class Hello {
    public static void main(String[] args) throws ParseException {
        System.out.println("请按YYYY年MM月dd日的格式输入您的出生日期:");
        String birStr = new Scanner(System.in).next(); //输入字符串日期
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日"); //指定日期模式
        Date birDate = sdf.parse(birStr); //调用parse方法将输入的字符串转换为日期对象
        long birSec = birDate.getTime(); //返回该日期的毫秒时刻

        Date curDate = new Date(); //获取此时此刻的日期
        long curSec = curDate.getTime();

        long diff = curSec - birSec; //计算差值
        if(diff < 0) System.out.println("还没出生呢!");
        else System.out.println(diff/1000/60/60/24); //毫秒转换为天
    }
}

4.13 Calendar 类

java.util.Calendar为日历类,该类将所有可能用到的时间信息封装为静态成员变量以便获取。

Calendar类中的方法取代了许多Date的方法。

一、创建对象

由于Calendar类为抽象类,它不能够直接创建,而是通过静态方法返回一个子类对象来进行创建(多态),该静态方法为:

  • public static Calendar getInstance():使用默认时区与语言环境获得一个 日历 。

    Calendar cal = Calendar.getInstance();
    

二、常用方法

Calendar类中提供很多有关日历的成员常量,它们各自代表不同的日历字段,同时也对应于Calendar相关方法中的传入参数field

字段值 含义
YEAR
MONTH 月(从0开始,可以+1使用)
DAY_OF_MONTH 月中的天(几号)
HOUR 时(12小时制)
HOUR_OF_DAY 时(24小时制)
MINUTE
SECOND
DAY_OF_WEEK 周中的天(周几,周日为1,可以-1使用)

Calendar.YEAR存的值为3,并不代表它就是记录月份,而是用于方法中识别的。其他成员常量也同理。

西方星期开始为周日,月份表示为\(0-11\)

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

  • public int get(int field):给定日历字段field,让其返回该字段含义的值。

    Calendar cal = Calendar.getInstance();
    int curyear = cal.get(Calendar.YEAR); 
    //我传入一个参数Calendar.YEAR,方法get()就会知道我要查询当前年份的值
    
  • public void set(int field, int value):给定日历字段field,要其字段对应含义的值修改为value

    Calendar cal = Calendar.getInstance();
    cal.set(Calendar.YEAR, 2020);
    
  • public abstract void add(int field, int amount):根据日历规则,为给定的日历字段添加或减去指定的时间量amount。若amount为负数,则说明减去偏移量;反之,为加。

    Calendar cal = Calendar.getInstance(); //2020年10月30日
    cal.add(Calendar.DAY_OF_MONTH, 2); //加2天
    cal.add(Calendar.MONTH, -3); //减三个月
    int mon = cal.get(Calendar.MONTH);
    int day = cal.get(Calendar.DAY_OF_MONTH);
    System.out.println(mon+"月"+day+"日"); //7月1日
    
  • public Date getTime():返回一个表示此Calendar时间值(从 纪元 到现在的毫秒数)的Date对象。(不同于Date类的getTime()方法!)

    Calendar cal = Calendar.getInstance();
    Date date = cal.getTime();//Fri Oct 30 00:40:00 CST 2020
    

4.14 System 类

java.lang.System 类中提供大量静态方法,可获取与系统相关的信息或系统级操作。常用方法有:

  • public static long currentTimeMillis():获取当前系统时间与 1970 年 01 月 01 日 00:00 点之间的毫秒差值

    public class JustTest {
        public static void main(String[] args) {
            long st = currentTimeMillis();
            for(int i = 0; i < 1000000000; i++){}
            long ed = currentTimeMillis();
            long diff = ed - st;
            System.out.println(diff); //4ms.........
        }
    }
    
  • public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length):将数组中指定的数据拷贝到另一个数组中。

posted @ 2020-10-23 22:27  J_StrawHat  阅读(149)  评论(0编辑  收藏  举报