Java语法糖4:内部类

内部类

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

内部类之所以也是语法糖,是因为它仅仅是一个编译时的概念,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编译器在编译期间做的手脚,既然是在编译期间做的手脚,那么如何知道运行方法期间才确定的某个局部变量的值是多少?先理清楚两点:

1、匿名内部类是唯一没有构造器的类

2、局部内部类有构造器,通过构造器把外部的变量传入局部内部类再使用是完全可以的

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

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

使用内部类的好处

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

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

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

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

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

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

时间: 2024-10-12 19:29:31

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

java语法糖

语法糖 Java语法糖系列,所以首先讲讲什么是语法糖.语法糖是一种几乎每种语言或多或少都提供过的一些方便程序员开发代码的语法,它只是编译器实现的一些小把戏罢了,编译期间以特定的字节码或者特定的方式对这些语法做一些处理,开发者就可以直接方便地使用了.这些语法糖虽然不会提供实质性的功能改进,但是它们或能提高性能.或能提升语法的严谨性.或能减少编码出错的机会.Java提供给了用户大量的语法糖,比如泛型.自动装箱.自动拆箱.foreach循环.变长参数.内部类.枚举类.断言(assert)等 断言(as

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

语法糖 接下来几篇文章要开启一个Java语法糖系列,所以首先讲讲什么是语法糖.语法糖是一种几乎每种语言或多或少都提供过的一些方便程序员开发代码的语法,它只是编译器实现的一些小把戏罢了,编译期间以特定的字节码或者特定的方式对这些语法做一些处理,开发者就可以直接方便地使用了.这些语法糖虽然不会提供实质性的功能改进,但是它们或能提高性能.或能提升语法的严谨性.或能减少编码出错的机会.Java提供给了用户大量的语法糖,比如泛型.自动装箱.自动拆箱.foreach循环.变长参数.内部类.枚举类.断言(as

Java语法糖设计

语法糖 Java语法糖系列,所以首先讲讲什么是语法糖.语法糖是一种几乎每种语言或多或少都提供过的一些方便程序员开发代码的语法,它只是编译器实现的一些小把戏罢了,编译期间以特定的字节码或者特定的方式对这些语法做一些处理,开发者就可以直接方便地使用了.这些语法糖虽然不会提供实质性的功能改进,但是它们或能提高性能.或能提升语法的严谨性.或能减少编码出错的机会.Java提供给了用户大量的语法糖,比如泛型.自动装箱.自动拆箱.foreach循环.变长参数.内部类.枚举类.断言(assert)等 断言(as

Java语法糖(3):泛型

泛型初探 在泛型(Generic type或Generics)出现之前,是这么写代码的: public static void main(String[] args){List list = new ArrayList();list.add("123");list.add("456"); System.out.println((String)list.get(0));}当然这是完全允许的,因为List里面的内容是Object类型的,自然任何对象类型都可以放入.都可以

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

语法糖(Syntactic Sugar),也叫糖衣语法,是英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语.指的是,在计算机语言中添加某种语法,这些语法糖虽然不会对语言的功能产生任何影响,却能使程序员更方便的使用语言开发程序,同时增强程序代码的可读性,避免出错的机会.但是如果只是大量添加和使用语法糖,却不去了解他,容易产生过度依赖,从而无法看清语法糖的糖衣背后,程序代码的真实面目. 总而言之,语法糖可以看做是编译器实现的一些"小把戏",这些"小

Java语法糖1:可变长度参数

先抄一段定义: 语法糖(Syntactic Sugar),也称糖衣语法,是由英国计算机学家 Peter.J.Landin 发明的一个术语,指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用.Java 中最常用的语法糖主要有泛型.变长参数.条件编译.自动拆装箱.内部类等.虚拟机并不支持这些语法,它们在编译阶段就被还原回了简单的基础语法结构,这个过程成为解语法糖. 简而言之就是语法糖就是为了方便编程,但不影响语言本身功能的情况下做的一种语法处理.虽然没有对语言本身有

Java 语法糖详解

语法糖 语法糖(Syntactic Sugar),也称糖衣语法,是由英国计算机学家 Peter.J.Landin 发明的一个术语,指在计算机语言中添加的某种语法. 这种语法对语言的功能并没有影响,但是更方便程序员使用.简而言之,语法糖让程序更加简洁,有更高的可读性. 有意思的是,在编程领域,除了语法糖,还有语法盐和语法糖精的说法,篇幅有限这里不做扩展了. 我们所熟知的编程语言中几乎都有语法糖.作者认为,语法糖的多少是评判一个语言够不够牛逼的标准之一. 很多人说 Java 是一个 "低糖语言&qu

Java语法糖初探(三)--变长参数

变长参数概念 在Java5 中提供了变长参数(varargs),也就是在方法定义中可以使用个数不确定的参数,对于同一方法可以使用不同个数的参数调用.形如 function(T -args).但是需要明确的一点是,java方法的变长参数只是语法糖,其本质上还是将变长的实际参数 varargs 包装为一个数组. 看下面的例子: 12345678910111213 public class VariVargs { public static void main(String []args) { tes

从jvm角度来解析java语法糖

java有很多语法糖,比如自动拆箱,自动装箱,foreach等等,这些原理相信每一个入门教程里都有讲,但是我相信不是每一个人 都通过查看这些语法糖的字节码来确认这些原理,因为我也是现在才想看一下. 1.自动拆箱和自动装箱 public void test() { Integer integer = 1; int i = integer; } //将常量1放入操作数栈 0: iconst_1 //调用Integer.valueOf 入参为0操作指令压入的1 1: invokestatic #2 /