一、继承性
子类可以继承父类的所有属性和方法。子类功能更加强大(一代更比一代强)
(一)继承的优势:
1、减少代码冗余,提高代码的复用性
2、便于功能扩展。如果子类都想加入某个功能或属性,直接在父类中加入就可以,非常方便。
3、为之后多态性的使用,提供了前提
(二)继承语法: extends
权限修饰符 class 子类名 extends 父类名 {
子类属性; //声明子类自己特有的属性
子类方法; //声明子类自己特有的方法
}
说明:
1、子类,也叫派生类(C++),subclass;
2、父类,也叫基类(C++),超类,superclass
3、子类继承了父类以后,子类就获取了父类中声明的结构:所有的属性和方法
特别说明:父类里面private的属性和方法也获取到了,但是由于封装性的影响,没法直接调用。但这些属性和方法确实已经存在于子类的堆内存里面啦。
4、子类继承父类以后,可以声明自己特有的属性或方法,实现功能扩展
(三)Java中继承的规定
1、Java支持单继承,多层继承,不允许多重继承(C++可多继承)
一个子类只能有一个父类。(理解成父子关系,父亲可以有多个孩子,但是孩子只能有一个父亲)
2、子父类是相对的概念,子类直接继承的父类称为直接父类。间接继承的父类称为间接父类。子类继承父类以后,就获取了直接父类以及间接父类中声明的所有属性和方法。
3、Java中所有的类(包括自己写的类),都直接或间接继承于java.lang.object类。因此所有类都包含了object类的属性和方法(object类的属性和方法很通用)
注意:继承了很多层的子类,特别是网上找的一些大神写的类,根本不知道他们继承了哪些类的属性和方法。Idea中,可以用ctrl + H 查看类的继承结构。右键 Jump to source 就可以去看相关类的原码。
(四)子类方法(函数)重写 (注意不是重载)
子类可以根据需要对从父类继承来的方法进行改造,也称为方法的重置,覆盖。在执行程序时,子类的方法将"覆盖"父类的方法。
注意:重写方法,必须方法名和形参列表都一样。这样才能精确定位是覆盖哪一个方法(父类可能有多个同名方法),只有非static才有重写。(static是类方法,每个类都可以有多个子类,子类继承父类方法的时候,先从静态开始,没法覆盖父类方法的,所以不能重写)
方法重写以后,Idea会在代码行那里标记小三角。
特别注意:覆盖不是指让父类的方法消失,只是提供方便而已,父类的方法还是存在。只是需要用super显示调用父类方法了
重写语法:
权限修饰符 返回值类型 方法名(形参列表) 【throws 异常类型】 // 方法名(形参列表)必须和父类被重写方法一样,其他分情况讨论
{
方法体; //自然需要和父类不一样
}
重写规定: (重写方法和被重写方法格式完全一样最好,就没后面那么多事了)
1、权限修饰符
子类重写方法的权限修饰符不小于父类被重新方法的权限修饰符。 (想象要覆盖一个东西,至少要一样大才能覆盖撒)
特别:如果子类方法的权限修饰符是private,这种就没法重写。(想象都封装了,子类根本看不到这个方法,就没法重写)
2、返回值类型,
(1)如果父类被重写方法返回值类型为 void,则子类重写方法的返回值必须也为void (同为空void)
(2)如果父类被重写方法的返回值类型为基本数据类型,如float,则子类重写方法的返回值类型也必须是float。(同一基本数据类型)
(3)如果父类被重写方法返回值类型为引用数据类型,如某个类(比如String),则子类重写方法的返回值类型也必须是String类或String类的子类。(同一类或该类的子类)
3、异常类型
重写方法跑出的异常类型不大于父类被重写方法抛出的异常类型(必须是父类异常类型或其子类)和上面的(3)相似
总结:实际操作很简单,直接把父类复制过来就对了(相当于完全一样)。即使想搞点不同,而且IDEA也会帮你找问题的。同时,IDEA可以帮你补全重写提示" ctrl+ 空格"
(五)super关键字
仿照this的理解,指向直接父类的指针,可以就将其看成父类。
使用场景:
1、调用父类构造器,子类之所以继承了父类的所有属性和方法,就是通过用super调用父类构造器获取的这些属性和方法。同时,也将父类构造器中的代码弄到子类来了,可以减少子类构造器中的代码量。
2、遇到子父类属性或方法同名的时候。//这种情况少,写代码的时候注意别让子父类同名就是了
语法: super.属性; //调用父类属性
super.方法; //调用父类方法
super(形参列表); //调用父类构造器
特别说明:和this一样,super也只能用于代码块首行。换句话说,this 和 super 只能二选一。如果在类的构造器中没有显式声明“this(形参列表)”或“super(形参列表)”,系统会默认调用父类中的空参构造器,即super() 来继承父类的所有属性和方法。所以注意,类一定要提供空参构造器(里面没代码也行),否则子类在由于粗心没写super或this的时候,默认用super(),而父类又没有,就会报错。
使用场景解释:
1、子类继承了父类的所有属性和方法,子类可以添加自己特有的属性和方法。由于子类在新的一个作用域,因此子类自己的属性可以和父类的属性名字一样(编译器不会报错,但是编译器会晕)。属性没有重写。此时为了不让编译器晕,就需要用super关键字来区别子父类的属性。
2、如果子类重写了父类中的方法,编译器就会调用子类的同名方法。但此时如果还是需要使用父类的方法的话,就需要使用super关键字
3、子类的构造器,往往都是父类构造器+子类特有的属性赋值。由于父类构造器部分都已经写过了,自然子类中,就不想再写这一段了,因此可以用super(形参列表),来copy父类的代码。当然最重要的是,通过super继承父类的所有资源。
exp: public class Person{
int id=20;
public Person(int id){ //父类构造器,给人的id赋值,假设是身份证
this.id = id;
}
......
}
public class Student extends Person {
int id=30; //假设学生id代表学号
public Student(int id,int stuId){
super(id); //调用父类构造器public Person(int id) 给 Person id 身份证赋值
this.id = stuId; // 给子类学生id 学号 赋值
}
public void show(){
System.out.println("id="+id); //输出30 在子类中,默认调用的是this.id 即子类的id
System.out.println("id="+super.id); //输出20 如果用了super关键字,就会调用父类的id
}
}
总结:一般实际工作中,为了提高代码可读性,就不要搞啥子同名的了。
(六)子类的实例化过程
1、从结果上来看(继承性)
子类继承父类以后,就获取了父类中声明的属性或方法。
创建子类的对象,在堆空间中,就会加载所有父类中声明的属性。
2、从过程上来看
当我们通过子类构造器构建子类对象时,我们一定会直接或间接调用其父类的构造器,进而调用父类的父类的构造器,一直到调用了最顶层 java.lang.object类中的空参构造器为止。正是由于加载过所有的父类的结构,所以才可以看到内存中有父类中的结构,子类对象才可以进行调用。
如图:通过 super 一级一级继承父类,直到 java.lang.object
二、多态性(polymorphism) 父类使用子类中重写的方法(虚拟方法调用)
父类的引用指向子类的对象(子类对象赋给父类的引用),可以直接用于抽象类和接口上。
有了对象多态性以后,我们在编译期,只能调用父类中声明的方法,因为有多个子类,编译器也不晓得要用哪个子类的方法(比如右边是通过随机数的值随机生成子类,编译时就完全没法知道子类是啥了),所以就编译左边父类的方法。但在运行期,右边的子类已经确定了,我们实际执行子类重写父类的方法(把子类方法虚拟成父类方法,即虚拟方法调用)。因此多态性是一个运行时的行为。
总结:编译,看左边;运行,看右边
语法: 父类 父对象名 = new 子类() ;
说明:在内存中,由于new了子类(),子类确实已经完整的创建了。由于引用数据类型是父类(显然父类的内存空间比子类少),所以子类自身的属性和特有的方法,没法传给父类对象。如果需要把子类所有属性展示出来,可以用强制转换。即:
子类 对象名 =(子类)父对象名。 此时,提升后的对象就解锁了子类的所有属性和方法。但是如果提升的不对,可能出现ClassCastException异常。因此做变量提升前,可以先判断该对象是否为该类的实例。(instanceof运算符)
多态性使用前提:
1、类的继承关系
2、子类有方法的重写
注意:多态性只适用于方法,属性没有多态性。属性的编译和运行都看左边,即只管父类。
特点:
1、当调用子类父类同名,同参数的方法时,实际执行的是子类方法。
2、由于编译时编译父类,同时子类的方法没内存展示(被屏蔽)自然没法调用父类没有的方法。(即没法调用子类特有的方法)
应用场景举例:数据库有多种,mysql,db,oracle等等。虽然数据库的操作方法不一样(函数体不一样),但是操作步骤和方法名可以一样。因此可以写一个数据库的父类 connection,父类中定义好每种方法名。每种数据库一个子类,在子类中各自重写调用方法。这样调用数据库时,就可以写一个函数,形参是connection,方法connection.方法名。然后实参传子类(mysql,db等),就实现了多态。函数只写了一次代码,实际可以调用所有子类的方法。
应用场景举例2: 欢迎光临不同国家的人。
扩展 instanceof 比较运算符instanceof运算符是一个比较运算符,类似 == ,< ,返回一个boolean类型。
instanceof运算符用于判断对象a是否是类A的实例,如果是,返回true
使用场景:instanceof一般用于使用了检查多态的父类对象(天晓得这个对象引用的是哪个子类)。通过强制转换提升该对象以后,可以解锁子类的全部属性和方法,可是为了避免出错,就用instanceof 来检验以下。
exp: if ( p instanceof Woman ){ //判读对象p是否属于Woman类
Woman w = (Woman) p;
}
注意:一旦p instanceof Woman为true,那p instanceof Woman的父类也是true,一直传递到object对象,都是true。就是因为多态性,父类 父对象 = new 子类(); 所以子类对象都可以给父类,只是由于内存,不能显示那么多内容而已。