抽象类

一、抽象类的概述

现在请思考一个问题:假如我现在又Dog、Cat、Pig等实例对象,现在我们把它们都抽象成一个Animal类,这个类应该包含了这些Dog、Cat、Pig等实例对象eat的功能,所以我们按照之前的思路会在Animal类当中定义一个eat方法,但是有个问题Dog、Cat、Pig的eat行为都有所不同,所以我们按照之前的方式自然会覆盖重写Animal类中eat方法。但是这样就导致Animal的eat方法中的方法体和定义方法的方式没有了任何存在的意义,对吗?

public class Animal {
public void eat(){ }
} public class Dog extends Animal{
@Override
public void eat(){
System.out.println("啃骨头!");
}
} public class Cat extends Animal{
@Override
public void eat(){
System.out.println("吃猫粮!");
}
}

那么如何解决这种无效代码的问题呢?其实抽象类就可以:

public abstract class Animal {
public abstract void eat();
}

从形式上来看是不是就已经解决了上述中的问题?其实这个时候的Animal类就是一个抽象类。所以:

父类中的方法,被它的子类们重写,子类各自的实现都不尽相同。那么父类的方法声明和方法主体,只有声明还有意义,而方法主体则没有存在的意义了。我们把没有方法主体的方法称为抽象方法(如Animal类中的eat方法)。Java语法规定,包含抽象方法的类就是抽象类(如Animal类)。

抽象方法 : 没有方法体的方法。
抽象类:包含抽象方法的类。

二、使用方式

1、抽象方法

使用 abstract 关键字修饰方法,该方法就成了抽象方法,抽象方法只包含一个方法名,而没有方法体。

定义格式:修饰符 abstract 返回值类型 方法名 (参数列表);

如 public abstract void eat();

2、抽象类

如果一个类包含抽象方法,那么该类必须是抽象类。

定义格式:

修饰符 abstract class 类名字 {
}

如 public abstract class Animal {}

3、使用方式

继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该父类的抽象方法,否则,从最初的父类到最终的子类都不能创建对象,失去意义。如下:

public abstract class Animal {
public abstract void eat();
} public class Dog extends Animal{
@Override
public void eat(){
System.out.println("啃骨头!");
}
} public class Demo {
public static void main(String[] args) {
Dog dog=new Dog();
dog.eat(); // 啃骨头
}
}

此时的方法重写(也可以不用写 @Override )是子类对父类抽象方法的完成实现,我们将这种方法重写的操作,也叫做实现方法。

注意事项:

(1) 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。

// 错误写法
Animal animal = new Animal();

假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。

(2)抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。

子类的构造方法中,有默认的super(),需要访问父类构造方法。

(3)抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。

public abstract class MyAbstract {

}

未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。

(4)抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。

public abstract class Animal {

    public abstract void eat();

}

public abstract class Dog extends Animal {

    @Override
public void eat() {
System.out.println("狗吃骨头");
} public abstract void sleep(); } public class DogGolden extends Dog { @Override
public void sleep() {
System.out.println("呼呼呼……");
} } public class Demo { public static void main(String[] args) { // Animal animal = new Animal(); // 错误!
// Dog dog = new Dog(); // 错误! DogGolden golden = new DogGolden(); // 正确!
golden.eat();
golden.sleep();
} }

假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。

接口

接口,是Java语言中一种引用类型,是方法的集合,如果说类的内部封装了成员变量、构造方法和成员方法,那么接口的内部主要就是封装了方法。

接口包含了抽象方法、默认方法、静态方法(JDK 8+)、私有方法(JDK 9+)。它也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型。

引用数据类型:数组,类,接口。

一、接口定义与使用

1、定义

它与定义类方式相似,但是使用 interface 关键字。再次声明接口并不是一个类。其格式为:

public interface 接口名称 {
  // 抽象方法
  // 默认方法
  // 静态方法
  // 私有方法
}

2、使用

接口在使用时不能创建对象,但是可以被实现( implements ,类似于被继承)。其格式为:

public class 实现类名称 implements 接口名称 {
  // ...
}
接口不能直接使用,必须有一个“实现类”来“实现”该接口。
类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。实现的动作类似继承,格式相仿只是关键字不同,实现使用 implements 关键字。

二、基本实现方式

1、抽象方法的使用

定义接口:

public interface MyInterfaceAbstract {

    // 这是一个抽象方法
public abstract void methodAbs1(); // 这也是抽象方法
abstract void methodAbs2(); // 这也是抽象方法
public void methodAbs3(); // 这也是抽象方法
void methodAbs4();

上面代码的四种定义方法的方式都是抽象方法的定义方式。

定义实现类:

public class MyInterfaceAbstractImpl implements MyInterfaceAbstract {
@Override
public void methodAbs1() {
System.out.println("这是第一个方法!");
} @Override
public void methodAbs2() {
System.out.println("这是第二个方法!");
} @Override
public void methodAbs3() {
System.out.println("这是第三个方法!");
} @Override
public void methodAbs4() {
System.out.println("这是第四个方法!");
}
}

MyInterfaceAbstractImpl 为 MyInterfaceAbstract 接口的实现类,并且推荐命名规则为:接口名Impl。

实现类必须重写接口中所有抽象方法。

定义测试类:

public class Demo {

    public static void main(String[] args) {
// 错误写法!不能直接new接口使用
// MyInterfaceAbstract inter = new MyInterfaceAbstract(); // 创建实现类的对象使用
MyInterfaceAbstractImpl impl = new MyInterfaceAbstractImpl();
impl.methodAbs1();
impl.methodAbs2();
     // ...
}
}

注意事项:如果实现类并没有覆盖重写接口中所有的抽象方法,那么这个实现类自己就必须是抽象类。

2、默认方法的使用

从Java 8开始,接口里允许定义默认方法。
定义接口:
public interface MyInterfaceDefault {

    // 默认方法
public default void methodDefault() {
System.out.println("这是接口的默认方法");
} }

定义实现类:

public class MyInterfaceDefaultImpl implements MyInterfaceDefaultImpl {
// 什么都不用写,后面直接调用
}

定义测试类:

public class Demo{

    public static void main(String[] args) {

        // 创建了实现类对象
MyInterfaceDefaultImpl a = new MyInterfaceDefaultImpl(); // 调用默认方法,如果实现类当中没有会向上找接口
a.methodDefault();
}
}
接口的默认方法,通过接口实现类对象直接调用。
接口的默认方法,也可以被接口实现类进行覆盖重写,代码如下:
实现类中代码有所变化,如下:
public class MyInterfaceDefaultImpl implements MyInterfaceDefaultImpl {

    @Override
public void methodDefault() {
System.out.println("实现类中覆盖重写了接口的默认方法");
} }

测试类中代码如下:

public class Demo {

    public static void main(String[] args) {

        // 创建了实现类对象
MyInterfaceDefaultImpl a = new MyInterfaceDefaultImpl();
a.methodDefault(); // 实现类中覆盖重写了接口的默认方法
} }

因此,接口的默认方法可以继承,可以重写,二选一,但是只能通过实现类的对象来调用。从作用上来看接口当中的默认方法,可以解决接口升级的问题。

3、静态方法的使用

从Java 8开始,接口当中允许定义静态方法。
定义接口:
public interface MyInterfaceStatic {

    public static void methodStatic() {
System.out.println("这是接口的静态方法!");
} }

定义实现类(也可以不用写):

public class MyInterfaceStaticImpl implements MyInterfaceStatic {

}

定义测试类:

public class Demo {

    public static void main(String[] args) {
// 创建了实现类对象
MyInterfaceStaticImpl impl = new MyInterfaceStaticImpl();
// 错误写法!
// impl.methodStatic(); // 直接通过接口名称调用静态方法
MyInterfaceStatic.methodStatic();
} }
注意事项:不能通过接口实现类的对象来调用接口当中的静态方法。
因为静态与.class 文件相关,只能使用接口名调用,不可以通过实现类的类名或者实现类的对象调用。

4、私有方法的使用

从Java 9开始,接口当中允许定义私有方法。如果一个接口中有多个默认方法,并且方法中有重复的内容,那么可以抽取出来,封装到私有方法中,供默认方法去调用。从设计的角度讲,私有的方法是对默认方法和静态方法的辅助。

定义接口:

public interface MyInterface {

    public default void methodDefault1() {
System.out.println("默认方法1");
methodCommon();
} public default void methodDefault2() {
System.out.println("默认方法2");
methodCommon();
} public default void methodCommon() {
System.out.println("AAA");
System.out.println("BBB");
System.out.println("CCC");
} }

定义实现类:

public class MyInterfaceImpl implements MyInterface {

    public void methodAnother() {
// 直接访问到了接口中的默认方法
methodCommon();
} }

定义测试类:

public class Demo {

    public static void main(String[] args) {
MyInterfaceImpl impl =new MyInterfaceImpl();
impl.methodDefault1();
impl.methodDefault2();
impl.methodAnother();
}
}

但是该方法应该只能使用在接口当中,因此不能将其暴露出来。为了避免破坏这种私有性,所以咱们可以采用私有方法来解决这个问题,因此在接口中的代码如下:

public interface MyInterface {

    public default void methodDefault1() {
System.out.println("默认方法1");
methodCommon();
} public default void methodDefault2() {
System.out.println("默认方法2");
methodCommon();
} private void methodCommon() {
System.out.println("AAA");
System.out.println("BBB");
System.out.println("CCC");
} }

其实上述代码中的 private 关键字对应的方法属于普通私有方法,普通私有方法可以解决多个默认方法之间重复代码以及私有性问题。

还有一种解决多个静态方法之间重复代码以及私有性问题的方式,这种方式我们叫它私有静态方法

定义测试类:

public interface MyInterface {

    public static void methodStatic1() {
System.out.println("静态方法1");
methodStaticCommon();
} public static void methodStatic2() {
System.out.println("静态方法2");
methodStaticCommon();
} private static void methodStaticCommon() {
System.out.println("AAA");
System.out.println("BBB");
System.out.println("CCC");
} }

在这里我们可以不用写接口的实现类,直接调用即可。代码如下:

public class Demo {

    public static void main(String[] args) {

        MyInterface.methodStatic1();
MyInterface.methodStatic2();
// 错误写法!
// MyInterface.methodStaticCommon();
} }

注意区分:

普通私有方法:只有默认方法可以调用。
私有静态方法:默认方法和静态方法都可以调用。

三、多实现方式

在继承体系中,一个类只能继承一个父类。而对于接口而言,一个类是可以实现多个接口的,这叫做接口的多实现。并且,一个类能继承一个父类,同时实现多个接口。

同时在使用接口的时候需要注意:

(1)接口是没有静态代码块或者构造方法的;
(2)一个类的直接父类是唯一的,但是一个类可以同时实现多个接口;
(3)如果实现类所实现的多个接口当中,存在重复的抽象方法,那么只需要覆盖重写一次即可;
(4)如果实现类没有覆盖重写所有接口当中的所有抽象方法,那么实现类就必须是一个抽象类;
(5)如果实现类所实现的多个接口当中,存在重复的默认方法,那么实现类一定要对冲突的默认方法进行覆盖重写;
(6)实现类如果父类当中的方法和接口当中的默认方法产生了冲突,优先用父类当中的方法;
(7)接口中,存在同名的静态方法并不会冲突,原因是只能通过各自接口名访问静态方法。

实现格式:

class 类名 [extends 父类名] implements 接口名1,接口名2,接口名3... {
  // 重写接口中抽象方法【必须】
  // 重写接口中默认方法【不重名时可选】
}

代码如下,定义接口:

// 定义父类
public class Parent { public void method(){
System.out.println("Parent");
}
} // 定义接口A
public interface MyInterfaceA { // 错误写法!接口不能有静态代码块
  // static {} // 错误写法!接口不能有构造方法
  // public MyInterfaceA() {} public abstract void methodA(); public abstract void methodAbs(); public default void methodDefault() {
System.out.println("默认方法AAA");
} public default void method() {
System.out.println("MyInterfaceA");
}
} // 定义接口B
public interface MyInterfaceB { public abstract void methodB(); public abstract void methodAbs(); public default void methodDefault() {
System.out.println("默认方法BBB");
} }

定义实现类:

public class MyInterfaceImpl extends Parent implements MyInterfaceA, MyInterfaceB {

    @Override
public void methodA() {
System.out.println("覆盖重写了A方法");
} @Override
public void methodB() {
System.out.println("覆盖重写了B方法");
} @Override
public void methodAbs() {
System.out.println("覆盖重写了AB接口都有的抽象方法");
} @Override
public void methodDefault() {
System.out.println("对多个接口当中冲突的默认方法进行了覆盖重写");
}
}

定义测试类:

public class Demo01Interface {

    public static void main(String[] args) {

        MyInterfaceImpl impl = new MyInterfaceImpl();

        impl.methodA(); // 覆盖重写了A方法

        impl.methodB(); // 覆盖重写了B方法

        impl.methodAbs(); // 覆盖重写了AB接口都有的抽象方法

        impl.methodDefault(); // 对多个接口当中冲突的默认方法进行了覆盖重写

        impl.method(); // Parent
} }

四、多继承方式

之前在讲类的继承性的时候我们知道Java类是不能实现多继承的。因为如果类要实现多继承就要面临一些问题,如:

(1)如果有两个父类,两个父类里有一个相同的方法,那么作为子类应该怎么继承这个方法?父类1的还是父类2的? 当然编译器可以报错,不允许出现相同的方法。

(2)两个父类继承自同一个基类,则子类中会包含两份祖父类的内容,不合并重复内容会引起一些歧义,而合并重复内容又会导致类成员的内存布局不能简单复制地从父类复制。

如上所述,这样其实增大了程序语言实现的复杂度,也没有带来很多的优化,但是接口能实现多继承。

一个接口能继承另一个或者多个接口,这和类之间的继承比较相似。接口的继承也是使用 extends 关键字,子接口继承父接口的方法。

定义父接口:

public interface MyInterfaceA {

    public abstract void methodA();

    public abstract void methodCommon();

    public default void methodDefault() {
System.out.println("AAA");
} } public interface MyInterfaceB { public abstract void methodB(); public abstract void methodCommon(); public default void methodDefault() {
System.out.println("BBB");
} }

定义子接口:

public interface MyInterface extends MyInterfaceA, MyInterfaceB {

    public abstract void method();

    @Override
public default void methodDefault() {
    // 如果父接口中的默认方法有重名的,那么子接口需要重写一次
}

定义实现类:

public class MyInterfaceImpl implements MyInterface {
@Override
public void method() { } @Override
public void methodA() { } @Override
public void methodB() { } @Override
public void methodCommon() { }
}

子接口重写默认方法时,default关键字必须要保留。
子类重写默认方法时,default关键字不可以保留。

通过上面代码可以看出接口实现多继承,不管哪个接口调用的都是同一个实现。因为继承父类包括实现,继承接口只包括接口,就是这样。

五、接口总结

接口中,无法定义成员变量,但是可以定义常量,其值不可以改变,默认使用public static final修饰。
接口中,没有构造方法,不能创建对象。
接口中,没有静态代码块。

【Java】抽象类和接口详解的更多相关文章

  1. java抽象类和接口详解

    接口和内部类为我们提供了一种将接口与实现分离的更加结构化的方法. 抽象类与接口是java语言中对抽象概念进行定义的两种机制,正是由于他们的存在才赋予java强大的面向对象的能力.他们两者之间对抽象概念 ...

  2. java抽象类与接口 详解

    在面向对象的概念中,我们知道所有的对象都是通过类来描绘的,但是并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类. 抽象类往往用来表征我们在对问题 ...

  3. java.io.DataInput接口和java.io.DataOutput接口详解

    public interface DataInput DataInput 接口用于从二进制流中读取字节,并重构所有 Java 基本类型数据.同时还提供根据 UTF-8 修改版格式的数据重构 Strin ...

  4. JDK1.8 java.io.Serializable接口详解

    java.io.Serializable接口是一个标志性接口,在接口内部没有定义任何属性与方法.只是用于标识此接口的实现类可以被序列化与反序列化.但是它的奥秘并非像它表现的这样简单.现在从以下几个问题 ...

  5. Java基础(basis)-----抽象类和接口详解

    1.抽象类 1.1 abstract修饰类:抽象类 不可被实例化 抽象类有构造器 (凡是类都有构造器) 抽象方法所在的类,一定是抽象类 抽象类中可以没有抽象方法 1.2 abstract修饰方法:抽象 ...

  6. Java中的接口详解

    接口 是Java语言中一种引用类型,是方法的集合,如果说类的内部封装了成员变量.构造方法和成员方法,那么接口的内部主要就是封装了方法,包含抽象方法(JDK 7及以前),默认方法和静态方法(JDK 8) ...

  7. java 锁 Lock接口详解

    一:java.util.concurrent.locks包下常用的类与接口(lock是jdk 1.5后新增的) (1)Lock和ReadWriteLock是两大锁的根接口,Lock代表实现类是Reen ...

  8. java.lang.Comparable 接口 详解

    参考https://blog.csdn.net/itm_hadf/article/details/7432782 http://www.blogjava.net/jjshcc/archive/2011 ...

  9. 第十八节:详解Java抽象类和接口的区别

    前言 对于面向对象编程来说,抽象是它的特征之一. 在Java中,实现抽象的机制分两种,一为抽象类,二为接口. 抽象类为abstract class,接口为Interface. 今天来学习一下Java中 ...

随机推荐

  1. ArrayList源码解析[一]

    ArrayList源码解析[一] 欢迎转载,转载烦请注明出处,谢谢. https://www.cnblogs.com/sx-wuyj/p/11177257.html 在工作中集合list集合用的相对来 ...

  2. 【OUC2019写作】论文写作第九小组英语常用表达整理

    第一部分:  一.简要综述以往和现在研究: 某方法被认为如何如何:it is well known that; it is regarded as; it is believed to ; It is ...

  3. 浏览器安装Tampermonkey(俗称油猴子插件),实现免费观看Vip视频、免费下载付费资源等……

    应用场景 说起浏览器,本人常用google,谷歌浏览器,速度快,里面有很多插件,可以实现用户百度云盘下载限制,破解vip视频.百度广告屏蔽,视频广告的屏蔽,百度网盘资源直接下载等实用功能.今天就来分享 ...

  4. 第三方应用 flashfxp,filezilla提权

    filezilla 提权 filezilla 开源的ftp服务器 默认监听14147端口 默认安装目录下有个敏感文件 filezillaserver.xml(包含用户信息) filezillaserv ...

  5. 使PC端网页宽度自适应手机屏幕大小

    有时候我们会纠结PC页面在手机页面上无法正常显示,超出屏幕,有些内容看不到,现在又了下面的代码,可以做到自适应手机端的屏幕宽度: 在html的<head>中增加一个meta标签: < ...

  6. 破解Android设备无法联调的谜题

    这篇文章要感谢来自知乎的小伙伴:子非鱼,他最近被一件事情困惑,那就是:Android手机无法联调了.在解决完他的疑问后,突然意识到,其实自己在前一段时间也曾遇到同样的问题,最后居然还怀疑是电脑和手机不 ...

  7. 基于 HTML5 + WebGL 的 3D 可视化挖掘机

    前言 在工业互联网以及物联网的影响下,人们对于机械的管理,机械的可视化,机械的操作可视化提出了更高的要求.如何在一个系统中完整的显示机械的运行情况,机械的运行轨迹,或者机械的机械动作显得尤为的重要,因 ...

  8. cordova开发环境搭建

    最近我在尝试了解跨平台技术的发展,首先则是想到了cordova.本文简单记录下cordova环境搭建的过程. 安装cordova 首先是要npm全局安装cordova npm install -g c ...

  9. vue表单属性

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  10. 机器学习回顾篇(9):K-means聚类算法. slides

    .caret, .dropup > .btn > .caret { border-top-color: #000 !important; } .label { border: 1px so ...