成员变量的赋值过程:
- 默认初始化
- 显式初始化/代码块中初始化
- 构造器中初始化
- 有了对象之后,可以""对象.属性或"对象.方法的方式对成员变量进行赋值。
多态中:属性不存在多态性。
class文件解读
字节含义
整个class文件中相关字节所表示含义:
- ux:表示占几个字节,如u4=占4个字节。
魔数与版本
- 前4个字节:ca fe ba be 表示识别文件为class文件。
- 往后4个字节显示编译使用jdk的版本号
常量池
可以理解为Class文件之中的资源仓库,它是Clas文件结构中与其他项目关联最多的数据类型(后面的很多数据类型都会指向此处),也是占用class文件空间最大的数据项目之一。
你在main方法里要调用方法a(),你要知道方法a()在内存中存在哪儿的吧?那怎么知道呢?
就是类加载的时候把符号引用转换为直接引用。
描述符
描述符的作用是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。根据描述符规则,基本数据类型(byte、char、double、float、int、long、short、boolean)以及代表无返回值的void类型都用一个大写字符来表示,而对象类型则用字符L加对象的全限定名来表示,详见下表:(数据类型:基本数据类型、引用数据类型)
类和接口
在访问标记后,会指定该类的类别、父类类别以及实现的接口,格式如下:
类口访问标识
是类还是接口;是否定义为 public类型;是否定义为 abstract类型;如果是类的话,是否被声明为 final等。各种访问标记如下所示:
字段表集合
- 用于描述接口或类中声明的变量。字段(field)包括类级变量以及实例级变量,但是不包括方法内部、代码块内部声明的局部变量。( local variables)
- 字段叫什么名字、字段被定义为什么数据类型,这些都是无法固定的,只能引用常量池中的常量来描述。
- 它指向常量池索引集合,它描述了每个字段的完整信息。比如字段的标识符、访问修饰符(public、private或protected)、是类变量还是实例变量(static修饰符〉、是否是常量(final修饰符)等。
注意事项:
- 字段表集合中不会列出从父类或者实现的接口中继承而来的字段,但有可能列出原本Java代码之中不存在的字段。譬如在内部类中为了保持对外部类的访问性,会自动添加指向外部类实例的字段。
- 在Java语言中字段是无法重载的,两个字段的数据类型、修饰符不管是否相同,都必须使用不一样的名称,但是对于字节码来讲,如果两个字段的描述符不一致,那手段重名就是合法的。
fields [](字段表)
fields表中的每个成员都必须是一个fields_info结构的数据项,用于表示当前类或接口中某个字段的完整描述。
一个字段的信息包括如下这些信息。这些信息中,各个修饰符都是布尔值,要么有,要么没有。
- 作用域( public、private、protected修饰符)
- 是实例变量还是类变量(static修饰符)
- 可变性(final)
- 并发可见性(volatile修饰符,是否强制从主内存读写)
- 可否序列化((transient修饰符)
- 字段数据类型(基本数据类型、对象、数组)>字段名称
字段表结构:字段表作为一个表,同样有他自己的结构;
字段访问标识
字段名索引
根据字段名索引的值,查询常量池中的指定索引项即可。
描述符索引
描述符的作用是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。根据描述符规则,基本数据类型(byte,char, double ,float,int,long, short ,boolean)及代表无返回值的void类型都用一个大写字符来表示,而对象则用字符L加对象的全限定名来表示,如下所示:
方法表集合
methods:指向常量池索引集合,它完整描述了每个方法的签名。
-
在字节码文件中,每一个method_info项都对应着一个类或者接口中的方法信息。比如方法的访问修饰符(puprivate或protected),方法的返回值类型以及方法的参数信息等。
-
如果这个方法不是抽象的或者不是native的,那么字节码中会体现出来。
-
一方面,methods表只描述当前类或接口中声明的方法,不包括从父类或父接口继承的方法。另一方面,me(thods表有可能会出现由编译器自动添加的方法,最典型的便是编译器产生的方法信息(比如:类(接口)初始化方法()和实例初始化方法())。
使用注意事项:
在Java语言中,要重载(Overload)一个方法,除了要与原方法具有相同的简单名称之外,还要求必须拥有一个与原方法不同的特征签名,特征签名就是一个方法中各个参数在常量池中的字段符号引用的集合,也就是因为返回值不会包含在特征签名之中,因此Java语言里无法仅仅依靠返回值的不同来对一个已有方法进行重载。但在Class文件格式中,特征签名的范围更大一些,只要描述符不是完全一致的两个方法就可以共存。也就是说,如果两个方法有相同的名称和特征签名,但返回值不同,那么也是可以合法共存于同一个class文件中。
也就是说,尽管Java语法规范并不允许在一个类或者接口中声明多个方法签名相同的方法,但是和Java语法规范相反,字节码文件中却恰恰允许存放多个方法签名相同的方法,唯一的条件就是这些方法之间的返回值不能相同。
methods [](方法表)
- methods表中的每个成员都必须是一个method_info结构,用于表示当前类或接口中某个方法的完整描述。如果某个method_info结构的access_flags项既没有设置 ACC_NATIVE 标志也没有设置ACC_ABSTRACT标志,那么该结构中也应包含实现这个方法所用的Java虚拟机指令。
- method_info结构可以表示类和接口中定义的所有方法,包括实例方法、类方法、实例初始化方法和类或接口初始化方法
- 方法表的结构实际跟字段表是一样的,方法表结构如下:
方法访问标识
- 方法名索引及描述符索引通过指向常量池的引用进行查找
属性表集合
-
方法表集合之后的属性表集合,指的是cLass文件所携带的辅助信息,比如该class 文件的源文件的名称。以及任何带有RetentionPolicy.CLASS或者RetentionPolicy.RUNTIME的注解。这类信息通常被用于Java虚拟机的验证和运行,以及Java程序的调试,一般无须深入了解。
-
此外,字段表、方法表都可以有自己的属性表。用于描述某些场景专有的信息。
-
属性表集合的限制没有那么严格,不再要求各个属性表具有严格的顺序,并且只要不与已有的属性名重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,但Java虚拟机运行时会忽略掉它不认识的属性。
attributes [](属性表)
属性通用格式:属性表的每个项的值必须是attribute_info结构。属性表的结构比较灵活,各种不同的属性只要满足以下结构即可。
属性类型
属性表实际上可以有很多类型,上面看到的Code属性只是其中一种,Java8里面定义了23种属性。下面这些是虚拟机中预定义的属性:
code属性表
Code属性就是存放方法体里面的代码。但是,并非所有方法表都有Code属性。像接口或者抽象方法,他们没有具体的方法体,因此也就不会有code属性了。
Code属性表的结构,如下图: