【Java】Java进阶学习笔记(四)—— 抽象类与接口
- 一、抽象类
- 1、抽象类的概念
- 抽象类的定义格式
- 2、抽象类的注意点
- 抽象方法的介绍
- 3、抽象类的具体作用
- 4、抽象类实例
- 二、接口
- (一)、接口的概念
- 1、接口与类的区别
- 2、接口特性
- 3、抽象类和接口的区别
- (二)、接口间的继承
- 1、接口的单继承
- 2、接口的多继承
- (三)、接口的实例
- (四)、标记接口
- 三、抽象类和接口的使用
抽象类与接口是 java语言中对抽象概念进行定义的两种机制,正是由于他们的存在才赋予java强大的面向对象的能力。他们两者之间对抽象概念的支持有很大的相似,甚至可以互换,但是也有区别。
一、抽象类
1、抽象类的概念
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
接口设计的好处,牢记多态的好处:让程序猿忘记类型。有了接口以后,类的使用者就不必关心具体类型,而只关注某个类是否具备某种能力。
抽象类的定义格式
抽象类的定义格式:在 class 前添加 abstract(中文意思:抽象的)关键字,就定义了一个抽象类。
abstract class A{}
2、抽象类的注意点
抽象类需要注意:
1、抽象类主要用于被继承,被继承后子类里面需要重写父类(抽象类)里面的所有抽象方法。
2、抽象类里面的方法可以有抽象方法和普通成员方法。换句话说也就是有抽象类不一定有抽象方法,但是有抽象方法一定要有抽象类。
3、抽象类不能实例化对象
。
4、抽象类里面可以有普通成员变量和普通成员方法。
5、抽象类可以有构造方法,创立子类对象时候,由子类构造方法初始化父类成员变量。
6、抽象类里面的抽象方法没有具体实现,就等于只是放了个名字在抽象类里面,该抽象方法用于被子类重写后通过抽象类这个父类调用子类重写后的方法。
7、如果一个抽象类A继承另一个抽象类B,那么此时这个抽象类A可以不重写B当中的抽象方法。
8、抽象方法不能是private的、static的、final的。
eg:
(1)抽象方法不能是private的
abstract class Fruit{abstract private void grow();
}//编译出错:
Error:(4,27)java:非法的修饰组合:abstract和private
注意:抽象方法没有加访问限定符时,默认是public
(2)抽象方法不能被 final 和 static 修饰,因为抽象方法要被子类重写
public abstract class Shape{abstract final void methodA();abstract public static void methodB();
}//编译报错
//Error:(20,25)java:非法的修饰符组合:abstract和final
//Error:(21,35)java:非法的修饰符组合:abstract和static
抽象方法的介绍
- 当子类继承父类时候,需要重写父类中的抽象方法,否则会报错。除非子类也是抽象类,继承的父类也是抽象类的时候不用重写父类的抽象方法
此时重写父类抽象方法不报错:
此时子类是抽象类继承父类也是抽象类的时候,不重写父类的抽象方法不报错:
3、抽象类的具体作用
- 那么抽象类的具体作用是什么呢?
便于检查是否错误调用父类方法。因为在写代码的时候,如果用了继承,然后父类是普通类,子类重写了父类方法,然后实例化对象的时候调用的是父类的方法而不是子类重写的方法,此时执行父类的方法得到并不是我们想要的功能。但是如果父类是抽象类,我们在父类中的抽象类可以不用写内容,这样在执行的时候我们可以发现我们调用的方法并没有效果,此时可以发现是错误地使用了父类的方法。
其实抽象类在我看来可以说是便于我们检查我们写代码的时候的错误。
4、抽象类实例
在 Java 语言中使用 abstract class 来定义抽象类。如下实例:
/* 文件名 : Employee.java */
public abstract class Employee
{private String name;private String address;private int number;public Employee(String name, String address, int number){System.out.println("Constructing an Employee");this.name = name;this.address = address;this.number = number;}public double computePay(){System.out.println("Inside Employee computePay");return 0.0;}public void mailCheck(){System.out.println("Mailing a check to " + this.name+ " " + this.address);}public String toString(){return name + " " + address + " " + number;}public String getName(){return name;}public String getAddress(){return address;}public void setAddress(String newAddress){address = newAddress;}public int getNumber(){return number;}
}
注意到该 Employee 类没有什么不同,尽管该类是抽象类,但是它仍然有 3 个成员变量,7 个成员方法和 1 个构造方法。 现在如果你尝试如下的例子:
/* 文件名 : AbstractDemo.java */
public class AbstractDemo
{public static void main(String [] args){/* 以下是不允许的,会引发错误 */Employee e = new Employee("George W.", "Houston, TX", 43);System.out.println("\n Call mailCheck using Employee reference--");e.mailCheck();}
}
当你尝试编译 AbstractDemo 类时,会产生如下错误:
Employee.java:46: Employee is abstract; cannot be instantiatedEmployee e = new Employee("George W.", "Houston, TX", 43);^
1 error
- 继承抽象类
我们可以通过以下方式继承 Employee 类的属性:
/* 文件名 : Salary.java */
public class Salary extends Employee
{private double salary; //Annual salarypublic Salary(String name, String address, int number, doublesalary){super(name, address, number);setSalary(salary);}public void mailCheck(){System.out.println("Within mailCheck of Salary class ");System.out.println("Mailing check to " + getName()+ " with salary " + salary);}public double getSalary(){return salary;}public void setSalary(double newSalary){if(newSalary >= 0.0){salary = newSalary;}}public double computePay(){System.out.println("Computing salary pay for " + getName());return salary/52;}
}
尽管我们不能实例化一个 Employee 类的对象,但是如果我们实例化一个 Salary 类对象,该对象将从 Employee 类继承 7 个成员方法,且通过该方法可以设置或获取三个成员变量。
/* 文件名 : AbstractDemo.java */
public class AbstractDemo
{public static void main(String [] args){Salary s = new Salary("Mohd Mohtashim", "Ambehta, UP", 3, 3600.00);Employee e = new Salary("John Adams", "Boston, MA", 2, 2400.00);System.out.println("Call mailCheck using Salary reference --");s.mailCheck();System.out.println("\n Call mailCheck using Employee reference--");e.mailCheck();}
}
以上程序编译运行结果如下:
Constructing an Employee
Constructing an Employee
Call mailCheck using Salary reference --
Within mailCheck of Salary class
Mailing check to Mohd Mohtashim with salary 3600.0Call mailCheck using Employee reference--
Within mailCheck of Salary class
Mailing check to John Adams with salary 2400.
创建抽象类和抽象方法非常有用,因为他们可以使类的抽象性明确起来,并告诉用户和编译器打算怎样使用他们.抽象类还是有用的重构器,因为它们使我们可以很容易地将公共方法沿着继承层次结构向上移动。(From:Think in java )
二、接口
(一)、接口的概念
接口,英文称作interface,在软件工程中,接口泛指供别人调用的方法或者函数。在 java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。
定一个接口的形式如下:
[public] interface InterfaceName {}
要让一个类遵循某组特地的接口需要使用 implements 关键字,具体格式如下:
class ClassName implements Interface1,Interface2,[....]{
}
1、接口与类的区别
接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。
- 接口不能用于实例化对象。
- 接口没有构造方法。
- 接口中所有的方法必须是抽象方法,Java 8 之后 接口中可以使用 default 关键字修饰的非抽象方法。
- 接口不能包含成员变量,除了 static 和 final 变量。
- 接口不是被类继承了,而是要被类实现。
- 接口支持多继承。
2、接口特性
-
接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。
-
重写接口中方法时,不能使用 default 访问权限修饰。
public interface USB{void openDevice(); //默认是public的void closeDevice(); //默认是public的
}public class Mouse implements USB{@Overridevoid openDevice(){System.out.println("打开鼠标");}
}//编译报错,重写USB中openDevice方法时,不能使用默认修饰符
//正在尝试分配更低的访问权限;以前为public
注:jdk8中:接口可以包含default方法。
- 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。
public interface USB{double brand = 3.0;//默认被final public static修饰void openDevice();void closeDevice();
}public class TestUSB{public static void main(String[] args){System.out.println(USB.brand);//可以直接通过接口名访问,说明是静态的//编译报错:Error:(12,12)java:无法为最终变量brand分配值USB.brand = 2.0; //说明brand具有final属性}
}
- 接口中不能有静态代码块和构造方法
public interface USB{//编译失败public USB(){}{} //编译失败void openDevice();void closeDevice();
}
-
接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。
-
接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是.class
-
如果类没有实现接口中的所有抽象方法,则类必须设置为抽象类。
-
阿里编码规范中约定,接口中的方法和属性不要加任何修饰符号,保持代码的简洁性。
public interface Flyable{
//接口方法public abstract void mathod1();//public abstract 是固定搭配,可以不写public void method2();abstract void method3();void method4();//注意:在接口中上述写法都是抽象方法,更推荐最后一种,代码更简洁
}
3、抽象类和接口的区别
- 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
- 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
- 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
- 一个类只能继承一个抽象类,而一个类却可以实现多个接口。
注:JDK 1.8 以后,接口里可以有静态方法和方法体了。
注:JDK 1.8 以后,接口允许包含具体实现的方法,该方法称为"默认方法",默认方法使用 default 关键字修饰。更多内容可参考 Java 8 默认方法。
注:JDK 1.9 以后,允许将方法定义为 private,使得某些复用的代码不会把方法暴露出去。更多内容可参考 Java 9 私有接口方法。
-
类继承类用extends,类继承接口用implement,接口与接口的继承用extends。
-
在普通的类继承抽象类的时候,普通类必须重写抽象类中所有抽象方法,在接口中,继承接口也必须重写接口所有的抽象方法,除非子类是抽象类。
(二)、接口间的继承
一个接口能继承另一个接口,和类之间的继承方式比较相似。接口的继承使用 extends关键字,子接口继承父接口的方法。
1、接口的单继承
下面的Sports接口被Hockey和Football接口继承:
// 文件名: Sports.java
public interface Sports
{public void setHomeTeam(String name);public void setVisitingTeam(String name);
}// 文件名: Football.java
public interface Football extends Sports
{public void homeTeamScored(int points);public void visitingTeamScored(int points);public void endOfQuarter(int quarter);
}// 文件名: Hockey.java
public interface Hockey extends Sports
{public void homeGoalScored();public void visitingGoalScored();public void endOfPeriod(int period);public void overtimePeriod(int ot);
}
Hockey接口自己声明了四个方法,从Sports接口继承了两个方法,这样,实现Hockey接口的类需要实现六个方法。
相似的,实现Football接口的类需要实现五个方法,其中两个来自于Sports接口。
2、接口的多继承
提示:IDEA 中使用 ctrl+i
快速实现接口
在Java中,类的多继承是不合法,但接口允许多继承。在接口的多继承中 extends 关键字只需要使用一次,在其后跟着继承接口。 如下所示:
interface IRunning{void run();
}interface ISwimming{void swim();
}//两栖的动物,既能跑也能游泳
interface IAmphibious extends IRunning,Swimming{}class Frog implements IAmphibious{
...
}
(三)、接口的实例
接口不能直接使用,必须需要有一个“实现类”来实现该接口,实现接口中所有的抽象方法。
public class 类名 implements 接口名称{//.....
}请实现笔记本电脑使用USB鼠标、USB键盘的例子
1.USB接口:包含打开设备,关闭设备功能
2.笔记本类:包含开机功能、关机功能、使用USB设备功能
3.鼠标类:使用USB接口,并具备点击功能
4.键盘类:使用USB接口,并具备输入功能//USB接口
public interface USB {void openDevice();void closeDevice();
}//鼠标类,实现USB接口
public class Mouse implements USB{@Overridepublic void openDevice(){System.out.println("打开鼠标");}@Overridepublic void closeDevice(){System.out.println("关闭鼠标");}public void click(){System.out.println("鼠标点击");}
}//键盘类,实现USB接口
public class KeyBoard implements USB{@Overridepublic void openDevice(){System.out.println("打开键盘");}@Overridepublic void closeDevice(){System.out.println("关闭键盘");}public void inPut(){System.out.println("键盘输入");}
}//笔记本类:使用USB设备
public class Computer {public void powerOn(){System.out.println("打开笔记本电脑");}public void powerOff(){System.out.println("关闭笔记本电脑");}public void useDevice(USB usb){usb.openDevice();if(usb instanceof Mouse){Mouse mouse = (Mouse)usb;mouse.click();}else if(usb instanceof KeyBoard){KeyBoard keyBoard = (KeyBoard) usb;keyBoard.inPut();}usb.closeDevice();}
}//测试类
public class TestUSB {public static void main(String[] args) {Computer computer = new Computer();computer.powerOn();//使用鼠标设备computer.useDevice(new Mouse());//使用键盘设备computer.useDevice(new KeyBoard());computer.powerOff();}
}
(四)、标记接口
最常用的继承接口是没有包含任何方法的接口。
标记接口是没有任何方法和属性的接口。它仅仅表明它的类属于一个特定的类型,供其他代码来测试允许做一些事情。
标记接口作用:简单形象的说就是给某个对象打个标(盖个戳),使对象拥有某个或某些特权。
例如:java.awt.event 包中的 MouseListener 接口继承的 java.util.EventListener 接口定义如下:
package java.util;
public interface EventListener
{}
没有任何方法的接口被称为标记接口。标记接口主要用于以下两种目的:
-
建立一个公共的父接口:
正如EventListener接口,这是由几十个其他接口扩展的Java API,你可以使用一个标记接口来建立一组接口的父接口。例如:当一个接口继承了EventListener 接口,Java虚拟机(JVM)就知道该接口将要被用于一个事件的代理方案。
-
向一个类添加数据类型:
这种情况是标记接口最初的目的,实现标记接口的类不需要定义任何接口方法(因为标记接口根本就没有方法),但是该类通过多态性变成一个接口类型。
三、抽象类和接口的使用
(1)当你关注一个事物的本质时,使用抽象类;当你关注一组操作的时候,使用接口。
(2)如果拥有一些方法并且想让他们中有一些默认的是实现,那么可以使用抽象类。
(3)如果想实现多重继承,那必须使用接口。由于Java不支持多继承,子类不能够继承多类,但是可以实现多个接口。
(4)如果基本功能在不断改变,那么就需要使用抽象类,如果不断改变基本功能并且使用接口,那么就需要改变所有实现了该接口的类。
参考博客:https://blog.csdn.net/SPMAX/article/details/124400507