java: “abstract 抽象类” 与 “ interface 接口” 的妙用之道

导读:本篇文章讲解 java: “abstract 抽象类” 与 “ interface 接口” 的妙用之道,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

java: “abstract 抽象类” 与 “ interface 接口” 的妙用之道

在这里插入图片描述

每博一文案

有句很扎心的话,我对这世间唯一的不满就是这世间总是让更懂事的人承受的更多。
生活中,往往你越善解人意,就越没人在乎你的委屈,时间,让你学会了坚强,经历,让你学会了
成长,年龄,让你学会了迁就。
你不计较,你不动怒,你让步,你宽容,人生中充满了各种破事,你说最多的就是没事,然而有些人,却习惯了
这样的你,一次次的得寸进尺,丝毫不在乎你的感受,正如此生未完成所说:三角恋里每一次都是哪个主动波汗,
苦苦纠缠,步步相逼获胜,哪怕你有那么一点点心软,不忍看着,他左右为难,腹背受敌,那么兵败的终归是你。
有些人轻轻挥手就什么都有,而有些人拼尽了全力,却还是一无所有,会哭的孩子,有糖吃,不会哭的孩子,只能冷暖自知,其实你
不必那么懂事。一定要学会摆脱他人的期待,不再习惯取悦他人,找到真正的自己。
一路走来,跨过荆棘,是为了遇见春暖花开,而不是为了,去将就一个或是凑合一段生活,别人最
懂事的人受最多的委屈。
                                        ——————   一禅心灵庙语


1. 抽象类

随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征,有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类

abstract关键字来修饰一个类,这个类叫做抽象类

abstract 可以用来修饰 类,方法

abstract class Person{

}

在这里插入图片描述

  • 抽象类是不可以 实例化对象的 new 。虽然我们的抽象类是不能 new ,但是却是必须要构造器(构造方法的)。

因为抽象类是用来 继承 使用的,而继承就涉及到了 子类实例化的全过程 :子类继承父类(抽象类)则就必须调用父类中的构造器,所以我们的抽象类是必须要有构造器的。这一点需要注意的。

在这里插入图片描述

我们为父类(抽象类)附加上 空参的构造器 就可以了,让子类实例化对象,有父类的无参构造器调用

在这里插入图片描述


1.2 抽象方法

abstract 修饰的方法叫做 “抽象方法”

abstract class Person{ 
    // 抽象方法
    abstract public void show();
    
}

抽象方法:没有方法体,只有方法的声明。

普通的类中是不能定义抽象方法的,抽象类中才可以定义抽象方法。所以就有了这么一句话:包含抽象方法的类,一定是抽象类(注意是类,除了接口) ,反之抽象类不一定就 有抽象方法。 因为抽象类中也可以没有抽象方法。
在这里插入图片描述


  • 抽象类的子类必须重写 对应父类(抽象类)中所有 的抽象方法(注意是所有的不是单独的其中一个),若不重写其中的抽象方法,编译无法通过。若子类没有重写父类中的所有的抽象方法,则子类也是一个抽象类,需要使用 abstract 修饰类

在这里插入图片描述

在这里插入图片描述

或者该子类也定义为抽象类

在这里插入图片描述


abstract 修饰符的注意事项:

  1. abstract 在抽象类中不能用来,修饰: 变量,代码块,构造器的,因为对应变量,代码块,构造器这些都无法通过继承重写的。

在这里插入图片描述

  1. abstract 在抽象类中不能用来修饰:静态方法,private 修饰的方法,final 修饰的方法这些都无法继承来重写的。

在这里插入图片描述


2. 多态应用: 模板方法设计模式

抽象类体现的就是一种模板模式 的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展,改造,但子类总体上会保留抽象类的行为方式。

解决问题的思路:

当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现,换句话说,在软件开发中实现一个算法时,整体步骤很固定,通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种 模板模式

举例具体代码如下:


public class TemplateTest {
    public static void main(String[] args) {
        Bank b1 = new DrawMoneys();
        b1.process();  // 多态:动态绑定;调用子类重写的方法transactBusiness()

        System.out.println("**********************");
        Bank b2 = new ManageMoneys();  // 多态
        b2.process();   // 多态调用的是子类重写的方法transactBusiness()
    }
}

abstract class Bank{

    public void drawNumber(){
        System.out.println("取号排队");
    }


    // 抽象方法: 不确定处理怎样的业务需求:让子类继承实现
    abstract public void transactBusiness();

    public void end(){
        System.out.println("最后:业务评价");
    }


    // 重点
    // final 无法被重写,最后将所有操作整合在一起处理,
    public final void process() {
        this.drawNumber();        // 取号
        // 重点,实际调用的是: 子类重写的方法,多态
        this.transactBusiness();  // 对应的业务处理,执行子类中重写的抽象方法
        this.end();               // 最后业务评价
    }
}


// 取钱,操作
class DrawMoneys extends Bank{
    @Override
    // 重写继承的抽象类的抽象方法(无法确定的部分)
    public void transactBusiness(){
        System.out.println("业务处理:取钱");
    }
}


// 存钱操作。
class ManageMoneys extends Bank{
    @Override
    // 重写所继承的抽象类的抽象方法(无法确定的部分)
    public void transactBusiness() {
        System.out.println("业务处理: 存钱");
    }
}

在这里插入图片描述


3. 接口

一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果。

另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有is-a的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3机、手机、数码相机、移动硬盘等都支持USB连接。

接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则必须能…”的思想。继承是一个”是不是”的关系,而接口实现则是”能不能”的关系。

接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守。

在这里插入图片描述

在这里插入图片描述

接口使用 interface 关键字定义修饰类

interface Flyable{
    
}

在这里插入图片描述


Java中,接口 是并列的两个结构。

3.1 JDK7 之前的接口特性

  • 接口是一种特殊的抽象类,这种抽象类中只包含常量抽象方法的定义(JDK7.0及之前),而没有变量和方法的实现。

  • 接口中的所有成员变量都默认是由 public static final 修饰的,对于编译器而言一般都是会省略的不写的。因为是被 final 修饰的所以是无法修改其中的变量的值的。被称为常量。
    在这里插入图片描述

package blogs.blog3;

public class InterfaceTest {
    public static void main(String[] args) {
        System.out.println(Flyable.MAX_SPEED);

        // 可以直接通过类名.的方式直接访问,省略的是 public static final;
        System.out.println(Flyable.Min_SPEED);
    }
}


interface Flyable{
    public static final int MAX_SPEED = 7900;
    int Min_SPEED = 1;   // 默认省略了 public static final

}

在这里插入图片描述


  • 接口中的所有抽象方法都默认是由 public abstract 修饰的,不过一般是会被省略不写的,但是默认是会附加上的

在这里插入图片描述


interface Flyable{
    public static final int MAX_SPEED = 7900;
    int Min_SPEED = 1;   // 默认省略了 public static final


    // 抽象方法
    public abstract void fly();

    // 省略了 public abstract
    void stop();
}

class Plane implements Flyable {

    @Override
    public void fly() {

    }

    @Override
    public void stop() {

    }
}

  • 接口是无需要 构造器(构造方法的),因为接口是不能 实例对象的 new 的,接口 与 类的关系是 :实现关系 implements, 接口与接口之间的关系是 继承关系。

在这里插入图片描述


  • Java开发中:接口通过让类去实现 implements 的方式来使用:

​ 如果对应实现类,必须重写接口中所有的抽象方法(abstract) 默认省略的,不然编译报错;如果没有重写接口中的抽象方法,则将该类也定义为 接口 interface 才行。
在这里插入图片描述

在这里插入图片描述


  • 接口 与 类的关系是 : implements 实现关系
    • 一个接口可以被多个类 implements 实现
    • 一个类可以 implements 实现多个 接口

在这里插入图片描述

interface AA{
    // 抽象方法
    public abstract void funAA();
}


interface BB{
    // 抽象方法
    public abstract void funBB();
}

// 一个类实现多个接口
class CC implements AA,BB{

    @Override
    public void funAA() {

    }

    @Override
    public void funBB() {

    }
}
  • 接口 与 接口 之间是 多继承关系

在这里插入图片描述

interface AA{
    // 抽象方法
    public abstract void funAA();
}


interface BB{
    // 抽象方法
    public abstract void funBB();
}



// 接口与接口之间是: 多继承关系
interface DD extends AA,BB{

}

3.2 JDK8 之后的接口特性

Java 8中,你可以为接口添加静态方法默认方法。从技术角度来说,这是完全合法的,只是它看起来违反了接口作为一个抽象定义的理念。

接口中的静态方法:

使用 static 关键字修饰,可以通过 “接口名.方法名” 的方式直接调用静态方法(因为是静态方法),并执行其方法体我们经常在相互一起使用的类中使用静态方发,你可以在标准库中找到像Collection/Collections或者 Path/Paths这样成对的接口和类。

interface CompareA{
    // 接口中定义:静态方法
    public static void method1(){
        System.out.println("接口中的静态方法 method1");
    }
}
package blogs.blog3;

public class InterfaceJDK8 {
    public static void main(String[] args) {
        // 调用接口中静态方法: 直接接口名.方法
        CompareA.method1();
    }

}


interface CompareA{
    // 接口中定义:静态方法
    public static void method1(){
        System.out.println("接口中的静态方法 method1");
    }


}

在这里插入图片描述


接口中的静态方法,不可以被重写,只能被接口自己是使用,通过,“接口名.方法名”调用。因为不是抽象方法,所以对于实现类来说是不需要重写的,也无法重写。
在这里插入图片描述

package blogs.blog3;

public class InterfaceJDK8 {
    public static void main(String[] args) {
        // 调用接口中静态方法
        CompareA.method1();

        SubClass subClass = new SubClass();
        subClass.method1();   // jdk8中接口中定义的静态方法无法 实例对象调用,只能接口名.方法名的方式调用
    }

}


interface CompareA{
    // 接口中定义:静态方法
    public static void method1(){
        System.out.println("接口中的静态方法 method1");
    }



}

class SubClass implements CompareA{

}

接口中的默认方法:

使用 default 关键字修饰,可以通过实现类对象来调用,我们在已有的接口中提供,新方法的同时保持了与旧版本代码的兼容性,比如:Java 8 API 中对 Collection, List, Comparator 等接口提供了丰富的默认方法。

接口中的默认方法的调用:通过实例化 new 实现类的对象。再通过对象的方式调用其中的默认的方法.

同样因为接口不是抽象方法,所以没有一定要重写的要求。

interface CompareA{
    
    // 接口中定义: 默认方法
    default void method2(){
        System.out.println("接口中的默认方法");
    }
}
package blogs.blog3;

public class InterfaceJDK8 {
    public static void main(String[] args) {
        SubClass subClass = new SubClass();
        subClass.method2();  // 调用接口中的默认方法
    }

}


interface CompareA{

    // 接口中定义: 默认方法
    default void method2(){
        System.out.println("接口中的默认方法");
    }

    // 接口中定义:静态方法
    public static void method1(){
        System.out.println("接口中的静态方法 method1");
    }



}

class SubClass implements CompareA{

}

在这里插入图片描述


接口中的默认方法:是可以重写的,重写默认方法和多态性中的动态绑定是一样的。实现运行的是调用重写以后的方法。

package blogs.blog3;

public class InterfaceJDK8 {
    public static void main(String[] args) {
        SubClass subClass = new SubClass();
        subClass.method2();  // 调用接口中的默认方法
    }

}


interface CompareA{

    // 接口中定义: 默认方法
    default void method2(){
        System.out.println("接口中的默认方法");
    }

    // 接口中定义:静态方法
    public static void method1(){
        System.out.println("接口中的静态方法 method1");
    }



}

class SubClass implements CompareA{
    //  重写接口中的默认方法
    @Override
    public void method2(){
        System.out.println("实现类重写的默认方法");
    }

}

在这里插入图片描述


如果接口中和继承的父类中含有同名同参数的方法,而实现类又没有重写其中 同名同参数的方法的情况下,默认调用的是继承类中类父类中的方法,不是接口中的方法——> 类优先,如果重写了就是子类中的方法先调用

寻找的属性和方法的顺序是: 1.本类,2.父类,3.接口

package blogs.blog3;

public class InterfaceJDK8 {
    public static void main(String[] args) {
        SubClass subClass = new SubClass();
        subClass.method2();  // 调用接口中的默认方法
    }

}


interface CompareA{

    // 接口中定义: 默认方法
    default void method2(){
        System.out.println("接口中的默认方法:同名同参数的方法");
    }

    // 接口中定义:静态方法
    public static void method1(){
        System.out.println("接口中的静态方法 method1");
    }



}


class SuperClass{
    public void method2(){
        System.out.println("SuperClass 父类中的 同名同参数的方法");
    }
}

class SubClass extends SuperClass implements CompareA{
    //  重写接口中的默认方法
    @Override
    public void method2(){
        System.out.println("实现类重写的默认方法,本类中的:同名同参数的方法");
    }

}

在这里插入图片描述

注释掉:本类中重写的方法:重新运行看看。

在这里插入图片描述

在这里插入图片描述


如果实现类实现了多个接口,而这多个接口又定义同名同参数的默认方法:那么在实现类上,如果没有重写此(同名同参数同方法名)的情况下,是会报错的。:接口冲突。注意这是在 同名同参数同方法的接口中的 默认方法 才会,抽象方法是不会的。

解决方式:本实现类中重写其中的默认方法。

在这里插入图片描述

interface CompareA{

    // 接口中定义: 默认方法
    default void method2(){
        System.out.println("CompareA接口中的: 默认方法");
    }

    // 接口中定义:静态方法
    public static void method1(){
        System.out.println("接口中的静态方法 method1");
    }

}

interface CompareB{
    // JDK8 接口: 默认方法
    default void method2() {
        System.out.println("CompareB: 接口中的默认方法");
    }
}

class SubClass implements CompareA,CompareB{
}

解决方式:重写其中默认方法

在这里插入图片描述

interface CompareA{

    // 接口中定义: 默认方法
    default void method2(){
        System.out.println("CompareA接口中的: 默认方法");
    }

    // 接口中定义:静态方法
    public static void method1(){
        System.out.println("接口中的静态方法 method1");
    }

}

interface CompareB{
    // JDK8 接口: 默认方法
    default void method2() {
        System.out.println("CompareB: 接口中的默认方法");
    }
}

class SubClass implements CompareA,CompareB{
    @Override
    public void method2() {
        System.out.println("实例类中重写的默认方法");
    }
}

4. 接口的应用:代理模式 (Proxy)

代理模式是Java开发中使用较多的一种设计模式。代理设计就是为其他对象提供一种代理以控制对这个对象的访问。

在这里插入图片描述

应该场景:

  • 安全代理: 屏蔽对真实角色的直接访问。
  • 远程代理: 通过代理类处理远程方法调用 RMI
  • 延迟加载: 先加载轻量级的代理对象,真正需要再加载真实对象

比如你要开发一个大文档查看软件,大文档中由大的图片,有可能一个图片有 100MB,在打开文件时,不可能将所有的图片都显示出来,这样就可以使用代理模式,当需要查看图片时,用 proxy 来进行大图片的打开。

  • 分类

静态代理(静态定义代理类)

动态代理(动态生成代理类)

JDK自带的动态代理,需要反射等知识

具体实现代码如下:

package day16;

// 代理模式
public class StaticProxyTest {

	public static void main(String[] args) {
		Star s = new Proxy(new RealStar());
		s.confer();
		s.signContract();
		s.bookTicket();
		s.sing();
		s.collectMoney();
	}
}

interface Star {
	void confer();// 面谈

	void signContract();// 签合同

	void bookTicket();// 订票

	void sing();// 唱歌

	void collectMoney();// 收钱
}

class RealStar implements Star {

	public void confer() {
	}

	public void signContract() {
	}

	public void bookTicket() {
	}

	public void sing() {
		System.out.println("明星:歌唱~~~");
	}

	public void collectMoney() {
	}
}

class Proxy implements Star {
	private Star real;

	public Proxy(Star real) {
		this.real = real;
	}

	public void confer() {
		System.out.println("经纪人面谈");
	}

	public void signContract() {
		System.out.println("经纪人签合同");
	}

	public void bookTicket() {
		System.out.println("经纪人订票");
	}

	public void sing() {
		// 调用的是: 对应 Star 多态重写的方法
		real.sing();
	}

	public void collectMoney() {
		System.out.println("经纪人收钱");
	}
}

在这里插入图片描述


5. 面试题:

如下代码是否存在编译错误:

package blogs.blog3;

public class InterviewTest {
    public static void main(String[] args) {
        new C().px();
    }
}

interface A{
    int x = 0;
}


class B{
    int x = 1;
}


class C extends B implements A{
    public void px(){
        System.out.println(x);
    }
}

在这里插入图片描述

解析:

System.out.println(x); 其中的 x 变量是不明确的,因为该变量名 x ,对于 C 类来说,其中的父类 B 也有,其中的实现接口 A 其中也有 x,对于这两个 父类,接口中的同名变量,存在歧义,其中的编译器无法识别其要调用的是,哪个变量。所以报错。

解决方法:明确其中调用的是那边的变量即可,如下

package blogs.blog3;

public class InterviewTest {
    public static void main(String[] args) {
        new C().px();
    }
}

interface A{
    int x = 0;
}


class B{
    int x = 1;
}


class C extends B implements A{
    public void px(){
        System.out.println(super.x);   // 调用父类中的变量 x
        System.out.println(A.x);       // 调用接口中的变量 x ,因为接口中的变量默认是被public static final修饰的
    }
}

在这里插入图片描述


观察如下代码:是否存在错误

package blogs.blog3;

public class InterviewTest {

}




interface Playable{
    public abstract void play();
}


interface Bounceable{
    public abstract void play();

}


interface Rollable extends Playable,Bounceable {
    Ball ball = new Ball("Pingpang");
}


class Ball implements Rollable {

    private String name;
    public Ball(String name) {
        this.name = name;
    }


    public String getName() {
        return name;
    }

    @Override
    public void play() {
        ball = new Ball("Football");
    }
}


在这里插入图片描述


解析:

ball = new Ball(“Football”); 报错原因是: 因为对应的接口 Rollable 中已经定义了 Ball ball = new Ball(“Pingpang”); 全局变量,对于

接口中的属性,默认是被 public static final 修饰的,其中被 final 修饰的变量是无法被修改的,而这里你想要重新修改赋值的变量是 ball 同名的无法修改。


一个类继承了 类 ,实现了接口 , 重写了 func() 方法,父类,接口,本类中都有 func() 方法,如何调用执行其中对应父类,接口中的 func()方法

public class InterviewTest {
    public static void main(String[] args) {
        Students students = new Students();
        students.play();
    }
    
    public static void main2(String[] args) {
        new C().px();
    }
}




interface People{
    // 接口中的默认方法
    default void func() {
        System.out.println("People 接口中的默认方法 func()");
    }

    // 接口中的静态方法
    public static void method(){
        System.out.println("People接口中的静态方法");
    }
}


class Persons{
    public void func(){
        System.out.println("父类 Persons中的 func()方法");
    }
}



class Students extends Persons implements People{
    @Override
    public void func() {
        System.out.println("本类中重写的 func()方法");
    }
    
    
    public void play(){
        this.func();      // 调用执行本类中的 func()重写的方法
        super.func();     // 调用执行父类中的 func()方法
        People.super.func();    // 调用接口中的默认方法 func()
        People.method();    // 调用接口中的静态方法 method();
    }
}

在这里插入图片描述


6. 总结:

  1. abstract 可以用来修饰 类,方法

  2. 抽象类不可以 实例化 new 对象,但必须要有构造器(构造方法) 用于继承中的子类的实例化全过程调用父类中的构造器。

  3. abstract 不能修饰 属性,代码块,构造器,因为无法重写

  4. abstract 不能修饰: private 修饰的方法,final修饰的方法,final 修饰的类,静态方法,都是无法重写的。

  5. 抽象类中的抽象方法必须被所 extends 继承的类全部重写,不然编译无法通过。

  6. 接口中的抽象方法必须被 implements实现类全部重写,不然编译无法通过

  7. 接口 JDK7 之前的接口:只包含常量和方法的定义

    • 接口中的所有成员变量都默认是由public static final修饰的
    • 接口中的所有抽象方法都默认是由public abstract修饰的。
    • 接口中没有构造器,也不能有构造器,接口不能实例化 new 对象
    • 接口可以多继承。接口与类之间是 : implements 实现关系,接口与接口之间是 :extends 继承关系
  8. 接口 JDK8 之后的接口:

    • 接口中可以定义静态方法 被 static 修饰的方法,接口中的静态方法,只能接口中自己使用,无法重写,无法继承,访问方式是 “接口名.方法名”
    • 接口中可以定义默认方法 使用 default 关键字修饰,可以通过实现类对象来调用 ”实例对象.方法名“,默认方法可以被重写。
  9. 接口的主要用途就是被实现类实现。(面向接口编程)

  10. 接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守。

  11. 定义类的语法格式是:先写 extends ,后写 implements

class SubClass extends SuperClass implements InterfaceA{ }

7. 最后:

限于自身水平,其中存在的错误,希望大家给予指教,韩信点兵——多多益善,谢谢大家,后会有期,江湖再见!!!

在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/82994.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!