目录
1.前言
2.数组的概念
3.在Java中的创建和初始化
3.1数组的创建
3.2数组的初始化
4.关于使用
4.1数组元素的访问
4.2数组的遍历
4.3length和length()的区别
5.数组其实是引用类型数据
5.1初始JVM的内存分布
5.2基本类型变量与引用类型变量的区别
5.3关于null的认识
5.4设计的原因
6.应用场景
6.1保存数据
6.2作为函数的参数
6.2.1参数基本数据类型
6.2.2参数传数组类型(引用数据类型)
1.前言
在Java编程中,数组是一种非常重要的数据结构,它允许我们存储多个值在一个单一的变量中。本文将深入探讨Java数组的基本概念、创建和使用方法,以及如何处理常见的数组问题。
2.数组的概念
可以看成是相同元素的一个集合。在内存中是一段连续的空间。
数组有以下三个特点:
- 数组中存放的元素类型相同
- 数组的空间是连在一起的
- 每个空间有自己的编号,起始位置的编号为0,即数组的下标。
3.在Java中的创建和初始化
3.1数组的创建
T[] 数组名=new T[n];
T: 表示数组中存放元素的类型
T[]: 表示数组的类型
N:表示数组长度
int[] array1=new int[10];//创建一个可以容纳10个int类型元素的数组
double[] array2=new double[5];//创建一个可以容纳5个double类型元素的数组
String[] array3=new double[3];//创建一个可以容纳3个字符串元素的数组
3.2数组的初始化
数组的初始化主要分为动态初始化以及静态初始化
- 动态初始化:在创建数组时,直接指定数组中的元素的个数
int[] array = new int[10];
- 静态初始化:在创建数组时不直接指定数据元素个数,而直接将具体的数据内容进行指定
语法格式:T[] 数组名称={data1,data2,...,datan};
int[] array1=new int[]{0,1,2,3,4,5,6,7,8,9};
double[] array2=new double[]{1.0,2.3,3.4,};
String[] array3=new String[]{"hello","world"};
注意事项
- 静态初始化虽然没有指定的长度,编译器在编译时会根据{}中的元素个数来确定数组的长度。
- 静态初始化时,{}中数据类型必须与[]前数据类型一致。
- 静态初始化可以简写,省去后面的new T[]
- 尽管省去了new T[] ,但是编译器编译代码时还是会还原
- 数组也可以按照C语言的方式创建数组,但是这种方式不太好,容易造成数组的类型是int的误解,[]如果定义在类型之后,就表示数组类型,因此int[]结合在一起写意思更清晰。
- 静态和动态初始化也可以分为两步,但是省略格式不可以
int[] array1;
array1=new int[10];
int array2;
array2=new int[]{1,2,3,4,5};
- 如果没有对数组进行初始化,数组中元素有其默认值,如果数组中存储的元素类型是基本类型,默认值为基类类型对应的默认值,比如:
类型 | 默认值 |
byte | 0 |
short | 0 |
int | 0 |
long | 0 |
float | 0.0f |
double | 0.0 |
char | /u0000 |
boolean | false |
如果数组存储元素类型为引用类型,默认值为null
4.关于使用
4.1数组元素的访问
数组在内存中是一段连续的空间,编号是从0开始的,依次递增,该编号称为数组的下标,数组可以通过下标访问任意位置的元素。
需要注意的是,数组是一段连续的内存空间,因此支持随机访问,即通过下标快速访问数组中任意位置元素,因为下标是从0开始的,介于[0,n)之间不包含n(即左闭右开),n为元素个数不能越界,否则会报出下标越界异常。
4.2数组的遍历
遍历,即将数组中所有的元素都访问了一遍,访问是指对数组中的元素进行某种操作,比如打印。
int[] arr=new int[]{1,2,3,4,5};
System.out.println(arr[1]);
System.out.println(arr[2]);
System.out.println(arr[3]);
System.out.println(arr[4]);
System.out.println(arr[5]);
上述代码实现了对数组中的元素遍历目的,但也发现了一些问题:
- 如果数组中增加了一个元素就需要增加一条打印语句
- 如果输入中元素特别多,那就要写同样多的打印语句
- 如果现在要把打印修改为给每个元素加一,修改起来将会很麻烦
上述的代码中,对数组中每个元素的操作都是相同的,则可以使用循环来进行打印。
int[] arr =new int[]{1,2,3,4,5};
for(int i=0;i<5;i++) {
System.out.println(arr[i]);
}
改成循环之后,上述的三个缺陷可以将2和3解决,但问题1并不能很好解决。但在Java中可以通过数组对象.length来获取数组长度。
int[] arr=new int[]{1,2,3,4,5};
for(int i=0;i<arr.length;i++) {
System.out.println(arr[i]);
}
也可以使用for-each遍历数组
int[] array={1,2,3};
for (int x:array) {
System.out.println(x);
}
4.3length和length()的区别
我们在写代码的时候也会遇到另外一个东西,即length()。那么他与length的区别是什么呢?
共同点,都可以求得某个数据的长度。
不同点,length是数组的一个属性,他返回数组能够容纳的元素数量。当创建数组时这个值就被确定了并且在整个数组的生命周期都不会改变;length()是String类的一个方法,用于返回字符串中的字符数。对于字符串而言,这个方法返回的是序列中实际字符数量,而不是像数组那样返回可容纳元素的数量。
总的来说,length是数组的属性,表示数组可以存储的元素数量;length()是字符串的方法,表示字符串中的字符数量在使用时,根据不同的数据类型选择适当的方式获取长度信息。
5.数组其实是引用类型数据
Java的数组是引用数据类型,这就意味着数组本身存储的是数组对象在内存中的地址,而不是数组元素的值,而这种设计不得不提到初始JVM的内存分布。
5.1初始JVM的内存分布
内存是一段连续的存储空间,主要用来存储程序运行时数据的,因此JVM也对所使用的内存按照功能的不同进行了划分。
- 程序计数器:只是一个很小的空间,保存下一条执行的指令地址
- 虚拟机栈:与方法调用相关的一些信息,每个方法在执行时都会先创建一个栈帧,栈帧中包含有:局部变量表,操作数栈,动态链接,返回地址以及其他的一些信息,保存的都是与方法执行时相关的一些信息。比如:局部变量。当方法运行结束后,栈帧就销毁了,即栈帧中保存的数据也被销毁了。
- 本地方法栈:本地方法栈与虚拟机栈的作用类似,只不过保存的内容是Native方法的局部变量。在有些版本的JVM实现中(例如HotSpot),本地方法和虚拟机是在一起的
- 堆:JVM所管理的最大内存区域,使用new创建的对象都是在堆上保存,堆是随着程序开始运行时而创建,随着程序的退出而销毁,堆中的数据只要还有在使用,就不会被销毁。
- 方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。方法编译出的字节码就是保存在这个区域。
5.2基本类型变量与引用类型变量的区别
基本数据类型创建的变量,称为基本变量,该变量空间中直接存放的是其所对应的值;而引用数据类型创建的变量,一般称为对象的引用,其空间中存储的是对象所在的空间地址。引用变量并不直接存储对象本身,可以简单理解成存储的是对象在堆中空间的起始地址。通过该地址,引用变量可以去操操作对象。这类似于C语言中的指针,但是Java中引用要比指针的操作更简单。
5.3关于null的认识
null在Java中表示"空引用",也就是一个不指向对象的引用,null的作用类似于C语言中的NULL(空指针),都是表示一个无效的内存位置,因此不能对这个内存进行任何读写操作。一旦尝试读写,就会抛出NullPointerException,需要注意的是由于Java没有指针,null和0号地址没有任何关联。
5.4设计的原因
Java数组这么设计有以下几个原因
- 灵活性:作为引用类型,数组可以被赋值给其他变量,传递到方法中,或者作为返回值,而不仅仅通过复制元素的方式。这提供了更大的灵活性和效率。
- 内存管理:引用数据类型允许Java虚拟机(JVM)更好地管理内存。对于大型数组,如果数组是基本数据类型,那么每次创建数组或将其作为参数传递给方法时,都需要复制所有元素,这种方式比较低效,使用引用类型只需要复制引用,而不是整个数组的内容。
- 多态性和扩展性:数组作为引用类型,可以更容易地与其他引用类型(如对象)交互,并利用Java面向对象的特性,如继承和多态。这使得数组可以容纳更复杂的数据结构,如自定义对象。
- 统一性:Java中的所有数据类型都可以看作是对象,包括基本类型和包装类。这使得Java的类型系统更加统一,因为所有的类型都遵循相同的规则和操作
- 性能优化:引用类型允许JVM进行优化,例如延迟初始化和垃圾回收。这些优化有助于提高性能和资源利用率。
小结:Java的数组作为引用数据类型,不仅提供了更大的灵活性和内存管理的便利,而且与Java的整体面向对象的设计理念相契合。
6.应用场景
6.1保存数据
int[] a={1,2,3,4,5};
6.2作为函数的参数
6.2.1参数基本数据类型
public static void main(String[] args) {
int num=0;
func(num);
System.out.println("num"+num);
}
public static void func(int x) {
x=10;
System.out.println(”x=“+x);
}
//执行结果
//num=10
//x=0
在func方法中修改形参x的值,不影响实参的num值
6.2.2参数传数组类型(引用数据类型)
public static void main(String[] args) {
int[] arr={1,2,3};
func(arr);
System.out.println("arr[0]="+arr[0]);
}
public static void func(int[] a) {
a[0]=10;
System.out.println("a[0]="+a[0]);
}
//执行结果
//a[0]=10
//arr[0]=10
在func方法中内部修改数组的内容,方法外部数组内容也发生改变。因为数组是引用类型,按照引用类型传递的,是可以修改其中存放的内容的。
小结:所谓的”引用“本质上只是存放了一个地址。Java将数组设定成引用类型,这样的话后续进行数组传参,其实只是将数组地址传入到函数形参中,这样可以避免对整个数组的拷贝,如果数组很长那么拷贝的开销就会很大。
6.3作为函数的返回值
求斐波那契数列的前n项
public class TestArray {public static int [] fib ( int n ) {if ( n <= 0 ){return null ;}int [] array = new int [ n ];array [ 0 ] = array [ 1 ] = 1 ;for ( int i = 2 ; i < n ; ++ i ) {array [ i ] = array [ i - 1 ] + array [ i - 2 ];}return array ;}public static void main ( String [] args ) {int [] array = fib ( 10 );for ( int i = 0 ; i < array . length ; i ++ ) {System . out . println ( array [ i ]);}}}