java学习笔记之基础:变量和数据类型、数组、控制结构
Java 是面向对象的语言,一个程序的基本单位是 class。
/**
* 可以用来创建文档的注释
*/
public class Hello {
public static void main(String[] args) {
// 向屏幕输出文本:
System.out.println("Hello, world!");
/* 多行注释开始
注释内容
注释结束 */
}
} // class定义结束
这里定义了 main 方法,返回值是 void,表示没有任何返回值。
程序要求
- Java 程序入口方法必须是静态方法,方法名必须为 main,括号内的参数必须是 String 数组
- Java 的每一行语句必须以分号结束
- 类名必须以英文字母开头,后接字母、数字和下划线的组合,习惯以大写字母开头
命令行参数
- main 方法可以接受一个命令行参数,它是一个
String[]。 - 这个命令行参数由 JVM 接收用户输入并传给 main 方法
变量和数据类型
在 Java 中,变量必须先定义后使用。在定义变量的时候,可以给它一个初始值。变量的一个重要特点是可以重新赋值,还可以赋值给其他变量。在 Java 中变量分为两种:基本类型的变量和引用类型的变量。
基本数据类型
基本数据类型是 CPU 可以直接进行运算的类型。Java 定义了以下几种基本数据类型:
- 整数类型:byte、short、int、long,分别占用 1、2、4、8 个字节,Java 只定义了带符号的整型,因此最高位的 bit 表示符号位。
- 浮点数类型:float,double,分别占用 4、8 个字节,float 类型需要加上 f 后缀。
- 字符类型:char,占用 2 个字节,表示一个字符。可表示 ASCII 和 Unicode 字符,char 类型定义使用单引号
' - 布尔类型:boolean,布尔类型只有 true 和 false 两个值
int i = 2147483647;
int i2 = -2147483648;
int i3 = 2_000_000_000; // 加下划线更容易识别
int i4 = 0xff0000; // 十六进制表示的 16711680
int i5 = 0b1000000000; // 二进制表示的 512
long n1 = 9000000000000000000L; // long 型的结尾需要加L
long n2 = 900; // 没有加 L,此处 900 为 int,但 int 类型可以赋值给 long
// int i6 = 900L; // 错误:不能把 long 型赋值给 int
float f1 = 3.14f;
float f2 = 3.14e38f; // 科学计数法表示的 3.14x10^38
// float f3 = 1.0; // 错误:不带 f 结尾的是 double 类型,不能赋值给 float
double d = 1.79e308;
double d2 = -1.79e308;
double d3 = 4.9e-324; // 科学计数法表示的 4.9x10^-324
boolean b1 = true;
boolean b2 = false;
boolean isGreater = 5 > 3; // 计算结果为true
char a = 'A';
char zh = '中';
引用类型
除了上述基本类型的变量,其它类型都是引用类型。引用类型的变量类似于 C 语言的指针,它内部存储一个'地址',指向某个对象在内存的位置。
String s = "hello";
变量定义省略类型 var 关键字
有时类型的名字太长,写起来比较麻烦。如果想省略变量类型,可以使用 var 关键字,编译器会根据赋值语句自动推断出变量的类型。因此使用 var 定义变量,仅仅是少写了变量类型而已。
var list = new ArrayList<String>();
// 等价于
ArrayList<String> list = new ArrayList<>();
变量的作用范围
在 Java 中,多行语句用 { ... } 括起来,称为语句块。在语句块中定义的变量,它有一个作用域,就是从定义处开始,到语句块结束。超出了作用域引用这些变量,编译器会报错。定义变量时,要遵循作用域最小化原则,尽量将变量定义在尽可能小的作用域,并且不要重复使用变量名。
整数运算
Java 的整数运算遵循四则运算规则,可以使用任意嵌套的小括号。整数的数值表示是精确的,而且整数运算永远是精确的,即使是除法也是精确的,因为两个整数相除只能得到结果的整数部分。
int x = 12345 / 67; // 184
int y = 12345 % 67; // 12345÷67的余数是17
溢出
整数由于存在范围限制,如果计算结果超出了范围就会产生溢出,而溢出不会出错却会得到一个奇怪的结果。
int x = 2147483640;
int y = 15;
int sum = x + y;
System.out.println(sum); // -2147483641
自增/自减
int n = 3300;
n++; // 3301, 相当于 n = n + 1;
n--; // 3300, 相当于 n = n - 1;
n += 100; // 3400, 相当于 n = n + 100;
n -= 100; // 3200, 相当于 n = n - 100;
移位运算
左移
int n = 7; // 00000000 00000000 00000000 00000111 = 7
int a = n << 1; // 00000000 00000000 00000000 00001110 = 14
int b = n << 2; // 00000000 00000000 00000000 00011100 = 28
int c = n << 28; // 01110000 00000000 00000000 00000000 = 1879048192
int d = n << 29; // 11100000 00000000 00000000 00000000 = -536870912
右移
int n = 7; // 00000000 00000000 00000000 00000111 = 7
int a = n >> 1; // 00000000 00000000 00000000 00000011 = 3
int b = n >> 2; // 00000000 00000000 00000000 00000001 = 1
int n1 = -536870912;
int a1 = n1 >> 1; // 11110000 00000000 00000000 00000000 = -268435456
int b1 = n1 >> 2; // 11111000 00000000 00000000 00000000 = -134217728
int c1 = n1 >> 28; // 11111111 11111111 11111111 11111110 = -2
int d1 = n1 >> 30; // -1
对 byte 和 short 类型进行移位时,会首先转换为 int 再进行移位。仔细观察可发现,左移实际上就是不断地 ×2 ,右移实际上就是不断地 ÷2 。
位运算
位运算是按位进行与、或、非和异或的运算。
- 与运算的规则是,必须两个数同时为 1,结果才为 1
- 或运算的规则是,只要任意一个为 1,结果就为 1
- 非运算的规则是,0 和 1 互换
- 异或运算的规则是,如果两个数不同,结果为 1,否则为 0
n = 0 & 0; // 0
n = 0 & 1; // 0
n = 1 & 0; // 0
n = 1 & 1; // 1
n = 0 | 0; // 0
n = 0 | 1; // 1
n = 1 | 0; // 1
n = 1 | 1; // 1
n = ~0; // 1
n = ~1; // 0
n = 0 ^ 0; // 0
n = 0 ^ 1; // 1
n = 1 ^ 0; // 1
n = 1 ^ 1; // 0
对两个整数进行位运算,实际上就是按位对齐,然后依次对每一位进行运算。
整数类型自动提升与强制转型
在运算过程中,如果参与运算的两个数类型不一致,那么计算结果为较大类型的整型。也可以将结果强制转型,即将大范围的整数转型为小范围的整数,但超出范围的强制转型可能会得到错误的结果。原因是转型时高位字节直接被扔掉,仅保留了低位字节。
short s = 1234;
int i = 123456;
int x = s + i; // s自动转型为int
short y = s + i; // 编译错误!
int i1 = 12345;
short s1 = (short) i1; // 12345
int i2 = 1234567;
short s2 = (short) i2; // -10617
浮点数运算
浮点数运算和整数运算相比,只能进行加减乘除这些数值计算,不能做位运算和移位运算。在计算机中,浮点数虽然表示的范围大,但是浮点数常常无法精确表示。由于浮点数存在运算误差,所以比较两个浮点数是否相等常常会出现错误的结果。正确的比较方法是判断两个浮点数之差的绝对值是否小于一个很小的数。
// 比较x和y是否相等,先计算其差的绝对值:
double r = Math.abs(x - y);
// 再判断绝对值是否足够小:
if (r < 0.00001) {
// 可以认为相等
} else {
// 不相等
}
类型提升
如果参与运算的两个数其中一个是整型另一个是浮点型,那么整型可以自动提升到浮点型。在一个复杂的四则运算中,两个整数的运算不会出现自动提升的情况。
int n = 5;
double d = 1.2 + 24.0 / n; // 6.0
double s = 1.2 + 24 / 5; // 结果不是 6.0 而是 5.2
溢出
整数运算在除数为 0 时会报错,而浮点数运算在除数为 0 时不会报错,但会返回几个特殊值:
- NaN 表示 Not a Number
- Infinity 表示无穷大
- -Infinity 表示负无穷大
double d1 = 0.0 / 0; // NaN
double d2 = 1.0 / 0; // Infinity
double d3 = -1.0 / 0; // -Infinity
强制转型
可以将浮点数强制转型为整数,在转型时浮点数的小数部分会被丢掉。如果转型后超过了整型能表示的最大范围,将返回整型的最大值。如果要进行四舍五入,可以对浮点数加上 0.5 再强制转型
int n1 = (int) 12.3; // 12
int n2 = (int) 12.7; // 12
int n3 = (int) -12.7; // -12
int n4 = (int) (12.7 + 0.5); // 13
int n5 = (int) 1.2e20; // 2147483647
布尔运算
布尔运算是一种关系运算,包括以下几类:
- 比较运算符:>,>=,<,<=,==,!=
- 与运算 &&
- 或运算 ||
- 非运算 !
boolean isGreater = 5 > 3; // true
int age = 12;
boolean isZero = age == 0; // false
boolean isNonZero = !isZero; // true
boolean isAdult = age >= 18; // false
boolean isTeenager = age >6 && age <18; // true
短路运算
布尔运算的一个重要特点是短路运算。如果一个布尔运算的表达式能提前确定结果,则后续的计算不再执行,直接返回结果。
三元运算符
Java 还提供一个三元运算符 b ? x : y,它根据第一个布尔表达式的结果,分别返回后续两个表达式之一的计算结果。注意到三元运算 b ? x : y 会首先计算 b,如果 b 为 true 则只计算 x,否则只计算 y。此外,x 和 y 的类型必须相同,因为返回值不是 boolean,而是 x 和 y 之一。
字符和字符串
一个 char 保存一个 Unicode 字符。Java 在内存中总是使用 Unicode 表示字符,所以一个英文字符和一个中文字符都用一个 char 类型表示,它们都占用两个字节。要显示一个字符的 Unicode 编码,只需将 char 类型直接赋值给 int 类型即可。还可以直接用转义字符\uUnicode 编码来表示一个字符。
char c1 = 'A';
char c2 = '中';
int n1 = 'A'; // 字母“A”的Unicodde编码是65
int n2 = '中'; // 汉字“中”的Unicode编码是20013
// 注意是十六进制:
char c3 = '\u0041'; // 'A',因为十六进制0041 = 十进制65
char c4 = '\u4e2d'; // '中',因为十六进制4e2d = 十进制20013
字符串类型 String 是引用类型,我们用双引号 "..." 表示字符串。一个字符串可以存储 0 个到任意个字符。
String s = ""; // 空字符串,包含0个字符
String s1 = "A"; // 包含一个字符
String s2 = "ABC"; // 包含3个字符
String s3 = "中文 ABC"; // 包含6个字符,其中有一个空格
String s4 = "abc\"xyz"; // 包含7个字符: a, b, c, ", x, y, z
String s5 = "abc\\xyz"; // 包含7个字符: a, b, c, \, x, y, z
String s6 = "ABC\n\u4e2d\u6587"; // 包含6个字符: A, B, C, 换行符, 中, 文
字符串连接
Java 的编译器对字符串做了特殊处理,可以使用 + 连接任意字符串和其他数据类型,这样极大地方便了字符串的处理。
String s1 = "Hello";
String s2 = "world";
String s3 = s1 + " " + s2 + "!";
int age = 25;
String s = "age is " + age; // age is 25
多行字符串
String s = """
SELECT *
FROM users
WHERE id > 100
ORDER BY name DESC
""";
字符串的不可变特性
Java 的字符串除了是一个引用类型外,还有个重要特点就是字符串不可变。
String s = "hello";
s = "world";
执行 String s = "hello"; 时,JVM 虚拟机先创建字符串 "hello",然后把字符串变量 s 指向它。执行 s = "world"; 时,JVM 虚拟机先创建字符串 "world",然后把字符串变量 s 指向它。原来的字符串 "hello" 还在,只是我们无法通过变量 s 访问它而已。因此字符串的不可变是指字符串内容不可变。
空值 null
引用类型的变量可以指向一个空值 null,它表示不存在,即该变量不指向任何对象。注意要区分空值 null 和空字符串 "",空字符串是一个有效的字符串对象,它不等于 null。
String s1 = null; // s1是null
String s2 = s1; // s2也是null
String s3 = ""; // s3指向空字符串,不是null
常量
定义变量的时候,如果加上 final 修饰符,这个变量就变成了常量。常量定义时进行初始化后就不可再次赋值,再次赋值会导致编译错误。为了和变量区分开来,常量名通常全部大写。
final double PI = 3.14; // PI是一个常量
double r = 5.0;
double area = PI * r * r;
PI = 300; // compile error!
数组类型
定义一个数组类型的变量,使用 类型[],例如: int[]。和单个基本类型变量不同,数组变量初始化必须使用 new int[5] 表示创建一个可容纳 5 个 int 元素的数组。
Java 的数组有几个特点:数组所有元素初始化为默认值,整型都是 0,浮点型是 0.0,布尔型是 false;数组一旦创建后,大小就不可改变。
要访问数组中的元素需要使用索引。数组索引从 0 开始,5 个元素的数组,索引范围是 0~4。可以使用赋值语句修改数组中的某一个元素,例如 ns[1] = 79;。可以用数组变量.length 获取数组大小。数组是引用类型,在使用索引访问数组元素时,如果索引超出范围,运行时将报错。也可以在定义数组时直接指定初始化的元素,这样就不必写出数组大小,而是由编译器自动推算数组大小。
int[] ns = new int[5];
System.out.println(ns.length); // 5
ns[0] = 68;
ns[1] = 79;
System.out.println(ns[1]); // 79
int[] ns = new int[] { 68, 79, 91, 85, 62 };
//简写
int[] ns1 = { 68, 79, 91, 85, 62 };
遍历数组
int[] ns = { 1, 4, 9, 16, 25 };
for (int i=0; i<ns.length; i++) {
System.out.println(ns[i]);
}
for (int n : ns) {
System.out.println(n);
}
打印整个数组
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] ns = { 68, 79, 91, 85, 62 };
System.out.println(Arrays.toString(ns));
}
}
数组排序
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));
}
}
必须注意,对数组排序实际上修改了数组本身。即变量 ns 指向的数组内容已经被改变了。
多维数组
多维数组的每个数组元素长度不要求相同。
int[][] ns = {
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 }
};
System.out.println(ns.length); // 3
System.out.println(Arrays.deepToString(ns));
System.out.println(ns[1][2]); // 7
int[][] ns1 = {
{ 1, 2, 3, 4 },
{ 5, 6 },
{ 7, 8, 9 }
};
for (int[] arr : ns) {
for (int n : arr) {
System.out.print(n);
System.out.print(', ');
}
System.out.println();
}
控制结构
输出
System.out.print("hello"); // 输出
System.out.println(" world"); // 输出,并换行
double d = 3.1415926;
System.out.printf("%.2f\n", d); // 格式化输出,显示两位小数3.14
输入
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); // 格式化输出
}
}
System.out 代表标准输出流,而 System.in 代表标准输入流。
if 条件判断
int n = 70;
if (n >= 90) {
System.out.println("优秀");
} else if (n >= 60) {
System.out.println("及格了");
} else {
System.out.println("挂科了");
}
在 Java 中,判断值类型的变量是否相等,可以使用 == 运算符。判断引用类型的变量是否相等,== 表示“引用是否相等”,或者说是否指向同一个对象。要判断引用类型的变量内容是否相等,必须使用 equals() 方法.
String s1 = "hello";
String s2 = "HELLO".toLowerCase();
if (s1.equals(s2)) {
System.out.println("s1 equals s2");
} else {
System.out.println("s1 not equals s2");
}
String s3 = null;
if (s3 != null && s3.equals("hello")) {
System.out.println("hello");
}
switch 多重选择
switch 语句根据 switch 表达式计算的结果,跳转到匹配的 case 结果,然后继续执行后续语句,直到遇到 break 结束执行。
int option = 1;
switch (option) {
case 1:
System.out.println("Selected 1");
break;
case 2:
System.out.println("Selected 2");
break;
default:
System.out.println("Selected other");
break;
}
使用 switch 时,注意 case 语句并没有花括号 {} ,而且 case 语句具有“穿透性”,漏写 break 将导致意想不到的结果:从匹配到的语句开始,后续语句将全部执行,直到遇到 break 语句。因此 任何时候都不要忘记写 break。
switch 语句还可以匹配字符串。字符串匹配时,是比较“内容相等”。
switch 表达式
从 Java 12 开始,switch 语句升级为更简洁的表达式语法,使用类似模式匹配的方法,保证只有一种路径会被执行,并且不需要 break 语句。
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");
}
//返回值
int opt = switch (fruit) {
case "apple" -> 1;
case "pear", "mango" -> 2;
default -> 0;
};
int opt1 = switch (fruit) {
case "apple" -> 1;
case "pear", "mango" -> 2;
default -> {
int code = fruit.hashCode();
yield code; // switch语句返回值
}
};
注意新语法使用 ->,如果有多条语句,需要用 {}括起来。不要写 break 语句,因为新语法只会执行匹配的语句,没有穿透效应。
while 循环
while 循环是先判断循环条件再循环,因此有可能一次循环都不做。编写循环时要注意循环条件,并避免死循环。
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
do while 循环
do while 循环则是先执行循环再判断条件,条件满足时继续循环,条件不满足时退出。使用 do while 循环时,同样要注意循环条件的判断。
int sum = 0;
int n = 1;
do {
sum = sum + n;
n ++;
} while (n <= 100);
System.out.println(sum);
for 循环
for 循环的功能非常强大,它使用计数器实现循环。for 循环会先初始化计数器,然后在每次循环前检测循环条件,在每次循环后更新计数器。 for 循环的初始化计数器总是会被执行,并且 for 循环也可能循环 0 次。
int sum = 0;
for (int i=1; i<=100; i++) {
sum = sum + i;
}
System.out.println(sum);
for each 循环
int[] ns = { 1, 4, 9, 16, 25 };
for (int n : ns) {
System.out.println(n);
}
和 for 循环相比,for each 循环的变量 n 不再是计数器,而是直接对应到数组的每个元素。for each 循环的写法也更简洁。但是 for each 循环无法指定遍历顺序,也无法获取数组的索引。
break 和 continue
无论是 while 循环还是 for 循环,有两个特别的语句可以使用:break 语句和 continue 语句。break 语句: 在循环过程中,可以使用 break 语句跳出当前循环。continue 语句: continue 则是提前结束本次循环,直接继续执行下次循环。
浙公网安备 33010602011771号