【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中 ...
随机推荐
- opencv::Laplance算子
Laplance算子 理论:在二阶导数的时候,最大变化处的值为零即边缘是零值.通过二阶导数计算,依据此理论我们可以计算图像二阶导数,提取边缘. 拉普拉斯算子(Laplance operator) 处理 ...
- Java Web 学习(1) —— Servlet
Java Web 学习(1) —— Servlet 一. 什么是 Servlet Java Servlet 技术是Java体系中用于开发 Web 应用的底层技术. Servlet 是运行在 Servl ...
- LeetCode 2: single-number II
Given an array of integers, every element appears three times except for one. Find that single one. ...
- konva canvas插件写雷达图示例
最近,做了一个HTML5的项目,里面涉及到了雷达图效果,这里,我将react实战项目中,用到的雷达图单拎出来写一篇博客,供大家学习. 以下内容涉及的代码在我的gitlab仓库中:Konva canva ...
- PHPStorm IntelliJ IDEA 代码缩进风格设置
关于缩进风格,我还是觉得4空格比tab好 File -> Settings -> Editor -> Code Style: 如图,把Detect and use exsiting ...
- (四)Kinect人脸识别
kinect可以通过摄动摄像头不仅可以获取人脸位置旋转信息,也可以获取脸部轮廓的三维坐标 可以参考插件中的场景KinectFaceTrackingDemo1-4,在kinectManager基础上需要 ...
- PHP输出A到Z及相关
先看以下一段PHP的代码,想下输出结果是什么. <?php for($i='A'; $i<='Z'; $i++) { echo $i . '<br>'; } ?> 输出的 ...
- git .gitignore详解
1.最近使用git又遇到一个陷阱: 场景:A和B使用的不同的编译器做的同一个解决方案下的不同的项目工程,刚开始没考虑到版本问题,后来发现A上传的csproj在B需要做很麻烦修改才能打开,后来想到各自用 ...
- iSCSI 共享存储
iSCSI(Internet Small Computer System Interface,发音为/ˈаɪskʌzi/),Internet小型计算机系统接口,又称为IP-SAN,是一种基于 ...
- CSPS模拟 51
蒟蒻由于仍然苟活在$1jf$,不得不接受省选题的吊打$QWQ$ 蒟蒻由于拿了大神们不屑打的弱智暴力,而大神们$T3$的各种快速变换没调出来,所以拿到辽人生第一个$1jf$黄名 既侥幸又$kx$ T1 ...