
Java文章目录Java基础篇数据类型面向对象关键字浅拷贝深拷贝反射注解异常Objectjava新特性序列化设计模式I/ONative基础篇java的特点平台无关性面向对象内存管理Java的优点缺点优点跨平台支持面向对象拥有强大的生态系统内存管理自动垃圾回收机制多线程支持缺点性能较低使用JVM相较于cpp直接使用操作系统来说较慢启动时间较长语法繁琐消耗内存JVM本身就占用内存开发效率较低java为什么能跨平台一次编译到处运行Java 程序不是直接跑在操作系统上的而是跑在 JVMJava 虚拟机上的JVM普通的翻译器比如 C 编译器是一次性翻译翻译完就走了。而 JVM 是一直活着的它在程序运行期间持续工作功能说明翻译字节码把字节码翻译成机器码JIT内存管理自动分配和回收内存垃圾回收 GC线程管理管理 Java 线程的创建和调度安全检查数组越界检查、空指针检查等类加载动态加载 .class 文件所以 JVM 不只是翻译器它更像是一个**“虚拟的操作系统”**——它给 Java 程序提供了一个统一的运行环境屏蔽了底层操作系统的差异JavaC跨平台方式编译一次字节码到处跑一套代码每个平台分别编译需要用户装什么JVM什么都不用装直接是 .exe开发者要做什么不用管平台需要处理平台差异条件编译/跨平台库运行性能有 JVM 开销直接跑机器码性能最优跨平台的是Java程序不是JVM。JVM是用C/C开发的是编译后的机器码不能跨平台不同平台下需要安装不同版本的JVMJDKJJREJVMJDK是开发工具包里面有编译器、调试工具这些也包含了JRE。JRE是运行环境包含了JVM和Java的核心类库。所以关系就是JDK大于JRE大于JVMJVM是 java 虚拟机主要工作是解释自己的指令集即字节码并映射到本地的CPU指令集和OS的系统调用。JVM屏蔽了与操作系统平台相关的信息使得Java程序只需要生成在Java虚拟机上运行的目标代码字节码就可在多种平台上不加修改的运行这也是Java能够“一次编译到处运行的”原因为啥Java既有编译又有解释编译型C/C解释型Python翻译时机运行前一次性全部翻译完运行时逐行翻译逐行执行执行速度快直接跑机器码慢每次都要翻译跨平台差编译产物绑定平台换平台要重新编译好源代码通用换平台装个解释器就行调试麻烦编译完才能发现错误方便跑到哪行出错就停在哪行代表语言C、C、Go、RustPython、JavaScript、Ruby值传递和引用传递对象在堆内存里只有一份变量在栈内存里各自独立。值传递复制的是变量栈上的那份但堆里的对象没有被复制Java只有值传递数据类型Java八种基本数据类型的字节数1字节(byte、boolean)、 2字节(short、char)、4字节(int、float)、8字节(long、double)浮点数的默认类型为double如果需要声明一个常量为float型则必须要在末尾加上f或F整数的默认类型为int声明Long型在末尾加上l或者L八种基本数据类型的包装类除了char的是Character、int类型的是Integer其他都是首字母大写char类型是无符号的不能为负所以是0开始的类型转换隐式转换小范围类型赋值为大范围类型时Java自动完成byte → short → int → long → float → double ↑ char 也可以自动转为 int强制转换大范围类型赋值为小范围类型时必须手动转换(目标类型)值字符串转换将字符串转为其他类型eg转为intInteger.parseInt()大转小可能出现的问题数据溢出此时会丢弃高位字节精度损失比如double转int小数部分会被舍弃BigDecimal为什么需要它double和float用二进制存储小数无法精确表示所有十进制数System.out.println(0.10.2);// 输出0.30000000000000004//0.10.2 产生误差是因为二进制无法精确表示十进制小数只能截断存储近似值BigDecimal 底层是怎么实现高精度的核心思路彻底不用二进制浮点数改用整数 标度来表示十进制数整数在二进制中是精确的不存在截断问题所以从根源上避免了精度丢失装箱和拆箱装箱Boxing和拆箱Unboxing是将基本数据类型和对应的包装类之间进行转换的过程java5引入自动拆装箱编译器会在编译阶段自动帮你转换弊端缓存机制-128 ~ 127Integer.valueOf()对-128 到 127之间的值做了缓存超出这个范围每次都会new新对象null拆箱会抛空指针性能损耗自动装箱拆箱本质是创建对象和调用方法频繁使用会有性能开销循环中尽量避免为啥要有包装类核心原因是让基本类型也能参与到万物皆对象的面向对象体系中泛型中的应用在Java中泛型只能使用引用类型而不能使用基本类型。因此如果要在泛型中使用int类型必须使用Integer包装类。eg如果我们使用基本数据类型int无法直接使用Collections.sort()方法转换中的应用在Java中基本类型和引用类型不能直接进行转换必须使用包装类来实现。例如将一个int类型的值转换为String类型必须首先将其转换为Integer类型然后再转换为String类型集合中的应用Java集合中只能存储对象而不能存储基本数据类型。因此如果要将int类型的数据存储在集合中必须使用Integer包装类integer相比int有啥优点核心优点在于它是对象因此支持 null 语义、泛型与集合存储并提供丰富的工具方法但代价是更高的内存开销与自动装箱拆箱的性能损耗int相比integer有啥优点读写效率高存储效率高integer的缓存integer类内部实现了一个静态缓存池用于存储特定范围内的整数值对应的Integer对象。默认情况下这个范围是-128至127。当通过Integer.valueOf(int)方法创建一个在这个范围内的整数对象时并不会每次都生成新的对象实例而是复用缓存中的现有对象会直接从内存中取出不需要新建一个对象面向对象面向对象是一种编程范式它将现实世界中的事物抽象为对象对象具有属性称为字段或属性和行为称为方法。面向对象编程的设计思想是以对象为中心通过对象之间的交互来完成程序的功能具有灵活性和可扩展性通过封装和继承可以更好地应对需求变化。Java面向对象的三大特性包括封装、继承、多态封装封装是指将对象的属性数据和行为方法结合在一起对外隐藏对象的内部细节仅通过对象提供的接口与外界交互。封装的目的是增强安全性和简化编程使得对象更加独立。继承继承是一种可以使得子类自动共享父类数据结构和方法的机制。它是代码复用的重要手段通过继承可以建立类与类之间的层次关系使得结构更加清晰。多态多态是指允许不同类的对象对同一消息作出响应。即同一个接口使用不同的实例而执行不同操作。多态性可以分为编译时多态重载和运行时多态重写。它使得程序具有良好的灵活性和扩展性。多态的体现方法重载方法重写接口与实现向上转型和向下转型多态是指子类可以替换父类在实际的代码运行过程中调用子类的方法实现。多态这种特性也需要编程语言提供特殊的语法机制来实现比如继承、接口类。多态可以提高代码的扩展性和复用性是很多设计模式、设计原则、编程技巧的代码实现基础。比如策略模式、基于接口而非实现编程、依赖倒置原则、里式替换原则、利用多态去掉冗长的 if-else 语句等等面向对象的设计原则单一职责原则SRP一个类应该只有一个引起它变化的原因即一个类应该只负责一项职责。例子考虑一个员工类它应该只负责管理员工信息而不应负责其他无关工作。开放封闭原则OCP软件实体应该对扩展开放对修改封闭。例子通过制定接口来实现这一原则比如定义一个图形类然后让不同类型的图形继承这个类而不需要修改图形类本身。里氏替换原则LSP子类对象应该能够替换掉所有父类对象。例子一个正方形是一个矩形但如果修改一个矩形的高度和宽度时正方形的行为应该如何改变就是一个违反里氏替换原则的例子。接口隔离原则ISP客户端不应该依赖那些它不需要的接口即接口应该小而专。例子通过接口抽象层来实现底层和高层模块之间的解耦比如使用依赖注入。依赖倒置原则DIP高层模块不应该依赖低层模块二者都应该依赖于抽象抽象不应该依赖于细节细节应该依赖于抽象。例子如果一个公司类包含部门类应该考虑使用合成/聚合关系而不是将公司类继承自部门类。最少知识原则 (Law of Demeter)一个对象应当对其他对象有最少的了解只与其直接的朋友交互。关键字Java 中 final 作用是什么final关键字主要有以下三个方面的作用用于修饰类、方法和变量。修饰类当final修饰一个类时表示这个类不能被继承是类继承体系中的最终形态。例如Java 中的String类就是用final修饰的这保证了String类的不可变性和安全性防止其他类通过继承来改变String类的行为和特性。修饰方法用final修饰的方法不能在子类中被重写。比如java.lang.Object类中的getClass方法就是final的因为这个方法的行为是由 Java 虚拟机底层实现来保证的不应该被子类修改。修饰变量当final修饰基本数据类型的变量时该变量一旦被赋值就不能再改变。例如final int num 10;这里的num就是一个常量不能再对其进行重新赋值操作否则会导致编译错误。对于引用数据类型final修饰意味着这个引用变量不能再指向其他对象但对象本身的内容是可以改变的。例如final StringBuilder sb new StringBuilder(Hello);不能让sb再指向其他StringBuilder对象但可以通过sb.append( World);来修改字符串的内容。Java 中 static的作用是什么static关键字主要用于修饰类的成员变量、方法、代码块和内部类其核心作用是将成员与类本身关联而非与类的实例对象关联。具体作用如下1、修饰变量被static修饰的变量属于类本身而非类的某个实例。所有对象共享同一份静态变量内存中只存在一份副本。可以通过类名.变量名直接访问无需创建对象也可通过对象访问但不推荐。通常用于存储所有对象共享的数据如常量、计数器等。publicclassStudent{// 静态变量所有学生共享同一个学校名称publicstaticStringschoolName阳光中学;// 实例变量每个学生有自己的姓名privateStringname;}// 访问静态变量publicclassTest{publicstaticvoidmain(String[]args){System.out.println(Student.schoolName);// 直接通过类名访问}}2、修饰方法静态方法属于类不属于任何实例因此不能直接访问类中的非静态成员变量 / 方法因为非静态成员依赖于对象存在但可以访问静态成员。通过类名.方法名直接调用无需创建对象。通常用于工具类方法如Math.random()、工厂方法等不需要依赖对象状态即可完成操作。publicclassMathUtils{// 静态方法无需创建对象即可调用publicstaticintadd(inta,intb){returnab;}}// 调用静态方法publicclassTest{publicstaticvoidmain(String[]args){intresultMathUtils.add(2,3);// 直接通过类名调用}}3、修饰代码块静态代码块在类初始化阶段即执行clinit时执行且只执行一次优于对象构造方法用于初始化静态变量或执行类级别的预处理操作。JVM 的类生命周期为加载 → 链接验证、准备、解析→ 初始化静态代码块属于初始化阶段而非加载阶段。多个静态代码块按定义顺序执行且先于非静态代码块和构造方法。publicclassDatabase{privatestaticStringurl;// 静态代码块初始化静态变量static{urljdbc:mysql://localhost:3306/test;System.out.println(数据库连接地址初始化完成);}}4、修饰内部类静态内部类不依赖于外部类的实例可以独立存在不能直接访问外部类的非静态成员需通过外部类实例访问。当内部类与外部类的实例无关时使用避免内部类持有外部类的引用导致的内存泄漏。publicclassOuterClass{privatestaticintstaticVar10;privateintinstanceVar20;// 静态内部类publicstaticclassStaticInnerClass{publicvoidprint(){System.out.println(staticVar);// 可访问外部类静态变量// System.out.println(instanceVar); // 错误不能直接访问非静态变量}}}// 使用静态内部类publicclassTest{publicstaticvoidmain(String[]args){OuterClass.StaticInnerClassinnernewOuterClass.StaticInnerClass();inner.print();}}浅拷贝深拷贝浅拷贝是指只复制对象本身和其内部的值类型字段但不会复制对象内部的引用类型字段。换句话说浅拷贝只是创建一个新的对象然后将原对象的字段值复制到新对象中但如果原对象内部有引用类型的字段只是将引用复制到新对象中两个对象指向的是同一个引用对象。深拷贝是指在复制对象的同时将对象内部的所有引用类型字段的内容也复制一份而不是共享引用。换句话说深拷贝会递归复制对象内部所有引用类型的字段生成一个全新的对象以及其内部的所有对象反射反射具有以下特性运行时类信息访问反射机制允许程序在运行时获取类的完整结构信息包括类名、包名、父类、实现的接口、构造函数、方法和字段等。动态对象创建可以使用反射API动态地创建对象实例即使在编译时不知道具体的类名。通常通过Constructor.newInstance()方法实现Class.newInstance()自 Java 9 起已被标记为Deprecated推荐使用clazz.getDeclaredConstructor().newInstance()。动态方法调用可以在运行时动态地调用对象的方法包括私有方法。这通过Method类的invoke()方法实现允许你传入对象实例和参数值来执行方法。访问和修改字段值反射还允许程序在运行时访问和修改对象的字段值即使是私有的。这是通过Field类的get()和set()方法完成的。反射机制就是java中代码控制代码的一种能力让代码能动态地操控其他的代码是实现软编码的其中一种技术为啥字符串可以动态改变直接new对象就不行new关键字静态编译使用new关键字是必须在编译器就明确写出要创建的具体类名。编译器在翻译代码是会严格校准这个类是否存在如果不存在会报错。因此new的灵活性较低反射机制动态编译反射的核心能力是在运行期动态操作类在反射中类名仅仅是一个普通的字符串。对于编译器来说字符串只是普通文本它不会校准这个字符串对应的类是否存在只有当程序真正运行到这一行时JVM才会去内存中加载这个类创建对象或调用注解注解本质是一个继承了Annotation的特殊接口其具体实现类是Java运行时生成的动态代理类。我们通过反射获取注解时返回的是Java运行时生成的动态代理对象。通过代理对象调用自定义注解的方法会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池。异常Error(错误) 表示运行环境的错误错误是程序无法处理的严重问题如虚拟机错误、动态链接库失效等。程序不应该尝试捕获这类错误。例如OutOfMemoryError、StackOverflowError等。Exception(异常) 表示程序本身可以处理的异常情况。异常分为两大类非运行时异常受检异常Checked Exception 这类异常在编译时就必须被捕获或者声明抛出。它们通常是外部错误如文件不存在 (FileNotFoundException)、类未找到 (ClassNotFoundException) 等。非运行时异常强制程序员处理这些可能出现的问题增强了程序的健壮性。运行时异常非受检异常Unchecked Exception 或 RuntimeException 这类异常特指RuntimeException及其子类。它与Error一起构成了 Java 中的非受检异常家族。运行时异常由程序逻辑错误导致如空指针访问 (NullPointerException)、数组越界 (ArrayIndexOutOfBoundsException) 等。运行时异常是不需要在编译时强制捕获或声明的。Objectjava 里 string的常用方法有哪些我常用的 String 常用方法有这些获取长度int length()返回字符串的长度字符个数。例abc.length()→ 3判断内容boolean equals(Object obj)比较两个字符串的内容是否完全相同区分大小写。例abc.equals(ABC)→ false截取子串String substring(int beginIndex)从指定索引开始截取到末尾。例hello.substring(2)→ “llo”去除空格String trim()去除字符串首尾的空白字符空格、制表符等。例 abc .trim()→ “abc”替换内容String replace(char oldChar, char newChar)替换所有指定字符。例aaa.replace(a, b)→ “bbb”判断空字符串boolean isEmpty()判断字符串长度是否为 0注意null调用会报错需先判空。String、StringBuffer、StringBuilder的区别和联系1、可变性String是不可变的Immutable一旦创建内容无法修改每次修改都会生成一个新的对象。StringBuilder和StringBuffer是可变的Mutable可以直接对字符串内容进行修改而不会创建新对象。2、线程安全性String因为不可变天然线程安全。StringBuilder不是线程安全的适用于单线程环境。StringBuffer是线程安全的其方法通过synchronized关键字实现同步适用于多线程环境。3、性能String性能最低尤其是在频繁修改字符串时会生成大量临时对象增加内存开销和垃圾回收压力。StringBuilder性能最高因为它没有线程安全的开销适合单线程下的字符串操作。StringBuffer性能略低于StringBuilder因为它的线程安全机制引入了同步开销。4、使用场景如果字符串内容固定或不常变化优先使用String。如果需要频繁修改字符串且在单线程环境下使用StringBuilder。如果需要频繁修改字符串且在多线程环境下使用StringBuffer。对比总结如下特性StringStringBuilderStringBuffer不可变性不可变可变可变线程安全是因不可变否是同步方法性能低频繁修改时高单线程中多线程安全适用场景静态字符串单线程动态字符串多线程动态字符串java新特性特性名称描述示例或说明Lambda 表达式简化匿名内部类支持函数式编程(a, b) - a b代替匿名类实现接口函数式接口仅含一个抽象方法的接口可用FunctionalInterface注解标记Runnable,Comparator, 或自定义接口FunctionalInterface interface MyFunc { void run(); }Stream API提供链式操作处理集合数据支持并行处理list.stream().filter(x - x 0).collect(Collectors.toList())Optional 类封装可能为null的对象减少空指针异常Optional.ofNullable(value).orElse(default)方法引用简化 Lambda 表达式直接引用现有方法System.out::println等价于x - System.out.println(x)接口的默认方法与静态方法接口可定义默认实现和静态方法增强扩展性interface A { default void print() { System.out.println(默认方法); } }并行数组排序使用多线程加速数组排序Arrays.parallelSort(array)重复注解允许同一位置多次使用相同注解Repeatable注解配合容器注解使用类型注解注解可应用于更多位置如泛型、异常等ListNonNull String listCompletableFuture增强异步编程能力支持链式调用和组合操作CompletableFuture.supplyAsync(() - result).thenAccept(System.out::println)序列化Java 默认的序列化虽然实现方便但却存在安全漏洞、不跨语言以及性能差等缺陷。无法跨语言 Java 序列化目前只适用基于 Java 语言实现的框架其它语言大部分都没有使用 Java 的序列化框架也没有实现 Java 序列化这套协议。因此如果是两个基于不同语言编写的应用程序相互通信则无法实现两个应用服务之间传输对象的序列化与反序列化。容易被攻击Java 序列化是不安全的我们知道对象是通过在 ObjectInputStream 上调用 readObject() 方法进行反序列化的这个方法其实是一个神奇的构造器它可以将类路径上几乎所有实现了 Serializable 接口的对象都实例化。这也就意味着在反序列化字节流的过程中该方法可以执行任意类型的代码这是非常危险的。序列化后的流太大序列化后的二进制流大小能体现序列化的性能。序列化后的二进制数组越大占用的存储空间就越多存储硬件的成本就越高。如果我们是进行网络传输则占用的带宽就更多这时就会影响到系统的吞吐量。用主流序列化框架比如FastJson、Protobuf来替代Java 序列化。如果追求性能的话Protobuf 序列化框架会比较合适Protobuf 的这种数据存储格式不仅压缩存储数据的效果好 在编码和解码的性能方面也很高效。Protobuf 的编码和解码过程结合.proto 文件格式加上 Protocol Buffer 独特的编码格式只需要简单的数据运算以及位移等操作就可以完成编码与解码。可以说 Protobuf 的整体性能非常优秀设计模式正确的双重检查锁定模式需要使用 volatile。volatile主要包含两个功能。保证可见性。使用 volatile 定义的变量将会保证对所有线程的可见性。禁止指令重排序优化。由于 volatile 禁止对象创建时指令之间重排序所以其他线程不会访问到一个未初始化的对象从而保证安全性。代理模式和适配器模式有什么区别目的不同代理模式主要关注控制对对象的访问而适配器模式则用于接口转换使不兼容的类能够一起工作。结构不同代理模式一般包含抽象主题、真实主题和代理三个角色适配器模式包含目标接口、适配器和被适配者三个角色。应用场景不同代理模式常用于添加额外功能或控制对对象的访问适配器模式常用于让不兼容的接口协同工作。责任链模式使用场景是什么责任链模式的使用场景核心很明确就是一个请求需要多个独立的处理逻辑来承接同时不想让请求发起方和所有处理者产生强关联还得让处理流程能灵活调整简单说就是谁能处理就谁来接手整个处理顺序和参与节点能按需改动。策略模式策略模式定义一系列算法把它们一个个封装起来并且使它们可相互替换。从而让算法可独立于使用它的客户而变化。假设你正在开发一个电商网站的支付模块。用户可以选择支付宝、微信支付或者银行卡支付。每种支付方式的底层逻辑比如调用的 API、加密方式、参数构造都完全不同但对于调用支付功能的上层业务逻辑来说它只需要调用一个统一的 “支付” 方法即可无需关心具体是哪种支付。这就是策略模式的用武之地。我们把每种支付方式支付宝、微信都封装成一个独立的 “策略” 类它们都遵循一个共同的接口。上层业务代码通过这个共同的接口来调用支付功能并可以在运行时根据用户的选择动态地切换不同的策略。所以策略模式适用场景如下当一个系统需要动态地在几种算法中选择一种时。当一个对象有很多行为不想使用大量的if-else或switch-case语句来选择时。当你希望算法的变化不会影响到使用它的客户端时。责任链模式责任链模式核心思想是为请求创建一个接收者对象的链。对请求的发送者和接收者进行解耦让多个对象都有机会处理这个请求。请求沿着链传递直到链上的一个对象处理它为止。想象一下公司里的请假审批流程。一个员工要请假他首先会把请假条交给自己的直属经理。如果请假天数很短比如 1 天经理自己就能批准。如果天数较长比如 5 天经理没有权限就需要把请假条上报给部门总监。总监也有自己的审批权限上限如果超过了就需要继续上报给总经理。在这个过程中请假条请求就在一条由经理 - 总监 - 总经理接收者链上传递直到某个角色接收者能够处理它。发送请假条的员工客户端并不知道最终是谁批准了他的假他只需要把请求提交给链的第一个节点经理即可。所以责任链模式适用场景总结当有多个对象可以处理同一个请求但具体由哪个对象处理需要在运行时决定时。当你希望请求的发送者和接收者解耦不直接通信时。当你需要动态地指定和修改处理一个请求的对象集合时I/OBIOblocking IO就是传统的 java.io 包它是基于流模型实现的交互的方式是同步、阻塞方式也就是说在读入输入流或者输出流时在读写动作完成之前线程会一直阻塞在那里它们之间的调用是可靠的线性顺序。优点是代码比较简单、直观缺点是 IO 的效率和扩展性很低容易成为应用性能瓶颈。NIOnon-blocking IO Java 1.4 引入的 java.nio 包提供了 Channel、Selector、Buffer 等新的抽象可以构建多路复用的、同步非阻塞 IO 程序同时提供了更接近操作系统底层高性能的数据操作方式。AIOAsynchronous IO 也称 NIO.2是 Java 1.7 引入的对 NIO 的扩展提供了异步非阻塞的 IO 操作方式所以人们叫它 AIOAsynchronous IO。异步 IO 是基于事件和回调机制实现的也就是应用操作之后会直接返回不会阻塞在那里当后台处理完成操作系统会通知相应的线程进行后续的操作NIO主要有三大核心部分Channel(通道)Buffer(缓冲区), Selector。传统IO基于字节流和字符流进行操作而NIO基于Channel和Buffer(缓冲区)进行操作数据总是从通道读取到缓冲区中或者从缓冲区写入到通道中。Selector(选择区)用于监听多个通道的事件比如连接打开数据到达。因此单个线程可以监听多个数据通道。Native在Java中native方法是一种特殊类型的方法它允许Java代码调用外部的本地代码即用C、C或其他语言编写的代码。native关键字是Java语言中的一种声明用于标记一个方法的实现将在外部定义。在Java类中native方法看起来与其他方法相似只是其方法体由native关键字代替没有实际的实现代码要实现native方法你需要完成以下步骤生成 JNI 头文件使用javac -h dir选项从你的 Java 类生成 C/C 的头文件这个头文件包含了所有 native 方法的原型注意旧的独立工具javah自 Java 9 起被废弃Java 10 已正式移除应改用javac -h。编写本地代码使用C/C编写本地方法的实现并确保方法签名与生成的头文件中的原型匹配。编译本地代码将C/C代码编译成动态链接库DLL在Windows上共享库SO在Linux上加载本地库在Java程序中使用System.loadLibrary()方法来加载你编译好的本地库这样JVM就能找到并调用native方法的实现了Java 进程是怎么跟操作系统交互的Java程序虽然是运行在JVM上的但JVM本身就是一个操作系统进程所以Java跟操作系统的交互本质上是通过JVM这个中间层来完成的。从用户态和内核态的角度说Java代码运行在用户态但IO、网络这些操作需要切换到内核态这个切换是有开销的。JVM会做很多优化比如用缓冲区减少系统调用次数。虽然JVM屏蔽了底层差异让Java代码跨平台但在每个平台上JVM都要适配该平台的系统调用和API。