java总结2024
java篇:
一、设计原则:
六大设计原则:
1、单一职责原则:
定义:即一个类或者模块只完成一个职责
好处:使用更灵活,效率更高
例子:例如篮球队,足球队,每个位置的人只需要负责自己位置的职责。
2、里氏替换原则:
定义:所有使用父类的地方可以使用子类的对象,子类可以扩展父类的功能,但是不是替换父类的功能,如果需求替换父类的功能,建议多用组合,少用继承
好处:(1)里氏替换原则就是针对继承而言的,如果继承是为了实现代码的重用,也就是为了共享方法,那么共享的父类的方法就应该保持不变,子类只能通过添加新方法来扩展功能。
(2)如果继承是为了多态,而多态的前提就是子类覆盖并重新定义父类的方法,我们应该将父类定义为抽象类,并定义抽象方法
3、迪米特原则:
定义:一个类对与其耦合或调用类所了解的越少越好
4、依赖倒置原则:
定义:下层模块引入上层模块的依赖,改变原有的自上而下的依赖方向
例子:基于上层提供的N个特征,提供符合特征的底层类,基于接口开发
5、开闭原则:
定义:类、方法、模块应该对扩招开发,对修改关闭,即添加一个功能,应该是在已有的代码基础上进行扩招,而不是修改
6、接口隔离原则:
定义:建立单一接口,不要建立臃肿庞大的接口,接口尽量细化,,同时接口的方法要尽量少。
好处:(1)接口要尽量小,不要违反单一职责原则,要适度
(2)接口要高内聚,提高模块、接口类的处理能力,减少对外交互。
(3) 定制服务,通过对高质量接口的组装,实现服务的定制化。
二、23种设计模式:(按面试出现频率排序)
1、单例模式:
定义:一个对象只能被实例化一次,并提供一个全局访问点
单例模式五种方式:
(1)饿汉式:
使用效率高,线程安全,但是不能延时加载。
因为还没有调用getInstance的时候,对象已经被实例化了。
public class Singleton1{
private Singleton1();
private static final Singleton1 single = new Singleton1();//此时已经实例化
public static Singleton1 getInstance(){
retuen single;
}
}
(2)懒汉式:
使用效率不高,线程安全,可以延时加载
因为懒汉式是在调用getInstance时,才实例化对象,但是如果多线程的情况下,当第一个线程抢到锁之后,对single对象进行校验,校验为true,实例化对象,释放锁,由下一个线程获得锁,需要重新进行校验,此刻single已经实例化过了,不在是null了,校验为false,返回single对象。由于在多线程的情况下,每个线程都要获得锁,释放锁,校验是否实例化,降低了使用效率。
public class Singleton2{
private Singleton2();
private static Singleton2 single = null; // 此时对象并没有实例化
public static synchronized Singleton2 getInstance(){
if(single == null){
single = new Singleton2();
}
return single;
}
}
(3)双重校验:
使用效率高,线程安全,可以延时加载
public class Singleton3{
private Singleton3();
private static Singleton3 single = null;
public static Singleton3 getInstance() {
if(single == null) {// 1
synchhronized(Singleton3.class){
if(single == null) {// 2
single = new Singleton2();
}
}
}
return single;
}
}
(4)静态内部类:
线程安全,使用效率高,延时加载
这种的好处,是解决了双重校验代码中的繁杂的if,private 静态内部类对外的getInstance方法返回静态类的instance对象,只有第一次访问的时候,才会被创建,类的初始化本身就是执行类的构造器的<clinit>方法,该方法是由javac编译器生成的,他是由一个类里面所有静态成员的赋值语句,和静态代码块组成的,jvm会保证一个类的clinit方法在多线程的环境下也可以正确的加锁同步,只有一个线程会执行clinit方法,在该线程执行的时候,其他线程进入阻塞等待状态,直到这个线程执行完,其他线程才被唤醒,但不会再进入clinit方法,也就是说一个加载器下,一个类只会被初始化一次。
public class Singleton4{
private static class lazyLoader{
private static final Singleton4 INSTANCE = new Singleton4
}
private Singleton(){};
public static final Singleton4 getInstance(){
return lazyLoader.INSTANCE;
}
}
(5)枚举类:
枚举本身是单例的,使用较少。一般用来定义内存字典的较多。
2、代理模式:
定义:代理模式为另一个对象提供一个替身或占位符,以控制对这个对象的访问,springAop采用的是动态代理
解决问题:当我们想要对一个业务进行横切性增强时,例如:增加请求与响应的日志、增加权限校验、增加远程请求对象封装等,可以采用代码模式实现,无需改变原有的类。
3、适配器模式:
定义:将一个类的接口,转换为客户期望的另一个接口,适配器让原本不兼容的两个类可以合作无间
4、模板模式:
定义:在一个方法中定义一个算法骨架,将一些步骤延迟到子类中,模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中某些步骤
5、中介者模式:
定义:使用中介者模式来集中相关对象之间复杂沟通和控制方法
6、建造者模式/生成器模式:
定义:使用生成器模式,可以封装一个产品的构造过程,并允许按步骤构造产品。
优点:将一个复杂的对象的创建过程封装起来,允许对象通过多个步骤来创建,并可以改变步骤。向客户隐藏产品内部表现,产品的实现可以替换,因为客户看到的只是一个抽象接口。
例子:可以类比购买车模型,可以直接购买成品,如果对内部构造比较感兴趣,可以购买乐高自行组装。
7、装饰模式:
定义:动态的将责任附加到对象上,若要扩展功能,装饰者提供了比继承更弹性的替代方案,
例如:煎饼,化妆等。
8、门面模式:
定义:提供了一个统一接口,用来访问子系统中的一群接口,外观定义了一个高层接口,让系统更容易使用。
9:命令模式:
定义:将“请求”封装成命令对象,以便使用不同的请求,队列,日志来参数化其他对象。
上游的命令不关心下游的命令谁去做,下游去做的也不关心上游的命令是谁发出的。上下游解耦。
10:职责链模式:
定义:使多个对现象都有机会处理请求,从而避免了请求的发送者和接收者的耦合关系,将这些对象连成一个链条,并沿着这条链传递请求,直到有对象处理它为止。
11:观察者模式:
定义:定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它所依赖者都会收到通知并自动更新。
12:访问者模式:
定义:表示一个作用于某个对象结构中的各个元素的操作,它使你在不改变各元素的类额前提下定义作用于这些元素的新操作。
三、反射:
1、什么是反射:
反射是在运行状态中,对于任意一个类,都可以获取该类的方法和属性;对于任意一个对象,都能调用其方法和属性,这种动态获取信息和动态调用对象方法的功能,称为java语言的反射机制
2、在什么地方用到反射:
在jdbc中,利用反射动态加载数据库驱动程序
开发中比较常见的入参的对象和我们要存储的对象属性基本相同,这种情况下可以使用反射,将对象添加进去,避免了代码中大量的get和set。
3、如何用反射将一个对象里属性相同的值,赋值给另外一个对象:
①使用反射实现,BeanUtil工具
Public class BeanUtil{
public static void convert(Object orginObj, Object targetObj) throws Throwable{
Class orginClazz = orginObj.getClass();
Class targetClazz = targetObj.getClass();
Map<String, Field> fieldCache = new HashMap<>();
Field[] orginFields = orginClazz.getDeclaredFields();
for(Field field : orginFields) {
fieldCache.put(field.getName(), field);
}
for(Field targetField : targetClazz.getDeclaredFields()) {
if(!fieldCache.containsKey(targetField.getName())) {
continue;
}
Field orginField = fieldCache.get(targetField.getName());
orginField.setAccessable(true);
targetField.setAccessable(true);
targetField.set(targetObj, targetField.get(OrginObj));
}
}
}
②还可以使用mapStruct工具
步骤:Ⅰ.先引入org.mapStruct依赖
Ⅱ.@Mapper
public interface CarMapper{
CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
@Mapping(source = "numberOfSeats", target="seatCount");
@Mapping(source="type", taarget = "typeDto")
CarDto carToCarDto(Car car);
}
其底层原理依旧是setxxx(getxxx());的方式,可以避免手写。
③还可以使用JsonUtil工具,转成String,在通过JsonUtil.toObject(指定的实体类)实现
4、反射机制和new对象在类加载的过程中有什么区别
不管是反射还是new对象的形式在类加载的过程中,都需要打开&检查class文件,区别就在于new对象的时候,是在类加载去做的,反射是在运行期做的。
5、jvm类加载流程和内存结构:
类加载器ClassLoader(将class文件加载到jvm中)
加载-->验证-->准备-->解析-->初始化
new对象和反射都需要解析class文件,class文件中包含类相关的所有相关信息,如:类名,包名,属性,方法等。class对象就是我们获得某一各类型的字节码的引用对象。
6、反射的方式有几种:
三种
1、Class personClazz = Person.class();
2、Person person = new Person();
Class personClazz1 = person.getClass();
3、Class personClazz2 = class.forName("com.xxx.xxx.Person");
personClazz == personClazz1;
personClazz == personClazz2;
三种方式获得的class实例相同
6、反射的步骤:
无论是new对象还是使用反射机制,创建实例对象的步骤,都需要以下三步:
1、加载class文件
2、查找入参匹配的构造函数
3、通过构造函数创建实例对象
Class personClazz = Class.forName("com.xxx.xxx.Person");
Constructor constructor = personClazz.getContructor();
Person person = (Person) constructor.newInstance();
7、单例模式可以被破坏么,如何破坏?
通过反射可以在运行期动态加载想要加载的类,通过反射可以破坏单例模式,是一个对象可以多次实例化
通过getDeclaredConstructor的方式即可
第一步:通过反射创建对象
Class singlePersonClazz = Class.forName("com.xxx.Person");
第二步:获得构造函数
Constructor constructor = singlePersonClazz.getDeclaredConstructor();// 即可获得私有化构造函数,但无使用权限
constructor.setAccessable(true);// 可以使用私有构造函数,也可以再次实例化对象。
8、getDeclaredField和getField有什么区别
getDeclaredField可以获取一个类里的所有修饰的字段,但无法获取父类的字段
getField可以获取一个类里所有的public修饰的字段,也可以获取到父类字段,但无法获取到public以外的字段。
9、什么是双亲委派机制
执行类加载的时候,从哪个类开始的,也从哪个类结束,比如:某个类从Extension ClassLoader开始加载,Extension ClassLoader委派给父类Bootstrap ClassLoader,若BootStrap ClassLoader依旧没有加载上,则开始向下加载,加载到Extension ClassLoader结束。
10、为什么需要spi破坏双亲委派机制:
SPI是Service provider Interface的缩写,是java提供的一套用于被第三方开发或者实现的API接口,可以用于模块化,解耦,插件机制等。如果本身是启动类加载器,双亲委派机制,无法加载到下面的层级,通过线程,可以获得当前线程的应用类加载器,应用类加载器可以去获得ClassPath里的相关类,即可见性原则,上层类加载器对下层类加载器是可见的,然而下层类加载起对上层类加载器是不可见的,所有由java团队设计了线程上下文类加载器,如果不破坏双亲委派机制,无法从ClassPath里获取到相应的类
四、泛型:
1、什么是泛型,为什么要用泛型:
早期java使用Object类型来代表任意类型,但是向下转型有强转的问题,线程也不安全,所以针对List、Set、Map等集合类型,它们对存储的元素是没有任何限制的,假如向List中存储一个Dog类型的对象,但是有人把Cat类型也存储到List中,编译上没有任何语法错误,所以把所有使用该泛型参数的地方都被统一化,保证类型一致,如果未指定具体的类型,默认是Object类型,集合体系中是所有的类型都增加了泛型,泛型也主要用于集合。
2、什么是泛型类:
泛型类就是把泛型定义在类上,用户使用该类的时候,才把类型明确下来,用户明确了什么类型,该类就代表什么类型,也不用担心强转的问题和运行时转换异常的问题。
3、为什么使用泛型方法:
除了在类上使用泛型,我们可能就要仅仅某个方法上需要使用泛型,外界仅仅关心该方法,不关心其他的属性,如果在整个类上定义泛型,未免小题大做,所以采用泛型方法。
4、什么是类型擦除:
泛型是提供给java编译器使用的,它可以作为类型的限制,让编译器在源码级别上挡住非法类型的数据,在java1.5之后,编译器编译完带有泛型的java程序后,生成的class文件里将不再带有泛型的信息,这个过程叫做类型擦除,擦除掉“T”由Object替代,编译器会自动生成一个桥方法,因为擦除掉之后,就会失去了类型的校验,可以用method.briged()来判断一个method是不是桥方法。
5、类型通配符(不常用)
List<?>表示类型未知的list,可以匹配任何类型的元素,声明List<?>后,不能向集合中添加元素,因为无法确定集合的元素类型,唯一例外的是可以添加null
6、泛型的上限和下限
泛型的上限:
格式:<? extends ? 类> 对象名称
意义:只能接收该类型及其子类
泛型的下限:
格式:<? super ?类> 对象名称
意义:只能接收该类型及其父类类型
五、ArrayList和LinkedList:
1、ArrayList的底层逻辑:
创建一个ArrayList就是创建了一个空数组,Object [],初始值的大小是0,存入一个元素,首次扩容至默认值为10,之后按1.5倍向下取整进行扩容。
2、为什么是按照1.5倍扩容?
通过源码来看,int newCapacity = oldCapacity + (oldCapacity >> 1);
右移一位表示÷2,左移一位表示乘2,
举例说明:32 = 2^5,BIN = 0100000。即从右向左查,1在下标5的位置,即2^5;右移一位之后,BIN变成0010000也就是2^4 = 16;左移一位BIN变成01000000 1在下标6的位置,也就是2^6 = 64。
3、ArrayList与LinkedList的区别?
①ArrayList的底层是数组,LinkedList的底层是双向链表。
②ArrayList增删比较慢,但是查询很快,LinkedList增删很快,查询很慢
③ArrayList的内存空间是连续的,LinkedList的内存空间可以是不连续的
④两者都不是线程安全的。
4、为什么ArrayList增删比较慢,查询较快,而LinkedList的增删很快,查询很慢:
ArrayList的查询比较快是因为ArrayList的内存空间是连续的,CPU内部缓存结构会缓存连续的内存片段,可以大幅降低读取内存的性能开销。而linkedList查询时根据index,如果index在前半段则是从头节点开始向后遍历,如果index在后半段则是从尾节点开始遍历。ArrayList增删的时候,可以有两种方式,一种是指定index插入的,一种是尾插法,无论是那种方法,ArrayList在新增的时候,都会去调用ensurecapacityInternal来校验数组长度,如果长度不够,会进行扩容,以确保已存在的数组有足够的容量来存储这个新元素,旧数组会被使用Arrays.copyOf方法复制到新的数组中去,现有的数组引用指向了新的数组。而linkedList在新增的时候采用的是尾插法,并且无需遍历尾节点,因为在LinkedList的代码中已经保存了尾节点,直接在尾节点后插入就可以了。
5、如何复制一个ArrayList到另外一个ArrayList中去:
根据业务场景的不同,可以采用深克隆和浅克隆,或者ArrayList的构造方法,或者Collection的copy方法
6、既然说ArrayList和LinkedList都是线程不安全的,为什么还要使用呢
因为在使用ArrayList的场景尽量使用多查询少增删的地方,不会太涉及增删操作。而如果碰到频繁的增删操作的时候采用linkedList,如果需要使用线程安全的可以使用vector和CopyOrWriteArrayList。
7、ArrayList和LinkedList如何相互转换
采用构造方法转换。或者list的add方法
六、HashMap:
1、HashMap的数据结构
数组加链表加红黑树
其中链表为单项链表,红黑树则为双向的,当链表长度超过8时,链表转换为红黑树
2、HashMap的底层逻辑及工作原理
hashMap的底层原理是hash数组➕单项链表实现的,数组中每个元素都是链表,由node的内部类Map.entry接口实现,hashMap通过put和get方法存储和获取。
①存储对象时,将K / V键值对传给put()方法,调用hash(K)方法计算K的hash值,然后结合数组长度,计算数组下标,
②根据元素个数调整数组大小,当元素的个数大于capacity*loadFactor时,会进行resize的二倍扩容。
③Ⅰ、如果K的hash值在hashMap中不存在则执行插入,若存在,则发生碰撞。
Ⅱ、如果K的hash值相同,且两者equals返回为true,则执行更新操作。
Ⅲ、如果K的hash值相同,且两者的equals的返回为false,则在尾部插入数据。
获取对象的时候,将K传给get()方法:
①调用hash(K)方法,计算K的hash值,从而获取该键值的所在链表的数组下标,
②顺序遍历列表,equals()方法,查找相同Node链表中K值对应的V值。
3、hash如何实现的
通过HashCode()的高16位异或低16位实现的,可以有效的节省系统开销,规避碰撞,高16位异或低16位的的也叫扰动函数,高16位的值右移到低16位,原低16位的值去掉,现在低16位的值变成原高16位的值,现在的高16位的值补0。然后做异或操作,当前是1,现在还是1的变成0,原先是0的现在还是0的依旧是0,原先是1,现在是0的变成1,原先是0现在是1的也变成1
4、两个对象hashCode相同会怎么样?
hashCode相同,并不代表两个对象相等,用equals方法进行比较,所以两个对象所在的数组下标相同,就会发生碰撞,因为HashMap是使用的链表存储对象,所以这个node会存储到链表中。
5、hashMap的table容量如何确定
table数组的大小是用capacity这个参数决定的,默认是16,也可以构造时传入,最大值是1<<30.10亿多。
装载因子LoadFactor,主要用来确认table数组是否需要动态扩展的,默认值是0.75。比如说table的大小是16,默认值是0.75,阈值就是12,当table的实际大小超过12时,就需要进行扩容。
扩容时调用resize()方法,将table的长度变为原来的两倍。
6、hashMpa里put和get的过程。
hashMap的put的过程,首先将K / V键值传给put方法,put方法调用hash(K),计算K的hash值,结合数组长度,计算数组下标。
根据元素个数,调整数组大小,当元素个数大于capacity*loadfactor时,进行resize的二倍扩容。
如果K的hash值在hashMap中不存在,则直接可以在链表的尾部插入数据,或者红黑树中。
如果存在,则调用equals方法,如果返回true,则进行更新操作。如果返回为false,则在链表后插入数据或者在红黑中添加数据。
get的过程是将K传给get()方法,调用hash(K)方法,计算K的hash值,从而获得该键值的所在链表的数组下标。顺序遍历列表,用equals()方法,查找相同的Node链表中K值对应的V值。
7、可以使用二叉树么,为什么使用红黑树,不用二叉树:
可以使用二叉树,不过二叉树在特殊情况下可能会变成一个线性结构,和链表一样了,这样会导致遍历查询变得非常慢
8、什么是红黑树
①只有红色和黑色两种节点
②所有的叶子节点都是黑色的
③根节点也是黑色的
④红色节点的左右子节点都是黑色的
⑤除去红色的节点,任意一个节点到其中一个节点的距离都是相同的
9、hashMap、LinkedHashMap、TreeMap有什么区别及使用场景
hashMap是比较常用的,hashMap只允许一条记录的建为null,允许多条记录的值为null,hashMap不支持线程同步,即在任一时刻,有多个线程在同时写HashMap可能会导致数据不一致,如果想要同步的话,可以使用Collections的synchronized方法使HashMap具备同步能力。或者使用ConcurrentHashMap,在map中插入,删除,定位数据的时候,用hashMap的比较多。
LinkedHashMap会保存存储的顺序,遍历LinkedHashMap的时候,也是按照存储的顺序进行输出的。LinkedHashMap遍历的速度和容量无关,只和数据相关。而hashMap的遍历速率和他的容量相关。输出的顺序和输入的顺序一致的时候使用LinkedHashMap
TreeMap实现sortMap接口,可以把保存的数据按照键值排序,默认升序,遍历TreeMap的时候,数据已经是排序后的,也可以指定排序的比较器。
10、hashMap和hashTable有什么区别
hashMap是线程不安全的,hashTable是线程安全的,hashMap的效率更高,。
hashMap最多只允许一条记录的键为null,可以有许多值为null。hashTable则不允许有空键值对。
hashMap默认初始化数组大小是16,hashTable的默认初始化大小是11,hashMap是二倍扩容,hashTable是二倍加1扩容
hashMap需要重新计算hash值,hashTable则直接使用对象的hashCode。
11、HashMap和ConcurrentHashMap有什么区别
concurrentHashMap是在hashMap的基础上加了锁。原理是并无太大区别,hashMap可以空键值对,concurrenthashMap不允许有空键值对。
12、concurrentHashMap锁机制怎么理解
concurrentHashMap采用的是Node+CAS+synchronized来保证并发安全的,直接用table数组保存键值对,当hashEntry的长度超过阈值,链表转为红黑树,底层变更为数组+链表+红黑树。
13、hashmap什么时候采用红黑树,什么时候不用红黑树
当hashMap中的元素超过8个的时候,采用红黑树,当元素为6的时候退化为链表,其实6和8都是大量的经验结果,是经过数学计算的,当元素小于8的时候,使用红黑树的话,元素比较少,新增的效率比较慢,而链表结构是可以保障查询性能的。如果一个hashMap不停的存储和删除的话,链表个数始终在8徘徊,就会导致链表和红黑树之间的不断切换,效率也会很低,所以有一个中间值7,防止链表和红黑树之间的频繁转换。
14、红黑树是如何做到自平衡的,如何理解红黑树的左旋和右旋
当红黑树插入或者删除节点的时候,可能会造成不平衡,这个时候红黑树就可以通过左旋和右旋以及变色来维持自平衡,假设有两个子节点,分别是x和y,y是x的右子节点,这个时候可以进行左旋操作,通过左旋使x和y互换,变成x是y的左子节点,这个过程就是左旋,同理,假设最开始的时候,y是x的左子节点,通过右旋,是x和y互换,变成x是y的右子节点,这个过程叫做右旋。
15、jdk1.7采用的是头插法,jdk1.8采用的是尾插法,为什么头插法会导致死循环,尾插法不会?
因为当有两个线程的时候,分别是线程A和线程B,两个线程同时对table进行数据迁移,两个线程都读取了待迁移元素A,和下一个元素B,线程A正常执行元素迁移,线程B由于某种原因,还未执行数据迁移操作,当线程A执行完之后,会将当前的table数组,替换为新的Table数组,等线程B开始执行数据迁移操作的时候,当前使用的table数组就不再是旧的Table数组,而是线程A执行后的新的table数组,由于原本的关系是A的next是B,现在变成B的next是A,会形成循环链表,迁移操作将无限循环。
框架篇:
七、Mybatis
一、采用JDBC方式访问数据库
使用JDBC的5个步骤
①注册驱动和数据库信息(jdbcDriver)
②获得Connection,并使用他打开statement对象
③通过statement对象执行sql语句,并获得结果对象的ResultSet
④通过代码将ResultSet对象转化位POJO对象
⑤关闭数据库资源
优点:
①我们只需要会调用JDBC接口中的方法即可,使用简单
②使用同一套java代码,进行少量的修改就可以访问其他JDBC支持的数据库
缺点:
①代码量很大,编码麻烦
②需要我们对异常进行正确的捕获并关闭链接。
采用Hibernate访问数据库
使用Hibernate的3个步骤
①引入Hibernate的Maven依赖
②在hibernate.cfg.xml配置文件中配置数据库数据源
③编写User.hbm.xml配置文件,配置User和tb_user表的映射关系
优点:
①将映射规则分离到xml/注解中,减少代码耦合度。
②无需管理数据库链接,只需配置相应的xml配置文件
③会话只需要操作Session对象即可,关闭资源也只需要关闭Session即可。
缺点:
①全表映射不便利,更新是需要发送所有字段
②无法根据不同的条件组装不同的sql
③对于多表关联和复杂的sql查询支持较差
④HQL性能较差,无法优化sql