Java新特性(一 · JDK1.5)

这里的Java新特性是从JDK1.5开始,这些新特性主要还是以理解为主。

Java5 - Java10新特性:

一、JDK1.5新特性

1、自动装箱与拆箱

什么是自动装箱拆箱?
简单一点说,装箱就是自动将基本数据类型转换为包装器类型;拆箱就是自动将包装器类型转换为基本数据类型。

Integer i = 10;  //装箱
int n = i;   //拆箱

下面我们来看看需要装箱拆箱的类型有哪些:

在Java中每种基本数据类型都会存在对应的包装器类型,为什么这么设计?

Java是一个面相对象的编程语言,基本类型并不具有对象的性质,为了让基本类型也具有对象的特征,就出现了包装类型(如我们在使用集合类型Collection时就一定要使用包装类型而非基本类型),它相当于将基本类型“包装起来”,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。

关于装箱与拆箱常见面试题:

public class Main {
    public static void main(String[] args) {
         
        Integer i1 = 100;
        Integer i2 = 100;
        Integer i3 = 200;
        Integer i4 = 200;
         
        System.out.println(i1==i2);    //true
        System.out.println(i3==i4);    //false
    }
}

为什么会这样呢?看下Integer的valueOf方法的具体实现:

public static Integer valueOf(int i) {
	final int offset = 128;
	if (i >= -128 && i <= 127) { // must cache 
	    return IntegerCache.cache[i + offset];
	}
        return new Integer(i);
    }

而其中IntegerCache类的实现为:

private static class IntegerCache {
    private IntegerCache(){}
    static final Integer cache[] = new Integer[-(-128) + 127 + 1];

    static {
        for(int i = 0; i < cache.length; i++)
	    cache[i] = new Integer(i - 128);
    }
}

由上可知,Integer包装类型如果数值在[-128,127]之间,便返回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象。

我们会联想到其他的包装类类型是否也有这样的规律,我们看下Double类

public class Main {
    public static void main(String[] args) {
         
        Double i1 = 100.0;
        Double i2 = 100.0;
        Double i3 = 200.0;
        Double i4 = 200.0;
         
        System.out.println(i1==i2);    //false
        System.out.println(i3==i4);    //false
    }
}

为什么比较结果都是false呢?看下Double的valueof方法的实现:

public static Double valueOf(double d) {
        return new Double(d);
    }

想想原因很简单:在某个范围内的整型数值的个数是有限的,而浮点数却不是。

注意,Integer、Short、Byte、Character、Long这几个类的valueOf方法的实现是类似的。Double、Float的valueOf方法的实现是类似的。

对于Boolean类型比较一下就一目了然:

public class Main {
    public static void main(String[] args) {
         
        Boolean i1 = false;
        Boolean i2 = false;
        Boolean i3 = true;
        Boolean i4 = true;
         
        System.out.println(i1==i2);    //true
        System.out.println(i3==i4);    //true
    }
}

源码:

public static Boolean valueOf(boolean b) {
        return (b ? TRUE : FALSE);
    }

TRUE和FALSE都是静态常量

public static final Boolean TRUE = new Boolean(true);

public static final Boolean FALSE = new Boolean(false);

另外还有一点需要注意:当 "=="运算符的两个操作数都是包装器类型的引用,则是比较指向的是否是同一个对象,而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程),对基础数据类型进行运算。对于包装器类型,equals方法并不会进行类型转换。

public class Main {
    public static void main(String[] args) {
         
        Integer a = 1;
        Integer b = 2;
        Integer c = 3;
        Integer d = 3;
        Integer e = 321;
        Integer f = 321;
        Long g = 3L;
        Long h = 2L;
         
        System.out.println(c==d);            //true
        System.out.println(e==f);            //false
        System.out.println(c==(a+b));        //true
        System.out.println(c.equals(a+b));   //true
        System.out.println(g==(a+b));        //true
        System.out.println(g.equals(a+b));   //false
        System.out.println(g.equals(a+h));   //true
    }
}

equals()方法源码

//Integer类中
public boolean equals(Object obj) {
	if (obj instanceof Integer) {
	    return value == ((Integer)obj).intValue();
	}
	return false;
    }

//Long类中
public boolean equals(Object obj) {
    if (obj instanceof Long) {
	return value == ((Long)obj).longValue();
    }
	return false;
}

我们指定equals比较的是内容本身,并且我们也可以看到equal的参数是一个Object对象,我们传入的是一个int类型,所以首先会进行装箱,然后比较,之所以返回true,是由于它比较的是对象里面的value值。当内容和类型都相同时才会返回true。

2、枚举

enum 的全称为 enumeration, 是 JDK 1.5  中引入的新特性,存放在 java.lang 包中。枚举主要用于定义一组可以使用的类对象,这样在使用时只能通过固定的几个对象来进行类的操作。

比如定义一个季节类时,在没有枚举类型前我们通常会使用常量的方式:

/*
 * 季节类
 * 有且仅有4个对象,且对象的属性是固定的
 * 手动实现枚举类
 */

public class Season {
	//季节属性:属性是固定的(外界不可访问,当前类不可以修改)
	private final String SEASON_NAME;
	private final String SEASON_DESC;
	//对象只能由本类提供
	private Season(String season_name, String season_desc) {
		this.SEASON_NAME = season_name;
		this.SEASON_DESC = season_desc;
	}
	public String getSEASON_NAME() {
		return SEASON_NAME;
	}
	public String getSEASON_DESC() {
		return SEASON_DESC;
	}
	
	//在本类中创建4个季节对象(不能修改)
	public static final Season SPRING = new Season("春天","雨想衣裳花想荣,春风芙兰露华荣");
	public static final Season SUMMER = new Season("夏天","接天莲花无穷尽,映日荷花别样红");
	public static final Season OUTUMN = new Season("秋天","月落乌啼霜满天,江枫渔火对愁眠");
	public static final Season WINTER = new Season("冬天","忽如一夜春风来,千树万树梨花开");
	@Override
	public String toString() {
		return "Season [SEASON_DESC=" + SEASON_DESC + ", SEASON_NAME="
				+ SEASON_NAME + "]";
	}
}

测试:

public classs Test{
    public static void main(String[] args){
	//获取Season对象
	Season s = Season.SPRING;
	System.out.println(s);
    }
}

/*
运行结果为:
Season [SEASON_DESC=雨想衣裳花想荣,春风芙兰露华荣, SEASON_NAME=春天]
*/

这样的定义方式并没有什么错,但为了提高类型安全和使用方便性我们引入了枚举类型

//使用enum定义枚举类
public enum SeasonEnum{
	//枚举类的实例,必须要在最前面给出
	SPRING("春天","雨想衣裳花想荣,春风芙兰露华荣"), 
	SUMMER("夏天","接天莲花无穷尽,映日荷花别样红"),
	OUTUMN("秋天","月落乌啼霜满天,江枫渔火对愁眠"),
	WINTER("冬天","忽如一夜春风来,千树万树梨花开");
        //属性:固定,不可修改
	private final String SEASON_NAME;
	private final String SEASON_DESC;
	public String getSEASON_NAME() {
		return SEASON_NAME;
	}
	public String getSEASON_DESC() {
		return SEASON_DESC;
	}
	private SeasonEnum(String season_name, String season_desc) {
		SEASON_NAME = season_name;
		SEASON_DESC = season_desc;
	}
	public String toString() {
		return "Season [SEASON_DESC=" + SEASON_DESC + ", SEASON_NAME="
				+ SEASON_NAME + "]";
	}
}

 测试:

public class Test{
    public static void main(String[] args){
	//获取Season对象
	SeasonEnum s = SeasonEnum.SUMMER;
	System.out.println(s);
    }
}

/*
运行结果为:
Season [SEASON_DESC=雨想衣裳花想荣,春风芙兰露华荣, SEASON_NAME=春天]
*/

 枚举只需要使用enum关键字定义就可以,但是严格来讲,枚举只是类结构的加强而已。因为在Java中使用enum定义的枚举类就相当于默认继承java.lang.Enum类,此类定义如下:

public abstract class Enum<E extends Enum<E>>
        implements Comparable<E>, Serializable

 Enum类中定义的方法:

protected  Objectclone()    抛出 CloneNotSupportedException。
 intcompareTo(E o)      比较此枚举与指定对象的顺序。
 booleanequals(Object other)     当指定对象等于此枚举常量时,返回 true。
protected  voidfinalize()      枚举类不能有 finalize 方法。
 Class<E>getDeclaringClass()       返回与此枚举常量的枚举类型相对应的 Class 对象。
 inthashCode()     返回枚举常量的哈希码。
 Stringname()       返回此枚举常量的名称,在其枚举声明中对其进行声明。
 intordinal()    返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零)。
 StringtoString()     返回枚举常量的名称,它包含在声明中。
static <T extends Enum<T>> TvalueOf(Class<T> enumType, String name)      返回带指定名称的指定枚举类型的枚举常量。

 实例:

public class Test{
    public static void main(String[] args){
	//获取所有枚举对象
	SeasonEnum[] seasons = SeasonEnum.values();
	for (SeasonEnum se : seasons) {
		System.out.println(se);
	}
	
	//将字符串转成枚举对象
	String s = "SPRING";
	//参数1:枚举类型;参数2:对应字符串
	SeasonEnum se = Enum.valueOf(SeasonEnum.class, s);
	System.out.println(se.getDeclaringClass());
        System.out.println(se);
        System.out.println(se.name());
        //返回枚举常量所在位置
        System.out.println(se.ordinal());
    }
}

/*
运行结果:
Season [SEASON_DESC=雨想衣裳花想荣,春风芙兰露华荣, SEASON_NAME=春天]
Season [SEASON_DESC=接天莲花无穷尽,映日荷花别样红, SEASON_NAME=夏天]
Season [SEASON_DESC=月落乌啼霜满天,江枫渔火对愁眠, SEASON_NAME=秋天]
Season [SEASON_DESC=忽如一夜春风来,千树万树梨花开, SEASON_NAME=冬天]
class se02.day07.enumdemo.SeasonEnum
Season [SEASON_DESC=雨想衣裳花想荣,春风芙兰露华荣, SEASON_NAME=春天]
SPRING
0
*/

常见面试题:请解释一下enum和Enum的关系。

enum是JDK1.5之后定义的新关键字,主要用于定义枚举类型,在Java中每一个使用enum定义的枚举类型实际上都表示一个类默认继承了Enum类。

枚举在switch语句中的使用

  • 在JDK1.5之前,switch只能操作int或char型数据;
  • 在JDK1.5之后,switch可以操作enum型
  • 在JDK1.7之后,switch可以操作String型

示例:

enum Color {GREEN,RED,BLUE}

public class EnumDemo4 {

    public static void printName(Color color){
        switch (color){
            case BLUE: //无需使用Color进行引用
                System.out.println("蓝色");
                break;
            case RED:
                System.out.println("红色");
                break;
            case GREEN:
                System.out.println("绿色");
                break;
        }
    }

    public static void main(String[] args){
        printName(Color.BLUE);
        printName(Color.RED);
        printName(Color.GREEN);

        //蓝色
        //红色
        //绿色
    }
}

枚举与接口

所有的枚举都继承自java.lang.Enum类。由于Java 不支持多继承,所以枚举对象不能再继承其他类。 但可以实现多个接口。

示例1:像一般实现接口规律一样实现接口中的所有方法,同时枚举类中可以重写toString()方法,有自己的属性和方法

interface Inter {
	String getInfo();
}
public enum SeasonEnumImpl implements Inter{
	//枚举类的实例,必须要在最前面给出
	SPRING("春天","雨想衣裳花想荣,春风芙兰露华荣"), 
	SUMMER("夏天","接天莲花无穷尽,映日荷花别样红"),
	OUTUMN("秋天","月落乌啼霜满天,江枫渔火对愁眠"),
	WINTER("冬天","忽如一夜春风来,千树万树梨花开");
	//属性:固定,不可修改
	private final String SEASON_NAME;
	private final String SEASON_DESC;
        public String getSEASON_NAME() {
		return SEASON_NAME;
	}
	public String getSEASON_DESC() {
		return SEASON_DESC;
	}
	
	private SeasonEnumImpl(String season_name, String season_desc) {
		SEASON_NAME = season_name;
		SEASON_DESC = season_desc;
	}
	public String toString() {
		return "Season [SEASON_DESC=" + SEASON_DESC + ", SEASON_NAME="
				+ SEASON_NAME + "]";
	}
	@Override
	public String getInfo() {
		switch(this){
			case SPRING:
				return "春天";
			case SUMMER:
				return "春天";
			case OUTUMN:
				return "春天";
			case WINTER:
				return "春天";
		}
		return null;
	}
}

测试:

public class Test{
    public static void main(String[] args){
	String info = SeasonEnumImpl.SPRING.getInfo();
	System.out.println(info);
    }
}

运行结果为:
春天

 示例2:采用匿名内部类的方式实现接口

public enum SeasonEnumImpl02 implements Inter{
	//枚举类的实例,必须要在最前面给出
	SPRING("春天","雨想衣裳花想荣,春风芙兰露华荣"){
		@Override
		public String getInfo() {
			return "春天";
		}
	}, 
	SUMMER("夏天","接天莲花无穷尽,映日荷花别样红"){
		@Override
		public String getInfo() {
			return "夏天";
		}
	},
	OUTUMN("秋天","月落乌啼霜满天,江枫渔火对愁眠"){
		@Override
		public String getInfo() {
			return "秋天";
		}
	},
	WINTER("冬天","忽如一夜春风来,千树万树梨花开"){
		@Override
		public String getInfo() {
			return "冬天";
		}
	};
        //属性:固定,不可修改
	private final String SEASON_NAME;
	private final String SEASON_DESC;

	public String getSEASON_NAME() {
		return SEASON_NAME;
	}
	public String getSEASON_DESC() {
		return SEASON_DESC;
	}

	private SeasonEnumImpl02(String season_name, String season_desc) {
		SEASON_NAME = season_name;
		SEASON_DESC = season_desc;
	}
	public String toString() {
		return "Season [SEASON_DESC=" + SEASON_DESC + ", SEASON_NAME="
				+ SEASON_NAME + "]";
	}
}

测试:

public class Test{
    public static void main(String[] args){
	SeasonEnumImpl02[] ses = SeasonEnumImpl02.values();
	for (SeasonEnumImpl02 se : ses) {
		System.out.println(se.getInfo());
	}
    }
}

 运行结果为:

春天
夏天
秋天
冬天

示例3:使用接口组织枚举

public interface Food {  
    enum Coffee implements Food{  
        BLACK_COFFEE,DECAF_COFFEE,LATTE,CAPPUCCINO  
    }  
    enum Dessert implements Food{  
        FRUIT, CAKE, GELATO  
    }  
}

测试:

public class Test {
        
    public  static void main(String[] args) {  
        for (Dessert dessertEnum : Food.Dessert.values()) {  
            System.out.print(dessertEnum + "  ");  
        }  
        System.out.println();    
        for (Coffee coffee : Food.Coffee.values()) {  
            System.out.print(coffee + "  ");  
        }  
        System.out.println();  
        //搞个实现接口,来组织枚举,简单讲,就是分类吧。如果大量使用枚举的话,这么干,在写代码的时候,就很方便调用啦。  
        //还有就是个“多态”的功能吧,  
        Food food = Food.Dessert.CAKE;  
        System.out.println(food);  
        food = Coffee.BLACK_COFFEE;  
        System.out.println(food);  
    } 
}

运行结果:

FRUIT CAKE GELATO
BLACK_COFFEE DECAF_COFFEE LATTE CAPPUCCINO
CAKE
BLACK_COFFEE

关于枚举接口的使用

java.util.EnumSet和java.util.EnumMap是两个枚举集合。EnumSet保证集合中的元素不重复;EnumMap中的 key是enum类型,而value则可以是任意类型。

①EnumSet类的用法

EnumSet继承AbstractSet,AbstractSet实现了set接口。

public abstract class EnumSet<E extends Enum<E>> extends AbstractSet<E>
    implements Cloneable, java.io.Serializable

 API中的方法:

static <E extends Enum<E>> EnumSet<E>allOf(Class<E> elementType)创建一个包含指定元素类型的所有元素的枚举 set。

 EnumSet<E>

clone()  返回 set 的副本。

static <E extends Enum<E>> EnumSet<E>

complementOf(EnumSet<E> s)创建一个其元素类型与指定枚举 set 相同的枚举 set,最初包含指定 set 中所 包含的此类型的所有元素。
static <E extends Enum<E>> EnumSet<E>copyOf(Collection<E> c)  创建一个从指定 collection 初始化的枚举 set。

 

static <E extends Enum<E>> EnumSet<E>

copyOf(EnumSet<E> s)  创建一个其元素类型与指定枚举 set 相同的枚举 set,最初包含相同的元素(如果有的话)。
static <E extends Enum<E>> EnumSet<E>noneOf(Class<E> elementType)创建一个具有指定元素类型的空枚举 set。
static <E extends Enum<E>> EnumSet<E>of(E e)创建一个最初包含指定元素的枚举 set。

static <E extends Enum<E>> EnumSet<E>

of(E first, E... rest) 创建一个最初包含指定元素的枚举 set。

static <E extends Enum<E>> EnumSet<E>

of(E e1, E e2)  创建一个最初包含指定元素的枚举 set。
static <E extends Enum<E>> EnumSet<E>of(E e1, E e2, E e3)创建一个最初包含指定元素的枚举 set。
static <E extends Enum<E>> EnumSet<E>of(E e1, E e2, E e3, E e4)创建一个最初包含指定元素的枚举 set。
static <E extends Enum<E>> EnumSet<E>of(E e1, E e2, E e3, E e4, E e5) 创建一个最初包含指定元素的枚举 set。
static <E extends Enum<E>> EnumSet<E>range(E from, E to)创建一个最初包含由两个指定端点所定义范围内的所有元素的枚举 set。

示例:

enum SeasonEnum{
	SPRING, SUMMER,OUTUMN,WINTER;
}

public class Test{
        public static void main(String[] args) {
	// 创建一个EnumSet集合,集合元素就是Season枚举类的全部枚举值
	EnumSet<SeasonEnum> es = EnumSet.allOf(SeasonEnum.class);
	System.out.println(es);//输出[SPRING, SUMMER, OUTUMN, WINTER]
	// 创建一个EnumSet空集合,指定其集合元素是Season类的枚举值。
	EnumSet<SeasonEnum> es2 = EnumSet.noneOf(SeasonEnum.class);
	System.out.println(es2);
	// 手动添加两个元素  
        es2.add(SeasonEnum.WINTER);  
        es2.add(SeasonEnum.SPRING);  
        System.out.println(es2); // 输出[SPRING,WINTER]
        // 以指定枚举值创建EnumSet集合  
        EnumSet<SeasonEnum> es3 = EnumSet.of(SeasonEnum.SUMMER , SeasonEnum.WINTER);  
        System.out.println(es3); // 输出[SUMMER,WINTER] 
        EnumSet<SeasonEnum> es4 = EnumSet.range(SeasonEnum.SUMMER , SeasonEnum.WINTER);  
        System.out.println(es4); // 输出[SUMMER,FALL,WINTER]  
        // 新创建的EnumSet集合的元素和es4集合的元素有相同类型,  
        // es5的集合元素 + es4集合元素 = EnumSet枚举类的全部枚举值  
        EnumSet es5 = EnumSet.complementOf(es4);  
        System.out.println(es5); // 输出[SPRING]
	}
}

②EnumMap类的用法

EnumMap继承AbstractMap,AbstractMap实现了Map接口。EnumMap是不同步的。

public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V>
    implements java.io.Serializable, Cloneable

API中的方法 :

voidclear() 从此映射中移除所有映射关系。
 EnumMap<K,V>clone() 返回此枚举映射的浅表副本。
 booleancontainsKey(Object key) 如果此映射包含指定键的映射关系,则返回 true。
 booleancontainsValue(Object value)  如果此映射将一个或多个键映射到指定值,则返回 true。
 Set<Map.Entry<K,V>>entrySet() 返回此映射中所包含映射关系的 Set 视图。
 booleanequals(Object o)  比较指定对象与此映射的相等性。
 Vget(Object key)返回指定键所映射的值,如果此映射不包含此键的映射关系,则返回 null
 Set<K>keySet()   返回此映射中所包含键的 Set 视图。
 Vput(K key, V value) 将指定值与此映射中指定键关联。
 voidputAll(Map<? extends K,? extends V> m)将指定映射中所有映射关系复制到此映射中。
 Vremove(Object key)  从此映射中移除该键的映射关系(如果存在)。
 intsize()   返回此映射中的键-值映射关系数。
 Collection<V>

values()      返回此映射中所包含值的 Collection 视图。

示例:

public class Test {
	public enum Color{//默认继承  extends Enum类,所以枚举也是个类,既然是类就有构造函数,变量,方法等
	    RED, BLUE, BLACK, YELLOW, GREEN;//下标为0,1,2,3,4
	    @Override
	    public String toString() {
		return super.toString()+"..."+super.ordinal();//ordinal()方法获取下标
            }
	}
 
	public static void main(String[] args) {
	    EnumMap<Color, String> map = new EnumMap<Color,String>(Color.class);
            System.out.println(Color.GREEN);
	    map.put(Color.YELLOW, "黄色");
	    map.put(Color.BLUE, null);
	    //map.put(null, "无"); //会报NullPonitException的错误
	    map.put(Color.RED, "红色");
	    map.put(Color.GREEN, "绿色");
            map.get(Color.BLACK);
	    for (Entry<Color, String> entry : map.entrySet()) {
		System.out.println(entry.getKey() + ":" + entry.getValue());
	    }
	    map.clear();
	    System.out.println(map);
	}
}

 输出结果:

GREEN...4
RED...0:红色
BLUE...1:null
YELLOW...3:黄色
GREEN...4:绿色
{}

3、静态导入

静态导入是JDK1.5中对import语句的增强,语法格式:import static 包名….类名.方法名;或者导入类中所有静态成员,此时方法名用*代替。

静态导入注意事项:

  • 方法或变量必须是静态的
  • 如果有多个同名的静态方法,这个时候要使用,必须加前缀。

示例:

import static java.lang.System.out; 
import static java.lang.Integer.*; 

public class Test {
     
        public static void main(String[] args) { 
            out.println(MAX_VALUE);         //2147483647
            out.println(toHexString(42));   //2a
        } 
}

静态导入节省了大量重复代码的击键次数,但同时也牺牲了代码的可阅读性。

4、可变参

当在Java中调用一个方法时,必须严格的按照方法定义的变量进行参数传递,但是在开发中有可能会出现这样一种情况:不确定要传递的参数个数。从JDK1.5开始,为了解决参数任意多个的问题,专门在方法定义上提供了可变参数的概念,本质上还是基于数组的实现。

语法如下:

[public | protected | private][static][final][abstract] 返回值类型 方法名称(参数类型...变量){
    方法体;    
    [return [返回值];]
}

 注意事项:

  • 可变参数只能作为函数的最后一个参数,但其前面可以有也可以没有任何其他参数

  • 由于可变参数必须是最后一个参数,所以一个函数最多只能有一个可变参数

  • Java的可变参数,会被编译器转型为一个数组

  • 变长参数在编译为字节码后,在方法签名中就是以数组形态出现的。这两个方法的签名是一致的,不能作为方法的重载。如果同时出现,是不能编译通过的。可变参数可以兼容数组,反之则不成立。

  • 可变参的参数类型可以为泛型

可变参在方法重载中注意:

①方法重载优先匹配固定参数

public class Varargs {
    public static void test(String... args) {
        System.out.println("version 1");
    }
    public static void test(String arg1, String arg2) {
        System.out.println("version 2");
    }
    public static void main(String[] args) {
        test("a","b");    //version 2 优先匹配固定参数的重载方法
        test();           //version 1
    }
}

②匹配多个可变参数

调用一个被重载的方法时,如果此调用既能够和两个可变长参数的重载方法匹配,则编译出错:

public class Varargs {
    public static void test(String... args) {
        System.out.println("version 1");
    }
    public static void test(String arg1, String... arg2) {
        System.out.println("version 2");
    }
    public static void main(String[] args) {
        test("a","b");    //Compile error
    }
}

可变参在方法重写中注意:

①避免带有变长参数的方法重载

即便编译器可以按照优先匹配固定参数的方式确定具体的调用方法,但在阅读代码的依然容易掉入陷阱。要慎重考虑变长参数的方法重载。别让 null 值和空值威胁到变长方法。

public class Client {  
     public void methodA(String str,Integer... is){       
     }  
     public void methodA(String str,String... strs){          
     }  
     public static void main(String[] args) {  
           Client client = new Client();  
           client.methodA("China", 0);  
           client.methodA("China", "People");  
           client.methodA("China");       //compile error
           client.methodA("China",null);  //compile error
           //可修改为
           String[] strs = null;  
	   client.methodA("China",strs);
     }  
}

 ②覆写变长方法也要循规蹈矩

// 基类
class Base {
    void print(String... args) {
        System.out.println("Base......test");
    }
}
// 子类,覆写父类方法
class Sub extends Base {
    @Override
    void print(String[] args) {
        System.out.println("Sub......test");
    }
}
public class VarArgsTest2 {
    public static void main(String[] args) {
        // 向上转型
        Base base = new Sub();
        base.print("hello");
        // 不转型
        Sub sub = new Sub();
        sub.print("hello");    //compile error
    }
}

第一个能编译通过,这是为什么呢?事实上,base 对象把子类对象 sub 做了向上转型,形参列表是由父类决定的,当然能通过。而看看子类直接调用的情况,这时编译器看到子类覆写了父类的 print 方法,因此肯定使用子类重新定义的 print 方法,尽管参数列表不匹配也不会跑到父类再去匹配,因为找到了就不再找了,因此有了类型不匹配的错误。

这是个特例,覆写的方法参数列表竟然可以与父类不相同,这违背了覆写的定义,并且会引发莫名其妙的错误。

这里,总结下覆写必须满足的条件:

  • 覆写方法不能缩小访问权限

  • 参数列表必须与被覆写方法相同(包括显示形式)

  • 返回类型必须与被覆写方法的相同或是其子类

  • 覆写方法不能抛出新的异常,或者超出父类范围的异常,但是可以抛出更少、更有限的异常,或者不抛出异常

使用 Object… 作为变长参数:int[] 无法转型为 Object[], 因而被当作一个单纯的数组对象 ; Integer[] 可以转型为 Object[], 可以作为一个对象数组。

public class SeasonTest {
	public static void foo(Object... args) {
	    System.out.println(args.length);
	}
	public static void main(String[] args) {  
	    foo(new String[]{"arg1", "arg2", "arg3"}); //3
	    foo(100, new String[]{"arg1", "arg1"});    //2
	    foo(new Integer[]{1, 2, 3});               //3
	    foo(100, new Integer[]{1, 2, 3});          //2
	    foo(1, 2, 3);                              //3
	    foo(new int[]{1, 2, 3});                   //1      
	} 
}

 反射方法调用时的注意事项

public class Test {
    public static void foo(String... varargs){
        System.out.println(args.length);
    }
    public static void main(String[] args){
        String[] varArgs = new String[]{"arg1", "arg2"};
        try{
            Method method = Test.class.getMethod("foo", String[].class);
            method.invoke(null, varArgs);
            method.invoke(null, (Object[])varArgs);
            method.invoke(null, (Object)varArgs);
            method.invoke(null, new Object[]{varArgs});
        } catch (Exception e){
            e.printStackTrace();
        }
    }
}

 

上面的四个调用中,前两个都会在运行时抛出 java.lang.IllegalArgumentException: wrong number of arguments 异常,后两个则正常调用。

反射是运行时获取的,在运行时看来,可变长参数和数组是一致的,因而方法签名为:

//方法签名 ([Ljava/lang/String;)V // public void foo(String[] varargs)

再来看一下 Method 对象的方法声明:

Object invoke(Object obj, Object... args)

args 虽然是一个可变长度的参数,但是 args 的长度是受限于该方法对象代表的真实方法的参数列表长度的,而从运行时签名来看,([Ljava/lang/String;)V 实际上只有一个形参,即 String[] varargs,因而 invoke(Object obj, Object… args) 中可变参数 args 的实参长度只能为1

//Object invoke(Object obj, Object... args) 
//String[] varArgs = new String[]{"arg1", "arg2"}; 
method.invoke(null, varArgs);               //varArgs长度为2,错误 
method.invoke(null, (Object[])varArgs);     //将String[]转换为Object[],长度为2的,错误 
method.invoke(null, (Object)varArgs);       //将整个String[] 转为Object,长度为1,符合 
method.invoke(null, new Object[]{varArgs}); //Object[]长度为1,正确。上一个和这个是等价的

反射机制:

反射不是jdk1.5的特性,从jdk1.0就有了

Java反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。而这也是Java被视为动态(或准动态,为啥要说是准动态,因为一般而言的动态语言定义是程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。)语言的一个关键性质。

反射的常用类和函数:Java反射机制的实现要借助于4个类:Class,Constructor,Field,Method;其中class代表的是类对象,Constructor-类的构造器对象,Field-类的属性对象,Method-类的方法对象,通过这四个对象我们可以粗略的看到一个类的各个组成部分。其中最核心的就是Class类,它是实现反射的基础。

Class类:反射的基石
概述:Java程序中的各个Java类属于同一类事物,可以用一个类来描述这类事物,就是Class。Class类代表Java类,它的各个实例对象又分别对应各个类在内存中的字节码,一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的,这一个个的空间可分别用一个个的对象来表示。学习反射,首先就要明白Class这个类。每个java类都是Class的一个实例对象。

得到字节码的三种方式:

  1. 类名.class,例如,System.class
  2. 对象.getClass(),例如,new Date().getClass()
  3. Class.forName("类名"),例如,Class.forName("java.util.Date");

注意:Class.forName()返回字节码的方式有两种,第一种是这份字节码曾经被加载过,已经存在于虚拟机中,直接返回。还有一种是java虚拟机中还没有这份字节码,则用类加载器去加载,把加载进来的字节码缓存在虚拟机中以后要得到这份字节码就不用再加载了。

Class类常用方法:

 static Class<?>forName(String className)    返回与带有给定字符串名的类或接口相关联的 Class 对象。
 static Class<?>forName(String name, boolean initialize, ClassLoader loader)
          使用给定的类加载器,返回与带有给定字符串名的类或接口相关联的 Class 对象。
 ClassLoadergetClassLoader()返回该类的类加载器。
 native Class<?>getComponentType()

返回一个数组的代表组件类型。

 Constructor<T>getConstructor(Class<?>... parameterTypes)
          返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法。
 Constructor<?>[]getConstructors()
          返回一个包含某些 Constructor 对象的数组,这些对象反映此 Class 对象所表示的类的所有公共构造方法。
 Constructor<T>

getDeclaredConstructor(Class<?>... parameterTypes)

获得类的指定构造方法,包括private 声明的和继承类,parameterTypes 参数指定构造方法的参数类型。

 Constructor<?>[]getDeclaredConstructors()    获得类的所有构造方法,包括private 声明的和继承类
 FieldgetDeclaredField(String name)   获得类的指定属性。包括private 声明的和继承类
 Field[]getDeclaredFields() 获得类的所有属性。包括private 声明的和继承类
 MethodgetDeclaredMethod(String name, Class<?>... parameterTypes)
          返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。
 Method[]getDeclaredMethods() 返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。
 Class<?>getDeclaringClass()
   如果此 Class 对象所表示的类或接口是另一个类的成员,则返回的 Class 对象表示该对象的声明类。
 FieldgetField(String name)
          返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段。
 Field[]getFields()   返回一个包含某些 Field 对象的数组,这些对象反映此 Class 对象所表示的类或接口的所有可访问公共字段。
 TypegetGenericSuperclass()
          返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的直接超类的 Type。
 MethodgetMethod(String name, Class<?>... parameterTypes)
          返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。
 Method[]getMethods() 返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口那些的类或接口)的公共 member 方法。
 intgetModifiers()返回此类或接口以整数编码的 Java 语言修饰符。
 StringgetName()
          以 String 的形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称。
 PackagegetPackage()  获取此类的包。
 URLgetResource(String name)    查找带有给定名称的资源。
 InputStreamgetResourceAsStream(String name)      查找具有给定名称的资源。
 Class<? super T>getSuperclass()     返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的超类的 Class
 TypeVariable<Class<T>>[]getTypeParameters()     按声明顺序返回 TypeVariable 对象的一个数组,这些对象表示用此 GenericDeclaration 对象所表示的常规声明来声明的类型变量。
 booleanisArray()     判定此 Class 对象是否表示一个数组类。
 booleanisAssignableFrom(Class<?> cls)   判定此 Class 对象所表示的类或接口与指定的 Class 参数所表示的类或接口是否相同,或是否是其超类或超接口。
 booleanisEnum()      当且仅当该类声明为源代码中的枚举时返回 true。
 booleanisInstance(Object obj)      判定指定的 Object 是否与此 Class 所表示的对象赋值兼容。
 booleanisInterface()      判定指定的 Class 对象是否表示一个接口类型。
 booleanisLocalClass()      当且仅当底层类是本地类时返回 true。
 booleanisMemberClass()      当且仅当底层类是成员类时返回 true。
 booleanisPrimitive()     判定指定的 Class 对象是否表示一个基本类型。
 TnewInstance()      创建此 Class 对象所表示的类的一个新实例。
 StringtoString()      将对象转换为字符串。

示例:

package se02.day07.reflect;

public class Person {
	private String name;
	int age;
	public String address;
	public Person() {
	}
	private Person(String name) {
		this.name = name;
	}
	Person(String name, int age) {
		this.name = name;
		this.age = age;
	}
	public Person(String name, int age, String address) {
		this.name = name;
		this.age = age;
		this.address = address;
	}
	public void show() {
		System.out.println("show");
	}
	public void method(String s){
		System.out.println("method"+s);
	}
	public String getString(String s,int i){
		return s+"----"+i;
	}
	private void function() {
		System.out.println("function");
	}
	@Override
	public String toString() {
		return "Person [address=" + address + ", age=" + age + ", name=" + name
				+ "]";
	}
	
}
package se02.day07.reflect;

public class ReflectDemo01 {
	public static void main(String[] args) {
		//方式1
		Person p = new Person();
		Class c = p.getClass();
		System.out.println(c);
		
		//方式2
		Class c2 = Person.class;
		System.out.println(c2);
		
		//方式3
		try {
			Class c3 = Class.forName("se02.day07.reflect.Person");
			System.out.println(c3);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
}

 输出结果:

 class se02.day07.reflect.Person
class se02.day07.reflect.Person
class se02.day07.reflect.Person

Field类:提供类的成员变量信息,就是类的属性。

方法摘要:

 booleanequals(Object obj)    将此 Field 与指定对象比较。
 Objectget(Object obj)       返回指定对象上此 Field 表示的字段的值。
 StringgetName()    返回此 Field 对象表示的字段的名称。
 shortgetShort(Object obj)
          获取 short 类型或另一个通过扩展转换可以转换为 short 类型的基本类型的静态或实例字段的值。
 Class<?>getType()      返回一个 Class 对象,它标识了此 Field 对象所表示字段的声明类型。
 voidset(Object obj, Object value)       将指定对象变量上此 Field 对象表示的字段设置为指定的新值。
 voidsetBoolean(Object obj, boolean z)     将字段的值设置为指定对象上的一个 boolean 值。
 voidsetByte(Object obj, byte b)      将字段的值设置为指定对象上的一个 byte 值。
 voidsetChar(Object obj, char c)      将字段的值设置为指定对象上的一个 char 值。
 voidsetDouble(Object obj, double d)      将字段的值设置为指定对象上的一个 double 值。
 voidsetFloat(Object obj, float f)      将字段的值设置为指定对象上的一个 float 值。
 voidsetInt(Object obj, int i)       将字段的值设置为指定对象上的一个 int 值。
 voidsetLong(Object obj, long l)      将字段的值设置为指定对象上的一个 long 值。
 voidsetShort(Object obj, short s)      将字段的值设置为指定对象上的一个 short 值。
 StringtoString()     返回一个描述此 Field 的字符串。
   boolean

isAccessible()    获取此对象的 accessible 标志的值。从AccessibleObject类中继承的方法

 void

setAccessible(boolean flag)    将此对象的 accessible 标志设置为指示的布尔值。从AccessibleObject类中继承的方法

 注意:setAccessible(ture)://如果是私有字段,要先将该私有字段进行取消权限检查的能力。也称暴力访问。

示例:

package se02.day07.reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

//通过反射获取成员变量并使用
public class ReflectDemo04 {
	public static void main(String[] args) throws Exception {
		Class c = Class.forName("se02.day07.reflect.Person");
		//Filed[] getFileds():获取所有公共的成员变量
		//获取所有的成员变量
		Field[] fields = c.getDeclaredFields();
		for (Field field : fields) {
			System.out.println(field);
		}
		
		//通过无参构造创建一个对象
		Constructor con = c.getConstructor();
		Object obj = con.newInstance();
		//获取单个成员变量(公共)
		//获取address
		Field addressField = c.getField("address");
		addressField.set(obj, "上海");
		System.out.println(obj);
		
		//获取name,并赋值(私有)
		Field nameField = c.getDeclaredField("name");
		nameField.setAccessible(true);
		nameField.set(obj, "老王");
		System.out.println(obj);
		
		//获取age,并赋值(默认default)
		Field ageField = c.getDeclaredField("age");
		ageField.set(obj, 55);
		System.out.println(obj);
	}
}

输出结果:

private java.lang.String se02.day07.reflect.Person.name
int se02.day07.reflect.Person.age
public java.lang.String se02.day07.reflect.Person.address
Person [address=上海, age=0, name=null]
Person [address=上海, age=0, name=老王]
Person [address=上海, age=55, name=老王]

Method类

概念:Method类代表某个类中成员方法。调用某个对象身上的方法,要先得到方法,再针对某个对象调用。

方法摘要:

booleanequals(Object obj)      将此 Method 与指定对象进行比较。
 Class<?>getDeclaringClass()    返回表示声明由此 Method 对象表示的方法的类或接口的 Class 对象。
 ObjectgetDefaultValue()   返回由此 Method 实例表示的注释成员的默认值。
 Type[]getGenericParameterTypes()
          按照声明顺序返回 Type 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型的。
 TypegetGenericReturnType()   返回表示由此 Method 对象所表示方法的正式返回类型的 Type 对象。
 StringgetName()     以 String 形式返回此 Method 对象表示的方法名称。
 Class<?>[]getParameterTypes()
          按照声明顺序返回 Class 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型。
 Class<?>getReturnType()  返回一个 Class 对象,该对象描述了此 Method 对象所表示的方法的正式返回类型。
 TypeVariable<Method>[]getTypeParameters()
          返回 TypeVariable 对象的数组,这些对象描述了由 GenericDeclaration 对象表示的一般声明按声明顺序来声明的类型变量。
 Objectinvoke(Object obj, Object... args)  对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。
 booleanisVarArgs()    如果将此方法声明为带有可变数量的参数,则返回 true;否则,返回 false。
 StringtoGenericString()    返回描述此 Method 的字符串,包括类型参数。
 StringtoString()      返回描述此 Method 的字符串。

注意:

①invoke方法:如果底层是静态的,那么可以忽略指定的obj参数,填充为null。即:如果传递给Method对象的invoke()方法的第一个参数为null,说明Method对象对应的是一个静态方法

② 如果底层方法所需的“形式参数”为0,则所提供的args数组长度可以为0或null。

示例:

package se02.day07.reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

//通过反射获取成员方法并使用成员方法
public class ReflectDemo03 {
	public static void main(String[] args) throws Exception {
		Class c = Class.forName("se02.day07.reflect.Person");
		/*
		 * Method[] getMethods():获取本类和父类里面所有公共的成员方法
		 */
		Method[] methods = c.getMethods();
		for (Method method : methods) {
			System.out.println(method);
		}
		System.out.println("==================================");
		//获取自己类中里面所有的成员方法
		Method[] methods02 = c.getDeclaredMethods();
		for (Method method : methods02) {
			System.out.println(method);
		}
		
		//创建一个对象
		Constructor con = c.getConstructor();
		Object obj = con.newInstance();
		
		/*
		 * 获取单个成员方法
		 * Method getMethod(String name,Class<?> ... parameterTypes)
		 * 参数1:方法名  参数2:方法的参数类型
		 */
		//获取show
		Method m1 = c.getMethod("show");
		//调用show方法
		m1.invoke(obj);
		System.out.println("==============================");
		
		//获取method(),并调用(公共有参)
		Method m2 = c.getMethod("method", String.class);
		m2.invoke(obj, "hello");
		//获取function(),并调用(私有无参)
		Method m3 = c.getDeclaredMethod("function");
		m3.setAccessible(true);
		m3.invoke(obj);
	}
}

 输出结果:

public java.lang.String se02.day07.reflect.Person.toString()
public java.lang.String se02.day07.reflect.Person.getString(java.lang.String,int)
public void se02.day07.reflect.Person.method(java.lang.String)
public void se02.day07.reflect.Person.show()
public final void java.lang.Object.wait() throws java.lang.InterruptedException
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 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 se02.day07.reflect.Person.toString()
private void se02.day07.reflect.Person.function()
public java.lang.String se02.day07.reflect.Person.getString(java.lang.String,int)
public void se02.day07.reflect.Person.method(java.lang.String)
public void se02.day07.reflect.Person.show()
show
==============================
methodhello
function

数组的反射:

数组的反射主要通过java.lang.reflect.Array类完成

Java反射机制通过Array类来处理数组,结合java.lang.Class类,可以在运行时获取数组的相关信息。要和集合包中的java.util.Arrays区分开,Arrays类是一个数组工具类(排序、遍历数组,将数组转换为集合等)。

实例化数组

//如果在编译时知道数组的类型(如int类型),使用new关键字实例化
int[] array = new int[3];
//也可以使用反射:
int[] array = Array.newInstance(int.class,3);

 对数组用Class.forName()不是很直观。例如,你可以像这样访问基本类型数组的Class对象:

Class intArray = Class.forName("[I");

JVM中用字母I代表int。在左边加上[(左中括号)表示int数组的类。这对所有其他的基本类型同样有效。

对于对象,你需要用一个稍微不同的符号:  

Class stringArrayClass = Class.forName("[Ljava.lang.String;");

 注意类名左边的[L和右边的分号(;)。这表示一个你指定类型的对象的数组。

访问数组 
创建数组后,可以通过Array.set()和Array.get()来设置和获取数组中的元素: 

float[] array = (float[]) Array.newInstance(float.class, 3);  
Array.set(array, 0, 16.0f);  
Array.set(array, 1, 12.4f);  
Array.set(array, 2, 16.4f);  
  
System.out.format("array[0]=%f\n", Array.get(array, 0));  
System.out.format("array[1]=%f\n", Array.get(array, 1));  
System.out.format("array[2]=%f\n", Array.get(array, 2));

 输出: 
array[0]=16.000000 
array[1]=12.400000 
array[2]=16.400000 

判断传入的对象是不是数组 

boolean bArray = obj.getClass().isArray();  

获取数组的长度 

int length = java.lang.reflect.Array.getLength(obj); 

 判断数组元素的类型 

try {
	Class intArray = Class.forName("[Ljava.lang.String;");
	System.out.println(intArray.getComponentType());	//class java.lang.String
} catch (ClassNotFoundException e) {
	e.printStackTrace();
}

5、内省(Introspector)

内省是Java语言对JavaBean类属性、事件的一种缺省处理方法。

JavaBean是一种特殊的类,主要用于传递数据信息,这种类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。如果在两个模块之间传递信息,可以将信息封装进JavaBean中,这种对象称为“值对象”(Value Object),或“VO”。方法比较少。这些信息储存在类的私有变量中,通过set()、get()获得。

例如Student类中有属性name,那我们可以通过getName,setName来得到其值或者设置新的值。通过getName/setName来访问name属性,这就是默认的规则。Java中提供了一套API用来访问某个属性的getter/setter方法,通过这些API可以使你不需要了解这个规则(但你最好还是要搞清楚),这些API存放于包java.beans中。

JDK内省类库:

①PropertyDescriptor类:表示JavaBean类通过存储器导出一个属性

构造器:

PropertyDescriptor(String propertyName, Class<?> beanClass)
          通过调用 getFoo 和 setFoo 存取方法,为符合标准 Java 约定的属性构造一个 PropertyDescriptor。
PropertyDescriptor(String propertyName, Class<?> beanClass, String readMethodName, String writeMethodName)
          此构造方法带有一个简单属性的名称和用于读写属性的方法名称。
PropertyDescriptor(String propertyName, Method readMethod, Method writeMethod)
          此构造方法带有某一简单属性的名称,以及用来读取和写入属性的 Method 对象。

方法摘要:

 booleanequals(Object obj)    将此 PropertyDescriptor 与指定对象进行比较。
 Class<?>getPropertyType()   获得属性的 Class 对象。
 MethodgetReadMethod()    获得应该用于读取属性值的方法。
 MethodgetWriteMethod()    获得应该用于写入属性值的方法。
 inthashCode()  返回对象的哈希码。
 voidsetReadMethod(Method readMethod)   设置应该用于读取属性值的方法。
 voidsetWriteMethod(Method writeMethod) 设置应该用于写入属性值的方法。

示例:

Student类:

public class Student {
	private String name;
	private int age;
	public Student() {}
	public Student(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	@Override
	public String toString() {
		return "Student [age=" + age + ", name=" + name + "]";
	}
}

测试类:

package se02.day07.introspector;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class BeanInfoUtil {
	public static void setProperty(Student info, String name){
		PropertyDescriptor propDesc;
		Method methodSetName;
		try {
			propDesc = new PropertyDescriptor(name,Student.class);
			methodSetName = propDesc.getWriteMethod();
			methodSetName.invoke(info,"张三");
		} catch (Exception e) {
			e.printStackTrace();
		}
		System.out.println("set studentName:"+info.getName());
	}
	public static void getProperty(Student info,String name){
		try {
			PropertyDescriptor proDescriptor = new PropertyDescriptor(name,Student.class);
			Method methodGetName = proDescriptor.getReadMethod();
			Object obj = methodGetName.invoke(info);
			System.out.println("get studentName:"+obj);
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}
	public static void main(String[] args) {
		Student student = new Student();
		setProperty(student,"name");
		getProperty(student,"name");
	}
}

输出结果为:

set studentName:张三
get studentName:张三

②Introspector类

将JavaBean中的属性封装起来进行操作。在程序把一个类当做JavaBean来看,就是调用Introspector.getBeanInfo()方法,得到的BeanInfo对象封装了把这个类当做JavaBean看的结果信息,即属性的信息。

方法摘要:

static Stringdecapitalize(String name)
          获得一个字符串并将它转换成普通 Java 变量名称大写形式的实用工具方法。
static voidflushCaches()
          刷新所有 Introspector 的内部缓存。
static voidflushFromCaches(Class<?> clz)
          刷新给定类的 Introspector 的内部缓存信息。
static BeanInfogetBeanInfo(Class<?> beanClass)
          在 Java Bean 上进行内省,了解其所有属性、公开的方法和事件。
static BeanInfogetBeanInfo(Class<?> beanClass, Class<?> stopClass)
          在给定的“断”点之下,在 Java Bean 上进行内省,了解其所有属性和公开的方法。
static BeanInfogetBeanInfo(Class<?> beanClass, int flags)
          在 Java Bean 上进行内省,了解其所有属性、公开的方法和事件,并将结果用一些控制标记表示。
static String[]getBeanInfoSearchPath()
          获得将用来查找 BeanInfo 类的包名称的列表。
static voidsetBeanInfoSearchPath(String[] path)
          更改将用来查找 BeanInfo 类的包名称的列表。

BeanInfo类

PropertyDescriptor[]getPropertyDescriptors()
          获得 JavaBean的所有属性描述器

实例:

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;

public class BeanInfoUtil {
	public static void setPropertyByIntrospector(Student info, String name){
		try {
			BeanInfo beanInfo = Introspector.getBeanInfo(Student.class);
			PropertyDescriptor[] proDescriptors = beanInfo.getPropertyDescriptors();
			if(proDescriptors != null && proDescriptors.length>0){
				for(PropertyDescriptor propDesc:proDescriptors){
					if(propDesc.getName().equals(name)){
						Method methodSetName = propDesc.getWriteMethod();
						methodSetName.invoke(info, "李四");
						break;
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		System.out.println("set studentName:"+info.getName());
	}
	public static void getPropertyByIntrospector(Student info,String name){
		try {
			BeanInfo beanInfo = Introspector.getBeanInfo(Student.class);
			PropertyDescriptor[] proDescriptors = beanInfo.getPropertyDescriptors();
			if(proDescriptors!=null && proDescriptors.length>0){
				for(PropertyDescriptor propDesc:proDescriptors){
					if(propDesc.getName().equals(name)){
						Method methodGetName = propDesc.getReadMethod();
						Object obj = methodGetName.invoke(info);
						System.out.println("get studentName:"+obj);
						break;
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	public static void main(String[] args) {
		Student student = new Student();
		setPropertyByIntrospector(student,"name");
		getPropertyByIntrospector(student,"name");
	}
}

输出结果:

set studentName:李四
get studentName:李四

6、泛型(Generic)

泛型,即“参数化类型”,一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。参数化类型就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用或调用时传入具体的类型(类型实参)。

泛型的本质是为了参数化类型(在不创建新的类型情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称之为泛型类、泛型接口、泛型方法。

泛型的好处:

  1. 将运行期间遇到的问题提前到编译期间
  2. 避免了向下转型
  3. 优化了程序设计,解决了黄色警告

实例1:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class GenericDemo01 {
	public static void main(String[] args) {
		List list = new ArrayList();
		list.add("hello");
		list.add("world");
		list.add("java");
		list.add(100);
		
		Iterator<String> iter = list.iterator();
		while(iter.hasNext()){
			String s = iter.next();
			System.out.println(s);
		}
	}
}

毫无疑问,程序的运行结果会以崩溃结束:

java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
	at se01.day05.GenericDemo01.main(GenericDemo01.java:18)

ArrayList可以存放任意类型,例子中添加了一个String类型,添加了一个Integer类型,再使用时都以String的方式使用,因此程序崩溃了。为了解决类似这样的问题(在编译阶段就可以解决),泛型应运而生。

我们将第一行声明初始化list的代码更改一下,编译器会在编译阶段就能够帮我们发现类似这样的问题。

List<String> list = new ArrayList<String>();
...
//list.add(100); 在编译阶段,编译器就会报错

 实例2:

//泛型类
class ObjectDemo<E>{
	private E e;
	public void setE(E e){
		this.e = e;
	}
	public E getE(){
		return e;
	}
}

//不使用泛型
class ObjectDemo02{
	private Object obj;

	public Object getObj() {
		return obj;
	}

	public void setObj(Object obj) {
		this.obj = obj;
	}
}
public class GenericDemo02 {
	public static void main(String[] args) {
		ObjectDemo<String> od = new ObjectDemo<String>();
		od.setE("张伟");
		System.out.println(od.getE());
		
		ObjectDemo<Integer> od2 = new ObjectDemo<Integer>();
		od2.setE(12);
		int i = od2.getE();
		System.out.println(i);
		
		ObjectDemo02 obj = new ObjectDemo02();
		obj.setObj("最低配");
//		Integer inter =  (Integer) obj.getObj();//.ClassCastException
//		System.out.println(inter);
		
	}
}

由上可知,在不使用时我们会用到Object作为数据类型,在使用时再根据具体的情况向下转型还可能出现java.lang.ClassCastException异常,使用泛型后就避免了这种情况。

泛型的使用:

①泛型类:定义在类上面

格式:public class 类名<泛型类型1,...>

例如:

//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Generic<T,R>{     //一个类上可以定义多种泛型声明
    //key这个成员变量的类型为T,T的类型由外部指定  
    private T key;

    public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
        this.key = key;
    }

    public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
        return key;
    }
    public R fun(T p){  //R是返回值类型,T为方法参数类型
        teturn null;    
    }
}

测试:

public class GenericTest{
    public static void main(String[] args){
       //泛型的类型参数只能是类类型(包括自定义类),不能是简单类型
       //传入的实参类型需与泛型的类型参数类型相同,即为Integer.
       Generic<Integer,String> genericInteger = new Generic<Integer,String>(123456);
       //传入的实参类型需与泛型的类型参数类型相同,即为String.
       Generic<String,Integer> genericString = new Generic<String,Integer>("key_vlaue");
       System.out.println("泛型测试","key is " + genericInteger.getKey());
       System.out.println("泛型测试","key is " + genericString.getKey());
    }
}

输出结果:

泛型测试: key is 123456
泛型测试: key is key_vlaue

 泛型类的所有实例都具有相同的运行时类,而不管它们的实际类型参数如何。

实例:

List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();
System.out.println(l1.getClass()== l2.getClass());    //true

注意:

  • 定义的泛型类,要么传入所有的泛型类型实参,要么不传入任何泛型类型实参。即:Generic<Integer,String>...或Generic...不能是Generic<Integer>...
  • 泛型的类型参数只能是类类型,不能是简单类型。
  • 不能对确切的泛型类型使用instanceof操作。如下操作是非法的,编译时会报错。if(ex_num instanceof Generic<Number>){}

②泛型接口:定义在接口上面

格式:public interface 接口名<泛型类型1,...>

任何情况下如果要使用接口,就必须定义相应的子类,而对于实现了泛型接口的子类而言,有以下两种实现方式:

  • 实现方式一:在子类继续设置泛型标记
  • 在子类不是设置泛型,而为接口明确定义一个泛型类型

示例:

//泛型接口
interface Inter<T>{
	void show(T t);
}

//实现类
//1)知道实现什么泛型
class InterImpl implements Inter<String>{
	@Override
	public void show(String s) {
		System.out.println(s);
	}
}
//2)不知道实现什么泛型
class InterImpl02<T> implements Inter<T>{
	@Override
	public void show(T t) {
		System.out.println(t);
	}
}
public class GenericDemo03 {
	public static void main(String[] args) {
		//1)
		InterImpl i = new InterImpl();
		i.show("hello");           // hello
		
		//2)
		InterImpl02<String> i2 = new InterImpl02<String>();
		i2.show("world");          //world
		InterImpl02<Integer> i3 = new InterImpl02<Integer>();
		i3.show(13);               //13
	}    
}

注意:如果不声明泛型,如:class InterImpl02 implements Inter<T>,编译会报错“Unknown class”

③泛型方法:定义在方法上

格式:修饰符 <泛型类型> 返回值类型 方法名(参数列表){}

注意:

  • 泛型方法即在方法上设置泛型类型,参数中可以出现泛型类或类中未定义的泛型标识
  • 在方法上定义泛型时,这个方法不一定要在泛型类中定义
  • 泛型方法中可以出现任意多个泛型标识符
  • 静态的泛型方法需要额外的泛型声明,即使使用了泛型类声明过的泛型类型
  • 静态方法若有返回值其类型不能为泛型

示例:

//泛型方法
class Generic<T>{//这个类是个泛型类,在上面已经介绍过
	private T key;
	
	//这只是类中一个普通的成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型。
	public T getKey(){
        return key;
        }
	/* 因为在类的声明中并未声明泛型K,所以在使用K做形参和返回值类型时,编译器会无法识别。
	 * K cannot be resolved to a type
	 * public K setKey(K key){	
        this.key = key;
	}*/
	//在泛型类中声明了一个泛型方法,使用泛型T,注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型
	public <T> void show(T t){
		System.out.println(t);
	}
	public <E> T fun(E e){
		System.out.println(e);
		return key;
	}
	//泛型的数量也可以为任意多个 
	public <R,V> R showKeyName(R r,V v){
		return null;
	} 
	/*
     * 如果在类中定义使用泛型的静态方法,需要添加额外的泛型声明(将这个方法定义成泛型方法)
     * 即使静态方法要使用泛型类中已经声明过的泛型也不可以。
     * 如:public static void method(T t){...},此时编译器会提示错误信息:
     * Cannot make a static reference to the non-static type T
     */
	public static <E> void method(E t){}
}
public class GenericTest {
	public static void main(String[] args) {
		Generic od = new Generic();
		od.show("hello");    //hello
		Generic<Integer> od2 = new Generic<Integer>();
		od2.show(132);       //132
	}
}

通配符:

利于泛型技术虽然解决了向下转型所带来的安全隐患问题,但同时又会产生一个新的问题:即便是同一个类,由于设置泛型类型不同,其对象表示的含义也不同,因此不能直接进行引用操作。为了解决这个问题,Java提供通配符“?”解决参数传递问题。

  • ?:表示任意类型
  • ? extends 类:设置泛型上限,可以在声明和方法参数上使用;如? extends Nummber:意味着可以设置Number或者Number的子类(Integer、Double...)
  • ? extends 类:设置泛型下限,方法参数上使用;如 ? extends String:意味着只能设置String或它的父类Object

实例:

import java.util.ArrayList;
import java.util.Collection;

//通配符
class Animal{}
class Cat extends Animal{}
class Dog extends Animal{}
public class GenericDemo05 {
	public static void main(String[] args) {
		//? 任意类型
		Collection<?> c1 = new ArrayList<Animal>();
		Collection<?> c2 = new ArrayList<Cat>();
		Collection<?> c3 = new ArrayList<Dog>();
		
		//? extends E (向下限定)
		Collection<? extends Animal> c4 = new ArrayList<Cat>();
		Collection<? extends Animal> c5 = new ArrayList<Dog>();
		Collection<? extends Animal> c6 = new ArrayList<Animal>();
//		Collection<? extends Animal> c4 = new ArrayList<Object>();
		
		//? super E (向上限定)
		Collection<? super Animal> c7 = new ArrayList<Animal>();
		Collection<? super Animal> c8 = new ArrayList<Object>();
//		Collection<? super Animal> c7 = new ArrayList<Cat>();	
	}
}

Java类型擦除机制:

类型擦除指的是通过类型参数合并,将泛型类型实例关联到同一份字节码上。编译器只为泛型类型生成一份字节码,并将其实例关联到这份字节码上。类型擦除的关键在于从泛型类型中清除类型参数的相关信息,并且在必要的时候添加类型检查和类型转换的方法。 类型擦除可以简单的理解为将泛型java代码转换为普通java代码,只不过编译器更直接点,将泛型java代码直接转换成普通java字节码。 类型擦除的主要过程如下: 1.将所有的泛型参数用其最左边界(最顶级的父类型)类型替换。(这部分内容可以看:Java泛型中extends和super的理解) 2.移除所有的类型参数。
例如:

//编译出错	
//Erasure of method setList(List<Integer>) is the same as another method in type GenericDemo
//即:方法setList(List<Integer>)的擦除与GenericDemo类型中的另一个方法相同
public void setList(List<String> list){}
public void setList(List<Integer> list){}

上例原因就是java类型擦除机制,但类型擦除同样有优点:在不破坏现有类库的情况下,将泛型融入到Java语言。

Java的泛型机制是在编译级别实现的。编译器生成的字节码在运行期间并不包含泛型的类型信息。

import java.util.ArrayList;

public class Q29 {
    public static void main(String[] args) {
        ArrayList<String> al = new ArrayList<String>();
        al.add("a");
        al.add("b");
        accept(al);
    }

    public static void accept(ArrayList<Object> al) {
        for (Object o : al)
            System.out.println(o);
    }
}

原因在于类型擦除。记住:Java的泛型机制是在编译级别实现的。编译器生成的字节码在运行期间并不包含泛型的类型信息。

在编译之后,List<Object>和List<String>将变成List,Object和String类型信息对于JVM来说是不可见的。在编译阶段,编译器发现它们不一致,因此给出了一个编译错误。

不过,你需要知道如果两个泛型类型有相同的参数,它们的继承关系是依据其类型。如ArrayList<String>是Collecton<String>的子类型。

注意:类型擦除后保留的是原始类型,擦除泛型信息,最后的字节码中的类型变量是真正的类型。无论何时定义一个泛型,相应的原始类型都会被自动提供,类型变量擦除,并使用其限定类型(无限定的变量用Object)替换。

如果类型变量有限定,那么原始类型就用第一个边界的类型变量类替换。

比如: Pair这样声明的话

public class Pair<T extends Number> {
        private T value;
	public T getValue(){
		return value;
	}
	public void setValue(T value){
		this.value = value;
	}
}

那么原始类型就是Number。

我们用javap -c className的反编译方式验证一下:

既然Java中有类型擦除机制,那么我们在使用泛型的时候依然可以限定类型呢?原因是Java编译器是通过先检查代码中的泛型类型,然后进行类型擦除,再编译。

类型擦除与多态的冲突和解决方法:

现有这样一个类继承上例中Pair类

class SunInter extends Pair<Integer>{
    @Override  
    public void setValue(Integer value) {  
        super.setValue(value);  
    }  
    @Override  
    public Integer getValue() {  
        return super.getValue();  
    } 
}

 从多态的角度或通过@Override标签可以看出子类重写了父类方法,但实际上,类型擦除后,父类的泛型类型全部变成了原始类型Number,并且JVM通过一种特殊的方法,来完成方法的重写,那就是桥方法

通过javap -c className的方式反编译下SunInter子类的字节码,结果如下:

class se01.day05.SunInter extends se01.day05.Pair<java.lang.Integer> {
  se01.day05.SunInter();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method se01/day05/Pair."<init>":(
)V
       4: return

  public void setValue(java.lang.Integer);    //子类重写的setValue()
    Code:
       0: aload_0
       1: aload_1
       2: invokespecial #16                 // Method se01/day05/Pair.setValue:(
Ljava/lang/Number;)V
       5: return

  public java.lang.Integer getValue();       //子类重写的getValue()
    Code:
       0: aload_0
       1: invokespecial #23                 // Method se01/day05/Pair.getValue:(
)Ljava/lang/Number;
       4: checkcast     #26                 // class java/lang/Integer
       7: areturn

  public java.lang.Number getValue();        //桥方法
    Code:
       0: aload_0
       1: invokevirtual #28                 // Method getValue:()Ljava/lang/Inte
ger;
       4: areturn

  public void setValue(java.lang.Number);     //桥方法
    Code:
       0: aload_0
       1: aload_1
       2: checkcast     #26                 // class java/lang/Integer
       5: invokevirtual #30                 // Method setValue:(Ljava/lang/Integ
er;)V
       8: return
}

由上可知,子类中真正覆盖父类两个方法的就是这两个我们看不到的桥方法。而打在我们自己定义的setvaluegetValue方法上面的@Oveerride只不过是假象。而桥方法的内部实现,就只是去调用我们自己重写的那两个方法。所以,虚拟机巧妙的使用了桥方法,来解决了类型擦除和多态的冲突。不过,要提到一点,这里面的setValuegetValue这两个桥方法的意义又有不同。setValue方法是为了解决类型擦除与多态之间的冲突。而getValue却有普遍的意义。

具体内容参见:https://www.cnblogs.com/wuqinglong/p/9456193.html

泛型与数组:

关于泛型数组的理解真的有点令人恼火(annoying)下面内容看看就行。在The Java™ Tutorials: Generics中讲到了泛型数组,并说道:除非使用通配符,否则一个数组对象的元素不能是泛型即:在java中是”不能创建一个确切的泛型类型的数组”的。也就是说下面的这个例子是不可以的:

List<String>[] ls = new ArrayList<String>[10];//Cannot create a generic array of ArrayList<String>

 而使用通配符创建泛型数组是可以的,如下面这个例子:

List<?>[] ls = new ArrayList<?>[10]; 
//这样也是可以的:
List<String>[] ls = new ArrayList[10];

这么做的原因,是为了防止下述代码产生的类型安全问题:

// Not really allowed.
List<String>[] lsa = new List<String>[10];     //1
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
// Unsound, but passes run time store check
oa[1] = li;

// Run-time error: ClassCastException.
String s = lsa[1].get(0);                      //2

 如果允许泛型数组的存在(第1处代码编译通过),那么在第2处代码就会报出ClassCastException,因为lsa[1]是List<Integer>。Java设计者本着首要保证类型安全(type-safety)的原则,不允许泛型数组的存在,使得编译期就可以检查到这类错误。

解决方案

但是连Java的设计者也承认,这样在使用上很令人恼火(原文是annoying),所以提供了变向的解决方案:显式类型转换

①通配符

The Java™ Tutorials: Generics给出的解决方案如下:

// OK, array of unbounded wildcard type.
List<?>[] lsa = new List<?>[10];                //1
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
// Correct.
oa[1] = li;
// Run time error, but cast is explicit.
String s = (String) lsa[1].get(0);              //2

 在第1处,用?取代了确定的参数类型。根据通配符的定义以及Java类型擦除的保留上界原则,在2处lsa[1].get(0)取出的将会是Object,所以需要程序员做一次显式的类型转换。

②反射

使用java.util.reflect.Array,可以不使用通配符,而达到泛型数组的效果:

List<String>[] lsa = (List<String>[])Array.newInstance(ArrayList.class, 4);     //1
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
// Correct.
oa[1] = li;
// Run time error, but cast is explicit.
String s = lsa[1].get(0);                                                      //2

 可以看到,我们利用了Array.newInstance()生成了泛型数组,这里没有使用任何通配符,在第2处也没有做显式的类型转换,但是在第1处,仍然存在显式类型转换。

类似地,尝试创建元素类型为类型变量的数组对象会导致编译时错误:

//不能用泛型来建立数组的实例
private T[] arr= new T[10];    //Cannot create a generic array of T
//可以参数化数组本身类型
private T[] arr;

<T> T[] makeArray(T t) {
    return new T[100]; // Error.
}

由于类型变量在运行时不存在,因此无法确定实际的数组类型。 

具体内容可参见:https://blog.csdn.net/yi_Afly/article/details/52058708?tdsourcetag=s_pctim_aiomsg

7、foreach循环

foreach语句是java5的新特征之一,在遍历数组、集合方面,foreach为开发人员提供了极大的方便。

foreach 语法格式如下:

for(元素类型t 元素变量x : 遍历对象obj){ 
     引用了x的java语句; 
} 

实例:用foreach遍历数组、集合

import java.util.ArrayList;
import java.util.List;

public class AddForDemo {
	public static void main(String[] args) {
		// foreach遍历数组
		int[] arr = {1,2,3,4,5};
		for(int num:arr){//num指的是arr数组里面所有元素
			System.out.println(num);
		}
		//foreach遍历List
		List<String> list = new ArrayList<String>();
		list.add("hello");
		list.add("world");
		list.add("java");
		for(String str:list){//str指的是list集合中所有元素
			System.out.println(str);
		}
	}
}

注意:foreach虽然能遍历数组或者集合,但是只能用来遍历,无法在遍历的过程中对数组或者集合进行修改,而for循环可以在遍历的过程中对源数组或者集合进行修改。

实例:

public static void main(String[] args) {
    List<String> names = new ArrayList<String>();
    names.add("beibei");
    names.add("jingjing");
    //foreach
    for(String name:names){
      name = "huanhuan";
    }
    System.out.println(Arrays.toString(names.toArray()));
    //for
    for (int i = 0; i < names.size(); i++) {
      names.set(i,"huanhuan");
    }
    System.out.println(Arrays.toString(names.toArray()));
  }
}
 
输出:
[beibei, jingjing]
[huanhuan, huanhuan]

foreach遍历的是一组元素,但可以在外部定义一个索引(int index = 0;),在内部进行自增操作(index++),来实现类似普通for中需要使用索引的操作。

foreach遍历集合类型和数组类型底层实现的不同

  • 集合类型的遍历本质是使用迭代器实现的
  • 数组的遍历是通过for循环来实现的

8、Annotation

什么是注解(Annotation)?

注解是元数据的一种形式,它提供的数据与程序本身无关。注释对它们注释的代码的操作没有直接影响。注解有两种用途一是通过注解检查错误或抑制警告(Warning);二是作为元数据,为程序提供数据,且与程序无关。

jdk1.5之后,最有鲜明特点的莫过于注解技术的提出与应用,利用注解技术可以回避面向对象中覆写方法名称固定的问题,并且其直观的描述也适合开发者进行程序的编写。

在Java SE里为了方便用户编写代码,提供了3种最为常见的基础Annotation定义,分别是

  • @Override                      —— “重写、覆盖”
  • @Deprecated                 ——“废弃的、过时的”
  • @SuppressWarnings      ——“压缩警告”

①准确的覆写:@Override

当进行方法覆写时,为了保证子类所覆写的方法的确是父类中定义过的方法,就可以加上"@Override"注解,这样即用户覆写方法是出现了错误,也可以在编译时直接检查出来。

@Override
public String toString() {
	return "XXX";
}

 注:不写“@Override”在正确覆写时没有任何问题,但是一旦覆写错误将无法验证。

②声明过期操作:@Deprecated

使用“@Deprecated”注解来声明一些过期的不建议使用的方法。如java.util.Date类下的一些方法:

@Deprecated
public Date(int year, int month, int date) {
    this(year, month, date, 0, 0, 0);
}
@Deprecated
public Date(int year, int month, int date, int hrs, int min) {
    this(year, month, date, hrs, min, 0);
}
@Deprecated
public Date(int year, int month, int date, int hrs, int min) {
    this(year, month, date, hrs, min, 0);
}
@Deprecated
public int getYear() {
    return normalize().getYear() - 1900;
}
...

③压制警告:@SuppressWarnings

如果使用了不安全的操作,程序在编译时一定会出现安全警告(例如:使用实例化支持泛型类时,没有指定泛型类型),而在很多情况下,开发者已经明确地知道这些警告信息却执意按照固定方式处理,那么这些警告信息的重复出现就有可能造成开发者困扰,这时可以在有可能出现警告信息的代码上使用“@SuppressWarnings”压制所有出现的警告信息

压制警告有以下三种方式:

  • @SuppressWarnings(“”)
  • @SuppressWarnings({})
  • @SuppressWarnings(value={})

抑制警告的关键字有:

关键字用途
all抑制所有警告
boxing 抑制装箱、拆箱操作时候的警告
cast抑制映射相关的警告
dep-ann抑制启用注释的警告
deprecation抑制过期方法警告
fallthrough抑制确在switch中缺失breaks的警告
finally 抑制finally模块没有返回的警告
hiding抑制相对于隐藏变量的局部变量的警告
incomplete-switch忽略没有完整的switch语句
nls忽略非nls格式的字符
null忽略对null的操作
rawtypes使用generics时忽略没有指定相应的类型
restriction禁止使用与禁止引用相关的警告
serial忽略在serializable类中没有声明serialVersionUID变量
static-access抑制不正确的静态访问方式警告
synthetic-access 抑制子类没有按最优方法访问内部类的警告
unchecked抑制没有进行类型检查操作的警告
unqualified-field-access抑制没有权限访问的域的警告
unused抑制没被使用过的代码的警告
resourceJ2EE,可以使用@Resource来完成依赖注入或者叫资源注入,但是当你在一个类中使用已经使用注解的类,却没有为其注入依赖时,"resource"关键字会抑制其没有注入依赖的警告。

 加上注解之后:

Annotation其实是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。通过使用Annotation,程序开发人员可以在不改变原有逻辑的情况下,在源文件嵌入一些补充信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。

Annotation提供了一条为程序元素设置元数据的方法,从某些方面来看,Annotation就像修饰符一样被使用,可用于修饰包、类、构造器、方法、成员变量、参数、局部变量的声明,这些信息被存储在Annotation的“name=value”对中。Annotation不能影响程序代码的执行,无论增加、删除Annotation,代码都始终如一地执行。

自定义Annotation格式:

public @interface MyAnnotation{
    ...
}

由上格式可知,使用@interface关键字定义新的Annotation类型,看上去很像是一个接口,可以看成是一种特殊的接口。另外还可以为其定义属性,或者引用其他的注解类型,被引用的Annotation称为元注解

Java使用反射机制来获取注解对象,使用Annotation接口来代表程序元素前面的的注释,该接口是所有Annotation类型的父接口。除此之外,Java在java.lang.reflect包下新增了AnnotateElement接口,该接口代表程序中可以接受注释的程序元素,该接口主要有如下几个实现类(注意以下是类):

  1. Class:类定义。
  2. Constructor:构造器定义。
  3. Field:类的成员变量定义。
  4. Method:类的方法定义。
  5. Package:类的包定义。

程序就可以调用该对象的如下三个方法来访问Annotation信息:

  1. getAnnotation(Class<T> annotationClass);  //返回该程序元素上存在的、指定类型的注释,如果该类型的注释不存在,则返回null。
  2. Annotation[] getAnnotations();      //返回该程序元素上存在的所有注释。
  3. boolean isAnnotationPresent(Class<? extends Annotation> annotationClass);      //判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false。 

示例:

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
@Documented
@interface MetaAnnotation{
	String value();//元注解MetaAnnotation设置有一个唯一的属性value
}
enum EnumTrafficLamp{
	RED;
}
//Retention注解决定MyAnnotation注解的生命周期
@Retention(RetentionPolicy.RUNTIME)
//Target注解决定MyAnnotation注解可以加在哪些成分上,如加在类身上,或者属性身上,或者方法身上等成分
@Target( { ElementType.METHOD, ElementType.TYPE })
@Inherited
@interface MyAnnotation {
	//定义基本属性
	String color();
	//使用default关键字为属性指定缺省值(默认值)
	String value() default "num";
	//数组类型的属性
	int[] arrayAttr() default {1,2,4};
	//枚举类型的属性
	EnumTrafficLamp lamp() default EnumTrafficLamp.RED;
	//注解类型的属性
	MetaAnnotation annotationAttr() default @MetaAnnotation("metadata");
}
@MyAnnotation(color = "Green")
public class Test{
	public static void main(String[] args) throws Exception {
		MyAnnotation annotation = Test.class.getAnnotation(MyAnnotation.class);
		System.out.println(annotation.color());	//输出Green
		new Test().test();
	}
	@MyAnnotation(color = "YelloW")
	public void test() throws Exception {
		Class<Test> clazz = Test.class;
		Method method = clazz.getMethod("test", null);
		if(method.isAnnotationPresent(MyAnnotation.class)) {
			MyAnnotation ma = method.getAnnotation(MyAnnotation.class);
			System.out.println(ma.color());	   //输出Yellow
		}
	}
}

系统元注释:

  • @Retention

  @Retention只能用于修饰一个Annotation定义,用于指定该Annotation可以保留多长时间,@Retention包含一个RetentionPolicy类型的value成员变量,所以使用@Retention时必须为该value成员变量指定值。

  value成员变量的值只能是如下三个:

  1. RetentionPolicy.SOURCE:  注解仅存在于源码中,在class字节码文件中不包含。
  2. RetentionPolicy.CLASS: 编译器将把注释记录在class文件中。当运行Java程序时,JVM不在保留注释,这是默认值。
  3. RetentionPolicy.RUNTIME: 编译器将把注释记录在class文件中。当运行Java程序时,JVM也会保留注释,程序可以通过反射获取该注释。
  • @Target

  @Target也是用于修饰一个Annotation定义,它用于指定被修饰Annotation能用于修饰那些程序元素。@TargetAnnotation也包含一个名为  value的成员变量,该成员变量只能是如下几个:

  1. ElementType.ANNOTATION_TYPE: 指定该策略的Annotation只能修饰Annotation。
  2. ElementType.CONSTRUCTOR:  指定该策略的Annotation能修饰构造器。
  3. ElementType.FIELD:  指定该策略的Annotation只能修饰成员变量。
  4. ElementType.LOCAL_VARIABLE:  指定该策略的Annotation只能修饰局部变量。
  5. ElementType.METHOD: 指定该策略的Annotation只能修饰方法。
  6. ElementType.PACKAGE:  指定该策略的Annotation只能修饰包定义。
  7. ElementType.PARAMETER:  指定该策略的Annotation可以修饰参数。
  8. ElementType.TYPE:  指定该策略的Annotation可以修饰类、接口(包括注释类型)或枚举定义。
  • Documented

  @Documented用于指定该元Annotation修饰的Annotation类将被javadoc工具提取成文档,如果定义Annotation类时使用了@Documented修饰,则所有使用该Annotation修饰的程序元素的API文档中将会包含该Annotation说明。

  • @Inherited

  @Inherited 元 Annotation指定被它修饰的Annotation将具有继承性:如果某个类使用了A Annotation(定义该Annotation时使用了@Inherited修饰)修饰,则其子类将自动具有A注释。注意:该注释只能定义在类上,并且所修饰的注解也只有定义在类上才具有继承性。

示例:

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface InheritedAnnotationType{}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface UninheritedAnnotationType {}

@UninheritedAnnotationType
class A {}

@InheritedAnnotationType
class B extends A {}

class C extends B {}

public class Test{
	public static void main(String[] args) {
	    System.out.println(new A().getClass().getAnnotation(InheritedAnnotationType.class));
	    System.out.println(new B().getClass().getAnnotation(InheritedAnnotationType.class));
	    System.out.println(new C().getClass().getAnnotation(InheritedAnnotationType.class));
	    System.out.println("------------------------------");
	    System.out.println(new A().getClass().getAnnotation(UninheritedAnnotationType.class));
	    System.out.println(new B().getClass().getAnnotation(UninheritedAnnotationType.class));
	    System.out.println(new C().getClass().getAnnotation(UninheritedAnnotationType.class));
	}
}

输出结果为:

null
@com.xxx.test.InheritedAnnotationType()
@com.xxx.test.InheritedAnnotationType()
------------------------------
@com.xxx.test.UninheritedAnnotationType()
null
null 

注解详细内容可参见:

https://www.cnblogs.com/be-forward-to-help-others/p/6846821.html

https://www.cnblogs.com/xdp-gacl/p/3622275.html

由于篇幅原因这里只介绍了jdk1.5的特性,后面内容放到下篇文章。这里的内容我只是做了整理总结,感谢那些愿分享的人。

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Superme_No1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值