概述
第一部分 静态导入、增强for、枚举、注解、泛型
1,静态导入
在实际开发中,我们要引用某个包下的某个类中的静态方法时,为了方便代码书写,JDK1.5提供了静态导入的新特性。
例如,我们要使用java.lang包下Math类的求最大值方法max()时,可以在类顶端加上如上代码:
import static java.lang.Math.max;
之后,若我们要用到这个类下的max()方法,时,就可以不用加上类名了,直接用max(a,b);这种形式。若我们还需要改类下
的多个静态方法,或者所有静态方法时,可以全部逐一导入,也可以一次性导入,形如:
import static java,lang.Math.*;
注意:静态导入是JDK1.5的新特性,1.5之前没有这个特性,所以以后在修改和维护JDK1.5前的程序时,这个要稍作注意。
2,形参可变的方法和增强for循环
形参可变方法:
从JDK1.5之后,Java允许定义形参长度可变的参数从而允许为方法指定数量不确定的形参。如果在定义方法时,在最后一个形参的类型后增加三点(...),
则表明该参数可以接受多个参数值,多个参数值被当成数组传入。
形参可变:详细请查阅 【黑马程序员】java基础(四)——面向对象_类、封装、构造器、this
高级for循环:
3,枚举
在某些情况下,一个类的对象是有限而且固定的,例如季节类,它只有4个对象,再例如星期类,它只有7个对象。这种实例有限而且固定的类,在Java里被称为枚举类。在java.lang包下,有个Enum类用于操作该类型的实例。
3.1,普通类模拟枚举类
需求:用普通类实现一个星期各个实例,一共有7天,所以有7个实例,模拟实例的调用,和计算当前实例日期的下一个实例日期
步骤:1,创建星期类WeekDay;2,列出每一天的实例,这里为了避免繁琐,只列出周日(SUN)和周一(MON)的实例;3,私有化构造器,不能直接创建该类实例;4,类中创建实例,并静态化对外提供出去;5,定义公共方法nextDay(),返回当前实例的下一个实例;6;覆盖toString()方法
WeekDay类:
public abstract class WeekDay {
private WeekDay() {
}
public static final WeekDay SUN = new WeekDay() {
public WeekDay nextDay() {
return MON;
}
};
public static final WeekDay MON = new WeekDay() {
public WeekDay nextDay() {
return SUN;
}
};
public abstract WeekDay nextDay();
/*
* public WeekDay nextDay() {
* if (this == SUN) {
* return Mon;
* } else { return
* SUN; }
* }
*/
public String toString() {
return this == SUN ? "Sun" : "Mon";
}
}
主函数调用:
public class EnumTest {
public static void main(String[] args) {
WeekDay day = WeekDay.SUN;
WeekDay nextDay = day.nextDay();
System.out.println("today::"+day.toString());
System.out.println("next::"+nextDay.toString());
}
}
运行结果:
通过上述代码的分析,可以看出用普通类的瓶颈,编程比较繁琐(注意,这里只简化一个星期的对象,只写了2个实例),那么改用枚举呢?
public class EnumTest1 {
public static void main(String[] args) {
WeekDay wd = WeekDay.SUN;
System.out.println(wd.toString());
wd.nextDay();
}
public enum WeekDay {
SUN, MON;
private WeekDay() {
}
public void nextDay() {
switch (this) {
case SUN:
System.out.println("next::MON");
break;
case MON:
System.out.println("next::SUN");
break;
default:
System.out.println("nono");
break;
}
}
}
}
3.2,枚举类入门
JDK1.5新增了一个enum关键字,用以定义枚举类。枚举类是一个特殊的类,它一样可以有自己的方法和属性,可以实现一个和多个接口,也可以定义自己的构造器。一个Java源文件中最多只能定义一个public访问权限的枚举类,且该Java源文件也必须和该枚举类的类名相同。但枚举类终究不是普通的类,它与普通类有如下的区别:
1)枚举类可以实现一个或多个接口,使用enum定义的枚举类默认继承了java.lang.Enum类,而不是继承Object类。其中java.lang.Enum类实现了java.lang.Serializable和 java.lang.Comparable两个接口。
2)枚举类的构造器只能使用private访问控制符,如果省略了其构造器的访问控制符,则默认使用private修饰;如果强制指定访问控制符,则只能指定private修饰符。
3)枚举类的所有实例必须在枚举类中显式列出,否则这个枚举类将永远都不能产生实例。列出这些实例时,系统会自动添加public static final修饰,无序显式添加。
4)所有枚举类都提供一个values方法,该方法可以很方便地遍历所有枚举值。
枚举类常用的方法:
protected Object clone():抛出CloneNotSupportedException。
int compareTo(E o):比较此枚举与指定对象的顺序。
boolean equals(Object other):当指定对象等于此枚举常量时,返回true。
protected void finalize():枚举类不能有 finalize 方法。
Class<E> getDeclaringClass():返回与此枚举常量的枚举类型相对应的Class对象。
int hashCode():返回枚举常量的哈希码。
String name():返回此枚举常量的名称,在其枚举声明中对其进行声明。
int ordinal():返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零)。
String toString():返回枚举常量的名称,它包含在声明中。
static <T extends Enum<T>> T valueOf(Class<T> enumType, String name):返回带指定名称的指定枚举类型的枚举常量。
3.3,枚举类的属性,方法和构造器
枚举类也是一种类,只是它是一种比较特殊的类,因此它一样可以使用属性和方法。下面程序将定义一个Gender枚举类,该类里包含了一个name属性,而且它定义成public访问权限,可以通过main方法来使用。
public class EnumTest2 {
public static void main(String[] args) {
Gender1 g1 = Enum.valueOf(Gender1.class, "FEMALE");
g1.name="女";
System.out.println(g1+"::"+g1.name);
}
public enum Gender1 {
MALE, FEMALE;
public String name;
}
}
为了有良好的安全性,Java应该把所有类设计成良好封装的类,所以不应该允许直接访问Gender类的name属性,而是应该通过方法来控制name属性值的访问。否则可能出现很混乱的情形:例如上面程序,恰好我们设置了g.name="女",要是采用g.name="男",那程序就会非常混乱了,可能出现FEMALE代表男的情况。为了安全性,我们应该把属性name给private私有化,从而避免程序直接访问name属性,并且向外提供setName方法修改该属性,通过getName方法获取该属性,代码如下:
public class EnumTest2 {
public static void main(String[] args) {
Gender2 g2 = Enum.valueOf(Gender2.class, "FEMALE");
g2.setName("女");
System.out.println(g2.getName());
}
public enum Gender2 {
MALE, FEMALE;
private String name;
public void setName(String name) {
switch (this) {
case MALE:
if (name.equals("男")) {
this.name = name;
} else {
System.out.println("参数无效");
return;
}
break;
case FEMALE:
if (name.equals("女")) {
this.name = name;
} else {
System.out.println("参数无效");
return;
}
break;
}
}
public String getName() {
return this.name;
}
}
}
实际上这种做法并不怎么好,枚举类通常应该设计成不可变类,也就是说它的属性值不应该允许改变,这样会更安全,而且代码更加简洁。为此,我们应该将枚举类的属性都使用private final修饰。因为所有的属性都使用了final修饰符来修饰了,所以必须在构造器中为这些属性指定初始值,因此应该为枚举显式定义带参数的构造器。 一旦为枚举显式定义了带参数的构造器,则列出枚举值时也必须对应地传入参数。
public class EnumTest2 {
public static void main(String[] args) {
Gender3 g3 = Enum.valueOf(Gender3.class, "FEMALE");
System.out.println(g3.getName());
}
public enum Gender3 {
MALE("男"), FEMALE("女");
private final String name;
private Gender3(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
}
从上面的代码可以看出,为Gender类提供了一个Gender(String name)构造器后,列出枚举值应该采用MALE("男"), FEMALE("女");这种形式来完成。也就是说,在枚举中列出枚举值时,实际上就是调用构造器创建枚举类对象,只是这里无需new关键字,也无需显式调用构造器。前面列出枚举值时无需传入参数,甚至无需括号,仅仅是因为前面的枚举类包含无参数的构造器。
3.4,实现接口的枚举类
枚举类也可以实现一个或多个接口。与普通类实现一个或多个接口完全一样,枚举类实现一个或多个接口时,也需要实现该接口所包含的方法。
interface Inter {
public abstract void info();
}
public class EnumTest3 {
public static void main(String[] args) {
Gender g = Enum.valueOf(Gender.class, "FEMALE");
g.info();
}
public enum Gender implements Inter {
MAIL("男"), FEMALE("女");
private final String name;
private Gender(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void info() {
System.out.println("这是枚举共有方法,来自于接口Inter");
}
}
}
通过上面的方式可以看出,枚举类在实现接口时跟普通类是一样的,但是这种方式只能让所有的枚举值都共用这一个方法,换言之,都有相同的行为方式。如果需要每个枚举值在调用该方法时呈现不同的行为方式,则可以让每个枚举值分别来实现该方法,每个枚举值提供不同的实现方式,从而让不同枚举值调用该方法时具有不同的行为方式。
interface Inter {
public abstract void info();
}
public class EnumTest3 {
public static void main(String[] args) {
Gender g = Enum.valueOf(Gender.class, "MALE");
g.info();
g = Enum.valueOf(Gender.class, "FEMALE");
g.info();
}
public enum Gender implements Inter {
MALE("男") {
public void info() {
System.out.println("MALE的实现行为:我是男性");
}
},
FEMALE("女") {
public void info() {
System.out.println("FEMALE的实现行为,我是女性");
}
};
private final String name;
private Gender(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
}
运行结果:
3.5,包含抽象方法的枚举类
枚举类不仅可以实现接口,还可以继承抽象类,道理跟实现接口差不多。但是若在枚举类内部出现了抽象方法,该怎么办呢?以上面的代码为例,如果没有接口可以实现,但是枚举类的各个枚举值都有同名方法,但是每个枚举值的实现方式又不一样,该怎么办?这时,我们应该先定义一个抽象方法,放在枚举类中,由不同的枚举值去实现自己的行为方式。
public class EnumTest4 {
public static void main(String[] args) {
Gender.MALE.info();
Gender.FEMALE.info();
}
public enum Gender {
MALE("男") {
public void info() {
System.out.println("我是男性");
}
},
FEMALE("女") {
public void info() {
System.out.println("我是女性");
}
};
private final String name;
private Gender(String name) {
this.name = name;
}
public abstract void info();
public String getName() {
return name;
}
}
}
枚举类里定义抽象方法时无需显式使用abstract关键字将枚举类定义成抽象类,但因为枚举类需要显式创建枚举值,而不是作为父类,所以定义每个枚举值时必须为抽象方法提供实现,否则将出现编译错误。
4,注解(Annotation)
所谓的注解(Annotation)其实就是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。通过使用Annotation,程序员可以在不改变原有逻辑的情况下,在源文件嵌入一些补充的信息。Annotation提供了一条为程序元素设置元数据的方法,从某些方面看,Annotation就像修饰符一样被使用,可以用于修饰包、类、构造器、方法、属性、局部变量的声明,这些信息被存储在Annotation中的“name=value”对中。
Annotation是一个接口,程序可以通过反射来获取指定程序元素的Annotation对象,然后通过Annotation对象来取得注释里的元数据。
4.1,基本的Annottaion
java.lang包下有3个基本的Annotation:
1)@Override:限定重写父类方法。它可以强制一个子类必须覆盖父类的方法,如果没有覆盖,编译器会出现一条错误信息。注意:@Override只能作用于方法,不能作用于其它元素。
2)@Deprecated:标记已过时。用于表示某个程序元素(类、方法等)已过时,当其它程序元素使用已过时的类、方法时,编译器将会给出警告。
3)@SuppressWarnings:抑制编译器警告。它指示被Annotation标识的程序元素(以及在该程序元素中所有子元素)取消显示指定的编译器警告。@SuppressWarnings会一直作用于该
程序元素的所有子元素,例如使用@SuppressWarnings标识一个类来取消显示某个编译器警告,同时又标识该类里某个方法取消显示另一个编译器警告,那么将在此方法中同时取消显示这两个编译器错误。
4.2,自定义Annotation
4.2.1,定义Annotation
定义新的Annotation类型使用@interface关键字,它用于定义新的Annotation类型。和定义一个接口非常像,如下格式:
public @interface Test
{
...
}
定义了Annotation之后,就可以在程序任何地方来使用该Annotation,使用Annotation时的语法非常类似于public、final这样的修饰符。通常可用于修饰程序中的类、方法、变量、接口等定义。通常会把Annotation放在所有修饰符之前,而且要单独存放一行中。如下格式:
@Test
public class MyClass
{
...
}
Annotation还可以带有成员变量,Annotation的成员变量在Annotation定义中以无参方法的形式来声明。其方法名和返回值定义了该成员的名字和类型。如下格式:
public @interface MyTag{
String name();
int age();
}
一旦在Annotation里定义了成员变量之后,使用该Annotation时应该为该Annotation的成员变量指定值,如下代码:
public class Test
{
//使用带成员变量的Annotation时,需要为成员变量赋值
@MyTag(name = "xx",age = 6)
public void info(){...}
}
我们还可以在定义Annotation的成员变量时为其指定初始值,即默认值。指定成员变量的初始值可使用default关键字。如下代码:
public @interface MyTag
{
String name() default "allen";
int age() default 23;
}
如果为Annotation的成员变量指定了默认值,使用该Annotation则可以不为这些成员变量指定值,而是直接使用默认值。当然也可以在使用MyTagAnnotation时为成员变量指定值,如果为MyTag的成员变量指定了值,则默认值不会起作用。根据Annotation是否可以包含成员变量,可以把Annotation分为如下两类:
1)标记Annotation:一个没有成员定义的Annotation类型被称为标记。这种Annotation仅使用自身的存在与否来为我们提供信息。
2)元数据Annotation:那些包含成员变量的Annotation,因为它们可接受更多元数据,所以被称为元数据Annotation。
4.2.2,提取Annotation的信息
Java使用Annotation接口来代表程序元素前面的注释,该接口是所有Annotation类型的父接口。除此之外,Java在java.lang.reflect包下新增了AnnotatedElement接口,该接口代表程序中可以接受注释的程序元素,该接口主要有如下几个实现类:Class、Constructor、Field、Method和Package。
java.lang.reflect包下主要包含一些实现反射功能工具类,实际上,java.lang.reflect包所提供的反射API扩充了读取运行时Annotation的能力。当一个Annotation类型被定义为运行时Annotation后,该注释才是运行时可见,当class文件被装载时被保存在class文件中的Annotation才会被JVM读取。
AnnotatedElement接口是所有程序元素(如Class、Method、Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象(如Class、Method、Constructor)之后,程序就可以调用该对象的如下三个方法来访问Annotation信息:
<T extends Annotation> T getAnnotation(Class<T> annotationClass):如果存在该元素的指定类型的注释,则返回这些注释,否则返回 null。
Annotation[] getAnnotations():返回此元素上存在的所有注释。
Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注释。
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass):如果指定类型的注释存在于此元素上,则返回 true,否则返回 false。
4.3,JDK的元Annotation
JDK除了在java.lang下提供了3个基本的Annotation之外,还在java.lang.annotation包下提供了四个Meta Annotation(元Annotation),这四个Annotation都是用于修饰其它Annotation定义。
4.3.1,使用@Retention
@Retention只能用于修饰一个Annotation定义,用于指定该Annotation可以保留多长时间,@Retention包含一个RetentionPolicy类型的value成员变量,所以使用@Retention时必须为该value成员变量指定值。value成员变量的只能是如下三个:
》》RetentionPolicy.CLASS:编译器将把注释记录在class文件中。当运行Java程序时,JVM不再保留注释。这是默认值。
》》RetentionPolicy.RUNTIME:编译器将把注释记录在class文件中。当运行Java程序时,JVM也会保留注释,程序可以通过反射获取该注释。
》》RetentionPolicy.SOURCE:编译器直接丢弃这种策略的注释。
使用@Retention元数据Annotation可采用如下代码为value指定值:
//定义下面的Testable Annotation的保留到运行时
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Testable{}
也可以采用如下的代码来为value指定值:
//定义下面的Testable Annotation将编译器直接丢弃
@Retention(RetentionPolicy.Source)
public @interface Testable{}
注意:上面代码中使用@Retention元数据Annotation时,并未直接通过value=RetentionPolicy.Source的方式来为成员变量指定值,这是因为如果Annotation的成员变量名为value时,程序中可以直接在Annotation后的括号里指定该成员变量的值,无须使用name=value的形式。如果我们定义的Annotation类型里的只有一个value成员变量,使用该Annotation时可以直接在Annotation后的括号里指定value成员变量的值,无须使用name=value的形式。
4.3.2,使用@Target
@Target也是用于修饰一个Annotation定义,它用于指定被修饰的Annotation能用于修饰哪些程序元素。@Target Annotation也包含一个名为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可以修饰类、接口(包括注释类型)或枚举定义。
注意:和使用@Rentention类似,使用@Target也可以直接在括号里指定value值,可以无须使用name=value的形式。
4.3.3,使用@Documented
@Documented用于指定被该元Annotation修饰的Annotation类将被javadoc工具提取成文档,如果定义Annotation类时使用了@Documented修饰,则所有使用Annotation修饰的程序元素的API文档中将会包含该Annotation说明。
4.3.4,使用@Inherited
@Inherited元Annotation指定被它修饰的Annotation将具有继承性:如果某个类使用了A Annotation(定义该Annotation时使用了@Inherited修饰)修饰,则其子类将自动具有A注释。
package cn.itcast.iannotation;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface Inheritable {
}
//使用@Inheritable修饰的Base类
@Inheritable
class Base {
}
//TestInheritable类只是继承了Base类,并未直接使用@Inheritable Annotation
public class TestInheritable extends Base{
public static void main(String[] args) {
boolean b = TestInheritable.class.isAnnotationPresent(Inheritable.class);
System.out.println(b);
}
}
上面的@Inherited表明@Inheritable Annotation具有继承性,如果某个类使用了该Annotation修饰,则该类的子类将自动具有@Inheritable Annotation。Base类使用该Annotation,那么Base类的子类将自动具有@Inheritable Annotation。程序的运行结果:true。如果将@Inheritable Annotation的@Inherited去掉,那么@Inheritable Annotation就不具备继承性,程序运行的结果:false。
5,泛型
在没有泛型之前,一旦将一个对象存储到Java集合中,集合就会忘记对象的类型,把所有的对象都当成Object类型处理。当程序要取出集合中的对象时,就需要强制类型转换,这种强制类型转换不仅代码臃肿,而且容易引起ClassCastException异常。
JDK1.5增加了泛型支持后,集合可以记住元素的类型,并且可以在编译时检查集合中元素的类型,如果试图向集合中添加不满足类型要求的对象,编译器就会提示错误。增加泛型后的集合,可以让代码更加简洁、程序更加健壮。
5.1,泛型入门
5.1.1,编译时不检查类型的异常
import java.util.ArrayList;
public class GenericDemo1 {
public static void main(String[] args) {
ArrayList al = new ArrayList();
al.add(1);
al.add(1L);
al.add("abc");
int element = (Integer) al.get(1);
System.out.println();
}
}
分析:以上代码是行之有效的,用javac命令编译或者使用eclipse编辑,都没有报错,只给出了一条友好提示,这并不妨碍代码的运行。但是,实际上在运行的时候,报出了ClassCastException异常。如上集合index=1的位置存储的是long类型数据,在取出的时候要强制转换成Integer类型,明显是不可行的。可幸的是,JDK1.5以后,引入了“参数化类型(parameterized type)”的概念,允许我们在创建集合时指定集合元素的类型,这种参数化类型被称为泛型(Generic)。
5.1.2,使用泛型
对于前面的GenericDemo.java使用泛型进行改进:
import java.util.ArrayList;
public class GenericDemo1 {
public static void main(String[] args) {
ArrayList<Integer> al = new ArrayList<Integer>();
al.add(1);
al.add(123);
al.add(34);
int element = al.get(1);
System.out.println(element);
}
}
分析:编译时,之前命令行上出现的友好提示不见了,并且编译正常,运行结果输出123。但是,在程序中添加al.add("abc")或者al.add(1L)诸如此类语句,那么,程序在编译时期就报错了,程序员必须停下来修改程序的错误,根本得不到运行,这样就是将程序运行期间的错误提前到了编译时期,提高了安全性。上面在集合类的前面加<classtype>表示泛型的使用,<>里面的类型,代表该集合只能存在的元素数据类型,如果存储了规定之外的类型,编译时期就会报错,存不进去。
5.2,深入泛型
泛型定义:就是允许在定义类、接口时指定类型形参,这个类型形参将在声明变量、创建对象时确定(即传入实际的类型参数,也可称为类型实参)。JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参。
5.2.1,泛型类
虽然泛型是集合类的重要使用场所,但并不是只有集合类才可以使用泛型声明,普通类也可以使用泛型声明。当普通类要操作的引用数据不确定时,早期是将不确定的引用类型定义成Object来完成扩展,现在可以定义泛型来完成扩展。例如:
package cn.itcast.igeneric;
//Apple类上使用了泛型定义
public class Apple<T> {
private T info; // 使用T类型声明成员变量
public Apple(){}
public Apple(T info) {
this.info = info;
}
// 使用T类型形参定义方法
public void setInfo(T info) {
this.info = info;
}
public T getInfo() {
return info;
}
public static void main(String[] args) {
// 因为传给T形参的实际类型是String,所以构造器的参数只能是String
Apple<String> a1 = new Apple<String>("ios");
System.out.println(a1.getInfo());
// 下面的代码会出错,因为声明Apple对象时指定了形参类型是String,不能接受其它类型
// Apple<String> a2 = new Apple<String>(12L);
}
}
注意:上面程序定义了一个带泛型声明的Apple<T>类,不要理会这个T是否具有含义,就当作是一种标记。实际使用Apple<T>类时会为T形参传入实际类型。当创建带泛型声明的自定义类,为该类定义构造器时,构造器名还是原来的类名,不要增加泛型声明。例如Apple<T>类定义构造器,其构造器名依然是Apple,而不是Apple<T>,但也可以使用Apple<T>的形式,当然应该为T形参传入实际的类型参数。
5.2.2,从泛型类派生子类
当创建了带泛型声明的父类后,可以从该父类派生子类,但值得指出的是,当使用这些父类时,不能再包含类型形参。例如下面的代码是错误的:
//定义类A继承Apple类,Apple类不能跟类型形参
public class A extends Apple<T>{}
注意:方法中的形参(这种形参代表变量、常量、表达式等数据),只有当定义方法时才可以使用数据形参,当调用方法时必须为这些数据形参传入实际的数据;与此类似的是:类、接口中的形参,只有在定义类、接口是才可以使用类型形参,当使用类、接口时应为类型形参传入实际的类型。
如果想从Apple类派生一个子类,可以改为如下代码:
//使用Apple类时为T形参传入String类型
public class A extends Apple<String>{}
使用方法时必须为所有的数据形参传入参数值,与使用方法不同的是:使用类、接口时可以不为类型形参传入实际类型,下面的代码也是正确的:
//使用Apple类时,没有为T形参传入实际的类型参数
public class A extends Apple{}
如果从Apple<String>类派生子类,则在Apple类中所有使用T类型的形参的地方都将被替换成String类型,即它的子类将会继承到String getInfo()和
void setInfo(String info)两个方法,如果子类需要重写父类的方法,必须注意这一点。
public class A extends Apple<String> {
public String getInfo() {
return "A" + super.getInfo();
}
//下面方法是错误的,重写父类方法时返回值类型不一致
public Object getInfo(){
return "A"+super.getInfo();
}
}
如果使用Apple类时没有传入实际的类型参数,Java编译器可能会发出警告:使用了未经检查或不安全的操作。
5.2.3,泛型接口
与泛型类相似,当定义接口不确定要操作的引用数据类型时,可以声明泛型来扩展。例如:
interface Inter<T> {
void show(T t);
}
public class InterImpl<T> implements Inter<T> {
public void show(T t) {
System.out.println("show::" + t);
}
public static void main(String[] args) {
new InterImpl().show("Generic_Interface");
}
}
子类使用这种方式来实现接口,编译器会发出警告。为做到更好,可以做与泛型类相似的方式,在实现这个泛型接口指定实际的参数类型
interface Inter<T>{
void show(T t);
}
public class InterImpl implements Inter<String> {
public void show(String t){
System.out.println("show::"+t);
}
public static void main(String[] args) {
new InterImpl().show("Generic_Interface");
}
}
也可以不为形式参数传入实际类型,让系统自动被当做Object使用,这种方法会引发编译器警告
interface Inter<T> {
void show(T t);
}
public class InterImpl implements Inter {
public void show(Object t) {
System.out.println("show::" + t);
}
public static void main(String[] args) {
new InterImpl().show("Generic_Interface");
}
}
5.3,类型通配符
正如前面的总结,当我们使用一个泛型类时(包括创建对象或者声明变量),应该为这个泛型类传入一个类型实参,如果没有传入类型实际参数,就会引发泛型警告。下面定义一个方法,该方法有一个几个形参,形参的元素类型是不确定的。
public void test(List c){
for(int i = 0;i < c.size(); i++){
System.out.println(c.get(i));
}
}
上面是一段普通的变量List集合的代码,问题是List是一个有泛型声明的接口,此处使用List接口时没有传入实际类型参数,将引起泛型警告。为此考虑为List接口传入实际的类型参数——因为List集合里的元素类型是不确定的。
public void test(List<Object> c){
for(int i = 0;i < c.size(); i++){
System.out.println(c.get(i));
}
}
试图用以下的代码调用该方法:
List<String> strList = new ArrayList<String>();
test(strList);
编译时会发现,在调用处的代码出现编译错误:
无法将Test中的test(java.util.List<java.lang.Object>)应用于(java.util.List<java.lang.Strig>)
这意味着:List<String>对象不能被当成List<Object>对象使用,也就是说List<String>类并不是List<Object>类的子类。
注意:如果Foo是Bar的一个子类型(子类或者子接口),而G是具有泛型声明的类或接口,那么G<Foo>是G<Bar>的子类型并不成立!
5.3.1,使用类型通配符
可以解决当具体类型不确定的情况,这个通配符的表示?。当操作类型时,不需要使用类型的具体功能时,只使用Object类中的功能,那么可以用?通配符来表示未知类型。上面的代码可以改为:
public void test(List<?> c){
for(int i = 0;i < c.size(); i++){
System.out.println(c.get(i));
}
}
现在我们可以使用任何类型的List来调用它,程序依然可以访问集合c中的元素,其类型是Object,这永远是安全的,因为不管List的真实类型是什么。它包含的都是Object。
注意:其实这个写法可以适用于任何支持泛型声明的接口和类,比如写成Set<?>、Collection<?>、Map<?,?>等。
但是这种带通配符的List仅表示它是各种泛型List的父类,并不能把元素加入到其中,例如下面的代码将会引起编译错误:
List<?> c = new ArrayList<String>();
c.add(new Object());
因为我们不知道上面程序中c集合里元素的类型,所以不能向其中添加对象。根据List<E>接口定义的代码可以发现:add方法有类型参数E作为集合的元素类型,
所以我们传给add的参数必须是E类的对象或者其子类对象。因为不知道E是什么类型,所以程序无法将任何对象添加到集合。唯一例外的是null,它是所有引用类型的实例。
但是,程序可以调用get()方法来返回List<?>集合指定索引处的元素,其返回值是一个未知类型,但可以肯定的是:它总是一个Object。因此把get()的返回值赋给一个Object类型的变量,或者放在任何希望是Object类型的地方都可以。
5.3.2,设定类型通配符的上限
当直接使用List<?>这种形式时,即表明这个List集合可以是任何泛型List的父类。但还有一种特殊的情形,我们不想这个List<?>是任何泛型List的父类,
只想表示它是某一类泛型List的父类。此时,可以考虑给List设定类型通配符的上限:List<? extends E>,它表示List可以接收E类型或者E类型的子类对象。
类型通配符上限:? extends E——>表示可以接收E类型或者E的子类型对象。
5.3.3,设定类型形参的上限
Java泛型不仅允许在使用通配符形参时设定类型上限,也可以在定义类型形参时设定上限,用于表示传给该类型形参的实际类型必须是该上限类型,或者是该上限类型的子类。例如示例代码:
public class Pig<T extends Number> {
T count;
public static void main(String[] args) {
Pig<Integer> p1 = new Pig<Integer>();
System.out.println(p1.count);
Pig<Double> p2 = new Pig<Double>();
System.out.println(p2.count);
//Pig<String> p3 = new Pig<String>;
}
}
上面程序定义了一个Pig泛型类,该Pig类的类型形参的上限是Number类,这表明使用Pig类时为T形参传入的实际类型参数只能是Number,或者Number类的子类。所以上面被注释的代码Pig<String> p3 = new Pig<String>();将会引发编译错误:因为类型形参T是有上限的,而此处传入的实际类型是String类型,既不是Number类型,也不是Number类型的子类型。
注意:在一种极端的情况下,程序需要为形参设定多个上限(至多有一个父类上限,可以有多个接口上限)表明该类型形参必须是其父类的子类(包括父类本身),并且实现多个上限接口。如下代码所示:
//表明T类型必须是Number类或其子类,并必须实现了java.io.Serializable接口
public class Apple<T extends Number & java.io.Serializable>
{
...
}
与类同时继承父类、实现接口类似的是:为类型形参指定多个上限时,所有的接口上限必须位于类上限之后。也就是说,如果需要为类型形参指定类上限,类上限必须位于第一位。
5.4,泛型方法
在定义了类、接口时可以使用类型形参,在该类的方法定义和属性定义、接口的方法定义中,这些类型形参可被当成普通类型来用。在另一些情况下,我们定义类、接口时没有使用类型形参,但定义方法时想自己定义类型形参,这也是可以的,JDK1.5还提供了泛型方法的支持。
5.4.1,定义泛型方法
所谓泛型方法,就是在声明方法时定义一个或多个类型形参。泛型方法的定义格式如下:
修饰符 <T,S> 返回值类型 方法名(形参列表)
{
//方法体...
}
把上面方法的格式和普通方法的格式进行对比,发现泛型方法的方法签名比普通方法的方法签名多了类型形参声明,类型形参声明以尖括号<>括起来,多个类型形参之间以逗号(,)隔开,所有形参声明放在方法修饰符和方法返回值类型之间,如:
1)普通泛型方法:当方法操作的引用数据类型不确定的时候,可以将泛型定义在方法上。
public <T> void method(T t){
System.out.println("method:"+t);
}
2)静态泛型方法:静态方法无法访问类上定义的泛型,如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。
public static <T> void function(T t){
System.out.println("function:"+t);
}
5.4.2,设定类型通配符下限
当直接使用List<?>这种形式时,即表明这个List集合可以是任何泛型List的父类。但还有一种特殊的情形,我们不想这个List<?>是任何泛型List的父类,只想表示它是某一类泛型List的子类。此时,可以考虑给List设定类型通配符的下限:List<? super E>,它表示List可以接收E类型或者E类型的父类对象。
类型通配符下限:? super E——>表示可以接收E类型或者E的父类型对象。
5.4.3,泛型方法与方法的重载
因为泛型既允许设定通配符上限,也允许设定通配符的下限,从而允许在一个类里包含这样两个方法的定义:
public class TestUtils
{
public static <T> void copy(Collection<T> dest , Collection<? extends T> src){...}
public static <T> T copy(Collection<? super T> dest , Collection<T> src){...}
}
上面的TestUtils类定义了两个copy方法,这两个方法的两个参数都是Collection对象,前一个集合里集合元素类型是后一个集合里集合元素类型的父类。这个类里包含了这两个方法不会有任何错误,问题是调用这个方法时,就会引起编译错误:
List<Number> ln = new ArrayList<Number>();
List<Integer> li = new ArrayList<Integer>();
copy(ln,li);
上面的调用copy的方法,既可以匹配TestUtils类里第一个copy方法,此时T代表的Number类型;也可以匹配第二个copy方法,此时T代表的是Integer类型。编译器无法确定这行代码到底调用的是哪个copy方法,所以这行代码将引起编译错误。
5.5,泛型擦除
在严格的泛型代码里,带泛型声明的类总应该带着类型参数。但为了与JDK1.5之前的代码保持一致,也允许在使用带泛型声明的类时不指定类型参数。如果没有为这个泛型类指定类型参数,则该类型参数被称作raw type(原始类型),默认是该声明该参数时指定的第一个上限类型。
当把一个具有泛型信息的对象赋给另一个没有泛型信息的变量时,则所有在尖括号之间的类型信息都被扔掉了。比如一个List<String>类型被转换为List,则该List对集合元素的类型检查变成了类型变量的上限(即Object)。
import java.util.*;
public class GenericReasure {
public static void main(String[] args) {
List<Integer> li = new ArrayList<Integer>();
li.add(2);
li.add(32);
List list = li;
//下面代码引发“未经检查的转换”,编译、运行正常
List<String> ls = list;
//访问ls集合里的元素,将会编译错误
System.out.println(ls.get(0));
}
}
上面的程序中定义了一个List<Integer>对象,这个List对象保留了集合元素的类型信息。但当把这个List对象赋给一个List变量list后,编译器就会丢失前者的泛型信息,即丢失了list集合里元素的类型信息,这是典型的擦除。Java允许直接把List对象赋给一个List<Type>(Type可以是任何类型)类型的变量,所以List<String> ls = list;编译可通过,只是会报出“未经检查的转换”警告。但对list变量实际上引用的是List<Integer>集合,所以当试图把集合里的元素当成String类型的对象取出时,将引发运行时异常。
5.6,泛型总结
1,泛型:JDK1.5版本以后出现的一个安全机制。表现格式:<Type>
2,好处:
1)将运行时期的问题ClassCastException问题转换成了编译失败,体现在编译时期,方便程序员解决问题。
2)避免了强制类型转换的麻烦。
3,只要带有<>的类或者接口,都属于带有类型参数的类或者接口,在使用这些类或者接口时,必须给<>中传递一个具体的引用数据类型。
4,泛型技术:其实应用在编译时期,是给编译器使用的技术,到了运行时期,泛型就不存在了。为什么?因为泛型擦除,也就是说,编译器检查了泛型的类型正确后,在生成的类文件中是没有泛型的。
5,在运行时,如何知道获取的元素类型而不用强制转换呢?
泛型补偿:因为存储元素的时候,类型已经确定了是同一个类型的元素,所以在运行时,只要获取到该元素的类型,在内部进行一次转换即可,所以使用者不用再做类型转换。
6,什么时候用泛型类?
当类中所操作的引用数据类型不确定的时候,以前用的是Object来扩展的,现在可以用泛型来表示。这样可以避免强转的麻烦,而且将运行期间的问题转移到了编译时期。
7,泛型的应用
1)泛型类
class Test<T>{
private T t;
public void setT(T t){
this.t = t;
}
public T getT(){
return t;
}
}
2)泛型接口
interface Inter<T>{
void show(T t);
}
class InterImpl<T> implements Inter<T>{
public void show(T t){
System.out.println("show:"+t);
}
}
3)普遍泛型方法
public <T> void method<T t>{
System.out.println("method:"+t);
}
4)静态泛型方法
public static <T> void function(T t){
System.out.println("function:"+t);
}
8,类型通配符:可以解决当具体类型不确定的时候,这个通配符就是“?”。当操作类型时,不需要使用类型的具体功能时,值使用Object类中的功能,那么可以用?通配符来表位置类型。
9,泛型限定
1)上限:? extends E -->可以接收E类型或者E的子类型对象。
2)下限:? super E -->可以接收E类型或者E的父类型对象。
第二部分 反射
1,获取字节码文件对象第一种方式:通过对象获得
Java中每个类的对象都有一个getClass()方法,返回一个Class类的对象
Person p = new Person();
Class c1 = p.getClass();
第二种方式:通过类的属性获得
Java中每个类都有一个class属性,该属性就是class对象
Class c2 = Person.class;
第三种方式:Class类下的静态方法Class forName(String str)方法,这个最常用
Class c3 = Class.forName("cn.itcast.shisong.Person");
面试题:forName(String str)的作用是什么?答,作用是返回字节码。返回的方式有两种,第一种是这个字节码曾经被加载过,已经存在于Java虚拟机中,返回这个字节码。另一种是这个字节码没有被加载过,则用类加载器去加载,存入Java虚拟机中,然后返回这个字节码,以后无需加载。
2,获取构造方法并运行(java.lang.Class)
获取公共的构造方法:
Constructor<T> getConstructor(Class<?>... parameterTypes):返回一个Constructor对象,它反映此Class对象所表示的类的指定公共构造方法。
Constructor<?>[] getConstructors():返回一个包含某些Constructor对象的数组,这些对象反映此Class对象所表示的类的所有公共构造方法。
获取所有的构造方法:
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes):返回一个Constructor对象,该对象反映此Class对象所表示的类或接口的指定构造方法。
Constructor<?>[] getDeclaredConstructors():返回Constructor对象的一个数组,这些对象反映此Class对象表示的类声明的所有构造方法。
运行得到的构造方法,创建对象(java.lang.reflect.Constructor)
T newInstance(Object... initargs):使用此Constructor对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例。
注意:当调用Constructor类下的getDeclaredConstructor(Class<?>... parameterTypes)方法获取私有构造方法时,要注意不能直接调用T newInstance(Object... initargs)来创建实例,这样会引发java.lang.IllegalAccessException异常。此时,要调用Constructor的父类java.lang.reflect.AccessibleObject下的void setAccessible(boolean flag)方法,flag值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。值为false则指示反射的对象应该实施Java语言访问检查。
需要反射操作的类Person:
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name) {
this.name = name;
}
protected Person(int age) {
this.age = age;
}
private Person(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return name + "..." + age;
}
}
反射操作类ReflectDemo1:
import java.lang.reflect.*;
public class ReflectDemo1 {
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("cn.itcast.shisong.Person");
// 获取多个构造方法,Class下的getConstructors()方法只能获取到公共的构造方法
/*
* Constructor[] con = clazz.getConstructors(); for(Constructor c :
* con){ System.out.println(c); }
*/
// 获取单个构造方法,Class类选的getConstructor()方法
Constructor con = clazz.getConstructor();
System.out.println(con);
System.out.println("-------------------------");
// 获取有参数的构造方法,Class类下的getConstructor(Class...class)方法
Constructor con1 = clazz.getConstructor(String.class);
System.out.println(con1);
// 通过获得的构造方法,创建实例
Object obj = con1.newInstance("zhangsan");
System.out.println(obj);
System.out.println("-------------------------");
// 获取某类中的私有构造方法
Constructor conprivate = clazz.getDeclaredConstructor(String.class,int.class);
System.out.println(conprivate);
conprivate.setAccessible(true); // 设置true,取消Java运行的语法检查
Object obj1 = conprivate.newInstance("shangsan", 20);
System.out.println(obj1);
}
}
3,获取成员方法并运行(java.lang.Class)
获取公共成员方法:
Method getMethod(String 方法名, Class<?>... 参数列表):通过传入方法名和参数列表,获取指定的公共成员方法。
Method[] getMethods():获取所有的公共方法,包括从父类继承得来的公共方法。
获取所有成员方法:
Method getDeclaredMethod(String 方法名, Class<?>... 参数列表):通过传入方法名和参数列表,获取指定的成员方法。
Method[] getDeclaredMethods():获取所有的构造方法。
运行获得的成员方法(java.lang.reflect.Method)
Object invoke(Object 对象引用, Object...参数列表):运行成员方法。
需要反射操作的类Person:
public class Person {
public void show() {
System.out.println("Person...show");
}
public void show(int x) {
System.out.println("Person...show..." + x);
}
private void method() {
System.out.println("Person...method");
}
public String toString() {
return name + "..." + age;
}
}
反射操作类ReflectDemo2:
import java.lang.reflect.*;
public class ReflectDemo2 {
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("cn.itcast.shisong.Person");
/*
* Method[] method = clazz.getMethods(); for(Method m : method){
* System.out.println(m); }
*/
// 获取无参的公共成员方法
Method method1 = clazz.getMethod("show");
System.out.println(method1);
// 运行成员方法要求有对象,所以建立对象
Object obj = clazz.newInstance();
method1.invoke(obj);
// 获取有参的公共成员方法
Method method2 = clazz.getMethod("show", int.class);
System.out.println(method2);
method2.invoke(obj, 22);
// 获取私有的成员方法
Method method3 = clazz.getDeclaredMethod("method");
System.out.println(method3);
// 取消Java的语法检查,否则会出异常
method3.setAccessible(true);
method3.invoke(obj);
}
}
4,获取成员变量并改值(java.lang.Class)
获取公共成员变量:
Field getField(String 成员变量名):获取指定字段名的公共的成员变量,封装成Field对象并返回。
Field[] getFields():获取所有的公共成员变量,封装成Field对象数组并返回。
获取所有成员变量:
Field getDeclaredField(String 成员变量名):获取指定字段名的的成员变量,封装成Field对象并返回。
Field[] getDeclaredFields():获取所有的成员变量,封装成Field对象数组并返回。
获取和修改成员变量的值(java.lang.reflect.Field):
Object get(Object obj):返回指定对象上此 Field 表示的字段的值。
void set(Object obj, Object value):将指定对象变量上此Field对象表示的字段设置为指定的新值。
需要反射操作的类Person:
public class Person {
private String name;
private int age;
public String address;
public String sex;
public Person() {
}
public String toString() {
return name + "..." + age;
}
}
反射操作类ReflectDemo3:
import java.lang.reflect.*;
public class ReflectDemo3 {
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("cn.itcast.shisong.Person");
/*
* Field[] field = clazz.getDeclaredFields(); for(Field f : field){
* System.out.println(f); }
*/
// 获取指定的公共权限成员属性
Field f1 = clazz.getField("address");
Field f2 = clazz.getField("sex");
// 获取该类的实例
Object obj = clazz.newInstance();
// 设置属性的值
f1.set(obj, "beijing");
f2.set(obj, "male");
System.out.println("address:" + ((Person) obj).address);
System.out.println("sex:" + ((Person) obj).sex);
// 获取指定的私有权限的成员属性
Field f3 = clazz.getDeclaredField("name");
Field f4 = clazz.getDeclaredField("age");
// 设置跳过java语法检查
f3.setAccessible(true);
f4.setAccessible(true);
// 改变私有变量的值
f3.set(obj, "lisi2013");
f4.set(obj, 24);
System.out.println(obj);
}
}
5,数组的反射应用
java.lang.Class类的判断方法:boolean isArray():判定此Class对象是否表示一个数组类。
java.lang.reflect.Array类下的方法:
static Object get(Object array, int index):返回指定数组对象中索引组件的值。
static int getLength(Object array):以 int 形式返回指定数组对象的长度。
static Object newInstance(Class<?> componentType, int... dimensions):创建一个具有指定的组件类型和维度的新数组。
static Object newInstance(Class<?> componentType, int length):创建一个具有指定的组件类型和长度的新数组。
static void set(Object array, int index, Object value):将指定数组对象中索引组件的值设置为指定的新值。
*其它各种对基本数据类型操作的get和set方法,详情查看jdk1.6 api。
import java.lang.reflect.*;
public class ReflectDemo4 {
public static void main(String[] args) {
String[] arr = new String[] { "abc", "abb", "acc" };
String str = "abcdefg";
arrayReflect(arr);
arrayReflect(str);
}
public static void arrayReflect(Object obj) {
Class clazz = obj.getClass();
if (clazz.isArray()) {
int len = Array.getLength(obj);
for (int index = 0; index < len; index++) {
System.out.println(Array.get(obj, index));
}
// 改变数组的值
Array.set(obj, 1, "xyz");
System.out.println("改变后的arr[1]::" + Array.get(obj, 1));
} else {
System.out.println(obj);
}
}
}
最后
以上就是想人陪铅笔为你收集整理的Java基础(十四)——高新技术之jdk1.5新特性、反射的全部内容,希望文章能够帮你解决Java基础(十四)——高新技术之jdk1.5新特性、反射所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复