🗓 Day01
📖 day01 大纲
1️⃣ JDK、JRE、JVM 之间的区别
2️⃣ hashCode() 与 equals() 之间的关系
3️⃣ String、StringBuffer、StringBuilder 的区别
4️⃣ 泛型中 extends 和 super 的区别
5️⃣ == 和 equals() 方法的区别
📑 JDK、JRE、JVM 之间的区别
JDK(Java SE Development Kit)
- Java 标准开发包,它提供了编译、运行 Java 程序所需的各种工具和资源,包括 Java 编译器、Java运行时环境,以及常用的 Java 类库等
JRE(Java Runtime Environment)
- Java 运行环境,用于运行Java 的字节码文件。JRE 中包括了 JVM 以及 JVM 工作所需要的类库,普通用户而只需要安装 JRE 来运行 Java 程序,而程序开发者 必须安装 JDK 来编译、调试程序。
JVM(Java Virtual Machine)
- Java 虚拟机 ,是 JRE 的一部分,它是整个 Java 实现跨平台的最核心的部分,负责运行字节码文件。
📚 具体流程
我们写 Java 代码,用 txt 就可以写,但是写出来的 Java 代码,想要运行,需要先编译成字节码,那就需要编译器,而 JDK 中就包含了编译器 javac ,编译之后的字节码,想要运行,就需要一个可以执行字节码的程序,这个程序就是 JVM (Java 虚拟机),专门用来执行 Java 字节码的。
📚 注意点
1️⃣ 如果我们要开发 Java 程序,那就需要 JDK ,因为要编译 Java 源文件
2️⃣ 如果我们只想运行已经编译好的 Java 字节码文件 ,就是 *.class 文件,那么就只需要 JRE
3️⃣ JDK 中包含了 JRE ,JRE 中包含了 JVM
4️⃣ 另外,JVM 在执行 Java 字节码时,需要把字节码解释为 机器指令,而不同的操作系统的机器指令有可能不一样,所以就导致不同操作系统上的 JVM 是不一样的,所以我们在安装 JDK 时 需要选择操作系统。
5️⃣ JVM 是用来执行 Java 字节码的,所以凡是某个代码编译之后是 Java 字节码,那就都能在 JVM 上运行。
📑 hashCode() 与 equals() 之间的关系
Demo 示例
User.java
/***
* @author: Alascanfu
* @date : Created in 2022/6/30 13:08
* @description: User 实体类 用于演示 hashCode() 与 equals() 之间的关系
* @modified By: Alascanfu
**/
public class User {
private String name ;
public User(String name ){
this.name = name ;
}
public String getName(){
return name ;
}
/**
* 如果两个 User 对象的 name 属性 相同则认为二者 equals
* */
@Override
public boolean equals(Object obj) {
User user = (User) obj;
return user.getName().equals(this.name);
}
}
Main.java
/***
* @author: Alascanfu
* @date : Created in 2022/6/30 13:12
* @description: Main 类 用于演示 hashCode() 与 equals() 之间的关系
* @modified By: Alascanfu
**/
public class Main {
public static void main(String[] args) {
HashMap<User, String> map = new HashMap<>();
map.put(new User("Alascanfu"),"201901094106");
System.out.println(map.get(new User("Alascanfu"))); // null
}
}
运行结果
❓ 这里拿到的是 null ,这是为什么呢?
通过查看源码
HashMap 中的 get()
方法
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
实际上 在 map 的 get()
方法实际首先会通过 getNode()
去判断二者的 hashCode() 是否相同。
final Node<K,V> getNode(int hash, Object key)
do {
// 这里就是判断 两个 对象的 hash 值是否相等
// 注意这里是 && 短路逻辑与 是先要进行判断 hash 值是否相等 若相等才会进行后续判断
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
上面提出了 hash 值并非一致相等 ,hash值是根据对象进行所返回的一个 hashCode
- 故我们需要对User 对象中的 hashCode() 方法进行重写,针对 name 属性的值生成对应的 hashCode。这样就保证 name 相同的值 生成的 hashCode 也是相同的。
User.java 中重写 hashCode()
方法
@Override
public int hashCode() {
return name.hashCode();
}
此时再去运行测试运行
📚 理论知识如何在面试中回答?—— ⭐ 背这里的
-
如果两个对象的 hashCode 不相同,那么这两个 对象肯定是不同的两个对象(即
equals()
一定返回 false)。 -
如果两个对象的 hashCode 相同,不代表这两个对象一定是同一个对象,也可能是两个对象。(即
equals()
可能返回 true 可能 返回 false) -
如果两个对象的相等,那么他们的 hashCode 就一定相同。
扩展
在 Java 的一些集合类的实现中,在比较两个对象是否相等时,会根据上面的原则,先调用对象的 hashCode 进行比较,如果不相同,直接认为这两个对象不相同,如果 hashCode 相同,那么就会进一步调用 equals()
方法进行比较。而 equals 方法,就是用来最终确定两个对象是否相等,通常 equals()
方法的实现会比较重,逻辑比较多 ,而 hashCode()
主要就是得到一个 哈希值,实际上就是一串数字,所以在比较两个对象是否相等时,通常会先根据 hashCode 进行粗略比较。
📚 注意点:所以如果我们重写了 equals()
方法,那么就要注意 hashCode()
方法,一定要保证能遵守上述规则。
📑 String、StringBuffer、StringBuilder 的区别
1️⃣ String 是不可变的 ,如果尝试去修改就会生成一个字符串对象,StringBuilder 与 StringBuffer 是可变的。
2️⃣ StringBuffer 是线程安全的,StringBuilder 是线程不安全的,所以在单线程环境下 StringBuilder 更高效。
**为什么 String 是不可变的 => **String为什么是不可变的
StringBuffer 为什么是线程安全的?——因为其中的每个方法都加上了 synchronized 同步关键字,进而每个方法都是同步方法,保证了StringBuffer的线程安全性。
📑 泛型中 extends 和 super 的区别
1️⃣ <? extends T>
:表示包括 T 在内的任何 T 的子类。
2️⃣ <? super ?>
: 表示包括 T 在内的任何 T 的父类。
快速了解泛型
MyList.java
/***
* @author: Alascanfu
* @date : Created in 2022/6/30 14:30
* @description: MyList 类用于快速了解理解泛型
* @modified By: Alascanfu
**/
public class MyList<E> {
// ...
public void add(E data){
// ...
}
// ...
}
📚 在类上标注了泛型 <E>
用于让用户指定 E 代表的数据类型(即 在类中 E 就代表传入的数据类型)——可以是八大基本数据类型,也可以是引用数据类型。
Main.java
public class Main {
public static void main(String[] args) {
// 这里指明泛型类型为 BigDecimal 这个精度类 jdk 中自带的
MyList<BigDecimal> myList = new MyList<>();
// 使用的时候 参数类型也就泛化为 指明的泛型类型 BigDecimal
myList.add(new BigDecimal(1));
}
}
根据修改泛化extends 或 super 类型来指明其泛化的范围
例如此时将其修改为 extends number
public class MyList<E extends Number> {
// ...
public void add(E data){
// ...
}
// ...
}
那么说明我们用户在指定类型泛化时 只可以是 Number 这个抽象类本身或其子类类型
此时编译报错——Type parameter 'java.lang.String' is not within its bound; should extend 'java.lang.Number'
此时就是合法的。
当然我们也可以进行测试判断对个对象的类是否相同?
public class Main {
public static void main(String[] args) {
MyList<Integer> myList1 = new MyList<>();
myList1.add(1);
MyList<Double> myList2 = new MyList<>();
myList2.add(1.0);
System.out.println(myList1.getClass().equals(myList2.getClass()));// true
}
}
这里的返回结果是 true 说明在通过对象获取其类 Class 时 是忽略掉泛型的。
📑 == 和 equals() 的区别
1️⃣ == :如果是基本数据类型,比较是值,如果是引用数据类型,比较的是引用地址
2️⃣ equals: 具体看各个类重写 equals 方法之后的逻辑比较,比如 String 类,虽然是 引用数据类型,但是 String 中重写了 equals 方法,方法内部比较的是字符串中的各个字符是否全部相等。
String.equals() 方法源码
public boolean equals(Object anObject) {
// 如果是基本数据类型值相等 就返回 true
if (this == anObject) {
return true;
}
// 如果其类型是 String 类型的对象则在进行比较
if (anObject instanceof String) {
// 强制类型转换用于后续判断
String anotherString = (String)anObject;
// 获取 原字符串的长度
int n = value.length;
// 判断字符长度是否相等
if (n == anotherString.value.length) {
// 将两个 String 对象的字符数组中的每个字符依次比较
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;
}