为了账号安全,请及时绑定邮箱和手机立即绑定
2.4 匿名内部类

2.4.1 定义匿名内部类就是没有名字的内部类。使用匿名内部类,通常令其实现一个抽象类或接口。请阅读如下代码:721运行结果:汽车跑飞机飞上述代码中的抽象父类中有一个方法 run(),其子类必须实现,我们使用匿名内部类的方式将子类的定义和对象的实例化放到了一起,通过观察我们可以看出,代码中定义了两个匿名内部类,并且分别进行了对象的实例化,分别为 car 和 airPlain,然后成功调用了其实现的成员方法 run()。2.4.2 特点含有匿名内部类的类被编译之后,匿名内部类会单独生成一个字节码文件,文件名的命名方式为:外部类名称$数字.class。例如,我们将上面含有两个匿名内部类的 Transport.java 编译,目录下将会生成三个字节码文件:Transport$1.classTransport$2.classTransport.class匿名内部类没有类型名称和实例对象名称;匿名内部类可以继承父类也可以实现接口,但二者不可兼得;匿名内部类无法使用访问修饰符、static、abstract 关键字修饰;匿名内部类无法编写构造方法,因为它没有类名;匿名内部类中不能出现静态成员。2.4.2 使用场景由于匿名内部类没有名称,类的定义可实例化都放到了一起,这样可以简化代码的编写,但同时也让代码变得不易阅读。当我们在代码中只用到类的一个实例、方法只调用一次,可以使用匿名内部类。

3.2 匿名内部类

匿名内部类的写法会比较简单直接,但是缺点是只能用一次,并且代码会集中在方法体内,如果处理逻辑过于复杂会导致方法代码冗余。所以通常在只需要使用一次并且内部逻辑不太复杂的时候使用。package com.emercy.myapplication;import android.app.Activity;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.Toast;public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button button = findViewById(R.id.button); // 创建匿名内部类绑定点击监听器 button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 在回调中处理点击事件 Toast.makeText(MainActivity.this, "Button被点击了", Toast.LENGTH_LONG).show(); } }); }}

1.2 替代 Java 中的匿名内部类

我们都知道在 Java 中有匿名内部类,一般情况直接通过 new 匿名内部类对象,然后重写内部抽象方法。但是在 Kotlin 使用 object 对象表达式来替代了匿名内部类,一般匿名内部类用在接口回调比较多。比如 Java 实现匿名内部类:public interface OnClickListener { void onClick(View view);}mButton.setOnClickListener(new OnClickListener() {//Java创建匿名内部类对象 @Override public void onClick(View view) { //do logic }});然而在 Kotlin 并不是直接创建一个匿名接口对象,而是借助 object 表达式来声明的。interface OnClickListener { fun onClick()}mButton.setOnClickListener(object: OnClickListener{//Kotlin创建object对象表达式 override fun onClick() { //do logic }})

7. 匿名内部类创建 Thread

首先确认,这并不是线程创建的第四种方式,先来看如何创建。实例:Thread t = new Thread(new Runnable() { @Override public void run() { System.out.println("通过匿名内部类创建Thread"); } });我们从代码中可以看出,还是进行了一个 Runnable 接口的使用,所以这并不是新的 Thread 创建方式,只不过是通过方式二实现的一个内部类创建。Tips: 在后续章节讲解 join 方法如何使用 的时候,我们会采用匿名内部类的方式进行多线程的创建。

Lambda 表达式 VS 匿名内部类

本节我们将重点讨论下 Lambda 表达式与匿名内部类之间除了语法外还有哪些差别。再开始讲解之前我们先列出两者重要的两点区别:无标识性: 内部类会确保创建一个拥有唯一表示的对象,而 Lambda 表达式的计算结果有可能有唯一标识,也可能没有。作用域规则: 由于内部类可以从父类继承属性,Lambda 表达式却不能,所以,内部类的作用域规则比 Lambda 表达式要复杂。(关于 Lambda 表达式的作用域规则可以回看 03 节的内容)我们来看一个例子:1267返回结果为:call run in innerRunnable: class com.github.x19990416.item.TestLambdaAndInnerClass$1 call run in lambdaRunnable: class com.github.x19990416.item.TestLambdaAndInnerClass上面的例子分别在内部类和 Lambda 表达式中调用各自的 this 指针。我们发现 Lambda 表达式的 this 指针指向的是其外围类 TestLambdaAndInnerClass,匿名内部类的指针指向的是其本身。(对于 super 也是一样的结果)我们来看下编译出来的文件:​​其中 TestLambdaAndInnerClass$1.class 是匿名类 innerRunnable 编译出来的 class 文件,对于 Lambda 表达式 lambdaRunnable 则没有编译出具体的 class 文件。这说明对于 Lambda 表达式而言,编译器并不认为它是一个完全的类(或者说它是一个特殊的类对象),所以也不具备一个完全类的特征。Tips: 匿名类的 this、super 指针指向的是其自身的实例,而 Lambda 表达式的 this、super 指针指向的是创建这个 Lambda 表达式的类对象的实例。

3.2 object 用于匿名内部类场景

object 使用匿名内部场景在开发中还是比较多的,对于需要写一些接口回调方法时,一般都离不开 object 对象表达式。interface OnClickListener { fun onClick()}mButton.setOnClickListener(object: OnClickListener{//Kotlin创建object对象表达式 override fun onClick() { //do logic }})

3.3 object 匿名内部类场景和 lambda 表达式场景如何选择

其实我们知道在 Kotlin 中对于匿名内部类场景,除了可以使用 object 对象表达式场景还可以使用 lambda 表达式,但是需要注意的是能使用 lambda 表达式替代匿名内部场景必须是匿名内部类使用的类接口中只能有一个抽象方法,超过一个都不能使用 lambda 表达式来替代。所以对于 object 表达式和 lambda 表达式替换匿名内部类场景就一目了然了。interface OnClickListener { fun onClick()}mButton.setOnClickListener{//因为OnClickListener中只有一个抽象方法onClick,所以可以直接使用lambda表达式的简写形式 //do logic}interface OnLongClickListener { fun onLongClick() fun onClickLog()}mButton.setOnLongClickListener(object : OnLongClickListener {//因为OnLongClickListener中有两个抽象方法,所以只能使用object表达式这种形式 override fun onLongClick() { } override fun onClickLog() { }})

4.3 匿名函数

没有名字的函数就是一个匿名函数var fn = function() { console.log('我是一个匿名函数');};除了在函数表达式中会出现匿名函数,还有许多场景。相对常见的一个就是自执行匿名函数,MDN官方翻译为立即调用函数表达式。自执行就是这个函数声明后就会立即执行,自执行的匿名函数通常会被用来形成独立的作用域。如:(function() { var num = 1; alert(num);})();这是一个自执行的匿名函数,这个匿名函数是被包裹了一段括号后才被调用的。以下这段代码会报错:// 报错function() { var num = 1; alert(num);}();浏览器会告诉你必须给函数一个名字。通过括号包裹一段函数,让js引擎识别成他是一个函数表达式,再对他进行执行,就不会报错,这是加括号的原因。同理,可以使用 +,! 等运算符代替括号,让一个匿名函数成为一个函数表达式即可。大部分第三方框架都会通过一个自执行的匿名函数包裹代码,与浏览器全局环境隔离,避免污染到全局环境。

4.1 使用使用匿名内部类来将行为和点击按钮进行关联

这是我们在 Java 8 以前,通常的写法:button.addActionListener(new ActionListener(){ @Override public void actionPerformed(ActionEvent actionEvent) { System.out.println("button click"); }});在上面的例子中,我们创建了一个对象来实现 ActionListener 接口(这个对象并没有命名,它是一个匿名内部类),这个对象有一个 actionPerformed 方法。当用户点击按钮 button 时,就会调用该方法,输出 button click。

4.1 Kotlin 和 Java 内部类或 lambda 访问局部变量的区别

在 Java 中在函数内部定义一个匿名内部类或者 lambda,内部类访问的函数局部变量必须需要 final 修饰,也就意味着在内部类内部或者 lambda 表达式的内部是无法去修改函数局部变量的值。可以看一个很简单的 Android 事件点击的例子:public class DemoActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_demo); final int count = 0;//需要使用final修饰 findViewById(R.id.btn_click).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { System.out.println(count);//在匿名OnClickListener类内部访问count必须要是final修饰 } }); }}在 Kotlin 中在函数内部定义 lambda 或者内部类,既可以访问final修饰的变量,也可以访问非 final 修饰的变量,也就意味着在 Lambda 的内部是可以直接修改函数局部变量的值。以上例子 Kotlin 实现:访问 final 修饰的变量:class Demo2Activity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_demo2) val count = 0//声明final btn_click.setOnClickListener { println(count)//访问final修饰的变量这个是和Java是保持一致的。 } }}访问非 final 修饰的变量,并修改它的值:class Demo2Activity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_demo2) var count = 0//声明非final类型 btn_click.setOnClickListener { println(count++)//直接访问和修改非final类型的变量 } }}通过以上对比会发现 Kotlin 中使用 lambda 会比 Java 中使用 lambda 更灵活,访问受到限制更少,这也就回答本博客最开始说的一句话,Kotlin 中的 lambda 表达式是真正意义上的支持闭包,而 Java中 的lambda 则不是。Kotlin 中的 lambda 表达式是怎么做到这一点的呢?

2. 匿名函数引用外部变量

如果在匿名函数内,使用了外部环境的变量,就构成了一个闭包。简单来讲就是一个函数内,使用匿名函数来操作函数内声明的变量。代码示例:package mainimport ( "fmt")func main() { str := "Hello World!" func() { str = "Hello Codey!" }() fmt.Println(str)}第 10 行:匿名函数直接操作了main函数之中的变量str,将其修改为了"Hello Codey!";第 12 行:输出变量的值。执行结果:上述例子简单的构造了一个闭包,在匿名函数中并没有声明或者定义str这个变量,但是可以直接操作,就是引用可main函数中的自由变量。这个例子可能对自由变量的引用表现不是很直观,我们接下来使用defer和闭包相结合,深入了解一下闭包中的引用外部变量。代码示例:package mainimport ( "fmt")func main() { str := "Hello World!" defer func() { fmt.Println("defer str=", str) }() str = "Hello Codey!" fmt.Println("main str=", str)}执行结果:从执行结果上来看应该是蛮出人意料的,因为前文介绍 defer 的时候明确介绍了 defer 后变量是保留它在 defer 时的值,而不会被 defer 之后的代码所改变。但是在闭包这边这个看起来不太适用,其实是适用的,只是闭包是引用了这个变量,也就是说,在 defer 时被保留下来的是这个变量的地址,后续代码改变的不是地址,而是这个地址存储的值,所以后续代码对这个变量的操作,都会反应到这个 defer 中。Tips:关于变量的地址,在后续的Go语言的指针中会有详细的介绍。

1. Go 语言的匿名函数

在上文中我们了解到了一个新的词汇——匿名函数,我们先来学习一下Go语言中的匿名函数,再来了解在 Go 语言中如何使用闭包。匿名函数,顾名思义,就是隐藏函数名的函数。代码示例:package mainimport ( "fmt")var f = func() { fmt.Println("匿名函数作为变量来使用")}func main() { f() func() { fmt.Println("匿名函数直接使用") }()}第7~9行:定义一个函数类型,值为一个匿名函数的变量;第 12 行:使用这个匿名函数;第 14~16 行:定义一个匿名函数。在这个函数后加上(),就可以直接使用这个匿名函数。执行结果:

1. 结构体内嵌类型

结构体中内嵌类型在开发中用的并不多,这只是一个延伸特性。意味着结构体在定义属性字段的时候可以只写类型,不写属性名,但是一个类型只能写一个,不然结构体会无法识别你调用的时候使用的是哪个字段,这种只写类型不写属性名的属性被称为匿名字段。代码示例:package mainimport "fmt"type Student struct { Name string Age int int}func newStudent(name string, age int) Student { return Student{Name: name, Age: age, int: 10}}func main() { stu := newStudent("Codey", 18) fmt.Println("匿名字段的值:", stu.int)}第 8 行:结构体中内嵌了一个int类型;第 12 行:给匿名字段赋值的时候直接使用其字段类型即可;第 16 行:打印匿名字段的值。执行结果:

2.1 成员内部类

2.1.1 定义成员内部类也称为普通内部类,它是最常见的内部类。可以将其看作外部类的一个成员。在成员内部类中无法声明静态成员。如下代码中声明了一个成员内部类:// 外部类 Carpublic class Car { // 内部类 Engine private class Engine { private void run() { System.out.println("发动机启动了!"); } }}我们在外部类 Car 的内部定义了一个成员内部类 Engine,在 Engine 下面有一个 run() 方法,其功能是打印输出一行字符串:“发动机启动了!”。另外,需要注意的是,与普通的 Java 类不同,含有内部类的类被编译器编译后,会生成两个独立的字节码文件:Car$Engine.classCar.class内部类 Engine 会另外生成一个字节码文件,其文件名为:外部类类名 $ 内部类类名.class。2.1.2 实例化内部类在外部使用时,无法直接实例化,需要借助外部类才能完成实例化操作。关于成员内部类的实例化,有 3 种方法:我们可以通过 new 外部类().new 内部类() 的方式获取内部类的实例对象:715运行结果:发动机启动了!我们可通过先实例化外部类、再实例化内部类的方法获取内部类的对象实例:public static void main(String[] args) { // 1.实例化外部类 Car car = new Car(); // 2.通过外部类实例对象再实例化内部类 Engine engine = car.new Engine(); // 3.调用内部类的方法 engine.run();}编译执行,成功调用了内部类的 run () 方法:$javac Car.javajava Car发动机启动了!我们也可以在外部类中定义一个获取内部类的方法 getEngine(),然后通过外部类的实例对象调用这个方法来获取内部类的实例:716运行结果:发动机启动了!这种设计在是非常常见的,同样可以成功调用执行 run() 方法。2.1.2 成员的访问成员内部类可以直接访问外部类的成员,例如,可以在内部类的中访问外部类的成员属性:717观察 Engine 的 run() 方法,调用了外部类的成员属性 name,我们在主方法实例化 Car 后,已经为属性 name 赋值。运行结果:大奔奔的发动机启动了!相同的,除了成员属性,成员方法也可以自由访问。这里不再赘述。还存在一个同名成员的问题:如果内部类中也存在一个同名成员,那么优先访问内部类的成员。可理解为就近原则。这种情况下如果依然希望访问外部类的属性,可以使用外部类名.this.成员的方式,例如:718运行结果:Engine中的成员属性name=引擎大奔奔的发动机启动了!大奔奔跑起来了!请观察内部类 run() 方法中的语句:第一行语句调用了内部类自己的属性 name,而第二行调用了外部类 Car 的属性 name,第三行调用了外部类的方法 run(),并将外部类的属性 name 作为方法的参数。

Java 内部类

本节我们将介绍 Java 中的内部类。通过本节的学习,我们将了解到什么是内部类,内部类的分类和作用。在内部类的分类部分,我们将逐一学习各个类型的内部类如何定义,如何实例化以及各自的特点,要注意区分不同类型内部类的异同。有了这些基础知识之后,我们也会结合示例介绍为什么需要内部类。

2.2 静态内部类

2.2.1 定义静态内部类也称为嵌套类,是使用 static 关键字修饰的内部类。如下代码中定义了一个静态内部类:public class Car1 { // 静态内部类 static class Engine { public void run() { System.out.println("我是静态内部类的run()方法"); System.out.println("发动机启动了"); } }}2.2.2 实例化静态内部类的实例化,可以不依赖外部类的对象直接创建。我们在主方法中可以这样写:// 直接创建静态内部类对象Engine engine = new Engine();// 调用对象下run()方法engine.run();运行结果:我是静态内部类的run()方法发动机启动2.2.2 成员的访问在静态内部类中,只能直接访问外部类的静态成员。例如:719在 run() 方法中,打印的 name 属性就是外部类中所定义的静态属性 name。编译执行,将会输出:外部类的静态属性name对于内外部类存在同名属性的问题,同样遵循就近原则。这种情况下依然希望调用外部类的静态成员,可以使用外部类名.静态成员的方式来进行调用。这里不再一一举例。如果想要访问外部类的非静态属性,可以通过对象的方式调用,例如在 run() 方法中调用 Car1 的实例属性 brand:public void run() { // 实例化对象 Car1 car1 = new Car1(); System.out.println(car1.brand);}

2. 分类

Java 中的内部类可以分为 4 种:成员内部类、静态内部类、方法内部类和匿名内部类。接下来我们按照分类一一介绍。

1. 内部类和嵌套类

我们都知道在 Java 中表示内部类实际上就是将内部类嵌套在外部类中就声明了一个内部类,那么内部类就能访问外部类私有成员。//PageAdapterpublic abstract class PageAdapter { public abstract int getCount(); public abstract String getItem(int position);}//PageTestpackage com.imooc.test;import java.util.Arrays;import java.util.List;public class PageTest { private List<String> mData = Arrays.asList("1", "2", "3"); class TestPageAdapter extends PageAdapter {//在Java中只需要把内部类TestPageAdapter声明在外部类PageTest内部即可 @Override public int getCount() { return mData.size();//内部类即可以访问外部私有成员 } @Override public String getItem(int position) { return mData.get(position); } }}然而在 Kotlin 中不是这样的,对于 Kotlin 中在一个类内部再声明一个类我们把它称为嵌套类,嵌套类是不能直接访问外部类的私有成员的。package com.imooc.testclass PageTestKt { private val mData = listOf<String>("1", "2", "3") class TestPageAdapter : PageAdapter() { override fun getItem(position: Int): String { return mData[position]//由于无法访问mData,所以mData[position]编译报错 } override fun getCount(): Int { return mData.size//由于无法访问mData,所以mData.size编译报错 } }为什么 Kotlin 嵌套类不能直接访问外部类私有成员,我们可以把它反编译成 Java 代码就一目了然了:public final class PageTestKt { private final List mData = CollectionsKt.listOf(new String[]{"1", "2", "3"}); public static final class TestPageAdapter extends PageAdapter {//可以看到实际上Kotlin嵌套类就是一个static静态类,所以它肯定不能访问外部类PageTestKt私有成员mData @NotNull public String getItem(int position) { return ""; } public int getCount() { return 0; } }}可以看到实际上 Kotlin 嵌套类就是一个 static 静态类,所以它肯定不能访问外部类 PageTestKt 私有成员 mData。如果要在 Kotlin 声明一个内部类,应该怎么做呢?很简单只需要在嵌套类基础上加上一个 inner 关键字声明即可。package com.imooc.testclass PageTestKt { private val mData = listOf<String>("1", "2", "3") inner class TestPageAdapter : PageAdapter() {//inner关键字声明一个Kotlin中的内部类 override fun getItem(position: Int): String { return mData[position]//由于TestPageAdapter是PageTestKt的内部类,那么它就可以直接访问外部类私有属性mData } override fun getCount(): Int { return mData.size } }}为了进一步验证 inner class 实际上就是对应 Java 中的内部类,我们可以上述代码反编译成 Java 代码验证下:public final class PageTestKt { private final List mData = CollectionsKt.listOf(new String[]{"1", "2", "3"}); public final class TestPageAdapter extends PageAdapter {//可以看到TestPageAdapter确实是PageTestKt内部类,所以能直接访问外部类的私有成员mData @NotNull public String getItem(int position) { return (String)PageTestKt.this.mData.get(position); } public int getCount() { return PageTestKt.this.mData.size(); } }}总结一下:声明嵌套类时,在 Java 中是在外部类内部使用 static class A , 而在 Kotlin 中只需要在外部类内部使用 class A 即可;声明内部时,在 Java 中只需要在外部类内部使用 class A , 而在 Kotlin 中则需要在外部类内部使用 inner class A 。类 A 在类 B 内部声明在 Java 中在 Kotlin 中嵌套类 (不能直接访问外部类私有属性)static class Aclass A 内部类 (能直接访问外部类私有属性)class Ainner class A

2.3 方法内部类

2.3.1 定义方法内部类,是定义在方法中的内部类,也称局部内部类。如下是方法内部类的代码:720运行结果:方法内部类的run()方法发动机启动了如果我们想调用方法内部类的 run() 方法,必须在方法内对 Engine 类进行实例化,再去调用其 run() 方法,然后通过外部类调用自身方法的方式让内部类方法执行。2.3.2 特点与局部变量相同,局部内部类也有以下特点:方法内定义的局部内部类只能在方法内部使用;方法内不能定义静态成员;不能使用访问修饰符。也就是说,Car2.getEngine() 方法中的 Engine 内部类只能在其方法内部使用;并且不能出现 static 关键字;也不能出现任何的访问修饰符,例如把方法内部类 Engine 声明为 public 是不合法的。

4.创建函数式接口对象

在上面,我们自定义了一个函数式接口,那么如何创建它的对象实例呢?我们可以使用匿名内部类来创建该接口的对象,实例代码如下:/** * 测试创建函数式接口对象 * @author colorful@TaleLin */public class Test { public static void main(String[] args) { // 使用匿名内部类方式创建函数式接口 FunctionalInterfaceDemo functionalInterfaceDemo = new FunctionalInterfaceDemo() { @Override public void run() { System.out.println("匿名内部类方式创建函数式接口"); } }; functionalInterfaceDemo.run(); }}运行结果:匿名内部类方式创建函数式接口现在,我们学习了Lambda表达式,也可以使用Lambda表达式来创建,这种方法相较匿名内部类更加简洁,也更推荐这种做法。实例代码如下:/** * 测试创建函数式接口对象 * @author colorful@TaleLin */public class Test { public static void main(String[] args) { // 使用 Lambda 表达式方式创建函数式接口 FunctionalInterfaceDemo functionalInterfaceDemo = () -> System.out.println("Lambda 表达式方式创建函数式接口"); functionalInterfaceDemo.run(); }}运行结果:Lambda 表达式方式创建函数式接口当然,还有一种更笨的方法,写一个接口的实现类,通过实例化实现类来创建对象。由于比较简单,而且不符合我们学习函数式接口的初衷,这里就不再做实例演示了。

4.1 无参数无返回值

无参数无返回值,指的是接口实现类重写的方法是无参数无返回值的,我们一开始提到的 Runnable 接口匿名内部类就属于此类:1208运行结果:Hello, 匿名内部类Hello, Lambda

1. 什么是 Lambda 表达式

什么是 Lambda 表达式呢?维基百科是这样定义的:Lambda expression in computer programming, also called an anonymous function, is a defined function not bound to an identifier. ——维基百科翻译过来就是 Lambda 表达式也叫作匿名函数,是一种是未绑定标识符的函数定义,在编程语言中,匿名函数通常被称为 Lambda 抽象。换句话说, Lambda 表达式通常是在需要一个函数,但是又不想费神去命名一个函数的场合下使用。这种匿名函数,在 JDK 8 之前是通过 Java 的匿名内部类来实现,从 Java 8 开始则引入了 Lambda 表达式——一种紧凑的、传递行为的方式。

3.1 声明内部类

通过新增内部类的形式实现OnClickListener接口,代码如下:package com.emercy.myapplication;import android.app.Activity;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.Toast;public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button button = findViewById(R.id.bottom); button.setOnClickListener(new EventHandle()); } private class EventHandle implements View.OnClickListener { @Override public void onClick(View v) { Toast.makeText(MainActivity.this, "Button被点击了", Toast.LENGTH_LONG).show(); } }}

1.3 内部静态类方式

这次我们先看代码:public class SingletonFour { private SingletonFour() { } public static SingletonFour getInstance() { return SingletonHolder.singletonFour; } private static class SingletonHolder{ private static final SingletonFour singletonFour = new SingletonFour(); }}代码中增加了内部静态类 SingletonHolder,内部有一个SingletonFour的实例,并且也是类级别的。那这种方式是饿汉式还是懒汉式?看起来像是饿汉式,因为实例化也是在类初实话的时候进行的。但如果是饿汉式,为什么还要兜这个圈?其实这是懒汉式。因为内部静态类是现在第一次使用的时候才会去初始化。所以SingletonHolder最初并未被初始化。当第一次执行 return SingletonHolder.singletonFour 时,才会去初始化SingletonHolder类,从而实例化SingletonFour。这种方式利用类加载的机制达到了双重检查模式的效果,而代码更为简洁。

Kotlin 数据类、密封类、内部类和嵌套类

从这篇文章我们一起来看下 Kotlin 几个比较特殊的类,其中数据类 (data class) 和密封类 (sealed class) 是 Java 中不存在的,所以下面会一一介绍它们如何使用、使用场景、它们解决了哪些问题以及语法糖背后的原理。此外在 Kotlin 中存在嵌套类和内部类,需要注意的是 Kotlin 的嵌套类不是内部类,这点和 Java 是不一样的,所以内部嵌套类不能访问外部类实例。在 Kotlin 中内部类声明需要单独使用 inner 关键字声明。

2. 为什么需要 Lambda 表达式

在 Java 8 之前,编写一个匿名内部类的代码很冗长、可读性很差,请查看如下实例:1206运行结果:Hello, 匿名内部类Lambda 表达式的应用则使代码变得更加紧凑,可读性增强;另外,Lambda 表达式使并行操作大集合变得很方便,可以充分发挥多核 CPU 的优势,更易于为多核处理器编写代码。下面我们使用 Lambda 表达式改写上面的代码。如果你使用 IDEA 编写代码,可以直接一键智能修改,首先,将鼠标光标移动到灰色的 new Runnable() 代码处,此时会弹出一个提示框,提示可以使用 Lambda 表达式替换,点击 Replace with lambda 按钮即可完成代码替换,截图如下:修改后实例如下:1207运行结果:Hello, 匿名内部类通过对比,使用 lambda 表达式实现了与匿名内部类同样的功能,并且仅仅用了一行代码,代码变得更加简洁了。对于这样的写法,你可能还非常疑惑,但别担心,我们马上就来详细讲解基础语法。

4. 小结

本小节,我们知道了什么是内部类,也知道了在 Java 中有四种内部类:成员内部类、静态内部类、方法内部类和匿名内部类。对于它们的定义和调用也做了详细讲解,理解内部类的作用是使用好内部类的关键。

2. 如何使用对象表达式

使用对象表达式很简单,只需要像声明类一样声明即可,只不过把 class 关键字换成了 object. 声明格式: object + 对象名 + : + 要实现 / 继承的接口或抽象类 (用做单例模式场景) 和 object + : + 要实现 / 继承的接口或抽象类 (用做匿名内部类场景)://1、用做单例模式形式object KSingleton : Serializable {//object关键字 + 对象名(KSingleton) + : + 要实现的接口(Serializable) fun doSomething() { println("do some thing") } private fun readResolve(): Any { return KSingleton }}//2、用做匿名内部类形式mButton.setOnClickListener(object: OnClickListener{//object关键字 + : + 要实现的接口(OnClickListener) override fun onClick() { //do logic }})

3. 对象表达式使用场景

在 Kotlin 中 object 对象表达式使用场景主要就是单例模式和替代匿名内部类场景。

1. 从外部迭代到内部迭代

对于一个集合迭代是我们常用的一种操作,通过迭代我们可以处理返回每一个操作。常用的就是 for 循环了。我们来看个例子:1273输出: 2这里我们统计数组 numbers 中大于 5 的元素的个数,我们通过 for 循环对 numbers 数组进行迭代,随后对每一个元素进行比较。这个调用过程如下:在这个过程中,编译器首先会调用 List 的 iterator() 方法产生一个 Iterator 对象来控制迭代过程,这个过程我们称之为 外部迭代。 在这个过程中会显示调用 Iterator 对象的 hasNext() 和 next() 方法来完成迭代。这样的外部迭代有什么问题呢?Tips: 对于循环中不同操作难以抽象。比如我们前面的例子,假设我们要对大于 5 小于 5 和等于 5 的元素分别进行统计,那么我们所有的逻辑都要写在里面,并且只有通过阅读里面的逻辑代码才能理解其意图,这样的代码可阅读性是不是和 Lambda 表达式的可阅读性有着天壤之别呢?Java 8 中提供了另一种通过 Stream 流来实现 内部迭代 的方法。我们先来看具体的例子:1274输出: 2在这个例子中,我们调用 stream() 方法获取到 Stream 对象,然后调用该对象的 filter() 方法对元素进行过滤,最后通过 count() 方法来计算过滤后的 Stream 对象中包含多少个元素对象。与外部迭代不同,内部迭代并不是返回控制对象 Iterator, 而是返回内部迭代中的相应接口 Stream。进而把对集合的复杂逻辑操作变成了明确的构建操作。在这个例子中,通过内部迭代,我们把整个过程被拆分成两步:找到大于 5 的元素;统计这些元素的个数。这样一来我们代码的可读性是不是大大提升了呢?

首页上一页1234567下一页尾页
直播
查看课程详情
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号