错误之处,请修正
继承与多态,方法重写与重截 肯定是面向对象的重中之中,考题变化多端
1 多态
发生的三个必要条件:
1) 有继承关系
2) 有方法重写 (非私有,非静态,非final方法重写)
3) 父类的引用指向子类
2 原则
1) 继承关系中, 属性, 私有方法, 静态方法不存在重写
2) 方法重写:
子类方法不能有更严格的权限
父类方法如果有异常,子类不能抛出更大的异常,但可以不抛,如果不抛,应该缺省为throws RuntimeException及其子类; 父类方法没抛需要处理的异常,子类也不能抛,但子类可以抛RuntimeException及其子类;简单来说,可以理解成RuntimeException等同于没有抛异常,重写时可以不考虑
子类方法的返值,不能大于父类;如果父类返值为void和基本类型,子类返值必须与父类完全一样;如果父类返值为引用类型object,子类可以返回object或者它的子类;
换句话说,返值是支持variant的,但参数绝对不支持variant,如果说参数有父类,有子类,那很可能就变成了重载,请参考后面的代码;
重写接口的方法,子类必须显式声明public权限控制符号
3) 父类不能转成子类,否则运行时会抛ClassCastException
编译时查类型,运行时找对象: 编译时,会产生一个类型Parent p,运行时,找对象Child c,如果编译时的对象和运行时的对象,就会抛ClassCastException
3 技巧
多态中,方法重写时,看实际对象,即=右边的部分;
多态中,对于没有重写的方法(static, final, private) 或者属性, 看引用, 即=左边的部分;
编译时看类型,运行时找对象....所以,如果将父类强转成子类Child c = (Child)Parent; 编译时不会报错,运行时,会报ClassCastException;
4 例如
关于成员变量、方法、重写的代码:
package com.ocjp.extend;
public class Upcast extends ParentWStringField {
// String s;
String className = "child class";
Upcast(){
s = "child";
}
void go(){
System.out.println("child go()");
}
public static void main(String[] args) {
ParentWStringField[] p = {new ParentWStringField(), new Upcast(),(ParentWStringField)new Upcast()};
for(ParentWStringField tmp:p){
//成员变量,私有方法,静态方法,不存在重写,也就是说不存在多态;
//成员变量,私有方法,静态方法,当有转型发生时,运行结果只看等号左边的类型,左边是什么,运行就调用誰的;
//普通方法,有多态多生,要看等号右边的;
//本例中,比较有意思的是,它在构造方法中,给父类的成员变量赋值了,当转型发生时,不会再调用构造方法,所以输出时,按转型前输出;
System.out.print(tmp.s + " " +tmp.className +" ");
tmp.go();
}
// ParentWStringField c = (ParentWStringField)new Upcast();
// System.out.println(c.s);
}
}
class ParentWStringField{
String s;
String className = "parent class";
ParentWStringField(){
s = "parent";
}
void go(){
System.out.println("parent go()");
}
}
输出:
parent parent class parent go()
child parent class child go()
child parent class child go()
分析:
tmp.s 打印父类的成员变量,由构造器来完成,转型后不会再次调用构造器;所以转型前是什么就打印什么
tmp.className 打印每个类自己的成员变量,转型后,等号左边是什么,就打印什么;本例中用FOR循环,全部强转成了父类;所以,全部打印父类的成员变量;
tmp.go() 有多态发生,运行时,看等号右边的,等号右边是什么实际内型,就调用实际类型的方法;
关于static方法:
在继承关系中,可以为当成成员变量来看。如果父类中有一个静态方法,
子类中有同样名字的,但是非静态的方法,报编译错误;
子类中有同样名字,也是static的方法,编译通过,但绝不是多态,可以看成成员变量。即运行时,看=左边是什么类型,是什么类型就调哪个类型的静态方法
模拟代码
class Weed {
protected static String s = "";
void grow() { s += "grow "; }
//这里如果不加static,会报编译错误,因为static的方法不能被重写;
//这里如果加static,就等于类的成员变量,运行时,看等号左边的部分;
static void growFast() { s += "fast "; }
}
public class Thistle extends Weed {
void grow() { s += "t-grow "; }
static void growFast() { s+= "t-fast "; }
public static void main(String[] args) {
Weed[] w = {new Weed(),new Thistle(),(Weed)new Thistle()};
for(Weed tmp: w){
tmp.growFast();
System.out.println(tmp.s);
}
}
}
Output:
fast
fast fast
fast fast fast
关于异常的代码:
import java.io.IOException;
interface Risky {
String doStuff() throws Exception;
// 父类抛RuntimeException,子类可以不抛异常;
// Risky doCrazy() throws NullPointerException;
Risky doCrazy();
void doInsane();
}
class Bungee implements Risky {
public String doStuff() throws IOException {
throw new IOException();
}
public Bungee doCrazy() { return new Bungee(); }
//父类没有抛异常,但子类可以抛RuntimeException及其子类;
public void doInsane() throws NullPointerException{
// 下面的写法也可以, RuntimeException可以不处理;
// public void doInsane(){
throw new NullPointerException();
} }
public class ExceptionTest {
public static void main(String[] args) {
Bungee b = new Bungee();
b.doInsane();
}
}
关于variant 参数:
返值: 子类重写父类的方法后,返值可以为variant类型,即可以为子类;
参数: 参数改变成variant类型,即子类后,就变成了重载.....
作为初学者,这个混淆了我很久....希望下面的代码,能给其他初学者一个参考
package com.ocjp.extend;
class MotorVehicle {
protected int doStuff(int x) { return x * 2; }
}
class Bicycle {
void go(MotorVehicle m) {
System.out.print(m.doStuff(21) + " ");
}
}
public class Beemer extends MotorVehicle {
public static void main(String[] args) {
System.out.print(new Beemer().doStuff(11) + " ");
// doStuff() is overloaded, not overrided, which doStuff() will be called is determined by its parameter;
new Bicycle().go(new Beemer()); //Beemer.doStuff() will be called;
new Bicycle().go(new MotorVehicle()); //MotorVehicle.doStuff() will be called;
}
protected int doStuff(int x) { return x * 3; }
}
Output:
33 63 42