主要内容
- 面向对象设计
- 如果创建标准Java类库中的类对象
- 如果编写自己的类
4.1_面向对象程序设计概述
4.1.1_类
- 类是构造对象的模板。
- 封装(encapsulation,数据隐藏):将数据和行为组合在一个包中,对对象的使用者隐藏了数据的实现方式。
- 实例域(instance field):对象中的数据。
- 方法:操纵数据的过程。
- 对象的状态:实例(对象)的实例域值。
- 超类 Object:所有类的父类。
- 继承:扩展一个类建立另一个类的过程。
4.1.2_对象
对象的三大特征
- 行为:对象可被施加的方法。
- 状态:对象如何响应被施加的方法。
- 标识:识别具有相同行为和状态的对象。
4.1.4_类之间的关系
- 依赖(uses-a):
- 聚合(has-a):类A对象包含一些类B对象。
- 继承(is-a):
4.2_使用预定义类
4.2.1_对象与对象变量
- 一个对象变量并没有实际包含一个对象,而是引用一个对象;
- 任何对象变量的值都是对存储在另一个地方的一个对象的引用,实际的对象在堆内存中;
- 当一个对象包含另一个对象变量时,这个变量依然包含着指向另一个堆对象的指针。
4.3_用户自定义类
4.3.3_剖析Employee类
public修饰实例域允许程序中的任何方法对其进行修改和读取,破坏了类的封装。
4.3.4_从构造器开始
- 所有的Java对象都是在堆中构造的,构造器伴随着
new操作符一起使用。 - 在构造器中定义与实例域重名的局部变量会屏蔽同名的实例域。
public Employee(String n, double s, ...) {
String name = n; // Error
double salary = s; // Error
}
4.3.5_隐式参数与显示参数
4.3.6_封装的优点
警告:不要编写返回引用可变对象的访问器方法,比如
class Employee {
private Date hireDay;
public Date getHireDay() {
return hireDay; // Bad
}
}
Date对象是可变的,如果需要返回一个可变对象的引用,首先应对它进行clone。为什么?
- 因为当外部调用了
getHireDay()这个方法后,生成了另一个对于hireDay所引用对象的引用,在新的引用中调用Date类中的方法,同样会改变当前实例中hireDay的值。 clone:存放在另一个位置的对象副本。
class Employee {
private Date hireDay;
public Date getHireDay() {
return (Date)hireDay.clone; // Ok
}
}
4.3.7_基于类的访问权限
方法可访问所属类的所有对象的私有数据。
- 某个方法出现这样的语句
if(harry.equals(boss)):这是正确的,因为 boss 是 Employee 类对象,Employee 类的方法可访问 Employee 类的任何一个对象的私有域。
4.3.8_私有方法
4.3.9_final 实例域
class Employee {
private final StringBuilder sb;
public void f() {
sb.append("modifid"); // Ok
}
}
final关键字只是表示存储在sb变量中的对象引用不会再指向其他StringBuilder对象,不过这个对象还可以修改。
4.4_静态与与静态方法
4.4.1_静态域
class Employee {
private static int nextId = 1;
private int id;
}
- 类的所有势力共享nextId
- 每个类的对象独有自己的id域
4.4.2_静态常量
本地方法(`native`)可绕过Java语言的存取控制机制修改final域,比如`System.out`就是`final`变量
System.setOut(...)
4.3.3_静态方法
静态方法是一种不能向对象实施操作的方法!!!
class Employee {
private static int nextId = 1;
private int id;
public static int getNextId() {
return nextId; // Ok
}
public static int getId() {
return id; // Bad
}
}
- 可以认为静态方法是没有隐含参数
this的方法。
4.4.4_工厂方法
class App {
public static void main(String[] args) {
// construct objects here
}
}
main 方法不对任何对象进行操作。
4.5_方法参数
Java采用按值调用。当参数有2种类型:
- 基本数据类型:值传递不可改变原值,因为先拷贝再处理。
- 对象引用:引用拷贝使用的是浅拷贝,即两个引用指向同一个内存地址,因此修改引用状态影响到双方。
public static void tripleSalary(Employee e) {
x.raiseSalary(200);
}
当调用
harry = new Employee(...)
tripleSalary(harry);
- e 被初始化为 harry 值的引用。
- raiseSalary 方法作用于该对象。
- harry 继续引用该被操作的对象。
错误警告:认为Java对对象采用的是引用调用是错误的。反例如下:
public static void swap(Employee x, Employee y) {
Employee t = x;
x = y;
y = t;
}
...
Employee a = new Employee("A");
Employee b = new Employee("B");
swap(a,b);
// x引用A,y引用B
Employee t = x;
x = y;
y = t;
// 现x引用B,y引用A
方法结束时,x、y都丢弃了,原来的a、b还是各自引用A、B。
Java 中方法参数的使用总结
- 方法不能修改一个基本数据类型的参数。
- 方法可改变一个对象参数的状态。
- 方法不能让对象参数引用一个新的对象。
4.6_对象构造
4.6.1_重载
方法重载的条件:
- 形参数量不同。
- 形参顺序不同。
- 形参类型不同
4.6.2_默认域初始化
如果在构造器中没有显式地给域赋予初值,那么就会被自动地赋为默认值
- 数值为 0
- 布尔值为 false
- 对象引用为 null
4.6.3_无参数构造器
如果类中至少有一个构造器,但是没有提供无参数构造器,在构造对象时,如果不提供参数,则报错!!! - 请记住:仅当类没有提供任何构造器时,系统才会提供一个默认的无参构造器。
4.6.5_参数名
4.6.6_调用另一个构造器
下面是调 用构造器的具体处理步骤:
- 所有数据域被初始化为默认值(0、false 或 null)。
- 按照在类声明中出现的次序, 依次执行所有域初始化语句和初始化块。
- 如果构造器第一行调用了第二个构造器,则执行第二个构造器主体.
- 执行这个构造器的主体.
class Employee {
private static int nextld;
private int id;
private String name;
private double salary;
// object initialization block
{
id = nextld;
nextld++;
}
public Employee(String n, double s) {
name = n; salary = s;
}
public Employee() { name = ""; salary = 0; }
}
Random():构造一个新的随机数生成器。int nextlnt(int n):返回一个 0 ~ n-1 之间的随机数。
4.6.8_对象析构与 finalize 方法
- 可以为任何一个类添加
finalize方法。finalize 方法将在垃圾回收器清除对象之前调用。 - 在实际应用中,不要依赖于使用 finalize 方法回收任何短缺的资源, 这是因为很难知道这个 方法什么时候才能够调用。
4.7_包
Java 允许使用包 package 将类组织起来。借助于包可以方便地组织自己的代码,并将自己的代码与别人提供的代码库分开管理。
4.7.1_类的导入
方式一
在每个类名之前添加完整的包名。 例如:
java.tiie.LocalDate today = java.tine.LocalDate.now();
方式二
使用 import 语句导人一个特定的类或者整个包。
import java.util.*;
4.7.2_静态导入
import 语句不仅可以导人类,还增加了导人静态方法和静态域。
import static java.lang.System.*;:就可以使用 System 类的静态方法和静态域,而不必加类名前缀:
out.println("Goodbye, World!");exit(0);- 导入Math包:
sqrt(pow(x, 2) + pow(y, 2))
4.7.3_将类放入包中
package com.horstiann.corejava;
public class Employee {}
4.7.4_包作用域
- 标记为 public 的部分可以被任意的类使用;
- 标记为 private 的部分只能被定义它们的类使用。
- 如果没有指定 public 或 private, 这个部分(类、方法或变量)可以被同一个包中的所有方法访问。
4.10_类设计技巧
- 保证数据私有。
- 一定要对数据初始化。
- Java 不对局部变量进行初始化,但是会对对象的实例域进行初始化
- 不要在类中使用过多的基本类型。
- 例如, 用一个称为
Address的新的类替换一个Customer类中以下的实例域,这样,可以很容易处理地址的变化,例如,需要增加对国际地址的处理。private String street; private String city; private String state; private int zip;
- 例如, 用一个称为
- 将职责过多的类进行分解。
public class CardDeck { // bad design private int口 value; private int[] suit; public CardDeck() { . . .} public void shuffle {...} public int getTopValue { . . .} public int getTopSuit { . . . } public void drawO {...} }public class CardDeck { private Card[] cards; public CardDeckO {...} 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 { . . . } } - 类名和方法名要能够体现它们的职责.
- 优先使用不可变的类.