在刚开始学Java到前一段时间,都没有完全理解接口的作用。 实际了解了一点接口的真正好处的时候应该是Spring的注入,因为代码上总是将实例直接注入到接口中,才真正想去了解下这是什么操作。(可能我比较菜吧)
一、为了解决抽象类的不能多重继承的问题
其实这句话就是接口最初始在我脑中需要出现的原因。 因为java中不支持多重继承,所以出现了接口(这个是不是主要原因。。自从懂得更多东西之后,我觉得是待定的)
就我自己最初的理解。为了要规范人们编码规范,抽象类是一个非常能约束,一个类必须实现什么的要求的。但是由于类不能多重继承,所以有了接口。 所以个人感觉,正常工作中,会有一个专门设计接口的人员,先把一个项目的整体架构写好了,让底层的搬砖工们去实现所有接口。(毕竟自己暂时没有在公司待过) 所以接口到这里,我觉得应该就是大多数人的理解了吧。
二、抽象类与接口
实际上,在开发过程中,很多相同的业务在implements后面写很多很多的接口名,是一件非常烦的事情吧。 举个栗子: 有如下几个接口,Color 表示动物毛色(emmm简陋点),Behaviour 表示动物行为。假设现在这有这两个接口。
public interface Color {
void paintColor();
void fade();
}
public interface Behaviour {
void eat();
void ask();
void run();
}
如果你有Cat, Dog是不是应该implments这两个接口。 当然这时候就会有童鞋说,你不会写个Animal接口? 那其实就是一个面向对象编程的基础问题了,你是希望东西拆的越细越好还是越笼统越好?因为如果只写个Animal的接口类,那么就会显得实现这个接口的类,耦合度太高了。如果某些动物只有行为,没有颜色怎么办? emmm行了,我们不纠结这个问题了。(反正我给的例子也很怪嘛。。)
那现在你要知道另一件事,java中呢,类是不能多重继承的,但是,接口是可以的。emmm长见识了嘛(可能我比较菜不知道吧。。)
public interface AnimalImpl extends Behaviour, Color {
}
这样不就能解决如果只写一个Animal接口,耦合度过高的问题了嘛。 当然对于这种情况我们也可以写一个Animal的抽象类去实现Behaviour和Color这两个接口,来让Dog和Cat不用implements那么多,当然这种操作比较建议,在那种有实现一些很重要的方法的抽象类中使用。毕竟因为,不能多重继承嘛,你继承一个抽象类就浪费了好多其他资源了。
public abstract class Animal implements Behaviour, Color {
}
三、多态的真正理解
多态最经常被认为是方法的重写和重载。 而在变量定义与实例化对象时,往往也会呈现多态。
举个例子:
public class Animal {
public void eat() {
System.out.println("动物会吃");
}
public void ask() {
System.out.println("动物会叫");
}
}
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫比较喜欢吃鱼");
}
@Override
public void ask() {
System.out.println("猫会喵喵喵");
}
}
public class Dog extends Animal {
@Override
public void ask() {
System.out.println("狗会汪汪的叫");
}
@Override
public void eat() {
System.out.println("狗喜欢吃肉");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Animal();
Animal dog = new Dog();
Animal cat = new Cat();
animal.ask();
dog.ask();
cat.eat();
}
}
/******打印情况******
动物会叫
狗会汪汪的叫
猫比较喜欢吃鱼
********************/
明显在实例化对象的时候,进行了隐式转换。三个变量定义的时候都是Animal,但是调用的时候却是各自实例化对象的方法。 emmmm。又会有人觉得,这些那么扑街的东西,除了在考试中会考到还有啥用。 别忘了这章是讲啥的。接口的多态! 实际上,如果Animal换成接口只有eat和ask方法,需要Dog和Cat实现,代码如下
public interface Animal {
default void eat() {
System.out.println("动物会吃");
}
default void ask() {
System.out.println("动物会叫");
}
}
public class Cat implements Animal {
@Override
public void eat() {
System.out.println("猫比较喜欢吃鱼");
}
@Override
public void ask() {
System.out.println("猫会喵喵喵");
}
}
public class Dog implements Animal {
@Override
public void ask() {
System.out.println("狗会汪汪的叫");
}
@Override
public void eat() {
System.out.println("狗喜欢吃肉");
}
}
public class Mouse implements Animal {
}
public class Main {
public static void main(String[] args) {
Animal dog = new Dog();
Animal cat = new Cat();
Animal mouse = new Mouse();
dog.ask();
cat.eat();
mouse.eat();
}
}
插曲一句,注意到这里的Animal接口,两个方法被default修饰了,这是1.8推出的特性,如果加上这个关键字,接口的方法可以不被实现,默认调用原本的方法。不得不说sun公司考虑得还是很周全的。
实际上好像跟刚刚没什么区别,所以接口也同样是有多态的特性的。 所以我们可以在方法的对象传输时,传入接口,以至于我们在调用的时候可以更加的泛化和方便。 例如,我们实现一个方法,他可以把吃和叫都执行:
public class Action {
private Animal animal;
public Action(Animal animal) {
this.animal = animal;
}
public Animal getAnimal() {
return animal;
}
public void setAnimal(Animal animal) {
this.animal = animal;
}
public void doSomeThing() {
animal.eat();
animal.ask();
}
}
public class Main {
public static void main(String[] args) {
Action action1 = new Action(new Dog());
action1.doSomeThing();
Action action2 = new Action(new Cat());
action2.doSomeThing();
Action action3 = new Action(new Mouse());
action3.doSomeThing();
}
}
有没发现这样子进行操作,代码耦合度就不会那么高了,当执行者需要什么样的对象进行操作的时候,传入该对象即可。emmm这种操作就是Spring框架依赖注入所考虑到的。 Java依赖注入(DI)实例详解
其实理解到上面所有东西,你就能去理解一下Spring的依赖注入与控制反转了 接口在这方面很大的解决了代码耦合度过高,调用不方便等问题。
四、反射与接口
这个呢,是我在看Spring的动态代理的时候看到的。因为感觉是真的很牛逼呀。其实就是为什么在使用Collections.sort时,一定要实现Compare方法的原因,因为多态的特性,所以当他直接执行的时候,调用的是你提供的Compare方法。
而接口加上反射是有多变态呢? 如果有了解过反射的,应该都了解过泛型吧,这两个基佬经常走在一起的,可以实现很多的通用程序。不懂反射的自己去百度下啦。
实际上,不妨先实现一个工厂,这个工厂通过传入主类的Class和接口的Class,执行他的所有的无参方法。 代码如下:
import java.lang.reflect.Method;
public class InterfaceFactory {
public static void action(Class<?> speciflicClazz, Class<?> interClazz) throws Exception {
if (!speciflicClazz.isAssignableFrom(interClazz)) {
System.out.println("没有实现该接口");
return ;
}
// 获取接口类的所有方法
Method[] interMethods = interClazz.getMethods();
// 调用该类实现该接口的方法
for (Method interMethod : interMethods) {
String name = interMethod.getName();
Method method = speciflicClazz.getMethod(name);
method.invoke(speciflicClazz);
}
}
}
这样的好处有什么呢,我们可以在拦截器上监听所有需要这么做的类,让他们直接去调用实现该接口的方法。 例如日志,每一块的日志可能并不同吧,对于某些单独业务的日志,是不是我们希望它能输出一些特殊的东西出来。emmm我提出的这个很奇怪的方法就可以做到了。。
本来所实现的东西好像并不单只那么简单的。可能是因为看静态代理和动态代理看晕了吧orz。再去好好预习预习,有所不对请各位大佬指出。