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 | 抑制没被使用过的代码的警告 |
resource | J2EE,可以使用@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