继承
继承概念
什么是继承?
简单来说就是对每个类的共性进行了抽取。
好处:可以达到代码的复用。
继承(inheritance)机制:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特 性的基础上进行扩展,增加新功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用。
就比如下面这两个代码:
public class Dog {
public String name;
public int age;
public void eat(){
System.out.println(this.name + "正在吃饭...");
}
public void bark(){
System.out.println(this.name + "正在汪汪叫...");
}
}
public class Cat {
public String name;
public int age;
public void eat(){
System.out.println(this.name + "正在吃饭...");
}
public void miaomiao(){
System.out.println(this.name + "正在喵喵叫...");
}
}
我们发现这两个类中都有 name,age,eat(),这些共性的内容,因此我们就可以将共性的内容进行抽取,然后采用继承的思想来达到。

继承的语法
Java中如果要表示类之间的继承关系,需要借助extends关键字,具体如下:
修饰符 class 子类 extends 父类{
//....
}
那么我们就可以对上面代码进行修改一下了:
public class Animal {
public String name;
public int age;
public void eat(){
System.out.println(this.name + "正在吃饭...");
}
}
public class Cat extends Animal{
public void miaomiao(){
System.out.println(this.name + "正在喵喵叫...");
}
}
public class Dog extends Animal{
public void bark(){
System.out.println(this.name + "正在汪汪叫...");
}
}
注意:
- 子类会将父类中的成员变量或者成员方法继承到子类中。
- 子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了。
super关键字
**super关键字的主要作用:在子类方法中访问父类的成员。**因为有的时候父类和子类可能会存在相同的名称的成员,此时就可以在子类方法中访问父类同名的成员了。
public class Base {
public int a = 1;
public int b = 2;
}
public class Derievd extends Base{
public int c = 3;
public void func(){
System.out.println(this.a);
System.out.println(this.b);
System.out.println(this.c);
}
}

但是当子类成员和父类成员变量同名的时候,是优先访问子类的。
而且如果想要父类成员变量,只能通过super进行访问。
public class Base {
public int a = 1;
public int b = 2;
}
public class Derievd extends Base{
public int c = 3;
public int a = 100;//和父类成员变量同名
public void func(){
System.out.println(this.a);
System.out.println(this.b);
System.out.println(this.c);
System.out.println(super.a);//访问父类成员变量a
}
public static void main(String[] args) {
Derievd derievd = new Derievd();
derievd.func();
}
}
//结果
100
2
3
1

成员方法也是一样:
- 通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到则访问,否则编译报错。
- 通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用方法适传递的参数选择合适的方法访问,如果没有则报错;
public class Base {
public int a = 1;
public int b = 2;
public void testA(){
System.out.println("testA()...");
}
public void testBase(){
System.out.println("testBase()...");
}
}
public class Derievd extends Base{
public int c = 3;
public int a = 4;//和父类成员变量同名
//构成重写
public void testA(){
System.out.println("testA() Derived....");
}
public void testDerived(){
System.out.println("testDerived() ....");
}
//构成重载
public void testA(char a){
System.out.println("testA(char a)....");
}
}
super和this异同
相同点:
- 都是Java关键字
- 只能在类的非静态方法中使用,用来访问非静态成员方法和字段。
- 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在。
不同点:
- this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父类继承下来部分成员的引用。
- 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性。
- 在构造方法中:this(…)用于调用本类构造方法,super(…)用于调用父类构造方法,两种调用不能同时在构造方法中出现。
- 构造方法中一定会存在super(…)的调用,用户没有写,编译器也会增加,但是this(…)用户不屑则没有。
子类构造方法
子类对象构造时,需要先调用基类构造方法,然后执行子类的构造方法。
public class Base {
public int a = 1;
public int b = 2;
public Base() {
System.out.println("Base()构造方法...");
}
}
public class Derievd extends Base{
public int c = 3;
public int a = 4;//和父类成员变量同名
public Derievd(){
//super(); //注意子类构造方法中默认会调用基类的无参构造方法:super();
//用户没有写时,编译器会自动添加,而且super()必须是子类构造方法中第一条语句。
//并且只能出现一次
System.out.println("Dervied()构造方法...");
}
public static void main(String[] args) {
Derievd derievd = new Derievd();
}
}
结果打印:
Base()
Derived()
上面是不带参数的,单参数的话有两种写法:
public class Base {
public int a = 1;
public int b = 2;
public Base() {
System.out.println("Base()构造方法...");
}
//有参数的构造方法
public Base(int a, int b) {
this.a = a;
this.b = b;
}
}
public class Derievd extends Base{
public int c = 3;
public int a = 4;//和父类成员变量同名
//写法一:
public Derievd(int a, int b) {
super(a, b);
}
//写法二:
public Derievd() {
super(1,1);
}
}
注意:
- 若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用基类构造方法
- 如果父类构造方法是带有参数的,此时 需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败。
- 在子类构造方法中,super(…)调用父类构造时,必须是子类构造函数中第一条语句。
- super(…)只能在子类构造方法中出现一次,
- 由于使用super()必须在第一行,this()也必须在第一行,换句话来说,super()和this()不能同时出现。
再谈初始化
在上一篇文章中,我们谈到过没有继承条件下的代码块之间的执行顺序:
- 静态代码块先执行,并且只执行一次,在类加载阶段执行。
- 当有对象创建时,才会执行实例代码块,实例代码块执行完成后,最后构造方法执行。
那现在我们在来看看在继承条件下的代码块之间的执行顺序:
public class Animal {
public String name;
public int age;
static{
System.out.println("static :: Animal{}...");
}
{
System.out.println("实例代码块 Animal{}..");
}
public Animal(){
System.out.println("Animal{}..");
}
}
public class Cat extends Animal{
static {
System.out.println("static :: Cat{}");
}
{
System.out.println("实例代码块 Cat{}");
}
public Cat(){
System.out.println("Cat()....");
}
public static void main(String[] args) {
Cat cat = new Cat();
System.out.println("==========");
Cat cat2 = new Cat();
}
}
输出结果:
static :: Animal{}...
static :: Cat{}
实例代码块 Animal{}..
Animal{}..
实例代码块 Cat{}
Cat()....
==========
实例代码块 Animal{}..
Animal{}..
实例代码块 Cat{}
Cat()....

protected 关键字
在类和对象章节中,为了实现封装特性,Java中引入了访问限定符,主要限定:类或者类中成员能否在类外或者其他包中被访问。

那父类中不同访问权限的成员,在子类中的可见性又是什么样子的呢?
// 为了掩饰基类中不同访问权限在子类中的可见性,为了简单类B中就不设置成员方法了
// extend01包中
public class B {
private int a;
protected int b;
public int c;
int d;
}
// extend01包中
// 同一个包中的子类
public class D extends B{
public void method(){
// super.a = 10; // 编译报错,父类private成员在相同包子类中不可见
super.b = 20; // 父类中protected成员在相同包子类中可以直接访问
super.c = 30; // 父类中public成员在相同包子类中可以直接访问
super.d = 40; // 父类中默认访问权限修饰的成员在相同包子类中可以直接访问
}
}
// extend02包中
// 不同包中的子类
public class C extends B {
public void method(){
// super.a = 10; // 编译报错,父类中private成员在不同包子类中不可见
super.b = 20; // 父类中protected修饰的成员在不同包子类中可以直接访问
super.c = 30; // 父类中public修饰的成员在不同包子类中可以直接访问
//super.d = 40; // 父类中默认访问权限修饰的成员在不同包子类中不能直接访问
}
}
// extend02包中
// 不同包中的类
public class TestC {
public static void main(String[] args) {
C c = new C();
c.method();
// System.out.println(c.a); // 编译报错,父类中private成员在不同包其他类中不可见
// System.out.println(c.b); // 父类中protected成员在不同包其他类中不能直接访问
System.out.println(c.c); // 父类中public成员在不同包其他类中可以直接访问
// System.out.println(c.d); // 父类中默认访问权限修饰的成员在不同包其他类中不能直接访问
}
}
注意:父类中private成员变量虽然在子类中不能直接访问,但是也继承到子类中了
继承方式
在Java中只支持以下几种继承方式:

Java虽然不支持多继承,但是我们可以通过接口的形式来支持多继承,后面写到接口时会讲到。
final关键字
- 修饰变量或字段,表示常量(证明此时这个变量的值将不能被修改)
public static void main(String[] args) {
final int a = 10;
a = 20;//编译报错
System.out.println(a);
}
- 修饰类:表示此类不能被继承

- 修饰方法:表示该方法不能被重写(后序介绍)
继承与组合
和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果。组合并没有涉及到特殊的语法(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段。
如果说继承表示对象之间是is-a的关系,比如:狗是动物,猫是动物
那么组合表示对象之间是has-a(或a part of)的关系,比如:汽车
//学生类
class Student{
....
}
//老师类
class Teacher{
...
}
class School{
private Student stu; //可以复用学生类中的属性和方法
private Teacher teacher; //可以复用老师类中的属性和方法
.....
}
组合和继承都可以实现代码复用,应该使用继承还是组合,需要根据应用场景来选择,一般建议:能用组合尽量用组合。

被折叠的 条评论
为什么被折叠?



