黑马程序员————java中类的加载、反射、动态代理、枚举

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


类的加载、反射、动态代理、枚举


一.类的加载

当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。

1.加载:

就是指将class文件读入内存,并为之创建一个Class对象
★★ 任何类被使用时系统都会建立一个Class对象

2.连接:

验证 是否有正确的内部结构,并和其他类协调一致
准备 负责为类的静态成员分配内存,并设置默认初始化值
解析 将类的二进制数据中的符号引用替换为直接引用

3.初始化:

先是静态初始化,后是成员变量初始化


4.类的初始化时机

创建类的实例
访问类的静态变量,或者为静态变量赋值
调用类的静态方法
使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
初始化某个类的子类
直接使用java.exe命令来运行某个主类

5.类的加载器:

负责将.class文件加载到内存中,并为之生成对应的Class对象。
虽然我们不需要关心类加载机制,但是了解这个机制我们就能更好的理解程序的运行。
类加载器的组成:
Bootstrap ClassLoader 根类加载器(核心类库)
Extension ClassLoader 扩展类加载器(工具插件类)
Sysetm ClassLoader 系统类加载器(我们自定义类)
类加载器的作用:
Bootstrap ClassLoader 根类加载器:也被称为引导类加载器,负责Java核心类的加载,比如System,String等。
在JDK中JRE的lib目录下rt.jar文件中。
Extension ClassLoader 扩展类加载器:负责JRE的扩展目录中jar包的加载。在JDK中JRE的lib目录下ext目录。
System ClassLoader 系统类加载器:负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径。



二.反射

1.反射的概述

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意一个方法和属性;
这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。
而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.


2.获取的Class对象的三种方式:


1).使用对象,调用getClass();

它是Object类中的方法。此方法是普通方法,不是静态的,要通过类的对象的引用去调用。
Student stu = new Studetn();
Class stuClass = stu.getClass();


2).静态的class属性;

任何的"数据类型"都有一个"静态的属性"--class属性,这个属性可以获取这个类的Class对象
int.class; 
Class stuClass2 = Student.class;


3).Class类forName()方法;

用Class类中的静态方法forName()可以获取某个类的Class对象;
public static Class forName(String className)
这里的classname是一个全名限定的类名,也就是带包名的。
Class stuClass3 = Class.forName("cn.itcast.demo01_获取Class对象的三种方式.Student");


   注意:在运行期间,一个类,只有一个Class对象产生。也就是说无论哪种方式获取的class对象都是同一个对象。


三:通过反射获取和使用构造方法和类中成员变量即成员方法


1.获取构造方法:使用的都是class类中的方法

批量获取:
public Constructor[] getConstructors():获取所有公有的构造方法;返回一个包含某些 Constructor 对象引用的数组,这些对象反映此 Class 对象所表示的类的所有公共构造方法
范例 for(Constructor c : conArray ){
★★ System.out.println(c);}//打印出来的就是构造方法名称,如:public cn.itcast.demo02_通过Class获取某个类的的构造方法并调用.Student(boolean)
public Constructor[] getDeclaredConstructors():获取所有的构造方法。包括公有,受保护,默认,私有的构造方法。

获取单个:
public Constructor getConstructor(Class... parameterTypes):获取的有参数的公有的构造方法。
parameterTypes 构造方法形参的类型的Class对象。
范例:Constructor con2 = stuClass.getConstructor(boolean.class);
public Constructor getDeclaredConstructor(Class... parameterTypes):获取指定的构造方法。可以是私有的。



2.调用构造方法:使用Constructor中的方法

public T newInstance(Object... initargs):使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例。
谁调用此方法就使用那个构造方法创建一个对象。也就是返回的是一个类的对象,而所传的参数就是调用构造方法所用的实际参数。
范例:obj = con2.newInstance(true);
System.out.println("obj = " + obj);//obj = cn.itcast.demo02_通过Class获取某个类的的构造方法并调用.Student@1f82982


//由于要访问是私有成员,所以这里要设置"取消访问限制检查",进行暴力访问
public void setAccessible(boolean flag),Constructor的父类AccessibleObject中的方法,取消访问检查。
可以用来访问类中的私有成员

    3.获取成员属性:class类中的方法

批量获取:
public Field[] getFields():获取所有公有的成员变量
public Field[] getDeclaredFields():获取所有的属性。包括私有的
获取单个:
public Field getField(String name):获取公有的指定属性
范例:Field f = stuClass.getField("stuName");
★★ System.out.println(f);//打印的是是属性名,而不是值。★★
public Field getDeclaredField(String name):获取指定属性。可以是私有的。


4.获取、设置属性值:Field类中的方法

public void set(Object obj,Object value):注意:设置前,一定要先实例化一个此类的对象。
参数:obj - 应该修改其字段的对象,也就是此字段所在类的对象;value - 正被修改的 obj 的字段的新值。
public object get(Object obj):获取字段的值,参数就是字段所在的类的对像,从中提取所表示字段的值的对象 。

范例:
//先实例化出一个Student对象
Object stuObj = stuClass.getConstructor().newInstance();
//为字段设置值
f.set(stuObj, "张学友");
//获取字段的值:
System.out.println("获取字段的值:" + f.get(stuObj));

对于私有的属性,要经行包里访问。
public void setAccessible(boolean flag),
Field类对象.setAccessible(true);



5.获取成员方法:class类中的方法


批量获取:
public Method[] getMethods() :获取所有的"公有"方法.并且从父类继承的方法也有★★★
public Method[] getDeclaredMethods() :获取所有方法,包括私有的。
范例:
for(Method m : methodArray){
System.out.println(m);打印的是方法名称(带参数类型)show(int)★★
获取单个:
public Method getMethod(String name, Class... parameterTypes) :获取某个公有的方法。
                形参:name:方法名; parameterTypes:形参的类型列表,可变参数
public Method getDeclaredMethod(String name, Class... parameterTypes):获取某个方法。可以是私有的。


6.访问成员方法:Method类中方法

注意:调用方法前,一定要先实例化一个此类对象
public Object invoke(Object obj,Object... args);object就是使用args实参后方法的返回值,★★
参数:obj : 方法所属的对象;args: 调用方法需要的实参

同样暴力访问
public void setAccessible(boolean flag)
method对象.setAccessible(true)


四.反射的重要应用:

1.通过反射运行配置文件的内容

public class Demo {
	public static void main(String[] args) throws Exception{
		
		/*
		//获取一个class类对象,使用三种方法中那一中,class类中静态方法
		Class aclass =Class.forName("cn.itcast.Test反射读取配置文件.line2");
		//由获得Class对象再得到一个该类的一个对象
		Object obj=aclass.getConstructor().newInstance();
		//得到的该类的一个成员方法
		Method m=aclass.getMethod("move", int.class);
		m.invoke(obj, 20);
		*/
		
		//定义一个class类对象
		Class aclass=Class.forName(getvalue("classname"));
		//通过反射,并调用构造方法,创建一个该类的对象
		Object obj=aclass.getConstructor(String.class).newInstance("好方法");
		//通过反射调用其成员方法
		aclass.getMethod(getvalue("methodname"), int.class).invoke(obj, 50);		
	}
	//通过定义一个方法来获取几种东西
	public static String getvalue(String str)throws IOException{
		//定义一个properties集合map集合,以便于读写文件
		Properties pro=new Properties();
		//定义一个输入流
		FileReader filein=new FileReader(new File("pro.properties"));
		//读取文件中内容到集合中
		pro.load(filein);
		//关闭资源
		filein.close();		
		return pro.getProperty(str);
	}
}
public class line2 {
	private String name;
	public line2(String name){
		this.name =name;
	}	
	public void move(int n){
		System.out.println("名字是:"+name+"小伙的年龄是"+n);
	}
}

2.通过泛型越过泛型检查

泛型只是在编译期间有作用,可以通过泛型直接加载其class,往声明泛型的集合添加东西

public class Demo {
	public static void main(String[] args) throws Exception  {
		//定义一个集合
		ArrayList list=new ArrayList<>();
		list.add(10);
		//使用反射创建此类的class对象,调用其方法
		Class aclass=ArrayList.class;//list.getclass();
		//获取其方法的对象,使用的参数为object.class,通用的类型
		Method m=aclass.getMethod("add", Object.class);
		m.invoke(list, "nihaoma ");
		System.out.println(list);
	}
}

3.通过泛型设置某个对象的某个属性为指定值

public class Demo {
	public static void main(String[] args) throws Exception {
		Cat c=new Cat();
		setValue(c,"name","bosimao");
		setValue(c, "age", 2);
		System.out.println("年龄:"+c.getAge()+"名字:"+c.getName());
	}


	private static void setValue(Object obj, String fieldName, Object value) throws Exception {
		Class aclass=obj.getClass();
		Field field=aclass.getDeclaredField(fieldName);
		//由于是私有属性,需要暴力访问
		field.setAccessible(true);
		field.set( obj, value);
	}	
}
public class Cat {
	private String name;
	private int age=0;
	
	public String getName(){
		return this.name;
	}
	public int getAge(){
		return this.age;
	}
}

五.动态代理

1.动态代理的概述

本来应该自己做的事情,却请了别人来做,被请的人就是代理对象。

在程序运行过程中产生的这个对象,而程序运行过程中产生对象其实就是我们刚才反射讲解的内容,所以,动态代理其实就是通过反射来生成一个代理

2.代理模式:

1).Student类中有一个coding()方法,写程序;
2).测试类中如果需要coding()方法,需要直接实例化一个Student,并调用方法;
如果想在coding()方法的前面或者后面添加一些其他功能,可以不用修改Student类,而为Student类添加
一个"代理类",代理类会调用Student类中的方法。
3).增加了代理类后,测试类不需要直接面对Student,转而使用代理类。代理类中为coding方法添加了新的功能。
实际上就是不直接使用基础类,而是使用第三方代理,并且代理可以添加一些基本功能
public class Demo {//测试类
	public static void main(String[] args) {
		/*
		Student stu = new Student();
		stu.coding();
		*/
		//使用代理模式
		StudentProxy proxy = new StudentProxy();
		proxy.coding();
	}
}




public class StudentProxy {//代理类
	public void coding(){
		check();
		new Student().coding();
		zj();
	}
	//先期检查
	public void check(){
		System.out.println("先期检查......");
	}
	//后期总结
	public void zj(){
		System.out.println("后期总结.......");
	}
}




public class Student {//基础类
	public void coding(){
		System.out.println("做项目,写程序......");
	}
}

3.动态代理的步骤;

1).自定义一个类,实现InvocationHander接口,并重写接口中的invoke()方法
2).在需要使用代理类的时候,使用Proxy类中的newProxyInstance()方法获取代理类的对象
3).JDK只能为接口做动态代理,所以要为所要代理的类定义一个接口,并去实现接口
public class Demo {//测试类
	public static void main(String[] args) throws Exception {
		//多态的子类对象,使用proxy类中的方法newproxyInstace
		
		//Idao idaostu=(Idao) Proxy.newProxyInstance(Class.forName("cn.itcast.Test动态代理.Student").getClassLoader(), 
		//									Student.class.getInterfaces(),new MyInvocationhander(new Student()));
		
		Idao idaostu=(Idao) Proxy.newProxyInstance(Student.class.getClassLoader(), 
													Student.class.getInterfaces(),
													new MyInvocationhander(new Student()));
		idaostu.write();//需要使用的方法
	}
}




public class MyInvocationhander implements InvocationHandler {//实现接口,代理类
	
	private Object obj;//定义需要代理的对象
	public MyInvocationhander(Object obj) {
		this.obj=obj;
	}


	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		Object objmethod=method.invoke(obj);
		show();
		return objmethod;
	}


	private void show() {
		System.out.println("真是牛叉啊!!");
	}
}


public interface Idao {//自己定义接口
	public void write();
}


public class Student implements Idao {//自定义类,需要代理的类
		
	@Override
	public void write() {
		System.out.println("你好,动态代理!!");
	}
}



六.枚举

1.枚举概述

一种模式"多例模式":在整个应用程序运行期间,有些类的实例,只允许有固定的几个,这种模式就是"多例模式"
  例如:骰子:需要2个实例;
          扑克:54个实例;        
    现在说的枚举,就是基于"多例模式":  
例如:我们的程序运行期间,需要三个颜色(红\绿\蓝),所以我们使用一个类MyColor来表示颜色。因为我们只需要三个颜色,所以这个类的对象,只能有三个;

public abstract class MyColor3 {//抽象类也是可以定义多例模式
	public static final MyColor3 RED  = new MyColor3("红"){
		@Override
		void show() {
			System.out.println("我是红色的!");
		}};
	public static final MyColor3 GREEN = new MyColor3("绿"){
		@Override
		void show() {
			System.out.println("我是绿色的!");
		}};
	public static final MyColor3 BLUE = new MyColor3("蓝"){
		@Override
		void show() {
			System.out.println("我是蓝色的!");
		}};
	private String colorName;
	
	private MyColor3(String colorName){
		this.colorName = colorName;
	}
	
	public String toString(){
		return this.colorName;
	}
	
	abstract void show();
}

2.制作枚举步骤:

1).定义枚举:public enum Xxxxx
2).直接定义枚举项。注意:可以有其它成员,但枚举项必须在第一行;
3).枚举中可以包含抽象方法;
4).任何的枚举类,都继承自:Enum类;
public enum MyColor3 {
	RED("红") {
		@Override
		void show() {
			System.out.println("我是红色的!");
		}
	},GREEN("绿") {
		@Override
		void show() {
			System.out.println("我是绿色的!");
		}
	},BLUE("蓝") {
		@Override
		void show() {
			System.out.println("我是蓝色的!");
		}
	};
	private String colorName;
	private MyColor3(String n){
		this.colorName = n;
	}
	
	public String getColorName(){
		return this.colorName;
	}
	
	abstract void show();
}

3.枚举类中常用方法,使用枚举对象调用

int compareTo(E o)//比较的是枚举项的索引值,做减法
String name()//枚举项的名字fieldname
int ordinal()//枚举项的索引值
String toString()//枚举对象所对对应的fieldname
T valueOf(Class type,String name)//将一个字符串转换为type类型的对象
values() 
此方法虽然在JDK文档中查找不到,但每个枚举类都具有该方法,它遍历枚举类的所有枚举值非常方便

public class Demo {
	public static void main(String[] args) {
		MyColor3 c1 = MyColor3.RED;
		MyColor3 c2 = MyColor3.BLUE;
		
		System.out.println(c2.compareTo(c1));//索引值的减法
		System.out.println("name = " + c2.name());//BLUE
		//int ordinal()
		System.out.println("c2.ordinal() : " + c2.ordinal());
		System.out.println("c1.ordinal() : " + c1.ordinal());
		//String toString()
		System.out.println("c2.toString():" + c2.toString());
		// T valueOf(Class type,String name)
		MyColor3 c3 = c1.valueOf("RED");//将一个字符串转换为MyColor3对象
		MyColor3 c4 = MyColor3.valueOf(MyColor3.class,"BLUE");
		
		System.out.println(c3);
		System.out.println(c4);
		
		//values() 
		MyColor3[] result = c1.values();
		System.out.println("循环遍历:");
		for(MyColor3 c : result){
			System.out.println(c);
		}
	}
}



你可能感兴趣的:(java学习)