您的位置:首页 > 编程语言 > Java开发

java 中的常量池概念附实例

2017-10-10 11:28 330 查看

1. java中的11中常量类型,

类型号 1-12 没有2类型号

常量池中类型均有如下通用表示方式.

cp_info

{

  u1 tag;

  u1 info[];

}

但是对于具体的某种类型,还可以进一步细化u1 info[].

我们知道, 整型(int,long),实型(float,double),utf8是基本类型,

其它是引用类型.

string: 包含一个utf8

class: 包含一个utf8

nametype: 包含2个utf8,一个name,一个type.

field: 字段,包含一个类,一个nametype

method: 方法,包含一个类,一个nametype

interfaceMethod: 接口方法,包含一个接口,一个nametype

通俗的理解,field 相当于变量, method 相当于函数, 接口方法就是接口函数.

1.1: 5种基本类型

utf8字符串, 整型,实型,长整型,双精度型,有c基础的还是容易理解的.

CONSTANT_Utf8 1 UTF-8编码字符串

CONSTANT_Utf8_info

{

u1 tag; // 1

u2 length;

u1 bytes[length];

}

CONSTANT_Integer 3

CONSTANT_Integer_info

{

u1 tag; //3

u4 bytes; // big endian

}

CONSTANT_Float 4

CONSTANT_Float_info

{

u1 tag; //4

u4 bytes; //IEEE 754单精度浮点格式

}

CONSTANT_Long 5

CONSTANT_Long_info

{

u1 tag; //5

u4 high_bytes; // big endian

u4 low_bytes;

}

CONSTANT_Double 6

CONSTANT_Double_info

{

u1 tag; //6

u4 high_bytes; // IEEE 754双精度浮点格式

u4 low_bytes;

}

1.2: 6种引用类型.

所谓的引用类型,是指当代码访问了成员变量,或者调用了成员函数,那么就构成了对类的成员变量或者类的成员函数的引用, 那个这个变量及其类型或者这个成员函数及其类型要保留到常量池中. 通俗的理解为你访问过什么对象,就要把这个对象保存到常量池中.

6种引用类型依次为:

类引用, 字符串引用,字段引用,方法引用,接口方法引用,名字和类型变量.

除了名字和类型变量我们容易理解, 就是说一个索引指向名字字符串,一个索引指向类型字符串

这些常量可能代表着一个类,一个字符串,一个成员变量,一个函数(成员函数或接口函数),

它们并不代表一个具体数值.

这些概念如何在常量池中表示,?

既然名字和类型这么重要, 那么可以先定义一个名字类型常量, 别的类型就好包含这个名字类型常量了.

深刻的理解还要看后续详细说明.

CONSTANT_Class 7

CONSTANT_Class_info

{

u1 tag; //7

u2 name_index;

}

就是定义一个类或接口的符号引用, 比较简单,容易理解. 就是一个类名.

CONSTANT_String 8

CONSTANT_String_info

{

u1 tag; //8

u2 string_index;

}

就是定义一个字符串引用, 比较简单,容易理解.

当你要访问一个字符串时, 常量池中就包含了一个这样的结构.

CONSTANT_Fieldref 9

CONSTANT_Fieldref_info

{

u1 tag; //9

u2 class_index;

u2 name_and_type_index;

}

当你要引用一个字段时,需要知道它属于那个类,它叫什么名字,是什么类型.

例如: 类A 有一个成员变量i

Class A {

int i;

};

当你要访问A.i时, 常量池中就包含了一个如上的结构常量.

CONSTANT_Methodref 10

CONSTANT_Methodref_info

{

u1 tag; //10

u2 class_index;

u2 name_and_type_index;

}

当你要调用一个类的方法时, 需要知道它属于哪个类, 方法名称叫什么,参数及返回类型是什么.

例如: 类A 有一个成员函数叫geti()

Class A {

int i;

int geti(){return i;}

};

当你要调用类A的geti()方法时, 常量池中就加入了一个

Methodref 常量

CONSTANT_InterfaceMethodref 11 对一个接口中声明的方法的符号引用

CONSTANT_InterfaceMethodref_info

{

u1 tag; //11

u2 class_index;

u2 name_and_type_index;

}

接口方法与类方法类似, 只是它的class_index是接口而不是类, 其它相同.

CONSTANT_NameAndType 12

CONSTANT_NameAndType_info

{

u1 tag; //12

u2 name_index;

u2 descriptor_index;

}

就是对名称和类型的描述, 包括字段名称和字段类型, 方法名称和方法类型

名称是你随意起得名字,是任意字符串, 类型则有一定的规则.

1.3 类型的表达方式

为了减少存储空间,类型并不用源码中所表达的类型来表示,而是采用一种简化的方式.

基本数据类型和void类型对应的字符

byte B

char C

double D

float F

int I

long J

short S

boolean Z

void V

引用数据类型

“L” + 类型的全限定名 + “;”

所谓类型的全限定名就是用/分割各包名形成的路径.例如object引用为:

Ljava/lang/Object;

多维数组类型表示

若干个“[” + 数组中元素类型的对应字符串

字段的类型描述符

int i –>I

object o –>Ljava/lang/Object;

方法类型描述符比较复杂, 包括所有参数的类型列表和方法返回值。

它的格式是这样的:

(参数1类型 参数2类型 参数3类型 …)返回值类型

举例:

方法类型描述符 方法声明

()I int getSize()

()Ljava/lang/String; String toString()

([Ljava/lang/String;)V void main(String[] args)

()V void wait()

(JI)V void wait(long timeout, int nanos)

(ZILjava/lang/String;II)Z boolean regionMatches(boolean ignoreCase, int toOffset, String other, int ooffset, int len)

([BII)I int read(byte[] b, int off, int len )

()[[Ljava/lang/Object; Object[][] getObjectArray()

我们看到, 格式化的方法类型描述符比源码中的方法声明还是简化了不少!

类的构造方法的方法名使用字符串 表示, 而静态初始化方法的方法名使用字符串 表示, 这两个是特殊名称.

有了上面的基本概念,我们看一下hello.java 的常量池吧.

2. hello.java 常量池

2.1 hello.java 源码

$ cat hello.java
public class hello{
public static void main(String[] args)
{
System.out.println("hello world\n");
}
}


2.2 生成hello.class

$ javac hello.java

2.3 导出hello.class

$ javap -v hello.class
Classfile /home/hjj/workspace/javat/hello.class
Last modified Oct 10, 2017; size 416 bytes
MD5 checksum 494dd3b28933beec5ca8cb3e62198615
Compiled from "hello.java"
public class hello
SourceFile: "hello.java"
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref          #6.#15         //  java/lang/Object."<init>":()V
#2 = Fieldref           #16.#17        //  java/lang/System.out:Ljava/io/PrintStream;
#3 = String             #18            //  hello world\n
#4 = Methodref          #19.#20        //  java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class              #21            //  hello
#6 = Class              #22            //  java/lang/Object
#7 = Utf8               <init>
#8 = Utf8               ()V
#9 = Utf8               Code
#10 = Utf8               LineNumberTable
#11 = Utf8               main
#12 = Utf8               ([Ljava/lang/String;)V
#13 = Utf8               SourceFile
#14 = Utf8               hello.java
#15 = NameAndType        #7:#8          //  "<init>":()V
#16 = Class              #23            //  java/lang/System
#17 = NameAndType        #24:#25        //  out:Ljava/io/PrintStream;
#18 = Utf8               hello world\n
#19 = Class              #26            //  java/io/PrintStream
#20 = NameAndType        #27:#28        //  println:(Ljava/lang/String;)V
#21 = Utf8               hello
#22 = Utf8               java/lang/Object
#23 = Utf8               java/lang/System
#24 = Utf8               out
#25 = Utf8               Ljava/io/PrintStream;
#26 = Utf8               java/io/PrintStream
#27 = Utf8               println
#28 = Utf8               (Ljava/lang/String;)V
{
public hello();
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1                  // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0

public static void main(java.lang.String[]);
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc           #3                  // String hello world\n
5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 4: 0
line 5: 8
}


2.4 分析常量池

我们只分析一下28个常量池, 至于其它留待以后再分析!

utf8 常量17个

#7 = Utf8 //特殊名称

#8 = Utf8 ()V //方法类型

#9 = Utf8 Code //—-

#10 = Utf8 LineNumberTable //—-

#11 = Utf8 main //执行程序的入口

#12 = Utf8 ([Ljava/lang/String;)V //方法类型

#13 = Utf8 SourceFile //—-

#14 = Utf8 hello.java //程序名称

#18 = Utf8 hello world\n //程序中字符串

#21 = Utf8 hello //类名

#22 = Utf8 java/lang/Object //对象全限定名

#23 = Utf8 java/lang/System //对象全限定名

#24 = Utf8 out //对象字段

#25 = Utf8 Ljava/io/PrintStream; //对象引用

#26 = Utf8 java/io/PrintStream //对象全限定名

#27 = Utf8 println //方法名称

#28 = Utf8 (Ljava/lang/String;)V //方法类型

我们发现, 有3个标识为//—-的似乎意义不大, 其它我们都标识了名称的用途.

大体上3类,一个自定义名称,一个类型. 一个其它类中定义的名称.

NameAndType 常量3个

#15 = NameAndType #7:#8 // “”:()V

#17 = NameAndType #24:#25 // out:Ljava/io/PrintStream;

#20 = NameAndType #27:#28 // println:(Ljava/lang/String;)V

由此也直接看到了3个是name, 3个是type,

细分还知道, 一个是特殊名称,一个是成员名称,一个是方法名称.

Class 常量4个

分别为hello类, Object类,System类, PrintStream 类

#5 = Class #21 // hello

#6 = Class #22 // java/lang/Object

#16 = Class #23 // java/lang/System

#19 = Class #26 // java/io/PrintStream

String 引用1个

#3 = String #18 // hello world\n

就是hello world, 客户要打印的字符串.

Fieldref 1个

#2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream;

是对System.out 的引用

Methodref 2个

#1 = Methodref #6.#15 // java/lang/Object.””:()V

#4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V

对Object 初始化函数的调用

对PrintStream的println 函数的调用.

常量池项 17+3+4+1+1+2 = 28 个分析完毕.

总结: java class 文件是对源代码的以此以人阅读为目的向以虚拟机阅读为目的的信息转换, 其中常量池除了描述数值外,还描述了代码中用到的成员变量及类型,代码中用到的成员函数及类型
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: