1、java语言中的数组是一种引用数据类型。不属于基本数据类型
数组的父类是object
2、数组实际上是一个容器,可以同时容纳多个元素(数组是一个数据的集合)
3、字面意思:数组意味着一组数据
4、数组当中可以存储“基本数据类型”的数据,也可以存储“引用数据类型” 的数据
5、数组因为是引用类型,所以数组对象是堆内存中(数组是存储在堆当中的)
6、数组中如果存储的是java对象的话,实际上存储的是对象的引用(内存地址)
7、数组一旦创建,在java中规定,数组长度不可改变
8、数组的分类:一维数组、二维数组、三维数组
9、所有数组对象都有length属性(java自带的),用来获取数组中元素的个数
10、java中的数组要求数组中元素的类型统一,比如:int类型数组只能存储int类型,Person类型数组只能存储Person类型
11、数组在内存方面存储的时候,数组中的元素内存地址是连续的 (存储的每一个元素都是有规则的挨着排列的)
12、所有的数组都是拿“第一个小方框的内存地址”作为整个数组对象的内存地址
这就意味着只要我们获取到数组的第一个内存地址,就可以通过数学表达式推算出数组其它空间的内存地址
图示:
图示:
13、数组的每一个元素都是有下标的,下标从0开始,以1递增,最后一个元素的下标是length - 1
下标非常重要,因为我们对数组中元素进行存取的时候,都需要通过下标进行
14、数组这种数据结构的优点和缺点:
优点:
查询、查找、检索某个下标上的元素时效率极高。可以说是查询效率最高的一个数据结构
为什么检索效率高:
第一:每一个元素的内存地址在空间存储上都是连续的
第二:每一个元素的类型相同,所以占用的内存空间大小是一样的
第三:知道第一个元素的内存地址,知道每一个元素占用空间的大小,又知道下标,所以通过一个数学表达式就可以计算出某个下标上元素的内存地址
直接通过内存地址定位元素,所以数组的检索效率是最高的
数组中存储100个元素,或者存储100万个元素,在元素查询、检索方面,效率是相同的,因为数组中元素查找的时候,不会一个个找,是通过数学表达式计算出来的
【算出一个内存地址,直接定位的】
缺点:
第一:由于为了保证数组中每个元素的内存地址连续,所以在数组上随机删除或者增加元素的时候,效率极低,因为随机增删元素会设计到后面元素统一向前或者向后位移的操作。
第二:数组不能存储大的数据量,因为很难在内存空间中找到一块特别大的连续的内存空间
注意:对于数组中,最后一个元素的增删,是没有效率影响的
15、怎么声明一个一维数组?
语法格式:
int[ ] array1;
double[ ] array2;
String[ ] array3;
Object[ ] array4;
16、怎么初始化一个一维数组呢?
包括两种方式:静态初始化一维数组,动态初始化一维数组
静态初始化语法格式:
int[ ] array = {100,200,300};
动态初始化语法格式:
int[ ] array = new int [5];
这里的5表示数组的元素个数,意思是初始化一个5个长度的int类型数组,每个元素默认值为0
String[ ] names = new String[6];
这里的6表示数组的元素个数,意思是初始化一个6个长度的String类型数组,每个元素默认值为null
案例测试:
package com.lbj.javase.array;/*** @author LBJ* @version V1.0* @Package com.lbj.javase.array* @date 2021/2/15 20:42* @Copyright 公司*/
public class ArrayTest01 {public static void main(String[] args) {//声明一个int类型的数组,使用静态初始化的方式int[] a1={1,100,10,20};//所有的数组对象都有length属性System.out.println("数组中元素的个数"+a1.length);//数组中的每个元素都有下标//通过下标对数组中的元素进行存取System.out.println("第一个元素"+a1[0]);System.out.println("最后一个元素"+a1[3]);System.out.println("最后一个元素"+a1[a1.length-1]);}
}
运行结果:
以下演示静态初始化数组的例子:
package com.lbj.javase.array;/*** @author LBJ* @version V1.0* @Package com.lbj.javase.array* @date 2021/2/15 20:42* @Copyright 公司*/
public class ArrayTest01 {public static void main(String[] args) {//声明一个int类型的数组,使用静态初始化的方式int[] a1={1,100,10,20};//所有的数组对象都有length属性System.out.println("数组中元素的个数"+a1.length);//数组中的每个元素都有下标//通过下标对数组中的元素进行存(改操作)/取(读操作)System.out.println("第一个元素"+a1[0]);System.out.println("最后一个元素"+a1[3]);System.out.println("最后一个元素"+a1[a1.length-1]);//存(改操作)//把第一个元素改为100a1[0]=100;//把最后一个元素改为1a1[3]=1;a1[a1.length-1]=1;//结果演示System.out.println("修改后第一个元素"+a1[0]);System.out.println("修改后最后一个元素"+a1[3]);System.out.println("修改后最后一个元素"+a1[a1.length-1]);//一维数组如何遍历呢?for (int i=0;i<a1.length;i++){System.out.println("遍历后的数组为"+"第"+i+"个"+a1[i]);}//下标为4表示为第5个元素,系统检测到第5个元素没有,说明下标越界,会出现下标越界异常//ArrayIndexOutOfBoundsException//System.out.println(a1[4]);//从最后一个元素遍历到第1个元素for(int i=a1.length-1;i>=0;i--){System.out.println("颠倒遍历后的数组为"+"第"+i+"个"+a1[i]);}}
}
演示结果:
以下是动态初始化一维数组的演示:
什么时候采用静态初始化方式,什么时候使用动态初始化方式?
静态初始化:当你创建数组的时候,确定数组中存储哪些具体的元素时候,采用静态初始化的方式
动态初始化:当你创建数组的时候,不决定将来数组中存储哪些数据,你可以采用动态初始化的方式,预先分配内存空间
package com.lbj.javase.array;/*** @author LBJ* @version V1.0* @Package com.lbj.javase.array* @date 2021/2/16 21:55* @Copyright 公司*/
public class ArrayTest02 {public static void main(String[] args) {//声明一个数组,采用动态初始化的方式创建//创建长度为3的数组,数组中每个元素都存在系统自带的默认值int[] a=new int[3];//遍历数组for(int i=0;i<a.length;i++){System.out.println("遍历出数组的"+i+"个元素"+a[i]);}}
}
以下演示的是方法的参数是数组的时候的例子
package com.lbj.javase.array;/*** @author LBJ* @version V1.0* @Package com.lbj.javase.array* @date 2021/2/16 22:47* @Copyright 公司*/
public class ArrayTest04 {public static void main(String[] args) {//静态初始化一维数组int[] a={1,2,3};printArray(a);//思考:传递一个静态数组的是时候,为什么下面这样语法不行//printArray({1,2,3});//动态初始化一维数组int[] a2=new int[3];printArray(a2);//和上面的不同之处在于,数组内的值是自定义的,不是系统默认的,相当于静态一维数组printArray(new int[]{1,2,3});}//为什么要使用静态方法?因为方便,不需要new对象public static void printArray(int[] array){for (int i = 0; i <array.length; i++) {System.out.println(array[i]);}}
}
思考:
main方法上面的String[] args 有什么用?
分析:
谁负责调用main方法
答案:
JVM
JVM调用main方法的时候,会自动传递一个String数组过来
main方法上面的String[] args 数组主要是用来接收用户输入参数的
package com.lbj.javase.array;public class ArrayTest05 {public static void main(String[] args) {//JVM默认传递过来的这个数组对象的长度默认是0//通过测试得出args不是nullSystem.out.println("JVM传递过来的String数组参数,它这个数组的长度是?"+args.length);//这个数组什么时候里面会有值呢?//其实这个数组是留给用户的,用户可以在控制台上输入参数,这个参数会自动被转换成 String[] agrs}
}
模拟一个系统,假设这个系统要使用,必须输入用户名和密码
package com.lbj.javase.array;/**
模拟一个系统,假设这个系统要使用,必须输入用户名和密码*/
public class ArrayTest06 {//用户名和密码输入到String[] args 数组中public static void main(String[] args) {if (args.length != 2) {System.out.println("请输入正确的格式例如 张三 123");return;}//程序执行到此处说明用户提供了账号和密码//接下来你就应该判断账号和密码是否正确//取出账号String name=args[0];//取出密码String password=args[1];//假设账号是admin,密码是123//判断这两个字符串是否相等,需要使用equals方法//if(name.equals("admin") && password.equals("123")){//上面写可能会导致空指针异常//采用以下编码风格,即使name和password都是null,也不会出现空指针异常//这是老程序员给的建议if("admin".equals(name) && "123".equals(password)){System.out.println("登录成功");}else{System.out.println("对不起,账号密码不存在");}}
}
一维数组的深入,数组中存储的类型为:引用数据类型
对于数组来书,实际上只能存储java对象的内存地址,数组中存储的每个元素都是“引用”
package com.lbj.javase.array;/**
一维数组的深入,数组中存储的类型为:引用数据类型对于数组来书,实际上只能存储java对象的内存地址,数组中存储的每个元素都是“引用”*/
public class ArrayTest07 {public static void main(String[] args) {//静态初始化创建一个Animal类型的数组Animal a1=new Animal();Animal a2=new Animal();Animal[] animals={a1,a2};//对数组进行遍历for (int i = 0; i <animals.length ; i++) {Animal a=animals[i];a.move();System.out.println("遍历结果为"+i+animals[i]);}//动态初始化一个长度为2的Animal类型一维数组Animal[] a3=new Animal[2];//创建一个animal对象,放到数组的第一个盒子中a3[0]=new Animal();//Animal和Product没有关系,因此Animal数组中只能存放Animal类型的数据//a3[1]=new Product();报错//Animal数组中可以存放Cat类型的数据,因为Cat是一个Animal的子类//子类对象指向父类,这就是多态的用法a3[1]=new Cat();//向下类型转换要强制类型转换,向上类型转换需要自动类型转换//创建一个Animal类型的数组,数组中存放的Cat和BirdCat cat=new Cat();Bird bird=new Bird();Animal[] animals1={cat,bird};//Animal[] animals1={new Cat(),new Bird()};//该数组中存储了两个对象的内存地址for (int i = 0; i <animals.length ; i++) {//这个取出来可能是Cat类型的,也可能是Bird类型的,不过,肯定是一个Animal类型的//注意:以下代码演示的是如果调用的方法是父类中存在的方法则不需要向下转型,直接使用父类型调用即可//向下类型转换要强制类型转换,向上类型转换需要自动类型转换//注意:这里不是向下转型,这是子类重写了父类的方法,多以调用子类的时候运行的是Cat和BirdAnimal animals2=animals1[i];animals2.move();//调用子类中特有方法的话,需要向下转型if(animals1[i] instanceof Cat){Cat cat1= (Cat) animals1[i];cat1.catchMouse();}else if(animals1[i] instanceof Bird){Bird bird1= (Bird) animals1[i];bird1.sing();}}}}class Animal{public void move(){System.out.println("move...");}
}//创建一个毫不相关的商品类
class Product{}//创建一个继承Animal类的子类Cat类
class Cat extends Animal{//重写父类的方法public void move(){System.out.println("Cat can moveCat...");}//Cat类特有的方法public void catchMouse(){System.out.println("Cat can catchMouse");}
}//创建一个继承Animal类的子类Bird类
class Bird extends Animal{//重写父类的方法public void move(){System.out.println("Bird can moveBird...");}//Bird类特有的方法public void sing(){System.out.println("Bird can sing");}}
关于一维数组的扩容
问:
在java开发中,数组长度一旦确定意味着不可变,呢们数组满了怎么办?
答案:
数组满了需要扩容
问:
如何扩容
答案:
java中对数组的扩容是,先创建一个大容量的数组,然后将原先小容量数组中数据一个一个拷贝到大数据中
结论:
数组扩容效率较低,因为涉及到拷贝问题,应该尽可能少进行对数组的扩容
解决:
可以在创建数组对象的时候预估计一下多长合适,这样可以减少数组的扩容次数,提高效率
因此我们引入一个方法
public static native void arraycopy(Object src, int srcPos,Object dest, int destPos,int length);
Object src源数组地址、int srcPos源数组拷贝位置、Object dest新数组地址、int destPos新数组拷贝位置、int length拷贝长度
其实拷贝就是将内存地址进行复制到一个新的位置去存储,新的数组实际上也是用内存地址去指向对象,只不过新数组和源数组的内存地址不一致
以下是代码演示:
package com.lbj.javase.array;/**
java中数组的拷贝*/
public class ArrayTest08 {public static void main(String[] args) {// public static native void arraycopy(Object src, int srcPos,// Object dest, int destPos,// int length);//Object src源数组地址、int srcPos源数组拷贝位置、Object dest新数组地址、int destPos新数组拷贝位置、int length拷贝长度//源数组地址int[] src={1,2,3};//新数组地址int[] dest=new int[20];//调用JDK System类中的arraycopy方法,来完成数组的拷贝//System.arraycopy(src,1,dest,2,1);//如果源数组的拷贝位置和拷贝长度有矛盾,拷贝会报错// Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException// at java.lang.System.arraycopy(Native Method)// at com.lbj.javase.array.ArrayTest08.main(ArrayTest08.java:22)System.arraycopy(src,1,dest,2,src.length);//拷贝源数组的全部元素到新数组上//System.arraycopy(src,0,dest,0,src.length);//遍历拷贝完后的新数组for (int i = 0; i <dest.length ; i++) {System.out.println(dest[i]);}}
}