一,内部类概述
1.1定义:
定义在其他类内部的类被称为内部类,包含内部类的类也被称为外部类。
2.1,特点:
(1)内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类。
(2)内部类成员可以直接访问外部类的私有数据,因为内部类被当成其外部类成员,同一个类的成员之间可以互相访问。
(3)匿名内部类适合用于创建那些仅需要一次使用的类。
(4)成员内部类的class文件格式为:OuterClass$InnerClass.class。
3.1,内部类的分类
(1)成员内部类(非静态内部类,静态内部类)
(2)局部内部类
(3)匿名内部类
二,非静态内部类
2.1,非静态内部类与外部类的访问规则
(1)非静态内部类能访问外部类的实例成员,也能访问外部类的类成员。
(2)外部类不能直接访问非静态内部类的成员,如果需要访问,则必须显式创建非静态内部类对象来调用
(1)(2)的原因:
非静态内部类对象必须寄存在外部类对象里,而外部类对象则不必一定有非静态内部类对象寄存在其中。就是说存在一个非静态内部类对象,则一定存在一个被它寄存的外部类对象。所以非静态内部类能访问外部类的成员。但是,外部类对象访问非静态内部类成员时,可能非静态内部类对象根本不存在!所以,外部类不能直接访问非静态内部类的成员。除非有内部类对象的引用。
(3)根据静态成员不能访问非静态成员的规则,外部类的静态方法,不能访问非静态内部类(包括创建非静态内部类对象)。
2.2非静态内部类方法内访问某个变量
当在非静态内部类的方法内访问某个变量时,系统优先在该方法内查找是否存在该名字的局部变量,如果存在则使用该成员变量;
如果不存在则到该方法所在的内部类查找是否存在该名字的成员变量,如果存在则使用该成员变量;
如果不存在,则到该内部类所在的外部类中查找 是否存在该名字的成员变量,如果存在则使用该成员变量,如果依然不存在,系统出现编译错误。
注:如果外部类成员变量,内部类成员变量与内部类方法里方法的局部变量同名,则可通过使用this,外部类类名.this作为限定来区分。
实例:
class Outer
{
private String s = "外部类的实例变量";
class Inner
{
private String s = "内部类的实例变量";
public void info()
{
String s = "局部变量";
//通过 外部类名.this.varName访问外部类的实例变量
System.out.println("外部类的Field值:"+Outer.this.s);
//通过this.varName访问外部类实例变量
System.out.println("内部类的Field值:"+this.s);
//直接访问局部变量
System.out.println("局部变量的值:"+s);
}
}
}
2.3,非静态内部类不能有静态成员。
三,静态内部类
3.1定义:
如果使用static来修饰一个内部类,则这个内部类就属于外部类本身,而不属于外部类的某个对象。因此使用static修饰的内部类被为静态内部类。
3.2静态内部类与外部类的访问规则
(1)根据静态成员不可以访问非静态成员的规则,静态内部类不能访问外部类的实例成员,只能访问外部类的类成员,即使是静态内部类的实例方法也不能访问外部类的实例成员
原因:因为静态内部类是外部类的类相关,而不是外部类的对象相关的。即静态内部类对象不是寄存在外部类对象里的,而是寄存在外部类的类本身中。静态内部类对象里只有外部类的类引用。如果允许静态内部类的实例方法访问外部类的实例成员,但找不到被寄存的外部类对象,这将引起错误。
(2)外部类依然不能直接访问静态内部类的成员,但可以使用静态内部类的类名作为调用者来访问静态内部类的类成员,也可以使用静态内部类对象作为调用者来访问静态内部类的实例成员。
实例:
class Outer
{
static class Inner
{
private static int p = 3;
private int p2 = 4;
}
public void info()
{
//System.out.println(p);
//上面代码出现错误,应改为如下形式
//通过类名访问静态内部类的类成员
System.out.println(Inner.p);
//System.out.println(p2);
//上面代码出现错误,应改为如下形式
//通过实例访问静态内部类的实例成员
System.out.println(new Inner().p2);
}
}
四,在外部类以外使用内部类
4.1,在访问控制符对应的访问权限使用内部类。
(1)如果想要在外部类以外使用非静态内部类,则内部类不能使用private访问控制权限。
(2)省略访问控制符的内部类,只能被与外部类处于同一个包中的其他类所访问。
(3)使用protected修饰的内部类,可悲与外部类处于同一个包中的其他类和外部类的子类所访问。
(4)使用public修饰的内部类,可以在任何地方被访问。
4.2在外部类以外定义内部类
OuterClass.InnerClass varName
4.3在外部类以外创建非静态内部类实例
OuterInstance.new InnerConstructor()
原因:因为非静态内部类的对象必须寄存在外部类的对象里,因此在 创建非静态内部类实例必须使用外部类实例和new来调用非静态内部类的构造器。
即非静态内部类的构造器必须使用外部类对象来调用。
扩展:如果需要在外部类以外创建非静态内部类的子类,怎么办?
首先,我们知道当创建一个子类时,子类构造器总会调用父类的构造器,而非静态内部类的构造器必须通过其外部类对象来调用。
因此在创建非静态内部类的子类时,必须存在一个外部类对象。
实例:
class SubClss extends Outer.inner
{
public SubClass(Outer out)
{
//通过传入的Outer对象显式调用Inner的构造器。
//out代表外部类对像
//super代表调用Inner类的构造器
out.super("java");
}
}
4.4创建内部类和创建子类的区别:
当创建非静态内部类Inner类的对象时,必须用过Outer对象来调用new关键字;
当创建SubClass类的对象时,必须使用Outer对象作为调用者来调用In类的构造器。
4.5创建静态内部类对象格式
new OuterClass.InnerConstructor()
五,局部内部类
5.1定义:
如果把一个内部类放在方法里定义,则这个内部类就是一个局部内部类,局部内部类仅在该方法里有效。
5.2,局部内部类不能使用访问控制符和static修饰符修饰。
原因:因为对于局部成员来说,他们的上一级成员单元是方法,而不是类,使用static修饰他们没有任何意义;局部成员的作用域是所在方法,其它程序单元永远也不可能访问到另一个方法的局部成员,所以不能使用访问控制符修饰。
5.3访问规则
(1)可以直接访问外部类中的成员,因为还持有外部类的引用。
(2)不可以访问它所在的局部中的变量,只能访问final修饰的局部变量。
原因:因为生命周期的不同。方法中的的局部变量,方法结束后,这个变量就要释放掉。final保证这个变量始终指向一个对象。
首先,内部类和外部类其实是处于同一个级别,内部类不会因为定义在方法中就会随着方法的执行完毕而被销毁。
问题就来了,如果外部类的方法中的变量不定义final,那么当外部类方法执行完毕的时候,这个局部变量也就被GC了。然而内部类的某个方法还没执行完,它所引用的局部变量已经找不到了。如果定义为final,java会将这个变量赋值一份作为成员变量内置于内部类中,这样的话,由于final修饰的值始终无法改变,所以这个变量所指向的内存区域就不会变。
class Outer
{
int x = 3;
void method(final int a)//a也是局部变量
{
//a++,会有问题,因为不能再操作它了
final int y = 4;//从内部类中访问局部变量需要被声明为最终类型。
class Inner
{
void function()
{
//只能访问被final修饰的局部变量
System.out.println(y);
}
}
new Inner().function();
}
}
class InnerClassDemo3
{
public static void main(String[] args)
{
Outer out = new Outer();
out.method(7);
out.method(8);//final修饰的变量不是不能改吗?不会出现错误,因为,method()方法是在栈内存中,
//参数a也是,当out.method(7);栈内存的a被释放。所以不会出现错误
}
}
七,匿名内部类
1,匿名内部类其实就是内部类的简写格式。
2,定义匿名内部类,匿名内部类必须继承一个父类,或实现一个接口,但最多只能继承一个父类或实现一个接口。
3,定义匿名内部类的格式
new 父类构造器(参数列表)|实现接口(){定义类体部分}
4,匿名内部类不能是抽象类,因为系统在创建匿名内部类时,会立即创建匿名内部类的对象,因此不能定义成抽象类
5,匿名内部类不能定义构造器,因为匿名内部类没有类名。
实例:创建一个实现接口的匿名内部类
interface Person
{
public String getName();
public int getAge();
}
class InnerTest
{
public void sop(Person p)
{
System.out.println("姓名:"+p.getName()+"........"+"年龄:"+p.getAge());
}
public static void main(String[] args)
{
InnerTest it = new InnerTest();
//调用sop方法时,需要传入一个Person参数
//可以传入其匿名内部类的实例
it.sop(new Person()
{
public String getName()
{
return "LISi";
}
public int getAge()
{
return 44;
}
});
}
}
注: 当通过实现接口在创建匿名内部类时,匿名内部类也不能显式创建构造器,因此匿名内部类只有一个隐式的无参构造器,故new接口名后的括号里不能传入参数值。
但如果通过集成父类来创建匿名内部类时,匿名内部类将拥有和父类相似的构造器,此处的相似指的是拥有相同的形参列表。