Java基础笔记9——反射、Junit、注解、单例模式

Java基础笔记9
十五、反射
通过获取指定类的Class信息,剖析该类具有的属性、方法、构造方法等等信息。这个过程就是反射的过程。
剖析类——得到类内部信息来实现特定功能。
能够实现解耦操作。
Class — 代表字节码的类 — 代表类的类——反射的核心
Field — 代表属性的类
Constructor — 代表构造方法的类
Method — 代表方法的类
Package — 代表包的类
Annotation代表注解的类
Class类
如何去获取一个Class对象?
①类名.class——当只有类的时用这个方法
Class clz1 = Person.class;
②类的对象.getClass()——当具有类的对象时用这个方法
Person p = new Person();
Class clz2 = p.getClass();
③Class.forName(“类的全路径名称”)——当既没有类也没有对象,只知道类的全路径名称的时候调用这个方法。
Class clz3 = Class.forName(“day24.reflect.Person”);
这个方法将会到内存中检查是否存在该类的字节码。如果存在,则直接返回代表该类的字节码的Class对象;如果不存在,则先加载类到内存,返回该类的字节码的Class对象。
所以,这个方法也是最简单的加载类的方法,当需要明确的加载某个类到内存时,可以调用这个方法。
由于一个类的字节码在内存中只有一份,而在虚拟机中会有一个Class对象代表这段字节码,所以在一个程序中,对于同一个类获取多次Class得到的将是同一个对象。
System.out.println(clz1 == clz2);
System.out.println(clz1 == clz3);
获取实例对象
1.new对象
2.字节码对象.newInstance() 注意:必须有无参构造才可以使用
3.如果想使用这个类中含参构造来创建对应的实例对象,那么需要先获取这个类中对应形式的构造函数,然后再调用newInstance(Object…)来创建实例对象
4.如果在获取这个类中的属性/方法/构造方法的时候,这些属性/方法是非公有的,那么在执行之前需要进行暴力破解—setAccessiable
Class中提供的方法:
T cast(Object obj) //进行强制类型转换的方法
Class<?>[] getInterfaces() //获取这个类实现过的所有的接口
String getName() //获取Class代表的字节码的类或接口的全路径名称
String getSimpleName() //获取Class代表的字节码的类或接口的简单名称(不带包名的类名)
Package getPackage() //获取Class代表的类或接口的包信息
Class<? super T> getSuperclass() //获取Class代表的类或接口的超类(父类)
boolean isInterface() //判断当前Class是否是一个接口
boolean isEnum() //判断当前Class是否是一个枚举类
T newInstance() //创建本类的一个对象 这个方法的调用有一个前提条件 这个类必须有共有的无参构造函数。
Method[] getMethods() //获取类中的所有方法
Method getMethod(String name, Class<?>… parameterTypes)
Constructor 方法:
String getName() //获取构造方法的名字
Class<?>[] getParameterTypes() //获取构造方法参数们的类型
T newInstance(Object… initargs)
构造方法信息:
Constructor<?>[] getConstructors()
Constructor getConstructor(Class<?>… parameterTypes)
Method方法
String getName() //获取方法名
Class<?>[] getParameterTypes() //获取方法的参数类型
Class<?> getReturnType() //获取方法的返回值类型
Object invoke(Object obj, Object… args) //在传入的对象上 应用传入的参数 执行当前方法
属性的信息
Field[] getFields() //获取所有属性
Field getField(String name) //获取指定属性
Field方法
String getName() //获取属性名称
Class<?> getType() //获取属性类型
Object get(Object obj) //传入一个对象 获取该对象上该属性的值
boolean getBoolean(Object obj)
byte getByte(Object obj)
char getChar(Object obj)
double getDouble(Object obj)
float getFloat(Object obj)
int getInt(Object obj)
long getLong(Object obj)
short getShort(Object obj) //传入一个对象 获取该对象上该属性的值
void set(Object obj, Object value) //传入一个对象 和 一个值 在该对象上将该值设置为 传入的值
setBoolean(Object obj, boolean z)
void setByte(Object obj, byte b)
void setChar(Object obj, char c)
void setDouble(Object obj, double d)
void setFloat(Object obj, float f)
void setInt(Object obj, int i)
void setLong(Object obj, long l)
void setShort(Object obj, short s) //传入一个对象 和 一个值 在该对象上将该值设置为 传入的值
操作私有和保护成员
Constructor<?>[] getDeclaredConstructors()
Constructor getDeclaredConstructor(Class<?>… parameterTypes)
Method[] getDeclaredMethods()
Method getDeclaredMethod(String name, Class<?>… parameterTypes)
Field[] getDeclaredFields()
Field getDeclaredField(String name)
以上方法可以帮我们获取到私有 保护 默认的成员 但是获取到后 如果访问权限不足 仍然无法使用
如果想要让没有访问权限的成员可以被访问 可以调用 setAccessible 设置为true 就可以强行访问 这种方式让我们反射可以访问类中的本来没有访问权限的成员 虽然方便 但是会破坏类的封装的特性 所以在使用时一定要谨慎 充分考虑是否可以承担这样的风险。
反射的用处
在正常的代码中是用不上反射技术的,更多的是在设计框架的过程中使用反射。

代码调试
1.junit测试框架
@Test //测试方法上标注的注解
@Before //标注的方法将在@Test方法执行前执行
@After //标注的方法将在@Test方法执行后执行
@BeforeClass //在测试类加载后立即执行 – 必须写在静态方法上
@AfterClass //在测试类销毁前立即执行-- 必须写在静态方法上
注意:@Test只能用在 公有的 非静态 无返回值 无参数的方法上

2.Debug调试模式
	打断点
	单步执行
	单步钻入
	单步跳出
	重新执行方法
	放行断点

进入debug模式,运行到非eclipse系统自带代码时需要导入源码。
十六、注解
顶级父类:Annotation
解释程序—给机器看的提示信息。
注释:给人看的提示信息
注解可以定义属性
用@interface来定义注解—jdk1.5出现的(声明接口是interface,没有@字符,要注意区分开来)
@Override重写(接口或父类方法)
@SuppressWarnings—压制警告
属性:数据类型 属性名() default 默认值
可以使用的数据类型:基本类型、String、Class、枚举、注解及以上类型对应的一维数组。
如果赋值的时候属性名为value并且只有一个属性,那么可以省略value不写。
元注解:修饰注解的注解,控制注解的行为特性。
@Target—限制注解的使用范围的— 如果注解没有指定范围,则表示任意一个范围都可以使用
@Retention — 控制注解被保留到什么时候
RetentionPolicy.SOURCE – 该注解被保留到源码阶段 在编译的过程中该注解被删除 – 这种注解 是给编译器看的 用来控制 编译器的行为的
RetentionPolicy.CLASS – 该注解在源码中 和 编译后的.class中都有保留 但是 在.class被加载为字节码的过程中 被删除 – 给类加载器看的 控制类加载器的行为
RetentionPolicy.RUNTIME – 该注解 在源码中 .class中 和 内存的字节码中 都可以找到 – 这种注解 在内存的字节码中存在 也就意味着 程序员 可以通过反射技术 得到注解的信息 来控制程序的运行 这也是 我们最常用的级别
@Documented — 表示当前这个注解能够生成到文档中
@Inherited — 控制注解是否具有继承性
注解中还可以声明属性
注解中声明属性的过程 非常类似于 在接口中定义方法 只不过 注解中声明的属性 必须是public的或者不写访问权限修饰符 默认就是public的。
注解中的属性 只能是 八种基本数据类型 Class类型 String类型 其他注解类型 及 这些类型的一维数组
注解中声明的属性 需要在使用注解时 为其赋值 赋值的方式 就是使用注解时 在注解后跟一对小括号 在其中 通过 属性名=属性值 的方式 指定属性的值
也可以在声明注解时 在注解的属性后 通过default关键字 声明属性的默认值 声明过默认值的属性 可以在使用注解时 不赋值 则默认采用默认值 也可以手动赋值 覆盖默认值
如果属性是 一维数组类型 而 在传入的数组中 只有一个值 则包括数组的大括号可以省略
如果注解的属性 只有一个需要赋值 且该属性的名称叫做value 则在使用注解时 value= 可以不写

反射注解:
	在Class Method Field Constructor..等类上 都有获取注解信息的方法
		Annotation[] getAnnotations()  //获取所有注解
		<A extends Annotation> A getAnnotation(Class<A> annotationClass)  //获取指定注解
		boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)  //是否在该元素上 标注过指定注解
	可以通过这些方法 反射得到 类上 方法 属性 构造器..等位置上标注过的注解 从而 通过 是否有注解 或 注解上属性的取值不同控制程序的运行过程。

	**只有@Retention 是 RetentionPolicy.RUNTIME级别的注解才可以被反射

**注解常用作 轻量级的配置 在后续学习的框架中 有非常广泛 而 灵活的运用

枚举
顶级父类:Enum
用在取值情况相对固定并且能够一一列举的情况下使用。
枚举常量必须定义在枚举类的第一行。
是一种特殊的类,无法直接创建对象,只能在枚举内部声明的固定个数的值。本质上是一个私有化构造方法的抽象类,在内部创建这个类被final的类供外界使用。
**枚举类可以包含属性
**枚举类也可以自己声明构造方法 必须是私有的
**枚举类也可以包含普通方法
**枚举类也可以包含抽象方法
ordinal() 返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零)。
从JDK1.5开始,switch case支持枚举。
泛型
JDK1.5特性之一。
就是一种不确定的类型。
参数化类型——对应接口ParameterizedType
早期的集合List list=new ArrayList();元素类型是Object,可以添加的元素类型多样,使用不方便,容易混淆。
**泛型的使用:可以两边没有 也可以一边有一边没有 也可以两边都有 但是一旦两边都有 必须一模一样 泛型中没有继承关系的概念!如下例:
List list=new ArrayList();//前后泛型类型不一致,编译报错
后来用泛型规范元素类型List list=new ArrayList(),E代表泛型,可以是String类、Integer包装类、可以是对象类。
在类名或接口名后添加<>,<>中定义泛型的名字。
泛型在命名的时候只要是遵循标识符命名原则就行。如。但是习惯上用一个大写字母表示,常用、、、、(分别表示:type、element、value、key、result)
泛型变量在定义的时候不允许初始化。
如果一个类要定义多个泛型,用,号隔开,如<K,V>,理论上不限制泛型的个数。
自定义泛型
方法上的泛型
方法上的泛型的作用范围是当前方法
方法上的泛型 需要先定义再使用
方法上的泛型通过尖括号 在方法的返回值前进行声明
泛型的名称可以随意的取 但是一般都取为一个大写字母 也可以在一个方法中定义多个泛型 只需要在尖括号中通过逗号分割依次声明就可以了
方法上的泛型 的 具体类型 是在方法被调用的时启动推测而知

类上的泛型
		类上定义的泛型的作用范围是当前类的内部
		类上定义的泛型 需要先定义再使用
		类上的泛型通过尖括号 在类名后进行声明
		泛型的名称可以随意的取 但是一般都取为一个大写字母 也可以在一个方法中定义多个泛型 只需要在尖括号中通过逗号分割依次声明就可以了
		类上定义的泛型 需要在创建类的对象或引用时 明确的指定泛型的具体类型		
		注意,类上定义的泛型,不能在静态方法中使用,如果非要在静态方法上使用泛型,可以在该方法上自己定义自己使用。

		**集合泛型其实一点都不特殊,本质上就是在集合类上定义的类上的泛型

	上边界
		泛型是一种不确定的类型 可以在使用时为其指定为任意类型 这种灵活的特性 使我们的程序更加的灵活 但是过于灵活可能也会带来问题 有时我们需要限定这种过于灵活的特点 此时可以使用上边界 在泛型定义时 通过extends关键字 限定 泛型 必须是指定类型 或其子孙类型
		而如果不指定泛型的上边界,泛型的上边界默认为Object	
==============
	下边界
		下边界通过super关键字来指定 
		限定泛型的取值为指定类型或其祖先类型
		但是super不是用来在泛型定义时使用的,而是用在泛型通配符中

	泛型通配符
		泛型的通配符 使用在 类上泛型指定具体类型时 如果无法确定 泛型的具体取值 可以使用?来替代
		而泛型通配符 可以通过 extends关键字声明?取值的上边界
==============

	泛型的原理 - 泛型擦除:
		.java -- 编译器 编译 --> .class --> jvm 运行	
	泛型只在编译阶段起作用 在编译的过程中 编译期 根据泛型的声明进行语法检查 在检查完成 真正将代码转换为.class文件的过程中 会将泛型相关的信息 进行擦除 所谓 的擦除 具体过程是 将所有的泛型替代为 其 上边界类型 并在必要的地方 加上必须的 类型强转 和 类型检查。所有 一个.java 编译为 .class之后 所有泛型的信息将会丢失 这个过程称之为 泛型的擦除。

1.给当前方法定义一个独有的泛型。
public void m(E e) {}
2.不确定返回值类型,用泛型表示。
public R m2() {
return null;
}
泛型不存在向上造型。
? extends Number 表示元素类型可以是Number及其子类——最大类型是Number,规定了泛型的上限。默认上边界为Object。
? extends 类/接口 表示传入这个类/接口的子类/子接口对象
? super String 表示元素类型可以是String及其父类——最小类型是String,规定了泛型的下限。
? super类/接口 表示传入这个类/接口的父类/父接口对象
super 不是用在泛型定义的时候使用,而是用在泛型通配符。
可以添加下限对应的类型的对象,不能添加上限对应的类型的对象。
不允许同时规定上限和下限。
?表示通配符
List <?> list 用于限定类型,除了null,不能add任何元素。
动态代理
利用Proxiy类产生对象的代理对象。
是一种设计模式。
代理分静态代理和动态代理。
改变已有方法的方式:
①继承——在对象还未创建之前就继承,直接创建的是子类才可以改变方法。如果已经有对象创建出来了,继承并重写对该对象无作用。
②装饰——可以改变已有对象身上的方法,但是编写的方式比较麻烦,特别是原来的类方法比较多的情况下,冗余的代码会比较多。
③动态代理——java中为了实现代理 专门提供了一个 java.lang.reflect.Proxy类 帮我们生成代理者
通过调用Proxy的 static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 就可以为原有对象生成对应的代理对象了
之后不允许直接使用被代理对象,所有的操作都找代理对象,而调用代理对象的任何方法都会走到InvocationHandler中的invoke方法中 可以在invoke方法中编写代码 处理代理的逻辑 对于不想改造的方法 调用原有对象身上的方法 对于想改造的方法 根据自己的意愿改造。

静态导入
import static 包名.类名.方法名 表示导入的静态方法。
好处:静态导入可以在一定程度上提高加载速率。
缺点:但是写起来麻烦而且降低程序的可读性。如果本类中含有同名方法,会导致这个静态导入无效。不建议使用。
单例设计模式
单例模式:在全局过程中只存在一个实例。
饿汉式的特点:无论是否需要这个对象,都会创建这个对象,这就会导致如果不需要这个对象时会使加载时间变长。在类加载时就已经创建好了。不存在并发安全问题。效率比较高,推荐此种方式。
懒汉式的特点:在对象需要的时候再创建,从而节省加载时间。但是会导致多线程的并发安全问题。需要用synchronized来同步。
单例模式共有7种实现:
第一种(懒汉,线程不安全):
Java代码  
1.public class Singleton {  
2.    private static Singleton instance;  
3.    private Singleton (){}  
4.  
5.    public static Singleton getInstance() {  
6.    if (instance == null) {  
7.        instance = new Singleton();  
8.    }  
9.    return instance;  
10.    }  
11.}  
  这种写法lazy loading很明显,但是致命的是在多线程不能正常工作。
第二种(懒汉,线程安全):
Java代码  
1.public class Singleton {  
2.    private static Singleton instance;  
3.    private Singleton (){}  
4.    public static synchronized Singleton getInstance() {  
5.    if (instance == null) {  
6.        instance = new Singleton();  
7.    }  
8.    return instance;  
9.    }  
10.}  
 
 这种写法能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading,但是,遗憾的是,效率很低,99%情况下不需要同步。
第三种(饿汉):
Java代码  
1.public class Singleton {  
2.    private static Singleton instance = new Singleton();  
3.    private Singleton (){}  
4.    public static Singleton getInstance() {  
5.    return instance;  
6.    }  
7.}  
 
 这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。
第四种(饿汉,变种):
Java代码  
1.public class Singleton {  
2.    private Singleton instance = null;  
3.    static {  
4.    instance = new Singleton();  
5.    }  
6.    private Singleton (){}  
7.    public static Singleton getInstance() {  
8.    return this.instance;  
9.    }  
10.}  
 
 表面上看起来差别挺大,其实更第三种方式差不多,都是在类初始化即实例化instance。
第五种(静态内部类):
Java代码  
1.public class Singleton {  
2.    private static class SingletonHolder {  
3.    private static final Singleton INSTANCE = new Singleton();  
4.    }  
5.    private Singleton (){}  
6.    public static final Singleton getInstance() {  
7.    return SingletonHolder.INSTANCE;  
8.    }  
9.}  
 
这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,它跟第三种和第四种方式不同的是(很细微的差别):第三种和第四种方式是只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy loading效果),而这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。想象一下,如果实例化instance很消耗资源,我想让他延迟加载,另外一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。这个时候,这种方式相比第三和第四种方式就显得很合理。
第六种(枚举):
Java代码  
1.public enum Singleton {  
2.    INSTANCE;  
3.    public void whateverMethod() {  
4.    }  
5.}  
 
 这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊,不过,个人认为由于1.5中才加入enum特性,用这种方式写不免让人感觉生疏,在实际工作中,我也很少看见有人这么写过。
第七种(双重校验锁):
Java代码  
1.public class Singleton {  
2.    private volatile static Singleton singleton;  
3.    private Singleton (){}  
4.    public static Singleton getSingleton() {  
5.    if (singleton == null) {  
6.        synchronized (Singleton.class) {  
7.        if (singleton == null) {  
8.            singleton = new Singleton();  
9.        }  
10.        }  
11.    }  
12.    return singleton;  
13.    }  
14.}

猜你喜欢

转载自blog.csdn.net/weixin_42394052/article/details/85385740