创建日期类
一、日期类
日期是通过年、月、日这三项来表示的。我们来创建一个日期类,将这些项定义为int型字段;如果将类命名为Day, 只考虑字段的话,可以像下面这样进行声明;类Day的内容如图所示:
将所有的字段都设为私有(private) 外部访问可以通过构造函数和方法间接执行。
二、构造函数和方法
构造函数是在创建实例时为了正确进行初始化而设置的控件;类Day的构造函数的定义如下所示,形参中接收到的三个整数值直接赋给了各个字段;
为了访问与形参同名的字段,这里使用了this,如果不使用this, 则会变成:
在创建类Day类型的实例时,要传递给该构造函数三个int型的参数,如下所示:
Day birthday = new Day(1963, 11, 18);
启动构造函数时,birthday的字段year、manth、date就会被分别赋入1963 、11 、18;创建完构造函数后,下面创建如下所示的方法:
class Day {
private int year; // 年
private int month; // 月
private int date; // 日
//--- 构造函数 ---//
Day(int year, int month, int date) {
this.year = year; // 年
this.month = month; // 月
this.date = date; // 日
}
//--- 获取年、月、日 ---//
int getYear() { return year; } // 获取年
int getMonth() { return month; } // 获取月
int getDate() { return date; } // 获取日
//--- 设置年、月、日 ---//
void setYear(int year) { this.year = year; } // 设置年
void setMonth(int month) { this.month = month; } // 设置月
void setDate(int date) { this.date = date; } // 设置日
void set(int year, int month, int date) { // 设置年月日
this.year = year; // 年
this.month = month; // 月
this.date = date; // 日
}
//--- 计算星期 --//
int dayOfWeek() {
int y = year; // 0 … 星期日
int m = month; // 1 … 星期一
if (m == 1 || m == 2) { // :
y--; // 5 … 星期五
m += 12; // 6 … 星期六
}
return (y + y / 4 - y / 100 + y / 400 + (13 * m + 8) / 5 + date) % 7;
}
}
方法dayOfWeek会计算并返回星期的整数值如果是星期日,则返回值为0; 如果是星期一则返回值为1; 以此类推,如果是星期六则返回值为6。
三、访问器
//--- 获取年、月、日 ---//
int getYear() { return year; } // 获取年
int getMonth() { return month; } // 获取月
int getDate() { return date; } // 获取日
//--- 设置年、月、日 ---//
void setYear(int year) { this.year = year; } // 设置年
void setMonth(int month) { this.month = month; } // 设置月
void setDate(int date) { this.date = date; } // 设置日
用于获取字段值的方法称为getter方法,用于设置字段值的方法称为setter方法,两者统称为访问器;
使用类Day的程序示例:
import java.util.Scanner;
class DayTester {
public static void main(String[] args) {
Scanner stdIn = new Scanner(System.in);
String[] wd = {"日", "一", "二", "三", "四", "五", "六"};
System.out.println("请输入阳历生日。");
System.out.print("年:"); int y = stdIn.nextInt();
System.out.print("月:"); int m = stdIn.nextInt();
System.out.print("日:"); int d = stdIn.nextInt();
Day birthday = new Day(y, m, d);
System.out.println("你的生日"
+ birthday.getYear() + "年"
+ birthday.getMonth() + "月"
+ birthday.getDate() + "日是星期"
+ wd[birthday.dayOfWeek()] + "。");
}
}
输出:
四、类类型变量的赋值
// 类类型变量的赋值
class DayAssign {
public static void main(String[] args) {
Scanner stdIn = new Scanner(System.in);
String[] wd = {"日", "一", "二", "三", "四", "五", "六"};
System.out.println("请输入阳历生日。");
System.out.print("年:"); int y = stdIn.nextInt();
System.out.print("月:"); int m = stdIn.nextInt();
System.out.print("日:"); int d = stdIn.nextInt();
Day birthday = new Day(y, m, d);
System.out.println("你的生日"
+ birthday.getYear() + "年"
+ birthday.getMonth() + "月"
+ birthday.getDate() + "日是星期"
+ wd[birthday.dayOfWeek()] + "。");
Day xDay = birthday;
xDay.set(2100, 12, 31); // 设置为2100年12月31日
System.out.println("birthday = "
+ birthday.getYear() + "年"
+ birthday.getMonth() + "月"
+ birthday.getDate() + "日("
+ wd[birthday.dayOfWeek()] + ")");
System.out.println("xDay = "
+ xDay.getYear() + "年"
+ xDay.getMonth() + "月"
+ xDay.getDate() + "日("
+ wd[xDay.dayOfWeek()] + ")");
}
}
输出:
在上述程序中,另外创建了一个Day类型的变量xDay,并将其设置为2100年12月3l日,但这样一来,本不应该被设置值的birday的日期也被改写了我们来分析一下原因:
Day xDay = birthday;
Day类型的变量xDay的声明,初始值birthday是类类型变量,是实例主体的引用。因此,xDay会被初始化为birthday实例的引用。最终,如图所示,遥控器birthday和xDay操作的对象(引用目标)实例是同一个实例。由于未调用函数, 因此肯定不会创建新的日期实例。
xDay.set(2100, 12, 31); // 设置为2100年12月31日
中使用方法set将xDay的各个字段设置为2100、12 、31。对xDay引用的实例设置值其实就是修改原本为birthday创建的实例的值
当将类类型的变量初始化或者赋值为同一类型的变量时,会复制引用目标,而不会复制所有字段的值。
五、类类型变量的比较
对类类型变量应用的相等运算符==和!=会判断引用目标是否相同, 而不会判断字段的值是否相等
创建一个判断两个日期是否相等的程序:
class DayComparator1 {
public static void main(String[] args) {
Scanner stdIn = new Scanner(System.in);
int y, m, d;
System.out.println("请输入日期1。");
System.out.print("年:"); y = stdIn.nextInt();
System.out.print("月:"); m = stdIn.nextInt();
System.out.print("日:"); d = stdIn.nextInt();
Day day1 = new Day(y, m, d);
System.out.println("请输入日期2。");
System.out.print("年:"); y = stdIn.nextInt();
System.out.print("月:"); m = stdIn.nextInt();
System.out.print("日:"); d = stdIn.nextInt();
Day day2 = new Day(y, m, d);
if (day1 == day2)
System.out.println("相等。");
else
System.out.println("不相等。");
}
}
输出:
程序中的if语句会判断dayl和day2的引用目标是否相同。由于dayl和day2是分别创建的实例,因此它们的引用目标不相同,不管字段为何值,==运算符的结果永远都是false
若要判断所有字段的值是否相等,就需要调用getter 方法,确认年、月、日, 正确的程序如下:
class DayComparator2 {
public static void main(String[] args) {
Scanner stdIn = new Scanner(System.in);
int y, m, d;
System.out.println("请输入日期1。");
System.out.print("年:"); y = stdIn.nextInt();
System.out.print("月:"); m = stdIn.nextInt();
System.out.print("日:"); d = stdIn.nextInt();
Day day1 = new Day(y, m, d);
System.out.println("请输入日期2。");
System.out.print("年:"); y = stdIn.nextInt();
System.out.print("月:"); m = stdIn.nextInt();
System.out.print("日:"); d = stdIn.nextInt();
Day day2 = new Day(y, m, d);
if (day1.getYear() == day2.getYear() && day1.getMonth() == day2.getMonth()
&& day1.getDate() == day2.getDate())
System.out.println("相等。");
else
System.out.println("不相等。");
}
}
输出:
六、作为参数的类类型变量
创建一个判断两个日期是否相等的方法,程序就会变得更加简洁,这样改良后的程序如下:
class DayComparator3 {
//--- d1和d2的日期相等吗? ---//
static boolean compDay(Day d1, Day d2) {
return d1.getYear() == d2.getYear() &&
d1.getMonth() == d2.getMonth() &&
d1.getDate() == d2.getDate();
}
public static void main(String[] args) {
Scanner stdIn = new Scanner(System.in);
int y, m, d;
System.out.println("请输入日期1。");
System.out.print("年:"); y = stdIn.nextInt();
System.out.print("月:"); m = stdIn.nextInt();
System.out.print("日:"); d = stdIn.nextInt();
Day day1 = new Day(y, m, d);
System.out.println("请输入日期2。");
System.out.print("年:"); y = stdIn.nextInt();
System.out.print("月:"); m = stdIn.nextInt();
System.out.print("日:"); d = stdIn.nextInt();
Day day2 = new Day(y, m, d);
if (compDay(day1, day2))
System.out.println("相等。");
else
System.out.println("不相等。");
}
}
输出:
方法compDay是在类Day的外部声明的,请注意如下3点:
1)接收类类型的参数
形参d1和d2是Day类型的类类型变量,如图所示这两个形参按收的是Day类型实例的引用。因此,d1会引用day1的实例,d2会引用day2的实例。
2)声明中加上了static
由于本方法定义在类Day的外部,因此它不属于各个实例(day1或day2),加上static进行声明的方法就是类方法。
3)无法访问私有字段
由于本方法定义在类Day的外部,因此无法直接访问日期的字段year、month、date。本方法中通过调用getter方法getYear、getMonth、getDate来确认年、月、日的值。
if (compDay(day1, day2))
调用方法compDay ,将dayl实例的引用和day2实例的引用作为实参传递给该方法;if语句中根据方法的boolean型返回值,会分别显示”相等” 或“不相等”。
七、类类型实例的数组
创建一个Day类型实例的数组,程序会创建一个数组, 其元素个数通过键盘输入,并将全部元素的日期都设置为2017年IO月15日, 然后将其显示出来。
// 日期类Day的数组(运行时错误)
import java.util.Scanner;
class DayArrayError {
public static void main(String[] args) {
Scanner stdIn = new Scanner(System.in);
String[] wd = {"日", "一", "二", "三", "四", "五", "六"};
System.out.print("日期个数:");
int n = stdIn.nextInt();
Day[] a = new Day[n]; // 元素个数为n的Day类型数组
for (int i = 0; i < a.length; i++)
a[i].set(2017, 10, 15); // 将全部元素都设置为2017年10月15日
for (int i = 0; i < a.length; i++)
System.out.println("a[" + i + "] = "
+ a[i].getYear() + "年"
+ a[i].getMonth() + "月"
+ a[i].getDate() + "日("
+ wd[a[i].dayOfWeek()] + ")");
}
}
输出:
运行时.,在读入日期个数n之后就会发生运行时错误;
Day[] a = new Day[n]; // 元素个数为n的Day类型数组
上述代码已经为a创建了数组主体,那为什么还会发生错误呢?
元素a[1]是引用Day的类类型变量(遥控器),不是日期的实例(主体) 当然、a[0] 和a[2] 也是如此
数组a并不是日期主体的数组,而是3个遥控器的集合数组。创建数组时,各个元素会被初始化为null;
for (int i = 0; i < a.length; i++)
a[i].set(2017, 10, 15); // 将全部元素都设置为2017年10月15日
上述执行会发生错误,因为此处对无任何引用的空引用a[i]调用了方法set。对于各个日期实例, 在类类型变量之外,还需要再使用new运算符来另外创建; 正确的程序如下所示:
for (int i = 0; i < a.length; i++)
a[i] = new Day(2017, 10, 15);
输出:为了使用类类型实例的数组,必须在创建类类型变量的数组的基础上,再创建各个元素的实例。
将for语句的执行内容展开后如图所示,每次循环时都会创建Day类型实例,并将其初始化为2017年10月15日。然后,使用赋值运符符将创建的实例的引用赋给a[i].。
八、日期类的改进
创建了使用日期类的程序,但该程序中还存在不少的问题:
1)由于构造函数中需要三个int型参数,因此在创建实例时缺乏灵活性。 例如,在创建数组时就不可以 “不直接设性值,而先创建元素,然后再设置值”;
2)不易于构建与某个日期相同的日期实例;
3)不易于判断两个日期是否相等;
4)显示日期时需要3~4行的程序。
修改后代码如下:
public class Day {
private int year; // 年
private int month; // 月
private int date; // 日
//--- 构造函数 ---//
public Day() { set(1, 1, 1); }
public Day(int year) { set(year, 1, 1); }
public Day(int year, int month) { set(year, month, 1); }
public Day(int year, int month, int date) { set(year, month, date); }
public Day(Day d) { set(d.year, d.month, d.date); }
//--- 获取年、月、日 ---//
public int getYear() { return year; } // 年
public int getMonth() { return month; } // 月
public int getDate() { return date; } // 日
//--- 设置年、月、日 ---//
public void setYear(int year) { this.year = year; } // 年
public void setMonth(int month) { this.month = month; } // 月
public void setDate(int date) { this.date = date; } // 日
public void set(int year, int month, int date) { // 年月日
this.year = year; // 年
this.month = month; // 月
this.date = date; // 日
}
//--- 计算星期 ---//
public int dayOfWeek() {
int y = year; // 0 … 星期日
int m = month; // 1 … 星期一
if (m == 1 || m == 2) { // :
y--; // 5 … 星期五
m += 12; // 6 … 星期六
}
return (y + y / 4 - y / 100 + y / 400 + (13 * m + 8) / 5 + date) % 7;
}
//--- 与日期d相等吗 ---//
public boolean equalTo(Day d) {
return year == d.year && month == d.month && date == d.date;
}
//--- 返回字符串表示 ---//
public String toString() {
String[] wd = {"日", "一", "二", "三", "四", "五", "六"};
return String.format("%04d年%02d月%02d日(%s)",
year, month, date, wd[dayOfWeek()]);
}
}
1)public类
类的声明中加上了public。根据有无public, 类的访问属性有如下不同:
2)构造函数
木程序重载了从不接收年、月、日的构造函数到接收全部的构造函数在内的共5种构造函数。
与方法一样, 构造函数也可以重载,提供了多个构造函数后,对于类的使用者来说、构建类实例的选择范围就比较广了。
各个构造函数的初始化如下所示, 不接收参数的项设为1:
所有的构造函数内部都调用了方法set,同一个类中的方法可以使用“方法名(...) " 的形式进行调用;通过重载构造函数, 我们就可以解决前面提到的问题1和问题2。
问题1
将日期设置为1年1月l日的构造函数不接收参数, 因此、之前的类Day中会发生错误的如下代码就可以正常运行了
for (int i = 0; i < a.length; i++)
a [i] = new Day () ;
for (inti = 0; i < a.length; i++)
a[i].set(2017, 10, 15);
由于可以先创建实例` 然后再设置值, 因此可以很灵活地创建数组。
问题2
构造函数的参数d的类型为Day,通过复制接收到的日期d的字段d.year、d.month、d.date的值, 来初始化日期;这个构造函数的动作如图, dayl的字段值会被复制到对应的day2的各个字段中。
问题3 equalTo…判断是否相等的方法
equal To 方法用于判断日期是否相等,将自身的日期和参数d中接收的日期进行比较。如果年、月、日都相等, 则返回true, 否则返回false。与比较引用目标的相等运算符==不同, equal To方法比较的是所有字段的值是否相等。
问题4 toString… 返回字符串表示的方法
to String方法用来返回日期的字符串表示,返回的字符串中,年的部分是4位,月和日的部分是2位. 形式为"2010年05月04 (五),创建字符串时使用的是String.format方法,可以将其理解成一种将System.out.printf在画面上的输出转换为字符串的方法。
class DayTester {
public static void main(String[] args) {
Scanner stdIn = new Scanner(System.in);
System.out.println("请输入day1。");
System.out.print("年:"); int y = stdIn.nextInt();
System.out.print("月:"); int m = stdIn.nextInt();
System.out.print("日:"); int d = stdIn.nextInt();
Day day1 = new Day(y, m, d); // 读入日期
System.out.println("day1 = " + day1);
Day day2 = new Day(day1); // 与day1相同的日期
System.out.println("创建了与day1的日期相同的day2。");
System.out.println("day2 = " + day2);
if (day1.equalTo(day2))
System.out.println("day1和day2相等。");
else
System.out.println("day1和day2不相等。");
Day d1 = new Day(); // 1年 1月 1日
Day d2 = new Day(2010); // 2010年 1月 1日
Day d3 = new Day(2010, 10); // 2010年10月 1日
Day d4 = new Day(2010, 10, 15); // 2010年10月15日
System.out.println("d1 = " + d1);
System.out.println("d2 = " + d2);
System.out.println("d3 = " + d3);
System.out.println("d4 = " + d4);
Day[] a = new Day[3]; // 元素个数为3的Day类型数组
for (int i = 0; i < a.length; i++)
a[i] = new Day(); // 将全部元素都设置为1年1月1日
for (int i = 0; i < a.length; i++)
System.out.println("a[" + i + "] = " + a[i]);
}
}
输出:
改进终版:
public class Day {
private int year = 1; // 年
private int month = 1; // 月
private int date = 1; // 日
//-- 构造函数 --//
public Day() { }
public Day(int year) { this.year = year; }
public Day(int year, int month) { this(year); this.month = month; }
public Day(int year, int month, int date) { this(year, month); this.date = date; }
public Day(Day d) { this(d.year, d.month, d.date); }
//--- 获取年、月、日 ---//
public int getYear() { return year; } // 获取年
public int getMonth() { return month; } // 获取月
public int getDate() { return date; } // 获取日
//--- 设置年、月、日 ---//
public void setYear(int year) { this.year = year; } // 设置年
public void setMonth(int month) { this.month = month; } // 设置月
public void setDate(int date) { this.date = date; } // 设置日
public void set(int year, int month, int date) { // 设置年月日
this.year = year; // 年
this.month = month; // 月
this.date = date; // 日
}
//--- 计算星期 ---//
public int dayOfWeek() {
int y = year; // 0 … 星期日
int m = month; // 1 … 星期一
if (m == 1 || m == 2) { // :
y--; // 5 … 星期五
m += 12; // 6 … 星期六
}
return (y + y / 4 - y / 100 + y / 400 + (13 * m + 8) / 5 + date) % 7;
}
//--- 与日期d相等吗 ---//
public boolean equalTo(Day d) {
return year == d.year && month == d.month && date == d.date;
}
//--- 返回字符串表示---//
public String toString() {
String[] wd = {"日", "一", "二", "三", "四", "五", "六"};
return String.format("%04d年%02d月%02d日(%s)",
year, month, date, wd[dayOfWeek()]);
}
}