精华内容
下载资源
问答
  • 2015-08-30 08:34:38

    类的初始化和对象初始化是两个不同的概念。类的初始化是发生在类加载过程,是类加载过程的一个阶段,该阶段并不调用类的构造器。而对象的初始化是在类加载完成后为对象分配内存,实例变量的初始化,实例变量的赋值及调用类构造器完成对象的初始化过程。对象初始化也称为对象实例化。本文主要是探索和分析类的加载过程及对象的实例化过程,主要参考《java编程思想》和《深入理解java虚拟机》,文章有错误之处还希望大家批评指正。

    类加载

    每个类编译后都对应一个独立的class文件,该文件只有在需要使用时才会被加载。一般来说,Class文件在初次使用时才加载,即一般情况下只加载一次。类的加载过程包括几个阶段:加载,连接(包括:验证,准备,解析),初始化,使用,卸载。这些阶段只是按顺序的开始并不代表一个阶段完成之后接着执行下一个阶段

    类加载发生在以下几种情况:
    1)new生成新的对象实例。
    2)使用java.lang.reflect包的方法对类进行发射调用时。
    3)当子类进行加载或初始化时。当加载一个类时,如果发现其存在父类并且未被加载则会继续加载父类。
    4)虚拟机启动时,用户指定的执行主类(包含main()的执行入口类),虚拟机会加载加载该类。
    5)调用类的变量(静态字段但非静态常量),类方法

    注意
    1. 调用类的静态字段,只有直接定义这个字段的类才会被加载和初始化。通过其子类来引用父类中定义的字段,只会触发父类的初始化而不会触发子类的初始化。
    2.调用类的静态常量是不触发类的加载过程。如果在A类中调用B类的静态常量,那么在编译阶段会将该静态常量放到A的Class文件的静态常量池中,所以对该常量的调用不涉及B的加载。

    class SuperClass{
        static{
            System.out.println("SuperClass 类初始化");
        }
        static int a=3;
        static final int b=4;
    }
    class SubClass extends SuperClass{
        static{
            System.out.println("SubClass 类初始化");
        }
    }
    public class Test {
        public static void main(String[] args) {
            System.out.println(SubClass.a);
        }
    }
    

    结果是:

    SuperClass 类初始化
    3

    这里调用了父类的静态变量a,父类进行了初始化,但子类并没有进行初始化。
    如果执行:

    System.out.println(SubClass.b);

    那么结果是:

    4

    两个类都没有进行初始化,因为此时b是类常量。

    加载阶段

    加载阶段虚拟机主要完成以下三件事:
    1)根据类的路径,定位并获取类的class文件
    2)通过加载器加载class文件,并将class文件里所代表的静态存储结构转化为方法区的运行数据结构
    3)在java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。

    java虚拟机内存可以分为五部分:方法区,堆,程序计数器,java虚拟机栈,本地方法栈。方法区和堆是所有线程共享的内存区域。程序计数器,java虚拟机栈和本地方法栈都是线程私有的,即每个线程有用自己的这三块内存。
    方法区:是虚拟机的一块内存,主要是存储类信息,编译后的代码等数据
    :主要是放对象实例,分为新生代和老年代
    程序计数器:是当前线程所执行的字节码行号的指示器
    java虚拟机栈:java方法(字节码)的内存模型,每个方法被执行时都会同时创建一个栈帧用于存储局部变量表,操作数栈,方法出口等信息。
    本地方法栈:功能与java虚拟机栈相同,但是为本地(native)方法服务。

    验证阶段

    确保Class文件的字节流包含的信息符合当前虚拟机的要求,并不会危害虚拟机自身安全。该阶段包括四个部分:
    1.文件格式验证:验证字节流是否符合Class文件格式的规范
    2.元数据验证:对字节码描述的信息进行语义分析,以确保其描述的信息符合java语言规范要求
    3.字节码验证:进行数据流和控制流分析,保证校验类的方法在运行时不会做出危害虚拟机安全的行为。
    4.符号引用验证:在虚拟机中将符号引用转换为直接引用

    准备阶段

    正式为类变量分配内存并设置类变量的默认值,这些内存在方法区中进行分配。内存分配仅针对类变量(static变量),不包括实例变量,实例变量是在对象实例化时和对象一起在堆中分配内存。
    类变量的默认值的设置和为了保证变量使用的安全性在对象实例化过程中虚拟机自动地对实例变量进行设置默认值是一样的。默认值的设置如下:

    数据类型默认值
    int0
    long0L
    char‘\u0000’
    byte(byte)0
    booleanfalse
    float0.0f
    double0.0d
    referencenull

    解析

    将常量池内的符号引用替换为直接应用的过程

    初始化

    初始化是执行类构造器() (在准备阶段提供的代码,即执行所有类变量的赋值动作及静态代码块内容)。在执行类构造器()时保证父类的()已经执行完毕。即一般我们会看到先执行父类的static方法块内容和static变量的赋值,然后再执行子类的static方法块内容和static变量赋值。

    注意:接口与类不同,执行接口的静态变量初始化操作是不需要先执行执行父类的静态变量初始化操作(接口不含有静态方法块)。只有父类接口中定义的变量被子类调用时父接口才会被初始化。接口的实现类在初始化时不一定会执行接口的静态变量初始化操作。

    class A{
        A(String str){
            System.out.println("A 在"+str+" 中被创建");
        }
    }
    interface SuperInterface{
        static A a=new A("SuperInterface");
    }
    class SuperInterfaceImp implements SuperInterface{
        static A a=new A("SuperInterfaceImp");
    }
    public class Test {
        public static void main(String[] args) {
            new SuperInterfaceImp();
        }
    }

    该代码运行结果是:

    A 在SuperInterfaceImp 中被创建

    所以接口的实现类在初始化时不一定会执行接口的静态变量初始化操作。

    对象的实例化

    对象实例化是new一个实例,当然也可以通过放射机制得到一个实例化对象。该过程会触发类加载过程。在类完成加载后,才进行对象的初始化其他操作,对象初始化过程包括类加载过程(如果之前已经加载过了,就不含该过程),对象内存分配,设置默认值,赋值操作,执行构造器。

    创建对象的初始化过程
    1)类加载过程和上面是一样的。如果在加载当前类(假设为C)过程中发现,其存在父类B,加载B,如果发现B还有父类继续加载其父类,直到Object。在完成Object的整个加载过程,包括类的初始化。然后进行Object的子类进行类加载过程,递归的执行该操作直到类C完成加载过程,完成类的初始化。(该过程的前提是这些类都没有被加载过)
    3)然后进行对象初始化的其他操作(4,5,6,7步奏),该步骤先从基类开始,然后到其子类进行这些操作,直到类C完成这些操作。
    4)首先在堆上对象分配足够的存储空间。
    5)对这块存储空间进行清零,即自动的为对象中的所有基本类型都设置为默认值,引用设置为null。
    6)执行所有出现于字段定义处的初始化动作,即属性的赋值操作(相当于定义 int i=2; 这块空间被赋值了两次了,第一次是空间清零,i赋值为0。然后根据字段的赋值将i赋值为2.)
    7)执行构造器。

    为了直观地体会对象的创建过程,可以看下面这段代码:

    //基类
    class A{
        D d=new D("A");
        static{
            System.out.println("load A");
        }
        public A(){
            System.out.println("Create A");
        }
    }
    //B是A的子类
    class B extends A{
        //字段,用于查看字段的初始化时间
        D d=new D("B");
        static{
            System.out.println("load B");
        }
        public B(){
            System.out.println("Create B");
        }
    }
    class C extends B{
        D d=new D("C");
        static{
            System.out.println("load C");
        }
        public C(){
            System.out.println("Create C");
        }
    }
    class D{
        static{
            System.out.println("load D");
        }
        D(String str){
            System.out.println("D在类"+str+"中初始化");
        }
    }
    public class Test{
        public static void main(String[] args) {
            new C();//实例化了对象C 
        }
    }

    A是B的基类,B是C的基类。并且它们都有自己的static静态方法块,构造器,即字段D;本例主要是创建C的对象,来观察在继承关系中的类加载和初始化过程。以上代码的运行结果如下:

    load A
    load B
    load C
    load D
    D在类A中初始化
    Create A
    D在类B中初始化
    Create B
    D在类C中初始化
    Create C

    从结果中我们可以知道,步骤如下:
    一、类加载过程
    1. 当创建C对象时,加载器加载C的class文件。在这过程中会发现它存在基类B,那么加载器继续加载B类的class文件。加载B时,发现B还有一个基类A,那么就会继续加载A的class文件。
    2. 从基类开始一直到C完成整个加载过程(加载,验证,准备,解析,初始化)
    所以显示 load A,load B ,load C,load D。发现D属于多个类的类变量,但只被加载一次。

    二,进行初始化
    1.对象初始化的其他过程从基类A开始,一直到当前类C。
    2.在堆上为A分配内存,并为所有字段设置为默认值。在这里讲d设置为null。
    3.根据字段的初始化代码,对字段进行赋值初始化。 (此时调用了D类的class文件,并调用static静态方法块。虽然D在A,B,C中都有出现,但仅加载一次D的class文件和执行一次static静态方法块代码。然后调用D的构造器,构造器每次创建对象时都会调用)。
    4.调用基类A的构造。
    5.接着就对A的导出类B执行和A相同的操作,分配内存,字段的初始化,调用构造器。
    6.最后是对C执行相同操作,当完成C构造器调用后,整个初始化过程就完成了。
    (这回D对象被多处实例化)

    对于类加载过程中的使用阶段,在类是实例化过程就是对class的一个使用,属于使用阶段。类被加载、连接和初始化后,它的生命周期就开始了。在虚拟机的生命周期中,始终不会被卸载。生命周期的结束意味着当前类对象或类成员没有引用指向它们,则虚拟机开始调用垃圾回收机制,清理类对象和类信息,类卸载完成。

    更多相关内容
  • C++对象(下)——初始化列表、static成员和友元

    千次阅读 热门讨论 2021-03-08 09:23:18
    C++对象——初始化列表、static成员和友元一、再谈构造函数1.1 构造函数整体赋值1.2 初始化列表三级目录 关于C++对象的学习 C++对象(上)——的基本概念、的限定符及封装和成员函数的this指针 C++...


    关于C++类和对象的学习
    C++类和对象(上)——类的基本概念、类的限定符及封装和类成员函数的this指针
    C++类和对象(中)——类的6个默认成员函数(构造、析构、拷贝构造、赋值运算符重载)
    在这里插入图片描述

    一、再谈构造函数

    1.1 构造函数整体赋值

    在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。例如我们之前创建的Date类

    class Date
    {
    public:
    
    	Date(int year = 1900, int month = 1, int day = 1)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    

    虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称作为类对象成员的初始化,构造函数体中的语句只能将其称作为赋初值,而不能称作初始化因为初始化只能初始化一次,而构造函数体内可以多次赋值

    1.2 初始化列表

    <1>为什么有初始化列表?

    对于我们定义的日期类,当类中出现以下成员时,如果只用构造函数则无法完成变量的初始化了,以下的成员变量需要在定义的时候就需要初始化

    • 引用成员变量
    • const成员变量
    • 自定义类型的成员变量

    在这里插入图片描述
    其次对于自定义的类成员变量,不使用初始化列表会调用多次构造函数
    在这里插入图片描述

    <2> 如何使用初始化列表

    初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式
    注意:每个成员变量在初始化列表中只能出现一次

    //1.2.2
    class Time
    {
    public:
    	Time(int hour = 1, int minute=1,int second = 1)
    		:_hour(hour)
    		,_minute(minute)
    		,_second(second)
    	{
    		cout << "Time(int hour = 1, int minute=1,int second = 1)" << endl;
    	}
    private:
    	int _hour;
    	int _minute;
    	int _second;
    };
    
    class Date
    {
    public:
    	//初始化列表写法
    	Date(int year=1900, int month=1, int day=1,int hour=1,int minute=1,int second=1)
    		:_year(year)
    		,_month(month)
    		,_day(day)
    		,_n(10)
    		,_ref(day)
    		,_t(hour,minute,second)
    	{
    		cout << "Date(int year, int month, int day,int hour,int minute,int second)" << endl;
    
    	}
    
    private:
    	int _year;
    	int _month;
    	int _day;
    
    	//定义的时候需要初始化
    	int& _ref;	//引用
    	const int _n; //const成员变量
    	Time _t;	//自定义类型的成员变量
    };
    
    void Test2()
    {
    	Date d1(2021,3,9,2,2,2);
    }
    

    结果在这里插入图片描述

    <3> 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关

    如下代码的运行结果是什么?

    //1.2.3
    class A
    {
    public:
    	A(int a)
    		:_a1(a)
    		, _a2(_a1)
    	{}
    	void Print() {
    		cout << _a1 << " " << _a2 << endl;
    	}
    private:
    	int _a2;
    	int _a1;
    };
    
    void Test2()
    {
    	A aa(1);
    	aa.Print();
    }
    

    结果

    在这里插入图片描述

    1.3 explicit关键字

    <1> 匿名对象

    匿名对象顾名思义就是没有名字,其作用域只在一行中有效,例如下面的代码

    //1.3.1
    class B
    {
    public:
    	B(int b = 0)
    		:_b(b)
    	{
    		cout << "B(int b = 0)" << endl;
    	}
    
    	//析构函数
    	~B()
    	{
    		cout << "~B()" << endl;
    	}
    private:
    	int _b;
    };
    
    int main()
    {
    	B bb(10);	//生命周期在main函数域
    	B(2);	//匿名对象生命周期在这一行
    	return 0;
    }
    

    在这里插入图片描述
    在这里插入图片描述

    <2> 为什么有explicit关键字?

    对于c++来说,构造函数不仅可以构造与初始化对象,对于单个参数的构造函数,还具有类型转换的作用

    当我们定义一个类进行初始化时,如果我们采取下面这种定义方式,编译器会自动生成一个匿名对象,然后用匿名对象对cc对象进行拷贝构造

    //1.3.2
    class C 
    {
    public:
    	C(int c)
    		:_c(c)
    	{
    		cout << "C()" << endl;
    	}
    
    private:
    	int _c;
    };
    
    int main()
    {
    	C cc(2);
    	cc = 8;	//编译器会自动生成一个匿名对象,然后用匿名对象对cc对象进行拷贝构造
    	return 0;
    }
    

    <3> 如何使用explicit关键字?

    用explicit修饰构造函数,将会禁止单参构造函数的隐式转换

    //1.3.3
    class C 
    {
    public:
    	explicit C(int c)
    		:_c(c)
    	{
    		cout << "explicit C(int c)" << endl;
    	}
    
    private:
    	int _c;
    };
    
    int main()
    {
    	C cc(2);
    	cc = 8;	//编译器会自动生成一个匿名对象,然后用匿名对象对cc对象进行拷贝构造
    	return 0;
    }
    

    在这里插入图片描述

    二、static成员

    2.1 概念

    声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态的成员变量一定要在类外进行初始化

    面试题:实现一个类,计算程序中创建出了多少个类对象?这个时候我们就可以使用static成员变量和static成员函数来实现

    //2.1 实现一个类,计算程序中创建出了多少个类对象。
    class A
    {
    public:
    	//构造函数
    	A()
    	{
    		++_scount;
    	}
    	//拷贝构造函数
    	A(const A& a)
    	{
    		++_scount;
    	}
    	
    	static int GetAcount()
    	{
    		return _scount;
    	}
    private:
    	static int _scount;
    };
    
    //初始化在类外
    int A::_scount = 0;
    
    void TestA()
    {
    	cout << A::GetAcount() << endl;
    	A aa;
    	A bb;
    	A cc(bb);
    	cout << A::GetAcount() << endl;
    }
    

    在这里插入图片描述

    2.2 特性

    <1> 静态成员为所有类对象所共享,不属于某个具体的实例

    <2> 静态成员变量必须在类外定义,定义时不添加static关键字

    在这里插入图片描述

    <3> 类静态成员即可用类名::静态成员或者对象.静态成员来访问

    在这里插入图片描述

    <4> 静态成员函数没有隐藏的this指针,不能访问任何非静态成员

    在这里插入图片描述

    <5>静态成员和类的普通成员一样,也有public、protected、private3种访问级别,也可以具有返回值

    Q1: 静态成员函数可以调用非静态成员函数吗?

    不可以,因为静态成员函数没有隐藏的this指针

    Q2:非静态成员函数可以调用类的静态成员函数吗?

    可以,因为非静态成员函数含有this指针,指定了静态成员函数的类域
    在这里插入图片描述

    三、C++11的成员初始化新玩法

    3.1 为什么?

    对于C++98而言,类内自定义的内置类型,编译器会进行初始化,而其他内置类型不会,因此出现则会中初始化方法。
    具体查看这篇博客的2.2.7节内容
    C++类和对象(中)——类的6个默认成员函数(构造、析构、拷贝构造、赋值运算符重载)
    在这里插入图片描述

    3.2 怎么用?

    C++11支持非静态成员变量在声明时进行初始化赋值,但是要注意这里不是初始化,这里是给声明的成员变量缺省值

    //3.C++11的成员初始化新玩法
    class B
    {
    public:
    	B(int b = 0)
    		:_b(b)
    	{}
    	int _b;
    };
    class A
    {
    public:
    	void Print()
    	{
    		cout << a << endl;
    		cout << b._b << endl;
    		cout << p << endl;
    	}
    private:
    	// 非静态成员变量,可以在成员声明时给缺省值。
    	int a = 10;
    	B b = 20;
    	int* p = (int*)malloc(4);
    	//静态成员不可以
    	static int n;
    };
    int A::n = 10;
    
    int main()
    {
    	A a;
    	a.Print();
    	return 0;
    }
    

    在这里插入图片描述

    四、友元

    • 友元分为:友元函数友元类
    • 友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用

    4.1 友元函数

    问题:现在我们尝试去重载operator<<,然后发现我们没办法将operator<<重载成成员函数。因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。所以我们要将operator<<重载成全局函数。但是这样的话,又会导致类外没办法访问成员,那么这里就需要友元来解决。operator>>同理

    //4.1
    class Date
    {
    public:
    	Date(int year, int month, int day)
    		: _year(year)
    		, _month(month)
    		, _day(day)
    	{}
    	ostream& operator<<(ostream& _cout)
    	{
    		_cout << _year << "-" << _month << "-" << _day;
    		return _cout;
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    int main()
    {
    	Date d(2017, 12, 24);
    	d << cout;
    	return 0;
    }
    

    在这里插入图片描述
    友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字

    //4.1
    class Date
    {
    friend ostream& operator<<(ostream& _cout, const Date& d);
    friend istream& operator >> (istream& _cin, Date& d);
    
    public:
    	Date(int year, int month, int day)
    		: _year(year)
    		, _month(month)
    		, _day(day)
    	{}
    	ostream& operator<<(ostream& _cout)
    	{
    		_cout << _year << "-" << _month << "-" << _day;
    		return _cout;
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    
    ostream& operator<<(ostream& _cout, const Date& d)
    {
    	_cout << d._year << "-" << d._month << "-" << d._day << endl;
    	return _cout;
    }
    
    istream& operator >> (istream& _cin, Date& d)
    {
    	_cin >> d._year >> d._month >> d._day;
    
    	return _cin;
    }
    
    int main()
    {
    	Date d(2017, 12, 24);
    	cout<<d;
    	//Date d1;
    	cin >> d;
    	cout << d;
    	return 0;
    }
    

    在这里插入图片描述

    4.2 友元类

    友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。

    • 友元关系是单向的,不具有交换性。
      比如下面Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
    • 友元关系不能传递
      如果B是A的友元,C是B的友元,则不能说明C时A的友元。
    class Date; // 前置声明
    class Time
    {
    	friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
    public:
    	Time(int hour=0, int minute=0, int second=0)
    		: _hour(hour)
    		, _minute(minute)
    		, _second(second)
    	{}
    private:
    	int _hour;
    	int _minute;
    	int _second;
    };
    
    class Date
    {
    public:
    	Date(int year = 1900, int month = 1, int day = 1)
    		: _year(year)
    		, _month(month)
    		, _day(day)
    	{}
    
    	void SetTimeOfDate(int hour, int minute, int second)
    	{
    		// 直接访问时间类私有的成员变量
    		_t._hour = hour;
    		_t._minute = minute;
    		_t._second = second;
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    
    	Time _t;
    };
    

    五、内部类

    概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。注意此时这个内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去调用内部类。外部类对内部类没有任何优越的访问权限。

    注意:内部类就是外部类的友元类。注意友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。

    特性:

    1. 内部类可以定义在外部类的public、protected、private都是可以的。
    2. 注意内部类可以直接访问外部类中的static、枚举成员,不需要外部类的对象/类名。
    3. sizeof(外部类)=外部类,和内部类没有任何关系
    class A
    {
    private:
    	static int k;
    	int h;
    public:
    	class B
    	{
    	public:
    		void foo(const A& a)
    		{
    			cout << k << endl;//OK
    			cout << a.h << endl;//OK
    		}
    	};
    };
    int A::k = 1;
    int main()
    {
    	A::B b;
    	b.foo(A());
    	return 0;
    }
    
    展开全文
  • jvm将class文读取到内存中,经过对class文件的校验、转换解析、初始化最终在jvm的heap和方法区分配内存形成可以被jvm直接使用的类型的过程。 的生命周期7个阶段依次为:Loading Verification Preparation ...

    什么是类的加载

    jvm将class文读取到内存中,经过对class文件的校验、转换解析、初始化最终在jvm的heap和方法区分配内存形成可以被jvm直接使用的类型的过程。

    类的生命周期7个阶段依次为:Loading Verification Preparation Resolution Initialization Using Unloading

     

    加载 验证 准备 初始化和卸载 的顺序是确定的,而“解析”不一定在初始化之前很有可能在初始化之后,实现java的伟大特性。

    1、加载Loading

    这个阶段jvm完成以下动作:

    首先  类加载器通过类的全路径限定名读取类的二进制字节流,

    然后  将二进制字节流代表的类结构转化到运行时数据区的 方法区中,

    最后  在jvm堆中生成代表这个类的java.lang.Class实例(不是这个类的实例)

    类加载器:

    获取类的二进制流 既可以使用jvm自带的类加载器,也可以自己写加载器来加载,这一小步是完全可控的。不同的加载器可以从各种地方读取:

    1.从本地文件系统加载class文件;

    2.从Jar包加载class文件;

    3.通过网络加载class文件;

    4.把一个class源文件动态编译,并执行加载。

    同一个加载器加载的同源类才是真的同类。不同加载器加载同源类,不是同类!instanceof为FALSE类加载的双亲委派模型各个加载器都是先委托自己的父加载器加载类,若确实没加载到再自己来加载于是java默认的类查找加载顺序是自顶向下的,树状结构双亲委托的意图是保证java类型体系中最基础的行为一致,优先加载JDK中的类。

     

    加载器主要有四种:

    jvm启动类加载器bootstrap loader,用c++实现为jvm的一部分(仅指sun的hotspot),负责 JAVA_HOME/lib下面的类库中的类的加载,这个加载器,java程序无法引用到。

    扩展类加载器Extension Loader,由sun.misc.Launcher$ExtClassLoader类实现,可在java中使用,负责JAVA_HOME/lib/ext 目录和java.ext.dir目录中类库的类的加载。

    应用系统类加载器Application System Loader,由sun.misc.Louncher$AppClassLoader实现,负责加载用户类路径中类库中的类,如果没有使用自定义的加载器,这个就是默认的 加载器!

    用户自定义加载器 自己定义从哪里加载类的二进制流

    2、类的连接

    当类被加载之后,系统为之生成一个对应的Class对象,接着将会进入连接阶段,连接阶段负责把类的。

    2.1验证verification

    Loading和 验证是交叉进行的,验证二进制字节流代表的字节码文件是否合格,主要从一下几方面判断:

    文件格式:参看class文件格式详解,经过文件格式验证之后的字节流才能进入方法区分配内存来存储。

    元数据验证:是否符合java语言规范

    字节码验证:数据流和控制流的分析,这一步最复杂

    符号引用验证:符号引用转化为直接引用时(解析阶段),检测对类自身以外的信息进行存在性、可访问性验证

    如果确认代码安全无误,可用 -Xverify:none关闭大部分类的验证,加快类加载时间

    2.2准备preparation

    在方法区中给类的类变量(static修饰)分配内存然后初始化其值,如果类变量是常量,则直接赋值为该常量值否则为java类型的默认的零值。

    2.3解析resolution

    指将常量池内的符号引用替换为直接引用的过程。

    3、类的初始化

    这个阶段才真正开始执行java代码,静态代码块和设置变量的初始值为程序员设定的值

    JVM首先加载class文件,静态代码段和class文件一同被装载并且只加载一次

    主动引用

    有且只有下面5种情况才会立即初始化类,称为主动引用:

    1new 对象时

    2、读取或设置类的静态字段(除了被final,已在编译期把结果放入常量池的静态字段)或调用类的静态方法时;

    3用java.lang.reflect包的方法对类进行反射调用没初始化过的类时Class.forname()会进行初始化

           而.classJVM将使用类装载器, 将类装入内存(前提是:类还没有装入内存),不做类的初始化工作.返回Class的对象

    4初始化一个类时发现其父类没初始化,则要先初始化其父类

    5含main方法的那个类,jvm启动时,需要指定一个执行主类,jvm先初始化这个类

    类的被动引用(不会发生类的初始化):

    --当访问一个静态变量时,只有真正实现这个静态变量的类才会被初始化(通过子类引用父类的静态变量,不会导致子类初始化)

    --通过数组定义类应用,不会触发此类的初始化  A[] a = new A[10];

    --引用常量(final类型)不会触发此类的初始化(常量在编译阶段就存入调用类的常量池中了)

    子类继承父类时的初始化顺序

       1.首先初始化父类的static变量和static块,按出现顺序

       2.初始化子类的static变量和static块,按出现顺序

       3.初始化父类的普通变量和构造块,按出现顺序然后调用父类的构造函数

       4.初始化子类的普通变量和构造块,按出现顺序然后调用子类的构造函数

    注意:

    (1)1.2两步在类加载(只加载一次,除非卸载)的时候执行,并且只执行一次。初始化之前已经分配完内存了。

    (2)接口不能使用static{}语句块,但编译器仍然会为接口生成“<clinit>()”类构造器,用于初始化接口中所定义的成员变量。接口和类真正区别是:当一个类在初始化的时候,要求其父类全部都已经初始化过了,但是一个接口在初始化时,并不要求其父接口全部都完成了初始化,只有在真正使用到父接口的时候(如引用接口中定义的常量)才会初始化。

    在类的初始化阶段,虚拟机负责对类进行初始化,主要就是对静态Field进行初始化,在Java类中对静态Fieldr指定初始值有两种方式:1. 声明静态Field时指定初始值;2. 使用静态初始化块为静态Field指定初始值。如:

    public class Test {
    
    	static int a = 5;
    	static int b;
    	static int c;
    	static
    	{
    	   b = 6;
    	}
    	
    	public static void main(String[] args) {
    		System.out.println("a="+a);
    		System.out.println("b="+b);
    		System.out.println("c="+c);
    	}
    
    
    }

    // 最终结果 a=5,b=6,c=0

    静态初始化块被当成类的初始化语句,JVM会按这些语句在程序中的排列顺序依次执行它们,如:

    public class Test {
    	static int a = 5;
    	
    	static
    	{
    	   b = 6;
    	}
    	
    	static int b=9;
    	static int c;
    	
    	public static void main(String[] args) {
    		System.out.println("a="+a);
    		System.out.println("b="+b);
    		System.out.println("c="+c);
    	}
    
    }

    // 最终结果 a=5,b=9,c=0

    JVM初始化一个类包含如下几个步骤:

    1、假如这个类还没有被加载和连接,则程序先加载并连接该类;

    2、假如该类的直接父类还没有被初始化,则先初始化其直接父类(直接父类也依次执行1,2,3 保证类依赖的所有父类都会被初始化);

    3、假如类中有初始化语句,则系统依次执行这些初始化语句。

    类初始化的时机:

    1、创建类的实例,包括使用new操作符来创建,通过反射来创建,通过反序列化的方式来创建;

    2、调用某个类的静态方法;

    3、访问某个类或者接口的静态Field,或者为该静态Field赋值;

    4、通过反射方式来强制创建某个类或接口对应的java.lang.Class对象,例如,Class.forName(“Person”),如果系统还未初始化Person类,则这行代码将会导致该Person类被初始化,并返回Person类对应的java.lang.Class对象;

    5、初始化某个类的子类;

    6、直接使用java.exe命令来运行某个主类。 

    另外:对于一个Field型的静态Field,如果该Field的值在编译时就可以确定下来,那么这个Field相当于“宏变量“,Java编译器会在编译时直接把这个Field出现的地方替换成它的值,因为即使程序使用该静态Field,也不会导致该类的初始化。

    类、变量初始化的顺序

    一般的,我们很清楚类需要在被实例化之前初始化,而对象的初始化则是运行构造方法中的代码。

    public class T  implements Cloneable{
    	  public static int k = 0;
    	  public static T t1 = new T("t1");
    	  public static T t2 = new T("t2");
    	  public static int i = print("i");
    	  public static int n = 99;
    	  
    	  public int j = print("j");
    	  
    	  {
    	      print("构造快");
    	  }
    	  
    	  static {
    	      print("静态块");
    	      //m=2;
    	      //System.out.println(m);//静态代码块不可以访问该静态代码块之后的静态变量,但可以赋值
    	  }
    	  //public static int m = 99;
    	  
    	  public T(String str) {
    	      System.out.println((++k) + ":" + str + "    i=" + i + "  n=" + n);
    	      ++n; ++ i;
    	  }
    	  
    	  public static int print(String str){
    	      System.out.println((++k) +":" + str + "   i=" + i + "   n=" + n);
    	      ++n;
    	      return ++ i;
    	  }
    	  
    	  public static void main(String[] args){
    	      T t = new T("init");
    	  }
    	}

    运行结果如下:

    1:j   i=0   n=0
    2:构造快   i=1   n=1
    3:t1    i=2  n=2
    4:j   i=3   n=3
    5:构造快   i=4   n=4
    6:t2    i=5  n=5
    7:i   i=6   n=6
    8:静态块   i=7   n=99
    9:j   i=8   n=100
    10:构造快   i=9   n=101
    11:init    i=10  n=102

    代码组成:

    成员变量 2~6 行的变量是 static 的,为类 T 的静态成员变量,需要在类加载的过程中被执行初始化;第 8 行的int j则为实例成员变量,只再类被实例化的过程中初始化。

    代码段 9~11 行为实例化的代码段,在类被实例化的过程中执行;13~15 行为静态的代码段,在类被加载、初始化的过程中执行。

    方法 方法public static int print(String str) 为静态方法,其实现中牵涉到 k,i,n 三个静态成员变量,实际上,这个方法是专门用来标记执行顺序的方法;T 的构造方法是个实例化方法,在 T 被实例化时调用。

    main 方法 main 方法中实例化了一个 T 的实例。

    执行顺序分析

    在一个对象被使用之前,需要经历的过程有:类的装载->链接(验证 ->准备-> 解析)->初始化->对象实例化。这里需要注意的点主要有:

    在类链接之后,类初始化之前,实际上类已经可以被实例化了

    就如代码中所述,在众多静态成员变量被初始化完成之前,已经有两个实例的初始化了。实际上,此时对类的实例化,除了无法正常使用类的静态承运变量以外(还没有保证完全被初始化),JVM 中已经加载了类的内存结构布局,只是没有执行初始化的过程。比如第 3 行public static T t1 = new T("t1");,在链接过程中,JVM 中已经存在了一个 t1,它的值为 null,还没有执行new T("t1")。又比如第 5 行的public static int i = print("i");,在没有执行初始化时,i 的值为 0.

    先执行成员变量自身初始化,后执行static {…}、{…}代码块中的内容。

    如此策略的意义在于让代码块能处理成员变量相关的逻辑。如果不使用这种策略,而是相反先执行代码块,那么在执行代码块的过程中,成员变量并没有意义,代码块的执行也是多余。

    类实例化的过程中,先执行隐式的构造代码,再执行构造方法中的代码 这里隐式的构造代码包括了{}代码块中的代码,以及实例成员变量声明中的初始化代码,以及父类的对应的代码(还好本题中没有考察到父类这一继承关系,否则更复杂;))。为何不是先执行显示的构造方法中的代码,再执行隐式的代码呢?这也很容易解释:构造方法中可能就需要使用到实例成员变量,而这时候,我们是期待实例变量能正常使用的。

     

    展开全文
  • 1.初始化列表 2.explicit关键字 3.static成员 概念: 4.友元 4.1友元函数 4.2友元

    1.初始化列表

    初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
    示例:

    class A
    {
    public:
    	A(int year, int month, int day)
    		: _year(year)
    		,_month(month)
    		,_day(day)
    	{}
    
    private:
    	int _year;//声明
    	int _month;
    	int _day;
    };
    

    注意:

    1. 每个成员变量在初始化列表中只能出现一次(即初始化一次)
    2. 类中包含一下成员的,必须放在初始化列表初始化
    • 引用成员变量
    • const成员变量
    • 自定义类型成员(没默认构造函数)
    class B
    {
    public:
    	B(int b)
    		: _ret(b)
    	{}
    
    private:
    	int _ret;
    };
    
    class A
    {
    public:
    	A(int a, int n)
    		: _a(a)
    		, _n(n)
    		, _b(5)
    	{}
    
    private:
    	int& _a;//引用
    	const int _n;//const
    	B _b;//自定义类型
    };
    
    1. 对于内置类型使用初始化列表或者函数体内初始化都行,对于自定义类型建议使用初始化列表。
    2. 成员变量在类中的声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。(尽量保证声明与初始化列表顺序一致)

    2.explicit关键字

    构造函数不仅可以构造与初始化对象,对于单个参数的构造函数,还具有类型转换的作用。
    匿名对象:

    class A
    {
    public:
    	explicit A(int n)
    		: _n(n)
    	{}
    	A(const A& a)
    		: _n(a._n)
    	{}	
    private:
    	int _n;
    };
    
    int main()
    {
    	A a1(1);//他的生命周期在main函数域
    	A(5);//匿名对象,他的生命周期在这一行
    	
    	A a2 = 5;
    	//隐式类型转换,结果调用构造函数 -> 先构造一个A(5)匿名临时对象,
    	//再用临时对象拷贝构造a2
    	return 0;
    }
    

    用explicit修饰构造函数,将会禁止单参构造函数的隐式转换。

    3.static成员

    概念:

    声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。

    面试题实现一个类,计算中程序中创建出了多少个类对象?

    class A
    {
    public:
    	A()
    	{
    		++count_A;
    	}
    
    	A(const A& a)
    	{
    		++count_A;
    	}
    
    	//静态成员函数没有this指针
    	static int GetCountA()
    	{
    		return count_A;
    	}
    
    private:
    	//静态成员变量属于类的所有对象,属于整个类
    	static int count_A;
    };
    
    //定义初始化
    int A::count_A = 0;
    
    int main()
    {
    	A a1;
    	A a2;
    	A a3;
    	A a4(a1);
    	A a5(a2);
    	
    	//如果没有访问限定符的限制,指定类域就可以访问静态成员
    	cout << A::count_A << endl;
    	
    	cout << a1.GetCountA() << endl;//对象.访问
    	cout << A::GetCountA() << endl;//类名::访问
    	return 0;
    }
    

    注意:

    1. 静态成员变量属于类的所有对象,属于整个类。
    2. 静态成员变量必须在类外定义,定义时不添加static关键字。
    3. 类静态成员即可用 : 1:类名::静态成员 2:对象.静态成员来访问。
    4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员。

    4.友元

    友元提供了一种突破封装的方式,有时候提供了便利。但是友元一定程度上破坏封装性,所以不宜多用。

    4.1友元函数

    现在我们尝试一段代码去重载operator<<,然后发现我们没办法将operator<<重载成成员函数。原因是cout的输出流对象和隐含的this指针在抢占第一个参数的位置

    class Date
    {
    public:
    	Date(int year, int month, int day)
    		: _year(year)
    		, _month(month)
    		, _day(day)
    	{}
    	ostream& operator<<(ostream& out)
    	{
    		out << d._year << "-" << d._month << "-" << d._day;
    		return out;
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    
    int main()
    {
    	Date d(2021, 3, 9);
    	cout << d;
    	return 0;
    }
    

    这里要用到友元函数来解决这个问题!!!

    友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。

    class Date
    {
    	friend ostream& operator<<(ostream& out, const Date& d);
    public:
    	Date(int year, int month, int day)
    		: _year(year)
    		, _month(month)
    		, _day(day)
    	{}
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    ostream& operator<<(ostream& out, const Date& d)
    {
    	out << d._year << "-" << d._month << "-" << d._day;
    	return out;
    }
    
    int main()
    {
    	Date d(2021, 3, 9);
    	cout << d << endl;
    	return 0;
    }
    

    在这里插入图片描述
    友元函数的特点:

    友元函数可访问类的私有和保护成员,但不是类的成员函数。
    友元函数不能用const修饰。
    友元函数可以在类定义的任何地方声明,不受类访问限定符限制。
    一个函数可以是多个类的友元函数。
    友元函数的调用与普通函数的调用和原理相同。

    4.2友元类

    友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
    但是注意:

    1. 友元关系是单向的,不具有交换性。
    2. 友元关系不能传递。
    class Date; // 前置声明
    class Time
    {
    	friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成
    	员变量
    public:
    	Time(int hour, int minute, int second)
    		: _hour(hour)
    		, _minute(minute)
    		, _second(second)
    	{}
    private:
    	int _hour;
    	int _minute;
    	int _second;
    };
    class Date
    {
    public:
    	Date(int year = 1900, int month = 1, int day = 1)
    		: _year(year)
    		, _month(month)
    		, _day(day)
    	{}
    	void SetTimeOfDate(int hour, int minute, int second)
    	{
    		// 直接访问时间类私有的成员变量
    		_t._hour = hour;
    		_t._minute = minute;
    		_t.second = second;
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    	Time _t;
    };
    

    上述代码中声明了Date类Time类的友元类,则Date类可以直接访问Time类的私有成员变量,但是Time类不能访问Date类。

    展开全文
  • 文章目录加载机制加载机制分类、加载器、双亲委派机制加载机制分类加载器双亲委派机制加载.class文件的方式生命周期生命周期结束加载过程JVM初始化步骤加载时机类初始化时机初始化类的实例化 ...
  • 《java面试宝典》三 类初始化实例化顺序

    千次阅读 多人点赞 2020-01-15 17:55:25
    1.一个类初始化,实际上就是初始化 方法。 注意: 方法,可能不存在,需要有静态方法或者静态代码块。 2.一个子类要想初始化,需要先初始化他的父类。 实例化过程: 实例化就是初始化 方法 有几...
  • 在标准模板库中,常见的文件流对象有fstream、ifstream、ofstream三种,我们可以用文件流的方式去操作文件,比如写文件和读文件,文件流类继承图如下: ifstream继承于istream,实现高层文件流输入(input)操作,...
  • 而我们所说的加载过程即是指JVM虚拟机把.class文件中信息加载进内存,并进行解析生成对应的class对象的过程。 简单来说:加载指的是把class字节码文件从各个来源通过加载器装载入内存中。 举个通俗点的????: ...
  • 中vector的初始化问题

    千次阅读 多人点赞 2020-07-10 08:31:45
    今天分享一个在中使用 vector 并初始化的小 trick 。 事情的起因是这样的: 在做牛客网的在线编程题时,一个小伙伴问我:“为什么我这里会报错啊?” 题目: 请实现一个函数用来找出字符中第一个只出现一次的...
  • Java初始化 和 实例化区别

    千次阅读 2020-09-23 17:13:30
    初始化:是完成程序执行前的准备工作。在这个阶段,静态的(变量,方法,代码块)会被执行。同时在会开辟一块存储空间用来存放静态的数据。初始化只在加载的时候执行一次。的实例化:是指创建一个对象的过程...
  • 上一章我们一步步地实现了日期,这一章我们继续往后讲解知识点,比如说友元啊,初始化列表啊、静态成员和内部,把这些拿出来讲一讲。还是保持最近养成的写作习惯,在讲解知识点之前,我都会用一个例子或问题进行...
  • C++11:初始化

    千次阅读 2017-08-22 09:58:34
    内成员初始化class Mem { public: Mem(int i): m(i){} //初始化列表给m初始化 int m; }; class Group { public: Group(){}private: int data = 1; // 使用"="初始化非静态普通成员,也可以 int data{1}; Mem ...
  • 加载和初始化

    千次阅读 2019-01-27 20:25:47
    程序使用某个时,如果该没有被加载到内存中,则系统会通过加载、连接、初始化3个步骤对该进行初始化加载指的是将的class 文件读入内存,并创建一个java.lang.Class 对象,程序使用任何时系统都会建立...
  • C++ 初始值及默认初始化

    千次阅读 2020-11-11 15:55:29
    文章目录初始值默认初始化内置类型的默认初始化类类型的默认初始化 初始值 在这里,对象(object) 是指一块能存储数据并具有某种类型的内存空间,而不单单指在与有关的场景下使用的“对象”这个词。 当对象在创建...
  • 的加载和初始化的区别

    千次阅读 2018-10-20 00:44:20
    的加载:代表jvm将java文件编译成class文件后,以二进制的方式存放到运行时数据的方法区中,并在java的堆中创建一个java.lang.Class对象,用来指向存放在方法堆中的数据结构。 这里给大家推荐一篇java加载...
  • JVM加载过程分为几个阶段,分别是加载、验证、准备、解析和初始化初始化阶段都是在什么时候触发的呢?本文给你带来详细解析。
  • 初始化一个的时候,如果发现其父没有进行过初始化,则先初始化其父类(注意!如果其父类是接口的话,则不要求初始化父类); 当虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的那个)...
  • 对象创建过程,初始化顺序

    千次阅读 2015-04-08 17:19:29
     对于以上第三个步骤,Java虚拟机可采用3种方式来初始化对象,到底采用何种初始化方式取决于创建对象的方式。 (1)如果对象是通过clone()方法创建的,那么Java虚拟机把原来被克隆对象的实例变量的值拷贝到新对象
  • C++成员变量初始化位置

    千次阅读 2015-09-08 11:29:16
    static: static表示的是静态的。的静态成员函数、静态成员变量是和相关的,而... 在C++中,static静态成员变量不能在的内部初始化。在的内部只是声明,定义必须在定义体的外部,通常在的实现文件中初始
  • netty源码学习之服务端客户端初始化

    万次阅读 多人点赞 2021-09-25 17:04:53
    } netty服务端启动和绑定过程并不复杂主要分成了两部分(两个核心方法) 初始化: initAndRegister方法,该方法主要功能是创建Channel对象(网络请求的数据通道),并整合ServerBootstrap相关配置比如工作线程...
  • 如何在Java中初始化List 对象

    千次阅读 2020-07-28 13:51:08
    I can not initialize a List as in the following code: 我无法初始化List,如下面的代码所示: List<String> supp
  • java接口的初始化

    千次阅读 2018-10-06 12:04:27
    真正不同的地方在于第三点,初始化执行之前要求父类全部都初始化完成了,但接口的初始化貌似对父接口的初始化不怎么感冒。 也就是说,子接口初始化的时候并不要求其父接口也完成初始化,只有在真正使用到父接口...
  • 【1】的生命周期 一个从加载进内存到卸载出内存为止,一共经历7个阶段: 加载—&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;验证—&amp;amp;amp;amp;amp;amp;amp;...
  • Java初始化HashMap

    千次阅读 2021-02-08 07:41:12
    在本教程中,我们将学习在Java中初始化HashMap的各种方法。 2.静态HashMap的静态初始化器 我们可以使用静态代码块初始化HashMap: public static Map<String, String> articleMapOne; static { ...
  • C++中的直接初始化指的是直接调用的构造函数进行初始化,如下例如 string a; //调用默认构造函数 string a("hello"); //调用参数为const char *类型的构造函数 string b(a); //调用拷贝构造函数 复制...
  • Flutter 初始化工程解析

    千次阅读 2022-03-07 16:30:57
    } MyHomePage 也是 Widget,但与 MyApp 不同的是,它并没有一个 build 方法去返回 Widget,而是多了一个 createState 方法返回 _MyHomePageState 对象,而 build 方法则包含在这个 _MyHomePageState 当中,...
  • C++初始化对象的方法总结

    千次阅读 2012-11-17 23:15:16
    最近在objective-c代码里要集成使用c++第三方的类库,所以就接触了c++初始化对象的方法,下面简单介绍一下。 初始化两种方法: 第一种初始化方法:ClassName object(初始化参数); 第二种初始化方法:ClassName...
  • Java的加载器是一个运行时核心基础设施模块,主要是在启动之初进行的加载、链接、初始化 第一步,Load阶段 读取文件产生二进制,并转为特定数据结构,初步校验cafe babe魔法数、常量池、文件长度、是否有...
  • 的整个生命周期包括加载、验证、准备、解析、初始化、使用和卸载7个阶段,其中验证、准备、解析这3个部分统称为连接,如下图所示。 加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,而解析阶段则不...
  • 加载“加载”是”加载”过程...3.在内存中生成一个代表这个的java.lang.Class对象,作为方法区这个的各种数据的访问入口。 1234验证验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节中包含...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 399,099
精华内容 159,639
关键字:

初始化类对象的流