JSR(Java Specification Requests) 335 = lambda表达式+接口改进(默认方法)+批量数据操做html
1、函数式编程
以处理数据的方式处理代码,这意味着函数能够被赋值给变量,传递给函数等,函数是第一等级的值
入门例子:java
[java] view plain copyshell
Scala/Groovy/Clojure是JVM平台上的编程语言,都支持函数式编程,如下以Scala语言来函数式改写上面的代码:express
[java] view plain copy编程
将过滤,转化和聚合三个行为分开,表面上看,上面的代码貌似先遍历列表挑出全部符合条件的元素传给map,map接着遍历并处理从filter传来的结果,最后将处理后的结果都传给reduce,reduce接着遍历处理。数了数貌似要经历过好屡次遍历呢,可是其具体的实现并不是如此,由于其有“惰性”,它的具体实现行为和上面JAVA for-loop实现基本一致,只是函数式更直观,更简洁而已。api
静态语言 VS 动态语言:http://www.zhihu.com/question/19918532数组
JAVA的函数式编程(Lambda):
当咱们用匿名内部类为某个接口定义不一样的行为时,语法过于冗余,lambda提供了更简洁的语法。
- 函数式接口 Functional interfaces
每个函数对象都对应一个接口类型。函数式接口只能拥有一个抽象方法,编译器会根据接口的结构自行判断(判断过程并非简单的对接口方法计数,由于该接口可能还定义了静态或默认方法)。API做者也能够经过@FunctionalInterface注解来指定一个接口为函数式接口,以后编译器就会验证该接口是否知足函数式接口的要求。
JAVA SE 7中已经存在的函数式接口,例如:
- java.lang.Runnable
- java.util.concurrent.Callable
- java.util.Comparator
- java.io.FileFilter
JAVA SE 8中新增长了一个包 java.util.function,包含了经常使用的函数式接口,例如:
- Predicate<T> -- boolean test(T t);
- Consumer<T> -- void accept(T t);
- Function<T, R> -- R apply(T t);
- Supplier<T> -- T get();数据结构
- BiFunction<T, U, R> -- R apply(T t, U u);oracle
lambda表达式例子:
x -> x+1
() -> {System.out.println("hello world");}app
(int x, int y) -> { x++; y--; return x+y;}
Function<Integer, Integer> func = (x, Integer y) -> x + y //wrong
Function<Integer, Integer> func = Integer x -> x + 1 //wrong
当且仅当下面全部条件均被知足时,lambda表达式才能够被赋给目标类型T:
- T是一个函数式接口
- lambda表达式的参数和T的方法参数在数量和类型上一一对应
- lambda表达式的返回值和T的方法返回值相兼容
- lambda表达式内所抛出的异常和T的方法throws类型想兼容
lambda表达式的参数类型能够从目标类型中得出的话就能够省略。
目标类型上下文:
- 变量声明
- 赋值
- 返回语句
public Runnable PrintSth() {
return () -> {
System.out.println("sth");
};
}
Comparator<String> c;
c = (String s1, String s2) -> s1.compareToIgnoreCase(s2);
- 数组初始化器
new FileFilter[] {
f -> f.exists(), f -> f.canRead(), f -> f.getName().startsWith("s")
}
- 方法和构造方法的参数
List<Person> ps = ...;
Stream<String> names = ps.stream().map(p -> p.getName());
map的参数是Function<T, R>,p能够从List<Person>推导出T为Person类型,而经过Stream<String>能够知道R的类型为String,当编译器没法解析出正确lambda类型的时候,能够:
1. 为lambda参数提供显示类型
2. 将lambda表达式转型为Function<Person, String>
3. 为泛型参数R提供一个实际类型,好比 .<String>map(p->p.getName())
- lambda表达式函数体
- 条件表达式(?:)
- 转型表达式
Supplier<Runnable> c = () -> () -> {System.out.println("hi");};
Callable<Integer> c = flag ? (() -> 23) : (() -> 42);
Object o = () -> {System.out.println("hi");}; //非法
Object o = (Runnable) () -> {System.out.println("hi");};
词法做用域:
lambda不会引入一个新的做用域。
public class Hello {
Runnable r1 = () -> { System.out.println(this); };
Runnable r2 = () -> { System.out.println(toString()); };
public String toString() { return "Hello, world"; }
public static void main(String... args) {
new Hello().r1.run();
new Hello().r2.run();
}
}
Output:
Hello, world
Hello, world
lambda不能够掩盖其所在上下文中的局部变量,相似:
int i = 0;
int sum = 0;
for (int i = 1; i < 10; i += 1) { //这里会出现编译错误,由于i已经在for循环外部声明过了
sum += i;
}
变量捕获:
[java] view plain copy
Output: 11, 2
Function<Integer, Integer> func = t.m(); //++this.sum;
System.out.println(func.apply(9)); //++this.sum; this.sum+i;
lambda只能捕获effectively final的局部变量,就是捕获时或捕获后不能修改相关变量。若是sum是m方法的局部变量,func不能修改该局部变量,由于m执行完就退出栈帧。同时这样也能够避免引发race condition,in a nutshell,lambda表达式对值封闭,对变量开放。
int sum = 0;
list.forEach(e -> { sum += e.size(); }); // Illegal, close over values
List<Integer> aList = new List<>();
list.forEach(e -> { aList.add(e); }); // Legal, open over variables
A race condition occurs when two or more threads can access shared data and they try to change it at the same time. Because the thread scheduling algorithm can swap between threads at any time, you don't know the order in which the threads will attempt to access the shared data.
list.parallelStream().forEach(e -> {sum += e;}); //此时多个线程同时处理,若是sum不是不可变的话,就会引发race condition,那样的话咱们就须要提供synchronization或者利用volatile来避免读取stale data。
http://www.lambdafaq.org/what-are-the-reasons-for-the-restriction-to-effective-immutability/
在普通内部类中,实例会一直保留一个对其外部类实例的强引用,每每可能会形成内存泄漏。而lambda表达式只有在有用到类实例变量时才保持一个this引用,如上面func会保存一个引用指向Test3的实例。
lambda简写形式 - 方法引用:
若是咱们想要调用的方法拥有一个名字,咱们就能够直接调用它。
- 静态方法引用 className::methodName
- 实例上的实例方法引用 instanceReference::methodName
- 超类上的实例方法引用 super::methodName
- 类型上的实例方法引用 className::methodName,好比String::toUpperCase,语法和静态方法引用相同,编译器会根据实际状况做出决定
- 构造方法引用 className::new
- 数组构造方法引用 TypeName[]::new
[java] view plain copy
2、默认方法和静态接口方法
如何给一个单抽象函数式接口添加其余的方法呢?接口方法能够是抽象和默认的,默认方法拥有默认的实现,实现接口的类型能够经过继承获得该默认实现。除此以外,接口还容许定义静态方法,使得咱们能够从接口直接调用和它相关的辅助方法。默认方法能够被继承,当父类和接口的超类拥有多个具备相同签名的方法时,好比菱形继承,咱们就须要遵守一些规则:
1. 类的方法优先于默认方法,不管该方法是具体仍是抽象的
2. 被其余类型所覆盖的方法会被忽略,当超类共享一个公共祖先的时候
class LinkedList<E> implements List<E>, Queue<E> { ... }
3. 当两个独立的默认方法或者默认方法和抽象方法相冲突时会发生编译错误,此时咱们须要显式覆盖超类方法
interface Robot implements Artist, Gun {
default void draw() { Artist.super.draw(); }
}
3、批量处理
- 传递行为而不是值
enhanced for loop VS forEach
[java] view plain copy
我的看法,这个例子中,forEach并无比for loop更高效,通常其在parallel stream下被调用的时候才发挥做用。
- 能够并行处理,批量处理
流是java8类库中新增的关键抽象,被定义于java.util.stream中。流的操做能够被组合成流水线pipeline。
Stream特性:
1. stream不会存储值,流的元素来自数据源(数据结构,生成函数或I/O通道)
2. 对stream的操做并不会修改数据源
3. 惰性求值,每一步只要一找到知足条件的数字,立刻传递给下一步去处理而且暂停当前步骤
4. 可选上界,由于流能够是无限的,用户能够不断的读取直到设置的上界
咱们能够把集合做为流的数据源(Collection拥有stream和parallelStream方法),也能够经过流产生一个集合(collect方法)。流的数据源多是一个可变集合,若是在遍历时修改数据源,会产生interference,因此只要该集合只属于当前线程而且lambda表达式不修改数据源就能够。
1. 不要干扰数据源
2. 不要干扰其余lambda表达式,当一个lambda在修改某个可变状态而另外一个lambda在读取状态时就会产生这种干扰
惰性:
急性求值,即在方法返回前完成对全部元素的过滤,而惰性求值则在须要的时候才进行过滤,一次性遍历。
Collectors:
利用collect()方法把流中的元素聚合到集合中,如List或者Set。collect()接收一个类型Collectors的参数,这个参数决定了如何把流中的元素聚合到其余数据结构中。Collectors类包含了大量经常使用收集器的工厂方法,toList()和toSet()就是其中最多见的两个。
并行:
为了实现高效的并行计算,引入了fork/join模型:
- 把问题分解为子问题
- 串行解决子问题从而获得部分结果
- 合并部分结果为最终结果
并行流:将流抽象为Spliterator,容许把输入元素的一部分转移到另外一个新的Spliterator中,而剩下的数据会保存在原来的Spliterator里面。以后还能够进一步分割这两个Spliterator。分割迭代器还能够提供源的元数据(好比元素的数量)和其余一系列布尔值特征(好比元素是否被排序)。若是想要本身实现一个集合或者流,就可能须要手动实现Spliterator接口。stream在JAVA SE8中很是重要,咱们为collection提供了stream和parallelStream把集合转化成流,此外数组也能够经过Arrays.stream()被转化为流。stream中还有一些静态工厂方法来建立流,例如Stream.of(), Stream.generate()等。
- 建立stream
1.Stream接口的静态方法
Stream<Integer> integerStream = Stream.of(1, 2, 3, 5);
Stream<String> stringStream = Stream.of("taobao");
2.Collection接口的默认方法
- 转换stream,每次转换会返回一个新的stream对象(把一个stream经过某些行为转换成一个新的stream)
1. distinct,stream中包含的元素不重复
2. filter,使用给定的过滤函数进行过滤操做
3. map,对于stream中包含的元素使用给定的转换函数进行转换操做,新生成的Stream只包含转换生成的元素。这个方法有三个对于原始类型的变种方法,分别是mapToInt, mapToLong和mapToDouble。
4. peek,生成包含原stream的各个元素的新stream,同时提供一个消费函数,新stream每一个元素被消费的时候都会执行给定的消费函数
5. limit,对一个stream进行截断
6. skip,返回一个丢弃原stream的前N个元素后剩下元素组成新的stream
- 对stream进行聚合,获取想要的结果
接受一个元素序列为输入,反复使用某个合并操做,把序列中的元素合并成一个汇总的结果
http://docs.oracle.com/javase/tutorial/essential/concurrency/forkjoin.html
4、其余
代码加载顺序:
非静态是在new申请内存空间并初始化其值。java必须先加载字节码,而后经过new根据字节码信息申请类的内存空间。静态是在类的字节码加载到jvm中,但仅且只执行一次。静态变量和静态代码块跟声明顺序有关,若是今天太代码块中调用静态变量,那么静态变量必须在静态代码块前面声明;若是静态代码块没有调用静态变量,那么就先声明谁被加载。
父类静态属性-》父类静态代码块-》子类静态变量-》子类静态代码块-》父类非静态变量-》父类非静态变量-》父类非静态代码块-》父类构造函数-》子类非静态变量-》子类非静态代码块-》子类构造函数
1. 初始化构造时,先父后子
2. 静态,非静态
3. java中的类只有被用到的时候才会被加载
4. java类只有在类字节码被加载后才能够被构形成对象实例
递归+组合之后再慢慢研究:
http://www.cnblogs.com/ldp615/archive/2013/04/09/recursive-lambda-expressions-1.html
http://www.kuqin.com/shuoit/20140913/342127.html
目前(2016-8)像JAD,JD-GUI都还不支持Lambda特性,因此发现了一个新反编译工具CFR,只要去官网下载CFR的jar包,而后CMD命令进入到放置该包的目录,运行:
java -jar cfr**.jar *.class (*号的地方本身写入符合的名字)
Reference:
1. Java 下一代:函数式编码风格(Groovy, Scala和Clojure共享的函数结构及其优点)
2. http://www.cnblogs.com/figure9/archive/2014/10/24/4048421.html
3. http://zh.lucida.me/blog/java-8-lambdas-insideout-library-features/
4. http://ifeve.com/stream/
5. http://www.oschina.net/question/2273217_217864