JavaSE学习之--抽象类,接口,内部类(三)

简介: JavaSE学习之--抽象类,接口,内部类(三)

JavaSE学习之--抽象类,接口,内部类(二)+https://developer.aliyun.com/article/1413499

3.hashCode方法

 对象的hashCode值反应的是其在内存中的存储位置

hashCode的源码:

注:native代表此方法是由c/c++写的,无法查看真正的源码

public static void main(String[] args) {
        // 创建两个内容完全相同的person对象
        Person person1 = new Person("lisi",18);
        Person person2 = new Person("lisi",18);
        // 验证他们在内存中是否位于同一地址
        System.out.println(person1.hashCode());
        System.out.println(person2.hashCode());
        // 结果显示并不位于同一块地址
}

重写hashCode方法:

 同样的,我们也可以重写hashCode方法,实现只要内容完全相同,对象就处于内存中的同一块地址(快捷方法和toString一样)

// 重写hashCode方法
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
public static void main(String[] args) {
        // 创建两个内容完全相同的person对象
        Person person1 = new Person("lisi",18);
        Person person2 = new Person("lisi",18);
        // 验证他们在内存中是否位于同一地址
        System.out.println(person1.hashCode());
        System.out.println(person2.hashCode());
}

结论:

1、hashCode方法用来确定对象在内存中存储的位置是否相同

2、事实上hashCode() 在散列表中才有用,在其它情况下没用。在散列表中hashCode() 的作用是获取对象的 散列码,进而确定该对象在散列表中的位置。

总结:

 如果是自定义类型,记得一定要重写equals和hashCode方法,因为你的逻辑不是根据地址来判断类型是否相同,而是根据类型的属性来判断,所以要重写这两个方法

四. 接口使用实例

 1.Comparable接口和Comparator接口

先设定一个学生对象,并创建一个学生数组

class Student {
    String name;
    int age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
public class Test2 {
    public static void main(String[] args) {
        Student[] students = new Student[] {
                new Student("张三", 10),
                new Student("李四", 20),
                new Student("王五", 30),
                new Student("赵六", 40),
        };
    }
}

  假如我们现在想通过年龄进行排序,能否直接利用Arrays.sort呢?

Arrays.sort(students);// 可以直接这样排序吗?

 发现产生类型转换异常,原因在于之前使用Arrays.sort排序的数组是整形,可以直接通过比较数字的大小来进行排序的,而student是一个引用类型,无法直接进行排序,必须指定排序的依据,比如我现在想通过年龄进行排序,该怎么实现呢?通过Comparable接口!!!

class Student implements Comparable<Student>{
    String name;
    int age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    @Override
    // 重写compareTo
    public int compareTo(Object o) {
        Student s = (Student) o;
/*        if (this.age > s.age) {
            return 1;
        } else if (this.age == s.age) {
            return 0;
        }else {
            return -1;
        }*/
        return this.age-s.age;
    }
}
// 输出打印
Arrays.sort(students);
System.out.println(Arrays.toString(students));

 sort方法会自动调用compareTo方法,compareTo方法的参数是Object类型,要进行强制类型转换。通过重写Comparable接口中的compareTo方法实现根据类的属性进行比较的目的

 如果数据类型是数字可以直接调用sort方法,如果数组的数据类型是对象,则要使每个对象具有“可比较性”,就是要让对应的类实现Comparable接口,并重写compareTo方法 ,你需要告诉编译器是通过类的哪项属性进行比较的

但是我们发现这个方法也有一定的缺陷性,缺陷性在于我们重写compareTo方法时只能指定类的一个属性进行比较,比如在上述代码中的compareTo方法中,我们是通过age这个属性来比较的,但如果我们想通过名字比较呢?那不是要重写compareTo方法吗?可以看出,这样进行比较的方法可拓展性太差,对类的侵入性强,那有没有一种方法可以实现想通过什么比较就通过什么比较呢?答案是有的,即通过“比较器”来进行比较

使用方法:构造一个比较类,实现Comparator接口,重写compare方法

class Student{
    String name;
    int age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
//  <Student>表示比较的是Student对象(最好加上,在重写方法时会很方便)
class AgeComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.age- o2.age;
    }
}
class NameComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.name.compareTo(o2.name);
    }
}
public class Test2 {
    public static void main(String[] args) {
        Student student1 = new Student("zhangsan",10);
        Student student2 = new Student("lisi",15);
        AgeComparator ageComparator = new AgeComparator();
        System.out.println(ageComparator.compare(student1, student2));
        NameComparator nameComparator = new NameComparator();
        System.out.println(nameComparator.compare(student1,student2));
    }

解释一下:“return o1.name.compareTo(o2.name);”为什么通过name能直接调用compareTo方法?

因为name是一个字符串类型,属于String类,而String类中有compareTo方法!!!

注意:Comparator和Comparable是两个不同的接口,且用法也不同

Comparator是为了构造比较器

Comparable是使类具有可比较性

为了进一步加深对接口的理解, 我们可以尝试自己实现一个 sort 方法来完成刚才的排序过程(使用冒泡排序

// 能够排序的数组,要求数组元素必须具有可比较性,那把每个类型都设置为Comparable就能实现可比较性
    public static void bubble_sort(Comparable[] comparables) {
        for (int i = 0; i < comparables.length-1; i++) {
            for (int j = 0; j < comparables.length-1-i ; j++) {
                if(comparables[j].compareTo(comparables[j+1]) > 0) {
                    // 不符合顺序就交换位置
                    Comparable tmp = comparables[j];
                    comparables[j] = comparables[j+1];
                    comparables[j+1] = tmp;
                }
            }
        }
    }

2.Clonable 接口和深拷贝

 Java中内置了很多有用的方法,clone就是其一,clone方法是Object类内置的一个方法,clone方法能够实现对象的拷贝,但要合法使用clone方法,需要先实现Clonable接口,否则会报错

原型:

代码实现:

class Money implements Cloneable {
    public double money = 19.9;
}
// 要想能clone,必须先实现Clonable接口
class Stu implements Cloneable{
    int age;
    Money m;
    public Stu(int age) {
        this.age = age;
        m = new Money();
    }
    // 重写Object类中的clone方法
    // 访问权限是protected,只能跨包子类中使用
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
public class Testdemo1 {
    public static void main(String[] args) throws CloneNotSupportedException {
        Stu stu1 = new Stu(18);
        // 返回值是Object,所以必须进行强制类型转换
        Stu stu2 = (Stu) stu1.clone();
        System.out.println(stu1.m.money);// 19.9
        System.out.println(stu2.m.money);// 19.9
        System.out.println("================");
    }
}

注意:main后面必须添加:

throws CloneNotSupportedException

否则会报错

鼠标移动到clone,同时按下:alt+enter,点击第一行即可自动添加

现在我改变Stu2的m对象的money,再分别打印,看看会有什么结果

// 返回值是Object,所以必须进行强制类型转换
        Stu stu2 = (Stu) stu1.clone();
        System.out.println(stu1.m.money);// 19.9
        System.out.println(stu2.m.money);// 19.9
        System.out.println("================");
        stu2.m.money = 20;
        System.out.println(stu1.m.money);
        System.out.println(stu2.m.money);

可以发现不仅Stu2的Money改变了,Stu1的Money也改变了,可我们不是只改变了Stu2的money吗?原因在于Cloneable的拷贝是一种浅拷贝,所谓浅拷贝就是只拷贝当前对象(Stu),并不拷贝对象里面的对象(m) ,让我画图解释一下

有浅拷贝就有深拷贝,深拷贝就是为了解决浅拷贝的缺陷存在的

深拷贝:解决对象里面嵌套对象的拷贝

拷贝方法:先克隆大对象,再克隆小对象(先暂存大对象)

修改后的Stu类

// 要想能clone,必须先实现Clonable接口
class Stu implements Cloneable{
    int age;
    Money m;
    public Stu(int age) {
        this.age = age;
        m = new Money();
    }
    // 重写Object类中的clone方法
    // 访问权限是protected,只能跨包子类中使用
    @Override
    protected Object clone() throws CloneNotSupportedException {
        // 深拷贝:先克隆大对象,再克隆小对象
        Stu tmp = (Stu)super.clone();
        // 克隆小对象
        // 要克隆的是当前对象的里面的m对象,所以通过this表示当前对象调用
        // 将当前对象的m克隆到tmp的m之内,所以两边要对齐
        tmp.m = (Money)this.m.clone();
        return tmp;
    }
}

运行截图:

深拷贝 浅拷贝看的是代码的实现过程,浅拷贝并不实现对象内的对象的拷贝,深拷贝实现对象内的对象的拷贝

注意:能被拷贝的对象一定要实现clone接口!!!

五.补充:内部类,外部类

1.内部类:

定义在类里面或方法内部的类就叫做内部类

2.分类:

  说内部类时一定要说清楚是什么内部类,对于内部类来说重点掌握静态内部类和匿名内部类

1.实例内部类

 在类中定义的不被static修饰的类就叫做实例内部类

class OuterClass {
    int data1 = 1;
    int data2 = 2;
    // 实例内部类
    class InnerClass {
        int data3 = 3;
        int data4 = 4;
        // 实例内部类中的成员变量不能被static修饰,但可以是static final修饰
//        static int data5 = 5;
        static final int data10 = 10;
        public void method3() {
            System.out.println(data3);
        }
        public void method4() {
            // 访问外部类的成员变量,先实例化一个外部对象,通过外部对象访问成员变量
            OuterClass outerClass = new OuterClass();
            System.out.println(data4);
            System.out.println(data1);// 打印内部类的data1
//            System.out.println(data5);
            System.out.println("=================");
            System.out.println(OuterClass.this.data1);// 打印外部类的data1
            System.out.println(outerClass.data2);
        }
    }
}
public static void main(String[] args) {
        // 实例化一个实例内部类-->先实例化一个外部类对象,再实例化此外部类对象的内部类
        // 实例内部类依赖于外部类对象
        // 第一种写法(实例化出一个具体的外部类对象)
        OuterClass outerClass = new OuterClass();
        OuterClass.InnerClass innerClass = outerClass.new InnerClass();
        // 第二种写法(用了一个匿名的外部类对象)
        OuterClass.InnerClass innerClass2 = new OuterClass().new InnerClass();
//        OuterClass.InnerClass innerClass = new OuterClass.InnerClass();// err
        innerClass.method3();
        innerClass.method4();
    }

总结:

1.实例内部类依赖于外部类对象,必须先有外部类对象才能实例化内部类对象

2.实例化一个内部类对象的方法有两种

  • 先实例化出一个具体的外部类对象,再实例化此外部类对象的内部类对象(注意语法格式)
  • 利用匿名外部类对象实例化内部类对象(很快速,但无具体的外部类对象)

3.实例内部类中的成员变量不能被static修饰,因为被static修饰的变量不依赖于对象,在类加载时就创建,而实例内部类的实例化又依赖于对象,两者矛盾。但如果被final修饰,则编译通过(此时已经是常量了)

4.当外部类成员变量和内部类成员变量相同时,会默认打印内部类的成员变量,Outerclass.this.访问外部类的成员变量

2.静态内部类

 在类中定义的被static修饰的类就叫做静态内部类

  我们都知道,类的成员变量如果被static修饰,那就说明这个成员变量是“类变量”,不依赖于对象。同样的,对于静态内部类的获取不依赖于外部类对象!

class OuterClass {
    int data1 = 1;
    int data2 = 2;
    // 静态内部类
    static class InnerClass {
        int data3 = 3;
        int data4 = 4;
        static int data5 = 5;
        public void method3() {
            System.out.println(data3);
        }
        public void method4() {
            // 访问外部类的成员变量,先实例化一个外部对象,通过外部对象访问成员变量
            OuterClass outerClass = new OuterClass();
            System.out.println(data4);
            System.out.println(data5);
            System.out.println("=================");
            System.out.println(outerClass.data1);
            System.out.println(outerClass.data2);
        }
    }
}
public class Test1 {
    public static void main(String[] args) {
        // 如何实例化一个静态内部类对象呢?
        // 一定是先有外部类,再有内部类,也就是说一定要指明内部类的归属
        OuterClass.InnerClass innerClass = new OuterClass.InnerClass();
        innerClass.method3();
        innerClass.method4();
    }
}

总结:

1.静态内部类是定义在类内部的,被static修饰的类

2.实例化一个静态内部类:外部类名.内部类名--》一定是先有外部类,再有内部类,也就是说一定要指明内部类的归属

3.在内部类中想要访问外部类的成员变量--》先实例化一个外部类对象,通过外部类对象访问成员变量

3.匿名内部类(通过接口实现)

// 匿名内部类
interface A {
    void methodA();
}
public class Test1 {
    public static void main(String[] args) {
        int a = 10;
        a = 20;// err如果修改,在匿名内部类中就无法打印
        final int b = 20;
        new A(){// 以下代码相当于一个类实现了A接口,并重写了A的方法,但是这个类没有名称,所以叫做匿名内部类()通过接口实现
            @Override
            public void methodA() {
//                System.out.println(a);
                System.out.println(b);
                System.out.println("hehe!!!");
            }
        }.methodA();
    }
}

总结:

1.在匿名内部类中只能打印被final修饰数据(常量) 或只被初始化未被修改过的数据(如果修改,就无法访问),所以建议在匿名内部类中访问的数据都设置为final修饰的

2.匿名内部类是通过接口实现的,调用方法有两种

  • 直接在花括号外.方法名
  • 创建一个对象,通过对象调用

4.局部内部类

 在方法中定义的类就是局部内部类

public class OutClass {
int a = 10;
public void method(){
int b = 10;
// 局部内部类:定义在方法体内部
// 不能被public、static等访问限定符修饰
class InnerClass{
public void methodInnerClass(){
System.out.println(a);
System.out.println(b);
}
}
// 只能在该方法体内部使用,其他位置都不能用
InnerClass innerClass = new InnerClass();
innerClass.methodInnerClass();
}
public static void main(String[] args) {
// OutClass.InnerClass innerClass = null; 编译失败
}
}

总结:

1.局部内部类只能在定义的方法之中使用,子其他位置均无法使用

2.局部内部类非常少见,了解即可

补充:

一个类对应一个字节码文件

实例,静态内部类和匿名内部类的字节码文件名称不同,

实例,静态内部类的字节码文件名称:外部类名$内部类名

匿名内部类的字节码文件名称:外部类类名$数字

总结:
 本文主要讲述了抽象类,接口的定义,使用,以及两者的区别。抽象类和接口都是面向对象编程的重要语法,要好好理解他们的作用和基础的语法,会大大提高代码的效率!最后还介绍了一种特殊的类--》内部类 ,重点掌握静态内部类和匿名内部类

目录
相关文章
|
8天前
|
Java
Java中的抽象类:深入了解抽象类的概念和用法
Java中的抽象类是一种不能实例化的特殊类,常作为其他类的父类模板,定义子类行为和属性。抽象类包含抽象方法(无实现)和非抽象方法。定义抽象类用`abstract`关键字,子类继承并实现抽象方法。抽象类适用于定义通用模板、复用代码和强制子类实现特定方法。优点是提供抽象模板和代码复用,缺点是限制继承灵活性和增加类复杂性。与接口相比,抽象类可包含成员变量和单继承。使用时注意设计合理的抽象类结构,谨慎使用抽象方法,并遵循命名规范。抽象类是提高代码质量的重要工具。
44 1
|
7月前
|
Java
【JavaSE专栏64】抽象类和接口,不能被实例化的类有什么用?
【JavaSE专栏64】抽象类和接口,不能被实例化的类有什么用?
136 0
|
5月前
|
Java
【零基础学Java】—抽象方法和抽象类(二十二)
【零基础学Java】—抽象方法和抽象类(二十二)
|
8天前
|
Java
JavaSE学习之--抽象类,接口,内部类(一)
JavaSE学习之--抽象类,接口,内部类(一)
67 0
|
8天前
|
存储 Java 机器人
JavaSE学习之--抽象类,接口,内部类(二)
JavaSE学习之--抽象类,接口,内部类(二)
39 0
|
8天前
|
Java 编译器
JavaSE学习之--继承和多态(三)
JavaSE学习之--继承和多态(三)
42 0
|
8天前
|
Java
JavaSE学习之--继承和多态(二)
JavaSE学习之--继承和多态(二)
50 0
|
8天前
|
Java 编译器
JavaSE学习之--继承和多态(一)
JavaSE学习之--继承和多态
43 0
|
8天前
|
Java 编译器
【Java 抽象类&抽象方法】什么是抽象类&方法,如何定义,起什么作用?
【Java 抽象类&抽象方法】什么是抽象类&方法,如何定义,起什么作用?
|
8天前
|
Java
JavaSE碎碎念:抽象类继承被子类继承之后方法调用关系
JavaSE碎碎念:抽象类继承被子类继承之后方法调用关系

热门文章

最新文章