OOP2:数组

OOP2:数组

总结了定义数组的方式、访问数组元素和数组长度的方法、用 for 循环遍历数组的方式、数组作为方法参数和方法返回值的用法、数组作为成员变量如何构造、如何复制数组、如何对数组进行排序和查找、Arrays 类和二维数组的相关知识点。

一、数组的定义

使用数组一般需要两个步骤:①声明数组:向编译器声明数组名称和元素的数据类型。②创建数组:为数组元素分配存储空间。

1. 声明数组

java 语言的数组是一种引用数据类型,即数组是对象,而数组名就是对象名(或引用名,即对象的引用,是一个引用变量)。数组的声明实际上是声明一个引用变量,形如:

java 复制代码
元素类型 []数组名;
元素类型 数组名[];
元素类型[] 数组名;//三种形式等价,推荐用第一种

下面代码声明了两个数组:

java 复制代码
double []marks;
Account []accounts;

数组元素类型可以是基本数据类型 (如 int 或 double 类型),也可以是引用数据类型 (如 String 或 Account 类型)。如果数组元素为引用类型,则该数组称为对象数组 。上面的 accounts 就是对象数组。声明数组不能指定数组元素的个数。

2. 创建数组

声明数组仅仅声明一个数组对象的引用,而创建数组才会为数组的每个元素分配存储空间。创建数组用 new 运算符,形如:

java 复制代码
数组名 = new 元素类型[size];//size 指定大小

下面代码创建了 marks 数组和 accounts 数组:

java 复制代码
marks = new double[5];
accounts = new Account[3];

Java 数组可以动态创建,也就是它的大小可以在运行时指定,可以以变量作为数组的大小。

数组的声明与创建可以写在一个语句中,如下所示:

java 复制代码
double[] marks = new double[5];
Account[] accounts = new Account[3];

创建数组时会为每个元素指定默认值,对于引用类型数组(对象数组),它的每个元素初值为 null ,因此,还需要创建数组元素对象。

java 复制代码
accounts[0] = new Account(101, "Fish Yu", 3000.0);
accounts[1] = new Account(102, "Tina Wang", 5000.0);
accounts[2] = new Account(103, "Tear", 8000.0);

也可以用下面这种形式完成对象数组的声明-创建-初始化:

java 复制代码
Account[] accounts = new Account[]
{
	new Account(101, "Fish Yu", 3000.0);
	new Account(102, "Tina Wang", 5000.0);
	new Account(103, "Tear", 8000.0);
}

3. 数组初始化器

初始化器是在一对花括号中给出数组的每个元素值,这种方式称为静态初始化。用这种方法创建数组不能指定大小,系统会根据元素个数确定数组大小。如下所示:

java 复制代码
double[] marks = {11, 45, 14, 1919, 810};
Account[] accounts = {new Account(101, "Fish Yu", 3000.0), new Account(102, "Tina Wang", 5000.0), new Account(103, "Tear", 8000.0)};

可以用 var 类型推断符声明数组,减少代码冗余:

java 复制代码
var marks = {11, 45, 14, 1919, 810};
var accounts = {new Account(101, "Fish Yu", 3000.0), new Account(102, "Tina Wang", 5000.0), new Account(103, "Tear", 8000.0)};

"声明-创建-初始化"的多种形式

下面两种"声明-创建"写法均合法:

java 复制代码
double[] marks = new double[5];
java 复制代码
var marks = new double[5];

下面三种"声明-创建-初始化"写法均合法:

java 复制代码
double[] marks = {11, 45, 14, 1919, 810};
java 复制代码
double[] marks = new double[]{11, 45, 14, 1919, 810};
java 复制代码
var marks = new double[]{11, 45, 14, 1919, 810};

下面的写法不合法,因为没有充分的依据进行类型推断:

java 复制代码
var marks = {11, 45, 14, 1919, 810};

二、访问数组

1. 访问数组元素

数组元素的使用方式如下:

数组名[下标]

数组元素下标从0开始,到数组长度减1。数组一经创建,大小不能改变。Java 运行时会对数组元素的范围进行越界检查,若数组元素的下标超出范围,会抛出 ArrayIndexOutOfBoundsException 运行时异常。

2. 访问数组长度

数组作为对象提供一个 length 成员变量,它的值是数组元素的个数,访问方法如下:

数组名.length

3. 增强的 for 循环

增强的 for 循环能按顺序访问数组元素,可以用来迭代数组和集合对象的每个元素。因此这种循环也称为 for each 循环。形如:

java 复制代码
for(type identifier : expression)
{
	//循环体
}

type 是元素类型,expression 必须是一个数组或集合对象,identifier 是元素本身,后面不需要再加中括号和下标。

下面代码求数组 marks 中各元素的和:

java 复制代码
double sum = 0;
for(var score : marks)
{
	sum = sum + score;
}
System.out.println("总成绩 = " + sum);

注意:增强的 for 循环只能按顺序访问数组元素,并且只能使用元素而不能对元素进行修改,不能获取当前元素的索引(下标)。

三、数组的应用

1. 数组元素的复制

数组元素的复制,是将一个数组中的所有元素复制到另一个同类型的数组中,得到的是两个彼此独立但又一模一样的数组。设有一个数组 source ,其中有4个元素,现在定义一个数组 target ,与原来数组的类型相同,元素个数相同。

区分:元素复制引用赋值

类似于对象名是对象的引用,数组名也是数组的引用。如果直接用赋值号 连接两个数组名,实际上是使前者指向后者所引用的数组对象,即令两个数组引用指向同一个数组对象。如下所示:

java 复制代码
int[] source = {10, 30, 20, 40};
int[] target = source;

上述代码无法将数组 source 中的每个元素复制到 target 数组中。

数组元素复制的方法

方法一:使用循环结构,将数组元素一个一个复制到目标数组中。

java 复制代码
int[] source = {10, 30, 20, 40};
int[] target = new int[source.length];
for(var i = 0; i < source.length; i++)
	target[i] = source[i];

方法二:使用 System 类的 arraycopy() 方法,格式如下:

java 复制代码
public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)

src 为原数组,srcPos 为原数组的起始下标,dest 为目标数组,destPos 为目标数组的下标,length 为复制的数组元素个数。下面代码实现将 source 中每个元素复制到数组 target 中:

java 复制代码
int[] source = {10, 30, 20, 40};
int[] target = new int[source.length];
System.arraycopy(source, 0, target, 0, 4);

使用 arraycopy() 方法也可以将源数组的一部分复制到目标数组中。注意,如果目标数组不足以容纳原数组元素,会抛出异常。

2. 数组冒泡排序

算法与写法和 C 语言中大同小异,直接展示代码:

java 复制代码
public static void bubbleSort(int[] array)
    {
        for(int i = array.length - 1; i > 0; i--)
        {
            boolean flag = true;
            for(int j = 0; j < i; j++)
            {
                if(array[j] > array[j + 1])
                {
                    int t = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = t;
                    flag = false;
                }
            }
            if(flag) break;
        }
    }

将该静态方法和主方法放在同一个类,然后即可在主方法中调用该方法。

3. java.util.Arrays 类

java.util.Arrays 类定义了若干静态方法对数组操作,下面介绍实用的几个。

  • public static int binarySearch(int[] a, int key)

    对于一个已升序排序的数组,通过二分查找快速定位 key 值位置。找到则返回下标,找不到则返回(插入点-1)。插入点为指定的值在数组中应该插入的位置。

  • public static void sort(int[] a)

    对数组 a 按升序排序。

  • public static void sort(int[] a, int fromIndex, int toIndex)

    对数组 a 中从起始下标 fromIndex (包含)到终止下标 toIndex (不包含)之间的元素排序。

  • public static void fill(int[] a, int val)

    用指定的 val 值填充数组 a 中的每个元素。

  • public static double[] copyOf(double[] original, int newLength)

    对原数组 original 进行复制,返回值为接收复制的新数组。新数组的长度可用参数 newLength 定义,若不想改变原数组长度,可填成 original.length。

4. 数组与方法

(1)数组作方法参数

数组对象可以作用参数传递给方法,下面代码定义了一个求数组元素和的方法:

java 复制代码
public static double sumArray(double array[])
{
	var sum = 0;
	for(var i = 0; i < array.length; i++)
		sum += array[i];
	return sum;
}

注意:由于数组是对象 ,因此将其传递给方法是按引用传递。如果在方法体中修改了数组元素的值,则该修改反映到返回的数组对象。

(2)方法返回数组

一个方法可以返回一个数组对象,下面的方法返回参数数组的元素反转后的一个数组:

java 复制代码
public static int[] reverse(int[] list)
{
	var result = new int[list.length];
	for(int i = 0, j = result.length - 1; i < list.length; i++, j--)
		result[j] = list[i];
	return result;
}

有了上述方法,可以使用如下语句实现数组的反转:

java 复制代码
int[] list = {6, 7, 8, 9, 10};
int[] list2 = reverse(list);

(3)可变参数方法

Java 允许定义方法(包括构造方法)带可变数量的参数,这种方法称为可变参数方法 。具体做法是在方法参数列表的最后一个参数的类型名之后、参数名之前使用省略号,如下所示:

java 复制代码
public static int average(int ... array)
{
	//方法体
}

带可变参数的方法中也可以有一般的参数,但可变参数必须是方法的最后一个参数。如下所示:

java 复制代码
public static int average(String name, int ... array)
{
	//方法体
}

这里,参数 values 被声明为一个 double 型值的序列,其中参数的类型可以是引用类型 。对可变参数的方法,调用时可以为其传递任意数量的指定类型的实际参数

在方法体中,编译器将为可变参数创建一个数组,并将传递来的实际参数值作为数组元素的值,相当于为方法传递一个指定类型、长度不定的数组。

下面程序使用可变参数方法求和,加数个数不定。

程序 2-1 VarargsDemo.java

java 复制代码
public class VarargsDemo
{
    //可变参数方法,可变参数类型定义成:类型...argName,它必须是方法的最后一个参数
    public static int getSum(int ... array)
    {
        var sum = 0;
        for(var i : array)
        {
            sum += i;
        }
        return sum;
    }
    public static void main(String[] args)
    {
        int result = getSum(2, 3, 4);
        System.out.println("2+3+4 = " + result);
        result = getSum(3, 5, 8, 9, 10);
        System.out.println("3+5+8+9+10 = " + result);

        int[] a = {1,2,3,4};
        result = getSum(a);
        System.out.println("a数组元素之和为 " + result);
    }
}

运行结果如下:

该程序调用了 getSum()方法并为其传递若干 int 型数,也可以传递一个 int 型数组。

5. 数组作成员变量

(1)设计构造方法

现有类 MyArray,带有私有成员数组 data。在类中进行代码设计。

java 复制代码
public class MyArray
{
	private int []data;
	//构造方法
}
  • 浅拷贝(直接引用)
java 复制代码
public MyArray(int[] inputArray)
{
	this.data = inputArray
}

通过传递的参数 inputArray 为 data 赋值,但由于是引用赋值,若 inputArray 改变,则对象内的 data 也会随之改变。

  • 深拷贝(创建副本)
java 复制代码
public MyArray(int[] inputArray)
{
	this.data = Arrays.copyOf(intputArray, inputArray.length);
}

上述代码为传来的参数 inputArray 创建了副本,再赋值给 data ,因此 data 不会被外部篡改。这种写法无疑安全性更高,故下面主要使用这种写法。

(2)getter / setter

java 复制代码
//getter 返回副本
public int[] getData()
{
	return Arrays.copyOf(data, data.length);
}
//setter 接收副本
public void setData(int[] newData)
{
	this.data = Arrays.copyOf(newData, newData.length);
}

(3)使用对象

在主方法中创建对象,并获取/修改对象成员的值。

  • 创建对象
java 复制代码
//写法一:先另定义数组,再将其作为参数写入构造方法
int[] source = {1, 2, 3};
MyArray array1 = new MyArray(source)
//写法二:直接在构造方法参数列表中定义数组
MyArray array2 = new MyArray(new int[]{1, 2, 3});
  • 获取/修改对象
java 复制代码
//获取数组
int[] got = array1.getData();
//修改数组
array1.setData(new int[]{4, 5, 6});

四、二维数组

Java 语言中数组元素还可以是一个数组,这样的数组称为数组的数组二维数组

1. 二维数组的定义

二维数组的使用也分声明、创建两个步骤。

(1)二维数组的声明

二维数组有下面3种等价的声明格式:

java 复制代码
元素类型 [][]数组名;//推荐使用第一种格式
元素类型 数组名[][];
元素类型 []数组名[];

元素类型可以是基本类型 ,也可以是引用类型,示例:

java 复制代码
int [][]matrix;
String [][]cities;

(2)二维数组的创建

创建二维数组即为每个元素分配存储空间。系统先为高维分配引用空间,然后顺次为低维分配空间。

  • 写法一:直接为每一维分配空间:
java 复制代码
var matrix = new int[2][3];

适用于数组的低维具有相同个数的数组元素。

二维数组是数组的数组,即数组元素也是一个数组。二维数组 matrix 有两个元素,matrix[0] 和 matrix[1] ,它们又都是数组,各有3个元素。matrix、matrix[0]、matrix[1]都是对象。每个元素在创建时会被指定默认值。

  • 写法二:先为第一维分配空间,再为第二维分配空间(反之不可):
java 复制代码
var matrix = new int[2][];
matrix[0] = new int[3];
matrix[1] = new int[4];

适用于低维数组元素个数不同的情况。

第一维的每个元素(matrix[0]、matrix[1])可以指定不同的大小。

对于引用类型的数组,除了为数组分配空间外,还要为每个数组元素分配空间:

java 复制代码
cities[0][0] = new String("成都");
cities[0][1] = new String("绵阳");
cities[0][2] = new String("雅安");
cities[1][0] = new String("沈阳");
cities[1][1] = new String("锦州");

(3)数组初始化器

二维数组可使用初始化器在声明数组的同时为数组元素初始化,如下所示:

java 复制代码
int[][] matrix = {{15, 56, 20, -2}, {10, 80, -9, 31}, {76, -3, 99, 21}};

2. 数组元素的使用

用下面的形式访问二维数组的元素:

数组名[下标1][下标2]

下标1和下标2可以是整型常数或表达式。每一维的下标是从0到该维长度减1.

  • 访问数组的长度:

若有数组 matrix[3][4] ,则说明 matrix 数组是一个3行4列的数组。当我们讨论 matrix 的长度时,我们其实指的是它第一维的长度。多维数组每一维都有一个 length 成员表示数组的长度。matrix.length 的值是3,matrix[0].length 的值是4.