java第十章学习笔记:构造器---对象制造的工厂

前面章节中,多次使用new关键字来创建对象,而new关键字后边调用的正是构造器。事实上,调用构造器是创建新对象的唯一方案。

10.1 基础知识

介绍构造器的基本知识及需注意的问题,包括编写构造器的语法规则,访问限制修饰符与构造器及构造器与返回类型等内容。
10.1.1 编写构造器的语法规则
构造器是在创建对象时候运行的代码所在位置,格式如下:
*访问限制修饰符 类名(参数列表)
{
构造器中的代码
}*
其中访问限制修饰符与成员相同,包括public、protected、private及默认不写;
构造器的名称必须与类名完全相同,大小写也要一样。
不能给出返回值类型eg:

public class Test
{
    public Test()
    {}
    public static void main(String[] args)
    {

    }
}

10.1.2 访问限制修饰符与构造器
1、public类型构造器
当构造器标识为public类型时,其对所有的类而言均是可见的,但需要注意其可见性要收到所在类的访问限制的约束,即,若这个类是默认的,那么及时构造器被标识为public类型,对包外类来说一样是不可见的。eg:

package lxx;
class Animal
{
    public Animal()
    {
        System.out.println("我是public类型构造器");
    }
}
package bupt;
import lxx;
public class Test
{
    public static void main(String[] args)
    {
        new Animal();//Error:Animal在lxx中不是公共的,无法在外部软件包中对其进行访问
    }
}

因为Animal对包外不可见,及时构造器是公共的,对包外也将不可见。将Animal类访问修饰符改为public后则可编译通过。
2、默认与保护类型构造器
**默认情况和保护情况只有在继承时,才有区别,而构造器是不会被继承的,所以对构造器来说,这两种类型的访问权限是一样的,即包外不可见**eg:

package lxx;
public class Animal
{
    Animal()
    {
        System.out.println("我是public类型构造器");
    }
}

该代码编译以后将报Animal()在lxx.Animal在包外不可见。解决方案:要么将构造器设置为public,要么将两个类放在一个类中。
3、private类型构造器以及静态工厂方法:
构造器可以被标识为私有类型,私有类型构造器只能在该类自身内的代码才能创建该类的对象,对类外构造器不可见,即对别的类而言无法创建其对象,可以理解为将构造器锁起来了eg:
java第十章学习笔记:构造器---对象制造的工厂_第1张图片
这时,若想得到该类的对象,则该类必须提供静态方法,也就是常说的静态工厂方法,通过访问这些静态方法得到该类的对象,eg:在上述代码中加入静态工厂方法getMe();

public class Test
{
    public static void main(String[] args)
    {
        Animal animal=Animal.getMe();
    }
}
class Animal
{
    private Animal()
    {
        System.out.println("我是私有类型的构造器");
    }
    public static Animal getMe()//静态工厂方法
    {
        return new Animal();
    }
}

运行结果:
这里写图片描述
在上述代码中,Animal类提供了静态工厂方法getMe(),Test类中main()方法通过调用静态工厂方法得到Animal对象。
从本例中可以看出,将构造器设置为private可以锁住构造器,是其他类不能通过访问构造器创建对象,如果还希望其他类可以使用此类的对象,则一般提供一个静态的工厂方法。
提示:之所以称为静态工厂是因为方法是静态的,而且有创建对象的功能,就像一个制造对象的工厂一样。
10.1.3 构造器与返回类型
需注意构造器没有返回类型,其也不需要返回什么,这也是构造器与方法之间最明显的差别之一。若添加返回值,则系统会认为这是一个碰巧和类同名的方法,而不再把其看作构造器。eg:

public class Test
{
    public static void main(String[] args)
    {
        new Animal();
    }
}
class Animal
{
    public void Animal()
    {
        System.out.println("我自认为是公有类型的构造器。。");
    }
}

运行结果:
这里写图片描述
从图中可以看出,在创建Animal对象的时候没有打印出任何东西,所以说明若将构造器上加上返回值,系统将认其为普通方法。eg:将main()方法修改为如下代码:

public class Test
{
    public static void main(String[] args)
    {
        Animal animal=new Animal();
        animal.Animal();//方法调用
    }
}
class Animal
{
    public void Animal()
    {
        System.out.println("我自认为是公有类型的构造器。。");
    }
}

运行结果:
这里写图片描述
构造器绝不可以有返回值;
构造器不可以同方法一样被对象引用eg:将上面代码中Animal类改为如下代码:
java第十章学习笔记:构造器---对象制造的工厂_第2张图片
因为访问Animal方法,而Animal类中没有这样的方法所以报错;
虽然方法与构造器外观上很相似,但其实是两个截然不同的概念,一个是创建对象时需执行的代码,由操作符new调用;而方法则是类或对象具有的行为(功能)

10.2 创建对象

前面章节创建对象使用new关键字,后面加上类名圆括号,其实后面的类名加圆括号就是构造器,开发人员只是用new调用类的某个构造器而已,构造器也可以具有参数。实际上,创建对象的语法如下:
new 构造器名(构造器参数序列)
上面的构造器名也就是类名;
构造器的代码用来执行对象的初始化工作eg:初始化成员变量的值
eg:调用有参构造器:

public class Test
{
    public static void main(String[] args)
    {
        Animal animal=new Animal("Marry",166);
       System.out.println("我的名字叫:"+animal.name+",我的身高长:"+animal.size+"厘米");
    }
}
class Animal
{
    String name;
    int size;
    public Animal(String name,int size)
    {
        this.name=name;
        this.size=size;
        System.out.println("有参构造函数Animal(String name,int size)执行");
    }
}

这里写图片描述

10.3 重载构造器

在实际开发中,有时创建同一个类的不同对象时知道的信息是不同的,eg:学生类有姓名、学号、班级年龄等属性,在创建学生对象时,有时这些信息都知道,有时只知道一些,这时就需要不同参数的构造器;
java中为解决这个问题,允许构造器重载重载构造器与重载方法的语法规则基本相同,主要区别如下:
重载方法的返回值可以不同;
构造器不能有返回值,因此重载构造器只是参数序列不同;
eg:重载构造器的例子:

public class Test
{
    public static void main(String[] args)
    {
        Animal animal=new Animal("Marry",166);
        Animal ani=new Animal("Kitty");
        Animal an=new Animal();

       System.out.println("我的名字叫:"+animal.name+",我的身高长:"+animal.size+"厘米");
       System.out.println("我的名字叫"+ani.name+",我的身高为:"+ani.size);
       System.out.println("我的名字叫:"+an.name+",我的身高为"+an.size);
    }
}
class Animal
{
    String name;
    int size;
    public Animal()
    {

    }
    public Animal(String name)
    {
        this.name=name;
    }
    public Animal(String name,int size)
    {
        this.name=name;
        this.size=size;
        System.out.println("有参构造函数Animal(String name,int size)执行");
    }
}

java第十章学习笔记:构造器---对象制造的工厂_第3张图片
构造器重载也会像方法一样匹配规则,会自动寻找最接近的一项若没有可以匹配的则编译不通过。
提示:很多情况下需要调用无参构造器来创建对象,另一方面也给扩展子类提供了一些方便。

10.4 级联调用的构造器

本节讨论在实际运行时,构造器是怎样调用的,包括构造器调用流程及默认构造器、自定义构造器需注意的问题不能继承构造器及调用兄弟构造器等内容
10.4.1 构造器的调用流程及默认构造器:
构造器与方法及成员变量不同,是不能被继承的原因在本节介绍:如下:
构造器是如何调用的?
级联调用的原因?
编译器提供的默认构造器
1、构造器是如何调用的:

public class Test
{
    public static void main(String[] args)
    {
        new Bird();
    }
}
class Animal
{
    public Animal()
    {
        System.out.println("我是Animal类的构造器");
    }
}
class Bird extends Animal
{
    public Bird()
    {
        System.out.println("我是Bird构造器");
    }
}

这里写图片描述
*执行new Bird()语句,进入Bird构造器体
级联调用Animal构造器,进入Animal 构造体,因为Animal是Bird的父类
级联object构造器,进入Object构造器体
执行object构造器中的代码直至完成,返回Animal构造体;
执行Animal构造体中的代码直至完成,返回Bird构造体;
执行bird中的代码直至完成;*
构造器是级联调用的,即调用子类构造器会自动调用父类构造器,直至object构造器;

2、级联调用的原因:
自己编写的调用父类构造器的代码必须写在构造器的第一句,调用父类构造器的语法是super后面加一对圆括号,圆括号中放上父类构造器所需的参数。eg:

public class Test
{
    public static void main(String[] args)
    {
        new Bird();
        System.out.println("==================================");
        new Bird(2);
    }
}
class Animal
{
    public Animal()
    {
        System.out.println("执行Animal类的无参构造器");
    }
    public Animal(int a)
    {
        System.out.println("执行Animal类的一个int参数的构造器,本次接收到的参数值为:"+a);
    }
}
class Bird extends Animal
{
    public Bird()
    {
        super();//调用父类无参构造器;
        System.out.println("执行Bird无参构造器");
    }
    public Bird(int a)
    {
        super(a);
        System.out.println("执行Animal类的有一个int参数的构造器,本次接收到的参数值为:"+a);
    }
}

java第十章学习笔记:构造器---对象制造的工厂_第4张图片
若将“super(a);”注释掉,再次编译结果为:
java第十章学习笔记:构造器---对象制造的工厂_第5张图片
注释掉“super(a)”,系统会自动加上调用父类无参构造器的代码“super()”
实际开发中,若不写调用父类构造器的代码,系统只能调用父类的无参构造器,若需要调用有参构造器,需自己编写;
构造器被设计为级联的原因使:替代性原理,子类对象必须能够替代父类对象,因此在构造子类对象之前首先要构造父类对象,然后在父类对象的基础上构造子类对象,即java中的子类对象中都隐含一个父类对象,子类对象是在父类对象的基础上进一步雕琢而成的。
3、编译器提供的默认构造器:
前面章节的例子并没有写构造器,但一样使用new调用构造器创建对象。这是因为如果开发人员不编写构造器,编译器会自动提供一个无参构造器,在该构造器中只有一句话“super()”eg:

public class Test
{
    public static void main(String[] args)
    {
        new Bird();
        new Animal();
    }
}
class Animal
{
    public Animal()
    {
        System.out.println("执行Animal类的构造器。。");
    }
}
class Bird extends Animal
{
}

运行结果:
这里写图片描述
并没有给Bird类提供构造器,但上图可以看出,其不但有 了构造器,而且还调用了父类的构造器,这说明,编译器在编译时提供了一个具有”super“语句的默认构造器
10.4.2 自定义构造器需要注意的问题
构造器在创建对象时是级联调用父类构造器完成的,如果不写则编译时候自动加上无参构造器,但是一旦编写任意一个构造器,无论是否有参数系统都不会再提供无参构造器eg:
java第十章学习笔记:构造器---对象制造的工厂_第6张图片
出“系统报找不到符号“错误,这是因为自行编写构造器以后,系统就不再提供默认无参构造器,一旦有继承就容易犯这样的错误eg:
java第十章学习笔记:构造器---对象制造的工厂_第7张图片
想解决上述问题,两个途径:
在父类中提供一个无参构造器(constructor)
在子类中编写构造器,并在构造器中第一句明确调用父类中提供的无参构造器。
10.4.3 不能继承构造器
构造器不能继承,其采用的是级联调用机制,下面demo用来说明构造器不能继承:
java第十章学习笔记:构造器---对象制造的工厂_第8张图片
Fruit中有一个公共的有参构造器,若可继承,则Apple中也应该有。
编译上面两个错误:
第一个错误是:父类的Fruit中没有无参构造器,子类Apple中又没有编写自己的构造器明确调用父类的有参构造器造成的。
第二个错误:构造器不能继承,因此Apple中没有有参构造器造成的。
10.4.4 调用兄弟构造器
前面介绍过构造器重载,某些功能复杂的构造器中一部分功能可能与其他重载构造器是相同的,如果在一个构造器中调用其他构造器,可以节省代码,维护方便
本节介绍重载构造器之间是如何调用,包括:
1、调用兄弟构造器的语法;
2、调用兄弟构造器需要注意的问题;
提示:重载构造器之间的相互调用称为调用兄弟构造器
1、调用兄弟构造器的语法:
若需要在构造器中调用另一个重载的构造器,则使用关键字this(“相匹配的参数值”);
eg:

public class Test
{
    public static void main(String[] args)
    {
        new Animal();
    }
}
class Animal
{
    public Animal(String name)
    {
        System.out.println("我是Animal的构造器,参数name的值,name="+name);
    }
    public Animal()
    {
        this("Marry");//调用兄弟构造器
    }

}

Animal类的无参构造器使用”this(“Marry”)“调用
运行结果:
这里写图片描述
在实际开发中常常需要以上语法调用兄弟构造器
2、调用兄弟构造器需要注意的问题;
一旦使用了”this(XX)“调用兄弟构造器,在该构造器中,编译器将不再自动添加super()【会在兄弟构造器中添加】而执行到调用的兄弟构造器时,会执行构造器中编译器为其添加的”super()“语句,也就是说推迟了”super()“的调用
另外,对兄弟构造器的调用与父类的一样,必须写在构造器的第一句,因此也可以看出调用了兄弟构造器就不能调用父类构造器了eg:构造器的调用过程:
java第十章学习笔记:构造器---对象制造的工厂_第9张图片
调用兄弟构造器时,有时虽然语法没有问题,但是不注意会产生循环调用。eg:
java第十章学习笔记:构造器---对象制造的工厂_第10张图片
在开发中需避免,另外需要注意的是:**构造器永远不能被方法调用**eg:
java第十章学习笔记:构造器---对象制造的工厂_第11张图片
“对this的调用必须在构造中的第一个语句”说明不能在普通方法中调用构造器

10.5 单列模式

实际开发中时常会有这样的需求:某个类同一时刻只允许有一个对象,这称之为单例模式,本节介绍java中如何实现单列模式eg:

public class Test
{
    public static void main(String[] args)
    {
        Singleton s1=Singleton.getInstance();
        Singleton s2=Singleton.getInstance();
        if(s1==s2)
        {
            System.out.println("两个引用指向同一个对象。。。");
        }else
        {
            System.out.println("两个引用指向不同对象;;;;");
        }
    }
}
class Singleton
{
    private static Singleton singleInstance;
    public static Singleton getInstance()//没有就新建对象,有就直接返回
    {
        if (singleInstance==null)
        {
            singleInstance=new Singleton();
        }
        return singleInstance;
    }
    private Singleton()
    {
        System.out.println("执行单列模式类的构造器。。。");
    }
}

Singleton类是单列模式的,其构造器设置为private,不对自己类以外的任何类开放,因为一旦构造器开放,就无法限制对象的个数了。
Singleton的静态方法getInstance相当于一个对象工厂,外面的类要使用Singleton的对象要通过其获取引用。
Singleton的静态成员singleInstance是用来指向其唯一对象的引用,初始值为null,一直到有使用对象的申请时才创建对象,并让其指向创建的对象,这样可以节约资源。
运行结果:
这里写图片描述
从上图可以看出:
虽然取了两次对象引用,但构造器只执行了一次,说明只创建了一个对象;
两次获取引用的值相等,也说明只有一个对象
提示:单列模式的使用一般是为了节省系统资源,注意:向单列模式对象注册的其他对象要及时处理掉,否则会产生内存漏洞,反而浪费资源,demo见后面章节。

10.6 java程序的加载过程

本节将介绍java程序在执行过程中,类、对象以及他们成员加载、初始化的顺序
在java中,当创建一个对象时总的加载过程如下:

  1. 加载要创建对象的类及其直接与间接父类;
  2. 在类被加载的同时会将静态成员进行加载,包括:静态成员变量的初始化,静态语句块的执行,加载时按代码的先后顺序。;
  3. 需要的类加载完之后,开始创建对象,首先会加载非静态的成员,主要包括非静态成员变量的初始化,非静态语句块的执行,按代码顺序加载。
  4. 最后执行构造器,构造器执行完毕对象生成。
    提示:再次创建同类的对象时,类的加载不再进行、语句块(无论是静态还是非静态的)只执行一次,加载完成后不会再执行,创建子类对象时上述过程要先走父类的,一直递归到java.lang.Object类;eg:
public class Test
{
    public static void main(String[] args)
    {
        new MyClass();
    }
}
class MyClassFather
{
    public MyClassFather()
    {
        System.out.println("执行要创建对象类的父类构造器。。");
    }
}
class MyClass extends MyClassFather
{
    UnStatic unStatic=new UnStatic();//非静态成员
    final UnstaticFinal uf=new UnstaticFinal();
    {
        System.out.println("执行非静态语句块。。。");
    }
    static
    {
        System.out.println("执行静态语句块。。。。。");
    }
    static final StaticFinal sf=new StaticFinal();
    static Static s=new Static();
    MyClass()
    {
        System.out.println("执行要创建对象类的构造器。。,,。。");
    }
}
class StaticFinal
{
    StaticFinal()
    {
        System.out.println("静态final的成员变量初始化。。");
    }
}
class Static
{
    Static()
    {
        System.out.println("静态非final的成员变量初始化。。");
    }
}
class UnstaticFinal
{
    UnstaticFinal()
    {
        System.out.println("非静态final的成员变量的初始化。。");
    }
}
class UnStatic
{
    UnStatic()
    {
        System.out.println("非静态非final的成员变量初始化。。");
    }
}

运行结果:
java第十章学习笔记:构造器---对象制造的工厂_第12张图片
从图中的执行结果可以看出,加载顺序如图:
这里写图片描述
提示:关于父类也有静态、非静态成员或多级继承的规则依次类推。

10.7 第十章完。~~

java第十章学习笔记:构造器---对象制造的工厂_第13张图片

你可能感兴趣的:(java基础学习笔记)