文章目录
面对对象-封装
- 封装概述
是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。 - 封装好处
a. 隐藏实现细节,提供公共的访问方式
b. 提高了代码的复用性
c. 提高安全性。 - 封装原则:
a. 将不需要对外提供的内容都隐藏起来。
b. 把属性隐藏,提供公共方法对其访问。
private关键字的概述和特点:
-
private关键字特点:
a. 是一个权限修饰符权限修饰符:
- public 公共的 可以修饰成员变量,成员方法,类,被他修饰的可以在任意地方进行访问。
- public > protected > 缺省的(默认的) > private
- public 和 private 最为常用
b. 可以修饰成员变量和成员方法
c. 被其修饰的成员只能在本类中被访问(被private修饰过的属性或方法利用对象.属性(对象.方法())的方式是无法访问的,因为不在该类内!) -
private 应用的标准案例:
(1). 把成员变量用private修饰
(2). 提供对应的getXxx()和setXxx()方法
在IDEA中快速生成与私有成员变量对应的 getXxx()和setXxx()方法:- 在IDEA中点击右键选择 Generate
- 再选择Getter和Setter
- 再选择需要针对哪些私有变量生成方法(全选可以按键:crtl+A)
- 点击OK,即可生成!
(3). 示例如下:
public class Person { //private 私有的,是一个权限修饰符,可以修饰成员变量和成员方法,修饰后,该成员只能在本类中访问 private String name; private int age; //get set 方法的规范 //提供一个公共的赋值的方法 public void setAge(int nianLing) { if (nianLing >= 0 && nianLing <= 100) { age = nianLing; } else { System.out.println("这个年龄的数据不合理"); } } public void setName(String mingzi) { name = mingzi; } public String getName() { return name; } public int getAge() { return age; } private void show() { System.out.println(name); System.out.println(age); } }
- 使用该类:
public class MyTest { public static void main(String[] args) { //我们通过 对象名.属性名=值 这种赋值方式,不能验证数据的合理性 //我们现在想要对赋值的数据进行合理性的验证,需要屏蔽掉 对象名.属性名=值 这种赋值方式 //怎么来屏蔽掉呢?可以使用一个关键字private 私有的,是一个权限修饰符 Person p = new Person(); // p.name="张三"; //p.age=230; //设置值 p.setName("张三"); p.setAge(20); //获取属性值的方法 int age = p.getAge(); String name = p.getName(); System.out.println(name); System.out.println(age); //int num=p.age; // p.show(); } }
this关键字的概述和应用:
- 为什么要有this?
当我们的局部变量和成员变量相同的时候,如果我们不使用this关键字,那么会导致一个问题:就是局部变量隐藏了成员变量的问题(因为就近原则的缘故,会首先使用“局部变量!”)
例如:
在这种情况下:由于就近原则的缘故,name指向的都是setName方法中的局部变量,而类中String name的值却一直都没有变化!class Demo { public static void main(String[] args) { Phone phone1 = new Phone(); System.out.println(phone1.getName()); phone1.setName("iphone"); System.out.println(phone1.getName()); } } class Phone { //私有成员变量 private String name; private double price; //提供公共的set get 方法 public void setName(String name) { //就近原则 name = name; System.out.println(name); } public String getName(){ // 就近原则(该name是类中定义的name) return name; } } ----------------------------------------- 输出: null iphone null
- 改用this指针来指明方法中哪些是类成员变量!
class Demo { public static void main(String[] args) { Phone phone1 = new Phone(); System.out.println(phone1.getName()); System.out.println("phone1对象的地址值" + phone1); phone1.setName("iphone"); System.out.println(phone1.getName()); } } class Phone { //私有成员变量 private String name; private double price; //提供公共的set get 方法 public void setName(String name) { //就近原则 //this 表示本类的一个引用。你可以理解为本类的一个对象,哪个对象调用这个方法,那么方法中的this就代表这个对象 private String name; this.name = name; System.out.println(name); System.out.println("this代表调用者的地址值2" + this); } public String getName(){ System.out.println("this代表调用者的地址值1" + this); // 就近原则(该name是类中定义的name) return this.name; } } ---------------------- 输出: this代表调用者的地址值1edu.nwu.demo.Phone@1b6d3586 null phone1对象的地址值edu.nwu.demo.Phone@1b6d3586 iphone this代表调用者的地址值2edu.nwu.demo.Phone@1b6d3586 this代表调用者的地址值1edu.nwu.demo.Phone@1b6d3586 iphone
- this关键字特点
a. 是当前类的对象引用
b. 简单的记,它就代表当前类的一个对象。谁调用这个方法,那么该方法的内部的this就代表谁 - this的应用场景:
解决局部变量隐藏成员变量(因就近原则而引起的变量指代混淆!)
面向对象-继承
继承的一些概念:
-
继承概述:
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。 -
继承格式:
通过extends关键字可以实现类与类的继承class 子类名 extends 父类名 {}
单独的这个类称为父类,基类或者超类;这多个类可以称为子类或者派生类。
-
继承的好处和弊端:
- 好处:
a. 提高了代码的复用性
b. 提高了代码的维护性
c. 让类与类之间产生了关系,是多态的前提 - 弊端:
类的耦合性增强了
- 好处:
-
开发的原则:
高内聚,低耦合
耦合:类与类的关系
内聚:就是自己完成某件事情的能力
Java中类的继承特点
- JAVA只支持单继承,不支持多继承
- 即:不支持一个子对象同时继承多个父对象。
- JAVA支持多层继承(继承体系)
- 即:一个子对象的父对象也是它“爷爷”对象的子对象。(该孙子类也具备了“爷爷”类的特性!)
- 继承的注意事项:
(1). 子类只能继承父类所有非私有的成员(包括成员方法和成员变量)。
(2). 子类不能继承父类的构造方法,但是可以通过super关键字去访问父类构造方法。
(3). 不要为了部分功能而去继承。(不符合程序设计:高内聚,低耦合的设计理念。) - 什么时候使用继承?
(1). 继承其实体现的是一种关系:“is a” (子类是父类的一种!)
(2). 采用假设法:
如果有两个类A,B。只有他们符合A是B的一种,或者B是A的一种,就可以考虑使用继承。 - 继承中成员变量的关系:
A. 子类中的成员变量和父类中的成员变量名称不一样。
B. 子类中的成员变量和父类中的成员变量名称一样。
C. 在子类中访问一个变量的查找顺序:(“就近原则”)- 在子类的方法的局部范围找,有就使用。
- 在子类的成员范围找,有就使用。
- 在父类的成员范围找,有就使用。
- 如果还找不到,就报错。
this和super的区别和应用:
- super 的用途:
子类局部范围访问父类成员变量。 - 说说this和super的区别:
this 代表的是本类对象的引用
super代表的是父类存储空间的标识(可以理解成父类的引用,可以操作父类的成员) - this和super的使用:
- 调用成员变量:
this.成员变量 //调用本类的成员变量
super.成员变量 //调用父类的成员变量
- 调用构造方法
this(...) //调用本类的构造方法
super(...) //调用父类的构造方法
- 调用成员方法:
this.成员方法 //调用本类的成员方法
super.成员方法 //调用父类的成员方法
- 注意:能调用这些方法的都是对象,是通过该类的对象来调用的,this,super都会有相应的对象来调用,在调用子类构造函数的同时,也会对父类对象进行初始化操作,所有才有一个能调用super的对象,详情请参考继承对象的内存图。
- 调用成员变量:
继承中的构造方法
- 继承中的构造方法的关系:
子类中所有的构造方法默认都会访问父类中空参数的构造方法 - 这样做的原因:
因为子类会继承父类中的数据,可能还会使用父类的数据,所以,子类初始化之前,一定要先完成父类数据的初始化。 - 注意:
- 其实,每一个子类构造方法的第一条语句默认都是:super()
- Object类,元类,是所有类的父类。(否则父类的父类会是谁呢?就是这个object)
- 继承中构造方法的注意事项:
- 父类没有无参构造方法,子类怎么办?(之所以没有,可能是因为被方法重载了)
a. 在父类中添加一个无参的构造方法(常用!)
b. 子类通过super去显示调用父类其他的带参的构造方法(常用!)
c. 子类通过this去调用本类的其他构造方法(本类其他构造也必须首先访问了父类的有参构造)
示例:(报错,无法初始化父类对象!)
示例:(正确方法!)public class DEMO4 { public static void main(String[] args) { Student2 student2 = new Student2("小波",10); student2.show(); } } class Person{ int x1 = 0; public Person(int x1){ System.out.println("父类的构造函数已经调用!"); this.x1 = x1; } public void show(){ System.out.println("父类的对象在呼唤!"); } } class Student2 extends Person{ String name; int age; public Student2(String name, int age){ // 这样的话,由于子类无法初始化父类对象 // (默认调用空参构造函数),所以该行报错! this.name = name; this.age = age; } public void test(){ super.show(); } }
public class DEMO4 { public static void main(String[] args) { Student2 student2 = new Student2("小波",10); student2.show(); } } class Person{ int x1 = 0; public Person(int x1){ System.out.println("父类的构造函数已经调用!"); this.x1 = x1; } public void show(){ System.out.println("父类的对象在呼唤!"); } } class Student2 extends Person{ String name; int age; public Student2(String name, int age){ super(10); // 加上这条语句就正常了! this.name = name; this.age = age; // super(20); ---------> 如果把super放到这里来,也是报错!(所以必须放到构造函数的第一行去!) } public void test(){ super.show(); } }
- super(…)或者this(….)必须出现在构造方法的第一条语句上
详情请参考上述代码中的注释部分!
- 父类没有无参构造方法,子类怎么办?(之所以没有,可能是因为被方法重载了)
- 经典例题:
public class DEMO5 { public static void main(String[] args) { Zi z = new Zi(); //请执行结果。 } } class Fu { static { System.out.println("静态代码块Fu"); //3 1 } { System.out.println("构造代码块Fu"); //4 3 } public Fu() { System.out.println("构造方法Fu"); //5 4 } } class Zi extends Fu { static { System.out.println("静态代码块Zi"); //1 2 } { System.out.println("构造代码块Zi"); //2 5 } public Zi() { super(); System.out.println("构造方法Zi"); //6 6 } } ------------------------ 输出: 静态代码块Fu 静态代码块Zi 构造代码块Fu 构造方法Fu 构造代码块Zi 构造方法Zi
继承中成员方法关系:
- 当子类的方法名和父类的方法名不一样的时候:
用谁调谁! - 当子类的方法名和父类的方法名一样的时候:(跟成员变量的用法相似:就近原则!)
则,如果通过子类调用方法:
a: 先查找子类中有没有该方法,如果有就使用
b:在看父类中有没有该方法,有就使用
c: 如果没有就报错
方法重写:
- 概念:
子类中出现了和父类中一模一样的方法声明(方法名,参数列表,返回值类型),也被称为方法覆盖,方法复写。
简单来说:子类对父类功能的实现不满意,想要根据自己的差异性,来实现。那子类就可以进行方法重写,区覆盖掉父类的方法,在实际调用中,以子类重写的为准! - 方法重写和方法重载的区别?方法重载可以改变返回值类型吗?
- 方法重写针对的是继承关系下的子类和父类的成员函数
- 方法重载针对的是同一类中不同参数个数或形式的成员方法
- 方法重载不能改变返回值类型,因为返回值类型不是方法是否重载的标志!(只是返回值类型不同的同名函数会报错!)
- 方法重写的应用:
当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的方法。这样,即沿袭了父类的功能,又定义了子类特有的内容。 - 方法重写的语法:
子类写一个和父类的方法一模一样(返回值,形参,方法名)的方法就行 - 方法重写和方法扩展:
在方法重写的逻辑里面,再加入super.方法,即:相当于在子类中,对父类的方法上进行了方法扩展! - 在IDEA中快速实现子类对父类的方法重写,在继承的子类中按住Ctrl+o,即可快速生成重写的方法(会自动添加一个注解符号:@Override:检测方法,是不是重写父类的!),示例如下:
public class Test { public static void main(String[] args) { Zi zi = new Zi(); zi.show(); System.out.println("这是主方法!"); } } class Fu{ public void show (){ System.out.println("这是父类的方法!"); } } class Zi extends Fu{ @Override //注解符 public void show() { super.show(); System.out.println("这是子类的方法!"); } } --------------------------------------------- 输出: 这是父类的方法! 这是子类的方法! 这是主方法!
- 关于注解符号的详细内容,会在以后的课程内容中进行讲解!
- 重写方法的注意事项:
- 父类私有的方法子类不能重写,因为私有的方法子类都不能继承,更不能重写
- 子类在重写父类方法时,子类方法前面的权限修饰符,不能比父类的低,要比父类的高,或者一样也行
- 权限修饰符:public > protected > 缺省的(就是在权限修饰符的位置啥也不写) > private
- 构造方法不能重写
- 静态方法不参与重写(可以用super方法来理解,super必须调用一个父类的对象,静态方法属于类,根本就没有对象-----------> 这样的理解好像也不对,因为new 子类的时候也会生成父类,但是确实不是重写,就像是重定义一样!利用Override注解符进行修饰的时候报错!)
继承中的final修饰符:(---->不等于权限修饰符)
- 为什么会有final:
由于继承中有一个方法重写的现象,而有时候我们不想让子类去重写父类的方法.这对这种情况java就给我们提供了一个关键字: final - final概述:
final关键字是最终的意思,可以修饰类,变量,成员方法。 - final修饰的特点:final可以理解为:最终版,则不会被改变,它就是最完美的了!
修饰对象 | 特点 |
---|---|
修饰类 | 被修饰类不能被继承 |
修饰方法 | 被修饰的方法不能被重写,子类只能继承下来使用 |
修饰变量 | 该变量变成常量,该变量(常量)的值不能被改变 |
final 修饰引用数据类型 | 指的是这个引用类型指向的地址值不能被改变! |
- 引出问题:JAVA中的常量到底指的是什么?
-
常量值:常量值又称为字面常量,它是通过数据直接表示的,因此有很多种数据类型,像整型和字符串型等。
-
常量:常量不同于常量值,它可以在程序中用符号来代替常量值使用,因此在使用前必须先定义。常量与变量(在《Java变量的声明和初始化》一节中讲解)类似也需要初始化,即在声明常量的同时要赋予一个初始值。常量一旦初始化就不可以被修改。它的声明格式为:
Java 语言使用 final 关键字来定义一个常量,其语法如下所示:
final dataType variableName = value
-
详情请参考: Java常量的定义和分类:
-
问题:在IDEA中,如果在同一个项目下,需要写一些重名的类,总会报错,难道在一个package下的类名都不能重复吗?
-
- 继承中一个很经典的例题:(必看!)
重点解析:public class DEMO6 { public static void main(String[] args) { Cat cat = new Cat(10,"加菲猫"); cat.run(); cat.eat(); Dog dog = new Dog(12,"二哈"); dog.run(); dog.eat(); } } class Animal{ int age; String name; public void eat(){ System.out.println(name+"正在吃饭"); } public void run(){ System.out.println(name+"正在跑步!"); } } class Cat extends Animal{ Cat(int age, String name){ this.age=age; this.name = name; System.out.println(this.name+"的初始化已经完成!"); System.out.println("父类的名称为"+super.name); } public void eat(){ System.out.println(this.name + "正在吃小鱼干!"); } } class Dog extends Animal{ Dog(int age,String name){ this.age = age; this.name = name; System.out.println(this.name +"的初始化已经完成!"); System.out.println("父类的名称为" + super.name); } public void eat(){ System.out.println(this.name + "正在吃骨头!"); } } ---------------------- 输出: 加菲猫的初始化已经完成! 父类的名称为加菲猫 加菲猫正在跑步! 加菲猫正在吃小鱼干 二哈的初始化已经完成! 父类的名称为二哈 二哈正在跑步! 二哈正在吃骨头!
- 父类Animal中的成员变量被继承了以后,this.name 和super.name指向的都是同一个name,但这个name是属于父类对象的成员变量。(age同理可得!)
- 父类的方法一旦被继承下来调用的时候,此时父类的成员方法中引入的非局部变量前面都是隐形的this,代指这个父类的对象(这个父类的对象是在初始化子类的时候,自动初始化的父类对象),但这里,猫跟狗的父类虽然都是Animal,但是其父类对象各有不同(因为其属性值不同),所以有两个父类的对象。参考继承过程中的内存图可以清晰地明白这个过程知:在继承过程中,创建的父类对象所在的内存是在堆中子类对象内存中的一部分。
面向对象-多态
多态的基础知识:
- 概念:
指的是一种事物,在不用时刻,所表现出的不同状态。
举例:Cat c=new Cat(); //普通写法 猫可以是猫的类型。猫 m = new 猫(); ---------------------------------------------- Animal a=new Cat(); //多态写法 同时猫也是动物的一种,也可以把猫称为动物。动物 d = new 猫();
- 多态的前提:(三者必须同时存在,且缺一不可!)
a. 要有继承关系
b. 要有方法重写。 (其实没有也是可以的,但是如果没有这个就没有意义。 )
c. 要用父类引用指向子类对象。 - 多态的示例:
public class MyTest { public static void main(String[] args) { //普通写法: // Cat cat = new Cat(); // cat.eat(); //多态的方式:父类引用,指向子类对象 Animal an = new Dog(); an.eat(); an = new Cat(); an.eat(); } } class Animal { public void eat() { System.out.println("吃饭"); } } class Cat extends Animal { @Override public void eat() { System.out.println("吃小鱼干"); } } class Dog extends Animal { @Override public void eat() { System.out.println("狗吃骨头"); } } ------------------- 输出: 狗吃骨头 吃小鱼干
多态中的成员访问特点:
- 多态的形式访问成员变量:编译看左边,运行看左边
Fu fu = new Zi(); system.out.println(fu.num) //编译看左边,运行看左边 //打印的是父类定义的变量值 //编译看左边的意思是:编译器检查报错 //运行看右边的意思是:运行代码的时候,到底运行的是谁的代码
- 构造方法:
创建子类对象的时候,会访问父类的构造方法,对父类的数据进行初始化。 - 多态的形式访问成员方法的特点:编译看左边,运行看右边(子类重写了父类的方法)
public class Test { public static void main(String[] args) { // Zi zi = new Zi(); // zi.show(); // System.out.println("这是主方法!"); // 父类引用指向子类对象: // 左(父类) 右(子类) Fu fu = new Zi(); ((Zi) fu).show2(); //这样就不会报错!(这样感觉就成了普通的继承,并不是多态!)这叫做向下转换! //fu.show2(); //编译报错!(编译看左边,左边是父类,但父类没有相应的show2方法) Fu fu2 = new Zi2(); fu2.show(); //运行的是右边的结果,即:子类的结果 } } class Fu{ public int age =100; public void show (){ System.out.println(age); System.out.println("这是父类的方法!"); } } class Zi extends Fu{ //public int age; @Override //注解符 public void show() { super.show(); System.out.println(age); System.out.println("这是子类的方法!"); } public void show2(){ System.out.println("这是Zi的show2方法"); } } class Zi2 extends Fu{ @Override public void show() { //super.show(); System.out.println("这是Zi2的show方法!"); } } ---------------------------------------- 输出: 这是Zi的show2方法 这是Zi2的show方法!
- 静态方法:
编译看左边,运行看左边。(静态和类相关,算不上重写,所以,访问还是左边的)、
示例:public class MyTest { public static void main(String[] args) { // Zi zi = new Zi(); // System.out.println(zi.num); // zi.fuShow(); //多态的形式访问成员变量的特点 //多态的形式访问成员变量:编译看左边,运行看左边 Fu fu = new Zi(); System.out.println(fu.num); //多态的形式访问成员方法的特点:编译看左边,运行看右边 (子类重写了父类的方法) fu.fuShow(); //静态方法:不参与重写 fu.fuShow2(); Fu.fuShow2(); Zi.fuShow2(); ((Zi)(fu)).fuShow2(); } } class Fu { int num = 100; public void fuShow() { System.out.println("fu show"); } public static void fuShow2() { System.out.println("fu 静态 show"); } } class Zi extends Fu { int num = 200; @Override public void fuShow() { System.out.println("zi show"); } public static void fuShow2() { System.out.println("zi 静态 show"); } } ---------------------- 输出: 100 zi show fu 静态 show fu 静态 show zi 静态 show zi 静态 show
多态的优点和缺点:
- 多态的好处:
a. 提高了代码的维护性(继承保证)
b. 提高了代码的扩展性(由多态保证) (详情可以参考下面的示例!!!即:在工具类的类方法中的形参的类型用父类来声明,传递实际参数的时候,用子类对象来代表实际参数,即:造成"父类引用指向子类对象的效果——多态"!)
示例:- 父类:
public class Animal { public void eat() { System.out.println("吃饭"); } }
- 子类Cat:
public class Cat extends Animal{ @Override public void eat() { System.out.println("猫吃鱼"); } public void catchMouse(){ System.out.println("猫抓老鼠"); } }
- 子类Dog:
public class Dog extends Animal{ @Override public void eat() { System.out.println("狗吃骨头"); } }
- 子类Mouse:
public class Mouse extends Animal{ @Override public void eat() { System.out.println("老鼠偷油吃"); } }
- 子类Tiger:
public class Tiger extends Animal{ @Override public void eat() { System.out.println("脑斧爱吃兔纸"); } }
- 子类大象:
public class 大象 extends Animal{ @Override public void eat() { System.out.println("大象吃苹果"); } }
- 工具类MyUtils:
结论:这里的好处是由多态带来的,工具类函数的形参定义为:父类的对象,此处若传入一个子类的对象,则:即可构成多态的写法!,利用多态编译看左边,运行看右边的原则。就可以实现一种写法,多种调用的结果,减少了工具类的函数编写的复杂度!public class MyUtils { //私有构造,外界就无法创建,该类对象 private MyUtils() { } // 工具类声明了一个形式参数是以父对象类型来定义的!(如果传入的实参是该父对象的一个对象,则:此处构成了多态的写法!) public static void test(Animal an) { // Animal an=cat 父类引用指向子类对象 // Animal an=dog 父类引用指向子类对象 //多态的方式:在调用成员方法的时候,编译看左边,运行看右边 //面向父类或接口 编程 an.eat(); } //------------------------------------ // 工具类以方法重载的方式来提供对主函数的调用:(这样写太过于繁琐,如果子类特别多,则不合理!) public static void test(Cat cat) { cat.eat(); } public static void test(Dog dog) { dog.eat(); } public static void test(Tiger tiger) { tiger.eat(); } public static void test(Mouse mouse) { mouse.eat(); } }
- 主函数MyTest:
public class MyTest { public static void main(String[] args) { //使用多态的好处: //1.提高了代码的复用性,是靠继承保证的 //2.提高了代码的扩展性。 Cat cat = new Cat(); MyUtils.test(cat); //使用工具类的方法来调用即可,不需要创建对象一个个来调用!(工具类的意义所在!) /*Dog dog = new Dog(); MyUtils.test(dog); /* Tiger tiger = new Tiger(); MyUtils.test(tiger); Mouse mouse = new Mouse(); MyUtils.test(mouse); 大象 dx = new 大象(); MyUtils.test(dx);*/ } } ------------------------------------- 输出: 猫吃鱼
- 父类:
- 测试类:
只需要提供一个入口即可,不需要在测试类中写太多的类方法,即:测试类中最好只有一个main函数即可。 - 多态的弊端:
- 多态的形式,去访问子类特有的方法,是调用不到的!(必须利用向下类型转换来访问子类特有的方法!)
就比如:不能在上面工具类的代码中用an这个对象去调用catMouse()这个方法!
注意:多态本身就是“向上转型!”Father father = new Zi(); //向上转型!(其实多态就是向上转型!) Zi zi = (Zi) father; //向下转型!
- 向下转型可能会遇到类型转换异常的情况:java.lang.ClassCastException(见老师上课演示)
示例:(改变上面的工具类文件中的一个写法)
原因就是:其实写成了子类和子类之间的相互转换,这是不能转换的!只能把向上转型过的子类对象再向下回转(型)为原子类对象,不能把向上转型过的子类对象向下回转(型)为另一个子类对象(同属于一个父类的另一个子类)public class MyUtils { //私有构造,外界就无法创建,该类对象 private MyUtils() { } // 工具类声明了一个形式参数是以父对象类型来定义的!(如果传入的实参是该父对象的一个对象,则:此处构成了多态的写法!) public static void test(Animal an) { // Animal an=cat 父类引用指向子类对象 // Animal an=dog 父类引用指向子类对象 //多态的方式:在调用成员方法的时候,编译看左边,运行看右边 //面向父类或接口 编程 //an.eat(); } //------------------------------------ // 工具类以方法重载的方式来提供对主函数的调用:(这样写太过于繁琐,如果子类特别多,则不合理!) public static void test(Cat cat) { cat.eat(); Cat cat= (Cat) an; //向下转换! cat.catchMouse(); //向下转换后才能调用子类特有的方法!(但是这样只对猫类的对象来说是成立的,对于其他对象来说,统统都要报类型转换异常的错误!) } public static void test(Dog dog) { dog.eat(); } public static void test(Tiger tiger) { tiger.eat(); } public static void test(Mouse mouse) { mouse.eat(); } }
即:猫可以变为动物,也可以从动物变回猫,但是猫不能转换为狗!
示例2:package westos.org.newtest; public class Test { public static void main(String[] args) { Cat cat = new Cat(); Animal an = cat; //多态:向上转换! Cat cat1 = (Cat) an; // 向下转换! //Tiger tiger1 = (Tiger)an;(编译器不报错,但是运行的时候会报出类型转换异常的错误!) //上下两者是等价的 //Tiger tiget1 = (Tiger)cat;(编译器直接报错,说明两个不同的子类是不能相互转换的!) Tiger tiger = new Tiger(); //新建立一个父对象,然后测试它能否被直接转换为子类对象(说明父类对象不能被强制转换为子类对象,子类指针不能指向父类对象(除非是向上转换过以后的!),但是父类对象可以指向子类对象(多态)) Animal an1 = new Animal(); Cat cat2 = (Cat)an1;//报错(类型转换异常)java.lang.ClassCastException westos.org.newtest.Animal cannot be cast to westos.org.newtest.Cat cat2.eat(); } } class Animal { String name; public void eat(){ System.out.println(name+"这是Animal的eat方法!"); } } class Cat extends Animal{ String name = "小猫咪"; @Override public void eat() { System.out.println(this.name +"爱吃小鱼干!"); } } class Tiger extends Animal { String name = "小老虎"; @Override public void eat() { System.out.println(name+"爱吃小猫咪!"); } }
- 多态的形式,去访问子类特有的方法,是调用不到的!(必须利用向下类型转换来访问子类特有的方法!)
多态(向上,向下类型转换)的一个非常经典的示例:“孔子装爹”
多态的内存图解
- 图示:
- 动态绑定(在上课讲授多态情况下的内存图总提到的!)
向上转换的时候,指针实际指向的区域是堆中子类对象中的一块super区域(初始化子类过程中初始化的父类对象),所以应用变量的时候它会直接取父对象的变量,但是在应用其方法的时候,会进行动态绑定,看子类中是否有定义的同名方法,如果有的话,则执行子类方法,否则,则执行父类方法。