------- android培训、java培训、期待与您交流! ----------
1. 内部类概述
内部类,顾名思义,就是将一个类定义在另一个类的内部,也称为内置类、嵌套类。我们来看一个例子,
代码1:
class Outer
{
/*
在成员位置上定义一个内部类
内部类在形式上与其他类没有什么区别
同样可以在内部定义成员变量和成员方法
当然也可以定义多个内部类
*/
class Inner
{
public void f()
{
System.out.println("Innerf() run");
}
}
public void f()
{
System.out.println("Outerf() run");
}
}
内部类在形式上与一般的类没有区别,同样可以在内部定义成员变量和成员方法,只不过是定义在了另一个类的内部。上述代码1中Outer类中的Inner类就是内部类的一个最简单的形式。
2. 内部类的意义
那么为什么要定义内部类呢?通常,一个类A要访问另一个类B的成员,首先要在A类内部创建一个B类对象,然后才能进行成员访问。如果类B定义在类A的内部,就可以直接访问A类的成员,就像上述代码1中Inner类的f()方法。
我们周围的很多事物本身是由其他事物组合而成的,也就是说事物内部还有事物,比如电脑是一种事物,而电脑内部的配件(如CPU、显卡等)也是事物,再比如,大学是一种事物,而大学内部的各个学院也是一类事物,学院内的各个系又是一类事物,那么在事物内部的事物就可以通过内部类的形式进行描述。
那么,可能会有朋友说,也可以把事物内部的事物单独进行描述,没有必要定义在内部啊?这是因为,内部事物通常都会直接去“访问”它所在外部类的一些“属性”和“方法”,通俗点儿说就是,内部事物总会和外部事物进行直接的沟通。如果将内部事物定义在外面,同时还要与它所属的外部事物进行沟通,就要先获取到外部事物对象,而这是不符合现实规律的。
此外,大多数情况下,无论出于安全还是保密等等原因,内部事物通常是不能被外界直接访问的,所以内部事物还应该被“私有”,并在外部类定义内部类的访问方式。
3. 内部类的分类
内部类可以分为4种,分别是:成员内部类、静态内部类、方法内部类和匿名内部类。我们就按这个顺序一一说明。
4. 成员内部类
成员内部类,顾名思义,就是定义在了外部类(内部类所属的那个类)成员位置上的类。为了表述方便,这里的成员内部类我们都简称为内部类。
1) 成员内部类的访问规则
代码2:
class Outer
{
private int x = 5;
class Inner
{
public void f()
{
System.out.println("Innerf() run");
//内部类可以直接访问外部类的私有成员变量,成员方法同理
System.out.println("Outer.x= "+x);
}
}
public void f()
{
System.out.println("Outerf() run");
//外部类若要访问内部类的成员,必须先创建内部类的对象
Inner in = new Inner();
System.out.println("Inner.x"= in.x);
}
}
class InnerClassDemo
{
public static void main(String[] args)
{
//直接创建内部类对象,并访问内部类成员
Outer.Inner in = new Outer().new Inner();
System.out.println(in.x);
in.f();
}
}
根据代码2的例子,我们做出如下总结:
1) 内部类可以直接访问外部类的成员,包括私有成员,就像Inner类中的f()方法。2) 外部类若要访问内部类的成员,必须要创建内部类的对象,就像Outer类中的f()方法。
3) 通过Outer.Innerin = new Outer().new Inner()语句可以在外部其他类(除内部类和内部类所在外部类以外的类)直接创建内部类对象,就像主函数的语句,通用格式就是:
外部类名.内部类名引用名 = 外部类对象.内部类对象
我们首先来看赋值语句的左边,必须要标明成员内部类的所属,避免因多个类定义了同名内部类而造成混乱;而关于赋值语句的右边,若要获得内部类对象就必须先要获得外部类对象,这是符合一般的思维逻辑的,就好比想吃牛肉,就必须要先有牛。
小知识点1:
通常,在实际开发中很少使用第三个规则创建成员内部类对象,这是因为内部类如果定义在成员位置上一般都会被定义为私有,这样一来是不能被直接访问到的。注意,只有内部类可以被定义为私有,一般的类是不能的。
2) 成员内部类访问原理
代码3:
class Outer
{
private int x = 1;
class Inner
{
private int x = 2;
public void f()
{
int x = 3;
//访问局部变量
System.out.println("Inner.f().x= "+x);
//访问内部类成员变量
System.out.println("Inner.x= "+this.x);
//访问外部类成员变量
System.out.println("Outer.x= "+Outer.this.x);
}
}
}
class InnerClassDemo
{
public static void main(String[] args)
{
//直接创建内部类对象
Outer.Inner in = new Outer().new Inner();
in.f();
}
}
运行结果为:
Inner.f().x = 3
Inner.x = 2
Outer.x = 1
第一个输出语句表明成员内部类访问变量的规则符合“就近原则”,也就是说首先参考局部位置,然后是内部类成员位置,最后是外部类成员位置。第二个输出语句中的this表示本类对象——内部类对象。第三个输出语句中“Outer.this”的意思是外部类的本类对象,那么这里的this就不再指代内部类本类对象了。第二三个输出语句中的标识符不是必须要显示标明的,之所以这里显示标明是因为,上例中在局部位置、内部类成员位置和外部类成员位置上都定义了同名变量,所以需要标明各自的所示以示区分。
那么上述例子也就解释了内部类可以直接访问外部类成员的原因。非静态方法的访问规则也是一样的。
5. 静态内部类
上面我们说到,成员内部类通常会被定义为私有,也就是说处在成员位置上的内部类可以被所有成员修饰符所修饰,这其中也包括static,那么被static修饰的成员内部类就是静态内部类。
1) 静态内部类的访问规则
代码4:
class Outer
{
//只有该外部类成员变量被定义为静态时才能被静态内部类访问
private static int x = 1;
static class Inner
{
public void f()
{
//静态内部类只能访问静态的外部类成员
System.out.println("Outer.x= "+x);
}
public static void f_2()
{
System.out.println("Innerf_2() run");
System.out.println("Outer.x= "+x);
}
}
}
class InnerClassDemo2
{
public static void main(String[] args)
{
/*
在外部其他类直接创建静态内部类成员对象的格式:
外部类名.内部类名引用名 = new 外部类名.内部类对象
*/
Outer.Inner in = new Outer.Inner();
//通过内部类对象访问静态内部类非静态方法
in.f();
//直接通过内部类名访问静态内部类静态方法
Outer.Inner.f_2();
}
}
运行结果为:
Inner f() run
Outer.x = 1
Inner f_2() run
Outer.x = 1
规则一:当内部类被静态修饰后,只能直接访问外部类中的静态成员,这同样是因为静态成员只能访问静态成员,出现了访问限制。当然这里是说不能直接访问,而不是说不能访问,还是可以通过创建外部类对象来访问其静态成员。
规则二:在外部其他类直接创建静态内部类成员对象的格式:
外部类名.内部类名引用名 =new 外部类名.内部类对象
同其他静态成员一样,不需要创建外部类对象,只需要通过外部类名就可以访问到静态内部类。
规则三:在外部其他类直接访问静态内部类静态成员的格式:
外部类名.内部类名.静态成员
注意: 1) 当内部类中定义了静态成员,那么该内部类必须是静态的。
2) 外部类中的静态方法也只能访问静态内部类。
2) 何时定义静态内部类
与其他静态成员类似,当某个类内部不需要处理对象特有数据的时候,就可以将成员内部类定义为静态成员内部类,简称静态内部类。但是,因为其特有的访问局限性,实际开发中很少会用到。
6. 方法内部类
内部类不仅可以定义在成员位置上,还可以定义在局部位置,比如成员方法内部,就成为方法内部类。那么方法内部类就不能再被诸如private和static等成员修饰符修饰了,同样,方法内部类中的方法也不能被成员修饰符修饰。
下面代码中注释部分的标号和代码之后的编号一一对应。
代码5:
class Outer
{
private int x = 10;
voidf(final int a)
{
final int y = 5;
class Inner//11111
{
private void f()//22222
{
System.out.println("Outer.x= "+Outer.this.x);//33333
System.out.println("Outer.f().y= "+y);//55555
System.out.println("Outer.f().a= "+a);
}
}
Inner in = new Inner();//44444
in.f();
}
}
class InnerClassDemo3
{
public static void main(String[] args)
{
Outer out = new Outer();
out.f(2000);
out.f(321);//66666
}
}
运行结果为:
Outer.x = 10
Outer.f().y = 5
Outer.f().a = 2000
Outer.x = 10
Outer.f().y = 5
Outer.f().a = 321
1) 在外部类成员方法内部定义的内部类,称为方法内部类。方法内部类由于并不处于成员位置所以不能被成员修饰符修饰,比如private和static。其次,方法内部类可以定义在外部类的任意局部位置,甚至在循环语句中,但是,这样做没有什么意义,因为类被加载一次就可以了
2) 由于方法内部类不能被修饰为静态,所以方法内部类的成员方法也不能被修饰为静态。
3) 如果不会引起混淆(比如方法内部类的局部位置、成员位置和外部类的成员位置),Outer.this可以省略不写
4) 如果想要调用方法内部类的成员方法f(),需要先创建内部类对象,否则仅仅是调用外部类的f()方法是不会自动调用方法内部类的f()方法的,这只是进行了方法内部类的加载动作。注意:方法中的语句是按顺序执行的,所以应该先定义方法内部类,然后再创建方法内部类的对象,并调用方法,否则,由于找不到指定的方法内部类,编译会失败。
5) 方法内部类只能访问被final修饰的外部类局部变量,同样,若要访问方法内部类所在方法的参数,那么该参数也要被final修饰。
6) 这里两次调用f()方法并传入不同的实际参数,看起来是对final变量a(参数)进行了二次赋值,但实际上并不是这样。这是因为:当我们第一次调用f()的方法的时候,就会在栈内存中为f()方法开辟一块空间,也就是所谓的“进栈”了。此时属于该内存区f()方法的形式参数a的值就被锁定为2000,然后f()方法执行完毕后,该方法所占内存就被释放了。然后第二次调用f()方法,重复上述过程,而此时的参数a就是另一个变量了,这时候新a就被锁定为了321。所以这两个参数a之间没有任何关系。
7. 匿名内部类
匿名内部类其实就是方法内部类的简写格式——最直观的特点就是没有类名。
1) 定义匿名内部类的前提
匿名内部类必须是继承一个类或实现一个接口。我们可以这样理解,比如汽车这类事物内部有引擎这一类事物,而无论是什么类型的汽车,其内部都必须有一个引擎,这时候我们就可以将引擎单独抽象出来对其进行抽象描述,反映到代码上就是定义一个抽象外部类,其内部定义一些抽象方法,然后由汽车类的引擎内部类继承并复写这些抽象方法。
2) 匿名内部类的定义与创建格式
我们就以上述汽车与引擎为例,来说说匿名内部类是如何从一般方法内部类转化而来的。
代码6:
//定义抽象引擎类
abstract class AllEngine
{
abstract public void start();
}
//定义轿车类
class Sedan
{
//轿车引擎内部类继承自抽象引擎类
class SedanEngine extends AllEngine//第一步
{
//复写了抽闲引擎类的start()方法
public void start()//第二步
{
System.out.println("转动钥匙,启动");
}
}
public void drive()
{
//创建轿车引擎对象,并调用start()方法
SedanEngine se = new SedanEngine();//第三步
se.start();//第四步
}
}
class InnerClassDemo4
{
public static void main(String[] args)
{
Sedan s = new Sedan();
s.drive();
}
}
运行结果为:
转动钥匙,启动
那么上述过程我们简单总结为四步:1) 继承外部类(或实现接口);2) 将外部类的方法复写;3) 创建内部类对象;4) 调用方法。这四步与代码中注释部分四步一一对应。匿名内部类就是将上述四步简化到一个代码块中完成。转化以后的代码如下:
代码7:
abstract class AllEngine
{
abstract public void start();
}
class Sedan
{
public void drive()
{
//匿名内部类
new AllEngine()
{
public void start()
{
System.out.println("转动钥匙,启动");
}
}.start();
}
}
class InnerClassDemo5
{
public static void main(String[] args)
{
Sedan s = new Sedan();
s.drive();
}
}
运行结果为:
转动钥匙,启动
那么匿名内部类的定义、创建对象以及方法的调用过程是这样的:
1) 同样通过new关键字创建对象,但是匿名内部类没有类名,所以就使用父类名创建对象,而这个对象是父类的子类对象;
2) 在这个例子中父类是抽象类,所以不能创建对象,因此要通过后面大括号来表明这是父类的子类,并在大括号内定义子类的内容。这里要注意,即使父类不是抽象类,匿名内部类的定义方式也是一样的;
3) 方法的复写与一般的格式相同;
4) 通过直接在大括号后面添加匿名内部类的方法名进行调用,之所以可以这么调用是因为大括号前面的内容不仅是对匿名内部类的定义,更重要的是它首先是一个父类的匿名子类对象。
那么我们通过上述的过程发现,匿名子类的定义和子类对象的创建是通过上述1、2、3步糅合在一起完成的,这样大大简化了代码,但是阅读性也随之下降。
匿名内部类的定义与创建格式就是:
new 父类名或接口名()
{
//定义子类的内容
}
3) 多态在匿名内部类中的应用
我们先来看下面的代码,
代码8:
abstract class Demo
{
abstract public void f_1();
}
class Outer
{
public void test()
{
//通过多态的形式创建匿名子类对象
Demo d = new Demo()
{
public void f_1()
{
System.out.println("Innerf_1 run");
}
public void f_2()
{
System.out.println("Innerf_2 run");
}
};
d.f_1();
//无法调用匿名子类的特有方法,因为父类中没有定义,编译失败
//d.f_2();
}
}
class InnerClassDemo6
{
public static void main(String[] args)
{
Outer out = new Outer();
out.test();
}
}
运行结果为:
Inner f_1 run
Outer类中的test方法内定义了一个抽象外部类的匿名子类,并通过多态的方式创建了你名字类对象,覆盖了父类Demo的方法f_1,又定义了一个特有方法f_2。然后通过父类引用d调用了f_1方法(子父类的共性方法),但是由于父类中并没有定义f_2方法,因此按照多态中子父类方法的特性,编译失败。
由此,我们也可以总结出:定义匿名内部类的目的就是为了继承外部父类,覆盖某一个方法,并一次性的进行调用,以此来简化代码。
4) 匿名内部类的优缺点
优点:
大幅度简化书写。
缺点:
(a) 阅读性较差。匿名内部类与一般类的定义与创建格式有很大不同。
(b) 如果父类的方法过多,不能使用匿名内部类,一般方法数目不超过3个。否则阅读性同样很差,而且调用方法不方便,需要多态。
(c) 不能定义子类的特有方法。因为没有类名,即使可以通过多态的形式创建子类对象,也无法“向下转型”。
5) 何时使用匿名内部类
当我们定义了某个方法,调用该方法的时候需要传入一个对象(该对象可以是某类的子类对象或者接口的子类对象),而且该对象所属类的父类中只有少于3个的方法。通常的做法是先定义一个类(无论是外部类还是内部类),继承父类或实现接口,复写方法,然后创建该类的对象,并传入方法中,就像下面的代码:
代码9:
//定义父类
class Super
{
public void f()
{
System.out.println("Super f()run");
}
}
//子类继承父类,并复写f()方法
class Sub extends Super
{
public void f()
{
System.out.println("Subf() run");
}
}
class InnerClassDemo7
{
public static void main(String[]args)
{
//创建子类对象,并将该对象传入method方法
Sub sub= new Sub();
method(sub);
}
publicstatic void method(Super sup)
{
sup.f();
}
}
运行结果为:
Sub f() run
现在我们学习了匿名内部类,就可以大大简化代码书写。就像下面的代码,
代码10:
// 定义父类
class Super
{
public void f()
{
System.out.println("Superf() run");
}
}
class InnerClassDemo8
{
public static void main(String[] args)
{
/*
通过匿名内部类的形式创建父类Super的子类对象
并直接传入method,简化代码
*/
method(newSuper()
{
public void f()
{
System.out.println("Subf() run");
}
});
}
public static void method(Super sup)
{
sup.f();
}
}
运行结果为:
Sub f() run
对比上下两种方法,很明显第二种方式更为简便。当然匿名内部类的弊端和限制也是很明显的。注意,千万区分清楚代码10中调用method方法的最后一行代码——“});”,大括号表示子类内容的结束;小括号表示参数列表的结束;分号表示语句的结束。
6) 匿名内部类练习
题目1:补足代码,使得代码可以执行。
interface Inter
{
public abstract void method();
}
class Test
{
//补足代码。使用匿名内部类。
}
class InnerClassTest
{
public static void main(String[]args)
{
Test.function().method();
}
}
分析:
(1) 主函数中用类名Test调用了方法function,表明Test类中定义了一个静态成员方法function。
(2) 通过类名调用了function方法后,紧接着又调用了一个method方法。这里只有可能是通过某个对象调用的,因此可以推断function方法返回了一个包含method成员方法的对象。
(3) 上述对象由于包含了与接口Inter同名的方法method,而且题目中提示使用匿名内部类,因此可以推断该对象应该是Inter接口的子类对象。
(4) 最后需要确定function方法的返回值类型。既然该方法需要返回Inter接口的子类对象,那么返回值类型就只能是Inter。这里面就又涉及到了多态。题目中主函数的语句等同于下述写法:
Inter in = Test.function();
in.method();
代码:
代码11:
interface Inter
{
public abstract void method();
}
class Test
{
public static Inter function()
{
return new Inter()
{
public void method()
{
System.out.println(“运行成功”);
}
};
}
}
class InnerClassTest
{
public static void main(String[] args)
{
Test.function().method();
}
}
运行结果为:
运行成功
题目2:在没有事先定义父类的情况下,通过匿名内部类的方式创建对象并调用方法。
分析:实际上所有类都有一个隐式的根父类就是——Object类。因此,我们可以通过匿名内部类的方式创建Object类的子类对象。
代码:
代码12:
class InnerClassTest3
{
public static void main(String[] args)
{
/*
通过匿名内部类的方式创建了一个Object类的子类对象
并在其中定义了子类的特有方法f(),并调用了该方法
*/
new Object()
{
public void f()
{
System.out.println("Subof Object f() run");
}
}.f();
}
}
运行结果为:
Sub of Object f() run
再次请大家注意,如果想要调用子类的特有方法就不要通过多态的形式创建匿名内部类。因为,父类Object类中没有定义子类的特有方法,无法通过编译。