java对象初始化代码详解

本文主要记录JAVA中对象的初始化过程,包括实例变量的初始化和类变量的初始化以及final关键字对初始化的影响。另外,还讨论了由于继承原因,探讨了引用变量的编译时类型和运行时类型

一,实例变量的初始化

这里首先介绍下创建对象的过程:

类型为Dog的一个对象首次创建时,或者Dog类的static字段或static方法首次访问时,Java解释器必须找到Dog.class(在事先设定好的路径里面搜索); 
找到Dog.class后(它会创建一个Class对象),它的所有static初始化模块都会运行。因此,static初始化仅发生一次——在Class对象首次载入的时候; 
创建一个newDog()时,Dog对象的构建进程首先会在内存堆(Heap)里为一个Dog对象分配足够多的存储空间; 
这种存储空间会清为零,将Dog中的所有基本类型(Primitive)设为它们的默认值(0用于数字,以及boolean和char的等价设定); 
进行成员字段定义时发生的所有初始化都会执行; 
执行构造函数。

然后,开始对实例变量进行初始化。一共有三种方式对实例变量进行初始化:

①定义实例变量时指定初始值

②非静态初始化块中对实例变量进行初始化

③构造器中对实例变量进行初始化

当new对象初始化时,①②要先于③执行。而①②的顺序则按照它们在源代码中定义的顺序来执行。

当实例变量使用了final关键字修饰时,如果是在定义该final实例变量时直接指定初始值进行的初始化(第①种方式),则:该变量的初始值在编译时就被确定下来,那么该final变量就类似于“宏变量”,相当于JAVA中的直接常量。

public class Test {
  public static void main(String[] args) {
    final String str1 = "HelloWorld";
    final String str2 = "Hello" + "World";
    System.out.println(str1 == str2);//true

    final String str3 = "Hello" + String.valueOf("World");
    System.out.println(str1 == str3);//false
  }
}

第8行输出false,是因为:第7行中str3需要通过valueOf方法调用之后才能确定。而不是在编译时确定。

再来看一个示例:

public class Test {

  final String str1 = "HelloWorld";
  final String str2 = "Hello" + "World";
  final String str3;
  final String str4;
  {
    str3 = "HelloWorld";
  }
  {
    System.out.println(str1 == str2);//true
    System.out.println(str1 == str3);//true
//    System.out.println(str1 == str4);//compile error
  }
  public Test() {
    str4 = "HelloWorld";
    System.out.println(str1 == str4);//true
  }

  public static void main(String[] args) {
    new Test();
  }
}

把第13行的注释去掉,会报编译错误“Theblankfinalfieldstr4maynothavebeeninitialized”

因为变量str4是在构造器中进行初始化的。而前面提到:①定义实例变量时直接指定初始值(str1和str2的初始化)、②非静态初始化块中对实例变量进行初始化(str3的初始化)要先于③构造器中对实例变量进行初始化。

另外,对于final修饰的实例变量必须显示地对它进行初始化,而不是通过构造器(<clinit>)对之进行默认初始化。

public class Test {
   final String str1;//compile error---没有显示的使用①②③中的方式进行初始化
   String str2;
 }

str2可以通过构造器对之进行默认的初始化,初始化为null。而对于final修饰的变量 str1,必须显示地使用 上面提到的三种方式进行初始化。如下面的这个Test.java(一共有22行的这个Test类)

public class Test {
  final String str1 = "Hello";//定义实例变量时指定初始值

  final String str2;//非静态初始化块中对实例变量进行初始化
  final String str3;//构造器中对实例变量进行初始化

  {
    str2 = "Hello";
  }
  public Test() {
    str3 = "Hello";
  }

  public void show(){
    System.out.println(str1 + str1 == "HelloHello");//true
    System.out.println(str2 + str2 == "HelloHello");//false
    System.out.println(str3 + str3 == "HelloHello");//false
  }
  public static void main(String[] args) {
    new Test().show();
  }
}

由于str1采用的是第①种方式进行的初始化,故在执行15行:str1+str1连接操作时,str1其实相当于“宏变量”

而str2和str3并不是“宏变量”,故16-17行输出false

在非静态初始化代码块中初始化变量和在构造器中初始化变量的一点小区别:因为构造器是可以重写的,比如你把某个实例变量放在无参的构造器中进行初始化,但是在new对象时却调用的是有参数的构造器,那就得注意该实例变量有没有正确得到初始化了。

而放在非静态初始化代码块中初始化变量时,不管是调用有参的构造器还是无参的构造器,非静态初始化代码块都会执行。

二,类变量的初始化

类变量一共有两个地方对之进行初始化:

❶定义类变量时指定初始值

❷静态初始化代码块中进行初始化

不管new多少个对象,类变量的初始化只执行一次。

三,继承对初始化的影响

主要是理解编译时类型和运行时类型的不同,从这个不同中可以看出this关键字和super关键字的一些本质区别。

class Fruit{
  String color = "unknow";
  public Fruit getThis(){
    return this;
  }
  public void info(){
    System.out.println("fruit's method");
  }
}

public class Apple extends Fruit{

  String color = "red";//与父类同名的实例变量

  @Override
  public void info() {
    System.out.println("apple's method");
  }

  public void accessFruitInfo(){
    super.info();
  }
  public Fruit getSuper(){
    return super.getThis();
  }

  //for test purpose
  public static void main(String[] args) {
    Apple a = new Apple();
    Fruit f = a.getSuper();

    //Fruit f2 = a.getThis();
    //System.out.println(f == f2);//true

    System.out.println(a == f);//true
    System.out.println(a.color);//red
    System.out.println(f.color);//unknow

    a.info();//"apple's method"
    f.info();//"apple's method"

    a.accessFruitInfo();//"fruit's method"
  }
}

值得注意的地方有以下几个:

⒈第35行引用变量a和f都指向内存中的同一个对象,36-37行调用它们的属性时,a.color是red,而f.color是unknow

因为,f变量的声明类型(编译时类型)为Fruit,当访问属性时是由声明该变量的类型来决定的。

⒉第39-40行,a.info()和f.info()都输出“apple'smethod”

因为,f变量的运行时类型为Apple,info()是Apple重载的父类的一个方法。调用方法时由变量的运行时类型来决定。

⒊关于this关键字

当在29行new一个Apple对象,在30行调用getSuper()方法时,最终是执行到第4行的returnthis

this的解释是:返回调用本方法的对象。它返回的类型是Fruit类型(见getThis方法的返回值类型),但实际上是Apple对象导致的getThis方法的调用。故,这里的this的声明类型是Fruit,而运行时类型是Apple

⒋关于super关键字

super与this是有区别的。this可以用来代表“当前对象”,可用return返回。而对于super而言,没有returnsuper;这样的语句。

super主要是为了:在子类中访问父类中的属性或者在子类中调用父类中的方法而引入的一个关键字。比如第24行。

⒌在父类的构造器中不要去调用被子类覆盖的方法(Override),或者说在构造父类对象时,不要依赖于子类覆盖了父类的那些方法。这样很可能会导致初始化的失败(没有正确地初始化对象)

因为:前面第1点和第2点谈到了,对象(变量)有声明时类型(编译时类型)和运行时类型。而方法的调用取决于运行时类型。

当new子类对象时,会首先去初始化父类的属性,而此时对象的运行时类型是子类,因此父类的属性的赋值若依赖于子类中重载的方法,会导致父类属性得不到正确的初始化值。示例如下:

class Fruit{
    String color;

    public Fruit() {
      color = this.getColor();//父类color属性初始化依赖于重载的方法getColor
//      color = getColor();
    }
    public String getColor(){
      return "unkonw";
    }

    @Override
    public String toString() {
      return color;
    }
  }

  public class Apple extends Fruit{

    @Override
    public String getColor() {
      return "color: " + color;
    }

//    public Apple() {
//      color = "red";
//    }

    public static void main(String[] args) {
      System.out.println(new Apple());//color: null
    }
  }

Fruit类的color属性 没有正确地被初始化为"unknow",而是为 null

主要是因为第5行 this.getColor()调用的是Apple类的getColor方法,而此时Apple类的color属性是直接从Fruit类继承的。

四,参考资料

疯狂Java 突破程序员基本功的16课  第二章

Effective Java中文版 第2版 中文 PDF版   第二版第17条

(0)

相关推荐

  • 浅谈java面向对象中四种权限

    俗话说没有规矩就没有方圆,java作为一门严谨的面向对象的高级编程语言,自然对权限整个重要的问题有严格的控制. Java中,可以通过一些Java关键字,来设置访问控制权限: 主要有 private(私有), package(包访问权限),protected(子类访问权限),public(公共访问权限) 在java里,这些语句都可以修饰类中的成员变量和方法,但是只有public和友好型可以修饰类.举个例子: 接下来就详细解释一下这几种权限的差别(博客最后有表格)按权限由低到高:(高权限有低权限所有

  • Java线程之锁对象Lock-同步问题更完美的处理方式代码实例

    Lock是java.util.concurrent.locks包下的接口,Lock 实现提供了比使用synchronized 方法和语句可获得的更广泛的锁定操作,它能以更优雅的方式处理线程同步问题,我们拿Java线程之线程同步synchronized和volatile详解中的一个例子简单的实现一下和sychronized一样的效果,代码如下: public class LockTest { public static void main(String[] args) { final Output

  • Java和C++通过new创建的对象有何区别?

    前言 本文我们不去谈int.float.char等基本数据类型,而是用一般的类来说明.因为Java中可以直接通过 int varName 的方式来定义和使用一个基本类型的变量,但对于其它一般类型的对象,必须使用 new 来创建. 因此,为了更一般性地分析,体现两种语言创建对象的差异,我们用自定义的类 Student 进行说明,以下内容均针对一般的类而言. Java 在 Java 中,我们可以通过如下方式定义变量: Student s; //定义标识符s,没有实际空间 Student s = ne

  • 将Java对象序列化成JSON和XML格式的实例

    1.先定义一个Java对象Person: public class Person { String name; int age; int number; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age =

  • 详谈Java中net.sf.json包关于JSON与对象互转的坑

    在Web开发过程中离不开数据的交互,这就需要规定交互数据的相关格式,以便数据在客户端与服务器之间进行传递.数据的格式通常有2种:1.xml:2.JSON.通常来说都是使用JSON来传递数据.本文正是介绍在Java中JSON与对象之间互相转换时遇到的几个问题以及相关的建议. 首先明确对于JSON有两个概念: JSON对象(JavaScript Object Notation,JavaScript对象表示法).这看似只存是位JavaScript所定制的,但它作为一种语法是独立于语言以及平台的.只是说

  • Java编程构造方法与对象的创建详解

    java构造方法与对象的创建 可以用类来声明对象,声明对象后必须创建对象 1构造方法 首先,我们来谈谈什么叫构造方法,既然都说了这是一个构造方法,那么很显然,它本质上就是一个方法. 那么,既然作为一个方法,它应该有方法的样子吧.它除了回调一个Class();之后,也没见它有其他的定义方法的代码呀?这是因为,在未对类自定义构造方法的情况下,编译器会自动在编译期为其添加默认的构造方法 (1)程序用类创建对象时,需要使用该类的构造方法 (2)类中构造方法的名字必须和类名完全相同,而且没有类型 (3)允

  • 关于Java跨域Json字符转类对象的方法示例

    前言 JSON是JavaScript Object Notation的缩写,是一种轻量级的数据交换形式,是一种XML的替代方案,而且比XML更小,更快而且更易于解析.因为JSON描述对象的时候使用的是JavaScript语法,它是语言和平台独立的,并且这些年许多JSON的解析器和类库被开发出来. JSON具有以下这些形式: 对象是一个无序的"'名称/值'对"集合.一个对象以"{"(左括号)开始,"}"(右括号)结束.每个"名称"

  • Java编程实现对象克隆(复制)代码详解

    克隆,想必大家都有耳闻,世界上第一只克隆羊多莉就是利用细胞核移植技术将哺乳动物的成年体细胞培育出新个体,甚为神奇.其实在Java中也存在克隆的概念,即实现对象的复制. 本文将尝试介绍一些关于Java中的克隆和一些深入的问题,希望可以帮助大家更好地了解克隆. 假如说你想复制一个简单变量.很简单: int apples = 5; int pears = apples; 不仅仅是int类型,其它七种原始数据类型(boolean,char,byte,short,float,double.long)同样适

  • java对象初始化代码详解

    本文主要记录JAVA中对象的初始化过程,包括实例变量的初始化和类变量的初始化以及final关键字对初始化的影响.另外,还讨论了由于继承原因,探讨了引用变量的编译时类型和运行时类型 一,实例变量的初始化 这里首先介绍下创建对象的过程: 类型为Dog的一个对象首次创建时,或者Dog类的static字段或static方法首次访问时,Java解释器必须找到Dog.class(在事先设定好的路径里面搜索):  找到Dog.class后(它会创建一个Class对象),它的所有static初始化模块都会运行.

  • Java中初始化块详解及实例代码

    Java中初始化块详解 在Java中,有两种初始化块:静态初始化块和非静态初始化块. 静态初始化块:使用static定义,当类装载到系统时执行一次.若在静态初始化块中想初始化变量,那仅能初始化类变量,即static修饰的数据成员. 非静态初始化块:在每个对象生成时都会被执行一次,可以初始化类的实例变量. 非静态初始化块会在构造函数执行时,且在构造函数主体代码执行之前被运行. 括号里的是初始化块,这里面的代码在创建Java对象时执行,而且在构造器之前执行! 其实初始化块就是构造器的补充,初始化块是

  • Java多线程同步器代码详解

    同步器 为每种特定的同步问题提供了解决方案,同步器是一些使线程能够等待另一个线程的对象,允许它们协调动作.最常用的同步器是CountDownLatch和Semaphore,不常用的是Barrier 和Exchanger Semaphore Semaphore[信号标:旗语],通过计数器控制对共享资源的访问. 测试类: package concurrent; import concurrent.thread.SemaphoreThread; import java.util.concurrent.

  • spring实现bean对象创建代码详解

    我以一个简单的示例解构spring是怎样管理java对象的. 首先,定义一个简单的pojo,代码如下: package com.jvk.ken.spring; public class Demo { private String name; public Demo() { name="I'm Demo."; } public void printName() { System.out.println(name); } public void setName(String name) {

  • Java同步函数代码详解

    /* 同步函数 当函数中的代码全部放在了同步代码块中,那么这个函数就是同步函数 */ //同步函数的锁是this锁,this是一个引用,this指向的对象就是锁 //下面证明一下同步函数的锁就是this //创建两个线程,一个在同步代码块中执行,另一个在同步函数中执行 //同步代码块用的锁是obj,同步函数用的所是this //这就导致了两个线程存在两把锁,会出现上次所说的安全问题,即出现错误数据 //只有两个线程同时用一把锁,才能解决多线程的安全问题 class Ticket implemen

  • java匿名内部类实例代码详解

    这篇文章主要介绍了java匿名内部类实例代码详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 Person.java package insof; public class Person extends Object{ String name; static int age; public Person() { this.name = "tom"; System.out.println("执行的是构造方法");

  • xml与Java对象的转换详解

    xml与Java对象的转换详解 1.xstream解析报文 XStreamComponent x = XStreamComponent.newInstance(); x.processAnnotations(new Class[]{EquityExchangeDetail.class,PearTicketCustomerDTO.class,Date.class,Integer.class}); EquityExchangeDetail ptd = (EquityExchangeDetail) x

  • Json转化为Java对象的实例详解

    Json转化为Java对象的实例详解 问题:前后端数据交互时,经常会遇到Json串与Java对象转化的问题,有的Java对象中还包含了List对象等. 解决方案: 引入 json-lib包,Maven坐标如下: <dependency> <groupId>net.sf.json-lib</groupId> <artifactId>json-lib</artifactId> <version>2.4</version> &l

  • Java数据溢出代码详解

    java是一门相对安全的语言,那么数据溢出时它是如何处理的呢? 看一段代码, public class Overflow { /** * @param args */ public static void main(String[] args) { int big = 0x7fffffff; //max int value System.out.println("big = " + big); int bigger = big * 4; System.out.println("

  • java多线程中断代码详解

    一.java中终止线程主要有三种方法: ①线程正常退出,即run()方法执行完毕了 ②使用Thread类中的stop()(已过期不推荐使用)方法强行终止线程. ③使用中断机制 t.stop()调用时,终止线程,会导致该线程所持有的锁被强制释放,从而被其他线程所持有,因此有可能导致与预期结果不一致.下面使用中断信号量中断非阻塞状态的线程中: public class TestStopThread { public static void main(String[] args) throws Int

随机推荐