(三)flutter入门之dart中的集合、动态类型和函数

0.543字数 3646阅读 1983

上个博客我们大概了解了dart中的基本数据类型的特性以及和java、javaScript等语言之间的比较,这篇博客我们主要来介绍一下dart中的集合操作,动态变量和函数相关的内容

集合

熟悉java的都知道,在java中集合分为了Map,List和Set三种,每一种职责和使用方式都不一样,并且在java中还提供了一个通用的数组类型Array用来更加精细的处理和集合不一样的操作,但是我们在使用List集合和Array的时候,经常会因为数组和集合之间的互相使用而感觉到为了详细区分牺牲了体验度,而js中就很简单,因为js中并不限制你是什么类型,可以直接初始化一个[]作为数组或者集合使用,并且可以初始化添加多个参数,还可以在初始化以后继续push的方式添加元素进入集合中,可以说比起java这方面操作上的确好了不少,但是缺陷也很明显,js的集合不能强制指定类型,可以任意更改,并且js中集合单一,并无map,set等具体的细节区分。dart作为一个新生代的语言,既然能集百家之长,肯定是要具备java和js这些比较优秀的特性,并且dart综合了两大语言的特点,摒弃了一些不好的体验

List

dart的list既有数组特性也有java中的list特性,接下来我们看一下使用:

void main() {
  List list = new List();
  list.add("aa");
  list.add(1);
  list.add(1.2);
  print(list); 
}

看到这里,熟悉java的一定会认为报错,但是输出的结果却是如下所示,是不是很惊讶?其实要明白,dart中默认为list的类型为最大的基类Object,所以如果我们不指定泛型的话,会当成List<Ob

ject>方式,所以这里能正确输出

[aa, 1, 1.2]

接着我们指定一下泛型为int类型以后,再来看看情况

list泛型插入其他类型报错.png

很明显,编译时期就报错了,因为强制指定了泛型的原因,而在开发中,我们往往有时候希望初始化的时候就设置几个参数进去,然后再去做add等操作,传统java类型的list.add操作可能要写很多次,这样的话初始化会很不方便,能不能像数组那样直接初始化插入值,然后后面有需要的话再去add呢?如果是在java中,这样的操作是不能实现的,因为数组的长度是固定的,而list又没有类型数组那样的初始化赋值方式,但是在js中数组初始化赋值是个最常见不过的事情,并且还支持后面push操作,在dart中,也是支持初始化赋值后期add元素的操作的,如下:

void main() {
  List<int> list = [1,2,3];
  list.add(1);
  print(list); 
}

并且在dart中提供了一系列语法糖api,不仅支持add单个元素,支持addAll添加一个集合进行合并,还支持多种remove操作,并且还有replaceRange 这样的语法糖可以进行元素的替换操作,集合的api在java和js中都有所不足,dart在此基础上进行了改进操作

Map

map是个key-value方式存储数据的集合,这点在java语言中体现的很多,dart中完美的继承了过来,并且提供了类似java的api,但是在dart中不存在接口的概念,所以可以直接new Map出来:

Map<String,Object> map = new Map<String,Object>();
map['abc'] = 'bbb';
print(map);

可以看出来dart中的map使用和js中一样,直接指定key进行赋值就可以了,并且支持key-value的泛型,还支持了一些常用的语法糖操作:

  map.containsKey("abc"); //是否存在当前key  true
  map.containsValue("bbb");//是否存在当前value true
  map.isEmpty;//false
  map.isNotEmpty;//true
  map.keys;//获取所有的key [abc]
  map.values;//获取所有的value [bbb]

可以看出来,dart中的map比起java的map的api支持还是很不错的,并且针对java中map无法访问value的问题做了改进,直接提供了api可以操作value

Set

dart中保留了Set集合这个设计(和java保持一致,这个优良特性被保留了下来),但是在官网的文档也好,包括flutter的文档都没有提到Set这个类型,但是测试下来的确是按照java的Set设计的Set类型,接下来看看Set的基本使用:

void main(){
  Set set = new Set();
  Set newSet = new Set();
  newSet.add("bac");
  set.add("daba");
  set.remove("daba");
  Set unionSet = set.union(newSet);//可以和其他的set拼接,返回合并的集合
  set.add(1);
  set.add([1,2,3]);
  print(set);//{1, [1, 2, 3]}
  print(unionSet);//{bac}
}

从上面可以看出来set的基本使用和java中几乎一样,但是常用的api中多了一个union,官方的注释如下:

/**
   * Returns a new set which contains all the elements of this set and [other].
   *
   * That is, the returned set contains all the elements of this [Set] and
   * all the elements of [other].
   */
  Set<E> union(Set<E> other);

可以看出来大概是说,将两个set合并成一个set,返回出去,也就是说这个方法是不改变原来的两个set的结构,只是将元素取出来,返回新的set,那么就有一个问题,这个新的set是否会根据两个合并的set的元素添加或者删除跟着改变元素呢(在js中的合并操作可以根据新增或者修改或者删除动态的改变)?

void main(){
  Set set = new Set();
  Set newSet = new Set();
  newSet.add("bac");
  set.add("daba");
  set.remove("daba");
  set.add(1);
  set.add(1);
  set.add([1,2,3]);
  Set unionSet = set.union(newSet);
  print(set);//{1, [1, 2, 3]}
  print(unionSet);//{1, [1, 2, 3], bac}
}

可以看出来我们只是多了一个重复添加1的操作,并且将最后的合并方法放在了两个set的添加元素之后,很明显最终输出的结果和上面的结果不一样,所以我们可以得出结论,set的union操作是把两个set的元素单独遍历取出来存入的新的set而不是动态合并的操作,所以可以看出来dart的set特性和java的一样

集合遍历

在java中list遍历的方法很多,for i循环或者for增强循环,或者迭代器遍历都可以,Map也支持keys循环取值或者for增强以及迭代器,set也有for增强和迭代器的遍历方案,dart中自然保留了这些特性,但是在dart中,for增强循环取消,取而代之的是js中的forEach循环,比起for增强,可以传递当前的每一个item,并且可以传递当前的index索引,比起java的for增强不能获取当前的索引来说可玩性好了很多

void main(){
  Map<String,Object> map = new Map<String,Object>();
  map['abc'] = 'abc';
  map['bbb'] = 'abc';
  map.forEach((key,value){
    print(key+",value:"+value);//abc,value:1   bbb,value:3
  });
  List list = ['1','a','pdc'];
  for(int i = 0;i<list.length;i ++){
    print('value:'+list[i]); //value:1 value:a value:pdc
  }
  Set set = {'abc',1212,'bbb'};
  //set和list之间可以直接使用toList或者toSet方法进行转换,但是map不可以直接进行转换!map需要转换成list或者set可以选择获取keys/values进行toList/toSet转换操作
  set.toList().forEach((value){
     print(value);  //abc 1212 bbb
  });
}

动态变量类型

在dart中加入了动态变量类型的特性,不仅包含了js和java8以后加入的var,并且支持Object基类,还加入了kotlin的dynamic关键字,接下来我们看看这三种动态变量

var

var的特性我们在js中都很熟悉,可以随意给var类型的变量赋值,但是在dart中,var只是可以让你后期再申明具体的类型,并不是可以任意改动类型(面向对象语言通病),如果你在申明了一次变量以后,再去设置一个新的类型,那么编译器就会直接抛出异常

  var t;
  t="hi world";
  t=1000;// 下面代码在dart中会报错,因为变量t的类型已经确定为String,

dynamic

在dart中存在一个情况,我们前期不确定这个类型,但是后期又想修改掉怎么办?var不能修改类型,有木有这种完全动态的类型呢?有,dynamic关键字就是完全动态的类型,和var的区别在于,var是在编译期就确定了类型,也就是说当第一次申明以后就确定了类型,而dynamic是属于运行时动态类型,也就是说你可以任意修改类型或者申明,只要最终运行的时候能确定类型,使用如下:

dynamic t;//很坑的是这个类型目前的dart插件居然没有代码提示必须全部写完才出现,不能判断正确性
t = "hi world";
t = 1000;//可以进行修改申明类型
print(t);//输出为:1000
Object

dart这个语言和java一样,一切都是对象,并且一切对象的基类都是Object,所以在dart中同样支持使用Object来申明任意类型,并且Object无限制可以接受任何类型,那么Object和dynamic的区别在哪呢?两者都可以动态接受类型并且都是运行时确定类型的啊,别急,我们看一个简单的案例:

dynamic a = "a";
Object b = "b";
void main() {
     print(a.length);//这里可以调用length属性,完美输出
     print(b.length);//这里是object类型就不能调用length方法,编译就报错
 }   

从上面的案例就能看出来区别,dynamic属于动态改变的类型,也就是说编译器会根据你申明的类型自动推断为该类型,所以该类型的所有的属性都可以使用,但是使用Object的类编译器无法推断具体类型,默认为还是Object类型,所以只能调用Object的属性和方法,而Object中不存在length,所以就会报错,所以总结下来,Object是需要手动转换具体类型来进行操作,而dynamic属于自动推断类型,调用该类型的属性,灵活度来说dynamic更高,但是按照代码的严谨性和可读性来说Object更高更不容易出错,所以一般来说,在大部分代码不是很严格的环境下,我们会选择dynamic来替代Object进行开发,提高灵活度

常量

const

在js我们申明变量有var,es6开始又加入了let和const两种类型,其中const代表的就是固定的值,一旦申明一次就不可以再去修改的变量,这点和java中的final很相似,dart中同时包含了js的const和final两个常量类型,在dart中const是编译常量,也就是说当const修饰了这个变量以后,如果再去修改,编译器直接就报错(这里应该是完全沿袭js中的const)

const str1 = "hi world";
str1 = "aaa";//编译器直接报错
var list =  const [1, 2, 3];//const还可以修饰变量对应的值,强制让这个值也变成固定的
final

dart中有了编译期的常量const以后,也加入了运行时的常量final关键字,final和const都是常量,区别在于作用范围,final只能修饰变量,并且确定变量的动态赋值类型以后就固定了,不能再去修改,而const不仅仅可以修饰变量,还可以修饰值,比如var list = const [1, 2, 3]就是通过const修饰了一个数组,这个数组的值是固定的,不能变,与修饰在变量上作用范围是不一样的

final str1 = "hi world";
str1 = "bbb";//这里也会报错

并且使用const修饰的常量可以作为构造,如果构造是完全一样的,这时候使用的是同一个实例,用于提高编译和运行的效率

getConst() => const [1, 2];
main() {
  var a = getConst();
  var b = getConst();
  identical(a, b); // =>true
}
static

dart中的static作用很弱,只能用来修饰成员变量,在类中使用,不可以在方法中给某个局部变量修饰,并且这个修饰的对象在运行期才会被初始化

void main(){
  print(Test.a);
}

class Test{
  static String a = "aaa";//只能写在类成员变量上,在方法或者main等修饰都不可以
}
函数

dart中有一种特殊的类型--函数类型Function,该函数可以在开发的时候传递使用,也可以进行赋值传递,这点和js完全相同

void main(){
  Function fun = getBool;//申明一个函数,后面就可以直接传递该函数了
}

bool getBool(){
  return false;
}

需要注意的是,dart中函数不设置返回值,也可以,因为存在类型dynamic类型,但是需要注意的是,这个时候返回值不会是动态推断类型,而是固定的dynamic 类型,不会跟着返回的内容具体类型自动变动

//这里我们返回的是bool类型,但是不设置类型返回的不是bool,而是dynamic
isNoble(int atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}
if(isNoble(1)){//这里编译就会报错,因为不是bool类型
}

上面说到函数还可以进行传递,可以申明成变量,也可以直接当成参数进行传递:

//函数作为变量直接使用
var say= (str){
  print(str);//输出-->hi world
};
say("hi world");

//函数作为参数进行传递
void execute(var callback){
    callback();//一样可以把参数当成函数调用,前提是这参数传来的时候是个函数,否则报错
}
execute(()=>print("xxx"))

可选位置参数与可选命名参数

可选位置参数

我们在开发java的过程中,方法中的参数可能有多个可能有一个,所以我们经常定义一个方法,有多个参数,不需要这个参数的时候传递null占位,或者我们需要重载多个一样的方法,只是参数不一样,进行不一样的业务开发,可以说虽然很严格,但是灵活度等都下降了不少,dart中同样支持传递几个固定的参数,但是dart在这个基础上改进了,分为必传参数和可不传参数,如果存在多个参数,我们固定了几个必传的参数,其他的参数不确定是否需要传递,我们可以使用[]包裹不确定的参数,这样我们使用的时候可以传该参数也可以不传,但是这里需要注意的是,[]包裹的参数类型和位置是固定的,这点和java的多参数一样,传递的时候必须按照对应的位置一一对应传递,唯一的区别在于,这个参数不传递的时候可以不写,而不是传递null(因为默认值就是null)

void main(){
  print(say("form","msg"));//-->form says msg
  print(say("form","msg","bbb"));//-->form says msg with a bbb
}

//这里的device固定了类型为String,固定了下标为2,传递的时候必须按照这个顺序传递,可以不传
String say(String from, String msg, [String device]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  return result;
}

但是,熟悉js的人都知道,在js中我们可以随便指定参数,也可以在函数无参数的情况下,调用方法的时候传递参数,只要在函数内部调用arguments获取当前传递的参数数量和类型,做自己的业务处理就可以了,(因为js函数和变量名不可以重复,所以不存在重载)但是这么做的坏处很明显,我们不能很清晰明了的看到可能有哪些参数,对于开发和可读性而言不是特别友好,dart为了改进这种情况,特意出了plus版本--可选命名参数

可选命名参数

可选命名参数和上述的可选位置参数大体使用相同,不是必传的参数需要用括号包裹,但是区别是,这里的参数使用{}包裹(当然也可以把必传参数也包裹进来,只需要在参数前加入一个必传的注解@required,但是这个注解是flutter的库才有,dart开发的时候不可以使用,只能选择固定参数独立出来,包裹动态参数)

flutter中我们可以这么写:

import 'package:flutter/material.dart';

void main(){
  print(say(from:"form",msg:"msg"));//-->form says msg
  print(say(from:"form",msg:"msg",device:"bbb"));//-->form says msg with a bbb
}

String say({@required String from,@required String msg,String device}) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  return result;
}

dart中可以这么写:

void main(){
  print(say("form","msg"));//-->form says msg
  print(say("form","msg",device:"bbb"));//-->form says msg with a bbb
}

String say(String from,String msg,{String device}) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  return result;
}

推荐阅读更多精彩内容