合规国际互联网加速 OSASE为企业客户提供高速稳定SD-WAN国际加速解决方案。 广告
# 接口与内部类 [TOC] ## 接口 ### 导学 在Java中,只能支持单继承。那么,如果想要在一个类型中能够兼容多种类型特征,代码该如何编写呢?如果多个不同的类型在不具有相同父类的情况下,仍然要具有相同的特征,那代码又该如何编写呢? 比如,我们可以使用一个案例来描述一下。 案例:要求描述手机的发展史? ![](https://img.kancloud.cn/d7/f0/d7f0be6f55801fd2f774f2a62cbb0539_472x248.png) 实现: ~~~ /** * 最原始的手机 * @author LiXinRong * */ public class Telphone { private String brand; private int price; public Telphone() { } public void call() { System.out.println("手机可以打电话"); } public String getBrand() { return brand; } public void setBrand(String brand) { this.brand = brand; } public int getPrice() { return price; } public void setPrice(int price) { this.price = price; } } ~~~ ~~~ package com.dodoke.phone.model; /** * 二代手机 * @author LiXinRong * */ public class SecondPhone extends Telphone{ public void message() { System.out.println("手机可以发短信"); } } ~~~ ~~~ package com.dodoke.phone.model; /** * 三代手机 * @author LiXinRong * */ public class ThirdPhone extends SecondPhone { public void video() { System.out.println("手机可以看视频"); } public void music() { System.out.println("手机可以听音乐"); } } ~~~ ~~~ package com.dodoke.phone.model; /** * 最新一代手机 * @author LiXinRong * */ public class FourthPhone extends ThirdPhone { public void photo() { System.out.println("手机可以拍照"); } public void network() { System.out.println("手机可以上网"); } public void game() { System.out.println("手机可以玩游戏"); } } ~~~ ~~~ package com.dodoke.phone.test; import com.dodoke.phone.model.FourthPhone; public class Test { public static void main(String[] args) { FourthPhone phone = new FourthPhone(); phone.call(); phone.message(); phone.video(); phone.game(); phone.network(); phone.photo(); } } ~~~ 我们可以看到,无论是其他几代的什么功能都可以在四代手机中实现。我们可以很简单的实现这样一个手机演变史的记录。 但是,现在相机可以拍照,电脑可以上网、 玩游戏, 智能手表也可以打电话。那么当程序中需要继续满足这个需求的时候,代码该如何调整? 电话,电脑,相机,智能手表。 他们没有办法抽取一个公共的父类。因为他们的行为是相互交叉,具有相似的能力。但硬性拆出一个公共父类, 就不太合理。那就得创建一个电脑类,智能手表类,相机类,在每个类中增加相应的功能等等.。只要在测试类中生成这几个类的实例对象, 调用他们的方法就可以实现相应的内容。 ~~~ public class Camera { public void photo() { System.out.println("相机可以拍照"); } } ~~~ ~~~ public class Computer { public void video() { System.out.println("电脑可以看视频"); } public void music() { System.out.println("电脑可以听音乐"); } public void network() { System.out.println("电脑可以上网"); } public void game() { System.out.println("电脑可以玩游戏"); } } ~~~ 但是这几种类型中间真的就没办法建立关联了吗? 他们之间的确不能抽取具有公共特征的父类, 但是他们当中很多类型中间是具有相同行为能力的。 那么,在Java中,就可以通过接口实现这些行为的关联 ### 接口实现 ~~~ public interface IPhoto { public void photo(); } ~~~ ~~~ package com.dodoke.phone.model; import com.dodoke.phone.interfaces.IPhoto; public class Camera implements IPhoto{ @Override public void photo() { System.out.println("相机可以拍照"); } } ~~~ ~~~ public class FourthPhone extends ThirdPhone implements IPhoto { @Override public void photo() { System.out.println("手机可以拍照"); } public void network() { System.out.println("手机可以上网"); } public void game() { System.out.println("手机可以玩游戏"); } } ~~~ ~~~ public class Test { public static void main(String[] args) { FourthPhone phone = new FourthPhone(); phone.call(); phone.message(); phone.video(); phone.game(); phone.network(); phone.photo(); System.out.println("====================="); //要求相机拍照可以,手机拍照也可以 IPhoto ip = new Camera(); ip.photo(); ip = new FourthPhone(); ip.photo(); } } ~~~ 小结: 1. 接口中的方法没有方法体。 2. 类使用implements关键字来实现接口。例如:`public class Camera implements IPhoto{}` 3. 如果一个类实现了某个接口,那么必须重写此接口中的所有方法(如果不实现则此类必须设置为抽象类)。 4. 接口无法直接实例化出对象,用接口的引用指向实现类的实例对象。例如:`IPhoto ip=new FourthPhone();` 实例化的对象ip只能调用`IPhoto`这个接口中的方法,调用方法具体的实现细节则由其实例化对象时使用的类决定(如在此如果`ip.photo();` 实际上是运行的`FourthPhone`类中的`photo()`方法)。 ### 接口成员 在讲接口给的成员之前,我们再来提一下接口,就如同抽象类是利用继承给子类指定规范一样,接口也是给一些没有关系的类制定了需要遵守的规范。接口不关心这些类的内部数据,也不关心这些类里面的方法的实现细节,它只规定这些类里面必须提供某些细节。 接下来,我们继续结合具体案例来看一看接口中的内容 #### 抽象方法和常量 ~~~Java package com.dodoke.phone.interfaces; //访问修饰符 只能是public 和 默认default public interface INet { //接口中抽象方法可以不写abstract关键字 public void network(); //接口中抽象方法可以不写public,但是依然会按照public的限定范围使用 void connection(); //接口中,可以包含常量 public static final int TEMP = 20; //关于常量的修饰符可以省略,默认public static final int TEMP2 = 15; } ~~~ 测试 ~~~java System.out.println(INet.TEMP); INet it = new Computer();//接口引用指向具体实现类 System.out.println(it.TEMP); ~~~ >[warning]如果在实现类中存在和接口同名的常量,在接口引用指向实现类时,调用的是接口中定义的常量信息 #### 默认方法和静态方法 ~~~ //访问修饰符 只能是public 和 默认default public interface INet { //接口中抽象方法可以不写abstract关键字 public void network(); //接口中抽象方法可以不写public,但是依然会按照public的限定范围使用 void connection(); //接口中,可以包含常量 public static final int TEMP = 20; //关于常量的修饰符可以省略,默认public static final int TEMP2 = 15; /** * jdk 1.8 以后提供了默认方法 * 可以带方法体,默认方法不一定要被实现 */ default void Connection() { System.out.println("我是接口中的默认链接"); } /** * jdk 1.8 以后提供了静态方法 * 可以带方法体 */ static void stop() { System.out.println("我是接口中的静态方法"); } } ~~~ 测试: ~~~ INet it = new Computer(); it.Connection(); //it.stop();不能调用 INet.stop(); ~~~ 默认方法可以在实现类中重写,也可以不重写,并可以通过**接口的引用**调用。静态方法不可以在实现类中重写,只能通过**接口名**调用。 ### 多接口重名处理 在Java中,只能单继承,但是可以实现多个接口 **同名方法** ~~~ public class SmartWatch implements INet,IPhoto{ public void call() { System.out.println("智能手表可以打电话"); } public void message() { System.out.println("智能手表可以发短信"); } @Override public void photo() { System.out.println("智能手表可以拍照"); } @Override public void network() { System.out.println("智能手表可以上网"); } } ~~~ 如果在接口中存在同名的默认方法,要么删除其中一个接口的方法,要么在实现类中,自己定义一个同名的方法。在接口引用指向实现类的时候,调用的是实现类中的方法。 一个类可以继承一个父类,同时实现若干接口 ~~~ public class FourthPhone extends ThirdPhone implements IPhoto,INet { @Override public void photo() { System.out.println("手机可以拍照"); } @Override public void network() { System.out.println("手机可以上网"); } public void game() { System.out.println("手机可以玩游戏"); } } ~~~ 如果在父类和接口中都存在同名方法,在`FourthPhone` 没有重写该方法不会报错,但最终指向的是父类中的同名方法。如果重写了该方法,最终指向的是`FourthPhone`中重写的方法。 **同名常量** ~~~ interface One { static int X = 11; } interface Two { final int X = 22; } class Three { public int X = 33;//父类中的属性与接口中的常量同名的时候,无法解析 } public class Test1 extends Three implements One,Two{ public void test() { System.out.println(One.X); System.out.println(Two.X); //System.out.println(X);报错 } public static void main(String[] args) { new Test1().test(); } } ~~~ 类实现了多接口时,如果多接口中出现了重名常量,在此类中通过接口名.变量的方式访问。 类继承父类又实现了多接口时,如果父类、多接口中出现了重名常量,只能在该实现类中自己再定义这个重名的变量,才能消除歧义。 ### 接口的继承 接口之间同样也是存在继承关系的。在Java中,接口之间可以实现多继 ~~~ public interface IFather1 { void say(); default void connection() { System.out.println("我是IFather1中的连接方法"); } } ~~~ ~~~ public interface IFather2 { void fly(); default void connection() { System.out.println("我是IFather2中的连接方法"); } } ~~~ ~~~ public interface ISon extends IFather1,IFather2{ void run(); default void connection() { System.out.println("我是儿子的连接方法"); } } ~~~ ~~~ public class Demo implements ISon{ @Override public void say() { // TODO Auto-generated method stub } @Override public void fly() { // TODO Auto-generated method stub } @Override public void run() { // TODO Auto-generated method stub } } ~~~ 总结: 1.子接口允许有多个父接口 2.子接口继承多个父接口,需重写父接口中所有的抽象方法 3.多个父接口默认方法重名时子接口会报错,解决方案:子接口重写重名的默认方法 ## 内部类 ### 导学 在程序开发中为了更加准确的描述结构体的作用,通常拥有各种嵌套结构。而程序**类**也是允许嵌套的! 内部类(内部定义普通类,抽象类,接口的统称)是指一种嵌套的结构关系,即在一个类的内部除了定义属性和方法外还可以定义一个类结构,这样的形式使得程序的结构定义更加灵活。 >[info] 内部类是一种常见的嵌套结构,利用这样的结构可以使内部类与外部类共存,并且方便的进行使用操作的访问。内部类也可以进一步扩展到匿名内部类的使用,在jdk 1.8后所提供的Lambda表达式与方法引用也可以简化代码结构 **示例:** ~~~java package lamda.dodoke.demo1; class Outer {//外部类(非公有类) private String msg = "Hello,World";//定义私有成员属性 public void fun() {//定义普通方法 Inner in = new Inner();//实例化内部类对象 in.print();//调用内部类方法 } class Inner {//在Outer类的内部定义Inner内部类 public void print() {//定义内部类的方法 System.out.println(Outer.this.msg);//调用Outer类的属性 } } } public class JavaDemo {//在一个Java文件中可以有多个类,但是只能有一个public修饰的类 public static void main(String[] args) { Outer out = new Outer();//实例化外部类对象 out.fun();//执行外部类方法 } } ~~~ 准确来说,内部类会使一个类的内部充斥着其他的类结构,所以内部类在整体设计中最大的缺点就是破坏了良好的程序结构,造成代码结构的混乱。但他最大的优点在于可以方便的访问外部类的私有成员。所以我们使用内部类,更多的时候是希望**某一个类只为单独一个类服务**。 ### 内部类注意点 * 内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的`.class`文件,前面冠以外部类的类名和`$`符号。 * 内部类不能用普通的方式访问。内部类是外部类的一个成员,因此内部类可以自由地访问外部类的成员变量,无论是否为 private 的。 * 内部类声明成静态的,就不能随便访问外部类的成员变量,仍然是只能访问外部类的静态成员变量。 ### 成员内部类 成员内部类(实例内部类)是指没有用 static 修饰的内部类,有的地方也称为非静态内部类。示例如下: ~~~ public class Out { class Inner {} } ~~~ * 在外部类的静态方法和外部类以外的其他类中,必须通过外部类的实例创建内部类的实例。 ~~~ public class Outer { class Inner {} Inner inner = new Inner(); // 不需要创建外部类实例 public void method1() { Inner i = new Inner(); // 不需要创建外部类实例 } public static void method2() { Inner i = new Outer().new Inner(); // 需要创建外部类实例 } } //注意是否在同一个包中 ~~~ * 在实例内部类中,可以访问外部类的所有成员。 ~~~ public class Outer { public int a = 100; static int b = 100; final int c = 100; private int d = 100; public String method3() { return "实例方法"; } public static String method4() { return "静态方法"; } class Inner { int a2 = a + 1; // 访问 public 的 a int b2 = b + 1; // 访问 static 的 b int c2 = c + 1; // 访问 final 的 c int d2 = d + 1; // 访问 private 的 d String str1 = method1(); // 访问实例方法method1 String str2 = method2(); // 访问静态方法method2 } public static void main(String[] args) { Inner in = new Outer().new Inner(); System.out.println(in.a2); // 输出 101 System.out.println(in.b2); // 输出 101 System.out.println(in.c2); // 输出 101 System.out.println(in.d2); // 输出 101 System.out.println(in.str1); // 输出实例方法 System.out.println(in.str2); // 输出静态方法 } } ~~~ * 在外部类中不能直接访问内部类的成员,而必须通过内部类的实例去访问。如果类 A 包含内部类 B,类 B 中包含内部类 C,则在类 A 中不能直接访问类 C,而应该通过类 B 的实例去访问类 C。 * 外部类实例与内部类实例是一对多的关系,也就是说一个内部类实例只对应一个外部类实例,而一个外部类实例则可以对应多个内部类实例。 ~~~ public class Outer { int a = 10; class Inner { int a = 20; int a1 = this.a; int b3 = Outer.this.a; } } ~~~ * 在实例内部类中不能定义 static 成员,除非同时使用 final 和 static 修饰。 ### 静态内部类 静态内部类是指使用 static 修饰的内部类。示例代码如下: ~~~ public class Outer { static class Inner {} // 静态内部类 } ~~~ * 在创建静态内部类的实例时,不需要创建外部类的实例。 ~~~ class OtherClass { Outer.Inner oi = new Outer.Inner(); } ~~~ * 静态内部类中可以定义静态成员和实例成员。外部类以外的其他类需要通过完整的类名访问静态内部类中的静态成员,如果要访问静态内部类中的实例成员,则需要通过静态内部类的实例。 ~~~ public class Outer { static class Inner { int a = 0; static int b = 0; } } class OtherClass { Outer.Inner oi = new Outer.Inner(); int a2 = oi.a; // 访问实例成员 int b2 = Outer.Inner.b; // 访问静态成员 } ~~~ * 静态内部类可以直接访问外部类的静态成员,如果要访问外部类的实例成员,则需要通过外部类的实例去访问。 ~~~ public class Outer { int a = 0; static int b = 0; static class Inner { Outer o = new Outer(); int a2 = o.b; // 访问实例变量 int b2 = b; // 访问静态变量 } } ~~~ ### 方法内部类 局部内部类(方法内部类)是指在一个方法中定义的内部类。示例代码如下: ~~~ public class Test { public void method() { class Inner {} // 局部内部类 } } ~~~ * 局部内部类与局部变量一样,不能使用访问控制修饰符(public、private 和 protected)和 static 修饰符修饰。 * 局部内部类只在当前方法中有效。 ~~~ public class Test { Inner i = new Inner(); // 编译出错 Test.Inner ti = new Test.Inner(); // 编译出错 Test.Inner ti2 = new Test().new Inner(); // 编译出错 public void method() { class Inner {} Inner i = new Inner(); } } ~~~ * 局部内部类中不能定义 static 成员。 * 局部内部类中还可以包含内部类,但是这些内部类也不能使用访问控制修饰符(public、private 和 protected)和 static 修饰符修饰。 * 在局部内部类中可以访问外部类的所有成员。 * 在局部内部类中可以直接访问当前方法中的参数与变量。如果方法中的成员与外部类中的成员同名,则可以使用`外部类.this.变量`的形式访问外部类中的成员。 ~~~ public class Test { int a = 0; int d = 0; public void method() { int b = 0; final int c = 0; final int d = 10; class Inner { int a2 = a; // 访问外部类中的成员 int b2 = b; // 访问外部类中的成员 int c2 = c; // 访问方法中的成员 int d2 = d; // 访问方法中的成员 int d3 = Test.this.d; //访问外部类中的成员 } Inner i = new Inner(); System.out.println(i.d2); // 输出 10 System.out.println(i.d3); // 输出 0 } } ~~~ ### 匿名内部类 ## 匿名内部类 匿名类是指没有类名的内部类,必须在创建时使用 new 语句来声明类。其语法形式如下: ~~~ new <类或接口>() { // .... } ~~~ 这种形式的 new 语句声明一个新的匿名类,它对一个给定的类进行扩展,或者实现一个给定的接口。使用匿名类可使代码更加简洁、紧凑,模块化程度更高。 匿名类有两种实现方式: * 继承一个类,重写其方法。 * 实现一个接口(可以是多个),实现其方法 ~~~ public class Out { void show() { System.out.println("调用 Out 类的 show() 方法"); } } public class Test { public static void main(String[] args) { Out o = new Out() { void show() { System.out.println("调用匿名类中的 show() 方法"); } } o.show(); // 调用匿名类中的 show() 方法 } } ~~~ * 匿名类和局部内部类一样,可以访问外部类的所有成员。 ~~~ public static void main(String[] args) { int a = 10; final b = 10; Out o = new Out() { void show() { System.out.println(a); // 编译通过 System.out.println(b); // 编译通过 } } o.show(); } ~~~ * 匿名类中允许使用非静态代码块进行成员初始化操作。 ~~~ Out o = new Out() { int i; { i = 10; } public void show() { System.out.println("i"); } } ~~~ * 匿名类的非静态代码块会在父类的构造方法之后被执行。 ## 练习 一、编程 1. 阅读下面的Java代码,能够填写在横线处的语句是 ![](https://img.kancloud.cn/99/da/99da37e1574055c9f2ddbded123a4cb5_401x145.png) ~~~ A. private int MAX\_LOG\_SIZE = 1000; B. public void print() {} C. private Boolean saveToFile(String fileNmae); D. int getSize(); ~~~ 2. 下列关于Java中接口的说法不正确的是 ~~~ A. 接口中方法的访问修饰符默认为public B. 接口中的方法如果写成void test();的形式,默认是抽象方法 C. 实现接口的类中在重写接口中方法时访问修饰符可以为protected D. 当类实现接口时,需要实现接口中所有的抽象方法,否则需要将该类设置为抽象类 ~~~ 3. 运行下列代码时,哪个位置会发生编译报错 ![](https://img.kancloud.cn/18/82/1882b7b3eb428b3f3a874dfd11b7eda1_445x103.png) ~~~ A. 位置1 B. 位置2 C. 位置3 D. 不存在错误 ~~~ 4. 在实现类中的划线处加入下列哪条代码可以调用 IAa 接口中默认的方法 ![](https://img.kancloud.cn/ae/9e/ae9e914a149821f4e6383950e43b2dcc_441x334.png) ~~~ A. IAa.show(); B. super.show(); C. IAa.super.show(); D. B.super.show(); ~~~ 5. 下列代码的运行结果是 ![](https://img.kancloud.cn/c3/5f/c35f8bcc5ab9c61e674453bb60dd8674_446x389.png) ~~~ A. 10temp B. temp10 C. 1010 D. temptemp ~~~ 6. 已知外部类Out中含有成员内部类Inner,在主方法中怎么获取内部类Inner的实例化对象inner(多选) ~~~ A. Out o = new Out(); Out.Inner inner = o.new Out(); B. Out o = new Out(); Out.Inner inner = o.new Inner(); C. Out.Inner inner = new Inner(); D. Out.Inner inner = new Out().new Inner(); ~~~ 7. 关于下列代码,说法正确的是 ![](https://img.kancloud.cn/5b/a7/5ba7bfc20d3dcbcccc60e48d6c99f557_447x290.png) ~~~ A. 代码编译成功 B. 代码编译错误,错误发生在位置1 C. 代码编译错误,错误发生在位置2 D. 代码编译错误,错误发生在位置3 ~~~ 二、编程 1. 使用接口的知识, 定义接口IFly,创建三个类Plane类、Bird类、Balloon类,分别重写接口中的fly( )方法,然后再测试类中进行调用。 程序运行参考效果如图所示: ![](https://img.kancloud.cn/98/07/9807bef44e7fde188869d4b078b7150f_167x94.png) 任务分析: 1、创建接口IFly( ) 方法:创建抽象方法 fly() 方法 2、创建子类:Plane 方法:实现接口中的方法fly( ),输出信息“飞机在天上飞” 创建子类:Bird 方法:实现接口中的方法fly( ),输出信息“小鸟在天空翱翔"” 创建子类:Balloon(气球) 方法:实现接口中的方法fly( ),输出信息“气球飞上天空” 3、创建测试类,分别创建Plane、Bird、Balloon类的对象,调用 fly( ) 方法,输出效果参考效果图