面向对象程序设计
类
A class is the template or blueprint from which objects are made. Think about classes as cookie cutters. Objects are the cookies themselves. When you construct an object from a class, you are said to have created an instance of the class.
As you have seen, all code that you write in Java is inside a class. The standard Java library supplies several thousand classes for such diverse purposes as user interface design, dates and calendars, and network programming. Nonetheless, in Java you still have to create your own classes to describe the objects of your application’s problem domain.
类是构造对象的蓝图,类构造对象的过程被称为创建类的实例。
Encapsulation (sometimes called information hiding) is a key concept in working with objects. Formally, encapsulation is simply combining data and behavior in one package and hiding the implementation details from the users of the object. The bits of data in an object are called its instance felds, and the procedures that operate on the data are called its methods.
A specifc object that is an instance of a class will have specifc values of its instance felds. The set of those values is the current state of the object. Whenever you invoke a method on an object, its state may change.
封装(数据隐藏)是将数据和行为组合在一个包里面,并对对象的使用者隐藏了数据的实现方式。对象中的数据称为实例域,操纵数据的过程称为方法。每个特定实例(对象)都有一组特定的实例域值,这些值的集合称为方法。
The key to making encapsulation work is to have methods never directly access instance felds in a class other than their own. Programs should interact with object data only through the object’s methods. Encapsulation is the way to give an object its “black box” behavior, which is the key to reuse and reliability. This means a class may totally change how it stores its data, but as long as it continues to use the same methods to manipulate the data, no other object will know or care.
绝对不能让类的方法直接访问其他类的实例域,程序仅通过对象的方法对数据进行交互。
To work with OOP, you should be able to identify three key characteristics of objects:
- The object’s behavior—what can you do with this object, or what methods can you apply to it?
- The object’s state—how does the object react when you invoke those methods?
- The object’s identity—how is the object distinguished from others that may have the same behavior and state?
对象的三个主要特征:
- 行为:可以施加的操作或方法
- 状态:施加方法后,对象如何反应
- 标识:辨别具有相同行为和状态的不同对象
A simple rule of thumb in identifying classes is to look for nouns in the problem analysis. Methods, on the other hand, correspond to verbs.
类在问题中是名词,方法是动词。
类之间的关系
The most common relationships between classes are
- Dependence (“uses–a”)
- Aggregation (“has–a”)
- Inheritance (“is–a”)
类与类的关系有三种:
- 依赖:类需要使用被依赖类的数据
- 聚合:类包含某类的对象
-
继承:某类具有被继承类的属性
预设定类
There is an important difference between objects and object variables. For example, the statement
Date deadline; // deadline doesn't refer to any object
defnes an object variable, deadline, that can refer to objects of type Date. It is important to realize that the variable deadline is not an object and, in fact, does not even refer to an object yet. You cannot use any Date methods on this variable at this time. The statement
s = deadline.toString(); // not yet
would cause a compile-time error.
You must frst initialize the deadline variable. You have two choices. Of course, you can initialize the variable with a newly constructed object:
deadline = new Date();
Or you can set the variable to refer to an existing object:
deadline = birthday;
It is important to realize that an object variable doesn’t actually contain an object. It only refers to an object.
一个对象变量并没有实际包含一个对象,而仅仅引用了一个对象。
In Java, the value of any object variable is a reference to an object that is stored elsewhere. The return value of the new operator is also a reference. A statement such as
Date deadline = new Date();
has two parts. The expression new Date() makes an object of type Date, and its value is a reference to that newly created object. That reference is then stored in the deadline variable.
You can explicitly set an object variable to null to indicate that it currently refers to no object.
deadline = null;
. . .
if (deadline != null)
System.out.println(deadline);
If you apply a method to a variable that holds null, a runtime error occurs.
birthday = null;
String s = birthday.toString(); // runtime error!
Local variables are not automatically initialized to null. You must initialize them, either by calling new or by setting them to null.
局部变量不会自动地初始化为null,必须通过调用new或将其设置为null
用户自定义类
Be careful not to introduce local variables with the same names as the instance felds. For example, the following constructor will not set the salary:
public Employee(String n, double s, . . .)
{
String name = n; // Error
double salary = s; // Error
. . .
}
The constructor declares local variables name and salary. These variables are only accessible inside the constructor. They shadow the instance felds with the same name. Some programmers accidentally write this kind of code when they type faster than they think, because their fngers are used to adding the data type. This is a nasty error that can be hard to track down.You just have to be careful in all of your methods to not use variable names that equal the names of instance felds.
不要在构造器中定义与实例域重名的局部变量。
隐式参数和显式参数
Methods operate on objects and access their instance felds. For example, the method
方法用于操作对象以及存取它们的实例域。
public void raiseSalary(double byPercent)
{
double raise = salary * byPercent / 100;
salary += raise;
}
sets a new value for the salary instance feld in the object on which this method is invoked. Consider the call
number007.raiseSalary(5);
The effect is to increase the value of the number007.salaryfeld by 5%. More specifcally, the call executes the following instructions:
double raise = number007.salary * 5 / 100;
number007.salary += raise;
The raiseSalary method has two parameters. The frst parameter, called the implicit parameter, is the object of type Employee that appears before the method name. The second parameter, the number inside the parentheses after the method name, is an explicit parameter. (Some people call the implicit parameter the target or receiver of the method call.)
As you can see, the explicit parameters are explicitly listed in the method declaration, for example, double byPercent. The implicit parameter does not appear in the method declaration.
隐式参数出现在方法名前的Employee类对象,显式参数是方法名后面括号中的数值。
注意:参数是对于方法而言的,变量是针对类而言的。
In every method, the keyword this refers to the implicit parameter. If you like, you can write the raiseSalary method as follows:
this表示隐式参数。
public void raiseSalary(double byPercent)
{
double raise = this.salary * byPercent / 100;
this.salary += raise;
}
Some programmers prefer that style because it clearly distinguishes between instance felds and local variables.
Sometimes, it happens that you want to get and set the value of an instance feld.
Then you need to supply three items:
- A private data feld;
- A public feld accessor method; and
- A public feld mutator method.
获取或设置实例域的值,需要提供三个东西:
- 私有的数据域
- 公有的域访问器方法
- 公有的域更改器方法
Be careful not to write accessor methods that return references to mutable objects.
最好不要编写返回引用为可变对象的访问器方法。
In a previous edition of this book, we violated that rule in our Employee class in which the getHireDay method returned an object of class Date:
class Employee
{
private Date hireDay;
. . .
public Date getHireDay()
{
return hireDay; // Bad
}
. . .
}
Unlike the LocalDate class, which has no mutator methods, the Date class has a mutator method, setTime, where you can set the number of milliseconds.
The fact that Date objects are mutable breaks encapsulation! Consider the following rogue code:
Employee harry = . . .;
Date d = harry.getHireDay();
double tenYearsInMilliSeconds = 10 * 365.25 * 24 * 60 * 60 * 1000;
d.setTime(d.getTime() - (long) tenYearsInMilliSeconds);
// let's give Harry ten years of added seniority
The reason is subtle. Both d and harry.hireDay refer to the same object (see Figure 4.2). Applying mutator methods to d automatically changes the private state of the employee object!
If you need to return a reference to a mutable object, you should clone it frst. A clone is an exact copy of an object stored in a new location.We discuss cloning in detail in Chapter 6. Here is the corrected code:
class Employee
{
. . .
public Date getHireDay()
{
return (Date) hireDay.clone(); // Ok
}
. . .
}
As a rule of thumb, always use clone whenever you need to return a copy of a mutable feld.
需要返回一个可变数据域的拷贝,就使用clone方法。
final实例域
You can defne an instance feld as final. Such a feld must be initialized when the object is constructed. That is, you must guarantee that the feld value has been set after the end of every constructor. Afterwards, the feld may not be modifed again. For example, the name feld of the Employee class may be declared as final because it never changes after the object is constructed—there is no setName method.
class Employee
{
private final String name;
// String 在被赋值之后就不能改变
}
The final modifer is particularly useful for felds whose type is primitive or an immutable class. (A class is immutable if none of its methods ever mutate its objects.)
final通常用来构建不可变的类。
静态域和静态方法
静态域
If you defne a feld as static, then there is only one such feld per class. In contrast, each object has its own copy of all instance felds.
如果将域定义为static,每一个类中只能有一个这样的域,而每个对象对于所有的实例域却都有自己的一份拷贝。
For example, let’s suppose we want to assign a unique identifcation number to each employee. We add an instance feld id and a static feld nextId to the Employee class:
class Employee
{
private static int nextId = 1;
private int id;
. . .
}
Every employee object now has its own id feld, but there is only one nextId feld that is shared among all instances of the class. Let’s put it another way. If there are 1,000 objects of the Employee class, then there are 1,000 instance felds id, one for each object. But there is a single static feld nextId. Even if there are no employee objects, the static feld nextId is present. It belongs to the class, not to any individual object.
在Employee类中有一个静态域nextId
,Employee类中的所有实例都共享者一个静态域nextId
,静态域伴随着类的创建而创建。
Let’s implement a simple method:
public void setId()
{
id = nextId;
nextId++;
}
Suppose you set the employee identifcation number for harry:
harry.setId();
Then, the id feld of harry is set to the current value of the static feld nextId, and the value of the static feld is incremented:
harry.id = Employee.nextId;
Employee.nextId++;
harry的ID被设为静态域nextId
当前的值,并且nextId
自增。
静态常量
Static variables are quite rare. However, static constants are more common. For example, the Math class defnes a static constant:
public class Math
{
. . .
public static final double PI = 3.14159265358979323846;
. . .
}
You can access this constant in your programs as Math.PI
If the keyword static
had been omitted, then PI would have been an instance feld of the Math class. That is, you would need an object of this class to access PI, and every Math object would have its own copy of PI.
静态方法
Static methods are methods that do not operate on objects. For example, the pow method of the Math class is a static method. The expression
Math.pow(x, a)
computes the power xa. It does not use any Math object to carry out its task. In other words, it has no implicit parameter.
You can think of static methods as methods that don’t have a this parameter. (In a nonstatic method, the this parameter refers to the implicit parameter of the method—see Section 4.3.5, “Implicit and Explicit Parameters,” on p. 152.)
A static method of the Employee class cannot access the id instance feld because it does not operate on an object. However, a static method can access a static feld.
Here is an example of such a static method:
public static int getNextId()
{
return nextId; // returns static field
}
To call this method, you supply the name of the class:
int n = Employee.getNextId();
Could you have omitted the keyword static for this method? Yes, but then you would need to have an object reference of type Employee to invoke the method.
静态方法可以直接通过“类名.方法”调用,如果没有static
关键字,则需要通过类对象的引用调用这个方法。
Use static methods in two situations:
- When a method doesn’t need to access the object state because all needed parameters are supplied as explicit parameters (example: Math.pow).
- When a method only needs to access static felds of the class (example: Employee.getNextId).
使用静态类的两种情况:
- 不需要访问对象状态,其所需参数都是通过显式参数提供(例如:Math.pow)
- 只需要访问类的静态域(例如:
Employee.getNextId()
)
Finally, C++ reused the keyword for a third, unrelated, interpretation—to denote variables and functions that belong to a class but not to any particular object of the class. That is the same meaning the keyword has in Java.
static
关键字被解释为:属于类且不属于类对象的变量和函数。
main方法
Note that you can call static methods without having any objects. For example, you never construct any objects of the Math class to call Math.pow.
注意:无需使用对象调用静态方法。
For the same reason, the main method is a static method.
main方法也是静态方法。
public class Application
{
public static void main(String[] args)
{
// construct objects here
. . .
}
}
The main method does not operate on any objects. In fact, when a program starts, there aren’t any objects yet. The static main method executes, and constructs the objects that the program needs.
main方法不对任何对象进行操作。
Every class can have a main method.
每一个类只能有一个main方法。
方法参数
Suppose a method tried to triple the value of a method parameter:
public static void tripleValue(double x) // doesn't work
{
x = 3 * x;
}
Let’s call this method:
double percent = 10;
tripleValue(percent);
However, this does not work. After the method call, the value of percent is still 10.
Here is what happens:
- x is initialized with a copy of the value of percent (that is, 10).
- x is tripled—it is now 30. But percent is still 10 (see Figure 4.6).
- The method ends, and the parameter variable x is no longer in use.
执行过程:
- x被初始化为percent值的一个拷贝(也就是10)
- x被乘以3后等于30.但是percent仍然是10
- 方法结束,参数变量x不再被使用
There are, however, two kinds of method parameters:
- Primitive types (numbers, boolean values)
- Object references
方法参数有两种类型:
- 基本数据类型(数字、布尔值)
- 对象引用
You have seen that it is impossible for a method to change a primitive type parameter. The situation is different for object parameters. You can easily implement a method that triples the salary of an employee:
public static void tripleSalary(Employee x) // works
{
x.raiseSalary(200);
}
When you call
harry = new Employee(. . .);
tripleSalary(harry);
then the following happens:
- x is initialized with a copy of the value of harry, that is, an object reference.
- The raiseSalary method is applied to that object reference. The Employee object to which both x and harry refer gets its salary raised by 200 percent.
- The method ends, and the parameter variable x is no longer in use. Of course, the object variable harry continues to refer to the object whose salary was tripled (see Figure 4.7).
执行过程:
- x被初始化为harry的值的拷贝,是一个对象的引用
-
raiseSalary
方法应用于这个对象引用。x和harry同时引用的那个Employee对象的薪金提高了200% - 方法结束后,参数变量x不再使用,对象变量harry继续引用薪金增至3倍的雇员对象
As you have seen, it is easily possible—and in fact very common—to implement methods that change the state of an object parameter. The reason is simple. The method gets a copy of the object reference, and both the original and the copy refer to the same object.
因为方法得到的是对象引用的拷贝,对象引用及其他的拷贝同时引用同一个对象。
Many programming languages (in particular, C++ and Pascal) have two mechanisms for parameter passing: call by value and call by reference. Some programmers (and unfortunately even some book authors) claim that Java uses call by reference for objects. That is false. As this is such a common misunderstanding, it is worth examining a counterexample in detail.
Let’s try to write a method that swaps two employee objects:
public static void swap(Employee x, Employee y) // doesn't work
{
Employee temp = x;
x = y;
y = temp;
}
If Java used call by reference for objects, this method would work:
Employee a = new Employee("Alice", . . .);
Employee b = new Employee("Bob", . . .);
swap(a, b);
// does a now refer to Bob, b to Alice?
However, the method does not actually change the object references that are stored in the variables a and b. The x and y parameters of the swap method are initialized with copies of these references. The method then proceeds to swap these copies.
// x refers to Alice, y to Bob
Employee temp = x;
x = y;
y = temp;
// now x refers to Bob, y to Alice
But ultimately, this is a wasted effort. When the method ends, the parameter variables x and y are abandoned. The original variables a and b still refer to the same objects as they did before the method call (see Figure 4.5).
This demonstrates that the Java programming language does not use call by reference for objects. Instead, object references are passed by value.
Java程序设计语言对对象的采用的不是引用调用,实际上,对象引用也是按值传递的。
Here is a summary of what you can and cannot do with method parameters in Java:
- A method cannot modify a parameter of a primitive type (that is, numbers or boolean values).
- A method can change the state of an object parameter.
- A method cannot make an object parameter refer to a new object.
Java中方法参数使用情况:
- 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)
- 一个方法可以改变一个对象参数的状态
- 一个方法不能让对象参数引用一个新的对象
对象构造
重载
Java allows you to overload any method—not just constructor methods. Thus, to completely describe a method, you need to specify its name together
with its parameter types.This is called the signature of the method. For example, the String class has four public methods called indexOf. They have signatures
indexOf(int)
indexOf(int, int)
indexOf(String)
indexOf(String, int)
The return type is not part of the method signature. That is, you cannot have two methods with the same names and parameter types but different return types.
方法的签名由两部分组成:
- 方法名
- 参数类型
默认域初始化
If you don’t set a feld explicitly in a constructor, it is automatically set to a default value: numbers to 0, boolean values to false, and object references to null. Some people consider it poor programming practice to rely on the defaults. Certainly, it makes it harder for someone to understand your code if felds are being initialized invisibly.
如果在构造器中没有显式地给域赋予初值,那么域就会被自动赋值:数值为0、布尔值为false、对象引用为null。
无参构造器
Many classes contain a constructor with no arguments that creates an object whose state is set to an appropriate default. For example, here is a constructor with no arguments for the Employee class:
public Employee()
{
name = "";
salary = 0;
hireDay = LocalDate.now();
}
If you write a class with no constructors whatsoever, then a no-argument constructor is provided for you. This constructor sets all the instance felds to their default values. So, all numeric data contained in the instance felds would be 0, all boolean values would be false, and all object variables would be set to null.
If a class supplies at least one constructor but does not supply a no-argument constructor, it is illegal to construct objects without supplying arguments. For example, our original Employee class in Listing 4.2 provided a single constructor:
Employee(String name, double salary, int y, int m, int d)
With that class, it was not legal to construct default employees. That is, the call
e = new Employee(); // Error
would have been an error.
如果类中提供了至少一个构造器,但是没有提供无参构造器,在构造对象时如果没有提供参数就会被视为不合法。
The initialization value doesn’t have to be a constant value. Here is an example in which a feld is initialized with a method call. Consider an Employee class where each employee has an id feld. You can initialize it as follows:
显式域初始化
class Employee
{
private static int nextId; // 初始值为0
private int id = assignId(); // 显式域初始化
. . .
private static int assignId()
{
int r = nextId;
nextId++;
174 Chapter 4 Objects and Classesreturn r;
}
. . .
}
在这里,每一个雇员都有一个id域,调用方法assignId()
进行赋值。
调用另一个构造器
The keyword this refers to the implicit parameter of a method. However, this keyword has a second meaning.
If the frst statement of a constructor has the form this(. . .), then the constructor calls another constructor of the same class. Here is a typical example:
public Employee(double s)
{
// calls Employee(String, double)
this("Employee #" + nextId, s);
nextId++;
}
When you call new Employee(60000), the Employee(double) constructor calls the Employee(String, double) constructor.
Using the this keyword in this manner is useful—you only need to write common construction code once.
There are three ways to initialize a data feld:
• By setting a value in a constructor
• By assigning a value in the declaration
• By setting an initialization block
三种初始化数据的方法:
- 构造器中赋值
- 声明中赋值
- 初始化块
Class declarations can contain arbitrary blocks of code. These blocks are executed whenever an object of that class is constructed. For example:
class Employee
{
private static int nextId;
private int id;
private String name;
private double salary;
// object initialization block
{
id = nextId;
nextId++;
}
public Employee(String n, double s)
{
name = n;
salary = s;
}
public Employee()
{
name = "";
salary = 0;
}
. . .
}
In this example, the id feld is initialized in the object initialization block, no matter which constructor is used to construct an object. The initialization block runs frst, and then the body of the constructor is executed.
This mechanism is never necessary and is not common. It is usually more straightforward to place the initialization code inside a constructor.
With so many ways of initializing data felds, it can be quite confusing to give all possible pathways for the construction process. Here is what happens in detail when a constructor is called:
- All data felds are initialized to their default values (0, false, or null).
- All feld initializers and initialization blocks are executed, in the order in which they occur in the class declaration.
- If the frst line of the constructor calls a second constructor, then the body of the second constructor is executed.
- The body of the constructor is executed.
调用构造器的顺序:
- 数据初始化为默认值
- 按照类声明中出现的次序,依次执行所有域初始化语句和初始化块
- 如果构造器第一行调用了构造器,执行这个构造器
- 执行构造器主体
If the static felds of your class require complex initialization code, use a static initialization block.
Place the code inside a block and tag it with the keyword static. Here is an example.
We want the employee ID numbers to start at a random integer less than 10,000.
// static initialization block
static
{
Random generator = new Random();
nextId = generator.nextInt(10000);
}
复杂的静态域初始化也可以用静态的初始化块。
类设计技巧
Without trying to be comprehensive or tedious, we want to end this chapter with
some hints that will make your classes more acceptable in well-mannered OOP
circles.
- Always keep data private.
保证数据私有
This is frst and foremost; doing anything else violates encapsulation. You may need to write an accessor or mutator method occasionally, but you are still better off keeping the instance felds private. Bitter experience shows that the data representation may change, but how this data are used will change much less frequently. When data are kept private, changes in their representation will not affect the user of the class, and bugs are easier to
detect. - Always initialize data.
对数据进行初始化
Java won’t initialize local variables for you, but it will initialize instance felds of objects. Don’t rely on the defaults, but initialize all variables explicitly, either by supplying a default or by setting defaults in all constructors. - Don’t use too many basic types in a class.
不要在类中使用过多的基本类型
The idea is to replace multiple related uses of basic types with other classes. This keeps your classes easier to understand and to change. For example, replace the following instance felds in a Customer class:
private String street;
private String city;
private String state;
private int zip;
with a new class called Address. This way, you can easily cope with changes to addresses, such as the need to deal with international addresses.
- Not all felds need individual feld accessors and mutators.
不是所有的域都需要独立的域访问器和域更改器
You may need to get and set an employee’s salary. You certainly won’t need to change the hiring date once the object is constructed. And, quite often, objects have instance felds that you don’t want others to get or set, such as an array of state abbreviations in an Address class. - Break up classes that have too many responsibilities.
分解职责过多的类
This hint is, of course, vague: “too many” is obviously in the eye of the beholder. However, if there is an obvious way to break one complicated class into two classes that are conceptually simpler, seize the opportunity. (On the 200 Chapter 4 Objects and Classesother hand, don’t go overboard; ten classes, each with only one method, are usually an overkill.)
Here is an example of a bad design:
public class CardDeck // bad design
{
private int[] value;
private int[] suit;
public CardDeck() { . . . }
public void shuffle() { . . . }
public int getTopValue() { . . . }
public int getTopSuit() { . . . }
public void draw() { . . . }
}
This class really implements two separate concepts: a deck of cards, with its shuffle and draw methods, and a card, with the methods to inspect its value and suit. It makes sense to introduce a Card class that represents an individual card. Now you have two classes, each with its own responsibilities:
public class CardDeck
{
private Card[] cards;
public CardDeck() { . . . }
public void shuffle() { . . . }
public Card getTop() { . . . }
public void draw() { . . . }
}
public class Card
{
private int value;
private int suit;
public Card(int aValue, int aSuit) { . . . }
public int getValue() { . . . }
public int getSuit() { . . . }
}
- Make the names of your classes and methods reflect their responsibilities.
类名和方法需要体现职责
Just as variables should have meaningful names that reflect what they represent, so should classes. (The standard library certainly contains some dubious examples, such as the Date class that describes time.)
A good convention is that a class name should be a noun (Order), or a noun preceded by an adjective (RushOrder) or a gerund (an “-ing” word, like
4.10 Class Design Hints 201BillingAddress). As for methods, follow the standard convention that accessor methods begin with a lowercase get (getSalary) and mutator methods use a lowercase set (setSalary). - Prefer immutable classes
优先使用不可变的类
The LocalDate class, and other classes from the java.time package, are immutable—no method can modify the state of an object. Instead of mutating
objects, methods such as plusDays return new objects with the modifed state.
The problem with mutation is that it can happen concurrently when multiple threads try to update an object at the same time. The results are unpredictable.
When classes are immutable, it is safe to share their objects among multiple threads.
Therefore, it is a good idea to make classes immutable when you can. This is particularly easy with classes that represent values, such as a string or a point in time. Computations can simply yield new values instead of updating existing ones.
Of course, not all classes should be immutable. It would be strange to have the raiseSalary method return a new Employee object when an employee gets a raise.
没看的内容:中P137-144 英P182-193 类路径、文档注释