内部类

最后一个语法糖,讲讲内部类,内部类指的就是在一个类的内部再定义一个类。

内部类之所以也是语法糖,是因为它仅仅是一个编译时的概念,outer.java里面定义了一个内部类inner,一旦编译成功,就会生成两个完全不同的.class文件了,分别是outer.class和outer$inner.class。所以内部类的名字完全可以和它的外部类名字相同

内部类分为四种:成员内部类、局部内部类、匿名内部类、静态内部类。先逐一了解下,再看下使用内部类有什么好处。

成员内部类

成员内部类是最常见的内部类,就是在外部类的基础上按照一般定义类的方式定义类罢了,看一个例子:

public class Outer
{
private int i; public Outer(int i)
{
this.i = i;
} public void privateInnerGetI()
{
new PrivateInner().printI();
} private class PrivateInner
{
public void printI()
{
System.out.println(i);
}
} public class PublicInner
{
private int i = 2; public void printI()
{
System.out.println(i);
}
}
}

主函数为:

public static void main(String[] args)
{
Outer outer = new Outer(0);
outer.privateInnerGetI();
Outer.PublicInner publicInner = outer.new PublicInner();
publicInner.printI();
}

运行结果为:

0
2

通过这个例子总结几点:

1、成员内部类是依附其外部类而存在的,如果要产生一个成员内部类,比如有一个其外部类的实例

2、成员内部类中没有定义静态方法,不是例子不想写,而是成员内部类中不可以定义静态方法

3、成员内部类可以声明为private的,声明为private的成员内部类对外不可见,外部不能调用私有成员内部类的public方法

4、成员内部类可以声明为public的,声明为public的成员内部类对外可见,外部也可以调用共有成员内部类的public方法

5、成员内部类可以访问其外部类的私有属性,如果成员内部类的属性和其外部类的属性重名,则以成员内部类的属性值为准

局部内部类

局部内部类是定义在一个方法或者特定作用域里面的类,看一下局部内部类的使用:

public static void main(String[] args)
{
final int i = 0;
class A
{
public void print()
{
System.out.println("AAA, i = " + i);
}
} A a = new A();
a.print();
}

注意一下局部内部类没有访问修饰符,另外局部内部类要访问外部的变量或者对象,该变量或对象的引用必须是用final修饰的

匿名内部类

这个应该是用得最多的,因为方便,在多线程模块中的代码示例中大量使用了匿名内部类,随便找一段:

public static void main(String[] args) throws InterruptedException
{
final ThreadDomain44 td = new ThreadDomain44();
Runnable runnable = new Runnable()
{
public void run()
{
td.testMethod();
}
};
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++)
threads[i] = new Thread(runnable);
for (int i = 0; i < 10; i++)
threads[i].start();
Thread.sleep(2000);
System.out.println("有" + td.lock.getQueueLength() "个线程正在等待!");
}

匿名内部类是唯一没有构造器的类,其使用范围很有限,一般都用于继承抽象类或实现接口(注意只能继承抽象类,不能继承普通类),匿名内部类Java自动为之起名为XXX$1.classs。另外,和局部内部类一样,td必须是用final修饰的。

静态内部类

用static修饰的内部类就是静态内部类,看下例子:

public class Outer
{
private static final int i = 1;public static class staticInner
{
public void notStaticPrint()
{
System.out.println("Outer.staticInner.notStaticPrint(), i = " + i);
} public static void staticPrint()
{
System.out.println("Outer.staticInner.staticPrint()");
}
}
}
public static void main(String[] args)
{
Outer.staticInner os = new Outer.staticInner();
os.notStaticPrint();
Outer.staticInner.staticPrint();
}

运行结果为:

Outer.staticInner.notStaticPrint(), i = 1
Outer.staticInner.staticPrint()

通过这个例子总结几点:

1、静态内部类中可以有静态方法,也可以有非静态方法

2、静态内部类只能访问其外部类的静态成员与静态方法

3、和普通的类一样,要访问静态内部类的静态方法,可以直接"."出来不需要一个类实例;要访问静态内部类的非静态方法,必须拿到一个静态内部类的实例对象

4、注意一下实例化成员内部类和实例化静态内部类这两种不同的内部类时写法上的差别

(1)成员内部类:外部类.内部类 XXX = 外部类.new 内部类();

(2)静态内部类:外部类.内部类 XXX = new 外部类.内部类();

为什么成员内部类可以访问外部类成员

用"javap"命令反编译一下第一个例子的内部类privateInner:

看一下这个内部类里的常量池中有哪些符号引用就知道了:

Constant pool:
#1 = Class #2 // com/xrq/test29/Outer$PrivateInner
#2 = Utf8 com/xrq/test29/Outer$PrivateInner
#3 = Class #4 // java/lang/Object
#4 = Utf8 java/lang/Object
#5 = Utf8 this$0
#6 = Utf8 Lcom/xrq/test29/Outer;
#7 = Utf8 <init>
#8 = Utf8 (Lcom/xrq/test29/Outer;)V
#9 = Utf8 Code
#10 = Fieldref #1.#11 // com/xrq/test29/Outer$PrivateInner.
this$0:Lcom/xrq/test29/Outer;
#11 = NameAndType #5:#6 // this$0:Lcom/xrq/test29/Outer;
#12 = Methodref #3.#13 // java/lang/Object."<init>":()V
#13 = NameAndType #7:#14 // "<init>":()V
#14 = Utf8 ()V
#15 = Utf8 LineNumberTable
#16 = Utf8 LocalVariableTable
#17 = Utf8 this
#18 = Utf8 Lcom/xrq/test29/Outer$PrivateInner;
#19 = Utf8 printI
#20 = Fieldref #21.#23 // java/lang/System.out:Ljava/io/Prin
tStream;
#21 = Class #22 // java/lang/System
#22 = Utf8 java/lang/System
#23 = NameAndType #24:#25 // out:Ljava/io/PrintStream;
#24 = Utf8 out
#25 = Utf8 Ljava/io/PrintStream;
#26 = Methodref #27.#29 // com/xrq/test29/Outer.access$0:(Lco
m/xrq/test29/Outer;)I
#27 = Class #28 // com/xrq/test29/Outer
#28 = Utf8 com/xrq/test29/Outer
#29 = NameAndType #30:#31 // access$0:(Lcom/xrq/test29/Outer;)I #30 = Utf8 access$0
#31 = Utf8 (Lcom/xrq/test29/Outer;)I
#32 = Methodref #33.#35 // java/io/PrintStream.println:(I)V
#33 = Class #34 // java/io/PrintStream
#34 = Utf8 java/io/PrintStream
#35 = NameAndType #36:#37 // println:(I)V
#36 = Utf8 println
#37 = Utf8 (I)V
#38 = Utf8 (Lcom/xrq/test29/Outer;Lcom/xrq/test29/Outer$PrivateI
nner;)V
#39 = Methodref #1.#40 // com/xrq/test29/Outer$PrivateInner.
"<init>":(Lcom/xrq/test29/Outer;)V
#40 = NameAndType #7:#8 // "<init>":(Lcom/xrq/test29/Outer;)V #41 = Utf8 SourceFile
#42 = Utf8 Outer.java
#43 = Utf8 InnerClasses
#44 = Utf8 PrivateInner

关键地方是两个:

1、第5行和第6行,Outer\$PrivateInner里面有一个this\$0,它是一个Lcom/xrq/test29/outer,开头的L表示复合对象。这表示内部类中有一个其外部类的引用

2、第7行和第8行,表示this$0这个引用通过构造函数赋值

顺便说一句,静态内部类并不持有其外部类的引用

局部内部类和匿名内部类只能访问final局部变量的原因

我是这么理解这个问题的。

开头就说了,内部类是一种语法糖,所谓语法糖,就是Java编译器在编译期间做的手脚,既然是在编译期间做的手脚,那么如何知道运行方法期间才确定的某个局部变量的值是多少?先理清楚两点:

  • 匿名内部类是唯一没有构造器的类
  • 局部内部类有构造器,通过构造器把外部的变量传入局部内部类再使用是完全可以的

那万一局部内部类中没有定义构造器传入局部变量怎么办呢?这时候Java想了一个办法,把局部变量修饰为final就好了,被final修饰的变量相当于是一个常量,编译时就可以确定并放入常量池。这样即使匿名内部类没有构造器、局部内部类没有定义有参构造器,也无所谓,反正要用到的变量编译时候就已经确定了,到时候去常量池里面拿一下就好了。

既然上面说到了"去常量池里面拿一下就好了",那么把局部内部类、匿名内部类里面要用到的局部变量设定为static的也是可以的(不过static不可以修饰局部变量,可以放在方法外),可以自己试一下

使用内部类的好处

最后来总结一下使用内部类的好处:

1、Java允许实现多个接口,但不允许继承多个类,使用成员内部类可以解决Java不允许继承多个类的问题。在一个类的内部写一个成员内部类,可以让这个成员内部类继承某个原有的类,这个成员内部类又可以直接访问其外部类中的所有属性与方法,是不是相当于多继承了呢?

2、成员内部类可以直接访问其外部类的private属性,而新起一个外部类则必须通过setter/getter访问类的private属性

3、有些类明明知道程序中除了某个固定地方都不会再有别的地方用这个类了,为这个只用一次的类定义一个外部类显然没必要,所以可以定义一个局部内部类或者成员内部类,写一段代码用用就好了

4、内部类某种程度上来说有效地对外隐藏了自己,比如我们常用的开发工具Eclipse、MyEclipse,看代码一般用的都是Packge这个导航器,Package下只有.java文件,我们是看不到定义的内部类的.java文件的

5、使用内部类可以让类与类之间的逻辑上的联系更加紧密

Java语法糖4:内部类的更多相关文章

  1. Java语法糖1:可变长度参数以及foreach循环原理

    语法糖 接下来几篇文章要开启一个Java语法糖系列,所以首先讲讲什么是语法糖.语法糖是一种几乎每种语言或多或少都提供过的一些方便程序员开发代码的语法,它只是编译器实现的一些小把戏罢了,编译期间以特定的 ...

  2. Java语法糖设计

    语法糖 Java语法糖系列,所以首先讲讲什么是语法糖.语法糖是一种几乎每种语言或多或少都提供过的一些方便程序员开发代码的语法,它只是编译器实现的一些小把戏罢了,编译期间以特定的字节码或者特定的方式对这 ...

  3. Java语法糖(二)

    语法糖之四:内部类 内部类:顾名思义,在类的内部在定义一个类.内部类仅仅是编译时的概念,编译成字节码后,内部类会生成单独的Class文件. 四种:成员内部类.局部内部类.匿名内部类.静态内部类. 1. ...

  4. Java语法糖(一)

    概述 语法糖(Syntactic Sugar):主要作用是提高编码效率,减少编码出错的机会. 解语法糖发生在Java源码被编译成Class字节码的过程中,还原回简单的基础语法结构. 语法糖之一:泛型( ...

  5. java语法糖---枚举

    java语法糖---枚举   在JDK5.0中提供了大量的语法糖,例如:自动装箱拆箱.增强for循环.枚举.泛型等.所谓“语法糖”就是指提供更便利的语法供程序员使用,只是在编译器上做了手脚,却没有提供 ...

  6. 转:【深入Java虚拟机】之六:Java语法糖

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/18011009 语法糖(Syntactic Sugar),也称糖衣语法,是由英国计算机学家P ...

  7. Java 语法糖详解

    语法糖 语法糖(Syntactic Sugar),也称糖衣语法,是由英国计算机学家 Peter.J.Landin 发明的一个术语,指在计算机语言中添加的某种语法. 这种语法对语言的功能并没有影响,但是 ...

  8. 深入理解java虚拟机(十二) Java 语法糖背后的真相

    语法糖(Syntactic Sugar),也叫糖衣语法,是英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语.指的是,在计算机语言中添加某种语法,这些语法糖虽然不会对语言 ...

  9. 【深入Java虚拟机】之六:Java语法糖

    语法糖(Syntactic Sugar),也称糖衣语法,是由英国计算机学家Peter.J.Landin发明的一个术语,指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使 ...

随机推荐

  1. flask-admin章节一:使用chartkick画报表

    一般中小型WEB整体来看逻辑比较简单些,一般都是基于数据库的增删改查.不过通过数据库查询到的记录直接展示给用户不是很直观,大家其实蛮期待有一个报表 直接展示他们期待的内容. 这块就涉及到数据的提取和展 ...

  2. MVC 之 WebAPI 系列二

    今天,我想在此记录下 WebApi 跨域调用 1. 什么叫跨域: 跨域问题简单理解就是JavaScript同源策略的限制,其根本原因是因为浏览器对于这种请求,所给予的权限是较低的,通常只允许调用本域中 ...

  3. win8 vs2010 openni2 配置

    打开一个新项目或者已存在的项目用以使用  OpenNI 在Visual Studio 菜单中, 打开项目菜单,选择项目属性. 在C/C++ 选项卡中, 在"常规" 下, 选择 &q ...

  4. 准备熟悉Kaggle -菜鸟进阶

    原文链接http://www.bubuko.com/infodetail-525389.html 1.Kaggle简介 Kaggle是一个数据分析的竞赛平台,网址:https://www.kaggle ...

  5. oracle 存储过程 包 【转】

    一.为什么要用存储过程? 如果在应用程序中经常需要执行特定的操作,可以基于这些操作简历一个特定的过程.通过使用过程可以简化客户端程序的开发和维护,而且还能提高客户端程序的运行性能. 二.过程的优点? ...

  6. Windows netstat 查看端口、进程占用

    目标:在Windows环境下,用netstat命令查看某个端口号是否占用,为哪个进程所占用. (1)查看该端口被那个PID所占用;方法一:有针对性的查看端口,在命令行下,使用命令netstat –an ...

  7. vs2015 附加到进程找不到w3wp.exe

    vs2015 附加到进程找不到w3wp.exe 解决办法: 浏览器打开你访问的IIS地址后就出现了~!!!!!!!!!

  8. OpenVPN 通过服务器上网

    在Windows环境中架设OpenVPN服务相对比较简单,网上这方面的教程也比较丰富,照葫芦画瓢即可.但是大部分教程都只讲了如何将client与Server通过VPN管道连接起来,使client可以正 ...

  9. 二 Java利用等待/通知机制实现一个线程池

    接着上一篇博客的 一Java线程的等待/通知模型 ,没有看过的建议先看一下.下面我们用等待通知机制来实现一个线程池 线程的任务就以打印一行文本来模拟耗时的任务.主要代码如下: 1  定义一个任务的接口 ...

  10. MariaDB kill命令

    MariaDB的KILL命令不只可以杀掉连接,而且可以只杀掉某连接当前的SQL,而不断开连接.KILL QUERY thread_id;kill thread_id可以杀掉当前的连接,而kill QU ...