• 可变长度参数以及foreach循环原理


    语法糖 

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

    可变长度参数

    先讲可变长度参数,看一段代码:

    复制代码
    public static void main(String[] args)
    {
        print("000", "111", "222", "333");
    }
        
    public static void print(String... strs)
    {
        for (int i = 0; i < strs.length; i++)
        {
            System.out.println(strs[i]);
        }
    }
    复制代码

    print方法的参数的意思是表示传入的String个数是不定的,看一下代码的运行结果:

    000
    111
    222
    333

    我用数组遍历的方式成功地将输入的参数遍历出来了,这说明两个问题:

    1、可以使用遍历数组的方式去遍历可变参数

    2、可变参数是利用数组实现的

    既然这样,那我其实main函数也可以这么写,完全可以:

    String[] strs = {"000", "111", "222", "333"};
    print(strs);

    那直接传入一个数组不就好了?问题是,数组是要指定长度的,万一这次我想传2个String,下次我想传3个String怎么办呢?

    最后,注意一点,可变长度参数必须作为方法参数列表中的的最后一个参数且方法参数列表中只能有一个可变长度参数

    foreach循环原理

    以前对foreach循环就是这么用着,触动我去研究foreach循环的原理的原因是大概两个月前,自己写了一个ArrayList,想用foreach循环遍历一下看一下写的效果,结果报了空指针异常。本文就写写foreach循环的原理,先看一下这么一段代码:

    复制代码
    public static void main(String[] args)
    {
        List<String> list = new ArrayList<String>();
        list.add("111");
        list.add("222");
        
        for (String str : list)
        {
            System.out.println(str);
        }
    }
    复制代码

    用foreach循环去遍历这个list,结果就不说了,都知道。看一下Java是如何处理这个foreach循环的,javap反编译一下:

    F:代码MyEclipseTestArticleincomxrq	est21>javap -verbose TestMain.class

    反编译出来的内容很多,有类信息、符号引用、字节码信息,截取一段信息:

    复制代码
     1   public static void main(java.lang.String[]);
     2     flags: ACC_PUBLIC, ACC_STATIC
     3     Code:
     4       stack=2, locals=4, args_size=1
     5          0: new           #16                 // class java/util/ArrayList
     6          3: dup
     7          4: invokespecial #18                 // Method java/util/ArrayList."<in
     8 it>":()V
     9          7: astore_1
    10          8: aload_1
    11          9: ldc           #19                 // String 111
    12         11: invokeinterface #21,  2           // InterfaceMethod java/util/List.
    13 add:(Ljava/lang/Object;)Z
    14         16: pop
    15         17: aload_1
    16         18: ldc           #27                 // String 222
    17         20: invokeinterface #21,  2           // InterfaceMethod java/util/List.
    18 add:(Ljava/lang/Object;)Z
    19         25: pop
    20         26: aload_1
    21         27: invokeinterface #29,  1           // InterfaceMethod java/util/List.
    22 iterator:()Ljava/util/Iterator;
    复制代码

    看不懂没关系,new、dup、invokespecial这些本来就是字节码指令表内定义的指令,虚拟机会根据这些指令去执行指定的C++代码,完成每个指令的功能。关键看到21、22这两行就可以了,看到了一个iterator,所以得出结论:在编译的时候编译器会自动将对for这个关键字的使用转化为对目标的迭代器的使用,这就是foreach循环的原理。进而,我们再得出两个结论:

    1、ArrayList之所以能使用foreach循环遍历,是因为ArrayList所有的List都是Collection的子接口,而Collection是Iterable的子接口,ArrayList的父类AbstractList正确地实现了Iterable接口的iterator方法。之前我自己写的ArrayList用foreach循环直接报空指针异常是因为我自己写的ArrayList并没有实现Iterable接口

    2、任何一个集合,无论是JDK提供的还是自己写的,只要想使用foreach循环遍历,就必须正确地实现Iterable接口

    实际上,这种做法就是23中设计模式中的迭代器模式

    数组呢?

    上面的讲完了,好理解,但是不知道大家有没有疑问,至少我是有一个疑问的:数组并没有实现Iterable接口啊,为什么数组也可以用foreach循环遍历呢?先给一段代码,再反编译:

    复制代码
    public static void main(String[] args)
    {
        int[] ints = {1,2,3,4,5};
            
        for (int i : ints)
            System.out.println(i);
    }
    复制代码

    同样反编译一下,看一下关键的信息:

    复制代码
     1          0: iconst_2
     2          1: newarray       int
     3          3: dup
     4          4: iconst_0
     5          5: iconst_1
     6          6: iastore
     7          7: dup
     8          8: iconst_1
     9          9: iconst_2
    10         10: iastore
    11         11: astore_1
    12         12: aload_1
    13         13: dup
    14         14: astore        5
    15         16: arraylength
    16         17: istore        4
    17         19: iconst_0
    18         20: istore_3
    19         21: goto          39
    20         24: aload         5
    21         26: iload_3
    22         27: iaload
    23         28: istore_2
    24         29: getstatic     #16                 // Field java/lang/System.out:Ljav
    25 a/io/PrintStream;
    26         32: iload_2
    27         33: invokevirtual #22                 // Method java/io/PrintStream.prin
    28 tln:(I)V
    29         36: iinc          3, 1
    30         39: iload_3
    31         40: iload         4
    32         42: if_icmplt     24
    33         45: return
    复制代码

    这是完整的这段main函数对应的45个字节码指令,因为这涉及一些压栈、出栈、推送等一些计算机原理性的内容且对于这些字节码指令的知识的理解需要一些C++的知识,所以就不解释了。简单对照字节码指令表之后,我个人对于这45个字节码的理解是Java将对于数组的foreach循环转换为对于这个数组每一个的循环引用。其实就是将foreache拆成for循环去执行,循环每一个数组内的内容。

    转载:http://www.cnblogs.com/xrq730/p/4868465.html

  • 相关阅读:
    Linux常用命令,touch命令,vi和vim命令,文件查看命令,文本处理命令,备份压缩命令,网络与磁盘命令,软件安装命令
    Linux命令整理,用户管理命令,用户组管理命令,系统管理命令,目录管理常用命令
    单元测试基本步骤?单元测试三个常用注解?日志体系结构和log4j,Log4j三个核心?
    cookies,sessionStorage 和 localStorage 之间有什么区别?
    filter() 、find()、findIndex()
    设置多行文本隐藏显示省略号时样式丢失了
    react 中封装一个简单的滚动条组件
    react-router-cache-router
    浅谈React 中 Component与PureComponent如何使用
    React.Fragment
  • 原文地址:https://www.cnblogs.com/haitaofeiyang/p/7716658.html
Copyright © 2020-2023  润新知