前言
成爲一名優秀的Android開發,需要一份完備的知識體系,在這裏,讓我們一起成長爲自己所想的那樣~。
一、前置知識
1、函數式編程
1)、什麼是函數式編程?
面向對象編程是對數據進行抽象,而函數式編程是對行爲進行抽象。現實世界中,數據和行爲並存,而程序也是如此。
2)爲什麼要學習函數式編程?
- 用函數(行爲)對數據處理,是學習大數據的基石。
- 好的效率(併發執行),
- 完成一個功能使用更少的代碼。
- 對象轉向面向函數編程的思想有一定難度,需要大量的練習
2、Java 1.8 Lambda 表達式
1)、什麼是 Lambda 表達式?
Lambda 是一個匿名函數,即沒有函數名的函數,它簡化了匿名委託的使用,讓代碼更加簡潔。
2)、兩個簡單的 Lambda 表達式示例
//匿名內部類
Runnable r = new Runnable() {
@Override
public void run() {
System.out.print("hello toroot");
}
};
//lambda
Runnable r2 = ()->System.out.print("hello toroot");
//匿名內部類
TreeSet<String> ts = new TreeSet<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return Long.compare(o1.length(),o2.length());
}
});
//lambda
TreeSet<String> ts2 = new TreeSet<>((o1,o2)-> Long.compare(o1.length(),o2.length()));
3)、Lambda 表達式語法
Lambda 表達式在 Java 語言中引入了一個新的語法元素和操作符。這個操作符爲 “->” ,該操作符被稱爲 Lambda 操作符或剪頭操作符。
它將 Lambda 分爲兩個部分:
- 左側:指定了 Lambda 表達式需要的所有參數。
- 右側:指定了 Lambda 體,即 Lambda 表達式要執行的功能。
4)、Lambda 表達式語法格式
- 1、無參數,無返回值:
() -> System.out.println("Hello Lambda!");
- 2、有一個參數,並且無返回值:
(x) -> System.out.println(x)
- 3、若只有一個參數,小括號可以省略不寫:
x -> System.out.println(x)
- 4、有兩個以上的參數,有返回值,並且 Lambda 體中有多條語句:
Comparator<Integer> com = (x, y) -> {
System.out.println("函數式接口");
return Integer.compare(x, y);
};
- 5、若 Lambda 體中只有一條語句, return 和 大括號 都可以省略不寫:
Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
- 6、Lambda 表達式的參數列表的數據類型可以省略不寫,因爲 JVM 編譯器可以通過上下文推斷出數據類型,即“類型推斷”:
(Integer x, Integer y) -> Integer.compare(x, y);
5)、函數式接口
Lambda 表達式需要“函數式接口”的支持。函數式接口即 接口中只有一個抽象方法的接口,稱爲函數式接口。 可以使用註解 @FunctionalInterface
修飾,它可以檢查是否是函數式接口。函數式接口的使用示例如下所示:
@FunctionalInterface
public interface MyFun {
public double getValue();
}
@FunctionalInterface
public interface MyFun<T> {
public T getValue(T t);
}
public static void main(String[] args) {
String newStr = toUpperString((str)->str.toUpperCase(),"toroot");
System.out.println(newStr);
}
public static String toUpperString(MyFun<String> mf,String str) {
return mf.getValue(str);
}
6)、Java 內置函數式接口
接口 | 參數 | 返回類型 | 示例 |
---|---|---|---|
Predicate | T | boolean | 這道題對了嗎? |
Consumer | T | void | 輸出一個值 |
Function<T,R> | T | R | 獲得 Person對象的名字 |
Supplier | None | T | 工廠方法 |
UnaryOperator | T | T | 邏輯非 (!) |
BinaryOperator | (T, T) | T | 求兩個數的乘積 (*) |
7)、方法引用
當要傳遞給 Lambda 體的操作,已經有實現的方法了,可以使用方法引用。方法引用使用 操作符 “ ::” 將方法名和對象或類的名字分隔開來。它主要有如下 三種 使用情況 :
- 對象 :: 實例方法
- 類 :: 靜態方法
- 類 :: 實例方法
8)、什麼時候可以用 :: 方法引用(重點)
在我們使用 Lambda 表達式的時候,”->” 右邊部分是要執行的代碼,即要完成的功能,可以把這部分稱作 Lambda 體。有時候,當我們想要實現一個函數式接口的那個抽象方法,但是已經有類實現了我們想要的功能,這個時候我們就可以用方法引用來直接使用現有類的功能去實現。示例代碼如下所示:
Person p1 = new Person("Av",18,90);
Person p2 = new Person("King",20,0);
Person p3 = new Person("Lance",17,100);
List<Person> list = new ArrayList<>();
list.add(p1);
list.add(p2);
list.add(p3);
// 這裏我們需要比較 list 裏面的 person,按照年齡排序
// 那麼我們最常見的做法是
// sort(List<T> list, Comparator<? super T> c)
// 1、因爲我們的 sort 方法的第二個參數是一個接口,所以我們需要實現一個匿名內部類
Collections.sort(list, new Comparator<Person>() {
@Override
public int compare(Person person1, Person person2) {
return person1.getAge().compareTo(person2.getAge());
}
});
// 2、因爲第二個參數是一個 @FunctionalInterface 的函數式接口,所以我們可以用 lambda 寫法
Collections.sort(list, (person1,person2) -> p1.getScore().compareTo(p2.getAge()));
// 3、因爲第二個參數我們可以用lambda的方式去實現,
// 但是剛好又有代碼 Comparator.comparing 已經實現了這個功能
// 這個時候我們就可以採用方法引用了
/**
* 重點:
* 當我們想要實現一個函數式接口的那個抽象方法,但是已經有類實現了我們想要的功能,
* 這個時候我們就可以用方法引用來直接使用現有類的功能去實現。
*/
Collections.sort(list, Comparator.comparing(Person::getAge));
System.out.println(list);
其它 Java 內置的函數式接口示例如下所示:
public static void main(String[] args) {
Consumer<String> c = x->System.out.println(x);
// 等同於
Consumer<String> c2 = System.out::print;
}
public static void main(String[] args) {
BinaryOperator<Double> bo = (n1,n2) ->Math.pow(n1,n2);
BinaryOperator<Double> bo2 = Math::pow;
}
public static void main(String[] args) {
BiPredicate<String,String> bp = (str1,str2) ->str1.equals(str2);
BiPredicate<String,String> bp2 = String::equals;
}
注意:當需要引用方法的第一個參數是調用對象,並且第二個參數是需要引用方法的第二個參數(或無參數)時,使用
ClassName::methodName
。
9)、構造器引用
格式: ClassName :: new
與函數式接口相結合,自動與函數式接口中方法兼容。
可以把構造器引用賦值給定義的方法,但是構造器參數列表要與接口中抽象方法的參數列表一致。示例如下所示:
public static void main(String[] args) {
Supplier<Person> x = ()->new Person();
Supplier<Person> x2 = Person::new;
}
public static void main(String[] args) {
Function<String,Person> f = x->new Person(x);
Function<String,Person> f2 = Person::new;
}
10)、數組引用
格式: type[] :: new,示例如下所示:
public static void main(String[] args) {
Function<Integer,Person[]> f = x->new Person[x];
Function<Integer,Person[]> f2 = Person[]::new;
}
3、Stream API
1)、Stream 是什麼?
Stream 是數據渠道,用於操作數據源(集合、數組等)所生成的元素序列。記住:“集合講的是數據,流講的是計算!”
2)、特點
- 1)、Stream 自己不會存儲元素。
- 2)、Stream 不會改變源對象。相反,他們會返回一個持有結果的新 Stream。
- 3)、Stream 操作是延遲執行的。這意味着他們會等到需要結果的時候才執行。
3)、一個 Stream 操作實例
取出所有大於18歲人的姓名,按字典排序,並輸出到控制檯,代碼如下所示:
private static List<Person> persons = Arrays.asList(
new Person("CJK",19,"女"),
new Person("BODUO",20,"女"),
new Person("JZ",21,"女"),
new Person("anglebabby",18,"女"),
new Person("huangxiaoming",5,"男"),
new Person("ROY",18,"男")
);
public static void main(String[] args) throws IOException {
persons.stream().filter(x- >x.getAge()>=18).map(Person::getName).sorted().forEach(System.out::println);
}
4)、Stream 的操作三個步驟
- 1、創建 Stream:一個數據源(如:集合、數組),獲取一個流。
- 2、中間操作:一箇中間操作鏈,對數據源的數據進行處理。
- 3、終止操作(終端操作):一個終止操作,執行中間操作鏈,併產生結果。
1、創建 Steam
創建流主要有四種方式,其示例代碼如下所示:
@Test
public void test1(){
//1. Collection 提供了兩個方法 stream() 與 parallelStream()
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream(); //獲取一個順序流
Stream<String> parallelStream = list.parallelStream(); //獲取一個並行流
//2. 通過 Arrays 中的 stream() 獲取一個數組流
Integer[] nums = new Integer[10];
Stream<Integer> stream1 = Arrays.stream(nums);
//3. 通過 Stream 類中靜態方法 of()
Stream<Integer> stream2 = Stream.of(1,2,3,4,5,6);
//4. 創建無限流
//迭代
Stream<Integer> stream3 = Stream.iterate(0, (x) -> x + 2).limit(10);
stream3.forEach(System.out::println);
//生成
Stream<Double> stream4 = Stream.generate(Math::random).limit(2);
stream4.forEach(System.out::println);
}
2、中間操作
- 1)、篩選與切片
- filter:接收 Lambda ,從流中排除某些元素。
- limit:截斷流,使其元素不超過給定數量。
- skip(n):跳過元素,返回一個扔掉了前 n 個元素的流。若流中元素不足 n 個,則返回一個空流。與 limit(n) 互補。
- distinct:篩選,通過流所生成元素的 hashCode() 和 equals() 去除重複元素。
- 2)、映射
- map:接收 Lambda ,將元素轉換成其他形式或提取信息。接收一個函數作爲參數,該函數會被應用到每個元素上,並將其映射成一個新的元素,類似於 python、go 的 map 語法。
- flatMap:接收一個函數作爲參數,將流中的每個值都換成另一個流,然後把所有流連接成一個流。
- 3)、排序
- sorted():自然排序。
- sorted(Comparator com):定製排序。
這裏,我們給出一些常見的使用示例,如下所示:
1、有個數組 Integer[] ary = {1,2,3,4,5,6,7,8,9,10},取出中間的第三到第五個元素。
List<Integer> collect = Arrays.stream(ary).skip(2).limit(3).collect(Collectors.toList());
2、有個數組 Integer[] ary = {1,2,2,3,4,5,6,6,7,8,8,9,10},取出裏面的偶數,並去除重複。
List<Integer> list = Arrays.stream(ary).filter(x -> x % 2 == 0).distinct().collect(Collectors.toList());
Set<Integer> integerSet = Arrays.stream(ary).filter(x -> x % 2 == 0).collect(Collectors.toSet());
3、有個二維數組,要求把數組組合成一個一維數組,並排序(1,2,3,4,5……12)
Integer[][] ary = {{3,8,4,7,5}, {9,1,6,2}, {0,10,12,11} };
Arrays.stream(ary).flatMap(item->Arrays.stream(item)).sorted().forEach(System.out::println);
3)、終止操作
終端操作會從流的流水線生成結果。其結果可以是任何不是流的值,例如:List、Integer,甚至是 void 。
1、查找與匹配
接口 | 說明 |
---|---|
allMatch(Predicate p) | 檢查是否匹配所有元素 |
anyMatch(Predicate p) | 檢查是否至少匹配一個元素 |
noneMatch(Predicate p) | 檢查是否沒有匹配所有元素 |
findFirst() | 返回第一個元素 |
findAny() | 返回當前流中的任意元素 |
count() | 返回流中元素總數 |
max(Comparator c) | 返回流中最大值 |
min(Comparator c) | 返回流中最小值 |
forEach(Consumer c) | 迭代 |
2、歸約
reduce(T iden, BinaryOperator b) 可以將流中元素反覆結合起來,得到一個值。返回 Optional。例如使用 reduce 來求所有人員學生的總分的示例代碼如下所示:
Integer all = persons.stream().map(Person::getScore).reduce((integer, integer2) -> integer + integer2).get()
3、收集
- collect(Collector c) 將流轉換爲其他形式。它接收一個 Collector 接口的實現,用於給 Stream 中元素做彙總的方法。
- Collector 接口中方法的實現決定了如何對流執行收集操作(如收集到 List、Set、Map)。
- Collectors 實用類提供了很多靜態方法,可以方便地創建常見收集器實例。
收集相關的 Stream API 與其實例代碼如下所示:
- 1)、toList List 把流中元素收集到 List:
List<Person> emps= list.stream().collect(Collectors.toList());
- 2)、toSet Set 把流中元素收集 到Set:
Set<Person> emps= list.stream().collect(Collectors.toSet());
- 3)、toCollection Collection 把流中元素收集到創建的集合:
Collection<Person> emps=list.stream().collect(Collectors.toCollection(ArrayList::new));
- 4)、counting Long 計算流中元素的個數:
long count = list.stream().collect(Collectors.counting());
- 5)、summing Int Integer 對流中元素的整數屬性求和:
int total=list.stream().collect(Collectors.summingInt(Person::getAge));
- 6)、averaging Int Double 計算流中元素 Integer 屬性的平均值:
double avg= list.stream().collect(Collectors.averagingInt(Person::getAge));
- 7)、summarizingInt IntSummaryStatistics 收集流中 Integer 屬性的統計值。如平均值:
Int SummaryStatisticsiss= list.stream().collect(Collectors.summarizingInt(Person::getAge));
- 8)、joining String 連接流中每個字符串:
String str= list.stream().map(Person::getName).collect(Collectors.joining());
- 9)、maxBy Optional 根據比較器選擇最大值:
Optional<Person> max= list.stream().collect(Collectors.maxBy(comparingInt(Person::getSalary)));
- 10)、minBy Optional 根據比較器選擇最小值:
Optional<Person> min = list.stream().collect(Collectors.minBy(comparingInt(Person::getSalary)));
- 11)、reducing 歸約產生的類型,從一個作爲累加器的初始值開始,利用 BinaryOperator 與流中元素逐個結合,從而歸約成單個值:
int total=list.stream().collect(Collectors.reducing(0, Person::getSalary, Integer::sum));
- 12)、collectingAndThen 轉換函數返回的類型,包裹另一個收集器,對其結果轉換函數
int how= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));
- 13)、groupingBy Map<K, List> 根據某屬性值對流分組,屬性爲 K,結果爲 V:
Map<Person.Status, List<Person>> map= list.stream().collect(Collectors.groupingBy(Person::getStatus));
- 14)、partitioningBy Map<Boolean, List> 根 據true 或 false 進行分區:
Map<Boolean,List<Person>>vd= list.stream().collect(Collectors.partitioningBy(Person::getManage));
4、終止操作練習案例
- 1)、取出Person對象的所有名字,放到 List 集合中:
List<String> collect2 = persons.stream().map(Person::getName).collect(Collectors.toList());
- 2、求 Person 對象集合的分數的平均分、總分、最高分,最低分,分數的個數:
IntSummaryStatistics collect = persons.stream().collect(Collectors.summarizingInt(Person::getScore));
System.out.println(collect);
- 3、根據成績分組,及格的放一組,不及格的放另外一組:
Map<Boolean, List<Person>> collect1 = persons.stream().collect(Collectors.partitioningBy(person -> person.getScore() >= 60));
System.out.println(collect1);
- 4、統計 aa.txt 裏面的單詞數:
public static void main(String[] args) throws IOException {
InputStream resourceAsStream = Person.class.getClassLoader().getResourceAsStream("aa.txt");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(resourceAsStream));
bufferedReader.lines().flatMap(x->Stream.of(x.split(" "))).sorted().collect(Collectors.groupingBy(String::toString)).forEach((a,b)-> System.out.println(a+":"+b.size()));
bufferedReader.close();
}
4、複雜泛型
1)、泛型是什麼?
泛型,即 “參數化類型”。就是將類型由原來的具體的類型參數化,類似於方法中的變量參數,此時類型也定義成參數形式(可以稱之爲類型形參),然後在使用/調用時傳入具體的類型(類型實參)。
2)、泛型的好處
- 適用於多種數據類型執行相同的代碼。
- 泛型中的類型在使用時指定,不需要強制類型轉換。
3)、泛型類和泛型接口
泛型的本質是爲了參數化類型(在不創建新的類型的情況下,通過泛型指定的不同類型來控制形參具體限制的類型)。而這種參數類型可以用在類、接口和方法中,分別被稱爲 泛型類、泛型接口、泛型方法。
泛型類
引入一個類型變量T(其他大寫字母都可以,不過常用的就是T,E,K,V等等),並且用<>括起來,並放在類名的後面。泛型類是允許有多個類型變量的。常見的示例代碼如下所示:
public class NormalGeneric<K> {
private K data;
public NormalGeneric() {
}
public NormalGeneric(K data) {
this.data = data;
}
public K getData() {
return data;
}
public void setData(K data) {
this.data = data;
}
}
public class NormalGeneric2<T,K> {
private T data;
private K result;
public NormalGeneric2() {
}
public NormalGeneric2(T data) {
this();
this.data = data;
}
public NormalGeneric2(T data, K result) {
this.data = data;
this.result = result;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public K getResult() {
return result;
}
public void setResult(K result) {
this.result = result;
}
}
泛型接口
泛型接口與泛型類的定義基本相同。示例代碼如下所示:
public interface Genertor<T> {
public T next();
}
但是,實現泛型接口的類,有兩種實現方法:
1、未傳入泛型實參
在 new 出類的實例時,需要指定具體類型:
public class ImplGenertor<T> implements Genertor<T> {
@Override
public T next() {
return null;
}
}
2、傳入泛型實參
在 new 出類的實例時,和普通的類沒區別。
public class ImplGenertor2 implements Genertor<String> {
@Override
public String next() {
return null;
}
}
泛型方法
泛型方法的 定義在 修飾符與返回值 的中間。示例代碼如下所示:
public <T> T genericMethod(T...a){
return a[a.length/2];
}
泛型方法,是在調用方法的時候指明泛型的具體類型,泛型方法可以在任何地方和任何場景中使用,包括普通類和泛型類。
泛型類中定義的普通方法和泛型方法的區別
在普通方法中:
// 雖然在方法中使用了泛型,但是這並不是一個泛型方法。
// 這只是類中一個普通的成員方法,只不過他的返回值是在聲明泛型類已經聲明過的泛型。
// 所以在這個方法中才可以繼續使用 T 這個泛型。
public T getKey(){
return key;
}
在泛型方法中:
/**
* 這纔是一個真正的泛型方法。
* 首先在 public 與返回值之間的 <T> 必不可少,這表明這是一個泛型方法,並且聲明瞭一個泛型 T
* 這個 T 可以出現在這個泛型方法的任意位置,泛型的數量也可以爲任意多個。
*/
public <T,K> K showKeyName(Generic<T> container){
// ...
}
4)、限定類型變量
public class ClassBorder<T extends Comparable> {
...
}
public class GenericRaw<T extends ArrayList&Comparable> {
...
}
-<T extends Comparable>
:T 表示應該綁定類型的子類型,Comparable 表示綁定類型,子類型和綁定類型可以是類也可以是接口。
- extends 左右都允許有多個,如 T,V extends Comparable&Serializable。
- 注意限定類型中,只允許有一個類,而且如果有類,這個類必須是限定列表的第一個。
- 限定類型變量既可以用在泛型方法上也可以用在泛型類上。
5)、泛型中的約束和侷限性
- 1、不能用基本類型實例化類型參數。
- 2、運行時類型查詢只適用於原始類型。
- 3、泛型類的靜態上下文中類型變量失效:不能在靜態域或方法中引用類型變量。因爲泛型是要在對象創建的時候才知道是什麼類型的,而對象創建的代碼執行先後順序是 static 的部分,然後纔是構造函數等等。所以在對象初始化之前 static 的部分已經執行了,如果你在靜態部分引用泛型,那麼毫無疑問虛擬機根本不知道是什麼東西,因爲這個時候類還沒有初始化。
- 4、不能創建參數化類型的數組,但是可以定義參數化類型的數組。
- 5、不能實例化類型變量。
- 6、不能使用 try-catch 捕獲泛型類的實例。
6)、泛型類型的繼承規則
泛型類可以繼承或者擴展其他泛型類,比如 List 和 ArrayList:
private static class ExtendPair<T> extends Pair<T>{
...
}
7)、通配符類型
?extends X
:表示類型的上界,類型參數是 X 的子類。?super X
:表示類型的下界,類型參數是 X 的超類。
?extends X
如果其中提供了 get 和 set 類型參數變量的方法的話,set 方法是不允許被調用的,會出現編譯錯誤,而 get 方法則沒問題。
?extends X 表示類型的上界,類型參數是 X 的子類,那麼可以肯定的說,get 方法返回的一定是個 X(不管是 X 或者 X 的子類)編譯器是可以確定知道的。但是 set 方法只知道傳入的是個 X,至於具體是 X 的哪個子類,是不知道的。
因此,?extends X 主要用於安全地訪問數據,可以訪問 X 及其子類型,並且不能寫入非 null 的數據。
?super X
如果其中提供了 get 和 set 類型參數變量的方法的話,set 方法可以被調用,且能傳入的參數只能是 X 或者 X 的子類。而 get 方法只會返回一個 Object 類型的值。
? super X 表示類型的下界,類型參數是 X 的超類(包括 X 本身),那麼可以肯定的說,get 方法返回的一定是個 X 的超類,那麼到底是哪個超類?不知道,但是可以肯定的說,Object 一定是它的超類,所以 get 方法返回 Object。編譯器是可以確定知道的。對於 set 方法來說,編譯器不知道它需要的確切類型,但是 X 和 X 的子類可以安全的轉型爲 X。
因此,?super X 主要用於安全地寫入數據,可以寫入 X 及其子類型。
無限定的通配符 ?
表示對類型沒有什麼限制,可以把 ?看成所有類型的父類,如 ArrayList<?>。
8)、虛擬機是如何實現泛型的?
泛型思想早在 C++ 語言的模板(Template)中就開始生根發芽,在 Java 語言處於還沒有出現泛型的版本時,只能通過 Object 是所有類型的父類和類型強制轉換兩個特點的配合來實現類型泛化。
由於 Java 語言裏面所有的類型都繼承於 java.lang.Object,所以 Object 轉型成任何對象都是有可能的。但是也因爲有無限的可能性,就只有程序員和運行期的虛擬機才知道這個 Object 到底是個什麼類型的對象。在編譯期間,編譯器無法檢查這個 Object 的強制轉型是否成功,如果僅僅依賴程序員去保障這項操作的正確性,許多 ClassCastException 的風險就會轉嫁到程序運行期之中。
此外,泛型技術在 C#/C++ 和 Java 之中的使用方式看似相同,但實現上卻有着根本性的分歧,C# 裏面的泛型無論在程序源碼中、編譯後的 IL 中(Intermediate Language,中間語言,這時候泛型是一個佔位符),或是運行期的 CLR 中,都是切實存在的,List<int> 與 List<String> 就是兩個不同的類型,它們在系統運行期生成,有自己的虛方法表和類型數據,這種實現稱爲類型膨脹,基於這種方法實現的泛型稱爲真實泛型。
而 Java 語言中的泛型則不一樣,它只在程序源碼中存在,在編譯後的字節碼文件中,就已經替換爲原來的原生類型(Raw Type,也稱爲裸類型)了,並且在相應的地方插入了強制轉型代碼,因此,對於運行期的 Java 語言來說,ArrayList<int> 與 ArrayList<String> 就是同一個類,所以 泛型技術實際上是 Java 語言的一顆語法糖,Java 語言中的泛型實現方法稱爲類型擦除,基於這種方法實現的泛型稱爲僞泛型。
將一段 Java 代碼編譯成 Class 文件,然後再用字節碼反編譯工具進行反編譯後,將會發現泛型都不見了,程序又變回了 Java 泛型出現之前的寫法,泛型類型都變回了原生類型。
由於 Java 泛型的引入,各種場景(虛擬機解析、反射等)下的方法調用都可能對原有的基礎產生影響和新的需求,如在泛型類中如何獲取傳入的參數化類型等。因此,JCP 組織對虛擬機規範做出了相應的修改,引入了諸如 Signature、LocalVariableTypeTable 等新的屬性用於解決伴隨泛型而來的參數類型的識別問題,Signature 是其中最重要的一項屬性,它的作用就是存儲一個方法在字節碼層面的特徵簽名,這個屬性中保存的參數類型並不是原生類型,而是包括了參數化類型的信息。修改後的虛擬機規範要求所有能識別 49.0 以上版本的 Class 文件的虛擬機都要能正確地識別 Signature 參數。
最後,從 Signature 屬性的出現我們還可以得出結論,擦除法所謂的擦除,僅僅是對方法的 Code 屬性中的字節碼進行擦除,實際上元數據中還是保留了泛型信息,這也是我們能通過反射手段取得參數化類型的根本依據。
二、初識 ByteX
ByteX 使用了純 Java 來編寫源碼,它是一個基於 Gradle transform api 和 ASM 的字節碼插樁平臺。
調試:gradle clean :example:assembleRelease -Dorg.gradle.debug=true --no-daemon
1、優勢
- 1)、自動集成到其它宿主和插件一起整合爲一個單獨的 MainTransformFlow,結合 class 文件多線程併發處理,避免了打包的額外時間呈線性增長。
- 2)、插件、宿主之間完全解耦,便於協同開發。
- 3)、common module 提供通用的代碼複用,每個插件只需專注自身的字節碼插樁邏輯。
2、MainTransformFlow 基本流程
在 MainTransformFlow implements MainProcessHandler
常規處理過程,會遍歷兩次工程構建中的所有 class。
- 1)、第一次,遍歷 traverse 與 traverseAndroidJar 過程,以形成完整的類圖。
- 2)、第二次,執行 transform:再遍歷一次工程中所有的構建產物,並對 class 文件做處理後輸出。
3、如何自定義獨立的 TransformFlow?
重寫 IPlugin 的 provideTransformFlow 即可。
4、類圖對象
context.getClassGraph() 獲取類圖對象,兩個 TransformFlow 的類圖是隔離的。
5、MainProcessHandler
- 通過複寫 process 方法,註冊自己的 FlieProcessor 來處理。
- FileProcessor 採用了責任鏈模式,每個 class 文件都會流經一系列的 FileProcessor 來處理。
6、IPlugin.hookTransformName()
使用 反射 Hook 方式 將 Transform 註冊到 proguard 之後。
三、ByteX 插件平臺構建流程探祕
添加 apply plugin: ‘bytex’ 之後,bytex 可以在 Gradle 的構建流程中起作用了。這裏的插件 id 爲 bytex,我們找到 bytex.properties 文件,查看裏面映射的實現類,如下所示:
implementation-class=com.ss.android.ugc.bytex.base.ByteXPlugin
可以看到,bytex 的實現類爲 ByteXPlugin,其源碼如下所示:
public class ByteXPlugin implements Plugin<Project> {
@Override
public void apply(@NotNull Project project) {
// 1
AppExtension android = project.getExtensions().getByType(AppExtension.class);
// 2
ByteXExtension extension = project.getExtensions().create("ByteX", ByteXExtension.class);
// 3
android.registerTransform(new ByteXTransform(new Context(project, android, extension)));
}
}
首先,註釋1處,獲取 Android 爲 App 提供的擴展屬性 AppExtension 實例。然後,在註釋2處,獲取 ByteX 自身創建的擴展屬性 ByteXExtension 實例。最後,在註釋3處,註冊 ByteXTransform 實例。ByteXTransform 繼承了抽象類 CommonTransform,其實現了關鍵的 transform 方法,其實現源碼如下所示:
@Override
public final void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
super.transform(transformInvocation);
// 1、如果不是增量模式,則清楚輸出目錄的文件。
if (!transformInvocation.isIncremental()) {
transformInvocation.getOutputProvider().deleteAll();
}
// 2、獲取 transformContext 實例。
TransformContext transformContext = getTransformContext(transformInvocation);
// 3、初始化 HtmlReporter(生成 ByteX 構建產生日誌的 HTML 文件)
init(transformContext);
// 4、過濾掉沒有打開插件開關的 plugin。
List<IPlugin> plugins = getPlugins().stream().filter(p -> p.enable(transformContext)).collect(Collectors.toList());
Timer timer = new Timer();
// 5、創建一個 transformEngine 實例。
TransformEngine transformEngine = new TransformEngine(transformContext);
try {
if (!plugins.isEmpty()) {
// 6、使用 PriorityQueue 對每一個 TransformFlow 進行優先級排序(在這裏添加的是與之對應的實現類 MainTransformFlow)。
Queue<TransformFlow> flowSet = new PriorityQueue<>((o1, o2) -> o2.getPriority() - o1.getPriority());
MainTransformFlow commonFlow = new MainTransformFlow(transformEngine);
flowSet.add(commonFlow);
for (int i = 0; i < plugins.size(); i++) {
// 7、給每一個 Plugin 註冊 MainTransformFlow,其實質是將每一個 Plugin 的 MainProcessHandler 添加到 MainTransformFlow 中的 handlers 列表中。
IPlugin plugin = plugins.get(i);
TransformFlow flow = plugin.registerTransformFlow(commonFlow, transformContext);
if (!flowSet.contains(flow)) {
flowSet.add(flow);
}
}
while (!flowSet.isEmpty()) {
TransformFlow flow = flowSet.poll();
if (flow != null) {
if (flowSet.size() == 0) {
flow.asTail();
}
// 8、按指定優先級執行每一個 TransformFlow 的 run 方法,默認只有一個 MainTransformFlow 實例。
flow.run();
// 9、獲取流中的 graph 類圖對象並清除。
Graph graph = flow.getClassGraph();
if (graph != null) {
//clear the class diagram.we won’t use it anymore
graph.clear();
}
}
}
} else {
transformEngine.skip();
}
// 10
afterTransform(transformInvocation);
} catch (Throwable throwable) {
LevelLog.sDefaultLogger.e(throwable.getClass().getName(), throwable);
throw throwable;
} finally {
for (IPlugin plugin : plugins) {
try {
plugin.afterExecute();
} catch (Throwable throwable) {
LevelLog.sDefaultLogger.e("do afterExecute", throwable);
}
}
transformContext.release();
release();
timer.record("Total cost time = [%s ms]");
if (BooleanProperty.ENABLE_HTML_LOG.value()) {
HtmlReporter.getInstance().createHtmlReporter(getName());
HtmlReporter.getInstance().reset();
}
}
}
在註釋7處,調用了 plugin.registerTransformFlow 方法,其源碼如下所示:
@Nonnull
@Override
public final TransformFlow registerTransformFlow(@Nonnull MainTransformFlow mainFlow, @Nonnull TransformContext transformContext) {
if (transformFlow == null) {
transformFlow = provideTransformFlow(mainFlow, transformContext);
if (transformFlow == null) {
throw new RuntimeException("TransformFlow can not be null.");
}
}
return transformFlow;
}
這裏繼續調用了 provideTransformFlow 方法,其源碼如下所示:
/**
* create a new transformFlow or just return mainFlow and append a handler.
* It will be called by {@link IPlugin#registerTransformFlow(MainTransformFlow, TransformContext)} when
* handle start.
*
* @param mainFlow main TransformFlow
* @param transformContext handle context
* @return return a new TransformFlow object if you want make a new flow for current plugin
*/
protected TransformFlow provideTransformFlow(@Nonnull MainTransformFlow mainFlow, @Nonnull TransformContext transformContext) {
return mainFlow.appendHandler(this);
}
可以看到,通過調用 mainFlow.appendHandler(this) 方法將每一個 Plugin 的 MainProcessHandler 添加到 MainTransformFlow 中的 handlers 列表之中。
在註釋8處,按指定優先級執行了每一個 TransformFlow 的 run 方法,默認只有一個 MainTransformFlow 實例。我們看到了 MianTransformFlow 的 run 方法:
@Override
public void run() throws IOException, InterruptedException {
try {
// 1
beginRun();
// 2
runTransform();
} finally {
// 3
endRun();
}
}
首先,在註釋1出,調用了 beginRun 方法,其實現如下:
// AbsTransformFlow
protected void beginRun() {
transformEngine.beginRun();
}
// TransformEngine
public void beginRun(){
context.markRunningState(false);
}
// TransformContext
private final AtomicBoolean running = new AtomicBoolean(false);
void markRunningState(boolean running) {
this.running.set(running);
}
最後,在 TransformContext 實例中使用了一個 AtomicBoolean 實例標記 MainTransformFlow 是否正在運行中。
然後,在註釋2處執行了 runTransform 方法,這裏就是真正執行 transform 的地方,其源碼如下所示:
private void runTransform() throws IOException, InterruptedException {
if (handlers.isEmpty()) return;
Timer timer = new Timer();
timer.startRecord("PRE_PROCESS");
timer.startRecord("INIT");
// 1、初始化 handlers 列表中的每一個 handler。
for (MainProcessHandler handler : handlers) {
handler.init(transformEngine);
}
timer.stopRecord("INIT", "Process init cost time = [%s ms]");
// 如果不是 跳過 traverse 僅僅只執行 Transform 方法時,才執行 traverse 過程。
if (!isOnePassEnough()) {
if (!handlers.isEmpty() && context.isIncremental()) {
timer.startRecord("TRAVERSE_INCREMENTAL");
// 2、如果是 增量模式,則執行 traverseArtifactOnly(僅僅增量遍歷產物)調用每一個 plugin 的對應的 MainProcessHandler 的 traverseIncremental 方法。這裏最終會調用 ClassFileAnalyzer.handle 方法進行遍歷分發操作。
traverseArtifactOnly(getProcessors(Process.TRAVERSE_INCREMENTAL, new ClassFileAnalyzer(context, Process.TRAVERSE_INCREMENTAL, null, handlers)));
timer.stopRecord("TRAVERSE_INCREMENTAL", "Process project all .class files cost time = [%s ms]");
}
handlers.forEach(plugin -> plugin.beforeTraverse(transformEngine));
timer.startRecord("LOADCACHE");
// 3、創建一個 CachedGraphBuilder 對象:能夠緩存 類圖 的 類圖構建者對象。
GraphBuilder graphBuilder = new CachedGraphBuilder(context.getGraphCache(), context.isIncremental(), context.shouldSaveCache());
if (context.isIncremental() && !graphBuilder.isCacheValid()) {
// 4、如果是增量更新 && graphBuilder 的緩存失效則直接請求非增量運行。
context.requestNotIncremental();
}
timer.stopRecord("LOADCACHE", "Process loading cache cost time = [%s ms]");
// 5、內部會調用 running.set(true) 來標記正在運行的狀態。
running();
if (!handlers.isEmpty()) {
timer.startRecord("PROJECT_CLASS");
// 6、執行 traverseArtifactOnly(遍歷產物)調用每一個 plugin 的對應的 MainProcessHandler 的 traverse 方法,這裏最終會調用 ClassFileAnalyzer.handle 方法進行遍歷分發操作。
traverseArtifactOnly(getProcessors(Process.TRAVERSE, new ClassFileAnalyzer(context, Process.TRAVERSE, graphBuilder, handlers)));
timer.stopRecord("PROJECT_CLASS", "Process project all .class files cost time = [%s ms]");
}
if (!handlers.isEmpty()) {
timer.startRecord("ANDROID");
// 7、僅僅遍歷 Android.jar
traverseAndroidJarOnly(getProcessors(Process.TRAVERSE_ANDROID, new ClassFileAnalyzer(context, Process.TRAVERSE_ANDROID, graphBuilder, handlers)));
timer.stopRecord("ANDROID", "Process android jar cost time = [%s ms]");
}
timer.startRecord("SAVECACHE");
// 8、構建 mClassGraph 類圖實例。
mClassGraph = graphBuilder.build();
timer.stopRecord("SAVECACHE", "Process saving cache cost time = [%s ms]");
}
timer.stopRecord("PRE_PROCESS", "Collect info cost time = [%s ms]");
if (!handlers.isEmpty()) {
timer.startRecord("PROCESS");
// 9、遍歷執行每一個 plugin 的 transform 方法。
transform(getProcessors(Process.TRANSFORM, new ClassFileTransformer(handlers, needPreVerify(), needVerify())));
timer.stopRecord("PROCESS", "Transform cost time = [%s ms]");
}
}
首先,在註釋1處,遍歷調用了每一個 MainProcessHandler 的 init 方法,它是用於 transform 開始前的初始化實現方法。
MainProcessHandler 接口的 init 方法是一個 default 方法,裏面直接調用了每一個 pluign 實現的 init 方法(如果 plugin 沒有實現,則僅僅調用 CommonPlugin 的實現的 init 方法:這裏通常是用於把不需要處理的文件添加到 mWhiteList 列表),這裏可以做一些plugin 的準備工作。
1、僅僅遍歷產物
traverseArtifactOnly(getProcessors(Process.TRAVERSE, new ClassFileAnalyzer(context, Process.TRAVERSE, graphBuilder, handlers)));
getProcessors 方法的源碼如下所示:
private FileProcessor[] getProcessors(Process process, FileHandler fileHandler) {
List<FileProcessor> processors = handlers.stream()
.flatMap((Function<MainProcessHandler, Stream<FileProcessor>>) handler -> handler.process(process).stream())
.collect(Collectors.toList());
switch (process) {
case TRAVERSE_INCREMENTAL:
processors.add(0, new FilterFileProcessor(fileData -> fileData.getStatus() != Status.NOTCHANGED));
processors.add(new IncrementalFileProcessor(handlers, ClassFileProcessor.newInstance(fileHandler)));
break;
case TRAVERSE:
case TRAVERSE_ANDROID:
case TRANSFORM:
processors.add(ClassFileProcessor.newInstance(fileHandler));
processors.add(0, new FilterFileProcessor(fileData -> fileData.getStatus() != Status.NOTCHANGED && fileData.getStatus() != Status.REMOVED))
break;
default:
throw new RuntimeException("Unknow Process:" + process);
}
return processors.toArray(new FileProcessor[0]);
}
這裏的 processor 的添加由 增量 進行界定,具體的處理標準如下:
TRAVERSE_INCREMENTAL
:FilterFileProcessor
:按照不同的過程過濾掉不需要的 FileData。IncrementalFileProcessor
:用於進行 增量文件的處理。
TRAVERSE/TRAVERSE_ANDROID/TRANSFORM
:FilterFileProcessor
:按照不同的過程過濾掉不需要的 FileData。ClassFileProcessor
:用於處理 .class 文件。
2、僅僅遍歷 Android Jar 包
traverseAndroidJarOnly(getProcessors(Process.TRAVERSE_ANDROID, new ClassFileAnalyzer(context, Process.TRAVERSE_ANDROID, graphBuilder, handlers)));
3、構建 mClassGraph 類圖對象
mClassGraph = graphBuilder.build();
4、執行 Transform
transform(getProcessors(Process.TRANSFORM, new ClassFileTransformer(handlers, needPreVerify(), needVerify())));
transform 的源碼如下所示:
// AbsTransformFlow 類中
protected AbsTransformFlow transform(FileProcessor... processors) throws IOException, InterruptedException {
beforeTransform(transformEngine);
transformEngine.transform(isLast, processors);
afterTransform(transformEngine);
return this;
}
1)、beforeTransform(transformEngine)
// MainTransformFlow
@Override
protected AbsTransformFlow beforeTransform(TransformEngine transformEngine) {
// 1
handlers.forEach(plugin -> plugin.beforeTransform(transformEngine));
return this;
}
註釋1處,遍歷執行 每一個 plugin 的 beforeTransform 方法做一些自身 transform 前的準備工作。
2)、transformEngine.transform(isLast, processors)
// TranformEngine
public void transform(boolean isLast, FileProcessor... processors) {
Schedulers.FORKJOINPOOL().invoke(new PerformTransformTask(context.allFiles(), getProcessorList(processors), isLast, context));
}
Shedulers.FORKJOINPOOL() 方法的源碼如下所示:
public class Schedulers {
private static final int cpuCount = Runtime.getRuntime().availableProcessors();
private final static ExecutorService IO = new ThreadPoolExecutor(0, cpuCount * 3,
30L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>());
// 1
private static final ExecutorService COMPUTATION = Executors.newWorkStealingPool(cpuCount);
public static Worker IO() {
return new Worker(IO);
}
public static Worker COMPUTATION() {
return new Worker(COMPUTATION);
}
public static ForkJoinPool FORKJOINPOOL() {
return (ForkJoinPool) COMPUTATION;
}
}
可以看到,最終是執行 Executors.newWorkStealingPool(cpuCount) 方法生成了一個 ForkJoinPool 實例。
ForkJoinPool 與 ThreadPoolExecutor 是屬於平級關係,ForkJoinPool 線程池是爲了實現“分治法”這一思想而創建的,通過把大任務拆分成小任務,然後再把小任務的結果彙總起來就是最終的結果,和 MapReduce 的思想很類似。除了“分治法”之外,ForkJoinPool 還使用了工作竊取算法,即所有線程均嘗試找到並執行已提交的任務,或是通過其他任務創建的子任務。有了它我們就可以儘量避免一個線程執行完自己的任務後“無所事事”的情況。
然後這裏會回調 PerformTransformTask 實例的 compute 方法,源碼如下所示:
@Override
protected void compute() {
if (outputFile) {
// 1、如果是最後一個 TransformFlow,則遞歸調用所有的 FileTransformTask。
List<FileTransformTask> tasks = source.map(cache -> new FileTransformTask(context, cache, processors)).collect(Collectors.toList());
// 2、對於Fork/Join模式,假如Pool裏面線程數量是固定的,那麼調用子任務的fork方法相當於A先分工給B,然後A當監工不幹活,B去完成A交代的任務。所以上面的模式相當於浪費了一個線程。那麼如果使用invokeAll相當於A分工給B後,A和B都去完成工作。這樣可以更好的利用線程池,縮短執行的時間。
invokeAll(tasks);
} else {
// 3、、遞歸調用 FileTransformTask
PerformTraverseTask traverseTask = new PerformTraverseTask(source, processors);
invokeAll(traverseTask);
}
}
在註釋1處,如果是最後一個 TransformFlow,則調用所有的 FileTransformTask。註釋2處,對於 Fork/Join 模式,假如 Pool 裏面線程數量是固定的,那麼調用子任務的 fork 方法相當於 A 先分工給 B,然後 A 當監工不幹活,B 去完成 A 交代的任務。所以上面的模式相當於浪費了一個線程。那麼如果使用 invokeAll 相當於 A 分工給 B 後,A 和 B 都去完成工作。這樣可以更好的利用線程池,縮短執行的時間。註釋3處,執行了 ForkJoinTask 的 invokeAll 方法,這裏便會回調 compute 方法,源碼如下所示:
@Override
protected void compute() {
List<FileTraverseTask> tasks = source.map(cache -> new FileTraverseTask(cache, processors)).collect(Collectors.toList());
// 1
invokeAll(tasks);
}
註釋1處,繼續回調所有的 FileTraverseTask 實例的 compute 方法,源碼如下所示:
@Override
protected void compute() {
List<TraverseTask> tasks = fileCache.stream().map(file -> new TraverseTask(fileCache, file, processors))
.toList().blockingGet();
// 1
invokeAll(tasks);
}
註釋1處,繼續回調所有的 TraverseTask 實例的 compute 方法,源碼如下所示:
@Override
protected void compute() {
try {
Input input = new Input(fileCache.getContent(), file);
ProcessorChain chain = new ProcessorChain(processors, input, 0);
// 1、調用 ProcessorChain 的 proceed 方法。
chain.proceed(input);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
註釋1處,調用了 ProcessorChain 的 proceed 方法。源碼如下所示:
@Override
public Output proceed(Input input) throws IOException {
if (index >= processors.size()) throw new AssertionError();
// 1
FileProcessor next = processors.get(index);
return next.process(new ProcessorChain(processors, input, index + 1));
}
註釋1處,會從 processors 處理器列表中獲取第一個處理器—FilterFileProcessor,並調用它的 process 方法,源碼如下所示:
@Override
public Output process(Chain chain) throws IOException {
Input input = chain.input();
if (predicate.test(input.getFileData())) {
// 1
return chain.proceed(input);
} else {
return new Output(input.getFileData());
}
}
註釋1處,如果有 FileData 的話,則繼續調用 chain 的 proceed 方法,內部會繼續調用 ClassFileProcessor 的 process 方法,源碼如下:
@Override
public Output process(Chain chain) throws IOException {
Input input = chain.input();
FileData fileData = input.getFileData();
if (fileData.getRelativePath().endsWith(".class")) {
// 1、如果 fileData 是 .class 文件,則調用 ClassFileTransformer 的 handle 方法進行處理。
handler.handle(fileData);
}
// 2、
return chain.proceed(input);
}
註釋1處,如果 fileData 是 .class 文件,則調用 ClassFileTransformer 的 handle 方法進行處理。其源碼如下所示:
@Override
public void handle(FileData fileData) {
try {
byte[] raw = fileData.getBytes();
String relativePath = fileData.getRelativePath();
int cwFlags = 0; //compute nothing
int crFlags = 0;
for (MainProcessHandler handler : handlers) {
// 1、設置 ClassWrite 的 flag 的默認值爲 ClassWriter.COMPUTE_MAXS。
cwFlags |= handler.flagForClassWriter();
if ((handler.flagForClassReader(Process.TRANSFORM) & ClassReader.EXPAND_FRAMES) == ClassReader.EXPAND_FRAMES) {
crFlags |= ClassReader.EXPAND_FRAMES;
}
}
ClassReader cr = new ClassReader(raw);
ClassWriter cw = new ClassWriter(cwFlags);
ClassVisitorChain chain = getClassVisitorChain(relativePath);
if (needPreVerify) {
// 2、如果需要預校驗,則將責任鏈表頭尾部設置爲 AsmVerifyClassVisitor 實例。
chain.connect(new AsmVerifyClassVisitor());
}
if (handlers != null && !handlers.isEmpty()) {
for (MainProcessHandler handler : handlers) {
// 3、遍歷執行所有 plugin 的 transform。其內部會使用 chain.connect(new ReferCheckClassVisitor(context)) 的方式 將
if (!handler.transform(relativePath, chain)) {
fileData.delete();
return;
}
}
}
// 4、兼容 ClassNode 處理的模式
ClassNode cn = new SafeClassNode();
chain.append(cn);
chain.accept(cr, crFlags);
for (MainProcessHandler handler : handlers) {
if (!handler.transform(relativePath, cn)) {
fileData.delete();
return;
}
}
cn.accept(cw);
if (!GlobalWhiteListManager.INSTANCE.shouldIgnore(fileData.getRelativePath())) {
raw = cw.toByteArray();
if (needVerify) {
ClassNode verifyNode = new ClassNode();
new ClassReader(raw).accept(verifyNode, crFlags);
AsmVerifier.verify(verifyNode);
}
// 5、如果不是白名單裏的文件,則將 ClassWriter 中的數據放入 fileData 之中。
fileData.setBytes(raw);
}
} catch (ByteXException e) {
throw e;
} catch (Exception e) {
LevelLog.sDefaultLogger.e(String.format("Failed to handle class %s", fileData.getRelativePath()), e);
if (!GlobalWhiteListManager.INSTANCE.shouldIgnore(fileData.getRelativePath())) {
throw e;
}
}
}
在 ClassFileProcessor 的 process 方法的註釋2處,又會繼續調用 ProcessorChain 的 proceed 方法,這裏會回調 BackupFileProcessor 實例的 process 方法,源碼如下所示:
@Override
public Output process(Chain chain) throws IOException {
Input input = chain.input();
// 僅僅是返回處理過的輸出文件
return new Output(input.getFileData());
}
按照這樣的模式,PerformTraverseTask 實例的所有 task 都被遍歷執行了。
最後,便會調用 MainTransformFlow 實例的 afterTransform 方法,源碼如下:
@Override
protected AbsTransformFlow afterTransform(TransformEngine transformEngine) {
handlers.forEach(plugin -> plugin.afterTransform(transformEngine));
return this;
}
這裏遍歷執行了所有 plugin 的 afterTransform 方法。
然後,我們再回到 CommonTransform 的 transform 方法,在執行完 MainTransformFlow 的 run 方法後,便會調用註釋9處的代碼來獲取流中的 graph 類圖對象並清除。最後,執行註釋10處的 afterTransform 方法用來做 transform 之後的收尾工作。
四、總結
在本文中,我們一起對 ByteX 插件平臺的構建流程進行了探祕。從 ByteX 的源碼實現中,我們可以看出作者對 函數式編程、Java 1.8 Lambda 表達式、Java 1.8 Stream API、複雜泛型 等技術的靈活運用,所以,幾乎所有看似很 🐂 的輪子,其實質都是依賴於對基礎技術的深度掌握。那麼,如何才能達到深度掌握基礎技術的程度呢?— 唯有不斷地練習與有規律的複習。
公衆號
我的公衆號 JsonChao
開通啦,歡迎關注~
參考鏈接:
-
1、ByteX
-
3、《深入理解 JVM》
-
4、《Java 編程思想》
Contanct Me
● 微信:
歡迎關注我的微信:
bcce5360
● 微信羣:
由於微信羣已超過 200 人,麻煩大家想進微信羣的朋友們,加我微信拉你進羣。
● QQ羣:
2千人QQ羣,Awesome-Android學習交流羣,QQ羣號:959936182, 歡迎大家加入~
About me
-
Email: [email protected]
-
Blog: https://jsonchao.github.io/
-
掘金: https://juejin.im/user/5a3ba9375188252bca050ade
很感謝您閱讀這篇文章,希望您能將它分享給您的朋友或技術羣,這對我意義重大。
希望我們能成爲朋友,在 Github、掘金上一起分享知識。
前言
成爲一名優秀的Android開發,需要一份完備的知識體系,在這裏,讓我們一起成長爲自己所想的那樣~。
一、前置知識
1、函數式編程
1)、什麼是函數式編程?
面向對象編程是對數據進行抽象,而函數式編程是對行爲進行抽象。現實世界中,數據和行爲並存,而程序也是如此。
2)爲什麼要學習函數式編程?
- 用函數(行爲)對數據處理,是學習大數據的基石。
- 好的效率(併發執行),
- 完成一個功能使用更少的代碼。
- 對象轉向面向函數編程的思想有一定難度,需要大量的練習
2、Java 1.8 Lambda 表達式
1)、什麼是 Lambda 表達式?
Lambda 是一個匿名函數,即沒有函數名的函數,它簡化了匿名委託的使用,讓代碼更加簡潔。
2)、兩個簡單的 Lambda 表達式示例
//匿名內部類
Runnable r = new Runnable() {
@Override
public void run() {
System.out.print("hello toroot");
}
};
//lambda
Runnable r2 = ()->System.out.print("hello toroot");
//匿名內部類
TreeSet<String> ts = new TreeSet<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return Long.compare(o1.length(),o2.length());
}
});
//lambda
TreeSet<String> ts2 = new TreeSet<>((o1,o2)-> Long.compare(o1.length(),o2.length()));
3)、Lambda 表達式語法
Lambda 表達式在 Java 語言中引入了一個新的語法元素和操作符。這個操作符爲 “->” ,該操作符被稱爲 Lambda 操作符或剪頭操作符。
它將 Lambda 分爲兩個部分:
- 左側:指定了 Lambda 表達式需要的所有參數。
- 右側:指定了 Lambda 體,即 Lambda 表達式要執行的功能。
4)、Lambda 表達式語法格式
- 1、無參數,無返回值:
() -> System.out.println("Hello Lambda!");
- 2、有一個參數,並且無返回值:
(x) -> System.out.println(x)
- 3、若只有一個參數,小括號可以省略不寫:
x -> System.out.println(x)
- 4、有兩個以上的參數,有返回值,並且 Lambda 體中有多條語句:
Comparator<Integer> com = (x, y) -> {
System.out.println("函數式接口");
return Integer.compare(x, y);
};
- 5、若 Lambda 體中只有一條語句, return 和 大括號 都可以省略不寫:
Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
- 6、Lambda 表達式的參數列表的數據類型可以省略不寫,因爲 JVM 編譯器可以通過上下文推斷出數據類型,即“類型推斷”:
(Integer x, Integer y) -> Integer.compare(x, y);
5)、函數式接口
Lambda 表達式需要“函數式接口”的支持。函數式接口即 接口中只有一個抽象方法的接口,稱爲函數式接口。 可以使用註解 @FunctionalInterface
修飾,它可以檢查是否是函數式接口。函數式接口的使用示例如下所示:
@FunctionalInterface
public interface MyFun {
public double getValue();
}
@FunctionalInterface
public interface MyFun<T> {
public T getValue(T t);
}
public static void main(String[] args) {
String newStr = toUpperString((str)->str.toUpperCase(),"toroot");
System.out.println(newStr);
}
public static String toUpperString(MyFun<String> mf,String str) {
return mf.getValue(str);
}
6)、Java 內置函數式接口
接口 | 參數 | 返回類型 | 示例 |
---|---|---|---|
Predicate | T | boolean | 這道題對了嗎? |
Consumer | T | void | 輸出一個值 |
Function<T,R> | T | R | 獲得 Person對象的名字 |
Supplier | None | T | 工廠方法 |
UnaryOperator | T | T | 邏輯非 (!) |
BinaryOperator | (T, T) | T | 求兩個數的乘積 (*) |
7)、方法引用
當要傳遞給 Lambda 體的操作,已經有實現的方法了,可以使用方法引用。方法引用使用 操作符 “ ::” 將方法名和對象或類的名字分隔開來。它主要有如下 三種 使用情況 :
- 對象 :: 實例方法
- 類 :: 靜態方法
- 類 :: 實例方法
8)、什麼時候可以用 :: 方法引用(重點)
在我們使用 Lambda 表達式的時候,”->” 右邊部分是要執行的代碼,即要完成的功能,可以把這部分稱作 Lambda 體。有時候,當我們想要實現一個函數式接口的那個抽象方法,但是已經有類實現了我們想要的功能,這個時候我們就可以用方法引用來直接使用現有類的功能去實現。示例代碼如下所示:
Person p1 = new Person("Av",18,90);
Person p2 = new Person("King",20,0);
Person p3 = new Person("Lance",17,100);
List<Person> list = new ArrayList<>();
list.add(p1);
list.add(p2);
list.add(p3);
// 這裏我們需要比較 list 裏面的 person,按照年齡排序
// 那麼我們最常見的做法是
// sort(List<T> list, Comparator<? super T> c)
// 1、因爲我們的 sort 方法的第二個參數是一個接口,所以我們需要實現一個匿名內部類
Collections.sort(list, new Comparator<Person>() {
@Override
public int compare(Person person1, Person person2) {
return person1.getAge().compareTo(person2.getAge());
}
});
// 2、因爲第二個參數是一個 @FunctionalInterface 的函數式接口,所以我們可以用 lambda 寫法
Collections.sort(list, (person1,person2) -> p1.getScore().compareTo(p2.getAge()));
// 3、因爲第二個參數我們可以用lambda的方式去實現,
// 但是剛好又有代碼 Comparator.comparing 已經實現了這個功能
// 這個時候我們就可以採用方法引用了
/**
* 重點:
* 當我們想要實現一個函數式接口的那個抽象方法,但是已經有類實現了我們想要的功能,
* 這個時候我們就可以用方法引用來直接使用現有類的功能去實現。
*/
Collections.sort(list, Comparator.comparing(Person::getAge));
System.out.println(list);
其它 Java 內置的函數式接口示例如下所示:
public static void main(String[] args) {
Consumer<String> c = x->System.out.println(x);
// 等同於
Consumer<String> c2 = System.out::print;
}
public static void main(String[] args) {
BinaryOperator<Double> bo = (n1,n2) ->Math.pow(n1,n2);
BinaryOperator<Double> bo2 = Math::pow;
}
public static void main(String[] args) {
BiPredicate<String,String> bp = (str1,str2) ->str1.equals(str2);
BiPredicate<String,String> bp2 = String::equals;
}
注意:當需要引用方法的第一個參數是調用對象,並且第二個參數是需要引用方法的第二個參數(或無參數)時,使用
ClassName::methodName
。
9)、構造器引用
格式: ClassName :: new
與函數式接口相結合,自動與函數式接口中方法兼容。
可以把構造器引用賦值給定義的方法,但是構造器參數列表要與接口中抽象方法的參數列表一致。示例如下所示:
public static void main(String[] args) {
Supplier<Person> x = ()->new Person();
Supplier<Person> x2 = Person::new;
}
public static void main(String[] args) {
Function<String,Person> f = x->new Person(x);
Function<String,Person> f2 = Person::new;
}
10)、數組引用
格式: type[] :: new,示例如下所示:
public static void main(String[] args) {
Function<Integer,Person[]> f = x->new Person[x];
Function<Integer,Person[]> f2 = Person[]::new;
}
3、Stream API
1)、Stream 是什麼?
Stream 是數據渠道,用於操作數據源(集合、數組等)所生成的元素序列。記住:“集合講的是數據,流講的是計算!”
2)、特點
- 1)、Stream 自己不會存儲元素。
- 2)、Stream 不會改變源對象。相反,他們會返回一個持有結果的新 Stream。
- 3)、Stream 操作是延遲執行的。這意味着他們會等到需要結果的時候才執行。
3)、一個 Stream 操作實例
取出所有大於18歲人的姓名,按字典排序,並輸出到控制檯,代碼如下所示:
private static List<Person> persons = Arrays.asList(
new Person("CJK",19,"女"),
new Person("BODUO",20,"女"),
new Person("JZ",21,"女"),
new Person("anglebabby",18,"女"),
new Person("huangxiaoming",5,"男"),
new Person("ROY",18,"男")
);
public static void main(String[] args) throws IOException {
persons.stream().filter(x- >x.getAge()>=18).map(Person::getName).sorted().forEach(System.out::println);
}
4)、Stream 的操作三個步驟
- 1、創建 Stream:一個數據源(如:集合、數組),獲取一個流。
- 2、中間操作:一箇中間操作鏈,對數據源的數據進行處理。
- 3、終止操作(終端操作):一個終止操作,執行中間操作鏈,併產生結果。
1、創建 Steam
創建流主要有四種方式,其示例代碼如下所示:
@Test
public void test1(){
//1. Collection 提供了兩個方法 stream() 與 parallelStream()
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream(); //獲取一個順序流
Stream<String> parallelStream = list.parallelStream(); //獲取一個並行流
//2. 通過 Arrays 中的 stream() 獲取一個數組流
Integer[] nums = new Integer[10];
Stream<Integer> stream1 = Arrays.stream(nums);
//3. 通過 Stream 類中靜態方法 of()
Stream<Integer> stream2 = Stream.of(1,2,3,4,5,6);
//4. 創建無限流
//迭代
Stream<Integer> stream3 = Stream.iterate(0, (x) -> x + 2).limit(10);
stream3.forEach(System.out::println);
//生成
Stream<Double> stream4 = Stream.generate(Math::random).limit(2);
stream4.forEach(System.out::println);
}
2、中間操作
- 1)、篩選與切片
- filter:接收 Lambda ,從流中排除某些元素。
- limit:截斷流,使其元素不超過給定數量。
- skip(n):跳過元素,返回一個扔掉了前 n 個元素的流。若流中元素不足 n 個,則返回一個空流。與 limit(n) 互補。
- distinct:篩選,通過流所生成元素的 hashCode() 和 equals() 去除重複元素。
- 2)、映射
- map:接收 Lambda ,將元素轉換成其他形式或提取信息。接收一個函數作爲參數,該函數會被應用到每個元素上,並將其映射成一個新的元素,類似於 python、go 的 map 語法。
- flatMap:接收一個函數作爲參數,將流中的每個值都換成另一個流,然後把所有流連接成一個流。
- 3)、排序
- sorted():自然排序。
- sorted(Comparator com):定製排序。
這裏,我們給出一些常見的使用示例,如下所示:
1、有個數組 Integer[] ary = {1,2,3,4,5,6,7,8,9,10},取出中間的第三到第五個元素。
List<Integer> collect = Arrays.stream(ary).skip(2).limit(3).collect(Collectors.toList());
2、有個數組 Integer[] ary = {1,2,2,3,4,5,6,6,7,8,8,9,10},取出裏面的偶數,並去除重複。
List<Integer> list = Arrays.stream(ary).filter(x -> x % 2 == 0).distinct().collect(Collectors.toList());
Set<Integer> integerSet = Arrays.stream(ary).filter(x -> x % 2 == 0).collect(Collectors.toSet());
3、有個二維數組,要求把數組組合成一個一維數組,並排序(1,2,3,4,5……12)
Integer[][] ary = {{3,8,4,7,5}, {9,1,6,2}, {0,10,12,11} };
Arrays.stream(ary).flatMap(item->Arrays.stream(item)).sorted().forEach(System.out::println);
3)、終止操作
終端操作會從流的流水線生成結果。其結果可以是任何不是流的值,例如:List、Integer,甚至是 void 。
1、查找與匹配
接口 | 說明 |
---|---|
allMatch(Predicate p) | 檢查是否匹配所有元素 |
anyMatch(Predicate p) | 檢查是否至少匹配一個元素 |
noneMatch(Predicate p) | 檢查是否沒有匹配所有元素 |
findFirst() | 返回第一個元素 |
findAny() | 返回當前流中的任意元素 |
count() | 返回流中元素總數 |
max(Comparator c) | 返回流中最大值 |
min(Comparator c) | 返回流中最小值 |
forEach(Consumer c) | 迭代 |
2、歸約
reduce(T iden, BinaryOperator b) 可以將流中元素反覆結合起來,得到一個值。返回 Optional。例如使用 reduce 來求所有人員學生的總分的示例代碼如下所示:
Integer all = persons.stream().map(Person::getScore).reduce((integer, integer2) -> integer + integer2).get()
3、收集
- collect(Collector c) 將流轉換爲其他形式。它接收一個 Collector 接口的實現,用於給 Stream 中元素做彙總的方法。
- Collector 接口中方法的實現決定了如何對流執行收集操作(如收集到 List、Set、Map)。
- Collectors 實用類提供了很多靜態方法,可以方便地創建常見收集器實例。
收集相關的 Stream API 與其實例代碼如下所示:
- 1)、toList List 把流中元素收集到 List:
List<Person> emps= list.stream().collect(Collectors.toList());
- 2)、toSet Set 把流中元素收集 到Set:
Set<Person> emps= list.stream().collect(Collectors.toSet());
- 3)、toCollection Collection 把流中元素收集到創建的集合:
Collection<Person> emps=list.stream().collect(Collectors.toCollection(ArrayList::new));
- 4)、counting Long 計算流中元素的個數:
long count = list.stream().collect(Collectors.counting());
- 5)、summing Int Integer 對流中元素的整數屬性求和:
int total=list.stream().collect(Collectors.summingInt(Person::getAge));
- 6)、averaging Int Double 計算流中元素 Integer 屬性的平均值:
double avg= list.stream().collect(Collectors.averagingInt(Person::getAge));
- 7)、summarizingInt IntSummaryStatistics 收集流中 Integer 屬性的統計值。如平均值:
Int SummaryStatisticsiss= list.stream().collect(Collectors.summarizingInt(Person::getAge));
- 8)、joining String 連接流中每個字符串:
String str= list.stream().map(Person::getName).collect(Collectors.joining());
- 9)、maxBy Optional 根據比較器選擇最大值:
Optional<Person> max= list.stream().collect(Collectors.maxBy(comparingInt(Person::getSalary)));
- 10)、minBy Optional 根據比較器選擇最小值:
Optional<Person> min = list.stream().collect(Collectors.minBy(comparingInt(Person::getSalary)));
- 11)、reducing 歸約產生的類型,從一個作爲累加器的初始值開始,利用 BinaryOperator 與流中元素逐個結合,從而歸約成單個值:
int total=list.stream().collect(Collectors.reducing(0, Person::getSalary, Integer::sum));
- 12)、collectingAndThen 轉換函數返回的類型,包裹另一個收集器,對其結果轉換函數
int how= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));
- 13)、groupingBy Map<K, List> 根據某屬性值對流分組,屬性爲 K,結果爲 V:
Map<Person.Status, List<Person>> map= list.stream().collect(Collectors.groupingBy(Person::getStatus));
- 14)、partitioningBy Map<Boolean, List> 根 據true 或 false 進行分區:
Map<Boolean,List<Person>>vd= list.stream().collect(Collectors.partitioningBy(Person::getManage));
4、終止操作練習案例
- 1)、取出Person對象的所有名字,放到 List 集合中:
List<String> collect2 = persons.stream().map(Person::getName).collect(Collectors.toList());
- 2、求 Person 對象集合的分數的平均分、總分、最高分,最低分,分數的個數:
IntSummaryStatistics collect = persons.stream().collect(Collectors.summarizingInt(Person::getScore));
System.out.println(collect);
- 3、根據成績分組,及格的放一組,不及格的放另外一組:
Map<Boolean, List<Person>> collect1 = persons.stream().collect(Collectors.partitioningBy(person -> person.getScore() >= 60));
System.out.println(collect1);
- 4、統計 aa.txt 裏面的單詞數:
public static void main(String[] args) throws IOException {
InputStream resourceAsStream = Person.class.getClassLoader().getResourceAsStream("aa.txt");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(resourceAsStream));
bufferedReader.lines().flatMap(x->Stream.of(x.split(" "))).sorted().collect(Collectors.groupingBy(String::toString)).forEach((a,b)-> System.out.println(a+":"+b.size()));
bufferedReader.close();
}
4、複雜泛型
1)、泛型是什麼?
泛型,即 “參數化類型”。就是將類型由原來的具體的類型參數化,類似於方法中的變量參數,此時類型也定義成參數形式(可以稱之爲類型形參),然後在使用/調用時傳入具體的類型(類型實參)。
2)、泛型的好處
- 適用於多種數據類型執行相同的代碼。
- 泛型中的類型在使用時指定,不需要強制類型轉換。
3)、泛型類和泛型接口
泛型的本質是爲了參數化類型(在不創建新的類型的情況下,通過泛型指定的不同類型來控制形參具體限制的類型)。而這種參數類型可以用在類、接口和方法中,分別被稱爲 泛型類、泛型接口、泛型方法。
泛型類
引入一個類型變量T(其他大寫字母都可以,不過常用的就是T,E,K,V等等),並且用<>括起來,並放在類名的後面。泛型類是允許有多個類型變量的。常見的示例代碼如下所示:
public class NormalGeneric<K> {
private K data;
public NormalGeneric() {
}
public NormalGeneric(K data) {
this.data = data;
}
public K getData() {
return data;
}
public void setData(K data) {
this.data = data;
}
}
public class NormalGeneric2<T,K> {
private T data;
private K result;
public NormalGeneric2() {
}
public NormalGeneric2(T data) {
this();
this.data = data;
}
public NormalGeneric2(T data, K result) {
this.data = data;
this.result = result;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public K getResult() {
return result;
}
public void setResult(K result) {
this.result = result;
}
}
泛型接口
泛型接口與泛型類的定義基本相同。示例代碼如下所示:
public interface Genertor<T> {
public T next();
}
但是,實現泛型接口的類,有兩種實現方法:
1、未傳入泛型實參
在 new 出類的實例時,需要指定具體類型:
public class ImplGenertor<T> implements Genertor<T> {
@Override
public T next() {
return null;
}
}
2、傳入泛型實參
在 new 出類的實例時,和普通的類沒區別。
public class ImplGenertor2 implements Genertor<String> {
@Override
public String next() {
return null;
}
}
泛型方法
泛型方法的 定義在 修飾符與返回值 的中間。示例代碼如下所示:
public <T> T genericMethod(T...a){
return a[a.length/2];
}
泛型方法,是在調用方法的時候指明泛型的具體類型,泛型方法可以在任何地方和任何場景中使用,包括普通類和泛型類。
泛型類中定義的普通方法和泛型方法的區別
在普通方法中:
// 雖然在方法中使用了泛型,但是這並不是一個泛型方法。
// 這只是類中一個普通的成員方法,只不過他的返回值是在聲明泛型類已經聲明過的泛型。
// 所以在這個方法中才可以繼續使用 T 這個泛型。
public T getKey(){
return key;
}
在泛型方法中:
/**
* 這纔是一個真正的泛型方法。
* 首先在 public 與返回值之間的 <T> 必不可少,這表明這是一個泛型方法,並且聲明瞭一個泛型 T
* 這個 T 可以出現在這個泛型方法的任意位置,泛型的數量也可以爲任意多個。
*/
public <T,K> K showKeyName(Generic<T> container){
// ...
}
4)、限定類型變量
public class ClassBorder<T extends Comparable> {
...
}
public class GenericRaw<T extends ArrayList&Comparable> {
...
}
-<T extends Comparable>
:T 表示應該綁定類型的子類型,Comparable 表示綁定類型,子類型和綁定類型可以是類也可以是接口。
- extends 左右都允許有多個,如 T,V extends Comparable&Serializable。
- 注意限定類型中,只允許有一個類,而且如果有類,這個類必須是限定列表的第一個。
- 限定類型變量既可以用在泛型方法上也可以用在泛型類上。
5)、泛型中的約束和侷限性
- 1、不能用基本類型實例化類型參數。
- 2、運行時類型查詢只適用於原始類型。
- 3、泛型類的靜態上下文中類型變量失效:不能在靜態域或方法中引用類型變量。因爲泛型是要在對象創建的時候才知道是什麼類型的,而對象創建的代碼執行先後順序是 static 的部分,然後纔是構造函數等等。所以在對象初始化之前 static 的部分已經執行了,如果你在靜態部分引用泛型,那麼毫無疑問虛擬機根本不知道是什麼東西,因爲這個時候類還沒有初始化。
- 4、不能創建參數化類型的數組,但是可以定義參數化類型的數組。
- 5、不能實例化類型變量。
- 6、不能使用 try-catch 捕獲泛型類的實例。
6)、泛型類型的繼承規則
泛型類可以繼承或者擴展其他泛型類,比如 List 和 ArrayList:
private static class ExtendPair<T> extends Pair<T>{
...
}
7)、通配符類型
?extends X
:表示類型的上界,類型參數是 X 的子類。?super X
:表示類型的下界,類型參數是 X 的超類。
?extends X
如果其中提供了 get 和 set 類型參數變量的方法的話,set 方法是不允許被調用的,會出現編譯錯誤,而 get 方法則沒問題。
?extends X 表示類型的上界,類型參數是 X 的子類,那麼可以肯定的說,get 方法返回的一定是個 X(不管是 X 或者 X 的子類)編譯器是可以確定知道的。但是 set 方法只知道傳入的是個 X,至於具體是 X 的哪個子類,是不知道的。
因此,?extends X 主要用於安全地訪問數據,可以訪問 X 及其子類型,並且不能寫入非 null 的數據。
?super X
如果其中提供了 get 和 set 類型參數變量的方法的話,set 方法可以被調用,且能傳入的參數只能是 X 或者 X 的子類。而 get 方法只會返回一個 Object 類型的值。
? super X 表示類型的下界,類型參數是 X 的超類(包括 X 本身),那麼可以肯定的說,get 方法返回的一定是個 X 的超類,那麼到底是哪個超類?不知道,但是可以肯定的說,Object 一定是它的超類,所以 get 方法返回 Object。編譯器是可以確定知道的。對於 set 方法來說,編譯器不知道它需要的確切類型,但是 X 和 X 的子類可以安全的轉型爲 X。
因此,?super X 主要用於安全地寫入數據,可以寫入 X 及其子類型。
無限定的通配符 ?
表示對類型沒有什麼限制,可以把 ?看成所有類型的父類,如 ArrayList<?>。
8)、虛擬機是如何實現泛型的?
泛型思想早在 C++ 語言的模板(Template)中就開始生根發芽,在 Java 語言處於還沒有出現泛型的版本時,只能通過 Object 是所有類型的父類和類型強制轉換兩個特點的配合來實現類型泛化。
由於 Java 語言裏面所有的類型都繼承於 java.lang.Object,所以 Object 轉型成任何對象都是有可能的。但是也因爲有無限的可能性,就只有程序員和運行期的虛擬機才知道這個 Object 到底是個什麼類型的對象。在編譯期間,編譯器無法檢查這個 Object 的強制轉型是否成功,如果僅僅依賴程序員去保障這項操作的正確性,許多 ClassCastException 的風險就會轉嫁到程序運行期之中。
此外,泛型技術在 C#/C++ 和 Java 之中的使用方式看似相同,但實現上卻有着根本性的分歧,C# 裏面的泛型無論在程序源碼中、編譯後的 IL 中(Intermediate Language,中間語言,這時候泛型是一個佔位符),或是運行期的 CLR 中,都是切實存在的,List<int> 與 List<String> 就是兩個不同的類型,它們在系統運行期生成,有自己的虛方法表和類型數據,這種實現稱爲類型膨脹,基於這種方法實現的泛型稱爲真實泛型。
而 Java 語言中的泛型則不一樣,它只在程序源碼中存在,在編譯後的字節碼文件中,就已經替換爲原來的原生類型(Raw Type,也稱爲裸類型)了,並且在相應的地方插入了強制轉型代碼,因此,對於運行期的 Java 語言來說,ArrayList<int> 與 ArrayList<String> 就是同一個類,所以 泛型技術實際上是 Java 語言的一顆語法糖,Java 語言中的泛型實現方法稱爲類型擦除,基於這種方法實現的泛型稱爲僞泛型。
將一段 Java 代碼編譯成 Class 文件,然後再用字節碼反編譯工具進行反編譯後,將會發現泛型都不見了,程序又變回了 Java 泛型出現之前的寫法,泛型類型都變回了原生類型。
由於 Java 泛型的引入,各種場景(虛擬機解析、反射等)下的方法調用都可能對原有的基礎產生影響和新的需求,如在泛型類中如何獲取傳入的參數化類型等。因此,JCP 組織對虛擬機規範做出了相應的修改,引入了諸如 Signature、LocalVariableTypeTable 等新的屬性用於解決伴隨泛型而來的參數類型的識別問題,Signature 是其中最重要的一項屬性,它的作用就是存儲一個方法在字節碼層面的特徵簽名,這個屬性中保存的參數類型並不是原生類型,而是包括了參數化類型的信息。修改後的虛擬機規範要求所有能識別 49.0 以上版本的 Class 文件的虛擬機都要能正確地識別 Signature 參數。
最後,從 Signature 屬性的出現我們還可以得出結論,擦除法所謂的擦除,僅僅是對方法的 Code 屬性中的字節碼進行擦除,實際上元數據中還是保留了泛型信息,這也是我們能通過反射手段取得參數化類型的根本依據。
二、初識 ByteX
ByteX 使用了純 Java 來編寫源碼,它是一個基於 Gradle transform api 和 ASM 的字節碼插樁平臺。
調試:gradle clean :example:assembleRelease -Dorg.gradle.debug=true --no-daemon
1、優勢
- 1)、自動集成到其它宿主和插件一起整合爲一個單獨的 MainTransformFlow,結合 class 文件多線程併發處理,避免了打包的額外時間呈線性增長。
- 2)、插件、宿主之間完全解耦,便於協同開發。
- 3)、common module 提供通用的代碼複用,每個插件只需專注自身的字節碼插樁邏輯。
2、MainTransformFlow 基本流程
在 MainTransformFlow implements MainProcessHandler
常規處理過程,會遍歷兩次工程構建中的所有 class。
- 1)、第一次,遍歷 traverse 與 traverseAndroidJar 過程,以形成完整的類圖。
- 2)、第二次,執行 transform:再遍歷一次工程中所有的構建產物,並對 class 文件做處理後輸出。
3、如何自定義獨立的 TransformFlow?
重寫 IPlugin 的 provideTransformFlow 即可。
4、類圖對象
context.getClassGraph() 獲取類圖對象,兩個 TransformFlow 的類圖是隔離的。
5、MainProcessHandler
- 通過複寫 process 方法,註冊自己的 FlieProcessor 來處理。
- FileProcessor 採用了責任鏈模式,每個 class 文件都會流經一系列的 FileProcessor 來處理。
6、IPlugin.hookTransformName()
使用 反射 Hook 方式 將 Transform 註冊到 proguard 之後。
三、ByteX 插件平臺構建流程探祕
添加 apply plugin: ‘bytex’ 之後,bytex 可以在 Gradle 的構建流程中起作用了。這裏的插件 id 爲 bytex,我們找到 bytex.properties 文件,查看裏面映射的實現類,如下所示:
implementation-class=com.ss.android.ugc.bytex.base.ByteXPlugin
可以看到,bytex 的實現類爲 ByteXPlugin,其源碼如下所示:
public class ByteXPlugin implements Plugin<Project> {
@Override
public void apply(@NotNull Project project) {
// 1
AppExtension android = project.getExtensions().getByType(AppExtension.class);
// 2
ByteXExtension extension = project.getExtensions().create("ByteX", ByteXExtension.class);
// 3
android.registerTransform(new ByteXTransform(new Context(project, android, extension)));
}
}
首先,註釋1處,獲取 Android 爲 App 提供的擴展屬性 AppExtension 實例。然後,在註釋2處,獲取 ByteX 自身創建的擴展屬性 ByteXExtension 實例。最後,在註釋3處,註冊 ByteXTransform 實例。ByteXTransform 繼承了抽象類 CommonTransform,其實現了關鍵的 transform 方法,其實現源碼如下所示:
@Override
public final void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
super.transform(transformInvocation);
// 1、如果不是增量模式,則清楚輸出目錄的文件。
if (!transformInvocation.isIncremental()) {
transformInvocation.getOutputProvider().deleteAll();
}
// 2、獲取 transformContext 實例。
TransformContext transformContext = getTransformContext(transformInvocation);
// 3、初始化 HtmlReporter(生成 ByteX 構建產生日誌的 HTML 文件)
init(transformContext);
// 4、過濾掉沒有打開插件開關的 plugin。
List<IPlugin> plugins = getPlugins().stream().filter(p -> p.enable(transformContext)).collect(Collectors.toList());
Timer timer = new Timer();
// 5、創建一個 transformEngine 實例。
TransformEngine transformEngine = new TransformEngine(transformContext);
try {
if (!plugins.isEmpty()) {
// 6、使用 PriorityQueue 對每一個 TransformFlow 進行優先級排序(在這裏添加的是與之對應的實現類 MainTransformFlow)。
Queue<TransformFlow> flowSet = new PriorityQueue<>((o1, o2) -> o2.getPriority() - o1.getPriority());
MainTransformFlow commonFlow = new MainTransformFlow(transformEngine);
flowSet.add(commonFlow);
for (int i = 0; i < plugins.size(); i++) {
// 7、給每一個 Plugin 註冊 MainTransformFlow,其實質是將每一個 Plugin 的 MainProcessHandler 添加到 MainTransformFlow 中的 handlers 列表中。
IPlugin plugin = plugins.get(i);
TransformFlow flow = plugin.registerTransformFlow(commonFlow, transformContext);
if (!flowSet.contains(flow)) {
flowSet.add(flow);
}
}
while (!flowSet.isEmpty()) {
TransformFlow flow = flowSet.poll();
if (flow != null) {
if (flowSet.size() == 0) {
flow.asTail();
}
// 8、按指定優先級執行每一個 TransformFlow 的 run 方法,默認只有一個 MainTransformFlow 實例。
flow.run();
// 9、獲取流中的 graph 類圖對象並清除。
Graph graph = flow.getClassGraph();
if (graph != null) {
//clear the class diagram.we won’t use it anymore
graph.clear();
}
}
}
} else {
transformEngine.skip();
}
// 10
afterTransform(transformInvocation);
} catch (Throwable throwable) {
LevelLog.sDefaultLogger.e(throwable.getClass().getName(), throwable);
throw throwable;
} finally {
for (IPlugin plugin : plugins) {
try {
plugin.afterExecute();
} catch (Throwable throwable) {
LevelLog.sDefaultLogger.e("do afterExecute", throwable);
}
}
transformContext.release();
release();
timer.record("Total cost time = [%s ms]");
if (BooleanProperty.ENABLE_HTML_LOG.value()) {
HtmlReporter.getInstance().createHtmlReporter(getName());
HtmlReporter.getInstance().reset();
}
}
}
在註釋7處,調用了 plugin.registerTransformFlow 方法,其源碼如下所示:
@Nonnull
@Override
public final TransformFlow registerTransformFlow(@Nonnull MainTransformFlow mainFlow, @Nonnull TransformContext transformContext) {
if (transformFlow == null) {
transformFlow = provideTransformFlow(mainFlow, transformContext);
if (transformFlow == null) {
throw new RuntimeException("TransformFlow can not be null.");
}
}
return transformFlow;
}
這裏繼續調用了 provideTransformFlow 方法,其源碼如下所示:
/**
* create a new transformFlow or just return mainFlow and append a handler.
* It will be called by {@link IPlugin#registerTransformFlow(MainTransformFlow, TransformContext)} when
* handle start.
*
* @param mainFlow main TransformFlow
* @param transformContext handle context
* @return return a new TransformFlow object if you want make a new flow for current plugin
*/
protected TransformFlow provideTransformFlow(@Nonnull MainTransformFlow mainFlow, @Nonnull TransformContext transformContext) {
return mainFlow.appendHandler(this);
}
可以看到,通過調用 mainFlow.appendHandler(this) 方法將每一個 Plugin 的 MainProcessHandler 添加到 MainTransformFlow 中的 handlers 列表之中。
在註釋8處,按指定優先級執行了每一個 TransformFlow 的 run 方法,默認只有一個 MainTransformFlow 實例。我們看到了 MianTransformFlow 的 run 方法:
@Override
public void run() throws IOException, InterruptedException {
try {
// 1
beginRun();
// 2
runTransform();
} finally {
// 3
endRun();
}
}
首先,在註釋1出,調用了 beginRun 方法,其實現如下:
// AbsTransformFlow
protected void beginRun() {
transformEngine.beginRun();
}
// TransformEngine
public void beginRun(){
context.markRunningState(false);
}
// TransformContext
private final AtomicBoolean running = new AtomicBoolean(false);
void markRunningState(boolean running) {
this.running.set(running);
}
最後,在 TransformContext 實例中使用了一個 AtomicBoolean 實例標記 MainTransformFlow 是否正在運行中。
然後,在註釋2處執行了 runTransform 方法,這裏就是真正執行 transform 的地方,其源碼如下所示:
private void runTransform() throws IOException, InterruptedException {
if (handlers.isEmpty()) return;
Timer timer = new Timer();
timer.startRecord("PRE_PROCESS");
timer.startRecord("INIT");
// 1、初始化 handlers 列表中的每一個 handler。
for (MainProcessHandler handler : handlers) {
handler.init(transformEngine);
}
timer.stopRecord("INIT", "Process init cost time = [%s ms]");
// 如果不是 跳過 traverse 僅僅只執行 Transform 方法時,才執行 traverse 過程。
if (!isOnePassEnough()) {
if (!handlers.isEmpty() && context.isIncremental()) {
timer.startRecord("TRAVERSE_INCREMENTAL");
// 2、如果是 增量模式,則執行 traverseArtifactOnly(僅僅增量遍歷產物)調用每一個 plugin 的對應的 MainProcessHandler 的 traverseIncremental 方法。這裏最終會調用 ClassFileAnalyzer.handle 方法進行遍歷分發操作。
traverseArtifactOnly(getProcessors(Process.TRAVERSE_INCREMENTAL, new ClassFileAnalyzer(context, Process.TRAVERSE_INCREMENTAL, null, handlers)));
timer.stopRecord("TRAVERSE_INCREMENTAL", "Process project all .class files cost time = [%s ms]");
}
handlers.forEach(plugin -> plugin.beforeTraverse(transformEngine));
timer.startRecord("LOADCACHE");
// 3、創建一個 CachedGraphBuilder 對象:能夠緩存 類圖 的 類圖構建者對象。
GraphBuilder graphBuilder = new CachedGraphBuilder(context.getGraphCache(), context.isIncremental(), context.shouldSaveCache());
if (context.isIncremental() && !graphBuilder.isCacheValid()) {
// 4、如果是增量更新 && graphBuilder 的緩存失效則直接請求非增量運行。
context.requestNotIncremental();
}
timer.stopRecord("LOADCACHE", "Process loading cache cost time = [%s ms]");
// 5、內部會調用 running.set(true) 來標記正在運行的狀態。
running();
if (!handlers.isEmpty()) {
timer.startRecord("PROJECT_CLASS");
// 6、執行 traverseArtifactOnly(遍歷產物)調用每一個 plugin 的對應的 MainProcessHandler 的 traverse 方法,這裏最終會調用 ClassFileAnalyzer.handle 方法進行遍歷分發操作。
traverseArtifactOnly(getProcessors(Process.TRAVERSE, new ClassFileAnalyzer(context, Process.TRAVERSE, graphBuilder, handlers)));
timer.stopRecord("PROJECT_CLASS", "Process project all .class files cost time = [%s ms]");
}
if (!handlers.isEmpty()) {
timer.startRecord("ANDROID");
// 7、僅僅遍歷 Android.jar
traverseAndroidJarOnly(getProcessors(Process.TRAVERSE_ANDROID, new ClassFileAnalyzer(context, Process.TRAVERSE_ANDROID, graphBuilder, handlers)));
timer.stopRecord("ANDROID", "Process android jar cost time = [%s ms]");
}
timer.startRecord("SAVECACHE");
// 8、構建 mClassGraph 類圖實例。
mClassGraph = graphBuilder.build();
timer.stopRecord("SAVECACHE", "Process saving cache cost time = [%s ms]");
}
timer.stopRecord("PRE_PROCESS", "Collect info cost time = [%s ms]");
if (!handlers.isEmpty()) {
timer.startRecord("PROCESS");
// 9、遍歷執行每一個 plugin 的 transform 方法。
transform(getProcessors(Process.TRANSFORM, new ClassFileTransformer(handlers, needPreVerify(), needVerify())));
timer.stopRecord("PROCESS", "Transform cost time = [%s ms]");
}
}
首先,在註釋1處,遍歷調用了每一個 MainProcessHandler 的 init 方法,它是用於 transform 開始前的初始化實現方法。
MainProcessHandler 接口的 init 方法是一個 default 方法,裏面直接調用了每一個 pluign 實現的 init 方法(如果 plugin 沒有實現,則僅僅調用 CommonPlugin 的實現的 init 方法:這裏通常是用於把不需要處理的文件添加到 mWhiteList 列表),這裏可以做一些plugin 的準備工作。
1、僅僅遍歷產物
traverseArtifactOnly(getProcessors(Process.TRAVERSE, new ClassFileAnalyzer(context, Process.TRAVERSE, graphBuilder, handlers)));
getProcessors 方法的源碼如下所示:
private FileProcessor[] getProcessors(Process process, FileHandler fileHandler) {
List<FileProcessor> processors = handlers.stream()
.flatMap((Function<MainProcessHandler, Stream<FileProcessor>>) handler -> handler.process(process).stream())
.collect(Collectors.toList());
switch (process) {
case TRAVERSE_INCREMENTAL:
processors.add(0, new FilterFileProcessor(fileData -> fileData.getStatus() != Status.NOTCHANGED));
processors.add(new IncrementalFileProcessor(handlers, ClassFileProcessor.newInstance(fileHandler)));
break;
case TRAVERSE:
case TRAVERSE_ANDROID:
case TRANSFORM:
processors.add(ClassFileProcessor.newInstance(fileHandler));
processors.add(0, new FilterFileProcessor(fileData -> fileData.getStatus() != Status.NOTCHANGED && fileData.getStatus() != Status.REMOVED))
break;
default:
throw new RuntimeException("Unknow Process:" + process);
}
return processors.toArray(new FileProcessor[0]);
}
這裏的 processor 的添加由 增量 進行界定,具體的處理標準如下:
TRAVERSE_INCREMENTAL
:FilterFileProcessor
:按照不同的過程過濾掉不需要的 FileData。IncrementalFileProcessor
:用於進行 增量文件的處理。
TRAVERSE/TRAVERSE_ANDROID/TRANSFORM
:FilterFileProcessor
:按照不同的過程過濾掉不需要的 FileData。ClassFileProcessor
:用於處理 .class 文件。
2、僅僅遍歷 Android Jar 包
traverseAndroidJarOnly(getProcessors(Process.TRAVERSE_ANDROID, new ClassFileAnalyzer(context, Process.TRAVERSE_ANDROID, graphBuilder, handlers)));
3、構建 mClassGraph 類圖對象
mClassGraph = graphBuilder.build();
4、執行 Transform
transform(getProcessors(Process.TRANSFORM, new ClassFileTransformer(handlers, needPreVerify(), needVerify())));
transform 的源碼如下所示:
// AbsTransformFlow 類中
protected AbsTransformFlow transform(FileProcessor... processors) throws IOException, InterruptedException {
beforeTransform(transformEngine);
transformEngine.transform(isLast, processors);
afterTransform(transformEngine);
return this;
}
1)、beforeTransform(transformEngine)
// MainTransformFlow
@Override
protected AbsTransformFlow beforeTransform(TransformEngine transformEngine) {
// 1
handlers.forEach(plugin -> plugin.beforeTransform(transformEngine));
return this;
}
註釋1處,遍歷執行 每一個 plugin 的 beforeTransform 方法做一些自身 transform 前的準備工作。
2)、transformEngine.transform(isLast, processors)
// TranformEngine
public void transform(boolean isLast, FileProcessor... processors) {
Schedulers.FORKJOINPOOL().invoke(new PerformTransformTask(context.allFiles(), getProcessorList(processors), isLast, context));
}
Shedulers.FORKJOINPOOL() 方法的源碼如下所示:
public class Schedulers {
private static final int cpuCount = Runtime.getRuntime().availableProcessors();
private final static ExecutorService IO = new ThreadPoolExecutor(0, cpuCount * 3,
30L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>());
// 1
private static final ExecutorService COMPUTATION = Executors.newWorkStealingPool(cpuCount);
public static Worker IO() {
return new Worker(IO);
}
public static Worker COMPUTATION() {
return new Worker(COMPUTATION);
}
public static ForkJoinPool FORKJOINPOOL() {
return (ForkJoinPool) COMPUTATION;
}
}
可以看到,最終是執行 Executors.newWorkStealingPool(cpuCount) 方法生成了一個 ForkJoinPool 實例。
ForkJoinPool 與 ThreadPoolExecutor 是屬於平級關係,ForkJoinPool 線程池是爲了實現“分治法”這一思想而創建的,通過把大任務拆分成小任務,然後再把小任務的結果彙總起來就是最終的結果,和 MapReduce 的思想很類似。除了“分治法”之外,ForkJoinPool 還使用了工作竊取算法,即所有線程均嘗試找到並執行已提交的任務,或是通過其他任務創建的子任務。有了它我們就可以儘量避免一個線程執行完自己的任務後“無所事事”的情況。
然後這裏會回調 PerformTransformTask 實例的 compute 方法,源碼如下所示:
@Override
protected void compute() {
if (outputFile) {
// 1、如果是最後一個 TransformFlow,則遞歸調用所有的 FileTransformTask。
List<FileTransformTask> tasks = source.map(cache -> new FileTransformTask(context, cache, processors)).collect(Collectors.toList());
// 2、對於Fork/Join模式,假如Pool裏面線程數量是固定的,那麼調用子任務的fork方法相當於A先分工給B,然後A當監工不幹活,B去完成A交代的任務。所以上面的模式相當於浪費了一個線程。那麼如果使用invokeAll相當於A分工給B後,A和B都去完成工作。這樣可以更好的利用線程池,縮短執行的時間。
invokeAll(tasks);
} else {
// 3、、遞歸調用 FileTransformTask
PerformTraverseTask traverseTask = new PerformTraverseTask(source, processors);
invokeAll(traverseTask);
}
}
在註釋1處,如果是最後一個 TransformFlow,則調用所有的 FileTransformTask。註釋2處,對於 Fork/Join 模式,假如 Pool 裏面線程數量是固定的,那麼調用子任務的 fork 方法相當於 A 先分工給 B,然後 A 當監工不幹活,B 去完成 A 交代的任務。所以上面的模式相當於浪費了一個線程。那麼如果使用 invokeAll 相當於 A 分工給 B 後,A 和 B 都去完成工作。這樣可以更好的利用線程池,縮短執行的時間。註釋3處,執行了 ForkJoinTask 的 invokeAll 方法,這裏便會回調 compute 方法,源碼如下所示:
@Override
protected void compute() {
List<FileTraverseTask> tasks = source.map(cache -> new FileTraverseTask(cache, processors)).collect(Collectors.toList());
// 1
invokeAll(tasks);
}
註釋1處,繼續回調所有的 FileTraverseTask 實例的 compute 方法,源碼如下所示:
@Override
protected void compute() {
List<TraverseTask> tasks = fileCache.stream().map(file -> new TraverseTask(fileCache, file, processors))
.toList().blockingGet();
// 1
invokeAll(tasks);
}
註釋1處,繼續回調所有的 TraverseTask 實例的 compute 方法,源碼如下所示:
@Override
protected void compute() {
try {
Input input = new Input(fileCache.getContent(), file);
ProcessorChain chain = new ProcessorChain(processors, input, 0);
// 1、調用 ProcessorChain 的 proceed 方法。
chain.proceed(input);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
註釋1處,調用了 ProcessorChain 的 proceed 方法。源碼如下所示:
@Override
public Output proceed(Input input) throws IOException {
if (index >= processors.size()) throw new AssertionError();
// 1
FileProcessor next = processors.get(index);
return next.process(new ProcessorChain(processors, input, index + 1));
}
註釋1處,會從 processors 處理器列表中獲取第一個處理器—FilterFileProcessor,並調用它的 process 方法,源碼如下所示:
@Override
public Output process(Chain chain) throws IOException {
Input input = chain.input();
if (predicate.test(input.getFileData())) {
// 1
return chain.proceed(input);
} else {
return new Output(input.getFileData());
}
}
註釋1處,如果有 FileData 的話,則繼續調用 chain 的 proceed 方法,內部會繼續調用 ClassFileProcessor 的 process 方法,源碼如下:
@Override
public Output process(Chain chain) throws IOException {
Input input = chain.input();
FileData fileData = input.getFileData();
if (fileData.getRelativePath().endsWith(".class")) {
// 1、如果 fileData 是 .class 文件,則調用 ClassFileTransformer 的 handle 方法進行處理。
handler.handle(fileData);
}
// 2、
return chain.proceed(input);
}
註釋1處,如果 fileData 是 .class 文件,則調用 ClassFileTransformer 的 handle 方法進行處理。其源碼如下所示:
@Override
public void handle(FileData fileData) {
try {
byte[] raw = fileData.getBytes();
String relativePath = fileData.getRelativePath();
int cwFlags = 0; //compute nothing
int crFlags = 0;
for (MainProcessHandler handler : handlers) {
// 1、設置 ClassWrite 的 flag 的默認值爲 ClassWriter.COMPUTE_MAXS。
cwFlags |= handler.flagForClassWriter();
if ((handler.flagForClassReader(Process.TRANSFORM) & ClassReader.EXPAND_FRAMES) == ClassReader.EXPAND_FRAMES) {
crFlags |= ClassReader.EXPAND_FRAMES;
}
}
ClassReader cr = new ClassReader(raw);
ClassWriter cw = new ClassWriter(cwFlags);
ClassVisitorChain chain = getClassVisitorChain(relativePath);
if (needPreVerify) {
// 2、如果需要預校驗,則將責任鏈表頭尾部設置爲 AsmVerifyClassVisitor 實例。
chain.connect(new AsmVerifyClassVisitor());
}
if (handlers != null && !handlers.isEmpty()) {
for (MainProcessHandler handler : handlers) {
// 3、遍歷執行所有 plugin 的 transform。其內部會使用 chain.connect(new ReferCheckClassVisitor(context)) 的方式 將
if (!handler.transform(relativePath, chain)) {
fileData.delete();
return;
}
}
}
// 4、兼容 ClassNode 處理的模式
ClassNode cn = new SafeClassNode();
chain.append(cn);
chain.accept(cr, crFlags);
for (MainProcessHandler handler : handlers) {
if (!handler.transform(relativePath, cn)) {
fileData.delete();
return;
}
}
cn.accept(cw);
if (!GlobalWhiteListManager.INSTANCE.shouldIgnore(fileData.getRelativePath())) {
raw = cw.toByteArray();
if (needVerify) {
ClassNode verifyNode = new ClassNode();
new ClassReader(raw).accept(verifyNode, crFlags);
AsmVerifier.verify(verifyNode);
}
// 5、如果不是白名單裏的文件,則將 ClassWriter 中的數據放入 fileData 之中。
fileData.setBytes(raw);
}
} catch (ByteXException e) {
throw e;
} catch (Exception e) {
LevelLog.sDefaultLogger.e(String.format("Failed to handle class %s", fileData.getRelativePath()), e);
if (!GlobalWhiteListManager.INSTANCE.shouldIgnore(fileData.getRelativePath())) {
throw e;
}
}
}
在 ClassFileProcessor 的 process 方法的註釋2處,又會繼續調用 ProcessorChain 的 proceed 方法,這裏會回調 BackupFileProcessor 實例的 process 方法,源碼如下所示:
@Override
public Output process(Chain chain) throws IOException {
Input input = chain.input();
// 僅僅是返回處理過的輸出文件
return new Output(input.getFileData());
}
按照這樣的模式,PerformTraverseTask 實例的所有 task 都被遍歷執行了。
最後,便會調用 MainTransformFlow 實例的 afterTransform 方法,源碼如下:
@Override
protected AbsTransformFlow afterTransform(TransformEngine transformEngine) {
handlers.forEach(plugin -> plugin.afterTransform(transformEngine));
return this;
}
這裏遍歷執行了所有 plugin 的 afterTransform 方法。
然後,我們再回到 CommonTransform 的 transform 方法,在執行完 MainTransformFlow 的 run 方法後,便會調用註釋9處的代碼來獲取流中的 graph 類圖對象並清除。最後,執行註釋10處的 afterTransform 方法用來做 transform 之後的收尾工作。
四、總結
在本文中,我們一起對 ByteX 插件平臺的構建流程進行了探祕。從 ByteX 的源碼實現中,我們可以看出作者對 函數式編程、Java 1.8 Lambda 表達式、Java 1.8 Stream API、複雜泛型 等技術的靈活運用,所以,幾乎所有看似很 🐂 的輪子,其實質都是依賴於對基礎技術的深度掌握。那麼,如何才能達到深度掌握基礎技術的程度呢?— 唯有不斷地練習與有規律的複習。
公衆號
我的公衆號 JsonChao
開通啦,歡迎關注~
參考鏈接:
-
1、ByteX
-
3、《深入理解 JVM》
-
4、《Java 編程思想》
Contanct Me
● 微信:
歡迎關注我的微信:
bcce5360
● 微信羣:
由於微信羣已超過 200 人,麻煩大家想進微信羣的朋友們,加我微信拉你進羣。
● QQ羣:
2千人QQ羣,Awesome-Android學習交流羣,QQ羣號:959936182, 歡迎大家加入~
About me
-
Email: [email protected]
-
Blog: https://jsonchao.github.io/
-
掘金: https://juejin.im/user/5a3ba9375188252bca050ade