文章目录
- 前言
- 继承
- 继承语法
- 继承总结
- super指定访问父级
- 子类构造方法
- super和this
- 再谈初始化(执行顺序)
- protected 关键字
- 继承方式
- final 关键字
- 继承与组合
- 多态
- 动态绑定与静态绑定
- 多态实现条件
- 重写
前言
适合复习看
继承
继承语法
修饰符 class 子类 extends 父类 {
// ...
}
子类会将父类中的成员变量或者成员方法继承到子类中了
子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了
继承总结
在子类方法中 或者 通过子类对象访问成员时:
- 如果访问的成员变量子类中有,优先访问自己的成员变量。
- 如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错。
- 如果访问的成员变量与父类中成员变量同名,则优先访问自己的
成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找
子类中访问父类的成员方法时:
- 通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到则访问,否则编译报错。
- 通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用方法视传递的参数选择合适的方法访问,如果没有则报错
super指定访问父级
Java提供了super关键字,该关键字主要作用:在子类方法中访问父类的成员。
注意
- 只能在非静态方法中使用
- 在子类方法中,访问父类的成员变量和方法
子类构造方法
父子父子,先有父再有子,即:子类对象构造时,需要先调用基类构造方法,然后执行子类的构造方法
在子类构造方法中,并没有写任何关于基类构造的代码,但是在构造子类对象时,先执行基类的构造方法,然后执行子类的构造方法,因为:子类对象中成员是有两部分组成的,基类继承下来的以及子类新增加的部分 。父子父子肯定是先有父再有子,所以在构造子类对象时候 ,先要调用基类的构造方法,将从基类继承下来的成员构造完整,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整。
注意:
- 若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用基类构
造方法- 如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的
父类构造方法调用,否则编译失败。- 在子类构造方法中,super(…)调用父类构造时,必须是子类构造函数中第一条语句。
- super(…)只能在子类构造方法中出现一次,并且不能和this同时出现(构造方法 this() 和 super() 都必须在第一行,所以不能同时出现)
super和this
【相同点】
- 都是Java中的关键字
- 只能在类的非静态方法中使用,用来访问非静态成员方法和字段
- 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在
【不同点】
- this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父类继承下来部分成
员的引用- 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
- 在构造方法中:this(…)用于调用本类构造方法,super(…)用于调用父类构造方法,两种调用不能同时在构造
方法中出现- 构造方法中一定会存在super(…)的调用,用户没有写编译器也会增加,但是this(…)用户不写则没有
再谈初始化(执行顺序)
1、父类静态代码块优先于子类静态代码块执行,且是最早执行
2、父类实例代码块和父类构造方法紧接着执行
3、子类的实例代码块和子类构造方法紧接着再执行
4、第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行
使用static关键字定义的代码块,一般用于初始化静态成员属性;静态代码块不管生成多少个对象,其只会执行一次,且是最先执行的;然后就地初始化与普通(实例)代码块初始化看书写的先后顺序,写在前面的先执行;然后才是构造方法初始化。
protected 关键字
子类 就是 基类(父类)的派生类(子类)
protected修饰的 可以调用 在不同包中的,继承该包的类的子类
注意:父类中private成员变量虽然在子类中不能直接访问,但是也继承到子类中了
什么时候下用哪一种呢?
我们希望类要尽量做到 “封装”, 即隐藏内部实现细节, 只暴露出 必要 的信息给类的调用者.
因此我们在使用的时候应该尽可能的使用 比较严格 的访问权限. 例如如果一个方法能用 private, 就尽量不要用 public.
另外, 还有一种 简单粗暴 的做法: 将所有的字段设为 private, 将所有的方法设为 public. 不过这种方式属于是
对访问权限的滥用, 还是更希望同学们能写代码的时候认真思考, 该类提供的字段方法到底给 “谁” 使用(是类内部自己用, 还是类的调用者使用, 还是子类使用)
继承方式
Java中只支持以下几种继承方式:
注意:Java中不支持多继承。
时刻牢记, 我们写的类是现实事物的抽象. 而我们真正在公司中所遇到的项目往往业务比较复杂, 可能会涉及到一系列复杂的概念, 都需要我们使用代码来表示, 所以我们真实项目中所写的类也会有很多. 类之间的关系也会更加复杂.
但是即使如此, 我们并不希望类之间的继承层次太复杂. 一般我们不希望出现超过三层的继承关系. 如果继承层次太多, 就需要考虑对代码进行重构了.
如果想从语法上进行限制继承, 就可以使用 final 关键字
final 关键字
final关键可以用来修饰变量、成员方法以及类。
- 修饰变量或字段,表示常量(即不能修改)
final int a = 10;
a = 20; // 编译出错
- 修饰类:表示此类不能被继承
final public class Animal {
...
}
public class Cat extends Animal {
...
}
// 编译出错
//Error:(3, 27) java: 无法从最终com.bit.Animal进行继承
我们平时是用的 String 字符串类, 就是用 final 修饰的, 不能被继承
- 修饰方法:表示该方法不能被重写
继承与组合
和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果。组合并没有涉及到特殊的语法
(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段。
继承表示对象之间是is-a的关系,比如:狗是动物,猫是动物
组合表示对象之间是has-a的关系,比如:汽车和其轮胎、发动机、方向盘、车载系统等的关系就应该是组合,因为汽车是有这些部件组成的
class Student2 {
}
class Teacher {
}
class School {
public Student2[] student2s = new Student2[10];
public Teacher[] teachers = new Teacher[5];
//不用继承就能使用"父类"
}
注意组合和继承都可以实现代码复用,应该使用继承还是组合,需要根据应用场景来选择,一般建议:能用组合尽量用组合
多态
动态绑定与静态绑定
多态的本质就是利用动态绑定,不同对象执行的效果也不一样
动态绑定
(假设,有一父类和一子类,里面都有除了内容外完全相同的eat方法)
创建父类类型的引用A,让A指向子类(new子类),然后用A调用eat方法时
编译的时候 认为 还是调用了父类的eat方法, 不过运行的时候 绑定到了子类当中
静态绑定
编译的时候,已经确定了调用那个方法了
(例如,类中有不带参数的构造方法A和带参数的构造方法B,调用构造方法时,带参数那就是执行构造方法B,不带参数则执行构造方法A)
多态实现条件
在java中要实现多态,必须要满足如下几个条件,缺一不可:
- 必须在继承体系下
- 子类必须要对父类中方法进行重写
- 通过父类的引用调用重写的方法
多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法
父类:
class Animal {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void eat() {
System.out.println("eat");
}
}
两个子类:
class Cat extends Animal {
Cat(String name) {
super.setName(name);
}
@Override//对父类的eat进行重写
public void eat() {
System.out.println(super.getName() + "吃猫粮");
}
}
class Dog extends Animal {
Dog(String name) {
super.setName(name);
}
@Override//对父类的eat进行重写
public void eat() {
System.out.println(super.getName() + "吃狗粮");
}
}
测试:
class test {
public static void main(String[] args) {
Animal animal = new Cat("猫猫");//向上转型,把子类传给父类的类型
animal.eat();
animal = new Dog("狗子");
animal.eat();
//打印为:
//猫猫吃猫粮
//狗子吃狗粮
}
}
重写
重写(override):也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法
方法重写规则:
- 子类在重写父类的方法时,一般必须与父类方法原型一致: 返回值类型 方法名 (参数列表) 要完全一致
- 被重写的方法返回值类型可以不同,但是(返回类型)必须是具有父子关系的
- 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类方法被public修饰,则子类中重写该方
法就不能声明为 protected- 父类被static、private修饰的方法、构造方法都不能被重写。
- 重写的方法, 可以使用 @Override 注解来显式指定. 有了这个注解能帮我们进行一些合法性校验. 例如不小心将方法名字拼写错了 (eat比如写成 aet), 那么此时编译器就会发现父类中没有 aet 方法, 就会编译报错, 提示无法
构成重写