使用的教材是java核心技术卷1,我将跟着这本书的章节同时配合视频资源来进行学习基础java知识。

day012  使用预定义类(对象与对象变量、LocalDate类、更改器方法与访问器方法)

在Java中,没有类就无法做任何事情,我们前面曾经接触过几个类。然而,并不是所有的类都具有面向对象特征。例如,Math类。在程序中,可以使用Math类的方法,如Math,random,并只需要知道方法名和参数(如果有的话),而不必了解它的具体实现过程。这正是封装的关键所在,当然所有类都是这样。但遗憾的是,Math类只封装了功能,它不需要也不必隐藏数据。由于没有数据,因此也不必担心生成对象以及初始化实例域。

下面将会给出一个更典型的类—Date类,从中可以看到如何构造对象,以及如何调用类的方法。


1.对象与对象变量

要想使用对象,就必须首先构造对象,并指定其初始状态。然后,对对象应用方法。

在Java程序设计语言中,使用构造器(constructor)构造新实例。构造器是一种特殊的方法,用来构造并初始化对象。下面看一个例子。在标准Java库中包含一个Date类。它的对象将描述一个时间点,例如:“December31,1999,23:59:59GMT”。

构造器的名字应该与类名相同。因此Date类的构造器名为Date。要想构造一个Date对象,需要在构造器前面加上new操作符,如下所示:

new Date()

这个表达式构造了一个新对象。这个对象被初始化为当前的日期和时间。如果需要的话,也可以将这个对象传递给一个方法:

System.out.println(new Date());

或者,也可以将一个方法应用于刚刚创建的对象。Date类中有一个toString方法。这个方法将返回日期的字符串描述。下面的语句可以说明如何将toString方法应用于新构造的Date对象上。

String s = new Date().toString();

在这两个例子中,构造的对象仅使用了一次。通常,希望构造的对象可以多次使用,因此,需要将对象存放在一个变量中:

Date birthday = new Date();

                                                 

Java 对object属性变更_java

一定要认识到:一个对象变量并没有实际包含一个对象,而仅仅引用一个对象。

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

Date deadline = new Date();

有两个部分。表达式newDate()构造了一个Date类型的对象,并且它的值是对新创建对象的引用。这个引用存储在变量deadline中。

可以显式地将对象变量设置为null,表明这个对象变量目前没有引用任何对象。

deadline = null;
...
if(dealline != null)
    System.out.println(deadline);

如果将一个方法应用于一个值为null的对象上,那么就会产生运行时错误。局部变量不会自动地初始化为null,而必须通过调用new或将它们设置为null进行初始化。

birthday = null;
String s = birthday.toString();//retime error!

2.Java类库中的LocalDate类

在前面的例子中,已经使用了Java标准类库中的Date类。Date类的实例有一个状态,即特定的时间点。

但是,Date类所提供的日期处理并没有太大的用途。Java类库的设计者认为:像*'December31,1999,23:59:59"这样的日期表示法只是阳历的固有习惯。这种特定的描述法遵循了世界上大多数地区使用的Gregorian阳历表示法。但是,同一时间点采用中国的农历表示和采用希伯来的阴历表示就很不一样,对于火星历来说就更不可想象了。

类库设计者决定将保存时间与给时间点命名分开。所以标准Java类库分别包含了两个类:一个是用来表示时间点的Date类;另一个是用来表示大家熟悉的日历表示法的LocalDate类。

将时间与日历分开是一种很好的面向对象设计。通常,最好使用不同的类表示不同的概念。

不要使用构造器来构造LocalDate类的对象。实际上,应当使用静态工厂方法(factorymethod)代表你调用构造器。下面的表达式

LocalDate.now()

会构造一个新对象,表示构造这个对象时的日期。可以提供年、月和日来构造对应一个特定日期的对象:

LocalDate.of(1999,12,31)

当然,通常都希望将构造的对象保存在一个对象变量中:

LocalDate newYearsEve = LocalDate.of(1999,12,31);

一旦有了一个LocalDate对象,可以用方法getYear、getMonthValue和getDayOfMonth得到年、月和日:

int year = newYearsEve.getYear(); // 1999 
int month = newYearsEve.getMonthValue(); // 12 
int day = newYearsEve.getDayOfMonth() ; // 31

看起来这似乎没有多大的意义,因为这正是构造对象时使用的那些值。不过,有时可能某个日期是计算得到的,你希望调用这些方法来得到更多信息。例如,plusDays方法会得到一个新的LocalDate,如果把应用这个方法的对象称为当前对象,这个新日期对象则是距当前对象指定天数的一个新日期:

LocalDate aThousandDaysLater = newYearsEve.plusDays(1000); 
year = aThousandDaysLater.getYear();// 2002 
month = aThousandDaysLater.getMonthValue(); // 09 
day = aThousandDaysLater.getDayOfMonth( ) ; // 26

LocalDate类封装了实例域来维护所设置的日期。如果不查看源代码,就不可能知道类内部的日期表示。当然,封装的意义在于,这一点并不重要,重要的是类对外提供的方法。


3.更改器方法与访问器方法

再来看上一节中的plusDays方法调用:

LocalDate aThousandDaysLater = newYearsEve.plusDays ( 1000 ) ;

这个调用之后newYeareEve会有什么变化?它会改为1000天之后的日期吗?事实上,并没有。plusDays方法会生成一个新的LocalDate对象,然后把这个新对象赋给aThousandDaysLater变量。原来的对象不做任何改动。我们说plusDays方法没有更改调用这个方法的对象。

Java库的一个较早版本曾经有另一个类来处理日历,名为GregorianCalendar。可以如下为这个类表示的一个日期增加1000天:

CregorianCalendar someDay = new CregorianCalendar ( 1999, 11, 31 ) ; 
//Odd feature of that class: month numbers go from 0 to 11 
someDay.add ( Calendar.DAY_0F_M0NTH, 1000 ) ;

与LocalDate.plusDays方法不同,GregorianCalendar.add方法是一个更改器方法(mutatormethod)调用这个方法后,someDay对象的状态会改变。可以如下査看新状态:

year = someDay.get ( Calendar.YEAR) ; // 2002 
month = someDay.get ( Calendar.MONTH)+ 1; // 09 
day = someDay.get ( Ca1endar.DAY_0F_M0NTH) ; // 26

正是因为这个原因,我们将变量命名为someDay而不是newYearsEve调用这个更改器方法之后,它不再是新年前夜。

相反,只访问对象而不修改对象的方法有时称为访问器方法例如,LocalDate.getYear和GregorianCalendar.get就是访问器方法。

下面来看一个应用LocalDate类的程序。这个程序将显示当前月的日历,其格式为:

                                            

Java 对object属性变更_Java_02

当前的日用一个*号标记。可以看到,这个程序需要解决如何计算某月份的天数以及一个给定日期相应是星期几。

下面看一下这个程序的关键步骤。首先,构造了一个日历对象,并用当前的日期和时间进行初始化。

LocalDate date = LocalDate.now();

下面获得当前的月和日。

int month = date.getMonthValue();
int today = date.getDayOfMonth();

然后,将date设置为这个月的第一天,并得到这一天为星期几。

date = date.minusDays(today - 1);//Set to start of month
DayOfWeek weekday = date.getDayOfWeek();
int value = weekday.getValue();//1=Monday,....7=Sunday

变量weekday设置为DayOfWeek类型的对象。我们调用这个对象的getValue方法来得到星期几的一个数值。这会得到一个整数,这里遵循国际惯例,即周末是一周的末尾,星期一就返冋1,星期二返回2,依此类推。星期日则返回7。

注意,日历的第一行是缩进的,使得月份的第一天指向相应的星期几。下面的代码会打印表头和第一行的缩进:

System.out.println("Mon Tue Wed Thu Fri Sat Sun");
	for (int i = 1;i < value;i++)
		System.out.print("    ");

现在我们来打印日历的主体。进入一个循环,其中date遍历一个月中的每一天。

每次迭代时,打印日期值。如果date是当前日期,这个日期则用一个*标记。接下来,把date推进到下一天。如果到达新的一周的第一天,则换行打印:

while(date.getMonthValue()==month)
	{
		System.out.printf("%3d", date.getDayOfMonth());
		if (date.getDayOfMonth()==today)
			System.out.print("*");
		else
			System.out.print(" ");
		date = date.plusDays(1);
		if (date.getDayOfWeek().getValue()==1)
			System.out.println();
	}

什么时候结束呢?我们不知道这个月有几天,是31天、30天、29天还是28天。实际上,只要date还在当月就要继续迭代。

下面是这个程序的完整代码:

import java.time.*;

public class CalendarTest 
{
	public static void main(String[] arg)
	{
		LocalDate date = LocalDate.now();
		int month = date.getMonthValue();
		int today = date.getDayOfMonth();
		
		date = date.minusDays(today - 1);//Set to start of month
		DayOfWeek weekday = date.getDayOfWeek();
		int value = weekday.getValue();//1=Monday,....7=Sunday
		
		System.out.println("Mon Tue Wed Thu Fri Sat Sun");
		for (int i = 1;i < value;i++)
			System.out.print("    ");
		while(date.getMonthValue()==month)
		{
			System.out.printf("%3d", date.getDayOfMonth());
			if (date.getDayOfMonth()==today)
				System.out.print("*");
			else
				System.out.print(" ");
			date = date.plusDays(1);
			if (date.getDayOfWeek().getValue()==1)
				System.out.println();
		}
		if (date.getDayOfWeek().getValue()!=1)
			System.out.println();	
	}
}

运行的结果是:(今日是2020年3月29日)

                                

Java 对object属性变更_Java_03

可以看到,利用LocalDate类可以编写一个口历程序,能处理星期几以及各月天数不同等复杂问题。你并不需要知道LocalDate类如何计算月和星期几。只需要使用这个类的接口,如plusDays和getDayOfWeek等方法。

我们看一下API文档中LocalDate类的方法。

  

Java 对object属性变更_Java_04

    

Java 对object属性变更_Java 对object属性变更_05


接下来就要学习用户自定义类了。