Java面试之Java基础篇(offer 拿来吧你)
Posted 研行笔录
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java面试之Java基础篇(offer 拿来吧你)相关的知识,希望对你有一定的参考价值。
现在关于Java面试的资料是层出不穷,对于选择困难症的同学来说,无疑是陷入了一次次的抉择与不安中,担心错过了关键内容,现在小曾哥秉持着"融百家之所长,汇精辟之文档"的思想,整理一下目前主流的一些八股文,以达到1+1 > 2 的效果!
文章目录
- Java特性篇
- Java语法基础篇
- 1、continue、break 和 return 的区别是什么?
- 2、成员变量与局部变量的区别?
- 3、说一说你对Java访问权限的了解?
- 4、重载和重写有什么区别?
- 5、为什么Java只有值传递? 传参机制是什么?
- 6、Java支持的数据类型有哪些?什么是自动拆装箱?
- 7、int和Integer有什么区别,二者在做==运算时会得到什么结果?
- 8、Java中,什么是构造方法?什么是构造方法重载?什么是复制构造方法?
- 9、Java支持多继承么?
- 10、深拷贝和浅拷贝区别了解吗?什么是引用拷贝?
- 11、Object 的常用方法有哪些?
- 12、说一说hashCode()和equals()的关系?
- 13、String、StringBuffer、StringBuilder 的区别?
- 14、说一说你对字符串拼接的理解?
- 15、 接口和抽象类有什么区别?
- 异常篇
- 泛型
- 反射
- Java序列化
- 集合类
- 1、Java中有哪些容器(集合类)?
- 2、ArrayList 与 LinkedList 区别?
- 3、Arraylist 和 Vector 的区别?
- 4、ArrayList的扩容机制
- 5、HashMap 和 HashSet 的区别
- 6、HashMap 和 Hashtable 的区别
- 7、HashMap底层实现原理
- 8、HashMap为什么用红黑树,而不是用B+树、AVL树?
- 9、HashMap实现存储和读取?
- 10、HashMap的扩容机制?
- 11、HashMap 的 size 为什么必须是 2 的整数次方?
- 12、HashTable 和 ConcurrentHashMap 的区别?
- 13、Iterator 怎么使用?有什么特点?
- 14、Iterator 和 Enumeration 接口的区别?
- IO
- 并发编程
Java特性篇
1、Java语言的特点
- 简单易学;
- 面向对象(封装,继承,多态);
- 平台无关性( Java 虚拟机实现平台无关性);
- 支持多线程( C++语言没有内置的多线程机制,因此必须调用操作系统的多线程功能来进行多线程程序设计,而 Java 语言却提供了多线程支持);
- 可靠性;
- 安全性;
- 支持网络编程并且很方便
- 编译与解释并存;
2、 解释下什么是面向对象?面向对象和面向过程的区别?
面向对象是一种基于面向过程的编程思想,是向现实世界模型的自然延伸,这是一种”万物皆对象”的编程思想。由执行者变为指挥者,在现实生活中的任何物体都可以归为一类事物,而每一个个体都是一类事物的实例。面向对象的编程是以对象为中心,以消息为驱动。
区别:
(1)编程思路不同:面向过程以实现功能的函数开发为主,用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了,而面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
(2)封装性:都具有封装性,但是面向过程是封装的是功能,而面向对象封装的是数据和功能。
(3)面向对象具有继承性和多态性,而面向过程没有继承性和多态性,所以面向对象优势很明显
面向对象是以功能来划分问题,而不是步骤
3、面向对象的三大特性?分别解释下?
- 封装:通常认为封装是把数据和操作数据的⽅法封装起来,对数据的访问只能通过已定义的接⼝。
- 继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为⽗类(超类/基类),得到继承信息的被称为⼦类(派⽣类)。
- 多态:分为编译时多态(⽅法重载)和运⾏时多态(⽅法重写)。要实现多态需要做两件事:⼀是⼦类继承⽗类并重写父类中的⽅法,⼆是⽤⽗类型引⽤⼦类型对象,这样同样的引⽤调⽤同样的⽅法就会根据⼦类对象的不同⽽表现出不同的⾏为。
4、什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”?
Java虚拟机是一个可以执行Java字节码的虚拟机进程。Java源文件被编译成能被Java虚拟机执行的字节码文件。Java被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译。Java虚拟机让这个变为可能,因为它知道底层硬件平台的指令长度和其他特性。
5、JDK和JRE的区别是什么?
- JDK >> JRE >>JVM (>>代表包含)
- JDK顾名思义是java开发工具包,是程序员使用java语言编写java程序所需的开发工具包,是提供给程序员使用的。JDK包含了JRE,同时还包含了编译java源码的编译器javac,还包含了很多java程序调试和分析的工具:jconsole,jvisualvm等工具软件,还包含了java程序编写所需的文档和demo例子程序。
- 如果你需要运行java程序,只需安装JRE就可以了。如果你需要编写java程序,需要安装JDK。
6、为什么Java代码可以实现一次编写、到处运行?
JVM(Java虚拟机)是Java跨平台的关键。
- 在程序运行前,Java源代码(.java)需要经过编译器编译成字节码(.class)。在程序运行时,JVM负责将字节码翻译成特定平台下的机器码并运行,也就是说,只要在不同的平台上安装对应的JVM,就可以运行字节码文件。
- 同一份Java源代码在不同的平台上运行,它不需要做任何的改变,并且只需要编译一次。而编译好的字节码,是通过JVM这个中间的“桥梁”实现跨平台的,JVM是与平台相关的软件,它能将统一的字节码翻译成该平台的机器码。
注意事项
编译的结果是生成字节码、不是机器码,字节码不能直接运行,必须通过JVM翻译成机器码才能运行;
跨平台的是Java程序、而不是JVM,JVM是用C/C++开发的软件,不同平台下需要安装不同版本的JVM。机器码和字节码的区别:
机器码,完全依附硬件而存在~并且不同硬件由于内嵌指令集不同,即使相同的0 1代码 意思也可能是不同的
我们知道JAVA是跨平台的,为什么呢?因为他有一个jvm,不论那种硬件,只要你装有jvm,那么他就认识这个JAVA字节码~~~~至于底层的机器码,咱不用管,有jvm搞定,他会把字节码再翻译成所在机器认识的机器码~~
7、Java 和 C++ 的区别?
Java 和 C++ 都是面向对象的语言,都支持封装、继承和多态,但是,它们还是有挺多不相同的地方:
- Java 不提供指针来直接访问内存,程序内存更加安全
- Java 的类是单继承的,C++ 支持多重继承;虽然 Java的类不可以多继承,但是接口可以多继承。
- Java 有自动内存管理垃圾回收机制(GC),不需要程序员手动释放无用内存。
- C++同时支持方法重载和操作符重载,但是 Java 只支持方法重载(操作符重载增加了复杂性,这与 Java 最初的设计思想不符)。
Java语法基础篇
1、continue、break 和 return 的区别是什么?
在循环结构中,当循环条件不满足或者循环次数达到要求时,循环会正常结束。但是,有时候可能需要在循环的过程中,当发生了某种条件之后 ,提前终止循环,这就需要用到下面几个关键词:
- continue :指跳出当前的这一次循环,继续下一次循环。
- break :指跳出整个循环体,继续执行循环下面的语句。
- return 用于跳出所在方法,结束该方法的运行。return 一般有两种用法:return; :直接使用 return 结束方法执行,用于没有返回值函数的方法;return value;:return 一个特定值,用于有返回值函数的方法。
2、成员变量与局部变量的区别?
- 语法形式 :从语法形式上看,成员变量是属于类的,而局部变量是在代码块或方法中定义的变量或是方法的参数;成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰。
- 存储方式 :从变量在内存中的存储方式来看,如果成员变量是使用 static 修饰的,那么这个成员变量是属于类的,如果没有使用 static 修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存。
- 生存时间 :从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动生成,随着方法的调用结束而消亡。
- 默认值 :从变量是否有默认值来看,成员变量如果没有被赋初始值,则会自动以类型的默认值而赋值(一种情况例外:被 final 修饰的成员变量也必须显式地赋值),而局部变量则不会自动赋值。
3、说一说你对Java访问权限的了解?
java 提供四种访问控制修饰符号,用于控制方法和属性(成员变量)的访问权限(范围):
- 公开级别:用public 修饰,对外公开
- 受保护级别:用protected 修饰,对子类和同一个包中的类公开
- 默认级别:default没有修饰符号,向同一个包的类公开.
- 私有级别:用private 修饰,只有类本身可以访问,不对外公开.
4、重载和重写有什么区别?
重载就是同样的一个方法能够根据输入数据的不同,做出不同的处理
重写就是当子类继承自父类的相同方法,输入数据一样,但要做出有别于父类的响应时,你就要覆盖父类方法
重载:发生在同一个类中(或者父类和子类之间),方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同。
重写:就是子类对父类方法的重新改造,外部样子不能改变,内部逻辑可以改变。
“两同两小一大”:
“两同”即方法名相同、形参列表相同;
“两小”指的是子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等;
“一大”指的是子类方法的访问权限应比父类方法的访问权限更大或相等。
5、为什么Java只有值传递? 传参机制是什么?
形参和实参
实参(实际参数) :用于传递给函数/方法的参数,必须有确定的值。【say(hello)】
形参(形式参数) :用于定义函数/方法,接收实参,不需要有确定的值。[say(String str)]
基本类型和引用类型
特点:
1、基本的数据类型,传递的是值(值拷贝),形参的任何改变不影响实参
2、引用数据类型(数组):引用类型传递的是地址(传递也是值,但是值是地址),可以通过形参影响实参!
基本数据类型
public static void main(String[] args)
int a = 10;
int b = 20;
//创建AA 对象名字obj
AA obj = new AA();
obj.swap(a, b); //调用swap
System.out.println("main 方法a=" + a + " b=" + b);//a=10 b=20
class AA
public void swap(int a,int b)
System.out.println("\\na 和b 交换前的值\\na=" + a + "\\tb=" + b);//a=10 b=20
//完成了a 和b 的交换
int tmp = a;
a = b;
b = tmp;
System.out.println("\\na 和b 交换后的值\\na=" + a + "\\tb=" + b);//a=20 b=10
输出结果:
a 和b 交换前的值 a=10 b=20
a 和b 交换后的值 a=20 b=10
main 方法 a=10 b=20
也充分证明了在基本数据类型中,形参的任何改变不会影响实参值
引用数据类型
public class bb
public static void main(String[] args)
//测试数组
B b = new B();
int[] arr = 1, 2, 3;
b.test100(arr);//调用方法
System.out.println(" main 的arr 数组");
//遍历数组
for (int i = 0; i < arr.length; i++)
System.out.print(arr[i] + "\\t");
System.out.println();
// 测试对象
Person p = new Person();
p.name = "jack";
p.age = 10;
b.test200(p);
System.out.println("main 的p.age=" + p.age);
class B
// 数组
public void test100(int[] arr)
arr[0] = 200;//修改元素
//遍历数组
System.out.println(" test100 的arr 数组");
for (int i = 0; i < arr.length; i++)
System.out.print(arr[i] + "\\t");
System.out.println();
// 对象
public void test200(Person p)
p.age = 1000; //修改对象属性
// 特例1
//p = null;
// 特例2
//p = new Person();
//p.name = "tom";
//p.age = 99;
输出结果:
test100的arr数组:[200,2,3]
main的arr数组:[200,2,3]
main的p.age=1000
分别从引用类型(数组和对象)的角度来举例,可以发现在引用数据类型中传递的是地址,可以通过形参影响实参!
如果大家对上述例子有所了解,下面再添加几个特例
特例1
p = null;
特例2
p = new Person();
p.name = "tom";
p.age = 99;
System.out.println("main 的p.age=" + p.age);
如果test200 执行的是p = null ,下面的结果是10
如果test200 执行的是p = new Person();…, 下面输出的是10
为什么会这样呢?
p = null 代表指向p的地址已经断开,不影响p对象的值
p = new Person() 代表指向了一个新的地址,无论怎么传递值,都不会影响p对象的值。
具体可以查看Java基础补充–查漏补缺(二)
6、Java支持的数据类型有哪些?什么是自动拆装箱?
Java语言支持的8种基本数据类型是: byte short int long float double boolean char
自动装箱:可以把一个基本类型的数据直接赋值给对应的包装类型;
自动拆箱:可以把一个包装类型的对象直接赋值给对应的基本类型;
以Integer对象为例子:
Integer.parseInt(“”);是将字符串类型转换为int的基础数据类型
Integer.valueOf(“”)是将字符串类型数据转换为Integer对象
Integer.intValue();是将Integer对象中的数据取出,返回一个基础数据类型int
基本类型和包装类型的区别?
- 成员变量包装类型不赋值就是 null ,而基本类型有默认值且不是 null。
- 包装类型可用于泛型,而基本类型不可以。
- 基本数据类型的局部变量存放在 Java 虚拟机栈中的局部变量表中,基本数据类型的成员变量(未被 static 修饰 )存放在 Java虚拟机的堆中。包装类型属于对象类型,我们知道几乎所有对象实例都存在于堆中。
- 相比于对象类型, 基本数据类型占用的空间非常小。
7、int和Integer有什么区别,二者在做==运算时会得到什么结果?
int是基本数据类型,Integer是int的包装类。二者在做==运算时,Integer会自动拆箱为int类型,然后再进行比较。届时,如果两个int值相等则返回true,否则就返回false。
8、Java中,什么是构造方法?什么是构造方法重载?什么是复制构造方法?
当新对象被创建的时候,构造方法会被调用。每一个类都有构造方法。在程序员没有给类提供构造方法的情况下,Java编译器会为这个类创建一个默认的构造方法。
构造方法特点如下:
- 名字与类名相同。
- 没有返回值,但不能用 void 声明构造函数。
- 生成类的对象时自动执行,无需调用。
构造方法不能重写。因为构造方法需要和类保持同名,而重写的要求是子类方法要和父类方法保持同名;Java中构造方法重载和方法重载很相似。可以为一个类创建多个构造方法。每一个构造方法必须有它自己唯一的参数列表。
9、Java支持多继承么?
Java中类不支持多继承,只支持单继承(即一个类只有一个父类)。 但是java中的接口支持多继承,即一个子接口可以有多个父接口。(接口的作用是用来扩展对象的功能,一个子接口继承多个父接口,说明子接口扩展了多个功能,当类实现接口时,类就扩展了相应的功能)。
10、深拷贝和浅拷贝区别了解吗?什么是引用拷贝?
- 浅拷贝:浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点),不过,如果原对象内部的属性是引用类型的话,浅拷贝会直接复制内部对象的引用地址,也就是说拷贝对象和原对象共用同一个内部对象。
- 深拷贝 :深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。
11、Object 的常用方法有哪些?
clone方法:用于创建并返回当前对象的一份拷贝;
getClass方法:用于返回当前运行时对象的Class;
toString方法:返回对象的字符串表示形式; .
finalize方法:实例被垃圾回收器回收时触发的方法;
equals方法:用于比较两个对象的内存地址是否相等,一般需要重写;
hashCode方法:用于返回对象的哈希值;
notify方法:唤醒一个在此对象监视器上等待的线程。如果有多个线程在等待只会唤醒一一个。
notifyAll方法:作用跟notify() 一样,只不过会唤醒在此对象监视器上等待的所有线程,而不是一个线程。
wait方法:让当前对象等待;
12、说一说hashCode()和equals()的关系?
hashCode()用于获取哈希码(散列码),eauqls()用于比较两个对象是否相等,它们应遵守如下规定:
- 如果两个对象相等,则它们必须有相同的哈希码。
- 如果两个对象有相同的哈希码,则它们未必相等。
为什么要重写hashCode()和equals()?
Object类提供的equals()方法默认是用==来进行比较的,也就是说只有两个对象是同一个对象时,才能返回相等的结果。而实际的业务中,我们通常的需求是,若两个不同的对象它们的内容是相同的,就认为它们相等。鉴于这种情况,Object类中equals()方法的默认实现是没有实用价值的,所以通常都要重写。
类没有重写 equals()方法 :通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是 Object类equals()方法。
类重写了 equals()方法 :一般我们都重写 equals()方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。
==和equals()有什么区别?
对于基本数据类型来说,== 比较的是值。
对于引用数据类型来说,== 比较的是对象的内存地址。
13、String、StringBuffer、StringBuilder 的区别?
主要从可变性、线程安全性、性能三方面进行考虑:
- 可变性:String 是不可变的(使用final关键字修饰字符数组来保存字符串),StringBuilder和StringBuffer非常类似,均代表可变的字符序列,而且方法也一样,都是继承 AbstractStringBuilder。
- 线程安全性 :String 中的对象是不可变的,也就可以理解为常量,线程安全;StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
- 性能:每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象;StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用;相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
效率: StringBuilder > StringBuffer > String
String、StringBuffer 和StringBuilder 的选择
- 1.如果字符串存在大量的修改操作,一般使用StringBuffer或StringBuilder
- 2.如果字符串存在大量的修改操作,并在单线程的情况,使用StringBuilder
- 3.如果字符串存在大量的修改操作,并在多线程的情况,使用StringBuffer
- 4.如果我们字符串很少修改,被多个对象引用,使用String,比如配置信息等
操作少量的数据: 适用 String
单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder
多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer
14、说一说你对字符串拼接的理解?
- +运算符:如果拼接的都是字符串直接量,则适合使用 + 运算符实现拼接;
- StringBuilder:如果拼接的字符串中包含变量,并不要求线程安全,则适合使用StringBuilder;
- StringBuffer:如果拼接的字符串中包含变量,并且要求线程安全,则适合使用StringBuffer;
- String类的concat方法:如果只是对两个字符串进行拼接,并且包含变量,则适合使用concat方法;
15、 接口和抽象类有什么区别?
语法区别(构造方法、静态方法、普通成员变量、非抽象的普通方法、访问类型,继承)
1.抽象类可以有构造方法,接口中不能有构造方法。
2.抽象类中可以有普通成员变量,接口中没有普通成员变量
3.抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。
4.抽象类中的抽象方法的访问类型可以是public,protected、但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。
5.抽象类中可以包含静态方法,接口中不能包含静态方法;抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。
7.一个类可以实现多个接口,但只能继承一个抽象类。
应用区别(系统架构、代码的重用):
接口更多的是在系统架构设计方法发挥作用,主要用于定义模块之间的通信契约。而抽象类在代码实现方面发挥作用,可以实现代码的重用。
异常篇
Java 异常类层次结构图概览 :
1、Exception 和 Error 有什么区别?
在 Java 中,所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类。Throwable 类有两个重要的子类:
- Exception :程序本身可以处理的异常,可以通过 catch 来进行捕获。Exception 又可以分为 Checked Exception (受检查异常,必须处理) 和 Unchecked Exception (不受检查异常,可以不处理)。
- Error :Error 属于程序无法处理的错误 ,我们没办法通过 catch 来进行捕获不建议通过catch捕获 。例如 Java虚拟机运行错误(Virtual MachineError)、虚拟机内存不够错误(OutOfMemoryError)、类定义错误(NoClassDefFoundError)等。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。
2、运行时异常(RuntimeException)包含哪些?
RuntimeException 及其子类都统称为非受检查异常,常见的有(建议记下来,日常开发中会经常用到):
NullPointerException(空指针错误)
IllegalArgumentException(参数错误比如方法入参类型错误)
NumberFormatException(字符串转换为数字格式错误,IllegalArgumentException的子类)
ArrayIndexOutOfBoundsException(数组越界错误)
ClassCastException(类型转换错误)
ArithmeticException(算术错误)
SecurityException (安全错误比如权限不够)
UnsupportedOperationException(不支持的操作错误比如重复创建同一用户)
3、try-catch-finally 如何使用?
- try块 : 用于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟一个 finally 块。
- catch块: 用于处理 try 捕获到的异常。
- finally 块 : 无论是否捕获或处理异常,finally 块里的语句都会被执行。当在 try块或 catch 块中遇到 return 语句时,finally 语句块将在方法返回之前被执行。
try
System.out.println("Try to do something");
throw new RuntimeException("RuntimeException");
catch (Exception e)
System.out.println("Catch Exception -> " + e.getMessage());
finally
System.out.println("Finally");
输出:
Try to do something
Catch Exception -> RuntimeException
Finally
1、不管有木有出现异常,finally块中代码都会执行;
2、当try和catch中有return时,finally仍然会执行;
3、finally是在return语句执行之后,返回之前执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,不管finally中的代码怎么样,返回的值都不会改变,仍然是之前保存的值),所以函数返回值是在finally执行前就已经确定了;
4、finally中如果包含return,那么程序将在这里返回,而不是try或catch中的return返回,返回值就不是try或catch中保存的返回值了。
4、异常使用有哪些需要注意的地方?
- 不要把异常定义为静态变量,因为这样会导致异常栈信息错乱。每次手动抛出异常,我们都需要手动 new 一个异常对象抛出。
- 抛出的异常信息一定要有意义。
- 建议抛出更加具体的异常比如字符串转换为数字格式错误的时候应该抛出NumberFormatException而不是其父类IllegalArgumentException。
- 使用日志打印异常之后就不要再抛出异常了(两者不要同时存在一段代码逻辑中)。
泛型
Java 泛型(Generics) 是 JDK 5 中引入的一个新特性。使用泛型参数,可以增强代码的可读性以及稳定性。
编译器可以对泛型参数进行检测,并且通过泛型参数可以指定传入的对象类型。比如 ArrayList persons = new ArrayList() 这行代码就指明了该 ArrayList 对象只能传入 Persion 对象,如果传入其他类型的对象就会报错。
ArrayList<E> extends AbstractList<E>
优点:1,类型安全、2,消除强制类型转换、3,潜在的性能收益。
注意:泛型只是提高了数据传输安全性,并没有改变程序运行的性能
1、什么是类型擦除?
类型擦除:泛型信息只存在于代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除。在泛型类被类型擦除的时候,之前泛型类中的类型参数部分如果没有指定上限,如则会被转译成普通的Object类型,如果指定了上限如< T extends String >则类型参数就被替换成类型上限。
List<String> list = new ArrayList<String>()
、两个 String 其实只有第⼀个起作⽤,后⾯⼀个没什么卵⽤,只不过 JDK7 才开始⽀持 Listlist = new ArrayList<> 这种写法。
2、第⼀个 String 就是告诉编译器,List 中存储的是 String 对象,也就是起类型检查的作⽤,之后编译器会擦除泛型占位符,以保证兼容以前的代码。
2、项目中哪里用到了泛型?
- 自定义接口通用返回结果 CommonResult 通过参数 T 可根据具体的返回类型动态指定结果的数据类型
- 定义 Excel 处理类ExcelUtil 用于动态指定 Excel 导出的数据类型
- 构建集合工具类(参考 Collections 中的 sort, binarySearch 方法)。
反射
1、什么是反射?
每个类都有一个Class对象,包含了与类有关的信息。当编译一个新类时,会产生一个同名的.class文件,该文件内容保存着Class对象。类加载相当于Class对象的加载,类在第一次使用时才动态加载到JVM中。也可以使用Class.forName(“com.mysql.jdbc.Driver”)这种方式来控制类的加载,该方法会返回一个Class对象。
具体来说,通过反射机制,我们可以实现如下的操作:
- 程序运行时,可以通过反射获得任意一个类的Class对象,并通过这个对象查看这个类的信息;
- 程序运行时,可以通过反射创建任意一个类的实例,并访问该实例的成员;
- 程序运行时,可以通过反射机制生成一个类的动态代理类或动态代理对象。
2、反射机制的优缺点?
- 优点:运行期类型的判断,class.forName() 动态加载类,提⾼代码的灵活度;
- 缺点:尽管反射⾮常强⼤,但也不能滥⽤;1、性能开销 :反射涉及了动态类型的解析,所以 JVM ⽆法对这些代码进⾏优化。2、安全限制 :使⽤反射技术要求程序必须在⼀个没有安全限制的环境中运⾏。如果⼀个程序必须在有安全限制的环境中运⾏,如 Applet,那么这就是个问题了;3、内部暴露(可能导致代码功能失调并破坏可移植性);
3、在实际项目中有哪些应用场景?
- 使用JDBC时,如果要创建数据库的连接,则需要先通过反射机制加载数据库的驱动程序;
- 多数框架都支持注解/XML配置,从配置中解析出来的类是字符串,需要利用反射机制实例化;
- 面向切面编程(AOP)的实现方案,是在程序运行时创建目标对象的代理类,这必须由反射机制来实现。
Java序列化
如果我们需要持久化 Java 对象比如将 Java 对象保存在文件中,或者在网络传输 Java 对象,这些场景都需要用到序列化。
- 序列化: 将数据结构或对象转换成二进制字节流的过程
- 反序列化:将在序列化过程中所生成的二进制字节流的过程转换成数据结构或者对象的过程
序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。
1、实际开发中有哪些用到序列化和反序列化的场景?
- 1、对象在进行网络传输(比如远程方法调用 RPC 的时候)之前需要先被序列化,接收到序列化的对象之后需要再进行反序列化;
- 2、将对象存储到文件中的时候需要进行序列化,将对象从文件中读取出来需要进行反序列化。
- 3、将对象存储到缓存数据库(如 Redis)时需要用到序列化,将对象从缓存数据库中读取出来需要反序列化。
2、序列化协议对应于 TCP/IP 4 层模型的哪一层?
我们知道网络通信的双方必须要采用和遵守相同的协议。TCP/IP 四层模型是下面这样的,序列化协议属于哪一层呢?
- 应用层 、传输层 、网络层 、网络接口层
表示层做的事情主要就是对应用层的用户数据进行处理转换为二进制流。反过来的话,就是将二进制流转换成应用层的用户数据。这不就对应的是序列化和反序列化么?
因为,OSI 七层协议模型中的应用层、表示层和会话层对应的都是 TCP/IP 四层模型中的应用层,所以序列化协议属于 TCP/IP 协议应用层的一部分。
集合类
1、Java中有哪些容器(集合类)?
2、ArrayList 与 LinkedList 区别?
- 是否保证线程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;
- 底层数据结构: ArrayList 底层使用的是 Object 数组;LinkedList 底层使用的是 双向链表 数据结构
- 插入和删除是否受元素位置的影响:ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。 LinkedList 采用链表存储,所以,如果是在头尾插入或者删除元素不受元素位置的影响
- 是否支持快速随机访问: LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。
- 内存空间占用: ArrayList 的空 间浪费主要体现在在 list 列表的结尾会预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间
3、Arraylist 和 Vector 的区别?
- ArrayList 是 List 的主要实现类,底层使用 Object[ ]存储,适用于频繁的查找工作,线程不安全 ;
- Vector 是 List 的古老实现类,底层使用 Object[ ]存储,线程安全的。
4、ArrayList的扩容机制
使用ArrayList()创建ArrayList对象时,不会定义底层数组的长度,当第一次调用add(E e) 方法时,初始化定义底层数组的长度为10,之后调用add(E e)时,如果需要扩容,则调用grow(int minCapacity) 进行扩容,长度为原来的1.5倍。
5、HashMap 和 HashSet 的区别
HashSet 底层就是基于 HashMap 实现的
6、HashMap 和 Hashtable 的区别
- 线程是否安全: HashMap 是非线程安全的,Hashtable 是线程安全的,因为 Hashtable 内部的方法基本都经过synchronized 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap );
- 效率: 因为线程安全的问题,HashMap 要比 Hashtable 效率高一点;
- 对 Null key 和 Null value 的支持:HashMap 可以存储 null 的 key 和 value,但 null 作为键只能有一个,null 作为值可以有多个;Hashtable 不允许有 null 键和 null 值,否则会抛出 NullPointerException。
- 初始容量大小和每次扩充容量大小的不同 :1、Hashtable 默认的初始大小为 11,之后每次扩充,容量变为原来的 2n+1;HashMap 默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍;2、创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为 2 的幂次方大小(HashMap 中的tableSizeFor()方法保证,下面给出了源代码)。
- 底层数据结构:HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树),以减少搜索时间(后文中我会结合源码对这一过程进行分析)。Hashtable 没有这样的机制。
7、HashMap底层实现原理
HashMap底层就是一个数组结构,数组中的每一项又是一个链表。当新建一个HashMap的时候,就会初始化一个数组。
JDK1.7之前:数组 + 链表
现在JDK1.8之后:数组+链表+红黑树进行数据的存储,
以上是关于Java面试之Java基础篇(offer 拿来吧你)的主要内容,如果未能解决你的问题,请参考以下文章
Java面试之JavaWeb常用框架(offer 拿来吧你)