匿名类的一个问题是,若是匿名类的实现很是简单,例如只包含一个方法的接口,那么匿名类的语法可能看起来不实用且不清楚,在这些状况下,你一般会尝试将功能做为参数传递给另外一个方法,例如当有人单击按钮时应采起的操做,Lambda表达式使你能够执行此操做,将功能视为方法参数,或将代码视为数据。html
上一节匿名类向你展现了如何在不给它命名的状况下实现基类,虽然这一般比命名类更简洁,但对于只有一个方法的类,即便是匿名类也彷佛有点过多和繁琐,Lambda表达式容许你更紧凑地表达单方法类的实例。java
假设你正在建立社交网络应用程序,你但愿建立一项功能,使管理员可以对知足特定条件的社交网络应用程序成员执行任何类型的操做,例如发送消息,下表详细描述了此用例:git
字段 | 描述 |
---|---|
名称 | 对选定的成员执行操做 |
主要角色 | 管理员 |
前提条件 | 管理员已登陆系统 |
后置条件 | 仅对符合指定条件的成员执行操做 |
主要成功案例 | 1. 管理员指定要执行特定操做的成员的条件 2. 管理员指定要对这些选定成员执行的操做 3. 管理员选择Submit按钮 4. 系统查找符合指定条件的全部成员 5. 系统对全部匹配成员执行指定的操做 |
扩展 | 管理员能够选择在指定要执行的操做以前或选择Submit按钮以前预览符合指定条件的成员 |
发生频率 | 一天中不少次 |
假设此社交网络应用程序的成员由如下Person类表示:github
public class Person { public enum Sex { MALE, FEMALE } String name; LocalDate birthday; Sex gender; String emailAddress; public int getAge() { // ... } public void printPerson() { // ... } }
假设你的社交网络应用程序的成员存储在List<Person>
实例中。算法
本节首先介绍这种用例的简单方法,它使用局部和匿名类改进了这种方法,而后使用lambda表达式以高效和简洁的方法完成,在示例RosterTest中找到本节中描述的代码摘录。express
一种简单的方法是建立几种方法,每种方法都会搜索与一个特征匹配的成员,例如性别或年龄,如下方法打印超过指定年龄的成员:segmentfault
public static void printPersonsOlderThan(List<Person> roster, int age) { for (Person p : roster) { if (p.getAge() >= age) { p.printPerson(); } } }
注意:List是有序集合,集合是将多个元素组合到一个单元中的对象,集合用于存储、检索、操做和传递聚合数据,有关集合的更多信息,请参阅集合路径。api
这种方法可能会使你的应用程序变得脆弱,这是因为引入了更新(例如更新的数据类型)致使应用程序没法工做的可能性。假设你升级应用程序并更改Person
类的结构,使其包含不一样的成员变量,也许类使用不一样的数据类型或算法记录和测量年龄,你必须重写大量API以适应此更改,此外,这种方法是没必要要的限制;例如,若是你想要打印年龄小于某个年龄的成员,该怎么办?数组
如下方法比printPersonsOlderThan
更通用;它会在指定的年龄范围内打印成员:网络
public static void printPersonsWithinAgeRange( List<Person> roster, int low, int high) { for (Person p : roster) { if (low <= p.getAge() && p.getAge() < high) { p.printPerson(); } } }
若是你想要打印指定性别的成员,或指定性别和年龄范围的组合,该怎么办?若是你决定更改Person
类并添加其余属性(如关系状态或地理位置),该怎么办?虽然此方法比printPersonsOlderThan
更通用,但尝试为每一个可能的搜索查询建立单独的方法仍然会致使代码脆弱,你能够改成分离指定要在其余类中搜索的条件的代码。
如下方法打印与你指定的搜索条件匹配的成员:
public static void printPersons( List<Person> roster, CheckPerson tester) { for (Person p : roster) { if (tester.test(p)) { p.printPerson(); } } }
此方法经过调用方法tester.test
来检查List
参数名单中包含的每一个Person
实例是否知足CheckPerson
参数tester
中指定的搜索条件,若是方法tester.test
返回true
值,则在Person
实例上调用方法printPersons
。
要指定搜索条件,请实现CheckPerson
接口:
interface CheckPerson { boolean test(Person p); }
如下类经过指定方法test
的实现来实现CheckPerson
接口,此方法可过滤符合美国选择性服务条件的成员:若是Person
参数为男性且年龄介于18和25之间,则返回true
值:
class CheckPersonEligibleForSelectiveService implements CheckPerson { public boolean test(Person p) { return p.gender == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25; } }
要使用此类,你须要建立它的新实例并调用printPersons
方法:
printPersons( roster, new CheckPersonEligibleForSelectiveService());
虽然这种方法不那么脆弱 — 若是更改Person
的结构,则没必要重写方法 — 你仍然须要额外的代码:你计划在应用程序中执行的每一个搜索的新接口和局部类,由于CheckPersonEligibleForSelectiveService
实现了一个接口,因此你可使用匿名类而不是局部类,而且无需为每次搜索声明一个新类。
下面调用方法printPersons
的一个参数是一个匿名类,它可过滤符合美国选择性服务条件的成员:那些男性、年龄在18到25岁之间的人:
printPersons( roster, new CheckPerson() { public boolean test(Person p) { return p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25; } } );
此方法减小了所需的代码量,由于你没必要为要执行的每一个搜索建立新类,可是,考虑到CheckPerson
接口只包含一个方法,匿名类的语法很笨重,在这种状况下,你可使用lambda表达式而不是匿名类,以下一节中所述。
CheckPerson接口是一个功能性接口,功能性接口是仅包含一个抽象方法的任何接口(功能性接口可能包含一个或多个默认方法或静态方法),因为功能性接口仅包含一个抽象方法,所以在实现该方法时能够省略该方法的名称。为此,不使用匿名类表达式,而是使用lambda表达式,该表达式在如下方法调用中显示:
printPersons( roster, (Person p) -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25 );
有关如何定义lambda表达式的信息,请参见Lambda表达式的语法。
你可使用标准功能性接口代替CheckPerson
接口,从而进一步减小所需的代码量。
从新考虑CheckPerson
接口:
interface CheckPerson { boolean test(Person p); }
这是一个很是简单的接口,它是一个功能性接口,由于它只包含一个抽象方法,此方法接受一个参数并返回一个布尔值,该方法很是简单,在你的应用程序中定义一个方法可能不值得,所以,JDK定义了几个标准的功能性接口,你能够在java.util.function
包中找到它们。
例如,你可使用Predicate<T>
接口代替CheckPerson
,该接口包含方法boolean test(T t)
:
interface Predicate<T> { boolean test(T t); }
接口Predicate<T>
是泛型接口的示例(有关泛型的更多信息,请参阅泛型(更新)课程),泛型类型(例如泛型接口)在尖括号(<>
)中指定一个或多个类型参数,该接口仅包含一个类型参数T
。当你使用实际类型参数声明或实例化泛型类型时,你具备参数化类型,例如,参数化类型Predicate<Person>
以下:
interface Predicate<Person> { boolean test(Person t); }
此参数化类型包含一个方法,该方法具备与CheckPerson.boolean test(Person p)
相同的返回类型和参数,所以,你可使用Predicate<T>
代替CheckPerson
,以下面的方法所示:
public static void printPersonsWithPredicate( List<Person> roster, Predicate<Person> tester) { for (Person p : roster) { if (tester.test(p)) { p.printPerson(); } } }
所以,如下方法调用与在方法3:在局部类中指定搜索条件代码以获取有资格得到选择性服务的成员中调用printperson
时相同:
printPersonsWithPredicate( roster, p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25 );
这不是此方法中使用lambda表达式的惟一可能位置,如下方法提出了使用lambda表达式的其余方法。
从新考虑printPersonsWithPredicate
方法以查看可使用lambda表达式的其余位置:
public static void printPersonsWithPredicate( List<Person> roster, Predicate<Person> tester) { for (Person p : roster) { if (tester.test(p)) { p.printPerson(); } } }
此方法检查List
参数roster
中包含的每一个Person
实例是否知足Predicate
参数tester
中指定的条件,若是Person
实例知足tester
指定的条件,则在Person
实例上调用printPersron
方法。
你能够指定一个不一样的操做来执行那些知足tester
指定的条件的Person
实例,而不是调用printPerson
方法,你可使用lambda表达式指定此操做。假设你想要一个相似于printPerson
的lambda表达式,它接受一个参数(Person
类型的对象)并返回void
,请记住,要使用lambda表达式,你须要实现一个功能性接口。在这种状况下,你须要一个包含抽象方法的功能性接口,该方法能够接受一个Person
类型的参数并返回void
。Consumer<T>
接口包含void accept(T t)
方法,它具备这些特性,如下方法将调用p.printPerson()
替换为调用方法accept
的Consumer<Person>
实例:
public static void processPersons( List<Person> roster, Predicate<Person> tester, Consumer<Person> block) { for (Person p : roster) { if (tester.test(p)) { block.accept(p); } } }
所以,如下方法调用与在方法3:在局部类中指定搜索条件代码以获取有资格得到选择性服务的成员中调用printPersons
时相同,用于打印成员的lambda表达式以下:
processPersons( roster, p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25, p -> p.printPerson() );
若是你想对成员的我的资料进行更多操做而不是打印出来,该怎么办?假设你要验证成员的我的资料或检索他们的联系信息?在这种状况下,你须要一个包含返回值的抽象方法的功能性接口,Function<T,R>
接口包含方法R apply(T t)
,如下方法检索参数mapper
指定的数据,而后对参数block
指定的操做执行操做:
public static void processPersonsWithFunction( List<Person> roster, Predicate<Person> tester, Function<Person, String> mapper, Consumer<String> block) { for (Person p : roster) { if (tester.test(p)) { String data = mapper.apply(p); block.accept(data); } } }
如下方法从有资格得到选择性服务的roster
中包含的每一个成员检索电子邮件地址,而后将其打印出来:
processPersonsWithFunction( roster, p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25, p -> p.getEmailAddress(), email -> System.out.println(email) );
从新考虑方法processPersonsWithFunction
,如下是它的泛型版本,它接受包含任何数据类型元素的集合做为参数:
public static <X, Y> void processElements( Iterable<X> source, Predicate<X> tester, Function <X, Y> mapper, Consumer<Y> block) { for (X p : source) { if (tester.test(p)) { Y data = mapper.apply(p); block.accept(data); } } }
要打印有资格得到选择性服务的成员的电子邮件地址,请按以下方式调用processElements
方法:
processElements( roster, p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25, p -> p.getEmailAddress(), email -> System.out.println(email) );
此方法调用执行如下操做:
source
获取对象源,在此示例中,它从集合roster
中获取Person
对象的源,请注意,集合roster
是List
类型的集合,也是Iterable
类型的对象。Predicate
对象tester
匹配的对象,在此示例中,Predicate
对象是一个lambda表达式,指定哪些成员有资格得到选择性服务。Function
对象mapper
指定的值,在此示例中,Function
对象是一个lambda表达式,它返回成员的电子邮件地址。Consumer
对象block
指定的每一个映射对象执行操做,在此示例中,Consumer
对象是一个lambda表达式,用于打印字符串,该字符串是Function
对象返回的电子邮件地址。你可使用聚合操做替换每一个操做。
如下示例使用聚合操做来打印有资格得到选择性服务的集合roster
中包含的成员的电子邮件地址:
roster .stream() .filter( p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25) .map(p -> p.getEmailAddress()) .forEach(email -> System.out.println(email));
下表将方法processElements
执行的每一个操做映射到相应的聚合操做:
processElements 行动 |
聚合操做 |
---|---|
获取对象的源 | Stream<E> stream() |
过滤与Predicate 对象匹配的对象 |
Stream<T> filter(Predicate<? super T> predicate) |
将对象映射到Function对象指定的另外一个值 | <R> Stream<R> map(Function<? super T,? extends R> mapper) |
执行Consumer 对象指定的操做 |
void forEach(Consumer<? super T> action) |
操做filter
、map
和forEach
是聚合操做,聚合操做处理流中的元素,而不是直接来自集合(这是本例中调用的第一个方法是stream
的缘由)。流是一系列元素,与集合不一样,它不是存储元素的数据结构,相反,流经过管道携带来自源(例如集合)的值,管道是一系列流操做,在此示例中为filter-map-forEach
,此外,聚合操做一般接受lambda表达式做为参数,使你能够自定义它们的行为方式。
有关聚合操做的更全面讨论,请参阅聚合操做课程。
要处理图形用户界面(GUI)应用程序中的事件,例如键盘操做、鼠标操做和滚动操做,一般会建立事件处理程序,这一般涉及实现特定的接口,一般,事件处理程序接口是功能性接口;他们每每只有一种方法。
在JavaFX示例HelloWorld.java中(在上一节匿名类中讨论过),你能够在此语句中用lambda表达式替换匿名类:
btn.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { System.out.println("Hello World!"); } });
方法调用btn.setOnAction
指定在选择由btn
对象表示的按钮时会发生什么,此方法须要EventHandler<ActionEvent>
类型的对象,EventHandler<ActionEvent>
接口只包含一个方法,void handle(T event)
,此接口是一个功能性接口,所以你可使用如下显示的lambda表达式来替换它:
btn.setOnAction( event -> System.out.println("Hello World!") );
lambda表达式包含如下内容:
括号中用逗号分隔的形式参数列表,CheckPerson.test
方法包含一个参数p
,它表示Person
类的实例。
注意:你能够省略lambda表达式中参数的数据类型,此外,若是只有一个参数,则能够省略括号,例如,如下lambda表达式也是有效的:
p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25
->
代码体,由单个表达式或语句块组成,此示例使用如下表达式:
p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25
若是指定单个表达式,则Java运行时将计算表达式,而后返回其值,或者,你可使用return
语句:
p -> { return p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25; }
return
语句不是表达式,在lambda表达式中,你必须将语句括在大括号({}
)中,可是,你没必要在大括号中包含void
方法调用,例如,如下是有效的lambda表达式:
email -> System.out.println(email)
请注意,lambda表达式看起来很像方法声明,你能够将lambda表达式视为匿名方法 — 没有名称的方法。
如下示例Calculator是lambda表达式的示例,它采用多个形式参数:
public class Calculator { interface IntegerMath { int operation(int a, int b); } public int operateBinary(int a, int b, IntegerMath op) { return op.operation(a, b); } public static void main(String... args) { Calculator myApp = new Calculator(); IntegerMath addition = (a, b) -> a + b; IntegerMath subtraction = (a, b) -> a - b; System.out.println("40 + 2 = " + myApp.operateBinary(40, 2, addition)); System.out.println("20 - 10 = " + myApp.operateBinary(20, 10, subtraction)); } }
方法operateBinary
对两个整数操做数执行数学运算,操做自己由IntegerMath
实例指定,该示例使用lambda表达式,addition
和subtraction
定义了两个操做,该示例打印如下内容:
40 + 2 = 42 20 - 10 = 10
像局部和匿名类同样,lambda表达式能够捕获变量,它们对封闭范围的局部变量具备相同的访问权限,可是,与局部和匿名类不一样,lambda表达式没有任何遮蔽问题(有关更多信息,请参阅遮蔽),Lambda表达式具备词法做用域。这意味着它们不会从超类型继承任何名称或引入新级别的范围,lambda表达式中的声明与封闭环境中的声明同样被解释,如下示例LambdaScopeTest演示了这一点:
import java.util.function.Consumer; public class LambdaScopeTest { public int x = 0; class FirstLevel { public int x = 1; void methodInFirstLevel(int x) { // The following statement causes the compiler to generate // the error "local variables referenced from a lambda expression // must be final or effectively final" in statement A: // // x = 99; Consumer<Integer> myConsumer = (y) -> { System.out.println("x = " + x); // Statement A System.out.println("y = " + y); System.out.println("this.x = " + this.x); System.out.println("LambdaScopeTest.this.x = " + LambdaScopeTest.this.x); }; myConsumer.accept(x); } } public static void main(String... args) { LambdaScopeTest st = new LambdaScopeTest(); LambdaScopeTest.FirstLevel fl = st.new FirstLevel(); fl.methodInFirstLevel(23); } }
此示例生成如下输出:
x = 23 y = 23 this.x = 1 LambdaScopeTest.this.x = 0
若是在lambda表达式myConsumer
的声明中用参数x
代替y
,则编译器会生成错误:
Consumer<Integer> myConsumer = (x) -> { // ... }
编译器生成错误“variable x is already defined in method methodInFirstLevel(int)”,由于lambda表达式不会引入新的做用域级别,所以,你能够直接访问封闭范围的字段、方法和局部变量。例如,lambda表达式直接访问methodInFirstLevel
方法的参数x
,要访问封闭类中的变量,请使用关键字this
,在此示例中,this.x
引用成员变量FirstLevel.x
。
可是,与局部和匿名类同样,lambda表达式只能访问final
或有效final
的封闭块的局部变量和参数,例如,假设你在methodInFirstLevel
定义语句以后当即添加如下赋值语句:
void methodInFirstLevel(int x) { x = 99; // ... }
因为这个赋值语句,变量FirstLevel.x
再也不是final
,所以,Java编译器生成相似于“local variables referenced from a lambda expression must be final or effectively final”的错误消息,其中lambda表达式myConsumer
尝试访问FirstLevel.x
变量:
System.out.println("x = " + x);
你如何肯定lambda表达式的类型?回想一下lambda表达式,它选择了男性和年龄在18到25岁之间的成员:
p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25
这个lambda表达式用于如下两种方法:
public static void printPersons(List<Person> roster, CheckPerson tester)
public void printPersonsWithPredicate(List<Person> roster, Predicate<Person> tester)
当Java运行时调用方法printPersons
时,它指望CheckPerson
的数据类型,所以lambda表达式属于这种类型,可是,当Java运行时调用方法printPersonsWithPredicate
时,它指望数据类型为Predicate<Person>
,所以lambda表达式属于此类型。这些方法所指望的数据类型称为目标类型,要肯定lambda表达式的类型,Java编译器使用发现lambda表达式的上下文或情境的目标类型,所以,你只能在Java编译器能够肯定目标类型的状况下使用lambda表达式:
Return
语句?:
对于方法参数,Java编译器使用另外两种语言功能肯定目标类型:重载决策和类型参数推断。
考虑如下两个功能性接口(java.lang.Runnable和java.util.concurrent.Callable<V>):
public interface Runnable { void run(); } public interface Callable<V> { V call(); }
方法Runnable.run
不返回值,而Callable<V>.call
则返回值。
假设你已按以下方式重载方法invoke
(有关重载方法的详细信息,请参阅定义方法):
void invoke(Runnable r) { r.run(); } <T> T invoke(Callable<T> c) { return c.call(); }
将在如下语句中调用哪一个方法?
String s = invoke(() -> "done");
将调用方法invoke(Callable<T>)
,由于该方法返回一个值,方法invoke(Runnable)
没有,在这种状况下,lambda表达式() -> "done"
的类型是Callable<T>
。
若是lambda表达式的目标类型及其捕获的参数是可序列化的,则能够序列化它,可是,与内部类同样,强烈建议不要对lambda表达式进行序列化。
你使用lambda表达式来建立匿名方法,可是,有时,lambda表达式只会调用现有方法,在这些状况下,经过名称引用现有方法一般更清楚,方法引用使你能够这样作;对于已经有名称的方法,它们是紧凑的,易于阅读的lambda表达式。
再次考虑Person类:
public class Person { public enum Sex { MALE, FEMALE } String name; LocalDate birthday; Sex gender; String emailAddress; public int getAge() { // ... } public Calendar getBirthday() { return birthday; } public static int compareByAge(Person a, Person b) { return a.birthday.compareTo(b.birthday); }}
假设你的社交网络应用程序的成员包含在一个数组中,而且你但愿按年龄对数组进行排序,你可使用如下代码(在示例MethodReferencesTest中查找本节中描述的代码摘录):
Person[] rosterAsArray = roster.toArray(new Person[roster.size()]); class PersonAgeComparator implements Comparator<Person> { public int compare(Person a, Person b) { return a.getBirthday().compareTo(b.getBirthday()); } } Arrays.sort(rosterAsArray, new PersonAgeComparator());
此调用排序的方法签名以下:
static <T> void sort(T[] a, Comparator<? super T> c)
请注意,Comparator
接口是一个功能性接口,所以,你可使用lambda表达式而不是定义而后建立实现Comparator
的类的新实例:
Arrays.sort(rosterAsArray, (Person a, Person b) -> { return a.getBirthday().compareTo(b.getBirthday()); } );
可是,这种比较两个Person
实例的出生日期的方法已经存在为Person.compareByAge
,你能够在lambda表达式的主体中调用此方法:
Arrays.sort(rosterAsArray, (a, b) -> Person.compareByAge(a, b) );
由于此lambda表达式调用现有方法,因此可使用方法引用而不是lambda表达式:
Arrays.sort(rosterAsArray, Person::compareByAge);
方法引用Person::compareByAge
在语义上与lambda表达式(a, b) -> Person.compareByAge(a, b)
相同,每一个都有如下特色:
Comparator<Person>.compare
复制的,它是(Person, Person)
。Person.compareByAge
。有四种方法引用:
种类 | 示例 |
---|---|
引用静态方法 | ContainingClass::staticMethodName |
引用特定对象的实例方法 | containingObject::instanceMethodName |
引用特定类型的任意对象的实例方法 | ContainingType::methodName |
引用构造函数 | ClassName::new |
方法引用Person::compareByAge
是对静态方法的引用。
如下是对特定对象的实例方法的引用示例:
class ComparisonProvider { public int compareByName(Person a, Person b) { return a.getName().compareTo(b.getName()); } public int compareByAge(Person a, Person b) { return a.getBirthday().compareTo(b.getBirthday()); } } ComparisonProvider myComparisonProvider = new ComparisonProvider(); Arrays.sort(rosterAsArray, myComparisonProvider::compareByName);
方法引用myComparisonProvider::compareByName
调用方法compareByName
,它是对象myComparisonProvider
的一部分,JRE推断出方法类型参数,在本例中是 (Person, Person)
。
如下是对特定类型的任意对象的实例方法的引用示例:
String[] stringArray = { "Barbara", "James", "Mary", "John", "Patricia", "Robert", "Michael", "Linda" }; Arrays.sort(stringArray, String::compareToIgnoreCase);
方法引用String::compareToIgnoreCase
的等效lambda表达式将具备形式参数列表(String a, String b)
,其中a
和b
是用于更好地描述此示例的任意名称,方法引用将调用方法a.compareToIgnoreCase(b)
。
你可使用名称new
以与静态方法相同的方式引用构造函数,如下方法将元素从一个集合复制到另外一个集合:
public static <T, SOURCE extends Collection<T>, DEST extends Collection<T>> DEST transferElements( SOURCE sourceCollection, Supplier<DEST> collectionFactory) { DEST result = collectionFactory.get(); for (T t : sourceCollection) { result.add(t); } return result; }
功能性接口Supplier
包含一个不带参数且返回对象的方法get
,所以,你可使用lambda表达式调用方法transferElements
,以下所示:
Set<Person> rosterSetLambda = transferElements(roster, () -> { return new HashSet<>(); });
你可使用构造函数引用代替lambda表达式,以下所示:
Set<Person> rosterSet = transferElements(roster, HashSet::new);
Java编译器推断你要建立包含Person
类型元素的HashSet
集合,或者,你能够按以下方式指定:
Set<Person> rosterSet = transferElements(roster, HashSet<Person>::new);
如嵌套类一节所述,嵌套类使你可以对仅在一个地方使用的类进行逻辑分组,增长封装的使用,并建立更易读和可维护的代码。局部类、匿名类和lambda表达式也赋予这些优势,可是,它们旨在用于更具体的状况:
lambda表达式:
嵌套类:若是你的要求与局部类的要求相似,则须要使用它,你但愿更普遍地使用该类型,而且不须要访问局部变量或方法参数。