语法糖(Syntactic Sugar),也称糖衣语法,指在计算机语言中添加的某种语法,这种语法对语言本身功能来说没有什么影响,只是为了方便程序员的开发,提高开发效率。说白了,语法糖就是对现有语法的一个封装。
语法糖在我们Android开发中经常遇到的我暂且归纳为以下几类:
1、 泛型
泛型我们平时会经常遇到,在写一些框架的时候是必不可少的元素。但是其一个实现机制很容易被我们忽视.
类型擦除
泛型这种语法糖,编译器会在编译期间「擦除」泛型语法并相应的做出一些类型转换动作。对于java虚拟机来说,他根本不认识Map<String, String> map这样的语法。需要在编译阶段通过类型擦除的方式进行解语法糖。
类型擦除的主要过程如下:
a、将所有的泛型参数用其最左边界(最顶级的父类型)类型替换。
b、移除所有的类型参数。
实例:
原代码
Map<String, String> map = new HashMap<String, String>();
map.put("name", "zhufk");
map.put("age", "28");
解语法糖之后
Map map = new HashMap();
map.put("name", "zhufk");
map.put("age", "28");
原代码
public static <A extends Comparable<A>> A max(Collection<A> xs) {
Iterator<A> xi = xs.iterator();
A w = xi.next();
while (xi.hasNext()) {
A x = xi.next();
if (w.compareTo(x) < 0)
w = x;
}
return w;
}
解语法糖之后
public static Comparable max(Collection xs){
Iterator xi = xs.iterator();
Comparable w = (Comparable)xi.next();
while(xi.hasNext())
{
Comparable x = (Comparable)xi.next();
if(w.compareTo(x) < 0)
w = x;
}
return w;
}
总结:虚拟机中没有泛型,只有普通类和普通方法,所有泛型类的类型参数在编译时都会被擦除,泛型类并没有自己独有的Class类对象。比如并不存在List<String>.class或是List<Integer>.class,而只有List.class。
2、枚举(switch 支持 String 与枚举)
java中的swith自身原本就支持基本类型。比如int、char等。对于int类型,直接进行数值的比较。对于char类型则是比较其ascii码。所以,对于编译器来说,switch中其实只能使用整型,任何类型的比较都要转换成整型。比如byte。short,char(ackii码是整型)以及int。
那么接下来看下switch对String得支持,有以下代码:
原代码
public class switchDemoString {
public static void main(String[] args) {
String str = "world";
switch (str) {
case "hello":
System.out.println("hello");
break;
case "world":
System.out.println("world");
break;
default:
break;
}
}
}
解语法糖之后的代码
public class switchDemoString{
public switchDemoString(){
}
public static void main(String args[]){
String str = "world";
String s;
switch((s = str).hashCode()){
default:
break;
case 99162322:
if(s.equals("hello"))
System.out.println("hello");
break;
case 113318802:
if(s.equals("world"))
System.out.println("world");
break;
}
}
}
看到这个代码,你知道原来字符串的switch是通过equals()和hashCode()方法来实现的。还好hashCode()方法返回的是int,而不是long。
顺道我们来解释下枚举的基本知识
声明枚举时必须使用 enum 关键字,然后定义枚举的名称、可访问性、基础类型和成员等。
a、最基本得创建方式
public enum ColorEnum {
RED,BLUE,GREEN
}
我们看着很熟悉,但是它内部实现的方式有注意过吗:
public final class ColorEnum extends Enum
{
//返回存储枚举实例的数组的副本。values()方法通常用于foreach循环遍历枚举常量。
public static ColorEnum[] values()
{
return (ColorEnum[])$VALUES.clone();
}
//根据实例名获取实例
public static ColorEnum valueOf(String s)
{
return (ColorEnum)Enum.valueOf(ColorEnum, s);
}
//私有构造方法,这里调用了父类的构造方法,其中参数s对应了常量名,参数i代表枚举的一个顺序(这个顺序与枚举的声明顺序对应,用于oridinal()方法返回顺序值)
private ColorEnum(String s, int i)
{
super(s, i);
}
//我们定义的枚举在这里声明了三个 ColorEnum的常量对象引用,对象的实例化在static静态块中
public static final ColorEnum RED;
public static final ColorEnum BLUE;
public static final ColorEnum GREEN;
//将所有枚举的实例存放在数组中
private static final ColorEnum $VALUES[];
static
{
RED = new ColorEnum("RED", 0);
BLUE = new ColorEnum("BLUE", 1);
GREEN = new ColorEnum("GREEN", 2);
//将所有枚举的实例存放在数组中
$VALUES = (new ColorEnum[] {
RED, BLUE, GREEN
});
}
}
b、增加自己的字段以及一些辅助方法
public enum ColorEnum {
RED("red","红色"),GREEN("green","绿色"),BLUE("blue","蓝色");
//防止字段值被修改,增加的字段也统一final表示常量
private final String key;
private final String value;
private ColorEnum(String key,String value){
this.key = key;
this.value = value;
}
//根据key获取枚举
public static ColorEnum getEnumByKey(String key){
if(null == key){
return null;
}
for(ColorEnum temp:ColorEnum.values()){
if(temp.getKey().equals(key)){
return temp;
}
}
return null;
}
public String getKey() {
return key;
}
public String getValue() {
return value;
}
}
内部实现同基本方式雷同我们就不一一介绍啦。
3、方法变长参数
我们在声明方法是经常会这么写:
原代码
public void printInfo(){
print(“zhufk”,“求关注”)
}
public void print(String... strs){
for (int i = 0; i < strs.length; i++){
System.out.println(strs[i]);
}
}
解语法糖之后
public void printInfo(){
{
print(new String[] {
"Holis", "\u516C\u4F17\u53F7"});
}
public transient void print(String strs[])
{
for(int i = 0; i < strs.length; i++)
System.out.println(strs[i]);
}
总结:可变参数在被使用的时候,他首先会创建一个数组,数组的长度就是调用该方法是传递的实参的个数,然后再把参数值全部放到这个数组当中,然后再把这个数组作为参数传递到被调用的方法中。
4、 for-each
增强for循环(for-each)相信大家都不陌生,日常开发经常会用到的,他会比for循环要少写很多代码,那么这个语法糖背后是如何实现的呢?
原代码
public static void main(String... args) {
String[] strs = {"zhufk", "QQ:308585736",“求关注”};
for (String s : strs) {
System.out.println(s);
}
List<String> strList = ImmutableList.of("zhufk", "QQ:308585736",“求关注”);
for (String s : strList) {
System.out.println(s);
}
}
解语法糖之后
public static transient void main(String args[])
{
String strs[] = {
"zhufk", "\u516C\u4F17308585736", "\u535A\u5BA2\uFF1A"
};
String args1[] = strs;
int i = args1.length;
for(int j = 0; j < i; j++)
{
String s = args1[j];
System.out.println(s);
}
List strList =ImmutableList.of("zhufk","\u516C\u4F17308585736","\u535A\u5BA2\uFF1A");
String s;
for(Iterator iterator = strList.iterator(); iterator.hasNext(); System.out.println(s))
s = (String)iterator.next();
}
for-each的实现原理其实就是使用了普通的for循环和迭代器。
5、Lambda表达式
我们在显示开发中如果用到过RxJava开发,应该很熟悉这种语法
首先我们先来了解一下Lambda:
什么叫函数式接口?他和普通接口有什么区别?
“函数式接口”是指仅仅只包含一个抽象方法的接口(可以包含默认方法和静态方法),其他特征和普通接口没有任何区别,Java中Runnalbe,Callable等就是个函数式接口;。我们可以给一个符合函数式接口添加@FunctionalInterface注解,这样就显式的指明该接口是一个函数式接口,如果不是,编译器会直接提示错误。当然你也可以不用添加此注解。添加的好处在于,由于Lambda表达式只支持函数式接口,如果恰好这个接口被应用于Lambda表达式,某天你手抖不小心添加了个抽象方法,编译器会提示错误。
//显式指明该接口是函数式接口
@FunctionalInterface
public interface ActionListener{
void actionPerformed(ActionEvent e);//这是一个抽象方法
default String onDefalutMethod(){//这是一个默认方法
return "这是一个默认方法";
}
static String onStaticMethod(){
return "这是一个静态方法";
}
}
通常的调用方式
button.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
ShowDialog(e.tostring());
}
})
用Lambda表达式
button.addActionListener((ActionEvent e) -> ShowDialog(e.tostring()));
待续。。。。。