Java面试题知识点

Java面试题

陆陆续续收集到的一些Java面试题知识点,后续加更

基础

1什么是逃逸分析

指JVM可以分析新创建对象的使用范围,以此来决定是否在Java堆上分配内存的技术

逃逸分析使用的算法引入了连通图,来构建对象和对象引用之间的可达性关系,以此为基础提出一种组合数据流分析法。

这种算法是上下文相关和流敏感的,分析精度相对较高,但是时间、内存开销相对较大。

逃逸的几种状态

  1. 全局逃逸

    即一个对象的作用范围逃出了当前方法或当前范围,有以下几种:

    • 对象是静态变量
    • 对象作为当前方法的返回值
    • 对象是一个已经逃逸的对象
  2. 参数逃逸

    即一个对象被作为方法参数传递或被参数引用,但不会发生全局逃逸,这个状态通过被调用方法的字节码确定

  3. 没有逃逸

逃逸分析优化

  1. 锁清除

    线程同步锁是很牺牲性能的。锁清除就是如果编译器知道当前对象只有当前线程使用,那么就会移除该对象的同步锁。

    比如StringBuffer和Vector都是用synchronized修饰线程安全的,但是大部分时候只在当前线程中使用,故编译器可以优化它

  2. 标量替换

    标量和聚合量:

    基础类型和对象引用可以说是标量,不能被进一步分解;能进一步分解的量就是聚合量,比如对象。

    对象可以被进一步分解为标量,分散的成员变量,这就是标量替换

    如果一个对象没有逃逸,那么不用创建它,只需要创建它的成员标量并存在栈中或者寄存器,节省内存空间也提升性能。

  3. 栈上分配

    当对象没有逃逸时,该对象可以通过分解为成员标量分配在栈内存,和方法的生命周期一致,随着栈帧出栈销毁,从而减少GC的压力,提高性能。

逃逸分析即是为了优化JVM内存和提升性能的。故开发当中要尽可能控制变量的作用域,作用域越小越好,

2 ==和equals()的区别

==:如果比较基本数据类型,比较数值是否相等

​ 如果比较引用类型,比较对象的地址是否相等

equals():默认比较对象地址是否相等,不能用于比较基本数据类型

为什么用equals()比较String的内容是否相等

对于String、Date、Integer灯类型重写了equals()方法,使其比较对象存储的内容是否相等

3 &和&&的区别?

& 可以作位运算符,进行位与运算;还可以进行逻辑与运算,作逻辑与时为长路与,即是说就算左边的表达式为假,右边的表达式也会运算

&& 是逻辑与,是短路与,若左边表达式为假,则右边表达式不运算

4 怎么理解值传递和引用传递?

值传递:形参传递的是基本数据类型的字面量值的拷贝,方法对形参的修改不影响实参的值

引用传递:形参传递的是该参数引用的对象在堆中地址值的拷贝,对形参的修改直接作用在实参

5 static可以修饰局部变量么?

不能,可以是内部类、全局变量、方法、代码块

6 私有方法可以重载或者重写吗?

可以重载,不能重写

7 String可变吗?

不可变,String是final类型的,其值value是char[],而且是private final的,故不可修改

8 transient关键字的作用

被transient修饰的变量不能被序列化

transient只作用于实现了Serializable接口的类

transient只能用来修饰普通成员变量字段

不管有无transient修饰,静态变量都不能被序列化

9 Class.forName和ClassLoader的区别

Class.forName除了将类的.class文件加载到JVM之外,还可以对类进行初始化

ClassLoader只会将.class文件加载到JVM中,不会进行初始化

10 main方法可以重载或者重写吗?

可以重载,但是JVM始终调用原始的main方法,不会调用重载的main

不能重写。因为main方法是static的,在Java中不能被覆盖

11 throw 和throws的区别

throw是真实抛出异常,并且throw是动作,代表抛出了异常

throws是声明可能抛出异常,表示一种抛出异常的可能性

12 int和Integer的区别

Integer是int的包装类,int则是Java的基本数据类型

Integer必须实例化在能使用,int不需要

Integer是对象引用,new一个Integer时,实际上生成一个指针指向此对象,而int直接存储数据值

Integer默认值是null,int默认是0

13 switch case语句

case里必须跟break,否则会一个一个case执行下去,直到最后一个break的case或default出现

case条件里只能是常量或者字面常量

default可有可无,最多有一个

switch支持类型:

基本数据类型:byte short int char

包装类型:上面四个的

枚举类型: Enum

字符串类型 String

14 不能用➕拼接字符串的时候:

通过多个表达式完成一个字符串拼接时不行

一次性拼接一个字符串时就可以用➕

15 Java金额计算怎么避免精度丢失?

金额运算尽量使用BigDecimal(String val)进行计算

数据库存储金额,一般是整形和浮点型两种,如果有汇率转换,建议用decimal进行存储,可以灵活控制精度,decimal直接对应Java 类型BigDecimal。

16 怎么理解Java的类型提升?

所谓类型提升,就是在含有多种数据类型的表达式中,类型会自动向范围表示大的数据类型提升。比如:

long count=100000000;
int price=1999;
long totalPrice=price*count;   //运算结果为long型,没有溢出

17 String有没有长度限制?

有,65534个字节,超过的话编译报错

18 Java语法糖是什么意思?

也称糖衣语法,指在计算机语言中添加的某种语法,对语言本身功能没有影响,只是为了便于程序员开发,提高效率。就是对现有语法的封装。

Java语法糖主要有:

泛型与类型擦除

自动装箱与拆箱

变长参数

增强for循环

内部类

枚举类

19 transient关键字的作用

  1. transient修饰的变量不能被序列化
  2. transient只作用于实现Serializable接口
  3. transient只能用来修饰普通成员变量字段
  4. 不管有无transient修饰,静态变量不能被序列化

20 如何实现对象克隆?

可以通过实现Cloneable接口,然后重写其clone()方法

21 Java8 添加的新特性

  • Lambda表达式
  • 函数式接口
  • 接口默认方法和静态方法
  • Optional类
  • 重复注解
  • BASE64编码解码已经加入jdk8
  • JVM内存取消永久代

22 String、StringBuffer、StringBuilder有什么区别?

String、StringBuffer、StringBuilder最大的不同是String不可变,后者可变。StringBuffer是线程安全的,StringBuilder线程不安全速度较快。

23 String与byte[]两者相互之间如何转换?

String > byte[] 通过String类的getBytes方法;byte[] > String通过new String(byte[])构造器。

24 普通类和抽象类有什么区别?

  • 普通类不能包含抽象方法,抽象类可以
  • 抽象类不能直接实例化,普通类可以直接实例化

25 throw和throws的区别

1位置不同:throws用在函数上,后面跟的是异常类,可以跟多个

​ throw用在函数内,后跟异常对象

2作用不同:

throws用来声明异常,让调用者知道可能发生的异常,预先做好处理准备

throw抛出具体的异常对象,执行到throw语句,那么功能也就结束了,跳转到调用者,并把具体异常抛给调用者

(throw语句独立存在的话后面就不应该跟别的语句了,因为根本不会执行)

throws表示可能发生某种异常,但是实际上并不一定会发生,而throw语句如果被执行了,就一定是抛出异常给调用者。

两者都是消极的异常处理方式,不会由出现异常的函数去处理异常,而是让调用者处理异常。区别就是一个告诉你可能抛出异常,另一个抛出异常

26 Java反射机制的概念

指在运行状态中,对于任意一个类都可以知道这个类的所有属性和方法,并且对于任意一个对象,都能调用它的任意一个方法,这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制

27 获取类对象的三种方法

1 调用某个对象的getClass()方法

Person person = new Person();
Class clazz = p.getClass();

2 调用某个类的class属性来获取其类对象

Class clazz = Person.class;

3 使用CLass类中的forName() 静态方法(最安全,性能最好)

Class clazz = CLass.forName("类的全路径")(最常用) 

获得了类对象后,可以通过Class类的方法获取并查看类中的方法和属性

//获取Person类的Class对象
Class clazz=Class.forName("reflection.Person");
//获取Person类的所有方法信息
Method[] method=clazz.getDeclaredMethods();
for(Method m:method){
    System.out.println(m.toString());
}
//获取Person类的所有成员属性信息
Field[] field=clazz.getDeclaredFields();
for(Field f:field){
    System.out.println(f.toString());
}
//获取Person类的所有构造方法信息
Constructor[] constructor=clazz.getDeclaredConstructors();
for(Constructor c:constructor){
    System.out.println(c.toString());
}

28

集合

集合使用泛型有什么优点?

  1. Java1.5引入泛型,所有集合接口和实现都大量使用它
  2. 泛型允许我们为集合提供一个可以容纳的对象类型,如果添加别的类型,编译会报错
  3. 避免在运行时出现ClassCastException
  4. 泛型使得代码整洁,不需要使用显示转换和instanceOf操作符
  5. 对运行时有好处,因为不会产生类型检查的字节码指令

常见集合有哪些

  1. Collection接口的子接口包括:Set接口和List接口
  2. Map接口的实现类:HashMap、TreeMap、Hashtable、ConcurrentHashMap等
  3. Set接口的实现了有:HashSet、TreeSet、LinkedHashSet等
  4. List接口的实现类有:ArrayList、LinkedList、Stack、Vector等

常用的线程安全的Map

Java中平时用的最多的Map集合就是HashMap了,它是线程不安全的。

举例两个场景:

1、当用在方法内的局部变量时,局部变量属于当前线程级别的变量,其他线程访问不了,所以这时也不存在线程安全不安全的问题了。

2、当用在单例对象成员变量的时候呢?这时候多个线程过来访问的就是同一个HashMap了,对同个HashMap操作这时候就存在线程安全的问题了。

img

线程安全的Map

为了避免出现场景2的线程安全的问题,不能使用HashMap作为成员变量,要寻求使用线程安全的Map,下面来总结下有哪些线程安全的Map呢?

1、HashTable

private Map<String, Object> map = new Hashtable<>();

HashTable的源码

img

img

HashTable的get/put方法都被synchronized关键字修饰,说明它们是方法级别阻塞的,它们占用共享资源锁,所以导致同时只能一个线程操作get或者put,而且get/put操作不能同时执行,所以这种同步的集合效率非常低,一般不建议使用这个集合。

2、SynchronizedMap

private Map<String, Object> map = Collections.synchronizedMap(new HashMap<String, Object>());

这种是直接使用工具类里面的方法创建SynchronizedMap,把传入进行的HashMap对象进行了包装同步而已,来看看它的源码。

img

这个同步方式实现也比较简单,看出SynchronizedMap的实现方式是加了个对象锁,每次对HashMap的操作都要先获取这个mutex的对象锁才能进入,所以性能也不会比HashTable好到哪里去,也不建议使用。

3、ConcurrentHashMap - 推荐

private Map<String, Object> map = new ConcurrentHashMap<>();

这个也是最推荐使用的线程安全的Map,也是实现方式最复杂的一个集合,每个版本的实现方式也不一样,在jdk8之前是使用分段加锁的一个方式,分成16个桶,每次只加锁其中一个桶,而在jdk8又加入了红黑树和CAS算法来实现。

img

虽然实现起来很复杂,但使用起来也是非常简单的,在java面试中问的频率也非常高,最重要的是性能要比上面两种同步方式要快太多,推荐使用。

HashMap的数据结构?

JDK1.7:数组➕链表

JDK1.8:数组➕链表➕红黑树(链表长度>8则转换为红黑树)

HashMap如何扩容?

先了解一些HashMap中的变量:

  • Node<K,V>:链表节点,包含了key、value、hash、next指针四个元素
  • table:Node<K,V>类型的数组,里面的元素是链表,用于存放HashMap元素的实体
  • size:记录了放入HashMap的元素个数
  • loadFactor:负载因子
  • threshold:阈值,决定了HashMap何时扩容,以及扩容后的大小,一般等于table大小乘以loadFactor
  1. table数组大小有capacity参数确定,默认16,也可以构造时传入,最大限制是1<<30
  2. 装载因子 loadFactor用来确认table数组是否需要动态扩展,默认0.75,比如table大小为16时,threshold就是12,如果table实际大小超过12,就动态扩容
  3. 扩容调用resize()方法,将table长度变为两倍
  4. 数据很大时,扩展会带来性能损失,如果性能要求很高,那么这种损失影响很大

ArrayList(数组实现,线程不安全)

ArrayList最常用的List实现类,内部采用数组实现,允许随机访问集合元素。每个元素之间不允许有间隔,当数组大小不足时需要增加存储能力,将原数组复制到新空间。插入、删除元素时需要移动其他数组元素,代价较高。适合随机查找和遍历,不适合插入删除

Vector(数组实现、线程同步,即线程安全)

Vector相比于ArrayList来说支持线程同步,某一时刻只能有一个线程能写Vector,避免多线程同时写引起的不一致性,但实现同步需要较高代价,因此Vector相对ArrayList操作慢

LinkedList(链表实现)

适合动态插入删除数据,但随机访问、插入删除的速度较慢,提供了List没有定义的方法,专门用来操作表头表尾元素,可以当做堆栈、队列和双向队列使用

Set

Set强调独一无二,即集合中的元素不允许重复,无序存储(存入和取出的顺序未必相同),值不能重复。

对象的相等性本质是对象hashCode值(java是依据对象的内存地址计算出的此序号)判断的,如果想要让两个不同的对象视为相等的,就必须覆盖Object的hashCode方法和equals方法。

HashSet(哈希表)

哈希表存放哈希值,按照哈希值的顺序来存取对应的数据。元素的哈希值是通过元素的hashcode方法来获取的, HashSet首先判断两个元素的哈希值,如果哈希值一样,接着会比较equals方法如果equls结果为true,HashSet就视为同一个元素。如果equals为false就不是同一个元素。哈希值相同equals为false的元素就是在同样的哈希值下顺延(可以认为哈希值相同的元素放在一个哈希桶中)。也就是哈希值一样的存一列,组成一个链表的意思,可以了解一下数据结构课程上学到的链地址法

TreeSet(二叉树)

1.TreeSet使用二叉树对新add()的元素按照指定顺序排序,每增加一个对象都会进行排序,将对象插入的二叉树指定的位置。

2.Integer和String对象都可以进行默认的TreeSet排序,而自定义类的对象是不可以的,自己定义的类必须实现Comparable接口,并且覆写相应的compareTo()函数,才可以正常使用。

3.在覆写compare()函数时,要返回相应的值才能使TreeSet按照一定的规则来排序

4.比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。

JVM相关

基本概念

JVM是可以运行Java代码的假想计算机,包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收,堆和一个存储方法域。JVM运行在操作系统之上,与硬件没有直接的交互。每种操作系统都对应于自己特定的JVM。

一般讲的“Java文件可以一次编译,到处运行”就是因为:同一份Java源文件通过编译器,能够生产相应的.Class文件,也就是字节码文件,字节码文件是统一格式的,不管拿到哪一种操作系统上的Java虚拟机,字节码文件通过Java虚拟机中的解释器,编译成特定机器上的机器码,就可以在特定机器上运行了。每一种平台的解释器是不同的,但是实现的虚拟机是相同的,这就是Java为什么能够跨平台的原因。

进程与线程

没有引入线程以前,进程是程序的一次动态执行过程,是操作系统资源分配与调度的基本单位。

引入线程后,进程仍然是资源分配的基本单位,但是线程才是处理机调度的基本单位。

进程之间的地址空间互相隔绝,不能越界访问;而线程由进程产生,同一个进程的各个线程共享该进程的内存地址空间资源。

Java线程

Hotspot JVM 中的Java 线程与原生操作系统线程有直接的映射关系。当线程本地存储、缓冲区分配、同步对象、栈、程序计数器等准备好以后,就会创建一个操作系统原生线程(计算机考研说的内核线程)。Java 线程结束,原生线程随之被回收。操作系统负责调度所有线程,并把它们分配到任何可用的CPU 上。当原生线程初始化完毕,就会调用Java 线程的run() 方法。run() 方法运行完毕,线程就结束了。当线程结束时,会释放原生线程和Java 线程的所有资源。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cxSPtmFB-1614667873005)(Java面试题.assets/image-20210302144123188.png)]

JVM内存

JVM 内存区域主要分为线程私有区域【程序计数器、虚拟机栈、本地方法区】线程共享区域【JAVA堆、方法区】、直接内存。

线程私有数据区域生命周期与线程相同, 依赖用户线程的启动/结束而创建/销毁(在Hotspot VM内, 每个线程都与操作系统的本地线程直接映射, 因此这部分内存区域的生存情况跟随本地线程的生存情况对应)。

线程共享区域随虚拟机的启动/关闭而创建/销毁。

直接内存并不是JVM运行时数据区的一部分,但也会被频繁的使用: 在JDK 1.4引入的NIO提供了基于Channel与Buffer的IO方式, 它可以使用Native函数库直接分配堆外内存, 然后使用DirectByteBuffer对象作为这块内存的引用进行操作(详见:Java I/O 扩展), 这样就避免了在Java堆和Native堆中来回复制数据, 因此在一些场景中可以显著提高性能

WEB

http和https区别?

  1. http是简单的无状态的协议,https是http➕SSL组合的,可以进行加密传输、身份验证的更为安全的协议
  2. 端口号不同,http为80,https为443

forward和redirect区别?

  1. forward是转发,redirect是重定向
  2. 使用forward,浏览器地址栏的url不会变,而使用rediret则会变化
  3. forward可以共享request里的数据,而redirect则不行
  4. forward比redirect效率更高

Servlet是什么?

是用Java编写的服务器端程序,主要功能是交互式得浏览、修改数据,生成动态web内容

狭义的servlet是java的一个借口,广义的是实现了servlet家口的类,一般理解是后者

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值