动态代理
JDK动态代理
Java标准库提供了一种动态代理(Dynamic Proxy)的机制,可以在运行期动态创建某个interface
的实例,这样就可以做到在接口并没有实现类的情况下,直接调用接口的方法。
例如下面的例子,我们先定义了接口IHello
,但是我们并没有去编写实现类,而是直接通过JDK提供的一个Proxy.newProxyInstance()
创建了一个IHello
接口的对象。这种没有实现类但是在运行期动态创建了一个接口对象的方式,就被称为动态代码。JDK提供的动态创建接口对象的方式,就叫动态代理。
一个最简单的动态代理实现如下:
1
2
3
|
interface IHello {
void sayHello(String message);
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
public class JdkDynamicProxy {
public static void main(String[] args) {
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("method: " + method.getName());
if ("sayHello".equals(method.getName())) {
System.out.println("hello " + args[0]);
}
return null;
}
};
IHello hello = (IHello) Proxy.newProxyInstance(IHello.class.getClassLoader(),
new Class[]{IHello.class},
handler);
hello.sayHello("liwenbo");
hello.toString();
}
}
// 输出
method: sayHello
hello liwenbo
method: toString
|
当hello对象执行方法的时候,例如 hello.sayHello
或者 hello.toString
时,就会被动态代理到 InvocationHandler 的invoke方法上去。
CGLIB动态代理
CGLIB(Code Generate Lib)是一个强大的、高性能的代码生成库。其被广泛应用于AOP框架(Spring、dynaop)中,用以提供方法拦截操作。CGLIB代理主要通过对字节码的操作,为对象引入间接级别,以控制对象的访问。我们知道Java中有一个动态代理也是做这个事情的,那我们为什么不直接使用Java动态代理,而要使用CGLIB呢?答案是CGLIB相比于JDK动态代理更加强大,JDK动态代理虽然简单易用,但是其有一个致命缺陷是,只能对接口进行代理。如果要代理的类为一个普通类、没有接口,那么Java动态代理就没法使用了。
CGLIB底层使用了ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类。除了CGLIB库外,脚本语言(如Groovy和BeanShell)也使用ASM生成字节码。ASM使用类似SAX的解析器来实现高性能。我们不鼓励直接使用ASM,因为它需要对Java字节码的格式足够的了解。
下面是一个使用cglib动态代理库的示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
public class CglibProxy {
public void sayHello(String message) {
System.out.println("hello " + message);
}
public static void main(String[] args) {
MethodInterceptor interceptor = new MethodInterceptor() {
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("Before method " + method.getName() + " run");
Object result = methodProxy.invokeSuper(obj, args);
System.out.println("After method " + method.getName() + " run");
return result;
}
};
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(CglibProxy.class);
enhancer.setCallback(interceptor);
CglibProxy test = (CglibProxy) enhancer.create();
test.sayHello("liwenbo");
}
}
// 输出
Before method sayHello run
hello liwenbo
After method sayHello run
|
有可能输出中会看到一些WARNING信息,这是由于高版本的java对反射访问私有成员做了一些告警。
二者比较
JDK的动态代理是面向接口的,必须得有接口定义,否则无法实现动态代理。cglib是基于字节码生产的,不受有无接口的限制。CGLib动态代理是通过字节码底层继承要代理类来实现,因此如果被代理类被final关键字所修饰,会失败。例如如下将上面的例子修改一下:
1
2
3
|
public final class CglibProxy {
...
}
|
这个时候执行会抛出异常:
1
2
|
Exception in thread "main" java.lang.IllegalArgumentException: Cannot subclass final class cloud.liwenbo.net.java.basic.dynanicproxy.cglib.CglibProxy
...
|
函数式编程(Java8)
函数式编程
每个人对函数式编程的理解不尽相同。但其核心是:在思考问题时,使用不可变值和函数,函数对一个值进行处理,映射成另一个值。
Lamda表达式
可以将Lambda表达式理解为一个匿名函数; Lambda表达式允许将一个函数作为另外一个函数的参数; 我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码作为实参),也可以理解为函数式编程,将一个函数作为参数进行传递。
下面是一个lamda表达式的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public class Test07 {
/**
* 定义两个正数的操作接口
**/
interface MathOperation {
int operation(int a, int b);
}
public static void main(String[] args) {
MathOperation addOpt = (int a, int b) -> {
return a + b;
};
// 可以不用类型声明,单条语句也可以不用大括号
MathOperation subOpt = (a, b) -> a - b;
int result = addOpt.operation(100, 200);
System.out.println("result = " + result);
}
}
|
函数接口
**函数式接口:**它指的是有且只有一个未实现的方法的接口,一般通过FunctionalInterface这个注解来表明某个接口是一个函数式接口。函数式接口是Java支持函数式编程的基础。
下面是一个函数式接口的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
@FunctionalInterface
interface Handler<T> {
void handle(T t);
}
public class Test04 {
public static <T> void handle(Handler<T> handler, T t) {
System.out.println("invoke handle");
handler.handle(t);
System.out.println("finished");
}
public static void main(String[] args) {
handle(t -> {
System.out.println("hello " + t);
}, "liwenbo");
}
}
// 输出:
invoke handle
hello liwenbo
finished
|
JDK默认定义了一些函数接口,在java.util.function
包下面,其中比较重要的有几个:Predicate、Consumer、Supplier等。
Consumer
Consumer是一个函数式编程接口; 顾名思义,Consumer的意思就是消费,即针对某个东西我们来使用它,因此它包含有一个有输入而无输出的accept接口方法;除accept方法,它还包含有andThen这个方法;JDK中Consumer函数接口的定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
/**
* Represents an operation that accepts a single input argument and returns no
* result. Unlike most other functional interfaces, {@code Consumer} is expected
* to operate via side-effects.
*
* <p>This is a <a href="package-summary.html">functional interface</a>
* whose functional method is {@link #accept(Object)}.
*
* @param <T> the type of the input to the operation
*
* @since 1.8
*/
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
/**
* Returns a composed {@code Consumer} that performs, in sequence, this
* operation followed by the {@code after} operation. If performing either
* operation throws an exception, it is relayed to the caller of the
* composed operation. If performing this operation throws an exception,
* the {@code after} operation will not be performed.
*
* @param after the operation to perform after this operation
* @return a composed {@code Consumer} that performs in sequence this
* operation followed by the {@code after} operation
* @throws NullPointerException if {@code after} is null
*/
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
|
下面是一个Consumer函数接口使用的例子:
1
2
3
4
5
6
7
8
9
10
|
public class Test06 {
public static void main(String[] args) {
Consumer<String> f1 = str -> System.out.print("hello " + str);
Consumer<String> f2 = str -> System.out.println(", nice to meet you!");
f1.andThen(f2).accept("liwenbo");
}
}
|
Predicate
Predicate为函数式接口,predicate的中文意思是“断定”,即判断的意思,判断某个东西是否满足某种条件; 因此它包含test方法,根据输入值来做逻辑判断,其结果为True或者False。
它的使用方法示例如下:
1
2
3
4
5
6
7
|
public class Test06 {
public static void main(String[] args) {
Predicate<Integer> isOdd = i -> i % 2 == 0;
System.out.println("45: " + isOdd.test(45));
System.out.println("30:" + isOdd.test(30));
}
}
|
Function
Function也是一个函数式编程接口;它代表的含义是“函数”,而函数经常是有输入输出的,因此它含有一个apply方法,包含一个输入与一个输出;
除apply方法外,它还有compose与andThen及indentity三个方法。
下面是一个Function使用的示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public class Test06 {
public static void main(String[] args) {
// count how many a's in a string
Function<String, Integer> countingA = t -> {
int result = 0;
for (int i = 0; i < t.length(); i++) {
if (t.charAt(i) == 'a') {
result++;
}
}
return result;
};
/// assume 5 a's in this string
int cnt = countingA.apply("this is a test for counting a, hahaha.");
System.out.println("a's count = " + cnt);
}
}
|
流(Stream)
Java 程序员在使用集合类时,一个通用的模式是在集合上进行迭代,然后处理返回的每一 个元素。
在Java中,集合是一种数据结构,或者说是一种容器,用于存放数据,流不是容器,它不关心数据的存放,只关注如何处理。
流通常是有三个部分组成
- **数据源:**流的获取,比如
list.stream()
方法;
- **中间处理:**中间处理是对流元素的一系列处理。比如过滤
filter
,排序sorted
,映射map
;
- **终端处理:**终端处理会生成结果,结果可以是任何不是流值。生成List,可用
collect(Collectors.toList())
,生成Map可用collect(Collectors.toMap())
。 也可以不返回结果,如stream.forEach(System.out::println)
就是将结果打印到控制台中,并没有返回。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
package lambda;
import java.util.ArrayList;
import java.util.List;
import static lambda.Person.FEMALE;
import static lambda.Person.MALE;
public class Test {
public static void main(String[] args) {
List<Person> family = new ArrayList<>();
family.add(new Person("liwenbo", MALE, 36));
family.add(new Person("hanzhu", FEMALE, 35));
family.add(new Person("lishiran", FEMALE, 7));
family.add(new Person("liyiran", MALE, 5));
family.add(new Person("handahan", MALE, 60));
// 使用传统的方式计算家庭中男性数量
int count = 0;
for (Person person : family) {
if (person.getGender() == MALE) {
count++;
}
}
// 使用流的方式计算家庭中男性数量
long maleCount = family.stream()
.filter(e -> (e.getGender() == MALE))
.count();
System.out.println("male count: " + maleCount);
}
}
|
这行代码并未做什么实际性的工作,filter 只刻画出了 Stream,但没有产生新的集合。像 filter 这样只描述 Stream,最终不产生新集合的方法叫作惰性求值方法;而像 count 这样 最终会从 Stream 产生值的方法叫作及早求值方法。
如果在过滤器中加入一条 println 语句,运行下面这段代码,程序不会输出任何信息!
1
2
3
4
5
|
// 不会打印HELLO XXX
family.stream().filter(e -> {
System.out.println("HELLO: " + e.getName());
return (e.getGender() == MALE);
});
|
如果将同样的输出语句加入一个拥有终止操作的流,则HELLO XXX就会被打印出来。
1
2
3
4
5
|
// 会打印HELLO XXX
family.stream().filter(e -> {
System.out.println("HELLO: " + e.getName());
return (e.getGender() == MALE);
}).count();
|
所有中间操作:
方法 |
说明 |
sequential |
返回一个相等的串行的Stream对象,如果原Stream对象已经是串行就可能会返回原对象 |
parallel |
返回一个相等的并行的Stream对象,如果原Stream对象已经是并行的就会返回原对象 |
unordered |
返回一个不关心顺序的Stream对象,如果原对象已经是这类型的对象就会返回原对象 |
onClose |
返回一个相等的Steam对象,同时新的Stream对象在执行Close方法时会调用传入的Runnable对象 |
close |
关闭Stream对象 |
filter |
元素过滤:对Stream对象按指定的Predicate进行过滤,返回的Stream对象中仅包含未被过滤的元素 |
map |
元素一对一转换:使用传入的Function对象对Stream中的所有元素进行处理,返回的Stream对象中的元素为原元素处理后的结果 |
mapToInt |
元素一对一转换:将原Stream中的使用传入的IntFunction加工后返回一个IntStream对象 |
flatMap |
元素一对多转换:对原Stream中的所有元素进行操作,每个元素会有一个或者多个结果,然后将返回的所有元素组合成一个统一的Stream并返回; |
distinct |
去重:返回一个去重后的Stream对象 |
sorted |
排序:返回排序后的Stream对象 |
peek |
使用传入的Consumer对象对所有元素进行消费后,返回一个新的包含所有原来元素的Stream对象 |
limit |
获取有限个元素组成新的Stream对象返回 |
skip |
抛弃前指定个元素后使用剩下的元素组成新的Stream返回 |
takeWhile |
如果Stream是有序的(Ordered),那么返回最长命中序列(符合传入的Predicate的最长命中序列)组成的Stream;如果是无序的,那么返回的是所有符合传入的Predicate的元素序列组成的Stream。 |
dropWhile |
与takeWhile相反,如果是有序的,返回除最长命中序列外的所有元素组成的Stream;如果是无序的,返回所有未命中的元素组成的Stream。 |
所有终端操作:
方法 |
说明 |
iterator |
返回Stream中所有对象的迭代器; |
spliterator |
返回对所有对象进行的spliterator对象 |
forEach |
对所有元素进行迭代处理,无返回值 |
forEachOrdered |
按Stream的Encounter所决定的序列进行迭代处理,无返回值 |
toArray |
返回所有元素的数组 |
reduce |
使用一个初始化的值,与Stream中的元素一一做传入的二合运算后返回最终的值。每与一个元素做运算后的结果,再与下一个元素做运算。它不保证会按序列执行整个过程。 |
collect |
根据传入参数做相关汇聚计算 |
min |
返回所有元素中最小值的Optional对象;如果Stream中无任何元素,那么返回的Optional对象为Empty |
max |
与Min相反 |
count |
所有元素个数 |
anyMatch |
只要其中有一个元素满足传入的Predicate时返回True,否则返回False |
allMatch |
所有元素均满足传入的Predicate时返回True,否则False |
noneMatch |
所有元素均不满足传入的Predicate时返回True,否则False |
findFirst |
返回第一个元素的Optioanl对象;如果无元素返回的是空的Optional; 如果Stream是无序的,那么任何元素都可能被返回。 |
findAny |
返回任意一个元素的Optional对象,如果无元素返回的是空的Optioanl。 |
isParallel |
判断是否当前Stream对象是并行的 |
方法引用
Lambda 表达式有一个常见的用法:Lambda 表达式经常调用参数。比 如想得到艺术家的姓名,Lambda 的表达式如下:
1
|
person -> person.getGender();
|
这种用法如此普遍,因此 Java 8 为其提供了一个简写语法,叫作方法引用,帮助程序员重用已有方法。用方法引用重写上面的 Lambda 表达式,代码如下:
标准语法为 Classname::methodName。需要注意的是,虽然这是一个方法,但不需要在后面加括号,因为这里并不调用该方法。
数据并发化
并行化操作流只需改变一个方法调用。 如果已经有一 个 Stream 对 象, 调用它的parallel方法就能让其拥有并行操作的能力。如果想从一个集合类创建一个流,调用parallelStream 就能立即获得一个拥有并行能力的流。
1
2
3
4
5
6
7
8
9
10
11
12
|
// 计算全部家庭成员的年龄之和
List<Person> family = new ArrayList<>();
family.add(new Person("liwenbo", MALE, 36));
family.add(new Person("hanzhu", FEMALE, 35));
family.add(new Person("lishiran", FEMALE, 7));
family.add(new Person("liyiran", MALE, 5));
family.add(new Person("handahan", MALE, 60));
// 将stream修改为parallelStream, 就支持并发的数据处理
long totalAge = family.parallelStream().mapToInt(Person::getAge).sum();
System.out.println(totalAge);
|
大家的第一反应可能是立即将手头代码中的 stream 方法替换为 parallelStream方法,因为这样做简直太简单了!先别忙,为了将硬件物尽其用,利用好并行化非常重 要,但流类库提供的数据并行化只是其中的一种形式。 并行化运行基于流的代码是否比串行化运行更快?这不是一个简单的问题。回到前面的例子,哪种方式花的时间更多取决于串行或并行化运行时的环境。
输入流的大小并不是决定并行化是否会带来速度提升的唯一因素,性能还会受到编写代码的方式和核的数量的影响。