corejava11(3.10 数组)

3.10 数组

数组保存同一类型的值序列。在下面的部分中,您将看到如何在Java中使用数组。

3.10.1 声明数组

数组是存储同一类型值集合的数据结构。您可以通过一个整数索引访问每个单独的值。例如,如果a是整数数组,则[i]是数组中的第i个整数。

通过指定数组类型来声明数组变量,该数组类型是元素类型后跟[]-和数组变量名。例如,下面是整数数组a的声明:

int[] a;

但是,此语句只声明变量a。它还没有用实际数组初始化。使用新的运算符创建数组。

int[] a = new int[100]; // or var a = new int[100];

此语句声明并初始化一个100个整数的数组。

数组长度不必是常量:new int[n]创建一个长度为n的数组。

创建数组后,就不能更改其长度(当然,可以更改单个数组元素)。如果您经常需要在程序运行时扩展数组的长度,您应该使用第5章中介绍的数组列表。

注意

可以将数组变量定义为

int[] a;

或者

int a[];

大多数Java程序员更喜欢前一种样式,因为它将类型int[](整型数组)与变量名整齐地分隔开来。

Java有一个创建数组对象并提供初始值的快捷方式:

int[] smallPrimes = { 2, 3, 5, 7, 11, 13 };

请注意,此语法不使用new,也不指定长度。

允许在最后一个值后使用逗号,这对于一个随时间不断向其添加值的数组很方便:

String[] authors = {
    "James Gosling",
    "Bill Joy",
    "Guy Steele",
    // add more names here and put a comma after each name
};

可以声明匿名数组:

new int[] { 17, 19, 23, 29, 31, 37 }

此表达式分配一个新数组,并用大括号内的值填充它。它计算初始值的数量并相应地设置数组大小。可以使用此语法在不创建新变量的情况下重新初始化数组。例如,

smallPrimes = new int[] { 17, 19, 23, 29, 31, 37 };

等价于

int[] anonymous = { 17, 19, 23, 29, 31, 37 };
smallPrimes = anonymous;

注意

长度为0的数组是合法的。如果编写一个方法来计算数组结果,而结果恰好为空,那么这样的数组可能很有用。将长度为0的数组构造为

new elementType[0]

或者

new elementType[] {}

请注意,长度为0的数组与空数组不同。

3.10.2 访问数组元素

数组元素的编号从0到99(而不是1到100)。创建数组后,可以使用循环填充数组中的元素:

int[] a = new int[100];
for (int i = 0; i < 100; i++)
	a[i] = i; // fills the array with numbers 0 to 99

创建数字数组时,所有元素都初始化为零。布尔值数组初始化为false。对象数组初始化为特殊值空,这表示它们还没有保存任何对象。这对初学者来说是令人惊讶的。例如,

String[] names = new String[10];

创建十个字符串的数组,所有字符串均为空。如果希望数组包含空字符串,则必须提供它们:

for (int i = 0; i < 10; i++) names[i] = "";

小心

如果构造一个包含100个元素的数组,然后尝试访问元素a[100](或0到99范围之外的任何其他索引),则会出现“数组索引越界”异常。

要查找数组的元素数,请使用array.length。例如:

for (int i = 0; i < a.length; i++)
	System.out.println(a[i]);

3.10.3 for each循环

Java具有强大的循环结构,允许您循环使用数组中的每个元素(或任何其他元素集合),而不必对索引值感到困惑。

for (variable : collection) statement

将给定变量设置为集合的每个元素,然后执行语句(当然,语句可能是块)。集合表达式必须实现Iterable接口(如ArrayList)的数组或类的对象。我们将在第5章讨论数组列表,并在第9章讨论Iterable接口。

例如,

for (int element : a)
	System.out.println(element);

每一行打印打印数组a的每个元素。

您应该将此循环理解为“针对a中的每个元素”。Java语言的设计者考虑使用关键字,如foreach和in。但是这个循环是对Java语言的后期添加,最后没有人想打破已经包含了这些名称的方法或变量的旧代码(例如System.in)。

当然,您可以使用传统的for循环实现相同的效果:

for (int i = 0; i < a.length; i++)
	System.out.println(a[i]);

但是,“for each”循环更简洁,也不容易出错,因为您不必担心那些烦人的开始和结束索引值。

注意

“for each”循环的循环变量遍历数组的元素,而不是索引值。

如果需要处理集合中的所有元素,“for each”循环比传统循环是一个令人愉快的改进。然而,仍然有很多机会使用传统的for循环。例如,您可能不希望遍历整个集合,或者可能需要循环内的索引值。

提示

使用Arrays类的toString方法,有一种更简单的方法可以打印数组的所有值。调用Arrays.toString(a)返回一个包含数组元素的字符串,用括号括起来,用逗号分隔,如“[2,3,5,7,11,13]”。要打印数组,只需调用

System.out.println(Arrays.toString(a));

3.10.4 数组拷贝

您可以将一个数组变量复制到另一个数组变量中,但随后这两个变量引用同一个数组:

int[] luckyNumbers = smallPrimes;
luckyNumbers[5] = 12; // now smallPrimes[5] is also 12

图3.14显示了结果。如果要将一个数组的所有值复制到新数组中,请使用Arrays类中的copyOf方法:

图3.14 复制数组变量

int[] copiedLuckyNumbers = Arrays.copyOf(luckyNumbers, luckyNumbers.length);

第二个参数是新数组的长度。此方法的常见用法是增大数组的大小:

luckyNumbers = Arrays.copyOf(luckyNumbers, 2 * luckyNumbers.length);

如果数组包含数字,则其他元素将填充为0;如果数组包含布尔值,则填充为false。相反,如果长度小于原始数组的长度,则只复制初始值。

C++注意

Java数组与堆栈上的C++数组有很大的不同。但是,它本质上与指向堆上分配的数组的指针相同。也就是说,

int[] a = new int[100]; // Java

不同于

int a[100]; // C++

而是

int* a = new int[100]; // C++

在Java中,[]操作符被预定义来执行边界检查。此外,没有指针算法,您不能用增量a来指向数组中的下一个元素。

3.10.5 命令行参数

您已经看到一个Java数组的例子重复了好几次。每个Java程序都有一个带有String[] args参数的main方法。此参数指示主方法接收字符串数组,即命令行上指定的参数。

例如,考虑这个程序:

public class Message
{
    public static void main(String[] args)
    {
        if (args.length == 0 || args[0].equals("-h"))
        	System.out.print("Hello,");
        else if (args[0].equals("-g"))
        	System.out.print("Goodbye,");
        // print the other command-line arguments
        for (int i = 1; i < args.length; i++)
        	System.out.print(" " + args[i]);
        System.out.println("!");
    }
}

如果程序如下调用

java Message -g cruel world

args数组会有下面的内容:

args[0]: "-g"
args[1]: "cruel"
args[2]: "world"

程序打印出消息

Goodbye, cruel world!

C++ 注意

在Java程序的main方法中,程序的名字没有在args数组中。例如,当你启动一个程序

java Message -h world

通过命令行,然后args[0]将会是"-h",并且不是"Message"或者"java"。

3.10.6 数组排序

为了排序一个数组的数字,你能够使用Arrays类中的一个sort方法

int[] a = new int[10000];
...
Arrays.sort(a);

这个方法使用一个优化版本的快排算法,它在大多数数据集中都很高效。Arrays类为数组提供几个其它方便的方法,它们在本节的最后API注释部分被包含。

清单3.7中的程序使数组工作。这个程序为一个彩票游戏随机抽取数字组合。例如,如果您玩“从49中选择6个数字”彩票,程序可能会打印以下内容:

Bet the following combination. It'll make you rich!
    4
    7
    8
    19
    30
    44

为了选择这样一组随机数字,我们首先用值1、2、……,n填充一个数组numbers:

int[] numbers = new int[n];
for (int i = 0; i < numbers.length; i++)
	numbers[i] = i + 1;

第二个数组保存要绘制的数字:

int[] result = new int[k];

现在我们抽取k个数字。Math.random方法返回一个介于0(含)和1(不含)之间的随机浮点数。通过将结果与n相乘,我们得到一个介于0和n-1之间的随机数。

int r = (int) (Math.random() * n);

我们将第i个结果设置为该索引处的数字。最初,这只是r+1,但正如您现在看到的,number数组的内容在每次抽取后都会发生更改。

result[i] = numbers[r];

现在我们必须确保再也不要抽那个号码。所有的彩票号码都必须是不同的。因此,我们用数组中的最后一个数字覆盖numbers[r],并将n减少1。

numbers[r] = numbers[n - 1];
n--;

关键是,在每次抽取中,我们都选择一个索引,而不是实际值。索引指向包含尚未抽取的值的数组。

在抽取k个彩票号码后,我们对结果数组进行排序,以获得更令人满意的输出:

Arrays.sort(result);
for (int r : result)
	System.out.println(r);

清单3.7 LotteryDrawing/LotteryDrawing.java

import java.util.*;

/**
 * This program demonstrates array manipulation.
 * @version 1.20 2004-02-10
 * @author Cay Horstmann
 */
public class LotteryDrawing
{
   public static void main(String[] args)
   {
      Scanner in = new Scanner(System.in);

      System.out.print("How many numbers do you need to draw? ");
      int k = in.nextInt();

      System.out.print("What is the highest number you can draw? ");
      int n = in.nextInt();

      // fill an array with numbers 1 2 3 . . . n
      int[] numbers = new int[n];
      for (int i = 0; i < numbers.length; i++)
         numbers[i] = i + 1;

      // draw k numbers and put them into a second array
      int[] result = new int[k];
      for (int i = 0; i < result.length; i++)
      {
         // make a random index between 0 and n - 1
         int r = (int) (Math.random() * n);

         // pick the element at the random location
         result[i] = numbers[r];

         // move the last element into the random location
         numbers[r] = numbers[n - 1];
         n--;
      }

      // print the sorted array
      Arrays.sort(result);
      System.out.println("Bet the following combination. It'll make you rich!");
      for (int r : result)
         System.out.println(r);
   }
}

java.util.Arrays 1.2

  • static String toString(xxx[] a) 5
    返回一个字符串,其中的元素a用括号括起来,用逗号分隔。在此方法和以下方法中,数组的组件类型xxx可以是int、long、short、char、byte、boolean、float或double。
  • static xxx[] copyOf(xxx[] a, int end) 6
  • static xxx[] copyOfRange(xxx[] a, int start, int end) 6
    返回一个与a类型相同的数组,长度为end或end-start,并用a的值填充。如果end大于a.length,则结果将用0或false值填充。
  • static void sort(xxx[] a)
    使用优化的快速排序算法对数组排序。
  • static int binarySearch(xxx[] a, xxx v)
  • static int binarySearch(xxx[] a, int start, int end, xxx v) 6
    使用二分查找算法搜索已排序数组a中的值v。如果找到v,则返回其索引。否则,返回负值r;-r-1是插入v以保持排序的点。
  • static void fill(xxx[] a, xxx v)
    将数组的所有元素设置为v。
  • static boolean equals(xxx[] a, xxx[] b)
    如果数组的长度相同,并且相应索引处的元素匹配,则返回true。

3.10.7 多维数组

多维数组使用多个索引来访问数组元素。它们用于表和其他更复杂的东西。您可以安全地跳过此部分,直到需要此存储机制为止。

假设你想做一个数字表,显示10000美元的投资在每年支付利息和再投资的不同利率情况下会增长多少(表3.8)。

表3.8 不同利率下的投资增长

10%11%12%13%14%15%
10,000.0010,000.0010,000.0010,000.0010,000.0010,000.00
11,000.0011,100.0011,200.0011,300.0011,400.0011,500.00
12,100.0012,321.0012,544.0012,769.0012,996.0013,225.00
13,310.0013,676.3114,049.2814,428.9714,815.4415,208.75
14,641.0015,180.7015,735.1916,304.7416,889.6017,490.06
16,105.1016,850.5817,623.4218,424.3519,254.1520,113.57
17,715.6118,704.1519,738.2320,819.5221,949.7323,130.61
19,487.1720,761.6022,106.8123,526.0525,022.6926,600.20
21,435.8923,045.3824,759.6326,584.4428,525.8630,590.23
23,579.4825,580.3727,730.7930,040.4232,519.4935,178.76

您可以将这些信息存储在二维数组(矩阵)中,我们称之为余额。

在Java中声明二维数组是很简单的。例如:

double[][] balances;

在初始化数组之前,不能使用该数组。在这种情况下,可以按如下方式进行初始化:

balances = new double[NYEARS][NRATES];

在其他情况下,如果知道数组元素,则可以使用一个简短的符号来初始化多维数组,而无需调用new。例如:

int[][] magicSquare =
{
    {16, 3, 2, 13},
    {5, 10, 11, 8},
    {9, 6, 7, 12},
    {4, 15, 14, 1}
};

初始化数组后,可以通过提供两对括号(例如balances[i][j])来访问单个元素。

示例程序存储一个一维利率数组和一个二维帐户余额数组,每年一个,利率一个。我们用初始余额初始化数组的第一行:

for (int j = 0; j < balances[0].length; j++)
	balances[0][j] = 10000;

然后我们计算其他行,如下所示:

for (int i = 1; i < balances.length; i++)
{
    for (int j = 0; j < balances[i].length; j++)
    {
        double oldBalance = balances[i - 1][j];
        double interest = . . .;
        balances[i][j] = oldBalance + interest;
    }
}

清单3.8显示了完整的程序。

注意

“for each”循环不会自动循环二维数组中的所有元素。相反,它通过行进行循环,这些行本身就是一维数组。要访问二维数组a的所有元素,请嵌套两个循环,如下所示:

for (double[] row : a)
    for (double value : row)
    	do something with value

提示

要打印出二维数组元素的快速脏列表,请调用

System.out.println(Arrays.deepToString(a));

输出结果的格式如下所示:

[[16, 3, 2, 13], [5, 10, 11, 8], [9, 6, 7, 12], [4, 15, 14, 1]]

清单3.8 CompoundInterest/CompoundInterest.java

/**
 * This program shows how to store tabular data in a 2D array.
 * @version 1.40 2004-02-10
 * @author Cay Horstmann
 */
public class CompoundInterest
{
   public static void main(String[] args)
   {
      final double STARTRATE = 10;
      final int NRATES = 6;
      final int NYEARS = 10;

      // set interest rates to 10 . . . 15%
      double[] interestRate = new double[NRATES];
      for (int j = 0; j < interestRate.length; j++)
         interestRate[j] = (STARTRATE + j) / 100.0;

      double[][] balances = new double[NYEARS][NRATES];

      // set initial balances to 10000
      for (int j = 0; j < balances[0].length; j++)
         balances[0][j] = 10000;

      // compute interest for future years
      for (int i = 1; i < balances.length; i++)
      {
         for (int j = 0; j < balances[i].length; j++)
         {
            // get last year's balances from previous row
            double oldBalance = balances[i - 1][j];

            // compute interest
            double interest = oldBalance * interestRate[j];

            // compute this year's balances
            balances[i][j] = oldBalance + interest;
         }
      }

      // print one row of interest rates
      for (int j = 0; j < interestRate.length; j++)
         System.out.printf("%9.0f%%", 100 * interestRate[j]);

      System.out.println();

      // print balance table
      for (double[] row : balances)
      {
         // print table row
         for (double b : row)
            System.out.printf("%10.2f", b);

         System.out.println();
      }
   }
}

3.10.8 不规则数组

到目前为止,您所看到的与其他编程语言没有太大的不同。但是在幕后确实有一些微妙的东西,你有时可以求助于你的优势:Java根本没有多维数组,只有一维数组。多维数组被伪造为“数组的数组”。

例如,前面例子中的balances数组实际上是一个包含十个元素的数组,每个元素都是六个浮点数的数组(图3.15)。

图3.15 一个二维数组

表达式balances[i]是指第i个子数组,即表的第i行。它本身就是一个数组,balances[i][j]指的是该数组的第j个元素。

因为可以单独访问数组行,所以实际上可以交换它们!

double[] temp = balances[i];
balances[i] = balances[i + 1];
balances[i + 1] = temp;

制作“不规则”数组也很容易,也就是说,不同行具有不同长度的数组。下面是标准示例。让我们制作一个数组,其中第i行和第j列的元素等于“从i个数字中选择j个数字”彩票可能产生的结果数。

1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1 6 15 20 15 6 1

因为j不能大于i,所以矩阵是三角形的。第i行有i+1个元素。(我们允许选择0个元素;有一种方法可以做出这样的选择。)要构建这个不规则数组,首先分配包含行的数组:

int[][] odds = new int[NMAX + 1][];

接下来,分配行:

for (int n = 0; n <= NMAX; n++)
	odds[n] = new int[n + 1];

既然已经分配了数组,那么只要不超出界限,我们就可以以正常方式访问元素:

for (int n = 0; n < odds.length; n++)
    for (int k = 0; k < odds[n].length; k++)
    {
        // compute lotteryOdds
        . . .
        odds[n][k] = lotteryOdds;
    }

清单3.9给出了完整的程序。

C++注意

在C++中,Java声明

double[][] balances = new double[10][6]; // Java

不同于

double balances[10][6]; // C++

或者甚至

double (*balances)[6] = new double[10][6]; // C++

相反,将分配一个由十个指针组成的数组:

double** balances = new double*[10]; // C++

然后,指针数组中的每个元素都被六个数字组成的数组填充:

for (i = 0; i < 10; i++)
	balances[i] = new double[6];

谢天谢地,当你要求一个new double[10][6]时,这个循环是自动的。当需要不规则数组时,可以单独分配行数组。

清单3.9 LotteryArray/LotteryArray.java

/**
 * This program demonstrates a triangular array.
 * @version 1.20 2004-02-10
 * @author Cay Horstmann
 */
public class LotteryArray
{
   public static void main(String[] args)
   {
      final int NMAX = 10;

      // allocate triangular array
      int[][] odds = new int[NMAX + 1][];
      for (int n = 0; n <= NMAX; n++)
         odds[n] = new int[n + 1];

      // fill triangular array
      for (int n = 0; n < odds.length; n++)
         for (int k = 0; k < odds[n].length; k++)
         {
            /*
             * compute binomial coefficient n*(n-1)*(n-2)*...*(n-k+1)/(1*2*3*...*k)
             */
            int lotteryOdds = 1;
            for (int i = 1; i <= k; i++)
               lotteryOdds = lotteryOdds * (n - i + 1) / i;

            odds[n][k] = lotteryOdds;
         }

      // print triangular array
      for (int[] row : odds)
      {
         for (int odd : row)
            System.out.printf("%4d", odd);
         System.out.println();
      }
   }
}

现在你已经看到了Java语言的基本编程结构。下一章将介绍Java中的面向对象编程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值