1.static、final、多态
1.1 主要内容
- static关键字
- final关键字
- 三大特征——多态
1.2 目标
- 能够理解static关键字
- 能够写出静态代码块的格式
- 描述final修饰的类的特点
- 描述final修饰的方法的特点
- 能够说出使用多态的前提条件
- 理解多态的向上转型
- 理解多态的向下转型
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);
}
静态方法调用的注意事项:
-
静态方法可以直接访问类变量和静态方法。
-
静态方法不能直接访问普通成员变量或成员方法。反之,成员方法可以直接访问类变量或静态方法。
-
静态方法中,不能使用this关键字。
Tips:静态方法只能访问静态成员。
2.2.3 调用格式
被static
修饰的成员可以并且建议通过类名直接访问。虽然也可以通过对象名访问静态成员,原因即多个对象均属于一个类,共享使用同一个静态成员,但是不建议,会出现警告信息。
格式:
// 访问类变量
类名.类变量名;
// 调用静态方法
类名.静态方法名(参数);
2.3 静态原理图解
static
修饰的内容:
- 是随着类的加载而加载的,且只加载一次。
- 存储于一块固定的内存区域(静态区),所以,可以直接被类名调用。
- 它优先于对象存在,所以,可以被所有对象共享。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mWN3lgAN-1618382469610)(assets/1572691681675.png)]
2.4 静态代码块
静态代码块:定义在成员位置,使用static修饰的代码块{ }。
-
位置:类中方法外。
-
执行:随着类的加载而执行且执行一次,优先于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 需要掌握
-
可以修饰哪些成员?
成员变量、方法、静态代码块
-
适用场景:
针对变量:通常我们static结合final一起使用,定义全局常量【直接通过类名进行调用,定义系统参数】
针对方法:一般封装工具类,比如Arrays
-
静态代码块 vs 普通代码块 vs 构造方法 vs 父类构造
- 静态先行(父类静态先行),行1次
- 普通随后,然后构造,造几次对象,执行几次
3.final
3.1 概述
学习了继承后,我们知道,子类可以在父类的基础上改写父类内容,比如,方法重写。那么我们能不能随意的继承 API中提供的类,改写其内容呢?显然这是不合适的。为了避免这种随意改写的情况,Java提供了 final 关键字, 用于修饰不可改变内容。
final: 不可改变。可以用于修饰类、方法和变量。
类:被修饰的类,不能被继承。 比如我们的String
、Math
【面试要能说出来】
方法:被修饰的方法,不能被重写。
变量:被修饰的变量,不能被重新赋值,常量。
3.2 使用方式
3.2.1 修饰类
final class 类名 {
}
查询API发现像public final class String
、public final class Math
、 public final class Scanner
等,很多我们学习过的类,都是被final修饰的,目的就是供我们使用,而不让我们所以改变其内容。
3.2.2 修饰方法
修饰符 final 返回值类型 方法名(参数列表){
//方法体
}
重写被final
修饰的方法,编译时就会报错。
3.2.3 修饰变量
-
局部变量——基本类型
基本类型的局部变量,被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
-
局部变量——引用类型
引用类型的局部变量,被final修饰后,只能指向一个对象,地址不能再更改。但是不影响对象内部的成员变量值的修改
-
成员变量
成员变量涉及到初始化的问题,初始化方式有两种,只能二选一:
显示初始化 :
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)]
提供上面的方法发现如下规律:
- 多个cure方法构成方法重载
- 仅仅是方法的参数不同,同时这些Dog、Penguin都是Pet的子类
- 如果再有Cat、Duck。。。在Master类中,都要cure()方法
对于代码的扩展性和可维护性较差,使用多态去解决。即参数传入父类
4.1.2 什么是多态
多态:面向对象的三大特征
生活中的多态:同一种事物/对象在不同场景下表现出来的形态/状态/行为
比如张三:
在教室:学生
路上:路人甲
理解:角色扮演 子类扮演父类[伪装]
在程序中:
父类引用指向子类对象
父类类型 父类引用 = new 子类类型();
若子类没有重写父类的方法,则调用的是父类继承过来的方法;重写的话,调用重写之后的方法 ,这种调用方式又称虚方法调用
—>了解
Java引用变量有两个类型: 编译时类型和 运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。简称: 编译时,看左边;运行时,看右边。
若编译时类型和运行时类型不一致 , 就出现了对象的多态性 (Polymorphism)
多态情况下
“ 看左边 ” : 看的是父类的引用(父类中不具备子类特有的方法)
“ 看右边 ” : 看的是子类的对象(实际运行的是子类重写父类的方法)
4.1.3 前提【重点】
- 继承或者实现【二选一】
- 方法的重写【意义体现:不重写,无意义】
- 父类引用指向子类对象【格式体现】
4.2 多态的体现
父类类型 变量名 = new 子类对象;
变量名.方法名();
父类类型:指子类对象继承的父类类型,或者实现的父接口类型。
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,执行的是子类重写 后方法。
4.3 多态的好处
- 提高代码的扩展性
- 提高的可维护性
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 多态的使用场景
- 父类作为形参,调用的时候使用父类引用,即子类
- 父类作为返回值类型
需求:买家给出需求,主人售卖你需要的宠物
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.匿名对象
所谓的匿名对象,就是不给这个对象起名称。一般在如下场景中使用:
- 不想重复使用某个对象,仅仅是想在传递实参的时候使用:
master.cure(new Penguin())
- 不想重复使用某个对象,将其赋值给数组元素:
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 实现步骤
- 创建汽车父类 Automobile、两个子类Car 、Bus
参考:card、brand、dayRentPrice、getTotalMoney(int days)
计算租金的逻辑:
比如1-10,所有租金打9折,10-20,8折,>30七折
- MotoOperation业务类
(1) 初始化一批数据【车库里面的车】
(2) 租车方法 service(brand,type,seatCount) Automobile
补充说明:①找轿车:根据brand,type去找;②找客车:brand、seatCount
③找卡车:根据brand和tonnage
- RentMgrSys
下面效果的输入功能,调用MotoOperation里面的service方法
难点:
-
Automobile使用什么存?Automobile[] 多态对象数组
-
在找车的方法中,你要进行类型判断
instanceof
-
我们输入都是数字,数字要转为对应的字符串…
初始化数据参考:
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 多态练习
打印商品价格:跟主人卖宠物一样
需求说明:自定义类和方法,使用父类作为返回值实现打印不同类型商品价格功能
- 父类:Goods(商品类),在中定义一个printPrice()的抽象方法
- 子类:TVs(电视类)、Foods(食品类),重写printPrice(),直接打印一句话
- 买家类:Goods buy(String goodsType)
- 测试类:商品类型应该从键盘输入,返回一个商品,调用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:使用多态实现为宠物喂食
需求说明:宠物饿了,主人需要为宠物喂食,使用多态实现该过程
- 不同宠物吃的东西不一样
- 不同宠物吃完东西后恢复健康值不一样(狗狗,健康值+3 ;企鹅,健康值+5)
- 健康值达到100时,不需要继续喂食
练习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 多态
- 好处:维护性 + 扩展性
- 是什么:
父类 变量名= new 子类()
- 应用场景:
- 作为形参
- 作为返回值
- 多态数组 ----》对象数组(在我们前面对象数组的基础之上,引入多态)