2017-11-04 21:58:46
内部类概述:把类定义在其他类的内部,这个类就叫做内部类。
内部类作用: 1.每个内部类都能独立的继承一个接口的实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。内部类使得多继承的解决方案变得完整
2.方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏
3.方便编写事件驱动程序
4.方便编写线程代码
5.内部类的存在使得Java的多继承机制变得更加完善
内部类的访问特点:内部类可以直接访问外部类的成员,包括私有;
外部类要访问内部类的成员必须创建对象;
内部类的分类:成员内部类(成员位置)
局部内部类(函数内部)
静态内部类
匿名内部类
- 成员内部类
访问成员内部类格式:外部类名.内部类名 对象名 = 外部类对象.内部类对象
成员内部类访问修饰符:跟成员变量一样没有作用域修饰符限制(创建依赖于外部类,即在外部类中如果要访问成员内部类成员时,需要先创建外部类对象,通过外部类对象引用来访问内部类对象),创建成员内部类方式:new OutterClass().new InnerClass();
一般需要加上private进行保护,但是这样就不能在外部访问了,但是可以在类内提供一个成员方法进行访问。
class OuterClass{ private int num = 10; class Inner{ public void show(){ System.out.println(num); } } } public class InnerClass { public static void main(String[] args) {
// 创建方法 OuterClass.Inner inner = new OuterClass().new Inner(); inner.show(); } }
成员内部类重名访问问题的解决:
/** * 依次输出30,20,10 */ class OuterClass{ private int num = 10; class Inner{ private int num=20; public void show(){ int num=30; System.out.println(num); System.out.println(this.num); System.out.println(OuterClass.this.num); } } } public class InnerClass { public static void main(String[] args) { OuterClass.Inner inner = new OuterClass().new Inner(); inner.show(); } }
内部类是很少用来作为继承用的。但是当用来继承的话,要注意两点:
1)成员内部类的引用方式必须为 外部类名.内部类名
2)构造器中必须有指向外部类对象的引用,并通过这个引用调用super()
class WithInner{ class Inner{} } public class InheritInner extends WithInner.Inner{ InheritInner(WithInner wi){ wi.super(); //wi的父类是object } public static void main(String[] args){ WithInner wi = new WithInner(); InheritInner ii = new InheritInner(wi); } }
而进一步,当被继承的内部类只有非默认构造器时应该怎么办呢?
class WithInner{ class Inner{ public Inner(int i){ System.out.println(i); } } } public class InheritInner extends WithInner.Inner{ InheritInner(WithInner wi){ int i=0; wi.super(i);//如代码所示,当被继承的构造器需要参数时,应把参数传递给这个super函数 } public static void main(String[] args){ WithInner wi = new WithInner(); InheritInner ii = new InheritInner(wi); } }
我们可以看出要想创建Inner的对象必须先创建WithInner的对象之后才能创建Inner对象,那么现在你要用一个类InheritInner继承Inner类,在继承过程中构造方法会被调用,即使你不写也会调用默认构造方法,但问题出现了,在调用父类Inner构造方法时找不到WithInner的对象,所以就必须给InheritInner类的构造方法传入WithInner对象再通过wi.super();方法调用Inner的默认构造方法,因为这是创建对象的基本流程,所以这句话wi.super();是必须的。
- 静态内部类
访问成员内部类格式:外部类名.内部类名 对象名 = 外部类名.内部类对象
静态内部类访问修饰符:跟静态变量一样没有作用域修饰符限制,创建静态内部类方式:new OutterClass.InnerClass();
被静态修饰的成员内部类只能访问外部类的静态成员
内部类方法被静态修饰后的方法可以是静态方法,也可以是非静态方法
class OuterClass{ private static int num = 10; static class Inner{ public void show(){ System.out.println(num); } } } public class InnerClass { public static void main(String[] args) { OuterClass.Inner inner = new OuterClass.Inner(); inner.show(); } }
- 局部内部类
局部内部类的访问修饰符:跟局部变量一样不能有作用域修饰符
局部内部类的访问特点:局部内部类可以直接访问外部类的成员
在局部位置,可以创建内部类对象,通过对象可以调用内部类的方法,来使用局部类功能
局部内部类访问局部变量必须是final修饰的
局部变量使用final修饰的原因:局部变量是随着方法的调用儿调用的,随着调用完毕而消失(所以局部变量不能声明为static);
而堆内存的内容不会立即消失,所以,我们需要加上final修饰符;
在加入fianl修饰符后,这个变量就变成了常量,编译器会进行优化,类似于C++里的宏变量,会直接将所有出现这个final的地方用它的值进行简单替换。
class Outer { int num =2; public void method() { final int num2 = 3; class Inner { System.out.println(num); System.out.println(num2); } } }
- 匿名内部类
匿名内部类:其实就是内部类的简化写法,省略了类名。好处是匿名内部类在使用完后就会被回收。
前提:存在一个类或者接口(这里的类可以是具体类也可以是抽象类)
匿名内部类访问修饰符:无修饰符
匿名内部类的本质:是一个继承了该类或者实现了该接口的实现类的对象
匿名内部类的缺陷:
1)它仅能被使用一次,创建匿名内部类时它会立即创建一个该类的实例,该类的定义会立即消失,所以匿名内部类是不能够被重复使用
2)匿名内部类中是不能定义构造函数的
3)匿名内部类中不能存在任何的静态成员变量和静态方法
4)匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法
5)匿名内部类为局部内部类,所以局部内部类的所有限制同样对匿名内部类生效
匿名内部类的格式:
new 类名或接口名(){ 重写方法; }
匿名内部类在访问局部变量的时候也需要是final类型,但是,局部内部类和匿名内部类访问的局部变量必须由final修饰,java8开始,可以不加final修饰符,由系统默认添加。java将这个功能称为:Effectively final 功能。
interface Interdemo { public void show(); } class OuterClass { public void method() { //final int num = 2; int num = 2; Interdemo interdemo = new Interdemo() { @Override public void show() { System.out.println(num); } }; interdemo.show(); } } public class InnerClass { public static void main(String[] args) { OuterClass outerClass = new OuterClass(); outerClass.method(); } }
匿名内部类初始化:
我们一般都是利用构造器来完成某个实例的初始化工作的,但是匿名内部类是没有构造器的!那怎么来初始化匿名内部类呢?使用构造代码块!利用构造代码块能够达到为匿名内部类创建一个构造器的效果。Java中匿名内部类的双括号初始化,形如:
// 新建一个列表并赋初值A、B、C ArrayList<String> list = new ArrayList<String>() {{ add("A"); add("B"); add("C"); }}; // 新建一个Map并给初值 HashMap<Character, Integer> map = new HashMap<Character, Integer>(){ { put('I', 1); put('V', 5); put('X', 10); put('L', 50); put('C', 100); put('D', 500); put('M', 1000); } };
这种方法被称为双大括号初始化(double brace initialization)或者匿名内部类初始化法,实际上是一种取巧的方式。
这里以ArrayList的例子解释,首先第一层花括号定义了一个继承于ArrayList的匿名内部类 (Anonymous Inner Class):
// 定义了一个继承于ArrayList的类,它没有名字 new ArrayList<String>(){ // 在这里对这个类进行具体定义 };
第二层花括号实际上是这个匿名内部类实例初始化块 (Instance Initializer Block)(或称为非静态初始化块):
new ArrayList<String>(){ { // 这里是实例初始化块,可以直接调用父类的非私有方法或访问非私有成员 } };
我们通过new得到这个ArrayList的子类的实例并向上转型为ArrayList的引用:
ArrayList<String> list = new ArrayList<String>() {{}};
- 我们得到的实际上是一个ArrayList的子类的引用,虽然这个子类相比ArrayList并没有任何功能上的改变。
- 可以认为这是个本身装有数据的子类(因为它的数据来自于自身的初始化),而不是取得引用后再赋值。
- 非静态内部类的对象会隐式强引用其外围对象,所以在内部类未释放时,外围对象也不会被释放,从而造成内存泄漏。