子类在构造时必须给父类属性赋值
- 子类继承了父类的属性
- 在内存里,子类对象包含父类那部分字段。
- 所以在创建子类对象时,必须把父类的那部分也初始化好。
- Dart 的
final和空安全 (null-safety)- 如果父类里有
final或者 非空类型 的字段(比如final Key key;或者Key key;),那么 Dart 要求它们必须在构造时被初始化。 - 否则对象会处于“不完整状态”,编译器直接报错。
- 如果父类里有
super(...)的作用super(...)就是子类在创建时,调用父类构造函数来初始化父类字段。- 这样父类的属性才能有值,保证对象完整、安全。
class Parent {
final String name;
Parent(this.name); // 必须传 name,否则报错
}
class Child extends Parent {
final int age;
// ❌ 错误:没有调用 super,name 没有赋值
// Child(this.age);
// ✅ 正确:调用父类构造函数,初始化 name
Child(String name, this.age) : super(name);
}
void main() {
var c = Child("Alice", 20);
print("${c.name}, ${c.age}"); //Alice, 20
}
子类在实例化的时候只是创建了一个对象
var c = Child("Alice", 20);:内存里创建的只是 一个 Child 实例。这个实例里面包含了 Parent 部分,但那不是单独的对象,而是 同一个对象中的一部分内存区域。
classDiagram
class Parent {
+String name
+Parent(name)
}
class Child {
+int age
+Child(name, age)
}
Child --|> Parent : extends
%% 内存结构
class ChildInstance["Child Instance"] {
+Parent 部分: name = "Alice"
+Child 部分: age = 20
}
ChildInstance ..> Child : is instance of
ChildInstance ..> Parent : contains Parent fields
实际情况
MaterialDemoApp({super.key})
在flutter里面,我们经常可以看到这样的写法:
void main() {
runApp(const MaterialDemoApp());
}
class MaterialDemoApp extends StatefulWidget {
const MaterialDemoApp({super.key});
@override
State<MaterialDemoApp> createState() => _MaterialDemoAppState();
}
这是因为MaterialDemoApp继承了StatefulWidget,而StatefulWidget继承Widget,Widget里面有一个key属性
@immutable
abstract class Widget extends DiagnosticableTree {
/// Initializes [key] for subclasses.
const Widget({this.key});
final Key? key; // 注意这里是可空类型
}
但是,实际上我们并没有给key传值,因为key是可以为空的。
没传 key,实际上就是给父类构造函数里的 key 传了 null。
由于 Key? 允许为空,所以不会报错。
super.key的这种写法,其实是一种语法糖的写法。
传统写法(2.17 之前)
class MaterialDemoApp extends StatefulWidget {
const MaterialDemoApp({Key? key}) : super(key: key);
}
子类的构造函数接收
Key? key参数。用
: super(key: key)把它传递给父类的构造函数。- 第一个 key 是父类的参数名称
- 第二个key是子类的传参
语法糖写法(2.17+):与上面等价
class MaterialDemoApp extends StatefulWidget {
const MaterialDemoApp({super.key});
}
它的作用就是:自动把子类构造函数的参数传递给父类构造函数。
class MyWidget extends StatelessWidget {
final String title;
// super.key 把 key 参数直接传给 StatelessWidget 的构造函数
const MyWidget({super.key, required this.title});
@override
Widget build(BuildContext context) {
return Text(title);
}
}
这里 super.key 就相当于帮你写了:
const MyWidget({Key? key, required this.title}) : super(key: key);
对FormField进行封装
class InputFormFieldWidget extends FormField<String> {
InputFormFieldWidget({
super.key,
required this.labelText,
this.tipText,
this.initValue,
this.onChanged,
this.controller,
this.placeholder,
this.prefix,
this.suffix,
this.obscureText,
this.cleanable,
this.keyboardType,
this.autofocus,
Function(String?)? validator,
}) : super(
initialValue: initValue ?? controller?.text,
validator: (val) {
if (validator != null) {
return validator(val);
}
return null;
},
builder: (field) {
void onChangedHandler(String value) {
field.didChange(value);
onChanged?.call(value);
}
return <Widget>[
// 字段说明
TextWidget.label(
labelText,
).paddingLeft(AppSpace.card),
// 输入框
InputWidget(
placeholder: placeholder,
prefix: prefix,
suffix: suffix,
controller: controller,
obscureText: obscureText ?? false,
cleanable: cleanable ?? true,
onChanged: onChangedHandler,
),
// 提示词
if (tipText != null)
TextWidget.muted(
tipText,
).paddingLeft(AppSpace.card),
// 错误提示
if (field.errorText != null)
TextWidget.muted(
field.errorText!,
color: field.context.colors.scheme.error,
).paddingLeft(AppSpace.card),
].toColumnSpace(
crossAxisAlignment: CrossAxisAlignment.start,
);
},
);
/// 字段文字
final String labelText;
/// 提示词
final String? tipText;
/// 初始值
final String? initValue;
/// 输入框控制器
final TextEditingController? controller;
/// 占位符
final String? placeholder;
/// 前缀
final Widget? prefix;
/// 后缀
final Widget? suffix;
/// 是否隐藏文本
final bool? obscureText;
/// 是否可清空
final bool? cleanable;
/// 值被改变时的回调
final void Function(String?)? onChanged;
/// 输入法类型
final TextInputType? keyboardType;
/// 自动焦点
final bool? autofocus;
@override
InputFormWidgetFieldState createState() => InputFormWidgetFieldState();
}
class InputFormWidgetFieldState extends FormFieldState<String> {
@override
InputFormFieldWidget get widget => super.widget as InputFormFieldWidget;
}
这里使用 super(...) 的目的,就是在 子类构造函数里把参数传递给父类 FormField 的构造函数,从而对 FormField<String> 进行封装,做一个自定义表单组件。
InputFormFieldWidget 继承自 FormField<String>,而 FormField 的构造函数大致是这样的:
class FormField<T> extends StatefulWidget {
/// Creates a single form field.
const FormField({
super.key,
required this.builder,
this.onSaved,
this.forceErrorText,
this.validator,
this.errorBuilder,
this.initialValue,
this.enabled = true,
AutovalidateMode? autovalidateMode,
this.restorationId,
}) : autovalidateMode = autovalidateMode ?? AutovalidateMode.disabled;
构造函数签名 :......:初始化列表
在 Dart 里,: 出现在构造函数签名后,叫 初始化列表 (initializer list)。
其中的 super(...) 就是 调用父类构造函数,并且在子类构造函数执行之前运行。
class Parent {
Parent(String msg) {
print("Parent 构造: $msg");
}
}
class Child extends Parent {
Child(String msg) : super(msg) {
print("Child 构造");
}
}
void main() {
Child("Hello");
}
/*输出:
Parent 构造: Hello
Child 构造
*/
可以看到:父类的构造函数 先执行,子类的构造函数体 后执行。
关于更多初始化列表的内容可以查看:面向对象那个章节