精华内容
下载资源
问答
  • 实例化

    千次阅读 2012-07-09 18:06:38
    实例化 - 定义 在面向对象的编程中,通常把用类创建对象的过程称为实例化,其格式如下: 类名 对象名 = new 类名(参数1,参数2...参数n) (这里,“类名”这个类名实例化成了“对象名”这个对象) 实例化 ...

    实例化 - 定义

    在面向对象的编程中,通常把用类创建对象的过程称为实例化,其格式如下:
    类名 对象名 = new 类名(参数1,参数2...参数n)
    (这里,“类名”这个类名实例化成了“对象名”这个对象)

    实例化 - 例子

    Date date=new Date();

    就是用日期类创建了一个日期的对象,就叫对象的实例化。
    多数语言中,实例化一个对象 其实就是在内存中开放一个空间 用于存储新的产物,即对象。

    实例化就是给,数据成员分配内存,构造对象。对象的成员函数和普通函数的区别就是,成员函数有个指向当前对象的this指针,可以访问对象的成员变量,其依赖于对象。静态函数就更像一个全局函数,没有this指针,可以通过类名直接访问,可以被对象的成员函数访问,静态函数只能访问类的静态成员。这些可访问属性是由编译器来实现的,编译之后就没有对象、成员这些东西了,都是数据和方法。

    实例化 - 什么是对象

    (object)是一件事、一个实体、一个名词,可以获得的东西,可以想象有自己的标识的任何东西。对象是类的实例化。一些对象是活的,一些对象不是。
    比如一辆汽车一个人、一间房子、一张桌子、一株植物、一张支票、一件雨衣。 概括来说就是:万物皆对象。
    面向对象(ObjectOriented,OO)是当前计算机界关心的重点,它是90年代软件开发方法的主流。面向对象的概念和应用已超越了程序设计和软件开发,扩展到很宽的范围。如数据库系统、交互式界面、应用结构、应用平台、分布式系统、网络管理结构、CAD技术、人工智能等领域。
    ASP动态网页中,对象是一个抽象的概念,是要操作的目标。比如,在现实生活中,电脑就是我们搜寻资料的一个对象,他具有外观、操作系统、价格等等特点,这在对象概念中被称为属性,而利用这个电脑玩游戏、看电影、查找资料等用途,这就对应于对象里的方法,另外,主板、CPU、显卡、键盘等等组件,我们可以称作对象的集合。
    在ASP动态网页中,对象的特点归结起来有三个:属性、方法、集合。

    实例化 - 面向对象的基本概念

    (1)类
      具有相同或相似性质的对象的抽象就是类。因此,对象的抽象是类,类的具体化就是对象,也可以说类的实例是对象。
      类具有属性,它是对象的状态的抽象,用数据结构来描述类的属性。
      类具有操作,它是对象的行为的抽象,用操作名和实现该操作的方法来描述。
    (2)类的结构
      在客观世界中有若干类,这些类之间有一定的结构关系。通常有两种主要的结构关系,即一般--具体结构关系,整体--部分结构关系。
      ①一般——具体结构称为分类结构,也可以说是“或”关系,或者是“is a”关系。
      ②整体——部分结构称为组装结构,它们之间的关系是一种“与”关系,或者是“has a”关系。
    (3)对象
      对象是人们要进行研究的任何事物,从最简单的整数到复杂的飞机等均可看作对象,它不仅能表示具体的事物,还能表示抽象的规则、计划或事件。
    (4)对象的状态和行为
      对象具有状态,一个对象用数据值来描述它的状态。
      对象还有操作,用于改变对象的状态,对象及其操作就是对象的行为。
      对象实现了数据和操作的结合,使数据和操作封装于对象的统一体中。

    在面向对象的语言中,程序员们就将一类有共同属性的事务封装成一个类,如果我们要说一个具体的哪一个事物,我们就需要对这个类作实例化,实例化时会用到new关键字来生成这个类的一个具体的对象。

    展开全文
  • Spring解析,加载及实例化Bean的顺序(零配置)

    万次阅读 多人点赞 2018-02-16 14:23:31
    Spring零配置下@Bean,@Import,@ComponentScan形式定义Bean的解析,加载及实例化顺序

    在使用Spring时,Bean之间会有些依赖,比如一个Bean A实例化时需要用到Bean B,那么B应该在A之前实例化好。很多时候Spring智能地为我们做好了这些工作,但某些情况下可能不是,比如Springboot的@AutoConfigureAfter注解,手动的指定Bean的实例化顺序。了解Spring内Bean的解析,加载和实例化顺序机制有助于我们更好的使用Spring/Springboot,避免手动的去干预Bean的加载过程,搭建更优雅的框架。

    Spring容器在实例化时会加载容器内所有非延迟加载的单例类型Bean,看如下源码:

    public abstract class AbstractApplicationContext extends DefaultResourceLoader
    		implements ConfigurableApplicationContext, DisposableBean {
    	
    	//刷新Spring容器,相当于初始化
    	public void refresh() throws BeansException, IllegalStateException {
    		......
    		// Instantiate all remaining (non-lazy-init) singletons.
    		finishBeanFactoryInitialization(beanFactory);
    	}
    }
    
    public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
    		implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
    		
    	/** List of bean definition names, in registration order */
    	private volatile List<String> beanDefinitionNames = new ArrayList<String>(256);
    
    	public void preInstantiateSingletons() throws BeansException {
    		List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);
    		for (String beanName : beanNames) {
    			......
    			getBean(beanName);  //实例化Bean
    		}
    	}
    
    }
    

    ApplicationContext内置一个BeanFactory对象,作为实际的Bean工厂,和Bean相关业务都交给BeanFactory去处理。
    在BeanFactory实例化所有非延迟加载的单例Bean时,遍历beanDefinitionNames 集合,按顺序实例化指定名称的Bean。beanDefinitionNames 属性是Spring在加载Bean Class生成的BeanDefinition时,为这些Bean预先定义好的名称,看如下代码:

    public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
    		implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
    	
    	public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
    			throws BeanDefinitionStoreException {
    		......
    		this.beanDefinitionNames.add(beanName);
    	}
    }
    

    BeanFactory在加载一个BeanDefinition(也就是加载Bean Class)时,将相应的beanName存入beanDefinitionNames属性中,在加载完所有的BeanDefinition后,执行Bean实例化工作,此时会依据beanDefinitionNames的顺序来有序实例化Bean,也就是说Spring容器内Bean的加载和实例化是有顺序的,而且近似一致,当然仅是近似。

    Spring在初始化容器时,会先解析和加载所有的Bean Class,如果符合要求则通过Class生成BeanDefinition,存入BeanFactory中,在加载完所有Bean Class后,开始有序的通过BeanDefinition实例化Bean。

    我们先看加载Bean Class过程,零配置下Spring Bean的加载起始于ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry(BeanDefinitionRegistry)方法,我总结了下其加载解析Bean Class的流程:

    Spring解析配置类流程

    配置类可以是Spring容器的起始配置类,也可以是通过@ComponentScan扫描得到的类,也可以是通过@Import引入的类。如果这个类上含有@Configuration,@Component,@ComponentScan,@Import,@ImportResource注解中的一个,或者内部含有@Bean标识的方法,那么这个类就是一个配置类,Spring就会按照一定流程去解析这个类上的信息。

    在解析的第一步会校验当前类是否已经被解析过了,如果是,那么需要按照一定的规则处理(@ComponentScan得到的Bean能覆盖@Import得到的Bean,@Bean定义的优先级最高)。

    如果未解析过,那么开始解析:

    1. 解析内部类,查看内部类是否应该被定义成一个Bean,如果是,递归解析。
    2. 解析@PropertySource,也就是解析被引入的Properties文件。
    3. 解析配置类上是否有@ComponentScan注解,如果有则执行扫描动作,通过扫描得到的Bean Class会被立即解析成BeanDefinition,添加进beanDefinitionNames属性中。之后查看扫描到的Bean Class是否是一个配置类(大部分情况是,因为标识@Component注解),如果是则递归解析这个Bean Class。
    4. 解析@Import引入的类,如果这个类是一个配置类,则递归解析。
    5. 解析@Bean标识的方法,此种形式定义的Bean Class不会被递归解析
    6. 解析父类上的@ComponentScan,@Import,@Bean,父类不会被再次实例化,因为其子类能够做父类的工作,不需要额外的Bean了。

    在1,3,4,6中都有递归操作,也就是在解析一个Bean Class A时,发现其上能够获取到其他Bean Class B信息,此时会递归的解析Bean Class B,在解析完Bean Class B后再接着解析Bean Class A,可能在解析B时能够获取到C,那么也会先解析C再解析B,就这样不断的递归解析。

    在第3步中,通过@ComponentScan扫描直接得到的Bean Class会被立即加载入beanDefinitionNames中,但@Import和@Bean形式定义的Bean Class则不会,也就是说正常情况下面@ComponentScan直接得到的Bean其实例化时机比其他两种形式的要早。

    通过@Bean和@Import形式定义的Bean Class不会立即加载,他们会被放入一个ConfigurationClass类中,然后按照解析的顺序有序排列,就是图片上的 “将配置类有序排列”。一个ConfigurationClass代表一个配置类,这个类可能是被@ComponentScan扫描到的,则此类已经被加载过了;也可能是被@Import引入的,则此类还未被加载;此类中可能含有@Bean标识的方法。

    Spring在解析完了所有Bean Class后,开始加载ConfigurationClass。如果这个ConfigurationClass是被Import的,也就是说在加载@ComponentScan时其未被加载,那么此时加载ConfigurationClass代表的Bean Class。然后加载ConfigurationClass内的@Bean方法。

    顺序总结:@ComponentScan > @Import > @Bean

    下面看实际的启动流程:

    Spring解析ConfigurationClass示例图

    此图顺序验证小框架:Spring Bean解析,加载及实例化顺序验证小框架

    Bean Class的结构图如上所示,A是配置类的入口,通过A能直接或间接的引入一个模块。

    此时启动Spring容器,将A引入容器内。

    如果A是通过@ComponentScan扫描到的,那么此时的加载顺序是:
    A > D > F > B > E > G > C

    如果A是通过@Import形式引入的,那么此时的加载顺讯是:
    D > F > B > E > G > A > C

    当然以上仅仅代表着加载Bean Class的顺序,实际实例化Bean的顺序和加载顺序大体相同,但还是会有一些差别。
    Spring在通过getBean(beanName)形式实例化Bean时,会通过BeanDefinition去生成Bean对象。在这个过程中,如果BeanDefinition的DependsOn不为空,从字面理解就是依赖某个什么,其值一般是某个或多个beanName,也就是说依赖于其他Bean,此时Spring会将DependsOn指定的这些名称的Bean先实例化,也就是先调用getBean(dependsOn)方法。我们可以通过在Bean Class或者@Bean的方法上标识**@DependsOn**注解,来指定当前Bean实例化时需要触发哪些Bean的提前实例化。

    当一个Bean A内部通过@Autowired或者@Resource注入Bean B,那么在实例化A时会触发B的提前实例化,此时会注册A>B的dependsOn依赖关系,实质和@DependsOn一样,这个是Spring自动为我们处理好的。

    了解Spring Bean的解析,加载及实例化的顺序机制能够加深对Spring的理解,搭建更优雅简介的Spring框架。

    展开全文
  • 动态实例化和静态实例化

    千次阅读 2014-09-26 18:53:06
    实例化:创建一个对象的过程 静态实例化:在编译时刻完成的实例化 动态实例化:在运行时刻完成的实例化 Java语言都是动态实例化
    实例化:创建一个对象的过程
    静态实例化:在编译时刻完成的实例化
    动态实例化:在运行时刻完成的实例化
    Java语言都是动态实例化
    展开全文
  • 深入理解Java对象的创建过程:类的初始化与实例化

    万次阅读 多人点赞 2017-05-18 14:17:45
    实例化一个对象时,JVM首先会检查相关类型是否已经加载并初始化,如果没有,则JVM立即进行加载并调用类构造器完成类的初始化。在类初始化过程中或初始化完毕后,根据具体情况才会去对类进行实例化。本文试图对JVM...

    摘要:

      在Java中,一个对象在可以被使用之前必须要被正确地初始化,这一点是Java规范规定的。在实例化一个对象时,JVM首先会检查相关类型是否已经加载并初始化,如果没有,则JVM立即进行加载并调用类构造器完成类的初始化。在类初始化过程中或初始化完毕后,根据具体情况才会去对类进行实例化。本文试图对JVM执行类初始化和实例化的过程做一个详细深入地介绍,以便从Java虚拟机的角度清晰解剖一个Java对象的创建过程。


    版权声明:

    本文原创作者:书呆子Rico
    作者博客地址:http://blog.csdn.net/justloveyou_/


    友情提示:

      一个Java对象的创建过程往往包括 类初始化 类实例化 两个阶段。本文的姊妹篇《 JVM类加载机制概述:加载时机与加载过程》主要介绍了类的初始化时机和初始化过程,本文在此基础上,进一步阐述了一个Java对象创建的真实过程。


    一、Java对象创建时机

      我们知道,一个对象在可以被使用之前必须要被正确地实例化。在Java代码中,有很多行为可以引起对象的创建,最为直观的一种就是使用new关键字来调用一个类的构造函数显式地创建对象,这种方式在Java规范中被称为 : 由执行类实例创建表达式而引起的对象创建。除此之外,我们还可以使用反射机制(Class类的newInstance方法、使用Constructor类的newInstance方法)、使用Clone方法、使用反序列化等方式创建对象。下面笔者分别对此进行一一介绍:


    1). 使用new关键字创建对象

      这是我们最常见的也是最简单的创建对象的方式,通过这种方式我们可以调用任意的构造函数(无参的和有参的)去创建对象。比如:

      Student student = new Student();

    2). 使用Class类的newInstance方法(反射机制)

      我们也可以通过Java的反射机制使用Class类的newInstance方法来创建对象,事实上,这个newInstance方法调用无参的构造器创建对象,比如:

      Student student2 = (Student)Class.forName("Student类全限定名").newInstance(); 
    或者:
      Student stu = Student.class.newInstance();

    3). 使用Constructor类的newInstance方法(反射机制)

      java.lang.relect.Constructor类里也有一个newInstance方法可以创建对象,该方法和Class类中的newInstance方法很像,但是相比之下,Constructor类的newInstance方法更加强大些,我们可以通过这个newInstance方法调用有参数的和私有的构造函数,比如:

    public class Student {
    
        private int id;
    
        public Student(Integer id) {
            this.id = id;
        }
    
        public static void main(String[] args) throws Exception {
    
            Constructor<Student> constructor = Student.class
                    .getConstructor(Integer.class);
            Student stu3 = constructor.newInstance(123);
        }
    }

      使用newInstance方法的这两种方式创建对象使用的就是Java的反射机制,事实上Class的newInstance方法内部调用的也是Constructor的newInstance方法。


    4). 使用Clone方法创建对象

      无论何时我们调用一个对象的clone方法,JVM都会帮我们创建一个新的、一样的对象,特别需要说明的是,用clone方法创建对象的过程中并不会调用任何构造函数。关于如何使用clone方法以及浅克隆/深克隆机制,笔者已经在博文《 Java String 综述(下篇)》做了详细的说明。简单而言,要想使用clone方法,我们就必须先实现Cloneable接口并实现其定义的clone方法,这也是原型模式的应用。比如:

    public class Student implements Cloneable{
    
        private int id;
    
        public Student(Integer id) {
            this.id = id;
        }
    
        @Override
        protected Object clone() throws CloneNotSupportedException {
            // TODO Auto-generated method stub
            return super.clone();
        }
    
        public static void main(String[] args) throws Exception {
    
            Constructor<Student> constructor = Student.class
                    .getConstructor(Integer.class);
            Student stu3 = constructor.newInstance(123);
            Student stu4 = (Student) stu3.clone();
        }
    }

    5). 使用(反)序列化机制创建对象

      当我们反序列化一个对象时,JVM会给我们创建一个单独的对象,在此过程中,JVM并不会调用任何构造函数。为了反序列化一个对象,我们需要让我们的类实现Serializable接口,比如:

    public class Student implements Cloneable, Serializable {
    
        private int id;
    
        public Student(Integer id) {
            this.id = id;
        }
    
        @Override
        public String toString() {
            return "Student [id=" + id + "]";
        }
    
        public static void main(String[] args) throws Exception {
    
            Constructor<Student> constructor = Student.class
                    .getConstructor(Integer.class);
            Student stu3 = constructor.newInstance(123);
    
            // 写对象
            ObjectOutputStream output = new ObjectOutputStream(
                    new FileOutputStream("student.bin"));
            output.writeObject(stu3);
            output.close();
    
            // 读对象
            ObjectInputStream input = new ObjectInputStream(new FileInputStream(
                    "student.bin"));
            Student stu5 = (Student) input.readObject();
            System.out.println(stu5);
        }
    }

    6). 完整实例

    public class Student implements Cloneable, Serializable {
    
        private int id;
    
        public Student() {
    
        }
    
        public Student(Integer id) {
            this.id = id;
        }
    
        @Override
        protected Object clone() throws CloneNotSupportedException {
            // TODO Auto-generated method stub
            return super.clone();
        }
    
        @Override
        public String toString() {
            return "Student [id=" + id + "]";
        }
    
        public static void main(String[] args) throws Exception {
    
            System.out.println("使用new关键字创建对象:");
            Student stu1 = new Student(123);
            System.out.println(stu1);
            System.out.println("\n---------------------------\n");
    
    
            System.out.println("使用Class类的newInstance方法创建对象:");
            Student stu2 = Student.class.newInstance();    //对应类必须具有无参构造方法,且只有这一种创建方式
            System.out.println(stu2);
            System.out.println("\n---------------------------\n");
    
            System.out.println("使用Constructor类的newInstance方法创建对象:");
            Constructor<Student> constructor = Student.class
                    .getConstructor(Integer.class);   // 调用有参构造方法
            Student stu3 = constructor.newInstance(123);   
            System.out.println(stu3);
            System.out.println("\n---------------------------\n");
    
            System.out.println("使用Clone方法创建对象:");
            Student stu4 = (Student) stu3.clone();
            System.out.println(stu4);
            System.out.println("\n---------------------------\n");
    
            System.out.println("使用(反)序列化机制创建对象:");
            // 写对象
            ObjectOutputStream output = new ObjectOutputStream(
                    new FileOutputStream("student.bin"));
            output.writeObject(stu4);
            output.close();
    
            // 读取对象
            ObjectInputStream input = new ObjectInputStream(new FileInputStream(
                    "student.bin"));
            Student stu5 = (Student) input.readObject();
            System.out.println(stu5);
    
        }
    }/* Output: 
            使用new关键字创建对象:
            Student [id=123]
    
            ---------------------------
    
            使用Class类的newInstance方法创建对象:
            Student [id=0]
    
            ---------------------------
    
            使用Constructor类的newInstance方法创建对象:
            Student [id=123]
    
            ---------------------------
    
            使用Clone方法创建对象:
            Student [id=123]
    
            ---------------------------
    
            使用(反)序列化机制创建对象:
            Student [id=123]
    *///:~

      从Java虚拟机层面看,除了使用new关键字创建对象的方式外,其他方式全部都是通过转变为invokevirtual指令直接创建对象的。


    二. Java 对象的创建过程

      当一个对象被创建时,虚拟机就会为其分配内存来存放对象自己的实例变量及其从父类继承过来的实例变量(即使这些从超类继承过来的实例变量有可能被隐藏也会被分配空间)。在为这些实例变量分配内存的同时,这些实例变量也会被赋予默认值(零值)。在内存分配完成之后,Java虚拟机就会开始对新创建的对象按照程序猿的意志进行初始化。在Java对象初始化过程中,主要涉及三种执行对象初始化的结构,分别是 实例变量初始化实例代码块初始化 以及 构造函数初始化


    1、实例变量初始化与实例代码块初始化

      我们在定义(声明)实例变量的同时,还可以直接对实例变量进行赋值或者使用实例代码块对其进行赋值。如果我们以这两种方式为实例变量进行初始化,那么它们将在构造函数执行之前完成这些初始化操作。实际上,如果我们对实例变量直接赋值或者使用实例代码块赋值,那么编译器会将其中的代码放到类的构造函数中去,并且这些代码会被放在对超类构造函数的调用语句之后(还记得吗?Java要求构造函数的第一条语句必须是超类构造函数的调用语句),构造函数本身的代码之前。例如:

    public class InstanceVariableInitializer {  
    
        private int i = 1;  
        private int j = i + 1;  
    
        public InstanceVariableInitializer(int var){
            System.out.println(i);
            System.out.println(j);
            this.i = var;
            System.out.println(i);
            System.out.println(j);
        }
    
        {               // 实例代码块
            j += 3; 
    
        }
    
        public static void main(String[] args) {
            new InstanceVariableInitializer(8);
        }
    }/* Output: 
                1
                5
                8
                5
     *///:~

      上面的例子正好印证了上面的结论。特别需要注意的是,Java是按照编程顺序来执行实例变量初始化器和实例初始化器中的代码的,并且不允许顺序靠前的实例代码块初始化在其后面定义的实例变量,比如:

    public class InstanceInitializer {  
        {  
            j = i;  
        }  
    
        private int i = 1;  
        private int j;  
    }  
    
    public class InstanceInitializer {  
        private int j = i;  
        private int i = 1;  
    }  

      上面的这些代码都是无法通过编译的,编译器会抱怨说我们使用了一个未经定义的变量。之所以要这么做是为了保证一个变量在被使用之前已经被正确地初始化。但是我们仍然有办法绕过这种检查,比如:

    public class InstanceInitializer {  
        private int j = getI();  
        private int i = 1;  
    
        public InstanceInitializer() {  
            i = 2;  
        }  
    
        private int getI() {  
            return i;  
        }  
    
        public static void main(String[] args) {  
            InstanceInitializer ii = new InstanceInitializer();  
            System.out.println(ii.j);  
        }  
    }  

      如果我们执行上面这段代码,那么会发现打印的结果是0。因此我们可以确信,变量j被赋予了i的默认值0,这一动作发生在实例变量i初始化之前和构造函数调用之前。


    2、构造函数初始化

      我们可以从上文知道,实例变量初始化与实例代码块初始化总是发生在构造函数初始化之前,那么我们下面着重看看构造函数初始化过程。众所周知,每一个Java中的对象都至少会有一个构造函数,如果我们没有显式定义构造函数,那么它将会有一个默认无参的构造函数。在编译生成的字节码中,这些构造函数会被命名成<init>()方法,参数列表与Java语言书写的构造函数的参数列表相同。

      我们知道,Java要求在实例化类之前,必须先实例化其超类,以保证所创建实例的完整性。事实上,这一点是在构造函数中保证的:Java强制要求Object对象(Object是Java的顶层对象,没有超类)之外的所有对象构造函数的第一条语句必须是超类构造函数的调用语句或者是类中定义的其他的构造函数,如果我们既没有调用其他的构造函数,也没有显式调用超类的构造函数,那么编译器会为我们自动生成一个对超类构造函数的调用,比如:

    public class ConstructorExample {  
    
    } 

      对于上面代码中定义的类,我们观察编译之后的字节码,我们会发现编译器为我们生成一个构造函数,如下,

    aload_0  
    invokespecial   #8; //Method java/lang/Object."<init>":()V  
    return  

      上面代码的第二行就是调用Object类的默认构造函数的指令。也就是说,如果我们显式调用超类的构造函数,那么该调用必须放在构造函数所有代码的最前面,也就是必须是构造函数的第一条指令。正因为如此,Java才可以使得一个对象在初始化之前其所有的超类都被初始化完成,并保证创建一个完整的对象出来。


      特别地,如果我们在一个构造函数中调用另外一个构造函数,如下所示,

    public class ConstructorExample {  
        private int i;  
    
        ConstructorExample() {  
            this(1);  
            ....  
        }  
    
        ConstructorExample(int i) {  
            ....  
            this.i = i;  
            ....  
        }  
    }  

      对于这种情况,Java只允许在ConstructorExample(int i)内调用超类的构造函数,也就是说,下面两种情形的代码编译是无法通过的:

    public class ConstructorExample {  
        private int i;  
    
        ConstructorExample() {  
            super();  
            this(1);  // Error:Constructor call must be the first statement in a constructor
            ....  
        }  
    
        ConstructorExample(int i) {  
            ....  
            this.i = i;  
            ....  
        }  
    }  

    或者,

    public class ConstructorExample {  
        private int i;  
    
        ConstructorExample() {  
            this(1);  
            super();  //Error: Constructor call must be the first statement in a constructor
            ....  
        }  
    
        ConstructorExample(int i) {  
            this.i = i;  
        }  
    }   

      Java通过对构造函数作出这种限制以便保证一个类的实例能够在被使用之前正确地初始化。


    3、 小结

      总而言之,实例化一个类的对象的过程是一个典型的递归过程,如下图所示。进一步地说,在实例化一个类的对象时,具体过程是这样的:

      在准备实例化一个类的对象前,首先准备实例化该类的父类,如果该类的父类还有父类,那么准备实例化该类的父类的父类,依次递归直到递归到Object类。此时,首先实例化Object类,再依次对以下各类进行实例化,直到完成对目标类的实例化。具体而言,在实例化每个类时,都遵循如下顺序:先依次执行实例变量初始化和实例代码块初始化,再执行构造函数初始化。也就是说,编译器会将实例变量初始化和实例代码块初始化相关代码放到类的构造函数中去,并且这些代码会被放在对超类构造函数的调用语句之后,构造函数本身的代码之前。

                 这里写图片描述

                    
      Ps: 关于递归的思想与内涵的介绍,请参见我的博文《 算法设计方法:递归的内涵与经典应用》


    4、实例变量初始化、实例代码块初始化以及构造函数初始化综合实例

      笔者在《 JVM类加载机制概述:加载时机与加载过程》一文中详细阐述了类初始化时机和初始化过程,并在文章的最后留了一个悬念给各位,这里来揭开这个悬念。建议读者先看完《 JVM类加载机制概述:加载时机与加载过程》这篇再来看这个,印象会比较深刻,如若不然,也没什么关系~~
      

    //父类
    class Foo {
        int i = 1;
    
        Foo() {
            System.out.println(i);             -----------(1)
            int x = getValue();
            System.out.println(x);             -----------(2)
        }
    
        {
            i = 2;
        }
    
        protected int getValue() {
            return i;
        }
    }
    
    //子类
    class Bar extends Foo {
        int j = 1;
    
        Bar() {
            j = 2;
        }
    
        {
            j = 3;
        }
    
        @Override
        protected int getValue() {
            return j;
        }
    }
    
    public class ConstructorExample {
        public static void main(String... args) {
            Bar bar = new Bar();
            System.out.println(bar.getValue());             -----------(3)
        }
    }/* Output: 
                2
                0
                2
     *///:~

      根据上文所述的类实例化过程,我们可以将Foo类的构造函数和Bar类的构造函数等价地分别变为如下形式:

        //Foo类构造函数的等价变换:
        Foo() {
            i = 1;
            i = 2;
            System.out.println(i);
            int x = getValue();
            System.out.println(x);
        }
        //Bar类构造函数的等价变换
        Bar() {
            Foo();
            j = 1;
            j = 3;
            j = 2
        }

      这样程序就好看多了,我们一眼就可以观察出程序的输出结果。在通过使用Bar类的构造方法new一个Bar类的实例时,首先会调用Foo类构造函数,因此(1)处输出是2,这从Foo类构造函数的等价变换中可以直接看出。(2)处输出是0,为什么呢?因为在执行Foo的构造函数的过程中,由于Bar重载了Foo中的getValue方法,所以根据Java的多态特性可以知道,其调用的getValue方法是被Bar重载的那个getValue方法。但由于这时Bar的构造函数还没有被执行,因此此时j的值还是默认值0,因此(2)处输出是0。最后,在执行(3)处的代码时,由于bar对象已经创建完成,所以此时再访问j的值时,就得到了其初始化后的值2,这一点可以从Bar类构造函数的等价变换中直接看出。


    三. 类的初始化时机与过程

      关于类的初始化时机,笔者在博文《 JVM类加载机制概述:加载时机与加载过程》已经介绍的很清楚了,此处不再赘述。简单地说,在类加载过程中,准备阶段是正式为类变量(static 成员变量)分配内存并设置类变量初始值(零值)的阶段,而初始化阶段是真正开始执行类中定义的java程序代码(字节码)并按程序猿的意图去初始化类变量的过程。更直接地说,初始化阶段就是执行类构造器<clinit>()方法的过程。<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态代码块static{}中的语句合并产生的,其中编译器收集的顺序是由语句在源文件中出现的顺序所决定。

      类构造器<clinit>()与实例构造器<init>()不同,它不需要程序员进行显式调用,虚拟机会保证在子类类构造器<clinit>()执行之前,父类的类构造<clinit>()执行完毕。由于父类的构造器<clinit>()先执行,也就意味着父类中定义的静态代码块/静态变量的初始化要优先于子类的静态代码块/静态变量的初始化执行。特别地,类构造器<clinit>()对于类或者接口来说并不是必需的,如果一个类中没有静态代码块,也没有对类变量的赋值操作,那么编译器可以不为这个类生产类构造器<clinit>()。此外,在同一个类加载器下,一个类只会被初始化一次,但是一个类可以任意地实例化对象。也就是说,在一个类的生命周期中,类构造器<clinit>()最多会被虚拟机调用一次,而实例构造器<init>()则会被虚拟机调用多次,只要程序员还在创建对象。

      注意,这里所谓的实例构造器<init>()是指收集类中的所有实例变量的赋值动作、实例代码块和构造函数合并产生的,类似于上文对Foo类的构造函数和Bar类的构造函数做的等价变换。


    四. 总结

      1、一个实例变量在对象初始化的过程中会被赋值几次?

      我们知道,JVM在为一个对象分配完内存之后,会给每一个实例变量赋予默认值,这个时候实例变量被第一次赋值,这个赋值过程是没有办法避免的。如果我们在声明实例变量x的同时对其进行了赋值操作,那么这个时候,这个实例变量就被第二次赋值了。如果我们在实例代码块中,又对变量x做了初始化操作,那么这个时候,这个实例变量就被第三次赋值了。如果我们在构造函数中,也对变量x做了初始化操作,那么这个时候,变量x就被第四次赋值。也就是说,在Java的对象初始化过程中,一个实例变量最多可以被初始化4次。


      2、类的初始化过程与类的实例化过程的异同?

      类的初始化是指类加载过程中的初始化阶段对类变量按照程序猿的意图进行赋值的过程;而类的实例化是指在类完全加载到内存中后创建对象的过程。


      3、假如一个类还未加载到内存中,那么在创建一个该类的实例时,具体过程是怎样的?

      我们知道,要想创建一个类的实例,必须先将该类加载到内存并进行初始化,也就是说,类初始化操作是在类实例化操作之前进行的,但并不意味着:只有类初始化操作结束后才能进行类实例化操作。例如,笔者在博文《 JVM类加载机制概述:加载时机与加载过程》中所提到的下面这个经典案例:

    public class StaticTest {
        public static void main(String[] args) {
            staticFunction();
        }
    
        static StaticTest st = new StaticTest();
    
        static {   //静态代码块
            System.out.println("1");
        }
    
        {       // 实例代码块
            System.out.println("2");
        }
    
        StaticTest() {    // 实例构造器
            System.out.println("3");
            System.out.println("a=" + a + ",b=" + b);
        }
    
        public static void staticFunction() {   // 静态方法
            System.out.println("4");
        }
    
        int a = 110;    // 实例变量
        static int b = 112;     // 静态变量
    }/* Output: 
            2
            3
            a=110,b=0
            1
            4
     *///:~

      大家能得到正确答案吗?笔者已经在博文《 JVM类加载机制概述:加载时机与加载过程》中解释过这个问题了,此不赘述。
      
      总的来说,类实例化的一般过程是:父类的类构造器<clinit>() -> 子类的类构造器<clinit>() -> 父类的成员变量和实例代码块 -> 父类的构造函数 -> 子类的成员变量和实例代码块 -> 子类的构造函数。


    五. 更多

      更多关于类初始化时机和初始化过程的介绍,请参见我的博文《 JVM类加载机制概述:加载时机与加载过程》

      更多关于类加载器等方面的内容,包括JVM预定义的类加载器、双亲委派模型等知识点,请参见我的转载博文《深入理解Java类加载器(一):Java类加载原理解析》

      关于递归的思想与内涵的介绍,请参见我的博文《 算法设计方法:递归的内涵与经典应用》


    引用:

    Java对象初始化详解
    Java中创建对象的几种方式

    展开全文
  • 一、什么是实例化和具体化?  为进一步了解模板,必须理解术语实例化和具体化。  (1)、实例化:在程序中的函数模板本身并不会生成函数定义,它只是一个用于生成函数定义的方案。编译器使用模板为特定类型生成...
  • Spring系列之实例化、初始化处理器

    千次阅读 多人点赞 2020-05-11 22:03:46
    谈论到实例化处理器和初始化处理器,我们要明确一个实例化和初始化概念,大牛们可以忽略此处说明。 实例化:指创建类实例(对象)的过程。比如使用构造方法new对象,为对象在内存中分配空间。(要创建对象,但是并...
  •  在控制器中定义一个方法来实例化模型,使用的是普通方式实例化:创建部门控制器文件: 实例化代码: 实例化结果: 5.2、快速实例化方法上述的普通实例化方法虽然可以进行实例化操作,但是使用上比较麻烦,还...
  • 抽象类无法直接实例化,它的实例化方式并不是通过普通的new方式来创建对象,而是通过父类的应用来指向子类的实例间接地实现父类的实例化,因为子类在实例化之前,一定会先实例化它的父类。这样创建了继承抽象类的...
  • Java的实例化

    千次阅读 2019-04-10 16:28:00
    实例化是什么 1.在Java中,使用已经定义好的类,创建该类对象的过程称为“实例化”。 2.实例化就是在堆中分配内存的过程,分配的内存即对象。 3.只有实例化后的对象,才能访问到类中的成员变量和方法。 实例化的特点...
  • tp跨模块实例化自定义模型

    万次阅读 2018-12-17 14:23:58
    分享一个大神的人工智能教程。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到人工智能的队伍中来!... 在tp3.2中,D方法用于实例化自定义的数据库模型;...//实例化到的数据表为hs_financial_pr...
  • 模板的实例化与具体化

    千次阅读 2019-06-22 11:27:12
    实例化:模板本身不会生成函数或类定义,它只是一个用于生成函数或类的方案,编译器使用模板为特定类型生成函数或类定义的过程叫做模板的实例化。 具体化:为模板中抽象的泛型指定具体的类型。它包含隐式实例化、...
  • 隐式模板实例化使用模板函数或模板类时需要实例。 如果这种实例还不存在,则编译器隐式实例化模板参数组合的模板。显式模板实例化编译器仅为实际使用的那些模板参数组合而隐式实例化模板。 该方法不适用于构造提供...
  • java实例化对象

    千次阅读 2016-05-15 08:35:19
    摘要:分享牛,分享牛分享,java类加载机制,java实例化对象,java实例化对象机制,java基础。java是如何实例化对象的呢?以及实例化对象的先后顺序是什么?下面我们以测试的方式说明.1.1. 声明父类public class A ...
  • 实例化和初始化的区别

    千次阅读 2019-09-02 11:00:54
    一、实例化和初始化的区别 实例化:只是单纯的把对象 new 一下就行了 例:Student st=new Student();这样就行了 初始化:是在实例化的基础上,并且对 对象中的值进行赋一下初始值(这样的好处是避免当你没有赋值的...
  • 实例化对象写法的区别 栈中实例化对象: 堆中实例化对象: 最大区别就在于,从栈中申请内存,在使用完后无需释放,但是从堆中申请内存,使用完以后必须释放! 注意:从堆中申请内存,是有可能失败的,所以...
  • Spring容器实例化、bean的实例化

    千次阅读 2014-08-14 21:37:58
    一、实例化Spring容器的方式:  ·在类路径(classpath)下寻找配置文件来实例化容器:ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");   注意:括号中的参数是...
  • ButterKnife实例化控件

    千次阅读 2018-09-29 09:48:55
    用于快速实例化控件的一种插件   安装ButterKnife 第一步,下载插件,如图操作 AndroidStudio 先组合键操作:Ctrl+Alt+S,弹出窗口选中Plugins选项   之后点击按钮Browse repositories...
  • 这样做可以减小代码的书写的复杂度,同时也便于修改(注:使用模板函数并不会减少最终可执行程序的大小,因为在调用模板函数时,编译器都根据调用时的参数类型进行了相应实例化)。下面来看看函数模板的使用过程: ...
  • Java 接口实例化

    千次阅读 2019-08-09 10:14:46
    1、接口不能直接实例化对象 一开始学习接口就知道它与类的区别:接口不能用于实例化对象。但是可以在 Java 中,使用接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。 ...
  • ArrayList实例化

    千次阅读 2018-05-14 21:53:55
    ArrayList的实例化方法:ArrayList&lt;String&gt;List = new ArrayList&lt;String&gt;();ArrayList:长度可变的数组,集合对象。动态的增加和减少元素;实现了ICollection和IList接口;灵活的设置...
  • C++模板实例化与调用

    千次阅读 2015-10-29 13:13:41
    模板的实例化指函数模板(类模板)生成模板函数(模板类)的过程。对于函数模板而言,模板实例化之后,会生成一个真正的函数。而类模板经过实例化之后,只是完成了类的定义,模板类的成员函数需要到调用时才会被初始...
  • C++模板实例化 分类: c++2011-03-29 12:12 1873人阅读 评论(0) 收藏 举报 c++编译器cclass 实例化:一个通过使用具体值替换模板参数,从模板产生的普通类,函数或者成员函数的过程。 特化:就是...
  • C#:实例化

    千次阅读 2019-02-16 17:48:16
    可能大家看到我这篇博客标题有些费解:C#视频都快学完了,你怎么还在纠结实例化这类基本概念?别急,学习就是一个循循渐进、利用新知完善旧解的过程,正因为这个概念是基本概念,我们更要将其搞懂。   相信学了...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 221,017
精华内容 88,406
关键字:

实例化