导图社区 java知识
java知识思维导图,包括集合、注解、异常处理、反射、虚拟机、设计模式、String相关,java并发的概念、种类和原理等内容。
编辑于2021-12-12 21:03:29java知识
集合
List
扩容机制
ModifiedException
arraylist多线程问题
Set
Map
HashMap
何时扩容
扩容的算法
如何解决散列碰撞
为什么非线程安全
jdk1.7 resize 导致的null问题
jdk1.7 循环引用导致的cpu 100%
jdk1.8 红黑树节点循环引用 cpu 100%
ConcurrentHashmap
jdk1.7 put
尝试获取锁,获取到直接插入
未获取,则自旋等待,知道最大阀值转为阻塞锁
jdk1.8 put
桶为空,则cas写入,失败则自旋直到成功
hashcode == MOVED == -1 则扩容
如果都不满足,则使用synchronize写入
jdk1.7 实现原理
采用分段锁实现, segment, 继承自ReenterLock
jdk1.8 实现原理
采用 cas和 synchronize
SparseArray
稀疏矩阵压缩
注解
元注解
关键字
@Target
@Rentention
注解的作用
检查格式/减少配置/减少重复工作
怎么工作的
异常处理
Exception
运行时异常都是RuntimeException等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般都是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免此类异常的发生。
RuntimeException
NullPointerException
IndexOutOfBoundsException
error
error发生时, java虚拟机一般会选择线程终止。
OutOfMemoryError
StackOverflowError
异常处理的俩个基本原则
尽量不捕获exception通用异常,而是应捕获特定异常
不要生吞异常
NoClassDefError/ClassNotFoundException
ClassNotFoundException产生的原因主要是:java支持反射运行时加载类,例如Class.forName方法来加载类时,如果没有在类路径找到,则抛出此异常。 NoClassDefFoundError产生的原因:jvm或者classloader实例尝试加载(正常的方法调用,或new)类时找不到类的定义。编译的时候存在,但是运行时找不到。
反射
Class.forName
类名.class
this.getClass
Method
Field
Constructor
虚拟机
设计模式
String相关
string为什么要设计成不可变的
java中编码方式
String/StringBuffer/StringBuilder
java并发
线程池
概念
线程池就是事先将多个线程对象放到一个容器中,使用的时候不用new线程,而是直接在池子中拿,节省了开辟线程的时间,提高了代码执行效率。
种类
new CachedThreadPool 不固定线程数量 有空闲线程则复用空闲线程,若无空闲线程则新建线程 一定程度减少频繁创建/销毁线程,减少系统开销 new FixedThreadPool 可控制线程最大并发数 超出的线程会在队列中等待 new SingleThreadExecutor 单线程 new ScheduledThreadPool
原理
从数据结构的角度来看,线程池主要使用了blockingqueue和hashset集合来实现的。 从任务提交的流程来看: 1 如果正在运行的线程数 < coresize,马上创建核心线程执行task,不排队等待。 2 如果正在运行的线程数 > coresize, 则把该任务加入阻塞队列 3 如果队列已满 && 正在运行的线程数 < maximumPoolSize,创建新的非核心线程执行该任务 4 如果队已满 && 正在运行的线程数 >= maximumPoolSize,线程池调用handler的reject方法拒绝本次提交。
复用
基础知识
oop概念
访问修饰符
solid原则
单一职责(single responsibility principle)
开闭原则
里氏替换
依赖倒置
接口隔离
重载和重写区别
泛型
类型擦除
public static void main(String[] args) { List<Integer> intList = new ArrayList<>(); List<String> strList = new ArrayList<>(); System.out.println(intList.getClass() == strList.getClass()); }
原始类型
原始类型就是泛型类型擦出泛型信息后,在字节码中真正的类型。 例如: //泛型类型 class Pair<T> { private T value; public T getValue() { return value; } public void setValue(T value) { this.value = value; } } //原始类型 class Pair { private Object value; public Object getValue() { return value; } public void setValue(Object value) { this.value = value; } } 因为在Pair<T>中,T是一个无限定的类型变量,所以用Object替换。如果是Pair<T extends Number>,擦除后,类型变量用Number类型替换。
extends和super
反射突破泛型类型
public static void main(String[] args) { List<Integer> intList = new ArrayList<>(); intList.add(1); Class<?> listClass = intList.getClass(); try { Method addMethod = listClass.getMethod("add", Object.class); addMethod.invoke(intList, "hello"); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } int len = intList.size(); for (int i = 0; i < len; i++) { System.out.println(intList.get(i)); } } //说明java里面的泛型是编译器层面实现的伪泛型
java不支持泛型引用传递
ArrayList<String> arrayList1=new ArrayList<Object>();//编译错误 ArrayList<Object> arrayList1=new ArrayList<String>();//编译错误
静态类和静态方法中的泛型
public class Test2<T> { public static T one; //编译错误 public static T show(T one){ //编译错误 return null; } } 因为泛型类中的泛型参数的实例化是在定义泛型类型对象(例如ArrayList<Integer>)的时候指定的,而静态变量和静态方法不需要使用对象来调用。对象都没有创建,如何确定这个泛型参数是何种类型,所以当然是错误的。 public class Test2<T> { public static <T >T show(T one){//这是正确的 return null; } } 因为这是一个泛型方法,在泛型方法中使用的T是自己在方法中定义的T,而不是泛型类中的T。
泛型常见面试题
1. Java中的泛型是什么 ? 使用泛型的好处是什么? 泛型是一种参数化类型的机制。它可以使得代码适用于各种类型,从而编写更加通用的代码,例如集合框架。 泛型是一种编译时类型确认机制。它提供了编译期的类型安全,确保在泛型类型(通常为泛型集合)上只能使用正确类型的对象,避免了在运行时出现ClassCastException。 2、Java的泛型是如何工作的 ? 什么是类型擦除 ? 泛型的正常工作是依赖编译器在编译源码的时候,先进行类型检查,然后进行类型擦除并且在类型参数出现的地方插入强制转换的相关指令实现的。 编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。例如List<String>在运行时仅用一个List类型来表示。为什么要进行擦除呢?这是为了避免类型膨胀。 3. 什么是泛型中的限定通配符和非限定通配符 ? 限定通配符对类型进行了限制。有两种限定通配符,一种是<? extends T>它通过确保类型必须是T的子类来设定类型的上界,另一种是<? super T>它通过确保类型必须是T的父类来设定类型的下界。泛型类型必须用限定内的类型来进行初始化,否则会导致编译错误。另一方面<?>表示了非限定通配符,因为<?>可以用任意类型来替代。 4. List<? extends T>和List <? super T>之间有什么区别 ? 这和上一个面试题有联系,有时面试官会用这个问题来评估你对泛型的理解,而不是直接问你什么是限定通配符和非限定通配符。这两个List的声明都是限定通配符的例子,List<? extends T>可以接受任何继承自T的类型的List,而List<? super T>可以接受任何T的父类构成的List。例如List<? extends Number>可以接受List<Integer>或List<Float>。 5. 如何编写一个泛型方法,让它能接受泛型参数并返回泛型类型? 编写泛型方法并不困难,你需要用泛型类型来替代原始类型,比如使用T, E or K,V等被广泛认可的类型占位符。泛型方法的例子请参阅Java集合类框架。最简单的情况下,一个泛型方法可能会像这样: public V put(K key, V value) { return cache.put(key, value); } 6. Java中如何使用泛型编写带有参数的类? 这是上一道面试题的延伸。面试官可能会要求你用泛型编写一个类型安全的类,而不是编写一个泛型方法。关键仍然是使用泛型类型来代替原始类型,而且要使用JDK中采用的标准占位符。 7. 编写一段泛型程序来实现LRU缓存? 对于喜欢Java编程的人来说这相当于是一次练习。给你个提示,LinkedHashMap可以用来实现固定大小的LRU缓存,当LRU缓存已经满了的时候,它会把最老的键值对移出缓存。LinkedHashMap提供了一个称为removeEldestEntry()的方法,该方法会被put()和putAll()调用来删除最老的键值对。 8. 你可以把List<String>传递给一个接受List<Object>参数的方法吗? 对任何一个不太熟悉泛型的人来说,这个Java泛型题目看起来令人疑惑,因为乍看起来String是一种Object,所以List<String>应当可以用在需要List<Object>的地方,但是事实并非如此。真这样做的话会导致编译错误。如果你再深一步考虑,你会发现Java这样做是有意义的,因为List<Object>可以存储任何类型的对象包括String, Integer等等,而List<String>却只能用来存储Strings。
数组支持泛型吗
多态和继承
匿名内部类
没有名字/只能继承一个父类或接口/Outerclass$N 匿名内部类内存泄漏的问题? 匿名内部类会默认持有外部类的引用,可能会导致内存泄漏。 由编译器生成。 参数列表包括: 定义在非静态域内的外部对象 父类的外部对象 父类的构造方法参数 外部捕获的变量(方法体内有引用外部的final变量) 匿名内部类引用外部变量,为什么要用final修饰? 匿名内部类最终会编译成一个单独的class文件,而被该类使用的变量会被内部类捕获 以构造函数参数的形式传递给该类,例如:Integer paramInteger, 如果变量不定义成final的, paramInteger在匿名内部类中可以被修改,进而造成和外部的paramInteger不一致的问题,为了避免这种不一致的情况,因此java规定匿名内部类只能访问final修饰的外部变量。 什么是内部类?内部类的作用 内部类的对象创建不依赖于外围类对象的创建。 内部类并没有令人迷惑的is-a关系,他就是一个独立的实体 内部类提供更好的封装,除了该外围类,其它类都不能访问
抽象类的意义
为其子类提供一个公共的类型,封装子类中的重复内容,定义抽象方法,子类虽然有不同的实现, 但是定义是一致的。
静态内部类/非静态内部类的理解
静态内部类:只是为了降低包的深度,方便类的使用,静态内部类适用于包含在类中,但️不依赖与外在的类,不用使用外在类的非静态属性和方法,只是为了方便管理类结构而定义。在创建静态内部类的时候,不需要外部类对象的引用。 非静态内部类:持有外部类的引用,可以自由使用外部类的所有变量和方法。
equals和hashcode
为什么 重写 equals 同时需要重写 hashcode, equals为true时,hashcode是否相同, 反之hashcode相同时,equals是否相等? equals为true我们认为就是同一个对象,当equals为true时,hashcode如果不同,在hashmap等集合中,就会因为hashcode不同而存放在不同等bucket中,被认为是俩个不同的对象。所以equals被重写,hashcode也要被重写。 equals为true时,hashcode肯定要相同,反之因为hash碰撞不成立。 equals方法要遵循约定如下 自反性/对称性/传递性/一致性/
浮点数的表示/负数的表示
final/finally/finalize的区别
final用来修饰类/方法/变量,分别有不同的意义,final修饰的class不可以被继承,final的变量是不可以修改的,而final的方法也是不可以ovveride finally 是java保证重点代码一定要被执行的一种机制。 finalize是基础类Object的一个方法,它的设计目的是保证对象在被垃圾回收前完成特定资源的回收。现在逐步使用java.lang.ref.Cleaner来替换。 cleaner实现利用了PhantomReference。
java中的四种引用
强引用 softreference 内存不足时回收 weakReference 无论内存是否充足,只要垃圾回收线程启动,它就会被回收。 PhantomReference 虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。在java中用java.lang.ref.PhantomReference类表示。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。 要注意的是,虚引用必须和引用队列关联使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之 关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
java中类加载过程
Person person = new Person() 1. 首先找到Person.class文件,并加载到内存中 2.执行该类中到static代码块,如果有的话给Person.class类进行初始化 3.在堆内存中开辟空间分配内存地址 4.在堆内存中建立对象的特有属性,并进行默认初始化 5.堆属性进行显示初始化 6.对 对象进行构造代码块初始化 7.对 对象进行与之对应的构造函数进行初始化 8.将内存地址给栈内存中的person句柄。