内容大纲
反射
注解
泛型
反射
参考文章:
https://blog.csdn.net/sinat_38259539/article/details/71799078
https://www.cnblogs.com/ysocean/p/6516248.html
https://www.jianshu.com/p/f67182a482eb
什么是反射?
Java反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。而这也是Java被视为动态(或准动态,为啥要说是准动态,因为一般而言的动态语言定义是程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。)语言的一个关键性质。
Java反射机制是指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
用一句话总结就是反射可以实现在运行时可以知道任意一个类的属性和方法。
作用:能够做一般做不到的事情。使用场景:插件式换肤 ,插件式开发 apk(没有安装插件)
所有的反射功能都是基于我们字节码(class),一个类的 class 在内存中应该只有一份,而且 class 其实也是一个对象 Class
反射的优缺点
-
优点
可以实现动态创建对象和编译,体现出很大的灵活性,特别是在J2EE的开发中它的灵活性就表现的十分明显。比如,一个大型的软件,不可能一次就把把它设计的很完美,当这个程序编译后,发布了,当发现需要更新某些功能时,我们不可能要用户把以前的卸载,再重新安装新的版本,假如这样的话,这个软件肯定是没有多少人用的。采用静态的话,需要把整个程序重新编译一次才可以实现功能的更新,而采用反射机制的话,它就可以不用卸载,只需要在运行时才动态的创建和编译,就可以实现该功能。 -
缺点
对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于只直接执行相同的操作。
获取 Class 的三种方式
public class TestPerson {
public static void main(String[] args) throws ClassNotFoundException {
//1、通过对象调用 getClass() 方法来获取,通常应用在:比如你传过来一个 Object
// 类型的对象,而我不知道你具体是什么类,用这种方法
Person p1 = new Person();
Class<? extends Person> clazz1 = p1.getClass();
//2、直接通过 类名.class 的方式得到,该方法最为安全可靠,程序性能更高
// 这说明任何一个类都有一个隐含的静态成员变量 class
Class<Person> clazz2 = Person.class;
//3、通过 Class 对象的 forName() 静态方法来获取,用的最多,
// 但可能抛出 ClassNotFoundException 异常
Class<?> clazz3 = Class.forName("com.ivyzh.basicknowledge.reflect.Person");
}
}
Java反射相关操作
- 获取构造器
- 获取成员变量
- 获取成员方法
代码:
Person类
public class Person {
//私有属性
private String name = "Tom";
//公有属性
public int age = 18;
boolean isMan = true;
protected char sex;
//默认构造方法
Person() {
System.out.println("调用了默认构造方法。。。");
}
//无参构造方法
public Person(String name) {
this.name = name;
System.out.println("调用了公有、有一个参数构造方法执行了。。。");
}
//有多个参数的构造方法
private Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("调用了私有、有两个参数构造方法执行了。。。" + "姓名:" + name + "年龄:" + age);
}
//有多个参数的构造方法
protected Person(String name, int age, boolean isMan) {
this.name = name;
this.age = age;
System.out.println("调用了保护、有三个参数构造方法执行了。。。" + "姓名:" + name + "年龄:" + age + ",性别:" + isMan);
}
//私有方法
private void say() {
System.out.println("private say()...");
}
private void say(String msg) {
System.out.println("private say()..." + msg);
}
//公有方法
public void work() {
System.out.println("public work()...");
}
public void work(String what) {
System.out.println("public work()..." + what);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
测试类:
public class TestPerson {
public static void main(String[] args) throws Exception {
//#######################获取Class对象的三种方式#######################
System.out.println("#######################获取Class对象的三种方式#######################");
//1、通过对象调用 getClass() 方法来获取,通常应用在:比如你传过来一个 Object
// 类型的对象,而我不知道你具体是什么类,用这种方法
Person p1 = new Person();//调用了默认构造方法。。。
Class<? extends Person> clazz1 = p1.getClass();
//2、直接通过 类名.class 的方式得到,该方法最为安全可靠,程序性能更高
// 这说明任何一个类都有一个隐含的静态成员变量 class
Class<Person> clazz2 = Person.class;
//3、通过 Class 对象的 forName() 静态方法来获取,用的最多,
// 但可能抛出 ClassNotFoundException 异常
Class<?> clazz3 = Class.forName("com.ivyzh.basicknowledge.reflect.Person");
//#######################通过反射获取构造方法并使用#######################
System.out.println("#######################通过反射获取构造方法并使用#######################");
// 1.所有公有构造方法
Constructor<?>[] constructors = clazz3.getConstructors();
for (Constructor c : constructors) {
System.out.println(c);//public com.ivyzh.basicknowledge.reflect.Person(java.lang.String)
}
System.out.println("---------------------");
// 2.所有的构造方法(包括:私有、受保护、默认、公有)
constructors = clazz3.getDeclaredConstructors();
for (Constructor c : constructors) {
System.out.println(c);
/*
protected com.ivyzh.basicknowledge.reflect.Person(java.lang.String,int,boolean)
private com.ivyzh.basicknowledge.reflect.Person(java.lang.String,int)
public com.ivyzh.basicknowledge.reflect.Person(java.lang.String)
com.ivyzh.basicknowledge.reflect.Person()
*/
}
System.out.println("---------------------");
// 3. 获取无参数构造器 - 默认修饰符
Constructor<?> constructor = clazz3.getDeclaredConstructor(null);
System.out.println(constructor);
Object obj = constructor.newInstance();
System.out.println(obj);//Person{name='Tom', age=18}
System.out.println(obj instanceof Person);//true
System.out.println("---------------------");
// 4. 获取一个参数构造器 - public修饰符
constructor = clazz3.getConstructor(String.class);
obj = constructor.newInstance("Spring");
System.out.println(obj);//Person{name='Spring', age=18}
System.out.println("---------------------");
// 5. 获取两个参数构造器 - private修饰符
constructor = clazz3.getDeclaredConstructor(String.class, int.class);// 注意这里不能用Integer.class
constructor.setAccessible(true);
obj = constructor.newInstance("Summer", 22);
System.out.println(obj);//Person{name='Summer', age=22}
System.out.println("---------------------");
// 6. 获取三个参数构造器 - protect修饰符
constructor = clazz3.getDeclaredConstructor(String.class, int.class, boolean.class);// 注意这里不能用Integer.class
constructor.setAccessible(true);
obj = constructor.newInstance("Autumn", 24, false);
System.out.println(obj);//Person{name='Autumn', age=24}
//#######################获取成员变量并调用#######################
System.out.println("#######################获取成员变量并调用#######################");
// 1.获取所有公有的字段
Field[] fields = clazz3.getFields();
for (Field f : fields) {
System.out.println(f);//public int com.ivyzh.basicknowledge.reflect.Person.age
}
System.out.println("---------------------");
//2. 获取所有的字段(包括私有、受保护、默认的)
fields = clazz3.getDeclaredFields();
for (Field f : fields) {
System.out.println(f);
/* private java.lang.String com.ivyzh.basicknowledge.reflect.Person.name
public int com.ivyzh.basicknowledge.reflect.Person.age
boolean com.ivyzh.basicknowledge.reflect.Person.isMan
protected char com.ivyzh.basicknowledge.reflect.Person.sex*/
}
System.out.println("---------------------");
// 3.获取公有字段并调用
Field age = clazz3.getField("age");
System.out.println(age);//public int com.ivyzh.basicknowledge.reflect.Person.age
constructor = clazz3.getDeclaredConstructor(null);
obj = constructor.newInstance();
Person p2 = (Person) obj;
System.out.println("p2.age:" + p2.age);//p2.age:18
age.set(p2, 22);
System.out.println("change p2.age:" + p2.age);//change p2.age:22
System.out.println("---------------------");
// 4.获取私有字段并调用
Field name = clazz3.getDeclaredField("name");
name.setAccessible(true);
name.set(p2, "Jane");
System.out.println("p2." + p2);//p2.Person{name='Jane', age=22}
//#######################获取成员方法并调用#######################
System.out.println("#######################获取成员方法并调用#######################");
//1.获取所有"公有方法";(包含了父类的方法也包含Object类)
Method[] methods = clazz3.getMethods();
for (Method m : methods) {
System.out.println(m);
/*public java.lang.String com.ivyzh.basicknowledge.reflect.Person.toString()
public void com.ivyzh.basicknowledge.reflect.Person.work()
public void com.ivyzh.basicknowledge.reflect.Person.work(java.lang.String)
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()*/
}
System.out.println("---------------------");
//2.获取所有的成员方法,包括私有的(不包括继承的)
methods = clazz3.getDeclaredMethods();
for (Method m : methods) {
System.out.println(m);
/*public java.lang.String com.ivyzh.basicknowledge.reflect.Person.toString()
private void com.ivyzh.basicknowledge.reflect.Person.say()
private void com.ivyzh.basicknowledge.reflect.Person.say(java.lang.String)
public void com.ivyzh.basicknowledge.reflect.Person.work()
public void com.ivyzh.basicknowledge.reflect.Person.work(java.lang.String)*/
}
System.out.println("---------------------");
//3.获取公有的work()方法
Method work = clazz3.getMethod("work");
work.invoke(p2); //public work()...
//4.获取公有的 work(String what)方法
work = clazz3.getMethod("work", String.class);
work.invoke(p2, "学习");//public work()...学习
//5.获取私有的say()方法
Method say = clazz3.getDeclaredMethod("say");
say.setAccessible(true);
say.invoke(p2);//private say()...
//5.获取私有的say(String msg)方法
say = clazz3.getDeclaredMethod("say", String.class);
say.setAccessible(true);
say.invoke(p2, "唱歌");//private say()...唱歌
}
}
控制台输出:
#######################获取Class对象的三种方式#######################
调用了默认构造方法。。。
#######################通过反射获取构造方法并使用#######################
public com.ivyzh.basicknowledge.reflect.Person(java.lang.String)
---------------------
protected com.ivyzh.basicknowledge.reflect.Person(java.lang.String,int,boolean)
private com.ivyzh.basicknowledge.reflect.Person(java.lang.String,int)
public com.ivyzh.basicknowledge.reflect.Person(java.lang.String)
com.ivyzh.basicknowledge.reflect.Person()
---------------------
com.ivyzh.basicknowledge.reflect.Person()
调用了默认构造方法。。。
Person{name='Tom', age=18}
true
---------------------
调用了公有、有一个参数构造方法执行了。。。
Person{name='Spring', age=18}
---------------------
调用了私有、有两个参数构造方法执行了。。。姓名:Summer年龄:22
Person{name='Summer', age=22}
---------------------
调用了保护、有三个参数构造方法执行了。。。姓名:Autumn年龄:24,性别:false
Person{name='Autumn', age=24}
#######################获取成员变量并调用#######################
public int com.ivyzh.basicknowledge.reflect.Person.age
---------------------
private java.lang.String com.ivyzh.basicknowledge.reflect.Person.name
public int com.ivyzh.basicknowledge.reflect.Person.age
boolean com.ivyzh.basicknowledge.reflect.Person.isMan
protected char com.ivyzh.basicknowledge.reflect.Person.sex
---------------------
public int com.ivyzh.basicknowledge.reflect.Person.age
调用了默认构造方法。。。
p2.age:18
change p2.age:22
---------------------
p2.Person{name='Jane', age=22}
#######################获取成员方法并调用#######################
public java.lang.String com.ivyzh.basicknowledge.reflect.Person.toString()
public void com.ivyzh.basicknowledge.reflect.Person.work()
public void com.ivyzh.basicknowledge.reflect.Person.work(java.lang.String)
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
---------------------
public java.lang.String com.ivyzh.basicknowledge.reflect.Person.toString()
private void com.ivyzh.basicknowledge.reflect.Person.say()
private void com.ivyzh.basicknowledge.reflect.Person.say(java.lang.String)
public void com.ivyzh.basicknowledge.reflect.Person.work()
public void com.ivyzh.basicknowledge.reflect.Person.work(java.lang.String)
---------------------
public work()...
public work()...学习
private say()...
private say()...唱歌
其他注意事项
- 调用静态方法
- 调用类变量
- 调用数组参数方法
Student类:
public class Student {
public static String country = "CH";//国籍,类变量
public static int getSum(int num) {// 静态方法
return num;
}
public int getSum(int[] num) {
int sum = 0;
for (int i = 0; i < num.length; i++) {
sum += num[i];
}
return sum;
}
public String getSum(String[] arr) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < arr.length; i++) {
sb.append(arr[i]).append(" ");
}
return sb.toString();
}
}
Test类:
public class TestStudent {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("com.ivyzh.basicknowledge.reflect.simple1.Student");
//1. 使用反射调用静态方法
Method method = clazz.getDeclaredMethod("getSum", int.class);
Object sum = method.invoke(null, 20);
System.out.println("sum:" + (int) sum);//sum:20
//2.使用反射调用类变量
Field country = clazz.getField("country");
String name = country.getName();
Object value = country.get(clazz);
System.out.println("name:" + name + ",value:" + value);//name:country,value:CH
// 3. 使用反射调用带数组参数的方法
int[] arr = {1, 2, 3, 4, 5};
method = clazz.getMethod("getSum", int[].class);
Object obj = clazz.newInstance();
sum = method.invoke(obj, arr);
System.out.println("sum:" + (int) sum);//sum:15
String[] arrStr = {"a", "b", "c", "d", "e"};
method = clazz.getMethod("getSum", String[].class);
//obj = method.invoke(obj, arrStr);//error:java.lang.IllegalArgumentException: wrong number of arguments
//sum = method.invoke(obj, new String[]{"a", "b", "c", "d", "e"});//error:java.lang.IllegalArgumentException: wrong number of arguments
sum = method.invoke(obj, new Object[]{new String[]{"a", "b", "c", "d", "e"}});//sum:a b c d e
System.out.println("sum:" + sum);
// 王道:调用方法的时候把实际参数统统作为Object数组的元素即可.
// Method对象.invoke(方法底层所属对象,new Object[]{ 所有实参 });
}
}
注解
参考文章:
《秒懂,Java 注解 (Annotation)你可以这样学》https://blog.csdn.net/briblue/article/details/73824058
注解定义
Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。Java 注解是从 Java5 开始添加到 Java 的。
注解的使用
类型进行注解,比如类、接口、枚举,方法,属性
注解的定义(分别在类、字段、方法上面):
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ClassAnnotation {
String name();
int id();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface FieldAnnotation {
boolean isMan() default true;
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MethodAnnotation {
String errorMsg() default "请检查网络";
String okMsg() default "操作成功";
}
注解的获取:
@ClassAnnotation(id = 23, name = "Spring")
public class TestAnnotation {
@FieldAnnotation(isMan = false)
String person1 = "Jane";
@FieldAnnotation(isMan = true)
String person2 = "Jakie";
@FieldAnnotation()
String person3 = "Make";
String person4 = "Rose";
public static void main(String[] args) throws Exception {
getClassAnnotation();
getFieldAnnotation();
getMethodAnnotation();
}
// 给方法进行注解
private static void getMethodAnnotation() throws Exception {
Method method = TestAnnotation.class.getDeclaredMethod("openCamera");
MethodAnnotation annotation = method.getAnnotation(MethodAnnotation.class);
if (annotation != null) {
String errorMsg = annotation.errorMsg();
String okMsg = annotation.okMsg();
TestAnnotation testAnnotation = TestAnnotation.class.newInstance();
boolean isOk = (boolean) method.invoke(testAnnotation, null);
if (isOk)
System.out.println("openCamera okMsg:" + okMsg);
else {
System.out.println("openCamera errorMsg:" + errorMsg);
}
} else {
System.out.println("openCamera is Not Annotation.");
}
}
//给属性进行注解
private static void getFieldAnnotation() throws Exception {
Field person1 = TestAnnotation.class.getDeclaredField("person1");
Field person2 = TestAnnotation.class.getDeclaredField("person2");
Field person3 = TestAnnotation.class.getDeclaredField("person3");
Field person4 = TestAnnotation.class.getDeclaredField("person4");
person1.setAccessible(true);
person2.setAccessible(true);
person3.setAccessible(true);
person4.setAccessible(true);
FieldAnnotation annotation1 = person1.getAnnotation(FieldAnnotation.class);
FieldAnnotation annotation2 = person2.getAnnotation(FieldAnnotation.class);
FieldAnnotation annotation3 = person3.getAnnotation(FieldAnnotation.class);
FieldAnnotation annotation4 = person4.getAnnotation(FieldAnnotation.class);
if (annotation1 != null) {
boolean man = annotation1.isMan();
System.out.println("p1 isMan:" + man);
}
if (annotation2 != null) {
boolean man = annotation2.isMan();
System.out.println("p2 isMan:" + man);
}
if (annotation3 != null) {
boolean man = annotation3.isMan();
System.out.println("p3 isMan:" + man);
}
if (annotation4 != null) {
boolean man = annotation4.isMan();
System.out.println("p4 isMan:" + man);
} else {
System.out.println("p4 is Not Annotation.");
}
}
//给一个类型进行注解,比如类、接口、枚举
private static void getClassAnnotation() {
boolean isAnnotation = TestAnnotation.class.isAnnotationPresent(ClassAnnotation.class);
if (isAnnotation) {
ClassAnnotation annotation = TestAnnotation.class.getAnnotation(ClassAnnotation.class);
int id = annotation.id();
String name = annotation.name();
System.out.println(name + "---" + id);//Spring---23
}
}
@MethodAnnotation()
public boolean openCamera() {
System.out.println("打开相机操作...");
return new Random().nextInt(100) % 2 == 0 ? true : false;
}
}
控制台输出:
Spring---23
p1 isMan:false
p2 isMan:true
p3 isMan:true
p4 is Not Annotation.
打开相机操作...
openCamera errorMsg:请检查网络 (或者openCamera okMsg:操作成功)
关于其他注解字段的解释
元注解
元注解是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其它的注解上面。
如果难于理解的话,你可以这样理解。元注解也是一张标签,但是它是一张特殊的标签,它的作用和目的就是给其他普通的标签进行解释说明的。
元标签有 @Retention、@Documented、@Target、@Inherited、@Repeatable 5 种。
@Retention
Retention 的英文意为保留期的意思。当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的的存活时间。
它的取值如下:
- RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
- RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。
- RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。
@Target
Target 是目标的意思,@Target 指定了注解运用的地方。
你可以这样理解,当一个注解被 @Target 注解时,这个注解就被限定了运用的场景。
类比到标签,原本标签是你想张贴到哪个地方就到哪个地方,但是因为 @Target 的存在,它张贴的地方就非常具体了,比如只能张贴到方法上、类上、方法参数上等等。@Target 有下面的取值
ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
ElementType.CONSTRUCTOR 可以给构造方法进行注解
ElementType.FIELD 可以给属性进行注解
ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
ElementType.METHOD 可以给方法进行注解
ElementType.PACKAGE 可以给一个包进行注解
ElementType.PARAMETER 可以给一个方法内的参数进行注解
ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举
注解的属性
注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ClassAnnotation {
String name();
int id();
}
@ClassAnnotation(id = 23, name = "Spring")
public class TestAnnotation {
@FieldAnnotation(isMan = false)
String person1 = "Jane";
}
另外,还有一种情况。如果一个注解内仅仅只有一个名字为 value 的属性时,应用这个注解时可以直接接属性值填写到括号内。
public @interface Check {
String value();
}
//上面代码中,Check 这个注解只有 value 这个属性。所以可以这样应用。
@Check("hi")
int a;
//这和下面的效果是一样的
@Check(value="hi")
int a;
注解的使用场景
注解是一系列元数据,它提供数据用来解释程序代码,但是注解并非是所解释的代码本身的一部分。注解对于代码的运行效果没有直接影响。
注解有许多用处,主要如下:
- 提供信息给编译器: 编译器可以利用注解来探测错误和警告信息
- 编译阶段时的处理: 软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理。
- 运行时的处理: 某些注解可以在程序运行的时候接受代码的提取
当开发者使用了Annotation 修饰了类、方法、Field 等成员之后,这些 Annotation 不会自己生效,必须由开发者提供相应的代码来提取并处理 Annotation 信息。这些处理提取和处理 Annotation 的代码统称为 APT(Annotation Processing Tool)。
使用场景:Junit测试框架、ButterKnife、Dagger2、Retrofit2
问题:butterknife 用了反射没有? 用了(new 对象的时候)
自己实现Android的findViewbyId功能,使用注解。
ViewById注解定义:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewById {
int value();
}
ViewUtils 绑定Activity:
public class ViewUtils {
final static String TAG = "ViewUtils";
public static void bind(Activity activity) {
Class<? extends Activity> clazz = activity.getClass();
Field[] fields = clazz.getDeclaredFields();
for (Field f : fields) {
ViewById annotation = f.getAnnotation(ViewById.class);
if (annotation != null) {
Log.e(TAG, f.getName() + "有注解");
View v = activity.findViewById(annotation.value());
f.setAccessible(true);
try {
f.set(activity, v);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} else {
Log.e(TAG, f.getName() + "没有注解");
}
}
}
public static void unBind(Activity activity) {
}
}
MainActivity:
public class MainActivity extends AppCompatActivity {
@ViewById(R.id.tv_result)
TextView mTextView;
TextView mTextView2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewUtils.bind(this);
mTextView.setText("mTextView ViewById");
}
}
效果:
效果.png
泛型
泛型后面再补充讲解吧。
ing.
网友评论