1.类的定义
在Dart中,定义类用class关键字。
类通常有两部分组成:成员(member)和方法(method)。
定义类的代码格式:
class 类名 {
类型 成员名;
返回值类型 方法名(参数列表) {
方法体
}
}
编写一个简单的Person类:
class Person {
String name;
eat() {
print('$name在吃东西');
}
}
我们来使用这个类,创建对应的对象:
- 注意:从Dart2开始,new关键字可以省略。
main(List<String> args) {
// 1.创建类的对象
var p = new Person(); // 直接使用Person()也可以创建
// 2.给对象的属性赋值
p.name = '韩梅梅';
// 3.调用对象的方法
p.eat();
}
2.类的构造构造器
2.1 默认构造器与命名构造器
// 定义一个类型:Dog
class Dog {
//为这个Dog类声明3个属性
String name;
String color;
int age;
/*
如果我们没有为这个类定义任何构造器的话,
系统会为这个类默认添加一个不带参数的构造器
*/
// Dog() {}
/*
如果我们想为这个Dog类添加一个带参的构造器,
则系统的默认无参构造器会无效;
注意,此处我把参数写错了命名可选参数;
这个参数类型可根据自己的需求设定;
*/
// Dog({String name, String color, int age}) {
// this.name = name;
// this.color = color;
// this.age = age;
// }
//带参的写法还可以升级为下面这样:
Dog({this.name, this.color, this.age}) {}
//命名构造函数;
Dog.unlaunched(String name, {String color, int age})
: this.color = color == null ? '白色' : color,
this.age = age == null ? 1 : age {
this.name = name;
}
//只读属性;
int get years => this.age;
}
2.2.重定向构造方法
在某些情况下, 我们希望在一个构造方法中去调用另外一个构造方法, 这个时候可以使用重定向构造方法
:
main(List<String> args) {
Person p = Person.fromName('ppp');
//通过使用重定向构造行数的方式,p.age次数就不会为null;
print(p.age);
Person2 p2 = Person2('rrr');
print(p2.age);
}
class Person {
String name;
int age;
Person(this.name, this.age);
Person.fromName(String name) : this(name, 1);
}
class Person2 {
String name;
int age;
Person2(String name) : this.fromName(name, 1);
Person2.fromName(String name, int age) {
this.name = name;
this.age = age;
}
}
2.3常量构造方法
在某些情况下,传入相同值时
,我们希望返回同一个对象
,这个时候可以使用常量构造方法
.
默认情况下,创建对象时,
即使传入相同的参数,创建出来的也不是同一个对象,看下面代码:
- 这里我们使用identical(对象1, 对象2)函数来判断两个对象是否是同一个对象:
main(List<String> args) {
var p1 = Person('why');
var p2 = Person('why');
print(identical(p1, p2)); // false
}
class Person {
String name;
Person(this.name);
}
但是, 如果将构造方法前加const
进行修饰,那么可以保证同一个参数,创建出来的对象是相同的,这样的构造方法就称之为常量构造方法
.
main(List<String> args) {
var p1 = const Person('why');
var p2 = const Person('why');
print(identical(p1, p2)); // true
//如果是将结果赋值给const修饰的标识符时,const可以省略.
const p3 = Person('why');
const p4 = Person('why');
print(identical(p3, p4)); // true
}
class Person {
final String name;
const Person(this.name);
}
常量构造方法有一些注意点:
注意一:拥有常量构造方法的类中,所有的成员变量必须是final修饰的.
注意二: 为了可以通过常量构造方法,创建出相同的对象,不再使用 new关键字,而是使用const关键字
如果是将结果赋值给const修饰的标识符时,const可以省略.
2.4 工厂构造方法
Dart提供了factory关键字, 用于通过工厂去获取对象:
main(List<String> args) {
final d1 = Dog.allocWithBreed('土狗');
final d2 = Dog.allocWithBreed('土狗');
d2.name = '旺财';
d1.name = '来福';
print(identical(d1, d2)); //打印结果: true
print('${d1.name}, ${d2.name}'); //打印结果: 来福, 来福
}
class Dog {
String name; // 狗名
String breed; // 狗品种
//这个地方使用了static
//_nameCache 和 _breedCache 都是 类属性;
// _nameCache 和 _breedCache 是Map<String,Dog>的字典类型;
static final Map<String, Dog> _nameCache = {};
static final Map<String, Dog> _breedCache = {};
Dog({this.name, this.breed});
factory Dog.allocWithNmae(String name) {
//判断是否使用过此 name 初始化过 Dog实例
if (_nameCache.containsKey(name)) {
Dog dog = _nameCache[name];
return dog;
} else {
Dog dog = Dog(name: name);
_nameCache[name] = dog;
return dog;
}
}
factory Dog.allocWithBreed(String breed) {
//判断是否使用过此 breed 初始化过 Dog实例
if (_breedCache.containsKey(breed)) {
Dog dog = _breedCache[breed];
print('使用过$breed\初始化Dog');
return dog;
} else {
Dog dog = Dog(breed: breed);
_breedCache[breed] = dog;
print('没使用过$breed\初始化Dog');
return dog;
}
}
}
3.类的继承 / 接口 / 混入(mixin) / 类属性 / 类方法
3.1 基本属性的继承
继承 -- 使用的关键字是: extends
类继承的写法:
// main函数;
main(List<String> args) {
Person p1 = Person();
p1.name = '韩梅梅';
print(p1.name);
//因为Person 继承于 Animal 所以也有 name属性;
}
class Animal {
String name;
}
//Person 继承了 Animal 类
class Person extends Animal {}
3.2 父类构造器的重定向使用
如果父类定义了构造,子类必须调用一下父类的构造器,实现父类的初始化;
main(List<String> args) {
Person p1 = Person('韩梅梅');
print(p1.name);
}
class Animal {
String name;
//父类定义了一个构造器
Animal(this.name);
}
class Person extends Animal {
//子类必须调用父类的构造器,实现父类的初始化;
Person(String name) : super(name);
}
使用父类构造器的方式也可以是下面这种,通过命名构造器来调用
main(List<String> args) {
// Person p1 = Person('韩梅梅');
// print(p1.name);
Person p2 = Person.allocWith('李雷');
print(p2.name);
}
class Animal {
String name;
//父类定义了一个构造器
Animal(this.name);
}
class Person extends Animal {
//子类必须调用父类的构造器,实现父类的初始化;
// Person(String name) : super(name);
Person.allocWith(String name) : super(name);
}
3.3 类是隐式接口 和 类的混入(mixin)
3.4 类属性 和 类方法
类属性和类方法和Swift的语法有点像,在此不做过多阐述;
main(List<String> args) {
Student.courses = 9;
String result = Student.playFootball();
print(result);
}
class Student {
// 成员变量(成员属性)
String name;
//这种属性就是静态属性,也叫做类属性
static int courses; //科目数量; 所有的学生学习的科目数量是一样的
static playFootball() {
return 'Student is playing football!';
}
}
4.抽象类:(abstract)
Demo:
main(List<String> args) {
Square s = Square();
s.getArea();
print(s.getInfo());
}
//定义一个抽象类;
abstract class Shape {
//抽象类可以定义一个方法,而不实现
void getArea();
String getInfo() {
return '你获得了一个正方形';
}
}
class Square extends Shape {
@override
void getArea() {
// TODO: implement getArea
print('我重载了抽象类Shape中的 getArea 方法');
}
}
总结:
- 抽象类就类似于协议,可以声明一个方法名,不用实现,
- 遵循协议的类去把协议中的方法实现;
- 抽象类也可以选择把方法的具体实现也写了;
- 抽象类是不可以实例化的
- 可以用swift协议的思想来理解抽象类;
4.1 奇怪的Map
当我们进入Map
的源码查看是,我们会发现:
abstract class Map<K, V> {
}
这说明 Map
也是一个抽象类,但是Map
确实可以实例化的,为什么呢?
因为Map
的内部有很多factory
函数,可以为其创建实例;
external factory Map();
我们看到这里使用了external
;
这个修饰符的作用是:把方法的声明和方法的实现拆分到两个不同的地方;
方法的实现用@path
来修饰:
Dart
语言的这个设计的好处是,使得一个方法的实现,可以根据不同的平台做出不同的实现;
下面我们去到Dart
的源码中看看external factory Map();
方法的实现,源码路径:flutter/bin/cache/dart-sdk/lib/_internal/vm/lib/map_patch.dart
;
使用VSCode 打开map_patch.dart
,看里面的源码:
@patch
class Map<K, V> {
// Factory constructing a Map from a parser generated Map literal.
// [elements] contains n key-value pairs.
// The keys are at position 2*n and are already type checked by the parser
// in checked mode.
// The values are at position 2*n+1 and are not yet type checked.
@pragma("vm:entry-point", "call")
factory Map._fromLiteral(List elements) {
var map = new LinkedHashMap<K, V>();
var len = elements.length;
for (int i = 1; i < len; i += 2) {
map[elements[i - 1]] = elements[i];
}
return map;
}
@patch
factory Map.unmodifiable(Map other) {
return new UnmodifiableMapView<K, V>(new Map<K, V>.from(other));
}
//这里就是 Map(); 的实现;
@patch
factory Map() => new LinkedHashMap<K, V>();
}
LinkedHashMap<K,V>();
的实现又在另外一个文件:
flutter/bin/cache/dart-sdk/lib/collection/linked_hash_map.dart
abstract class LinkedHashMap<K, V> implements Map<K, V> {
/// Creates an insertion-ordered hash-table based [Map].
///
/// If [equals] is provided, it is used to compare the keys in the table with
/// new keys. If [equals] is omitted, the key's own [Object.==] is used
/// instead.
///
/// Similar, if [hashCode] is provided, it is used to produce a hash value
/// for keys in order to place them in the hash table. If it is omitted, the
/// key's own [Object.hashCode] is used.
///
/// If using methods like [operator []], [remove] and [containsKey] together
/// with a custom equality and hashcode, an extra `isValidKey` function
/// can be supplied. This function is called before calling [equals] or
/// [hashCode] with an argument that may not be a [K] instance, and if the
/// call returns false, the key is assumed to not be in the set.
/// The [isValidKey] function defaults to just testing if the object is a
/// [K] instance.
///
/// Example:
///
/// new LinkedHashMap<int,int>(equals: (int a, int b) => (b - a) % 5 == 0,
/// hashCode: (int e) => e % 5)
///
/// This example map does not need an `isValidKey` function to be passed.
/// The default function accepts only `int` values, which can safely be
/// passed to both the `equals` and `hashCode` functions.
///
/// If neither `equals`, `hashCode`, nor `isValidKey` is provided,
/// the default `isValidKey` instead accepts all keys.
/// The default equality and hashcode operations are assumed to work on all
/// objects.
///
/// Likewise, if `equals` is [identical], `hashCode` is [identityHashCode]
/// and `isValidKey` is omitted, the resulting map is identity based,
/// and the `isValidKey` defaults to accepting all keys.
/// Such a map can be created directly using [LinkedHashMap.identity].
///
/// The used `equals` and `hashCode` method should always be consistent,
/// so that if `equals(a, b)` then `hashCode(a) == hashCode(b)`. The hash
/// of an object, or what it compares equal to, should not change while the
/// object is in the table. If it does change, the result is unpredictable.
///
/// If you supply one of [equals] and [hashCode],
/// you should generally also to supply the other.
external factory LinkedHashMap(
{bool Function(K, K)? equals,
int Function(K)? hashCode,
bool Function(dynamic)? isValidKey});
/// Creates an insertion-ordered identity-based map.
///
/// Effectively a shorthand for:
///
/// new LinkedHashMap<K, V>(equals: identical,
/// hashCode: identityHashCode)
external factory LinkedHashMap.identity();
/// Creates a [LinkedHashMap] that contains all key value pairs of [other].
///
/// The keys must all be instances of [K] and the values to [V].
/// The [other] map itself can have any type.
factory LinkedHashMap.from(Map<dynamic, dynamic> other) {
LinkedHashMap<K, V> result = LinkedHashMap<K, V>();
other.forEach((dynamic k, dynamic v) {
result[k as K] = v as V;
});
return result;
}
我们在来看看下面的代码:
Map map = Map();
print(map.runtimeType);
//打印结果:_InternalLinkedHashMap<dynamic, dynamic>
网友评论