文章目录
当我们面对如下两个需求时:
- 要求某个类的对象仅存在于某个类中。
- 要求两个类之间访问成员要不受访问权限限制。
如果使用普通类单独定义两个类是不能实现这两个需求的,因为普通类没有私有的概念,不能控制这个类仅在某一个类中创建对象,而且普通类不能直接访问其它类的私有成员。
这就需要使用内部类,将一个类定义在另一个类的类体中,可以近似的看成另一个类的成员。在其他类的内部,就有了私有的概念,这个内部的类就有四种访问权限,如果私有化该类,就对外不可见了,而且在包裹它的类中,不再受访问权限控制,内部类可以访问包裹它的类的所有成员,包括私有成员。内部类具有这些特点,在这样的场景下,就可以考虑使用内部类。
内部类的定义
在Java中类可以嵌套定义,广义的内部类指的是定义在另一个类当中的一个类,内部类有很多,可以具体来分。
内部类的分类
按照内部类在类中定义的位置不同,可以分为:
- 定义在成员位置的内部类,包括:成员内部类、静态内部类。
- 定义在局部位置的内部类,包括:局部内部类、匿名内部类、Lambda表达式。
按照定义的语法结构,可以分为:
- 语法上定义了一个类,使用这些内部类时需要创建对象,包括:成员内部类、静态内部类、局部内部类。
- 语法上直接创建了一个对象,可以直接使用,包括:匿名内部类和Lambda表达式。
成员内部类
成员内部类是最普通的内部类,定义在另一个类的成员位置,可以看成该类的一个成员。
- 成员内部类可以近似的看成外围类的一个成员,这是因为在成员内部类对象中,会持有外围类对象的引用,作为自身对象的成员变量,于是要想得到成员内部类对象,必须先创建外围类对象,在外围类对象的基础上,才能去创建成员内部类对象。总的一句话:成员内部类对象需要依赖于外围类对象而存在,成员内部类对象会持有外围类对象的引用作为一个成员变量。
- 创建对象会触发类加载,即便是内部类也不例外,类加载的顺序也应该是先类加载外围类,再加载成员内部类,所以,成员内部类没有static声明,在成员内部类的类加载过程中,也不会执行static相关的内容,原因如下:
- 成员内部类被设计依赖外围类而存在,如果一个成员内部类有了静态成员,那么静态成员不依托于任何内部类实例,破坏了成员内部类依赖于外围类的设计。
- 没必要有,静态成员的目的是脱离对象而使用的一个变量或方法,完全可以将他们定义到外围类中使用。
- 单从语法上来讲,如果有静态成员,那么在静态成员中是可以创建自身对象的,但是类加载过程执行静态成员,此时并没有对象,而创建自身对象又依赖于外部类对象,这就矛盾了。
语法
// 外围类
[访问权限修饰符] class EnclosedClazz{
// 成员内部类
[访问权限修饰符] class InnerClazz{
}
}
自身特点
-
访问权限修饰符
普通类的访问权限级别只有两个(public和默认),而成员内部类近似看成外围类的一个成员,访问级别四种都有。一般来说,既然需要使用内部类,那就是需要隐藏这个内部类,使用private访问权限私有化。
-
成员特点
没有static声明,包括静态成员变量、静态成员方法、静态代码块。但是可以有static修饰的,用字面值常量赋值的全局常量,静态成员常量不触发类初始化,是可以在成员内部类中定义的。
-
继承和实现
可以继承别的类,也可以实现别的接口,包括内部类和内部接口。
访问特点
首先明确
- 成员内部类和外围类之间的访问不受权限限制,包括private。
- 成员内部类对象依赖于外围类对象而存在。成员内部类对象会持有外围类对象的引用,所以要想得到成员内部类对象,必须是在外围类对象的基础上创建的。
- 成员内部类的成员方法中,一共存在两个对象:一个是当前成员内部类对象,用“this关键字”指向它;另一个是当前成员内部类的外围类对象,用“外围类类名.this”指向它。
- 外围类的成员方法中,只有一个对象,即外围类的自身对象。虽然成员内部类对象依赖于外围类对象,但外围类对象并不依赖成员内部类。由于外围类对象已经存在了,就可以在方法中直接new创建成员内部类对象(这个new隐含了this传参,表示在外围类对象的基础上创建成员内部类对象)。
- 成员内部类中没有静态成员方法。
- 外围类的静态成员方法中没有任何对象,但可以创建,可以直接创建外围类自身对象,而如果要创建成员内部类对象,则必须先创建外围类对象,再在外围类的基础上创建成员内部类对象(可以使用匿名对象链式调用创建)。
而成员内部类的具体访问特点如下:
-
成员内部类内部访问外围类
由于成员内部类中没有静态方法,所以只讨论成员方法中的访问情况:由于已有外围类对象,可直接访问。
特殊——出现同名:
- 同名成员变量:默认就近原则访问成员内部类自己的,如果要访问外围类的,就用“外围类类名.this.成员变量名”访问。如果外围类、成员内部类、成员内部类的成员方法中都具有同名变量,那么访问方式为:
- 直接使用变量名:就近原则访问方法中的局部变量。
- this.变量名:访问当前对象,即成员内部类中的成员变量。
- 外围类类名.this.变量名:访问外围类中的成员变量。
- 同名全局常量:用“类名.”区分,因为全局常量是属于类的。
- 同名成员变量:默认就近原则访问成员内部类自己的,如果要访问外围类的,就用“外围类类名.this.成员变量名”访问。如果外围类、成员内部类、成员内部类的成员方法中都具有同名变量,那么访问方式为:
-
外围类访问成员内部类成员
-
成员方法中访问
直接new创建成员内部类对象,然后正常访问。
-
静态成员方法中访问
先创建外围类自身对象,然后在此基础上再创建成员内部类对象。
特殊——出现同名:
- 使用各自不同的引用访问不同的成员。
-
-
外部类访问成员内部类成员
首先,访问的全程受访问权限限制:第一需要外围类权限、第二需要成员内部类权限、第三需要被访问的成员的权限。
对于外部类,不论是在成员方法中还是在静态成员方法中访问内部类成员,没有区别,因为都没有外围类对象,都需要先创建外围类对象,再在此基础上创建成员内部类对象进行访问。
在外部类创建成员内部类对象时,接收对象的引用类型需要注意:不能直接使用成员内部类类名,而是需要明确指出该成员内部类属于哪个外围类,使用“外围类类名.成员内部类类名”来指出引用的类型。
然而在实际中,定义内部类的目的就是为了不让外部类访问,因此这种访问场景并不多见。
-
成员内部类访问外部类成员
需要创建外部类对象然后访问,并且全程受访问权限限制。这种场景同样不多见。
静态内部类
静态内部类相比于其它内部类的极致的封装思想大不相同,甚至不像一个“内部类”,它与外围类是两个独立的类。静态内部类本身是一个独立的类,只不过将它放入了一个类的类体中,借助外围类来保护自己罢了。静态内部类的成员特点和普通类没有任何区别,静态内部类和外围类没有任何依赖关系(仅仅是嵌套的关系)。
语法
// 外围类
[访问权限修饰符] class EnclosedClazz{
// 静态内部类
[访问权限修饰符] static class NestedClazz{
}
}
自身特点
-
访问权限修饰符
只要是成员位置的内部类,包括成员内部类和静态内部类,都有四种访问级别。
-
成员特点
和普通类别无二致
-
继承和实现
可以继承类,也可以实现接口,但是在继承内部类时只能继承静态内部类。
访问特点
首先明确
- 静态内部类和外围类互相访问不受访问权限限制。
- 静态内部类和外围类相互独立,创建对象不会互相影响。
- 静态内部类中的成员方法中,只有一个对象,即静态内部类自身对象。如果想要访问外围类,就直接new创建外围类对象,然后正常用对象访问。
- 外围类中的成员方法中,只有一个对象,即外围类自身对象。如果想要访问静态内部类,同样直接new创建静态内部类对象即可。
- 不论是静态内部类还是外围类的静态成员方法中,没有对象,想访问谁就new创建谁的对象,没有限制。
而静态内部类的具体访问特点如下:
-
静态内部类内部访问外围类
静态内部类中没有外围类对象,需要new创建外围类对象再访问。
-
外围类中访问静态内部类成员
外围类中没有静态内部类对象,需要new创建静态内部类对象再访问。
-
外部类访问静态内部类成员
无需创建外围类对象,直接创建静态内部类对象再访问,整个访问过程受访问权限限制。
但是要注意的是,在外部类中创建静态内部类对象,需要使用“外围类类名.静态内部类类名”明确指出该静态内部类属于哪个外围类,但是和成员内部类不同的是,这里不需要创建外围类对象。
-
静态内部类访问外部类成员
创建对应外部类对象再访问,整个访问过程受访问权限限制。
小结:与成员内部类的比较
静态内部类和外围类相互独立,类加载、创建对象都不会互相影响。使用静态内部类相当于使用成员内部类,更加灵活,不需要考虑内部类和外围类具有依赖关系,使用静态内部类就是单纯的封装隐藏类。
成员内部类和静态内部类的首要用途是隐藏类,当在某一个位置需要一个对象来完成,但又不希望外界知道这个类时,可以用内部类,用的时候优先用静态内部类,因为它不依赖外围类,限制比较少。
局部内部类
局部内部类是定义在一个方法或者一个作用域里面的类,简单来说,将局部内部类看成局部变量即可,该类的有效范围仅在作用域内部。这意味着要创建它的对象,必须在作用域内部创建。局部内部类相比成员内部类和静态内部类要更加重要,因为局部内部类是学习匿名内部类和Lambda表达式的基础。
语法
// 局部位置
class Inner{
}
// 局部位置
自身特点
-
修饰符特点
局部内部类就相当于局部变量,它没有访问权限的概念,因为它已经被大括号限制死了作用域。
-
成员特点
和成员内部类一样,没有static声明,但是可以定义用字面值常量赋值的全局常量(不触发类加载)。
-
继承和实现
【重要用途】定义局部内部类多数情况下都是为了继承外部类或实现外部接口。
自身特点的注意事项
- 局部内部类是一种比成员位置内部类更加极致的封装思想的体现,成员位置的内部类在成员位置,整个外围类都是可以访问到它的;但是局部内部类一旦出了作用域就不在生效了。
- 局部内部类的使用会显著增加代码层级,导致代码可读性变差,所以非必要不使用,如果在局部位置遇到问题希望有一个对象能够解决问题,同时又希望这个类仅在方法内部生效,不被外部感知,就可以使用局部内部类。
访问特点
首先明确
- 局部内部类当中可以任意访问外围类成员,不受权限限制。但是,外围类访问局部内部类成员时,虽然不受访问权限限制,但受到局部内部类作用域的限制,在作用域外不能访问。
- 局部内部类和外围类的关系与局部内部类的位置有关
- 如果是外围类成员方法中的局部内部类,该局部内部类对象会持有外围类对象的引用,类似成员内部类对象。
- 如果是外围类静态成员方法中的局部内部类,该局部内部类对象不会持有外围类对象的引用,类似静态内部类对象。
而局部内部类的具体的访问特点如下:
-
局部内部类内部访问外围类(分两种情况)
- 外围类成员方法中的局部内部类:该局部内部类对象会持有外围类对象的引用,所以可以直接访问外围类成员,如果出现同名情况,则使用“外围类类名.this.同名成员名”进行区分(与成员内部类方式一样)。
- 外围类静态成员方法中的局部内部类:静态成员方法中没有外围类对象,需要new创建外围类对象再正常访问。
-
外围类访问局部内部类成员
只有在装有局部内部类的作用域内部创建对象,才能够访问,不受访问权限限制。其余位置访问不到。
-
外部类访问局部内部类成员
不能直接访问,原因是作用域的限制,而与访问权限无关。
但是如果真的需要访问也是可以实现的,间接访问,对于方法内部的局部内部类,可以用方法返回值将该局部内部类对象返回出来。
-
局部内部类访问外部类成员
创建对应外部类对象再访问,整个访问过程受访问权限限制。
局部内部类的使用场景
-
方法需要返回一个对象,返回值类型是引用数据类型时
可以在方法的局部位置写一个局部内部类,继承或实现外部的类或接口,创建对象后作为返回值。当返回值是引用数据类型时,方法的返回值可以是返回值类型的对象,也可以是返回值类型的子类对象。
-
方法需要传入一个对象,形参数据类型是引用数据类型时
可以在调用方法的位置定义一个局部内部类,继承或实现外部的类或接口,创建对象后作为方法的实参传入。当形参是引用数据类型时,方法的实参可以是形参类型的对象,也可以是形参类型的子类对象。
使用局部内部类的优缺点
优点
- 绝对对外界隐藏、封装。
- 相比较于传统的定义类,然后创建对象,它相对更加简洁省事。
缺点
- 这个类是一次性的。
- 虽然相比传统的定义类更加省事,但是实际上还是比较麻烦的,更加常用的则是局部内部类的简化(匿名内部类、Lambda表达式)。
注意事项
-
假如在局部位置需要一次性使用某个对象,可以使用局部内部类创建它。但是,如果需要多次在不同方法中使用,使用局部内部类则得不偿失。
-
在局部内部类当中,如果想要访问局部内部类所在作用域内的局部变量,那么这应该是一个常量:
- 要么是一个直接用final修饰的局部常量
- 要么是一个事实上的常量,只能赋值一次,可以访问,不能修改
原因:
主要原因在于局部变量和对象生命周期的冲突,作用域内的局部变量随着方法出栈被销毁,但局部内部类对象仍然存在,该对象的成员方法依然会访问局部变量,但这时局部变量已经没有了。
为了解决这种生命周期的冲突,JVM会在创建访问了作用域局部变量的局部内部类对象时,将该局部变量作为局部内部类对象的成员变量(简单说就是在局部内部类对象中拷贝了一份局部变量),这样当真正的那个局部变量随着方法出栈被销毁后,对象中依然有一份局部变量的数据可供访问。
但是这种方法本身带来新的问题,一旦用拷贝成员变量的方式,如果原本的成员变量发生修改,拷贝的这一份局部变量则无法同步取值;如果在对象中的这一份拷贝发生修改,也无法将取值同步给原本的局部变量。为了避免同步的麻烦,Java的设计者干脆禁止对成员变量进行修改,使其成为一个常量。这样一来,牺牲掉变量的修改能力,解决了局部变量和对象生命周期的冲突,又避免了同步的问题。
在Java7及更早的JDK版本中,在局部内部类中,被访问的作用域内部的局部变量,必须显式声明为final局部常量。但是到Java8,就不必须这么做了,只需要不修改它即可(语法糖)。
以上的三种内部类(成员内部类、静态内部类、局部内部类)都是实实在在的定义了一个类,后续使用需要创建对象。既然需要创建对象,那么如果一步到位,直接创建这个内部类的对象进行使用,而这个类叫什么已经不重要了,这称之为内部类对象,本质上还是属于定义在别的类内部的一个类。我们定义内部类的目的,最终还是为了获取对象使用,所以内部类对象相比先定义类再创建对象的方式会更加常用,比如匿名内部类和Lambda表达式。
匿名内部类
匿名内部类是特殊的局部内部类,是简化语法的局部内部类,仍然定义在局部位置。匿名内部类和匿名对象类似,这个内部类没有名字。当一个类没有名字,就显然不能在定义这个类之后再创建对象的,只能在定义匿名内部类的同时,直接new创建它的对象。
语法
// 局部位置
new 类名/接口名(){
// 类名或接口名的子类的类体
};
// 局部位置
语法解释:
-
new关键字表示直接创建对象,因为这个特殊的局部内部类是匿名的,不直接创建对象,后面也就无法创建了。
-
类名或接口名可以是普通类或接口,也可以是抽象类或接口。
-
【重要】匿名内部类语法中隐含“继承”或“实现”,其创建的对象不是“类名或接口名”的对象(首先接口就不能创建对象),而是创建“类名或接口名”的子类对象,匿名内部类的实质是“匿名的、某个类或接口的子类内部类对象”。
-
new关键字后面的部分,类名/接口名加上类体:
类名/接口名(){ // 类名或接口名的子类的类体 };是匿名子类的类体,在这个类体中,可以重写父类方法,也可以新增自身成员,没有static声明,但是可以定义不触发类加载的全局常量。实际上,匿名内部类中不经常会自定义新增成员。
-
【重要】对于语法中的小括号“( )”的解释:
- 这个小括号是给父类构造器传参的,而不是给匿名子类的构造器传参的。
- 如果匿名内部类创建的是接口的子类对象,由于接口没有构造器,那么“( )”就没有用了,仅仅是语法格式,没有意义。
- 匿名内部类的语法没有办法给匿名子类构造器传参,所以匿名内部类没有构造器,即便有,也没用。这也是匿名内部类和局部内部类相比特殊的地方。
使用
- 匿名内部类的语法本身就是得到一个对象(类名/接口名的匿名子类对象),可以直接使用,直接在语法后面用“.”访问成员即可。
- 优点:简单快捷,并且可以访问匿名子类中的独有成员。
- 缺点:只能用一次。
- 用语法中的“类名/接口名”作为父引用接收这个对象,然后通过引用去使用。
- 优点:可以使用多次。
- 缺点:不能访问匿名子类的独有成员(因为不能做强制类型转换)。
直接使用的方式更加常用,且如果想要访问匿名子类的成员,也必须直接使用。
注意事项
-
匿名内部类是特殊的局部内部类。
在成员方法中的匿名内部类会持有外围类对象的引用,可以直接访问外围类成员;
但是在静态成员方法中的匿名内部类,不会持有外围类对象的引用,不可以直接访问外围类成员。
-
匿名内部类当中一旦想要访问作用域(方法)内部的局部变量,那么该局部变量应该是常量。
-
匿名内部类语法中的大括号表示该匿名子类的类体。
匿名内部类的使用场景
匿名内部类比局部内部类语法更简洁,使用更方便,基本取代局部内部类的使用场景。
- 作为方法的返回值(返回的是方法返回值类型的子类对象)
- 作为方法的实参(传入的是方法形参类型的子类对象)
一定要明确认识到,匿名内部类对象是子类对象,只是可以当作父类使用,尤其是对于抽象类或接口这种本身不能创建对象的结构,使用匿名内部类创建它们的匿名子类对象返回或传入。
Lambda表达式
Lambda表达式是JDK8的一个新特性,可以取代接口的匿名内部类,如果说匿名内部类实际上是局部内部类的进一步简化,那么Lambda表达式就是匿名内部类的进一步简化,语法上更加简洁,代码更加优雅。
要认识Lambda表达式,要具体认识到:
- Lambda表达式仍然是局部内部类,是特殊的局部内部类,仍然定义在局部位置,局部内部类受到的限制Lambda表达式一样存在,而且Lambda表达式肯定和局部内部类、匿名内部类有很大的区别。
- Lambda表达式在取代匿名内部类时,并非能取代所有的匿名内部类,而是只能取代接口的匿名内部类,而类的匿名内部类Lambda表达式是不能取代的。
- Lambda表达式既然是匿名内部类的进一步简化,那么它得到的也是一个对象,并且创建的是接口的子类对象。
Lambda表达式的接口
Lambda表达式是创建接口的子类对象,所以使用Lambda表达式首先需要一个接口。而且Lambda表达式对这个接口有特殊的要求:接口必须有且仅有一个要强制子类实现的抽象方法。
这种“有且仅有一个要强制子类实现的抽象方法”的接口被称为功能接口(Functional Interface)
语法上可以使用注解
@FunctionalInterface标记某个接口,如果不报错,则该接口就是一个功能接口(类似于方法重写的注解@Override)
关于功能接口,还要了解到:
- 功能接口中虽然有且仅有一个强制子类实现的抽象方法,但是自Java8开始,接口中可以存在默认实现方法或具体实现方法,这两种实现方法不强制子类实现,因此,功能接口中不一定只有一个方法。
- 功能接口中也不是只能有一个抽象方法,原因是:Java中的每一个类都继承了Object类,那么接口的子类必然也继承了Object类,如果接口中的抽象方法,可以使用Object类当中的方法作为实现,那么这个抽象方法就不需要子类实现,也就出现了那个强制子类实现的抽象方法之外的抽象方法。
- 虽然有上述两种情况允许功能接口中出现更多的方法,但是大多数功能接口,都是只有一个方法,并且这个方法就是要强制子类实现的抽象方法。
Lambda表达式的语法
() -> {}
语法解释:
-
(),表示功能接口中,那个要强制子类实现的抽象方法的形参列表。直接将抽象方法的形参列表抄过来即可。 -
->,是Lambda表达式的运算符,当作一个固定的语法格式,读作“goes to”。 -
{},是重写功能接口中,那个要强制子类实现的抽象方法的重写方法体。注意:这里的大括号
{}是方法体,区别于局部内部类和匿名内部类中的大括号{}是类体。
至此,可以进一步理解:
- Lambda表达式的语法结构决定了Lambda表达式最多且必须重写一个抽象方法(语法结构显然不能重写两个方法),所以Lambda表达式要求接口必须是功能接口。
- 在Lambda表达式中的
{}是重写方法的方法体,那么在方法体中定义的变量是局部变量,这就决定了Lambda表达式实现接口的子类对象,不可能新增自己的成员,只能重写接口中的抽象方法。
Lambda表达式的类型推断
有了Lambda表达式的语法,但是如果直接在局部位置写Lambda表达式的语法,程序会报错,这是因为直接写语法,编译器无法得知该Lambda表达式究竟要创建谁的子类对象。
这就需要帮助编译器明确Lambda表达式所创建的对象的类型,也就是告诉编译器Lambda表达式创建的究竟是哪个功能接口的子类对象。而这一步骤,就是“Lambda表达式的类型推断”。
四种方式
-
直接使用父接口的引用指向Lambda表达式,接收Lambda表达式创建的子类对象。
这种方式最简单、直接,在使用引用接收对象后直接使用引用操作对象即可。
而且,由于Lambda表达式只重写方法,不会在子类对象中新增成员,所以避免了匿名内部类中父引用限制访问范围的情况。
-
使用Lambda表达式的特殊语法,不用引用接收,直接告诉编译器类型。
((父接口的接口名)(Lambda表达式)).方法名(实参);这种形式必须严格按照上述语法直接调用方法,否则会编译报错。
-
借助方法的返回值类型
如果方法的返回值类型是一个功能接口,那么编译器就肯定知道方法最后需要返回的数据类型。那么在方法体中,可以直接用Lambda表达式创建该功能接口的子类对象作为返回值。
-
借助方法的形参列表
如果方法的形参数据类型是一个功能接口,那么编译器肯定知道这里需要传入的实参的数据类型。那么调用方法时,可以直接用Lambda表达式创建该功能接口的子类对象作为实参传入方法。
前两种方式在实际使用中并不常见,更多的时候Lambda表达式的类型推断需要借助方法完成,后两种方式更为常见。
Lambda表达式的进一步简化(进阶)
简化的前提
Lambda表达式能够简化的前提是它是功能接口的子类对象,功能接口中有且只有一个必须要实现的抽象方法,而且简化不能引起歧义,否则不能简化。
具体的简化规则
根据Lambda表达式的语法考虑哪些部分能简化
(形参列表) -> {
// 方法体
}
(形参列表)——可以简化- 形参列表中的形参数据类型是可以省略的,因为写或不写,数据类型都是确定的。
- 如果形参列表只有一个参数,那么小括号可以省略,直接写形参名。
- 但是如果形参列表没有参数即方法是无参的,那么空的小括号不能省略,必须写出来。
{}——可以简化- 在大括号中重写方法体的语句仅有一条时,可以省略大括号,直接在箭头后面跟这一条语句。
- 如果这仅有的一条语句还是方法的返回值语句时,可以将大括号和return关键字一起省略,直接在箭头后面跟返回值。
方法引用
在Lambda表达式的简化中,形参列表的简化其实变动不大,而最需要被去掉的应该是大括号,因为大括号增加了代码的层级。但是在实际开发中,不太可能一句话就能完成对抽象方法的重写,因此上述大括号的简化条件很难达到。所以,Java中提供了“方法引用”来简化Lambda表达式的实现。
Lambda表达式其实就是用一个实现方法来表示功能接口中抽象方法的实现,进一步说,Lambda表达式的实质就是方法的重写。而方法的重写实质上还是一个方法,那么就可以用一个已经存在的、已经实现好的方法作为Lambda表达式的实现,将Lambda表达式的实现直接指向这个方法,这就是“方法引用”。
作为Lambda表达式实现的方法的条件
作为Lambda表达式的实现方法,这个方法是对功能接口中抽象方法的重写,那么根据抽象方法的语法分析:
返回值类型 方法名(形参列表);
能够作为Lambda表达式实现的方法需要满足:
- 修饰符列表:需要有访问权限,其他没有要求。static方法或是非static方法都可以。
- 返回值类型:要求“兼容”。
- 基本数据类型和void,必须保持一致。
- 引用数据类型,返回值可以是一致的,也可以是子类类型。
- 方法名:没有要求。
- 形参列表:必须要求完全一致。
- 方法体:没有要求。
方法引用的语法
方法引用的语法有两种。
第一种
不省略“->”的语法。
(形参列表) -> “调用”已存在的方法(形参列表);
语法解释:
-
“调用”已存在的方法指的是在Lambda表达式的这个位置去调用那个已实现的方法。
- 如果是静态成员方法:用“类名.”表示调用,若是在同类中,则可以直接用方法名表示调用这个方法。
- 如果是成员方法:用“对象名.”表示调用,如果没有对象,可以使用创建匿名对象的方式调用。
需要注意理解的是,这里虽然表述为“调用”已存在的方法,但是实际上后面要写的是形参列表。而具体的实参需要等到使用Lambda表达式创建的功能接口子类对象真正的调用这个方法时才传入。
-
上述语法中的两个
(形参列表)是保持一致的,表示将Lambda表达式的形参传递给这个被“调用”的已实现的方法,而Lambda表达式的形参列表则就是功能接口中,那个要强制子类实现的抽象方法的形参列表。
第二种
省略形参列表和“->”的语法。这种语法更常用、更简洁。
父引用接口 对象名 = 方法的归属者::方法名;
语法解释:
- 方法的归属者
- 静态成员方法属于类,写类名。
- 成员方法属于对象,写对象名。如果没有对象,可以使用匿名对象的方式。
Lambda表达式需要注意的细节(强调)
- Lambda表达式的方法引用,可以用一个已经存在的方法作为它的实现,这个方法可以是自己写的,也可以是JDK源码中的,也可以是第三方工具包中的,只要是已存在的方法都可以。
- Lambda表达式是特殊的局部内部类,访问作用域内部的局部变量时,该局部变量是常量。
- Lambda表达式语法中的大括号是重写父接口抽象方法的方法体(区别于局部内部类和匿名内部类的大括号表示类体),在方法体中定义的变量是局部变量,但是,Lambda表达式的大括号没有自身独立的作用域,而是和装Lambda表达式的作用域共用一个作用域。
本文详细介绍了Java中的内部类,包括成员内部类、静态内部类和局部内部类,阐述了它们的定义、特点和访问规则。同时,文章深入探讨了Lambda表达式的概念、语法和使用场景,以及其与内部类的区别和联系,展示了Lambda表达式在简化代码和提高效率方面的优势。

1041

被折叠的 条评论
为什么被折叠?



