【Java】构造方法及类的初始化

这篇具有很好参考价值的文章主要介绍了【Java】构造方法及类的初始化。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

活动地址:CSDN21天学习挑战赛
【Java】构造方法及类的初始化博客主页: XIN-XIANG荣
系列专栏:【Java SE】
一句短话: 难在坚持,贵在坚持,成在坚持!

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

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();
    }
}
  1. 如果用户没有显式定义,编译器会生成一份默认的构造方法,生成的默认构造方法一定是无参的; 一旦用户定义,编译器则不再生成;下面代码中,没有定义任何构造方法,编译器会默认生成一个不带参数的构造方法。
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();
    }
}
  1. 构造方法中,可以通过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();
    }
}
  1. 如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法并在子类构造方法中选择合适的父类构造方法调用,否则编译失败。
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);
    }
}
  1. 在子类构造方法中,super(…)调用父类构造时,必须是子类构造方法中第一条语句。
  2. 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();
    }
}

执行结果:

【Java】构造方法及类的初始化

  • 构造 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
  1. 设置对象头信息(关于对象内存模型后面会介绍)
  2. 调用构造方法,给对象中各个成员赋值

三. 就地初始化对象

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

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

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. 普通类(没有继承关系)

  1. 静态部分(静态变量、常量,静态代码块)
  • 在类加载阶段执行,类中存在多个静态部分时,会按顺序执行
  • 静态代码块只会执行一次,且静态的变量、常量等只会创建一份
  1. 非静态部分(实例变量、常量、实例代码块)
  • 当有对象创建时才会执行,按顺序执行
  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);
    }
}

执行结果:

【Java】构造方法及类的初始化

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

  1. 静态部分(静态变量、常量,静态代码块)
  • 父类静态代码块优先于子类静态代码块执行,且是最早执行
  • 只有第一次实例化子类对象时,父类和子类的静态部分会执行; 之后再实例化子类对象时,父类和子类的静态部分都不会再执行
  1. 父类非静态部分(实例变量、常量、实例代码块)和父类构造方法
  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】构造方法及类的初始化文章来源地址https://www.toymoban.com/news/detail-409540.html

到了这里,关于【Java】构造方法及类的初始化的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C++奇遇记】构造函数 | 初始化列表

    🎬 博客主页:博主链接 🎥 本文由 M malloc 原创,首发于 CSDN🙉 🎄 学习专栏推荐:LeetCode刷题集 数据库专栏 初阶数据结构 🏅 欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正! 📆 未来很长,值得我们全力奔赴更美好的生活✨ 🐤本篇文章将讲授C++的初始化列表相关的知识

    2024年02月12日
    浏览(45)
  • C++ | 谈谈构造函数的初始化列表

    我们知道,对于下面这个类A的成员变量 _a1 和 _a2 属于【声明】,还没有在内存中为其开辟出一块空间以供存放,真正开出空间则是在【定义】的时候,那何时定义呢?也就是使用这个类A去实例化出对象的时候 这个对象的空间被开出来了,难道里面的成员变量就一定开出空间

    2023年04月11日
    浏览(83)
  • java中初始化数组的方法

    方式一: 注:此种方式创建的数组,如不显式初始化数组元素,则各元素为当前数据类型的默认值。基本数据类型为0,对象类型为null。所以使用前需要将各元素显式赋值。 方式二: 注:此方式与方式一的结果相同,但是更简便。 方式三: 注:此方式与方式一和方式二的结

    2024年02月12日
    浏览(33)
  • C++中包含初始化列表的构造函数

    构造函数对初始化成员变化很有用。另一种初始化成员的方式是使用初始化列表。对于程序中接受两个参数的构造函数,其包含初始化列表的变种类似于下面这样: 格式为: :成员变量1(参数1),成员变量2(参数2) 编译器会将初始化列表一一转换成代码,并将这些代码放

    2024年02月05日
    浏览(40)
  • 【C++】构造函数和初始化列表的性能差距

    构造函数和初始化列表的性能差距对比测试 在C++类和对象中,你可能听到过更加推荐用初始化列表来初始化类内成员。如果类内成员是自定义类型,则只能在初始化列表中调用自定义类型的构造函数。 但初始化列表和在构造函数体内直接赋值有无性能差距呢?今天就用一份

    2024年02月11日
    浏览(34)
  • JVM——类的生命周期(加载阶段,连接阶段,初始化阶段)

    类的生命周期 ⚫ 1、加载(Loading)阶段第一步是类加载器根据类的全限定名通过不同的渠道以二进制流的方式获取字节码信息。 程序员可以使用Java代码拓展的不同的渠道。 ⚫ 2、类加载器在加载完类之后,Java虚拟机会将字节码中的信息保存到方法区中。 ⚫ 3、类加载器在加载

    2024年02月05日
    浏览(42)
  • Java 中 Map 初始化的几种方法

    方式1:传统方式 方式2:java8新特性-双括号初始化 匿名内部类通常会导致内存泄漏问题,因为它会隐式地持有外部类的引用,如果外部类的引用一直存在而没有释放,它所持有的内存也就不会被垃圾回收机制回收,产生内存泄漏。而通过使用静态内部类或者弱引用等技术可以

    2024年02月05日
    浏览(41)
  • 【C++】const、static关键字和构造函数初始化

    💗个人主页💗 ⭐个人专栏——C++学习⭐ 💫点击关注🤩一起学习C语言💯💫 目录 1. const修饰成员函数 1.1 语法格式 1.2 权限放大缩小 1.3 思考 1.4 解答 2. 再谈构造函数 2.1 构造函数体赋值 2.2 初始化列表 2.3 explicit 3. static成员 3.1 静态变量 3.2 静态函数 3.3 静态成员变量

    2024年02月19日
    浏览(38)
  • c++类和对象(拷贝构造、运算符重载、初始化列表、静态成员、友元等)

    拷贝构造函数的特征: 1、拷贝构造函数是构造函数的一个重载形式; 2、拷贝构造函数的参数只有一个且必须是同类类型对象的引用, 使用传值方式编译器直接报错 ,因为会引发无穷递归调用。 在c++中自定义类型 传值传参 的时候要调用拷贝构造函数。 3、若未显式定义,

    2024年02月15日
    浏览(30)
  • 【Java基础教程】(八)面向对象篇 · 第二讲:Java 数组全面解析——动态与静态初始化、二维数组、方法参数传递、排序与转置、对象数组、操作API~

    掌握数组的动态及静态创建方式、使用及特征; 掌握引用类型数据的特征; 掌握数组的排序、转置操作; 数组可以将多个变量进行统一的命名,这样相同类型的元素就可以按照一定的顺序进行组合排列 。在 Java中,数组属于引用类型数据,所以在数组的操作过程中,也一定

    2024年02月13日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包