1、复习:再谈操作数栈与局部变量表
i/s是槽位共用,因为int i = 0出了代码块,就失效了,所以下面的String s变量就不开辟新的槽位,直接复用上面的i
2、局部变量压栈指令
Xload操作:从局部变量表中的指定索引位置的变量,压入操作数栈中
3、常量入栈指令
const、push、ldc:将常量压入操作数栈
对应的常量表示数值的范围依次变大
4、出栈入局部变量表指令
里面有代码,也有字节码,所以可以根据老师给的图展开分析,首先该方法被调用的时候,形式参数k和d都是有确定的值,由于该方法不是静态方法,所以局部变量表中的第一个位置(槽位)存储this,而第二个位置存储k具体的值,由于老师只是分析,没有调用这个方法,所以老师全部使用的变量名称来代替具体的值,所以明白就好,继续来分析,然后第三个和第四个位置储存d具体的值,由于d是double类型,所以需要占据两个槽位,数据已经准备好了,那就来看字节码,首先iload_1是将局部变量表中下标为1的k值取出来压入操作数栈中,然后iconst_2是将常量池中的整型值2压入操作数栈,iadd让操作数栈弹出的k值和整型值2执行相加操作,之后将相加的结果值m压入操作数栈中,请注意老师的画法,在执行弹栈和压栈操作之后,老师并没有删除操作数栈中的k值和2,这是因为老师让我们知道具体的操作过程,所以故意为之,不过真正的操作是弹栈之后k值和2就会从操作数栈中弹出,之后操作数栈中就没有k值和2了,只有m值了,然后istore_4是将操作数栈中的m值弹出栈,然后存在局部变量表中下标为4的位置,idc2_w #13代表将long型值12压入操作数栈,istore5是将值12弹栈之后放入局部变量表中下标为5的位置,由于12是long型,所以占据两个位置(槽位),ldc #15代表将字符串atguigu压入操作数栈,astore 7代表将字符串atguigu弹栈之后放入局部变量表中下标为7的位置,idc #16代表将float类型数据10.0压入操作数栈,fstore 8代表将10.0弹出栈,然后放入局部变量表中下标为8的位置,idc2_w #17代表将10.0压入操作数栈,dstore2代表将10.0弹出栈,之后将10.0放入下标为2和3的操作,毕竟这是double类型数据
槽位复用:
注意:在方法没有运行的时候,根据字节码文件就可以计算出需要几个槽位
对于无穷大和NaN的举例:
1、所有算术指令
①举例
②一个曾经的案例1
2、比较指令的说明
注意:NaN(Not a Number)表示不是一个数字,比如0.0/0.0得到的可能是1.0(两个数相等),也可能是0.0(0.0是分子),也可能是无穷大(0.0是分母),所以老师给出的解释是NaN代表无法确定是什么数字,只有double和float类型中可能出现NaN的情况,而long类型不会出现NaN,所以只有lcmp,而没有lcml
3、i++和++i的区别
- 对于
i++
,先赋值后++
先加载10到操作数栈【bipush 10】,并保存到局部变量表1索引的位置(0的位置是this)【istore_1】,然后自增局部变量表索引1的变量【iinc 1 by 1】,(此时10还在操作数栈中),然后把操作数栈中当前的10,存储在索引2中【istore_2】,最终的结果是i=11(索引1的位置),a=10(索引2的位置);总结i=11,a=10;
- 对于
++i
,先++后赋值
先是加载20到操作数栈中【bipush 20】,将操作数栈20的数据保存到索引3的位置【istore_3】,自增索引3位置的数据【iinc 3 by 1】,此时索引3的数据是21,加载索引3的数据到操作数栈中【iload_3】,然后存储到索引为4的位置,此时索引为3的数据为21,索引为4的数据也是21,;总结j=21,b=21;
对于++i和i++,对于i都会自增,但是对于他所赋值给的对象,会有不同的时机进行自增;
针对java中的基本类型
1、宽化类型转换
容量小的转换为大的,自动类型提升
2、窄化类型转换
容量大的转化为小的,强制类型转换
注意:从float、double、long等类型往byte、short、char类型转换的时候,需要先把前面几种类型转换成int类型,然后在从int类型转换到后面这几种类型,所以int类型
相等于一种过渡类型
面向对象包含:类类型、数组类型
1、创建指令
2、字段访问指令
注意:get是入栈,而put是出栈
3、数组操作指令
4、类型检查指令
1、方法调用指令
- invokespecial:↓静态分派
- invokestatic:↓
- invokeinterface:↓
注意:
1、invokedynamic老师不讲,估计是很少遇到吧
2、invokeinterface是对接口而言的,用属于接口类型的对象调用方法的时候就是这个
3、invokespecial只有构造器、私有方法、super.方法名()调用父类方法这几种情况,其中调用父类方法这种情况可能出现其直接父类没有该方法,那就可以调用其父类继承的父类中的该方法,最终找到一个方法调用就是了
4、invokestatic是调用static静态方法,无论是使用对象.静态方法名()还是类名.静态方法名()都是invokestatic,也不难理解
5、invokevirtual是调用类中的非静态普通方法,而这种实例方法可能调用的是子类重写的非静态普通方法,比如A a = new B();a.hello(),其中B类继承A类,并且B类重写了A类中的hello()方法,这种情况下就是invokevirtual了,但是有可能该类没有子类,调用的就是本类中的非静态普通方法,这种情况也是invokevirtual了
2、方法返回指令
1、条件跳转指令
注意:
1、对于float、double、long类型的比较,它们比较之后生成的是int类型的0、1、-1,这个过程可以使用比较指令和条件跳转指令来完成,虽然得到的是int类型的值,但是System.out.println(XXX)中的值是布尔类型,你可以在jclasslib中的常量池信息中看到写的是Z,代表布尔值类型
2、int类型值(包含byte、char、short)比较 和 对象类型值比较需要使用比较条件跳转指令
2、比较条件跳转指令
注意:
1、上面所说的后者是栈顶元素,而前者是栈顶下面的元素
2、对于float、double、long类型的比较,它们比较之后生成的是int类型的0、1、-1,这个过程可以使用比较指令和条件跳转指令来完成
而 int类型值(包含byte、char、short)比较 和 对象类型值比较需要使用比较条件跳转指令,其中对象类型值不是比较的地址,就是比较对象中的某些字段值,这又归咎到float、double、long、int类型的比较中了
3、无论哪种比较,也不管两个比较值的中间是什么符号(>、=、
3、多条件分支跳转
- tableswitch:↓
- lookupswitch:↓
- jdk7加入判断String类型的方式:↓
通过计算这个String字符串的hash值来判断是否相等