java语言有什么特点?
面向对象(封装,继承,多态);
平台无关性(Java虚拟机实现平台无关性),其实现在Docker也可以实现跨平台;
支持多线程;
编译和解释并存;
Java强大生态,开发人员存储及和其他中间件兼容性;
什么是字节码?采用字节码有什么好处?
JVM 可以理解的代码就叫做字节码(即扩展名为 .class
的文件),会去先加载再解释,解释器逐行翻译速度比较慢,在转换成机器码的过程中,存在热点代码可以通过JIT编译器进行运行时编译,并会将第一次编译下来的机器码保存下来,下次直接使用。所以Java是编译和解释并存的语言。
好处是:由于字节码并不针对一种特定的机器,因此,Java 程序无须重新编译便可在多种不同操作系统的计算机上运行。
什么是AOT?为什么不全部使用AOT?
JDK9引入一种新的编译模式AOT(Ahead of Compilation),它是直接将字节码转换成机器码,可以避免JIT预热等各方面开销,节省启动时间。
因为这和Java语言的动态性有关,比如CLIGB动态代理使用的是ASM技术,如果全部使用AOT,则没法支持类似这种特性。
成员变量和局部变量的区别?
形式上:成员变量属于类的,局部变量属于方法块或者方法定义的变量或者方法的参数;
存储方式:如果成员变量被static修饰,那么这个变量属于类的,如若没有则这个变量属于实例的。对象存放在堆中,局部变量存放在栈中;
生存时间:成员变量随着对象的创建而存在,局部变量随着方法调用自动生成,方法调用结束而消亡;
默认值:成员变量如果没有被赋初始值,会自动以类型的默认值赋值;而局部变量则不会;
静态变量有什么用?
静态变量被所有实例所共享,无论创建了多少对象,都共享同一份静态变量;
静态变量+final=常量
静态方法为什么不能调用非静态成员?
静态方法属于类的,在类加载的时候会被分配内存,可以通过类名直接访问,非静态成员属于实例对象,只有对象实例化之后才能调用;
在类的非静态成员不存在时静态方法就已经存在,此时调用属于非法操作;
静态方法只允许访问静态成员变量和静态方法;
什么是值传递?
Java 中将实参传递给方法(或函数)的方式是 值传递 :
如果参数是基本类型的话,很简单,传递的就是基本类型的字面量值的拷贝,会创建副本。
如果参数是引用类型,传递的就是实参所引用的对象在堆中地址值的拷贝,同样也会创建副本。
什么是重载?
重载就是同一个类中多个同名方法根据不同的传参数来执行不同的逻辑处理;
什么是重写?
重写是子类对父类方法的重新改造,外部样子不能改变,内部逻辑可以改变,
子类重写的异常要<=父类,返回值类型<=父类,访问修饰符>=父类
基本类型和包装类型的区别?
基本类型:byte,short,int,long,float,double,char,boolean;
包装类型:Byte,Short,Integer,Long,Float,Double,Character,Boolean;
成员包装类型不赋值是null,包装类型可用于泛型,包装类型属于对象存在堆中,而基本类型存在虚拟机栈中的变量表中,基本类型占用的空间非常小。
包装类型的缓存机制?
Java基本的包装类型会通过缓存机制来提高性能;
如Byte
,Short
,Integer
,Long
这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character
创建了数值在 [0,127] 范围的缓存数据,Boolean
直接返回 True
or False
。
注意:所有整型包装类对象之间值的比较都要使用equals比较。
自动装箱和拆箱的了解?
装箱:将基本类型用它们对应的引用类型包装起来;
拆箱:将包装类型转换为基本数据类型;
注意:如果频繁拆装箱的话,也会严重影响系统的性能。我们应该尽量避免不必要的拆装箱操作。
如何解决浮点数精度丢失问题?
BigDecimal
可以实现对浮点数的运算,不会造成精度丢失。通常情况下,大部分需要浮点数精确运算结果的业务场景(比如涉及到钱的场景)都是通过 BigDecimal
来做的。
超过Long整型的数据应该如何表示?
基本数值类型都有一个表达范围,如果超过这个范围就会有数值溢出的风险。
在 Java 中,64 位 long 整型是最大的整数类型。BigInteger
内部使用 int[]
数组来存储任意大小的整形数据。相较于常规整数类型运算说,BigInteger
效率会低些
面向对象和面向过程的区别?
面向过程把解决问题拆分成一个个方法,通过执行这些方法来解决问题;
面向对象会先将问题进行划分归属及抽象成对象,通过执行对象的方法来解决问题;使用面向对象可以易于复用,扩展,及维护。
对象实体和对象引用的区别?(了解)
对象实例在堆内存中,对象引用指向对象实例(对象引用存放在栈内存中)
一个对象引用可以指向 0 个或 1 个对象(一根绳子可以不系气球,也可以系一个气球);一个对象可以有 n 个引用指向它(可以用 n 条绳子系住一个气球).
对象的相等和引用相等的区别?
对象的相等一般比较的是内存中存放的内容是否相等。
引用相等一般比较的是他们指向的内存地址是否相等。
说说构造方法?
主要功能是完成类的初始化工作;
类创建会有默认的构造方法,如果显示创建有参数的构造方法,则需要将无参数构造方法写出来;
名字和类名同,没有返回值,且类创建时自动调用;
构造方法不能被重写,调用父类通过super,但是能被重载,一个类有多个构造方法;
面向对象的三大特征?
封装:一个对象将属性信息隐藏在内部,不允许外部直接访问,但是提供一些被外部访问的方法;
继承:通过继承子类可以拥有父类的属性和方法,快速创建类和提高代码的复用;extends。
多态:一个类有多种状态,具体表现为父类引用指向子类的实例。
多态的特点有哪些?
对象类型和引用类型之间具有继承(类)/实现(接口)的关系;
引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定; 如策略模式
多态不能调用“只在子类存在但在父类不存在”的方法;
如果子类重写了父类的方法,真正执行的是子类覆盖的方法
,如果子类没有覆盖父类的方法,执行的是父类的方法。
接口和抽象类的比较?
共同点 :
都不能被实例化。
都可以包含抽象方法。
都可以有默认实现的方法(Java 8 可以用 default
关键字在接口中定义默认方法)。
区别 :
接口主用于对类的行为进行约束,实现了某个接口就具有了对应的行为。抽象类主用于代码复用,强调是所属关系。
一个类只能继承一个类,但是可以实现多个接口。
接口中的成员变量只能是 public static final
类型的,不能被修改且必须有初始值,而抽象类的成员变量默认 default,可在子类中被重新定义,也可被重新赋值。
深浅拷贝区别及什么是引用拷贝?
浅拷贝:浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点),不过,如果原对象内部的属性是引用类型的话,浅拷贝会直接复制内部对象的引用地址,也就是说拷贝对象和原对象共用同一个内部对象。
深拷贝 :深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。
引用拷贝:两个不同的引用指向同一个对象。
==和equals()区别?
基本类型==
比较的是值,引用类型==
比较的是对象的内存地址;(因为Java只有值传递)。
equals()
用来判断两个对象是否相等,Object拥有该方法,每个类都都继承Object类,所有类都有
equals()
方法;
类没有重写
equals()
方法 :通过equals()
比较该类的两个对象时,等价于“==”比较这两个对象;
String的equals()认识?
String
中的 equals
方法是被重写过的,因为 Object
的 equals
方法是比较的对象的内存地址,而 String
的 equals
方法比较的是对象的值。
创建 String
类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就将它赋给当前引用。如果没有就在常量池中重新创建一个 String
对象。
hashCode()的介绍?
hashCode()
作用是获取哈希码/散列码,哈希作用是标识对象在哈希/散列表中的索引位置;可通过该码快速检索出对象。
hashCode()
是本地方法,每个类都拥有的方法;
一些容器(比如HashMap
、HashSet
)有了 hashCode()
,判断元素在容器内的效率会更高(比如向这些容器中添加元素过程,同样的hashcode
有多个对象,会继续使用equals()
来判断是否真相同,会缩小查找成本);
hashcode
值相等不代表两个对象相等;
如果两个对象的hashCode
值相等,那这两个对象不一定相等(哈希碰撞)。
如果两个对象的hashCode
值相等并且equals()
方法也返回 true
,我们才认为这两个对象相等。
如果两个对象的hashCode
值不相等,我们就可以直接认为这两个对象不相等。
String、StringBuffer、StringBuilder的区别?
String
是不可变的,线程安全的;
StringBuffer
对方法加了同步锁或者对调用的方法加了同步锁,是线程安全的。StringBuilder
并没有对方法进行加同步锁,是非线程安全的。
每次对 String
类型进行改变的时候,都会生成一个新的 String
对象,然后将指针指向新的 String
对象;
操作少量的数据: 适用 String
单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder
多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer
String
真正不可变有下面几点原因:
保存字符串的数组被 final
修饰且为私有的,并且String
类没有提供/暴露修改这个字符串的方法。
String
类被 final
修饰导致其不能被继承,进而避免了子类破坏 String
不可变。
字符串常量池的作用了解吗?
字符串常量池是JVM为提升性能和减少内存消耗对字符串专门开辟的一块区域,主要目的是为了避免字符串重复创建。
String类型变量和常量“+”运算时发生什么?
对于编译期可以确定值的字符串,也就是常量字符串 ,jvm 会将其存入字符串常量池。字符串常量拼接得到的字符串常量在编译阶段就已经被存放字符串常量池,这个得益于编译器的优化。
引用的值在程序编译期是无法确定的,编译器无法对其进行优化。
Exception和Error的区别?
Exception:程序可以处理的异常,可以通过catch捕获,分为受检查异常和运行时异常;
Error:属于程序无法处理的错误,例如OutOfMemoryError
,这些异常发生时会选择终止线程;
Checked Exception 即 受检查异常 ,Java 代码在编译过程中,如果受检查异常没有被 catch
或者throws
关键字处理的话,就没办法通过编译。例如 IO 相关的异常、ClassNotFoundException
、SQLException
Unchecked Exception 即 不受检查异常 ,Java 代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译。常见的例如 NullPointerException
(空指针异常),IllegalArgumentException
(参数错误异常)
try-catch-finally如何使用?
注意:不要在 finally 语句块中使用 return
程序所在的线程死亡和CPU死亡这两种情况下,finally
块的代码也不会被执行:
try-with-resources如何使用
面对必须要关闭的资源,我们总是应该优先使用 try-with-resources
而不是try-finally
。随之产生的代码更简短,更清晰,产生的异常对我们也更有用。try-with-resources
语句让我们更容易编写必须要关闭的资源的代码
try (BufferedInputStreambin=newBufferedInputStream(newFileInputStream(newFile("test.txt")));
BufferedOutputStreambout=newBufferedOutputStream(newFileOutputStream(newFile("out.txt")))) {
intb;
while ((b=bin.read()) !=-1) {
bout.write(b);
}
}
catch (IOExceptione) {
e.printStackTrace();
}
项目中哪里用到泛型?
自定义接口通用返回结果 CommonResult
通过参数 T
可根据具体的返回类型动态指定结果的数据类型
定义 Excel
处理类 ExcelUtil
用于动态指定 Excel
导出的数据类型
构建集合工具类(参考 Collections
中的 sort
, binarySearch
方法)。
说说反射?
可以在运行时分析类以及执行类中方法的能力,通过反射可以获取并调用任意一个类的属性和方法。
使得代码更灵活,获得在运行时分析操作类的能力,增加安全性问题,性能会差点;
使用场景:动态代理,注解
代理模式?
使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。
代理模式的主要作用是扩展目标对象的功能,比如说目标对象的某个方法执行前后你可以增加一些自定义操作。
静态代理?
静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。
静态代理实现步骤:
定义一个接口及其实现类;
创建一个代理类同样实现这个接口
将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法。这样的话,我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情。
动态代理?
动态代理更加灵活,不需要针对目标类单独创建代理类,可以直接代理实现类(CGLIB动态代理机制),从JVM角度,动态代理是在运行时动态生成字节码加载到JVM中,Spring AOP,RPC框架就是依赖动态代理。
JDK动态代理步骤:
定义一个接口及其实现类;
自定义 InvocationHandler
并重写invoke
方法,在 invoke
方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;
通过 Proxy.newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)
方法创建代理对象;
CGLIB动态代理步骤:
定义一个类;
自定义 MethodInterceptor
并重写 intercept
方法,intercept
用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke
方法类似;
通过 Enhancer
类的 create()
创建代理类;
JDK动态代理和CGLIB动态代理区别?
JDK动态代理只能代理实现了接口类或者直接代理接口,而CGLIB可以未实现接口的类;
CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。
大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显;
静态代理和动态代理的区别?
灵活性 :动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的!
JVM 层面 :静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。
序列化和反序列化?
当需要持久化java对象比如将 Java 对象保存在文件中,或者在网络传输 Java 对象,这些场景都需要用到序列化;
序列化: 将数据结构或对象转换成二进制字节流的过程
反序列化:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程
序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。
OSI 七层协议模型中的应用层、表示层和会话层对应的都是 TCP/IP 四层模型中的应用层,所以序列化协议属于 TCP/IP 协议应用层的一部分。
对于不想进行序列化的变量,使用 transient
关键字修饰。
关于 transient
还有几点注意:
transient
只能修饰变量,不能修饰类和方法。
transient
修饰的变量,在反序列化后变量值将会被置成类型的默认值。例如,如果是修饰 int
类型,那么反序列后结果就是 0
。
static
变量因为不属于任何对象(Object),所以无论有没有 transient
关键字修饰,均不会被序列化。
常用的序列化协议有 Hessian、Kryo、Protobuf,基于二进制的序列化协议。JSON 和 XML 这种属于文本类序列化方式。虽然可读性比较好,但是性能较差;
Java I/O流了解?
IO 流在 Java 中分为输入流和输出流,而根据数据的处理方式又分为字节流和字符流。
Java有哪些常见的语法糖?
Java 中最常用的语法糖主要有泛型、自动拆装箱、变长参数、枚举、内部类、增强 for 循环、try-with-resources 语法、lambda 表达式等。
BigDecimal介绍使用
浮点数没有办法用二进制精确表示,因此存在精度丢失的风险。
Java 提供了BigDecimal
来操作浮点数。BigDecimal
的实现利用到了 BigInteger
(用来操作大整数), 所不同的是 BigDecimal
加入了小数位的概念。