首页
技术小册
AIGC
面试刷题
技术文章
MAGENTO
云计算
视频课程
源码下载
PDF书籍
「涨薪秘籍」
登录
注册
第一章:OOP:继承
第二章:关键字 super
第三章:关键字 static
第四章:成员变量初始化
第五章:OOP:多态
第六章:关键字 native
第七章:关键字 final
第八章:Object 类的使用
第九章:抽象类
第十章:接口
第十一章:经典接口介绍
第十二章:包装类
第十三章:内部类
当前位置:
首页>>
技术小册>>
Java语言基础6-面向对象高级
小册名称:Java语言基础6-面向对象高级
13.1 概述 - 当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内部类。 - 在 Java 中,允许一个类 A 的定义位于另一个类 B 的内部,类 A 就被称为内部类,而类 B 就被称为外部类。 - 因为内部类在外部类的里面,因此可以直接访问外部类的私有成员。 13.2 内部类的分类 - 根据内部类声明的位置不同,我们可以将内部类分为如下两类: - ① 成员内部类。 - 静态成员内部类。 - 非静态成员内部类。 - ② 局部内部类。 - 有名字的局部内部类。 - 匿名内部类。 - 成员内部类作为类的成员角色: - 外部类只能通过 public 和 default(缺省)修饰,而内部类还可以声明为 private 和 protected 。 - 可以调用外部类的结构。 - 内部类可以声明为 static 的,那么此时不能再调用外部类的非static的成员。 - 成员内部类作为类的角色: - 可以在内部定义属性、方法、构造器等结构。 - 可以声明为 abstract 类,因此可以被其它的内部类继承。 - 可以声明为 final 的。 - 编译以后生成 OuterClass$InnerClass.class 类似的字节码文件。 注意: - ① 非 static 的成员内部类中的成员不能声明为 static 的,只有在外部类或 static 的成员内部类才可以声明 static 成员。 - ② 外部类访问成员内部类的成员,需要通过 内部类.成员 或 内部类对象.成员 的方式。 - ③ 成员内部类可以直接使用外部类的所有成员,包括私有数据。 - ④ 当想要在外部类的静态成员中使用内部类的时候,可以考虑将内部类声明为 static 的。 13.3 静态内部类 - 语法: ```bash [权限修饰符2种] class 外部类名{ [权限修饰符4种] static [final] class 内部类名 { ... } } ``` - 特点: - ① 和其他类一样,它只是定义在外部类中的另一个完整的类结构。 - 可以继承自己想要继承的父类,实现自己想要实现的接口,和外部类的父类以及外部类的父接口无关。 - 可以在静态内部类中声明属性、方法、构造器等结构,包括静态成员。 - 可以使用 abstract 修饰,因此它也可以被其他类继承。 - 可以使用 final 修饰,表示不能被继承。 - 编译后有自己的独立的字节码文件,只不过在内部类前面冠以 外部类名和$符号 。 - ② 和外部类不同的是,它允许四种权限修饰符:public、protected、缺省、private ;而外部类只允许 public 和 缺省。 - ③ 静态内部类可以访问外部类的资源: - 静态的属性。 - 静态的方法。 - ④ 外部类使用内部类的资源: - 如果是静态资源,可以直接通过 内部类.资源名 。 - 如果是非静态资源,那么需要通过 内部类的对象.资源名 。 - ⑤ 如果在内部类中有变量和外部类的成员变量相同,可以使用 外部类.变量名进行区别 。 其实严格的讲(在 James Gosling 等人编著的《The Java Language Specification》)静态内部类不是内部类,而是类似于 C++ 的嵌套类的概念,外部类仅仅是静态内部类的一种命名空间的限定名形式而已。所以接口中的内部类通常都不叫内部类,因为接口中的内部成员都是隐式是静态的(即 public static )。例如:Map.Entry。 示例: ```bash package com.inner.demo1; /** * @author maxiaoke.com * @version 1.0 * @return */ public class Outer { // 外部类 private static final int age = 66; int num = 10; public static void outerMethod2() { System.out.println("outerMethod2"); } public void outerMethod1() { System.out.println(Inner.a); System.out.println(new Inner().num); } static class Inner { // 内部类 static int a = 100; static int age = 100; int num = 100; public void method() { System.out.println(age); System.out.println(Outer.age); outerMethod2(); } } } ``` ```bash package com.inner.demo1; /** * @author maxiaoke.com * @version 1.0 * @return */ public class Test { public static void main(String[] args) { Outer outer = new Outer(); System.out.println("outer.num = " + outer.num); Outer.Inner inner = new Outer.Inner(); System.out.println("inner.num = " + inner.num); inner.method(); } } ``` 13.4 非静态成员内部类 - 语法: ```bash [权限修饰符2种] class 外部类名{ [权限修饰符4种] [final] class 内部类名 { ... } } ``` - 特点: - ① 和其他类一样,它只是定义在外部类中的另一个完整的类结构。 - 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关。 - 可以在非静态内部类中声明属性、方法、构造器等结构,但是 不允许声明静态成员 ,但是可以 继承 父类的静态成员,而且 可以声明静态常量 。 - 可以使用 abstract 修饰,因此它也可以被其他类继承。 - 可以使用 final 修饰,表示不能被继承。 - 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以 外部类名和$符号 。 - ② 和外部类不同的是,它可以允许四种权限修饰符:public ,protected ,缺省 ,private 。 - 外部类只允许 public 或 缺省 。 - ③ 非静态内部类可以直接使用外部类的所有资源。 - ④ 外部类使用内部的资源: - 首选创建非静态内部类的对象,然后才能使用。 - 如果是内部类中的静态常量,可以直接使用。 - ⑤ 如果在内部类中有变量和外部类的成员变量相同,可以使用 外部类.this.变量名 进行区别。 - 示例: ```bash package com.inner.demo2; /** * @author maxiaoke.com * @version 1.0 * @return */ public class Outer { static int age = 20; int num = 30; public static void outerMethod2() { System.out.println("outerMethod2"); } public void outerMethod1() { System.out.println("outerMethod1"); System.out.println(Inner.a); Inner inner = new Inner(); System.out.println(inner.name); inner.innerMethod(); } class Inner { static final int a = 10; int num = 100; String name = "张三"; public void innerMethod() { System.out.println(age); System.out.println(Outer.this.num); System.out.println(this.num); outerMethod2(); } } } ``` ```bash package com.inner.demo2; /** * @author maxiaoke.com * @version 1.0 * @return */ public class Test { public static void main(String[] args) { Outer outer = new Outer(); System.out.println("outer.num = " + outer.num); Outer.Inner inner = new Outer().new Inner(); inner.innerMethod(); System.out.println(inner.num); } } ``` 13.6 匿名内部类 13.6.1 概述 - 当我们在开发的过程中,需要用到一个抽象类的子类或一个接口的实现类对象,而且只创建一个对象,并且逻辑也不复杂,我们原来是怎么做的? - ① 编写类,继承这个父类或实现这个接口。 - ② 重写父类或父接口的方法。 - ③ 创建这个这个类的对象。 - 示例: ```bash package com.github.demo16; /** * @author maxiaoke.com * @version 1.0 * @return */ public class Person { private String name; private int age; private double salary; public Person() {} public Person(String name, int age, double salary) { this.name = name; this.age = age; this.salary = salary; } @Override public String toString() { return "Person{" + "name='" + this.name + '\'' + ", age=" + this.age + ", salary=" + this.salary + '}'; } public String getName() { return this.name; } public void setName(String name) { this.name = name; } public int getAge() { return this.age; } public void setAge(int age) { this.age = age; } public double getSalary() { return this.salary; } public void setSalary(double salary) { this.salary = salary; } } ``` ```bash package com.github.demo16; import java.util.Comparator; /** * @author maxiaoke.com * @version 1.0 * @return */ public class Arrays { public static void sort(Object[] arr, Comparator comparator) { for (int i = 0; i < arr.length - 1; i++) { for (int j = 0; j < arr.length - i - 1; j++) { if (comparator.compare(arr[j], arr[j + 1]) > 0) { Object temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } } } ``` ```bash package com.github.demo16; import java.util.Comparator; /** * @author maxiaoke.com * @version 1.0 * @return */ public class PersonAgeCompare implements Comparator<Person> { @Override public int compare(Person o1, Person o2) { return Integer.compare(o1.getAge(), o2.getAge()); } } ``` ```bash package com.github.demo16; /** * @author maxiaoke.com * @version 1.0 * @return */ public class Test { public static void main(String[] args) { Person p1 = new Person("张三", 78, 100); Person p2 = new Person("李四", 16, 9000); Person p3 = new Person("王五", 39, 500); Person p4 = new Person("赵六", 20, 3000); Person[] persons = new Person[] {p1, p2, p3, p4}; System.out.println("----------------排序前----------------"); printArray(persons); System.out.println("----------------排序后----------------"); PersonAgeCompare compare = new PersonAgeCompare(); Arrays.sort(persons, compare); printArray(persons); } private static void printArray(Person[] persons) { for (Person person : persons) { System.out.println(person); } } } ``` 因为考虑到子类或实现类是一次性的,那么我们 费尽心思 给它取名字,就显得多余,我们完全可以使用匿名内部类的方式来实现,避免给类命名的问题。 ```bash package com.github.demo16; import java.util.Comparator; /** * @author maxiaoke.com * @version 1.0 * @return */ public class Test { public static void main(String[] args) { Person p1 = new Person("张三", 78, 100); Person p2 = new Person("李四", 16, 9000); Person p3 = new Person("王五", 39, 500); Person p4 = new Person("赵六", 20, 3000); Person[] persons = new Person[] {p1, p2, p3, p4}; System.out.println("----------------排序前----------------"); printArray(persons); System.out.println("----------------排序后----------------"); Arrays.sort(persons, new Comparator<Person>() { @Override public int compare(Person o1, Person o2) { return o1.getAge() - o2.getAge(); } }); printArray(persons); } private static void printArray(Person[] persons) { for (Person person : persons) { System.out.println(person); } } } ``` 13.6.2 语法 语法格式一: ```bash // 创建了一个子类对象,但是子类对象没有名字 new 父类名 (实参列表){ 重写方法... } ``` 语法格式二: ```bash // // 创建了一个子类对象,但是子类对象没有名字 new 父接口(){ 重写方法... } ``` 语法格式三: ```bash // 创建了一个子类对象,但是子类对象没有名字 new 父类名 (){ 重写方法... } ``` 温馨提示: - 匿名对象:没有名字的对象。 - 匿名内部类是一种特殊的局部内部类,是没有名字的类,其本质是继承该类或实现接口的子类(实现类)匿名对象。 - 示例: ```bash package com.inner.demo4; /** * @author maxiaoke.com * @version 1.0 * @return */ public class Test { public static void main(String[] args) { // 证明了只能在方法中使用,不就是局部内部类吗? Person person = new Person() { @Override public void show() { System.out.println("重写的show"); } }; System.out.println(person instanceof Person); // true 证明了person是Person的对象 System.out.println(person.getClass()); // class com.inner.demo13.Test$1 证明了 person是内部类 person.show(); // 重写的show 证明了重写了Person类中的show方法 Person person2 = new Person(20) { @Override public void show() { super.show(); } }; person2.show(); // show20 } } class Person { int num; public Person() {} public Person(int num) { this.num = num; } public void show() { System.out.println("show" + this.num); } } ``` 13.6.3 语法理解 我们知道匿名内部类有三种语法格式: ```bash new 父类名 (){ 重写方法... } new 父类名 (实参列表){ 重写方法... } new 父接口(){ 重写方法... } ``` - 因为匿名内部类是没有名字的,就只能是一次性的、唯一的对象,必须在声明类的同时将对象创建好,否则后期将无法创建它的对象;即声明类的同时就将对象创建好,换言之,匿名内部类既声明了一个类,也创建了一个匿名对象。 - 因为匿名内部类没有名字,所以创建对象的时候,就只能用父类或父接口来表示,并且表明了该匿名内部类的父类和父接口是谁? ```bash new 父类名 (){ // 相当于 class 匿名子类 extends 父类 {} 重写方法... } new 父类名 (实参列表){ // 相当于 class 匿名子类 extends 父类 {} 重写方法... } new 父接口(){ // 相当于 class 匿名子类 extends Object implements 父接口 {} 重写方法... } ``` 子类在继承父类的时候,一定要在子类构造器的首行调用父类的构造器。 ```bash new 父类名 (){ // 相当于在 匿名子类 的构造器的首行有 super(); 重写方法... } new 父类名 (实参列表){ // 相当于在 匿名子类 的构造器的首行有 super(实参列表); 重写方法... } new 父接口(){ // 相当于在 匿名子类 的构造器的首行有 super(); 并且调用的是 Object 类的无参构造 重写方法... } ``` 13.6.4 匿名内部类的注意事项 - 匿名内部类是一种特殊的局部内部类,只不过没有名字而已,所有局部内部类的限制都适用于匿名内部类,比如: - ① 在匿名内部类中是否可以使用外部类的非静态成员变量,看所在方法是否静态。 - ② 在匿名内部类中如果需要访问当前方法的局部变量,该局部变量需要加 final( JDK 1.8 之后,如果某个局部变量在局部内部类中被使用了,自动加 final )。 13.6.5 匿名内部类的使用场景 - 匿名内部类产生的匿名对象能做什么? - ① 调用某个方法。 - ② 赋值给父类或父接口的变量,通过多态引用使用该对象。 - ③ 作为某个方法调用的实参。 - 示例:匿名内部类的对象直接调用方法 ```bash package com.inner.demo5; interface A { void a(); } /** * @author maxiaoke.com * @version 1.0 * @return */ public class Test { public static void main(String[] args) { new A() { @Override public void a() { System.out.println("^_^"); } }.a(); } } ``` 示例:过父类或父接口的变量多态引用匿名内部类的对象 ```bash package com.inner.demo6; interface A { void a(); } /** * @author maxiaoke.com * @version 1.0 * @return */ public class Test { public static void main(String[] args) { A demo = new A() { @Override public void a() { System.out.println("(*^▽^*)"); } }; demo.a(); } } ``` 示例:匿名内部类的对象作为实参 ```bash package com.inner.demo7; interface A { void show(); } /** * @author maxiaoke.com * @version 1.0 * @return */ public class Test { public static void main(String[] args) { test(new A() { @Override public void show() { System.out.println("你好啊"); } }); } public static void test(A a) { a.show(); } } ```
上一篇:
第十二章:包装类
该分类下的相关小册推荐:
深入拆解 Java 虚拟机
Java语言基础14-枚举和注解
Java语言基础7-Java中的异常
SpringBoot零基础到实战
Java语言基础4-数组详解
Mybatis合辑4-Mybatis缓存机制
Java并发编程
Java语言基础8-Java多线程
Java语言基础2-运算符
java源码学习笔记
Java语言基础11-Java中的泛型
SpringBoot合辑-初级篇