继承
一、概念:
使用类来对同一类型的对象建模,不同的类可能会有一些共同的特征和行为,我们可以先定义一个通用的类,然后再定义一个特定的类去继承通用类。
通用类——父类 超类 基类
特定类——子类 继承类 派生类
子类从父类中继承可访问的数据域和方法,另外可以添加新的数据域和方法。私有数据域同样被子类继承,但子类不可以访问
public class cat extends animals{} //extends作为关键字,告诉编译器cat继承自animals类
一个子类可能继承于多个父类,叫做多重继承,java是不允许多重继承的,java通过接口来实现,而c++允许多重继承
二、super关键字
关键词super指代父类,主要用于两种途径:
1、调用父类的构造方法:
父类的构造方法不会被子类继承,只能使用super从子类的构造方法中调用,且该调用必须是构造方法的第一条语句:
public Circle(double radius,String color,boolean filled){
super(color,filled); //调用父类有参构造方法 super()无参构造
this->radius=radius;
}
在构造一个子类的对象时,子类会先调用父类的构造方法,如果没有代码中没有显式的调用,编译器会自动增加super()作为子类构造方法中的第一条语句
2、调用父类的普通方法
通过super调用父类中的普通方法:
super.方法名(参数);
三、方法重写和方法重载
1、方法重写:
有时子类除了继承父类方法,还需要修改父类中定义的方法的实现
重写的方法必须与被重写的方法具有一样的签名,一样或者兼容的返回类型。(兼容:重写方法的返回类型可以是被重写方法的返回类型的子类型)
私有方法不可以被重写,静态方法也不能被重写
2、方法重载:
方法重载是使用相同的方法名,但不同签名来定义多个方法,而方法重写是在子类中提供一个对父类方法的新的实现。
为了容易分辨,可以使用一种特殊的Java语法,“重写标注”,在子类方法前面加一个**@Override**
如果进行标注了,那必须重写父类的一个方法,否则编译报错
多态
子类是父类的特殊化,每个子类的实例都是其父类的实例,但反过来不成立。比如说:每个圆都是一个几何对象,但并非每个几何对象都是圆。因此,使用父类对象都可以使用子类对象去代替,多态意味着父类的变量可以引用子类的变量。
动态绑定:
animal m = new cat();
//父类: animal 子类: cat 多态:父类的变量可以引用子类的变量
// 相当于m = &(cat类型的一个实例) m是一个引用类型变量
System.out.println(m.toString());
一个变量必须被声明为某种类型。变量的这个类型称为它的声明类型。此处m的声明类型为animal
一个引用类型变量可以是一个null值,或者是对一个声明类型实例的引用。该实例可以使用声明类型或它的子类型的构造方法创建。这样的变量有实际类型,即被变量引用的对象的实际类。此处cat是m的实际类型。
m调用哪个toString()方法是由m的实际类型决定,称为动态绑定
引用变量的声明类型决定了编译时匹配哪个方法,实际类型决定了动态绑定方法的实现
class Animal{
public Animal(){
System.out.println("Animal的构造函数调用");
}
public void speak() {
System.out.println("动物会说话");
}
}
class Cat extends Animal{
public Cat(){
//super(); //编译器会默认加上该行,在子类自己构造方法实现之前会先实现父类构造方法
System.out.println("Cat的构造函数调用");
}
@Override
public void speak() {
System.out.println("小猫会说话");
//super.speak() 可以调用父类的speak方法,输出动物会说话
}
}
public class TestCat {
public static void main(String[] args) {
m(new Animal());
m(new Cat()); 调用方法参数是cat的那个m方法
//q(new Animal()); 子类不可以引用父类
n(new Cat()); 虽然按照声明类型调用了方法参数是Animal的n方法,但是最后由实际类型cat绑定方法实现,输出小猫会说话
}
public static void m(Animal a) {
a.speak();
}
public static void m(Cat c) { 构建重载方法,通过声明类型决定编译时匹配哪个方法
c.speak();
}
public static void n(Animal a) { //Animal a = new Cat();
c.speak();
}
public static void q(Cat q){
q.speak();
}
}
输出:
Animal的构造函数调用
动物会说话
Animal的构造函数调用
Cat的构造函数调用
小猫会说话
Animal的构造函数调用
Cat的构造函数调用
小猫会说话
对象转换和instanceof操作符
对象转换:一个对象的引用可以类型转换为对另外一个对象的引用
向上转换:可以将一个子类的实例转换为一个父类的变量 可以隐式转换
结合上文:
n(new Cat()); 该语句将对象new Cat()赋值给了Animal类型的参数a,等价于:
Animal a = new Cat();
n(a);
向下转换:将一个父类的实例转换为它的子类变量,必须使用转换标记“(子类名)”进行显示转换。为了转换成功,必须确保要转换的对象是子类的一个实例。如果父类对象不是子类的一个实例,就会出现一个运行时异常,比如说上文那个错误。所以在尝试转换之前确保该对象是另一个对象的实例,可以利用instanceof来实现
Animal cat = new Cat();
if(cat instanceof Cat) {
q((Cat)cat);
}
对基本类型值进行转换不同于对对象引用进行转换:转换一个基本类型值会返回一个新的值,而转换一个对象引用不会创建一个新的对象,转换前后的变量都会指向同一个变量