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

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

作者:Mylvzi 

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

目录

一.抽象类

1.抽象类的定义

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

3.抽象类的注意事项

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

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

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

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

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

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

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

二.接口(interface)

1.接口的定义

2.接口的语法

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

2.关于接口方法

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

3.接口的特性

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

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

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

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

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

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

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

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

4.实现多接口

5.接口之间的继承

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

三. Object类

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

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

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

toString的源码

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

 equals的源码:

重写equals方法 :

3.hashCode方法 

hashCode的源码:

重写hashCode方法: 

四. 接口使用实例

  1.Comparable接口和Comparator接口

 2.Clonable 接口和深拷贝

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

1.内部类:

2.分类:

1.实例内部类 

2.静态内部类

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

4.局部内部类

补充: 


一.抽象类

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 {@Overridepublic void method() {System.out.println("普通方法!");}@Overridevoid 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 {// 抽象方法必须被重写@Overridepublic void draw() {System.out.println("矩形");}@Overridepublic void trangle() {}
}class Flower extends Shape {// trangle方法未被重写// 只重写一个抽象方法-->err// 所有的抽象方法必须被重写!@Overridepublic void draw() {System.out.println("✿");}
}

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

abstract class Shape {public abstract void draw();public abstract void trangle();}
abstract class Flower extends Shape {// 只重写一个抽象方法-->err// 所有的抽象方法必须被重写!@Overridepublic void draw() {System.out.println("✿");}
}class SmallFlower extends Flower {// 父类Flower的抽象类@Overridepublic void trangle() {}// shape的抽象类@Overridepublic 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}@Overridevoid 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 {@Overridepublic void drawMap() {System.out.println("矩形!!!");}
}class Flower implements IShape {@Overridepublic 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接口中的所有方法@Overridepublic void openDevice() {System.out.println("鼠标设备开启!");}@Overridepublic void closeDevice() {System.out.println("鼠标设备关闭!");}public void clickMouse() {System.out.println("鼠标点击");}
}// 键盘类
public class KeyBoard implements IUSB {// 实现USB类@Overridepublic void openDevice() {System.out.println("键盘设备开启!");}@Overridepublic 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 abstractvoid 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{}// errstatic {}
}

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 {@Overridepublic 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快速调出接口抽象方法的重写@Overridepublic void run() {System.out.println(this.name + "猫正在跑步");}
}

狗类:跑,游

// 狗类 跑+游
class Dog extends Animal implements IRunning,ISwimming {public Dog(String name) {super(name);}@Overridepublic void swim() {System.out.println(this.name + "正在狗刨");}@Overridepublic void run() {System.out.println(this.name + "狗正在跑步");}
}

 鸭子类:跑,游,飞

// 鸭子类 跑,飞,游
class Duck extends Animal implements IRunning,ISwimming,IFlying {public Duck(String name) {super(name);}@Overridepublic void fly() {System.out.println(this.name + "鸭子正在飞");}@Overridepublic void swim() {System.out.println(this.name + "鸭子正在游泳");}@Overridepublic 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 {@Overridepublic 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);// falseSystem.out.println(person1 == person2);// falseSystem.out.println(person1.equals(person2));// falseSystem.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;}@Overridepublic boolean equals(Object object) {// 两个对象地址相同,直接返回trueif (this == object) return true;// 比较的对象是null或两个对象的类型不同,直接返回falseif (object == null || getClass() != object.getClass()) return false;// 此处进行向下转型Person person = (Person) object;return age == person.age;}@Overridepublic 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));// falseSystem.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方法@Overridepublic 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;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}@Override// 重写compareTopublic 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;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}
}//  <Student>表示比较的是Student对象(最好加上,在重写方法时会很方便)
class AgeComparator implements Comparator<Student> {@Overridepublic int compare(Student o1, Student o2) {return o1.age- o2.age;}
}class NameComparator implements Comparator<Student> {@Overridepublic 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,只能跨包子类中使用@Overrideprotected 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.9System.out.println(stu2.m.money);// 19.9System.out.println("================");}
}

 注意:main后面必须添加:

throws CloneNotSupportedException

否则会报错

 

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

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

        // 返回值是Object,所以必须进行强制类型转换Stu stu2 = (Stu) stu1.clone();System.out.println(stu1.m.money);// 19.9System.out.println(stu2.m.money);// 19.9System.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,只能跨包子类中使用@Overrideprotected 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);// 打印外部类的data1System.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();// errinnerClass.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的方法,但是这个类没有名称,所以叫做匿名内部类()通过接口实现@Overridepublic 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.局部内部类非常少见,了解即可

补充: 

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

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

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

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

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

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

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

相关文章

OpenCV实现视频的读取、显示、保存

目录 1&#xff0c;从文件中读取视频并播放 1.2代码实现 1.3效果展示 2&#xff0c;保存视频 2.1 代码实现 2.2 结果展示 1&#xff0c;从文件中读取视频并播放 在OpenCV中我们需要获取一个视频&#xff0c;需要创建一个VideoCapture对象,指定你要读取的视频文件&am…

基于微信小程序的模拟考试小程序的设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言系统主要功能&#xff1a;具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计…

量化交易全流程(四)

本节目录 数据准备&#xff08;数据源与数据库&#xff09; CTA策略 数据源&#xff1a; 在进行量化分析的时候&#xff0c;最基础的工作是数据准备&#xff0c;即收集数据、清理数据、建立数据库。下面先讨论收集数据的来源&#xff0c;数据来源可分为两大类&#xff1a;免…

SLAM从入门到精通(tf的使用)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 在ros的机器人学习过程中&#xff0c;有一件事情是肯定少不了的。那就是坐标系的转换。其实这也很容易理解。假设有一个机器人&#xff0c;它有一个…

推荐算法——Apriori算法原理

0、前言&#xff1a; 首先名字别读错&#xff1a;an pu ruo ao rui 【拼音发音】Apriori是一种推荐算法推荐系统&#xff1a;从海量数据中&#xff0c;帮助用户进行信息的过滤和选择。主要推荐方法有&#xff1a;基于内容的推荐、协同过滤推荐、基于关联规则的推荐、基于知识的…

Spring整合RabbitMQ——生产者

1.生产者整合步骤 添加依赖坐标&#xff0c;在producer和consumer模块的pom文件中各复制一份。 配置producer的配置文件 配置producer的xml配置文件 编写测试类发送消息

《HelloGitHub》第 90 期

兴趣是最好的老师&#xff0c;HelloGitHub 让你对编程感兴趣&#xff01; 简介 HelloGitHub 分享 GitHub 上有趣、入门级的开源项目。 https://github.com/521xueweihan/HelloGitHub 这里有实战项目、入门教程、黑科技、开源书籍、大厂开源项目等&#xff0c;涵盖多种编程语言 …

javascript: Sorting Algorithms

// Sorting Algorithms int JavaScript https://www.geeksforgeeks.org/sorting-algorithms/ /** * file Sort.js * 1. Bubble Sort冒泡排序法 * param arry * param nszie */ function BubbleSort(arry, nszie) {var i, j, temp;var swapped;for (i 0; i < nszie - 1; i)…

动态规划算法(1)--矩阵连乘和凸多边形剖分

目录 一、动态数组 1、创建动态数组 2、添加元素 3、删除修改元素 4、访问元素 5、返回数组长度 6、for each遍历数组 二、输入多个数字 1、正则表达式 2、has.next()方法 三、矩阵连乘 1、什么是矩阵连乘&#xff1f; 2、动态规划思路 3、手推m和s矩阵 4、完…

【生物信息学】计算图网络中节点的中心性指标:聚集系数、介数中心性、度中心性

目录 一、实验介绍 二、实验环境 1. 配置虚拟环境 2. 库版本介绍 3. IDE 三、实验内容 0. 导入必要的工具 1. 生成邻接矩阵simulate_G: 2. 计算节点的聚集系数 CC(G): 3.计算节点的介数中心性 BC(G) 4. 计算节点的度中心性 DC(G) 5. 综合centrality(G) 6. 代…

xilinx的原语的使用

xilinx的原语的使用 在学习FPGA实现千兆网时需要GMII转RGMII&#xff0c;这就涉及了原语的使用&#xff0c;特此记录&#xff01; 一、原语 与RGMII接口相关的原语&#xff1a; BUFG:全局时钟网络 BUFIO&#xff1a;只能采集IO的数据&#xff0c;采集IO数据的时候延时是最低的…

【【萌新的Risc-V学习之再看读不懂的流水线设计-10】】

萌新的Risc-V学习之再看读不懂的流水线设计-10 我们将流水线和之前案例中洗衣服的例子进行对照 我们把整个流水线分为5个阶段 也就是做成五级流水线 IF: 取指令ID: 指令译码和读寄存器堆EX: 执行或计算地址MEM: 数据存储器访问WB: 写回 我先在这里表述一下基本的几个指令的用…

【Java基础】抽象类和接口的使用

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【JavaSE_primary】 本专栏旨在分享学习JavaSE的一点学习心得&#xff0c;欢迎大家在评论区讨论&#x1f48c; 目录 一、抽象类抽象类概念…

无设计经验也能制作专业国庆微传单

如果你正在计划一个国庆活动&#xff0c;或者想要创建一个微传单来宣传你的品牌或产品&#xff0c;那么你可以尝试使用乔拓云微传单平台。通过这个平台&#xff0c;你可以轻松地创建和发布一个精美的微传单&#xff0c;而且完全免费。 以下是制作国庆微传单H5的步骤&#xff1a…

stl格式-3D三角形

文章目录 什么是stl文件?格式首选stl的语法1.这是一个stl格式的文件:(ASCII码)2.下面先举个例子(难度略微提示)补充:关于\<\<我试了一下:这个法线你随便写好像也没问题\>> 3.来个立方体4.最后再写一个由三个直角形组成的立方体(直棱锥)5.amend 修正(右手定则,法线…

【ArcGIS Pro二次开发】(69):使用MapTool实现隐藏和隔离图层

一、MapTool简介 在ArcGIS Pro SDK中&#xff0c;MapTool是一个重要的组件&#xff0c;用于自定义地图操作工具&#xff0c;使用户能够在ArcGIS Pro中执行特定的地图交互操作。 在VS中添加新项&#xff0c;可以找到ArcGIS Pro 地图工具&#xff0c;即为MapTool。 新建后打开c…

优雅的写Controller 层代码这样写才可以

前 言 本篇主要要介绍的就是controller层的处理&#xff0c;一个完整的后端请求由4部分组成&#xff1a; 接口地址(也就是URL地址)请求方式(一般就是get、set&#xff0c;当然还有put、delete)请求数据(request&#xff0c;有head跟body)响应数据(response) 本篇将解决以下3个…

Vue+element开发Simple Admin后端管理系统页面

最近看到各种admin&#xff0c;头大&#xff0c;内容太多&#xff0c;根本不知道怎么改。所以制作了这个项目&#xff0c;只包含框架、和开发中最常用的表格和表单&#xff0c;不用自己从头搭建架构&#xff0c;同时也容易上手二次开发。可以轻松从其他开源项目整合到本项目。项…

基于Qt Creator开发的坦克大战小游戏

目录 介绍开发环境技术介绍安装说明项目目录设计思想项目介绍运行演示知识点记录Gitee源码链接 介绍 &#xff01;&#xff01;&#xff01;资源图片是从网上免费下载&#xff0c;源码都是原创&#xff0c;供个人学习使用&#xff0c;非盈利&#xff01;&#xff01;&#xff…

UE5报错及解决办法

1、编译报错&#xff0c;内容如下&#xff1a; Unable to build while Live Coding is active. Exit the editor and game, or press CtrlAltF11 if iterating on code in the editor or game 解决办法 取消Enable Live Coding勾选