JAVA基础
# JAVA基础
# 1.String,StringBuilder和StringBuffer
# 区别对比
- String
String是final修饰的,不可变的(它的对象生成在方法区),不可以对String对象进行操作或者赋值。当使用substring()或者是split()等方法时会创建新的String对象。
- StringBuilder,StringBuffer
StringBuilder是线程不安全的;而StringBuffer是线程安全的,因为它的每个方法都是synchronized修饰的,但是效率低。使用时一般有限考虑StringBuilder,而在多线程使用共享变量下则需要使用StringBuffer。对象不需要修改则使用String。
- 相互转换
String str="ssg"; //String和StringBuilder转换
StringBuilder s=new StringBuilder();
s.append(str);
String str2=s.toString();
char[] s1=str.toCharArray(); //String和字符数组转换
String str3=new String(s1,0,s1.length);
2
3
4
5
6
7
- 字符串判空
if(fid!=null&&!"".equals(fid)){}
# String字符串API
String.valueOf(char[] data):将char[]数组转换成String
String.substring:substring(a,b):返回下标为a到b-1之间的string
String.replace:replaceAll("ss","bb")直接把ss全部替换bb
利用String找到十进制数的最高位和最低位数字:
int a=7893; String s=String.valueOf(a); int high=s.charAt(0)-'0'; int low=s.charAt(s.length()-1)-'0';1
2
3
4String按照字典序重排
char[] chars=s.toCharArray(); Arrays.sort(chars); s=String.valueOf(chars);1
2
3
# StringBuilder API
- StringBuilder.delete(int start,int end)从原字符串删除下标为start到end-1的子串。
- StringBuilder.insert(int start,String s)在原字符串下标start位置插入字符串s(首字符下标为start)。
# 2.列表与队列
无论是队列、堆、栈都可以调用remove(Object o)删除指定元素。
# ArrayList与LinkedList区别
ArrayList基于动态数组,适合使用下标搜索,随机访问。扩容时头部插入数据损耗性能,而使用尾插法效率更高。
LinkedLIst基于链表,适合插入删除。实现了Deque接口,可以当作双端队列使用。
# List转换
- 数组转换成List类型使用Arrays.asList()方法。
List<String> strList;
String[] arr;
strList=Arrays.asList(arr);
2
3
- List转指定类型数组
String[] strs=list.toArray(new String[list.size()])
- List转int[]
int[] arr=list.stream().mapToInt(Integer::valueOf).toArray();
# List容器API
list容器(数组线性表):List<List<Integer>> list=new ArrayList<>()
- list.add(obj)向尾部添加元素,list.add(int index,Object obj)向index索引位置添加元素。特别要注意的是,add()方法加入的是对象的地址,要加入不同对象需要new新的对象。add()允许添加重复元素。
- list.remove(int i)删除索引i位置的元素,可以这样子写remove(list.indexOf(Object a))。另一种是remove(Object a),如果是对象引用则直接传对象引用作为参数,若传的是基本类型,则需要先转化成包装类remove(Integer.valueOf(250))。
- list.size()获取list的长度
- list.get(int i)返回索引下标为i的元素
- list.contains(int i)容器中含有i则返回true,可以用来去重。
- List<object> list=new ArrayList<>(Arrays.asList(object a,object b,object c...)):一种初始化写法,将数组转换集合
- Arrays.sort(nums):对数组排序
# Queue队列API
LinkedList类实现了Queue接口,因此可以使用LinkedList实现队列:
- Queue<TreeNode> queue=new LinkedList<TreeNode>();
- queue.offer();插入元素到队尾
- queue.poll();返回队首元素并删除
- queue.isEmpty();判空
- queue.size();返回队列大小
# Deque双端队列
Deque是LinkedList类实现的一个双端队列的接口,Deque<Integer> deque=new LinkedList<>()。
首元素 首元素 尾元素 尾元素 抛出异常 特殊值 抛出异常 特殊值 插入 addFirst(e) offerFirst(e) addLast(e) offerLast(e) 删除 removeFirst() pollFirst() removeLast() pollLast() 检查 getFirst() peekFirst() getLast() peekLast()
# 优先级队列PriorityQueue与Comparator比较器
对二维数组进行排序,先按照第一列元素降序,如果第一列元素相同则按照第二列元素升序
Arrays.sort(people,new Comparator<int[]>(){
public int compare(int[] o1,int[] o2){
if(o1[0]!=o2[0]) return o2[0]-o1[0];
return o1[1]-o2[1];
}
});
2
3
4
5
6
优先级队列PriorityQueue升序排序,出队列第一个元素即为最小元素:
PriorityQueue<Integer> queue=new PriorityQueue<>(new Comparator<Integer>(){
public int compare(Integer o1,Integer o2){
return o1-o2;
}
});
Integer minVal=queue.poll();
2
3
4
5
6
# 3.HashMap与TreeMap
# 3.1HashMap
# HashMap刷新流程
- put()
首先根据key值的hashcode计算出hash值,也就是桶的位置;然后进行判断空;接着遍历该桶中的元素,先判断hashcode是否相同,然后再用equals方法判断,如果为重复元素key则进行覆盖;否则将新的键值对插入到该桶中。
- get()
get()方法和put方法类似,只不过不需要判空。
- rehash
HashMap通过resize()进行扩容,通过transfer()方法把旧数组拷贝到新数组当中。其中旧数组元素经过rehash后,新索引下标要么在原来同一个桶索引的位置,要么是在新的索引位置(因为数组长度变化,旧元素计算的哈希值也可能变化。类似于mod 数组长度的原理。但是JDK1.8不需要重新计算哈希值)
# HashMap容器API
- hashmap.put(i,j):添加元素key=i对应value为j
- hashmap.get(i):访问i对应的value,若不存在key则返回null,可以在初次插入用来判空
- for(Integer(key类型) i:hashmap.keySet()) hashmap.get(i)
- for(Integer(value类型) i:hashmap.values())
- containsKey()/containsValue() 容器含有对应的key值或者value则返回true,用来判空
# 3.2TreeMap
# TreeMap性质
TreeMap具有如下性质:
- 插入元素时,在内部会对Key进行排序,默认是升序。
- 通过firstKey()和LastKey()两个API分别获取第一个key和最后一个key的值(最小key和最大key)
- cellingKey(K num):返回大于等于num的最小key值;floorKey(K num):返回小于等于num的最大key值
- pollFirstEntry():删除key最小的元素。
# 4.Pair<TreeNode,Integer>
使用Pair类型对象返回一个不同类型对象的元组,类似于HashMap的每一组K-V值。
作为递归函数的返回值类型用于传递更多的信息:
Pair<TreeNode,Integer> node;
TreeNode tree=node.getKey();
Integer val=node.getValue();
2
3
# 5.Stream流
# Collectors#groupingBy
List集合通过stream流重新收集构造Map,groupingBy指定容器数据类型的哪个字段作为key,value为聚合的List集合。key->groupId,value->List<GatewayServerDetailVO>
Map<String, List<GatewayServerDetailVO>> gatewayServerDetailMap = gatewayServerDetailVOS.stream()
.collect(Collectors.groupingBy(GatewayServerDetailVO::getGroupId));
2
# List集合stream流提供的方法
- filter():筛选过滤集合中满足的item,并return返回一个布尔类型,如果为true则表示item留在集合中。
- map():对所有的item进行操作,返回对象类型可以是item类型,也可以是自定义new的新类型,并把这些return的对象收集起来。
- sorted:升序排序,return顺序和参数顺序一致则升序,否则降序。
# 结合BeanUtils组装DTO对象
场景:用于把A类的List集合封装成具有更多业务属性的B类的List集合
List<SetmealDto> list = records.stream().map((item) -> {
SetmealDto SetmealDto = new SetmealDto();
BeanUtils.copyProperties(item, SetmealDto);
Long cId = item.getCategoryId();
Category category = categoryService.getById((cId));
SetmealDto.setCategoryName(category.getName());
return SetmealDto;
}).collect(Collectors.toList());
2
3
4
5
6
7
8
9
# 6.JAVA线程
# JAVA线程六种状态
初始状态(NEW):通过new一个新的线程对象后,还没有调用执行start()方法。
运行状态(就绪+运行,Runnable):对象创建之后,其它线程比如main()调用了该对象的start()方法,则该线程就处于就绪状态,位于可运行的线程池等待分配时间片被调用。获得时间片后转为运行态。
阻塞状态(Blocked):表示线程阻塞于锁。阻塞在synchronized修饰的方法块。
等待状态(Waiting):线程需要等待其它线程做出特定的动作或者通知。才被唤醒。类似于休眠等待唤醒。
超时等待(Timed_waiting):不同于等待状态,一定时间后线程可以自行返回。在这个状态下不会释放锁资源。
终止状态(Terminated):线程已经执行完毕。run{}方法或者是main()方法完成。

- 不区分就绪态和运行态的原因
因为JAVA中线程上下文切换比较快,如果区分就绪和运行,那么你看到运行态线程可能早就已经切换下去了。因此区分就绪态和运行态没有太大意义。
# 简述线程wait(),notify(),sleep(),join()等方法
- wait()
当前线程调用某个对象的wait()方法,表示线程释放这个对象的锁,进入等待状态,等待再次获得这个对象的锁。(因此wait()方法必须在同步块synchronized进行)
- notify()
随机唤醒一个等待对象锁的线程。
static class Thread1 extends Thread{
@Override
public void run() {
synchronized (object) { //等待进入synchronized方法块,处于阻塞态
try {
object.wait(); //等待notify()唤醒,处于等待态
} catch (InterruptedException e) {
}
System.out.println("线程"+Thread.currentThread().getName()+"获取到了锁");
}
}
}
2
3
4
5
6
7
8
9
10
11
12
- sleep()
使当前线程停止执行一段时间,进入time_waiting状态,此处并没有释放锁资源。
- join()
当前线程调用其它线程的join()方法,进入time_waiting状态,当其它线程执行完毕后,当前线程才重新进入运行态。
# 7.JAVA基本对象
# JAVA包装类型
Character和Integer,Long是char和int,long的包装类,是一个类,而char和int,long是基本数据类型,基本数据类型默认值是0。
# 包装对象与数据类型转换
包装类可以拆包成基本数据类型,基本数据类型可以包装成类。类似于类和类内属性的关系。 Integer转int:Integer.intValue() int转Integer:Integer.valueOf(int) Integer封装了int类型的最大值最小值:Integer.MIN_VALUE, Integer.MAX_VALUE
# 8.JAVA代理模式
代理模式分为目标角色,代理角色,抽象角色。代理角色的意义在于控制客户端对目标角色的直接访问或是附加扩展对目标角色的操作。调用者(客户端)感知不到使用的其实是代理对象。
- 静态代理
代理对象和目标对象共同实现一个业务接口,代理对象中有一个private的私有目标对象引用。使用时只需要new一个目标对象并传给代理角色即可。
缺点在于目标角色和代理角色都实现相同业务接口,代码冗余,且维护不方便。
- JDK动态代理
代理对象不需要实现功能接口。代理对象调用目标对象的方法是通过调用处理类中的invoke()方法,里面利用反射机制来调用目标类的方法。JDK动态代理实际上是通过接口来实现代理。
public class JdkProxyFactory {
public static Object getProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(), //代理接口
new DebugInvocationHandler(target)
);
}
}
2
3
4
5
6
7
8
9
缺点在于JDK代理实际上代理的只是目标类所实现的接口中方法,而对于目标类的私有方法,则不能代理。
- CDLIB动态代理
通过继承目标类的一个子类来实现代理。代理对象会把目标类设置为自己的父类。实现代理方法和JDK动态代理类似,通过invoke方法。
public class CglibProxyFactory {
public static Object getProxy(Class<?> clazz) {
// 创建动态代理增强类
Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setClassLoader(clazz.getClassLoader());
// 设置委托类(设置父类)
enhancer.setSuperclass(clazz);
// 设置方法拦截器
enhancer.setCallback(new DebugMethodInterceptor());
// 创建代理类
return enhancer.create();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
缺点在于CGlib需要引入外部依赖,并且不能代理继承final修饰的类。

# 9.单例模式的懒汉式和饿汉式
- 单例模式
单例模式是指当你多次使用一个对象且作用相同时,为了防止多次重复创建该对象,导致内存飙升,内存中只创建一个对象,让所有需要调用的地方共享该对象。
- 懒汉式与饿汉式
懒汉式是指如果没有使用或者调用该对象时,就不创建一个新的对象,仅当使用时才创建new一个对象。而饿汉式是指不管有没有被调用,在类加载时已经创建好该单例对象。
- DCL实现饿汉式
懒汉式通过DCL实现,保证高并发场景下,不会多次创建多个对象;如果已经创建有对象,则不需要等待锁直接返回该对象(高效)。
public class SingleDemo{
private static SingleDemo volatile singleDemo;
private SingeleDemo(){}
public static SingleDemo getInstance(){
if(singleDemo==null){ //高效性
synchronized(SingleDemo.class){ //控制高并发
if(singleDemo==null){
singleDemo=new SingleDemo();
}
}
}
return singleDemo;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
- 破坏单例模式
可以通过反射机制,强制访问该类的私有构造器,去创建另一个对象。通过T.class.getConstructor获得该类的构造方法(构造器),setAccessible(true)取消语言访问,暴力访问。
# 10.JAVA开启线程
进程是CPU资源分配的最小单位,线程是CPU任务调度的最小单位。线程隶属于进程。
- 线程开启方式
开启线程主要有两种方式,第一种是通过实现Runnable接口,实现run()方法。第二种是通过继承Thread类,重写run()方法。除此之外还可以通过实现Callable接口实现call方法或者是通过线程池来开启新的线程。
- 线程安全
通过加锁来保证线程安全,有两种方式,一种是使用JVM提供的隐式锁Synchronized(偏向锁,轻量锁...)。另一种是使用JDK的显示锁(实现Lock接口,而Lock操作借助于内部类Sync,Sync继承了AQS类)。
# 11.Object对象数组与对象转换
- 对象转换
Object转String,注意如果是Integer类型,那么(String) object强制转换会报错,正确转换如下:
Object pageObj=session.getAttribute("page");
String page=String.valueOf(pageObj);
------
String page=Object.toString(pageObj);
2
3
4
- 对象数组内存分配
因为Object数组中每个元素都是一个对象,因此它的对象在堆内存中不是连续分配的;但是在栈中的对象引用指针是连续的。
# 12.多态、重写、重载
- 多态
JAVA多态性是指继承,重写,父类引用指向子类对象。
- 重载
重载是指方法名相同,参数类型,个数,顺序不同。(如果仅有返回类型不同不属于重载)
- 重写
重写发生在子类父类之间,方法名、参数列表相同,返回值范围小于等于父类,访问修饰符范围要大于等于父类(父类为private则不能重写)。
# 13.CopyOnWriteArrayList
CopyOnWriteArrayList容器写操作时,会复制一个数组副本,在副本上进行修改数据,此时可以对原数组继续无锁读操作。写操作完成之后,就把旧容器引用指向副本数组。 缺点在于仅能保证最终一致性,而不能保证强一致性(写过程读会读到旧数据)。并且需要另外开辟内存空间。
一般用于读多写少场景,比如白名单黑名单
# 14.重写equals方法与hashcode方法
hashcode方法它设计的理念就是保证equals认定为是相同的两个对象拥有相同的两个哈希值。默认的equals方法是比较对象的地址,当根据业务需要重写equals方法,变为值相等时,hashcode也需要改为值相等。如果不重写hashcode,那么在遍历桶中元素判断相等时,先执行Object方法中的hashcode(比较的是地址),这时候不同地址的相同元素就会直接返回false(hashcode为true桶相同,才继续用equals遍历桶中元素)。
# 15.日期格式转换
java和数据库日期交互(一般来说向数据库插入日期时,可以是Date类型,也可以是String类型——默认的日期格式)
java.util.Date包含日期时间,对应Mysql的Datetime类型
java.sql.Date仅仅包含日期,没有时间,对应Mysql的Date类型
Date date = new Date();
String str="2022-11-11 13:59"
DateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//日期转String
String replyDate = simpleDateFormat.format(date);
//String转日期
Date date=simpleDateFormat.parse(str);
2
3
4
5
6
7
# 16.ThreadLocal
①ThreadLocal是用来维护每个线程的私有变量和数据。ThreadLocal的set和get方式实际上是通过调用每个Thread对象中都有的一个ThreadLocalMap来进行操作(key就是这个ThreadLocal弱引用对象,value就是ThreadLocal声明的强引用类型数据)。
②ThreadLocal对象使用完后按理说,应该要被垃圾回收。但是因为线程是强引用指向ThreadLocalMap,而ThreadLocalMap也是强引用指向Entry对象,因此只要线程不回收,那么先前的Entry对象就不会回收,进而造成内存泄漏。
解决方案:每次使用完ThreadLocal后,需要手动进行remove清除Entry对象。
③使用场景:连接管理,一个线程一个连接,不同线程不共享连接。利用线程隔离机制维护客户端线程变量。
# 17.JAVA引用对象
强引用:被强引用的对象即使内存不够也不会被垃圾回收,要想取消某个引用和对象之间的强引用关系,可以通过把引用显式设置为null,那么那个没有被引用的对象就会被垃圾回收。一般new对象,还有反射都是强引用。
软引用:系统内存充足则不会被回收,不充足则会被回收。适用于对内存敏感。
弱引用:无论内存是否够用,GC时都会把弱引用对象进行垃圾回收。一般用于缓存当中。
虚引用:GC时就会被回收,必须搭配队列来使用。可以得到GC回收的通知。
# 18.static
static静态方法需要通过类名调用,static的数据和代码块会在jvm中存放在方法区。而类对象会保存在堆中。
final修改的变量可以通过反射修改变量值。
stream()map()块内不能用外部变量,只能用final。
BeanUtils.getProperty(bean,attr):获取bean对象的attr属性字段值。
# 19.JAVA对象的引用
# 等号给对象引用赋值
对象的引用通过"="赋值给另一个对象的引用时,实际上是把这个对象在堆中的地址赋值给另一个引用,这时候两者对象的引用指向的是同一个地址。以下面这个为例子:
public class Linklist {
int val;
Linklist next;
Linklist(){}
Linklist(int _val){this.val=_val;this.next=null;}
}
Linklisk p1=new Linklist(8);//实例化一个Linklist对象,并让p1指向这个对象
Linklist p2=p1;//把p1这个引用所指向对象的地址赋值给p2
p1.val=6;
p1=new Linklist(7);
2
3
4
5
6
7
8
9
10
这里对应两种应用调用操作:
- 通过p1(p2)来改变对应地址的属性p1.val,那么所有指向该对象地址的引用访问该对象属性的值时都会改变。即p1指向对象的内容改了,那么p2指向对象的内容也会相应改变。
- 当直接改变p1指向对象的地址,相当于让p1换一个对象时,p2的指向不受影响,还是指向val=8的对象。
# 方法调用
形参为对象的引用时的方法调用。例子如下:
public static void main(String[] args)
{
Linklist p1=new Linklist(6);
fun(p1);
//p.val=7
}
public void fun(Linklist p)
{
p.val=7;//改变引用地址的属性。或者改变p.next
p=new Linklisk(8);
}
2
3
4
5
6
7
8
9
10
11
当你调用函数时,先会隐式的生成(复制)一个所传入实参p1引用的形参副本p,这个形参p和p1指向的是同一个堆中的对象。同样对应两种操作:
- 当通过形参副本p修改p所指向的堆中的对象的属性值时,那么主函数中的主元(实参)所指向的地址的内容确实被改变了。
- 直接修改形参副本p的指向,让p等于(指向)另一个对象时,改变的仅仅只是副本引用的指向,你在主函数中实参引用指向的对象的地址仍然没变,还是原来那个对象。

因此,修改一个对象引用的对象属性,会影响到所有指向该对象的引用;修改一个对象引用的指向(p1=p2),只会影响到被修改引用自身。所以如果需要通过子函数修改引用的指向,只能够过子函数返回那个对象的地址,在主函数中赋给那个引用(p1=fun(p1))。