写点什么

2020 年 6 月 7 日 接口、lambda 表达式与内部类

发布于: 2020 年 06 月 07 日

接口技术主要是用来描述类具有什么功能,并不给出每个功能的具体实现,一个类可以实现一个或多个接口,并在需要接口的地方,随时使用实现了相应接口的对象。

lambda表达式,一种可以表示在将来某个时间点执行的代码块的简洁方法。

内部类,定义在另一个类的内部,其中方法可以访问包含它们外部类的域,内部类主要是用于设计具有相互协作关系的结合。

代理:这是一种实现任意接口的对象

1、接口

1、接口概念

接口不是类,,而是对类的一组需求描述,这些类需要遵从接口描述的统一格式进行定义。

接口中所有的方法都自动地属于public,因此,在接口中申明方法时,不必提供关键字public。

提供实例域和方法实现的任务,应该由实现接口的那个类来完成。因此可以将接口看成没有实例域的抽象类。

实现一个接口的步骤:

1、对类声明为实现给定的接口

2、对接口中的所有方法进行定义

2、接口特性

1、接口不是类,尤其不能使用new运算符实例化一个接口

2、接口变量必须引用实现了接口的类对象

3、可以使用instance检查一个对象是否实现了某个特定的接口。

4、接口中的域将被自动设为public static final。

3、接口与抽象类

1、每个类只能扩展一个类(只能继承一个类)

2、一个类可以实现多个接口

4、静态方法

接口中可以增加静态方法

5、默认方法

在接口中将一个方法用default修饰符进行修饰。

6、解决默认方法冲突

如果先在一个接口中将一个方法定义为默认方法,然后又在超类或另一个接口中定义了同样的方法,Java的规则是:

1、超类优先。如果超类提供了一个具体方法,同名而且具有相同参数类型的默认方法会被忽略。

2、接口冲突,如果一个超接口提供了一个默认方法,另一个接口提供了一个同名而且参数类型相同的方法,必须覆盖方法来解决这个冲突。

3、如果一个类扩展了一个超类,同时实现了一个接口,并从超类和接口继承了相同的方法。在这种情况下,只会考虑超类方法,接口的默认方法都会被忽略,这就是“类优先”规则。

2、接口示例

1、接口与回调

回调是一种常见的程序设计模式。在这种模式中,可以指出某个特定事件发生时应该采取什么动作。

例如Java中的定时器

ActionListener listener = new TimePrinter();
Timer t = new Timer (10000,listener);

Timer构造器的第一个参数是发出通告的时间间隔,他的单位是毫秒。第二个参数是监听器对象

2、Comparator接口

如果对一个对象数组排序:

1、这些对象是实现了Comparable接口的类的实例

2、利用Arrays.sort(数组,比较器)

3、对象克隆

要了解克隆的具体含义,先来回忆为一个包含对象引用的变量建立副本时会发生什么。注意:原变量和副本都是同一个对象的引用。这说明任何一个变量改变时都会影响另一个变量。

如果希望copy一个新对象,它的初始状态与original相同,但是之后它们各自会有自己不同的状态,就需要使用clone()方法。

注意:clone方法是Object的一个protected方法,说明你的代码不能直接调用和这个方法,只有Employee类可以克隆Employee对象。默认的克隆操作是“浅拷贝”,并没有克隆对象中引用的其他对象。

通常子对象都是可变的,必须重新定义clone方法来建立一个深拷贝,同时克隆所有子对象

对于每一个类需要确定:

1、默认的clone方法是否满足需求;

2、是否可以在改变的子对象上调用clone来修补模拟人生的clone方法;

3、是否不该使用clone

即使默认的clone方法能够满足需求,还是需要实现Cloneable接口,将clone重新定义为public,再调用super.clone();

3、lambda表达式

1、为什么引入lambda表达式

lambda表达式是一个可传递的代码块,可以在以后执行一次或多次。

2、lambda表达式的语法

案例:(String first,String second )-> first.length()-second.length()

这就是一个lambda表达式。lambda表达式就是一个代码块,以及必须传入代码的变量规范。

参数,箭头(—>) 以及一个表达式

如果代码要完成的计算无法放在一个表达式中,就像写方法一样,把这些代码放在{}中,并包含显示的return语句。

3、函数式接口

对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个lambda表达式。这种接口称为函数式接口。

例如:

Arrays.sort(words, (first,second) ->first.length()-second.length() ).

在底层,Arrays.sort方法会接收实现了Comparable<String>的某个类的对象,在这个对象上调用compare方法会执行这个lambda表达式的体。

最好把lambda表达式看成一个函数而不是一个对象。

lambda表达式可以转换为接口,这一点让lambda表达式很具有吸引力,具体的语法很简短。

案例:

Timer t = new Timer (1000 , event -> {
System.out.println(new Date());
})

4、方法引用

案例:

Timer t =new Timer(1000,event -> System.out.println(event));

等价于

Timer t = new Timer(1000,System.out::println);

表达式System.out::println是一个方法引用,他等价于上面的lambda表达式。

方法引用需要用 :: 操作符分割方法名与对象或类名。主要有三种情况:

  • object::instanceMethod

  • Class::staticMethod

  • Class::instanceMethod

前两种情况,方法引用等价于提供方法参数的lambda表达式

对于第三种情况,第一个参数会成为方法的目标,例如:String::compareToIgnoreCase等同于(x,y)->x.compareToIgnoreCase(y);

5、构造器引用

构造器引用与方法引用很类似,只不过方法名为new。

例如:Person::new是Person构造器的一个引用

可以用数组类型建立构造器引用

例如:int[]::new 是一个构造器引用,它有一个参数:即数组长度。这等价于lambda表达式 x-> new int[x]。

6、变量作用域

lambda表达式有三个部分:

1、一个代码块

2、参数

3、自由变量值,这是指非参数而且不在代码中定义的变量

如果希望在咋lambda表达式中访问外围方法或类中的变量,需要注意:

1、在lambda表达式中,只能引用 值不会改变的变量,如果在lambda表达式中改变变量,并发执行多个操作时就会不安全。

2、如果在lambda表达式中引用变量,而这个变量可能在外部改变,这也是不合法的

3、lambda表达式中捕获的变量必须实际上是最终变量,实际上最终变量指的是这个变量初始化之后就不会在为它赋新值。

在一个lambda表达式中使用this关键字时,是指创建这个lambda表达式的方法的this参数。

7、处理lambda表达式

使用lambda表达式的重点是延迟执行。

使用lambda表达式的场景:

1、在一个单独的线程中运行代码

2、多次运行代码

3、在算法的适当位置运行代码

4、发生某种行为时执行代码

5、只在必要时运行代码

要接收一个lambda表达式,需要选择一个函数式接口

4、内部类

内部类是定义在另一个类中的类。

使用内部类的原因:

  • 内部类可以访问该类定义所在的作用域中的数据,包括私有数据

  • 内部类可以对同一个包中的其他类隐藏起来

  • 当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷

嵌套是一种类之间的关系,而不是对象之间的关系。嵌套有两个好处:命名控制和访问控制。

1、使用内部类访问对象状态

内部类既可以访问自身的数据域,也可以访问创建它的外围类对象的数据域。

内部类的对象总有一个隐式引用,它指向了创建它的外部类对象。这个引用在内部类中定义是不可见的。

2、内部类的特殊语法

事实上,使用外围类引用的正规语法还要复杂一些。

表达式:OuterClass.this 表示外围类引用

例如:if(TalkingClock.this.beep) Toolkit.beep()

反过来,可以采用下列语法格式更加明确地编写内部对象的构造器:

outerObject.new InnerClass(construction parameters);

例如:ActionListener listener = this.new TimePrinter();

需要注意的是:在外围类的作用域之外,可以这样引用内部类:

OuterClass.InnerClass

内部类中声明的所有静态域都必须是final,原因很简单,我们希望一个静态域只有一个实例。内部类不能有static方法

3、内部类是否有用、必要和安全

尽量减少使用内部类

4、局部内部类

如果某个类只在方法中调用了一次,那么就可以在一个 方法中定义局部类。

局部类不能用public或private访问说明符进行声明。它的作用域被限定在声明这个局部类的块中。

局部类有一个优势,即对外部的世界可以完全的隐藏起来

5、由外部方法访问变量

局部类还有一个特点。它们不仅能访问包含它们的外部类,还可以访问局部变量。不过那些局部变量必须事实上为final,这说明它们一旦赋值就绝对不会改变。

6、匿名内部类

将局部内部类在深入一步,假如只创建这个类的一个对象,就不必命名了。这种类被称为匿名内部类。

例如:

public void start(int interval ,boolean beep){
ActionListener listener = new ActionListener()
{
public void actionPerformed(ActionEvent event){
System.out.println(new Date());
}
}
}

它的含义是创建一个实现ActionListener接口的类的对象,需要实现的方法actionPerformed定义在括号{}内。

由于构造器的名字必须与类名相同,而匿名类没有类名,所以匿名类不能有构造器。取而代之的是,将构造器参数传递给超类构造器。

7、静态内部类

有时候,使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用外围类对象。为此,可以将内部类声明为static,以便取消引用。

当然,只有内部类可以声明为static,静态内部类的对象除了没有对生成它的外围对象的引用特权外,与其他所有内部类完全一样。

在内部类不要访问外围类对象的时候,应该使用静态内部类。

5、代理

利用代理可以在运行时创建一个实现了一组给定接口的新类。这种功能只有在编译时无法确定需要实现哪个接口时才有必要使用。

1、何时使用代理

假设有一个表示接口的Class对象,它确切类型再编译时无法知道。要想构造一个实现这些接口的类,就需要使用newInstance方法或反射找出这个类的构造器。但是不能实例化一个接口,需要再程序运行时定义一个新类。

为了解决这个问题,有些程序将会生成代码,将这些代码放置再一个文件中;调用编译器,然后再加载结果类文件。而代理机制是一种更好的解决方案,代理类可以再运行时创建全新的类。这样的代理能够实现指定的接口。尤其是,它具有以下方法:

1、指定接口所需要的全部方法

2、Object类中的全部方法,例如,toString(),equals()

然而,不能在运行时定义这些方法的新代码。而是要提供一个调用处理器。调用处理器是实现了InvocationHandler接口的对象。

无论何时调用代理对象的方法,调用处理器的invoke方法都会被调用,并向其传递Method对象和原始的参数。

在本书的案例中,由于数组中填充了代理对象,所以compareTo调用了TranceHander类中的invoke方法,这个方法打印出来方法名和参数。之后用包装好的Integer对象调用compareTo。

2、创建代理对象

要想创建一个带来对象,需要使用Proxy类的newProxyIntance方法。这个方法有三个参数:

  • 一个类加载器

  • 一个Class对象数组,每个元素都是需要实现的接口

  • 一个调用处理器

3、代理类的特征

代理类是在程序运行过程中创建的。然而一旦创建就变成了常规类,与虚拟机中的任何其他类没有区别。

所有的代理类都扩展于Proxy类。一个代理类只有一个实例域——调用处理器,它定义在Proxy的超类中。为了履行代理对象职责,所需要的任何附加参数都必须存储在调用处理器中。

代理类一定是public 和final。如果代理类实现的所有接口都是public,代理类就不属于某个特定的包;否则,所有非共有的接口都必须属于同一个包,同时代理类也属于这个包。

发布于: 2020 年 06 月 07 日阅读数: 63
用户头像

还未添加个人签名 2020.05.18 加入

还未添加个人简介

评论

发布
暂无评论
2020年6月7日    接口、lambda表达式与内部类