2025-02-18 Notes
2025-02-17
知识点
运算符
++和--:符号在前就先加/减,符号在后就后加/减。
^ 异或 ~ 取反
>>= 和<<= 都是赋值运算
switch
- switch 语句中的变量类型可以是: byte、short、int 或者 char。从 Java SE 7 开始,switch 支持字符串 String 类型了,同时 case 标签必须为字符串常量或字面量。
- switch 语句可以拥有多个 case 语句。每个 case 后面跟一个要比较的值和冒号。
Java Number和Math类
所有的包装类(Integer、Long、Byte、Double、Float、Short)都是抽象类 Number 的子类。
Math 的方法都被定义为 static 形式,通过 Math 类可以在主函数中直接调用。
Number常用函数:
xxxValue(),将 Number 对象转换为xxx数据类型的值并返回。,如Integer->int
compareTo(),equals(),valueOf(),parseInt(),
Math常用函数:
abs(),min(),max() , sqrt() , pow(2,3) (即2的3次方 ),ceil() , foor() , round(), random() (不是rondom)
floor,round 和 ceil 方法实例比较
参数 | Math.floor | Math.round | Math.ceil |
---|---|---|---|
1.4 | 1 | 1 | 2 |
1.5 | 1 | 2 | 2 |
1.6 | 1 | 2 | 2 |
-1.4 | -2 | -1 | -1 |
-1.5 | -2 | -1 | -1 |
-1.6 | -2 | -2 | -1 |
Character
\, \", \'这三个转义字符
常用方法:
isLetter(): 是否是一个字母
isDigit(): 是否是一个数字字符
isUpperCase(): 是否是大写字母
isLowerCase(): 是否是小写字母
toUpper/LoweCase()
大写字母ASCII范围[65,90], 小写字母+32
String
String 类是不可改变的,所以你一旦创建了 String 对象,那它的值就无法改变了
String 创建的字符串存储在公共池中,而 new 创建的字符串对象在堆上:
String s1 = "Runoob"; // String 直接创建
String s2 = "Runoob"; // String 直接创建
String s3 = s1; // 相同引用
String s4 = new String("Runoob"); // String 对象创建
String s5 = new String("Runoob"); // String 对象创建
常用函数:
length(), charAt(), equals() ,equalsIgnoreCase() , indexOf() ,lastIndexof() , substring() , toCharArray() ,toUpper/LowerCase() ,isEmpty() , contains() , valueOf() ,
trim(): 去掉前导空白和尾部空白,返回新字符串
replace(char oldChar, char newChar): 返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。
replaceAll(String regex, String replacement):使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
matches() 是否匹配给定的正则表达式
compareTo(str1,str2): 按字典顺序比较两个字符串
split():根据给定正则表达式的匹配拆分此字符串,返回字符串数组String[],
starsWith() ,endsWith():此字符串是否以指定的前缀/后缀开始。
StringBuffer和StringBuilder
当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。
和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。
StringBuilder 类在 Java 5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。
由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。
以下是 StringBuffer 类支持的主要方法:
序号 | 方法描述 |
---|---|
1 | public StringBuffer append(String s) 将指定的字符串追加到此字符序列。 |
2 | public StringBuffer reverse() 将此字符序列用其反转形式取代。 |
3 | public delete(int start, int end) 移除此序列的子字符串中的字符。 |
4 | public insert(int offset, int i) 将 int 参数的字符串表示形式插入此序列中。 |
5 | insert(int offset, String str) 将 str 参数的字符串插入此序列中。 |
6 | replace(int start, int end, String str) 使用给定 String 中的字符替换此序列的子字符串中的字符。 |
7 | void setLength(int newLength) 设置字符序列的长度。 |
8 | char charAt(int index):返回此序列中指定索引处的 char 值。 |
9 | void setCharAt(int index, char ch)将给定索引处的字符设置为 ch 。 |
以及String中有的函数
数组和Arrays类
java.util.Arrays 类能方便地操作数组,它提供的所有方法都是静态的。
具有以下功能:
- 给数组赋值:通过 fill 方法。
- 对数组排序:通过 sort 方法,按升序。
- 比较数组:通过 equals 方法比较数组中元素值是否相等。
- 查找数组元素:通过 binarySearch 方法能对排序好的数组进行二分查找法操作。
具体说明请查看下表:
序号 | 方法和说明 |
---|---|
1 | public static int binarySearch(Object[] a, Object key) 用二分查找算法在给定数组中搜索给定值的对象(Byte,Int,double等)。数组在调用前必须排序好的。如果查找值包含在数组中,则返回搜索键的索引;否则返回 (-(插入点) - 1)。 |
2 | public static boolean equals(long[] a, long[] a2) 如果两个指定的 long 型数组彼此相等,则返回 true。如果两个数组包含相同数量的元素,并且两个数组中的所有相应元素对都是相等的,则认为这两个数组是相等的。换句话说,如果两个数组以相同顺序包含相同的元素,则两个数组是相等的。同样的方法适用于所有的其他基本数据类型(Byte,short,Int等)。 |
3 | public static void fill(int[] a, int val) 将指定的 int 值分配给指定 int 型数组指定范围中的每个元素。同样的方法适用于所有的其他基本数据类型(Byte,short,Int等)。 |
4 | public static void sort(Object[] a) 对指定对象数组根据其元素的自然顺序进行升序排列。同样的方法适用于所有的其他基本数据类型(Byte,short,Int等)。 |
Date类和Thread.sleep()
java.util 包提供了 Date 类来封装当前的日期和时间。 Date 类提供两个构造函数来实例化 Date 对象。第一个构造函数使用当前日期和时间来初始化对象。
import java.util.Date;
public class DateDemo {
public static void main(String[] args) {
// 初始化 Date 对象
Date date = new Date();
// 使用 toString() 函数显示日期时间
System.out.println(date.toString());
}
}
sleep()使当前线程进入停滞状态(阻塞当前线程),让出CPU的使用、目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会。
你可以让程序休眠一毫秒的时间或者到您的计算机的寿命长的任意段时间。例如,下面的程序会休眠3秒:
try{
System.out.println(new Date()+ "\n");
Thread.sleep(1000*3) //休眠3秒
System.out.println(new Date()+ "\n");
}catch(Exception e){
Syetem.out.println("Got an exception");
}
正则表达式
正则表达式定义了字符串的模式。
正则表达式可以用来搜索、编辑或处理文本。
正则表达式并不仅限于某一种语言,但是在每种语言中有细微的差别。
Java 提供了 java.util.regex 包,它包含了 Pattern 和 Matcher 类,用于处理正则表达式的匹配操作。
正则表达式实例
一个字符串其实就是一个简单的正则表达式,例如 Hello World 正则表达式匹配 "Hello World" 字符串。
.(点号)也是一个正则表达式,它匹配任何一个字符如:"a" 或 "1"。
下表列出了一些正则表达式的实例及描述:
正则表达式 | 描述 |
---|---|
this is text | 匹配字符串 "this is text" |
this\s+is\s+text | 注意字符串中的 \s+。匹配单词 "this" 后面的 \s+ 可以匹配多个空格,之后匹配 is 字符串,再之后 \s+ 匹配多个空格然后再跟上 text 字符串。可以匹配这个实例:this is text |
^\d+(.\d+)? | ^ 定义了以什么开始\d+ 匹配一个或多个数字? 设置括号内的选项是可选的. 匹配 "."可以匹配的实例:"5", "1.5" 和 "2.21"。 |
java.util.regex 包
java.util.regex 包是 Java 标准库中用于支持正则表达式操作的包。
java.util.regex 包主要包括以下三个类:
-
Pattern 类:
pattern 对象是一个正则表达式的编译表示。Pattern 类没有公共构造方法。要创建一个 Pattern 对象,你必须首先调用其公共静态编译方法,它返回一个 Pattern 对象。该方法接受一个正则表达式作为它的第一个参数。
-
Matcher 类:
Matcher 对象是对输入字符串进行解释和匹配操作的引擎。与Pattern 类一样,Matcher 也没有公共构造方法。你需要调用 Pattern 对象的 matcher 方法来获得一个 Matcher 对象。
-
PatternSyntaxException:
PatternSyntaxException 是一个非强制异常类,它表示一个正则表达式模式中的语法错误
方法
方法重载: 相同名字但参数不同的方法,重载的方法必须拥有不同的参数列表。你不能仅仅依据修饰符或者返回类型的不同来重载方法。
可变参数:
JDK 1.5 开始,Java支持传递同类型的可变参数给一个方法。
方法的可变参数的声明如下所示:
typeName... parameterName
在方法声明中,在指定参数类型后加一个省略号(...) 。
一个方法中只能指定一个可变参数,它必须是方法的最后一个参数。任何普通的参数必须在它之前声明。
public class VarargsDemo {
public static void main(String[] args) {
// 调用可变参数的方法
printMax(34, 3, 3, 2, 56.5);
printMax(new double[]{1, 2, 3});
}
public static void printMax( double... numbers) {
if (numbers.length == 0) {
System.out.println("No argument passed");
return;
}
double result = numbers[0];
for (int i = 1; i < numbers.length; i++){
if (numbers[i] > result) {
result = numbers[i];
}
}
System.out.println("The max value is " + result);
}
}
以上实例编译运行结果如下:
The max value is 56.5
The max value is 3.0
Java 流(Stream)、文件(File)和IO
流可以认为是一个数据的序列, 输入流表示从一个源读取数据,输出流表示向一个目标写数据。
Java Scanner类
用法和例子见Java Scanner 类 | 菜鸟教程
主要是hashNext(),和sc.next(), hashNextLine()和nextLine(), hashInt()和nextInt(), hashDouble()和nextDouble()
Java异常处理
异常发生的原因有很多,通常包含以下几大类:
- 用户输入了非法数据。
- 要打开的文件不存在。
- 网络通信时连接中断,或者JVM内存溢出。
这些异常有的是因为用户错误引起,有的是程序错误引起的,还有其它一些是因为物理错误引起的。
要理解 Java 异常处理是如何工作的,你需要掌握以下三种类型的异常:
-
检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这些异常在编译时强制要求程序员处理。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。
这类异常通常使用 try-catch 块来捕获并处理异常,或者在方法声明中使用 throws 子句声明方法可能抛出的异常。
try { // 可能会抛出异常的代码 } catch (IOException e) { // 处理异常的代码 }
或者:
public void readFile() throws IOException { // 可能会抛出IOException的代码 }
-
运行时异常: 这些异常在编译时不强制要求处理,通常是由程序中的错误引起的,例如 NullPointerException、ArrayIndexOutOfBoundsException 等,这类异常可以选择处理,但并非强制要求。
try { // 可能会抛出异常的代码 } catch (NullPointerException e) { // 处理异常的代码 }
-
错误: 错误不是异常,而是脱离程序员控制的问题,错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。
Java 提供了以下关键字和类来支持异常处理:
- try:用于包裹可能会抛出异常的代码块。
- catch:用于捕获异常并处理异常的代码块。
- finally:用于包含无论是否发生异常都需要执行的代码块。
- throw:用于手动抛出异常。
- throws:用于在方法声明中指定方法可能抛出的异常。
- Exception类:是所有异常类的父类,它提供了一些方法来获取异常信息,如 getMessage()、printStackTrace() 等。
Exception 类的层次
所有的异常类是从 java.lang.Exception 类继承的子类。
Exception 类是 Throwable 类的子类。除了Exception类外,Throwable还有一个子类Error 。
Java 程序通常不捕获错误。错误一般发生在严重故障时,它们在Java程序处理的范畴之外。
Error 用来指示运行时环境发生的错误。
例如,JVM 内存溢出。一般地,程序不会从错误中恢复。
异常类有两个主要的子类:IOException 类和 RuntimeException 类。
异常方法
下面的列表是 Throwable 类的主要方法:
序号 | 方法及说明 |
---|---|
1 | public String getMessage() 返回关于发生的异常的详细信息。这个消息在Throwable 类的构造函数中初始化了。 |
2 | public Throwable getCause() 返回一个 Throwable 对象代表异常原因。 |
3 | public String toString() 返回此 Throwable 的简短描述。 |
4 | public void printStackTrace() 将此 Throwable 及其回溯打印到标准错误流。 |
5 | public StackTraceElement [] getStackTrace() 返回一个包含堆栈层次的数组。下标为0的元素代表栈顶,最后一个元素代表方法调用堆栈的栈底。 |
6 | public Throwable fillInStackTrace() 用当前的调用栈层次填充Throwable 对象栈层次,添加到栈层次任何先前信息中。 |
自定义异常类, 用到时需要手动用throw抛出
继承
Java 不支持多继承,但支持多重继承。
使用 implements 关键字可以变相的使java具有多继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口(接口跟接口之间采用逗号分隔)。
重写(Override)
重写(Override)是指子类定义了一个与其父类中具有相同名称、参数列表和返回类型的方法,并且子类方法的实现覆盖了父类方法的实现。 即外壳不变,核心重写!
重写时建议使用@Override注解,防止方法名错误导致覆盖,便于阅读
多态
多态是同一个行为具有多个不同表现形式或形态的能力。
多态就是同一个接口,使用不同的实例而执行不同操作,例如打印机,彩色打印机和黑白打印机
多态存在的三个必要条件
- 继承
- 重写
- 父类引用指向子类对象:Parent p = new Child();
多态的实现方式
方式一:重写:
方式二:接口
方式三:抽象类和抽象方法
接口
接口与类相似点:
- 一个接口可以有多个方法。
- 接口文件保存在 .java 结尾的文件中,文件名使用接口名。
- 接口的字节码文件保存在 .class 结尾的文件中。
- 接口相应的字节码文件必须在与包名称相匹配的目录结构中。
接口与类的区别:
- 接口不能用于实例化对象。
- 接口没有构造方法。
- 接口中所有的方法必须是抽象方法,Java 8 之后 接口中可以使用 default 关键字修饰的非抽象方法。
- 接口不能包含成员变量,除了 static 和 final 变量。
- 接口不是被类继承了,而是要被类实现。
- 接口支持多继承。
接口特性
- 接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。
- 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。
- 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。
抽象类和接口的区别
- \1. 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
- \2. 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
- \3. 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
- \4. 一个类只能继承一个抽象类,而一个类却可以实现多个接口。
注:JDK 1.8 以后,接口里可以有静态方法和方法体了。
注:JDK 1.8 以后,接口允许包含具体实现的方法,该方法称为"默认方法",默认方法使用 default 关键字修饰。更多内容可参考 Java 8 默认方法。
注:JDK 1.9 以后,允许将方法定义为 private,使得某些复用的代码不会把方法暴露出去。更多内容可参考 Java 9 私有接口方法。
标记接口
最常用的标记接口是没有包含任何方法的接口。
标记接口是没有任何方法和属性的接口.它仅仅表明它的类属于一个特定的类型,供其他代码来测试允许做一些事情。
标记接口作用:简单形象的说就是给某个对象打个标(盖个戳),使对象拥有某个或某些特权。
例如:java.awt.event 包中的 MouseListener 接口继承的 java.util.EventListener 接口定义如下:
package java.util; public interface EventListener {}
没有任何方法的接口被称为标记接口。标记接口主要用于以下两种目的:
-
建立一个公共的父接口:
正如EventListener接口,这是由几十个其他接口扩展的Java API,你可以使用一个标记接口来建立一组接口的父接口。例如:当一个接口继承了EventListener接口,Java虚拟机(JVM)就知道该接口将要被用于一个事件的代理方案。
-
向一个类添加数据类型:
这种情况是标记接口最初的目的,实现标记接口的类不需要定义任何接口方法(因为标记接口根本就没有方法),但是该类通过多态性变成一个接口类型。
枚举enum
enum Color
{
RED, GREEN, BLUE;
}
public class Test
{
// 执行输出结果
public static void main(String[] args)
{
Color c1 = Color.RED;
System.out.println(c1); //输出 RED
}
}
每个枚举都是通过 Class 在内部实现的,且所有的枚举值都是 public static final 的。
以上的枚举类 Color 转化在内部类实现:
class Color
{
public static final Color RED = new Color();
public static final Color BLUE = new Color();
public static final Color GREEN = new Color();
}
枚举类常应用于 switch 语句中:
enum Color
{
RED, GREEN, BLUE;
}
public class MyClass {
public static void main(String[] args) {
Color myVar = Color.BLUE;
switch(myVar) {
case RED:
System.out.println("红色");
break;
case GREEN:
System.out.println("绿色");
break;
case BLUE:
System.out.println("蓝色");
break;
}
}
}
values(), ordinal() 和 valueOf() 方法
enum 定义的枚举类默认继承了 java.lang.Enum 类,并实现了 java.lang.Serializable 和 java.lang.Comparable 两个接口。
values(), ordinal() 和 valueOf() 方法位于 java.lang.Enum 类中:
- values() 返回枚举类中所有的值。
- ordinal()方法可以找到每个枚举常量的索引,就像数组索引一样。
- valueOf()方法返回指定字符串值的枚举常量。
enum Color
{
RED, GREEN, BLUE;
}
public class Test
{
public static void main(String[] args)
{
// 调用 values()
Color[] arr = Color.values();
// 迭代枚举
for (Color col : arr)
{
// 查看索引
System.out.println(col + " at index " + col.ordinal());
}
// 使用 valueOf() 返回枚举常量,不存在的会报错 IllegalArgumentException
System.out.println(Color.valueOf("RED"));
// System.out.println(Color.valueOf("WHITE"));
}
}
输出结果为:
RED at index 0
GREEN at index 1
BLUE at index 2
RED
Java 反射(Reflection)
Java 反射(Reflection)是一个强大的特性,它允许程序在运行时查询、访问和修改类、接口、字段和方法的信息。反射提供了一种动态地操作类的能力,这在很多框架和库中被广泛使用,例如Spring框架的依赖注入。
反射 API
Java 的反射 API 提供了一系列的类和接口来操作 Class 对象。主要的类包括:
java.lang.Class
:表示类的对象。提供了方法来获取类的字段、方法、构造函数等。java.lang.reflect.Field
:表示类的字段(属性)。提供了访问和修改字段的能力。java.lang.reflect.Method
:表示类的方法。提供了调用方法的能力。java.lang.reflect.Constructor
:表示类的构造函数。提供了创建对象的能力。
工作流程
- 获取
Class
对象:首先获取目标类的Class
对象。 - 获取成员信息:通过
Class
对象,可以获取类的字段、方法、构造函数等信息。 - 操作成员:通过反射 API 可以读取和修改字段的值、调用方法以及创建对象。
AI解惑
IDEA新建项目时选择JDK和Java版本可以不同吗
在 IntelliJ IDEA 中新建项目时,JDK(Project SDK) 和 Java 版本(Language Level) 是两个独立的配置项,它们可以不同,但需要满足一定的兼容性要求。
- JDK(Project SDK)
- 这是指项目运行和编译所依赖的具体 JDK 版本。
- 它决定了项目实际使用的 Java 虚拟机和工具链。
- 例如:选择 JDK 8 或 JDK 17。
- Java 版本(Language Level)
- 这是指代码中允许使用的 Java 语言特性。
- 它定义了语法级别的限制,例如是否可以使用 Lambda 表达式、记录类、模式匹配等新特性。
- Language Level 的值不能高于所选 JDK 的版本。
是否可以不同?
是的,可以不同。例如:
- 你可以选择 JDK 17 作为 Project SDK。
- 同时将 Language Level 设置为 8(Java 8)。
这种配置常见于以下场景:
- 团队协作:项目需要兼容旧版本的 Java,但开发环境支持最新 JDK。
- 逐步升级:项目正在从旧版本迁移到新版本,暂时保留较低的 Language Level
Java中常见的compareTo()和equals()
在 Java 中,compareTo()
和 equals()
是两个非常重要的方法,分别用于比较对象的顺序和判断对象是否相等。它们的作用和使用场景有所不同,下面详细解释两者的区别和用法。
equals()
方法
定义
equals()
方法是Object
类中的一个方法,默认实现是比较两个对象的引用地址(即是否指向同一个内存地址)。- 如果需要比较对象的内容是否相等,通常需要重写该方法。
作用
- 判断两个对象是否“相等”。
- 返回值是一个布尔值:
true
表示相等,false
表示不相等。
使用场景
- 当你需要检查两个对象的内容是否相同(而不是引用是否相同)时,通常会重写
equals()
方法。
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true; // 比较引用是否相同
if (obj == null || getClass() != obj.getClass()) return false; // 检查类型是否一致
Person person = (Person) obj;
return age == person.age && name.equals(person.name); // 比较内容是否相同
}
}
// 测试代码
Person p1 = new Person("Alice", 25);
Person p2 = new Person("Alice", 25);
System.out.println(p1.equals(p2)); // 输出: true
compareTo()
方法
定义
compareTo()
方法属于Comparable
接口,通常用于比较两个对象的顺序。- 它返回一个整数值:
- 负数:当前对象小于传入对象。
- 零:当前对象等于传入对象。
- 正数:当前对象大于传入对象。
作用
- 主要用于排序操作(如
Collections.sort()
或Arrays.sort()
),帮助确定对象之间的相对顺序。
使用场景
- 当你需要对一组对象进行排序时,通常会实现
compareTo()
方法。
public class Person implements Comparable<Person> {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Person other) {
return Integer.compare(this.age, other.age); // 根据年龄排序
}
}
// 测试代码
List<Person> people = Arrays.asList(
new Person("Alice", 25),
new Person("Bob", 30),
new Person("Charlie", 20)
);
Collections.sort(people); // 使用 compareTo() 方法排序
people.forEach(p -> System.out.println(p.name + " " + p.age));
// 输出:
// Charlie 20
// Alice 25
// Bob 30
equals()
和compareTo()
的主要区别
特性 | equals() |
compareTo() |
---|---|---|
返回值 | 布尔值 (true 或 false ) |
整数值 (<0 , 0 , >0 ) |
用途 | 判断对象是否相等 | 判断对象之间的相对顺序 |
接口 | 属于 Object 类 |
属于 Comparable 接口 |
默认行为 | 比较引用地址 | 抛出 ClassCastException (未实现时) |
适用场景 | 检查对象内容是否相同 | 对象排序 |
-
注意事项
-
一致性问题
- 在某些情况下(例如使用
TreeSet
或TreeMap
时),compareTo()
和equals()
的行为应该保持一致。 - 如果两个对象通过
compareTo()
方法比较为相等(返回值为0
),那么equals()
方法也应该返回true
。
- 在某些情况下(例如使用
-
compareTo()
的实现compareTo()的实现必须满足以下条件:
- 自反性:
x.compareTo(x)
必须返回0
。 - 对称性:如果
x.compareTo(y) > 0
,则y.compareTo(x) < 0
。 - 传递性:如果
x.compareTo(y) > 0
且y.compareTo(z) > 0
,则x.compareTo(z) > 0
。
- 自反性:
-
equals()
的实现- 重写
equals()
方法时,通常也需要重写hashCode()
方法,以确保哈希表(如HashMap
)的行为正确。
- 重写
总结
equals()
用于判断两个对象是否相等,返回布尔值。compareTo()
用于比较两个对象的顺序,返回整数值。- 它们在不同的场景中发挥各自的作用,理解它们的区别和使用规则对于编写高质量的 Java 程序非常重要。
hashCode()
hashCode()
是什么?
定义
hashCode()
是 Java 中Object
类的一个方法,默认实现是基于对象内存地址生成一个整数值。- 它的主要目的是为对象生成一个哈希码(hash code),用于支持基于哈希表的数据结构(如
HashMap
、HashSet
等)。
作用
- 哈希码是一个整数,通常用来快速定位对象在哈希表中的存储位置。
- 在哈希表中插入或查找对象时,会先通过
hashCode()
方法计算对象的哈希值,然后根据哈希值确定对象的存储位置。
规则
- 如果两个对象通过
equals()
方法比较为相等,则它们的hashCode()
必须返回相同的值。 - 如果两个对象不相等,它们的
hashCode()
不一定不同(允许哈希冲突)。
- 为什么需要
hashCode()
?
性能优化:哈希表利用哈希码快速定位数据,避免线性扫描整个集合。
一致性:确保 equals() 和 hashCode() 的行为一致,避免在哈希表中出现错误的结果。
Java.lang包
java.lang
包是 Java 标准库中最核心、最基本的包,它包含了 Java 程序运行所必需的核心类和接口。这些类和接口为 Java 语言提供了基础支持,几乎所有 Java 程序都会直接或间接地使用 java.lang
包中的功能。
java.lang
包的主要作用
java.lang
包的主要作用是提供 Java 程序运行所需的基本功能,包括:
- 定义了 Java 的基本数据类型(如
Integer
、String
等)。 - 提供了与对象操作相关的类(如
Object
、Class
等)。 - 提供了与线程管理相关的类(如
Thread
、Runnable
等)。 - 提供了与系统交互的类(如
System
、Runtime
等)。 - 提供了异常处理机制(如
Throwable
、Exception
等)。
java.lang
包的核心类
以下是一些 java.lang
包中最重要的类和接口:
(1) 基础类
Object
:所有 Java 类的根类,定义了所有对象都具备的基本行为(如toString()
、equals()
、hashCode()
等)。String
:用于表示不可变的字符串。StringBuilder
和StringBuffer
:用于构建可变的字符串。Math
:提供数学计算方法(如sqrt()
、pow()
等)。System
:提供与系统交互的功能(如标准输入输出流、获取当前时间等)。Runtime
:允许程序与 JVM 进行交互(如执行外部命令、获取内存信息等)。
(2) 数据类型包装类
Integer
、Double
、Boolean
等:为基本数据类型(如int
、double
、boolean
)提供对应的包装类,便于将基本类型转换为对象。Character
:用于表示单个字符,并提供与字符相关的操作。
(3) 线程相关类
Thread
:用于创建和管理线程。Runnable
:定义了线程任务的接口。ThreadLocal
:用于实现线程本地存储。
(4) 异常处理类
Throwable
:所有异常和错误的基类。Exception
:表示程序可以捕获和处理的异常。Error
:表示程序无法处理的严重错误(如OutOfMemoryError
)。RuntimeException
:表示不需要显式捕获的异常(如NullPointerException
、ArrayIndexOutOfBoundsException
)。
(5) 反射相关类
Class
:表示类的运行时类对象,用于反射机制。Package
:表示包的信息。
(6) 其他实用类
Process
和ProcessBuilder
:用于启动和管理外部进程。Enum
:用于定义枚举类型。Void
:表示无返回值类型。
java.lang
包的特点
- 自动导入:
java.lang
包中的类在 Java 程序中默认导入,无需显式使用import
语句。 - 核心功能:
java.lang
包提供了 Java 程序运行所需的基础功能,是 Java 平台的核心部分。 - 不可扩展:
java.lang
包中的许多类(如Object
、String
)是最终类(final
),不能被继承或修改。
ValueOf()的意思
为什么叫 valueOf()
?
- "Value":表示“值”。
- "Of":表示“属于”或“来自”。
结合起来,valueOf()
的意思就是“获取某个东西的值”。例如:
Integer.valueOf("123")
的意思是“获取字符串 '123' 对应的整数值”。Color.valueOf("RED")
的意思是“获取名称为 'RED' 的颜色值”
重写与 @Override
的关系
特性 | 重写(Override) | @Override 注解 |
---|---|---|
定义 | 子类重新定义父类方法的行为 | 标记方法是否正确重写了父类方法 |
是否必须使用 | 不需要显式标记 | 可选,但推荐使用 |
作用 | 实现多态性 | 提高代码安全性、可读性和可维护性 |
编译时检查 | 编译器不会主动检查方法是否正确重写 | 编译器会验证方法是否符合重写规则 |
哈希表和哈希码
哈希表(Hash Table)是一种高效的数据结构,其核心思想是通过哈希函数将键(key)映射到数组的索引位置,从而实现快速插入、删除和查找操作。哈希码(Hash Code)在哈希表中起着至关重要的作用,它是哈希表高效性的基础。
- 哈希表存在的意义
(1) 提高数据访问效率
- 哈希表的核心目标是提供接近 O(1) 的时间复杂度来完成插入、删除和查找操作。
- 与线性查找(如数组或链表)相比,哈希表通过哈希函数直接定位数据的位置,避免了逐一比较的过程。
(2) 简化键值对存储
- 哈希表非常适合存储键值对(Key-Value Pair),例如:
- 数据库中的索引。
- 缓存系统(如 Redis)。
- 字典或映射(Map)数据结构。
(3) 支持动态扩展
- 哈希表可以通过调整底层数组大小(即扩容)来适应更多的数据,同时保持高效的性能。
- 哈希码的作用
哈希码是哈希表工作的基础,它是一个整数值,用于将键映射到数组的具体位置。
(1) 定位数据
-
哈希码通过哈希函数计算得到,然后通过对数组长度取模(
%
运算)确定数据在数组中的具体索引位置。 -
示例:
int index = hashCode(key) % tableSize;
(2) 分布数据
- 哈希码的设计直接影响数据在哈希表中的分布情况。一个良好的哈希函数能够尽量减少冲突(即不同的键映射到同一个位置的情况)。
(3) 确保一致性
- 如果两个键相等(
equals()
返回true
),它们的哈希码必须相同(hashCode()
返回值一致)。这是哈希表正确工作的前提。
- 哈希表的工作原理
(1) 插入操作
- 计算键的哈希码。
- 根据哈希码计算数组索引位置。
- 将键值对存储到该位置(如果发生冲突,则使用链地址法或其他方法解决)。
(2) 查找操作
- 计算键的哈希码。
- 根据哈希码找到对应的数组索引位置。
- 在该位置查找键值对(如果有冲突,遍历链表或其他冲突解决结构)。
(3) 删除操作
- 计算键的哈希码。
- 找到对应的数组索引位置。
- 删除该位置的键值对(如果有冲突,从链表中移除)。
- 哈希表的优点和缺点
优点
- 高效性:插入、删除和查找的时间复杂度接近 O(1)。
- 灵活性:支持动态调整大小以适应更多数据。
- 简单性:适合存储键值对数据。
缺点
- 哈希冲突:不同键可能映射到相同的索引位置,需要额外机制(如链地址法或开放寻址法)解决。
- 空间开销:为了减少冲突,通常需要预留较大的数组空间。
- 顺序性差:哈希表不保证元素的存储顺序。
- 哈希表的应用场景
- 缓存系统:如 Redis、Memcached。
- 数据库索引:加速查询操作。
- 集合操作:如
HashSet
、HashMap
。 - 去重:判断某个元素是否已经存在。
- 频率统计:记录元素出现的次数。
- 哈希码与哈希表的关系
特性 | 哈希码 | 哈希表 |
---|---|---|
定义 | 对象内容的整数表示 | 使用哈希码定位数据的高效数据结构 |
作用 | 用于生成索引位置 | 实现快速插入、删除和查找 |
依赖关系 | 哈希码是哈希表的基础 | 哈希表依赖哈希码进行数据定位 |
- 总结
哈希表通过哈希码将键映射到数组索引位置,从而实现高效的键值对存储和检索。它的意义在于提供了接近常数时间复杂度的操作,适用于需要快速查找的场景。然而,哈希表也面临哈希冲突和空间开销等问题,因此在实际应用中需要根据需求选择合适的哈希函数和冲突解决策略。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示