javaSE知识点整理总结(上)

目录

一、面向对象

1. 类、对象、方法

2.面向对象三大特征

(1)封装

(2)继承

(3)多态

二、常用类

1.Object类

2.Array类

3.基本数据类型包装类

4.String类

5.StringBuffer类

6.Math类

7.Random类

8.Date类

三、集合

1.Collection接口

(1)List接口

(2)Set接口

2.Map接口


一、面向对象

1. 类、对象、方法

        java作为一种面向对象思想语言,关注的焦点是类。

(1)类:具有相同特征的事物的抽象描述

        而一类事物,其中必然有许多的属性,这些属性即为成员变量(因为变量直接定义在类中,所以称为类的成员)这里我们需要注意,在类中定义的成员变量可以不赋默认值,系统会自动初始化默认值。

(2)对象:类是一个抽象概念,因此我们不能拿来直接使用,这时我们就需要一个类的实例来实现。这种以类为模版,在内存中创造一个实际存在的例子就叫做对象。

模版:(以一个汽车Car类为例)

        Car bwm = new Car();

Car();类的构造方法,类中会自动提供;
new Car(); 使用new关键字创造一个具体的对象,存在内存中;
Car bwm: 创建一个Car类型引用变量。Car类的引用,就是以后可以用来指向Car对象的对象引用。

public class TestCar {    //创建Car类的实例化对象bwmpublic static void main(String[] args) {Car bwm = new Car();}
}
class Car{
}

(3)方法

  • 构造方法

特点:

方法名与类名相同,没返回值,不要void修饰;

每个类都有一个默认的无参的构造方法,一旦自定义了参数,原来无参的就不存在了;

除非自定义的就是默认值。

public class Car {String name;    //成员变量float price;String color;public Car(){//构造方法--无参}public Car(String a,float c,String b){//构造方法--有参name = a;price = c;color = b;
}
  • 成员方法(表示一种功能、行为)
public class Car {public void run(){ //分别为访问权限修饰符、返回类型、方法名System.out.println("汽车行驶"); //方法体}
}

(4)方法重载

        一个类中有多个名称相同的方法

如何在调用时区分同名方法: 通过方法的参数的个数,类型,顺序区分;

构造方法和成员方法都可以实现方法重载;

方法重载与返回值没关系。

(5)抽象类 

一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类;
用abstract修饰,类中有抽象方法必是抽象类,抽象类不一定有抽象方法。

        特点:

抽象类不能被创建对象,其他同普通类,可以有构造方法;
子类想继承抽象类,要么重写其所以抽象方法,要么该子类也定义为抽象类。

注:

        抽象类中的抽象方法,没有方法体用abstract修饰
        只定义方法,没具体实现

public abstract class Animal {private String name;private int age;public Animal(){}public abstract void eat();//抽象方法public void sleep(){System.out.println("睡觉");}
}

2.面向对象三大特征

(1)封装

封装:将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是

           通过该类提供的方法来实现对隐藏信息的操作和访问

        封装的特点:

通过特定的方法访问

隐藏类的信息

方便加入控制语句

方便修改实现

package day3.fengzhuang;public class Fz {//封装案例1:成员变量设为私有权限,在其他类中不能直接访问/*private String name;private int age;*///解决方法:向外界提供一个公共方法来访问,可在方法中加控制语句,属性控制权掌握在类自己这里public void setName(String name){if (name.length()>2&&name.length()<6) {this.name = name;}}public String getName(){return this.name;}public void setAge(int age){this.age = age;}public int getAge(){return this.age;}//封装案例2:构造方法私有化,外界不能随意调用public Fz(){}
//在类加载时,只创建了这一个对象,对外提供(类似于电脑的任务管理器窗口,不想让外界创建多个对象,不论点几次,只打开一个窗口)static Fz f3 = new Fz();
//于是就将方法私有化private Fz(){}
//向外界提供获得此对象的方法public static Fz getF3(){return f3;}}
(2)继承

        日常编码中,当多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么,多个类中无需再定义这些属性和行为,只需要和抽取出来的类构成继承关系。(通过extends关键字实现)

        优点:

继承的出现减少了代码冗余,提高了代码的复用性。

继承的出现,更有利于功能的扩展。

继承的出现让类与类之间产生了is-a的关系,为多态的使用提供了前提。

 class Dog extends Animal{
//子类想继承抽象类,要么重写其所以抽象方法,要么该子类也定义为抽象类@Overridepublic void eat(){System.out.println("达到");}//这里是方法的重写,父类的功能不能满足子类的需求,进行方法重写。/*已实现方法的参数、返回值要和抽象类中的方法一样*/@Overridepublic int asd() {return 0;}
}abstract class Cat extends Animal{//抽象类
}public abstract class Animal {//猫类和狗类都属于动物类,他们都能吃饭,因此继承该动物类private String name;private int age;public Animal(){//构造方法}public abstract void eat();public abstract int asd();public void sleep(){System.out.println("睡觉");}
}
(3)多态
  • 所谓多态,即父类引用指向子类对象,从而产生多种形态

Animal dog = new Dog();

Animal cat = new Cat();

同一种事物,在不同时刻表现不同状态(两者之间必须存在继承关系)

  • 两个时间段

1.编译期:写代码时 类型是Animal(父类类型)
2.运行期:运行代码时 类型是子类类型
多态也称向上转型 将子类型转为父类型(只能调用父类中的方法)
用父类类型表示任意的子类类型对象,便于程序扩展

public class Test {public static void main(String[] args) {//原本创建对象  Dog dog = new DOG();Animal dog = new Dog();Animal cat = new Cat();dog.eat();   //多态中成员方法的调用--编译看左边(父类),运行看右边(调用子类重写的方法)cat.eat();dog.show();  //静态成员方法的调用--编译运行都看左边(调的是Animal类的方法)System.out.println(dog.num);  //变量不存在被子类覆写的说法,只有方法可以--编译运行都看创建对象时等号左边(Animal类)
/*向下转型父类引用仅能访问父类所声明的属性和方法,不能访问子类独有的属性和方法。为了访问子类独有方法,用类似强制类型转化的方法父类类型转为子类类型
*/Dog d = (Dog) dog;d.sleep();}
}
  • 向下转型

父类引用仅能访问父类所声明的属性和方法,不能访问子类独有的属性和方法。
为了访问子类独有方法,用类似强制类型转化的方法,使父类类型转为子类类型

public class Test {public static void main(String[] args) {Animal dog = new Dog();dog.sleep();Dog d = (Dog) dog;d.sleep();}
}class Dog extends Animal{int num = 50;public void sleep(){System.out.println("睡觉");}static void show(){System.out.println("狗");}
}class Animal {private String name;private int age;public void eat(){System.out.println("动物吃");}//静态成员方法,不存在方法重写,因此调用时用的是父类方法static void show(){System.out.println("动物");}
}

二、常用类

1.Object类

● Object类是所有Java类的祖先(根基类)。每个类都使用 Object 作为超类(父类)。所有对象(包括数组)都继承实现这个类的方法。

● 如果在类的声明中未使用extends关键字指明其基类,则默认基类为Object类

2.Array类

(1)equals方法

比较两个数组对象中元素是否相等。返回true或false

(2)copyOf方法

把原数组元素放到一个新数组中

(3)fill方法

用指定的值将指定数组每个元素填充

(4)toString方法

将数组元素内容拼接成字符串输出

(5)sort方法

对指定数组排序

(6)binarySearch方法

二分查找(前提是有序数组,或先进行数组排序),返回下标,返回负数表示未找到,或数组未排序

(7)compareTo 指定排序规则

public class Array3 implements Comparable<Array3>{//一个类要排序,实现Comparable接口,指定一个排序方法(使所以的类使用同一种排序方法)例如本类,既可以用id来排序也可应用nameprivate int id;private String name;public Array3(){}public Array3(int id,String name){this.id = id;this.name = name;}@Overridepublic String toString() {return "Array3{" +"id=" + id +", name='" + name + '\'' +'}';}//compareTo  指定排序规则//用类中的某个属性作为排序规则//此方法在sort()方法底层调用,判断大小@Overridepublic int compareTo(Array3 o) {return o.id-this.id;//这里自定义降序排序。整形比大小 前减后 大于0 等于0 小于0 默认升序}
}class TestArray3 {public static void main(String[] args) {Array3 a1 = new Array3(1,"张三1");Array3 a2 = new Array3(2,"张三2");Array3 a3 = new Array3(3,"张三3");Array3 a4 = new Array3(4,"张三4");Array3 a5 = new Array3(5,"张三5");Array3[] array3s = new Array3[5];array3s[0] = a1;    //这里是乱序存放array3s[1] = a5;array3s[2] = a2;array3s[3] = a4;array3s[4] = a3;Arrays.sort(array3s);//整形比大小排序格式System.out.println(Arrays.toString(array3s));}
}

其结果为:

3.基本数据类型包装类

        java语言是一个面向对象的语言,但是Java中的基本数据类型却是不面 向对象的,这在实际使用时存在很多的不便,为了解决这个不足,在设 计类时为每个基本数据类型设计了一个对应的类进表示,这样八个和基本数据类型对应的类统称为包装类.

      (1)  包装类主要用途:

● 作为和基本数据类型对应的类类型存在。

● 包含每种基本数据类型的相关属性如最大值、最小值等,以及相关的操作方法。

public class IntegerDemo { //这里演示几个常见的方法public static void main(String[] args) {System.out.println(Integer.MAX_VALUE);System.out.println(Integer.MIN_VALUE);System.out.println(Integer.SIZE);System.out.println(Integer.BYTES);Integer integer = new Integer(10);Integer integer1 = new Integer(20);System.out.println(integer + integer1);//静态方法(操作的只是传入的值,完成后返回)System.out.println(Integer.toBinaryString(10));//二进制System.out.println(Integer.toHexString(17));//16进制System.out.println(Integer.toOctalString(9));//8进制System.out.println(Integer.max(10,5));//比大小System.out.println(Integer.compare(10,5));//比大小 大于返回1,等于返回0,小于返回-1//非静态(操作的是对象包装的值)System.out.println(integer.equals(integer1));//比相等System.out.println(integer.compareTo(integer1));//比大小 大于返回1,等于返回0,小于返}
}

        (2)装箱和拆箱

● 装箱

自动将基本数据类型转换为包装器类型

装箱的时候自动调用的是Integer的valueOf(int)方法

● 拆箱

自动将包装器类型转换为基本数据类型

拆箱的时候自动调用的是Integer的intValue方法

public class IntegerDemo {public static void main(String[] args) {//int c = integer.intValue(); 自动拆箱,默认调用了 intValue()方法int c = integer;int d = 128;//Integer x = Integer.valueOf(d); 自动装箱,默认调用valueOf()方法Integer x = d;}
}

4.String类

常见方法:

        ●获取功能
        char charAt(int index)
        int indexOf(String str)
        int indexOf(String str,int fromIndex)
        String substring(int start)
        ● 转换功能
        static String valueOf(char[] chs)
        String toLowerCase()
        String toUpperCase()
        String concat(String str)
        Stirng[] split(分割符);
        ● 去除字符串两端空格
        String trim()

5.StringBuffer类

        我们如果对字符串进行拼接操作,每次拼接,都会构建一个新的String对象,既耗时,又浪费空间。而StringBuffer就可以解决这个问题线程安全的可变字符序列。

常见方法:

● 添加功能
        public StringBuffer append(String str)
        public StringBuffer insert(int offset,String str)
● 删除功能
        public StringBuffer deleteCharAt(int index)
        public StringBuffer delete(int start,int end)
● 替换功能
        public StringBuffer replace(int start,int end,String str)
● 反转功能
        public StringBuffer reverse()
● 截取功能
        public String substring(int start)
        public String substring(int start,int end)

注:● StringBuilder类功能和StringBuffer功能完全一致, StringBuffer是线程安全的。

6.Math类

Math.abs 绝对值
Math.sqrt 平方根
Math.pow(double a, double b) a的 b 次幂
Math.max(double a, double b)
Math.min(double a, double b)
Math.random() 返回 0.0 到 1.0 的随机数
Math.long round(double a) double型的数据 a 转换为 long 型(四舍五入)
7.Random类

        产生随机数

8.Date类

● 日期转字符串
        Date now=new Date();
        myFmt.format(now);
● 字符串转日期
        myFmt.parse(“2018-02-10”);
        字符串日期格式与 指定格式必须一致
        例如:String s = “2018-03-15”;
                new SimpleDateFormat(“yyyy-MM-dd”)

三、集合

        为什么需要集合?因为数组的长度一旦规定就是固定不变的,而实际开发中,我们经常需要进行添加或删除数据操作,这样我们更倾向于一种能动态增长的容器来存放数据,这就需要用到集合了。

1.Collection接口

(1)List接口
● List 中的数据对象有顺序 ( 添加顺序 ) 且可以重复。
  • ArrayList(数组列表,数据以数组存放,连续空间,遍历和访问元素效率高)

常用方法:

        add(E element)
        add(int index, E element)
        get(int index)
        indexOf(Object o)
        lastIndexOf(Object o)
        remove(int index) 删除并返回指定位置元素
  • LinkList(链表存储,插入、删除元素效率高) 

常用方法:

        add(int index,Object element)
        addFirist(Object element)
        get(int index)
        removeFirst()
        remove(int index)
  • List接口三种遍历
import java.util.ArrayList;
import java.util.Iterator;public class ArrayListDemo4 {public static void main(String[] args) {/*List 接口实现类/ List集合遍历方式,三种遍历*/ArrayList<String> arrayList = new ArrayList<>();arrayList.add("a");arrayList.add("a");arrayList.add("a");arrayList.add("b");arrayList.add("c");arrayList.add("d");//1. for 循环,允许修改元素,但要注意元素位置移动和索引改变// 一旦遇到两个相同元素相邻,便会出现只能删除其中一个的情况//这时还需要退位计数器
/*        for (int i = 0; i < arrayList.size(); i++) {if ("a".equals(arrayList.get(i))){arrayList.remove("a");i--;}}System.out.println(arrayList);*///2. 增强for循环,循环遍历时不允许修改元素集合(添加,删除操作)/*for (String s:arrayList){if (s.equals("a")){arrayList.remove(s);}arrayList.add("a");arrayList.remove("c");System.out.println(s);}*///3. 迭代器遍历--获得集合对象的迭代对象Iterator<String> iterator = arrayList.iterator();while (iterator.hasNext()){String s = iterator.next();//获取下一个元素if (s.equals("a")){iterator.remove();//删除元素}}System.out.println(arrayList);}
}

 

(2)Set接口
● Set 中的数据对象不可以重复。
  • HashSet(HashSet类中的元素不能重复,元素是无序的)

①HashSet 添加元素时,如何判断元素是否重复:(已由开发者在函数内部实现,这里只是讲述原理) 如果只用equals比较内容,效率较低;

②在底层会调用hashCode()方法--Object中的hashCode()返回的是对象的地址(很显然我们不想比较这个);

③因此会调用类中重写的hashCode(),他返回的是根据内容计算的哈希值 遍历时,会先用哈希值进行比较,提高效率,但是哈希值会出现,内容不同,哈希值相同的情况;

④因此,在此基础上还要调用equals()比较内容,提高效率和准确度

  • TreeSet(可以给Set集合中的元素进行指定方式的排序。存储的对象必须实现Comparable接口)

2.Map接口

Map接口共性:

        ①数据存储是 键,值(key,value) 的方式

        ②键不能重复,值可以一致

        ③可以通过键找到值

        ④一个键只能映射一个值

  • HashMap

HashMap底层存储数据结构:

        哈希表(数组)存储---方便查找

        同一位置存储元素8个以内---链表存储

        存储元素=8且哈希数组长度>64---链表转为红黑树存储(太长链表不好查询)

        若哈希数组长度不满足要求,不会转为红黑树

        数组使用率超过0.75后也会进行扩容

  • TreeMap

        ①底层使用树形结构存储

        ②键可以排序

        ③键元素类型必须实现Compare接口,重写compareTo()方法

  • Map遍历方式
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;public class HashMapDemo3 {public static void main(String[] args) {HashMap<String,String> hashMap = new HashMap<>();hashMap.put("a","aa");//添加一组键 值对hashMap.put("q","qq");hashMap.put("c","aa");hashMap.put("b","bb");hashMap.put("a","aa");hashMap.put(null,null);//拿到所有的值Collection<String> values = hashMap.values();System.out.println(values);//map遍历方式//1. 先拿到所有的键 遍历键 由键得到值Set<String> keySet = hashMap.keySet();for (String s: keySet){System.out.println(s+":"+hashMap.get(s));}//2. 推荐使用Set<Map.Entry<String, String>> entries = hashMap.entrySet();for (Map.Entry entry:entries){System.out.println(entry.getKey()+":"+entry.getValue());}}
}

 

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

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

相关文章

CesiumJS【Basic】- #022A JavaScript 模块化加载:传统 <script> 标签与 type=“module“ 的比较

文章目录 JavaScript 模块化加载:传统 \<script> 标签与 type="module" 的比较1 目标2 对比2.1 带type="module"2.2 不带type="module"3 代码3.1 带type="module"3.1.1 hello.js3.1.2 index.html3.2 不带type="module&quo…

摄影楼电子相册打开的正确方式,快来看看

​随着科技的不断发展&#xff0c;电子相册已经成为许多人存储和分享照片的重要方式。然而&#xff0c;你知道如何正确打开电子相册吗&#xff1f;今天&#xff0c;我就来教大家一下电子相册的正确打开方式&#xff0c;快来学习一下吧&#xff01; 第一步&#xff1a;选择合适的…

docker 安装syslog

Syslog-ng是一个可靠、多功能的日志管理系统&#xff0c;用于收集日志并将其转发到指定的日志分析工具。 使用Docker CLI方式搭建 步骤 1: 拉取Syslog-ng镜像 首先&#xff0c;需要从Docker Hub拉取Syslog-ng的官方镜像。 docker pull balabit/syslog-ng:latest 步骤 2: 启动…

QT拖放事件之七:子类化QMimeData,实现对多个自定义类型进行数据

1、前提说明 /*自定义的MIME类型数据存储在QMimeData对象中, 存在两种方法:1. setData(...)可以把自定义类型的数据以QByteArray的形式直接存储在QMimeData中,但是使用此方法一次只能对一个MIME类型进行处理(可参考 QT拖放事件六:自定义MIME类型的存储及读取demo ) 一文。…

成立近30年,它如何找到政企采购突破点?

回看中国采购行业的发展&#xff0c;大致可以被分为四个阶段&#xff1a;上世纪90年代的传统采购时代、本世纪初的ERP采购时代、近10年的SRM采购时代以及2018年以来开启的数字化采购时代。近年来&#xff0c;大数据、人工智能和物联网的高速发展&#xff0c;为采购信息化提供底…

从探头到传感器:德思特数字化仪的全面结合与应用

电压探头可以用于转换信号电平、改变阻抗或提供更方便的连接方法。而包括电流探头、加速度计和光电倍增管在内的传感器或变送器&#xff0c;则可以将各种物理量转换为电信号。这两种输入设备都受到德思特数字化仪的支持。这篇应用笔记将介绍如何将德思特板卡式数字化仪和探头、…

psensor 的手势功能

psensor 的手势功能的移植过程 有时间再来写下

Spring6全面详解-自溜用

Spring Spring是一个框架,这个框架是用来帮我们解决一些问题的.其中就有IOC和AOP,分别是控制反转和面向切面编程 IOC: Inversion of Control,IOC容器放对象,使用map集合存放. Spring通过IOC容器进行管理所有JAVA对象的实例化和初始化,控制对象和对象之间的依赖关系,它管理的JAV…

PX2平台Pytorch源码编译

写在前面&#xff1a;以下内容完成于2019年底&#xff0c;只是把笔记放到了CSDN上。 需要注释掉NCLL及分布式相关的配置 libcudart.patch diff --git a/torch/cuda/__init__.py b/torch/cuda/__init__.py index 4591702..07e1268 100644 --- a/torch/cuda/__init__.pyb/torc…

Python中的for循环:深入解析与实用技巧

本套课在线学习视频&#xff08;网盘地址&#xff0c;保存到网盘即可免费观看&#xff09;&#xff1a; ​​https://pan.quark.cn/s/677661ea63b3​​ 00:01 - 详解for循环语句及其遍历功能 基本语法 Python中的for循环用于遍历集合中的元素。其基本语法如下&#xff1a; …

一文2000字记录基于jmeter+perfmon的稳定性测试

01、任务情况 1、任务总览 本次平台稳定性测试的目的在于&#xff1a;在服务器压力处于较饱和&#xff08;达到80%系统最大TPS&#xff09;压力之下&#xff0c;在较长时间&#xff08;>8小时&#xff09;之内观测服务器稳定性问题&#xff0c;以及资源使用情况和异常。 …

数仓建模—数据生命周期管理

数仓建模—数据生命周期管理 数据生命周期管理 (DLM) 是一种在从数据输入到数据销毁的整个生命周期内管理数据的方法。 数据根据不同的条件分处不同的阶段,随着其完成不同的任务或满足特定要求而逐次经历这些阶段。 一个出色的 DLM 流程提供针对企业数据的结构和组织,帮助实…

什么是异常规格(Exception Specification)

异常规格&#xff08;Exception Specification&#xff09;是C编程语言中用于声明函数可能抛出的异常类型的一种机制。以下是关于异常规格的详细解释&#xff1a; 定义与用途&#xff1a; 异常规格允许程序员在函数声明时指定该函数可能抛出的异常类型。它通过throw(exception…

ai智能语音机器人是如何影响客户体验的?电销机器人部署

随着人工智能技术的进步&#xff0c;越来越多的企业在寻求如何将人工智能技术融合到现有的商业模式上&#xff0c;进而实现自动化、智能化。在通信行业大量使用智能语音机器人、聊天机器人、客服机器人时&#xff0c;它能和“客户体验”并驾齐驱吗&#xff0c;还是可以让客户体…

【随笔】提高代码学习水平(以更高的视角看事物)

最近&#xff0c;我感觉到自己的代码水平似乎卡在了一个瓶颈。似乎只想着数仓&#xff0c;Hive&#xff0c;Spark技术优化&#xff0c;但只要稍微离开这几个点&#xff0c;我就感到无所适从。我开始反思&#xff0c;或许&#xff0c;我应该总结一下自己的学习方法。 1.站的高&…

springcloud-gateway 路由加载流程

问题 Spring Cloud Gateway版本是2.2.9.RELEASE&#xff0c;原本项目中依赖服务自动发现来自动配置路由到微服务的&#xff0c;但是发现将spring.cloud.gateway.discovery.locator.enabledfalse 启动之后Gateway依然会将所有微服务自动注册到路由中&#xff0c;百思不得其解&a…

【仿真建模-anylogic】开发规范

Author&#xff1a;赵志乾 Date&#xff1a;2024-06-28 Declaration&#xff1a;All Right Reserved&#xff01;&#xff01;&#xff01; 0. 说明 实际模型开发过程中&#xff0c;对遇到的问题进行总结归纳出以下开发规范&#xff0c;仅供参考&#xff01; 1. 强制性规范 1…

vivado CLOCK_BUFFER_TYPE

时钟缓冲区类型 默认情况下&#xff0c;Vivado synthesis推断输入缓冲区和全局时钟缓冲区&#xff08;IBUF/BUFG&#xff09; 时钟端口的组合。但是&#xff0c;您可以使用IO_BUFFER_TYPE和 CLOCK_BUFFER_TYPE属性组合在一起&#xff0c;以指导Vivado合成工具更改 默认缓冲区类…

Schema Free

向量检索服务DashVector在设计上支持Schema Free&#xff0c;在插入Doc、更新Doc、插入或更新Doc时&#xff0c;可设置任意KeyValue结构的字段(Field)&#xff0c;如下所示&#xff1a; Python示例&#xff1a; collection.insert(Doc(id1,vectornp.random.rand(4),fields{na…

pcr实验室和P2实验室装修设计中的区别

PCR实验室和P2实验室在装修设计的区别是什么&#xff1f;PCR实验室指的是基因扩增实验室&#xff0c;而P2实验室是指生物安全实验室中的一个分类&#xff0c;是生物安全防护达到二级的实验室。那么PCR实验室和P2实验室装修设计标准是什么&#xff1f;实验室装修公司小编为您详解…