参考:https://www.cnblogs.com/doudouxiaoye/p/5661940.html
接口
接口,英文称作interface,在软件工程中,接口泛指供别人调用的方法或者函数。从这里,我们可以体会到Java语言设计者的初衷,它是对行为的抽象。在Java中,定一个接口的形式如下:
1 2 3 |
|
接口中可以含有 变量和方法。但是要注意,接口中的变量会被隐式地指定为public static final变量(并且只能是public static final变量,用private修饰会报编译错误),而方法会被隐式地指定为public abstract方法且只能是public abstract方法(用其他关键字,比如private、protected、static、 final等修饰会报编译错误),并且接口中所有的方法不能有具体的实现,也就是说,接口中的方法必须都是抽象方法。从这里可以隐约看出接口和抽象类的区别,接口是一种极度抽象的类型,它比抽象类更加“抽象”,并且一般情况下不在接口中定义变量。
要让一个类遵循某组特地的接口需要使用implements关键字,具体格式如下:
1 2 |
|
可以看出,允许一个类遵循多个特定的接口。如果一个非抽象类遵循了某个接口,就必须实现该接口中的所有方法。对于遵循某个接口的抽象类,可以不实现该接口中的抽象方法。
JAVA是只支持单继承的,但现实之中存在多重继承这种现象,如“金丝猴是一种动物”,金丝猴从动物这个类继承,同时“金丝猴是一种值钱的东西”,金丝猴从“值钱的东西”这个类继承,同时“金丝猴是一种应该受到保护的东西”,金丝猴从“应该受到保护的东西”这个类继承。这样金丝猴可以同时从 “动物类”、“值钱的东西类”、“应该受到保护的东西” 这三个类继承,但由于JAVA只支持单继承,因此金丝猴只能从这三个类中的一个来继承,不能同时继承这三个类。因此为了封装现实生活中存在的多重继承现象,为了实现多继承,可以把其中的两个类封装成接口。使用接口可以帮助我们实现多重继承。
接口的本质——接口是一种特殊的抽象类,这种抽象类里面只包含常量和方法的定义,而没有变量和方法的实现。
抽象类所具有的一些东西接口可以具有,假如一个抽象类里面所有的方法全都是抽象的,没有任何一个方法需要这个抽象类去实现,并且这个抽象类里面所有的变量都是静态(static)变量,都是不能改变(final)的变量,这时可以把这样的抽象类定义为一个接口(interface)。把一个类定义成一个接口的格式是把声明类的关键字class用声明接口的关键字interface替换掉即可。
1 /** 2 * java中定义接口 3 */ 4 public interface JavaInterfaces { 5 6 }
接口(interface)是一种特殊的抽象类,在这种抽象类里面,所有的方法都是抽象方法,并且这个抽象类的属性(即成员变量)都是声明成“public static final 类型 属性名”这样的,默认也是声明成“public static final”即里面的成员变量都是公共的、静态的,不能改变的。因此在接口里面声明常量的时候,可以写成“public static final 类型 常量名=value(值)”这样的形式,也可以直接写成“类型 常量名=value(值)”如:“public static final int id=10”可以直接写成“int id=10”这样的形式,因为在接口里面默认的属性声明都是“public static final”的,因此“public static final”可以省略不写。在接口里面声明的抽象方法可以不写abstract关键字来标识,因为接口里面所有的方法都是抽象的,因此这个“abstract”关键字默认都是省略掉的,如在一个接口里面声明这样的三个方法:“public void start()”、“public void run()”、“public void stop()”这三个方法前面都没有使用abstract关键字来标识,可它们就是抽象方法,因为在接口里面的声明的方法都是抽象方法,因此在接口里面的抽象方法都会把abstract关键字省略掉,因为默认声明的方法都是抽象的,所以就没有必要再写“abstract”字了,这一点与在抽象类里面声明抽象方法时有所区别,在抽象类里面声明抽象方法是一定要使用“abstract”关键字的,而在接口里面声明抽象方法可以省略掉“abstract”。注意:在接口里面声明的抽象方法默认是“public(公共的)”的,也只能是“public(公共的)”之所以要这样声明是为了修正C++里面多重继承的时候容易出现问题的地方,C++的多继承容易出现问题,问题在于多继承的多个父类之间如果他们有相同的成员变量的时候,这个引用起来会相当地麻烦,并且运行的时候会产生各种各样的问题。JAVA为了修正这个问题,把接口里面所有的成员变量全都改成static final,成员变量是static类型,那么这个成员变量就是属于整个类里面的,而不是专属于某个对象。对于多重继承来说,在一个子类对象里面实际上包含有多个父类对象,而对于单继承来说,子类对象里面就只有一个父类对象。多继承子类对象就有多个父类对象,而这些父类对象之间可能又会存在有重复的成员变量,这就非常容易出现问题,因此在JAVA里面避免了这种问题的出现,采用了接口这种方式来实现多继承。作为接口来说,一个类可以从接口继承(或者叫实现接口),这也是多继承,接口里面的成员变量不专属于某个对象,都是静态的成员变量,是属于整个类的,因此一个类去实现多个接口也是无所谓的,不会存在对象之间互相冲突的问题。实现多个接口,也就实现了多重继承,而且又避免了多重继承容易出现问题的地方,这就是用接口实现多重继承的好处。
二、接口特性
2.1.接口举例
1 package javastudy.summary; 2 3 /** 4 * 这里定义了接口:Painter。 在Painter接口里面定义了paint()和eat()这两个抽象方法。 5 * 6 * @author gacl 7 * 8 */ 9 interface Painter { 10 public void eat(); 11 12 public void paint(); 13 } 14 15 /** 16 * 这里定义了两个接口:Singer 在Singer接口里面定义了sing()和sleep()这两个抽象方法。 17 * 18 * @author gacl 19 * 20 */ 21 interface Singer { 22 public void sing(); 23 24 public void sleep(); 25 } 26 27 /** 28 * 类Student实现了Singer这个接口 29 * 30 * @author gacl 31 * 32 */ 33 class Student implements Singer { 34 35 private String name; 36 37 public Student(String name) { 38 this.name = name; 39 } 40 41 public String getName() { 42 return name; 43 } 44 45 public void setName(String name) { 46 this.name = name; 47 } 48 49 /** 50 * 实现接口中定义的sing方法 51 */ 52 @Override 53 public void sing() { 54 System.out.println("student is singing"); 55 } 56 57 /** 58 * 实现接口中定义的sleep方法 59 */ 60 @Override 61 public void sleep() { 62 System.out.println("student is sleeping"); 63 } 64 65 public void study() { 66 System.out.println("Studying..."); 67 } 68 69 } 70 71 /** 72 * Teacher这个类实现了两个接口:Singer和Painter。 这里Teacher这个类通过实现两个不相关的接口而实现了多重继承。 73 * 74 * @author gacl 75 * 76 */ 77 class Teacher implements Singer, Painter { 78 79 private String name; 80 81 public Teacher(String name) { 82 this.name = name; 83 } 84 85 /** 86 * 在Teacher类里面重写了这两个接口里面的抽象方法, 87 * 通过重写抽象方法实现了这两个接口里面的抽象方法。 88 */ 89 @Override 90 public void eat() { 91 System.out.println("teacher is eating"); 92 } 93 94 public String getName() { 95 return name; 96 } 97 98 @Override 99 public void paint() { 100 System.out.println("teacher is painting"); 101 } 102 103 public void setName(String name) { 104 this.name = name; 105 } 106 107 @Override 108 public void sing() { 109 System.out.println("teacher is singing"); 110 } 111 112 @Override 113 public void sleep() { 114 System.out.println("teacher is sleeping"); 115 } 116 117 public void teach() { 118 System.out.println("teaching..."); 119 } 120 } 121 122 public class TestInterfaces { 123 124 public static void main(String[] args) { 125 /** 126 * 这里定义了一个接口类型的变量s1 127 */ 128 Singer s1 = new Student("le"); 129 s1.sing(); 130 s1.sleep(); 131 Singer s2 = new Teacher("steven"); 132 s2.sing(); 133 s2.sleep(); 134 Painter p1 = (Painter)s2; 135 p1.paint(); 136 p1.eat(); 137 } 138 }
这里验证了两个规则,“一个类可以实现多个无关的接口”,Teacher类既实现了Singer接口,同时也实现了Painter接口,而Singer接口和Painter接口是无关系的两个接口。“多个无关的类可以实现同一接口”,Student类和Teacher类都实现了Singer接口,而Student类和Teacher类并不是关系很密切的两个类,可以说是无关的两个类。
运行结果:
2.2.画内存分析图体会接口与实现类之间存在的多态性
首先分析main方法的第一句话
Singer s1 = new Student(“le”);
这里首先定义了一个接口类型的变量s1,接口Singer是Student类实现的,即相当于Student类从Singer接口继承,Singer接口的本质是一个特殊的抽象类,所以这里Singer接口就是Student类的父类,因此s1就是父类对象的一个引用,即这里这句话执行完后就是一个父类对象s1的引用指向子类对象Student。所以内存里面的布局应该是这样:栈空间里面有一个父类对象的引用s1,堆空间里面new出了一个Student对象,创造这个Student对象的时候调用了Student类的构造方法Student(String name),其定义如下:
Student(String name){
this.name = name;
}
通过调用构造方法使得这个Student对象有了一个自己的名字“le”,因此堆内存里面的Student对象的name属性值为“le”。
这个Student对象能够访问位于代码区里面的sleep()方法和sing()方法,因为Student类从父类Sing继承而来,因此自然可以访问到这两个方法,除此之外,还能访问Student类里面自定义的Study()方法。因此代码区里面存放着这三个方法等待着Student类的对象去访问,也就是去调用。一个正常的Student可以直接调用这三个方法。那么怎么找得到位于代码区的这三个方法呢?Student对象里面存在着能找得到这个三个方法的函数指针,引用对象通过这个指针的索引指向就能找到代码区里面的这三个方法。
s1是父类对象的索引,但此时s1指向的却是子类对象,即一个父类对象的索引指向了子类对象。这里很不幸的是,由于这个s1是一个父类对象的引用,站在s1的角度上,它就是只把你这个子类对象Student当成是一个Singer,s1只能看到Student对象里面的sing()和sleep这两个方法的方法指针,因此使用这个s1引用对象只能去访问从父类继承下来的sleep()和sing()这两个方法,但由于这两个方法在子类Student里面被重写了,那么现在就是这种情况了,子类Student从父类Singer继承,在子类里面重写了从父类继承下来的sing()和sleep()这两个方法,父类对象的引用指向了子类对象,这三种情况加在一起就使得多态可以存在了,这样调用位于代码区里面的方法时,会根据new出来的实际对象去调用代码区里面的方法,因此这里在s1眼里虽然是把这个new出的Student当成一个Singer,但这个对象实际上就是一个Student,因此使用父类对象的引用s1调用代码区里面的sleep()和sing()方法时,调用的是在子类里面重写过后的sing()和sleep()方法。
接着分析第二句话
Singer s2 = new Teacher(“steven”);
Teacher这个类实现了Singer接口和Painter接口,即相当于从两个父类继承,一个父类是Singer,另一个父类是Painter。
这里的s2也是父类对象Singer的引用,指向的却是子类对象Teacher,因此也是一个父类对象的引用指向子类对象。
创造这个Teacher对象的时候,调用Teacher(String name)构造方法,其定义如下:
Teacher(String name){
this.name=name;
}
调用构造方法后,Teacher有了自己的名字steven,所以Teacher的name属性值为steven,由于这个Teacher实现了Painter接口和Singer接口,因此也继承这两个接口里面的方法,因此一个正常的Teacher可以访问的方法有:paint()、eat()和sing()、sleep。前面两个方法是从Painter类继承过来的,后面两个方法是从Singer类继承过来的。除了这四个方法外,还有自己定义的Teach()方法。可是很不幸的是,由于s2是一个Singer类对象的引用,因此站在s2的角度来看,它只把Teacher当成是一个普通的Singer,因此它看到的只是Teacher对象里面的sing()和sleep()这两方法,然后要调用时就通过Teacher对象里面的函数指针找到位于代码区的sleep()和sing()这两个方法。别的方法s2是看不到的,因此也调用不了。
Painter p1=(Painter)s2;
这里把s2强制转换成Painter,s2对象实际是指向Teacher的,把s2强制转换成Painter以后,就可以把Teacher当成Painter来用,所以p1会把Teacher当成Painter来看待,因此p1只能看到Teacher里面的painter()方法和eat()方法,因此能够访问到的也只有这两个方法。所以接口对于我们实际当中的对象来说,每一个接口暴露了我们这个实际对象的一部分方法。你使用什么样的接口,就只能访问这个接口里面定义的方法,别的接口定义的方法就没办法访问得到。
接口可以帮助我们实现多重继承这种逻辑,接口和它的实现类之间存在多态性。
2.3.通过下面这些代码验证接口更进一步的特性
1 package javastudy.summary; 2 3 /** 4 * 把“值钱的东西”这个类定义成一个接口Valuable。在接口里面定义了一个抽象方法getMoney() 5 * @author gacl 6 * 7 */ 8 interface Valuable { 9 public double getMoney(); 10 } 11 12 /** 13 * 把“应该受到保护的东西”这个类定义成一个接口Protectable。 14 * 在接口里面定义了一个抽象方法beProtected(); 15 * @author gacl 16 * 17 */ 18 interface Protectable { 19 public void beProteced(); 20 } 21 22 /** 23 * 这里是接口与接口之间的继承,接口A继承了接口Protectable, 24 * 因此自然而然地继承了接口Protectable里面的抽象方法beProtected()。 25 * 因此某一类去实现接口A时,除了要实现接口A里面定义的抽象方法m()以外, 26 * 还要实现接口A从它的父接口继承下来的抽象方法beProtected()。 27 * 只有把这两个抽象方法都实现了才算是实现了接口A。 28 * @author gacl 29 * 30 */ 31 interface A extends Protectable { 32 void m(); 33 } 34 35 /** 36 * 这里定义了一个抽象类Animal。 37 * @author gacl 38 * 39 */ 40 abstract class Animal { 41 private String name; 42 /** 43 * 在Animal类里面声明了一个抽象方法enjoy() 44 */ 45 abstract void enjoy(); 46 } 47 48 /** 49 * 这里是为了实现了我们原来的语义: 50 * “金丝猴是一种动物”同时“他也是一种值钱的东西”同时“他也是应该受到保护的东西”。而定义的一个类GoldenMonKey。 51 * 为了实现上面的语义,这里把“值钱的东西”这个类定义成了一个接口Valuable, 52 * 把“应该受到保护的东西”这个类也定义成了一个接口Protectable。这样就可以实现多继承了。 53 * GoldenMonKey类首先从Animal类继承,然后GoldenMonKey类再去实现Valuable接口和Protectable接口, 54 * 这样就可以实现GoldenMonKey类同时从Animal类,Valuable类,Protectable类继承了,即实现了多重继承, 55 * 实现了原来的语义。 56 * @author gacl 57 * 58 */ 59 class GoldenMonKey extends Animal implements Valuable,Protectable { 60 61 /** 62 * 在GoldenMoKey类里面重写了接口Protectable里面的beProtected()这个抽象方法, 63 * 实现了接口Protectable。 64 */ 65 @Override 66 public void beProteced() { 67 System.out.println("live in the Room"); 68 } 69 70 /** 71 * 在GoldenMoKey类里面重写了接口Valuable里面的getMoney()这个抽象方法,实现了接口Valuable。 72 */ 73 @Override 74 public double getMoney() { 75 return 10000; 76 } 77 78 /** 79 * 这里重写了从抽象类Animal继承下来的抽象方法enjoy()。 80 * 实现了这抽象方法,不过这里是空实现,空实现也是一种实现。 81 */ 82 @Override 83 void enjoy() { 84 85 } 86 87 public static void test() { 88 /** 89 * 实际当中在内存里面我们new的是金丝猴,在金丝猴里面有很多的方法, 90 * 但是接口的引用对象v能看到的就只有在接口Valuable里面声明的getMoney()方法, 91 * 因此可以使用v.getMoney()来调用方法。而别的方法v都看不到,自然也调用不到了。 92 */ 93 Valuable v = new GoldenMonKey(); 94 System.out.println(v.getMoney()); 95 /** 96 * 把v强制转换成p,相当于换了一个窗口,通过这个窗口只能看得到接口Protectable里面的beProtected()方法 97 */ 98 Protectable p = (Protectable)v; 99 p.beProteced(); 100 } 101 } 102 103 /** 104 * 这里让Hen类去实现接口A,接口A又是从接口Protectable继承而来,接口A自己又定义了一个抽象方法m(), 105 * 所以此时相当于接口A里面有两个抽象方法:m()和beProtected()。 106 * 因此Hen类要去实现接口A,就要重写A里面的两个抽象方法,实现了这两个抽象方法后才算是实现了接口A。 107 * @author gacl 108 * 109 */ 110 class Hen implements A { 111 112 @Override 113 public void beProteced() { 114 115 } 116 117 @Override 118 public void m() { 119 120 } 121 122 } 123 124 /** 125 * java中定义接口 126 */ 127 public class JavaInterfacesTest { 128 129 public static void main(String[] args) { 130 GoldenMonKey.test(); 131 } 132 }
接口总结:接口和接口之间可以相互继承,类和类之间可以相互继承,类和接口之间,只能是类来实现接口。