【鸿蒙开发】第六章 ArkTS基础知识 - 类、接口及泛型

2023-12-17 10:28

本文主要是介绍【鸿蒙开发】第六章 ArkTS基础知识 - 类、接口及泛型,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1 前言

上一章节我们学习了【鸿蒙开发】第五章 ArkTS基础知识 - 声明变量、常量、类型和函数,大概了解ArkTS的基础类型和用法和ts的大致基本相同,与Java也极其相似。本章节我们继续学习ArkTS类和接口、泛型等相关知识,为后续鸿蒙应用开发夯实基础。
在这里插入图片描述

2 类

类声明引入一个新类型,并定义其字段方法构造函数

有两种方法创建实例:

  1. 通过new创建实例
  2. 通过对象字面量创建实例
// 常规的类创建与使用
class Person {name: string = ''surname: string = ''constructor(n: string, sn: string) {this.name = nthis.surname = sn}fullName(): string {return this.name + ' ' + this.surname}
}// 1.通过new创建实例
let person = new Person('John', 'Smith')
console.log(person.fullName())// 2.可以使用对象字面量创建实例,声明时要给属性赋值,给方法赋值等
let person2: Person = { name: 'John', surname: 'Smith', fullName(): string {return this.name + ' ' + this.surname} }}
console.log(person2 .fullName())

2.1 字段

字段是直接在类中声明的某种类型的变量。
类可以具有实例字段或者静态字段

2.1.1 实例字段

实例字段存在于类的每个实例上。每个实例都有自己的实例字段集合。
实例字段就是上面我们类实例中的属性,如:

let person = new Person('John', 'Smith')
// person.name为实例字段
console.log(person.name)

2.1.2 静态字段

使用关键字static将字段声明为静态。静态字段属于类本身,类的所有实例共享一个静态字段。

要访问静态字段,需要使用类名:

class Person {static sex:number = 0
}
// 通过类调用
console.log(Person.sex)

2.1.3 字段初始化

为了减少运行时的错误和获得更好的执行性能, ArkTS要求所有字段在声明时或者构造函数中显式初始化。这和标准TS中的strictPropertyInitialization模式一样。

class Person {// 可能为`undefined`name ?: stringsetName(n:string): void {this.name = n}// 编译时错误:name可以是"undefined",所以将这个API的返回值类型标记为stringgetNameWrong(): string {return this.name}// 返回类型匹配name的类型getName(): string | undefined {return this.name}
}function main(){let jack = new Person()// 假设代码中没有对name赋值,例如调用"jack.setName('Jack')"// 编译时错误:编译器认为下一行代码有可能会访问undefined的属性,报错let length1 = jack.getName().length// 编译成功,没有运行时错误,?.表示可以为undefined let length2 = jack.getName()?.length
}

2.1.4 getter和setter

settergetter可用于提供对对象属性的受控访问

在以下示例中,setter用于禁止将age属性设置为无效值:

class Person {name: string = ''private _age: number = 0get age(): number { return this._age }set age(x: number) {if (x < 0) {throw Error('Invalid age argument')}this._age = x}
}let p = new Person()
console.log (p.age) // 将打印输出0
p.age = -42 // 设置无效age值会抛出错误

2.2 方法

方法属于类。类可以定义实例方法或者静态方法静态方法属于类本身,只能访问静态字段。而实例方法既可以访问静态字段,也可以访问实例字段,包括类的私有字段

2.2.1 实例方法

上面Person类中fullName()方法就是实例方法,必须通过类的实例调用实例方法

let person = new Person('John', 'Smith')
// 通过类的实例调用实例方法
person.fullName()

2.2.2 静态方法

使用关键字static将方法声明为静态。静态方法属于类本身,只能访问静态字段。

静态方法定义了类作为一个整体的公共行为。所有实例都可以访问静态方法。必须通过类名调用静态方法

class Person {static staticMethod(): string {return 'this is a static method.'}
}
console.log(Person.staticMethod())

2.3 继承

一个类可以继承另一个类(称为基类),可以实现多个接口。单继承类,多实现接口
继承类可以继承基类的字段和方法,但不继承构造函数。继承类可以新增定义字段和方法,也可以覆盖其基类定义的方法

基类也称为“父类”或“超类”。继承类也称为“派生类”或“子类”。

包含implements子句的类必须实现列出的接口中定义的所有方法,但使用默认实现定义的方法除外。

interface DateInterface {now(): string;
}class Person {name: string = ''surname: string = ''constructor(n: string, sn: string) {this.name = nthis.surname = sn}fullName(): string {return this.name + ' ' + this.surname}
}class Employee extends Person implements DateInterface {// 实现接口的now方法now(): string {throw new Error('Method not implemented.');}
}

2.3.1 父类调用

父类属性父类方法都通过super调用

class Employee extends Person implements DateInterface {id: number = 0constructor(n: string, sn: string) {// 构造函数也通过super直接调用父类构造函数,// 如果构造函数函数体不以父类构造函数的显式调用开始,// 则构造函数函数体隐式地以父类构造函数调用super()开始。super(n, sn)}sessionId(): string {// 通过super访问父类属性return super.name + this.id}// 方法重写fullName(): string {// 通过super访问父类方法return super.fullName();}// 实现接口的now方法now(): string {throw new Error('Method not implemented.');}}

2.3.2 方法重写

  1. 重写的方法必须具有与原始方法相同的参数类型和相同或派生的返回类型。
class Employee extends Person implements DateInterface {// 方法重写fullName(): string {// 通过super访问父类方法return super.fullName();}// 实现接口的now方法now(): string {throw new Error('Method not implemented.');}}

2.3.3 方法重载

通过重载签名,指定方法的不同调用。具体方法为,为同一个方法写入多个同名但签名不同的方法头,方法实现紧随其后。构造方法重载也一样

class C {foo(): void;            /* 第一个签名 */foo(x: string): void;   /* 第二个签名 */foo(x?: string): void { /* 实现签名 */console.log(x)}
}
let c = new C()
c.foo()     // OK,使用第一个签名
c.foo('aa') // OK,使用第二个签名

如果两个重载签名的名称和参数列表均相同,则为错误。

3 接口

接口声明引入新类型。接口是定义代码协定的常见方式。
任何一个类的实例只要实现了特定接口,就可以通过该接口实现多态

接口通常包含属性和方法的声明

interface Style {color: string
}// 继承接口包含被继承接口的所有属性和方法
// 还可以添加自己的属性和方法。
interface Area extends Style {width: numberheight: number// 方法的声明someMethod(): void;
}class Rectangle implements Area {// 接口的属性必须实现// 1.接口属性通过直接声明实现width: number;height: number;// 2.接口属性通过getter、setter方法实现,等价于上面方法1private _color: string = ''get color(): string {return this._color}set color(x: string) {this._color = x}// 接口方法实现someMethod(): void {console.log('someMethod called')}}

4 泛型

泛型类型和函数允许创建的代码在各种类型上运行,而不仅支持单一类型。

4.1 泛型类和接口

类和接口可以定义为泛型,将参数添加到类型定义中,如以下示例中的类型参数Element

class Stack<Element> {public pop(): Element {// ...}public push(e: Element):void {// ...}
}

要使用类型Stack,必须为每个类型参数指定类型实参:

let s = new Stack<string>
s.push('hello')
typescript
let s = new Stack<string>
s.push('hello')

编译器在使用泛型类型和函数时会确保类型安全。参见以下示例:

let s = new Stack<string>
s.push(55) // 将会产生编译时错误
typescript
let s = new Stack<string>
s.push(55) // 将会产生编译时错误

4.2 泛型约束

泛型类型的类型参数可以绑定。例如,HashMap<Key, Value>容器中的Key类型参数必须具有哈希方法,即它应该是可哈希的。

interface Hashable {hash(): number
}
class HasMap<Key extends Hashable, Value> {public set(k: Key, v: Value) {let h = k.hash()// ...其他代码...}
}

在上面的例子中,Key类型扩展了HashableHashable接口的所有方法都可以为key调用。

4.3 泛型函数

使用泛型函数可编写更通用的代码。比如返回数组最后一个元素的函数:

function last(x: number[]): number {return x[x.length - 1]
}
console.log(last([1, 2, 3])) // 输出:3

如果需要为任何数组定义相同的函数,使用类型参数将该函数定义为泛型:

function last<T>(x: T[]): T {return x[x.length - 1]
}

现在,该函数可以与任何数组一起使用。

在函数调用中,类型实参可以显式或隐式设置:

// 显式设置的类型实参
console.log(last<string>(['aa', 'bb']))
console.log(last<number>([1, 2, 3]))// 隐式设置的类型实参
// 编译器根据调用参数的类型来确定类型实参
console.log(last([1, 2, 3]))
typescript
// 显式设置的类型实参
console.log(last<string>(['aa', 'bb']))
console.log(last<number>([1, 2, 3]))// 隐式设置的类型实参
// 编译器根据调用参数的类型来确定类型实参
console.log(last([1, 2, 3]))

4.4 泛型默认值

泛型类型的类型参数可以设置默认值。这样可以不指定实际的类型实参,而只使用泛型类型名称。下面的示例展示了类和函数的这一点。

class SomeType {}
interface Interface <T1 = SomeType> { }
class Base <T2 = SomeType> { }
class Derived1 extends Base implements Interface { }
// Derived1在语义上等价于Derived2
class Derived2 extends Base<SomeType> implements Interface<SomeType> { }function foo<T = number>(): T {// ...
}
foo()
// 此函数在语义上等价于下面的调用
foo<number>()
typescript
class SomeType {}
interface Interface <T1 = SomeType> { }
class Base <T2 = SomeType> { }
class Derived1 extends Base implements Interface { }
// Derived1在语义上等价于Derived2
class Derived2 extends Base<SomeType> implements Interface<SomeType> { }function foo<T = number>(): T {// ...
}
foo()
// 此函数在语义上等价于下面的调用
foo<number>()

5 包

5.1 模块

程序可划分为多组编译单元模块

每个模块都有其自己的作用域,即,在模块中创建的任何声明(变量、函数、类等)在该模块之外都不可见,除非它们被显式导出。

与此相对,从另一个模块导出的变量、函数、类、接口等必须首先导入到模块中。

5.2 导出

可以使用关键字export导出顶层的声明。
未导出的声明名称被视为私有名称,只能在声明该名称的模块中使用。

注意:通过export方式导出,在导入时要加{}。

export class Point {x: number = 0y: number = 0constructor(x: number, y: number) {this.x = xthis.y = y}
}
export let Origin = new Point(0, 0)
export function Distance(p1: Point, p2: Point): number {return Math.sqrt((p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y))
}

5.3 导入

导入声明用于导入从其他模块导出的实体,并在当前模块中提供其绑定。导入声明由两部分组成:

  1. 导入路径,用于指定导入的模块;
  2. 导入绑定,用于定义导入的模块中的可用实体集和使用形式(限定或不限定使用)。

导入绑定可以有几种形式。
假设模块具有路径“./utils”和导出实体“X”和“Y”。
导入绑定* as A表示绑定名称“A”,通过A.name可访问从导入路径指定的模块导出的所有实体:

import * as Utils from './utils'
Utils.X // 表示来自Utils的X
Utils.Y // 表示来自Utils的Y

导入绑定{ ident1, ..., identN }表示将导出的实体与指定名称绑定,该名称可以用作简单名称:

import { X, Y } from './utils'
X // 表示来自utils的X
Y // 表示来自utils的Y

如果标识符列表定义了ident as alias,则实体ident将绑定在名称alias下:

import { X as Z, Y } from './utils'
Z // 表示来自Utils的X
Y // 表示来自Utils的Y
X // 编译时错误:'X'不可见

这篇关于【鸿蒙开发】第六章 ArkTS基础知识 - 类、接口及泛型的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/504042

相关文章

Android 悬浮窗开发示例((动态权限请求 | 前台服务和通知 | 悬浮窗创建 )

《Android悬浮窗开发示例((动态权限请求|前台服务和通知|悬浮窗创建)》本文介绍了Android悬浮窗的实现效果,包括动态权限请求、前台服务和通知的使用,悬浮窗权限需要动态申请并引导... 目录一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后

Deepseek R1模型本地化部署+API接口调用详细教程(释放AI生产力)

《DeepseekR1模型本地化部署+API接口调用详细教程(释放AI生产力)》本文介绍了本地部署DeepSeekR1模型和通过API调用将其集成到VSCode中的过程,作者详细步骤展示了如何下载和... 目录前言一、deepseek R1模型与chatGPT o1系列模型对比二、本地部署步骤1.安装oll

MyBatis-Flex BaseMapper的接口基本用法小结

《MyBatis-FlexBaseMapper的接口基本用法小结》本文主要介绍了MyBatis-FlexBaseMapper的接口基本用法小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具... 目录MyBATis-Flex简单介绍特性基础方法INSERT① insert② insertSelec

Spring排序机制之接口与注解的使用方法

《Spring排序机制之接口与注解的使用方法》本文介绍了Spring中多种排序机制,包括Ordered接口、PriorityOrdered接口、@Order注解和@Priority注解,提供了详细示例... 目录一、Spring 排序的需求场景二、Spring 中的排序机制1、Ordered 接口2、Pri

Idea实现接口的方法上无法添加@Override注解的解决方案

《Idea实现接口的方法上无法添加@Override注解的解决方案》文章介绍了在IDEA中实现接口方法时无法添加@Override注解的问题及其解决方法,主要步骤包括更改项目结构中的Languagel... 目录Idea实现接China编程口的方法上无法添加@javascriptOverride注解错误原因解决方

基于Python开发PPTX压缩工具

《基于Python开发PPTX压缩工具》在日常办公中,PPT文件往往因为图片过大而导致文件体积过大,不便于传输和存储,所以本文将使用Python开发一个PPTX压缩工具,需要的可以了解下... 目录引言全部代码环境准备代码结构代码实现运行结果引言在日常办公中,PPT文件往往因为图片过大而导致文件体积过大,

Java function函数式接口的使用方法与实例

《Javafunction函数式接口的使用方法与实例》:本文主要介绍Javafunction函数式接口的使用方法与实例,函数式接口如一支未完成的诗篇,用Lambda表达式作韵脚,将代码的机械美感... 目录引言-当代码遇见诗性一、函数式接口的生物学解构1.1 函数式接口的基因密码1.2 六大核心接口的形态学

使用DeepSeek API 结合VSCode提升开发效率

《使用DeepSeekAPI结合VSCode提升开发效率》:本文主要介绍DeepSeekAPI与VisualStudioCode(VSCode)结合使用,以提升软件开发效率,具有一定的参考价值... 目录引言准备工作安装必要的 VSCode 扩展配置 DeepSeek API1. 创建 API 请求文件2.

详解Java如何向http/https接口发出请求

《详解Java如何向http/https接口发出请求》这篇文章主要为大家详细介绍了Java如何实现向http/https接口发出请求,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 用Java发送web请求所用到的包都在java.net下,在具体使用时可以用如下代码,你可以把它封装成一

基于Python开发电脑定时关机工具

《基于Python开发电脑定时关机工具》这篇文章主要为大家详细介绍了如何基于Python开发一个电脑定时关机工具,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 简介2. 运行效果3. 相关源码1. 简介这个程序就像一个“忠实的管家”,帮你按时关掉电脑,而且全程不需要你多做