Java 面试题梳理1
Java 的特性
- 便携性,Java 是平台无关性的,这意味着在一个平台上编写的任何应用程序都可以轻松移植到另一个平台上。
- 安全性, 编译后会将所有的代码转换为字节码,人类无法读取。它使开发无病毒,无篡改的系统/应用成为可能。
- 动态性,它具有适应不断变化的环境的能力,它能够支持动态内存分配,从而减少了内存浪费,提高了应用程序的性能。
- 分布式,Java 提供的功能有助于创建分布式应用。使用远程方法调用(RMI),程序可以通过网络调用另一个程序的方法并获取输出。
- 健壮性,Java 有强大的内存管理功能,在编译和运行时检查代码,它有助于消除错误。
- 高性能,Java 最黑的科技就是字节码编程,Java 代码编译成的字节码可以轻松转换为本地机器代码。通过 JIT 即时编译器来实现高性能。
- 解释性,Java 被编译成字节码,由 Java 运行时环境解释。
- 多线程性,Java支持多个执行线程(也称为轻量级进程),包括一组同步原语。这使得使用线程编程更加容易,Java 通过管程模型来实现线程安全性。
值传递和引用传递的区别
- 值传递 是指在调用函数时将实际参数复制一份到函数中,这样的话如果函数对其传递过来的形式参数进行修改,将不会影响到实际参数。
- 引用传递 是指在调用函数时将对象的地址直接传递到函数中,如果在对形式参数进行修改,将影响到实际参数的值。
(值传递不会影响到实际值 引用传递会影响到实际值)
== 和 equals 区别是什么
- == 是 Java 中一种操作符,它有两种比较方式
- 对于基本数据类型来说, == 判断的是两边的值是否相等
public class DoubleCompareAndEquals { Person person1 = new Person(24,"boy"); Person person2 = new Person(24,"girl"); int c = 10; private void doubleCompare(){ int a = 10; int b = 10; System.out.println(a == b); System.out.println(a == c); System.out.println(person1.getId() == person2.getId()); } }
2.对于引用类型来说, == 判断的是两边的引用是否相等,也就是判断两个对象是否指向了同一块内存区域。
private void equals(){ System.out.println(person1.getName().equals(person2.getName())); }
- equals 是 Java 中所有对象的父类,即 Object 类定义的一个方法。
它只能比较对象,它表示的是引用双方的值是否相等。
所以记住,并不是说 == 比较的就是引用是否相等,equals 比较的就是值,这需要区分来说的。
- equals 用作对象之间的比较具有如下特性
- 自反性:对于任何非空引用 x 来说,x.equals(x) 应该返回 true。
- 对称性:对于任何非空引用 x 和 y 来说,若x.equals(y)为 true,则y.equals(x)也为 true。
- 传递性:对于任何非空引用的值来说,有三个值,x、y 和 z,如果x.equals(y) 返回true,y.equals(z) 返回true,那么x.equals(z) 也应该返回true。
- 一致性:对于任何非空引用 x 和 y 来说,如果 x.equals(y) 相等的话,那么它们必须始终相等。
- 非空性:对于任何非空引用的值 x 来说,x.equals(null) 必须返回 false。
String 中的 equals 是如何重写的
- String代表的是java中的字符串,String类比较特殊,被flinal修饰的不能被任何类继承任何 修改 String 字符串的方法都是创建了一个新的字符串。
- equals 方法是 Object 类定义的方法,Object 是所有类的父类,当然也包括 String,String 重写了 equals 方法
- 首先会判断要比较的两个字符串它们的引用是否相等。如果引用相等的话,直接返回 true ,不相等的话继续下面的判断。
- 然后再判断被比较的对象是否是 String 的实例,如果不是的话直接返回 false,如果是的话,再比较两个字符串的长度是否相等,如果长度不想等的话也就没有比较的必要了;长度如果相同,会比较字符串中的每个 字符 是否相等,一旦有一个字符不相等,就会直接返回 false。
- 下面是它的流程图
为什么重写 equals 方法必须重写 hashcode 方法
(equals 方法和 hashCode 都是 Object 中定义的方法)
- equals 方法是用来比较对象大小是否相等的方法,hashcode 方法是用来判断每个对象 hash 值的一种方法。如果只重写 equals 方法而不重写 hashcode 方法,很可能会造成两个不同的对象,它们的 hashcode 也相等,造成冲突。比如
String str1 = "你好"; String str2 = "我好";
它们两个的 hashcode 相等,但是 equals 可不相等。
- 如果两个对象的 equals 相等,那么 hashCode 必须相同。
- 如果两个对象 equals 不相等,那么 hashCode 也有可能相同,所以需要重写 hashCode 方法。
- hashCode 通常是将地址转换为整数来实现的。
String s1 = new String("abc") 在内存中创建了几个对象
一个或者两个
- String1是声明了一个String类型的s1变量,它不是对象。
- 使用new关键字会在堆内存中创建一个对象,另外一个对象是abc,他会在常量池中创建,所以一共创建了两个对象。
- 如果abc在常量池中存在的话,那么就会创建一个对象。
static 关键字是干什么用的?谈谈你的理解
- static表示静态的,在java中static主要用来修饰变量。
- static修饰的变量称为静态变量,也称类变量,类变量属于类所有,对于不同的类来说,static变量只有一份,static修饰的变量位于方法区中。
- static修饰的变量能够直接通过类名.变量名进行访问,不用通过实例化在进行使用。
- static 可以修饰代码块,主要分为两种,一种直接定义在类中,使用 static{},这种被称为静态代码块,一种是在类中定义静态内部类,使用 static class xxx 来进行定义。
- static 可以和单例模式一起使用,通过双重检查锁来实现线程安全的单例模式。
final 关键字是干什么用的?谈谈你的理解
- final 是 Java 中的关键字,它表示的意思是 不可变的。
- 用来修饰类,final 修饰的类不能被继承,不能被继承的意思就是不能使用 extends 来继承被 final 修饰的类。
- 修饰变量,final 修饰的变量不能被改写,不能被改写的意思有两种,对于基本数据类型来说,final 修饰的变量,其值不能被改变,final 修饰的对象,对象的引用不能被改变,但是对象内部的属性可以被修改。final 修饰的变量在某种程度上起到了不可变的效果,所以,可以用来保护只读数据,尤其是在并发编程中,因为明确的不能再为 final 变量进行赋值,有利于减少额外的同步开销。
- 修饰方法,final 修饰的方法不能被重写。
抽象类和接口的区别是什么
抽象类和接口中都允许进行方法的定义,而不用具体的方法实现。抽象类和接口都允许被继承。
不同点在于:
- 抽象级别不同:类、抽象类、接口其实是三种不同的抽象级别,抽象程度依次是 接口 > 抽象类 > 类。在接口中,只允许进行方法的定义,不允许有方法的实现,抽象类中可以进行方法的定义和实现;而类中只允许进行方法的实现,我说的方法的定义是不允许在方法后面出现 {}。
- 使用的关键字不同:类使用 class 来表示;抽象类使用 abstract class 来表示;接口使用 interface 来表示。
- 变量:接口中定义的变量只能是公共的静态常量,抽象类中的变量是普通变量。
重写和重载的区别
- 子父级关系不同,重写是针对子级和父级的不同表现形式,而重载是在同一类中的不同表现形式;
- 概念不同,子类重写父类的方法一般使用 @override 来表示。
- 重写后的方法其方法的声明和参数类型、顺序必须要与父类完全一致。
- 重载是针对同一类中概念,它要求重载的方法必须满足下面任何一个要求:方法参数的顺序,参数的个数,参数的类型任意一个保持不同即可。
byte的取值范围是多少,怎么计算出来的
- byte 的取值范围是 -128 -> 127 之间,一共是 256 。
- 一个 byte 类型在计算机中占据一个字节,那么就是 8 bit,所以最大就是 2^7 = 1111 1111。
int 和 Integer 的区别
- int 是 Java 中的基本数据类型,int 代表的是 整型,一个 int 占 4 字节,也就是 32 位,int 的初始值是默认值是 0 ,int 在 Java 内存模型中被分配在栈中,int 没有方法。
- Integer 是 Java 中的基本数据类型的包装类,Integer 是一个对象,Integer 可以进行方法调用,Integer 的默认值是 null,Integer 在 Java 内存模型中被分配在堆中。int 和 Integer 在计算时可以进行相互转换,int -> Integer 的过程称为 装箱,Integer -> int 的过程称为 拆箱,Integer 还有 IntegerCache ,会自动缓存 -128 - 127 中的值
HashMap 和 HashTable 的区别
相同点:
HashMap 和 HashTable 都是基于哈希表实现的,其内部每个元素都是 key-value 键值对,HashMap 和 HashTable 都实现了 Map、Cloneable、Serializable 接口。
不同点:
- 父类不同:HashMap 继承了 AbstractMap 类,而 HashTable 继承了 Dictionary 类
- 空值不同:HashMap 允许空的 key 和 value 值,HashTable 不允许空的 key 和 value 值。HashMap 会把 Null key 当做普通的 key 对待。不允许 null key 重复。
- 线程安全性:HashMap 不是线程安全的,如果多个外部操作同时修改 HashMap 的数据结构比如 add 或者是 delete,必须进行同步操作,仅仅对 key 或者 value 的修改不是改变数据结构的操作。可以选择构造线程安全的 Map 比如 Collections.synchronizedMap 或者是 ConcurrentHashMap。而 HashTable 本身就是线程安全的容器。
- 性能方面:虽然 HashMap 和 HashTable 都是基于单链表的,但是 HashMap 进行 put 或者 get操作,可以达到常数时间的性能;而 HashTable 的 put 和 get 操作都是加了 synchronized 锁的,所以效率很差。
总结 :安全性:HashMap 不是线程安全的 ,HashTable 本身就是线程安全的容器。
效率:HashMap 》 HashTable 因为HashTable 加了 synchronized 锁效率很差。
- 初始容量不同:HashTable 的初始长度是11,之后每次扩充容量变为之前的 2n+1(n为上一次的长度)而 HashMap 的初始长度为16,之后每次扩充变为原来的两倍。创建时,如果给定了容量初始值,那么HashTable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小。
HashMap 和 HashSet 的区别
- HashSet 继承于 AbstractSet 接口,实现了 Set、Cloneable,、java.io.Serializable 接口。
- HashSet 不允许集合中出现重复的值。
- HashSet 底层其实就是 HashMap,所有对 HashSet 的操作其实就是对 HashMap 的操作。所以 HashSet 也不保证集合的顺序,也不是线程安全的容器。
HashMap 的底层结构
JDK1.7 中,HashMap 采用位桶 + 链表的实现,即使用链表来处理冲突,同一 hash 值的链表都存储在一个数组中。但是当位于一个桶中的元素较多,即 hash 值相等的元素较多时,通过 key 值依次查找的效率较低。
与 JDK 1.7 相比,JDK 1.8 在底层结构方面做了一些改变,当每个桶中元素大于 8 的时候,会转变为红黑树,目的就是优化查询效率。
HashMap 多线程操作导致死循环问题
HashMap 不是一个线程安全的容器,在高并发场景下,应该使用 ConcurrentHashMap
HashMap 线程安全的实现有哪些
- 因为 HashMap 不是一个线程安全的容器,所以并发场景下推荐使用 ConcurrentHashMap ,或者使用线程安全的 HashMap,使用 Collections 包下的线程安全的容器,比如说
Collections.synchronizedMap(new HashMap());
- 还可以使用 HashTable ,它也是线程安全的容器,基于 key-value 存储,经常用 HashMap 和 HashTable 做比较就是因为 HashTable 的数据结构和 HashMap 相同。
- 效率最高的就是 ConcurrentHashMap。
ConcurrentHashMap 底层实现
ConcurrentHashMap 是线程安全的 Map,它也是高并发场景下的首选数据结构,ConcurrentHashMap 底层是使用分段锁来实现的。
Collection 和 Collections 的区别
Collection 和 Collections 都是位于 java.util 包下的类
Collection 是集合类的父类,它是一个顶级接口,大部分抽象类比如说 AbstractList、AbstractSet 都继承了 Collection 类,Collection 类只定义一节标准方法比如说 add、remove、set、equals 等,具体的方法由抽象类或者实现类去实现。
Collections 是集合类的工具类,Collections 提供了一些工具类的基本使用
- sort 方法,对当前集合进行排序, 实现 Comparable 接口的类,只能使用一种排序方案,这种方案叫做自然比较.。
- 比如实现线程安全的容器 Collections.synchronizedList、 Collections.synchronizedMap 等。
- reverse 反转,使用 reverse 方法可以根据元素的自然顺序 对指定列表按降序进行排序。
- fill,使用指定元素替换指定列表中的所有元素。
ArrayList、LinkedList 和 Vector 的区别
ArrayList、LinkedList、Vector 都是位于 java.util 包下的工具类,它们都实现了 List 接口。
- ArrayList 的底层是动态数组,它是基于数组的特性而演变出来的,所以ArrayList 遍历访问非常快,但是增删比较慢,因为会涉及到数组的拷贝。ArrayList 是一个非线程安全的容器,在并发场景下会造成问题,如果想使用线程安全的容器的话,推荐使用 Collections.synchronizedList;ArrayList 在扩容时会增加 50% 的容量。
- LinkedList 的底层是双向链表,所以 LinkedList 的增加和删除非常快,只需把元素删除,把各自的指针指向新的元素即可。但是 LinkedList 遍历比较慢,因为只有每次访问一个元素才能知道下一个元素的值。LinkedList 也是一个非线程安全的容器,推荐使用 Collections.synchronizedList。
- Vector 向量是最早出现的集合容器,Vector 是一个线程安全的容器,它的每个方法都粗暴的加上了 synchronized 锁,所以它的增删、遍历效率都很低。Vector 在扩容时,它的容量会增加一倍。
总结:安全性:ArrayList非安全;LinkedList 非安全;Vector 安全
效率性:ArrayList查询快,增删慢;LinkedList 查询慢,增删快;Vector 增删、遍历都慢效率低
Exception 和 Error 有什么区别
Exception 泛指的是 异常:
Exception 主要分为两种异常:
-
- 一种是编译期出现的异常,称为 checkedException
- 一种是程序运行期间出现的异常,称为 uncheckedException
- 一种是编译期出现的异常,称为 checkedException
常见的 checkedException 有 IOException,uncheckedException 统称为 RuntimeException
常见的 RuntimeException 主要有NullPointerException、 IllegalArgumentException、ArrayIndexOutofBoundException等,Exception 可以被捕获。
Error 是指程序运行过程中出现的错误,通常情况下会造成程序的崩溃,Error 通常是不可恢复的,Error 不能被捕获。
String、StringBuilder 和 StringBuffer 有什么区别
- String 特指的是 Java 中的字符串,String 类位于 java.lang 包下,String 类是由 final 修饰的,String 字符串一旦创建就不能被修改,任何对 String 进行修改的操作都相当于重新创建了一个字符串。String 字符串的底层使用 StringBuilder 来实现的。
- StringBuilder 位于 java.util 包下,StringBuilder 是一非线程安全的容器,StringBuilder 的 append 方法常用于字符串拼接,它的拼接效率要比 String 中 + 号的拼接效率高。StringBuilder 一般不用于并发环境。
- StringBuffer 位于 java.util 包下,StringBuffer 是一个线程安全的容器,多线程场景下一般使用 StringBuffer 用作字符串的拼接。
- StringBuilder 和 StringBuffer 都是继承于AbstractStringBuilder 类,AbstractStringBuilder 类实现了 StringBuffer 和 StringBuilder 的常规操作。
Java 提供了哪些 I/O 方式
Java I/O 方式有很多种,传统的 I/O 也称为 BIO,主要流有如下几种
Java I/O 包的实现比较简单,但是容易出现性能瓶颈,传统的 I/O 是基于同步阻塞的。
JDK 1.4 之后提供了 NIO,也就是位于 java.nio 包下,提供了基于 channel、Selector、Buffer的抽象,可以构建多路复用、同步非阻塞 I/O 程序。
JDK 1.7 之后对 NIO 进行了进一步改进,引入了 异步非阻塞 的方式,也被称为 AIO(Asynchronous IO)。
可以用生活中的例子来说明:项目经理交给手下员工去改一个 bug,那么项目经理不会一直等待员工解决 bug,他肯定在员工解决 bug 的期间给其他手下分配 bug 或者做其他事情,员工解决完 bug 之后再告诉项目经理 bug 解决完了。
谈谈你知道的设计模式
比如全局唯一性可以用 单例模式。
可以使用 策略模式 优化过多的 if...else...
制定标准用 模版模式
接手其他人的锅,但不想改原来的类用 适配器模式
使用 装饰器可以制作加糖、加奶酪的咖啡
Comparator 和 Comparable 有什么不同
- Comparable 更像是自然排序
- Comparator 更像是定制排序
Object 类中一般都有哪些方法
Object 类是所有对象的父类,它里面包含一些所有对象都能够使用的方法
- hashCode():用于计算对象的哈希码
- equals():用于对象之间比较值是否相等
- toString(): 用于把对象转换成为字符串
- clone(): 用于对象之间的拷贝
- wait(): 用于实现对象之间的等待
- notify(): 用于通知对象释放资源
- notifyAll(): 用于通知所有对象释放资源
- finalize(): 用于告知垃圾回收器进行垃圾回收
- getClass(): 用于获得对象类
反射的基本原理,反射创建类实例的三种方式是什么
创建类实例的三种方式是
- 对象实例.getClass();
- 通过 Class.forName() 创建
- 对象实例.newInstance() 方法创建
final、finally 和 finalize() 的区别
这三者可以说是没有任何关联之处
- final 可以用来修饰类、变量和方法。
- finally 是一个关键字,它经常和 try 块一起使用,用于异常处理。使用 try...finally 的代码块种,finally 部分的代码一定会被执行,所以我们经常在 finally 方法中用于资源的关闭操作。
- finalize 是 Object 对象中的一个方法,用于对象的回收方法,这个方法我们一般不推荐使用,finalize 是和垃圾回收关联在一起的,在 Java 9 中,将 finalize 标记为了 deprecated, 如果没有特别原因,不要实现 finalize 方法,也不要指望他来进行垃圾回收。
说出几种常用的异常
- NullPointerException: 空指针异常
- NoSuchMethodException:找不到方法
- IllegalArgumentException:不合法的参数异常
- IndexOutOfBoundException: 数组下标越界异常
- IOException:由于文件未找到、未打开或者I/O操作不能进行而引起异常
- ClassNotFoundException :找不到文件所抛出的异常
- NumberFormatException:字符的UTF代码数据格式有错引起异常;
- InterruptedException:线程中断抛出的异常