1.1、概述
- Java语言中的数组是一种引用数据类型。不属于基本数据类型。数组的父类是Object类。
- 数组实际上是一个容器,可以同时容纳多个元素(数组是一个数据的集合)
- 数组当中可以存储基本数据类型的数据,也可以存储引用数据类型的数据(存的是引用数据类型的内存地址)数组中不能直接存储Java对象
- 数组因为是引用类型,所以数组对象是在堆内存当中
- 在Java中,数组一旦创建,长度不可变
- 数组的分类 包括一维数组、二维数组、三维数组,多维数组…
- 所有的数组对象都有length属性,用来获取数组中元素的个数
- Java中的数组要求数组中的元素类型统一,比如int类型数组只能存储int类型,Person类型只能存储Person类型。
- 数组在内存中存储的时候,数组中的元素内存地址是连续的;数组实际上是一种简单的数据结构。
- 所有的数组都是拿第一个数组元素的内存地址,作为整个数组对象的内存地址
- 数组中的每个元素都有索引(下标),索引从0开始,以1递增。最后一个元素的的下标是:length-1
- 数组的优点和缺点:
- 优点:查询/检索某个下标上的元素时效率极高。
- 为什么检索效率高
- 每一个元素的内存地址在空间存储上是连续的
- 每一个元素类型相同,所以占用空间大小一样
- 知道第一个元素内存地址,知道每一个元素占用空间的大小,又知道下标,所以通过一个数学表达式就可以计算出某个下标上元素的内存地址。直接通过内存地址定位元素,所以数组的检索效率是最高的。
- 例如数组中存储100个元素,或者100万个元素,在元素查询/检索方面,效率是相同的。因为数组中元素查找的时候不会一个一个去找,而是通过数学表达式计算出来的。(算出一个内存地址,直接定位的)
- 缺点:
- 由于为了保证数组中每个元素的内存地址连续,所以在数组上随机删除或者增加元素的时候,效率极低,因为随机增删元素会涉及到后面元素统一向前或向后位移的操作。
- 数组不能存储大量的数据,因为很难在内存空间中,找到一块大的且连续的内存空间。
数组内存结构图
1.2、使用数组
一维数组
- 声明数组
数据类型[] 数组名;
// 数据类型可以是基本数据类型也可以是引用数据类型
- 初始化数组
//1.静态初始化
int[] array = {100, 2100, 300, 40};
//2.动态初始化
int[] array = new int[5]; //初始化一个长度为5的整型数组,每个数组元素默认值为0
- 取,改数组中的元素
//取数组元素
array[0]; //取数组中第一个元素
array[array.length - 1]; //取数组中最后一个元素
//改数组中元素
array[0] = 11; //把数组中第一个元素改为11
- 遍历数组
//for循环遍历
for(int i = 0; i < array.lrngth; i++){
System.out.println(array[i]); // 输出数组中的每一个元素
}
//增强for循环
for(int i : array){ //i表示索引
System.out.println(array[i]);
}
- 方法的参数是数组
public class ArrayTest{
public static void main(String[] args){
//通过创建数组,传入方法中
int[] x = {1,2,3,4,5};
printArray(x);
//不能直接传入静态数组,只能这样写
//printArray({1,2,3,4,5});
printArray(new int[] {1,2,3,4,5});
// 传入 动态数组
printArray(new int[4]); //传入一个长度为4的数组
}
public static void printArray(int[] array){ //使用静态方法,不用创建对象,直接调用方法
for(int i : array){
System.out.println(array[i]);
}
}
}
- main方法中String[] args数组
这个数组是留给用户的,用户可以在控制台输入参数,这个参数自动转换为String[] args 数组中的元素
注意使用DOS可以直接加参数,在IDE中需要设置程序参数
例如运行程序 java ArrayTest abc def
那么这个时候JVM会自动将"abc def" 通过空格进行分割,然后从左到右依次存入数组
原先这个数组为空,传入参数后为 args = {“abc”, “def”};
public class ArrayTest{
public static void main(String[] args){
}
}
实例
/*
使用main方法的String[] args数组模拟用户登录
*/
public class Login{
public static void main(String[] args){
if(args.length != 2){
System.out.println("使用该系统时请输入系统参数,参数中包括用户名和密码信息,例如:zhangsan 123");
}
/*
程序执行这里说明用户确实输入了用户名和密码
接下来判断用户名和密码是否正确
*/
String userName = args[0];
String userPassword = args[1];
//if(userName.equals("admin") && userPassword.equals("123456")){
if("admin".equals(userName) && "123456".equals(userPassword)){ //这样写能够避免空指针异常,即使userName和userPassword为null,也不会报错。建议以后都这样写,比较对象时,使用确定不是空值的对象调用equals()方法
System.out.println("登录成功!"+userName+"欢迎你!");
}else{
System.out.println("验证失败,用户名不存在或者密码错误!");
}
}
}
- 数组中存储引用数据类型
public class ArrayTest{
public static void main(String[] args){
//创建两个Animal对象并存储到数组中,遍历数组调用方法
Animal a1 = new Animal();
Animal a2 = new Animal();
Animal[] animals = {a1,a2};
for(int i : animals){
animals[i].move();
}
//创建一个Animal类型的数组,数组当中存储Cat和Bird,
//因为Cat和Bird继承了Animal类,所以Cat和Bird对象可以存入Animal数组中
//相当于向上转型,通过调用父类的move方法,动态执行子类的move方法,这就是使用了多态
Animal[] anis = {new Cat(), new Bird()};
for(int i : anis){
//如果调用的方法是父类中存在的方法不需要向下转型,直接父类型引用调用即可
anis[i].move();
//如果调用子类中特有方法,就需要向下转型,因为anis数组中可能Cat或者Bird对象,所以需要先进行类型判断,才能进行向下转型。
if(anis[i] instanceof Cat){
Cat cat = (Cat)anis[i];
cat.catchMouse();
}else if(anis[i] instanceof Bird){
Bird bird = (Bird)anis[i];
bird.catchWorm();
}
}
}
}
class Animal{
public void move(){
System.out.println("move....");
}
}
class Cat extends Animal{
public void move(){
System.out.println("猫在走猫步");
}
public void catchMOuse(){
System.out.println("猫在抓老鼠");
}
}
class Brid extends Animal{
public void move(){
System.out.println("鸟在飞");
}
public void catchWorm(){
System.out.println("鸟在抓害虫");
}
}
- 一维数组扩容
/*
思想
如果想要扩容的话,重新申请一个大的数组,将已经满的数组的元素,移到大数组中
*/
//注意由于扩容需要用到遍历,将数组中的元素一个一个移动到大数组中去,效率极低。所以尽可能少使用数组扩容,使用数组时最好预估好数组的长度
//当然我们也可以使用System.arraycopy()方法进行数组的拷贝,然后进行扩容,但是这个与上述思想一致,效率也极低
- 数组拷贝
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
/*
src 拷贝源
dest 目标(源拷贝到这个目标)
srcPos 从源那个位置开始
destPos 从目标那个位置开始拷贝
length 从源中拷贝的个数
*/
int[] src = {1,2,3,4,5};
int[] dest = new int[20];
//拷贝
System.arraycopy(src, 1, dest, 3, 2);
for(int i: dest){
System.out.println(dest[i]); // 0 0 0 2 3 0 0...
}
1.3、二维数组
- 二维数组其实是一个特殊的一维数组,特殊在这个一维数组中每一个元素都是一个数组
- 一维数组可以看作空间中的一条线,二维数组可以看作空间中的一个有限平面,多条平行且紧密接触线做出一个平面,这个比喻也不太恰当,以为在空间中不在同一条直线上的三点可以形成一个平面。
- 初始化二维数组
//静态初始化
int[][] a = {
{1,2,3},
{4,5,6},
{7,8,9}
};
//动态初始化二维数组
int[][] a1 = new int[3][4];
- 二维数组的length属性
int[][] a = {
{1,2,3},
{4,5,6},
{7,8,9}
};
a.length //表示二维数组中有几个一维数组
a[i].length //表示二维数组中某个一维数组的长度
- 二维数组中元素的访问
int[][] a = {
{1,2,3},
{4,5,6},
{7,8,9}
};
/*取二维数组中的元素*/
//取二维数组a中的第一个一维数组
int[] a1 = a[0];
//取第一个一维数组的第一个元素
int a11 = a1[0];
//合并
int a11 = a[0][0];
//取二维数组中第2个一维数组中第三个元素
a[1][2];
- 遍历二维数组
String[][] array = {
{"java", "c++", "html", "go", "python"},
{"lucy", "tom", "hack"},
{"1", "2", "3"}
}
for(int i: array){
for(int j: array[i]){
System.out.println(array[i][j] + " ");
}
}
- 方法的参数是一个二维数组
public class ArrayTest{
public static void main(String[] args){
String[][] a = {
{"java", "c++", "html", "go", "python"},
{"lucy", "tom", "hack"},
{"1", "2", "3"}
}
//遍历数组
printArray(a);
}
public static void printArray(int[][] array){ //使用静态方法,不用创建对象,直接调用方法
for(int i: array){
for(int j: array[i]){
System.out.println(array[i][j] + " ");
}
}
}
}