第四章 对象与类
对象构造
重载
默认域初始化
必须明确地初始化方法中的局部变量,但是如果没有初始化类中的域,将会被自动初始化为默认值。
无参数的构造器
仅当类没有提供任何构造器的时候,系统才会提供一个默认的构造器。
显式域初始化
不管怎么调用构造器,每个实例域都可以被设置为一个有意义的初值。
参数名
在参数前面加上前缀“a”
public Employee(String aName, double aSalary)
{
name = aName;
salary = aSalary;
}
用同样的名字将实例域屏蔽起来,可以采用this.的形式访问实例域。this指示隐式参数。
public Employee(String name, double salary)
{
this.name = name;
this.salary = salary;
}
调用另一个构造器
构造器第一个语句形如this(···),这个构造器将调用同一个类的另一个构造器。
初始化块
首先运行初始化块。
java.util.Random
- Random() 构造一个新的随机数生成器
- int nextInt(int n) 返回一个0~n-1之间的随机数
对象析构与finalize方法
Java有自动的垃圾回收器,Java不支持析构器。
可以为任何一个类调用finalize方法,finalize方法将在垃圾回收器清除对象之前调用。
包
所有标准的Java包都处于java和javax包层次中。
使用包确保类名的唯一性。
嵌套的包之间没有任何关系。每一个都拥有独立的类集合。
类的导入
一个类可以使用所属包中的所有类,以及其他包中的公有类。
与包机制类似的是命名空间。
静态导入
可以使用System类的静态方法和静态域
import static java.lang.System.*;
将类放入包中
包作用域
标记为public的部分可以被任意的类使用;标记为private的部分只能被定义它们的类使用。如果没有指定,这个部分可以被同一个包中的所有方法访问。
包路径
设置类路径
文档注释
javadoc可以由源文件生成一个html文件。
注释的插入
编写注释
- 包
- 公有类与接口
- 公有的和受保护的构造器及方法
- 公有的和受保护的域
注释以/**开始,*/结束。
类注释
import语句之后,类定义之前。
方法注释
@param变量描述
@return描述
@throws类描述,用于表示这个方法有可能抛出异常
域注释
对公有域建立文档
通用注释
@author姓名,可使用多个@author标记
@version文本(版本)
@since文本(始于)
@deprecated文本,添加一个不再使用的注释
@see引用
包与概述注释
产生包注释,需要在每一个包目录中添加一个单独的文件。
注释的抽取
类设计技巧
1、一定要保证数据私有
2、一定要对数据初始化
3、不要在类中使用过多的基本类型
4、不是所有的域都需要独立的域访问器和域更改器
5、将职责过多的类进行分解
6、类名和方法名要能够体现它们的职责。命名类名采用一个名词、前面有形容词修饰的名词或动名词。
7、优先使用不可变的类
第五章 继承
类、超类和子类
定义子类
关键字extends表明正在构造的新类派生于一个已存在的类。已存在的类为超类、基类或父类。新类称为子类、派生类或孩子类。
覆盖方法
public double getSalary()
{
double baseSalary = super.getSalary();
return baseSalary;
}
super不像this是一个对象的引用,不能将super赋给另一个对象变量。super只是一个指示编译器调用超类方法的特殊关键字。
子类构造器
由于子类的构造器不能访问超类的私有域,则必须利用超类的构造器对这部分私有域进行初始化,可以通过super实现对超类构造器的调用。
使用super调用构造器的语句必须是子类构造器的第一条语句。
如果子类的构造器没有显式地调用超类的构造器,则将自动地调用超类默认的构造器。
如果超类没有不带参数的构造器,并且在子类的构造其中又没有显式地调用超类的其他构造器,则Java编译器将报告错误。
this用途:
- 引用隐式参数
- 调用该类其他的构造器
super用途: - 调用超类的方法
- 调用超类的构造器
继承层次
由一个公共超类派生出来的所有类的集合被称为继承层次。
从某个特定的类到其祖先的路径被称为该类的继承链。
Java不支持多继承。
多态
对象变量是多态的,一个超类对象既可以引用一个超类对象,也可以引用一个任何子类的对象。
理解方法调用
阻止继承:final类和方法
不允许扩展的类被称为final类。
类的特定方法声明为final,子类也不能覆盖这个方法。
如果将一个类声明为final,只有其中的方法自动成为final,不包括域。
如果一个方法没有被覆盖并且很短,编译器就能够对它进行优化处理,这个过程称为内联。
强制类型转换
将一个超类的引用赋给一个子类变量,必须进行类型转换。使用instanceof操作符查看是否能够成功转换。
注意事项:
- 只能在继承层次内进行类型转换
- 在超类转换成子类之前,使用instanceof进行检查
尽量少使用类型转换和instanceof运算符。
抽象类
使用abstract关键字就不需要实现方法。
包含一个或多个抽象方法的类本身必须被声明为抽象的。
抽象类还可以包含具体数据和具体方法。
类即使不含抽象方法,也可以将类声明为抽象类。
抽象类不能被实例化。
可以定义一个抽象类的对象变量,但是它只能引用非抽象子类的对象。
Person p = new Student("Vince Wu", "Economics");
Person是抽象类,Student是非抽象子类。
受保护访问
超类中某些方法允许被子类访问,或允许子类的方法访问超类的某个域,需要将这些方法或域声明为protected。
Java中受保护部分对所有子类及同一个包中的所有其他类都可见。
4个访问修饰符:
- 仅对本类可见–private
- 对所有类可见–public
- 对本包和所有子类可见–protected
- 对本包可见–默认,不需要修饰符
Object:所有类的超类
Java中每个类都由Object扩展而来。
Java中,只有基本类型不是对象。
equals方法
相等测试与继承
hashCode方法
散列码是由对象导出的一个整型值。散列码是没有规律的。
每个对象都有一个默认的散列码,其值为对象的存储地址。
equals与hashCode的定义必须一致。
toString方法
toString方法用于返回表示对象值的字符串。
public String toString()
{
return getClass().getName()
+ "[name=" + name
+ ",salary=" + salary
+ ",hireDay=" + hireDay
+ "]";
}
使用toString方法的主要原因是:只要对象与一个字符串通过操作符“+”连接起来,Java编译就会自动调用toString方法。
Object定义的toString方法,用来打印输出对象所属的类名和散列码。
java.lang.Object
- Class getClass() 返回包含对象信息的类对象
- String toString() 返回描述该对象值的字符串。自定义类应该覆盖这个方法。
java.lang.Class
- String getName() 返回这个类的名字
- Class getSuperclass() 以Class对象的形式返回这个类的超类信息
泛型数组列表
ArrayList是一个采用类型参数的泛型类。
ArrayList<Employee> staff = new ArrayList<Employee>();
ArrayList<Employee> staff = new ArrayList<>();
如果清楚可能存储的元素数量,可以填充数组之前调用ensureCapacity方法。
staff.ensureCapacity(100);
也可以把初始容量传给ArrayList构造器
ArrayList<Employee> staff = new ArrayList<>(100);
size方法将返回数组列表中包含的实际元素数目。staff.size()相当于数组的a.length
能够确认数组列表的大小不再变化,就调用trimToSize方法。垃圾回收器将回收多余的存储空间。
java.util.ArrayList
- ArrayList() 构造一个空数组列表
- ArrayList(int initialCapacity) 用指定容量构造一个空数组列表
- boolean add(E obj) 在数组列表的尾端添加一个元素,永远返回true
- int size() 返回数组列表中的当前元素数量
- void ensureCapacity(int capacity) 确保数组列表能够保存给定数量的元素
- void trimToSize() 将数组列表的存储容量削减到当前尺寸
访问数组列表元素
使用get和set实现访问或改变数组元素的操作。
设置第i个元素
staff.set(i, harry);
获取数组列表的第i个元素
Employee e = staff.get(i);
原始的ArrayList类提供的get方法只能返回Object,所以get方法的调用者必须对返回值进行类型转换。(以前的版本)
Employee e = (Employee) staff.get(i);
既可以灵活扩展数组又可以方便地访问数组元素的方法
- 首先,创建一个数组,添加所有元素。
ArrayList<X> list = new ArrayList<>();
while(···)
{
x = ···;
list.add(x);
}
- 使用toArray方法将数组元素拷贝到一个数组中
X[] a = new X[list.size()];
list.toArray(a);
java.util.ArrayList
- void set(int index, E obj) 设置指定位置的元素值
- E get(int index) 获得指定位置的元素值
- void add(int index, E obj) 向后移动元素,插入元素
- E remove(int index) 删除一个元素,后面元素前移,返回被删除的元素
类型化与原始数组列表的兼容性
对象包装器与自动装箱
对象包装器类:Integer\Long\Float\Double\Short\Byte\Character\Void\Boolean
对象包装器类是不可变的,也不能定义子类。
定义整型数组列表,尖括号中的类型参数不允许是基本类型
ArrayList<Integer> list = new ArrayList<>();
由于每个值分别包装在对象中,ArrayList的效率远远低于int[]数组。
自动装箱:list.add(3)自动变换成list.add(Integer.valueOf(3))
自动拆箱:int n = list.get(i)自动变换成int n = list.get(i).intValue()
Integer对象是不可变的
java.lang.Integer
- int intValue() 以int形式返回Integer对象的值
- static String toString(int i) 以一个新的String对象的形式返回给定数值的十进制表示
- static int parseInt(String s) 返回字符串s表示的整型数值(十进制整数)
- static int parseInt(String s, int radix) 返回字符串s表示的整型数值(radix参数进制的整数)
java.text.NumberFormat
- Number parse(String s) 返回数字值
参数数量可变的方法
允许将一个数组传递给可变参数方法的最后一个参数
System.out,printf("%d %s", new Object[] {new Integer(1), "widgets"});
枚举类
public enum Size
{
SMALL("S"), MEDIUM("M"), LARGE("L"), EXTRA_LARGE("XL");
private String abbreviation;
private Size(String abbreviation) {this.abbreviation = abbreviation;}
public String getAbbreviation() {return abbreviation;}
}
Size.SMALL.toString()将返回字符串"SMALL"
toString的逆方法是静态方法valueOf
Size s = Enum.valueOf(Size.class, "SMALL");
s设置成Size.SMALL
每个枚举类型都有一个静态的values方法,返回一个保护焊全部枚举值的数组。
Size [] values = Size.values();
ordinal方法返回枚举常量的位置。