在现实生活中,一个对象可能是由很多部分组成的,比如房子这个对象是由许多窗户对象,墙对象等等共同组成的。所以有时需要从一个对象扩展,进而创建出另一个对象。
在程序设计中,有些时候也要对一个对象扩展创建新的对象,比如在大学里你希望创建一个course类,它拥有如下信息:课程的名字,导师的各种信息(姓、名、办公室),所用书本的信息(书名、出版社、作者)。
在这个例子中,course类涉及到了导师和书本这两个对象。当然,我们可以把这两个对象的所有消息都储存在course类的字段当中,但是这并不是一个好的习惯。好的原则是分离这些项,使他们有自己的类,比如导师有自己的instructor类,书本有textbook类,下面以instructor类为例。
/**
This class stores data about an instructor.
*/
public class Instructor
{
private String lastName; // Last name
private String firstName; // First name
private String officeNumber; // Office number
/**
This constructor initializes the last name,
first name, and office number.
@param lname The instructor's last name.
@param fname The instructor's first name.
@param office The office number.
*/
public Instructor(String lname, String fname,
String office)
{
lastName = lname;
firstName = fname;
officeNumber = office;
}
/**
The copy constructor initializes the object
as a copy of another Instructor object.
@param object2 The object to copy.
*/
public Instructor(Instructor object2)
{
lastName = object2.lastName;
firstName = object2.firstName;
officeNumber = object2.officeNumber;
}
/**
The set method sets a value for each field.
@param lname The instructor's last name.
@param fname The instructor's first name.
@param office The office number.
*/
public void set(String lname, String fname,
String office)
{
lastName = lname;
firstName = fname;
officeNumber = office;
}
/**
toString method
@return A string containing the instructor
information.
*/
public String toString()
{
// Create a string representing the object.
String str = "Last Name: " + lastName +
"\nFirst Name: " + firstName +
"\nOffice Number: " + officeNumber;
// Return the string.
return str;
}
}
textbook类和instructor类基本上一样:
/**
This class stores data about a course.
*/
public class Course
{
private String courseName; // Name of the course
private Instructor instructor; // The instructor
private TextBook textBook; // The textbook
/**
This constructor initializes the courseName,
instructor, and text fields.
@param name The name of the course.
@param instructor An Instructor object.
@param text A TextBook object.
*/
public Course(String name, Instructor instr,
TextBook text)
{
// Assign the courseName.
courseName = name;
// Create a new Instructor object, passing
// instr as an argument to the copy constructor.
instructor = new Instructor(instr);
// Create a new TextBook object, passing
// text as an argument to the copy constructor.
textBook = new TextBook(text);
}
/**
getName method
@return The name of the course.
*/
public String getName()
{
return courseName;
}
/**
getInstructor method
@return A reference to a copy of this course's
Instructor object.
*/
public Instructor getInstructor()
{
// Return a copy of the instructor object.
return new Instructor(instructor);
}
/**
getTextBook method
@return A reference to a copy of this course's
TextBook object.
*/
public TextBook getTextBook()
{
// Return a copy of the textBook object.
return new TextBook(textBook);
}
/**
toString method
@return A string containing the course information.
*/
public String toString()
{
// Create a string representing the object.
String str = "Course name: " + courseName +
"\nInstructor Information:\n" +
instructor +
"\nTextbook Information:\n" +
textBook;
// Return the string.
return str;
}
}
/**
This program demonstrates the Course class.
*/
public class CourseDemo
{
public static void main(String[] args)
{
// Create an Instructor object.
Instructor myInstructor =
new Instructor("Kramer", "Shawn", "RH3010");
// Create a TextBook object.
TextBook myTextBook =
new TextBook("Starting Out with Java",
"Gaddis", "Pearson");
// Create a Course object.
Course myCourse =
new Course("Intro to Java", myInstructor,
myTextBook);
// Display the course information.
System.out.println(myCourse);
}
}
主函数先创建了一个instructor对象和textbook对象,初始化这两个对象,然后创建了course对象,将之前创建的instructor类型的实例和textbook类型的实例当做参数传入。实际上相当于分级进行初始化,更具层次。
靠近course类的那个菱形符号表示聚合,靠近谁就表示谁是聚合的整体。
聚合显然是一种相当复杂的关系,所以可能出现安全问题,使得类之外的代码可以篡改类里的私有数据,要注意以下两点:
public Course(String name, Instructor instr,
TextBook text)
{
// Assign the courseName.
courseName = name;
// Create a new Instructor object, passing
// instr as an argument to the copy constructor.
instructor = new Instructor(instr);
// Create a new TextBook object, passing
// text as an argument to the copy constructor.
textBook = new TextBook(text);
}
为什么进行深复制呢?因为course类的构造器是public的,所以可以在course类之外使用course类的构造器,如果在course类之外使用了course类的构造器,在主函数里也使用了course类的构造器,在主函数中初始化的course类的值就有可能被course类之外所使用的course类构造器所覆盖,这显然不是我们希望发生的。注意,使用深复制不是禁止course类之外使用构造器(如果希望禁止在类外使用构造器应该在构造器前使用private) ,而是保证即使主函数外使用了course类构造器时,主函数中初始化的数据也不会被覆盖。
那为什么string类的数据就不需要进行深复制呢?因为string类型非常特殊,虽然不属于基础数据类型,虽然传参的时候和其他非基础数据类型一样也传递的是地址,但是string类型本身是不可变的。比如一个内存中储存了string=“I love you”,这个内存中的数据就是不可改变的了。而同样不属于基础数据类型的textbook类就是可变的,比如textbook类有字段page,page有一个自己的内存,储存的值是一个int 10 ,这个内存还可以被改成任意的int类型的值。
所以在本例中,“Even if variables outside the Course class reference the same object that courseName references, the object cannot be changed.”
而如果聚合的时候传参是基本数据类型,正如我们之前所说的,基础数据类型会先进行复制,再传入构造器,在构造器中做的任何改变都只是改变复制过后的值,并不影响原来的值。比如原来的参数a=2储存在内存001中,传参的时候会将2复制一份储存在内存002中,在course类之外你只会改变002的值,并不会改变001中储存的a=2.
public Instructor getInstructor()
{
// Return a copy of the instructor object.
return new Instructor(instructor);
}
如果我们将程序这样写:
// Bad method
public Instructor getInstructor()
{
// Return a reference to the instructor object.
return instructor; // WRONG! Causes a security hole.
}
这个程序返回了一个对instructor类的引用,这意味着如果在course类之外调用这个方法的时候可以直接访问这个instructor对象,进而进行修改,这显然不是我们希望的,所以我们返回一个和原来instructor对象相同的对象,当从course类调用这个方法的时候,只会返回这个复制过的对象,并不会改变原来对象的值。
默认情况下,一个实例的所有字段会默认地设置为null,如果希望对这些设置为null的字段或变量执行操作,将不会通过编译。比如这个例子:
/**
This class stores a person's first, last, and middle
names. The class is dangerous because it does not
prevent operations on null reference fields.
*/
public class FullName
{
private String lastName; // Last name
private String firstName; // First name
private String middleName; // Middle name
/**
The setLastName method sets the lastName field.
@param str The String to set lastName to.
*/
public void setLastName(String str)
{
lastName = str;
}
/**
The setFirstName method sets the firstName field.
@param str The String to set firstName to.
*/
public void setFirstName(String str)
{
firstName = str;
}
/**
The setMiddleName method sets the middleName field.
@param str The String to set middleName to.
*/
public void setMiddleName(String str)
{
middleName = str;
}
/**
The getLength method returns the length of the
full name.
@return The length.
*/
public int getLength()
{
return lastName.length() + firstName.length()
+ middleName.length();
}
/**
The toString method returns the full name.
@return A reference to a String.
*/
public String toString()
{
return firstName + " " + middleName + " "
+ lastName;
}
}
这个例子中实例有三个string字段,由于没有编写构造器,这三个字段会默认设置为null,当调用getLength方法时,如果没有初始化三个string字段中的任意一个,就会报错,解决方法如下:
public int getLength()
{
int len = 0;
if (lastName != null)
len += lastName.length();
if (firstName != null)
len += firstName.length();
if (middleName != null)
len += middleName.length();
return len;
}
在getLength方法里,保证调用时对象不为null。
或者在构造类的时候,加上自己的构造器。当你为类编写构造器后,默认构造器就没办法再使用,类中的字段也就不可为null了。