java中的语法糖

本文详细介绍了Java中的语法糖,包括泛型及其类型擦除、泛型与重载的关系、自动装箱和自动拆箱、遍历循环的实现原理,以及条件编译的使用。泛型在编译后变为原始类型,提供类型安全,但存在类型擦除导致的问题。自动装箱和拆箱简化了基本类型与包装类之间的转换,遍历循环利用迭代器实现。条件编译通过if语句在编译阶段决定代码路径。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Table of Contents

概述

语法糖:泛型与类型擦出

泛型和重载

自动装箱,自动拆箱,与遍历循环

条件编译


概述

在博客java重要基础知识汇总中,我们提到了内部类是一种java的语法糖。事实上java除了内部类还提供了另外一些语法糖,用于方便程序员的代码开发。但是值得注意的是,语法糖对代码不会带来任何实质性的功能上的变化。他仅仅是编译器做的一些'把戏'.语法糖在各种编程语言中几乎都存在。

语法糖:泛型与类型擦出

泛型是java5开始支持的一项特性。其本质是参数化类型的应用,也就是说要被用于操作的数据类型可以被指定为一个参数。再次重复,将数据类型作为一个参数即为泛型。这样带来的作用就是对于一些代码我们在定义时无需指定类型,在实际调用时才指定类行即可,带来了更大的灵活性。

在java5之前事实上java语言当中存在一种体现方式强制转换的类泛型,以Hashmap的get方法为例:

   public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }

在java8中的get方法定义如上,然而在泛型没有出现之前,这个方法的返回值就是一个Object对象,因为java语言里面的所有类型都是Object的子类。但是在这里返回类型定义的是V,这就是定义fan

java中的泛型只存在于程序源码当中,在编译后的字节码当中就已经不存在。而替换为原来类型Raw Type了,并且在相应的地方插入强制类型转换代码。这也为什么我们通过反编译看到的代码和我们自己写的代码往往在泛型定义上是不一样的原因。因此,对于处于runtime的JAVA语言来说,ArrayList<int>与ArrayList<String>就是同一个类型。java语言中的泛型实现方法称为类型擦除-type-erasure,基于这种方法实现的泛型称为伪泛型。

下面是一个例子程序:

import java.util.HashMap;
import java.util.Map;

public class TestGeneric {
    public static void main(String[] args){
        Map<String,String> map = new HashMap<String, String>();
        map.put("hello","你好");
        map.put("hi","初次见面");
        System.out.println(map.get("hello"));
        System.out.println(map.get("hi"));
    }
}

上述代码是我们写的java代码, 将这段代码编译成Class文件,然后再用字节码反编译工具进行反编译后,得到的代码如下:

import java.util.HashMap;
import java.util.Map;

public class TestGeneric {
    public static void main(String[] args){
        Map map = new HashMap();
        map.put("hello","你好");
        map.put("hi","初次见面");
        System.out.println((String)map.get("hello"));
        System.out.println((String)map.get("hi"));
    }
}

我们发现,所有的泛型都不见了,程序变成了java引入泛型之前的写法,泛型的类型都变为了原生类型。注意,单独看上面的map<String, String>并不是一个泛型的定义,而是一个使用泛型定义的地方。而这个泛型定义的地方在HashMap的源代码当中:

 * <p>This class is a member of the
 * <a href="{@docRoot}/../technotes/guides/collections/index.html">
 * Java Collections Framework</a>.
 *
 * @param <K> the type of keys maintained by this map
 * @param <V> the type of mapped values
 *
 * @author  Doug Lea
 * @author  Josh Bloch
 * @author  Arthur van Hoff
 * @author  Neal Gafter
 * @see     Object#hashCode()
 * @see     Collection
 * @see     Map
 * @see     TreeMap
 * @see     Hashtable
 * @since   1.2
 */
public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {

    private static final long serialVersionUID = 362498820763181265L;

    /*
     * Implementation notes.
     *
     * This map usually acts as a binned (bucketed) hash table, but

 另一个例子是List。

我们定义一个List是按照如下方式定义:

List<String> list = new ArrayList<String>();

这里的List<String>的定义就是泛型的使用,其泛型定义为:

 * @see AbstractList
 * @see AbstractSequentialList
 * @since 1.2
 */

public interface List<E> extends Collection<E> {
    // Query Operations

泛型的一大优点是在编译时保证类型安全。

 

泛型和重载

当泛型和重载一起使用时,还可能会存在一些问题。下面代码是一个例子:

public class GenericAndOverloading {
    public static void method(List<String> list){
        System.out.println("String");
    }

    public static void method(List<Integer> list){
        System.out.println("Integer");
    }
}

根据重载的知识我们知道,两个方法名字不一样的方法,若参数不同则是重载方法,可以同时共存?但上述代码是正确的吗?

答案是否定的,这段代码是不能被编译的。原因我们在上面已经提到事实上。List<String>和List<Integer>在编译时会进行类型擦除,它们都会变成原生类型List<E>,这意味着这两个方法的参数是一样的了。因此他们不能被重载。

自动装箱,自动拆箱,与遍历循环

从纯技术的角度来讲,自动装箱和自动拆箱,以及遍历循环的实现难度,比泛型的实现难度是低非常多的。但是这几个语法糖却是java里使用最多的语法糖。请看下面代码;


import java.util.Arrays;
import java.util.List;

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

上述代码,在编译反编译之后得到的代码如下:

    public static void main(String[] args){
        List list = Arrays.asList(new Integer[]{
                Integer.valueOf(1),
                Integer.valueOf(2),
                Integer.valueOf(3),
                Integer.valueOf(4)});

        int sum = 0;
        for(Iterator locaIterator = list.iterator();locaIterator.hasNext();){
            int i = ((Integer)locaIterator.next()).intValue();
            sum += i;
        }
        System.out.println(sum);
    }

上述代码清单中包含了一共五种语法糖:

  • 泛型:List<Integer> list
  • 自动装箱:  Integer.valueOf(1),
  • 自动拆箱: int i = ((Integer)locaIterator.next()).intValue();
  • 遍历循环: for(int i:list) 
  • 变长参数:asList(1,2,3,4)

其中遍历循环被还原成了迭代器的实现,这也是为何遍历循环需要被遍历的类实现Iterable接口的原因。

边长参数在调用的时候变成了一个数组类型的参数,在变长参数出现之前,程序员就是使用数组来完成类似功能的。

条件编译

java语言实现条件编译的方法是通过使用if语句。并且java的条件编译语句在编译阶段就会被执行,且生成的代码当中,只包含满足条件的代码。如下:

public class CaseCompile {
    public static void main(String[] args){
        if(true){
            System.out.println("block 1");
        } else{
            System.out.println("block 2");
        }
    }
}

该代码在编译之后只会包含一条语句,反编译得到的代码如下:

public class CaseCompile {
    public static void main(String[] args){
            System.out.println("block 1");     
}

注意只有使用if才能达到上述效果,其他关键字如while则不会生效。条件编译实现原理不难理解,根据布尔值,编译器可以将代码分支中不成立的代码块消掉。

自此,我们已经在上面的内容当中提到了泛型,自动装箱,自动拆卸,遍历循环,变长参数,和条件编译几种语法糖。除此之外,java还有内部类,枚举类,断言语句,try语句中定义和关闭资源等语法糖。

<think>我们正在讨论Java中的语法糖概念。根据引用[1],语法糖是开发中方便程序员使用的语法,但底层需要依靠一些技术或普通语法实现。编译器在编译过程中会进行“解糖”操作,将语法糖还原为普通语法。引用[3]也提到,语言处理器(如编译器)会在处理前将语法糖结构扩展为更基本的结构,这个过程称为“去糖化”。 因此,我们可以这样定义Java中的语法糖: **语法糖**:一种语法层面的便捷写法,它并不引入新的语言功能,而是通过编译器在编译阶段将其转换为底层JVM能够识别的标准语法结构。这种转换对程序员透明,使得代码更简洁易读。 现在,我们来看Java中常见的语法糖例子(注意:每个例子都需要说明其解糖后的形式): 1. **自动装箱与拆箱(Auto-boxing/Unboxing)** - **定义**:基本类型和其对应的包装类之间的自动转换。 - **例子**: ```java Integer a = 10; // 自动装箱:实际为 Integer a = Integer.valueOf(10); int b = a; // 自动拆箱:实际为 int b = a.intValue(); ``` 2. **增强for循环(For-each Loop)** - **定义**:简化集合和数组的遍历。 - **例子**: ```java for (String s : list) { ... } ``` - 解糖后(对集合): ```java for (Iterator<String> it = list.iterator(); it.hasNext(); ) { String s = it.next(); ... } ``` - 解糖后(对数组): ```java for (int i=0; i<array.length; i++) { String s = array[i]; ... } ``` 3. **变长参数(Varargs)** - **定义**:允许方法接受不定数量的参数。 - **例子**: ```java void print(String... strs) { ... } ``` - 解糖后:编译器将其转换为数组参数: ```java void print(String[] strs) { ... } ``` 4. **泛型(Generics)** - **定义**:在编译时提供类型检查,避免强制类型转换。 - **例子**: ```java List<String> list = new ArrayList<>(); String s = list.get(0); ``` - 解糖后(类型擦除): ```java List list = new ArrayList(); // 类型信息被擦除 String s = (String) list.get(0); // 插入强制转换 ``` 5. **字符串拼接(String Concatenation)** - **定义**:使用`+`拼接字符串。 - **例子**: ```java String s = "a" + "b" + "c"; ``` - 解糖后:编译器使用`StringBuilder`优化: ```java String s = new StringBuilder().append("a").append("b").append("c").toString(); ``` 6. **Lambda表达式(Java 8+)** - **定义**:简化函数式接口的实现。 - **例子**: ```java Runnable r = () -> System.out.println("Hello"); ``` - 解糖后:编译器生成一个实现接口的类(具体实现可能使用invokedynamic指令,但概念上类似): ```java Runnable r = new Runnable() { @Override public void run() { System.out.println("Hello"); } }; ``` 7. **try-with-resources(Java 7+)** - **定义**:自动关闭资源,无需显式调用`close()`。 - **例子**: ```java try (FileInputStream fis = new FileInputStream("file.txt")) { // ... } ``` - 解糖后:编译器生成`finally`块关闭资源,并处理异常。 8. **switch支持字符串和枚举(Java 7+)** - **定义**:`switch`语句支持字符串和枚举类型。 - **例子**: ```java switch (str) { case "A": ... break; case "B": ... break; } ``` - 解糖后:编译器将字符串转换为哈希值进行比较,并使用`equals()`确保准确性。 9. **记录类(Records,Java 14+)** - **定义**:用于创建不可变数据类。 - **例子**: ```java record Point(int x, int y) { } ``` - 解糖后:编译器自动生成构造方法、`equals()`、`hashCode()`、`toString()`等。 10. **文本块(Text Blocks,Java 15+)** - **定义**:多行字符串字面量。 - **例子**: ```java String json = """ { "name": "John", "age": 30 } """; ``` - 解糖后:编译器将其转换为普通字符串,处理换行和缩进。 **总结**:语法糖Java代码更简洁、易读,但程序员需要了解其背后的实现机制,以避免性能陷阱(如循环中的自动装箱)或理解其限制(如泛型类型擦除)[^1][^2][^3]。 §§相关问题§§ 1. 为什么说泛型是Java中的语法糖? 2. 自动装箱和拆箱在性能上有什么影响? 3. 如何查看Java代码解语法糖后的形式?</think>### Java 语法糖详解 #### 定义 **语法糖**(Syntactic Sugar)是一种编程语言的语法特性,它通过更简洁、易读的写法替代底层复杂的实现,但**不引入新功能**。编译器在编译阶段会将其"解糖"(desugar)为等效的基础语法结构[^1][^3]。本质是开发便捷性与底层实现之间的桥梁。 #### 核心特征 1. **语法层优化**:提供更简洁的代码书写方式 2. **编译时转换**:在生成字节码前被转换为标准语法 3. **零功能新增**:不改变语言核心能力,仅改变表达形式 4. **开发者友好**:减少样板代码,提升可读性 --- ### 常见语法糖示例 #### 1. 自动装箱/拆箱(Autoboxing/Unboxing) ```java Integer a = 42; // 装箱:实际调用 Integer.valueOf(42) int b = a; // 拆箱:实际调用 a.intValue() ``` 编译器自动在基本类型和包装类间转换[^1]。 #### 2. 增强 for 循环(For-each) ```java for (String s : list) { ... } ``` 解糖为迭代器模式: ```java for (Iterator<String> it = list.iterator(); it.hasNext(); ) { String s = it.next(); ... } ``` #### 3. 变长参数(Varargs) ```java void print(String... strs) { ... } ``` 解糖为数组: ```java void print(String[] strs) { ... } ``` #### 4. 泛型(Generics) ```java List<String> list = new ArrayList<>(); String s = list.get(0); ``` 类型擦除后: ```java List list = new ArrayList(); // 类型信息丢失 String s = (String) list.get(0); // 强制转换 ``` #### 5. Lambda 表达式 ```java list.forEach(s -> System.out.println(s)); ``` 解糖为匿名内部类: ```java list.forEach(new Consumer<>() { public void accept(String s) { System.out.println(s); } }); ``` #### 6. 字符串拼接 ```java String s = "A" + 1 + "B"; ``` 解糖为 `StringBuilder` 操作: ```java String s = new StringBuilder().append("A").append(1).append("B").toString(); ``` #### 7. try-with-resources ```java try (FileInputStream fis = new FileInputStream("file")) { ... } ``` 编译器自动添加资源关闭逻辑: ```java try { FileInputStream fis = ...; try { ... } finally { fis.close(); } // 自动生成 } catch (...) { ... } ``` #### 8. 记录类(Records) ```java record Point(int x, int y) {} ``` 解糖为包含构造器/访问器/`equals()`/`hashCode()`的 `final class`。 --- ### 语法糖的价值与注意事项 | **优势** | **风险** | |----------|----------| | 提升代码可读性 | 可能隐藏性能开销(如循环内自动装箱) | | 减少样板代码 | 调试时需理解底层转换 | | 降低编码错误率 | 类型擦除可能导致运行时异常[^3] | > 💡 **最佳实践**:使用 `javap -c` 反编译查看解糖后的字节码,深入理解转换逻辑[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值