Dart高級(一)——泛型與Json To Bean

從 Flutter 發佈到現在, 越來越多人開始嘗試使用 Dart 來完成部分功能;

Dart 的前生今世一類的話題,這裏就不展開了,只要知道 Flutter 是 google 推出跨平臺方案就好,至少不必擔心Dart性能與生態問題(如果google真的想支持的話).

先看一下最終的效果顯示:
在這裏插入圖片描述

Dart 語言

根據最新的語言排行榜…不好意思,沒找到Dart,就目前來看,在前端沒拼過JS,在其他領域好像也就目前 Flutter 在支持,不過相信隨着Flutter盛行,情況會越來越好的.

Dart 語言有兩種模式:JIT,AOT;這個和之前 Android/Java 情況很相似,JVM 一直採用解釋執行的方式, 直到 ART 出現,Android端纔開始 AOT 模式;

JIT比較方便,不需要提前編譯,AOT 運行時比較快,而 Flutter 則在 debug 和 release 時,分別適用不同模式,保證開發效率以及應用流暢.

說了那麼多,還沒有到正題,接下來,我們來看下 Dart 中比較 “特殊” 的特性:泛型

Dart泛型 與 其他泛型

首先來看一段關於泛型的 Dart 代碼(排列組合:C^2_5):

  List<int> a = <int>[];
  List<num> b = <num>[];
  List<dynamic> c = <dynamic>[];
  List<Object> d = <Object>[];
  List e = [];

  print(a.runtimeType == b.runtimeType);  // false
  print(a.runtimeType == c.runtimeType);  // false
  print(a.runtimeType == d.runtimeType);  // false
  print(a.runtimeType == e.runtimeType);  // false
  print(b.runtimeType == c.runtimeType);  // false
  print(b.runtimeType == d.runtimeType);  // false
  print(b.runtimeType == e.runtimeType);  // false
  print(c.runtimeType == d.runtimeType);  // false
  print(c.runtimeType == e.runtimeType);  // true
  print(d.runtimeType == e.runtimeType);  // false

雖然都是 List 類型,但泛型不同,獲取到的runtimeType都不同,由此可知:

Dart中泛型運行時依舊存在.

相比之下,Java中的泛型就比較隨意了:

List<Integer> a = new ArrayList<>();
List<Object> b = new ArrayList<>();
List c = new ArrayList<>();

java泛型在運行時,會被擦除,所以上面的a,b,c判斷時都屬於List類型.

再回到前面 Dart 部分,可以看到只有變量c和e的運行時類型相同,並且如果使用編譯器的話,就可以發現:

List c = <dynamic>[];

List 其實就是 List,兩者是一樣的.

在知道了dart的泛型特性後,不禁會思考:List和List,或者說和List是什麼關係?和List呢?

還是開始的dart示例,在後面我們添加幾行判斷(變量b的類型爲List<num>)

  print(b is List<int>);  //  false
  print(b is List<num>);  //  true
  print(b is List<Comparable>);  //  true
  print(b is List);  //  true

Comparable是num類實現的抽象類,根據驗證結果可知:
dart泛型List時,泛型爲父類的List是泛型爲子類List的父類
這個有點繞,理解這個意思就行;其實看到這裏,如果之前有過java開發經驗的話,就會發現這個其實和java中的數組很相似,參考Java中的數組和List集合以及類型強轉
在這裏插入圖片描述
這個圖說明了java中數組之間是存在繼承關係的,並且和實際類之間的繼承關係相似.

接下來我們看一下泛型類中泛型的關係;

void main() {
  var aa = AA.name("123");
  aa.test();
}

class AA<T> {
  T field;

  AA.name(this.field);

  void test() {
    print("".runtimeType); //String
    print(T); //String
    print(field.runtimeType == T);  //true
  }
}

相信大家對這段代碼以及結果都沒有什麼疑問,泛型在傳遞之後,到了類AA中仍然爲String類型,但下面這段代碼就不同了:

void main() {
  var aa = AA.name([]);
  aa.test();
}

class AA<T> {
  T field;

  AA.name(this.field);

  void test() {
    print([].runtimeType); //List<dynamic>
    print(T); //List<dynamic>
    print(field.runtimeType == T);  //false
  }
}

我們在創建類時,指定泛型爲List(或者說是List),然後在AA中判斷,發現泛型T和field的運行時類型不同,雖然toString是一樣的,但如果打印hashCode可以發現,對應着兩個不同的Type.

加入我們傳入一個map類型,結果更是不如人意

void main() {
  var aa = AA.name({"ss":1});
  aa.test();
}

class AA<T> {
  T field;

  AA.name(this.field);

  void test() {
    print(field.runtimeType); // _InternalLinkedHashMap<String, int>
    print(T); // Map<String, int>
    print(field.runtimeType == T); // false
  }
}

實際類型是泛型的一個實現類;

那假如傳入一個自定義泛型類呢?

void main() {
  var aa = AA.name(BB<num>());
  aa.test();
}

class AA<T> {
  T field;

  AA.name(this.field);

  void test() {
    print(field.runtimeType); // BB<num>
    print(T); //  BB<num>
    print(field.runtimeType == T); // true
  }
}

class BB<T> {}

還好,自定義泛型類時,運行時runtimeType與泛型Type是相同的

關於dart泛型的探討只進行到這裏,大概總結一下:

  1. 泛型在運行時保留
  2. List泛型之間存在關係,可以通過 is 進行判斷,如 field is List
  3. List泛型即便toString方法返回相同的值,也可能是兩個不同的Type

有了上面的論斷,接下來進入正題

dart-bean

bean只是我們通用的一種class類的說明,用於表明數據模型.

這裏還是先看一下其他語言中的bean;

先看java中通常的做法:

public class TestBean {
    private String username;
    private boolean isVip;

    public String getUsername() {
        return username;
    }

    public TestBean setUsername(String username) {
        this.username = username;
        return this;
    }

    public boolean isVip() {
        return isVip;
    }

    public TestBean setVip(boolean vip) {
        isVip = vip;
        return this;
    }
}

kotlin中表示形式會簡單的多:

data class TestBean(
        var username: String? = null,
        var isVip: Boolean = false
)

其實bean的代碼提現不是關鍵,重要的地方在於,如何方便的爲bean賦值和操作bean,因爲 JVM 語言可以進行反射,因此,在獲取到後臺傳入的json格式數據時,我們可以很方便的通過一些庫完成自動賦值操作:

var bean = Gson().fromJson("{\"username\":\"www\",\"isVip\":false}",TestBean::class.java)

在dart中,bean的代碼表現形式和其他語言基本相同,都是在單獨的class中聲明成員變量:

class TestBean {
  String username;
  bool isVip;
}

如果只是單純的dart項目,我們仍然可以通過鏡像功能來爲bean賦值:

import 'dart:mirrors';

class TestBean {
  String username;
  bool isVip;
}

void main() {
  Map<String, dynamic> json = {
    "username": "www",
    "isVip": false,
  };
  var class_bean = reflectClass(TestBean);
  var obj = class_bean.newInstance(Symbol.empty, []).reflectee;
  var instance_bean = reflect(obj);
  class_bean.declarations.forEach((key, value) {
    if (value is VariableMirror) {
      var key_string = RegExp("^Symbol\\(\"(.+?)\"\\)\$")
          .firstMatch(key.toString())
          .group(1);
      instance_bean.setField(key, json[key_string]);
    }
  });
  print("${obj.username}  ${obj.isVip}"); // www  false
}

雖然dart的鏡像功能看起來很是"不爽",但確實是有這個實現的.

不過有些不幸的是,在Flutter中,dart不允許使用鏡像功能,具體原因可以參考Flutter實戰中提到一段話:

很多人可能會問Flutter中有沒有像Java開發中的Gson/Jackson一樣的Json序列化類庫?答案是沒有!因爲這樣的庫需要使用運行時反射,這在Flutter中是禁用的。運行時反射會干擾Dart的tree shaking,使用tree shaking,可以在release版中“去除”未使用的代碼,這可以顯著優化應用程序的大小。由於反射會默認應用到所有代碼,因此tree shaking會很難工作,因爲在啓用反射時很難知道哪些代碼未被使用,因此冗餘代碼很難剝離,所以Flutter中禁用了Dart的反射功能,而正因如此也就無法實現動態轉化Model的功能。

因此,如果想在Flutter中實現bean,就需要其他的一些技巧.

flutter dart-bean

目前大多數情況下,都是在bean類中添加命名構造函數,然後通過工具自動生成部分代碼幫助解析和構建bean.

例如上面的bean類會對應生成如下代碼:

class Response {
  String username;
  bool isVip;

  Response.fromJsonMap(Map<String, dynamic> map)
      : username = map["username"],
        isVip = map["isVip"];

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['username'] = username;
    data['isVip'] = isVip;
    return data;
  }
}

即多添加兩部分內容:

  1. 命名構造函數:Response.fromJsonMap,參數爲Map<String, dynamic>類型,用於由 json 生成 bean;爲json反序列化
  2. toJson 方法,用於將對象序列化爲json字符串

通過添加兩個方法,可以實現json串及bean的相互轉換.不過如果每次都得手寫那會很麻煩,因爲可能需要的字段特別多,一般這種情況都需要用工具來完成.
官方也提供了相應的庫:json_serializable,不過如果我們習慣了 GsonFormat 的快捷,不免會對這種方法感到不滿,並且目前針對 dart 生成bean有很多不得不考慮的問題,比如泛型,嵌套內部類等等,這些情況下,我們無法通過一般的命令行或者工具直接生成.

這裏我改寫了 ,把其中的java類型部分修改爲了dart類型,同時修改了生成方法:

插件地址在jetbrains-plugins-JsonToDartBean;

Package包地址在json_holder_impl;

詳細的使用教程請參考github:json-to-dart-bean;

效果大概是這樣:
在這裏插入圖片描述
使用方法很簡單,只要按照教程導入包,然後安裝插件就好,這裏進行幾點說明:

1. 插件功能

因爲插件修改自GsonFormat,因此命名規範等都沿用了之前的定義,功能主要包括:

  1. 在dart文件中,使用快捷鍵alt + d,會根據粘貼的json 生成 bean
  2. json 支持創建時修改類型,這點和 GsonFormat 相同
  3. json 支持嵌套內部類,如果內部類不想創建新的,想使用已存在的某個class,也可以通過創建時修改類型來達到目的:
    假如默認要生成的類型是這樣:
    在這裏插入圖片描述
    我們可以修改類型或者去掉勾選,變成這樣:

在這裏插入圖片描述
4. 支持兩層List連續嵌套,比如這種情況,會生成List類型的變量:

{
    "values":[
        [
        1,2,4
        ]
    ]
}
庫特點

通過該插件簡單的生成較短的代碼進行查看:

//******************************************************************
//**************************** Generate By JsonToDartBean  **********
//**************************** Thu Jun 06 18:33:38 CST 2019  **********
//******************************************************************

import 'package:json_holder_impl/json_holder_impl.dart';

class FirstBean with JsonHolderImpl<FirstBean> {
  /// [key : value] => [name : www]
  String get name => getValue("name");
  set name(String value) => setValue("name", value);

  FirstBean.fromJson([Map<String, dynamic> json]) {
    fromJson(json);
  }

  @override
  JsonHolderImpl<FirstBean> provideCreator(Map<String, dynamic> json) {
    return FirstBean.fromJson(json);
  }

  @override
  List<FirstBean> provideListCreator() {
    return <FirstBean>[];
  }

  @override
  List<List<FirstBean>> provideListListCreator() {
    return <List<FirstBean>>[];
  }

}

可以看到類中其實沒有生成任何的成員域,所有變量都同等的被get 和 set方法替代,這種方式相對於目前普遍的bean構建方法有一個好處,那就是變量值只有在真正調用的時候纔會去解析,能在一定程度上加快運行速度(同樣在父類中處理了類型轉換,並且可自定義,具體自定義方式參考上面提到的github教程即可).

代碼中只看到了fromJson方法,toJson方法 定義在了父類JsonHolderImpl中,直接調用即可.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章