什么是数组
我们先来看看这样一个例子,如果一个系统中存储的是一个 Java 工程师信息,假设这个系统需要存储 100 个 Java 工程师信息,难道需要定义 100 个编号变量、100 个姓名变量和 100 个底薪变量吗?显然,编程语言不会这么傻。Java 提供了一种称为数组的数据类型,数组不是基本数据类型,而是引用数据类型。
数组是把相同类型的多个变量按一定顺序组织起来,这些按序排列的同类型数据元素的集合称为数组。数组中的元素在内存中是连续存储的。数组中的数据元素既可以是基本类型,也可以是引用类型。
常见的错误类型
语法错误,编译时异常,运行时异常
数组的创建方法
静态初始化
数组的三种遍历方式
Arrays.toString,for循环,增强for循环
数组的内存结构
前面学习 Java 基本数据类型的时候提到,Java 数据类型分为两大类,分别是基本数据类型和引用数据类型。内存存储形式的不同是基本数据类型和引用数据类型本质的区别:
引用数据类型的名称实际代表的是存放数据的地址,不是数据本身,基本数据类型反之。如 String name=“颜群”, name 存放的是字符串“颜群”的地址;int age=30; 可认为 age 存放的是数值 30 本身。int[] engNo 中 engNo 是引用数据类型变量,所以其存放的是数组的地址。
Java 的内存分为栈内存和堆内存。基本数据类型的变量和数据都存储在栈中,引用数据类型的变量(地址)存储在栈中,数据存储在堆中,前面说的地址完整地说是“堆内存地址”。
数组不属于基本数据类型(虽然其内部存储的可能是基本数据类型),只能是引用数据类型。因此语句 int[] engNo = new int[]{1001,1002,1003,1004,1005}; 中,数组本身是在堆内存中开辟的空间,该空间的地址存储在栈内存中并命名为 engNo,如图 所示。
String类型数组演变过程
之前讲到过,引用的名称实际代表的是存放数据的地址。因此,如果数组中的元素是 String 类型,那么数组元素存放的就是 String 类型的引用即数据的地址。
在使用 String[] names = new String[4]; 创建字符串数组时,会在堆内存中分配 4 个连续空间,并把空间地址赋给 names ,使 names 指向这 4 个连续的内存空间。这 4 个空间里存放的默认初始值为 null(String 类型数据的默认值),如图所示。
names[0] = “王云”; 因为数组的元素是引用类型,因此会将"王云"的地址放到 names 数组的第 1 个元素空间里。
之后,依次将"刘静涛", “南天华”, "雷静"的地址存放到 names 中相应的位置,如图所示。
(重点!)将数组作为参数传递
首先我们还是先创建文件 TestArray2.java 并添加如下代码:
public class TestArray2{
}
设计方法 exchange1(int a, int b) 作用是交换 a 和 b 的值:
//传递基本数据类型,交换int型a和b的值
public static void exchange1(int a, int b) {
int temp = a;
a = b;
b = temp;
}
设计方法 exchange2(int[] x) 注意,该方法的形参是一个 int 型数组,该方法的作用是交换数组 x 的第 1 个和第 2 个元素的值:
//传递引用数据类型,交换数组x第1个元素和第2个元素的值
public static void exchange2(int[] x) {
int temp = x[0];
x[0] = x[1];
x[1] = temp;
}
提供 main 方法,提供实参并对上述两个方法进行调用,完整代码如下:
class TestArray2 {
public static void main(String[] args) {
int engNo1 = 1001;
int engNo2 = 1002;
System.out.println("传递值交换数值:");
//调用前
System.out.println("调用前第1、第2编号为:" + engNo1 + "\t" + engNo2);
//值传递,传递的实质是数值的副本,所以没有交换原值
exchange1(engNo1, engNo2);
//调用后
System.out.println("调用后第1、第2编号为:" + engNo1 + "\t" + engNo2);
int[] engNo = new int[2];//数组类型实参
//给数组赋值
engNo[0] = 1001;
engNo[1] = 1002;
System.out.println("传递引用交换数值:");
//调用前
System.out.println("调用前第1、第2编号为:" + engNo[0] + "\t" + engNo[1]);
//传递引用类型,传递的实质是指向数组的地址,所以交换了数组里的值
exchange2(engNo);
//调用后
System.out.println("调用后第1、第2编号为:" + engNo[0] + "\t" + engNo[1]);
}
//传递基本数据类型,交换int型a和b的值
public static void exchange1(int a, int b) {
int temp = a;
a = b;
b = temp;
}
//传递引用数据类型,交换数组x第1个元素和第2个元素的值
public static void exchange2(int[] x) {
int temp = x[0];
x[0] = x[1];
x[1] = temp;
}
}
编译、运行程序,运行结果如下:
查看结果我们发现,执行完 exchange1 方法后,原来的 engNo1 和 engNo2 的值没有发生交换,但是执行完 exchange2 方法后,数组 engNo 的第 1 和第 2 个元素的值发生和调换,出现这种情况的原因是:
传递基本数据类型时,其传递的实质是数值的副本,所以在调用使用值传递交换数据的方法时(即调用 exchange1 时),只是在方法内将值的副本的数据内容进行了交换,其原数据本身并没有发生变化。
而传递引用数据类型时,其传递的实质为引用的地址,本例中传递的是数组的地址,在调用使用“引用地址”传递交换数据的方法时(即调用 exchange2 时),是对这个地址指向的数据进行了交换,即对原数组的值进行了交换。
**说明,在 Java 中只存在值传递,并不存在引用传递。**有些资料上写的“引用传递”,实际上传递的是引用对象的地址。
例:【练一练】根据加薪标准计算实际薪资
之前的需求如下:
计算出底薪大于等于 6000 元的高薪人员比例以及这些高薪人员的底薪平均值。
输出用户选择的某个工程师的底薪。
现在调整需求,在用户输入这 10 个 Java 工程师的底薪后,对他们的底薪进行加薪,加薪标准如下。
高薪人员(底薪大于等于 6000 元),加薪 5%。
非高薪人员,加薪 10%。 最后根据用户选择输出某个工程师加薪后的底薪。
新建一个 TestArray3.java 文件,接下来分步实现。
设计方法 inputEngSalary(int[] salary),作用是接收用户输入的薪资信息,并根据高薪还是低薪人员,计算出调薪后的薪资,存储到数组 salary 中,注意,我们暂且忽略调薪中出现的精度问题。
import java.util.Scanner;
class TestArray3 {
static Scanner input = new Scanner(System.in);
public static void inputEngSalary(int[] salary) {
for (int i = 1; i <= 10; i++) {
System.out.print("请输入第" + i + "工程师底薪:");
salary[i - 1] = input.nextInt();
if (salary[i - 1] >= 6000) {
//高薪人员加薪5%
salary[i - 1] = salary[i - 1] + (int) (salary[i - 1] * 0.05);
} else {
//非高薪人员加薪 10%
salary[i - 1] = salary[i - 1] + (int) (salary[i - 1] * 0.1);
}
}
}
}
假设调薪完成,数组中存储调薪后的薪资,根据用户输入查找第 index 个工程师的薪资的核心代码如下:
System.out.print("请输入你需要获取第几个工程师的底薪:");
int index = input.nextInt();//获取要查询的薪资的序号
System.out.println("第" + index + "个工程师的底薪为:" + basSalary[index - 1]);//注意第index个的下标是index-1
编写 main 方法实现需求,完整代码如下:
import java.util.Scanner;
class TestArray3 {
static Scanner input = new Scanner(System.in);
public static void main(String[] args) {
//创建一个长度为10的整型数组,存放工程师底薪
int[] basSalary = new int[10];
//调用inputEngSalary方法输入工程师底薪并执行加薪操作
inputEngSalary(basSalary); //传递引用
System.out.print("请输入你需要获取第几个工程师加薪后的底薪:");
int index = input.nextInt();
System.out.println("第" + index + "个工程师加薪后的底薪为:" + basSalary[index - 1]);
}
public static void inputEngSalary(int[] salary) {
for (int i = 1; i <= 10; i++) {
System.out.print("请输入第" + i + "工程师底薪:");
salary[i - 1] = input.nextInt();
if (salary[i - 1] >= 6000) {
//高薪人员加薪5%
salary[i - 1] = salary[i - 1] + (int) (salary[i - 1] * 0.05);
} else {
//非高薪人员计数10%
salary[i - 1] = salary[i - 1] + (int) (salary[i - 1] * 0.1);
}
}
}
}
编译运行此程序,结果如下图所示。
javac TestArray3.java
java TestArray3
上面的程序中,main 方法中定义了一个数组 basSalary,并对其进行了赋值操作。main 方法在调用 inputEngSalary 方法的时候,将数组 basSalary 以参数的形式,传递给 inputEngSalary 方法,这就是所谓的引用传递。