文章目录
- 前言
- 继承
-
- 概念
- 语法
- 父类成员变量访问
- 父类成员方法的访问
- [super 关键字](#super 关键字)
- 父类与子类的构造方法
- 父类与子类代码执行的顺序
- protected
- 继承的方式
- [final 关键字](#final 关键字)
- 多态
前言
本人的技术笔记会优先发布在个人站点longyxblg.cn
继承
面向对象思想中提出了继承的概念,专门用来进行共性抽取,实现代码复用。
概念
继承是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性
的基础上进行扩展,增加新功能,这样产生新的类,称子类(也称派生类或扩展类)。继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程。
语法
在 Java 中如果要表示类之间的继承关系,需要用到 extends 关键字
java
修饰符 class 子类 extends 父类 {
// ...
}
此时有一个 Animal 类, 有 Dog 类和 Cat 类继承他
java
public class Animal {
String name = "默认名称";
int age = 18;
public void eat(){
System.out.println(this.name + "正在吃饭");
}
}
java
public class Cat extends Animal{
}
java
public class Dog extends Animal{
}
子类会将父类中的成员变量和成员方法继承
java
public class Test {
public static void main(String[] args) {
//
Cat cat = new Cat();
Dog dog = new Dog();
System.out.println(cat.age);
System.out.println(cat.name);
System.out.println(dog.age);
System.out.println(dog.name);
cat.eat();
dog.eat();
}
}

父类成员变量访问
-
子类和父类不存在同名成员变量
参考上文代码。
-
子类和父类存在同名成员变量
javapublic class Dog extends Animal{ String name = "旺财"; }javaDog dog = new Dog(); Animal animal = new Animal(); System.out.println(dog.name); System.out.println(animal.name);
如果访问的成员变量子类中有,优先访问自己的成员变量。
父类成员方法的访问
-
成员方法不同名(或子类中没有该成员方法)
参考上文中的代码
-
成员方法同名
javapublic class Dog extends Anima{ String name = "旺财"; @Override public void eat() { System.out.println(this.name + "正在吃狗粮"); } }javaDog dog = new Dog(); Animal animal = new Animal(); dog.eat(); animal.eat();
同名时,子类对象会优先使用自己的成员方法。
super 关键字
在某些情况下,想要在子类中使用父类的成员方法和成员变量,这时候可以用到 super 关键字。
java
public class Animal {
String name = "默认名称";
int age = 18;
String variety = "我也不知道写什么";
public void eat(){
System.out.println(this.name + "正在吃饭");
}
}
java
public class Dog extends Animal{
String name = "旺财";
String variety = "中华田园犬";
public void eat() {
System.out.println(this.name + "正在吃狗粮");
}
public void test1(){
System.out.println(super.variety);
System.out.println("测试用 super 访问父类中的 variety");
}
}
java
Dog dog = new Dog();
System.out.println(dog.variety);
dog.test1();

java
public class Animal {
String name = "默认名称";
int age = 18;
String variety = "我也不知道写什么";
public void eat(){
System.out.println(this.name + "正在吃饭");
}
}
java
public class Dog extends Animal{
String name = "旺财";
String variety = "中华田园犬";
public void eat() {
System.out.println(this.name + "正在吃狗粮");
}
public void animalEat(){
super.eat();
}
public void test1(){
System.out.println(super.variety);
System.out.println("测试用 super 访问父类中的 variety");
}
}
java
Dog dog = new Dog();
dog.eat();
dog.animalEat();

super 关键字也可以访问父类中的成员方法。
需要注意的是 super 关键字不能在静态方法中使用。

父类与子类的构造方法
java
public class Animal {
String name = "默认名称";
int age = 18;
String variety = "我也不知道写什么";
public Animal(String name, int age, String variety) {
this.name = name;
this.age = age;
this.variety = variety;
}
public void eat(){
System.out.println(this.name + "正在吃饭");
}
}
给 Animal 中手写一个构造方法后,编译报错了

这其实是因为子类也会继承父类的构造方法,前文的代码中 Animal、Cat、Dog 中都没有写构造方法,这时编译器会给这三个类中自动生成构造方法。
Animal 中自动生成的是
java
public Animal() {
}
而到 Cat 和 Dog 类中生成的构造方法则是
java
public Cat(){
super;
}
java
public Dog(){
super;
}
编译器给子类自动生成的构造方法会用 super 关键字自动调用父类的构造方法。这意味着开发人员也可以用 super 关键字进行调用。
java
public class Animal {
String name = "默认名称";
int age = 18;
String variety = "我也不知道写什么";
public Animal(String name, int age, String variety) {
this.name = name;
this.age = age;
this.variety = variety;
}
public Animal() {
}
public void eat(){
System.out.println(this.name + "正在吃饭");
}
}
java
public class Dog extends Animal{
String name = "旺财";
String variety = "中华田园犬";
public void eat() {
System.out.println(this.name + "正在吃狗粮");
}
public void animalEat(){
super.eat();
}
public Dog() {
}
public Dog(String name, int age, String variety) {
super(name, age, variety);
}
public static void test2(){
// super 关键字不能在静态方法中使用。
// System.out.println(super.age);
}
public void test1(){
System.out.println(super.variety);
System.out.println("测试用 super 访问父类中的 variety");
}
}
java
Dog dog = new Dog("哈吉汪",2,"哈士奇");
dog.eat();
dog.animalEat();

通过调试窗口可以看到,在 Dog 类中调用使用 Super 方法后, 是给从 Animal 中继承下来的成员变量进行赋值。Dog 类中存在的同名成员变量并没有进行赋值。
-
this 是当前对象的引用,当前对象即调用实例方法的对象,super 相当于是子类对象中从父类继承下来部分成员的引用
-
在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
-
在构造方法中无论是 this 引用还是 super 引用都要放在构造方法的第一句


-
在构造方法中不能同时出现 this 引用和 super 引用
-
构造方法中一定会存在super(...)的调用,用户没有写编译器也会增加,但是this(...)用户不写则没有
-
this 和 super 都只能在类的非静态方法中使用, 访问非静态方法和字段
父类与子类代码执行的顺序
java
public class A {
int a;
static {
System.out.println("父类的静态代码块执行了");
}
{
this.a = 10;
System.out.println("父类的构造代码块执行了");
}
}
java
public class B extends A{
int b;
static {
System.out.println("子类的静态代码块执行了");
}
{
this.b = 20;
System.out.println("子类的构造代码块执行了");
}
}

可见是静态代码块先执行,父类的代码块比子类的代码块先执行
java
A a1 = new A();
B b1 = new B();
System.out.println("====我是一条分割线====");
A a2 = new A();
B b2 = new B();

静态代码块只会执行一次
初始化顺序为父类静态代码块 → 子类静态代码块 → 父类实例代码块 → 父类构造方法 → 子类实例代码块 → 子类构造方法
protected
在类与对象中介绍了 public private default 三个访问修饰限定符。
| 名称 | 同一个包同类 | 同一个包不同类 | 不同包中子类 | 不同包中的非子类 |
|---|---|---|---|---|
| public | √ | √ | √ | √ |
| protected | √ | √ | √ | |
| default | √ | √ | ||
| private | √ |
而 protected 主要用于继承中
此时新建一个 Demo2 包, 其中存放 C Test 类 , C 继承 A 类。
java
package Demo1;
public class A {
protected int a;
protected void test(){
System.out.println("这是一个A类中被 protected 修饰的方法");
}
public A(){
}
}
java
package Demo2;
import Demo1.A;
public class C extends A {
public void c_Test(){
System.out.println(super.a);
super.test();
}
public static void main(String[] args) {
C c1 = new C();
c1.c_Test();
}
}
java
package Demo2;
import Demo1.A;
public class Test {
public static void main(String[] args) {
A a1 = new A();
a1.test();
}
}

那么让 A 中出现一个被 private 修饰的变量
java
package Demo1;
public class A {
protected int a;
private int b;
protected void test(){
System.out.println("这是一个A类中被 protected 修饰的方法");
}
public A(){
}
java
public static void main(String[] args) {
C c1 = new C();
System.out.println();
}

通过调试查看到, 被 private 修饰的成员变量 b 也被集成到了子类中


但是就算被继承到了子类中也不能访问
继承的方式
Java 中只支持四种继承方式:
-
单继承
最简单的继承方式,例如
javapublic class A{ ... } public class B extends A{ ... } -
不同类继承同一个类
多个类继承同一个类,例如
javapublic class A{ ... } public class B extends A{ ... } public class C extends A{ ... } -
多层继承
Java 中不支持多继承,例如
javapublic class A{ ... } public class B { ... } public class C extends A,B{ ... }可以做到多层继承,例如
javapublic class A{ ... } public class B extends A { ... } public class C extends B{ ... }
一般不希望出现超过三层的继承关系. 如果继承层次太多, 就需要考虑对代码进行重构了。如果想从语法上进行限制继承, 就可以使用 final 关键字
final 关键字
可以用来修饰变量、成员方法以及类。
修饰变量
被 final 修饰的变量就会被视为常量,不允许修改,类似于 C 语言中的 const 关键字。

修饰类
被 final 修饰的类将不被允许继承
java
package Demo1;
final public class A {
}
java
package Demo1;
public class B extends A {
}

修饰成员方法
被 final 修饰的成员方法不允许被重写,会在后文介绍。
多态
面向对象的三大特性之一,简单点说就是不同的对象去完成同一个行为,会产生不同的状态。例如同样都是学习 Java , 张三学得非常轻松,李四学得非常痛苦。这就是多态。
Java 中多态的实现条件
在 Java 中想要实现多态,必须满足以下条件,缺一不可:
-
必须在继承体系下
-
子类必须要对父类中方法进行重写
-
通过父类的引用调用重写的方法
方法的重写
也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写, 返回值和形参都不能改变。
即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
规则
子类在重写父类的方法时,一般必须与父类方法原型一致: 返回值类型 、方法名 、(参数列表) 要完全一致。
java
package Demo1;
public class A {
public void test1(){
System.out.println("这是 A 类中的test1方法");
}
}
java
package Demo1;
public class B extends A{
@Override
public void test1() {
System.out.println("这是 B 类中的test1方法");
}
}
java
A a1 = new A();
B b1 = new B();
a1.test1();
b1.test1();

@Override 表示该方法被重写,编译器会多一层校验
- 被重写的方法返回值类型可以不同,但是必须是具有父子关系的

- 访问权限不能比父类中被重写的方法的访问权限更低

这里 B 类中没写修饰符默认为 default , 编译报错。
重写和重载
| 重写\重载 | 参数列表 | 方法名 | 返回值 | 访问修饰限定符 |
|---|---|---|---|---|
| 重写 | 必须相同 | 必须相同 | 必须相同,不同必须具有父子类关系 | 权限大于等于被重写的方法 |
| 重载 | 必须不同 | 必须相同 | 可以不同 | 可以不同 |
重写的原则
对于已经投入使用的类,尽量不要进行修改。最好的方式是:重新定义一个新的类,来重复利用其中共性的内容,并且添加或者改动新的内容。
通过重写,可以让同一个方法在被不同类型的对象使用时发生不同的行为,这正是多态的体现。
向上转型与动态绑定
现在新创建 Animal Dog Cat 类
java
package Demo2;
public class Animal {
String name;
int age;
String habit;
public Animal(String name, int age, String habit) {
this.name = name;
this.age = age;
this.habit = habit;
}
public void eat(){
System.out.println(this.name + "正在吃饭");
}
}
java
package Demo2;
public class Dog extends Animal{
public Dog(String name, int age, String habit) {
super(name, age, habit);
}
@Override
public void eat() {
System.out.println(this.name + "正在吃狗粮");
}
}
java
package Demo2;
public class Cat extends Animal{
public Cat(String name, int age, String habit) {
super(name, age, habit);
}
@Override
public void eat() {
System.out.println(this.name + "正在吃猫粮");
}
}
实际就是创建一个子类对象,将其当成父类对象来使用。发生在三种情况中:
- 直接在创建对象时发生向上转型
java
Animal animal = new Dog("大黄",1,"出去玩");
animal.eat();
在这里 new Dog 是实例化的一个 Dog 类对象,而 Animal 类对象 animal 却能引用这个对象,就是在此处发生了向上转型。

而 Animal 类型的对象 animal 在调用 eat 方法时输出的是 Dog 类中的 eat 方法。这是发生了动态绑定,JVM 发现 animal 实际是 Dog,就调用 Dog 的 eat。
- 方法传参时发生向上转型:
java
package Demo2;
public class Animal {
String name;
int age;
String habit;
public Animal(String name, int age, String habit) {
this.name = name;
this.age = age;
this.habit = habit;
}
public Animal(){
}
public void test1(Animal animal){
System.out.println(animal.name + "这是 Animal 类的 test1 方法");
}
public void eat(){
System.out.println(this.name + "正在吃饭");
}
}
java
package Demo2;
public class Dog extends Animal{
public Dog(String name, int age, String habit) {
super(name, age, habit);
}
@Override
public void eat() {
System.out.println(this.name + "正在吃狗粮");
}
@Override
public void test1(Animal animal) {
System.out.println(animal.name + "这是 Dog 类的 test1 方法");
}
}
java
Dog dog = new Dog("大黄",1,"出去玩");
Animal animal = new Animal();
dog.test1(dog);

- 方法返回值发生向上转型
java
package Demo2;
public class Dog extends Animal{
public Dog(String name, int age, String habit) {
super(name, age, habit);
}
public Dog() {
super();
}
@Override
public void eat() {
System.out.println(this.name + "正在吃狗粮");
}
@Override
public void test1(Animal animal) {
System.out.println(animal.name + "这是 Dog 类的 test1 方法");
}
public static Dog test2(){
return new Dog();
}
}
java
Animal animal = Dog.test2();
System.out.println();

调试窗口可以看到也成功赋值了。
需要注意的是,不能调用到子类特有的方法
java
package Demo2;
public class Dog extends Animal{
public Dog(String name, int age, String habit) {
super(name, age, habit);
}
public Dog() {
super();
}
@Override
public void eat() {
System.out.println(this.name + "正在吃狗粮");
}
@Override
public void test1(Animal animal) {
System.out.println(animal.name + "这是 Dog 类的 test1 方法");
}
public static Dog test2(){
return new Dog();
}
public void test3(){
System.out.println("这是 Dog 类独有的 test3 方法");
}
}
java
Animal animal = new Dog();
animal.test3();

向下转型
将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法,但有时候可能需要调用子类特有的方法,此时:将父类引用再还原为子类对象即可,即向下转型。
java
Animal animal = new Dog();
Dog dog = (Dog) animal;
dog.test3();

需要注意的是向下转型会用到强制类型转换,这本来就是不安全的。如果转换失败,运行时会抛出异常。Java 中为了提高向下转型时的安全性,引入了 instanceof 关键字。

java
Animal animal = new Animal();
if(animal instanceof Dog){
Dog dog = (Dog) animal;
}
else
{
System.out.println("不能这么转换!");
}

多态的使用
此时新建另一个包 Demo3,在这个包下新建 Animal Dog Cat Bird Fish 类,Dog Cat Bird Fish 继承 Animal 。
java
package Demo3;
public class Animal {
public void eat(){
System.out.println("吃饭!");
}
}
java
package Demo3;
public class Dog extends Animal{
@Override
public void eat() {
System.out.println("吃狗粮!");
}
}
java
package Demo3;
public class Cat extends Animal{
@Override
public void eat() {
System.out.println("吃猫粮");
}
}
java
package Demo3;
public class Bird extends Animal{
@Override
public void eat() {
System.out.println("吃虫子!");
}
}
java
package Demo3;
public class Fish extends Animal{
@Override
public void eat() {
System.out.println("吃蚯蚓!");
}
}
现在想输出每个类的 eat 方法。采用分支语句可能要这么写
java
public static void main(String[] args) {
Dog dog = new Dog();
Cat cat = new Cat();
Bird bird = new Bird();
Fish fish = new Fish();
Animal[] animals = {dog,cat,bird,fish};
for (Animal animal: animals){
if(animal.equals(dog)){
dog.eat();
}else if(animal.equals(cat)){
cat.eat();
}else if(animal.equals(bird)){
bird.eat();
}else if(animal.equals(fish)){
fish.eat();
}
}

而是用多态的话就简单多了
java
Dog dog = new Dog();
Cat cat = new Cat();
Bird bird = new Bird();
Fish fish = new Fish();
Animal[] animals = {dog,cat,bird,fish};
for(Animal animal: animals){
animal.eat();
}
}

新增一个 Monkey 类

这时要输出这个 Monkey 类的 eat() 方法使用多态完成也更简单
java
Dog dog = new Dog();
Cat cat = new Cat();
Bird bird = new Bird();
Fish fish = new Fish();
Monkey monkey = new Monkey();
Animal[] animals = {dog,cat,bird,fish,monkey};
for(Animal animal: animals){
animal.eat();
}

但多态并非没有缺点;
-
属性没有多态性,当父类和子类都有同名属性的时候,通过父类引用,只能引用父类自己的成员属性
-
构造方法没有多态性
避免在构造方法中调用重写的方法
java
public class A {
public A(){
func();
}
public void func(){
System.out.println("A类中的func方法执行了");
}
}
java
public class B extends A{
private int num = 1;
@Override
public void func() {
System.out.println("B类中的func方法执行了"+num);
}
}
java
B b = new B();

在实例化 b 时, 会调用 A 中的构造方法,而构造方法中又调用了 func 方法。这个过程是由 b 这个对象调用的,触发动态绑定,直接调用了 B 类中被重写的 func 方法,而此时 b 对象还没有被构造完成,num 处于未初始化的状态,所以为 0 。如果具有多态性的话,num 值应该为 1 。