最后希望可以帮助到大家!
千千万万要记得:多刷题!!多刷题!!
之前算法是我的硬伤,后面硬啃了好长一段时间才补回来,算法才是程序员的灵魂!!!!
篇幅有限,以下只能截图分享部分的资源!!
(1)多线程(这里以多线程为代表,其实整理了一本JAVA核心架构笔记集)
(2)刷的算法题(还有左神的算法笔记)
(3)面经+真题解析+对应的相关笔记(很全面)
(4)视频学习(部分)
ps:当你觉得学不进或者累了的时候,视频是个不错的选择
在这里,最后只一句话:祝大家offer拿到手软!!
PS:不过说实话,直接看比较官方的定义还是有点难理解,再来更加通俗点的说吧~
(2)一般情况下,我们使用某个类,都会知道这个类,以及要用它来做什么,可以直接通过new
实例化创建对象,然后使用这个对象对类进行操作,这个就属于正射
~
(3)而反射
则是一开始并不知道要初始化的是什么类,无法使用new
来实例化创建对象,主要是通过JDK提供的反射API来实现,在运行时才知道要操作的是什么类,并且可以获取到类的完整构造以及调用对应的方法,这就是反射
~
📣1.2 反射例子
代码如下:
package com.justin.java.lang;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
/\*\*
\* @program: Jdk1.8 Test
\* @description: 正射、反射简单调用示例
\* @author: JustinQin
\* @create: 2021/8/22 13:23
\* @version: v1.0.0
\*\*/
public class Student {
private int id;
public void setId(int id) {
this.id = id;
}
public int getId() {
return id;
}
public static void main(String[] args) throws Exception{
//一、正射调用过程
Student student = new Student();
student.setId(1);
System.out.println("正射调用过程Student id:" + student.getId());
//二、反射调用过程
Class clz = Class.forName("com.justin.java.lang.Student");
Constructor studentConstructor = clz.getConstructor();
Object studentObj = studentConstructor.newInstance();
Method setIdMethod = clz.getMethod("setId", int.class);
setIdMethod.invoke(studentObj, 2);
Method getIdMethod = clz.getMethod("getId");
System.out.println("正射调用过程Student id:" + getIdMethod.invoke(studentObj));
}
}
输出结果:
正射调用过程Student id:1
反射调用过程Student id:2
上述例子反射的调用过程,可以看到获取一个类的反射对象
,主要过程为:
- 获取类的
Class
实例对象 - 根据
Class
实例对象获取Constructor
对象 - 再根据
Constructor
对象的newInstance
方法获取到类的反射对象
获取到类的反射对象
后,就可以对类进行操作了~ 例如,上述示例中对类的方法进行调用过程为:
- 根据
Class
实例对象获取到类的Method
对象 - 再根据
Method
对象的invoke
方法调用到具体类的方法
前面一点也提到了获取到类的Class
实例对象,上面示例反向调用过程
中我们是通过Class.forName("类的全局定名")
这种方式来获取到类的Class
实例对象,除了这种,常用的还有其他两种,往下讲解~
🎵二、Java反射机制中获取Class的三种方式及区别?
📀2.1 Class的几种获取方式
(1)获取类的java.lang.Class
实例对象,常见的三种方式分别为:
- 通过
MyClass.class
获取,这里的MyClass指具体类~~ - 通过
Class.forName("类的全局定名")
获取,全局定名为包名+类名 - 通过
new MyClass().getClass()
获取,这里的MyClass指具体类~
(2)通过MyClass.class
获取,JVM会使用ClassLoader
类加载器将类加载到内存中,但并不会做任何类的初始化工作,返回java.lang.Class
对象
(3)通过Class.forName("类的全局定名")
获取,同样,类会被JVM加载到内存中,并且会进行类的静态初始化工作,返回java.lang.Class
对象
(4)通过new MyClass().getClass()
获取,这种方式使用了new
进行实例化操作,因此静态初始化和非静态初始化工作都会进行,getClass
方法属于顶级Object
类中的方法,任何子类对象都可以调用,哪个子类调用,就返回那个子类的java.lang.Class
对象
PS: 这3种方式,最终在JVM堆区对应类的
java.lang.Class
对象都属于同一个,也就是内存地址相同,进行==
双等号比较结果为true
,原因是JVM类加载过程中使用的是同一个ClassLoader
类加载器加载某个类,不论加载多少次,生成到堆区的java.lang.Class
对象始终只有一个,除非自定义类加载器,破坏JVM的双亲委派机制,使得同一个类被不同类加载器加载,JVM才会把它当做两个不同的java.lang.Class
对象
🔊2.2 代码演示几种方式的区别
创建一个实体类,分别在实体类中创建类的静态代码块
、动态代码块
、有参构造方法
、无参构造方法
,方便测试几种方式的区别及内存地址是否相同~
(1)实体类:
public class MyClass {
private static final String staticStr = "Hi";
private static int staticInt = 2021;
private String id;
static {
System.out.println("静态代码块:staticStr=" + staticStr + ",staticInt=" + staticInt);
}
{
System.out.println("动态代码块~");
}
public MyClass() {
System.out.println("无参构造方法~");
}
public MyClass(String id) {
System.out.println("有参构造方法~");
this.id = id;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@Override
public String toString() {
return "MyClass{" +
"id='" + id + '\'' +
'}';
}
}
(2)单元测试类:
通过@Test
注解对三种方式分别进行单元测试,再对这三种方式的组合进行单元测试~
package com.justin.java.lang;
import org.junit.Test;
/\*\*
\* @program: Jdk1.8Test
\* @description: Java反射机制中获取类的Class实例对象的常见三种方式及区别对比
\* @author: JustinQin
\* @create: 2021/8/22 15:04
\* @version: v1.0.0
\*\*/
public class MyClassTest {
@Test
public void test1() {
System.out.println("一、MyClass.class方式=========");
Class<?> class1 = MyClass.class;
}
@Test
public void test2() throws ClassNotFoundException {
System.out.println("二、Class.forName方式=========");
Class class2 = Class.forName("com.justin.java.lang.MyClass");
}
@Test
public void test3() {
System.out.println("三、new MyClass().getClass方式=========");
Class class3 = new MyClass().getClass();
}
@Test
public void test12() throws ClassNotFoundException {
System.out.println("一、MyClass.class方式=========");
Class<?> class1 = MyClass.class;
System.out.println("二、Class.forName方式=========");
Class class2 = Class.forName("com.justin.java.lang.MyClass");
}
@Test
public void test13() {
System.out.println("一、MyClass.class方式=========");
Class<?> class1 = MyClass.class;
System.out.println("三、new MyClass().getClass方式=========");
Class class3 = new MyClass().getClass();
}
@Test
public void test23() throws ClassNotFoundException {
System.out.println("二、Class.forName方式=========");
Class class2 = Class.forName("com.justin.java.lang.MyClass");
System.out.println("三、new MyClass().getClass方式=========");
Class class3 = new MyClass().getClass();
}
@Test
public void test() throws ClassNotFoundException {
System.out.println("四、三种方式内存地址比较=========");
Class<?> class1 = MyClass.class;
Class class2 = Class.forName("com.justin.java.lang.MyClass");
Class class3 = new MyClass().getClass();
System.out.println("比较结果=========");
System.out.println("MyClass.class和Class.forName内存地址比较是否相同:" + (class1 == class2));
System.out.println("MyClass.class和new MyClass().getClass内存地址比较是否相同:" + (class1 == class3));
System.out.println("Class.forName和new MyClass().getClass内存地址比较是否相同:" + (class2 == class3));
}
}
逐个执行单元,得出测试结果为:
\* test1()方法
一、MyClass.class方式=========
\* test2()方法
二、Class.forName方式=========
静态代码块:staticStr=Hi,staticInt=2021
\* test3()方法
三、new MyClass().getClass方式=========
静态代码块:staticStr=Hi,staticInt=2021
动态代码块~
无参构造方法~
\* test12()方法
一、MyClass.class方式=========
二、Class.forName方式=========
静态代码块:staticStr=Hi,staticInt=2021
\* test13()方法
一、MyClass.class方式=========
三、new MyClass().getClass方式=========
静态代码块:staticStr=Hi,staticInt=2021
动态代码块~
无参构造方法~
\* test23()方法
二、Class.forName方式=========
静态代码块:staticStr=Hi,staticInt=2021
三、new MyClass().getClass方式=========
动态代码块~
无参构造方法~
\* test()方法
四、三种方式内存地址比较=========
静态代码块:staticStr=Hi,staticInt=2021
动态代码块~
无参构造方法~
比较结果=========
MyClass.class和Class.forName内存地址比较是否相同:true
MyClass.class和new MyClass().getClass内存地址比较是否相同:true
Class.forName和new MyClass().getClass内存地址比较是否相同:true
通过test1
、test2
、test3
的测试结果验证了2.1 三种方式及区别
中黄色标记部分的区别说明,即:
MyClass.class
不会做任何类的初始化工作Class.forName
会进行类的静态初始化工作new MyClass().getClass
静态初始化和非静态初始化工作都会进行- 使用这三种方式任意一种最终在JVM加载到内存中都会是
内存地址相同
的
而test23
组合得到的测试结果,说明静态代码块只会被加载一次
~
讲了这么多,除了知道基本原理和基本使用之外,更重要的还是要知道它的一些比较实际的应用场景
,往下介绍~
💥三、Java反射机制的应用场景有哪些?
🎶3.1 应用场景
工厂模式
中的简单工厂模式优化代理模式
中的动态代理方式实现Java JDBC
数据库操作
🎧3.2 简单工厂模式优化
📢3.2.1 什么是简单工厂模式?
Java中主要有23种设计模式,其中工厂模式就是其中一种,而简单工厂模式,顾名思义,也是属于工厂模式中的一种,只不过比较简单。简单工厂模式也可以叫做静态方法模式(因为工厂类一般都是在内部定义了一个静态方法)。
从现实生活角度来理解的话,工厂是专门负责生产产品的,同样在设计模式中,简单工厂模式我们可以理解为专门负责生产对象的一个类,称为“工厂类”。
🎹3.2.2 简单工厂模式有什么用?
简单工厂模式通过创建一个对应的工厂类,将
类实例化的操作
与使用对象的操作
进行分开,让使用者不用知道具体参数就可以实例化出所需要的具体产品
类,从而避免了在客户端代码中显式指定,实现了解耦。即使用者可直接消费产品而不需要知道其生产的细节~
🎸3.2.3 如何实现简单工程模式?
实现简单工程模式的核心是创建一个
工厂类
,并且在内部定义了一个静态方法,传入不同的参数标识
通过switch
进行分组,通过new
实例化创建不同的子类对象返回~
实现例子:
步骤1:创建抽象产品类
public interface Product {
public abstract void show();
}
步骤2:创建具体产品类:
public class ProductA implements Product {
@Override
public void show() {
System.out.println("生产了产品A");
}
}
public class ProductB implements Product {
@Override
public void show() {
System.out.println("生产了产品B");
}
}
public class ProductC implements Product {
@Override
public void show() {
System.out.println("生产了产品C");
}
}
步骤3:创建简单工厂类
public class SimpleFactory {
/\*\*
\* 实现简单工厂模式
\* @param pName 产品标识
\* @return 返回具体的产品
\*/
public static Product createProduct(String pName){
switch (pName){
case "A":
return new ProductA();
case "B":
return new ProductB();
case "C":
return new ProductC();
default:
return null;
}
}
}
步骤4:调用简单工厂类
public class SimpleFactoryTest {
public static void main(String[] args) {
try {
SimpleFactory.createProduct("A").show();
} catch (NullPointerException e) {
System.out.println("没有A这款产品,无法生产~");
}
try {
SimpleFactory.createProduct("B").show();
} catch (NullPointerException e) {
System.out.println("没有B这款产品,无法生产~");
}
try {
SimpleFactory.createProduct("C").show();
} catch (NullPointerException e) {
System.out.println("没有C这款产品,无法生产~");
}
try {
SimpleFactory.createProduct("D").show();
} catch (NullPointerException e) {
System.out.println("没有D这款产品,无法生产~");
}
}
}
📣3.2.4 简单工厂模式优化
(1)简单工厂模式弊端
- 操作成本高:每增加一个接口的子类,必须修改工厂类的逻辑
- 系统复杂性提高:每增加一个接口的子类,都必须向工厂类添加逻辑
这两点弊端从前面的例子SimpleFactory
工厂类的实现,可以看出简单工厂模式
中对工厂类SimpleFactory
的维护成本有点大,因为实际中可能会很频繁的去更新具体产品类
,每一次变更都需要去修改工厂类,此时就可以利用Java反射机制
对简单工厂模式进行优化~
(2)简单工厂模式的优化思路
采用Java反射机制,通过传入子类全局定名(包名+类名)
动态的创建不同的子类对象实例
,从而使得在不增加产品接口子类和修改工厂类的逻辑的情况下还能实现了工厂类对子类实例对象的统一创建~
(3)简单工厂模式的优化步骤
步骤1:创建工厂类
采用Java反射机制对工厂类进行优化,主要是将className
即子类全局定名(包名+类名)
作为入参,通过Class.forName
方式获取类的java.lang.Class
实例对象,再通过Class
实例对象的getInstance
方法获取到具体子类的实例对象~
public class Factory {
public static Product getInstance(String className) {
Product realProduct = null;
try {
Class pClass = Class.forName(className);
realProduct = (Product) pClass.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return realProduct;
}
}
步骤2:调用工厂类
public class FactoryTest {
public static void main(String[] args) {
try {
Product productA = Factory.getInstance("com.justin.java.lang.ProductA");
productA.show();
} catch (NullPointerException e) {
System.out.println("没有A这款产品,无法生产~");
}
try {
Product productB = Factory.getInstance("com.justin.java.lang.ProductB");
productB.show();
} catch (NullPointerException e) {
System.out.println("没有B这款产品,无法生产~");
}
try {
Product productC = Factory.getInstance("com.justin.java.lang.ProductC");
productC.show();
} catch (NullPointerException e) {
System.out.println("没有C这款产品,无法生产~");
}
try {
Product productD = Factory.getInstance("com.justin.java.lang.ProductD");
productD.show();
} catch (Exception e) {
System.out.println("没有D这款产品,无法生产~");
}
}
}
优化结果:
使用
Java反射机制
优化简单工厂模式后,可以看到,不论具体产品类
更新多频繁,都不需要再修改工厂类
,从而解决了普通简单工厂模式操作成本高
和系统复杂性高
的问题~
🎵3.2.5 简单工厂模式再次优化
(1)再次优化背景
简单工厂模式的工厂类采用
Java反射机制
进行优化后,此时的仍然存在这样一个问题,子类的全局定名(包名+类名)
是写死的,但是实际上开发者在写代码时是很难提前预知所有的子类的全局定名(包名+类名)
的,因此需要进行二次优化~
(2)再次优化实现思路
通过
配置文件
方式,统一定义类名对应全局定名(包名+类名)
,将配置文件存放到资源目录下,程序运行时通过ClassLoader
类加载器动态获取到配置文件
中定义的子类的全局定名~
(3)再次优化实现步骤
再次优化步骤1:相关优化与第一次优化保持不变~
再次优化步骤2:配置类名对应全局定名(包名+类名)
创建属性配置文件Product.properties
//产品抽象类Product相关子类的全局定名(包名+类名)定义
ProductA = com.justin.java.lang.ProductA
ProductB = com.justin.java.lang.ProductB
ProductC = com.justin.java.lang.ProductC
注意:将Product.properties
需要存放在src/main/resources
资源目录下,若资源目录不存在则需要手动创建~
再次优化步骤3:修改调用工厂类
public class FactoryTest {
@Test
public void test() throws IOException {
ClassLoader classLoader = this.getClass().getClassLoader();
Properties prop = new Properties();
prop.load(classLoader.getResourceAsStream("Product.properties"));
String className = "";
try {
className = prop.getProperty("ProductA");
Product productA = Factory.getInstance(className);
productA.show();
} catch (NullPointerException e) {
System.out.println("没有A这款产品,无法生产~");
}
try {
className = prop.getProperty("ProductB");
Product productA = Factory.getInstance(className);
productA.show();
} catch (NullPointerException e) {
System.out.println("没有B这款产品,无法生产~");
}
try {
className = prop.getProperty("ProductC");
Product productA = Factory.getInstance(className);
productA.show();
} catch (NullPointerException e) {
System.out.println("没有C这款产品,无法生产~");
}
}
}
运行结果:
生产了产品A
生产了产品B
生产了产品C
📀3.3 代理模式中的动态代理实现
🔊3.3.1 什么是代理模式?
代理(Proxy)模式
是一种设计模式
,通过代理对象
来访问目标对象
,还可以在不修改目标对象
的情况下,对代理对象
进行拓展,增强目标对象
的功能~
什么?还是不太理解?
更通俗一点的说代理模式,就是想做某件事(
买火车票
),自己
能买(直接去火车站
买),却委托别人去买(没空还是代理点
买吧),还可以让别人帮自己做其他事(订好酒店)~
代理模式又分为静态代理、动态代理,往下介绍~
💥3.3.2 什么是静态代理?
(1)
静态代理
属于代理模式
的一种代理方式,需要代理对象
和目标对象
实现相同的接口
(2)静态代理
的代理类是由程序员编写源码,编译后即可获取到代理类的class字节码文件,也就是在程序运行前
就已经得到实际的代理类class字节码文件了
🎶3.3.2 什么是动态代理?
动态代理
(1)
动态代理
也属于代理模式
的一种代理方式,不过只需要目标对象
实现接口,代理对象
不需要实现接口~
(2)动态代理
的代理类编译后是没有class字节码文件的,而是在运行时利用Java反射机制
动态的生成代理类的class字节码文件~
如何快速更新自己的技术积累?
- 在现有的项目里,深挖技术,比如用到netty可以把相关底层代码和要点都看起来。
- 如果不知道目前的努力方向,就看自己的领导或公司里技术强的人在学什么。
- 知道努力方向后不知道该怎么学,就到处去找相关资料然后练习。
- 学习以后不知道有没有学成,则可以通过面试去检验。
我个人觉得面试也像是一场全新的征程,失败和胜利都是平常之事。所以,劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油!
以上面试专题的答小编案整理成面试文档了,文档里有答案详解,以及其他一些大厂面试题目
(1)
动态代理
也属于代理模式
的一种代理方式,不过只需要目标对象
实现接口,代理对象
不需要实现接口~
(2)动态代理
的代理类编译后是没有class字节码文件的,而是在运行时利用Java反射机制
动态的生成代理类的class字节码文件~
如何快速更新自己的技术积累?
- 在现有的项目里,深挖技术,比如用到netty可以把相关底层代码和要点都看起来。
- 如果不知道目前的努力方向,就看自己的领导或公司里技术强的人在学什么。
- 知道努力方向后不知道该怎么学,就到处去找相关资料然后练习。
- 学习以后不知道有没有学成,则可以通过面试去检验。
我个人觉得面试也像是一场全新的征程,失败和胜利都是平常之事。所以,劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油!
以上面试专题的答小编案整理成面试文档了,文档里有答案详解,以及其他一些大厂面试题目
[外链图片转存中…(img-j8OTL69Q-1715140039432)]
[外链图片转存中…(img-76XcCw1Q-1715140039432)]