5.Java基础之面向对象二

1.static、final、多态

1.1 主要内容

  1. static关键字
  2. final关键字
  3. 三大特征——多态

1.2 目标

  1. 能够理解static关键字
  2. 能够写出静态代码块的格式
  3. 描述final修饰的类的特点
  4. 描述final修饰的方法的特点
  5. 能够说出使用多态的前提条件
  6. 理解多态的向上转型
  7. 理解多态的向下转型

2.static

2.1 概述

static关键字的使用,可以用来修饰成员变量、成员方法、代码块,被修饰的成员是属于类的,而不是单单是属于某个对象的。也就是说,既然属于类,就可以不靠创建对象来调用了。属于我们方法区的内容

2.2 定义和使用格式

2.2.1 类变量

static修饰成员变量时,该变量称为类变量。该类的每个对象都共享同一个类变量的值。任何对象都可以更改
该类变量的值,但也可以在不创建该类的对象的情况下对类变量进行操作。

类变量:使用 static关键字修饰的成员变量

定义格式:

static 数据类型 变量名;

举例:

static int count;

需求:模拟实现选民投票过程:一群选民进行投票,每个选民只允许投一次票,并且当投票总数达到100时,就停止投票

2.2.2 静态方法

static修饰成员方法时,该方法称为类方法 。静态方法在声明中有 static ,建议使用类名来调用,而不需要 创建类的对象。调用方式非常简单。

类方法:使用 static关键字修饰的成员方法,习惯称为静态方法

定义格式:

修饰符 static 返回值类型 方法名 (参数列表){
// 执行语句
}

举例:在Voter类中定义静态方法

//打印投票结果
public static void printResult(){
    System.out.println("选民投票总数为:"+count);
}

静态方法调用的注意事项:

  1. 静态方法可以直接访问类变量和静态方法。

  2. 静态方法不能直接访问普通成员变量或成员方法。反之,成员方法可以直接访问类变量或静态方法。

  3. 静态方法中,不能使用this关键字。

    Tips:静态方法只能访问静态成员。

2.2.3 调用格式

static修饰的成员可以并且建议通过类名直接访问。虽然也可以通过对象名访问静态成员,原因即多个对象均属于一个类,共享使用同一个静态成员,但是不建议,会出现警告信息。

格式:

// 访问类变量
类名.类变量名;
// 调用静态方法
类名.静态方法名(参数)

2.3 静态原理图解

static修饰的内容:

  1. 是随着类的加载而加载的,且只加载一次。
  2. 存储于一块固定的内存区域(静态区),所以,可以直接被类名调用。
  3. 它优先于对象存在,所以,可以被所有对象共享。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mWN3lgAN-1618382469610)(assets/1572691681675.png)]

2.4 静态代码块

静态代码块:定义在成员位置,使用static修饰的代码块{ }。

  1. 位置:类中方法外。

  2. 执行:随着类的加载而执行且执行一次,优先于main方法和构造方法的执行。

格式:

public class ClassName{
    static {
    // 执行语句
    }
}

作用:给类变量进行初始化赋值。

小贴士: static 关键字,可以修饰变量、方法和代码块。在使用的过程中,其主要目的还是想在不创建对象的情况 下,去调用方法。下面将介绍两个工具类,来体现static 方法的便利。

2.5 小结

static变量的作用:(1)能被类的所有实例共享,可作为实例之间进行交流的共享数据(2)如果类的所有实例都包含一个相同的常量属性,可把这个属性定义为静态常量类型,从而节省内存空间,比如Student,大家国籍一样,此时我们为可以定义为静态变量,只在方法区中维护一份即可;而不需要每个对象都在堆中维护一个值

通常我们static结合final一起使用,定义全局常量【直接通过类名进行调用,定义系统参数】

public class SystemConstant {
    final static String SESSION_USER="user";
}

2.6 需要掌握

  1. 可以修饰哪些成员?

    成员变量、方法、静态代码块

  2. 适用场景:

    针对变量:通常我们static结合final一起使用,定义全局常量【直接通过类名进行调用,定义系统参数】

    针对方法:一般封装工具类,比如Arrays

  3. 静态代码块 vs 普通代码块 vs 构造方法 vs 父类构造

    1. 静态先行(父类静态先行),行1次
    2. 普通随后,然后构造,造几次对象,执行几次

3.final

3.1 概述

学习了继承后,我们知道,子类可以在父类的基础上改写父类内容,比如,方法重写。那么我们能不能随意的继承 API中提供的类,改写其内容呢?显然这是不合适的。为了避免这种随意改写的情况,Java提供了 final 关键字, 用于修饰不可改变内容。

final: 不可改变。可以用于修饰类、方法和变量。

:被修饰的类,不能被继承。 比如我们的StringMath【面试要能说出来】

方法:被修饰的方法,不能被重写

变量:被修饰的变量,不能被重新赋值,常量

3.2 使用方式

3.2.1 修饰类

final class 类名 {
}

查询API发现像public final class Stringpublic final class Mathpublic final class Scanner 等,很多我们学习过的类,都是被final修饰的,目的就是供我们使用,而不让我们所以改变其内容。

3.2.2 修饰方法

修饰符 final 返回值类型 方法名(参数列表){
	//方法体
}

重写被final修饰的方法,编译时就会报错。

3.2.3 修饰变量

  1. 局部变量——基本类型

    基本类型的局部变量,被final修饰后,只能赋值一次,不能再更改。

    思考,如下两种写法,哪种可以通过编译?

    写法1:

    final int c = 0;
    for (int i = 0; i < 10; i++) {
        c = i;
        System.out.println(c);
    }	//编译通过? no
    

    写法2:

    for (int i = 0; i < 10; i++) {
        final int c = i;	//没进一次循环,都是一个新的初始值,跟前面的那个二维数字求每个班的sum一样的
        System.out.println(c);
        c = 10;  // 还行不?
    }	//编译通过? yes
    
  2. 局部变量——引用类型

    引用类型的局部变量,被final修饰后,只能指向一个对象,地址不能再更改。但是不影响对象内部的成员变量值的修改

  3. 成员变量

    成员变量涉及到初始化的问题,初始化方式有两种,只能二选一:

    显示初始化 :

    public class User {
        final String USERNAME = "张三";
        private int age;
    }
    

    构造方法初始化

    public class User {
        final String USERNAME ;
        private int age;
        public User(String username, int age) {
            this.USERNAME = username;
            this.age = age;
        }
    }
    

4.多态

4.1 概述

多态是继封装、继承之后,面向对象的第三大特性。

4.1.1 为什么需要多态

如下主人类中给宠物看病的方法,如果又需要给XXX宠物看病,怎么办?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cEyEwv9g-1618382469624)(assets/1572693604915.png)]

提供上面的方法发现如下规律:

  1. 多个cure方法构成方法重载
  2. 仅仅是方法的参数不同,同时这些Dog、Penguin都是Pet的子类
  3. 如果再有Cat、Duck。。。在Master类中,都要cure()方法

对于代码的扩展性和可维护性较差,使用多态去解决。即参数传入父类

4.1.2 什么是多态

多态:面向对象的三大特征

生活中的多态:同一种事物/对象在不同场景下表现出来的形态/状态/行为

比如张三:

​ 在教室:学生

​ 路上:路人甲

理解:角色扮演 子类扮演父类[伪装]

在程序中:

父类引用指向子类对象

父类类型 父类引用 = new 子类类型();

若子类没有重写父类的方法,则调用的是父类继承过来的方法;重写的话,调用重写之后的方法 ,这种调用方式又称虚方法调用 —>了解

Java引用变量有两个类型: 编译时类型运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。简称: 编译时,看左边;运行时,看右边。
若编译时类型和运行时类型不一致 , 就出现了对象的多态性 (Polymorphism)
多态情况下
“ 看左边 ”
: 看的是父类的引用(父类中不具备子类特有的方法)
“ 看右边 ” : 看的是子类的对象(实际运行的是子类重写父类的方法)

4.1.3 前提【重点】

  1. 继承或者实现【二选一】
  2. 方法的重写【意义体现:不重写,无意义】
  3. 父类引用指向子类对象【格式体现】

4.2 多态的体现

父类类型 变量名 = new 子类对象;
变量名.方法名();

父类类型:指子类对象继承的父类类型,或者实现的父接口类型。

当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,执行的是子类重写 后方法。

4.3 多态的好处

  1. 提高代码的扩展性
  2. 提高的可维护性

4.4 引用类型转换

多态的转型分为向上转型与向下转型两种:

4.4.1 向上转型

类似:自动类型转换

父类类型 变量名/父类引用  = new 子类类型();

4.4.2 向下转型

类似:前面的强制类型转换

子类类型 引用名称 = (子类类型)父类引用;
Dog dog = (Dog)pet;
dog.keepDoor();

4.4.3 为什么要转型

对于向上转型【多态】有一个副作用:子类特有的方法无法调用,此时要想要调用的话,需要将父类引用转为真实的子类类型。

4.4.4 转型的异常

在向下转型的过程中,可能会遇到ClassCastException异常,为了避免此异常,通常需要进行类型检测/判断,使用instanceof

if(pet2 instanceof  Dog) {
    Dog dog2 = (Dog)pet2;
    dog2.keepDoor();
}else {
    Penguin penguin = (Penguin)pet2;
    penguin.swimming();
}

4.5 多态的使用场景

  1. 父类作为形参,调用的时候使用父类引用,即子类
  2. 父类作为返回值类型

需求:买家给出需求,主人售卖你需要的宠物

public void feed(Pet pet) {
    //TODO 根据健康值去喂食
    pet.eat();
}
//父类作为返回值
public Pet sell(String petType){
    //new Dog() 没有赋值给某个一引用变量,这种称为匿名对象
    //该对象不打算重复使用,此时可以使用匿名对象
    //2种使用地方
    //1.作为实参传入
    //2.作为返回值返回   
    if(petType.equals("dog")){
        System.out.println("你太狠了....");            
        return new Dog();
    } else if("penguin".equals(petType)){
        System.out.println("bye.....");
        return new Penguin();
    } else {
        System.out.println("没有你要买的宠物类型..");
        return null;
    }
}

//另外一种写法
public Pet sell(String petType){
    //new Dog() 没有赋值给某个一引用变量,这种匿名对象
    //该对象不打算重复使用,此时可以使用匿名对象
    //2种使用地方
    //1.作为实参传入
    //2.作为返回值返回
    Pet pet = null;
    if(petType.equals("dog")){
        System.out.println("你太狠了....");
        pet = new Dog();
        // return pet;
        // return new Dog();
    } else if("penguin".equals(petType)){
        System.out.println("bye.....");
        pet = new Penguin();
        //  return new Penguin();
    } else {
        System.out.println("没有你要买的宠物类型..");
        // return null;
    }
    return pet;
}

注意:我们在使用字符串的时候,尽量使用已知的值去调用方法,避免出现空指针异常:NullPointerException

5.匿名对象

所谓的匿名对象,就是不给这个对象起名称。一般在如下场景中使用:

  1. 不想重复使用某个对象,仅仅是想在传递实参的时候使用:master.cure(new Penguin())
  2. 不想重复使用某个对象,将其赋值给数组元素:cc[0] = new CommonUser("张三"+i,100)或者就想调用一下类中的方法new Voter("zs").vote();

6.练习

6.1 完成汽车租赁系统

6.1.1 需求

在这里插入图片描述

用户模块:

功能1:登录

功能2:注册

功能3:忘记密码

功能4:修改密码 ----》必须登录才能执行

功能5:退出 ----》必须登录才能执行

实现说明:

​ User的属性说明:String userId 用户id、String userName 用户名、String pwd 密码、String phoneNo 手机号、String address 地址【有余力的同学可以尝试使用 Address[] addresses】

​ service包:UserService类 中 预先在User[] users中设置几个登录账号

​ 方法的提示不一定完全对:

​ User login(String userName,String pwd)

​ void regist(User user)

​ String forgetPwd(String userName):根据用户名查找,返回密码即可,简化处理

​ void updatePwd(String oldPwd,String newPwd):该方法,应该是登录成功之后,才能执行的方法

​ void logout():退出

具体需求如下:

在这里插入图片描述

在这里插入图片描述

6.1.2 实现步骤

  1. 创建汽车父类 Automobile、两个子类Car 、Bus

参考:card、brand、dayRentPrice、getTotalMoney(int days)

计算租金的逻辑:

比如1-10,所有租金打9折,10-20,8折,>30七折

  1. MotoOperation业务类

(1) 初始化一批数据【车库里面的车】

(2) 租车方法 service(brand,type,seatCount) Automobile

补充说明:①找轿车:根据brand,type去找;②找客车:brand、seatCount

​ ③找卡车:根据brand和tonnage

  1. RentMgrSys

下面效果的输入功能,调用MotoOperation里面的service方法

难点:

  1. Automobile使用什么存?Automobile[] 多态对象数组

  2. 在找车的方法中,你要进行类型判断 instanceof

  3. 我们输入都是数字,数字要转为对应的字符串…

初始化数据参考:

public Automobile motos[] = new Automobile[8];  // 多态数组

public void init(){
    motos[0] = new Car("京NY28588", "宝马", "X6",800);
    motos[1] = new Car("京CNY3284", "宝马", "550i",600);
    motos[2] = new Car("京NT37465", "别克", "林荫大道",300);
    motos[3] = new Car("京NT96968", "别克", "GL8",600);
    motos[4] = new Bus("京6566754", "金杯", 16,800);
    motos[5] = new Bus("京8696997", "金龙", 16,800);
    motos[6] = new Bus("京9696996", "金杯", 34,1500);
    motos[7] = new Bus("京8696998", "金龙", 34,1500);
    //补上几辆卡车
}

效果图:

在这里插入图片描述
在这里插入图片描述

6.1.3 添加货车类

加一个货车类,加一个属性tonnage吨位,品牌:东风、西风;吨位:20 60

6.2 多态练习

打印商品价格:跟主人卖宠物一样

需求说明:自定义类和方法,使用父类作为返回值实现打印不同类型商品价格功能

  1. 父类:Goods(商品类),在中定义一个printPrice()的抽象方法
  2. 子类:TVs(电视类)、Foods(食品类),重写printPrice(),直接打印一句话
  3. 买家类:Goods buy(String goodsType)
  4. 测试类:商品类型应该从键盘输入,返回一个商品,调用printPrice()

扩展:根据商品的二级分类打印价格printPrice(String category2),Goods buy(黑白电视),category1和category2事先保存在数组中,通过随机数取值。尝试使用二维数组去存储

String[][] categorys = new String[2][] 比如categorys[0][1]代表TV下面的黑白

{{"黑白","彩色"},{"坚果","猕猴桃","黄焖鸡"}}

6.3 宠物商城–自己扩展

需求:使用多态数组实现宠物的管理

用户模块:

功能1:登录

功能2:注册

功能3:忘记密码

功能4:修改密码

功能5:退出

实现说明:

​ User的属性说明:int userId 用户id、String userName 用户名、String pwd 密码、String phoneNo 手机号、String address 地址【有余力的同学可以尝试使用 Address[] addresses】

​ service包:UserService类 中 预先在User[] users中设置几个登录账号

​ User login(String userName,String pwd)

​ void regist(User user)

​ String forgetPwd(String userName):根据用户名查找,返回密码即可,简化处理

​ void updatePwd(String oldPwd,String newPwd):该方法,应该是登录成功之后,才能执行的方法

​ void logout():退出

购物模块:

功能1:宠物列表

功能2:购买宠物

功能3:购物车

功能4:搜索宠物

功能5:查看订单

​ Pet的属性说明:String name 名字、int health 健康值、int love 亲密度、double money 价格、int count 库存

​ PetService:Pet[] pet;

​ ①void init():初始化一批数据

​ ②void showPets():宠物列表

6.4 多态的基本使用

边际效应 —》管理学里面的

练习1:使用多态实现为宠物喂食

需求说明:宠物饿了,主人需要为宠物喂食,使用多态实现该过程

  1. 不同宠物吃的东西不一样
  2. 不同宠物吃完东西后恢复健康值不一样(狗狗,健康值+3 ;企鹅,健康值+5)
  3. 健康值达到100时,不需要继续喂食

练习2:狗具有特有的接飞盘方法,企鹅具有特有的南极游泳方法。请编写测试类分别调用各种具体宠物的特有方法

  1. 使用向下转型
  2. 使用instanceof判断宠物类型

练习3:使用多态优化前面的宠物店【不强求】

封装一个方法:根据输入需求,返回对应的宠物类型,最后在测试方法中输入的测试,并调用toString()方法,输出宠物的自白

Pet getPetByConditions(5个参数) 应该根据哪个参数去创建对应的子类对象???

扩展:5个参数比较多,能否将5个参数写到1个类中去

Pet getPetByConditions(SearchConditions conditions)

SearchConditions 这个类是自己写4个成员变量呢?还是有别的方案呢?继承Pet类,只需要写2个参数? 还有没有别的写法?直接利用已有的类

7.总结

7.1 继承

带着问题去复习

/**
 * 继承的前提:子父类要满足 is a 关系
 * 目的:减少重复代码,提供代码的复用性【重复使用】
 * 父类的哪些成员,子类可以继承?
 * 1.构造方法能否继承?
 * 2.父类私有的能否?
 * 3.公共的能否?
 * 4.子类跟父类的方法名一样?我们的子类对象调用的是哪个呢?这个方法名一样,我们又称之为什么呢?
 * 5.super? vs this
 * 6.子类在调用构造方法的时候,需不需要走父类的构造方法?怎么去调用父类的构造?
 * 7.支持多继承? 几个亲爹??
 * 8.支持多层继承吗?A B extends A  C extends B  ----》B是C的直接父类,A看成是间接父类
 * 9.继承中,子类在构造对象时,如何调用父类的构造方法???
 */

/**
 * 重写的要求:
 * 1.前提:???
 * 2.方法名是否要一致?参数列表是否要一致?
 * 3.返回值有啥要求?     ----》容易答漏掉子类类型
 * 4.访问修饰符有啥要求?
 * 5.预留:讲异常的时候再说
 重写之后,子类调用的是重写之后的方法还是继承过来的方法??
 */

7.2 抽象类

/**
 * 快捷键:ctrl + i
 * 在父类中定义了一个eat方法
 * 1.子类并没有强制要求需要重写吧???  强制重写
 * 2.每个宠物吃什么,父类知道吗? 父类不知道,只声明有这么一个方法,具体方法体什么,由子类决定
 * ---需要声明为抽象方法
 * 抽象方法的特点:
     * 1.只做声明,不做实现,即没有方法体,其他跟普通方法没区别  修饰符 返回值 方法名(形参列表) {}
     * 2.语法要求,抽象方法需要在抽象类中
 * 3.抽象类有什么特点???【高频面试 vs 接口】  vs 普通类
 * 3.1 抽象类中可以有什么成员? 普通类可以有什么,抽象类就可以有什么,还比普通类多了一个abstract 方法
 * 3.2 应不应该有构造?可以有构造吗?  ok  ---》子类造对象,需要走父类的构造方法
 * 3.3 能否实例化?no
 *  ①语义上分析:反推 ---》假设可以new,必然可以调用抽象方法,那么就应该执行方法体,有方法体吗 ???
 *  ②语法上分析:语法不允许
 * 3.4 应该场景:是一个类,有些方法是抽象方法,所以,一般用作继承【设计模式-模板方法】
 * 4.子类是否可以不重写抽象方法?
 
 抽象类之模版方法设计模式 ---- 第一个设计模式
 */

7.3 static

/**
 * static
 * 1.可以修饰什么? 变量 、方法、代码块
 * 2.如何去调用,如何获取属性、调用方法?
 *  类变量:只能如果类名调用
 *  静态方法:只能调用静态(变量、方法)
 *  普通方法:可以调用静态,也可以调用非静态
 * 3.修饰代码块 vs 普通代码块 vs 构造 ---》看执行顺序   静态先行(1次);普通随后,再构造【构造几个,就执行几次】
 * 4.static为什么会是上面的结果?? 回归到JVM的运行时数据区  ---》方法区  ---》静态随着类的加载而加载,只加载一次
 * @author azzhu
 * @create 2020-06-06 14:28:28
 */

7.4 多态

  1. 好处:维护性 + 扩展性
  2. 是什么:父类 变量名= new 子类()
  3. 应用场景:
    1. 作为形参
    2. 作为返回值
  4. 多态数组 ----》对象数组(在我们前面对象数组的基础之上,引入多态)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值