0%

Java基础

前言

学过 C/C++, 所以最基础部分就跳过啦~

基础知识

  • Java 是一门高级编程语言。Sun 于 1995 年,后被 Oracle 收购。

  • Java 技术体系:Java SE: 标准版、Java EE: 企业版、Java ME: 小型版

  • JDK: Java Development Kit 即开发工具包,目前常用的为 JDK-8,JDK-17 (Leetcode 的 Java 环境就是 17)

  • javac 是个编译工具,java 是个执行工具

  • .java 源代码 --(javac编译)--> .class 字节码文件 --> 使用 java 运行

    • jdk 11 可以直接使用 java 命令运行,后台会自动编译运行
  • JVM: Java Virtual Machine, 即 Java 虚拟机,运行 Java 程序的地方

  • JRE: Java Runtime Environment, Java 运行环境

  • JVM,JRE,核心类库 包含在 JDK 中

  • 跨平台性:class 文件在不同平台的 JVM 上运行

数据类型

  • 基本数据类型:4类8种 - 整型(byte, short, int, long),浮点型(float, double), 字符型,布尔型
  • 应用数据类型:字符串(String)

类型转换

  • 自动类型转换:范围小 --> 范围大
  • 表达式自动类型转换:范围小 --> 范围大,结果取决于最高类型
    • 表达式中 byte, short, char 直接强制转换为int参与运算
  • 强制类型转换:一般是大范围 --> 小范围

运算符

  • 基本算数运算符:+,-,*,,%
  • 自增自减运算:++,--、
  • 赋值运算符:=,+=,-=,*=,/=,%=
  • 关系运算符:>=, <=, >, <, ==, !=
  • 逻辑运算符:&,|,!,^,&&,||
    • && 与 &,| 与 || 的区别是单个的双边都会执行,双倍的有短路现象
    • && 优先级高于 ||
  • 位运算:>> 和 <<

程序流程控制

  • 分支语句
    • if () {} else if () {} else {}
    • switch () { case v1: break; default: ;} switch 只支持 byte, short, int, char, String 不支持 long, float, double
  • 循环结构
    • for(;;) {} 循环
    • while () {} 循环
    • do {} while() 循环
  • 控制关键字:continue,break

数组

1
2
3
4
5
6
7
8
9
10
11
// 数组的定义
int[] s = new int[]{1, 2, 3, 4};
int[] s = {1, 2, 3, 4};
int s[] = {1, 2, 3, 4}; // 不推荐这种写法
int[] s = new int[12]; // 动态初始化
// 获取长度
int n = s.length;
// 遍历数组
for (int i = 0; i < n; ++i) {
System.out.println(s[i]);
}

数组变量名存储的是数组在内存的地址,是一种引用数据类型

Java 内存分配

  • 方法区:.class 字节码文件
  • 栈:方法运行使用
  • 堆:new 的东西

方法

1
2
3
修饰符 返回值类型 方法名(形参表) {
代码;
}
  • 参数传递:值传递
  • 方法重载:方法名称相同,形参列表不同(不同关心形参具体名称)

对象

  • 面向对象编程:万物皆对象
  • 三大特征:封装、继承、多态
  • 一个java文件只能有一个 public 修饰的 class 类,且该类的类名必须与文件名相同
  • 构造方法:
    • 一个无返回值,与类名相同的方法,可以重载
    • 没有构造方法会默认生成个无参数的构造方法
    • 存在有参构造方法就没有默认的无参构造方法了
  • static :修饰变量或方法,表示类变量或类方法(静态变量、静态方法),同样保存的内存中的堆中

单例设计模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 单例设计模式
* 1、将构造方法私有
* 2、手动创建一个对象
* 3、设置取得对象的类方法
*/
public class SingleInstance {

public static void main(String[] args) {
SingleInstance si = SingleInstance.get();
System.out.println(si);
}
private SingleInstance() {
System.out.println("一个单例模式实例");
}

private static final SingleInstance si = new SingleInstance();

public static SingleInstance get() {
return si;
}
}

继承

子类可以继承父类的非私有成员

1
2
3
public class B extends A {
// ...
}

权限修饰符

修饰符 当前类 同一package下其他class 其他包的子类中 其他包其他类
private
缺省
protected
public
  • 方法可以重写,private,static 修饰的成员不能重写
  • 子类访问成员:就近有原则
  • 访问父类成员:使用 super 关键字
  • 子类构造函数会默认调用父类无参构造函数,如果没有无参构方法,必须手动调用

多态

  • 对象多态、行为多态:即父类可以指向子类对象,但表现方法不同
  • 技巧:编译看左边,执行看右边(但是成员变量只看左边)
  • 用处:父类变量作为形参,可以接受子类对象,但是要调用子类特有方法需要强制类型转换
1
2
3
4
5
// 安全的子类类型转换方法
A a = new C(); // 这里假设 B,C 都是 A 的一个子类
if (a instanceof B) {
B b2 = (B)a;
}
  • final
    • 修饰类:不能被继承
    • 修饰方法:不能被重写
    • 修饰变量:不能修改、必须初始化

抽象类

  • 即 abstract 修饰的类或方法
  • abstract 修饰的方法只有声明,没有具体实现,且有抽象方法只能再抽象类中
  • 抽象类不能实例化,只能继承,且子类重写所有抽象方法才能实例化,不然也是抽象类

接口类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public interface InterfaceDemo {
String NAME = "成员变量固定是常量(即 static final 修饰的)";
void method(); // 成员方法固定是抽象方法,即默认abstract修饰

// 不能有其他东西
// JDK 8 新增的三种方法

/**
* 1、默认方法,default修饰,默认是 public 的
* 需要使用实例对象访问
*/
default void method1() {

}

/**
* 2、私有方法,private 修饰(JDK 9 才支持)
* 同样是是对象方法,只能默认方法调用
*/
private void method2() {

}

/**
* 3、静态方法,static 修饰,默认是 public 的
* 是类方法
*/
static void method3() {

}
}

实现接口

1
2
3
4
5
6
class InterfaceImplements implements InterfaceDemo, InterfaceDemo2 {
@Override
public void method() {
// 必须实现全部抽象方法!
}
}
  • 接口可以继承接口,且可以继承多个接口(extends 继承)
    • 但是多个接口之间不能有冲突的方法声明
  • 类可以实现多个接口
    • 也不能有冲突方法声明
    • 不冲突的可以,但是要重写
  • 继承的方法与接口中有同名方法,先调用父类方法

内部类

类内再定义了个类,也没什么区别,但是实例化时要先实例化外部类

1
2
3
4
5
6
7
8
9
10
// 内部类
Outer.Inner in = new Outer().new Inner();
// 静态内部类
Outer.Inner in = new Outer.Inner();
// 匿名内部类(本质是一个子类,即继承外部类的方法)
new Outer() {
void method() {

}
}
  • 内部类可以访问外部类的成员和方法
    • 可以用外部类名指定访问外部类成员 (Outer.method())
  • 静态内部类的访问类似与静态方法

枚举类

1
2
3
4
5
enum EnumDemo {
a, b, c; // 第一行都是常量,不能实例化(构造方法私有的),不能继承

// 其他和普通的类没什么区别
}

简单来说,就是单例模式的拓展化,里面的常量其实就是枚举类的实例化,相当于固定了具体实例对象

泛型

简单来说,数据类型也是个变量,用 <E> 这样的方法定义个数据类型,可以手动传个类型,默认是 Object

一个更严格一点的约束方法 <E extends B> 那么数据类型 E 只能是 B 或者 B 的子类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 泛型类
*/
class Test<E> {
//...
// 一个泛型方法
public static <T> T method(T t) {
//...
return t;
}
// 一个通配的
public static void method(List<?> t) {
//...
}
}
  • 泛型只在编译阶段,编译好就没有了,称为泛型擦除
  • 泛型不支持基本类型,要用引用数据类型

常用API

这里不具体解释方法,详细内容看文档有更详细的内容

  • 传统时间类:Date, SimpleDateFormat, Calendar
  • JDK8新增的日期时间类:
    • LocalDate, localTime, LocalDateTime
    • ZoneId, ZoneDateTime
    • Instant (时间戳)
    • DateTimeFormatter
    • Duration, Period

Lambda表达式

主要是在排序函数中用到的,但不仅限于此,直接代码展示吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import java.util.Arrays;
import java.util.Comparator;
import java.util.Random;

public class LambdaDemo {
public static void main(String[] args) {
final int n = 5;
Random r = new Random();
People[] p = new People[n];
for (int i = 0; i < n; ++i) {
p[i] = new People(r.nextInt(0, 60), r.nextInt(150, 200), r.nextInt(40, 120));
}

System.out.println(Arrays.toString(p));

// 方法1:实现 Comparable 接口
System.out.println("按年龄升序");
Arrays.sort(p);
System.out.println(Arrays.toString(p));

// 方法2:创建 Comparator 比较器,但lambda表达式
System.out.println("按升高升序");
Arrays.sort(p, new Comparator<People>() {
@Override
public int compare(People o1, People o2) {
return o1.height - o2.height;
}
});
System.out.println(Arrays.toString(p));

// 方法3:还是比较器,但是简化lambda表达式
System.out.println("体重降序排序");
Arrays.sort(p, ((o1, o2) -> o2.wight - o1.wight));
System.out.println(Arrays.toString(p));

// 方法4:静态方法引用
System.out.println("体重升序排序");
Arrays.sort(p, ComparePeople::compare);
System.out.println(Arrays.toString(p));

// 方法4:实例方法引用
ComparePeople cp = new ComparePeople();
System.out.println("年龄降序排序");
Arrays.sort(p, cp::compare2);
System.out.println(Arrays.toString(p));

}
}

class ComparePeople {
public static int compare(People o1, People o2) {
return o1.wight - o2.wight;
}

public int compare2(People o1, People o2) {
return o2.age - o1.age;
}
}

class People implements Comparable<People> {
int age, height, wight;

public People(int age, int height) {
this.age = age;
this.height = height;
}

public People(int age, int height, int wight) {
this.age = age;
this.height = height;
this.wight = wight;
}

public People() {
}

@Override
public int compareTo(People o) {
return this.age - o.age;
}

@Override
public String toString() {
return "People{" +
"age=" + age +
", height=" + height +
", wight=" + wight +
'}';
}
}

正则表达式

正则匹配规则参考官方文档,关键字:Pattern

使用正则匹配:String.matches()

Stream

流式处理数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.util.*;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class StreamTest {
public static void main(String[] args) {
Random r = new Random();
List<Integer> list = new ArrayList<>();
int[] nums = new int[30];
for (int i = 0, n = r.nextInt(20, 50); i < n; ++i) {
list.add(r.nextInt(100));
}
for (int i = 0; i < 30; ++i) {
nums[i] = r.nextInt(100);
}

// Stream 流
List<Integer> out = list.stream().filter(x -> x >= 50).distinct().sorted().map(x -> x - 50).toList();
System.out.println(out);

IntStream sl = Arrays.stream(nums);
sl.forEach(x -> System.out.print(x + ", "));
}
}

异常处理

  • 异常的基类是 java.lang.Throwable,其子类有两个,一个 Error ,一个是 Exception
  • Error 可以不同管,Exception包含 RuntimeException 和其他异常(编译异常)
  • 使用 try {} catch (e) {} 来捕获处理异常,也可以不出来,抛到上层调用处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ExceptionTest {
public static void main(String[] args) throws ArithmeticException {

try {
for (int i = 10; i >= 0; --i) {
System.out.println(10 / i);
}
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
// 释放资源的代码,无论有没有异常都会执行(即使你try中间 return)
// 除非 JVM 崩溃啦 >_<
}
}
}

也可以自定义异常类

1
2
3
4
5
6
7
8
9
class ExampleRuntimeException extends RuntimeException {
public ExampleRuntimeException() {
}

public ExampleRuntimeException(String message) {
super(message);
}
}

文件读写

首先是文件或文件夹的路径的读写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import java.io.*;
import java.util.*;

public class FileTest {
public static void main(String[] args) {
File f = new File(".");
System.out.println(f);
if (f.isFile()) {
System.out.println("这是一个文件" + f.getName());
}
else if (f.isDirectory()) {
System.out.println("这是一个路径" + f.getAbsolutePath());
}
System.out.println( f.getAbsolutePath() + "路径下有 " + dfs(f) + " 个文件");

}

public static int dfs(File f) {
File[] nxts = f.listFiles();
if (nxts == null) return 0;
int cnts = 0;
for (File nxt : nxts) {
if (nxt.isFile()) {
System.out.println(nxt.getAbsolutePath());
++cnts;
}
else {
cnts += dfs(nxt);
}
}
return cnts;
}
}

这是一个简单文件读取的示例,下面展示数据的编码和解码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import java.util.Arrays;

public class EncodeDecode {
public static void main(String[] args) throws Exception {
String s = "这是一个字符串string";

// UTF-8: 汉字 3 字节,英文数字标点 1 字节
// GBK: 汉字 2 字节,英文数字标点 1 字节

// 编码
byte[] b = s.getBytes();
System.out.println(b.length); // 27
System.out.println(Arrays.toString(b));

byte[] b2 = s.getBytes("GBK");
System.out.println(b2.length); // 20
System.out.println(Arrays.toString(b2));

// 解码
String s1 = new String(b);
System.out.println(s1);
// 这是一个字符串string

String s2 = new String(b, "GBK");
System.out.println(s2);
// 杩欐槸涓�涓瓧绗︿覆string

String s3 = new String(b2);
System.out.println(s3);
// ����һ���ַ���string

String s4 = new String(b2, "GBK");
System.out.println(s4);
// 这是一个字符串string
}
}

IO流

包含两种,一种是字节流,一种是字符流,分布是InputStream/OutputStreamReader/writer,不过这俩都是抽象类,常用的实现类是前面加个 File.

看看示例吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package com.szu.test;

import java.io.*;

public class IOStream {
public static void main(String[] args) throws Exception {
String path = "./Java-Study/src/text.txt";
String opath = "./Java-Study/src/out.txt";

/* 字节流
InputStream is = new FileInputStream(path);
int b ;
// 一个一个字节读取
while ((b = is.read()) != -1) {
System.out.print((char)b);
}
System.out.println();
is.close();
*/

// 一次读完全部字节
// 用 缓存流包装下效率更高,其他三种其实也可以包装
InputStream is = new BufferedInputStream(new FileInputStream(path));
byte[] buffer = is.readAllBytes();
System.out.println(new String(buffer));
is.close();

// 写到新文件
// 用 缓存流包装下效率更高,其他三种其实也可以包装
OutputStream os = new BufferedOutputStream(new FileOutputStream(opath, true));
os.write(buffer);
os.write("\r\n".getBytes());
os.close();

// 字符流
System.out.println("===字符流===");
// try with resource -> 可以自动关闭流
try (Reader r = new FileReader(path)) {
char[] rc = new char[1024];
int len;
while ((len = r.read(rc)) != -1) {
System.out.print(new String(rc, 0, len));
}
} catch (Exception e) {
throw e;
}

// 写字符
try (Writer w = new FileWriter(opath, true)) {
w.write("输出一些东西!\r\n");
w.flush();// 必须刷新或者关闭才会将缓冲区内容写到文件哦~
} catch (Exception e) {
throw e;
}

// 一种更高级的输出流(也有对应的字节流
try (
PrintWriter pw = new PrintWriter(new FileOutputStream(opath, true))
) {
pw.write("This is some words");
pw.write(10086);
pw.write('\n');
} catch (Exception e) {
throw e;
}

/* 输出重定向
try (
PrintStream ps = new PrintStream(opath);
) {
// 输出重定向

} catch (Exception e) {
throw e;
}*/
}
}

多线程

线程创建和常用方法

看代码和注释吧~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class ThreadTest {
public static void main(String[] args) throws Exception {
// 线程有两种办法
// 1. 继承 Thread 并重写 run 方法
// 2. 实现 Runnable 接口,并实现 run 方法
// 3. 实现 Callable 接口,实现 call 方法,并封装成 FutureTask (可以返回线程执行结果)

// 方法一
Thread t1 = new PrintNumber(1, "T1");
Thread t2 = new PrintNumber(2, "T2");
t1.start();
t2.start();
t1.join(); // 等 t1 运行完
t2.join(); // 等 t2 运行完

// 方法二
Sound s1 = new Sound("喵");
Sound s2 = new Sound("汪");
Thread t3 = new Thread(s1);
Thread t4 = new Thread(s2);
t3.start();
t4.start();

// 方法三
Eat e1 = new Eat("苹果");
Eat e2 = new Eat("香蕉");
FutureTask<String> ft1 = new FutureTask<>(e1);
FutureTask<String> ft2 = new FutureTask<>(e2);
Thread t5 = new Thread(ft1);
Thread t6 = new Thread(ft2);
t5.start();
t6.start();
// 获取线程返回结果
String r1 = ft1.get();
String r2 = ft2.get();
System.out.println(r1);
System.out.println(r2);

// 获取当前线程 - 此处即主线程
Thread tm = Thread.currentThread();
System.out.println("主线程名为: " + tm.getName());
}
}


class PrintNumber extends Thread {
// 打印奇数或偶数
int val;

public PrintNumber(int val, String name) {
super(name);
this.val = val;
}

public PrintNumber(int val) {
this.val = val;
}

@Override
public void run() {
System.out.print(this.getName() + "开始执行\n");
for (int i = val; i < 20; i += 2) {
System.out.print(i + ", ");
}
}
}

class Sound implements Runnable {
// 喵喵喵?
public String s;

public Sound(String s) {
this.s = s;
}

@Override
public void run() {
for (int i = 0; i < 5; ++i) {
System.out.print(s);
}
}
}

class Eat implements Callable<String> {
// 泛型变量填返回数据类型
private String food;

// 吃东西啦~
public Eat(String food) {
this.food = food;
}

@Override
public String call() throws Exception {
Random r = new Random();
int i = 0, n = r.nextInt(2, 6);
for ( ;i < n; ++i) {
System.out.println("吃 " + food);
}
return "吃了 " + n + " 个 " + food;
}
}

线程同步

同步代码块

同步代码块,将共享代码包装一下,需要传入一个对象作为锁(建议共享资源作为锁),实例对象建议使用当前对象作为锁,如果是静态方法建议使用 类名.class 作为锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

public class SynchronizeTest {
public static void main(String[] args) throws Exception {
Number n1 = new Number(1);
Thread t1 = new Thread(n1);
Thread t2 = new Thread(n1);

t1.start();
t2.start();

t1.join();
t2.join();
// 不加锁输出结果大多数情况都是 -1
System.out.println(n1.getVal());
}
}


class Number implements Runnable {
private int val;

public Number(int val) {
this.val = val;
}

public int getVal() {
return val;
}

public void setVal(int val) {
this.val = val;
}

@Override
public void run() {
// 若 val > 0 则将 val - 1
// 同步代码块,将共享代码包装一下,需要传入一个对象作为锁(建议使用当前对象作为锁,如果是静态方法建议使用 类名.class 作为锁
synchronized (this) {
if (val > 0) {
// 执行太快了看不出问题,这里停一下
try {
Thread.sleep(200);
} catch (Exception e) {
System.out.println("Exception");
}
val--;
}
}
}
}

同步方法

同步方法,即将整个方法加锁,内部隐含的其实就是前面同步代码块推荐的锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Number implements Runnable {
private int val;

public Number(int val) {
this.val = val;
}

public int getVal() {
return val;
}

public void setVal(int val) {
this.val = val;
}

@Override
public synchronized void run() {
// 给方法加锁,即使用 synchronized 关键字修饰
// 若 val > 0 则将 val - 1
if (val > 0) {
// 执行太快了看不出问题,这里停一下
try {
Thread.sleep(200);
} catch (Exception e) {
System.out.println("Exception");
}
val--;
}
}
}

Lock锁

自行定义一个锁对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Number implements Runnable {
private int val;
private final Lock lk = new ReentrantLock(); // 加上 final 保证锁唯一

public Number(int val) {
this.val = val;
}

public int getVal() {
return val;
}

public void setVal(int val) {
this.val = val;
}

@Override
public void run() {
// 若 val > 0 则将 val - 1
lk.lock(); // 加锁
try {
if (val > 0) {
// 执行太快了看不出问题,这里停一下
try {
Thread.sleep(200);
} catch (Exception e) {
System.out.println("Exception");
}
val--;
}
} finally {
// 放到 finally 防止代码执行出错导致没有解锁
lk.unlock(); // 解锁
}

}
}

线程间通信

主要是 waitnotify , notifyAll 三个 Object 自带的方法

注意上述三个方法必须要有锁的时候才能调用(即前面三种方法加锁)否则会报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class ThreadCommunication {
public static void main(String[] args) {
Env e = new Env();

// 生产者
Thread p = new Thread(() -> {
while (true) e.produce();
}, "productor");
// 消费者
Thread c = new Thread(() -> {
while (true) e.consume();
}, "consumer");

p.start();
c.start();
}
}


class Env {
private int n = 0;

public Env() {
}

public synchronized void consume() {
String cur = Thread.currentThread().getName();
try {
if (n > 0) {
--n;
System.out.println(cur + " 消费~~~" + "(剩余 " + n + " 资源)");
Thread.sleep(10);
}
this.notifyAll();
this.wait();
} catch (Exception e) {
e.printStackTrace();
}
}

public synchronized void produce() {
String cur = Thread.currentThread().getName();
try {
if (n == 0) {
++n;
System.out.println(cur + " 生产~~~" + "(剩余 " + n + " 资源)");
Thread.sleep(10);
}
this.notifyAll();
this.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
}

线程池

有一堆线程,然后有个任务队列,依次从队列中取任务使用线程运行

说说参数吧

1
2
3
4
5
6
7
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
  • corePoolSize: 核心线程数量,即长期存在的线程数量
  • maximumPoolSize: 最大线程数量,要比核心线程数量多,多出的部分是临时线程,即只存在一段时间的线程
    • 当核心线程占满、任务队列满了,才会开临时线程,新的的任务会直接到临时线程中运行(即不进队列而是越过队列中的任务直接运行)
    • 如果还有任务来把临时线程也占满了,就会使用后面的拒绝策略
    • 开了临时线程后会队列中的任务会按需分配到既有的空闲线程,不分核心和临时
  • keepAliveTime: 临时线程存活时间
  • unit: 存活时间单位,是个枚举类
  • workQueue: 工作队列,一般由链表(无限大)和数组(优先大小)两种
  • threadFactory: 生产线程的工程,暂时用默认的就好
  • handler: 任务队列和临时进程都满了时候还有新任务来时使用的策略

下面是使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import java.util.concurrent.*;

public class ThreadPoolTest {
public static void main(String[] args) throws Exception {
// 1、通过 ThreadPoolExecutor 创建线程池对象 (是 ExecutorService 的一个实现类)
ExecutorService es = new ThreadPoolExecutor(3, 5, 8,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(4),
Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());

// 运行 Runnable 示例
Runnable target = new Sound("喵");
Runnable target2 = new Sound("汪");
Runnable target3 = new Sound("啾");
Runnable target4 = new Sound("饿饿,饭饭");
es.execute(target); // 线程池会自动创建新线程,自动处理任务,自动执行
es.execute(target); // 线程池会自动创建新线程,自动处理任务,自动执行
es.execute(target); // 线程池会自动创建新线程,自动处理任务,自动执行
es.execute(target2); // 只有 3 个核心线程,所以会进入任务队列等着
es.execute(target2); //
es.execute(target2); //
es.execute(target2); // 4 个任务队列也满啦,下一个再有任务就会开临时进程
// 4 个任务队列也满啦,开临时进程
es.execute(target3);
es.execute(target3);
// 临时进程也满了,按照 ThreadPoolExecutor.AbortPolicy() 策略会丢弃任务并抛异常(RejectedExecutionException)
// 临时进程也满了,按照 ThreadPoolExecutor.CallerRunsPolicy() 策略会使用主线程执行任务
es.execute(target4);

// 运行 Callable 任务示例
Future<String> f1 = es.submit(new Eat("橘子"));
Future<String> f2 = es.submit(new Eat("葡萄"));
System.out.println(f1.get()); // 获取 Feature 得到的返回结果
System.out.println(f2.get());

es.shutdown(); // 等线程池任务执行完后关掉线程池
// es.shutdownNow(); // 立刻关闭线程池!
}
}

网络编程

主要都在 java.net.* 包下。

  • 基础架构:CS架构(客户端、服务端),BS架构(浏览器、服务端)
  • UDP: 无连接,不可靠
  • TCP: 有连接,可靠通信

UDP通信

客户端发送数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class Client {
public static void main(String[] args) throws Exception {
// 1. 客户端对象
DatagramSocket socket = new DatagramSocket();

// 2. 数据包对象
byte[] bytes = "一条来自客户端的数据!".getBytes();
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getLocalHost(), 6666);

// 3. 发生数据
socket.send(packet);

System.out.println("客户端数据发生完毕!");
socket.close();
}
}

服务端接受数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class Server {
public static void main(String[] args) throws Exception {
// 1. 创建服务端对象
DatagramSocket socket = new DatagramSocket(6666);

// 2. 创建数据对象
byte[] buffer = new byte[1024 * 64]; // 64 KB
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);

// 3. 接受数据
socket.receive(packet);

// 4. 读取数据
String rs = new String(buffer, 0, packet.getLength());
System.out.println("收到了来自 " + packet.getAddress() + " (端口号为 " + packet.getPort() + ")发送的数据:" +rs);

socket.close();
}
}

TCP 通信

客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

public class Client {
public static void main(String[] args) throws Exception {
TCPClient();
}

public static void TCPClient() throws Exception {
// TCP 客户端
// 创建 Socket 对象,请求与服务端的程序的连接
Socket socket = new Socket("127.0.0.1", 8888);
System.out.println("客户端已启动!");

// 从 socket 通信管道中得到一个字节输出流,用于发送数据
OutputStream os = socket.getOutputStream();

// 包装输出流
DataOutputStream dos = new DataOutputStream(os);

Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请输入要发送的消息:");
String msg = sc.nextLine();
if ("exit".equals(msg)) break;

// 发送数据
dos.writeUTF(msg);

}

dos.close();
socket.close();
}
}

服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
public static void main(String[] args) throws Exception {
// UTPServer();
TCPServer();
}

public static void TCPServer() throws Exception {
// TCP 服务端
// 创建服务端对象,并注册服务端端口
ServerSocket ss = new ServerSocket(8888);

// 调用 accept 方法等待连接请求
System.out.println("服务端已启动,等待连接...");
Socket socket = ss.accept();

// 从通信管道得到字节输入流
InputStream is = socket.getInputStream();

// 包装输入流
DataInput dis = new DataInputStream(is);

while (true) {
// 读取收到的消息
try {
String rs = dis.readUTF();
System.out.println("收到来自 " + socket.getRemoteSocketAddress() + " 的消息:" + rs);
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress() + " 已经退出,连接关闭!");
break;
}
}

ss.close();
}
}

使用多线程改造后的服务端,可以与多个客户端连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
public static void main(String[] args) throws Exception {
MultiTCPServer();
}

public static void MultiTCPServer() throws Exception {
// TCP 服务端(多线程版)
// 主线程只负责接受连接,接受消息交给子线程
// 创建服务端对象,并注册服务端端口
ServerSocket ss = new ServerSocket(8888);

// 调用 accept 方法等待连接请求
System.out.println("服务端已启动,等待连接...");

while (true) {
Socket socket = ss.accept();
System.out.println("收到" + socket.getRemoteSocketAddress() + " 连接,开始通信!");
new ServerReadThread(socket).start();
}

// ss.close();
}
}

class ServerReadThread extends Thread {
private Socket socket;

public ServerReadThread(Socket socket) {
this.socket = socket;
}

@Override
public void run() {
// 从通信管道得到字节输入流
try {
InputStream is = socket.getInputStream();

// 包装输入流
DataInput dis = new DataInputStream(is);

while (true) {
// 读取收到的消息
try {
String rs = dis.readUTF();
System.out.println("收到来自 " + socket.getRemoteSocketAddress() + " 的消息:" + rs);
} catch (IOException e) {
System.out.println(socket.getRemoteSocketAddress() + " 已经退出,连接关闭!");
break;
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

反射

可以从字节码获取到类的信息,通常在框架中对类做一些通用操作用得多一些吗,具体实现可以看看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;

public class TestClass {
public static void main(String[] args) throws Exception {

// 反射第一步,获取 Class 对象,有三种方法
System.out.println("========== 反射第一步,获取 Class 对象 ==========");
Class c1 = Student.class;
System.out.println(c1.getName());
System.out.println(c1.getSimpleName());

Class c2 = Class.forName("com.szu.test.Reflect.Student");
System.out.println(c1 == c2);

Student s3 = new Student();
Class c3 = s3.getClass();
System.out.println(c3 == c1);

// 获取构造器
System.out.println("========== 获取构造器 ==========");
// Constructor[] constructors = c1.getConstructors(); // 只能得到 public 的构造器
Constructor[] constructors = c1.getDeclaredConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor.getName() + " -> " + constructor.getParameterCount() + " -> " + Arrays.toString(constructor.getParameterTypes()));
}
// 获取特定构造器
Constructor constructor = c1.getDeclaredConstructor(String.class, int.class);
System.out.println(constructor.getName() + " -> " + constructor.getParameterCount() + " -> " + Arrays.toString(constructor.getParameterTypes()));

// 初始化对象
System.out.println("========== 初始化对象 ==========");
// constructor.setAccessible(true); // 禁止访问检查(暴力反射):可以调用 private 的构造方法
Student s = (Student) constructor.newInstance("YanLili", 18);
System.out.println(s);

// 获取成员变量 (同理不加 Declared 只能得到 public 变量)
System.out.println("========== 获取成员变量 ==========");
Field[] fields = c1.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getName() + " -> " + field.getType());
}
// 获取特定成员变量
Field field = c1.getDeclaredField("age");
// 赋值和取值
field.setAccessible(true); // 暴力反射
System.out.println(field.getName() + " -> " + field.getType() + " -> " + field.get(s));
field.set(s, 19);
System.out.println(field.getName() + " -> " + field.getType() + " -> " + field.get(s));

// 获取成员方法 (同理不加 Declared 只能得到 public 方法)
System.out.println("========== 获取成员方法 ==========");
Method[] methods = c1.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName() + " -> " + Arrays.toString(method.getParameterTypes()) + " -> " + method.getReturnType());
}
// 获取特定方法
Method method = c1.getDeclaredMethod("getName");
System.out.println(method.getName() + " -> " + Arrays.toString(method.getParameterTypes()) + " -> " + method.getReturnType());
// 执行
System.out.println(method.invoke(s));


}
}

注解

就是类似 @Override 这样的东西,自定义方法:

1
2
3
4
5
6
public @interface 注解名称 {
public 属性类型 属性名() default 默认值;
}

// 使用方法
@属性名称(属性名=值)

如果只有一个叫 value 的注解,使用可以不写属性名

  • 注解本质上其实个继承了 Annotation 的接口,里面的数学都是抽象方法,使用时就是一个实现类对象

元注解

即注解的注解,主要有两个

  • @Target(): 声明注解只能用哪里
  • @Retention() :声明注解的保留周期

注解的功能,大概就是结合前面的反射做些通用性的事情,是一个标记

--- ♥ end ♥ ---

欢迎关注我呀~