Java基础(4)

Java8新特性:Effectively final

Java 中局部内部类和匿名内部类访问的局部变量必须由 final 修饰,以保证内部类和外部类的数据一致性。但从 Java 8 开始,我们可以不加 final 修饰符,由系统默认添加,当然这在 Java 8 以前的版本是不允许的。Java 将这个功能称为 Effectively final 功能。

下面的代码在Java7下编译会出现,但是Java8以上没有问题,因为系统会默认添加 final 修饰符;也就是说从 Java 8 开始,它不要求程序员必须将访问的局部变量显式的声明为 final 的。只要该变量不被重新赋值就可以。

public static void main(String[] args) {
	// TODO Auto-generated method stub
	String name = "zengraoli";
    new Runnable() {
        @Override
        public void run() {
            System.out.println(name);
        }
    };
}

一个非 final 的局部变量或方法参数,其值在初始化后就从未更改,那么该变量就是 effectively final。在 Lambda 表达式中,使用局部变量的时候,也要求该变量必须是 final 的,所以 effectively final 在 Lambda 表达式上下文中非常有用。

Lambda 表达式在编程中是经常使用的,而匿名内部类是很少使用的。那么,我们在 Lambda 编程中每一个被使用到的局部变量都去显示定义成 final 吗?显然这不是一个好方法。所以,Java 8 引入了 effectively final 新概念

Java Lambda表达式

Lambda 表达式是推动 Java 8 发布的重要新特性,它允许把函数作为一个方法的参数(函数作为参数传递进方法中)

看下面的一个示例代码

package com.zengraoli.test;

public class Test13 {

	// 可计算接口
	public interface Calculable {
	    // 计算两个int数值
	    int calculateInt(int a, int b);
	}
	
    /**
     * 通过操作符,进行计算
     *
     * @param opr 操作符
     * @return 实现Calculable接口对象
     */
	public static Calculable calculate(char opr) {
        Calculable result;
        if (opr == '+') {
            // 匿名内部类实现Calculable接口
            result = new Calculable() {
                // 实现加法运算
                @Override
                public int calculateInt(int a, int b) {
                    return a + b;
                }
            };
        } else {
            // 匿名内部类实现Calculable接口
            result = new Calculable() {
                // 实现减法运算
                @Override
                public int calculateInt(int a, int b) {
                    return a - b;
                }
            };
        }
        return result;
    }
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Calculable c = Test13.calculate('+');
		System.out.println(c.calculateInt(1, 4));
	}
}

用Lambda表达式来进行精简

package com.zengraoli.test;

public class Test1 {
	
	// 可计算接口
	public interface Calculable {
	    // 计算两个int数值
	    int calculateInt(int a, int b);
	}
	
	 /**
     * 通过操作符,进行计算
     *
     * @param opr 操作符
     * @return 实现Calculable接口对象
     */
	public static Calculable calculateLambda(char opr) {
        Calculable result;
        if (opr == '+') {
            result = (int a, int b) -> {
            	return a + b;
            };
        } else {
            // 匿名内部类实现Calculable接口
        	result = (int a, int b) -> {
            	return a - b;
            };
        }
        return result;
    }

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Calculable c = Test1.calculateLambda('-');
		System.out.println(c.calculateInt(1, 4));
	}
}

Lambda 表达式标准语法形式如下:

(参数列表) -> {
    // Lambda表达式体
}

->被称为箭头操作符或 Lambda 操作符,箭头操作符将 Lambda 表达式拆分成两部分:

  • 左侧:Lambda 表达式的参数列表。
  • 右侧:Lambda 表达式中所需执行的功能,用{ }包起来,即 Lambda 体。

Java Lambda 表达式的优缺点

优点:

  • 代码简洁,开发迅速
  • 方便函数式编程
  • 非常容易进行并行计算
  • Java 引入 Lambda,改善了集合操作(引入 Stream API)

缺点:

  • 代码可读性变差
  • 在非并行计算中,很多计算未必有传统的 for 性能要高
  • 不容易进行调试

函数式接口

Lambda 表达式实现的接口不是普通的接口,而是函数式接口。如果一个接口中,有且只有一个抽象的方法(Object 类中的方法不包括在内),那这个接口就可以被看做是函数式接口。这种接口只能有一个方法。如果接口中声明多个抽象方法,那么 Lambda 表达式会发生编译错误:

为了防止在函数式接口中声明多个抽象方法,Java 8 提供了一个声明函数式接口注解 @FunctionalInterface,示例代码如下。

// 可计算接口
@FunctionalInterface
public interface Calculable {
    // 计算两个int数值
    int calculateInt(int a, int b);
}

@FunctionalInterface 注解与 @Override 注解的作用类似。Java 8 中专门为函数式接口引入了一个新的注解 @FunctionalInterface。该注解可用于一个接口的定义上,一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。需要注意的是,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。

Java Lambda表达式的3种简写方式

省略参数类型

Lambda 表达式可以根据上下文环境推断出参数类型。calculate 方法中 Lambda 表达式能推断出参数 a 和 b 是 int 类型,简化形式如下:

public static Calculable calculate(char opr) {
    Calculable result;
    if (opr == '+') {
        // Lambda表达式实现Calculable接口
        result = (a, b) -> {
            return a + b;
        };
    } else {
        // Lambda表达式实现Calculable接口
        result = (a, b) -> {
            return a - b;
        };
    }
    return result;
}

省略参数小括号

如果 Lambda 表达式中的参数只有一个,可以省略参数小括号。修改 Calculable 接口中的 calculateInt 方法,代码如下

// 可计算接口
@FunctionalInterface
public interface Calculable {
    // 计算一个int数值
    int calculateInt(int a);
}

其中 calculateInt 方法只有一个 int 类型参数,返回值也是 int 类型。调用 calculateInt 方法代码如下:

public class Test2 {
	/**
	* 通过幂计算
	*
	* @param power 幂
	* @return 实现Calculable接口对象
	*/
	public static Calculable calculate(int power) {
	    Calculable result;
	    if (power == 2) {
	        // Lambda表达式实现Calculable接口
	        // 标准形式
	        result = (int a) -> {
	            return a * a;
	        };
	    } else {
	        // Lambda表达式实现Calculable接口
	        // 省略形式
	        result = a -> {
	            return a * a * a;
	        };
	    }
	    return result;
	}
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Calculable c = calculate(2);
		System.out.println(c.calculateInt(3));
		Calculable c2 = calculate(3);
		System.out.println(c2.calculateInt(3));
	}
}

省略return和大括号

如果 Lambda 表达式体中只有一条语句,那么可以省略 return 和大括号,代码如下:

public static Calculable calculate2(int power) {
    Calculable result;
    if (power == 2) {
        // Lambda表达式实现Calculable接口
        // 标准形式
        result = (int a) -> {
            return a * a;
        };
    } else {
        // Lambda表达式实现Calculable接口
        // 省略形式
        result = a -> a * a * a;
    }
    return result;
}

Lambda表达式与匿名内部类的联系和区别

Lambda 表达式与匿名内部类的相同点如下:

  • Lambda 表达式与匿名内部类一样,都可以直接访问 effectively final 的局部变量
  • Lambda 表达式创建的对象与匿名内部类生成的对象一样,都可以直接调用从接口中继承的默认方法

下面程序示范了 Lambda 表达式与匿名内部类的相似之处。

public class Test4 {

	@FunctionalInterface
	interface Displayable {
	    // 定义一个抽象方法和默认方法
	    void display();
	    default int add(int a, int b) {
	        return a + b;
	    }
	}
	
	private int age = 12;
	private static String name = "zengraoli";

	public void test2() {
		Displayable dis = new Displayable() {
			@Override
			public void display() {
			}
		};
		dis.display();
	}
	
	// 带省略形式的lambda形式
	public void test() {
		String url = "http://www.baidu.com/";
		Displayable dis = () -> {
			// 访问的局部变量
            System.out.println("url 局部变量为:" + url);
            // 访问外部类的实例变量和类变量
            System.out.println("外部类的 age 实例变量为:" + age);
            System.out.println("外部类的 name 类变量为:" + name);
		};
		dis.display();
		// 调用dis对象从接口中继承的add()方法
        System.out.println(dis.add(3, 5)); 
	}
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Test4 lambda = new Test4();
        lambda.test();
	}

}

对于 Lambda 表达式的代码块不允许调用接口中定义的默认方法的限制,可以尝试对上面的 LambdaAndInner.java 程序稍做修改,在 Lambda 表达式的代码块中增加如下一行

// 尝试调用接口中的默认方法,编译器会报错
System.out.println(add(3, 5));

虽然 Lambda 表达式的目标类型 Displayable 中包含了 add() 方法,但 Lambda 表达式的代码块不允许调用这个方法;如果将上面的 Lambda 表达式改为匿名内部类的写法,当匿名内部类实现 display() 抽象方法时,则完全可以调用这个 add() 方法,如下面代码所示

public void test2() {
	Displayable dis = new Displayable() {
		@Override
		public void display() {
			System.out.println(add(4, 5));
		}
	};
	dis.display();
}

Java Lambda表达式的使用

作为参数使用Lambda表达式

Lambda 表达式一种常见的用途就是作为参数传递给方法,这需要声明参数的类型声明为函数式接口类型。示例代码如下:

public class Test5 {
	@FunctionalInterface
	public interface Calculable {
	    // 计算一个int数值
	    int calculateInt(int a, int b);
	}
	
	public static void display(Calculable calc, int n1, int n2) {
	    System.out.println(calc.calculateInt(n1, n2));
	}
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int n1 = 10;
	    int n2 = 5;
	    
	    // 打印加法计算结果
	    display((a, b) -> {
	        return a + b;
	    }, n1, n2);
	    
	    // 打印减法计算结果
	    display((a, b) -> a - b, n1, n2);
	}
}

访问变量

Lambda 表达式可以访问所在外层作用域定义的变量,包括成员变量和局部变量

访问成员变量

成员变量包括实例成员变量和静态成员变量。在 Lambda 表达式中可以访问这些成员变量,此时的 Lambda 表达式与普通方法一样,可以读取成员变量,也可以修改成员变量

public class LambdaDemo {
    // 实例成员变量
    private int value = 10;
    // 静态成员变量
    private static int staticValue = 5;
    // 静态方法,进行加法运算
    public static Calculable add() {
        Calculable result = (int a, int b) -> {
            // 访问静态成员变量,不能访问实例成员变量
            staticValue++;
            int c = a + b + staticValue;
            // this.value;
            return c;
        };
        return result;
    }
    // 实例方法,进行减法运算
    public Calculable sub() {
        Calculable result = (int a, int b) -> {
            // 访问静态成员变量和实例成员变量
            staticValue++;
            this.value++;
            int c = a - b - staticValue - this.value;
            return c;
        };
        return result;
    }
}
访问局部变量

对于成员变量的访问 Lambda 表达式与普通方法没有区别,但是访问局部变量时,变量必须是 final 类型的(不可改变)

public class Test6 {

	@FunctionalInterface
	public interface Calculable {
	    // 计算一个int数值
	    int calculateInt(int a, int b);
	}
	
	public void test() {
		int age = 23;
		Calculable c = (int a, int b) -> {
			System.out.println("age value:" + age);
//			age++; // 不管这个变量是否显式地使用 final 修饰,它都不能在 Lambda 表达式中修改变量
			return a + b;
		};
		System.out.println(c.calculateInt(1, 11));
	}
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		new Test6().test();
	}

}

Lambda 表达式只能访问局部变量而不能修改,否则会发生编译错误,但对静态变量和成员变量可读可写。

方法引用

Java 8 之后增加了双冒号::运算符,该运算符用于“方法引用”,注意不是调用方法。“方法引用”虽然没有直接使用 Lambda 表达式,但也与 Lambda 表达式有关,与函数式接口有关。 方法引用的语法格式如下:

ObjectRef::methodName // 其中,ObjectRef 是类名或者实例名,methodName 是相应的方法名。

注意:被引用方法的参数列表和返回值类型,必须与函数式接口方法参数列表和方法返回值类型一致,示例代码如下。

public class Test7 {

	@FunctionalInterface
	public interface Calculable {
	    // 计算一个int数值
	    int calculateInt(int a, int b);
	}
	
	public static void display(Calculable calc, int n1, int n2) {
	    System.out.println(calc.calculateInt(n1, n2));
	}
	
    public static int add(int a, int b) {
        return a + b;
    }
    // 实例方法,进行减法运算
    // 参数列表要与函数式接口方法calculateInt(int a, int b)兼容
    public int sub(int a, int b) {
        return a - b;
    }
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int n1 = 10;
		int n2 = 5;
		// 打印加法计算结果
        display(Test7::add, n1, n2);
        display(new Test7()::sub, n1, n2);
	}
}

Test7类中提供了一个静态方法 add,一个实例方法 sub。这两个方法必须与函数式接口方法参数列表一致,方法返回值类型也要保持一致。

参考链接

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值