- 浏览: 64832 次
- 性别:
- 来自: 重庆
文章分类
最新评论
二、Java反射API
Java中以反射(reflection)API实现了Java语言的自省,关于自省和反射的区别,笔者无法给出明确的定义。反射API使得Java语言更易实现运行时的动态性,获取Java程序在运行时刻的内部结构,如Java列中的构造方法、域和方法等。反射API的主要功能包括:
① 确定一个对象的类
② 取出类的修饰符(modifiers),字段,方法,构造器和超类
③ 找出某个接口里定义的常量和方法说明
④ 创建一个类实例,这个实例在运行时刻才有名字(运行时间才生成的对象)
⑤ 取得和设定对象数据成员的值,如果数据成员名是运行时刻确定的也能做倒。
⑥ 在运行时刻调用动态对象的方法
⑦ 创建数组,数组大小和类型在运行时刻才确定。也能更改数组成员的值。
1. 对类的分析
① 获取对象的类(Class对象)
Class c = obj.getClass();
② 获取一个类的超类
Class sc = c.getSuperclass();
下面一段代码打印一个对象所有的父类
③ 获取类的名字
String name = c.getName();
④ 获取类的修饰符
int m = c.getModifiers();
此处getModifiers()方法返回的是个整型的结果,结果m并不具体表示哪一种修饰符。先看java.lang.reflect.Modifier类,该类对修饰符进行了相应的包装。
类Modifier中定义了一系列的类描述符常量:Modifier.ABSTRACT、Modifier.FINAL、Modifier.PUBLIC等。getModifiers()方法返回的即是类的所有修饰符分别对应的整型值的或运算结果,如public abstract class MyClass{},new MyClass().getClass().getModfiers()即得到Modifier.ABSTRACT | Modifier.PUBLIC。
同时Modifier类提供了一些列的方法,用于判断getModifiers()返回的整型数字中包含的类修饰符:Modifier.isPublic(m)、Modifier.isAbstract(m)、Modifier.isFinal(m)等。
⑤ 确定一个类实现的接口
Class[] theInterfaces = c.getInterfaces();
从上面的语句中可以看到,接口在反射的API中也是用Class表示的,接口是一种特殊的抽象类。可以使用Class类的isInterface()方法判断一个Class类型是一个类还是接口。
⑥ 获取类的字段
一个类的字段(Field)可能来自本类、父类、实现的接口或者接口的接口,可以使用Class对象的getFields()方法获取类中所有的public属性的字段的数组(Field对象数组)。Field对象提供方法取得字段的名字、类型和描述符,甚至可以给字段赋值或者取字段的值。当然,Class类对象提供了getDeclaredFields()方法获取包括public属性在内的所有在类中声明了的域。
⑦ 获取构造方法
构造方法是在创建类对象时调用的特殊方法,构造方法可以重载,由它们的参数加以区别。调用getConstructors方法可以取得类构造方法的有关信息,这个方法返回一个数组的
Constructor对象。可以用 Constructor对象里的相关方法来确定构造方法的名字、描述符、参数类型和抛出的意外列表。也可以用Constructor.newInstance创建一个新的Constructor对象。
⑧ 获取成员方法
如何找出类的public方法呢?当然是调用getMethods方法。由getMethods方法返回一个数组,数组元素类型是Method对象。方法的名字,类型,参数,描述和抛出的意外都可
以由Method对象的方法来取得。用 Method.invoke 方法自己调用这个方法。
Method类的对象可以通过getName取方法名、getReturnType取返回值的类型、用
getParameterTypes取得参数类型(Class对象)的数组,对每个参数用getName取参数的类型名。
同样,对于非public的方法,可以使用getDeclaredMethods()方法获取。
2. 实现程序的动态性
① 创建对象
如果知道一个类型,很容易使用new操作符创建一个类的实例。但是如果在编译时并不知道具体要实例化的是哪个类的对象,如何创建该实例呢?
Java中提供Class.forName(String className)从一个字符串(含包的类全名称)加载一个类,再利用newInstance方法创建该类的实例。
当然,也可以先获取一个类的Constructor,再调用其newInstance方法创建对象。不同的是,constructor对象的newInstance方法需要传递一个对象数组作为构造方法的参数列表。
② 获取/设置字段值
利用Reflection API获取或者设置字段的值,首先要得到Class对象,然后利用Class对象的getField方法取得相应的字段Field对象,然后调用Field对象对应的getXXX/setXXX方法获取或者设置属性的值。Filed对象提供getInt/setInt、getLong/setLong等方法对基本类型的属性进行值的获取和设置,可以直接使用get/set方法获取复杂类型属性的值(返回一个对象值/传递一个对象值作为参数)。
具体操作方法见如下代码片段。当然,若试图获取/设置一个非public的字段值(getField方法也不能获取非pubic的属性对象,可以尝试使用getDeclaredField方法),将产生IllegalAccessException,可以通过setAccessible(true)使非public的字段可见,然后对字段值进行访问。
③ 调用方法
方法调用的过程类似于设置字段值的过程,首先要得到Class对象,然后调用getMethod方法得到方法Method的对象,getMethod方法要求传递两个参数,一个是方法名,第二个是Class[],即方法参数列表中各参数对应的类型的数组。然后执行Method对象的invoke方法,进行方法的调用,invoke方法需要传递两个参数,第一个是方法绑定的对象,第二个是方法的参数列表。如果是static的方法,则第一个参数将自动被忽略(可为null)。
同理,对于非public的方法,可以使用getDeclaredMethod方法获取Method对象,并使用setAccessible设置其可见性。
3. 其他操作
① 操作数组
反射API中对数组的操作方式不同于一般的java对象,需要通过专门的java.lang.reflect.Array工具类进行实现。Array类提供了创建和操作数组中元素的方法。Array.newInstance方法用来创建新数组,第一个参数为数组中元素的类型,后面的参数为数组各维度的长度(newInstance为变长参数的方法)。
② 关于权限和异常
使用Java反射API的一个重要好处是可以绕过Java语言中默认的访问控制权限。Constructor、Filed和Method都继承自java.lang.reflect.AccessibleObject,其中的setAccessible方法可以用于设置是否绕过默认的权限检查,否则,访问非public的方法或者字段将产生IllegalAccessException异常。
而在利用Invoke方法来调用方法是,如果方法本身抛出异常,invoke方法将抛出InvocationTargetException异常,通过getCause方法可以获取实际的异常信息。
在Java7中,为所有与反射相关的异常类添加了一个统一的父类java.lang.ReflectiveOperationException,在处理反射相关的异常时可以直接捕获该异常而不用分别捕获各个异常子类。
关于Java动态性,除了反射机制外,Java7中提供了一种新的动态调用Java程序的方法,即方法句柄MethodHandler,有些类似于C中的函数指针。此部分内容将在后续的文章中进行单独的分析。
附件中包含了该文章的测试代码。
Java中以反射(reflection)API实现了Java语言的自省,关于自省和反射的区别,笔者无法给出明确的定义。反射API使得Java语言更易实现运行时的动态性,获取Java程序在运行时刻的内部结构,如Java列中的构造方法、域和方法等。反射API的主要功能包括:
① 确定一个对象的类
② 取出类的修饰符(modifiers),字段,方法,构造器和超类
③ 找出某个接口里定义的常量和方法说明
④ 创建一个类实例,这个实例在运行时刻才有名字(运行时间才生成的对象)
⑤ 取得和设定对象数据成员的值,如果数据成员名是运行时刻确定的也能做倒。
⑥ 在运行时刻调用动态对象的方法
⑦ 创建数组,数组大小和类型在运行时刻才确定。也能更改数组成员的值。
1. 对类的分析
① 获取对象的类(Class对象)
Class c = obj.getClass();
② 获取一个类的超类
Class sc = c.getSuperclass();
下面一段代码打印一个对象所有的父类
// 分析类的父类 public static void printSuperclasses(Object o) { Class subclass = o.getClass(); Class superclass = subclass.getSuperclass(); while (superclass != null) { String className = superclass.getName(); System.out.println(className); subclass = superclass; superclass = subclass.getSuperclass(); } }
③ 获取类的名字
String name = c.getName();
// 分析对象的类 public static void classAnalysis(Object obj) { // 获得对象的类 Class c = obj.getClass(); System.out.println(obj + " is instance of class: " + c.getName()); // 获得对象所属类的父类 Class sc = c.getSuperclass(); System.out.println(c.getName() + " inherits from class: " + sc.getName()); }
④ 获取类的修饰符
int m = c.getModifiers();
此处getModifiers()方法返回的是个整型的结果,结果m并不具体表示哪一种修饰符。先看java.lang.reflect.Modifier类,该类对修饰符进行了相应的包装。
类Modifier中定义了一系列的类描述符常量:Modifier.ABSTRACT、Modifier.FINAL、Modifier.PUBLIC等。getModifiers()方法返回的即是类的所有修饰符分别对应的整型值的或运算结果,如public abstract class MyClass{},new MyClass().getClass().getModfiers()即得到Modifier.ABSTRACT | Modifier.PUBLIC。
同时Modifier类提供了一些列的方法,用于判断getModifiers()返回的整型数字中包含的类修饰符:Modifier.isPublic(m)、Modifier.isAbstract(m)、Modifier.isFinal(m)等。
// 分析类的修饰符 public static void printModifiers(Object obj) { System.out.print("Modifier: "); Class c = obj.getClass(); int m = c.getModifiers(); if (Modifier.isPublic(m)) System.out.print("public "); if (Modifier.isAbstract(m)) System.out.print("abstract "); if (Modifier.isFinal(m)) System.out.print("final "); System.out.println(c.getName()); }
⑤ 确定一个类实现的接口
Class[] theInterfaces = c.getInterfaces();
从上面的语句中可以看到,接口在反射的API中也是用Class表示的,接口是一种特殊的抽象类。可以使用Class类的isInterface()方法判断一个Class类型是一个类还是接口。
// 分析类的接口 public static void printInterfaceNames(Object o) { Class c = o.getClass(); Class[] theInterfaces = c.getInterfaces(); for (int i = 0; i < theInterfaces.length; i++) { String interfaceName = theInterfaces[i].getName(); System.out.println(interfaceName); } }
// 判断接口 public static void verifyInterface(Class c) { String name = c.getName(); if (c.isInterface()) { System.out.println(name + " 是接口."); } else { System.out.println(name + " 是类."); } }
⑥ 获取类的字段
一个类的字段(Field)可能来自本类、父类、实现的接口或者接口的接口,可以使用Class对象的getFields()方法获取类中所有的public属性的字段的数组(Field对象数组)。Field对象提供方法取得字段的名字、类型和描述符,甚至可以给字段赋值或者取字段的值。当然,Class类对象提供了getDeclaredFields()方法获取包括public属性在内的所有在类中声明了的域。
// 分析类的字段 public static void printFieldNames(Object o) { Class c = o.getClass(); //public的字段 Field[] publicFields = c.getFields(); for (int i = 0; i < publicFields.length; i++) { String fieldName = publicFields[i].getName(); Class typeClass = publicFields[i].getType(); String fieldType = typeClass.getName(); System.out.println("字段名: " + fieldName + ", 类型: " + fieldType); } //所有的字段 Field[] allFields = c.getDeclaredFields(); for (int i = 0; i < allFields.length; i++) { String fieldName = allFields[i].getName(); Class typeClass = allFields[i].getType(); String fieldType = typeClass.getName(); System.out.println("字段名: " + fieldName + ", 类型: " + fieldType); } }
⑦ 获取构造方法
构造方法是在创建类对象时调用的特殊方法,构造方法可以重载,由它们的参数加以区别。调用getConstructors方法可以取得类构造方法的有关信息,这个方法返回一个数组的
Constructor对象。可以用 Constructor对象里的相关方法来确定构造方法的名字、描述符、参数类型和抛出的意外列表。也可以用Constructor.newInstance创建一个新的Constructor对象。
//分析类的构造方法 public static void showConstructors(Object o) { Class c = o.getClass(); Constructor[] theConstructors = c.getConstructors(); for (int i = 0; i < theConstructors.length; i++) { System.out.print(theConstructors[i].getName()); System.out.print(" ( "); Class[] parameterTypes = theConstructors[i].getParameterTypes(); for (int k = 0; k < parameterTypes.length; k++) { String parameterString = parameterTypes[k].getName(); System.out.print(parameterString + " "); } System.out.println(")"); } }
⑧ 获取成员方法
如何找出类的public方法呢?当然是调用getMethods方法。由getMethods方法返回一个数组,数组元素类型是Method对象。方法的名字,类型,参数,描述和抛出的意外都可
以由Method对象的方法来取得。用 Method.invoke 方法自己调用这个方法。
Method类的对象可以通过getName取方法名、getReturnType取返回值的类型、用
getParameterTypes取得参数类型(Class对象)的数组,对每个参数用getName取参数的类型名。
同样,对于非public的方法,可以使用getDeclaredMethods()方法获取。
//分析类中的成员方法 public static void showMethods(Object o) { Class c = o.getClass(); Method[] theMethods = c.getMethods(); for (int i = 0; i < theMethods.length; i++) { String methodString = theMethods[i].getName(); System.out.println("Name: " + methodString); String returnString = theMethods[i].getReturnType().getName(); System.out.println(" Return Type: " + returnString); Class[] parameterTypes = theMethods[i].getParameterTypes(); System.out.print(" Parameter Types:"); for (int k = 0; k < parameterTypes.length; k++) { String parameterString = parameterTypes[k].getName(); System.out.print(" " + parameterString); } System.out.println(); } }
2. 实现程序的动态性
① 创建对象
如果知道一个类型,很容易使用new操作符创建一个类的实例。但是如果在编译时并不知道具体要实例化的是哪个类的对象,如何创建该实例呢?
Java中提供Class.forName(String className)从一个字符串(含包的类全名称)加载一个类,再利用newInstance方法创建该类的实例。
//动态创建类对象 public static Object createObject(String className) { Object object = null; try { Class classDefinition = Class.forName(className); object = classDefinition.newInstance(); } catch (InstantiationException e) { System.out.println(e); } catch (IllegalAccessException e) { System.out.println(e); } catch (ClassNotFoundException e) { System.out.println(e); } return object; }
当然,也可以先获取一个类的Constructor,再调用其newInstance方法创建对象。不同的是,constructor对象的newInstance方法需要传递一个对象数组作为构造方法的参数列表。
//使用Constructor动态创建对象 public static Object createObject(Constructor constructor, Object[] arguments) { System.out.println("Constructor: " + constructor.toString()); Object object = null; try { object = constructor.newInstance(arguments); System.out.println("Object: " + object.toString()); return object; } catch (InstantiationException e) { System.out.println(e); } catch (IllegalAccessException e) { System.out.println(e); } catch (IllegalArgumentException e) { System.out.println(e); } catch (InvocationTargetException e) { System.out.println(e); } return object; }
② 获取/设置字段值
利用Reflection API获取或者设置字段的值,首先要得到Class对象,然后利用Class对象的getField方法取得相应的字段Field对象,然后调用Field对象对应的getXXX/setXXX方法获取或者设置属性的值。Filed对象提供getInt/setInt、getLong/setLong等方法对基本类型的属性进行值的获取和设置,可以直接使用get/set方法获取复杂类型属性的值(返回一个对象值/传递一个对象值作为参数)。
具体操作方法见如下代码片段。当然,若试图获取/设置一个非public的字段值(getField方法也不能获取非pubic的属性对象,可以尝试使用getDeclaredField方法),将产生IllegalAccessException,可以通过setAccessible(true)使非public的字段可见,然后对字段值进行访问。
// 获取字段值 static void getFiledValue(Object o, String filedName) { Class c = o.getClass(); Field filed; Object value; try { filed = c.getField(filedName); // filed = c.getDeclaredField(filedName); // filed.setAccessible(true); //修改字段访问权限 value = filed.get(o); //可使用getInt、getLong等(若知晓字段基本类型) System.out.println(filedName + ": " + value.toString()); } catch (NoSuchFieldException e) { System.out.println(e); } catch (SecurityException e) { System.out.println(e); } catch (IllegalAccessException e) { System.out.println(e); } }
// 修改字段值 public static void setFieldValue(Object o, String filedName, Object value) { Field filed; Class c = o.getClass(); try { filed = c.getField(filedName); // filed = c.getDeclaredField(filedName); // filed.setAccessible(true); //修改字段访问权限 filed.set(o, value); //可使用setInt、setLong等(若知晓字段基本类型) } catch (NoSuchFieldException e) { System.out.println(e); } catch (IllegalAccessException e) { System.out.println(e); } }
③ 调用方法
方法调用的过程类似于设置字段值的过程,首先要得到Class对象,然后调用getMethod方法得到方法Method的对象,getMethod方法要求传递两个参数,一个是方法名,第二个是Class[],即方法参数列表中各参数对应的类型的数组。然后执行Method对象的invoke方法,进行方法的调用,invoke方法需要传递两个参数,第一个是方法绑定的对象,第二个是方法的参数列表。如果是static的方法,则第一个参数将自动被忽略(可为null)。
同理,对于非public的方法,可以使用getDeclaredMethod方法获取Method对象,并使用setAccessible设置其可见性。
//动态调用方法 public static Object callMethod(Object o, String methodName, Class paramsType[], Object paramsValue[]) { Object result = null; Class c; Method method; try { c = o.getClass(); method = c.getMethod(methodName, paramsType); // method = c.getDeclaredMethod(methodName, paramsType); // method.setAccessible(true); result = method.invoke(o, paramsValue); } catch (NoSuchMethodException e) { System.out.println(e); } catch (IllegalAccessException e) { System.out.println(e); } catch (InvocationTargetException e) { System.out.println(e); } return result; }
3. 其他操作
① 操作数组
反射API中对数组的操作方式不同于一般的java对象,需要通过专门的java.lang.reflect.Array工具类进行实现。Array类提供了创建和操作数组中元素的方法。Array.newInstance方法用来创建新数组,第一个参数为数组中元素的类型,后面的参数为数组各维度的长度(newInstance为变长参数的方法)。
// 反射API操作数组 public static void useArray() { String[] names = (String[]) Array.newInstance(String.class, 10); names[0] = "Hello"; Array.set(names, 1, "World"); String str = (String) Array.get(names, 0); int[][][] matrix1 = (int[][][]) Array.newInstance(int.class, 3, 3, 3); matrix1[0][0][0] = 1; int[][][] matrix2 = (int[][][]) Array.newInstance(int[].class, 3, 4); matrix2[0][0] = new int[10]; matrix2[0][1] = new int[3]; matrix2[0][0][1] = 1; }
② 关于权限和异常
使用Java反射API的一个重要好处是可以绕过Java语言中默认的访问控制权限。Constructor、Filed和Method都继承自java.lang.reflect.AccessibleObject,其中的setAccessible方法可以用于设置是否绕过默认的权限检查,否则,访问非public的方法或者字段将产生IllegalAccessException异常。
而在利用Invoke方法来调用方法是,如果方法本身抛出异常,invoke方法将抛出InvocationTargetException异常,通过getCause方法可以获取实际的异常信息。
在Java7中,为所有与反射相关的异常类添加了一个统一的父类java.lang.ReflectiveOperationException,在处理反射相关的异常时可以直接捕获该异常而不用分别捕获各个异常子类。
关于Java动态性,除了反射机制外,Java7中提供了一种新的动态调用Java程序的方法,即方法句柄MethodHandler,有些类似于C中的函数指针。此部分内容将在后续的文章中进行单独的分析。
附件中包含了该文章的测试代码。
- ReflectionDemo.jar (6.5 KB)
- 下载次数: 10
发表评论
-
SpingMVC第一个拦截器未执行BUG分析
2016-07-15 16:24 2417问题描述: SpringMvc项目中使用<mvc: ... -
MySQL JDBC的setFetchSize
2014-12-05 15:09 5362正常情况下MySQL的JDBC是不支持setFetc ... -
Java SE 6 HotSpot虚拟机的垃圾回收机制
2012-10-31 21:25 1272官方资料,关于Java SE 6 HotSpot虚拟机的gar ... -
Java语言的动态性支持(一)
2012-10-26 00:37 12296一、脚本语言的支持 JSR 223中规范了在Java ... -
Java并发程序设计-注解
2012-10-24 10:50 90391. 类Annotation 3个Annotation描述类的 ... -
Java Applet小结
2011-12-03 23:44 73401. Applet基础 在网页 ...
相关推荐
这种探索对于Java开发者来说是一个重要的启示,它提示我们可以在保持Java语言特性的同时,通过合理的机制引入动态性,以适应快速变化的软件开发需求。 文章最后指出,随着计算机时代的发展,编程语言和开发技术也在...
#### 七、动态性 Java支持动态加载类和库,这意味着应用程序可以根据需要动态地扩展其功能。这种特性使得Java程序能够在运行时适应变化的环境,同时也为开发者提供了更大的灵活性。Java的平台独立性也使得它可以在...
### Java语言程序设计第二版习题解答知识点解析 #### 面向对象软件开发方法的重要意义 面向对象的软件开发方法是一种重要的编程范式,它强调以对象为核心的设计思路,将现实世界的实体映射到软件中,使得软件设计...
* 解释型(Interpreted):Java语言的执行采用半编译、半解释的方式,提高了Java语言的可移植性和灵活性。 * 健壮性(Robust):Java语言提供自动垃圾收集来进行内存管理,防止程序员在管理内存时容易产生的错误。 *...
7. 动态语言支持(Dynamic Language Support):Java平台也支持其他动态语言,如JRuby、Groovy等,它们可以在JVM上运行,利用Java的动态性同时享受动态语言的便利。 8. invokedynamic指令:Java 7引入了一种新的...
- Java的实时性支持虽然不如C++强大,但对于大多数应用而言已经足够。 - Java通过优化垃圾回收机制等方式来提高实时性能。 - **1.2.11 Java的状态管理** - Java在状态管理方面优于C++,因为它提供了一种更简洁的...
Java语言概述 Java是一种广泛使用的高级编程语言,由Sun Microsystems的James Gosling、Bill Joy和Eric Schmidt等人在1991年发起的“Green Project”中孕育而生。最初,这个项目的目标是为消费电子产品市场,特别是...
Groovy是一种基于Java平台的动态脚本语言,它在Java开发者中越来越受欢迎,因为它提供了简洁、灵活的语法,以及强大的动态编程能力。Groovy与Java兼容性极佳,可以直接调用Java类库,使得它在Java生态系统中具有广泛...
全国计算机等级考试二级教程针对Java语言程序设计,旨在帮助考生全面掌握Java的基础知识和技能。以下是根据教程大纲和部分内容提炼出的关键知识点: 1. **Java语言特点**: - **简单易学**:语法简洁,避免了C++中...
- **动态性**:支持动态加载类,适应变化的需求。 - **丰富的类库**:Java标准库提供了大量预先编写好的类,方便开发。 - **嵌入浏览器运行**:Applet可以直接在浏览器中运行。 3. **Java的用途** - **Web ...
11. **反射(Reflection)**:Java反射允许在运行时检查类的信息,创建并调用对象,增强了代码的灵活性和动态性。 12. **泛型**:泛型引入了类型参数,增加了代码的类型安全,减少了强制类型转换。 13. **枚举...
#### 二、Java语言特性 Java语言以其独特的特性在众多编程语言中脱颖而出: 1. **简单性**:Java的设计哲学追求简洁,易于理解和学习,即便没有深厚的专业背景也能快速上手。其语法与C++类似,但去除了后者复杂且...
跨平台性(Write Once, Run Anywhere): Java的代码可以在不同的平台上运行,只需编写一次代码,就可以在任何支持Java的设备上执行。这得益于Java虚拟机(JVM),它充当了代码和底层硬件之间的中介。 面向对象: ...
Java语言程序设计是计算机科学领域中的一个重要主题,尤其适合初学者和专业开发者。这门课程主要涵盖Java语言的基础概念和面向对象编程的核心原则。清华大学出版社出版的《JAVA与面向对象程序设计实验指导与习题集》...
2. Java语言的特点:Java语言是一种面向对象的语言,具有平台独立性、简单性、分布式性、多线程性、动态性等特点。 3. Java程序设计的基本结构:Java程序设计的基本结构主要包括类、对象、方法、字段等。类是Java...
#### 二、Java语言基础 1. **应用程序与小应用程序**:Java支持创建独立的应用程序(Application)和嵌入网页的小应用程序(Applet)。应用程序可以直接在用户操作系统上运行,而小应用程序则通过浏览器插件在Web...
Java动态代码执行是一种在...通过学习和掌握Groovy框架,开发者可以充分利用这些特性,提升应用程序的动态性和灵活性。在实际项目中,务必注意安全问题,因为动态代码执行也可能带来潜在的代码注入风险,需要谨慎处理。
* Java语言的跨平台性:Java语言可以在任何有Java虚拟机的机器上执行,不受平台的限制。 Java语言基础是指Java语言的基本概念和特点,Java语言是面向对象的、简单的、安全的、多线程的、解释型的、与平台无关的编程...