ruijiege

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

面向对象-多态

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. 面试题

谈谈你对面向对象的三大特征的理解?

posted on 2019-07-18 16:45  哦哟这个怎么搞  阅读(259)  评论(0编辑  收藏  举报