概述
在博客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与ArrayList就是同一个类型。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的定义就是泛型的使用,其泛型定义为:
* @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和List在编译时会进行类型擦除,它们都会变成原生类型List,这意味着这两个方法的参数是一样的了。因此他们不能被重载。
自动装箱,自动拆箱,与遍历循环
从纯技术的角度来讲,自动装箱和自动拆箱,以及遍历循环的实现难度,比泛型的实现难度是低非常多的。但是这几个语法糖却是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 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语句中定义和关闭资源等语法糖。
转自:https://blog.csdn.net/topdeveloperr/article/details/88713443