浏览以下内容前,请点击并阅读 声明
一个由其他类继承的类叫子类(也叫继承类,扩展类等),该类继承的类叫父类或超类。除了Object类意外,所有的类都有切仅有一个父类,如果一个类没有用extends关键词声明父类,则该类隐含继承Object类,因此如果一个类继承另外一个父类,而该父类可能又是继承的另外一个类,最终的起点在Object类。
java中继承的概念简单而强大,当你需要定义的一个类中的许多代码已经在另外一个类中存在时,我们就可以选择定义时继承该类,这样免去许多书写和调试的麻烦,子类能够继承父类的字段,方法和嵌套类,即类成员,构造器不是类成员,因此不会被继承,但是可以在子类中使用。
可继承成员的处理
子类继承父类中可视范围修饰词为public和protected的成员,无论子类和父类是否在同一个程序包中,如果同在一个程序包内,则子类还可以继承父类中仅包内可见(没有可视范围修饰词)的成员,针对于可继承的类成员,子类可进行如下处理:
- 可直接用继承的字段,就像类中其他声明的字段一样。
- 也可以在类中声明一个与继承字段名称相同的字段,即隐藏(不推荐这样做)。
- 可以声明在父类中不存在的字段。
- 可以直接使用继承的方法,就像类中其他声明的方法一样。
- 也可以在类中声明一个与继承方法的签名相同的方法,即覆写。
- 可以定义一个新的与继承方法签名相同的静态方法,即隐藏。
- 也可以定义一个父类中不存在的方法。
- 可以在类中定义可以使用隐含地或者使用关键词super调用父类中的构造器。
类型的转化
如下的赋值语句:
//1 编译可以通过 Object obj = new MountainBike(); //2 编译不能通过 MountainBike myBike = obj; //3 转化操作,编译可以通过 MountainBike myBike = (MountainBike)obj;
上述代码中,MountainBike是由Object类多次继承下来的,所以将MountainBike类的实例赋值给类型为Object的变量不会出错,反过来,如果将Object对象赋值给类型为MountainBike的变量,就不能编译通过,此时我们使用一个转化操作,就可以将obj变量的类型转化成MountainBike类型,不过,如果变量obj实际指向的对象不是MountainBike类型的话,运行过程中就会抛出异常。
可以先通过instanceof操作符对一个变量指向的对象进行类型的判断,然后进行类型的转化:
if (obj instanceof MountainBike) { MountainBike myBike = (MountainBike)obj; }
除了能够对与对象类型使用转化意外,基本数据类型也可以使用转化操作,如将字符型转化成整型的操作。
状态,实现和类型的多重继承
类和接口的重要区别是类能够声明字段,而接口不能(只能声明常量)声明字段,此外,类可以实例化而接口不能实例化。对象使用类中声明的字段存储其状态,java语言不允许继承多个类是为了防止继承多个类的状态造成的问题,假如一个类继承多个类,那么创建该类的实例时,那么如果该类继承的类中含有相同的方法或者字段,优先使用哪一个呢?由于接口不含有字段,因此不存在继承多个状态的问题。
实现的多重继承是指如果允许继承多个类,那么就会有多个类中的方法命名冲突的问题,然而类的声明是允许继承多个接口的,静态方法和默认方法会造成实现的多重继承问题,java编译器有一些规则来从多个相同签名的方法做出选择。
java允许多个类型的继承,即通过实现多个类,一个对象可以看做是有多个类型的,即其自身的类和类实现的接口。
覆写和隐藏方法
实例方法
子类中声明与父类中签名和返回类型相同的方法就会覆写父类中对应的方法,方法的覆写是子类和父类行为尽可能相近,而又允许做出相应调整,子类中如果包含与父类中签名相同的方法,则该方法返回类型必须与父类中相应的方法相同,或者是返回类型的子类型。
可以使用@Override注释覆写的方法,这样编译器便会检查是否成功覆写父类中的方法(有时候方法名称写错)。
静态方法
在子类中声明与父类中签名相同的方法会隐藏父类中对应的方法,方法的隐藏和方法的覆写差异在于:
- 子类的对象调用覆写的方法是子类中声明的方法。
- 而被隐藏的静态方法则取决与方法是被父类还是被子类调用,如果被父类调用,则执行父类中被隐藏的方法,被子类调用则执行子类中的方法。
接口中的方法
默认方法和抽象方法的继承就像实例方法一样,然而父类型的类或者接口含有相同签名默认方法时,java编译器的选择遵循如下两个原则:
- 父类中的实例方法优先于接口中的默认方法被使用
- 已经被覆写的方法会被忽略,这种通常在实现的两个接口继承同一个接口时发生,如下例所示:
public interface Animal { default public String identifyMyself() { return "I am an animal."; } } public interface EggLayer extends Animal { default public String identifyMyself() { return "I am able to lay eggs."; } } public interface FireBreather extends Animal { } public class Dragon implements EggLayer, FireBreather { public static void main (String... args) { Dragon myApp = new Dragon(); System.out.println(myApp.identifyMyself()); } }
上述代码输出:I am able to lay eggs
如果两个以上独立的默认方法发生冲突时,或者默认方法和抽象方法冲突时,java编译器会产生一个编译错误,你必须明确地覆写该方法,不过父类中的实例方法可以覆写实现接口的抽象方法。
注意:接口中的静态方法不会被继承。
可视范围修饰词
对于可视范围的控制,子类中对于继承的成员可视范围只能扩大而不能缩小,比如在一个接口中一个抽象方法的可视范围修饰词为protect,则实现该接口的类中对于该方法的声明的可视范围修饰词只能为protect或者public,否则将会编译出错。
多态
字典中对于多态的定义是指一个生物或者一个物种可以有不同的形态或者阶段,这一概念被应用到了面向对象的编程语言中,java也不例外。子类可以定义自己独特的行为也可以和父类由相同的功能,当然,这些功能都是通过类成员实现的。
java虚拟机(JVM)是根据实际引用的对象来调用方法的,而不是根据变量的类型,这一特性叫做虚拟方法调用(virtual method invocation),体现出了java语言的特征。
字段的隐藏
在一个类中声明和父类中名称相同的字段会隐藏父类中的相应的字段,而无论名称相同的字段是否是相同的类型,在子类中不能直接引用被隐藏的父类中的字段,而只能通过super关键词,一般不提倡隐藏字段,因为这样会使代码的易读性降低。
super关键词的使用
用来使用父类中的成员
当子类覆写父类中的方法时,你可以通过super关键词调用被覆写的方法实现,同时,super关键词也可以用于使用被隐藏的字段。
子类构造器中使用super()
在构造器中,使用super(var)可以调用父类中的构造器,圆括号内是构造器参数,在子类构造器中,父类构造器调用语句必须位于构造器主体中的第一行。
如果没有用super()明确调用父类中的构造器,则java编译器会自动给子类插入对于父类中不带参数构造器的调用,如果父类中不含有无参数构造器,则会编译出错,当然如果该类的超类是Object的话就不会有问题。
子类中的构造器一定会调用父类的构造器,父类的构造器也会调用自身继承的类的构造器,以此类推,最终每个类都会调用Object的构造器。
Final 方法和类
在方法的声明中加入修饰词final,那么该方法则不能被覆写,一般建议在构构造器中使用的方法定义为final方法。、
同样,在类的声明也可以加入修饰词final,那么该类就不能被继承,如String就不能被继承。
Object类
Object类在java.lang包中,是所有类继承树的最顶端,无论是直接或者间接继承,所有的类都是其后代,Object类中有一些方法,你写的每一个类都会继承这些方法,如果有必要,你可以覆写这些方法:
protected Object clone() throws CloneNotSupportedException
创建并返回该对象的副本。
public boolean equals(Object obj)
验证某个对象是否与该对象相等
protected void finalize() throws Throwable
当没有对该对象的引用时,垃圾回收器调用该方法 。
public final Class getClass()
返回该对象的运行时类。
public int hashCode()
返回该对象的hashcode。
public String toString()
返回该对象的字符串表示。
另外,还有五个在多线程中发挥作用的方法:
public final void notify()
public final void notifyAll()
public final void wait()
public final void wait(long timeout)
public final void wait(long timeout, int nanos)
clone()方法
一个对象要使用继承的clone()方法,首先需要实现Cloneable
接口,否则会抛出异常CloneNotSupportedException
,如果要覆写clone方法,则该方法的声明为:
protected Object clone() throws CloneNotSupportedException //或者 public Object clone() throws CloneNotSupportedException
当然,最简单的方法就是在类的声明中加入对接口Cloneable
的实现,而不必覆写clone方法,直接调用。
从Object类继承clone方法有一些局限性,当一个对象含有对于外部对象的引用时,用Object类的clone方法产生的对象的副本会受到原对象更改引用的外部对象影响,此时应覆写clone方法。
equals()方法
equals方法比较两个对象是否等同,Object类中的equals方法是用操作符==判断两个对象是否相等,对于基本数据类型,这样判断是可行的,然而对于两个对象的判断,必须两个对象是同一个对象(变量指向同一个对象)才会返回true。
为了进行更有意义的判断,有时我们需要覆写这一方法,如果覆写了equals方法,则必须同时覆写hashCode方法。
finalize()方法
Object类提供了finalize这个回调方法,当要该对象资源进行回收时,会对该对象调用finalize方法,你可以覆写该方法来进行一些资源的清理。finalize方法是系统自动调用的,当不要指望你能够自行调用该方法替代一些文件描述符的close方法,达到垃圾回收的目的。
getClass()
getClass方法不能被覆写,getClass方法返回一个Class对象,通过该Class对象,你可以获得关于对象类型的信息,如名称,父类,实现的接口等。Class类在java.lang中,其中由许多的方法,详见其javadoc。
hashCode()
Object中的hashCode方法返回的值是该对象的内存地址的十六进制表示。
如果两个对象根据equals方法判断为相等,那么两个对象的hashCode方法返回的值也一定要相等,否则hashCode方法虽然可用,但也没有意义。
toString()
直接继承Object时,最好考虑覆写toString方法。
Object的toString方法返回该对象的字符串表示,在代码调试时比较有用,返回的字符串视对象而定。
抽象类和抽象方法
抽象类是在类声明中含有修饰词abstract的类,抽象类不能被实例化,但是可以被继承。
抽象方法是声明中没有实现代码的方法,方法声明中没有方法的主体,以分号结尾。
一下定义一个含有抽象方法的抽象类:
public abstract class GraphicObject { // 可定义字段 //可定义非抽象方法 abstract void draw(); }
当一个类继承一个抽象类时,该类必须实现其父类的所有抽象方法。
值得注意的是:接口中定义的抽象方法没有abstract修饰词,可以有,但是没有必要。
抽象方法与接口的比较
和接口一样,抽象类不能实例化,也可以定义抽象方法和非抽象方法,然而,有一点不同的是抽象类可以定义字段(接口中只能定义常量),而且其成员的可视范围修饰词可以是public,protect,private或者是没有,接口中的字段隐含的修饰词都是public static final,类只能继承一个父类,无论父类是普通的类还是抽象类,而类能够实现多个接口。
以下情况建议使用抽象类:
- 如果你想在多个相关的类之间共享一部分代码
- 如果你希望一些继承一个抽象方法的类中含有通用的方法,或者需要类成员的可视范围为非public的情况
- 如果你需要定义一些非常量的字段,这样就能通过定义方法来改变对象的状态。
以下情况建议使用接口:
- 如果你希望一些不相关的类能够实现一个接口,如接口Comparable就被许多不相关的类实现
- 如果你想指定特殊的数据类型的行为,而不关心具体是哪个类实现了这样的行为
- 如果你想利用接口类型的多重继承的特性
一个抽象类的例子就是JDK中的AbstractMpa,它是集合框架的一部分,其子类有HashMap
, TreeMap
和 ConcurrentHashMap,共享了包括
get
, put
, isEmpty
, containsKey
和containsValue等AbstractMpa定义的方法。
JDK中实现了多个接口的例子就是HashMap类,它实现了Serializable
, Cloneable
和 Map<K, V>,从中可以推测该类的实例可以被克隆,是可序列化的并且从功能上是一个键-值映射集。
注意的是,许多软件库会同同时使用接口和抽象类,如HashMap实现了多个接口,同时,它的父类又是一个抽象类。
当一个抽象类实现一个接口
如同接口继承接口一样,抽象类实现一个接口时可以不必实现甚至不用声明其接口中的抽象方法。