面试题整理-JavaSE基础
Java三大特性
1 封装
2 继承
3 多态
若有人问第四个特性则说抽象
基本数据类型
- 整型
- byte 1字节
- int 2字节
- short 4字节
- long 8字节
- 浮点型
- float 4字节
- doublue 8字节
- 字符型
- char 2字节
- 布尔型
- boolean (暂不确定 不到1字节)
Object类常用方法
- wait()
- sleep()
- getClass()
- notify()/notifyAll()
wait与sleep区别
最大的不同是在等待时 wait 会释放锁,而 sleep 一直持有锁。
Clone 深拷贝与浅拷贝
User user1 = new User('张三',15);
User user2 = user1;
System.out.println(user1);
System.out.println(user2);
//user1与user2地址相同 属于对象引用
User user1 = new User('张三',15);
User user2 = (User)user1.clone();
System.out.println(user1);
System.out.println(user2);
//user1与user2地址不同 属于对象复制 使用clone方法创建了一个新的对象
浅拷贝:被复制对象的所有值属性都含有与原来对象的相同,且对象地址值相同。
深拷贝:在浅拷贝操作基础上,创建一个新的对象,地址值与原对象不同。
如果想要深拷贝一个对象,这个对象必须要实现 Cloneable 接口,实现clone方法,并且在 clone 方法内部,把该对象引用的其他对象也要 clone 一份,这就要求这个被引用的对象必须也要实现Cloneable 接口并且实现 clone 方法。
运算符
逻辑与短路
&&之所以称为短路运算是因为,如果&&左边的表达式的值是 false,右边的表达式会被直接短路掉,不会进行运算。很多时候我们可能都需要用&&而不是&,例如在验证用户登录时判定用户名不是 null 而且不是空字符串,应当写为 username != null &&!username.equals(""),二者的顺序不能交换,更不能用&运算符,因为第一个条件如果不成立,根本不能进行字符串的 equals 比较,否则会产生 NullPointerException 异常。注意:逻辑或运算符(|)和短路或运算符(||)的差别也是如此
==与equals方法区别
- ==是运算符 equals是方法
- ==如果比较的是基本数据类型 则是比较他们的值 如果是对象 则比较他们的地址值
- equals用来比较两个对象的值是否相等,但要重写hashcode方法
下面内容来自JAVA中Integer的==和equals注意l
equals(Object obj)方法,在equals(Object obj)方法中,会先判断参数中的对象obj是否是Integer同类型的对象,如果是则判断值是否相同,值相同则返回true,值不同则返回false,如果obj不是Integer类的对象,则返回false。
需要注意的是:当参数是基本类型int时,编译器会给int自动装箱成Integer类,然后再进行比较。
- 基本类型(值类型)之间无法使用equals比较。
- equals参数为值类型,则参数会进行自动装箱为包装类型,之后请参见第3点。
- equals参数为包装类型,则先比较是否为同类型,非同类型直接返回false,同类型再比较值。
示例:
new Long(0).equals(0) 为 false,equals参数默认为int类型,装箱为Integer类型,不同类型直接返回false
new Integer(500).equals(500) 为 true,equals参数默认为int类型,装箱为Integer类型,相同类型再比较值返回true
new Integer(500).equals((short)500) 为 false,equals参数为byte类型,装箱为Byte类型,不同类型直接返回false
new Long(0).equals(0L) 为 true,equals参数为long类型,装箱为Long类型,相同类型再比较值返回true
“==”比较
- 基本类型之间互相比较:以值进行比较
- 一边是基本类型,一边是包装类型
同类型的进行比较,如Integer 与int,Long与long进行==比较时,会自动拆箱比较值
不同类型之间进行比较,则会自动拆箱,且会进行自动向上转型再比较值(低级向高级是隐式类型转换如:byte<short<int<long<float<double,高级向低级必须强制类型转换) - 两边都是包装类型则直接比较引用地址,但是要注意IntegerCache除外。
+=(含隐性强制类型转换)
byte i = 1;
i = i + 1;
//不能编译成功,这是两个动作,先求和再赋值,求和时i自动类型提升,求和结果是int类型,
//但是因为编译器无法判断数值大小,所以无法为我们强制转换类型
byte i = 0;
i += 1;
//+=是一个赋值运算符,在底层是一个动作并且有一个强制转换的一个过程
方法重载与方法重写
方法重载
- 方法名相同,参数列表顺序,名称,类型,个数不同
- 与返回值无关,可以存在于父类子类同类中
- 可以有不同的修饰符,抛出不同的异常
方法重写
- 参数列表类型个数和返回值类型必须与被重写方法一致
- 构造方法不能被重写,final修饰的方法不能被重写,static修饰的方法不能被重写
- 访问权限不能比父类中被重写方法的权限低
- 重写后的方法可以抛出任何非强制性异常,无论被重写方法是否抛出异常,但重写方法就不能抛出新的强制性异常,或者比被重写方法更广泛的异常,反之则可以。
String/StringBuffer/StringBuilder
String
final修饰类,不可变,线程安全。 注:final修饰的方法/类,不能被重写/继承
属于引用数据类型
StringBuffer与StringBuilder
StringBuffer是线程安全,执行效率较慢,适用于多线程下操作字符串缓冲区,大量数据。
StringBuilder是线程不安全的,适用于单线程下操作字符串缓冲区大量数据。
异常处理
Exception
- CheckedException编译时异常
- RuntimeException运行时异常
- 常见异常
- 1)java.lang.NullPointerException 空指针异常;出现原因:调用了未经初始化的对象或者是不存在的对象。
- 2)java.lang.ClassNotFoundException 指定的类找不到;出现原因:类的名称和路径加载错误;通常都是程序试图通过字符串来加载某个类时可能引发异常。
- 3)java.lang.NumberFormatException 字符串转换为数字异常;出现原因:字符型数据中包含非数字型字符。
- 4)java.lang.IndexOutOfBoundsException 数组角标越界异常,常见于操作数组对象时发生。
- 5)java.lang.IllegalArgumentException 方法传递参数错误。
- 6)java.lang.ClassCastException 数据类型转换异常。
- 7)java.lang.NoClassDefFoundException 未找到类定义错误。
- 8)SQLException SQL 异常,常见于操作数据库时的 SQL 语句错误。
- 9)java.lang.InstantiationException 实例化异常。
- 10)java.lang.NoSuchMethodException 方法不存在异常。
- 常见异常
Error
是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM出现的问题。
异常处理流程
系统对异常的处理使用统一的异常处理流程:
1、自定义异常类型。
2、自定义错误代码及错误信息。
3、对于可预知的异常由程序员在代码中主动抛出,由SpringMVC统一捕获。
4、对于不可预知的异常(运行时异常)由SpringMVC统一捕获Exception类型的异常。
5、可预知的异常及不可预知的运行时异常最终会采用统一的信息格式(错误代码+错误信息)来表示,最终也会随请求响应给客户端。
//自定义异常类
public class CustomException extends RuntimeException {
private ResultCode resultCode;
public CustomException(ResultCode resultCode) {
//异常信息为错误代码+异常信息
super("错误代码:"+resultCode.code()+"错误信息:"+resultCode.message());
this.resultCode = resultCode;
}
public ResultCode getResultCode(){
return this.resultCode;
}
}
//控制器增强
@ControllerAdvice
public class ExceptionCatch {
static{
//在这里加入一些基础的异常类型判断
builder.put(HttpMessageNotReadableException.class,CommonCode.INVALIDPARAM);
builder.put(NullPointerException.class,CommonCode.DATAISNULL);
}
private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionCatch.class);
//使用EXCEPTIONS存放异常类型和错误代码的映射,ImmutableMap的特点的一旦创建不可改变,并且线程安全
private static ImmutableMap<Class<? extends Throwable>,ResultCode> EXCEPTIONS;
//使用builder来构建一个异常类型和错误代码的异常
protected static ImmutableMap.Builder<Class<? extends Throwable>,ResultCode> builder =
ImmutableMap.builder();
//捕获Exception异常
@ResponseBody
@ExceptionHandler(Exception.class)
public ResponseResult exception(Exception e) {
LOGGER.error("catch exception : {}\r\nexception: ",e.getMessage(), e);
if(EXCEPTIONS == null){
EXCEPTIONS = builder.build();
final ResultCode resultCode = EXCEPTIONS.get(e.getClass());
final ResponseResult responseResult;
if (resultCode != null) {
responseResult = new ResponseResult(resultCode);
} else {
responseResult = new ResponseResult(CommonCode.SERVER_ERROR);
}
return responseResult;
}
//捕获 CustomException异常
@ExceptionHandler(CustomException.class)
@ResponseBody
public ResponseResult customException(CustomException e) {
LOGGER.error("catch exception : {}\r\nexception: ",e.getMessage(), e);
ResultCode resultCode = e.getResultCode();
ResponseResult responseResult = new ResponseResult(resultCode);
return responseResult;
}
}
I/O
字节流
- 输入流(inputStream)
- FileInputStream(多用于文件读取)
- BufferedInputStream
- 输出流(outputStream)
- FileOutStream(多用于文件写入)
- BufferedOutStream
字符流
- 输入流(Reader)
- InputStreamReader(字节转换字符流)
- BufferedReader
- FileReader(多用于读取文本文件)
- 输出流(Writer)
- OutputStreamWriter(字节转换字符流)
- FileWriter(多用于写入文本文件)
字符流适用于文本较多的传输,对于视频、图片这类的文件只能通过字节流传输
集合
- Iterator
- Map
- HashMap(数组+链表,1.8后容量超过8时链表变为红黑树,无序,支持null值和键)
- LinkedHashMap(在HashMap的基础上保持了顺序)
- HashTable(线程安全,关键方法用sychornized修饰,不支持null值和键)
- TreeMap
- List
- ArrayList(底层数组,增删慢,查询快)
- LinkedList(底层链表,增删快,查询慢)
- Vector(底层数组,线程安全,增删查询慢)
- Set
- HashSet(无序)
- LinkedHashSet(有序)
- TreeSet
- Map
多线程
创建多线程的方式
- 继承Thread类
- 实现Runnable接口
- 使用Callable和Future创建线程
- 使用线程池Executor框架
线程池作用
- 降低资源消耗
- 提高响应速度
- 提高线程客观理性
为什么使用线程池?
减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务
可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为因为消耗过多的内存,而把服务器累趴下
并发
并发包
名称 | 作用 |
---|---|
Lock | 锁 |
Condition | Condition的功能类似于在传统的线程技术中的Object.wait()和Object.notify()的功能 |
Executors | 线程池 |
ReentrantLock | 可重入的互斥锁 |
ConcurrentHashMap | 其内部使用锁分段技术,维持这锁Segment的数组,在Segment数组中又存放着Entity[]数组,内部hash算法将数据较均匀分布在不同锁中 |
AtomicInteger | 原子类 |
… |
并发队列
1.阻塞队列(锁机制实现)
2.非阻塞队列(CAS实现)
CAS算法是由硬件直接支持来保证原子性的,有三个操作数:内存中的值V、旧值A和新值B,当且仅当V符合预期值A时,CAS用新值B原子化地更新V的值,否则什么都不做
死锁
产生条件
- 互斥条件:线程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某 资源仅为一个线程所占有
- 不剥夺条件:线程所获得的资源在未使用完毕之前,不能被其他线程强行夺走,即只能由获得该资源的线程自己来释放
- 请求和保持条件:线程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他线程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放
- 循环等待条件:存在一种线程资源的循环等待链,链中每一个线程已获得的资源同时被链中下一个线程所请求
如何避免
- 注意加锁顺序
- 设置加锁时限
- 对代码进行检测
数据结构
数组
固定长度,添加数据时,会添加索引,根据索引确定值的位置,查找速度很快,但添加需要确定索引位置后将数据添加入对应的位置
链表
链表添加数据时,将之前的数据指向添加的数据,用指针连接,新添加的数据会在头部,添加速度比数组快,但查询时,若要查的数据在链表最后一位,那么所消耗时间很长(On)
二叉树
二叉树的结构由根节点出发,左边是比根节点小的数,右边是比根节点大的数,查询耗时复杂度On
红黑树
简而言之,就是会自动平衡的二叉树,有几个特性:
- 节点是红色或黑色。
- 根节点是黑色。
- 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
- 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点
B-Tree
B-tree是一种常见的数据结构。使用B-tree结构可以显著减少定位记录时所经历的中间过程,从而加快存取速度。这个数据结构一般用于数据库的索引,综合效率较高。
B+Tree
对于B+树,其结点结构与B-tree相同,不同的是各结点的关键字和可以拥有的子结点数。
这两种处理索引的数据结构的不同之处:
1.B树中同一键值不会出现多次,并且它有可能出现在叶结点,也有可能出现在非叶结点中。而B+树的键一定会出现在叶结点中,并且有可能在非叶结点中也有可能重复出现,以维持B+树的平衡
2.因为B树键位置不定,且在整个树结构中只出现一次,虽然可以节省存储空间,但使得在插入、删除操作复杂度明显增加。B+树相比来说是一种较好的折中
3.B树的查询效率与键在树中的位置有关,最大时间复杂度与B+树相同(在叶结点的时候),最小时间复杂度为1(在根结点的时候)。而B+树的时间复杂度对某建成的树是固定的
JMM线程内存模型
volatile关键字
- 可见性
- 有序性(禁止指令重排序)
- 不保证原子性
保证变量在多线程中的可见性
底层通过汇编语言lock前缀指令,锁定这块内存区域的缓存并写回主程序,lock指令作用:
1.将缓存数据立即写到系统内存
2.开启MESI协议(总线嗅探机制)
底层实现