Java面经

203 阅读7分钟

JAVA基础

1. 什么是面向对象?谈谈你对面向对象的理解。

答:面向对象是指将需求中共性的部分封装成一个具有属性和行为的类,通过操作类和类的实例来完成这个需求。
面向对象有三个特性:

  • 封装
    封装是指将数据和对数据的操作封装为一个不可分割的独立实体,只暴露给外部他想暴露的部分,使用者无需知道这个封装后的实体的内部细节就能操作他。
  • 继承
    子类通过继承父类可以实现方法或数据的拓展。
  • 多态
    多态分为编译时多态和运行时多态,编译时多态即重载,而运行时多态指程序中定义的对象的引用所指的具体类型只有在运行时才能知道 实现多态的三个基本条件:继承、重写、向上转型

2. JDK、JRE、JVM之间的区别?

答:JDK包括Java编译器、Java运行环境和常用的Java类库等,JRE指Java运行环境,用于运行Java字节码,JRE包括JVM和JVM工作所需要的类库,JVM是JRE的一部分,负责运行字节码文件。
Java的一次编译到处运行正是因为JVM和字节码的存在,可以使同一份代码经过编译为字节码文件后,能够在不同机器上运行。

3. hashCode()和equals()之间的关系

答:某些集合类比如hashmap的方法比较两个对象是否相等首先比较两个对象的hashcode值是否相等,然后再去通过equals()方法进行比较,所以通常重写了equals()方法后,要注意hashcode()的影响。
通过hashcode可以做出以下判断:

  • 如果两个对象的hashcode值不相同,那么这两个对象必定是不同的两个对象
  • 如果两个对象的hashcode值相同,这两个对象不一定是同一个对象,因为hashcode可以重写
  • 如果两个对象相同,那么hashcode一定相同

4. String、StringBuffer、StringBuilder的区别

答:String对象是不可变的,如果修改String对象,那会得到一个新的String对象,而StringBuffer和StringBuilder是可变的。
StringBuffer是线程安全的,StringBuilder是线程不安全的,所以单线程情况下StringBuilder的效率更高

5. Java泛型

答:泛型适用于多种数据类型执行相同代码。
泛型方法,是在调用方法的时候指明泛型的具体类型。

  • 定义泛型方法语法格式
  • 调用泛型方法语法格式

说明一下,定义泛型方法时,必须在返回值前边加一个<T>,来声明这是一个泛型方法,持有一个泛型T,然后才可以用泛型T作为方法的返回值。

Class<T>的作用就是指明泛型的具体类型,而Class<T>类型的变量c,可以用来创建泛型类的对象。

为什么要用变量c来创建对象呢?既然是泛型方法,就代表着我们不知道具体的类型是什么,也不知道构造方法如何,因此没有办法去new一个对象,但可以利用变量c的newInstance方法去创建对象,也就是利用反射创建对象。

泛型方法要求的参数是Class<T>类型,而Class.forName()方法的返回值也是Class<T>,因此可以用Class.forName()作为参数。其中,forName()方法中的参数是何种类型,返回的Class<T>就是何种类型。在本例中,forName()方法中传入的是User类的完整路径,因此返回的是Class<User>类型的对象,因此调用泛型方法时,变量c的类型就是Class<User>,因此泛型方法中的泛型T就被指明为User,因此变量obj的类型为User。

当然,泛型方法不是仅仅可以有一个参数Class<T>,可以根据需要添加其他参数。

为什么要使用泛型方法呢?因为泛型类要在实例化的时候就指明类型,如果想换一种类型,不得不重新new一次,可能不够灵活;而泛型方法可以在调用的时候指明类型,更加灵活。

泛型通过extends和super来确定泛型的上下限。

Java中的泛型是伪泛型,因为只在编译器进行类型消除,将所有泛型表示都替换为具体的类型。

6.重载和重写的区别

答:重载发生在同一个类中,方法名必须相同,但参数类型、参数个数、参数顺序不同,返回类型和修饰符可以不同,重载发生在编译时期,注意,如果两个方法只有返回值不同,那么在编译时期会报错,这不是重载。

public void add(String a, String b) {...}
public String add(String a, String b) {...} //编译报错

重写发生在父子类中,方法名和参数列表相同,但返回值范围和抛出的异常范围小于等于父类,访问修饰符大于等于父类,如果父类方法是private,那么这个方法就不能被重写。

7.List和Set有哪些区别?

答:List是有序的,按对象进入的顺序保存对象,可以重复,可以出现多个NULL元素对象,获取元素的方法有两种,一种是get(index)方法,还有一种通过iterator迭代器获取元素
Set是无序的,不可以重复,只能允许一个NULL元素对象,获取元素也只能通过iterator

8.ArrayList和LinkedList的区别?

答:ArrayList是基于数组实现的,适合随机查找,LinkedList是基于链表实现的,适合插入和删除,同时LinkedList除了和ArrayList一样实现List接口,LinkedList还实现了Deque接口,可以用作栈、队列和双向队列

9.谈谈ConcurrentHashMap的扩容机制

答:

10.jdk1.7到jdk1.8 HashMap的底层发生了什么变化?

答:jdk1.7HashMap采用链表加数组实现,而jdk1.8采用链表加数组加红黑树实现,加入红黑树的目的是为了提高HashMap整体的查询和插入效率;
jdk1.7插入使用的是头插法,1.8使用的是尾插法,因为1.8在插入的时候要判断链表元素的个数,所以使用尾插法方便一些;
jdk1.7中的hash算法比较复杂,使用了大量的右移与异或运算,复杂的hash算法能提高散列型,来提供HahsMap的整体效率,而jdk1.8因为使用了红黑树,简化了hash算法,节省CPU资源。

11.说一下HashMap的Put方法

答: jdk1.8的put方法如下:

image.png 而putVal方法的流程为: 首先获取当前hashMap的下标,再判断当前下标位置元素是否为空,如果为空,则将key和value封装为Node并且放入该位置

image.png
如果不为空,则先判断这个元素是链表节点还是红黑树节点

image.png 如果是红黑树节点就把这个node插入到红黑树中,如果是链表节点,就使用尾插法将node插入链表节点,在插入的过程中还要判断是否存在当前key,如果存在就修改对应的value,插入完后还要判断当前链表的长度,如果大于等于8,就将链表转为红黑树。

image.png
插入完后根据链表或红黑树节点个数判断是否需要扩容,需要就扩容,不需要就结束put方法

image.png

12.浅拷贝和深拷贝

答:在一个对象中有两种类型,一个是基本数据类型比如int、float等,直接存储在栈中的数据,另外一种则是对象数据类型,在栈中只存储了对该对象的引用,真实的数据存储在堆内存中。
浅拷贝只会拷贝基本数据类型的值和实例对象的引用,而深拷贝不仅会拷贝基本数据类型的值,还会复制生成新的实例对象,深拷贝修改实例对象的内容不会影响被拷贝的对象。
赋值不同于拷贝,赋值是把当前对象的引用复制过去

image.png