# 面向对象
# 类
类( class) 是构造对象的模板或蓝图,我们可以将类想象成一个工业的模版,通过这个模版生产出来的一个个工业制品就是对象。对象与对象之间可以存在着差异。也就是不同,但也可以相同。
所有的类都源自于一个“ 神通广大的超类”,它就是 Object
# 对象
对象的行为:我们可以对对象施加哪些操作,或者可以对对象施加哪些方法
对象的状态:当施加那些方法时,对象如何的响应 (对象属性值,我们可以通过方法去改变它)
对象标识:如何辨别具有相同行为与状态的不同对象 (引用地址)
对象的状态可能会随着时间而发生改变,但这种改变不会是自发的 ,对象状态的改变必须通过调用方法实现(如果不经过方法调用就可以改变对象状态, 只能说明封装性遭到了破坏。)
# 类之间的关系
# 依赖关系
依赖(dependence ), 即“ uses-a” 关系, 是一种最明显的、 最常见的关系。和关联关系不同的是,依赖关系是在运行过程中起作用的。
一个类使用了另一个类的服务或信息,但不包含对方的对象。这种关系通常通过以下三种方式体现的
- 参数传递
- 局部变量
- 返回值
简单的理解,依赖就是一个类A使用到了另一个类B,而这种使用关系是具有偶然性的、临时性的、非常弱的,但是类B的变化会影响到类A。在UML类图设计中,依赖关系用由类A指向类B的带箭头虚线表示。

代码演示 Book.java
public class Book {
private String name="《红楼梦》";
public String getName(){
return this.name;
}
}
2
3
4
5
6
People.java
public class People {
// Book作为read()方法的形参
public void read(Book book){
System.out.println("读的书是 "+book.getName());
}
}
2
3
4
5
6
解释
People类中的read方法接受一个Book类型的参数。这意味着People类在执行read方法时依赖于Book类。- 这种依赖关系是在运行时建立的,因为只有当你调用
People的实例上的read方法,并传入一个Book实例时,才会涉及到Book类。 - 如果没有
Book类或没有提供Book类的一个实例给read方法,那么People类的这个特定功能(即阅读一本书)就无法完成。 - 重要的是要认识到,虽然
People类依赖于Book类来完成read方法的功能,但这并不意味着People类不能存在或拥有其他不依赖于Book类的功能。因此,这是一种弱依赖关系,因为它不影响People类的基本结构或存在。
总结来说:People 类与 Book 类之间存在一种依赖关系,这种关系具体体现在 People 类的 read 方法上,它需要 Book 类的一个实例才能正确工作。但是,应该强调的是这种依赖是动态的、基于方法调用的,而不是静态的类级别依赖。
在日常开发当中尽可能地将相互依赖的类减至最少,。如果类A不知道B的存在,它就不会关心B的任何改变(这意味着B的改变不会导致A产生任何bug)。用软件工程的术语来说,就是让类之间的耦合度最小。
# 关联关系
关联体现的是两个类之间语义级别的一种强依赖关系,比如我和我的朋友,这种关系比依赖更强、不存在依赖关系的偶然性、关系也不是临时性的,一般是长期性的,而且双方的关系一般是平等的。关联可以是单向、双向的。表现在代码层面,为被关联类B以类的属性形式出现在关联类A中,也可能是关联类A引用了一个类型为被关联类B的全局变量。在UML类图设计中,关联关系用由关联类A指向被关联类B的带箭头实线表示。

概念: 单向关联表现为:类A当中使用了类B,其中类B是作为类A的成员变量。双向关联表现为:类A当中使用了类B作为成员变量;同时类B中也使用了类A作为成员变量。
代码演示 Son.java
public class Son {
private String name;
Father father=new Father();
public String getName(){
return this.name;
}
public void giveGift(){
System.out.println("送给"+father.getName()+"礼物");
}
}
2
3
4
5
6
7
8
9
10
Father.java
public class Father {
private String name;
Son son = new Son();
public String getName(){
return this.name;
}
public void getGift(){
System.out.println("从"+son.getName()+"获得礼物");
}
}
2
3
4
5
6
7
8
9
10
解释 关联关系使一个类获得另一个类的属性和方法.关联关系可以是单向的,也可以是双向的.例如:儿子送个父亲一个礼物,儿子和父亲都需要打印一句话.从儿子的角度,儿子需要知道父亲的名字,从父亲的角度,父亲需要知道儿子的名字,于是就需要在各自的类中实例对方的类,为得到对方的名字,这样,儿子和父亲都可以访问对方的属性和方法了.
# 聚合关系
聚合是关联关系的一种特殊形式,它表示了一种“整体-部分”的关系,但与组合不同的是,部分可以在没有整体的情况下独立存在。换句话说,即使整体对象被销毁,部分对象仍然可以继续存在,并且它们可能属于其他整体对象。在代码中实现聚合关系时,通常会有一个类(整体)包含对另一个类(部分)的引用,但是这些引用的对象是在外部创建的,并且可以在多个整体之间共享。在UML类图设计中,聚合关系以空心菱形加实线表示。(菱形指向整体)

代码演示:大学和教师关系
University.java
// 整体类 University
public class University {
private String name;
private List<Teacher> teachers = new ArrayList<>();
public University(String name) {
this.name = name;
}
// 添加教师到大学的方法
public void addTeacher(Teacher teacher) {
teachers.add(teacher);
teacher.setUniversity(this); // 设置教师所属的大学
}
// 移除教师的方法
public void removeTeacher(Teacher teacher) {
teachers.remove(teacher);
teacher.setUniversity(null); // 解除教师与大学的关系
}
public List<Teacher> getTeachers() {
return Collections.unmodifiableList(teachers);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Teacher.java
// 部分类 Teacher
public class Teacher {
private String name;
private University university;
public Teacher(String name) {
this.name = name;
}
// 获取教师所属的大学
public University getUniversity() {
return university;
}
// 设置教师所属的大学
public void setUniversity(University university) {
this.university = university;
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
", university=" + (university != null ? university.getName() : "无") +
'}';
}
// 获取教师的名字
public String getName() {
return name;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
解释
在这个例子中,University 类代表整体,而 Teacher 类代表部分。一个大学可以有多个教师,但是这些教师也可以在不同的大学工作,或者不隶属于任何特定的大学。这体现了聚合的特点——部分可以在没有整体的情况下独立存在
# 继承关系
继承指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,并可以增加它自己的新功能的能力。在Java中继承关系通过关键字extends明确标识,在设计时一般没有争议性。在UML类图设计中,继承用一条带空心三角箭头的实线表示,从子类指向父类,或者子接口指向父接口。

概念: 类A当中使用了类B,其中类B是作为类A的方法参数、方法中的局部变量、或者静态方法调用。 代码演示 Animal.java
public class Animal {
private void getPrivate(){
System.out.println("private");
}
void getDefault(){
System.out.println("default");
}
protected void getProtected(){
System.out.println("protected");
}
public void getPublic(){
System.out.println("public");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
Dog.java
public class Dog extends Animal{
public static void main(String[] args) {
Dog dog=new Dog();
dog.getDefault();
dog.getProtected();
dog.getPublic();
}
}
输出结果
default
protected
public
2
3
4
5
6
7
8
9
10
11
12
13
解释 继承关系是非常常用的一种关系,使用extends来进行子类继承父类.子类可以继承父类的非私有属性和方法
# 实现关系
实现指的是一个class类实现interface接口(可以是多个)的功能,实现是类与接口之间最常见的关系。在Java中此类关系通过关键字implements明确标识,在设计时一般没有争议性。在UML类图设计中,实现用一条带空心三角箭头的虚线表示,从类指向实现的接口。

概念: 表示一个类实现一个或多个接口的方法。接口定义好操作的集合,由实现类去完成接口的具体操作。在java中使用implements表示。 代码演示 IPeople.java
public interface IPeople {
public void eat();
}
2
3
People.java
public class People implements IPeople{
@Override
public void eat() {
System.out.println("人正在吃饭。。。");
}
}
2
3
4
5
6
7
8
解释 实现是指一个类实现一个接口,接口使用interface定义,接口并不是一个类.接口中属性只有常量,方法是抽象的,只能定义,不能包含方法体,并且属性和方法都必须是public的(即使不写方法修改符,也是公共方法),实现类必须使用implements去实现接口的所有方法,可以为空方法.
# 组合关系
组合也是关联关系的一种特例,这种关系比聚合更强,也称为强聚合。它同样体现整体与部分间的关系,但此时整体与部分是不可分的,整体的生命周期结束也就意味着部分的生命周期结束,比如人和人的大脑。表现在代码层面,和关联关系是一致的,只能从语义级别来区分。在UML类图设计中,组合关系以实心菱形加实线表示。

概念: 相比于聚合,组合是一种耦合度更强的关联关系。存在组合关系的类表示“整体-部分”的关联关系,“整体”负责“部分”的生命周期,他们之间是共生共死的;并且“部分”单独存在时没有任何意义。 代码演示 Body.java
public class Body {
private String name;
public String getName(){
return this.name;
}
}
2
3
4
5
6
Soul.java
public class Soul {
private String name;
public String getName(){
return this.name;
}
}
2
3
4
5
6
People.java
public class People {
Soul soul;
Body body;
//组合关系中的成员变量一般会在构造方法中赋值
public People(Soul soul, Body body){
this.soul = soul;
this.body = body;
}
public void study(){
System.out.println("学习要用灵魂"+soul.getName());
}
public void eat(){
System.out.println("吃饭用身体:"+body.getName());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
解释 它也是关联关系,但是聚合度比聚合关系还要强.组合关系是整体-部分,部分单独存在没有意义,需要与整体结合使用.也就是表示整体的这个类负责部分的这些类的生命周期,整体消亡,部分也就不存在了.
# 构造器
要想使用对象,首先必须构造对象,并且指定其初始状态。可以使用够再起构造器方法创建新对象。
构造器方法是和类名相同的,默认会存在一个无参的构造器。如果存在构造器,将不会存在默认无参构造器方法。
# 隐式参数与显示参数
public void raiseSalary(double byPercent){
double raise = salary * byPercent / 100;
salary += raise;
}
2
3
4
我们分析上面的方法,在这个方法当中用到了两个参数salary和byPercent,其中salary是来自调用的对象本身的属性,称为隐式属性。byPercent是方法参数中显示声明需要传入的,称为显示参数。
修改代码如下:可以将实例域与局部变量明显地区分开来
public void raiseSalary(double byPercent){
double raise = this.salary * byPercent / 100;
this.salary += raise;
}
2
3
4
# 基于类的访问权限
一个方法可以访问所属类的所有对象的私有数据
# 私有方法
在面向对象编程中,private是一种访问修饰符,用于限制成员的访问范围。私有成员只能在所属的类内部访问,对外部的类或对象是不可见的。
private的使用可以带来以下几个好处:
封装实现细节:
私有成员可以隐藏在类的内部,不被外部对象直接访问。这样可以封装类的实现细节,使类的使用者只能通过公共接口进行操作,从而达到隐藏内部实现的目的,提高代码的安全性和可维护性。
防止误操作:
私有成员的使用可以防止外部对象对类的内部状态进行直接修改,避免了错误的操作和数据的不一致性。只有通过类提供的公共接口,才能对私有成员进行访问和修改,确保了类的数据的正确性和一致性。
提供更好的封装性:
私有成员可以控制对类的内部实现的访问权限,使类的使用者只能通过公共接口进行操作。这样可以提供更好的封装性,使类的内部细节对外部完全隐藏,减少了类的依赖和耦合性,提高了类的灵活性和可维护性。
需要注意的是,私有成员只能在所属的类内部进行访问,不能被外部类或对象直接访问。如果子类需要访问父类的私有成员,可以通过继承和公共方法来实现。另外,私有成员可以是属性、方法或构造函数等任何成员类型。
总结起来,private是一种访问修饰符,用于限制成员的访问范围。私有成员只能在所属的类内部访问,对外部的类或对象是不可见的。通过private的使用,可以实现封装实现细节、防止误操作和提供更好的封装性等好处。
示例代码
public class Person{
private String name;
private int age;
private void priMethod(){
System.out.println("这是一个私有成员方法");
}
public void pubMethod(){
System.out.println("这是一个公有成员方法,接下来要调用私有成员方法");
this.priMethod();
}
//setter & getter方法
public void setAge(int age){
if(age < 0 || age > 150){
System.out.println("年龄非法~被设为默认值18");
this.age = 18;
return;
}
System.out.println("年零为"+age);
this.age = age;
}
public int getAge(){
return age;
}
public void setName(String name){
System.out.println("名字是"+name);
this.name = name;
}
public String getName(){
return name;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
这段代码定义了一个名为Person的类,其中包含了私有的属性name和age,以及公有的方法pubMethod。
在这个类中,私有方法priMethod被定义为私有的,只能在Person类内部被访问。公有方法pubMethod可以被外部类或对象访问,并在其内部调用了私有方法priMethod,即通过公有方法来访问私有方法。
除了方法的定义,代码中还包含了属性的setter和getter方法。setter方法用于设置属性的值,在设置age属性时,添加了一些对年龄合法性的判断,如果年龄小于0或大于150,则输出错误信息并将年龄设为默认值18。getter方法用于获取属性的值。
总的来说,这段代码使用了private关键字来修饰成员变量name和age,使其成为私有成员,只能在Person类内部访问。私有方法priMethod也被定义为私有的,只能在Person类内部调用。通过使用私有成员,可以提高类的封装性,隐藏内部实现细节,只允许通过公有方法来访问和修改私有成员。
# final 实力域
参考第二章
# 静态域与静态方法
参考第二章
# 方法参数
Java 程序设计语言总是采用按值调用。也就是说, 方法得到的是所有参数值的一个拷贝,特别是,方法不能修改传递给它的任何参数变量的内容
例如
//基本数据类型
public static void tripieValue(double x) // doesn't work
{
x = 3 * x;
}
//然后调用
double percent = 10;
tripieValue(percent);
//打印percent还是10
//引用类型
public static void tripieSalary(Employee x) // works
{
x.raiseSa1ary(200) ;
}
harry = new Employee(. . .) ;
tripieSalary(harry) ;
//引用类型参数传递的是引用地址,地址不会改变。但是引用地址的对象内容是可以改变的
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 重载
有相同的名字、 不同的参数的方法这样的方法我们称为重载,在执行方法的时候可以根据方法的类型 .
Java 允许重载任何方法, 而不只是构造器方法。
因此,要完整地描述一个方法,需要指出方法名以及参数类型。这叫做方法的签名(signature)
# 默认域初始化
如果在构造器中没有显式地给域赋予初值,那么就会被自动地赋为默认值
# 无参数的构造器
很多类都包含一个无参数的构造函数, 对象由无参数构造函数创建时, 其状态会设置为适当的默认值。 例如, 以下是 Employee 类的无参数构造函数
public Employee {
name = salary = 0;
hireDay = LocalDate,now();
}
2
3
4
5
6
7
8
如果在编写一个类时没有编写构造器, 那么系统就会提供一个无参数构造器。
这个构造器将所有的实例域设置为默认值。于是, 实例域中的数值型数据设置为 0、 布尔型数据设置为 false、 所有对象变量将设置为 null。
如果类中提供了至少一个构造器, 但是没有提供无参数的构造器, 则在构造对象时如果没有提供参数就会被视为不合法
# 显式域初始化
直接给每一个实例域都设置一个有意义的初值,但是这个处置也可以不是常量。
常量
class Employee{
private String name ="";
...
}
2
3
4
5
非常量
class Employee{
private static int nextld;
private int id = assignld();
private static int assignld(){
int r = nextld;
nextld++;
}
...
}
2
3
4
5
6
7
8
9
10
# 参数名
使用单个字符命名存在缺点:只有阅读代码才能够了解参数的含义 。可以在每个参数前面加上一个前缀“ a” .
参数变量用同样的名字将实例域屏蔽起来,通过this进行区分。
# 调用另一个构造器
可以在构造器方法当中通过this(...)调用其他构造器
# 包
Java 允许使用包(package)将类组织起来。借助于包可以方便地组织自己的代码,并将自己的代码与别人提供的代码库分开管理。
同时也可以区分相同类名的类。
# 类的导入
可以通过import 导入指定包中的类
import java.util.*;
# 静态导入
import static java.lang.System.*;
导人静态方法和静态域的功能
# 将类放入包中
必须将包的名字放在源文件的开头
# 包作用域
没有将类定义为公有类(public),因此只有在同一个包(在此是默认包)中的其他类可以访问
# LocalDate/LocalDateTime
最开始的时候,Date既要承载日期信息,又要做日期之间的转换,还要做不同日期格的显示,职责较繁杂
后来从JDK 1.1 开始,这三项职责分开了:
使用Calendar类实现日期和时间字段之间转换;
使用DateFormat类来格式化和分析日期字符串;
而Date只用来承载日期和时间信息。
2
3
原有Date中的相应方法已废弃。不过,无论是Date,还是Calendar,都用着太不方便了,这是API没有设计好的地方。
坑爹的year和month
Date date = new Date(2012,1,1);
System.out.println(date);
输出 Thu Feb 01 00:00:00 CST 3912
2
3
观察输出结果,year是2012+1900,而month,月份参数我不是给了1吗? 怎么输出二月(Feb)了?
应该曾有人告诉你,如果你要设置日期,应该使用 java.util.Calendar,像这样…
Calendar calendar = Calendar.getInstance();
calendar.set(2013, 8, 2);
2
这样写又不对了,calendar的month也是从0开始的,表达8月份应该用7这个数字,要么就干脆用枚举
calendar.set(2013, Calendar.AUGUST, 2);
注意上面的代码,Calendar年份的传值不需要减去1900(当然月份的定义和Date还是一样),这种不一致真是让人抓狂!
有些人可能知道,Calendar相关的API是IBM捐出去的,所以才导致不一致。
java.util.Date与java.util.Calendar中的所有属性都是可变的
下面的代码,计算两个日期之间的天数….
public static void main(String[] args) {
Calendar birth = Calendar.getInstance();
birth.set(1975, Calendar.MAY, 26);
Calendar now = Calendar.getInstance();
System.out.println(daysBetween(birth, now));
System.out.println(daysBetween(birth, now)); // 显示 0?
}
public static long daysBetween(Calendar begin, Calendar end) {
long daysBetween = 0;
while(begin.before(end)) {
begin.add(Calendar.DAY_OF_MONTH, 1);
daysBetween++;
}
return daysBetween;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
daysBetween有点问题,如果连续计算两个Date实例的话,第二次会取得0,因为Calendar状态是可变的,考虑到重复计算的场合,最好复制一个新的Calendar
public static long daysBetween(Calendar begin, Calendar end) {
Calendar calendar = (Calendar) begin.clone(); // 复制
long daysBetween = 0;
while(calendar.before(end)) {
calendar.add(Calendar.DAY_OF_MONTH, 1);
daysBetween++;
}
return daysBetween;
}
2
3
4
5
6
7
8
9
SimpleDateTimeFormat是非线程安全的。
缺点:
- 设计不合理,使用不方便。很多都被淘汰了。
- 都是可变对象,修改后会丢失最开始的信息。
- 线程不安全
- 只能精确到毫秒
Java 8仍然延用了ISO的日历体系,并且与它的前辈们不同,java.time包中的类是不可变且线程安全的。新的时间及日期API位于java.time包中,下面是里面的一些关键的类:
- Instant——它代表的是时间戳(时间戳)
- LocalDate——不包含具体时间的日期,比如2014-01-14。它可以用来存储生日,周年纪念日,入职日期等。(年 月 日)
- LocalTime——它代表的是不含日期的时间(时 分 秒)
- LocalDateTime——它包含了日期及时间,不过还是没有偏移信息或者说时区。(年 月 日 时 分 秒)
- ZoneId: 时区
- ZonedDateTime——这是一个包含时区的完整的日期时间,偏移量是以UTC/格林威治时间为基准的。(带时区的时间)
- DateTimeFormatter 用于时间的格式化和解析
- Duration: 时间间隔(时 分 秒 纳秒)
- Period 时间间隔 (年 月 日)
方法概览
该包的API提供了大量相关的方法,这些方法一般有一致的方法前缀:
- of: 静态工厂方法。
- parse: 静态工厂方法,关注于解析。
- get: 获取某些东西的值。
- is: 检查某些东西的是否是true。
- with: 不可变的setter等价物。
- plus: 加一些量到某个对象。
- minus: 从某个对象减去一些量。
- to: 转换到另一个类型。
- at: 把这个对象与另一个对象组合起来,例如: date.atTime(time)。
public class TimeIntroduction {
public static void testClock() throws InterruptedException {
//时钟提供给我们用于访问某个特定 时区的 瞬时时间、日期 和 时间的。
Clock c1 = Clock.systemUTC(); //系统默认UTC时钟(当前瞬时时间 System.currentTimeMillis())
System.out.println(c1.millis()); //每次调用将返回当前瞬时时间(UTC)
Clock c2 = Clock.systemDefaultZone(); //系统默认时区时钟(当前瞬时时间)
Clock c31 = Clock.system(ZoneId.of("Europe/Paris")); //巴黎时区
System.out.println(c31.millis()); //每次调用将返回当前瞬时时间(UTC)
Clock c32 = Clock.system(ZoneId.of("Asia/Shanghai"));//上海时区
System.out.println(c32.millis());//每次调用将返回当前瞬时时间(UTC)
Clock c4 = Clock.fixed(Instant.now(), ZoneId.of("Asia/Shanghai"));//固定上海时区时钟
System.out.println(c4.millis());
Thread.sleep(1000);
System.out.println(c4.millis()); //不变 即时钟时钟在那一个点不动
Clock c5 = Clock.offset(c1, Duration.ofSeconds(2)); //相对于系统默认时钟两秒的时钟
System.out.println(c1.millis());
System.out.println(c5.millis());
}
public static void testInstant() {
//瞬时时间 相当于以前的System.currentTimeMillis()
Instant instant1 = Instant.now();
System.out.println(instant1.getEpochSecond());//精确到秒 得到相对于1970-01-01 00:00:00 UTC的一个时间
System.out.println(instant1.toEpochMilli()); //精确到毫秒
Clock clock1 = Clock.systemUTC(); //获取系统UTC默认时钟
Instant instant2 = Instant.now(clock1);//得到时钟的瞬时时间
System.out.println(instant2.toEpochMilli());
Clock clock2 = Clock.fixed(instant1, ZoneId.systemDefault()); //固定瞬时时间时钟
Instant instant3 = Instant.now(clock2);//得到时钟的瞬时时间
System.out.println(instant3.toEpochMilli());//equals instant1
}
public static void testLocalDateTime() {
//使用默认时区时钟瞬时时间创建 Clock.systemDefaultZone() -->即相对于 ZoneId.systemDefault()默认时区
LocalDateTime now = LocalDateTime.now();
System.out.println(now);
//自定义时区
LocalDateTime now2 = LocalDateTime.now(ZoneId.of("Europe/Paris"));
System.out.println(now2);//会以相应的时区显示日期
//自定义时钟
Clock clock = Clock.system(ZoneId.of("Asia/Dhaka"));
LocalDateTime now3 = LocalDateTime.now(clock);
System.out.println(now3);//会以相应的时区显示日期
//不需要写什么相对时间 如java.util.Date 年是相对于1900 月是从0开始
//2013-12-31 23:59
LocalDateTime d1 = LocalDateTime.of(2013, 12, 31, 23, 59);
//年月日 时分秒 纳秒
LocalDateTime d2 = LocalDateTime.of(2013, 12, 31, 23, 59, 59, 11);
//使用瞬时时间 + 时区
Instant instant = Instant.now();
LocalDateTime d3 = LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault());
System.out.println(d3);
//解析String--->LocalDateTime
LocalDateTime d4 = LocalDateTime.parse("2013-12-31T23:59");
System.out.println(d4);
LocalDateTime d5 = LocalDateTime.parse("2013-12-31T23:59:59.999");//999毫秒 等价于999000000纳秒
System.out.println(d5);
//使用DateTimeFormatter API 解析 和 格式化
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
LocalDateTime d6 = LocalDateTime.parse("2013/12/31 23:59:59", formatter);
System.out.println(formatter.format(d6));
//时间获取
System.out.println(d6.getYear());
System.out.println(d6.getMonth());
System.out.println(d6.getDayOfYear());
System.out.println(d6.getDayOfMonth());
System.out.println(d6.getDayOfWeek());
System.out.println(d6.getHour());
System.out.println(d6.getMinute());
System.out.println(d6.getSecond());
System.out.println(d6.getNano());
//时间增减
LocalDateTime d7 = d6.minusDays(1);
LocalDateTime d8 = d7.plus(1, IsoFields.QUARTER_YEARS);
//LocalDate 即年月日 无时分秒
//LocalTime即时分秒 无年月日
//API和LocalDateTime类似就不演示了
// 两个日期是否相等
System.out.println(d1.equals(d2));
// MonthDay - 用来检查生日
LocalDate dateOfBirth = LocalDate.of(2010, 01, 14);
MonthDay birthday = MonthDay.of(dateOfBirth.getMonth(), dateOfBirth.getDayOfMonth());
MonthDay currentMonthDay = MonthDay.from(today);
System.out.println(currentMonthDay.equals(birthday));
// YearMonth - 用来检查信用卡过期
YearMonth currentYearMonth = YearMonth.now(); System.out.printf("Days in month year %s: %d%n", currentYearMonth, currentYearMonth.lengthOfMonth());
YearMonth creditCardExpiry = YearMonth.of(2018, Month.FEBRUARY);
System.out.printf("Your credit card expires on %s %n", creditCardExpiry);
// 判断闰年 - LocalDate类有一个isLeapYear()的方法
System.out.println(dateOfBirth.isLeapYear());
}
public static void testZonedDateTime() {
//即带有时区的date-time 存储纳秒、时区和时差(避免与本地date-time歧义)。
//API和LocalDateTime类似,只是多了时差(如2013-12-20T10:35:50.711+08:00[Asia/Shanghai])
ZonedDateTime now = ZonedDateTime.now();
System.out.println(now);
ZonedDateTime now2 = ZonedDateTime.now(ZoneId.of("Europe/Paris"));
System.out.println(now2);
//其他的用法也是类似的 就不介绍了
ZonedDateTime z1 = ZonedDateTime.parse("2013-12-31T23:59:59Z[Europe/Paris]");
System.out.println(z1);
}
public static void testDuration() {
//表示两个瞬时时间的时间段
Duration d1 = Duration.between(Instant.ofEpochMilli(System.currentTimeMillis() - 12323123), Instant.now());
//得到相应的时差
System.out.println(d1.toDays());
System.out.println(d1.toHours());
System.out.println(d1.toMinutes());
System.out.println(d1.toMillis());
System.out.println(d1.toNanos());
//1天时差 类似的还有如ofHours()
Duration d2 = Duration.ofDays(1);
System.out.println(d2.toDays());
}
public static void testChronology() {
//提供对java.util.Calendar的替换,提供对年历系统的支持
Chronology c = HijrahChronology.INSTANCE;
ChronoLocalDateTime d = c.localDateTime(LocalDateTime.now());
System.out.println(d);
}
/**
* 新旧日期转换
*/
public static void testNewOldDateConversion(){
Instant instant=new Date().toInstant();
Date date=Date.from(instant);
System.out.println(instant);
System.out.println(date);
}
public static void main(String[] args) throws InterruptedException {
testClock();
testInstant();
testLocalDateTime();
testZonedDateTime();
testDuration();
testChronology();
testNewOldDateConversion();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
其它语言时间
日期与时间处理API,在各种语言中,可能都只是个不起眼的API,如果你没有较复杂的时间处理需求,可能只是利用日期与时间处理API取得系统时间,简单做些显示罢了,然而如果认真看待日期与时间,其复杂程度可能会远超过你的想象,天文、地理、历史、政治、文化等因素,都会影响到你对时间的处理。所以在处理时间上,最好选用JSR310(如果你用java8的话就实现310了),或者Joda-Time。
不止是java面临时间处理的尴尬,其他语言同样也遇到过类似的问题,比如
Arrow: Python 中更好的日期与时间处理库
Moment.js: JavaScript 中的日期库
Noda-Time: .NET 阵营的 Joda-Time 的复制
总结
看完了这些例子后,我相信你已经对Java 8这套新的时间日期API有了一定的了解了。现在我们来回顾下关于这个新的API的一些关键的要素。
- 它提供了javax.time.ZoneId用来处理时区。
- 它提供了LocalDate与LocalTime类 Java 8中新的时间与日期API中的所有类都是不可变且线程安全的,这与之前的Date与Calendar API中的恰好相反,那里面像java.util.Date以及SimpleDateFormat这些关键的类都不是线程安全的。
- 新的时间与日期API中很重要的一点是它定义清楚了基本的时间与日期的概念,比方说,瞬时时间,持续时间,日期,时间,时区以及时间段。它们都是基于ISO日历体系的。
- 每个Java开发人员都应该至少了解这套新的API中的这五个类: Instant 它代表的是时间戳,比如2014-01-14T02:20:13.592Z,这可以从java.time.Clock类中获取,像这样: Instant current = Clock.system(ZoneId.of(“Asia/Tokyo”)).instant(); LocalDate 它表示的是不带时间的日期,比如2014-01-14。它可以用来存储生日,周年纪念日,入职日期等。 LocalTime – 它表示的是不带日期的时间 LocalDateTime – 它包含了时间与日期,不过没有带时区的偏移量 ZonedDateTime – 这是一个带时区的完整时间,它根据UTC/格林威治时间来进行时区调整
- 这个库的主包是java.time,里面包含了代表日期,时间,瞬时以及持续时间的类。它有两个子package,一个是java.time.foramt,这个是什么用途就很明显了,还有一个是java.time.temporal,它能从更低层面对各个字段进行访问。
- 时区指的是地球上共享同一标准时间的地区。每个时区都有一个唯一标识符,同时还有一个地区/城市(Asia/Tokyo)的格式以及从格林威治时间开始的一个偏移时间。比如说,东京的偏移时间就是+09:00。 OffsetDateTime类实际上包含了LocalDateTime与ZoneOffset。它用来表示一个包含格林威治时间偏移量(+/-小时: 分,比如+06:00或者 -08: 00)的完整的日期(年月日)及时间(时分秒,纳秒)。 DateTimeFormatter类用于在Java中进行日期的格式化与解析。与SimpleDateFormat不同,它是不可变且线程安全的,如果需要的话,可以赋值给一个静态变量。DateTimeFormatter类提供了许多预定义的格式器,你也可以自定义自己想要的格式。当然了,根据约定,它还有一个parse()方法是用于将字符串转换成日期的,如果转换期间出现任何错误,它会抛出DateTimeParseException异常。类似的,DateFormatter类也有一个用于格式化日期的format()方法,它出错的话则会抛出DateTimeException异常。
- 再说一句,“MMM d yyyy”与“MMm dd yyyy”这两个日期格式也略有不同,前者能识别出”Jan 2 2014″与”Jan 14 2014″这两个串,而后者如果传进来的是”Jan 2 2014″则会报错,因为它期望月份处传进来的是两个字符。为了解决这个问题,在天为个位数的情况下,你得在前面补0,比如”Jan 2 2014″应该改为”Jan 02 2014″。
优点:
- 设计更合理,使功能丰富。使用更方便
- 都是不可变对象,修改后会返回新的时间对象,不会丢失最开始的时间
- 线程安全
- 能精确到毫秒,纳秒
# 三大特性
# 封装
利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体。数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。用户无需知道对象内部的细节,但可以通过对象对外提供的接口来访问该对象。
优点:
- 减少耦合: 可以独立地开发、测试、优化、使用、理解和修改
- 减轻维护的负担: 可以更容易被程序员理解,并且在调试的时候可以不影响其他模块
- 有效地调节性能: 可以通过剖析确定哪些模块影响了系统的性能
- 提高软件的可重用性
- 降低了构建大型系统的风险: 即使整个系统不可用,但是这些独立的模块却有可能是可用的
以下 Person 类封装 name、gender、age 等属性,外界只能通过 get() 方法获取一个 Person 对象的 name 属性和 gender 属性,而无法获取 age 属性,但是 age 属性可以供 work() 方法使用。
注意到 gender 属性使用 int 数据类型进行存储,封装使得用户注意不到这种实现细节。并且在需要修改 gender 属性使用的数据类型时,也可以在不影响客户端代码的情况下进行。
public class Person {
private String name;
private int gender;
private int age;
public String getName() {
return name;
}
public String getGender() {
return gender == 0 ? "man" : "woman";
}
public void work() {
if (18 <= age && age <= 50) {
System.out.println(name + " is working very hard!");
} else {
System.out.println(name + " can't work any more!");
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 继承
继承实现了 IS-A 关系,例如 Cat 和 Animal 就是一种 IS-A 关系,因此 Cat 可以继承自 Animal,从而获得 Animal 非 private 的属性和方法。
继承应该遵循里氏替换原则,子类对象必须能够替换掉所有父类对象。
Cat 可以当做 Animal 来使用,也就是说可以使用 Animal 引用 Cat 对象。父类引用指向子类对象称为 向上转型 。
Animal animal = new Cat();
# 多态
多态分为编译时多态和运行时多态:
- 编译时多态主要指方法的重载
- 运行时多态指程序中定义的对象引用所指向的具体类型在运行期间才确定
运行时多态有三个条件:
- 继承
- 覆盖(重写)
- 向上转型
下面的代码中,乐器类(Instrument)有两个子类: Wind 和 Percussion,它们都覆盖了父类的 play() 方法,并且在 main() 方法中使用父类 Instrument 来引用 Wind 和 Percussion 对象。在 Instrument 引用调用 play() 方法时,会执行实际引用对象所在类的 play() 方法,而不是 Instrument 类的方法。
public class Instrument {
public void play() {
System.out.println("Instrument is playing...");
}
}
public class Wind extends Instrument {
public void play() {
System.out.println("Wind is playing...");
}
}
public class Percussion extends Instrument {
public void play() {
System.out.println("Percussion is playing...");
}
}
public class Music {
public static void main(String[] args) {
List<Instrument> instruments = new ArrayList<>();
instruments.add(new Wind());
instruments.add(new Percussion());
for(Instrument instrument : instruments) {
instrument.play();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
