说起内部类,大多数人都知道但却不怎么用,常规使用中,最常用到的也就是匿名内部类,所以下面会理一理各种内部类的相关知识及用法。
内部类的定义
Java中,类通常作为一个独立的程序单元。但在某些情况下,把将一个类定义在另一个类中,这就叫做内部类,而包含了内部类的类就叫做外部类。
内部类的主要作用与注意点
- 内部类隐藏在外部类中,别的类无法轻易访问,提供了更好的封装。
- 内部类属于外部类成员,可以直接访问外部类的成员属性和方法(包括私有数据),但外部类却不能直接访问内部类成员。
- 内部类可以使用private、protected来控制访问权限;使用static修饰。反之,外部类不能使用这些修饰符。
- 有时候某些方法只用一次,没必要单独创建类,这时就可以采用匿名内部类。
- 如果非静态内部类,是不能定义静态成员的,就好像在普通代码块中无法定义静态一样。
上面只是简要说明,但内部类又分为成员内部类、静态内部类、匿名内部类和方法内部类(局部内部类)。各自的应用场景、使用方法和访问权限皆有不同之处,在下面,将会对四个内部类的要点进行分析,附带实例。
成员内部类
public class Demo01 {
private int a = 20;
public static int b = 20; //静态变量
private void testInstance() {System.out.println("外部类实例方法");}
private static void testStatic() {System.out.println("外部类静态方法");}
//打印a b 值
void printAttribute() {
System.out.println("外部类:" + "a:" + a + " , b:" + b);
}
//成员内部类,权限默认,可被同类、同包访问
class InnerClass{
private int a = 10;
// public static void test() {}//error
//访问外部类成员
public void changeOutside() {
a = a - 10;
b = b - 10;
//能访问外部类静态方法和实例
testInstance();
testStatic();
System.out.println("内部类:" + "a:" + a + " , b:" + b);
}
}
//返回一个内部类对象
public InnerClass getInnerClass(){
return new InnerClass();
}
}
//测试
public class Test {
public static void main(String[] args) {
Demo01 d = new Demo01();
//创建内部类对象
Demo01.InnerClass ic = d.new InnerClass();
ic.changeOutside();
d.printAttribute();
}
}
//输出结果:
外部类实例方法
外部类静态方法
内部类:a:0 , b:10
外部类:a:20 , b:10
成员内部类实例解析
- 从实例来看,创建内部类对象时需要依托于外部类对象的存在,因为成员内部类在类中等同于一个成员,所以需要外部类对象才能对其访问。
- 在上面的changeOutside()方法中,因为成员内部类等同于成员,所以能访问所有外部类成员,包括静态。但是不能创建静态成员和方法。
- 如果成员内部类和外部类的属性名相同,那么默认使用内部类的属性,如同上面a、b值一样。如果要访问外部类同名属性,用this来指定。
//创建内部类对象
Demo01.InnerClass ic = d.new InnerClass();
System.out.println(ic.getClass());
//输出结果:
class com.InnerClass.test.Demo01$InnerClass
根据上面代码,可以看到内部类的class文件就是上面所打印的结构,从名字的命名也能看出内部类和外部类的关系。静态内部类
public class outClass {
private int insVar = 555;
private String a = "外部A"; //同名变量,用于测验
private static int staticVar = 333;
//静态内部类
static class InnerClass{
//能创建静态变量和方法
private static String a = "内部A";
public static void test() {
//访问同名变量
System.out.println("a: " + a);
System.out.println("外部类 a: " + new outClass().a); //访问外部类同名实例变量,this不能使用
//访问外部静态变量
System.out.println("外部类 staticVar : " + staticVar);
}
}
}
//测试
public static void main(String[] args) {
InnerClass inn = new InnerClass();//可以直接创建
inn.test();
//输出结果:
a: 内部A
外部类 a: 外部A
外部类 staticVar : 333
}
静态内部类实例解析
- 静态内部类能够直接创建对象,不像成员内部类依附于对象而存在,这说明静态内部类依附于类本身,而非对象。
- 静态内部类不能有非静态的成员/方法,如果要引用外部类实例变量/方法,可以采用外部对象.变量/方法的形式来引用。
- 遇到同名变量和方法时,不能像成员内部类一样用this来区分,依旧需要使用外部对象来调用。
- 为什么不允许访问实例变量/方法呢?从类加载的过程来看,静态内部类依附于类,而使用实例变量/方法需要对象,所以直接引用外部实例会引发错误。
匿名内部类
- 匿名内部类就是没有名字的内部类,相当于简写的内部类。既然没名字,自然也就找不到,访问修饰符就无意义。
- 创建匿名内部类时,会立即创建一个该类实例,然后该类就被销毁了。
- 匿名内部类必须是继承一个抽象类或者实现接口,然后匿名内部类就可以进行重写。
- 匿名内部类只能用一次,不能重复调用。
- 创建匿名内部类和对象差不多,但实现内容是放在里面,可以理解成带内容的对象。
实例1
public class outClass {
public static void main(String[] args) {
//常规执行方法形式
new Test().start();
}
}
//用一个线程来重复打印i
class Test extends Thread {
@Override
public void run() {
for(int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName() + " , i:" + i);
}
}
}
上面实例使用常规方式:定义类->定义方法->调用方法,但如果这方法只用一次,用完就不管了,那么去创建类来定义方法不仅显得很麻烦,而且还占空间,所以匿名内部类就是为了解决该类问题而用。改用匿名内部类如下://使用匿名内部类,实现同样的功能,但是代码量大大降低。
new Thread() {
@Override
public void run() {
for(int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName() + " , i:" + i);
}
}
}.start();
实例2
interface Command {
void processArr(int[] arr);
}
public class outClass {
public static void main(String[] args) {
int[] arr = {24,5,12,3,34};
dealArr(arr, new addCommand());
dealArr(arr, new getMaxCommand());
}
//使用命令模式,根据相应命令来处理数组
public static void dealArr(int[] arr, Command cmd) {
cmd.processArr(arr);
}
}
//累计数组之和
class addCommand implements Command {
@Override
public void processArr(int[] arr) {
int sum = 0;
for(int i = 0; i < arr.length; i++) {
sum += arr[i];
}
System.out.println("数组总和:" + sum);
}
}
//得到数组最大值
class getMaxCommand implements Command {
@Override
public void processArr(int[] arr) {
int sum = arr[0];
for(int i = 1; i < arr.length; i++) {
if(arr[i] > sum) {
sum = arr[i];
}
}
System.out.println("数组最大值:" + sum);
}
}
实例2使用了简单的命令模式来处理数组,但从上面可以看出,代码量很多。因为只使用一次,所以改用匿名内部类,如下: //为了简化,所以省略一些不必要的代码,只保留关键代码
dealArr(arr, new Command() {
@Override
public void processArr(int[] arr) {
//累计数组和的代码
}
});
dealArr(arr, new Command() {
@Override
public void processArr(int[] arr) {
//得到数组最大值的代码
}
});
同上面比较,减少了创建两个类的代码量,代码显得简洁多了。但也有缺点,那就是方法过多时就没必要使用匿名内部类了。就像使用匿名内部类最频繁的绑定事件,方法最多2、3个,也是这个道理。局部内部类
方法内部类也叫作局部内部类,可以定义在方法和作用域中,例如普通代码块、for循环、while循环中,局部内部类的访问仅限于方法内或者该作用域内,这意味着不能被public、protected、private和static修饰。
由于方法内部类很少使用,所以实在没什么好例子,下面就以一个动态代理的例子来演示一下:
interface USB{
void connect(); //连接
}
//实现类
class Kingson implements USB{
@Override
public void connect() {
System.out.println("金士顿USB开始连接");
}
}
class methodClass {
private Object target;
//对指定对象进行代理,并返回代理对象
public Object getProxy(Object target){
this.target = target;
Object proObj = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
new InvocationHandler() { //看起来像是在方法中使用匿名内部类....
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("打开电脑USB接口");
method.invoke(target, args);
System.out.println("记录USB接口连接信息");
return null;
}
});
return proObj; //返回生成的代理对象
}
}
//测试类
public class Test {
public static void main(String[] args) {
Kingson ks = new Kingson();
USB u = (USB)new methodClass().getProxy(ks);
u.connect();
//输出结果:
打开电脑USB接口
金士顿USB开始连接
记录USB接口连接信息
}
}
上面这个实例估计看不出局部内部类有啥特点...我也没办法,因为局部内部类实在是基本用不到,上面这个勉强体现了把类放在方法中,尽管使用的匿名内部类。关于内部类的个人感想
从上面对四种内部类使用来看,内部类本质上就是对类的一种简化,所以内部类能做的事,直接创建类也一定能做。所以无需纠结什么地方该用内部类,什么地方不用,再极端无外乎就是多创建一个类,如果不够就两个。当然,能简便就简便,但没必要过于追求极端化,最终都是为了解决问题,所以怎么顺手怎么来。