目录
我们举一个例子.
如果要定义三个整型变量, 在本篇文章学习之前一般会这样做:
- int a1 = 1;
- int a2 = 2;
- int a3 = 3;
如果是要定义更多个整型变量, 我们就会接着往下按照类型 变量名 = 值;去写.
但是会发现不管是几个变量, 他们的类型都是一样的, 属于同一种数据类型, 那么我们把同一种数据类型组织起来, 通过一行代码解决, 这就是我们的数组.
在Java中创建(定义)数组就是类型[] 数组名 = {};, 如下所示:
int[] array = {1, 2, 3}; // 定义了一个数组, 有三个变量.
同样我们可以定义其他类型的数组.
float[] array2 = {1.0f, 2.5f};
以上面的array数组为例, 它创建的时候是在内存中给它开辟一块连续的空间, 空间大小是3.

给每一个位置编号的时候是从0开始编号.
数组的定义:数组是一块连续的存储空间, 存储的是相同数据类型的元素.
那么如何通过下标访问数据呢? 我们来看.
比如: array[1], 这里的元素是一个整数, 使用ret来接收一下.
- int[] array = {1, 2, 3};
- int ret = array[1];
- System.out.println(ret);
输出结果:
2
注意, 通过下标访问数据时的下标不可以超出数组的大小, 如果超出会异常.

可以看到, 报错"数组下标超出了范围", 也就是"数组越界异常".
数组的下标范围: [0, len-1] (len为数组长度)
Java中输出数组长度可以通过数组名.length得出数组长度
- int len = array.length;
- System.out.println(len);
简单区分几种定义数组的方式:
- int[] array1 = {1, 2, 3};//直接赋值 静态初始化
- // new type[] {dates};
- int[] array2 = new int[]{1, 2, 3, 4}; //动态初始化, 不指定数组长度, 根据元素个数确定
以上两种没有本质上的区别, 只有写法上的区别.
再看以下的写法:
- int[] array3 = new int[10];//只是分配了内存 但是没有进行赋值 只有默认值。
-
- int[] array4;
- array4 = new int[]{10, 20, 30};//静态和动态初始化也可以分为两步,但是省略格式不可以。
| 类型 | 默认值 |
|---|---|
| byte | 0 |
| short | 0 |
| int | 0 |
| long | 0 |
| float | 0.0f |
| double | 0.0 |
| char | /u0000 |
| boolean | false |
我们可以通过下标来访问数组中的元素, 也可以通过下标去修改元素.
- int[] array1 = {1, 2, 3}; // 假设这是我们的数组
- System.out.println(array1[1]); // 我们可以通过 sout 来输出它的 1下标
- array1[1] = 99; // 也可以通过 array1 把 1下标 的值改成 99.
- System.out.println(array1[1]);
以上代码说明我们不仅可以通过 1下标 来访问 1 下标的值, 也可以修改 1下标的值.
【注意事项】
1. 数组是一段连续的内存空间,因此支持随机访问,即通过下标访问快速访问数组中任意位置的元素
2. 下标从0开始,介于[0, N)之间不包含N,N为元素个数,不能越界,否则会报出下标越界异常。
- int[] array = {1, 2, 3};
- System.out.println(array[3]); // 数组中只有3个元素,下标一次为:0 1 2,array[3]下标越界
执行结果:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100
at Test.main(Test.java:4)
所谓 "遍历" 是指将数组中的所有元素都访问一遍, 访问是指对数组中的元素进行某种操作.
for循环遍历- int[] array1 = {1, 2, 3};
- for (int i = 0; i < array1.length; i++) {
- System.out.print(array1[i] + " ");
- }
- System.out.println();
for-each循环遍历- // for each: 增强for循环
- // 数组当中数据的类型定义的变量 : 数组名
- for (int x : array1) {
- System.out.print(x + " ");
- }
- System.out.println();
两者区别:
for可以拿到数组的下标, 而for-each拿不到数组的下标.
如果去遍历数组只是为了简单的打印, 就用for-each即可.
如果有需要使用数组下标, 就使用for.
ArraysJava中有一个工具类, 可以专门用来操作数组, 这个工具类叫做Arrays.
那么要使用它, 就需要导包, 也就是在所有代码的前面加上:
import java.util.Arrays;
于是就可以使用toString()方法.
- //把数组转变为字符串,然后返回
- String ret = Arrays.toString(array1);
要理解什么是引用, 首先先来了解JVM内存的划分.
为什么要进行内存的划分?
对于我们内存来说, 它是一块连续的空间. 那么我们怎么知道我们的数据放到哪里?
所以我们需要对内存进行划分.
内存是一段连续的存储空间,主要用来存储程序运行时数据的。比如:
1. 程序运行时代码需要加载到内存
2. 程序运行产生的中间数据要存放在内存
3. 程序中的常量也要保存
4. 有些数据可能需要长时间存储,而有些数据当方法运行结束后就要被销毁
如果对内存中存储的数据不加区分的随意存储,那对内存管理起来将会非常麻烦。
JVM对所使用的内存按照功能的不同进行了划分:

可以看到, 有方法区, 虚拟机栈, 本地方法栈, 堆, 程序计数器. 总共有五块内存, 这五块内存所存储的数据是不同的. 比如我们平时所说的局部变量的内存就存在虚拟机栈中. 再比如本地方法栈, JVM有部分代码底层是C/C++写的, 那么这部分代码就是在本地方法栈中. 再比如堆, 我们所说的对象, 数组就是在堆上, 方法区就是存放静态变量的. 程序计数器就用于保存下一条执行的指令的地址.
new int[]{1, 2,3} ),堆是随着程序开始运行时而创建,随着程序的退出而销毁,堆中的数据只要还有在使用,就不会被销毁。现在我们只简单关心 堆 和 虚拟机栈 这两块空间,后续JVM中还会更详细介绍。
基本数据类型创建的变量,称为基本变量,该变量空间中直接存放的是其所对应的值;
而引用数据类型创建的变量,一般称为对象的引用,其空间中存储的是对象所在空间的地址。
以下代码在内存中应该如何存储?
- int a = 10;
- int b = 20;
- int[] array = {1, 2, 3, 4};

那么我们如果直接打印array, 会看到一个类似于地址的东西.
System.out.println(array);

这串字符串可以先简单认为就是地址, 但是它并不是真实的地址, 而是地址经过哈希得到的.
引用变量并不直接存储对象本身,可以简单理解成存储的是对象在堆中空间的起始地址。通过该
地址,引用变量便可以去操作对象.
接下来我们看一些代码来感受一下.
代码示例1
- int[] array = {1, 2, 3, 4}; // 定义一个数组
- System.out.println(Arrays.toString(array));// 输出这个数组, 输出为 1234
-
- int[] array2 = array;
- array2[1] = 99;
- System.out.println(Arrays.toString(array));
- System.out.println(Arrays.toString(array2));

执行结果:

代码示例2
- int[] array = {1, 2, 3, 4};
- int[] array2 = {4, 5, 6, 7, 8};
- array = array2;
- System.out.println(Arrays.toString(array));
- System.out.println(Arrays.toString(array2));

执行结果:

一个引用能否同时指向多个对象?
不可以. 由上例可知array这个变量存储的引用只能有一个.
再比如:int a = 10 = 20 = 30;显然错误, 一个变量里面所存储的值只能有一个.
nullnull 在 Java 中表示 "空引用" , 也就是一个不指向对象的引用.
- // 引用类型的赋值
- int[] array = null; // 代表这个引用不指向任何的对象
- System.out.println(array);

当引用不指向任何对象的时候, 如果尝试对这个引用做一些事情的时候, 就会发生空指针异常.
System.out.println(array.length);

null 的作用类似于 C 语言中的 NULL (空指针), 都是表示一个无效的内存位置. 因此不能对这个内存进行任何读写操作. 一旦尝试读写, 就会抛出 NullPointerException.
注意: Java 中并没有约定 null 和 0 号地址的内存有任何关联.
- public static void main(String[] args) {
- int[] array = {1, 2, 3};
- for(int i = 0; i < array.length; ++i){
- System.out.println(array[i] + " ");
- }
- }
有如下数组:
int[] array = {1, 2, 3, 4};
接下来我们写两个函数来看一下这两个函数有什么区别:
- public static void func1(int[] array) {
- array = new int[10];
- }
-
- public static void func2(int[] array) {
- array[0] = 99;
- }
当我们调用其中一个函数的时候, 输出的结果是什么?
- func1(array);
- //func2(array);
- System.out.println(Arrays.toString(array));
分析调用func1:


所以可以知道: 不是传引用就可以改变实参的值.
分析调用func2:


总结: 所谓的 "引用" 本质上只是存了一个地址. Java 将数组设定成引用类型, 这样的话后续进行数组参数传参, 其实只是将数组的地址传入到函数形参中. 这样可以避免对整个数组的拷贝(数组可能比较长, 那么拷贝开销就会很大).
- // 在 Java 中可以返回一整个数组
- public static int[] func3() {
- int[] tmp = {1, 2, 3, 4, 5, 6, 7};
- return tmp;
- }
-
- public static void main(String[] args) {
- int[] ret = func3();
- System.out.println(Arrays.toString(ret));
- }

代码示例
- public static void swap(int[] array) {
- int tmp = array[0];
- array[0] = array[1];
- array[1] = tmp;
- }
-
- public static void main(String[] args) {
- int[] tmp = {1, 2};
- System.out.println("交换前:" + tmp[0] + " " + tmp[1]);
- swap(tmp);
- System.out.println("交换后:" + tmp[0] + " " + tmp[1]);
- }

对于数组转字符串来说其实前文已经提过, 就是使用工具类Arrays的toString()方法.
那么我们这里就用自己写代码, 模拟实现一下toString().
首先来看一下我们在使用原来的toString是什么样的一个结果:
- public static void main(String[] args) {
- int[] array = {1, 2, 3, 4};
- String ret = Arrays.toString(array);
- System.out.println(ret);
- }

可以看到结果字符串由中括号, 逗号, 数字组成.
那么显然我们需要写出字符串拼接的效果.
于是可以自然而然的写出如下代码:
- public static String myToString(int[] tmp) {
- // 比普通的数据多了中括号和逗号, 所以最后的返回值是要先有"[", 而后进行字符拼接
- String ret = "[";
- for (int i = 0; i < tmp.length; i++) { // 拿到每个元素数据
- ret = ret + tmp[i] + ","; // 每次有一个数据就拼接上
- }
- ret += "]"; // 拼接完数字就补"]"在结尾
- return ret;
- }
但是上面的代码是有问题的, 我们对这份我们第一次写出的代码进行测试:
- public static void main(String[] args) {
- int[] array = {1, 2, 3, 4};
- // String ret = Arrays.toString(array);
- String ret = myToString(array);
- System.out.println(ret);
- }

在运行结果中, 会发现最后一个应该是要不加逗号的, 所以加逗号的逻辑是最后一个不加.
- public static String myToString(int[] tmp) {
- // 注意: 增加非空判断, 否则会有空指针异常
- if (tmp == null) {
- return "null";
- }
- String ret = "[";
- for (int i = 0; i < tmp.length; i++) { // 拿到每个元素数据
- // ret = ret + tmp[i] + ","; // 每次有一个数据就拼接上 -> 但, 加逗号不能加在这里
- ret += tmp[i];
- if (i != tmp.length - 1) { // i是最后一个元素的时候就不加逗号, 也就是: 不是最后一个元素的时候才能加逗号
- ret += ",";
- }
- }
- ret += "]"; // 拼接完数字就补"]"在结尾
- return ret;
- }
注意拷贝的前提: 拷贝需要有原内容, 并且拷贝后会产生一个一模一样的新内容, 才是拷贝.
我们先来看以下代码.
- int[] array = {1, 3, 5, 7, 9};
- int[] array2 = array;
这个代码并不是拷贝, 因为这个代码根本没有产生新的内存空间.

所以:
- int[] array = {1, 2, 3, 4, 5};
- int[] array2 = new int[array.length];
- // 接下来就可以通过 for循环 把 array 中每个元素拷贝到 array2 中
完整代码:
- public static void main(String[] args) {
- int[] array = {1, 2, 3, 4, 5};
- int[] array2 = new int[array.length];
- // 把对应的元素拷贝到 array2 中
- for (int i = 0; i < array.length; i++) {
- array2[i] = array[i];
- }
- System.out.println(Arrays.toString(array));
- System.out.println(Arrays.toString(array2));
- }

但是以后我们的拷贝都不用这么麻烦, 直接使用工具类Arrays类的copyOf()方法即可.
- public static void main(String[] args) {
- int[] array = {1, 3, 5, 7, 91, 11, 22, 44, 88, 18, 29, 17, 14};
- int[] array2 = Arrays.copyOf(array,array.length);
- System.out.println(Arrays.toString(array));
- System.out.println(Arrays.toString(array2));
- }

补充小技巧: 数组扩容, 在数据结构中会有涉及到.
- //扩容 2倍
- int[] array2 = Arrays.copyOf(array, 2 * array.length);



我们也可以用一下arraycopy()方法.
- public static void main(String[] args) {
- int[] array = {1, 3, 5, 7, 91, 11, 22, 44, 88, 18, 29, 17, 14};
- int[] copy = new int[array.length];
- // 注: 也支持局部的拷贝
- System.arraycopy(array, 0, copy, 0, array.length);
- System.out.println(Arrays.toString(array));
- System.out.println(Arrays.toString(copy));
- }

- public static void main(String[] args) {
- int[] array = {1, 3, 5, 7, 91, 11, 22, 44, 88, 18, 29, 17, 14};
- // copyOfRange() 也可进行 局部拷贝, range是范围的意思
- int[] array2 = Arrays.copyOfRange(array, 2, 5);// [2,5) 左闭右开
- System.out.println(Arrays.toString(array));
- System.out.println(Arrays.toString(array2));
- }

注意:数组当中存储的是基本类型数据时,不论怎么拷贝基本都不会出现什么问题,但如果存储的是引用数据类型,拷贝时需要考虑深浅拷贝的问题,关于深浅拷贝在后续详细介绍。
给定一个整型数组, 求平均值.
- public static double avg(int[] array) {
- int sum = 0;
- for (int x : array) {
- sum += x;
- }
- return sum * 1.0 / array.length;
- }
给定一个数组, 再给定一个元素, 找出该元素在数组中的位置.
- public static int find(int[] array, int key) {
- for (int i = 0; i < array.length; i++) {
- if (array[i] == key) {
- return i;
- }
- }
- return -1;
- }
针对有序数组, 可以使用更高效的二分查找.
啥叫有序数组?
有序分为 "升序" 和 "降序"
如 1 2 3 4 , 依次递增即为升序.
如 4 3 2 1 , 依次递减即为降序.
以升序数组为例, 二分查找的思路是先取中间位置的元素, 然后使用待查找元素与数组中间元素进行比较:
- //建立在有序的情况下
- public static int binarySearch(int[] array, int key) {
- int left = 0;
- int right = array.length - 1;
- while (left <= right) {
- int mid = (left + right) >>> 1;
- if (array[mid] < key) {
- left = mid + 1;
- } else if (array[mid] > key) {
- right = mid - 1;
- } else {
- return mid;
- }
- }
- return -1;
- }
让数组有序(升序):
Arrays.sort(array);// 不是冒泡排序 底层快排
给定一个数组, 将里面的元素逆序排列.
思路
设定两个下标, 分别指向第一个元素和最后一个元素. 交换两个位置的元素.
然后让前一个下标自增, 后一个下标自减, 循环继续即可.
- public static void reverse(int[] array) {
- int left = 0;
- int right = array.length - 1;
- while (left < right) {
- int tmp = array[left];
- array[left] = array[right];
- array[right] = tmp;
- left++;
- right--;
- }
- }
给定一个数组, 让数组升序 (降序) 排序.
算法思路
假设排升序:
1. 将数组中相邻元素从前往后依次进行比较,如果前一个元素比后一个元素大,则交换,一趟下来后最大元素就在数组的末尾
2. 依次重复上述过程,直到数组中所有的元素都排列好
- public static void bubbleSort(int[] array) {
- //i控制趟数
- for (int i = 0; i < array.length - 1; i++) {
- //优化:检查某一趟之后,是否有序了?
- boolean flg = false;
- //array.length - 1 - i 中的 -i 优化的是 每一趟比较的次数
- for (int j = 0; j < array.length - 1 - i; j++) {
- if (array[j] > array[j + 1]) {
- int tmp = array[j];
- array[j] = array[j + 1];
- array[j + 1] = tmp;
- flg = true;
- }
- }
- if (flg == false) {
- return;
- }
- }
- }
二维数组本质上也就是一维数组, 只不过每个元素又是一个一维数组.
数据类型[][] 数组名称 = new 数据类型 [行数][列数] { 初始化数据 };
- int[][] array1 = new int[2][3];
- int[][] array2 = new int[][]{{1, 2, 3}, {4, 5, 6}};
- int[][] arrays = {{1, 2, 3}, {4, 5, 6}};
- public static void main(String[] args) {
- //2行3列的
- int[][] array1 = new int[2][3];
- for (int i = 0; i < 2; i++) {
- for (int j = 0; j < 3; j++) {
- System.out.print(array1[i][j] + " ");
- }
- System.out.println();
- }
- System.out.println();
- System.out.println(Arrays.deepToString(array1));
- }