day03【流程控制、JVM内存、数组】
一、流程控制
1.1 流程控制概述
我们知道程序是从上往下依次执行的,但有的时候我们必须改变程序的执行顺序,如到达某个条件才执行某段程序,这样我们必须对程序的执行流程加以控制;
1.2 判断语句
1.2.1 if 语句
if 被翻译为如果,如果布尔表达式成了(结果为真),则执行if语句中的程序,反之不执行;
- if语句格式:
if(条件表达式){
执行的语句...
}
- if语句执行流程
- 1)根据条件表达式判断结果是true还是false;
- 2)如果是true就执行if语句体中的代码;
- 3)如果是false就不执行if语句体中的代码;
例如:今天上班了吗?上了(真),奖励100块钱;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01 {
public static void main(String[] args) {
boolean job = true;
if(job){
System.out.println("奖励100块钱!");
}
}
}
1.2.1 if…else 语句
else翻译过来是"其他",当if中的条件表达式不满足时,则执行else中的语句;
- if…else语句执行流程:
- 1)根据条件表达式判断结果是true还是false
- 2)如果是true则执行if语句体中的代码;
- 3)如果是false则执行else语句体中的代码;
例如:今天下雨了吗?下了(真),打不了篮球了,没下(假)可以去打篮球了!
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo02 {
public static void main(String[] args) {
boolean raining = false;
if(raining){
System.out.println("打不了篮球了!");
}else{
System.out.println("去打篮球吧!");
}
}
}
1.2.3 if…else if…else 语句
当输出结果可能有多个条件时,如果我们需要根据不同的结果来反馈不同的信息,就需要使用到if…else if…else语句了;
- if…else if…else语句执行流程:
- 1)判断条件表达式1的结果是true还是false;
- 2)如果为true则执行语句体1
- 3)如果为false则执行条件表达式2,判断其结果为true还是false;
- 4)以此类推…
例如:数字1代表男,数字0代表女,其他数字提示输入错误!
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo03 {
public static void main(String[] args) {
int sex = 1;
if (sex == 1) {
System.out.println("你是男的啊!");
} else if (sex == 0) {
System.out.println("你是女的啊!");
} else {
System.out.println("???");
}
}
}
1.3.4 if语句综合练习
需求:指定学生的成绩,范围在[0-100],根据成绩判断等级;
[90-100]等级为“优秀”
[80-90)等级为“良好”
[70-80)等级为“中等”
[60-70)等级为“及格”
[0-60)等级为“不及格”
其它数字表示成绩不合法。
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo04 {
public static void main(String[] args) {
int score = 88;
if (score < 0 || score > 100) {
System.out.println("对不起,您输入的成绩不合法!");
} else if (score >= 90 && score <= 100) {
System.out.println("成绩等级:优秀");
} else if (score < 90 && score >= 80) {
System.out.println("成绩等级:良好");
} else if (score < 80 && score >= 70) {
System.out.println("成绩等级:中等");
} else if (score < 70 && score >= 60) {
System.out.println("成绩等级:及格");
} else if (score < 60 && score >= 0) {
System.out.println("成绩等级:不及格");
}
}
}
1.3 选择语句
1.3.1 switch 语句
switch 语句和 if 语句一样,都属于选择语句(分支语句),不再赘述,我们直接来看一下一个比较完整的 switch 语句结构是怎样的,请看下图:
需求:根据数字判断是星期几;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01 {
public static void main(String[] args) {
// 定义变量,判断是星期几
int weekday = 3;
//switch语句实现选择
switch (weekday) {
case 1:
System.out.println("星期一");
break;
case 2:
System.out.println("星期二");
break;
case 3:
System.out.println("星期三");
break;
case 4:
System.out.println("星期四");
break;
case 5:
System.out.println("星期五");
break;
case 6:
System.out.println("星期六");
break;
case 7:
System.out.println("星期日");
break;
default:
System.out.println("输入有误!");
break;
}
}
}
tips:switch语句中,表达式的数据类型,可以是byte,short,int,char,enum(枚举),JDK7后可以接收字符串。
1.3.2 case 合并
在switch 语句当中 case 是可以进行合并的,例如以下代码:
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo02 {
public static void main(String[] args) {
int season = 3;
switch (season){
case 1: case 2: case 3:
System.out.println("爆竹声中一岁除,春风送暖入屠苏");
break;
case 4: case 5: case 6:
System.out.println("接天莲叶无穷碧,映日荷花别样红");
break;
case 7: case 8: case 9:
System.out.println("塞下秋来风景异,衡阳雁去无留意");
break;
case 10: case 11: case 12:
System.out.println("窗含西岭千秋雪,门泊东吴万里船");
break;
}
}
}
1.3.3 case 穿透
switch 语句当中当某个分支匹配成功,则开始执行此分支当中的 java 语句,当遇到当前分支中的“break;”语句,switch 语句就结束了,但是如果当前分支中没有“break;”语句,则会发生 case 穿透现象,也就是说下一个分支也不再进行匹配,直接进入下一个分支执行,直到遇到break为止。
示例:
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo03 {
public static void main(String[] args) {
int i = 1;
switch (i) {
case 0:
System.out.println("0");
break;
case 1:
System.out.println("1");
case 2:
System.out.println("2");
default:
System.out.println("default");
}
}
}
执行情况:
二、循环语句
在不少实际问题中有许多具有规律性的重复操作,因此在程序中就需要重复执行某些语句。一组被重复执行的语句称之为循环体,能否继续重复,取决于循环的终止条件。循环结构是在一定条件下反复执行某段程序的流程结构,被反复执行的程序被称为循环体。循环语句是由循环体及循环的终止条件两部分组成的。
Java中有三种主要的循环结构:
- while 循环
- do…while 循环
- for 循环
2.1 while 循环
2.1.1 while 循环语法
while是最基本的循环,它的结构为:
while(条件表达式) {
// 循环内容
}
- while语句执行流程:
- 1)根据条件表达式的结果判断是true还是false;
- 2)如果是true则执行循环体语句;
- 3)如果是false则不执行循环体语句;
- 4)循环体语句珍惜完毕后重新根据条件表达式判断是否再次执行循环体语句;
2.1.2 while 语句练习
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01 {
public static void main(String[] args) {
while (true){
System.out.println("执行了循环体");
}
}
}
在上述代码中,由于条件表达式是一个定值,要么一直true,要么一直为false,为true则一直执行循环体,造成死循环;为false则不会执行一次循环体的代码;
那么使用 while 循环实现输出 1~5 应该怎么做呢?
我们尝试改进一下条件表达式中的条件:
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo02 {
public static void main(String[] args) {
int i = 1;
while (i <= 5) {
System.out.println("执行了" + i + "次");
/*
执行完毕一次循环体i自增1
i的变化: 1,2,3,4,5
*/
i++;
}
}
}
2.2 while 循环练习
我们前面尝试使用while循环输出1~5的值,其写法有很多种,大家可以参考:
- 第一种写法:
public static void print1() {
int i = 0;
while (i < 5) {
System.out.println("执行了" + (i + 1) + "次");
/**
* i的变化值: 0,1,2,3,4
*/
i++;
}
}
- 第二种写法:
public static void print2() {
int i = 0;
while (i < 5) {
/**
* i的变化值: 0(不打印),1,2,3,4,5
*/
System.out.println("执行了" + (++i) + "次");
}
}
- 第三种写法:
public static void print3() {
int i = 1;
while (i <= 5) {
/**
* i的变化值: 1,2,3,4,5,6(不打印)
*/
System.out.println("执行了" + (i++) + "次");
}
}
2.3 do…while 循环
2.3.1 do…while 循环语法
对于 while 语句而言,如果不满足条件,则不能进入循环。但有时候我们需要即使不满足条件,也至少执行一次。do…while 循环和 while 循环相似,不同的是,do…while 循环至少会执行一次。
语法:
do {
// 代码语句
}while(条件表达式);
- while语句执行流程:
- 1)首先执行一次循环体语句;
- 2)执行循环体语句之后进行条件判断;
- 3)如果是true则继续执行循环体语句,如果是false则结束do…while循环;
- 与while循环不同的是,do…while循环即使条件表达式不成立也会执行一次循环体语句:
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01 {
public static void main(String[] args) {
do {
System.out.println("do...while执行...");
} while (false);
}
}
2.3.2 do…while语句练习
- 使用
do...while
循环实现输出 1~5:
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01 {
public static void main(String[] args) {
int i = 0;
do {
System.out.println("执行了" + (i + 1) + "次");
/**
* i的变化值: 0,1,2,3,4
*/
i++;
} while (i < 5);
}
}
2.4 for 循环
2.4.1 for 循环语法
虽然所有循环结构都可以用 while 或者 do…while表示,但 Java 提供了另一种语句 —— for 循环,使一些循环结构变得更加简单。for循环执行的次数是在执行前就确定的。语法格式如下:
for(条件初始化①; 条件表达式②; 增量表达式④) {
// 循环体语句③
}
- for循环执行流程:
- 1)执行步骤①进行条件初始化
- 2)执行步骤②判断条件是否成立,如不成立则终止for循环,不执行③和④
- 3)如步骤②返回true,执行步骤③循环体,之后执行步骤④进行条件修改
2.4.2 for 循环练习
使用for循环输出1~5:
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01 {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
/**
* i的变化值: 0,1,2,3,4
*/
System.out.println("执行了" + (i + 1) + "次");
}
}
}
其他写法:
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo02 {
public static void main(String[] args) {
for (int i = 5; i >0 ; i--) {
/**
* i的变化值: 5,4,3,2,1
*/
System.out.println("执行了" + i+ "次");
}
}
}
2.5 嵌套循环
嵌套循环指的是循环的循环体语句是另一个循环。比如for循环里面还有一个for循环,就是嵌套循环。
总共的循环次数=外循环次数*内循环次数;
语法格式:
for(条件初始化①; 条件表达式②; 增量表达式④) {
for(条件初始化①; 条件表达式②; 增量表达式④) {
// 循环体语句③
}
}
循环体可以无限嵌套,但是循环体嵌套层次越深,那么代码的执行效率将会非常低;
循环嵌套练习:
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo03 {
public static void main(String[] args) {
// 外层循环控制行
for (int i = 0; i < 6; i++) {
// 内层循环控制列
for (int j = 0; j < 8; j++) {
// 不要换行
System.out.print("*");
}
// 一次外层循环执行完毕时进行换行(外层循环执行多次那么就会有多少行)
System.out.println();
}
}
}
tips:不仅是for循环可以嵌套,while、do…while这些循环一样是可以进行嵌套的;不仅如此,这些循环都可以相互嵌套,比如for里面有个while,while里面有个do…while,do…while里面又有一个for…
2.6 break 关键字
break 主要用在循环语句或者 switch 语句中,用来跳出整个语句块。break 跳出最里层的循环,并且继续执行该循环下面的语句。
/**
* @author lscl
* @version 1.0
* @intro: break语句
*/
public class Demo04 {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
if (i == 5) {
// i==5的时候跳出循环
break;
}
System.out.println(i);
}
}
}
break的作用一般是提前结束循环,或者是满足某个条件时手动结束循环
2.7 continue 关键字
continue 作用是让程序立刻跳转到下一次循环的迭代。在 for 循环中,continue 语句使程序立即跳转到更新语句。在 while 或者 do…while 循环中,程序立即跳转到布尔表达式的判断语句。
/**
* @author lscl
* @version 1.0
* @intro: continue语句
*/
public class Demo05 {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
if (i == 5) {
// i==5的时候跳出本次循环,继续下一次循环
continue;
}
System.out.println(i);
}
}
}
continue的作用是结束本次循环,继续下一次循环;
三、数组
3.1 数组概述
3.1.1 什么是数组
数组是编程语言中非常常见的一种数据结构,是一种容器,用于存储多个相同类型的数据(元素)。在数组中,会为每一个元素分配一个下标(也叫索引),该下标默认从0开始,一直自增累加,我们可以通过下标来访问数组中的任意元素;
总结:数组就是一个容器,能够帮我们存储很多相同类型的数据;
3.1.2 数组的特点
前面提到过,数组就是一个容器,可以存储很多相同类型的数据;此外,数字的长度是固定的,最大下标默认为长度-1
如图所示:
数组特点:
- 1)数组是一个容器,只能存储相同类型的数据;
- 2)数组的长度在创建时就已经定义好,不可改变;
- 3)数组的最大下标为长度-1
3.2 数组的定义
3.2.1 方式一:
动态初始化,只指定数组的长度,由系统为数组的每个元素分配初始值;
数组存储的数据类型[] 数组名字=new 数组存储的数据类型[长度];
int[] arr=new int[3];
tips:数组的长度一旦指定,不能更改
3.2.2 方式二:
静态初始化,创建数组时,显式的指定数组元素的初始值,数组的长度由系统根据元素的格式来决定;
数据类型[] 数组名称=new 数据类型[]{元素1,元素2,元素3....};
int[] arr=new int[]{3,5,6};
tips:数组已经给定了元素,就不能再指定长度了
3.2.3 方式三:
数据类型[] 数组名称={元素1,元素2,元素3};
int[] arr={1,34,65};
tips:即指定长度又给定元素;
3.3 数组的访问
直接输出数组名,输出的是该数组在内存中的内存地址值[I@1b6d3586
- 索引:前面提到过,数组会为其中每个元素分配一个下标,这个下标我们称之为索引,索引在数组中都是从0开始,我们可以通过数组的索引来获取数组中的任意元素
访问格式
数组名[索引];
示例:
arr[0];
-
通过索引访问数组中的元素
- 数组名[索引]:取出数组中指定索引位置上的元素
- 数组名[索引]=数值:给数值中指定索引位置上的元素赋值
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01 {
public static void main(String[] args) {
// 定义数组
int[] arr = {520, 521, 1314};
/*
输出: [I@1540e19d
[: 表示数组
I: 表示int类型数组, D表示double类型数组
@: 没有什么用(分割作用)
1540e19d: 数组在内存中的地址
*/
System.out.println(arr);
// 通过索引访问数组中的元素
System.out.println(arr[0]);
System.out.println(arr[1]);
System.out.println(arr[2]);
//通过索引修改数组中的元素
arr[1]=20;
// 输出20
System.out.println(arr[1]);
}
}
3.4 数组的长度属性
数组的长度属性: 每一个数组都具有一个length
属性,该属性的值为当前数组的元素个数,也就是数组的长度,通过数组名.length
,可获取当前数组的长度值,返回的是一个int数,由此可以推断出,数组的最大索引值为length-1
;
- 用法:
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo02 {
public static void main(String[] args) {
int[] arr = {1, 4, 7, 2, 5, 8, 3, 6, 9};
//打印数组的长度: 9
System.out.println(arr.length);
}
}
3.5 数组遍历
遍历数组的意思就是对数组整个数据的一个搜索,通过数组的长度属性我们可以知道数组里面到底有多少个元素,使用循环语句便可以逐个取出:
- 数组遍历: 就是将数组中的每个元素分别获取出来。遍历也是数组操作中的基石。
public static void main(String[] args) {
int[] arr=new int[]{11,22,33,44};
System.out.println(arr[0]);
System.out.println(arr[1]);
System.out.println(arr[2]);
}
以上代码是可以将数组中每个元素全部遍历出来,但是如果数组元素非常多,这种写法肯定不行,因此我们需要改
造成循环的写法。数组的索引是 0
到 lenght-1
,可以作为循环的条件出现。
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo03 {
public static void main(String[] args) {
int[] arr = new int[]{11, 22, 33};
/*
System.out.println(arr[0]);
System.out.println(arr[1]);
System.out.println(arr[2]);
*/
//以上代码的问题:如果数组有几万个元素,我们打印就需要写几万行
//我们发现上面的代码是重复的代码,只有arr[]中的数字是变化的
//对于重复的代码我们可以用循环来解决
for (int i = 0; i < 3; i++) {
System.out.println(arr[i]);
}
//以上代码看似非常好,但是不通用,如果数组元素多或少i<4;这个4不是固定的
//数组最小索引:0
//数组最大索引:arr.length - 1
//将4替换成arr.length,从0打印到arr.length-1
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
不同循环遍历数组:
- for循环遍历数组:
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo03 {
public static void main(String[] args) {
int[] arr = {1, 4, 7, 2, 5, 8};
}
/**
* for循环遍历数组
* @param arr
*/
public static void print1(int[] arr){
for (int i = 0; i < arr.length; i++) {
/**
* i的变化: 0,1,2,3,4,5
*/
System.out.println(arr[i]);
}
}
}
- while循环遍历数组:
/**
* while循环遍历数组
* @param arr
*/
public static void print2(int[] arr){
// 定义数组索引,从0开始
int i=0;
while (i<arr.length){
System.out.println(arr[i]);
// 每次循环i自增1
i++;
}
}
- do…while循环遍历数组:
/**
* do...while循环遍历数组
*
* @param arr
*/
public static void print3(int[] arr) {
// 定义数组索引,从0开始
int i=0;
do {
System.out.println(arr[i]);
// 每次循环i自增1
i++;
}while (i<arr.length);
}
3.5 数组越界异常
观察如下代码:
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo07 {
public static void main(String[] args) {
// 数组长度为3,最大索引为3-2=1
int[] arr = {1, 4, 7};
System.out.println(arr[3]);
}
}
创建数组,赋值3个元素,数组的索引就是0,1,2,没有3索引,因此我们不能访问数组中不存在的索引,程序运行后,将会抛出 ArrayIndexOutOfBoundsException
数组越界异常。在开发中,数组的越界异常是不能出现的,一旦出现了,就必须要修改我们编写的代码。
3.6 空指针异常
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo08 {
public static void main(String[] args) {
int[] arr = {1, 4, 7};
arr=null;
System.out.println(arr[1]);
}
}
arr = null
这行代码,意味着变量arr将不会在保存数组的内存地址,也就不允许再操作数组了,因此运行的候会抛出 NullPointerException
空指针异常。
四、数组原理内存图
3.1 内存概述
内存是计算机中的重要原件,临时存储区域,作用是运行程序。我们编写的程序是存放在硬盘中的,在硬盘中的程序是不会运行的,必须放进内存中才能运行,运行完毕后会清空内存。
Java虚拟机要运行程序,必须要对内存进行空间的分配和管理。
3.2 Java虚拟机的内存划分
为了提高运算效率,就对空间进行了不同区域的划分,因为每一片区域都有特定的处理数据方式和内存管理方式。
- JVM的内存划分:
区域名称 | 作用 |
---|---|
寄存器 | 给CPU使用,和我们开发无关 |
本地方法栈 | JVM在使用操作系统功能的时候使用,和我们开发无关 |
方法区 | 存储可以运行的class文件,包含类的一些基本信息、常量、静态变量等 |
堆内存 | 存储对象或者数组,通过new创建的,都存储在堆内存 |
方法栈(虚拟机栈) | 也叫虚拟机栈,VM栈,方法运行时使用的内存,所有方法调用时,都会进栈执行。比如main方法运行时,就会进如方法栈中执行。 |
- JVM内存图解:
3.3 数组在内存中的存储
3.3.1 一个数组内存图
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo04 {
public static void main(String[] args) {
int[] arr = new int[3];
System.out.println(arr); // [I@1540e19d
arr[1] = 8;
System.out.println(arr[0]);
}
}
以上方法执行,输出的结果是[I@1540e19d
,这个是什么呢?是数组在内存中的地址。new出来的内容,都是在堆内存中存储的,而方法中的变量arr保存的是数组的地址。 输出arr[0],就会输出arr保存的内存地址中数组中0索引上的元素
3.3.2 两个数组内存图
public static void main(String[] args) {
int[] arr1=new int[2];
int[] arr2=new int[3];
System.out.println(arr1);
System.out.println(arr1[0]);
System.out.println(arr1[1]);
System.out.println(arr2);
System.out.println(arr2[0]);
System.out.println(arr2[1]);
System.out.println(arr2[2]);
arr1[0]=100;
arr2[0]=200;
System.out.println("----------------");
System.out.println(arr1);
System.out.println(arr1[0]);
System.out.println(arr1[1]);
System.out.println(arr2);
System.out.println(arr2[0]);
System.out.println(arr2[1]);
System.out.println(arr2[2]);
}
3.3.3 两个变量指向一个数组
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo06 {
public static void main(String[] args) {
int[] arr1 = {100, 200, 300};
System.out.println(arr1);
System.out.println(arr1[0]);
System.out.println(arr1[1]);
System.out.println(arr1[2]);
int[] arr2 = arr1;
arr2[0] = 111;
arr2[1] = 222;
arr2[2] = 333;
System.out.println("---------");
System.out.println(arr1);
System.out.println(arr1[0]);
System.out.println(arr1[1]);
System.out.println(arr1[2]);
System.out.println(arr2);
System.out.println(arr2[0]);
System.out.println(arr2[1]);
System.out.println(arr2[2]);
}
}
六、数组作为方法参数和返回值
6.1 数组作为方法参数
以前的方法中我们学习了方法的参数和返回值,但是使用的都是基本数据类型。那么作为引用类型的数组能否作为方法的参数进行传递呢,当然是可以的。
- 数组作为方法参数传递,传递的参数是数组内存的地址。
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo09 {
public static void main(String[] args) {
int[] arr = new int[]{1, 2, 3, 4, 5};
System.out.println("main方法中的arr: " + arr); //[I@1b6d3586
print(arr);
}
public static void print(int[] arr) {
System.out.println("print方法中的arr: " + arr); //[I@1b6d3586
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + ",");
}
}
}
6.2 数组作为方法返回值
- 数组作为方法的返回值,返回的是数组的内存地址
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo10 {
public static void main(String[] args) {
//调用getArr方法接收方法返回值,接收到的是数组的内存地址值
int[] arr = getArr();
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
public static int[] getArr() {
int[] arr = {12, 54, 32};
// 返回数组的的内存地址值
return arr;
}
}