(3)执行效率:StringBuilder最高,StringBuffer次之,String最低
每次对String类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String对象。StringBuffer每次都会对StringBuffer本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用StringBuilder相比使用StringBuffer仅能获得10%~15%左右的性能提升,但要冒多线程不安全的风险
对于三者使用的总结:
当操作少量数据时,优先使用String。
当在单线程下操作大量数据,优先使用StringBuilder类
当在多线程下操作大量数据,优先使用StringBuffer类
5、String为什么要设置成不可变的?
(1)实现字符串常量池
字符串常量池(String pool)是Java堆内存中一个特殊的存储区域,当创建一个String对象时,假如此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象;假若字符串对象允许改变,那么将会导致各种逻辑错误,比如改变一个对象会影响到另一个独立对象,严格来说,这种常量池的思想,是一种优化手段
(2)允许String对象缓存HashCode
Java中String对象的哈希码被频繁地使用, 比如在HashMap等容器中;字符串不变性保证了hash码的唯一性;因此可以放心地进行缓存,这也是一种性能优化手段,意味着不必每次都去计算新的哈希码.
(3)安全性
String被许多的Java类(库)用来当做参数,例如:网络连接地址URL,文件路径path,还有反射机制所需要的String参数等,假若String不是固定不变的,将会引起各种安全隐患;
数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接,或者在socket编程中,主机名和端口都是以字符串的形式传入;因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞。
在并发场景下,多个线程同时读写资源时,由于 String 是不可变的,不会引发线程的问题而保证了线程安全
总体来说,String不可变的原因包括 设计考虑,效率优化与安全性 这三大方面
6、自动装箱与拆箱
装箱:将基本类型用它们对应的引用类型包装起来
拆箱:将包装类型转化为基本数据类型
如下代码:
Integer i = 100; // 实际上是Integer.valueOf(100),
int n = i; // 实际上是 i.intValue()
如上代码所示,在装箱的时候 自动调用 的是 Integer的valueOf(int) 方法;而在拆箱的时候自动调用的是 Integer的intValue() 方法。
详解: 在调用Integer.valueOf()方法中,如果数值在[-128,127]之间,将直接从IntegerCache中获取
因为IntegerCache中缓存了[-128,127]之间的值,通过Integer.valueOf()方法创建数值在[-128,127]之间的Integer对象时,便返回指向IntegerCache中已经存在的对象的引用;否则创建一个新的Integer对象
7、抽象类和接口的异同?
不同点:
| 序号 | 不同点 |
| — | — |
| 1 | 在JDK1.8之前接口只有方法的定义,不能有方法的实现;JDK1.8中接口可以有默认方法(default修饰)和静态方法(static修饰)的实现;JDK1.9开始接口中可以定义和实现私有方法(普通私有方法和静态私有方法);而抽象类可以有方法的定义与实现 |
| 2 | 接口里只能定义静态常量(static final),不能定义普通成员变量;抽象类中既可以定义静态常量,也能定义普通成员变量 |
| 3 | 接口中不能包含静态代码块,抽象类中可以包含静态代码块 |
| 4 | 一个类只能继承一个抽象类,但是一个类可以实现多个接口 |
| 5 | 接口强调的是特定功能的实现,抽象类强调的是所属关系 |
| 6 | main 方法:接口不能有 main 方法;抽象类可以有 main 方法,并且可以运行它 |
| 7 | 接口中定义的成员变量,只能是静态常量,默认修饰符 public static final,而且必须给其赋初值;接口中定义的成员方法,抽象方法,默认修饰符为public abstract;抽象类中成员变量默认default(默认,什么也不写,同一包中可见),可在子类中被重新定义,也可被重新赋值;抽象类中抽象方法被abstract修饰,不能被private、static、synchronzed和native等修饰,必须以分号结尾,不带花括号 |
| 8 | 接口中不包含构造器;抽象类里可以包含构造器,抽象类中的构造器并不是用于创建对象,而是让其子类调用这些构造器来完成属于抽象类的初始化操作 |
| 9 | 接口被用于常用的功能,便于日后维护和添加删除;抽象类更倾向于充当公共类的角色,不适用于日后重新对立面的代码修改;功能需要累积时用抽象类,不需要累积时用接口 |
相同点:
| 序号 | 相同点 |
| — | — |
| 1 | 接口和抽象类都不能被实例化;接口的实现类或抽象类的子类都只有实现了接口或抽象类中的方法后才能被实例化 |
| 2 | 接口和抽象类都可以包含抽象方法 |
8、Java中" == "与equals()方法的区别?
" == “: 对于八大基本数据类型来说,直接比较值;如果是引用数据类型,则是比较内存地址;(因为 Java只有值传递 ,所以对于” == "来说,不管是比较基本数据类型,还是引用数据类型的变量,本质都是比较值,只是引用类型变量存的值是对象的地址)
equals():equals()是Object类提供的方法之一;每一个Java类都继承自Object类,所以每一个对象都具有equals方法。Object中的equals方法是直接使用" == “运算符比较的两个对象,所以在没有重写equals方法的情况下,equals与” == "运算符一样,比较的是地址; 可以通过重写equals方法来比较两个对象的内容是否相等
9、为什么重写equals()方法时必须重写hashCode()方法?
equals()和hashCode()方法要遵循如下的 原则 :
1、如果两个对象equals()方法相等,它们的hashCode返回值一定要相同
2、如果两个对象的hashCode返回值相同,但它们的equals()方法不一定相等
3、两个对象的hashcode()返回值不相等,则可以判定两个对象的内容一定不相等
如果只重写equals()方法而不重写hashCode()方法,将会造成equals()方法判断出的结果是true,但hashCode()返回值不同的情况,也可能会出现hashCode()方法返回值相等,但equals()方法相同的情况,因此equals()方法与hashCode()方法都要进行修改,使两者遵循上述原则; 即:equals方法被覆盖,则hashCode方法也必须被覆盖
问题:为什么需要hashCode()方法?
答:使用hashCode()方法提前校验,避免每一次比较都调用equals方法,提高效率
面试官:你有没有重写过equals()和hashcode()?
答: 在使用HashMap的“key”的部分存放自定义的对象时,重写过hashCode和equals方法,从而保证key是唯一的
10、Java中的八大基本数据类型
Java有8中基本数据类型,其中包括
6种数字类型:byte(1)、short(2)、int(4)、long(8)、float(4)、double(8)
1种字符类型:char(2)
1种布尔型:boolean
对于boolean型,官方文档未明确定义,它依赖于JVM厂商的具体实现
11、final关键字
final用来修饰类、方法和变量
1、 final修饰的类不能被继承,final类中的所有成员方法都会被隐式的指定为final方法 (但是final修饰的类中成员变量是可变的,如果想要final类的成员变量不可变,必须给成员变量增加final修饰)
2、final修饰的方法不能被重写
3、 final修饰的变量是常量 ;如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能让其指向另一个对象
12、static关键字
static关键字主要有以下4种用法:
1、修饰成员变量和成员方法
被 static 修饰的成员属于类,被类中所有对象共享,可通过 类名.成员变量 或 对象.成员变量的方式调用
static变量也称作静态变量,静态变量和非静态变量的区别是: 静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化 ;而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响;static成员变量的初始化顺序按照定义的顺序进行初始化
static方法也称作静态方法,静态方法不依赖于任何对象就可以进行访问,静态方法只能访问静态成员,但不能访问类的非静态成员,因为非静态成员必须依赖具体的对象才能够被调用(成员:成员方法与成员变量)
2.静态代码块
静态代码块定义在类中方法外,类中可以有多个static块
静态代码块在非静态代码块之前按照static块的顺序来执行每个static块(静态代码块–>非静态代码块–>构造方法)
一个类不管创建多少对象,静态代码块只执行一次
3.静态内部类
static修饰类的话只能修饰内部类;静态内部类与非静态内部类之间存在一个最大的区别: 非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:<1>它的创建是不需要依赖外围类的创建;<2>它不能使用任何外围类的非静态成员(成员:成员方法与成员变量)
4.静态导包
用来导入类中的静态资源,1.5之后的新特性;可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中的静态成员,可以直接使用类中静态成员变量和成员方法。
静态代码块的加载次序:
父类静态代码块–>子类静态代码块–>父类非静态代码块–>父类构造函数–>子类非静态代码块–>子类构造函数
13、深拷贝和浅拷贝
浅拷贝:对基本数据类型进行值传递;对引用数据类型只是进行引用的传递,并没有在内存中的创建一个新的对象,此为浅拷贝
深拷贝:对基本数据类型进行值传递;对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝
14、Java异常体系
Java中,所有的异常都有⼀个共同的祖先java.lang包中的Throwable类:Throwable类有两个重要的⼦类:Exception(异常) 和 Error(错误),⼆者都是Java异常体系的重要⼦类,各⾃都包含⼤量⼦类
Error(错误):程序⽆法处理的错误,表示运⾏应⽤程序中较严重问题;⼤多数错误与代码编写者执⾏的操作⽆关,⽽表示代码运⾏时JVM出现的问题;例如,Java 虚拟机运⾏错误(Virtual MachineError),当 JVM 不再有继续执⾏操作所需的内存资源时,将出现内存溢出(OutOfMemoryError);这些异常发⽣时,JVM⼀般会选择终⽌线程
Exception(异常):程序本身可以处理的异常;Exception 类有⼀个重要的⼦类RuntimeException;RuntimeException异常由JVM抛出;还有NullPointerException(要访问的变量没有引⽤任何对象时,抛出该异常);ArithmeticException(算术运算异常,⼀个整数除以0时,抛出该异常)以及 ArrayIndexOutOfBoundsException(数组下标越界异常)
Exception(异常)又分为两类:运行时异常和编译时异常
1、运行时异常(不受检异常):RuntimeException类及其子类表示JVM在运行期间可能出现的错误;比如说试图使用空值对象的引用(NulIPointerException)、数组下标越界(ArraylndexOutBoundException);此类异常属于不可查异常,一般是由程序逻辑错误引起的,在程序中可以选择捕获处理,也可以不处理
2、编译时异常(受检异常):Exception中除RuntimeException及其子类之外的异常;如果程序中出现此类异常,比如说IOException,必须对该异常进行处理,否则编译不通过;在程序中,通常不会自定义该类异常,而是直接使用系统提供的异常类
15、反射
1、什么是Java的反射
反射是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取信息以及动态调用对象的方法的功能称为 Java 语言的反射机制
2、反射的作用
运行时:判断对象所属的类;构造一个对象所属的类;判断一个对象的方法和属性;调用对象的方法和属性
16、Java泛型
Java的泛型即"参数化类型",允许程序在创建集合时指定集合元素的类型,表示该集合只能保存该类型的对象
为什么要使用泛型?
答: 如果不使用泛型,当把一个对象存入集合后,集合就会忘记这个对象的数据类型,再次取出该对象时,该对象的编译类型就变成了Object类型,还需要进行强制转换;当使用泛型后,集合中只能存入指定类型的对象,否则将报错,并且将对象从集合取出后无需对元素进行强制转换,就是原本的类型(指定的类型) ;
由此可见在集合中存储对象并在使用前进行类型转换是多么的不方便;泛型防止了那种情况的发生,提供了编译时的类型安全,确保只能把正确类型的对象放入集合中,避免了在运行时出现ClassCastException(类型转换异常)
扩展知识:
父子对象之间的转换分为了向上转型和向下转型;它们解释如下:
向上转型: 通俗地讲,就是将子类对象向上转为父类对象,或者说是父类引用指向子类对象 ,此处父类对象可以是接口
格式: 父类 变量名 = new 子类();
向上转型的好处?
向上转型后,父类引用可以调用子类重写过的父类方法,当需要新添功能时,可以新增一个(子)类即可,而不用更改原父类代码
向上转型后的对象不是新创建的父类对象,而是子类对象的"简化"状态,它不关心子类新增的功能,只关心子类继承和重写的功能;当一个类有很多子类时,并且这些子类都重写了父类中的某个方法,使用上转型对象调用这个方法时就可能具有多种形态,因为不同的子类在重写父类的方法时可能产生不同的行为;也就是说, 不同对象的上转型对象调用同一方法可能产生不同的行为
向下转型:与向上转型相反,即是把父类对象转为子类对象;一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制转换的格式
格式: 子类 变量名 = (父类类型)父类变量;
为什么要向下转型?
向下转型一般是为了重新获得因为向上转型而丢失的子类特性;因此,通常在向下转型前已经有向上转型,而向下转型通常也会结合instanceof一起使用;借由向下转型,可以在灵活应用多态的基础上,同时兼顾子类的独有特征(instanceof:用来测试一个对象是否为一个类的实例)
用法是: boolean result = obj instanceof Class
17、什么是泛型擦除
在代码中定义 List<Object>
和 List<String>
等类型,在编译后都会变成List,JVM看到的只是List,而由泛型附加的类型信息对JVM是看不到的
在如下例子中,定义了两个ArrayList数组;一个是ArrayList 泛型类型的,只能存储字符串;一个是ArrayList
泛型类型的,只能存储整数;通过list1对象和list2对象的getClass()方法获取他们的类的信息,最后发现结果为true
说明泛型类型String和Integer都被擦除掉了,只剩下原始类型
Java代码:
import java.util.*;
public class Test {
public static void main(String[] args) {
List l1 = new ArrayList();
List l2 = new ArrayList();
System.out.println(l1.getClass() == l2.getClass());
System.out.println(“l1.getClass():”+l1.getClass().getName());
复习的面试资料
这些面试全部出自大厂面试真题和面试合集当中,小编已经为大家整理完毕(PDF版)
- 第一部分:Java基础-中级-高级
- 第二部分:开源框架(SSM:Spring+SpringMVC+MyBatis)
- 第三部分:性能调优(JVM+MySQL+Tomcat)
- 第四部分:分布式(限流:ZK+Nginx;缓存:Redis+MongoDB+Memcached;通讯:MQ+kafka)
- 第五部分:微服务(SpringBoot+SpringCloud+Dubbo)
- 第六部分:其他:并发编程+设计模式+数据结构与算法+网络
进阶学习笔记pdf
- Java架构进阶之架构筑基篇(Java基础+并发编程+JVM+MySQL+Tomcat+网络+数据结构与算法)
- Java架构进阶之开源框架篇(设计模式+Spring+SpringMVC+MyBatis)
- Java架构进阶之分布式架构篇 (限流(ZK/Nginx)+缓存(Redis/MongoDB/Memcached)+通讯(MQ/kafka))
- Java架构进阶之微服务架构篇(RPC+SpringBoot+SpringCloud+Dubbo+K8s)
- Java架构进阶之开源框架篇(设计模式+Spring+SpringMVC+MyBatis)
[外链图片转存中…(img-bkYn1rWH-1719200037163)]
[外链图片转存中…(img-vxJpLYmY-1719200037164)]
[外链图片转存中…(img-E2yf1LxU-1719200037166)]
- Java架构进阶之分布式架构篇 (限流(ZK/Nginx)+缓存(Redis/MongoDB/Memcached)+通讯(MQ/kafka))
[外链图片转存中…(img-H4lIU5xq-1719200037167)]
[外链图片转存中…(img-6YnDU89T-1719200037167)]
[外链图片转存中…(img-bIb3xp8K-1719200037167)]
- Java架构进阶之微服务架构篇(RPC+SpringBoot+SpringCloud+Dubbo+K8s)
[外链图片转存中…(img-0IgH3ScD-1719200037168)]
[外链图片转存中…(img-hmfviA1L-1719200037168)]