栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

【无标题】一起学技术——java封装,继承,多态

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【无标题】一起学技术——java封装,继承,多态

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录
  • 前言
  • 一、包
    • 导入包中的类
    • 静态导入
  • 二、封装
  • 三、继承
    • 继承的引出
    • 多重继承
    • 多层继承
    • 隐式继承
    • 对于protected关键字
    • fianl关键字
  • 四、多态
    • 索引
    • super关键字
    • 修饰构造方法
    • 组合
    • 向上转型
    • 方法重写
    • 向下转型
    • 抽象类
  • 接口
  • 总结


前言

Java面向对象的三大特性:继承,封装,多态

一、包
  • 包(package)是组织类的一种方式
    使用包的主要目的就是保证类的唯一性
    例如:你在代码中写了一个Test类,然后你的同事也可能写一个Test类,如果出现两个同名的类,就会冲突,导致代码不能比编译通过
导入包中的类

Java 中已经提供了很多现成的类供我们使用. 例如

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

可以使用 java.util.Date 这种方式引入 java.util 这个包中的 Date 类

但是这种写法比较麻烦一些,可以使用import语句导入包

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

如果需要使用Java.util 中的其他类,可以使用import java.untl.*

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

但是我们更建议显示的指定要导入的类名,否则还是容易出现冲突的情况

import java.util.*;
import java.sql.*;
public class Test {
  public static void main(String[] args) {
    // util 和 sql 中都存在一个 Date 这样的类, 此时就会出现歧义, 编译出错
    Date date = new Date();
    System.out.println(date.getTime());
 }
}
// 编译出错
Error:(5, 9) java: 对Date的引用不明确
 java.sql 中的类 java.sql.Date 和 java.util 中的类 java.util.Date 都匹配

在这种情况下需要完整的类名

import java.util.*;
import java.sql.*;
public class Test {
  public static void main(String[] args) {
    java.util.Date date = new java.util.Date();
    System.out.println(date.getTime());
 }
}

注:import 和 C++ 的 #include 差别很大. C++ 必须 #include 来引入其他文件内容, 但是 Java 不需要.
import 只是为了写代码的时候更方便. import 更类似于 C++ 的 namespace 和 using

静态导入

使用impore static可以导入包中的静态方法和字段

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

这样可以更方便的编写代码

import static java.lang.Math.*;
public class Test {
  public static void main(String[] args) {
    double x = 30;
    double y = 40;
    // 静态导入的方式写起来更方便一些.
    // double result = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
    double result = sqrt(pow(x, 2) + pow(y, 2));
    System.out.println(result);
 }
}
二、封装

使用private将属性或方法进行封装(表示这个属性或方法只在类的内部可见,对外部隐藏,想要访问这个属性或方法,只能通过间接方法访问)

封装是为了保护性和易用性(通过程序对外提供的方法来操作属性)

像人类-心脏,汽车和发动机的关系

三、继承 继承的引出

代码中创建的类, 主要是为了抽象现实中的一些事物(包含属性和方法).
有的时候客观事物之间就存在一些关联关系, 那么在表示成类和对象的时候也会存在一定的关联.

例如下面代码

// Animal.java
public class Animal {
	public String name;

	public void eat(String food) {
		System.out.println(this.name + "正在吃" + food);
	}
}

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

	public void eat(String food) {
		System.out.println(this.name + "正在吃" + food);
}

我们分别定义了三个类
但是我们发信啊其中存在了大量的冗余代码
仔细发现这三个类中存在一定的关系

  • 这三个类都具备一个相同的eat方法,而且行为是完全一样的
  • 这三个类都具备一个相同的name属性,而且意义是完全一样的
  • 从逻辑上来讲,dog is an animal ,cat is an animal,所有的animal的类都应该具备name属性和eat这个方法,这时我们就可以让dog和cat类分别继承animal可,达到代码重用的效果。
  • 此时, Animal 这样被继承的类, 我们称为 父类 , 基类 或 超类, 对于像 Cat 和 Bird 这样的类, 我们称为 子类, 派生类和现实中的儿子继承父亲的财产类似, 子类也会继承父类的字段和方法, 以达到代码重用的效果

基本语法

class 子类 extends 父类{
}

所以上面的代码可以改为

public class animal {
    public String name;

    public void eat(String food){
        System.out.println(this.name +"正在吃" + food);
    }
}

public class dog extends animal {

}


public class Test {
    public static void main(String[] args) {
        animal animal = new animal();
        animal.name = "动物";
        animal.eat("食物");

        dog dog = new dog();
        dog.name = "六六";
        dog.eat("狗粮");

        System.out.println(animal.getAge());
    }
}

//
运行结果
动物正在吃食物
六六正在吃狗粮

这和上一个代码运行结果相同

这么写是代码更加简洁,减少了不必要的麻烦,但是继承也是不能随便写的,要想使用继承必须要满足 is a的关系。

比如人和狗不能相互继承,虽然有着某些相同的属性和方法,但是人不是狗,狗也不是人,因此并不存在继承关系

多重继承

即一个类可以继承多个类,比如泰迪既是狗,又是动物,继承类之间用,隔开

public class Teddy extends Animal,Dog{
}
多层继承

多层继承又被称为继承“树”,满足继承关系的类之间一定是逻辑上垂直的关系,比如泰迪属于狗,狗属于动物,即子类还可以进一步的再派生出新的子类。

  • 时刻牢记, 我们写的类是现实事物的抽象. 而我们真正在公司中所遇到的项目往往业务比较复杂, 可能会涉及到一系列复杂的概念, 都需要我们使用代码来表示, 所以我们真实项目中所写的类也会有很多. 类之间的关系也会更加复杂.
  • 但是即使如此, 我们并不希望类之间的继承层次太复杂. 一般我们不希望出现超过三层的继承关系. 如果继承层次太多, 就需要考虑对代码进行重构了.
  • 如果想从语法上进行限制继承, 就可以使用 final 关键字
//Animal.java
public Animal{
}

//Dog.java
public Dog extends Animal{
}

//Taidi.java
public Taidi extends Dog{
}
隐式继承

代码如下(示例):

ackage animal;

public class animal {
    public String name;

    private int age = 10;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void eat(String food){
        System.out.println(this.name +"正在吃" + food);
    }

}
package animal;

public class dog extends animal {

}
public class Test {
    public static void main(String[] args) {
        animal animal = new animal();
        animal.name = "动物";
        animal.eat("食物");

        dog dog = new dog();
        dog.name = "六六";
        dog.eat("狗粮");

        System.out.println(animal.getAge());
    }
}

对于Animal类中的private int age,子类在继承的时候也会继承这个私有属性,但是对于继承的私有属性没有办法直接使用,需要使用父类中的get和set方法对私有属性进行使用,所以被称为隐式继承。

对于protected关键字

刚才我们发现, 如果把字段设为 private, 子类不能访问. 但是设成 public, 又违背了我们 “封装” 的初衷.
两全其美的办法就是 protected 关键字.

  • 对于类的调用者来说, protected 修饰的字段和方法是不能访问的
  • 对于类的 子类 和 同一个包的其他类 来说, protected 修饰的字段和方法是可以访问的
package person;
import animal.animal;
public class Test {
    public static void main(String[] args) {
        animal animal = new animal();
        System.out.println(animal.name);
    }
}
package animal;

public class animal {
    protected String name = "ll";
}
  • 运行结果:java: name 在 animal.animal 中是 protected 访问控制

  • 说明:protected作用域:只在当前类和同包中的不同类以及不同包下的子类内部中是可以使用的,即使import引入当前包也不可以

小结: Java 中对于字段和方法共有四种访问权限

  • private: 类内部能访问, 类外部不能访问
  • 默认(也叫包访问权限): 类内部能访问, 同一个包中的类可以访问, 其他类不能访问.
  • protected: 类内部能访问, 子类和同一个包中的类可以访问, 其他类不能访问.
  • public : 类内部和类的调用者都能访问
  • 什么时候下用哪一种呢? 我们希望类要尽量做到 “封装”, 即隐藏内部实现细节, 只暴露出 必要 的信息给类的调用者.
  • 因此我们在使用的时候应该尽可能的使用 比较严格 的访问权限. 例如如果一个方法能用 private, 就尽量不要用 public.
  • 另外,还有一种 简单粗暴 的做法: 将所有的字段设为 private, 将所有的方法设为 public. 不过这种方式属于是对 访问权限的滥用,还是更希望同学们能写代码的时候认真思考, 该类提供的字段方法到底给 “谁” 使用(是类内部 自己用, 还是类的调用者使用, 还是子类使用)

fianl关键字

之前说过final关键字,修饰一个变量或者字段的时候,表示常量(不能改变)

final int a = 1;
a = 2; // 编译出错

那么final修饰类呢?

final public class Animal {
...
}
public class Bird extends Animal {
...
}
// 编译出错
Error:(3, 27) java: 无法从最终com.bit.Animal进行继承

这说明final关键字的功能是限制类被继承

  • “限制” 这件事情意味着 “不灵活”. 在编程中, 灵活往往不见得是一件好事.
  • 灵活可能意味着更容易出错. 是用 final 修饰的类被继承的时候, 就会编译报错, 此时就可以提示我们这样的继承是有悖这个类设计的初衷的
四、多态 索引

回忆一下this关键字:表示当前对象的引用

this关键字可以修饰属性、修饰普通方法和构造方法、表示当前对象的引用

this是到当前类中去寻找,而super就是到父类去寻找

在将super关键字之前,先要了解一个知识点:要有子类必需先有父类

package person;
import animal.animal;
public class Test {
    public static void main(String[] args) {
        person person = new person();
    }
}
//运行结果
animal的无参构造
person的无参构造
package person;
import animal.animal;

public class person extends animal{
    public person(){
        System.out.println("person的无参构造");
    }
}
package animal;

    public animal() {
        System.out.println("animal的无参构造");
    }

所以可以看出,创建person的对象的时候,先是执行了父类的构造方法,然后在执行子类的构造方法

这是符合逻辑的,通俗的来讲,没有你的父亲哪来的你,所以说当调用子类的无参构造产生子类对象之前,先默认调用父类的构造方法产生父类对象。

知道上面的内容,看你是否会下面这道阿里笔试题

package animal;

public class B {
    public B(){
        System.out.println("1.B的构造方法——————————");
    }
    {
        System.out.println("2.B的构造块——————————");
    }
    static{
        System.out.println("3.B的静态块————————————");
    }
}

package animal;

public class D extends B{
    public D(){
        System.out.println("4.D的构造方法——————");
    }
    {
        System.out.println("5.D的构造块");
    }
    static{
        System.out.println("6.D的静态块");
    }

    public static void main(String[] args) {
        System.out.println("7.main开始......");
        new D();
        new D();
        System.out.println("8.main结束。。。。");
    }
}

执行结果

原因:

super关键字

super修饰属性 表示从父类中寻找同名属性
super表示获取到父类实例的引用。涉及到两种常见方法
1.使用了super关键字调用父类的构造器

public Bird(String name) {
super(name);
}

2.使用super来调用父类的普通方法

package supertest;

public class person {
    public String name = "person";
}
package supertest;

public class China extends person{
    public String name = "china";
    public void fun(){
        //在访问成员变量的时候,建议写上this,尤其是有继承的时候,不容易混淆
        System.out.println(name);
    }
    public static void main(String[] args){
        China china = new China();
        china.fun();
    }
}
//执行结果:china

上面代码中的fun()功能里,打印的name依然遵循就近匹配原则,会找最近的name,而不是去找父类中的name

当子类没有name属性时

package supertest;

public class China extends person{
    

    public void fun(){
        //在访问成员变量的时候,建议写上this,尤其是有继承的时候,不容易混淆
        System.out.println(this.name);
    }

    public static void main(String[] args){
        China china = new China();
        china.fun();
    }
    //执行结果 person

当存在继承关系时,this关键字默认现在当前类中寻找同名属性,若没找到,继续向上寻找。

那么如果向直接访问父类的成员属性,可以使用super关键字

package supertest;

public class China extends person{
    public String name = "china";

    public void fun(){
        //在访问成员变量的时候,建议写上this,尤其是有继承的时候,不容易混淆
        System.out.println(super.name);
    }

    public static void main(String[] args){
        China china = new China();
        china.fun();
    }
}
//运行结果:person

注:private修饰属性,super不可见
如果父类还有一个父类怎么办呢?

package supertest;

public class Animal {
        public String name = "animal";
}
package supertest;

public class person extends Animal{
    public String name = "person";
}
package supertest;

public class China extends person{
    public String name = "china";

    public void fun(){
        //在访问成员变量的时候,建议写上this,尤其是有继承的时候,不容易混淆
        System.out.println(super.name);
    }

    public static void main(String[] args){
        China china = new China();
        china.fun();
    }
}
//运行结果:person

说明super寻找的是直接父类,而不是一直向上寻找

如果父类没有该属性怎么办呢?会继续向上寻找,这里就不再追赘述了

注:如果父类中的成员属性是私有的,super会找到直接父类中的成员属性,只不过没有访问权限,并且不会继续向上寻找

修饰构造方法
package supertest;

public class person extends Animal{
    public String name = "person";

    public person(){
        System.out.println("person的无参构造");
    }
}
package supertest;

public class Animal {
        public String name = "animal";

        public Animal(){
                System.out.println("Animal的无参构造");
        }
}
{
    public String name = "china";

    public void fun(){
        //在访问成员变量的时候,建议写上this,尤其是有继承的时候,不容易混淆
        System.out.println(super.name);
    }

    public China(){
        System.out.println("China的无参构造");
    }

    public static void main(String[] args){
        China china = new China();
    }
}
//运行结果
Animal的无参构造
person的无参构造
China的无参构造

当产生子类对象时,默认先产生父类对象,若父类对象还有父类,继续向上,先产生祖父类对象

如果子类的直接父类没有无参构造,那么在调用子类的无参构造时就会出错

super修饰构造方法的语法:
super(父类的构造方法的参数)
super()//直接父类的无参构造,可写可不写
若父类中不存在无参构造,则子类构造方法的首行必须使用super(有参构造)

注:在一个构造方法中无法显式使用this()和super()同是出现。

super修饰普通方法,和修饰属性,直接从父类中寻找同名方法

package supertest;

public class person extends Animal{
    public String name = "person";

    public void fun(){
        System.out.println("fun方法");
    }

    public person(){
        System.out.println("person的无参构造");
    }
}

package supertest;

public class China extends person{
    public String name = "china";

    public void fun(){
        //在访问成员变量的时候,建议写上this,尤其是有继承的时候,不容易混淆
        System.out.println(super.name);
    }

    public void test(){
        super.fun();
    }
    public China(){
        System.out.println("China的无参构造");
    }

    public static void main(String[] args){
        China china = new China();
        china.test();
    }
}

super向上寻找,找到同名的方法

注:super和this不同,super不能指代当前父类的对象

System.out.println(this);
System.out.println(super);//error

super关键字类似于一种指示器,告诉你到父类中去寻找,而不是引用父类对象。

组合

类和类之间除了继承关系,还有一种是组合关系,也是能达到代码重用的效果。

例如表示一个学校

public class Student {
...
}
public class Teacher {
...
}
public class School {
public Student[] students;
public Teacher[] teachers;
}

组合并没有涉及到特殊的语法(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段.
这是我们设计类的一种常用方式之一.

组和表示has a含义
在刚才的例子中, 我们可以理解成一个学校中 “包含” 若干学生和教师

向上转型

在之前创建对象中,我们写了形如下面的代码
Animal animal = new Animal9();
Dog dog = new Dog("六六");
对于狗这个对象来说,他实际上也是动物,因此可以换一种写法
Animal animal = new Dog();

这种创建方式被称为向上转型:具体写法
父类名称 父类引用 = new 子类实例();
注:向上转型只发生在有继承关系的类之间,不一定是直接子类,也可以是孙子类。

向上转型的最大意义在于参数统一化,降低使用者的使用难度!!

package polymorphism;

import supertest.Animal;

public class Test {
    public static void main(String[] args){
        //2.类的使用者/程序的使用者
        //如果没有向上转型,我要使用fun方法,就需要了解Animal及其子类的所有对象
        //这样才能知道调用那个fun方法
    }
    //1.作为类的实现这:
    //现在我们需要一个方法来接受Animal及其子类的对象作为参数
    //假设没有向上转型,那么我们应该重载多个方法,用于接收不同类型的方法
    //这样的话Animal有多少个子类,我们就要重载多少次fun方法
    //大自然中的动物的种类有上百万种,就要重写上百万次代码
    public static void fun(Animal animal){}
    public static void fun(Dog dog){}
    public static void fun(Teddy teddy){}
    
}

这样写太过于麻烦,每写一次就要写一次fun方法

那么既然子类有了父类,为什么不能用父类去指代所有的子类呢?

因此有了向上转型,代码就可以写成这样:

 
方法重写 

方法重载(overload):发生在同一个类中,定义了若干个方法名称相同,参数列表不同的一组方法。

方法重写(override):发生在有继承关系的类之间,子类定义了和父类除了权限不同,其他全都相同的方法,这样的一组方法称之为方法重写。

package polymorphism;

public class Bird extends Animal {
    public void eat(){
        System.out.println("Bird的eat方法");
    }
}

package polymorphism;

public class Animal {
    public void eat(){
        System.out.println("Animal类的eat方法");
    }
}

package polymorphism;

public class Dog extends Animal {
    public void eat(){
        System.out.println("Dog的eat方法");
    }
}

package polymorphism;

public class Duck extends Bird{
    public void eat(){
        System.out.println("Duck的eat方法");
    }
}

package polymorphism;

public class Test {
    public static void main(String[] args){
        //2.类的使用者/程序的使用者
        //如果没有向上转型,我要使用fun方法,就需要了解Animal及其子类的所有对象
        //这样才能知道调用那个fun方法
        fun(new Bird());
        fun(new Duck());
        fun(new Dog());
        fun(new Animal());

    }
    //1.作为类的实现这:
    //现在我们需要一个方法来接受Animal及其子类的对象作为参数
    //假设没有向上转型,那么我们应该重载多个方法,用于接收不同类型的方法
    //这样的话Animal有多少个子类,我们就要重载多少次fun方法
    //大自然中的动物的种类有上百万种,就要重写上百万次代码
    public static void fun(Animal animal){
        animal.eat();
    }

}
运行结果
Bird的eat方法
Duck的eat方法
Dog的eat方法
Animal类的eat方法

为什么会是这样的运行结果呢,虽然我们在给子类中都定义了eat方法,但是fun方法接收的参数是Animal类的引用,运行结果不应该都是Animal类中的eat方法吗?

实际上到底调用的是谁的方法呢

不用去看前半部分,看当前是通过哪个类new的对象,若该类重写了相关方法,则调用的一定是重写后的方法。

注:千万不要被类名给搞晕,就看new的是谁,只要new这个对象的类中覆写了同名的方法,则调用的一定是复写后的方法。

那么如果子类中没有重写这个方法,调用的是

就近匹配原则,碰到最接近的调用

那么方法重写有哪些条件呢?

  • 当发生重写是,子类权限必须>=父类权限才可以
  • 但是注意private不包含在内
  • java中有一个注解@Override:使用这个注解写在重写方法之前,帮你校验你的方法重写是否符合规则。
    方法重写只发生在普通方法中
package polymorphism;

public class Dog extends Animal {
    @Override
    public void eat(){
        System.out.println("Dog的eat方法");
    }
}

能否重写static方法
多态的本质就是因为调用了不同子类“对象”,这些子类对象所属的类复写相应的方法,才能表现出不同的行为,static和对象无关!!!

向上转型发生的三个位置

//赋值时
Animal animal1 = new Dog();
Animal animal2 = new Teddy();
//方法传参时
fun(animal2);
public static void fun(Animal animal){
	animal,eat();
}
//方法返回值
public static Animal test(){
	Dog dog = new Dog();
	return dog;
}
向下转型

向上转型是子类对象转成父类对象, 向下转型就是父类对象转成子类对象. 相比于向上转型来说, 向下转型没那么常见,但是也有一定的用途.

Animal animal = new Dog();
animal.play();

animal这个引用是披着狗皮的动物,本质上是个dog,批了个Animal的外衣,此时只能调用Animal中定义的方法

此时就想调用子类的拓展的方法该怎么办?
脱掉这层外衣,还原作为子类引用-> 向下转型

语法为:子类名称 子类引用 = (子类名称)父类引用
Dog dog = (Dog)animal;//脱掉animal对应的对象的外衣还原为具体的子类引用

将父类引用强制类型转换为子类引用

public static void main(String[] args){
	Animal animal = new Dog();
	Dog dog = (Dog) animal;
	dog.play();
}

注:要发生向下转型,要先向上转型,如果没有向上转型,两者之间就没有任何关系

判断向下转型是否合理使用关键字instanceof

引用名称 instanceof 类名->返回布尔值,表示该引用指向的对象是不是该类的对象

    public static void main(String[] args) {
        Animal animal1 = new Animal();
        Animal animal2 = new Dog();
        System.out.println(animal1 instanceof Dog);//false
        System.out.println(animal2 instanceof Dog);//true
//instanceof使用方法
        if (animal1 instanceof Dog){
            Dog dog = (Dog) animal1;
            System.out.println("animal1转型成功");
        }
        else{
            System.out.println("animal1不是指向Dog类型的引用");
        }
        if (animal2 instanceof Dog){
            Dog dog = (Dog)animal2;
            System.out.println("animal2转型成功");
        }
        else{
            System.out.println("animal2不是指向Dog类型的引用");
        }
    }
}
执行结果
animal1不是指向Dog类型的引用
animal2转型成功
  • 什么时候发生向上转型,方法接收一个类和当前类的子类,参数指定为相应的父类引用,发生的就是向上转型
  • 只有某个特殊的情况下,需要使用子类拓展的方法,才需要将原本向上转型的引用用向下转型还原为子类引用
抽象类

语法规则

在刚才的打印图形例子中,我们发现,父类Sharp中的draw方法好像并没有什么实际工作,主要的绘制图形都是由Sharp的各种子类的draw方法来完成的。像这种没有实际工作的方法, 我们可以把它设计成一个 抽象方法(abstract method), 包含抽象方法的类我们称为 抽象类(abstract class).

abstract class Shape {
	abstract public void draw();
}
  • 在 draw 方法前加上 abstract 关键字, 表示这是一个抽象方法. 同时抽象方法没有方法体(没有 { }, 不能执行具体代码).
  • 对于包含抽象方法的类, 必须加上 abstract 关键字表示这是一个抽象类

注意事项:
1.抽象类不能直接实例化

Shape shape = new Shape();
// 编译出错
Error:(30, 23) java: Shape是抽象的; 无法实例化

2.抽象方法不能是private的

abstract class Shape {
	abstract private void draw();
}
// 编译出错
Error:(4, 27) java: 非法的修饰符组合: abstract和private

3.抽象类中可以包含其他的非抽象方法, 也可以包含字段. 这个非抽象方法和普通方法的规则都是一样的, 可以被重写,也可以被子类直接调用

abstract class Shape {
	abstract public void draw();
	
	void func() {
		System.out.println("func");
	}
}
	
class Rect extends Shape {
	...
}

public class Test {
	public static void main(String[] args) {
		Shape shape = new Rect();
		shape.func();
	}
}

// 执行结果
func

抽象类的作用

  • 抽象类的抽象类存在的最大意义就是为了被继承.
  • 抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类. 然后让子类重写抽象类中的抽象方法

那么,普通的类也可以被继承呀, 普通的方法也可以被重写呀, 为啥非得用抽象类和抽象方法呢?

确实如此. 但是使用抽象类相当于多了一重编译器的校验

使用抽象类的场景就如上面的代码, 实际工作不应该由父类完成, 而应由子类完成. 那么此时如果不小心误用成父类了,使用普通类编译器是不会报错的. 但是父类是抽象类就会在实例化的时候提示错误, 让我们尽早发现问题.

很多语法存在的意义都是为了 “预防出错”, 例如我们曾经用过的 final 也是类似. 创建的变量用户不去修改, 不就相当于常量嘛?
但是加上 final 能够在不小心误修改的时候, 让编译器及时提醒我们.充分利用编译器的校验, 在实际开发中是非常有意义的.

接口

接口是抽象类的更进一步. 抽象类中还可以包含非抽象方法, 和字段. 而接口中包含的方法都是抽象方法, 字段只能包含静态常量.

一般来说,接口的使用表示两种场景

1.接口表示具备某种能力/行为,子类实现接口时不是is a,而是具备这种行为或者能力
比如:“游泳”->能力或者行为,person满足,Dog也能满足游泳接口,Duck也能满足游泳接口。

2.接口表示一种规范或者标准
比如:“USB接口”,5G标准

接口中只有全局常量和抽象方法->更加纯粹的抽象概念。其他东西统统没有
接口使用关键字interface声明,子类使用implement实现接口。

1.USB接口

package interface_test;

public interface USB {
    //插入
    public abstract void plugIn();
    //
    public abstract void work();
}

子类使用implements实现接口,必须覆写所有的抽象方法

package interface_test;

public class Mouse implements USB{
//    鼠标
    @Override
    public void plugIn() {
        System.out.println("鼠标安装驱动中。。");
    }

    @Override
    public void work() {
        System.out.println("鼠标驱动安装完成,现在可以正常使用!");
    }
}
//键盘
public class Camera implements USB{

    @Override
    public void plugIn() {
        System.out.println("读取相机文件中。。");
    }

    @Override
    public void work() {
        System.out.println("相机文件读取成功,已打开文件夹!");
    }
}

鼠标,键盘外设都属于USB接口的子类


那么电脑算不算USB接口的子类?

当然是不算的,所有带USB线插入到电脑的设备都应该满足USB规范。

电脑叫做USB规范的使用者!!!

package interface_test;

public class Computer {
    //fun方法就模拟电脑的USB的插口
    public void fun(USB usb){
        usb.plugIn();
        usb.work();
    }
    //主方法
    public static void main(String[] args){
        Computer computer = new Computer();
        Mouse mouse = new Mouse();
        //插入鼠标
        computer.fun(mouse);
        
        Camera camera = new Camera();
        //插入键盘
        computer.fun(camera);
        
    }
}

  • 对于电脑的使用生产者来说,我根本不关心到底哪个具体设备插入到我的电脑上,只要这个设备满足了USB接口,都能被电脑识别。

  • 就可以实现,一个接口可以实现无数种设备,只要这个设备满足USB接口,都可以插入到电脑且被电脑识别。兼容所有的USB子类对象

  • 但是fun(mouse)->这个插口只能插入鼠标,键盘都无法识别,这是两个毫无关系的类。

  • 这种设计方法对于新的子类来说使用也非常方便,比如我们只需要创建好子类相机,覆写USB中的抽象方法即可,之后再插入相机,就可以被电脑识别了。

  • 这种程序设计方法被称为开闭原则:程序应当对扩展开放,对修改关闭,换言之就是方便扩展,并不能影响已经写好的程序。

实现多个接口
有的时候我们需要让一个类同时继承自多个父类. 这件事情在有些编程语言通过 多继承 的方式来实现的.

然而 Java 中只支持单继承, 一个类只能 extends 一个父类. 但是可以同时实现多个接口, 也能达到多继承类似的效果.

现在我们通过类来表示一组动物.

class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
}

另外我们再提供一组接口, 分别表示 “会飞的”, “会跑的”, “会游泳的”.

class Fish extends Animal implements ISwimming {
public Fish(String name) {
super(name);
}
@Override
public void swim() {
System.out.println(this.name + "正在用尾巴游泳");
}
}

青蛙, 既能跑, 又能游(两栖动物)

class Frog extends Animal implements IRunning, ISwimming {
public Frog(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.name + "正在往前跳");
}
@Override
public void swim() {
System.out.println(this.name + "正在蹬腿游泳");
}
}

提示, IDEA 中使用 ctrl + i 快速实现接口

上面的代码展示了 Java 面向对象编程中最常见的用法: 一个类继承一个父类, 同时实现多种接口

总结

提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/856772.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号