书接上文:Java之成员内部及静态类
3 局部内部类
在外部类方法中定义的内部类,称为局部内部类,其作用范围只是在当前方法中。
局部内部类是最不常用的一种内部类,了解即可。
定义格式:
[修饰符] class 外部类 {
//省略...
//成员方法
[修饰符] 返回值类型 方法名(形式参数列表) {
//功能实现省略...
//局部内部类定义
class 成员内部类名 {
0或多个数据成员
0或多个构造方法
0或多个成员方法
}
//注意:局部内部类的作用范围在当前方法中,只能在该方法中使用
}
}
注意事项:
-
局部内部类只能在定义的方法中使用
-
在局部内部类方法中操作它外部定义的局部变量,则该局部变量默认为final修饰
案例展示:
package com.briup.chap07.test;
//外部类
class Outer3 {
private int num = 10;
//包含局部内部类的方法
public void innerFun() {
//下面两行效果一样
//int num = 20;
final int num = 20;
//在方法内部,定义局部内部类
class Inner {
private int i_num = 30;
public void test() {
System.out.println("局部变量num: " + num);
System.out.println("内部类成员变量this.i_num: " + this.i_num);
System.out.println("外部类成员变量Outer3.this.num: " + Outer3.this.num);
//方法中声明的局部变量,只要在内部类中使用,默认会加上final修饰
//所以下面一行 编译会报错:给final变量赋值
//num = 60;
}
}
//创建局部内部类对象
Inner inn = new Inner();
//访问内部类私有成员
System.out.println(inn.i_num);
System.out.println("-------------");
//调用内部内方法
inn.test();
}
}
//测试类
public class Test033_Local {
public static void main(String[] args) {
Outer3 outer = new Outer3();
outer.innerFun();
}
}
运行效果:
面试:JDK8中,如果在局部内部类中访问:外部类方法中定义的局部变量,那么这个局部变量会默认加上final修饰,其值不可以改变。
4 匿名内部类
匿名内部类,是一种没有名字的内部类,本质上是一个特殊的局部内部类(定义在方法内部)。
在今后的课程中,匿名内部类使用最多(必须掌握)。
常规接口、抽象类的操作步骤:
- 声明一个类,去实现这个接口,或去继承抽象父类
- 重写所有抽象方法
- 用接口或引用去指向子类或实现类对象
- 通过接口或抽象类引用去调用重写的方法
在这个过程中,我们的核心任务是重写抽象方法,最后再调用这些重写的方法。所以上述过程过于复杂,使用匿名内部类,可以大大简化上述步骤!
匿名内部类书写格式:
父类或接口类型 变量名 = new 父类或接口(构造方法实参列表) {
// 重写所有的抽象方法
@Override
public 返回值类型 method1(形参列表) {
方法体实现
}
@Override
public 返回值类型 method2(形参列表) {
方法体实现
}
//省略...
};
//匿名内部类对象调用方法
变量名.重写方法(实参列表);
匿名内部类的俩种形式:
- 利用父类,进行声明并创建匿名内部类对象,这个匿名内部类默认就是这个父类的子类型
- 利用接口,进行声明并创建匿名内部类对象,这个匿名内部类默认就是这个接口的实现类
匿名内部类注意事项:
- 匿名内部类必须依托于一个接口或一个父类(可以是抽象类,也可以是普通类)
- 匿名内部类在声明的同时,就必须创建出对象,否则后面就没法创建了
- 匿名内部类中无法定义构造器
1)匿名内部类实现接口案例
定义ISleep
接口,然后创建匿名内部类对象,用它调用方法。
package com.briup.chap07.test;
//定义接口
interface ISleep {
void sleep();
}
// 匿名内部类基础测试
public class Test034_Interface {
public static void main(String[] args) {
//1.正常写法【局部内部类写法】
class SleepImpl implements ISleep {
@Override
public void sleep() {
System.out.println("躺着睡");
}
}
ISleep s1 = new SleepImpl();
s1.sleep();
System.out.println("------------");
//2.简化写法【匿名内部类写法】
ISleep s2 = new ISleep() {
@Override
public void sleep() {
System.out.println("趴着睡");
}
};
s2.sleep();
System.out.println("------------");
//3.进一步简化写法
// 匿名内部类对象直接调用抽象方法
new ISleep() {
@Override
public void sleep() {
System.out.println("水里面睡");
}
}.sleep();
}
}
//运行效果:
躺着睡
------------
趴着睡
------------
水里面睡
2)匿名内部类实现抽象类案例
package com.briup.chap07.test;
//定义抽象类
abstract class MyThread {
//抽象方法
public abstract void run();
//普通方法
public void test() {
System.out.println("in Mythread,test ...");
}
}
public class Test034_Abstract {
public static void main(String[] args) {
//1.普通写法
MyThread th = new MyThread() {
@Override
public void run() {
System.out.println("重写 run1");
}
};
th.run();
th.test();
System.out.println("--------------");
//2.简化写法
new MyThread() {
public void run() {
System.out.println("in run2 ...");
}
}.run();
//注意,匿名对象只能使用一次,因为没有名字,无法再次访问
}
}
//运行效果:
重写 run1
in Mythread,test ...
--------------
in run2 ...
3)匿名内部类对象使用父类构造方法案例
package com.briup.chap07.test;
abstract class Animal3 {
private String name;
public Animal3() {}
public Animal3(String name) {
this.name = name;
}
public String getName() {
return name;
}
//抽象方法
public abstract void eat();
public abstract void sleep();
}
public class Test034_Constructor {
public static void main(String[] args) {
//实例化匿名内部类对象
Animal3 a = new Animal3() {
//必须重写所有的抽象方法
@Override
public void eat() {
//注意:匿名内部类属于子类,在重写方法中可通过super关键字访问父类方法或成员
System.out.println(super.getName() + " 喜欢吃鱼");
}
@Override
public void sleep() {
System.out.println(super.getName() + " 睡觉");
}
};
//父类引用调用重写方法
a.sleep();
a.eat();
System.out.println("---------------");
//实例化子类对象,调用父类构造器对父类进行初始化
new Animal3("小汤姆") {
//必须重写所有的抽象方法
@Override
public void eat() {
//注意:匿名内部类属于子类,在重写方法中可通过super关键字访问父类方法或成员
System.out.println(super.getName() + " 喜欢抓老鼠吃");
}
@Override
public void sleep() {
System.out.println(super.getName() + " 睡觉");
}
}.eat();
}
}
//运行效果:
null 睡觉
null 喜欢吃鱼
---------------
小汤姆 喜欢抓老鼠吃
内部类总结
内部类应用场景选择:
-
考虑这个内部类,是否需要反复的进行多次使用(必须有名字)
在这个内部类中,如果需要定义静态的属性和方法,选择使用静态内部类
在这个内部类中,如果需要访问外部类的非静态属性和方法,选择使用成员内部类
-
考虑这个内部类,如果只需要使用一次(可以没有名字)
选择使用匿名内部类
-
局部内部类,几乎不会使用