构造函数

java之构造函数

一、构造函数的作用主要是在类的对象创建时定义初始化的状态,而一个类可以有多个构造函数,以重载的形式存在。不能使用构造器名称来调用另一个构造器,而是应该使用Java特定的this(….)来调用,this(….)方法必须出现在构造器中的第一行,用来调用其他重载构造器,调用时参数必须严格匹配。这种调用方式的优点在于一个构造器可以不必重复编写其他构造器中已有的代码,而是通过调用其他构造函数以实现复用,从而提供良好和类代码结构。

public class Persion {
    public static void main(String[] args) {
        new test();
        new test("张三");
        new test("李四", 18);
    }
}

class test{
    test(){
        System.out.println("无参构造函数");
    }

    test(String name){
        this(name,18);//调用第二个有参构造函数
        System.out.println("第一个有参构造函数");
    }

    test(String name, int age){
        System.out.println("第二个有参构造函数");
    }
}
执行结果:
无参构造函数
第二个有参构造函数
第一个有参构造函数
第二个有参构造函数

二、当我们创建一个类时,如果没有显示的定义构造函数,java编译器会默认的为我们创建一个无参的构造函数(类似于构造函数1)。也就是说当我们没有定义任何构造函数时,我们任然可以new persion()创建对象,这是因为编译器已经默认为我们创建了无参构造函数。
三、构造函数的命名必须和类名完全相同。在java中普通函数可以和构造函数同名,但是必须带有返回值
四、构造函数没有返回值,也不能用void来修饰。这就保证了它不仅什么也不用自动返回。而其他方法都有返回值,即使是void返回值。尽管方法体本身不会自动返回什么,但仍然可以让它返回一些东西,而这些东西可能是不安全的。
五、只要我们显示的定义了构造函数(不管有参函数还是无参构造函数),那么编译器不会再为我们创建默认的无参构造函数。
六、如果子类继承父类时,当你创建子类对象时(也就是new对象时),编译器会默认在子类构造器的第一句添加super();的方法,先执行父类的构造函数,再执行子类的构造函数

public class StaticTest {

    public static void main(String[] args) {
        Son s = new Son("小明");
    }
}

class Father{
    Father(){
        System.out.println("父类构造方法");
    }
}

class Son extends Father{
    Son(String name){
        //编译器会默认添加此方法
        //super();
        System.out.println("子类构造方法");
    }
}

结果:
父类构造方法
子类构造方法

构造函数注意事项:

一、使用super调用父类构造器的语句必须是子类构造器的第一条语句,如果子类构造器没有显式地调用父类的构造器,则将自动调用父类的默认(没有参数)的构造器。如果父类没有不带参数的构造器,并且在子类的构造器中又没有显式地调用父类的构造器,则java编译器将报告错误
二、尽量构造函数不要进行复杂的业务逻辑
这也是阿里手册里面的规约之一,我们来看一个例子:

public class Client {

    public static void main(String[] args) {
        Server s = new SimpleServer(1000);

    }
}
//父类
abstract class Server{
    public final static int DEFAULT_PORT = 40000;
    public Server(){
        //获得子类提供的端口号
        int port = getPort();
        System.out.println("端口号:" + port);
    }

    protected abstract int getPort();
}

class SimpleServer extends Server{
    private int port = 100;
    //初始化传递一个端口号
    public SimpleServer(int _port){
        port = _port;
    }
    //检查端口号是否有效,无效则使用默认端口,这里使用随机数模拟
    @Override
    protected int getPort(){
        return Math.random() > 0.5?port:DEFAULT_PORT;
    }
}

该代码是一个服务类的简单模拟程序,Server类实现了服务器的创建逻辑,子类只要在生成实例对象时传递一个端口号即可创建一个监听该端口的服务,改代码的意图如下:
1、通过SimpleServer的构造函数接收端口参数
2、子类的构造函数默认调用父类的构造函数
3、父类构造函数调用子类的getPort方法获得端口号
4、父类构造函数建立端口监听机制
5、对象创建完毕,服务监听启动,正常运行
代码貌似很合理,但当我们运行时,会发现结果要么是0,要么是4000,永远不会出现100或1000,这是为什么呢?
子类实例化时,会首先初始化父类(注意这里是初始化,而不是生产父类对象),也就是初始化父类的变量,调用父类的构造函数,然后才会初始化子类的变量,调用子类自己的构造函数,最好生产一个实例对象。其执行过程如下:
1、子类SimpleServer的构造函数接收int类型的参数:1000.
2、父类初始化变量,也就是DEFAULT_PORT初始化,并设置为40000.
3、执行父类无参构造函数,也就是子类的有参构造中默认包含了super()方法。
4、父类无参构造函数执行到“int port = getPort()”方法,调用子类的getPort方法实现
5、子类的getPort方法返回port值(注意,此时port变量还没有赋值,默认为0)或DEFAULT_PORT(此时已经是40000)了
6、父类初始化完毕,开始初始化子类的实例变量,port赋值100.
7、执行子类构造函数,port被重新赋值为1000.
8、子类SimpleServer实例化结束,对象创建完毕
是不是觉得有点头晕呢,总之尽量不要在构造函数里写复杂的业务逻辑。
三、避免在构造函数中初始化其他类
构造函数时一个类初始化必须执行的代码,它决定着类的初始化效率,如果构造函数比较复杂,而且还关联了其他类,可能产生意想不到的问题:

public class Client {

    public static void main(String[] args) {
        Son s = new Son();
        s.doSomething();
    }
}
//父类
class Father{
    Father(){
        new Other();
    }
}

class Son extends Father{


    public void doSomething(){
        System.out.println("doSomething");
    }
}
class Other{
    public Other(){
        new Son();
    }
}

如果你执行这段代码,会报StackOverflowError异常,栈(Stack)内存溢出。这是因为声明s变量时,调用了Son的无参构造函数,而JVM有默认调用了父类的无参构造函数,接着Father类又初始化了Other类,而Other类又调用了Son类,形成了一个死循环。所以不要在构造函数中声明初始化其他类,养成良好习惯。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值