前言
刚好Android Studio3.0发布了,不但对kotlin有了更进一步的支持,Java 8的支持也加强了。随便对Java 8的支持还分api版本和各种条件,但是在这里复习加强一下Java 8是有必要的。这几年可以说Java SDK的发展是极慢的,Java 8也是经过几次跳水才出来,现在的Java 9也是一样,上个月也是千呼万唤始出来。刚毕业的时候Java是现代语言的代表,各种好用工具类库,一次开发处处运行的承诺,没有复杂的继承结构,简单的数据类型,绝对的面向对象,无一不代表着前沿的编程语言,给程序员端上一杯咖啡可不是开玩笑的。然而。。在这个各种语言起发的时代,Java慢慢变成了臃肿的代表,已不再年轻,各种简约的语言奋起直追,咖啡已经快要喝不成了。再次默哀Sun公司被Oracle收购,而不是IBM。
Java 8
新特性
Lambda表达式语法和Stream API无疑是Java最大的更新,这玩意放在后面说,现说一下更新的小点。
字符串
- 添加join方法拼接字符串
数字类
- 数字类型的包装类添加静态字段BYTES来表示这种数据类型占有多少字节。java 5添加了SIZE表示占有多少位
- 基础类型包装类各自都提供了一些聚合函数方法,在流操作中可以使用,例如Integer.max
- 最早和公司的C++同事做网络交互,痛苦了一下。现在数字类型增加无符号计算。toUnsignedLong等。注意:无符号是有符号大于0的两倍,小心溢出,及时转化为更大的存储类型
- Math数学计算类提供精确计算方法addExact。如果溢出就抛异常。之前直接计算,溢出后会得出错误结果。
集合
- 一些集合类也都添加了新的方法,大部分都是为Stream API服务的。这个我们后面说到Stream API时再说。
注解
- 可重复的注解,一个元素上可以声明两个相同的注解,这两个相同的注解会被编译器合并成一个注解集合(前提是你定义这个注解集合)
反射
- 反射可取出参数名称
时间和日期新的api java.time
之前我们处理时间和日期的计算都是用的Date和Calendar,他们都在util包里面。不能说好用和不好用。因为我们没得选,只有他们。后来在我们的工程里还引入了一个第三方的时间类库org.joda.time,但是说实话在android开发里面,每次引入一个第三方工具类库,都要下一定的决心。每一次的引入都代表了包体积的变大,方法数增加(感谢公司大哥解决方法数超标的问题),甚至类多了的时候还有加载类的时间消耗。这次java大发慈悲,开了个.time的新的工具包专门用来处理时间相关的东西,大致瞅了一眼,很值得用一下,基本可以把joda库移除了。
- java.time的对象都是不可变的。每次计算都会生成新的实力。这点可以避免了像Calendar对象传来传去不知道在哪里被改了的尴尬。我们程序中增减发生过这样的bug,不得不把所有用到Calendar的方法都看了一遍。如果你真的想用Calendar做为参数,请传进去的时候clone一下好吗?
- Instant一个时间点
- Duration两个时间点的距离,内含方法可以获得时间转换,例如两个时间点之间是多少分钟,多少小时。
- LocalDateTime A date-time without a time-zone in the ISO-8601 calendar system, such as 2007-12-03T10:15:30.注意没有时区信息。ZonedDateTime,指定某个时区中的一个时间点。关于处理带有时区的片段时间,使用Period,而非Duration,Period有夏令时的时间变化。以后我们处理时间段,基本都会使用Period。
- TemporalAdjuster处理日历的常用信息。例如找到某月的第几个星期一。该类位于java.time.temporal包中,java.time还有4个子包,分别是zone(处理时区),chrono(通用API以外的日历系统默认的ISO。),format(格式化),temporal(时间处理)。
- 使用DateTimeFormatter格式化时间。
- LocalData是一个年月日的日期,如果你的程序大量使用日期(不含时间),就可以用它的。反正我们现在项目就是这样,用Calendar的时候,还要把时间信息清零,很是麻烦。最大的利好!终于把Date的反人类设计,月份从0开始改为了1。常用方法详见api。里面有各种计算,甚至还能加上减去一个周期,参数为Period对象或者Duration
- LocalTime表示一天的时间。LocalDataTime既有时间又有日期。
- Java 8新特性探究(七)深入解析日期和时间-JSR310,网上搜到文章,一看日期,尼玛4年前。哥们玩了四年吗?
新的JS引擎Nashorn
搜了一下Nashorn的英文,犀利啊,犀牛式自行反坦克炮。前代官方自带JS引擎还是犀牛(Rhino)呢。速度更快,更好的利用JVM的性能。对ECMA有更好的兼容性。在我的android项目中,使用了Rhino库。android里面要想使用js引擎必须引入jar包。在android中引入js引擎之前,我们的项目一直采用new webview的方式加载js,速度非常慢,还必须在主线程中new Webview,而且有时候还发生调用无响应的情况。后来筛选了可用的js引擎,几个重量级的库都太大,还是引入了jdk官方自带的Rhino,一个叫rhino.jar的包。
JavaFX
JavaFX得到加强,现在Java PC客户端开发应该不多了吧?但是在公司写个带界面小工具还是很有用的。
题外话:
之前有做过公司的AWT项目,是一个房间清洁系统.KTV包房客人离场后,服务员进来检查包房内设施,然后在包房屏幕上对所检查的项目进行勾选,什么屏幕是否完好,这样的一个项目。当时我做出来的东西丑的要死,被老板嫌弃的一套糊涂。很有意思的是这个项目第一次用到JNI,java调用本地windows的ddl动态库,例子很难找,jni的jar包也超级难找。找到了发现还是包名为com.sun的。再次鄙视Oracle。
Lambda表达式和StreamAPI
函数式编程
我认为的函数式编程就是以函数为中心,把函数穿起来形成一个流的编程方法就是函数式编程。这和我们传统(本来还是先进的)的结构化编程是两种思想。函数式编程对特定的一些场景拥有绝对的优势,例如流式操作:要干啥–>干完啥还要干啥,还有并发和事件驱动上(android中有大量的事件)。结构化编程的代表作就是设计模式。对一项功能,一个工程按照面向对象的设计思想,进行抽象重用的设计。这两种编程模式,一个针对整体结构,一个针对具体操作。不能说谁能替代谁。不能滥用设计模式,也不能处处都用函数式。
Lambda表达式
一个lambda表达式,可以认为是一个带参数的代码块。所以在我们Android Studio中经常会把
1
2
3
4
5
6llContent.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
xxx;
}
});转换成
1
llContent.setOnClickListener((v)->{xxx;}).
这种事件,就是点击时我要执行xxx;传进去一个view对象。按照传统的写法,我们必须明确Callback的对象类型,这里必须传入一个View.OnClickListener的匿名对象。在点击时,android框架会调用这个OnClickListener对象,把view传进去。其实,我们并不关心Callback的对象类型,管你是View.OnClickListener还是什么鬼。只要你能接收一个View参数,然后被执行就可以了。这就是lambda的优势。省去类型执行,直接写参数代码块。(v)->{xxx;}这样的写法,系统会自动找到一个参数为view的回调设置进去。
想要代码在以后的某个时间点执行可以用lambda表达式
Lambda语法
参数 -> 代码.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19标准写法:
(View v)->{xxx;}
如果参数可推导出来,比如接收者是一个泛型,或则只有一个方法一个参数,总之就是参数的类型可以确定一个方法签名,类型就可以省略
(v) -> {xxx;}
如果只有一个参数,且可推导,括号可省略
v -> {xxx;}
如果代码只有一句话,可省略花括号
v -> xxx;
不用给表达式指定返回类型。而且,如果有返回值,就一定要返回
v -> {if( 1 < 0) return 1; };是并不合法的
如果没有参数
() -> {xxx;}
被执行的lambda只有一句代码的话,可以***传递方法引用***
1. (v) -> {System.out.println(v)} 可以直接写成System.out::println 传递的其实是println方法,冒号前面是对象,冒号后面是执行的方法,默认执行方法的参数就是lambda的参数
2. (x, y) -> {x.compareToIgnoreCase(y);} 可以直接写成String::compareToIgnoreCase,默认调用的是lambda第一个参数x的compareToIgnoreCase方法,compareToIgnoreCase的参数为lambda的第二个参数y
3. 方法引用传递还可以使用this和super。例如:x -> this.equals(x) 等同于this::equals,super同理。注意:这里的this和super都是函数式接口的匿名实现类的外部类。
4. 还可以这样 外部类.this::外部类方法 外部类.super::外部类方法
5. 构造方法引用同样可以传递 x -> new String(x); 等同于 String::new 数组也可以这样搞 x->new int[x] 等同 int[]::new
Java老版本也定义了很多只有一个方法的接口,例如:Runnable,java.util.Comparator,这种接口称为函数式接口,在Java新的支持Lambda的版本中(java8),lambda表达式只能赋值给函数式接口。so:在理解lambda表达式的时候,可以全面摒弃之前的思想,觉得他就是一个接口声明的简便形式。要理解成它一个代码块,把代码块传进去就能执行,而在编写lambda表达式的时候,只要遵循上面的语法,应该不会有什么大问题(参数) -> {代码},在旧的代码中,每个接口有固定的作用,比如java.util.Comparator。没有lambda的时候,你只能老老实实的new一个java.util.Comparator实现类然后传进sort方法中。
在有lambda的情况下你可以这么写1
2
3Arrays.sort(a, (one, tow) -> {
return 0;
});也可以这么写
1
2
3
4
5Comparator c = (one, tow) -> {
return 0;
};
Arrays.sort(a, c);也就是说,
1
2
3
4(one, tow) -> {
return 0;
}这段lambda可以赋值给任何具有 两个参数一个返回值的函数式接口。
java8中添加了java.util.function包,里面有各式各样的函数式接口。如果你需要【两个参数一个返回值】这样的函数式接口,最好从这个包里找,不要直接使用Comparator,虽然他们的形式是一样的,但是在java的世界里,他们还是属于不同的类型。
例如:BiFunction这个接口和Comparator是一样的。但是你总不能这么写吧? 1
2
3
4
5BiFunction b = (one, tow) -> {
return 0;
};
Arrays.sort(a, b);//这里就错了,sort方法只能接收一个Comparator,而不是BiFunction。@FunctionalInterface可以标注在函数式接口的接口上,让编译器帮助检查该函数式接口的合法性。就是你不写这个注解,只有一个抽象方法的接口类也是函数式接口,可以接收lambda。
- 使用lambda的时候,异常信息也要匹配,才能成功转换。如果一个函数式接口没有抛出异常
- lambda表达式可以使用外部闭合域的变量,拥有闭包行为,但是不能修改它的值,这个跟我们匿名内部类使用外部类的变量必须是final的有点类似,但是在这里lambda表达式里面使用的变量不用声明成final的。
接口的默认方法和静态方法
- 定义方法使用default关键字:例如default String getName(){return “xxx”};
- 冲突规则:如果接口中的默认方法,和父类中的方法冲突,父类优先。如果两个接口的默认方法冲突,必须在实现类中手动解决实现。
- 接口可以添加静态方法
Stream API
Java 8为提供函数式编程特别支持的工具包java.util.stream,Stream API都在里面。在java8里,Collection接口中添加了stream()方法,可以把集合转为一个流,使用parallelStream可转换成一个支持并发的流。如果是一个数组,可以使用Stream.of方法转换,Arrays.stream也可以把数组转为流,并且可以截取一段数组。Stream中含有各种生成流的方式,有生成空流的empty,有可以生成带元素的generate,生成序列的iterate。还有很多类提供了转换流的方式,例如正则表达式的Pattern.splitAsStream。
Stream实现了AutoCloseable接口,可以关闭,也可以支持java7中自动关闭资源语法。
流是延迟操作的,经过一些列的转换,执行逻辑,到最后执行一个方法才会进行真正的操作。
流转换
- filter 过滤:通过过滤条件生成新的流。它接收的函数式接口为一个参数为T,返回值为布尔的接口,这里的接口名为Predicate。再重申一次,真正的函数式编程并不在乎这个接口的类型是什么,因为函数式只是一个有参数有返回值的代码块,只是因为Java要兼容以前的基于接口的结构化编程,所以才这么做。在别的彻底支持函数式编程的语言中,同样形式的代码块是可以通用的。后面我们再看到一些函数式接口,不要在意它的类型,重点关注参数和返回值。一般参数和返回值都写在函数式接口的类泛型声明中。
- map 映射:对流中的每个元素执行方法,产生一个新的流。如果是流中套流,可以使用flatmap展开
- limit(n):截取长度为n的流元素为新的流。skip(n):丢弃n个元素生成新的流。concat:连接两个流,第一个流不能是无限流。peek:产生一个完全相同的流,peek新流时,每获取一个元素会执行一个方法。peek可以检测原始流中的内容是否已改变。
- 有状态的转换: 1,distinct去重后,按原顺序返回一个流 2,sorted 排序。和集合中的排序不同,它会产生新的流,集合中的排序是对原有集合排序。
- 终止操作:代表要从一个流中取结果了,进行了终止操作的流无法再进行转换,所有被延迟的操作都会立即执行,返回值是一个Optional
- 聚合操作:count,min,max
- 查找最终值:findFirst 查找第一个就返回,findAny查找全部,发现一个立即返回,常用于并行操作时找到的第一个执行完成的任务后,结束整个计算。
- parallel: 获取一个并发流,如果不是并发流会获取新的并发流。如果是并发流,会获取自己。
- 插播一条文章:Java8中 Parallel Streams 的陷阱 [译],文章大意是带并发的流共享一个线程池,有可能被和本业务不相关的流阻塞掉。这里其实跟上一篇Java 7介绍并发说的一样,如果使用自带的线程池,所有并发api共享同一个线程池。
- reduce,持续操作,每次迭代传入上次结果和当前值。可以指定起始位置。
Optional
Java为消除空指针而做出的努力!你如果这个Optional只是一个封装类,那就太小看它了。
isPresent():可以看下有没有包含结果,返回值为布尔。get()为获取结果,如果没有结果硬要get,只能送你一NoSuchElementException。
public void ifPresent(Consumer<? super T> consumer):带有参数的isPresent。参数是一个函数式接口,在有值得情况下,会把值传递给这个函数式接口。Consumer是一个有一个参数且没有返回值的函数式接口。该方法没有返回值。
map:可以对结果进行处理,再次返回一个Optional,这样做的好处是,你不用先判断结果可不可用再去处理它。而是先处理了再说,处理完后再看结果。
orElse,orElseGet:都可以获取如果没有结果的默认值。也许你以后可以写optional.orElse(“”),不用再写str == null ? “” : str;
Optional的构造函数是私有的,只能通过静态的几个of方法来创建。
Optional的flatmap和Stream的flatmap是一样的,都是属于可展开结果的方法。如果你的结果为OptionalOptional<U>
,那么你调用flatmap(T::f),可以直接得到Optional<U>
结果.
收集结果
得到Optional的方法一般都是只有一个结果的聚合方法,如果想得到一个集合可以通过iteractor。通过toArray可以获取一个数组,可以传入String[]::new这样的方法引用来获取正确的类型,如果不传,将获取一个Object[]。
collect用来把流中的元素收集到集合中。collect方法有一个参数,参数参考Collectors,可以选择用Set,List还是具体HashSet类型收集。stream.collect(Collectors.toCollection(Hashset::new)),Collectors的作用还有很多,还可以用toMap把结果收集在map里,收集到map时需注意,键不能一样,如果一样会抛异常,不过还可以使用带有第三个参数的tomap进行处理,他会把新值旧值都给你,让你选一个。具体可查看Collectors的文档,这玩意就是为了收集结果用的帮助类。收集时注意并发问题,使用toConcurrentMap。
Collectors:还可以分组,groupingBy返回一个map。接收一个函数式获取key,同样key的元素将被收集到一个list做为map的value。还可以收集的集合类型,可以选择是list还是set。partitioningBy跟groupingBy差不多,但是它接收的函数式只能返回布尔,也就是只能分成两组。
mapping方法可以对结果执行函数。
foreach也是终止操作,foreachOrder可以按顺序执行。
maptoInt可以把对象流转换为原始类型流IntStream。boxed可以把原始类型流装箱为对象流。
特别注意:使用流操作时,要认真看api文档,如果文档有说:“This is a terminal operation.”,那么这个操作就是终止操作,流使用完就不能再使用了
并行流
流的行为模式分为串行流和并行流,一般默认生成的都是串行流。parallel方法可以把任意未结束的串行流转换为并行流。时刻谨记:并行流中执行的代码是并行的,线程不安全的!!!
有序和无序:从有序集合转过去的就是有序流,无序集合转的就是无序的。有无顺序并不影响并行操作结果,它会并行执行,然后把结果有序的组织起来。如果放弃顺序,可以得到性能上的提升。
函数式接口
函数式接口 | 参数类型 | 返回类型 | api文档描述 | 特殊解释 |
---|---|---|---|---|
BiConsumer |
T,U | void | Represents an operation that accepts two input arguments and returns no result. | |
BiFunction |
T,U | R | Represents a function that accepts two arguments and produces a result. | |
BinaryOperator |
T,T | T | Represents an operation upon two operands of the same type, producing a result of the same type as the operands. | BiFunction的子类,日常用于第一个参数是上一次跌的结果,第二参数是当前元素,返回值是运算后的与当前元素同类型的参数 |
BiPredicate |
T,U | Boolean | Represents a predicate (boolean-valued function) of two arguments. | |
BooleanSupplier | void | Boolean | Represents a supplier of boolean-valued results. | |
Consumer |
T | void | Represents an operation that accepts a single input argument and returns no result. | |
DoubleBinaryOperator | Double,Double | Double | Represents an operation upon two double-valued operands and producing a double-valued result. | |
DoubleConsumer | Double | void | Represents an operation that accepts a single double-valued argument and returns no result. | |
DoubleFunction |
Double | R | Represents a function that accepts a double-valued argument and produces a result. | |
DoublePredicate | Double | Boolean | Represents a predicate (boolean-valued function) of one double-valued argument. | |
DoubleSupplier | VOID | double | Represents a supplier of double-valued results. | |
DoubleToIntFunction | double | int | Represents a function that accepts a double-valued argument and produces an int-valued result. | |
DoubleToLongFunction | double | long | Represents a function that accepts a double-valued argument and produces a long-valued result. | |
DoubleUnaryOperator | double | double | Represents an operation on a single double-valued operand that produces a double-valued result. | |
Function |
T | R | Represents a function that accepts one argument and produces a result. | |
IntBinaryOperator | int, int | int | Represents an operation upon two int-valued operands and producing an int-valued result. | |
IntConsumer | int | void | Represents an operation that accepts a single int-valued argument and returns no result. | |
IntFunction |
int | R | Represents a function that accepts an int-valued argument and produces a result. | |
IntPredicate | int | boolean | Represents a predicate (boolean-valued function) of one int-valued argument. | |
IntSupplier | void | int | Represents a supplier of int-valued results. | |
IntToDoubleFunction | int | double | Represents a function that accepts an int-valued argument and produces a double-valued result. | |
IntToLongFunction | int | long | Represents a function that accepts an int-valued argument and produces a long-valued result. | |
IntUnaryOperator | int | int | Represents an operation on a single int-valued operand that produces an int-valued result. | |
LongBinaryOperator | long,long | long | Represents an operation upon two long-valued operands and producing a long-valued result. | |
LongConsumer | long | void | Represents an operation that accepts a single long-valued argument and returns no result. | |
LongFunction |
long | R | Represents a function that accepts a long-valued argument and produces a result. | |
LongPredicate | long | boolean | Represents a predicate (boolean-valued function) of one long-valued argument. | |
LongSupplier | void | long | Represents a supplier of long-valued results. | |
LongToDoubleFunction | long | double | Represents a function that accepts a long-valued argument and produces a double-valued result. | |
LongToIntFunction | long | int | Represents a function that accepts a long-valued argument and produces an int-valued result. | |
LongUnaryOperator | long | long | Represents an operation on a single long-valued operand that produces a long-valued result. | |
ObjDoubleConsumer |
T,double | void | Represents an operation that accepts an object-valued and a double-valued argument, and returns no result. | |
ObjIntConsumer |
T,int | void | Represents an operation that accepts an object-valued and a int-valued argument, and returns no result. | |
ObjLongConsumer |
T,long | void | Represents an operation that accepts an object-valued and a long-valued argument, and returns no result. | |
Predicate |
T | boolean | Represents a predicate (boolean-valued function) of one argument. | |
Supplier |
void | T | Represents a supplier of results. | |
ToDoubleBiFunction |
T,U | Double | Represents a function that accepts two arguments and produces a double-valued result. | |
ToDoubleFunction |
T | double | Represents a function that produces a double-valued result. | |
ToIntBiFunction |
T,U | long | Represents a function that accepts two arguments and produces an int-valued result. | |
ToIntFunction |
T | int | Represents a function that produces an int-valued result. | |
ToLongBiFunction |
T,U | long | Represents a function that accepts two arguments and produces a long-valued result. | |
ToLongFunction |
T | long | Represents a function that produces a long-valued result. | |
UnaryOperator |
T | T | Represents an operation on a single operand that produces a result of the same type as its operand. |
Api文档上Java8 function包中的函数式接口全在这里了,我特意把参数和返回值分开,方便查询。这些函数式接口有的是继承关系。为什么要继承呢?因为有的泛型类型不同,但是个数相同。还有就是继承关系可以承接父接口的静态或者默认方法。这些函数式接口大部分都有默认或者静态方法,在使用lambda表达式时非常有用。
函数式接口带来的性能分析疑问
目前看来,要使用lambda表达式,必须有函数式接口。可以说lambda表达式可以和函数式接口互相转换。在以前的java版本,所谓的函数式接口也就是我们普通认为的回调。
而且lambda有一个问题,它被写在类中,这类似于以前我们所写的匿名内部类。每个做android的可能对此印象更加深刻,因为在activity中使用匿名内部类,很大可能性的会造成内存泄露,匿名内部类会持有外部类的对象,如果JVM把lambda当成和匿名内部类一样处理方式,我们还能畅快的使用lambda吗?
我们在android中,避免内部类持有外部类对象的一个做法是把这个类声明为static的。但是匿名内部类并不能这么做,除非你不使用匿名内部类,而是使用内部类。不管是内部类,还是匿名内部,都会生成一个带$符的class文件,而且使用它的时候,jvm其实还是当成正常的类去加载,还是会创建这个类的实例。如果大量使用,势必会引起频繁GC,加载类过多过缓。
带这这些疑问使用lambda,心里肯定不好受,要去探究答案就要去反编译lambda编译成的class文件,和调用代码的class文件。在上一篇文章说Java 7的时候,说到了一个invokedynamic,那篇文章已经说了,在java7当中,任何api都无法生成带invokedynamic指令的class文件,而ASM等第三方库可以生成,而且一些基于jvm的非java语言也可以生成并使用。一晃眼!到了java8,invokedynamic终于被使用了,具体invokedynamic指令是什么意思可以自行google,也可以看我上一篇文章。
没错,lambda表达式可以生成这样的指令,invokedynamic这玩意可以延迟决定需要调用哪个方法。而反编译lambda表达式生成class文件也看到并没有生成新的内部类的class文件,而是生成了一个static的方法。当运行时,会被invokedynamic指向这个方法。具体可以查看这篇文章深入探索Java 8 Lambda表达式,非常的详细,里面说,lambda表达式不但是简化书写,而且会提升性能。这才是我们想要的。
Ending
发展趋势
从Java7主打JVM支持多种语言,到Java8主推Lambda表达式的函数式编程,Java被别的优秀语言特性影响已经是不可逆的了。据说Java9将支持模块化,文章推荐:关于 Java 9 你所需要知道的一切。不难看出Java生态圈未来的趋势:主推强类型Java语言,然后以弱类型JVM脚本语言为辅助。函数式,响应式编程大规模应用,项目的模块化管理。优化的越来越好的并发库,可以应对未来硬件多核心cpu的发展。
每个版本重点优化的地方都有并发,分支合并模型,NIO,以应对java在大数据方面的优势。前10年,J2EE垄断企业级开发web端。后十年要在大数据分布式上发力吗?
Android的Java8
上周刚出的as3.0也支持了java8的部分特性,开篇也附上了几个链接。再附上一片官方的文章。里面讲到新的Java8编译器采用了Jack工具链,之前javac编辑器都是java文件编译成.class,然后使用dx把.class编译成.dex。现在Jack工具链直接把.java文件编译成.jack,然后又转换成.dex。这是不是依偎着之前AOP编程,直接修改class的工具库都不能用了?
官方这样说 :
已知问题
Instant Run 目前不能用于 Jack,在使用新的工具链时将被停用。由于 Jack 在编译应用时不生成中间类文件,依赖这些文件的工具目前不能用于 Jack。下面是一些工具示例:
对类文件进行操作的 Lint 检测工具
需要应用类文件的工具和库(例如使用 JaCoCo 进行仪器测试中)
如果您在使用 Jack 的过程中发现其他问题,请提交错误。
看来,估计,大概是用不了了。看来我们的项目能用上Java8也是要一段时间的(不可能放弃那些修改class文件的库,不然刚上的那些性能分析,无痕埋点怎么办)。
是不是应该有相应的修改.jack文件的库出来呢?