【Java】抽象类和接口详解
抽象类
一、抽象类的概述
现在请思考一个问题:假如我现在又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 接口名称 {// ...}
二、基本实现方式
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、默认方法的使用
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、静态方法的使用
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();
}
}
4、私有方法的使用
定义接口:
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();
}
}
注意区分:
普通私有方法:只有默认方法可以调用。
私有静态方法:默认方法和静态方法都可以调用。
三、多实现方式
在继承体系中,一个类只能继承一个父类。而对于接口而言,一个类是可以实现多个接口的,这叫做接口的多实现。并且,一个类能继承一个父类,同时实现多个接口。
同时在使用接口的时候需要注意:
实现格式:
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】抽象类和接口详解的更多相关文章
- java抽象类和接口详解
接口和内部类为我们提供了一种将接口与实现分离的更加结构化的方法. 抽象类与接口是java语言中对抽象概念进行定义的两种机制,正是由于他们的存在才赋予java强大的面向对象的能力.他们两者之间对抽象概念 ...
- java抽象类与接口 详解
在面向对象的概念中,我们知道所有的对象都是通过类来描绘的,但是并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类. 抽象类往往用来表征我们在对问题 ...
- java.io.DataInput接口和java.io.DataOutput接口详解
public interface DataInput DataInput 接口用于从二进制流中读取字节,并重构所有 Java 基本类型数据.同时还提供根据 UTF-8 修改版格式的数据重构 Strin ...
- JDK1.8 java.io.Serializable接口详解
java.io.Serializable接口是一个标志性接口,在接口内部没有定义任何属性与方法.只是用于标识此接口的实现类可以被序列化与反序列化.但是它的奥秘并非像它表现的这样简单.现在从以下几个问题 ...
- Java基础(basis)-----抽象类和接口详解
1.抽象类 1.1 abstract修饰类:抽象类 不可被实例化 抽象类有构造器 (凡是类都有构造器) 抽象方法所在的类,一定是抽象类 抽象类中可以没有抽象方法 1.2 abstract修饰方法:抽象 ...
- Java中的接口详解
接口 是Java语言中一种引用类型,是方法的集合,如果说类的内部封装了成员变量.构造方法和成员方法,那么接口的内部主要就是封装了方法,包含抽象方法(JDK 7及以前),默认方法和静态方法(JDK 8) ...
- java 锁 Lock接口详解
一:java.util.concurrent.locks包下常用的类与接口(lock是jdk 1.5后新增的) (1)Lock和ReadWriteLock是两大锁的根接口,Lock代表实现类是Reen ...
- java.lang.Comparable 接口 详解
参考https://blog.csdn.net/itm_hadf/article/details/7432782 http://www.blogjava.net/jjshcc/archive/2011 ...
- 第十八节:详解Java抽象类和接口的区别
前言 对于面向对象编程来说,抽象是它的特征之一. 在Java中,实现抽象的机制分两种,一为抽象类,二为接口. 抽象类为abstract class,接口为Interface. 今天来学习一下Java中 ...
随机推荐
- [Luogu1291][SHOI2002]百事世界杯之旅
题目描述 “……在2002年6月之前购买的百事任何饮料的瓶盖上都会有一个百事球星的名字.只要凑齐所有百事球星的名字,就可参加百事世界杯之旅的抽奖活动,获得球星背包,随声听,更克赴日韩观看世界杯.还不赶 ...
- 《Java并发编程实战》读书笔记-第一部分 基础知识小结
并发技巧清单 可变状态是至关重要的 所有的并发问题都可以归结为如何协调对并发状态的访问.可变状态越少,就越容易确保线程安全性. 尽量将域声明为final类型,除非需要它们是可变的. 不可变对象一定是线 ...
- JS实现生成一个周对应日期数组
/* 获取日期和周 */ getDateWeek() {/* 得到当前日期的时间戳 */ const timestamp = Date.now() // const timestamp = new D ...
- tkinter基础-标签、按钮
本节内容: 明白标签.按钮的使用 实现简单的点击界面 Tkinter 简称tk,在python中属于内置模块,不需要进行安装,可直接引用,import tkinter 一. 首先我们做一个如图所示的图 ...
- 基于 HTML5 + WebGL 实现的垃圾分类系统
前言 垃圾分类,一般是指按一定规定或标准将垃圾分类储存.分类投放和分类搬运,从而转变成公共资源的一系列活动的总称.分类的目的是提高垃圾的资源价值和经济价值,力争物尽其用.垃圾在分类储存阶段属于公众的私 ...
- Arduino学习笔记⑧ 红外通信
1.前言 红外通信是一种利用红外光编码进行数据传输的无线通信方式,在目前来说是使用非常广泛的.生活中常见电视遥控器,空调遥控器,DVD遥控器(现在估计是老古董了),均使用红外线遥控.使用红外线 ...
- linux 中more、less 和 most 的区别
如果你是一个 Linux 方面的新手,你可能会在 more.less.most 这三个命令行工具之间产生疑惑.在本文当中,我会对这三个命令行工具进行对比,以及展示它们各自在 Linux 中的一些使用例 ...
- Android9.0 如何区分SDK接口和非 SDK接口
刚刚有同学问我,不太了解 "非SDK接口" 是什么意思?android9.0有什么限制 ?apache的http也有限制 ? 而且现在的大部分系统都升级上来了,黑名单.灰名单和白名 ...
- django-URL反向解析Reverse(九)
解决path中带参数的路径. reverse(viewname,urlconf=None,args=None,Kwargs=None,current_app=None) book/views.py f ...
- django-URL重定向(八)
HttpResponseRedirect()不常用 redirect(to,permanent=False,*args,**kwargs) to:指重定向的位置,可以是视图,也可以是url地址,也可以 ...