JAVA基础

JAVA基础常见面试题

Posted by Jian Shen on April 29, 2020

JAVA基础

1、JAVA中的几种基本数据类型是什么,各自占用多少字节?

数据类型 字节 默认值
byte 1 0
short 2 0
char 2 ‘\u0000’
int 4 0
float 4 0.0f
long 8 0
double 8 0.0d
boolean 4 false

PS:JVM规范中,boolean当做int处理,也就是4个字节,而boolean数组当做byte数组处理,即boolean类型的数组里面每一个元素占一个字节

2、String类能被继承么,为什么?

不可以,因为String类有final,而final修饰的类是不能被继承的

3、String、StringBuffer、StringBuilder的区别及扩容机制

  • String:不可变,每次对String进行操作都会产生新对象,效率低且浪费内存空间
  • StringBuffer:可变字符序列,效率低,线程安全
  • StringBuilder:可变字符序列,效率高,线程不安全

StringBuilder、StringBuffer继承AbstractStringBuilder,无参数默认初始容量为16,追加字符串的时候长度超过16则扩容:增加自身长度一倍再加2,如果还放不下,则扩容为所需长度minCapacity

1
2
3
4
5
6
private int newCapacity(int minCapacity){
	int newCapacity = (value.length <<1)+2;
    if(newCapacity - minCapacity < 0){
        newCapacity = minCapacity;
    }
}

4、ArrayList和LinkedList有什么区别

  • ArrayList:基于数组实现的非线程安全的集合。查询速度快,插入、删除中间元素速度慢
  • LinkedList:基于链表实现的非线程安全的集合。采用synchronize加锁,性能比ArrayList差
  • CopyOnWriteArrayList:基于数组实现的线程安全的写时复制集合,采用ReentrantLock加锁,性能比Vector高,适合读多写少的场景

5、讲讲类的实例化顺序,比如父类静态数据,构造函数,字段,子类静态数据,构造函数,字段,当new的时候,他们的执行顺序。

父类静态变量

父类静态代码块

子类静态变量

子类静态代码块

父类非静态变量(父类实例成员变量)

父类构造函数

子类非静态变量(子类实例成员变量)

子类构造函数

6、用过哪些Map类,都有什么区别,HashMap是线程安全的么?并发下使用的Map是什么,他们内部原理分别是什么,比如存储方式,hashcode,扩容,默认容量等

HashMap、ConcurrentHashMap、TreeMap、LinkedHashMap

HashMap线程不安全,由数组(Node数组)+链表+红黑树(链表长度大于8转换为红黑树)实现。采用

hash表来存储,key采用hashcode,数量超过threshold时进行2倍扩容,默认容量16,负载因子0.75,桶的数量11,采用链地址法解决hash冲突,简单来说,就是数组加链表的结合。在每个数组元素上都有一个链表结构,当数据被hash后,得到数组下标,把数据放在对应下标元素的链表上,大量采用CAS操作,JDK1.7对Segment进行加锁,JDK1.8对桶中的头结点进行加锁。

TreeMap和LinkedHashMap有序的,TreeMap默认升序,LinkedHashMap记录插入的顺序

6.1 为什么HashMap是非线程安全的?什么情况下会出现线程安全的问题?

HashMap会进行resize操作,在resize操作的时候会造成线程不安全

  • put的时候导致多线程数据不一致
  • get操作可能因为resize而引起死循环(cpu100%)

7、JAVA8的ConcurrentHashMap为什么放弃了分段锁,有什么问题吗?如果你来设计,你会如何设计

Segment继承ReentrantLock,每个锁控制一段,当每个Segment越来越大时,锁定粒度则变大,性能会下降ConcurrentHashMap底层采用数组+链表+红黑树,大量采用CAS操作,加锁采用synchronized,只对桶的头节点进行加锁,粒度较小。

  • 减少内存开销。如果采用ReentrantLock需要节点继承AQS来获取同步支持,增加内存开销,

  • 内部优化。 synchronized是有JVM直接支持的,JVM能够在运行时做相应优化:锁粗化、锁消除、锁自旋等。

8、有没有有顺序的Map实现类,如果有,他们是怎么保证有序的

TreeMap和LinkedHashMap有序的(TreeMap默认升序,LinkedHashMap记录插入的顺序)

TreeMap是基于比较器Comparator实现有序的,LinkedHashMap是基于链表实现数据插入有序的

9、抽象类和接口的区别,类可以继承多个类么,接口可以继承多个接口么,类可以实现多个接口么

  • 接口只能做方法声明,抽象类既可以做方法声明也可以做方法实现
  • 接口里定义的变量只能是公共的静态变量,抽象类中变量是普通变量
  • 抽象类里可以没有抽象类,如果一个类中有抽象方法,则一定是抽象类
  • 抽象方法要被实现,所以不能是静态的,也不能是私有的
  • 接口继承接口,并可多继承接口,但类只能单继承
  • 类不能继承多个类
  • 接口可以继承多个接口
  • 类可以实现多个接口

10、继承、组合、聚合有什么区别

继承:is-a的关系,指一个类继承另一个类 public class A extends B{}

聚合:has-a的关系,A可以有B public class A {List<B> b}

组合:contains-a,强聚合,A一定有B public class A {B b}

####11、IO模型有哪些,讲讲你理解的nio,它和bio,aio的区别是啥,谈谈reactor模型

BIO、NIO、AIO

  • BIO: 同步并阻塞。客户端有一个IO请求,服务端就需要启动一个线程进行处理。适用于连接数比较少,tomcat采用传统的BIO+线程池模式
  • NIO:同步非阻塞。客户端的IO请求会注册到多路复用器上,多路复用器轮询到有IO请求时才启动一个线程处理。适用于连接数多且连接比较短(轻操作)的架构,比如聊天服务器
  • AIO:异步非阻塞。客户端的IO请求都是有OS完成后,在通知服务器启动线程处理。适用于连接数多且连接比较长(重操作)的架构,如相册服务器

Reactor模型

  • 事件驱动
  • 可以处理一个或多个输入源
  • 通过Service Handle同步的将输入事件采用多路复用分发给相应的Request Handler(一个或多个)处理

12、反射的原理,反射创建类实例的三种方式是什么

所谓的反射机制就是JAVA语言在运行时拥有一项自观的能力

1
2
3
4
5
Class class1 = HelloWorld.class;
Class class2 = helloWorld.getClass();
Class class3 = Class.forName("com.huximi.HelloWorld");

class1.newInstance();

13、反射中,Class.forName和ClassLoader的区别

Class.forName(className)内部实际调用的是Class.forName(className,true,classLoader),其中true表示初始化,静态变量与静态代码块执行

ClassLoader.loadClass(className)内部实际调用的是ClassLoader.loadClass(className,false),false表示目标对象不进行链接(即验证、准备、解析),意味着也不进行初始化工作,静态变量与静态代码块不执行

14、描述动态代理的几种实现方式,分别说出相应的优缺点

JAVA动态代理 借助JAVA内部反射机制来生成代理接口匿名类,在调用具体方法前调用InvocationHandler,反射机制在生成类的过程中比较高效,应用前提必须为目标类基于统一的接口

CGLIB动态代理 借助ASM来实现,对代理对象的class文件加载进来,通过修改其字节码生成子类来处理,ASM在生成类后执行过程中比较高效

15、为什么CGLIB方式可以对接口实现代理

对接口进行代理的CGLIB,最后的源码是实现了该接口和Factory接口

对实现类进行代理的CGLIB,最后的源码时继承了实现类并实现了Factory接口

16、final的用途

final 修饰符既可以修饰类、方法,也可以修饰变量

  • 用final修饰的类不能被继承
  • 用final修饰的方法不能被重写
  • 用final 修饰的变量只能赋值一次且不可修改,静态、实例、局部变量必然被明确赋值

17、写出单例模式的三种实现方式

见单例模式

18、如何在父类中为子类自动完成所有的hashcode和equals实现?这么做有何优劣

父类的equals方法不一定满足子类equals的需求,比如所有的对象都继承Object,默认使用其equals方法,比较两个对象的时候,看他们是否指向同一个地址

但我们的需求是对象的某个属性相同,就相同,此时需要重写equals方法与hashcode方法,否则会降低map等集合的索引速度

19、请结合 OO设计理念,谈谈访问修饰符public 、private、protected、default在应用设计中的作用

OO设计理念:封装、继承、多态

关键字 类内部 本包 子类 外部包
public
protected ×
default × ×
private × × ×

20、深拷贝和浅拷贝的区别

如果一个对象内部只有基本数据类型,那么clone()方法获取到的就是这个对象的深拷贝,而如果其内部还有引用数据类型,那么clone()方法:

浅拷贝 对基本数据类型进行值拷贝,对引用数据类型进行引用传递般的拷贝,此为浅拷贝;

深拷贝 对基本数据类型进行值拷贝,对引用数据类型,创建一个新的对象,并复制其内容,此炎深拷贝

  • 序列化
  • clone 方法 对其内部引用类型的变量,再进行一次clone

21、数组和链表数据结构描述,各自的时间复杂度

数组 元素在内存中连续存放,可以通过下标迅速访问数组中任何元素。但是如果要在数组中增加一个元素,需要移动大量元素,在内存中空出一个元素的空间,然后将要增加的元素放在其中。同样的道理,如果想删除一个元素,同样需要移动大量元素去填掉被移动的元素。如果应用需要快速访问数据,很少或不插入和删除元素,就应该用数组。

链表 元素在内存中不是顺序存储的,而是通过存在元素中的指针联系到一起。比如:上一个元素有个指针指到下一个元素,以此类推,直到最后一个元素。如果要访问链表中一个元素,需要从第一个元素开始,一直找到需要的元素位置。但是增加和删除一个元素对于链表数据结构就非常简单了,只要修改元素中的指针就可以了。如果应用需要经常插入和删除元素,你就需要用链表数据结构了。

数组和链表的区别

  • 从逻辑结构角度来看:
    • 数组必须事先定义固定的长度(元素个数),不能适应数据动态地增减的情况。当数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费。
    • 链表动态地进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据项。(数组中插入、删除数据项时,需要移动其它数据项)
  • 数组元素在栈区,链表元素在堆区
  • 从内存存储角度来看:
    • (静态)数据从栈中分配空间,对于程序员方便快速,但自由度小。
    • 链表从堆中分配空间,自由度大但申请管理比较麻烦。
    • 数组利用下标定位,时间复杂度为O(1),链表定位元素时间复杂为度O(n);
    • 数组插入、删除元素的时候复杂度为O(n),链表的时间复杂度为O(1);

22、error和exception的区别,CheckedException、RuntimeException 的区别

  • Error: 程序无法处理的错误,如OutOfMemoory
  • Exception:程序可以处理的异常
  • RuntimeException表示虚拟机运行时可能遇到的错误,只要程序设计的没有问题通常不会发生。如空指针、数据下标越界异常
  • CheckedException与运行的上下文环境有关,即使程序设计无误,仍然可能因为使用问题而发生。

编译器不要求声明抛出RuntimeException,要求必须声明抛出CheckedException

23、请列出5个运行时异常

ClassCastException、IndexOutOfBoundsException、NullPointerException、ArrayStoreException、BufferOverflowException

24、在自己的代码中,如果创建一个java.lang.String类,这个类是否可以被类加载器加载?为什么

不能。由于双亲委派柳模型限制,先从父加载器加载,依次为引导类加载器 > 扩展类加载器 > 应用程序类加载器,父加载器找不到,才从子加载器加载,依次类推

25、在JDK1.5中,引入了泛型,泛型的存在是用来解决什么问题

泛型主要针对向下转型时带来的安全隐患,其核心组成是在声明类或接口时,不设置参数或属性的类型

类型擦除: 编译器声明的类型擦除并替换为Object(如果设置了上限如 T extend List)则替换为上限,最后插入强制转换,代码才能正常运行

26、这样的a.hashcode() 有什么用,与a.equals(b)有什么关系

hashcode()提供了对象的hashcode值,是一个native函数,返回的默认值与System.identityHashCode(obj)一致 作用是用一个数字标识对象,比如HashMap中的key就是基于hashcode, hashcode只能说是标识对象,在hash算法中可以将对象相对离散些,但不是唯一的,根据hashcode定位到具体的链表后,需要循环链表,然后通过equals()方法来对比key是否是一致的。 equals相等的两个对象,hashcode一定相等;hashcode相等的两个对象不一定equals相等

27、有没有可能2个不相等的对象有相同的hashcode

有。同26

28、Java中的HashSet内部是如何工作的

HashSet内部采用HashMap实现。实现了Set接口,不允许有重复的值,只允许有一个null key

29、什么是序列化,怎么序列化,为什么序列化,反序列化会遇到什么问题,如何解决

什么是序列化

  • 序列化 把对象转换为字节序列的过程称为序列化
  • 反序列化 把字节序列转化为对象的过程称为序列化

为什么序列化 内存对象保存到文件或数据库时需要序列化; 用套接字在网络上传输对象的时需要序列化; 通过RMI传输对象的时候需要序列化

怎么序列化 对象实现Serializable接口,然后通过ObjectOutputStream.writeObject进行序列化,通过ObjectInputStream.readObject进行反序列化

问题及解决方式 serialVersionUID未定义,导致反序列化报InvalidClassException,这是由于在反序列化时Java会自动为serialVersionUI赋值,导致两边不一致找不到旧数据报错 当属性是对象时,也需要实现serializable接口,不然会报NotSerializableException

ps: transient修饰的属性不参与序列化;静态属性不参与序列化

30、java8的新特性

  • 接口提供默认方法
  • Lambda表达式
  • 函数式接口
  • 方法与构造函数引用
  • 扩展了集合类,可以通过 Collection.stream() 或者 Collection.parallelStream() 来创建一个Stream
  • 在包java.time下包含了一组全新的时间日期API