JAVA入门之面向对象 继承和重写

继承

首先什么是继承呢?

继承就是使用一个类的定义,复制并扩展出一个新的类型,那么新的类型可以使用原来类型的属性和功能,也就是不劳而获。
当然新类型也可以扩展出,自己个性化的属性和功能,这就叫长江后浪推前浪,一代更比一代强。

那为什么要使用继承呢?

当然是为了省事呗,这和现实中是一个道理,现实中所谓人生何处不拼爹,父母拥有的儿女都能自动享用,所以java中的继承和现实中都是一个道理,为了省事。

什么是重写呢?

但是子类也不是照单全收父类的属性和方法,如果子类觉得从父类继承来的方法不爽,它完成可以利用重写来推翻父类的定义,自己定义一个新的方法实现。
重写是JAVA当中非常非常重要的一个功能,利用重写可以实现JAVA中最强大的功能之一“动态方法调度”。


接下来让我们进一步的理解继承和重写

在这里插入图片描述

概念(什么是继承)

继承就是使用一个类的定义,复制并扩展出一个新的类定义。
子类( Sub class )可以继承父类( Super class )的成员变量及成员方法,同时也可以定义自己的成员变量和成员方法;
Java语言不支持多重继承,-个类只能继承一-个父类 ,但一个父类可以有多个子类。

泛化的过程

泛化:从多个类中,抽取相同部分, 生成父类的过程叫泛化
设计时,从子类泛化出公共父类,再让子类继承父类。
例如:电商网站,用户分为买家和卖家。其中:

-买家信息:姓名,地址,手机号,等级等
-卖家信息:姓名,地址,手机号,粉丝数,服务态度等

另外,买家和卖家拥有的功能,分别是:

-买家功能:查询商品,查询订单,评价商品等
-卖家功能:查询商品,查询订单,修改订单,修改商品等

JAVA的继承的实现和生活中的实现有点颠倒,现实中大家都是先有父母后有孩子,而JAVA在设计时,却是先有子类,后有父类。

接下来我们跟着买家和卖家 设计出两个类的定义

在这里插入图片描述
我们可以发现红色标记出来的属性和功能,都是两个类相同的部分,甚至这两个相同的部分,占了类定义的绝大部分,如果今后在代码中实现这两个类,这些重复部分则会重复定义两次,这种重复不但影响了开发的效率,还会给今后的修改带来巨大的隐患。

开发人员以后在开发中,应该时刻记住: 依次定义,处处使用的效果,既能避免重复代码,又能会今后的修改提高遍历。

既然发现了重复,应该怎么避免呢?

这就要采用泛化,把相同的部分提取到一个公共的类中。

比如说我们知道买家和卖家,其实在需求中都称为用户,所以我们就需要就定义一个用户类,用来包含两个类公共的部分。
首先用户类中包含,属性和功能:
在这里插入图片描述

此时大家观察一下,买家和卖家类中 只剩下各自个性化的部分,由此我们得出结论:
泛化过程就是将 共性的部分,提取到父类中,而子类仅包含个性的部分。

下面,我们在代码中按照泛化的结果,定义三个类

程序中先定义父类,在定义子类
extends关键字

编码时,必须先定义父类,再定义子类,然后才能继承父类
子类可以通过extends关键字 ,继承父类中的属性和功能:
语法: public class子类extends父类{… …}
例如:

  • public class Customer extends 'ier
    -表示Customer类的对象可以继承得到User类的属性和功能
  • public class Seller extends User
    -表示Seller类的对象可以继承得到User类的属性和功能

首先我们来创建一个 封装买家和卖家公共属性和功能的父类
package day06;

/**
 * 封装买家和卖家公共属性和功能的父类
 */
public class User {
    
    
    /*公共属性*/
    String name; //姓名
    String address; //地址
    String mobile; //手机号

    /*公共的功能*/
    //1.查询商品的功能
    public void searchProduct(){
    
    
        System.out.println(name+"查询商品...");
    }

    //2.查询订单
    public void searchOrders(){
    
    
        System.out.println(name+"查询订单...");
    }

}

创建封装买家属性和个性功能的子类
package day06;

/**
 * 封装买家属性和个性功能的类
 */
public class Customer extends User{
    
    
    /*买家独有的属性 等级*/
    int lv1;
    /*买家独有的功能 评价商品*/
    public void evaluate(){
    
    
        System.out.println(name+"评价商品");
    }
}

创建封装卖家属性和个性功能的子类
package day06;

/**
 * 封装卖家属性和个性功能的类
 */
public class Seller extends User{
    
    
    /*卖家独有的属性 粉丝的数量*/
    int fans;
    /*卖家独有的属性 服务态度的评分*/
    double service;

    /*卖家独有的功能 修改订单*/
    public void modifyOrder(){
    
    
        System.out.println(name+"修改订单... ");
    }
    /*卖家独有的功能 修改商品*/
    public void modifyProduct(){
    
    
        System.out.println(name+"修改商品...");
    }
}

接下来让我们测试子类能否调用父类的公共属性与方法呢?

package day06;

public class Test {
    
    
    public static void main(String[] args) {
    
    
        User user = new User();
        Customer customer = new Customer();
        Seller seller = new Seller();
        //给客户取一个名字
        customer.name="白富美";
        seller.name="店小二";
        //客户 白富美查询订单
        customer.searchOrders();
        //客户 白富美查询商品
        customer.searchOrders();
    }
}

运行结果:
在这里插入图片描述
调用成功,说明customer 和 seller 两个对象都通过继承,实现对User父类的调用

子类之间能否互相使用对方的属性或功能呢?

接下来我们看一下:
在这里插入图片描述
我们可以看到,程序报错了,说customer 不是 fans的方法,说明了子类之间,是毫无关系的,各自为栈。
这就好像生活中,你兄弟有钱,和你家没关系,这就是好比亲兄弟明算账。


继承中的传递性

多级继承:将一个子类作为另一个类的父亲。
传递性:子类可以使用自己所有父类的属性和功能。反之则不能使用。
例如:
在这里插入图片描述

继承中构造方法

・Java中 ,实例化对象,都会调用构造方法。作用是初始化成员变量的值。
・继承中,实例化子类对象同时,既要初始化父类成员变量的值,又要初始化子类成员变量的值。所以,实例化子类对象时, 一定会先调用父类的构造方法,再调用子类的构造方法。
・子类的构造方法中,第一句话必须要使用super关键字调用父类的构造方法。
・如果子类的构造方法中没有调用父类的构造方法,编译器会自动加入一行代码: super() ,强行首先调用父类无参构造方法。
・如果父类找不到无参构造方法,则编译错误。

接下来让我们看案例:

在这里插入图片描述

package day06;

public class Test {
    
    
    public static void main(String[] args) {
    
    
    }
}
class IPad{
    
    
    double screen; //屏幕大小
    int battery; //电池容量
    //模拟编译器自动生成
    IPad(){
    
    
        this.screen=0.0;
        this.battery=0;
    }
}

class IPhone extends IPad{
    
    
    String mobile; //手机号
    //模拟编译器自动生成
    //因为现在IPhone作为子类出现了,所以在初始化自己的成员变量之前
    //必须先调用super()方法,就算你不加编译器也会加上
    IPhone(){
    
    
        super();//表示调用父类的构造方法
        this.mobile=null;
    }
}

在这里插入图片描述

为Ipad和IPhone添加带参构造方法

在这里插入图片描述

package day06;


public class Test {
    
    
    public static void main(String[] args) {
    
    
        /*继承中的构造方法测试*/
        IPad air2=new IPad(9.7,8759);//初始化一个IPad,9.7寸 8759毫安
        System.out.println("屏幕:"+air2.screen+" 电池:"+air2.battery);
        IPhone if6=new IPhone(5.2,1810,"123456789");
        System.out.println("屏幕:"+if6.screen+" 电池:"+if6.battery+" 手机号:"+if6.mobile);
    }
}
class IPad{
    
    
    double screen; //屏幕大小
    int battery; //电池容量

    public IPad(double screen, int battery) {
    
    
        this.screen = screen;
        this.battery = battery;
    }
}

class IPhone extends IPad{
    
    
    String mobile; //手机号

    public IPhone(double screen, int battery, String mobile) {
    
    
        super(screen, battery);
        this.mobile = mobile;
    }
}

运行结果:
在这里插入图片描述

父类的引用指向子类的对象

一个子类的对象可以向上转型,为父类的类型。即,父类型的变量,可以指向子类的对象。
在这里插入图片描述

package day06;

public class Test {
    
    
    public static void main(String[] args) {
    
    
        /*继承中的构造方法测试*/
        IPad air2=new IPad(9.7,8759);//初始化一个IPad,9.7寸 8759毫安
        System.out.println("屏幕:"+air2.screen+" 电池:"+air2.battery);
        IPhone if6=new IPhone(5.2,1810,"123456789");
        System.out.println("屏幕:"+if6.screen+
                " 电池:"+if6.battery+
                " 手机号:"+if6.mobile);
        /*向上转型测试*/
        IPad charger;
        charger=air2;//编译正确:air本身加上IPad类型
        charger=if6;//编译正确:
        charger(charger);
        charger(air2);
    }
    public static void charger(IPad charger){
    
    
        System.out.println(charger.toString()+"正在充电");
    }
}
class IPad{
    
    
    double screen; //屏幕大小
    int battery; //电池容量

    public IPad(double screen, int battery) {
    
    
        this.screen = screen;
        this.battery = battery;
    }
}

class IPhone extends IPad{
    
    
    String mobile; //手机号

    public IPhone(double screen, int battery, String mobile) {
    
    
        super(screen, battery);
        this.mobile = mobile;
    }
}

方法的重写(Override)

子类可以重写继承自父类的方法,即方法名和参数列表与父类的方法相同;但方法的实现不同。

当调用子类对象的重写方法时,运行的只能是子类重写后的新实现。

在这里插入图片描述

第一种情况,典型的仅继承

package day06;

public class override {
    
    
    public static void main(String[] args) {
    
    
       double fee=480; //实际学费480元
        double money=500; //家长给了500元
        Parent p = new Parent(); //家长类型的对象
        Child1 c1 = new Child1(); //大儿子类型的对象
        Child2 c2 = new Child2(); //小儿子类型的对象
        
        //家长的要求
        p.pay(money,fee);
        //大儿子的做法
        c1.pay(money,fee);
        //小儿子的做法
        c2.pay(money,fee);
    }
}

/**
 * 封装家长功能的类
 */
class Parent{
    
    
    /**
     * 交学费的方法
     * @param money 找家长要了多少钱?
     * @param fee 实际交的学费
     */
    public void pay(double money,double fee){
    
    
        System.out.println("家长让交:"+fee+"元学费,剩下 "+(money-fee)+"当零花钱");
    }
}

/**
 * 封装大儿子的子类
 */
class Child1 extends Parent{
    
    

}

/**
 * 封装小儿子的类
 */
class Child2 extends Parent{
    
    

}

运行结果:大儿子和小儿子仅继承了家长的方法
在这里插入图片描述

第二种情况 完全重写

在这里插入图片描述

package day06;

public class override {
    
    
    public static void main(String[] args) {
    
    
       double fee=480; //实际学费480元
        double money=500; //家长给了500元
        Parent p = new Parent(); //家长类型的对象
        Child1 c1 = new Child1(); //大儿子类型的对象
        Child2 c2 = new Child2(); //小儿子类型的对象

        //家长的要求
        p.pay(money,fee);
        //大儿子的做法
        c1.pay(money,fee);
        //小儿子的做法
        c2.pay(money,fee);
    }
}

/**
 * 封装家长功能的类
 */
class Parent{
    
    
    /**
     * 交学费的方法
     * @param money 找家长要了多少钱?
     * @param fee 实际交的学费
     */
    public void pay(double money,double fee){
    
    
        System.out.println("家长让交:"+fee+"元学费,剩下 "+(money-fee)+"当零花钱");
    }
}

/**
 * 封装大儿子的子类
 */
class Child1 extends Parent{
    
    
    /**
     * 大儿子重写的交学费方法
     * @param money 找家长要了多少钱?
     * @param fee 实际交的学费
     */
   public void pay(double money,double fee){
    
    
       System.out.println("大儿子调皮,没交学费都买了变形金刚"+money+"元的变形金刚");
   }
}

/**
 * 封装小儿子的类
 */
class Child2 extends Parent{
    
    

}

运行结果:大儿子重写了父类的方法
在这里插入图片描述

第三种情况:在父类的基础上,再加工。

子类在重写父类方法时,可以通过super关键字调用父类的方法。
在这里插入图片描述

package day06;

public class override {
    
    
    public static void main(String[] args) {
    
    
       double fee=480; //实际学费480元
        double money=500; //家长给了500元
        Parent p = new Parent(); //家长类型的对象
        Child1 c1 = new Child1(); //大儿子类型的对象
        Child2 c2 = new Child2(); //小儿子类型的对象

        //家长的要求
        p.pay(money,fee);
        //大儿子的做法
        c1.pay(money,fee);
        //小儿子的做法
        c2.pay(money,fee);
    }
}

/**
 * 封装家长功能的类
 */
class Parent{
    
    
    /**
     * 交学费的方法
     * @param money 找家长要了多少钱?
     * @param fee 实际交的学费
     */
    public void pay(double money,double fee){
    
    
        System.out.println("家长让交:"+fee+"元学费,剩下 "+(money-fee)+"当零花钱");
    }
}

/**
 * 封装大儿子的子类
 */
class Child1 extends Parent{
    
    
    /**
     * 第二种情况,重写
     * 大儿子重写的交学费方法
     * @param money 找家长要了多少钱?
     * @param fee 实际交的学费
     */
   public void pay(double money,double fee){
    
    
       System.out.println("大儿子调皮,没交学费都买了变形金刚"+money+"元的变形金刚");
   }
}

/**
 * 封装小儿子的类
 */
class Child2 extends Parent{
    
    
    /**
     * 第三种情况
     * 小儿子重写的交学费方法
     * @param money 找家长要了多少钱?
     * @param fee 实际交的学费
     */
    public void pay(double money,double fee){
    
    
        //第一件事:按照妈妈的要求,先交学费
        super.pay(money,fee);//使用super关键字调用了父类的方法
        //第二件事:将剩余的钱给了爸爸
        System.out.println("小儿子把零花钱给了爸爸");
    }
}

运行结果:调用完父类的方法,在重写
在这里插入图片描述

动态方法调度

动态方法调度:在运行时,父类变量根据指向子类对象的不同,动态判断调用何种重写方法。

依据:当调用子类对象的重写方法时,运行的只能是子类重写后的新实现

例如: -位爸爸带着两个孩子去参加爸爸去哪儿。爸爸只知道要为两个孩子做饭。而孩子们都得到一张自己的任务卡。任务卡上写着具体要做什么饭。
在这里插入图片描述

让我们先创建好需要的类和方法

package day06;

public class override {
    
    
    public static void main(String[] args) {
    
    
       double fee=480; //实际学费480元
        double money=500; //家长给了500元
        Parent p = new Parent(); //家长类型的对象
        Child1 c1 = new Child1(); //大儿子类型的对象
        Child2 c2 = new Child2(); //小儿子类型的对象
        
    }
}

/**
 * 封装家长功能的类
 */
class Parent{
    
    
    /**
     * 爸爸做饭的任务
     */
   public void cook(){
    
    
       System.out.println("爸爸的任务: 做饭");
   }
}

/**
 * 封装大儿子的子类
 */
class Child1 extends Parent{
    
    
    /**
     * 大儿子任务卡,要做蛋炒饭
     * 继承了父类的方法,并重写了它
     */
   public void cook(){
    
    
       System.out.println("帮大儿子做蛋炒饭");
   }
}

/**
 * 封装小儿子的类
 */
class Child2 extends Parent{
    
    
    /**
     * 小儿子的任务卡是:包饺子
     * 继承了父类的方法,并重写了它
     */
    public void cook() {
    
    
        System.out.println("帮小儿子包饺子");
    }
}

接下来,这位爸爸怎么帮儿子完成做法的任务呢?

首先爸爸先去帮大儿子完成蛋炒饭

在这里插入图片描述

爸爸在去帮小儿子完成包饺子

在这里插入图片描述

请大家记住:new的是谁,就调用谁的方法

在这里插入图片描述

package day06;

public class override {
    
    
    public static void main(String[] args) {
    
    
       double fee=480; //实际学费480元
        double money=500; //家长给了500元
        Parent p = new Parent(); //家长类型的对象
        Child1 c1 = new Child1(); //大儿子类型的对象
        Child2 c2 = new Child2(); //小儿子类型的对象

        /*动态方法调度*/
        Parent dad=null; //定义一个父类型的变量引用
        Child1 son1=new Child1();
        Child2 son2=new Child2();
        dad=son1; //爸爸带着大儿子一起做蛋炒饭
        dad.cook();
        dad=son2; //爸爸又帮小儿子一起包饺子
        dad.cook();
        dad=new Parent(); //只有父类型对象自己,可以调用父类中的方法
        dad.cook();
    }
}

/**
 * 封装家长功能的类
 */
class Parent{
    
    
    /**
     * 爸爸做饭的任务
     */
   public void cook(){
    
    
       System.out.println("爸爸的任务: 做饭");
   }
}

/**
 * 封装大儿子的子类
 */
class Child1 extends Parent{
    
    
    /**
     * 大儿子任务卡,要做蛋炒饭
     * 继承了父类的方法,并重写了它
     */
   public void cook(){
    
    
       System.out.println("帮大儿子做蛋炒饭");
   }
}

/**
 * 封装小儿子的类
 */
class Child2 extends Parent{
    
    
    /**
     * 小儿子的任务卡是:包饺子
     * 继承了父类的方法,并重写了它
     */
    public void cook() {
    
    
        System.out.println("帮小儿子包饺子");
    }
}

运行结果:
在这里插入图片描述

总结一下:

在这里插入图片描述

重写和重载的区别(鄙视题)

在这里插入图片描述

现在就当你自己是编译器

凡是new的代码都给我先屏蔽掉,因为编译器谁给你创建对象去,所以现在所有的new都是干扰:

在这里插入图片描述

然后我们直接看最后一句话就行了:

在这里插入图片描述

顺着g方法,我们找到goo类中,g方法的定义:

今后凡是看见重载,先看参数的差别

一个是Super类型的参数,一个是Sub类型的参数
在这里插入图片描述

我们发现刚才调用g方法的时候,传入的obj变量是Super类型,显然编译器绑定的是第一个g方法在这里插入图片描述

下面,我们人格分裂一下,在扮演一下JVM,看看运行时程序怎么执行第一个g方法的

运行JVM就要创建对象了,我们把刚抹掉的new,拿回来:

在这里插入图片描述

new的是谁就执行谁的方法,我们可以看见这里new的是Sub对象,所以这里obj.f()和new的左边没有半点关系在这里插入图片描述

这道题中父类Super如果没有f方法的定义,就是编译错误,因为如果父类Super没有方法的定义,这就不是一个重写,如果不是重写,父类类型的变量这个变量obj就永远无法访问到子类 类型的非重写方法!

final关键字

1,概述

最终的,final的本意是用来, 控制子类重写的现象.
如果父类的某些方法,不许子类修改. 只需要把父类的方法修饰成最终的

2,特点

可以修饰类,不能被继承
可以修饰方法,不能被重写
可以修饰变量,值不能被修改,是一个常量

3,测试

package cn.tedu.oop;
//测试 final关键字
public class Test1_Final {
    
    
	public static void main(String[] args) {
    
    
			Zi zi = new Zi();
			zi.eat();//使用了父类的方法
//			zi.name = "张三" ;
//			System.out.println( zi.name );//继承了父类的属性
			System.out.println( Fu.NAME );//继承了父类的属性
	}
}
//1, 可以修饰类,不能被继承The type Zi cannot subclass the final class Fu
//final class Fu{ 
class Fu {
    
    
	//2, 可以修饰方法,只能被继承不能被重写 Cannot override the final method from Fu
	final public void eat() {
    
    
		System.out.println("吃肉");
	}
	//3, 可以修饰变量,值不能被修改,是一个常量 The final field Fu.name cannot be assigned
//	final String name = "tony" ;
	public static final String NAME = "tony" ;//常量的 标准写法
}
class Zi extends Fu{
    
    
	//重写: 方法声明和父类一样 + 有权限
//	@Override
//	public void eat() {
    
    
//		System.out.println("喝汤");
//	}
}

猜你喜欢

转载自blog.csdn.net/QQ1043051018/article/details/112392012
今日推荐