内部类定义
定义:将一个类的定义放在另一个类的定义内部。
内部类允许把一些逻辑相关的类组织在一起,并控制内部类的可视性。内部类不单只是一种代码隐藏机制。还可以与外部类通信。内部类看起来有些奇怪,需要花些时间才能在设计中轻松使用它们。
为什么需要内部类
内部类实现一个接口和外部类实现这个接口有什么区别了?答案是:后者不是总能享用到接口带来的方便,因为有时需要用到接口的实现。
1:使用内部类最吸引人的原因是:每个内部类都能独立的继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
2:内部类能访问创建它的外围对象的所有成员,所以可以认为内部类提供了某种进入其外围类的窗口。
3: 接口和内部类为我们提供了一种将接口和实现分离的更加结构化的方法。内部类提供的可以继承多个具体的或抽象的类的能力,可以有效的实现“多重继承”,解决接口不能解决的部分问题。
如果不需要解决”多重继承“问题,自然可以使用别的编码方式。但如果使用内部类,还可以获取一些其他的特性:
1:内部类可以有多个实例,每个实例都有自己的状态信息,与其外部类对象相互独立。
2:可以让多个内部类以不同形式实现同一个接口,或继承同一个类。
3:内部类并没有令人迷惑的”is-a“关系,它就是一个独立的类,比如说集合类里的迭代器。
内部类的分类
- 成员内部类
成员内部类是最普通的内部类,它可以无条件的访问外部类的成员属性和方法。
但是在拥有外部类对象前是不可能创建内部类对象的。成员内部类对象会暗暗的连接到创建它的外部类对象上,也就是隐式的保存一个指向外部类对象的引用。所以创建内部类对象必须使用外部类对象来创建。(使用.new来创建,如下)
如果你需要生成对外部类对象的引用,可以使用外部类的名字.this。这样产生的引用自动具有正确的类型,并且在编译期就被知晓,因此没有任何运行时开销。
public class DotNew {
void f(){
System.out.println("outer");
}
public class Inner{
public DotNew outer(){
return DotNew.this;
}
}
public static void main(String[] args){
DotNew dn = new DotNew();
DotNew.Inner inner = dn.new Inner();
inner.outer().f();
}
}
- 局部内部类
局部内部类是定义在方法和作用域内的内部类。就和局部变量一样。这么做通常有两个理由:
1:实现了某类型的接口,于是可以创建并返回对其的引用。
2:你要解决一个复杂的问题,想创建一个类来辅助你的解决方案,但是又不希望这个类是公共可用的。
如下代码在方法的作用域里创建了一个内部类,PDestination类是方法的一部分而不是类Test的一部分。所以在方法外面是不能访问PDestination。
public class Test {
public Destination getDestination(String s){
class PDestination implements Destination{
private String labal;
private PDestination(String whereTo){
labal = whereTo;
}
@Override
public String readLabel() {
return labal;
}
}
return new PDestination(s);
}
}
- 匿名内部类
匿名内部类使用的比较多的地方就是在事件监听等场合。匿名内部类相当于是直接使用new出来的引用。如果定义一个匿名内部类并且希望他使用一个在其外部定义的对象,那么编译器会要求其参数引用是final的。如果你想做一些类似构造器的行为,就需要通过实例初始化来实现(因为匿名内部类中不可能有命名构造器)。
匿名内部类与正规继承相比有些受限,因为匿名内部类既可以扩展类,也可以实现接口。但是不能两者兼备,并且实现接口也只能实现一个接口。
new 父类构造器(参数列表)|实现接口()
{
//匿名内部类的类体部分
}
public class Test {
public void test(Bird bird){
bird.fly();
}
public static void main(String[] args){
Test test = new Test();
test.test(new Bird() {
{
System.out.println("类似构造器行为放这里");
}
@Override
public void fly() {
System.out.println("I can Fly");
}
});
}
}
为什么匿名内部类只能访问局部final变量?
考虑生命周期。含有匿名内部类的代码会被编译为两个class文件,Outer.class和Outerx.class(x为正整数1,2,3等)
当f()方法执行完了,变量不存在了,Thread对象就没办法访问到了。Java采用复制的方式来解决这一问题。也就是编译器会在匿名内部类的常量池中添加一个内容相等的字面量或将相应的字节码嵌入到执行码中,也就是说内部类使用的是一个值和方法中局部变量相等的另一个局部变量,是和方法中的局部变量独立开的。
同样的,f方法中的形参b也会拷贝进来。
如果局部变量的值在编译器间就可以确定,则直接在匿名内部类里面创建一个拷贝。如果局部变量的值无法在编译器确定,则通过构造器传参的方式来对拷贝进行初始化赋值。
那么如果在run方法中改变参数的值,就会造成数据不一致,所以变量就必须限制为final变量。
public class Test {
public void f(final int b){
final int test = 1;
new Thread(){
@Override
public void run() {
System.out.println(b);
System.out.println(test);
}
}.start();
System.out.println("outer");
}
public static class Tester{
public static void main(String[] args){
Test t = new Test();
t.f(2);
}
}
}
- 静态内部类
使用static修饰一个内部类,则这个类就属于外部类本身而不属于外部类的对象。所以静态内部类通常意味着:
1:创建静态内部类对象不需要外围类对象。
2:不能从静态内部类中访问外部类的实例成员。
普通内部类里面是不能有static成员的,也不能嵌套静态内部类。但是静态内部类可以有这些。
/**
* 每个类都带一个main方法来测试类的话,缺点就是必须带着这些额外编译的代码。
* 使用静态内部类来实现,会生成一个独立的类,比如说测试时候直接运行 java Test$Tester即可,
* 发布的时候删除Test$Tester.class类即可。
*/
public class Test {
public void f(){
System.out.println("f()");
}
public static class Tester{
public static void main(String[] args){
Test t = new Test();
t.f();
}
}
}
内部类的继承
因为内部类的构造器必须连接到指向其外围类对象的引用,所以在继承内部类的时候,那个指向外围类对象的秘密引用必须要被初始化,而在导出类中不再存在可连接的默认对象。要解决这个问题,必须使用特殊的语法来明确说清他们之间的关联。
/**
* Test只继承自内部类,但是当要生成一个构造器时,默认的构造器并不算好,
* 而且不能只是传递一个指向外围类对象的引用。此外,必须在构造器内使用如下语法:
* enclosingClassReference.super();
*/
public class Test extends WithInner.Inner{
Test(WithInner withInner){
withInner.super();
}
public static class Tester{
public static void main(String[] args){
WithInner withInner = new WithInner();
Test t = new Test(withInner);
}
}
}
class WithInner{
class Inner{}
}