一小时搞明白注解处理器(Annotation Processor Tool)
annotationprocessor 用法

annotationprocessor 用法Annotation Processor是Java编译器提供的一个工具,用于处理源代码中的注解,通过生成额外的Java代码来扩展或修改源代码的功能。
它可以用于自动生成代码、进行静态代码分析、生成文档等。
1.使用方式:-在项目的build.gradle文件中,添加annotationProcessor依赖关系。
例如:`annotationProcessor 'com.google.dagger:dagger-compiler:2.38.1'`。
-创建一个继承自javax.annotation.processing.AbstractProcessor的注解处理器,实现其中的方法,如process()和getSupportedAnnotationTypes()等。
-在注解处理器类上使用javax.annotation.processing.AutoService注解,以便注册注解处理器。
-在要处理的代码中添加相应的注解。
-构建项目时,注解处理器将会被自动触发,处理注解并生成对应的代码。
2.拓展:- Annotation Processor可以实现自定义的代码生成逻辑,常见的应用场景包括自动生成代码、实现依赖注入、自动生成文档等。
-使用Annotation Processor可以在编译期间对代码进行静态检查,提高代码质量和可维护性。
- Annotation Processor可以用于减少运行时的反射调用,提高代码的执行效率。
-可以通过注解处理器间接地实现代码的插桩功能,对现有代码进行修改和扩展。
- Annotation Processor还可以与其他工具或库结合使用,如Dagger、ButterKnife等。
总结:Annotation Processor是一种在编译期对注解进行处理的工具,通过生成额外的Java代码来扩展或修改源代码的功能。
它可以自动生成代码、进行静态代码分析、生成文档等,提高代码质量、可维护性和执行效率。
注解Annotation原理详解及其应用示例

注解Annotation原理详解及其应⽤⽰例⼀、什么是注解 注解也叫元数据,例如我们常见的@Override和@Deprecated,注解是JDK1.5版本开始引⼊的⼀个特性,⽤于对代码进⾏说明,可以对包、类、接⼝、字段、⽅法参数、局部变量等进⾏注解。
「ng.annotation.Annotation」接⼝中有这么⼀句话,⽤来描述『注解』。
The common interface extended by all annotation types所有的注解类型都继承⾃这个普通的接⼝(Annotation) 看⼀个 JDK 内置注解的定义:@Target(ElementType.METHOD)@Retention(RetentionPolicy.SOURCE)public @interface Override {} 这是注解 @Override 的定义,其实它本质上就是:public interface Override extends Annotation{} 注解的本质就是⼀个继承了 Annotation 接⼝的接⼝。
⼀个注解准确意义上来说,只不过是⼀种特殊的注释⽽已,如果没有解析它的代码,它可能连注释都不如。
⽽解析⼀个类或者⽅法的注解往往有两种形式:⼀种是编译期直接的扫描,⼀种是运⾏期反射。
编译器的扫描指的是编译器在对 java 代码编译字节码的过程中会检测到某个类或者⽅法被⼀些注解修饰,这时它就会对于这些注解进⾏某些处理。
典型的就是注解 @Override,⼀旦编译器检测到某个⽅法被修饰了 @Override 注解,编译器就会检查当前⽅法的⽅法签名是否真正重写了⽗类的某个⽅法,也就是⽐较⽗类中是否具有⼀个同样的⽅法签名。
这⼀种情况只适⽤于那些编译器已经熟知的注解类,⽐如JDK 内置的⼏个注解,⽽你⾃定义的注解,编译器是不知道你这个注解的作⽤的,当然也不知道该如何处理,往往只是会根据该注解的作⽤范围来选择是否编译进字节码⽂件,仅此⽽已。
Java中的注解(Annotation)

Java中的注解(Annotation)⽬录结构:contents structure [+]1.2.3.1.2.4.1.2.1.什么是注解⽤⼀个词就可以描述注解,那就是元数据,即⼀种描述数据的数据。
所以,可以说注解就是源代码的元数据。
⽐如,下⾯这段代码:@Overridepublic String toString() {return "This is String Representation of current object.";}上⾯的代码中,我重写了toString()⽅法并使⽤了@Override注解。
但是,即使我不使⽤@Override注解标记代码,程序也能够正常执⾏。
那么,该注解表⽰什么?这么写有什么好处吗?事实上,@Override告诉编译器这个⽅法是⼀个重写⽅法(描述⽅法的元数据),如果⽗类中不存在该⽅法,编译器便会报错,提⽰该⽅法没有重写⽗类中的⽅法。
如果我不⼩⼼拼写错误,例如将toString()写成了toStrring(){double r},⽽且我也没有使⽤@Override注解,那程序依然能编译运⾏。
但运⾏结果会和我期望的⼤不相同。
现在我们了解了什么是注解,并且使⽤注解有助于阅读程序。
Annotation是⼀种应⽤于类、⽅法、参数、变量、构造器及包声明中的特殊修饰符。
它是⼀种由JSR-175标准选择⽤来描述元数据的⼀种⼯具。
2.为什么要使⽤注解使⽤Annotation之前(甚⾄在使⽤之后),XML被⼴泛的应⽤于描述元数据。
不知何时开始⼀些应⽤开发⼈员和架构师发现XML的维护越来越糟糕了。
他们希望使⽤⼀些和代码紧耦合的东西,⽽不是像XML那样和代码是松耦合的(在某些情况下甚⾄是完全分离的)代码描述。
假如你想为应⽤设置很多的常量或参数,这种情况下,XML是⼀个很好的选择,因为它不会同特定的代码相连。
如果你想把某个⽅法声明为服务,那么使⽤Annotation会更好⼀些,因为这种情况下需要注解和⽅法紧密耦合起来,开发⼈员也必须认识到这点。
注解是什么

注解是什么?一、注解是什么注解(Annotation)是JDK1.5引入的注释机制,它本身没有任何意义,仅仅是对代码的注释,被修饰的代码不会被影响执行。
但是它和普通的代码注释又不同,可以保留在各个时间段(源码、字节码、运行时),在各个时间段通过不同的技术(APT、字节码增强、反射),做不同的事情。
@Override:检查该方法是否是重写方法,仅保留在源码阶段,编译时判断如果父类和接口中没有该方法,会报错。
二、自定义注解咱们依然拿@Override注解举例,下面是它的源码@Target(ElementType.METHOD)@Retention(RetentionPolicy.SOURCE)public @interface Override {}从上面代码我们看到了三个比较新的东西,@Target、@Retention、@interface,咱们一个个来说2.1 关键字:@interface类使用class关键字修饰、接口使用interface关键字修饰、注解使用@interface关键字修饰。
2.2 元注解:@Target注解是用来注释代码的,而元注解是用来注释注解的,给自定义的注解增加一些限定范围。
@Target:元注解之一,限制注解的使用范围,比如作用在属性、方法还是类上。
接收的是一个数组,可以指定多个范围。
可接收的范围:public enum ElementType {// 类、接口(包括注释类型)或枚举TYPE,// 字段(包括枚举常量)FIELD,// 方法METHOD,// 参数PARAMETER,// 构造方法CONSTRUCTOR,// 局部变量LOCAL_VARIABLE,// 注释类型ANNOTATION_TYPE,// 包PACKAGE}// 单个范围,@Override仅可用在方法上@Target(ElementType.METHOD)public @interface Override {}// 多个范围,@Test可使用在构造方法和方法上@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})public @interface Test {}2.3 元注解:@Retention@Retention:元注解之一,保留级别,设置该注解代码可以保留到什么阶段。
java注解的实现原理及实际运用

java注解的实现原理及实际运用Java注解是一种元数据的形式,可以在Java代码中以注解的形式给程序中的元素(类、方法、变量等)添加额外的信息。
它们被编译器读取,并在编译期或运行期根据这些注解做一些特定的处理。
Java注解的实现原理:Java注解的实现原理主要涉及两个关键的技术:反射和注解处理器。
1.反射(Reflection):Java的反射机制可以在运行时动态地获取类的信息,并且可以通过反射机制在运行时操作类的属性、方法和构造方法等。
Java注解利用了反射机制来读取程序中的注解信息。
2.注解处理器(Annotation Processor):注解处理器是Java编译器的一部分,用于在编译期读取源代码中的注解信息,并根据注解做一些特定的处理。
注解处理器可以通过反射机制来读取和处理注解信息,生成额外的辅助文件或者对代码进行修改。
Java注解的实际运用:Java注解在实际开发中有广泛的应用,下面介绍一些常见的使用场景:1.测试框架:JUnit是一个常用的单元测试框架,它通过使用注解来标记测试方法和测试类,使得编写和运行测试代码更加简单。
2.依赖注入(Dependency Injection):Spring框架是一个使用广泛的Java企业级开发框架,它通过使用注解来实现依赖注入,避免了繁琐的手动配置。
3.数据库映射:MyBatis和Hibernate等ORM框架使用注解来标记实体类和数据库表之间的映射关系,简化了数据库操作的代码。
4. Web开发:JavaWeb框架(如SpringMVC)使用注解来标记Controller类和请求处理方法,简化了URL路由和请求参数的绑定。
5.并发编程:Java的并发编程库(如java.util.concurrent包)提供了一些注解来实现线程安全的编程,例如@ThreadSafe和@Immutable。
6.安全检查:Java的安全框架(如Spring Security)使用注解来定义权限控制规则,方便开发人员对系统进行安全管理。
android library中使用annotationprocessor -回复

android library中使用annotationprocessor -回复Android Library中使用Annotation Processor在Android开发中,我们经常会使用一些第三方库来帮助我们提高开发效率和功能实现。
其中,Annotation Processor是一种强大的工具,它可以在编译期间自动处理注解,并生成相关的代码。
本文将一步一步回答有关在Android Library中使用Annotation Processor的问题,以帮助开发者更好地理解和使用这一工具。
什么是Annotation Processor?Annotation Processor是Java编译器的一部分,它可以扫描源代码中的注解,并生成相应的代码。
在Android开发中,我们可以使用Annotation Processor来实现一些自动化的任务,例如生成代码、做静态检查等。
为什么要使用Annotation Processor?在Android开发中,使用Annotation Processor有许多好处。
首先,它可以帮助我们减少重复的工作。
通过自动生成一些样板代码,它可以简化开发过程,并提高代码的可读性和可维护性。
其次,Annotation Processor 可以在编译期对代码进行一些静态检查,帮助我们发现潜在的错误。
最后,它还可以帮助我们实现一些复杂的功能,例如依赖注入、序列化等。
如何使用Annotation Processor?在Android开发中,使用Annotation Processor的第一步是引入相关的依赖。
我们可以在项目的build.gradle文件中添加如下配置:groovydependencies {implementation 'com.google.auto.service:auto-service:1.0-rc6' annotationProcessor'com.google.auto.service:auto-service:1.0-rc6'}其中,auto-service是一个帮助我们自动生成META-INF/services下的文件的库。
Java注解(2)-注解处理器(运行时RetentionPolicy.RUNTIME)
Java注解(2)-注解处理器(运⾏时RetentionPolicy.RUNTIME)如果没有⽤来读取注解的⼯具,那注解将基本没有任何作⽤,它也不会⽐注释更有⽤。
读取注解的⼯具叫作注解处理器。
Java提供了两种⽅式来处理注解:第⼀种是利⽤运⾏时反射机制;另⼀种是使⽤Java提供的API来处理编译期的注解。
反射机制⽅式的注解处理器仅当定义的注解的@Retention为RUNTIME时,才能够通过运⾏时的反射机制来处理注解。
下⾯结合例⼦来说明这种⽅式的处理⽅法。
Java中的反射API(如ng.Class、ng.reflect.Field等)都实现了接⼝ng.reflect.AnnotatedElement,来提供获取类、⽅法和域上的注解的实⽤⽅法。
通过JavaBean上定义的注解来⽣成相应的SQL。
1.1、定义注解1.1.1、类注解映射表名package com.zenfery.example.annotation.sql;import ng.annotation.ElementType;import ng.annotation.Retention;import ng.annotation.RetentionPolicy;import ng.annotation.Target;@Target(ElementType.TYPE)//定义注解应⽤于类@Retention(RetentionPolicy.RUNTIME)//定义注解在JVM运⾏时保留public@interface TableSQL {String value() default"";//指定对应的表名}定义注解@TableSQL,只定义⼀个value值来映射表名,默认值为空,如果程序不给此值,将使⽤类名(⼩写)来作为表名。
1.1.2、属性与字段对应注解package com.zenfery.example.annotation.sql;import ng.annotation.ElementType;import ng.annotation.Retention;import ng.annotation.RetentionPolicy;import ng.annotation.Target;@Target(ElementType.FIELD)//定义注解应⽤于成员变量@Retention(RetentionPolicy.RUNTIME)//定义注解在JVM运⾏时保留public@interface TableColumnSQL {String value() default"";Constraint constraint() default@Constraint();}定义注解@TableColumnSQL的⽬标为FIELD,仅能在类的属性上使⽤;value()属性定义对应的字段名;constraint()定义字段的约束,它是由注解@Constraint定义,其定义如下:package com.zenfery.example.annotation.sql;import ng.annotation.ElementType;import ng.annotation.Retention;import ng.annotation.RetentionPolicy;import ng.annotation.Target;@Target(ElementType.FIELD)//定义注解应⽤于成员变量@Retention(RetentionPolicy.RUNTIME)//定义注解在JVM运⾏时保留public@interface Constraint {boolean allowNull() default true; //是否允许为空boolean isPrimary() default false; //是否为主键}@Constraint注解仅定义了两个注解元素,allowNull()指定字段是否允许为空值;isPrimary()指定字段是否是主键。
java 类加载完成后自动调用的方法
java 类加载完成后自动调用的方法在Java编程语言中,类加载是一个非常重要的过程,而当类被加载完成后,有时我们需要执行一些初始化操作。
本文将详细介绍Java类加载完成后自动调用的方法,帮助读者更好地理解这一机制。
在Java中,当类被加载完成后,有几个特殊的方法会被自动调用,主要包括以下几种:1.静态代码块(Static Block)静态代码块是位于类定义中,且被static关键字修饰的代码块。
它在类加载时执行,并且只会执行一次。
通常用于初始化静态变量或者执行一些只需要执行一次的代码。
```javapublic class MyClass {static {System.out.println("静态代码块执行");}public static void main(String[] args) {MyClass myClass1 = new MyClass();MyClass myClass2 = new MyClass();}}```在上面的例子中,尽管我们创建了两个MyClass对象,但静态代码块只执行了一次。
2.初始化方法(Initializer)除了静态代码块,还可以通过在类中定义一个初始化方法(无返回值且方法名与类名相同),并在该方法中进行初始化操作。
```javapublic class MyClass {public MyClass() {System.out.println("构造方法执行");}{System.out.println("非静态代码块执行");}public static void main(String[] args) {MyClass myClass = new MyClass();}}```在这个例子中,非静态代码块(没有static修饰的代码块)在每次创建对象时都会执行。
3.构造方法(Constructor)构造方法是用于创建对象时初始化对象的方法。
java注解实现原理
Java注解的实现原理1. 什么是Java注解Java注解(Annotation)是一种用于向程序中添加元数据(metadata)的标记。
它可以在编译、运行时被读取和使用,用来为程序元素(类、方法、字段等)提供额外的信息。
Java注解具有以下特点: - 注解以@符号开头,紧跟着注解名称。
- 注解可以拥有多个元素,每个元素都具有一个名称和一个值。
- 注解可以被应用在类、方法、字段、参数等程序元素上。
Java内置了许多常用的注解,比如@Override用于标识方法重写父类方法,@Deprecated用于标识已过时的代码。
此外,开发者也可以自定义注解以满足特定需求。
2. Java注解的分类根据注解的作用范围和生命周期,Java注解可以分为三类: - 源码级别(Source Level):这些注解只存在于源码中,在编译后会被编译器抛弃。
- 编译时级别(Class Level):这些注解在编译过程中会被保留在字节码文件中,并可被反射读取。
- 运行时级别(Runtime Level):这些注解在运行时会被保留,并可通过反射读取和使用。
3. 注解的实现原理Java注解的实现原理涉及到三个主要的组成部分:注解定义、注解处理器和反射机制。
3.1 注解定义注解是通过@interface关键字来定义的,其本质是一种特殊的接口。
注解可以包含多个元素,每个元素都可以指定默认值。
public @interface MyAnnotation {String value() default "";int count() default 0;}3.2 注解处理器注解处理器(Annotation Processor)是编译器或其他工具用来处理注解的程序。
它会扫描源码中的注解,并根据注解提供的信息生成相应的代码或执行特定操作。
在Java中,注解处理器通常是通过APT(Annotation Processing Tool)来实现的。
注解的方法
注解的方法近年来,注解作为一种强大的代码标记方法,已经成为了程序员不可或缺的一部分。
注解可以为代码提供很多便利,例如在代码中添加额外的信息,提供运行时支持,并且可以在编译时执行某些操作。
本文将介绍注解的一些方法和使用方式,希望能够帮助读者更好地理解和使用注解。
一、定义注解定义注解是最基本的步骤。
在Java中,注解被定义为一种特殊的接口。
在这个接口中,可以定义一些属性,并为这些属性指定默认值。
以下是一个简单的注解定义示例:```java public @interface TestAnnotation{ String value() default "default"; } ```这个注解定义了一个属性value,它的默认值是"default"。
注意,注解的定义需要加上@符号。
二、使用注解定义好注解之后,就可以在代码中使用它了。
在Java 中,注解可以被用于类、方法、变量以及包等元素上。
例如:```java @TestAnnotation(value = "test") publicclass MyClass { @TestAnnotation(value = "test") public void myMethod() { @TestAnnotation String str = "test"; } } ```在这个例子中,注解@TestAnnotation被用于了类、方法和变量上。
注意,当注解只有一个属性时,可以省略属性名。
三、编译时处理注解编译器在编译Java源代码时可以处理注解,并根据注解来生成代码。
这个过程称为注解处理(annotation processing)。
Java提供了一套注解处理API,可以在编译时扫描代码中的注解,并生成新的Java代码。
这些生成的代码可以是配置文件、服务端代码、客户端代码、代理类等等。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
一小时搞明白注解处理器(AnnotationProcessor Tool)什么是注解处理器?注解处理器是(Annotation Processor)是javac的一个工具,用来在编译时扫描和编译和处理注解(Annotation)。
你可以自己定义注解和注解处理器去搞一些事情。
一个注解处理器它以Java代码或者(编译过的字节码)作为输入,生成文件(通常是java文件)。
这些生成的java文件不能修改,并且会同其手动编写的java代码一样会被javac编译。
看到这里加上之前理解,应该明白大概的过程了,就是把标记了注解的类,变量等作为输入内容,经过注解处理器处理,生成想要生成的java代码。
处理器AbstractProcessor处理器的写法有固定的套路,继承AbstractProcessor。
如下:[java] view plain copy 在CODE上查看代码片派生到我的代码片public class MyProcessor extends AbstractProcessor {@Overridepublic synchronized void init(ProcessingEnvironment processingEnv) {super.init(processingEnv);}@Overridepublic Set<String> getSupportedAnnotationTypes() {return null;}@Overridepublic SourceVersion getSupportedSourceVersion() {return testSupported();}@Overridepublic boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {return true;}}init(ProcessingEnvironment processingEnv) 被注解处理工具调用,参数ProcessingEnvironment 提供了Element,Filer,Messager等工具getSupportedAnnotationTypes() 指定注解处理器是注册给那一个注解的,它是一个字符串的集合,意味着可以支持多个类型的注解,并且字符串是合法全名。
getSupportedSourceVersion 指定Java版本process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) 这个也是最主要的,在这里扫描和处理你的注解并生成Java代码,信息都在参数RoundEnvironment 里了,后面会介绍。
在Java7 中还可以使用[java] view plain copy 在CODE上查看代码片派生到我的代码片@SupportedSourceVersion(testSupported())@SupportedAnnotationTypes({// 合法注解全名的集合})代替getSupportedSourceVersion() 和getSupportedAnnotationType() ,没毛病,还可以在注解处理离器中使用注解。
注册注解处理器打包注解处理器的时候需要一个特殊的文件javax.annotation.processing.Processor 在META-INF/services 路径下[plain] view plain copy 在CODE上查看代码片派生到我的代码片--myprcessor.jar----com------example--------MyProcessor.class----META-INF------services--------javax.annotation.processing.Processor打包进javax.annotation.processing.Processor的内容是处理器的合法全称,多个处理器之间换行。
[plain] view plain copy 在CODE上查看代码片派生到我的代码片com.example.myprocess.MyProcessorAcom.example.myprocess.MyProcessorBgoogle提供了一个注册处理器的库[plain] view plain copy 在CODE上查看代码片派生到我的代码片compile 'com.google.auto.service:auto-service:1.0-rc2'一个注解搞定:[java] view plain copy 在CODE上查看代码片派生到我的代码片@AutoService(Processor.class)public class MyProcessor extends AbstractProcessor {...}读到这里ButterKnife用到的知识点我们都已经了解了1.自定义注解2.用注解处理器解析注解3.解析完成后生成Java文件BufferKnife使用:[java] view plain copy 在CODE上查看代码片派生到我的代码片public class MainActivity extends AppCompatActivity {@Bind(R.id.rxjava_demo)Button mRxJavaDemo;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(yout.activity_main);ButterKnife.bind(this);mRxJavaDemo.setText("Text");}}然后我们编译一下,打开路径:/app/build/intermediates/classes/release/com/ming/rxdemo/MainActivity$$ViewBinder.class这就是我们生成的Java文件,可以看到Button已经在bind里面初始化了。
[java] view plain copy 在CODE上查看代码片派生到我的代码片public class MainActivity$$ViewBinder<T extends MainActivity> implements ViewBinder<T> { public MainActivity$$ViewBinder() {}public void bind(Finder finder, T target, Object source) {View view = (View)finder.findRequiredView(source, 2131492944, "field \'mRxJavaDemo\'");target.mRxJavaDemo = (Button)finder.castView(view, 2131492944, "field \'mRxJavaDemo\'");}public void unbind(T target) {target.mRxJavaDemo = null;}}接下来我们创建一个项目,写一个简单的用注解绑定控件的例子项目结构[plain] view plain copy 在CODE上查看代码片派生到我的代码片--apt-demo----bindview-annotation(Java Library)----bindview-api(Android Library)----bindview-compiler(Java Library)----app(Android App)bindview-annotation 注解声明bindview-api 调用Android SDK APIbindview-compiler 注解处理器相关app 测试App1.在bindview-annotation 下创建一个@BindView注解,该注解返回一个值,整型,名字为value,用来表示控件ID。
[java] view plain copy 在CODE上查看代码片派生到我的代码片@Target(ElementType.FIELD)@Retention(RetentionPolicy.CLASS)public @interface BindView {/*** 用来装id** @return*/int value();}2.在bindview-compiler 中创建注解处理器BindViewProcessor 并注册,做基本的初始化工作。
[java] view plain copy 在CODE上查看代码片派生到我的代码片@AutoService(Processor.class)public class BindViewProcessor extends AbstractProcessor {/*** 文件相关的辅助类*/private Filer mFiler;/*** 元素相关的辅助类*/private Elements mElementUtils;/*** 日志相关的辅助类*/private Messager mMessager;/*** 解析的目标注解集合*/private Map<String, AnnotatedClass> mAnnotatedClassMap = new HashMap<>();@Overridepublic synchronized void init(ProcessingEnvironment processingEnv) {super.init(processingEnv);mElementUtils = processingEnv.getElementUtils();mMessager = processingEnv.getMessager();mFiler = processingEnv.getFiler();}@Overridepublic Set<String> getSupportedAnnotationTypes() {Set<String> pes = new LinkedHashSet<>();types.add(BindView.class.getCanonicalName());//返回该注解处理器支持的注解集合return types;}@Overridepublic SourceVersion getSupportedSourceVersion() {return testSupported();}@Overridepublic boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {return true;}}是不是注意到了里面有个Map容器,而且类型是AnnotatedClass,这是干啥的呢?这个很好理解,我们在解析XML,解析Json的时候数据解析完之后是不是要以对象的形式表示出来,这里也一样,@BindView用来标记类成员,一个类下可以有多个成员,好比一个Activity 中可以有多个控件,一个容器下有多个控件等。