单例设计模式
饿汉式实现方式
package cn.tedu.single;
/*本类用于测试单例设计模式1 饿汉式*/
public class Singleton1 {
public static void main(String[] args) {
MySingle single1=MySingle.getSingle();
MySingle single2=MySingle.getSingle();
System.out.println(single1==single2);//比较的是地址值,说明是同一个对象
}
}
class MySingle{
/*1.构造方法私有化的目的:为了不让外界随意实例化\new本类对象*/
private MySingle(){ }
/*3.构造方法和对象私有化后,通过公共的访问点来获取对象,那外界如何调用这个方法呢?
* 之前我们都是在外部创建本类对象并进行方法调用,但是现在单例程序中外部无法直接创建本类对象
* 解决方案;我们可以利用之前学习的静态的概念,将方法修饰成静态的,就可以通过类名直接调用啦
* 注意事项:静态只能调用静态,所以静态方法中返回的对象也需用静态修饰*/
private static MySingle single=new MySingle();
/*2.也就是以公共的方式向外界提供获取本类私有对象的方法*/
public static MySingle getSingle(){
return single;
}
}
懒汉式实现方式
public class Singleleton2 {
public static void main(String[] args) {
MySingle2 s1=MySingle2.getMySingle2();
MySingle2 s2=MySingle2.getMySingle2();
System.out.println(s1==s2);
System.out.println(s1);
System.out.println(s2);
}
}
class MySingle2{
private MySingle2(){}
private static MySingle2 single2;
static Object o=new Object();
/*问题:程序中有共享资源single2,并且有多条语句操作了共享数据
* 此时single2共享资源在多线程环境下一定会存在多线程数据安全隐患
* 解决方案1:同步代码块(加锁)
* 解决方案2:同步方法[如果方法中的所有代码都需要被同步,那么这个方法可以修饰成同步方法]
* 注意事项:锁对象在静态方法中,不可以使用this,因为静态资源优先于对象加载
* 锁对象可以使用外部创建好的唯一的锁对象o,但请注意,需要是静态的,静态只能调用静态*/
synchronized public static MySingle2 getMySingle2(){
/*注意:这需要增加一个判断
* 如果调用方法时single2的值为null,说明之间没有new过,保存的是默认值
* 这时才需要new对象,如果single2的值不为null,直接return single2即可*/
synchronized (o){
if(single2==null){
single2=new MySingle2();
}
return single2;
}
}
}
关于单例模式的两种实现方式
1.饿汉式 : 不管你用不用这个类的对象,都会直接先创建一个
2.懒汉式 : 先不给你创建这个类的对象,等你需要的时候再帮你创建 利用了延迟加载的思想
延迟加载的思想:是指不会再第一时间就把对象创建好来占用内存,而是什么时候用到,什么时候再去创建对象
3.线程安全问题 : 时候共享资源有线程并发的数据安全隐患,可以通过加锁的方式[同步代码块/同步方法]
反射
获取字节码对象
- Class.forName(“包名.类名”);
- 类名.class
- 对象.getClass();
单元测试方法:
是java测试的最小范围,使用灵活,非常推荐
语法要求: @Test + void + 没有参数 + public
注意:使用时需要导包
测试反射
先准备物料类,然后测试反射
单元测试1:用来测试获取指定类的字节码对象
@Test
public void getClazz() throws ClassNotFoundException {
Class<?> student1=Class.forName("cn.tedu.reflection.Student");
Class<?> student2=Student.class;
Class<?> student3=new Student().getClass();
System.out.println(student1);//打印通过反射获取到的字节码对象
System.out.println(student2.getName());//获取全路径名
System.out.println(student3.getSimpleName());//获取类名
System.out.println(student3.getPackage().getName());//获取包名
}
单元测试2:获取构造方法
@Test
public void getConstruct(){
Class<?> clazz=Student.class;
Constructor<?>[] cs=clazz.getConstructors();
for(Constructor c:cs){
System.out.println(c.getName());
Class[] cp=c.getParameterTypes();
System.out.println(Arrays.toString(cp));
}
}
单元测试3:获取普通方法
@Test
public void getFunction() throws ClassNotFoundException {
//获取字节码对象
Class<?> zj = Class.forName("cn.tedu.reflection.Student");
//获取指定类中的所有普通方法
Method[] ms=zj.getMethods();
//遍历存放所有方法的数组
for(Method m:ms){
//通过当前遍历到的方法对象获取当前方法的名字
System.out.println(m.getName());
//通过当前遍历到的方法对象获取当前方法的参数类型
Class<?>[] pt=m.getParameterTypes();
System.out.println(Arrays.toString(pt));
}
}
单元测试办法4:获取成员变量
@Test
public void getFields(){
/**注意: 目前所有成员变量的修饰符必须是public才能获取到
* 如果属性采用的是默认的修饰符,是反射不到的*/
/*Class<?>中的"?"是泛型约束的通配符,类似于"*"*/
Class<?> clazz=Student.class;
Field[] fs=clazz.getFields();
for(Field f:fs){
System.out.println(f.getName());
System.out.println(f.getType().getName());
}
}
单元测试5:通过单元测试来反射创建对象
* 方式一:通过字节码对象直接调用newInstance(),触发无参构造来创建对象
* 方式二:先获取指定的构造函数,再通过构造函数对象调用newInstance,触发时对应类型的构造函数来创建对象
@Test
public void makeObject() throws Exception {
Class<?> clazz=Student.class;//触发的是无参构造
Object obj=clazz.newInstance();
System.out.println(obj);
/**注意参数类型的指定,是对应类型的字节码对象,不能直接光写类型*/
Constructor<?> c=clazz.getConstructor(String.class,int.class);
Object obj2=c.newInstance("小灰灰",5);/**此处是多态的向上造型*/
System.out.println(obj2);
/**查看对象具体的属性值,或者调用方法,需要把Object强转成指定的子类对象
* 为什么要把Object转成子类类型呢?因为想要使用子类的属性或者是特有功能
* 父类是无法使用子类的特有属性和功能的,上面的obj2就是Object类型
* Object类中没有Student中的属性和特有功能
* 这种把之前看做是父类类型的子对象,重新转回子类对象的现象,叫做向下造型
* */
}