Java语言面试题
1、Java语言的特点?
(1)Java是面向对象的语言,封装、继承和多态都展示了面向对象的思想。
(2)Java编程不需要考虑内存申请和销毁,这些由JVM的垃圾回收器完成。
(3)Java是跨平台的语言,编译成字节码文件后可以在JVM上运行。
2、介绍下封装、继承和多态?
(1)封装是将对象的属性和方法装在一个类中,外界只能调用接口,无法知道具体实现。
(2)继承是一个类可以继承其父类的属性和方法。
(3)多态是一个类的对象可以是其子类的实例,而这个类可以有多个子类,不同的子类对象调用方法可能 有不同的效果,实现接口重用。
3、介绍下public、protected和private修饰符?
(1)public修饰的属性和方法可以在任意地方被调用。
(2)protected修饰的属性和方法只能在同一包下被调用。在不同的包下,子类可以访问父类中protected修饰的属性和方法。
(3)private修饰的属性和方法只能在类的内部被访问,在外部不能被访问。
4、重写和重载的区别?
(1)重写:是子类实现父类的方法,是发生在父类和子类之间。
(2)重载:是一个类中可以有两个重名的方法,但是参数列表不能相同。
(3)重载的条件:两个方法的方法名必须相同;参数列表必须不相同,可以是参数个数,参数类型或者顺序不同;返回值类型可以相同也可以不相同。
5、抽象类和接口的区别?
(1)抽象类的定义是abstract关键字,并且抽象类可以有普通方法;抽象类中可以定义成员变量和静态变量;一个类只能继承一个抽象类。抽象类与具体类相比,多了一个可以定义抽象方法。
(2)接口的定义是interface关键字,接口中全都是抽象方法;接口只能定义静态变量;一个类可以实现多个接口。
6、Java中有哪些数据类型?
(1)Java中分基本数据类型和引用数据类型,基本数据类型传递的是值类型,引用数据类型传递的是指针引用。
(2)基本数据类型包括byte(1字节)、char(2字节)、short(2字节)、int(4字节)、long(8字节)、float(4字节)、double(8字节)、boolean(1字节)
(3)引用数据类型包括string、对象实例、接口、抽象类、枚举、数组
7、String的底层实现?
在Jdk1.9之后,string的底层是用byte数组实现的,之前都是用的char数组。而数组在定义后是不能够修改的,所以string也不能被修改。对string的操作都会生成新的string。
8、String的常用方法?
equals方法、length方法、contains方法、split方法、trim方法、charAt方法、concat方法、replace方法、subString方法等
(1)equals方法可以判断两个字符串是否相同
(2)length方法获取字符串长度
(3)split方法切分字符串
(4)contains方法判断字符串中是否包含子串
(5)trim方法是去掉字符串首尾空白字符
(6)charAt方法用于获取字符的索引
(7)concat方法用于拼接两个字符串
(8)replace方法用于替换字符串中的字符
(9)subString方法用于截取部分字符串
9、String、stringBuffer和stringBuilder的区别?
(1)string是初始化后不可变的,对string的操作会生成新的string
(2)stringBuilder是可以修改的,不会生成新的对象
(3)stringBuffer和stringBuilder类似,不过stringBuffer是线程安全的,他的方法被synchronized关键字修饰
10、Equals和==的区别?
==方法判断的是两个变量是否指向同一块内存区域,而equals只判断两个变量指向的区域中存储的值是否相同。
11、String中hashCode方法?
hashCode方法是用来计算string的哈希值,这个在将字符串数据存入hashmap中很有用。由于hash冲突的存在,两个hashCode值相同的字符串未必相同。
12、Object类中有哪些方法?
object类中有hashCode、equals、toString、wait、notify、notifyAll、getClass、finalize方法。
13、Java中有哪些常用的数据结构?
Java集合分为两大类,collection和map。其中collection又分list和set。List有ArrayList、 LinkedList、Vector,set有Hashset、LinkedHashSet,Map有HashMap、LinkedHashMap、 ConcurrentHashMap、HashTable
14、ArrayList和LinkedList的区别?
(1)ArrayList底层实现是动态数组,LinkedList是双向链表
(2)ArrayList的查询和修改操作效率高,LinkedList的新增和删除操作效率高
(3)ArrayList的扩容比LinkedList的效率要低一些
15、ArrayList和Vector的区别?
ArrayList是线程不安全的,Vector是线程安全的,Vector的方法都用synchronized关键字修饰。
16、HashMap的底层实现?
在jdk1.7及以前,HashMap的底层是由Entry数组和链表实现的。Entry类中包含有四个属性,分别是key、value、hash值和指向下一个Entry的引用。在jdk1.8及以后,HashMap中链表元素个数超过8个,会转换成红黑树。而红黑树节点数少于6个会转回成链表。
17、HashMap是如何新增数据的?
HashMap新增数据使用的是put方法。
(1)首先判断数组是否为空,如果为空就扩容,否则,根据数据的key计算出hash值,然后通过hash计算出数组的存放位置。通过hash值比较链表上的数与新数据是否相同,如果相同就覆盖,如果不相同,继续遍历,最后放到链表尾部
(2)Jdk1.8版本,HashMap中的链表可能会变成红黑树,所以在确定数据在数组上的存放位置后,要先判断遍历的是链表还是红黑树。而且,数据在放到尾部时,需要判断是否满足从链表变成红黑树的要求,如果满足要求,就要进行转换。
(3)当数据添加到链表尾部时,如果数组长度大于等于64,并且链表元素个数大于等于8,链表就会转换成红黑树。如果数组长度小于64,只会进行数组扩容。
18、HashMap什么时候触发扩容?
当HashMap的元素个数大于(负载因子 * 数组长度),负载因子大小默认是0.75。触发扩容。
19、HashMap的扩容机制?
当HashMap的元素个数大于(负载因子 * 数组长度),触发扩容。数组的长度扩大成原来的两倍。重新计算元素在数组中的位置,如果元素的hash值与旧数组的长度按位&运算是0,那么元素存在数组的旧位置上,否则存在新的位置上。
20、HashMap、HashTable和ConcurrentHashMap的区别?
(1)HashMap是线程不安全的,HashTable和ConcurrentHashMap是线程安全的
(2)HashTable线程安全的做法是将每个方法都用synchronized关键字修饰
(3)在jdk1.7,ConcurrentHashMap线程安全的做法是其有segment数组,每个segment都有一把锁,当有多个线程对segment进行操作时,就会进行加锁。在jdk1.8,ConcurrentHashMap是在数组的每个索引位置加锁。其线程安全的颗粒度比HashTable更小。
21、HashMap的容量规定?
HashMap的容量一定是2的整数次幂,如果初始化时不是2的整数次幂,也会生成对象时给与大于初始化容量数值的一个数,保证是2的整数次幂。
22、HashMap和HashTable的详细差别?
(1)HashMap是线程不安全的,HashTable是线程安全的
(2)HashTable不允许key和value为null,但是HashMap允许。所以在判断key是否存在时,不能根据是否可以通过key获取到value不为空来判断,而是要通过containsKey这个方法
(3)HashMap扩容时原来的2倍,HashTable扩容时原来的2倍+1
(4)HashMap的默认初始容量是16,HashTable是11
(5)HashMap使用的是二次处理后的hash值,HashTable使用的是对象原本的hash值
23、线程与进程的区别?
进程是独立的一个程序,具有某一功能,进程间不能够资源共享。线程是程序执行的最小单元,一个进程中可以有多个线程。线程间共享进程中的堆空间和方法区。线程有自己的栈空间和程序计数器。
24、线程的生命周期?
在java代码中,线程有以下6种状态:
(1)NEW:Thread对象被创建出来,但是还没有执行start方法
(2)RUNNABLE:调用了start方法。cpu调度或者没有调度都是这个状态
(3)BLOCKED:一般没有拿到锁资源,会处于此状态
(4)WAITING:在调用wait方法后,会处于此状态,需要手动唤醒
(5)TIMED_WAITING:在调用sleep或者join方法后,会处于此状态,不用手动唤醒
(6)TERMINATED:线程资源被回收,生命周期结束
25、线程中断,以及线程中断的方式?
(1)线程中断是指通过调用线程的方法,给线程的中断标志位设置成true,此时线程并没有中断,具体什么时候中断,要看代码实现
(2)调用interrupt方法是给线程中断标志位设置成true
(3)调用isInterrupted或interrupted方法是检查线程中断标志位是否为true。两者的区别是,interrupted会在检查到线程中断标志位是true后,默默改成false
26、创建线程的方式?
(1)通过继承Thread类创建线程
(2)通过实现Runnable接口创建线程
(3)通过实现Callable接口创建线程
(4)通过Executors来创建线程池
27、Thread、Runnable和Callable的区别?
(1)Thread用于继承,而且不能放到线程池中执行
(2)Runnable实现后,可以放到线程池中执行
(3)Callable实现call方法,可以放到线程池中执行,而且有返回值
28、Wait和sleep方法的区别?
(1)wait方法是object类的,sleep方法是thread类的
(2)wait方法执行前先要获取锁,sleep方法不用
(3)wait方法执行后会释放锁,sleep不会
(4)wait方法被唤醒的方式是notify或notifyAll,sleep是一段时间后被唤醒
29、Sleep和yield方法区别?
(1)sleep方法执行后,线程调度器在一段时间内都不会调用此线程
(2)yield方法执行后,线程虽然释放了cpu,但是线程调度器随时可能会调度此线程
30、Join方法?
join方法可以控制当前线程的执行。当前线程调用其他线程join方法,当前线程需要等其他线程执行结束才能往下执行。
31、守护线程?
守护线程是默默运行在后台的线程,它在执行着某个任务,像垃圾回收线程就是这种
32、介绍下Threadlocal?
线程对象都有一个ThreadLocalMap属性,这个map可以存线程的本地变量。map存的数据key为ThreadLocal对象本身,value为变量副本。对map进行操作,可以通过ThreadLocal对象,其提供了get和set方法。在第一次调用方法时,会初始化ThreadLocalMap。
33、有哪些常见线程池?
(1)newFixedThreadPool:最大核心线程数与最大线程数相同的线程池
(2)newSingleTreadPool:最大核心线程数与最大线程数是1的线程池
(3)newCachedTreadPool:最大核心线程数为0,最大线程数为整数最大值的线程池。如果有任务,但是没有空余线程,就会创建线程
(4)newScheduledThreadPool:最大核心线程数需要设置,而最大线程数为整数最大值的线程池,执行定时任务
34、线程池的参数?
(1)最大核心线程数:空闲时不会被销毁的线程数
(2)最大线程数:线程池中最多可以被创建出来的线程数
(3)非核心线程空闲时间:非核心线程在这段时间内没有任务的话会被销毁
(4)任务队列:存放任务的队列
(5)线程工厂:用于创建线程
(6)拒绝策略:指线程池任务队列已经满了,并且没有空闲线程了,此时新任务来了,此时采用的行为。包括有丢弃任务并且抛异常、丢弃任务不抛异常、让调用者线程执行任务和丢弃任务队列中最老的任务,将新任务加入队列并尝试提交。
35、线程池怎么工作的?
(1)新任务来了后,先判断有没有空闲的线程,没有的话,判断当前的线程数有没有超过核心线程数,没有的话,创建新线程执行任务;
(2)如果达到核心线程数,判断任务队列是否满了,如果没满,放入队列中;
(3)如果任务队列满了,判断线程数是否超过最大线程数,没有的话,创建新线程执行任务;
(4)如果达到最大线程数,执行拒绝策略。
36、executor和submit的区别?
(1)executor方法执行的是没有返回值的任务,Runnable任务;
(2)submit可以执行有返回值的任务,Callable任务。因为有返回值,所以可以知道任务是否执行成功。任务返回的是Future对象,调用get方法可以获取任务的返回值。如果执行任务发生异常,submit代码有处理异常的逻辑。
37、Shotdown和shotdownnow的区别?
(1)shotdownnow会调用线程的interrupt方法修改中断标志位,shotdown方法不会。
(2)shotdown不会影响正在执行的任务,而且队列中的任务还会被执行
(3)shotdownnow会终止正在执行的并且响应中断的任务,队列中的任务也不会被执行,而且会返回等待执行的任务列表
(4)这两个方法执行后,线程池不再接收新的任务
38、线程池的状态?
(1)running:线程池正在执行任务
(2)shotdown:线程池不接收新任务,继续执行剩余任务
(3)stop:线程池不接收新任务,终止正在执行的任务
(4)tidying:线程池执行完所有任务,在整理资源,进行收尾工作
(5)terminated:线程池资源全部释放
39、线程池中线程数怎么设置?
主要看执行的任务类型,如果任务是cpu密集型,说明要频繁使用cpu,而cpu资源有限,所以一般设置成cpu核数+1;如果任务是io密集型,说明有频繁的io操作,cpu空闲时间比较多,所以可以设置更多的线程数,比如cpu核数的2倍
40、什么是死锁?
两个或两个以上的线程,为了抢夺资源而出现的相互等待的现象。例如,线程A有了资源x,但是要抢夺资源y,线程B有资源y,但是要抢夺资源x,然后线程A和线程B相互等待对方释放资源。
41、出现死锁要达成的条件?
(1)线程占有的资源是互斥的,不能被其他线程同时占有
(2)线程占有的资源不能被其他线程抢夺
(3)线程在占有资源的同时,申请其他资源
(4)多个线程之间形成环路等待,线程A持有资源x,等待资源y,线程B持有资源y等待资源x
42、如何避免死锁?
(1)互斥条件一般是无法被破坏的
(2)一次性申请所有的资源,这样就不会存在占有部分资源,又去申请资源的情况
(3)在出现资源申请不到的情况,就放弃占有的资源
(4)所有的线程,按照顺序申请资源,申请到A后,才能去申请B,所有的线程都必须这样
43、Volatile关键字的作用?
(1)第一个作用是可见性,线程对内存中数据的操作,其他线程也能看见
(2)防止指令重排序,在JVM中,通过对volatile修饰的变量的读写操作前后加上内存屏障防止指令重排序
44、Volatile如何保证可见性?
是缓存一致性协议保证的。Volatile修饰的变量被修改后,数据写回内存中,其他cpu缓存中这个内存地址的数据无效。其他cpu是通过嗅探在总线上的数据来确认数据是否失效的。当cpu发现缓存行中的数据被修改过,这个缓存行被设置为无效状态。
45、Volatile为什么无法保证原子性?
原子性意味着操作不可以被中断,但是volatile修饰的变量的操作是可以被中断的。例如i++这个操作,一个线程将变量数据读取到缓存后,修改完成,准备写回内存时发生中断;此后,其他线程读取i的数据并修改,原来的线程恢复中断后,会继续往内存中写i的数据,这就不符合原子性。
46、Synchronized关键字用法?
(1)修饰普通方法,使用的是对象锁
(2)修饰静态方法,使用的是类锁
(3)修饰代码块,使用的是括号中的锁
47、Synchronized作用?
(1)原子性:synchronized修饰的代码不能被中断,要么全部执行完成,要不全部不执行
(2)可见性:共享变量的修改确保可见。具体实现是当线程执行synchronized修饰的代码时,所需要的变量都会重新从内存中读取到最新的值。
(3)有序性:保证被同一把synchronized锁修饰的多个代码块或者方法按照顺序依次执行
(4)排他性:synchronized修饰的代码同一时刻只能被一个线程执行
48、Synchronized原理?
synchronized是通过获取monitor和释放monitor来实现同步的。在指令层面是通过monitorenter和monitorexit两个指令。在同步代码块开始地方执行monitorenter指令,获取对象头中的monitor,在代码块结束,或者异常位置,执行monitorexit指令,释放monitor。每个对象的对象头中都有monitor,所以每个对象都可以作为锁。同步方法也是通过monitor实现的,不过逻辑与同步代码块不同。
49、什么是偏向锁、轻量级锁和重量级锁?
(1)偏向锁:一个线程获得锁之后,下次再执行同步代码块,不用重复获取锁,只需判断对象头中的线程id是否是此线程
(2)轻量级锁:当有其他线程来获取锁,但是未发生锁竞争时,偏向锁就会升级成轻量级锁
(3)重量级锁:当发生锁竞争时,轻量级锁会升级成重量级锁。未获取锁的线程会先自旋一定次数尝试获取锁,如果获取不到就堵塞
50、锁的分类?
(1)公平锁和非公平锁:公平锁是线程会按照申请的顺序依次获得锁;非公平锁是刚来申请的线程优先获得锁
(2)共享锁和独占式锁:共享锁是同一时刻会有多个线程获得同步状态;独占锁是只有一个线程可以
(3)悲观锁和乐观锁:悲观锁是每次访问资源都会加锁,默认每次都会有线程来抢夺资源;乐观锁是不会锁定资源,线程可以共享资源,在不发生冲突的情况下修改资源,例如CAS操作
51、CAS是什么?
CAS即比较再交换。将内存中的数据读取到缓存中,修改缓存中数据之前,先比较缓存中的旧数据与内存中的数据是否相同,如果相同,在修改缓存中的数据并且写入内存,否则不进行修改。在比较之前,会对内存中的数据加锁(硬件层面实现),并且在完成比较和修改数据这些操作之后,才会释放锁,而且这些操作不会被中断,以此来保证原子性。
CAS操作是原子性的,可以和volatile搭配使用保证线程安全。例如i++操作,CAS会对数据进行比较后,发现新旧数据不一样就不会写入内存了。如果在CAS比较期间,其他线程修改了缓存中i的值,缓存一致性可以保证CAS能感知到。
52、CAS存在的问题?
(1)ABA问题:是指内存中的数原本是A,后来变成了B,最后又变回了A,这样是无法检测到的。解决办法是在数据上加个版本号,发生变化就修改版本号
(2)CAS自旋时间长会浪费cpu资源
(3)CAS只能保证一个共享变量的原子性
53、有哪些原子类?
(1)基本类型原子类:AtomicInteger、AtomicLong、AtomicBoolean
(2)数组类型原子类:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
(3)引用类型原子类:AtomicReference、AtomicStampedReference
54、什么是AQS?
AQS是队列同步器,它有一个state成员变量,表示同步状态,还有一个Node内部类,是双向链表的节点,用于存储抢锁失败的线程。线程首先会获取同步状态,如果state不为0,则获取失败,AQS会把线程和等待状态等信息封装成一个节点放入队列中,同时会阻塞此线程,当线程释放同步状态时,会唤醒队列中head节点的下一个节点中的线程去获取锁,并且设置这个节点为head节点。
55、Error和Exception的区别?
(1)error是发生错误,这个JVM是无法处理的,会导致JVM奔溃,程序退出
(2)exception分为运行时异常和非运行时异常,非运行时异常在编译阶段会报错,运行时异常是在代码运行时会报错。
56、Throw和throws的区别?
(1)throw用在方法内部,手动抛出异常
(2)throws用于方法声明中,将异常抛给调用者
57、final、finally和fianlize的区别?
(1)final用于修饰类、方法和变量,修饰类时不能被继承,修饰方法时不能被重写,修饰变量时不能被重新赋值
(2)finally与try_catch连用,用于处理异常
(3)finalize是object类中的方法,会被GC垃圾回收器调用
58、Finally一定会被调用吗?
不一定,比如在try代码中程序退出了或者在进入try之前,发生了异常,那么finally就不会被执行。
59、Finally什么时候会被执行?
执行到try中的代码时,大概率会执行,除非遇到exit方法。在return语句执行前,会去执行finally方法,如果finally方法中也有return语句,那么会执行finally方法中的return语句中的值。
60、常见的异常类?
空指针异常、数组越界异常、类型转换异常、非法参数异常、io异常、sql语句异常
61、主线程可以捕捉到子线程的异常吗?
可以捕捉到。简单方式是通过try-catch语句来捕捉。
62、什么是反射?
通过Class对象可以获取到类的成员变量,构造方法,普通方法。获取Class对象的方法有三个。
(1)Class.forName(类的全限定名);
(2)类名.class;
(3)对象实例.getClass()
63、什么是java序列化?
java对象序列化是将对象转换成字节序列,反序列化是将对象的字节序列转换成对象。
64、什么时候需要序列化?
(1)需要将对象存入文件或者数据库中,即对象落盘
(2)需要将对象通过网络传播
65、动态代理是什么?
动态代理可以在不改变源代码的情况下,对方法进行功能加强。动态代理与静态代理不同,一个动态代理可以对多个类方法进行加强。Java实现的动态代理方式有jdk动态代理和cglib。jdk动态代理是通过实现接口的方式实现动态代理,cglib是通过继承的方式实现。
66、什么时候用到动态代理?
spring的aop特性还有mybatis的数据库连接池。
67、怎么实现对象的克隆?
实现cloneable接口,重写clone方法,使用对象调用此方法。
68、什么是浅拷贝和深拷贝?
(1)浅拷贝是将对象的内存空间中的内容拷贝了一份到新的内存空间,但是内存空间中的引用还是一样的
(2)深拷贝不仅拷贝了内容空间中的内容,还将空间中的引用指向的数据也拷贝了一份
69、什么是BIO、NIO和AIO?
(1)BIO是阻塞性IO,发送网络请求后,线程阻塞,等待返回响应
(2)NIO是非阻塞性IO,发送网络请求后,线程不会阻塞,但是进程中会有一个线程循环检查是否有网络响应
(3)AIO是异步非阻塞性IO,发送网络请求后,如果有响应,会调用回调函数,线程会过来处理
(4)BIO适合网络请求很少的场合;NIO适合网络请求较多,但是响应很快的场合;AIO适合网络请求多,而且响应慢的场合。
70、Comparator和Comparable的区别?
(1)通过实现Comparator的compare方法,传入两个参数,比较这两个参数的值。Comparator不用修改要比较的对象的类代码,它可以定义多种比较器。
(2)通过实现Comparable的compareTo方法,传入一个参数,比较实现Comparable接口的对象的属性与传入的参数对象的属性大小。需要类去实现Comparable接口,实现compareTo方法。由于只能实现一次,所以只能定义一种规则。
// Comparable
class Student implements Comparable<Student> {
String name;
int age;
@Override
public int compareTo(Student other) {
return this.age - other.age; // 按年龄升序
}
}
// 使用
List<Student> students = new ArrayList<>();
students.add(new Student("Alice", 20));
students.add(new Student("Bob", 18));
Collections.sort(students); // 自动调用 compareTo
// Comparator
class Student {
String name;
int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// Getter 方法
public String getName() { return name; }
public int getAge() { return age; }
}
public class Main {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
students.add(new Student("Alice", 20));
students.add(new Student("Bob", 18));
students.add(new Student("Charlie", 22));
// 使用匿名内部类定义 Comparator
Comparator<Student> ageComparator = new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
return s1.getAge() - s2.getAge(); // 按年龄升序
}
};
// 排序
Collections.sort(students, ageComparator);
// 输出结果
for (Student s : students) {
System.out.println(s.getName() + ": " + s.getAge());
}
}
}