JavaSE:成员变量的初始化、多态

一、成员变量初始化

1.成员变量分为两大类:

(1)静态变量:有static修饰

(2)实例变量:没有static修饰

2.静态变量的初始化

(1)在类初始化进行时,一个类只有一次。因为静态变量是所有对象共享的,属于类的,不是专属与某个对象

(2)和静态变量的显示复制语句和静态代码块有关

本质上把两部分代码合并到一个()类初始化方法中,由类加载器在类初始化时调用。

3.实例变量的初始化

1.一定是在new对象时进行的,而且每次new对象都要初始化一次,因为每一个对象都是独立的

A:super()或super(实参列表)
B:实例变量的显示赋值
C:非静态代码块
D:构造器
以上4个部分:A必须先完成,然后B和C的话是按照编写的顺序进行,最后在执行D构造器中除了A的剩下的代码。

本质上,创建对象时,是在执行实例初始化方法,一个类可能有多个实例初始化方法,有几个看你编写的构造器的个数,如果没有编写,那么只有一个。
实例初始化方法的代码就是由以上四个部分的代码组成。
A:super()或super(实参列表) 在实例初始化方法的首行
B:实例变量的显示赋值
C:非静态代码块
D:构造器中剩下的代码

要注意的是:B 和 C 是按照编写的顺序组装,可能先B后C,也可能先C后B

注意:super()现在不仅仅是代表父类的无参构造了,而是代表父类的()无参实例初始化方法。
super(实参列表)现在不仅仅是代表父类的有参构造了,而是组装后的父类的(…)有参实例初始化方法。

Demo类编译后:
class Demo {
    private int a;

    <init>(){//无参实例初始化方法
        a= getNum();
        System.out.println("非静态代码块,a = " + a);
        System.out.println("无参构造");
    }
    <init>(int a){//有参实例初始化方法
        a= getNum();
        System.out.println("非静态代码块,a = " + a);
        this.a = a;
        System.out.println("有参构造a = " + this.a);
    }

    public int getNum(){
        System.out.println("getNum()被执行了,a = " + a);
        return 10;
    }
}
 */
public class TestInitialize {
    public static void main(String[] args) {
        Demo d = new Demo();
        System.out.println("----------------------------");
        Demo d2 = new Demo(20);
    }
}

class Demo {
    private int a = getNum();

    {
        System.out.println("非静态代码块,a = " + a);
    }

    Demo(){
        System.out.println("无参构造");
    }
    Demo(int a){
        this.a = a;
        System.out.println("有参构造a = " + this.a);
    }

    public int getNum(){
        System.out.println("getNum()被执行了,a = " + a);
        return 10;
    }
}

/*

4.静态变量的初始化和非静态的实例变量的初始化合起来。

(1)静态的变量的初始化先进行

(2)完成之后,才会轮到实例变量的初始化

如果有父类,有子类。
先父类的静态,再子类的静态。然后父类的非静态,最后子类的非静态。
每一个类只会初始化一次。即每个类的静态初始化只会完成一次。

public class TestInitialize3 {
    public static void main(String[] args) {
        Father f = new Father();//32314
        Son son = new Son();//单独它 3276 314 758

        //两句合起来  32314 76 314 758 父类的静态不需要初始化2次

        Son son2= new Son();
        //三句合起来  32314 76 314 758  314 758  子类的静态也不用执行2次
    }
}
class Father{
    private static int a = getNum();
    private int b = getNum();
    {
        System.out.println("1 Father:not_static");
    }
    static{
        System.out.println("2 Father:static");
    }

    public static int getNum(){
        System.out.println("3 getNum()");
        return 1;
    }

    Father(){
        System.out.println("4 Father()无参构造");
    }
}
class Son extends Father{
    private static int a = getNum();
    private int b = getNum();
    {
        System.out.println("5 Son:not_static");
    }
    static{
        System.out.println("6 Son:static");
    }

    public static int getNum(){
        System.out.println("7 getNum()");
        return 1;
    }

    Son(){
        System.out.println("8 Son()无参构造");
    }
}

二、多态

1.三大基本特征的好处:

1封装:(1)安全(2)便捷

2.继承:(1)复用(2)扩展)(3)is-a关系

3.多态:代码的灵活性

2.多态的作用:代码的灵活性
3.多态要求:

1.有继承关系

2.有方法重写

3.有父类的引用指向子类的实例

3.语法格式

父类 变量 = new 子类(【实参列表】);

本质上: 本质上:父类的引用指向了子类的对象,就是多态引用。

编译时类型:看父类
编译时,只能调用父类声明的方法,无法调用子类“扩展”的方法。
运行时类型:看子类
运行时:
如果子类重写了父类的方法,运行时执行的一定是子类重写的代码。
如果子类没有重写父类的方法,运行时执行的还是父类的方法。

public class TestPolymorphism {
    public static void main(String[] args) {
       Animal animal = new Animal();//本态引用
        Dog dog = new Dog();//本态引用

        Animal animal1 = new Dog();//多态引用*/

        Animal animal = new Animal();
        animal.eat();//吃东西

        Dog dog = new Dog();
        dog.eat();//啃骨头*/

        Animal animal = new Dog();
        animal.eat();//啃骨头
//        animal.watchHouse();//编译报错
    }
}
class Animal{
    public void eat(){
        System.out.println("吃东西");
    }
}
class Dog extends Animal{
    public void eat(){
        System.out.println("啃骨头");
    }
    public void watchHouse(){
        System.out.println("看家");
    }
}
4.多态的应用之一:多态数组

数组的元素类型是父类的类型,数组中存储的元素对象是子类的对象。
我们可以使用父类类型的数组统一管理它各种子类的对象。

举例:
声明一个图形类Graphic,包含一个方法:public double area(),返回0.0
声明它的两个子类,圆Circle和矩形Rectangle,重写area()
用一个数组存储多个的图形对象(可能有圆,可能有矩形),并且要遍历他们的面积。

public class TestUse1 {
    public static void main(String[] args) {
        Circle c1 = new Circle(2.5);
        Circle c2 = new Circle(4);

        Rectangle r1 = new Rectangle(2,6);
        Rectangle r2 = new Rectangle(1,3);

        Circle[] arr1 = new Circle[4];//不行,只能装Circle对象
        arr1[0] = c1;
        arr1[1] = c2;
//        arr1[2] = r1;//矩形的对象是不能赋值给Circle类型的元素
//        arr1[3] = r2;

        Graphic[] arr = new Graphic[4];
        arr[0] = c1; //左边的arr[0]的类型是Graphic,右边的c1是Circle对象
                    //隐含了  Graphic  arr[0] =  new Circle(2.5);
        arr[1] = c2;
        arr[2] = r1;
        arr[3] = r2;

        for(int i=0; i<arr.length; i++){
            System.out.println(arr[i].area());
            /*
            编译期间,arr[i]是Graphic类,看能不能通过编译,是看Graphic中是否有一个无参的area()方法。
            运行期间:arr[i]的运行时类型是什么,就执行谁的area()方法
                        arr[0]是Circle,运行的是Circle里面重写的area();
                        arr[2]是Rectangle,运行的是Rectangle里面重写的area();
             */
        }

        //按照对象的面积排序
        for(int i=1; i<arr.length; i++){
            for(int j=0; j<arr.length-i; j++){
                if(arr[j].area() > arr[j+1].area()){
                    //交换arr[j]与arr[j+1]
                    Graphic temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }
        }

        System.out.println("排序后:");
        for(int i=0; i<arr.length; i++) {
            System.out.println(arr[i].area());
        }
    }
}

class Graphic {
    public double area() {
        return 0.0;
    }
}

class Circle extends Graphic {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    public double getRadius() {
        return radius;
    }

    public void setRadius(double radius) {
        this.radius = radius;
    }

    @Override
    public double area() {
        return Math.PI * radius * radius;
    }
}

class Rectangle extends Graphic{
    private double length;
    private double width;

    public Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    public double getLength() {
        return length;
    }

    public void setLength(double length) {
        this.length = length;
    }

    public double getWidth() {
        return width;
    }

    public void setWidth(double width) {
        this.width = width;
    }

    @Override
    public double area() {
        return length * width;
    }
}
5.多态的应用之二:多态参数,多态形参和实参

形参是父类的类型
实参是子类的对象

public class TestUse2 {
    public static void main(String[] args) {
        meet(new Chinese());//实参给形参赋值   形参 Person person = new Chinese();实参
        meet(new American());
        meet(new Thai());
    }

    //重载
    /*public static void meet(Chinese chinese){
        chinese.welcome();
    }
    public static void meet(American american){
        american.welcome();
    }
    public static void meet(Thai thai){
        thai.welcome();
    }*/
    public static void meet(Person person){
        person.welcome();
    }
}
class Person{
    public void welcome(){
        System.out.println("微笑");
    }
}
class Chinese extends Person{
    @Override
    public void welcome() {
        System.out.println("你好");
    }
}

class American extends Person{
    @Override
    public void welcome() {
        System.out.println("hello");
    }
}

class Thai extends Person{
    @Override
    public void welcome() {
        System.out.println("sawadika");
    }
}
6.多态的应用之三:多态返回值

方法的返回值类型是父类的类型,
实际返回的对象是子类的对象。

public class TestUser3 {
    public static void main(String[] args) {
        Car car = buy("宝马");//左边是Car car,右边返回的对象是new BMW()
        car.drive();
    }

    public static Car buy(String info){
        if("宝马".equals(info)){
            return new BMW();  
        }else if("奔驰".equals(info)){
            return new Benz();
        }else{
            return new Car();
        }
    }
}
class Car{
    public void drive(){
        System.out.println("~~~~");
    }
}
class BMW extends Car{
    @Override
    public void drive() {
        System.out.println("宁可坐在宝马车上哭,也不坐在自行车上笑,宝马车嘟嘟嘟~~~");
    }
}
class Benz extends Car{
    @Override
    public void drive() {
        System.out.println("坐在引擎盖上哭,奔驰车滴滴滴~~~");
    }
}
7.类型转换

引用数据类型,而且要求是父子类关系,包括爷爷和孙子类。
(1)向上转型:up-casting
当我们把子类的对象/变量 赋值给父类的变量时,就会“自动”发生向上转型。

(2)向下转型:down-casting
当我们把父类的变量 赋值给子类的变量时,就需要“强制”类型转换,称为向下转型。

注意:无论是向上还是向下都只针对“编译时”类型。而“运行时”类型从头到尾都不会改变。

影响:
编译时:向上转型之后,只能看到父类声明的成员,无法访问子类扩展的成员(不管是成员变量还是成员变量)

向上和向下转型,编译和运行能否通过:
(1)编译通过:只要有父子类关系即可,符合语法格式
(2)运行通过:
向上转型:= 右边的对象的运行时类型 小于等于 =左边的变量的类型即可
向下转型:= 右边的变量中的对象的运行时类型 小于等于 = 左边的变量的类型即可

举例:
    Animal a1 = new Dog();   =右边的运行时类型是Dog 小于  =左边的变量的类型Animal
    Dog dog = (Dog)a1;       =右边变量a1中的对象的运行时类型Dog 等于  =左边的变量的类型Dog

    Animal a6 = new TaiDiDog();    =右边的运行时类型是TaiDiDog  小于  =左边的变量的类型Animal
    Dog dog4 = (Dog) a6;           =右边变量a6中的对象的运行时类型TaiDiDog 小于  =左边的变量的类型Dog

    Animal a4 = new Animal();  =右边的运行时类型是Animal 等于  =左边的变量的类型Animal
    Dog dog2 = (Dog)a4;        =右边变量a4中的对象的运行时类型Animal 大于  =左边的变量的类型Dog(错误)

    Animal a5 = new Cat();      =右边的运行时类型是Cat  小于  =左边的变量的类型Animal
    Dog dog3 = (Dog) a5;        =右边变量a5中的对象的运行时类型Cat  和  =左边的变量的类型Dog  无大小关系(错误)

9、如何保证向下转型,编译通过,运行就安全呢?
可以使用instanceof进行判断,来避免ClassCastException异常

语法格式:
对象/变量 instanceof 类型

该条件要编译和运行返回true的前提是:
(1)对象/变量的编译时类型和instanceof 后面的类型必须是父子类关系
(2)对象/变量的运行时类型 <= instanceof 后面的类型

public class TestClassCast {
    public static void main(String[] args) {
        Animal a1 = new Dog(); //a1从Dog类型向上转型为Animal,当然只发生在“编译时”
        a1.eat();
//        a1.watchHouse();//编译时,按照Animal处理

        Dog dog = (Dog)a1;//a1从Animal类型向下转型为Dog,然只发生在“编译时”
        dog.watchHouse();//编译时,按照Dog处理

        System.out.println("--------------------------------------");

//        Animal a2 = "hello"; //左边a2是Animal,右边"hello"是String类型,它俩之间没有父子类关系。所以无法向上转型/向下转型
//        Animal a3 = (Animal)"hello";

        System.out.println("--------------------------------------");
        Animal a4 = new Animal();//本态引用,没有类型转换
       // Dog dog2 = (Dog)a4; //编译时,把Animal类型的a4,转为了子类Dog类型。编译通过。  父类转为子类类型OK。
        //上面的代码编译通过,因为它符合向下转型的语法,但是运行时报错:ClassCastException,类型转换异常。
        //因为a4的运行时类型是Animal,它是不能被强制转换为Dog类型。
        //反证法
      //  dog2.watchHouse();//假设上面的代码成功,该句代码就可以执行,但是通过dog2中记录对象的地址是无法找到watchHouse(),因为dog2中存储的是Animal的对象

        System.out.println("--------------------------------------");
        Animal a5 = new Cat();
       // Dog dog3 = (Dog) a5;//编译时,把Animal类型的a5,转为了子类Dog类型。编译通过。  父类转为子类类型OK。
        //上面的代码编译通过,因为它符合向下转型的语法,但是运行时报错:ClassCastException,类型转换异常。
        //因为a5的运行时类型是Cat,它是不能被强制转换为Dog类型。
       // dog3.watchHouse();

        System.out.println("--------------------------------------");
        Animal a6 = new TaiDiDog();//向上转型
        Dog dog4 = (Dog) a6;//向下转型  编译时类型,a6是Animal类型,dog4是Dog
        dog4.watchHouse();


        System.out.println("--------------------------------------");
        Animal a7 = new Animal();
        Animal a8 = new Dog();
        Animal a9 = new Cat();
        Animal a10 = new TaiDiDog();

        System.out.println(a7 instanceof  Dog);//如果是true,说明a7可以强制为Dog   false
        System.out.println(a8 instanceof  Dog);//如果是true,说明a7可以强制为Dog   true
        System.out.println(a9 instanceof  Dog);//如果是true,说明a7可以强制为Dog   false
        System.out.println(a10 instanceof  Dog);//如果是true,说明a7可以强制为Dog  true

        if(a7 instanceof Dog){
            Dog dog5 = (Dog) a7;
            System.out.println("a7被向下转型为Dog");
        }
        if(a8 instanceof Dog){
            Dog dog6 = (Dog) a8;
            System.out.println("a8被向下转型为Dog");
        }
    }
}
class Animal{
    public void eat(){
        System.out.println("吃");
    }
}
class Dog extends Animal{
    public void eat(){
        System.out.println("啃骨头");
    }
    public void watchHouse(){
        System.out.println("看家");
    }
}
class TaiDiDog extends Dog{

}
class Cat extends Animal{

}
8、多态引用时,关于成员变量的引用规则

是哪个成员变量的值,只看编译时类型。

9、多态引用时,对成员方法的引用规则
1、类中对成员变量的引用分为虚方法和非虚方法

虚方法:能够被重写的方法
非虚方法:除了虚方法,剩下的都是非虚方法
非虚方法包括:静态方法、私有方法、final的方法、实例初始化方法(),super.方法等

2、非虚方法,只看编译时类型
1.public class TestExer1 {
    public static void main(String[] args){
        Animal a = new Cat();
        a.eat();//吃鱼
    }
}
class Animal {
    public void eat(){
        System.out.println("吃");
    }
}
class Cat extends Animal {
    public void eat() {
        System.out.println("吃鱼");
    }
}

class Dog extends Animal {
    public void eat() {
        System.out.println("吃骨头");
    }
}

2.public class TestExer2 {
    public static void main(String[] args) {
        Father f = new Father();
        Father s = new Son();
        Father d = new Daughter();

        MyClass my = new MyClass();
        my.method(f); //f的编译时类型是Father
        /*
        method方法是虚方法。
        my的编译时类型是MyClass,运行时类型也是MyClass。
        (1)编译时,看父类MyClass,在MyClass中找,实参与形参最匹配的方法。
              实参与形参匹配时,原则上只看编译时类型。f的编译时类型是Father
              public void method(Father f) {
                System.out.println("father");
            }
         (2)运行时,看子类,但是现在没有MyClass子类,即my这个变量的编译时和运行时都是MyClass
            执行的还是刚刚在MyClass中找到的方法
              public void method(Father f) {
                    System.out.println("father");
            }
         */
        my.method(s);
                /*
        method方法是虚方法。
        my的编译时类型是MyClass,运行时类型也是MyClass。
        (1)编译时,看父类MyClass,在MyClass中找,实参与形参最匹配的方法。
              实参与形参匹配时,原则上只看编译时类型。s的编译时类型是Father
              public void method(Father f) {
                System.out.println("father");
            }
         (2)运行时,看子类,但是现在没有MyClass子类,即my这个变量的编译时和运行时都是MyClass
            执行的还是刚刚在MyClass中找到的方法
              public void method(Father f) {
                    System.out.println("father");
            }
         */
        my.method(d);

                        /*
        method方法是虚方法。
        my的编译时类型是MyClass,运行时类型也是MyClass。
        (1)编译时,看父类MyClass,在MyClass中找,实参与形参最匹配的方法。
              实参与形参匹配时,原则上只看编译时类型。d的编译时类型是Father
              public void method(Father f) {
                System.out.println("father");
            }
         (2)运行时,看子类,但是现在没有MyClass子类,即my这个变量的编译时和运行时都是MyClass
            执行的还是刚刚在MyClass中找到的方法
              public void method(Father f) {
                    System.out.println("father");
            }
         */
    }
}
class MyClass{
    public void method(Father f) {
        System.out.println("father");
    }
    public void method(Son s) {
        System.out.println("son");
    }
    public void method(Daughter f) {
        System.out.println("daughter");
    }
}

class Father{

}
class Son extends Father{

}
class Daughter extends Father{

}
3、虚方法:可能被重写的方法

情况一:
看.前面的对象的类型,.前面的对象编译时类型和运行时类型一致的,只在一个类中查找,找到谁执行谁。

找对应方法时,实参和形参只看编译类型和谁最匹配,如果没有最匹配的,就找能兼容

情况二:
看.前面的对象的类型,.前面的对象编译时类型和运行时类型不一致的,
(1)编译时看父类,到父类中查找是否有该方法
(2)运行时看子类,执行子类重写的方法,如果子类没有重写,还是执行父类中找到的方法

例三:
public class TestVirtualMethod {
   public static void main(String[] args) {
       Father f = new Son();
       f.method();
   }
}

class Father{
   public void method(){
       System.out.println("父类的方法");
   }
}
class Son extends Father{
   public void method(){
       System.out.println("子类的方法");
   }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值