文档章节

Java高级-内部类详解

陶邦仁
 陶邦仁
发布于 2012/11/14 23:47
字数 2481
阅读 1416
收藏 77
点赞 0
评论 0

前言

提起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通过内部类加上接口,可以很好的实现多继承的效果。

© 著作权归作者所有

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

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

MFrank ⋅ 06/21 ⋅ 0

关于Java内部类字段和方法不能使用static修饰的原因

昨天的文章中,遗留了一个问题就是,为什么Java内部类字段和方法不能使用static修饰。 先下下面一段代码: 上面的内部类的成员变量和方法,只要加上了static修饰,就会出现编译错误。 原因:...

九劫散仙 ⋅ 06/02 ⋅ 0

React Native通信原理源码分析二

本篇文章已授权微信公众号 JueCode 独家发布 在上一篇中分析了Native调用JavaScript的原理,这一篇我们分析下JavaScript调用Native的原理。上一篇提到的内容这里就不重复了,建议小伙伴们先看...

juexingzhe ⋅ 04/16 ⋅ 0

Java程序员必读书单,家族又添新成员

点击关注异步图书,置顶公众号 每天与你分享IT好书 技术干货 职场知识 参与文末话题讨论,每日赠送异步图书。 ——异步小编 有些革命出其不意地吸引了全世界的眼球。Twitter、Linux操作系统和...

异步社区 ⋅ 05/09 ⋅ 0

【Java并发专题】27篇文章详细总结Java并发基础知识

努力的意义,就是,在以后的日子里,放眼望去全是自己喜欢的人和事! github:https://github.com/CL0610/Java-concurrency,欢迎题issue和Pull request。所有的文档都是自己亲自码的,如果觉...

你听___ ⋅ 05/06 ⋅ 0

Java开发者不会这些永远都只能是三流程序员,细数一下你是不是?

源码系列 手写spring mvc框架 基于Spring JDBC手写ORM框架 实现自己的MyBatis Spring AOP实战之源码分析 Spring IOC高级特性应用分析 ORM框架底层实现原理剖析 手写Spring MVC框架实现 手把手...

美的让人心动 ⋅ 04/16 ⋅ 0

大型互联网架构必备技术——性能调优专题

性能调优 深入内核,直击故障 ,拒绝蒙圈 性能优化如何理解 1、性能基准 2、什么是性能优化 3、衡量标准 JVM调优 1、Jvm虚拟机内存剖析 2、垃圾收集器 3、实战调优案例与解决方案 4、Jvm运行...

Java高级架构 ⋅ 04/15 ⋅ 0

ThreadLocal源码分析

阅读原文请访问我的博客 BrightLoong's Blog 一. 简介 提醒篇幅较大需耐心。 简介来自ThreadLocal类注释 ThreadLocal类提供了线程局部 (thread-local) 变量。这些变量与普通变量不同,每个线...

BrightLoong ⋅ 05/28 ⋅ 0

Scala笔记整理(三):Scala面向对象—类详解1

[TOC] getter和setter 简单类和无参方法 需要说明的是,官方给出的参考资料对于这方面讲解得十分详细,目前入门来说,不需要达到这样的理解,只需要可以进行开发即可,这一部分我给出的是官方...

xpleaf ⋅ 04/20 ⋅ 0

Java Lambda表达式 实现原理分析

本文分析基于JDK 9 一、目标 本文主要解决两个问题: 1、函数式接口 到底是什么? 2、Lambda表达式是怎么实现的? 先介绍一个jdk的bin目录下的一个字节码查看工具及反编译工具:javap 二、函...

xunzaosiyecao ⋅ 04/06 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

个人博客的运营模式能否学习TMALL天猫质量为上?

心情随笔|个人博客的运营模式能否学习TMALL天猫质量为上? 中国的互联网已经发展了很多年了,记得在十年前,个人博客十分流行,大量的人都在写博客,而且质量还不错,很多高质量的文章都是在...

原创小博客 ⋅ 今天 ⋅ 0

JavaScript零基础入门——(十一)JavaScript的DOM操作

JavaScript零基础入门——(十一)JavaScript的DOM操作 大家好,欢迎回到我们的JavaScript零基础入门。最近有些同学问我说,我讲的的比书上的精简不少。其实呢,我主要讲的是我在开发中经常会...

JandenMa ⋅ 今天 ⋅ 0

volatile和synchronized的区别

volatile和synchronized的区别 在讲这个之前需要先了解下JMM(Java memory Model :java内存模型):并发过程中如何处理可见性、原子性、有序性的问题--建立JMM模型 详情请看:https://baike.b...

MarinJ_Shao ⋅ 今天 ⋅ 0

深入分析Kubernetes Critical Pod(一)

Author: xidianwangtao@gmail.com 摘要:大家在部署Kubernetes集群AddOn组件的时候,经常会看到Annotation scheduler.alpha.kubernetes.io/critical-pod"="",以表示这是一个关键服务,那你知...

WaltonWang ⋅ 今天 ⋅ 0

原子性 - synchronized关键词

原子性概念 原子性提供了程序的互斥操作,同一时刻只能有一个线程能对某块代码进行操作。 原子性的实现方式 在jdk中,原子性的实现方式主要分为: synchronized:关键词,它依赖于JVM,保证了同...

dotleo ⋅ 今天 ⋅ 0

【2018.06.22学习笔记】【linux高级知识 14.4-15.3】

14.4 exportfs命令 14.5 NFS客户端问题 15.1 FTP介绍 15.2/15.3 使用vsftpd搭建ftp

lgsxp ⋅ 今天 ⋅ 0

JeeSite 4.0 功能权限管理基础(Shiro)

Shiro是Apache的一个开源框架,是一个权限管理的框架,实现用户认证、用户授权等。 只要有用户参与一般都要有权限管理,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户...

ThinkGem ⋅ 昨天 ⋅ 0

python f-string 字符串格式化

主要内容 从Python 3.6开始,f-string是格式化字符串的一种很好的新方法。与其他格式化方式相比,它们不仅更易读,更简洁,不易出错,而且速度更快! 在本文的最后,您将了解如何以及为什么今...

阿豪boy ⋅ 昨天 ⋅ 0

Python实现自动登录站点

如果我们想要实现自动登录,那么我们就需要能够驱动浏览器(比如谷歌浏览器)来实现操作,ChromeDriver 刚好能够帮助我们这一点(非谷歌浏览器的驱动有所不同)。 一、确认软件版本 首先我们...

blackfoxya ⋅ 昨天 ⋅ 0

线性回归原理和实现基本认识

一:介绍 定义:线性回归在假设特证满足线性关系,根据给定的训练数据训练一个模型,并用此模型进行预测。为了了解这个定义,我们先举个简单的例子;我们假设一个线性方程 Y=2x+1, x变量为商...

wangxuwei ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部