当前位置: 首页 > news >正文

Java---Object和内部类

Object类和内部类

  • 前言:
  • 一、Object类
    • 1.object类初识
    • 2.Object的方法
      • 2.(1).获取对象的信息--toString方法
      • 2.(2).对象比较equals方法
      • 2.(3).hashCode方法
  • 二、内部类
    • 1.内部类初识:
    • 2.内部类的分类:
      • 2.(1).实例内部类
      • 2.(2).静态内部类
      • 2.(3).匿名内部类
      • 2.(4).局部内部类

前言:

上期内容为大家带来了抽象类与接口的知识点的学习,这期内容我将为大家带来了
Object类与内部类的两种类的学习,这其内容是对于类与对象的补充。

一、Object类

1.object类初识

Object类是Java中一个默认提供的类,Java里面内置了object类,其余所有的类都是具有继承关系的,默认继承Object父类,那么也就是所有的对象都是可以使用object的引用来接收。
所以也可以发生了向上转型。

class Person1{}
class Student{}
class Test {public static void main(String[] args) {function(new Person1());function(new Student());}public static void function(Object obj) {System.out.println(obj);}
}

在这里插入图片描述
这个时候我们可以看到打印的是对象所在位置的类型名和哈希值。

在这里插入图片描述
通过API文档,我们可以看到object类中有许多方法,这些方法我们都要全部掌握!
但是我们已经学习了clone方法了,这节我们要掌握三种方法,分别是:toString,hashCode,equals方法
hashCode我们后期在哈希表会再次进行详细的讲解。

2.Object的方法

2.(1).获取对象的信息–toString方法

class Person{public String name;public Person(String name, int age) {this.name = name;this.age = age;}  
}
public class Test {public static void main(String[] args) {Person person1 = new Person("张三",10);System.out.println(person1);Person person2 = new Person("张三",10);System.out.println(person2);
}   

在这里插入图片描述
这个时候我们看到打印的是对象所在位置的类型的名和哈希值
我们看一下Object类中toString方法的源码:
在这里插入图片描述
获取的是对象所在位置的类型的名和哈希值。
如果我们想要打印对象的成员信息,这个时候我们可以重写这个toString方法,去获取对象成员变量的信息。

class Person{public String name;public Person(String name, int age) {this.name = name;this.age = age;}public int age;@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}
}
public class Test {public static void main(String[] args) {Person person1 = new Person("张三",10);System.out.println(person1);Person person2 = new Person("张三",10);System.out.println(person2);
}    

既然重写toString方法,那么就会发生了多态。
在这里插入图片描述

2.(2).对象比较equals方法

在Java中,我们要比较两者数据之间的是否一样用等于号来进行,比如我们用==来进行比较,如果两者不一样,则返回假,一样就返回真。

public static void main(String[] args) {int a = 10;int b = 10;System.out.println(a == b);
}
//这个时候我们打印会的是true

那么我们比较两个自定义类型呢?

 public static void main(String[] args) {Person person1 = new Person("张三",10);System.out.println(person1);Person person2 = new Person("张三",10);System.out.println(person2);System.out.println("=========");System.out.println(person1 == person2);
}

在这里插入图片描述
这里表明了两个person不是同一个,为什么会出现这种情况呢?那么我们此时来观察一下他们所处的地址
在这里插入图片描述
我们看到了这两地址也就是哈希值是完全不同的,所以我们可以明白"=="
是比较自定义类型的地址是否是完全相同的。
有一个方法叫做equals,也是比较两个对象是否相同,下面我们可以看看这个方法的源码:
在这里插入图片描述
我们会发现它返回的是也是地址,比较判断对象是否指向了一个同一个地址。
所以直接调用还是会为false

但是我们不想让它比较的是地址,就像上述代码一般,两个张三的实例化,就应该是同一个对象,所以这个时候就要涉及到重写equals方法了。

class Person{public String name;public Person(String name, int age) {this.name = name;this.age = age;}public int age;@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Person person = (Person) o;return age == person.age && Objects.equals(name, person.name);}
}
public class TestDemo {public static void main(String[] args) {Person person1 = new Person("张三",10);System.out.println(person1);Person person2 = new Person("张三",10);System.out.println(person2);System.out.println("=========");System.out.println(person1 == person2);//比较两者实例化后是不是一个person//结果是false//通过直接打印了它们的地址,发现两者的地址不同//所以  时候比较的就是变量中的值System.out.println(person1.equals(person2));}
}

这个时候我们重写equals方法,这个时候的结果就是:true
在这里插入图片描述
上述代码这个重写equals方法是IDEA为我们进行重写的,我们可以自己手动重写一个equals方法
比如:

 @Overridepublic boolean equals(Object obj) {if (obj == null) {//如果是被比较的对象是空类型return false ;}if(this == obj) {//两者指向了一个对象的空间,那么获得的地址是一个,所以两者是一个return true ;}// 不是Person类对象if (!(obj instanceof Person)) {//判断personreturn false ;}Person person = (Person) obj ; // 向下转型,比较属性值,两者的属性是自己定义的,不是object类return this.name.equals(person.name) && this.age==person.age ;}

两者的逻辑是一样的,都可以实现equals方法重写。

结论:所以通过equals方法的比较,我们要明白:如果是以后自定义的类型,那么一定要记住重写equals方法
并且在比较对象中内容是否相同的时候,一定要重写equals方法。

2.(3).hashCode方法

哈希值:hashcode这个方法会帮我们算出一个地址以16进制输出
我们看到对象那个的打印的时候,我们会发现打印了哈希值,也就是这个方法
在这里插入图片描述
hashCode方法,那么我们可以进行查看一下hashCode的源码:
在这里插入图片描述
native的方法代表着我们是本地方法,用C/C++来进行写的方法,所以我们也看不到的这个方法中的具体实现。
比如我们这个时候直接打印一下它们的哈希值:

System.out.println(person1.hashCode());
System.out.println(person2.hashCode());

在这里插入图片描述

我们可以发现两者的哈希值是不同的,但是也代表两者的地址不同,因为我们认为两个名字相同,年龄相同的对象,将存储在同一个位置,所以这个时候我们就需要重写hashCode()方法了
我们在Person类中重写了hashCode方法:

	@Overridepublic int hashCode() {return Objects.hash(name, age);}

这个时候再来比较person1和person2是否相同:
在这里插入图片描述
我们可以看到结果是两者的哈希值是一样的。
如果我们没有重写了hashCode方法,那么就是直接打印的是两者的哈希值。
如果我们判断名字和姓名是一样,那么就不需要重复在开辟空间了,所以我们这个时候可以重写hashcode
所以我们重写的hashcode会发现某些对象出现在堆中同一个位置

后期讲到哈希表的时候便会知道(现实逻辑中的)如果两个一样的对象想放在同一个位置,
此时我就可以利用重写hashcode这个方法来实现
结论:
1、hashcode方法用来确定对象在内存中存储的位置是否相同
2、事实上hashCode() 在散列表中才有用,在其它情况下没用。在散列表中hashCode() 的作用是获取对象的
散列码,进而确定该对象在散列表中的位置。

二、内部类

1.内部类初识:

内部类:
在 Java 中,可以将一个类定义在另一个类或者一个方法的内部,前者称为内部类,后者称为外部类。内部类也是封装的一种体现。
关于类,我们深知是一个类只有一个对应的字节码文件,但是如果我们遇到这种内部类,会发现一个类中还有一定的类,我们先写一个内部类,观察一下:

class OutClass{class InnerClass{}
}

通过IDEA打开了本地文件的字节码的文件:
在这里插入图片描述

我们可以看到它有OutClass和OutClass$InnerClass两个字节码文件
所以我们就可以知道内部类是外部类$内部类,当然还有一种是外部类$数字,我们会在后面讲解。

2.内部类的分类:

有三种内部类:静态、实例、匿名、局部 总共三种内部类;
使用频率:实例>静态>匿名>局部

2.(1).实例内部类

实例内部类就是在普通的类中去定义一个类,比如我们定义一个实例内部类:

class OutClass1{public int data1 = 1;public int data2 = 2;public static int data3 = 3;class InnerClass{public int data1 = 11111;public int data4 = 4;private int data5 = 5;public static int data6 = 6;public void test(){System.out.println(data1);System.out.println(data5);System.out.println("内部类的test方法");}}public void test(){System.out.println("外部类的test方法");}
}

我们定义一个实例内部类,这个时候我们来执行这个实例内部类

public class Test2 {public static void main(String[] args) {OutClass1 outClass1 = new OutClass1(); System.out.println(outClass1.data1);System.out.println("================");InnerClass innerClass = new InnerClass();}
}     

这时候就会显示报错:
在这里插入图片描述
所以实例内部类不能同正常的类去进行实例化,我们应该注意,用外部类访问内部类
所以我们用外部类名去访问内部类:

OutClass1 outClass1 = new OutClass1(); 
OutClass1.InnerClass innerClass = outClass1.new InnerClass();//我们用对象名去调用内部类名
innerClass.test();//上述这种写法是把outClass的对象名给其去调用
//我们也可以直接把对象引用:
OutClass1.InnerClass innerClass2 = new OutClass1().new InnerClass();
innerClass2.test();

当内部类数据成员和外部类数据成员是同名的时候,会怎么样?,让我们来进行看一下:

在这里插入图片描述
这次执行成功了,但是我们会发现了外部类与内部类均有data1,但是我们发现打印的是内部类中的data1,
这个时候就是前面所讲的就近原则,Java的编译器进行检查的时候,发现内部类的data1与其位置最近,那么优先访问它
那么我们要访问外部的data1呢?我们可以加上引用,

class InnerClass{public int data1 = 11111;public int data4 = 4;private int data5 = 5;public static int data6 = 6;public void test(){System.out.println(data1);System.out.println(this.data1);//我们加上了this引用,这个时候表明这个data1是哪一类中的System.out.println(OutClass1.this.data1);   System.out.println(data5);System.out.println("内部类的test方法");}
}

在这里插入图片描述

这个this就表明当前谁在调用这个内部类的成员变量,然后我们用外部类的this
那么可能会有人说,在外部类的方法中去访问内部类的成员,我们可以先来看一下:
在这里插入图片描述
//这个时候IDEA也就会自动爆红,显示出错,但是我们也可以来运行一下代码,但是也会发现它们会报错;
在这里插入图片描述
因为这个data4是在内部类中,我们没有内部类的实例化,外部类是无法访问的其内部类中的成员变量的
所以我们需要先进行内部类的实例化才可以执行这个代码:

public void test(){System.out.println("外部类的test方法");//访问内部类成员:/*System.out.println(data4);//这就报错了*/InnerClass innerClass = new InnerClass();System.out.println(innerClass.data4);
}

这个时候我们会发现内部类在外部类中的实例化的时候居然与普通的类进行实例化是一样的

总结:
获取实例内部类的对象的时候,依赖于外部类的对象;
要获得实例内部类对象,一定要先有外部类对象的访问。

这个时候我们前面的代码在内部类中提到了一个静态成员变量data6这个时候我们可以看一下这个代码行的结果:

//外部类中也有静态成员
class InnerClass{public int data1 = 11111;public int data4 = 4;private int data5 = 5;public static int data6 = 6;public void test(){System.out.println(data1);System.out.println(data5);System.out.println("内部类的test方法");}
}

等待我们执行完成后会发现出现了错误,
在这里插入图片描述

因为static修饰的成员是不依赖于类本身,并且是最先执行的,而实例内部类确实需要依赖于外部类的对象,实例内部类是没有独立的储存空间来储存静态成员,如果直接用static修饰的话,会造成外部类的执行在加载的时候,导致访问静态成员会比较混乱,静态成员是属于类的,所以用final修饰我们可以确定是一个常量,并且表示对于开发者和编译器来说这只有一个这样的静态成员

所以我们最后应该写成:

public static final int data6 = 6;

通过类名进行访问,我们可以得到这样的结果:
在这里插入图片描述

2.(2).静态内部类

静态内部类就是在普通的类中用static来定义一个类

class OutClass2 {public int data1 = 1;public int data2 = 2;public static int data3 = 3;static class InnerClass{public int data4 = 4;private int data5 = 5;public static int data6 = 6;public void test(){System.out.println(data1);System.out.println("内部类的test方法");}}
}

这个时候我们看到这个静态类中还有非静态成员,但是我们用类名访问只能是静态成员和静态方法,所以我们还需要把这个静态类进行实例化:

public class Test2 {public static void main(String[] args) {OutClass2.InnerClass innerClass = new OutClass2.InnerClass();innerClass.test();}
}

虽然我们把静态内部类进行实例化,但是我们还需要访问内部类中的静态成员最好还是用类名进行访问
我们执行完了这个程序后,发现了报错,我们在静态内部类无法直接访问外部类的非静态成员变量
在这里插入图片描述
所以我们也可以在静态内部类中进行实例化外部类,从而去访问它的成员变量

static class InnerClass{public int data4 = 4;private int data5 = 5;public static int data6 = 6;public void test(){OutClass2 outClass2 = new OutClass2();System.out.println(outClass2.data1);System.out.println(data4);System.out.println(data5);System.out.println(data6);System.out.println("内部类的test方法");}
}

执行完后我们可以看到,可以直接打印出来
在这里插入图片描述
我们会看到两个特殊的变量:静态变量data3和data6两者
在外部类中也可以直接用内部类的类名访问其中的静态成员变量data6
但是我们在静态内部类中可以直接访问外部类中的静态成员变量data3

static class InnerClass{public int data4 = 4;private int data5 = 5;public static int data6 = 6;public void test(){System.out.println(data3);}
}

在这里插入图片描述

在静态内部类中我们可以直接访问外部类中的静态成员变量,非静态成员需要将外部类进行实例化,才可以进行访问。

2.(3).匿名内部类

匿名内部类是没有类名的一种类,但是前提也是根据接口,来实现的
那么我们需要首先定义一个接口:

interface A{void testA();
}
public class Test2 {public static void main(String[] args) {new A(){@Overridepublic void testA() {System.out.println("X!");}}}
}

这种方式就是匿名内部类,这时候我们发现这个类没有名字,实现了接口A,也重写了接口A中的方法
那么这种类怎么去调用呢?
我们可以在尾大括号后加入了.testA(),便可以调用这个类
在这里插入图片描述
当然我们可以像类一样去进行接口的**“实例化”**,得到了也是匿名内部类,

public class Test2 {public static void main(String[] args) {int val = 10;A a = new A() {@Overridepublic void testA() {System.out.println("值:" + val);}};a.testA();System.out.println(val);}
}

去定义一个接口,去实现这个方法,但是我们依旧发现了这个类没有名字
正常类应该有class来定义,这里面的类没有用class来定义,所以它依旧是匿名内部类
在这里插入图片描述
如果我们这个时候去修改val的值,会发生什么呢?

public class Test2 {public static void main(String[] args) {int val = 10;val = 100;A a = new A() {@Overridepublic void testA() {System.out.println("值:" + val);}};a.testA();System.out.println(val);}
}

在这里插入图片描述
可是执行完之后我们发现它报错了。

因为在匿名内部类当中能够访问的是没有被修改过的数据
这种限制叫做变量的捕获
所以默认在匿名内部类中能访问的是被final修饰的变量,隐式的

2.(4).局部内部类

局部内部类:定义在方法的内部
它不能被public,static等访问限定符修饰
编译器也有自己独立的字节码文件,命名格式:外部类名字$数字内部类名字.class
这种类只能在方法体中使用:

public class Test4 {public void func(){class Inner{public int data = 1;}Inner inner = new Inner();System.out.println(inner.data);}public static void main(String[] args) {Test4 test4 = new Test4();test4.func();System.out.println();}
}

在这里插入图片描述
我们在日常中很少用到局部内部类,所以我们在这里不太过深入进行讲解。

好了,感谢大家的支持,这期的内容就先到这里了,当然如果某些地方的不足,欢迎大家在评论区中指正!

相关文章:

  • 协程补充---viewModelScope 相关知识点
  • 蓝桥杯 19. 植树
  • 事务隔离(MySQL)
  • 5.4 - 5.5Web基础+c语言拓展功能函数
  • sqli-labs靶场11-17关(POST型)
  • 深度解析:从 GPT-4o“谄媚”到 Deepseek“物理腔”,透视大模型行为模式的底层逻辑与挑战
  • ns-3仿真_pcap抓取时间太长问题_log打印时间显示5s结束,pcap抓包抓到了10s
  • Kubernetes控制平面组件:Controller Manager详解
  • ByteArrayInputStream 类详解
  • 什么是“系统调用”
  • JS DAY3
  • STM32 PulseSensor心跳传感器驱动代码
  • 【实战教程】React Native项目集成Google ML Kit实现离线水表OCR识别
  • unity TMP字体使用出现乱码方框
  • 【QT】QT中的软键盘设计
  • Java开发者面试实录:微服务架构与Spring Cloud的应用
  • Java面试场景分析:从音视频到安全与风控的技术探讨
  • 查看并升级Docker里面Jenkins的Java17到21版本
  • suna工具调用可视化界面实现原理分析(二)
  • 数据资本化:解锁数字资产价值的证券化与质押融资之路
  • 体坛联播|米兰逆转热那亚豪取3连胜,阿诺德官宣离开利物浦
  • 贵州省委省政府迅速组织开展黔西市游船倾覆事故救援工作
  • 因雷雨、沙尘等天气,这些机场航班运行可能受影响
  • 全国铁路昨日发送2311.9万人次,同比增长11.7%创历史新高
  • 经营业绩持续稳中向好,国铁集团2024年度和2025年一季度财务决算公布
  • 李开复出任福耀科技大学理事会理事,助力学校AI战略