Java学习笔记04——子类与继承

本文详细介绍了Java中的子类与父类的关系,包括子类的声明、instanceof运算符的使用、Object类的重要性。讨论了子类的继承性,如在不同包中的访问权限。讲解了成员变量的隐藏、方法重写,以及Super关键字的应用。还涉及final关键字的作用,对象的上转型对象的概念,以及面向对象编程的三大特性:封装、继承与多态。文章最后探讨了abstract类和abstract方法,以及开-闭原则在设计中的应用。

1、子类与父类

由继承得到的类称为子类,被继承的类称为父类(超类, superclass)。当我们编写一个类的时候,发现某个类已经有我们所需的部分成员变量和方法,我们可以将新编写的类继承为这个类的子类。子类继承了父类的部分变量和方法,也可以自己创建新的变量和方法。

Java不支持多重继承,一个子类只能有一个父类,而一个父类可以有多个子类。

子类的声明

在类的声明中,通过使用关键字extends来定义一个类的子类:

/* 格式 */
class 子类名 extends 父类名 {
...
}
/* 举例 */
class Fruit { // 父类
... 
}
class Apple extends Fruit { // 子类,使用 extends 关键字继承父类的变量与方法
...
}

instanceof运算符

instanceof运算符是Java独有的运算符,其左边的操作元是对象,右边的操作员是类。当左边的对象属于右边的类或其子类创建的对象,该运算结果为true,否则结果为false

 Fruit fruit = new Fruit();
 Apple apple = new Apple();
 System.out.println(fruit instanceof Apple);	// 输出 false
 System.out.println(apple instanceof Apple);	// 输出 true
 System.out.println(apple instanceof Fruit);	// 输出 true

Object类与类的树形结构

在Java中,所有的类都有一个相同的源头: Object类,所有的类都间接或直接的继承于 Object类,若某个类在定义的时候没有使用extends关键字,这个类将被默认为 Object 的子类。由于一个子类只能有一个父类,而一个父类可以有多个子类;所有的类共同形成一个树形结构,而 Object 类就是这棵树的根节点。

2、子类的继承性

所谓子类继承了父类中的变量和方法,就像在子类中直接定义了这些继承的变量和方法,可以被子类中自己定义的任何实例方法调用。

子类和父类在同一个包中

如果子类和父类在同一个包中,子类将继承父类中非私有(private)的变量和方法,继承的变量和方法的访问权限保持不变。

子类和父类不在同一个包中

当子类和父类不在同一个包中,仅受保护的(protected)和公有的(public)方法和变量被继承 。

访问权限

在Java中存在三种访问限制修饰符:private、protected、public,对应四种级别的访问权限,下表列举了被每种修饰符修饰的方法和变量能在什么条件下被访问:

修饰符级别类自身的方法相同包中的类非相同包中的类
private私有××
友好×
protected保护×
public公有

补充

  • 还可以用public修饰类,被public修饰的类能被所有包中的类引用。而友好类只能被相同包中的类引用。
  • 友好类和保护类在访问时的区别,假设存在A、B和C三个类,B是A的子类,而C与A、B无继承关系;在C中创建B类的对象b,该对象b通过.运算符访问变量或方法。
    • 若C和A在同一个包中,B在另一个包中,则通过b只能访问到A中的保护方法。因为B继承了A的保护变量和保护方法,但没有继承A中的友好变量与友好方法;又因为C与A在同一个包中,C得以访问到不在同一个包中被B继承的A类的保护变量和保护方法。

3、子类中的成员变量和方法

当用子类的构造方法创建一个对象时,不仅子类中声明的成员变量被分配了内存空间,父类中所有的成员变量(即使没被继承)也会被分配内存空间。可以通过子类继承的方法对未继承的变量进行操作。

成员变量的隐藏

在子类中也可以声明成员变量,但如果声明的成员变量和从父类中继承的成员变量名字相同时(类型可以不同),从父类中继承的成员变量就会被隐藏。这时:

  1. 子类对象及子类定义的方法操作该同名变量将指定子类声明的成员变量。
  2. 子类所继承的方法操作该同名变量将指定被隐藏的继承变量。

方法重写(覆写)

子类通过重写(覆写)可以隐藏已继承的方法。

重写注解@Override

重写注解@Override是放在重写后的方法前,告诉编译器后面的方法是重写方法。如果加上该注释后重写错误(没有重写成功但方法本身没有错误),编译器也会提示错误。

重写注解并非强制要求,但在重写方法前加上@Override注解,代码会更加规范。

重写的语法规则

  1. 如果子类可以继承父类的某个方法,那么子类就可以对该方法进行重写。
  2. 重写方法的类型可以改变,但只能由原类型改为其继承类型。
  3. 方法的名字、参数个数、参数类型必须和父类完全相同(参数名可以改变)。
  4. 不允许使用比被重写方法更严格的访问权限。
class A {
    Object response() {
        return null;
    }
}
class B extends A {
    @Override
    Integer response() {    // Integer属于 Object 的子类,合法
        return 1;
    }
}
class C extends A {
    @Override
    protected Object response() {   // protected访问权限更高,合法
        return null;
    }
}
class D extends A {
    @Override
    Integer response(Integer i) {   // 参数个数不同,非法
        return i;
    }
}
class E extends A {
    @Override
    private Object response() {    // private访问权限更低,非法
        return null;
    }
}

4、Super关键字

用 super 操作被隐藏的成员变量和方法

如果想在子类中使用被子类隐藏的成员变量和方法,就需要使用关键字super。

class Fruit {
    int index = 0;
    int getIndex() {
        return index;
    }
}
class Apple extends Fruit {
    int index = super.index + 1;
    int getIndex() {
        return index;
    }
    int getFatherIndex() {
        return super.getIndex();
    }
}
public class Test {
    public static void main(String[] args) {
        Apple apple = new Apple();
        System.out.println(apple.getIndex());       // 输出1
        System.out.println(apple.getFatherIndex()); // 输出0
    }
}

用super调用父类的构造方法

子类在构造方法中需使用 super 来调用父类的构造方法,而且 super 必须是子类构造方法中的第一条,如果子类没有指定使用父类的哪个构造方法,那么系统默认使用 super();。因此,如果在父类中定义了多个构造方法时,应包括一个不带参数的构造方法,避免子类省略 super 时出现错误。

class Fruit {
    float weight;
    Fruit() {
        weight = 1.0f;
    }
    Fruit(float weight) {
        this.weight = weight;
    }
}
class Apple extends Fruit {
    char color;
    /* 没有指定,默认调用 super() */
    Apple() {
        color = 'r';
    }
    /* 指定调用有参数的构造方法 */
    Apple(float weight, char color) {
        super(weight);
        this.color = color;
    }
}

5、final关键字

final(中文意思:最后的、终极的)关键字可以修饰类、成员变量和方法中的局部变量。

final类

使用final关键字可以将类声明为final类(最终类),这种类不能被继承,即不能有子类。

final class A {
    ...
}

final方法

使用final修饰类中的一个方法,则这个方法不能被子类重写。

常量

使用final修饰的变量称为常量,常量在运行期间不能做任何修改。因此,常量在声明时没有默认值,并且必须在声明时就指定常量的值。

final int a = 0;    // 合法
final int b;        // 错误,必须声明时就给出值
a += 1;             // 错误,不能再做出任何修改

注意:对于引用类型数据,其不可更改指的是其引用不可更改,其引用的实体中的值是有可能被改变的。

6、对象的上转型对象

允许父类对象中引用子类对象的实体,假设Fruit是Apple的父类,则可以:

Fruit fruit = new Apple()/* 或者 */
Apple apple = new Apple();
fruit = apple;

这时,可以称fruit是apple的上转型对象。

上转型对象会失去原对象的一些属性和功能。

  1. 上转型对象不能操作子类新增的成员变量,也不能调用子类新增的方法。
  2. 上转型对象可以访问子类继承或隐藏的成员变量。
  3. 也可以调用子类继承的方法或子类重写的实例方法。但如果被重写的是静态方法,上转型对象将调用父类中重写的方法。
  4. 可以将上转型对象再强转回子类对象,这时对象将恢复失去的属性和功能。

7、面向对象编程的三大特性:封装、继承与多态

封装、继承与多态是面向对象编程的三大特性。

  1. 封装:将描述一个对象的属性和行为的代码封装在一个类中,属性用变量定义,行为用方法定义,方法可以直接访问同一个类中的属性。
  2. 继承:子类继承父类的特征和行为。子类可以获得父类的变量和方法,也可以对父类进行扩展,或者重写父类的方法。
  3. 多态:父类的某个方法被其子类重写时,可以各自产生自己的功能行为。比如某个上转型对象所产生的行为并不确定(因为不同的子类在重写父类的方法时可能产生不同的行为)。

8、abstract类和abstract方法

用abstract关键字修饰的方法称为abstract方法(抽象方法),抽象方法只允许声明,不允许实现(没有方法体)。同时不能使用static或final关键字修饰抽象方法。

abstract int sum(int a, int b);

用abstract关键字修饰的类称为abstract类(抽象类)。

abstract class A {
...
}

抽象类具有如下特点:

  1. 允许抽象类拥有抽象方法(其他类中不能有抽象方法),也允许拥有非抽象方法。
  2. 不能使用new运算符创建抽象类的对象。
  3. 如果某个非抽象类是抽象类的子类,则其必须重写父类的抽象方法,给出方法体。
  4. 可以使用抽象类对象引用其子类的实体作为上转型对象,这个上转型对象可以调用子类重写的方法。

为什么要有抽象类?

  1. 抽象类可以抽象出重要的标准,该标准用抽象方法来表示。
  2. 抽象类声明的对象可以成为其子类的上转型对象,调用子类重写的方法。这样可以使开发者将精力集中在标准上,更有利于设计出易维护、易扩展的程序。

9、开-闭原则

所谓开-闭原则(Open-Closed Principle)就是指让设计的系统对扩展开发,对修改关闭。意思是当系统中增加新的模块时,不需要修改现有的模块。在设计程序时,应首先考虑到用户需求的变化,将应对用户变化的部分设计为对扩展开放,而基本结构是对修改关闭的。遵守了开-闭原则的系统是易维护的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值