java中接口的作用

2,333 阅读6分钟

在刚开始学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。再去好好预习预习,有所不对请各位大佬指出。