解析Java根基:Object类核心方法

Object类常见方法解析

在Java编程中,Object类是所有类的根类,它包含了许多实用的方法,这些方法在不同的场景下发挥着重要作用。下面我们来详细了解一下Object类中的一些常见方法。

1. toString方法

toString方法是用于将对象转换为字符串表示形式的方法。在默认情况下,toString方法返回的结果是类名加上@符号,再跟上该对象对应哈希码的十六进制表示。例如,当我们打印一个对象时,如果没有重写toString方法,就会得到类似这样的结果:com.example.MyClass@12345678

然而,在实际开发中,我们通常需要根据对象的具体属性来定制它的字符串表示形式,以便更清晰地展示对象的信息。这时,我们就需要重写toString方法。比如,对于一个表示学生信息的类Student,我们可以这样重写toString方法:

复制代码
public class Student {
    private String name;
    private int age;

    // 构造方法和其他方法省略

    @Override
    public String toString() {
        return "Student{name='" + name + "', age=" + age + "}";
    }
}

这样,当我们打印一个Student对象时,就会得到包含学生姓名和年龄信息的字符串。

2. equals和hashcode方法

equals方法和hashcode方法在对象比较和集合操作中起着关键作用。

  • equals方法 :在进行对象比较的时候,我们通常会使用equals方法。如果不重写equals方法,默认情况下它会根据对象的地址进行比较,即只有两个引用指向同一个对象时,它们才被认为是相等的。但在很多实际场景中,我们需要根据对象的属性和方法来判断它们是否相等。例如,对于两个表示学生信息的对象,如果它们的姓名和年龄都相同,我们就认为这两个学生是相等的。这时,我们就需要重写equals方法,如下所示:

    @Override
    public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null || getClass()!= obj.getClass()) return false;
    Student student = (Student) obj;
    return age == student.age && Objects.equals(name, student.name);
    }

  • hashcode方法hashcode方法用于返回对象的哈希码值。哈希码是一个整数,它在许多数据结构中都有重要作用,比如HashMapHashSet。当我们对对象进行存储和查找操作时,哈希码可以帮助我们快速定位对象的位置。

需要注意的是,当我们重写equals方法时,通常也需要同时重写hashcode方法。这是因为如果两个对象根据equals方法判断是相等的,那么它们的哈希码也必须相等。如果不重写hashcode方法,可能会导致哈希冲突的概率增大。例如,在HashMap中,如果只重写了equals方法而没有重写hashcode方法,那么具有相同属性的两个对象可能会被存储在不同的哈希桶中,这会使得一些哈希桶中的对象个数过多,从而降低搜索效率。

另外,如果只重写了hashcode方法而没有重写equals方法,当发生哈希冲突时,会导致对象的安全性受到威胁。因为在哈希冲突的情况下,不同的对象可能会被存储在同一个哈希桶中,如果不重写equals方法,就无法正确判断这些对象是否真的相等,从而可能导致一些错误的结果。

3. wait、notify和notifyAll方法

waitnotifynotifyAll方法是用于线程间通信的方法,它们只能在同步代码块或同步方法中使用。

  • wait方法 :当一个线程调用某个对象的wait方法时,它会解除对该对象的锁,并进入等待状态,直到其他线程调用该对象的notifynotifyAll方法来唤醒它。例如,在生产者-消费者模型中,当消费者发现缓冲区为空时,它会调用缓冲区对象的wait方法进入等待状态,直到生产者生产了新的数据并调用notifynotifyAll方法来唤醒它。
  • notify方法notify方法用于唤醒陷入等待状态的某一进程。它会随机选择一个正在等待该对象锁的线程,并将其唤醒。需要注意的是,notify方法只会唤醒一个线程,如果有多个线程在等待,那么具体唤醒哪个线程是不确定的。
  • notifyAll方法notifyAll方法用于唤醒陷入等待状态的所有进程。它会唤醒所有正在等待该对象锁的线程,这些线程会竞争获取对象的锁,然后继续执行。

在这个示例中,生产者线程先生产数据,然后通过notify方法唤醒消费者线程,消费者线程在收到通知后开始消费数据。

Java为什么被称为平台无关性语言

Java实现平台无关性的核心机制在于JVM(Java虚拟机)的中间层设计。当Java源代码通过javac编译器生成字节码文件(.class文件)后,这些包含平台中立中间代码的文件可以在任何安装有对应平台JVM的设备上运行。各个操作系统虽然使用不同的机器指令集,但通过针对特定平台实现的JVM ,字节码会被实时转换为所在系统的本地机器指令执行。这种"一次编译,到处运行"的特性,使得开发者无需针对不同操作系统修改源代码,JVM作为抽象层有效隔离了底层硬件和操作系统的差异

= =和equals有什么区别?

== 和 equals 的区别

  1. 基本概念
    • == 是一个操作符,用于比较两个变量的值或引用。
    • equalsObject 类的一个方法,用于比较两个对象的内容。如果 equals 方法没有被重写,它的默认行为与 == 相同。
  1. 比较规则
    • 对于基本数据类型(如 int**、** char****等)
      == 比较的是变量的值。例如:

      int a = 5;
      int b = 5;
      System.out.println(a == b); // 输出 true

    • 对于对象类型(如 String**、** Integer****等)
      == 比较的是对象的引用(即内存地址)。例如:

      String str1 = new String("hello");
      String str2 = new String("hello");
      System.out.println(str1 == str2); // 输出 false

equals 方法比较的是对象的内容。例如:

复制代码
System.out.println(str1.equals(str2)); // 输出 true
  1. equals 方法的重写
    • 默认情况下,equals 方法比较的是对象的引用(与 == 相同)。
    • 如果类重写了 equals 方法,则可以根据自定义的逻辑比较对象的内容。例如,String 类重写了 equals 方法,用于比较字符串的内容。
    • 重写 equals 方法时,通常需要同时重写 hashCode 方法,以确保对象在哈希表等数据结构中的行为一致。

equals()与hashcode()

equals和hashCode属于Object的方法,equals默认情况下和==等效,hashCode()是根据一定的规则根据对象返回的值,比如对象的地址,经过处理,返回哈希值

在进行对象的比较的时候,可以进行自定义比较方法,需要重写equals方法,为了使HashMap、HashSet等方法正常存储,还需要对HashCode()进行重写

HashMap存储对象的时候,会先调用HashCode方法进行比较,当HashCode值相等的时候,再使用equals方法再进行比较

Student对象,有name和height两个成员变量,使用HashSet存储两个name和height相等的对象,判断Student对象是否相等就看这两个变量,当没有重写HashCode的时候,存储到HashSet中也会当作两个不同的对象,调用HashCode方法,因为没有重写,默认哈希值不会相同,所以会被认为两个对象不相同

解决方法就是重写HashCode(),最简单的就是返回两个变量哈希值的积

equals() 与 hashCode() 的核心机制

equals() 和 hashCode() 是定义在 java.lang.Object 类中的基础方法。

equals() 方法:默认实现与 == 运算符等价,用于判断两个对象是否严格相等(即是否指向同一内存地址)。开发中需根据业务需求重写此方法,以实现自定义的相等性逻辑。

hashCode() 方法:默认返回对象的内存地址经过哈希算法处理后的整数值。其作用是为对象生成一个紧凑的哈希标识,主要用于快速定位数据存储位置(如哈希表)。

集合框架中的关键作用

当使用 HashMap、HashSet 等基于哈希的集合存储对象时:

哈希优先原则:集合会先调用对象的 hashCode() 方法计算哈希值,若哈希值冲突,则进一步通过 equals() 方法比较对象的实际内容。

一致性要求:若重写了 equals(),必须同步重写 hashCode()。若二者逻辑不一致(例如两个对象通过 equals() 判断相等,但 hashCode() 返回不同值),会导致集合无法正确维护元素唯一性,引发潜在 bug。

Student 对象的典型场景分析

假设存在如下 Student 类:

public class Student {

private String name;

private int height;

}

未重写 hashCode() 的问题

向 HashSet<Student> 中添加两个属性完全相同的对象时:

Student s1 = new Student("Alice", 160);

Student s2 = new Student("Alice", 160);

Set<Student> students = new HashSet<>();

students.add(s1);

students.add(s2); // 实际执行结果:s2 会被视为新元素加入集合

原因 :默认的 hashCode() 实现基于对象内存地址,两次 new 操作生成的 s1 和 s2 地址不同,导致哈希值冲突被判定为不同对象。

解决方案:重写 hashCode()

通过覆盖 hashCode() 方法,将关键字段纳入哈希值计算:

@Override

public int hashCode() {

return Objects.hash(name, height); // 或简化为 name.hashCode() * 31 + height

}

原理 :将 name 和 height 的哈希值按规则组合,确保内容相同 的对象生成相同的哈希码,从而解决集合存储异常问题。

重载和重写

重载(Overloading)

重载指的是在同一个类中,允许存在多个方法名相同但参数列表不同的方法。这些方法的参数个数、顺序或类型必须有所区别。通过这种方式,可以为同一操作提供多种不同的实现方式,以适应不同的输入需求。

特点:

方法签名不同:方法名相同,但参数列表(参数的个数、顺序或类型)不同。

编译时决定 :重载的解析是在编译期间 完成的,编译器根据调用方法时提供的参数类型来决定具体调用哪一个重载的方法。

静态绑定:由于在编译时已经确定了调用的具体方法,因此重载属于静态绑定。

重写(Overriding)

重写是指子类对父类中已有的方法进行重新定义,方法名、参数列表以及返回类型都必须与父类中的方法完全一致。通过重写,子类可以改变父类方法的行为,以适应子类的特定需求。

特点:

方法签名相同方法名、参数列表和返回类型必须与父类中的方法完全一致。

运行 时决定:重写的解析是在运行期间完成的,具体执行哪一个方法取决于对象的实际类型。

动态绑定:由于在运行时才确定调用的具体方法,因此重写属于动态绑定。

抽象和接口

抽象类与接口的对比分析

【核心概念】

抽象类使用 abstract 关键字修饰,用于定义具有部分实现的基类;接口使用 interface 关键字声明,作为纯粹的行为契约。二者在代码组织层面存在本质区别:

【抽象类的应用】

当多个类存在代码复用需求时,通过抽象类进行逻辑抽取可提升代码简洁性与可维护性。其核心价值体现在:

代码复用:将公共实现逻辑提取到抽象基类中,子类通过继承复用代码

强制约束:通过抽象方法(public abstract 修饰)强制子类实现特定功能

类型标识:表达严格的继承关系,体现"是"(is-a)的语义特征

修改时只需调整抽象基类即可影响所有子类,但需注意普通类也能实现类似效果,抽象类的核心区别在于其无法实例化并具有强制约束力。

【接口的演进】

接口作为规范标准,体现"具有...能力"(like-a)的语义特征,其发展历经多个阶段:

传统接口(Java 7-)

包含隐式 public abstract 修饰的抽象方法

允许声明 public static final 常量

增强接口(Java 8+)

引入 default方法(含具体实现)

支持静态方法定义

允许定义私有方法(Java 9+)

【实现规范】

类实现接口时需遵循:

必须实现所有抽象方法(未实现则类需声明为 abstract)

选择性 覆盖 default方法

常量字段自动继承且不可修改

支持多接口实现,体现灵活的组合特性

Final

final 是 Java 的关键字,用于限制类、方法或变量的可修改性,具体用法如下:

修饰类

当类被 final 修饰时,该类不能被继承

核心目的:防止通过子类继承修改原有类的行为,常用于保护核心 API 的稳定性。

示例:

public final class String { ... } // String 类不可被继承

修饰方法

当方法被 final 修饰时,该方法不能被子类重写

核心目的:确保关键方法的行为不被破坏,常用于模板方法模式中的固定步骤。

示例:

public class Parent {

public final void lock() { ... } // 子类无法重写 lock()

}

修饰变量

基本类型变量:

变量值初始化后不可修改,成为常量

示例:

final int MAX_VALUE = 100;

// MAX_VALUE = 200; // 编译报错

引用类型变量:

引用类型变量

变量指向的内存地址不可变,但对象内部状态可以修改。

示例:

final List list = new ArrayList<>();

list.add("Java"); // 允许修改内容 // list = new LinkedList<>(); 编译报错(地址不可变)

static final 联合使用static final 修饰的变量为全局常量 ,在类加载时初始化。 优势: 编译时常量(如字符串字面量)会被 JVM 优化,直接存入常量池,提升访问效率。 示例: public static final String VERSION = "1.0";

不可变设计的经典案例:

String 类的不可变性: String 类被 final 修饰,禁止继承。 内部存储数据的 char[] value 数组被 private final 修饰,且不提供修改方法(如 setter)。 所有修改操作(如 substring、replace)均返回新对象,原对象不变。 优势: 线程安全(无需同步锁)。 哈希值可缓存,提升性能。

多线程场景下的应用

使用 final 修饰变量可确保该变量的值在线程间可见且不可变,避免数据竞争问题。

示例:

public class SafePublication {

private final int safeValue; // 安全发布,防止指令重排序

public SafePublication(int value) {

this.safeValue = value;

}

}

异常的理解

异常 是指在程序运行过程中可能发生的非正常事件,这类事件会中断程序的正常执行流程。通过异常处理机制,开发者可以捕获并处理这些异常,在保障程序核心功能不受致命影响的前提下完成错误恢复操作。

异常的种类

Java 的异常类继承体系以 Throwable 为顶层父类,具体分为两类:

Error(错误)

由 JVM 或底层系统资源引发的严重问题(如 StackOverflowError 栈溢出、OutOfMemoryError 内存耗尽),属于不可恢复的致命错误。

Exception(异常)

可被捕获处理的非致命性问题,进一步细分为:

编译时异常(Checked Exception)

编译器强制要求处理的异常类型,如 FileNotFoundException(文件未找到)、IOException(输入输出异常)。

运行时异常(Runtime Exception)

由代码逻辑缺陷引发的异常,编译器不强制处理,如 NullPointerException(空指针异常)、IndexOutOfBoundsException(数组越界异常)。

thow和throws的区别

throw是在方法内部使用,进行抛出异常,是一个动作 。throws是在方法声明的后面,表示可能会抛出的异常,是一种声明

那么直接try-catch不就行了,为什么还要抛出异常呢?

首先我们要分情况说明,如果该异常可以被try-catch处理的话,就可以直接try-catch,比如说虽然A方法抛出了一个异常,但是基于他异常处理的方法在其他地方,并不需要处理,不需要处理的情况下,就抛出异常给调用者 。其次,当调用的几个方法中都存在相同的异常,如果在这几个方法内部进行try-catch的话,会使得整体变得冗余,可以让调用方统一进行捕获异常 ,并进行处理,简洁化。最后还有可能就是上层调用方处理

String

  1. 不可变类的核心优势
    线程安全:String的不可变性天然规避了多线程同步问题,无需额外锁机制即可保证线程安全。
    哈希缓存:首次计算哈希值后缓存,作为Map键时无需重复计算,显著提升查找效率(如HashMap的键比较场景)。
  2. 字符串创建与常量池机制
    共享优化:常量池通过引用复用已存在的字符串,避免重复创建。例如:
    String s1 = "hello"; // 常量池新建对象(若不存在)
    String s2 = "hello"; // 直接引用常量池现有对象
    对象创建示例:String a = new String("aa") + "bb"; 的详细过程:
    常量池新建"aa"对象(若不存在)。
    new String("aa")在堆中创建新对象(非池引用)。
    常量池新建"bb"对象(若不存在)。
    拼接生成"aabb",堆中创建新对象并可能更新常量池(若未存在),共创建了四个对象
  3. 字符串存储与内存限制
    底层结构:使用char[]存储,理论最大长度为Integer.MAX_VALUE(2³¹-1)。
    实际限制:
    编译期:最大长度为2¹⁶-2(65534,受CONSTANT_Utf8_info限制)。
    运行时:受JVM可用内存限制(如堆大小)。
  4. 字符串常量池的演进
    Java 7前:位于永久代(PermGen),易引发OOM。
    Java 8+:迁移至元空间(Metaspace),降低内存溢出风险。

字符串拼接效率对比

String的+操作:

String result = "a" + "b"; // 编译后等效于:

String result = new StringBuilder().append("a").append("b").toString();

隐含创建StringBuilder和临时String对象,效率较低。
StringBuffer:

直接操作内部可变数组,避免频繁对象创建。

线程安全(同步开销),适合多线程场景。

StringBuilder(补充建议):单线程下更高效(非线程安全)。

相关推荐
weifexie23 分钟前
ruby可变参数
开发语言·前端·ruby
王磊鑫24 分钟前
重返JAVA之路-初识JAVA
java·开发语言
千野竹之卫24 分钟前
3D珠宝渲染用什么软件比较好?渲染100邀请码1a12
开发语言·前端·javascript·3d·3dsmax
半兽先生1 小时前
WebRtc 视频流卡顿黑屏解决方案
java·前端·webrtc
liuluyang5301 小时前
C语言C11支持的结构体嵌套的用法
c语言·开发语言·算法·编译·c11
凌叁儿1 小时前
python保留关键字详解
开发语言·python
南星沐2 小时前
Spring Boot 常用依赖介绍
java·前端·spring boot
明飞19872 小时前
C_内存 内存地址概念
c语言·开发语言
代码不停2 小时前
Java中的异常
java·开发语言
兮兮能吃能睡3 小时前
Python中的eval()函数详解
开发语言·python