简介
接口和内部类为我们提供了一种将接口与实现分离的更加结构化的方式
抽象类和抽象方法
在学习到抽象类之前,我们实现多态的方式就是通过实现一个基类,然后在不同的导出类中重写基类的方法以及进行一些个性的拓展来实现多态。在这种关系中,基类的目的是为它的所有导出类创建一个通用接口。纯粹作为接口而言,普通的类显得有些多余。因为它会要求基类同样实现接口方法。但实际上对于这些方法的实现有时候并不是必须的。因此我们引入了抽象类和抽象方法的概念
这种方法是不完整的,仅有声明而没有方法体。下面是抽象方法声明采用的语法
包含抽象方法的类被称为抽象类。如果一个类包含一个或多个抽象方法,该类必须被限定未抽象的,否则编译错误。同时由于抽象类是不完整的,所以我们无法通过抽象类来创建实例。
如果从一个抽象类继承,并想创建该新类的对象,那么就必须为抽象类中所有抽象方法提供方法定义。如果不这么做,那该类依旧是抽象类,需要用abstract关键字来限定
}
@Override
void f2() {
System.out.println('f2');
}
}
接口
inteface关键字使得抽象的概念更近异步,abstract关键字允许我们在一个类中创建一个或多个没有实现的方法声明,然后由继承者实现。而interface关键字则产生了一个完全抽象的类,它只提供了形式而不具有任何具体实现。在java8后,接口中允许加入默认的方法定义。由于我们知道java是单继承体系,而C++则是允许多继承,相较于单继承,多继承可以为新类整合多种不同的接口,但是由于如果在java中加入多继承则会带来一些定义不清淅的问题,假设我们继承了A和B两类,但是两个类中存在相同的域,那么我们到底继承谁的。所以Java采用了继承+接口来实现多重继承。
接口也可以包含域,但这些域隐式地是static和final的。接口中的方法自动就是public,当然我们可以显式地将方法声明为public
完全解耦
只要有一个方法操作地是类而不是接口,那么你就只能使用这个类及其子类。如果你想将该方法应用于不在此继承结构中的某个类,那你就要触霉头了。接口在很大程度上放宽了这种限制。因此接口使得我们编写出复用性更好的代码
解释一下:假设我们为了多态以及代码的复用,希望新类都可以实现某个相同的方法。我们在学习接口之前通常会使用类继承来解决,但是继承给我们解决问题的同时也带来了很多的麻烦。比如:我现在希望导出类实现一个f()方法,但是基类中又不止一个f()方法,还有f1(),f2()等等,这些方法很可能是与我新类本身定位无关的方法,我也必须要继承下来。这是不合理的,也是违反单一职责原则的。由于java实现的是单继承结构,如果我们还希望继承一些其他的方法,那我必须要求基类增加这些方法,而基类一旦增加了这些方法,意味着该基类的其他导出类都会继承这些它们并不一定用的到的方法。这就是个恶性循环。
上面的解释也是有点抽象,因此我们举个例子,我们希望创建一只鸟和一只猫。因此我们会创建一个Bird类和Cat类,但是处于代码复用和多态的考虑我们会去抽取它们的共性来作为基类,所以它们的共性是它们都是动物,都会喝水,都会吃东西。好,按照这个思路我们开始使用我们刚学到的抽象类来写代码
classBirdextendsAnimal{publicvoideat(){Systeout.println(“鸟吃虫子”);}
public void drink(){
System.out.println('鸟喝水');
}
}
classCatextendsAnimal{publicvoideat(){Systeout.println(“猫吃鱼”);}
public void drink(){
System.out.println('猫喝水');
}
}
这样乍一看很完美了,好像没什么问题。但是仔细一想我们就会发现,猫除了喝水吃饭,还会跳跃,会抓老鼠。而鸟还会飞行。因此为了鸟能够非我们需要加上飞行这个行为。按照以往的想法我们可以这么干
classBirdextendsAnimal{publicvoideat(){Systeout.println(“鸟吃虫子”);}
public void drink(){
System.out.println('鸟喝水');
}
public void fly(){
System.out.println('飞行');
}
}
但是注意,我们实现多态的原因就是为了代码的可复用性以及抽象性。像在导出类直接加一个方法这也的行为不是不可以,但是会失去多态和复用性这些特性,以后,如果我要编写另一个会飞的生物例如蝙蝠、钢铁侠。那岂不是要在每一个新类中都重新增加一个飞行方法,由于失去了多态性,我以后希望调用飞行方法时,都需要指定具体的导出类作为参数。所以我们重新用接口来做
classBirdextendsAnimalimplementsFly{publicvoideat(){Systeout.println(“鸟吃虫子”);}
public void drink(){
System.out.println('鸟喝水');
}
public void fly(){
System.out.println('鸟振翅飞行');
}
}
classIronManimplementsFly{publicvoidfly(){Systeout.println(“钢铁侠喷射飞行”);}}
publicclassTest{publicstaticvoidmain(){Test.fly(newBird());Test.fly(newIronMan());/**Output*鸟振翅飞行*钢铁侠喷射飞行**/}
}
这样以来,这个鸟类又重新具有了多态性,当我需要在方法内调用飞行方法时,完全可以指定参数类型为Fly,这样我不管你传入的是鸟还是钢铁侠,我只要调用Fly接口的fly方法就能够调用对应实际传入类型的对象的飞行方法
Java中的多重继承
接口不仅是一种更加纯粹的抽象类,它的目标更高。因为接口是没有任何具体实现的。也就是说,没有任何与接口相关的存储。因此也就无法阻止多个接口的组合。这一点是有价值的,因为我们有时需要标识“一个x是一个a和一个b以及一个e。在C+中组合多个类的接口的行为被称之为多重继承。它会使得你的新类背负的太多,因为每个类都会都具体实现,而在Java中,你可以执行相同的行为,但只有一个类可以有具体实现。
在导出类中,不强制要求你必须有一个具体或抽象的基类,但是如果从一个非接口的类继承,那么只能从一个类中去继承,其余元素必须是接口,然后使用implements关键字实现,不同的接口使用逗号隔开
组合接口时发生名字冲突
实现多接口继承时会发生一个小陷阱,那就是接口中存在相同方法,同时基类中也有相同的方法。同名方法我们可以认为归一当作一个用就可以了,但是如果它们的返回类型又不同呢,那我们该如果抉择。其实我们不用决战,因为这种会产生混淆的问题编译器根本不能通过。如果基类和接口中的方法同名同参,那么我们就会认为接口中的方法已被实现不需要再次在导出类中实现。
接口中的域
接口中所有的域都默认是staticfinal的。同时由于接口中不存在方法的具体实现,也没有构造方法,所以无法去对域进行任何操作,编译器不允许接口中出现空final的存在。我们前面提到,接口中不存在任何存储,这些域由于是静态的,它们不属于接口的一部分,它们被存储在该接口的静态存储区内
补充
接口和类中允许嵌套接口。但是这种方式比较少见,所以我们也只是提一下
接口和工厂模式
接口经常穿插于各种设计模式之间,因此我们现在介绍其中一种最常见之一的工厂模式,它与接口之间的关系非常之紧密
classServiceImplement1implementsService{publicvoidf1(){Systrout.println(“ServiceImplementf1”);}}
classServiceFactory1implementsServiceFactory{publicServicegetService(){returnnewServiceImplement1()}}
classServiceImplement2implementsService{publicvoidf1(){Systrout.println(“ServiceImplementf1”);}}
classServiceFactory2implementsServiceFactory{publicServicegetService(){returnnewServiceImplement2()}}上面就是工厂模式最简单的体现,这里不做赘述,有需要的小伙伴可以去我的博客看一下,有一章专门对工厂模式进行了分析解释
总结
我们可以看到很多接口的妙用,甚至一度认为接口相对于基类它更灵活更好用,所以要优先使用接口而非类。这种认知是存在误区的,因为我们是这么认为的,基类决定了你是什么,而接口决定了你拥有什么样的行为,属于对基类的补充而非替代。过度使用接口只会为程度带来很大程度的复杂性以及降低代码的复用性。由于接口不可以存储普通的域,所以在数据方面,接口存在很大的短板。我们设计时可以这么去做,比如我们要设计一个人,那么首先我们去定义其作为人类的基类,该基类记录了一些人本身的特性,比如身份证,性别,年龄等。这是伴随人类本身存在的,不会因为不同的人类的不同实现产生歧义或者变化,变化的只是数据本身。但是人和人是不同的,比如由于工种的不同,不同的人具有不同的技能,这些技能就需要通过接口来进行补充了。所以不要因为接口灵活就去滥用了。
文章为作者独立观点,不代表 股票程序化软件自动交易接口观点