视频链接:https://www.bilibili.com/video/BV1Rx411876f?p=1
视频范围P543 - P575
目录描述
1.数组概述
- java语言中的数组是一种引用数据类型,不属于基本数据类型,数组的父类是Object
- 数组实际上是一个容器,可以同时容纳多个元素。(数组是一个数据的集合)
- 数组当中可以存储基本数据类型的数据,也可以存储引用数据类型的数据
- 数组因为是引用类型,所以数组对象是在堆内存当中。(数组是存储在堆当中的)
- 数组当中如果存储的是“java对象”的话,实际上存储的是对象的“引用(内存地址)”,数组中不能直接存储java对象
- 数组一旦创建,在java中规定,长度不可变(。数组长度不可变)
- 数组的分类:一维数组,二维数组,三维数组,多维数组…(一维数组较多,二维偶尔使用!)
- 所有的数组对象都有length属性(java自带的),用来获取数组中元素的个数
- java中的数组要求数组中元素的类型统一
比如:int类型数组只能存储int类型等等 - 数组存储元素的特点:数组中的元素内存地址(存储的每一个元素都是有规则的挨着排列的)是连续的。数组实际上是一种简单的数据结果
- 所有的数组都是拿“第一个小方框的内存地址”作为整个对象的内存地址。(数组中首元素的内存地址作为整个数组对象的内存地址)
- 数组中每一个元素都是有下标的,下标从0开始,以1递增,最后一个元素的下标是:length - 1
对数组中元素进行“存取”的时候,都需要通过下标来进行
数组内存演示图:
2.数组优缺点
优点:查询/查找/检索某个下标上的元素时效率极高。(查询效率最高的一个数据结构)
理由:
- 每一个元素的内存地址在空间存储上是连续的
- 每一个元素类型相同,所以占用空间大小一样
- 知道第一个元素内存地址,知道每一个元素占用空间的大小,又知道下标,所以通过一个数学表达式就可以计算出某个下标上元素的内存地址。直接通过内存地址定位元素,所以数组的检索效率是最高的
数组中存储100个元素,或者存储100万个元素,在元素查询/检索方法,效率是相同的,因为数组中元素查找的时候不会一个一个找,是通过数学表达式计算出来的。(算出一个内存地址,直接定位)
缺点:
- 由于为了保证数组中每个元素的内存地址连续,所以在数组上随机删除或者增加元素的时候,效率较低,因为随机增删元素涉及到后面元素统一向前或者向后位移的操作
- 数组不能存储大数据量
因为很难在内存空间上找打一块特别大的连续的空内存空间
注意:对于数组中最后一个元素的增删,是没有效率影响的。
3.一维数组
3.1 一维数组声明和初始化
语法格式:
int[] array1;
double[] array2;
boolean[] array3;
初始化一个一维数组:
- 静态初始化
语法格式:
int[] array = {100,200,300}
- 动态初始化
语法格式:
int[] array = new int[5];//这里5表示数组的元素个数
//初始化一个5个长度的int类型数组,每个元素默认值0
String[] names = new String[6];//初始化6个长度的String类型数组,每个元素默认值null
3.2 静态初始化一维数组
package array;
public class ArrayTest01 {
public static void main(String[] args) {
//声明一个Int类型的数组,使用静态初始化的方式
int[] a = {1,20,300,14,25};
//这是C++风格,不建议java中使用
//int a[] = {1,20,300,14,25};
//所有的数组对象都有length属性
System.out.println("数组中元素的个数:" + a.length);//输出为:数组中元素的个数:5
//数组中每一个元素都有下标
//通过下标对数组中的元素进行存和取
//取(读)
System.out.println("第一个元素 = " + a[0]);//输出为:第一个元素 = 1
System.out.println("最后一个元素 = " + a[4]);//输出为:最后一个元素 = 25
System.out.println("最后一个元素 = " + a[a.length - 1]);//输出为:最后一个元素 = 25
//存(改)
a[0] = 100;
a[4] = 520;
System.out.println("第一个元素 = " + a[0]);//输出为:第一个元素 = 100
System.out.println("最后一个元素 = " + a[4]);//输出为:最后一个元素 = 520
//一维数组正序遍历
for (int i = 0; i < a.length; i++) {
System.out.println(a[i]);
}
//报错:ArrayIndexOutOfBoundsException
//System.out.println(a[5]);
//一维数组逆序遍历
for (int i = a.length - 1; i >= 0 ; i--) {
System.out.println(a[i]);
}
}
}
3.3 动态初始化一维数组
package array;
public class ArrayTest02 {
public static void main(String[] args) {
int[] a = new int[4];
for (int i = 0; i < a.length; i++) {
System.out.println("数组中下标为:" + i +"的元素是:" + a[i]);
}
//赋值
a[0] = 1;
a[1] = 10;
a[2] = 100;
a[3] = 1000;
System.out.println("=============================================");
for (int i = 0; i < a.length; i++) {
System.out.println("数组中下标为:" + i +"的元素是:" + a[i]);
}
//初始化一个Object类型的数组,采用动态初始方式
Object[] objs = new Object[3];
System.out.println("=============================================");
for (int i = 0; i < objs.length; i++) {
System.out.println(objs[i]);
}
System.out.println("=============================================");
String[] strs = new String[3];
for (int i = 0; i < strs.length; i++) {
System.out.println(strs[i]);
}
System.out.println("=============================================");
//存储Object,采用静态初始化
Object[] objects = {new Object(),new Object(),new Object()};
for (int i = 0; i < objects.length; i++) {
System.out.println(objects[i]);
}
}
}
运行结果:
总结:
- 当创建数组的时候,确定数组中存储哪些具体的元素时,采用静态初始化方式
- 当创建数组的时候,不确定将来数组中存储哪些数据,采用动态初始化方式,预先分配内存空间
3.4 方法的参数是数组
代码实例一:
package array;
public class ArrayTest03 {
public static void main(String[] args) {
int[] x = {1,2,3,4};
printArray(x);
System.out.println("=====================");
String[] stringArray = {"a","b","c","d"};
printArray(stringArray);
System.out.println("=====================");
printArray(new String[3]);
System.out.println("=====================");
printArray(new int[4]);
}
public static void printArray(int[] array){
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
}
public static void printArray(String[] args){
for (int i = 0; i < args.length; i++) {
System.out.println(args[i]);
}
}
}
运行结果:
代码实例二:
使用静态方法很方便,不需要new对象
package array;
public class ArrayTest03 {
public static void main(String[] args) {
int[] x = {1,2,3,4};
printArray(x);
System.out.println("=====================");
//等价写法
printArray(new int[]{1,2,3,4});
}
public static void printArray(int[] array){
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
}
3.5 main方法中的“String[] args”
3.5.1 代码实例一
String[] args数组:主要是用来接受用户输入参数的
package array;
public class ArrayTest05 {
//这个方法程序员负责写出来,JVM负责调用,JVM调用的时候一定会传一个String数组过来
public static void main(String[] args) {
//JVM默认传递过来的这个数组对象长度为0
System.out.println("JVM给传递过来的String数组参数,长度为:" + args.length);//输出为:JVM给传递过来的String数组参数,长度为:0
System.out.println("===========================");
String[] strs1 = new String[0];//数组对象创建了,但是数组中没有任何数据
String[] strs2 = {};//静态初始化数组,里面没有东西
printLength(strs1);
printLength(strs2);
}
public static void printLength(String[] args){
System.out.println(args.length);
}
}
运行结果:
疑问:那这个数组什么时候里面会有值呢?
答: 这个数组是留给用户的,用户可以在控制台上输入参数,这个参数自动转换为“String[] args”
举例:
在cmd中运行程序:java ArrayTest05 abc def xyz
这时JVM自动将“abc def xyz”通过空格的方式进行分离,分离完之后,自动放到“String[] args”数组中
package array;
public class ArrayTest05 {
//这个方法程序员负责写出来,JVM负责调用,JVM调用的时候一定会传一个String数组过来
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
System.out.println(args[i]);
}
}
}
在IDEA中模拟cmd效果:
- 先点击Run中的Edit Configurations
- 找到对应项目中的Program arguments,输入abc def xyz,点击OK
- 运行程序,结果如下
3.5.2 代码实例二
模拟一个系统,假设这个系统要使用,必须输入用户名和密码
package array;
public class ArrayTest06 {
//用户名和密码输入到String[] args数组当中
public static void main(String[] args) {
if(args.length != 2){
System.out.println("使用该系统时请输入程序参数,参数中包括用户名和密码信息");
return;
}
//程序执行到此处说明用户确实提供了用户名和密码
//接下来判断用户名和密码是否正确
//取出用户名
String username = args[0];
//取出密码
String password = args[1];
//假设用户名为admin,密码为123的时候表示登录成功,其余一律失败
//判断两个字符串是否相等,需要使用equals方法
if(username.equals("admin") && password.equals("123")){
System.out.println("登录成功,欢迎[" + "admin" + "]回来!");
System.out.println("您可以继续使用该系统");
}else{
System.out.println("验证失败,用户名不存在或者密码错误!");
}
}
}
其中,在进行判断的时候,需要进行改动:
if("admin".equals(username) && "123".equals(password)){
System.out.println("登录成功,欢迎[" + "admin" + "]回来!");
System.out.println("您可以继续使用该系统");
}else{
System.out.println("验证失败,用户名不存在或者密码错误!");
}
改动后,即使username和password都是null,也不会出现空指针异常!
根据代码实例一中的模拟cmd方式赋值,最终效果为:
3.6 数组中存储引用数据类型
代码实例一:
package array;
public class ArrayTest07 {
public static void main(String[] args) {
//创建Animal类型的数组
Animal a1 = new Animal();
Animal a2 = new Animal();
Animal[] animals = {a1 , a2};
//对Animal数组进行遍历
for (int i = 0; i < animals.length; i++) {
Animal a = animals[i];
a.move();
}
}
}
class Animal{
public void move(){
System.out.println("Animal move....");
}
}
运行结果:
代码实例二:
对于数组来说,实际上只能存储java对象的“内存地址”,数组中存储的每个元素是“引用”
package array;
public class ArrayTest07 {
public static void main(String[] args) {
//创建一个Animal类型的数组,数组当中存储Cat和Bird
Cat c = new Cat();
Bird b = new Bird();
Animal[] anis = {c,b};
//等价于
//Animal[] anis = {new Cat(),new Bird()};
for (int i = 0; i < anis.length; i++) {
//取出来的可能是Cat,也可能是Bird,不过肯定是一个Animal
//如果调用的方法是父类中存在的方法不需要向下转型, 直接使用父类型引用调用就行
Animal an = anis[i];
an.move();
}
}
}
class Animal{
public void move(){
System.out.println("Animal move....");
}
}
//动物类的子类
class Cat extends Animal{
public void move() {
System.out.println("猫在走猫步!");
}
}
//动物类的子类
class Bird extends Animal{
public void move() {
System.out.println("鸟儿在翱翔!");
}
}
运行结果:
代码实例三:
调用子对象特有方法的话,需要向下转型!
package array;
public class ArrayTest07 {
public static void main(String[] args) {
//创建一个Animal类型的数组,数组当中存储Cat和Bird
Cat c = new Cat();
Bird b = new Bird();
Animal[] anis = {c,b};
//等价于
//Animal[] anis = {new Cat(),new Bird()};
for (int i = 0; i < anis.length; i++) {
if (anis[i] instanceof Cat) {
Cat cat = (Cat)anis[i];
cat.catchMouse();
}else if(anis[i] instanceof Bird){
Bird bird = (Bird)anis[i];
bird.sing();
}
}
}
}
class Animal{
public void move(){
System.out.println("Animal move....");
}
}
//动物类的子类
class Cat extends Animal{
public void move() {
System.out.println("猫在走猫步!");
}
public void catchMouse(){
System.out.println("猫抓老鼠!");
}
}
//动物类的子类
class Bird extends Animal{
public void move() {
System.out.println("鸟儿在翱翔!");
}
public void sing(){
System.out.println("鸟儿在唱歌!");
}
}
运行结果:
3.7 一维数组的扩容和拷贝
在java开发中,数组长度一旦确定不可变,当数组满了后,需要扩容。(无论数组中的是基本数据类型还是引用数据类型,都可以进行拷贝)
java中对数组的扩容是:
- 先新建一个大容量的数组
- 然后将小容量数组中的数据一个个拷贝到大数组当中
注意:
- 数组扩容效率低,所以以后在开发中尽可能少的进行数组的拷贝
- 在创建数组对象的时候预估计一下多长合适,这样考研减少数组的扩容次数,提高效率
package array;
public class ArrayTest08 {
public static void main(String[] args) {
//拷贝源(从这个数组中拷贝)
int[] src = {1,5,6,9};
//拷贝目标(拷贝到这个目标数组上)
int[] dest = new int[10];
//调用JDK System类中的arraycopy方法,来完成数组的拷贝
System.arraycopy(src,1,dest,3,3);
//遍历目标数组
for (int i = 0; i < dest.length; i++) {
System.out.println(dest[i]);
}
}
}
运行结果:
4.二维数组
- 二维数组其实是一个特殊的一维数组,特殊在这个一维数组当中的每一个元素是一个一维数组
- 三维数组是一个特殊的二维数组,特殊在这个二维数组当中的每一个元素是一个二维数组
- 实际开发中使用最多的就是一维数组,二维数组也很少使用,三维数组几乎不用
- 二维数组静态初始化
int[][] array = {{1,2,3},{4,5,6},{7,8,9}};
4.1 二维数组中元素的:读和改
public class ArrayTest09{
public class void main(String[] args){
//定义二维数组
int[][] array = {{1,2,3},{4,5,6},{7,8,9}};
//读
System.out.println(array[0][0]);//输出为:1
//改
array[0][0] = 10;
System.out.println(array[0][0]);//输出为:10
//注意别越界
//System.out.println(array[0][5]);
}
}
4.2 二维数组的遍历
package array;
public class ArrayTest11 {
public static void main(String[] args) {
//定义二维数组
String[][] array = {{"java","oracle","c++","python","c#"},
{"张三","李四","王五"},
{"lucky","jack","rose"}};
//遍历二维数组
for (int i = 0; i < array.length; i++) {
String[] arr = array[i];
//负责遍历一维数组
for (int j = 0; j < arr.length; j++) {
System.out.print(arr[j] + " ");
}
//输出换行符
System.out.println();
}
System.out.println("=================");
//等价的合并代码
for (int i = 0; i < array.length; i++) {
//负责遍历一维数组
for (int j = 0; j < array[i].length; j++) {
System.out.print(array[i][j] + " ");
}
//输出换行符
System.out.println();
}
}
}
运行结果:
4.3 动态初始化二维数组
package array;
public class ArrayTest12 {
public static void main(String[] args) {
//动态初始化二维数组
int[][] array = new int[3][4];
printArray(array);
System.out.println("====================");
//静态初始化二维数组
int[][] array1 = {{1,2,3},{4,5,6},{7,8,9}};
printArray(array1);
System.out.println("====================");
//等价于
printArray(new int[][]{{1,2,3},{4,5,6},{7,8,9}});
}
//遍历数组
public static void printArray(int[][] array){
for (int i = 0; i < array.length; i++) {
for (int j = 0; j < array[i].length; j++) {
System.out.print(array[i][j] + " ");
}
System.out.println();
}
}
}
运行结果:
5.数组模拟栈
需求:编写程序,使用一维数组,模拟栈数据结构
要求:
- 这个栈可以存储java中的任何引用类型的数据
- 在栈中提供push方法模拟压栈(栈满了,要有提示信息)
- 在栈中提供pop方法模拟弹栈(栈空了,要有提示信息)
- 编写测试程序,new栈对象,调用push,pop方法来模拟压栈弹栈的动作
- 假设栈的默认初始化容量是10。(请注意无参数构造方法的编写方式)
栈类:
package array.homework;
public class MyStack {
//向栈中存储元素,这里使用一维数组模拟,存到栈中,就表示存储到数组中
//Object类型数组可以存储java中的任何引用类型的数据(一般对象的超级父类就是Object)
//String父类也是Object,但是在java中有优待,不需要new也是一个对象
//private Object[] elements = new Object[10]; 也可以这样进行初始化 //等号在构造方法执行的时候赋值
private Object[] elements;
//栈帧 永远指向栈顶元素
private int index = -1;
//无参构造方法
public MyStack() {
//默认初始化栈容量10
this.elements = new Object[10];
}
//压栈
public void push(Object obj){
if (this.index >= this.elements.length - 1){
System.out.println("压栈失败,栈已满!");
return;
}
//程序能够走到这里,说明栈没满
//向栈中加1个元素,栈帧向上移动一个位置
this.index++;
this.elements[index] = obj;
//上面两行可以等价于
//this.elements[++index] = obj;
//obj是一个引用,自动调用引用的toString()方法 如:obj.toString()
System.out.println("压栈"+ obj + "元素成功,栈帧指向 " + this.index);
}
//弹栈
public void pop(){
if (this.index < 0){
System.out.println("栈已空,弹栈失败!");
return;
}
//程序能够走到这里,说明栈没空
System.out.println("弹栈"+ this.elements[this.index] + "元素成功 ");
//栈帧向下移动一位
this.index--;
System.out.println(" 栈帧指向 " + this.index);
}
//set和get方法也许用不上,但是必须写,这是规矩
//封装:第一步:属性私有化 第二步:对外提供set和get方法
public Object[] getElements() {
return elements;
}
public void setElements(Object[] elements) {
this.elements = elements;
}
}
测试类:
package array.homework;
public class ArrayTest {
public static void main(String[] args) {
//创建一个栈对象,初始化容量是10个
MyStack stack = new MyStack();
//调用方法压栈
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());//最后压入的,最先弹出来
//弹栈
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
}
}
运行结果:
6.酒店管理系统
需求:为酒店编写程序:酒店管理系统,模拟订房、退房、打印所有房间状态等功能
要求:
- 该系统的用户是:酒店前台
- 酒店使用一个二维数组来模拟。“Room[][] rooms;”
- 酒店中的每一个房间应该是一个java对象:Room
- 每一个房间Room应该有:房间编码、房间类型、房间是否空闲
- 系统应该对外提供的功能:
可以预定房间:用户输入房间编号,订房
可以退房:用户输入房间编号,退房
可以查看所有房间的状态:用户输入某个指令应该可以查看所有房间状态
房间类:
package array.homework;
public class Room {
//房间编号 比如 1楼:101 102 103 2楼: 201 202 203等等
private int no;
//房间类型 比如:标准间、单人间、总统套房
private String type;
//房间状态 比如:true 空闲,房间可以预定;false 占用,房间不能预定
private boolean status;
//无参构造方法
public Room() {
}
//有参构造方法
public Room(int no, String type, boolean status) {
this.no = no;
this.type = type;
this.status = status;
}
//set和get方法
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
//IDEA工具对于boolean类型的遍历,生成的get方法名是:isXxx(),如果不喜欢可以手动修改为getXxx()
public boolean isStatus() {
return status;
}
public void setStatus(boolean status) {
this.status = status;
}
//equals方法重写
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof Room)) {
return false;
}
if (this == obj) {
return true;
}
Room room = (Room) obj;
return this.getNo() == room.getNo();
}
//toString方法重写
public String toString(){
return "[" + this.no + "," + type + "," +(status ? "空闲":"占用") + "]";
}
//临时测试程序
/*public static void main(String[] args) {
Room room = new Room(101,"单人间",false);
System.out.println(room.toString());
//等价于
//System.out.println(room);
}*/
}
酒店类:
package array.homework;
public class Hotel {
//二维数组,模拟大厦所有的房间
private Room[][] rooms;
//盖楼通过构造方法来盖楼
public Hotel(){
//一共有几层,每层的房间类型是什么,每个房间的编号是什么
//先写死,一共三层,一层单人间,二层标准间,三层总统套房
//房间编号 比如 1楼:101 102 103 2楼: 201 202 203等等
//三层楼,每层10个房间
rooms = new Room[3][10];
//创建30个Room对象,放到数组当中
for (int i = 0; i < rooms.length; i++) {
for (int j = 0; j < rooms[i].length; j++) {
if (i == 0 ){
rooms[i][j] = new Room((i + 1)*100 + (j + 1),"单人间",true);
}else if (i == 1){
rooms[i][j] = new Room((i + 1)*100 + (j + 1),"标准间",true);
}else if(i == 2){
rooms[i][j] = new Room((i + 1)*100 + (j + 1),"总统套房",true);
}
}
}
}
//在提供一个打印房间列表的方法
public void print(){
//就是遍历二维数组
for (int i = 0; i < rooms.length; i++) {
//里面的for循环负责输出一层
for (int j = 0; j < rooms[i].length; j++) {
Room room = rooms[i][j];
System.out.print(room.toString());
}
//换行
System.out.println();
}
}
//订房方法,调用此方法时需要传递一个房间编号过来,编号是前台输入的
public void order(int roomNo){
//订房最主要的是将房间对象的status修改为false
//Room对象的status修改为false
rooms[roomNo / 100 - 1][roomNo % 100 - 1].setStatus(false);
System.out.println(roomNo + "已订房!");
}
//退房方法
public void exit(int roomNo){
//订房最主要的是将房间对象的status修改为false
//Room对象的status修改为false
rooms[roomNo / 100 - 1][roomNo % 100 - 1].setStatus(true);
System.out.println(roomNo + "已退房!");
}
}
测试类:
package array.homework;
import java.util.Scanner;
public class HotelMgtSystem {
public static void main(String[] args) {
Hotel hotel = new Hotel();
System.out.println("欢迎使用酒店管理系统!请认真阅读以下使用说明:");
System.out.println("功能编号对于的功能:【1】表示查看房间列表 【2】表示订房 【3】表示退房");
Scanner s = new Scanner(System.in);
while (true){
System.out.println("请输入功能编号:");
int i = s.nextInt();
if (i == 1){
//查看房间列表
hotel.print();
}else if (i == 2) {
//订房
System.out.println("请输入订房编号");
int roomNo = s.nextInt();//小姐姐输入房间编号
hotel.order(roomNo);
}else if (i == 3) {
//退房
System.out.println("请输入退房编号");
int roomNo = s.nextInt();//小姐姐输入房间编号
hotel.exit(roomNo);
}else if (i == 0) {
System.out.println("再见,欢迎下次再来!");
return;
}else{
System.out.println("输入功能编号有误,请重新输入!");
}
}
}
}
运行结果: