程序控制结构
在程序中,程序运行的流程控制决定程序是如何执行的,是我们必须掌握的,主要有三大流程控制语句:顺序控制、分支控制、循环控制
顺序控制
略
分支控制
让程序有选择的的执行,分支控制有三种。
单分支if
基本语法是(当 if语句块 只有一行语句时,可以省略花括号{}):
if (条件) {
// 条件满足时执行
}
根据if的计算结果(true还是false),JVM决定是否执行if语句块(即花括号{}包含的所有语句)。
if 的快速入门
public class Main {
public static void main(String[] args) {
int n = 70;
if (n >= 60) {
System.out.println("及格了");
System.out.println("恭喜你");
}
System.out.println("END");
}
}
当条件 n>=60 计算结果为true时,if语句块被执行,将打印"及格了"
,否则,if语句块将被跳过。修改n的值可以看到执行效果。
双分支if-else
基本语法:
if (条件表达式) {
执行代码块1;// 条件满足时执行
}else{
执行代码块1;// 条件不满足时执行
}
快速入门
public class Main {
public static void main(String[] args) {
int n = 70;
if (n >= 60) {
System.out.println("及格了");
} else {
System.out.println("挂科了");
}
System.out.println("END");
}
}
多分支if-else if -....-else
基本语法
if (条件表达式) {
执行代码块1;
}else if{
执行代码块1;
}
......
else{
执行代码块n;
}
多分支的流程图(重要!)
案例
public class Main {
public static void main(String[] args) {
int n = 70;
if (n >= 90) {
System.out.println("优秀");
} else if (n >= 60) {
System.out.println("及格了");
} else {
System.out.println("挂科了");
}
System.out.println("END");
}
}
以上代码相当于:
if (n >= 90) {
// n >= 90为true:
System.out.println("优秀");
} else {
// n >= 90为false:
if (n >= 60) {
// n >= 60为true:
System.out.println("及格了");
} else {
// n >= 60为false:
System.out.println("挂科了");
}
}
使用多分支时,要特别注意判断顺序,要按照判断范围从大到小依次判断,还要特别注意边界条件。
浮点数在计算机中常常无法精确表示,并且计算可能出现误差,因此,判断浮点数相等用==
判断不靠谱,正确的方法是利用差值小于某个临界值来判断:
public class Main {
public static void main(String[] args) {
double x = 1 - 9.0 / 10;
if (Math.abs(x - 0.1) < 0.00001) {
System.out.println("x is 0.1");
} else {
System.out.println("x is NOT 0.1");
}
}
}
判断引用类型相等
在Java中,判断值类型的变量是否相等,可以使用==
运算符。但是,判断引用类型的变量是否相等,==
表示“引用是否相等”,或者说,是否指向同一个对象。例如,下面的两个String类型,它们的内容是相同的,但是,分别指向不同的对象,用==
判断,结果为false
:
public class Main {
public static void main(String[] args) {
String s1 = "hello";
String s2 = "HELLO".toLowerCase();
System.out.println(s1);
System.out.println(s2);
if (s1 == s2) {
System.out.println("s1 == s2");
} else {
System.out.println("s1 != s2");
}
}
}
要判断引用类型的变量内容是否相等,必须使用equals()
方法:
public class Main {
public static void main(String[] args) {
String s1 = "hello";
String s2 = "HELLO".toLowerCase();
System.out.println(s1);
System.out.println(s2);
if (s1.equals(s2)) {
System.out.println("s1 equals s2");
} else {
System.out.println("s1 not equals s2");
}
}
}
注意:执行语句s1.equals(s2)
时,如果变量s1
为null
,会报NullPointerException,
要避免NullPointerException
错误,可以利用短路运算符&&。
public class Main {
public static void main(String[] args) {
String s1 = null;
if (s1 != null && s1.equals("hello")) {
System.out.println("hello");
}
}
}
还可以把一定不是null
的对象"hello"
放到前面:例如:if ("hello".equals(s)) { ... }
。
多分支练习
请用if ... else编写一个程序,用于计算体质指数BMI,并打印结果。
BMI = 体重(kg)除以身高(m)的平方
BMI结果:
过轻:低于18.5
正常:18.5-25
过重:25-28
肥胖:28-32
非常肥胖:高于32
import java.util.Scanner;
public class If03 {
//编写一个main方法
public static void main(String[] args) {
//应该定义一个Scanner 对象
Scanner myScanner = new Scanner(System.in);
System.out.println("请输入体重(kg):");
//把结果保存到一个变量double weight
double weight = myScanner.nextDouble();
//把结果保存到一个变量double height
System.out.println("请输入身高(m):");
double height = myScanner.nextDouble();
double BMI= weight/Math.pow(height,2);
if(BMI >= 5 && BMI <= 60){
System.out.print("你的BMI为:" + BMI + ",");
if(BMI < 18.5){
System.out.println("过轻");
}else if(BMI >= 18.5 && BMI < 25){
System.out.println("正常");
}else if(BMI >= 25 && BMI < 28){
System.out.println("过重");
}else if(BMI >= 28 && BMI <= 32){
System.out.println("肥胖");
}else if(BMI > 32){
System.out.println("非常肥胖");
}
}else{
System.out.println("您输入的数据不正常,请重新输入~");
}
}
}
switch分支结构
除了if语句外,还有一种条件判断,是根据某个表达式的结果,分别去执行不同的分支。
例如,在游戏中,让用户选择选项:
- 单人模式
- 多人模式
- 退出游戏
这时,switch语句就派上用场了。
switch语句根据 switch(表达式) 计算的结果,跳转到匹配的case结果,然后继续执行后续语句,直到遇到break结束执行。
public class Main {
public static void main(String[] args) {
int option = 99;
switch (option) {
case 1:
System.out.println("Selected 1");
break;
case 2:
System.out.println("Selected 2");
break;
case 3:
System.out.println("Selected 3");
break;
default:
System.out.println("Not selected");
break;
}
}
}
上述代码中,如果option的值没有匹配到任何case,例如 option = 99,那么,switch语句不会执行任何语句。这时,可以给switch语句加一个default,当没有匹配到任何case时,执行default
使用switch时,注意case语句并没有花括号{}
,而且,case语句具有“穿透性”,漏写break将导致意想不到的结果:
public class Main {
public static void main(String[] args) {
int option = 2;
switch (option) {
case 1:
System.out.println("Selected 1");
case 2:
System.out.println("Selected 2");
case 3:
System.out.println("Selected 3");
default:
System.out.println("Not selected");
}
}
}
当option = 2 时,将依次输出"Selected 2"
、"Selected 3"
、"Not selected"
,原因是从匹配到case 2开始,后续语句将全部执行,直到遇到break语句。因此,任何时候都不要忘记写break。
如果有几个case语句执行的是同一组语句块,可以这么写:
public class Main {
public static void main(String[] args) {
int option = 2;
switch (option) {
case 1:
System.out.println("Selected 1");
break;
case 2:
case 3:
System.out.println("Selected 2, 3");
break;
default:
System.out.println("Not selected");
break;
}
}
}
使用switch语句时,只要保证有break,case的顺序不影响程序逻辑,但是仍然建议按照自然顺序排列,便于阅读。
switch语句还可以匹配字符串。字符串匹配时,是比较“内容相等”。例如:
public class Main {
public static void main(String[] args) {
String fruit = "apple";
switch (fruit) {
case "apple":
System.out.println("Selected apple");
break;
case "pear":
System.out.println("Selected pear");
break;
case "mango":
System.out.println("Selected mango");
break;
default:
System.out.println("No fruit selected");
break;
}
}
}
switch使用细节
- 表达式数据类型,应和case 后的常量类型一致, 或者是可以自动转成可以相互比较的类型,比如输入的是字符,而常量是 int
- switch(表达式)中表达式的返回值必须是:(byte,short,int,char,enum[枚举],String)
- case子句中的值必须是常量(1,'a')或者是常量表达式,而不能是变量
- default子句是可选的,当没有匹配的case时,执行default,如果没有default 子句,有没有匹配任何常量,则没有输出
- break语句用来在执行完一个case分支后使程序跳出switch语句块;如果没有写break,程序会顺序执行到switch结尾,除非执行到break
练习:使用switch实现一个简单的石头、剪子、布游戏。
import java.util.Scanner;
public class SwitchDetail {
//编写一个main方法
public static void main(String[] args) {
//使用switch实现一个简单的石头、剪子、布游戏。
Scanner scanner = new Scanner(System.in);
System.out.println("你要出什么?:");
System.out.println(" 1: 石头");
System.out.println(" 2: 剪刀");
System.out.println(" 3: 布");
// 用户输入:
int choice = scanner.nextInt();
// 计算机随机数 1, 2, 3:
int random = 1 + (int)(Math.random() * 3);
System.out.println("计算机出的是"+random);
switch (random) {
// TODO:
case 1 :
switch (choice){
case 1 :
System.out.println("平局");
break;
case 2 :
System.out.println("你输了");
break;
case 3 :
System.out.println("你赢了");
break;
}
break;
case 2 :
switch (choice){
case 1 :
System.out.println("你赢了");
break;
case 2 :
System.out.println("平局");
break;
case 3 :
System.out.println("你输了");
break;
}
break;
case 3 :
switch (choice){
case 1 :
System.out.println("你输了");
break;
case 2 :
System.out.println("你赢了");
break;
case 3 :
System.out.println("平局");
break;
}
break;
}
}
}
switch表达式
使用switch时,如果遗漏了break,就会造成严重的逻辑错误,而且不易在源代码中发现错误。从Java 12开始,switch语句升级为更简洁的表达式语法,使用类似模式匹配(Pattern Matching)的方法,保证只有一种路径会被执行,并且不需要break语句:
public class Main {
public static void main(String[] args) {
String fruit = "apple";
switch (fruit) {
case "apple" -> System.out.println("Selected apple");
case "pear" -> System.out.println("Selected pear");
case "mango" -> {
System.out.println("Selected mango");
System.out.println("Good choice!");
}
default -> System.out.println("No fruit selected");
}
}
}
新语法使用 ->,如果有多条语句,需要用 {} 括起来。不要写break语句,因为新语法只会执行匹配的语句,没有穿透效应。
很多时候,我们还可能用switch语句给某个变量赋值。例如:
int opt;
switch (fruit) {
case "apple":
opt = 1;
break;
case "pear":
case "mango":
opt = 2;
break;
default:
opt = 0;
break;
}
使用新的switch语法,不但不需要break,还可以直接返回值。把上面的代码改写如下:
public class Main {
public static void main(String[] args) {
String fruit = "apple";
int opt = switch (fruit) {
case "apple" -> 1;
case "pear", "mango" -> 2;
default -> 0;
}; // 注意赋值语句要以;结束
System.out.println("opt = " + opt);
}
}
yield
大多数时候,在switch表达式内部,我们会返回简单的值。
但是,如果需要复杂的语句,我们也可以写很多语句,放到{...}
里,然后,用yield返回一个值作为switch语句的返回值:
public class Main {
public static void main(String[] args) {
String fruit = "orange";
int opt = switch (fruit) {
case "apple" -> 1;
case "pear", "mango" -> 2;
default -> {
int code = fruit.hashCode();
yield code; // switch语句返回值
}
};
System.out.println("opt = " + opt);
}
}
小结
- switch语句可以做多重选择,然后执行匹配的case语句后续代码;
- switch的计算结果必须是整型、字符串或枚举类型;
- 注意千万不要漏写break,建议打开fall-through警告;
- 总是写上default,建议打开missing default警告;
- 从Java 14开始,switch语句正式升级为表达式,不再需要break,并且允许使用yield返回值。
循环控制
while循环
循环语句就是让计算机根据条件做循环计算,在条件满足时继续循环,条件不满足时退出循环。
例如,计算从1到100的和:
1 + 2 + 3 + 4 + … + 100 = ?
除了用数列公式外,完全可以让计算机做100次循环累加。因为计算机的特点是计算速度非常快,我们让计算机循环一亿次也用不到1秒,所以很多计算的任务,人去算是算不了的,但是计算机算,使用循环这种简单粗暴的方法就可以快速得到结果。
我们先看Java提供的while条件循环。它的基本用法是:
while (条件表达式) {
循环语句
}
// 继续执行后续代码
while循环在每次循环开始前,首先判断条件是否成立。如果计算结果为true,就把循环体内的语句执行一遍,如果计算结果为false,那就直接跳到while循环的末尾,继续往下执行。
我们用while循环来累加1到100,可以这么写:
public class Main {
public static void main(String[] args) {
int sum = 0; // 累加的和,初始化为0
int n = 1;
while (n <= 100) { // 循环条件是n <= 100
sum = sum + n; // 把n累加到sum中
n ++; // n自身加1
}
System.out.println(sum); // 5050
}
}
注意到while循环是先判断循环条件,再循环,因此,有可能一次循环都不做。对于循环条件判断,以及自增变量的处理,要特别注意边界条件。
思考一下下面的代码为何没有获得正确结果:
public class Main {
public static void main(String[] args) {
int sum = 0;
int n = 0;
while (n <= 100) {
n ++;
sum = sum + n;
}
System.out.println(sum);
}
}
如果循环条件永远满足,那这个循环就变成了死循环。死循环将导致100%的CPU占用,用户会感觉电脑运行缓慢,所以要避免编写死循环代码。
如果循环条件的逻辑写得有问题,也会造成意料之外的结果:
public class Main {
public static void main(String[] args) {
int sum = 0;
int n = 1;
while (n > 0) {
sum = sum + n;
n ++;
}
System.out.println(n); // -2147483648
System.out.println(sum);
}
}
表面上看,上面的while循环是一个死循环,但是,Java的int类型有最大值,达到最大值后,再加1会变成负数,结果,意外退出了while循环。
练习
使用while计算从m到n的和:
//while循环的案例
//
public class While {
//编写一个main方法
public static void main(String[] args) {
int sum = 0;
int m = 20;
int n = 100;
// 使用while计算M+...+N:
while (m <= n) {
sum += m;
m++;
}
System.out.println(sum);//4860
}
}
小结
- while循环先判断循环条件是否满足,再执行循环语句;
- while循环可能一次都不执行;
- 编写循环时要注意循环条件,并避免死循环。
do...while循环
在Java中,while循环是先判断循环条件,再执行循环。而另一种do while循环则是先执行循环,再判断条件,条件满足时继续循环,条件不满足时退出。它的用法是:
do {
执行循环语句
} while (条件表达式);
可见,do while循环会至少循环一次。使用do while循环时,同样要注意循环条件的判断。
把对1到100的求和用do while循环改写一下:
public class Main {
public static void main(String[] args) {
int sum = 0;
int n = 1;
do {
sum = sum + n;
n ++;
} while (n <= 100);
System.out.println(sum);
}
}
练习
使用do while计算从m到n的和:
public class DoWhile01 {
//编写一个main方法
public static void main(String[] args) {
int sum = 0;
int m = 20;
int n = 100;
// 使用do while计算M+...+N:
do {
sum += m;
m++;
} while (m <= n);
System.out.println(sum);
}
}
小结
do while循环先执行循环,再判断条件;
do while循环会至少执行一次。
for循环(重要!!!)
除了while和do while循环,Java使用最广泛的是for循环。
循环的功能非常强大,它使用计数器实现循环。for循环会先初始化计数器,然后,在每次循环前检测循环条件,在每次循环后更新计数器。计数器变量通常命名为i
。
把1到100求和用for循环改写一下:
public class For01 {
public static void main(String[] args) {
int sum = 0;
for (int i=1; i<=100; i++) {
sum = sum + i;
}
System.out.println(sum);
}
}
在for循环执行前,会先执行初始化语句int i=1
,它定义了计数器变量i
并赋初始值为1
,然后,循环前先检查循环条件i<=100
,循环后自动执行i++
,因此,和while
循环相比,for循环把更新计数器的代码统一放到了一起。在for循环的循环体内部,不需要去更新变量i
。
因此,for循环的用法是:
for (初始条件; 循环检测条件; 循环后更新计数器) {
// 执行语句
}
如果我们要对一个整型数组的所有元素求和,可以用for
循环实现:
public class For02 {
public static void main(String[] args) {
int[] ns = { 1, 4, 9, 16, 25 };
int sum = 0;
for (int i=0; i<ns.length; i++) {
System.out.println("i = " + i + ", ns[i] = " + ns[i]);
sum = sum + ns[i];
}
System.out.println("sum = " + sum);
}
}
上面代码的循环条件是i<ns.length
。因为ns
数组的长度是5
,因此,当循环5
次后,i
的值被更新为5
,就不满足循环条件,因此for循环结束。
使用for循环时,千万不要在循环体内修改计数器!在循环体中修改计数器常常导致莫名其妙的逻辑错误。对于下面的代码:
public class Main {
public static void main(String[] args) {
int[] ns = { 1, 4, 9, 16, 25 };
for (int i=0; i<ns.length; i++) {
System.out.println(ns[i]);
i = i + 1;
}
}
}
虽然不会报错,但是,数组元素只打印了一半,原因是循环内部的i = i + 1
导致了计数器变量每次循环实际上加了2
(因为for循环还会自动执行i++
)。因此,在for循环中,不要修改计数器的值。计数器的初始化、判断条件、每次循环后的更新条件统一放到for()语句中可以一目了然。
如果希望只访问索引为奇数的数组元素,应该把for循环改写为:
int[] ns = { 1, 4, 9, 16, 25 };
for (int i=0; i<ns.length; i=i+2) {
System.out.println(ns[i]);
}
通过更新计数器的语句i=i+2
就达到了这个效果,从而避免了在循环体内去修改变量i
。
使用for
循环时,计数器变量i
要尽量定义在for循环中:
int[] ns = { 1, 4, 9, 16, 25 };
for (int i=0; i<ns.length; i++) {
System.out.println(ns[i]);
}
// 无法访问i
int n = i; // compile error!
如果变量i
定义在for循环外:
int[] ns = { 1, 4, 9, 16, 25 };
int i;
for (i=0; i<ns.length; i++) {
System.out.println(ns[i]);
}
// 仍然可以使用i
int n = i;
那么,退出for循环后,变量i
仍然可以被访问,这就破坏了变量应该把访问范围缩到最小的原则。
灵活使用for循环
for循环还可以缺少初始化语句、循环条件和每次循环更新语句,例如:
// 不设置结束条件:
for (int i=0; ; i++) {
...
}
// 不设置结束条件和更新语句:
for (int i=0; ;) {
...
}
// 什么都不设置:
for (;;) {
...
}
通常不推荐这样写,但是,某些情况下,是可以省略for循环的某些语句的。
for each循环
for循环经常用来遍历数组,因为通过计数器可以根据索引来访问数组的每个元素:
int[] ns = { 1, 4, 9, 16, 25 };
for (int i=0; i<ns.length; i++) {
System.out.println(ns[i]);
}
但是,很多时候,我们实际上真正想要访问的是数组每个元素的值。Java还提供了另一种for each循环,它可以更简单地遍历数组:
public class Main {
public static void main(String[] args) {
int[] ns = { 1, 4, 9, 16, 25 };
for (int n : ns) {
System.out.println(n);
}
}
}
和for循环相比,for each循环的变量n不再是计数器,而是直接对应到数组的每个元素。for each循环的写法也更简洁。但是,for each循环无法指定遍历顺序,也无法获取数组的索引。
除了数组外,for each循环能够遍历所有“可迭代”的数据类型,包括后面会介绍的List、Map等。
练习
给定一个数组,请用for循环倒序输出每一个元素:
public class ForExercise02 {
//编写一个main方法
public static void main(String[] args) {
int[] ns = {3, 9, 5, 14, 8, 6};
for (int i = ns.length-1; i >= 0; i--) {
System.out.print(ns[i] + " ");
}
}
}
利用for each循环对数组每个元素求和:
public class ForExercise02 {
//编写一个main方法
public static void main(String[] args) {
int[] ns = { 1, 4, 9, 16, 25 };
int sum = 0;
for (int i : ns) {
// TODO
sum += i;
}
System.out.println(sum); // 55
}
}
圆周率π可以使用公式计算:
请利用for each循环计算π:
public class ForExercise03 {
//编写一个main方法
public static void main(String[] args) {
double pi = 0;
double n = 0;
for (double i = 1; i <= 100000000; i += 4) {
// TODO
n += 1/i - 1/(i + 2);
}
pi = n * 4;
System.out.println("pi= " + pi);
}
}
break和continue语句
无论是while循环还是for循环,有两个特别的语句可以使用,就是break语句和continue语句。
break
在循环过程中,可以使用break语句跳出当前循环。
public class BreakExercise {
//编写一个main方法
public static void main(String[] args) {
//1-100以内的数求和,求出 当和 第一次大于20的当前数 【for + break】
//思路分析
//1. 循环 1-100, 求和 sum
//2. 当 sum > 20 时,记录下当前数,然后break
//3. 在for循环外部,定义变量 n , 把当前i 赋给 n
int sum = 0; //累积和
//注意i 的作用范围在 for{}
int n = 0;
for(int i = 1; i <= 100; i++) {
sum += i;//累积
if(sum > 20) {
System.out.println("和>20时候 当前数i=" + i);
n = i;
break;
}
}
System.out.println("当前数=" + n);
}
}
break语句通常都是配合if
语句使用。要特别注意,break语句总是跳出自己所在的那一层循环。
public class Main {
public static void main(String[] args) {
for (int i=1; i<=10; i++) {
System.out.println("i = " + i);
for (int j=1; j<=10; j++) {
System.out.println("j = " + j);
if (j >= i) {
break;
}
}
// break跳到这里
System.out.println("breaked");
}
}
}
上面的代码是两个for
循环嵌套。因为break语句位于内层的for
循环,因此,它会跳出内层for
循环,但不会跳出外层for
循环。
import java.util.Scanner;
public class BreakExercise02 {
//编写一个main方法
public static void main(String[] args) {
//实现登录验证,有3次机会,如果用户名为"丁真" ,密码"666"提示登录成功,
//否则提示还有几次机会,请使用for+break完成
//
// 思路分析
// 1. 创建Scanner对象接收用户输入
// 2. 定义 String name ; String passwd; 保存用户名和密码
// 3. 最多循环3次[登录3次],如果 满足条件就提前退出
// 4. 定义一般变量 int chance 记录还有几次登录机会
//
// 代码实现
Scanner myScanner = new Scanner(System.in);
String name = "";
String passwd = "";
int chance = 3; //登录一次 ,就减少一次
for( int i = 1; i <= 3; i++) {//3次登录机会
System.out.println("请输入名字");
name = myScanner.next();
System.out.println("请输入密码");
passwd = myScanner.next();
//比较输入的名字和密码是否正确
//补充说明字符串 的内容 比较 使用的 方法 equals
if("丁真".equals(name) && "666".equals(passwd)) {
System.out.println("恭喜你,登录成功~");
break;
}
//登录的机会就减少一次
chance--;
System.out.println("你还有" + chance + "次登录机会");
}
}
}
continue
break会跳出当前循环,也就是整个循环都不会执行了。而continue则是提前结束本次循环,直接继续执行下次循环。
public class Main {
public static void main(String[] args) {
int sum = 0;
for (int i=1; i<=10; i++) {
System.out.println("begin i = " + i);
if (i % 2 == 0) {
continue; // continue语句会结束本次循环
}
sum = sum + i;
System.out.println("end i = " + i);
}
System.out.println(sum); // 25
}
}
练习
public class ContinueDetail {
//编写一个main方法
public static void main(String[] args) {
label1:
for(int j = 0; j < 2; j++){
label2:
for(int i = 0; i < 10; i++){
if(i == 2){
//看看分别输出什么值,并分析
//continue ; //等价于 continue label2
//continue label2;//等价 continue;
continue label1; //输出 2次[0,1]
}
System.out.println("i = " + i);//输出2次[0,1,3,4,5,6,7,8,9]
}
}
}
}
注意观察continue语句的效果。当i
为奇数时,完整地执行了整个循环,因此,会打印begin i=1
和end i=1
。在i为偶数时,continue语句会提前结束本次循环,因此,会打印begin i=2
但不会打印end i = 2
。
在多层嵌套的循环中,continue语句同样是结束本次自己所在的循环。
小结
- break语句可以跳出当前循环;
- break语句通常配合
if
,在满足条件时提前结束整个循环; - break语句总是跳出最近的一层循环;
- continue语句可以提前结束本次循环;
- continue语句通常配合
if
,在满足条件时提前结束本次循环。
数组
- 数组是多个相同类型数据的组合,实现对这些数据的统一管理
- 数组中的元素可以是任何数据类型,包括基本类型和引用类型,但是不能混用。
- 数组创建后,如果没有赋值,有默认值
- int 0,short 0, byte 0, long 0, float 0.0,double 0.0,char \u0000,boolean false,String null
- 使用数组的步骤1. 声明数组并开辟空间2 给数组各个元素赋值3 使用数组
- 数组的下标是从0 开始的。
- 数组下标必须在指定范围内使用,否则报:下标越界异常,比如int [] arr=new int[5]; 则有效下标为0-4
- 数组属引用类型,数组型数据是对象(object)
public class Array {
//编写一个main方法
public static void main(String[] args) {
//1. 数组是多个相同类型数据的组合,实现对这些数据的统一管理
//int[] arr1 = {1, 2, 3, 60,"hello"};//错误,不能 String ->int
double[] arr2 = {1.1, 2.2, 3.3, 60.6, 100};//int ->double
//2. 数组中的元素可以是任何数据类型,包括基本类型和引用类型,但是不能混用
String[] arr3 = {"北京","jack","milan"};
//3. 数组创建后,如果没有赋值,有默认值
//int 0,short 0, byte 0, long 0,
//float 0.0,double 0.0,char \u0000,
//boolean false,String null
//
short[] arr4 = new short[3];
System.out.println("=====数组arr4=====");
for(int i = 0; i < arr4.length; i++) {
System.out.println(arr4[i]);
}
//6. 数组下标必须在指定范围内使用,否则报:下标越界异常,比如
//int [] arr=new int[5]; 则有效下标为 0-4
//即数组的下标/索引 最小 0 最大 数组长度-1(4)
int [] arr = new int[5];
//System.out.println(arr[5]);//数组越界
}
}
练习1:创建一个char类型的26个元素的数组,分别 放置'A'-'Z'。使用for循环访问所有元素并打印出来。(提示:char类型数据运算 'A'+1 -> 'B' )
public class Exercise01 {
//编写一个main方法
public static void main(String[] args) {
/*
思路分析
1. 定义一个 数组 char[] chars = new char[26]
2. 因为 'A' + 1 = 'B' 类推,所以使用for来赋值
3. 使用for循环访问所有元素
*/
char[] chars = new char[26];
for( int i = 0; i < chars.length; i++) {//循环26次
//chars 是数组 char[]
//chars[i] 是数组元素 char
chars[i] = (char)('A' + i); //'A' + i 是int , 需要强制转换
}
//循环输出
System.out.println("===chars数组===");
for( int i = 0; i < chars.length; i++) {//循环26次
System.out.print(chars[i] + " ");
}
}
}
练习2:请求出一个数组int[]的最大值 {4,-1,9, 10,23},并得到对应的下标。
public class Exercise02 {
public static void main(String[] args) {
//思路分析
//1. 定义一个int数组 int[] arr = {4,-1,9, 10,23};
//2. 假定 max = arr[0] 是最大值 , maxIndex=0;
//3. 从下标 1 开始遍历arr, 如果max < 当前元素,说明max 不是真正的
// 最大值, 我们就 max=当前元素; maxIndex=当前元素下标
//4. 遍历数组arr后 , max就是真正的最大值,maxIndex就是最大值对应的下标
int[] arr = {4,-1,9,10,23};
int max = arr[0];//假定第一个元素就是最大值
int maxIndex = 0; //
for(int i = 1; i < arr.length; i++) {//从下标 1 开始遍历arr
if(max < arr[i]) {//如果max < 当前元素
max = arr[i]; //把max 设置成 当前元素
maxIndex = i;
}
}
//遍历数组arr后 , max就是真正的最大值,maxIndex就是最大值下标
System.out.println("max=" + max + " maxIndex=" + maxIndex);
}
}
练习3:请求出一个数组int[]的最大值 {4,-1,9, 10,23},并得到对应的下标
public class Exercise03 {
//编写一个main方法
public static void main(String[] args) {
int[] arr = {4,-1,9,10,23};
int sum = 0;
for(int i = 0; i < arr.length; i++) {//从下标 1 开始遍历arr
sum += arr[i];
}
System.out.println("数组和= " + sum + " 平均值= " + sum / arr.length);
}
}
数组赋值机制
- 基本数据类型赋值,这个值就是具体的数据,而且相互不影响。
- 数组在默认情况下是引用传递,赋的值是地址。
案例
public class ArrayAssign {
//编写一个main方法
public static void main(String[] args) {
//基本数据类型赋值, 赋值方式为值拷贝
//n2的变化,不会影响到n1的值
int n1 = 10;
int n2 = n1;
n2 = 80;
System.out.println("n1=" + n1);//10
System.out.println("n2=" + n2);//80
//数组在默认情况下是引用传递,赋的值是地址,赋值方式为引用赋值
//是一个地址 , arr2变化会影响到 arr1
int[] arr1 = {1, 2, 3};
int[] arr2 = arr1;//把 arr1赋给 arr2
arr2[0] = 10;
//看看arr1的值
System.out.println("====arr1的元素====");
for(int i = 0; i < arr1.length; i++) {
System.out.println(arr1[i]);//10, 2, 3
}
System.out.println("====arr2的元素====");
for(int i = 0; i < arr2.length; i++) {
System.out.println(arr2[i]);//10, 2, 3
}
}
}
数组拷贝
编写代码实现数组拷贝(内容复制)
案例
将int[] arr1 = {10,20,30}; 拷贝到arr2 数组, 要求数据空间是独立的。
public class ArrayCopy {
//编写一个main方法
public static void main(String[] args) {
int[] arr1 = {10,20,30};
//创建一个新的数组arr2,开辟新的数据空间
//大小 arr1.length;
int[] arr2 = new int[arr1.length];
//遍历 arr1 ,把每个元素拷贝到arr2对应的元素位置
for(int i = 0; i < arr1.length; i++) {
arr2[i] = arr1[i];
}
//老师修改 arr2, 不会对arr1有影响.
arr2[0] = 100;
//输出arr1
System.out.println("====arr1的元素====");
for(int i = 0; i < arr1.length; i++) {
System.out.println(arr1[i]);//10,20,30
}
//
System.out.println("====arr2的元素====");
for(int i = 0; i < arr2.length; i++) {
System.out.println(arr2[i]);//
}
}
}
数组反转
要求:把数组的元素内容反转。arr {11,22,33,44,55,66} {66, 55,44,33,22,11}
方式1:通过找规律反转
public class ArrayReverse01 {
//编写一个main方法
public static void main(String[] args) {
//定义数组
int[] arr = {11, 22, 33, 44, 55, 66};
//老韩思路
//规律
//1. 把 arr[0] 和 arr[5] 进行交换 {66,22,33,44,55,11}
//2. 把 arr[1] 和 arr[4] 进行交换 {66,55,33,44,22,11}
//3. 把 arr[2] 和 arr[3] 进行交换 {66,55,44,33,22,11}
//4. 一共要交换 3 次 = arr.length / 2
//5. 每次交换时,对应的下标 是 arr[i] 和 arr[arr.length - 1 -i]
//代码
//优化
int temp = 0;
int len = arr.length; //计算数组的长度
for( int i = 0; i < len / 2; i++) {
temp = arr[len - 1 - i];//保存
arr[len - 1 - i] = arr[i];
arr[i] = temp;
}
System.out.println("===翻转后数组===");
for(int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + "\t");//66,55,44,33,22,11
}
}
}
方法2:使用逆序赋值方式
public class ArrayReverse02 {
//编写一个main方法
public static void main(String[] args) {
//定义数组
int[] arr = {11, 22, 33, 44, 55, 66};
//使用逆序赋值方式
//老韩思路
//1. 先创建一个新的数组 arr2 ,大小 arr.length
//2. 逆序遍历 arr ,将 每个元素拷贝到 arr2的元素中(顺序拷贝)
//3. 建议增加一个循环变量 j -> 0 -> 5
int[] arr2 = new int[arr.length];
//逆序遍历 arr
for(int i = arr.length - 1, j = 0; i >= 0; i--, j++) {
arr2[j] = arr[i];
}
//4. 当for循环结束,arr2就是一个逆序的数组 {66, 55, 44,33, 22, 11}
//5. 让 arr 指向 arr2数据空间, 此时 arr原来的数据空间就没有变量引用
// 会被当做垃圾,销毁
arr = arr2;
System.out.println("====arr的元素情况=====");
//6. 输出 arr 看看
for(int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + "\t");
}
}
}
数组添加/扩容
要求:实现动态的给数组添加元素效果,实现对数组扩容。ArrayAdd.java
- 原始数组使用静态分配int[] arr = {1,2,3}
- 增加的元素4,直接放在数组的最后arr = {1,2,3,4}
- 用户可以通过如下方法来决定是否继续添加,添加成功,是否继续?y/n
import java.util.Scanner;
public class ArrayAdd02 {
//编写一个main方法
public static void main(String[] args) {
/*
要求:实现动态的给数组添加元素效果,实现对数组扩容。ArrayAdd.java
1.原始数组使用静态分配 int[] arr = {1,2,3}
2.增加的元素4,直接放在数组的最后 arr = {1,2,3,4}
3.用户可以通过如下方法来决定是否继续添加,添加成功,是否继续?y/n
思路分析
1. 定义初始数组 int[] arr = {1,2,3}//下标0-2
2. 定义一个新的数组 int[] arrNew = new int[arr.length+1];
3. 遍历 arr 数组,依次将arr的元素拷贝到 arrNew数组
4. 将 4 赋给 arrNew[arrNew.length - 1] = 4;把4赋给arrNew最后一个元素
5. 让 arr 指向 arrNew ; arr = arrNew; 那么 原来arr数组就被销毁
6. 创建一个 Scanner可以接受用户输入
7. 因为用户什么时候退出,不确定,老师使用 do-while + break来控制
*/
Scanner myScanner = new Scanner(System.in);
//初始化数组
int[] arr = {1,2,3};
do {
int[] arrNew = new int[arr.length + 1];
//遍历 arr 数组,依次将arr的元素拷贝到 arrNew数组
for(int i = 0; i < arr.length; i++) {
arrNew[i] = arr[i];
}
System.out.println("请输入你要添加的元素");
int addNum = myScanner.nextInt();
//把addNum赋给arrNew最后一个元素
arrNew[arrNew.length - 1] = addNum;
//让 arr 指向 arrNew,
arr = arrNew;
//输出arr 看看效果
System.out.println("====arr扩容后元素情况====");
for(int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + "\t");
}
//问用户是否继续
System.out.println("是否继续添加 y/n");
char key = myScanner.next().charAt(0);
if( key == 'n') { //如果输入n ,就结束
break;
}
}while(true);
System.out.println("你退出了添加...");
}
}
练习
有一个数组{1, 2, 3, 4, 5}, 可以将该数组进行缩减,提示用户是否继续缩减,每次缩减最后那个元素。当只剩下最后一个元素,提示,不能再缩减。
import java.util.Scanner;
public class ArrayReduce {
//编写一个 main 方法
public static void main(String[] args) {
/*
思路分析:
1. 定义初始数组 int[] arr = {1,2,3};
2. 定义新数组 int[] arr2 = new int[arr.length - 1];
3. 遍历 arr 数组,依次将 arr 的元素拷贝到 arrNew 数组
4. 依次缩减最后一个元素直至只剩下一个元素
5. 让 arr 指向 arrNew ; arr = arrNew; 那么 原来 arr 数组就被销毁
6. 创建一个 Scanner 可以接受用户输入
7. 因为用户什么时候退出,不确定,老师使用 do-while + break 来控制
*/
Scanner myScanner = new Scanner(System.in);
//初始化数值
int[] arr = {1, 2, 3, 4, 5};
do{
int[] arr2 = new int[arr.length - 1];
//将arr的每个元素顺序拷贝到arr2
for( int i = 0; i < arr2.length; i++ ){
arr2[i] = arr[i];
}
arr = arr2;
System.out.println("====arr扩容后的元素情况====");
for(int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + "\t");
}
//问用户决定是否继续添加,添加成功,(是否继续?y/n)
System.out.println("是否继续?y/n");
char key = myScanner.next().charAt(0);
//如果不继续添加,输入 n 就结束添加(跳出循环)
if(key != 'y' || arr.length == 1){
break;
}
}while(true);
System.out.println("你退出了缩减");
}
}
遍历数组
我们在Java程序基础里介绍了数组这种数据类型。有了数组,我们还需要来操作它。而数组最常见的一个操作就是遍历。
通过for循环就可以遍历数组。因为数组的每个元素都可以通过索引来访问,因此,使用标准的for循环可以完成一个数组的遍历:
public class Main {
public static void main(String[] args) {
int[] ns = { 1, 4, 9, 16, 25 };
for (int i=0; i<ns.length; i++) {
int n = ns[i];
System.out.println(n);
}
}
}
为了实现for循环遍历,初始条件为i=0,因为索引总是从0开始,继续循环的条件为i<ns.length,因为当i=ns.length时,i已经超出了索引范围(索引范围是0 ~ ns.length-1),每次循环后,i++。
第二种方式是使用for each循环,直接迭代数组的每个元素:
public class Main {
public static void main(String[] args) {
int[] ns = { 1, 4, 9, 16, 25 };
for (int n : ns) {
System.out.println(n);
}
}
}
注意:在for (int n : ns)循环中,变量n直接拿到ns数组的元素,而不是索引。
显然for each循环更加简洁。但是,for each循环无法拿到数组的索引,因此,到底用哪一种for循环,取决于我们的需要。
打印数组内容
直接打印数组变量,得到的是数组在JVM中的引用地址:
int[] ns = { 1, 1, 2, 3, 5, 8 };
System.out.println(ns); // 类似 [I@7852e922
这并没有什么意义,因为我们希望打印的数组的元素内容。因此,使用for each循环来打印它:
int[] ns = { 1, 1, 2, 3, 5, 8 };
for (int n : ns) {
System.out.print(n + ", ");
}
使用for each循环打印也很麻烦。幸好Java标准库提供了Arrays.toString(),可以快速打印数组内容:
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] ns = { 1, 1, 2, 3, 5, 8 };
System.out.println(Arrays.toString(ns));
}
}
小结
- 遍历数组可以使用for循环,for循环可以访问数组索引,for each循环直接迭代每个数组元素,但无法获取索引;
- 使用Arrays.toString()可以快速获取数组内容
数组排序
对数组进行排序是程序中非常基本的需求。常用的排序算法有冒泡排序、插入排序和快速排序等。
冒泡排序
我们来看一下如何使用冒泡排序算法对一个整型数组从小到大进行排序:
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] ns = { 28, 12, 89, 73, 65, 18, 96, 50, 8, 36 };
// 排序前:
System.out.println(Arrays.toString(ns));
for (int i = 0; i < ns.length - 1; i++) {
for (int j = 0; j < ns.length - i - 1; j++) {
if (ns[j] > ns[j+1]) {
// 交换ns[j]和ns[j+1]:
int tmp = ns[j];
ns[j] = ns[j+1];
ns[j+1] = tmp;
}
}
}
// 排序后:
System.out.println(Arrays.toString(ns));
}
}
冒泡排序的特点是,每一轮循环后,最大的一个数被交换到末尾,因此,下一轮循环就可以“刨除”最后的数,每一轮循环都比上一轮循环的结束位置靠前一位。
另外,注意到交换两个变量的值必须借助一个临时变量。像这么写是错误的:
int x = 1;
int y = 2;
x = y; // x现在是2
y = x; // y现在还是2
正确的写法是:
int x = 1;
int y = 2;
int t = x; // 把x的值保存在临时变量t中, t现在是1
x = y; // x现在是2
y = t; // y现在是t的值1
实际上,Java的标准库已经内置了排序功能,我们只需要调用JDK提供的Arrays.sort()就可以排序:
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] ns = { 28, 12, 89, 73, 65, 18, 96, 50, 8, 36 };
Arrays.sort(ns);
System.out.println(Arrays.toString(ns));
}
}
// 测试
[8, 12, 18, 28, 36, 50, 65, 73, 89, 96]
对于数组排序的解释:
- 必须注意,对数组排序实际上修改了数组本身,例如:排序前的数组是:
int[] ns = { 9, 3, 6, 5 };
练习
请思考如何实现对数组进行降序排序:
方法一:
// 降序排序
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] ns = { 28, 12, 89, 73, 65, 18, 96, 50, 8, 36 };
// 排序前:
System.out.println(Arrays.toString(ns));
// TODO:
for (int i = 0; i < ns.length - 1; i ++ ) {
for (int j = 0; j < ns.length - 1; j ++ ) {
//如果前面的数<后面的数,就交换
if(ns[j] < ns[j + 1]){
int temp = ns[j];
ns[j] = ns[j + 1];
ns[j + 1] = temp;
}
}
}
// 排序后:
System.out.println(Arrays.toString(ns));
if (Arrays.toString(ns).equals("[96, 89, 73, 65, 50, 36, 28, 18, 12, 8]")) {
System.out.println("测试成功");
} else {
System.out.println("测试失败");
}
}
}
方法二:
// 降序排序
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] ns = { 28, 12, 89, 73, 65, 18, 96, 50, 8, 36 };
// 排序前:
System.out.println(Arrays.toString(ns));
// TODO:
Arrays.sort(ns);
int temp = 0;
/**
* 用来控制数组前半部分与后半部分元素的交换过程。
* 循环变量i从0开始,每次循环增加1,直到i小于数组长度的一半(ns.length / 2)时停止。
* 这样可以确保数组的前半部分元素都能找到对应的后半部分元素进行交换,而不会发生重复交换
*/
for (int i = 0; i < ns.length / 2; i++) {
//在循环体内,首先将数组中索引为i的元素值暂存到temp变量中。这是交换两元素值的经典第一步,即保存一个值。
temp = ns[i];
// 将数组中对应索引为(ns.length - 1 - i)的元素(也就是从数组末尾开始计数的第i个元素)的值赋给索引为i的位置。
// 这实现了两个元素的值的第一步交换。
ns[i] = ns[ns.length - 1 - i];
// 将之前暂存在temp变量中的值(即原来索引为i的元素的值)赋给索引为(ns.length - 1 - i)的位置,完成了两个元素值的交换。
ns[ns.length - 1 - i] = temp;
}
// 排序后:
System.out.println(Arrays.toString(ns));
if (Arrays.toString(ns).equals("[96, 89, 73, 65, 50, 36, 28, 18, 12, 8]")) {
System.out.println("测试成功");
} else {
System.out.println("测试失败");
}
}
}
小结
- 常用的排序算法有冒泡排序、插入排序和快速排序等;
- 冒泡排序使用两层for循环实现排序;
- 交换两个变量的值需要借助一个临时变量。
- 可以直接使用Java标准库提供的Arrays.sort()进行排序;
- 对数组排序会直接修改数组本身。修改了指向
顺序查找
练习
有一个数列:白眉鹰王、金毛狮王、紫衫龙王、青翼蝠王猜数游戏:从键盘中任意输入一个名称,判断数列中是否包含此名称【顺序查找】。要求: 如果找到了,就提示找到,并给出下标值。
import java.util.Scanner;
public class SeqSearch {
//编写一个main方法
public static void main(String[] args) {
/*
思路分析
1. 定义一个字符串数组
2. 接收用户输入, 遍历数组,逐一比较,如果有,则提示信息,并退出
*/
//定义一个字符串数组
String[] names = {"白眉鹰王", "金毛狮王", "紫衫龙王", "青翼蝠王"};
Scanner myScanner = new Scanner(System.in);
System.out.println("请输入名字");
String findName = myScanner.next();
//遍历数组,逐一比较,如果有,则提示信息,并退出
//这里老师给大家一个编程思想/技巧, 一个经典的方法
int index = -1;
for(int i = 0; i < names.length; i++) {
//比较 字符串比较 equals, 如果要找到名字就是当前元素
if(findName.equals(names[i])) {
System.out.println("恭喜你找到 " + findName);
System.out.println("下标为= " + i);
//把i 保存到 index
index = i;
break;//退出
}
}
if(index == -1) { //没有找到
System.out.println("sorry ,没有找到 " + findName);
}
}
}
随机生成10个整数(范围1~100)保存到数组,并倒序打印, 以及求平均值、最大值和最大值下标,并查找里面是否有37。
public class Homework05 {
//编写一个 main 方法
public static void main(String[] args) {
int[] arr = new int[10];
for(int i = 0; i < arr.length; i++){
arr[i] = (int)(Math.random() * 100) + 1;
}
for(int i = 0; i < arr.length; i++){
System.out.print(arr[i] + " ");
}
System.out.println();
for(int i = arr.length - 1; i >= 0; i--){
System.out.print(arr[i] + " ");
}
double sum = arr[0];
int max = arr[0];
int maxIndex = 0;
for(int i = 0; i < arr.length; i++){
sum += arr[i];
if(max < arr[i]){
max = arr[i];
maxIndex = i;
}
}
System.out.println("\n平均值=" + sum / arr.length);
System.out.println("\nmax=" + max + "\nmaxIndex=" + maxIndex);
int findNum = 90;
int index = -1;
for(int i = 0; i < arr.length; i++){
if(findNum == arr[i]){
System.out.println("找到" + findNum + "的下标= " + i);
index = i;
break;
}
}
if(index == -1){
System.out.println("没找到" + findNum);
}
}
}
多维数组
二维数组
动态初始化
语法: 类型[][] 数组名=new 类型[大小][大小],比如: int a[][]=new int[2][3]
public class TwoDimensionalArray02 {
//编写一个main方法
public static void main(String[] args) {
//int arr[][] = new int[2][3];
int arr[][]; //声明二维数组
arr = new int[2][3];//再开空间
arr[1][1] = 8;
//遍历arr数组
for(int i = 0; i < arr.length; i++) {
for(int j = 0; j < arr[i].length; j++) {//对每个一维数组遍历
System.out.print(arr[i][j] +" ");
}
System.out.println();//换行
}
}
}
二维数组在内存的存在形式
二维数组就是数组的数组。定义一个二维数组如下:
public class Main {
public static void main(String[] args) {
int[][] ns = {
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 }
};
System.out.println(ns.length); // 3
}
}
二维数组的内存结构
因为 ns 包含 3 个数组,因此, ns.length 为 3 。实际上 ns 在内存中的结构如下:
列数不确定时
练习
看一个需求:动态创建下面二维数组,并输出
i = 0: 1
i = 1: 2 2
i = 2: 3 3 3
一个有三个一维数组, 每个一维数组的元素是不一样的
public class TwoDimensionalArray03 {
//编写一个main方法
public static void main(String[] args) {
//创建 二维数组,一个有3个一维数组,但是每个一维数组还没有开数据空间
int[][] arr = new int[3][];
for(int i = 0; i < arr.length; i++) {//遍历arr每个一维数组
//给每个一维数组开空间 new
//如果没有给一维数组 new ,那么 arr[i]就是null
arr[i] = new int[i + 1];
//遍历一维数组,并给一维数组的每个元素赋值
for(int j = 0; j < arr[i].length; j++) {
arr[i][j] = i + 1;//赋值
}
}
System.out.println("=====arr元素=====");
//遍历arr输出
for(int i = 0; i < arr.length; i++) {
//输出arr的每个一维数组
for(int j = 0; j < arr[i].length; j++) {
System.out.print(arr[i][j] + " ");
}
System.out.println();//换行
}
}
}
静态初始化
定义类型数组名[][] = {{值1,值2..},{值1,值2..},{值1,值2..}}
public class TwoDimensionalArray04 {
//编写一个main方法
public static void main(String[] args) {
int[][] arr = {{1,1,1}, {8,8,9}, {100}};
}
}
练习
int arr[][]={{4,6},{1,4,5,7},{-2}}; 遍历该二维数组,并得到和。
public class TwoDimensionalArray05 {
//编写一个main方法
public static void main(String[] args) {
/*
思路
1. 遍历二维数组,并将各个值累计到 int sum
*/
int arr[][]= {{4,6},{1,4,5,7},{-2}};
int sum = 0;
for(int i = 0; i < arr.length; i++) {
//遍历每个一维数组
for(int j = 0; j < arr[i].length; j++) {
sum += arr[i][j];
}
}
System.out.println("sum=" + sum);
}
}
使用二维数组打印一个10 行杨辉三角
public class YangHui {
//编写一个main方法
public static void main(String[] args) {
/*
使用二维数组打印一个 10 行杨辉三角
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
规律
1.第一行有 1 个元素, 第 n 行有 n 个元素
2. 每一行的第一个元素和最后一个元素都是 1
3. 从第三行开始, 对于非第一个元素和最后一个元素的元素的值. arr[i][j]
arr[i][j] = arr[i-1][j] + arr[i-1][j-1]; //必须找到这个规律
*/
int[][] yangHui = new int[12][];
for(int i = 0; i < yangHui.length; i++) {//遍历yangHui的每个元素
//给每个一维数组(行) 开空间
yangHui[i] = new int[i+1];
//给每个一维数组(行) 赋值
for(int j = 0; j < yangHui[i].length; j++){
//每一行的第一个元素和最后一个元素都是1
if(j == 0 || j == yangHui[i].length - 1) {
yangHui[i][j] = 1;
} else {//中间的元素
yangHui[i][j] = yangHui[i-1][j] + yangHui[i-1][j-1];
}
}
}
//输出杨辉三角
for(int i = 0; i < yangHui.length; i++) {
for(int j = 0; j < yangHui[i].length; j++) {//遍历输出该行
System.out.print(yangHui[i][j] + "\t");
}
System.out.println();//换行.
}
}
}
如果我们定义一个普通数组 arr0,然后把ns[0]赋值给它:
public class Main {
public static void main(String[] args) {
int[][] ns = {
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 }
};
int[] arr0 = ns[0];
System.out.println(arr0); // [I@71a794e5 地址
System.out.println(arr0.length); // 4
}
}
解释:实际上arr0就获取了ns数组的第0个元素。因为ns数组的每个元素也是一个数组,因此,arr0指向的数组就是{ 1, 2, 3, 4 }。在内存中,结构如下:
访问二维数组的某个元素需要使用array[row][col](而且行和列都是从0开始的),直接具体到行和列例如:
System.out.println(ns[1][2]); // 7
二维数组的每个数组元素的长度并不要求相同,例如,可以这么定义ns数组:
int[][] ns = {
{ 1, 2, 3, 4 },
{ 5, 6 },
{ 7, 8, 9 }
};
上面二维数组在内存中的结构:
要打印一个二维数组,可以使用两层嵌套的for循环:
for (int[] arr : ns) {
for (int n : arr) {
System.out.print(n);
System.out.print(', ');
}
System.out.println();
}
或者使用Java标准库的Arrays.deepToString():
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[][] ns = {
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 }
};
System.out.println(Arrays.deepToString(ns));
}
}
// [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
三维数组
三维数组就是二维数组的数组。可以这么定义一个三维数组:
int[][][] ns = {
{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
},
{
{10, 11},
{12, 13}
},
{
{14, 15, 16},
{17, 18}
}
};
三维数组的内存结构
如果我们要访问三维数组的某个元素,例如,ns[2][0][1],只需要顺着定位找到对应的最终元素15即可。
理论上,我们可以定义任意的N维数组。但在实际应用中,除了二维数组在某些时候还能用得上,更高维度的数组很少使用。
练习
使用二维数组可以表示一组学生的各科成绩,请计算所有学生的平均分:
方式一循环:
public class Main {
public static void main(String[] args) {
// 用二维数组表示的学生成绩:
int[][] scores = {
{ 82, 90, 91 },
{ 68, 72, 64 },
{ 95, 91, 89 },
{ 67, 52, 60 },
{ 79, 81, 85 },
};
// TODO:
double average = 0;
double sum = 0;
System.out.println(average);
for(int[] arr : scores){
for(int n : arr){
sum = sum + n;
}
}
average = sum / 15;
if (Math.abs(average - 77.733333) < 0.000001) {
System.out.println("测试成功");
} else {
System.out.println("测试失败");
}
}
}
方式二循环:
public class Main {
public static void main(String[] args) {
// 用二维数组表示的学生成绩:
int[][] scores = {
{ 82, 90, 91 },
{ 68, 72, 64 },
{ 95, 91, 89 },
{ 67, 52, 60 },
{ 79, 81, 85 },
};
// TODO:
double average = 0;
double sum = 0;
System.out.println(average);
for(int i = 0; i < scores.length; i++){//遍历scores数组
for(int j = 0; j < scores[i].length; j++){//对每个一维数组遍历
sum += score[i][j];
}
}
average = sum / (scores.length * scores[0].length);
if (Math.abs(average - 77.733333) < 0.000001) {
System.out.println("测试成功");
} else {
System.out.println("测试失败");
}
}
}
小总结
- 二维数组就是数组的数组,三维数组就是二维数组的数组;
- 多维数组的每个数组元素长度都不要求相同;
- 打印多维数组可以使用Arrays.deepToString();
- 最常见的多维数组是二维数组,访问二维数组的一个元素使用array[row][col]
命令行参数
Java程序的入口是main方法,而main方法可以接受一个命令行参数,它是一个String[]数组。
这个命令行参数由JVM接收用户输入并传给main方法:
public class Main {
public static void main(String[] args) {
for (String arg : args) {
System.out.println(arg);
}
}
}
我们可以利用接收到的命令行参数,根据不同的参数执行不同的代码。例如,实现一个-version参数,打印程序版本号:
public class Main {
public static void main(String[] args) {
for (String arg : args) {
if ("-version".equals(arg)) {
System.out.println("v 1.0");
break;
}
}
}
}
小总结
- 命令行参数类型是String[]数组;
- 命令行参数由JVM接收用户输入并传给main方法;
- 如何解析命令行参数需要由程序自己实现。