一:什么是内部类?
内部类,就是定义在另外一个类里面的类。与之对应,包含内部类的类被称作为外部类。
二:内部类的作用
内部类主要提供了三种作用:
1.内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其它类直接访问内部类。
2.内部类的方法可以直接访问外部类的所有数据。
3.内部类所实现的功能外部类同样可以实现,只不过有时实现内部类较为方便。
三:内部类的分类
内部类主要分为以下几种类型:
1.成员内部类
2.静态内部类
3.方法内部类
4.匿名内部类
接下来分别详细说明4种内部类型的用法。
四:成员内部类
直接在类中定义一个内部类,Out1.java
public class Out1{
private String outA = "outA";
private String outB = "outB";
class In1{
private String outB = "inB";
public void showIn(){
String outB = "methodB";
System.out.println("调用外部类中的outB: " + Out1.this.outB);
System.out.println("调用内部类实例变量outB: " + this.outB);
System.out.println("调用内部类中方法(本方法)的局部变量outB: " + outB);
}
}
public void showOut(){
System.out.println("在Out1外部类中访问内部类的数据outB: " + new In1().outB);
}
/**
* 测试main方法
* @param args
*/
public static void main(String[] args){
Out1 out = new Out1();
out.showOut();
//生成内部类的方法
Out1.In1 in = out.new In1();
in.showIn();
}
}
运行结果:
在Out1外部类中访问内部类的数据outB: inB
调用外部类中的outB: outB
调用内部类实例变量outB: inB
调用内部类中方法(本方法)的局部变量outB: methodB
总结:
1.程序编译后会出现两个.class文件,一个是Out1.class,另一个是Out1$In1.class.
2.上述代码生成内部类的方法Out1.In1 in = out.new In1();说明了必须先有外部类的对象才能有内部类,开头的Out1是为了标识此内部类在哪一个外部类中。
3.内部类在没有同名成员变量和局部变量的情况下,内部类会直接访问外部类的成员变量,而无需指定Out1.this.属性名
否则,内部类中的局部变量会覆盖外部类的成员变量,而访问内部类本身的成员变量可用this.属性名,访问外部类的成员变量需要使用Out1.this.属性名。
4.非静态成员内部类的成员不允许是static类型的。
注意:
还有一个比较特殊的成员内部类叫做私有成员内部类,既成员内部类被private修饰,此时此内部类只能被外部类的方法去操作,而不能在之外生成此内部类的对象。
五:静态内部类
静态内部类是被static修饰的内部类,Out2.java
public class Out2 {
private static String outA = "outA";
static class In2{
public void show(){
System.out.println(outA);
}
}
}
测试类Test.java
public class Test {
public static void main(String[] args) {
Out2.In2 in = new Out2.In2();
in.show();
}
}
运行结果:
outA
总结:
1.静态内部类中的方法只能访问外部类的静态变量。
2.静态内部类实例的创建不再依赖外部类的实例。
3.静态内部类的成员允许是static类型的。
六:方法内部类
见名知意,一个类在一个方法之中,见Out3.java
public class Out3 {
private String outA = "outA";
//注意下面两个final
public void test(final int a){
final int b = 2;
class In3{
public void show(){
System.out.println("输出传递进来的参数a: " + a);
System.out.println("输出外部类的实例变量outA : " + outA);
System.out.println("输出函数中的局部变量b: " + b);
}
}
new In3().show();
}
public static void main(String[] args) {
new Out3().test(1);
}
}
运行结果:
输出传递进来的参数a: 1
输出外部类的实例变量outA : outA
输出函数中的局部变量b: 2
总结:
1.方法内部类若想要访问方法中的局部变量,必须给变量声明为final类型。
2.方法内部类依旧能访问外部类的所有。
注意:
对于总结的第一条,解释如下:
内部类的生命周期和方法中的局部变量是不一样的,内部类是也是一个类,是存储在堆中,也只有当对该类的引用消失时,内部类才会消亡。而方法的局部变量是存储在堆栈中的,当调用结束时就会退栈,即在内存中这个属性就消失了。也就是说,内部类的生命周期超过了方法中局部变量的生命周期,内部类可能会调用到已经消失的属性,因此内部类不能访问方法中的局部变量。 解决方法就是在局部变量前加修饰符final ,此时局部变量就会存在堆中,生命周期跟工程的生命周期是一样的,此时内部类就可以访问方法中的局部变量
七:匿名内部类
匿名内部类也就是没有名字的内部类
正因为没有名字,所以匿名内部类只能使用一次,它通常用来简化代码编写
但使用匿名内部类还有个前提条件:必须继承一个父类或实现一个接口
通常,我们会这样写代码:
abstract class Parent {
public abstract void show();
}
public class Son extends Parent {
@Override
public void show() {
// TODO Auto-generated method stub
System.out.println("Hello Son");
}
}
public class Demo {
public static void main(String[] args) {
Parent p = new Son();
p.show();
}
}
输出结果:
Hello Son
引入匿名内部类后,我们可以这样改写代码(第一种语法形式)。
public class Demo {
public static void main(String[] args) {
new Parent(){
@Override
public void show() {
System.out.println("Hello Son");
}
}.show();
}
}
这种结果和上面的一样,这是形式有大大的变化,实际上上面这段代码的意思就是,声明一个Parent的子类并重写Parent的show()方法,然后创建一个该子类的实例然后调用其show()方法。由于声明的该Parent的子类没有名字,所以叫匿名类。又由于没有名字的类只能存在于一个类或者一个方法内部,所以又称为匿名内部类。
我们还可以这样写(第二种语法形式)
public class Demo {
public static void main(String[] args) {
Parent p = new Parent(){
@Override
public void show() {
System.out.println("Hello Son");
}
};
p.show();
}
}
这种和第一种唯一的区别是不是直接创建子类并调用其方法,而是声明一个该子类的父类引用p,然后通过该父类引用调用子类方法。创建完匿名类的实例后,没有立即执行show(),创建实例和执行实例的方法分开。
还有第三种写法(第三种语法形式)
public class Demo {
public static void main(String[] args) {
new Parent(){
@Override
public void show() {
System.out.println("Hello Son");
}
{
show();
}
};
}
}
实际上这种写法就是在匿名子类的类局部代码块中调用其类方法。 局部代码块内的语句是在创建该类的实例后由类加载器隐式立即执行的。
一般来说,匿名内部类的优点就是书写较为简单,实例化一个类后立即做一些事情,比较方便。
但它的缺点也很明显,总结两条:
1.每一个内部类的实例都会隐性的持有一个指向外部类的引用(静态内部类除外),这样一方面是多余的引用浪费。
2.另一方面当串行化这个子类实例时外部类也会被不知不觉的串行化,如果外部类没有实现serialize接口时,就会报错。