接口
接口的理解
-
一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果。
-
另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有“A是B”的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3机、手机、数码相机、移动硬盘等都支持USB连接。
-
接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则必须能…”的思想。继承是一个"是不是"的关系,而接口实现则是 "能不能"的关系。
-
接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守。
为什么Java是单继承,多实现,而不多继承?
多继承:指一个子类同时继承多个父类,从而具备多个父类的特征
多继承会造成
1、若子类继承的父类中拥有相同的成员变量,子类在引用该变量时将无法判别使用哪个父类的成员变量
2、若一个子类继承的多个父类拥有相同方法,同时子类并未覆盖该方法(若覆盖,则直接使用子类中该方法),那么调用该方法时将无法确定调用哪个父类的方法。
Java为了简单,废弃了C++中非常容易混淆的多继承等特性。
Java实现“多继承”的三种方式:
多层继承:实际就是多个单继承累积,最下面的子类可以具备前几个父类的特征,但这样的多继承会造成代码冗余,可读性较差,一般的开发同学都不会这样浪费时间。
内部类:通过成员内部类实现多继承,代码示例如下
class dota { private String str = "let's play some dota"; public void play() { System.out.println(str); System.out.println("好呀好呀"); } } class lol { private String str2 = "let's play some lol"; public void play() { System.out.println(str2); System.out.println("不约"); } } class player { class dotar extends dota { public void play() { super.play(); } } class loler extends lol { public void play() { super.play(); } } public void play() { dotar d = new dotar(); d.play(); loler l = new loler(); l.play(); } } public class MultiExtendTest1 { public static void main(String[] args) { player p = new player(); p.play(); } }
接口:多继承机制实现优先使用接口,接口使用比较灵活,在企业级项目编程是最推荐的方式,示例代码:
interface Dota{ void play(); } interface Lol{ void play(); } interface test extends Dota,Lol{ void sayhi(); } class Player implements test,Dota,Lol{ public void play(){ System.out.println("let's play some dota"); } public void sayhi(){ System.out.println("hi"); } } public class MultiExtendTest2{ public static void main(String[] args) { Player p=new Player(); p.play(); p.sayhi(); } }
接口的定义
-
接口(interface)是抽象方法和常量值定义的集合。
-
使用关键字interface定义接口,格式如下:
修饰符 interface 接口名 { } //如: public interface Test {}
-
具体说明:
-
接口和类是并列关系,或者可以理解为一种特殊的类。从本质上讲,接口是一种特殊的抽象类。
- JDK7.0及之前:类中只包含常量和方法的定义,而没有变量和方法的实现。
- JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法。
-
接口中的所有成员变量都默认是由
public static final
修饰的。 —— 全局常量。就算不写修饰符或者没写全,也会被自动加上。(即可以省略不写) -
接口中的所有抽象方法都默认是由
public abstract
修饰的。 就算不写修饰符或者没写全,也会被自动加上。(即可以省略不写) -
接口中不能有构造器。 —— 接口不能实例化。开发中使用接口是通过用类实现接口的方式。
-
接口的实现
-
接口的主要用途就是被实现类实现。
- 面向接口编程:项目的具体需求是多变的,我们必须以不变应万变才能从容开发,此处的“不变”就是规范。因此,我们开发项目往往都是面向接口编程!能作为规范除了接口的语法特征还有重要的一点就是接口与实现类之间具有多态性。
-
如何实现?(如何定义实现类?)
-
类的声明上用关键字 implements
//如: class Test2 implements Test {} //如有extends:先写extends,后写implements class Test2 extends Object implements Test{}
-
-
实现接口的注意点:
-
接口采用多继承机制。一个类可以实现多个接口,接口也可以继承(可以多继承)其它接口。
-
interface Test1 { void test(); } interface Test2 { void test(); } //类实现多个接口,用逗号隔开。要实现所有接口的所有抽象方法。 class Test implements Test1,Test2 { //这个方法算是实现了哪个接口的呢?算一起实现了两个接口的。 public void test() {} } //接口可以多继承,用逗号隔开。(继承:获取父类的所有结构) interface Test3 extends Test1,Test2 {} interface Test4 extends Test1 {}
-
-
实现接口的类中必须提供接口中所有方法的具体实现内容(习惯上叫实现方法而不是重写方法:本身没有方法体),方可实例化。否则,仍为抽象类。
-
实现接口的实现类会获取到接口的属性、默认方法(jdk8及之后)。
- 别忘了属性默认是public static final的。
-
interface B { int x = 2; default void test() { System.out.println("B_test"); } } class A implements B { public static void main(String[] args) { System.out.println(x);//2 new A().test();//B_test } }
-
父类和实现的接口拥有相同属性名称的情况下,编译报错:The field x is ambiguous
interface A { int x = 0; int y = 2; } class B { int x = 1; } class C extends B implements A { public void testX() { //System.out.println(x);//编译报错:The field x is ambiguous //要想使用接口中的x怎么办呢?别忘了接口中默认是全局常量:public static final System.out.println(A.x);//0; //要想使用父类中的x怎么办?用super System.out.println(super.x);//1 } }
-
与继承关系类似,接口与实现类之间存在多态性
-
interface Runner { public void run();} interface Swimmer {public double swim();} class Creator{public int eat(){…}} class Man extends Creator implements Runner ,Swimmer{ public void run() {……} public double swim() {……} public int eat() {……} } public class Test{ public static void main(String args[]){ Test t = new Test(); Man m = new Man(); t.m1(m); t.m2(m); t.m3(m); } public String m1(Runner f) { f.run(); } public void m2(Swimmer s) {s.swim();} public void m3(Creator a) {a.eat();} }
-
匿名实现类的对象:
-
interface A { void a(); } class B implements A { public void a() {} } class Test { public static void main(String[] args) { //1.创建了接口的非匿名实现类的非匿名对象 B b = new B(); //2.创建了接口的非匿名实现类的匿名对象 test(new B()); //3.创建接口的匿名实现类的非匿名对象 A a = new A(){ public void a(){} }; //4.创建接口的匿名实现类的匿名对象 test(new A(){ public void a(){} }); } static test(A a) { a.a(); } }
-
-
-
类优先原则:同名时,父类的方法重写接口中的方法(默认方法、抽象方法)。当然,实现类(即子类)有重写,用实现类的,就没有这些矛盾了,而当实现类没有重写该方法时,遵循上述原则。
注意接口中的方法默认权限是public的,而重写时要求权限不小于被重写的方法。否则编译报错。
-
对于抽象方法:当实现类没有实现时,会找父类的,一直到父类都没有实现该方法时,编译时报错。
-
对于默认方法:当实现类没有重写,而且该方法跟父类的方法同名时,会用父类的方法重写接口中的方法,如果重写不了,则编译报错(不兼容)。
interface B { abstract void test3(); //jdk8对于接口新加了一些特性:可以有静态方法和默认方法 static void test() { System.out.println("B_test"); } default void test2() { System.out.println("B_test2"); } default void test4() { System.out.println("B_test4"); } } class C { void test() { System.out.println("C_test"); } //对于默认方法:会用父类中的此方法重写接口中的该方法,注意接口中的方法默认权限是public的,而重写时要求权限不小于被重写的方法。 public void test2() { System.out.println("C_test2"); } //对于抽象方法:也会用父类中的此方法重写接口中的该方法,注意接口中的方法默认权限是public的,而重写时要求权限不小于被重写的方法。 public void test3() { System.out.println("C_test3"); } public String test4() { //此时编译会报错:The return types are incompatible for the inherited methods B.test4(), C.test4() System.out.println("C_test4"); } } class A extends C implements B { public static void main(String[] args) { C c = new C(); c.test();//C_test。接口中的静态方法不会被子类获取到,所以并不会影响继承父类 c.test2();//C_test2 c.test3();//C_test3 } }
-
实现多个接口中有同名且不同返回值类型的抽象方法时:会有冲突。无法解决,只能避免!
interface Test1 { void test(); } interface Test2 { String test(); } class Test implements Test1,Test2 { @Override public String test() {//编译报错:返回值String与void不兼容,改为void也是,无法确定是实现两个接口中的哪个。 return null; } public void test() { }//编译报错:与上面方法重名,不构成重载 }
-
-
若一个接口中定义了一个默认方法,而另外一个接口中也定义了一个同名同参数的方法(不管此方法是否是默认方法),在实现类同时实现了这两个接口时,会出现:接口冲突。
- 解决办法:实现类或实现类的父类必须覆盖接口中同名同参数的方法,来解决冲突。
interface Filial { default void help() { System.out.println("老妈,我来救你了"); } default void test() { System.out.println("filial"); } } interface Spoony { default void help() { System.out.println("媳妇,别怕,我来了"); } } //方式二:让父类重写! //class Father { // public void help() { // System.out.println("儿子,救我媳妇"); // } //} class Man extends Father implements Filial, Spoony { //编译报错:Duplicate default methods named help with the parameters () and () are inherited from the types Spoony and Filial //解决冲突的方法:实现类/子类 覆盖该方法 @Override public void help() { System.out.println("我该怎么办呢?"); //调用指定接口中的默认方法 Filial.super.help(); Spoony.super.help(); } }
-
Java8中关于接口的新特性
jdk新版总是与旧版本兼容的,所以Java8拥有Java7及以前的所有特性,在此基础上多了一些新特性。
Java 8中,你可以为接口添加静态方法和默认方法。从技术角度来说,这是完全合法的,只是它看起来违反了接口作为一个抽象定义的理念。
-
静态方法:
- 使用 static 关键字修饰。默认是public的,可以省略,会自动加上。
- 只能通过接口直接调用其静态方法,并执行其方法体。不能通过实现类调用,即实现类并不能获取到接口中的静态方法。
- 我们经常在相互一起使用的类中使用静态方法。你可以在标准库中找到像Collection/Collections或者Path/Paths这样成对的接口类。
-
默认方法:
-
默认方法使用 default 关键字修饰。只能通过实现类对象来调用。我们在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。比如:java 8 API中对Collection、List、Comparator等接口提供了丰富的默认方法。
/* 结论: 1.default方法可以被重写(相当于被继承了) 2.default类似于一种权限修饰符的作用 —— 重写时要求重写的权限修饰符要大于被重写的方法。 default的权限大小:可以在同一工程下被访问,不同包之间也访问得到,不同工程下没试过。 3.实现类可以用 `接口名.super.默认方法` 指明调用的默认方法。可以用来调用被重写的默认方法。 */ interface A { default void test() { System.out.println("test"); } default void test2() { System.out.println("test2"); } } class B { } class C implements A { void t() { test(); } //void test2() {//编译报错:Cannot reduce the visibility of the inherited method from A。所以default类似于一种权限修饰符的作用 —— 重写时要求重写的权限修饰符要大于被重写的方法。 public void test2() { //结果会重写接口中的default方法——test2 System.out.println("test3"); } public static void main(String[] args) { new C().test();//test new C().test2();//test3 A.super.test();//指明调用的默认方法,可以用来调用被重写的默认方法。 } }
-
使用时注意:类优先原则、接口冲突。(看上面接口的实现说明)
-
接口与抽象类的区别?
实现不等于继承?
- 实现会获取到接口的结构吗?
答:对于属性:会。对于方法:只有默认方法(抽象方法abstract会起作用:即要求实现)。
- 所以,接口是特殊的抽象类?
答:可以这么理解,但二者不是相等关系。二者有相似之处也有一定区别。各有各的特点。
接口的应用
-
作为一种规范:想装上就必须实现指定的功能。 —— 面向接口编程
-
代理模式
-
概述:代理模式是Java开发中使用较多的一种设计模式。代理设计就是为其他对象提供一种代理以控制对这个对象的访问。
-
举例:
interface Network { public void browse(); } // 被代理类 class RealServer implements Network { @Override public void browse() { System.out.println("真实服务器上网浏览信息"); } } // 代理类 class ProxyServer implements Network { private Network network; public ProxyServer(Network network) { this.network = network; } public void check() { System.out.println("检查网络连接等操作"); } public void browse() { check(); network.browse(); } } public class ProxyDemo { public static void main(String[] args) { Network net = new ProxyServer(new RealServer()); net.browse(); } } //以上是静态代理!
-
-
分类:
-
静态代理(静态定义代理类):不变的,专门针对某个点的代理。
-
动态代理(动态生成代理类)如:
-
JDK自带的动态代理,需要反射等知识
-
应用场景:
- 安全代理:屏蔽对真实角色的直接访问。
- 远程代理:通过代理类处理远程方法调用(RMI)
- 延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象比如你要开发一个大文档查看软件,大文档中有大的图片,有可能一个图片有100MB,在打开文件时,不可能将所有的图片都显示出来,这样就可以使用代理模式,先加载轻量级的代理对象,当需要查看图片时,用proxy来进行大图片的打开。
-
-
工厂设计模式