构造方法探析

构造方法探析

编译环境

  • Windows 10 Pro
  • jdk1.8.0_91

如果没有特殊说明,下面程序都有javac、java的编译运行过程。

构造方法的特点

  • 方法名与类名相同。
  • 不用定义返回值类型。
  • 没有具体的返回值。

此次整理是按照知识点具体为例子的方式。

当没有写构造方法时,Java虚拟机默认会创建一个空构造

ClassDemo1.java

class ClassDemo1{
    public static void main(String[] args){
        //new 关键字后面跟的必须是构造方法
        Car car = new Car();
    }
}

class Car{
    String color = "black" ;
    int tires ;
    //类中没有写构造方法
    void run(){
        System.out.println(color + " , run....");
    }
}

编译运行通过。

这是为什么呢?

原因就在,于Java虚拟机(这里是 HotSpot)在没有构造方法的情况下创建了空构造。为了说明一点,我们使用JDK(Java SE Development Kit) 提供的开发工具javap查看。

javap
用法: javap <options> <classes>
其中, 可能的选项包括:
  -l                       输出行号和本地变量表
  -public                  仅显示公共类和成员
  -protected               显示受保护的/公共类和成员
  -package                 显示程序包/受保护的/公共类
                           和成员 (默认)
  -p  -private             显示所有类和成员
  -c                       对代码进行反汇编
  -s                       输出内部类型签名
  -sysinfo                 显示正在处理的类的
                           系统信息 (路径, 大小, 日期, MD5 散列)
  -constants               显示最终常量
  -classpath <path>        指定查找用户类文件的位置
  -cp <path>               指定查找用户类文件的位置
  -bootclasspath <path>    覆盖引导类文件的位置
javap -p Car.class
Compiled from "ClassDemo1.java"
class Car {
  java.lang.String color;
  int tires;
  Car();//请注意这里,方法名与类名相同,没有返回值;这就可以说明jvm在没有构造方法的情况下,默认创建了空构造
  void run();
}

其次,我们还需要检验,看下例。

当类有写其他构造方法时,而没有空构造时,java虚拟机不会创建空构造

这一次由于写在同一个文件里,编译通不过,所以先将Car类写出来,先编译通过。

Car.java

class Car{
    String color = "black" ;
    int tires ;
    public Car(String color){  //类中有没有空构造,但有其他构造
        this.color = color;
    }
    void run(){
        System.out.println(color + " , run....");
    }
}

ClassDemo2.java


class ClassDemo2{
    public static void main(String[] args){
        Car car = new Car(); //调用空构造
    }
}

ClassDemo2.java 编译没有通过,输出信息


ClassDemo2.java:3: 错误: 无法将类 Car中的构造器 Car应用到给定类型;
        Car car = new Car();
                  ^
  需要: String
  找到: 没有参数
  原因: 实际参数列表和形式参数列表长度不同
1 个错误

这也验证了我们所说的当类有写其他构造方法时,而没有空构造时,java虚拟机不会创建空构造。

再来看看Car类中的变量和方法^ - ^


Compiled from "Car.java"
class Car {
  java.lang.String color;
  int tires;
  public Car(java.lang.String); //没有空构造
  void run();
}

所以,我们得显式地加上空构造。

需要说明的是构造方法的访问控制符,不仅可以是public,可以缺省,还可以是private。单例设计模式中就是private,这点会在后面讲述。

构造方法没有返回值

为了突出这点,我们写成下面这样。

ClassDemo4.java


class ClassDemo4{
    public static void main(String[] args){
        Car c1 = new Car();
        c1.run();
    }
}
class Car{
    String color = "black" ;
    int tires ;
    public Car(){
        System.out.println("new Car()");
    }

    public Car Car(String color){ //编译运行通过了,且方法名与类名相同,但这是构造方法吗?
        this.color = color ;
        Car c = new Car();
        return c;
    }

    void run(){
        System.out.println(color + " , run....");
    }
}

public Car Car(String color)

这仅仅是普通方法,仅仅是像构造方法;

构造方法用来初始化对象,new后面跟的一定是构造方法,new出来就要获得,是不需要显式地注明返回值的。

同样的,如果你像下面这样写,在构造方法中用上表示函数结束的return,编译运行可以通过,但显得多此一举。

ClassDemo5.java

class ClassDemo5{
    public static void main(String[] args){
        Car car = new Car();
    }
}

class Car{
    String color = "black" ;
    int tires ;
    public Car(){
        System.out.println("new Car()");
        return; // ?!类似返回值void!?
    }
    void run(){
        System.out.println(color + " , run....");
    }
}

构造方法会和普通方法重载吗?

重载就是,函数或者方法有相同的名称,但是参数列表不相同的情形,这样的同名不同参数的函数或者方法之间,互相称之为重载函数或者方法。

但是问题来了,下面两个方法可以构成重载吗?或者说,c3.Car(“white”)调用的是下面哪个?

  • A. ①
  • B. ②

ClassDemo7.java

class ClassDemo7{
    public static void main(String[] args){
        Car c1 = new Car();
        c1.run();
        Car c2 = new Car("white");
        c2.run();
        Car c3 = new Car("white");
        c3.Car("white"); // 这个方法调用的是下面哪个
    }
}
class Car{
    String color = "black" ;
    int tires ;
    public Car(){
        System.out.println("new Car()");
    }

    public Car(String color){ // ①
        this(); //this()必须是第一行。构造方法调用构造方法,这样解决了构造方法的复用。
        this.color = color ;
        System.out.println("this car‘s color is "+color);
    }

    public Car Car(String color){ // ②
        this.color = color ;
        Car c = new Car();
        return c;
    }
    void run(){
        System.out.println(color + " , run....");
    }
}

编译运行是通过的,调用的是 ② ;

输出信息


new Car()
black , run....
new Car()
this car‘s color is white
white , run....
new Car()
this car‘s color is white
new Car()

从这个问题延伸出下面这个问题。

构造方法只能通过new来调用吗?

ClassDemo8.java


class ClassDemo8{
    public static void main(String[] args){
        Car c1 = new Car();
        c1.Car("white"); //可以调用构造方法吗?
    }
}
class Car{
    String color = "black" ;
    int tires ;
    public Car(){
        System.out.println("new Car()");
    }

    public Car(String color){
        this();
        this.color = color ;
        System.out.println("this car‘s color is "+color);
    }

    void run(){
        System.out.println(color + " , run....");
    }
}

并不能,编译就通不过。

ClassDemo8.java:4: 错误: 找不到符号
        c1.Car("white");
          ^
  符号:   方法 Car(String)
  位置: 类型为Car的变量 c1
1 个错误

说明构造函数只能由new关键字调用。

错误拾遗

  1. 根据java规范,类名的首字母是大写的,构造方法名可以小写吗?

    当然不行,java是严格区分大小写的。构造方法的特点之一就是”方法名与类名相同”.

  2. 构造方法没有返回值

    下面这个例子是具有迷惑性的,编程的时候需要留意。

    ClassDemo3.java

    class ClassDemo3{
        public static void main(String[] args){
            Car car = new Car();
        }
    }
    
    class Car{
        String color = "black" ;
        int tires ;
        public void Car(){
            System.out.println("haha");
        }
        void run(){
            System.out.println(color + " , run....");
        }
    }
    

    什么输出都没有,正奇怪呢。可这是构造方法吗?!

    javap一看,两个方法构成了重载。

    Compiled from "ClassDemo3.java"
    class Car {
      java.lang.String color;
      int tires;
      Car(); //jvm创建的构造方法
      public void Car();//编写的方法
      void run();
    }
  3. 构造方法中可以写构造方法吗?

    Congratulations!当你这样写时,虽然编译通过了,但栈溢出了,相当于递归没有终止条件(除非你加了终止条件,不过你真会倒腾)。

    ClassDemo6.java

    
    class ClassDemo6{
        public static void main(String[] args){
            Car car = new Car();
        }
    }
    
    class Car{
        String color = "black" ;
        int tires ;
        public Car(){
            new Car();
        }
        void run(){
            System.out.println(color + " , run....");
        }
    }

    错误类型:StackOverflowError

    Exception in thread "main" java.lang.StackOverflowError

构造方法的使用

前面提到多个构造方法可以重载,构造方法和普通方法间并不构成重载。

构造方法调用构造方法

那么构造方法如何调用构造方法呢?其实,就是使用使用this语句,this()、this(xxx,xxx)实现对构造方法的重用。

ClassDemo9.java

class ClassDemo9{
    public static void main(String[] args){
        Car c1 = new Car();
        c1.run();
        Car c2 = new Car("white");
        c2.run();
        Car c3 = new Car("red",4); //多个构造方法重载,重载根据形参列表区分
        c3.run();

        System.out.println(Benz.getBrand());
    }
}
class Car{
    String color = "black" ;
    int tires ;
    public Car(){
        System.out.println("new Car()");
    }

    public Car(String color){
        this();// this语句必须放在第一行
        this.color = color ;
        System.out.println("this car‘s color is "+color);
    }

    public Car(String color,int tires){
        this(color); // this语句必须放在第一行;体现对构造方法的重用
        this.tires = tires;
        System.out.println("this car‘s color is "+color+" and it has "+tires+" tires");
    }

    void run(){
        System.out.println(color + " , run....");
    }
}

//public -- private
class Benz extends Car{ //奔驰 继承自 车
    private static String BRAND = "BENZ" ;//商标

    public static String getBrand(){
        return BRAND ;
    }
}

输出:

new Car()
black , run....
new Car()
this car‘s color is white
white , run....
new Car()
this car‘s color is red
this car‘s color is red and it has 4 tires
red , run....
BENZ

this语句必须放在第一行,this()、this(color)都是this语句; 但注意区别于this关键字,this代表其所在函数所属对象的引用。

说到this就得说super,这点在后面继承中详细说明。

我们用javap看看ClassDemo9的class字节码。


javap ClassDemo9

发现static {};是静态代码块,说明java静态成员变量的声明是解释成字节码的时候是作为一个静态代码块的。


Compiled from "ClassDemo9.java"
class Benz extends Car {
  Benz();
  public static java.lang.String getBrand();
  static {}; //静态代码块
}

这里谈一谈java中的代码块,因为也与理解构造函数有关。

构造方法与构造代码块和静态代码块

java中有4种代码块:普通代码块、静态代码块、构造代码块、同步代码块。

ClassDemo10.java


class ClassDemo10{

    public static void main(String[] args){
        Car car = new Car();
    }
}

class Car{
    String color = "black" ;
    int tires ;
    Car(){ // 构造方法
        System.out.println("new Car()");
    }

    {   // 构造代码块
        System.out.println("world");
    }

    static{ // 静态代码块
        System.out.println("hello");
    }

    void run(){
        System.out.println(color + " , run....");
    }
}

问题来了?构造方法与构造代码块和静态代码块 的执行顺序是什么呢。

我们编译运行一下。

hello
world
new Car()

说明三者的执行顺序是① 静态代码块,② 构造代码块,③ 构造方法。

需要说明的是静态代码块在类加载完成时,构造代码块和构造方法在对象创建时完成,且构造代码块在构造方法之前完成。

静态代码块和构造代码块都可以为对象创建做好初始化准备。如创建数据库连接,定义变量并赋值等。

那么创建多个对象时,它们的执行次数是什么情况呢?

ClassDemo11.java


class ClassDemo11{

    public static void main(String[] args){
        Car car1 = new Car();
        Car car2 = new Car();
        Car car3 = new Car();
    }
}

class Car{
    String color = "black" ;
    int tires ;
    Car(){
        System.out.println("new Car()");
    }

    {
        System.out.println("构造代码块");
    }

    static{
        System.out.println("静态代码块");
    }

    void run(){
        System.out.println(color + " , run....");
    }
}

输出:


静态代码块
构造代码块
new Car()
构造代码块
new Car()
构造代码块
new Car()

静态代码块只在最初执行一次,构造代码块和同步代码块在对象创建时执行。

通过继承理解构造方法

在堆空间里,一个对象的创建包含了整个家族树的创建。

所有类都是Object的子类。抽象类也一定有构造方法,但抽象类不能实例化。

下面例子中Dog类继承了Animal,Jing8继承了Dog。

ClassDemo12.java


class ClassDemo12{
    //static Jing8 d = new Jing8();
    public static void main(String[] args){
        Jing8 d = new Jing8();
        //d.blood = "B"; //fina变量不能被修改
        d.run();
    }
}
class Animal{
    String category ;
    public Animal(String c){
        System.out.println("new Animal()");
    }
    public final void run(){
        System.out.println("run...");
    }
}

class Dog extends Animal{
    String color ;
    public Dog(){
        super("kkk");
        System.out.println("new Dog()");
    }
}

final class Jing8 extends Dog{ //final

    final String blood = "A";
    public Jing8(){
        System.out.println("new Jing8()");
    }
}

现在问题来了,是先输出new Jing8(),然后输出new Dog(),最后输出new Animal()呢;还是先输出new new Animal(),然后输出new Dog(),最后输出new Jing8()呢?

其实堆内存中,子类调用父类构造函数是一个入栈的过程,到Object结束,JVM的栈空间中调用方法区的方法其实都是栈操作。

这么来说,就是如下输出:

new Animal()
new Dog()
new Jing8()
run...

这里面,有super(),类似this()调用本类的构造函数,super()调用父类的构造函数。而super是指向父类的引用,是父类在内存中的标识。

需要说明的是,构造函数的第一行,要么是this语句(包括this()或this(xxx)),要么是super语句(包括super()或super(xxx))。

ClassDemo13.java

class ClassDemo13{
    public static void main(String[] args){
        Son s1 = new Son();
        s1.eat();
        s1.run();
        Father father = new Father();
        s1.setAsset(1000);
        System.out.println(s1.getAsset()); // 验证子类的内存中有从父类继承来的private asset变量
        System.out.println(father.getAsset());// 子类输出的值和父类输出的值不同,
        //就验证了private asset变量的内存存储位置一定不同,
        //也就negotiation说明子类的内存中有父类private变量和方法
    }
}

class Father{
    String name ;
    private int asset=10000 ;//资产

    private void eat(){
        System.out.println("洗手!");
    }
    public int getAsset(){
        return this.asset ;
    }
    public void setAsset(int asset){
        this.asset = asset ;
    }
    void walk(){
        System.out.println("walk...");
    }
}
class Son extends Father{

    /*
    //这里没有写构造函数,
    //① 默认创建的空构造,
    //② 构造函数的第一行默认调用的是super();
    public Son(){
        //super();
    }
    */
    public void run(){
        super.walk();//调用父类方法
    }
    //复写 或 覆盖
    public void walk(){
    }
    /*
    //子类默认还继承了父类的非私有变量和非私有方法。
    //但并不能“继承”私有方法和私有变量,
    //需要注意的是,继承的私有方法和私有变量仍然在子类创建的内存中!!
    public int getAsset(){
        return this.asset ;
    }
    public void setAsset(int asset){
        this.asset = asset ;
    }
    */
    public void eat(){
        //super.eat();//尝试复用父类方法,但失败,
        //原因是父类的eat()方法是private私有,没有被继承,自然不能被引用
        System.out.println("洗澡!"); //再添加上子类特有方法
    }
}

子类默认还继承了父类的非私有变量和非私有方法。但并不能“继承”私有方法和私有变量。需要注意的是,extends”继承”的实质是将父类对象完全拷贝到子类对象的内存中,私有方法和私有变量仍然在子类创建的内存中!!

验证这个仅需看看,子类对象用从父类继承来的public int getAsset()和public void setAsset(int asset)才能调用private int asset变量,同时创建一个父类对象。比较两者asset值是否相同,即是否操作的是同一块内存空间。

让我们看看输出结果:

洗澡!
walk...
1000
10000

结果说明值正是不同的,说明子类的asset变量的内存地址一定与父类继承来的private asset变量的内存地址不同。子类”继承”了父类的私有变量和私有方法,extends的继承的实质是完全拷贝!

我们画一画堆内存图

JVM分配的堆空间默认是物理内存的1/4,栈空间默认是1M(有争议)。

从上面的继承的实质看,我们要尽量少些私有方法和私有方法,子类一定无法调用私有方法!应用中,在庞大复杂的继承系统里,这些块无用的内存空间拖慢的系统的运行效率。

内部类的构造

内部类可以定义在成员的位置上,也可以定义在方法中。

内部类访问实例变量,变量要用final修饰。

待填充。

构造方法与单例模式

待填充。

时间: 2024-10-11 12:23:43

构造方法探析的相关文章

读后感(一) web运作原理探析

tomcat与java web开发技术详解之web运作原理探析 成为一名web开发工程师,首先要明白web运作原理,原理可以带我们更好的去解决底层问题,怎么去理解现在流行的开源框架,甚至如何去自己写一个框架. 1 什么是web? web是网络上使用最广泛的分布式框架.它采用了客户端/服务器的通信模式,客户端可以是浏览器,通过浏览器它就可以连接服务器,访问许多服务器浏览各种各样的网站,这也是为什么说web是一种分布式的运用框架了. 2 URL 我们访问一个网站的时候会在浏览器显示一个网站地址,比如

合并财务报表分步合并与一次合并的差异探析

多层控股关系下,集团编制合并报表的顺序采用一次合并法还是分步合并法,两者的结果是否相同,其使用有否受到限制,实务中存在疑惑.本文着重分析在抵销内部交易未实现损益.同一控制下企业合并.购买子公司少数股权等事项中,不同合并顺序的应用及其对于合并报表权益结构的影响,探讨在实务中如何消除因合并顺序不同而引发的差异,以提高合并报表的编制质量. 一.多层控股关系下有关合并报表顺序的两种方法 当存在多层控股关系时,编制合并报表的合并顺序一般有分步合并和一次合并两种方法. (一)分步合并法 分步合并法,指企业存

【转】代码签名探析

转至:objc中国 代码签名探析 dopcn  15 Oct 2014 分享文章 "用户会感激代码签名带来的好处" – Apple Developer Library: Code Signing Guide 在 iOS 或 OS X 平台上进行应用开发时,你所需要使用的 API 大多设计得简洁明了.你可以轻易地实现酷炫的动画效果,便捷地进行应用发布前测试,或是用 Core Data 将数据安全的存储在本地.但是总有一天,你会碰上代码签名 (code signing) 和配置文件 (pr

浏览器环境下JavaScript脚本加载与执行探析之动态脚本与Ajax脚本注入

在<浏览器环境下JavaScript脚本加载与执行探析之defer与async特性>中,我们研究了延迟脚本(defer)和异步脚本(async)的执行时机.浏览器支持情况.浏览器bug以及其他的细节问题.而除了defer和async特性,动态脚本和Ajax脚本注入也是两种常用的创建无阻塞脚本的方法.总的来看,这两种方法都能达到脚本加载不影响页面解析和渲染的作用,但是在不同的浏览器中,这两种技术所创建的脚本的执行时机还是有一定差异,今天我们再来探讨一下通过动态脚本技术和Ajax注入的脚本在这些方

深入探析 Rational AppScan Standard Edition 多步骤操作

序言 IBM Rational AppScan Standard(下文简称 AppScan)作为面向 Web 应用安全黑盒检测的自动化工具,得到业界的广泛认可和应用.很多人使用 AppScan 时都采用其强大的手工探索加自动探测的方式,然而这种方式并不适用于所有场景.使用 AppScan 进行安全扫描时,我们必须保证 AppScan 探索出来的 URL 的有效性(尤其是用户想导出这些探索结果以供复用的情况下),有效性即指该 URL 对应的 HTTP 请求能被服务器端接受并按照期望的方式进行处理.

[置顶] 深入探析Java线程锁机制

今天在iteye上提了一个关于++操作和线程安全的问题,一位朋友的回答一言点醒梦中人,至此我对Java线程锁有了更加深刻的认识.在这里也做个总结供大家参考. 先看几段代码吧! 代码一: [java] view plaincopy public class TestMultiThread2 implements Runnable{ private static Object o = new Object(); private static Integer si = 0; private stati

广播探析

一.几种特殊的IP地址 如图所示,在表格的下半部分有四种特殊的广播地址.分别为受限的广播地址.指向网络的广播.指向子网的广播.指向所有子网的广播.由于对其概念,以及相应子网掩码的概念理解不是很透彻,这次探析给了我很大的帮助. 一.体会这四种广播 以网络地址为142.1.2.3为例 ①             若掩码为255.255.128.0 我们可以获知在该IP的所有子网为142.1.0.0,142.1.128.0. 因此,该地址的广播地址为142.1.127.255,而142.1.255.2

云桌面及桌面虚拟化中的安全问题探析

安全问题一直作为云计算平台的重中之重,本文探析了云桌面及桌面虚拟化中的安全问题. 数据泄露防护(Data leakage prevention, DLP),又称为"数据丢失防护"(Data Loss prevention, DLP)是指通过IT技术方式,防止企业指定的数据信息的非法被策略规定以外的第三方获取.目前Symantec.Macafee等国际企业,以及中软.伊时代.圣博润等国内厂商都有相应的数据防泄密产品方案. 同时,根据诸多IT调研机构的报告显示,该市场在2010年至2020

2019考研数学题源探析经典1000题习题+解析分册(数一+数二+数三)

资源链接:https://pan.baidu.com/s/1gnDlPrVEQG6bd003Un-5Kg2019考研数学题源探析经典1000题习题+解析分册(数一+数二+数三)考研数学刷题必备!如下: 原文地址:http://blog.51cto.com/14084127/2320170