数组是任何编程语言都绕不过去的话题,因为它就是最基本的数据结构之一,它是是用来存储固定大小的同类型元素的线性结构。依稀记得当时上数据结构与算法的时候,老师布置的作业就是用最基本的数组和链表实现ArrayList,Stack,Queue,还有HashMap。那么今天我们一起来学习Java和Go在数组上的区别,今天只谈用法。
声明和初始化
Java
如果你知道数组元素具体都有谁,那我们可以直接创建,无需给定元素个数(事实上这个初始值会自动确定)
// 直接声明并初始化一个整数数组
int[] myArray = {1, 2, 3, 4, 5};
/** 果我们看其他人的代码,有的人习惯写
* int[] myArray; <- 我个人偏爱这个写法,一眼就明了这是int数组
* 但是有人更喜欢
* int myArray[]; <- 其实是一样的效果
/
不过大部分时间我们都是先创建一个已知大小的数组,然后动态填入数据
// 声明一个整数数组,但不初始化
int[] myArray = new int[5];
// 分配元素值
myArray[0] = 1;
myArray[1] = 2;
myArray[2] = 3;
myArray[3] = 4;
myArray[4] = 5;
GO
如果已知元素
// 使用数组字面量声明并初始化一个数组
myArray := [5]int{1, 2, 3, 4, 5}
//也可以不写长度,让编译器自动推断
myArray := [...]int{1, 2, 3, 4, 5}
如果只知道长度
// 声明并初始化一个包含5个整数的数组
var myArray [5]int
//后续再动态填入
以上部分Java和Go还算相同,接下来来介绍一下Go的独特用法和需要注意的点
- 部分初始化
// 部分初始化数组,未指定的元素默认为零值
myArray := [5]int{1, 2}
//在这种情况下,数组的前两个元素被初始化为1和2,
//而剩余的元素将被设置为整数的零值,即0。
- 使用索引初始化数组
// 使用索引初始化数组元素
myArray := [5]int{0: 100, 2: 86}
//因为数组的坐标是从0开始的,所以上述代码的意思是
//第一个元素初始值是100
//第三个元素初始值是86
- 对Go语言来说,数组长度是类型的一部分
咱们直接看代码
我先写一个Java的
int[] myArray = new int[2];
myArray = new int[]{1, 2, 3, 4, 5, 6};
for (int i : myArray) {
System.out.println(i);
}
再来看看Go
var myArray [3]int
myArray = [5]int{0: 100, 2: 86}
//这会报错,而错误是
//Cannot use '[5]int{0: 100, 2: 86}' (type [5]int) as the type [3]int
在Go语言中,数组的长度是编译时确定的,因此无法在运行时动态创建数组所以大家在使用的时候要注意,如果真的有需要我们可以用之后会学到的切片(slice)来实现类似动态数组的功能,因为切片的长度是动态变化的。
传递类型
Java
Java语言的数组在传参时是采用引用传递,这意味着在Java中,数组传递的是数组的引用(可以理解成数组的地址),而不是数组的副本(并不是在其他地方开辟新的空间进行完完全全地复制)。这种传递方式使得在方法内部对数组的修改会影响到原始数组。
public class Main {
public static void main(String[] args) {
int[] arr = {1, 2, 3};
modifyArray(arr);
System.out.println("After modifying:");
for (int num : arr) {
System.out.println(num);
}
}
public static void modifyArray(int[] array) {
// 在方法内部修改数组的值
for (int i = 0; i < array.length; i++) {
array[i] = array[i] * 2;
}
}
}
所以大家在使用的时候需要注意,在不同的地方对同一数组进行操作而导致意外的行为。
Go
在Go语言中,数组是值类型的概念意味着当你将一个数组赋值给另一个变量时,实际上是在内存中创建了一个新的数组副本。这与引用类型(如切片)不同,对引用类型的操作会影响到底层数据结构,因为它们指向同一片内存空间(像前面Java一样)。
理解Go语言中数组是值类型的概念,可以通过以下几个要点:
- 数组赋值会复制整个数组:
当你将一个数组赋值给另一个变量时,会复制整个数组的内容,包括数组中的每个元素。这意味着对新数组的修改不会影响原始数组,反之亦然。可以理解在一个空白的地方盖一座一模一样的房子。
array1 := [3]int{1, 2, 3}
array2 := array1 // 复制 array1 到 array2
array2[0] = 100
fmt.Println(array1) // 输出 [1 2 3]
fmt.Println(array2) // 输出 [100 2 3]
- 作为函数参数传递时会复制整个数组:
当你将一个数组作为参数传递给函数时,会将整个数组复制一份传递给函数。这意味着在函数内部对数组的修改不会影响到原始数组。
func modifyArray(arr [3]int) {
arr[0] = 100
fmt.Println("Inside function:", arr)
}
array := [3]int{1, 2, 3}
modifyArray(array)
fmt.Println("Outside function:", array)
// 输出:
// Inside function: [100 2 3]
// Outside function: [1 2 3]
- 值类型的特点带来了安全性和预测性:
因为数组是值类型,对数组的操作是安全的,不会因为在不同的地方对同一数组进行操作而导致意外的行为。这有助于编写更加清晰、可预测的代码。
总的来说,我们可以讲Go中的数组当作基本类型,但是我就是想传递数组本身怎么办?之后指针章节我们会解决这个问题。
长度获取
Java
在Java中,获取数组的长度可以使用数组对象的 length 属性来实现。这个属性会返回数组中元素的个数。
public class Main {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
// 使用数组的 length 属性获取数组的长度
int length = arr.length;
System.out.println("Array length: " + length);
}
}
数组长度是数组本身的一部分,如果我们看JVM布局
图片来源于文章:https://ost.51cto.com/posts/14956
Go
在Go语言中,获取数组的长度可以使用内置函数 len() 来实现。len() 函数用于获取数组、切片、映射、通道等数据结构的长度。
package main
import "fmt"
func main() {
// 声明并初始化一个整型数组
myArray := [5]int{1, 2, 3, 4, 5}
// 使用内置函数 len() 获取数组的长度
length := len(myArray)
fmt.Println("Array length:", length)
}
len() 函数对于数组、切片、映射、通道等数据结构都适用,因此它是一个非常有用的工具函数,用于获取这些数据结构的大小或长度。
迭代
Java
- 使用普通的 for 循环:
int[] array = {1, 2, 3, 4, 5};
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
- 使用增强型 for 循环(for-each 循环):
int[] array = {1, 2, 3, 4, 5};
for (int num : array) {
System.out.println(num);
}
这两个方法都可以用来迭代(遍历)Java数组中的元素。根据你的需求和代码的清晰度,可以选择其中一种方法。增强型 for 循环在简单的迭代过程中更为常用,而普通的 for 循环则更灵活,可以在迭代过程中执行更复杂的操作例如改变数组中的值。
###Go
在Go语言中,可以使用 for 循环和 range 关键字来迭代(遍历)数组中的元素。以下是几种常用的迭代方法:
- 使用普通的 for 循环:
package main
import "fmt"
func main() {
// 声明并初始化一个整型数组
array := [5]int{1, 2, 3, 4, 5}
// 使用普通的 for 循环遍历数组
for i := 0; i < len(array); i++ {
fmt.Println(array[i])
}
}
- 使用 range 关键字:
package main
import "fmt"
func main() {
// 声明并初始化一个整型数组
array := [5]int{1, 2, 3, 4, 5}
// 使用 range 关键字遍历数组,_替代了索引,有需要的就用变量接收
for _, num := range array {
fmt.Println(num)
}
}
在这里,range 关键字返回数组的索引和元素值,_ 用来忽略索引值,如果你需要索引值,可以替换为一个变量名。
以上是两种常见的迭代数组的方法,根据你的需求和代码的清晰度,可以选择其中一种方法。使用 range 关键字可以更简洁地遍历数组,并且更常用。
多维数组
Java
在Java中创建多维数组非常简单,你可以使用类似于一维数组的语法来声明和初始化多维数组。以下是几种常见的创建多维数组的方法:
- 使用数组变量声明和初始化:
// 声明一个二维整型数组
int[][] twoDimArray;
// 初始化二维数组
twoDimArray = new int[3][3];
这里声明了一个二维整型数组变量 twoDimArray,然后使用 new 关键字创建了一个3x3的二维数组对象。
- 声明并初始化多维数组:
// 声明并初始化一个二维整型数组
int[][] twoDimArray = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
这里声明了一个二维整型数组 twoDimArray,并用大括号括起来的初始化列表初始化了数组的每个元素。
- 使用循环初始化多维数组:
// 声明一个三维整型数组
int[][][] threeDimArray = new int[3][3][3];
// 初始化三维数组
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
for (int k = 0; k < 3; k++) {
threeDimArray[i][j][k] = i + j + k;
}
}
}
这里声明了一个三维整型数组 threeDimArray,然后使用嵌套的循环初始化了数组的每个元素。
以上是几种创建多维数组的常用方法。根据你的需求和代码的清晰度,可以选择最适合你的方法来创建多维数组。
Go
在Go语言中,可以使用类似于一维数组的语法来声明和初始化多维数组。以下是几种常见的创建多维数组的方法:
- 使用数组变量声明和初始化:
// 声明一个二维整型数组
var twoDimArray [3][3]int
// 初始化二维数组
twoDimArray = [3][3]int{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}
这里声明了一个二维整型数组变量 twoDimArray,然后使用大括号括起来的初始化列表初始化了数组的每个元素。
- 声明并初始化多维数组:
// 声明并初始化一个二维整型数组
twoDimArray := [3][3]int{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}
这里声明了一个二维整型数组 twoDimArray,并用大括号括起来的初始化列表初始化了数组的每个元素。
- 使用循环初始化多维数组:
// 声明一个三维整型数组
var threeDimArray [3][3][3]int
// 初始化三维数组
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
for k := 0; k < 3; k++ {
threeDimArray[i][j][k] = i + j + k
}
}
}
这里声明了一个三维整型数组 threeDimArray,然后使用嵌套的循环初始化了数组的每个元素。
可以看出在多维数组这方面,二者基本一致。不过对于多维的数据结构,Go语言更推荐使用slice切片
总结
总的来说,Go和Java在数组方面有很多相似之处,个人认为只是要额外注意Go的俩个特殊点,一个是数据长度是类型的一部分,另一个是传递参数是,Java是传地址(传的是门牌号,指向的是同一个地方),Go传递的是数组的副本(找个其他地方新盖房子)。
需要免费使用IDEA的小伙伴可以关注公众号【AIGoland之星】回复【idea】领取最新且免费的激活工具。