代码编织梦想

一、概念

所谓的 语法糖 ,其实就是指 java 编译器把 *.java 源码编译为 *.class 字节码的过程中,自动生成 和转换的一些代码,主要是为了减轻程序员的负担,算是 java 编译器给我们的一个额外福利(给糖吃 嘛)

注意:以下代码的分析,借助了 javap 工具,idea 的反编译功能,idea 插件 jclasslib 等工具。另外, 编译器转换的结果直接就是 class 字节码,只是为了便于阅读,给出了几乎等价的 java 源码方式,并不是编译器还会转换出中间的 java 源码,切记。

二、默认构造器

public class Candy1 {
}
public class org.example.classLoading.Candy1
  ...
Constant pool:
	#1 = Methodref          #3.#13         // java/lang/Object."<init>":()V
    ...
{
  // 编译器优化:这个无参构造是编译器帮助我们加上的
  public org.example.classLoading.Candy1();
    ...
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1	//super();
         4: return
      LineNumberTable: ...
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lorg/example/classLoading/Candy1;
}

二、自动拆装箱

代码片段1 :显然当前版本太麻烦了,需要在基本类型和包装类型之间来回转换(尤其是集合类中操作的都是 包装类型)

public class Candy2 {
	public static void main(String[] args) {
		Integer x = Integer.valueOf(1);
		int y = x.intValue();
	}
}

因此这些转换的事情在 JDK 5 以后都由编译器在编译阶段完成。即 代码片段1 都会在编译阶段被转换为 代码片段2

代码片段2: 这段代码在 JDK 5 之前是无法编译通过的,JDK 5 开始自动优化

public class Candy2 {
	public static void main(String[] args) {
		Integer x = 1;	//编译器进行优化👆
		int y = x;	//编译器进行优化👆
	}
}

三、泛型擦除、反射泛型

泛型是在 JDK 5 开始加入的特性,主要是在编译期间做类型检查。
java 在编译泛型代码后会执行 泛型擦除 的动作,即泛型信息在编译为字节码之后就丢失了,实际的类型都当做了 Object 类型来处理:取值时,字节码需做一个强制类型转换

public class Candy3 {
    public static void main(String[] args) {
        List<Integer> list=new ArrayList<>();
        list.add(10);
        Integer x=list.get(0);//取值时,字节码需做一个强制类型转换
    }
}

泛型是为了在编译期间的类型检查,泛型擦除是为了便于字节码处理

擦除的是字节码上的泛型信息,可以看到 LocalVariableTypeTable(局部变量类型表) 仍然保留了方法参数泛型的信息

public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: new           #2
         3: dup
         4: invokespecial #3
         7: astore_1
         8: aload_1
         9: bipush        10
        11: invokestatic  #4                  // 1.Integer.valueOf(10)
        14: invokeinterface #5,  2            // 2.注意为Object:List.add(Object)
        19: pop
        20: aload_1
        21: iconst_0
        22: invokeinterface #6,  2            // 3.list.get(Object);
        27: checkcast     #7                  // 4.注意强制类型转换:(Integer)list.get(0);
        30: astore_2
        31: return
      LineNumberTable: ...
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      32     0  args   [Ljava/lang/String;
            8      24     1  list   Ljava/util/List;
           31       1     2     x   Ljava/lang/Integer;
      LocalVariableTypeTable:
        Start  Length  Slot  Name   Signature
            8      24     1  list   Ljava/util/List<Ljava/lang/Integer;>;

反射泛型

注意:局部遍历的泛型信息无法通过反射获取。只有类结构上的泛型可以通过反射获取

public class Candy3 {
    public static void main(String[] args) throws NoSuchMethodException {
        Method test = Candy3.class.getMethod("test", List.class, Map.class);
        Type[] types = test.getGenericParameterTypes();
        for (Type type : types) {
            if (type instanceof ParameterizedType){
                ParameterizedType parameterizedType= (ParameterizedType) type;
                System.out.println("原始类型 - "+parameterizedType.getRawType());
                Type[] arguments = parameterizedType.getActualTypeArguments();
                for (int i=0;i<arguments.length;i++) {
                    System.out.printf("泛型参数[%d] - %s\n",i,arguments[i]);
                }
            }
        }
    }
    public Set<Integer> test(List<String> list,Map<Integer,Object> map){
        return new HashSet<>();
    }
}
原始类型 - interface java.util.List
泛型参数[0] - class java.lang.String
原始类型 - interface java.util.Map
泛型参数[0] - class java.lang.Integer
泛型参数[1] - class java.lang.Object

四、可变参数

可变参数也是 JDK 5 开始加入的新特性:

public class Candy4 {
	public static void main(String[] args) {
		foo("hello", "world");
	}
    
	public static void foo(String... args) {
		String[] array = args; //可以直接赋值
		System.out.println(array);
	}
}

可变参数 String… args 其实是一个 String[] args ,从代码中的赋值语句中就可以看出来。

同样 java 编译器会在编译期间将上述代码变换为:

注意:如果调用了 foo() 则等价代码为 foo(new String[]{}) ,创建了一个空的数组,而不会传递 null 进去

public class Candy4 {
	public static void main(String[] args) {
		foo(new String[]{"hello", "world"});	//编译器进行优化
	}
    
	public static void foo(String[] args) {
		String[] array = args; // 可以直接赋值
		System.out.println(array);
	}
}

五、foreach循环

仍是 JDK 5 开始引入的语法糖。

注意: foreach 循环写法,能够配合数组,以及所有实现了 Iterable 接口的集合类一起使用。其中 Iterable 用来获取集合的迭代器( Iterator )

数组的循环

public class Candy5_1 {
	public static void main(String[] args) {
		int[] array = {1, 2, 3, 4, 5};
		for (int e : array) {
			System.out.println(e);
		}
	}
}

编译器编译后的字节码:

public class Candy5_1 {
	public static void main(String[] args) {
		int[] array = new int[]{1, 2, 3, 4, 5};	//编译器进行优化
		for(int i = 0; i < array.length; ++i) {	//编译器进行优化
			int e = array[i];
			System.out.println(e);
		}
	}
}

集合的循环

public class Candy5_2 {
	public static void main(String[] args) {
		List<Integer> list = Arrays.asList(1,2,3,4,5);
		for (Integer i : list) {
			System.out.println(i);
		}
	}
}

编译器编译后的字节码:迭代器的调用

public class Candy5_2 {
	public static void main(String[] args) {
		List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
		Iterator iter = list.iterator();	//编译器进行优化
		while(iter.hasNext()) {	//编译器进行优化
			Integer e = (Integer)iter.next();
			System.out.println(e);
		}
	}
}

六、switch 字符串

从 JDK 7 开始,switch 可以作用于字符串和枚举类,这个功能其实也是语法糖,例如:

注意:配合 String 和枚举使用时,不能 case “null”。因为 null 不能调用hashCode()

public class Candy6_1 {
	public static void choose(String str) {
		switch (str) {
			case "hello": {
				System.out.println("h");
				break;
			}
			case "world": {
				System.out.println("w");
				break;
			}
		}
	}
}

编译器编译后的字节码:将字符串转换为 byte 类型,再进行比较

public class Candy6_1 {
	public static void choose(String str) {
		byte x = -1;
		switch(str.hashCode()) {
			case 99162322: //"hello".(hashCode):编译器进行优化
				if (str.equals("hello")) {
		    		x = 0;
				}
				break;
			case 113318802: //"world".(hashCode):编译器进行优化
				if (str.equals("world")) {
					x = 1;
				}
		}
		switch(x) {
			case 0:
				System.out.println("h");
				break;
			case 1:
				System.out.println("w");
		}
	}
}

七、switch 枚举

switch 枚举的例子,原始代码:

public class Candy7 {
	public static void foo(Sex sex) {
		switch (sex) {
			case MALE:
				System.out.println("男"); break;
			case FEMALE:
				System.out.println("女"); break;
		}
	}
}
enum Sex {
	MALE, FEMALE
}

编译器编译后的字节码:

public class Candy7 {
    //编译器进行优化:定义一个合成类,仅 jvm 使用
	static class $MAP {
		static int[] map = new int[2]; //大小即为枚举元素个数
		static {
			map[Sex.MALE.ordinal()] = 1;	//存储case对应的数字
			map[Sex.FEMALE.ordinal()] = 2;
		}
	}
	public static void foo(Sex sex) {
		switch (sex.ordinal()) {	//编译器进行优化
			case 1:
				System.out.println("男");
				break;
			case 2:
		        System.out.println("女");
				break;
		}
	}
}

八、枚举类

JDK 7 新增了枚举类,以前面的性别枚举为例:

enum Sex {
	MALE, FEMALE
}

编译器编译后的字节码:

final class Sex extends Enum<Sex>{	//编译器进行优化
    static{
        MALE = new Sex("MALE", 0);
		FEMALE = new Sex("FEMALE", 1);
		$VALUES = new Sex[]{MALE, FEMALE};
    }
    public static final Sex MALE;
    public static final Sex FEMALE;
    private static final Sex[] $VALUES;
    private Sex(String name, int ordinal) {
		super(name, ordinal);
	}
    public static Sex[] values(){
        return (Sex[])$VALUES.clone();
    }
    public static Sex valueOf(String name){
        return (Sex)Enum.valueOf(Sex.class,name);
    }
}

九、try-with-resources

JDK 7 开始新增了对需要关闭的资源处理的特殊语法 try-with-resources

其中资源对象需要实现 AutoCloseable 接口,例如 InputStream 、 OutputStream 、 Connection 、 Statement 、 ResultSet 等接口都实现了 AutoCloseable ,使用 try-withresources 可以不用写 finally 语句块,编译器会帮助生成关闭资源代码,例如:

public class Candy9 {
	public static void main(String[] args) {
        try(资源变量 = 创建资源对象){

		} catch( ) {

		}
	}
}

编译器编译后的字节码:

public class Candy9 {
	public static void main(String[] args) {
		try {	//编译器进行优化
			InputStream is = new FileInputStream("d:\\1.txt");
			Throwable t = null;
			try {
				System.out.println(is);
			} catch (Throwable e1) {
				t = e1;
				throw e1;
			} finally {
				if (is != null) {
					if (t != null) {
						try {
							is.close();
						} catch (Throwable e2) {
							t.addSuppressed(e2);	//注意:输出外层套内存的异常
						}
                    } else {
						is.close();
					}
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

十、方法重写时的桥接方法

我们都知道,方法重写时对返回值分两种情况:

  • 父子类的返回值完全一致
  • 子类返回值可以是父类返回值的子类(比较绕口,见下面的例子)
class A {
	public Number m() {
		return 1;
	}
}
class B extends A {
	@Override
	public Integer m() {	//返回值类型与父类不一致,那怎么说是重写呢?
		return 2;
	}
}

对于子类,编译器编译后的字节码:

class B extends A {
	public Integer m() {
		return 2;
	}
	// 合成方法,仅JVM可见:真正的重写方法
	public synthetic bridge Number m() {
		return m();
	}
}

验证

public class Candy10 {
    public static void main(String[] args) {
        Method[] declaredMethods = B.class.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            System.out.println(declaredMethod);	//输出两个m()
        }
    }
}
#两个m()
public java.lang.Integer org.example.classLoading.candy.B.m()
public java.lang.Number org.example.classLoading.candy.B.m()

十一、匿名内部类

源代码:

public class Candy11 {
	public static void main(String[] args) {
		Runnable runnable = new Runnable() {
			@Override
			public void run() {
				System.out.println("ok");
			}
		};
	}
}

编译器编译后的字节码:

public class Candy11 {
	public static void main(String[] args) {
		Runnable runnable = new Candy11$1();
	}
}
// 额外生成的类
final class Candy11$1 implements Runnable {
	Candy11$1() {
	}
	public void run() {
		System.out.println("ok");
	}
}

引用 局部变量 的匿名内部类,源代码:

注意

为什么匿名内部类引用局部变量时,局部变量必须是 final 的?

  • 因为在创建 Candy11$1 对象时,将 x 的值赋值给了 Candy11 1 对 象 的 v a l 1 对象的 val 1valx 属性,所以 x 不应该再发生变 化了。如果变化,那么 val$x 属性没有机会再跟着一起变化
public class Candy11 {
	public static void test(final int x) {
		Runnable runnable = new Runnable() {
			@Override
			public void run() {
				System.out.println("ok:" + x);
			}
		};
	}
}

编译器编译后的字节码:

public class Candy11 {
	public static void test(final int x) {
		Runnable runnable = new Candy11$1(x);
	}
}
// 额外生成的类
final class Candy11$1 implements Runnable {
	int val$x;
	Candy11$1(int x) {
		this.val$x = x;
	}
	public void run() {
		System.out.println("ok:" + this.val$x);
	}
}
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_43401592/article/details/128019546

深入理解jvm字节码执行引擎_走在小路的博客-爱代码爱编程_jvm执行引擎

我们都知道,在当前的Java中(1.0)之后,编译器讲源代码转成字节码,那么字节码如何被执行的呢?这就涉及到了JVM的字节码执行引擎,执行引擎负责具体的代码调用及执行过程。就目前而言,所有的执行引擎的基本一致: 输入:字节码文件 处理:字节码解析 输出:执行结果。 物理机的执行引擎是由硬件实现的,和物理机的执行过程不同的是虚拟机的执行引擎由于自己

jvm之字节码-爱代码爱编程

目录 一、引言 二、二进制字节码与操作码助记符 三、字节码主要指令 1.加载或存储指令 2.运算指令 3.类型转换指令 4.对象创建和访问指令 5.操作栈管理指令 6.方法调用与返回指令 7.同步指令 四、源码转字节码过程 1.词法解析 2.语法解析 3.语义分析 五、字节码执行过程 一、引言 0 与 1 是计算机仅能识

jvm(一):前端编译、jit编译、aot编译_luk流的博客-爱代码爱编程

1.前端编译:源代码到字节码 把Java源码文件(.java)编译成Class文件(.class)的过程。 我们知道在 JDK 的安装目录里有一个 javac 工具,就是它将 Java 代码翻译成字节码,这个工具我们叫做

jvm-5.程序编译与代码优化_tasisme的博客-爱代码爱编程_compilethreshold

目录: JVM-1.自动内存管理 JVM-2.字节码和字节码指令 JVM-3.类的加载机制 JVM-4.字节码执行和方法调用 JVM-5.程序编译与代码优化 JVM-6.Java线程内存模型和线程实现 java

jvm字节码指令集大全及其介绍_没头脑遇到不高兴的博客-爱代码爱编程_jvm字节码

本节将会着重介绍一下JVM中的指令集、Java是如何跨平台的、JVM指令集参考手册等内容。 目录 Java是怎么跨平台的 平台无关的基石 JVM字节码指令介绍 字节码与数据类型 加载和存储指令 算术指令 类型转换指令 对象创建与访问指令 操作数栈管理指令 控制转移指令 方法调用和返回指令 异常处理指令 同步指令 JVM指令集大

JVM 具体是怎样运行 Java 字节码的-爱代码爱编程

文章目录 为什么 Java 要在虚拟机里运行?Java 虚拟机具体是怎样运行 Java 字节码的?Java 虚拟机的运行效率如何?小结 Java 代码有很多种不同的运行方式,可以在开发工具种运行;可以双击执行 jar 文件运行;可以在命令行种运行;甚至可以在网页种运行,这些都离不开 JRE,也就是 Java运行环境。(包含 Java 程序的必须

JVM 学习笔记(三)类加载与字节码技术&内存模型-爱代码爱编程

四、类加载与字节码技术 1、类文件结构 通过 javac 类名.java 编译 java 文件后,会生成一个 .class 的文件! 以下是字节码文件: 0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09 0000020 00 16 00 17 08 00 18 0a 00 19 00

java字节码和机器码_JVM和字节码的关系 -惊觉...-爱代码爱编程

程序运行的两种方式:静态编译和动态解释 静态编译的程序在执行前全部被翻译为机器码 动态解释执行的则是一句一句边运行边翻译 Java严格说来是“半解释半编译”型的语言 Java代码首先由javac编译器编译成字节码(ByteCode)。字节码文件中JVM指令(也称字节码指令)是JVM唯一能够识别的指令,JVM执行JVM指令时,通过JVM解释器逐条

java web项目编译_Java三种编译方式: 前端编译 JIT编译 AOT编译-爱代码爱编程

java程序代码需要编译后才能在虚拟机中运行,编译涉及到非常多的知识层面:编译原理、语言规范、虚拟机规范、本地机器码优化等;了解编译过程有利于了解整个Java运行机制,不仅可以使得我们编写出更优秀的代码,而且还可以使得在JVM调优时更得心应手。 下面我们先来看下Java体系中的三种编译方式:前端编译、即时编译(JIT编译)、静态提前编译(AOT编译)

《JVM系列》 第七章 -- 字节码执行引擎-爱代码爱编程

执行引擎概述 执行引擎属于JVM的下层,里面包括: 解释器、及时编译器、垃圾回收器 执行引擎是Java虚拟机核心的组成部分之一。“虚拟机”是一个相对于“物理机”的概念,这两种机器都有代码执行能力,其区别是物理机的执行引擎是直接建立在处理器、缓存、指令集和操作系统层面上的,而虚拟机的执行引擎则是由软件自行实现的,因此可以不受物理条件制约地定制指令集与执行

【JVM类加载及字节码技术】编译期处理-语法糖-构造、泛型、拆装箱(一)-爱代码爱编程

文章目录 前言一、默认构造函数1.案例代码2.编译优化后二、自动拆装箱1.案例代码2.编译优化后二、泛型集合取值1.案例代码2.字节码文件 前言 语法糖:其实就是指java编译器把 .java 源文件编译为 .class 字节码的过程,自动生成和转换的代码,主要是为了减轻程序员的负担,算是java编译器给我们的额外福利。 注

JVM系列之:宏观分析Java代码是如何执行的-爱代码爱编程

前言 作为一名 Java 程序员,平日里都是和 Java 代码打交道,但是仅限于使用,比如说使用 Java 核心类库,以及调用第三方类库里的 API。凭借上述“本事”便可以专注于实现具体业务,并且依赖 Java 虚拟机自动执行乃至优化我们的应用程序。那么自己就仅限于此了吗? 众所周知,JVM 和并发是应聘面试中两个绕不开的考点,大厂一些岗位招聘要求上明

黑马程序员JVM笔记03-类加载与字节码技术-爱代码爱编程

类文件结构 以一段简单的java代码为例: package cn.itcast.jvm.t5; // HelloWorld 示例 public class HelloWorld { public static void main(String[] args) { System.out.println("hello world"); }