《On Java 8》笔记 2
第十一章 内部类
Java 8 的 Lambda 表达式和方法引用减少了编写内部类的需求
外部类可以提供一个方法返回一个指向内部类的引用
链接外部类
内部类还拥有其外部类的所有元素的访问权
使用 .this 和 .new
外部类对象的引用:外部类类名.this
创建其某个内部类的对象:外部对象.new
非静态内部类情况下,在拥有外部类对象之前是不可能创建内部类对象的。这是因为内部类对象会暗暗地连接到建它的外部类对象上
内部类与向上转型
普通(非内部)类的访问权限不能被设为 private 或者 protected;他们只能设置为 public 或 package 访问权限,内部类都可以
非静态内部类创建方式:
- 内部类public方法返回内部类对象
- 外部对象.new 创建内部对象(构造器private就无法创建对象)(内部类Private也无法通过.new 创建对象)
interface Destination {
String readLabel();
}
interface Contents {
int value();
}
public class Parcel4 {
// private 外部对象不能直接new
private class PContents implements Contents {
private int i = 11;
@Override
public int value() {
return i;
}
}
protected final class PDestination implements Destination {
private String label;
public PDestination(String whereTo) {
label = whereTo;
}
public String readLabel() {
return label;
}
}
// 提供获取内部类的方法 用接口接对象 向上转型
public Destination destination(String s) {
return new PDestination(s);
}
// 提供获取内部类的方法 用接口接对象 向上转型
public Contents contents() {
return new PContents();
}
}
class Test {
public static void main(String[] args) {
Parcel4 p = new Parcel4();
Contents contents = p.contents();
Destination destination = p.destination("test");
// PContents 构造器私有
// Parcel4.PContents pContents = p.new PContents();
}
}
private 内部类给类的设计者提供了一种途径,通过这种方式可以完全阻止任何依赖于类型的编码,并且完全隐藏了实现的细节
内部类方法和作用域
在方法的作用域内的类:局部内部类
在对应方法之外不能访问
通过局部内部类创建一个接口的实现类,向上转型return 接口类型
public class Parcel5 {
public Destination destination(String s) {
final class PDestination implements Destination {
private String label;
private PDestination(String whereTo) {
label = whereTo;
}
@Override
public String readLabel() { return label; }
}
return new PDestination(s);
}
public static void main(String[] args) {
Parcel5 p = new Parcel5();
Destination d = p.destination("Tasmania");
}
}
任意的作用域内嵌入一个内部类,超出作用域就失效
public class Parcel6 {
private void internalTracking(boolean b) {
if (b) {
class TrackingSlip {
private String id;
public TrackingSlip(String id) {
this.id = id;
}
public String getSlip() {
return id;
}
}
TrackingSlip ts = new TrackingSlip("slip");
String s = ts.getSlip();
System.out.println(s);
}
// !无效超出作用域
// TrackingSlip ts = new TrackingSlip("slip");
}
public void track() {
internalTracking(true);
}
public static void main(String[] args) {
Parcel6 p = new Parcel6();
p.track();
}
}
匿名内部类
“创建一个继承自 Contents 的匿名类的对象。”通过 new 表达式返回的引用被自动向上转型为对 Contents 的引用
public class Parcel8 {
public Wrapping wrapping(int x) {
return new Wrapping(x) {
@Override
public int value() {
return super.value() * 47;
}
};
}
public static void main(String[] args) {
Parcel8 p = new Parcel8();
Wrapping w = p.wrapping(10);
System.out.println(w.value());
}
}
class Wrapping {
private int i;
public Wrapping(int i) {
this.i = i;
}
public int value() {
return i;
}
}
如果在定义一个匿名内部类时,它要使用一个外部环境(在本匿名内部类之外定义)对象,那么编译器会要求其(该对象)参数引用是 final 或者是 “effectively final”(也就是说,该参数在初始化后不能被重新赋值,所以可以当作 final)(如果不会在匿名类内部被直接使用,可以不定义为final,即使不加 final, Java 8 的编译器也会为我们自动加上 final,以保证数据的一致性)
匿名内部类要么继承类,要么实现接口,但是不能两者兼备。而且如果是实现接口,也只能实现一个接口
嵌套类
不需要内部类对象与其外部类对象之间有联系,可以将内部类声明为 static,这通常称为嵌套类
- 创建嵌套类的对象时,不需要其外部类的对象。
- 不能从嵌套类的对象中访问非静态的外部类对象。
普通的内部类不能有 static 数据和 static 字段,也不能包含嵌套类
接口内部的类
接口中的任何类都自动地是 public 和 static,不写也是
创建某些公共代码,使得它们可以被某个接口的所有不同实现所共用,那么使用接口内部的嵌套类会显得很方便
可以使用嵌套类放置测试代码,会生成了一个独立的类 TestBed$Tester,打包产品之前删除即可
public class TestBed {
public void f() {
System.out.println("f()");
}
public static class Tester {
public static void main(String[] args) {
new TestBed().f();
}
}
}
从多层嵌套类中访问外部类的成员
多层嵌套类能透明地访问所有它所嵌入的外部类的所有成员
为什么需要内部类
一般说来,内部类继承自某个类或实现某个接口,内部类的代码操作创建它的外部类的对象。所以可以认为内部类提供了某种进入其外部类的窗口
使用内部类最吸引人的原因是:每个内部类都能独立地继承自一个(接口的)实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响
接口解决了部分问题,而内部类有效地实现了“多重继承”。也就是说,内部类允许继承多个非接口类型(类或抽象类)
class D {
}
abstract class E {
}
class Z extends D {
E makeE() {
return new E() {
};
}
}
public class MultiImplementation {
static void takeD(D d) {
}
static void takeE(E e) {
}
public static void main(String[] args) {
Z z = new Z();
takeD(z);
takeE(z.makeE());
}
}
内部类的一些特性:
- 内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外部类对象的信息相互独立
- 在单个外部类中,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类。 稍后就会展示一个这样的例子
- 创建内部类对象的时刻并不依赖于外部类对象的创建
- 内部类并没有令人迷惑的"is-a”关系,它就是一个独立的实体
闭包与回调
闭包(closure)是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域
在 Java 8 之前,内部类是实现闭包的唯一方式。在 Java 8 中,我们可以使用 lambda 表达式来实现闭包行为
interface Incrementable {
void increment();
}
// Callee别召唤者
class Callee1 implements Incrementable {
private int i = 0;
@Override
public void increment() {
i++;
System.out.println(i);
}
}
class MyIncrement {
public void increment() {
System.out.println("Outer operation");
}
static void f(MyIncrement mi) {
mi.increment();
}
}
class Callee2 extends MyIncrement {
private int i = 0;
@Override
public void increment() {
super.increment();
i++;
System.out.println(i);
}
// 内部类 private
private class Closure implements Incrementable {
@Override
public void increment() {
Callee2.this.increment();
}
}
Incrementable getCallbackReference() {
return new Closure();
}
}
class Caller {
private Incrementable callbackReference;
public Caller(Incrementable cbr) {
this.callbackReference = cbr;
}
void go() {
callbackReference.increment();
}
}
public class Callbacks {
public static void main(String[] args) {
Callee1 c1 = new Callee1();
Callee2 c2 = new Callee2();
MyIncrement.f(c2);
System.out.println("==============");
Caller caller1 = new Caller(c1);
Caller caller2 = new Caller(c2.getCallbackReference());
caller1.go();
caller1.go();
caller2.go();
caller2.go();
}
}
Callee1直接实现Incrementable接口,所以可以直接重写调用increment方法
Callee2用内部类Closure实现了Incrementable,以提供一个返回Callee2的“钩子”(hook)-而且是一个安全的钩子。无论谁获得此 Incrementable 的引用,都只能调用 increment()
内部类与控制框架
设计模式总是将变化的事物与保持不变的事物分离开,在 模板方法 设计模式中,模板方法是保持不变的事物,而可重写的方法就是变化的事物
内部类允许:
- 控制框架的完整实现是由单个的类创建的,从而使得实现的细节被封装了起来。内部类用来表示解决问题所必需的各种不同的 action()
- 内部类能够很容易地访问外部类的任意成员,所以可以避免这种实现变得笨拙
继承内部类
继承内部类的子类构造器必须使用:父类对象.super(); 才能通过编译
内部类可以被重写么?
一个类继承了某个外部类的时候,内部类并没有发生什么特别神奇的变化。这两个内部类是完全独立的两个实体,各自在自己的命名空间内
一个内部类明确继承另一个内部类时,并且重写其内部类方法,这个时候就是重写,可以使用多态的方式调用
public class BigEgg2 extends Egg2 {
public class Yolk extends Egg2.Yolk {
局部内部类
局部内部类不能有访问说明符,因为它不是外部类的一部分。但是它可以访问当前代码块内的常量、此外部类的所有成员
使用局部内部类而不是匿名内部类呢?理由是:
- 需要一个已命名的构造器,或者需要重载构造器,而匿名内部类只能使用实例初始化
- 需要不止一个该内部类的对象
内部类标识符
内部类也必须生成一个 .class 文件以包含它们的 Class 对象信息
类文件命名规则:外部类的名字,加上 "$" ,再加上内部类的名字
如果内部类是匿名的,编译器会简单地产生一个数字作为其标识符
Counter.class
LocalInnerClass$1.class
LocalInnerClass$1LocalCounter.class
LocalInnerClass.class
第十二章 集合
泛型和类型安全的集合
没有使用泛型,Java编译器会给出警告,使用特定的注解来抑制警告信息。 @SuppressWarning 注解及其参数表示只抑制“unchecked”类型的警告
通过使用泛型,就可以在编译期防止将错误类型的对象放置到集合中
// new ArrayList<>() 。这有时被称为“菱形语法”(diamond syntax)。在 Java 7 之前,必须要在两端都进行类型声明
ArrayList<Apple> apples = new ArrayList<Apple>();
当指定了某个类型为泛型参数时,并不仅限于只能将确切类型的对象放入集合中。向上转型也可以像作用于其他类型一样作用于泛型
基本概念
Java集合类库采用“持有对象”(holding objects)的思想,并将其分为两个不同的概念
- 集合(Collection) :一个独立元素的序列,这些元素都服从一条或多条规则
- 映射(Map) : 一组成对的“键值对”对象,允许使用键来查找值
Collection 的构造器可以配合Arrays.asList() 作为输入,但是 Collections.addAll() 运行得更快
Collection.addAll() 方法只能接受另一个 Collection 作为参数,因此它没有 Arrays.asList() 或 Collections.addAll() 灵活。这两个方法都使用可变参数列表
注意!Arrays.asList() 的输出作为一个 List ,但是这里的底层实现是数组,可以修改,但没法调整大小
Arrays.toString() 集合的打印
subList() 方法可以轻松地从更大的列表中创建切片
Collections.sort() 和 Collections.shuffle() 方法,不会影响 containsAll() 结果
retainAll() 交集,依赖于equals()
List有一个重载的 addAll(int index, Collection<? extends E> c) 可以将新列表插入到原始列表的中间位置
Collection:Object[] toArray(); 无参版本返回一个 Object 数组
Object[] objects = collection.toArray();
Integer[] integers = collection.toArray(new Integer[0]); // 如果参数数组太小而无法容纳 List 中的所有元素(就像本例一样),则 toArray() 会创建一个具有合适尺寸的新数组
迭代器
迭代器(也是一种设计模式)通常被称为轻量级对象(lightweight object)
能够将遍历序列的操作与该序列的底层结构分离,迭代器统一了对集合的访问方式
- iterator() 方法要求集合返回一个 Iterator。 Iterator 将准备好返回序列中的第一个元素
- next() 方法获得序列中的下一个元素
- hasNext() 方法检查序列中是否还有元素
- remove() 方法将迭代器最近返回的那个元素删除,调用 remove() 之前必须先调用 next()
写方法迭代集合 入参为Iterator就需要手动传入Collection的iterator对象
入参使用 Iterable 接口更简洁,该接口描述了“可以产生 Iterator 的任何东西
// Collection 继承了 Iterable
public interface Collection<E> extends Iterable<E> {
ListIterator
只能用于List
- 双向移动hasPrevious() previous()
- previousIndex() nextIndex()
- set()
- add()
链表LinkedList
中间执行插入和删除操作时比 ArrayList 更高效,随机访问操作效率方面却要逊色一些
LinkedList 还添加了一些方法,使其可以被用作栈、队列或双端队列(deque)
- getFirst() 和 element() 是相同的,它们都返回列表的头部(第一个元素)而并不删除它,如果 List 为空,则抛出 NoSuchElementException 异常。 peek- () 方法与这两个方法只是稍有差异,它在列表为空时返回 null
- removeFirst() 和 remove() 也是相同的,它们删除并返回列表的头部元素,并在列表为空时抛出 NoSuchElementException 异常。 poll() 稍有差异,它在- 列表为空时返回 null
- addFirst() 在列表的开头插入一个元素
- offer() 与 add() 和 addLast() 相同。 它们都在列表的尾部(末尾)添加一个元素
- removeLast() 删除并返回列表的最后一个元素
Queue 接口在 LinkedList 的基础上添加了 element() , offer() , peek() , poll() 和 remove() 方法,以使其可以成为一个 Queue 的实现
堆栈Stack
Java 1.0 中附带了一个 Stack 类,使用了继承而非组合进行实现
class Stack<E> extends Vector<E> {
使用 ArrayDeque 可以产生更好的 Stack
public class Stack<T> {
private Deque<T> storage = new ArrayDeque<>();
public void push(T v) { storage.push(v); }
public T peek() { return storage.peek(); }
public T pop() { return storage.pop(); }
public boolean isEmpty() { return storage.isEmpty(); }
@Override
public String toString() {
return storage.toString();
}
}
集合Set
TreeSet 将元素存储在红-黑树数据结构中
HashSet 使用散列函数
LinkedHashSet 也使用散列来提高查询速度,但是似乎使用了链表来维护元素的插入顺序
映射Map
通过使用 containsKey() 和 containsValue() 方法去测试一个 Map ,以查看它是否包含某个键或某个值
Map 可以返回由其键组成的 Set ,由其值组成的 Collection ,或者其键值对的 Set
队列Queue
LinkedList 实现了 Queue 接口,通过将 LinkedList 向上转换为 Queue
offer() 是 Queue 的特有方法之一,它在允许的情况下,在队列的尾部插入一个元素,或者返回 false
peek() 和 element() 都返回队头元素而不删除它,但如果队列为空,则 peek() 返回 null , 而 element() 抛出 NoSuchElementException
poll() 和 remove() 都删除并返回队头元素,但如果队列为空,则 poll() 返回 null ,而 remove() 抛出 NoSuchElementException
优先级队列PriorityQueue
PriorityQueue 确保在调用 peek() , poll() 或 remove() 方法时,获得的元素将是队列中优先级最高的元素
Integer , String 和 Character 可以与 PriorityQueue 一起使用,因为这些类已经内置了自然排序
public static void main(String[] args) {
List<Integer> ints = Arrays.asList(25, 22, 20,
18, 14, 9, 3, 1, 1, 2, 3, 9, 14, 18, 21, 23, 25);
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(ints);
QueueDemo.printQ(priorityQueue);// 1 1 2 3 3 9 9 14 14 18 18 20 21 22 23 25 25
priorityQueue = new PriorityQueue<>(Collections.reverseOrder());
priorityQueue.addAll(ints);
System.out.println(priorityQueue);// [25, 25, 21, 23, 14, 14, 20, 22, 1, 2, 3, 9, 9, 3, 18, 1, 18]
QueueDemo.printQ(priorityQueue); // 25 25 23 22 21 20 18 18 14 14 9 9 3 3 2 1 1
}
for-in和迭代器
也适用于任何 Collection 对象
Java 5 引入了一个名为 Iterable 的接口,该接口包含一个能够生成 Iterator 的 iterator() 方法。for-in 使用此 Iterable 接口来遍历序列
适配器方法惯用法
如果已经有一个接口并且需要另一个接口时,则编写适配器就可以解决这个问题
添加方法返回Iterable对象,用匿名内部类重写其iterator方法返回Iterator对象
注意
不要在新代码中使用遗留类 Vector ,Hashtable 和 Stack
- Vector:所有get()、set()方法都是synchronized方法,不能对同步进行细粒度控制
- Hashtable:线程安全造成的效率低下(而且没有遵循驼峰命名法),继承了被弃用的父类Dictionary
除 TreeSet 之外的所有 Set 都具有与 Collection 完全相同的接口。List 和 Collection 存在着明显的不同,尽管 List 所要求的方法都在 Collection 中。另一方面,在 Queue 接口中的方法是独立的,在创建具有 Queue 功能的实现时,不需要使用 Collection 方法。最后, Map 和 Collection 之间唯一的交集是 Map 可以使用 entrySet() 和 values() 方法来产生 Collection
第十三章 函数式编程
函数式编程语言操纵代码片段就像操作数据一样容易。 虽然 Java 不是函数式语言,但 Java 8 Lambda 表达式和方法引用 (Method References) 允许你以函数式编程
函数式编程(FP):通过合并现有代码来生成新功能而不是从头开始编写所有内容,我们可以更快地获得更可靠的代码。至少在某些情况下,这套理论似乎很有用。在这一过程中,函数式语言已经产生了优雅的语法,这些语法对于非函数式语言也适用
OO(object oriented,面向对象)是抽象数据,FP(functional programming,函数式编程)是抽象行为
纯粹的函数式语言在安全性方面更进一步。它强加了额外的约束,即所有数据必须是不可变的:设置一次,永不改变。将值传递给函数,该函数然后生成新值但从不修改自身外部的任何东西(包括其参数或该函数范围之外的元素)
“不可变对象和无副作用”范式解决了并发编程中最基本和最棘手的问题之一(当程序的某些部分同时在多个处理器上运行时)。这是可变共享状态的问题
Lambda表达式
是使用最小可能语法编写的函数定义
interface Description {
String brief();
}
interface Body {
String detailed(String head);
}
interface Multi {
String twoArg(String head, Double d);
}
public class LambdaExpressions {
static Body bod = h -> h + "No Parens!";
static Body bod2 = (h) -> h + "More details";
static Description desc = () -> "Short info";
static Multi mult = (h, n) -> h + n;
static Description moreLines = () -> {
System.out.println("moreLines()");
return "from moreLines()";
};
public static void main(String[] args) {
System.out.println(bod.detailed("Oh!"));
System.out.println(bod2.detailed("Hi"));
System.out.println(desc.brief());
System.out.println(mult.twoArg("Pi!", 3.14159));
System.out.println(moreLines.brief());
}
}
表达式的基本语法是:
- 参数
- 接着 ->,可视为“产出”
- -> 之后的内容都是方法体
注意:
- ()内放参数,一个参数可省略
- Lambda 表达式方法体都是单行的,结果自动成为返回值,在这里 return非法
- Lambda 表达式中确实需要多行,则必须将放在花括号中,这时需要使用 return
递归
可以编写递归的 Lambda 表达式,递归方法必须是实例变量或静态变量,否则会出现编译时错误
整数 n 的阶乘
interface IntCall {
int call(int arg);
}
public class RecursiveFactorial {
static IntCall fact;
public static void main(String[] args) {
fact = n -> n == 0 ? 1 : n * fact.call(n - 1);
for (int i = 0; i < 10; i++) {
System.out.println(fact.call(i));
}
}
}
斐波那契
interface IntCall {
int call(int arg);
}
public class RecursiveFibonacci {
IntCall fib;
public RecursiveFibonacci() {
// fib = n -> {
// if (n == 0) return 0;
// if (n == 1) return 1;
// return fib.call(n - 1) + fib.call(n - 2);
// };
fib = n -> n == 0 ? 0 :
n == 1 ? 1 :
fib.call(n - 1) + fib.call(n - 2);
}
int fibonacci(int n) {
return fib.call(n);
}
public static void main(String[] args) {
RecursiveFibonacci rf = new RecursiveFibonacci();
for (int i = 0; i <= 10; i++) {
System.out.println(rf.fibonacci(i));
}
}
}
方法引用
方法引用组成: 类名或对象名::方法名称
interface Callable {
void call(String s);
}
class Describe {
void show(String msg) {
System.out.println(msg);
}
}
public class MethodReferences {
static void hello(String name) {
System.out.println("hello," + name);
}
static class Description {
String about;
public Description(String about) {
this.about = about;
}
void help(String msg) {
System.out.println(about + " " + msg);
}
}
static class Helper {
static void assist(String msg) {
System.out.println(msg);
}
}
public static void main(String[] args) {
Describe d = new Describe();
// 1. 方法引用
// 因为方法引用符合 Callable 的 call() 方法的签名
Callable c = d::show;
// Java 将 call() 映射到 show()
c.call("call()");
// 2. 静态方法引用
c = MethodReferences::hello;
c.call("Bob");
// 3. 内部静态类非静态方法 已实例化对象的方法的引用,有时称为绑定方法引用
c = new Description("valuable")::help;
c.call("infomation");
// 4. 静态内部类中的静态方法
c = Helper::assist;
c.call("Help!");
}
}
Runnable接口
class Go {
static void go() {
System.out.println("Go::go");
}
}
public class RunnableMethodReference {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Anonymous");
}
}).start();
new Thread(() -> System.out.println("lambda")).start();
new Thread(Go::go).start();
}
}
未绑定的方法引用
没有关联对象的普通(非静态)方法
class X {
String f() {
return "X::f()";
}
}
interface MakeString {
String make();
}
interface TransformX {
String transform(X x);
}
public class UnboundMethodReference {
public static void main(String[] args) {
// MakeString ms = X::f; // invalid method reference
TransformX sp = X::f;
X x = new X();
// 效果相同
System.out.println(sp.transform(x)); // [1]
System.out.println(x.f());// 对象调用方法
}
}
第一种方式:
- 拿到未绑定的方法引用
- 调用它的transform()方法,将一个X类的对象传递给它
- 最后使得 x.f() 以某种方式被调用
Java知道它必须拿第一个参数,该参数实际就是this 对象,然后对此调用方法
构造函数引用
每种情况下赋值给不同的接口,编译器可以从中知道具体使用哪个构造函数
class Dog {
String name;
int age = 1;
public Dog() {
name = "stray";
}
public Dog(String name) {
this.name = name;
}
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
}
interface MakeNoArgs {
Dog make();
}
interface Make1Args {
Dog make(String nm);
}
interface Make2Args {
Dog make(String nm, int age);
}
public class CtorReference {
public static void main(String[] args) {
MakeNoArgs mna = Dog::new;
Make1Args m1a = Dog::new;
Make2Args m2a = Dog::new;
Dog make = mna.make();
Dog comet = m1a.make("Comet");
m2a.make("Ralph", 14);
}
}
函数式接口
Java 8 引入了 java.util.function 包。它包含一组接口,这些接口是 Lambda 表达式和方法引用的目标类型。 每个接口只包含一个抽象方法,称为 函数式方法
可以使用 @FunctionalInterface 注解强制执行此“函数式方法”模式,当接口中抽象方法多于一个时产生编译期错误
如果将方法引用或 Lambda 表达式赋值给函数式接口(类型需要匹配),Java 会适配你的赋值到目标接口。 编译器会在后台把方法引用或 Lambda 表达式包装进实现目标接口的类的实例中
java.util.function 包旨在创建一组完整的目标接口,使得我们一般情况下不需再定义自己的接口
以下是基本命名准则:
- 如果只处理对象而非基本类型,名称则为 Function,Consumer,Predicate 等。参数类型通过泛型添加
- 如果接收的参数是基本类型,则由名称的第一部分表示,如 LongConsumer,DoubleFunction,IntPredicate 等,但返回基本类型的 Supplier 接口例外
- 如果返回值为基本类型,则用 To 表示,如 ToLongFunction 和 IntToLongFunction
- 如果返回值类型与参数类型相同,则是一个 Operator :单个参数使用 UnaryOperator,两个参数使用 BinaryOperator
- 如果接收参数并返回一个布尔值,则是一个 谓词 (Predicate)
- 如果接收的两个参数类型不同,则名称中有一个 Bi
在使用函数接口时,名称无关紧要——只要参数类型和返回类型相同。 Java 会将你的方法映射到接口方法。 要调用方法,可以调用接口的函数式方法名,而不是你的方法名
可以使用Function <Integer,Double> 并产生正常的结果,所以用基本类型(IntToDoubleFunction)的唯一理由是可以避免传递参数和返回结果过程中的自动拆装箱,进而提升性能
高阶函数
高阶函数(Higher-order Function)只是一个消费或产生函数的函数
// 使用继承为专用接口起别名
interface FuncSS extends Function<String, String> {
}
public class ProduceFunction {
static FuncSS produce() {
return s -> s.toLowerCase();
}
public static void main(String[] args) {
// 这里 produce() 是高阶函数
FuncSS f = produce();
System.out.println(f.apply("YELLING"));
}
}
闭包
考虑一个更复杂的 Lambda,它使用函数作用域之外的变量。 返回该函数会发生什么? 也就是说,当你调用函数时,它对那些 “外部 ”变量引用了什么? 如果语言不能自动解决,那问题将变得非常棘手
Java 8 提供了有限但合理的闭包支持
被 Lambda 表达式引用的局部变量必须是 final 或者是等同 final 效果的
等同 final 效果(Effectively Final)。这个术语是在 Java 8 才开始出现的,表示虽然没有明确地声明变量是 final 的,但是因变量值没被改变过而实际有了 final 同等的效果。 如果局部变量的初始值永远不会改变,那么它实际上就是 final 的
在闭包中,在使用 x 和 i 之前,通过将它们赋值给 final 修饰的变量解决问题
每个闭包都有自己独立的 ArrayList,它们之间互不干扰
public class Closure8 {
Supplier<List<Integer>> makeFun() {
// final 可以去掉
final List<Integer> ai = new ArrayList<>();
ai.add(1);
return () -> ai;
}
public static void main(String[] args) {
Closure8 c7 = new Closure8();
List<Integer> l1 = c7.makeFun().get(),
l2 = c7.makeFun().get();
System.out.println(l1);
System.out.println(l2);
l1.add(42);
l2.add(96);
System.out.println(l1);
System.out.println(l2);
}
}
规则并非只是 “在 Lambda 之外定义的任何变量必须是 final 的或等同 final 效果” 那么简单。相反,你必须考虑捕获的变量是否是等同 final 效果的。 如果它是对象中的字段(实例变量),那么它有独立的生命周期,不需要任何特殊的捕获以便稍后在调用 Lambda 时存在。(注:结论是——Lambda 可以没有限制地引用 实例变量和静态变量。但 局部变量必须显式声明为final,或事实上是final 。)
作为闭包的内部类
public class AnonymousClosure {
IntSupplier makeFun(int x) {
int i = 0;
return new IntSupplier() {
@Override
public int getAsInt() {
return x + i;
}
};
}
}
实际上只要有内部类,就会有闭包(Java 8 只是简化了闭包操作)
在 Java 8 之前,变量 x 和 i 必须被明确声明为 final。在 Java 8 中,内部类的规则放宽,包括等同 final 效果
函数组合
- andThen(argument) 执行原操作,再执行参数操作
- compose(argument) 执行参数操作,再执行原操作
- and(argument) 原谓词(Predicate)和参数谓词的短路逻辑与 Predicate
- or(argument) 原谓词和参数谓词的短路逻辑或 Predicate
- negate() 该谓词的逻辑非
柯里化和部分求值
柯里化意为:将一个多参数的函数,转换为一系列单参数函数
参考资料
《On Java 8》笔记 2的更多相关文章
- 《Netlogo多主体建模入门》笔记8
8 -GINI系数计算与 如何使用行为空间做实验 首先,我们加入保底机制. 对于每一个agent,都有一个随机的保底比例 s(每个agent的 s 不都一样,且s初始化之后不会改变) 进行交易 ...
- 《Netlogo多主体建模入门》笔记 2
从自带的模型库开始 财富分配模型 黄色代表稻谷,有的人消化快,有的慢,稻谷的积累代表财富的积累,不涉及交易行为. 点击setup后 ,点击 go 红线--穷人: 绿线-- 中产 : 蓝 ...
- 《Netlogo多主体建模入门》笔记4
4- 从Langton的蚂蚁看Turtle与Patch的交互 这只蚂蚁从10000步开始,就会自发地 “建桥” Turtle与Patch就好比是,一个方块和一个格子的关系. 一个格子上可以 ...
- 《Netlogo多主体建模入门》笔记3
3- 用“生命游戏”认识Patch 代码: patches-own[living] to setup clear-all ask patches [ < 0.3[ set pcolo ...
- 每天成长一点---WEB前端学习入门笔记
WEB前端学习入门笔记 从今天开始,本人就要学习WEB前端了. 经过老师的建议,说到他每天都会记录下来新的知识点,每天都是在围绕着这些问题来度过,很有必要每天抽出半个小时来写一个知识总结,及时对一天工 ...
- ES6入门笔记
ES6入门笔记 02 Let&Const.md 增加了块级作用域. 常量 避免了变量提升 03 变量的解构赋值.md var [a, b, c] = [1, 2, 3]; var [[a,d] ...
- [Java入门笔记] 面向对象编程基础(二):方法详解
什么是方法? 简介 在上一篇的blog中,我们知道了方法是类中的一个组成部分,是类或对象的行为特征的抽象. 无论是从语法和功能上来看,方法都有点类似与函数.但是,方法与传统的函数还是有着不同之处: 在 ...
- React.js入门笔记
# React.js入门笔记 核心提示 这是本人学习react.js的第一篇入门笔记,估计也会是该系列涵盖内容最多的笔记,主要内容来自英文官方文档的快速上手部分和阮一峰博客教程.当然,还有我自己尝试的 ...
- redis入门笔记(2)
redis入门笔记(2) 上篇文章介绍了redis的基本情况和支持的数据类型,本篇文章将介绍redis持久化.主从复制.简单的事务支持及发布订阅功能. 持久化 •redis是一个支持持久化的内存数据库 ...
- redis入门笔记(1)
redis入门笔记(1) 1. Redis 简介 •Redis是一款开源的.高性能的键-值存储(key-value store).它常被称作是一款数据结构服务器(data structure serv ...
随机推荐
- OAuth2 Authorization Server
基于Spring Security 5 的 Authorization Server的写法 先看演示 pom.xml <?xml version="1.0" encoding ...
- Rancher 2.x 安装
Rancher 是一个容器管理平台.Rancher 简化了使用 Kubernetes 的流程. 下面记录一下手动安装Rancher的步骤 1. 部署 Rancher Server 执行以下命令即可( ...
- nginx新增conf文件
说明 最近租了一台美国vps,通过nginx反向代理设置搞谷歌镜像.因为BxxDx搜索太垃圾.中间涉及到添加反向代理配置. 操作步骤 1.在conf.d文件下新增配置 cd /etc/nginx/co ...
- Shiro实战1-介绍
什么是 Shiro 官网:http://shiro.apache.org/ shiro是一款主流的 Java 安全框架,不依赖任何容器,可以运行在 Java SE和 Java EE 项目中,它的主要作 ...
- Spring Boot图书管理系统项目实战-8.续借图书
导航: pre: 7.借阅图书 next:9.归还图书 只挑重点的讲,具体的请看项目源码. 1.项目源码 需要源码的朋友,请捐赠任意金额后留下邮箱发送:) 2.页面设计 2.1 bookReBorr ...
- Java并发编程实例--19.在一个锁中使用多个条件
一个锁可能关联了一个或多个条件.这些条件可以在Condition接口中声名. 使用这些条件的目的是去控制一个锁并且可以检查一个条件是true或false,如果为false,则暂停直到 另一个线程来唤醒 ...
- windbg 学习
常用的 windbg 命令 .ecxr 用来切换到异常发生时的上下文,主要用在分析静态 dump 文件的时候.当我们使用 .reload 命令去强制加载库的 pdb 文件后,需要执行 .ecxr 命令 ...
- 关于dpi awareness 的清单文件设置
要设置dpi 意识,一般是使用SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE)来设置 具体可参考:Setting the default DP ...
- Redis原理再学习02:数据结构-动态字符串sds
Redis原理再学习:动态字符串sds 字符 字符就是英文里的一个一个英文字母,比如:a.中文里的单个汉字,比如:好. 字符串就是多个字母或多个汉字组成,比如字符串:redis,中文字符串:你好吗. ...
- go语言range语句中的值的坑
在range语句中生成的数据的值是真实集合元素的副本,它们不是原有元素的引用.这意味着更新这些值将不会 修改原来的数据,同时也意味着使用这些值的地址将不会得到原有数据的指针. package main ...