Java核心技术-接口、lambda表达式与内部类

本章将主要介绍:

接口技术:主要用来描述类具有什么功能,而并不给出每个功能的具体实现。一个类可以实现一个或多个接口。

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

内部类机制:内部类定义在另一个类的内部,其中的方法可以访问包含它们的外部类的域。

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

1 接口

1.1 接口的概念

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

“如果类遵从某个特定的接口,那么就履行这项服务”:

Arrays类中的sort方法承诺可以对对象数组进行排序,但要求满足下列前提:对象所属的类必须实现了Comparable接口。

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

接口中可以定义常量,但绝不能含有实例域,Java SE 8之后,可以在接口中提供简单的方法(不能引用实例域)

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

为了让类实现一个接口,需要一下步骤:

1.将类声明为实现给定的接口(implements关键字)。

2.对接口中的所有方法进行定义(实现接口时,必须把方法声明为public)。

不直接提供compareTo方法而实现接口的原因:

Java是一种强类型语言,在调用方法的时候,编译器将检查这个方法是否存在。而实现了Compareble接口的类必定存在这个方法。

1.2 接口的特性

接口不是类,所以不能用new实例化一个接口,然而,却能声明接口的变量,接口变量必须引用实现了接口的类对象。

使用instanceof检查一个对象是否属于某个特定的类,使用instance检查一个对象是否实现了某个特定的接口

接口中的常量被自动设为public static final。

尽管每个类只能够拥有一个超类,但却可以实现多个接口。这就为定义类的行为提供了极大的灵活性。(使用逗号将实现的每个接口分开)

1.3 接口与抽象类

接口与抽象类的区别:

使用抽象类表示通用属性存在每个类只能扩展与一个类的问题,但每个类可以实现多个接口。

1.4 静态方法

在Java SE 8中,允许在接口中增加静态方法。

目前为止,通常的做法都是将静态方法放在伴随类中。在标准库中,你会看到成对出现的接口和使用工具类,如Collection/Collections或Path/Paths。

这个伴随类实现了相应接口的部分或全部方法。

现在只要在实现接口时,加入静态方法,就不再需要再为使用工具方法另外提供一个伴随类。

1.5 默认方法

可以为接口方法提供一个默认实现。必须用default修饰符标记这样的一个方法。

实现这个接口的类可以选择性的覆盖接口中的默认方法。

默认方法的一个重要用法是——接口演化(给原先设计的接口添加默认方法,不需要重新编译之前实现接口的所有类且这些类调用新添加的方法时将调用接口中的实现)

1.6 解决默认方法冲突

考虑这样的情况:如果先在一个接口中将一个方法定义为默认方法,然后又在超类或另一个接口中定义了同样的方法,会发生什么情况?(二义性)

Java的处理规则如下:

1.超类优先(考虑到接口新增默认方法的兼容性)

2.接口冲突:考虑三种情况

1.两个接口中都有这个方法的默认实现——必须覆盖这个方法来解决冲突(可以选择两个冲突方法中任意一个提供的默认实现)

2.两个接口中有一个有这个方法的默认实现——必须覆盖这个方法来解决冲突(可以选择两个冲突方法中提供默认实现的那一个)

3.两个接口中都没有这个方法的默认实现——可以实现这个方法,也可以不实现这个方法。如果不实现,这个类本身就是抽象的


2 接口示例

2.1 接口与回调

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

ActionListener listen=new TimePrinter();
Timer time=new Timer(10000,listen)

2.2 Comparator接口

考虑这样的情况:

一个类本身已经继承Comparable接口,有一个按字典顺序排序的comparaTo方法。现在我们希望按长度递增的顺序对这个类进行排序。而不是按字典排序。

解决这种情况,考虑使用一个数组和一个比较器作为参数,比较器是实现了Comparator接口的类的实例。

Comparator<String> comp=new LengthComparator();
if(comp.compare(words[i],words[j])>0...

将这个调用与words[i].compareTo(words[j])做比较。这个compare方法要在比较器对象上调用,而不是在字符串本身上调用。

Comparator与Comparable的区别:

1.使用Comparator进行比较时需要先建立一个Comparator具体实例

2.Comparator调用对象是比较器,comparable调用对象是字符串本身

3.利用Comparator可以实现更多样的比较方式

2.3 对象克隆

本节讨论Cloneable接口,这个接口指示一个类提供了一个安全的clone方法

考虑这样的情况:

当我们为一个包含对象引用的变量建立副本时,原变量和副本都是同一个对象的引用。这说明任何一个变量的改变都会影响另一个变量。

Employee origina=new Employee("john Public",50000);
Employee copy=original;
copy.raiseSalary(10);

如果希望copy是一个新对象,它的初始状态与original相同,但是之后它们各自会有自己不同的状态,这种情况下就可以使用clone方法。

Employee copy=original.clone();
copy.raiseSalary(10);

默认的克隆操作是浅拷贝,并没有克隆对象中引用的其他对象。

如果原对象中的所有子对象都是不可变的,那么使用浅拷贝是安全的。

如果子对象属于一个不可变类,就需要使用深拷贝。

对于每一个类,需要确定:

1.默认的clone方法是否满足要求;

2.是否可以在可变的子对象上调用clone来修补默认的clone方法;

如果选择第一项或第二项,类必须:

1.实现Cloneable接口;

2.重新定义clone方法,并指定public访问修饰符。

Object中的clone方法声明为protected,因此要在实现类中重新定义为public,才能在其他类中调用实例化的这个实现类对象的clone方法。

Cloneable接口是标记接口,标记接口不包含任何方法;它唯一的作用就是允许在类型查询中使用instanceof

如果在一个对象上调用clone,但这个对象的类并没有实现Cloneable接口,Object类的clone方法就会抛出一个CloneNotSupportedException。

所有数组类型都有一个public的clone方法


3 lambda表达式

采用一种简洁的语法定义代码块

3.1 为什么引入lambda表达式

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

比如传递一个实现ActionListener接口的类实例到定时器或实现Comparator接口的类实例到sort方法。

3.2 lambda表达式的语法

(String first,String second)
    ->first.length()-second.length()

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

即使lambda表达式没有参数,仍然要提供空括号

()->{for(int i=100;i>=0;i--)System.out.println(i);}

如果可以推导出一个lambda表达式的参数类型,则可以忽略其类型

Comparator<String> comp
    =(first,second)
        ->first.length()-second.length();

如果方法只有一个参数,而且这个参数的类型可以推导得出,那么甚至还可以省略小括号:

ActionListener listener=event->
    System.out.println("The time is"+new Date());

无需指定lambda表达式的返回类型

如果lambda表达式只在某些分支返回一个值,而在另外一些分支不返回值,这是不合法的。

(int x)->{if(x>=0)return 1;}

3.3 函数式接口

概念:只有一个抽象方法的接口称为函数式接口

可以用lambda表达式替换函数式接口

例如:Comparator就是只有一个方法的接口,所以可以提供一个lambda表达式

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

在底层,Arrays.sort方法会接收实现了Comparator<String>的某个类的对象。在这个对象上调用compare方法会执行这个lambda表达式的体。(要接受lambda表达式可以传递到函数式接口)

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

实际上,在Java中,对lambda表达式能做的也只是能转换为函数式接口。

3.4 方法引用

要用::操作符分隔方法名与对象或类名,主要有三种情况:

*object::instanceMethod

*Class::staticMethod

*Class::instanceMethod

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

表达式System.out::println是一个方法引用,它等价于lambda表达式x->System.out.println(x)

表达式Math::pow是一个方法引用,它等价于lambda表达式(x,y)->Math.pow(x,y)

对于第三种情况,第一个参数会成为方法的目标:

表达式String:;compareToIgnoreCase是一个方法引用,它等价于(x,y)->x.compareToIgnoreCase(y)

类似于lambda表达式,方法引用不能独立存在,总是会转换为函数式接口的实例

可以在方法引用中使用this和super参数

this::equals等同于x->this,equals(x)

3.5 构造器引用

构造器引用与方法引用很类似,只不过方法名为new。例如,Person::new是Person构造器的一个引用。具体调用哪一个构造器取决于上下文。

可以用数组类型建立构造器引用,例如,int[]::new是一个构造器引用,等价于lambda表达式x->new int[x]

Java有一个限制,无法构造泛型类型T的数组。数组构造器引用对于克服这个限制很有用。

3.6 变量作用域

通常,你可能希望能够在lambda表达式中访问外围方法或类中的变量。

例子:

public static void repeatMessage(String text,int delay)
{
    ActionListener listener=event->
        {
            System.out.priintln(text);
            Toolkit.getDefaultToolkit().beep();
        };
    new Timer(delay,listener).start();
}

lambda表达式的代码可能会在repeatMessage调用返回很久以后才运行,而那时这个参数变量已经不存在了。如何保留text变量呢?

一个lambda表达式由下面三部分组成:

1.一个代码块;

2.参数;

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

在Java中,lambda表达式就是闭包。

原文地址:https://www.cnblogs.com/dyj-blog/p/8979635.html

时间: 2024-10-15 18:10:58

Java核心技术-接口、lambda表达式与内部类的相关文章

Java 终于有 Lambda 表达式啦~Java 8 语言变化&mdash;&mdash;Lambda 表达式和接口类更改【转载】

原文地址 en cn 下载 Demo Java? 8 包含一些重要的新的语言功能,为您提供了构建程序的更简单方式.Lambda 表达式 为内联代码块定义一种新语法,其灵活性与匿名内部类一样,但样板文件要少得多.接口更改使得接口可以添加到现有接口中,同时又不会破坏与现有代码的兼容性.本文将了解这些更改是如何协同工作的. Java 8 的最大变化在于添加了对 lambda 表达式 的支持.Lambda 表达式是可按引用传递的代码块.类似于一些其他编程语言中的闭包:它们是实现某项功能的代码,可接受一个

java 8 中lambda表达式学习

转自 http://blog.csdn.net/renfufei/article/details/24600507 http://www.jdon.com/idea/java/10-example-of-lambda-expressions-in-java8.html Lambda表达式的语法基本语法:(parameters) -> expression或(parameters) ->{ statements; } 下面是Java lambda表达式的简单例子: // 1. 不需要参数,返回值

在Android中使用Java 8的lambda表达式

译自http://www.coshx.com/blog/2015/03/23/use-java-8s-lambda-functions-in-android/ 在Android中使用Java 8的lambda表达式 作为一名Java开发者,或许你时常因为信息的封闭性而产生许多的困扰.幸运的是:Java's 8th version introduced lambda functions给我们带来了好消息:然而,这咩有什么卵用,在android上面,我们仍旧只能使用Java7. 那么现在情况如何?哈

[Java 8] 使用Lambda表达式进行设计

使用Lambda表达式进行设计 在前面的几篇文章中,我们已经见识到了Lambda表达式是如何让代码变的更加紧凑和简洁的. 这一篇文章主要会介绍Lambda表达式如何改变程序的设计,如何让程序变的更加轻量级和简洁.如何让接口的使用变得更加流畅和直观. 使用Lambda表达式来实现策略模式 假设现在有一个Asset类型是这样的: public class Asset { public enum AssetType { BOND, STOCK }; private final AssetType ty

【转】Java 8十个lambda表达式案例

1. 实现Runnable线程案例 使用() -> {} 替代匿名类: //Before Java 8: new Thread(new Runnable() { @Override public void run() { System.out.println("Before Java8 "); } }).start(); //Java 8 way: new Thread( () -> System.out.println("In Java8!") ).s

[Java 8] (8) Lambda表达式对递归的优化(上) - 使用尾递归 .

递归优化 很多算法都依赖于递归,典型的比如分治法(Divide-and-Conquer).但是普通的递归算法在处理规模较大的问题时,常常会出现StackOverflowError.处理这个问题,我们可以使用一种叫做尾调用(Tail-Call Optimization)的技术来对递归进行优化.同时,还可以通过暂存子问题的结果来避免对子问题的重复求解,这个优化方法叫做备忘录(Memoization). 本文首先对尾递归进行介绍,下一票文章中会对备忘录模式进行介绍. 使用尾调用优化 当递归算法应用于大

[Java 8] (9) Lambda表达式对递归的优化(下) - 使用备忘录模式(Memoization Pattern) .

使用备忘录模式(Memoization Pattern)提高性能 这个模式说白了,就是将需要进行大量计算的结果缓存起来,然后在下次需要的时候直接取得就好了.因此,底层只需要使用一个Map就够了. 但是需要注意的是,只有一组参数对应得到的是同一个值时,该模式才有用武之地. 在很多算法中,典型的比如分治法,动态规划(Dynamic Programming)等算法中,这个模式运用的十分广泛. 以动态规划来说,动态规划在求最优解的过程中,会将原有任务分解成若干个子任务,而这些子任务势必还会将自身分解成更

Java里的lambda表达式

在上一篇文章<Java里的函数式接口>介绍了关于函数式接口的内容,那么本文基于函数式接口来继续学习lambda表达式. 语法结构 Runnable runnable = () -> System.out.println("Runnable Instance"); 这种使用箭头符号->分开参数列表和方法体的语法就是lambda表达式.在Java 里没有和lambda 表达式对应的类定义或者代码库,lambda表达式总是在定义后赋值给某个函数式接口类型变量. 一个

Java8函数式接口/Lambda表达式/接口默认方法/接口静态方法/接口冲突方法重写/lambda表达式指定泛型类型等

一:函数式接口 1.函数式接口的概念就是此接口必须有且只能有一个抽象方法,可以通过@FunctionalInterface来显示规定(类似@Override),但是没有此注解的但是只有一个抽象方法的接口也是函数式接口:(接口也和类一样有包访问权限,但是内部的方法则默认是public) @FunctionalInterface public interface IFoo{ void print(); }  // 就是一个最简单的函数式接口,但是如果再有个如void print2()抽象方法则这个接