类继承,是面向对象的三大特征之一。继承的核心思想是代码的复用。
子类的成员变量
子类并不能继承父类中所有的成员:
静态变量和静态方法
不能被继承。静态变量和静态方法都可以通过“子类名.父类静态成员”的形式成功调用。- 所有的
私有成员
不能继承, 即private修饰的成员。 构造函数
不能被继承。
继承中成员变量的特点:
a) 子类只能获取父类非私有成员
b) 子父类中成员变量的名字不一样直接获取父类的
成员变量
c) 子父类中成员变量名字是一样的获取的是子类的
成员变量
成员变量使用的就近原则:谁离我近我就用谁
a) 如果有局部变量
就使用局部变量
b) 如果没有局部变量,有子类的成员变量就使用子类的成员变量
c) 如果没有局部变量和子类的成员变量,有父类的成员变量就使用父类的成员变量
d) 啥都没有,出错了!!!
/*
函数功能:
“就近原则”的求证
*/
class Dad {
String name = "Dad";
}
class Kid extends Dad {
String name = "Kid";
public void show() {
String name = "Little";
System.out.println(super.name); //指定用父类的成员变量:输出:建霖
System.out.println(this.name); //指定用子类的成员变量;输出:四葱
System.out.println(name); //就近原则;输出:五葱
}
}
/*输出:
Dad
Kid
Little
*/
子类的成员方法
继承中成员方法的特点:
- 子类中没有这个方法,调用父类的
- 子类中重写了这个方法,调用子类的
继承中构造方法的执行顺序:
- 创建子类对象,调用子类的构造方法;如果子类的构造方法的第一行代码
如果没有调用父类
的构造或者没有调用子类的其他构造,则默认调用父类无参构造
; - 我们可以在子类的构造方法中
使用“super()”方法,主动调用父类的构造方法;使用“this()”方法调用子类的构造方法;
- 在执行上,肯定是
先执行父类中的构造方法,再执行子类中的构造方法;
// 程序功能:求证构造方法的执行顺序
class Dad {
public Dad() {
System.out.println("我是父类无参构造");
}
public Dad(int num) {
System.out.println("我是父类有参构造");
}
}
class Kid extends Dad {
public Kid() {
// super(); //编译器默认调用,当开发者没有使用以下两种主动调用方式时生效
// super(1); //调用父类的有参构造方法
this(1);// 不会再调用父类的无参构造了,而调用本类的有参构造
System.out.println("我是子类无参构造");
}
public Kid(int num) {
// 会默认调用父类无参构造,即执行super();
System.out.println("我是子类有参构造");
}
}
public class Main {
public static void main(String[] args) {
Dad d = new Dad(); //输出:我是父类无参构造
System.out.println("----分割线-----");
Kid k = new Kid(); //输出:我是父类午餐构造 我是子类无参构造
}
}
/*输出:
我是父类无参构造
----分割线-----
我是父类无参构造
我是子类有参构造
我是子类无参构造
*/
重写(Override)
重写,又叫覆盖,是子类对父类的
允许访问的方法
的实现过程进行重新编写
, 返回值和形参都不能改变。_即外壳不变,核心重写!_也就是说子类能够根据需要实现父类的方法。
注意:重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。
重写方法的法则:
- 父类的成员方法
只能被
它的子类重写
。 - 声明为
final
的方法不能被重写
。声明为private的方法不能被重写
- 声明为
static的方法不能被重写
,但是能够被再次声明。 - 子类和父类在
同一个包中
,那么子类可以重写父类所有方法
,除了声明为private和final的方法。 - 子类和父类
不同一个包中
,那么子类只能够重写父类的声明为public和protected的非final方法
。 - 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法
不能抛出
新的强制性异常,或者比被重写方法声明的更广泛
的强制性异常
,反之则可以。 - 构造方法不能被重写。
子类的对父类方法的重写法则:
- 参数列表完全
相同
; - 返回类型完全
相同
; 访问权限不能
比父类中被重写的方法的访问权限更低
。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected。(权限由高到低:public、package、protected、private)
多态
多态发生的三大条件:
- 子父类的
继承关系
- 方法的
重写
父类引用指向子类对象
;动态绑定:运行期间(区分编译期间)调用的方法,是根据其具体的类型
什么是“父类引用指向子类对象”:
//程序功能:理解父类引用指向子类对象
public class PoymorphicDemo {
public static void main(String[] args) {
//父类引用 Animal a
//指向 =
//子类对象 new Cat()
Animal a = new Cat(); //父类引用指向子类对象
a.eat(); //先调用new后面的类中方法,如果没有再去父类中找。输出:猫吃鱼
}
}
class Animal {
public void eat() {
System.out.println("吃东西");
}
}
class Cat extends Animal {
public void eat() {
System.out.println("猫吃鱼");
}
}
多态的成员特点:
- 成员变量:
编译时看的是等号的左边,运行时看的左边
即编译时的变量在左边的类中寻找,找不到则编译错误,反之编译通过;
运行时的变量在左边的类中寻找; - 成员方法:
编译时看的是等号的左边,运行时看右边
编译时看左边的类是否有该方法,找不到该方法即编译错误;运行时看右边的类有没有该方法,有则调用,没有则调用父类的方法; - 静态方法: 编译时看的是等号的左边,运行时看的也是左边
**总结:**编译时看的都是左边,运行时成员方法看的是右边,其他(成员变量和静态的方法)看的都是左边
多态成员变量的特点:
//程序功能:验证多态成员变量的特点
class Dad {
int num = 20;
public void method() {
System.out.println("我是父类方法");
}
public static void function() {
System.out.println("我是父类静态方法");
}
}
class Kid extends Dad {
int num = 10;
public void method() {
System.out.println("我是子类方法");
}
public static void function() {
System.out.println("我是子类静态方法");
}
}
public class Main {
public static void main(String[] args) {
Dad d = new Kid(); //父类引用指向子类对象
System.out.println(d.num); //成员变量:编译时看的是等号的左边,运行时看的左边
d.method(); //成员方法:编译时看的是等号的左边,运行时看右边
d.function();//静态方法:使用变量去调用静态方法,其实相当于用变量类型的类名去调用
}
}
/* 输出:
20:成员变量,运行时看左边,左边是Dad类,就使用Dad类中的成员变量
我是子类方法:成员方法,运行时看右边,右边是Kid类,就使用Kid类中的成员方法
我是父类静态方法:静态方法,运行时看左边,左边是Dad类,就使用Dad类中的静态方法
*/
多态中的类型转换
多态中的向上转型和向下转型:
- 基本数据类型的转换
自动类型转换(由小到大):byte short char — int — long — float — double
强制类型转换(由大到小)
- 引用类型之间的转换
向上转型:由小到大(子类型转换成父类型)
向下转型:由大到小(父类型转换为子类型)
类型转换实例:
class Animal {
public void eat() {
System.out.println("吃东西");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("狗在吃东西");
}
public void swim() {
System.out.println("狗在浮水");
}
}
public class Main {
public static void main(String[] args) {
Animal a = new Dog(); //向上转型:子类对象转换为父类对象
a.eat(); //多态,对于方法,运行时看右边,右边是Dog类,故调用Dog类中的方法;输出“狗在吃东西”
//如果我想要实现a对象使用Dog类的swim方法,应该怎么实现?
Dog d = (Dog) a;//向下转型,本身是什么类型,才能转换成什么类型
d.swim(); //输出:狗在浮水
}
}
多态的高可拓展形实例:
// 程序功能:多态的应用;对一个类功能的拓展;
/*===============================起始 分割线================================*/
//问题:每当我们需要生产一种手机,就需要修改添加代码,这样很麻烦
class MiFactory {
/*public void createPhone(MiNote mi) {
mi.call();
}
public void createPhone(RedMi mi) {
mi.call();
}*/
public void createPhone(Phone p) { //好处:我们只需要写一个方法即可实现生产所有手机,即高可拓展性
p.call();
}
}
/*===============================结束 分割线================================*/
/*===============================起始 分割线================================*/
//解决:创建一个手机接口,让我们需要生产的手机都继承它
interface Phone {
public void call();
}
//小米Note
class MiNote implements Phone{
public void call() {
System.out.println("小米Note打电话");
}
}
//红米
class RedMi implements Phone {
public void call() {
System.out.println("红米打电话");
}
}
/*===============================结束 分割线================================*/
public class Main {
public static void main(String[] args) {
MiFactory factory = new MiFactory();
factory.createPhone(new MiNote());
factory.createPhone(new RedMi());
}
}
/* 输出:
小米Note打电话
红米打电话
*/