Java 基础知识
Java 基础知识
一、语法基础
1.1 变量
变量必须先定义,才可以使用。不能重名。
变量定义的方式:
public class Main {
public static void main(String[] args) {
int a = 5;
int b, c = a, d = 10 / 2;
}
}
内置数据类型:
类型 | 字节数 | 举例 |
---|---|---|
byte | 1 | 123 |
short | 2 | 12345 |
int | 4 | 123456789 |
long | 8 | 1234567891011L |
float | 4 | 1.2F |
double | 8 | 1.2, 1.2D |
boolean | 1 | true, false |
char | 2 | ‘A’ |
常量:
使用final
修饰:
final int N = 110;
类型转化:
- 显示转化:
int x = (int)'A';
- 隐式转化:
double x = 12, y = 4 * 3.3;
1.2 运算符
A = 10, B = 20
运算符 | 描述 | 实例 |
---|---|---|
+ | 把两个数相加 | A + B 将得到 30 |
- | 从第一个数中减去第二个数 | A - B 将得到 -10 |
* | 把两个数相乘 | A * B 将得到 200 |
/ | 分子除以分母 | B / A 将得到 2 |
% | 取模运算符,向零整除后的余数,注意余数可能为负数 | B % A 将得到 0 |
++ | 自增运算符 | A++ :先取值后加1;++A :先加1后取值 |
-- | 自减运算符 | A-- :先取值后减1;--A :先减1后取值 |
+= | 第一个数加上第二个数 | A = A + B 可以简写为 A += B |
-= | 第一个数减去第二个数 | A = A - B 可以简写为 A -= B |
*= | 第一个数乘以第二个数 | A = A * B 可以简写为 A *= B |
/= | 第一个数除以第二个数 | A = A / B 可以简写为 A /= B |
%= | 第一个对第二个数取余数 | A = A % B 可以简写为 A %= B |
1.3 表达式
整数的加减乘除四则运算:
public class Main {
public static void main(String[] args) {
int a = 6 + 3 * 4 / 2 - 2;
System.out.println(a);
int b = a * 10 + 5 / 2;
System.out.println(b);
System.out.println(23 * 56 - 78 / 3);
}
}
浮点数(小数)的运算:
public class Main {
public static void main(String[] args) {
double x = 1.5, y = 3.2;
System.out.println(x * y);
System.out.println(x + y);
System.out.println(x - y);
System.out.println(x / y);
}
}
整型变量的自增、自减:
public class Main {
public static void main(String[] args) {
int a = 1;
int b = a ++ ;
System.out.println(a + " " + b);
int c = ++ a;
System.out.println(a + " " + c);
}
}
1.4 输入
方式1:效率较低,输入规模较小时使用。
import java.util.Scanner;
public class Main {
public static void main(String[] args) throws Exception {
Scanner sc = new Scanner(System.in);
String str = sc.next(); // 读入下一个字符串
int x = sc.nextInt(); // 读入下一个整数
float y = sc.nextFloat(); // 读入下一个单精度浮点数
double z = sc.nextDouble(); // 读入下一个双精度浮点数
String line = sc.nextLine(); // 读入下一行
}
}
方式2:效率较高,输入规模较大时使用。注意需要抛异常。
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class Main {
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str = br.readLine();
System.out.println(str);
}
}
1.5 输出
方式1:效率较低,输出规模较小时使用。
public class Main {
public static void main(String[] args) throws Exception {
System.out.println(123); // 输出整数 + 换行
System.out.println("Hello World"); // 输出字符串 + 换行
System.out.print(123); // 输出整数
System.out.print("yxc\n"); // 输出字符串
System.out.printf("%04d %.2f\n", 4, 123.456D); // 格式化输出,float与double都用%f输出
}
}
System.out.printf()
中不同类型变量的输出格式:
(1) int:%d
(2) float: %f
, 默认保留6位小数
(3) double: %f
, 默认保留6位小数
(4) char: %c
, 回车也是一个字符,用 '\n'
表示
(5) String: %s
方式2:效率较高,输出规模较大时使用。注意需要抛异常。
import java.io.BufferedWriter;
import java.io.OutputStreamWriter;
public class Main {
public static void main(String[] args) throws Exception {
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
bw.write("Hello World\n");
bw.flush(); // 需要手动刷新缓冲区
}
}
二、判断语句
学习语言最好的方式就是实践,每当掌握一个新功能时,就要立即将这个功能应用到实践中。——闫学灿
2.1 if 语句
- 基本 if-else 语句
当条件成立时,执行某些语句;否则执行另一些语句。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int a = sc.nextInt();
if (a > 5) {
System.out.printf("%d is big!\n", a);
System.out.printf("%d + 1 = %d\n", a, a + 1);
} else {
System.out.printf("%d is small!\n", a);
System.out.printf("%d - 1 = %d\n", a, a - 1);
}
}
}
else 语句可以省略:
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int a = sc.nextInt();
if (a > 5) {
System.out.printf("%d is big!\n", a);
System.out.printf("%d + 1 = %d\n", a, a + 1);
}
}
}
当只有一条语句时,大括号可以省略:
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int a = sc.nextInt();
if (a > 5)
System.out.printf("%d is big!\n", a);
else
System.out.printf("%d is small!\n", a);
}
}
练习:输入一个整数,输出这个数的绝对值。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int x = sc.nextInt();
if (x > 0)
System.out.println(x);
else
System.out.println(-x);
}
}
练习:输入两个整数,输出两个数中较大的那个。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int a = sc.nextInt(), b = sc.nextInt();
if (a > b)
System.out.println(a);
else
System.out.println(b);
}
}
if-else 语句内部也可以是 if-else 语句。
练习:输入三个整数,输出三个数中最大的那个。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int a = sc.nextInt(), b = sc.nextInt(), c = sc.nextInt();
if (a > b) {
if (a > c)
System.out.println(a);
else
System.out.println(c);
} else {
if (b > c)
System.out.println(b);
else
System.out.println(c);
}
}
}
常用比较运算符
(1) 大于
>
(2) 小于<
(3) 大于等于>=
(4) 小于等于<=
(5) 等于==
(6) 不等于!=
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int a = sc.nextInt(), b = sc.nextInt();
if (a > b) System.out.printf("%d > %d\n", a, b);
if (a >= b) System.out.printf("%d >= %d\n", a, b);
if (a < b) System.out.printf("%d < %d\n", a, b);
if (a <= b) System.out.printf("%d <= %d\n", a, b);
if (a == b) System.out.printf("%d == %d\n", a, b);
if (a != b) System.out.printf("%d != %d\n", a, b);
}
}
if-else连写:
输入一个0到100之间的分数,
如果大于等于85,输出A;
如果大于等于70并且小于85,输出B;
如果大于等于60并且小于70,输出C;
如果小于60,输出 D;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int s = sc.nextInt();
if (s >= 85) {
System.out.println("A");
} else if (s >= 70) {
System.out.println("B");
} else if (s >= 60) {
System.out.println("C");
} else {
System.out.println("D");
}
}
}
练习:
1.判断闰年。闰年有两种情况:
(1) 能被100整除时,必须能被400整除;
(2) 不能被100整除时,被4整除即可。
输入一个年份,如果是闰年输出yes,否则输出no。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int year = sc.nextInt();
if (year % 100 == 0) {
if (year % 400 == 0)
System.out.println("yes");
else
System.out.println("no");
} else {
if (year % 4 == 0)
System.out.println("yes");
else
System.out.println("no");
}
}
}
2.2 条件表达式
(1) 与 &&
(2) 或 ||
(3) 非!
例题:输入三个数,输出三个数中的最大值。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int a = sc.nextInt(), b = sc.nextInt(), c = sc.nextInt();
if (a >= b && a >= c)
System.out.println(a);
else if (b >= a && b >= c)
System.out.println(b);
else
System.out.println(c);
}
}
练习:用一条 if
语句,判断闰年。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int year = sc.nextInt();
if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
System.out.println("yes");
else
System.out.println("no");
}
}
2.3 switch 语句
注意: swtich
语句中如果不加 break
语句,则从上到下匹配到第一个 case
后,会顺次执行后面每个 case
中的语句。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int day = sc.nextInt();
String name;
switch(day) {
case 1:
name = "Monday";
break;
case 2:
name = "Tuesday";
break;
case 3:
name = "Wednesday";
break;
case 4:
name = "Thursday";
break;
case 5:
name = "Friday";
break;
case 6:
name = "Saturday";
break;
case 7:
name = "Sunday";
break;
default:
name = "not valid";
}
System.out.println(name);
}
}
三、循环语句
学习编程语言语法是次要的,思维是主要的。如何把头脑中的想法变成简洁的代码,至关重要。——闫学灿
学习循环语句只需要抓住一点 —— 代码执行顺序!
3. 1 while 循环
可以简单理解为循环版的 if
语句。if
语句是判断一次,如果条件成立,则执行后面的语句;
while
是每次判断,如果成立,则执行循环体中的语句,否则停止。
public class Main {
public static void main(String[] args) {
int i = 0;
while (i < 10) {
System.out.println(i);
i ++ ;
}
}
}
练习:求 ~ 中所有数的立方和。
public class Main {
public static void main(String[] args) {
int i = 1, sum = 0;
while (i <= 100) {
sum += i * i * i;
i ++ ;
}
System.out.println(sum);
}
}
练习:求斐波那契数列的第 项。。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int a = 1, b = 1, i = 1;
while (i < n) {
int c = a + b;
a = b;
b = c;
i ++ ;
}
System.out.println(a);
}
}
死循环:循环永久执行,无法结束。我们要避免写出死循环。
public class Main {
public static void main(String[] args) {
int x = 1;
while (x == 1)
System.out.println("!");
}
}
3.2 do while 循环
do while
循环不常用。
do while
语句与 while
语句非常相似。唯一的区别是,do while
语句限制性循环体后检查条件。不管条件的值如何,我们都要至少执行一次循环。
public class Main {
public static void main(String[] args) {
int x = 1;
while (x < 1) {
System.out.println("x!");
}
int y = 1;
do {
System.out.println("y!");
} while (y < 1);
}
}
3.3 for 循环
基本思想:把控制循环次数的变量从循环体中剥离。
for (init-statement; condition; expression) {
statement
}
init-statement
可以是声明语句、表达式、空语句,一般用来初始化循环变量;condition
是条件表达式,和 while 中的条件表达式作用一样;可以为空,空语句表示true;expression
一般负责修改循环变量,可以为空。
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 10; i ++ ) { // 循环体中只有一条语句时,可以不加大括号
System.out.println(i);
}
}
}
练习:求 ~ 中所有数的立方和。
public class Main {
public static void main(String[] args) {
int sum = 0;
for (int i = 1; i <= 100; i ++ )
sum += i * i * i;
System.out.println(sum);
}
}
练习:求斐波那契数列的第 项。。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int a = 1, b = 1;
for (int i = 1; i < n; i ++ ) {
int c = a + b;
a = b;
b = c;
}
System.out.println(a);
}
}
init-statement
可以定义多个变量,expression
也可以修改多个变量。
例如求 1 * 10 + 2 * 9 + 3 * 8 + 4 * 7 + 5 * 6
:
public class Main {
public static void main(String[] args) {
int sum = 0;
for (int i = 1, j = 10; i < j; i ++, j -- ) {
sum += i * j;
}
System.out.println(sum);
}
}
3.4 跳转语句
3.4.1 break
可以提前从循环中退出,一般与 if
语句搭配。
例题:判断一个大于 的数是否是质数:
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
boolean isPrime = true;
for (int i = 2; i < n; i ++ )
if (n % i == 0) {
isPrime = false;
break;
}
if (isPrime)
System.out.println("yes");
else
System.out.println("no");
}
}
3.4.2 continue
可以直接跳到当前循环体的结尾。作用与 if
语句类似。
例题:求 ~ 中所有偶数的和。
public class Main {
public static void main(String[] args) {
int sum = 0;
for (int i = 1; i <= 100; i ++ ) {
if (i % 2 == 1) continue;
sum += i;
}
System.out.println(sum);
}
}
3.5 多层循环
将 ~ 打印到一个 的矩阵中:
public class Main {
public static void main(String[] args) {
for (int i = 0, k = 1; i < 10; i ++ ) {
for (int j = 0; j < 10; j ++, k ++ ) {
System.out.printf("%d ", k);
}
System.out.println();
}
}
}
练习:打印 ~ 中的所有质数
public class Main {
public static void main(String[] args) {
for (int i = 2; i <= 100; i ++ ) {
boolean isPrime = true;
for (int j = 2; j < i; j ++ ) {
if (i % j == 0) {
isPrime = false;
break;
}
}
if (isPrime)
System.out.println(i);
}
}
}
四、数组
程序 = 逻辑 + 数据,数组是存储数据的强而有力的手段。 ——闫学灿
4.1 一维数组
4.1.1 数组的定义
数组的定义方式和变量类似。
public class Main {
public static void main(String[] args) {
int[] a = new int[10], b;
float[] f = new float[33];
double[] d = new double[123];
char[] c = new char[21];
}
}
4.1.2 数组的初始化
public class Main {
public static void main(String[] args) {
int[] a = {0, 1, 2}; // 含有3个元素的数组,元素分别是0, 1, 2
int[] b = new int[3]; // 含有3个元素的数组,元素的值均为0
char[] d = {'a', 'b', 'c'}; // 字符数组的初始化
}
}
4.1.3 访问数组元素
通过下标访问数组。
public class Main {
public static void main(String[] args) {
int[] a = {0, 1, 2}; // 数组下标从0开始
System.out.printf("%d %d %d\n", a[0], a[1], a[2]);
a[0] = 5;
System.out.println(a[0]);
}
}
练习题1: 使用数组实现求斐波那契数列的第 项。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[] f = new int[n + 1];
f[0] = 0;
f[1] = 1;
for (int i = 2; i <= n; i ++ )
f[i] = f[i - 1] + f[i - 2];
System.out.println(f[n]);
}
}
练习题2:输入一个 ,再输入 个整数。将这 个整数逆序输出。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[] a = new int[n];
for (int i = 0; i < n; i ++ )
a[i] = sc.nextInt();
for (int i = n - 1; i >= 0; i -- )
System.out.printf("%d ", a[i]);
}
}
练习题3:输入 个数,将这 个数按从小到大的顺序输出。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[] a = new int[n];
for (int i = 0; i < n; i ++ )
a[i] = sc.nextInt();
for (int i = 0; i < n; i ++ )
for (int j = i + 1; j < n; j ++ )
if (a[i] > a[j]) {
int t = a[i];
a[i] = a[j];
a[j] = t;
}
for (int i = 0; i < n; i ++ )
System.out.printf("%d ", a[i]);
}
}
4.2 多维数组
多维数组就是数组的数组。
public class Main {
public static void main(String[] args) {
int[][] a = new int[3][4]; // 大小为3的数组,每个元素是含有4个整数的数组。
int[][][] b = new int[10][20][30]; // 将所有元素的初值为0
// 大小为10的数组,它的每个元素是含有20个数组的数组
// 这些数组的元素是含有30个整数的数组
}
}
public class Main {
public static void main(String[] args) {
int[][] a = { // 三个元素,每个元素都是大小为4的数组
{0, 1, 2, 3}, // 第1行的初始值
{4, 5, 6, 7}, // 第2行的初始值
{8, 9, 10, 11} // 第3行的初始值
};
for (int i = 0; i < 4; i ++ ) // 将第一行全部变成0
a[0][i] = 0;
for (int i = 0; i < 3; i ++ ) { // 输出二维数组
for (int j = 0; j < 4; j ++ ) {
System.out.printf("%d ", a[i][j]);
}
System.out.println();
}
}
}
4.3 数组的范围遍历
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
int[][] a = {
{0, 1, 2, 3},
{4, 5, 6, 7},
{8, 9, 10, 11},
};
for (int[] row: a) { // 范围遍历
for (int x: row) // 范围遍历
System.out.printf("%d ", x);
System.out.println();
}
}
}
4.3 常用API
- 属性
length
:返回数组长度,注意不加小括号 Arrays.sort()
:数组排序Arrays.fill(int[] a, int val)
:填充数组Arrays.toString()
:将数组转化为字符串Arrays.deepToString()
:将多维数组转化为字符串- 数组不可变长
- 使用
Arrays
需要import java.util.Arrays
五、字符串
字符串是计算机与人类沟通的重要手段。——闫学灿
5.1 字符与整数的联系——ASCII码
每个常用字符都对应一个 -128 ~ 127
的数字,二者之间可以相互转化。
注意:目前负数没有与之对应的字符。
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
char c = 'a';
System.out.println((int)c);
int a = 66;
System.out.println((char)a);
}
}
常用ASCII值:'A'- 'Z'
是65 ~ 90
,'a' - 'z'
是97 - 122
,0 - 9
是 48 - 57
。
字符可以参与运算,运算时会将其当做整数:
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int a = 'B' - 'A';
int b = 'A' * 'B';
char c = 'A' + 2;
System.out.println(a);
System.out.println(b);
System.out.println(c);
}
}
5.2 String 类
初始化:
String a = "Hello World";
String b = "My name is ";
String x = b; // 存储到了相同地址
String c = b + "yxc"; // String可以通过加号拼接
String d = "My age is " + 18; // int会被隐式转化成字符串"18"
String str = String.format("My age is %d", 18); // 格式化字符串,类似于C++中的sprintf
String money_str = "123.45";
double money = Double.parseDouble(money_str); // String转double
只读变量,不能修改,例如:
String a = "Hello ";
a += "World"; // 会构造一个新的字符串
访问String中的字符:
String str = "Hello World";
for (int i = 0; i < str.length(); i ++ ) {
System.out.print(str.charAt(i)); // 只能读取,不能写入
}
常用API:
length()
:返回长度charAt(i)
:字符串的第i
个位置的字符split(String regex)
:分割字符串String[] strs = sc.nextLine().split(” “);
indexOf(char c)
、indexOf(String str)
、lastIndexOf(char c)
、lastIndexOf(String str)
:查找,找不到返回-1equals()
:判断两个字符串是否相等,注意不能直接用==compareTo()
:判断两个字符串的字典序大小,负数表示小于,0表示相等,正数表示大于startsWith()
:判断是否以某个前缀开头endsWith()
:判断是否以某个后缀结尾trim()
:去掉首尾的空白字符toLowerCase()
:全部用小写字符toUpperCase()
:全部用大写字符replace(char oldChar, char newChar)
:替换字符replace(String oldRegex, String newRegex)
:替换字符串substring(int beginIndex, int endIndex)
:返回[beginIndex, endIndex)
中的子串toCharArray()
:将字符串转化成字符数组reverse()
:翻转字符串范围遍历:
String str = “Hello World”; for ( char c:str.toCharArray()) { System.out.print(c); }
5.3 输入与输出
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String str1 = sc.next(); // 输入字符串,遇到空格、回车等空白字符时停止输入
String str2 = sc.nextLine(); // 输入一整行字符串,遇到空格不会停止输入,遇到回车才会停止
System.out.println(str1); // 可以直接输出
System.out.printf("%s\n", str2); // 也可以格式化输出,用 %s 表示字符串
}
}
5.4 StringBuilder、StringBuffer
String
不能被修改,如果打算修改字符串,可以使用StringBuilder
和StringBuffer
。
StringBuffer
线程安全,速度较慢;StringBuilder
线程不安全,速度较快。
StringBuilder sb = new StringBuilder("Hello "); // 初始化
sb.append("World"); // 拼接字符串
System.out.println(sb);
for (int i = 0; i < sb.length(); i ++ ) {
sb.setCharAt(i, (char)(sb.charAt(i) + 1)); // 读取和写入字符
}
System.out.println(sb);
六、函数
理解函数,最重要的是理解代码的执行顺序。——闫学灿
6.1 函数基础
一个典型的函数定义包括以下部分:修饰符、返回类型、函数名字、由0个或多个形参组成的列表以及函数体。
6.1.1 编写函数
我们来编写一个求阶乘的程序。程序如下所示:
private static int fact(int val) {
int res = 1;
for (int i = 1; i <= val; i ++ )
res *= i;
return res;
}
函数名字是 fact
,它作用于一个整型参数,返回一个整型值。return
语句负责结束 fact
并返回 res
的值。
修饰符包括 private、static
等,它们属于类相关的概念,会在下一章解释。
6.1.2 调用函数
public class Main {
private static int fact(int val) {
int res = 1;
for (int i = 1; i <= val; i ++ )
res *= i;
return res;
}
public static void main(String[] args) {
int res = fact(5);
System.out.printf("5! is %d\n", res);
}
}
函数的调用完成两项工作:一是用实参初始化函数对应的形参,二是将控制权转移给被调用函数。此时,主调函数的执行被暂时中断,被调函数开始执行。
6.1.3 形参和实参
实参是形参的初始值。第一个实参初始化第一个形参,第二个实参初始化第二个形参,依次类推。形参和实参的类型和个数必须匹配。
fact("hello"); // 错误:实参类型不正确
fact(); // 错误:实参数量不足
fact(42, 10, 0); // 错误:实参数量过多
fact(' '); // 正确:该实参能自动转换成int类型,' '的ASCII值为32,所以该操作等价于fact(32);
6.1.4 函数的形参列表
函数的形参列表可以为空,但是不能省略。
void f1() {/* …. */} // 空形参列表
形参列表中的形参通常用逗号隔开,其中每个形参都是含有一个声明符的声明。即使两个形参的类型一样,也必须把两个类型都写出来:
int f3(int v1, v2) {/* … */} // 错误
int f4(int v1, int v2) {/* … */} // 正确
6.1.5 函数返回类型
大多数类型都能用作函数的返回类型。一种特殊的返回类型是 void
,它表示函数不返回任何值。
函数的返回类型也可以是数组、字符串或者其他对象:
import java.util.Arrays;
public class Main {
private static int[] newArray() {
int[] a = {1, 2, 3};
return a;
}
private static String newString() {
return "Hello World";
}
public static void main(String[] args) {
System.out.println(Arrays.toString(newArray()));
System.out.println(newString());
}
}
6.1.6 变量的作用域
本章中我们只使用静态成员变量和静态成员函数,非静态成员变量/函数及其区别会在下一章中介绍。
- 函数内定义的变量为局部变量,只能在函数内部使用。
- 定义在类中的变量为成员变量,可以在类的所有成员函数中调用。
- 当局部变量与全局变量重名时,会优先使用局部变量。
public class Main {
private static int x = 4;
private static void f1() {
int x = 3;
System.out.println(x);
}
private static void f2() {
System.out.println(x);
}
private static void f3() {
System.out.println(x + 1);
}
public static void main(String[] args) {
f1();
f2();
f3();
}
}
6.2 参数传递
6.2.1 值传递
八大基本数据类型和 String
类型等采用值传递。
将实参的初始值拷贝给形参。此时,对形参的改动不会影响实参的初始值。
public class Main {
private static void f(int x) {
x = 5;
}
public static void main(String[] args) {
int x = 10;
f(x);
System.out.println(x);
}
}
6.2.2 引用传递
除 String
以外的数据类型的对象,例如 数组、StringBuilder
等采用引用传递。
将实参的引用(地址)传给形参,通过引用找到变量的真正地址,然后对地址中的值修改。所以此时对形参的修改会影响实参的初始值。
import java.util.Arrays;
public class Main {
private static void f1(int[] a) {
for (int i = 0, j = a.length - 1; i < j; i ++, j -- ) {
int t = a[i];
a[i] = a[j];
a[j] = t;
}
}
private static void f2(StringBuilder sb) {
sb.append("Hello World");
}
public static void main(String[] args) {
int[] a = {1, 2, 3, 4, 5};
f1(a);
System.out.println(Arrays.toString(a));
StringBuilder sb = new StringBuilder("");
f2(sb);
System.out.println(sb);
}
}
6.3 返回类型和 return 语句
return
语句终止当前正在执行的函数并将控制权返回到调用该函数的地方。
return
语句有两种形式:
return;
return expression;
6.3.1 无返回值函数
没有返回值的 return
语句只能用在返回类型是 void
的函数中。
返回 void
的函数不要求非得有 return
语句,因为在这类函数的最后一句后面会隐式地执行 return
。
通常情况下,void
函数如果想在它的中间位置提前退出,可以使用 return
语句。return
的这种用法有点类似于我们用 break
语句退出循环。
public class Main {
private static void swap(int[] a) { // 交换a[0]和a[1]
// 如果两个值相等,则不需要交换,直接退出
if (a[0] == a[1])
return;
// 如果程序执行到了这里,说明还需要继续完成某些功能
int tmp = a[0];
a[0] = a[1];
a[1] = tmp;
// 此处无须显示的return语句
}
public static void main(String[] args) {
int[] a = {3, 4};
swap(a);
System.out.printf("%d %d\n", a[0], a[1]);
}
}
6.3.2 有返回值的函数
只要函数的返回类型不是 void
,则该函数内的每个分支都必须有 return
语句,且每条 return
语句都必须返回一个值。return
语句返回值的类型必须与函数的返回类型相同,或者能隐式地转换函数的返回类型。
import java.util.Scanner;
public class Main {
private static int max(int a, int b) {
if (a > b)
return a;
return b;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int x = sc.nextInt(), y = sc.nextInt();
System.out.println(max(x, y));
}
}
6.4 函数重载
函数重载 是指:在同一个类中存在多个函数,函数名称相同但参数列表不同。
编译器会根据实参的类型选择最匹配的函数来执行。
import java.util.Scanner;
public class Main {
private static int max(int a, int b) {
System.out.println("int max");
if (a > b) return a;
return b;
}
private static double max(double a, double b) {
System.out.println("double max");
if (a > b) return a;
return b;
}
public static void main(String[] args) {
System.out.println(max(3, 4));
System.out.println(max(3.0, 4.0));
}
}
6.5 函数递归
在一个函数内部,也可以调用函数本身。
import java.util.Scanner;
public class Main {
private static int fib(int n) { // 求斐波那切数列第n项
if (n <= 2) return 1;
return fib(n - 1) + fib(n - 2);
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
System.out.println(fib(n));
}
}
七、类与接口
类可以将变量、函数完美地打包在一起。——闫学灿
7.1 类与对象
类定义一种全新的数据类型,包含一组变量和函数;对象是类这种类型对应的实例。
例如在一间教室中,可以将 Student
定义成类,表示 “学生” 这个抽象的概念。那么每个同学就是 Student
类的一个对象(实例)。
7.1.1 源文件声明规则
- 一个源文件中只能有一个
public
类。 - 一个源文件可以有多个非
public
类。 - 源文件的名称应该和
public
类的类名保持一致。 - 每个源文件中,先写
package
语句,再写import
语句,最后定义类。
7.1.2 类的定义
public
: 所有对象均可以访问private
: 只有本类内部可以访问protected
:同一个包或者子类中可以访问- 不添加修饰符:在同一个包中可以访问
- 静态(带
static
修饰符)成员变量/函数与普通成员变量/函数的区别:- 所有
static
成员变量/函数在类中只有一份,被所有类的对象共享; - 所有普通成员变量/函数在类的每个对象中都有独立的一份;
- 静态函数中只能调用静态函数/变量;访问静态变量最好通过类名;
- 普通函数中既可以调用普通函数/变量,也可以调用静态函数/变量。
- 所有
class Point {
private int x;
private int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public String toString() {
return String.format("(%d, %d)", x, y);
}
}
7.1.3 类的继承
每个类只能继承一个类。
class ColorPoint extends Point {
private String color;
public ColorPoint(int x, int y, String color) {
super(x, y);
this.color = color;
}
public void setColor(String color) {
this.color = color;
}
public String toString() {
return String.format("(%d, %d, %s)", super.getX(), super.getY(), this.color);
}
}
7.1.4 类的多态
public class Main {
public static void main(String[] args) {
Point point = new Point(3, 4);
Point colorPoint = new ColorPoint(1, 2, "red");
// 多态,同一个类的实例,调用相同的函数,运行结果不同
System.out.println(point.toString());
System.out.println(colorPoint.toString());
}
}
7.2 接口
interface
与 class
类似。主要用来定义类中所需包含的函数。
接口也可以继承其他接口,一个类可以实现多个接口。
7.2.1 接口的定义
接口中不添加修饰符时,默认为 public
。
interface Role {
public void greet();
public void move();
public int getSpeed();
}
7.2.2 接口的继承
每个接口可以继承多个接口
interface Hero extends Role {
public void attack();
}
7.2.3 接口的实现
每个类可以实现多个接口
class Zeus implements Hero {
private final String name = "Zeus";
public void attack() {
System.out.println(name + ": Attack!");
}
public void greet() {
System.out.println(name + ": Hi!");
}
public void move() {
System.out.println(name + ": Move!");
}
public int getSpeed() {
return 10;
}
}
7.2.4 接口的多态
class Athena implements Hero {
private final String name = "Athena";
public void attack() {
System.out.println(name + ": Attack!!!");
}
public void greet() {
System.out.println(name + ": Hi!!!");
}
public void move() {
System.out.println(name + ": Move!!!");
}
public int getSpeed() {
return 10;
}
}
public class Main {
public static void main(String[] args) {
Hero[] heros = {new Zeus(), new Athena()};
for (Hero hero: heros) {
hero.greet();
}
}
}
八、常用容器
8.1 List
接口:java.util.List<>
实现:
java.util.ArrayList<>
:变长数组java.util.LinkedList<>
:双链表
函数:
add()
:在末尾添加一个元素clear()
:清空size()
:返回长度isEmpty()
:是否为空get(i)
:获取第i
个元素set(i, val)
:将第i
个元素设置为val
8.2 栈
类:java.util.Stack<>
函数:
push()
:压入元素pop()
:弹出栈顶元素,并返回栈顶元素peek()
:返回栈顶元素size()
:返回长度empty()
:栈是否为空clear()
:清空
8.3 队列
接口:java.util.Queue<>
实现:
java.util.LinkedList<>
:双链表java.util.PriorityQueue<>
:优先队列- 默认是小根堆,大根堆写法:
new PriorityQueue<>(Collections.reverseOrder())
- 默认是小根堆,大根堆写法:
函数:
add()
:在队尾添加元素remove()
:删除并返回队头isEmpty()
:是否为空size()
:返回长度peek()
:返回队头clear()
:清空
8.4 Set
接口:java.util.Set<K>
实现:
java.util.HashSet<K>
:哈希表java.util.TreeSet<K>
:平衡树
函数:
add()
:添加元素contains()
:是否包含某个元素remove()
:删除元素size()
:返回元素数isEmpty()
:是否为空clear()
:清空
java.util.TreeSet
多的函数:
ceiling(key)
:返回大于等于key
的最小元素,不存在则返回null
floor(key)
:返回小于等于key
的最大元素,不存在则返回null
8.5 Map
接口:java.util.Map<K, V>
实现:
java.util.HashMap<K, V>
:哈希表java.util.TreeMap<K, V>
:平衡树
函数:
put(key, value)
:添加关键字和其对应的值get(key)
:返回关键字对应的值containsKey(key)
:是否包含关键字remove(key)
:删除关键字size()
:返回元素数isEmpty()
:是否为空clear()
:清空entrySet()
:获取Map
中的所有对象的集合Map.Entry<K, V>
:Map
中的对象类型getKey()
:获取关键字getValue()
:获取值
java.util.TreeMap<K, V>
多的函数:
ceilingEntry(key)
:返回大于等于key
的最小元素,不存在则返回null
floorEntry(key)
:返回小于等于key
的最大元素,不存在则返回null
九、异常处理
异常处理可以允许我们在程序运行时进行诊断和补救。—— 闫学灿
9.1 Error 与 Exception 的区别
Error 是程序无法处理的错误,比如 OutOfMemoryError、ThreadDeath 等。这些异常发生时,Java 虚拟机 (JVM) 一般会选择线程终止。此类异常是程序的致命异常,是无法捕获处理的。
Exception 是程序本身可以处理的异常,这种异常分两大类运行时异常和非运行时异常。 程序中应当尽可能去处理这些异常。
9.2 Exception类的继承关系
9.3 运行时异常和非运行时异常的区别
运行时异常都是 RuntimeException
类及其子类异常,如 NullPointerException
、IndexOutOfBoundsException
等, 这些异常是非检查型异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
非运行时异常是 RuntimeException
以外的异常,类型上都属于 Exception
类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如 IOException、SQLException
等以及用户自定义的 Exception
异常,这些是检查型异常。一般情况下不自定义检查型异常。
9.4 内置异常类
非检查性异常:
异常 | 描述 |
---|---|
ArithmeticException | 当出现异常的运算条件时,抛出此异常。例如,一个整数”除以零”时,抛出此类的一个实例。 |
ArrayIndexOutOfBoundsException | 用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引。 |
ArrayStoreException | 试图将错误类型的对象存储到一个对象数组时抛出的异常。 |
ClassCastException | 当试图将对象强制转换为不是实例的子类时,抛出该异常。 |
IllegalArgumentException | 抛出的异常表明向方法传递了一个不合法或不正确的参数。 |
IllegalMonitorStateException | 抛出的异常表明某一线程已经试图等待对象的监视器,或者试图通知其他正在等待对象的监视器而本身没有指定监视器的线程。 |
IllegalStateException | 在非法或不适当的时间调用方法时产生的信号。换句话说,即 Java 环境或 Java 应用程序没有处于请求操作所要求的适当状态下。 |
IllegalThreadStateException | 线程没有处于请求操作所要求的适当状态时抛出的异常。 |
IndexOutOfBoundsException | 指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。 |
NegativeArraySizeException | 如果应用程序试图创建大小为负的数组,则抛出该异常。 |
NullPointerException | 当应用程序试图在需要对象的地方使用 null 时,抛出该异常。 |
NumberFormatException | 当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。 |
SecurityException | 由安全管理器抛出的异常,指示存在安全侵犯。 |
StringIndexOutOfBoundsException | 此异常由 String 方法抛出,指示索引或者为负,或者超出字符串的大小。 |
UnsupportedOperationException | 当不支持请求的操作时,抛出该异常 |
检查性异常:
异常 | 描述 |
---|---|
ClassNotFoundException | 应用程序试图加载类时,找不到相应的类,抛出该异常。 |
CloneNotSupportedException | 当调用 Object 类中的 clone 方法克隆对象,但该对象的类无法实现 Cloneable 接口时,抛出该异常。 |
IllegalAccessException | 拒绝访问一个类的时候,抛出该异常。 |
InstantiationException | 当试图使用 Class 类中的 newInstance 方法创建一个类的实例,而指定的类对象因为是一个接口或是一个抽象类而无法实例化时,抛出该异常。 |
InterruptedException | 一个线程被另一个线程中断,抛出该异常。 |
NoSuchFieldException | 请求的变量不存在 |
NoSuchMethodException | 请求的方法不存在 |
9.5 内置异常方法
方法 | 说明 |
---|---|
public String getMessage() | 返回关于发生的异常的详细信息。这个消息在Throwable 类的构造函数中初始化了。 |
public Throwable getCause() | 返回一个 Throwable 对象代表异常原因。 |
public String toString() | 返回此 Throwable 的简短描述。 |
public void printStackTrace() | 将此 Throwable 及其回溯打印到标准错误流 |
public StackTraceElement [] getStackTrace() | 返回一个包含堆栈层次的数组。下标为0的元素代表栈顶,最后一个元素代表方法调用堆栈的栈底。 |
public Throwable fillInStackTrace() | 用当前的调用栈层次填充Throwable 对象栈层次,添加到栈层次任何先前信息中。 |
9.6 捕获异常
import java.util.Scanner;
public class Main {
private static void foo() {
int[] array = new int[5];
for (int i = 0; i < 5; i ++ )
array[i] = i;
Scanner sc = new Scanner(System.in);
int k = sc.nextInt();
int x = sc.nextInt();
try {
array[k] /= x;
} catch (ArithmeticException e) {
System.out.println("除零错误!");
e.printStackTrace();
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("数组越界!");
e.printStackTrace();
} finally {
for (int i = 0; i < 5; i ++ ) {
System.out.println(array[i]);
}
}
}
public static void main(String[] args) {
foo();
}
}
9.7 抛出异常
throw
: 在函数内抛出一个异常。throws
:在函数定义时抛出一些可能的异常。
检查型异常必须被捕获或者抛出。
import java.io.IOException;
import java.util.Scanner;
public class Main {
private static void foo() throws IOException, NoSuchFieldException {
Scanner sc = new Scanner(System.in);
int x = sc.nextInt();
if (x == 1)
throw new IOException("找不到文件!!!");
else
throw new NoSuchFieldException("自定义异常");
}
public static void main(String[] args) {
try {
foo();
} catch (IOException e) {
System.out.println("IOException!");
e.printStackTrace();
} catch (NoSuchFieldException e) {
System.out.println("NoSuchFieldException!");
e.printStackTrace();
}
}
}
9.8 try-with-resources
JDK7 之后,Java 新增的 try-with-resource
语法糖来打开资源,并且可以在语句执行完毕后确保每个资源都被自动关闭 。
try
用于声明和实例化资源,catch
用于处理关闭资源时可能引发的所有异常。
import java.io.*;
public class Main {
public static void main(String[] args) {
String line;
try (
BufferedReader br = new BufferedReader(new FileReader("input.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"));
) {
while ((line = br.readLine()) != null) {
System.out.println("Line => " + line);
bw.write("copy: " + line + "\n");
}
bw.flush();
} catch (IOException e) {
System.out.println("IOException in try block =>" + e.getMessage());
}
}
}
十、注解与反射
10.1 注解
(1) 注解(Annotation)也被称为元数据(Metadata),用于修饰包、方法、属性、构造器、局部变量等数据信息。
(2) 注解不影响程序逻辑,但注解可以被编译或运行。
(3) 在 JavaSE 中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在 JavaEE 中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替 JavaEE 旧版中所遗留的繁冗代码和 XML 配置等。
10.1.1 常用注解
(1) @Override
: 限定某个函数必须重写其他函数,该注解只能用于函数。函数名和参数列表必须相同。
(2) @Overload
: 限定某个函数必须重载其他函数,该注解只能用于函数。函数名必须相同,参数列表必须不同。
(3) @Deprecated
:用于表示某个程序元素(类、函数)已过时
(4) @SuppressWarnings
:抑制编译器警告
10.1.2 元注解
修饰其他注解的注解,就被称为元注解。
(1) Retention
:指定注解的作用范围
(2) Target
:指定注解可以用在哪些地方
(3) Document
:注定注解是否出出现在 javadoc 中
(4) Inherited
:子类会继承父类的注解
10.2 反射
反射:动态引入类、动态调用实例的成员函数、成员变量等。
10.2.1 常用API
(1) java.lang.Class
:获取类的实例对象
(2) java.lang.reflect.Method
:获取类或接口中的单个方法的信息
(3) java.lang.reflect.Field
:获取类或接口中的单个字段的信息
(4) java.lang.reflect.Constructor
:获取类中的单个构造函数的信息
package org.yxc;
public class Calculator {
public String name;
public Calculator() {}
public Calculator(String name) {
this.name = name;
}
public int add(int a, int b) {
return a + b;
}
}
package org.yxc;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
Class<?> cls = Class.forName("org.yxc.Calculator");
Object o = cls.newInstance();
Method method = cls.getMethod("add", int.class, int.class);
int res = (int)method.invoke(o, 3, 4);
System.out.println(res);
Field field = cls.getField("name");
field.set(o, "My Calculator!");
System.out.println(field.get(o));
Constructor<?> constructor = cls.getConstructor(String.class);
Object new_o = constructor.newInstance("New Calculator!");
System.out.println(new_o);
}
}
10.2.2 优缺点
优点:可以动态创建和使用对象,使用灵活
缺点:执行速度慢
十一、多线程与锁
11.1 多线程
11.1.1 实现多线程
写法1:继承 Thread 类
class Worker extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i ++ ) {
System.out.println("Hello! " + this.getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Main {
public static void main(String[] args) {
Worker worker1 = new Worker();
Worker worker2 = new Worker();
worker1.setName("thread-1");
worker2.setName("thread-2");
worker1.start();
worker2.start();
}
}
写法2:实现 Runnable 接口
class Worker1 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i ++ ) {
System.out.println("Hello! " + "thread-1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
class Worker2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i ++ ) {
System.out.println("Hello! " + "thread-2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Main {
public static void main(String[] args) {
new Thread(new Worker1()).start();
new Thread(new Worker2()).start();
}
}
11.1.2 常用API
start()
:开启一个线程Thread.sleep()
: 休眠一个线程join()
:等待线程执行结束interrupt()
:从休眠中中断线程(不能中断任何线程)setDaemon()
:将线程设置为守护线程。当只剩下守护线程时,程序自动退出
11.2 锁
lock
:获取锁,如果锁已经被其他线程获取,则阻塞unlock
:释放锁,并唤醒被该锁阻塞的其他线程
import java.util.concurrent.locks.ReentrantLock;
class Worker extends Thread {
public static int cnt = 0;
private static final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
for (int i = 0; i < 100000; i ++ ) {
lock.lock();
try {
cnt ++ ;
} finally {
lock.unlock();
}
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
Worker worker1 = new Worker();
Worker worker2 = new Worker();
worker1.start();
worker2.start();
worker1.join();
worker2.join();
System.out.println(Worker.cnt);
}
}
11.3 同步(Synchronized)
写法1:将 Synchronized 加到代码块上
class Count {
public int cnt = 0;
}
class Worker extends Thread {
public final Count count;
public Worker(Count count) {
this.count = count;
}
@Override
public void run() {
synchronized (count) {
for (int i = 0; i < 100000; i ++ ) {
count.cnt ++ ;
}
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
Count count = new Count();
Worker worker1 = new Worker(count);
Worker worker2 = new Worker(count);
worker1.start();
worker2.start();
worker1.join();
worker2.join();
System.out.println(count.cnt);
}
}
写法2:将 Synchronized 加到函数上(锁加到了 this 对象上)
class Worker implements Runnable {
public static int cnt = 0;
private synchronized void work() {
for (int i = 0; i < 100000; i ++ ) {
cnt ++ ;
}
}
@Override
public void run() {
work();
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
Worker worker = new Worker();
Thread worker1 = new Thread(worker);
Thread worker2 = new Thread(worker);
worker1.start();
worker2.start();
worker1.join();
worker2.join();
System.out.println(Worker.cnt);
}
}
11.3.1 wait 与 notify
package org.yxc;
class Worker extends Thread {
private final Object object;
private final boolean needWait;
public Worker(Object object, boolean needWait) {
this.object = object;
this.needWait = needWait;
}
@Override
public void run() {
synchronized (object) {
try {
if (needWait) {
object.wait();
System.out.println(this.getName() + ": 被唤醒啦!");
} else {
object.notifyAll();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
for (int i = 0; i < 5; i ++ ) {
Worker worker = new Worker(object, true);
worker.setName("thread-" + i);
worker.start();
}
Worker worker = new Worker(object, false);
worker.setName("thread-" + 5);
Thread.sleep(1000);
worker.start();
}
}