JAVA潜心修炼五天——第5天
反射
类加载
将class文件读取到内存中的过程,称为类加载
class文件的加载由ClassLoader完成,称为类加载器,类加载器有三种类型:
-
BootStrapClassLoader:启动类加载器,用C++编写的,由JVM在启动时加载初始化的,主要负责加载%JAVA_HOME%/jre/lib路径下的类
-
ExtClassLoader:扩展类加载器,由BootStrapClassLoader加载,主要负责加载%JAVA_HOME%/jre/lib/ext路径下的类
-
AppClassLoader:系统类加载器,由BootSreapClassLoader加载,主要负责加载classpath路径下的类(对Eclipse就是项目路径/bin目录)
可以使用 类名
.class.getClassLoader()
获取当前类的类加载器
class文件读取到内存中会被封装成java.lang.Class
类的对象
反射
在程序运行状态中:
- 对于任意一个类,都能够获取这个类的所有属性和方法
- 对于任意一个对象,都能够调用这个对象的任意一个属性和方法
这种在运行时动态获取信息以及动态调用对象方法的功能称为反射reflection
相关API
反射相关的API都在java.lang包中
类/接口名 | 作用 |
---|---|
Class | 表示运行中的类和接口 |
Field | 表示类中的属性 |
Method | 表示类中的方法 |
Constructor | 表示类中的构造方法 |
Package | 表示类所属的包 |
Modifier | 表示修饰符 |
Parameter | 表示方法的参数 |
Annotation | 表示注解 |
Class对象
运行中的class文件通过Class对象来表示的
- Class对象是在类加载时由JVM自动创建的,一个类在JVM中只会有一个Class对象
- Class类没有公共的构造方法,不能自己创建Class对象,但可以获取其实例并进行操作
Class是反射的核心类,要想操作类中和属性和方法,都必须从获取Class对象开始
获取Class对象(三种)
- 调用对象的getClass()方法
- 调用类的class属性
- 调用Class类的forName()静态方法
常用方法
Class cls= Student.class;
//获取类中的信息
System.out.println("类全名:"+cls.getName());
System.out.println("简单类名:"+cls.getSimpleName());
System.out.println("是否为接口:"+cls.isInterface());
//获取父类
Class superClass=cls.getSuperclass();
System.out.println("父类:"+superClass.getSimpleName());
//获取实现的接口
Class[] interfaces=cls.getInterfaces();
System.out.println("实现的接口:");
for(Class c:interfaces){
System.out.println(c.getSimpleName()+"\t");
}
System.out.println();
//获取修饰符
int modifiers=cls.getModifiers();
System.out.println("修饰符: "+ Modifier.toString(modifiers));
//调用无参构造方法创建对象
try{
Object obj=cls.newInstance();
System.out.println(obj);
}catch (InterruptedException e){
System.out.println("实例化异常:"+e.getMessage());
}catch (IllegalAccessException e){
System.out.println("非法访问异常: "+e.getMessage());
}
反射操作
操作Field
Class cls=Student.class;
//1.获取当前类及父类中所有public修饰的属性
Field[] fields=cls.getFields();
//2.获取当前类中所有的属性,包含private修饰的
fields=cls.getDeclaredFields();
for (Field f:fields){
//属性名,属性类型,修饰符
System.out.println(f.getName()+"\t"+f.getType()+"\t"+Modifier.toString(f.getModifiers()));
}
//3.获取当前类及父类中指定的public修饰的属性
Field field=cls.getField("sex");
//4.获取当前类中指定的属性
field=cls.getDeclaredField("height");
System.out.println(field);
//通过反射为属性赋值
Student stu=new Student();
//取消权限控制检查,访问属性时忽略访问控制符(破坏了封装原则)
field.setAccessible(true);
//为stu对象的field熟悉赋值为180.5
field.set(stu, 180.5);
//通过反射获取属性值
//获取stu对象的指定field属性
Object value=field.get(stu);
System.out.println(value);
操作Method
Class cls=Student.class;
//1.获取当前类及父类中所有额的public修饰的方法
Method[] methods=cls.getMethods();
//2.获取当前类中所有的方法,包含private修饰的
methods=cls.getDeclaredMethods();
for(Method m:methods){
//方法名,返回值类型,修饰符,参数列表
System.out.println(m.getName()+"\t"+m.getReturnType()+"\t"+Modifier.toString(m.getModifiers())+"\t"+ Arrays.toString(m.getParameters()));
}
//3.获取当前类及父类中指定的public修饰的方法
Method method=cls.getMethod("c", int.class,int.class);
//4.获取当前类中指定的方法
method =cls.getDeclaredMethod("c", int.class,int.class);
System.out.println(method);
//通过反射调用方法
Student stu=new Student();
method.setAccessible(true);
//调用stu对象的method方法,传入参数4和3.5
Object value=method.invoke(stu, 4, 3.5);
System.out.println(value);
操作Constructor
Class cls=Student.class;
//1.获取所有public修饰的构造方法
Constructor[] constructors=cls.getConstructors();
//2.获取所有的构造方法
constructors=cls.getDeclaredConstructors();
for(Constructor c:constructors){
System.out.println(c);
}
//3.获取指定的public修饰的构造方法
Constructor c=cls.getConstructor(String.class,String.class,double.class);
//4.获取指定的构造方法
c=cls.getDeclaredConstructor(String.class);
System.out.println(c);
//通过反射调用构造方法,创建对象
c.setAccessible(true);
Object obj=c.newInstance("male");
System.out.println(obj);
//直接调用Class对象的newInstance()方法创建对象
Object obj2=cls.newInstance();
System.out.println(obj2);
注解
Annotation注解是一种新的类型,与接口很相似,它与类,接口,枚举是在同一个层次
注解相当于是一种标记,以 @
开头,一般写作 @xxx
,可以声明在类,属性,方法,参数等的前面
用来对这些元素进行说明,标注或实现特定功能
JDK自带注解
在java.lang包中
注解 | 作用 |
---|---|
@Override | 标记在方法前面,表示该方法是重写父类的方法 |
@Deprecated | 标记在类,属性,方法等前面,表示该内容已过时,不建议使用 |
@SuppressWarning | 标记在类,属性,方法等前面,用来关闭某些警告信息 |
自定义注解
定义注解:使用 @interface
关键字+元注解(标注在注解上的注解,称为元注解)
语法:
//元注解
public @interface 注解名{//抽象方法}
元注解 | 作用 |
---|---|
@Target | 定义注解的作用域,即可以被标记的哪些元素上,通过枚举类型ElementType来取值,省略时表示可以声明在任何元素前 |
@Retention | 定义注解的生命周期,通过枚举类型RetentionPolicy来取值 |
@Documented | 指定当使用javadoc生成API文档时包含该注解信息 |
@Inheried | 指定允许子类继承父类中的注解 |
使用注解
语法 @注解名(属性名=属性值,属性名=属性值...)
- 在使用注解时需要为注解中的抽象方法指定返回值,也称为:为注解指定属性值
- 属性名就是注解中抽象方法的名称
注解解析
对于生命周期为运行期间(RetentionPolicy.RUNTIME)的注解,可以通过反射获取元素上的注解,实现特定的功能
注解的应用
通过注解加载属性文件,为类中属性赋值
public class User {
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Value{
String value();
}
@Value("${user.username}")
private String username;
@Value("${user.pwd}")
private String password;
@Value("${user.email}")
private String email;
private String address;
public String getUsername(){
return username;
}
public User(){
init();
}
//初始化
private void init(){
//加载属性文件
Properties p=new Properties();
try{
p.load(User.class.getClassLoader().getResourceAsStream("info.properties"));
}catch (IOException e){
e.printStackTrace();
}
//获取类中所有属性上的@value注解,并解析处理
//获取所有属性
Class<? extends User> cls=this.getClass();
Field[] fields=cls.getDeclaredFields();
//循环获取每个属性上的@Value注解
for(Field field:fields){
Value annotation=field.getAnnotation(Value.class);
//判断是否有该注解
if(annotation!=null){
//获取注解上的值,并对值进行判断处理
String value=annotation.value();
if(value.startsWith("#{")&&value.endsWith("}")){
String key=value.substring(2,value.length()-1);
value=p.getProperty(key);
}
//为类中的属性赋值
try{
field.setAccessible(true);
field.set(this,value);
}catch (IllegalArgumentException e){
e.printStackTrace();
}catch (IllegalAccessException e){
e.printStackTrace();
}
}
}
}
}
info.properties
user.username=admin
user.pwd=4566
user,email=tom@sina.com
设计模式
设计模式(Design Pattern)是一套被人反复使用,多数人知晓的,经过分类的,代码设计经验的总结。
使用设计模式的目的:为了代码可重用性,让代码更容易被他人理解,保证代码可靠性。
设计原则
面向对象的基本原则:
- 高内聚——模块内部要高度内聚,每个类完成特定的独立的功能
- 低耦合——模块之间要降低耦合度,类之间的依赖应该要尽量低
设计模式的六大原则
-
单一职责原则
一个类只负责一个职责
-
开闭原则
对扩展开放,对修改关闭
在程序需要进行扩展的时候,不能去修改原有的代码,实现一个热拔插的效果(需要使用接口和抽象类)
-
里氏替换原则
所有引用基类(父类)的地方必须能透明的使用其子类的对象。
任何基类出现的地方,都可以换成子类,程序还可以正常执行
-
依赖倒置原则
细节应当依赖于抽象,抽象不应该依赖于细节,也就是“面向接口编程”或“面向抽象类编程”
-
接口隔离原则
使用多个隔离的接口,而不使用单一的总接口
每个接口应该承担一个相对独立的角色,不干不该干的事
-
迪米特法则,也称为最少知道原则
一个类对自己依赖的类知道的越少越好
23种设计模式
分为三大类:
- 创建型模式:工厂模式,抽象工厂模式,单例模式,建造者模式,原型模式
- 结构型模式:配适器模式,装饰器模式,代理模式,外观模式,桥接模式,组合模式,享元模式
- 行为型模式:策略模式,模板模式,观察者模式,迭代子模式,责任链模式,命令模式,备忘录模式,状态模式,访问者模式,中介者模式,解释器模式
单例模式
Singleton单例模式 确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
实现模式(两种)
饿汉式:在类加载时创建实例,不管后期是否会使用都会创建(线程安全)
懒汉式:在需要时才创建实例,延迟加载(线程不安全)
模板模式
Template定义一个抽象类,将部分逻辑以具体方法的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。
不同的子类可以不同的方式来实现这些抽象方法,从而实现对剩余的逻辑有不同的实现。
先制定一个顶级逻辑框架,而将逻辑的细节留给具体的子类来实现。
实现方式:
/**
* 排序工具类,抽象类
*/
public abstract class SortUtil<T> {
//将部分逻辑以具体的形式实现
public void sort(T[] arr){
//先制定一个顶级逻辑框架
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr.length-i-1; j++) {
//将逻辑的细节留给具体的子类来实现
int m=compareTo(arr[j],arr[j+1]);
if(m>0){
T temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
}
//声明一些抽象方法来迫使子类实现剩余的逻辑
public abstract int compareTo(T o1,T o2);
}
修改后的模板模式
/**
* 排序工具类
*/
public class SortUtil{
//将部分逻辑以具体的形式实现
public static <T> void sort(T[] arr,Comparable<T> t){
//先制定一个顶级逻辑框架
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr.length-i-1; j++) {
//将逻辑的细节留给具体的子类来实现
int m=compareTo(arr[j],arr[j+1]);
if(m>0){
T temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
}
}
public interface Comparator<T>{
public abstract int compareTo(T o1,T o2);
}
工厂模式
Factory定义一个工厂类,对实现同一个接口的一组类进行实例化对象的操作
实现方式
/**
* 工厂类
*/
public class MotoVehicleFactory {
public MotoVehicle create(String type){
if("car".equals(type)){
return new Car();
}else if("bus".equals(type)){
return new Bus();
}else if("truck".equals(type)){
return new Truck();
}
return null;
}
}
修改后的工厂模式
/**
* 创建对象的工厂类
*
* 根据类路径下的objs.properties属性文件中的配置来创建对象
* 要求:
* 1.文件中不能有空行
* 2.注意编写顺序,由于对象间可能存在依赖关系,被依赖的对象必须放在依赖对象的前面
*/
public class ObjectFactory {
//定义一个Map集合,用来存储对象
private static Map<String,Object> objs=new HashMap<>();
//类加载时读取属性文件,创建相应对象,对象是单调
static {
try(BufferedReader reader=new BufferedReader(new InputStreamReader(ObjectFactory.class.getClassLoader().getResourceAsStream("objs.properties")));){
String line=null;
while ((line=reader.readLine())!=null){
String[] entry=line.split("=");
objs.put(entry[0],Class.forName(entry[1]).newInstance());
}
} catch (Exception e){
throw new ExceptionInInitializerError("ObjectFactory初始化失败:"+e);
}
}
//工厂方法,用来获取对象
public static Object getObject(String key){
return objs.get(key);
}
}
代理模式
Proxy为其他对象提供一种代理,以控制对这个对象的访问,起到中介的作用。
被代理的对象称作目标对象,代替目标对象的对象称为代理对象,通过代理对象访问目标对象
作用:可以扩展目标对象的功能呢,增强额外的操作,同时不侵入原代码
代理的三要素:
-
共同接口(被代理接口)
-
目标对象(target)
-
代理对象(proxy)
实现方式(两种)
静态代理
代理类有程序员创建或工具生成
所谓静态就是在程序运行前就已经存在代理类的class文件,代理类和委托类的关系在运行前就确定了
**缺点:**代理对象需要和目标对象实现相同的接口,所以会有很多代理类,如果接口增加方法,目标对象和代理对象都要维护
public class UserServiceProxy implements UserService { //private UserService userService=new UserServiceImpl(); /* 代理对象可以只声明目标对象的接口,不负责目标对象的创建,目标对象由外部创建并传入 */ private UserService userService=(UserService) ObjectFactory.getObject("userService"); @Override public void login(String username,String password){ System.out.println("login start: "+new Date().getTime()); userService.login(username,password); } @Override public String logout(){ System.out.println("logout start:"+new Date().getTime()); return userService.logout(); } }
动态代理
代理类是程序运行期间有JVM根据反射机制动态生成的,自动生成代理类和代理对象
所谓动态是指在程序运行前不存在代理类的class文件,代理类和委托类的关系是在程序运行时确定
动态代理的实现:
-
jdk技术:只适用于实现了接口的类,使用
java.lang.reflect.Proxy
-
cglib技术:可用于没有实现任何借口的类,需要使用第三方jar包(通过继承实现的,让代理类继承目标类)
(PS:如果一个类没有实现任何接口,并且被final修饰,那么这个类无法创建代理对象)
UserService userService=(UserService)Proxy.newProxyInstance( //目标类的类加载器 UserServiceImpl.class.getClassLoader(), //目标类的接口列表 new Class[]{UserService.class}, //代理要执行的操作 new InvocationHandler(){ //参数的含义:生成的代理对象,目标方法,方法参数 @Override public Object invoke(Object proxyMethod method,Object[] args) throws Throwable{ System.out.println(method.getName()+"start:"+new Date().getTime()+",参数,"+Arrays.toString(args)); Object returnValue = method.invoke(new UserServiceImpl(),args); return returnValue; } }); userService.login("admin","123"); System.out.println("---------------------------------"); System.out.println(userService.logout()); //代理类的类型 class com.sun.proxy.$Proxy0 Syetem.out.println(userService.getClass());
-