接口的基本定义
- 抽象类与普通类相比最大的优势在于:可以实现对子类覆写方法的控制,但是在抽象类里面可能依然会保留有一些普通方法,而普通方法里面可能会涉及到一些安全或者隐私方面的操作问题,那么在进行开发过程中,如果想要对外部隐藏全部的实现细节,则可以通过接口来进行描述;
- 接口可以理解为一个纯粹的抽象类(最原始的定义接口之中只包含有抽象方法和全局常量),但是从JDK1.8开始由于引入了Lambda表达式的概念,所以接口的定义也得到了加强,除了抽象方法和全局常量之外,还可以定义普通方法或静态方法,如果从涉及本身的角度来讲,接口之中的组成还是应该以抽象方法和全局常量为主;
- 在Java中接口主要使用interface关键字来进行定义;
//定义一个接口
//由于类名称与接口名称定义相同,所以为了区分出接口,接口名称前往往会 加入字母I
interface IMessage { //定义一个接口
public static final String INFO = "hello"; //全局常量
public abstract void getInfo(); //抽象方法
}
- 但是现在很明显的问题出现了,此时的接口肯定无法直接产生实例化对象,所以对于接口的使用原则如下:
- 接口需要被子类实现(implements),一个子类可以实现多个父接口;
- 子类(如果不是抽象类)那么一定要覆写接口之中的全部抽象方法;
- 接口对象可以利用子类对象的向上转型进行实例化;
//定义一个接口
interface IMessage {
public static final String INFO = "hello";
public abstract String getInfo();
}
class MessageImpl implements IMessage { //实现了接口
public String getInfo() {
return "这是一个接口";
}
}
public class JavaDemo {
public static void main(String [] args) {
IMessage msg = new MessageImpl();
System.out.println(msg.getInfo());
System.out.println(IMessage.INFO); //直接输出全局常量
}
}
- 以上是接口的基本使用,但是在Java里面之所以使用接口主要目的是一个子类实现多个接口,利用接口可以实现多继承的概念;
//子类实现多个父接口
interface IMessage {
public static final String INFO = "hello";
public abstract String getInfo(); //抽象方法
}
interface IChannel {
public abstract boolean connect(); //定义抽象方法
}
class MessageImpl implements IMessage,IChannel { //实现了接口
public String getInfo() {
if(this.connect()) {
return "这是一个接口,通道建立成功";
}
return "错误的接口,通道建立失败";
}
public boolean connect() {
System.out.println("消息通道已经建立");
return true;
}
}
public class JavaDemo {
public static void main(String [] args) {
IMessage msg = new MessageImpl();
System.out.println(msg.getInfo());
}
}
-
但是这个时候就需要考虑一个实际的情况了:关于对象的转型问题;
此图来源于李兴华老师 - 此时MessageImpl子类的对象可以任意实现父接口的转换
//观察转换
public class JavaDemo {
public static void main(String [] args) {
IMessage msg = new MessageImpl();
IChannel chl = (IChannel) msg;
System.out.println(chl.connect());
}
}
- 由于MessageImpl子类实现了IMessage和IChnnel两个接口,所以这个子类可以是这两个接口任意一个接口的实例,那么就表示此时这两个接口实例之间是可以转换的
- 在Java程序里面接口是不允许去继承父类的,所以接口绝对不会是Object的子类,但是根据之前的分析可以发现,MessageImpl是Object的子类,所以接口一定可以通过Object接收
//观察Object与接口转换
IMessage msg = new MessageImpl();
Object obj = msg;
IChannel chl = (IChannel) obj;
System.out.println(chl.connect());
- Object类对象可以接收所有数据类型,包括基本数据类型、类对象、接口对象、数组;
- 由于接口描述的是一个公共的定义标准,所以在接口之中所有的抽象方法的访问权限都为public,也就是说写与不写都是一样的,例如下面两个接口本质是完全相同的:
interface IMessage { //定义一个接口
public static final String INFO = "hello";
public abstract String getInfo();
}
/**
是完全相同的
*/
interface IMessage { //定义一个接口
String INFO = "hello";
String getInfo();
}
- 方法不写访问权限也是public,不是default,所以覆写的时候只能够使用public
- 接口虽然已经可以成功的定义,但是千万不要忽略,在实际的开发过程之中,实现接口的有可能是抽象类,一个抽象类可以实现多个接口,而一个普通类只能继承一个抽象类并且可以实现多个父接口,但是要求先继承后实现;
//子类继承抽象类并且实现接口
//由于类名称与接口名称定义相同,所以为了区分出接口,接口名称前往往会加入字母I
interface IMessage { //定义一个接口
public static final String INFO = "hello"; //全局常量
public abstract String getInfo(); //抽象方法
}
interface IChannel {
public abstract boolean connect(); //定义抽象方法
}
abstract class DatabaseAbstract { //定义一个抽象类
//abstract只有接口中的abstract可以省略,抽象类中不可省略
public abstract boolean getDatabaseConnect();
}
class MessageImpl extends DatabaseAbstract implements IMessage,IChannel {
public String getInfo() {
if(this.connect()) {
if(this.getDatabaseConnect()) {
System.out.println("继承抽象类完成接口");
}
return "这是一个接口,通道建立成功";
}
return "错误的接口,通道建立失败";
}
public boolean connect() {
System.out.println("消息通道已经建立");
return true;
}
public boolean getDatabaseConnect() {
return true;
}
}
public class JavaDemo {
public static void main(String [] args) {
IMessage msg = new MessageImpl();
System.out.println(msg.getInfo());
}
}
- 虽然一个接口无法继承一个父类,但是一个接口却可以通过extends继承若干个父接口,此时成为接口的多继承
//实现接口多继承
interface IMessage {
public abstract String getInfo();
}
interface IChannel {
public abstract boolean connect();
}
//extends在类继承上只能继承一个,接口上可以是多个
interface IService extends IMessage, IChannel { //接口多继承
public abstract String service();
}
class MessageServiceImpl implements IService {
public String getInfo() {
return "获取信息";
}
public boolean connect() {
return true;
}
public String service() {
return "获取消息服务";
}
}
public class JavaDemo {
public static void main(String [] args) {
IService msg = new MessageServiceImpl();
System.out.println(msg.service());
System.out.println(msg.getInfo());
}
}
- 在实际开发之中,接口的使用往往有三种形式:
- 进行标准设置屁;
- 表示一种操作的能力;
- 暴露远程方法视图,这个一般都在RPC分布式开发中使用;
接口定义加强
-
接口最早的主要特点是全部由抽象方法和全局常量所组成,但是如果你的项目设计不当,就有可能出现一种非常严重的问题;
此图来源于李兴华老师 -
一直在强调该操作是属于结构设计不当的结果,那么在最初的时候,任何人都不能保证你的接口设计的足够完善,所以在这样的情况下,为了方便子类的修改,往往不会让子类直接实现接口,而是中间追加一个过度的抽象类;
此图来源于李兴华老师 - 但是从JDK1.8之后开始,为了解决接口设计的缺陷,所以在接口之中允许开发者定义普通方法;
//观察普通方法定义
interface IMessage {
public abstract String message();
public default boolean connect() {
System.out.println("普通的公共方法");
return true;
}
}
class MessageImpl implements IMessage {
public String message() {
return "hello";
}
}
public class JavaDemo {
public static void main(String [] args) {
IMessage msg = new MessageImpl();
System.out.println(msg.message());
System.out.println(msg.connect());
}
}
- 接口中的普通方法必须追加default声明,但是需要提醒的是该操作属于挽救功能,所以如果不是必须的情况下,不应该作为你设计的首选;
- 除了可以追加普通方法之外,接口里也可以定义static方法了,而static方法可以通过接口直接调用;
//在接口中定义static方法
interface IMessage {
public abstract String message();
public default boolean connect() {
System.out.println("普通的公共方法");
return true;
}
public static IMessage getInstance() {
return new MessageImpl(); //获取子类对象
}
}
class MessageImpl implements IMessage {
public String message() {
if(this.connect()) {
return "hello";
}
return "hi";
}
}
public class JavaDemo {
public static void main(String [] args) {
IMessage msg = IMessage.getInstance();
System.out.println(msg.message());
}
}
- 如果现在真的可以接口里面定义普通方法或者static方法,那么这个功能已经可以取代抽象类了,但是不应该将这两个组成作为接口的主要设计原则,应该奉行:接口就是抽象方法;
使用接口定义标准
对于接口来言,开发之中最为重要的应用就是进行标准的制定;
interface IUSB {
public abstract boolean check();
public abstract void work();
}
class Computer {
public void plugin(IUSB usb) {
if(usb.check()) {
usb.work();
} else {
System.out.println("硬件连接不成功");
}
}
}
class KeyboardImpl implements IUSB {
public boolean check() {
return true;
}
public void work() {
System.out.println("进行打字");
}
}
class PrintImpl implements IUSB {
public boolean check() {
return false; //检测失败
}
public void work() {
System.out.println("进行打印");
}
}
public class JavaDemo {
public static void main(String [] args) {
Computer computer = new Computer();
computer.plugin(new KeyboardImpl()); //插入键盘
computer.plugin(new PrintImpl()); //插入打印机
}
}
工厂设计模式(Factory)
对于接口来言,已经可以明确的清楚,必须有子类,并且子类可以通过对象的向上转型来获取接口的实例化对象,但是在进行对象实例化的过程中,可能存在有我们的设计问题
interface IFood { //定义一个食物
public abstract void eat();
}
class BreadImpl implements IFood { //定义一种食物
public void eat() {
System.out.println("吃面包");
}
}
public class JavaDemo {
public static void main(String [] args) {
IFood food = new BreadImpl();
food.eat();
}
}
-
在本程序之中根据接口进行子类定义,并且利用对象的向上转型进行接口对象的实例化处理,而此时程序的结构如下:
此图来源于李兴华老师 - 客户端需要明确的知道具体的哪一个子类,如果面包吃腻了,需要牛奶了,那么客户端就要做出修改
interface IFood { //定义一个食物
public abstract void eat();
}
class BreadImpl implements IFood { //定义一种食物
public void eat() {
System.out.println("吃面包");
}
}
class MilkImpl implements IFood { //定义一种食物
public void eat() {
System.out.println("喝牛奶");
}
}
public class JavaDemo {
public static void main(String [] args) {
IFood food = new MilkImpl();
food.eat();
}
}
- 所以此时的程序就表示出现有耦合的问题,而造成耦合最直接的元凶:“关键字new”,以JVM的设计为例,Java实现可移植性的关键在于JVM,而JVM的核心原理:利用虚拟机来运行Java,所有的程序并不与具体的操作系统有任何的关联,而是由JVM来进行匹配,所以良好的设计应该避免耦合;
//工厂设计实现
interface IFood { //定义一个食物
public abstract void eat();
}
class BreadImpl implements IFood { //定义一种食物
public void eat() {
System.out.println("吃面包");
}
}
class MilkImpl implements IFood { //定义一种食物
public void eat() {
System.out.println("喝牛奶");
}
}
class FoodFactory {
public static IFood getInstance(String className) {
if("BreadImpl".equals(className)) {
return new BreadImpl();
} else if("MilkImpl".equals(className)) {
return new MilkImpl();
} else {
return null;
}
}
}
public class JavaDemo {
public static void main(String [] args) {
IFood food = FoodFactory.getInstance(args[0]);
food.eat();
}
}
-
在本程序之中,客户端程序类与IFood接口的子类没有任何的关联,所有的关联都是通过Factory类完成的,而在程序运行的时候可以通过初始化参数进行要使用的子类定义;
此图来源于李兴华老师
代理设计模式(Proxy)
- 代理设计模式的主要功能是可以帮助用户将所有的开发注意力只集中在核心业务功能的处理上;
//实现代理设计
interface IEat {
public abstract void get();
}
class EatRealImpl implements IEat {
public void get() {
System.out.println("得到一份食物");
}
}
class EatProxyImpl implements IEat { //服务代理
private IEat eat; //为吃东西服务
public EatProxyImpl(IEat eat) { //一定要有一个代理项
this.eat = eat;
}
public void prepare() { //准备过程
System.out.println("购买食材、加工食品");
}
public void clear() {
System.out.println("收拾食物垃圾");
}
public void get() {
this.prepare();
this.eat.get();
this.clear();
}
}
public class JavaDemo {
public static void main(String [] args) {
IEat eat = new EatProxyImpl(new EatRealImpl());
eat.get();
}
}
- 代理模式的主要特点是:一个接口提供有两个子类,其中一个子类是真实业务操作类,另一个主题是代理操作业务类,没有代理业务操作,真实业务无法进行;
抽象类与接口区别
- 在实际开发之中可以发现抽象类和接口的定义形式是非常相似的,这点从JDK1.8开始就特别明显了,因为在JDK1.8里面接口也可以定义default和static方法类,但是这两者依然是有着明显的定义区别与使用区别的;
No. | 区别 | 抽象类 | 接口 |
---|---|---|---|
1 | 定义 | abstract class 抽象类名称 {} | interface 接口名称 {} |
2 | 组成 | 构造方法、普通方法、静态方法、全局常量、成员 | 抽象方法、全局常量、普通方法、static方法 |
3 | 权限 | 可以使用各种权限定义 | 只能够使用public |
4 | 子类使用 | 子类通过extends关键字可以继承一个抽象类 | 子类使用implements关键字可以实现多个接口 |
5 | 两者关系 | 抽象类可以实现若干接口 | 接口不允许继承抽象类,但是允许继承多个父接口 |
6 | 使用 | 抽象类或接口必须定义子类、子类一定要覆写抽象类或接口中的全部抽象方法、通过子类的向上转型实现抽象类或接口的对象实例化 | 抽象类或接口必须定义子类、子类一定要覆写抽象类或接口中的全部抽象方法、通过子类的向上转型实现抽象类或接口的对象实例化 |
- 当抽象类和接口都可以使用的情况下优先要考虑接口,因为接口可以避免子类的单继承局限;
-
另外从设计角度来言,也需要先从接口来进行项目的整体设计
此图来源于李兴华老师
网友评论