视频看的是狂神说Java
一、电脑快捷键
1.Ctrl + shift + esc
打开任务管理器
2.win + E
打开“我的电脑”
3.win + R
打开运行方式
4.ctrl + F4
快速关闭网页或者文件
5.shift + delete
永久删除文件
6.在任务管理器中将“Windows资源管理器”结束任务时,桌面将会消失。点击文件
中的运行任务
输入explorer
就会出现桌面。
二、Dos命令
打开cmd方式
- 开始 + windows系统 + 命令提示符;
- win键 + R + 输入cmd打开控制台;
- 在任意的文件夹空白处,按住shift键+鼠标右键点击,在此处打开命令行窗口;
- 我的电脑中的地址栏最前面,输出
cmd
+空格,即可进入控制台;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZFcyQYxF-1637023966165)(java.assets/image-20210625111722301.png)]
管理员运行方式
开始 + windows系统 + 命令提示符 + 鼠标右键 + 更多 + 以管理员方式运行(可以获得最高权限)
常用的Dos命令
#盘符切换
E: + 回车
#查看当前目录下的所有文件:dir
#切换目录 cd /d f:\文件名
#返回上一级 cd ..
#清楚屏幕 cls
#退出终端 exit
#查看ip ipconfig
#打开应用
calc计算机 mspaint画图 notepad记事本
#ping 命令
ping www.baidu.com
#创建文件夹 md 文件夹名称
#删除文件夹 rd 文件夹名称
#创建文件 cd>文件名.后缀
#删除文件 del 文件名.后缀
ipconfig/release和ipconfig/renew的区别
ipconfig/release
:释放全部(或指定)适配器的由DHCP分配的动态IP地址。此参数适用于IP地址非静态分配的网卡,通常和下文的renew参数结合使用。
ipconfig/renew
:为全部(或指定)适配器重新分配IP地址。此参数同样仅适用于IP地址非静态分配的网卡,通常和上文的release参数结合使用。
三、Java入门
基本背景
**1. 三个问题:**高可用、高性能、高并发
2. 版本:
J2SE:Java2标准版 J2ME:Java2移动版 J2EE:Java2企业版
Java特性与优势
-
简单性:比C++易理解、易操作
-
面向对象:万物皆可成对象
-
可移植性:一次编译,多次运行。借助的是Java虚拟机
-
高性能:
-
分布式:通过url可以链接网页
-
动态性:通过反射机制实现
-
多线程:实现实时和同时做不同事情
-
安全性:底层代码设置了很多关于安全技术,防漏洞技术
-
健壮性:运行代码会自主查看内存消耗情况,没有指针的到处引用现象
Java三大版本
**JavaSE:**标准版(桌面程序,控制台开发…)
**JavaME:**嵌入式开发(手机,小家电…)
**JavaEE:**企业级开发(web端,服务器开发…)
JDK / JRE / JVM
JDK: Java Development Kit
JRE: Java Runtime Environment
JVM: Java Virtual Machine
Java程序运行机制
.java文件(源文件) -> (Java编译器)—> .class文件(字节码) —> 类装载器 —> 字节码校验器 —> 解释器 —> 操作系统平台
四、Java基础
idea创建空项目
创建空的project后,随后点击file
,然后点击创建module
,在project Structure
里指定JDK版本和下面对应的解释等级,例如JDK是1.8,则下面对应的数字为8
Java注释
单行注释:// 多行注释:/* */
文本注释:
/**
* @Description 描述代码功能信息
* @Author 编写代码的作者是谁
*/
标识符
- 以字母,美元符($)或者下划线开始;
- 首字母之后可以是字母、美元符、下划线和数字的组合;
- 不能以关键字命名;
- 区分大小写;
数据类型
**强类型语言:**所有变量都必须先定义后才能使用
弱类型语言:
类别:
基本类型:
**整数类型:**byte short(2) int(4) long(8)后面加L
**浮点类型:**float(4) 后面加F double(8)
**字符类型:**char(2)
**Boolean类型:**true或false
类型转换:
低----》 高
byte short char int long float double
强制转换 (类型)变量名 高—》低
自动转换 低----》 高
注:不能对布尔值进行转换
不能把对象类型转换为不相干的类型
在把高容量转换到低容量的时候强制转换
转换的时候可能存在内存溢出或精度问题
1B (byte,字节) = 8 bit(位)
引用类型:
类 接口 数组
变量
作用域
类变量: 前面有static修饰 static double salary = 2500;
实例变量: 赋初值,从属于对象,如果不初始化则为默认值,0,0.0,false(boolean),null(string)
局部变量: 定义在方法里,使用前必须声明和初始化
命名规范
类名: 首字母大写和驼峰原则
**方法名:**首字母小写和驼峰原则
常量
final 常量名 = 值;
常量名一般使用大写字符
修饰符不区分前后,public,static,final
位运算符
^ 异或 相同为0,不同为1
<< 代表左移,*2 2<<3 =16
>> 代表右移 /2
没有long时,所有非int类型转为int类型
“”+a+b 输出为1020
a+b+"" 输出为30
JavaDoc
参数信息
- @author 作者名
- @version 版本号
- @since 指明需要最早使用的jdk版本
- @param 参数名
- @return 返回值情况
- @throws 异常抛出情况
命令行生成JavaDoc文档
javadoc -encoding UTF-8 -charset UTF-8 文件名.java
Scanner对象
Scanner scanner = new Scanner(System.in); // 从键盘接受数据
Scanner.hasnext() // 判断接受的数据是否为空,以空格作为结束标志
Scanner.hasNextLine() // 判断接受的数据是否为空,以回车符为结束标志
scanner.close(); // 用完记得关闭
int i = scanner.nextInt(); // 接受的是整数数据
float f = scanner.nextFloat(); // 接受的是浮点数据
String str = scanner.next();
获取输入的字符串
next() 以空格符结束,nextLine() 以回车符结束
hasNext(),hasNextLine() 判断是否还有输入的数据
Java流程控制
if选择结构 For循环
Switch结构
-
switch语句中的变量类型可以是:byte、short、int、string、枚举或者char;
-
从JavaSE7开始,switch支持字符串String类型了
- 同时case标签必须为字符串常量或字面量
-
default:前面case全没有匹配成功后,才输出默认语句,否则不输出
增强for循环
While结构 break continue
DoWhile结构
goto
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f0rrLfkn-1637023966170)(java.assets/image-20210704104146461.png)]
outer:for (int i = 100; i < 205; i++){
for (int j = 2; j < i / 2; j++){
if (i % j == 0){
continue outer; // 当不满足质数要求时候,跳到外面循环outer
}
}
System.out.println(i + " ");
}
方法的重载
- 重载:发生在一个类中,函数名相同,但是形参不同
- 规则:
- 方法名称必须相同;
- 参数列表必须不同(个数不同、或类型不同、参数排列顺序不同等);
- 返回值可相同可不同;
- 仅仅返回值类型不同构成不了方法的重载
可变参数
- 在方法声明中,在指定参数类型后加一个省略号(…)
- 一个方法中只能指定一个可变参数,必须是方法的最后一个参数
public void min(int x,int ... y)
数组初始化
1. 静态初始化
int[] a = {1,2,3};
2. 动态初始化
int[] a = new int[10];
3. 默认初始化
数组是引用类型,它的元素相当于类的实例变量,已经分配空间,其中的每个元素也被按照实例变量同样的方式被隐式初始化。
Java内存分析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nr3t25jn-1637023966176)(java.assets/image-20210705160929729.png)]
Array类
-
数组的工具类java.util.Arrays
-
Arrays类中的方法都是static修饰的静态方法,使用时直接用类名进行调用
-
常用的功能:
给数组赋值:
Arrays.fill();
对数组排序:
Arrays.sort();
比较数组:
Arrays.equals();
查找数组元素:
Arrays.binarySearch();
输出数组:
Arrays.toString();
五、面向对象
创建与初始化对象
- 使用new创建对象,会分配内存空间,对创建好的对象进行默认初始化,以及对类中构造器的调用;
- 构造器的特点:
- 必须与类名相同;
- 必须没有返回类型,不能写void
三大特性
-
封装:高内聚、低耦合
- 提高程序的安全性,保护数据
- 隐藏代码的实现细节
- 统一接口
- 增加系统可维护性
-
继承
-
关键字:extends
-
Java中类只有单继承,没有多继承!
-
子类的无参构造函数会首先默认调用父类的无参构造函数
-
super注意点:
a. super调用父类的构造方法,必须在构造方法的第一个;
b. super必须只能出现在子类的方法或者构造方法中;
c. super和this不能同时调用构造方法。
-
super与this:
-
代表的对象不同:
a. this:本身调用者这个对象
b. super:代表父类对象的应用
-
前提:
a. this:没有继承也可以使用
b. super:只能在继承条件才使用
-
构造方法:
a. this():本类的构造
b. super():父类的构造
-
重写:
- 方法名,参数列表必须相同;
- 修饰符:范围可以扩大但不能缩小;
- 抛出的异常:范围,可以被缩小,但不能扩大;
-
如果父类和子类中的方法都有static修饰时,方法的调用只和左边定义的数据类型有关;
-
如果父类和子类中的方法都没有static修饰时,方法的调用只和右边new创建的对象和定义的数据类型有关;
-
-
-
多态
-
多态是方法的多态,属性没有多态;
-
父类与子类存在联系,否则会报:ClassCastException
-
存在条件:继承关系,方法需要重写,父类引用指向子类对象!
当子类没有重写父类的方法时,此时调用的是父类方法;当子类重写了父类方法时,此时调用的是子类方法;当调用子类独有的方法时,此时需要父类引用进行类型强制转换。
-
不能重写的方法有:
- static方法:属于类,不属于实例
- final方法:常量无法继承
- private方法
-
static
- 非静态方法可以调用静态方法,静态方法不能调用非静态方法;
- static修饰的方法会随着类的创建一起出现,而非静态方法需要创建对象后再调用;
- 当类中出现匿名函数、静态代码块、无参构造函数时,调用顺序为静态代码块->匿名函数->无参构造函数
public class Person{
// 匿名函数
{
System.out.println("匿名函数");
}
// 静态代码块
static {
System.out.println("静态代码块"); //只执行一次
}
// 无参构造函数
pra01(){
System.out.println("无参构造函数");
}
}
//输出的结果为:
静态代码块
匿名函数
无参构造函数
抽象类
- 抽象类里可以有抽象方法和正常的方法
- 抽象类的所有方法,继承了它的子类都必须实现它的方法,除非也是抽象的
- 不能new抽象类,只能靠子类去实现它
- 抽象类可以有普通方法
public abstract class Person {
//抽象方法
public abstract void run();
//正常方法
public void go(){
System.out.println("正常的方法");
}
}
接口
- 普通类、抽象类和接口
**普通类:**只有具体实现
**抽象类:**具体实现和规范(抽象方法)都有
**接口:**只有规范
- 声明类的关键字是class,声明接口的关键字为interface,声明实现接口的关键字为implements,声明抽象的关键字为abstract
- 接口里的所有方法都是public abstract
- 接口里所有的变量都是静态常量public static final
- 接口可以实现多继承
- 接口不能实例化,没有构造方法
内部类
- 分类:成员内部类、静态内部类、局部内部类、匿名内部类
- 一个Java类中只有一个public类,能有多个class
public class Outer{
private int id = 10;
public void out(){
System.out.println("这是外部类的方法");
}
//成员内部类
public class Inner{
public void in(){
System.out.println("这是内部类的方法");
}
//获得外部类的私有属性
public void GetID(){
System.out.println(id);
}
}
}
Outer outer = new Outer();
//通过外部类来实例化内部类
Outer.Inner inner = outer.new Inner();
inner.getID();
异常
- 体系结构
- 所有异常的超类:java.lang.Throwable
- 异常分为两大类:错误Error和异常Exception
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-29ZZUCwW-1637023966180)(java.assets/image-20210708092755954.png)]
-
Error
- Error类对象由Java虚拟机生成并抛出;
- Virtual MachineErrorJava虚拟机运行错误;OutOfMemoryError当Java不再有继续执行操作所需的内存资源
- NoClassDefFoundError类定义错误;LinkageError链接错误
-
Exception
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ivQ29Y75-1637023966183)(java.assets/image-20210708093551468.png)]
异常处理机制
-
两大类
- 抛出异常
- 捕获异常
-
异常处理五个关键字:try catch finally throw throws
-
finally:不管是否出现异常,都会执行finally里的相关语句
-
当出现多个catch语句时,从上往下范围依次变大,最后只有一个起作用,其他catch语句不输出
-
idea快捷键:ctrl + alt + T
// 方法上抛出异常,使用throws
public void test(int a, int b) throws ArithmeticException{
if(b == 0){
// 主动抛出异常,一般在方法中使用throw
throw new ArithmeticException();
}
}
自定义异常
- 需要继承Exception
- 步骤
- 创建自定义异常类
- 在方法中通过throw关键字排除异常对象
- 如果在当前抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理异常;否则在方法的声明处通过throws关键字指明要抛出给方法调用者的异常,继续进行下一步操作
- 在出现异常方法的调用者中捕获并处理异常
实际应用的经验总结
- 采用逻辑去合理规避同时辅助try-catch处理
- 在多重catch块后面,可以加一个catch(Exception)来处理可能会被遗漏的异常
- 对于不确定的代码,也可以加上try-catch,处理潜在的异常
- 尽量去处理异常,切忌只是简单地调用printStackTrace()去打印输出
- 根据不同的业务需求和异常类型去决定如何处理异常
- 尽量添加finally语句块去释放占用的资源。
六、多线程
进程与线程
**进程:**系统资源分配的单位
**线程:**CPU调度和执行的单位。
- 线程就是独立的执行路径;
- 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程(垃圾回收线程);
- main()称之为主线程,为系统的入口,用于执行整个程序;
- 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预
- 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;
- 线程会带来额外的开销,如cpu调度时间,并发控制开销
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
创建线程
方法一:继承Thread类
- 自定义线程类继承Thread类
- 重写run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
public class TestThread1 extends Thread{
//重写run方法
public void run(){
for (int i = 0; i < 10; i++){
System.out.println("代码在跑步。。。。");
}
}
public static void main(String[] args) {
TestThread1 thread1 = new TestThread1();
thread1.start();
for (int i = 0; i < 1000; i++){
System.out.println("main主线程");
}
}
}
线程不一定立即执行,CPU安排调度
注意:
如果main函数里调用的是 thread1.start()语句,则main主线程和分支线程同时交替执行
如果main函数里调用的是 thread1.run()语句,则先执行创建的分支线程,再执行main主线程
方法二:实现 Runnable接口
- 定义MyRunnable类实现Runnable接口
- 实现run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
推荐使用Runnable对象,因为Java单继承的局限性
public class TestThread2 implements Runnable{
public void run(){
....
}
public static void main(String[] args) {
TestThread2 thread2 = new TestThread2();
new Thread(thread2).start(); // 将实现Runnable接口的对象放入Thread构造函数中进行启动
....
}
}
常用的方法
Thread.currentThread().getName(); //获取当前线程的名字
Thread.sleep(); // 让线程睡眠,单位毫秒
方法三:实现 Callable接口
- 实现Callable接口,需要返回值类型
- 重写call方法,需要抛出异常
- 创建目标对象
- 创建执行服务:
ExecutorService ser = Executors.newFixedThreadPool(3); // 3代表创建线程的个数
- 提交执行
Future<Boolean> result1 = ser.submit(t1); // 泛型<>里的类型与实现Callable接口返回值类型一致
- 获取结果
boolean r1 = result1.get(); // 与实现Callable接口返回值类型一致
- 关闭服务
ser.shutdownNow();
lamda表达式
其实质属于函数式编程
(params) -> expression[表达式]
(params) -> statement[语句]
(params) -> {statements}
总结:
- lambda表达式只能有一行代码的情况下才能简化成一行,如果是多行,那么就用代码块包裹
- 前提是接口为函数式接口
- 多个参数也可以去掉参数类型,要去掉就都去掉,必须加上括号
函数式接口
- 任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口
- 对于函数式接口,我们可以通过lamda表达式老创建该接口的对象
例子
package com.ty.Thread_prac.lamda;
public class Testlambda1 {
// 3.静态内部类
static class Like2 implements Ilike{
@Override
public void lambda() {
System.out.println("i like lambda2");
}
}
public static void main(String[] args) {
Ilike like = new Like();
like.lambda();
like = new Like2();
like.lambda();
// 4.局部内部类
class Like3 implements Ilike{
@Override
public void lambda() {
System.out.println("i like lambda3");
}
}
like = new Like3();
like.lambda();
// 5.匿名内部类,没有类的名称,只能借助接口或者父类
like = new Ilike() {
@Override
public void lambda() {
System.out.println("i like lambda4");
}
};
like.lambda();
// 6.用lambda简化
like = ()->{
System.out.println("i like lambda5");
};
like.lambda();
// 7.lambda再简化
like = ()-> System.out.println("i like lambda6");
like.lambda();
}
}
// 1.定义一个函数式接口
interface Ilike{
void lambda();
}
// 2. 实现类
class Like implements Ilike{
@Override
public void lambda() {
System.out.println("i like lambda");
}
}
线程状态
创建状态、就绪状态、阻塞状态、运行状态、死亡状态
创建状态
Thread t = new Thread()线程对象一旦创建就进入到了新生状态
就绪状态
当调用start() 方法,线程立即进入就绪状态,但不意味着立即调度执行
阻塞状态
当调用sleep,wait或同步锁定时,线程进入阻塞状态,就是代码不往下执行,阻塞时间解除后,重新进入就绪状态,等待cpu调度执行
运行状态
进入运行状态,线程才真正执行线程体的代码块
死亡状态
线程中断或者结束,一旦进入死亡状态,就不能再次启动
常用的方法
方法 | 说明 |
---|---|
setPriority(int newPriority) | 更改线程的优先级 |
static void sleep(long millis) | 在指定的毫秒数内让当前正在执行的线程休眠 |
void join() | 等待该线程终止 |
static void yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
void interrupt() | 中断线程,别用这个方式 |
boolean isAlive() | 测试线程是否处于活动状态 |
停止线程
- 不推荐使用JDK提供的stop()、destroy()方法
- 推荐线程自己停止下来
- 建议使用一个标志位进行终止变量,当flag=false,则终止线程运行
线程休眠
- sleep(时间)指定当前线程阻塞的毫秒数
- sleep存在异常InterruptedException;
- sleep时间达到后线程进入就绪状态
- sleep可以模拟网络延时,倒计时等
- 每一个对象都有一个锁,sleep不会释放锁
模拟倒计时
public class TestSleep {
public static void main(String[] args) {
try {
tenDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 模拟倒计时
public static void tenDown() throws InterruptedException{
int num = 10;
while(true){
Thread.sleep(1000);
System.out.println(num--);
if (num < 0){
break;
}
}
}
}
打印当前系统时间
public static void main(String[] args) {
// 打印当前系统时间
Date startTime = new Date(System.currentTimeMillis()); // 获取系统当前时间
while (true){
try {
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
startTime = new Date(System.currentTimeMillis());//更新当前时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
线程礼让
- 礼让线程,让当前正在执行的线程暂停,但不阻塞
- 将线程从运行状态转为就绪状态
- 让cpu重新调度,但不一定成功!看cpu心情
public class TestYield {
public static void main(String[] args) {
MyYield myi = new MyYield();
new Thread(myi,"a").start();
new Thread(myi,"b").start();
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程成功了!");
Thread.yield();
System.out.println(Thread.currentThread().getName() + "线程成功了!");
}
}
线程强制执行(插队)
thread.join()
观测线程的状态
Thread.State state = thread.getState() // 状态有NEW RUNNABLE BLOCKED WAITING TIMED_WAITING TERMINATED
线程的优先级
- Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行
- 线程的优先级用数字表示,范围从1~10
Thread.MIN_PRIORITY = 1;
Thread.MAX_PRIORITY = 10;
Thread.NORM_PRIORITY = 5;
- 使用以下方式改变或获取优先级
getPriority() setPriority(int xxx)
优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了,这都看cpu的调度
优先级的设定建议再sart()调度前
守护(daemon)线程
-
线程分为用户线程和守护线程
-
虚拟机必须确保用户线程执行完毕
-
虚拟机不用等待守护线程执行完毕
-
如,后台记录操作日志,监控内存,垃圾回收等待
// 默认是false表示是用户线程,正常的线程都是用户线程
Thread.setDaemon(true)
线程同步(等待机制)
**并发:**同一个对象被多个线程同时操作
锁机制synchronized导致的一些问题:
1. 一个线程持有锁会导致其他所有需要此锁的线程挂起;
2. 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换 和 调度延时,引起性能问题;
3. 如果一个优先级高的线程等待一个优先级低的线程释放锁 会导致优先级倒置,引起性能问题
同步方法
- synchronized关键字,包括两种用法:synchronized方法和synchronized块
- synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,知道该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行
同步块
- 同步块:synchronized(obj){}
- obj称之为同步监视器
- Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
- 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class
- 同步监视器的执行过程
- 第一个线程访问,锁定同步监视器,执行其中代码;
- 第二个线程访问,发现同步监视器被锁定,无法访问;
- 第一个线程访问完毕,解锁同步监视器;
- 第二个线程访问,发现同步监视器没有锁,然后锁定并访问
死锁
产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用;
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放;
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺;
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
注意:
只要破坏其中任意一个或多个条件就可以避免死锁发生
Lock(锁)
class TestLock implements Runnable{
int tickNums = 10;
// 定义lock锁,ReentrantLock可重用锁
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
lock.lock(); // 加锁
if (tickNums > 0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(tickNums--);
}else {
break;
}
} finally {
// 解锁
lock.unlock();
}
}
}
}
如果同步代码有异常,要将unlock()写入finally语句块
synchronized与Lock的对比
- Lock是显示锁(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,出了作用域自动释放
- Lock只有代码块锁,synchronized有代码块锁和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
- 优先使用顺序:
- Lock > 同步代码块 (已经进入了方法体,分配了相应资源) > 同步方法(在方法体之外)
线程协作
应用场景:生产者和消费者问题
- 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费
- 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止;
- 如果仓库中有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止
线程通信
- wait()
- wait(long timeout)
- notify()
- notifyAll()
解决方式1:
并发写作模型“生产者/消费者模式” -->管程法
- 生产者:负责生产数据的模块(可能是方法,对象,线程,进程);
- 消费者:负责处理数据的模块(可能是方法,对象,线程,进程);
- 缓冲区:消费者不能直接使用生产者的数据,他们之间有个缓冲区
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
package com.ty.Thread_prac.state;
// 测试:生产者消费者模型-->利用缓冲区解决:管程法
// 生产者,消费者,产品,缓冲区
public class TestPC {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Productor(container).start();
new Consumer(container).start();
}
}
// 生产者
class Productor extends Thread{
SynContainer container;
public Productor(SynContainer container) {
this.container = container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
container.push(new Chicken(i));
System.out.println("生产了" + i + "只鸡");
}
}
}
// 消费者
class Consumer extends Thread{
SynContainer container;
public Consumer(SynContainer container) {
this.container = container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了-->"+container.pop().id+"只鸡");
}
}
}
// 产品
class Chicken{
int id;
public Chicken(int id) {
this.id = id;
}
}
// 缓冲区
class SynContainer{
Chicken[] chickens = new Chicken[10];
int count = 0;
// 生产者放入产品
public synchronized void push(Chicken chicken){
//如果容器满了,就需要等待消费者消费
if (count == chickens.length){
//通知消费者消费,生产者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果没有满,我们就需要丢入产品
chickens[count]=chicken;
count++;
// 可以通知消费者消费了
this.notifyAll();
}
// 消费者消费产品
public synchronized Chicken pop(){
// 判断能否消费
if (count == 0){
// 等待生产者生产,消费者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果可以消费
count--;
Chicken chicken = chickens[count];
// 吃完了,通知生产者生产
this.notifyAll();
return chicken;
}
}
解决方式2:
并发协作模型“生产者/消费者模式” -->信号灯法
package com.ty.Thread_prac.state;
public class TestPC2 {
public static void main(String[] args) {
TV tv = new TV();
new Player(tv).start();
new Watcher(tv).start();
}
}
// 生产者--> 演员
class Player extends Thread{
TV tv;
public Player(TV tv){
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i % 2 ==0){
this.tv.play("快乐大本营播放中");
}else{
this.tv.play("抖音:记录美好生活");
}
}
}
}
// 消费者--> 观众
class Watcher extends Thread{
TV tv;
public Watcher(TV tv){
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
tv.watch();
}
}
}
// 产品 -- > 节目
class TV{
// 演员表演,观众等待 T
// 观众观看,演员等待 F
String voice;// 表演节目
boolean flag = true;
// 表演
public synchronized void play(String voice){
if (!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演员表演了:" + voice);
// 通知观众
this.notifyAll(); // 通知唤醒
this.voice = voice;
this.flag = !this.flag;
}
// 观看
public synchronized void watch(){
if (flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观看了:" + voice);
// 通知演员表演
this.notifyAll();
this.flag = !this.flag;
}
}
线程池
思路
提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具
好处
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理(…)
- corePoolSize:核心池的大小
- maximumPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间后会终止
使用线程池
-
JDK5.0起提供了线程池相关API:ExecutorService和Executors
-
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
- void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
- Futuresubmit(Callabletask):执行任务,有返回值,一般用来执行Callable
- void shutdown():关闭连接池
-
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
package com.ty.Thread_prac.state;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestPool {
public static void main(String[] args) {
// 1.创建服务,创建线程池,参数为线程池大小
ExecutorService service = Executors.newFixedThreadPool(10);
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
// 2.关闭连接
service.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
线程总结:
public class ThreadNew{
pulic static void main(String[] args){
new MyThread1().start();
new Thread(new MyThread2()).start();
FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyThread3());
new Thread(fatureTask).start();
try{
Intrger integer = futureTask.get();
System.out.println(integer);
}catch(InterruptedException e){
e.printStackTrace();
}catch(Exception e){
e.printStackTrace();
}
}
}
//1、继承Thread类
class MyThread1 extends Thread{
@override
public void run(){
System.out.println("MyThread1");
}
}
//2、实现Runnable接口
class MyThread2 imlpements Runnable{
@override
public void run(){
System.out.println("MyThread2");
}
}
//3、实现Runnable接口
class MyThread3 implements Callable<Integer>{
@override
public Integer call() throes Exception{
System.out.println("MyThread3");
return 100;
}
}
七、注解
什么是注解Annotation
**作用:**可以被其他程序(编译器)读取
格式:@注释名
**使用:**通过反射机制编程实现对元数据的访问
内置注解
**@Override:**定义在java.lang.Override中,表示一个方法声明打算重写超类中的另一个方法声明(作用在方法)
**@Deprecated:**定义在java.lang.Deprecated中,不鼓励使用(作用在方法、属性和类)
**@SupperessWarnings:**定义在java.lang.SupperessWarnings中,用于抑制编译时的警告信息,这个需要参数(“all” “unchecked” )
元注解
// @Target 表示我们的注解在什么地方起作用
@Target(value ={ElementType.METHOD,ElementType.TYPE})
// @Retention 表示我们的注解在什么地方还有效 SOURCE<CLASS<RUNTIME
@Retention(value= RetentionPolicy.RUNTIME)
// @Documented 表示是否将我们的注解生成在Javadoc中
@Documented
// @Inherited 子类可以继承父类的注解
@Inherited
//定义一个注解,直接写内部类
@interface MyAnnotation{}
自定义注解
- 使用
@interface
自定义注解时,自动继承了java.lang.annotation.Annotation接口 - 分析:
- 格式:public @interface注解名{定义内容}
- 其中每一个方法实际上是声明了一个配置参数
- 方法的名称就是参数的名称
- 返回值类型就是参数的类型(基本类型)
- 可以通过default来声明参数的默认值
- 如果只有一个参数成员,一般参数名为value
- 注解元素必须要有值,定义注解元素时,经常使用空字符串、0作为默认值
例子
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public class Test03 {
// 注解可以显示赋值,如果没有默认值,我们就必须给注解赋值
@MyAnnotation02(age = 18,name = "李四") //有默认值可以不写
public void test(){}
@MyAnnotation03("赵四") //默认值只有一个,用value表示,使用时可以省略
public void test02(){}
}
@Target({ElementType.TYPE,ElementType.METHOD}) //作用在类和方法上
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation02{
// 注解的参数:参数类型 + 参数名();
String name() default "";
int age() default 0;
int id() default -1; // 如果默认值为-1,代表不存在
String[] schools() default {"清华大学","北京大学"};
}
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation03{
// 注解的参数:参数类型 + 参数名();
String value();
}
八、反射
静态VS动态语言
动态语言
- 在运行时,代码可以根据某些条件改变自身结构
- 主要动态语言:Object-C、C#、JavaScript、PHP、Python等
静态语言
- 在运行时,代码不可以改变自身结构
- Java称为“准动态语言”。可以利用反射机制获得类似动态语言的特性。
Java Reflection
- 反射机制允许程序在执行期间借助Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法
- 加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。通过这个对象看到类的结构。形象称之为反射
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sjhcoixr-1637023966187)(java.assets/image-20210806210619030.png)]
反射对象优缺点
**优点:**可以实现动态创建对象和编译,体现出很大的灵活性
**缺点:**性能较慢
(原因:反射是一种解释操作,告诉JVM希望做什么并让JVM满足要求,这类操作总是慢于直接执行相同的操作)
反射相关的主要API
- java.lang.Class:代表一个类
- java.lang.reflect.Method:代表类的方法
- java.lang.reflect.Field:代表类的成员变量
- java.lang.reflect.Constructor:代表类的构造器
package com.ty.JavaPractice.annotation;
public class Test04 {
public static void main(String[] args) throws ClassNotFoundException {
//通过反射获取类的class对象
Class c1 = Class.forName("com.ty.JavaPractice.annotation.User");
System.out.println(c1);
// 一个类在内存中只有一个class对象
// 一个类被加载后,类的整个结构都会被封装在class对象中
Class c2 = Class.forName("com.ty.JavaPractice.annotation.User");
Class c3 = Class.forName("com.ty.JavaPractice.annotation.User");
Class c4 = Class.forName("com.ty.JavaPractice.annotation.User");
System.out.println(c2.hashCode());
System.out.println(c3.hashCode());
System.out.println(c4.hashCode());
}
}
Class类
在Object类中定义了以下的方法,此方法将被所有子类继承
public final Class getClass()
反射的本质:通过对象反射求出类的名称
Class类的内容:
类的属性、方法和构造器、实现的接口
- Class本身也是一个类
- Class对象只能由系统建立对象
- 一个加载的类在JVM中只会有一个Class实例
- 一个Class对象对应的是一个加载到JVM中的一个.class文件
- 每个类的实例都记得由哪个类实例生成
- 通过Class可以获得类所有加载的结构信息
- Class是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象
Class类常用的方法
方法名 | 功能说明 |
---|---|
static Class.forName(String name); | 返回指定类名name的Class对象 |
Object newInstance(); | 调用缺省构造函数,返回对象的一个实例 |
getName(); | 返回此Class对象所表示的实体(类、接口、数组类或void)的名称 |
Class getSupperClass(); | 返回当前Class对象的父类的Class对象 |
Class[] getinterfaces() | 获取当前Class对象的接口 |
ClassLoader getClassLoader() | 返回该类的类加载器 |
Constructor[] getConstructors() | 返回一个包含某些Constructor对象的数组 |
Method getMethod(String name,Class… T) | 返回一个Method对象,次对象的形参类型为paramType |
Field[] getDeclaredFields() | 返回Field对象的一个数组 |
获取Class类的实例
- 若已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高
Class clazz = Student.class;//通过类名.class获得
clazz输出为:class demo01.Student
- 已知某个类的实例,调用该实例的getClass()方法获取Class对象
Class clazz = person.getClass();//通过对象获得
- 已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException
Class clazz = Class.forName("demo01.Student");//通过forname获得
- 内置基本数据类型可以直接用类名.Type
- 还可以利用ClassLoader
哪些类型可以有Class对象
-
**class:**外部类、成员(成员内部类、静态内部类),局部内部类,匿名内部类
-
**interface:**接口
-
**[]:**数组
-
**enum:**枚举
-
**annotation:**注解@interface
-
**primitive type:**基本数据类型
-
void
// 类
Class c1 = Object.class;
// 接口
Class c2 = Comparable.class;
// 一维数组
Class c3 = String[].class;
// 二维数组
Class c4 = int[][].class;
// 注解
Class c5 = Override.class;
// 枚举
Class c6 = ElementType.class;
// 基本数据类型
Class c7 = Integer.class;
// void
Class c8 = void.class;
// Class
Class c9 = Class.class;
注:只要元素类型与维度一样,就是同一个Class
类加载内存分析
package com.ty.JavaPractice.annotation;
public class Test05 {
public static void main(String[] args) {
A a = new A();
System.out.println(A.m);
/*
1.加载到内存,会产生一个类对应Class对象
2.链接,链接结束后m=0,赋默认值;
3.初始化
<clinit>(){
System.out.println("A的静态代码块区域");
m=10;
m=20;
}
输出m=20
*/
}
}
class A{
static {
System.out.println("A的静态代码块区域");
int m = 10;
}
static int m = 20;
public A(){
System.out.println("A类的无参构造函数初始化");
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-47l98Vsn-1637023966189)(java.assets/image-20210807094931326.png)]
什么时候会发生初始化
类的主动引用(一定会发生类的初始化)
- 当虚拟机启动,先初始化main方法所在的类
- new一个类的对象
- 调用类的静态成员(除了final常量)和静态方法
- 使用java.lang.reflect包的方法堆类进行反射调用
- 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类
类的被动引用(不会发生类的初始化)
- 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化
- 通过数组定义类引用,不会触发此类的初始化
- 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
//测试类什么时候初始化
public class Test06 {
static {
System.out.println("main所在的类被加载");
}
public static void main(String[] args) {
//1.主动引用
Son son = new Son();
/**
输出结果为:
main所在的类被加载
父类被加载
子类被加载
**/
}
}
class Father{
static int b = 1;
static {
System.out.println("父类被加载");
}
}
class Son extends Father{
static {
System.out.println("子类被加载");
m = 10;
}
static int m =20;
static final int M = 25;
public static void main(String[] args) throws ClassNotFoundException {
//2.反射也会产生主动引用
Class.forName("com.ty.JavaPractice.annotation.Son");
/**
* 输出结果为:
* main所在的类被加载
* 父类被加载
* 子类被加载
*/
}
public static void main(String[] args) throws ClassNotFoundException {
// 3.不会产生类的引用
System.out.println(Son.b);
/*
输出结果为:
main所在的类被加载
父类被加载
1
*/
}
public static void main(String[] args) throws ClassNotFoundException {
Son[] array = new Son[5];
/**
* 输出结果为;
* main所在的类被加载
*/
}
public static void main(String[] args) throws ClassNotFoundException {
// 子类里的常量
System.out.println(Son.M);
/*
* 输出结果为:
* main所在的类被加载
25
* */
}
类加载器
**类加载器作用:**将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java,lang.Class对象,作为方法区中类数据的访问入口
**类缓存:**标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0bnQpFzD-1637023966191)(java.assets/image-20210807105911900.png)]
类型:
- 引导类加载器(Bootstap Classloader):C++编写,JVM自带的类加载器,负责Java平台核心库,用来加载核心类库。无法直接获取
- 扩展类加载器(Extension Classloader):负责jre/lib/ext目录下的jar包 或 java.ext.dirs指定目录下的jar包装入工作库
- 系统类加载器(System Classloader):负责java-classpath 或 java.class.path所指的目录下的类与jar包装入工作(常用的加载器)
加载机制:
- 双亲委派机制:如果自定义一个与java自带的库相同的包,则会选择加载java自带的,保证安全
package com.ty.JavaPractice.annotation;
public class Test07 {
public static void main(String[] args) {
// 获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
// jdk.internal.loader.ClassLoaders$AppClassLoader@3fee733d
// 获取系统类加载器的父类加载器-->扩展加载器
ClassLoader parent = systemClassLoader.getParent();
System.out.println(parent);
// jdk.internal.loader.ClassLoaders$PlatformClassLoader@723279cf
// 获取扩展加载器的父类加载器-->根加载器
ClassLoader parent1 = parent.getParent();
System.out.println(parent1);
// null
//如何获取系统类加载器可以加载的路径
System.out.println(System.getProperty("java.class.path"));
}
}
获取类的运行时结构
package com.ty.JavaPractice.annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Test08 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
Class c1 = Class.forName("com.ty.JavaPractice.annotation.User");
// 获得类的名字
System.out.println(c1.getName()); // 获得包名+类名
System.out.println(c1.getSimpleName()); // 获得类名
// 获得类的属性
System.out.println("====================");
Field[] fields = c1.getFields(); // 只能找到public属性
fields = c1.getDeclaredFields(); // 找到所有的属性
for (Field field : fields) {
System.out.println(field);
}
System.out.println("====================");
// 获得指定的属性
Field name = c1.getDeclaredField("name");
System.out.println(name);
// 获得类的方法
System.out.println("====================");
Method[] methods = c1.getMethods(); // 获得本类及其父类的全部public方法
for (Method method : methods) {
System.out.println(method);
}
Method[] declaredMethods = c1.getDeclaredMethods();// 获得本类的所有方法
for (Method declaredMethod : declaredMethods) {
System.out.println(declaredMethod);
}
// 获得指定方法
// 由于是重载,所以得指定参数
Method getName = c1.getMethod("getName", null);
Method setName = c1.getMethod("setName", String.class);
System.out.println(getName);
// 获得指定的构造器
System.out.println("====================");
Constructor[] constructors = c1.getConstructors();
constructors = c1.getDeclaredConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
// 获得指定的构造器
Constructor declaredConstructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
System.out.println(declaredConstructor);
}
}
动态创建对象执行方法
- 调用Class对象的newInstance()方法的条件:
- 类必须有一个无参数的构造器
- 类的构造器的访问权限需要足够
- 当没有无参构造函数时,需要借助Class类的getDeclaredConstructor()方法来创建对象
- 调用类中的方法通过getDeclaredMethod()
- 通过Class类的getDeclaredMethod(String name,Class…parameterTypes)取得对象,设置相应的参数
- 使用invoke(对象,值)进行调用
- 方法、属性和构造器对象都有setAccessible()方法
- setAccessible()作用是启动和禁用访问安全检查的开关
- 参数为true表示反射对象在使用时取消Java语言访问检查,即可以访问私有属性
package com.ty.JavaPractice.annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Test10 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
// 获得class对象
Class c1 = Class.forName("com.ty.JavaPractice.annotation.User");
// 构造一个对象
User user1 = (User) c1.newInstance(); // 本质是调用了类的无参构造器
System.out.println(user1);
// 通过构造器创建对象
Constructor constructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
User user2 =(User) constructor.newInstance("李四", 23, 2);
System.out.println(user2);
// 通过反射调用普通方法
User user3 = (User) c1.newInstance();
Method setName = c1.getDeclaredMethod("setName", String.class);
// invoke:激活的意思(对象,”方法的值“)
setName.invoke(user3,"赵四");
System.out.println(user3);
// 通过反射操作属性
System.out.println("=================================");
User user4 = (User) c1.newInstance();
Field name = c1.getDeclaredField("name");
// 私有属性需要关闭程序的安全检测,属性或方法的setAccessible(true)
name.setAccessible(true);
name.set(user4,"王五");
System.out.println(user4);
}
}
性能对比分析
普通调用方法 最快
关闭Java语言检查进行反射调用,setAccessible(true) 其次
直接反射调用方法 最慢
反射操作泛型
-
Java采用泛型擦除的机制来引入泛型。
- 仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换问题。
- 一旦编译完成,所有和泛型有关的类型全部擦除
-
反射泛型相关的类:
- ParameterizedType:表示一种参数化类型
- GenericArrayType:表示一种元素类是参数化类型或者类型变量的数组类型
- TypeVariable:是各种类型变量的公共父接口
- WildcardType:代表一种通配符类型表达式
package com.ty.JavaPractice.annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
public class Test11 {
public static void test01(Map<String,User> map, List<User> list){
System.out.println("test01");
}
public Map<String,User> test02(){
System.out.println("test02");
return null;
}
public static void main(String[] args) throws NoSuchMethodException {
Method test01 = Test11.class.getMethod("test01", Map.class, List.class);
Type[] genericParameterTypes = test01.getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
System.out.println(genericParameterType);
if (genericParameterType instanceof ParameterizedType){
Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}
System.out.println("================================");
Method test02 = Test11.class.getMethod("test02", null);
Type genericReturnType = test02.getGenericReturnType();
if (genericReturnType instanceof ParameterizedType){
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}
}
ORM(对象关系映射)
- 类和表对应
- 属性和字段对应
- 对象和记录对应
package com.ty.JavaPractice.annotation;
import java.lang.annotation.*;
import java.lang.reflect.Field;
public class Test12 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class c1 = Class.forName("com.ty.JavaPractice.annotation.Student");
// 通过反射获得注解
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
//输出结果:@com.kuang.reflection.TableA(value=db_student)
// 获得注解的val值
TableA tableA = (TableA)c1.getAnnotation(TableA.class);
String value = tableA.value();
System.out.println(value);
// 获得指定的注解
Field name = c1.getDeclaredField("name");
FieldA annotation = name.getAnnotation(FieldA.class);
System.out.println(annotation.columnName());
System.out.println(annotation.type());
System.out.println(annotation.length());
//输出为:db_name varchar 10
}
}
@TableA("db_student")
class Student{
@FieldA(columnName = "db_name",type = "varchar",length = 10)
private String name;
@FieldA(columnName = "db_id",type = "int",length = 10)
private int id;
@FieldA(columnName = "db_age",type = "int",length = 10)
private int age;
public Student() {
}
public Student(String name, int id, int age) {
this.name = name;
this.id = id;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", id=" + id +
", age=" + age +
'}';
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TableA{
String value();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldA{
String columnName();
String type();
int length();
}
面试
基本数据类型
浮点数
float和double表示的长度都是优先的,存在舍入误差的情况,所以float和double类型的变量虽然赋予的值相等,但是二者比较则不会相等。
float f = 0.1f;
double d = 1.0/10;
System.out.println(f==d); // false
银行业务设计浮点数时,使用
BigDecimal
数学工具类
BigDecimal num1 = new BigDecimal("24");
BigDecimal num2 = new BigDecimal("2");
BigDecimal sum_result = num1.add(num2); // 加法
BigDecimal sub_result = num1.subtract(num2); // 减法
BigDecimal mul_result = num1.multiply(num2); // 乘法;
BigDecimal abs_result = num1.abs(); // 绝对值
BigDecimal div_result = num1.divide(num2); // 除法;
字符
编码Unicode: 占2字节 0~65536
转义字符:
\t 制表符
\n 换行
\u Unicode编码 \u0061表示16进制数,10进制为65
字符数组
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println(str1 == str2); //false;
String str3 = "hello";
String str4 = "hello";
System.out.println(str3 == str4); // true
StringBuilder 和 StringBuilder的区别
共同点
都继承的是抽象的字符串父类:AbstractStringBuilder
区别点
1. 线程安全
StringBuffer的所有公开方法都是synchronized修饰的,而StringBuilder并没有;因此StringBuffer是线程安全的,而StingBuilder是线程不安全的
2.缓冲区
StringBuffer每次获取toString都会直接使用缓冲区的toStringCache值来构造一个字符串;StringBuilder则每次都需要复制一次字符数组,再构造一个字符串
3.性能
由于StringBuilder没有线程安全,因此性能要远大于StringBuffer。
name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", id=" + id +
", age=" + age +
'}';
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TableA{
String value();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldA{
String columnName();
String type();
int length();
}
# 面试
## 基本数据类型
### 浮点数
float和double表示的长度都是优先的,存在舍入误差的情况,所以float和double类型的变量虽然赋予的值相等,但是二者比较则不会相等。
```java
float f = 0.1f;
double d = 1.0/10;
System.out.println(f==d); // false
银行业务设计浮点数时,使用
BigDecimal
数学工具类
BigDecimal num1 = new BigDecimal("24");
BigDecimal num2 = new BigDecimal("2");
BigDecimal sum_result = num1.add(num2); // 加法
BigDecimal sub_result = num1.subtract(num2); // 减法
BigDecimal mul_result = num1.multiply(num2); // 乘法;
BigDecimal abs_result = num1.abs(); // 绝对值
BigDecimal div_result = num1.divide(num2); // 除法;
字符
编码Unicode: 占2字节 0~65536
转义字符:
\t 制表符
\n 换行
\u Unicode编码 \u0061表示16进制数,10进制为65
字符数组
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println(str1 == str2); //false;
String str3 = "hello";
String str4 = "hello";
System.out.println(str3 == str4); // true
StringBuilder 和 StringBuilder的区别
共同点
都继承的是抽象的字符串父类:AbstractStringBuilder
区别点
1. 线程安全
StringBuffer的所有公开方法都是synchronized修饰的,而StringBuilder并没有;因此StringBuffer是线程安全的,而StingBuilder是线程不安全的
2.缓冲区
StringBuffer每次获取toString都会直接使用缓冲区的toStringCache值来构造一个字符串;StringBuilder则每次都需要复制一次字符数组,再构造一个字符串
3.性能
由于StringBuilder没有线程安全,因此性能要远大于StringBuffer。