java对象和变量踩坑集合

java对象和变量

static修饰符

由static修饰的变量称为静态变量或者类变量,当类被初始化时候就会被创建。这里通过一个例子来说明他的特点。

public class Test {
    int i=j+1;     //非法前向引用
    int j=2;
}

当我们编写如上的代码时候,编译器会提示非法的前向引用,是因为i被初始化的时候还找不到j的值,但是我们将j改为static类型的值时候,编译顺利通过,同时运行也没有问题。

public class Test {
    int i=j+1;
    static int  j=2;
}

这里可以说明j是在类初始化的时候就会进行初始化。
同时static当类被装载时候被初始化,同时只会初始化一次,一旦被初始化,就会一直存在于内存中。

父类构造器

我们知道当一个类继承于其他的类时候,当子类被调用时候,如果不指定父类的构造函数类型,将会隐式的调用父类的无参构造方法,但是我们可以通过显式调用super指定父类应该执行的构造函数。这里通过一个例子来说明这种关系。

public class One {  //基类函数构造分为有参和无参
    public One(){
        Log.e("Test","one函数无参");
    }
    public One(int i){
        Log.e("Test","one函数有参");
    }
}
public class Two extends One {
    public Two(){
        Log.e("Test","two函数无参");
    }
    public Two(int i){
        Log.e("Test","two函数有参");
    }

}

这里Two继承于One,同时也实现了有参和无参构造方法。

public class Three extends Two {
    public Three(){
        super(2);
        Log.e("Test","Three函数构造");
    }
}

Three类继承于Two,我们实例化Three,这里通过super指定调用Two的有参构造函数,我们看下打印的log
E/Test:one函数无参
E/Test: two函数有参
E/Test: Three函数构造

由于Three继承于Two,Two继承于One,又由于Two并没有指定One中应该执行什么构造函数,所以最先执行的是One的无参构造,Three类中显式调用指定了执行two类中的有参构造,所以接着执行two的有参构造,最后再是Three的构造函数。

编译时类型和运行时候类型
public class One {
    private int i=2;
    One(){
        this.cout();
    }
    public void cout(){
        Log.e("Test","I的值:"+i );
    }
}
public class Two extends One {
    private int i=22;
    public void cout(){
        Log.e("Test","I的值:"+i );
    }
}

这里我们同样通过一个例子来进行探究,类One 和类Two同样都拥有一个变量i,同时在One的构造函数中,调用cout函数。实例化Two,会发现输出的是0。为什么不是2,或者22.
实际上构造函数仅仅是为类中的变量进行初始化的操作,类不是由构造器进行构造的,当我们进行new操作的时候,Two类中的内存空间都被划分了出来,里面的变量都是空值,例如上列中的i,由于还没有执行构造,所以值为0。
但是问题又来了,既然是Two中的i没有值,那为什么调用父类的cout函数会定位到Two的函数中,这里面牵扯到运行时变量和编译时候变量问题。
我们将上面的代码更改一下,如下,将Two添加函数new,在One中我们调用this.new(),发现编译器找不到这个函数,说明在编译时候这个this是One类型的,同样我们通过打印
this.getClass()执行可以发现这个class是Two。

所以This指针当在编译时候是指向当前的类,但是在运行中是会指向当前正在初始化的类。

public class One {
    private int i=2;
    One(){
        this.cout();
        this.new();
    }
    public void cout(){
        Log.e("Test","I的值:"+i );
    }
}
public class Two extends One {
    private int i=22;
    public void cout(){
        Log.e("Test","I的值:"+i );
    }
    public void new(){
    }
}

这里我们在对这个类进行修改,

public class One {
    private int i=2;
    One(){
        Log.e("Test","I的值:"+this.i);//打印this.i
        this.cout();
    }
    public void cout(){
        Log.e("Test","I的值:"+i );
    }
}

我们子啊调用this.cout()前打印this.i,这里我们可以看到执行的结果变为了2,0,不是说运行时候的this会指向子类吗,这里不是应该为0吗,但是这里是this.i是一个基本类型的变量,当我们通过this调用一个函数类型的时候,将会指向其实际引用的对象(这里是Two),但是当我们调用一个实例变量的时候,将会由声明其实例变量的对象决定。(这里是One,其在One中声明)

还记得上面说过了static修饰得变量将会在类被初始化时候就会被初始化,这里我们将Two修改一下。

public class Two extends One {
    private static int i=22;
    public void cout(){
        Log.e("Test","I的值:"+i );
    }
}

这样的话,输出的值就会正常的变为22。

继承成员变量和继承方法的区别

public class One {
    int i=2;
    public void cout(){
        Log.e("Test","I的值:"+i );
    }
}
public class Two extends One {
    int i=22;

    public void cout(){
        Log.e("Test","I的值:"+i );
    }
}

依次执行下列代码

Two two=new Two();
Log.e("Test","I的值:"+two.i);
Log.e("Test","I的值:"+two.cout);
One one=new Two();
Log.e("Test","I的值:"+one.i);
Log.e("Test","I的值:"+one.cout);
One oneTwo=two;
Log.e("Test","I的值:"+oneTwo.i);
Log.e("Test","I的值:"+oneTwo.cout);

可以发现,Two two=new Two();中输出的值为22,22.
这个很正常。
One one=new Two();中我们发现输出的值依次为2,22.
从这里我们可以看出当我们执行继承时候,其中的变量值会以声明的变量中的值为准,但是函数又会拥有子类中的特性。
One oneTwo=two;
这里同样会输出2,22.但是two.i输出22,oneTwo.i输出2,但是他们又指向同一块内存,说明这个对象中同时存在了i=2,i=22的变量。

总结:实例化类中的变量会和声明的类保持一致,但是其中的函数会和继承的子类中函数一致。
从上面的例子可以看出来,为什么
One one=new Two();
Log.e(“Test”,”I的值:”+one.i);
Log.e(“Test”,”I的值:”+one.cout);
会输出两个不同的值,我们考虑他们在内存中的位置,发现当我们创建如上的类的时候,它不仅会初始化子类中的变量 ,同时会初始化父类中的变量,他们同时存在一个对象中,子类中的同名函数和同名的变量会对父类的变量和函数进行覆盖,当我们通过父类声明的时候,函数会调用子类的习惯,当我们直接通过父类调用该值时候,是调用父类的值。
当我们需要通过子类执行父类的方法或者变量时候,通过super来进行执行,但是我们发现不能执行return super。
说明super不是一个对象的引用,甚至不是一个变量,它仅仅用来调用父类的函数和变量。

final关键字

关于final关键字相信在实际运用中也是多次遇见了,被final关键字修饰有如下的性质
类:不能被继承(如String)
函数:不能被重写
变量:不能被修改

关于final的初始化

public class Two extends One {
    final int i;
    public void cout(){

    }
}

如上代码所示,如果普通变量会被编译器初始化,那么被final修饰的变量必须由程序员手动对其进行修饰,如上的代码就会提示i没有被初始化。
一个被final修饰的普通变量有如下的方式进行初始化。

  • 非静态初始化块中对其进行初始化。
  • 在构造函数中对其进行初始化。
  • 在定义时候直接对其进行初始化。

    值得一提的是,不论通过什么方式进行初始化,当程序编译执行后,都是在构造函数中对其进行初始化赋值。

一个被final修饰的static变量有如下的方式进行初始化。

  • 在静态初始化块进行初始化
  • 在声明时候进行初始化

    最后它们都会在静态初始化块处进行赋值。

猜你喜欢

转载自blog.csdn.net/zxc641483573/article/details/79012981
今日推荐