文章目录
1、JDK 1.5 新特性
JDK1.5 一个重要主题就是通过新增一些特性来简化开发。
1.1、自动装箱和拆箱
1.1.1、什么是自动装箱和拆箱
- 装箱
是把基本数据类型装箱成对象数据类型。
- 拆箱
是把对象数据类型拆箱成基本数据类型。
1.1.2、自动装箱拆箱要点
-
1.5 以前需要调用 valueOf() 方法手动装箱。使用 intValue() ,doubleValue() 等这类的方法手动拆箱。
-
1.5 以后才有自动装箱和拆箱,自动装箱时,编译器调用 valueOf 将原始类型值转换成对象。自动拆箱时,编译器通过调用类似intValue(),doubleValue()等这类的方法将对象转换成原始类型值。
基本数据类型 | 对象数据类型 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
boolean | Boolean |
char | Charachter |
- Integer.valueOf(int i)基本数据类型转对象数据类型
- Integer.parseInt(String s)字符串转基本数据类型
- intValue 对象数据类型转基本数据类型
1.1.3、自动装箱拆箱样例
自动装箱主要发生在两种情况,一种是赋值时,另一种是在方法调用的时候。
(1)赋值时候
Integer y = 3 //自动装箱
int c = y //自动拆箱
(2)方法调用时候
public static Integer autoboxing(Integer iParam){
return iParam;
}
autoboxing(3) //自动装箱
int result = show(3) //自动拆箱
1.1.4、自动装箱缺点
Integer sum = 0;
for(int i=1000; i<5000; i++){
sum = sum + i;
}
首先 sum 进行自动拆箱操作,进行数值相加操作,最后发生自动装箱操作转换成 Integer 对象。其内部变化如下
sum = sum.intValue() + i;
Integer sum = new Integer(sum);
由于我们这里声明的 sum 为Integer类型,在上面的循环中会创建将近 4000 个无用的Integer对象,在这样庞大的循环中,会降低程序的性能并且加重了垃圾回收的工作量。因此在编程时,需要注意到这一点,正确地声明变量类型,避免因为自动装箱引起的性能问题。
1.1.5、重载与自动装箱
重载方法:方法名相同,参数类型不同。
private void add(Integer a){
System.out.println("Integer");
}
private void add(int a){
System.out.println("int");
}
测试方法
DateDemo test = new DateDemo();
test.add(1);
test.add(Integer.valueOf(1));
结果
int
Integer
重载时传入int a 和 Integer a 是两个不同的方法,会根据传入基本类型或者包装类型来判断走哪个方法。
1.1.6、自动拆装箱的缓存机制
通过上面的讲述我们知道了从jdk1.5以后不再需要通过valueOf()的方式手动装箱,采用自动装箱的方式,其实底层用的还是valueOf()方法,只是现在不用要手动执行了,是通过编译器调用,执行时会自动生成一个静态数组作为缓存,例如Integer 默认对应的缓存数组范围在[-128,127],只要数据在这个范围内,就可以从缓存中拿到相应的对象。超出范围就新建对象,这个就是缓存机制。
源码
valueOf 方法
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
Integer 的内部类 IntegerCache
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
IntegerCache是Integer的静态内部类,valueOf()调用的IntegerCache.cache就是一个数组对象,数组的大小取决于范围内的最大值和最小值,例如上面的Integer是[-128,127]。然后数组内的元素都会被赋一个Integer对象,缓存也就形成了。
存在数组缓存,也就意味着,如果取值在[-128,127],使用valueOf()或者自动装箱创建的Integer对象都是在数组中取出,因此对象指向的内存地址是完全一样的。而如果用new或者是超出这个范围都要重新创建对象。
其它类型缓存范围
Byte:(全部缓存)
Short:(-128 — 127缓存)
Integer:(-128 — 127缓存)
Long:(-128 — 127缓存)
Float:(没有缓存)
Double:(没有缓存)
Boolean:(全部缓存)
Character:(0 — 127缓存)
测试
Integer a = new Integer(1);
Integer b = new Integer(1);
System.out.println(a == b); //new创建的两个对象,即使值相同,指向的内存地址也是不同的,使用==进行比较,比较的是地址,返回结果为false
Integer c = 1;
Integer d = 1;
System.out.println(c == d); //自动装箱和缓存机制,两个对象实际上是相同的,返回结果为true
Integer e = 128;
Integer f = 128;
System.out.println(e == f); //超出缓存范围,执行时会new新对象,两个对象不同,返回结果为false
结果
false
true
false
1.2、for - each 循环
- for ( 类型 变量名:集合或数组 ){}
循环样例
int[] a = new int[] {2, 3, 1, 4};
for (int i : a) {
System.out.println(i);
}
结果
2
3
1
4
1.3、可变参数
- 用 … 定义。
- 本质就是一个数组。
- 可以传入任意个参数。
- 可变参数只能放在方发参数的最后一个位置。
可变参数方法样例
public static double sum(int a, int b, double... ds) {
double sum = 0;
sum = a + b;
if (ds != null) {
for (int i = 0; i < ds.length; i++) {
sum += ds[i];
}
}
return sum;
}
测试
System.out.println(sum(1, 2)); //3.0
System.out.println(sum(1, 2, 1)); //4.0
System.out.println(sum(1, 2, 1, 2)); //6.0
System.out.println(sum(1, 2, null)); //3.0
System.out.println(sum(1, 2, new double[] {1, 2, 3})); //9.0
1.4、 静态导入
- 用 import static 包名.类名.静态属性或静态方法名
- 可以提高开发的效率
- 降低了可读,建议要慎用
定义一个静态类,包含静态变量和静态方法
public class Static_Import {
// 静态变量
public static int kk = 100;
// 静态方法
public static void method() {
System.out.println("cc");
}
}
测试静态导入
常规用法
// 常规用法,明确知道变量取自于某个类
System.out.println(Static_Import.kk); //100
Static_Import.method(); //cc
静态导入的用法
// 本类静态导入变量kk和方法method
import static com.yang.test.controller.Static_Import.kk;
import static com.yang.test.controller.Static_Import.method;
// 静态导入的用法(慎用)
System.out.println(kk); //100
method(); //cc
1.5、枚举(enum)
-
当取值为有限固定的值,可以使用枚举类型,枚举是一个数据类型。
-
枚举也可以有方法和属性和构造函数,但是构造方法必须是私有的。
-
枚举还可以实现接口,不能进行继承,枚举也可以包含抽象方法。
-
所有枚举的类型都默认继承 java.lang.Enum 类。
现在说下最简单的枚举类型。每个枚举值只有一个字符串,如一个星期的枚举类:
public enum Week {
Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday;
}
但是在实际使用中,可能想给每个枚举值赋予更多的含义,例如,给每个星期中文说明和编号等。修改后的星期枚举类如下:
public enum Week {
Monday("星期一", "1"),
Tuesday("星期二", "2"),
Wednesday("星期三", "3"),
Thursday("星期四", "4"),
Friday("星期五", "5"),
Saturday("星期六", "6"),
Sunday("星期日", "7");
private String name;
private String number;
Week(String name, String number) {
this.name = name;
this.number = number;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
}
可以在枚举类中增加了 name/number 两个属性,并重新编写了构造方法。实现了要求。
测试得到结果
System.out.println(Week.Monday); //Monday
System.out.println(Week.Monday.getName()); //星期一
System.out.println(Week.Monday.getNumber()); //1
还有就是可以在枚举类中增加自定义抽象方法,再次修改星期枚举类如下:
public enum Week {
Monday("星期一", "1") {
@Override
void helloYang() {
System.out.println("hello Monday");
}
},
Tuesday("星期二", "2") {
@Override
void helloYang() {
System.out.println("hello Monday");
}
},
Wednesday("星期三", "3") {
@Override
void helloYang() {
System.out.println("hello Monday");
}
},
Thursday("星期四", "4") {
@Override
void helloYang() {
System.out.println("hello Monday");
}
},
Friday("星期五", "5") {
@Override
void helloYang() {
System.out.println("hello Monday");
}
},
Saturday("星期六", "6") {
@Override
void helloYang() {
System.out.println("hello Monday");
}
},
Sunday("星期日", "7") {
@Override
void helloYang() {
System.out.println("hello Monday");
}
};
private String name;
private String number;
Week(String name, String number) {
this.name = name;
this.number = number;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
abstract void helloYang();
}
测试得到结果
Week.Monday.helloYang(); //hello Monday
1.6、反射
运行期间动态获取类中的信息(属性,方法,包的信息,注解等)以及动态调用对象的方法和属性的功能,称之为java语言的反射机制,通俗的理解,就是在运行期间对类的内容进行操作。
Person 类
public class Person {
private String test1;
public String test2;
public Person(){}
public void eatPerson() {
}
private void badPerson() {
}
}
User类 继承 Person类
public class User extends Person{
private String userName;
private String userPassword;
public String sex;
protected String email;
String phone;
private User() {}
public User(String userName, String userPassword) {
super();
this.userName = userName;
this.userPassword = userPassword;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserPassword() {
return userPassword;
}
public void setUserPassword(String userPassword) {
this.userPassword = userPassword;
}
protected void eat() {
}
private void bad() {
}
void hello() {
}
}
传统的做法,不使用反射在编译期间确定调用的关系
User user = new User();
user.setUserName("杨");
user.setUserPassword("1212");
System.out.println(user.getUserName() + " " + user.getUserPassword());
}
接下来我来介绍如何使用反射完成上述的对User类属性的操作,在这之前先了解下Class类型对象和反射的api。
1.6.1、Class类型对象
要想使用反射,就要获取到类中的所有信息(属性,方法,注解等),在java中有一个特殊的类,类型是Class,此类型的对象中存储的是某个类中的信息。在运行期间,通过Class对象调用反射的api可以反射实例化对象,可以反射访问属性,和反射调用方法,总之,编译期间能写的代码,用反射也能实现。
3种方式获取Class类型的对象
-
对象.getClass();
User user = new User(); Class clazz = user.getClass();
-
类名.class
Class clazz = User.class
-
Class.forName(“包名.类名”)
Class.clazz = Class.forName("cn.tedu.User");
测试
User user = new User();
// 对象.getClass()
Class clazz1 = user.getClass();
// 类名.class
Class clazz2 = User.class;
// Class.forName("包名.类名")
Class clazz3 = Class.forName("com.yang.test.controller.User");
// 输出hashcode值一样,证明此Class对象只有一个
System.out.println(clazz1.hashCode());
System.out.println(clazz2.hashCode());
System.out.println(clazz3.hashCode());
1.6.2、反射的API
1.6.2.1、获取对象
- 用无参数构造创建对象
Class对象.newInstance()
- 用有参数构造创建对象
Class对象.getConstructor(new Class[]{若干参数的类类型}).newInstance(构造函数的参数);
// 无参构造创建对象
Class clazz = User.class;
Object obj = clazz.newInstance();
// 有参构造创建对象
Object obj1 = clazz.getConstructor(String.class, String.class).newInstance("yang", "yy");
if (obj1 instanceof User) {
User user = (User)obj1;
System.out.println(user.getUserName() + " " + user.getUserPassword());
}
1.6.2.2、通过反射获取属性信息(Field)
1.6.2.2.1、通过反射获取本类的所有的属性(getDeclaredFields)
Class clazz = User.class;
Field[] fields = clazz.getDeclaredFields();
for (Field f : fields) {
System.out.print(f.getModifiers() + " ");
System.out.print(f.getType().getName() + " ");
System.out.println(f.getName());
}
结果
2 java.lang.String userName
2 java.lang.String userPassword
1 java.lang.String sex
4 java.lang.String email
0 java.lang.String phone
1.6.2.2.2、通过反射获取本类以及长辈类所有的公有属性(getFields)
Class clazz = User.class;
// Declared 不存在只能公有
Field[] fields = clazz.getFields();
for (Field f : fields) {
System.out.print(f.getModifiers() + " ");
System.out.print(f.getType().getName() + " ");
System.out.println(f.getName());
}
结果
1 java.lang.String sex
1 java.lang.String test2
1.6.2.2.3、通过反射获取本类的指定的属性(getDeclaredField)
Class clazz = User.class;
Field field = clazz.getDeclaredField("userName");
System.out.print(field.getModifiers() + " ");
System.out.print(field.getType().getName() + " ");
System.out.print(field.getName());
结果
2 java.lang.String userName
1.6.2.2.4、通过反射获取本类以及长辈类指定的公有属性(getField)
Class clazz = User.class;
Field field = clazz.getField("test2");
System.out.print(field.getModifiers() + " ");
System.out.print(field.getType().getName() + " ");
System.out.print(field.getName());
结果
1 java.lang.String test2
1.6.2.2.5、通过反射设定和获取Field属性
- Field对象.set(Object obj,Object value)。
- Object value = Field对象.get(object)。
如果Field是私有的,必须先执行,Field对象.setAccessable(true),设置属性可以访问。
// 通过反射获取指定属性
Class clazz = User.class;
Field field = clazz.getDeclaredField("userName");
// 要给field赋值,先要创建对象
Object obj = clazz.newInstance();
// 设置field具备可访问性
field.setAccessible(true);
// 赋值
field.set(obj, "yang");
// 要取出field中的数据
String value = field.get(obj).toString();
System.out.println(value);
结果
yang
1.6.2.3、通过反射获取方法信息(Method)
1.6.2.3.1、通过反射获取本类的所有的方法(getDeclaredMethods)
Class clazz = User.class;
Method[] methods = clazz.getDeclaredMethods();
for (Method m : methods) {
System.out.print(m.getModifiers() + " ");
System.out.print(m.getReturnType() + " ");
System.out.print(m.getName() + "(");
Class[] claxxs = m.getParameterTypes();
if (claxxs != null && claxxs.length > 0) {
for (int i = 0; i < claxxs.length - 1; i++) {
System.out.print(claxxs[i].getName() + ",");
}
System.out.print(claxxs[claxxs.length - 1]);
System.out.println(")");
} else {
System.out.println(")");
}
}
结果
1 class java.lang.String getUserName()
1 void setUserName(class java.lang.String)
0 void hello()
1 class java.lang.String getUserPassword()
4 void eat()
2 void bad()
1 void setUserPassword(class java.lang.String)
1.6.2.3.2、通过反射获取本类以及长辈类所有的公有方法(getMethods)
Class clazz = User.class;
Method[] methods = clazz.getMethods();
for (Method m : methods) {
System.out.print(m.getModifiers() + " ");
System.out.print(m.getReturnType() + " ");
System.out.print(m.getName() + "(");
Class[] claxxs = m.getParameterTypes();
if (claxxs != null && claxxs.length > 0) {
for (int i = 0; i < claxxs.length - 1; i++) {
System.out.print(claxxs[i].getName() + ",");
}
System.out.print(claxxs[claxxs.length - 1]);
System.out.println(")");
} else {
System.out.println(")");
}
}
结果 (包含了Object类的公有方法)
1 class java.lang.String getUserName()
1 void setUserName(class java.lang.String)
1 class java.lang.String getUserPassword()
1 void setUserPassword(class java.lang.String)
1 void eatPerson()
17 void wait()
17 void wait(long,int)
273 void wait(long)
1 boolean equals(class java.lang.Object)
1 class java.lang.String toString()
257 int hashCode()
273 class java.lang.Class getClass()
273 void notify()
273 void notifyAll()
1.6.2.3.3、通过反射获取本类的指定的方法(getDeclaredMethod)
Class clazz = User.class;
Method method = clazz.getDeclaredMethod("setUserName", String.class);
System.out.print(method.getModifiers() + " ");
System.out.print(method.getReturnType() + " ");
System.out.print(method.getName() + "(");
Class[] claxxs = method.getParameterTypes();
if (claxxs != null && claxxs.length > 0) {
for (int i = 0; i < claxxs.length - 1; i++) {
System.out.print(claxxs[i].getName() + ",");
}
System.out.print(claxxs[claxxs.length - 1]);
System.out.println(")");
} else {
System.out.println(")");
}
结果
1 void setUserName(class java.lang.String)
1.6.2.3.4、通过反射获取本类以及长辈类指定的公有方法(getMethod)
Class clazz = User.class;
Method method = clazz.getMethod("eatPerson", null);
System.out.print(method.getModifiers() + " ");
System.out.print(method.getReturnType() + " ");
System.out.print(method.getName() + "(");
Class[] claxxs = method.getParameterTypes();
if (claxxs != null && claxxs.length > 0) {
for (int i = 0; i < claxxs.length - 1; i++) {
System.out.print(claxxs[i].getName() + ",");
}
System.out.print(claxxs[claxxs.length - 1]);
System.out.println(")");
} else {
System.out.println(")");
}
结果
1 void eatPerson()
1.6.2.3.5、通过反射动态调用Method
- Object returnValue = Method对象.invoke(Object,object…args)。
Class clazz = User.class;
Object obj = clazz.newInstance();
Method method = clazz.getDeclaredMethod("setUserName", String.class);
method.setAccessible(true);
method.invoke(obj, "cc");
Method method1 = clazz.getDeclaredMethod("getUserName");
method1.setAccessible(true);
System.out.println(method1.invoke(obj, null));
结果
cc
1.6.2.4、通过反射获取构造函数信息(Constructor)
1.6.2.4.1、通过反射获取本类所有的构造方法(getDeclaredConstructors)
Class clazz = User.class;
Constructor[] constructors = clazz.getDeclaredConstructors();
for (Constructor c : constructors) {
System.out.print(c.getModifiers() + " ");
System.out.print(c.getName() + "(");
Class[] claxxs = c.getParameterTypes();
if (claxxs != null && claxxs.length > 0) {
for (int i = 0; i < claxxs.length - 1; i++) {
System.out.print(claxxs[i].getName() + ",");
}
System.out.print(claxxs[claxxs.length - 1]);
}
System.out.println(")");
}
结果
2 com.yang.test.controller.User()
1 com.yang.test.controller.User(java.lang.String,class java.lang.String)
1.6.2.4.2、通过反射获取本类公有的构造方法(getDeclaredConstructor)
Class clazz = User.class;
Constructor constructor = clazz.getDeclaredConstructor(String.class, String.class);
System.out.print(constructor.getModifiers() + " ");
System.out.print(constructor.getName() + "(");
Class[] claxxs = constructor.getParameterTypes();
if (claxxs != null && claxxs.length > 0) {
for (int i = 0; i < claxxs.length - 1; i++) {
System.out.print(claxxs[i].getName() + ",");
}
System.out.print(claxxs[claxxs.length - 1]);
System.out.println(")");
}
}
结果
1 com.yang.test.controller.User(java.lang.String,class java.lang.String)
1.6.2.4.3、通过反射获取本类指定的构造方法(getDeclaredConstructor)
Class clazz = User.class;
Constructor constructor = clazz.getDeclaredConstructor(String.class, String.class);
System.out.print(constructor.getModifiers() + " ");
System.out.print(constructor.getName() + "(");
Class[] claxxs = constructor.getParameterTypes();
if (claxxs != null && claxxs.length > 0) {
for (int i = 0; i < claxxs.length - 1; i++) {
System.out.print(claxxs[i].getName() + ",");
}
System.out.print(claxxs[claxxs.length - 1]);
System.out.println(")");
}
结果
1 com.yang.test.controller.User(java.lang.String,class java.lang.String)
1.6.2.4.4、通过反射获取本类指定的公有构造方法(getConstructor)
Class clazz = User.class;
Constructor constructor = clazz.getConstructor(String.class, String.class);
System.out.print(constructor.getModifiers() + " ");
System.out.print(constructor.getName() + "(");
Class[] claxxs = constructor.getParameterTypes();
if (claxxs != null && claxxs.length > 0) {
for (int i = 0; i < claxxs.length - 1; i++) {
System.out.print(claxxs[i].getName() + ",");
}
System.out.print(claxxs[claxxs.length - 1]);
System.out.println(")");
}
结果
1 com.yang.test.controller.User(java.lang.String,class java.lang.String)
1.6.2.5、通过反射获取注解信息(Annotation)
自行查看 API 文档
1.6.3、反射的应用场景
- 用反射实现 jdbc 的通用查询和通用更新。
- 单元测试,就是用反射实现的。
- 常见的框架,spring框架,springmvc框架都是用反射实现的。
- EL表达式。
1.6.4、反射的优点和缺点
优点
- 大幅度提高开发效率,框架就是反射实现的,框架可以大大提高开发效率。
缺点
- 反射执行效率比非反射的方式执行效率低,反射可以暴露类中的所有细节,突破了封装。
1.7、内省
本质就是反射,通过反射的方式访问javabean的技术。
User类
public class User {
private String userName;
private String passWord;
public User(String userName, String passWord) {
super();
this.userName = userName;
this.passWord = passWord;
}
public User() {}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
}
1.7.1、JDK内省类库(Introspector类和PropertyDescriptor类)
Introspector可以按照JavaBean的规范将一个类封装成BeanInfo对象。通过调getPropertyDescriptors()方法会返回一个包含所有属性的PropertyDescriptor对象数组,通过PropertyDescriptor可以操作类的属性。
PropertyDescriptor可以获取某一个具体的属性。主要的方法有:
- getPropertyType(),获得属性的Class对象。
- getReadMethod(),获得用于读取属性值的方法,例如getXXX()方法。
- getWriteMethod(),获得用于写入属性值的方法,例如setXXX()方法。
- hashCode(),获取对象的哈希值。
- setReadMethod(Method readMethod),设置用于读取属性值的方法。
- setWriteMethod(Method writeMethod),设置用于写入属性值的方法。
样例代码
User user = new User();
// 获取BeanInfo对象
BeanInfo bi = Introspector.getBeanInfo(user.getClass());
// 用BeanInfo的api来获取所有属性的描述器
PropertyDescriptor[] pds = bi.getPropertyDescriptors();
// 遍历所有的属性描述器,每一个描述器代表的是反射中的Field
for (PropertyDescriptor pd : pds) {
Method writeMethod = pd.getWriteMethod();
if (writeMethod != null) {
writeMethod.invoke(user, "yang");
}
Method readMethod = pd.getReadMethod();
if (readMethod != null) {
Object returnValue = readMethod.invoke(user);
System.out.println(returnValue);
}
}
结果
yang
1.7.2、apache提供的Common-beanutils工具
此工具类中有若干工具类
- MethodUtil工具类
- ConstructorUtil工具类
- PropertyUtils工具类
样例代码
/**
* 演示PropertyUtils工具类
*/
@Test
public void test01() throws Exception {
User user = new User();
// 用MethodUtil工具类的api方法
// 先查找方法后调用
Method method = MethodUtils.getAccessibleMethod(User.class, "setUserName", String.class);
if (method != null) {
// 说明找到了方法
method.invoke(user, "杨");
}
// 查找到方法就直接调用
Object value = MethodUtils.invokeMethod(user, "getUserName", null);
System.out.println(value);
}
/**
* 演示ConstructorUtil工具类
*/
@Test
public void test02() throws Exception {
// 用ConstructorUtils工具类
User user1 = (User)ConstructorUtils.invokeConstructor(User.class, new String[] {"yang", "jun"});
System.out.println(user1.getUserName() + " " + user1.getPassWord());
}
/**
* 演示PropertyUtils工具类
*/
@Test
public void test03() throws Exception {
User user1 = new User("yang", "jie");
User user2 = new User();
PropertyUtils.copyProperties(user2, user1);
System.out.println(user2.getUserName() + " " + user2.getPassWord());
}
1.8、注解
注解应用场景很广泛,将来是一个趋势,他可以提高开发效率,但是执行效率堪忧,因为其底层解析注解是用反射解析的,用注解可以替换xml配置和属性文件。
1.8.1、如何定义注解
(1)设定注解MyAnnotation应用在什么位置上 @Target(value={ElementType.METHOD})。
ElementType 类型 | 描述 |
---|---|
METHOD | 方法 |
FIELD | 属性 |
CONSTRUCTOR | 构造 |
TYPE | 类/接口 |
PARAMETER | 方法的参数上 |
等
(2)指定注解MyAnnotation的保留策略 @Retention(RetentionPolicy.RUNTIME)。
RetentionPolicy 类型 |
---|
SOURCE |
CLASS |
RUNTIME |
SOURCE
源代码级别,Source修饰的注解是给编译器看的,编译器把源代码编译完毕后,在class文件中就没有注解。
CLASSS
Class修饰的注解给类加载器看的,在类加载的时候可以做一系列的引导操作,在编译器编译完毕后注解存在,在类加载加载之后就要丢弃注解。
RUNTIME
运行时级别,给JVM看的,在程序运行的过程中做相关的操作可以在jvm中借助反射api解析注解。
(3)设定MyAnnotation注解中的属性。
-
注解定义属性和接口定义的方法类似,缺省默认public(public 类型 属性名称())。
-
定义属性如果没有使用default指定默认值,则在使用注解,必须给属性赋值,如果带有默认值,可以在使用注解的时候给属性赋值为新值或者使用默认值。
-
注解中的属性类型必须遵守是八种基本数据类型,枚举类型,Class类型,String类型以及前面类型的一维数组。
-
在给数组赋值的时候,如果数组只有一个值就不用写{}。
-
有一个极特殊的属性value,如果只为该属性赋值,value=值,但是如果注解中只有value这一个属性,那么value可以省略。例如下面的 @MyAnnotation(“xxx”),本质是 @MyAnnotation(value = “xxx”)。
1.8.2、自定义和使用注解样例
自定义注解
@Target(value = {ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
// 注解的value属性
public String value();
// 注解的默认属性,且有默认值abc
public String name() default "abc";
// 注解的colors属性,且是一个数组,默认值是red和blue
public String[] colors() default {"red", "blue"};
}
使用注解
@MyAnnotation(value = "xx", name = "yy", colors = {"pink", "green", "gay"})
public class AnnotationClass {
@MyAnnotation(value = "aa", name = "bb")
public void method1() {
System.out.println("method1");
}
@MyAnnotation("xxx")
public void method2() {
System.out.println("method2");
}
@MyAnnotation("yyy")
public void method3() {
System.out.println("method3");
}
}
1.8.3、写反射代码解析注解
用反射的代码来确定是否有注解和注解属性值 ,根据是否有注解以及注解的值做相应的功能。
以下代码是来解析上述类上的注解:
Class clazz = AnnotationClass.class;
// 查找并判断类上是否有指定的注解
boolean flag = clazz.isAnnotationPresent(MyAnnotation.class);
if (flag) {
// 说明类上有指定的@MyAnnotation注解
// 可以获取注解对象
MyAnnotation myAnnotation = (MyAnnotation)clazz.getAnnotation(MyAnnotation.class);
// 通过注解的对象,获取注解中的属性的数据
String value = myAnnotation.value();
String name = myAnnotation.name();
String[] colors = myAnnotation.colors();
System.out.println(value);
System.out.println(name);
System.out.println(Arrays.toString(colors));
// 根据注解属性的定义,来决定doSomething
if ("xx".equals(value) && "yy".equals(name)) {
System.out.println("doSomething 代表一段业务逻辑代码");
}
}
结果
xx
yy
[pink, green, gay]
doSomething 代表一段业务逻辑代码
以下代码是来解析上述方法上的注解:
Class clazz = AnnotationClass.class;
Object obj = clazz.newInstance();
// 获取类中的所有的方法,要查看方法上是否有指定的注解
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
// 判断方法上是否有指定的注解
boolean flag = method.isAnnotationPresent(MyAnnotation.class);
if (flag) {
// 说明方法上有指定的注解
// 获取到方法上的注解的对象
MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class);
String value = myAnnotation.value();
String name = myAnnotation.name();
String[] colors = myAnnotation.colors();
if ("xxx".equals(value) || "bb".equals(name)) {
method.invoke(obj);
}
}
}
结果
method2
method1
1.9、泛型(一种参数化的类型)
Java 泛型是 JDK 1.5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。
泛型的本质是参数化类型,即给类型指定一个参数,然后在使用时再指定此参数具体的值,那样这个类型就可以在使用时决定了。这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
1.9.1、为什么使用泛型
(1)保证了类型的安全性。
在没有泛型之前,从集合中读取到的每一个对象都必须进行类型转换,如果不小心插入了错误的类型对象,在运行时的转换处理就会出错。
比如:没有泛型的情况下使用集合:
ArrayList list = new ArrayList();
list0.add("abc");
list0.add(10);
比如:没有泛型的情况下使用集合:
ArrayList<String> list = new ArrayList<String>();
list.add("abc");
list.add(10);// 编译报错
相当于告诉编译器每个集合接收的对象类型是什么,编译器在编译期就会做类型检查,告知是否插入了错误类型的对象,使得程序更加安全,增强了程序的健壮性。
(2) 消除强制转换
泛型的一个附带好处是,消除源代码中的许多强制类型转换,这使得代码更加可读,并且减少了出错机会。
没有泛型的代码段需要强制转换
ArrayList list = new ArrayList();
list.add("a");
String s = (String)list.get(0);
当使用泛型时,代码不需要强制转换
ArrayList<String> list = new ArrayList<String>();
list.add("a");
String s = list.get(0);
(3)多种数据类型执行相同的代码使用泛型可以复用代码。
1.9.2、泛型的分类
1.9.2.1、泛型类(类上的泛型)
定义格式:
public class 类名 <泛型类型,...> {
}
- 泛型类型必须是引用类型(非基本数据类型)
- 定义泛型类,参数可以有多个,多个参数使用逗号分隔。
- 参数类型也是有规范的,通常类型参数我们都使用大写的单个字母表示。
E: Element (在集合中使用,因为集合中存放的是元素)
T:Type(Java 类)
K: Key(键)
V: Value(值)
N: Number(数值类型)
?: 表示不确定的java类型
定义泛型类
public class User<T> {
public T name;
public User(T name) {
this.name = name;
}
}
测试泛型类
User<String> user = new User<>("yang");
System.out.println(user.name);
User<Integer> number = new User<>(123);
System.out.println(number.name);
结果
yang
123
1.9.2.2、泛型接口(接口上的泛型)
定义格式:
public <泛型类型> 返回类型 方法名(泛型类型 变量名) {
}
方法声明中定义的形参只能在该方法里使用,而接口、类声明中定义的类型形参则可以在整个接口、类中使用。当调用fun()方法时,根据传入的实际对象,编译器就会判断出类型形参T所代表的实际类型。
定义泛型接口
public interface GenericInterface<T> {
void show(T value);}
}
定义泛型接口实现类
public class StringShowImpl implements GenericInterface<String> {
@Override
public void show(String value) {
System.out.println(value);
}
}
public class NumberShowImpl implements GenericInterface<Integer> {
@Override
public void show(Integer value) {
System.out.println(value);
}
}
测试泛型接口
GenericInterface<String> genericInterface = new StringShowImpl();
GenericInterface<Integer> genericInterface1 = new NumberShowImpl();
GenericInterface<String> genericInterface = new NumberShowImpl(); //编译报错
GenericInterface<Integer> genericInterface1 = new StringShowImpl(); //编译报错
使用泛型的时候,前后定义的泛型类型必须保持一致
1.9.2.3、泛型方法(方法上的泛型)
定义格式:
修饰符 <代表泛型的变量> 返回值类型 方法名(参数){ }
- 修饰符与返回值中间<代表泛型的变量>非常重要,可以理解为声明此方法为泛型方法。
- 只有声明了<代表泛型的变量>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
- 与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E等形式的参数常用于表示泛型。
定义泛型方法
public <T> T genercMethod(T t){
System.out.println(t.getClass());
System.out.println(t);
return t;
}
测试泛型方法
User<String> user = new User<>("yang");
user.method("yang");
User<Integer> number = new User<>(123);
user.method(123);
结果
class java.lang.String
yang
class java.lang.Integer
123
1.9.3、泛型的通配符
-
无边界的通配符,就是<?>,比如List<?>。
无边界的通配符的主要作用就是让泛型能够接受未知类型的数据。 -
固定上边界的通配符,采用<? extends E>的形式,比如< T extends Animal >。
使用固定上边界的通配符的泛型,就能够接受指定类及其子类类型的数据(Animal类和其子类)。 -
固定下边界的通配符,采用<? super E>的形式,比如< T super Animal >。
使用固定下边界的通配符的泛型, 就能够接受指定类及其父类类型的数据(Animal类和其长辈类)。
你可以为一个泛型指定上边界或下边界, 但是不能同时指定上下边界。
如果不指定泛型默认的上边界是 Object 。
1.9.4、泛型擦除
Java 中的泛型基本上都是在编译器这个层次来实现的。在生成的 Java 字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。如在代码中定义的 List< Object > 和 List< String >等类型,在编译之后都会变成 List。 这个过程就称为类型擦除。
测试泛型擦除
ArrayList<String> list1 = new ArrayList<String>();
list1.add("abc");
ArrayList<Integer> list2 = new ArrayList<Integer>();
list2.add(123);
// 泛型类型String和Integer都被擦除掉了,只剩下原始类型
System.out.println(list1.getClass() == list2.getClass());
结果
true