一、内部类
1、引入
类的成员包括:
(1)属性:成员变量;
(2)方法:成员方法;
(3)构造器;
(4)代码块;
(5)内部类;
其中1、2是代表这类事物的特征;
其中3、4是初始化类和对象用的;
其中5是协助完成2的功能的实现,表现;
2、内部类
当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内部类。
在Java中,允许一个类的定义位于另一个类的内部,前者称为内部类,后者称为外部类。
Inner class一般用在定义它的类或语句块之内,在外部引用它时必须给出完整的名称。
Inner class的名字不能与包含它的外部类类名相同;
3、分类
根据内部类的所在的位置不同:
(1)成员内部类:(static成员内部类和非static成员内部类)外部类中方法外;
(2)局部内部类:匿名内部类,方法体内(也可以在代码块内)
4、
二、成员内部类
1、概述及分类
成员内部类:定义在 类中方法外 的类。
(1)有 static 修饰的:静态成员内部类,简称 静态内部类;
(2)没有 static 修饰的:非静态成员内部类,简称 成员内部类;
思考:什么情况下会用到成员内部类(方法外声明的?)
当描述一个事物时,发现它的内部还有一个完整的结构需要用一个类来描述;
并且发现这内部的结构,如果独立存在是没有意义的,必须在这个外部类中才有意义。而且这个内部结构只为这个外部类服务。例如:Body身体,发现它内部还有完整的结果,如:心脏,发现心脏单独创建对象没有意义,只有在 Body 对象中才有意义,而且只为 Body 对象服务。
2、成员内部类的特点
(1)成员内部类作为类的成员的角色:
① 和外部类不同,Inner class 还可以声明为 private 或 protected;
② 可以调用外部类的结构;
③ Inner class 可以声明为 static 的,但此时就不能再使用外层类的非static 的成员变量;
(2)成员内部类作为类的角色:
① 成员内部类,可以出现原本类中能够定义的所有的成员:
属性:可以有静态属性和非静态属性;
方法:可以有静态方法和非静态方法,如果静态内部类是抽象类,还可定义抽象方法;
构造方法:有参方法、无参方法;
代码块:可以有静态代码块和非静态代码块;
内部类:允许,但很少再写内部类(臃肿)
② 可以声明为 abstract 类,因此可以被其他的内部类继承;
③ 可以声明为 final 类;
④ 编译以后生成 OuterClass$InnerClass.class 字节码文件(也适用于局部内部类)
(3)注意
① 非 static 的成员内部类中的成员不能声明为 static 的,只有在外部类或 static 的成员内部类才可声明 static 成员;
② 外部类访问成员内部类的成员,需要“内部类.成员” 或 “内部类对象.成员” 的方式;
③ 成员内部类可以直接使用外部类的所有成员,包括私有的数据;
④ 当想要在外部类的静态成员部分使用内部类时, 可以考虑内部类声明为静态的;
2、静态成员内部类(静态内部类)
(1)语法格式
【修饰符】 class 外部类 【 extends 父类】 【implements 父接口们】{
【其他修饰符】 static class 内部类 【 extends 父类】 【implements 父接口们】{
}
}
注意:
① 只有成员内部类才能用 static 修饰,其他的外部类,局部内部类等够不可以用 static 修饰。
② 外部类,内部类的父类,父接口都没有关系,各是各自的。
(2)特点
① 静态内部类中不能使用外部类的非静态的成员;
② 在外部类中,使用静态内部类,和使用其他的类一样的原则;
如果使用静态内部类的静态成员,直接“静态内部类名.”
如果使用静态内部类的非静态成员,直接“静态内部类对象名.”
③ 在外部类的外面,使用静态内部类:
如果使用静态内部类的静态成员,直接“类名”;
使用 外部类名.静态内部类名.静态方法;
使用 import 包.外部类名.静态内部类名; 然后在代码中使用“静态内部类名”
④ 静态内部类不会随着外部类的初始化一起初始化,而是要在使用到这个静态内部类才会初始化;
(3)小节
① 同级的来说静态的不能直接使用非静态的;
② 访问一个类的静态成员,用 “类名.” 即可;访问一个类的非静态成员,用“对象名.”即可;
③ 当使用到这个类时,这个类才会进行初始化;
(4)Demo:
1 package com.java.test;
2 //import com.java.test.Outer.Inner;
3
4 public class TestStaticInner {
5 public static void main(String[] args) {
6 // 访问内部类的静态方法
7 // Inner.test();//上面有导包语句,import 包.外部类名.静态内部类名;
8
9 Outer.Inner.test();//外部类名.静态内部类名.静态方法(不使用导包语句)
10
11 // 访问内部类的非静态方法
12 // Inner in = new Inner();//上面有导包语句,import 包.外部类名.静态内部类名;
13 Outer.Inner in = new Outer.Inner();
14 in.method();
15
16 Outer out = new Outer(); // 内部类不会初始化
17 out.outMethod(); // 该方法中用到内部类,内部类会进行初始化
18 }
19 }
20 class Outer{
21 private int i = 1;
22 private static int j = 2;
23
24 static{
25 System.out.println("外部类的静态代码块");
26 }
27
28 static class Inner{
29 static{
30 System.out.println("静态内部类的代码块");
31 }
32
33 public void method(){
34 System.out.println("静态内部类的非静态方法");
35 //System.out.println(i);//错误,不能访问非静态的
36 System.out.println(j);
37 }
38
39 public static void test(){
40 System.out.println("静态内部类的静态方法");
41 }
42 }
43 // 外部类访问内部类
44 public void outMethod(){
45 Inner in = new Inner();
46 in.method();//非静态方法,用对象名.访问
47
48 Inner.test();//静态方法,用类名.访问
49 }
50 }
(5)
3、成员内部类(非静态成员内部类)
(1)语法格式
【修饰符】 class 外部类 【 extends 父类】 【implements 父接口们】{
【其他修饰符】 class 内部类 【 extends 父类】 【implements 父接口们】{
}
}
(2)特点
① 在非静态内部类中,不能出现任何和 static 有关的声明;
② 在非静态内部类中,可以随意访问外部类的所有的成员,包括静态的和非静态的;
③ 在外部类的静态成员中,不能使用非静态的成员内部类;
④ 在外部类的外面使用
第一步:先创建外部类的对象
第二步:要么通过外部类的对象,去创建内部类的对象
Outer out = new Outer();
Outer.Inner in = out.new Inner();
要么通过外部类的对象,去获取内部类的对象
Outer out = new Outer();
Outer.Inner in = out.getInner();
(3)Demo
1 public class TestNonStaticInner {
2 public static void main(String[] args) {
3 //Outer.Inner in = new Outer.Inner();//错误的
4
5 //在这里使用Inner,因为此时的Inner是Outer的非静态成员,所以需要用到Outer的对象
6 Outer out = new Outer();
7 //Outer.Inner in = out.new Inner();
8 Outer.Inner in = out.getInner();
9 in.method();
10 }
11 }
12 class Outer{
13 private int i = 1;
14 private static int j = 2;
15
16 class Inner{
17 public void method(){
18 System.out.println("非静态内部类的非静态方法");
19 System.out.println(i);
20 System.out.println(j);
21 }
22 }
23
24 public static void outTest(){
25 //Inner in = new Inner();//静态的方法不能访问非静态的成员
26 }
27 public void outMethod(){
28 Inner in = new Inner();
29 in.method();
30 }
31
32 public Inner getInner(){
33 return new Inner();
34 }
35 }
(4)练习案例:创建一个外部类,里面有一个抽象内部类,并有一个抽象方法,要求写一个类去继承该内部类;
代码:
1 public class Test {
2 public static void main(String[] args) {
3 MySub my = new MySub(new Outer());
4 my.test();
5 }
6 }
7 class Outer{
8 abstract class Inner{
9 public abstract void test();
10 }
11 }
12 class MySub extends Outer.Inner{
13 MySub(Outer out){
14 out.super();//需要外部类的对象,才能调用非静态内部类的构造器
15 }
16
17 @Override
18 public void test() {
19 System.out.println("hello");
20 }
21 }
(5)小节
① 使用非静态内部类名时,可以使用:import 包.外部类名.内部类名; 或 外部类名.内部类名;
② 要调用非静态内部类的构造器,需要用到外部类的对象;
③ 因为子类的构造器的首行一定要调用父类的构造器,默认调用父类的无参构造(需要借助外部类对象);
④ 继承抽象内部类,要重写抽象类的抽象方法;
注意:内部类仍然是一个独立的类,在编译之后会内部类会被编译成独立的 .class 文件,但是前面冠以外部类的类名和 $ 符号。如:Person$Heart.class
三、局部内部类
1、概述
局部内部类:定义在类中方法中(方法内、代码块内、构造器内)的类,就是局部内部类。
即只有当前所属的方法才能使用它,出了这个方法外面就不能使用了。
2、分类
(1)有名字的局部内部类:简称 局部内部类;
(2)没名字的局部内部类:简称 匿名内部类。
3、语法格式
局部内部类定义格式:
【修饰符】 class 外部类名称 {
【修饰符】 返回值类型 外部类方法名称(参数列表) {
【修饰符】 class 有名字的局部内部类名称 {
// ...
}
}
}
说明:如果子类调用的是父类的无参构造,那么()中实参列表不用写;
如果子类调用的是父类的有参构造,那么()中传入实参列表;
定义一个类的时候,权限修饰符规则:
(1)外部类:public / default
(2)成员内部类:public / protected / default / private
(3)局部内部类:不加任何修饰符
4、特点
(1)局部内部类的修饰符,只能有 abstract 或 final;
(2)局部内部类有使用的作用域;
(3)如果局部内部类在静态方法中,不能使用外部类的非静态成员;
(4)在局部内部类中,可以使用当前局部内部类所在方法的局部变量,但是要求,这个局部变量必须是 final的常量。
5、扩展
在Java8时,如果某个局部变量被局部内部类使用了,会自动添加final变为常量,一旦变为常量,它的值就不能修改了。
为什么它要这么要求?给局部变量加 final呢?
避免局部内部类对象被返回到外部类的外面使用时,访问不到这个局部变量,所以要把这个局部变量变为final的常量。(常量在方法区中)
原因:
① new出来的对象在堆内存当中。
② 局部变量是跟着方法走的,在栈内存当中。
③ 方法运行结束之后,立刻出栈,局部变量就会立刻消失。
④ 但是new出来的对象会在堆当中持续存在,直到垃圾回收消失。
Demo:
1 public class TestLocalInner {
2 public static void main(String[] args) {
3 Outer out = new Outer();
4 Father in = out.test();//在外部类的外面虽然不能使用局部内部类,但是可以得到它的对象
5 System.out.println(in.getClass());
6 in.method();//在这里仍然可以访问到这个a,那么这个a就不能存在栈中,得挪到方法区,变为常量
7 }
8 }
9 abstract class Father{
10 public abstract void method();
11 }
12 class Outer{
13 private int i = 1;//成员变量,实例变量,非静态成员变量
14 private static int j = 2;//成员变量,类变量,静态变量
15
16 public Father test(){
17 //Inner in = new Inner(); 现在还没有声明该类,不能使用
18
19 final int a = 10;//局部变量==>局部的常量
20
21 //局部内部类
22 class Inner extends Father{
23 public void method(){
24 System.out.println(i);
25 System.out.println(j);
26 System.out.println(a);
27 }
28 }
29
30 Inner in = new Inner();
31 in.method();
32
33 return in;
34 }
35
36 public void method(){
37 //Inner in = new Inner(); 超过了作用域
38 }
39
40 public static void fun(){
41 //局部内部类
42 class Inner{
43 public void method(){
44 //System.out.println(i);//是因为fun方法是静态的
45 System.out.println(j);
46 }
47 }
48 }
49 }
6、
四、匿名内部类【重要】
1、概述
匿名内部类:是内部类的简化写法。它的本质是一个 带具体实现的 父类或者父接口的 匿名的 子类对象。
匿名内部类不能定义任何静态成员、方法和类,只能创建匿名内部类的一个实例。一个匿名内部类一定是在 new 的后面,用其隐含实现一个接口或实现一个类。
开发中,最常用到的内部类就是匿名内部类了,以接口为例,当你使用一个接口时,都得做如下几步操作:
(1)定义子类
(2)重写接口中的方法
(3)创建子类对象
(4)调用重写后的方法
而匿名类就可以把以上四步合成一步。
前提:匿名内部类必须继承一个父类或者实现一个父接口
2、语法格式
定义格式:
new 父类名或者接口名(){
// 方法重写
@Override
public void method() {
// 执行语句
}
};
特殊:声明匿名内部类与创建它的对象是一起完成的,即匿名内部类只有唯一的对象。
匿名内部类的特点:
(1)匿名内部类必须继承父类或实现接口;
(2)匿名内部类只能有一个对象;
(3)匿名内部类对象只能使用多态形式引用;
3、使用案例
以接口为例,匿名内部类的使用。
定义接口:
1 public abstract class FlyAble{
2 public abstract void fly();
3 }
创建匿名内部类,并调用:
1 public class InnerDemo {
2 public static void main(String[] args) {
3 /*
4 1.等号右边:是匿名内部类,定义并创建该接口的子类对象
5 2.等号左边:是多态赋值,接口类型引用指向子类对象
6 */
7 FlyAble f = new FlyAble(){
8 public void fly() {
9 System.out.println("我飞了~~~");
10 }
11 };
12 //调用 fly方法,执行重写后的方法
13 f.fly();
14 }
15 }
通常在方法的形式参数是接口或者抽象类时,也可以将匿名内部类作为参数传递。如下所示
1 public class InnerDemo2 {
2 public static void main(String[] args) {
3 /*
4 1.等号右边:定义并创建该接口的子类对象
5 2.等号左边:是多态,接口类型引用指向子类对象
6 */
7 FlyAble f = new FlyAble(){
8 public void fly() {
9 System.out.println("我飞了~~~");
10 }
11 };
12 // 将f传递给showFly方法中
13 showFly(f);
14 }
15 public static void showFly(FlyAble f) {
16 f.fly();
17 }
18 }
将以上两步,也可以简化为一步:
1 public class InnerDemo3 {
2 public static void main(String[] args) {
3 /*
4 创建匿名内部类,直接传递给showFly(FlyAble f)
5 */
6 showFly( new FlyAble(){
7 public void fly() {
8 System.out.println("我飞了~~~");
9 }
10 });
11 }
12 public static void showFly(FlyAble f) {
13 f.fly();
14 }
15 }