Java基础面试题
1.jdk与jre的区别
jdk(Java Development Kit ):java开发工具包
jre(Java Runtime Environment):Java运行环境
jdk中包含了jre,在jdk根目录中有一个jre目录,其中的两个文件夹bin和lib,bin就是jvm虚拟机,lib就是jvm所需要的类库.
2.==和equals的区别
==:当数据是基本类型时,比较的是数据的值,数据是引用类型时,则比较地址值
equals:底层原理就是==,如果数据没有重写equals和hashcode那么作用和==一样,
重写了equals和hashcode,则比较内容是否相等.
3.final的作用
(1)修饰引用
1.修饰基本类型:该数据成为常量,此数据不能被修改
2.修饰引用类型:对象,数组等,他们本身可以修改,但是对象,数组的引用不能改变
3.修饰成员变量:必须赋值,否则编译报错
(2)修饰方法
1.修饰方法,此方法不能被重写,但是可以被继承,叫做最终方法
(3)修饰类
1.修饰类,此类不会被继承,叫做最终类,比如String类就是被final修饰的
4.java的数据类型有哪些
java由两大数据类型分别是基本类型和引用类型
基本类型共有八种:
1.byte:
2.short
3.int
4.long
5.float
6.double
7.char
8.boolean
引用类型:比如String,Arrays数组,以及自己创建的类等
5.String a = “i” 和 String a = new String(“i”)一样吗:
不一样.前者的数据会被储存到常量池中,常量池中没忽悠重复的元素,如果常量池中存在该数据,那么直接将地址赋予变量,如果没有,就新建一个在赋值给变量
后者会储存在堆中,不管有没有这个数据,都会重新创建对象,分配一个新的地址
6.如何反转字符串
将字符串封装为StringBuilder
String a = “abc”;
StirngBuilder sb = new StringBuilder(a);
String b = sb.reverse().toString();
System.out.println(b);
7.普通类和抽象类的区别
普通类:普通类中全是普通方法
抽象类:抽象类中必须有抽象方法,也可以由普通方法
抽象类用abstract修饰
抽象类不能被实例化,但是有无参构造,但是是为了子类的创建提供super()
抽象类的子类必须实现所有父类中的抽象方法,如果没有,则子类也是抽象类
抽象方法不能被static,final修饰
8.java中IO流分为哪些:
(1)按流分:输入流和输出流
(2)按单位分:字节流和字符流
字节流:字节输入流(inputStream),字节输出流(OutputStream)
字符流:字符输入流(Reader),字符输出流(Writer)
9.抽象类和接口的区别
接口:
接口用interface修饰
接口不能被实例化
接口没有构造方法
类可以实现多个接口
在java8以前,接口中的方法都是抽象方法,默认是public abstract
java8以后,和以哦存在静态方法,静态方法必须有方法体,普通方法没有方法体
抽象类:
抽象类用abstract修饰
抽象类不能被实例化
抽象类有构造方法,作用是实例化子类时用
抽象类中可以含有普通方法,抽象方法
父类时抽象类,子类必须实现父类所有抽象方法,否则子类也是抽象类
10.final,finally,finalize的区别
首先这三个是毫不相干的概念
final:
被final修饰的基本数据类型,叫做常量,数据不能被修改
被final修饰的方法不能被重写
被final修饰的类不能被继承
finally:
和try-catch连用,在finally中的代码肯定被执行,也经常用于关流
finalize:垃圾回收机制
11.java操作字符串都有哪些
String:
String是不可变对象,String类被final修饰,每次改变String内容都会生成一个新对象
StringBuilder:
线程安全,用于多线程,因为枷锁的原因效率稍低
StringBuffer:
线程不安全,效率高,用于单线程
StringBuilder>StringBuffer>String
12.集合的特点
1.集合可以储存多个对象
2.数据个数确定可以用数组,个数不确定使用集合,因为集合的长度可变
13.集合与数组的区别
1.数组的长度不可变,集合长度可变
2.数组中可以储存基本数据类型和引用类型,集合只能储存引用类型
3.数组中的数据类型都相同,集合中可以储存不同类型的数据
14.使用集合的好处
1.容量自增长
2.提供高性能的数据结构和算法,提高程序的速度和质量
3.允许不同API之间相互操作,API之间可以来回传递集合
4.可以方便的扩展和改写集合,提高了代码的重用性和可操作性
5.通过jdk自带的集合,维护代码和学习成本都大大降低
15.常用的集合类有哪些?
Collection接口和Map接口时所有集合的父接口
1.Collection接口的子接口有:Set集合和List集合
2.Map集合的子接口有:HashMap,TreeMap,Hashtable,ConcurrentHashMap以及Properties等
3.Set的实现类有:HashSet,TreeSet,LinkedHashSet
4.List的实现类有:ArrayList,LinkedList,Stack以及Vector
16.包装类
自动装箱与拆箱
装箱:将基本类型用他们相应的引用类型包装起来
拆箱:将包装类型自动变为基本数据类型
java为什么要引用包装类
基本数据类型只能用作数据的储存,并没有相应的一些方法对其操作,为了让基本数据类型可以像对象一样操作,就引用了包装类
基本数据类型 -------------------------------包装类
byte----------------------------------------------Byte
short---------------------------------------------Short
int-------------------------------------------------Integer
long----------------------------------------------Long
double-------------------------------------------Double
float----------------------------------------------Float
boolean-----------------------------------------Boolean
char----------------------------------------------Character
Integer a = 127 与 Integer b = 127相等吗?
答:相等,对于引用类型而言==比较的是地址值是否相同,如果对比值在-128到127之间,那么就会直接到常量池中查找,不会新建对象,所以相等.如果不在这个范围,就会重新创建对象,据不相等了.(扩展:-128-127这个范围是可以修改的,通过修改jvm参数即可)
17.遍历一个 List 有哪些不同的方式?每种方法的实现原理是什么?
1.for循环遍历,基于计数器,按照下标一次读取元素
2.Ietrator迭代器:可以遍历Collection中所有集合,这是一个面向对象的设计模式
3.foreach循环遍历,也叫加强for循环,底层其实就是Iterator迭代器,不需要声明
18.说一下ArrayList的优缺点
优点:
1.ArrayList底层数据结构是数组,是一种随机访问模式,实现了RandomAccess接口,查找的时候比较快
2.在顺序添加元素的时候比较快
缺点:
1.删除和插入元素时需要做一次元素复制操作,如果元素较多,那么就比较耗内存
19.数组和List之间如何相互转换
Array转List:使用Arrays.asList(array)
List转Array:使用List自带的方法toArray()
20.ArrayList与LinkedList的区别是什么?
数据结构:ArrayLIst是动态数组,LinkedLIst是双向链表
随机访问效率:ArrayList比LinkedList效率高,因为LinkedList时链表结构,需要通过前后指针查找元素.
增删操作:在非首尾操作时,LinkedList比ArrayList效率高,因为ArrayList会影响所有元素的下标.
21.多线程场景下如何保证ArrayList线程安全
ArrayList线程是不安全的,如果遇到多线程问题,可以通过Collections的synchronizedList方法将其转换成线程安全的的容器后在使用
public static void main(String[] args) {
List<String> list = new ArrayList<>();
List<String> synchronizedList = Collections.synchronizedList(list);
synchronizedList.add("aaa");
synchronizedList.add("bbb");
for(int i = 0; i<synchronizedList.size(); i++){
System.out.println(synchronizedList.get(i));
}
}
22.List与Set的区别
List,Set都是Collection的子接口
List特点:有序,元素可重复,可以包含null,元素都有索引,常用的实现类是ArrayList,LinkedList,Vector.
Set特点:无序,元素不可重复,只允许储存一个null,必须保证元素唯一性,Set的接口是HashSet,LinkedHashSet以及TreeSet.
List支持for循环遍历,也支持Iterator迭代器遍历,Set值能通过Iterator遍历,因为是无序的,无法使用下标操作
Set:增删效率高,查询效率低
List:查询效率高,增删效率低
23.HashMap的储存过程
在jdk1.8版本中,HashMap底层数据结构是数组加链表或者数组加红黑树,HashMap底层是Entry数组,默认长度是16.当数据长度超过其75%时,自动扩容,扩容长度时原来的两倍.
HashMap在储存数据时会根据key的hash值/n的结果给值分配位置,n时Entry的长度,如果这个位置没有数据,就直接添加,有数据就会发生hash碰撞,就会链在后边成为数组加链表的结构,当链表长度大于8Entry数组长度超过64时,就会变为数组加红黑树的结构,当链表小于6时,再恢复为数组加链表的结构.
24.为什么HashMap中String,Integer这样的包装类适合做key
1.String和Integer都是final修饰的,值都是不可变的,满足了key值不可变的需求
2.内部重写了equals和hashcode,遵循了HashMap的内部规范,不容易出现hash值错误
25.如果使用Object作为key应该怎么做
1.重写equals和hashcode方法,重写hashcode方法是为了计算数据储存的位置.
2.重写equals是为了保证key的唯一性.
26.有关String
26.1 什么是字符串常量池?
字符串常量池位于堆内存中,专门用来储存字符串常量,提高内存的使用效率,避免开辟多个空间用来储存相同的字符串,在创建字符串时,jvm首先会扫描常量池中是否存在,如果有,直接返回其引用,如果没有就放到池中并返回其引用
26.2 String的特性
1.不变性:String是只读字符串,对其进行任何操作,都会创建新的对象再将引用指向该对象,这就保证了其线程的安全性
2.常量池优化:String创建时会在常量池中储存,如果再次创建相同的对象,则直接返回缓存中的引用
3.final:String类被final修饰,表明其不可被继承,提高了系统安全性
26.3 String为什么是不可变的
因为String类是被final修饰的,所以是不可变的,但是其引用是可变的,例如
String str = "hello";
str = str + "world";
System.out.println(str);
str这个引用就重新指向了一个String对象.
26.4 数组中有length()吗,String有length()吗
数组中没有length()但是有length属性,String有length()
27.什么情境下使用Set,List,Map,回答他们的优缺点即可
1.如果经常使用索引对容器中的元素进行访问,并且希望插入的元素时有序的,元素也可相同的,同时能插入多个null的情况下使用List
2.如果想要保证插入元素的唯一性,有并指只能有一个null元素的时候可以选择Set
3.想要以键值对的形式储存那么久选择Map
28.ArrayList,LinkedList,Vector的区别
1.ArrayList与Vector都是数组结构,LinkedList时双向链表结构,前两者查询效率比较高,后者增删效率比较高,但是头尾增删前者效率高
2.Vactor线程安全,ArrayList以及LinkedList线程不安全
3.ArrayList在元素填满时自动扩容50%,Vector则是100%,前者更加节省空间
29.Java异常
29.1 Throwable
1.Throwable是java中所有错误与异常的父类
Throwable包含两个子类:Error(错误)和Exception(异常),都表示程序是有问题的
2.Error:表示这不是代码性错误,而是表示程序运行中出现了错误,通常表示JVM出现了问题
3.Exception:表示程序本身出现了问题,我们可以捕获并处理该类异常,通常有4.运行时异常以及编译时异常
运行时异常:RuntimeException类以及子类,表示JVM在程序运行期间出现问题
特点:也就是我们是没有预见性的,java编译器不会检查出来,假如我们既没有try-catch捕获,也没有throws向上抛出,只能说等着程序报错,比如让人头疼的空指针异常NullPointerException,ArrayIndexOutBoundException下标越界异常,ClassCastException类型转换异常,ArithmeticExecption算术异常.这种异常还是可以通过try-catch以及throws来声明抛出.
5.编译时异常:除了RuntimeException之外的异常
特点:java编译器会检查,比如IO异常,ClassNotFoundException没有找到指定类,必须通过try-catch进行捕获或者throws向上抛出,否则编译不通过.
29.2 java异常关键字
try:用于对代码块的监听,一般将可能出现异常的程序放在里面,比如IO流,一旦有异常就会抛出
catch:用于捕获异常,在这里可以打印异常信息等
finally:在此语句块中的代码必定会执行,所以一般将必须执行的代码放进去,比如关流操作
throw:用于抛出异常
throws:用在方法后,表示向上抛出异常
29.3自定义异常
通常自定义异常会提供无参和含参构造,这样使用起来比较多样化比如
@Data
@NoArgsConstructor
@AllArgsConstructor
public class GuliException extends RuntimeException{
private Integer code; // 状态码
private String msg; // 信息
}
自定义异常类继承一个异常类,可以直接继承Exception在提供构造方法,这里用的是lombok插件,加上注解后自动生成含参和无参构造方法
29.4 Error和Exception的区别是什么
Error:一般是虚拟机出现异常,比如内存不足,堆栈溢出等,编译器不会检测,也不会捕获此异常,一旦报错,编译不成功
Exception:一般可以在可应用程序中进行捕获,向上抛出或者try-catch
30.线程并发
java线程实现和创建的方法
1.继承Thread
Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例,启动线程的唯一方法就是Thread调用start()实例方法,start()是一个native方法,也就是平台方法,他将启动新线程并调用run().
package com.jt.thread;
public class ThreadTest {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
class MyThread extends Thread{
@Override
public void run() {
System.out.println("this is my thread");
}
}
2.实现Runnable接口
因为java是单继承, 所以当一个类继承其他父类后就不可以在继承Thread了,所以科技实现Runnable接口
package com.jt.thread;
public class RunnableTest {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("this is my runnable");
}
}
30.1线程池
java中顶级的线程池接口是Executor,但实际线程池的接口是ExecutorService
四个线程池
1:newCachedThreadPool:可创建足够多的线程,其中调用execute()将重用以前的线程,加入没有,则创建新线程并添加到池中,终止并移除60秒内未使用的线程.淫才长时间不用的线程不会使用任何资源
2:newFixedThreadPool:创建一个可指定线程池线程数的池
3:newScheduledThreadPool:可设置延时后运行或者定期执行
4:newSingleThreadPool:穿件一个只有一个线程的线程池,当原有的线程挂掉后可创建新的线程代替
30.2线程生命周期
当线程被创建并启动后,并不是立马开始工作,而是需要经历新建-就绪-运行-阻塞-死亡五种状态.线程启动后,会根据cpu时间线来决定其当前状态,并不是一直运行.
新建状态:new一个新的线程,此时线程处于新建状态,由jvm分配地址,初始化成员变量值
就绪状态:线程调用了start方法,此时处于就绪状态,等待cpu分配时间线
运行状态:cpu分配给就绪的线程,此线程就开始运行
阻塞状态:运行中的线程遇到某种状况后,失去了cpu的使用权,此时就处于阻塞状态,等待再次拥有cpu使用权.
死亡状态:
1:正常死亡
2:抛出异常
3:手动调用stop()方法,但是此方法是线程不安全的,所以不推荐使用
30.3 sleep与wait的区别
1.sleep属于Thread中的方法,wait是属于Object中的方法
2.sleep执行后会暂停当前线程指定的时间,让出cpu,但是会处于监听状态,暂停时间过去后,重新回复运行状态
3.调用sleep方法线程不会释放锁对象
4.调用wait方法时会放弃锁对象,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后对象所定池准备获取对象锁进入运行状态
30.4 run()与start()的区别
1.start()方法用来启动线程,真正实现了多线程运行,无需等待run()方法方法体执行完毕,可以直接执行下面代码
2.调用start()后,线程并不是立即运行,而是处于就绪状态等得cpu的时间线
3.run()成为线程体,线程要完成的任务在run()方法中,run()执行完毕,当前线程执行结束.