黑马程序员--高新技术--枚举、反射、注解、类加载器、内省

时间:2023-02-18 13:41:38

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

1.枚举

关键字 enum
1.枚举就是要让某个类型的变量的取值只能为若干固定值之中的一个。否则,编译器就会报错。枚举可以让编译器在编译时就可以控制源程序中填写的非法值,普通变量的方式在开发阶段无法实现这一目标。
是一个特殊的类,其中的每一个元素都是该类的一个对象。

2.枚举就相当于一个类,其中也可以定义构造方法、成员变量、普通方法和抽象方法。枚举元素必须位于举体中的最开始部分,枚举元素列表的后要有分号与其他成员分隔。把枚举中的成员方法或变量等放在枚举元前面,译器报告错误。

3.当枚举只有一个成员的时,就可以作为一种单例的实现方式。
4.构造方法必须定义成私有的。
注意 : 因为不能New 枚举的实力,所以枚举不能继承其他类,也不能被其他类

继承。

示例:

public class EnumDemo {

public static void main(String[] args) {
WeekDay1 weekDay1 = WeekDay1.MON;
System.out.println(weekDay1.nextDay());

WeekDay weekDay = WeekDay.FRI;
System.out.println(weekDay.ordinal());// 在枚举中顺序
System.out.println(WeekDay.valueOf("SAT"));// 静态方法,将字符串转成枚举类型
System.out.println(WeekDay.values().length);// 静态方法,枚举的长度

}

public enum WeekDay {// 枚举
SUN(1), MON, TUE(), WED(2), TUR, FRI, SAT(2);// 带参数枚举
private WeekDay() {
System.out.println("first");
}

private WeekDay(int x) {
System.out.println("second");
}

public enum trafficLamp {
RED(30) {
@Override
public trafficLamp nextLamp() {
return YELLOW;
}
},
GREEN(45) {
@Override
public trafficLamp nextLamp() {
return RED;
}
},
YELLOW(10) {
@Override
public trafficLamp nextLamp() {
return GREEN;
}
};
private int time;

private trafficLamp(int time) {
this.time = time;
}

public abstract trafficLamp nextLamp();
}

}
}

abstract class WeekDay1 {// 抽象类
private WeekDay1() {
};

public final static WeekDay1 SUN = new WeekDay1() {

@Override
public WeekDay1 nextDay() {
return MON;
} // 匿名内部类
};
public final static WeekDay1 MON = new WeekDay1() {

@Override
public WeekDay1 nextDay() {
return SUN;
}
};

public abstract WeekDay1 nextDay();// 抽象方法

@Override
public String toString() {
return this == SUN ? "SUN" : "MON";
}

}


2.反射

2.1反射是在运行状态中,对于任意一个类(class文件),都能够知道这个类的所有属性和方法;
对于任意一个对象,都只能都调用它的任意一个方法和属性,这种动态获取的信息一级动态调用
对象的方法的功能呢个称为java 的反射机制。

2.2反射其实就是动态加载一个指定的类,并获取该类中的所有的内容。而且将字节码文件封装成对象,
并将字节码文件中的内容都封装成对象,这样便于操作这些成员。就是把JAVA类中的各种成分反射成
为相应的JAVA类
简单说:反射技术可以对一个类进行解剖。
 
 
 
 
2.3反射的好处:大大的增强了程序的扩展性。 

2.4反射的基本步骤: 
1、获得Class对象,就是获取到指定的名称的字节码文件对象。 
2、实例化对象,获得类的属性、方法或构造函数。 
3、访问属性、调用方法、调用构造函数创建对象。

2.5得到类的字节码文件相同,这三种方式。

1.Class cls1=类名.class 相对简单,还是需要明确类名

2.cls1.getclass();必须要明确具体的类,并创建对象

3.class.forName("完整的类名(带包名)");


2.6 9个预定义的Class实例对象。
Integer.class包装类的字节码
Integer.TYPE Integer中基本类型的字节码
class.isPrimitive();
class.isArray();

int.class==Integer.TYPE

数组 

class.isPrimitive();

class.isArray();

注:只要在源程序中出现的类型,都有各自的Class实例对象,例如 int[] void 等等

2.7 创建类的对象
public class ReflectDemo5 {
public static void main(String[] args) throws Exception{

String name="cn.heima.类名";
// 寻找该名称类文件,并加载进内存,并非产生class对象
Class clazz=Class.forName(name);
// 产生该类的对象
Object obj=clazz.newInstance();
// 得到某一个指定构造方法
Constructor constructor= Class.forName(name).getConstructor(String.class);
// 创建实例对象
Object obj2=constructor.newInstance("abc");

}

}

2.8 Constructor 构造方法:代表某个类中的一个构造方法。
    //得到某个类中所有的构造方法Constructor  constructors[]= Class.forName("cn.heima.className").getConstructors();;     // 得到某一个构造方法    Constructor  constructor1= Class.forName("cn.heima.className").getConstructor(StringBuffer.class);         //创建实例对象         String str=(String) constructor1.newInstance(new StringBuffer("abc"));        // 调用获得的方法时要用到上面相同类型的实例对象         //Class.newInstance()方法:先得到默认的构造方法,然后用该构造方法创建实例对象。          String obj3=(String)Class.forName("java.lang.String").newInstance();     
2.9 成员变量 Filed类 代表某个类中的一个成员变量
// 对一个类进行反射。Field fieldy = Class.forName("cn.heima.className").getField("Y");// 只代表哪个对象Field fieldx = Class.forName("cn.heima.className").getDeclaredField("x");// 获取对象不管是私有还是被保护的fieldy.setAccessible(true);// 设置可以访问,暴力反射Object obj5 = Class.forName("cn.heima.className").newInstance();// 对象实例化fieldy.get(obj5);// 取出Y的值// 将字段中的b全变成aField[] fields = obj5.getClass().getFields();// 获取全部对象for (Field field : fields) {if (field.getType() == String.class) {// 如果是字符串String oldValue = (String) field.get(obj);// 获取字符串内容String newValue = oldValue.replace('b', 'a');// 将字符串内容替换field.set(obj, newValue);// 将新值赋给对象}}

2.10 Method类 成员方法的反射。
//("charAt"--方法名,int.class--这是参数类型,如果是空参函数则是 null)Method  methodCharAt = String.class.getMethod("charAt",int.class);     Object obj6=clazz.newInstance();          methodCharAt.invoke(obj6,1);

2.11 反射来获取泛型信息

ArrayList<Integer> al = new ArrayList<Integer>();

Method method = Generic.class.getMethod("genericType", ArrayList.class);
Type[] types = method.getGenericParameterTypes();//获得泛型参数类型
ParameterizedType para = (ParameterizedType) types[0];// 并将泛型参数类型强转成参数类型

// ParameterizedType para=(ParameterizedType)method.getGenericParameterTypes()[0];//将上面两句合成一句,

System.out.println(para.getRawType());// 原始类型
System.out.println(para.getActualTypeArguments()[0]);// 实际类型

3.注解

注解是java 的一个新的类型(与接口很相似),它与类、接口、枚举是在同一个层次,它们都称作为java 的一个类型(TYPE)。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。它的作用非常的多,例如:进行编译检查、生成说明文档、代码分析等。

3.1JDK提供的几个基本注解

1. @SuppressWarnings

该注解的作用是阻止编译器发出某些警告信息。

它可以有以下参数:

deprecation:过时的类或方法警告。

unchecked:执行了未检查的转换时警告。

fallthrough:当Switch 程序块直接通往下一种情况而没有Break 时的警告。

path:在类路径、源文件路径等中有不存在的路径时的警告。

serial:当在可序列化的类上缺少serialVersionUID 定义时的警告。

finally:任何finally 子句不能完成时的警告。

all:关于以上所有情况的警告。

2. @Deprecated

该注解的作用是标记某个过时的类或方法。

3. @Override

该注解用在方法前面,用来标识该方法是重写父类的某个方法。

元注解

4. @Retention

它是被定义在一个注解类的前面,用来说明该注解的生命周期。

它有以下参数:

RetentionPolicy.SOURCE:指定注解只保留在一个源文件当中。

RetentionPolicy.CLASS:指定注解只保留在一个class 文件中。

RetentionPolicy.RUNTIME:指定注解可以保留在程序运行期间。

5. @Target

它是被定义在一个注解类的前面,用来说明该注解可以被声明在哪些元素前。

它有以下参数:

ElementType.TYPE:说明该注解只能被声明在一个类前。

ElementType.FIELD:说明该注解只能被声明在一个类的字段前。

ElementType.METHOD:说明该注解只能被声明在一个类的方法前。

ElementType.PARAMETER:说明该注解只能被声明在一个方法参数前。

ElementType.CONSTRUCTOR:说明该注解只能声明在一个类的构造方法前。

ElementType.LOCAL_VARIABLE:说明该注解只能声明在一个局部变量前。

ElementType.ANNOTATION_TYPE:说明该注解只能声明在一个注解类型前。

ElementType.PACKAGE:说明该注解只能声明在一个包名前。

3.2注解的生命周期
一个注解可以有三个生命周期,它默认的生命周期是保留在一个CLASS 文件,但它也可以由一个
@Retetion 的元注解指定它的生命周期。

1. java 源文件

当在一个注解类前定义了一个@Retetion(RetentionPolicy.SOURCE)的注解,那么说明该注解只保留在一个源文件当中,当编译器将源文件编译成class 文件时,它不会将源文件中定义的注解保留在class 文件中。

2. class 文件中

当在一个注解类前定义了一个@Retetion(RetentionPolicy.CLASS)的注解,那么说明该注解只保留在一个class 文件当中,当加载class 文件到内存时,虚拟机会将注解去掉,从而在程序中不能访问。

3. 程序运行期间
当在一个注解类前定义了一个@Retetion(RetentionPolicy.RUNTIME)的注解,那么说明该注解在程序运行期间都会存在内存当中。此时,我们可以通过反射来获得。


3.3注解的定义

一个简单的注解:

public @interface Annotation01 {

//定义公共的final静态属性
.....
//定义公共的抽象方法
.....

} 

3.2 注解可以有哪些成员

注解和接口相似,它只能定义final 静态属性和公共抽象方法。

3.3. 注解的方法

1.方法前默认会加上public abstract

2.在声明方法时可以定义方法的默认返回值。
 

例如:

String color() default "blue";

String[] color() default {"blue", "red",......}

3.方法的返回值可以有哪些类型

8 种基本类型,String、Class、枚举、注解及这些类型的数组。

3.4. 使用注解
注解的使用分为三个过程。
定义注解-->声明注解-->得到注解

a. 定义注解(参照上面的注解定义)
b. 声明注解
c. 得到注解

1. 在哪些元素上声明注解
如果定义注解时没有指定@Target 元注解来限制它的使用范围,那么该注解可以使用在ElementType 枚举指定的任何一个元素前。否则,只能声明在@Target 元注解指定的元素前。
一般形式:

@注解名()

2. 对注解的方法的返回值进行赋值
对于注解中定义的每一个没有默认返回值的方法,在声明注解时必须对它的每一个方法的返回值进行赋值。

一般形式:

@注解名(方法名=方法返回值,、、、、、、)

如果方法返回的是一个数组时,那么将方法返回值写在{}符号里
@注解名(方法名={返回值1,返回值2,、、、、、、},、、、、、、、)

3. 对于只含有一个方法的注解,在声明注解时可以只写返回值。
4. 得到注解

对于生命周期为运行期间的注解,都可以通过反射获得该元素上的注解实例。

1、声明在一个类中的注解

可以通过该类Class 对象的getAnnotation 或getAnnotations 方法获得。

2、声明在一个字段中的注解

通过Field 对象的getAnnotation 或getAnnotations 方法获得

3、声明在一个方法中的注解

通过Method 对象的getAnnotation 或getAnnotations 方法获得

总结:
注解可以看成是一个接口,注解实例就是一个实现了该接口的动态代理类。
注解大多是用做对某个类、方法、字段进行说明,标识的。以便在程序运行期间我们通过反射获得该字段或方法的注解的实例,来决定该做些什么处理或不该进行什么处理 
@AnnotationOne("OMG")// 应用注解类
public class AnnotationTest {
@SuppressWarnings("deprecation")// 注解
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
System.runFinalizersOnExit(true);
if (AnnotationTest.class.isAnnotationPresent(AnnotationOne.class)) {// 判断是否有注解
// 获取注解
AnnotationOne annotation = (AnnotationOne) AnnotationTest.class.getAnnotation(AnnotationOne.class);
System.out.println(annotation);
System.out.println(annotation.color());// 获取注解的属性信息
System.out.println(annotation.value());
System.out.println(annotation.arrayAttr().length);
System.out.println(annotation.annotationAttr().value());
}

Method mainMethod = AnnotationTest.class.getMethod("main",String[].class);
AnnotationOne annotation2 = (AnnotationOne) mainMethod.getAnnotation(AnnotationOne.class);
System.out.println(annotation2.value());
}

@Deprecated
public static void sayHello() {
System.out.println("hi,everyone!");
}
}

@Retention(RetentionPolicy.RUNTIME)
// 设置注解的生命周期
@Target({ ElementType.METHOD, ElementType.TYPE })
// 设置注解的位置
@interface AnnotationOne {
String color() default "blue";// 设置注解默认的属性

String value();

int[] arrayAttr() default { 3, 4, 4 };// 设置注解默认的属性

MetaAnnotation annotationAttr() default @MetaAnnotation("10");
}

@interface MetaAnnotation {// 注解类
String value();
}


4.类加载器

4.1类加载器:就是加载类的工具。

4.2JAVA虚拟机中可以安装多个类加载器,系统默认三个主要的类加载器,每个类负责加载特定位置的类。

 BootStrap-------ExtClassLoader------AppClassLoader classpath指定的类加载器

    父级              子集               子子集

4.3类加载器也是JAVA类,因为其他是JAVA类的类加载器本身也要被类加载其加载。显然必须有第一个类加载器不是JAVA类,这正是BootStrap;所以类加载器是用BootStrap加载的。

4.4Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载。 

4.5获取类加载器名称。

类名.class.getClassLoader().getClass().getname();


4.6类加载器的委托机制:

当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?

   
   
  1. 首先当前线程的类加载器去加载线程中的第一个类。
  2. 如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器来加载类B。
  3. 还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
  4. 每个类加载器加载类时,又先委托给其上级类加载器。

4.7自定义类加载器:

   首先,类加载器必须要继承ClassLoader。 覆盖findClass方法
public class ClassLoaderDemo {

public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
//打印加载器名称
System.out.println(ClassLoaderDemo.class.getClassLoader().getClass().getName());
System.out.println(System.class.getClassLoader());//null:因为System的类加载器是BootStrap
ClassLoader loader = ClassLoaderDemo.class.getClassLoader();//获取类加载器
while (loader != null) {//如果非BootStrap
System.out.println(loader.getClass().getName());//打印加载器名称
loader = loader.getParent();//父类加载器
}
System.out.println(loader);
Class clazz = new MyClassLoader("heima").loadClass("cn.heima.newtechnology.ClassLoaderAttachment");
Date d = (Date) clazz.newInstance();
System.out.println(d);
}

}

class ClassLoaderAttachment extends Date {
public String toString() {
return "hello,girl";
}
}

// 自定义类加载器
class MyClassLoader extends ClassLoader {

public static void main(String[] args) {
MyClassLoader.class.getResourceAsStream("name");
String scrpath = args[0];
String desdir = args[1];
String fileName = scrpath.substring(scrpath.lastIndexOf("\\"));
String despath = desdir + "\\" + fileName;
System.out.println(despath);

}

private String dir;

@SuppressWarnings("deprecation")
@Override
// 自定义类加载器需覆盖findClass()方法
protected Class<?> findClass(String name) throws ClassNotFoundException {

String fileNamePath = dir + "\\" + name;
try {
FileInputStream fis = new FileInputStream(fileNamePath);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int len = 0;
while ((len = fis.read(buf)) != -1) {
baos.write(buf, 0, len);
}
fis.close();
baos.close();
byte[] arr = baos.toByteArray();
return defineClass(arr, 0, baos.size());

} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

return null;
}

public MyClassLoader() {
}

public MyClassLoader(String dir) {
this.dir = dir;

}

}



5.内省

5.1 JavaBean与内省

  
  
    1. 内省:IntroSpector
    2. JavaBean是一种特殊的Java类,主要用于传递数据信息,这种java类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。
    3. 如果要在两个模块之间传递多个信息,可以将这些信息封装到一个JavaBean中,这种JavaBean的实例对象通常称之为值对象(Value Object,简称VO)。这些信息在类中用私有字段来存储,如果读取或设置这些字段的值,则需要通过一些相应的方法来访问。
    4. JavaBean的属性是根据其中的setter和getter方法来确定的,而不是根据其中的成员变量。如果方法名为setId,意思即为设置id。如果方法名为getId,意思即为获取id。去掉set前缀,剩余部分就是属性名,如果剩余部分的第二个字母是小写的,则把剩余部分的首字母改成小的。

例如:

setId()的属性名:id

getCPU的属性名:CPU

总之,一个类被当作javaBean使用时,JavaBean的属性是根据方法名推断出来的,它根本看不到java类内部的成员变量。

5.2一个符合JavaBean特点的类可以当作普通类一样进行使用。

5.3JavaBean好处如下:

1-在Java EE开发中,经常要使用到JavaBean。很多环境就要求按JavaBean方式进行操作。

2-JDK中提供了对JavaBean进行操作的一些API,这套API就称为内省。如果要你自己去通过getX方法来访问私有的x,会有一定难度,用内省这套api操作JavaBean比用普通类的方式更方便。

public class IntroSpectorDemo {

public static void main(String[] args) throws Exception {
ReflectPoint rp = new ReflectPoint(5, 1);
// x--->X-->getX-->MethodgetX
String propertyName = "X";
Object retVal = getXMethod(rp, propertyName);
System.out.println(retVal);
Object value = 8;
setXMethod(rp, propertyName, value);
System.out.println(retVal);
getXMethod1(rp, propertyName);

}

// 另外一种方法
private static Object getXMethod1(Object rp1, String propertyName)
throws Exception {
BeanInfo beanInfo = Introspector.getBeanInfo(rp1.getClass());
PropertyDescriptor[] dps = beanInfo.getPropertyDescriptors();
Object value1 = null;
for (PropertyDescriptor dp : dps) {
if (dp.getName().equals(propertyName)) {
Method getX = dp.getReadMethod();
value1 = getX.invoke(rp1);
break;
}
}
return value1;
}
//通过内省的方式对ReflectPoint对象中的成员变量进行读写操作。
private static void setXMethod(Object rp, String propertyName, Object value)
throws IntrospectionException, IllegalAccessException,
InvocationTargetException {
PropertyDescriptor prop1 = new PropertyDescriptor(propertyName,rp.getClass());
Method methodSetX = prop1.getWriteMethod();
methodSetX.invoke(rp, value);// 写的方法没有返回值,但又设置值
}
//通过内省的方式对ReflectPoint对象中的成员变量进行读写操作。
private static Object getXMethod(Object obj, String propertyName)
throws IntrospectionException, IllegalAccessException,
InvocationTargetException {
PropertyDescriptor prop = new PropertyDescriptor(propertyName,obj.getClass());
Method methodGetX = prop.getReadMethod();
Object retVal = methodGetX.invoke(obj);// 获取值是空参
return retVal;
}

}

class ReflectPoint {
public int y;
private int x;

public int getX() {
return x;
}

public void setX(int x) {
this.x = x;
}

public int getY() {
return y;
}

public void setY(int y) {
this.y = y;
}

public int add() {
int sum = 0;
sum = x + y;
return sum;

}

public int add(int x, int y) {
int sum = 0;
sum = (x + y) * 2;
return sum;

}

public ReflectPoint(int x, int y) {
super();
this.x = x;
this.y = y;
}

}