基础知识
JDK、JRE、JVM的关系:
JDK:Java Development Kit,Java开发工具包
JRE: Java Runtime Environment,Java运行环境
JVM:Java Virtual Machine,Java虚拟机
JDK包含JRE,JRE包含JVM
JDK版本选择
目前JDK1.8(也叫JDK8,注意不是JDK18)用得最多
Java代码的编译运行流程
将Java源码编译成Java字节码。
使用JVM将Java字节码转化成机器码。
JVM作用:跨平台、内存管理、安全。
JSE、JEE、JME的区别
JSE: Java Standard Edition,标准版
JEE:Java Enterprise Edition,企业版
JME: Java Mirco Edition,移动版
Spring是JEE的轻量级替代品
SpringBoot是Spring + 自动化配置
变量、运算符、输入与输出
普通Java项目创建
类似于C#,Java的所有变量和函数都要定义在class中。
内置数据类型
类型 字节数 举例
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;
表达式
与C++、Python3类似:
int a = 1, b = 2, c = 3;
int x = (a + b) * c;
x ++;
输入
方式1,效率较低,输入规模较小时使用。
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,效率较高,输入规模较大时使用。注意需要抛异常。
package com.yxc;
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,效率较低,输出规模较小时使用。
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输出
方式2,效率较高,输出规模较大时使用。注意需要抛异常。
package com.yxc;
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(); // 需要手动刷新缓冲区
}
}
判断语句
if-else语句
与C++、Python中类似。
例如:
package com.yxc;
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.printf("%d是闰年\n", year);
else
System.out.printf("%d不是闰年\n", year);
} else {
if (year % 4 == 0)
System.out.printf("%d是闰年\n", year);
else
System.out.printf("%d不是闰年\n", year);
}
}
}
switch语句
与C++中类似。
package com.yxc;
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);
}
}
逻辑运算符与条件表达式
与C++、Python类似。
例如:
package com.yxc;
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 && year % 4 == 0 || year % 400 == 0)
System.out.printf("%d是闰年\n", year);
else
System.out.printf("%d不是闰年\n", year);
}
}
循环语句
while循环
与C++、Python类似,例如:
int i = 0;
while (i < 5) {
System.out.println(i);
i ++ ;
}
do while循环
与C++、Python类似,例如:
int i = 0;
do {
System.out.println(i);
i ++ ;
} while (i < 5);
do while语句与while语句非常相似。唯一的区别是,do while语句限制性循环体后检查条件。不管条件的值如何,我们都要至少执行一次循环。
for循环
与C++、Python类似,例如:
for (int i = 0; i < 5; i ++ ) { // 普通循环
System.out.println(i);
}
int[] a = {0, 1, 2, 3, 4};
for (int x: a) { // forEach循环
System.out.println(x);
}
数组
Java中的数组与C++中的数组类似。
初始化
与C++类似。
初始化定长数组,长度可以是变量,可以在初始化时赋值。
int[] a = new int[5]; // 初始化长度为5的int数组,初始值为0
int n = 10;
float[] b = new float[n]; // 初始化长度为n的float数组,初始值为0.0F
char[] c = {'a', 'b', 'c'}; // 初始化长度为3的char数组,初始值为:'a', 'b', 'c'
char[] d = c; // d与c地址相同,更改c中的元素,d中的元素也会改变
数组元素的读取与写入
与C++类似。
int[] a = new int[5];
for (int i = 0; i < 5; i++) {
a[i] = i;
}
for (int i = 0; i < 5; i ++ ) {
System.out.println(a[i] * a[i]);
}
多维数组
与C++类似。
int[][] a = new int[2][3];
a[1][2] = 1;
int[][] b = {
{1, 2, 3},
{4, 5, 6},
};
System.out.println(a[1][2]);
System.out.println(b[0][1]);
常用API
属性length:返回数组长度,注意不加小括号
Arrays.sort(arr):数组排序
Arrays.fill(int[] a, int val):填充数组
Arrays.toString(arr):将数组转化为字符串
Arrays.deepToString(arr):将多维数组转化为字符串
如果Arrays.toString(arr)则会输出[地址, 地址, ...]的形式
数组不可变长
字符串
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):查找,找不到返回-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)中的子串
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():翻转字符串
sb.reverse();
System.out.println(sb);
函数
Java的所有变量和函数都要定义在类中。
函数或变量前加static表示静态对象,类似于全局变量。
静态对象属于class,而不属于class的具体实例。
静态函数中只能调用静态函数和静态变量。
示例:
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
System.out.println(max(3, 4));
int[][] a = new int[3][4];
fill(a, 3);
System.out.println(Arrays.deepToString(a));
int[][] b = getArray2d(2, 3, 5);
System.out.println(Arrays.deepToString(b));
}
private static int max(int a, int b) {
if (a > b) return a;
return b;
}
private static void fill(int[][] a, int val) {
for (int i = 0; i < a.length; i ++ )
for (int j = 0; j < a[i].length; j ++ )
a[i][j] = val;
}
private static int[][] getArray2d(int row, int col, int val) {
int[][] a = new int[row][col];
for (int i = 0; i < row; i ++ )
for (int j = 0; j < col; j ++ )
a[i][j] = val;
return a;
}
}
类与接口
类
class与C++、Python类似。
源文件声明规则
一个源文件中只能有一个public类。
一个源文件可以有多个非public类。
源文件的名称应该和public类的类名保持一致。
每个源文件中,先写package语句,再写import语句,最后定义类。
类的定义
public: 所有对象均可以访问
private: 只有自己可以访问
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);
}
}
类的继承
每个类只能继承一个类。
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);
}
}
类的多态
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());
}
}
接口
interface与class类似。主要用来定义类中所需包含的函数。
接口可以包含变量,但是一般不这么做
接口也可以继承其他接口,一个类可以实现多个接口。
接口的定义
interface Role {
public void greet();
public void move();
public int getSpeed();
}
接口的继承
每个接口可以继承多个接口
interface Role2 {
public void eat();
}
interface Hero extends Role, Role2 {
public void attack();
}
接口的实现
每个类可以实现多个接口
interface Person {
public void sleep();
}
class Zeus implements Hero, Person {
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;
}
public int sleep() {
System.out.println(name + ": Sleep!");
}
}
接口的多态
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();
}
}
}
泛型
类似于C++的template,Java的类和接口也可以定义泛型,即同一套函数可以作用于不同的对象类型。
泛型只能使用对象类型,不能使用基本变量类型。比如Integer,而不是int。
Stack<Integer> stk = new Stack<Integer>();
常用容器
只有栈是类,其它都是接口。
List
接口:java.util.List<>。
实现:
java.util.ArrayList<>:变长数组 List<Integer> list = new ArrayList<>();
java.util.LinkedList<>:双链表 List<Integer> list = new LinkedList<>();
函数:
add():在末尾添加一个元素
clear():清空
size():返回长度
isEmpty():是否为空
get(i):获取第i个元素
set(i, val):将第i个元素设置为val
栈
类:java.util.Stack<>
函数:
push():压入元素
pop():弹出栈顶元素,并返回栈顶元素
peek():返回栈顶元素
size():返回长度
empty():栈是否为空
clear():清空
队列
接口:java.util.Queue<>
实现:
java.util.LinkedList<>:双链表 Queue<Integer> queue = new LinkedList<>();
java.util.PriorityQueue<>:优先队列 Queue<Integer> queue = new PriorityQueue<>();
默认是小根堆,大根堆写法:new PriorityQueue<>(Collections.reverseOrder())
函数:
add():在队尾添加元素
remove():删除并返回队头
isEmpty():是否为空
size():返回长度
peek():返回队头,不删除
clear():清空
Set
接口:java.util.Set<K>
实现:
java.util.HashSet<K>:哈希表,无序
java.util.TreeSet<K>:平衡树,有序
无序的增删改查都是O(1)的,而有序的都是O(logN)
函数:
add():添加元素
contains():是否包含某个元素
remove():删除元素
size():返回元素数
isEmpty():是否为空
clear():清空
java.util.TreeSet多的函数:二分操作
ceiling(key):返回大于等于key的最小元素,不存在则返回null
floor(key):返回小于等于key的最大元素,不存在则返回null
注意,如果想要用这两个操作,就不能用Set接口,而是要用TreeSet,因为Set接口里没有这两个操作
Map
接口:java.util.Map<K, V>
实现:
java.util.HashMap<K, V>:哈希表,无序
java.util.TreeMap<K, V>:平衡树,有序
函数:
put(key, value):添加关键字和其对应的值
get(key):返回关键字对应的值
getOrDefault(key, v) 如果没有找到对应关键字,则返回v
containsKey(key):是否包含关键字
remove(key):删除关键字
size():返回元素数
isEmpty():是否为空
clear():清空
values():返回值构成的Collection
keys():返回键构成的Collection
entrySet():获取Map中的所有对象的集合
Map.Entry<K, V>:Map中的单个对象类型
getKey():获取关键字
getValue():获取值
for (Map.Entry<Integer, String> p: map.entrySet()) {
System.out.println(p.getKey());
System.out.println(p.getValue());
}
java.util.TreeMap<K, V>多的函数:二分操作
ceilingEntry(key):返回大于等于key的最小元素,不存在则返回null
floorEntry(key):返回小于等于key的最大元素,不存在则返回null
注意,如果想要用这两个操作,就不能用Map接口,而是要用TreeMap,因为Map接口里没有这两个操作
Java栈的代码写的并不好,用LinkedList代替栈是更好的选择。队列同样可以用LinkedList
异常处理
异常处理可以允许我们在程序运行时进行诊断和补救。—— 闫学灿
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 对象栈层次,添加到栈层次任何先前信息中。
捕获异常
如果列举exception都不匹配,就会抛出异常,如果抛出的异常还是没被处理,就会报错退出
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 { // 无论是否报错,都执行;即使catch里return也会执行
for (int i = 0; i < 5; i ++ ) {
System.out.println(array[i]);
}
}
}
public static void main(String[] args) {
foo();
}
}
如果报错处理代码一样,可以简化代码
try {
array[k] /= x;
} catch (ArithmeticException | ArrayIndexOutOfBoundsException e) {
System.out.println(e.getMessage());
e.printStackTrace(); // 不会中止程序
} finally {
for (int i = 0; i < 5; i ++ ) {
System.out.println(array[i]);
}
}
抛出异常
throw: 在函数内抛出一个异常。
throws:在函数定义时抛出一些可能的异常。
检查型异常必须被捕获或者抛出。
注意try-catch的运行效率并不高,所以不推荐将其写到循环里
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
对于资源的利用时,我们可能会因为抛出错误,而忘记关闭资源,这就可能导致资源浪费。之前我们可以将关闭资源的代码写到finally里,但是比较麻烦。
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());
}
}
}
注解与反射
注解
注解(Annotation)也被称为元数据(Metadata),用于修饰包、方法、属性、构造器、局部变量等数据信息。
注解不影响程序逻辑,但注解可以被编译或运行。
在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗代码和XML配置等。
常用注解
@Override: 限定某个函数必须是在重写基类的函数,该注解只能用于函数。函数名和参数列表必须相同
@Overload: 限定某个函数必须是在重载基类的函数,该注解只能用于函数。函数名必须相同,参数列表必须不同
@Deprecated:用于表示某个程序元素(类、函数)已过时
@SuppressWarnings:抑制编译器警告
元注解
修饰其他注解的注解,就被称为元注解。
Retention:指定注解的作用范围
Target:指定注解可以用在哪些地方,函数、类还是变量等等
Document:注定注解是否出出现在javadoc中
Inherited:子类会继承父类的注解
反射
Java不是完全的编译型语言,也不是解释型语言。Java源码会先编译成Java字节码,然后加载所有类,再在虚拟机执行。
Java因此有个类似Python解释型代码的优点,就是如果有少量代码需要修改,那么不需要重新编译所有代码,只需要编译对应类代码,然后重新加载这个类就行。这就是反射机制。
反射:动态引入类、动态调用实例的成员函数、成员变量等。
常用API
java.lang.Class
java.lang.reflect.Method
java.lang.reflect.Field
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;
}
public String toString() {
return name;
}
}
Class:Java中每一个类都对应一个Class类型
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); // 对象调用函数 等价于正常情况下的 o.add(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);
}
}
优缺点
优点:可以动态创建和使用对象,使用灵活
缺点:执行速度慢
多线程与锁
多线程
实现多线程
写法1:继承Thread类
这种方法每个线程都要new一个对象
class Worker extends Thread {
@Override
public void run() { // 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(); // 启动线程,会自动调用run函数
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();
}
}
class Worker1 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i ++ ) {
System.out.println("Hello! ");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Main {
public static void main(String[] args) {
Worker worker = new Worker();
new Thread(worker).start();
new Thread(worker).start(); // 两个线程可以用一个实例创建
}
}
常用API
start():开启一个线程
Thread.sleep(ms): 休眠一个线程,ms表示休眠多少毫秒
join(ms):等待某个线程执行结束,例如如果thread1中加了一个代码thread2.join(),那么thread1执行到这个代码时就会等待thread2的线程执行结束,才会继续往下执行。最多等待ms毫秒。
interrupt():从休眠sleep中中断线程,实质是给对应线程抛出一个异常,然后具体处理方式看这个线程的代码逻辑
注意,接受异常的线程如果不是wait状态,或者未捕获InterruptedException异常,那么就不会处理interrupt()
setDaemon():将线程设置为守护线程。当只剩下守护线程时,程序自动退出
守护线程:当其他线程都结束之后,守护线程就会结束,无论是不是在休眠sleep状态,无论守护线程是否执行完成
守护线程可以有多个
线程默认不是守护线程
注意设置守护线程,要在start前
锁
一个进程的多个线程会共享一个内存空间,所以可能存在读写冲突
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);
}
}
同步(Synchronized)
快捷实现锁的一个语法糖
写法1:将Synchronized加到代码块上
class Count {
public int cnt = 0;
}
class Worker extends Thread {
public final Count count;
// 或者也可以写一个额外的静态对象用来锁 private final static Object object = new Object(); synchronized (object) {}
public Worker(Count count) {
this.count = count;
}
@Override
public void run() {
synchronized (count) { // 注意,这里面锁住的对象需要是常量
// synchronized 会保证同一时间只有一个线程访问这块代码
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对象上)
implements Runnable这种写法,多个线程可以由同一个对象创建,所以更适合用来同步
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);
}
}
注意,下面这种写法就不会起作用
class Worker extends Thread {
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 worker1 = new Worker();
Worker worker2 = new Worker();
worker1.start();
worker2.start();
worker1.join();
worker2.join();
System.out.println(Worker.cnt);
}
}
因为synchronized本质锁的是this,即
private void work() {
synchronized (this) {
for (int i = 0; i < 100000; i ++ ) {
cnt ++ ;
}
}
}
那么,worker1和worker2的对象并不是一个,即this不是同一个,所以锁不起作用
wait与notify
wait(ms) 让线程等待一段时间,ms表示最大等待时间,超过之后会自动唤醒
notify() 唤醒等待中的一个线程,随机唤醒的
notifyAll()唤醒所有的线程
注意,即使是被唤醒,也需要保持同一时间synchronized 内代码只有一个线程在访问,其他被唤醒的线程需要等待正在访问synchronized 内代码的线程访问完成。
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(); // 线程就会卡死到这里,其他线程就可以执行synchronized里的代码
System.out.println(this.getName() + ": 被唤醒啦!");
} else {
object.notifyAll(); // 唤醒所有的线程 notify()是唤醒一个
}
} 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();
}
}