构造函数

作用:在构造对象的同时初始化对象。java强制要求对象 诞生同时被初始化,保证数据安全。

调用过程和机制:①申请内存,②执行构造函数的函数体,③返回对象的引用。

特点:与类同名,无返回类型,可以重载。

每一个类都至少有1个构造函数,如果不显示定义构造函数,则javac会帮我们添加一个默认的无参数的构造函数。如果自己提供了构造函数,则javac不会再提供默认的无参构造函数。

一个类中,对象构造时的构造过程

顺序如下:

1、字段获取相应类型的默认值(基本类型是 0 ,引用类型是null),定义时给了初始值的则获得给定的值。

2、执行构造块中语句

3、执行构造函数中的主体语句

先来看一个类中,初始化时,各个构造方法的调用顺序

下面是一个例子

public class Main
{ public static void main(String[] args)
{ Base b = new Base(); System.out.println("Base中baseVal的最终值为:"+b.baseVal); /*
baseVal=11
baseVal=12
Base中baseVal的最终值为:12
   */ } } class Base
{ public int baseVal = 10; // ① { // ②构造块
baseVal = 11;
System.out.println("baseVal=" + baseVal);
} public Base() // ③
{
baseVal = 12;
System.out.println("baseVal=" + baseVal);
} }

实质上,为了初始化实例字段,javac会对每一个构造函数进行"修改",原理如下图。所以,无论一个类中使用定义初始化,还是构造块,最终都是调用构造函数来起作用的。

例子验证

public class Test
{ private int x = 100;
private int y;
private int z; {y= 200;} public Test()
{
z = 300;
} }

使用javap得到字节码的结果,可以很清楚的看到,字段的定义初始化,构造块初始化,其实都是语法特性,最终的内部实现都是依赖构造函数的执行。执行的顺序一目了然

Compiled from "Test.java"
public class Test {
public Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: bipush 100
7: putfield #2 // Field x:I
10: aload_0
11: sipush 200
14: putfield #3 // Field y:I
17: aload_0
18: sipush 300
21: putfield #4 // Field z:I
24: return
}

一个继承链中,对象构造时的过程

java中,子类的构造函数,如果不显式用super写出来的话,默认会在第一句调用父类的默认构造函数。可以通过super 显式指定调用父类的哪个构造函数。需要注意的是:这个super调用必须是子类构造函数的第一条语句。

可以发现:构造一个子类,会先去调用父类的整套构造机制(注意:我并没有这样说:构造一个子类,会先去构造一个父类,原因待会解释),父类又会调用父类的父类的构造机制,.......一直带到Object为止。那么,这就会引发一条调用链,过程有点类似递归,只有高层的函数调用完了,底层的函数才会执行完。

public class Main
{ public static void main(String[] args)
{ SubClass sub = new SubClass(); /*
* console:
* ①baseVal=11
②baseVal=12
③baseVal=12 sub Val=91
④baseVal=12 sub Val=92
*/ } } class Base
{ public int baseVal = 10; {
baseVal = 11;
System.out.println("①baseVal=" + baseVal);
} public Base()
{
baseVal = 12;
System.out.println("②baseVal=" + baseVal);
} } class SubClass extends Base
{
public int subVal = 90; {
subVal = 91;
System.out.println("③baseVal="+baseVal+" sub Val=" + subVal); } public SubClass()
{
// super() //子类构造函数默认隐式调用父类的默认构造函数 ,当然你也可以显示调用
subVal = 92; System.out.println("④baseVal="+baseVal+" sub Val=" + subVal);
} }

当然,子类还可以使用this来调用自己的另一个版本的构造函数完成构造,这个可以叫做委托构造。

class Student
{ public static void main(String[] args)
{ Student s = new Student(); System.out.println(s); } private double score ; private String name; //构造函数的第一句不是super(xx,xx...)就是this(xx,xx...)。super用来借用父类的构造函数来构造自己,这是java强制要求的,其它语言也多是这样。
//this(xx,xx...)则是程序员自己复用本类构造函数代码的手段。
//如果不显示的写super(xx,xx...),则编译javac会插入super()来调用父类默认的构造函数,如果父类没有默认的构造函数,则编译出错。
//而this(xx,xx...)必须是程序员自己指定的,编译器不会插入。
public Student(String name,double score )
{
//super() 第一句如果不是this(xx,xx),则默认就是super();因为这里Student无显示的父类,所以super()就是调用Object的构造函数。
this.name = name;
this.score = score;
} public Student()
{
this("no name",0.0); //无参数的构造函数委托有2个参数的构造函数完成。
} @Override
public String toString()
{
return String.format("name:%s , score:%.2f", name,score);
} }

子类的构造,会生成一个父类对象吗?

这个问题的关键在于:是生成的一个内嵌的父类,还是生成一个独立的父类。

显然,我认为是生成的是内嵌的父类,即子类包含父类的数据,父类并不是独立存在的。

试想一下:如果new一个子类对象,会生成一个独立的父类,那么,对于深层次的继承链。比如10层,new一个最高层子类,岂不是会在内存生成 10个对象,显然是不可以的,不高效的。

那么子类对象在构造时,为什么要调用父类地方构造 函数呢?那是因为,子类要借用父类的构造函数类构造自己。因为子类包含父类的数据。

静态字段的构造

javac在编译时,会自动每一个类提供一个"静态构造函数",将 静态字段的定义初始化 和 static初始化块 插入到这个"静态构造函数" 中去,先插入 静态字段的定义初始化,后插入 static初始化块。意味着  静态字段的定义初始化先于  static初始化块执行。

当类第一次使用的时候(被JVM加载的时候),执行这个"静态构造函数",让static字段得到初始化。

静态字段,无论是否使用了static初始化块,都是在类第一次被加载时,完成的初始化。

例子:

public class Test
{ private static int x = 100; static { x = 200;} }

字节代码:

Compiled from "Test.java"
public class Test {
public Test(); //<-----------javac提供的默认无参构造函数
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return static {}; //<------------javac自动合成的 “静态构造函数”
Code:
0: bipush 100
2: putstatic #2 // Field x:I
5: sipush 200
8: putstatic #2 // Field x:I
11: return
}

细节总结

1、每一个类至少有一个构造函数。如果不手动编写构造函数,javac会为这个类提供一个默认的,无参数的构造函数。如果手动编写了构造函数,则javac就不会提供默认的构造函数。

2、如果想让一个类不能实例化对象,则让他仅有的默认构造函数私有(private),见Math类。

3、java 中的对象都在堆中生成,而引用他们的引用变量则视情况而定。比如一个类对象中有一个String成员,变量名为name,那么name也在堆中。C++中,如果使用new,则对象生成在堆中,如果不用new,一般在栈中。

4、类的成员变量如果不显式初始化,则赋值为默认值。但最好明确地个一个初始值。

5、final成员变量必须在new对象时被初始化,包括定义final变量时给初始值,或者在初始化块中,或者在构造函数中。

6、静态字段也会有默认值,也可以静态构造块中初始化。JVM第一次加载类时,这个过程就完成了。

7、通过this来调用本类其他的构造函数,这个this也必须是构造函数中的第一句,所以,不能同时使用this个super调用构造函数。

8、如果一个类不准在类外是例外对象,则应该定义仅有的1个默认私有无参构造函数:如 java.lang.System类。

 /** Don't let anyone instantiate this class */
private System() {
}

Java中对象构造的更多相关文章

  1. 2.java中c#中statc 静态调用不同之处、c#的静态构造函数和java中的构造代码块、静态代码块

    1.java和c#静态成员调用的不同之处 static 表示静态的,也就是共享资源,它是在类加载的时候就创建了 java中   可以通过实例来调用,也可以通过类名.成员名来调用,但是一般最好使用类名. ...

  2. Java中对象的生与灭- 核心篇

    前言 大家好啊,我是汤圆,今天给大家带来的是<Java中对象的生与灭- 核心篇>,希望对大家有帮助,谢谢 文章纯属原创,个人总结难免有差错,如果有,麻烦在评论区回复或后台私信,谢啦 简介 ...

  3. 你真的了解JAVA中对象和类、this、super和static关键字吗

    作者:小牛呼噜噜 | https://xiaoniuhululu.com 计算机内功.JAVA底层.面试相关资料等更多精彩文章在公众号「小牛呼噜噜 」 目录 Java对象究竟是什么? 创建对象的过程 ...

  4. Java中对象的深复制和浅复制详解

    1.浅复制与深复制概念 ⑴浅复制(浅克隆) 被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象. ⑵ ...

  5. java 中对象比较大小

    java 中对象比较大小 java 中对象比较大小有两种方法 1:实现Comparable 接口 的 public int compareTo(T o) 方法: 2:实现Comparator 接口 的 ...

  6. Java中对象、对象引用、堆、栈、值传递以及引用传递的详解

    Java中对象.对象引用.堆.栈.值传递以及引用传递的详解 1.对象和对象引用的差别: (1).对象: 万物皆对象.对象是类的实例. 在Java中new是用来在堆上创建对象用的. 一个对象能够被多个引 ...

  7. Java中对象流使用的一个注意事项

    再写jsp的实验作业的时候,需要用到java中对象流,但是碰到了之前没有遇到过的情况,改bug改到崩溃!!记录下来供大家分享 如果要用对象流去读取一个文件,一定要先判断这个文件的内容是否为空,如果为空 ...

  8. Java中对象和引用的理解

    偶然想起Java中对象和引用的基本概念,为了加深下对此的理解和认识,特地整理一下相关的知识点,通过具体实例从两者的概念和区别两方面去更形象的认识理解,再去记忆. 一.对象和引用的概念: 在Java中万 ...

  9. Java之对象构造过程

    先来运行一段代码 class A { public A() { init(); } public void init() { } public static void main(String[] ar ...

随机推荐

  1. QUnit使用笔记-2同步与异步处理方式

    同步: 有时候如果我们想判断方法执行的次数,可以通过间接设置expect(n);//可以将expect的参数放到test的第二参数来简化: QUnit.test("expect test&q ...

  2. Period[HDU1358]

    Period Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Sub ...

  3. BZOJ4282 : 慎二的随机数列

    首先在开头加上-inf,结尾加上inf,最后答案减2即可. 设s[i]为i之前未知的个数,f[i]为以i结尾的LIS,且a[i]已知,那么: f[i]=max(f[j]+min(s[i]-s[j],a ...

  4. 移动WEB测试工具 Adobe Edge Inspect

    要用到的内容: Adobe Edge Code CC      https://creative.adobe.com/products/code?promoid=KFKML Adobe Edge In ...

  5. MONO 说谈

     Xamarin android现在到底依赖Dalvik不?是部分依赖的 驱动 编译后的IL经过CLR并不能直接成为机器码,而是要借助Dalvik才能成为机器码吗? 而是关于Android的驱动部分主 ...

  6. Myeclipse10 + JBPM4.4 环境搭建图文教程

    一.软件环境 IDE:Myeclipse10.0 (jbpm4.0以上版本好像只能与Myeclipse7.5以上版本集成) JBPM:4.4 与Myeclipse集成 1.解压jbpm-4.4.zip ...

  7. CSS光标属性一览表

    光标类型 CSS 把你的光标放到相应文字上查看效果 要注意光标的实际效果依赖于用户的系统设置,与你在这里看到的效果并不一定一致. 十字准心 cursor: crosshair; 手 cursor: p ...

  8. bin/bash 和 /bin/sh 的区别

    今天在用ssh Secure shell 连接虚拟机中的Ubuntu编写程序时,想比对一下两个源代码有什么差别,但是在一个ssh 客户端下不断的切换很是费劲.于是想着在主机中再添加一个用户.我原本用s ...

  9. ubuntu 14.04安装

    最近想搞spark.没有集群环境,想先在单机上跑.但是两年前一直用到现在的virtualbox已经太老,很多源都停更了.所以准备装ubuntu14.04. 0. 更新源.163的源有问题,用的是soh ...

  10. YII 查找View的5种方式

    别名开头,路径指定view文件@app/views/site/about.php //开头,使用 app目录下面的views//site/about.php /开头,使用当前Module中的views ...