赞
踩
面向对象程序设计(Object Orientied Programming, OOP) 是当今主流的程序设计范型,它已经取代了 20 世纪 70 年代的 “结构化” 过程化程序设计开发技术。Java 是完全面向对象的,必须熟悉 OOP 才能够编写 Java 程序。
面向对象的程序是由对象组成的,每个对象包含对用户公开的特定功能部分和隐藏的实现部分。程序中的很多对象来自标准库,还有一些是自定义的。究竟是自己构造对象,还是从外界购买对象完全取决于开发项目的预算和时间。但是,从根本上说,只要对象能够满足要求,就不必关心其功能的具体实现过程。在 OOP 中,不必关心对象的具体实现,只要能够满足用户的需求即可。
对于一些规模较小的问题,将其分解为过程的开发方式比较理想。而面向对象更加适用于解决规模较大的问题。要想实现一个简单的 Web 浏览器可能需要大约 2000 个过程,这些过程可能需要对一组全局数据进行操作。采用面向对象的设计风格,可能只需要大约 100 个类,每个类平均包含 20 个方法(如图1所示)后者更易于程序员掌握,也容易找到 bug 假设给定对象的数据出错了,在访问过这个数据项的 20 个方法中查找错误要比在 2000 个过程中查找容易得多。
“类”表示一系列具有相同属性和行为的事物的抽象,例如:人类、书类;而“对象”表示属于某一个“类”的具体的事物,又称为“实例”,例如:小明属于人类、《射雕英雄传》属于书类······
只有先将“类”实例化成“对象”后,才能对“对象”进行操作。在 Java 程序设计语言中,使用构造器 (constructor) 构造新实例。构造器是一种特殊的方法,用来构造并初始化对象,“类”的构造器与其类名相同。
Date birthday = new Date(); // birthday 引用一个 Date 对象
String s = birthday.toString();
Date deadline; // deadline 为 null
deadline = birthday; // deadline 和 birthday 引用同一个对象
在上例中,一共构造了 1 个 Date
对象。第 3 行定义了一个 Date
类的对象变量 deadline
,但它没有引用任何对象,因此为 null
。一般来说,用 new <类名>();
构造新的对象,注意区分对象变量和对象的区别。
对于 Java 已封装的预定义类,按照根据文档说明构造对象即可。
关于日期,标准 Java 类库分别包含了两个类:一个是用来表示时间点的 Date
类;另一个是用来表示大家熟悉的日历表示法的 LocalDate
类。
将时间与日历分开是一种很好的面向对象设计。通常,最好使用不同的类表示不同的概念。
不要使用构造器来构造 LocalDate
类的对象。实际上,应当使用静态工厂方法 (factory method) 代表你调用构造器。
LocalDate.now(); // 根据当前时间构造一个 LocalDate 对象
LocalDate newYearsEve = LocalDate.of(1999, 12, 31); // 构造特定时间的 LocalDate 对象
int year = newYearsEve.getYear(); // 1999
int month = newYearsEve.getMonthValue(); // 12
int day = newYearsEve.getDayOfMonth(); // 31
一旦有 了一个 LocalDate
对象, 可以用方法 getYear
、getMonthValue
和 getDayOfMonth
得到年、月和日。看起来这似乎没有多大的意义,因为这正是构造对象时使用的那些值。不过,有时可能某个日期是计算得到的,你希望调用这些方法来得到更多信息。例如,plusDays
方法会得到一个新的 LocalDate
, 如果把应用这个方法的对象称为当前对象,这个新日期对象则是距当前对象指定天数的一个新日期。
LocalDate aThousandDaysLater = newYearsEve.piusDays(1000):
year = aThousandDaysLater.getYear(); // 2002
month = aThousandDaysLater.getMonthValue(); // 09
day = aThousandDaysLater.getDayOfMonth(); // 26
注意:类库设计者意识到应当单独提供类来处理日历,不过在此之前这些方法已经是 Date
类的一部分了。Java 1.1 中引入较早的一组日历类时,Date
方法被标为废弃不用。虽然
仍然可以在程序中使用这些方法,不过如果这样做,编译时会出现警告。最好还是不要使用这些废弃不用的方法,因为将来的某个类库版本很有可能将它们完全删除。
LocalDate
类常用方法:
LocalDate.now()
:构造一个表示当前日期的对象。LocalDate.of(int year, int month, int day)
:构造一个表示给定日期的对象。LocalDate.getYear()
或 LocalDate.getMonthValue
或 LocalDate.getDayOfMonth
:得到当前日期的年、月和曰。LocalDate.getDayOfWeek()
:得到当前日期是星期几, 作为 DayOfWeek
类的一个实例返回。 调用 getValue
来得到 1 ~ 7 之间的一个数,表示这是星期几,1 表示星期一, 7 表示星期日。LocalDate.plusDays(int n)
或 LocalDate.minusDays(int n)
:生成当前日期之后或之前 n 天的日期。在 Java 中,最简单的类定义形式为:
class ClassName
{
field1;
field2;
......
constructor1;
constructor2;
......
method1;
method2;
......
}
import java.time.*; /** * This program tests the Employee class. * @version 1.12 2015-05-08 * @author Cay Horstmann */ public class EmployeeTest { public static void main(String[] args) { // fill the staff array with three Employee objects Employee[] staff = new Employee[3]; staff[0] = new Employee("Carl Cracker", 75000, 1987, 12, 15); staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1); staff[2] = new Employee("Tony Tester", 40000, 1990, 3, 15); // raise everyone's salary by 5% for (Employee e : staff) e.raiseSalary(5); // print out information about all Employee objects for (Employee e : staff) System.out.println("name=" + e.getName() + ",salary=" + e.getSalary() + ",hireDay=" + e.getHireDay()); } } class Employee { private String name; private double salary; private LocalDate hireDay; public Employee(String n, double s, int year, int month, int day) { name = n; salary = s; hireDay = LocalDate.of(year, month, day); } public String getName() { return name; } public double getSalary() { return salary; } public LocalDate getHireDay() { return hireDay; } public void raiseSalary(double byPercent) { double raise = salary * byPercent / 100; salary += raise; } }
上例中,我们自定义了一个 Employee
类,一共包括 3 个实例域、1 个构造器和 4 个方法,接下来将逐一分析。
public Employee(String n, double s, int year, int month, int day)
{
name = n;
salary = s;
hireDay = LocalDate.of(year, month, day);
}
类的构造器和类名同名,在构造 Employee 类的对象时,构造器会运行,以便将实例域初始化为所希望的状态,并且没有返回值。
构造器与其他的方法有一个重要的不同。构造器总是伴随着 new
操作符的执行被调用,而不能对一个已经存在的对象调用构造器来达到重新设置实例域的目的。
注意:不能在构造器中使用与实例域相同变量名的参数,否则会报错。
public LocalDate getHireDay()
{
return hireDay;
}
public void raiseSalary(double byPercent)
{
double raise = salary * byPercent / 100;
salary += raise;
}
方法用于操作对象以及存取它们的实例域,主要由 5 个部分组成:
public
return
关键字raiseSalary(double byPercent)
返回值类型:方法的返回值类型可以是任意类型,如 int
、double
等,甚至可以是各种类,如 LocalDate
。而关键字 void
表示没有返回值,则方法体内可以不出现 return
,或直接使用 return;
,表示没有任何返回值,方法运行结束。
参数:一个类方法的参数一共有两种:一种是显式参数,另一种是隐式参数。上例中,raiseSalary
的显式参数是double byPercent
,隐式参数是 this
。
每个类方法都会有 this
参数,它引用的是调用该方法的对象本身。好处在于在方法内部,便于访问对象的实例域和其他方法,同时还能将局部变量和对象的实例域明显地区分开。
public
:表示任何类的任何方法都可以调用该方法或者访问该实例域。private
:确保了只有类自身的方法能够访问对象的实例域, 而其他类的方法不能够读写这些域。类的实例域一般设置为 private
,再单独编写访问实例域的各种方法,设置为 public
。这样做的好处是,将类的实例域私有化,不能通过外部任意修改,最大程度地限制了外部对实例域的操作,避免了由外部任意修改实例域而产生的意外的错误,同时也体现了类的封装性的特点。
封装(encapsulation,有时称为数据隐藏)是与对象有关的一个重要概念。从形式上看,封装不过是将数据和行为组合在一个包中,并对对象的使用者隐藏了数据的实现方式。
实现封装的关键在于绝对不能让类中的方法直接地访问其他类的实例域。程序仅通过对象的方法与对象数据进行交互。封装给对象赋予了 “黑盒” 特征,这是提高重用性和可靠性的关键。这意味着一个类可以全面地改变存储数据的方式,只要仍旧使用同样的方法操作数据,其他对象就不会知道或介意所发生的变化。
封装性的好处:
当然,对于一些特定的方法,也同样可以根据需要设置为 private
权限,实现对外部封闭。
注意:不要编写返回引用可变对象的访问器方法。在 Employee
类中就违反了这个设计原则,其中的 getHireDay
方法返回了一个 Date 类对象。
Employee harry = ...;
Date d = harry.getHireDay();
double tenYearsInMilliSeconds = 10 * 365.25 * 24 * 60 * 60 * 1000;
d.setTime(d.getTime() - (long) tenYearsInMilliSeconds);
运行这段程序就会产生问题!因为外部定义的 d
和 harry.getHireDay()
引用的是同一个对象,因此当在外部修改 d
的时候,同时也修改了 harry
的实例域,从而破坏了类的封装性。正确的方式应该是返回该私有实例域对象的克隆(clone
)。
public Date getHireDayO
{
return (Date) hireDay.clone(); // Ok
}
可以将实例域定义为 final
。构建对象时必须初始化这样的域。也就是说,必须确保在每一个构造器执行之后,这个域的值被设置,并且在后面的操作中,不能够再对它进行修改。
注意:final
表示变量引用的对象一旦完成初始化后,该变量不能再引用其他的对象,但是已经引用的变量本身却是可以改变的。
参考资料:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。