Java编程思想学习笔记_2(继承和多态)

时间:2023-02-25 18:46:06
  • 静态初始化:

   静态初始化只在必要的时刻进行.(即当程序需要加载类进入内存的时候,执行静态初始化.静态变量和静态代码块的初始化顺序,按照在代码中声明的顺序老执行.例如:如果要执行某个public类,那么首先需要加载进内存,这时候就开始静态初始化.随后将主函数加载进栈.静态初始化只在Class对象首次加载的时候执行一次.(对象不创建,非静态变量不初始化.类不加载,静态变量不初始化.下面是代码示例:

public class Test3 {
    public static void main(String[] args) {
        /*主函数中不声明任何语句静态变量也会初始化,因为主函数进栈就需要把类加载进内存.*/
           //ADog dog;            //声明引用,非静态变量也不会初始化
      //ADog dog3=new ADog();   //创建对象.执行非静态变量和构造函数的初始化    
    }
    static ADog dog1=new ADog();
    static ADog dog2=new ADog();
    static {
        System.out.println("汪汪!");
    }    /*静态代码块和静态变量初始化顺序看语句中的顺序.*/
}
class ADog {
    public ADog() {
        System.out.println("小狗狗在此!");
    }
}
  • 可变参数列表:

  参数列表中,采用参数类型加...代表数组的称为可变参数.例如:int... 可变参数可以接受任意大小的元素进入数组,编译器会为你去填充数组.例如:

  

f(int ... arr)        //f(1)OK  f(2,3,4)OK

  实际上arr也是数组,可以接受任意类型的数组.这里有几个特殊情况说明:

  1. 如果向可变参数列表中传入了一个数组,而不是一系列数组的元素,编译器会发现它已经是个数组了,所以不会在其上做任何转换.
    public class Test4 {
        public static void main(String[] args)  {
    //        
            Integer[] ins={1,2,3,4};
            f(ins);
        }
        static void f(Integer...arr) {
            for(int x:arr) {
                System.out.println(x);
            }
        }

    输出1,2,3,4

  2. 如果向一个Object参数的可变参数列表传入一个基本数据类型的数组,则会将整个数组引用当成一个Object,因此会输出数组的引用的toString.
    public class Test4 {
        public static void main(String[] args) {
            f((new int[]{1,2,3,4}));
        }
        static void f(Object...arr) {
            for(Object x:arr) {
                System.out.println(x);    //输出[I@15db9742
            }
        }

   3.可变参数列表也是存在自动装箱的,因此如果往Integer...可变参数列表中加入一系列int元素是可以的.但是如果在其中传入int[]元素,编译器在检查的时候会发现int[]元素无法装入Integer[]中,因此报错.

public class Test4 {
    public static void main(String[] args)  {
        f(1,2,3,4);        //Ok
        f(new int[]{1,2,3,4});    //Error
    }
    static void f(Integer...arr) {
        for(Object x:arr) {
            System.out.println(x);
        }
    }

  4.应该总是在重载方法的一个版本上使用可变参数,或者根本不使用它.因为可变参数有可能有的时候,表面看上去方法不同,但是在传入参数的时候,两个方法都可以调用.

public class Test4 {
    public static void main(String[] args) {
        f('a','b');        //编译错误.
    }
    static void f(float ... args) /*float也接受char类型.*/{
        System.out.println("first");
    }
    static void f(Character... args) {
        System.out.println("second");
    }
}

  这个特性显著不同于在方法中传递参数,传入的参数将优先寻找类型匹配的参数,但是在可变参数中,只要可接受,编译器就默认可以装入数组.容易造成方法调用的不确定性.

public class Test4 {
    public static void main(String[] args) {
        f('a');
    }
    static void f(float args)/*可接受char.但是优先匹配Character和char,然后是int,然后才是float*/{
        System.out.println("first");
    }
    static void f(Character args) {
        System.out.println("second");
    }
}

 

  • 在继承的时候确保正确清理

  在清理方法中,还必须注意对基类清理方法和成员对象清理方法的调用顺序,以防止某个子对象依赖于另一个子对象的情形的发生.一般而言,首先执行类的所有特定清理动作,其顺序同生成顺序相反(通常这要求基类元素仍然存活.然后再调用基类的清理方法.

  • final参数

  Java允许在参数列表中以声明的方式将参数声明为final.这意味着无法在方法中更改参数的值.(引用无法更改参数指向的对象).

  • final和private关键字

  类中所有的private方法都隐式的被指定为final的,由于无法取用private方法,所以也就无法覆盖它.可以给private方法添加final修饰词,但这并不能给方法增加额外的意义.覆盖只有在能将一个对象向上转型并调用它的方法的时候才会出现,如果方法是private,则它不是基类接口的一部分,仅仅是隐藏与类中的程序代码,当声明一个public protected和包访问权限的方法的话,该方法不会产生在基类中出现的仅仅具有相同名称的情况.此时并没有覆盖方法,仅仅是生成了一个新的方法.下面的代码说明了这个问题:

public class Dad {
    private void say() {
        System.out.println("I am Dad");
    }
    public static void main(String[] args) {
        Dad d=new Son();    
        //子类的Public访问权限的say相当于定义在子类的全新的方法,不符合动态绑定的情况,仍然使用父类的say方法
        d.say();//输出I am Dad
    }
}
class Son extends Dad{
    public void say() {
        //在子类中无法访问父类的private方法,private包装在子类对象内部的父类对象内,无法访问.
        //super.say();错误.无法访问,类的访问权限.
        System.out.println("I am son.");
    }
}
  • 初始化以及类的加载

  下面的代码提供了初始化的示例:

import static net.mindview.util.Print.*;
class Insect {
    private int i=9;
    protected int j;
    Insect() {
        print("i="+i+"j="+j);
        j=39;
    }
    private static int x1=printInit("static Insect.x1 initialized.");
    static int printInit(String s) {
        print(s);
        return 47;
    }
}
public class Beetle extends Insect{
    private int k=printInit("Bettle.k initialized");
    public Beetle() {
        print("k="+k);
        print("j="+j);
    }
    private static int x2=printInit("static Bettle.x2 initialized");
    public static void main(String[] args) {
        print("Beetle constructor:");
        Beetle b=new Beetle();
    }
}

  首先在Beetle上运行Java的时候,视图访问Beetle.main,于是加载器开始启动并找出Beetle类的编译代码,在对它进行加载的过程中,编译器注意到它有一个基类,于是它继续进行加载,不管是否打算产生一个基类的对象,这都会发生.

  接下来,根基类的static元素会被初始化,然后是下一个子类,以此类推.这种方式很重要,因为子类的static初始化可能会依赖于基类成员是否被正确初始化.至此,必要的类加载完毕,对象就可以创建了.首先对象中所有的基本类型都会设为默认值,对象的引用被设为null-然后基类的构造器会被调用,基类构造器和子类的一样,以相同的顺序经历相同的过程,在基类构造器完成后,实例变量按次序被初始化.最后,构造器的其余部分被执行.

  • 多态发生时的域

  只有普通的方法调用才可以是多态的.如果直接访问某个域,这个访问就在编译器开始进行解析:

class Super {
    public int field=0;
    public int getField() {
        return field;
    }
}
class Sub extends Super {
    public int field=1;
    public int getField() {
        return field;     //相当于return this.field.
    }
}
public class Test3 {
    public static void main(String[] args) {
        Super sup=new Sub();
        System.out.println("sup.field="+sup.field+",sup.getField="+sup.getField());  //sup.field=0,sup.getField=1
    }
}

  当Sub对象转型为Super引用的时候,任何域访问操作都将有编译器解析,因此不是多态的.Sub实际上包含两个称为field的域,它自己的和从Super处得到的.而在引用Sub中的field域时候所产生的默认域并非Super版本的field域.