Java中的泛型类 --为集合的学习做准备

学习目标

● 掌握在集合中正确使用泛型
● 了解泛型类、泛型接口、泛型方法
● 了解泛型上下限
● 了解基本的使用场景

1.有关泛型

1.1泛型的概念

泛型(Generics)是Java中引入的参数化类型机制,允许在定义类、接口或方法时使用类型参数(如),实际类型在使用时才指定。其本质是通过类型参数化,增强代码的灵活性和安全性。

● 我们将要在集合中接触泛型。了解泛型,为学习集合做知识准备
● 泛型在集合中用于类型检查,那么为什么集合中 一定要用到泛型?
○ 若没有泛型的使用,那么在设计集合类型时,只能确定集合用来装对象,但是无法确定装什么类型的对象,即集合的元素类型未知。在使用时,编译器就无法进行更加具体的类型检查,存在类型安全问题;
○ 从集合中取出的集合元素对象,为了调用该元素对象的非Object类的方法,不得不向下转型,就存在ClassCastException的风险存在,代码繁琐的问题;
● 有了泛型的使用,既能保证安全,又能简化代码。
● 因为如果通过了编译,那么类型一定是符合要求的,因此就避免了类型转换;

● <类型>这种语法形式就叫泛型。
○ <类型>代表未知的数据类型,我们可以指定为String,Student等。相当于把某个具体的类型泛化为一般的类型,所以称为泛型,或者成为参数化数据类型。参数化类型必须是引用数据类型。
○ <类型> 一般使用一个泛型符号表示;泛型符号只是一个占位符 先占着位置,给引用类型占位置。
○ 泛型符号,名称不要求,个数不要求。 在使用的时候当成已知类型来进行使用。一般使用大写字母A-Z表示。
○ 泛型符号如果不指定 统统都是Object类型

1.2 泛型的分类

泛型符号,可以出现在类声明处 、接口声明处、方法定义中。
● 泛型在类声明处使用,一般称为泛型类
● 泛型在接口声明处使用,一般称为泛型接口
● 泛型在方法定义中使用,一般称为泛型方法

1.3泛型的作用

● 类型安全:编译时检查类型一致性,避免运行时因类型转换导致的ClassCastException
● 泛型核心作用: 实现数据类型的自动转换,避免出现强制转换(较少出现类型转换的异常)。
● 弊端: 泛型只在编译期间有效,运行期间泛型擦除,底层还是会将每一个参数化类型改为Object

2.泛型类

在类名后声明类型参数,用于成员变量或方法中
● 泛型符号 可以出现在 类声明处
● 泛型符号 在本类中当成已知类型来进行使用
● 静态方法中不能出现 类的泛型符号

2.1 创建泛型类

//泛型里面: <>里面必须编写参数化数据类型。引用数据类型  默认值null
public class MyClass<S, E> {//S,E就是2个参数化数据类型。具体什么类型我们是不清楚的,要看使用的时候,传过来的数据类型。//使用MyClass,没有传递S,E的真实的数据类型,S,E默认为Object。//S,E是一个数据类型,肯定可以作为形参,返回值,属性的数据类型。
}

2.2 使用泛型类

  • 创建泛型类对象,不指定参数化类型
private static void method1() {//调用MyClass里面的属性+方法。---> 面向对象编程//还会报警告。对于泛型类、接口 建议指定参数化数据类型//1.创建MyClass类对象MyClass myClass1 = new MyClass();//使用MyClass时候 没有指定S,E的真实数据类型。ObjectmyClass1.setName("张三");String name = myClass1.getName();myClass1.setS(100);int s = (int) myClass1.getS();//对s进行类型强制转换----> 会有几率出现ClassCastExceptionmyClass1.setS("hello");String str = (String) myClass1.getS();System.out.println(myClass1.demo1(new int[]{1, 2, 4}));
}
  • 创建泛型类对象,指定参数化类型
private static void method2() {//创建泛型类对象 指定参数化数据类型(引用数据类型)//MyClass<Integer, String> myClass1 = new MyClass<Integer, String>();//jdk1.7+之后的写法MyClass<Integer, String> myClass2 = new MyClass<>();//S:Integer//E:StringmyClass2.setName("李四");String name = myClass2.getName();myClass2.setS(100);Integer s = myClass2.getS();Integer hello = myClass2.demo1("hello");
}

3.泛型方法

● 任意一个类/接口中,都可以存在泛型方法。 就是在方法上使用<>修饰方法。<>里面的参数化类型就可以是任意数据类型。
● 泛型符号 只出现本方法的声明处
● 泛型符号的声明 在返回值的前面

3.1 创建泛型方法

  • 参数化类型仅仅作为形参/返回值使用,不是泛型方法
//S 参数化数据类型 作为返回值类型使用
public S getS() {return s;
}
//S作为形参数据类型使用。
public void setS(S s) {this.s = s;
}
//getS  setS不是泛型方法,仅仅是参数化数据类型作为形参/返回值。//普通的方法
//1.非静态的
public S demo1(E e) {System.out.println("demo1............e:" + e);return s;
}//以上方法仅仅是普通方法 只不过是参数化类型作为形参和返回值使用而已。
//具体形参和返回值是什么类型,要看创建对象时指定的数据类型,因为这些属性/方法,称为实例变量/方法,必须对象访问。
  • 存在类中的泛型方法
//泛型里面: <>里面必须编写参数化数据类型。引用数据类型  默认值null
public class MyClass<S, E> {//普通的方法//1.非静态的public S demo1(E e) {System.out.println("demo1............e:" + e);return s;}//2.静态的方法---->类名.静态//静态方法里面使用参数化数据类型  必须将这个方法标识为"泛型方法"public static <A,Z> void staticMethod(A s1) {System.out.println("s1:" + s1);}//在任意一个类中  静态方法里面使用了参数类型  这个方法一定是泛型方法//普通的功能方法: 也可以是一个泛型方法public <B> void demo2(B b){System.out.println("b:"+b);}
}
  • 存在接口中的泛型方法
public interface MyInterface<K, S> {default <T> void demo1(T t) {System.out.println("t:" + t);}static <E> int demo2(E e) {return 100;}
}

3.2 使用泛型方法

private static void method3() {//调用staticMethod方法/*MyClass<String,String> myClass = new MyClass<>();myClass.staticMethod(100.0);*/MyClass.staticMethod("hello");MyClass<String,String> myClass = new MyClass<>();myClass.demo2(100);
}
private static void method3() {System.out.println(MyInterface.demo2("hello"));
}

4.泛型接口

接口定义时声明类型参数,实现类需指定具体类型或保留泛型
● 泛型符号 可以出现在 接口声明处
● 在本接口中当成 已知类型来使用

4.1 创建泛型接口

public interface MyInterface<K, S> {//封装行为//在接口中  定义方法的时候  可以让参数化类型作为形参/返回值使用S method1();void method2(K k);//接口中可以有泛型方法  一般都是静态的//自己很少编写public static <A> void aa(A a) {}public default <B> void bb(B b) {}
}
public interface GenericInterface<A, B, C> {void methodA(A a);void methodB(B b);void methodC(C c);
}

4.2 使用泛型接口

//封装/设计: 一个类实现类泛型接口  这个类一般也是一个泛型类。
public class MyInterfaceImpl<K,S> implements MyInterface<K,S>{@Overridepublic S method1() {return null;}@Overridepublic void method2(K k) {}
}class Test{public static void main(String[] args) {MyInterface<Integer,String> myInterface = new MyInterfaceImpl<>();}
}
//一般在设计中  我们很少这样写
/*** 实现类取实现泛型接口 直接固定类型*/
public class InterfaceImpl1 implements GenericInterface<String, Integer, Double> {@Overridepublic void methodA(String s) {}@Overridepublic void methodB(Integer integer) {}@Overridepublic void methodC(Double aDouble) {}
}
/*** 实现类 也带上泛型符号* @param <A>* @param <B>* @param <C>*/
public class InterfaceImpl3<A, B, C> implements GenericInterface<A, B, C> {private A a;@Overridepublic void methodA(A a) {}@Overridepublic void methodB(B b) {}@Overridepublic void methodC(C c) {}
}

5.泛型上下限

通配符与上下界

  • 通配符<?>:表示未知类型,用于接受任意泛型类型:
public void printList(List<?> list) { /* 可处理List<String>、List<Integer>等 */ }
  • 上界<? extends T>:限定类型为T或其子类,用于读取操作:
List<? extends Number> numbers = new ArrayList<Double>(); // 允许Double
Number num = numbers.get(0); // 安全读取
  • 下界<? super T>:限定类型为T或其父类,用于写入操作:
List<? super Integer> list = new ArrayList<Number>();
list.add(123); // 允许写入Integer

5.1 问题

  • 创建父类Animal
public class Animal {public void eat() {}
}
  • 创建子类 Dog 重写 eat()方法
public class Dog extends Animal {@Overridepublic void eat() {System.out.println("狗喜欢吃骨头");}
}
  • 创建饲养员类
public class Feeder {public void feed(List<Animal> animals) {for (Animal animal : animals) {animal.eat();}}
}
  • 测试:饲养员喂食不同小动物
public static void main(String[] args) {Feeder feeder = new Feeder();List<Animal> animals = new ArrayList<>();animals.add(new Dog());animals.add(new Cat());List<Dog> dogList = new ArrayList<>();dogList.add(new Dog());feeder.feed(animals);//编译报错,dogList不能传入 feeder.feed(dogList);}

5.2 原因

● 泛型类型的指定上和多态并不相同,它要求两边类型必须一致 才能使用,因此如何解决上述问题,因为在实际业务开发中,子类集合也应该是可以传入的,此时就需要使用泛型上限进行解决;
● ? 通配符 代表任意类型;
○ 完整形式为:类名 或接口名,此时?代表上限类型本身或者上限的子类,即?代表

5.3 解决

/*** 饲养员类*/
public class Feeder {/*** @param animals 泛型的上限*                ? 通配符*/public void feed(List<? extends Animal> animals) {for (Animal animal : animals) {animal.eat();}}/** * 指定了下限 下限就到Animal* 上不要求 所有Animal父类型都可以传*/public void method1(List<? super Animal> animals) {for (Object animal : animals) {
//            animal.eat();}}
}

5.4 测试

public static void main(String[] args) {Feeder feeder = new Feeder();List<Animal> animals = new ArrayList<>();animals.add(new Dog());animals.add(new Cat());List<Dog> dogList = new ArrayList<>();dogList.add(new Dog());List<Object> objectList = new ArrayList<>();// 泛型上限feeder.feed(dogList);feeder.feed(animals);// 泛型下限feeder.method1(animals);feeder.method1(objectList);}

6.泛型擦除

public static void main(String[] args) {List<Integer> list = new ArrayList<>(10);list.add(100);list.add(200);//我就是想存一个“hello”到list集合中//编译的class文件中 不存在List<Integer> 只有List<Object>//泛型在运行期间会被擦除 还是Object类型try {Method addMethod = list.getClass().getMethod("add", Object.class);System.out.println(addMethod.invoke(list,"hello"));} catch (Exception e) {e.printStackTrace();}System.out.println(list);}

7.使用场景

泛型类以及泛型接口的出现一般都是满足项目的整体设计。

7.1 泛型类

● 满足与整体的项目功能设计。通用的操作。
● 需求: 模拟前端请求,触发一个按钮的功能。后端就要返回固定格式数据

成功的数据:statusmsgdata失败的数据:statusmsg满足所有的模块数据封装。    

1.普通类编写

  • 创建MyResult类
@Setter
@Getter    
public class MyResult {private int status;private String msg;private Object data;//查询成功的数据  数据类型不定的public MyResult(int status, String msg, Object data) {this.status = status;this.msg = msg;this.data = data;}public MyResult(int status, String msg) {this.status = status;this.msg = msg;}
}
  • 测试不同模块功能
private static MyResult testFindOneProduct() {ProductDao productDao = new ProductDaoImpl();Product product = productDao.findOne(1L);if (product == null) {return new MyResult(StatusEnum.ERROR.getStatus(), StatusEnum.ERROR.getMsg());}//状态码 msg  productreturn new MyResult(StatusEnum.SUCCESS.getStatus(), StatusEnum.SUCCESS.getMsg(), product);
}private static MyResult testFindAllUser() {//模拟测试查询所有用户SysUserDao sysUserDao = new SysUserDaoImpl();SysUser[] users = sysUserDao.findAll();if (users == null || users.length == 0) {//查询失败  状态码  msgreturn new MyResult(StatusEnum.ERROR.getStatus(), StatusEnum.ERROR.getMsg());}//查询成功//状态码  msg  查询所有成功的数据  usersreturn new MyResult(StatusEnum.SUCCESS.getStatus(), StatusEnum.SUCCESS.getMsg(), users);
}private static MyResult testFindOneUser() {//模拟测试查询单个用户SysUserDao sysUserDao = new SysUserDaoImpl();SysUser sysUser = sysUserDao.findOne(1);if (sysUser == null) {//查询失败  状态码  msgreturn new MyResult(StatusEnum.ERROR.getStatus(), StatusEnum.ERROR.getMsg());}//查询成功//状态码  msg  查询成功的数据  sysUserreturn new MyResult(StatusEnum.SUCCESS.getStatus(), StatusEnum.SUCCESS.getMsg(), sy
  • 测试
public static void main(String[] args) {//前端触发了对用户模块的增删改查//成功/失败MyResult result = test2();int status = result.getStatus();String msg = result.getMsg();Object data = result.getData();//查询单个用户对象System.out.println(status);System.out.println(msg);if(status==200){SysUser sysUser = (SysUser) data;System.out.println(sysUser.getId());System.out.println(sysUser.getName());}System.out.println(data);System.out.println("----------------------------------");MyResult myResult = test4();int status1 = myResult.getStatus();if(status1==200){Object data1 = myResult.getData();Product product = (Product) data1;}}

以上代码封装数据完全ok

  • 弊端: 每一次获得data 都要向下转型 会有几率出现类型转换的异常
    所以getData的时候 自动的转换成想要的数据类型想到使用 泛型

2.泛型类编写

  • 创建MyResult类
//T: 满足查询成功之后的数据的封装
//封装成功之后的任意类型的封装。
@Setter
@Getter  
public class ReturnResult<T> {private int status;private String msg;private T data;private ReturnResult(int status, String msg, T data) {this.status = status;this.msg = msg;this.data = data;}private ReturnResult(int status, String msg) {this.status = status;this.msg = msg;}public static <T> ReturnResult<T> success(T data){return new ReturnResult<>(StatusEnum.SUCCESS.getStatus(),StatusEnum.SUCCESS.getMsg(),data);}public static <T> ReturnResult<T> error(){return new ReturnResult<>(StatusEnum.ERROR.getStatus(),StatusEnum.ERROR.getMsg());}}
  • 测试模块功能
public static ReturnResult<Product> testFindOneProduct(){ProductDao productDao = new ProductDaoImpl();Product product = productDao.findOne(1L);if(product==null){return ReturnResult.error();}return ReturnResult.success(product);}//测试查询单个用户public static ReturnResult<SysUser> testFindOneUser() {SysUserDao sysUserDao = new SysUserDaoImpl();SysUser sysUser = sysUserDao.findOne(1);if (sysUser == null) {return ReturnResult.error();}return ReturnResult.success(sysUser);}

7.2 泛型接口

//开发XXXX管理系统:
//1.系统用户模块----> 增加/删除/修改/查询用户 与角色/权限相关的一些行为
//2.订单模块----> 
//3.商品模块----> 
//......
//提高程序的扩展性。多态----> 继承类与类   实现 类与接口//有3个实体类封装每个对象具备的信息
//有3个接口封装每个模块里面行为:
  • 对子接口的抽象: 封装模块里面共有的行为。

//E就是实体类型 T:id的数据类型
public interface BaseDao<E,T> {//封装的很多模块具备的一些行为void insert(E entity);void delete(T id);void update(E entity);E findOne(T id);E[] findAll();//还没讲解集合 先使用数组编写
}
  • 对共有的行为的实现进行 封装。
public abstract class BaseDaoImpl<E,T> implements BaseDao<E,T> {@Overridepublic void insert(E entity) {}@Overridepublic void delete(T id) {}@Overridepublic void update(E entity) {}@Overridepublic E findOne(T id) {return null;}@Overridepublic E[] findAll() {return null;}
}
  • 具体子模块的编写
public interface SysUserDao extends BaseDao<SysUser,Integer> {//编写用户模块特有的行为void userLogout();}
public class SysUserDaoImpl extends BaseDaoImpl<SysUser,Integer> implements SysUserDao {//编写用户模块特有行为的实现
}
public interface ProductDao extends BaseDao<Product,Long> {//维护每个模块独有的行为
}public class ProductDaoImpl extends BaseDaoImpl<Product,Long> implements ProductDao {}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/896635.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VUE3+Vite使用TailwindCSS【若依前后端分离框架】

参考&#xff1a;https://tailwind.nodejs.cn/docs/guides/vite#vue 和 https://blog.csdn.net/hjl_and_djj/article/details/144694485依次运行命令&#xff1a; cnpm install -D tailwindcss3.4.17 postcss autoprefixernpx tailwindcss init -p修改配置文件tailwind.config.…

FFmpeg入门:最简单的音频播放器

FFmpeg入门&#xff1a;最简单的音频播放器 欢迎大家来到FFmpeg入门的第二章&#xff0c;今天只做一个最简单的FFmpeg音频播放器&#xff1b;同样&#xff0c;话不多说&#xff0c;先上流程图 流程图 以上流程和视频播放器的解码过程基本上是一致的&#xff1b; 不同点在于 S…

在Ubuntu下,源码编译安装Python

在Ubuntu下&#xff0c;源码编译安装Python 知识点 知识点1&#xff1a;在 Linux 系统里&#xff0c;/usr 目录通常用于存放一些共享的、只读的程序和数据&#xff0c;是系统安装软件的一个重要位置。而 /usr/src 目录一般是用来存放系统源代码以及一些软件包的源代码的地方 …

《每天读一个JDK源码》之HashMap解读

&#x1f4cc;《每天读一个JDK源码》之HashMap解读 &#x1f517;源码定位&#xff1a;java.util.HashMap&#xff08;建议IDE对照阅读&#xff09; 今天我们来破解Java集合框架中最精妙的艺术品——HashMap&#xff01;它不仅是面试必考题&#xff08;出现率99%&#xff09;&…

【Java项目】基于SpringBoot的Java学习平台

【Java项目】基于SpringBoot的Java学习平台 技术简介&#xff1a;采用Java技术、SpringBoot框架、MySQL数据库等实现。系统基于B/S架构&#xff0c;前端通过浏览器与后端数据库进行信息交互&#xff0c;后端使用SpringBoot框架和MySQL数据库进行数据处理和存储&#xff0c;实现…

使用ChatGPT-Deep Reaserch两步给出文献综述!

文献综述是学术论文写作中不可或缺的一部分&#xff0c;它不仅是对已有研究的梳理和总结&#xff0c;更是为后续研究奠定理论基础的关键步骤。通过文献综述研究者能够全面了解当前研究领域的现状、主要观点和研究方法&#xff0c;从而找到自己研究的切入点和创新点。这一过程需…

java基础知识(理论篇)

一、java介绍 1.1Java语言 Java 是一种广泛使用的、通用的、面向对象的编程语言&#xff0c;Java 的设计目标是“一次编写&#xff0c;到处运行”&#xff0c;这也这意味着 Java 程序可以在任何支持 Java 的平台&#xff08;如 Windows、Linux、macOS 等&#xff09;上运行。 …

金融赋能绍兴纺织 民生银行助力外贸中小微企业“走出去”

在浙江绍兴&#xff0c;纺织业作为一张熠熠生辉的产业名片&#xff0c;承载着深厚的历史底蕴与蓬勃的发展活力。这里依傍长三角经济圈&#xff0c;交通网络纵横交错&#xff0c;将原材料产地与广阔市场紧密相连&#xff1b;产业集群高度成熟&#xff0c;上下游产业链完备&#…

综合实验处理表格

新建excel表格&#xff0c;输入信息&#xff0c;另存为csv文件。 利用notepad打开csv文件&#xff0c;可以观察格式 目标&#xff1a;通过编程处理文件&#xff0c;实现对数据的处理&#xff0c;成绩求和以及评价 对数据逐行处理&#xff0c;读一行&#xff0c;处理一行&#…

我和我的通义灵码

我和我的通义灵码 我和我的通义灵码个人版&企业版个人版登录个人版workspace应用场景terminal 企业版登录企业版#team docs 体验总结 我和我的通义灵码 说到通义灵码&#xff0c;作为程序员的我们是最有发言权的。从全国首个AI代码助手-通义灵码公测到现在&#xff0c;不知…

一文学会Volatile关键字

引言 在 Java 多线程实战中&#xff0c;volatile 是一个重要的关键字&#xff0c;用于修饰变量&#xff0c;经常在JUC源码中出现&#xff0c;本文详细解析一下这个关键字的奥秘 1. 基本概念 volatile 关键字的主要作用是保证变量的可见性以及在一定程度上禁止指令重排序。在…

Java测试框架Mockito快速入门

Mockito结合TestNG快速入门 什么是Mockito Mockito 是一个专门用于 Java 的强大测试框架&#xff0c;主要用来创建和管理模拟对象&#xff0c;辅助开发者进行单元测试&#xff0c;具有以下特点和功能&#xff1a; 创建模拟对象&#xff1a;能通过简洁的语法创建类或接口的模…

week 3 - More on Collections - Lecture 3

一、Motivation 1. Java支持哪种类型的一维数据结构&#xff1f; Java中用于在单一维度中存储数据的数据结构&#xff0c;如arrays or ArrayLists. 2. 如何在Java下创建一维数据结构&#xff1f;&#xff08;1-dimensional data structure&#xff09; 定义和初始化这些一…

Ubuntu 防火墙iptables和 ufw

文章目录 iptables 和 ufw 的区别Ubuntu 上使用 ufw 配置 iptables 和 ufw 的区别 iptables 和 ufw 是 Linux 系统中用于管理防火墙的工具&#xff0c;但它们的设计目标和使用方式有所不同。 iptables&#xff1a;功能强大&#xff0c;适合高级用户和复杂场景&#xff0c;但配…

(动态规划 最长连续递增子序列)leetcode 674

我上个文章提到了最长递增子序列这个题可以去看看 这个题目翻译人话就是找出最长的递增子串&#xff0c;用一层for循环就行&#xff0c;时间复杂度是O(n) 比起上个题&#xff0c;一个范围多条子序列&#xff08;路径&#xff09;这里一个范围只有一条递增路径&#xff0c;所以…

STM32CubeMx DRV8833驱动

一、DRV8833驱动原理 ​ STBY口接单片机的IO口&#xff0c;STBY置0电机全部停止&#xff0c;置1才能工作。STBY置1后通过AIN1、AIN2、BIN1、BIN2 来控制正反转。 AIN1AIN2电机状态00停止1speed反转speed1正转11停止 其中A端&#xff08;AIN1与AIN2&#xff09;只能控制AO1与…

JSON Schema 入门指南:如何定义和验证 JSON 数据结构

文章目录 一、引言二、什么是 JSON Schema&#xff1f;三、JSON Schema 的基本结构3.1 基本关键字3.2 对象属性3.3 数组元素3.4 字符串约束3.5 数值约束 四、示例&#xff1a;定义一个简单的 JSON Schema五、使用 JSON Schema 进行验证六、实战效果6.1 如何使用 七、总结 一、引…

前端Npm面试题及参考答案

目录 npm 是什么?它的主要作用是什么? npm 包管理工具与 Yarn 有何不同? npm 的 package.json 文件有哪些重要字段? 什么是 npm 依赖?如何在项目中安装、更新和移除依赖? npm 的 node_modules 目录是什么?它的作用是什么? 什么是 npm 脚本?如何在 package.json 中…

零样本思维链(Zero-shot CoT)

Large Language Models are Zero-Shot Reasoners (Kojima et al., 2022) 这篇文章研究了大型语言模型 (LLMs) 在推理任务上的能力&#xff0c;并提出了一种名为 Zero-shot-CoT 的新方法&#xff0c;该方法能够有效地引导 LLM 进行多步骤推理&#xff0c;并在各种推理任务上取得…

day01_Java基础

文章目录 day01_Java基础一、今日课程内容二、Java语言概述&#xff08;了解&#xff09;1、Java语言概述2、为什么要学习Java语言3、Java平台版本说明4、Java特点 三、Java环境搭建&#xff08;操作&#xff09;1、JDK和JRE的概述2、JDK的下载和安装3、IDEA的安装4、IDEA的启动…