我:冉妹子,你知道一个方法两种实现怎么写吗?比如说你要吃面包,而我却想吃面条,你该怎么去编写这个代码呢?
冉妹子:这还不简单吗?看我的
public class A {
public void eat() {
System.out.println("吃面包");
}
}
public class B {
public void eat() {
System.out.println("吃面条");
}
}
public class TestMain {
public static void main(String[] args) {
A a = new A();
a.eat();
B b = new B();
b.eat();
}
}
这样不就行了么?
我:嗯?那又来一个人,想吃米饭呢?
冉妹子:这有啥难得?我再创建一个类,再加一个方法不就行了。
我:那再来很多人呢?
冉妹子:屁事真多,吃空气去吧。。。
我:额。。。。。。今天说个新东西吧---接口
冉妹子:好像听说过这个东西,那你讲讲吧!
我:创建一个通用的接口十分有用,可以让不同的子类用不同的方式去实现这个接口。
- 基本语法
接口和类的写法类似,只需要将 class 换成 interface就行了。像这样:
public interface People {
}
当然,在了解接口之前,你需要知道什么是抽象类和抽象方法;包含抽象方法的类叫做抽象类。
一、抽象类(抽象方法)
写法是这样的:
public abstract class People {
}
抽象方法的写法是这样的:
public abstract void eat();
注意,这个方法和普通方法的区别在哪?
冉妹子:没有大阔号!是分号结尾
我:对的,没大括号,这个没有大括号,只有分号的,而且用了abstract修饰的方法,我们成为抽象方法。这种方法是没有具体的方法实现的。
冉妹子:没有具体的实现,要这个方法有什么用呢?
我:你想想继承,我们可以用一个类来继承他吗?
public class Student extends People{
@Override
public void eat() {
System.out.println("吃面包");
}
}
你看,就像这个样子(@Override是一个注解,表示重写父类的方法,如果和父类的方法表述有改变,则编译器会报错),当来了一个新人,只需要去增加一个People的子类,去实现这个抽象方法即可。
冉妹子:。。。额,这么做,不是更麻烦,还多了一个父类,类不更多吗?还没我写的简单呢吧!
我:别慌呀,我们再增加一个子类,来看:
public class Student1 extends People{
@Override
public void eat() {
System.out.println("吃面条");
}
}
这样就有了两个子类,如果用原始的方法来创建这两个子类时,你就需要具体的考虑类的具体类型,并且去知道类中的细节,使用继承抽象类,就没这样的烦恼了,你只需要知道抽象类就行了
public class PeopleMain {
public static void creatPeople(People p) {
p.eat();
}
public static void main(String[] args) {
creatPeople(new Student());
creatPeople(new Student1());
}
}
像这样设计,你只需要在方法中 new 一个子类,就行了,这种实现依赖于向上转型,子类可以自动转换为父类,而p.eat(),这个方法的调用,编译器之所以能清楚的知道调用的哪个类中的方法,依赖于多态这个特性
这么做,是不是就简单了呢?不用再new一个对象,再调用一次方法了吧!
冉妹子:好像是这么回事,如果有成百上千个类,我就要调用成百上千次的方法,那还不得难受死我!
我:这也是抽象的好处,实际上就是让你不去在意方法的实现细节;比如,我说:你快给我洗脚。我不用在乎你去哪里找的盆子,去哪里接的水,只需要发出命令即可!
冉妹子:还没睡醒,搁着做白日梦呢?
我:额。。。再说两个规则:
- 抽象类中可以有普通的方法(不带abstract修饰的方法,并含有方法具体的实现)
- 普通类中不能含有抽象方法(只要是有抽象方法,那么这个类也一定要是抽象的)
抽象类大致了解了一下,我们继续说接口。
二、接口
接口使得抽象的概念更向前,抽象类中你可以写个普通方法,可以有具体的实现,但是在接口中没有提供任何形式的具体实现(jdk1.8以后,接口中可以有方法体了),即接口提供了形式,未提供行为。
像上面我们定义的那样,接口的方法定义与之相似:
public interface People {
void eat();
}
接口的方法中不必要给出权限修饰符,默认就是public,这和类的default有所不同,而 abstract 修饰符也是接口方法中默认拥有的
实现接口的基本语法:
public class Student implements People{
@Override
public void eat() {
System.out.println("吃面包");
}
}
只需要把 extends 关键字换成 implements 即可。
冉妹子:这么说,抽象类可以代替接口了?我在抽象类中不写具体的方法实现,不也是相当于一个接口吗?
我:额。。。。类可以多继承吗?
冉妹子:不行。。。啊?难道接口可以?
我:嗯,嗯 可以多继承。
演示一下,再来一个接口
public interface Man {
void drink();
}
public interface A extends People,Man{
}
像这样,同事继承了People 和Man 两个接口;同时,接口也可以多实现,像这样:
public class Student implements People,Man{
@Override
public void eat() {
System.out.println("吃面包");
}
@Override
public void drink() {
System.out.println("喝水");
}
}
多实现时,就像这样写就行了。所以在一些特殊场景,还是要选择正确的抽象方式才行。不能盲目崇拜抽象类或接口。
冉妹子:好像懂了,接口的优点也是只用给外部提供一个方法,不让用户看到方法中的具体实现细节,原来你先说抽象类是因为这呀!
我:嗯~孺子可教也。理解的还挺快的。你可以使用接口,来编写复用性更好的代码
冉妹子:戚,小垃圾,类中可以有各种字段,比如基本数据类型的属性等等,接口中有吗?
我:当然也有了,不过跟类中的有所不同
- 接口中的域
接口中的所有域,自动都是 static 和 final 的(即常量),所以接口就能很便捷的来创建常量,当然,想要创建常量,枚举是一种不错的选择。
注意:常量的命名一般都为大写字母,单词与单词之间使用下划线 " _ " 隔开。
和类中的static修饰的变量没区别---只会在第一次加载时初始化一次。
- 接口嵌套
接口还有一个很有趣的特性,他可以嵌套,比如:
public interface People {
public interface People1 {
void learn();
}
void eat();
}
而你要去实现接口 People1时,只需要这么写就行了
public class Student implements People.People1{
@Override
public void learn() {
}
}
冉妹子:额,不行了不行了,再听下去,脑袋就要爆炸了。
我:额,那就到此为止吧。来吧,给我洗个脚!
冉妹子:年轻人,我劝你耗子尾汁。
三、总结:
接口和抽象类虽然是很理想的选择,但是并不是无时无刻都要使用这种方式来创建类,这是一种错误的想法,任何的抽象,都应该有具体的需求才是最好的,盲目的抽象,只会加重程序的负担,必要时,你应该重构接口,而不是随时的去添加额外的间接性。合理利用接口和抽象,才能让程序变得更加优秀。
如有错误,欢迎指出,一定及时改正!