Java---抽象类与接口
抽象类与接口
- 前言
- 一、抽象类
- 1.抽象类的概念
- 2.抽象类的语法
- 3.抽象类的特点
- 4.抽象类的操作
- 5.抽象类的作用
- 二、接口
- 1.接口的概念
- 2.接口语法
- 3.接口的使用与特性
- 4.实现多个接口
- 5.接口之间的继承
- 6.接口的实例
- (1).对象大小的比较
- (1).Comparable接口
- (2).Comparator接口
- (2).实现类的克隆
- (1).类的克隆
- (2).浅拷贝
- (3).深拷贝
- 三、抽象类和接口的区别(面试常考)
前言
上期我们介绍了类与对象的知识点,那么这期我先为大家带来关于抽象类与接口的具体的讲解,希望大家能更进一步理解Java中的特点,下节再为大家介绍两大特性的详解。
一、抽象类
1.抽象类的概念
在面向对象的概念中,所有的对象都是通过类来进行描绘的,但是并不是所有的类来进行描绘对象的。
如果一个类中没有足够的信息内容来回一个具体的对象,那么这种类就是抽象类。
2.抽象类的语法
1.抽象类都是被abstract类修饰的叫做抽象类
public abstract class A{}
2.抽象类中的成员方法用abstract修饰的方法,叫做抽象方法
public abstract class A{public abstract void test();//抽象方法
}
3.抽象类的特点
- 通过上述方法我们可以得知:一个类中有抽象方法,那么这个类必是抽象类
- 抽象类中也可以有普通成员变量和普通成员方法
比如:
abstract class Shape{public int a = 10;//普通成员变量public void draw(){//普通成员方法System.out.println("画图形");}public abstract void draw2();//抽象方法
}
- 抽象类不能直接实例化
public static void main(String[] args) {Shape shape = new Shape();}
抽象类不能实例化,那么是用来干什么的呢?
当然是用来被继承的了,所以抽象类也可以实现了向上转型
- 抽象类中的方法是不能被private修饰的
private abstract void draw2();
- final 和 abstract 两者不能同时存在,被abstract修饰的方法就是要被继承的与重写的,而final修饰的不能被重写和继承,属于是密封类和密封方法
public abstract final void draw2();
- 抽象方法不能被static修饰,因为抽象方法要被子类重写,而static是属于类本身的方法
public static final void draw2();
- 当一个普通类继承了这个抽象类之后,这个普通类一定要重写这个抽象类的当中的所有的抽象方法
abstract class Shape{public int a = 10;public void draw(){System.out.println("画图形");}public abstract void draw2();//抽象方法public void test(){}
}
class React extends Shape{@Overridepublic void draw2() {System.out.println("矩形");}
}
class Flower extends Shape{@Overridepublic void draw2() {System.out.println("花" );}
}
- 子类如果不想重写抽象方法,那么就把子类也设为抽象类
比如这有一个子类A
abstract class A extends Shape{public abstract void testDemo();
}
但是我们会想到,如果一直设计为抽象子类,那么它们的抽象方法怎么办?
class B extends A{@Overridepublic void draw2() {}@Overridepublic void testDemo() {}
}
所以B继承了A,A继承了Shape,两个类都是抽象类,所以这个普通类B类就要重写两个方法
- 当一个抽象类A不想被一个普通类B继承,此时可以把这个B类变成抽象类,那么在当一个普通类C继承这个抽象类B之后,C要重写B和A的里面的所有抽象方法
4.抽象类的操作
给出完整的代码,让我们分析一下:
abstract class Shape{public int a = 10;public void draw(){System.out.println("画图形");}public abstract void draw2();public void test(){}public Shape() {//既然它不能实例化,但是可以让子类调用帮助这个抽象类初始化它自己的成员}
}
class React extends Shape{@Overridepublic void draw2() {System.out.println("矩形");}
}
class Flower extends Shape{@Overridepublic void draw2() {System.out.println("花" );}
}
public class Test {public static void drawMap(Shape shape) {shape.draw2();}public static void main(String[] args) {Shape shape = new React();drawMap(new React());drawMap(new Flower());}
Shape shape1 = new React();
Shape shape2 = new Flower();
这个操作是向上转型,在我们调用了一些抽象方法的时候,由于我们将其抽象方法进行了重写,进而发生了动态绑定,从而发生多态。
public static void drawMap(Shape shape) {shape.draw2();}
这个方法我们来接收Shape类型,但是由于子类继承父类,构成了协变类型,也会发生向上转型,往里面传入了参数时,再调用抽象方法,这个时候发生了动态绑定,就会发生了多态
new React() new Flower() 这种没有名字的对象 —> 匿名对象
匿名对象的缺点:每次使用,都得去重新实例化
所以我们可以使用向上转型的直接赋值,来去传进参数
public static void main(String[] args) {Shape shape1 = new React();Shape shape2 = new Flower();drawMap(shape1);drawMap(shape2);}
5.抽象类的作用
我们都知道普通的类也能被继承,那还需要抽象类来做什么?
但是我们正常都需要跟业务和实际情况来进行选择,
使用抽象类相当于多了一重编译器的校验:
使用抽象类的场景就如上面的代码,实际工作不应该由父类完成,而应由子类完成.。
那么此时如果不小心误用成父类了,使用普通类编译器是不会报错的;
但是父类是抽象类就会在实例化的时候提示错误, 让我们尽早发现问题。
意义:
很多语法存在的意义都是为了 “预防出错”, 例如我们曾经用过的 final 也是类似. 创建的变量用户不去修改, 不
就相当于常量嘛? 但是加上 final 能够在不小心误修改的时候, 让编译器及时提醒我们.
充分利用编译器的校验, 在实际开发中是非常有意义的.
二、接口
1.接口的概念
比如:我们都知道电脑上面有USB接口,可以插些:U盘,鼠标等符合USB协议的设备;
接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。
在Java中,接口可以看成是多个类的公共规范,是一种引用数据类型。
2.接口语法
public interface 接口名称{//抽象方法//成员变量
}
一般的接口名称前加一个I字母。
interface IShape{//一般情况下我们以I开头来表示接口的int a = 10;void draw();default void test(){System.out.println("test()");}public static void test2(){System.out.println(" static test2()");}
}
在这个接口中我没有看到修饰符,难道是默认的吗?
并不是默认的!
原因这是在接口中,成员变量默认是被public static final修饰的,成员方法是抽象方法,但是默认被public abstract修饰的但是这种我们一般不写
接口中的方法一般不能实现,都是抽象方法,但是从JDK8之后,可以支持default修饰的成员方法和static修饰的成员方法中可以有具体的实现(即主体)
3.接口的使用与特性
- 接口的成员方法必须是默认为public abstract来修饰的。
其他修饰符不可以;private 和 protected还有default均不可以,都会出现报错
接口类型也是一种引用类型 - 接口不能有普通成员方法,也不能有成员变量
interface IShape{//一般情况下我们以I开头来表示接口的private int a = 10;void draw();default void test(){System.out.println("test()");}public void test2(){System.out.println(" static test2()");}
}
就算你写成了public的成员变量,这时候也是默认成public static final修饰的,所以不会出现错误
- 接口不能被new关键字来进行实例化,但是也可以用向上转型
IShape iShape = new IShape();
- 类实现了接口,那么该子类就要重写接口中的所有抽象的方法,用implements关键字来实现
class Rect implements IShape{@Overridepublic void draw() {System.out.println("矩形");}
}
class Flower implements IShape{@Overridepublic void draw() {System.out.println("花");}
}
- 如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类
abstract class A implements IShape{}
当然了,还是要还的,因为他只能被用来继承,所以这种类被继承后还是需要重写接口中的方法
- 当一个类实现了接口当中的方法之后,当前类中的方法不能不加public,因为原来接口中的方法是public修饰的,所以重写的方法要大于等于public的范围,所以必须是public修饰
class Rect implements IShape{@Overridevoid draw() {System.out.println("矩形");}
}
- 接口当中,不能使用构造方法和静态代码块
interface IShape{//一般情况下我们以I开头来表示接口的public IShape(){}static{}
}
4.实现多个接口
在Java中,类和类之间是单继承的,一个类只能有一个父类,即Java中不支持多继承,但是一个类可以实现多个接口。
在父类中是放入一些共性的特点,而不是特有的,是共有的
比如我们创建了一个动物类,再创建几个具体的动物的类,再分别创建几个接口:
//游泳接口
public interface ISwimming {void swim();
}
//跑步接口
public interface IRunning {void run();
}
//飞行接口
public interface IFlying {void fly();
}
//动物类:
public abstract class Animals {public String name;public int age;public Animals(String name, int age) {this.name = name;this.age = age;}public abstract void eat();
}
鸟可以进行飞行和跑步,所以实现这两接口,并且继承了动物类
//鸟类
public class Bird extends Animals implements IFlying, IRunning {public Bird(String name, int age) {super(name, age);//帮助父类进行构造,用super来进行访问父类}@Overridepublic void eat() {System.out.println(this.name + "正在吃饭!");}@Overridepublic void fly() {System.out.println(this.name + "正在用翅膀飞!");}@Overridepublic void run() {System.out.println(this.name + "正在用鸟腿跑!");}
}
狗类是可以进行跑步和游泳,所以实现这两个接口,并且继承了父类
//狗类
public class Dog extends Animals implements IRunning, ISwimming {public Dog(String name, int age) {super(name, age);//帮助父类进行构造}@Overridepublic void eat() {System.out.println(this.name + "正在吃狗粮!");}@Overridepublic void run() {System.out.println(this.name + "正在用狗腿跑!");}@Overridepublic void swim() {System.out.println(this.name + "正在狗腿游泳!");}
}
主类
public class Test {public static void test1(Animals animals){animals.eat();}public static void test4(IFlying iFlying){iFlying.fly();}public static void test2(IRunning iRunning){iRunning.run();}public static void test3(ISwimming iSwimming){iSwimming.swim();}public static void main(String[] args) {Bird bird = new Bird("小鸟",1);Dog dog = new Dog("小狗",10);test1(dog);test1(bird);test2(bird);test2(dog);System.out.println("============");test3(dog);//test3(bird);bird没有实现swimming的接口System.out.println("============");test4(bird);//test4(dog);dog没有实现了flying的接口}
}
上面的代码展示了 Java 面向对象编程中最常见的用法: 一个类继承一个父类, 同时实现多种接口。
语法上先继承后实现,否则会报错
继承表示是其中的一种,接口表示的是它具有什么特性,能做什么。
这时候我们在创建一个机器人类:
public class Robot implements IRunning {@Overridepublic void run() {System.out.println("机器人在跑!");}
}
比如我们在主类中测试
test2(new Robot());
那么这种操作的好处是什么?
时刻牢记多态的好处, 让我们忘记类型. 有了接口之后, 类的使用者就不必关注具体类型,
而只关注某个类是否具备某种能力
我们创建了这个机器人类,实现了跑步的接口,所以直接去实现调用test2的方法,这个时候我们就可以发现了这个接口,回避了向上转型会使其更加灵活。
5.接口之间的继承
在Java中,类和类之间是单继承的,但是一个类可以实现多个接口,接口与接口之间可以多继承。
用接口达到多继承的目的。
接口的继承用extends关键字
interface A{void testA();
}
interface B{void testB();
}
interface C{void testC();
}
interface D extends B,C{//D 这个接口具备了B和C接口的功能//并且D还可以有自己的方法void testD();
}
在接口中是进行抽象方法的声明,所以接口之间继承不用重写抽象方法
当类来实现这个D接口的时候,我们要重写D接口中的方法和D所继承的接口中的方法
public class Test implements D{@Overridepublic void testB() {}@Overridepublic void testC() {}@Overridepublic void testD() {}//就如同子孙类一样
总结:
接口间的继承相当于把多个接口合并在一起。
在接口中是进行抽象方法的声明,所以接口之间继承不用重写抽象方法。
6.接口的实例
(1).对象大小的比较
当我们先给出了几个对象,从而想要按照一定顺序比较其中内容的大小
我们先创建了一个Student类
class Student {public String name ;public int age;public Student(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}
}
这个时候我们再创建一个主类
public class Test {public static void main(String[] args) {Student student1 = new Student("zhangsan", 10);Student student2 = new Student("lisi", 15);System.out.println(student1 > student2);}
}
这么运行会出现错误;(因为两者都是引用类型,无法直接进行比较)
所以这个时候我们用接口来实现这个自定义类型的比较,所以分别是 Comparable和Comparator接口
(1).Comparable接口
这个是接口Comparable中的源码
这个接口中涉及到后续的泛型,关于这个泛型到后期的数据结构我们在详细的讲解
如果我们要实现比较的方法,就要用compareTo这个方法,
当然如果直接使用:
System.out.println(student1.compareTo(student2));
因为compareTo是Comparable中的方法,所以要想使用这个方法,就要实现这个接口,并指定比较的类型用<>
class Student implements Comparable<Student>{public String name ;public int age;public Student(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}@Overridepublic int compareTo(Student o) {//重写的CompareTo方法return this.age - o.age;//大于0,则前者大于后者//小于0,前者小于后者//this.age表示的是student1,o.age表示的是student2}
}public class Test {public static void main(String[] args) {Student student1 = new Student("zhangsan", 10);Student student2 = new Student("lisi", 15);System.out.println(student1.compareTo(student2));}
}
如果我们给出的是student的对象数组,让其大小进行排序,不作对student类进行修改,只在主类中进行实例化
import java.util.Arrays;
public class Test {public static void main(String[] args) {//利用对象数组Student[] students = new Student[3];students[0] = new Student("zhangsan",10);students[1] = new Student("lisi",2);students[2] = new Student("liwu",18);Arrays.sort(students);//这个时候要用comparable来进行一定的顺序排序.System.out.println(Arrays.toString(students));}
}
因为通过comparable这个接口来进行查找一定的内容,从而按一定的内容来进行比较大小
当然,我们想要按其他内容的时候进行排序,需要改动某些代码,这时候就容易会导致某些错误。
(2).Comparator接口
Comparator接口中有compare的方法为默认权限,所以我们调用这个方法
这种方式叫做比较器,在类的外部进行实现
class Student2{public String name;public int age;@Overridepublic int compareTo(Student2 o) {return this.age - o.age;}public Student2(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}
}
比如我们要按对象的年龄大小进行比较,这时候再创建一个age类,实现了Comparator这个接口
class AgeComparator implements Comparator<Student2>{@Overridepublic int compare(Student2 o1, Student2 o2) {return o1.age - o2.age;}
}
这个时候我们先把在外部的类实例化,进而要比较需要两个对象的变量传递到compare方法中去,从去重写这个方法,
并且发生向上转型(参数类型)
import java.util.Comparator;
public class Test2 {public static void main(String[] args) {Student2 student1 = new Student2("zhangsan", 10);Student2 student2 = new Student2("lisi", 15);AgeComparator ageComparator = new AgeComparator();System.out.println(ageComparator.compare(student1, student2));}
}
当我们按照name进行比较的时候,这个时候在创建了Name类,再去实现了Comparator这个接口
class NameComparator implements Comparator<Student2>{@Overridepublic int compare(Student2 o1, Student2 o2) {return o1.name.compareTo(o2.name);}
}
name是引用类型,不能进行相减来进行比较,但是我们发现了String类中也有去实现Comparable的方法
所以我们只需要重写compareTo方法即可
当然我们还是需要进行将Name类进行类的实例化
NameComparator nameComparator = new NameComparator();System.out.println(nameComparator.compare(student1, student2));
关于对于自定义类型比较的总结:
Comparator这种方式的优势就是对于类的侵入性不强,会比较灵活
Comparable这种方式对于类的侵入性比较强,所以做好不要去改动原有的逻辑,比如我按名字排序这个时候就会容易发生变化
并且两者可以共存,互不干扰;因为comparable是对原有的类进行实现,comparator是对要比较内容的类而实现,所以互不干扰
(2).实现类的克隆
(1).类的克隆
Java中有许多的内置接口,而Cloneable就是一个非常有用的接口
Object类中有一个clone方法,调用这个对象可实现创建一个对象的“拷贝”,但是想要成功调用这个方法,就要实现这个其中的接口
class Person {public int age;public Person(int age) {this.age = age;}@Overridepublic String toString() {return "Person{" +" age=" + age +" }";}
}
public class Test3 {public static void main(String[] args){Person person1 = new Person(10);System.out.println(person1);Person person2 = person1.clone();System.out.println(person1);}
}
例如:我们创建一个person类,存入了一个年龄的成员变量,并重写了toString方法;
然后我们对Person类实例化,并存入了一个age为10的值让其构造方法为它自己进行初始化,这个时候我们直接打印了person1,这个时候我们再创建一个变量person2来接收person1的拷贝后的值。
但是如果我们直接这么做,直接去调用:
Person person1 = person.clone();
这个时候就会出现这种错误;
因为clone方法是在object类中的方法,所以我们点击查看clone的源码:
protected native Object clone() throws CloneNotSupportedException;
这个时候我们就会发现了这个clone方法是本地方法,是用C/C++来写的,所以我们也无法知道这个具体的过程实现,并且我们也看到了这个方法是被protected修饰的,它的访问权限是在不同包的子类可以访问不同包的父类被protected修饰的成员变量和方法, 但是也需要用super来访问,所以需要重写这个clone方法;
class Person {public int age;public Person(int age) {this.age = age;}@Overridepublic String toString() {return "Person{" +" age=" + age +" }";}@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();//调用object类的方法来进行访问}
}
这个时候我们会发现还是有错误啊,但是这时候提示我们类型不兼容,因为Object类是所有的类的父类,但是其中clone方法是比较特殊的,它返回的是object类型,但是它并没有与Person构成显示的父与子的协变类型,所以这也反映了不构成重写的条件之一,所以会出现这种错误,但是我们把这个类型强制转换为Person类,所以就可以了
public class Test3 {public static void main(String[] args){Person person1 = new Person(10);System.out.println(person1);Person person2 = (Person) person1.clone();System.out.println(person1);}
}
这个时候在运行结果:
这个时候还是报了错误,告诉我们还有异常这错误,但是异常处理我们后期会详细讲解,因为clone方法的异常是受查异常/编译时异常,所以必须在编译时处理:
public class TestDemo {public static void main(String[] args) throws CloneNotSupportedException{//必须在编译时处理Person person1 = new Person(10);System.out.println(person1);Person person2 = (Person) person1.clone();System.out.println(person2);
}
但是我们运行完,还是会出现错误,这可是真愁人
CloneNotSupportedException,不支持克隆,告诉我们Person类不支持克隆
所以我们这时候就要提到前面所说的Cloneable接口了,这个时候我们就用Person实现这个接口,
那么运行结果:
这时候我们就完成了person2对person1的所指的对象的克隆
这个时候我们来看一下Cloneable这个接口:
Cloneable是一个空接口,我们可以在IDEA中去查看源码:
它是一个空接口,也作为一种标记接口,因为它用来证明当前类是可以被克隆的
在堆上,创建了一个对象,假设地址是0X98,那么在虚拟机栈上得到是person1所指这个对象的地址,通过clone的方法来进行克隆出堆中一个相同的一份的对象,但是两者(person1和person2)在堆上不是一个位置,所以person2这个克隆后的获得了一个新的地址0X88(假设的),所以我们通过 return super.clone() 来克隆。
那么就来总结一下Person person1 = person.clone();这个其中的所有的错误:
- 修饰符的错误,protected,这一步用super.clone()访问object类,来重写这个clone方法
- 异常,clone方法的异常是受查异常/编译时异常,所以必须在编译时处理,(在main后面加入throws)
- 向下转型,进行强制转换,因为我们用的是object类中的clone方法,所以返回类型是固定的object类,而不是构成了协变类型。所以我们要进行向下转型,将其转换为子类;进行了上述的操作,还是会报错:CloneNotSupportedException,不支持克隆。
- 所以我们要实现Cloneable这个接口,从而才能使用
我的理解:clone方法实现的具体操作,用super.clone来访问,Cloneable判断这个类是否支持克隆操作
(2).浅拷贝
这时候我们在Person类的基础上再重新创建了一个Money类,让它与其Person类构成组合
class Money{public double money = 19.9;
}
class Person1 implements Cloneable{public int age;public Money m;public Person1(int age) {this.age = age;this.m = new Money();//在构造方法中实例化一个money}@Overridepublic String toString() {return "Person{" +" age=" + age +" }";}@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
}
public class TestDemo {public static void main(String[] args) throws CloneNotSupportedException {Person1 person1 = new Person1(10);Person1 person2 = (Person1) person1.clone();System.out.println(person1);System.out.println(person2);System.out.println("===============");System.out.println(person1.m.money);System.out.println(person2.m.money);//先调用的是person1的成员变量m,//由于在构造方法中进行了m的实例化,所以我还可以调用Money类中的成员变量System.out.println("===============");//将person2进行修改,正常我们所期望的是只有person2进行修改,person1没有变化person2.m.money = 99.99;System.out.println(person1.m.money);System.out.println(person2.m.money);}
}
this.m = new Money();也就是在堆上的空余的空间再创建了一个实例化Money的对象,并且这个m在堆上那个原本的空间占有的一定内存空间。
person1.m.money为什么会这么调用money?
这个m是Peron1类的成员变量,但是我们也在Person1类中的构造方法去实例化Money这个类,所以这个m又是Money实例化的变量名,再用这个m来调用了Money类中的成员变量money
将person2进行修改,正常我们所期望的是只有person2进行修改,person1没有变化,
但是可惜的是两者都发生了变化。
所以我们可以通过图例来演示一下这个过程:
在虚拟机栈上,会创建两个空间分别是person1和person2,
person1所实例化的对象会在堆上创建一个对象的空间(地址为0x98),存入两个成员变量,但是其中m是Money类型即是引用类型,并且也是实例化了,所以那么也会是创建money类型的对象的空间(地址为0x65),这时候在对第一个对象中的成员变量m也会得到它实例化的地址
person1.clone()是把person1指向的对象进行克隆,但是没把这个对象的空间中成员变量m所指的对象进行克隆
所以那也意味着成员变量m还是指的是0x65的它自己的对象空间Money,这个对象的空间的成员变量是money
person2还是指的是第一次的对象中的对象的成员变量money;
所以这个时候堆money的修改还是一次就改变person1和person2所指的money的值,两者还是一样的
此时这个现象被称之为浅拷贝:并没有将对象中的对象去进行克隆
(3).深拷贝
class Money1 implements Cloneable{public double money = 19.9;@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
}
class Person2 implements Cloneable{public int age;public Money1 m;public Person2(int age) {this.age = age;this.m = new Money1();//在构造方法中实例化一个money}@Overridepublic String toString() {return "Person{" +" age=" + age +" }";}@Overrideprotected Object clone() throws CloneNotSupportedException {Person2 tmp = (Person2) super.clone();//tmp.m类的调用成员变量,this.m.clone()中的this是指这个对象的成员变量m的引用tmp.m = (Money1) this.m.clone();//谁调用某个方法谁就是this,现在是person1来调用,那么person1就是thisreturn tmp;}
}
public class Test5 {public static void main(String[] args) throws CloneNotSupportedException{Person2 person1 = new Person2(10);Person2 person2 = (Person2) 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); }
}
这时候我们把Money1类也进行实现了Cloneable的接口,这时候我们再对clone方法的重写,
但是我们会发现两者的重写的clone方法不同,在Person2类中的重写方法,创建一个临时变量tmp,通过将其向下转型变成了Person2类型,tmp.m是Person2类的调用成员变量m,this.m.clone()就是person1正在调用clone方法,这个时候先访问了Pesron2类中成员变量m,再去通过Money1类的实例化之后再调用clone方法,这个时候最终是Money1这个类型去调用这个方法;
但是我们会发现tmp是局部变量,除了生命作用域就会销毁,但是最终返回的是给person2,又因为person2是引用类型,会得到了tmp的地址。
所以我们可以通过图例来演示一下这个过程:
在虚拟机栈上,会创建两个空间分别是person1和person2,还有一个在person类中创建一个临时变量tmp,
因为是把对象的对象进行克隆,所以要再类中的另一个类(该类作为成员变量)中重写clone方法,但是在原有的类中的clone方法也需要进行修改
person1所实例化的对象会在堆上创建一个对象的空间(地址为0x98),存入两个成员变量,但是其中m是Money类型即是引用类型,
并且也是实例化了,所以那么也会是创建money类型的对象的空间(地址为0x65),这时候在对中的成员变量m也会得到它实例化的地址
因为我们还在Person类中修改了重写方法;通过创建了一个临时变量tmp也是为Person类型,先把外面的对象先进行克隆完成,
这时候再通过这个临时变量去调用Person类中的m(其实也就是类的引用),
由于我将m这个变量也进行了Money类的实例化的操作,所以这时候再用m去调用正常的clone方法,从而通过去调用Money类中的clone方法
这个就完成了克隆那么Money类的克隆的地址(0x888),但是tmp是临时的局部的变量,出了作用域就销毁,最终返回的是给的是person2
[person2 = (Person2) person1.clone()]这个就是返回了person2么
因为它是类类型(引用类型)所以这个时候的person2就会得到它的地址(0x91)
最终person2和person1所指的两个值是不一样
此时这个现象被称之为深拷贝:完全将对象中的对象去进行克隆。
注意:深拷贝与浅拷贝就是看的是代码实现的过程,跟克隆方法没有关系
三、抽象类和接口的区别(面试常考)
(1).抽象类中可以有普通成员方法和成员变量,其中还可以有抽象方法,如果子类继承抽象类,必须要重写抽象类中的抽象方法,如果不想重写抽象方法,那么就要将子类设计为抽象类
一个类可以实现多个接口(模拟多继承),但是类与类之间的继承必须是单继承的关系,继承用extends关键字
(2).接口中不可以有普通的成员方法和成员变量,它的成员变量默认是public static final修饰,成员方法默认是public abstract修饰,如果类要实现接口,就要重写接口中的抽象方法
实现用implements关键字,接口之间也可以继承多个接口(这种操作也是多继承),这种操作不用父接口的抽象方法;但是用类实现时,要注意重写接口与被继承接口的所有的抽象方法。
这期内容就分享到这里了,希望大家可以获得新的知识,当然,如果有哪些细节和内容不足,欢迎大家在评论区中指出!
相关文章:
Java---抽象类与接口
抽象类与接口 前言一、抽象类1.抽象类的概念2.抽象类的语法3.抽象类的特点4.抽象类的操作5.抽象类的作用 二、接口1.接口的概念2.接口语法3.接口的使用与特性4.实现多个接口5.接口之间的继承6.接口的实例(1).对象大小的比较(1).Comparable接口(2).Comparator接口 (2).实现类的克…...
玩转Docker | 使用Docker部署linkding书签管理工具
玩转Docker | 使用Docker部署linkding书签管理工具 前言一、linkding介绍简介主要特点二、系统要求环境要求环境检查Docker版本检查检查操作系统版本三、部署linkding服务下载镜像创建容器检查容器状态检查服务端口设置登录账号与密码安全设置四、访问linkding服务访问linkding…...
K8s 集群网络疑难杂症:解决 CNI 网络接口宕机告警的完整指南
引言 在 Kubernetes 集群运维过程中,网络问题往往是最棘手的故障之一。当你收到一条 [CRITICAL] 网络接口宕机 (172.18.109.55:9100) 的告警,并且告警内容显示 172.18.109.55:9100 的网络接口 cni0 已宕机5分钟 时,这通常意味着你的 Kubernetes 集群中有一个节点的容器网络…...
程序员/运维绘图工具---Mermaid
效果 介绍 Mermaid 是一种基于文本的图表生成工具,通过类似 Markdown 的简洁语法快速创建流程图、甘特图、类图等各类专业图表 应用场景 程序员绘图 系统架构图&代码逻辑可视化 项目管理图 数据可视化 AI辅助生成:LLM生成mermaid代码然后去渲染成…...
《MATLAB实战训练营:从入门到工业级应用》趣味入门篇-用MATLAB画一朵会动的3D玫瑰:从零开始的浪漫编程之旅
《MATLAB实战训练营:从入门到工业级应用》趣味入门篇-🌹用MATLAB画一朵会动的3D玫瑰:从零开始的浪漫编程之旅 你是否想过用代码创造一朵永不凋谢的玫瑰?今天,我将带你走进MATLAB的奇妙世界,用数学公式和编…...
激光院董事长龚赤坤到北京研发中心检查指导工作
4月11日,激光院党委书记、董事长龚赤坤到北京研发中心检查指导工作。 龚赤坤详细了解了北京研发中心的建设情况和科研进展,充分肯定所取得的成绩,对发展寄予厚望,龚赤坤指出北京研发中心的成立正处于激光院加速发展与产业进化的关…...
AbortController:让异步操作随时说停就停
AbortController:让异步操作随时说停就停 一、什么是 AbortController? AbortController 是 JavaScript 在浏览器和部分 Node.js 环境中提供的全局类,用来中止正在进行或待完成的异步操作(如 fetch() 请求、事件监听、可写流、数…...
leetcode572 另一棵树的子树
1.与100、101解法相同 递归: class Solution { private:bool compare(TreeNode* p, TreeNode* q){if(!p && !q) return true;else if(!p || !q) return false;else if(p->val ! q->val) return false;bool leftside compare(p->left, q->lef…...
再看 MPTCP 时的思考
2022 年夏,居家办公时,第一次接手 mptcp 就觉得它不靠谱,以至于我后来搞了 mpudp for DC,再后来我调研了很多 mptcp-based 方案,发现它们都是向善而来,最终灰头土脸而终。mptcp 实则一个坑,业内…...
将三维非平面点集拆分为平面面片的MATLAB实现
将三维非平面点集拆分为平面面片的MATLAB实现 要将三维空间中不在同一平面上的点集拆分为多个平面面片,可以采用以下几种方法: 1. 三角剖分法 (Delaunay Triangulation) 最简单的方法是将点集进行三角剖分,因为三个点总是共面的࿱…...
Python(10.2)Python可变与不可变类型内存机制解密:从底层原理到工程实践
目录 一、类型特性引发的内存现象1.1 电商促销活动事故分析1.2 内存机制核心差异 二、内存地址追踪实验2.1 基础类型验证2.2 复合对象实验 三、深度拷贝内存分析3.1 浅拷贝陷阱3.2 深拷贝实现 四、函数参数传递机制4.1 默认参数陷阱4.2 安全参数模式 五、内存优化最佳实践5.1 字…...
华为hcie证书的有效期怎么判断?
在ICT行业,华为HCIE证书堪称含金量极高的“敲门砖”,拥有它往往意味着在职场上更上一层楼。然而,很多人在辛苦考取HCIE证书后,却对其有效期相关事宜一知半解。今天,咱们就来好好唠唠华为HCIE证书的有效期怎么判断这个关…...
【前端】CSS Grid 布局详解
CSS Grid 布局详解(通俗易懂版) 一、概述 CSS Grid 是一种二维布局系统,可以同时控制行和列,相比 Flex(一维布局),更适合用在整体页面布局或复杂模块结构中。 二、基础概念 Grid 容器&#x…...
物美“外贸转内销”极速绿色通道正式开启
「TMT星球」获悉,在国家“提振消费、扩大内需”及“内外贸一体化”战略指引下,物美集团依托自身零售生态优势,打造“云超绿通”专项通道,助力中国优质外贸企业实现“出口转内销”的高效转型,通过极速绿通、线上线下全渠…...
【说明书#1】Node.js 和 npm安装与使用
系统提示 npm 不是内部或外部命令,也不是可运行的程序或批处理文件,也就是 npm 命令无法识别。这个错误通常是因为 Node.js 和 npm 没有正确安装,或者它们的路径没有添加到系统的环境变量中。 解决方法如下: 1. 安装 Node.js 和 npm: 如果你还没有安装 Node.js,可以从…...
【触想智能】安卓工业平板电脑和普通商业平板电脑的区别
安卓工业平板电脑是基于ARM架构开发的一种工业平板电脑,它在自助终端、智能制造、产线车间、智慧物流、商业金融等诸多领域有着广泛的应用。 触想安卓工业平板电脑TPC-A2系列 安卓工业平板电脑和普通商业平板电脑在一些方面存在一些区别,包括设计、硬件规…...
Java基于SSM的课程答疑微信小程序【附源码、文档说明】
博主介绍:✌IT徐师兄、7年大厂程序员经历。全网粉丝15W、csdn博客专家、掘金/华为云//InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专栏推荐订阅👇dz…...
模板引擎语法-变量
模板引擎语法-变量 文章目录 模板引擎语法-变量(一)在Django框架模板中使用变量的代码实例(二)在Django框架模板中使用变量对象属性的代码实例(三)在Django框架模板中使用变量显示列表 (一&…...
1260 最大公约数
1260 最大公约数 ⭐️难度:中等 🌟考点:GCD 📖 📚 import java.util.Scanner; import java.util.Arrays;public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);int t …...
Node.js中net模块详解
Node.js 中 net 模块 API 详解 Node.js 的 net 模块提供了基于 TCP/IP 的网络功能,用于创建 TCP 服务器和客户端。以下是 net 模块的所有 API 详解: 1. 创建 TCP 服务器 const net require(net);// 1. 基本服务器 const server net.createServer((s…...
【从零开始学习JVM | 第三篇】虚拟机的垃圾回收学习(一)
堆空间的基本结构 Java 的自动内存管理主要是针对对象内存的回收和对象内存的分配。同时,Java 自动内存管理最核心的功能是 堆 内存中对象的分配与回收。 Java 堆是垃圾收集器管理的主要区域,因此也被称作 GC 堆(Garbage Collected Heap&am…...
intellj idea 2024.1
参考资料 激活 https://www.microcharon.com/tech/5.htmlja-netfilter-all下载地址:https://drive.microcharon.com/OneDrive/Software/JetBrains%20ja-netfilter-all%20Build%202024.1.11.zip 步骤及问题 下载后:安全前确保旧版本的idea已经卸载。安…...
redis之缓存击穿
一、前言 本期我们聊一下缓存击穿,其实缓存击穿和缓存穿透很相似,区别就是,缓存穿透是一些黑客故意请求压根不存在的数据从而达到拖垮系统的目的,是恶意的,有针对性的。缓存击穿的情况是,数据确实存在&…...
Node.js中path模块详解
Node.js path 模块全部 API 详解 Node.js 的 path 模块提供了处理文件路径的工具函数,支持跨平台路径操作。以下是 path 模块的所有 API 详解: 1. 路径解析与操作 const path require(path);// 1. 路径连接 const fullPath path.join(__dirname, fi…...
重构艺术 | 内联与查询替代临时变量
重构艺术 | 内联与查询替代临时变量 在代码重构的殿堂里,临时变量常常扮演着双面角色:既是代码清晰的助力器,也可能成为代码腐败的温床。本文将深入探讨两种处理临时变量的重要手法:内联临时变量(Inline Temp…...
数据分析-数据预处理
数据分析-数据预处理 处理重复值 duplicated( )查找重复值 import pandas as pd apd.DataFrame(data[[A,19],[B,19],[C,20],[A,19],[C,20]],columns[name,age]) print(a) print(--------------------------) aa.duplicated() print(a)只判断全局不判断每个 any() import p…...
Java基础 4.12
1.方法的重载(OverLoad) 基本介绍 Java中允许同一个类,多个同名方法的存在,但要求形参列表不一致! 如 System.out.println(); out是PrintStream类型 重载的好处 减轻了起名的麻烦减轻了记名的麻烦 2.重载的快速入…...
PostgreSQL有类似oracle的move表吗
PostgreSQL有类似oracle的move表吗 PostgreSQL 提供了类似 Oracle MOVE 表功能的重组操作,但实现方式和具体命令有所不同。以下是详细对比和 PostgreSQL 中的实现方案: 一 Oracle MOVE 与 PostgreSQL 对比 特性Oracle MOVEPostgreSQL 等效操作主要用途…...
AUTO-RAG: AUTONOMOUS RETRIEVAL-AUGMENTED GENERATION FOR LARGE LANGUAGE MODELS
Auto-RAG:用于大型语言模型的自主检索增强生成 单位:中科院计算所 代码: https://github.com/ictnlp/Auto-RAG 拟解决问题:通过手动构建规则或者few-shot prompting产生的额外推理开销。 贡献:提出一种以LLM决策为中…...
ABC-CNN-GRU-Attention、CNN-GRU-Attention、ABC-CNN-GRU和CNN-GRU四类对比模型多变量时序预测
人工蜂群算法四模型对比!ABC-CNN-GRU-Attention系列四模型多变量时序预测 目录 人工蜂群算法四模型对比!ABC-CNN-GRU-Attention系列四模型多变量时序预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 本研究针对多变量时间序列预测任务…...
ssh 免密登录服务器(vscode +ssh 免密登录)
每次打开vscode连接服务器都需要输入密码,特别繁琐。 然后自己在网上翻阅了一下教程,发现说的内容比较啰嗦,而且个人感觉非常有误导性倾向。 因此自己直接干脆写一个简便易懂的教程算了。 (以经过本人亲测,真实可靠&am…...
Elasticsearch 系列专题 - 第七篇:实战项目
理论学习固然重要,但实战才能真正巩固知识。本篇将通过两个项目,带你从需求分析到实现,体验 Elasticsearch 在真实场景中的应用。 1. 项目一:日志分析系统 1.1 需求分析与架构设计 需求: 实时采集服务器日志。按时间和日志级别(INFO、ERROR)分析。可视化错误趋势。架构…...
C++初阶-类和对象(上)
本章内容相对于之后的类和对象中和下都比较简单,但是整体还是有些难度的。 目录 1.类的定义 1.1类定义格式 1.2访问限定符 1.3类域 2.实例化 2.1实例化概念 2.2对象大小 3.this指针 4.练习 4.1选择题1 4.2选择题2 5.总结 1.类的定义 1.1类定义格式 &am…...
(十九)安卓开发中的Application类的使用详解
在 Android 开发中,Application 类是一个全局的单例类,代表应用进程本身。它常用于初始化全局资源、维护应用级别的状态和注册全局生命周期回调。以下是详细讲解和代码示例: 一、自定义 Application 类 1. 创建子类 public class MyApplica…...
算法思想之位运算(一)
欢迎拜访:雾里看山-CSDN博客 本篇主题:算法思想之位运算(一) 发布时间:2025.4.12 隶属专栏:算法 目录 滑动窗口算法介绍六大基础位运算符常用模板总结 例题位1的个数题目链接题目描述算法思路代码实现 比特位计数题目链接题目描述…...
十八、TCP多线程、多进程并发服务器
1、TCP多线程并发服务器 服务端: #include<stdio.h> #include <arpa/inet.h> #include<stdlib.h> #include<string.h> #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <pthread.h>…...
『生成内容溯源系统』详解
生成内容溯源系统详解 1. 定义与核心目标 生成内容溯源系统(Generative Content Provenance System)是指能够追踪AI生成内容的来源、生成过程、版权归属及修改历史的技术体系。其核心目标是: 验证真实性:证明内容由特定AI模型生…...
mac 解压 nsz 文件
nsz 地址 下载 nsz PIP 套餐 使用以下命令安装仅限 Console 的版本: pip3 install --upgrade nsz使用以下命令安装 GUI 版本: pip3 install --upgrade nsz[gui]解压 nsz 文件 nsz -D 文件路径...
Python进阶(3):函数(接上篇)
上一篇我们初步介绍python中函数的定义与调用 Python进阶(2):函数-CSDN博客 这里继续: 关键字参数: 形参1实参1,形参2实参2,...... 关键字参数是指使用形式参数的名字来确定输入的参数值。通过该方式指定实际参数时,不再需要与形式参数的位置完全一致。只要将参数名写正确…...
卒/兵过河前的判断和走法触发器优化
兵(卒):兵(卒)在未过河前,只能向前一步步走,过河以后,除不能后退外,允许左右移动,但也只能一次一步。 迷你世界地图已上传 优化...
生物信息Rust-01
前言-为什么想学Rust? 一直想多学一门编译语言,主要有几个原因吧(1. 看到一位老师实验室要求需要掌握一门编译语言;2. 自己享想试着开发一些实用的生信工具,感觉自己现在相比于数据分析,探索生物学层面的意…...
基于HTML + jQuery + Bootstrap 4实现(Web)地铁票价信息生成系统
地铁票价信息表生成系统 1. 需求分析 1.1 背景 地铁已经成为大多数人出行的首选,北京地铁有多条运营线路, 截至 2019 年 12 月,北京市轨道交通路网运营线路达 23 条、总里程 699.3 公里、车站 405 座。2019 年,北京地铁年乘客量达到 45.3 亿人次,日均客流为 1241.1 万人次…...
智慧水务项目(八)基于Django 5.1 版本PyScada详细安装实战
一、说明 PyScada,一个基于Python和Django框架的开源SCADA(数据采集与监视控制系统)系统,采用HTML5技术打造人机界面(HMI)。它兼容多种工业协议,如Modbus TCP/IP、RTU、ASCII等,并具…...
DeepSeek在消防救援领域的应用解决方案
DeepSeek在消防救援领域的应用解决方案 一、火灾风险动态感知与早期预警 火灾风险动态感知与早期预警是智慧消防的关键环节,DeepSeek通过多模态数据分析,融合烟雾传感器、热成像摄像头和气体浓度检测等数据,能够识别传统阈值法难以捕捉的火…...
VSCode CMake调试CPP程序
文章目录 1 安装C与CMake插件2 配置CMakeLists.txt3 使用CMake编译调试3.1 编译3.2 调试 4 自定义构建调试参考 1 安装C与CMake插件 C插件 CMake插件 2 配置CMakeLists.txt 编写测试程序 #include<iostream>int main(int argc, char const *argv[]) {int a 1, b 2;i…...
AI Agent工程师认证-学习笔记(3)——【多Agent】MetaGPT
学习链接:【多Agent】MetaGPT学习教程 源代码链接(觉得很好,star一下):GitHub - 基于MetaGPT的多智能体入门与开发教程 MetaGPT链接:GitHub - MetaGPT 前期准备 1、获取MetaGPT (1ÿ…...
Spring AI 结构化输出详解
一、Spring AI 结构化输出的定义与核心概念 Spring AI 提供了一种强大的功能,允许开发者将大型语言模型(LLM)的输出从字符串转换为结构化格式,如 JSON、XML 或 Java 对象。这种结构化输出能力对于依赖可靠解析输出值的下游应用程…...
AMGCL库使用示例
AMGCL库使用示例 AMGCL是一个用于解决大规模稀疏线性方程组的C库,它实现了代数多重网格(AMG)预处理器和Krylov子空间迭代求解器。下面是一些AMGCL的使用示例。 基本示例:求解稀疏线性系统 #include <iostream> #include <vector> #includ…...
关于 Java 预先编译(AOT)技术的详细说明,涵盖 GraalVM 的配置、Spring Boot 3.x 的集成、使用示例及优缺点对比
以下是关于 Java 预先编译(AOT)技术的详细说明,涵盖 GraalVM 的配置、Spring Boot 3.x 的集成、使用示例及优缺点对比: 1. 预先编译(AOT)技术详解 1.1 核心概念 AOT(Ahead-of-Time)…...
Video Encoder:多模态大模型如何看懂视频
写在前面 大型语言模型(LLM)已经掌握了理解文本的超能力,而多模态大模型(MLLM)则更进一步,让 AI 拥有了“看懂”图像的眼睛。但这还不够!真实世界是动态的、流动的,充满了运动、变化和声音。视频,正是承载这一切动态信息的关键媒介。 让 LLM 看懂视频,意味着 AI 需…...