java类和对象:继承、多态、接口、抽象类-1

简介: java类和对象:继承、多态、接口、抽象类

前言:继承和多态是面向对象开发的重要环节,使用得当可以让代码的的功能更加灵动、高效,同时还可以减少代码的冗余。


一、类的继承

     

       1、什么是继承

       例如,现在有这里有一些动物:鸡,鸭,猪,狗等,他们都属于一个动物类(Animal)。根据java类的基础特性我们可以知道,类可以抽象出来实例化成为一个个具体的对象,但是他们都有很多共同属性,例如:

               静态属性: 都有重量、年龄等,你甚至可以给它取个名字来当做他的静态属性。

               动态属性: 都需要进食,都可以行动等。

如果将这些属性都写进猪狗鸡鸭这些类的话,那么上面的这些属性难免会重复写上几次,造成代码的冗余,那么我们就可以将这些共有的特性抽取出来抽象成一个类,然后让这些普通类包含这个类的属性,这就叫继承


       2、如何继承

       1、关键字extends

代码如下:

class Animal{
    public String name;
    public int age;
    public void eat(){
        System.out.println(name+"正在吃饭");
    }
}
class Dog extends Animal{
    public void wangwang(){
        System.out.println(name+"正在汪汪叫");
    }
    public void eat(){
        System.out.println(name+"正在吃狗粮");
    }
}
class Bird extends Animal{
    public String wing;
    public void fly(){
        System.out.println(name + "正在飞");
    }
    @Override
    public void eat(){
        System.out.println(name+"正在吃鸟粮");
    }
}


        这个被共用的Animal类称为父类,这些共用父类属性的被称为子类,其基本的思想就是子类基于某个父类进行扩展,得到一个新的子类。让这个子类也可以继承父类所具有的属性,这个子类同时也可以自己增加父类所不具备的属性,

       子类对象无法调用父类中被private修饰的成员,如图:

这里显示 index  在父类Animal中被 private修饰,无法访问从而报错。对于成员方法同样如此。

子类只能调用父类中被public 或者 protected 修饰的成员变量或方法。

在子类中可以使用super来调用父类的方法或者成员,也可以通过super来进行父类的构造。

       2、构造方法

如果提供了子类的构造方法,那么编译器默认不再提供不带参数的构造方法,如果自己提供了子类的构造方法,那么必须先要帮助父类进行构造:

class Animal{
    public String name;
    public int age;
 
    // 父类构造方法
    public Animal(String name, int age){
        this.name = name ;
        this.age = age ;
    }
}
class Dog extends Animal {
    public int dog_spec1;
    public int dog_spec2;
 
 
    // 子类构造方法
    public Dog(String name, int age, int dog_spec1, int dog_spec2) {
        super(name, age); // 在子类构造完成之前先帮助父类构造
    // 父类构造完成
        this.dog_spec1 = dog_spec1;
        this.dog_spec2 = dog_spec2;
    }
    // 子类构造完成
}

在继承的处理机制当中,党实例化一个子类对象时,父类对象也相应的被实例化,换句话说,在实例化子类对象的时候,java编译器会在子类的构造方法中自动调用父类的无参构造方法或者提供的构造方法。

       3、重写

子类在继承父类后并不只是拥有了父类的属性,还可以对父类的属性进行拓展,即对父类的成员方法进行重写。


       重写就是在子类中保留父类中方法的方法名,然后重写方法体里面的实现内容,更改成员方法的权限,在上面关键字extends 的代码例子中,dog和bird两个类都继承了Animal类然后都重写了Animal类中的eat方法,这里不再举例。


       注意:当重写父类方法的时候,修改方法的权限只能从小范围到大范围的改变,例如,父类中的eat方法被public 修饰,那么子类重写的eat方法就不能被private和protected 修饰。


       3、Object类

       java中有一种比较特殊的类,Object类是所有类的父类,是java类中的最高层的类。在用户创建一个类的时候,除非这个类已经指定了要继承某一个类(java中一个类只能有一个父类),那么它就是默认java.lang,Object 类中继承Object类,(java中每一个类都源于java.lang.Object 类)。由于所有类,除了已经有父类的子类,都是Object的子类,所以在定义类的时候,extends Object 可以省略不写。

       1、Obejct类中方法:

①clone()方法


        创建并返回此对象的副本,一般表达式为x.clone!= x (意思是和原来的对象不是同一个对象)。想要克隆一个对象,首先这个对象的类要实现Cloneable接口,否则则会抛出CloneNotSupportedException异常。


       在java中创建对象有两种方法,一种是我们所熟悉的关键字new,一种是Object类里面的clone()方法。


       那么new是如何创建对象的过程是怎么样的? new关键字用于创建类的新实例对象。new首先会根据new的对象的类型来分配空间。分配完之后,再调用构造函数,为对象进行初始化,构造方法结束后,一个对象创建完毕,然后就会返回这个对象所在堆区的引用地址,在外面就可以使用这个类的引用来接收这个地址并对这个对象进行相关操作。


        都是分配内存,调用clone方法时,分配的内存和源对象(即调用clone方法的对象)相同,然后再使用源对象中对应的各个域,填充新对象的域, 填充完成之后,clone方法返回,一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部。


       而clone()方法,会在堆区生成一个和源对象类相同的对象,然后将源对象的内容填充到新的对象里,这个新对象存放在堆区的不同地方,然后返回这个新对象的引用地址,在外部可以使用所兼容的类的引用来接收这个地址,即x.clone!= x。


       如何使用clone()方法?


使用clone来复制一个对象的时候需要实现Cloneable这个接口,由于clone()这个方法源自Object类,我们在idea中按住左ctrl然后用左键点击clone进去可以发现里面是一个没有方法体的方法


( 由翻译得知,这段绿色的文字建议我们重写这个clone方法)


如果需要复制某个对象,成生一个新的副本,则在这个对象的类实现了这个Cloneable接口之后,还需要对这个clone方法进行重写,在idea中,在类中点击鼠标左键,然后选择Generate快速生成clone方法的重写,如图:

6bddf7a454e4b9633a74124d7ab27f56_f15b2c9c56774c55a74a535b605ff141.png


throws为异常关键字,它用于方法体内部,并且抛出一个异常,党程序执行到throw语句时立即终止,他后面的语句将不会执行。如果不想他抛出异常,现阶段只用将throw 及后面的异常 复制粘贴到main方法的后面。

440b3937206b37c946833e50658d2894_741ec8420d884ec1b897fee627e5b80d.png



我们使用clone来克隆一个对象,但是由于clone方法返回的是一个object类,因此此时相当于将一个父类赋值给一个子类,就形成了向下转型,对于克隆的返回值还需要进行一个强制类型转化,将其转化为与接收对象相同的类类型:

b23d6ce20b3a63ce062612a05b492fd9_02726bdc428442738d96fde5366c00f7.png


代码如下:

以学生为例子,创建一个Student类,并实现Cloneable接口,然后重写clone方法。

class Student implements Cloneable{
    String name;
    int age;
    int score;
 
    public Student(String name, int age, int score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }
 
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
 
    @Override
    public String toString() {
        return "Student"+ "name:"+name +"  age: "+age + " score:"+score;
    }
}
public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        Student student1 = new Student("zhangsan",18,100);
        Student student2 = (Student) student1.clone();
        System.out.println(student1);
        System.out.println(student2);
    }
}


打印结果如下:


深浅克隆:

我们在上面的Student的类外面定义一个另外一个类Money(可以认为money是每个人的刚需必须有而生成的一个组合类型),我们暂时忽略student类当中的其他成员变量,只写入一个Money类来组合,代码如下

class Money{
    public int money = 10;
}
class Student implements Cloneable{
 
    public Money m = new Money();
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    @Override
    public String toString() {
        return "Student"+ " money:"+m;
    }
}
public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        Student student1 = new Student();
        Student student2 = (Student) student1.clone();
        System.out.println(student1.m.money);
        System.out.println(student2.m.money);
        student1.m.money = 100;
        System.out.println(student1.m.money);
        System.out.println(student2.m.money);
    }
}


解释如下:


我们给出的Money类中的有一个money成员,给出的默认值为10,然后将这个类和Student类进行组合,在mian方法中生成一个新的student1对象,并对其进行克隆,然后用一个student2接收这个引用地址,重写tostring方法后进行打印。后将student其中的Money类生成的m对象中的money改为100,然后再进行money的打印。结果如下:


25fafec6f10adafcf9975bc8c71c8396_be5a5aa42374489096349a44d8eaffd7.png


对比结果可以发现,修改student1的money的值后,student2 的money值也被修改了。

这是为什么呢???

这是因为clone在赋值对象的时候,其中的Money类的对象m的引用也复制了,

即:student1.m == student2.m  

修改student1 的m的对象的内容,就相当于修改了student2的m对象的内容;

为了印证这个猜想,我们对其hash值进行打印:结果发现相同

644351f86f953080a895753edf4c78a6_e636038939df45bfb52fbb66251e78b3.png


图解如下

这种克隆称为浅克隆,也叫作浅拷贝。

如何进行深克隆???

如果要进行深克隆,就需要继续重写clone方法。针对克隆的m是相同引用的问题,我们做出修改:在克隆出一个student对象后,将里面的Money类的m对象的引用进行修改,要么使用new来新建一个m对象,或者使用clone对当前对象的Money类类型的m对象进行再次克隆。

注意:为了实现对m的拷贝,m的Money类也需要实现Cloneable接口并重写clone方法。

d54a92b2166e1aeb28125a99d6f25f74_2ab61936cb5744df80ba797fc59a66c6.png

    protected Object clone() throws CloneNotSupportedException {
        Student student = (Student) super.clone();
        //student.m = new Money();
        //student.m.money = this.m.money
        student.m = (Money)m.clone(); // m的Money类型也需要接口和重写clone方法
        return  student;
    }


②equals方法

在java中我们经常使用 == 运算符来比较其左右两端的数据,其规则如下:

如果 == 两侧都是相同的基本数据类型,则比较其值是否相同,返回true或者fasle

如果 == 两侧是对象的引用,则比较其引用的地址是否相同,例如:

public class Test {
 
        public static void main(String[] args) throws CloneNotSupportedException {
            String str1 = new String("123");
            String str2 = new String("123");
            if ( str1 == str2 ){
                System.out.println("True");
            }else {
                System.out.println("False");
            }
            /
            String str3 = "123";
            String str4 = "123";
            if (str3 == str4){
                System.out.println("True");
            }else {
                System.out.println("False");
            }
            if (str1.equals(str2)){
                System.out.println("True");
            }else {
                System.out.println("False");
            }
 
 
        }
}

结果为:

94c701a5c34a6cadc2ce85f2e771fcbd_c7ba6ebe86384b9ea2f9547a78926697.png

我们在main函数里面新创建两个str1和str2对象,然后用 == 对其进行比较,因为他们是两个不同对象的引用,所以第一个输出位false,而str3和str4,两个都是指向的同一个内容“123”,在str3去创建的时候,“123”这个字符串被放入了堆区的内存池中,str3指向这个字符串,当str4去创建一个字符串对象的时候,程序会首先去内存池查看是否已经存在相同内容的字符串,如果存在,就将其指向已经存在的字符串,否则就会生成一个新的字符串对象》》:


b33dd97a07f2cc6bd73f1215844f6c52_1fa8e9c40b3941758a1d8c87245cd483.png


而.equals方法则是比较两个引用的对象的实际内容是否相同,案例如上图代码,因为str1和str2都的对象的内容都是:字符串"123",所以第三个值为true。

但是由于在自定义类中使用equals方法比较两个同类的不同对象的时候,equals方法的默认实现是使用“==”,运算符来比较两个对象的引用地址,而不是比较对象内容,所以要想真正做到比较两个自定义类的对象的内容,还需要在自定义类中重写equals方法。


③getClass()方法

他会返回对象执行时的Class实例,然后使用此实例调用getName()方法取得实例对应的类的名称,语法如下:

getClass().getName();


使用getClass方法返回并用Class对象来接收这个类的实例,并以此调用他的信息,例如这个实例里面的方法等。


④toString()方法


功能是将一个对象以字符串的形式输出,他会返回一个String实例,通常toString方法在自定义类中需要重写,来完整地输出这个对象的字符串形式。案例如下:

import java.lang.Object;
class Student{
    String name;
    int age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return name + " " + age;
    }
}
public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        Student student1 = new Student("zhangsan",18);
        System.out.println(student1);
    }
}

输出结果为

3e05be438fe7399e7a24f2ec6785c453_3d08d0876ca942078d4adcca7659b4fa.png

实例化一个student1对象,在重写toString方法后,调用打印函数对其进行输出,结果如上图。


但是为什么这个打印函数会自动调用toString方法???

我们在idea中按住ctrl+鼠标左键单机println,结果如下:


a189009c4ac095db607883a1bc0b50eb_6ee6498ed01c44d295f1e8cbcf49b7cd.png


发现接收类型使一个Object类的x,Object类是所有类的父类,这里发生向上转型,在synchronize里面打印s,于是我们再次按住ctrl加鼠标左键单机进入String s = 后面的valueOf查看Object类的x实例转化为String s 的原理,情况如下:


image.png


发现,这里调用了你传进来的Object类的实例obj的方法toString (),而toString()方法已经在我们的子类中被重写,此时发生了动态绑定,jvm会依次在Student,Object类中查找(顺着继承链)实现了该toSting方法的类,并调用。此处已经在Student类中实现。


同时这也是多态的思想,关于多态,后面会讲到。


java类和对象:继承、多态、接口、抽象类-2

https://developer.aliyun.com/article/1515769

目录
相关文章
|
24天前
|
JSON Java Apache
非常实用的Http应用框架,杜绝Java Http 接口对接繁琐编程
UniHttp 是一个声明式的 HTTP 接口对接框架,帮助开发者快速对接第三方 HTTP 接口。通过 @HttpApi 注解定义接口,使用 @GetHttpInterface 和 @PostHttpInterface 等注解配置请求方法和参数。支持自定义代理逻辑、全局请求参数、错误处理和连接池配置,提高代码的内聚性和可读性。
|
1月前
|
安全 Java 编译器
Java对象一定分配在堆上吗?
本文探讨了Java对象的内存分配问题,重点介绍了JVM的逃逸分析技术及其优化策略。逃逸分析能判断对象是否会在作用域外被访问,从而决定对象是否需要分配到堆上。文章详细讲解了栈上分配、标量替换和同步消除三种优化策略,并通过示例代码说明了这些技术的应用场景。
Java对象一定分配在堆上吗?
|
15天前
|
Java
在Java中,接口之间可以继承吗?
接口继承是一种重要的机制,它允许一个接口从另一个或多个接口继承方法和常量。
45 1
|
25天前
|
Java
java线程接口
Thread的构造方法创建对象的时候传入了Runnable接口的对象 ,Runnable接口对象重写run方法相当于指定线程任务,创建线程的时候绑定了该线程对象要干的任务。 Runnable的对象称之为:线程任务对象 不是线程对象 必须要交给Thread线程对象。 通过Thread的构造方法, 就可以把任务对象Runnable,绑定到Thread对象中, 将来执行start方法,就会自动执行Runable实现类对象中的run里面的内容。
38 1
|
1月前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
44 4
|
1月前
|
安全 Java
在 Java 中使用实现 Runnable 接口的方式创建线程
【10月更文挑战第22天】通过以上内容的介绍,相信你已经对在 Java 中如何使用实现 Runnable 接口的方式创建线程有了更深入的了解。在实际应用中,需要根据具体的需求和场景,合理选择线程创建方式,并注意线程安全、同步、通信等相关问题,以确保程序的正确性和稳定性。
|
1月前
|
Java
Java基础(13)抽象类、接口
本文介绍了Java面向对象编程中的抽象类和接口两个核心概念。抽象类不能被实例化,通常用于定义子类的通用方法和属性;接口则是完全抽象的类,允许声明一组方法但不实现它们。文章通过代码示例详细解析了抽象类和接口的定义及实现,并讨论了它们的区别和使用场景。
|
1月前
|
Java 测试技术 API
Java零基础-接口详解
【10月更文挑战第19天】Java零基础教学篇,手把手实践教学!
23 1
|
1月前
|
存储 缓存 NoSQL
一篇搞懂!Java对象序列化与反序列化的底层逻辑
本文介绍了Java中的序列化与反序列化,包括基本概念、应用场景、实现方式及注意事项。序列化是将对象转换为字节流,便于存储和传输;反序列化则是将字节流还原为对象。文中详细讲解了实现序列化的步骤,以及常见的反序列化失败原因和最佳实践。通过实例和代码示例,帮助读者更好地理解和应用这一重要技术。
33 0
|
18天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####