目录
反射是什么
反射的用法
反射的优点和缺点
什么是枚举
解决了哪些问题
如何使用枚举
泛型是什么
为什么要使用泛型
泛型有哪些
怎么用泛型
反射就是在程序运行过程中,动态的从类的字节码文件(class文件)中获取类或者对象中的属性。
三种反射方式获得实例对象
public class ReflectDemo {
public static void main(String[] args) throws ClassNotFoundException {
// Class.forName("");
Class c1 = Class.forName("com.api.reflect.User");
System.out.println(c1);
// 类名.class
Class c2 = User.class;
System.out.println(c2);
// .getClass
User user = new User();
Class c3 = user.getClass();
System.out.println(c3);
System.out.println(c1 == c2);
System.out.println(c1 == c3);
System.out.println(c2 == c3);
}
}
获取成员变量
public Field getField(String name):获取指定名称的成员变量(public)。
public Field[]getFields():获取全部成员变量(public)。
public Field getDeclaredField(String name):不考虑修饰符。
public Field[]getDeclaredFields():不考虑修饰符。
public class User implements Serializable {
public String name;
protected Integer age;
Integer sex;
private String phone;
public User() {
}
public User(String name, Integer age, Integer sex, String phone) {
this.name = name;
this.age = age;
this.sex = sex;
this.phone = phone;
}
private User(String name, Integer age) {
this.name = name;
this.age = age;
}
.........省略set、get
public void setPhone(String phone) {
this.phone = phone;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
", phone='" + phone + '\'' +
'}';
}
public void run() {
System.out.println("跑步...");
}
private void eat(String username) {
System.out.println(username + "正在吃饭...");
}
}
上面的user对象编译成class文件,下面程序用反射机制获取成员变量;
public static void main(String[]args)throws Exception{
// 1,通过Class.forName方式获取User对象
Class aClass=Class.forName("com.api.reflect.User");
// 获取public修饰的成员变量
Field[]fields=aClass.getFields();
for(Field field:fields){
System.out.println("1,public修饰的成员变量--->"+field);
}
// 2,获取指定成员变量名称
Field name=aClass.getField("name");
System.out.println("2,指定成员变量名称--->"+name);
// 3,获取全部的成员变量,忽略修饰符
Field[]declaredFields=aClass.getDeclaredFields();
for(Field declaredField:declaredFields){
System.out.println("3,不考虑修饰符,获取全部成员变量--->"+declaredField);
}
// 4,获取指定成员变量
Field phone=aClass.getDeclaredField("phone");
// 获取访问权限,暴力反射
phone.setAccessible(true);
System.out.println("4,暴力反射--->"+phone);
}
构造方法
public Constructor getConstructor(Class>...parameterTypes)
public Constructor>[]getConstructors()
public Constructor getDeclaredConstructor(Class>...parameterTypes)
public Constructor>[]getDeclaredConstructors()
public static void main(String[]args)throws Exception{
// 通过Class.forName方式获取User对象
Class aClass=Class.forName("com.api.reflect.User");
// 1,获取指定构造方法(public),参数是可变参数
Constructor constructor=aClass.getConstructor(String.class,Integer.class,Integer.class,String.class);
// 实例化对象
Object user=constructor.newInstance("张三",20,1,"123456");
System.out.println(user);
// 2,获取全部构造方法(public)
Constructor[]constructors=aClass.getConstructors();
for(Constructor constructor1:constructors){
System.out.println(constructor1);
}
// 3,不考虑修饰符,获取指定构造方法
Constructor declaredConstructor=aClass.getDeclaredConstructor(String.class,Integer.class);
// 获取权限
declaredConstructor.setAccessible(true);
Object declaredUser=declaredConstructor.newInstance("李四",21);
System.out.println("不考虑修饰符--->"+declaredUser);
// 4,获取全部构造方法
Constructor[]declaredConstructors=aClass.getDeclaredConstructors();
for(Constructor declaredConstructor1:declaredConstructors){
System.out.println("不考虑修饰符--->"+declaredConstructor1);
}
}
成员方法:
public Method getMethod(String name,Class>...parameterTypes)
public Method[]getMethods()
public Method getDeclaredMethod(String name,Class>...parameterTypes)
public Method[]getDeclaredMethods()
public static void main(String[]args)throws Exception{
// 通过Class.forName方式获取User对象
Class aClass=Class.forName("com.api.reflect.User");
// 1,获取指定成员方法,public修饰
Method methodRun=aClass.getMethod("run");
// 实例化User,并调用invoke()执行方法
Object user=aClass.newInstance();
methodRun.invoke(user);
// 2,获取全部成员方法,public修饰
Method[]methods=aClass.getMethods();
for(Method method:methods){
System.out.println(method);
}
// 3,获取私有成员方法
Method eat=aClass.getDeclaredMethod("eat",String.class);
// 获取权限
eat.setAccessible(true);
// 执行方法
eat.invoke(user,"小李");
}
优点:1、可以在程序运行的过程中,动态的操作这些对象。2、可以解耦,提高程序的可扩展性。
缺点:1、因为是JVM操作,所以对于性能来说会有所下降。2、容易对程序源码造成一定的混乱。
Enum关键字在java5开始引入,表示一种特殊类型的类,继承自java.lang.Enum类。被enum关键字修饰的类型就是枚举类型,提供了一些基本方法, 如果枚举值不添加任何方法,默认值从0开始。例如:
enum Color { RED, GREEN, BLUE } values();
返回enum实例的数组,而且该数组中的元素严格保持在enum中声明时的顺序。
name():返回实例名。
ordinal():返回实例声明时的次序,从0开始。
getDeclaringClass():返回实例所属的enum类型。
equals():判断是否为同一个对象。
可以使用 ==来比较enum实例。
此外,java.lang.Enum实现了Comparable和Serializable接口,所以也提供compareTo()方法。枚举 - C++中文 - API参考文档
枚举和常量对比,允许进行编译时检查,预先记录可接受值的列表,并避免由于传入无效值而引起的意外行为。
举例:短信验证码有几种不同的用途,定义:
publicenum PinType{
REGISTER(100000, "注册使用"),
FORGET_PASSWORD(100001, "忘记密码使用"),
UPDATE_PHONE_NUMBER(100002, "更新手机号码使用");
privatefinalint code;
privatefinal String message;
PinType( int code, String message){
this.code = code;
this.message = message;
}
public int getCode () {
return code;
}
public String getMessage () {
return message;
}
@Override
public String toString () {
return "PinType{" +
"code=" + code +
", message='" + message + '\'' +
'}';
}
}
实际使用:
System.out.println(PinType.FORGET_PASSWORD.getCode());
System.out.println(PinType.FORGET_PASSWORD.getMessage());
System.out.println(PinType.FORGET_PASSWORD.toString());
Output:
100001
忘记密码使用
PinType{code=100001, message='忘记密码使用'}
错误码枚举类型的定义
public enum ErrorCodeEn {
OK(0, "成功"),
ERROR_A(100, "错误A"),
ERROR_B(200, "错误B");
ErrorCodeEn(int number, String description) {
this.code = number;
this.description = description;
}
private int code;
private String description;
public int getCode() {
return code;
}
public String getDescription() {
return description;
}
public static void main(String args[]) {
for (ErrorCodeEn s : ErrorCodeEn.values()) {
System.out.println("code: " + s.getCode() + ", description: " + s.getDescription());
}
}
}
枚举可以实现接口
通过实现接口,可以约束它的方法。
public interface INumberEnum {
int getCode();
String getDescription();
}
public enum ErrorCodeEn2 implements INumberEnum {
OK(0, "成功"),
ERROR_A(100, "错误A"),
ERROR_B(200, "错误B");
ErrorCodeEn2(int number, String description) {
this.code = number;
this.description = description;
}
private int code;
private String description;
@Override
public int getCode() {
return code;
}
@Override
public String getDescription() {
return description;
}
}
通过枚举实现一些设计模式
策略枚举:
enum PayrollDay {
MONDAY(PayType.WEEKDAY), TUESDAY(PayType.WEEKDAY), WEDNESDAY(
PayType.WEEKDAY), THURSDAY(PayType.WEEKDAY), FRIDAY(PayType.WEEKDAY), SATURDAY(
PayType.WEEKEND), SUNDAY(PayType.WEEKEND);
private final PayType payType;
PayrollDay(PayType payType) {
this.payType = payType;
}
double pay(double hoursWorked, double payRate) {
return payType.pay(hoursWorked, payRate);
}
private enum PayType {
WEEKDAY {
double overtimePay(double hours, double payRate) {
return hours <= HOURS_PER_SHIFT ? 0 : (hours - HOURS_PER_SHIFT)
* payRate / 2;
}
},
WEEKEND {
double overtimePay(double hours, double payRate) {
return hours * payRate / 2;
}
};
private static final int HOURS_PER_SHIFT = 8;
abstract double overtimePay(double hrs, double payRate);
double pay(double hoursWorked, double payRate) {
double basePay = hoursWorked * payRate;
return basePay + overtimePay(hoursWorked, payRate);
}
}
}
测试:
System.out.println("时薪100的人在周五工作8小时的收入" + PayrollDay.FRIDAY.pay(8.0, 100));
System.out.println("时薪100的人在周六工作8小时的收入" + PayrollDay.SATURDAY.pay(8.0, 100));
Java泛型(generics)是JDK5开始引入的一个新特性,指所操作的数据类型被指定为一个参数。
在没有泛型的情况的下,通过对类型Object的引用来实现参数的"任意化","任意化"带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这本身就是一个安全隐患。那么泛型的好处就是在编译的时候能够检查类型安全,并且所有的强制转换都是自动和隐式的。
? 表示不确定的java类型
T (type) 表示具体的一个java类型
K V (key value) 分别代表java键值中的Key Value
E (element) 代表Element
?无界通配符
有一个父类Animal和N个子类:
static int countLegs (List extends Animal > animals ) {
int retVal = 0;
for (Animal animal : animals){
retVal += animal.countLegs();
}
return retVal;
}
static int countLegs1 (List animals){
int retVal = 0;
for (Animal animal : animals ){
retVal += animal.countLegs();
}
return retVal;
}
public static void main(String[] args) {
List dogs = new ArrayList<>();
// 不会报错
countLegs( dogs );
// 报错
countLegs1(dogs);
}
对于不确定要操作的类型,可以使用无限制通配符(即 >),表示可以持有任何类型。像countLegs方法中,限定了上界,但是不关心具体类型是什么,所以对于传入的Animal的所有子类都可以支持,而countLegs1 就不行。
上界通配符 < ? extends E>
上届:用extends关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的子类。在类型参数中使用extends表示这个泛型中的参数必须是E或者E的子类,这样有两个好处:
如果传入的类型不是E 或者 E 的子类,编译不成功
private E test(K arg1, E arg2){
E result = arg2;
arg2.compareTo(arg1);
//.....
return result;
}
类型参数列表中如果有多个类型参数上限,用逗号分开
下界通配符 < ? super E>
下界: 用super进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型
private void test(List super T> dst, List src){
for (T t : src) {
dst.add(t);
}
}
public static void main(String[] args) {
List dogs = new ArrayList<>();
List animals = new ArrayList<>();
new Test3().test(animals,dogs);
}
// Dog 是 Animal 的子类
class Dog extends Animal {
}
dst类型 “大于等于” src 的类型,这里的“大于等于”是指 dst 表示的范围比 src 要大,因此装得下 dst 的容器也就能装 src 。
?和 T 的区别
T 是一个确定的类型,通常用于泛型类和泛型方法的定义,?是一个 不确定 的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法。比如如下这种 :
区别1:通过T来确保泛型参数的一致性
public void test(List dest, List src)
//通配符是不确定的,所以这个方法不能保证两个List具有相同的元素类型
public void test(List extends Number> dest, List extends Number> src)
区别2:类型参数可以多重限定而通配符不行
public class Multilimit implements MultilimitInterfaceA,MultilimitInterfaceB{
//使用&符号设定多重边界
public static void test(T t){
}
}
interface MultilimitInterfaceA{}
interface MultilimitInterfaceB{}
区别3:通配符可以使用超类限定而类型参数不行 类型参数 T只具有一种类型限定方式:
T extends A
但是通配符 ? 可以进行 两种限定:
? extends A
? super A
Class和 Class>区别
常见的是在反射场景下的使用
// 通过反射的方式生成multiLimit对象
MultiLimit multiLimit = (MultiLimit)
Class.forName("com.glmapper.bridge.boot.generic.MultiLimit").newInstance();
对于上述代码,在运行期,如果反射的类型不是MultiLimit类,那么一定会报
ava.lang.ClassCastException错误。
Class在实例化的时候,T 要替换成具体类。Class>它是个通配泛型,? 可以代表任何类型,
所以主要用于声明时的限制情况。