澳门新浦京娱乐游戏Java基础——抽象abstract和接口

澳门新浦京娱乐游戏 3

基本上所有的Java教程都会告诉我们Java接口的方法都是public、abstract类型的,没有方法体的。

一、abstract和接口初认识

目录:

但是在JDK8里面,你是可以突破这个界限的哦。

abstract
class和interface是Java语言中对于抽象类定义进行支持的两种机制。abstract
class和interface之间在对于抽象类定义的支持方面具有很大的相似性,甚至可以相互替换,因此很多开发者在进行抽象类定义时对于abstract
class和interface的选择显得比较随意。其实,两者之间还是有很大的区别的,对于它们的选择甚至反映出对于问题领域本质的理解、对于设计意图的理解是否正确、合理。

  • 接口的定义
  • jdk7-9,接口属性的变化
  • jdk8,default、public static
    method的提出解决了什么问题,使用时需要注意什么
  • jdk9的补充(引入private method、private static method)
  • 新老生常谈:接口和抽象类的对比
  • 单继承还是多继承

假设我们现在有一个接口:TimeClient,其代码结构如下:

abstract 关键字可以修饰类或方法。


import java.time.*;

public interface TimeClient {
    void setTime(int hour, int minute, int second);
    void setDate(int day, int month, int year);
    void setDateAndTime(int day, int month, int year,
                               int hour, int minute, int second);
    LocalDateTime getLocalDateTime();
}

抽象方法Java中可以利用abstract关键字定义一些不包括方法体的方法,没有方法体就没有实现,具体实现交给子类,这样的方法称为
抽象方法

 

接下来,SimpleTimeClient类实现了TimeClient接口,具体代码如下:

抽象类有抽象方法的类就是抽象类

一、接口的定义:

package defaultmethods;

import java.time.*;
import java.lang.*;
import java.util.*;

public class SimpleTimeClient implements TimeClient {

    private LocalDateTime dateAndTime;

    public SimpleTimeClient() {
        dateAndTime = LocalDateTime.now();
    }

    public void setTime(int hour, int minute, int second) {
        LocalDate currentDate = LocalDate.from(dateAndTime);
        LocalTime timeToSet = LocalTime.of(hour, minute, second);
        dateAndTime = LocalDateTime.of(currentDate, timeToSet);
    }

    public void setDate(int day, int month, int year) {
        LocalDate dateToSet = LocalDate.of(day, month, year);
        LocalTime currentTime = LocalTime.from(dateAndTime);
        dateAndTime = LocalDateTime.of(dateToSet, currentTime);
    }

    public void setDateAndTime(int day, int month, int year,
                               int hour, int minute, int second) {
        LocalDate dateToSet = LocalDate.of(day, month, year);
        LocalTime timeToSet = LocalTime.of(hour, minute, second);
        dateAndTime = LocalDateTime.of(dateToSet, timeToSet);
    }

    public LocalDateTime getLocalDateTime() {
        return dateAndTime;
    }

    public String toString() {
        return dateAndTime.toString();
    }

    public static void main(String... args) {
        TimeClient myTimeClient = new SimpleTimeClient();
        System.out.println(myTimeClient.toString());
    }
}

接口一个类中如果所有方法都是abstract 方法,那么这个类我们可以利用
interface 定义成接口。

首先让我们看一下接口的最新定义:What is an
Interface,里面提到:

现在假设你想在接口TimeClient中添加一个新功能,通过这个功能我们可以指定我们所在的时区。

什么是抽象方法声明为 abstract 的方法就是抽象方法。

In the Java programming language, an interface is a reference type,
similar to a class, that can contain only constants, method
signatures, default methods, static methods, and nested types. Method
bodies exist only for default methods and static methods. Interfaces
cannot be instantiated—they can only be implemented by classes
or extended by other interfaces. 

public interface TimeClient {
    void setTime(int hour, int minute, int second);
    void setDate(int day, int month, int year);
    void setDateAndTime(int day, int month, int year,
        int hour, int minute, int second);
    LocalDateTime getLocalDateTime();                           
 ZonedDateTime getZonedDateTime(String zoneString); }

抽象方法的写法

再来看一下jdk1.7时的定义(具体原版定义官网已经找不到了,只能通过书籍及博客找到大概的版本):

随着TimeClient的变化,你不得不修改你的SimpleTimeClient类,实现getZonedDateTime方法。一般来说,设置时区这个功能会是各个TimeClient实现类中通用的一个功能。这个时候,你通常会选择再定义一个AbstractTimeClient类来实现getZonedDateTime方法。而在JDK8中,你可以选择直接在接口中来实现该方法(interface已经把手伸到abstract
class的地盘了)。

abstract 返回值类型 方法名;

An interface in Java is similar to a class, but the body of an interface
can include only abstract methods and final fields (constants). A class
implements an interface by providing code for each method declared by
the interface.

package defaultmethods;

import java.time.*;

public interface TimeClient {
    void setTime(int hour, int minute, int second);
    void setDate(int day, int month, int year);
    void setDateAndTime(int day, int month, int year,
                               int hour, int minute, int second);
    LocalDateTime getLocalDateTime();

    static ZoneId getZoneId (String zoneString) {
        try {
            return ZoneId.of(zoneString);
        } catch (DateTimeException e) {
            System.err.println("Invalid time zone: " + zoneString +
                "; using default time zone instead.");
            return ZoneId.systemDefault();
        }
    }

    default ZonedDateTime getZonedDateTime(String zoneString) {
        return ZonedDateTime.of(getLocalDateTime(), getZoneId(zoneString));
    }
}

抽象方法的特点

我们知道,在Java中,接口不是类,而是对类的一组需求描述,类遵循接口描述的统一格式进行定义。Java不支持多继承,主要是为了避免多继承会让语言本身变得复杂(像C++),效率也会降低。而接口可以提供多继承的大多数好处,同时避免多重继承的复杂性和低效性。

从上面的例子,我们可以看到通过static和default修饰符我们可以直接在接口中实现方法体,同时不要忘记,任何在接口中方法声明都是public类型的哦。

  • 抽象方法只有声明,没有实现,抽象方法必须由子类进行重写实现没有实现就是没有方法体(方法体就是方法后面的花括号),意味着这是一个没有完成的方法,抽象的。抽象方法只能由子类去重写实现

  • abstract 关键字不能应用于 static、private 或 final
    方法,因为这些方法不能被重写。

通过对比上面两段定义,可以看出最新的接口多了一些新的属性,接下来主要围绕这些新属性来理解接口的定义。 

OK,现在我们需要一个新的接口:AnotherTimeClient,它得继承TimeClient接口。那么,对于TimeClient接口中定义的getZonedDateTime方法,你可以做如下三种处理:

什么是抽象类


  1. 重新声明getZonedDateTime方法,使它变成abstract类型。
  2. 重新定义getZonedDateTime方法。
  3. 直接继承。

有抽象方法的类就是抽象类

 

而static方法和我们在类里面定义的static方法概念一致。

抽象类的写法public abstract class 类名{}

二、jdk7-jdk9,接口的变化:

抽象类的特点

然后再来简单看一下java interface的一个演变过程:

  • 1、抽象类不能被实例化,实例化就必须实现全部方法,实现全部方法后这个类就不是抽象类。,但可以有构造函数
  • 2、抽象方法必须由子类进行重写实现
  • 3、子类继承自抽象类,必须实现抽象类的全部抽象方法,这样的子类也叫具体类,具体类才可以被实例化(这个实现全部方法方法的子类不是抽象类,抽象类不能实例化)。如果没有实现全部抽象方法,那么这个子类必须也是抽象类。
  • 4、一个类只要出现了abstract方法,那么这个类就必须是abstract类
  • 5、抽象类中可以有非抽象方法,可以有变量
  • Constants (until Java 1.7)

  • Method signatures (until Java 1.7)

  • Nested types (until Java 1.7)

  • Default methods (since 1.8)

  • Static methods (since 1.8)

  • Private methods (since 1.9)

  • Private static methods (since 1.9) 

如果一个类中方法都是抽象方法,那么我们就可以把这个类定义成接口。(接口是一种特殊的类,接口也是类)


代码示例

 

接下看通过简单的代码,看一下抽象类和抽象方法

三、default和public static method:

AbsPerson

接着我们先从一个8年前的问题开始:如果接口中有个方法的定义是可以确定的(实现该接口的类必须重复实现该方法),如何更优雅的设计这个接口?

public abstract class AbsPerson { public int number = 16; { System.out.println("抽象类的代码块"); } public AbsPerson(){ System.out.println("抽象类的构造函数"); } int maxAge = 200; public abstract void say(String str); public abstract void age(); public void absPersonNormal(){ System.out.println("抽象类的普通方法,或者叫 非抽象方法"); } }

文中提到了许多折中的方法:通过接口里的静态类、借助工具类的静态方法等。

..Student

但到了jdk8开始,这个问题被解决了:接口引入了default关键字和静态方法(public
static
method),接口中通过default修饰的方法可以有方法体,实现具体功能。看下面代码:

public class Student extends AbsPerson{ { System.out.println("学生类的代码块"); } public Student() { System.out.println("学生类的构造函数"); } @Override public void say(String str) { System.out.println; } @Override public void age() { System.out.println; }}
public interface Skill{
    void oldSkill();
    default public void newSkill(){
        System.out.print("实现了这个接口的类可以不实现这个方法");
    }
  public static newStaticSkiil(){
        System.out.print("实现了这个接口的类可以不实现这个静态方法");  
    }
}

..AbsPerson

引入default的目的官网有解释:Default
Method ,总的来说,引入default关键字是为了当接口有新的行为且现有依赖该接口的类与接口不需做任何改变,可以不再对于已实现该接口的类或继承该接口的接口进行修改,可以更加方便的拓展现有接口。

// 没实现 AbsPerson 这个抽象类的所有方法,所以这个类也是抽象类public abstract class Worker extends AbsPerson{ @Override public void say(String str) { // TODO Auto-generated method stub }}

文中还指出,对于拓展了包含default method的接口或类:

..TestClass

  • Not mention the default method at all, which lets your extended
    interface inherit the default method.(可以不理,直接继承)
  • Redeclare the default method, which makes
    it abstract.(重新声明为抽象方法)
  • Redefine the default method, which overrides it.(重新定义方法)
public class TestClass{ public static void main(String[] args) { Student student = new Student(); student.say("day day up");// 子类调用实现自抽象类的方法 student.age();// 子类调用实现自抽象类的方法 student.absPersonNormal();// 子类调用抽象类的 非抽象方法 System.out.println("子类调用抽象类的变量: "+student.number); }}

最后,还提到了jdk8开始,接口可以支持静态方法(public static
method),这些都是为了接口可以有更好的演变性。

..运行结果:

例如,jdk8开始引入的Lambda语法以及Stream
API,都是在接口层实现的,因此default及public static
method的提出算是为新的设计服务,也响应了这么久以来定义接口方法的呼声。

抽象类的代码块抽象类的构造函数学生类的代码块学生类的构造函数day day up年龄18抽象类的普通方法,或者叫 非抽象方法子类调用抽象类的变量: 16

Collection接口中,default关键字的身影:

代码已经说得很清楚了。

澳门新浦京娱乐游戏 1

如果一个类中方法都是抽象方法,那么我们就可以把这个类定义成接口。接口的出现让类不必受限于单一继承的束缚,可以灵活地继承一些共有的特性,间接实现类似多继承的目的。

引入default关键字后,有些地方可能要注意一下。首先是如果类实现的多个接口都包含了对同一个方法的default定义,那这个方法必须重写,看下面例子:

接口里面只可能有两种东西

public interface Ina {
    default void say(){
        System.out.println("say a");
    }
}

public interface Inb {
    default void say(){
        System.out.println("say b");
    }
}
  • 1、抽象方法
  • 2、全局静态常量(接口中没有变量,默认都是用 public static
    final标识的,不过在interface中一般不定义属性成员,只定义抽象方法)

如果classc在实现这两个接口时不重写say方法,那编译器无法确定到classc调用say方法时,选择哪一个接口的方法。因此Classc必须重写say:

接口的特点:

澳门新浦京娱乐游戏 2

  • 1、接口的访问修饰符只能是public,或者不写*
    2、interface中定义的方法和成员变量,默认为public访问权限,且仅能为public(声明为其他访问修饰符会报错)

  • 3、接口中的没有变量,只有全局静态常量。(看起来像常量,但是依然是静态全局常量)

  • 4、实现接口的非抽象类必须要实现该接口的所有方法。抽象类可以不用实现。(接口中的方法不能是static、final或者private,也好理解,毕竟带了这些就不能被@Override了)

  • 5、不能使用new操作符实例化一个接口,但可以声明一个接口变量,该变量必须引用一个实现该接口的类的对象。通过这个做回调接口,这也开发中特别常见的。

  • 6、可以使用 instanceof
    检查一个对象是否实现了某个特定的接口。例如:if(anObject instanceof
    Comparable){}。

  • 7、在java8中,接口里也可以定义默认方法:

public class classc implements Ina,Inb {
    @Override
    public  void say(){
        System.out.println("say c");   
    }
}

注意点:在实现多接口的时候一定要避免方法名的重复。(多实现的时候,如果接口重名会比较麻烦,所以起名要有规范)

当然,根据上面提示的3条规则,我们也可以把say方法重新声明为抽象方法:

public interface java8{ //在接口里定义默认方法 default void test(){ System.out.println("java 新特性"); }}
public interface Ind extends Ina{
    public abstract void say();
}

基本特点如上,下面通过示例代码大概看一下:


示例代码

 

IStuent

四、jdk9的补充:

public interface IStuent{ int minAge = 9; // 默认会加上 public static final ,全局静态常量 void iStudentDohomeWord(); // 接口中的方法默认就是 abstract方法 void iStudentGoToSchool();}

jdk9中,接口引入的私有方法(private method)和私有静态方法(private static
method)。由于不可被继承,因此私有方法必须定义方法体才有意义。同时也意味着接口的私有方法和私有静态方法不可被实现该接口的类或继承该接口的接口调用或重写。私有方法(private
method)和私有静态方法(private static
method)的提出都是对jdk8提出的default和public static method的补充。

..IOther

默认方法和静态方法可以共享接口中的私有方法,因此避免了代码冗余,这也使代码更加清晰。如果私有方法是静态的,那这个方法就属于这个接口的。并且没有静态的私有方法只能被在接口中的实例调用。

interface IOther { void iOtherMethod();}

..AbsClass

 

// 抽象类实现接口,可以不复写接口中的 抽象方法public abstract class AbsClass implements IStudent{ // 这里不复写任何抽象方法没问题}

五、接口和抽象类的区别:

..TestClass

抽象类定义:An abstract class is a class that is declared abstract—it
may or may not include abstract methods. Abstract classes cannot be
instantiated, but they can be subclassed.

public class TestClass implements IPerson,IStuent{ public static void main(String[] args) { TestClass testClass = new TestClass(); testClass.iPersonEat(); testClass.iPersonSleep(); testClass.iStudentDohomeWord(); testClass.iStudentGoToSchool(); //minAge = 12; // 会报错,因为接口中的属性都是全局静态常量,不可以重新复制 System.out.println("访问接口中的全局静态常量 "+minAge); // 判断一个类是否实现了某个接口 // 判断方式1: isAssignableFrom boolean result1 = IPerson.class.isAssignableFrom(TestClass.class); System.out.println("IPerson.class.isAssignableFrom ---- IPerson: "+result1); boolean result2 = IOther.class.isAssignableFrom(TestClass.class); System.out.println("IOther.class.isAssignableFrom ---- IOther: "+result2); // 判断一个类是否实现了某个接口 // 判断方式2: instanceof if(testClass instanceof IPerson){ System.out.println("testClass instanceof IPerson: true"); }else{ System.out.println("testClass instanceof IPerson: false"); } if(testClass instanceof IOther){ System.out.println("testClass instanceof IOther: true"); }else{ System.out.println("testClass instanceof IOther: false"); } } @Override public void iPersonEat() { System.out.println("学生是人,会吃东西"); } @Override public void iPersonSleep() { System.out.println("学生是人,会吃睡觉"); } @Override public void iStudentDohomeWord() { System.out.println("做作业,学生要做这个"); } @Override public void iStudentGoToSchool() { System.out.println("上学,学生要做这个"); }}// 这里不能写public访问修饰符,因为interface也是类,一个类Java文件中只能有一个public的类interface IPerson{ void iPersonEat(); void iPersonSleep();}

jdk8之后,接口的行为不再局限在“描述”,接口的行为具有了“定义”,对后期接口的演变和拓展提供了便利性,也表示类的行为可以多继承了;当然即使是多了这些新特性,接口还是和抽象类有一条最明显的分界线:接口无状态,抽象类只能单继承。接口的成员域依然只能是常量,接口依然不能实例化。除此之外,还有权限上的区别,不支持protect声明及包可见等属性。

输出结果


学生是人,会吃东西学生是人,会吃睡觉做作业,学生要做这个上学,学生要做这个访问接口中的全局静态常量 9IPerson.class.isAssignableFrom ---- IPerson: trueIOther.class.isAssignableFrom ---- IOther: falsetestClass instanceof IPerson: truetestClass instanceof IOther: false

 

由上可知1、interface和class写在同一个文件,因为class是public,所以inerface本你来只能写public,但是现在不能写了。2、接口里面看起来像普通变量其实是全局静态常量,不可以重新赋值3、抽象类可以不用全部实现接口中的抽象方法4、具体类需要实现接口中的全部抽象方法才可以实例化5、可以通过isAssignableFrom或者instanceof判断一个类是否实现了某个接口

六、单继承还是多继承:

普通回调

不过,接口的这些新属性是为了更好的服务“单继承多实现”的理念还是更好的往“多继承”靠拢呢?网上有很多不同的声音,而本人更倾向于更好的服务“单继承多实现”,我们知道,最大的改变还是jdk8提出的default和public
static method,接口跳出了只能“声明”方法,只有public abstract
method的界限,从而使得原有框架上多出了更多优雅的设计。打破的规则如果可以让Java更优雅,那这规则就是应该被打破的。

我们现在通过简单代码演示一下点击一个按钮,触发一些功能的逻辑。

参考:

ButtomClass

《Java核心技术 卷1》

public class ButtomClass { private IBtnClick mBtnClick; // 通过对方开放的方法,给调用一个回调接口 public void setOnClickListen(IBtnClick btnClick){ mBtnClick = btnClick; } // 假设是系统按钮内部的单击,不让外部调用 private void systemClick(){ System.out.println("系统内逻辑处理中,等待用户操作"); if(mBtnClick!=null){ mBtnClick.onClick(); } } // 假设是系统按钮内部的双击,不让外部调用 private void systemDoubleClick(){ System.out.println("系统内逻辑处理中,等待用户操作"); if(mBtnClick!=null){ mBtnClick.onDoubleClick(); } } // 假设是系统按钮内部的长按,不让外部调用 private void systemLongClick(){ System.out.println("系统内逻辑处理中,等待用户操作"); if(mBtnClick!=null){ mBtnClick.onLongClick(); } } //========= 以下是模拟用户行为 ========= // 模拟用户单击 public void userDoClick(){ systemClick(); } // 模拟用户双击 public void userDoDoubleClick(){ systemDoubleClick(); } // 模拟用户长按 public void userDoLongClick(){ systemLongClick(); }}

..IBtnClick

public interface IBtnClick { void onClick(); void onLongClick(); void onDoubleClick();}

..

TestClass

More Power to Interface in Java
9

public class TestClass{ public static void main(String[] args) { ButtomClass buttomClass = new ButtomClass(); buttomClass.setOnClickListen(new IBtnClick() { @Override public void onLongClick() { System.out.println("外部回调,按钮 长按 长按"); } @Override public void onDoubleClick() { System.out.println("外部回调,按钮 双击 双击"); } @Override public void onClick() { System.out.println("外部回调,按钮 单击 单击"); } }); buttomClass.userDoClick(); }}

Java Interface-Implementation
Pair(8年前的问题)

假设ButtomClass是一个系统的按钮控件。这个按钮有单击,双击,长按三个事件,这些事件系统的内部处理肯定是对外隐藏的,开发者拿到这个控件的实例后,只需要通过
setOnClickListen
设置一个接口,复写对应的方法,然后写上我们点击之后需要的逻辑,即可将逻辑回调回系统控件ButtomClass。

像上面这么写,已经模拟完成了。

打印输出:

系统内逻辑处理中,等待用户操作外部回调,按钮 单击 单击

..这一切没什么问题,但是用的人可能觉得不爽,对一个按钮来说,最常见是单击,每次你都让我复写三个方法,我肯有可能
双击 和 长按 都是放空不写的,代码多余长了一些我不喜欢。

好,那现在我们就再优化一下代码

升级版回调

第一步,新增一个抽象类起名为FreeClickListen,然后先把方法再这里先复写了。相当于这是一个子接口

FreeClickListen

public abstract class FreeClickListen implements IBtnClick{ @Override public void onClick() { } @Override public void onLongClick() { } @Override public void onDoubleClick() { } }

..第二步,TestClass 代码有小小的改动

public class TestClass{ public static void main(String[] args) { ButtomClass buttomClass = new ButtomClass();// buttomClass.setOnClickListen(new IBtnClick() {// @Override// public void onLongClick() {// System.out.println("外部回调,按钮 长按 长按");// }// // @Override// public void onDoubleClick() {// System.out.println("外部回调,按钮 双击 双击");// }// // @Override// public void onClick() {// System.out.println("外部回调,按钮 单击 单击");// }// }); // 通过这种方式我们就可以不用必须复写三个方法了,也不会觉得有多余的很长的代码 buttomClass.setOnClickListen(new FreeClickListen() { @Override public void onClick() { super.onClick(); System.out.println("外部回调,按钮 单击 单击 现在我可以只复写我需要的了"); } }); buttomClass.userDoClick(); }}

完成。

打印输出:

系统内逻辑处理中,等待用户操作外部回调,按钮 单击 单击 现在我可以只复写我需要的了

这种方式很常见,比如系统给我们的接口需要复写很多个方法,通过这种方式,只需要多加一个抽象类,我们就可以在很多地方避免拖着长长的代码,而在需要比如
长按 这些功能的时候,我们只需要在

buttomClass.setOnClickListen(new FreeClickListen() { @Override public void onClick() { super.onClick(); System.out.println("外部回调,按钮 单击 单击 现在我可以只复写我需要的了"); } });

里面复写对应的方法即可。

通过这个可以举一反三,比如一些网络请求,我们可以通过类似的方式,调整一下,做统一处理,比如结果码的统一处理。

比如一个interface里面有10个接口,子类每次实现,只有2个方法是必须强制复写的,剩下都是可选项。怎么做呢。其实就是根据上面的代码,哪一个抽象类去实现一个接口,然后抽象类里面只复写那些非强制的,那么下次我们new
这个抽象类的时候,就必须强制复写那2个强制的了。另外的8个变成可选项了。

还是来个代码吧

接口

public interface IPostJsonStringCb { void onSuccess(String str); void onError(String str); void onStart(Request<String, ? extends Request> str); void onFinish();}

.抽象类

public abstract class AbsPostJsonStringCb implements IPostJsonStringCb{ // 抽象类里面复写的方法后面作为 非必选方法。 @Override public void onError(String str) { } @Override public void onStart(Request<String, ? extends Request> str) { }}

.剩下onSuccess和onFinish就是需要强制实现的了

澳门新浦京娱乐游戏 3image.png

1、基本都是接口2、什么时候用抽象类?

  • 需要定义子类的行为,有要为子类提供基础性功能时
  • 做一个封装,比如适配器模式等
比较 抽象类 接口
关键字 abstract interface
定义 包括一个抽象方法 都是抽象方法和全局静态常量
组成 抽象方法,普通方法,变量,常量,构造 抽象方法,全局静态常量
权限 不能private 只能是public
使用 通过extents继承 通过implement实现
局限 抽象类只能单继承
顺序 先继承后实现,多个接口都好隔开 先继承后实现,多个接口都好隔开
设计模式 模板模式等 工厂模式,代理模式等
结合 可以一起做个设配器模式 可以一起做个设配器模式

两者都是依靠多态性,通过子类进行实例化。

You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图