文档章节

Java高级-内部类详解

陶邦仁
 陶邦仁
发布于 2012/11/14 23:47
字数 2481
阅读 1424
收藏 77

前言

提起Java内部类(Inner Class)可能很多人不太熟悉,实际上类似的概念在C++里也有,那就是嵌套类(Nested Class),其实Java内部类与C++嵌套类最大的`不同就在于是否有指向外部的引用上(下面会介绍到)`。

内部类从表面上看,就是在类中又定义了一个类,而实际上并没有那么简单,乍看上去内部类似乎有些多余,它的用处对于初学者来说可能并不是那么显著,但是随着对它的深入了解,你会发现Java的设计者在内部类身上的确是用 心良苦。学会使用内部类,是掌握Java高级编程的一部分,它可以让你更优雅地设计你的程序结构。

一、第一次见面

public interface Contents {
    int value(); 
}
public interface Destination {
    String readLabel(); 
}
public class Goods {        
    private class Content implements Contents {              
        private int i = 11;              
        public int value() {
            return i;              
        }        
    }
    
    protected class GDestination implements Destination {              
        private String label;              
        private GDestination(String whereTo) {                    
            label = whereTo;              
        }              
        public String readLabel() {                    
            return label;              
        }        
    }
    
    public Destination dest(String s) {              
        return new GDestination(s);        
    }        
    
    public Contents cont() {              
        return new Content();        
    }
}

class TestGoods {     
    public static void main(String[] args) {         
        Goods p = new Goods();         
        Contents c = p.cont();         
        Destination d = p.dest("Beijing");     
    } 
}

在这个例子里类Content和GDestination被定义在了类Goods内部,并且分别有着protected和private修饰符来控制访问级 别。Content代表着Goods的内容,而GDestination代表着Goods的目的地。它们分别实现了两个接口Content和 Destination。在后面的main方法里,直接用 Contents c和Destination d进行操作,你甚至连这两个内部类的名字都没有看见!这样,内部类的第一个好处就体现出来了——`隐藏你不想让别人知道的操作`,也即封装性。

同时,我们也发现了在外部类作用范围之外得到内部类对象的第一个方法,那就是利用其外部类的方法创建并返回。上例中的cont()和dest()方法就是这么做的。那么还有没有别的方法呢?当然有,其语法格式如下:

outerObject=new OuterClass(Constructor Parameters);
OuterClass.innerClass innerObject=outerObject.new InnerClass(Constructor Parameters);

注意:`在创建非静态内部类对象时,一定要先创建起相应的外部类对象`。至于原因,也就引出了我们下一个话题。

二、非静态内部类对象有着指向其外部类对象的引用

对刚才的例子稍作修改:

public class Goods {
    private valueRate=2;
    private class Content implements Contents {
        private int i = 11*valueRate;
        public int value() {
            return i;
        }
    }
    protected class GDestination implements Destination {
        private String label;
        private GDestination(String whereTo) {
            label = whereTo;
        }
        public String readLabel() {
            return label;
        }
    }
    public Destination dest(String s) {
        return new GDestination(s);
    }
    public Contents cont() {
        return new Content();
    }
}

在这里我们给Goods类增加了一个private成员变量valueRate,意义是货物的价值系数,在内部类Content的方法value()计算价值时把它乘上。我们发现,value()可以访问valueRate,这也是内部类的第二个好处——`一个内部类对象可以访问创建它的外部类对象的内容, 甚至包括私有变量!`这是一个非常有用的特性,为我们在设计时提供了更多的思路和捷径。`要想实现这个功能,内部类对象就必须有指向外部类对象的引用。`Java编译器在创建内部类对象时,隐式的把其外部类对象的引用也传了进去并一直保存着。这样就使得内部类对象始终可以访问其外部类对象,同时这也是为什么在外部类作用范围之外向要创建内部类对象必须先创建其外部类对象的原因。

有人会问,`如果内部类里的一个成员变量与外部类的一个成员变量同名,也即外部类的同名成员变量被屏蔽了`,怎么办?没事,Java里用如下格式表达外部类的引用:

OuterClass.this

有了它,我们就不怕这种屏蔽的情况了。

三、静态内部类

和普通的类一样,内部类也可以有静态的。不过和非静态内部类相比,区别就在于`静态内部类没有了指向外部的引用`。这实际上和C++中的嵌套类很相像 了,Java内部类与C++嵌套类最大的不同就在于是否有指向外部的引用这一点上,当然从设计的角度以及以它一些细节来讲还有区别。

除此之外,`在任何非静态内部类中,都不能有静态数据,静态方法或者又一个静态内部类(内部类的嵌套可以不止一层)`。不过静态内部类中却可以拥有这一切。这也算是两者的第二个区别吧。

与接口相关方面,正常情况下,你不能在接口内部放置任何代码,但静态内部类可以作为接口的一部分,因为它是static 的。只是将静态内部类置于接口的命名空间内,这并不违反接口的规则。

静态内部类,意味着:

1. 创建一个static内部类的对象,不需要一个外部类对象;

2. 不能从一个static内部类的一个对象访问一个外部类对象;

四、局部内部类

Java内部类也可以是局部的,它可以定义在一个方法甚至一个代码块之内。

public class Goods1 {
    public Destination dest(String s) {
        class GDestination implements Destination {
            private String label;
            private GDestination(String whereTo) {
                label = whereTo;
            }
            public String readLabel() {
                return label;
            }
        }
        return new GDestination(s);
    }
    public static void main(String[] args) {
        Goods1 g= new Goods1();
        Destination d = g.dest("Beijing");
    }
}

上面就是这样一个例子。在方法dest中我们定义了一个内部类,最后由这个方法返回这个内部类的对象。如果我们在用一个内部类的时候仅需要创建它的一个对象并创给外部,就可以这样做。当然,定义在方法中的内部类可以使设计多样化,用途绝不仅仅在这一点。

下面有一个更怪的例子:

public class Goods2{
    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 s = ts.getSlip();           
        }       
    }
    public void track() { 
        internalTracking(true); 
    }
    public static void main(String[] args) {           
        Goods2 g= new Goods2();           
        g.track();       
    } 
}

你不能在if之外创建这个内部类的对象,因为这已经超出了它的作用域。不过在编译的时候,内部类TrackingSlip和其他类一样同时被编译,只不过它由它自己的作用域,超出了这个范围就无效,除此之外它和其他内部类并没有区别。

五、匿名内部类

当一个内部类的类声名只是`在创建此类对象时用了一次`,而且`要产生的新类需继承于一个已有的父类或实现一个接口`,才能考虑用匿名类,`由于匿名类本身无名,因此它也就不存在构造方法`,它需要显示地调用一个无参的父类的构造方法,并且重写父类的方法。

java的匿名内部类的语法规则看上去有些古怪,不过如同匿名数组一样,当你只需要创建一个类的对象而且用不上它的名字时,使用内部类可以使代码看上去简洁清楚。它的语法规则是这样的:

new interfacename(){......}; 或 
new superclassname(){......};

下面接着前面继续举例子:

public class Goods3 { 
    public Contents cont(){
        return new Contents(){ 
            private int i = 11; 
            public int value() { 
                return i; 
            } 
        }; 
    } 
}

这里方法cont()使用匿名内部类直接返回了一个实现了接口Contents的类的对象,看上去的确十分简洁。

在java的事件处理的匿名适配器中,匿名内部类被大量的使用。例如在想关闭窗口时加上这样一句代码:

frame.addWindowListener(new WindowAdapter(){ 
     public void windowClosing(WindowEvent e){ 
          System.exit(0); 
     } 
});

有一点需要注意的是,`匿名内部类由于没有名字,所以它没有构造函数(但是如果这个匿名内部类继承了一个只含有带参数构造函数的父类,创建它的时候必须带上这些参数,并在实现的过程中使用super关键字调用相应的内容)`。如果你想要初始化它的成员变量,有下面几种方法:

  1. 如果是在一个方法的匿名内部类,可以利用这个方法传进你想要的参数,不过记住,这些参数必须被声明为final。

  2. 将匿名内部类改造成有名字的局部内部类,这样它就可以拥有构造函数了。

  3. 在这个匿名内部类中使用初始化代码块,如下图。

QQ截图20121114234605   

六、为什么需要内部类

java内部类有什么好处?为什么需要内部类?

首先举一个简单的例子,如果你想实现一个接口,但是这个接口中的一个方法和你构想的这个类中的一个方法的名称,参数相同,你应该怎么办?这时候,你 可以建一个内部类实现这个接口。由于内部类对外部类的所有内容都是可访问的,所以这样做可以完成所有你直接实现这个接口的功能。

不过你可能要质疑,更改一下方法的不就行了吗?

的确,以此作为设计内部类的理由,实在没有说服力。

真正的原因是这样的,`java中的内部类和接口加在一起,可以的解决常被C++程序员抱怨java中存在的一个问题——没有多继承`。实际上,C++的多继承设计起来很复杂,而java通过内部类加上接口,可以很好的实现多继承的效果。

© 著作权归作者所有

共有 人打赏支持
陶邦仁
粉丝 1606
博文 420
码字总数 1483822
作品 0
海淀
技术主管
【目录导航】JAVA零基础进阶之路

【JAVA零基础入门系列】(已完结)导航目录 Day1 开发环境搭建 Day2 Java集成开发环境IDEA Day3 Java基本数据类型 Day4 变量与常量 Day5 Java中的运算符 Day6 Java字符串 Day7 Java输入与输出...

MFrank
06/21
0
0
【Scala】Scala高级使用技巧之一

版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/gongxifacai_believe/article/details/82110617 1、Scaladoc的使用 Scaladoc是Scala的API文档,包含了Scala所有的A...

魏晓蕾
08/27
0
0
一份关于 Java、Kotlin 与 Android 的学习笔记

JavaKotlinAndroidLearn 这是一份关于 Java 、Kotlin 、Android 的学习笔记,既包含对基础知识点的介绍,也包含对一些重要知识点的源码解析,笔记的大纲如下所示: Java 重拾Java(0)-基础知...

叶应是叶
08/08
0
0
new和反射产生java内部类的实例化详解

前两天看到一道口试题,是关于内部类的知识,觉得很有意思。 这道题是这样的: 根据注释填写(1),(2),(3)处的代码 public class Test{ ?????? public static void main(String[] args){ ??...

zaizai_loong
2013/09/02
0
0
大数据开发培训:0基础学习Java编程语言有哪些知识点?

Java 技术通用、高效、具有平台移植性和安全性,广泛应用于PC、数据中心、游戏控制台、科学超级计算机、移动电话和互联网等,学习Java首先要知道学习知识点有哪些。在这就用加米谷大数据培训...

加米谷大数据
07/25
0
0

没有更多内容

加载失败,请刷新页面

加载更多

arts-week10

Algorithm 905. Sort Array By Parity - LeetCode Review Who’s Afraid of the Big Bad Preloader? 一文读懂前端缓存 一个网络请求3个步骤:请求,处理,响应,而前端缓存主要在请求处响应这两步...

yysue
今天
0
0
00.编译OpenJDK-8u40的整个过程

前言 历经2天的折腾总算把OpenJDK给编译成功了,要说为啥搞这个,还得从面试说起,最近出去面试经常被问到JVM的相关东西,总感觉自己以前学的太浅薄,所以回来就打算深入学习,目标把《深入理...

凌晨一点
今天
5
0
python: 一些关于元组的碎碎念

初始化元组的时候,尤其是元组里面只有一个元素的时候,会出现一些很蛋疼的情况: def checkContentAndType(obj): print(obj) print(type(obj))if __name__=="__main__": tu...

Oh_really
昨天
6
2
jvm crash分析工具

介绍一款非常好用的jvm crash分析工具,当jvm挂掉时,会产生hs_err_pid.log。里面记录了jvm当时的运行状态以及错误信息,但是内容量比较庞大,不好分析。所以我们要借助工具来帮我们。 Cras...

xpbob
昨天
136
0
Qt编写自定义控件属性设计器

以前做.NET开发中,.NET直接就集成了属性设计器,VS不愧是宇宙第一IDE,你能够想到的都给你封装好了,用起来不要太爽!因为项目需要自从全面转Qt开发已经6年有余,在工业控制领域,有一些应用...

飞扬青云
昨天
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部