Javaer初识Dart:我们不一样 (一)

153 阅读7分钟

前言

文中对比的版本:Java是JDK8,Dart是3.3.1。

1724201301609.png 版本更新以及会引起语言的变化,导致对比项的结果会引起变化。

继承体系的异同

Dart是一种面向对象的语言,作为Flutter的基石,与之前作为Android基石的Java有很多异同(Android现在是Kotlin First)。Dart和Java都是具有单继承,但是Dart新增了mixin的继承,这样就变相地增加了多继承的实现。Dart中每个对象都是一个类的实例,除Null和Object之外的所有类都继承自Object。基于Mixin的继承意味着尽管每个类(顶级类 Object 除外)都只有一个超类,但类主体可以在多个类层次结构中重复使用。

变量

Dart用var声明的属性值,赋值之后Dart的类型系统能够轻松推断出这个属性值的类型,方便调用此对象的相应方法和属性。Dart没有基本数据类型,一切皆对象,只有像Java一样的封装数据类型:int(依赖于操作系统,数据位数不会长于64位)和double(64位双精度浮点数),没有像Java中的float(单精度浮点数,32位)。Dart有静态检查的机制,同样动态改变属性值类型的dynamic关键字,Java不具有相关的功能。示例代码如下所示:

void main() {
  dynamic c = 1;
  print(c.runtimeType);
  c = 'Bob';
  print(c.runtimeType);
}
//输入结果
int
String

字符串以及对象之间的比较

Dart字符串除了可以用+拼接外,还可以用单引号或者双引号里面包裹属性值或者属性值或者{对象方法的调用}来进行组合,多行显示可以用三引号进行显示。后两项是Java所不具有的。

==运算符测试两个对象是否相等。如果两个字符串包含相同的代码单元序列,则它们是相等的。如果对象之间的比较是否相等,需要重写==运算符。如果对象需要放到集合中,最好重写hashcode方法。Java==对比的内存地址引用是否相同,也就是说是否是同一个对象;Java对比内容是否相同,需要重写类的equals方法,比较内容是否相等调用的是equals方法。示例代码如下所示:

void main() {
  var p1 = Person('Bob');
  var p2 = Person('Bob');
  print(p1 == p2);
  print(p1);
  print(p1.hashCode == 'Bob'.hashCode);
}

class Person {
  String name;
  Person(this.name);
  @override
  String toString() {
    return "Person{name:$name}";
  }
  @override
  int get hashCode =>name.hashCode;
  @override
  bool operator ==(Object other) => other is Person && name == other.name;
}
//输出结果
true
Person{name:Bob}
true

Record

Record(记录)是一种匿名、不可变的聚合类型。与其他集合类型一样,它们允许您将多个不同类型的对象捆绑为一个对象,与其他集合类型不同。Record(记录)是实际值;您可以将它们存储在变量中、嵌套它们、将它们传递给函数和从函数传递它们,以及将它们存储在列表、映射和集合等数据结构中。使用带有位置字段的记录模式进行解构,可以返回多个值。示例代码如下:

// Returns multiple values in a record:
(String name, int age) userInfo(Map<String, dynamic> json) {
  return (json['name'] as String, json['age'] as int);
}

final json = <String, dynamic>{
  'name': 'Dash',
  'age': 10,
  'color': 'blue',
};

void main() {
  // Destructures using a record pattern with positional fields:
var (name, age) = userInfo(json);

/* Equivalent to:
  var info = userInfo(json);
  var name = info.$1;
  var age  = info.$2;
*/
  print("${name.runtimeType}:$name");
  print("${age.runtimeType}:$age");
  (num, Object, {String b}) pair = (42, 'a', b: "caicai");

var first = pair.$1; // Static type `num`, runtime type `int`.
var second = pair.$2; // Static type `Object`, runtime type `String`.
print("${pair.b.runtimeType}:${pair.b}");
}
//输出结果
String:Dash
int:10
String:caicai

注意:Record(记录)包括两种字段,一种是位置字段(位置字段的索引是从1开始的,使用$1代表第一个位置字段),另一种是命名字段。Record(记录)可以组合多种属性,这是Java中Pair类所不具有的功能。Record用于解构对象,也是一个非常棒的功能。

集合

Dart默认有三种集合:List(线性有序,可重复添加对象),Set(不可重复添加),Map(不存在重复的key和value键值对,当key相同时候,会替换value)。Dart中没有Java中array数组这种数据类型(数组是连续的内存存储空间,数组的大小固定不变,如果重新分配空间,需要把原来的数据复制到新分配空间,随机访问任意位置的数据时间复杂度是O(1)),而使用List来当array使用。Dart中还有扩展操作符的概念: 您可以使用扩展运算符 (...) 将一个列表的所有值插入到另一个列表中。如果扩展运算符右侧的表达式可能为空,则可以使用可识别空值的扩展运算符 (...?) 来避免异常。示例代码如下所示:

void main() {
  var list = <String>[];
  var names = <String>{};
  Set<String> sets = {}; // This works, too.
  var maps = {}; // Creates a map, not a set.
  print(list.runtimeType);
  print(names.runtimeType);
  print(sets.runtimeType);
  print(maps.runtimeType);
  var list1 = [1, 2, 3];
  var list2 = [0, ...list1];
  print(list2.length);
  var list3 = null;
  var list4 = [0, ...?list3];
  print(list4.length);
}
//输出结果
List<String>
_IdentityHashSet<String>
_IdentityHashSet<String>
LinkedMap<dynamic, dynamic>
4
1

类型别名

类型别名(通常称为typedef,因为它使用关键字typedef声明)是一种引用类型的简洁方式。示例代码如下所示:

typedef Compare<T> = int Function(T a, T b);

int sort(int a, int b) => a - b;
typedef ListMapper<X> = Map<X, List<X>>;

void main() {
  print(sort is Compare<int>);
  ListMapper<String> listMapper = {"caicai": ["technology", 'blog']};
  print(listMapper['caicai']);
}
//输出结果
true
[technology, blog]

typedef可以理解为重命名成一个更简洁更短的名称。Java是没有这个功能的。

类的声明

Dart中类中有属性和方法构成,方法包括对象的构造方法和实例方法。类的构造方法包括默认构造方法,如果没有声明构造方案,Dart会使用默认构造方法(默认构造方法是没有参数的生成式的构造方法)。如果声明了构造方法,Dart只能声明一个生成式的构造方法。Dart可以声明多个命名式的构造方法。Java因为可以有多个生成式的构造方法(参数个数或者参数顺序不同),所以不需要有命名式的构造方法。

const double xOrigin = 0;
const double yOrigin = 0;

class Point {
  final double x;
  final double y;

  // Sets the x and y instance variables
  // before the constructor body runs.
  Point(this.x, this.y);

  // Named constructor
  Point.origin()
      : x = xOrigin,
        y = yOrigin;
  @override
  String toString() => 'Point{x:$x, y:$y}';
}

void main() {
  var p1 = Point(1, 2);
  var p2 = Point.origin();
  print(p1);
  print(p2);
}
//输出结果
Point{x:1, y:2}
Point{x:0, y:0}

注意:Dart对象的实例化前面的new关键字是可选的,可以不需要写。Java实例化需要用new关键字,Kotlin不需要new关键字。

factory方法

Dart有工厂构造器的概念,使用factory,可以构造类的子类。代码如下:


void main() {
 print(Shape.fromTypeName('square'));
 print(Shape.fromTypeName('circle'));
}


class Square extends Shape {}

class Circle extends Shape {}

class Shape {
  Shape();

  factory Shape.fromTypeName(String typeName) {
    if (typeName == 'square') return Square();
    if (typeName == 'circle') return Circle();

    throw ArgumentError('Unrecognized $typeName');
  }
}

Java中没有factory的概念,但是可以使用工厂方法或者抽象工厂等设计模式来创建不同的子类。

实例方法VS类方法

Dart中的实例方法和类方法是不一样的。示例代码如下所示:

import 'dart:math';

class Point {
  double x, y;
  Point(this.x, this.y);

  static double distanceBetween(Point a, Point b) {
    var dx = a.x - b.x;
    var dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
  }
  
  double distance(Point b) {
    var dx = this.x - b.x;
    var dy = this.y - b.y;
    return sqrt(dx * dx + dy * dy);
  }
}

void main() {
  var p1 = Point(0, 1);
  var p2 = Point(0, 2);
  print('p1 to p2 distance:${p1.distance(p2)}');
  print('p1 to p2 distance:${Point.distanceBetween(p1, p2)}');

}
//输出结果
p1 to p2 distance:1
p1 to p2 distance:1

对象实例不能调用类方法(静态防范)。如下图所示:

1724210127125.png Java对象是可以调用类方法(静态方法),这是因为Java对象持有类的引用,这样就能直接调用类的方法了。

mixin

Mixins是一种定义可在多个类层次结构中重用的代码的方式。它们旨在提供大量成员和方法的实现,复用属性和方法。mixin是 Dart中的一个重要概念,它是抽象类和接口的“表亲”。mixin 只是一个没有构造函数的类,可以“附加”到其他类以重用代码而无需继承,像是把属性和方法复制粘贴到相应的类里面一样。使用mixin和with关键字来表示,mixin子类可以直接访问mixin类里面声明的属性和方法。

mixin Swimming {
 int during = 30;
 void swim();
 bool likesWater() => true;
}
mixin Walking {
 void walk() => print("Walking");
}

class Human with Walking {
  final String _name;
  final String _surname;
  Human(this._name, this._surname);
  void printName() => print("$_name $_surname");
}

class Dog with Swimming {
  void swim() => print("Swimming");
}


void main() {
 final me = Human("Alberto", "Miola");
 // prints "Alberto Miola"; method is defined in the class
 me.printName();
 // prints "Walking"; method is not defined in the class
 // but it's "copied" and "pasted" from the mixin.
 me.walk();
 var dog = Dog();
 dog.swim();
 print(dog is Swimming);
 print(dog.during);
}
//输出结果
Alberto Miola
Walking
Swimming
true
30

注意:mixin声明类,复用mixin类用with关键字。mixin声明的类里面可以包括属性和方法,如果里面有抽象的方法(只有方法声明,没有方法体),复用mixin类的子类里面必须重写抽象方法。is来判断是否是某个类,从dog is Swimming结果来看,反映出实现了多重继承。'extends'、'with' 和 'implements' 的顺序在Dart中至关重要,必须按照extends → with → implements 的顺序进行。 mixin总结如下:mixin在Dart中非常适合用于代码复用、功能扩展、避免多重继承,以及提供统一接口的默认实现。通过使用 mixin,你可以将通用的功能模块化,并将其混入到需要这些功能的类中,保持代码的简洁和可维护性。

智能转化

Dart是一种空安全的语言,Java还没有此功能,Kotlin已经具备此功能。在Dart中,智能转换是指语言能够根据控制流和类型检查自动推断和转换变量的类型。Dart在可以保证变量类型符合预期的情况下执行智能转换,从而减少了显式类型转换的需要。借助Dart的空安全,智能强制类型转换可处理不可空类型,从而使类型检查更加可靠。如果您检查变量是否为空,并且保证在特定范围内不为空,Dart 将推断其不可空性。代码如下图所示:

void processValue(Object value) {
  if (value is String) {
    // `value` is automatically cast to `String` here
    print(value.length); // Safe to use `value` as `String`
  } else {
    print('Not a string');
  }
}
void checkLength(String? value) {
  if (value != null) {
    // `value` is automatically cast to `String` here
    print(value.length); // Safe to use `value` as `String`
  }
}

总结

Dart作为Flutter的基石,它的高性能,高产出等特性是保证Flutter成功的重要保证。Dart设计优良,语法也比较灵活,可以让研发人员快速上手。精通Dart语言的底层原理,这样才能保证我们开发出来的Flutter应用优良。希望文章对您有帮助,如果文中有问题,希望您不吝指教。