函数式编程--方法和构造器引用

前面已经说过了,如果lambda表达式的代码块只有一行代码,可以省略lambda表达式中代码块的花括号,语句后面的逗号也要省去的。不仅如此,如果lambda表达式的代码块只有一行代码,还可以在代码中使用方法引用和构造器引用。

方法引用和构造器引用可以让lambda表达式编码更加简洁,具体语法就是使用2个英文冒号,然后加上方法名字,注意没有括号的。一共有4种情况,类引用类方法,对象引用实例方法,类引用构造器都很好理解,注意的是这里还有一个类引用实例方法。



  • 1,引用类方法
我们先来看一段代码,然后在分析下这段代码,差不多就能掌握这种语法了。
public class Test
{
	public static void main(String[] args)
	{
		//原始的方法
		//Converter converter = str -> Integer.parseInt(str);
		//使用类方法引用
		Converter converter = Integer::parseInt;
		System.out.println(converter.convert("25"));
	}


}


@FunctionalInterface
interface  
{
	//定义一个将字符串串成integer的方法
	Integer convert(String str);
}

上面的Lambda表达式的代码块(注释掉的代码)只有一行代码,所以程序可以省略该代码块的花括号,而且由于该函数式接口里面的convert()方法需要返回值,所以Lambda表达式会把这条代码的值作为返回值。现在我们用方法引用来代替Lambda表达式,将Converter接口中被实现方法的全部参数传给该类方法作为参数。比如上面我们自己写的这行代码Converter converter = Integer::parseInt;当我们调用该接口中的唯一的抽象方法时,调用参数将会传给Integer类的parseInt方法。

  • 2,引用特定对象的实例方法
先来看下面的代码:
public class Test
{
	public static void main(String[] args)
	{
		//原始的lambda表达式
		//Converter converter = str -> "LinkinPark".indexOf(str);
		//使用类方法引用
		Converter converter = "LinkinPark"::indexOf;
		System.out.println(converter.convert("kin"));
	}


}


@FunctionalInterface
interface Converter
{
	//定义一个将获取字符串下表的方法
	Integer convert(String str);
}
上面我们的注释掉的lambda表达式只有一行调用"LinkinPark"的indexOf()实例方法的代码,所以我们使用方法引用来代替。对于上面的实例方法引用,也就是调用字符串对象"LinkinPark"的indexOf()实例方法时,调用参数将会传给"LinkinPark"对象的indexOf()的实例方法。


  • 3,引用类的实例方法
public class Test
{
	public static void main(String[] args)
	{
		//原始的lambda表达式
		//		Converter converter = (str, index, end) -> str.substring(index, end);
		//使用方法引用
		Converter converter = String::substring;
		System.out.println(converter.convert("LinkinPark", 0, 6));
	}


}


@FunctionalInterface
interface Converter
{


	/**
	 * @param str 原始的字符串
	 * @param index 开始截图的下标
	 * @param end 终止截图的下标
	 * @return 截取后的字符串
	 * @Description: 定义一个截取字符串的方法
	 */
	String convert(String str, int index, int end);
}
这个语法我们在第一次使用的时候觉得有点别扭,记住就好了。针对上面的代码,当我们调用Converter接口中的唯一的抽象方法时,第一个调用参数将作为substring方法的调用者,剩下的调用参数将会作为substring()实例方法的调用参数。另外一点,通过使用super,可以引用方法的父类版本,语法如下:
super::方法名字

  • 4,构造器引用
public class Test
{
	public static void main(String[] args)
	{
		//原始的lambda表达式
		//		Converter converter = str -> new String(str);
		//使用构造器引用
		Converter converter = String::new;
		System.out.println(converter.convert("LinkinPark"));
	}


}


@FunctionalInterface
interface Converter
{
	//定义一个方法,处理一个字符串然后在返回一个字符串
	String convert(String str);
}

上面的lambda表达式只有一行代码,而且是调用了某一个类的构造器,所以我们可以使用构造器引用来替换。new就代表使用该类的构造器,这里有一个问题,我们直接使用某一个类的new来表示调用这个类的构造器,那么如果说这个类很多个构造器的时候,应该要使用哪一个构造器呢?这里取决于调用函数式接口里面的抽象方法时传入的参数类型,比如上面代码我们调用Converter类的convert方法,实际传入一个String类型的参数,那么这个参数将会传入到String的使用String类型作为参数的构造器中,上面的构造器引用将调用String类的,带一个String参数的构造器。


  • 总结一下:
有一个很重要的特性与lambda表达式相关,叫做方法引用。方法引用提供了一种引用而不执行方法的方式。使用方法引用,需要由兼容的函数式接口构成的目标类型的上下文,计算时,方法引用也会创建函数式接口的一个实例。上面的几个例子我们都是直接使用现成的jdk里面的类,现在我们自己定义几个类和方法来练习下方法引用和构造器引用。看下面代码:

public class Test
{
	//这其实就是命令者模式
	public String test(String str, StringFunc func)
	{
		return func.convert(str);
	}
	
	public String test1(StringFuncImpl stringFuncImpl, String str, StringFunc1 func)
	{
		return func.convert(stringFuncImpl, str);
	}


	public StringFuncImpl test2(String str, StringFunc2 func)
	{
		return func.getStringFuncImpl(str);
	}


	public static void main(String[] args)
	{
		Test test = new Test();
		StringFuncImpl stringFuncImpl = new StringFuncImpl();
		//下面的代码输出LinkinPark...
		System.out.println(test.test("LinkinPark", StringFuncImpl::convert));
		System.out.println(test.test("LinkinPark", stringFuncImpl::convert1));
		System.out.println(test.test1(stringFuncImpl, "LinkinPark", StringFuncImpl::convert1));
		System.out.println(test.test2("LinkinPark", StringFuncImpl::new).getName());
	}


}


@FunctionalInterface
interface StringFunc
{
	//定义一个接口,一个方法来处理字符串
	String convert(String str);
}


@FunctionalInterface
interface StringFunc1
{
	//定义一个接口,一个方法来处理字符串
	String convert(StringFuncImpl stringFuncImpl, String str);
}


@FunctionalInterface
interface StringFunc2
{
	//得到一个StringFuncImpl实例
	StringFuncImpl getStringFuncImpl(String str);
}


class StringFuncImpl
{
	private String name;
	
	public StringFuncImpl()
	{
	}
	
	public StringFuncImpl(String name)
	{
		this.name = name;
	}
	
	public String getName()
	{
		return name;
	}
	
	//注意,这里要和上面的函数式接口兼容
	static String convert(String str)
	{
		return str + "...";
	}


	String convert1(String str)
	{
		return str + "...";
	}


}



  • 泛型中的方法引用
在泛型类和泛型方法中,也可以使用方法引用。先来看一段代码:
public class Test
{


	public static <T> int test(T[] vals, T value, LinkinFunc<T> func)
	{
		return func.func(vals, value);
	}


	public static void main(String[] args)
	{
		Integer[] vals = { 1, 2, 3 };
		System.out.println(test(vals, 1, LinkinImpl::<Integer> countMatch));
		String[] valss = {"1","2","3"};
		System.out.println(test(valss, "1", LinkinImpl::<String> countMatch));
	}


}


@FunctionalInterface
interface LinkinFunc<T>
{
	//一个处理泛型数组的算法
	int func(T[] vals, T value);
}


class LinkinImpl
{
	//上面函数式接口的具体实现
	static <T> int countMatch(T[] vals, T value)
	{
		int count = 0;
		for (T t : vals)
		{
			if (t == value)
			{
				count++;
			}
		}
		return count;


	}
}
阅读上面的代码没什么难度的,参数的传递发送在::的后面。当把泛型方法指定为方法引用时,参数类型出现在::之后,方法名称之前。需要指出的是在一般情况下,并非必须显示指定类型参数,类型参数会被自动推断得出。

上面写的一些例子只是显示了使用方法引用的机制,并没有展现他的真正优势。方法引用能够一展拳脚的一处地方是在与集合一起时候的时候,比如说现在我们要使用Collections类定义的max()方法来确定集合中最大的元素,以前我们传入一个集合,还要传入一个实现了Comparator<T>接口的对象的实例,匿名内部类,比较麻烦的。现在我们只需要自己实现一个与Comparator接口中定义的compare()方法兼容的方法就可以。比如下面代码:
static int linkinCompare(MyClass a,MyClass b)
{
retutn a.getId()-b.getId();
}
在调用时候直接用类名::linkinCompare就可以了。

原文地址:https://www.cnblogs.com/LinkinPark/p/5232973.html