1.变量、运算符、表达式、输入输出
编写一个简单的Java程序–手速练习 public class Main { public static void main(String[] args) { System.out.println("Hello World"); } } 三、语法基础
-
变量 变量必须先定义,才可以使用。不能重名。 变量定义的方式:
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;
-
运算符 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
-
表达式 整数的加减乘除四则运算:
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,效率较低,输入规模较小时使用。
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,效率较低,输出规模较小时使用。
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、判断语句
一、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"); } }
} 二、条件表达式 (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"); }
} 三、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、循环语句
一、while循环 可以简单理解为循环版的if语句。if语句是判断一次,如果条件成立,则执行后面的语句;while是每次判断,如果成立,则执行循环体中的语句,否则停止。
public class Main { public static void main(String[] args) { int i = 0; while (i < 10) { System.out.println(i); i ++ ; } } } 练习:求1~100中所有数的立方和。
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); } } 练习:求斐波那契数列的第n项。f(1) = 1, f(2) = 1, f(3) = 2, f(n) = f(n-1) + 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 = 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("!"); } } 二、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); }
} 三、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); } } } 练习:求1~100中所有数的立方和。
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); } } 练习:求斐波那契数列的第n项。f(1) = 1, f(2) = 1, f(3) = 2, f(n) = f(n-1) + 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 = 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); }
} 四、跳转语句
-
break 可以提前从循环中退出,一般与if语句搭配。 例题:判断一个大于1的数是否是质数:
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"); }
}
-
continue 可以直接跳到当前循环体的结尾。作用与if语句类似。 例题:求1~100中所有偶数的和。
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); }
} 五、多层循环 将1~100打印到一个10 * 10的矩阵中:
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(); } } } 练习:打印1~100中的所有质数
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); } } }
-
数组
程序 = 逻辑 + 数据,数组是存储数据的强而有力的手段。 ——闫学灿
-
一维数组 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]; } } 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'}; // 字符数组的初始化 } } 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: 使用数组实现求斐波那契数列的第 NN 项。
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:输入一个 nn,再输入 nn 个整数。将这 nn 个整数逆序输出。
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:输入 nn 个数,将这 nn 个数按从小到大的顺序输出。
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]); }
}
-
多维数组 多维数组就是数组的数组。
public class Main { public static void main(String[] args) { int a = new int3; // 大小为3的数组,每个元素是含有4个整数的数组。 int[] b = new int10[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(); } }
}
-
数组的范围遍历 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(); } }
} 3 常用API 属性length:返回数组长度,注意不加小括号 Arrays.sort():数组排序 Arrays.fill(int[] a, int val):填充数组 Arrays.toString():将数组转化为字符串 Arrays.deepToString():将多维数组转化为字符串 数组不可变长 使用Arrays需要import java.util.Arrays
5、字符串
-
字符与整数的联系——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); }
}
-
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():返回长度 split(String regex):分割字符串 indexOf(char c)、indexOf(String str)、lastIndexOf(char c)、lastIndexOf(String str):查找,找不到返回-1 equals():判断两个字符串是否相等,注意不能直接用== 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():将字符串转化成字符数组
-
输入与输出 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 表示字符串 }
}
-
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); 常用API:
reverse():翻转字符串
6、函数
-
函数基础 一个典型的函数定义包括以下部分:修饰符、返回类型、函数名字、由0个或多个形参组成的列表以及函数体。
1.1 编写函数 我们来编写一个求阶乘的程序。程序如下所示:
public class Main { 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等,它们属于类相关的概念,会在下一章解释。
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); }
} 函数的调用完成两项工作:一是用实参初始化函数对应的形参,二是将控制权转移给被调用函数。此时,主调函数的执行被暂时中断,被调函数开始执行。
1.3 形参和实参 实参是形参的初始值。第一个实参初始化第一个形参,第二个实参初始化第二个形参,依次类推。形参和实参的类型和个数必须匹配。
fact("hello"); // 错误:实参类型不正确 fact(); // 错误:实参数量不足 fact(42, 10, 0); // 错误:实参数量过多 fact(' '); // 正确:该实参能自动转换成int类型,' '的ASCII值为32,所以该操作等价于fact(32); 1.4 函数的形参列表 函数的形参列表可以为空,但是不能省略。
void f1() {/* …. */} // 空形参列表 形参列表中的形参通常用逗号隔开,其中每个形参都是含有一个声明符的声明。即使两个形参的类型一样,也必须把两个类型都写出来:
int f3(int v1, v2) {/* … /} // 错误 int f4(int v1, int v2) {/ … */} // 正确 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()); }
} 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(); }
}
-
参数传递 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); }
} 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); }
}
-
返回类型和return语句 return语句终止当前正在执行的函数并将控制权返回到调用该函数的地方。return语句有两种形式:
return; return expression; 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]); }
} 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)); }
}
-
函数重载 函数重载是指:在同一个类中存在多个函数,函数名称相同但参数列表不同。
编译器会根据实参的类型选择最匹配的函数来执行。
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)); }
}
-
函数递归 在一个函数内部,也可以调用函数本身。
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、类与接口
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、常用容器
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、异常处理
-
Error与Exception的区别 Error是程序无法处理的错误,比如OutOfMemoryError、ThreadDeath等。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。此类异常是程序的致命异常,是无法捕获处理的。
Exception是程序本身可以处理的异常,这种异常分两大类运行时异常和非运行时异常。 程序中应当尽可能去处理这些异常。
-
Exception类的继承关系
-
运行时异常和非运行时异常的区别 运行时异常都是RuntimeException类及其子类异常,如NullPointerException、IndexOutOfBoundsException等, 这些异常是非检查型异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
非运行时异常是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,这些是检查型异常。一般情况下不自定义检查型异常。
-
内置异常类 非检查性异常
异常 描述 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 请求的方法不存在
-
内置异常方法 方法 说明 public String getMessage() 返回关于发生的异常的详细信息。这个消息在Throwable 类的构造函数中初始化了。 public Throwable getCause() 返回一个 Throwable 对象代表异常原因。 public String toString() 返回此 Throwable 的简短描述。 public void printStackTrace() 将此 Throwable 及其回溯打印到标准错误流 public StackTraceElement [] getStackTrace() 返回一个包含堆栈层次的数组。下标为0的元素代表栈顶,最后一个元素代表方法调用堆栈的栈底。 public Throwable fillInStackTrace() 用当前的调用栈层次填充Throwable 对象栈层次,添加到栈层次任何先前信息中。
-
捕获异常 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(); }
}
-
抛出异常 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(); } }
}
-
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、注解与反射
10.1 注解 (1) 注解(Annotation)也被称为元数据(Metadata),用于修饰包、方法、属性、构造器、局部变量等数据信息。 (2) 注解不影响程序逻辑,但注解可以被编译或运行。 (3) 在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗代码和XML配置等。
10.1.1 常用注解 (1) @Override: 限定某个函数必须重载其他函数,该注解只能用于函数 (2) @Deprecated:用于表示某个程序元素(类、函数)已过时 (3) @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、多线程与锁
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(); }
}