零基础 “入坑” Java--- 十三、再谈类和接口

发布于:2025-07-21 ⋅ 阅读:(11) ⋅ 点赞:(0)


在之前的学习中,我们已经了解了有关类以及接口的知识,在本章节中,我们继续来探究类和接口相关的知识。

一、Object类

Object类是Java默认提供的一个类。在Java中,除了Object类,其余所有的类都存在继承关系,默认继承Object类。即所有类实例化出的对象都可以使用Object类的引用接收,如:

class A {

}
class B {

}
public class Test {
    public static void test(Object object) {
        System.out.println(object);
    }
    public static void main(String[] args) {
        test(new A());
        test(new B());
    }
}

Object类中也存在一些定义好的方法,在编写代码过程中经常使用,接下来我们就来学习一下。

1.获取对象信息

如果想要打印对象中的内容,可以通过重写toString的方式来完成,这一点在之前的学习中也已经介绍过,我们来看一下源码即可:
在这里插入图片描述

查看源码方式:双击Shift,因为toString方法在Object类中,所以输入Object,再寻找toString方法即可。

2.对象比较:equals方法

在Java中,使用 == 进行比较时:

a.如果 == 两边为基本数据类型,则比较的是变量的值是否相同。
b.如果 == 两边为引用类型,则比较的是"地址"是否相同。
c.当 == 两边为引用类型时,如果想比较其内容是否相同,就可以重写Object中的equals方法,使用equals进行比较(equals默认按照"地址"进行比较)。

举个例子:

class Person {
    public String name;
    public int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
public class Test {
    public static void main(String[] args) {
        Person person = new Person("小王", 20);
        Person person1 = new Person("小王", 20);
        System.out.println(person.equals(person1));
    }
}

定义一个Person类,实例化两个对象,并将其姓名和年龄都进行了统一,没重写equals方法时,运行结果为"false"。

class Person {
    public String name;
    public int age;
    public Person(String name, int age) {
        this.name = name;
        this.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);
    }
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}
public class Test {
    public static void main(String[] args) {
        Person person = new Person("小王", 20);
        Person person1 = new Person("小王", 20);
        System.out.println(person.equals(person1));
    }
}

当我们重写了equals方法之后,此时运行结果就为"true"。

重写equals方法也很简单:在空行单击鼠标右键选择生成,再选择equals() 和 hashCode(),再一直点击下一步即可:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
当涉及到需要比较对象中的内容是否相同时,一定要重写equals方法。

对于什么是hashCode(),等我们学习完哈希表之后就会知道。

二、再谈接口

1.比较相关接口

我们来看这样一个例子:

class Person {
    public String name;
    public int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class Test {
    public static void main(String[] args) {
        Person person = new Person("小王", 20);
        Person person1 = new Person("小李", 18);
    }
}

我们定义一个Person类,实例化两个对象,现在我们想比较两个对象的大小:

    public static void main(String[] args) {
        Person person = new Person("小王", 20);
        Person person1 = new Person("小李", 18);
        System.out.println(person > person1); //error
    }

对于二元运算符,两边的操作数应该为基本数据类型,但此时比较的为引用类型的数据,并不能进行比较。如果我们想比较引用类型的数据,可以实现一个接口:

class Person implements Comparable<Person> {
    public String name;
    public int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    @Override
    public int compareTo(Person o) {
        //原始语句
        //return 0;
        if (this.age > o.age) {
            return 1;
        } else if (this.age < o.age) {
            return -1;
        } else {
            return 0;
        }
    }
}
public class Test {
    public static void main(String[] args) {
        Person person = new Person("小王", 20);
        Person person1 = new Person("小李", 18);
        System.out.println(person.compareTo(person1)); //1
    }
}

我们实现Comparable接口,"<>"中的为类名,并重写compareTo方法;在这个例子中,我们根据年龄的大小比较两个数据。

重写方法可以使用鼠标右键,生成的方式完成。

对于自定义类型,要想比较大小,就需要实现Comparable这个接口,实现这个接口就需要重写compareTo方法:
在这里插入图片描述
对于compareTo方法中的实现逻辑,还可以写为:

    public int compareTo(Person o) {
        return this.age - o.age;
    }

返回值为两个数据中年龄的差值。

equals和compareTo:equals比较的是 是否相等,返回值为布尔类型;compareTo比较的是 大小关系,返回值为整型。

使用Comparable这个接口虽然能解决比较大小的问题,但这个接口对类的侵入性较强:在上面这个例子中,我们通过年龄对 对象进行比较,但当我们想根据其他变量比较大小时,就需要重新编写和比较相关的所有的代码,修改成本过高。

我们可以通过Comparator这个接口来解决这个问题:

class Person {
    public String name;
    public int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
//根据年龄比较
class AgeCom implements Comparator<Person> {
    @Override
    public int compare(Person o1, Person o2) {
        //原始语句
        //return 0;
        return o1.age - o2.age;
    }
}
//根据姓名比较
class NameCom implements Comparator<Person> {
    @Override
    public int compare(Person o1, Person o2) {
        //name为String类型,String中实现了Comparable接口,重写了compareTo方法
        return o1.name.compareTo(o2.name);
    }
}
public class Test {
    public static void main(String[] args) {
        Person person = new Person("小王", 20);
        Person person1 = new Person("小李", 18);
        //实例化对象,通过对象调用类中的方法
        AgeCom ageCom = new AgeCom();
        System.out.println(ageCom.compare(person, person1));
        NameCom nameCom = new NameCom();
        System.out.println(nameCom.compare(person, person1));
    }
}

在实现Comparator接口时,我们需要根据比较的逻辑重写compare方法。 实现Comparator接口后,代码的修改和增加 效率更高,使用更方便。

2.Cloneable接口和深拷贝

class Person {
    public String name;
    public int age;
    public Person(int age) {
        this.age = age;
    }
    //重写clone方法
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                '}';
    }
}
public class Test {
    public static void main(String[] args) {
        Person person = new Person(20);
        Person person1 = person.clone();
    }
}

此时代码并不能正常运行,会编译报错,我们需要解决三处错误才能使代码正常运行:
在这里插入图片描述
第一处错误是有关异常的知识,我们之后会学习到。

第三处错误:
Cloneable接口的源码为:
在这里插入图片描述
我们称之为空接口/标记接口,其作用为:证明当前类是可以被克隆的。

我们还需要注意一点:
在这里插入图片描述


解决完这三处问题后,代码就可以正常运行了:

class Person implements Cloneable {
    public String name;
    public int age;
    public Person(int age) {
        this.age = age;
    }
    //重写clone方法
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                '}';
    }
}
public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person = new Person(20);
        Person person1 = (Person) person.clone();
        System.out.println(person);
        System.out.println(person1);
    }
}

运行结果为:
在这里插入图片描述
执行图为:
在这里插入图片描述


我们对代码做出如下修改:

class Money {
    public double money = 66.6;
}
class Person implements Cloneable {
    public String name;
    public int age;
    //组合思想
    public Money m;
    public Person(int age) {
        this.age = age;
        this.m = new Money();
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                '}';
    }
}
public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person = new Person(20);
        Person person1 = (Person) person.clone();
        System.out.println(person.m.money); //66.6
        System.out.println(person1.m.money); //66.6
        System.out.println("===============");
        person1.m.money = 88.8;
        System.out.println(person.m.money); //88.8
        System.out.println(person1.m.money); //88.8
    }
}

我们使用组合的思想增加一个成员变量,并在构造方法中为其实例化一个对象。我们想修改person1的值,但最后却将person和person1的值都修改了。

在修改值的过程中,会发生如下逻辑
在这里插入图片描述
此时发生的这种现象,我们就称之为浅拷贝浅拷贝并没有将 对象中的对象进行克隆

但我们只想修改person1的值,应该怎么办呢?

我们对代码进行修改:

//实现接口
class Money implements Cloneable {
    public double money = 66.6;
    //重写方法
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

对于Person类中的clone方法的内容进行修改 (核心!!!)

    @Override
    protected Object clone() throws CloneNotSupportedException {
        //return super.clone();
        Person tmp = (Person) super.clone();
        tmp.m = (Money) this.m.clone();
        return tmp;
    }

修改完以上两处,再次运行代码,只有person1的值被修改了。

流程图为:
在这里插入图片描述
此时发生的这种现象,我们就称之为深拷贝区分深浅拷贝主要取决于代码的实现过程。

三、内部类

在Java中,可以将一个类定义在另一个类或者方法的内部,前者称为内部类,后者称为外部类。 内部类也是封装思想的一种体现。

内部类可以分为:静态内部类、实例内部类、匿名内部类、局部内部类(几乎不用)。

class A {
    class B {
        //实例内部类
    }
    static class C {
        //静态内部类
    }
}
interface D {
    void test();
}
public class Test {
    public static void main(String[] args) {
        new D() {
            @Override
            public void test() {
            }
        }; //匿名内部类
    }
}

在编译之后,内部类也会生成独立的字节码文件。一个类对应一个字节码文件。

1.匿名内部类

对于匿名内部类,可以使用以下两种方式调用类中的方法:

        //方式一
        new D() {
            @Override
            public void test() {
                System.out.println("嘻嘻");
            }
        }.test(); //匿名内部类
        //方式二
        D d = new D() {
            @Override
            public void test() {
                System.out.println("嘻嘻");
            }
        }; //匿名内部类
        d.test();

对于匿名内部类我们可以认为:有一个类实现了一个接口,并在类中重写了接口中的方法。


    public static void main(String[] args) {
        int ret = 100;
        D d = new D() {
            @Override
            public void test() {
                System.out.println("值为:" + ret);
            }
        }; //匿名内部类
        d.test();
    }

在匿名内部类中可以使用初始化的变量,

    public static void main(String[] args) {
        int ret = 100;
        ret = 200;
        D d = new D() {
            @Override
            public void test() {
                System.out.println("值为:" + ret); //error
            }
        }; //匿名内部类
        d.test();
    }

但当变量的值被修改后,代码就会报错。在匿名内部类中,不能访问被修改的数据。

2.实例内部类

我们再来看实例内部类:

class Outer {
    public int a = 1;
    private int b = 2;
    public static int c = 3;
    //实例内部类
    class Inner {
        public int x = 1;
        private int y = 2;
        public void test() {
            System.out.println("内部类");
        }
    }
    public void test() {
        System.out.println("外部类");
    }
}
public class Test2 {
    public static void main(String[] args) {
        //实例化对象
        //方式一
        Outer.Inner inner = new Outer().new Inner();
        //方式二
        Outer outer = new Outer();
        Outer.Inner inner1 = outer.new Inner();
        
        System.out.println(inner.x); //1
        inner.test(); //内部类
    }
}

在使用内部类时,内部类的实例化需要通过外部类才能实现。

    class Inner {
        public int x = 1;
        private int y = 2;
        public static int y = 3;
        public void test() {
            System.out.println("内部类");
        }
    }

我们在实例内部类中添加一个静态成员变量,会报错,这是因为:static修饰的成员不依赖于对象,而内部类却需要依赖于外部类对象。

        public static final int z = 3;

使用final关键字修饰就可以解决这个错误,final修饰的为常量。


    class Inner {
        public int a = 666;
        public int x = 1;
        private int y = 2;
        public static final int z = 3;
        public void test() {
            System.out.println("内部类");
            System.out.println(a); //666
            System.out.println(Outer.this.a); //1
        }
    }

当外部类和实例内部类中存在同名的成员变量时,优先访问实例内部类中的成员变量;如果想要访问外部类中的成员变量,需要使用 “外部类类名.this.成员变量” 的方式访问。

实例内部类也受访问修饰限定符的约束。

3.静态内部类

class Outer1 {
    public int a = 1;
    private int b = 2;
    public static int c = 3;
    //静态内部类
    static class Inner {
        public int x = 1;
        private int y = 2;
        public static int z = 3;
        public void test() {
            System.out.println(x); //1
            System.out.println(c); //3
            //a为非静态
            System.out.println(a); //error
            System.out.println("内部类");
        }
    }
}
public class Test3 {
    public static void main(String[] args) {
        //实例化
        Outer1.Inner inner = new Outer1.Inner();
        inner.test();
    }
}

静态内部类的实例化也较为特殊,需注意。

在静态内部类中不可以直接调用静态内部类外的 非静态的成员。如果想访问,需要通过外部类对象进行访问:

        public void test() {
            System.out.println(x); //1
            System.out.println(c); //3
            //实例化外部类对象
            Outer1 outer1 = new Outer1();
            //通过外部类对象访问
            System.out.println(outer1.a); //1
            System.out.println("内部类");
        }

4.局部内部类

    public void test() {
        //局部内部类
        class Inner {
            public int num = 1;
        }
        Inner inner = new Inner();
        System.out.println(inner.num);
    }

局部内部类只能在当前定义的 方法体的 内部使用,不能被访问修饰限定符 修饰,很少使用。


Ending。


网站公告

今日签到

点亮在社区的每一天
去签到