Friedy's blog Friedy's blog
首页
  • 《Vue》笔记
  • 《JavaScript教程》笔记
  • 小程序笔记
  • 《CSS》笔记
  • 《HTML》笔记
  • 《Java》笔记
  • 《SpringBoot》笔记
  • 《SpringCloud》笔记
  • 机器学习笔记
  • 深度学习知识点总结
  • 大模型学习笔记
  • 面试经验
  • 友情链接
关于
  • 网站
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Friedy

全栈攻城狮
首页
  • 《Vue》笔记
  • 《JavaScript教程》笔记
  • 小程序笔记
  • 《CSS》笔记
  • 《HTML》笔记
  • 《Java》笔记
  • 《SpringBoot》笔记
  • 《SpringCloud》笔记
  • 机器学习笔记
  • 深度学习知识点总结
  • 大模型学习笔记
  • 面试经验
  • 友情链接
关于
  • 网站
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 基础
  • 技巧
  • 进阶
  • 高级
    • 多线程
      • 名词释义
      • 多线程实现
      • 线程的生命周期
      • 线程的优先级
      • 线程的调度模型
      • 线程安全
      • 守护线程
      • 定时器
      • Object 中的 wait 方法和 notify 方法
      • 获取文件绝对路径
    • 资源绑定器
    • 反射
      • 反射的优点
      • 反射的缺点
    • 注解
    • 面向对象思想设计原则
    • 设计模式
  • 《Java》笔记
friedy37
2024-06-09
目录

高级

# 多线程

# 名词释义

  • 程序:为完成特定任务,用多种语言编写的一组指令的集合,即指一段静态的代码,静态对象
  • 进程:程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程
  • 线程:进程可进一步细化为线程,是一个程序内部的一条执行路径,同时它也是程序使用 CPU 的最基本单位(进程中要同时干几件事情,每一件事情的执行路径就是线程)
  • 并行:多个 CPU 同时执行多个任务,可理解为多个人同时做不同的事
  • 并发:一个 CPU 同时执行多个任务,可理解为多个人做同一件事
  • 单线程:一个进程只用一条执行路径
  • 多线程:一个进程有多条执行路径

注:

  • 线程是依赖于进程而存在的,只有运行的程序才会出现进程
  • 多进程的意义在于提高 CPU 的使用率
  • 多线程的意义在于提高程序的处理效率
  • 不同的进程内存独立不共享
  • 在内存当中多个线程共享堆内存和方法区内存,而每一个线程都有自己的栈内存,栈内存不共享,一个线程一个栈
  • Java 程序运行原理:Java 命令会启动 JVM,JVM 可看作是一个应用程序,等同于启动了一个应用程序,也就是启动了一个进程,该进程会自动启动一个主线程,然后主线程会调用某个类的 main 方法,所以 main 方法运行在主线程中,然后再启动垃圾回收线程用来看护主线程,JVM 至少启动了这两个线程,所以 JVM 是多线程的

# 多线程实现

方式一:编写一个类,直接继承 Thread 类并重写 run() 方法

public class lianxi_02
{
	public static void main(String[] args)
	{
		//创建线程对象
		MyThread mt = new MyThread();
		
		mt.start();//启动线程
	}
}

//自定义类继承Thread类
class MyThread extends Thread
{
	//在自定义类中重写run()方法
	public void run()
	{
		//被线程执行的代码
		...
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  • 在继承 Thread 类的类中只有 run() 方法中的代码才会被线程执行
  • 直接调用 run() 方法就是普通方法调用
  • start() 方法的作用:启动一个分支线程,在 JVM 中开辟一个新的栈空间,只要新的栈空间开出来了,start() 方法就结束了,启动成功的线程会自动调用 run() 方法,并且 run() 方法在分支栈的栈底部(压栈),这时 main() 方法在主栈的底部,所以 run() 方法与 main() 方法是平级的

方式二:编写一个类,实现 Runnable 接口并重写 run() 方法

public class lianxi_02
{
	public static void main(String[] args)
	{
		//创建线程对象
		MyThread mt = new MyThread();
		
		//通过线程对象创建Thread对象
		Thread t = new Thread(mt);
		
		t.start();//启动线程
		
		
	}
}

//自定义类实现Runnable接口
class MyThread implements Runnable
{
	//在自定义类中重写run()方法
	public void run()
	{
		//被线程执行的代码
		
	}
}
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

在上述两种方式中方式二较为常用,因为方式二避免了 Java 单继承带来的局限性,适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码、数据有效分离,较好的体现了面向对象的设计思想(低耦合、高内聚)

1、返回当前正在执行的线程对象
public static Thread currentThread();

2、获取线程对象的名字
public final String getName();

3、更改线程对象的名字
public final void setName(String name);
1
2
3
4
5
6
7
8

方式三:实现 Callable 接口(JDK8 新特性)

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 实现线程的第三种方式:实现Callable接口
 * 
 * 这种方式的优点是:可以获取到线程的执行结果
 * 这种方式的缺点是:在获取线程结果时容易引起当前线程阻塞,效率较低
 */
public class Lianxi_02
{
	public static void main(String[] args)
	{
		MyThread1 mt = new MyThread1();
		
		//创建一个“未来类”对象
		FutureTask task = new FutureTask(mt);
		
		//创建线程对象
		Thread t = new Thread(task);
		
		t.start();
		
		
		try{
			
			//通过get()方法获取线程返回值
			System.out.println(task.get());
		} catch (InterruptedException e){
			e.printStackTrace();
		} catch (ExecutionException e){
			e.printStackTrace();
		}
		
		//get()方法会导致当前线程阻塞,所以此方法效率比较低
		//因为get()方法需要等线程结束后拿到线程返回值
		//所以main()方法这里的代码需要等get()方法结束才能执行,也就是要等以上线程结束后才执行
		System.out.println("结束了");
	}
}

//实现Callable接口
class MyThread1 implements Callable<String>
{
	
	//这里的call()方法就相当于run()方法
	public String call() throws Exception
	{
		String str = "hhhh";
		Thread.sleep(5000);
		System.out.println("2222");
		Thread.sleep(5000);
		System.out.println("3333");
		return str;
	}
	
}
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

# 线程的生命周期

线程的生命周期总共有五个状态:

  1. 新建
  2. 就绪
  3. 运行
  4. 阻塞
  5. 死亡

  • 让当前线程进入休眠,进入 “阻塞状态”,放弃 CPU 时间片,让给其它线程使用,跟调用的对象无关,因为是静态只跟出现的位置有关,出现在哪个线程中,就使哪个线程睡眠(注:静态方法看位置,实例方法就看对象)
public static void sleep(long millis);
1
  • 该方法会让当前线程出异常,从而达到中断线程的效果,可用它来唤醒睡眠的线程
public void interrupt();
1
  • 让位方法,暂停当前正在执行的线程对象,并执行其他线程,注意:yield()方法不是阻塞方法,它是让线程从 “运行状态” 回到“就绪状态”,回到就绪状态的线程有可能再次抢到 CPU 时间片,所以有时让位效果不明显
public static void yield();
1
  • 合理地终止(结束)一个线程的执行
public class ThreadTest
{
	public static void main(String[] args)
	{
		MyThread2 mt = new MyThread2();
		Thread th = new Thread(mt);

		th.start();
		
		//过5秒后
		try{
			Thread.sleep(5000);
		} catch (InterruptedException e){
			e.printStackTrace();
		}
		mt.run = false;//改变run属性,通过run的值来控制线程执行
		System.out.println("终止成功");
	}
}

class MyThread2 implements Runnable
{
	// 打一个布尔标记
	boolean run = true;

	public void run()
	{

		for (int i = 0; i < 10; i++)
		{
			if (run)
			{
				System.out.println(Thread.currentThread().getName() + "---->" + i);
				try{
					Thread.sleep(1000);
				} catch (InterruptedException e){
					e.printStackTrace();
				}
			} else//如果run属性为false就结束run()方法,意味着线程结束了
			{
				return;
			}

		}
	}
}
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
  • 合并线程
public final void join();
1
在以下代码,合并线程的作用是:t1线程进入阻塞状态,t2线程执行,直到t2线程结束,t1线程才可以正常执行

public class Lianxi_02
{
	public static void main(String[] args)
	{
		Thread t1 = new Thread(new Myth1());
		t1.setName("t1");
		t1.start();
	}
}

class Myth1 implements Runnable
{
	public void run()
	{
		Thread t2 = new Thread(new Myth2());
		
		t2.setName("t2");
		//启动t2线程
		t2.start();
		
		//合并t2
		try{
			t2.join();//t1进入阻塞,t2执行
		} catch (InterruptedException e){
			
			e.printStackTrace();
		}
		
		//t1的线程代码
		for(int i = 0; i < 5; i++)
		{
			System.out.println(Thread.currentThread().getName() + "---->" + i);
		}
	}
}

class Myth2 implements Runnable
{
	public void run()
	{
		//t2的线程代码
		for(int i = 0; i < 5; i++)
		{
			System.out.println(Thread.currentThread().getName() + "---->" + i);
		}
	}
}

执行结果:
t2---->0
t2---->1
t2---->2
t2---->3
t2---->4
t1---->0
t1---->1
t1---->2
t1---->3
t1---->4
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

# 线程的优先级

  • 线程默认优先级是 5
  • 线程最高优先级是 10
  • 线程最低优先级是 1
1、返回线程对象的优先级
public final int getPriority();

2、更改线程的优先级
public final void setPriority(int newPriority);
1
2
3
4
5

线程优先级高仅仅表示线程获取到 CPU 时间片的概率高

# 线程的调度模型

  • 抢占式调度模型:优先级越高的线程抢到 CPU 时间片的概率就越大,Java 采用的就是抢占式调度模型
  • 均分式调度模型:平均分配 CPU 时间片,每个线程占有的 CPU 时间片时间长度一样

# 线程安全

判断一个程序是否可能会有线程安全问题:

  1. 是否是多线程环境
  2. 是否有共享数据
  3. 是否有多个线程操作共享数据

可以理解为:当有多个线程同时操作同一数据对象时(线程并发),就容易导致数据状态错误的情况,这时的数据就不安全了

以下代码体现了线程安全问题

/**
 * 线程安全示例:
 * 多个用户同时从同一个账户中取钱
 *
 */
public class Lianxi_02
{
	public static void main(String[] args)
	{
		//创建一个账户对象(共享数据)
		Account ac = new Account(10000);
		
		//创建两个线程对象(多线程环境)
		Thread t1 = new Thread(new User(ac, "用户1"));
		Thread t2 = new Thread(new User(ac, "用户2"));
		
		t1.start();
		t2.start();
	}
}

//账户类
class Account
{
	
	private double money;
	
	
	public Account(double money)
	{
		this.money = money;
	}
	
	public double getMoney()
	{
		return this.money;
	}
	
	public void setMoney(double money)
	{
		this.money = money;
	}
	
	//取款(操作同一账户)
	public void Withdrawal(double money)
	{
		//取之前的余额
		double before = this.money;
		//取之后的余额
		double after = before - money;
		
		//这里让线程睡眠1s,就一定会出现线程安全问题
		try{
			Thread.sleep(1000);
		} catch (InterruptedException e){
			
			e.printStackTrace();
		}
		//设置取之后的余额
		this.setMoney(after);
		
	}
	
	
}

//用户类
class User implements Runnable
{
	private Account ac;
	private String name;
	
	public User(Account ac, String name)
	{
		this.name = name;
		this.ac = ac;
	}
	public void run()
	{
		ac.Withdrawal(5000);
		System.out.println(this.name + "已取款完成,余额为" + ac.getMoney());
	}
}
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

Java 提供了线程同步机制(synchronized)用于解决线程安全问题,其思想是:把多条语句操作共享数据的代码包装成一个整体,让某个线程在执行的时候,其他线程不能执行

线程同步实际上就是让线程不能并发了,必须排队执行

synchronized 的三种用法:

  1. 同步代码块(灵活)
synchronized(这里填的是想要同步的线程(也就是想要排队的线程)所共享的对象) {
	//需要同步的代码块(这部分的代码块越少程序执行效率就越高)
}
1
2
3
//取款(操作同一账户)
public void Withdrawal(double money)
	{
		synchronized(this) {//这里的锁对象是账户对象,在这个类中就是this
			//取之前的余额
			double before = this.getMoney();
			//取之后的余额
			double after = before - money;
			
			try{
				Thread.sleep(1000);
			} catch (InterruptedException e){
				
				e.printStackTrace();
			}
			
			//设置取之后的余额
			this.setMoney(after);
		}
			
	}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  1. 在实例方法上使用 synchronized,表示共享对象一定是 this,且同步代码块是整个方法体(为了保护实例变量的安全),使用有局限性,但简化了代码
//取款(操作同一账户)
	public synchronized void Withdrawal(double money)
	{
			//取之前的余额
			double before = this.getMoney();
			//取之后的余额
			double after = before - money;
			
			try{
				Thread.sleep(1000);
			} catch (InterruptedException e){
				
				e.printStackTrace();
			}
			
			//设置取之后的余额
			this.setMoney(after);
			
	}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  1. 在静态方法上使用 synchronized,表示锁对象是类锁(字节码文件对象),类锁永远只有一把(为了保护静态变量的安全)

注:

  • 局部变量永远都不会存在线程安全问题,因为局部变量在栈中不共享,一个线程一个栈
  • 实例变量在堆内存中,静态变量在方法区内存中,堆内存和方法区内存都是多线程共享的,所以可能存在线程安全问题
  • 同步机制虽然可以解决数据安全问题,但其缺点在于当线程相当多时,因为每个线程都会去判断同步上的锁对象,极其耗费资源,无形中会降低程序的运行效率

synchronized 在开发中最好不要嵌套使用,可能会导致死锁(指两个或两个以上的线程在执行的过程中,因争夺资源产生的一种相互等待现象),

以下代码是死锁示例

public class Lianxi_02
{
	public static void main(String[] args)
	{
		Object o1 = new Object();
		Object o2 = new Object();
		
		Thread t1 = new Myth1(o1, o2);
		Thread t2 = new Myth2(o1, o2);
		
		t1.start();
		t2.start();
	}
}

class Myth1 extends Thread
{
	private Object o1;
	private Object o2;
	
	public Myth1(Object o1, Object o2)
	{
		this.o1 = o1;
		this.o2 = o2;
	}
	
	public void run()
	{
		synchronized(o1) {
			try{
				Thread.sleep(1000);
			} catch (InterruptedException e){
				
				e.printStackTrace();
			}
			synchronized(o2) {
				
			}
		}
	}
}

class Myth2 extends Thread
{
	private Object o1;
	private Object o2;
	
	public Myth2(Object o1, Object o2)
	{
		this.o1 = o1;
		this.o2 = o2;
	}
	
	public void run()
	{
		synchronized(o2) {
			try{
				Thread.sleep(1000);
			} catch (InterruptedException e){
				e.printStackTrace();
			}
			synchronized(o1) {
				
			}
		}
	}
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

解决线程安全问题的方案:

  1. 尽量使用局部变量代替实例变量和静态变量
  2. 如果必须是实例变量,那么可以考虑创建多个对象,一个线程一个对象,这样实例变量的内存就不共享了
  3. 如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择 synchronized 同步机制了

# 守护线程

Java 语言中线程分为两大类:

  1. 用户线程(如主线程 main 方法)
  2. 守护线程(后台线程,如垃圾回收线程)

守护线程的特点:一般的守护线程是一个死循环,所有的用户线程只要结束,守护线程就自动结束

将该线程标记为守护线程或用户线程,当正在运行的线程都是守护线程时,Java 虚拟机退出。

java.lang.Thread中的方法

public final void setDaemon(boolean on);//true表示守护线程,false表示用户线程
1
2
3

# 定时器

定时器的作用:间隔特定的时间,执行特定的程序

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class ThreadTest
{
	public static void main(String[] args)
	{
		//创建定时器对象
		Timer ti = new Timer();
		
		//创建定时任务对象
		TimerTask task = new MyThread();
		
		//任务第一次执行的时间
		Date firstTime = new Date();
		
		//任务task从时间date开始执行,每隔2000ms执行一次
		ti.schedule(task, firstTime, 2000);
		
	}
}


//定时任务类
class MyThread extends TimerTask
{
	//指定定时任务run()代码块
	public void run()
	{
		System.out.println("hhhh");
	}
}
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

# Object 中的 wait 方法和 notify 方法

wait() 和 notify() 不是线程对象的方法,是 Java 中任何一个 Java 对象都有的方法,因为这两个方法是 Object 类中自带的

  • wait():让对象上活动的线程进入等待状态,并释放对象锁
public final void wait();
1
  • notify():唤醒对象上等待的单个线程(若有多个线程等待,则随机选择一个线程唤醒),不释放锁
public final void notify();
1
  • notifyAll():唤醒对象上等待的所有线程,不释放锁
public final void notifyAll();
1

注:

  • 唤醒并不表示线程可以立马执行,还要抢夺 CPU 时间片
  • 这些方法都与线程有关,所以一般用在锁对象上,通过锁对象来控制线程

sleep() 与 wait() 的区别:sleep() 只能通过线程对象调用,且必须指定睡眠时间,不释放锁。wait() 方法可以通过任意对象调用,且可指定时间,也可不指定时间,释放锁

# 获取文件绝对路径

采用以下的代码可以拿到一个文件的绝对路径,前提是文件必须在当前类路径下(在当前 src 下),这种写法无论在哪个系统上都可获得绝对路径,是通用的

String path = Thread.currentThread().getContextClassLoader()
.getResource("从当前类路径开始的文件路径(相对路径)").getPath();
		
//Thread.currentThread();表示获取当前线程对象
//getContextClassLoader();表示获取当前线程的类加载器对象
//getResource();获取资源,当前线程的类加载器默认从类的根路径下加载资源


直接返回流
InputStream path = Thread.currentThread().getContextClassLoader()
.getResourceAsStream("从当前类路径开始的文件路径(相对路径)");
1
2
3
4
5
6
7
8
9
10
11

# 资源绑定器

Java.util 包下提供了一个资源绑定器,便于获取属性配置文件(.properties)中的内容,使用以下方法的前提是配置文件必须在当前类路径下,且此方法只适用于配置文件,注意:在写路径时路径后的扩展名不能写

ResourceBundle bundle = ResourceBundle.getBundle("db");//不能写成db.properties
		
String driver = bundle.getString("driver");
1
2
3

# 反射

反射就是通过字节码文件对象把 java 类中的各种成分(变量、方法、构造方法)映射成一个个 java 对象,实际上是通过 class 对象反向获取该类的信息,即通过类的字节码文件动态的解析类

获取 Class 对象的三种方式(在运行期间,一个类只有一个 class 对象产生):

方式一:会导致类加载,类加载静态代码块执行
Class c = Class.forName("带有包名的完整类名");

方式二:会执行静态代码块
Class c = 对象.getClass();

方式三:java语言中任何一种类型,包括基本数据类型都有.class属性 不会执行静态代码块(不会静态初始化)
Class c = 任何类型.class;
1
2
3
4
5
6
7
8

以下所有方法中若要获取私有成员时就在 Declared 之后调用解除私有限定(安全检查管理器)方法

public void setAccessible(boolean flag);
flag为true时表示关闭检查,可调用私有
flag为false时表示打开检查,不可调用私有
1
2
3

通过 class 对象获取构造方法对象

java.lang.reflect.Constructor<T>

获取批量的构造方法对象
public Constructor<?>[] getConstructors();//所有公有的构造方法
public Constructor<?>[] getDeclaredConstructors();//获取所有的构造方法(包括私有、受保护、默认、公有)


获取单个指定的构造方法对象
//Class<?>... parameterTypes表示参数类型的字节码,如String.class、int.class

public Constructor<T> getConstructor(Class<?>... parameterTypes);//获取单个指定的公有构造方法
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes);//获取单个指定不限修饰符的构造方法

创建新对象
public T newInstance(Object... initargs);//括号里的是填实际参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

通过 class 对象获取成员变量对象

java.lang.reflect.Field

获取批量的成员变量对象
public Field[] getFields();//获取公有的成员变量
public Field[] getDeclaredFields();//获取所有成员变量,包括:私有、受保护、默认、公有

通过变量名获取单个指定的成员变量对象
public Field getField(String name);//获取指定公有的成员变量
public Field getDeclaredField(String name);//获取单个指定成员变量、不限修饰符

获取变量的值,如果是私有就需要关闭安全检查
public Object get(Object obj);//括号里表示对象,返回该对象的某个变量的值

给获取的变量赋值
public void set(Object obj, Object value);//obj表示变量所在的对象,value表示要为变量赋的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

通过 class 对象获取成员方法对象

java.lang.reflect.Method

获取批量的成员方法对象
public Method[] getMethods();//获取公有的成员方法,包含了父类的公有成员方法
public Method[] getDeclaredMethods();//获取所有的成员方法,包含了私有成员方法,不包括继承的

获取单个指定成员方法对象
public Method getMethod(String name, Class<?>... parameterTypes);//获取公有的指定成员方法,name是方法名,后面填的是形参类型字节码
public Method getDeclaredMethod(String name, Class<?>... parameterTypes);//获取所有的成员方法

调用该成员方法
public Object invoke(Object obj, Object... args);//obj表示要调用方法的对象,args表示调用方法时传递的实际参数
1
2
3
4
5
6
7
8
9
10
11
12
获取类的父类对象
public Class<? super T> getSuperclass();
1
2
获取类实现的所有接口对象
public Class<?>[] getInterfaces();
1
2

反射的核心是:JVM 在运行时才动态加载类或调用方法 / 访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁

# 反射的优点

  • 可扩展性特性:应用程序可以通过使用它们的完全限定名称创建可扩展性对象的实例来使用外部的、用户定义的类。

  • 类浏览器和可视化开发环境:类浏览器需要能够枚举类的成员。可视化开发环境可以受益于利用反射中可用的类型信息来帮助开发人员编写正确的代码。

  • 调试器和测试工具:调试器需要能够检查类的私有成员。测试工具可以利用反射来系统地调用定义在类上的可发现集 API,以确保测试套件中的高水平代码覆盖率。

# 反射的缺点

  • 性能开销:由于反射涉及动态解析的类型,因此无法执行某些 Java 虚拟机优化。因此,反射操作的性能比非反射操作慢,应避免在对性能敏感的应用程序中频繁调用的代码段中使用。

  • 安全问题:反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。

  • 内部暴露:由于反射允许代码执行非反射代码中非法的操作,例如访问私有字段和方法,使用反射会导致意想不到的副作用,这可能会使代码功能失调并可能破坏可移植性. 反射代码打破了抽象,因此可能会随着平台的升级而改变行为。

# 注解

Java 注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。

注解 Annotation 是一种引用数据类型,编译后生成. class 文件

注解定义语法

[修饰符列表] @interface 注解类型名{
	
}
1
2
3

注解使用语法

@ 注解类型名
1
  • @Override 这个注解只能注解方法,这个注解是给编译器参考的,和运行阶段没有关系,凡是 java 中的方法带有这个注解的,编译器都会进行编译检查,如果这个方法不是重写父类的方法,编译器就会报错
  • 用来标注 “注解类型” 的注解称为元注解
  • @Target 注解是一个元注解,用来标注 “被标注的注解” 可以出现在哪些位置上
@Target(ElementType.METHOD)//被标注的元素只能出现在方法上
@Target(ElementType.ANNOTATION_TYPE)//被标注的元素只能出现在注解类型上
@Target(ElementType.TYPE)//被标注的元素只能出现在类上
@Target(ElementType.FIELD)//被标注的元素只能出现在字段上
@Target(ElementType.PARAMETER)//被标注的元素只能出现在参数上
@Target(ElementType.CONSTRUCTOR)//被标注的元素只能出现在构造方法上
@Target(ElementType.LOCAL_VARIABLE)//被标注的元素只能出现在局部变量上
@Target(ElementType.PACKAGE)//被标注的元素只能出现在包上
@Target(ElementType. MODULE)//被标注的元素只能出现在模块上
1
2
3
4
5
6
7
8
9
  • @Retention 注解是一个元注解,用来标注 “被标注的注解” 最终保存在哪里
@Retention(RetentionPolicy.SOURCE)//被标注的元素只被保存在java源文件中
@Retention(RetentionPolicy.CLASS)//被标注的元素只被保存在class文件中
@Retention(RetentionPolicy.RUNTIME)//被标注的元素只被保存在class文件中,并且可以被反射机制所读取
1
2
3
  • @Deprecated 注解用来标注已过时的元素

注解中定义属性

属性类型 属性名();//表示该属性名只能被赋值该属性类型的数据

public @interface MyAnnotation
{
	int value();
	String name();
	String address() default "";//给address属性赋默认值,在该注解使用时可省略不写
}
1
2
3
4
5
6
7
8
  • 如果一个注解当中有属性,那么在使用该注解时必须给该注解中的属性赋值(除非该属性使用 default 指定默认值就可省略赋值)
  • 如果一个注解的属性名为 value 且只有这一个属性或其余属性有默认值时,那么在使用该注解的这个属性时,属性名(value)可以省略不写,但属性值必须要有

注解使用

@ 注解类型名(属性名=属性值, 属性名=属性值......)

@MyAnnotation(value = 9, name = "xxx", address = "dddd")
class Person
{
	@MyAnnotation(value = 9, name = "xxx" )//因属性address有默认值可省略
	public void run()
	{
		
	}
}
1
2
3
4
5
6
7
8
9
10
11
  • 注解当中的属性可以是这些类型:byte、short、int、long、float、double、boolean、char、String、class、枚举以及以上每种类型的数组类型
  • 在使用注解中数组型的属性时如果属性值只有一个值,大括号 { } 可省略不写

反射注解

判断这个注解是否在此类上
//括号里填注解类型的字节码,
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass);//返回true表示在此类上有此注解,false表示没有

获取该注解对象
public <A extends Annotation> A getAnnotation(Class<A> annotationClass);//返回该注解类型,可能需强转
1
2
3
4
5
6

# 面向对象思想设计原则

这些设计原则都是为了提高代码的维护性、扩展性、复用性、低耦合高内聚

  • 单一职责原则:“高内聚、低耦合”,功能细化,每个类应该只有一个功能,对外只提供一种功能,而引起类变化的原因应该只有一个,所有的设计模式都应该遵循这一原则
  • 开闭原则:“一个对象对扩展开放对修改关闭”,对类的改动是通过增加代码进行的,而不是修改现有代码,借助抽象和多态,把可能变化的内容抽象出来,从而使抽象的部分是相对稳定的,而具体的实现则是可以改变和扩展的
  • 里氏替换原则:“在任何父类出现的地方都可以用它的子类来替代”,同一个继承体系中的对象应该有共同的行为特征
  • 依赖注入原则:“要依赖于抽象,不要依赖于具体实现”,在应用程序中,所有的类如果使用或依赖于其他的类,则应该依赖这些其他类的抽象类,而不是这些其他类的具体类,为了实现这一原则,就要我们在编程的时候针对抽象类或接口编程,而不是针对具体实现编程
  • 接口分离原则:“不应该强迫程序依赖它们不需要使用的方法”,一个接口不需要提供太多的行为,一个接口应该只提供一种对外的功能,不应该把所有的操作都封装到一个接口中
  • 迪米特原则:“一个对象应当对其他对象尽可能少的了解”,降低各个对象之间的耦合度,提高系统的可维护性,在模块之间应该只通过接口编程,而不理会模块的内部工作原理,它可以使各个模块耦合度降到最低,促进软件的复用

# 设计模式

  • 简单工厂模式:又叫静态工厂方法模式,它定义一个具体的工厂负责创建一些类的实例
    优点:客户端不需要负责对象的创建,从而明确了各个类的职责(功能)
    缺点:这个静态工厂类负责所有对象的创建,如果有新的对象增加,或者某些对象的创建方式不同,就需要不断的修改工厂类,不利于后期的维护
  • 工厂方法模式:工程方法模式中抽象工厂类负责定义创建对象的接口,具体对象的创建工作由继承抽象工厂的具体类实现
    优点:客户端不需要负责对象的创建,从而明确了各个类的职责,如果有新的对象增加,只需要增加一个具体的类和具体的工厂类即可,不影响已有的代码,后期维护容易,增强了系统的扩展性
    缺点:需要额外的编写代码,增加了工作量
  • 单例设计模式:单例设计模式就是要确保类在内存中只有一个对象,该实例必须自动创建,并且对外提供
    优点:在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象,单例模式无疑可以提高系统的性能
    缺点:没有抽象层,因此扩展很难,职责过重,在一定程度上违背了单一职责原则
编辑 (opens new window)
上次更新: 2024/06/09, 21:19:54
进阶

← 进阶

最近更新
01
大模型学习笔记
11-29
02
机器学习笔记
11-29
03
深度学习知识点总结
11-22
更多文章>
Theme by Vdoing | Copyright © 2019-2024 Evan Xu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式