【Java学习】面向对象编程(二)【详解篇14】

抽象类

什么是抽象类?

普通类代码示例:

 class Shape {//此时这个Shape类里面的方法没有具体的实现
    public void draw(){//如果一个方法没有具体的实现,那么这个方法就可以是一个抽象方法。
        
    }
 }

1.抽象方法:一个方法如果被abstract修饰,那么这个方法就是抽象方法。抽象方法可以没有具体的的实现。直接就可以以分号结束。
2.包含抽象方法的类,叫做抽象类。需要用关键字abstract来修饰这个类。

抽象类代码示例:

abstract class Shape {//抽象类Shape
    public abstract void draw();//此时可以给draw方法加一个abstract关键字,如果一个方法被abstract所修饰,那么这个方法叫做抽象方法

}

也就是说抽象类里面一定是包含抽象方法的,当然不包含也可以,但是如果类里面有一个方法是抽象方法,那么这个类就一定要给它声明成抽象类。

抽象类需要注意的点

1.抽象类不可以被实例化,普通的类可以实例化一个对象,但是抽象类不能够实例化一个对象,所以不能这样写Shape shape=new Shape();

代码示例:

abstract class Shape {//抽象类Shape
    public abstract void draw();//此时可以给draw方法加一个abstract关键字,如果一个方法被abstract所修饰,那么这个方法叫做抽象方法
}
public class TestDemo1 {
    public static void main(String[] args) {
        Shape shape=new Shape();//error:抽象类不可以被实例化
    }
}

image-20210914110607175

2.抽象类(类内的数据成员)和普通类没有太多区别,唯一的区别就是普通类可以被实例化,抽象类不可。

代码示例:

abstract class Shape {//抽象类Shape
    public abstract void draw();//抽象方法 
    public int age;//成员属性
    private int name;
    public void func(){//成员方法
        
    }
}

抽象类存在的作用是什么?

3.抽象类存在的最大意义就是为了被继承。

4.如果一个类继承了抽象类,那么这个类必须要重写这个抽象类当中的抽象方法,不重写就会报错。

代码示例:

abstract class Shape {//抽象类Shape
    public abstract void draw();//抽象方法
   
    }
  class Cycle extends Shape {//普通类Cycle继承抽象类Shape
      @Override  //Override是重写的意思, @Override叫做注解,给一个方法加上注解说明这个方法是可以重写的方法,
     //如果一个类继承了抽象类,那么这个类必须要重写这个抽象类当中的抽象方法,不重写就会报错。
      public void draw(){//实现方法的重写
          System.out.println("画一个○");
      }
  }
    class React extends Shape {//普通类React继承抽象类Shape
        @Override   //重写快捷键:ctrl+O或alt+insert;
        public void draw() {//实现方法的重写
            System.out.println("画一个♦");
        }
    }
    class Quert extends Shape {
        @Override
        public void draw() {
            System.out.println("画一个□");
        }
    }
    class Flower extends Shape{
        @Override
        public void draw() {
            System.out.println("画一朵❀");
        }
    }

5.如果不想重写抽象类当中的抽象方法,可以在继承抽象类的这个类前面加上abstract,让他也变成抽象类,此时就成了抽象类继承抽象类。但是如果一个类想要继承抽象类继承抽象类的类,还是需要重写抽象类当中的继承方法的,否则这个类就会报错。

(即:当抽象类A继承抽象类B,那么A可以不重写B中的方法,但是一旦A要是再被继承,那么一定还要重写这个抽象方法。)

代码示例:

abstract class Shape {//抽象类Shape
    public abstract void draw();//抽象方法
   
    }
abstract  class React extends Shape {//抽象类React继承抽象类Shape

    }
class Flower extends React{//普通类继承抽象类React
    @Override//重写抽象方法
    public void draw() {
        System.out.println("画一朵❀");  
    }

image-20210914100406059
6.类和方法之所以声明为抽象的就是为了被继承再重写,所以抽象类或者是抽象方法一定是不能被final和private修饰的,因为一旦被修饰就不能重写抽象方法了。
image-20210914105828953
image-20210914105651482
7.虽然Shape这个类不能被实例化对象了,但是它仍然可以发生向上转型,多态和运行时绑定。

abstract class Shape {//此时这个Shape类里面的方法没有具体的实现
    public abstract void draw();//如果一个方法没有具体的实现,那么这个方法就可以是一个抽象方法。
}   
    class Cycle extends Shape {//Cycle继承抽象类Shape
        @Override  //Override是重写的意思, @Override叫做注解,给一个方法加上注解说明这个方法是可以重写的方法,
        //如果一个类继承了抽象类,那么这个类必须要重写这个抽象类当中的抽象方法,不重写就会报错。
        public void draw() {//实现方法的重写
            System.out.println("画一个○");
        }
    }
    class React extends Shape {//React继承抽象类Shape
        @Override
        public void draw() {
            System.out.println("画一个♦");
        }
    }
    class Flower extends React {
        @Override
        public void draw() {
            System.out.println("画一朵❀");
        }
        class Quert extends Shape {
            @Override
            public void draw() {
                System.out.println("画一个□");
            }
        }
    }
    public class TestDemo1 {
        public static void drawMap(Shape shape) {
            shape.draw();
        }
        public static void main(String[] args) {
            //Shape shape=new Shape();//抽象类不可以被实例化
            //虽然Shape这个类不能被实例化对象了,但是它仍然可以发生向上转型,多态和运行时绑定
            Shape shape1 = new Cycle();
            Shape shape2 = new React();
            Shape shape3 = new Flower();
            drawMap(shape1);
            drawMap(shape2);
            drawMap(shape3);
        }
    }

打印结果:image-20210914110426048

面试小问题:

抽象类和普通类的区别是什么?

答:抽象类内的数据成员和普通类没有太多区别,唯一的区别就是普通类可以被实例化,抽象类不可

抽象类和接口的区别是什么?

答:想要回答这个问题我们得学习什么是接口。

接口

什么是接口?

接口是抽象类的更进一步. 抽象类中还可以包含非抽象方法, 和字段. 而接口中包含的方法都是抽象方法, 字段只能包含静态常量。

接口的代码示例:

interface IShape {//我们创建接口的时候, 接口的命名一般以大写字母 I 开头.
    void draw();
    //接口中的方法一定是抽象方法, 因此可以省略 abstract
    //接口中的方法一定是 public, 因此可以省略 public
}
class Cycle implements IShape {//Cycle 使用 implements 继承接口. 此时表达的含义不再是 "扩展", 而是 "实现"
    @Override
    public void draw() {
        System.out.println("○");
    }
}
public class TestDemo {
    public static void main(String[] args) {
        IShape shape = new Cycle();//在调用的时候同样可以创建一个接口的引用, 对应到一个子类的实例.
        // shape:接口引用,对应 new Cycle():子类实例
        shape.draw();
    }
}

语法规则:

  • 使用 interface 定义一个接口

  • 接口中的方法一定是抽象方法, 因此可以省略 abstract

  • 接口中的方法一定是 public, 因此可以省略 public

  • Cycle 使用 implements 继承接口. 此时表达的含义不再是 “扩展”, 而是 “实现”

  • 在调用的时候同样可以创建一个接口的引用, 对应到一个子类的实例.

  • 接口不能单独被实例化

扩展(extends) 和实现(implements)的区别

扩展指的是当前已经有一定的功能了, 进一步扩充功能.

实现指的是当前啥都没有, 需要从头构造出来.

实现接口的八大注意事项

1、接口当中的方法都是抽象方法。

image-20210916111805306

2、其实可以有具体实现的方法,这个方法是被default修饰的。(在JDK1.8中加入的),但是一般情况下不使用它。

image-20210916111636408

3、接口当中定义的成员变量,默认是常量,而抽象类和普通类当中默认的就不是常量,除非给他们加一个final才是常量。

interface Shape{//Shape就是一个接口
    public static final int a=10;//接口当中定义的成员变量,默认是常量
}
public class TestInterFace {

}

4、在抽象类当中什么类型的成员变量都可以定义,但是在接口当中定义的成员变量一定是被public static final所修饰,并且对于字段来说, 接口中只能包含静态常量(final static).

而方法都默认为public abstract,如果写成private或protected就会报错。

总之就是一句话,接口当中的成员变量默认是:public static final,成员方法是: public abstract。

interface Shape{//Shape就是一个接口
    public abstract void draw();//抽象方法
    public static final int num=10;//接口当中定义的成员变量(字段),默认是静态常量
    //接口当中的成员变量默认是:public static final,成员方法是: public abstract,
    //所以也可以这样写。
    int num=10;
    void draw();
}
public class TestInterFace {

}

其中的 public, static, final 的关键字都可以省略. 省略后的 num 仍然表示 public 的静态常量.

提示:
1.我们创建接口的时候, 接口的命名一般以大写字母 I 开头.
2. 接口的命名一般使用 “形容词” 词性的单词.
3. 阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性

5、接口是不可以单独实例化的,这样写就会报错Shape shape=new Shape();

​可以这样想接口比抽象类还抽象呢,抽象类都不可以实例化,接口当然也不可以了。
image-20210916114904445
6、接口和类之间的关系不能用extends了,应该用implements。
image-20210916130615263
接口和继承一样,一旦一个类实现了这个接口Shape,一定要重写这个接口当中的方法。
image-20210916130656896

7、接口出现是为了解决Java单继承的问题。(后面会有详细解释)

因为在Java当中只能继承一个类,不能继承多个类,所以是单继承,而有了接口,就可以解决Java单继承的问题,实现继承多个类的能。

image-20210916131010968

8、只要这个类实现了该接口,就可以进行向上转型了。

接口也是可以发生向上转型的,前提是把一个对象赋值给接口类型之前一定要保证这个类实现了这个接口。

代码示例:

interface Shape {//Shape就是一个接口
    void draw();
    class Cycle implements Shape {//Cycle实现了接口Shape
      //接口和类之间的关系不能用extends了,应该用implements,一旦一个类实现了这个接口Shape,一定要重写这个接口当中的方法
        @Override  //Override是重写的意思, @Override叫做注解,给一个方法加上注解说明这个方法是可以重写的方法,
      //如果一个类实现了接口,那么这个类必须要重写这个接口当中的抽象方法,不重写就会报错。
        public void draw() {//实现方法的重写
            System.out.println("画一个○");
        }
    }
    class React implements Shape {//React实现接口Shape
        @Override
        public void draw() {
            System.out.println("画一个♦");
        }
    }
    class Flower extends React {//Flowe继承React
        @Override
        public void draw() {
            System.out.println("画一朵❀");
        }
    }
    class Quert implements Shape {//Quert实现接口Shape
        @Override
        public void draw() {
            System.out.println("画一个□");
        }
    }
    public class TestInterFace {
        public static void drawMap(Shape shape) {
            shape.draw();//发生多态
        }
        public static void main(String[] args) {
            Shape shape1 = new Cycle();//接口也是可以发生向上转型的,前提是把一个对象赋值给接口类型之前
            //一定要保证这个类实现了这个接口。
            Shape shape2 = new React();
            Shape shape3=new Quert();
            Shape shape4=new Flower();
            drawMap(shape1);//发生向上转型
            drawMap(shape2);
            drawMap(shape3);
            drawMap(shape4);
        }
    }
}

打印结果:image-20210920072118773

实现多个接口

有的时候我们需要让一个类同时继承自多个父类. 这件事情在有些编程语言通过 多继承 的方式来实现的.

然而 Java 中只支持单继承, 一个类只能 extends 一个父类. 但是可以同时实现多个接口, 也能达到多继承类似的效果.

现在我们通过类来表示一组动物

class Animal{
    protected String name;
    public Animal(String name){//提供带有一个参数的构造方法。
        this.name=name;
    }
}

另外我们再提供一组接口, 分别表示 “会飞的”, “会跑的”, “会游泳的”.

interface IFlying{//提供接口,接口的名字一般以动词为主
    void fly();//提供抽象方法
}
interface IRunning{
    void run();
}
interface ISwimming{
    void swim();
}

接下来我们创建几个具体的动物

猫, 是会跑的,

鱼, 是会游的.

青蛙, 既能跑, 又能游(两栖动物).

"鸳鸯"水陆空三栖。

class Cat extends Animal implements IRunning{//猫继承于Animal并且实现IRunning这个接口,因为猫是一个动物并且拥有跑步这个功能
    public Cat(String name){//因为Animal是有构造方法的所以需要提供带有一个参数的构造方法
        super(name);
    }
    public void run(){//实现IRunning这个接口的同时需要重写IRunning的run方法。
        System.out.println(this.name+"正在欢快的跑步");
    }
}
class Fish extends Animal implements ISwimming{//鱼继承于Animal并且实现ISwimming这个接口,因为鱼是一个动物并且拥有游泳这个功能
    public Fish(String name){//提供带有一个参数的构造方法
        super(name);
    }
    public void swim(){
        System.out.println(this.name+"正在欢快的游泳");
    }
}
class Frog extends Animal implements IRunning,ISwimming{//实现多个接口,也就解决了多继承的问题,要什么就实现什么就ok了
    public Frog(String name){//因为Animal是有构造方法的所以需要提供带有一个参数的构造方法
        super(name);
    }
   //ctrl+O:重写 IRunning的run方法和ISwimming的swim方法
    @Override
    public void run() {
        System.out.println(this.name+"正在跑!!");
    }
    @Override
    public void swim() {
        System.out.println(this.name+"正在游!!");
    }
}
class YuanYang extends Animal implements IRunning, ISwimming, IFlying {
    public YuanYang(String name) {
        super(name);
    }
    @Override
    public void fly() {
        System.out.println(this.name + "正在用翅膀飞");
    }
    @Override
    public void run() {
        System.out.println(this.name + "正在用两条腿跑");
    }
    @Override
    public void swim() {
        System.out.println(this.name + "正在漂在水上");
    }
}
//从实现多个接口的方式可以发现,把run方法、swim方法和fly方法写在Animal当中是不合适的,如果写在Animal当中就需要实现多个不同的类,因为不是每一种动物都会这些功能,所以应该写到接口当中,继承Animal之后,直接要什么就实现什么接口就ok了。

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

继承表达的含义是 is - a 语义, 而接口表达的含义是 具有 xxx 特性,它主要是对方法的一个抽象 .

猫是一种动物, 具有会跑的特性.

青蛙也是一种动物, 既能跑, 也能游泳

鸳鸯也是一种动物, 既能跑, 也能游, 还能飞

这样设计有什么好处呢? 时刻牢记多态的好处, 让程序猿忘记类型. 有了接口之后, 类的使用者就不必关注具体类型, 而只关注某个类是否具备某种能力.

比如:

class Robot implements IRunning{//使用接口,不管是什么类,只要会跑就行,会跑就可以实现跑的功能

    @Override
    public void run() {
        System.out.println("我是机器人,我也会跑步!");
    }
}
public class TestMoreExtends {
    public static void main(String[] args) {
        IRunning iRunning=new Robot();
        iRunning.run();
    }
}

打印结果:image-20210921081716591

忘记类型,实现多态(继承)的代码示例:

class Robot implements IRunning{//使用接口,不管是什么类,只要会跑就行,会跑就可以实现跑的功能
    @Override
    public void run() {
        System.out.println("我是机器人,我也会跑步!");
    }
}
public class TestMoreExtends {
    //忘记类型,实现多态,实现一个方法, 叫 "散步",参数类型为IRunning
    public static void walk(IRunning running){//
        System.out.println("我带着伙伴去散步");
        running.run();
    }
    //忘记类型,实现多态,实现一个方法, 叫 "玩耍",参数类型为ISwimming
    public static void play(ISwimming swimming){
        System.out.println("我和小伙伴在游泳");
        swimming.swim();
    }
    public static void main(String[] args) {
        IRunning iRunning1=new Robot();//接口可以发生向上转型
        walk(iRunning1);//也可以发生多态,调用walk,把参数传进去
        //对于接口来说,不关心传参传的是谁,只要是实现了IRunning接口的就可以运行起来。
        IRunning iRunning2=new Frog("青蛙");
        walk(iRunning2);
        ISwimming iSwimming=new Fish("小鱼");
        play(iSwimming);
    }
}

打印结果:image-20210921085143868

接口可以继承一个接口, 达到复用的效果. 使用 extends 关键字,接口间的继承相当于把多个接口合并在一起

接口使用实例

给对象数组排序

给定一个学生类

class Student {//实现Comparable这个接口说明这个接口一定是有一个抽象方法
    public String name;
    public int age;
    public int score;
    public Student(String name, int age, int score) {//提供构造方法
        this.name = name;
        this.age = age;
        this.score = score;
    }
    //想要打印这三个属性的话,需要重写toString方法
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }

再给定一个学生对象数组, 对这个对象数组中的元素进行排序(按年龄降序)

public class TestDemo {
    public static void main(String[] args) {
        Student student1=new Student("小王",18,99);
        Student student2=new Student("小明",21,69);
        Student student3=new Student("小李",17,79);
        /*if(student1<student2){//仅仅将这两个学生进行比较是错误的,因为不知道比较的是什么,name还是age,还是score?
        所以自定义类型是不能进行比较的,想要比较的话,需要在学生类后面加上implements Comparable<Student>,尖括号里面写的东西代表你当前要比较的是什么
        }*/
       /* if(student1.compareTo(student2)<0){
            System.out.println("student1的年龄小于student2的年龄");
        }*/
        Student[] students=new Student[3];//定义一个数组,里面放三个学生,并且对这三个学生进行排序。
        students[0]=student1;//学生1放在0号下标
        students[1]=student2;
        students[2]=student3;
      

按照我们之前的理解, 数组我们有一个现成的 sort 方法, 能否直接使用这个方法呢?

  Arrays.sort(students);//Arrays.sort默认从小到大排序
        //想要排序的话,底层势必会有两个学生进行两两比较
        System.out.println(Arrays.toString(students));
    }

运行结果出错:抛出异常

image-20210921185251552

仔细思考, 不难发现, 和普通的整数不一样, 两个整数是可以直接比较的, 大小关系明确. 而两个学生对象的大小关系怎么确定? 需要我们额外指定.

让我们的 Student 类实现 Comparable 接口, 并实现其中的 compareTo 方法

class Student implements Comparable<Student>{//实现Comparable这个接口说明这个接口一定是有一个抽象方法
    public String name;
    public int age;
    public int score;

    public Student(String name, int age, int score) {//提供构造方法
        this.name = name;
        this.age = age;
        this.score = score;
    }
    //想要打印这三个属性的话,需要重写toString方法
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }

    @Override
    public int compareTo(Student o) {//重写compareTo方法,用当前对象和你传过来的参数进行比较
        //如果是根据年龄来比较的话就可以这样写
        //自定义类型需要在compareTo内部指定比较方式,即从小到大排列还是从大到小排列。
        if(this.age>o.age){//如果是大于号,就是从小到大排列,如果是小于号,就是从大到小排列,返回1
            return 1;

        }else if(this.age==o.age){//如果相等,返回0
            return 0;
        }else {//返回-1,说明小于
            return -1;
        }
    }
}

在 sort 方法中会自动调用 compareTo 方法. compareTo 的参数是 Object , 其实传入的就是 Student 类型的对象.

然后比较当前对象和参数对象的大小关系(按年龄来算).

  • 如果当前对象应排在参数对象之前, 返回小于 0 的数字;
  • 如果当前对象应排在参数对象之后, 返回大于 0 的数字;
  • 如果当前对象和参数对象不分先后, 返回 0.

执行结果:符合预期
image-20210921184939054
完整代码如下:

package com.xiaoba.demo4;
import java.util.Arrays;
/**
 * 一般情况下:自定义类型进行比较,需要是可以比较的,通过实现Comparable接口,比较是从大到小排列还是从小到大排列需要自己重写compareTo方法
 * 根据自己的想法来安排排列方式
 */
//比较自定义类型可以让这个类实现Comparable接口
class Student implements Comparable<Student>{//实现Comparable这个接口说明这个接口一定是有一个抽象方法
    public String name;
    public int age;
    public int score;
    public Student(String name, int age, int score) {//提供构造方法
        this.name = name;
        this.age = age;
        this.score = score;
    }
    //想要打印这三个属性的话,需要重写toString方法
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }
    @Override
    public int compareTo(Student o) {//重写compareTo方法,用当前对象和你传过来的参数进行比较
        //如果是根据年龄来比较的话就可以这样写
        //自定义类型需要在compareTo内部指定比较方式,即从小到大排列还是从大到小排列。
        if(this.age>o.age){//如果是大于号,就是从小到大排列,如果是小于号,就是从大到小排列,返回1
            return 1;
        }else if(this.age==o.age){//如果相等,返回0
            return 0;
        }else {//返回-1,说明小于
            return -1;
        }
    }
}
public class TestDemo {
    public static void main(String[] args) {
        Student student1=new Student("小王",18,99);
        Student student2=new Student("小明",21,69);
        Student student3=new Student("小李",17,79);
        /*if(student1<student2){//仅仅将这两个学生进行比较是错误的,因为不知道比较的是什么,name还是age,还是score?
        //所以自定义类型是不能进行比较的,想要比较的话,需要在学生类后面加上implements Comparable<Student>,尖括号里面写的东西代表你当前要比较的是什么

        }*/
      /* if(student1.compareTo(student2)<0){
            System.out.println("student1的年龄小于student2的年龄");
        }*/
        Student[] students=new Student[3];//定义一个数组,里面放三个学生,并且对这三个学生进行排序。
        students[0]=student1;//学生1放在0号下标
        students[1]=student2;
        students[2]=student3;
        Arrays.sort(students);//Arrays.sort默认从小到大排序
        //想要排序的话,底层势必会有两个学生进行两两比较
        System.out.println(Arrays.toString(students));
    }
    //一般写的整型默认就是从小到大排列,只有自定义类型才可以控制排序方式
    public static void main1(String[] args) {
        int[] array={12,3,8,24,36,78};
        Arrays.sort(array);//排序
        System.out.println(Arrays.toString(array));
        int a=10;
        int b=20;
        if(a>b){
        }
    }
}

注意事项: 对于 sort 方法来说, 需要传入的数组的每个对象都是 “可比较” 的, 需要具备 compareTo 这样的能力. 通过重写 compareTo 方法的方式, 就可以定义比较规则。

Clonable接口和深拷贝

对于简单类型是深拷贝:画图举例说明
image-20210921230508990

深拷贝代码示例:

public class demo5 {
    public static void main(String[] args) {
        int[] array1={1,2,3,4,5};
        int[] array2=array1.clone();//让array1通过克隆就可以克隆出一个新的数组赋值给array2。
        //对于clone()来说是浅拷贝,什么是浅拷贝和深拷贝呢?
        /*如果是简单类型就是深拷贝,如果是引用类型就是浅拷贝,但是java面向对象的,面向对象的必然是一个浅拷贝,所以如果面试的时候,可以不用分太细,直接统一说成浅拷贝也可。*/
        array2[0]=99;
        System.out.println(Arrays.toString(array1));
        System.out.println(Arrays.toString(array2));
    }
}

打印结果:image-20210921230041263

对于引用类型是浅拷贝:画图举例说明

image-20210921232501033

此时两个引用同时指向一个对象,通过array2修改person里面的某一个数据
之后,array1下标所对应的数据也会被修改,此时就达到了浅拷贝。

如何实现深拷贝呢?
想要实现深拷贝,不仅要克隆它自己本身,还要将对象也克隆一份,克隆出来的对象假设有一个新的地址,array2就指向了这个新的地址,此时通过修改array2就不会影响到array1的值了,这种拷贝就达到了深拷贝。

image-20210921232536657

Cloneable接口

1、如果想要克隆自定义类型,需要实现Cloneable接口,public interface Cloneable{ }这个接口是一个空接口
面试问题:为什么是一个空接口呢?
空接口也把他叫做标记接口:即只要这个类实现了这个接口,那么就标记这个类是可以进行克隆的。

class Person implements Cloneable{
    public int age;//对象里面存储的是一个未初始化的简单类型age
    //2、重写父类的克隆方法,这个克隆方法是object的克隆方法
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
public class demo5 {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person1 = new Person();
        Person person2 = (Person) person1.clone();//因为克隆方法的返回值是object,所以需要进行强制类型转换是类型的到匹配
        System.out.println(person1.age);
        System.out.println(person2.age);
        System.out.println("=============修改===============");
        person2.age = 99;
        System.out.println(person1.age);
        System.out.println(person2.age);
    }

上述代码中,对象里面存储的是一个未初始化的简单类型age,要想实现自定义类型进行克隆,只需两步
第一步:需要实现Cloneable接口
第二步:重写父类的克隆方法,只需要默认的即可。

疑问:克隆出来是深拷贝还是浅拷贝呢?

克隆方法本身就是一个浅拷贝。

class Money{//定义一个钱类
    double money=39.9;//类里面有一个钱,初始值为39.9
}
class Person implements Cloneable{
    public int age;//对象里面存储的是一个未初始化的简单类型age

    Money m=new Money();//在Person类当中定义一个Money,并且实例化一个对象
    //2、重写父类的克隆方法,这个克隆方法是object的克隆方法
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
public class demo5 {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person1 = new Person();
        Person person2 = (Person) person1.clone();
        System.out.println(person1.m.money);
        System.out.println(person2.m.money);
        System.out.println("=============修改===============");
        person2.m.money=99.9;
        System.out.println(person1.m.money);
        System.out.println(person2.m.money);
    }
    //将简单类型换成引用类型之后,就成了浅拷贝

打印结果:image-20210922070600615

如何做到深拷贝呢?

第一步:让Money实现克隆接口

第二步:重写克隆方法

class Money implements Cloneable{//定义一个钱类
    double money=39.9;//类里面有一个钱,初始值为39.9

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
class Person implements Cloneable{
    public int age;//对象里面存储的是一个未初始化的简单类型age

    Money m=new Money();//在Person类当中定义一个Money,并且实例化一个对象
    //2、重写父类的克隆方法,这个克隆方法是object的克隆方法
    @Override
    protected Object clone() throws CloneNotSupportedException {
        //return super.clone();
        //1.克隆person
        Person p=(Person) super.clone();//将当前对象克隆一份赋给p
        //2.克隆当前的Money对象
        p.m=(Money) this.m.clone();//将当前对象的m克隆一份出来,给p这个地方生成的m,此时就指向了一个新的对象,以达到深拷贝的目的
        //3.克隆完之后,调用克隆,将引用返回给person2
        return p;
    }
}
public class demo5 {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person1 = new Person();
        Person person2 = (Person) person1.clone();
        System.out.println(person1.m.money);
        System.out.println(person2.m.money);
        System.out.println("=============修改===============");
        person2.m.money=99.9;
        System.out.println(person1.m.money);
        System.out.println(person2.m.money);
    }

打印结果:image-20210922072150831

总结:

封装:主要是通过private关键字来修饰某些字段和某些方法,如果字段、方法被private关键字修饰之后,那么他们只能在当前类当中进行访问,不能出了这个类,也就是说它的作用域就在这个类当中。

  • 封装的优点:让类的调用者对类的使用成本进一步降低了。

继承:继承是通过extends关键字来表明他们之间的关系的,这个他们指的是子类/派生类 extends(继承于) 父类/基类/超类,在继承里面除了构造方法没有被继承,其他的都被继承了。

  • super关键字和this关键字二者的区别:
  • 子类在构造的时候要帮助父类进行构造
  • private修饰符、default修饰符(包访问权限)、protected(它的出现就是为了解决继承的问题)、public,这四种访问权限的大小关系是从小到大排列的。
  • 重写和重载的区别:
    • 子类的访问修饰限定符要大于等于父类的访问修饰限定。
    • 向上转型
  • 运行时绑定/动态绑定
    • 父类引用要引用子类对象,且父类和子类有同名的覆盖方法,通过父类引用去调用同名的覆盖(重写)方法就会发生运行时绑定。
  • 理解多态:一个引用可以指向不同的对象从而调用同样的方法展现出不同的运行时的状态的这种机制叫做多态。
  • 抽象类:包含抽象方法的类叫做抽象类,使用关键字abstract来修饰。
    • 抽象方法是没有具体的实现的
    • 抽象类是不能new(实例化)的
    • 抽象类的目的就是为了被继承
    • 一个类如果继承了这个抽象类,就一定要重写这个抽象类里面的抽象方法,如果这个类不想实现的话,再把这个类变成抽象类,但是不管怎样,早晚都还是要实现的,因为抽象类不能实例化,只能被继承。

接口:使用interface关键字来修饰的

  • 对于它的成员变量:public static final (静态变量)
  • 对于它的成员方法:public abstract (抽象方法)
  • 接口也是不能够被实例化的
  • 实现接口使用implements关键字,可以实现多个接口。
  • 接口的出现主要是为了解决Java的多继承问题,因为Java是单继承,想要多继承就需要实现多个接口来实现多继承.
  • Compareable接口
    • 自定义类型要可以比较,需要实现这个接口。这个接口里面有一个compareTo方法。
    • Compareable接口主要是为了Java当中对象的一个比较。

常见面试题:

抽象类和接口的区别核心区别: 抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写), 而接口中不能包含普通方法, 子类必须重写所有的抽象方法。

如之前写的 Animal 例子. 此处的 Animal 中包含一个 name 这样的属性, 这个属性在任何子类中都是存在的. 因此此处的 Animal 只能作为一个抽象类, 而不应该成为一个接口。

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

抽象类存在的意义是为了让编译器更好的校验, 像 Animal 这样的类我们并不会直接使用, 而是使用它的子类. 万一不小心创建了 Animal 的实例, 编译器会及时提醒我们

NO区别抽象类(abstract)接口(interface)
1结构组成普通类+抽象方法抽象方法+全局常量
2权限各种权限public
3子类使用使用extends关键字继承抽象类使用implements关键字实现接口
4关系一个抽象类可以实现若干接口接口不能继承抽象类,但是接口可以使用extends关键字继承多个父接口
5子类限制一个子类只能继承一个抽象类一个字类可以实现多个接口
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值