Java程序基础
整数运算
整数的数值表示不但是精确的,而且整数运算永远是精确的,即使是除法也是精确的
移位运算
在计算机中,整数总是以二进制的形式表示。
左移位:<<
右移位:>>
无符号右移:>>>
对byte
和short
类型进行移位时,会首先转换为int
再进行位移
仔细观察可发现,左移实际上就是不断地×2,右移实际上就是不断地÷2。
位运算
位运算是按位进行与(&)、或(|)、非(~)和异或(^)的运算
对两个整数进行位运算,实际上就是按位对齐,然后依次对每一位进行运算。
浮点数运算
浮点数运算和整数运算相比,只能进行加减乘除这些数值计算,不能做位运算和移位运算。
在计算机中,浮点数虽然表示的范围大,但是,浮点数有个非常重要的特点,就是浮点数常常无法精确表示。
由于浮点数存在运算误差,所以比较两个浮点数是否相等常常会出现错误的结果。正确的比较方法是判断两个浮点数之差的绝对值是否小于一个很小的数
布尔运算
布尔运算中的与&&
、或||
、非!
运算
短路运算:布尔运算的一个重要特点是短路运算。如果一个布尔运算的表达式能提前确定结果,则后续的计算不再执行,直接返回结果。
发生短路与的情况:第一个表达式执行结果是false,会发生短路与
发生短路或的情况:第一个表达式执行结果是true,会发生短路或
因为false && x
的结果总是false
,无论x
是true
还是false
,因此,与运算在确定第一个值为false
后,不再继续计算,而是直接返回false
。
//短路运算
public class Main {
public static void main(String[] args) {
boolean b = 5 < 3;
boolean result = b && (5 / 0 > 0);
System.out.println(result);
}
}
如果没有短路运算,&&
后面的表达式会由于除数为0
而报错,但实际上该语句并未报错,原因在于与运算是短路运算符,提前计算出了结果false
。
数组类型
定义一个数组类型的变量,使用数组类型“类型[]”,例如,int[]
。和单个基本类型变量不同,数组变量初始化必须使用new int[5]
表示创建一个可容纳5个int
元素的数组。
数组是同一数据类型的集合,数组一旦创建后,大小就不可变;
可以通过索引访问数组元素,但索引超出范围将报错;
数组元素可以是值类型(如int)或引用类型(如String),但数组本身是引用类型;
数组的特点:
- 数组所有元素初始化为默认值,整型都是
0
,浮点型是0.0
,布尔型是false
- 数组一旦创建后,大小就不可改变。
- 要访问数组中的某一个元素,需要使用索引。数组索引从
0
开始,例如,5个元素的数组,索引范围是0~4
。 - 可以修改数组中的某一个元素,使用赋值语句,例如,
ns[1] = 79;
。 - 可以用
数组变量.length
获取数组大小
数组是引用类型,在使用索引访问数组元素时,如果索引超出范围,运行时将报错。也可以在定义数组时直接指定初始化的元素,这样就不必写出数组大小,而是由编译器自动推算数组大小。例如:
public class Main{
public static void main(Stirng[] args){
int[] ns = new int[] {68, 79, 20, 80, 90};
//还可以简写为int[] ns = {68, 79, 20, 80, 90}
System.out.println(ns.length); //5
}
}
字符串数组
字符串是引用类型,定义一个字符串数组:String[] names = {"ABC", "XYZ", "zoo"};
流程控制
输入
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in); // 创建Scanner对象
System.out.print("Input your name: "); // 打印提示
String name = scanner.nextLine(); // 读取一行输入并获取字符串
System.out.print("Input your age: "); // 打印提示
int age = scanner.nextInt(); // 读取一行输入并获取整数
System.out.printf("Hi, %s, you are %d\n", name, age); // 格式化输出
}
}
判断
判断引用类型相等
判断引用类型的变量是否相等,==
表示“引用是否相等”,或者说,是否指向同一个对象。要判断引用类型的变量内容是否相等,必须使用equals()
方法
Switch多重选择
从Java 12开始,switch
语句升级为更简洁的表达式语法,使用类似模式匹配(Pattern Matching)的方法,保证只有一种路径会被执行,并且不需要break
语句:
public class Switch {
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");
}
}
}
//运行结果是 Selected apple
注意新语法使用->
,如果有多条语句,需要用{}
括起来。不要写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);
}
}
数组操作
遍历数组
通过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 each
循环,直接迭代数组的每个元素,for each
循环更加简洁,但是,for each
循环无法拿到数组的索引,因此,到底用哪一种for
循环,取决于我们的需要:
public class Main {
public static void main(String[] args) {
int[] ns = { 1, 4, 9, 16, 25 };
for (int n : ns) {
System.out.println(n);
}
}
}
打印数组内容
直接打印数组变量,得到的是数组在JVM中的引用地址
打印数组内容,可使用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));
}
}
//输出结果是 [1, 1, 2, 3, 5, 8]
数组排序
常用的排序算法有冒泡排序、插入排序和快速排序等。
冒泡排序算法对一个整型数组从小到大进行排序,冒泡排序的特点是,每一轮循环后,最大的一个数被交换到末尾,因此,下一轮循环就可以“刨除”最后的数,每一轮循环都比上一轮循环的结束位置靠前一位:
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));
}
}
//输出结果
//[28, 12, 89, 73, 65, 18, 96, 50, 8, 36]
//[8, 12, 18, 28, 36, 50, 65, 73, 89, 96]
实际上,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));
}
}
多维数组
二位数组
二维数组就是数组的数组。定义一个二维数组如下:
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
在内存中的结构如下:
定义一个普通数组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.length); // 4
}
}
实际上arr0
就获取了ns
数组的第0个元素。因为ns
数组的每个元素也是一个数组,因此,arr0
指向的数组就是{ 1, 2, 3, 4 }
。在内存中,结构如下:
访问二维数组的某个元素需要使用array[row][col]
,例如:
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维数组。但在实际应用中,除了二维数组在某些时候还能用得上,更高维度的数组很少使用。
命令行参数
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;
}
}
}
}
上面这个程序必须在命令行执行,我们先编译它:javac Main.java
然后,执行的时候,给它传递一个-version
参数:java Main -version
输出v 1.0
这样,程序就可以根据传入的命令行参数,作出不同的响应。