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("群发短信");
}
}
注意事项:
- 成员变量是直接定义在类内部,在方法外部。若成员变量没有进行复制,那么会有默认值(规则与数组相同)
- 成员方法不用写
static
关键字。 - 在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
为例,若要生成构造方法同理)
![](https://gitee.com/j__strawhat/MyImages/raw/master/20201024113806.png)
选择你需要生成的方法类型,此处选中Getter和Setter
,得到下面窗口。
![](https://gitee.com/j__strawhat/MyImages/raw/master/20201024113954.png)
下图即为生成成功。
![](https://gitee.com/j__strawhat/MyImages/raw/master/20201024114026.png)
TIPS:有些程序员会为每个参数前面加上一个前缀"a"。如:
public Employee(String aName, double aSalary)
,十分清晰。C++中一般用下划线作为实例域的前缀(
确实),如salary
域会被命名为_salary
。但Java程序员通常不这样做。
4.2.3 对象的构造方法
构造方法(构造器)就是专门用于创建对象的方法,当我们通过关键字new
来创建对象时,实际上就是在调用构造方法。其格式为:
public 类名称(参数类型 参数名称, ... ){
方法体
}
注意事项:
- 仅当类没有提供任何构造方法的时候,系统才会提供一个默认的构造方法。(一旦编写了至少一个构造方法,系统则不再提供)
- 构造方法的名称必须与所在的类名称完全一样(包括大小写)。
- 构造方法不要写返回值类型(连
void
都不写),当然也不能return
一个具体的返回值。 - 构造方法总是伴随
new
操作一起调用。
4.2.4 对象的创建与使用
我们知道,一个类并不能直接使用,需要根据类创建一个对象,方能使用。
-
导包:指出需要使用的类在哪里
格式:
import 包名称.类名称;
import day03.demo1.Student; //别漏分号
注:对于和当前类属于同一个包的情况,可省略不写导包语句
在Java中,
package
与import
语句,类似于C++中namespace
和using
指令 -
创建
格式:
类名称 对象名 = new 类名称()
或者类名称 对象名 = new 类名称(参数1, 参数2, ...)
Student stu = new Student();
-
使用
(在没有
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();
}
}
[附]几个对象的内存图
![](https://gitee.com/j__strawhat/MyImages/raw/master/02-两个对象使用同一个方法的内存图.png)
![](https://gitee.com/j__strawhat/MyImages/raw/master/03-两个引用指向同一个对象的内存图.png)
在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 { //静态代码块中的内容 } }
特点:
-
一旦第一次使用了该类的静态代码块,该静态代码块以后都不再执行
静态代码块的典型用途:用于一次性地对静态成员变量进行赋值
-
静态内容总是优先于非静态,故静态代码块比构造方法更早执行。
-
4.3 封装
封装性在Java中的体现:
- 方法是一种封装;
- 关键字
private
也是一种封装。
一旦使用private
进行修饰,那么本类当中仍然可以随意访问。但是,超出本类范围之外,不能再直接访问了。要间接访问private
成员变量,就是定义一对Getter/Setter
方法。
-
仅访问实例域而不进行修改的方法,称之为访问器方法(accessor method)。在Java中,对应为
Getter
,不能有参数,返回值类型和成员变量相对应。命令规则一般为getXXX
(若为boolean
类型,则应命名为isXXX
)。eg:getTime
-
对实例域做出修改的方法,称之为更改器方法(mutator method)。在Java中,对应为
Setter
,不能有返回值,参数类型与成员变量相对应。命令规则一般为setXXX
。eg:setTime
如此封装的好处:
- 可改变内部实现,除了该类的方法之外,不会影响其他代码。
- 更改器方法可以执行错误检查,然而直接对域进行赋值将不会进行这些处理。
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 引用类型的使用步骤
-
导包
格式:
import 包路径.类名称;
只有
java.lang
包下的内容不需要导包,其他的包均需要import
语句。 -
创建
格式:
对象名 = new 类名称(参数1,...);
-
使用
格式:
对象名.成员方法名()
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();
使用:
-
获取一个随机
int
型数字,其范围为int
所有范围,含正负。int num = r.nextInt();
-
获取一个随机
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
的赋值语句的操作结果,是让a
和b
引用同一个数组列表。另外,
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
类的一个实例。
特点:
-
字符串是常量,它们的值在创建之后不能更改!
String strA = "Hello"; strA = "Java"; //上面的写法中,字符串的内容仍然没有改变,这样的写法是修改字符串变量strA,让它应用另外一个字符串。相当于将存放3的数值变量改成存放4一样
-
字符串是共享使用的。
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
在本字符串当中首次出现的索引位置,若找不到则返回-1
:int 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
类运算时需用其add
和multiply
。
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)
:将数组中指定的数据拷贝到另一个数组中。