内部类就是将类B的定义放在类A定义的内部,A是外部类,B是内部类。
先来看一下内部类和外部类之间的关系。
public class Outer {
private String workNumber;
private String name;
public Outer() {
workNumber = "O12345";
name = "Frank";
}
public class InnerClass {
public InnerClass() {
workNumber = "I12345";
name = "Franklin";
}
private void show() {
System.out.println(String.format("workNumber: %s\nname: %s", workNumber, name));
}
}
/**
* @param args
*/
public static void main(String[] args) {
Outer out = new Outer();
Outer.InnerClass inner = out.new InnerClass();
inner.show();
}
}
虽然外部类Outer
的成员变量workNumber
和name
是私有的,但是内部类InnerClass
可以直接访问这两个成员变量。这是因为创建内部类对象时,内部类对象会保存一个指向外部类对象的引用,可以通过这个引用来选择外部类的成员。
在Debug模式下,可以看到内部类持有外部类的引用。↓
内部类与向上转型
先看代码
public class Outer {
public Animal getPanda() {
return new Panda();
}
public Plant getTree() {
return new Tree();
}
private class Panda implements Animal {
@Override
public String getAniName() {
return "Panda";
}
}
protected class Tree implements Plant {
@Override
public String getPlantName() {
return "Tree";
}
}
}
public class JavaMain {
public static void main(String[] args) {
Outer out = new Outer();
Outer.Tree tree = out.new Tree();
System.out.println(tree.getPlantName());
// Outer.Panda panda = out.new Panda(); 编译错误
Animal panda = out.getPanda();
System.out.println(panda.getAniName());
}
}
首先定义了两个接口Animal
和Plant
,并声明了方法getAniName()
和getPlantName()
。
在Outer
类中,定义了两个内部类Panda
和Tree
,Panda
类被定义成private
的,Tree
类被定义成protected
的。
如果这两个内部类,没有实现接口。因为Panda
类是私有的,所以在创建Panda
类的对象时,会报编译错误。
如果想创建Panda
类的对象,可以先实现接口Animal
。创建对象时,将内部类Panda
向上转型为接口类型Animal
。这样就可以得到私有内部类的对象,还完全隐藏了内部类的实现细节。
由于普通类不能声明为private
和protected
,如果想隐藏类的实现细节(封装),可以采用私有内部类+向上转型接口的方式来实现。
下面介绍一下内部类的分类,Java中的内部类分为成员内部类、局部内部类、匿名内部类和静态内部类。
成员内部类
成员内部类是最普通的内部类,文章开头介绍内部类时,使用的代码中的内部类就属于成员内部类。
在内部类中可以直接访问外部类的成员对象,如果内部类和外部类中有同名的成员变量时,该如何处理?
public class Outer {
private String workNumber;
private String name;
public Outer() {
workNumber = "O12345";
name = "Frank";
}
public class InnerClass {
private String name;
public InnerClass() {
workNumber = "I12345";
name = "Franklin";
}
private void show() {
String name = "tmp";
System.out.println("局部变量:" + name);
System.out.println("内部类变量:" + this.name);
System.out.println("外部类变量:" + Outer.this.name);
}
}
/**
* @param args
*/
public static void main(String[] args) {
Outer out = new Outer();
Outer.InnerClass inner = out.new InnerClass();
inner.show();
}
}
在内部类中用this
来引用内部类的成员变量,Outer.this
来引用外部类的成员变量。
局部内部类
局部内部类分为方法内的内部类和作用域内的内部类。局部内部类主要用于解决比较复杂的问题时,需要创建一个类来解决特定的问题,但又不希望这个类是公共可用的。
- 方法内部类1
public class Outer {
public Destination dest(String s) {
// 注意这里不指定访问权限修饰符
class PDestination implements Destination {
private String label;
private PDestination(String whereTo) {
label = whereTo;
}
@Override
public String readLabel() {
return label;
}
}
return new PDestination(s);
}
public static void main(String[] args) {
Outer out = new Outer();
Destination obj = out.dest("shenyang");
System.out.println(obj.readLabel());
}
}
- 作用域内部类2
public class Outer {
private void internalTracking(boolean b) {
if (b) {
// 注意这里不指定访问权限修饰符
class TrackingSlip {
private String id;
TrackingSlip(String s) {
id = s;
}
String getSlip() {
return id;
}
}
TrackingSlip ts = new TrackingSlip("slip");
String str = ts.getSlip();
}
}
public void track() {
internalTracking(true);
}
public static void main(String[] args) {
Outer out = new Outer();
out.track();
}
}
匿名内部类
顾名思义,匿名内部类是没有名字的,也没有访问修饰符。直接使用new
来生成一个对象引用。
public interface Animal {
String getAniName(String area);
}
public class Outer {
public Animal getAniObj() {
return new Animal() {
@Override
public String getAniName() {
return "Tiger";
}
};
}
public static void main(String[] args) {
Outer out = new Outer();
Animal aniObj = out.getAniObj();
System.out.println(aniObj.getAniName());
}
}
out.getAniObj()
返回的是Animal
类型的引用,实际上是匿名内部类实现了Animal
接口,并将返回的引用向上转型为Animal
类型的引用。
如果匿名内部类继承的基类的构造函数中有参数时,可以这样来处理。
public class Pet {
private String type;
public Pet() {
type = "No Pet";
}
public Pet(String petType) {
type = petType;
}
public String getType() {
return type;
}
}
public class Outer {
public Pet getPetObj(String area) {
return new Pet(area) {
public String getType() {
return "德国" + super.getType();
}
};
}
public static void main(String[] args) {
Outer out = new Outer();
Pet petObj = out.getPetObj("牧羊犬");
System.out.println(petObj.getType());
}
}
只需要将参数传给基类的构造函数就可以。执行new Pet(area)
时,Pet的构造函数会被调用。
匿名内部类特性
1.匿名内部类总是默认实现接口或者继承其他类。
2.匿名内部类没有构造方法,只有一个实例。
3.匿名内部类中不能定义静态成员和静态方法。
静态内部类
静态内部类是用static
修饰的内部类,在Oracle官方文档中被叫作非静态嵌套类。静态内部类没有隐性的保存外部类的引用,所以静态内部类的对象和外部类对象之间没有联系,也就不能访问外部类中的非静态对象。
public class Outer {
private static String name;
private String number;
public Outer(String name, String number) {
Outer.name = name;
this.number = number;
}
// 静态内部类
private static class InnerStaticClass {
private void show() {
System.out.println("Static Nested Class:" + name);
// Non-static field 'number' cannot be referenced from a static context
// 只能访问外部类的静态成员
//System.out.println("Static Nested Class:" + number);
}
}
// 非静态内部类
private class InnerClass {
// Inner classes cannot have static declarations
// 非静态内部类中不能有静态成员
//private static String staticVal;
private void show() {
// 非静态内部类中可以调用外部类的成员
System.out.println("Inner Class(name):" + name);
System.out.println("Inner Class(number):" + number);
}
}
private void show() {
// 静态内部类可以直接创建实例
new InnerStaticClass().show();
}
/**
* @param args
*/
public static void main(String[] args) {
Outer out = new Outer("Franklin", "I12345");
out.show();
Outer.InnerClass inner = out.new InnerClass();
inner.show();
}
}
通过上面的代码可以看到,非静态内部类中不能定义静态成员(变量,方法)。否则会出现编译错误。
外部类在加载的时候,并不会加载非静态内部类,而是在使用时才会被加载。
如果非静态内部类中存在静态成员的话,就会出现非静态内部类没有加载,但是试图在内存中创建内部类中的静态属性和方法。这显然是矛盾的,所以在非静态内部类中不能定义任何的静态成员。
总结
内部类的存在让多重继承的实现方式变得更完整。一个类可以实现多个接口,但是如果想继承多个类,就需要引入内部类。
内部类还有很多实用技巧,需要我们在以后逐渐去发掘。