这篇文章只是为了复习做的记录,还会有其他的文章
一部分来自JavaGuide
大家可以关注😁😁😁
JavaSE面试常见题目(上)
JavaSE基础
数据类型
数据类型 | 字节数 | 位数 | 默认值 | 取值范围 |
---|---|---|---|---|
整数型 | ||||
byte | 1 | 8 | 0 | -128 ~ 127 |
short | 2 | 16 | 0 | -32768 ~ 32767 |
int | 4 | 32 | 0 | -2^32 ~ 2^31 - 1 |
long | 8 | 64 | 0L | -2^64 ~ 2^63 - 1 |
浮点型 | ||||
float | 4 | 32 | 0f | 1.4E-45 ~ 3.4028235E38 |
double | 8 | 64 | 0d | 4.9E-324 ~ 1.7976931348623157E308 |
字符型 | ||||
char | 2 | 16 | ‘\u0000’ | 0 ~ 65535 |
布尔型 | ||||
boolean | 1 | false |
基础语法规范
- 大小写敏感
- 包名:尽量保证小写,例如
com.first.lyu
- 类名:首字母大写,例如
MyFirstClass
- 方法名:首字母小写,后面每个单词字母需要大写,例如
myFirstMethod()
- 标识符可以包含英文字母,0-9的数字,$以及_ 标识符不能以数字开头,标识符不是关键字
注释
注释不会执行,注释就是代码说明书,能够给阅读代码的人快速理清代码逻辑
代码的注释不是越详细越好。实际上好的代码本身就是注释,我们要尽量规范和美化自己的代码来减少不必要的注释。
若编程语言足够有表达力,就不需要注释,尽量通过代码来阐述。
举个例子:
去掉下面复杂的注释,只需要创建一个与注释所言同一事物的函数即可
// check to see if the employee is eligible for full benefits
if ((employee.flags & HOURLY_FLAG) && (employee.age > 65))
应替换为
if (employee.isEligibleForFullBenefits())
关键字
分类 | 关键字 | ||||||
---|---|---|---|---|---|---|---|
访问控制 | private | protected | public | ||||
类,方法和变量修饰符 | abstract | class | extends | final | implements | interface | native |
new | static | strictfp | synchronized | transient | volatile | enum | |
程序控制 | break | continue | if | else | switch | case | return |
for | while | default | assert | instanceof | do | ||
错误处理 | try | catch | finally | throw | throws | ||
包相关 | package | import | |||||
基本类型 | byte | short | int | long | float | double | boolean |
char | |||||||
变量引用 | super | this | void | ||||
保留字 | goto | const |
Tips:所有的关键字都是小写的,在 IDE 中会以特殊颜色显示。
⚠注意 :虽然 true, false, 和 null 看起来像关键字但实际上他们是字面值,同时你也不可以作为标识符来使用。
运算符
- 赋值运算符
=
对于对象来说,复制到不是对象的值,而是对象的引用,所以如果说将一个对象复制给另一个对象,实际上是将一个对象的引用复制给另一个对象
- 自增、自减运算符
int a = 5;
b = ++a; // b=6
c = a++; // c=5
++ 和 – 运算符可以放在变量之前,也可以放在变量之后,当运算符放在变量之前时(前缀),先自增/减,再赋值;当运算符放在变量之后时(后缀),先赋值,再自增/减。也就是,++a 输出的是 a+1 的值,a++输出的是 a 值。“符号在前先加/减,符号在后后加/减”。
- 比较运算符
返回结果为boolean型,关系成立时,结果为true,反之结果为false
- 逻辑运算符
- 位运算符
位运算符用来操作整型基本类型的每个比特位。并最后生成一个结果
运算符 | 作用 |
---|---|
& | 4 & 5 = 4 |
| | 4 | 5 = 5 |
~ | ~4 = ~5 |
^ | 4 ^ 5 = 1 |
-
AND:都为1,结果才为1,否则为0。
-
OR:只要一个为1,结果就是1,否则为0。
-
XOR:位为0,结果为1,位为1,结果为0。
-
NOT:相同结果为0,不同为1。
-
移位运算符
运算符 | 含义 |
---|---|
>> | 右移指定位数 |
<< | 左移指定位数 |
>>> |
instanceof关键字的作用
instanceof严格来说是Java中的一个双目运算符,用来测试一个对象是否是一个类的实例
boolean res = obj instanceof Class
其中 obj 为一个对象,Class 表示一个类或者一个接口,当 obj 为 Class 的对象,或者是其直接或间接子类,或者是其接口的实现类,结果result 都返回 true,否则返回false。
注意:编译器会检查 obj 是否能转换成右边的class类型,如果不能转换则直接报错,如果不能确定类型,则通过编译,具体看运行时定。
obj
必须是引用类型,不能是基本类型- 在JavaSE规范中,如果
obj
为null
,那么返回false
。
基本类型与包装类型区别
- 成员变量包装类型不赋值就是
null
,基本数据类型有默认值 - 包装类型可用于泛型,基本类型不能
- 基本数据类型的局部变量存放在Java虚拟机栈中的局部变量表中,基本数据类型的成员变量(为被
static
修饰)存放在Java虚拟机的堆中。包装类型属于对象类型,几乎所有对象都存在与堆中。 - 基本数据类型占用空间非常小
Java自动装箱与拆箱
装箱就是自动将基本数据类型转换为包装类型(int -> Integer);Integer.valueOf(int)方法
拆箱就是自动将包装类型转换为基本类型;Integer.intValue方法
// jdk5.0之前生成一个数值为10的Integer对象
Integer i = new Integer(10);
// jdk5.0之后提供了自动装箱特性
Integer i = 10;
⚠注意:如果频繁拆装箱的话,也会严重影响系统的性能,应该尽量避免不必要的拆装箱操作。
包装类型的缓存机制
Java基本数据类型的包装类型的大部分都用到了缓存机制来提升性能。
Byte
,Short
,Integer
,Long
这4种包装类默认创建了数值[-128, 127]的相应类型的缓存数据,Character
创建了数值在[0, 127]范围的缓存数据,Boolean
直接返回True
或False
.
如何解决浮点数运算的精度丢失问题
BigDecimal
可以实现对浮点数的运算,不会造成精度丢失。通常情况下,大部分需要浮点数精确运算结果的业务场景(比如涉及到钱的场景)都是通过 BigDecimal
来做的。
final有哪些用法
- 被final修饰的类不可以被继承,final类中的成员方法都隐式地指定为final方法
- 被final修饰的方法不可以被重写
- 被final修饰的变量不可以被改变,如果修饰引用,那么表示引用不可变,引用指向的内容可变
- 被final修饰的方法,JVM会尝试将其内联,以提高运行效率
- 被final修饰的常量,在编译阶段会存入常量池,修饰引用类型时,对其初始化后不能再指向另一个对象。
static都有哪些用法
- 被
static
修饰的变量或方法都属与类的静态资源,类实例所共享。 - static也用于静态块,多用于初始化操作
- static也用于修饰内部类
- 静态导包,即
import static
可以用来指定导入某个类中的静态资源,并且不需要使用类名,可以直接使用资源名
重载和重写的区别
重写
重写是子类继承父类原有的方法,但有时子类并不想原封不动的继承父类的某个方法,所以在方法名,参数列表,返回类型都相同的情况下,对方法体进行修改。注意子类方法的访问权限不能低于父类。
- 发生在父类与子类之间
- 方法名,参数列表,返回值类型(除非子类中方法的返回类型是父类返回类型的子类)必须相同
- 访问修饰符的限制移动要大于被重写方法的访问修饰符
- 重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常
重载
在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同甚至是参数顺序不同)则视为重载。同时,重载对返回类型没有要求,可以相同也可以不同,但不能通过返回类型是否相同来判断重载。
- 重载Overload是一个类中多态性的一种表现
- 重载要求同名方法的参数列表不同(参数类型,参数个数甚至是参数顺序)
- 重载的时候,返回值类型可以相同也可以不相同。无法以返回型别作为重载函数的区分标准
类与对象
类与对象
类是一系列对象的抽象,通过使用class
来定义类
”万事万物都是对象“,尽管一起都看作是对象,但是操纵的是对象的引用
。例如:车钥匙和车是一组对象的引用和对象的组合.
Car key = new Car();
在Java中,一旦创建了一个引用,就希望它能与一个新的对象进行关联,通常使用new
操作符来进行实例化。
Java创建对象的几种方式
new
创建新对象
User user = new User();
- 通过反射机制
//使用newInstance(),但是得处理两个异常InstantiationException、IllegalAccessException
- 采用
clone
机制,clone
是Object
的方法 - 通过反序列化机制,调用
ObjectInputStream
类的readObject()
方法。我们反序列化一个对象,JVM会给我们创建一个单独的对象。JVM创建对象并不会调用任何构造函数。一个对象实现了Serializable
接口,就可以把对象写入到文件中,并通过读取文件来创建对象。
对象实体与对象引用有何不同
对象引用指向对象实例
一个对象引用可以指向0个或1个对象(一个绳子可以不系气球,也可以系一个气球);
一个对象可以有n个引用指向它( 可以用n条绳子系住一个气球)
对象的相等和引用相等的区别
- 对象的相等一般比较的是内存中存放的内容是否相等
- 引用相等一般比较的是他们指向的内存地址是否相等
初始化顺序
- 静态属性:
static
修饰的属性- 静态方法块:
static{}
包起来的代码块- 普通属性:非
static
定义的属性- 普通方法块:
{}
包起来的代码块- 构造函数:类名相同的方法
- 方法:普通方法
顺序:
静态属性初始化->静态方法快初始化->普通属性初始化->普通方法初始化->构造函数初始化
类的构造方法的作用
主要完成对象的初始化工作
构造方法的特点,是否可被Override
- 名字与类名相同
- 没有返回值,但不能用
void
声明构造函数 - 生成类的对象时自动执行,无需调用
构造方法不能被重写,但是可以重载,所以一个类可以有多个构造函数
面向对象
面向对象和面向过程的区别
两者主要区别在于解决问题的方式不同:
- 面向过程把解决问题的过程拆成一个个方法,通过一个个方法的执行解决问题
- 面向对象会先抽象出对象,然后用对象执行方法的方式解决问题
面向对象三大特征
封装
封装是指把一个对象的状态信息(也就是属性)隐藏在对象内部,不允许外部对象直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法来操作属性。
优点:
- 减少耦合
- 易于维护
- 有效地调节性能
- 提供软件的可重用性
继承
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承,可以快速地创建新的类,可以提高代码的重用,程序的可维护性,节省大量创建新类的时间 ,提高我们的开发效率。
- 子类拥有父类对象所以的属性和方法(包括私有属性和私有方法),但父类中的私有属性和方法子类无法访问,只是拥有。
- 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
- 子类可以用自己的方式实现父类的方法。
多态
表示一个对象具有多种的状态,具体表现为父类的引用指向子类的实例。
特点:
- 对象类型和引用类型之间具有继承(类)/实现(接口)的关系;
- 引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定;
- 多态不能调用“只在子类存在但在父类不存在”的方法;
- 如果子类重写了父类的方法,真正执行的是子类覆盖的方法,如果子类没有覆盖父类的方法,执行的是父类的方法。
接口和抽象类有什么共同点和区别
共同点:
- 都不能被实例化
- 都可以包含抽象方法
- 都可以有默认实现的方法(jdk8可以用
default
关键字在接口中定义默认方法)
区别:
- 接口主要用于对类的行为进行约束,实现了某个接口就具有了对应的行为。抽象类主要用于代码复用,强调的是所属关系
- 一个类只能继承一个类,但是可以实现多个接口
- 接口中的成员变量只能是
public static final
类型的,不能被修改且必须有初始值,而抽象类的成员变量默认default
,可在子类中被重新定义,也可被重新赋值
Java常见类
String
- 可变性
Sring是不可变的。
StringBuilder
与StringBuffer
对继承自AbstractStringBuilder
类,在AbstractStringBuilder
中也是使用字符数组保存字符串,不过没有使用final
修饰,最关键的是AbstractStringBuilder
类还提供修改字符串的方法比如append
方法
- 线程安全性
String中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder
是StringBuilder
与StringBuffer
的公共父类,定义了一些字符串的基本操作,如expandCapacity
、append
、insert
、indexOf
等公共方法。StringBuffer
对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder
并没有对方法进行加同步锁,所以是非线程安全的。
- 性能
每次对String类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String对象。StringBuffer
每次都会对StringBuffer
对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用StringBuilder
相比使用StringBuffer
仅能获得10%~15%左右的性能提升,但却要冒多线程不安全的风险。
- 总结
- 操作少量数据适用
String
- 单线程操作字符串缓冲区下操作大量数据适用
StringBuilder
- 多线程操作字符串缓冲区下操作大量数据适用
StringBuffer
为什么String是不可变的
- 保存字符串的数组被
final
修饰且为私有的,并且String类没有提供/暴露修改这个字符串的方法 - String类被
final
修饰导致不能被继承,继而避免了子类破坏String的不可变
补充:在Java 9 之后,
String
、StringBuilder
、StringBuffer
的实现改用byte
数组存储字符串
字符串拼接用“+”还是“append”
“+”和“+=”是专门为String类重载过的字符串,是Java中唯二重载过的运算符。
字符串对象通过“+”的方式拼接,实际是通过StringBuilder
调用append()
方法实现的,拼接完成后调用toString()
得到一个String对象
使用“+”的方式有一个明显的缺点:编译器不会创建单个**StringBuilder**
对象以复用,会导致创建过多的**StringBuilder**
对象
所以拼接字符串使用**append()**
字符串常量池的作用
字符串常量池是JVM为了提升性能和减少内存消耗针对字符串(String类)专门开辟的一块区域,主要为了避免字符串的重复创建
String s1 = new String(“abc”);这句话创建了几个字符串对象
会创建1或2个字符串对象
- 如果字符串常量池中不存在字符串对象"abc"的引用,那么会在堆中创建2个字符串对象"abc"
- 如果字符串常量池中已存在字符串对象"abc"的引用,则只会在堆中创建1个字符串对象"abc"
Object
Object类是所有类的父类,主要提供11个方法
//native 方法,用于返回当前运行时对象的 Class 对象,使用了 final 关键字修饰,故不允许子类重写。
public final native Class<?> getClass()
//native 方法,用于返回对象的哈希码,主要使用在哈希表中,比如 JDK 中的HashMap。
public native int hashCode()
//用于比较 2 个对象的内存地址是否相等,String 类对该方法进行了重写以用于比较字符串的值是否相等。
public boolean equals(Object obj)
//naitive 方法,用于创建并返回当前对象的一份拷贝。
protected native Object clone() throws CloneNotSupportedException
//返回类的名字实例的哈希码的 16 进制的字符串。建议 Object 所有的子类都重写这个方法。
public String toString()
//native 方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。
public final native void notify()
//native 方法,并且不能重写。跟 notify 一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。
public final native void notifyAll()
//native方法,并且不能重写。暂停线程的执行。注意:sleep 方法没有释放锁,而 wait 方法释放了锁 ,timeout 是等待时间。
public final native void wait(long timeout) throws InterruptedException
//多了 nanos 参数,这个参数表示额外时间(以毫微秒为单位,范围是 0-999999)。 所以超时的时间还需要加上 nanos 毫秒。。
public final void wait(long timeout, int nanos) throws InterruptedException
//跟之前的2个wait方法一样,只不过该方法一直等待,没有超时时间这个概念
public final void wait() throws InterruptedException
//实例被垃圾回收器回收的时候触发的操作
protected void finalize() throws Throwable { }
==和equals()
的区别
==
对于基本类型和引用类型的作用效果是不同的:
- 对于基本类型来说,==比较的是值
- 对于引用类型来说,==比较的是对象的内存地址
equals()
不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。equals()
方法存在于Object
类中,而Object
类是所有类的直接或间接父类,因此所有的类都有equals()
方法
public boolean equals(Object obj) {
return (this == obj);
}
equals()
方法存在有两种情况:
- 类没有重写
equals()
**方法:**通过equals()
比较该类的两个对象是,等价与“==”比较两个对象,使用的默认的Object
类的equals()
方法 - 类重写了
equals()
方法:一般我们都重写equals()
方法来比较两个对象中的属性是否相等;若属性相等,则返回true
,即认为这两个对象相等。
String类中的equals
方法是被重写过的,Object的equals方法是比较对象的地址,String的equals方法是比较对象的值。
当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和有创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中创建一个String对象
hashCode()
有什么用
hashCode()是获取哈希码(int
整数)。作用是确定该对象在哈希表中索引的位置
hashCode()定义在JDK的Object
类中,意味着任何类都包含hashCode()
方法,Object类的hashCode方法是native
方法。
当在set中插入元素的时候,如果元素太多,调用equals
方法效率很低,所以有了hashCode方法,它是根据对象的内存地址换算出一个值,当有新的元素有加入到set中,先调用hashCode方法,定位到对应的物理位置,如果位置上没有元素,就直接存储到这个位置,不用再比较了;如果这个位置已经有了元素,就调用它的equals方法
为什么重写equals
方法时必须重写hashCode
方法
因为两个相等的对象的hashCode
值必须是相等。也就是说如果equals
方法判断两个对象是相等的,那这两个对象的hashCode
值也要相等。
如果重写equals()
时没有重写hashCode()
方法的话就可能会导致equals
方法判断是相等的两个对象,hashCode
值却不相等。
equals
方法判断两个对象是相等的,那这两个对象的hashCode
值也要相等。- 两个对象有相同的
hashCode
值,他们也不一定相等**(哈希冲突)**。