2021年Java中高级面试必备知识点总结
在这个部分总结了2019年到目前为止Java常见面试问题,取其面试核心编写成这份文档笔记,从中分析面试官的心理,摸清面试官的“套路”,可以说搞定90%以上的Java中高级面试没一点难度。
本节总结的内容涵盖了:消息队列、Redis缓存、分库分表、读写分离、设计高并发系统、分布式系统、高可用系统、SpringCloud微服务架构等一系列互联网主流高级技术的知识点。
目录:
(上述只是一个整体目录大纲,每个点里面都有如下所示的详细内容,从面试问题——分析面试官心理——剖析面试题——完美解答的一个过程)
部分内容:
对于每一个做技术的来说,学习是不能停止的,小编把2019年到目前为止Java的核心知识提炼出来了,无论你现在是处于什么阶段,如你所见,这份文档的内容无论是对于你找面试工作还是提升技术广度深度都是完美的。
不想被后浪淘汰的话,赶紧搞起来吧,高清完整版一共是888页,需要的话可以点赞+关注
SimpleFactory.create(“A”).doSomeThing();
SimpleFactory.create(“B”).doSomeThing();
SimpleFactory.create(“C”).doSomeThing();
}
}
可以看到,SimpleFactory
中依然包含if-else
,如果没有SimpleFactory
,那么这些if-else
就是在客户端里,每次有新产品加入,就需要修改客户端的代码。但是有了SimpleFactory
,修改SimpleFactory
就好了,这就是隔离变化,将对象的创建与客户端解耦。
可以去掉SimpleFactory
中的if-else
吗?非常遗憾的说,无法去掉,个人观点,复杂度是守恒的,只能被转移,无法被消除。如果当前产品比较固定,或者将来的变化不大,少量的if-else
存在也是可以容忍的。
2、Map缓存产品对象
在某些场景下,if-else
可以隐式存在。比如创建一个对象不需要外界传递参数,不需要重复创建,无状态,以单例的形式存在,这时就可以用一个Map
来缓存对象。
public class SimpleFactory {
public static IProduct getInstance(String name) {
IProduct product = INSTANCE_MAP.get(name);
if (product == null) {
throw new UnsupportedOperationException(“not match product, name=” + name);
}
return product;
}
private static final Map<String, IProduct> INSTANCE_MAP = new HashMap<>();
static {
INSTANCE_MAP.put(“A”, new ProductA());
INSTANCE_MAP.put(“B”, new ProductB());
}
}
看似if-else
没有了,其实是转移到了Map
中,此时如果新增产品,就需要改到Map
。
3、反射创建对象
用反射也可以让代码中没有if-else
。再次强调if-else
没有被消除,而是转移到了class
和实例对象中,且是以性能为代价。这次新增产品也不需要改到SimpleFactory
,但是增加了一点客户端交互成本,客户端必须知道生产的产品对象的class
对象是什么。
public class SimpleFactory {
public static IProduct getInstance2(Class<? extends IProduct> clazz) {
if (clazz == null) {
throw new IllegalArgumentException(“class is null”);
}
try {
return clazz.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
4、反射+Map创建单例对象
单纯用Map缓存事先创建好的对象,无法达到懒加载的效果,而单纯用反射,每次创建的对象都是新的,如果不需要每次创建新的对象,且还想有懒加载的效果,有新产品也不想改SimpleFactory
,那就可以用反射+Map,看下面代码,是不是有些熟悉,单例模式的double check
:
public class SimpleFactory {
public static IProduct getInstance(Class<? extends IProduct> clazz) {
if (clazz == null) {
throw new IllegalArgumentException(“class is null”);
}
IProduct product = INSTANCE_MAP.get(clazz.getSimpleName());
if (product != null) {
return product;
}
synchronized (SimpleFactory.INSTANCE_MAP) {
// double check
product = INSTANCE_MAP.get(clazz.getSimpleName());
if (product != null) {
return product;
}
try {
product = clazz.newInstance();
INSTANCE_MAP.put(clazz.getSimpleName(), product);
return product;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
private static final Map<String, IProduct> INSTANCE_MAP = new HashMap<>();
}
5、注解+反射+Map创建对象
前面3、4用到反射创建对象,客户端需要知道某个产品的class
是什么,通过class
的名称获取实例对象,且这个名称不能自定义。如果想要对象名称自定义,可以使用注解:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Product {
public String name() default “”;
}
@Product(name = “A”)
public class ProductA implements IProduct {
@Override
public void doSomeThing() {
System.out.println(“ProductA…doSomeThing”);
}
}
@Product(name = “B”)
public class ProductB implements IProduct {
@Override
public void doSomeThing() {
System.out.println(“ProductB…doSomeThing”);
}
}
@Product
public class ProductC implements IProduct {
@Override
public void doSomeThing() {
System.out.println(“ProductC…doSomeThing”);
}
}
public class SimpleFactory {
public static IProduct getInstance(String name) {
IProduct product = INSTANCE_MAP.get(name);
if (product == null) {
throw new UnsupportedOperationException(“not match product, name=” + name);
}
return product;
}
private static final Map<String, IProduct> INSTANCE_MAP = new HashMap<>();
static {
init();
}
private static void init() {
System.out.println(“init…”);
Set<Class<?>> classSet = ClassUtil.scanPackageByAnnotation(“com.stefan.designPattern.factory”, Product.class);
for (Class<?> clazz : classSet) {
boolean isProduct = false;
in : for (Class<?> i : clazz.getInterfaces()) {
if (IProduct.class.equals(i)) {
isProduct = true;
break in;
}
}
if (!isProduct) {
continue;
}
Product product = clazz.getAnnotation(Product.class);
String name = product.name();
// 虽然指定name default是“”,但是这里取的话会隐式new String()
// 所以必须用equals比较,不能用==
if (“”.equals(name)) {
name = clazz.getSimpleName();
}
try {
INSTANCE_MAP.put(name, (IProduct) clazz.newInstance());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
这里用到了hutool
的ClassUtil.scanPackageByAnnotation
扫描类的工具,方法使用很简单,但是过程复杂一些,包扫面首先会获取当前类加载器并调用getResources
获取指定目录下的所有类资源,如果是目录,扫描目录下的类文件或者jar文件,如果是jar包,则直接从jar包中获取类名(比较考验类加载机制和反射基本功)。
6、简单工厂在JDK中的体现
JDK中用到工厂模式的地方老多了,下面简单举几个例子:
(1)Calendar.getInstance()
,可以根据不同时区TimeZone
、地区Locale
获取不同Calendar
实例(不是单例),最终会调用到CalendarProvider
的getInstance
:
(2)DateFormat
的创建,最终会调用到DateFormatProvider
创建对象的方法:
(3)几个包装类型,如String
、Interger
、Long
、Double
等的valueOf
方法都是会创建对象或者从缓存中获取。
(4)线程工厂java.util.concurrent.ThreadFactory#newThread
。
(其他开源框架如Tomcat中也会大量用到简单工厂,暂时不再举例。)
7、简单工厂优缺点
(1)优点:简单工厂的优点就是简单,容易理解,还有就是解耦,隔离变化。
(2)缺点:简单工厂一般以静态方法为主,工厂类单一,难以扩展;当产品基数增多时,工厂类代码可能会比较臃肿,虽然可以通过缓存、反射、自定义注解等方式缓解if-else
,但是需要牺牲一些内存和性能,且有一定的使用场景和开发成本。
工厂方法,外国人起的破名字,和抽象工厂傻傻分不清楚,且看定义和类图:
工厂方法就是为了解决简单工厂难以扩展问题的,依然是生产同类产品,但是每个产品生产较为复杂且细节不尽相同,所以给每个产品分配一个专属工厂:
1、通用写法
产品类和简单工厂一样,假装创建对象的过程复杂且不同;每个产品一个工厂,为了面向接口编程,可扩展性,所以建立一个工厂接口,所有工厂实现这个接口:
public interface IProduct {
void doSomeThing();
}
public class ProductA implements IProduct {
@Override
public void doSomeThing() {
System.out.println(“ProductA…doSomeThing”);
}
}
public class ProductB implements IProduct {
@Override
public void doSomeThing() {
System.out.println(“ProductB…doSomeThing”);
}
}
public class ProductC implements IProduct {
@Override
public void doSomeThing() {
System.out.println(“ProductC…doSomeThing”);
}
}
public interface IFactory {
IProduct create();
}
public class AFactory implements IFactory {
@Override
public IProduct create() {
return new ProductA();
}
}
public class BFactory implements IFactory {
@Override
public IProduct create() {
return new ProductB();
}
}
public class CFactory implements IFactory {
@Override
public IProduct create() {
return new ProductC();
}
}
public class Test {
public static void main(String[] args) {
IFactory factoryA = new AFactory();
IProduct productA = factoryA.create();
productA.doSomeThing();
IFactory factoryB = new BFactory();
IProduct productB = factoryB.create();
productB.doSomeThing();
}
}
很明显类增加了不少,也使得整体变复杂了,再看客户端怎么使用工厂生产产品,需要先实例化对应工厂类,然后创建对应产品。如果客户端不知道使用哪个工厂那还得if-else
选择工厂,咋还回去了,越来越复杂了。。。那就再给工厂加个简单工厂,把if-else
移到一个简单工厂里,生产不同的工厂方法,真的是复杂了,达咩!注意工厂方法的使用场景!
工厂方法的出现是因为每个对象的创建过程较复杂且有自己的创建细节,所以才给每个产品对象分配了一个工厂;且客户端调用、是有不同的场景对应不同的工厂方法,不需要再在工厂方法上面加简单工厂,这是最佳适配。
如果后期新增产品,同时新增工厂,不需要改到旧代码。
2、工厂方法在slf4j+logback中的体现
在开源日志框架门面slf4j
中提供了Logger
接口和ILoggerFactory
供第三方框架扩展(如logback
)。不同的工厂负责生产不同的日志框架,基本类图如下:
可以看出slf4j+logback
既使用了工厂方法,又使用了简单工厂去管理工厂方法。
3、工厂方法优缺点
(1)优点:易扩展,新增产品同时新增工厂,隔离变化,隔离复杂,理想状态完全符合开闭原则、迪米特原则(最少知道原则)、依赖倒置原则。
(2)缺点:类太多了,增加了代码复杂度;更加抽象,不易理解;依然只能生产一种产品,种类单一。
抽象工厂,可以生产不同类型的产品(实现于不同抽象接口),这些产品之间有一定的联系,可以归纳为一个族系的产品。
抽象工厂因为可以生产多种产品,所以不会像工厂方法那样有那么多的工厂类。不过也是因为产品的族系越来越复杂,才使得抽象工厂有了用武之地。
如有两条产品系列1和2,每个系列有一个抽象工厂,每个工厂可以生产A和B。
1、通用写法
public interface IProductA {
void doSomeThing();
}
public interface IProductB {
void doSomeThing();
}
public class ProductA1 implements IProductA {
public void doSomeThing() {
System.out.println(“ProductA1…doSomeThing”);
}
}
public class ProductA2 implements IProductA {
public void doSomeThing() {
System.out.println(“ProductA2…doSomeThing”);
}
}
public class ProductB1 implements IProductB {
public void doSomeThing() {
System.out.println(“ProductB1…doSomeThing”);
}
}
最后
给大家送一个小福利
附高清脑图,高清知识点讲解教程,以及一些面试真题及答案解析。送给需要的提升技术、准备面试跳槽、自身职业规划迷茫的朋友们。
{
public void doSomeThing() {
System.out.println(“ProductA1…doSomeThing”);
}
}
public class ProductA2 implements IProductA {
public void doSomeThing() {
System.out.println(“ProductA2…doSomeThing”);
}
}
public class ProductB1 implements IProductB {
public void doSomeThing() {
System.out.println(“ProductB1…doSomeThing”);
}
}
最后
给大家送一个小福利
[外链图片转存中…(img-bgbEv4Rg-1715610460643)]
附高清脑图,高清知识点讲解教程,以及一些面试真题及答案解析。送给需要的提升技术、准备面试跳槽、自身职业规划迷茫的朋友们。
[外链图片转存中…(img-9xVIQo31-1715610460643)]