内部类
内部类的基本概念
- 当一个类的定义出现在另外一个类的类体中时,那么这个类叫做内部类(Inner),而这个内部类所在的类叫做外部类(Outer)。也就是类内部的类。
- 类中的内容:成员变量、成员方法、构造方法、静态成员(变量、方法,隶属于类层级的)、构造块和静态代码块、内部类。
实际作用
- 当一个类存在的价值仅仅是为某一个类单独服务时,那么就可以将这个类定义为所服务类中的内部类,这样可以隐藏该类的实现细节(可以对外不可见,也不需要可见)并且可以方便的访问外部类的私有成员而不再需要提供公有的get和set方法(相当于是外部类的成员,成员和成员是一家人,所以可以直接访问)。
内部类的分类
- 普通内部类 - 直接将一个类的定义放在另外一个类的类体中。隶属于对象层级,通过引用.的方式访问。
- 静态内部类 - 使用static关键字修饰的内部类,隶属于类层级。通过类名.的方式访问。
- 局部内部类 - 直接将一个类的定义放在方法体的内部时。
- 匿名内部类 - 就是指没有名字的内部类。
普通(成员)内部类的格式
-
语法规则: 访问修饰符 class 外部类的类名 { 访问修饰符 class 内部类的类名 { 内部类的类体; } }
普通内部类的使用方式
-
普通内部类和普通类一样可以定义成员变量、成员方法以及构造方法等。
-
普通内部类和普通类一样可以使用final或者abstract关键字修饰。
- 普通内部类还可以使用private或protected关键字进行修饰。
-
普通内部类需要使用外部类对象来创建对象。
-
如果内部类访问外部类中与本类内部同名的成员变量或方法时,需要使用this关键字。在内部类中获取内部类的成员变量使用:this.成员变量名 外部类中的成员变量获取:外部类类名.this.成员变量名(笔试考点)
package com.lagou.module02.task05; /** * @author hhc19 * @date 2022/1/1 14:33 * @description 编程实现普通内部类的定义和使用 - 文档注释 */ public class NormalOuter { private int cnt = 1; // 定义普遍内部类, 隶属于外部类的成员,并且是对象层级,只要没有static关键字修饰,它都是对象层级而不是类层级 // 内部类应该是对当前类好使,对外部类不可见,故而把public关键字改为private public /*final*/ class NormalInner { // 加了final内部类可以限制该内部类和该类其它内部类之间不能继承,即该类中其它内部类不能继承该内部类 /*private class NormalInner {*/ private int ia = 2; private int cnt = 3; public NormalInner() { System.out.println("普通内部类的构造方法体执行到了!"); } public void show() { System.out.println("外部类中变量cnt的数值为:" + cnt); // 1 System.out.println("ia = " + ia); // 2 } public void show2(int cnt) { System.out.println("形参变量 cnt = " + cnt); // 局部优先原则 System.out.println("内部类中 cnt = " + this.cnt); // 3 System.out.println("外部类中 cnt = " + NormalOuter.this.cnt); // 1 外部类的类名.this 可以得到它所在的外部类中的this关键字,然后就可以得到外部类中成员变量的值 } } } package com.lagou.module02.task05; /** * @author hhc19 * @date 2022/1/1 14:44 * @description */ public class NormalOuterTest { public static void main(String[] args) { // 1、声明NormalOuter类型的引用指向该类型的对象 NormalOuter no = new NormalOuter(); // 2、声明NormalOuter类中内部类的引用指向内部类的对象 // 'com.lagou.module02.task05.NormalOuter' is not an enclosing clas // NormalOuter.NormalInner ni = new NormalOuter.NormalInner(); // . 就是'的'的意思,对象层级的东西要想使用,得使用引用.的方式 // 私有了内部类之后:'com.lagou.module02.task05.NormalOuter.NormalInner' has private access in 'com.lagou.module02.task05.NormalOuter' NormalOuter.NormalInner ni = no.new NormalInner(); NormalOuter.NormalInner ni2 = new NormalOuter().new NormalInner(); // 3、调用内部类的show方法] ni.show(); System.out.println("------------------------------------"); ni.show2(4); } }
静态内部类的格式
-
语法规则: 访问修饰符 class 外部类的类名 { 访问修饰符 static class 内部类的类名 { 内部类的类体; } }
静态内部类的使用方式
-
静态内部类不能直接访问外部类的非静态成员。
-
静态内部类可以直接创建对象,不再需要外部类先创建对象了。
-
如果静态内部类访问外部类中与本类内同名的成员变量或方法时,需要使用类名.的方式访问。(笔试考点)
package com.lagou.module02.task05; /** * @author hhc19 * @date 2022/1/1 15:35 * @description 实现静态内部类的定义和使用 */ public class StaticOuter { private int cnt = 1; // 隶属于对象层级 private static int snt = 2; // 隶属于类层级 public /*static*/ void show() { System.out.println("外部类的show方法就是这里!"); } /** * 定义静态内部类 有static关键字修饰隶属于类层级 */ public static class StaticInner { private int ia = 3; private static int snt = 4; public StaticInner() { System.out.println("讲台内部类的构造方法体执行到了!"); } public void show() { System.out.println("ia = " + ia); // 3 System.out.println("外部类中的 snt = " + snt); // 2 // System.out.println("外部类中的 cnt = " + cnt); // Error:Non-static field 'cnt' cannot be referenced from a static context // 不能在静态内容中访问非静态成员 静态上下文中不能访问非静态的成员,因为此时可能还没有创建对象 } public void show2(int snt) { // 就近原则 System.out.println("snt = " + snt); // 参数变量snt的值 System.out.println("内部类中的成员 snt = " + StaticInner.snt); // 4 System.out.println("外部类中的成员 snt = " + StaticOuter.snt); // 2 // StaticOuter.this.show(); // Non-static method 'show()' cannot be referenced from a static context // 'com.lagou.module02.task05.StaticOuter.this' cannot be referenced from a static context // StaticOuter.show(); new StaticOuter().show(); } } } package com.lagou.module02.task05; /** * @author hhc19 * @date 2022/1/1 15:48 * @description */ public class StaticOuterTest { public static void main(String[] args) { // 1、声明StaticInner类型的引用指向该类型的对象 StaticOuter.StaticInner si = new StaticOuter.StaticInner(); // 类层级可以直接类名.访问了,不需要new 对象,所以此处可以直接这样创建对象,而不用像之前一样先创建外部类对象 // 2、调用show方法进行测试 $作为标识符的命名规范,日常开发中我们一般不会使用到它。但是在内部类的字节码文件名处会使用到 // 内部类的字节码文件名为:外部类类名$内部类类名 si.show(); System.out.println("--------------------------"); si.show2(5); } }
局部(方法)内部类的格式
-
语法规则: 访问修饰符 class 外部类的类名 { 访问修饰符 返回值类型 成员方法名(形参列表) { class 内部类的类名 { 内部类的类体; } } }
-
局部内部类不需要使用 public、private、protected等访问修饰符修饰。
局部内部类的使用方式
-
局部内部类只能在该方法的内部可以使用。
-
局部内部类可以在方法体内部直接创建对象。
-
局部内部类不能使用访问控制符和static关键字修饰符。(啥时候见过在局部变量上加访问控制符和static关键字吗?)
-
局部内部类可以使用外部方法的局部变量,但是必须是final的。由局部内部类和局部变量的声明周期不同所致。(笔试考点)
package com.lagou.module02.task05; /** * @author hhc19 * @date 2022/1/1 16:31 * @description 编程实现局部内部类的定义和使用 */ public class AreaOuter { private int cnt = 1; public void show() { // 定义一个局部变量进行测试, Java8开始默认理解为final关键字修饰的变量 // 虽然可以省略final关键字,但建议还是加上 // int ic = 4; final int ic = 4; /** * 在局部内部类中使用外部类中方法体中的局部变量必须声明为final的主要原因: * 局部变量从声明之后一直到方法结束期间一直都是有效的,局部内部类也是如此。 * 明显,局部变量的有效范围大于局部内部类的有效范围,因为局部变量先于局部内部类声明,意味着二者的有效范围或者是有效区域是不一样的。 * 当内部类中使用到局部变量的时候,底层有个原理:它会把局部变量拷贝一份拿到内部类的内部去使用。 * 在使用过程中,为什么加final是因为:如果不加final的话意味着该局部变量的值是可以改变的。 * 我拷贝了一份拿到里面傻傻的在使用,结果你外面背着我把这个值给改了。外部类一旦把这个值改了,内部类里面拿到的使用的这个值跟外部类中真实的值就不一样了。 * 不一样就会造成数据的不一致性问题。为了避免这种错误的发生,就要求加final。 * 之所以要避免这个问题是因为二者的声明周期是不一致的。 */ // 定义局部内部类,只在当前方法体的内部好使 拷贝一份局部变量然后操作 /*public*/ class AreaInner { // Modifier 'public' not allowed here private int ia = 2; public AreaInner() { System.out.println("局部内部类的构造方法!"); } public void test() { int ib = 3; System.out.println("ia = " + ia); // 2 System.out.println("cnt = " + cnt); // 1 // ic = 5; // 不加final:Variable 'ic' is accessed from within inner class, needs to be final or effectively final // ic = 5; Cannot assign a value to final variable 'ic' 加了final关键字后 System.out.println("ic = " + ic); // 4 } } // 声明局部内部类的引用指向局部内部类的对象 AreaInner ai = new AreaInner(); ai.test(); } } package com.lagou.module02.task05; /** * @author hhc19 * @date 2022/1/1 16:37 * @description */ public class AreaOuterTest { public static void main(String[] args) { // 1、声明外部类类型的引用指向外部类的对象 AreaOuter ao = new AreaOuter(); // 2、通过show方法的调用实现局部内部类的定义和使用 ao.show(); } }
回调模式的概念和编程
-
回调模式是指——如果一个方法的参数是接口类型,则在调用该方法时,需要创建并传递一个实现此接口类型的对象(因为接口不能new对象);而该方法在运行时会调用到参数对象中所实现的方法(接口中定义的)。
-
为什么叫回调?我把我的对象传给你,然后你又根据我的对象回来调用我内部的方法,所以这才叫回调,回过头来调我的方法。所以这叫做回调模式
package com.lagou.module02.task05; /** * @author hhc19 * @date 2022/1/1 17:28 * @description */ public interface AnonymousInterface { // 自定义抽象方法 public abstract void show(); } package com.lagou.module02.task05; /** * @author hhc19 * @date 2022/1/1 17:38 * @description */ public class AnonymousInterfaceImpl implements AnonymousInterface { // 写接口实现类的时候都习惯在接口名后+Impl Impl时implements 的缩写 @Override public void show() { System.out.println("这里是接口的一个实现类!!!"); } } package com.lagou.module02.task05; /** * @author hhc19 * @date 2022/1/1 17:30 * @description */ public class AnonymousInterfaceTest { // 假设已有下面的方法,请问如何调用下面的方法? // AnonymousInterface ai = new AnonymousInterfaceImpl(); // 典型的接口类型的引用指向实现类型的对象,形成了多态 public static void test(AnonymousInterface ai) { // 编译阶段调用父类版本,运行调用实现类重写的版本 ai.show(); } public static void main(String[] args) { // AnonymousInterfaceTest.test(() -> System.out.println("淳神好帅!!!")); // Error:AnonymousInterfaceTest.test(new AnonymousInterface()); // 'AnonymousInterface' is abstract; cannot be instantiated 接口不能实例化 AnonymousInterfaceTest.test(new AnonymousInterfaceImpl()); } }
开发经验分享
- 当接口/类类型的引用作为方法的形参时,实参的传递方式有两种:
- 自定义类实现接口/继承类并重写方法,然后创建该类对象作为实参传递;
- 使用上述匿名内部类的语法格式得到接口/类类型的引用即可;
匿名内部类的语法格式(重点 )
-
接口/父类类型 引用变量名 = new 接口/父类类型() {方法的重写};
package com.lagou.module02.task05; /** * @author hhc19 * @date 2022/1/1 17:30 * @description */ public class AnonymousInterfaceTest { // 假设已有下面的方法,请问如何调用下面的方法? // AnonymousInterface ai = new AnonymousInterfaceImpl(); // 典型的接口类型的引用指向实现类型的对象,形成了多态 public static void test(AnonymousInterface ai) { // 编译阶段调用父类版本,运行调用实现类重写的版本 ai.show(); } public static void main(String[] args) { // AnonymousInterfaceTest.test(() -> System.out.println("淳神好帅!!!")); // Error:AnonymousInterfaceTest.test(new AnonymousInterface()); // 'AnonymousInterface' is abstract; cannot be instantiated 接口不能实例化 AnonymousInterfaceTest.test(new AnonymousInterfaceImpl()); System.out.println("------------------------------------------"); // 使用匿名内部类的语法格式来得到接口类型的引用,格式为:接口/父类类型 引用变量名 = new 接口/父类类型() {方法的重写}; // 使用匿名内部类的优势:new完对象,这个类的价值就不存在了,就可以销毁它的存储空间了 AnonymousInterface ait = new AnonymousInterface() { @Override public void show() { System.out.println("匿名内部类就是这么玩的,虽然你很抽象!!!"); } }; // 从Java8开始提出新特性:Lambda表达式,可以简化上述代码,格式为:(参数列表) -> {方法体} AnonymousInterface ait2 = () -> System.out.println("lambda表达式原来是如此简单"); AnonymousInterfaceTest.test(ait); AnonymousInterfaceTest.test(ait2); } }