一.类的继承
1.java只能有一个直接父类,实际上,java类可以有无限多个间接父类。
class Fruit extends Plant{...}
class Apple extends Fruit{...}
2.java.lang.Object类是所有类的父类,要么是其直接父类,要么是其间接父类。因此所有的Java对象都可调用java.lang.Object类所定义的实例方法。
3.方法的重写
遵循"两同两小一大"规则
“两同”:即方法名相同,形参列表相同;
“两小”指的是子类方法返回值类型应该比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等。
一大指的是子类方法的访问权限应该比父类方法的访问权限更大或相等。
另外覆盖方法和被覆盖的方法要么都是类方法,要么都是实例方法。不能一个是类方法,一个是实例方法。
下面的代码会引发编译错误:
class BaseClass{
public static void test(){...}
}
class SubClass extends BaseClass{
public void test(){...}
}
4.当子类覆盖了父类方法后,子类的对象将无法访问父类中被覆盖的方法,但可以在子类方法中调用父类中被覆盖的方法。通过使用super(被覆盖的是实例方法)或者父类类名(被覆盖的是类方法)作为调用者来调用父类中被覆盖的方法。
5.super用于限定该对象调用它从父类继承得到的实例变量或方法。this和super均不能出现在static修饰的方法中。
class BaseClass
{
public int a = 5;
}
public class SubClass extends BaseClass
{
public int a = 7;
public void accessOwner()
{
System.out.println(a);
}
public void accessBase()
{
// 通过super来限定访问从父类继承得到的a实例变量
System.out.println(super.a);
}
public static void main(String[] args)
{
SubClass sc = new SubClass();
sc.accessOwner(); // 输出7
sc.accessBase(); // 输出5
}
}
6.子类强转为父类,调用父类的方法
class Parent
{
public String tag = "疯狂Java讲义"; //①
}
class Derived extends Parent
{
// 定义一个私有的tag实例变量来隐藏父类的tag实例变量
private String tag = "轻量级Java EE企业应用实战";
protected String getTag(){
return tag;
}
}
public class HideTest
{
public static void main(String[] args)
{
Derived d = new Derived();
// 程序不可访问d的私有变量tag,所以下面语句将引起编译错误
// System.out.println(d.tag); //③
// 将d变量显式地向上转型为Parent后,即可访问tag实例变量
// 程序将输出:“疯狂Java讲义”
System.out.println(d.getTag());
System.out.println("======================");
System.out.println(((Parent)d).tag); //④
}
}
结果:
轻量级Java EE企业应用实战
======================
疯狂Java讲义
7.在一个构造器中调用另一个重载的构造器使用this调用来完成,在子类构造器中调用父类构造器使用super调用来完成。
public class Apple
{
public String name;
public String color;
public double weight;
public Apple(){}
// 两个参数的构造器
public Apple(String name , String color)
{
this.name = name;
this.color = color;
}
// 三个参数的构造器
public Apple(String name , String color , double weight)
{
// 通过this调用另一个重载的构造器的初始化代码
this(name , color);
// 下面this引用该构造器正在初始化的Java对象
this.weight = weight;
}
}
class Base
{
public double size;
public String name;
public Base(double size , String name)
{
this.size = size;
this.name = name;
}
}
public class Sub extends Base
{
public String color;
public Sub(double size , String name , String color)
{
// 通过super调用来调用父类构造器的初始化过程
//super调用父类构造器也必须出现在子类构造器执行体的第一行。
super(size , name);
this.color = color;
}
public static void main(String[] args)
{
Sub s = new Sub(5.6 , "测试对象" , "红色");
// 输出Sub对象的三个实例变量
System.out.println(s.size + "--" + s.name
+ "--" + s.color);
}
}
所以this调用和super调用是不会同时出现的。
二.多态
- java引用变量有两个类型:一个是编译时类型,一个是运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,就可能出现所谓的多态。
class BaseClass
{
public int book = 6;
public void base()
{
System.out.println("父类的普通方法");
}
public void test()
{
System.out.println("父类的被覆盖的方法");
}
}
public class SubClass extends BaseClass
{
//重新定义一个book实例变量隐藏父类的book实例变量
public String book = "轻量级Java EE企业应用实战";
public void test()
{
System.out.println("子类的覆盖父类的方法");
}
public void sub()
{
System.out.println("子类的普通方法");
}
public static void main(String[] args)
{
// 下面编译时类型和运行时类型完全一样,因此不存在多态
BaseClass bc = new BaseClass();
// 输出 6
System.out.println(bc.book);
// 下面两次调用将执行BaseClass的方法
bc.base();
bc.test();
// 下面编译时类型和运行时类型完全一样,因此不存在多态
SubClass sc = new SubClass();
// 输出"轻量级Java EE企业应用实战"
System.out.println(sc.book);
// 下面调用将执行从父类继承到的base()方法
sc.base();
// 下面调用将执行从当前类的test()方法
sc.test();
// 下面编译时类型和运行时类型不一样,多态发生
// 定义一个ploymophicBc变量,编译时类型为BaseClass
// ploymophicBc运行时类型为SubClass。
BaseClass ploymophicBc = new SubClass();
// 输出6 —— 表明访问的是父类对象的实例变量
System.out.println(ploymophicBc.book);
// 下面调用将执行从父类继承到的base()方法
ploymophicBc.base();
// 下面调用将执行从当前类的test()方法
ploymophicBc.test();
// 因为ploymophicBc的编译类型是BaseClass,
// BaseClass类没有提供sub方法,所以下面代码编译时会出现错误。
// ploymophicBc.sub();
}
}
结果:
6
父类的普通方法
父类的被覆盖的方法
轻量级Java EE企业应用实战
父类的普通方法
子类的覆盖父类的方法
6
父类的普通方法
子类的覆盖父类的方法
2.引用类型之间的转换只能在具有继承关系的两个类型之间进行,如果试图把一个父类实例转换成子类类型,则这个对象必须实际上是子类实例才行(即编译时类型为父类类型,而运行时类型时子类类型),否则将在运行时引发ClassCastException异常。
class Animal{}
class Dog extends Animal{}
class Cat extends Animal{}
public class Ploymophic {
public static void main(String [] args) {
Animal m;
Dog d = new Dog();
Cat c = new Cat();
Animal ma = new Animal();
//m = d; //right 子类可以为父类对象赋值
//d = (Dog) m; //right 父类对象要赋值给子类对象变量,必须执行类型转换
d = (Dog) ma; //error 父类为对象时又不行
}
}
3.instanceof运算符
强制转换之前,先用instanceof运算符判断是否可以成功转换,从而避免ClassCastException异常。
前一个操作数通常是一个引用类型变量,后一个操作数通常是一个类(也可以是接口,可以把接口理解成一种特殊的类),它用于判断前面的对象是否是后面的类,或者其子类,实现类的实例。如果是,则返回true,否则返回false。
class FatherClass{
public static int [] fatherTestArray;
}
public class InstanceofTest extends FatherClass
{
public static void main(String[] args)
{
// 声明hello时使用Object类,则hello的编译类型是Object,
// Object是所有类的父类, 但hello变量的实际类型是String
Object hello = "Hello";
// String与Object类存在继承关系,可以进行instanceof运算。返回true。
System.out.println("字符串是否是Object类的实例:"
+ (hello instanceof Object));
System.out.println("字符串是否是String类的实例:"
+ (hello instanceof String)); // 返回true。
// Math与Object类存在继承关系,可以进行instanceof运算。返回false。
System.out.println("字符串是否是Math类的实例:"
+ (hello instanceof Math));
// String实现了Comparable接口,所以返回true。
System.out.println("字符串是否是Comparable接口的实例:"
+ (hello instanceof Comparable));
String a = "Hello";
// String类与Math类没有继承关系,所以下面代码编译无法通过
// System.out.println("字符串是否是Math类的实例:"
// + (a instanceof Math));
FatherClass father = new FatherClass();
System.out.println("fatherclass instanceof subclass" + (father instanceof InstanceofTest));
}
}
三.继承与组合
1.使用继承的注意点
为保证父类良好的封装性,不会被子类随意改变,设计父类通常应该遵循如下规则:
1> 尽量隐藏父类的内部数据,尽量把父类的所有成员变量都设置成private访问类型,不要让子类直接访问父类的成员变量。
2>不要让子类可以随意访问,修改父类的方法。父类中那些仅为辅助其他的工具方法,应该使用private访问控制符修饰,让子类无法访问该方法;如果父类中的方法需要被外部类调用,则必须public修饰,但又不希望子类重写该方法,可以用final修饰符来修饰该方法;如果希望父类的某个方法被子类重写,但不希望被其他类自由访问,则可以使用protected来修饰该方法。
3>尽量不要在父类构造器中调用将要被子类重写的方法。
class Base
{
public Base()
{
test();
}
public void test() // ①号test()方法
{
System.out.println("将被子类重写的方法");
}
}
public class Sub extends Base
{
private String name;
public void test() // ②号test()方法
{
//所以正常情况下我们对对象的方法的调用都需要进行判空判断的
if (name != null) {
System.out.println("子类重写父类的方法,"
+ "其name字符串长度" + name.length());
}
}
public static void main(String[] args)
{
// 下面代码会引发空指针异常
// 当创建Sub对象时,会先执行Base类中的Base的构造器,而Base构造器中
// 调用了test方法,并不是调用1号test方法,而是2号test方法,此时Sub
// 对象的name实列变量是null,因此会引发空指针异常。
Sub s = new Sub();
}
}
上述是当系统试图创建Sub对象时,同样会先执行其父类构造器,如果父类构造器调用了被其子类重写的方法,则变成调用被子类重写后的方法。
2.利用组合实现复用
继承与组合对比
下面是继承的用法
class Animal
{
private void beat()
{
System.out.println("心脏跳动...");
}
public void breath()
{
beat();
System.out.println("吸一口气,吐一口气,呼吸中...");
}
}
// 继承Animal,直接复用父类的breath()方法
class Bird extends Animal
{
public void fly()
{
System.out.println("我在天空自在的飞翔...");
}
}
// 继承Animal,直接复用父类的breath()方法
class Wolf extends Animal
{
public void run()
{
System.out.println("我在陆地上的快速奔跑...");
}
}
public class InheritTest
{
public static void main(String[] args)
{
Bird b = new Bird();
b.breath();
b.fly();
Wolf w = new Wolf();
w.breath();
w.run();
}
}
下面是组合的方式
class Animal
{
private void beat()
{
System.out.println("心脏跳动...");
}
public void breath()
{
beat();
System.out.println("吸一口气,吐一口气,呼吸中...");
}
}
class Bird
{
// 将原来的父类组合到原来的子类,作为子类的一个组合成分
private Animal a;
public Bird(Animal a)
{
this.a = a;
}
// 重新定义一个自己的breath()方法
public void breath()
{
// 直接复用Animal提供的breath()方法来实现Bird的breath()方法。
a.breath();
}
public void fly()
{
System.out.println("我在天空自在的飞翔...");
}
}
class Wolf
{
// 将原来的父类组合到原来的子类,作为子类的一个组合成分
private Animal a;
public Wolf(Animal a)
{
this.a = a;
}
// 重新定义一个自己的breath()方法
public void breath()
{
// 直接复用Animal提供的breath()方法来实现Wolf的breath()方法。
a.breath();
}
public void run()
{
System.out.println("我在陆地上的快速奔跑...");
}
}
public class CompositeTest
{
public static void main(String[] args)
{
// 此时需要显式创建被组合的对象
Animal a1 = new Animal();
Bird b = new Bird(a1);
b.breath();
b.fly();
// 此时需要显式创建被组合的对象
Animal a2 = new Animal();
Wolf w = new Wolf(a2);
w.breath();
w.run();
}
}
如何选择,继承要表达的是一种is-a的关系,而组合表达的是一种has-a的关系。