Java面试题

2021/6/30 12:20:35

本文主要是介绍Java面试题,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

Java 基础12

ArrayList 和 LinkedList的区别?

ArrayList 是容量可变列表,使用数组实现,扩容时会创建更大的数组,把原有数组复制到新数组。支持对元素的随机访问,但插入与删除速度慢。ArrayList 实现了 RandomAcess 接口,如果类实现了该接口,使用索引遍历比迭代器更快。

LinkedList 本质是双向链表,与 ArrayList 相比增删速度更快,但随机访问慢。除继承 AbstractList 外还实现了 Deque 接口,该接口具有队列和栈的性质。成员变量被 transient 修饰,原理和 ArrayList 类似。优点是可以将零散的内存单元通过附加引用的方式关联起来,形成按链路顺序查找的线性结构,内存利用率高。

HashMap 和 HashTable 的区别?

① HashMap 继承自 AbstractMap,HashTable 继承自 Dictionary。

② HashMap 中键值都可以为 null,HashTable 的键值都不允许为 null。

③ HashMap 线程不安全,HashTable 通过 synchronized 保证了线程安全。

重载和重写的区别?

重载指方法名称相同,但参数列表不同,是行为水平方向不同实现。对编译器来说,方法名称和参数列表组成了一个唯一键,称为方法签名,JVM 通过方法签名决定调用哪种重载方法。不管继承关系多复杂,重载在编译时可以确定调用哪个方法,因此属于静态绑定。重载顺序:① 精确匹配。② 基本数据类型自动转换成更大表示范围。③ 自动拆箱与装箱。④ 子类向上转型。⑤ 可变参数。

重写指子类实现接口或继承父类时,保持方法签名完全相同,实现不同方法体,是行为垂直方向不同实现。元空间有一个方法表保存方法信息,如果子类重写父类的方法,方法表中的方法引用会指向子类。重写方法访问权限不能变小,返回类型和抛出的异常类型不能变大。

泛型和泛型擦除?

泛型本质是参数化类型,解决不确定对象具体类型的问题。

泛型的好处:① 类型安全,不存在 ClassCastException。② 提升可读性,编码阶段就显式知道泛型集合、泛型方法等处理的数据类型。

泛型用于编译阶段,编译后的字节码文件不包含泛型类型信息,因为虚拟机没有泛型类型对象,所有对象都属于普通类。例如定义 List 或 List,在编译后都会变成 List 。

反射

在运行状态中,对于任意一个类都能知道它的所有属性和方法,对于任意一个对象都能调用它的任意方法和属性,这种动态获取信息及调用对象方法的功能称为反射,缺点是破坏了封装性及泛型约束。

== 和 equals的区别?

== :既可以用于比较基本数据类型,又可以在对象之间进行比较。

equals: 只能用于对象之间的比较,默认使用 == 比较,也可以重写自定义比较规则。

equals 和 hashCode 的关系?

每个对象都有默认散列码,值由对象存储地址得出。字符串散列码由内容导出,值可能相同。为了在集合中正确使用,一般需要同时重写 equals 和 hashCode,要求 equals 相同 hashCode 必须相同,hashCode 相同 equals 未必相同。

Object 有哪些方法?

方法说明
equals检测对象是否相等,默认使用 == 比较,可以重写该方法自定义规则。
hashCode每个对象都有默认散列码,值由对象存储地址得出。字符串散列码由内容导出,值可能相同。
toString默认打印表示对象值的一个字符串。
clone默认声明为 protected,只能由本类对象调用,且是浅拷贝。一般重写 clone 方法需要实现 Cloneable 接口并声明为 public,如果没有实现 Cloneable 接口会抛出 CloneNotSupport 异常。
finalizeGC 判断垃圾时,如果对象没有与 GC Roots 相连会被第一次标记,之后判断对象是否有必要执行 finalize 方法,有必要则由一条低调度优先级的 Finalizer 线程执行。虚拟机会触发该方法但不保证结束,防止方法执行缓慢或发生死循环。只要对象在 finalize 方法中重新与引用链相连,就会在第二次标记时移出回收集合。由于运行代价高且具有不确定性,在 JDK9 标记为过时方法。
getClass返回对象所属类的 Class 对象。
wait阻塞持有该对象锁的线程。
notify唤醒持有该对象锁的线程,notify 随机唤醒一个线程,notifyAll 唤醒全部线程。

Java 三大特性?

封装:是对象功能内聚的表现形式,在抽象基础上决定信息是否公开及公开等级。主要任务是对属性、数据、敏感行为实现隐藏,使对象关系变得简单,降低耦合。

继承:用来扩展类,子类可继承父类的部分属性和行为,使模块具有复用性。

多态:以封装和继承为基础,根据运行时对象实际类型使同一行为具有不同表现形式。多态指在编译层面无法确定最终调用的方法体,在运行期由 JVM 动态绑定,调用合适的重写方法。由于重载属于静态绑定,本质上重载结果是完全不同的方法,因此多态一般专指重写。

序列化是什么?
Java 对象在 JVM 退出时会全部销毁,如果需要将对象持久化就要通过序列化实现,将内存中的对象保存在二进制流中,需要时再将二进制流反序列化为对象。对象序列化保存的是对象的状态,属于类属性的静态变量不会被序列化。

常见的序列化有三种:① Java 原生序列化,实现 Serializabale 标记接口,兼容性最好,但不支持跨语言,性能一般。序列化和反序列化必须保持序列化 ID 的一致,一般使用 private static final long serialVersionUID 定义序列化 ID,如果不设置编译器会根据类的内部实现自动生成该值。② Hessian 序列化,支持动态类型、跨语言。③ JSON 序列化,将数据对象转换为 JSON 字符串,抛弃了类型信息,反序列化时只有提供类型信息才能准确进行。相比前两种方式可读性更好。

序列化通常使用网络传输对象,容易遭受攻击,因此不需要进行序列化的敏感属性应加上 transient 关键字,把变量生命周期仅限于内存,不会写到磁盘。

Java 八大基本数据类型?

数据类型 内存大小
byte 1 B
short 2 B
int 4 B
long 8 B
float 4 B
double 8 B
char 英文 1B,中文 UTF-8 占 3B,GBK 占 2B。
boolean 单个变量 4B / 数组 1B

List、Set 和 Map 的区别?

① List 和 Set 实现了 Collection 接口,List 的元素有序可重复、Set 的元素无序不可重复,Map 是以键值对存储元素的。

② List 的实现包括 ArrayList(数组实现)、LinkedList(链表实现)、Vector(线程安全的 ArrayList) 和 Stack(继承 Vector,有栈的语义)。

③ Set 的实现包括 HashSet(通过 HashMap 实现,元素就是 HashMap 的 Key,Value 是一个 Object 类型的常量)、LinkedHashSet(通过 LinkedHashMap 实现)和 TreeSet(可以对元素排序,通过实现 Compare 接口或 Comparator 接口)。

④ Map 的实现主要包括 HashMap、LinkedHashMap(通过 LinkedList 维护插入顺序) 和 TreeMap(可以按 Key 排序,通过实现 Compare 接口或 Comparator 接口)。

JVM5
StackOverflow是怎么发生的?
如果线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError,例如一个递归方法不断调用自己。该异常有明确错误堆栈可供分析,容易定位问题。

GC 算法

标记清除:分为标记和清除阶段,首先从每个 GC Roots 出发依次标记有引用关系的对象,最后清除没有标记的对象。如果堆包含大量对象且大部分需要回收,必须进行大量标记清除,效率低。

存在内存空间碎片化问题,分配大对象时容易触发 Full GC。

标记复制:为解决内存碎片,将可用内存按容量划分为大小相等的两块,每次只使用其中一块,主要用于新生代。对象存活率高时要进行较多复制操作,效率低。如果不想浪费空间就需要有额外空间分配担保,老年代一般不使用此算法。

标记整理:老年代使用标记整理算法,标记过程与标记清除算法一样,但不直接清理可回收对象,而是让所有存活对象都向内存空间一端移动,然后清理掉边界以外的内存。

垃圾回收器?
Serial:最基础的收集器,使用复制算法、单线程工作,进行垃圾收集时必须暂停其他线程。Serial 是客户端模式的默认新生代收集器,对于处理器核心较少的环境,由于没有线程开销,可获得最高的单线程收集效率。

ParNew:Serial 的多线程版本,ParNew 是虚拟机在服务端模式的默认新生代收集器。

Parallel Scavenge:基于复制算法、多线程工作的新生代收集器,目标是高吞吐量。

Serial Old:Serial 的老年代版本,使用整理算法,是客户端模式的默认老年代收集器。

Parellel Old:Parallel Scavenge 的老年代版本,支持多线程,基于整理算法。

CMS:以获取最短回收停顿时间为目标,基于清除算法,过程分为四个步骤:初始标记、并发标记、重新标记、并发清除。缺点:① 对处理器资源敏感,并发阶段虽然不会导致用户线程暂停,但会降低吞吐量。② 无法处理浮动垃圾,有可能出现并发失败而导致 Full GC。③ 基于清除算法,会产生空间碎片。

G1:开创了面向局部收集的设计思路和基于 Region 的内存布局,主要面向服务端,最初设计目标是替换 CMS。可面向堆任何部分来组成回收集进行回收,衡量标准不再是分代,而是哪块内存中垃圾的价值最大。价值即回收所获空间大小以及回收所需时间的经验值,G1 在后台维护一个优先级列表,每次根据用户设定允许的收集停顿时间优先处理回收价值最大的 Region。运作过程:初始标记、并发标记、最终标记、筛选回收。

ZGC:JDK11 中加入的具有实验性质的低延迟垃圾收集器,目标是尽可能在不影响吞吐量的前提下,实现在任意堆内存大小都可以把停顿时间限制在 10ms 以内的低延迟。基于 Region 内存布局,不设分代,使用了读屏障、染色指针和内存多重映射等技术实现可并发的标记整理。ZGC 的 Region 具有动态性,是动态创建和销毁的,并且容量大小也是动态变化的。

类加载机制了解吗?

JVM 把描述类的数据从 Class 文件加载到内存,并对数据进行验证、解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型。

加载:通过一个类的全限定类名获取对应的二进制流,在内存中生成对应的 Class 实例,作为方法区中这个类的访问入口。

验证:确保 Class 文件符合约束,防止因载入有错字节流而遭受攻击。包含:文件格式验证、元数据验证、字节码验证、符号引用验证。

准备:为类静态变量分配内存并设置零值,该阶段进行的内存分配仅包括类变量,不包括实例变量。

解析:将常量池内的符号引用替换为直接引用。

初始化:直到该阶段 JVM 才开始执行类中编写的代码,根据程序员的编码去初始化类变量和其他资源。

双亲加载机制?⭐
双亲委派模型要求除了顶层的启动类加载器外,其余类加载器都应该有自己的父加载器。

一个类加载器收到了类加载请求,不会自己去尝试加载,而将该请求委派给父加载器,每层的类加载器都是如此,因此所有加载请求最终都应该传送到启动类加载器,只有当父加载器反馈无法完成请求时,子加载器才会尝试。

类跟随它的加载器一起具备了有优先级的层次关系,确保某个类在各个类加载器环境中都是同一个,保证程序的稳定性。

并发4

final 关键字?

final 类不能被继承,所有成员方法都会被隐式地指定为 final 方法,final 方法不能被重写。

final 变量表示常量,只能被赋值一次,赋值后值不再改变。

修饰基本数据类型时,该值在初始化后不能改变。
修饰引用类型时,引用指向的对象在初始化后不能改变,但该对象的内容可以发生变化。
内存语义

编译器会在 final 域的写后,构造方法的 return 前插入一个 Store Store 屏障,确保对象引用为任意线程可见前其 final 域已初始化。
编译器在读 final 域操作的前面插入一个 Load Load 屏障,确保在读一个对象的 final 域前一定会先读包含这个 final 域的对象引用。

Java 怎么实现线程?

① 继承 Thread 类并重写 run 方法。实现简单,但不符合里氏替换原则,不可以继承其他类。

② 实现 Runnable 接口并重写 run 方法。避免了单继承局限性,实现解耦。

③实现 Callable 接口并重写 call 方法。可以获取线程执行结果的返回值,并且可以抛出异常。

Java 线程通信的方式?

Java 采用共享内存模型,线程间的通信总是隐式进行,整个通信过程对程序员完全透明。

volatile 告知程序任何对变量的读需要从主内存中获取,写必须同步刷新回主内存,保证所有线程对变量访问的可见性。

synchronized 确保多个线程在同一时刻只能有一个处于方法或同步块中,保证线程对变量访问的原子性、可见性和有序性。

等待通知机制指一个线程 A 调用了对象的 wait 方法进入等待状态,另一线程 B 调用了对象的 notify/notifyAll 方法,线程 A 收到通知后结束阻塞并执行后序操作。对象上的 wait 和 notify/notifyAll 完成等待方和通知方的交互。

如果一个线程执行了某个线程的 join 方法,这个线程就会阻塞等待执行了 join 方法的线程终止,这里涉及等待/通知机制。join 底层通过 wait 实现,线程终止时会调用自身的 notifyAll 方法,通知所有等待在该线程对象上的线程。

管道 IO 流用于线程间数据传输,媒介为内存。PipedOutputStream 和 PipedWriter 是输出流,相当于生产者,PipedInputStream 和 PipedReader 是输入流,相当于消费者。管道流使用一个默认大小为 1KB 的循环缓冲数组。输入流从缓冲数组读数据,输出流往缓冲数组中写数据。当数组已满时,输出流所在线程阻塞;当数组首次为空时,输入流所在线程阻塞。

ThreadLocal 是线程共享变量,但它可以为每个线程创建单独的副本,副本值是线程私有的,互相之间不影响。
框架6

IoC 的原理?

IoC 控制反转,把对象创建、依赖反转给容器实现,需要创建一个容器和一种描述让容器知道对象间的依赖关系,Spring 通过 IoC 容器管理对象及其依赖关系。IoC 的主要实现方式是 DI,对象不是从容器中查找依赖的类,而是容器实例化对象时主动为它注入依赖的类。

AOP 的原理?

AOP 面向切面编程,将代码中重复的部分抽取出来,使用动态代理技术,在不修改源码的基础上对方法进行增强。

如果目标对象实现了接口,默认采用 JDK 动态代理,也可以强制使用 CGLib;如果目标对象没有实现接口,采用 CGLib 的方式。

常用场景包括权限认证、自动缓存、错误处理、日志、调试和事务等。

相关注解

@Aspect:声明被注解的类是一个切面 Bean。

@Before:前置通知,指在某个连接点之前执行的通知。

@After:后置通知,指某个连接点退出时执行的通知(不论正常返回还是异常退出)。

@AfterReturning:返回后通知,指某连接点正常完成之后执行的通知,返回值使用 returning 属性接收。

@AfterThrowing:异常通知,指方法异常退出时执行的通知,和 @AfterReturning 只会有一个执行,异常使用 throwing 属性接收。

相关术语

Aspect:切面,一个关注点的模块化,这个关注点可能会横切多个对象。

Joinpoint:连接点,程序执行过程中的某一行为,即业务层中的所有方法。

Advice:通知,指切面对于某个连接点所产生的动作,包括前置通知、后置通知、返回后通知、异常通知和环绕通知。

Pointcut:切入点,指被拦截的连接点,切入点一定是连接点,但连接点不一定是切入点。

Proxy:代理,Spring AOP 中有 JDK 动态代理和 CGLib 代理,目标对象实现了接口时采用 JDK 动态代理,反之采用 CGLib 代理。

Target:代理的目标对象,指一个或多个切面所通知的对象。

Weaving :织入,指把增强应用到目标对象来创建代理对象的过程。

Spring 中有哪些设计模式?

简单工厂模式:Spring 中的 BeanFactory,根据传入一个唯一的标识来获得 Bean 实例。

工厂方法模式:Spring 的 FactoryBean 接口的 getObject 方法。

单例模式:Spring 的 ApplicationContext 创建的 Bean 实例都是单例对象。

代理模式:Spring 的 AOP。

适配器模式:Spring MVC 中的 HandlerAdapter,由于 handler 有很多种形式,包括 Controller、HttpRequestHandler、Servlet 等,但调用方式又是确定的,因此需要适配器来进行处理,根据适配规则调用 handle 方法。

创建 Bean 的方式?

XML:

默认无参构造方法,只需指明 bean 标签中的 id 和 class 属性。

静态工厂方法,通过 bean 标签的 class 属性指明工厂,factory-method 属性指明方法。

实例工厂方法,通过 bean 标签的 factory-bean 属性指明工厂,factory-method 属性指明方法。

注解:

@Component 把当前类对象存入 Spring 容器,相当于在 xml 中配置一个 bean 标签。

@Controller,@Service,@Repository 都是 @Component 的衍生注解,作用及属性都一模一样,只是提供了更明确的语义,@Controller 用于表现层,@Service用于业务层,@Repository用于持久层。

如果想注入第三方类又没有源码,就没法使用 @Component,需要用 @Bean。被 @Bean 注解的方法返回值是一个对象,这个对象由 Spring 的 IoC 容器管理,name 属性用于给对象指定一个名称。

MVC 常用注解?

@Controller:是 @Component 的衍生注解,作用及属性都一模一样,只是提供了更明确的语义,用于表现层,

@RequtestMapping:将 URL 请求和方法映射起来,在类和方法定义上都可以添加。value 属性指定 URL 请求的地址。method 属性限制请求类型,如果没有使用指定方法请求 URL,会报 405 错误。params 属性限制必须提供的参数。

@RequestParam:如果 Controller 方法的形参和 URL 参数名不一致可以使用该注解绑定。value 属性表示 HTTP 请求中的参数名,required 属性设置参数是否必要,默认false。defaultValue 属性指定没有给参数赋值时的默认值。

@PathVariable:Spring MVC 支持 RESTful 风格 URL,通过 @PathVariable 完成参数绑定。

Spring Cloud中有哪些组件?

1.服务治理 Eureka:服务治理由三部分组成:服务提供者、服务消费者、注册中心。Spring Cloud 的服务治理使用 Eureka 实现,Eureka 是 Netflix 开源的基于 REST 的服务治理解决方案,Spring Cloud 集成了 Eureka,提供服务注册和服务发现的功能,可以和基于 Spring Boot 搭建的微服务应用轻松完成整合。

服务网关 Zuul:Zuul 是 Netflix 提供的一个开源的 API 网关服务器,是客户端和网站后端所有请求的中间层,对外开放一个 API,将所有请求导入统一的入口,屏蔽了服务端的具体实现逻辑,可以实现方向代理功能,在网关内部实现动态路由、身份认证、数据监控等。

负载均衡 Ribbon:Ribbon 是 Netflix 发布的均衡负载器,Spring Cloud 集成了 Ribbon,提供用于对 HTTP 请求进行控制的负载均衡客户端。在注册中心对 Ribbon 进行注册之后,Ribbon 就可以基于某种负载均衡算法(轮循、随机、加权轮询、加权随机等)自动帮助服务消费者调用接口,开发者也可以根据具体需求自定义 Ribbon 负载均衡算法。

声明式接口调用 Feign:Feign 是 Netflix 提供的,一个声明式、模板化的 Web Service 客户端,简化了开发者编写 Web 客户端的操作,开发者可以通过简单的接口和注解来调用 HTTP API。相比于 Ribbon + RestTemplate 的方式,Feign 可以大大简化代码开发,支持多种注解,包括 Feign 注解、Spring MVC 注解等。

服务配置 Config:Spring Cloud Config 通过服务端可以为多个客户端提供配置服务,既可以将配置文件存储在本地,也可以将配置文件存储在远程的 Git 仓库,创建 Config Server,通过它管理所有的配置文件。

服务跟踪 Zipkin:Spring Cloud Zipkin 是一个可以采集并跟踪分布式系统中请求数据的组件,让开发者更直观地监控到请求在各个微服务耗费的时间,Zipkin 包括两部分 Zipkin Server 和 Zipkin Client。

服务熔断 Hystrix:在不改变各个微服务调用关系的前提下,针对错误情况进行预先处理。

版权声明:本文为CSDN博主「2020GetGoodOffer」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_41112238/article/details/108899842



这篇关于Java面试题的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程