java基础八股
java
特点
Java语言的特点可以概括为以下几点:
-
跨平台性:Java的"一次编写,到处运行"(Write Once, Run Anywhere)特性,通过JVM实现。
- 速记技巧:"跨平台"。
-
面向对象:支持封装、继承和多态等面向对象的特性。
- 速记技巧:"面向对象"。
-
健壮性:Java设计时注重错误处理和异常机制。
- 速记技巧:"健壮"。
-
安全性:提供了安全机制,如内存管理和访问控制。
- 速记技巧:"安全"。
-
多线程:原生支持多线程编程。
- 速记技巧:"多线程"。
-
动态性:Java支持动态链接和动态加载。
- 速记技巧:"动态"。
-
高性能:通过JIT编译器优化代码执行。
- 速记技巧:"高性能"。
-
分布式:支持网络编程,易于构建分布式应用。
- 速记技巧:"分布式"。
-
简单性:语法相对简洁,去掉了C++中的一些复杂特性。
- 速记技巧:"简单"。
-
可移植性:代码可在不同平台间轻松移植。
- 速记技巧:"可移植"。
记住这些特点的核心是理解它们如何影响Java程序的编写和运行。
字节码
字节码是Java源代码编译后的中间代码,采用字节码的好处是平台无关性、安全性和性能优化。
速记技巧:字节码“码”字和“码上加码”谐音,联想到“加码安全”,即“平台无关,安全加固”。
JIT(Just in Time Compilation) 编译器,而 JIT 属于运行时编译。当 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。而我们知道,机器码的运行效率肯定是高于 Java 解释器的。这也解释了我们为什么经常会说 Java 是编译与解释共存的语言 。---
为什么说 Java 语言“编译与解释并存”?
Java语言“编译与解释并存”是因为Java源代码首先被编译成字节码,然后在JVM上通过即时编译器(JIT)进一步编译为机器码执行。速记技巧:“编”即编译,“解”即解释,联想到“编解并存”,即“编译后解释”。
Oracle JDK vs OpenJDK
Oracle JDK和OpenJDK都是Java开发工具包,但它们有以下区别:
- Oracle JDK是Oracle公司提供的,包含更多特性和商业支持,但对个人用户免费。
- OpenJDK是开源的Java开发工具包,由社区驱动,完全免费,是Oracle JDK的开源版本。
速记技巧:"奥商开源"(Oracle商业,OpenJDK开源)。
"JDK"的中文谐音发音是"杰迪凯"。
oracle 奥瑞克
分类 | 关键字 | ||||||
---|---|---|---|---|---|---|---|
访问控制 | private | protected | public | ||||
类,方法和变量修饰符 | abstract | class | extends | final | implements | interface | native |
new | static | strictfp | synchronized | transient | volatile | enum | |
程序控制 | break | continue | return | do | while | if | else |
for | instanceof | switch | case | default | assert | ||
错误处理 | try | catch | throw | throws | finally | ||
包相关 | import | package | |||||
基本类型 | boolean | byte | char | double | float | int | long |
short | |||||||
变量引用 | super | this | void | ||||
保留字 | goto | const |
Tips:所有的关键字都是小写的,在 IDE 中会以特殊颜色显示。
包装类型的缓存机制了解么
Java中的包装类型(如Integer、Double等)有一个缓存机制,用于提高性能。这个机制在-128到127之间(对于Integer)的值会使用相同的实例,避免重复创建对象。
速记技巧:想象一个装满相同物品的仓库,每次需要时直接取用,而不是重新制造,这就是“缓存”(Cache)。
记住:所有整型包装类对象之间值的比较,全部使用 equals 方法比较。
自动装箱与拆箱了解吗?原理是什么?
从字节码中,我们发现装箱其实就是调用了 包装类的 valueOf()
方法,拆箱其实就是调用了 xxxValue()
方法。
Integer i = 10
等价于Integer i = Integer.valueOf(10) 装箱
int n = i
等价于int n = i.intValue()
; 拆箱
为什么浮点数运费会有精度丢失
这个和计算机保存浮点数的机制有很大关系。我们知道计算机是二进制的,而且计算机在表示一个数字时,宽度是有限的,无限循环的小数存储在计算机时,只能被截断,所以就会导致小数精度发生损失的情况。这也就是解释了为什么浮点数没有办法用二进制精确表示。---
记忆技巧:想象浮点数是一个有限的容器,它只能装下一定量的水(数值),如果水(数值)太多,就会溢出(精度丢失)。
就比如说十进制下的 0.2 就没办法精确转换成二进制小数:
如何解决浮点数运算丢失问题
使用大数值对象 BigDecimal
超过lang的数值怎么表示
BigInteger
内部使用 int[]
数组来存储任意大小的整形数据。
成员变量和局部变量
语法 形式 存储方式 生存时间 默认值
成员变量属于类 堆内存 随着类二变 有
局部变量属于方法内 栈内存 方法周期而变 无
静态变量有什么用
静态变量也就是被 static
关键字修饰的变量。
无论一个类创建了多少个对象,它们都共享同一份静态变量。
也就是说,静态变量只会被分配一次内存,这样可以节省内存。
字符型常量和字符串常量的区别?
- 形式:单引号包字符("'"),双引号包字符串("")。
- 含义:字符是整数值(ASCII),可运算;字符串是地址,存位置。
- 内存:字符常量两字节,字符串常量字节多。
用中文顺口溜来记忆就是:“单引字符整,双引存地址;字符二字节,字符串多字节。”这样可以帮助记忆它们之间的区别。
静态方法为什么不能调用非静态成员?
静态方法属于类,而非静态成员属于对象实例。因为静态方法在类加载时就存在,而非静态成员需要等到对象实例化后才存在。所以,静态方法不能调用还没在内存中出现的非静态成员,这是非法操作。
记忆口诀:"静态类存早,非静态等实例"。
静态方法和实例方法有何不同?
静态方法属于类,不需要实例化即可调用,而实例方法需要通过对象来调用。静态方法不能访问类的非静态成员,因为它们在对象实例化之前就存在。实例方法则可以访问类的所有成员。
速记技巧:"静态类属,实例呼唤;静态早存,实例后见"。
英文词汇谐音:
- Static: "丝塔特克"
- Instance: "因斯坦斯"
重载和重写有什么区别?
重载发生在同一个类中,是多个同名方法根据不同参数实现不同功能;
重写则是子类对父类方法的重新实现,要求方法名和参数列表与被重写的父类方法一致,但可以改变返回值类型、异常声明和访问权限。
速记技巧:"重载变参,重写变体;重载编译决,重写运行替"。
面向对象和面向过程
解决问题方式不同
- 面向过程把问题拆分成多个方法,通过一个个方法解决问题
- 面相对象,会抽象出对象,然后用对象执行方法解决问题
面向对象更容易维护,易复用
创建一个对象用什么运算符?对象实体与对象引用有何不同?
创建一个对象使用new运算符。例如,在Java中创建一个类 Car
的实例可以写作 Car myCar = new Car();
对象实体与对象引用的不同在于:
- 对象实体是指实际的对象,它存在于内存的堆区,包含了对象的实际数据和行为。
- 对象引用是指向对象实体的引用变量,它存储了对象在内存中的地址,通过引用可以访问和操作对象实体。
速记技巧:"new来创建,引用指向实体"。
英文词汇谐音:
- new: "牛"
- Object: "欧-不-杰克特"
- Reference: "瑞弗-伦斯"
对象的相等和引用相等的区别
- 对象的相等一般比较的是内存中存放的内容是否相等。
- 引用相等一般比较的是他们指向的内存地址是否相等。
面向对象三大特征
面向对象的三大特征是封装、继承和多态。下面是对这三个特征的简洁解释和速记技巧:
-
封装:封装是将对象的状态(属性)和行为(方法)结合在一起,形成一个独立的对象。外部代码不能直接访问对象的内部状态,而是通过对象提供的方法来实现对内部状态的访问和操作。
速记技巧:"封装遮内部,方法来交互"。
-
继承:继承允许一个类(子类)继承另一个类(父类)的属性和方法。这支持了代码的重用,并且可以形成层次结构。
速记技巧:"继承接特性,扩展新功能"。
-
多态:多态是指允许不同类的对象对同一消息做出响应,但具体的行为会根据对象的实际类型而有所不同。这通常通过方法重写实现。
速记技巧:"多态一消息,行为各不同"。
关于继承的三点重要信息:
- 子类拥有父类的所有属性和方法,但无法访问父类的私有成员。
- 子类可以定义自己的属性和方法,对父类进行扩展。
- 子类可以重写父类的方法,以提供特定的实现。
关于多态的特点:
- 多态要求引用类型和对象类型之间存在继承或实现关系。
- 方法调用的确定是在运行时进行的,这称为动态绑定。
- 多态不能调用只在子类中存在的方法。
- 如果子类重写了父类的方法,执行的是子类的版本;否则,执行的是父类的版本。
英文词汇谐音:
- Encapsulation: "恩-卡普-扫-雷-深"
- Inheritance: "因-赫里-坦斯"
- Polymorphism: "泡-利-莫-飞-斯姆"
- Private: "普-赖-维特"
- Override: "欧-弗-赖德"
接口和抽象类
接口(Interface)和抽象类(Abstract Class)在面向对象编程中都是用来抽象化对象的,它们有一些共同点和明显的区别:
共同点:
- 都不能被实例化,即不能直接创建接口或抽象类的对象。
- 都可以包含抽象方法,这些方法没有具体的实现,需要由实现接口的类或继承抽象类的子类来提供实现。
区别:
- 实现方式:接口使用关键字
interface
定义,抽象类使用abstract class
定义。 - 构造方法:抽象类可以有构造方法,接口不能有。
- 成员变量:抽象类的成员变量可以是各种访问类型,而接口中的所有成员变量默认都是
public static final
的。 - 方法实现:抽象类可以包含非抽象方法,即有具体实现的方法;接口中的所有方法默认是抽象的,直到Java 8之前,接口不能包含非抽象方法,Java 8开始接口可以包含默认方法和静态方法。
- 访问修饰符:抽象类的成员可以有各种访问控制,如
private
、protected
等,而接口的成员变量和方法默认都是public
的。 - 多继承:Java不支持类的多继承,即一个类不能继承多个类,但可以实现多个接口。
- 使用场景:当不同类之间需要有共同的行为时,可以使用抽象类;当需要定义一个可以被多种不同类实现的契约时,使用接口。
速记技巧:"接口立契约,抽象塑基架;接口无构造,抽象有方法;类可多接口,继承单一化"。
英文词汇谐音:
- Interface: "因-特-费-斯"
- Abstract: "阿-布-斯特-拉克特"
- Class: "克-拉-丝"
- Method: "美-瑟-德"
浅拷贝和深拷贝有了解吗,什么是引用拷贝
深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是对象拷贝的两种不同形式,它们在复制对象时的行为和结果有所不同:
浅拷贝:
- 浅拷贝只复制对象本身和对象中包含的值类型的数据。
- 对于对象中的引用类型数据,浅拷贝只是复制了引用的地址,不会复制引用的对象本身。
- 因此,如果原始对象或拷贝的对象修改了引用数据,会影响到另一方。
深拷贝:
- 深拷贝会创建一个新对象,并且递归地复制对象中的所有成员变量,包括引用类型的数据。
- 深拷贝中的引用类型数据也会被完全复制,新对象和原始对象的引用类型数据互不影响。
引用拷贝:
- 引用拷贝实际上并不是真正意义上的拷贝,它只是创建了一个新的引用,指向同一个对象实体。
- 因此,引用拷贝的速度非常快,但它不会产生新的对象实体。
速记技巧:"浅拷贝浅层仿,深拷贝深层详;引用拷贝快,实则同一向"。
英文词汇谐音:
- Shallow Copy: "少-罗-拷-皮"
- Deep Copy: "迪-普-拷-皮"
- Reference Copy: "瑞弗-伦斯-拷-皮"
Object常见的方法
Java中的 Object
类是所有类的基类,它提供了一些常见的方法,这些方法在类的实例中都可用。以下是 Object
类的一些常用方法:
equals(Object obj)
:检查两个对象是否相等。默认实现比较的是对象的内存地址,但通常需要重写这个方法来提供更有意义的比较逻辑。hashCode()
:返回对象的哈希码,通常与equals
方法配合使用,用于在哈希表中定位对象。toString()
:返回对象的字符串表示,通常重写这个方法以提供对象的详细描述信息。clone()
:创建并返回对象的一个副本。这个方法在Object
类中是受保护的,需要在子类中重写并实现具体的复制逻辑。finalize()
:在对象被垃圾回收器回收前执行的清理操作。这个方法不推荐使用,因为其执行时机不确定,且在Java 9中已经被标记为过时。getClass()
:返回对象的Class
对象,可以用来反射操作。wait()
:导致当前线程等待,直到另一个线程调用该对象的notify()
或notifyAll()
方法。notify()
:唤醒在此对象上等待的单个线程。notifyAll()
:唤醒在此对象上等待的所有线程。
速记技巧:"equals比对象,hashCode对哈希;toString描自身,clone造副本;finalize清垃圾,getClass反信息;wait等notify醒,Object基通用"。
英文词汇谐音:
- Object: "欧-不-杰克特"
- equals: "衣-苦-欧斯"
- hashCode: "哈希-扣德"
- toString: "托-斯-特-林"
- clone: "克隆"
- finalize: "发-奈-赖-泽"
- getClass: "葛-特-克-拉斯"
- wait: "等待"
- notify: "通知"
==和equals区别
==
基本数据类型,比较数值;引用数据类型,比较对象的内存地址;
equals
没重写,用得上默认的object的,相当于==;重写,比较属性;
hascode
hashCode()
的作用是获取哈希码(int
整数),也称为散列码。这个哈希码的作用是确定该对象在哈希表中的索引位置。
我们以“HashSet
如何检查重复”为例子来说明为什么要有 hashCode
?
下面这段内容摘自我的 Java 启蒙书《Head First Java》:
当你把对象加入
HashSet
时,HashSet
会先计算对象的hashCode
值来判断对象加入的位置,同时也会与其他已经加入的对象的hashCode
值作比较,如果没有相符的hashCode
,HashSet
会假设对象没有重复出现。但是如果发现有相同hashCode
值的对象,这时会调用equals()
方法来检查hashCode
相等的对象是否真的相同。如果两者相同,HashSet
就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样我们就大大减少了equals
的次数,相应就大大提高了执行速度。
其实, hashCode()
和 equals()
都是用于比较两个对象是否相等。
总结下来就是:
- 如果两个对象的
hashCode
值相等,那这两个对象不一定相等(哈希碰撞)。 - 如果两个对象的
hashCode
值相等并且equals()
方法也返回true
,我们才认为这两个对象相等。 - 如果两个对象的
hashCode
值不相等,我们就可以直接认为这两个对象不相等。
如果重写 equals()
时没有重写 hashCode()
方法的话就可能会导致 equals
方法判断是相等的两个对象,hashCode
值却不相等。
String类 重点
String | StringBuffer | StringBuilder | |
---|---|---|---|
执行速度 | 最差 | 其次 | 最高 |
线程安全 | 线程安全 | 线程安全 | 线程不安全 |
使用场景 | 少量字符串操作 | 多线程环境下的大量操作 | 单线程环境下的大量操 |
Exception 和 Error 有什么区别?
在 Java 中,所有的异常都有一个共同的祖先 java.lang
包中的 Throwable
类。Throwable
类有两个重要的子类:
Exception
:程序本身可以处理的异常,可以通过catch
来进行捕获。Exception
又可以分为 Checked Exception (受检查异常,必须处理) 和 Unchecked Exception (不受检查异常,可以不处理)。Error
:Error
属于程序无法处理的错误 ,我们没办法通过不建议通过catch
来进行捕获catch
捕获 。例如 Java 虚拟机运行错误(Virtual MachineError
)、虚拟机内存不够错误(OutOfMemoryError
)、类定义错误(NoClassDefFoundError
)等 。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。
Checked Exception 和 Unchecked Exception 有什么区别?
Checked Exception 即 受检查异常 ,Java 代码在编译过程中,如果受检查异常没有被 catch
或者 throws
关键字处理的话,就没办法通过编译。
Unchecked Exception 即 不受检查异常 ,Java 代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译。
SPI优缺点
通过 SPI 机制能够大大地提高接口设计的灵活性,但是 SPI 机制也存在一些缺点,比如:
- 需要遍历加载所有的实现类,不能做到按需加载,这样效率还是相对较低的。
- 当多个
ServiceLoader
同时load
时,会有并发问题。
序列化和反序列化
- 序列化:将数据结构或对象转换成二进制字节流的过程
- 反序列化:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程
java值传递
- 值传递:方法接收的是实参值的拷贝,会创建副本。
- 引用传递:方法接收的直接是实参所引用的对象在堆中的地址,不会创建副本,对形参的修改将影响到实参。
- 实参(实际参数,Arguments):用于传递给函数/方法的参数,必须有确定的值。
- 形参(形式参数,Parameters):用于定义函数/方法,接收实参,不需要有确定的值。
反射机制
获取对象四种方式
1. 知道具体类的情况下可以使用:
Class alunbarClass = TargetObject.class;
但是我们一般是不知道具体类的,基本都是通过遍历包下面的类来获取 Class 对象,通过此方式获取 Class 对象不会进行初始化
2. 通过 Class.forName()
传入类的全路径获取:
Class alunbarClass1 = Class.forName("cn.javaguide.TargetObject");
3. 通过对象实例 instance.getClass()
获取:
TargetObject o = new TargetObject();
Class alunbarClass2 = o.getClass();
4. 通过类加载器 xxxClassLoader.loadClass()
传入类路径获取:
ClassLoader.getSystemClassLoader().loadClass("cn.javaguide.TargetObject");
通过类加载器获取 Class 对象不会进行初始化,意味着不进行包括初始化等一系列步骤,静态代码块和静态对象不会得到执行