JDK1.5版本新增特性

JDK1.5版本新增特性

1.自动装箱与拆箱

1.定义

装箱就是自动将基础类型转换成包装器类型;拆箱就是自动将包装器类型转换成基础数据类型。
示例:
Integer i = 16;//自动装箱
int j = i;//自动拆箱
在Java中,每一种基本数据类型都存在对应的包装器类型。具体对应关系如下图:
在这里插入图片描述

2.为什么需要

Java是一种面向对象的编程语言,基本类型并不具备对象的性质,为了让基本类型也具有对象的特征,就出现了包装类型。包装类型具备对象的特征,就存在自己的属性与方法,丰富基本类型的操作。

3.代码示例

⑴常用方法
主要介绍Integer,其他类的方法查看源码
①compare(x, y)

public static int compare(int x, int y) {
	return (x < y) ? -1 : ((x == y) ? 0 : 1);
}

②compareTo(another)
所有类的该方法都如下

public int compareTo(Integer anotherInteger) {
	return compare(this.value, anotherInteger.value);
}

⑤valueOf()

public static Integer valueOf(int i) {
  if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}
public static Integer valueOf(String s) throws NumberFormatException {
	return Integer.valueOf(parseInt(s, 10));
}
public static Integer valueOf(String s, int radix) throws NumberFormatException {
	return Integer.valueOf(parseInt(s,radix));
}
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 =
		    sun.misc.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() {}
}

⑥toString()

public static String toString(int i) {
    if (i == Integer.MIN_VALUE)
        return "-2147483648";
    int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
    char[] buf = new char[size];
    getChars(i, size, buf);
    return new String(buf, true);
}
public static String toString(int i, int radix) {
	if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX)
	    radix = 10;
	/* Use the faster version */
	if (radix == 10) {
	    return toString(i);
	}
	char buf[] = new char[33];
	boolean negative = (i < 0);
	int charPos = 32;
	if (!negative) {
	    i = -i;
	}
	while (i <= -radix) {
	    buf[charPos--] = digits[-(i % radix)];
	    i = i / radix;
	}
	buf[charPos] = digits[-i];
	if (negative) {
	    buf[--charPos] = '-';
	}
	return new String(buf, charPos, (33 - charPos));
}

4.注意事项及其他

⑴数字类型边界

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方法与IntegerCache类的具体实现,代码是Java8版本。
由上可知,Integer包装类型如果数值在[-128,127]之间,便返回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象。
另外我们再看Long、Float、Double、Boolean、Character类的比较:

	public static void main (String[] args) {
		Long a = 100L;
		Long b = 100L;
		Long c = 200L;
		Long d = 200L;
		System.out.println(a == b);//true
		System.out.println(c == d);//false
		
		//小数默认是double类型,所以在定义float类型的数据需要强转
		Float f1 = (float) 100.0;
		Float f2 = (float) 100.0;
		Float f3 = (float) 200.0;
		Float f4 = (float) 200.0;
		System.out.println(f1 == f2);//false
		System.out.println(f3 == f4);//false
		
		Double d1 = 100.0;
		Double d2 = 100.0;
		Double d3 = 200.0;
		Double d4 = 200.0;
		System.out.println(d1 == d2);//false
		System.out.println(d3 == d4);//false
		
		Character c1 = 'a';
		Character c2 = 'a';
		Character c3 = '$';
		Character c4 = '$';
		System.out.println(c1 == c2);//true
		System.out.println(c3 == c4);//true
		
		Boolean i1 = true;
		Boolean i2 = true;
		Boolean i3 = false;
		Boolean i4 = false;
		System.out.println(i1 == i2);//true
		System.out.println(i3 == i4);//true
	}

Integer、Short、Byte、Character、Long这几个类的valueOf方法的实现是类似的。Double、Float的valueOf方法的实现是类似的。
另外还有一点需要注意:当 "=="运算符的两个操作数都是包装器类型的引用,则是比较指向的是否是同一个对象,而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程),对基础数据类型进行运算。对于包装器类型,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)

枚举(Enum)是可以定义了一组可以使用的类对象的对象。可以在枚举类中定义相同结构的多个不同的类对象,可以对类对象进行操作。
比如,我们不使用枚举类:

/*
 * 季节类
 * 有且仅有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=春天]
*/

3.静态导入(import static)

1.定义

静态导入就是导入一个类中的静态方法,代码中静态导入方式:import static com.spring.springdemo.test.statics.methods.Statics.*;

2.为什么需要

为了减少字符输入量,提高代码的可阅读性,以便更好地理解程序。

3.代码示例

先创建一个含静态方法的类:

package com.spring.springdemo.test.statics.methods;

public class Statics {

	public static void printMsg(String msg) {
		System.out.println(msg);
	}
	
	public static void printMsg2(String msg) {
		System.out.println(msg);
	}
}

不使用静态导入:

package com.spring.springdemo.test.statics;

import com.spring.springdemo.test.statics.methods.Statics;

public class StaticsTest {
	public static void main(String[] args) {
		String msg = "需要打印的信息";
		Statics.printMsg(msg);
		Statics.printMsg2(msg);
	}
}

使用静态导入:

package com.spring.springdemo.test.statics;

import static com.spring.springdemo.test.statics.methods.Statics.*;

public class StaticsTest {
	public static void main(String[] args) {
		String msg = "需要打印的信息";
		printMsg(msg);
		printMsg2(msg);
	}
}

从代码层次上,需要在引入静态方法的时候,import后需要增加static关键字,并且类后面需要跟方法名称,也可以使用通配符。然后在使用方法的时候,简写了类名,直接使用方法名。
另外,静态导入不仅仅可以导入静态方法,也可以导入常量或变量。

4.注意事项及其他

1.方法或变量必须是静态的
2.如果有多个同名方法或变量,使用的时候需要加上地址前缀
3.静态导入不建议大面积使用,因编码风格和习惯问题,代码反而造成较差的阅读性,增加理解成本。

4.可变参

1.定义

在Varargs机制中,允许直接定义能和多个实参相匹配的形参。从而,可以用一种更简单的方式,来传递个数可变的实参。
声明可变参数的语法格式如下:
methodName({paramList},paramType…paramName)
其中,methodName 表示方法名称;paramList 表示方法的固定参数列表;paramType 表示可变参数的类型;… 是声明可变参数的标识;paramName 表示可变参数名称。

2.为什么需要

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

3.代码示例

public class VarargsTest {
	
	public static void main(String[] args) {
		sumPrint(1,2,3,4);   //10
		
	}
	/**
	 * 	数字类型的可变参
	 */
	public static void sumPrint(int... args) {
		int min = 0;
		if(args.length > 0) {
			for(int i = 0;i < args.length;i++) {
				min += args[i];
			}
			System.out.println("最终值:" + min);  
		}else {
			throw new NullPointerException("参数为空");
		}
	}
}

4.注意事项及其他

  • 可变参数只能作为函数的最后一个参数,但其前面可以有也可以没有任何其他参数
  • 由于可变参数必须是最后一个参数,所以一个函数最多只能有一个可变参数
  • Java的可变参数,会被编译器转型为一个数组
  • 变长参数在编译为字节码后,在方法签名中就是以数组形态出现的。这两个方法的签名是一致的,不能作为方法的重载。如果同时出现,是不能编译通过的。可变参数可以兼容数组,反之则不成立
  • 可变参的参数类型可以为泛型

可变参数方法重载
相同方法名,如果不输入参数或者与定参方法不同数量的参数,调用变参方法;如果输入与定参方法相同的参数个数,则优先匹配固定定参方法。

public class VarargsTest {
	
	public static void main(String[] args) {
		showPrint("1","2","3"); 	//变参
		showPrint("1","2"); 		//定参
		showPrint();        		//变参
	}
	/**
	 * 	数字类型的可变参(方法重载)
	 */
	public static void showPrint(String... args) {
		System.out.println("变参");
	}
	/**
	 * 	数字类型的定参(方法重载)
	 */
	public static void showPrint(String a,String b) {
		System.out.println("定参");
	}
}

方法重载,不同类型的变参方法,在参数为空的时候调用会报错。因为int类型方法在前,所以错误提示内容是以int变参方法为准。

public static void main(String[] args) {
		showPrint();   //error,The method showPrint(int[]) is ambiguous for the type VarargsTest
	}
	/**
	 * 	数字类型的可变参(方法重载)
	 */
	public static void showPrint(int... args) {
		System.out.println("int变参");
	}
	/**
	 * 	字符串类型的可变参(方法重载)
	 */
	public static void showPrint(String... args) {
		System.out.println("String变参");
	}

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

public static void main(String[] args) {
		showPrint1("数字类型变参",1,2,3);
		showPrint1("数字类型变参","1","2","3");
		showPrint1("数字类型变参");			//error,The method showPrint1(String, Integer[]) is ambiguous for the type VarargsTest
		showPrint1("数字类型变参",null);		//error,The method showPrint1(String, Integer[]) is ambiguous for the type VarargsTest
}
public static void showPrint1(String str,Integer...args) {
}
public static void showPrint1(String str,String...args) {
}

可变参数方法重写

// 基类
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      
	} 
}

5.内省(Introspector)

1.定义

内省是指计算机程序在运行时(Run time)检查对象(Object)类型的一种能力,通常也可以称作运行时类型检查。是Java语言对Bean类属性、事件的一种缺省处理方法。
JavaBean是一种特殊的类,主要用于传递数据信息,这种类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。如果在两个模块之间传递信息,可以将信息封装进JavaBean中,这种对象称为“值对象”(Value Object),或“VO”。方法比较少。这些信息储存在类的私有变量中,通过set()、get()获得。
Java JDK中提供了一套 API 用来访问某个属性的 getter/setter 方法,这就是内省。

2.JDK内省类库

1.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 对象。

常见方法:

方法返回类型方法说明
equals(Object obj)boolean将此 PropertyDescriptor 与指定对象进行比较。
getPropertyType()Class<?>获得属性的 Class 对象。
getReadMethod()Method获得应该用于读取属性值的方法。
getWriteMethod()Method获得应该用于写入属性值的方法。
hashCode()int返回对象的哈希码。
setReadMethod(Method readMethod)void设置应该用于读取属性值的方法。
setWriteMethod(Method writeMethod)void设置应该用于写入属性值的方法。

示例:
bean类

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:张三

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

BeanInfo类

方法返回类型方法说明
getPropertyDescriptors()PropertyDescriptor[]获得 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.泛型

1.定义

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

2.目的

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

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

3.示例

以前没有使用泛型的时候

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); 在编译阶段,编译器就会报错

泛型示例:

public class GenericTest {
	public static void main(String[] args) {
		Demo1<String> demo1 = new Demo1<String>();
		demo1.setE("泛型1");
		System.out.println(demo1.getE());
		
		Demo1<Integer> demo12 = new Demo1<Integer>();
		demo12.setE(123);
		int i = demo12.getE();//因为泛型原因,获取值只能使用integer类型获取
		System.out.println(i);
		
		Demo2 demo2 = new Demo2();
		demo2.setObject("未使用泛型");
		//int j = (int) demo2.getObject();
		//System.out.println(j);
	}
}
class Demo1<E>{
	private E e;
	public E getE() {
		return e;
	}
	public void setE(E e) {
		this.e = e;
	}
}
class Demo2{
	private Object object;
	public Object getObject() {
		return object;
	}
	public void setObject(Object object) {
		this.object = object;
	}
}

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

4.具体应用

①泛型类
格式:public class 类名<泛型类型1,泛型类型2,…>
示例:

//此处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,泛型类型2,…>
在使用泛型接口的时候,有两种方式进行调用
定义的泛型接口:

public interface GenericService<T> {
	void showPrint(T t);
}

第一种:在接口确定泛型的参数类型

public class GenericeServiceImpl01 implements GenericService<String> {
	@Override
	public void showPrint(String t) {
		System.out.println("实现接口的时候,接口确定参数类型"+t);
	}
}

第二种:在调用实现类的时候,确定泛型的参数类型

public class GenericeServiceImpl02<T> implements GenericService<T> {
	@Override
	public void showPrint(T t) {
		System.out.println("调用实现类方法的时候,确定参数类型:"+t);
	}
}

测试:

public class GenericTest {
	public static void main(String[] args) {
		GenericeServiceImpl01 genericeServiceImpl01 = new GenericeServiceImpl01();
		genericeServiceImpl01.showPrint("实现类1");
		
		GenericeServiceImpl02<String> genericeServiceImpl02String = new GenericeServiceImpl02<String>();
		genericeServiceImpl02String.showPrint("实现类2的字符串");
		GenericeServiceImpl02<Integer> genericeServiceImpl02Integer = new GenericeServiceImpl02<Integer>();
		genericeServiceImpl02Integer.showPrint(123);
	}
}

输出:

实现接口的时候,接口确定参数类型:实现类1
调用实现类方法的时候,确定参数类型:实现类2的字符串
调用实现类方法的时候,确定参数类型:123

注意:如果不声明泛型,如: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>();	
	}
}

另外还有类型擦除、泛型与数组等知识点,单独写出。

7.ForEach循环(语法糖)

1.定义

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

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

2.示例

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(注解)

1.定义

注解是元数据的一种形式,它提供的数据与程序本身无关。注释对它们注释的代码的操作没有直接影响。
注解有两种用途:

  • 注解检查错误或抑制警告(Warning)
  • 作为元数据,为程序提供数据,且与程序无关。

2.常用注解

①覆写@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;
}
...

当然还有一些线程方法,比如stop()方法。一般过期的方法不建议使用,会造成一定错误。
③压制警告:@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"关键字会抑制其没有注入依赖的警告。

在这里插入图片描述在这里插入图片描述

3.自定义注解

格式:修饰符 @interface 注解名称
示例:

@Documented
public @interface MetaAnnotation {
	//@Documented注解标记的元素,Javadoc工具会将此注解标记元素的注解信息包含在javadoc中。
	//元注解MetaAnnotation设置一个属性
	String value();
}
//Retention注解决定MyAnnotation注解的生命周期
@Retention(RetentionPolicy.RUNTIME)
//Target注解决定MyAnnotation注解可以加在哪些成分上,如加在类身上,或者属性身上,或者方法身上等成分
@Target({ElementType.TYPE,ElementType.METHOD})
@Inherited
public @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");
}
//枚举类
enum EnumTrafficLamp{
	RED;
}
@MyAnnotation(color = "blue")
public class AnnotationTest {
	public static void main(String[] args) throws NoSuchMethodException, SecurityException {
		MyAnnotation annotation = AnnotationTest.class.getAnnotation(MyAnnotation.class);
		System.out.println(annotation.color());
		System.out.println(annotation.value());
		System.out.println(annotation.arrayAttr().length);
		System.out.println(annotation.annotationAttr().value());
		new AnnotationTest().test();
	}
	@MyAnnotation(color = "yellow")
	@SuppressWarnings(value = { "all" })
	public void test() throws NoSuchMethodException, SecurityException {
		Class<AnnotationTest> clazz = AnnotationTest.class;
		Method method = clazz.getMethod("test", null);
		if(method.isAnnotationPresent(MyAnnotation.class)) {
			MyAnnotation ma = method.getAnnotation(MyAnnotation.class);
			System.out.println(ma.color());	   //输出Yellow
		}
	}
}

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

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

  • Class:类定义。

  • Constructor:构造器定义。

  • Field:类的成员变量定义。

  • Method:类的方法定义。

  • Package:类的包定义。
    程序就可以调用该对象的如下三个方法来访问Annotation信息:

  • getAnnotation(Class annotationClass); //返回该程序元素上存在的、指定类型的注释,如果该类型的注释不存在,则返回null。

  • Annotation[] getAnnotations(); //返回该程序元素上存在的所有注释。

  • boolean isAnnotationPresent(Class<? extends Annotation> annotationClass); //判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false。

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

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

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

  • ElementType.ANNOTATION_TYPE: 指定该策略的Annotation只能修饰Annotation。
  • ElementType.CONSTRUCTOR: 指定该策略的Annotation能修饰构造器。
  • ElementType.FIELD: 指定该策略的Annotation只能修饰成员变量。
  • ElementType.LOCAL_VARIABLE: 指定该策略的Annotation只能修饰局部变量。
  • ElementType.METHOD: 指定该策略的Annotation只能修饰方法。
  • ElementType.PACKAGE: 指定该策略的Annotation只能修饰包定义。
  • ElementType.PARAMETER: 指定该策略的Annotation可以修饰参数。
  • 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值