Flutter学习之Dart方法,异常与类

2020-04-18  本文已影响0人  echoSuny

1 方法

1.1 一等方法对象

在dart中,所有的方法都是对象,可以直接赋值给Function对象

void test(){
}
// 赋值
Function f = test;
// 调用
f();

这样就可以调用到test()方法了

void test(Function fun){
}

Function f = test;

// 这里缺少了调用

可以看到上面第二段代码是没有调用的,是因为我们的test()方法需要传入进去一个Function对象的。但是我们没有Function对象。一个方法其实就是Function对象。所以我们把匿名函数当作Function传入:

f.((int a,int b,int c){
    return "echo";
})

但是Function没有办法表示要传递的这个方法需要传几个参数,有没有返回值。虽然编译器不会报错,但是一运行就会出错。那么该怎么解决呢?我们可以使用 typedef 关键字:

typedef void F(int a,String b);

void test(F f){
    f(1,"echo");
}
// 调用

test((int a,String b){
    print("${a}${b}");
})
// 甚至可以直接传入整个方法 但是不推荐这样写 很麻烦
void test1(void (int a,String b)){
}

使用 typedef 关键字可以定义一个类型,也就是代码中的F类型,这个F类型其实就是一个方法,接收两个参数,返回void。定义好了之后 就可以作为参数传入test()方法中了。
具体使用场景可以根据Android中的setOnClickListener():

button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                
            }
        });

在Android中我们需要传入一个接口回调才能够实现调用onClick()函数,那么在dart中我们可以直接这样使用:

typedef void OnClickListener(String str);
void setOnClickListener(OnClickListener onClick){
      onClick("点击了一下view");
}

1.2 可选位置参数

void test(int a,String b){
}

void test(String b){
  test(10,b)
}

在java中我们可以经常看到这种重载函数供调用者选择调用,dart考虑到了这样的不便性,于是就支持了可选位置参数来解决这个问题:

void test([int a,int b]){
  print(a);
  print(b);
}

// 不传任何参数调用
test();
// 只传一个参数 默认给a赋值
test(1);

不传参数调用的打印结果为 a和b都为null,只传一个参数打印结果是 a 为1,b 为 null。若想给b传值,那么只能两个都传,不能实现只传一个参数然后给b赋值。dart甚至可以和kotlin一样给方法参数赋默认值:

void test([int a = 1,int b = 2]){
  print(a);
  print(b);
}

1.3 可选命名参数

void test({int a, int b}){
  print(a);
  print(b);
}

可以看到可选命名参数与可选位置参数不同的是是用{}括起来的参数。下面是具体使用:

test(); // 可以什么都不传

test(10);// 这样是不允许的

test(b:6); // 可以给指定的参数传值 但必须以键值对的形式,也就是 参数名:值

test(b:6 , a:3); // 顺序甚至可以颠倒过来

另外可选命名参数也是可以在方法参数中设置默认值的。

2 异常

java的异常是需要我们进行强制捕获的,但是dart是不需要的,但并不是说不支持try-catch。dart的异常和kotlin一样。

// java当中
public void test() throw Exception{
    throw new Exception("this is a exception");
}
// 调用时就需要主动捕获
public void testException(){
        try {
            test();
        } catch (Exception e) {
            e.printStackTrace();
        }
}

// dart中
void test(){
    throw new Exception("this is a exception");
}
// 不用主动捕获
void testException(){
      test();   
}

虽然dart不强制我们捕获,但是如果不确定方法是不是会出现问题,我们也可以主动添加try-catch:

void test(){
    throw new Exception("this is a exception");
}
void testException(){
    try{
      a();   
    }catch(e){
      print(e.runtimeType)
      print(e)
  }     
}

输出结果为_ Exception和Exception this is a exception。另外catch语句的括号内还支持多个参数(最多两个):

  try{
      a();   
    }catch(e,s){
      print(s.runtimeType)
  }     

输出结果为_StackTrace,也就是调用栈信息。如果print(s)则会输出第几行的方法在第几行被调用出现了什么什么异常这样的信息。
另外在Java 中我们经常要根据不同的异常类型来做不同的处理,往往要catch很多种异常:

void testException(){
        try {
            a();
        } catch (IOException e) {
            e.printStackTrace();
        }catch (SocketException e) {
            e.printStackTrace();
        }
}

要知道在java中我们捕获的都是Exception的子类。这种方法在dart中同样是支持的,并且更为灵活:

void test(){
    throw new Exception("this is a exception");
     //  throw 123;
     //  throw "hello";
    //  throw test1;
}

void test1(){
    print("hello");
}

void testException(){
    try{
      a();   
    }on Exception catch(e){
      print(e);
   } on int catch(e){
      print(e);
   }on String catch(e){
      print(e);
   } on Function catch(e){
      e();
   }       
}

可以看到在dart中不仅可以抛出异常,甚至一个普通的对象或者方法(前面讲了其实方法也是对象)都是可以抛出的,那么对应的我们只需要在catch前面使用 on 关键字加上类型就可以进行对应类型的捕获。

3 类

3.1 类的基础了解

dart和java一样也是一门面向对象语言,每个对象都是一个类的实例,所有的类都继承于Object。在java中我们总是去新建一个java class 并且用大写开头驼峰式的命名规则来创建一个类,那么这个 .java 文件就会自动帮我们生成 public class Xxx 这种格式。但是在dart中我们需要先创建一个dart文件,这个一般使用小写开头加上下划线的风格来命名。并且建好之后是一个空白的文件,需要我们手动去写class Xxx这些内容。

first.dart // dart文件名

class People{
    String name;
    int age;
}

需要注意的是在dart语言中是没有private,public,protected这种权限修饰符的,那么这是不是意味着我们定义的类的属性就可以被外界随意访问呢?

test_class.dart

void main(){
    var p = new People();
    p.x;
    p.y;
}

不幸的是可以看到外界可以随便调用。那么就没有别的办法来隐藏属性吗?其实是有的。我们只需要在属性名字前面加上下划线就可以防止外界随意调用了。

class People{
    String _name;
    int _age;
}

这样别的类就不可以访问People的属性了。同样的类名如果以下划线开头,那么这个类就变成私有的,就不能被外界使用了。

3.2 命名构造方法

class People{
    String _name;
    int age;

    People(this._name, this.age);
}

可以看到相较于java的构造方法来说还是简便了一点。这样的构造方法必须把所有的参数都传入,不能只使用其中的一个来进行构造方法的重载(dart中的普通方法也是不允许重载的)。那么我们有时候确实有重载构造函数的需求的,那么我们怎么办呢?

People.name(this._name);

我们只需要构造方法的中间点上一个名字。本例中使用的是 .name ,其中 name 可以换成其他任何的名字,例如 B,x,name等等都可以,不区分大小写。这种方式被称为命名构造函数。那么在调用的时候是这样的:

var p = People.name("echo");

另外构造方法也是支持可选命名参数和可选位置参数的用法的:

    // 构造方法可选命名用法
    People({_name , age});
    // 构造方法可选位置用法
    People([_name , age]);

3.3 参数初始化列表

class People{
    String _name;
    String age;

    People(this._name, this.age);

    People.na() : _name = "echo",age = "18";
}

可以看到如果我们使用了new People.na()这样去构造People对象的时候,我们的 _name 和 age 就会被赋值。如果这个People是用来接收数据的话该怎么初始化参数呢?有一种简便的写法:

// 方法体 也就是后面的{}可以去掉
People.na(Map map) : _name = map["name"],age = ["age"]{} ; 

这样就方便很多了。我们只需要传入一个map就可以轻松的把所有属性参数初始化。

3.4 重定向构造函数

在Android中自定义view的时候经常需要重写三个构造函数,并在构造函数里由参数少的构造方法逐渐调用参数多的:

public class MyView extends View {

        public MyView(Context context) {
            this(context, null);
        }

        public MyView(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, 0);
        }

        public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);

        }
    }

那么在dart中是不支持重载函数的,那如果我们有这样的需求该怎么办呢?

class People{
    String _name;
    int age;

    People(String _name, int age);

    People.name(String name) : this(name , 0); 
}

具体就是在命名构造函数后面加上 :this就可以了。

3.5 常量构造函数

class People{
    final String _name;
    final int age;

    const People(String _name, int age);
}

常量构造函数就是在类的构造函数前面加上一个 const 关键字,并且属性需要使用 final 来修饰。那这和普通的构造函数有什么区别呢?

void main(){
     var p1 = new People("echo",18);
     var p2 = new People("echo",18);
}

可以看到和普通的构造方法没什么区别。事实上也确实是没什么区别(可以把 new 关键字去了,也是没区别)。

void main(){
     var p1 = const People("echo",18);
     var p2 = const People("echo",18);
     print("p1&hashCode == p2&hashCode : " + (p1.hashCode == p2.hashCode));
     print("p1 == p2 : "+ (p1== p2));
}

下面稍微修改一下:

void main(){
     var p1 = const People("echo",18);
     var p2 = const People("echo",19);
     print("p1&hashCode == p2&hashCode : " + (p1.hashCode == p2.hashCode));
     print("p1 == p2 : "+ (p1== p2));
}

根据上面的两个不同的结果我们可以得出:

使用 const 修饰构造函数的类,在创建多个对象的时候,如果参数也一样,那么这几个对象就是同一个编译期常量。也就意味着只占用一份内存。

3.6 工厂构造函数

在设计模式中有一种叫工厂模式,可以用来生产一类对象。比如:

public class Factory {
        public static TextView create(Context context) {
            return new TextView(context);
        }
    }

那dart中的工厂构造函数和这个类似,具体使用方法如下:

 class Manager {

        Manager();

        factory Manager.get() {
            return new Manager();
        }
    }

其中factory是一个关键字,使用在命名构造函数前面且必须返回一个Manager实例。根据这个例子来说默认构造方法也是必须的。
那么具体的使用场景的话可以使用这种方式来实现单例:

 class Manager {
          // 前面加下划线是为了私有
          staticManager _instance;
          // 这种构造方法就不可以了 因为前面不可以加下划线 别的类还是可以使用
          // Manager();
         
          // 使用命名构造函数,并且在函数的名字前面加上下划线
         Manager._instance();
        factory Manager.get() {
            if(_instance == null){
                _instance = new Manager._instance();
            }
            return _instance;
        }
    }

这样就完成了一个单例模式。

3.7 getter & setter

java中每写一个实体类我们都需要写对应的get和set方法。同样dart也支持这样做,只是形式有所不同:

class People{
    String _name;
    int _age;
    // 第一种 get写法
    String get name => _name;
    // 第二种 get写法
    String get name{
        return _name;
    } 

  // 第一种 set写法 void可以省去
  void set name(String name) => _name = name;

  // 第二种 set写法 void可以省去
   void set name(String name) {
        _name = name;
   }
}

上面展示的例子中所有的属性都是私有的(假如都是公有属性的话dart会为我们生成隐式的get和set方法)。需要注意的是代码中无论是get和set中的name实际上是方法名,是可以修改的。下面是使用:

var p = People();
p.name = "echo";
var name = p.name;

3.8 操作符重载

操作符重载值得是可以对 + ,- ,* ,/ 等等这一类的运算符进行重载并根据需求重写。这么说可能有些抽象,下面我们可以根据一个例子来理解一下:

class People{
    String _name;
    int _age;

     String get name => _name;
     set name(String name) => _name = name;

     int get age => _age;
     set age(int age) => _age = age;

     People operator + (People wife){
        var p = People();
        p._name = wife.name + _name;
        p._age = 0; 
        return p;
    }
}

operator是一个关键字,后面跟的是要重载的操作符。下面是具体使用:

  // 丈夫角色
var husband = People();
husband.name = "mark";
husband.age = 23;
// 妻子角色
var wife = People();
wife.name = "echo";
wife.age = 24;
// 孕育了一个小baby
var child = husband + wife;

dart中的操作符重载非常灵活,上面只是一种符合常理的用法,你几乎可以使用任何类型,比如String operator + (int i)这样的代码写在People类当中也是可以的。

3.9 抽象类和接口

1 抽象类

dart中的抽象类和java中都是使用 abstract 关键字来声明的。但是dart中的抽象方法却不需要在方法前面加上 abstract 关键字。除此之外和java就没有什么区别了。

abstract  class People{
    String _name;
    int _age;
     
    // 抽象方法
    void walk();
    // 非抽象方法
    void run(){
    }
}
2 接口

与java不同的是dart没有 interface 关键字。dart中每个类都隐式的定义了一个包含所有实例成员的接口。并且这个类实现了这个接口。看文字可能不是很直观,那么我们通过代码来了解:

class Animal{
    
    void eat(){ };
}
// 实现
class Dog implements Animal{
    // 强制实现
    @override
    void eat(){

    }
}
// 继承
class Cat extends Animal{
    // 可以不用实现
}

可以看到接口的定义依然是用class,但是实现上还是和java一样使用 implements 关键字,然后重写里面的方法。而下面的是继承则不用重写方法。

在dart中所有的类都可以当作是接口。并且和java一样只能单继承,但是可以多实现

下面介绍一下dart中的一个语法糖的使用:

class Test{
    void call(){ };

    void move(){ };
}

void main(){
    var test = Test();
    test();
}

什么情况?对象竟然可以被当作方法来使用???这其实就是dart中的一个语法糖。当你的类中存在call()方法时(只能是call(),可以有参数。move()方法是调不到的),可以使用 对象名称() 这样的方式来调用内部的call()方法。当然我们也可以使用test.call()来显式的调用。

3.10 Mixins 混入

Mixins是在一种在"多继承"中重用一个类代码的方法,它的基本形式如下:

  // 老虎
class Tiger{
    // 游泳
    void swim(){
    }
}
// 狮子
class Lion{
    // 爬树
    void climbTree(){
    }
}

// 狮虎兽
class LionTiger with Tiger, Lion{
    // 跳跃
    void jump(){
    }
}

void main(){
    var lt = LionTiger();
    lt. swim();
    lt. climbTree();
    lt. jump();
}

可以看到老虎会游泳,狮子会爬树。那么它们的杂交后台狮虎兽除了可以有自己的跳跃方法之外,还可以使用老虎的游泳和狮子的爬树方法。这就是Mixins方式的简单实用。需要注意的是Tiger和Lion是不能有自己的构造方法的,否则就不能用在关键字 with 后。假如LionTiger没有自己特有的方法,那么还可以进行如下简写:

class LionTiger = Object with Tiger, Lion;

这样还是可以调用老虎中的游泳和狮子中的爬树方法的。假如Tiger和Lion中有相同的方法:

class Tiger{
    void swim(){
    }

    void eat(){
    }
}
class Lion{
    void climbTree(){
    }

    void eat(){
    }
}

class LionTiger with Tiger, Lion{

    void jump(){
    }

    void eat(){
        // 甚至可以调用super
       super.eat() 
    }
}

那么这种情况下如果LionTiger调用eat()方法的话则会根据 with 关键字后面的类按照倒序的方式来调用。按照例子来说就是会调用Lion的eat()方法,Tiger的eat()则不会调用。如果说Lion中没有找到,那么就会往前在Tiger中找。假如LionTiger类中也有一样的重名方法eat(),那么会优先使用自己当中的eat()方法。
这样看下来Mixins就基本上可以算是多继承了,但是官方也没有说混入就是多继承。不过这不妨碍我们使用,我们只需要按照多继承来理解就好了。

上一篇下一篇

猜你喜欢

热点阅读