Java面试题
1 面向对象的理解
- 要与面向过程对比
- 面向过程:注重事情的每一个步骤以及顺序(直接高效)
- 面向对象:注重事情有哪些参与者(易于复用、拓展和维护)
- 面向对象的三大特性
- 封住:提供给外部调用的属性和方法,内部细节无需外部知道以及修改
- 继承:继承基类的方法,并作出自己的改变或拓展
- 多态:父类引向子类对象
父类 变量名 = new 子类()
,创建的类虽然是父类,但调用的方法的逻辑,是子类的逻辑
2 JDK JRE JVM
- JDK:Java Develpment Kit(Java开发工具)
- JRE:Java Runtime Enviroment(Java运行时环境)
- JVM:Java Virtual Machine(Java虚拟机)
3 ==和equals
- 一般笔试中用于判断true / false
==
比较的是栈中的值,比较的是堆中内存对象的地址- 如果创建一个对象,用
==
比较,比较的是地址,而不是值
- 如果创建一个对象,用
- equals一般与
==
是同一比较效果- 但我们一般会对equals进行方法重写,用来比较类中的值,而不是地址
4 final
- (1)final作用
- 修饰类:表示类不可被继承
- 修饰方法:表示方法不可被子类覆盖(重写),但可以重载
- 修饰变量:表示变量一旦被赋值就不可以更改它的值
- 修饰成员变量
1
2
3public class Finalvar {
final int b = 0; //在声明的时候就需要赋值;或者在代码块中赋值;或者构造器赋值
}
- 修饰局部变量
1
2
3
4
5public class Finalvar {
public static void main(String[] args) {
final int a; //局部变量可以在声明的时候不赋值,但要在使用前进行赋值
}
}
- (2)局部内部类和匿名内部类
- 局部内部类和匿名内部类方法中的参数要添加
final
修饰,否者访问不到 - 原因:
- 内部类与外部类是同一级别,不会因为内部方法执行完后,会进行销毁
- 实际执行流程,将局部变量赋值一份作为内部类的成员变量,当方法结束,局变量变量销毁,内部类仍然可以访问局部变量,即copy的变量
- 复制变量的过程汇总,必须保证两个变量是一样的,所以添加一个final来修饰局部变量
- 局部内部类和匿名内部类方法中的参数要添加
5 String StringBuffer StringBuilder
- String是final修饰的,不可变,每次操作都会产生新的String对象
- StringBuffer和StringBuilder都是在原对象上操作
- StringBuffer是线程安全的,StringBuilder线程不安全
- StringBuffer方法都是synchronized修饰
- 性能:StringBuilder > StringBuffer > String
- 场景:经常需要改变字符串内容时使用后面两个,优先使用StringBuilder,多线程使用共享变量时使用StringBuffer
6 重载和重写
- (太过容易简单回答,注意要详细)
- 重载:发生在同一个类中,方法名相同,参数类型,个数,顺序不同,方法返回值和访问修饰符可以不同
- 重写:发生在父子类中,方法名、参数列表必须相同,返回值的范围小于等于父类,抛出的异常小于等于父类,访问修饰符访问大于等于父类(如果父类为private,则子类不能重写该方法)
7 接口和抽象类的区别
- 抽象类可以存在普通成员函数(可以有实现方法),而接口中只能存在public、abstract方法
- 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型
- 抽象类只能继承一个,接口可以实现多个
8 List和Set的区别
- List:有序,按对象进入的顺序保存对象;可重复,允许多个Null元素对象,可以使用迭代器(Iterator)取出所有元素,逐一遍历,还以使用get(int index)获取指定下标的元素
- Set:无序,不可重复,最多允许一个Null元素对象,取元素时只能用迭代器取出所有元素,再逐一遍历各个元素
9 hashCode与equals
- 如果两个对象相等,则hashCode一定也相等
- 两个对象相等 ,对两个对象分别调用equals方法都会返回true
- 两个对象有相同的hashCode值,它们也不一定是相等,因此equals你发呗覆盖过,则hashCode方法也必须被覆盖
- hashCode的默认行为是对对上的对象产生独特值,如果没有重写hashCode()方法,则该class的两个对象无论润滑都不会相等(即使这两个对象指向相同的数据)
10 ArrayList和LinkedList区别
- ArrayList:基于动态数组,连续内存存储,适合下标访问
- 如果ArrayList使用尾插法,就不会涉及元素的移动,就会极大提高性能,甚至会超过LinkedList
- LinkedList:基于链表,分散在内存中,适合做数据插入以及删除操作,不适合做查询
- 遍历LinkedList必须使用迭代器进行访问(for效率低),比较麻烦。
- 另外不要用IndexOf来返回元素索引,当结果为空时,会遍历整个列表
11 HashMap和HashTable区别
区别
- (1)HashMap方法没有synchronized修饰,线程非安全,HashTable线程安全
- (2)HashMap允许key和value为null,而HashTable不允许
底层实现:(数组和链表实现)
- JDK8开始链表高度到8,数组长度超过64,链表转为红黑树,元素以内部类Node节点存在
- 实现方法类似HashSet,利用hashcode计算数组位置,判断该位置上是否有值?没,直接插入;有和链表上的数据比较hashCode,判断是否相同?不相同,链表上插入新数据;相同,使用equals比较,判断是否相同?相同,取消插入;不相同,将原来值修改为新插入的值
12 ConcurrentHashMap
13 如何实现一个IOC容器
- (1)配置文件配置包扫描路径
- (2)递归包扫描获取.class文件
- (3)反射、确定需要交给IOC管理的类
- (4)对需要注入的类进行依赖注入
- 配置文件中指定需要扫描的包路径
- 定义一些注解,分别表示访问控制层,业务服务成…
- 从配置文件中获取需要扫描的包路径,获取到当前路径下的文件信息及文件夹信息,我们将当前路径下所有以.class结尾的文件添加到一个set集合中进行存储
- 遍历这个set集合,获取在类上有指定注解的类,并将其交给IOC容器,定义一个安全的Map用来存储这些对象
- 遍历这个IOC容器,获取到每一个类的实例,判断里面是否有依赖其他类的实例,然后进行递归注入
14 什么是字节码?采用字节码的好处是什么?
- Java中的编译器和解释器
- Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟机,这台虚拟的机器在任何平台上都提供给编译程序一个共同的接口(实现同一Java代码,能够在不同平台之间运行)
- 编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统机器码执行。
- Java中,供虚拟机理解代码叫做字节码(.class文件)
- 字节码好处
- 一定程度上解决传统解释型语言执行效率低的问题,同时又保留解释型语言可移植的特点,无需重新边意思便可在多种不同的计算机上运行
15 Java类加载器有哪些?
- JDK自带有三个类加载器:bootstrap ClassLoader、ExClassLoader、AppClassLoader
- bootstrapClassLoader是ExtClassLoader的父类加载器,默认负责加载%JAVA_HOME%lib下的jar包和class文件
- ExtClassLoader是AppClassLoader的父类加载器,负责加载%JAVA_HOME%/lib/ext文件下的jar包和class类
- AppClassLoader是自定义类加载器的父类,负责加载classpath下的类文件。继承ClassLoader实现自定义类加载器
16 双亲委托(派)模型
双亲委派模型的好处:
- 主要是为了安全性,避免了用户自己编写类动态替换Java的一些核心类,比如String
- 同时也避免了类的重复加载,因为JVM中区分不同类,不仅仅是根据类名,相同的class被不同的ClassLoader加载就是不同的两个类
17 Java中的异常体系
- Java中的所有异常都来自顶级父类
Throwable
Throwable
下有两个子类Exception
和Error
Error
是程序无法处理的错误,一旦出现这个错误,即程序将被迫停止运行Exception
不糊导致程序停止,又分为两个部分RunTimeException
运行时异常 和CheckedException
检查异常RunTimeException
常常发生在程序运行过程中,会导致程序当前线程执行失败。CheckedException
常常发生在程序编译过程中,会导致程序编译不通过
18 GC如何判断对象可以被回收(JVM)
19 线程的生命周期,线程有哪些状态
- 线程的五种状态:创建,就绪,运行,阻塞,死亡
- 阻塞的情况分为三种:
- (1)等待阻塞:运行的线程执行了wait()方法
- (2)同步阻塞:运行线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入”锁池”中
- (3)其他阻塞:运行的线程执行了sleep或join方法,或发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep状态超时,join等待线程的终止或者超时。或者I/O处理完毕时,线程重新转入就绪状态。sleep是thread类的方法
- 创建状态(new):新创建一个线程对象
- 就绪状态(Runable):线程对象创建后,其他线程调用了该对象的start方法,该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权
- 运行状态(Running):就绪状态的线程获取了CPU,执行程序代码
- 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态
- 死亡状态(Dead):线程执行完或者因一场退出了run方法,该线程结束生命周期
20 sleep()、wait()、join()、yield()区别
- (1)锁池:
- 所有需要竞争同步锁的线程都会放在锁池当中,比如当前对象的锁已经被其中一个线程得到,则其他线程需要在这个锁池进行等待,当前面的线程释放同步锁后锁池中的线程去竞争同步锁,当某个线程得到后会进入就绪队列进行等待CPU资源分配
- (2)等待池:
- ·当我们调用wait()方法后,线程会放到等待池当中,等待池的线程是不会去竞争同步锁。只有调用notify()或notifyAll()后等待池的线程才会开始去竞争锁,notify()是随机从等待池选出一个线程放到锁池,而notifyAll()是将等待池中的所有线程方法哦锁池当中
- sleep是Thread类的静态本地方法,wait()是Object类的本地方法
- sleep方法不会释放lock,但是wait会释放,并加入等待列表中
- sleep方法不依赖于同步器synchronzied,但是wait()需要依赖synchronzied关键字
- sleep不需要被唤醒(时间到自动唤醒),但是wait()需要notify进行唤醒
- sleep一般用于当前线程休眠,或者轮循暂停操作,wait则多用于多线程之间的通信
- sleep会让出CPU执行时间且强制上下文切换,而wait()则不一定,wait后可能还是有机会重新竞争到锁继续执行的
- yield()执行后线程直接进入就绪状态,马上释放了cpu的执行权,但是依然保留了cpu的执行资格,所以有可能cpu下次进行线程调度还会让出这个线程获得到执行权继续执行
- join() 执行后线程进入阻塞状态,例如在线程B中使用线程A的join(),那线程B会进入到阻塞队列,直到线程A结束或中断线程
21 对线程安全的理解
32 Sring是什么?
- spring是一个轻量级的开源的J2EE框架,它是一个容器框架,用来装JavaBean。中间层可以起一个连接作用,整合其他框架,可以让我们的企业开发更快、更简洁
- Spring是一个轻量级的控制反转(IOC)和面向切面(AOP)的容器框架
- 从大小与开销两方面而言,Spring都是轻量级的
- 通过控制反转(IOC)技术到到松耦合的目的
- 提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务进行内聚性的开发
- 包含并管理应用对象(Bean)的配置和声明周期,这个意义上是一个容器
- 将简单的组件配置,组合成为复杂的应用,这个意义上是一个框架
33 对AOP的理解
- 系统由许多不同的组件组成,每一个组件各负责一块特定功能,除了实现自身核心功能之外,这些组件还经常承担额外的制作,例如日志,事务管理
- 当我们需要为分散的对象映日公共行为的时候,OOP(面向对象)则显得无能为力。OOP允许你定义从上到下的关系,但不适合定义从左到右的关系。例如日志功能。
- AOP(面向切面),将程序中的交叉业务逻辑(安全,日志,事务等),封装成一个切面,然后注入到目标对象(具体业务逻辑)中去。AOP可以对某个对象或某个对象的功能进行增强,比如对象中的方法进行增强,可以在执行某个方法之前额外的做一些事情,在某个方法执行之后外的做一些事情
34 对IOC理解
切入点:容器概念、控制反转、依赖注入
- IOC容器:
- 实际上市一个Map(key, value),里面存储各种对象(被注解标识的类,xml中配置的Bean)。在项目启动时,通过全限定类名使用反射创建对讲放入map里
- map中拥有各种对象,通过
@autowired
/@resource
等注解,或xml中bean节点内的ref属性
- 控制反转:
- (1)没有引入IOC容器之前,对象A依赖对象B,在对象A初始化,运行到某一点时,需要自己去创建对象B或使用已经创建的对象B,控制权都在自己的手上
- (2)引入IOC之后,对象A与对象B失去了直接联系,当对象A运行到需要对象B时候,IOC会主动创建一个对象B注入到对象A需要的地方
- 对象A获取对象B的过程是有主动变成被动应为,控制权颠倒,称为
控制反转
- 依赖注入:
- 获取依赖对象的过程有自身管理变为了有IOC容器主动注入,依赖注入是实现IOC的方法,在IOC容器运行时间,动态地将某种依赖关系注入到对象之中
35 BeanFactory和ApplicationContext区别
- ApplicationContext是BeanFactory的子接口
- ApplicationContext提供了更完整的功能
- (1)继承了MessageSource,因此支持国际化
- (2)同一的资源文件访问方式
- (3)提供在监听器中注册bean的事件
- (4)同时加载多个配置文件
- (5)载入多个(有继承关系)上下文,使得每一个上下文都有专注于一个特定的层次
- BeanFactory采用延迟加载形式来注入Bean,使用使用某个Bean时(getBean()),才对该Bean进行加载实例化
- ApplicationContext,它是在容器启动时,一次性创建所有Bean。
- 两者相比,ApplicationContext在启动时,就能发现哪个Bean配置出现问题。而BeanFactory只有使用到相应的Bean时才知道是否出现问题。
- 两者相比,ApplicationContext相比于BeanFactory比较占用内存
- BeanFactory通常以编程的方式被创建,Application还能以声明的方式被创建,如使用ContextLoader
- BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册
36 Spring Bean的生命周期
- 扫描路径,解析类获得BeanDefinition
- 如果有多个构造方法,则要推断构造方法
- 确定好构造方法后,进行实例化得到一个对象
- 对对象中的加了@autowired注解属性进行属性填充
- 回调Aware方法,比如BeanNameAware、BeanFactoryAware
- 调用BeanPostProcessor初始化前的方法
- 调用初始化方法
- 调用BeanPostProcessor的初始化后的方法,在这里会进行AOP
- 如果当前创建的Bean是单例的则会把Bean放入单例池
- 使用Bean
- Spring容器关闭时调用DisposableBean中的destory()方法
37 Spring支持的Bean的作用域
- singleton:默认,每一个容器中只有一个bean的实例,单例的模式由BeanFactory自身来维护,该对象的生命周期是与Spring IOC容器一致的(但在第一次被注入时才会创建一个新的对象)
- prototype:为每一个Bean请求提供一个实例。在每次注入时都会创建一个新的对象
- request:Bean被定义为在每个HTTP请求中创建一个单例对象,也就是说在单个请求中都会复用这一个单例对象
- session:与request范围类似,确保每个session中有个bean的实例,在session过期后,bean会随之失效
- application:Bean被定义为ServeltContext的生命周期中复用一个单例对象
- websocker:Bean被定义在websocker的生命周期中复用一个单例对象
38 Spring框架中的单例Bean是线程安全的吗
- 单例Bean:IOC容器中只有这么一个Bean,无论多少个线程去注入Bean,都是同一个Bean,不会额外new一个新的Bean
- 该单例Bean并不是线程安全的,因为框架并没有对Bean进行多线程的封装处理
- 如果Bean是有状态的(Bean里面存储数据),会出现线程安全问题,需要更改Bean的作用于,将默认的singleton更改为prototype,每次依赖注入时都会创建一个新的Bean
- 如果Bean是无状态的,只是调用Bean里面的接口,就不会发生线程安全问题
- 例子:DAO会操作数据库的connection,connection是带有状态的,比如数据库事务,spring事务管理器使用Threadlocal为不同线程维护了一套独立的connection副本,保证线程之间不会互相影响
39 Spring 框架使用到的设计模式
(1)简单工厂:有一个工厂类根据传入的参数,动态决定应该创建哪一个产品类
1
spring中的`BeanFactory`就是简单工厂模式的体现,根据传入一个唯一的标识来获取Bean对象,但是否是在传入参数后创建,还是传入参数前创建就根据具体的情况来决定
(2)工厂方法:
1
实现了`FactoryBean`接口的bean是一类叫做factory的bean,其特点是,spring会在使用getBean()调用火哥该bean时,会自动调用该bean的getObject()方法,所以返回的不是factory这个bean,而是这个bean.getObject()方法的返回值
- (3)单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点
1
spring对单例的实现,spring中的单例模式完成了后半句话,即提供了全局的访问点BeanFactory,但没有从构造器级别去控制单例,这是因为spring管理的是任意的java对象
- (4)适配器模式:
1
Spring定义一个适配接口,使得每一种Controller有一种对应的适配器实现类,让适配器代替Controller执行相应的方法,这样在拓展Controller时,只需要加一个适配器类就完成了SpringMVC的扩展了
- (5)装饰器模式:动态给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活切面在应用运行的时刻被织入,在织入切面时,AOP容器会为目标对象动态创建一个代理对象,SpringAOP就是以这种方式织入切面
1
2
3
4
5Spring中用到的包装器模式在类名上有两种表现:一种是类名中含有wrapper,另一种是类名中含有Desorator
```
+ (6)动态代理:
织入:把切面应用到目标对象并创建新的代理对象的过程