本系列文章经补充和完善,已修订整理成书《Java编程的逻辑》(马俊昌著),由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买:京东自营连接 html
![]()
以前咱们一直在说,程序主要就是数据以及对数据的操做,而为了方便操做数据,高级语言引入了数据类型的概念,Java定义了八种基本数据类型,而类至关因而自定义数据类型,经过类的组合和继承能够表示和操做各类事物或者说对象。java
但,这种只是将对象看作属于某种数据类型,并按该类型进行操做,在一些状况下,并不能反映对象以及对对象操做的本质。编程
为何这么说呢?不少时候,咱们实际上关心的,并非对象的类型,而是对象的能力,只要能提供这个能力,类型并不重要。咱们来看一些生活中的例子。数组
要拍个照片,不少时候,只要能拍出符合需求的照片就行,至因而用手机拍,仍是用Pad拍,或者是用单反相机拍,并不重要,关心的是对象是否有拍出照片的能力,而并不关心对象究竟是什么类型,手机、Pad或单反相机均可以。微信
要计算一组数字,只要能计算出正确结果便可,至因而由人心算,用算盘算,用计算器算,用电脑软件算,并不重要,关心的是对象是否有计算的能力,而并不关心对象究竟是算盘仍是计算器。ide
要将冷水加热,只要能获得热水便可,至因而用电磁炉加热,用燃气灶加热,仍是用电热水壶,并不重要,重要的是对象是否有加热水的能力,而并不关心对象究竟是什么类型。工具
在这些状况中,类型并不重要,重要的是能力。那如何表示能力呢?this
Java使用接口这个概念来表示能力。spa
接口这个概念在生活中并不陌生,电子世界中一个常见的接口就是USB接口。电脑每每有多个USB接口,能够插各类USB设备,能够是键盘、鼠标、U盘、摄像头、手机等等。3d
接口声明了一组能力,但它本身并无实现这个能力,它只是一个约定,它涉及交互两方对象,一方须要实现这个接口,另外一方使用这个接口,但双方对象并不直接互相依赖,它们只是经过接口间接交互。图示以下:
拿上面的USB接口来讲,USB协议约定了USB设备须要实现的能力,每一个USB设备都须要实现这些能力,电脑使用USB协议与USB设备交互,电脑和USB设备互不依赖,但能够经过USB接口相互交互。
下面咱们来看Java中的接口。
咱们经过一个例子来讲明Java中接口的概念。
这个例子是"比较",不少对象均可以比较,对于求最大值、求最小值、排序的程序而言,它们其实并不关心对象的类型是什么,只要对象能够比较就能够了,或者说,它们关心的是对象有没有可比较的能力。Java API中提供了Comparable接口,以表示可比较的能力,但它使用了泛型,而咱们尚未介绍泛型,因此本节,咱们本身定义一个Comparable接口,叫MyComparable。
如今,首先,咱们来定义这个接口,代码以下:
public interface MyComparable {
int compareTo(Object other);
}
复制代码
解释一下:
再来解释一下compareTo方法:
接口与类不一样,它的方法没有实现代码。定义一个接口自己并无作什么,也没有太大的用处,它还须要至少两个参与者,一个须要实现接口,另外一个使用接口,咱们先来实现接口。
类能够实现接口,表示类的对象具备接口所表示的能力。咱们来看一个例子,之前面介绍过的Point类来讲明,咱们让Point具有能够比较的能力,Point之间怎么比较呢?咱们假设按照与原点的距离进行比较,下面是Point类的代码:
public class Point implements MyComparable {
private int x;
private int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public double distance(){
return Math.sqrt(x*x+y*y);
}
@Override
public int compareTo(Object other) {
if(!(other instanceof Point)){
throw new IllegalArgumentException();
}
Point otherPoint = (Point)other;
double delta = distance() - otherPoint.distance();
if(delta<0){
return -1;
}else if(delta>0){
return 1;
}else{
return 0;
}
}
@Override
public String toString() {
return "("+x+","+y+")";
}
}
复制代码
咱们解释一下:
咱们再来解释一下Point的compareTo实现:
一个类能够实现多个接口,代表类的对象具有多种能力,各个接口之间以逗号分隔,语法以下所示:
public class Test implements Interface1, Interface2 {
....
}
复制代码
定义和实现了接口,接下来咱们来看怎么使用接口。
与类不一样,接口不能new,不能直接建立一个接口对象,对象只能经过类来建立。但能够声明接口类型的变量,引用实现了接口的类对象。好比说,能够这样:
MyComparable p1 = new Point(2,3);
MyComparable p2 = new Point(1,2);
System.out.println(p1.compareTo(p2));
复制代码
p1和p2是MyComparable类型的变量,但引用了Point类型的对象,之因此能赋值是由于Point实现了MyComparable接口。若是一个类型实现了多个接口,那这种类型的对象就能够被赋值给任一接口类型的变量。
p1和p2能够调用MyComparable接口的方法,也只能调用MyComparable接口的方法,实际执行时,执行的是具体实现类的代码。
为何Point类型的对象非要赋值给MyComparable类型的变量呢?在以上代码中,确实不必。但在一些程序中,代码并不知道具体的类型,这才是接口发挥威力的地方,咱们来看下面使用MyComparable接口的例子。
public class CompUtil {
public static Object max(MyComparable[] objs){
if(objs==null||objs.length==0){
return null;
}
MyComparable max = objs[0];
for(int i=1;i<objs.length;i++){
if(max.compareTo(objs[i])<0){
max = objs[i];
}
}
return max;
}
public static void sort(Comparable[] objs){
for(int i=0;i<objs.length;i++){
int min = i;
for(int j=i+1;j<objs.length;j++){
if(objs[j].compareTo(objs[min])<0){
min = j;
}
}
if(min!=i){
Comparable temp = objs[i];
objs[i] = objs[min];
objs[min] = temp;
}
}
}
}
复制代码
类CompUtil提供了两个方法,max获取传入数组中的最大值,sort对数组升序排序,参数都是MyComparable类型的数组。sort使用的是简单选择排序。
能够看出,这个类是针对MyComparable接口编程,它并不知道具体的类型是什么,也并不关心,但却能够对任意实现了MyComparable接口的类型进行操做。咱们来看下对Point类型进行操做,代码以下:
Point[] points = new Point[]{
new Point(2,3),
new Point(3,4),
new Point(1,2)
};
System.out.println("max: " + CompUtil.max(points));
CompUtil.sort(points);
System.out.println("sort: "+ Arrays.toString(points));
复制代码
建立了一个Point类型的数组points,而后使用CompUtil的max方法获取最大值,使用sort排序,并输出结果,输出以下:
max: (3,4)
sort: [(1,2), (2,3), (3,4)]
复制代码
这里演示的是对Point数组操做,实际上能够针对任何实现了MyComparable接口的类型数组进行操做。
这就是接口的威力,能够说,针对接口而非具体类型进行编程,是计算机程序的一种重要思惟方式。针对接口,不少时候反映了对象以及对对象操做的本质。它的优势有不少,首先是代码复用,同一套代码能够处理多种不一样类型的对象,只要这些对象都有相同的能力,如CompUtil。
更重要的是下降了耦合,提升了灵活性,使用接口的代码依赖的是接口自己,而非实现接口的具体类型,程序能够根据状况替换接口的实现,而不影响接口使用者。解决复杂问题的关键是分而治之,分解为小问题,但小问题之间不可能一点关系没有,分解的核心就是要下降耦合,提升灵活性,接口为恰当分解,提供了有力的工具。
上面咱们介绍了接口的基本内容,接口还有一些细节,包括:
咱们逐个来介绍下。
接口中能够定义变量,语法以下所示:
public interface Interface1 {
public static final int a = 0;
}
复制代码
这里定义了一个变量int a,修饰符是public static final,但这个修饰符是可选的,即便不写,也是public static final。这个变量能够经过"接口名.变量名"的方式使用,如Interface1.a。
接口也能够继承,一个接口能够继承别的接口,继承的基本概念与类同样,但与类不一样,接口能够有多个父接口,代码以下所示:
public interface IBase1 {
void method1();
}
public interface IBase2 {
void method2();
}
public interface IChild extends IBase1, IBase2 {
}
复制代码
接口的继承一样使用extends关键字,多个父接口之间以逗号分隔。
类的继承与接口能够共存,换句话说,类能够在继承基类的状况下,同时实现一个或多个接口,语法以下所示:
public class Child extends Base implements IChild {
//...
}
复制代码
extends要放在implements以前。
与类同样,接口也可使用instanceof关键字,用来判断一个对象是否实现了某接口,例如:
Point p = new Point(2,3);
if(p instanceof MyComparable){
System.out.println("comparable");
}
复制代码
上节咱们提到,可使用接口替代继承。怎么替代呢?
咱们说继承至少有两个好处,一个是复用代码,另外一个是利用多态和动态绑定统一处理多种不一样子类的对象。
使用组合替代继承,能够复用代码,但不能统一处理。使用接口,针对接口编程,能够实现统一处理不一样类型的对象,但接口没有代码实现,没法复用代码。将组合和接口结合起来,就既能够统一处理,也能够复用代码了。咱们仍是以上节的例子来讲明。
咱们先增长一个接口IAdd,代码以下:
public interface IAdd {
void add(int number);
void addAll(int[] numbers);
}
复制代码
修改Base代码,让他实现IAdd接口,代码基本不变:
public class Base implements IAdd {
//...
}
复制代码
修改Child代码,也是实现IAdd接口,代码基本不变:
public class Child implements IAdd {
//...
}
复制代码
这样,就既能够复用代码,也能够统一处理,并且不用担忧破坏封装了。
本节咱们谈了数据类型思惟的局限,提到了不少时候关心的是能力,而非类型,因此引入了接口,介绍了Java中接口的概念和细节,针对接口编程是一种重要的程序思惟方式,这种方式不只能够复用代码,还能够下降耦合,提升灵活性,是分解复杂问题的一种重要工具。
接口没有任何实现代码,而以前介绍的类都有完整的实现,均可以建立对象,Java中还有一个介于接口和类之间的概念,抽象类,它有什么用呢?
未完待续,查看最新文章,敬请关注微信公众号“老马说编程”(扫描下方二维码),深刻浅出,老马和你一块儿探索Java编程及计算机技术的本质。用心原创,保留全部版权。