Java:继承和多态

1.包

在Java中,包是组织类的一种方式。

使用包的主要目的是保证类的唯一性。

例如,你在代码中写了一个Test类,你的同事也写了一个Test类,当两个同名的类出现时,就会产生冲突。

1.1 import和package的区别

在Java中,当我们需要用到某一个具体的类时,就需要用到import关键字;有时我们会看到一个类的最上面会有package…字段,表示的是当前类存放在哪一个包中,即存放在哪一个文件夹中。

例如:

package demo2;

import java.util.Date;
public class Test {
    public static void main(String[] args) {
        Date date = new Date();
        // 得到一个毫秒级别的时间戳
        System.out.println(date.getTime());
   }
}

代码中的package就表示当前类存放在文件夹demo2中,import引用的util包下的Date类,import还有一种引用方式,如下:

import java.util.*;

这个*代表util包下的所有类,但这样写不会把这个包中的所有类都引入进来,而是在你后续的代码中用到了其中的哪一个类,他就会自动引入对应的类。(这里与C语言中的include不一样,C语言中的include引入一个包会将整个包都引入进来)

但是*这种引用方式也有不好的地方,对于Date类,不止util包中有,如果我们同时引用两个包含Date类的包,这时候系统就不知道该调用哪一个,就会出现错误。

1.2 静态导入

使用import static可以导入包中的静态的方法和字段,例如:

import static java.lang.System.*;
public class Test {
    public static void main(String[] args) {
        out.println("hello");
   }
}

一般情况下我们的输出是System.out,但是当我们引用了System包后,程序中就不用再写System了,这种静态导入不常用,并且也降低了代码的可读性。

2.继承

我们知道面向对象的三大属性是封装、继承和多态,封装是将不必要公开的成员和方法用private关键字进行修饰,这样这些成员和方法就只能在类内使用了,也保证了其安全性。

那么继承的意义是什么呢?我们来看一段代码:

//猫类
class Cat { 
 public String name; 
 
 public Cat(String name) { 
 this.name = name; 
 } 
 
 public void eat(String food) { 
 System.out.println(this.name + "正在吃" + food); 
 } 
} 

//鸟类
class Bird { 
 public String name; 
 
 public Bird(String name) { 
 this.name = name; 
 } 
 
 public void eat(String food) { 
 System.out.println(this.name + "正在吃" + food); 
 } 
 
 public void fly() { 
 System.out.println(this.name + "正在飞 ︿( ̄︶ ̄)︿"); 
 } 
}

在猫和鸟这两个类中,我们可以发现,他们都有名字和eat方法,上述写法有大量重复的代码。这时候我们就可以让继承登场了,继承的意义就在于可以抽取不同类中的共性,从而减少冗余,来看下面通过继承来实现的代码:

//动物类
public class Animal { 
 public String name; 
 
 public Animal(String name) { 
 this.name = name; 
 } 
 
 public void eat(String food) { 
 System.out.println(this.name + "正在吃" + food); 
 } 
} 

//猫类
class Cat extends Animal{ 
 public Cat(String name){
     super(name);
 }
} 

//鸟类
class Bird extends Animal{ 
 public Bird(String name){
     super(name);
 }
 
 public void fly() { 
 System.out.println(this.name + "正在飞 ︿( ̄︶ ̄)︿"); 
 } 
}

我们称Animal类为父类、超类和基类,Cat和Bird这样的类称为子类、派生类。

语法

  • 使用extends指定父类
  • Java中一个子类只能继承一个父类
  • 子类会继承父类的所有public的字段和方法
  • 父类的private字段和方法,子类中无法访问
  • 子类的实例中,包含着父类的实例,可以用super关键字得到父类实例的引用

2.1 访问权限

在这里插入图片描述

在Java中,访问权限修饰符有4个,其作用范围如上图所示,对于选择哪种修饰符需要结合具体情况。

2.2 final关键字

规则:

  • final修饰变量时,表示常量,即不能修改,并且需要在创建的时候就赋初值
  • final修饰类时,表示被修饰的类不能被继承

我们平时使用的String字符串类,就是用final修饰的,不能被继承。

3. 多态

3.1 向上转型

我们用上面的鸟类来创建一个鸟对象,代码应该是这样的:

Bird bird = new Bird("小鸟鸟");

这段代码也可以写成这样:

Animal bird = new Bird("小鸟鸟");

此时的bird是一个父类(Animal)的引用,指向一个子类(Bird)的实例,这种写法就称为向上转型

向上转型发生的时机:

  • 直接赋值
  • 方法传参
  • 方法返回

3.2 动态绑定

当子类和父类中出现同名方法时,我们调用的时候会调用哪一个呢?

我们对上面的代码稍加修改,如下:

// Animal.java 
public class Animal { 
 protected String name; 
 public Animal(String name) { 
 this.name = name; 
 } 
 public void eat(String food) { 
 System.out.println("我是一只小动物"); 
 System.out.println(this.name + "正在吃" + food); 
 } 
 }
 
 // Bird.java 
public class Bird extends Animal { 
 public Bird(String name) { 
 super(name); 
 } 
 public void eat(String food) { 
 System.out.println("我是一只小鸟"); 
 System.out.println(this.name + "正在吃" + food); 
 } 
} 
// Test.java 
public class Test { 
 public static void main(String[] args) { 
 Animal animal1 = new Animal("圆圆"); 
 animal1.eat("谷子"); 
 Animal animal2 = new Bird("扁扁"); 
 animal2.eat("谷子"); 
 } 
} 

// 执行结果
我是一只小动物
圆圆正在吃谷子
我是一只小鸟
扁扁正在吃谷子

我们发现,当用父类引用指向子类,即发生向上转型时,会调用子类的eat方法,这个过程是程序运行时决定的,不是编译时决定的,就被称为动态绑定

3.3 方法重写

对于上面的eat方法,子类实现父类同名方法,并且参数个数和类型完全一样,就称为方法的重写、覆盖、覆写

重写注意事项:

  • 普通方法可以重写,静态方法不行
  • 重写时,子类方法的访问权限不能低于父类
  • 重写方法返回值类型需要一样(特殊情况,协变类型返回值类型可以不一样)
  • 对于重写,可以在重写的方法上使用@override来注解

重写与重载的区别:

  • 重载要求方法名称一样,参数的类型或个数不一样;重写要求方法名称、返回值类型、参数个数及类型完全一样
  • 重载时在一个类中实现的;重写是在继承关系中实现的
  • 重载对于权限没有要求;重写要求子类重写方法的访问权限必须大于等于父类

3.4 理解多态

有了向上转型、动态绑定和方法重写后,我们就可以使用多态了。来看代码:

class Animal{
    public void func(){
        System.out.println("hahaha");  //这个可以为空
    }
}

class Dog extends Animal{
    @Override
    public void func() {
        System.out.println("我是小狗");
    }
}

class Cat extends Animal{
    @Override
    public void func() {
        System.out.println("我是小猫");
    }
}

public class Test {

    public static void print(Animal animal){
        animal.func();
    }

    public static void main(String[] args) {
        Dog dog = new Dog();
        Cat cat = new Cat();
        print(dog);
        print(cat);
    }
}

输出为:

我是小狗
我是小猫

在这段代码中,Test上方的代码是类的实现者编写的,下方是类的调用者编写的。当类的调用者在编写print方法时,参数类型为Animal(父类),该方法内部此时不知道、也不需要知道Animal引用要指向哪个子类,这时指向不同的子类可能就会有不同的表现,这种行为就称为多态

多态的好处:

  • 封装可以让类的调用者不需要知道类的实现细节
  • 多态可以让类的调用者连这个类的类型时什么都不需要知道,只需要知道这个对象有某个方法即可,可以理解为封装的更进一步,让类的调用者对类的使用成本进一步降低

3.5 super关键字

super表示获取到父类实例的引用,有两种常见的用法。

  1. 使用super来调用父类的构造器

    public Bird(String name) { 
     super(name); 
    } 
    
  2. 使用super来调用父类的普通方法

    public class Bird extends Animal { 
     public Bird(String name) { 
     super(name); 
     } 
     @Override 
     public void eat(String food) { 
     // 修改代码, 让子调用父类的接口. 
     super.eat(food); 
     System.out.println("我是一只小鸟"); 
     System.out.println(this.name + "正在吃" + food); 
     } 
    } 
    

    在子类和父类中都有eat方法,如果不用super就被视为子类自己调用自己的eat方法,即递归,当加上super关键字后,就是调用父类的方法了。super关键字还可以调用父类的成员变量。

3.6 super和this的区别

  • this用于访问本类中的属性和方法;super是在子类中访问父类的属性和方法
  • this会先查找本类,本类中没有就会调用父类;super会直接调用父类中的属性或方法
  • this是谁去调用它就指向谁,super没有这一特点
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值