1.Java 基础 知识
1.1 面向对象的特征(了解)
面向对象的特征:封装、继承、多态、抽象
封装:就是把对象的属性和行为(数据)结合为一个独立的整体,并尽量隐藏对象的内部细节,公开我希望公开的,别人只能调用而不知道如何实现,增加安全性。
继承:子类继承父类的数据属性和行为,并能根据自己的需求扩展出新的行为,提高代码的复用性。
多态:指允许不同的对象对同一消息做出响应。即同一消息可以根据发送对象的不同,而采取多种不同的行为方式。封装和继承几乎都是为多态而准备的。
抽象:表示对问题领域进行分析、设计中得出抽象的概念,是对一系列看上去不同,但本事相同的具体概念的抽象。
在 Java 中抽象用 abstract 关键字来修饰,用abstract修饰类时,此类就不能被实例化,从这里可以看出,抽象类(接口)就是为了继承而存在的。
1.2 Java 的基本数据类型有哪些
整型:数据类型 字节数 位数
byte 1 8
short 2 16
int 4 32
long 8 64
浮点型
float 4 32
double 8 64
布尔型
boolean 1 8
字符型
char 2 16
1.3JDK JRE JVM 的区别 (必会)
JDK(Java Development Kit):是整个java的核心;是
java开发工具包,
包括了java运行环境
JRE、
java工具和
java基础类库
JRE(Java Runtime Environment)是运行JAVA程序所必须的环境的集合,包含
java虚拟机和java程序的
一些核心类库
JVM是Java virtual machine (JAVA虚拟机)的缩写,是整个java实现跨平台的
最核心的部分,能够
运行以java语言写作的软件程序
1.4 重载和重写的区别
重载:发送在同一个类中,
方法名必须相同,
参数类型不同,
个数不同,
顺序不同
重写:发送在
父子类中,
方法名,
参数列表必须
相同,
返回值范围必须
不大于父类,
抛出的异常范围不大于父类,
访问修饰符不小于父类;如果父类方法为
私有,子类
不能重写该方法
1.5 Java 中==和 equals 的区别
==的作用:
对于基本类型如int:比较的就是
值是否相同
对于引用类型如数组;比较的就是
地址值是否相同
equals的作用:
引用类型:没有重写的默认情况下,比较的是
地址值
重写后:比较的是
内容
例如String,Integer,Date这些类库中的equals已经被重写
即:==:比较的是两个字符串的
内存地址(堆内存)的数值是否相等,属于数值比较
equals():比较的是两个字符串的
内容,属于内容比较
1.6 String、StringBuffer、StringBuilder 三者之间的区别
String:字符串常量
StringBuffer:字符串变量(线程安全)
StringBuilder:字符串变量(非线程安全)
String中的String类中使用
final关键字修饰字符数组来保存字符串:
private final char value[],
String对象是不可变的,就是
常量、线程安全
StringBuffer和StringBuilder都继承了
AbstractStringBuilder,定义了一些字符串的基本操作;
StringBuffer:对方法加了
同步锁或者对调用的方法加了同步锁,所以是线程安全
的
StringBuilder:并没有对方法进行同步锁,所以是
非线程安全的
小结:
- 如果要操作少量的数据用:String
- 多线程操作字符串缓冲区下操作大量数据用:StringBuffer
- 单线程操作字符串缓冲区下操作大量数据用:StringBuilder
1.7 接口和抽象类的区别是什么?
1.实现:抽象类的子类使用
extends来继承;接口必须使用
implements来实现接口。
2.构造方法:
抽象类可以有构造函数;接口不能有
3.main:
抽象类可以有main方法,并且能运行;接口没有
3.实现数量:类可以实现
多个接口,但是
只能继承一个抽象类
4.访问修饰符:接口中的方法默认使用
public;
抽象类可以是
任意访问修饰符
1.8 string 常用的方法有哪些?
indexOf():返回指定字符的索引。
charAt():返回指定索引处的字符。
replace():字符串替换。
trim():去除字符串两端空白。
split():分割字符串,返回一个分割后的字符串数组。
getBytes():返回字符串的 byte 类型数组。
length():返回字符串长度。
toLowerCase():将字符串转成小写字母。
toUpperCase():将字符串转成大写字符。
substring():截取字符串。
equals():字符串比较。
1.9 什么是单例模式?有几种?
单例模式:某个类的实例在
多线程环境下只会被创建一次出来
单例模式有饿汉式和懒汉式还有双重检查锁
饿汉式:线程安全,一开始就初始化
public class Singleton{
private static Singleton instance =new Singleton();
private Singleton(){}
private static Singleton getInstance()
{
return instance;
}
}
懒汉式:非线程安全,延迟初始化
public class Singleton{
private static Singleton instance ;
private Singleton(){}
private static Singleton getInstance()
{
if(instance==null)
{
intstance=new Singleton();
}
return instance;
}
}
双重检查锁:线程安全,延迟初始化
public class Singleton{
private volatile static Singleton instance ;
private Singleton(){}
private static Singleton getInstance()
{
if(instance==null)
{
synchronized(Singleton.class){
if(instance==null){
intstance=new Singleton();
}
}
}
return instance;
}
}
1.10 反射
在java中的反射机制是指在运行状态中,对于任意的类都能知道这个类所有的属性和方法;并且对于任意一个对象,都能调用它非私有的任意一个方法;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制
获取Class对象的3种方法:
调用某个对象的getClass()方法
Person p=new Person();
Class clazz=p.class;
调用某个类的class属性来获取该类对于的Class对象
Class clazz=Person.class;
使用Class类中的forName()静态方法(最安全、性能最好)
Class clazz=Class.forName("类的全路径"); (最常用)
1.11 jdk1.8 的新特性
1. Lambda表达式
Lambda允许把函数作为一个方法的参数
new Thread( ()->System.out.println("abc")).start();
2.方法引用
方法引用允许直接引用已有 Java 类或对象的方法或构造方法。
ArrayList<String> list=new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.forEach(System.out::println)
3.函数式接口
有且仅有一个抽象方法的接口叫做函数式接口,函数式接口可以被隐式转换为Lambda表达式。通常函数式接口上会添加@FunctionalInterface注解。
4.接口允许定义默认方法和静态方法
从JDK8开始,允许接口中存在一个或多个默认非抽象方法和静态方法
5.Stream API
新添加的StreamAPI把真正的函数式编程风格引入java中。这种风格将要处理的元素集合可以看作一种流,流在管道中传输,并且可以在管道和节点上进行处理,比如筛选,排序,聚合等
List<String> list=Arrays.asList("abc","","abc");
list.stream()
.filter(string->!string.isEmpty())//过滤空字符串
.distince()//去重
.forEach(a->System.out.print(a));
6 日期/时间类改进
之前的 JDK 自带的日期处理类非常不方便,我们处理的时候经常是使用的第三方
工具包,比如 commons-lang
包等。不过 JDK8 出现之后这个改观了很多,比如日期时间的创建、比较、调整、
格式化、时间间隔等。
这些类都在 java.time 包下,LocalDate/LocalTime/LocalDateTime。
7 Optional 类
Optional 类是一个可以为 null 的容器对象。如果值存在则 isPresent()方法会返
回 true,调用 get()方法会返回该对象。
8 Java8 Base64 实现
Java 8 内置了 Base64
1.12 Java 的异常

Throwable是所有java程序中错误处理的父类,有两种子类:Error和Exception
Error:表示由jvm所侦测到的无法预期的错误,是属于JVM层次的严重错误,导致JVM无法继续执行。这是不可捕捉到的,最多只能显示错误信息
Exception:表示可恢复的列外,这是可捕捉到的。
- 运行时异常:都是RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常一般都是由程序逻辑错误引起的。
- 非运行时异常(编译异常):是RuntimeException以外的异常。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException 等以及用户自定义的Exception 异常,一般情况下不自定义检查异常。
常见的RunTime异常几种如下:
NullPointerException- 空指针引用异常
ClassCastException- 类型强制转换异常。
IllegalArgumentException- 传递非法参数异常。
ArithmeticException- 算术运算异常
ArrayStoreException- 向数组中存放与声明类型不兼容对象异常
IndexOutOfBoundsException- 下标越界异常
NegativeArraySizeException- 创建一个大小为负数的数组错误异常
NumberFormatException- 数字格式异常
SecurityException- 安全异常
UnsupportedOperationException- 不支持的操作异常
1.13 BIO、NIO、AIO 有什么区别?
BIO:
Block IO 同步阻塞式IO,就是我们平常使用的
传统IO,它的特点是
模式简单使用方便,
并发处理能力低
NIO:
New IO 同步非阻塞IO,是传统IO的升级,客户端和服务器端通过Channel(通道)通讯,实现了
多路复用
AIO:
Asynchronous IO是NIO的升级,也叫NIO2,实现了
异步非阻塞IO,异步IO的操作
基于时间和
回调机制
1.14 Threadlocal(线程局部变量) 的原理
ThreadLocal:为
共享变量在
每个线程中创建一个单独的变量副本,每个线程只可以
访问自己内部的副本变量。通过
threadlocal保证
线程安全性
其实在
ThreadLocal类中有一个
静态内部类ThreadLocalMap(其类似于Map),用
键值对的形式存储每一个线程的变量副本,ThreadLocalMap中元素的
key为当前ThreadLocal对象,而
value对应线程的变量副本。
ThreadLocal本身并不存储值,它只是作为一个
key保存到ThreadLocalMap中,但是这里要注意到的是它作为一个key用的是
弱引用,因为
没有强引用链,
弱引用在GC的时候可能会被回收。这样就会在
ThredLocalMap中存在
一些key为null的键值对(Entry)。因为key变成null了,我们是
没法访问这些Entry的,但是这些Entry本身是不会被清除的。如果没有手动删除对应的key就会导致这些内存不会回收也无法访问,也就是
内存泄漏。
弱引用:
弱引用不会阻止垃圾回收它所指向的对象
强引用:
保证对象的存活,使其不被垃圾机制回收
使用完ThreadLocal之后,记得调用
remove方法。
在
不使用线程池的前提下,即使
不调用remove方法,线程的
“变量副本”也会被gc回收,既不会造成内存泄漏的情况。
通俗来说:每一个线程代表一个人,每个人都有自己的私有物品。ThreadLocal就是一个神奇储物柜(
一种机制),存储每个人(
线程)的私有物品(
变量副本)。
储物柜(
ThreadLocal)内部是一个小柜子(
ThredLocalMap)。柜子的钥匙key是(
ThreadLocal对象),抽屉(value)是存放
变量副本的地方。
物品(变量副本)会在线程结束时被回收,但锁(ThreadLocal
对象)却是用
弱引用
存储的。这意味着,如果
没有其他地方强引用
这个锁,
垃圾回收器可能会把它收走
,就像把钥匙弄丢了一样。这样一来,虽然抽屉还在(ThreadLocalMap
中的Entry),但我们却没办法打开它了,因为钥匙没了。这些打不开的抽屉就占用了空间,但又没法用,这就是所谓的
内存泄漏
。
所以使用完后要调用remove相当于要把抽屉里的东西拿走 ,这样就防止了内存泄漏、 如果没有使用线程池 不调用remove 变量副本也会被gc回收
1.16 同步锁、死锁、乐观锁、悲观锁
同步锁:
当多个线程同时访问同一个数据时,很容易出现问题。为了避免这种情况出现,我们要
保证线程同步互斥、就是指
并发执行的多个线程,在同一时间内只允许一个线程访问共享数据。java中可以使用
synchronized关键字来取得一个对象的同步锁
死锁:
何为死锁,就是
多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放
乐观锁:/
总是假设
最好的情况,每次去拿数据的
时候都认为别人不会修改,所以
不会上锁,但是在
更新的时候会判断一下在此期间
别人有没有更新这个数据,可以使用
版本号机制和
CAS算法实现。乐观锁适用于
多读的应用的类型,这样可以
提高吞吐量,像数据库提供的类似于
write_conditio机制,其实都是提供的乐观锁。在java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的
悲观锁:
总是假设
最坏的情况,每次去拿数据的时候都认为别人会修改,所以
每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(
共享资源每次只给一个线程使用,
其他线程阻塞,
用完后再把资源转让给其他线程)。传统的关系型数据库里面就用到了很多这种锁机制,比如
行锁、表锁等,
读锁、写锁等,就是在
做操作之前先上锁。java中synchronized和reentrantLock等独占锁就是悲观锁思想的实现。
1.17 synchronized 底层实现原理
临界区:访问
共享资源的
那段代码
synchronized可以
保证方法或者代码块在运行时,同一时刻
只有一个线程可以进入到临界区(访问共享资源),同时它还可以
保证共享变量的内存可见性。
java中
每一个对象都可以作为锁,这是synchronized实现的同步的基础;
- 普通同步方式,锁是当前实例对象
- 静态同步方法,锁是当前类的class对象
- 同步方法块,锁是括号里面的对象
1.18 synchronized 和 volatile 的区别是什么?
volatile 本质是
告诉jvm当前变量需要从主存中读取,而
不是从线程中读取;
synchronized则是
锁定当前变量,只有
当前线程可以访问该变量,
其他线程被阻塞住。
volatile仅能使用在
变量级别;synchronized则可以使用在
变量、方法、和类级别等。
volatile仅能实现变量的修改可见性,
不能保证原子性;而
synchronized则可以保证变量的修改
可见性和原子性
volatile
不会造成线程的
阻塞;synchronized
可能会造成线程的阻塞
volatile标记的变量
不会被编译器优化;synchronized标记的变量
可以被编译器优化
1.19synchronized 和 Lock 有什么区别?
首先
synchronized是
java内置的关键字,在jvm层面,
Lock是个Java类;
synchronized
无法判断是否获取锁的状态,Lock
有返回值可以判断是否获取到锁;
synchronized
会自动释放锁(a线程执行完同步代码会释放锁;b线程执行过程中发送异常会释放锁),Lock
需在finally中手动释放锁(unlock()方法释放锁),否则容易造成线程死锁;
用
synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,
线程2则会一直等待下去,
而
Lock锁就不一定会等待下去,
如果尝试获取不到锁,线程
可以不用一直等待,比如
带有超时的tryLock(long time, TimeUnit unit)方法,超过时间后就停止等待
synchronized
的锁
可重入、不可中断、非公平
,
Lock
锁
可重入、可判断、可公平
Lock
锁适合
大量
需要同步的代码,
synchronized
锁适合
少量
代码的同步问题