Demo10-面向对象进阶(多态、内部类、常用API)
1.多态
1.1多态基础知识
1.同类型的对象,执行同一个行为,会表现出不同的行为特征,这就是多态
2.多态的常见形式:
- 父类类型 对象名称 = new 子类构造器
- 接口 对象名称 = new 实现类构造器
3.多态的前提:
- 有继承/实现关系
- 有父类引用指向子类对象
- 有方法重写
1.2多态的访问特点
- 方法调用:编译看左边,运行看右边
- 变量调用:编译看左边,运行也看左边
为什么有这样的访问特点呢:因为多态侧重行为多态
public class Test {
public static void main(String[] args) {
Animal a = new Dog();
a.run(); //方法调用:编译看左(Animal a),运行看右(new Dog()),所以执行Dog的run
System.out.println(a.name); //变量调用:编译看左,运行也看左,所以输出"动物名称"
Animal a2 = new Tortoise();
a2.run();
System.out.println(a2.name); //输出"动物名称
}
}
public class Animal {
public String name = "动物名称";
public void run(){
System.out.println("动物可以跑~~");
}
}
public class Dog extends Animal{
public String name = "狗名称";
@Override
public void run() {
System.out.println("🐕跑的贼溜~~~~~");
}
public void lookDoor(){
System.out.println("🐕在看🚪!!!");
}
}
public class Tortoise extends Animal{
public String name = "乌龟名称";
@Override
public void run() {
System.out.println("🐢跑的非常慢~~~");
}
}
1.3多态的优势
- 在多态形式下,右边的对象可以实现解耦合,便于扩展和维护
Animal a = new Dog();
a.run();
//后来我不想用Dog的业务了,我想用Tortoise,那么只需将Dog改为Tortoise而不用修改后续代码
Animal a = new Tortoise();
a.run();
- 定义方法的时候,使用父类型作为参数,该方法就可以接收这父类的一切子类对象,体现出多态的扩展性与遍历
1.4类型转换
1.多态下会产生一个问题:因为编译阶段看的是Animal a = new Dog();左边的,而Animal中没有方法lookDoor,那么如果a.lookDoor()就会报错,此时就需要强制类型转换
2.引用类型的类型转换分为两种:
- 自动类型转换(从子到父):子类对象赋值给父类类型的变量指向
- 强制类型转换(从父到子):子类 对象变量 = (子类)子类类型的变量,这样就可以解决多态下的劣势,可以实现调用子类独有的功能
3.如果转型后的类型和对象真实类型不是同一种类型,编译阶段不会报错,但是在运行阶段就会出现ClassCastException异常
//编译时不报错,运行时出现ClassCastException异常
Animal a = new Dog();
Tortoise b = (Tortoise) a;//有继承或实现的两个类型就可以进行强制转换,编译一定不会报错
(为什么编译阶段不报错呢,因为编译阶段只能看到Animal a = new Dog();左边的Animal a,所以编译器这样想:a是动物类型,那a指向的可能真的是Tortoise类型,所以我允许你强制转换为Tortoise类型)
针对这种现象java建议强制转换之前使用instanceof判断当前对象的真实类型,再进行强制转换
变量名 instanceof 真实类型
判断关键字左边的变量指向的对象的真实类型,是否是右边的类型或者是其子类类型,是则返回true,反之false
下面这个案例很经典,用到了多态,继承,并且很培养面向对象的思维~~
public class Test {
public static void main(String[] args) {
Layer layer = new Layer();
USB keyBoard = new KeyBoard("键盘");
USB mouse = new Mouse("鼠标");
layer.installUSB(keyBoard);
layer.installUSB(mouse);
}
}
//一.接口
public interface USB {
void connect();
void unconnect();
}
//二.鼠标和键盘的模板类
public class Template implements USB{
private String name;
public Template(String name) {
this.name = name;
}
@Override
public void connect() {
System.out.println(name + "成功的接入了设备了~~~");
}
@Override
public void unconnect() {
System.out.println(name + "成功的从设备弹出了~~~");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//3.1键盘具体类
public class KeyBoard extends Template{
public KeyBoard(String name) {
super(name);
}
//键盘的独有功能
public void keyDown(){
System.out.println(getName() + "写下了:老铁,6666,下次再来哦~~");
}
}
//3.2鼠标具体类
public class Mouse extends Template{
public Mouse(String name) {
super(name);
}
//鼠标的独有功能
public void click(){
System.out.println(getName() + "双击点亮小红心~~");
}
}
//四.有了USB接口,Template模板,Mouse和KeyBoard具体类了,
//已经可以在Test类里进行测试了,但是这样的话Test代码太多
//太乱,所以把具体实现写在多加的这一层,然后让Test调用即可
public class Layer {
public void installUSB(USB u) {
u.connect();
if(u instanceof Mouse){
Mouse m = (Mouse) u;
m.click();
}else if(u instanceof KeyBoard) {
KeyBoard k = (KeyBoard) u;
k.keyDown();
}
u.unconnect();
}
}
2.内部类
2.1内部类概述
1.内部类就是定义在一个类里面的类
2.内部类的使用场景,作用:
- 当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构可以选择使用内部类来设计
- 内部类通常可以直接访问外部类的成员,包括私有成员
- 内部类相比外部类提供了更好的封装性,内部类本身就可以用private protected等修饰,封装性可以做更多控制
外部类的上一层单元是包,对于外部类来说,只有两个作用域:同包或者任何位置,因此只需要两种控制权限:包控制权限和公开控制权限,即default和public。
而对于内部类来说就可以使用private和protected修饰符了,因为内部类外部有三个作用域:外部类、同包或者任何位置,以及还有父子类。因此,内部类使用private修饰,限定为本外部类使用;内部类使用protected修饰,限定为本外部类及父子类使用;内部类使用default修饰,限定为同包使用;内部类使用public修饰,任何位置均可使用。
3.内部类的分类
- 静态内部类[了解]
- 成员内部类(非静态内部类)[了解]
- 局部内部类[鸡肋语法,了解即可]
- 匿名内部类[重点]
2.2四种内部类
2.2.1静态内部类
-
静态内部类特点:使用static修饰,使用与普通类是一样的,类有的成分它都有,只是位置在别人里面而已
-
静态内部类中是否可以直接访问外部类的静态成员
- 可以,外部类的静态成员只有一份,可以被共享访问(可以这样理解:即使不能直接访问,我们也可以在内部类中通过外部类的类名访问其静态成员,然后这是在同一个类中,所以可以省去类名,直接用外部类静态成员的变量名或方法名访问,也就是可以直接访问)
-
静态内部类中是否可以直接访问外部类的实例成员
- 不可以,外部类的实例成员必须用外部类对象访问(如果可以直接访问外部类的实例成员了,那你访问的时候人家知道你要用的是哪个实例对象的成员吗,这样就会产生歧义)
public class Test {
public static void main(String[] args) {
Outer.Inner in = new Outer.Inner();
in.setName("张三");
in.show();
}
}
public class Outer {
public static int a = 100;
private String hobby;
//静态内部类
public static class Inner{
private String name;
public Inner(){}
public Inner(String name) {
this.name = name;
}
public void show(){
System.out.println("名称:" + name);
System.out.println("访问外部类静态成员:" + a);
//System.out.println(hobby);//报错
//通过创建外部类对象间接访问外部类的实例成员
Outer o = new Outer();
System.out.println(o.hobby);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
2.2.2成员内部类
- 无static修饰,归属于外部类的对象
- 成员内部类创建对象的格式
- 外部类名.内部类名 对象名 = new 外部类构造器.new 内部类构造器;
- Outer.Inner in = new Outer().new Inner();
- JDK16之前,成员内部类中不能定义静态成员,JDK16开始也可以定义静态成员了(两个方面理解:一.因为成员内部类归属于外部类的对象,而静态资源是应该属于类的,所以规定成员内部类中不能定义静态成员二.既然成员内部类归属于外部类的对象,如果创建了一百个外部类对象,那么成员内部类中的静态资源不就也有一百份了?所以规定成员内部类中不能定义静态成员。但从JDK16开始给成员内部类单独搞了一个共享静态区,就允许定义静态成员了,自此,成员内部类的功能不再受到阉割,和普通类的成分一样了)
- 成员内部类是否可以直接访问外部类的静态成员
- 可以,外部类的静态成员只有一份,可以被共享访问
- 成员内部类的实例方法中是否可以直接访问外部类的实例成员
- 可以,因为必须先有外部类对象,才能有成员内部类对象,所以可以直接访问外部类对象的实例成员
public class Test {
public static void main(String[] args) {
Outer.Inner in = new Outer("吃饭").new Inner();
in.setName("内部");
in.show();
Outer.Inner.test();//访问成员内部类的静态成员
}
}
public class Outer {
public static int num = 111;
private String hobby;
public Outer() {
}
public Outer(String hobby) {
this.hobby = hobby;
}
public class Inner{
private String name;
public static int a = 100; //JDK16开始支持静态成员了
public static void test(){//静态成员方法
System.out.println(a);
}
public void show(){
System.out.println("名称:" + name);
System.out.println("数量:" + num);//访问外部类静态成员
System.out.println("爱好:" + hobby);//访问外部类实例成员
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
静态内部类和成员内部类的区别:静态内部类其实就是将两个类合在一起了,没有什么逻辑,而成员内部类呢:就比如一个汽车对象和一个发动机对象,肯定要先有汽车才有发动机呀,此时用成员内部类就非常合理,所以成员内部类还是挺符合逻辑的,使用场景比静态内部类多
在成员内部类中访问所在外部类对象,格式:外部类对象.this
public class Test2 {
public static void main(String[] args) {
People.Heart heart = new People().new Heart();
heart.show();
}
}
class People{
private int heartbeat = 150;
public class Heart{
private int heartbeat = 110;
public void show(){
int heartbeat = 78;
System.out.println(heartbeat); // 78
System.out.println(this.heartbeat); // 110
System.out.println(People.this.heartbeat); // 150
}
}
}
2.2.3匿名内部类
- 本质上是一个没有名字的局部内部类,定义在方法中,代码块中等
- 作用:方便创建子类对象,最终目的为了简化代码编写
- new 类|抽象类名或接口名|() {重写方法;};
public class Test {
public static void main(String[] args) {
Animal a = new Animal(){//子类类型赋值给父类的变量(多态)
@Override
public void run() {
System.out.println("老虎跑的块~~~");//不需要定义一个子类也可能完成某个功能
}
};
a.run();
}
}
abstract class Animal{
public abstract void run();
}
//我们要因为完成这一个功能而定义一个子类,匿名内部的使用使我们拜托了这个麻烦
//class Tiger extends Animal{
// @Override
// public void run() {
// System.out.println("老虎跑的块~~~");
// }
//}
public class Test {
public static void main(String[] args) {
Animal a = new Animal(){
@Override
public void run() {
System.out.println("老虎跑的块~~~");
}
};
a.run();
}
}
class Animal{//类不一定是抽象类
public void run() {
System.out.println("跑呀跑~~");
}
}
匿名内部类特点总结:
- 匿名内部类是一个没有名字的内部类
- 匿名内部类写出来就会产生一个匿名内部类的对象(不是抽象类或接口对象,抽象类,接口永远不会有实例对象)
- 匿名内部类的对象类型相当于是当前new的那个类型的子类类型
- 匿名内部类编译后也会产生class文件
public class Test2 {
public static void main(String[] args) {
Swimming s = new Swimming() {
@Override
public void swim() {
System.out.println("学生快乐的自由泳🏊");
}
};
go(s);
//匿名内部类作为方法的实际参数进行传输
go(new Swimming() {
@Override
public void swim() {
System.out.println("运动员🏊的贼快啊~~~~~");
}
});
}
public static void go(Swimming s){
System.out.println("开始。。。");
s.swim();
System.out.println("结束。。。");
}
}
interface Swimming{
void swim();
}
上述实例可以得到匿名内部类可以作为方法的实际参数进行传输
3.常用API
3.1Object类
一个类要么默认继承了Object类,要么间接继承了Object类,Object类是java中的祖宗类,所以Object类的方法是一切子类都可以直接使用的,我们要学习Object类的方法
方法名 | 说明 |
---|---|
public String toString() | 默认返回当前对象在堆内存中的地址信息:类的全限名@内存地址 |
public Boolean equals(Object o) | 默认是比较当前对象与另一个对象的地址是否相同,相同返回true,不同返回false |
3.1.1toString方法
Student s = new Student("周雄", '男', 19);
System.out.println(s.toString());
//可以直接输出对象变量,默认调用的就是s.toString()这应该是java做的优化
System.out.println(s);
问题引出:
- 开发中直接输出对象,默认输出对象的地址其实是毫无意义的
- 开发中输出对象变量(默认调用toString方法),更多的时候是希望看到对象的内容数据而不是对象的地址信息
所以父类toString()方法存在的意义就是为了被子类重写,以便返回对象的内容信息而不是地址信息
3.1.2equals方法
问题引出:
- 直接比较两个对象的地址是否相同完全可以用"=="替代equals
所以父类equals方法存在的意义就是为了被子类重写,以便子类自己来定制比较规则
public class Test2 {
public static void main(String[] args) {
Student s1 = new Student("周雄");
Student s2 = new Student("周雄");
// equals默认是比较2个对象的地址是否相同,子类重写后会调用子类重写的来比较内容是否相同。
System.out.println(s1.equals(s2));//true
System.out.println(s1 == s2);//false
System.out.println(Objects.equals(s1, s2));//true(Objects的equals方法源码中会
//调用s1的equals方法,根据多态的特性:调用方法时编译看左,运行看右,所以会执行Student重
//写后的equals方法,看不懂的可以看完3.2和1.2再回来看)
}
}
public class Student { //extends Object{
private String name;
public Student() {
}
public Student(String name) {
this.name = name;
}
//官方的
// @Override
// public boolean equals(Object o) {
// // 1、判断是否是同一个对象比较,如果是返回true。
// if (this == o) return true;
// // 2、如果o是null返回false 如果o不是学生类型返回false ...Student != ..Pig
// if (o == null || this.getClass() != o.getClass()) return false;
// // 3、已证明o一定是学生类型而且不为null,那么进行强转以便后续使用学生的特有成员name
// Student student = (Student) o;
// return Objects.equals(name, student.name);
// }
//自己的
@Override
public boolean equals(Object o){
// 1、判断o是不是学生类型(包含了判断o是否为空)
if(o instanceof Student){
Student s2 = (Student) o;
// 2、判断2个对象的内容是否一样。
return this.name.equals(s2.name);//字符串比较用equals
}else {
// 学生只能和学生比较,否则结果一定是false
return false;
}
}
}
3.2Objects类
方法名 | 说明 |
---|---|
public static boolean equals(Object a, Object b) | 比较两个对象的,底层会先进行非空判断,从而可以避免空指针异常,再进行equals比较 |
public static boolean isNull(Object obj) | 判断变量是否为null,为null返回true,反之 |
上述代码中官方进行字符串name比较时,没有用字符串对象自己的equals方法,而是选择了Objects的equals方法来比较两个对象
Objects的equals方法源码分析:
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));//如果a指向的真实对象重写了equals方 //法,那么根据多态的访问特点a.equals(b)会调用重写后的equals方法
}
会先判断a是否为空,如果不为空再调用a的equals方法进行比较,从而避免了空指针,所以说Objects的equals方法和String的equals方法比较结果是一样的,但是前者更安全
3.2StringBuilder类
3.2.1StringBuilder类的基本使用
- StringBuilder是一个可变的字符串类,我们可以把它看成是一个对象容器
- 作用:提高字符串的操作效率,如拼接,修改等
StringBuilder构造器:
名称 | 说明 |
---|---|
public StringBuilder() | 创建一个空白的可变的字符串对象,不包含任何内容 |
public StringBuilder(String str) | 创建一个指定字符串内容的可变字符串对象 |
使用StringBuilder时的注意点:
- 和String一样,StringBuilder重写了toString方法并且java对其进行了优化,所以可以直接输出输出指向StringBuilder对象的变量,java会优化为输出字符串内容:System.out.println(sb);
- 有很多类都重写了toString方法以便输出指向对象的变量时不是打印对象的地址而是打印具体对象里面的值,这是java做的优化,如后面涉及的BigDecimal/Date等等,这里就不再多赘述
- 进append源码可以看到append方法和reverse方法等等返回值是该对象的引用,所以可以套娃,即支持链式编程:sb.append(“a”).append(“b”);
- StringBuilder只是拼接字符串的手段:效率高.最后还是要转为String类型
- 点进StringBuilder可以看到这个类和String类没有继承关系,那总不能别人声明方法时参数类型给你写StringBuilder吧,大家都已经习惯了写String,所以StringBuilder类型的变量最终还是要转成String类型
append方法的源码:
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
3.2.2String类及StringBuilder类拼接字符串原理
String类拼接字符串原理图:
String类拼接字符串时一个加号,堆内存中就会创建两个对象(StringBuilder对象和String对象),拼接完成后丢弃StringBuilder对象,让String类型的变量指向String对象.如果再次进行加号拼接,就会再创建两个对象,拼接完成后丢弃StringBuilder对象和旧的String对象,让String类型的变量指向新创建的String对象,这样极度浪费内存和性能
StringBuilder提升效率原理图:
StringBuilder进行拼接时永远只会创建这一个对象,加号连接都在这个对象上进行,不会出现浪费内存的现象
综上我们知道了String是内容不可变的,StringBuilder是内容可变的,所以拼接,修改字符串时最好用StringBuilder进行
3.3Math类
Math是一个工具类
- 工具类不需要创建对象,所以Math类的构造器是私有的
- 那我们怎么使用工具类中的方法呢
- 工具类中的方法都是static修饰的,可以直接通过类名调用
Math类常用方法:
方法名 | 说明 |
---|---|
public static int abs(int a) | 获取参数绝对值 |
public static double ceil(double a) | 向上取整 |
public static double floor(double a) | 向下取整 |
public static int round(double a) | 四舍五入 |
public static int max(int a, int b) | 获取两个int值中的较大值 |
public static double pow(double a, double b) | 返回a的b次幂的值 |
public static double random() | 返回值为double的随机值,范围[0.0,1.0) |
注意这里Math的random()方法不要和以前使用的Random类搞混了,以前使用的是Random类的nextInt()方法
使用Math的random方法时不需要像Random那样先创建类再调用类的方法,所以推荐使用这种方式
拓展:使用random方法生成一个[3,9]的随机数:
//[3,9]-->[0,6]+3
int data = (int)(Math.random() * 7) + 3;//注意这里是乘7而不是乘6,因为生成的[0.0,1.0)包左不包右
System.out.println(data);
3.4System类
System类和Math类是一样的,都是工具类,所以构造器私有,方法用static修饰
System类的常用方法:
方法名 | 说明 |
---|---|
public static void exit(int status) | 终止当前运行的java虚拟机,非0表示异常终止 |
public static long currentTimeMillis() | 返回当前系统的时间毫秒值形式 |
public static void arraycopy(数据源数组,起始索引,目的地数组,起始索引,拷贝个数) | 数组拷贝 |
3.5BigDecimal类
3.5.1BigDecimal类的作用及使用方法
BigDecimal用于解决浮点型运算精度失真的问题
使用方法:创建BigDecimal对象封装浮点型数据,下面有三种方式:
-
使用构造方法BigDecimal(double)的方式将double转化为BigDecimal对象(这种方法错误!!!)
- BigDecimal(double)存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常
- 比如:BigDecimal g = new BigDecimal(0.1F);实际存的值为:0.10000000149
-
使用入参为String类型的构造方法:BigDecimal g = new BigDecimal(“0.1”);
- 但是实际开发中谁会将一个浮点数转换为String类型再传参呢,所以这种方法可行,但不推荐
-
使用BigDecimal的valueOf方法:BigDecimal g = BigDecimal.valueOf(0.1);
- 该方法内部其实执行了Double的toString方法,而Double的toString方法按double的实际能表达的精度对尾数进行了截断
- valueOf方法的源码:
public static BigDecimal valueOf(double val) { return new BigDecimal(Double.toString(val)); }
综上,创建BigDecimal对象封装浮点型数据时我们最好使用valueOf方法
3.5.2BigDecimal常用API
方法名 | 说明 |
---|---|
public BigDecimal add(BigDecimal b) | 加法 |
public BigDecimal subtract(BigDecimal b) | 减法 |
public BigDecimal multiply(BigDecimal b) | 乘法 |
public BigDecimal divide(BigDecimal b) | 除法 |
public BigDecimal divide(另一个BigDecimal对象,精确几位,舍入模式) | 除法 |
public double doubleValue() | BigDecimal类型转为double类型 |
1.说一下public double doubleValue()这个API:
和StringBuilder类一样,用的时候必须用,但是正常开发中我们肯定不会给方法的参数设为BigDecimal类型,所以使用BigDecimal完成精度运算后需要将其转为double类型,这就是这个API存在的价值
2.说一下public BigDecimal divide(另一个BigDecimal对象,精确几位,舍入模式)这个API:
BigDecimal是一定要进行精度运算的,如果计算结果不能精度运算则报错
BigDecimal a = BigDecimal.valueOf(10.0);
BigDecimal b = BigDecimal.valueOf(3.0);
BigDecimal c = a11.divide(b);//10除以3除不尽,会报错
System.out.println(c);
BigDecimal a = BigDecimal.valueOf(10.0);
BigDecimal b = BigDecimal.valueOf(3.0);
BigDecimal c = a11.divide(b, 2, RoundingMode.HALF_UP);//四舍五入保留小数点后2位
System.out.println(c);
这就是这个API存在的价值
3.6Date类
Date的构造器:
构造器 | 说明 |
---|---|
public Date() | 创建一个Date对象,代表的是系统当前此刻日期时间 |
public Date(long time) | 把时间毫秒值转换成Date日期对象 |
Date的常用方法:
public long getTime() | 获取时间对象的毫秒值 |
public void setTime(long time) | 设置日期对象的时间为当前时间毫秒值对应的时间 |
3.7SimpleDateFormat类
- 可以对Date对象或时间毫秒值格式化成我们喜欢的时间格式
- 也可以把字符串的时间形式解析成日期对象
SimpleDateFormat的构造器
构造器 | 说明 |
---|---|
public SimpleDateFormat() | 构造一个SimpleDateFormat,使用默认格式 |
public SimpleDateFormat(String pattern) | 构造一个SimpleDateFormat,使用指定的格式 |
SimpleDateFormat的格式化方法:
格式化方法 | 说明 |
---|---|
public final String format(Date date) | 将日期格式化成日期/时间字符串 |
public final String format(Object time) | 将时间毫秒值格式化成日期/时间字符串 |
SimpleDateFormat的解析方法:
解析方法 | 说明 |
---|---|
public Date parse(String source) | 从给定字符串额开始解析文本以生成日期 |
public class SimpleDateFormatDemo01 {
public static void main(String[] args) {
// 1、日期对象
Date d = new Date();
// 2、指定最终格式化的形式
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss EEE a");
// 3.1、格式化日期对象成为喜欢的字符串形式
String rs = sdf.format(d);
System.out.println(rs);
System.out.println("----------------------------");
// 3.2、格式化时间毫秒值成为喜欢的字符串形式
long time1 = System.currentTimeMillis();
String rs2 = sdf.format(time1);
System.out.println(rs2);
}
}
yyyy | 年 |
MM | 月 |
dd | 日 |
HH | 小时 |
mm | 分钟 |
ss | 秒 |
EEE | 周几 |
a | 上午or下午 |
public class SimpleDateFormatDemo2 {
public static void main(String[] args) throws ParseException {
// 目标: 学会使用SimpleDateFormat解析字符串时间成为日期对象。
// 有一个时间 2021年08月06日 11:11:11 往后 2天 14小时 49分 06秒后的时间是多少。
// 1、把字符串时间拿到程序中来
String dateStr = "2021年08月06日 11:11:11";
// 2、把字符串时间解析成日期对象(本节的重点):形式必须与被解析时间的形式完全一样,否则运行时解析报错!
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
Date d = sdf.parse(dateStr);
// 3、往后走2天 14小时 49分 06秒
long time = d.getTime() + (2L*24*60*60 + 14*60*60 + 49*60 + 6) * 1000;
// 4、格式化这个时间毫秒值就是结果
System.out.println(sdf.format(time));
}
}
上述案例的细节:
- 写完Date d = sdf.parse(dateStr);后会报错,因为java怕你定义的形式与被解析时间的形式不一样,所以java规定在这里需要抛出异常或捕获异常,我们用的是抛出:在方法上加throws ParseException
- long time = d.getTime() + (2L*24*60*60 + 14*60*60 + 49*60 + 6) * 1000;时可能这个值很大,超过了int的范围,所以需要在运算的最开始也就是"2"的部分加上L使其转为long类型
3.8Calendar
- Calendar代表了此系统此刻日期对应的日历对象
- Calendar是一个抽象类,不能直接创建对象,通过调用静态方法getInstance获取对象
getInstance方法的源码:
public static Calendar getInstance()
{
return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
}
为什么调用该方法可以获取对象呢(以下皆为看源码所得,理解不动的话可以看着源码理解):
- createCalendar方法中new了一个对象(一个switch分支,根据不同的情况new不同的对象,这里假设new的是BuddhistCalendar对象)并经过一些操作后返回该对象
- BuddhistCalendar类继承自GregorianCalendar类,GregorianCalendar类又继承自Calendar抽象类
- 根据多态的特点createCalendar方法中new的BuddhistCalendar对象的地址可以赋值给Calendar类型的变量,自此,成功获取对象
Calendar常用方法:
方法名 | 说明 |
---|---|
public int get(int field) | 取日期中的某个字段信息 |
public void set(int field, int value) | 修改日历的某个字段信息 |
public void add(int field, int amount) | 为某个字段增加/减少指定的值 |
public final Date getTime() | 拿到此刻日期对象 |
public long getTimeInMillis() | 拿到此刻时间毫秒值 |
使用set和add方法时注意Calendar是可变日期对象,一旦修改后其对象本身表示的时间将产生变化
3.9JDK8开始新增API
1.有哪些新增API
新增日期类 | 特性 |
---|---|
LocalDate | 不包含具体时间的日期 |
LocalTime | 不含日期的时间 |
LocalDateTime | 包含了日期及时间 |
Instant | 代表的是时间戳 |
DateTimeFormatter | 用于时间的格式化和解析 |
Duration | 用于计算两个"时间"间隔 |
Period | 用于计算两个"日期"间隔 |
- 新增的API严格区分了时刻,本地时间,本地时间,并且对日期和时间进行运算更加方便
- 新API的类型几乎全部是不变类型(和String的使用类似),可以放心使用不比担心被修改
2.LocalDate,LocalTime,LocalDateTime部分API
方法名 | 说明 | |
---|---|---|
public static Xxxx now(); | 静态方法,根据当前时间创建对象 | LocaDate localDate =LocalDate.now(); LocalTime localTime =LocalTime.now(); LocalDateTime localDateTime =LocalDateTime.now(); |
public static Xxxx of(…); | 静态方法,指定日期/时间创建对象 | LocalDate localDate1 = LocalDate.of(2099, 11,11); LocalTime localTime1 = LocalTime.of(11, 11, 11); LocalDateTime localDateTime1 = LocalDateTime.of(2020, 10, 6, 13, 23, 43) |
LocalDateTime的转换API:
方法名 | 说明 |
---|---|
public LocalDate toLocalDate() | 转换成一个LocalDate对象 |
public LocalTime toLocalTime() | 转换成一个LocalTime对象 |