——-android培训、java培训、期待与您交流! ———-
注:前面所有总结都是通过毕向东老师所讲的视频所得。反射部分由张孝祥老师所讲。
一、反射
反射 :是指程序在运行时能够获取自身的信息。在java中,只要给定类的名字,那么就可以通过反射机制来获得类的所有信息。反射就是得到元数据的行为。
反射的基础是Class类:
Class类:Java程序中的各个Java类属于同一类事物,描述这类事物的Java类就是Class类。
如:人——> Person //Person类代表人,它的实例对象就是张三,李四这样一个个具体的人
Java类——> Class //Class类代表Java类,它的各个实例对象对应各个类在内存中的字节码.。例如,Person类的字节码,ArrayList类的字节码等。
字节码:一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码。不同的类的字节码是不同的,所以它们在内存中的内容是不同的。
注:一个类在虚拟机中只有一份字节码。
得到字节码对应的实例对象的三种方法:
1)类名.class //如:Class c1=Person.class;
2)对象.getClass() //如:new Person().getClass()
3)Class.forName(String 类名) //类名要是全限定名,如String的全限定名是java.lang.String
九个定义Class对象:
Class类中的方法:
1.boolean isPrimitive() :判定指定的 Class 对象是否表示一个基本类型。
2.boolean isArray() :判定此 Class 对象是否表示一个数组类型。
包装类和Void类的静态TYPE字段。包装类都有一个TYPE字段,用来表示其基本数据类型的字节码。
如:Integer.TYPE == int.class ; Integer.class ==int.class;
数组类型的Class实例对象。如:Class
clz = String[].class; 利用Class获取类的属性信息:
示例1:用示例的方式来演示Class类方法的使用
Class
c=person.class;//类可以,接口也可以 c.getPackage()//得到包名 c.getName();//得到全限定名 c.getSimpleName()//得到类的简称 c.getSuperclass().getSimpleName()//先获取父类,再获取父类的简称 Class[] arr = c.getInterfaces();//得到接口 Class[] cl = c.getClasses();//获得public修饰的类。返回一个包含某些 Class 对象的数组, //这些对象表示属于此 Class 对象所表示的类的成员的所有公共类和接口。 (如果内部类前面没有加上public的话那么得不到!) int i = c.getModifiers();//获得修饰符 System.out.println(i);//常量值1表示public System.out.println(Modifier.toString(i));//直接打印出public Constructor类用于描述类中的构造方法:
Constructor
getConstructor(Class>... parameterTypes) //返回该Class对象表示类的指定的public构造方法;
Constructor>[] getConstructors()//返回该Class对象表示类的所有public构造方法;
ConstructorgetDeclaredConstructor(Class>... parameterTypes)//返回该Class对象表示类的指定的构造方法,和访问权限无关;
Constructor>[] getDeclaredConstructors() //返回该Class对象表示类的所有构造方法,和访问权限无关;
示例2:用示例的方式来演示Class类方法的使用Class
c=person.class;//类可以,接口也可以 Constructor c1 = c.getConstructor(String.class,int.class);//得到指定的构造器,也是必须public Constructor[] con1 = c.getConstructors();//前面的修饰符必须是public才可以在这个方法下获取到 Constructor c2 = c.getDeclaredConstructor(String.class,int.class);//现在想获得不受public影响的,getDeclaredConstructor(),暴力反射 Constructor[] con2 = c.getDeclaredConstructors();//现在想获得不受public影响的,getDeclaredConstructors(),暴力反射 Method类用于描述类中的方法:
Method getMethod(String name, Class> ... parameterTypes) //返回该Class对象表示类和其父类的指定的public方法;
Method[] getMethods()//返回该Class对象表示类和其父类的所有public方法;
Method getDeclaredMethod(String name, Class>... parameterTypes) //返回该Class对象表示类的指定的方法。和访问权限无关,但不包括继承的方法;
Method[] getDeclaredMethods()//获得类所有的方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法;示例3:用示例的方式来演示Class类方法的使用
Class
c=person.class;//类可以,接口也可以 Method me = c.getMethod("main", String[].class);//获取指定的方法 Method[] m = c.getMethods();//获取所有的(包含父类的方法)public修饰的方法 Method me = c.getDeclaredMethod("show");//访问所有方法,不受访问权限影响 Method[] m = c.getDeclaredMethods();//访问所有方法,不受访问权限影响 /*注:Method[] getDeclaredMethods() :返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法, 包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法,只可以对当前类有效*/ Field类用于描述类中的字段:
Field getField(String name, Class> ... parameterTypes) //返回该Class对象表示类和其父类的指定的public字段;
Field[] getFields()//返回该Class对象表示类和其父类的所有public字段;
Field getDeclaredField(String name, Class>... parameterTypes) //返回该Class对象表示类的指定的字段。和访问权限无关,但不包括继承的方法;
Field[] getDeclaredFields()//获得类所有的字段,包括公共、保护、默认(包)访问和私有方法,但不包括继承的字段;示例4:用示例的方式来演示Class类方法的使用
Stu s = new Stu("刘昭", "男", 12); Class
c = Stu.class; Field f = c.getField("name");//""里面是名称//特定字段 f.set(s,"章泽天"); f.get(s);//从哪个对象身上取!//此时显示章泽天 Field[] f = c.getFields()//只得到了public的字段 Field[] f = c.getDeclaredFields();//得到不受修饰符限定的字段,但是只对当前类有效 Annotation[] a = c.getAnnotations();//注释 Annotation
Deprecated d = c.getAnnotation(Deprecated.class);//特定注解
二、利用反射的方式创建对象
传统方式创建对象:new User( );
反射方式一:使用Class对象的newInstance()方法创建该Class对象的实例,此时该Class对象必须要有无参数的构造方法。
如:Class
c = User. class;Useru = c.newInstance();//前提条件:构造函数没有被私有化
反射方式二:使用Class对象获取指定的Constructor对象,再调用Constructor的newInstance()方法创建对象类的实例,此时可以选择使用某个构造方法。如果这个构造方法被私有化起来,那么必须先申请访问,将可以访问设置为true,即设置setAccessible(boolean flag)方法,然后再调用Xxx x = getDeclaredXxxx();//才能得到私有的类字段。步骤:1.获取该类的Class对象。
2.利用Class对象的getConstructor()方法来获取指定的构造方法。
3.申请访问。
4.调用Constructor的newInstance()方法创建对象。
如:Class
c=person.class;//获取本类的Class对象
Constructorcon = c.getDeclaredConstructor(String.class);//获取指定的构造方法
con.setAccessible(true);//允许访问
Person p = con.newInstance(" ");//可以通过私有的受保护的构造方法创建对象注:用反射的方式可以破解单例设计模式,但是对于枚举,反射依然没有办法创建对象。
三、使用反射方式调用方法
每个Method的对象对应一个具体的底层方法。获得Method对象后,程序可以使用Method里面的invoke方法来执行该底层方法。
Objectinvoke(Object obj,Object ... args):obj表示调用底层方法的对象,后面的args表示传递的实际参数。
如果底层方法是静态的,那么可以忽略指定的 obj参数。该参数可以为null。
如果底层方法所需的形参个数为 0,则所提供的 args数组长度可以为 0或 null。
传统方式:new Person().show("zhangsan");
反射方式:Class
c=Person.class; Method m=c.getMethod("show",String.class);//调用有参方法,参数是String类型
Object o=m.invoke(c.newInstance(),"zhangsan");
注:如果访问的是私有方法,需申请访问
Method m = c.getDeclaredMethod("privateshow");//调用无参方法
m.setAccessible(true);//申请访问
o=m.invoke(c.newInstance());
注:如果是静态方法,则不需要创建对象
m.c.getMethod("staticshow");
m.invoke(null);
四、使用反射调用可变参数方法
把可变参数都当做是其对应的数组类型参数。如 :show(XX... is)作为show(XX[] is)调用;
1)若可变参数元素类型是引用类型:
JDK内部接收到参数之后,会自动拆包取出参数再分配给该底层方法,为此我们需要把这个数组实参先包装成一个Object对象或把实际参数作为一个Object一维数组的元素再传递。
2)若可变参数元素类型是基本类型:
JDK内部接收到参数之后,不会拆包,所以可以不必再封装.不过封装了也不会错.所以建议,不管基本类型还是引用类型都使用Object[]封装一层,保证无误.
如:Class
c =VaryMethod.class; Method m = c.getMethod("show",int[].class);
m.invoke(null,new int[]{1,2,3});
m = c.getMethod("show",String[].class);//调用传String类型的可变参数show方法
//m.invoke(null,new String[]{"A","B","C"});//ERROR
m.invoke(null,(Object)new String[]{"A","B","C"});//YES,强转为Object类型
m.invoke(null,new Object[]{new String[]{"A","B","C"}});//推荐写法
五、使用反射操作字段
Field类提供两组方法操作字段:
1)xxx getXxx(Object obj):获取obj对象该Field的字段值,此处的xxx表示8个基本数据类型。若该字段的类型是引用数据类型则使用,Object get(Object obj);
2)void setXxx(Objectobj,xxx val):将obj对象的该Field字段设置成val值,此处的xxx表示8个基本数据类型。若该字段的类型是引用数据类型则使用,void set(Object obj, Objectvalue);
步骤:
1.获取类
2.获取字段
3.赋值(set(c.newInstance(),””));{如果为私有的话设置可接受}
如:
Class
clz = Cat.class;//获取类
Field[] f = clz.getDeclaredFields();//获取字段
Field fi = clz.getDeclaredField("name");
System.out.println(fi.getName());//nameCat c = clz.newInstance();
fi.setAccessible(true);
fi.set(c, "zhangsan");//将指定对象变量上此 Field 对象表示的字段设置为指定的新值。
Object o = fi.get(c);
System.out.println(o);//取出成功fi = clz.getDeclaredField("age");
fi.setAccessible(true);
fi.set(c, 21);
int i = fi.getInt(c);//左边的接受类型已经写成了int,右边的返回类型就也必须是int
System.out.println(i);//获取成功
六、利用反射获取泛型的类型
步骤:
1.获取当前类
2.获取目标字段
3.获取包含泛型类型的类型 getGenericType()
4.强转至子类ParameterizedType,因为Type没有任何对应的方法,TypegetRawType()//返回被泛型限制的类型
5.获得泛型真正的类型Type[] getActualTypeArguments()
如:
Map
map = new HashMap ();
Class c = GetGenericTypeDemo14.class;
Field f = c.getDeclaredField("map");
Class cl = f.getType(); //返回一个 Class 对象,它标识了此 Field 对象所表示字段的声明类型.//其结果为:interface java.util.Map
Type t = f.getGenericType();// 返回一个 Type 对象,它表示此 Field 对象所表示字段的声明类型。 Type是Class的接口; 包含泛型的类型//其结果为:java.util.Map
//因为Type这个类里面没有任何的方法,所以需要调用子类的方法,那么大的类型转到小的类型,需要强转
ParameterizedType pt = (ParameterizedType)t;//强转到其子类
t = pt.getRawType();//返回 Type 对象,表示声明此类型的类或接口。
Type t1=pt.getOwnerType(); //返回 Type 对象,表示此类型是其成员之一的类型。
Type[] ts = pt.getActualTypeArguments();//返回表示此类型实际类型参数的 Type对象的数组。
for (Type type : ts) {
System.out.println(type);//其结果为:class java.lang.String,class java.lang.Integer
七、反射的作用
作用:实现框架功能。
例如:我做房子卖给用户住,由用户自己安装门窗和空调,我做的房子就是框架,用户需要使用我的框架,把门窗插入进我提供的框架中。框架与工具类有区别,工具类被用户的类调用,而框架则是调用用户提供的类。
框架要解决的核心问题:我在写框架(房子)时,你这个用户可能还在上小学,还不会写程序呢?我写的框架程序怎样能调用到你以后写的类(门窗)呢?
因为在写才程序时无法知道要被调用的类名,所以,在程序中无法直接new某个类的实例对象了,而要用反射方式来做。
框架与工具类的区别:工具类被用户的类调用。而框架则是调用用户提供的类。
如:你做的门调用锁,锁是工具;你做的门被房子调用,房子是框架;锁和房子都是别人提供的。
八、JDK1.5新特性
枚举:在视频中没有详细讲解,可查看http://blog.csdn.net/kangmiao89757/article/details/10813171
枚举:就是要让某个类型的变量的取值只能为若干个固定值中的一个,否则,编译器就会报错。枚举可以让编译器在编译时就可以控制源程序中填写的非法值,普通变量的方式在开发阶段无法实现这一目标。
枚举的应用:
1、通过enum关键字定义枚举类,枚举类是一个特殊的类,每个元素都是该类的一个实例对象。
2、用枚举类规定值。以后用此类型定义的值只能是这个类中规定好的那些值,若不是这些值,编译器不会通过。
3、好处:在编译时期就会发现错误,表明值不符合,减少了运行时期的错误。
4、如果调用者想打印枚举类中元素的信息,需由编写此类的人定义toString方法。
注:枚举类是一个class,而且是一个不可被继承的final类,其中的元素都是类静态常量。
常用方法:
A)构造器:
1.构造器只是在构造枚举值的时候被调用。
2.构造器只有私有private,绝不允许有public构造器。这样可以保证外部代码无法重新构造枚举类的实例。因为枚举值是public static final的常量,但是枚举类的方法和数据域是可以被外部访问的。
3.构造器可以有多个,调用哪个即初始化相应的值。
B)非静态方法:(所有的枚举类都继承了Enum方法)
1.String toString() ;//返回枚举量的名称
2.int ordinal() ;//返回枚举值在枚举类中的顺序,按定义的顺序排
3.Class getClass() ;//获取对应的类名
4.String name();//返回此枚举常量的名称,在其枚举声明中对其进行声明
C)静态方法:
1.valueOf(String e) ;//转为对应的枚举对象,即将字符串转为对象
2.values() ;//获取所有的枚举对象元素
示例:
package cn.itheima; public class EnumDemo { public static void main(String[] args) { WeekDay weekDay=WeekDay.MON; System.out.println(weekDay);//输出枚举常量名 System.out.println(weekDay.name());//输出对象名 System.out.println(weekDay.getClass());//输出对应类 System.out.println(weekDay.toString());//输出枚举对象名 System.out.println(weekDay.ordinal());//输出此对象在枚举常量的次序 System.out.println(WeekDay.valueOf("WED"));//将字符串转化为枚举常量 System.out.println(WeekDay.values().length);//获取所以的枚举元素,并打印其长度 } //定义枚举内部类 public enum WeekDay{ SUN(1),MON,TUE,WED,THI,FRI,SAT;//分号可有可无,但如果下面还有方法或其他成员时,分号不能省。 //而且当有其他方法时,必须在这些枚举变量的下方。 //无参构造器 private WeekDay(){ System.out.println("First"); } //带参数的构造器 private WeekDay(int day){ System.out.println("Second"); } } }
枚举的高级应用
1、枚举就相当于一个类,其中也可以定义构造方法、成员变量、普通方法和抽象方法。
2、枚举元素必须位于枚举体中的最开始部分,枚举元素列表的后要有分号与其他成员分隔。把枚举中的成员方法或变量等放在枚举元素的前面,编译器报告错误。
3、带构造方法的枚举,构造方法必须定义成私有的。枚举元素MON和MON()的效果一样,都是调用默认的构造方法。
4、带方法的枚举。
示例:
/* * 抽象的枚举方法 * 此时枚举中的常量需要子类来实现,这是可以利用内部类的方式来定义枚举常量 * 带方法的枚举 1)定义枚举TrafficLamp 2)实现普通的next方法 3)实现抽象的next方法:每个元素分别是由枚举类的子类来生成的实例对象,这些子类 4)用类似内部类的方式进行定义。 5)增加上表示时间的构造方法 * */ package cn.itheima; public class EnumTest { public enum TrafficLamp{ RED(30){ public TrafficLamp nextLamp(){ return GREEN; } }, GREEN(30){ public TrafficLamp nextLamp(){ return YELLOW; } }, YELLOW(5){ public TrafficLamp nextLamp(){ return RED; } }; private int time; //构造器 private TrafficLamp(int time){ this.time=time;} //抽象方法 public abstract TrafficLamp nextLamp(); } }
注:1.所有的枚举都继承自java.lang.Enum类。由于Java不支持多继承,所以枚举对象不能再继承其他类。
2.switch语句支持int,char,enum类型,使用枚举,能让代码可读性更强。
小结:
1.匿名内部类比较常用
2.类的方法返回的类型可以是本类的类型
3.类中可定义静态常量,常量的结果就是自己这个类型的实例对象
4.枚举只有一个成员时,可以作为一种单例的实现方式。
还有其他新特性。如:静态导入、高级for循环、可变参数、基本数据类型的自动拆箱与装箱等前面文档都有讲过,在这里就不再讲解了。
九、正则表达式
视频中没有讲解,可查看http://blog.csdn.net/kangmiao89757/article/details/10807567
正则表达式:
概念:符合一定规则的表达式。
作用:用于专门操作字符串。
特点:用于一些特定的符号来表示一些代码操作,这样可以简化书写。所以学习正则表达式,就是在学习一些特殊符号的使用。
好处:可以简化对字符串的复杂操作。
弊端:符合定义越多,正则越长,阅读性越差。
常见符号:
1、字符
x 字符 x
\\ 反斜线字符
\t 制表符 ('\u0009')
\n 新行(换行)符 ('\u000A')
\r 回车符 ('\u000D')
\f 换页符 ('\u000C')
\a 报警 (bell)符 ('\u0007')
2、字符类
[abc] a、b或 c(简单类)
[^abc] 任何字符,除了 a、b或 c(否定)
[a-zA-Z] a到 z或 A到 Z,两头的字母包括在内(范围)
[a-d[m-p]] a到 d或 m到 p:[a-dm-p](并集)
[a-z&&[def]] d、e或 f(交集)
[a-z&&[^bc]] a到 z,除了 b和 c:[ad-z](减去)
[a-z&&[^m-p]] a到 z,而非 m到 p:[a-lq-z](减去)
3、预定义字符类
. 任何字符(与行结束符可能匹配也可能不匹配)
\d 数字:[0-9]
\D 非数字: [^0-9]
\s 空白字符:[ \t\n\x0B\f\r]
\S 非空白字符:[^\s]
\w 单词字符:[a-zA-Z_0-9]
\W 非单词字符:[^\w]
4、边界匹配器
^ 行的开头
$ 行的结尾
\b 单词边界
\B 非单词边界
\A 输入的开头
\G 上一个匹配的结尾
\Z 输入的结尾,仅用于最后的结束符(如果有的话)
\z 输入的结尾
5、Greedy数量词(X表示字符X或者匹配的规则)。
X? X,一次或一次也没有
X* X,零次或多次
X+ X,一次或多次
X{n} X,恰好 n次
X{n,} X,至少 n次
X{n,m} X,至少 n次,但是不超过 m次
6、组和捕获
捕获组可以通过从左到右计算其开括号来编号。例如,在表达式 ((A)(B(C)))中,存在四个这样的组:
1 ((A)(B(C)))
2 \A
3 (B(C))
4 (C)
组零始终代表整个表达式。在替换中常用$匹配组的内容。
正则表达式具体功能:
1)匹配:String类中的booleanmatches(String regex)方法。用规则匹配整个字符串,只要有一处不符合规则,就匹配结束,返回false。
2)切割:String类中的String[]split(String regex)方法。 按叠词完成切割:为了让规则被重用,可将规则封装成一个组,用()完成。组的出现都有编号,从1开始。想要使用已有的组可通过\n(n就是组的编号)的形式来获取。对于组中所匹配的字符,可以用$n来获取。$在正则中表示行的结尾,所以出现在正则中不能用来表示组,一般用于替换中。如下面功能中。
3)替换: String replaceAll(String regex,String replacement)方法。
4)获取:将字符串中的符合规则的子串取出。
操作步骤:1.将正则表达式封装成对象。2.让正则对象和要操作的字符串相关联。3.关联后,获取正则匹配引擎。4.通过引擎对符合规则的子串进行操作,比如取出。
——-android培训、java培训、期待与您交流! ———-