Java 基础面试题
1.JDK和JRE的区别?
- JDK:Java Development Kit 的简称,Java 开发工具包,提供了 Java 的开发环境和运行环境。
- JRE:Java Runtime Environment 的简称,Java 运行环境,为 Java 的运行提供了所需环境。
2.==和equals的区别?
- 基本类型:比较的是值是否相同;
- 引用类型:比较的是引用是否相同;
String str1 = "abc";
String str2 = "abc";
System.out.println(str1==str2);//true
String str3 =newString("abc");
String str4 =newString ("abc");
System.out.println(str3==str4);//false
System.out.println(str3.equals(str4));//true
解析:equals 本质上就是 ==,只不过 String 和 Integer 等重写了 equals 方法,把它变成了值比较。
PS:
Integer i1 = 100, i2 = 100;
Integer i3 = 1000, i3 = 1000;
System.out.println(i1==i2);//true
System.out.println(i3==i4);//false
解析:在整数的包装类当中,在第一次创建 Integer 类的对象的时候,都会首先创建好缓存数组。当需要包装的 值是在 IntegerCache 数组当中的元素的时候,就会返回数组当中的Integer 对象。JVM 默认就会设置数组的范围为 -128 ~ 127 。如下:
@HotSpotIntrinsicCandidate
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
3.Java基本数据类型
byte short int long float double char boolean, String 属于对象。
4.两个对象的 hashCode()相同,则 equals()也一定为 true,对吗?
不对,两个对象的 hashCode() 相同,equals() 不一定 true。
5. final 在 java 中有什么作用?
- final 修饰的类叫最终类,该类不能被继承。
- final 修饰的方法不能被重写。
- final 修饰的变量叫常量,常量必须初始化,初始化之后值就不能被修改。
6. java 中的 Math.round(-1.5) 等于多少?
等于 -1,因为在数轴上取值时,中间值(0.5)向右取整,所以正 0.5 是往上取整,负 0.5 是直接舍弃。
7.String、StringBuffer、StringBuilder的区别?
String 是字符串常量, final 修饰; StringBuffer 字符串变量(线程安全); StringBuilder 字符串变量(线程不安全).此外 StringBuilder 和 StringBuffer 实现原理一样,都是基于数组扩容来实现的.
StringBuffer 和 StringBuilder 的实现原理一样,其父类都是 AbstractStringBuilder.StringBuffer 是线程安全的, StringBuilder 是 JDK 1.5 新增的,其功能和 StringBuffer 类似,但是非线程安全.因此,在没有多线程问题的前提下,使用 StringBuilder 会取得更好的性能.
8. String str="i" 与 String str=new String("i") 一样吗?
不一样,因为内存的分配方式不一样。String str="i" 的方式,java 虚拟机会将其分配到常量池中;而 String str=new String("i") 则会被分到堆内存中。
9. 如何将字符串反转?
1)使用 StringBuilder 或者 StringBuffer 的 reverse() 方法。
String str = "abcdefg";
StringBuffer stringBuffer = new StringBuffer(str);
StringBuilder stringBuilder = new StringBuilder(str);
System.out.println(stringBuffer.reverse());
System.out.println(stringBuilder.reverse());
2)使用 String 对象的 toCharArray() 方法,将字符串转换为 char[] 数组,然后逆向遍历并拼接。
String str = "abcdefg";
StringBuilder strNew =new StringBuilder();
char[] chars = str.toCharArray();
for(int i = chars.length -1; i >= 0; i--){
strNew.append(chars[i]);
}
3)使用 String 对象的 charAt 方法逐个获取字符,正向遍历,逆向拼接
String str = "abcdefg";
StringBuilder strNew =new StringBuilder();
for (int i = 0; i < str.length(); i++) {
strNew.insert(0, str.charAt(i));
}
10.String类的常用方法有哪些?
- indexOf():返回指定字符的索引。
- charAt():返回指定索引处的字符。
- replace():字符串替换。
- trim():去除字符串两端空白。
- split():分割字符串,返回一个分割后的字符串数组。
- getBytes():返回字符串的 byte 类型数组。
- length():返回字符串长度。
- toLowerCase():将字符串转成小写字母。
- toUpperCase():将字符串转成大写字符。
- substring():截取字符串。
- equals():字符串比较。
11.普通类和抽象类的区别?抽象类可以用final修饰吗?抽象类和接口的区别?
1)普通类不能有抽象方法,抽象类可以有也可以没有抽象方法,抽象类不能直接实例化,普通类可以。
2)抽象类不能用 final 修饰,因为抽象类的定义就是为了被继承。
3)抽象类和接口的区别:
抽象类使用 extends 继承,接口使用 implements 实现;
抽象类可以有构造函数,接口不能有;
抽象类可以有非抽象接口,接口不能有(java8 之后可以有了)
类可以实现多个接口,但是只能继承一个类;
接口中的方法默认使用 public 访问修饰符,抽象类中的方法可以任意访问修饰符(抽象方法不能使用 private)
12.Java中的IO流分为几种?
- 按功能划分:输入流、输出流;
- 按类型划分:字节流、字符流,字节流按 8 为传输以字节为单位,字符流按 16 为传输以字符为单位;
13.BIO、NIO、AIO有什么区别?
- BIO: Block IO 同步阻塞式 IO,即传统 IO,特点是使用简单,并发处理能力低。
- NIO: Non IO 同步非阻塞 IO,传统 IO的升级,客户端和服务端通过 Channel 通讯,实现了多路复用。
- AIO: Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非阻塞 IO,异步 IO 的操作基于事件和回调机制。
14.Files的常用方法都有哪些?
- Files.exists():检测文件路径是否存在;
- Files.createFile():创建文件;
- Files.createDirectory():创建文件夹;
- Files.delete():删除一个文件或目录;
- Files.copy():复制文件;
- Files.move():移动文件;
- Files.size():查看文件个数;
- Files.read():读取文件;
- Files.write():写入文件;
15.面向对象的特征
封装、继承、多态
16.父类的静态方法能否被子类重写
不能.重写只适用于实例方法,不能用于静态方法,而子类当中含有和父类相同签名的静态方法。
17.什么是不可变对象?好处是什么?
不可变对象指对象一旦被创建,状态就不能再改变,任何修改都会创建一个新的对象,如 String、Integer 及其它包装类.不可变对象最大的好处是线程安全.
18.静态变量和实例变量的区别?
静态变量存储在方法区,属于类所有.实例变量存储在堆当中,其引用存在当前线程栈.需要注意的是从 JDK1.8 开始用于实现方法区的 PermSpace 被 MetaSpace 取代了.
19.java 创建对象的几种方式
- new 创建新对象
- 通过反射机制
- 采用 clone 机制
- 通过序列化机制
PS:前两者都需要显式地调用构造方法. 对于 clone 机制,需要注意浅拷贝和深拷贝的区别,对于序列化机制需要明确其实现原理,在 java 中序列化可以通过实现 Externalizable 或者 Serializable 来实现.
20.Object中有哪些公共方法?
equals(),clone(),getClass(),notify(),notifyAll(),wait(),toString()
21.& 和 &&的区别
基础的概念不能弄混: & 是位操作, && 是逻辑运算符.需要记住逻辑运算符具有短路特性,而 & 不具备短路特性.
22.在.java文件内部可以有多少类(非内部类)?
在一个 java 文件中只能有一个 public 公共类,但是可以有多个 default 修饰的类.
23.如何正确的退出多层嵌套循环?
- 使用标号和 break;
- 通过在外层循环中添加标识符
24.final,finalize()和finally{}的不同之处
三者没有任何相关性,遇到有问着问题的面试官就拖出去砍了吧.
- final是一个修饰符,用于修饰变量,方法和类.如果 final 修饰变量,意味着该变量的值在初始化后不能被改变.
- finalize() 方法是在对象被回收之前调用的方法,给对象自己最后一个复活的机会.但是该方法由 Finalizer 线程调用,但调用时机无法保证.
- finally是一个关键字,与 try 和 catch 一起用于异常的处理, finally{} 一定会被执行,在此处我们通常用于资源关闭操作.
25.clone()是哪个类的方法?
java.lang.Cloneable 是一个标示性接口,不包含任何方法.clone ()方法在 Object 类中定义的一个Native方法:
protected NativeObject clone() throws CloneNotSupportedException;
26.深拷贝和浅拷贝的区别是什么?
- 浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象.
- 深拷贝:被复制对象的所有变量都含有与原来的对象相同的值.而那些引用其他对象的变量将指向被复制过的新对象.而不再是原有的那些被引用的对象.换言之.深拷贝把要复制的对象所引用的对象都复制了一遍.
27.64位的JVM当中,int的长度是多少?
Java中数据类型所占用的位数和平台无关,在 32 位和 64 位 的Java 虚拟机中,int 类型的长度都是占 4 字节.
PS:short(2)、int(4)、long(8)、float(4)、double(8)
28.int和Integer的区别?
Integer 是 int 的包装类型,在拆箱和装箱中,二者自动转换.int是基本类型,直接存数值;而integer是对象;用一个引用指向这个对象.由于Integer是一个对象,在JVM中对象需要一定的数据结构进行描述,相比int而言,其占用的内存更大一些.
29.String s = new String("abc") 创建了几个String对象?
2 个.一个是字符串字面常数,在字符串常量池中;另一个是new出来的字符串对象,在堆中.
30.请问s1 == s3 是 true 还是 false,s1 == s4 是 false 还是true? s1 == s5 呢?
String s1 = "abc";
String s2 = "a";
String s3 = s2 + "bc";
String s4 = "a" + "bc";
String s5 = s3.intern();
s1==s3返回false,s1==s4返回true,s1==s5返回true.
解析:
- “abc" 这个字符串常量值会直接方法字符串常量池中,s1 是对其的引用.由于 s2 是个变量,编译器在编译期间无法确定该变量后续会不会改,因此无法直接将 s3 的值在编译器计算出来,因此 s3 是堆中 "abc" 的引用.因此 s1!=s3.
- 对于 s4 而言,其赋值号右边是常量表达式,因此可以在编译阶段直接被优化为 "abc”,由于 "abc" 已经在字符串常量池中存在,因此 s4 是对其的引用,此时也就意味 s1 和 s4 引用了常量池中的同一个 "abc". 所以s1==s4.
- String 中的 intern() 会首先从字符串常量池中检索是否已经存在字面值为 "abc" 的对象,如果不存在则先将其添加到字符串常量池中,否则直接返回已存在字符串常量的引用.此处由于 "abc" 已经存在字符串常量池中了,因此 s5 和 s1 引用的是同一个字符串常量.
31.以下代码中,s5==s2返回值是什么?
String s1="ab";
String s2="a"+"b";
String s3="a";
String s4="b";
String s5=s3+s4;
返回 false. 在编译过程中,编译器会将 s2 直接优化为 "ab",将其放置在常量池当中;而 s5 则是被创建在堆区,相当于 s5=new String(“ab”);
32.你对String对象的intern()熟悉么?
Stirng 中的 intern() 是个Native方法,它会首先从常量池中查找是否存在该常量值的字符串,若不存在则先在常量池中创建,否则直接返回常量池已经存在的字符串的引用.
33.什么是编译器常量?使用它有什么风险?
公共静态不可变,即 public static final 修饰的变量就是我们所说的编译期常量.这里的 public 可选的.实际上这些变量在编译时会被替换掉,因为编译器明确的能推断出这些变量的值(如果你熟悉C++,那么这里就相当于宏替换).
编译器常量虽然能够提升性能,但是也存在一定问题:你使用了一个内部的或第三方库中的公有编译时常量,但是这个值后面被其他人改变了,但是你的客户端没有重新编译,这意味着你仍然在使用被修改之前的常量值.
34. 3*0.1==0.3返回值是什么
false,因为有些浮点数不能完全精确的表示出来.
35.java当中使用什么类型表示价格比较好?
如果不是特别关心内存和性能的话,使用 BigDecimal.否则使用预定义精度的 double 类型.
36.如何将byte转为String
可以使用 String 接收 byte[] 参数的构造器来进行转换,注意要使用的正确的编码,否则会使用平台默认编码.这个编码可能跟原来的编码相同.也可能不同.
String str = new String("abc".getBytes(), StandardCharsets.UTF_8);
37.可以将int强转为byte类型么?会产生什么问题?
可以做强制转换,但是 Java 中int是 32 位的而byte是 8 位的.如果强制转化 int 类型的高 24 位将会被丢弃, byte 类型的范围是从-128到128.
38.a=a+b与a+=b有什么区别吗?
+= 操作符会进行隐式自动类型转换,此处a+=b隐式的将加操作的结果类型强制转换为持有结果的类型,而a=a+b则不会自动进行类型转换.如:
39.了解泛型么? T、R、K、V、E分别指什么?简述泛型的上界和下界?
常用的范型含义:
- T - Type(类型)
- R - Result(结果)
- K - Key(键)
- V - Value(值)
- E - Element(元素)
- N - Number(数字)
- ? - 不确定类型
有时候希望传入的类型有一个指定的范围,从而可以进行一些特定的操作,这时候就需要通配符了?在Java中常见的通配符主要有以下几种:
- : 无限制通配符;
- : extends 关键字声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类;
- : super关键字声明了类型的下界,表示参数化的类型可能是指定的类型,或者是此类型的父类;
它们的目的都是为了使方法接口更为灵活,可以接受更为广泛的类型;
- : 用于灵活读取,使得方法可以读取 E 或 E 的任意子类型的容器对象;
- : 用于灵活写入或比较,使得对象可以写入父类型的容器,使得父类型的比较方法可以应用于子类对象;
用简单的一句话来概括就是为了获得最大限度的灵活性,要在表示生产者或者消费者的输入参数上使用通配符,使用的规则就是:生产者有上限(读操作使用extends),消费者有下限(写操作使用super).
39.Java到底是值传递还是引用传递?
Java 中方法参数严格来说是按值传递的。
- 如果参数是基本类型:传递的是基本类型的字面量值的拷贝。
- 如果参数是引用类型:传递得是该参量所引用的对象在队中地址的拷贝。
40.char类型可以存储中文汉字吗?
可以的,因为 Java 中使用的编码时 Unicode,一个 char 类型占 2 个字节(16 Bit),而一个中文也是占 2 个字节,所以存放一个中文时没有问题的。
41.重载和重写的区别?
方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。
- 方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载(Overloading)。
- 方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写(Overriding)。
- 方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。
42.Java异常有哪些分类?常见的异常有哪些?
异常分类:
- Throwable:是 Java 异常的顶级类。
- Error:是非程序异常类,即程序不能捕获的异常,一般是编译或者系统性错误,如:OutOfMemorry 内存溢出异常等。
- Exception:是程序异常类,由程序内部产生。Exception 又分为运行时异常、非运行时异常
- 运行时异常:不用显示捕获的异常,如:NullPointerException、ArrayIndexOutOfBoundsException 等。
- 非运行时异常:是程序必须处理的异常,如:IOException、ClassNotFoundException 等。
常见的异常:
- NullPointerException:空指针异常,操作一个 null 对象的方法或属性时会抛出这个异常。
- OutOfMemoryError:内存溢出异常,是指要分配的对象的内存超出了当前最大的堆内存,需要调整堆内存的大小以及优化程序。
- IOException:IO异常,需要手工捕获。
- FileNotFoundException:文件找不到异常,如果文件不存在就会抛出这种异常。
- ClassNotFoundException:类找不到异常,即在类路径下不能加载指定的类。
- ClassCastException:类转换异常,将一个不是该类的实例转换成这个类就会抛出这个异常。
- NoSuchMethodException:没有这个方法异常,一般发生在反射调用方法时。
- IndexOutOfBoundsException:索引越界异常,当操作字符串或者数组时经常遇到。
- ArithmeticException:算数异常,发生在数字的算术运算时的异常,如:3/0
- SQLException:SQL 异常,发生在操作数据库时的异常。
43.throw和throws的区别?
- throw:是真实抛出一个异常
- throws:是声明可能会抛出一个异常
44.try里面return,finally还会执行吗?
- 不管 try 有没有 return, finally 都会执行
- 在 try 中 return,在 finally 执行前会把结果保存起来。即使在 finally 中有修改也以 try 中保存的指为准,但如果是引用类型,修改的属性会以 finally 修改后为准。
- 如果 try/finally 都有 return,直接返回 finally 中的 return。
45.静态方法可以直接调用非静态方法吗?
不可以,静态方法只能直接调用静态方法。
因为非静态方法的调用需要先创建对象,而调用静态方法不需要对象实例化。
46.静态内部类和普通内部类的区别?
静态内部类可以不依赖于外部类实例被实例化,而普通内部类需要在外部类实例化后才能实例化。
47.内部类可以访问其外部类的成员吗?
可以的,内部类可以访问创建它的外部类对象的成员,包括私有成员。
48.Java类初始化顺序是怎样的?
-
类的初始化顺序:
静态变量, 静态代码快 -》 实例变量(属性,实例代码块,构造方法) -
继承关系初始化顺序:
父类静态成员,静态代码块 -》 子类静态成员,静态代码块 -》 父类实例变量(属性,实例代码块,构造方法)-》子类实例变量(属性,实例代码块,构造方法)
相同等级的初始化的先后顺序,是直接依赖代码中初始化的先后顺序
49.为什么成员变量不建议用isXXX?
对应阿里巴巴开发手册第一章的命名风格的第八条:POJO 类中的任何布尔类型的变量,都不要加 is 前缀,否则部分框架解析会引起序列化错误。
50.Java常用的元注解有哪些?
- @Target:描述了注解修饰的对象范围,取值在java.lang.annotation.ElementType定义,常用的包括:
METHOD:用于描述方法
PACKAGE:用于描述包
PARAMETER:用于描述方法变量
TYPE:用于描述类、接口或enum类型 - @Retention: 表示注解保留时间长短。取值在java.lang.annotation.RetentionPolicy 中,取值为:
RetentionPolicy.SOURCE -------------注解将被编译器丢弃
RetentionPolicy.CLASS -------------注解在 class 文件中可用,但会被 JVM 丢弃
RetentionPolicy.RUNTIME ---------VM将在运行期也保留注释,因此可以通过反射机制读取注解的信息 - @Documented
Documented 注解的作用是:描述在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息。 - @Inherited
Inherited 注解的作用是:使被它修饰的注解具有继承性(如果某个类使用了被 @Inherited 修饰的注解,则其子类将自动具有该注解)。
51.Java金额计算怎么避免丢失精度?
Java中的类型 float、double 用来做计算会有精度丢失问题,所以使用 BigDecimal
例如:
double totalAmount = 0.09;
double feeAmount = 0.02;
System.out.println(totalAmount - feeAmount);//结果:0.06999999999999999
BigDecimal bigDecimal = BigDecimal.valueOf(0.09);
BigDecimal bigDecimal2 = BigDecimal.valueOf(0.02);
System.out.println(bigDecimal.subtract(bigDecimal2));//结果0.07
52.Java语法糖是什么意思?
语法糖就是对现有语法的一个封装。
Java中的语法糖:
- 泛型与类型擦除
- 自动装箱与拆箱
- 可变参数
- 曾强for循环
- 内部类
- 枚举类
53.transient关键字是什么意思?
- transient 修饰的成员变量不能被序列化;
- transient 只作用于实现 Serializable 接口;
- transient 只能用来修饰普通成员变量字段;
- 不管有没有 transient 修饰,静态变量都不能被序列化
54.Java反射机制有什么用?反射机制的优缺点?Class类的常用方法有哪些?
- Java反射机制有什么用?
Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。 - 反射机制的优缺点?
优点:可以动态执行,在运行期间根据业务功能动态执行方法、访问属性,最大限度发挥了 Java 的灵活性。
缺点:对性能有影响,这类操作总是慢于直接执行 Java 代码。 - Class类的常用方法有哪些?
- Class.forName():动态加载类
- newInstance():根据对象的class新建一个对象
- getSuperclass():获取继承的父类
- getInterfaces():获取继承的接口
- getDeclaredFields():获取字段名称
- getDeclaredMethods():获取当前类的所有方法
- getConstructors():获取所有的构造函数
- getModifiers():反射中获得修饰符
- getPackage():反射中获取package
- getField(String name):反射中获得域成员
- getFields():获得域数组成员
- isAnnotation():判断是否为注解类型
- isPrimitive():判断是否为基本类型
- isArray():判断是否为数组类型
- isEnum():判断是否为枚举类型
- getClassLoader():获得类的类加载器
- getMethods():获得公共方法
55.Java反射可以访问私有方法和私有变量吗?
可以的,使用 .setAccessible(true);
public static void main(String[] args) throws Exception {
User user = new User();
user.setName("神雕");
user.setAge(500);
Class<? extends User> aClass = user.getClass();
//访问私有成员变量
Field age = aClass.getDeclaredField("age");
age.setAccessible(true);
System.out.println(age.get(user));
//访问私有方法
Method getAge = aClass.getDeclaredMethod("getAge");
getAge.setAccessible(true);
Object invoke = getAge.invoke(user);
System.out.println(invoke);
}
class User{
public String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
private int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
56.什么是宏变量和宏替换?
首先来理解下宏变量:
Java 中,一个用 final 定义的变量,不管它是类型的变量,只要用final定义了并同时指定了初始值,并且这个初始值是在编译时就被确定下来的,那么这个final变量就是一个宏变量。编译器会把程序所有用到该变量的地方直接替换成该变量的值,也就是说编译器能对宏变量进行宏替换。
final String a = "hello";
final String b = a;
final String c = getHello();
解析:a在编译期间就能确定下来,而b、c不行,所以a是宏变量,b、c不是。
57.什么是逃逸分析?
逃逸分析(Escape Analysis)简单来讲就是,Java Hotspot 虚拟机可以分析新创建对象的使用范围,并决定是否在 Java 堆上分配内存的一项技术。
逃逸分析的 JVM 参数如下:
- 开启逃逸分析:-XX:+DoEscapeAnalysis
- 关闭逃逸分析:-XX:-DoEscapeAnalysis
- 显示分析结果:-XX:+PrintEscapeAnalysis
对象的逃逸状态:
-
全局逃逸(GlobalEscape)
即一个对象的作用范围逃出了当前方法或者当前线程,有以下几种场景:
对象是一个静态变量
对象是一个已经发生逃逸的对象
对象作为当前方法的返回值 -
参数逃逸(ArgEscape)
即一个对象被作为方法参数传递或者被参数引用,但在调用过程中不会发生全局逃逸,这个状态是通过被调方法的字节码确定的。 -
没有逃逸
即方法中的对象没有发生逃逸。
逃逸分析优化:
针对上面第三点,当一个对象没有逃逸时,可以得到以下几个虚拟机的优化。
-
锁消除
我们知道线程同步锁是非常牺牲性能的,当编译器确定当前对象只有当前线程使用,那么就会移除该对象的同步锁。
例如,StringBuffer 和 Vector 都是用 synchronized 修饰线程安全的,但大部分情况下,它们都只是在当前线程中用到,这样编译器就会优化移除掉这些锁操作。
锁消除的 JVM 参数如下:
开启锁消除:-XX:+EliminateLocks
关闭锁消除:-XX:-EliminateLocks
锁消除在 JDK8 中都是默认开启的,并且锁消除都要建立在逃逸分析的基础上。 -
标量替换
首先要明白标量和聚合量,基础类型和对象的引用可以理解为标量,它们不能被进一步分解。而能被进一步分解的量就是聚合量,比如:对象。
对象是聚合量,它又可以被进一步分解成标量,将其成员变量分解为分散的变量,这就叫做标量替换。
这样,如果一个对象没有发生逃逸,那压根就不用创建它,只会在栈或者寄存器上创建它用到的成员标量,节省了内存空间,也提升了应用程序性能。
标量替换的 JVM 参数如下:
开启标量替换:-XX:+EliminateAllocations
关闭标量替换:-XX:-EliminateAllocations
显示标量替换详情:-XX:+PrintEliminateAllocations
标量替换同样在 JDK8 中都是默认开启的,并且都要建立在逃逸分析的基础上。 -
栈上分配
当对象没有发生逃逸时,该对象就可以通过标量替换分解成成员标量分配在栈内存中,和方法的生命周期一致,随着栈帧出栈时销毁,减少了 GC 压力,提高了应用程序性能。
58.什么是伪共享?有什么解决方案?
CPU的缓存是以缓存行(cache line)为单位进行缓存的,当多个线程修改不同变量,而这些变量又处于同一个缓存行时就会影响彼此的性能。例如:线程 1 和线程 2 共享一个缓存行,线程1只读取缓存行中的变量 1,线程 2 修改缓存行中的变量 2,虽然线程1和线程2操作的是不同的变量,由于变量 1 和变量 2 同处于一个缓存行中,当变量2被修改后,缓存行失效,线程 1 要重新从主存中读取,因此导致缓存失效,从而产生性能问题。
解决方案:
可以通过填充来解决伪共享问题,Java8 中引入了@sun.misc.Contended注解来自动填充。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE})
public @interface Contended {
String value() default "";
}
59.Java8 都新增了哪些新特性?
Java8 新增了非常多的特性,我们主要讨论以下几个:
- Lambda 表达式 − Lambda 允许把函数作为一个方法的参数(函数作为参数传递到方法中)。
- 方法引用 − 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
- 默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。
- 新工具 − 新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。
- Stream API −新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。
- Date Time API − 加强对日期与时间的处理。
- Optional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
- Nashorn, JavaScript 引擎 − Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。
60.Java8 中的Lambda 表达式有什么用?
Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。
Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
使用 Lambda 表达式可以使代码变的更加简洁紧凑。
lambda 表达式的语法格式如下:
(parameters) -> expression
或
(parameters) ->{ statements; }
以下是lambda表达式的重要特征:
- 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
- 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
- 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
- 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
Lambda 表达式的简单例子:
// 1. 不需要参数,返回值为 5
() -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y
// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
61.Java8 中的Optional 类有什么用?
Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用 get() 方法会返回该对象。
Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
Optional 类的引入很好的解决空指针异常。
以下是一个 java.util.Optional
public final class Optional<T> extends Object
使用:
public static void main(String args[]){
Java8Tester java8Tester = new Java8Tester();
Integer value1 = null;
Integer value2 = new Integer(10);
// Optional.ofNullable - 允许传递为 null 参数
Optional<Integer> a = Optional.ofNullable(value1);
// Optional.of - 如果传递的参数是 null,抛出异常 NullPointerException
Optional<Integer> b = Optional.of(value2);
System.out.println(java8Tester.sum(a,b));
}
public Integer sum(Optional<Integer> a, Optional<Integer> b){
// Optional.isPresent - 判断值是否存在
System.out.println("第一个参数值存在: " + a.isPresent());
System.out.println("第二个参数值存在: " + b.isPresent());
// Optional.orElse - 如果值存在,返回它,否则返回默认值
Integer value1 = a.orElse(new Integer(0));
//Optional.get - 获取值,值需要存在
Integer value2 = b.get();
return value1 + value2;
}
62.Java8 中的Stream 有什么用?
Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。
1)筛选与切片
filter:过滤流中的某些元素
limit(n):获取n个元素
skip(n):跳过n元素,配合limit(n)可实现分页
distinct:通过流中元素的 hashCode() 和 equals() 去除重复元素
Stream
Stream<Integer> newStream = stream.filter(s -> s > 5) //6 6 7 9 8 10 12 14 14
.distinct() //6 7 9 8 10 12 14
.skip(2) //9 8 10 12 14
.limit(2); //9 8
newStream.forEach(System.out::println);
2) 映射
map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
List<String> list = Arrays.asList("a,b,c", "1,2,3");
//将每个元素转成一个新的且不带逗号的元素
Stream<String> s1 = list.stream().map(s -> s.replaceAll(",", ""));
s1.forEach(System.out::println); // abc 123
Stream<String> s3 = list.stream().flatMap(s -> {
//将每个元素转换成一个stream
String[] split = s.split(",");
Stream<String> s2 = Arrays.stream(split);
return s2;
});
s3.forEach(System.out::println); // a b c 1 2 3
3) 排序
sorted():自然排序,流中元素需实现Comparable接口
sorted(Comparator com):定制排序,自定义Comparator排序器
List<String> list = Arrays.asList("aa", "ff", "dd");
//String 类自身已实现Compareable接口
list.stream().sorted().forEach(System.out::println);// aa dd ff
Student s1 = new Student("aa", 10);
Student s2 = new Student("bb", 20);
Student s3 = new Student("aa", 30);
Student s4 = new Student("dd", 40);
List<Student> studentList = Arrays.asList(s1, s2, s3, s4);
//自定义排序:先按姓名升序,姓名相同则按年龄升序
studentList.stream().sorted((o1, o2) -> {
if (o1.getName().equals(o2.getName())) {
return o1.getAge() - o2.getAge();
} else {
return o1.getName().compareTo(o2.getName());
}
}).forEach(System.out::println);
4) 匹配、聚合操作
allMatch:接收一个 Predicate 函数,当流中每个元素都符合该断言时才返回true,否则返回false
noneMatch:接收一个 Predicate 函数,当流中每个元素都不符合该断言时才返回true,否则返回false
anyMatch:接收一个 Predicate 函数,只要流中有一个元素满足该断言则返回true,否则返回false
findFirst:返回流中第一个元素
findAny:返回流中的任意元素
count:返回流中元素的总个数
max:返回流中元素最大值
min:返回流中元素最小值
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
boolean allMatch = list.stream().allMatch(e -> e > 10); //false
boolean noneMatch = list.stream().noneMatch(e -> e > 10); //true
boolean anyMatch = list.stream().anyMatch(e -> e > 4); //true
Integer findFirst = list.stream().findFirst().get(); //1
Integer findAny = list.stream().findAny().get(); //1
long count = list.stream().count(); //5
Integer max = list.stream().max(Integer::compareTo).get(); //5
Integer min = list.stream().min(Integer::compareTo).get(); //1
63.Java8 中的@Repeatable 注解有什么用?
重复注解,即一个注解可以在一个类、方法或者字段上同时使用多次。
源码:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
Class<? extends Annotation> value();
}
Spring中可以使用多个扫描组件来扫描多个包的注解:
@ComponentScan
@ComponentScan
public class Configuration {
}
64.Java8 中的方法引用是指什么?
方法引用是只需要使用方法的名字,而具体调用交给函数式接口,需要和Lambda表达式配合使用。
如:
List<String> list = Arrays.asList("a", "b", "c");
list.forEach(str -> System.out.print(str));
list.forEach(System.out::print);
65.Java8 中的函数式编程怎么用?
传统创建线程的方式:
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("t1");
}
});
t1.start();
函数式接口方式:
Thread t2 = new Thread(() -> System.out.println("t2"));
t2.start();
凡是被@FunctionalInterface注解的接口都可以使用函数式编程
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
66.怎么创建一个Stream 流
1). Stream.of 可变参数、数组
Stream<String> stream = Stream.of("A", "B", "C");
String[] values = new String[]{"A", "B", "C"};
stream = Stream.of(values);
System.out.println("stream : " + stream .collect(Collectors.joining()));
2). Arrays、List、Set、Map的stream()方法
String[] values = new String[]{"A", "B", "C"};
Stream<String> stream = Arrays.stream(values);//Arrays
stream = Arrays.asList(values).stream();//List
stream = new HashSet<>(Arrays.asList(values)).stream();//Set
HashMap<String, String> map = new HashMap<>();
stream = map.values().stream();//Map
System.out.println("stream3: " + stream.collect(Collectors.joining()));
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· winform 绘制太阳,地球,月球 运作规律
· 上周热点回顾(3.3-3.9)