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

目录
相关文章
|
1天前
|
Java 开发者
Java 面向对象编程实战:从类定义到对象应用,让你成为高手!
【6月更文挑战第15天】在Java中,掌握面向对象编程至关重要。通过创建`Book`类,展示了属性如`title`和`author`,以及构造方法和getter方法。实例化对象如`book1`和`book2`,并访问其属性。进一步扩展类,添加`pages`和`calculateReadingTime`方法,显示了类的可扩展性。在更大规模的项目中,如电商系统,可以定义`Product`、`User`和`Order`类,利用对象表示实体和它们的交互。实践是精通Java OOP的关键,不断学习和应用以提升技能。
|
1天前
|
Java 开发者
为什么 Java 的类和对象如此重要?看完这篇你就懂了!
【6月更文挑战第15天】在Java中,类和对象是核心概念。类作为蓝图定义数据(如角色的名称和健康值)和行为(如`takeDamage`方法),而对象是类的实例,允许交互(如战士受伤)。通过继承,如`Circle`和`Rectangle`扩展`Shape`类,实现多态性,增强代码灵活性。类和对象促进代码组织、复用和可维护性,是高效软件开发的基础。
|
1天前
|
设计模式 Java
一文掌握 Java 面向对象精髓:从类定义到对象实战
【6月更文挑战第15天】Java面向对象编程初学者指南:类是对象模板,如`Person`类含`name`和`age`属性。创建对象用`new`,如`Person person = new Person()`。访问属性如`person.name="Alice"`,调用方法如`person.sayHello()`。类能继承,如`Student extends Person`。对象间共享数据可传参或共用引用。多态性允许父类引用调用子类方法。注意对象生命周期和内存管理,避免内存泄漏。通过实践和理解这些基础,提升编程技能。
|
1天前
|
Java
深入 Java 面向对象:类的定义,竟然藏着这么多门道!
【6月更文挑战第15天】Java中的类定义是OOP的基础,它封装属性(如Student的name和age)和行为(如study())。简单的类仅触及表面,而复杂的类可模拟真实世界对象的多样性和交互。类还可通过继承扩展,如Student从Person派生,增加特有属性和行为。接口和抽象类等概念进一步增强了灵活性和可扩展性。类定义的深度和广度是构建高效、可维护代码的关键。
|
1月前
|
Java
Java中的继承和多态是什么?请举例说明。
Java中,继承让子类继承父类属性和方法,如`class Child extends Parent`,子类可重写父类方法。多态允许父类引用指向子类对象,如`Animal a = new Dog()`,调用`a.makeSound()`会根据实际对象类型动态绑定相应实现,增强了代码灵活性和可扩展性。
18 0
|
1月前
|
搜索推荐 Java
Java的面向对象特性主要包括封装、继承和多态
【4月更文挑战第5天】Java的面向对象特性主要包括封装、继承和多态
22 3
|
1月前
|
Java 程序员 编译器
Java继承与多态知识点详解
本文主要讲解的是Java中继承与多态的知识点
|
4天前
|
Java 编译器 开发者
Java基础3-JVM层面理解Java继承、封装、多态的实现原理(二)
Java基础3-JVM层面理解Java继承、封装、多态的实现原理(二)
6 0
|
4天前
|
存储 Java 索引
Java基础3-JVM层面理解Java继承、封装、多态的实现原理(一)
Java基础3-JVM层面理解Java继承、封装、多态的实现原理(一)
9 0
|
16天前
|
Java 数据安全/隐私保护
Java的封装、继承与多态技术性文章
Java的封装、继承与多态技术性文章
19 1