🎄欢迎来到@边境矢梦°的csdn博文🎄
🎄本文主要梳理Java面试中JavaSE中会涉及到的知识点 🎄
🌈我是边境矢梦°,一个正在为秋招和算法竞赛做准备的学生🌈
🎆喜欢的朋友可以关注一下🫰🫰🫰,下次更新不迷路🎆
目录
🪴变量
🌴基本数据类型与包装类型
🍁运算符
🏵️数组
🌸方法
🌻面向对象编程
💐面对象和面向过程的区别
💐面向对象三大特征
🌷封装
🌷继承
🌷多态
💐接口和抽象类的共同点和区别
💐拷贝的区别
🌳Object
💐Object 类的常见方法有哪些?
💐== 和 equals() 的区别
💐hashCode() 方法
💐hashCode() 和 equals()
💐toString()
💐finalize()
🍓String
💐String不可变的原因
🏆Java SE(Java Platform,Standard Edition): Java 平台标准版,Java 编程语言的基础,它包含了支持 Java 应用程序开发和运行的核心类库以及虚拟机等核心组件。Java SE 可以用于构建桌面应用程序或简单的服务器应用程序。
🪴变量
成员变量 | 局部变量 | |
语法方式 | 从语法形式上看,成员变量是属于类的,可以被 public ,private ,static 等修饰符所修饰. | 局部变量是在代码块或方法中定义的变量或是方法的参数, 局部变量不能被访问控制修饰符及 static 所修饰;final可以修饰. |
存储方式 | 存储在对象的内存空间中, 对空间中. | 存储在栈内存中的局部变量表 |
生存空间 | 随着对象的创建而存在,随着对象的销毁而消亡 | 随着方法的调用而自动生成,随着方法的调用结束而消亡 |
默认值 | 成员变量如果没有被赋初始值,则会自动以类型的默认值而赋值 | 不会自动赋值 |
🌴基本数据类型与包装类型
Java 中有 8 种基本数据类型,分别为:
- 6 种数字类型:
- 4 种整数型:
byte
、short
、int
、long
- 2 种浮点型:
float
、double
- 4 种整数型:
- 1 种字符类型:
char
- 1 种布尔型:
boolean
。
基本类型 | 位数 | 字节 | 默认值 | 取值范围 |
---|---|---|---|---|
byte | 8 | 1 | 0 | -128~127 |
short | 16 | 2 | 0 | -32768(-2^15)~32767(2^15-1) |
int | 32 | 4 | 0 | -2147483648(-2^31)~2147483647(2^63 - 1) |
long | 64 | 8 | 0L | -9223372036854775808(-2^63) ~ 9223372036854775807(2^63 -1) |
char | 16 | 2 | 'u0000'(Unicode编码) | 0 ~ 65535(2^16 - 1) |
float | 32 | 4 | 0f | 1.4E-45 ~ 3.4028235E38 |
double | 64 | 8 | 0.0 | 4.9E-324 ~ 1.7976931348623157E308 |
boolean | 1 | false | true, false |
取值范围减 1 的原因 : 因为在二进制补码表示法中,最高位是用来表示符号的(0 表示正数,1 表示负数),其余位表示数值部分。
所以,如果我们要表示最大的正数,我们需要把除了最高位之外的所有位都设为 1。
如果我们再加 1,就会导致溢出,变成一个负数。
所以就是最高位为1, 再减 1 , 得到的就是取值范围。
基本数据类型 | 包装类型 | |
---|---|---|
用途 | 较少使用(工程中) | 较多使用(泛型中也可以使用) |
存储方式 | 基本数据类型的局部变量存放在 Java 虚拟机栈中的局部变量表中;基本数据类型的成员变量(未被 | 包装类型属于对象类型,几乎所有对象实例都存在于堆中。(如果是在缓存范围的,就在方法区中的运行时常量池中) |
占用空间 | 非常小 | 引用+实例所占的空间 |
默认值 | 基本类型有默认值且不是 null 。 | 成员变量包装类型不赋值就是 null |
比较方式 | 对于基本数据类型来说,== 比较的是值。 | 对于包装数据类型来说,== 比较的是对象的内存地址。所有整型包装类对象之间值的比较,全部使用 equals() 方法。 |
为什么说是几乎所有对象实例都存在于堆中呢? 这是因为 HotSpot 虚拟机引入了 JIT 优化之后,会对对象进行逃逸分析,如果发现某一个对象并没有逃逸到方法外部,那么就可能通过标量替换来实现栈上分配,而避免堆上分配内存。
⚠️ 注意:基本数据类型存放在栈中是一个常见的误区! 基本数据类型的存储位置取决于它们的作用域和声明方式。如果它们是局部变量,那么它们会存放在栈中;如果它们是成员变量,那么它们会存放在堆中。
包装类型的缓存机制
Java 基本数据类型的包装类型的大部分都用到了缓存机制来提升性能。
Byte
,Short
,Integer
,Long
这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character
创建了数值在 [0,127] 范围的缓存数据,Boolean
直接返回True
orFalse
。如果超出对应范围仍然会去创建新的对象,缓存的范围区间的大小只是在性能和资源之间的权衡。
两种浮点数类型的包装类
Float
,Double
并没有实现缓存机制。
public static Character valueOf(char c) {
if (c <= 127) { // must cache
return CharacterCache.cache[(int)c];
}
return new Character(c);
}
private static class CharacterCache {
private CharacterCache(){}
static final Character cache[] = new Character[127 + 1];
static {
for (int i = 0; i < cache.length; i++)
cache[i] = new Character((char)i);
}
}
在 Java 虚拟机中,静态变量会存储在方法区中,而
CharacterCache
作为Character
类的一个静态变量,也会存储在方法区。具体来说,CharacterCache
存储在运行时常量池中的Character
类的常量池项中。
自动装箱与拆箱
- 装箱:将基本类型用它们对应的引用类型包装起来;
- 拆箱:将包装类型转换为基本数据类型;
Integer i = 10
等价于Integer i = Integer.valueOf(10)
int n = i
等价于int n = i.intValue()
;
注意:如果频繁拆装箱的话,也会严重影响系统的性能。我们应该尽量避免不必要的拆装箱操作。
🍁运算符
- 二进制的表示都是原码, 运算都是补码
- 没有无符号左移
- 在运算的时候符号位主要体现在符号右移的时候, 其他像~, ^, |, !,&等逻辑运算的时候不会在意符号位(一视同仁), 直接进行运算
🏵️数组
- 一维数组使用方式
- 动态初始化 :数组类型 数组名[ ] = new 数组类型[大小];
- 静态初始化 :数组类型 数组名[ ] = {x, y, z};
- 注意事项:
数组创建后,如果没有赋值,有默认值int 0 , short 0, byte 0, long 0, float 0.0,double 0.0 , char \u0000 , boolean false , String null 数组赋值机制 :栈中存数组首地址, 堆中存数组数据
- 二维数组
可以这样理解,原来的一维数组的每个元素是一维数组 , 就构成二维数组- 二维数组的使用方式
- 方式一 :动态初始化 → 类型[][] 数组名=new 类型[大小][大小]
- 方式二 :动态初始化 → 类型[][] 数组名=new 类型[大小][]
- 方式三 :静态初始化 → 类型 数组名[][] = {{值 1,值 2..},{值 1,值 2..},{值 1,值 2..}}
- 注意事项
- 二维数组实际上是由多个一维数组组成的,它的各个一维数组的长度可以相同,也可以不相同
🌸方法
重载和重写有什么区别?
重载就是同样的一个方法能够根据输入数据的不同,做出不同的处理
重写就是当子类继承自父类的相同方法,输入数据一样,但要做出有别于父类的响应时,你就要覆盖父类方法
重载
发生在同一个类中(或者父类和子类之间),方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同。
《Java 核心技术》这本书是这样介绍重载的:
如果多个方法(比如
StringBuilder
的构造方法)有相同的名字、不同的参数, 便产生了重载。
编译器必须挑选出具体执行哪个方法,它通过用各个方法给出的参数类型与特定方法调用所使用的值类型进行匹配来挑选出相应的方法。 如果编译器找不到匹配的参数, 就会产生编译时错误, 因为根本不存在匹配, 或者没有一个比其他的更好(这个过程被称为重载解析(overloading resolution))。
Java 允许重载任何方法, 而不只是构造器方法。
综上:重载就是同一个类中多个同名方法根据不同的传参来执行不同的逻辑处理。
重写
重写发生在运行期,是子类对父类的允许访问的方法的实现过程进行重新编写。
- 方法名、参数列表必须相同,子类方法返回值类型应比父类方法返回值类型更小或相等,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。
- 如果父类方法访问修饰符为
private/final/static
则子类就不能重写该方法,但是被static
修饰的方法能够被再次声明。 - 构造方法无法被重写
总结
综上:重写就是子类对父类方法的重新改造,外部样子不能改变,内部逻辑可以改变。
区别点 | 重载方法 | 重写方法 |
---|---|---|
发生范围 | 同一个类 | 子类 |
参数列表 | 必须修改 | 一定不能修改 |
返回类型 | 可修改 | 子类方法返回值类型应比父类方法返回值类型更小或相等 |
异常 | 可修改 | 子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等; |
访问修饰符 | 可修改 | 一定不能做更严格的限制(可以降低限制) |
发生阶段 | 编译期 | 运行期 |
方法的重写要遵循“两同两小一大”(以下内容摘录自《疯狂 Java 讲义》,issue#892open in new window ):
- “两同”即方法名相同、形参列表相同;
- “两小”指的是子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等;
- “一大”指的是子类方法的访问权限应比父类方法的访问权限更大或相等。
⭐️ 关于 重写的返回值类型 这里需要额外多说明一下,上面的表述不太清晰准确:如果方法的返回类型是 void 和基本数据类型,则返回值重写时不可修改。但是如果方法的返回值是引用类型,重写时是可以返回该引用类型的子类的。
🌻面向对象编程
💐面对象和面向过程的区别
两者的主要区别在于解决问题的方式不同:
- 面向过程把解决问题的过程拆成一个个方法,通过一个个方法的执行解决问题。
- 面向对象会先抽象出对象,然后用对象执行方法的方式解决问题。
另外,面向对象开发的程序一般更易维护、易复用、易扩展。
相关 issue : 面向过程:面向过程性能比面向对象高??open in new window 。
💐面向对象三大特征
🌷封装
封装是指把一个对象的状态信息(也就是属性)隐藏在对象内部,不允许外部对象直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法来操作属性。如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。
🌷继承
不同类型的对象,相互之间经常有一定数量的共同点。例如,小明同学、小红同学、小李同学,都共享学生的特性(班级、学号等)。同时,每一个对象还定义了额外的特性使得他们与众不同。例如小明的数学比较好,小红的性格惹人喜爱;小李的力气比较大。继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承,可以快速地创建新的类,可以提高代码的重用,程序的可维护性,节省大量创建新类的时间 ,提高我们的开发效率。
关于继承如下 3 点请记住:
- 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有。
- 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
- 子类可以用自己的方式实现父类的方法。(以后介绍)。
🌷多态
多态,顾名思义,表示一个对象具有多种的状态,具体表现为父类的引用指向子类的实例。
多态的特点:
- 对象类型和引用类型之间具有继承(类)/实现(接口)的关系;
- 引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定;
- 多态不能调用“只在子类存在但在父类不存在”的方法;
- 如果子类重写了父类的方法,真正执行的是子类覆盖的方法,如果子类没有覆盖父类的方法,执行的是父类的方法。
💐接口和抽象类的共同点和区别
共同点:
- 都不能被实例化。
- 都可以包含抽象方法。
- 都可以有默认实现的方法(Java 8 可以用
default
关键字在接口中定义默认方法)。
区别:
- 接口主要用于对类的行为进行约束,你实现了某个接口就具有了对应的行为。抽象类主要用于代码复用,强调的是所属关系。
- 一个类只能继承一个类,但是可以实现多个接口。
- 接口中的成员变量只能是
public static final
类型的,不能被修改且必须有初始值,而抽象类的成员变量默认 default,可在子类中被重新定义,也可被重新赋值。
💐拷贝的区别
图片来自 :Java基础常见面试题总结(中) | JavaGuide
🌳Object
💐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)
/**
* native 方法,用于创建并返回当前对象的一份拷贝。
*/
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() 的区别
==
对于基本类型和引用类型的作用效果是不同的:
- 对于基本数据类型来说,
==
比较的是值。 - 对于引用数据类型来说,
==
比较的是对象的内存地址。
因为 Java 只有值传递,所以,对于 == 来说,不管是比较基本数据类型,还是引用数据类型的变量,其本质比较的都是值,只是引用类型变量存的值是对象的地址。
equals()
不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。equals()
方法存在于Object
类中,而Object
类是所有类的直接或间接父类,因此所有的类都有equals()
方法。
Object
类 equals()
方法:
public boolean equals(Object obj) {
return (this == obj);
}
equals()
方法存在两种使用情况:
- 类没有重写
equals()
方法:通过equals()
比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是Object
类equals()
方法。 - 类重写了
equals()
方法:一般我们都重写equals()
方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。
举个例子(这里只是为了举例。实际上,你按照下面这种写法的话,像 IDEA 这种比较智能的 IDE 都会提示你将 ==
换成 equals()
):
String a = new String("ab"); // a 为一个引用
String b = new String("ab"); // b为另一个引用,对象的内容一样
String aa = "ab"; // 放在常量池中
String bb = "ab"; // 从常量池中查找
System.out.println(aa == bb);// true
System.out.println(a == b);// false
System.out.println(a.equals(b));// true
System.out.println(42 == 42.0);// true
String
中的 equals
方法是被重写过的,因为 Object
的 equals
方法是比较的对象的内存地址,而 String
的 equals
方法比较的是对象的值。
字面量创建的字符串会共享常量池中的对象,而通过new
关键字创建的字符串则是独立的对象,即使内容相同也会在堆内存中创建新的对象。
String
类equals()
方法:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
💐hashCode() 方法
1) 提高具有哈希结构的容器的效率!2) 两个引用,如果指向的是同一个对象,则哈希值肯定是一样的!3) 两个引用,如果指向的是不同对象,则哈希值是不一样的4) 哈希值主要根据地址号来的!!!, 不能完全将哈希值等价于地址。
💐hashCode() 和 equals()
用于判断对象是否相等
重写的equals方法加上重写的hashCode()方法, 先判断地址, 再判断内容
- 如果两个对象的
hashCode
值相等,那这两个对象不一定相等(哈希碰撞)。- 如果两个对象的
hashCode
值相等并且equals()
方法也返回true
,我们才认为这两个对象相等。- 如果两个对象的
hashCode
值不相等,我们就可以直接认为这两个对象不相等。
💐toString()
- 基本介绍
- 默认返回:全类名+@+哈希值的十六进制,【查看 Object 的 toString 方法】
- 子类往往重写 toString 方法,用于返回对象的属性信息
- 重写 toString 方法,打印对象或拼接对象时,都会自动调用该对象的 toString 形式.
💐finalize()
- 当对象被回收时,系统自动调用该对象的 finalize 方法。子类可以重写该方法,做一些释放资源的操作。
- 什么时候被回收:当某个对象没有任何引用时,则 jvm 就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,在销毁该对象前,会先调用 finalize 方法。
- 垃圾回收机制的调用,是由系统来决定(即有自己的 GC 算法), 也可以通过 System.gc() 主动触发垃圾回收机制。
🍓String
String | StringBuffer | StringBuilder | |
---|---|---|---|
可变性 | 不可变 | 可变 | 可变 |
线程安全性 | 安全 | 安全 | 不安全 |
性能 | 慢 | 比String快 | 比StringBuffer快10%~15% |
- 使用建议
- 操作少量的数据: 适用
String
- 单线程操作字符串缓冲区下操作大量数据: 适用
StringBuilder
- 多线程操作字符串缓冲区下操作大量数据: 适用
StringBuffer
💐String不可变的原因
public final class ImmutableDemo {
private final int[] myArray;
public ImmutableDemo(int[] array) {
// this.myArray = array; wrong
this.myArray = array.clone(); // 采用深度copy来创建一个新的对象保证不会通过传入的array来修改myArray的数组元素
}
}