疯狂Java讲义(四)

1.控制循环结构

Java语言没有提供 goto语句来控制程序的跳转,这种做法提高了程序流程控制的可读性,但降低了程序流程控制的灵活性。为了弥补这种不足,Java提供了continue和 break 来控制循环结构。除此之外,return可以结束整个方法,当然也就结束了一次循环。

  使用break结束循环

        break语句不仅可以结束其所在的循环,还可以直接结束其外层循环。此时需要在 break后紧跟一个标签,这个标签用于标识一个外层循环。


        Java中的标签就是一个紧跟着英文冒号(:)的标识符。与其他语言不同的是,Java 中的标签只有放在循环语句之前才有作用。例如下面代码。

        程序从外层循环进入内层循环后,当j等于1时,程序遇到一个 break outer;语句,这行代码将会导致结束outer标签指定的循环,不是结束break所在的循环,而是结束break循环的外层循环。所以看到上面的运行结果。
        值得指出的是,break后的标签必须是一个有效的标签,即这个标签必须在 break语句所在的循环之前定义,或者在其所在循环的外层循环之前定义。当然,如果把这个标签放在 break语句所在的循环之前定义,也就失去了标签的意义,因为break 默认就是结束其所在的循环。

  使用continue忽略本次循环剩下语句

        与break类似的是,continue后也可以紧跟一个标签,用于直接跳过标签所标识循环的当次循环的剩下语句,重新开始下一次循环。例如下面代码。

 

         运行上面程序可以看到,循环变量j的值将无法超过1,因为每当j等于1时,continue outer;语句就结束了外层循环的当次循环,直接开始下一次循环,内层循环没有机会执行完成
        与break 类似的是,continue后的标签也必须是一个有效标签,即这个标签通常应该放在continue所在循环的外层循环之前定义。

  使用return结束方法

        return关键字并不是专门用于结束循环的,return的功能是结束一个方法。当一个方法执行到一个return 语句时(return关键字后还可以跟变量、常量和表达式,这将在方法介绍中有更详细的解释),这个方法将被结束。

2.数组类型

   定义数组:

Java 语言支持两种语法格式来定义数组:

         对这两种语法格式而言,通常推荐使用第一种格式。因为第一种格式不仅具有更好的语意,而且具有更好的可读性。对于type[] arrayName;方式,很容易理解这是定义一个变量,其中变量名是 arrayName,而变量类型是type[]。前面已经指出: type[]确实是一种新类型,与 type类型完全不同(例如 int类型是基本类型,但int[]是引用类型)。因此,这种方式既容易理解,也符合定义变量的语法。但第二种格式type arrayName[]的可读性就差了,看起来好像定义了一个类型为type的变量,而变量名是arrayName[],这与真实的含义相去甚远。
        可能有些读者非常喜欢 type arrayName[];这种定义数组的方式,这可能是因为早期某些计算机读物的误导,从现在开始就不要再使用这种糟糕的方式了

注意:

 数组是一种引用类型的变量,因此使用它定义一个变量时,仅仅表示定义了一个引用变量(也就是定义了一个指针),这个引用变量还未指向任何有效的内存,因此定义数组时不能指定数组的长度。而且由于定义数组只是定义了一个引用变量,并未指向任何有效的内存空间,所以还没有内存空间来存储数组元素,因此这个数组也不能使用,只有对数组进行初始化后才可以使用。

  数组初始化

 数组的初始化有如下两种方式。

  •   静态初始化:初始化时由程序员显式指定每个数组元素的初始值,由系统决定数组长度
  •   动态初始化:初始化时程序员只指定数组长度,由系统为数组元素分配初始值

静态初始化

 因为Java语言是面向对象的编程语言,能很好地支持子类和父类的继承关系:子类实例是一种特殊的父类实例。在上面程序中,String类型是Object类型的子类,即字符串是一种特殊的Object实例。关于继承更详细的介绍,请参考本书第5章。

动态初始化:

  使用数组:

        如果访问数组元素时指定的索引值小于0,或者大于等于数组的长度,编译程序不会出现任何错误,但运行时出现异常: java.lang.ArrayIndexOutOfBoundsException: N (数组索引越界异常),异常信息后的N就是程序员试图访问的数组索引。

所有的数组都提供了一个length属性,通过这个属性可以访问到数组的长度,一旦获得了数组的长度,就可以通过循环来遍历该数组的每个数组元素。下面代码示范了输出prices数组(动态初始化的int[]数组)的每个数组元素的值(程序清单同上)。

3.foreach循环

        从Java 5之后,Java 提供了一种更简单的循环: foreach 循环,这种循环遍历数组和集合(关于集合的介绍请参考本书第8章)更加简洁。使用foreach循环遍历数组和集合元素时,无须获得数组和集合长度,无须根据索引来访问数组元素和集合元素,foreach 循环自动遍历数组和集合的每个元素。

         从上面程序可以看出,使用foreach循环遍历数组元素时无须获得数组长度,也无须根据索引来访问数组元素。foreach 循环和普通循环不同的是,它无须循环条件,无须循环迭代语句,这些部分都由系统来完成,foreach 循环自动迭代数组的每个元素,当每个元素都被迭代一次后, foreach 循环自动结束。

4.深入数组

        数组是一种引用数据类型,数组引用变量只是一个引用,数组元素数组变量在内存里是分开存放的。下面将深入介绍数组在内存中的运行机制。

  内存中的数组

        实际的数组对象被存储在堆(heap)内存中;如果引用该数组对象的数组引用变量是一个局部变量,那么它被存储在栈(stack) 内存中。数组在内存中的存储示意图如图4.2所示。

        如果需要访问如图4.2所示堆内存中的数组元素,则程序中只能通过p[index]的形式实现。也就是说,数组引用变量是访问堆内存中数组元素的根本方式

 只要类型相互兼容,就可以让一个数组变量指向另一个实际的数组,这种操作会让人产生数组的长度可变的错觉。如下代码所示。

         运行上面代码后,将可以看到先输出b数组的长度为4,然后依次输出a数组和b数组的每个数组元素,接着会输出b数组的长度为3看起来似乎数组的长度是可变的,但这只是一个假象。必须牢记:

        定义并初始化一个数组后,在内存中分配了两个空间,一个用于存放数组的引用变量,另一个用于存放数组本身。下面将结合示意图来说明上面程序的运行过程。

        当程序定义并初始化了a、b两个数组后,系统内存中实际上产生了4块内存区,其中栈内存中有两个引用变量: a和b;堆内存中也有两块内存区,分别用于存储a和b引用所指向的数组本身。此时计算机内存的存储示意图如图4.3所示。

 

         当执行上面的粗体字标识代码b=a时,系统将会把a的值赋给b,a和b都是引用类型变量,存储的是地址。因此把a的值赋给b后,就是让b指向a所指向的地址。此时计算机内存的存储示意图如图4.4所示。

        从图4.4中可以看出,当执行了b = a;之后,堆内存中的第一个数组具有了两个引用: a变量和b变量都引用了第一个数组。此时第二个数组失去了引用,变成垃圾,只有等待垃圾回收机制来回收它一但它的长度依然不会改变,直到它彻底消失

 5.引用类型数组的初始化

 

 

         从图4.11中可以看出,此时zhang和students[0]指向同一个内存区,而且它们都是引用类型变量,因此通过zhang和students[0]来访问Person实例的实例变量和方法的效果完全一样,不论修改students[0]所指向的Person实例的实例变量,还是修改zhang变量所指向的Person实例的实例变量,所修改的其实是同一个内存区,所以必然互相影响。同理,lee 和students[1]也是引用同一个Person对象,也具有相同的效果。

6.没有多维数组

        Java语言里提供了支持多维数组的语法。但本书还是想说,没有多维数组一一如果从数组底层的运行机制上来看。
        Java语言里的数组类型是引用类型,因此数组变量其实是一个引用,这个引用指向真实的数组内存数组元素的类型也可以是引用,如果数组元素的引用再次指向真实的数组内存,这种情形看上去很像多维数组。

        接着对这个“二维数组”执行初始化,同样可以把这个数组当成一维数组来初始化,把这个“二维数组”当成一个-维数组,其元素的类型是type[]类型,则可以采用如下语法进行初始化:

 

         从上面程序中可以看出,初始化多维数组时,可以只指定最左边维的大小;当然,也可以一次指定每一维的大小。 例如下面代码(程序清单同上):

 

         还可以使用静态初始化方式来初始化二维数组。使用静态初始化方式来初始化二维数组时,二维数组的每个数组元素都是一维数组,因此必须指定多个一维数组作为二维数组的初始化值。如下代码所示(程序清单同上):

7. Java 8 增强的工具包: Arrays

        Java提供的Arrays 类里包含的一些static修饰的方法可以直接操作数组,这个Arrays 类里包含了如下几个static修饰的方法(static修饰的方法可以直接通过类名调用)。

 代码:

         除此之外,在System类里也包含了一个static void arraycopy(Object sre, int srcPos, Object dest, int  destPos, int length)方法,该方法可以将sre数组里的元素值赋给dest数组的元素,其中srcPos指定从src数组的第几个元素开始赋值,length参数指定将sre数组的多少个元素值赋给dest数组的元素。
        Java 8增强了Arrays 类的功能,为Arrays类增加了一些工具方法,这些工具方法可以充分利用多CPU并行的能力来提高设值、排序的性能。下 面是Java 8为Arrays类增加的工具方法。

 上面方法列表中,所有以parallel开头的方法都表示该方法可利用CPU并行的能力来提高性能。上面方法中的xxx代表不同的数据类型,比如处理int[]型数组时应将xxx换成int,处理long[]型数组时应将xxx换成long。

散称知识点:

  • JDK5提供了一种新的循环: foreach循环,能以更简单的方式来遍历集合、数组的元素。

并且Java7增强了switch 语句的功能,允许switch语句的控制表达式是java.lang.String类型的变量或表达式——只能是java.lang.String类型,不能StringBuffer或StringBuilder这两种字符串类型。

  • 把for循环的初始化语句放在循环之前定义还有一个作用:可以扩大初始化语句中所定义变量的作用域。在 for 循环里定义的变量,其作用域仅在该循环内有效,for 循环终止以后,这些变量将不可被访问。

猜你喜欢

转载自blog.csdn.net/indeedes/article/details/120832553
今日推荐