内部类介绍
内部类是定义在类体、方法体、甚至比方法体更小的代码块内部的类。
如果A类需要直接访问B类中的成员,而B类又需要建立A类的对象。这时,为了方便设计和访问,直接将A类定义在B类中。就可以了。A类就称为内部类。
内部类可以直接访问外部类中的成员。而外部类想要访问内部类,必须要建立内部类的对象。
一般来说,Java有四中内部类:静态内部类、成员内部类、局部内部类、匿名内部类。
当内部类定义在外部类中的成员位置上,可以使用一些成员修饰符修饰 private、static。
内部类编译后的文件名为:“外部类名$内部类名.java”;
为什么使用内部类?
每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
在我们程序设计中有时候会存在一些使用接口很难解决的问题,这个时候我们可以利用内部类提供的、可以继承多个具体的或者抽象的类的能力来解决这些程序设计问题。可以这样说,接口只是解决了部分问题,而内部类使得多重继承的解决方案变得更加完整。
其实使用内部类最大的优点就在于它能够非常好的解决多重继承的问题,但是如果我们不需要解决多重继承问题,那么我们自然可以使用其他的编码方式,但是使用内部类还能够为我们带来如下特性:
1)内部类可以用多个实例,每个实例都有自己的状态信息,并且与其他外围对象的信息相互独立。
2)在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或者继承同一个类。
3)创建内部类对象的时刻并不依赖于外围类对象的创建。
4)内部类并没有令人迷惑的“is-a”关系,他就是一个独立的实体。
5)内部类提供了更好的封装,除了该外围类,其他类都不能访问。
为什么内部类可以直接访问外部类中的成员呢?
那是因为内部中都持有一个外部类的引用。这个是引用是 外部类名.this 。
内部类可以定义在外部类中的成员位置上,也可以定义在外部类中的局部位置上。
当内部类被定义在局部位置上,只能访问局部中被final修饰的局部变量。
静态内部类
声明在类体部,方法体外,并且使用static修饰的内部类。
由于static内部类不具有任何对外部类实例的引用,因此static内部类中不能使用this关键字来访问外部类中的实例成员,但是可以(直接)访问外部类中的static成员(包含私有)。
如果内部类中定义了静态成员,那么该内部类必须是静态的。
构建静态内部类的实例可以脱离外部类的实例独立创建:
1)在外部类的外部构建内部类的实例
new Outer.Inner();
2)在外部类的内部构建内部类的实例
new Inner();
我们来看看下面的例子:
public class StaticInnerTest {
public static void main(String[] args) {
// 在外部类的外部构建内部类的实例
StaticOuter.StaticInner si = new StaticOuter.StaticInner();
si.test2(); // 调用内部类的成员方法
StaticOuter.StaticInner.test(); // 调用内部类的静态方法
System.out.println("si.b = " + si.b); // 访问外部类的私有静态成员变量
System.out.println("si.a = " + si.a); // 访问外部类的私有成员变量
//System.out.println("StaticOuter.b = " + StaticOuter.b); 这里报错
}
}
class StaticOuter {
private int a = 10;
private static int b = 15;
public static void test(){
System.out.println("Outer static test ...");
}
public void test2(){
System.out.println("Outer instance test ...");
}
static class StaticInner {
public int a = 20;
static int b = 30;
public static void test(){
System.out.println("Inner static test ...");
}
public void test2(){
System.out.println("Inner instance test ...");
StaticOuter.test(); // 调用外部类的静态方法
new StaticOuter().test2(); // 调用外部类的成员方法
System.out.println(StaticOuter.b); // 访问外部类的似有静态变量
}
}
}
执行结果:
Inner instance test ...
Outer static test ...
Outer instance test ...
15
Inner static test ...
si.b = 30
si.a = 20
成员内部类(实例内部类)
没有使用static修饰的内部类。
在成员内部类中不允许出现静态变量和静态方法的声明。static只能用在静态常量的声明上。
成员内部类中可以访问外部类中所有的成员(变量,方法),包含私有成员。
构建成员内部类的实例,要求必须外部类的实例先存在:
1) 外部类的外部/外部类的静态方法:
这种应用不多见,因为内部类之所以定义在内部就是为了封装。想要获取内部类对象通常都通过外部类的方法来获取。这样可以对内部类对象进行控制。
new Outer().new Inner(); // 这种直接实例化的形式很少用。
2)外部类的实例方法:
new Inner();
this.new Inner();
下面来看个例子:
public class MemberInnerTest {
public static void main (String args []){
// 这种直接实例化的形式很少用
MemberOuter.MemberInner mm = new MemberOuter().new MemberInner();
mm.test2();
MemberOuter mo = new MemberOuter();
mo.setS1("");
// 推荐使用getxxx()来获取成员内部类,尤其是该内部类的构造函数无参数时
MemberOuter.MemberInner mi = mo.getMemberInner();
mi.test2(); // 实例化对象后调用public方法
}
}
class MemberOuter{
private String s1 = "Outer Instance s1";
private String s2 = "Outer Instance s2";
public void setS1(String s1) {
this.s1 = s1;
new MemberOuter().new MemberInner();
this.new MemberInner(); // 此时MemberOuter已经实例化完成,所以可以使用this
new MemberInner().test2();
}
public static void test2 (){
new MemberOuter().new MemberInner();
// static 是在MemberOuter构造器前使用,所以此时this不能使用
// this.new MemberInner(); // 此时MemberOuter没有实例化完成,所以不可以使用this
}
public MemberInner getMemberInner(){
MemberInner memberInner = new MemberInner();
return memberInner;
}
class MemberInner{
String s1= "Inner Instance s1";
static final String s4 = "static final MemberInner";
void test2(){
System.out.println("s1 = " + s1);
System.out.println("MemberOuter.this.s1 = " + MemberOuter.this.s1);
System.out.println("s2 = " + s2);
}
}
}
执行结果:
s1 = Inner Instance s1
MemberOuter.this.s1 = Outer Instance s1
s2 = Outer Instance s2
s1 = Inner Instance s1
MemberOuter.this.s1 =
s2 = Outer Instance s2
s1 = Inner Instance s1
MemberOuter.this.s1 =
s2 = Outer Instance s2
局部内部类
定义在方法体,甚至比方法体更小的代码块中。类比局部变量。
局部内部类是所有内部类中最少使用的一种形式。
局部内部类可以访问的外部类的成员根据所在方法体不同:
1)如果在静态方法中:可以访问外部类中所有静态成员,包含私有;
2)如果在实例方法中:可以访问外部类中所有的成员,包含私有。
局部内部类可以访问所在方法中定义的局部变量,但是要求局部变量必须使用final修饰。
public class LocalInnerTest {
private int a = 1;
private static int b = 2;
public void test() {
final int c = 3;
class LocalInner { // 定义在成员方法里
public void add1() {
// 在实例方法中可以访问外部类中所有的成员,包含私有
System.out.println("a = " + a);
System.out.println("b = " + b);
// 访问所在方法中定义的局部变量,要求局部变量必须使用final修饰。
System.out.println("c = " + c);
}
}
new LocalInner().add1();
}
static public void test2() {
final int d = 5;
class LocalInner2 { // 定义在静态方法里
public void add1() {
// 在静态方法中不可以访问外部类中所有非静态成员
// System.out.println("a = " + a);
// 在静态方法中可以访问外部类中所有静态成员,包含私有
System.out.println("b = " + b);
System.out.println("d = " + d);
}
}
new LocalInner2().add1();
}
private void test3(boolean b){
if(b){
class LocalInner3{ // 定义在作用域内
private String id;
LocalInner3(String str) {
id = str;
}
String getId(){
return id;
}
}
LocalInner3 li3 = new LocalInner3("zwj");
System.out.println("id = " + li3.getId());
}
}
public void test3(){
test3(true);
}
public static void main(String args[]) {
// LocalInnerTest() lc = new LocalInnerTest();
new LocalInnerTest().test2();
new LocalInnerTest().test();
new LocalInnerTest().test3();
}
}
执行结果如下:
b = 2
d = 5
a = 1
b = 2
c = 3
id = zwj
匿名内部类:
定义类的最终目的是创建一个类的实例,但是如果某个类的实例只是用一次,则可以将类的定义与类的创建,放到与一起完成,或者说在定义类的同时就创建一个类,以这种方法定义的没有名字的类称为匿名内部类。
匿名内部类其实就是一个匿名子类对象。
没有class、interface、implements、extends关键字,没有构造器。
想要定义匿名内部类:需要前提,内部类必须继承一个类或者实现接口。
匿名内部类中可以包含:字段、方法、实例初始化代码、本地类。
匿名内部类特点:
1)它必须继承一个类或者实现一个接口,而不能显示的使用extends或者implements,没有父类。
2)匿名内部类没有构造方法。通过new<父类名> 创建对象,匿名内部类定义与创建对象是同时进行的。
3)匿名内部类只能一次性的创建,并有父类句柄持有。
4)匿名内部类不需要初始化,只有默认的构造方法。
匿名内部类的格式:
// 匿名类是不能有名称的类,所以没办法引用它们。必须在创建时,作为new语句的一部分来声明它们
new 父类构造器(参数列表)| 接口(){
// 匿名内部类的主体
}
这种形式的new语句声明一个新的匿名类,它对一个给定的类进行扩展,或者实现一个给定的接口。它还创建那个类的一个新实例,并把它作为语句的结果而返回。
匿名内部类可以继承一个类或实现一个接口,但不能同时实现一个接口和继承一个类,也不能实现多个接口。如果实现了一个接口,该类是Object类的直接子类,匿名类继承一个类或实现一个接口,不需要extends和implements关键字。
要扩展的类和要实现的接口是new语句的操作数,后跟匿名类的主体。如果匿名类对另一个类进行扩展,它的主体可以访问类的成员、覆盖它的方法等等,这和其他任何标准的类都是一样的。如果匿名类实现了一个接口,它的主体必须实现接口的方法。
由于匿名内部类没有名称,所以类体中不能定义构造方法,由于不知道类名也不能使用关键字来创建该类的实例。实际上匿名内部类的定义、构造、和第一次使用都发生在同样一个地方。此外,上式是一个表达式,返回的是一个对象的引用,所以可以直接使用或将其复制给一个对象变量。例如:
// 成员匿名类
TypeName obj = new Name(){
// 此处为类体
}
同样,也可以将构造的对象作为调用的参数。例如:
// 局部匿名类
testMethod(new Name(){
// 此处为类体
});
匿名内部类的使用场景:
当函数的参数是接口类型引用时,如果接口中的方法不超过3个。可以通过匿名内部类来完成参数的传递。
其实就是在创建匿名内部类时,该类中的封装的方法不要过多,最好两个或者两个以内。
下面是一个成员匿名内部类的例子:
public class Outter {
public static void main(String[] args) {
Animal animal = new Animal() {
@Override
public void eat() {
System.out.println("我在吃!");
}
@Override
public void run() {
System.out.println("我在跑!");
}
};
animal.eat();
animal.run();
}
}
interface Animal {
void eat();
void run();
}
执行结果:
我在吃!
我在跑!
下面是一个关于局部匿名类的示例:
public class Outter {
public static void main(String[] args) {
Person person = new Person();
person.show(new Message(){
public void show(){
System.out.println("在匿名内部类中");
}
});
}
}
class Person{
public void show(Message message){
message.show();
}
}
class Message{
public void show(){
System.out.println("在Message类中");
}
}
执行结果如下:
在匿名内部类中
匿名对象和匿名类区别:
匿名对象还是一个对象,它必须有已经写好的成员变量、属性、方法。匿名类,在声明它的对象的时候,才对它进行代码的构造(封装它的成员变量、属性、方法等)
匿名对象:一个对象只需要使用一次的时候,通常使用匿名对象。匿名类:比如我们需要一个监听类,并且在实例化这个监听类的时候才需要实现这个监听类内部的方法,那么使用匿名类就最方便了。
使用匿名类的注其它意事项:
1)匿名内部类不能是抽象类,因为系统在创建匿名内部类的时候,会立即创建内部类的对象。因此不允许将匿名内部类定义成抽象类。
2)匿名内部类不能定义构造器,因为匿名内部类没有类名,所以无法定义构造器,但匿名内部类可以定义实例初始化块,通过实例初始化块来完成构造器需要完成的事情。
3)匿名内部类中不能定义静态初始化代码块(Static Initializer)。
4)不能在匿名类里面定义接口。