面向对象-多态
1. 课程介绍
Ø 1.super(掌握)
Ø 2.多态(掌握)
Ø 3.引用类型的转换(掌握)
Ø 4.final(掌握)
Ø 5.单例模式[掌握:面试题]
2. super(简单)
2.1 Super引入前示例
1. 假设关于继承的理解(示例):
1) 目前理解的子类可以继承父类哪些东西 : 非私有的字段和方法【主要是从访问权限的角度,局限性】
2) 看示例:做学生管理系统,需要登陆的功能: 涉及 ,学生账号,老师账号
都是账号有共同属性 account password 但是学生账号和老师账号都有独有的属性行为
3) 类的设计
a) 设计一个用户类User : 包含: 用户名 密码 字段
b) 让学生类和老师类继承 User用户类
4) 规范写法:User类中的用户名及密码字段应该私有化提供get set方法
5) 问题:父类User中的字段私有化,子类学生类和老师类能够继承到用户名和密码吗?
a. 从目前理解(主要是从访问权限来看),子类是继承不到私有成员的;
b. 是否应该在子类中再定义用户名和密码字段呢? 不应该!
c. 结论:子类对象中应该没有用户字段及密码字段可以存值
6) 就按照上面的写法,验证子类中是否具有父类中的属性这样的一个特点
7) 类设计: 账号类User,学生账号类StudentUser,老师账号类TeacherUser
class User{//账号类
private String account;
private String password;
public void setAccount(String account){
this.account = account;
}
public String getAccount(){
return this.account;
}
public void setPassword(String password){
this.password = password;
}
public String getPassword(){
return this.password;
}
}
class StudentUser extends User{//学生账号类 extends 账号类
}
class TeacherUser extends User{//老师账号类 extends 账号类
}
8) 测试类
//测试类
class Test {
public static void main(String[] args) {
StudentUser stu = new StudentUser();
stu.setAccount("嚣张");
stu.setPassword("123456");
System.out.println("账号:"+stu.getAccount()+", 密码:"+stu.getPassword());
}
}
---------- Java ----------
账号:嚣张, 密码:123456
输入完成 (耗时0秒) - 正常终止
9) 分析:发现StudentUser类,虽然没有继承到字段,但是还是在通过set get方法正常的存值取值,感觉还是用到User类中的account password字段的,为什么且听super分析
2.2 回顾this
1. this的概念:this表示当前对象,持有当前对象的地址
2. 判断this当前对象是谁?
a) 官方:this所在的函数正在被谁调用,this就指代谁
b) 民间:this当前被哪个对象持有this就指代谁
3. this的使用场景
a) 通过this访问当前对象中的成员(字段,方法);区分成员变量与局部变量的二义性
b) 把this(当前对象)看成是一个数据, 就可以作为值返回,作为参数传递...
c) 使用场景-2 : 本类中构造方法的之间的方法体第一句相互调用;
i. this(); 调用本类中无参数的构造方法;
ii. this(123); 调用本类中具有一个int参数的构造方法;
2.3 什么是super
1. 什么是Super代码分析示例
//测试类
class Test {
public static void main(String[] args) {
StudentUser stu = new StudentUser();
stu.eat("大花花");
}
}
class User{
String name = "小花花";
}
class StudentUser extends User{
String name = "中花花";
void eat(String name){
System.out.println("name = "+name);
System.out.println("this.name = "+this.name);
System.out.println("super.name = "+super.name);
}
}
2. super的概念:在子类中表示父类的对象
3. 区别this:super不持有父类对象地址
//子类方法测试打印super
System.out.println(super);
Test.java:17: 错误: 需要';'
System.out.println(super);
^
Test.java:19: 错误: 解析时已到达文件结尾
}
^
4 个错误
输出完成 (耗时 0 秒) - 正常终止
2.4 super的使用场景
1. 前提:super访问父类的成员,都必须是在有访问权限的条件之下;
2. super访问父类对象中的字段 及 普通方法;
class User{//父类User
String name = "小花花";
void login(){
System.out.println("User login");
}
}
class StudentUser extends User{//子类 StudentUser
String name = "中花花";
void eat(String name){
System.out.println("super.name = "+super.name);//super访问父类字段
super.login();//super访问父类普通方法
System.out.println("name = "+name);
System.out.println("this.name = "+this.name);
}
}
class Test {//有主方法的测试类Test
public static void main(String[] args) {
StudentUser stu = new StudentUser();
stu.eat("大花花");
}
}
---------- Java ------------------------------------------------------
super.name = 小花花
User login
name = 大花花
this.name = 中花花
输出完成 (耗时 0 秒) - 正常终止
3. 在子类的构造方法体第一句访问父类的构造方法; 最常用的!!!!!!!!!!!!
1) super(); 调用父类无参数的构造方法;
2) super(123); 调用父类中具有一个int参数的构造方法;
2.5 super的特殊使用场景及分析
1. 特殊使用场景 :在子类的构造方法第一句,
a) 如果没有显示的写出对于父类构造方法的调用,那么会隐式的调用父类的无参数的构造方法!
b) 如果有显示的写出对于父类构造方法的调用,那么会隐式的调用父类的无参数的构造方法,就不存在了!
2. 特殊使用场景结论:子类的构造方法中一定会调用到父类的构造方法
3. 特殊使用场景测试
class User{
User(){
System.out.println("隐式无参父类User");
}
User(String name){
System.out.println("显示有参式父类User");
}
}
class StudentUser extends User{
StudentUser(){//隐式调用父类的无参数的构造方法
System.out.println("隐式子类StudentUser");
}
StudentUser(String name){//显示的写出隐式的调用就存在了
super(name);
System.out.println("显示子类有参StudentUser");
}
}
//测试类
class Test {
public static void main(String[] args) {
System.out.println("----------隐式父类无参数构造方法调用---------");
new StudentUser();
System.out.println("----------显式父类有参数构造方法调用----------");
new StudentUser("小王八");
}
}
4. super特殊用途分析:无论如何,子类的构造方法一定会存在对于父类构造方法的调用【难点:重在理解】
a) 私有化字段在子类getset可以用到的原因
b) javaBean为什么一定要提供一个无参数的构造方法
c) 为什么子类可以继承父类的东西寄特性
2.6 super小结
1. super概念
2. super的作用
3. super的特殊用途
4. super的特殊用途分析
3. 多态(要点,难点)
3.1 多态的引入
1. 概念引入:重点代码思想
2. 简单理解一种事物的多种形态
3. 示例:
a) 今天晚上请大家吃大餐 : 思想上的大餐 , 凉开水
b) 同学们准备买一辆车: 宾利 兰博基尼 柯塞尼格,老谭二八杠..
c) 同学们要上天 : 坐飞机,
4. java代码中的体现
1) 一个动物就是一个蛇
2) 一个动物就是一个猪
3) 一个动物就是一个人
4) 一个人就是一个动物:Animal aml = new Person();//一个动物变量中,可能存放子类对象的多可能
5) 一个猪就是一个动物:Animal aml = new Pig();
6) 一个蛇就是一个动物:Animal aml = new Snak();
7) 一个人就是一个猪
8) 一个蛇就是一个人
9) 一个猪就是一个蛇
3.2 什么是多态
1.一个人就是一个动物:
(1) Animal aml = new Person();//一个动物变量中,可能存放子类对象的多种可能
2.理解:使用aml:编译是看的是Animal类型,运行时看的是实际存放的对象[真实类型]
3.官方概念:编译时跟运行时类型不一致就产生了多态
4.民间理解:父类类型的变量,存放子类类型的对象,可能存放子类类型的对象有多种可能
5.多态存在的前提:必须有继承关系
3.3 多态方法调用编译运行过程[重点]
class Animal{
void eat(){
System.out.println(“食物........”);
}
}
class Person extends Animal{
}
//----------------------------------测试类-----------------------
class Test {
public static void main(String[] args) {
Animal aml = new Person();
aml.eat();
}
}
1. 上面两句代码的编译,运行过程:
1) 编译时 :
a) aml 编译时,看的是父类类型,会在父类类型中eat方法
b) 如果没有找到,会继续向上找[aml编译时父类类型]
i. 找到:编译通过
ii. 找不到:编译报错
iii. 注意:是不会向下找的[aml编译时子类类型]
2) 运行时 :
a) 先到运行时类型[Person]中找eat方法,
i. 如果找到:就执行,
ii. 没找到:向上到父类中找并执行
2. 思考 : 有没有可能编译通过了,而运行找不到方法... 不可能!
3.4 编译时与运行时的几种情况分析
1. 父类中有一个方法,子类覆写了
2. 父类中有一个方法,子类没有
3. 父类中没有,子类有一个方法
4. 父类子类都没有
----------- 上面都是实例方法,下面来一个静态方法---------------------------------
5. 静态方法
6. 字段没有覆写一说
分析:
1. 实际开发中一般不会再子类中定义一个和父类同名的字段,
2. 如果是有这样的情况存在,
1) 如果编译时类型父类的类型,取值是父类里面字段的值;
2) 如果编译时类型子类的类型,取值是子类里面字段的值;
3.5 多态方法调用以及参数传递应用示例[重点]
class Dog{//父类类型Dog
void eat(){
System.out.println("吃食物");
}
}
class DDog extends Dog{//子类类型DDog
void eat(){
System.out.println("哈根达斯");
}
}
class XDog extends Dog{//子类类型XDog
void eat(){
System.out.println("吃牛排喝红酒");
}
}
class Person{//人类:定义喂狗方法
void feedDog(Dog dog){
dog.eat();
}
}
//------------------------------测试类-----------------------------------
class Test {
public static void main(String[] args) {
Dog ddog = new DDog();
XDog xdog = new XDog();
Person pson = new Person();
pson.feedDog(ddog);
pson.feedDog(xdog);
}
}
3.6 多态体现的几种情况
1. 如上代码多态的体现的本质:都是父类类型的变量存放子类类型的对象
2. Dog dog = new XDog();//核心本质
3. 方法参数传递:方法形参父类类型,允许传子类类型对象
i. Dog dog = new XDog();
ii. XDog xdog = new XDog();
iii. void feedDog(Dog dog){ }//此方法运行传递上面两个参数dog 或者 xdog : 本质传递的都是子类对象
4. 方法返回值
Dog getDog(){//此方法返回值类型为父类类型Dog
return new XDog();//允许返回子类类型
}
思考:方法内部返回值的类型一定是,方法返回值位置指定的类型么?
5. 多态的好处:屏蔽了不同子类之间实现的差异
6. 加强面向对象编程思想:
a) 此处体现java语言设计是想,希望将现实生活中的对象与对象之间的关系在计算机系统得以体现
3.7 多态小结
1. 多态的概念
2. 编译时运行时过程
3. 多态的用途【多态的方法参数传递】
(1) 结合面向对象编程思想及执行流程分析
4. 多态在代码中如何体现的
5. 多态的好处
6. 加强面向对象编程思想的认识
4. 引用类型转换
4.1 引用类型转换引入
class Cat{
void eat(){}
}
class TomCat extends Cat{
void say(){}
}
class CoffeeCat extends Cat{
void drink(){}
}
//测试类
class Test {
public static void main(String[] args) {
Cat cat = new Tomcat();
cat.drink();//编译报错
}
}
1.分析:类设计:提取公共属性,子类存在特有属性
2.示例:猫[Cat]都会吃,但是汤姆猫[TomCat]会说话,咖啡猫[CoffeeCat]会喝咖啡
3.多态使用存在可能:
a) Cat cat = new TomCat();
b) cat.say();//编译时报错:say动态属性在,Cat中不存在
4.2 为什么需要引用数据类型转换
1. 如上示例:明知cat里面存放的是Tomcat,但是因为多态编译不能通过,不能使用,需要转换成Tomcat真实类型,即可以使用
4.3引用数据类型转换的两种情况
1. 明确:数据类型转换存在两种,情况,大转小:小转大
1) 子类类型转父类类型:小转大
Cat cat = new TomCat();
double d = 1;
2) 父类类型转子类类型:大转小
TomCat tc =(TomCat)cat;
2. 在引用数据类型中:父类是较大的数据类型,子类是较小的数据类型
4.4 引用数据类型转换的注意事项
class Cat{
void eat(){}
}
class TomCat extends Cat{
void say(){}
}
class CoffeeCat extends Cat{
void drink(){}
}
//测试类
class Test {
public static void main(String[] args) {
Cat cat = new Tomcat();
CoffeeCat cc = (CoffeeCat)cat;
cc.drink();
}
}
1. 如上代码示例分析;
a) cat可能是传来的参数:在使用的时候不知道存放的是Tomcat,误以为存的是CoffeeCat类型
b) 红色代码使用多态:绿色代码引用类型数据转换:cc.drink();
c) 编译只看类型不看值,如上代码编译都不会有问题!
d) 但是:运行时反应的是真实类型,绿色代码等于要将TomCat 变成 CoffeCat 显然不行!
2. 因此引用数据类型转换,最好在转换之前先判断类型在转换
3. 判断类型的方式
a) 获得运行时类型 Object 中getClass();方法
b) 类型判断运算符 instanceof
4. 判断类型示例
a) 获得运行时类型 Object 中getClass();方法
class Test {
public static void main(String[] args) {
Cat cat = new TomCat();
String name = cat.getClass().getName();//获取到类型的名字String类型的值
if(name.equals("CoffeeCat")){//比较是否羽CoffeeCat字符串一致
CoffeeCat cc = (CoffeeCat)cat;
cc.drink();
}else{
System.out.println("友好提示:小哥哥类型错了");
}
}
}
b) 类型判断运算符 instanceof
//测试类
class Test {
public static void main(String[] args) {
Cat cat = new TomCat();
if(cat instanceof CoffeeCat){
CoffeeCat cc = (CoffeeCat)cat;
}else{
System.out.println("友情提示:小姐姐你的类型错了");
}
}
}
5. instanceof运算符
Systme.out.println(cat instanceof Cat);//true
Systme.out.println(cat instanceof TomCat);//true
Systme.out.println(cat instanceof Object);//true
Systme.out.println(cat instanceof CoffeeCat);//false
System.out.println(tom instanceof CoffeeCat);////编译报错,不存在继承关系,不兼容,完全相关类型:编译器只看类型不看值
Systme.out.println(cat instanceof String);//编译报错,不存在继承关系,不兼容,完全相关类型:编译器只看类型不看值
4.5引用数据类型转换小结
1. 为什么需要引用数据类型转
(1) 明知道多态父类类型装的是子类对象,但是子类特性,父类对象访问,编译报错,需要转换成真实类型
(2) Cat cat = new TomCat(); cat.say();//父类Cat中没有say属性 编译报错
2. 数据类型转换的两种情况
(1) 大赚小 TomCat tc = (TomCat)cat;
(2) 小转大 Cat cat = new TomCat();
(3) 父类大子类小 : 父类类型兼容子类类型
3. 数据类型转换的注意事项
(1) 在转换之前要进行类型判断
(2) 两种判断方式
4. instanceof运算符的运用
(1) 只看类型不看值,不存在继承关系的,编译不通过
5. final
5.1 final是什么
1. final : Java中的一个关键字,修饰符:表示的意思:最终的,不可变的,不可拓展的!
2. 如何学习关键字修饰符
a) 关键字的含义
b) 可以修饰什么东西
c) 修饰之后有什么效果
5.2 final可以修饰的东西
1. 外部类:可以
2. 普通方法:可以
3. 成员字段:可以
4. 局部变量:可以
5. 内部类:可以 [ 暂时不学 ]
6. 构造方法:不可以
5.3 final修饰类的效果
1. final修饰类:最终类,不可拓展的类,太监类 :
2. 比如说: String类 Integer等包装类 是使用final修饰的;
final class Cat{
}
class TomCat extends Cat{
}
---------- Javac ----------
Test.java:10: 错误: 无法从最终Cat进行继承
class TomCat extends Cat{
^
1 个错误
输出完成 (耗时 0 秒) - 正常终止
3. 思考:final修饰的类有不有父类呢
5.4 final 修饰普通方法的效果
1. final修饰普通方法:最终的方法,不可拓展的方法:
class Cat{
final void eat(){}
}
class TomCat extends Cat{
@Override
void say(){}
}
---------- Javac ----------
Test.java:12: 错误: 方法不会覆盖或实现超类型的方法
@Override
^
1 个错误
输出完成 (耗时 0 秒) - 正常终止
2. 目前不能被覆写的方法 有哪些?
1) 使用final修饰的方法
2) 使用static修饰的方法
3) 使用private修饰的方法
5.5 final 修饰变量
1. final修饰变量:表示最终的变量 :
//测试类
class Test {
final static int i = 10;
public static void main(String[] args) {
final int j = 20;
i = 30;
j = 40;
}
}
2. final 修饰变量一般使用 public static final double PI = 3.14159265; 构成类中的全局常量仅供使用
3. final修饰变量拓展及堆栈分析
//测试类
class Test {
public static void main(String[] args) {
final Student stu = new Student();
stu.name = "小王";//可以因为修饰的不是name字段
System.out.println(stu.name);
stu = new Student();//不可以因为修饰的是stu变量
}
}
---------- Javac ----------
Test.java:7: 错误: 无法为最终变量stu分配值
stu = new Student();//不可以因为修饰的是stu变量
^
1 个错误
输出完成 (耗时 0 秒) - 正常终止
4. 堆栈分析
5.6 final小结
1. final关键字修饰符最终的不可拓展的不可变的
2. final可以修饰那些内容
3. final修饰类的效果
4. final修饰方法的效果
5. final 修饰变量的效果
6. final修饰变量的拓展分析
1. 单例模式(面试题)
2.1 什么是单例模式
1. 单例模式名词解释
单 一个
例 实例instance(对象)
模式 就是一种设计模式[为了解决某类问题,而提出的比较好的解决方案]
2. 单利模式概念:需要设计一个类,达到的效果: 此类在整个应用中只存在一个对象
1) 整个应用:
a. java代码写好的一个应用软件,系统:java代码开发结果,运行在JVM[处于]JRE运行环境}
2) 应用软件:java代码开发的
3) 单利模式好处:节省了系统资源,节省了内存空间
4) 单利模式对象怎么用 : 如果系统很多位置都会用到该对象,通过该对象的引用地址对其引用
3. 如何达到这样的效果:设计一个类,这个类的对象,永远只有一个?
1) 设计思路:
a. 设计一个类 class A{}
b. 对象怎么来的?new A(); new A(); new A(); ... 调用一次构造方法就得到一个对象
c. 把构造方法私有化,本类的外部就不能够随意的访问创建对象了
d. 思考 : 一个类的所有构造方法都被私有化,就不能够创建对象了,说法正确吗?不正确
a) 外部不能创建,自己内部可以创建
e. 可以在A类内部创建好一个,并保存起来,别人需要对象给它一个地址
2.2 设计实现之饿汉模式
1. 设计实现之饿汉模式
1) 构造方法私有化
2) 在类的内部创建一个对象
3) 使用一个字段保存起来
4) 提供一个方法允许外部使用该方法访问该字段
5) 提供的方法必须静态修饰,因为外部不能创建对象
6) 外部通过方法访问instance 字段,方法静态修饰,所以字段必须静态修饰
7) 字段不私有化,别人可以通过类名.instance修改子字段值,所以必须私有化
class A{
private A(){}
private A instance = new A();
public A getInstance(){
return instance;
}
}
分析:
1 单例模式的类也是一个普通的类,其中也可以有其他的字段 方法等
2 上面代码中,instance 对象是A类被加载[把类放到JVM的过程中]的时候创建的
3 问题:如果A类中其他的字段和方法很多,创建对象的过程比较长,类加载会比较慢,有可能加载之后,很长时间其实都没有人来获得对象:加载慢,消耗系统资源
4 需求:有没有一种方式:在类加载的时候先不创建对象,而是在有人第一次来调用方法获得对象的时候才创建一个对象,之后需要保存起来,以后再有人调用就不用创建对象
2.3 设计实现之懒汉模式
1. 设计实现之懒汉模式
1) 明确:应用程序在第一次调用方法获取单利模式对象的时候创建对象
2) 构造方法私有化
3) 设置一个A类类型的字段私有化不初始化值
4) 提供一个方法允许外部使用该方法访问该字段
5) 外部不能创建对象,所以方法必须static修饰
6) 什么时候是第一次【instance == null】
7) 当第一调用的时候判断
a. 如果instance == null :初始化instance
b. 如果instance != null : 直接返回instance
class A{
private A(){}
private static A instance;
public static A getInstance(){
if(instance == null){
instance = new A();
}
return instance;
}
}
2.4懒汉模式存在线程安全问题分析
1. 懒汉模式存在线程安全问题:有可能返回的不是同一个对象!
2. 线程A1,A2对代码的访问都是独立的
3. 线程A1,执行到位置A1 切换到A2 ,线程A2执行到位置A2,
4. A1线程继续执行,创建对象初始化instance,返回!切换到线程A2,继续执行又创建了对象给instance赋值,instance原来A1创建的对象地址值,被覆盖,A2会返回自己新创建的地址值。A1,A2 线程返回的不是同一个对象
5. 解决办法:加锁,A1,A2 必须访问的是同一把锁!
2.5使用场景
1. 主要设计在工具类中,例如Arrays中!static静态修饰方法
2. 单利模式设计在一个工具类中:好处!不用频繁的去创建创建对象
3. 频繁创建对象的不好之处:消耗系统资源,对象过多占用内存
4. JavaEE:JDBC数据裤链接的时候,还会使用到单利模式
2.6 小结
1. 知道什么是设计模式
2. 理解什么单利模式【结合对象创建堆栈分析】
3. 单利模式之饿汗模式设计及缺点
4. 单利模式之懒汉模式设计及缺点
5. 单利模式必须会写[7~8初级面试默写]
6. 课程总结
1.1. 重点
1. super
2. 多态
3. 引用类型转换
4. final
5. 单例模式
1.2. 难点
1. super特殊用途分析
2. 多态的应用,代码逻辑稍微较多。
3. 引用类型转换
1.3. 如何掌握?
1. 勤加练习...
7. 课后练习[30分钟]
第一题:面向对象的三大特征是什么?
第二题:谈谈对封装,继承,多态的理解?
第三题:使用final修饰一个类,测试它能否被继承?测试final修饰一个字段,能否被修改字段里面的值?
第四题:练习今天上课的所有的代码,并在代码上面说明清楚;
8. 面试题
谈谈你对面向对象的三大特征的理解?