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

Flutter 發佈到如今, 愈來愈多人開始嘗試使用 Dart 來完成部分功能;前端

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

先看一下最終的效果顯示:git

Dart 語言

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

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

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

說了那麼多,尚未到正題,接下來,咱們來看下 Dart 中比較 "特殊" 的特性:泛型markdown

Dart泛型 與 其餘泛型

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

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 部分,能夠看到只有變量ce的運行時類型相同,而且若是使用編譯器的話,就能夠發現:

List c = <dynamic>[];
複製代碼

List<dynamic> 其實就是 List,二者是同樣的.

在知道了dart的泛型特性後,不由會思考:List<int>List,或者說和List<dynamic>是什麼關係?和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
複製代碼

Comparablenum類實現的抽象類,根據驗證結果可知:

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<dynamic>),而後在AA中判斷,發現泛型Tfield的運行時類型不一樣,雖然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<num>
  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<List>類型的變量:

    {
        "values":[
            [
            1,2,4
            ]
        ]
    }
    複製代碼

2. 庫特色

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

//******************************************************************
//**************************** 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中,直接調用便可.

注: 代碼不可用於商業用途 注: 若有 插件 GsonFormat 涉及到的版權問題,請及時告知

相關文章
相關標籤/搜索