JavaSE学习之--抽象类和接口

news2024/12/23 16:59:19

 💕"没有眼泪我们就会迷路,彻底变成石头,我们的心会变成冰凌,吻会变成冰块。"💕

作者:Mylvzi 

 文章主要内容:JavaSE学习之--抽象类和接口 

一.抽象类

1.抽象类的定义

        我们知道,对象是通过类来描绘的。但并不是所有的类都用来描绘对象,当你抽象出来的类所含有的信息不足以描绘出一个完整的对象时,这个类就叫做“抽象类”(所有的实现细节需要子类来自己完成)

    我们发现shape中draw方法无法被具体实现,需要在对应的子类中实现,我们把这种方法叫做“抽象方法”,抽象方法就是所有子类方法的“蓝图”,根据这个蓝图来创建不同的子类特有的方法!同时,含有抽象方法的类就是抽象类!

2.抽象类的语法规则--abstract关键字

        抽象类往往是父类,子类继承抽象类实现子类特有的方法,创建抽象类是通过关键字abstract

// abstract关键字创建抽象类
abstract class Shape {
    // 创建抽象方法  不需要具体执行过程  是一个蓝图
    public abstract void draw();
}

3.抽象类的注意事项

1.抽象类无法实例化对象

  抽象类包含的信息无法描绘一个完整的对象,所以无法通过抽象类来实例化对象!

        Shape shape = new Shape();

2.抽象类中可以存在普通成员变量,普通成员方法

abstract class Shape {
    // 普通成员变量
    int a;

    // 普通方法
    public void method() {
        System.out.println("普通方法!");
    }

    // 抽象方法--》不能含有函数体
    abstract void method2();
}

class Rect extends Shape {
    

    @Override
    public void method() {
        System.out.println("普通方法!");
    }

    @Override
    void method2() {
        System.out.println("抽象方法!");
    }
}

3.抽象方法不能被private修饰

  如果一个方法被private修饰,那他只能在此类中使用,也就是只能在抽象类中使用,而抽象类的方法就是用来被子类重写的,所以抽象类方法不能被private修饰

 

4.抽象方法不能被final和static修饰

  如果一个方法被final修饰,则该方法无法被重写;如果被static修饰,那么此方法是属于类的,无法被重写 (要谨记,重写是子类对父类方法的重写,被static修饰的方法无法被继承或重写)

 

5.抽象类必须被继承,且子类必须重写抽象类所有的抽象方法(可以快捷键创建)

abstract class Shape {
    public abstract void draw();
    public abstract void trangle();

}

class Rect extends Shape {
    // 抽象方法必须被重写
    @Override
    public void draw() {
        System.out.println("矩形");
    }

    @Override
    public void trangle() {

    }
}

class Flower extends Shape {
    // trangle方法未被重写
    // 只重写一个抽象方法-->err
    // 所有的抽象方法必须被重写!
    @Override
    public void draw() {
        System.out.println("✿");
    }
}

注意到,这个报错的前一句是“Flower不是抽象的”,那是不是说明子类的也可以是抽象类呢?答案是可以的,Java中允许抽象子类,抽象子类将继续把抽象方法传递给他的子类,让他的子类来重写该抽象方法,形成了类的层级结构(但是不推荐将子类设置为抽象类,因为要重写多个方法) 

abstract class Shape {
    public abstract void draw();
    public abstract void trangle();

}
abstract class Flower extends Shape {
    // 只重写一个抽象方法-->err
    // 所有的抽象方法必须被重写!
    @Override
    public void draw() {
        System.out.println("✿");
    }
}

class SmallFlower extends Flower {
    // 父类Flower的抽象类
    @Override
    public void trangle() {
        
    }
    
    // shape的抽象类
    @Override
    public void draw() {
        draw();
    }
}

6.抽象类可以存在构造方法,供子类初始化父类的成员变量(抽象类自己无法初始化)

abstract class Shape {
    // 父类的成员变量
    int a;
    
    // 父类的构造方法
    public Shape(int a) {
        this.a = a;
    }
    // 抽象方法--》不能含有函数体
    abstract void method2();
}

class Rect extends Shape {
    int b;
    public Rect(int a,int b) {
        super(a);
        this.b = b
    }
 @Override
    void method2() {
        System.out.println(a);
    }
}

7.抽象类中不一定含有抽象方法,但抽象方法存在的类一定是抽象类!!! 

抽象类存在的意义是什么?多一层编译检查!!!

抽象类中抽象方法的存在强制了子类必须重写抽象方法,如果你想让所有的子类都包含某种方法,但具体的行为还要取决于具体的子类,就可以在抽象类(父类)中将此方法设置为抽象方法!


普通类的方法可以被继承,重写,但普通类的方法是具体实现的,有可能通过子类误调用成父类的方法,而抽象类的抽象方法不是具体实现的,他是一个“模板”,不含有函数体,所有的子类都可以根据自身情况去重写该方法,且不会出现误调用的情况!

二.接口(interface)

1.接口的定义

  接口常见于我们的生活之中,比如最经典的是一直被诟病的苹果接口,众所周知,苹果接口只能给苹果手机充电,适用范围仅限于苹果设备,无法通过type-c接口充电,也就是说只有符合苹果规范的设备才能使用苹果接口!苹果接口是所有苹果设备的“共同规范”!

  计算机中的接口也是类似的,是一种“共同规范”,简而言之类似于“父类”,所有能使用此接口的类都符合接口设定的规范(也就类含有接口中的方法!!!)

2.接口的语法

// 将class替换为interface
public interface IShape {
    // 接口中的所有方法都默认是public abstract的
    public abstract void method1();
    void method2();

    // 接口中的成员变量都是默认被public static final修饰的
}

1.接口关键字--》interface(替换原来的class),接口名一般以I开头(规范性)

2.关于接口方法

   接口中的所有方法都默认是public abstract的,为了代码的规范性和简洁性,推荐第二种写法(method2)

3.接口的实现--implements关键字

    我们创建了接口,接下来就要使用接口,相当于你把苹果手机插入到苹果接口,接下来就要使用苹果手机了;在Java中,我们称之为类实现接口

  接口的实现是通过关键字--implements 

public interface IShape {

void drawMap();

}

// implements关键字代表此类实现该接口
class Rect implements IShape {
    @Override
    public void drawMap() {
        System.out.println("矩形!!!");
    }
}

class Flower implements IShape {
    @Override
    public void drawMap() {
        System.out.println("❀❀❀");
    }
}


public class Testdemo {
    // 类似于类里面的向上转型!!!
    public static void drawMap2 (IShape ishape) {
        ishape.drawMap();
    }
    public static void main(String[] args) {
        drawMap2(new Rect());
        drawMap2(new Flower());
    }
}

注意事项:

1.子类与父类是继承关系,类与接口是实现关系,接口和父类很相似,很多用法都是类似的

2.接口中的方法都是抽象方法,实现接口的类必须重写接口中所有的方法!

3.接口中不能存在普通方法 !!!

4.一个例子

代码实现:

// USB接口
public interface IUSB {
    void openDevice();
    void closeDevice();
}

// 鼠标类
public class Mouse implements IUSB {
    // 实现USB接口就是重写USB接口中的所有方法
    @Override
    public void openDevice() {
        System.out.println("鼠标设备开启!");
    }

    @Override
    public void closeDevice() {
        System.out.println("鼠标设备关闭!");
    }

    public void clickMouse() {
        System.out.println("鼠标点击");
    }
}

// 键盘类
public class KeyBoard implements IUSB {
    // 实现USB类

    @Override
    public void openDevice() {
        System.out.println("键盘设备开启!");
    }

    @Override
    public void closeDevice() {
        System.out.println("键盘设备关闭!");
    }

    public void inKeyBoard() {
        System.out.println("键盘输入!");
    }
}

// 计算机开机--通过usb使用相关设备--关机
public class Computer {
    public void openComputer() {
        System.out.println("电脑开启!");
    }

    public void closeComputer() {
        System.out.println("电脑关闭!");
    }

    // 使用usb设备(类似于向上转型)
    public void useUSB(IUSB iusb) {
        // 注意这里开启,关闭设备是Mouse和KeyBoard类共有的,写在外面即可
        iusb.openDevice();

        // 利用关键字instanceof来判断引用的是哪个类
        if (iusb instanceof Mouse) {
            ((Mouse) iusb).clickMouse();
            //clickMouse是Mouse特有的方法,无法直接通过iusb实现
/*            Mouse mouse = (Mouse) iusb;
            mouse.clickMouse();*/
        }else if(iusb instanceof KeyBoard) {
            ((KeyBoard) iusb).inKeyBoard();
        }

        iusb.closeDevice();
    }
}

// 测试类
public class Testdemo3 {
    public static void main(String[] args) {
        Computer computer = new Computer();
        computer.openComputer();
        
        // 设备使用
        computer.useUSB(new Mouse());
        computer.useUSB(new KeyBoard());

        computer.closeComputer();
    }
}

3.接口的特性

1.接口类型是一种引用类型,但是不能直接new接口对象(类似于抽象类)

interface IUSB {
}
public class Test1 {
    public static void main(String[] args) {
        IUSB iusb = new IUSB();
        // err
    }

2.接口的方法默认都是被public abstract修饰的,使用其他修饰符会报错

interface IUSB {
    void method1();
    public abstract void method2();
    private void method3();
}

3.接口中的方法都不带有主体,只能有实现接口的类来实现

interface IUSB {
    void method1() {
        System.out.println("hehe");
    }
}

4.重写接口的抽象方法时,不能使用默认权限,只能是public修饰

  接口中的方法默认都是public权限的,实现接口的类中重写的方法的权限不能比Public低,所以只能是Public修饰的

interface IUSB {
    // 默认是public abstract
    void method();
}

class Mouse implements IUSB {
    @Override
    // err
    // 接口中的方法都是public权限
    void method() {
        System.out.println("hehe");
    }
}

5.接口中可以有变量,但变量都是被public static final修饰的

   static:说明变量是属于类的,能直接通过类来访问

   final:证明变量无法被修改

interface IUSB {
    int a = 10;
}

public static void main(String[] args) {
        System.out.println(IUSB.a);// 可以直接通过接口访问-->被static修饰
        IUSB.a = 20;// err无法被修改
}

6.接口中不能有构造方法和代码块

public interface INTERFACE {
    // err
    {

    }
    // err
    static {

    }
}

7.接口编译完成的字节码文件的后缀格式也是.class

8.在jdk8中:接口还可以包含default方法

   我们知道接口中的方法都是抽象方法,不含有主体,但在jdk8中引入了一个新特性-->default关键字,在接口中,如果方法被default修饰,则此方法可以含有主体,且在类中不必须被重写(相当于自动继承了)

interface IUSB {
    void method1();
    default void method2() {
        System.out.println("This is a default method!");
    }
}

class Mouse implements IUSB {
    @Override
    public void method1() {
        System.out.println("hehe");
    }
    // method2不重写也不会报错
    // 相当于“自动继承”
}
public class Test1 {
    public static void main(String[] args) {
        Mouse mouse = new Mouse();
        mouse.method1();
        // 没有重写method2,也能调用该方法,证明该方法被“自动继承”了
        mouse.method2();
    }
}

4.实现多接口

  Java中类是单继承的,一个类无法继承多个类(不能有多个父亲),但可以同时实现多个接口!

通过implements+','实现多个接口!!!

下面通过类来表示一组动物

class Animal {
    protected String name;

    public Animal(String name) {
        this.name = name;
    }
}

另外提供一些接口,分别表示 "会飞的", "会跑的", "会游泳的".

interface IFlying {
    void fly();
}

interface ISwimming {
    void swim();
}

interface IRunning {
    void run();
}

猫类:跑

// 猫类
class Cat extends Animal implements IRunning {
    public Cat(String name) {
        super(name);
    }

    // ctrl+i快速调出接口抽象方法的重写
    @Override
    public void run() {
        System.out.println(this.name + "猫正在跑步");
    }
}

狗类:跑,游

// 狗类 跑+游
class Dog extends Animal implements IRunning,ISwimming {
    public Dog(String name) {
        super(name);
    }

    @Override
    public void swim() {
        System.out.println(this.name + "正在狗刨");
    }

    @Override
    public void run() {
        System.out.println(this.name + "狗正在跑步");
    }
}

 鸭子类:跑,游,飞

// 鸭子类 跑,飞,游
class Duck extends Animal implements IRunning,ISwimming,IFlying {
    public Duck(String name) {
        super(name);
    }

    @Override
    public void fly() {
        System.out.println(this.name + "鸭子正在飞");
    }

    @Override
    public void swim() {
        System.out.println(this.name + "鸭子正在游泳");
    }

    @Override
    public void run() {
        System.out.println(this.name + "鸭子正在跑步");
    }
}

 注意:一个类实现多个接口时,所有接口中的抽象方法都要被重写!!!

public class Test1 {
    public static void main(String[] args) {
        Dog dog = new Dog("mimi");
        dog.run();
        dog.swim();
        System.out.println("================");
        Cat cat = new Cat("jiji");
        cat.run();
        System.out.println("================");

        Duck duck = new Duck("hehe");
        duck.fly();
        duck.run();
        duck.swim();
    }
}

上面代码展示了Java面向对象编程中最常见的用法:一个类继承一个父类,同时实现多个接口!!

可以理解为接口时“部分共性”,但本质还是为了代码复用

继承表达式是is-a语义,接口表达的含义是“具有xxx特性”

  • 猫是一种动物, 具有会跑的特性.
  • 狗也是一种动物, 既能跑, 也能游泳
  • 鸭子也是一种动物, 既能跑, 也能游, 还能飞

 这样做的好处是可以让程序员忘记类型,只关注实现特性,关注某个类是否具有该特性

比如我们可以创建一个Robot类,他也可以实现IRunning接口,尽管他不是Animal类!

class Robot implements IRunning {
    @Override
    public void run() {
        System.out.println("the robot is running!");
    }
}

 创建一个方法调用接口中的方法(接口作为参数)

public class Test1 {
    // 接口和类一样,可以作为参数类型,当对象传递时发生动态绑定
    // 谁实现了接口,谁就可以调用接口方法
    public static void Run(IRunning running) {
        running.run();
    }

    public static void main(String[] args) {
        // 匿名对象的直接调用
        Run(new Cat("jiji"));
        Run(new Dog("mimi"));
        Run(new Duck("hehe"));
        Run(new Robot());
    }

5.接口之间的继承

  接口之间也存在继承关系,不同于类的是,一个接口可以继承多个接口

interface IRunning {
    void run();
}

interface ISwimming {
    void swim();
}

// 两栖的动物, 既能跑, 也能游
interface IAmphibious extends IRunning, ISwimming {
}

class Frog implements IAmphibious {
...
}

      通过接口继承创建一个新的接口 IAmphibious 表示 "两栖的". 此时实现接口创建的 Frog 类, 就继续要实现 run 方 法, 也需要实现 swim 方法,同时继承两个接口!!!

6.抽象类和接口的区别:

1.成员变量:抽象类中可以含有普通成员变量,接口中的成员变量都是被public static final修饰的

2.方法:抽象类中既可以有抽象方法也可以有普通方法,而接口中所有的方法都是public abstract修饰

3.继承关系:一个类只能继承一个父类,但可以实现多个接口。但接口与接口之间只有继承关系 

 

三. Object类

  Object类是Java中默认提供的一个类,他是所有类的父类,是类的“祖先”。为什么会有这么一个类呢?其实也很好理解,Java是一个面向对象编程的语言,它存在很多自定义的类,那程序员是如何写出这些自定义的类呢?本质上还是通过Object这个祖先类来开发的!!!

1.Object可以接受任意类型的对象

class Person{};
class Stu{};

public class Test1 {
    // 此处发生了向上转型
    public static void func(Object object) {
        System.out.println(object);
    }
    public static void main(String[] args) {
        func(new Person());
        func(new Stu());
    }
}

2.Object本质上还是Java中的一个类,含有一些自带的方法

   

本文主要讲解equals,hashcode,toString方法

1.toString方法--获取对象信息

    前面我们已经重写了很多toString方法,当时可能很不理解为什么toString方法要重写呢?他是来源于哪个类呢?现在可以解释这个问题了,toString方法是Object类自带的一个方法,所有的类都是Object的子类,所以要重写toString方法来实现我想获取的信息

toString的源码

2.equals方法--进行对象比较

  在Java中,==比较时

如果左右两侧是基本数据类型(int等等),直接比较值的大小即可

如果左右两侧是引用数据类型(比如对象),实际上比较的是引用类型的地址是否相同

如果想比较对象中的内容,就要重写equals方法 

 equals的源码:

    public static void main(String[] args) {
        Person person1 = new Person();
        Person person2 = new Person();
        // 当两个引用类型比较时,实际上比较的是地址
        
        System.out.println(10 == 20);// false
        System.out.println(person1 == person2);// false
        System.out.println(person1.equals(person2));// false
        System.out.println("=======================");
        person2 = person1;
        System.out.println(person1.equals(person2));// true
    }

 

两个对象的比较是判断对象的地址是否相同,也即是在内存中存储的位置是否相同 

        Person person1 = new Person("lisi",18);
        Person person2 = new Person("lisi",18);
        System.out.println(person1.toString());
        System.out.println(person2.toString());

可以看出,两个对象的地址不同 

 

重写equals方法 :

假如我想使用Person类中的age来判断两个对象是否相同,此时就要重写equals方法

class Person{
    int age;

    public Person(int age) {
        this.age = age;
    }

    @Override
    public boolean equals(Object object) {
        // 两个对象地址相同,直接返回true
        if (this == object) return true;
        // 比较的对象是null或两个对象的类型不同,直接返回false
        if (object == null || getClass() != object.getClass()) return false;
        // 此处进行向下转型
        Person person = (Person) object;
        return age == person.age;
    }

    @Override
    public int hashCode() {
        return Objects.hash(age);
    }
}
public class Test1 {
    public static void main(String[] args) {
        Person person1 = new Person(10);
        Person person2 = new Person(20);
        Person person3 = new Person(20);

        System.out.println(person1.equals(person2));// false
        System.out.println(person2.equals(person3));// true
    }

注意:可以通过快捷键快速生成toString和hashcode的重写方法

结论:通过对象中的内容进行比较时,一定要重写toString方法!

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.内部类:

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

分类:(说内部类时一定要说清楚是什么内部类) 

实例内部类,静态内部类

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

class OuterClass {

    //一个类一个字节码文件
    class InnerClass {
        //实例内部类
    }

    static class InnerClass2{
        //静态内部类
    }
}

// 匿名内部类
interface A {
    void methodA();
}
public class Testdemo2内部类 {
    public static void main(String[] args) {
        new A(){// 以下代码相当于一个类实现了A接口,并重写了A的方法,但是这个类没有名称,所以叫做匿名内部类()通过接口实现
            @Override
            public void methodA() {
                System.out.println("hehe!!!");
            }
        };
    }
}

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

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1054404.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数仓精品理论-做大数据还有没有前途?

数仓精品理论-做大数据还有没有前途&#xff1f; 做大数据还有没有前途&#xff1f;大数据三要三不要我来讲讲大数据前景 做大数据还有没有前途&#xff1f; 先说&#xff0c;答案是肯定的&#xff0c;但一定要记住三要三不要。 datapulse官网&#xff1a; github:https://data…

cesium gltf控制

gltf格式详解 glTF格式本质上是一个JSON文件。这一文件描述了整个3D场景的内容。它包含了对场景结构进行描述的场景图。场景中的3D对象通过场景结点引用网格进行定义。材质定义了3D对象的外观,动画定义了3D对象的变换操作(比如选择、平移操作)。蒙皮定义了3D对象如何进行骨骼…

黑豹程序员-架构师学习路线图-百科:HTML-网页三剑客

为什么需要HTML 在网站技术发达之前&#xff0c;千年来我们获取信息是通过书籍。电脑流行后我们看文章、小说通过txt文件。看图通过单独的图片流量工具看单个的图片文件。 而HTML把文字和图片一起展示&#xff0c;让今天的电子书成为可能。 另外一点&#xff0c;我们的信息是…

【操作系统】了解Linux操作系统中PCB进程管理模块与进程PID

本篇要分享的内容是有关于操作系统中进程的内容。 目录 1.进程的简单理解 2.了解task_struct&#xff08;进程控制模块&#xff09;内容分类 3.task_struct&#xff08;进程控制模块&#xff09;中的PID 4.调用查看PID的函数 1.进程的简单理解 首先我们需要理解的是什么是…

简单讲解 glm::mat4

文章目录 前言一、下载glm库二、基本数学知识1. 三维中的 4 x 4 矩阵2.旋转3. 位移4. 缩放5. 组合 三、行向量或列向量四、总结 前言 glm库是OpenGL的官方数学库&#xff0c;里面内置多种跟几何变换相关的函数&#xff0c;熟练掌握glm库可以省下很多麻烦。 因为最近在项目中主…

鞋类 整鞋试验方法 剥离强度

声明 本文是学习GB-T 3903.3-2011 鞋类 整鞋试验方法 剥离强度. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 GB/T 3903 的本部分规定了整鞋鞋底与鞋帮或外底与外中底之间剥离强度的试验方法。 本部分适用于采用模压、硫化、注塑、灌注、胶…

暴力破解及验证码安全

1.暴力破解注意事项 1、破解前一定要有一个有郊的字典&#xff08;Top100 TOP2000 csdn QQ 163等密码&#xff09; https://www.bugku.com/mima/ 密码生成器 2、判断用户是否设置了复杂的密码 在注册页面注册一个,用简单密码看是否可以注册成功 3、网站是…

Jenkins集成AppScan实现

一、Jenkins上安装插件 在Jenkins里安装以下插件 ibm-security-appscanstandard-scanner 二、打开AppScan 1、配置需要扫描的地址 配置需要扫描的地址 2、记录好要扫描的URL登录序列 记录好要扫描的URL登录序列 3、导出要扫描的URL登录序列设置 导出要扫描的URL登录序列设置 三…

C程序设计内容与例题讲解 -- 第四章--选择结构程序设计(第五版)谭浩强

前言&#xff1a;在第三章我们介绍了顺序结构程序设计。在顺序结构中&#xff0c;各个语句是按自上而下的顺序执行的&#xff0c;执行完上一个语句就自动执行下一个语句&#xff0c;是无条件的&#xff0c;不必做任何判断。是这最简单的程序结构。实际上&#xff0c;在很多情况…

IDEA 配置 Maven(解决依赖下载缓慢)

IDEA 配置 Maven&#xff08;解决依赖下载缓慢&#xff09; 这一篇主要介绍 Maven 的基本用法。等我之后学习到框架知识时&#xff0c;会完善此部分内容。 一、Maven 简介 Maven 是专门用于管理和构建 Java 项目的工具&#xff0c;Apache Maven 是一个项目管理和构建工具&#…

ali内核服务器搭建Linux版本的小皮面板(微调)

一、搭建小皮面板 windows版本的小皮面板我们经常使用,早就熟悉了搭建和配置 那么这里我们就来使用Linux版本的小皮面板,看看如何进行操作 安装网址: https://www.xp.cn/linux.html 这里根据自己的操作系统选择合适的命令 我使用的是kali搭建,因此选择Debian安装脚本 注意:…

28270-2012 智能型阀门电动装置 学习笔记

声明 本文是学习GB-T 28270-2012 智能型阀门电动装置. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了智能型阀门电动装置(以下简称智能电装)的术语、技术要求、试验方法、检验规则、标 志、包装、运输和贮存条件等。 本标准适…

24Hibench

1. Hibench 官网 ​ HiBench is a big data benchmark suite that helps evaluate different big data frameworks in terms of speed, throughput and system resource utilizations. It contains a set of Hadoop, Spark and streaming workloads, including Sort, WordCou…

国庆放假作业2

1、select实现服务器并发 #include <myhead.h>#define PORT 7373 #define IP "192.168.1.9"int main(int argc,const char *argv[]) {//创建报式套接字int sfdsocket(AF_INET,SOCK_STREAM,0);if(sfd<0){ERR_MSG("socket error");return -1;}prin…

MATLAB与Python:优势与挑战

本文旨在探讨MATLAB与Python在特定领域内的使用情况&#xff0c;并分析两者之间的优势和挑战。 MATLAB和Python都是流行的编程语言&#xff0c;广泛应用于科学计算、数据分析和机器学习等领域。在某些领域&#xff0c;如航空航天工程、自动化和电子工程嵌入式系统开发等&#…

【分布式事务】

文章目录 解决分布式事务的思路seata四种模式1. XA模式2. AT模式AT模式与XA模式的区别是什么&#xff1f;脏写问题 3. TCC模式事务悬挂和空回滚 4. SAGA模式 四种模式对比口述AT模式与TCC模式高可用 什么是分布式事务&#xff1f; 分布式事务&#xff0c;就是指不是在单个服务或…

Arduino ESP32/ESP8266 +ST7735 1.8“tft中秋小时钟

Arduino ESP32 ST7735 1.8"tft中秋小时钟 &#x1f33c;原作者B站视频&#xff1a; ESP32中秋小时钟&#xff0c;表盘自动切换&#xff0c;代码开源&#xff0c;原图可下载&#xff08;案例应用&#xff09; &#x1f39e;tft ST7735 128160 1.8" 显示效果:(由于原作…

【vue3】wacth监听,监听ref定义的数据,监听reactive定义的数据,详解踩坑点

假期第二篇&#xff0c;对于基础的知识点&#xff0c;我感觉自己还是很薄弱的。 趁着假期&#xff0c;再去复习一遍 之前已经记录了一篇【vue3基础知识点-computed和watch】 今天在学习的过程中发现&#xff0c;之前记录的这一篇果然是很基础的&#xff0c;很多东西都讲的不够…

gcc中-I(大写的i)参数的作用

《gcc -I -L -l区别》是我参考的一篇博客。 gcc中-I参数可以帮助找到头文件的目录&#xff0c;比如在当前目录下有一个名为includeTestCom.c的c文件和名为includeCom的目录。 includeTestCom.c里边的内容如下&#xff1a; #include "good.h" int main(){printf(&q…

自己动手写编译器:实现命令行模块

在前面一系列章节中&#xff0c;我们完成了词法解析的各种算法。包括解析正则表达式字符串&#xff0c;构建 NFA 状态就&#xff0c;从 NFA 转换为 DFA 状态机&#xff0c;最后实现状态机最小化&#xff0c;接下来我们注重词法解析模块的工程化实现&#xff0c;也就是我们将所有…