java疯狂讲义(二.对象与内存控制)

实例变量与类变量

class Test {
	int num1 = num2 + 2;
	static int num2 = 10;
}

类变量的初始化总是在实例变量之前

实例变量的初始化三种方式:

1.定义时指定

2.非静态初始化块中对实例变量指定

3.构造器中指定

定义变量时指定的初始值和初始化块指定初始值的执行顺序与他们在源程序中的排列顺序相同

父类构造器

当调用某个类得到构造器来创建java对象.系统总会先调用父类的非静态初始化块进行初始化,这个调用是隐式执行的,父类的静态初始化块总是会被执行,接着调用父类的构造器进行初始化.然后再调用本类的非静态初始化块,构造器进行初始化.

super与this

super调用用于显式调用父类构造器,this用于显式调用本类.super与this只能在构造器中使用,作为第一行代码.

访问子类对象的实例变量


/**
 * 
 */
package com.gsoft.geloin;

import junit.framework.TestCase;

import org.junit.Test;

/**
 * @author Geloin
 * 
 */
public class MyTest extends TestCase {

	@Test
	public void testArrays() throws Exception {
		new Derived();
	}
}

class Base {
	private int i = 2;

	public Base() {
		this.display();
	}

	public void display() {
		System.out.println(i);
	}
}

class Derived extends Base {
	private int i = 22;

	public Derived() {
		i = 222;
	}

	public void display() {
		System.out.println(i);
	}
}
  Derived继承Base,按照JVM规则,调用new Derived()时,系统开始为这个Derived对象分配内存空间,此时,Derived对象有两个i实例变量,其中一个属于base类i的实例变量,一个属于derived的i的实例变量,都为0.

程序在执行Derived的构造器之前,会首先执行Base的构造器,于是执行以下两行代码:

        i = 2;
        this.display();

当this在构造器中时,this代表正在初始化的Java对象。此时的情况是:此时的this位于Base构造器内,但目前正在初始化的Java对象是Derived——是Derived()构造器隐式调用了Base()构造器的代码。由此可见,此时的this应该代表Derived对象。


既然this代码Derived对象,那我们再改写Base构造子,将它变更成以下代码:

	public Base() {
		System.out.println(this.getClass());
		System.out.println(this.i);
		System.out.println(this.getClass());
		this.display();
	}

再次运行单元测试代码,发现结果如下所示:

class com.gsoft.geloin.Derived
2
class com.gsoft.geloin.Derived
0

既然this,表示的是Derived,那么为什么打印this.i时,显示的是2呢?

编辑时和运行时。编译时顾名思义就是正在编译的时候.而所谓的编译,就是编译器帮你把源代码翻译成机器能识别的代码,简单来说,编译时就是简单的作一些翻译工作;所谓运行时就是代码跑起来了.被装载到内存中去了。

当变量的编辑时类型和运行时类型不同时,通过该变量访问它引用的对象的实例变量时,该实例变量的值由声明该变量的类型决定。但通过该变量调用它引用的对象的实例方法时,该方法行为将由它实际所引用的对象决定。

在一个构造子中,调用this.变量名时,无论如何,这个this都是指它所在的类;当调用this.方法时,这个this则表示它实际上的类型。如上代码所示,调用this.i时,取得的是Base类中被初始化的i,而调用this.display()时,调用的则是Derived的display方法。

父子实例的内存控制

package qianglih;

public class FieldAndMethodTest {

	public static void main(String[] args) {
		//声明并创建一个Base对象
		Base b =new Base();//1
		//直接访问count实例变量和通过display访问count实例变量
		System.out.println(b.count);
		b.display();
		//声明并创建一个Derived对象
		Derived d=new Derived();//2
		//直接访问count实例变量和通过display访问count实例变量
		System.out.println(d.count);
		d.display();
		//声明一个Base变量,并将Derived对象赋值给该变量
		Base bd=new Derived();//3
		System.out.println(bd.count);
		bd.display();
		//让d2b变量指向原d变量所指向的Derived对象
		Base d2b =d;//4
		System.out.println(d2b.count);
		d2b.display();
		System.out.println(d2b ==d);
		}

}
class Base{
	int count = 2;
	public void display(){
		System.out.println(this.count);
	}
}
class Derived extends Base{
	int count = 20;
	public void display(){
		System.out.println(this.count);
	}
}

结果:

2
2
20
20
2
20
2
20
true


d2b ==d返回true,两个指向同一个对象的变量,分别访问他们的实例变量时却输出不同的值,这表明d2b、d变量所指向的java对象中包含了两块内存,分别存放值为2的count实例和值为20的count的实例变量。

因为继承成员变量和继承方法之间存在差别,对于一个引用类型的变量来说,当通过该变量访问所引用对象的实例变量时.该实例变量的值取决于生命该变量时的类型,当通过变量调用它所引用对象方法时,方法行为取决于所实际引用对象的类型


内存中子类实例
class Fruit{
    String color="未确定颜色";
    
    public Fruit getThis(){
        return this;
    }
    
    public void info(){
        System.out.println("Fruit方法");
    }
    
}

public class JavaTest extends Fruit{
    
    @Override
    public void info() {
        System.out.println("JavaTest方法");
    }
    
    public void AccessSuperInfo(){
        super.info();
    }
    
    public Fruit getSuper(){
        return super.getThis();
    }
    
    String color="红色";
    
    public static void main(String[] args) {
        JavaTest javaTest=new JavaTest();
        Fruit f=javaTest.getSuper();
        
        System.out.println("javaTest和f所引用的对象是否相同:"+(javaTest==f));
        System.out.println("所引用对象的color实例变量:"+javaTest.color);
        System.out.println("所引用对象的color实例变量:"+f.color);
        
        javaTest.info();
        f.info();
        javaTest.AccessSuperInfo();
    }
    
}

super关键字本身不能引用任何对象

所以程序结果:

true

红色

未确定颜色

apple方法

apple方法

fruit方法

通过javaTest对象的getSuper方法返回的实际是该对象本身,只是他的声明类型fruit,该实例变量的值由fruit类决定,但通过f变量调用info()方法时,该方法的行为由f变量实际所引用的java对象决定.

综上:当程序创建一个子类对象时,系统不仅会为该类中定义的实例变量分配内存,也会为父类定义的所有实例变量分配内存

final修饰符

final可以修饰变量,方法,类

final初始化实例变量的三种方法:

1.定义时指定初值

2.非静态初始化块中为final实例变量指定初值

3.构造器中为实例变量指定初值

final初始化类变量的两种方法

1.定义时指定初值

2.非静态初始化块中为final实例变量指定初值

final的宏变量

当定义final变量时就为该变量指定了初始值,而且该初始值可以在编译时就确定下来,那么这个final变量本质上就是一个“宏变量”,编译器会把程序中所有用到该变量的地方直接替换成该变量的值。

package cn.lsl;

public class FinalTest {
    public static void main(String[] args){
        final String name = "小明" + 22.0;
        final String name1 = "小明" + String.valueOf(22.0);
        System.out.println(name == "小明22.0");//true
        System.out.println(name1 == "小明22.0");//false
    }
}

final String name1 = "小明" + String.valueOf(22.0);中调用了String类的方法,因此编译器无法再编译的时候确定name1的值,所以name1不会被当成“宏变量”。

package cn.lsl;

public class FinalTest {
    public static void main(String[] args){
        String s1 = "小明";
        String s2 = "小" + "明";
        System.out.println(s1 == s2);    //true
        
        String str1 = "小";
        String str2 = "明";
        String s3 = str1 + str2;
        System.out.println(s1 == s3);        //false
        
        //宏替换
        final String str3 = "小";
        final String str4 = "明";
        String s4 = str3 + str4;
        System.out.println(s1 == s4);        //true
    }
}

1.java会使用常量池管理曾经使用过的字符串直接量。String a = "hello";  那么字符串池中会缓存一个字符串"hello",当执行String b = "hello";会让b直接指向字符串池中的"hello"字符串。所以a==b返回true。
2.String s3 = str1 + str2;由于str1,str2只是两个普通变量,编译器不会执行“宏替换”,因此编译器无法在编译时确定s3的值。
3.String s4 = str3 + str4;因为执行了宏替换,所以在编译的时候就已经确定了s4的值。



猜你喜欢

转载自blog.csdn.net/hewenjing8168/article/details/80000363