疯狂Java笔记之对象及其内存管理

(复习疯狂Java的笔记)

1.实例变量和类变量

Java程序的变量大体可分为成员变量和局部变量。其中局部变量可分为如下二类。

  • 形参:在方法签名中定义的局部变量,由方法调用者负责为其赋值,随方法的结束而消亡。
  • 方法内的局部变量:在方法内定义的局部变量,必须在方法内对其进行显式初始化口这种类型的局部变量从初始化完成后开始生效,随方法的结束而消亡。
  • 代码块内的局部变量:在代码块内定义的局部变量,必须在代码块内对其进行显式初始化。这种类型的局部变量从初始化完成后开始生效,随代码块的结束而消亡。

局部变量的作用时间很短暂,他们都被存储在栈内存中。

类体内定义的变量被称为成员变量〔英文是Field)。如果定义该成员变量时没有使用static
修饰,该成员变量又被称为非静态变量或实例变量;如果使用了static修饰,则该成员变量又可被称为静态变量或类变量

(坑:表面上看定义成员变量是没有先后顺序的,实际上还是要采用合法的前向引用)如:

int num=num2+1;
int num2=2;

是会报错的,出得num2位静态比变量的时候。

2.实例变量和类变量的属性

使用static修饰的成员变量是类变量,属于该类本身:没有使用属于该类的实例。在同一个JVM内,侮个类只对应一个
Java对象口static修饰的成员变量是Class对象,但侮个类可以创建多个

由于同一个JVM内每个类只对应一个static对象,因此同一个JVM内的一个类的类变量只需一块内存空间;但对于实例变量而言,改类每创建一次实例,就需要为实例变量分配一块内存空间。也就是说,程序中有几个实例,实例变量就需要几块内存空间。

3.实例变量的初始化时机

对于实例变量,它是Java对象本身。每创建Java对象时都需要为实例变量分配内存空间,并对实例进行初始化。
程序可以在三个地方进行初始化:

  • 定义实例变量时指定初始值。
  • 非静态初始化块中对实例变量指定初始值。
  • 构造器中对实例变量指定初始值。
    其中第1,2种方式都比在构造器初始化更早执行,当第1,2种的执行顺序与他们在源程序中的排列顺序相同。

4.类变量的初始化时机

类变量是属于Java类本身。从程序运行的角度来看,每个jvm对一个Java类只初始化一次,因此只有每次运行Java程序时,才会初始化该Java类,才会为该类的类变量分配内存空间,并执行初始化。

程序可以在两个地方对类变量执行初始化:

  • 定义类变量时指定初始值。
  • 静态初始化块中对类变量指定初始值。

这两种方式的执行顺序与它们在源程序中的排列顺序相同。

父类构造器

1.隐式调用和显式调用

当创建Java对象时,系统会先调用父类的非静态初始化块进行初始化。而这种调用是隐式调用。而第一次初始化时最优先初始化的是静态初始化块。接着会调用父类的一个或多个构造器进行初始化,这个调用是用过super()的方法来显式调用或者隐式调用。当所有父类初始化完之后才初始化子类。实例代码如下:

class Animal{
    
    static{
        System.out.println("Animal静态初始化块");
    }
    
    {
       System.out.println("Animal初始化块");
    }
        
    public Animal(){
        System.out.println("Animal构造器");
    }
    
    
}

class Cat extends Animal{
    public Cat(String name,int age){
        super();
        System.out.println("Cat构造器");
    }
    
    static{
        System.out.println("Cat静态初始化块");
    }
    
    {
        System.out.println("Cat初始化块");
        weight=2.0;
    }
    
    double weight=2.3;

    public String toString(){
        return "weight="+weight;
    }
    
}

public class JavaTest {

    public static void main(String[] args) {
        
        Cat cat=new Cat("kitty",2);
        System.out.println(cat);
//      Cat cat2=new Cat("Garfied",3);
//      System.out.println(cat2);
    }
    
}

输出的结果是:

java.PNG

2访问子类对象的实例变量

子类因为继承父类所以可以访问父类的成员方法和变量,当一般情况下父类是访问不了子类的,因为父类不知道哪个子类继承。但是在特殊情况下是可以的,如下代码:

class BaseClass{
    private int i=2;
    public BaseClass(){
        this.display();
    }
    public void display(){
        System.out.println("BaseClass");
        System.out.println(i);
    }
    
}

class Derived extends BaseClass{
    private int i=22;
    public Derived(){
        i=222;
    }
    public void display(){
        System.out.println("Derived");
        System.out.println(i);
    }
    public void sub(){
        System.out.println("sub");
    }
}

public class JavaTest {
    public static void main(String[] args) {
        Derived derived=new Derived();
    }
}

结果如下:

java2.PNG

仔细看代码,好像怎么也不会输出0吧,为什么呢。

首先我们要知道Java构造器只是起到对变量进行初始化的作用,而在执行构造器之前我们的对象已经初始化了,在内存中已经被分配起来了,而这些值默认是空值。

其次this在代表正在初始化的对象,一般看会以为就是BaseClass对象,不过在上面代码里,this是放在BaseClass的构造器里,当时我们是在Derived()构造器执行的,是Derived()构造器隐式调用了BaseClass()构造器的代码,所以在这个情况下是this是Derived对象。所以当我们改为this.sub()时是报错的。

此外这个this的编译类型是BaseClass,所以我们改为this.i的时候输出是2.

所以应该避免在父类构造器中调用被子类重写的方法。

父子实例的内存控制

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

class Animal{
    public String name="Animal";
    
    public void sub(){
        System.out.println("AnimalSub");
    }
    
}
class Wolf extends Animal{
    public String name="Wolf";

    public void sub(){
        System.out.println("WolfSub");
    }
}

public class JavaTest {
    public static void main(String[] args) {
        Animal animal=new Animal();
        System.out.println(animal.name);
        animal.sub();
        Wolf wolf=new Wolf();
        System.out.println(wolf.name);
        wolf.sub();
        Animal sub=new Wolf();
        System.out.println(sub.name);
        sub.sub();
    }
}

结果如下:

image.png

所以当声明类型为父类,运行类型为子类是,成员变量表现出父类,而方法表现出子类,这就是多态。

2.内存中的子类实例

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();
    }
    
}

当创建一个对象时,系统不仅为该类的实例变量分配内存,同时也为其父类定义的所有实例变量分配内存,即是子类定义了与父类同名的实例变量。也就是说,当系统创建一个Java对象时,如果该Java类有两个父类(一个直接父类A,一个间接父类g ),假设A类中定义了2个实例变量,B类
中定义了3个实例变量,当前类中定义了2个实例变量,那么这个Java对象将会保存2+3十2个实例变量。

如果子类里定义了与父类中已有变量同名的变量,那么子类中定义的变量会隐藏父类中定义的变量,而不是覆盖。因此系统创建子类对象是依然会为父类定义的,被隐藏的变量分配内存空间。

为了在子类中访问父类定义的,被隐藏的变量和方法,可以使用super来限定修饰这些变量和方法。

3.父,子类的类变量

如果在子类中要访问父类中被隐藏的静态变量和方法,程序有两种方式:

  • 直接使用父类的类名作为主调来访问类变量
  • 使用super.作为限定来访问类变量

一般情况下,都建议使用第一种方式访问类变量,因为类变量属于类本身,使用类名做主调来访问可以较好的可读性

final修饰符

1.final 修饰的变量

final修饰的实例变量必须显示指定初始值,只能在如下三个位置指定初始值。

  • 定义final实例变量时指定初始值
  • 在非静态初始化块中为final实例变量指定初始值
  • 在构造器中为final实例变量指定初始值

对于普通实例java可以指定默认初始化,而final实例变量只能显示指定初始化。

2.执行‘宏替换’的变量

在定义时final类变量指定了初始值,该初始值在编译时就被确定下来,这个final变量本质上已经不再是变量而是一个直接量,如果被赋的表达式只是基木的算术表达式或字符串连接运算,没有访问普通变量,调用方法,Java编译器同样会将这种final变量当成“宏变量”来处理。

3.final方法不能重写

如果父类中某个方法使用了final修饰符进行修饰,那么这个方法将不可能被他的子类访问到,因此这个方法也不可能被他的子类重写。从这个层面说,private和final同时修饰某个方法没有太大的意义,但是被java语法允许。

4.内部类中的局部变量

Java要求所有被内部类访问的局部变量都使用final修饰也是有其原因的。对于井通的局部变量而言,‘它的作用域就停留在该方法内,当方法执行结束后,该局部变量也随之消失;但内部类则可能产生隐式的“闭包(Closure)”,闭包将使得局部变量脱离它所在的方法继续存在。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容