目录
[2.finalize:在 JDK 9 中此方法已经被标记为过时的。](#2.finalize:在 JDK 9 中此方法已经被标记为过时的。)
[3.getClass:public final Class getClass():获取对象的运行时类型](#3.getClass:public final Class getClass():获取对象的运行时类型)
[4.hashCode:public int hashCode():返回每个对象的 hash 值。](#4.hashCode:public int hashCode():返回每个对象的 hash 值。)
学习面向对象内容的三条主线
Java类及类的成员:(重点)属性,方法,构造器;(熟悉)代码块,内部类
面向对象的特征:封装,继承,多态,(抽象)
其他关键字的使用:this,super,package,import,static,final,interface,abstract等
this关键字
01.目前可能出现的问题?及解决方案?
我们在声明一个属性对应的setXxx方法时,通过形参给对应的属性赋值。如果形参名和属性名同名了,那么该如何在方法内区分这两个变量呢?
解决方案:使用this。 具体来讲,使用this修饰的变量,表示的是属性。没有用this修饰的,表示的是形参。

02.this可以调用的结构:成员变量,方法,构造器
03.this调用属性和方法
针对于方法内的使用情况:(准确的说是非static修饰的方法)
一般情况:我们通过对象a调用方法,可以在方法内调用当前对象a的属性或其他方法。此时,我们可以在属性和其他方法前使用"this.",表示当前属性或方法所属的对象a。但是,一般情况下,我们都选择省略此"this."结构。
持殊情况:如果方法的形参与对象的属性同名了,我们必须使用"this,"进行区分。使用this.修饰的变量即为属性(或成员变量),投有使用this.修饰的变量,即为局部变量。
针对于构造器内的使用情况:
一般情况:我们通过构造器创建对象时,可以在构造器内调用当前正在创建的对象的属性或方法。此时,我们可以在属性和方法前使用"this.",表示当前属性或方法所属的对象。但是,一般情况下,我们都选择省略此"this."结构。
特殊情况:如果构造器的形参与正在创建的对象的属性同名了,我们必须使用"this."进行区分。使用this.修饰的变量即为属性(或成员变量),没有使用this.修饰的变量,即为局部变量。
04.this调用构造器说明:
1.格式:"this(形参列表)"
2.我们可以在类构造器中,调用当前类中指定的其他构造器
3.要求:"this(形参列表)"必须声明在当前构造器的首行
4.结论:"this(形参列表)"在构造器中最多声明一个
5.如果一个类中声明了n个构造器,则最多有n-1个构造器可以声明有"this(形参列表)"的结构
Java特性之一-----继承性
01.继承性的理解与概念:
继承有延续(下一代延续上一代的基因,财富),扩展(下一代和上一代又有所不同)的意思
Java中的继承:
> 自上而下:定义了一个类A,在定义另一个类B时,发现类B的功能与类A相似,考虑类B继承于类A
> 自下而上:定义了类B,C,D等,发现B,C,D有类似的属性和方法,则可以考虑将相同的属性和方法进行抽取,封装到类A中,让类B,C,D继承于类A,同时,B,C,D中的相似功能就可以删除了.
02.继承性的好处:
- 继承的出现减少了代码的冗余,提高了代码的复用性.
- 继承的出现,更有利于功能的扩展.
- 继承的出现让类与类之间产生了"is-a"的关系,为多态的使用提供了前提
- 继承 描述事物之间的所属关系,这种关系是:"is-a"的关系.可见,父类更通用,更一般,子类更具体.
03.继承的格式:
// 类A:父类,superClass,超类,基类
class A{
// 属性.方法
}
// 类B:子类,subClass,派生类
class B extends A{
}
04.继承性的说明:
- 子类继承父类之后,子类就获取了父类中的声明的所有属性和方法.但是由于封装性的影响,可能子类不能直接调用父类中声明的属性和方法.
- 子类继承父类之后,子类还可以扩展自己特有的功能(体现:增加特有的属性,方法),extends:延展,扩展,延申
- 不要为了继承而继承.在继承之前,判断一下是否为is-a关系.
- Java中声明的类,如果没有显式声明其父类,则默认继承于Java.lang.Object
- Java是支持多层继承
- Java中的子父类的概念是相同的,Java中一个父类可以声明多个子类,反之,一个子类只能有一个父类(Java的单继承性)
方法重写(override/overwrite)
01.引入
提问:父类的所有方法子类都会继承,但是当某个方法被继承到子类之后,子类觉得 父类原来的实现不适合于自己当前的类,该怎么办呢?
回答:子类可以对从父类中继 承来的方法进行改造,我们称为方法的重写 (override、overwrite) 。也称为 方法的重置 、覆盖。
在程序执行时,子类的方法将覆盖父类的方法。
02.方法举例:
java
// 父类
public class Phone {
public void sendMessage(){
System.out.println("发短信");
}
public void call(){
System.out.println("打电话");
}
public void showNum(){
System.out.println("来电显示号码");
}
}
// 子类
public class SmartPhone extends Phone{
//重写父类的来电显示功能的方法
@Override
public void showNum(){
System.out.println("显示来电姓名");
System.out.println("显示头像");
}
//重写父类的通话功能的方法
@Override
public void call() {
System.out.println("语音通话 或 视频通话");
}
}
@Override 使用说明 : 写在方法上面,用来检测是不是满足重写方法的要求 。这个注解就算 不写,只要满足要求,也是正确的方法覆盖重写。建议保留,这样编译器可以帮助我们检查格式,另外也可以让阅读源代码的程序员清晰的知道这是一个重写的方法。
03.方法重写的要求:
- 子类重写的方法必须 和父类被重写的方法具有相同的方法名称 、参数列表。
- 子类重写的方法的返回值类型不能大于 父类被重写的方法的返回值类型。
注意:如果返回值类型是基本数据类型和 void,那么必须是相同
3 .子类重写的方法使用的访问权限不能小于 父类被重写的方法的访问权限。 (public>protected>缺省>private)
注意: ① 父类私有方法不能重写 ② 跨包的父类缺省的方法也不能重写 - 子类方法抛出的异常不能大于父类被重写方法的异常
5.子类与父类中同名同参数的方法必须同时声明为非 static 的(即为重写),或者同时声明为 static 的(不是重写),因为 static 方法是属于类的,子类无法覆盖父类的方法。
04.方法重写与重载:
方法的重载:方法名相同,形参列表不同。不看返回值类型。
方法的重写:子类重写的方法 必须 和父类被重写的方法具有相同的 方法名称 、 参数列表等等.
关键字super
01.super关键字的作用
在 Java 类中使用 super 来调用父类中的指定操作:
- super 可用于访问父类中定义的属性
- super 可用于调用父类中定义的成员方法
- super 可用于在子类构造器中调用父类的构造器
注意:
- 尤其当子父类出现同名成员时,可以用 super 表明调用的是父类中的成员
- super 的追溯不仅限于直接父类
- super 和 this 的用法相像,this 代表本类对象的引用,super 代表父类的内存空间的标识
02.子类中调用父类被重写的方法:
1.如果子类没有重写父类的方法,只要权限修饰符允许,在子类中完全可以直接调用父类的方法;
2.如果子类重写了父类的方法,在子类中需要通过 *super.*才能调用父类被重写的方法,否则默认调用的子类重写的方法
代码展示:
java
// 父类
public class Phone {
public void sendMessage(){
System.out.println("发短信");
}
public void call(){
System.out.println("打电话");
}
public void showNum(){
System.out.println("来电显示号码");
}
}
// 子类
public class SmartPhone extends Phone{
////重写父类的来电显示功能的方法
public void showNum(){
System.out.println("显示来电姓名");
System.out.println("显示头像");
super.showNum();
}
方法前面没有 super.和 this.
先从子类找匹配方法,如果没有,再从直接父类找,再没有,继续往上追溯
方法前面有 this.
先从子类找匹配方法,如果没有,再从直接父类找,再没有,继续往上追溯
方法前面有 super.
从当前子类的直接父类找,如果没有,继续往上追溯
03.子类中调用父类中同名的成员变量
1.如果实例变量与局部变量重名,可以在实例变量前面加 this.进行区别
2.如果子类实例变量和父类实例变量重名,并且父类的该实例变量在子类仍然可见,在 子类中要访问父类声明的实例变量需要在父类实例变量前加 super.,否则默认访问的 是子类自己声明的实例变量
3.如果父子类实例变量没有重名,只要权限修饰符允许,在子类中完全可以直接访问父 类中声明的实例变量,也可以用 this.实例访问,也可以用 super.实例变量访问
代码展示:
java
// 父类
class Father{
int a = 10;
int b = 11;
}
class Son extends Father{
int a = 20;
public void test(){
// 子类与父类的属性同名,子类中就有两个a
System.out.println("子类的 a:" + a); // 20 先找局部变量找,没有再从本类成员变量找
System.out.println("子类的 a:" + this.a); // 20 先找局部变量找,没有再从本类成员变量找
System.out.println("父类的 a:" + super.a); // 10 直接从父类成员变量找
// 子类与父类的属性不同名,是同一个 b
System.out.println("b = " + b); //11 先找局部变量找,没有再从本类成员变量找,没有再从父类找
System.out.println("b = " + this.b);//11 先从本类成员变量找,没有再从父类找
System.out.println("b = " + super.b);//11 直接从父类局部变量找
}
变量前面没有 super.和 this.
在构造器、代码块、方法中如果出现使用某个变量,先查看是否是当前块声明的局部变量
如果不是局部变量,先从当前执行代码的 本类去找成员变量
如果从当前执行代码的本类中没有找到,会往上找父类声明的成员变量(权限修饰符允许在子类中访问的)
变量前面有 this.
通过 this 找成员变量时,先从当前执行代码的==本类去找成员变量==
如果从当前执行代码的本类中没有找到,会往上找==父类声明的成员变量(==权限修饰符允许在子类中访问的)
变量前面 super.
通过 super 找成员变量,直接从当前执行代码的直接父类去找成员变量 (权限修饰符允许在子类中访问的)
如果直接父类没有,就去父类的父类中找(权限修饰符允许在子类中访问 的)
特别说明:应该避免子类声明和父类重名的成员变量
04.子类构造器中调用父类构造器
① 子类继承父类时,不会继承父类的构造器。 只能通过"super(形参列表)"的方
式调用父类指定的构造器。
② 规定:"super(形参列表)", 必须声明在构造器的首行 。
③在构造器的首行可以使用"this(形参列表)",调用本类中重载 的构造器, 结合②,结论:在构造器的首行, "this(形参列表)" 和 "super(形 参列表)"只能二选一。
④ 如果在子类构造器的首行既没有显示调用"this(形参列表)",也没有显式调用 "super(形参列表)", 则子类此构造器默认调用"super()",即调用父类中空参的构造器。
⑤ 由③和④得到结论: 子类的任何一个构造器中,要么会调用本类中重载的构 造器,要么会调用父类的构造器。 只能是这两种情况之一。
⑥ 由⑤得到: 一个类中声明有 n 个构造器,最多有 n-1 个构造器中使用了 "this(形参列表)",则剩下的那个一定使用"super(形参列表)"。常见错误:
如果子类构造器中既未显式调用父类或本类的构造器,且父类中又没 有空参的构造器,则编译出错
05.this和super的使用格式:
this:
- this.成员变量:表示当前对象的某个成员变量,而不是局部变量
- this.成员方法:表示当前对象的某个成员方法,完全可以省略 this
- this()或 this(实参列表):调用另一个构造器协助当前对象的实例化,只能在 构造器首行,只会找本类的构造器,找不到就报错
super:
- super.成员变量:表示当前对象的某个成员变量,该成员变量在父类中声明的
- super.成员方法:表示当前对象的某个成员方法,该成员方法在父类中声明的
- super()或 super(实参列表):调用父类的构造器协助当前对象的实例化,只能在构造器首行,只会找直接父类的对应构造器,找不到就报错
多态的形式和体现
01.什么是多态??
多态,顾名思义,就是"多种形态".在Java中,多态性是指允许不同类的对象对同一消息(方法调用)做出的不同的响应.
简单来说,就是"一个接口,多种实现"。同样的父类引用,在调用同一个方法时,可能会表现出不同的行为。
02.多态存在的必要条件:
继承:必须存在子类继承父类的关系.
重写:子类必须对父类中的某些方法进行重写(Override).
父类引用指向子类对象:这是最关键的一点,我们称之为"向上转型"。
03.什么是向上转型??
就是创建一个子类对象,并将其引用赋值给一个父类类型的变量。
javaAnimal myDog = new Dog(); // 向上转型这里,
myDog的编译时类型是Animal,但它的运行时类型是Dog。通过这个父类引用
myDog,你只能访问到父类中定义的属性和方法,而不能访问 子类特有的属性和方法。但是,当调用一个被重写 的方法时,实际执行的是子类中重写后的版本。
04.多态的两种主要形式
形式一:编译时的多态
这主要通过方法重载来实现。
-
概念 :在同一个类中,允许存在多个同名方法,但它们的参数列表不同(参数个数、类型或顺序不同)。
-
特点:在编译阶段,编译器就能根据调用时传入的参数类型和个数,确定要执行哪个具体的方法。所以叫"编译时多态"。
形式二:运行时多态
这才是我们通常意义上所说的、也是最重要的多态性。它通过方法重写 和向上转型来实现。
-
概念:当父类引用指向不同的子类对象时,调用同一个重写方法,会表现出不同的行为。
-
特点 :在编译时无法确定到底执行哪个方法,只有在程序运行期间,JVM(Java虚拟机)才会根据引用变量实际指向的对象类型,来决定调用哪个子类的方法。所以叫"运行时多态"。
java
class Animal {
public void makeSound() {
System.out.println("动物发出声音");
}
}
class Dog extends Animal {
public void makeSound() {
System.out.println("狗叫声:汪汪汪!");
}
// 子类特有的方法
public void wagTail() {
System.out.println("狗摇尾巴");
}
}
class Cat extends Animal {
public void makeSound() {
System.out.println("猫叫声:喵喵喵~");
}
}
public class TestPolymorphism {
public static void main(String[] args) {
// 关键:父类引用 指向 子类对象
Animal myAnimal1 = new Dog();
Animal myAnimal2 = new Cat();
// 执行结果分析:
myAnimal1.makeSound(); // 输出:狗叫声:汪汪汪!
myAnimal2.makeSound(); // 输出:猫叫声:喵喵喵~
// myAnimal1.wagTail(); // ❌ 编译错误!因为myAnimal1是Animal类型,编译器只认识Animal类里的方法。
}
}
06.多态的执行机制
为什么 myAnimal1.makeSound() 调用的是 Dog 类的方法,而不是 Animal 类的方法?
这是因为Java使用了动态绑定或叫后期绑定的机制:
编译器检查引用变量
myAnimal1的类型 (Animal),看它有没有makeSound()方法。有,所以编译通过。编译阶段,编译器并不知道要执行哪个具体的
makeSound()方法。当程序运行时,JVM 会去检查
myAnimal1这个引用变量实际指向的对象 ,发现是一个Dog对象。JVM 就会在
Dog类中查找makeSound()方法,找到后就执行它。这就是"运行时"多态的由来。
07.多态的优势
可替换性:多态代码对已存在的代码具有很好的可替换性。
可扩充性:增加新的子类不会影响已有多态代码的运行。这是多态最突出的优点。
接口统一性:多态为具有相同父类的子类提供了统一的接口,让调用者可以统一风格。
简化代码:可以编写出更简洁、更通用的方法,来处理父类及其所有子类的对象。
08.多态的劣势
一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象, 那么该变量就不能再访问子类中添加的属性和方法。
java
// 一个可以处理任何动物的方法,体现了多态的威力
public class AnimalDoctor {
// 无需为每种动物写一个方法,只需要一个方法,接收父类引用
public void cure(Animal animal) {
System.out.println("开始治疗...");
animal.makeSound(); // 这里的makeSound()会表现出多态性,具体动物具体发声
System.out.println("治疗结束。");
}
public static void main(String[] args) {
AnimalDoctor doctor = new AnimalDoctor();
Animal dog = new Dog();
Animal cat = new Cat();
doctor.cure(dog); // 传入Dog对象
doctor.cure(cat); // 传入Cat对象
// 未来假如新增了 Bird 类继承 Animal,cure(Animal animal) 方法无需任何修改,可以直接使用!
}
}
09.多态的常见使用方法
java
// 父类
class Account{
}
//子类1
class CheckAccount extens Account{
// 存在方法重写
}
// 子类2
class SavingAccount extens Account {
// 存在方法重写
}
class Customer{
Account account;
public void setAccount(Account account){
this.account = account;
}
public Account getAccount{
return account;
}
}
class CustomerTest{
public static void main(String args[]){
Customer cust = new Customer();
cust.setAccount(new CheckAccount());
cust.getAccount.子类1重写方法;
}
}
10.向下转型
①向下转型的基本概念: 向下转型(Downcasting)是指将父类类型的引用转换为子类类型的操作。与向上转型(自动转换)不同,向下转型需要显式进行,且存在一定风险。
②为什么需要向下转型??
使用多态时,不能调用子类特有的方法和属性,这也是多态的弊端,解决此方法就必须使用向下转型.
③向下转型的语法:
// 基本语法 子类类型 变量名 = (子类类型) 父类引用;
// 示例 Animal animal = new Dog(); Dog dog = (Dog) animal; // 向下转型
④instanceof运算符
在进行向下转型前,最好使用 instanceof 运算符检查对象的真实类型,以避免 ClassCastException(类型转型错误)。
基本语法:
a instanceof A 判断对象a是否是类A的实例
如果 a instanceof A返回true,则:a instanceof superA 返回也是ture,其中A是superA的子类
if (变量名 instanceof 子类类型){
子类类型 变量名 = (子类类型) 父类引用;
}
// 示例:
Animal animal = new Dog();
if (animal instanceof Dog){
Dog dog = (Dog) animal;
}
object类
java.lang.object类是Java类层次结构的根类,所有类都直接或间接继承自object类.如果一个类没有显式使用extends关键字继承其他类,那么它默认继承object类.每个类都使用object作为超类.
// 两种写法是等价的
public class Myclass{} // 隐式继承object
public class Myclass extends object {} // 显式继承object
01.重点说明:
- Object类型的变量与除object以外的任意引用数据类型的对象都存在多态引用
- 所有对象(包括数组)都实现这个类的方法
- 如果一个类没有特别指定父类,那么默认则继承自object类
02.object类的主要方法
object类提供了11个方法,这些方法可以被所有Java类继承和重写
|-----------------------------------------------------------------------------|--------------|
| 方法 | 简单描述 |
| public final native Class<?> getClass() | 返回对象的运行时类 |
| public native int hashCode() | 返回对象的哈希码值 |
| public boolean equals(Object obj) | 比较对象是否相等 |
| protected native Object clone() throws CloneNotSupportedException | 创建并返回对象的副本 |
| public String toString() | 返回对象的字符串表示 |
| public final void wait() throws InterruptedException | 使线程进入等待状态 |
| public final native void notify() | 唤醒等待的单个线程 |
| public final native void notifyAll() | 唤醒等待的所有线程 |
| protected void finalize() throws Throwable | 垃圾回收前调用(已废弃) |
| public final native void wait(long timeout) throws InterruptedException | 带超时时间的wait方法 |
| public final void wait(long timeout, int nanos) throws InterruptedException | 更精细的超时控制 |
03.重点关注6个方法
1.clone
clone方法是Java中创建对象副本的一种方式,它定义在object类中,因此所有Java类都继承了它.不过,要正确使用它,需要理解其保护性访问,标记接口约定以及浅拷贝与深拷贝的核心区别.
**clone()**方法在Object类中的声明是protected native Object clone() throws CloneNotSupportedException;
三个关键点:
protected访问权限:这意味着如果一个类没有显式重写clone()并将其访问权限改为public,那么其他类就无法直接调用该对象的clone()方法native关键字:表明这是一个本地方法,它的实现不是由Java代码完成的,而是由底层的C/C++代码实现,通常效率较高CloneNotSupportedException:如果调用了clone()方法的类没有实现cloneable接口,就会抛出异常
java
//Object 类的 clone()的使用
public class CloneTest {
public static void main(String[] args) {
Animal a1 = new Animal("花花");
try {
Animal a2 = (Animal) a1.clone();
System.out.println("原始对象:" + a1);
a2.setName("毛毛");
System.out.println("clone 之后的对象:" + a2);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
class Animal implements Cloneable{
private String name;
public Animal() {
super();
}
public Animal(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Animal [name=" + name + "]";
}
@Override
protected Object clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
return super.clone();
}
}
2.finalize:在 JDK 9 中此方法已经被标记为过时的。
当对象被回收时,系统自动调用该对象的 finalize() 方法 。( 不是垃圾回收器调用的,
是本类对象调用的 )
永远不要主动调用某个对象的 finalize 方法,应该交给垃圾回收机制调用。
什么时候被回收:当某个对象没有任何引用时,JVM 就认为这个对象是垃圾对象,就
会在之后不确定的时间使用垃圾回收机制来销毁该对象,在销毁该对象前,会先调用
finalize()方法。
子类可以重写该方法,目的是在对象被清理之前执行必要的清理操作。比如,在方法
内断开相关连接资源。
如果重写该方法,让一个新的引用变量重新引用该对象,则会重新激活对象
java
public class FinalizeTest {
public static void main(String[] args) {
Person p = new Person("Peter", 12);
System.out.println(p);
p = null;//此时对象实体就是垃圾对象,等待被回收。但时间不确定。
System.gc();//强制性释放空间
}
}
class Person{
private String name;
private int age;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
//子类重写此方法,可在释放对象前进行某些操作
@Override
protected void finalize() throws Throwable {
System.out.println("对象被释放--->" + this);
}
}
3.getClass: public final Class<?> getClass():获取对象的运行时类型
因为 Java 有多态现象,所以一个引用数据类型的变量的编译时类型与 运行时类型可能不一致,因此如果需要查看这个变量实际指向的对象 的类型,需要用 getClass()方法
java
public class Project3GetClass {
public static void main(String[] args) {
Object object = new Person1();
System.out.println(object.getClass()); // 运行时类型 class mianXiangDuiXiangTwo.ObjectMethodPractice.Person1
}
}
class Person1{
private String name;
private int age;
public Person1() {
}
public Person1(String name, int age) {
this.name = name;
this.age = age;
}
public void Speak(){
System.out.println("大家好");
}
}
4.hashCode :public int hashCode():返回每个对象的 hash 值。
java
public static void main(String[] args) {
System.out.println("AA".hashCode());//2080
System.out.println("BB".hashCode());//2112
}
5.equals
我们先来看"=="符号
- 基本类型比较值:只要两个变量的值相等,即为 true。
- 引用类型比较引用(是否指向同一个对象):只有指向同一个对象时,==才返回 true。用"=="进行比较时,符号两边的数据类型必须兼容(可自动转换的基本数 据类型除外),否则编译出错
equals()方法是Java中用于比较对象相等性的核心方法,它定义在object类中,因此所有Java类都继承了它.
01.equals()方法的格式
obj1.equals(obj2)
02.equals()方法的默认行为
java
public boolean equals(Object obj) {
return (this == obj);
}
总结:默认情况下,equals()比较的是两个对象的引用是否相等(地址值),即他们是否指向堆内存中的同一个对象.这与==运算符在比较对象是的行为是一致的
03.equals()与==的区别
|------|------------------------|--------------------------|
| 比较方式 | ==运算符 | equals()方法 |
| 基本类型 | 比较值是否相等 | 不适用(基本类型不是对象) |
| 引用类型 | 比较两个引用是否指向同一个对象(地址值相等) | 默认行为同==,但通常被重写为比较对象的内容相等 |
| 可重写性 | 无法重写 | 可以在子类中重写,以定义自定义的相等逻辑 |
java
// 理解重写
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); // false,不同对象
System.out.println(s1.equals(s2)); // true,String重写了equals,比较内容
04.为什么需要重写equals()
当我们需要根据对象的内容 (即字段的值)来判断两个对象是否相等,而不是根据它们是否是同一个内存实例时,就必须重写 equals()。
05.equals特例
当用equals()方法进行比较时,对类File,String,Data及包装类(Wrapper Class)来说,是比较类型及内容而不考虑引用的是否是同一个对象;
原因:在这些类中重写了object类的equals()方法.
06.equals()方法重写的原则
(1).对称性:如果x.equals(y)返回"true",那么y.equals(x)也应该返回的是"true"
(2).自反性:x.equals(x)必须返回是"true"
(3).传递性:如果x.equals(y)返回的是"ture",y.equals(z)返回的是"ture",那么x.equals(z)返回的也应该是"ture",
(4).一致性:如果x.equals(y)返回的是true,只要x,y内容一直不变,不管你重复x.equals(y)多少次,返回都是"true"
(5)任何情况下,x.equals(null),永远返回的是"flase"
(6).x.equlas(和x不同类型的对象)返回的永远是"flase"
07.重写举例
java
public class Person {
private int id;
private String name;
@Override
public boolean equals(Object obj) {
// 1. 自反性快速检查:如果是同一个对象,直接返回true
if (this == obj) {
return true;
}
// 2. 非空性检查:如果参数为null,返回false
if (obj == null) {
return false;
}
// 3. 类型检查:通常使用 getClass() 确保是同一个类
// 也可以使用 instanceof,但需要注意与子类比较的对称性问题
if (getClass() != obj.getClass()) {
return false;
}
// 4. 类型转换
Person other = (Person) obj;
// 5. 比较关键字段
// - 基本类型用 ==
// - 引用类型用 Objects.equals() 或 对应字段的equals()
// - 对于float/double,可以使用 Float.compare/ Double.compare
return id == other.id && Objects.equals(name, other.name);
}
}
08.重写equals()时总要重写hashCode()方法
这是一个必须遵守的规则:如果两个对象通过 equals() 比较相等,那么它们的 hashCode() 返回值也必须相等。反之不要求。
这条规则的目的是为了保证基于哈希的集合(如 HashMap、HashSet、HashTable)能够正常工作。这些集合利用 hashCode 来确定对象存储的"桶",再用 equals 来精确比较
6.toString
01.toString()方法的定义
java
public String toString()
它是
public方法,所有子类都可以访问和重写。返回值类型是
String,即一个字符串。通常,这个方法应该返回一个简洁且信息丰富的字符串,能够描述对象的当前状态。
02.默认实现
java
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
03.为什么需要重写toString?
提供有意义的调试信息 :当你在 IDE 中调试或打印对象时,看到
Person@1b6d3586远不如看到Person{name='张三', age=25}有用。日志记录:在记录日志时,直接输出对象即可得到关键信息,而不需要手动拼接字段。
字符串拼接自动调用 :当使用
+运算符拼接字符串时,如果其中一个操作数是对象,Java 会自动调用该对象的toString()方法。
javaSystem.out.println("用户信息:" + person); // 自动调用 person.toString()异常信息中显示 :异常栈跟踪中也会调用对象的
toString()来展示相关信息。因此,为了让对象能够以人类可读的形式展示自身状态,强烈建议在自定义类中重写toString()。
04.示例
java
import java.util.Objects;
public class Person {
private String name; // 可能为 null
private String address; // 可能为 null
public Person(String name, String address) {
this.name = name;
this.address = address;
}
@Override
public String toString() {
return "Person{" +
"name=" + Objects.toString(name, "未知姓名") + // 若 name 为 null,显示 "未知姓名"
", address=" + Objects.toString(address, "未知地址") + // 若 address 为 null,显示 "未知地址"
'}';
}
public static void main(String[] args) {
Person p1 = new Person("张三", "北京");
Person p2 = new Person(null, "上海");
Person p3 = new Person("李四", null);
Person p4 = new Person(null, null);
System.out.println("使用 Objects.toString 自定义默认值:");
System.out.println(p1);
System.out.println(p2);
System.out.println(p3);
System.out.println(p4);
}
}
关键字native的理解
使用 native 关键字说明这个方法是原生函数 ,也就是这个方法是用 C/C++ 等非 Java 语言实现的,并且被编译成了 DLL ,由 Java 去调用
- 本地方法是有方法体的,用 c 语言编写。由于本地方法的方法体源码没有对我们开源,所以我们看不到方法体
- 在 Java 中定义一个 native 方法时,并不提供实现体。
我们为什么要用native方法?
Java 使用起来非常方便,然而有些层次的任务用 java 实现起来不容易,或者我们对程序的效率很在意时,例如:Java 需要与一些底层操作系统或某些硬件交 换信息时的情况。native 方法正是这样一种交流机制:它为我们提供了一个非 常简洁的接口,而且我们无需去了解 Java 应用之外的繁琐的细节。
native 声明的方法,对于调用者,可以当做和其他 Java 方法一样使用
native method 的存在并不会对其他类调用这些本地方法产生任何影响,实际上调用这些方法的其他类甚至不知道它所调用的是一个本地方法。JVM 将控制调用本地方法的所有细节.