精华内容
下载资源
问答
  • 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;
    }
    
    展开全文
  • 加载和对象初始化过程

    千次阅读 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变量),不包括实例变量,实例变量是在对象实例化时和对象一起在堆中分配内存。
    类变量的默认值的设置和为了保证变量使用的安全性在对象实例化过程中虚拟机自动地对实例变量进行设置默认值是一样的。默认值的设置如下:

    数据类型 默认值
    int 0
    long 0L
    char ‘\u0000’
    byte (byte)0
    boolean false
    float 0.0f
    double 0.0d
    reference null

    解析

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

    初始化

    初始化是执行类构造器() (在准备阶段提供的代码,即执行所有类变量的赋值动作及静态代码块内容)。在执行类构造器()时保证父类的()已经执行完毕。即一般我们会看到先执行父类的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的一个使用,属于使用阶段。类被加载、连接和初始化后,它的生命周期就开始了。在虚拟机的生命周期中,始终不会被卸载。生命周期的结束意味着当前类对象或类成员没有引用指向它们,则虚拟机开始调用垃圾回收机制,清理类对象和类信息,类卸载完成。

    展开全文
  • java里初始化一个对象,通过初始化快或者构造方法进行数据赋值。与其相关的执行代码有这么几种: 静态初始化初始化块 构造方法 静态初始化块 静态初始化块只在加载时执行一次,同时静态初始化块只能给静态...

    java里初始化一个类的对象,通过初始化快或者构造方法进行数据赋值。与其相关的执行代码有这么几种:

    • 静态初始化块
    • 初始化块
    • 构造方法

    静态初始化块

    静态初始化块只在类加载时执行一次,同时静态初始化块只能给静态变量赋值,不能初始化普通的成员变量。


    非静态初始化块

    非静态初始化块在每次初始化实例对象的时候都执行一次,可以给任意变量赋值。


    构造方法

    在每次初始化实例对象时调用。


    重点:执行顺序->

    1. 在加载类时执行一次静态初始化块(之后不再调用)。
    2. 在每次初始化实例对象时:先执行非静态初始化块,再执行构造方法。

    若是概念不好懂,请看如下实例:

    package com.mgh;
    
    public class Main {
    
        String name; // 声明变量name
        String sex; // 声明变量sex
        static int age;// 声明静态变量age
    
        // 构造方法
        public Main() {
            System.out.println("通过构造方法初始化name");
            name = "tom";
        }
    
        // 初始化块
        {
            System.out.println("通过初始化块初始化sex");
            sex = "男";
        }
    
        // 静态初始化块
        static {
            System.out.println("通过静态初始化块初始化age");
            age = 20;
        }
    
        public void show() {
            System.out.println("姓名:" + name + ",性别:" + sex + ",年龄:" + age);
        }
    
        public static void main(String[] args) {
    
            // 创建对象
            Main obj1 = new Main();
            Main obj2 = new Main();
            // 调用对象的show方法
            obj1.show();
            obj2.show();
        }
    }
    
    output:
    通过静态初始化块初始化age
    通过初始化块初始化sex
    通过构造方法初始化name
    通过初始化块初始化sex
    通过构造方法初始化name
    姓名:tom,性别:男,年龄:20
    姓名:tom,性别:男,年龄:20
    展开全文
  • 加载和初始化

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

    简介

    调用java命令运行某个程序时,该命令会启动一个java 虚拟机进程。程序使用某个类时,如果该类没有被加载到内存中,则系统会通过加载、连接、初始化3个步骤对该类进行初始化。类加载指的是将类的class 文件读入内存,并创建一个java.lang.Class 对象,程序使用任何类时系统都会建立一个java.lang.Class对象。

    加载器

    类加载由类加载器完成,类加载器由JVM 提供,JVM 提供的类加载器通常称为系统类加载器,开发者可以继承ClassLoader 创建自己的类加载器。
    JVM启动时会形成3个类加载器组成的加载器层次结构:
    1.Bootstrap ClassLoader: 根类加载器
    2.Extension ClassLoader: 扩展类加载器
    3.System ClassLoader:系统类加载器

    Bootstrap ClassLoader 是引导加载器,负责加载java 核心类,使用C++ 实现的,它不是java.lang.ClassLoader 的子类;

    Extension ClassLoader 称为扩展类加载器,负责加载JRE 的扩展目录(JAVA_HOME/jre/lib/ext) 中的jar 包类;

    System ClassLoader 称为系统类加载器,负责在JVM 启动时记载来自java 命令的 -classpath 选项,或CLASSPATH 环境变量指定加载的jar 包和类路径。程序可通过ClassLoader 的静态方法getSystemClassLoader() 获取系统类加载器。

    系统为所有加载的类生成一个java.lang.Class 实例,一旦一个类被加载进JVM,同一个类就不会被再次加载。JVM 中使用类加载器和全限定类名作为唯一标识。

    类的连接

    类被加载生成对于的Class 对象后,会进入连接阶段:
    1.验证: 验证阶段用于校验被加载的类是否有正确的内部结构
    2.准备: 为类变量分配内存,设置默认初始值
    3.解析: 把类的2进制数据中的符号引用替换成直接引用

    类的初始化

    JVM 初始化一个类需要经历如下步骤:
    1.如果这个类没有被加载、连接,则程序先加载、连接该类;
    2.如果该类的直接父类没有被初始化,则先初始化直接父类;
    3.如果该类中有初始化语句则先执行初始化语句

    JVM 初始化父类初始化也遵循1~3 的流程,即先初始化继承结构最顶层,再依次初始化间接父类、直接父类、本类。

    加载机制

    一、父类委托:
    先让parent (父) 类加载器加载该class ,父类加载器无法加载该类时才使用子类加载器加载。

    二、缓存机制:
    缓存机制保障所有加载过的Class 都被缓存,程序使用某个类时,先从缓存区查找是否缓存该类,没被缓存则加载该类。
    获取类加载器层次结构:

    package com.sxt.test;
     
    public class Test {
    	 
    	public static void main(String[] args) {
    		
    		ClassLoader loader = ClassLoader.getSystemClassLoader();
    		System.out.println(loader);
    		System.out.println(loader.getParent());
    		System.out.println(loader.getParent().getParent());
    	}
    }
    

    效果如下:
    在这里插入图片描述

    JVM 的根类加载器不是java实现的,而且在程序中无需访问根类加载器,故访问扩展类加载器的父类加载器是为null。

    自定义类加载器

    java.lang.ClassLoader 核心分析

    /**
         * Loads the class with the specified <a href="#name">binary name</a>.  The
         * default implementation of this method searches for classes in the
         * following order:
         *
         * <ol>
         *
         *   <li><p> Invoke {@link #findLoadedClass(String)} to check if the class
         *   has already been loaded.  </p></li>
         *
         *   <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method
         *   on the parent class loader.  If the parent is <tt>null</tt> the class
         *   loader built-in to the virtual machine is used, instead.  </p></li>
         *
         *   <li><p> Invoke the {@link #findClass(String)} method to find the
         *   class.  </p></li>
         *
         * </ol>
         *
         * <p> If the class was found using the above steps, and the
         * <tt>resolve</tt> flag is true, this method will then invoke the {@link
         * #resolveClass(Class)} method on the resulting <tt>Class</tt> object.
         *
         * <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link
         * #findClass(String)}, rather than this method.  </p>
         *
         * <p> Unless overridden, this method synchronizes on the result of
         * {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method
         * during the entire class loading process.
         *
         * @param  name
         *         The <a href="#name">binary name</a> of the class
         *
         * @param  resolve
         *         If <tt>true</tt> then resolve the class
         *
         * @return  The resulting <tt>Class</tt> object
         *
         * @throws  ClassNotFoundException
         *          If the class could not be found
         */
        protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
        {
            synchronized (getClassLoadingLock(name)) {
                // First, check if the class has already been loaded
                Class<?> c = findLoadedClass(name);
                if (c == null) {
                    long t0 = System.nanoTime();
                    try {
                        if (parent != null) {
                            c = parent.loadClass(name, false);
                        } else {
                            c = findBootstrapClassOrNull(name);
                        }
                    } catch (ClassNotFoundException e) {
                        // ClassNotFoundException thrown if class not found
                        // from the non-null parent class loader
                    }
    
                    if (c == null) {
                        // If still not found, then invoke findClass in order
                        // to find the class.
                        long t1 = System.nanoTime();
                        c = findClass(name);
    
                        // this is the defining class loader; record the stats
                        sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                        sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                        sun.misc.PerfCounter.getFindClasses().increment();
                    }
                }
                if (resolve) {
                    resolveClass(c);
                }
                return c;
            }
        }
    

    loadClass(String name, boolean resolve) 方法被调用后将会调用
    1.findLoadedClass(String) 从缓存中查找是否缓存该类,有则返回该类
    2.loadClass(String) 调用父类加载器加载该类,加载成功则返回该类
    3.findClass(String) 调用子类加载器查找该类,成功则返回

    根据jdk 源码分析可知,在自定义加载器时 推荐重写findClass(String) 方法实现加载器自定义。

    如下为简单类加载器的实现:

    package com.sxt.test;
    
    import java.nio.file.Files;
    import java.nio.file.Paths;
    import java.io.*;
    import java.lang.reflect.*;
    
    
    public class Test {
    	 
    	public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
    		
    		 Class clz = new MyClassLoader().loadClass("/Users/vincent/Desktop/test/Test");
    		 
    		 //通过反射获取该Test类的main 方法
    		 Method tmain = clz.getMethod("main", String[].class);
    		 tmain.invoke(null,new Object[] {new String[] {"自定义类加载器"}});
    	}
    }
    
     class MyClassLoader extends ClassLoader {
    	 
    	 
    	 //从输入流中加载该类
    	 //使用方法:	Class clz = new MyClassLoader().loadClass("/Users/vincent/Desktop/test/Test");
    	 //路径/Users/vincent/Desktop/test 下有Test.class 文件,且该文件是没有包名
    	 @Override
    	 public Class<?> findClass(String name){
    		 ByteArrayOutputStream bos = new ByteArrayOutputStream();
    		 try {
    			InputStream is = Files.newInputStream(Paths.get(name+".class"));
    			int len = 0;
    			byte[] buf = new byte[1024];
    			while ((len=is.read(buf)) > 0) {
    				bos.write(buf,0, len);
    			}
    			is.close();
    			byte[] b = bos.toByteArray();
    			
    			String[] parts = name.split("/");
    			Class<?> clz = defineClass(parts[parts.length-1],b, 0, b.length);
    			return clz;
    			
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    		 
    		 return null;
    	 }
     }
    

    /Users/vincent/Desktop/test 路径下的Test.java 文件实现如下:

    import java.io.*;
    import java.util.*;
    
    public class Test{
    	volatile boolean flag = true;
        public static void main(String[] args){
            System.out.println("Test class");
            System.out.println(Arrays.toString(args));
        }
    }
    

    javac Test.java 命令编译该文件生成Test.class 文件

    运行com.sxt.test.Test 文件效果如下:
    在这里插入图片描述
    自定义类加载器成功。

    总结

    自定义类加载器可以实现如下功能
    1.对编译后的class 文件加密,防止反编译 *.class 文件
    2.根据需求动态加载类

    展开全文
  • 1.初始化列表 2.explicit关键字 3.static成员 概念: 4.友元 4.1友元函数 4.2友元
  • 文章目录加载机制加载机制分类、加载器、双亲委派机制加载机制分类加载器双亲委派机制加载.class文件的方式生命周期生命周期结束加载过程JVM初始化步骤加载时机类初始化时机初始化类的实例化 ...
  • C++成员变量初始化位置

    千次阅读 2015-09-08 11:29:16
    static: static表示的是静态的。的静态成员函数、静态成员变量是和相关的,而... 在C++中,static静态成员变量不能在的内部初始化。在的内部只是声明,定义必须在定义体的外部,通常在的实现文件中初始
  • 对象创建过程,初始化顺序

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

    千次阅读 2013-05-13 22:15:40
  • 《java面试宝典》三 类初始化实例化顺序

    千次阅读 多人点赞 2020-01-15 17:55:25
    1.一个类初始化,实际上就是初始化 方法。 注意: 方法,可能不存在,需要有静态方法或者静态代码块。 2.一个子类要想初始化,需要先初始化他的父类。 实例化过程: 实例化就是初始化 方法 有几...
  • 对象创建过程/初始化顺序

    千次阅读 2012-07-13 20:31:42
     对于以上第三个步骤,Java虚拟机可采用3种方式来初始化对象,到底采用何种初始化方式取决于创建对象的方式。 (1)如果对象是通过clone()方法创建的,那么Java虚拟机把原来被克隆对象的实例变量的值拷贝到新对象
  • java对象创建过程/初始化顺序

    千次阅读 2013-10-15 17:36:15
    Java虚拟机创建一个对象都包含以下步骤。 (1)给对象分配内存。 (2)将对象的实例变量自动初始化为其变量类型... 对于以上第三个步骤,Java虚拟机可采用3种方式来初始化对象,到底采用何种初始化方式取决于创建
  • 【JVM】加载、连接和初始化过程

    千次阅读 2016-08-04 14:35:30
    程序运行时,加载主要经过3个阶段分别是的加载,连接和初始化。分别介绍一下这三个过程。 一、加载的加载指的是将的.class文件中二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一...
  • jvm将class文读取到内存中,经过对class文件的校验、转换解析、初始化最终在jvm的heap和方法区分配内存形成可以被jvm直接使用的类型的过程。 的生命周期7个阶段依次为:Loading Verification Preparation ...
  • 的加载和初始化的区别

    千次阅读 2018-10-20 00:44:20
    的加载:代表jvm将java文件编译成class文件后,以二进制的方式存放到运行时数据的方法区中,并在java的堆中创建一个java.lang.Class对象,用来指向存放在方法堆中的数据结构。 这里给大家推荐一篇java加载...
  • 当实例化对象、读取或设置的静态字段(final修饰的常量除外)、调用静态方法时将触发的加载。 第75行输出父类的静态字段,但是并没有引起父类的加载。在编译时,常量传播将该常量直接放置在主类的常量池...
  • 在标准模板库中,常见的文件流对象有fstream、ifstream、ofstream三种,我们可以用文件流的方式去操作文件,比如写文件和读文件,文件流类继承图如下: ifstream继承于istream,实现高层文件流输入(input)操作,...
  • Spring OXM-XStream流化对象

    千次阅读 2017-12-07 20:32:12
    示例源码概述XStream为java.io.ObjectInputStream和ObjectOutputStream提供了替代的实现,允许以对象流方式进行XML序列或者反序列操作。 这对于处理集合对象非常有用(List<User> users),在内存中只保留一个...
  • 文章目录条款04-确定... 成员初始化次序3.1 的成员初始化次序3.2 不同编译单元内的 `non-local static` 对象初始化次序4. 总结 @Author:CSU张扬 @Email:csuzhangyang@gmail.com or csuzhangyang@qq.com @...
  • java接口的初始化

    千次阅读 2018-10-06 12:04:27
    真正不同的地方在于第三点,初始化执行之前要求父类全部都初始化完成了,但接口的初始化貌似对父接口的初始化不怎么感冒。 也就是说,子接口初始化的时候并不要求其父接口也完成初始化,只有在真正使用到父接口...
  • C# 对象和集合初始化

    千次阅读 2011-03-23 19:54:00
    在最合适的地方用C# Object and Collection Initializer。该初始化器,是与get、set器,indexer索引器一样重要的。
  • 读书笔记之类初始化和实例化本质

    千次阅读 2019-12-16 22:46:33
    类初始化时才开始执行我们程序中代码(或字节码),本质上:是执行构造<clinit>方法的过程 实例化时执行我们的构造方法中的代码(或字节码),本质是执行构造<init>方法的过程 #构造<clinit>...
  • 对象反序列化时,如果父类未实现序列化接口,则反序列出的对象会再次调用父类的构造函数来完成属于父类那部分内容的初始化。 1、当将一个父类没有实现序列化的对象son使用ObjectOutputStream写到本地文件中时,...
  • 如何在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-01-06 16:25:16
    之前写过一篇关于Java中普通代码块和static代码块的区别,大致讲解了普通代码块和Static代码的区别,但是并没有讲它们的加载执行顺序,本章就细细的将一下的加载机制(初始化顺序)。 生命周期 的字节码从...
  • MyBatis初始化原理

    千次阅读 2019-03-19 16:26:58
    文章目录MyBatis初始化原理MyBatis的初始化做了什么MyBatis基于XML配置文件创建Configuration对象的过程手动加载XML配置文件创建Configuration对象完成初始化,创建并使用SqlSessionFactory对象涉及到的设计模式 ...
  • 初始化一个的时候,如果发现其父没有进行过初始化,则先初始化其父类(注意!如果其父类是接口的话,则不要求初始化父类); 当虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的那个)...
  • 转换流和对象流

    千次阅读 2018-07-24 17:55:49
    输入字节——字符:InputStreamReader InputStreamReader是Reader的子类,可以将一个字节输入流转变成字符输入,在转换时默认使用本地操作系统的字符编码或者指定其他字符编码,常用方法如下: ...
  • 初始化参数和上下文初始化参数训练 (下述步骤根据本机球境作适当修改)   1.实训目标 创建一个Servlet,读取初始化参数和上下文初始化参数,并将这些参数显示在网页上。掌握Servlet读取参数的编程要点和配置...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 352,262
精华内容 140,904
关键字:

初始化类对象的流