1.枚举:
目的:让某个类型的变量取值只能为若干个固定值中的一个,否则,编译器会报错;
作用:可以让编译器在编译时就可以控制源程序中填写的非法值,普通变量的方式在开发阶段无法实现这一目标。
练习:用普通类如何实现枚举功能,定义一个Weekday的类来模拟枚举功能。
步骤: 1.私有构造函数,防止外部随意创建对象;
2.每个元素分别用一个公有的静态成员变量表示;
3.可以有若干公有方法或抽象方法,例如,要提供nextDay()方法必须是抽象的,因为具体的星期中的第一天是星期几我们是不知道的。
class WeekDayDemo{
public static void main(String[] args) {
WeekDay weekDay=WeekDay.MON;
System.out.println(weekDay.nextDay());
}
}
//将nextDay方法分别由其子类去覆写它中的内容
abstract class WeekDay{
//在这里假设一周只有两天,周一和周日,循环。public final static WeekDay SUN=new WeekDay(){@Overridepublic WeekDay nextDay() {return MON;}};public final static WeekDay MON=new WeekDay(){@Overridepublic WeekDay nextDay() {return SUN;}};private WeekDay(){}public String toString(){return this==SUN?"SUN":"MON";}public abstract WeekDay nextDay();/*public WeekDay nextDay(){if(this==SUN){return MON;}else{return SUN;}}*///将许多的if else语句转成了一个个独立的类,有多少个子类,就转成多少个类。}
枚举的基本应用:定义一个WeekDay枚举,包括一些基本方法;
class WeekDay{
public static void main(String[] args) {
WeekDay weekDay1=WeekDay.FRI;
System.out.println(weekDay1);//这里已经覆写过toString方法,可以直接输出对象内容;
System.out.println(weekDay1.name());
System.out.println(weekDay1.ordinal());//获取到枚举的序数
System.out.println(WeekDay.valueOf("SUN".toString()));//将参数列表中的参数变为对象
System.out.println(WeekDay.values().length);//values方法将其变为数组
}
//在类中定义一个枚举public enum WeekDay{SUN,MON,TUE,WED,THD,FRI,SAR;}}
总结:枚举是一种特殊的类,其中的每个元素都是该类的一个实例对象,
实现带构造函数的枚举:
注意:枚举类Enum中的构造函数 protected Enum(String name,int ordinal),程序员无法调用此构造方法,只有它的子类才能使用,用于由响应枚 举类型声明的编译器发出的代码。
在上面代码基础之上:
public enum WeekDay{
SUN(1),MON,TUE,WED,THD,FRI,SAR;
private WeekDay(){
System.out.println("first");
}
private WeekDay(int day){
System.out.println("second");
}
//在这里,有两个构造函数,如果想知道对象在静态初始化后,调用的是哪个构造函数,可以通过看对象是否带有参数。
// 如这里的SUN就调用的有参构造函数。
}}
实现带有抽象方法的枚举:
class EnumTrafficLamp{
public static void main(String[] args){
TrafficLamp tl=TrafficLamp.GREEN;
System.out.println(tl.nextLamp());
}
public enum TrafficLamp {
//因TrafficLamp是抽象的,所以创建它的三个子类RED,GREEN,YELLOW来覆写它的抽象方法
//RED(20l) {@Overridepublic TrafficLamp nextLamp() {return GREEN;}},GREEN(32l) {@Overridepublic TrafficLamp nextLamp() {return YELLOW;}},YELLOW(10l){@Overridepublic TrafficLamp nextLamp() {return RED;}};public abstract TrafficLamp nextLamp();
//定义时间,作为交通灯的等待时间private long time;TrafficLamp(long time){this.time=time;}}}
注意:枚举中只有一个成员变量时,可以作为一种单例的实现方式。
2.反射(JDK1.2就有的特性)
1.反射的基石:Class,它是一个类,java中各个类都是类,描述这些类的类就是Class.即Class是描述java中类的类;
基本方法应用:它没有公有的构造函数,不能通过new来创建实例对象,而是根据加载进内存的.class文件,如:
Person p1=new Person();
Person p2=new Person();
Class c1=Person.class;//Person类的的字节码就是Class类的一个实例。
p1.getClass();//获得对象所属的类的字节码
Class.forName("java.lang.String");
//得到类的字节码,返回方式两种:如果已加载进内存,则不需再加载直接返回;如果虚拟机中没有此字节码,类加载区需加载,缓存在JVM中,以后不用再加载;
总结:得到各个类实例对象的三种方式:
1.类名.class;如System.class
2.对象.getClass();如new Person().getClass();
3.Class.forName("类名");如Class.forName("java.lang.Date");
九个预定义的Class对象:八个基本数据类型加 void;
Class中有一个方法isPrimitive(),返回布尔型,判断指定的Class实例对象是否表示一个基本数据类型。
Class cl=void.class;//表示void的Class实例对象
String s="abc";Class c1=s.getClass();Class c2=String.class;Class c3=Class.forName("java.lang.String");//这里会抛异常,因为可能类名写错以致找不到类。
System.out.println(c1==c2);//true
System.out.println(c1==c3);//true
System.out.println(c1.isPrimitive());//false,因为String类不是基本类型。
System.out.println(int.class==Integer.TYPE);//true
System.out.println(int[].class.isArray());//数组的Class实例对象,true
总结:只要是在源程序中的类型,都有自己的Class实例对象。
2.反射:把java类中的各种成分映射成相应的java类。
动态的获取类中的内容,自动去找相应的类,并自动加载这个类创建该类的实例对象;在类中,将类中的Filed,Method, Constructor,Package都封装成各自的类。
好处:提高了程序的扩展性(多态也能提高程序的扩展性,但要自己new对象,而反射自动new对象,不用手动操作)。
class Class{
//Class类中的属性
Field field;//字段
Constructor constructor;//构造器
Method method;//方法
//Class类中的方法
Field getField(name);//获取单个字段
Field[] getFields;//获取所有所有字段
Constructor getConstructor();//获取单个构造函数
Constructor[] getConstructors();//获取所有构造函数
Method getMethod();//获取方法
Method[] getMethods();
}
反射首先要获取到字节码文件Class的对象,有三种方式:
方式一:通过对象的getClass()方法;如Class cla=对象.getClass();该种方式不利于扩展,因其需要使用该类对象,并还需创建该类对象;
方式二:任何一个数据类型,都对应着一个类的描述,都可以获取到对应的Class对象,而且通过数据类型的一个静态属性.class完成,但还是要用到对象,对扩展性仍然不好;
方式三:通过Class类中的静态方法forName(String name)方法,根据指定的字符串名称(全类名),可以获取到对应的字节码文件;这种方式不会用到对象,扩展性最好;
如 Class cla=Class.forName("java.lang.String");
反射第二步是创建字节码文件的对象,即Class具体描述的食物实例;
Class cla=Class.forName("java.lang.Object);
Object obj=cla.newInstance();
//这两句话相当于下面一句话;
Object obj=new Object();
Object obj=new Object();这句话的原理:
1.加载了java.lang.Object.class文件进内存;
2.将该文件封装成Class对象;
3.通过new关键字在堆内存中分配空间;
4.调用空参数的构造函数堆该类对象进行初始化。
Class cla=Class.forName("java.lang.Object:);
Object obj=cla.newInstance();原理:
1.查找指定的字节码文件;
2.将其加载进内存,并封装成Class对象;
3.通过newInstance创建实例;其实就是在该方法内部通过new关键字创建实例,并调用空参的构造方法对对象进行初始化。
注意:newInstance只能调用空参数的构造函数初始化对象。
反射第三步,创建字节码文件的带参数的对象,首先获取到这个构造函数的对象;代码体现如下:
Class cla=Class.forName("java.lang.String");
Object obj=cla.newInstance();
//获取字节码文件对象对应的构造函数Constructor con=cla.getConstructor(String.class);
//通过指定的构造函数new实例对象String s=(String)con.newInstance("acd");System.out.println(s);
反射四:通过反射调用对象中的属性,Field类;
Class cla=Class.forName("cn.itcast.Person");
Object obj=cla.newInstance();
//age属性被私有
Field field=cla.getDeclaredField("age");
//需设置为可访问field.setAccessible(true);
//对对象的age属性赋值field.set(obj, 22);field.get(obj);//toString方法被复写System.out.println(obj);
反射五:动态获取指定类中的方法;
getMethods():获取所有方法,包括从父类继承来的方法;
getDeclaredMethods():获取本类中所有权限的方法,不包括从父类继承来的方法。
获取public修饰的方法:
Class cla=Class.forName("cn.itcast.Person");
Object obj=cla.newInstance();Method method=cla.getMethod("方法名",null);//后面的这个参数是方法中的参数列表method.invoke(obj, null);
获取private修饰的方法:
Class cla=Class.forName("cn.itcast.Person");Object obj=cla.newInstance();Method method=cla.getDeclaredMethod("方法名",null);method.setAccessible(true);method.invoke(obj, null);
获取static修饰的方法:
Class cla=Class.forName("cn.itcast.Person");Object obj=cla.newInstance();Method method=cla.getMethod("方法名",null);method.invoke(null, null);//静态方法不需要对象
获取有参数的方法:
Class cla=Class.forName("cn.itcast.Person");
Object obj=cla.newInstance();
Method method=cla.getMethod("Study",String.class,int.class);
method.invoke(obj, "lisi",23);
练习:将任意一个对象中的String类型的成员变量所对应的字符串内容中的"b"改为"a"。
public class Test {
public static void main(String[] args) throws Exception {
//获取字节码文件
Class cla=Class.forName("Test.Ball");
//创建字节码文件对象
Object obj=cla.newInstance();
//获取所有字段
Field[] fields=obj.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
//遍历所有符合条件的字段类型
if (field.getType()==String.class) {
String oldValue=(String)field.get(obj);
String newValue=oldValue.replace('b','a');
field.set(obj, newValue);
}
}
System.out.println(obj);
}
}
class Ball{
private String s1="ball";
private String s2="basketball";
private String s3="pingpeng";
public String toString(){
return s1+"::"+s2+"::"+s3;
}
}