Object类和内部类
前言:
上期内容为大家带来了抽象类与接口的知识点的学习,这期内容我将为大家带来了
Object类与内部类的两种类的学习,这其内容是对于类与对象的补充。
一、Object类
1.object类初识
Object类是Java中一个默认提供的类,Java里面内置了object类,其余所有的类都是具有继承关系的,默认继承Object父类,那么也就是所有的对象都是可以使用object的引用来接收。
所以也可以发生了向上转型。
java
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方法
java
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方法,去获取对象成员变量的信息。
java
class Person{
public String name;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public int age;
@Override
public 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中,我们要比较两者数据之间的是否一样用等于号来进行,比如我们用==来进行比较,如果两者不一样,则返回假,一样就返回真。
java
public static void main(String[] args) {
int a = 10;
int b = 10;
System.out.println(a == b);
}
//这个时候我们打印会的是true
那么我们比较两个自定义类型呢?
java
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方法了。
java
class Person{
public String name;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public int age;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public 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方法
比如:
java
@Override
public boolean equals(Object obj) {
if (obj == null) {//如果是被比较的对象是空类型
return false ;
}
if(this == obj) {//两者指向了一个对象的空间,那么获得的地址是一个,所以两者是一个
return true ;
}
// 不是Person类对象
if (!(obj instanceof Person)) {//判断person
return 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++来进行写的方法,所以我们也看不到的这个方法中的具体实现。
比如我们这个时候直接打印一下它们的哈希值:
java
System.out.println(person1.hashCode());
System.out.println(person2.hashCode());

我们可以发现两者的哈希值是不同的,但是也代表两者的地址不同,因为我们认为两个名字相同,年龄相同的对象,将存储在同一个位置,所以这个时候我们就需要重写hashCode()方法了
我们在Person类中重写了hashCode方法:
java
@Override
public int hashCode() {
return Objects.hash(name, age);
}
这个时候再来比较person1和person2是否相同:
我们可以看到结果是两者的哈希值是一样的。
如果我们没有重写了hashCode方法,那么就是直接打印的是两者的哈希值。
如果我们判断名字和姓名是一样,那么就不需要重复在开辟空间了,所以我们这个时候可以重写hashcode
所以我们重写的hashcode会发现某些对象出现在堆中同一个位置
后期讲到哈希表的时候便会知道(现实逻辑中的)如果两个一样的对象想放在同一个位置,
此时我就可以利用重写hashcode这个方法来实现
结论:
1、hashcode方法用来确定对象在内存中存储的位置是否相同
2、事实上hashCode() 在散列表中才有用,在其它情况下没用。在散列表中hashCode() 的作用是获取对象的
散列码,进而确定该对象在散列表中的位置。
二、内部类
1.内部类初识:
内部类:
在 Java 中,可以将一个类定义在另一个类或者一个方法的内部,前者称为内部类,后者称为外部类。内部类也是封装的一种体现。
关于类,我们深知是一个类只有一个对应的字节码文件,但是如果我们遇到这种内部类,会发现一个类中还有一定的类,我们先写一个内部类,观察一下:
java
class OutClass{
class InnerClass{
}
}
通过IDEA打开了本地文件的字节码的文件:
我们可以看到它有OutClass和OutClass$InnerClass两个字节码文件
所以我们就可以知道内部类是外部类内部类,当然还有一种是外部类数字,我们会在后面讲解。
2.内部类的分类:
有三种内部类:静态、实例、匿名、局部 总共三种内部类;
使用频率:实例>静态>匿名>局部
2.(1).实例内部类
实例内部类就是在普通的类中去定义一个类,比如我们定义一个实例内部类:
java
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方法");
}
}
我们定义一个实例内部类,这个时候我们来执行这个实例内部类
java
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();
}
}
这时候就会显示报错:
所以实例内部类不能同正常的类去进行实例化,我们应该注意,用外部类访问内部类
所以我们用外部类名去访问内部类:
java
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呢?我们可以加上引用,
java
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是在内部类中,我们没有内部类的实例化,外部类是无法访问的其内部类中的成员变量的
所以我们需要先进行内部类的实例化才可以执行这个代码:
java
public void test(){
System.out.println("外部类的test方法");
//访问内部类成员:
/*System.out.println(data4);//这就报错了*/
InnerClass innerClass = new InnerClass();
System.out.println(innerClass.data4);
}
这个时候我们会发现内部类在外部类中的实例化的时候居然与普通的类进行实例化是一样的
总结:
获取实例内部类的对象的时候,依赖于外部类的对象;
要获得实例内部类对象,一定要先有外部类对象的访问。
这个时候我们前面的代码在内部类中提到了一个静态成员变量data6这个时候我们可以看一下这个代码行的结果:
java
//外部类中也有静态成员
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修饰我们可以确定是一个常量,并且表示对于开发者和编译器来说这只有一个这样的静态成员
所以我们最后应该写成:
java
public static final int data6 = 6;
通过类名进行访问,我们可以得到这样的结果:
2.(2).静态内部类
静态内部类就是在普通的类中用static来定义一个类
java
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方法");
}
}
}
这个时候我们看到这个静态类中还有非静态成员,但是我们用类名访问只能是静态成员和静态方法,所以我们还需要把这个静态类进行实例化:
java
public class Test2 {
public static void main(String[] args) {
OutClass2.InnerClass innerClass = new OutClass2.InnerClass();
innerClass.test();
}
}
虽然我们把静态内部类进行实例化,但是我们还需要访问内部类中的静态成员最好还是用类名进行访问
我们执行完了这个程序后,发现了报错,我们在静态内部类无法直接访问外部类的非静态成员变量
所以我们也可以在静态内部类中进行实例化外部类,从而去访问它的成员变量
java
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
java
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).匿名内部类
匿名内部类是没有类名的一种类,但是前提也是根据接口,来实现的
那么我们需要首先定义一个接口:
java
interface A{
void testA();
}
public class Test2 {
public static void main(String[] args) {
new A(){
@Override
public void testA() {
System.out.println("X!");
}
}
}
}
这种方式就是匿名内部类,这时候我们发现这个类没有名字,实现了接口A,也重写了接口A中的方法
那么这种类怎么去调用呢?
我们可以在尾大括号后加入了.testA(),便可以调用这个类
当然我们可以像类一样去进行接口的**"实例化"**,得到了也是匿名内部类,
java
public class Test2 {
public static void main(String[] args) {
int val = 10;
A a = new A() {
@Override
public void testA() {
System.out.println("值:" + val);
}
};
a.testA();
System.out.println(val);
}
}
去定义一个接口,去实现这个方法,但是我们依旧发现了这个类没有名字
正常类应该有class来定义,这里面的类没有用class来定义,所以它依旧是匿名内部类
如果我们这个时候去修改val的值,会发生什么呢?
java
public class Test2 {
public static void main(String[] args) {
int val = 10;
val = 100;
A a = new A() {
@Override
public void testA() {
System.out.println("值:" + val);
}
};
a.testA();
System.out.println(val);
}
}

可是执行完之后我们发现它报错了。
因为在匿名内部类当中能够访问的是没有被修改过的数据
这种限制叫做变量的捕获
所以默认在匿名内部类中能访问的是被final修饰的变量,隐式的
2.(4).局部内部类
局部内部类:定义在方法的内部
它不能被public,static等访问限定符修饰
编译器也有自己独立的字节码文件,命名格式:外部类名字$数字内部类名字.class
这种类只能在方法体中使用:
java
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();
}
}

我们在日常中很少用到局部内部类,所以我们在这里不太过深入进行讲解。
好了,感谢大家的支持,这期的内容就先到这里了,当然如果某些地方的不足,欢迎大家在评论区中指正!