类的继承
1、概念和语法
概念
- 根据已有类来设计新类,新类具有已有类的所有功能(属性和行为)。
- Java中只支持类的单继承,每个子类只能拥有一个直接超类。
- 超类是所有子类的公共属性和方法的集合;子类是超类的特殊化。
- 继承机制可以提高程序的抽象程度,提高代码的可重用性。
超类和子类
- 子类对象和超类对象的关系:是一种(个)……
- 子类对象
- 从外部看它应该包括
- 与超类相同的接口
- 可以就有更多的方法和数据成员
- 其内部包含着超类的所有属性和方法
- 从外部看它应该包括
语法
[ClassModifier] class ClassName extends SuperClassName { //类体 }
子类不能直接访问从超类继承来的私有属性和方法,但可以使用公有(或保护)方法来访问(如果超类定义了的话)。
2、隐藏和覆盖
属性的隐藏
子类中声明了与超类中相同的成员变量名(实例变量)(静态属性不影响,因为他们都是一个)——从超类继承的变量将隐藏。这时子类中将有两个名字相同的变量,一个继承自超类,一个由自己声明。
- 当子类执行继承自超类的操作时,处理的时继承来的超类的变量
- 当子类执行自己声明的方法时,操作的是它自己声明的变量
访问被隐藏的属性
- 调用从超类继承来的方法
- 在本类中声明的方法访问需要使用
super.属性
访问
方法覆盖
如果子类不需要使用从超类继承来的方法,可以声明自己的同名方法,称为方法覆盖。
覆盖方法的返回类型、方法名称、参数个数必须和被覆盖的方法完全一致。访问权限可以更宽松,但不能更严格。
区分覆盖方法和被覆盖方法:在方法名前面使用不同的类名或不同的对象名即可区分。
方法覆盖的应用场合
- 子类中实现与超类相同的功能,但算法或公式不一样;
- 名字相同的方法中,要做比超类方法更多的事情;
- 子类中要取消从超类继承过来的方法。
注意事项
- 必须覆盖的方法:派生类必须覆盖基类中的抽象方法,否则派生类自身将成为抽象类
- 不能覆盖覆盖的方法:终结方法(final)、静态方法(static)
- 调用被覆盖的方法:
super.被覆盖的方法名();
3、Object类
Object类是所有类的直接或间接超类,处于类层次的最高点,包含了所有Java类的公共属性和行为。
(1) getClass方法
获取当前对象所属类的信息,返回Class对象:
public final Class getClass()
返回的Class对象用来代表所属类,可以查询类的各种信息:名字、超类、实现接口的名字等等。
(2) toString方法
返回表示当前对象本身有关信息的字符串对象:
//Object类中实现的toString方法 public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
(3) equals方法
比较两个对象引用是否指向同一个对象:
//Object类中实现的equals方法 public boolean equals(Object obj) { return (this == obj); }
相等和同一
- 相等(equal):两个对象类型相同,属性值相同。
- 同一(identical):两个引用变量指向的是同一对象。
- 同一肯定相等,相等不一定同一。
- 比较运算符
==
判断两个对象是否同一。
equals方法:判断两个对象引用是否同一(this == obj
)
String类覆盖了equals方法:可以比较两个字符串对象是否相等。
覆盖equals方法实现对象相等比较
原型必须和Object类中的一样,即:
public boolean equals(Object obj)
注意到形参为Object对象,并不知道其具体类型,方法体中首先要比较两者类型是否相同:
if (this.getClass() != obj.getClass()) return false;
在类型相同的情况下,将obj强制转换成当前类型,之后初始化当前类型的引用:
ClassName b = (ClassName) obj;
最后分别比较两者的属性值。
或者使用
instanceof
运算符:if(obj instanceof ClassName) { ClassName b = (ClassName) obj; //分别比较两者的属性值 } return false;
(4) clone方法
复制当前对象,并返回副本:
protected Object clone()
使用clone方法复制对象
覆盖为public方法:在Object类中被定义为了protected方法(类外不能访问),需要覆盖为public。
实现
Cloneable
接口,赋予一个对象被复制的能力(cloneability)。
class ClassName implements Cloneable { //…… public Object clone() { //…… } }
(5) hashCode方法
返回对象的哈希代码值(散列码):
public int hashCode()
方法实现的一般规定
- 在一个Java程序的一次执行过程中,如果对象”相等比较”所使用的信息没有被修改的话,同一对象执行hashCode方法每次都应返回同一个整数。在不同的执行中,对象的hashCode方法返回值不必一致。
- 如果依照equals方法两个对象是相等的,则在这两个对象上调用hashCode方法应该返回同样的整数结果。
- 如果依据equals方法两个对象不相等,并不要求在这两个对象上调用hashCode方法返回值不同。
- 只要实现得合理,Object类定义的hashCode方法为不同对象返回不同的整数。一个典型的实现是,将对象的内部地址转换为整数返回,但是Java语言并不要求必须这样实现。
(6) finalize方法
在对象被垃圾回收器回收前系统自动调用,通常完成资源的释放工作:
protected void finalize() throws Throwable
若要覆盖finalize方法,要在第一句调用超类的该方法
super.finslize()
(7) 与线程相关的方法:notify
、notifyAll
、wait
final方法,不能覆盖,主要用在多线程程序中。
4、终结类和终结方法
用final修饰的类和方法。
- 终结类不能被继承;
- 终结方法在子类中不能被覆盖。
5、抽象类
规定了整个类家族公共的属性和方法。
抽象类
- 类名前加修饰符
abstract
; - 可包含常规类能包含的任何成员,包括非抽象方法;
- 也可包含抽象方法:用
abstract
修饰,只有方法原型,没有方法的实现; - 没有具体实例对象的类,不能使用new方法进行实例化,只能用作超类;
- 只有当子类实现了抽象超类中的所有抽象方法,子类才不是抽象类,才能产生实例;
- 如果子类中仍有抽象方法未实现,则子类也只能是抽象类。
语法形式
abstract class ClassName { //…… }
如果写:new ClassName();
编译将显示错误。
抽象方法
- 仅有方法原型,没有方法体;
- 具体实现在各自的类声明中完成;
- 只有抽象类可以包含抽象方法。
语法形式
public abstract 返回值类型 方法名(参数表);
抽象方法的优点
- 隐藏具体的细节信息,所有的子类使用的都是相同的方法原型,其中包含了调用该方法时需要了解的全部信息;
- 强迫子类完成指定的行为,规定所有子类的“标准”行为。
类的组合
一个类的对象是另一个类的成员——将已经存在的类的对象放在新的类中。
与继承的区别
- 继承:是一个(种)…… ——从属关系
- 组合:有一个…… ——包含关系
泛型
泛型的本质:参数化类型。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
泛型类
泛型类也就是把泛型定义在类上,这样用户在使用类的时候才把类型给确定下来。
语法形式参见下例:
public class ObjectTool<T> { private T obj; public T getObj() { return obj; } public void setObj(T obj) { this.obj = obj; } }
在使用时如果定义了类型,那么在使用时就可以不用进行强制类型转换,直接就可以得到一个T类型的对象。
使用方法:
ObjectTool<Integer> i = new ObjectTool<Integer> (2); ObjectTool<Double> j = new ObjectTool<Double> (3.14);
对于基本类型应该使用它们的封装类型。
定义的泛型类,并不一定要传入泛型类型实参
- 在使用泛型的时候如果传入泛型实参,则会根据传入的泛型实参做相应的限制,此时泛型才会起到本应起到的限制作用。
- 如果不传入泛型类型实参的话,在泛型类中使用泛型的方法或成员变量定义的类型可以为任何的类型。
注意:不能对确切的泛型类型使用 instanceof
操作
泛型方法
有时候只关心某个方法,那么使用泛型时可以只定义一个泛型方法,语法形式参见下例:
public <T> void show(T t) { System.out.println(t); }
泛型接口
例如 有如下的泛型接口
public interface Generator<T> { public T next(); }
当实现泛型接口的类,未传入泛型实参时:
未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中。
class FruitGenerator<T> implements Generator<T>{ @Override public T next() { return null; } }
如果不声明泛型,如:
class FruitGenerator implements Generator<T>
,编译器会报错:Unknown class
。
当实现泛型接口的类,传入泛型实参时:
定义一个生产器实现这个接口,虽然我们只创建了一个泛型接口
Generator<T>
,但是我们可以为T传入无数个实参,形成无数种类型的Generator接口。
public class FruitGenerator implements Generator<String> { private String[] fruits = new String[]{"Apple", "Banana", "Pear"}; @Override public String next() { Random rand = new Random(); return fruits[rand.nextInt(3)]; } }
在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型。
类型通配符
无界
和泛型方法相差不大,但不用在使用前进行定义,语法形式参见下例:
public void processElements(List<?> elements){ for(Object o : elements){ System.out.println(o); } }
?
可以接收任何类型。
上界
能够接收这一类或者这一类的子类。
public void processElements(List<? extends A> elements){ for(A a : elements){ System.out.println(a.getValue()); } }
下界
能够接收这一类或者这一类的超类。
public static void insertElements(List<? super A> list){ list.add(new A()); list.add(new B()); list.add(new C()); }
有限制的泛型
在参数 Type
后面使用 extends
关键字并加上类名或接口名,表明参数所代表的类型必须是该类的子类或者实现了该接口。
注意,对于实现了某接口的有限制泛型,也是使用
extends
关键字,而不是implements
关键字。