第一部分 静态导入、增强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前的程序时,这个要稍作注意。
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()); } }运行结果:
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,枚举类入门
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关键字,也无需显式调用构造器。前面列出枚举值时无需传入参数,甚至无需括号,仅仅是因为前面的枚举类包含无参数的构造器。
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; } } }运行结果:
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关键字将枚举类定义成抽象类,但因为枚举类需要显式创建枚举值,而不是作为父类,所以定义每个枚举值时必须为抽象方法提供实现,否则将出现编译错误。
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分为如下两类:
//定义下面的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的形式。
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。
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)。
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>表示泛型的使用,<>里面的类型,代表该集合只能存在的元素数据类型,如果存储了规定之外的类型,编译时期就会报错,存不进去。
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形参传入实际的类型参数。
//定义类A继承Apple类,Apple类不能跟类型形参 public class A extends Apple<T>{}注意:方法中的形参(这种形参代表变量、常量、表达式等数据),只有当定义方法时才可以使用数据形参,当调用方法时必须为这些数据形参传入实际的数据;与此类似的是:类、接口中的形参,只有在定义类、接口是才可以使用类型形参,当使用类、接口时应为类型形参传入实际的类型。
//使用Apple类时为T形参传入String类型 public class A extends Apple<String>{}使用方法时必须为所有的数据形参传入参数值,与使用方法不同的是:使用类、接口时可以不为类型形参传入实际类型,下面的代码也是正确的:
//使用Apple类时,没有为T形参传入实际的类型参数 public class A extends Apple{}如果从Apple<String>类派生子类,则在Apple类中所有使用T类型的形参的地方都将被替换成String类型,即它的子类将会继承到String getInfo()和
public class A extends Apple<String> { public String getInfo() { return "A" + super.getInfo(); } //下面方法是错误的,重写父类方法时返回值类型不一致 public Object getInfo(){ return "A"+super.getInfo(); } }如果使用Apple类时没有传入实际的类型参数,Java编译器可能会发出警告:使用了未经检查或不安全的操作。
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);编译时会发现,在调用处的代码出现编译错误:
public void test(List<?> c){ for(int i = 0;i < c.size(); i++){ System.out.println(c.get(i)); } }现在我们可以使用任何类型的List来调用它,程序依然可以访问集合c中的元素,其类型是Object,这永远是安全的,因为不管List的真实类型是什么。它包含的都是Object。
List<?> c = new ArrayList<String>(); c.add(new Object());因为我们不知道上面程序中c集合里元素的类型,所以不能向其中添加对象。根据List<E>接口定义的代码可以发现:add方法有类型参数E作为集合的元素类型,
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> { ... }与类同时继承父类、实现接口类似的是:为类型形参指定多个上限时,所有的接口上限必须位于类上限之后。也就是说,如果需要为类型形参指定类上限,类上限必须位于第一位。
修饰符 <T,S> 返回值类型 方法名(形参列表) { //方法体... }把上面方法的格式和普通方法的格式进行对比,发现泛型方法的方法签名比普通方法的方法签名多了类型形参声明,类型形参声明以尖括号<>括起来,多个类型形参之间以逗号(,)隔开,所有形参声明放在方法修饰符和方法返回值类型之间,如:
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,设定类型通配符下限
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方法,所以这行代码将引起编译错误。
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类型的对象取出时,将引发运行时异常。
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类中的功能,那么可以用?通配符来表位置类型。
第二部分 反射
1,获取字节码文件对象Person p = new Person(); Class c1 = p.getClass();第二种方式:通过类的属性获得
Class c2 = Person.class;第三种方式:Class类下的静态方法Class forName(String str)方法,这个最常用
Class c3 = Class.forName("cn.itcast.shisong.Person");面试题:forName(String str)的作用是什么?答,作用是返回字节码。返回的方式有两种,第一种是这个字节码曾经被加载过,已经存在于Java虚拟机中,返回这个字节码。另一种是这个字节码没有被加载过,则用类加载器去加载,存入Java虚拟机中,然后返回这个字节码,以后无需加载。
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)
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)
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,数组的反射应用
*其它各种对基本数据类型操作的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); } } }