- 子类对象实例化过程图示:
思考:
1.为什么super(…)和this(…)调用语句不能同时在一个构造器中出现?
答: 因为他俩都只能出现在首行,所以只能写一个(好比古代皇后只能有一个)
2.为什么super(…)或this(…)调用语句只能作为构造器中的第一句出现?
答: 无论通过哪个构造器创建子类对象,需要保证先初始化父类.
目的: 当子类继承父类后,"继承"父类中所有的属性和方法,因此子类有必要知道父类如何为对象进行初始化.
所以加载顺序是首先加载间接父类,其次加载直接父类,然后才是加载到本类
强调说明:
虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象
图示:
虽然加载了父类构造器了,但不认为创建了多个对象,对外是整个红框,体现的是一个类型的对象,就是new的Dog
5.6.多态性-多态性的使用
面向对象特征之三: 多态性
1.多态性的理解: 可以理解为一个事物的多种形态
2.什么是多态性: 父类的引用指向子类的对象(或子类的对象赋给父类的引用)
Person p = new Man();
Object obj = new 任意();
3.多态的使用: 虚拟方法使用
有了对象的多态性后,在编译期(编译器编译的时候),只能调用父类中声明的方法,但在运行期,实际执行的是子类重写父类的方法
如果子类中没有重写父类的方法,就执行父类的方法(这样使用多态性没有意义)
p.eat();
总结:编译时看等号左边的类型,运行时看右边的对象(编译时是父类,执行时是子类)
4.多态性的使用前提(只说方法的事没有属性的事,多态性方面跟属性没关系): (1)要有类的继承关系 (2)方法的重写
5.对象的多态性: 只适用于重写的方法,不适用于属性(属性的编译和运行都看左边).
如果Java中没有多态性的话,那抽象类和接口就没有存在的意义,因为抽象类和接口都不能造对象,开发中使用抽象类和接口,一定会提供子类的对象或实现类的对象,这里体现都是多态性
多态性的好处: 减少大量方法的重载
public class PersonTest {
public static void main(String[] args) {
Person p1 = new Person();
p1.eat();
Man man = new Man();
man.eat();
man.age = 25;
man.earnMoney();
System.out.println("*********************");
// 对象的多态性: 父类的引用(p2)指向子类的对象(new Man())
Person p2 = new Man(); // 多态的形式
Person p3 = new Woman();
// 多态的使用: 当调用子类同名同参数的方法时,实际执行的是子类重写父类的方法---虚拟方法调用
// 有了多态的形式后,通过引用调用子父类都声明过的方法,真正执行的是子类重写父类的方法
p2.eat(); // 编译看左,运行看右
p2.walk();
// 多态性的使用不能调父类没有的方法属性, 编译时,p2是Person类型
// p2.earnMoney(); 报错
// p2.isSmoking = true; 报错
System.out.println(p2.id); // 1001; 多态性与属性无关,p2编译时的类型就是属性的类型
}
}
public class Person {
String name;
int age;
int id = 1001;
public void eat(){
System.out.println("人吃饭");
}
public void walk(){
System.out.println("人走路");
}
}
public class Man extends Person{
boolean isSmoking;
int id = 1002;
public void earnMoney(){
System.out.println("男人负责挣钱");
}
/*@Override
public void eat() {
System.out.println("男人多吃肉,长肌肉");
}*/
@Override
public void walk() {
System.out.println("男人霸气的走路");
}
}
public class Woman extends Person{
boolean isBeauty;
public void goShopping(){
System.out.println("女人喜欢购物");
}
@Override
public void eat() {
System.out.println("女人少吃为了减肥");
}
@Override
public void walk() {
System.out.println("女人窈窕的走路");
}
}
- 多态性的使用举例:
import java.sql.Connection;
// 多态性的使用举例一:
public class AnimalTest {
public static void main(String[] args) {
AnimalTest test = new AnimalTest();
test.func(new Dog());
test.func(new Cat());
}
// 使用多态性: 凡是new子类的对象
// 每种动物对一件事表现出来的形态都不一样 就是多态
public void func(Animal animal){
// Animal animal = new Dog(); 对象多态性的形式
// 因为形参是Animal类型的,所以只能调Animal类里的方法,但实际new了一个Dog类的对象,真正运行的时候,是Dog重写父类方法的执行
animal.eat();
animal.shout();
}
// 不用多态性的写法: 声明什么类型只能new这个类型的对象
public void func(Dog dog){
dog.eat();
dog.shout();
}
public void func(Cat cat){
cat.eat();
cat.shout();
}
}
// 父类
class Animal{
public void eat(){
System.out.println("动物进食");
}
public void shout(){
System.out.println("动物叫");
}
}
// 子类
class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头");
}
@Override
public void shout() {
System.out.println("汪汪汪");
}
}
class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼");
}
@Override
public void shout() {
System.out.println("喵喵喵");
}
}
// 举例二:
class Order{
public void method(Object obj){
}
}
// 举例三:
class Driver{
// 形参里传哪个对象就建立跟哪个数据库的连接,因为子类方法都重写过了,自然就能实现对那个数据库中表的操作
public void doData(Connection conn){
// conn = new MySQLConnection();/ conn = new OracleConnection();/...
// 规范步骤去操作数据 都是子类重写过的方法
/*conn.method1();
conn.method2();
conn.method3();*/
}
}
- 虚拟方法调用的再理解
- 面试题: 多态是编译时行为还是运行时行为?
编译时期和运行时期的区别
面试题: 多态是编译时行为还是运行时行为?
运行时行为;看代码看不出来调的是哪个,因为有随机数,运行了才知道随机数是多少
/ 面试题: 多态是编译时行为还是运行时行为?
// 证明结论: 运行时行为,main函数中的animal引用指向哪个子类的对象,是由运行时产生的随机数再调用了判断得到的,最后运行结果是返回对象类型中eat()方法而不是编译时Animal类中的eat()方法
import java.util.Random;
public class InterviewTest {
public static void main(String[] args) {
int key = new Random().nextInt(3); // 取随机数: 0~2
System.out.println(key);
// 通过下面new的对象来看,这实际上就是多态
Animal animal = getInstance(key);
animal.eat();
}
public static Animal getInstance(int key){
switch (key){
case 0:
return new Cat();
case 1:
return new Dog();
default:
return new Sheep();
}
}
}
class Animal{
protected void eat(){
System.out.println("animal eat food");
}
}
class Dog extends Animal {
@Override
protected void eat() {
System.out.println("dog eat bone");
}
}
class Cat extends Animal {
@Override
protected void eat() {
System.out.println("cat eat fish");
}
}
class Sheep extends Animal {
@Override
protected void eat() {
System.out.println("sheep eat grass");
}
}
- 小结: 方法的重载和重写
5.7.向下转型的使用
内存解析的说明
引用类型的变量,只可能储存两类值:
- null
- 内存地址值,包含变量(对象)的类型
例如,直接打印一个实例化后对象的值
Phone p = new Phone();
System.out.println(p)
得到的结果为:
- instanceof关键字的使用
向下转型的目的: a instanceof A 判断 对象a 运行时是不是多态中子类的A类类型,如果是,则要向下转型,向下转型的目的是可以让对象a点出运行时实际(真实)类型对象的特有的属性或方法
a instanceof A: 判断对象a是否是类A的实例.如果是,返回true,如果不是,返回false
使用情境,为了避免在向下转型时出现ClassCastException的异常,在向下转型之前,
先进行instanceof的判断,一旦返回true,就进行向下转型.如果返回false,不进行向下转型.
如果 a instanceof A 返回 true,则 a instanceof B 也返回true,
其中类B和类A的不都是同一父类的子类
public class PersonTest {
public static void main(String[] args) {
Person p1 = new Person();
p1.eat(); // 父类调用自己的方法
Man man = new Man();
man.eat(); // 子类继承父类调用父类的方法
man.age = 25;
man.earnMoney();
System.out.println("*********************");
// 对象的多态性: 父类的引用(p2)指向子类的对象(new Man())
Person p2 = new Man(); // 多态的形式
Person p3 = new Woman();
Person p4 = new Person();
Man m1 = new Man();
// 多态的使用: 编译时当调用子类同名同参数的方法时,认为是父类的方法,实际执行的是子类重写父类的方法---虚拟方法调用
// 有了多态的形式后,通过引用调用子父类都声明过的方法,真正执行的是子类重写父类的方法
p2.eat(); // 编译看左,运行看右; 在编译期认为p2是Person类型,所以只能调用Person中声明的属性和方法
p2.walk();
System.out.println("===================");
p2.name = "tom";
// 多态性的使用不能调父类没有子类特有的方法,属性, 编译时,p2是Person类型
// p2.earnMoney(); // 报错
// p2.isSmoking = true; // 报错
System.out.println(p2.id); // 1001; 多态性与属性无关,p2编译时的类型就是属性的类型
// 有了对象的多态性后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型.
// 导致编译时,只能调用父类中声明的属性和方法,(子类特有的属性和方法相当于被屏蔽了)子类特有的属性和方法不能调用.
// 如何才能调用子类特有的属性和方法?
// Man m1 = p2; // java中左右赋值要么左右类型一样,要么是基本数据类型且有自动类型提升; 父类型变量不能赋给子类型变量
System.out.println(p4);
System.out.println(m1);
p4 = m1; // 类类型变量,子类型变量可以赋给父类型变量,表现为多态;
// 向下转型: 使用强制类型转换符
System.out.println(p2); // p2运行时是Man类型
Man m2 = (Man) p2; // 编译时,把Person类型的变量强转成Man类型的一个新变量,相当于m2是Man类型的p2
m2.earnMoney();
m2.isSmoking = true;
// 使用强转时,出现ClassCastException的异常
// Woman w1 = (Woman) p2; // 编译虽然通过,但运行时p2已经是Man类型了,子类类型之间不能不能互相转换,地址类型不相同,
// w1.goShopping(); // 执行时出错,出现ClassCastException的异常
/*
* instanceof关键字的使用
* a instanceof A: 判断对象a是否是类A的实例.如果是,返回true,如果不是,返回false
* 使用情境,为了避免在向下转型时出现ClassCastException的异常,在向下转型之前,
* 先进行instanceof的判断,一旦返回true,就进行向下转型.如果返回false,不进行向下转型.
* 如果 a instanceof A 返回 true,则 a instanceof B 也返回true,
* 其中类B是类A的父类
*/
// 本质就是把p2的new的类型能否赋值给声明时的类型,是子类对象一定是父类对象,是父类对象不一定是子类对象
if (p2 instanceof Person){
System.out.println("person");
}
if (p2 instanceof Man){
Man m3 = (Man) p2;
m3.earnMoney();
System.out.println("man");
}
if (p2 instanceof Woman){
Woman w1 = (Woman) p2;
w1.goShopping();
System.out.println("woman");
}
// 练习:
// 问题一: 编译时通过,运行时不通过
/*Person p5 = new Woman();
Man m4 = (Man) p5;*/
Person p6 = new Man();
Man m6 = (Man) p6;
// 问题二: 编译时通过,运行时也通过
Object obj = new Woman();
Person p = (Person) obj;
// 问题三: 编译时不通过,运行时也不通过
// Man m5 = new Woman();
// String s = new Date(); // Date类型和String类没任何关系
}
}
public class Person {
String name;
int age;
int id = 1001;
public void eat(){
System.out.println("人吃饭");
}
public void walk(){
System.out.println("人走路");
}
}
public class Man extends Person{
boolean isSmoking;
int id = 1002;
public void earnMoney(){
System.out.println("男人负责挣钱");
}
@Override
public void eat(