JAVA基础知识总结(一)
Java基础
Java 语言有哪些特点?
- 面向对象(封装,继承,多态)
- 平台无关性( Java 虚拟机实现平台无关性,java源代码被编译成字节码之后,JVM在每个特定的平台上负责解释并执行这些字节码。)
- 支持多线程
JDK、JRE、JVM
解释三者?
Java 和 C++区别?
Java 和 C++ 都是面向对象的语言,都支持封装、继承和多态,但是,它们还是有挺多不相同的地方:
1、Java 不提供指针来直接访问内存,程序内存更加安全
2、Java 的类是单继承的,C++ 支持多重继承;虽然 Java 的类不可以多继承,但是接口可以多继承。
3、Java 有自动内存管理垃圾回收机制(GC),不需要程序员手动释放无用内存。
4、C ++同时支持方法重载和操作符重载,但是 Java 只支持方法重载(操作符重载增加了复杂性,这与 Java 最初的设计思想不符)。
指针、多重继承、内存释放、重载
静态方法为什么不能调用非静态成员?
他们两个的生命周期是不同的,静态方法是属于类的,在类加载的时候就会分配内存,而非静态成员属于实例对象,只有在对象实例化之后才存在。所以在我们的对象还没有实例化的时候,使用静态方法调用非静态成员的话,此时内存中还不存在静态成员,属于非法操作。
基本类型和包装类型的区别?
1、用途:包装类型可用于泛型,而基本类型不可以。
2、存储方式:基本数据类型的局部变量存放在 Java 虚拟机栈中的局部变量表中,基本数据类型的非静态成员变量跟随着对象实例存放在 Java 虚拟机的堆中。包装类型属于对象类型,我们知道几乎所有对象实例都存在于堆中。
3、占用空间:相比于包装类型(对象类型), 基本数据类型占用的空间往往非常小。
4、默认值:成员变量包装类型不赋值就是 null ,而基本类型有默认值且不是 null。
5、比较方式:对于基本数据类型来说,== 比较的是值。对于包装数据类型来说,== 比较的是对象的内存地址,所以包装类对象之间值的比较,全部使用 equals 方法比较。
“几乎”所有的对象实例都在堆分配内存,但并非全部。因为还有一些对象是在栈上分配的。
在JDK 1.7及以后的版本中,默认开启了逃逸分析。逃逸分析是指在运行时分析对象的作用域,以确定对象是否会逃逸出当前方法或线程的范围,如果不会逃逸出去,即不会被外部访问,这样对象就可以被优化为栈上分配或进行标量替换。
栈上分配是指将对象直接分配在线程的栈空间上,而不是在堆上分配。
标量替换是指将对象拆分为其各个成员变量,并将这些成员变量作为栈上的标量进行操作,从而避免在堆上创建对象实例。
此外逃逸分析还可以进行同步消除的优化:
同步消除:如果一个对象被发现只在一个线程内被访问,那么对该对象的同步操作(如synchronized块)就可以被消除,因为它们并不会造成线程安全问题。
基本数据类型存放在栈中是一个常见的误区:如果它们是局部变量,那么它们会存放在栈中;如果它们是非静态成员变量,那么它们会存放在堆中。
静态成员变量存放在Java虚拟机的方法区(Method Area)中。方法区用于存储类的结构信息、静态变量、常量、方法字节码等数据。
面向对象和面向过程的区别
两者的主要区别在于解决问题的方式不同:
- 面向过程把解决问题的过程拆成一个个方法,通过一个个方法的执行解决问题。
- 面向对象会先抽象出对象,然后用对象执行方法的方式解决问题。
字符串拼接用“+” 还是 StringBuilder?
使用+来拼接底层实际上还是转变为了StringBuilder并采用append的方法来拼接,然后在使用toString来转换为String,并且在for循环中,StringBuilder不能重复利用,会产生大量的StringBuilder,因此推荐使用StringBuilder的append的方式。
比如下面这种方式,会产生大量的StringBuilder
public class StringConcatenationExample {
public static void main(String[] args) {
// 初始化一个空字符串
String result = "";
// 模拟一个循环
for (int i = 0; i < 1000; i++) {
// 使用加号进行字符串拼接
result = result + "String " + i + ", ";
}
// 输出拼接后的字符串
System.out.println(result);
}
}
不过,使用 “+” 进行字符串拼接会产生大量的临时对象的问题在 JDK9 中得到了解决。在 JDK9 当中,字符串相加 “+” 改为了用动态方法 makeConcatWithConstants()
来实现,而不是大量的 StringBuilder
了。这个改进是 JDK9 的 提出的,这也意味着 JDK 9 之后,你可以放心使用“+” 进行字符串拼接了
什么是泛型?有什么作用?
Java 泛型(Generics) 是 JDK 5 中引入的一个新特性。通过泛型参数可以指定传入的对象类型,并且在编译期间对泛型参数进行检测,因此可以增强代码的可读性以及稳定性。
泛型的使用方式有哪几种?
泛型一般有三种使用方式:泛型类、泛型接口、泛型方法。
你的项⽬中哪⾥⽤到了泛型?
为什么静态泛型方法不能使用类上声明的泛型?
类上声明的泛型只有在对象实例化的时候才能够传递参数,而静态泛型方法属于类,在对象实例化之前静态方法就已经加载完成了,所以静态泛型方法是没有办法使用类上声明的泛型的
何谓注解?
Annotation
(注解) 是 Java5 开始引入的新特性,可以看作是一种特殊的注释,主要用于修饰类、方法或者变量,提供某些信息供程序在编译或者运行时使用。
注解本质是一个继承了Annotation
的特殊接口。
注解的解析方法有哪几种?
注解只有被解析之后才会生效,常见的解析方法有两种:
- 编译期直接扫描:编译器在编译 Java 代码的时候扫描对应的注解并处理,比如某个方法使用
@Override
注解,编译器在编译的时候就会检测当前的方法是否重写了父类对应的方法。 - 运行期通过反射处理:像框架中自带的注解(比如 Spring 框架的
@Value
、@Component
)都是通过反射来进行处理的。
常见序列化协议有哪些?
JDK 自带的序列化方式一般不会用 ,因为序列化效率低并且存在安全问题。
比较常用的序列化协议有 Hessian、Kryo、Protobuf、ProtoStuff,这些都是基于二进制的序列化协议。
像 JSON 和 XML 这种属于文本类序列化方式。虽然可读性比较好,但是性能较差,一般不会选择。
Kryo 是一个高性能的序列化/反序列化工具,由于其变长存储特性并使用了字节码生成机制,拥有较高的运行速度和较小的字节码体积。
Protobuf 出自于 Google,性能还比较优秀,也支持多种语言,同时还是跨平台的。就是在使用中过于繁琐,因为你需要自己定义 IDL 文件和生成对应的序列化代码。这样虽然不灵活,但是,另一方面导致 protobuf 没有序列化漏洞的风险。
异常Exception的分类?
- Checked Exceptions(受检查异常):在编译时必须进行处理的异常,他们通常是外部情况导致的异常,例如文件未找到、网络连接断开等。在Java中,所有继承自Exception类但不是RuntimeException的异常都属于Checked Exceptions。
- Unchecked Exceptions(不受检查异常):也称为运行时异常,是在编译时不需要被显式捕获的异常,通常指由程序错误导致的异常,例如除以零、空指针引用等。这些异常在编译时不强制要求捕获或声明,通常由编程错误造成。
为什么不推荐使用 JDK 自带的序列化?
我们很少或者说几乎不会直接使用 JDK 自带的序列化方式,主要原因有下面这些原因:
- 不支持跨语言调用 : 如果调用的是其他语言开发的服务的时候就不支持了。
- 性能差:相比于其他序列化框架性能更低,主要原因是序列化之后的字节数组体积较大,导致传输成本加大。
- 存在安全问题:序列化和反序列化本身并不存在问题。但当输入的反序列化的数据可被用户控制,那么攻击者即可通过构造恶意输入,让反序列化产生非预期的对象,在此过程中执行构造的任意代码。
序列化协议对应于 TCP/IP 4 层模型的哪一层?
OSI 七层协议模型中对应的是表示层,TCP/IP模型中对应的是应用层。
OSI 七层协议模型中,表示层做的事情主要就是对应用层的用户数据进行处理转换为二进制流。
为什么浮点数运算的时候会有精度丢失的风险呢?
因为计算机的存储是二进制的,并不是所有的小数都能被计算机精确的表示,只能表示成无限循环的小数,而无限循环的小数存储时,只能被截断,所以会发生精度损失。
BigDecimal 等值比较问题
这是因为 equals()
方法不仅仅会比较值的大小(value)还会比较精度(scale),而 compareTo()
方法比较的时候会忽略精度。
SPI实战演练
SLF4J (Simple Logging Facade for Java)是 Java 的一个日志门面(接口),其具体实现有几种,比如:Logback、Log4j、Log4j2 等等,而且还可以切换,在切换日志具体实现的时候我们是不需要更改项目代码的,只需要在 Maven 依赖里面修改一些 pom 依赖就好了。