接口
1、接口的基本定义
当你可以灵活的使用抽象类和接口进行设计的时候,那么基本上就表示你面向对象的概念理解了,这个过程是需要大量的代码来积累的,不是直接就会的。
接口的基本定义:
抽象类与普通类相比最大的优势在于:可以实现对子类重写方法的控制,但是在抽象类里面可能依然会保留一些普通方法,而普通方法里面可能会涉及到一些安全或者隐私的操作问题,那么这样在进行开发的过程之中,如果想要对外部实现全部的隐藏细节,则可以通过接口来进行描述。
接口可以理解为纯粹的抽象类(最原始的定义接口之中是只包含有抽象方法与全局变量的),但是从JDK1.8开始,由于引入了Lambda表达式的概念,所以接口的定义也得到了加强,除了抽象方法与全局变量之外,还可以定义普通方法或静态方法。如果从设计本身的角度来讲,接口之中的组成还是应该以抽象方法和全局常量为主。
在Java中接口主要使用interface关键字来进行定义
定义一个接口:
//由于类名称与接口名称的定义要求相同,所以为了区分接口名称一般会加字母I(Interface的简写)
interface IMessage{ //定义一个接口
public static final String INFO = "www.shenmenglin.xyz" ;
public abstract String getIngo() ;
}
但是现在很明显的问题出现了,此时的接口肯定无法直接产生实例化对象,所以对于接口的使用原则
- 接口需要被子类实现(),一个子类可以实现多个父接口
- 子类(如果不是抽象类)那么一定要重写接口之中的全部抽象方法
- 接口对象可以利用子类对象的向上转型进行实例化。
定义接口:
public class JavaDemo{
public static void main(String [] args){
IMessage msg = new MessageImpl() ;
System.out.println(msg.getInfo()) ;
System.out.println(IMessage.INFO) ; //输出全局变量
}
}
//由于类名称与接口名称的定义要求相同,所以为了区分接口名称一般会加字母I(Interface的简写)
interface IMessage{ //定义一个接口
public static final String INFO = "www.shenmenglin.xyz" ;
public abstract String getInfo() ;
}
class MessageImpl implements IMessage{ //实现接口
public String getInfo() {
return "得到信息,秘密的信息,有人胖了" ;
}
}
以上是接口的基本使用,但是在Java淋面之所以使用接口主要目的是一个子类可以实现多个接口,利用接口可以实现多继承的概念。
子类实现多个父接口:
public class JavaDemo{
public static void main(String [] args){
IMessage msg = new MessageImpl() ;
System.out.println(msg.getInfo()) ;
System.out.println(IMessage.INFO) ; //输出全局变量
}
}
//由于类名称与接口名称的定义要求相同,所以为了区分接口名称一般会加字母I(Interface的简写)
interface IMessage{ //定义一个接口
public static final String INFO = "www.shenmenglin.xyz" ; //全局变量
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 ;
}
}
但是这个时候就需要考虑一个世纪的情况了,关于对象转型问题
MessageImpl子类的对象可以任意实现父接口的转换
转换:
public class JavaDemo{
public static void main(String [] args){
IMessage msg = new MessageImpl() ;
IChannel chl = (IChannel) msg ;
System.out.println(msg.getInfo()) ;
}
}
由于MessageImpl子类实现了IMessage与IChannel两个接口,所以这个子类可以是这两个接口任意一个接口的实例,那么就表示此时这两个接口实例之间是可以转换的
在Java程序里面接口是不允许去继承父类的,所以接口绝对不会是Object的子类,但是根据之前的分析可以发现,MessgeImpl是Object子类,所以接口一定可以通过Object接收
Object与接口转换:
public class JavaDemo{
public static void main(String [] args){
IMessage msg = new MessageImpl() ;
Object obj = msg ;
IChannel chan = (IChannel) obj ;
System.out.println(chan.connect()) ;
}
}
Object类对象可以接收所有数据类型,包括基本数据类型、类对象、接口对象、数组
由于接口描述的是一个公共的定义标准,所以在接口之中所有的抽象方法的访问权限都为public,写与不写是一样的
完整定义:
interface IMessage{ //定义一个接口
public static final String INFO = "www.shenmenglin.xyz" ; //全局变量
public abstract String getInfo() ; //抽象方法
}
简化定义:
interface IMessage{ //定义一个接口
String INFO = "www.shenmenglin.xyz" ; //全局变量
String getInfo() ; //抽象方法
}
方法不写访问权限也是public,不是default
接口虽然已经可以成功的进行定义,但是千万不要忽略,在实际的开发过程之中,实现接口的有可能是抽象类,一个抽象类可以实现多个接口,一个普通类只能够是继承一个抽象类并且实现多个父接口,但是要求先继承,后实现
子类继承抽象类并且实现接口:
public class JavaDemo{
public static void main(String [] args){
IMessage msg = new MessageImpl() ;
Object obj = msg ;
IChannel chan = (IChannel) obj ;
System.out.println(chan.connect()) ;
}
}
//由于类名称与接口名称的定义要求相同,所以为了区分接口名称一般会加字母I(Interface的简写)
interface IMessage{ //定义一个接口
public static final String INFO = "www.shenmenglin.xyz" ; //全局变量
public abstract String getInfo() ; //抽象方法
}
interface IChannel{
public abstract boolean connect() ; //定义抽象方法
}
abstract class DatabaseAbstract{
public abstract boolean getDatabaseConnection() ;
}
class MessageImpl implements IMessage,IChannel{ //实现接口
public String getInfo() {
if(this.connect()){
return "得到数据库信息,秘密的信息,有人胖了" ;
}else{
return "数据库消息无法访问" ;
}
}
public boolean connect(){
System.out.println("消息发送通道已经成功建立") ;
return true ;
}
public boolean getDatabaseConnection(){
return true ;
}
}
虽然接口无法去继承一个父接口,但是一个接口却可以通过extends继承若干个父接口,此时称为接口的多继承
实现接口多继承:
interface IMessage{ //定义一个接口
public abstract String getInfo() ; //抽象方法
}
interface IChannel{
public boolean connect() ; //定义抽象方法
}
interface IService extends IMessage,IChannel{
public String service() ;
}
class MessageService implements IService{
public String getInfo(){
return null ;
}
public boolean connect(){
return true ;
}
public String service(){
return "获取信息服务" ;
}
}
在实际的开发之中,接口的使用往往有三种形式:
- 进行标准设置
- 表示一种操作的能力
- 暴露远程方法视图,这个一般都在RPC分布式开发中使用
2、使用接口定义标准
对于接口而言在开发之中,最为重要的应用就是进行标准的制定,实际上在日常生活之中也会听见许多接口的名字
例如:USB接口、PCI接口、鼠标接口等等,这些实际上都是属于标准的应用
以USB的程序为例,电脑上也可以插入各种USB的设备,所以电脑上人的只是USB的标准,二不关心这个标准的具体实现类
public class JavaDemo{
public static void main(String [] args){
Computer computer = new Computer() ;
computer.plugin(new Keyboard()) ; //插入键盘
computer.plugin(new Print()) ; //插入打印机
}
}
interface IUSB{ //定义USB标准
public boolean check() ; //检查通过可以工作
public void work() ;
}
class Computer{
public void plugin(IUSB usb){
if(usb.check()){
usb.work() ; //开始工作
}else{
System.out.println("硬件设备出现问题,打印机无法识别") ;
}
}
}
class Keyboard implements IUSB{
public boolean check(){
return true ;
}
public void work(){
System.out.println("开始进行打字工作") ;
}
}
class Print implements IUSB{
public boolean check(){
return false;
}
public void work(){
System.out.println("开始进行照片打印工作") ;
}
}
在现实开发之中,对于标准的概念无处不在
3、接口定义加强
接口最早的主要特点是全部由抽象方法和全局常量所组成,但是如果你的项目设计部当,就有可能出现一种严重的问题
- 可能你一个接口的子类随着时间的推移,已经出现了1080个子类,当我们再次去完善接口中的方法时,就会出现巨大的工作量——需要重写1080次添加的该方法。
一直在强调该操作是属于接口设计不当的结果,那么在最初的时候任何人都不敢保证自己的接口设计的足够完善,在这种情况下,往往不会让子类直接实现接口,而是中间追加一个过渡的抽象类
但是从JDK1.8之后开始,为了解决接口设计的缺陷,所以在接口之中允许开发者定义普通方法
普通方法定义:
public class JavaDemo{
public static void main(String [] args){
IMessage msg = new MessageImpl() ;
if(msg.connect()){
System.out.println(msg.message()) ;
}
}
}
//由于类名称与接口名称的定义要求相同,所以为了区分接口名称一般会加字母I(Interface的简写)
interface IMessage{ //定义一个接口
public String message() ; //抽象方法
public default boolean connect(){ //方法是一个公共方法,都具备
System.out.println("建立消息的发送通道") ;
return true ;
}
}
class MessageImpl implements IMessage{
public String message(){
return "www.shenmenglin.xyz" ;
}
}
接口中的普通方法必须追加default的声明,但是需要提醒的是,该操作属于挽救功能,所以不是必须的情况下,不应该是设计的首选,首选应该是添加一个过渡抽象类
除了可以追加普通方法之外,接口里面亦可以定义static方法了,而static方法就可以通过接口直接调用
在接口中定义static方法:
public class JavaDemo{
public static void main(String [] args){
IMessage msg = IMessage.getInstance() ;
System.out.println(msg.message()) ;
}
}
//由于类名称与接口名称的定义要求相同,所以为了区分接口名称一般会加字母I(Interface的简写)
interface IMessage{ //定义一个接口
public 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 "www.shenmenglin.xyz" ;
}
return "没有消息发送" ;
}
}
如果现在真的可以在接口里面定义普通方法或static方法,那么这个功能就已经可以取代抽象类了,但是不应该将这两个组成作为接口的主要涉及原则,所写的方法里面还是应该奉行:接口就是抽象方法
4、抽象类与接口的区别
在实际的开发之中可以发现抽象类和接口的定义形式是非常相似的,这一点从JDK1.8开始实际上就特别明显了,因为在JDK1.8里面接口也可以定义default或static方法了,但是这两者依然有着明显的定义区别
NO | 区别 | 抽象类 | 接口 |
---|---|---|---|
1 | 定义关键字 | abstract class 抽象类名称{} | interface 接口名称{} |
2 | 组成 | 构造、普通方法、静态方法、全局常量、普通成员、static方法 | 抽象方法、全局变量、普通方法、static方法 |
3 | 权限 | 可以使用各种权限定义 | 只能够使用public |
4 | 子类使用 | 子类通过extends关键字可以继承一个抽象类 | 子类implements关键字可以实现多个接口 |
5 | 两者关系 | 抽象类可以实现若干个接口 | 接口不允许继承抽象类,但是允许继承多个父接口 |
6 | 使用 | 1、抽象类或接口必须定义子类 2、子类一定要重写抽象类或接口中的全部抽象方法 3、通过子类的向上转型实现抽象类或接口对象实例化 | 1、抽象类或接口必须定义子类 2、子类一定要重写抽象类或接口中的全部抽象方法 3、通过子类的向上转型实现抽象类或接口对象实例化 |
当抽象类和接口都使用的情况下优先考虑接口,因为接口可以避免子类的单继承局限
另外,从一个正常的设计角度而言,我们也需要从接口来进行项目的整体设计
5、代理设计模式(Proxy)
代理设计模式的主要功能是可以帮助用户将所有的开发注意力只集中在核心业务功能上
例如:肚子饿了,思考如何吃到东西
实现代理设计:
public class JavaDemo{
public static void main(String [] args){
IEat eat = new EatProxy(new EatReal()) ;
eat.get() ;
}
}
interface IEat{
public void get() ;
}
class EatReal implements IEat{
public void get(){
System.out.println("【真实主题】得到一份食物,开始品尝美食") ;
}
}
class EatProxy implements IEat{
private IEat eat ;
public EatProxy(IEat eat){
this.eat = eat ;
}
public void get() {
this.prepare() ;
this.eat.get() ;
this.clear() ;
}
public void prepare(){
System.out.println("【代理模式】1、精心购买食材") ;
System.out.println("【代理模式】2、小心处理食材") ;
}
public void clear(){
System.out.println("【代理模式】3、收拾碗筷") ;
}
}
代理设计模式的主要特点是:一个接口提供两个子类,其中一个子类是真实业务操作类,另一外一个子类是代理业务操作类,没有代理业务操作,真实业务无法进行
6、工厂设计模式(Factory)
对于接口而言,已经可以明确的清除,必须有子类,并且子类可以通过对象的向上转型来获取接口的实例化对象。但是在进行对象实例化的过程之中也可能存在有设计问题
public class JavaDemo{
public static void main(String [] args){
IFood food = new Bread() ;
food.eat() ; //吃面包
}
}
interface IFood{ //定义一个食物标准
public void eat() ; //吃
}
class Bread implements IFood{ //定义一种吃的食物
public void eat(){
System.out.println("吃面包") ;
}
}
在本程序之中根据接口进行子类的定义,并且利用对象向上转型进行接口对象实例化的处理
客户端需要明确的知道具体的哪一类子类,如果说现在面包吃腻了,需要牛奶了,那么客户端就需要做出修改
扩展一类食物:
public class JavaDemo{
public static void main(String [] args){
IFood food_o = new Bread() ;
IFood food_t = new Milk() ;
food_o.eat() ; //吃面包
food_t.eat() ; //喝牛奶
}
}
interface IFood{ //定义一个食物标准
public void eat() ; //吃
}
class Bread implements IFood{ //定义一种吃的食物
public void eat(){
System.out.println("吃面包") ;
}
}
class Milk implements IFood{
public void eat() {
System.out.println("喝牛奶") ;
}
}
此时的程序表示了出现有耦合问题,而造成耦合最直接的元凶:关键字 new
以JVM的设计为例,Java实现可移植性的关键自安于:JVM,而JVM的核心原理:利用一个虚拟机来运行Java程序,所有的程序不与具体的操作系统有任何的关联,而是由JVM来进行匹配,所以得出结论:良好的设计应该避免耦合
工厂设计实现:
public class JavaDemo{
public static void main(String [] args){
IFood food = Factory.getInstance(args[0]) ;
food.eat() ;
}
}
interface IFood{ //定义一个食物标准
public void eat() ; //吃
}
class Bread implements IFood{ //定义一种吃的食物
public void eat(){
System.out.println("吃面包") ;
}
}
class Milk implements IFood{
public void eat() {
System.out.println("喝牛奶") ;
}
}
class Factory{
public static IFood getInstance(String className){
if("bread".equals(className)){
return new Bread() ;
}else if("milk".equals(className)){
return new Milk() ;
}else{
return null ;
}
}
}
在本程序中,客户端程序类与IFood接口的子类没有任何的关联,所有的关联都是通过Factory类完成的,而程序运行的时候可以通过初始化参数进行要使用的子类定义:
-
java JavaDemo bread
-
java JavaDemo milk
如果在日后进行子类扩充的时候只需要修改Factory程序类即可实现
interface IMessage{
public void send(String str) ;
}
class MessageImpl implements IMessage{
public void send(String str){
System.out.println("消息发送:" + str);
}
}
class Factory{
public static <Type> Type getInstance(String className){
if("messageImpl".equalsIgnoreCase(className)){
return new MessageImpl();
}
return null;
}
}
public class JavaDemo {
public static void main(String[] args) {
IMessage msg = (IMessage) Factory.getInstance("messageImpl");
}
}