Method

原文地址:http://docs.oracle.com/javase/tutorial/reflect/member/method.html

1.获得方法类型信息


一个方法的声明包括方法名,修饰符, 参数, 和返回类型,同时还有一些方法可能抛出的异常. 类 java.lang.reflect.Method 提供了一种方式让我们可以得到方法的这些信息.

实例程序  MethodSpy 展示了枚举一个类中所有的方法,并返回指定方法的相关信息.

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import static java.lang.System.out;public class MethodSpy {private static final String  fmt = "%24s: %s%n";// for the morbidly curious<E extends RuntimeException> void genericThrow() throws E {}public static void main(String... args) {try {Class<?> c = Class.forName(args[0]);Method[] allMethods = c.getDeclaredMethods();for (Method m : allMethods) {if (!m.getName().equals(args[1])) {continue;}out.format("%s%n", m.toGenericString());out.format(fmt, "ReturnType", m.getReturnType());out.format(fmt, "GenericReturnType", m.getGenericReturnType());Class<?>[] pType  = m.getParameterTypes();Type[] gpType = m.getGenericParameterTypes();for (int i = 0; i < pType.length; i++) {out.format(fmt,"ParameterType", pType[i]);out.format(fmt,"GenericParameterType", gpType[i]);}Class<?>[] xType  = m.getExceptionTypes();Type[] gxType = m.getGenericExceptionTypes();for (int i = 0; i < xType.length; i++) {out.format(fmt,"ExceptionType", xType[i]);out.format(fmt,"GenericExceptionType", gxType[i]);}}// production code should handle these exceptions more gracefully} catch (ClassNotFoundException x) {x.printStackTrace();}}
}

实例输入及其输出:

$ java MethodSpy java.lang.Class getConstructor
public java.lang.reflect.Constructor<T> java.lang.Class.getConstructor(java.lang.Class<?>[]) throws java.lang.NoSuchMethodException,java.lang.SecurityExceptionReturnType: class java.lang.reflect.ConstructorGenericReturnType: java.lang.reflect.Constructor<T>ParameterType: class [Ljava.lang.Class;GenericParameterType: java.lang.Class<?>[]ExceptionType: class java.lang.NoSuchMethodExceptionGenericExceptionType: class java.lang.NoSuchMethodExceptionExceptionType: class java.lang.SecurityExceptionGenericExceptionType: class java.lang.SecurityException

它的实际定义代码为:

public Constructor<T> getConstructor(Class<?>... parameterTypes)

第一点需要注意的是.这里的返回和参数类型都是泛型. Method.getGenericReturnType() 会首先在查询类中的属性签名,如果找到了,那就返回该类型.如果没有找到,该方法会去调用 Method.getReturnType() ,它的返回值便是定义该泛型时的所使用过的值.

第二点需要注意的是,最后一个参数,  parameterType,是一个可变数量的参数. 它被标识为一个一维数组.如果要区别这个和一个显示声明为数组的参数的话,可以使用方法 Method.isVarArgs().

接下来的示例展示了一个返回值为泛型的例子:

$ java MethodSpy java.lang.Class cast
public T java.lang.Class.cast(java.lang.Object)ReturnType: class java.lang.ObjectGenericReturnType: TParameterType: class java.lang.ObjectGenericParameterType: class java.lang.Object

对于返回值是泛型的 Class.cast() 的方法,使用反射之后得到它的返回值是java.lang.Object. 这是因为在编译时期所有的跟泛型有关的信息都会被擦除. T的擦除被定义为CLass:

public final class Class<T> implements ..

因此T被取代使用向上转型原则.在这个示例中便是java.lang.Object了.

最后一个示例程序展示了一个输出一个多次重载的方法的例子:

$ java MethodSpy java.io.PrintStream format
public java.io.PrintStream java.io.PrintStream.format(java.util.Locale,java.lang.String,java.lang.Object[])ReturnType: class java.io.PrintStreamGenericReturnType: class java.io.PrintStreamParameterType: class java.util.LocaleGenericParameterType: class java.util.LocaleParameterType: class java.lang.StringGenericParameterType: class java.lang.StringParameterType: class [Ljava.lang.Object;GenericParameterType: class [Ljava.lang.Object;
public java.io.PrintStream java.io.PrintStream.format(java.lang.String,java.lang.Object[])ReturnType: class java.io.PrintStreamGenericReturnType: class java.io.PrintStreamParameterType: class java.lang.StringGenericParameterType: class java.lang.StringParameterType: class [Ljava.lang.Object;GenericParameterType: class [Ljava.lang.Object;

2.获得方法的参数名称


我们可以通过 java.lang.reflect.Executable.getParameters.方法获得任何方法和构造函数的形式参数的名称.(Method类和Constructor类都继承Executable类,因此他们都继承了方法Executable.getParameters.) 然而,默认情况下.class文件并不会存储形式参数名称.

如果想要保留形参形成,我们需要编译源码使用-parameters 选项.

实例程序 MethodParameterSpy 展示了如果获取一个指定类的所有方法和构造函数的形式参数的名称.实例同时还展示出一些关于参数的其他信息.

MethodParameterSpy源码:

/** Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.** Redistribution and use in source and binary forms, with or without* modification, are permitted provided that the following conditions* are met:**   - Redistributions of source code must retain the above copyright*     notice, this list of conditions and the following disclaimer.**   - Redistributions in binary form must reproduce the above copyright*     notice, this list of conditions and the following disclaimer in the*     documentation and/or other materials provided with the distribution.**   - Neither the name of Oracle or the names of its*     contributors may be used to endorse or promote products derived*     from this software without specific prior written permission.** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR* PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.*/import java.lang.reflect.*;
import java.util.function.*;
import static java.lang.System.out;public class MethodParameterSpy {private static final String  fmt = "%24s: %s%n";// for the morbidly curious<E extends RuntimeException> void genericThrow() throws E {}public static void printClassConstructors(Class c) {Constructor[] allConstructors = c.getConstructors();out.format(fmt, "Number of constructors", allConstructors.length);for (Constructor currentConstructor : allConstructors) {printConstructor(currentConstructor);}  Constructor[] allDeclConst = c.getDeclaredConstructors();out.format(fmt, "Number of declared constructors",allDeclConst.length);for (Constructor currentDeclConst : allDeclConst) {printConstructor(currentDeclConst);}          }public static void printClassMethods(Class c) {Method[] allMethods = c.getDeclaredMethods();out.format(fmt, "Number of methods", allMethods.length);for (Method m : allMethods) {printMethod(m);}        }public static void printConstructor(Constructor c) {out.format("%s%n", c.toGenericString());Parameter[] params = c.getParameters();out.format(fmt, "Number of parameters", params.length);for (int i = 0; i < params.length; i++) {printParameter(params[i]);}}public static void printMethod(Method m) {out.format("%s%n", m.toGenericString());out.format(fmt, "Return type", m.getReturnType());out.format(fmt, "Generic return type", m.getGenericReturnType());Parameter[] params = m.getParameters();for (int i = 0; i < params.length; i++) {printParameter(params[i]);}}public static void printParameter(Parameter p) {out.format(fmt, "Parameter class", p.getType());out.format(fmt, "Parameter name", p.getName());out.format(fmt, "Modifiers", p.getModifiers());out.format(fmt, "Is implicit?", p.isImplicit());out.format(fmt, "Is name present?", p.isNamePresent());out.format(fmt, "Is synthetic?", p.isSynthetic());}public static void main(String... args) {        try {printClassConstructors(Class.forName(args[0]));printClassMethods(Class.forName(args[0]));} catch (ClassNotFoundException x) {x.printStackTrace();}}
}

下面的输出显示了类 ExampleMethods的方法和构造函数的形参名称.(注意:记得使用-parameters选项编译ExmapleMethods)

Number of constructors: 1Constructor #1
public ExampleMethods()Number of declared constructors: 1Declared constructor #1
public ExampleMethods()Number of methods: 4Method #1
public boolean ExampleMethods.simpleMethod(java.lang.String,int)Return type: booleanGeneric return type: booleanParameter class: class java.lang.StringParameter name: stringParamModifiers: 0Is implicit?: falseIs name present?: trueIs synthetic?: falseParameter class: intParameter name: intParamModifiers: 0Is implicit?: falseIs name present?: trueIs synthetic?: falseMethod #2
public int ExampleMethods.varArgsMethod(java.lang.String...)Return type: intGeneric return type: intParameter class: class [Ljava.lang.String;Parameter name: manyStringsModifiers: 0Is implicit?: falseIs name present?: trueIs synthetic?: falseMethod #3
public boolean ExampleMethods.methodWithList(java.util.List<java.lang.String>)Return type: booleanGeneric return type: booleanParameter class: interface java.util.ListParameter name: listParamModifiers: 0Is implicit?: falseIs name present?: trueIs synthetic?: falseMethod #4
public <T> void ExampleMethods.genericMethod(T[],java.util.Collection<T>)Return type: voidGeneric return type: voidParameter class: class [Ljava.lang.Object;Parameter name: aModifiers: 0Is implicit?: falseIs name present?: trueIs synthetic?: falseParameter class: interface java.util.CollectionParameter name: cModifiers: 0Is implicit?: falseIs name present?: trueIs synthetic?: false

MethodParameterSpy 实例程序使用了Parameter类中的这些方法:

1.getType: 返回该参数被声明时的类型所对应的Class对象.

2.getName: 返回该参数的名称.如果这个参数的名称在.class文件中可以找到,那么就返回该名称.如果没有找到,

那么会自动生成一个名称,argN.其 中N是参数的索引.

作为一个示例,不适用-parameters选项编译ExampleMethods源码.那么MethodParameterSpy将会打印出如下信息:

public boolean ExampleMethods.simpleMethod(java.lang.String,int)Return type: booleanGeneric return type: booleanParameter class: class java.lang.StringParameter name: arg0Modifiers: 0Is implicit?: falseIs name present?: falseIs synthetic?: falseParameter class: intParameter name: arg1Modifiers: 0Is implicit?: falseIs name present?: falseIs synthetic?: false

3. getModifiers:返回一个Int值表示该形参的特征.这个值是下面这些值中被应用到该形参的值的和.

4.isImplicit: 返回true如果这个参数是被隐式的声明.

5.isNamePresent: 返回true如果该参数在.class文件中有对应的名称.

6.isSynthetic: 返回true如果这个参数既不是显示也不是隐式声明的.

(详细介绍Implicit和synthetic 请看下一小节)

隐式参数和合成参数:

有一些结构虽然没有被显示的在源码中声明,但是它却被隐式的声明了.例如 ExampleMethods类并没有包含构造函数.一个默认的构造函数被隐式的声明了.

MethodParameterSpy显示了ExampleMethods类中隐式声明的构造函数.

Number of declared constructors: 1
public ExampleMethods()

考虑一下情况:

public class MethodParameterExamples {public class InnerClass { }
}

MethodParameterExamples是一个非静态的内部类. 一个默认的构造函数被隐式的为其声明.然而,然而这个构造函数还包含了一个参数.当java编译器编译内部类的时候,它会创建一个.class文件.类似于这个:

public class MethodParameterExamples {public class InnerClass {final MethodParameterExamples parent;InnerClass(final MethodParameterExamples this$0) {parent = this$0; }}
}

内部类的构造函数使用包含该内部类的类作为参数.因此,实例MethodParameterExample显示如下:

public MethodParameterExamples$InnerClass(MethodParameterExamples)Parameter class: class MethodParameterExamplesParameter name: this$0Modifiers: 32784Is implicit?: trueIs name present?: trueIs synthetic?: false

因为InnerClass的构造函数是隐式声明的,那么他的参数也是隐式声明的.

注意:

  • java编译器为一个内部类创建一个构造函数是为了可以在创建包含该内部类的类时,方便的将该类的实例传递给该内部类.
  • 值32784说明了InerClass的构造函数同时是final(16)类型和implicit(32768)

Constructs emitted by a Java compiler are marked as synthetic if they do not correspond to a construct declared explicitly or implicitly in source code, unless they are class initialization methods. Synthetic constructs are artifacts generated by compilers that vary among different implementations. Consider the following excerpt from

Note:

  • The Java compiler creates a formal parameter for the constructor of an inner class to enable the compiler to pass a reference (representing the immediately enclosing instance) from the creation expression to the member class's constructor.
  • The value 32784 means that the parameter of the InnerClass constructor is both final (16) and implicit (32768).
  • The Java programming language allows variable names with dollar signs ($); however, by convention, dollar signs are not used in variable names.

Constructs emitted by a Java compiler are marked as synthetic if they do not correspond to a construct declared explicitly or implicitly in source code, unless they are class initialization methods. Synthetic constructs are artifacts generated by compilers that vary among different implementations. Consider the following excerpt from MethodParameterExamples:

由java编译器生成, 并且该结构在源代码中没有被显示或者隐式的声明,并且该结构不是类初始化方法.则该结果被标记为synthetic.不同的编译器会生成不同的合成结构.

考虑以下情况 MethodParameterExamples:

public class MethodParameterExamples {enum Colors {RED, WHITE;}
}

当java编译器遇到一个枚举结构,它会创建一系列方法.提供枚举类型所需要的功能.例如. java编译器会为enum结构Color创建以个.class文件,该文件内容如下:

final class Colors extends java.lang.Enum<Colors> {public final static Colors RED = new Colors("RED", 0);public final static Colors BLUE = new Colors("WHITE", 1);private final static values = new Colors[]{ RED, BLUE };private Colors(String name, int ordinal) {super(name, ordinal);}public static Colors[] values(){return values;}public static Colors valueOf(String name){return (Colors)java.lang.Enum.valueOf(Colors.class, name);}
}

java编译器为Color枚举结构创建了三个方法: Colors(String name, int ordinal)Colors[] values(), 和 Colors valueOf(String name). 方法values和valueOf是隐式声明的,因此它的形参也是隐式的.

枚举的构造函数 Colors(String name, int ordinal)  是一个默认的构造函数,它是隐式声明的.然而,它的形参却不是隐式的.因为它的参数既不是显示也不是隐式声明的,所以它们是synthetic.(合成的). ( 枚举类型的默认构造函数的形参不是隐式声明,因为不同的编译器不一定会采用相同的形式.另外一个java编译器可能指定不同的形参.当编译器编译使用枚举常量的时候,依赖的是枚举结构的共有的域,这些域是隐式声明的.而不会依赖构造函数或者这些变量是怎么被初始化的)

MethodParameterExample展示了枚举类型Colors的相关信息:

enum Colors:Number of constructors: 0Number of declared constructors: 1Declared constructor #1
private MethodParameterExamples$Colors()Parameter class: class java.lang.StringParameter name: $enum$nameModifiers: 4096Is implicit?: falseIs name present?: trueIs synthetic?: trueParameter class: intParameter name: $enum$ordinalModifiers: 4096Is implicit?: falseIs name present?: trueIs synthetic?: trueNumber of methods: 2Method #1
public static MethodParameterExamples$Colors[]MethodParameterExamples$Colors.values()Return type: class [LMethodParameterExamples$Colors;Generic return type: class [LMethodParameterExamples$Colors;Method #2
public static MethodParameterExamples$ColorsMethodParameterExamples$Colors.valueOf(java.lang.String)Return type: class MethodParameterExamples$ColorsGeneric return type: class MethodParameterExamples$ColorsParameter class: class java.lang.StringParameter name: nameModifiers: 32768Is implicit?: trueIs name present?: trueIs synthetic?: false

更多详细信息,请参考 Java Language Specification

3.获得和解析方法修饰符


方法的修饰符有以下几种:

  • Access modifiers: publicprotected, and private
  • Modifier restricting to one instance: static
  • Modifier prohibiting value modification: final
  • Modifier requiring override: abstract
  • Modifier preventing reentrancy: synchronized
  • Modifier indicating implementation in another programming language: native
  • Modifier forcing strict floating point behavior: strictfp
  • Annotations

实例程序MethodModifierSpy 会列出给定方法名的修饰符.同时它还展示了该方法是不是合成的,是不是可变参数的等.

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import static java.lang.System.out;public class MethodModifierSpy {private static int count;private static synchronized void inc() { count++; }private static synchronized int cnt() { return count; }public static void main(String... args) {try {Class<?> c = Class.forName(args[0]);Method[] allMethods = c.getDeclaredMethods();for (Method m : allMethods) {if (!m.getName().equals(args[1])) {continue;}out.format("%s%n", m.toGenericString());out.format("  Modifiers:  %s%n",Modifier.toString(m.getModifiers()));out.format("  [ synthetic=%-5b var_args=%-5b bridge=%-5b ]%n",m.isSynthetic(), m.isVarArgs(), m.isBridge());inc();}out.format("%d matching overload%s found%n", cnt(),(cnt() == 1 ? "" : "s"));// production code should handle this exception more gracefully} catch (ClassNotFoundException x) {x.printStackTrace();}}
}

MethodModifierSpy的一些实例输出:

$ java MethodModifierSpy java.lang.Object wait
public final void java.lang.Object.wait() throws java.lang.InterruptedExceptionModifiers:  public final[ synthetic=false var_args=false bridge=false ]
public final void java.lang.Object.wait(long,int)throws java.lang.InterruptedExceptionModifiers:  public final[ synthetic=false var_args=false bridge=false ]
public final native void java.lang.Object.wait(long)throws java.lang.InterruptedExceptionModifiers:  public final native[ synthetic=false var_args=false bridge=false ]
3 matching overloads found
$ java MethodModifierSpy java.lang.StrictMath toRadians
public static double java.lang.StrictMath.toRadians(double)Modifiers:  public static strictfp[ synthetic=false var_args=false bridge=false ]
1 matching overload found
$ java MethodModifierSpy MethodModifierSpy inc
private synchronized void MethodModifierSpy.inc()Modifiers: private synchronized[ synthetic=false var_args=false bridge=false ]
1 matching overload found
$ java MethodModifierSpy java.lang.Class getConstructor
public java.lang.reflect.Constructor<T> java.lang.Class.getConstructor(java.lang.Class<T>[]) throws java.lang.NoSuchMethodException,java.lang.SecurityExceptionModifiers: public transient[ synthetic=false var_args=true bridge=false ]
1 matching overload found
$ java MethodModifierSpy java.lang.String compareTo
public int java.lang.String.compareTo(java.lang.String)Modifiers: public[ synthetic=false var_args=false bridge=false ]
public int java.lang.String.compareTo(java.lang.Object)Modifiers: public volatile[ synthetic=true  var_args=false bridge=true  ]
2 matching overloads found

注意到 Method.isVarArgs() 返回 true 对于 Class.getConstructor().

这些说明了该方法的声明应该看起来如下:

public Constructor<T> getConstructor(Class<?>... parameterTypes)

而不是这样

public Constructor<T> getConstructor(Class<?> [] parameterTypes)

Notice that the output for  String.compareTo()  contains two methods. The method declared in  String.java :

注意到对于方法 String.compareTo() 包含了两个方法,该方法被声明在String.java中:

public int compareTo(String anotherString);

Java反射基础(三)--Methods对象的使用相关推荐

  1. Java反射基础(一)--Class对象获取

    Classes Java中,任何一个对象要么是一个引用类型要么是基本数据类型.引用类型指的是那些直接或间接 Java.lang.Object的类.Classse,enum,和接口都是应用类型.基本类型 ...

  2. Java反射基础(二)--Fileds对象的使用

    在说Filed之前,我们先来了解一下Member接口. 反射中定义了一个接口 java.lang.reflect.Member . java.lang.reflect.Field, java.lang ...

  3. java 反射基础知识

    java 反射 基础知识 反射:reflection 反射关键类 java 反射部分应用 反射:reflection 在运行中分析类. 在运行中查看和操作对象. 基于反射自己创建对象. 调用不可以访问 ...

  4. java 反射基础 万字详解(Class-Constructor-Method-Field一条龙)

    目录 前言 一.反射及其相关概念 1.什么是反射? 2.反射的用途: ①分析类: ②查看并使用对象: 3.反射的应用场景: 4.类加载器: 类的加载时机: 5.Class对象: 联系: 二.获取Cla ...

  5. java反射之获取class对象,Java之反射机制(获取Class对象的三种方式)

    Java之反射机制(获取Class对象的三种方式) 开发工具与关键技术:MyEclipse 10,java 作者:刘东标 撰写时间:2019-06-14 如何得到各个字节码对应的实例对象? 每个类被加 ...

  6. java反射基础_Java反射基础(一)--Class对象获取

    ClassesJava中,任何一个对象要么是一个引用类型要么是基本数据类型.引用类型指的是那些直接或间接 Java.lang.Object的类.Classse,enum,和接口都是应用类型.基本类型是 ...

  7. 一篇文章弄懂Java反射基础和反射的应用场景

    文章目录 一.Java反射定义 二.Java反射机制实现 1.Class对象获取 2.获取class对象的摘要信息 3.获取class对象的属性.方法.构造函数等 三.反射的应用场景 1.动态代理 2 ...

  8. Java反射基础,代码示例

    文章出处:https://blog.csdn.net/ylyang12/article/details/53469957 说明:本文,在转载时,对内容略作修改,更方便阅读,代码做了调试和格式整理,总之 ...

  9. java 反射基础_Java基础教程:反射基础

    Java基础教程:反射基础 引入反射 反射是什么 能够动态分析类能力的程序称为反射.反射是一种很强大且复杂的机制. Class类 在程序运行期间,Java运行时系统始终为所有对象维护一个被称为运行时的 ...

最新文章

  1. Mysql,SqlServer,Oracle主键自动增长的设置
  2. 谷歌语音转录背后的神经网络
  3. resin php mysql_RedhatAS4.0上安装Apache2+PHP5+MySQL+Resin+SSL+GD+webalizer
  4. [转]在Winform(C#)中使用Flash控件
  5. geoprocessor and georocessing 的关系
  6. leetcode 802. Find Eventual Safe States | 802. 找到最终的安全状态(有向图DFS)
  7. arduino的esp32程序无法上传_原装正版arduino uno R3无法上传程序
  8. 2019入门级云主机选型测试报告
  9. aix oracle监听配置_Oracle数据库03用户权限与数据库的连接
  10. linux 系统调优查看排除方法
  11. 罗斯蒙特电磁流量计8723说明书_罗斯蒙特电磁流量计8732EM变送器信号处理算法说明...
  12. 互联网带来的颠覆,改变了传统的营销套路
  13. 如何解密网易ncm/qq音乐的qmcflac/酷狗kgm等加密格式转换成MP3
  14. 使用Battery Historian采集android耗电数据
  15. 主程玩失踪,公司蒸发600w,创始人秒变打工仔(含视频)
  16. 操作系统笔记(一)——操作系统的定义及作用
  17. adb 不可以网络连接问题
  18. 【计算机视觉】相机标定原理(像素点与三维坐标点的转换)
  19. SDUT-1150 因式分解
  20. 风格迁移0-04:stylegan-论文超详细解读(持续更新修改)

热门文章

  1. PowerDesigner 中SQL文件、数据库表反向生成PDM
  2. stm32f10x单片机进阶--spi使用
  3. 获取某个周在本年的开始日期和结束日期
  4. Discuz网警过滤关键词库
  5. [转] 三种Python下载url并保存文件的代码
  6. C#语言之“中英文混合字符串对齐”的方法
  7. Android开发中目前流行控件和知识点总结
  8. 计算机网络(十二)-信道划分介质访问控制-信道复用
  9. 对于JDBC的简单理解
  10. 服务器的防火墙禁止了对指定通讯端口的访问,使用iptables限制访问网站指定端口...