JDK 1.5 新特性

文章目录

1、JDK 1.5 新特性

JDK1.5 一个重要主题就是通过新增一些特性来简化开发。

1.1、自动装箱和拆箱

1.1.1、什么是自动装箱和拆箱

  • 装箱

是把基本数据类型装箱成对象数据类型。

  • 拆箱

是把对象数据类型拆箱成基本数据类型。

1.1.2、自动装箱拆箱要点

  • 1.5 以前需要调用 valueOf() 方法手动装箱。使用 intValue() ,doubleValue() 等这类的方法手动拆箱。

  • 1.5 以后才有自动装箱和拆箱,自动装箱时,编译器调用 valueOf 将原始类型值转换成对象。自动拆箱时,编译器通过调用类似intValue(),doubleValue()等这类的方法将对象转换成原始类型值。

基本数据类型对象数据类型
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
booleanBoolean
charCharachter
  • 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
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值