Java面向对象 —— 继承

Posted 行稳方能走远

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java面向对象 —— 继承相关的知识,希望对你有一定的参考价值。

前言

下面C语言的代码中定义了两个在内容上高度重复的结构体

#include <stdio.h>

struct Person  //人
{
	int name;
	char* address;
	void (*pEat)();  
	void (*pDrink)();
};

struct Student //学生也是人,所以有一定的重复性
{             
	int name;
	char* address;
	void (*pEat)();
	void (*pDrink)();

	int score; //学生独有的特征,有分数,要上学
	void (*goToSchool)();
};

然而对于C语言来说这两个结构体没有任何联系,这样造成了代码的浪费,重复。

而java的继承特性,就解决了这一问题。

继承的概念和实现

生活中,继承的概念随处可见。

比如:

在这里插入图片描述

java继承背后的思想是:基于已存在的类来构建新类。

当从已存在类继承时,就重用了他的方法和属性,还可以添加新的方法和属性来定制新类以应对要求。

继承的意义:代码重用,体现不同抽象层次

约定:从其他类导出的类叫做子类,通常父类更通用更抽象,子类更特殊更具体。

在java中,除了object外,所有类都是子类,都只有唯一的父类,即为object类。

所以继承在OOP(Object-Oriented Program面向对象编程 )中不可或缺,创建一个类时,总是在继承

extends关键字

在java语言中,用extends关键字来表示一个类继承了另一个类,如Student继承了Person

public class Student extends Person{
...
}

 
  • 1
  • 2
  • 3

例子:

class Person {
    String name;
    String address;

    void eat() {
        System.out.println("人吃饭");
    }
    void drink() {
        System.out.println("人喝水");
    }
}

class Student extends Person{ //可以直接使用Person中的属性了
    int score;
    void goToSchool() {
        System.out.println("学生要上学");
    }
}

public class Demo1 {
    public static void main(String[] args) {
        Person p = new Person();
        p.name = "人";
        p.eat();

        Student s = new Student();
        s.name = "学生";  //person中的特征
        s.eat();         //person中的方法
    }
}

super关键字

super和this关键字的特点类似:super代表的是对父类对象的引用

作用:

(1)当子父类的成员出现同名时,可以通过super来区分
(2)子类的构造方法中,通过super关键字调用父类的构造方法

强调:

(1)当构造一个子类对象的时候一定会先调用父类的构造方法来构造父类的对象。
(2)调用父类构造方法的语句必须是子类构造方法中的第一条指令

super关键字的例子:

class Person {
    String name;
    String address;

    void eat() {
        System.out.println("人吃饭");
    }
    void drink() {
        System.out.println("人喝水");
    }

    //Person(){}//解决方法一:在父类中写一个无参的构造方法
    Person(String name,String address){//构造方法给属性赋初值
        this.name = name;
        this.address = address;
        System.out.println("父类构造方法被调用");
    }
}

class Student extends Person{ //可以直接使用Person中的属性了
    int score;
    void goToSchool() {
        System.out.println("学生要上学");
    }

    /*
    Student(){ //如果不写父类中已有的构造方法的话,系统分配类似于这样的无参构造方法
               //如果父类写了构造方法而子类没有“对应的”构造方法,就会出错
                //解决方法一:在父类中写一个无参的构造方法;
                //解决方法二:应用super关键字,在子类中完成对父类构造方法的引用
    }*/         //父类可能有多个构造方法,子类只需要引用一个初始化自己就好

    //解决方法二:
    Student(String name,String address){ //没有下面的super的话 系统不认定这是一个构造方法
        super(name, address); //调用了父类的构造方法,这个语句必须放在子类构造方法的第一句
        System.out.println("子类构造方法被调用");
    }

    public void eat(){ //因为父类已经有了方法eat,这里权限不能降低,所以加public,后面讲多态会介绍:方法的重写
        super.eat(); //调用父类的eat,super是对父类的引用。
                     //子类中有eat,父类也有同名的方法,如何区分是父类还是子类的eat,用super!
        System.out.println("student eat");
    }
}

public class Demo1 {
    public static void main(String[] args) {
    
        //在实例化一个对象的时候,子类的构造方法调用会导致父类的构造方法也被调用:
        Student s = new Student("小明","西祠胡同1号");
        s.eat();
    }
}

结果:

父类构造方法被调用
子类构造方法被调用
人吃饭
student eat

父类的地址:西祠胡同1号

继承过来的属性和方法的权限研究

重点看看父类中的私有属性private会不会被子类继承,如果可以,显然其他的默认属性如public、默认等肯定也可以。

class Person {
    String name;
    private String address;  //私有的属性

    void eat() {
        System.out.println("人吃饭");
    }

    Person(String name,String address){
        this.name = name;
        this.address = address;
        System.out.println("父类构造方法被调用");
    }    
}

class Student extends Person{
    int score;
    void goToSchool() {
        System.out.println("学生要上学");
    }

    Student(String name,String address){
        super(name, address); //调用了父类的构造方法 可以间接的访问到父亲的address!
    }
    /*
    void setAddress(String address){
        this.address = address; //这里提示了address是父类私有的,说明实际上没有被继承过来                
                               //不给你用,父亲”私藏的“所有东西儿子都是不知道的,继承不到
    }
     */
}

结论:父类私有的无论是方法还是属性,都无法被子类继承。

方法重写Override

方法重写是指子类可以根据需要对从父类继承过来的方法进行改写,是多态机制的前奏。

(1)重写方法必须和被重写方法具有相同的方法名称、参数列表和返回值
(2)重写方法不能比被重写方法有更严格的访问权限
(3)父类中的私有方法,不能被重写
(4)在子类重写的方法中继承调用父类被重写的方法可以通过super.函数名获取

注意区别:前面封装部分提到的方法重载是同一方法名,不同参数列表。而重写的话则全部都是一样的,包括函数名、参数、返回值。

例子:

class Person
{
    String name;
    private String address;

    public void printName(){
        System.out.println("name="+name);
    }

    private void printAddress(){   //父类的私有方法,想要"重写"这个方法,子类中的方法不能是private
        System.out.println("address="+address);
    }
}

class Student extends Person
{
    int score;
    public void printName() { //(1)重写方法必须和被重写方法具有相同的名称、参数列表和返回值
                              //(2)重写方法不能比被重写方法有更严格的访问权限
        System.out.println("子类name=" + name); //方法的实现改了,也是重写啊,别看就改了一点
    }

    public void printAddress(){             //如果这里用的是private,那下面的stu1.printAdd就会报错
        System.out.println("想用你的私有方法");//注意:如果父类方法权限是private,子类不能用private去重写这个方法
    }                                       //这对于子类来说,父类是不可见的,是看不到父类的方法的
}                                           //所以这对于子类来说,就是【构造了一种新的方法】,既不是方法重写也不是方法重载!
                                            //是不是重写,就概念的东西,初学,也不必争辩,知道这么用就行
public class Demo1 {
    public static void main(String[] args){
        Student stu1 = new Student();
        stu1.name = "小明";
        stu1.printName();
        stu1.printAddress();   
    }
}

运行结果:

子类name=小明	
想用你的私有方法            //说明子类对父类私有方法的重写 “成功” 

Object根类常见方法

java中,所有类都直接或间接继承自java.lang.Object类,是所有类的根类。如hashcode()、clone()、getClass()、finalize()。

重点介绍下面两类toString()equals()

toString()

做个简单的测试:

package com.huatianzhu.learn;

class Person
{
    String name;
}

public class Demo1 {
    public static void main(String[] args) {
        Person p = new Person();
        p.name = "huatianzhu";

        System.out.println(p.toString());
    }
}

结果

com.huatianzhu.learn.Person@49e4cb85

 
  • 1

为什么会是上面的结果?

返回格式:

getClass().getName()+"@"+Integer.toHexString(hashCode());

 
  • 1

(1)getClass().getName()代表返回对象所属类的包名.类名,即com.huatianzhu.learn
(2)Integer.toHexString(hashCode())代表将对象的哈希值用16进制表示,其中hashCode()代表返回该对象的哈希值。

常把toString()方法重写后应用

然而,在实际开发中,通常希望toString()方法返回的不只是上面的信息,所以Object的toString()方法通常会被重写,如下:

@Override
public String toString() {
    return "Person{" +
            "name='" + name + '\\'' +
            ", address='" + address + '\\'' +
            '}';
}

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

(这是IDEA的重写方案:自动补全后就跳出来了如上代码)

运行结果:使得toString()根据实际需求打印出信息

Person{name='huatianzhu', address='null'}

 
  • 1

equals()

equals方法没有重写的话,用于判断对象的内存地址引用是否使用一个地址。重写之后一般用来比较对象的内容是否相等(比如student对象,里面有姓名和年龄,我们重写,判断这两个对象是相等的

重写:判断两个对象p1和p2特征是否相同

package com.huatianzhu.learn;

import java.util.Objects;

class Person {
    String name;
    String address;

    public boolean equals(Person p){//传对象参数
        //Person p = (Person)arg0;不给这样做强换
        if(this.address == p.address && this.name == p.name)//C语言比较字符串使用strcmp()
            return true;
        else
            return false;
    }
}

public class Demo1 {
    public static void main(String[] args) {
        Person p1 = new Person();
        p1.name = "xiaoming";
        p1.address = "西祠胡同1号";

        Person p2 = new Person();
        p2.name = "xiaoming";
        p2.address = "西祠胡同1号";

        System.out.println(p1.equals(p2));
    }
}

运行结果

true

 
  • 1

IDEA的重写模板:敲equals可选择的方案之一

package com.huatianzhu.learn;

import java.util.Objects;

class Person {
    String name;
    String address;

    //下面两个override都是IDEA自带的重写模板
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Person)) return false;
        Person person = (Person) o;
        return Objects.equals(name, person.name) &&
                Objects.equals(address, person.address);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, address);
    }
}

public class Demo1 {
    public static void main(String[] args) {
        Person p1 = new Person();
        p1.name = "xiaoming";
        p1.address = "西祠胡同1号";

        Person p2 = new Person();
        p2.name = "xiaoming";
        p2.address = "西祠胡同1号";

        System.out.println(p1.equals(p2));
    }
}

运行结果

true

 
  • 1

String 类重写:判断字符串是否相等

String 类中重写了 equals() 方法用于比较两个字符串的内容是否相等。

语法:public boolean equals(Object anObject)

实例:

public class Test {
    public static void main(String args[]) {
        String Str1 = new String("huatianzhu");
        String Str2 = Str1;  //直接把 对象1 赋值给 对象2 
        String Str3 = new String("chenlichen");
        boolean retVal;

        retVal = Str1.equals( Str2 );
        System.out.println("返回值 = " + retVal );

        retVal = Str1.equals( Str3 );
        System.out.println("返回值 = " + retVal );
    }
}

运行结果:

返回值 = true
返回值 = false

 
  • 1
  • 2

继承练习:java面向对象下的简单工厂模式

之前做智能家居项目的时候,用了C语言的链表,做了简单工厂模式。在java中感受一下,这面向对象(主要是继承)的魅力。免除了C语言中在工厂链表增加节点的模式,其实底层的实现机制也是类似链表。

abstract class Device         //抽象类,abstract后面也会有介绍
{
    String open;
    String close;
    void init(){
        System.out.println("设备的初始化");
    }

    abstract void diffFunction(); //抽象方法,不同设备有不同的使用方法,不好在
                                    //这个大的类进行规划,所以做得抽象点

    Device(String cmd1,String cmd2){
        this.open = cmd1;         //设备的开关指令
        this.close = cmd2;
    }
}

class Camera extends Device
{
    Camera(String cmd1,String cmd2){
        super(cmd1,cmd2);      //当父类有构造方法时(非空),子类必须调用父类的
    }                          //构造方法进行属性的初始化

    void diffFunction(){
        System.out.println("相机拿来拍照"); //对抽象方法的具体化,相机是拿来拍照的
    }                                   //也必须具体化,否则这个继承的子类也要修饰成抽象类

    /*public void takePhoto(){
        System.out.println("拍了张张片"); //每个设备不一样的功能可以在其父类Device中进行方法的抽象
    } */                                //然后在子类中进行具体化,这是面向对象很方便的地方
}

class Lightone extends Device
{
    Lightone(String cmd1,String cmd2){
        super(cmd1,cmd2);
    }

    void diffFunction(){
        System.out.println("第一个灯拿来照亮卧室");
    }

    /*void printInfo(){
        System.out.println("灯1额外执行的东西");
    }*/
}

class Lighttwo extends Device
{
    Lighttwo(String cmd1,String cmd2){
        super(cmd1,cmd2);
    }

    void diffFunction(){
        System.out.println("第二个灯拿来照亮浴室");
    }

    /*void printInfo(){
        System.out.println("灯2额外执行的东西");
    }*/
}

class Factory
{                                          //返回值是对象Device的方法
    static Device getDevice(String name){  //作为静态方法,不用依靠Factory创建的对象访问
        if以上是关于Java面向对象 —— 继承的主要内容,如果未能解决你的问题,请参考以下文章

Java:面向对象--继承

Java面向对象三大特征之继承和多态

Java面向对象

23. java面向对象 - 继承性

Java面向对象特性

java面向对象的三大特征?