详解Java的构造方法及类的初始化

目录
  • 一. 利用构造方法给对象初始化
    • 1. 构造方法的概念
    • 2. 构造方法的特性
    • 3. 子类构造方法
    • 4. 避免在构造方法中调用重写的方法
  • 二. 对象的默认初始化
  • 三. 就地初始化对象
  • 四. 类的初始化顺序
    • 1. 普通类(没有继承关系)
    • 2. 派生类( 有继承关系)

一. 利用构造方法给对象初始化

1. 构造方法的概念

构造方法(也称为构造器)是一个特殊的成员方法,其名字必须与类名相同,在创建对象时,由编译器自动调用,并且在整个对象的生命周期内只调用一次。

构造方法的作用就是给对象中的成员进行初始化,并不负责给对象开辟空间。

public class Date {
    public int year;
    public int month;
    public int day;

    // 构造方法:
    // 名字与类名相同,没有返回值类型,设置为void也不行
    // 一般情况下使用public修饰
    // 在创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次
    public Date(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
        System.out.println("Date(int,int,int)方法被调用了");
    }

    public void printDate() {
        System.out.println(year + "-" + month + "-" + day);
    }

    public static void main(String[] args) {
        // 此处创建了一个Date类型的对象,并没有显式调用构造方法
        Date d = new Date(2021, 6, 9);
        // 输出Date(int,int,int)方法被调用了
        d.printDate(); // 2021-6-9
    }
}

2. 构造方法的特性

1.名字必须与类名相同

2.没有返回值类型,设置为void也不行

3.创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次

4.绝大多数情况下使用public来修饰,特殊场景下会被private修饰

5.构造方法可以重载(用户根据自己的需求提供不同参数的构造方法); 下面两个构造方法:名字相同,参数列表不同,因此构成了方法重载

public class Date {
    public int year;
    public int month;
    public int day;

    // 无参构造方法
    public Date(){
        this.year = 1900;
        this.month = 1;
        this.day = 1;
    }

    // 带有三个参数的构造方法
    public Date(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }
    public void printDate(){
        System.out.println(year + "-" + month + "-" + day);
    }
    public static void main(String[] args) {
        Date d = new Date();
        d.printDate();
    }
}

6.如果用户没有显式定义,编译器会生成一份默认的构造方法,生成的默认构造方法一定是无参的; 一旦用户定义,编译器则不再生成;下面代码中,没有定义任何构造方法,编译器会默认生成一个不带参数的构造方法。

public class Date {
    public int year;
    public int month;
    public int day;
    public void printDate(){
        System.out.println(year + "-" + month + "-" + day);
    }
    public static void main(String[] args) {
        Date d = new Date();
        d.printDate();
    }
}

7.构造方法中,可以通过this调用其他构造方法来简化代码

【注意事项】

  • 构造方法中,通过this(…)去调用其他构造方法,这条语句必须是构造方法中第一条语句
  • 多个构造方法不可以互相调用(不能形成环), 会形成构造器的递归调用,但却没有调用的结束条件
public class Date {
    public int year;
    public int month;
    public int day;
// 无参构造方法--内部给各个成员赋值初始值,该部分功能与三个参数的构造方法重复
// 此处可以在无参构造方法中通过this调用带有三个参数的构造方法
// 但是this(2022,8,16);必须是构造方法中第一条语句

    public Date(){
//System.out.println(year); 注释取消掉,编译会失败
        this(2022, 8, 16);
//this.year = 1900;
//this.month = 1;
//this.day = 1;
    }

    // 带有三个参数的构造方法
    public Date(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }
}

3. 子类构造方法

在继承基础上,子类对象构造时,需要先调用基类构造方法,然后执行子类的构造方法。

在子类构造方法中,并没有写任何关于基类构造的代码,但是在构造子类对象时,先执行基类的构造方法,然后执行子类的构造方法,

原因在于:子类对象中成员是有两部分组成的,基类继承下来的以及子类新增加的部分 。父类和子类, 肯定是先有父再有子,所以在构造子类对象时候 ,子类构造方法中先要调用基类的构造方法,将从基类继承下来的成员构造完整 ,然后再完成子类自己的构造,将子类自己新增加的成员初始化完整 。

【注意事项】

1.若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用基类构造方法

public class Base {
    public Base(){
        System.out.println("Base()");
    }
}

public class Derived extends Base{
    public Derived(){
// super(); // 注意子类构造方法中默认会调用基类的无参构造方法:super(),
// 用户没有写时,编译器会自动添加,而且super()必须是子类构造方法中第一条语句,
// 并且只能出现一次
        System.out.println("Derived()");
    }
}

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

2.如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败。

public class Animal {
    public String name;
    public int  age;

   public Animal(String name, int age) {
        this.name = name;
        this.age = age;
       System.out.println("Animal(String , int )");
    }

}

public class Dog extends Animal{
    //傻狗  是狗的属性
    public boolean silly;

   public Dog(String name,int age,boolean silly) {
        //1. 先帮助父类部分初始化 必须放到第一行
        super(name,age);
        this.silly = silly;
        System.out.println("Dog(String ,int ,boolean )");
   }
    public static void main(String[] args) {
        Animal animal2 = new Dog("金毛",6,false);
    }
}

3.在子类构造方法中,super(…)调用父类构造时,必须是子类构造方法中第一条语句。

4.super(…)只能在子类构造方法中出现一次,由与this(…)调用时也要在第一条语句,所以super(…)不能和this(…)同时出现,也就是是说子类构造方法中不能使用this(…)

4. 避免在构造方法中调用重写的方法

一段有坑的代码. 我们创建两个类, B 是父类, D 是子类. D 中重写 func 方法. 并且在 B 的构造方法中调用 func

class B {
    public B() {
// do nothing
        func();
    }
    public void func() {
        System.out.println("B.func()");
    }
}
class D extends B {
    private int num = 1;
    @Override
    public void func() {
        System.out.println("D.func() " + num);
    }
}
public class Main {
    public static void main(String[] args) {
        D d = new D();
    }
}

执行结果:

  • 构造 D 对象的同时, 会调用 B 的构造方法.
  • B 的构造方法中调用了 func 方法, 此时会触发动态绑定, 会调用到 D 中的 func
  • 此时 D 对象自身还没有构造, num 处在未初始化的状态, 值为 0;如果具备多态性,num的值应该是1.
  • 所以在构造函数内,尽量避免使用实例方法,除了final和private方法。

【结论】:

“用尽量简单的方式使对象进入可工作状态”, 尽量不要在构造器中调用方法(如果这个方法被子类重写, 就会触发动态绑定, 但是此时子类对象还没构造完成), 可能会出现一些隐藏的但是又极难发现的问题.

二. 对象的默认初始化

在Java方法内部定义一个局部变量时,用户必须要将其赋值或者初始化,否则会编译失败;

但对象中的字段(成员变量),用户不需要将其初始化就可直接访问使用,这里其原因在于new对象时,jvm会给出字段的默认初始化。

下面是new对象是时,jvm层面执行的概述:

1.检测对象对应的类是否加载了,如果没有加载则加载

2.为对象分配内存空间

3.处理并发安全问题

比如:多个线程同时申请对象,JVM要保证给对象分配的空间不冲突

4.初始化所分配的空间

即:对象空间被申请好之后,对象中包含的成员已经设置好了初始值

数据类型 默认值
byte 0
short 0
int 0
long 0
float 0.0f
double 0.0
char /u0000
boolean false
reference (引用类型) null

5.设置对象头信息(关于对象内存模型后面会介绍)

6.调用构造方法,给对象中各个成员赋值

三. 就地初始化对象

在声明成员变量时,就直接给出了初始值。

代码编译完成后,编译器会将所有给成员初始化的这些语句添加到各个构造方法中

public class Date {
    public int year = 1900;
    public int month = 1;
    public int day = 1;

    public Date(){
    }

    public Date(int year, int month, int day) {
    }

    public static void main(String[] args) {
        Date d1 = new Date(2022,8,16);
        Date d2 = new Date();
    }
}

四. 类的初始化顺序

1. 普通类(没有继承关系)

静态部分(静态变量、常量,静态代码块)

  • 在类加载阶段执行,类中存在多个静态部分时,会按顺序执行
  • 静态代码块只会执行一次,且静态的变量、常量等只会创建一份

非静态部分(实例变量、常量、实例代码块)

当有对象创建时才会执行,按顺序执行

最后执行构造方法,当有对象创建时才会执行

代码演示:

class Person {
    public String name;
    public int age;
    public Organ organ = new Organ();
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("构造方法执行");
    }
    {
        System.out.println("实例代码块执行");
    }
    static {
        System.out.println("静态代码块执行");
    }
}
class Organ {
    //...
    public Organ() {
        System.out.println("实例变量::organ");
    }
}
public class TestDemo {
    public static void main(String[] args) {
        Person person1 = new Person("xin",21);
        System.out.println("==============");
        Person person2 = new Person("xinxin",20);
    }
}

执行结果:

2. 派生类( 有继承关系)

静态部分(静态变量、常量,静态代码块)

  • 父类静态代码块优先于子类静态代码块执行,且是最早执行
  • 只有第一次实例化子类对象时,父类和子类的静态部分会执行; 之后再实例化子类对象时,父类和子类的静态部分都不会再执行

父类非静态部分(实例变量、常量、实例代码块)和父类构造方法

子类非静态部分(实例变量、常量、实例代码块)和子类构造方法

class Person {
    public String name;
    public int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("Person:构造方法执行");
    }
    {
        System.out.println("Person:实例代码块执行");
    }
    static {
        System.out.println("Person:静态代码块执行");
    }
}
class Student extends Person{
    public Student(String name,int age) {
        super(name,age);
        System.out.println("Student:构造方法执行");
    }
    {
        System.out.println("Student:实例代码块执行");
    }
    static {
        System.out.println("Student:静态代码块执行");
    }
}
public class TestDemo4 {
    public static void main(String[] args) {
        Student student1 = new Student("张三",19);
        System.out.println("===========================");
        Student student2 = new Student("gaobo",20);

    }
    public static void main1(String[] args) {
        Person person1 = new Person("bit",10);
        System.out.println("============================");
        Person person2 = new Person("gaobo",20);
    }
}

执行结果:

到此这篇关于详解Java的构造方法及类的初始化的文章就介绍到这了,更多相关Java初始化内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java类的初始化实践分析

    本文实例讲述了Java类的初始化.分享给大家供大家参考,具体如下: 一 点睛 1 下面6种方式,系统会初始化该类或接口. 创建类的实例.为某个类创建实例的方式包括使用new操作符来创建实例,通过反射来创建实例,通过反序列化的方式来创建实例. 调用某个类的静态方法. 访问某个类或接口的静态属性,或为该静态属性赋值. 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象.例如代码:Class.forName("Person"). 初始化某个类的子类,当初始化某个类的子

  • 简单了解Java类成员初始化顺序

    这篇文章主要介绍了简单了解Java类成员初始化顺序,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 类中包含7中成员: 1.静态变量 static 2.final静态常量 final static 3.静态代码块 static{} //多个代码块顺序执行 4.普通变量 5.普通代码块 {} //多个代码块顺序执行 6.构造方法 7.普通方法 类中成员初始化方式(排出7): 非继承类中: 首先执行1/2/3,执行顺序为顺序执行(谁在前先执行谁).

  • Java类加载连接和初始化原理解析

    1.当程序使用某个类时,JVM将会完成以下这三个步骤 2.连接 1)验证 验证是否是合法的字节码文件.验证JDK版本是否正确等. 2)准备 给成员变量(类变量/静态变量)赋默认值 把常量(final)等值在方法区的常量池中给准备好. 3)解析 虚拟机常量池内的符号引用(常量名)替换为引用(地址)的过程 3.初始化 <clinit> 类初始化 <clinit> 类初始化由两部分组成: (1)静态变量的显示初始化代码,赋值代码 (2)静态代码块 (1) 和 (2) 的顺序从上往下 当一

  • Java 超详细讲解对象的构造及初始化

    目录 如何初始化对象 构造方法 特性 默认初始化 就地初始化 如何初始化对象 我们知道再Java方法内部定义一个局部变量的时候,必须要初始化,否则就会编译失败 要让这串代码通过编译,很简单,只需要在正式使用a之前,给a设置一个初始值就好那么对于创造好的对象来说,我们也要进行相对应的初始化我们先写一个Mydate的类 public class MyDate { public int year; public int month; public int day; /** * 设置日期: */ pub

  • java中的this引用及对象构造初始化

    目录 1. this 引用 1.1 为什么要有this引用 1.2 什么是this引用 1.3 this引用的特性 1.4 this引用练习题 2. 对象的构造及初始化 2.1 如何初始化对象 2.2 构造方法 2.2.1 概念 2.2.2 特性 2.3 默认初始化 2.4 就地初始化 1. this 引用 1.1 为什么要有this引用 先来写一个日期类的例子: public class classCode { public int year; public int month; public

  • Java类加载初始化的过程及顺序

    Java类的加载说明 Java类的编译代码都存在于它自己的独立文件中(class),该文件只在需要使用程序代码时才会被加载. 类加载在创建类的第一个对象时发生,但当访问static域或static方法时,也会发生加载. 构造器也是static方法,尽管static关键字没有显式写出,故可进一步说,类是在任何static成员被访问时加载的. 示例说明加载过程 示例源于<Java编程思想> //父类 public class SuperClass { protected int super_a;

  • 详解Java的构造方法及类的初始化

    目录 一. 利用构造方法给对象初始化 1. 构造方法的概念 2. 构造方法的特性 3. 子类构造方法 4. 避免在构造方法中调用重写的方法 二. 对象的默认初始化 三. 就地初始化对象 四. 类的初始化顺序 1. 普通类(没有继承关系) 2. 派生类( 有继承关系) 一. 利用构造方法给对象初始化 1. 构造方法的概念 构造方法(也称为构造器)是一个特殊的成员方法,其名字必须与类名相同,在创建对象时,由编译器自动调用,并且在整个对象的生命周期内只调用一次. 构造方法的作用就是给对象中的成员进行初

  • 详解java JDK 动态代理类分析(java.lang.reflect.Proxy)

    详解java JDK 动态代理类分析(java.lang.reflect.Proxy) /** * JDK 动态代理类分析(java.lang.reflect.Proxy使用) * * @author 张明学 * */ public class ProxyStudy { @SuppressWarnings("unchecked") public static void main(String[] args) throws Exception { // 动态代理类:通用指定类加载器,和接

  • 详解Java中的日期类

    Java 编程语言中时间的处理类有 Date类与 Calendar类.目前官方不推荐使用 Date类,因为其不利于国际化:而是推荐使用 Calendar类,并使用 DateFormat 类做格式化处理. 一.Date 类介绍 Date 表示特定的瞬间,精确到毫秒. 在 JDK 1.1 之前,类 Date 有两个其他的函数.它允许把日期解释为年.月.日.小时.分钟和秒值.它也允许格式化和解析日期字符串. 不过,这些函数的 API 不易于实现国际化.从 JDK 1.1 开始,应该使用 Calenda

  • 详解Java抽象类与普通类的区别

    浅谈抽象类 在面向对象概念中,所有的对象都是通过类来描述的,但是反过来,并不是所有的类都是用来描述对象的.如果一个类中没有足够多的信息来描述一个具体的对象,这样的类就是抽象类. 看到这里可能还是觉得有些难以理解,举个例子说明一下:说到动物你会想到什么?猫,狗,鸡鸭鹅?当然这些都可以.那么动物这两个字,你能确定一个具体的对象吗?显然不能.甚至更严格意义上讲,说到猫你会想到什么?橘猫,短美- 毕竟: 一千个人心中有一千个哈姆雷喵. 所以我们在设计中,动物类可以设计成为抽象类,而某一种特定的物种可以采

  • 详解Java 中的嵌套类与内部类

    详解Java 中的嵌套类与内部类 在Java中,可以在一个类内部定义另一个类,这种类称为嵌套类(nested class).嵌套类有两种类型:静态嵌套类和非静态嵌套类.静态嵌套类较少使用,非静态嵌套类使用较多,也就是常说的内部类.其中内部类又分为三种类型: 1.在外部类中直接定义的内部类. 2.在函数中定义的内部类. 3.匿名内部类. 对于这几种类型的访问规则, 示例程序如下: package lxg; //定义外部类 public class OuterClass { //外部类静态成员变量

  • 详解JAVA 时间处理相关类

    时间处理相关类: 1.java.util.Date:时间类 2.java.text.DateFormat:时间格式化类(抽象类),实现类:java.text.SimpleDateFormat 3.java.util.Calendar:日历类(抽象类),实现类:java.util.GergorianCalendar 1.java.util.Date 时间就是一个数轴,在计算机中,1970年1月1日00:00:00定位基准时间,也就是数轴的原点,每个度量单位是毫秒(1000毫秒=1秒) java中我

  • 详解java 对象锁与类锁

    一.什么是对象锁 对象锁也叫方法锁,是针对一个对象实例的,它只在该对象的某个内存位置声明一个标识该对象是否拥有锁,所有它只会锁住当前的对象,而并不会对其他对象实例的锁产生任何影响,不同对象访问同一个被synchronized修饰的方法的时候不会阻塞, 例如: public class MyObject { private synchronized void method1(){ try { System.out.println(Thread.currentThread().getName());

  • 详解Java正则表达式中Pattern类和Matcher类

    前言 本文将介绍Java正则表达式中的Pattern类与Matcher类.首先我们要清楚指定为字符串的正则表达式必须首先被编译为pattern类的实例.因此如何更好的了解这两个类,是编程人员必须知道的. 以下我们就分别来看看这两个类: 一.捕获组的概念 捕获组可以通过从左到右计算其开括号来编号,编号是从1 开始的.例如,在表达式 ((A)(B(C)))中,存在四个这样的组: 1 ((A)(B(C))) 2 (A) 3 (B(C)) 4 (C) 组零始终代表整个表达式. 以 (?) 开头的组是纯的

  • 详解java创建一个女朋友类(对象啥的new一个就是)==建造者模式,一键重写

    创建一个女朋友,她有很多的属性,比如:性别,年龄,身高,体重,类型等等,虽然每个女朋友都有这些属性,但是每个人找女朋友的要求都是不一样的,有的人喜欢男的,有的人喜欢女的,有的喜欢胖的,不同的人可以根据自己的喜好去建造不同的女朋友,我们不需要关心她是怎么建造的,我们只需要去指定她的属性就行了 相比如文字解释,我更习惯撸代码来解释,下面来一步步实现怎么用java来为你创建一个女朋友 首先定义一个女朋友类: package nuoyanli; /** * Created by ${nuoyanli}

  • 详解java为什么不允许类多重继承却允许接口多重继承

    首先看下面这一段代码: interface a{ void b(); } interface a1 extends a{ void b(); } interface a2 extends a{ void b(); } interface a12 extends a1,a2{ void b(); } public class Main { public static void main(String args[]){ } } 上面a1和a2都继承了接口a,都获得了b方法的定义.然后a12又多重继承

随机推荐