Java中的内部类
总结一下内部类,如有错误或者不足,欢迎交流讨论。
- 内部类的定义,作用
- 内部类的分类
- 和内部类相关的几个问题
1、什么是内部类,为什么需要内部类,它有什么用?
在一个类的内部定义一个类,就是内部类。内部类提供了某种进入外围类的窗口;每个内部类都能独立继承子一个实现(接口或抽象类),无论其外围类是否实现,对内部类都没有影响;实现多继承,Java的中继承是单根继承,只能继承自一个类,虽然可以实现多个接口,但是如果要继承自多个类,那么只有内部类才可以解决,思路是外围类继承一个类,内部类继承另外一个类,虽然内部类只继承了一个类,但是因为它有一个隐式的指向外围类对象的引用(非静态内部类才可以),它可以访问外围类的成员,若是要访问外围类的方法,可以定义一个生成对外围类对象的引用的方法,这样就是实现了“多继承”。
2、内部类的分类
2.1 普通内部类
直接在一个类里面再定义一个类,不在某个方法里面。创建内部类的对象不能像普通类一样,由于隐含有指向外围类对象的引用,必须先创建外围类。如下所示(还实现了继承自两个类):
package innerclass;
/**
* 普通内部类,并实现“多继承”
* @author Administrator @date 2016年3月28日
* http://blog.csdn.net/xiaoguobaf
*/
class A{
public A(){
System.out.println("class A constructor");
}
public void f(){
System.out.println("class A f()");
}
}
class B{
public B(){
System.out.println("class B constructor");
}
public void f(){
System.out.println("class B f()");
}
class InnerClass extends A{
public InnerClass(){
System.out.println("class InnerClass constructor");
}
//返回生成内部类的外围类对象的引用
public B outer(){
return B.this;
}
}
//典型新建内部类情况,外围类有一个返回内部类对象的引用的方法
public InnerClass inner(){
return new InnerClass();
}
}
public class CommonInnerClass {
public static void main(String[] args) {
B b = new B();
//通过外围类创建内部类
B.InnerClass ic1 = b.new InnerClass();
//通过外围类的方法创建内部类,典型方法
B.InnerClass ic2 = b.inner();
ic1.f();
//使用内部类访问外围类的f()
ic2.outer().f();
//验证outer()方法返回的引用就是创建内部类的外围类的引用,打印其hashcode
System.out.println(b);
System.out.println(ic2.outer());
}
}
/**
输出:
class B constructor
class A constructor
class InnerClass constructor
class A constructor
class InnerClass constructor
class A f()
class B f()
innerclass.B@2a139a55
innerclass.B@2a139a55
*/
2.2 局部内部类
在方法中定义的类,它不能有访问说明符,因为不是外围类的一部分,它属于包含它的方法。另外,如果要在内部类中使用在外部定义的对象,为了防止在外围类中被修改,应将该对象引用修饰为final。
package innerclass;
/**
* 局部类
* @author Administrator @date 2016年3月28日
* http://blog.csdn.net/xiaoguobaf
*/
interface Counter{
int next();
}
public class LocalInnerClass {
private int count = 0;
//内部类要使用name,定义为final是为了防止外围类对name进行了修改
Counter getCounter(final String name){
class LocalCounter implements Counter{
public LocalCounter(){
System.out.println("LocalCounter constructor");
}
public int next(){//继承、实现不能缩小访问范围
System.out.print(name);
return count++;
}
}
return new LocalCounter();
}
public static void main(String[] args) {
LocalInnerClass l = new LocalInnerClass();
Counter c = l.getCounter(" LocalInnerClass ");
for (int i = 0; i < 4; i++)
System.out.println(c.next());
}
}
/**
* 输出:
LocalCounter constructor
LocalInnerClass 0
LocalInnerClass 1
LocalInnerClass 2
LocalInnerClass 3
*/
2.3匿名内部类
匿名内部类看起来就像在创建一个类的对象时,突然插入了一个类的定义。因为没有名字,所以没有构造器。没有构造器也带来了一个有趣的问题,如何初始化呢?在初始化的博客中,我写到,实例初始化在构造器之前,就是因为匿名内部类的存在,它没有构造器,可以利用实例初始化当做构造器使用,所以,实例初始化是在构造器之前执行。
如果某一内部类只是需要一个对象,那么定义为匿名内部类最合适不过了。不过也有缺点,匿名内部类只能实现一个接口或继承自一个类,不能实现两个接口,或者一个接口并继承自一个类,因为是匿名的,若可以两个,那么返回类型(通过new)无法确定。
package innerclass;
/**
* 匿名内部类,在一个包内,Counter接口在上面已经定义了
* @author Administrator @date 2016年3月28日
* http://blog.csdn.net/xiaoguobaf
*/
public class AnonymousInnerClass {
private int count = 0;
Counter getCounter(final String name){
return new Counter(){
//实例初始化,当做构造器使用
{
System.out.println("Instance initialization");
}
public int next(){
System.out.print(name);
return count++;
}
};
}
public static void main(String[] args) {
AnonymousInnerClass a = new AnonymousInnerClass();
Counter c2 = a.getCounter(" Anonymous inner ");
for(int i = 0 ; i < 4;i++)
System.out.println(c2.next());
}
}
/**
* 输出:
Instance initialization
Anonymous inner 0
Anonymous inner 1
Anonymous inner 2
Anonymous inner 3
*/
2.4 静态内部类
又名嵌套类(注意不是嵌套的类)。如其名,内部类的定义前加了static修饰符,这时静态内部类与外围类无联系,创建静态内部类的对象不需要先创建外围类的对象,也不能访问非静态的外围类对象,也称为嵌套类。
静态内部类与普通内部类的区别除了上述外,普通内部类的成员或方法无法被static修饰。这是一个编译问题,先从静态成员变量说起,静态成员变量是在类加载时初始化的,也就是第一次创建类的对象或者访问静态成员变量,它在类的内存内存分布中是有确切的位置的,对于内部类来说,其内存的分配和非静态的成员变量是类似的,不创建外围类的对象,那么成员变量的值就不确定,从而其内存位置也不确定,且外围类对象的内存位置也不确定,其引用的内存位置也就不能确定,而普通内部类的创建依赖外围类的对象的创建,所以,普通的内部类是不能有static修饰的成员变量和方法的。
package innerclass;
/**
* 静态内部类
* @author Administrator @date 2016年3月28日
* http://blog.csdn.net/xiaoguobaf
*/
interface Contents{
int value();
}
interface Destination{
String readLabel();
}
public class NestedClass {
private static class ParcelContents implements Contents{
private int i = 11;
public int value(){
return i;
}
void printf(){
System.out.println("ParcelContents: "+i);
}
}
protected static class ParcelDestination implements Destination{
private String label;
private ParcelDestination(String label){
this.label = label;
}
public String readLabel(){
return label;
}
public static void f(){}
static int x = 10;
static class AnotherLevel{
static int x = 10;
public static void f(){}
}
}
public static Destination destination(String s){
return new ParcelDestination(s);
}
public static Contents contents(){
return new ParcelContents();
}
public static void main(String[] args) {
Contents c = contents();
Destination d = destination("Yanni");
c.value();
}
}
这段例程例程有些地方还需要分析,在后面的内部类与向上转型。
接口内部类,即在接口里面定义的类,接口中的任何类都是隐式public和static的。例如:
package innerclass;
/**
* 接口内部类
* @author Administrator @date 2016年3月28日
* http://blog.csdn.net/xiaoguobaf
*/
public interface ClassInsideInterface {
void howdy();
class Test implements ClassInsideInterface{
@Override
public void howdy() {
System.out.println("howdy");
}
public static void main(String[] args){
new Test().howdy();
}
}
}
编译器不会报错,接口内部类可用来实现接口的公共代码,该代码可以被不同的实现所公用。
为每个类编写测试,可用嵌套类,就不用每个都编写。
3、内部类的几个问题
- 内部类与向上转型
将内部类向上转型为基类,尤其是接口,内部类的实现细节就隐藏了,并且不可见,若内部类是private的,那么在非外围类中还无法向下转型,可以说实习细节完全隐藏了。
package innerclass;
/**
* 内部类和向上转型
* @author Administrator @date 2016年3月29日
* http://blog.csdn.net/xiaoguobaf
*/
public class InnerClassAndUpcasting {
private class ParcelContents implements Contents{
private int i = 11;
public int value(){
return i;
}
//默认是private的,随类,实际上没什么用,搭配public配合接口,向上转型为接口类型,可实现完全隐藏实现细节
void printf(){
System.out.println("ParcelContents: "+i);
}
//只在InnerClassAndUpcasting中能访问,无特色,在protected修饰的类中还可以访问,但没有必要,不如声明为private的,
public void callPrintf(){
printf();
}
}
protected class ParcelDestination implements Destination{
private String label;
private ParcelDestination(String label){
this.label = label;
}
public String readLabel(){
return label;
}
}
public Destination destination(String s){
return new ParcelDestination(s);
}
public Contents contents(){
return new ParcelContents();
}
public static void main(String[] args) {
InnerClassAndUpcasting inau = new InnerClassAndUpcasting();
Contents c = inau.contents();
Destination d = inau.destination("Yanni");
//必须先要向下转型才能访问子类型的方法,因为继承扩展信息,父类不能访问子类中扩展的信息
// c.printf();//提示方法没有定义
((ParcelContents)c).printf();
((ParcelContents)c).callPrintf();
d.readLabel();
}
}
/**
* 一个class文件里面可以有两个main函数,只是在执行时eclipse将提示你要执行哪个,我这里是为了方便测试
* @author Administrator @date 2016年3月29日
* http://blog.csdn.net/xiaoguobaf
*/
class Test {
public static void main(String[] args){
InnerClassAndUpcasting inau1 = new InnerClassAndUpcasting();
Contents c1 = inau1.contents();
System.out.println(c1.value());
//无法向下转型,由于ParcelContents类是private,出了其外围类InnerClassAndUpcasting,无法访问
//((ParcelContents)c).printf();
}
}
/**
* 输出:
ParcelContents: 11
ParcelContents: 11
11
*/
- 多层嵌套的内部类的访问
多层嵌套的内部类可以透明的访问所有它所嵌入的外围类的所有成员,很好理解,因为有指向外围类的引用(非static的),静态内部类可以理解为和静态成员变量一样的,从内存角度来看的话。 - 内部类的继承
内部类可以继承,语法特殊些,因为要将指向外围类的引用初始化(非static)。
package innerclass;
/**
* 内部类的继承
* @author Administrator @date 2016年3月29日
* http://blog.csdn.net/xiaoguobaf
*/
class WithInner{
class Inner{}
}
public class InnerClassInheriting extends WithInner.Inner{
InnerClassInheriting(WithInner wi){
wi.super();
}
public static void main(String[] args){
WithInner wi = new WithInner();
InnerClassInheriting ic = new InnerClassInheriting(wi);
}
}
内部类可以覆盖么
内部类不可以覆盖,就像在父类和子类中定义同名同属性的成员类似,是不同的。内部类与闭包、回调
闭包,是一个可调用的对象,它的信息来自于创建它的作用域,内部类是面向对象的闭包,因为它包含外围类对象的信息,还默认拥有(非static)一个指向外围类对象的引用。
回调:简单的来说,就是你调用我,我调用你,先写到这里。看TIJ我还觉得上面例子有点想策略模式,查的结果有的说想观察者模式,先放着吧,可参考 以及。
参考资料: