单例设计模式的意思是,一个类只允许创建一个实例,也就是一个对象,对象在堆内存中只能开辟一个空间。
根据实例化对象的时机单例设计模式又分为以下两种:
饿汉单例设计模式
饿汉单例设计模式就是使用类的时候已经将对象创建完毕,不管以后会不会使用到该实例化对象,先创建了再说。很着急的样子,故被称为“饿汉模式”。
懒汉单例设计模式
懒汉单例设计模式就是调用getInstance()方法时实例才被创建,先不急着实例化出对象,等要用的时候才实例化出对象。不着急,故称为“懒汉模式”。
饿汉式
比较饥饿,着急创建出来唯一的一个对象
/*
单例设计模式
比较饥饿,着急创建出来唯一的一个对象
*/
public class Single
{
//1、私有化构造函数
private Single(){}
//2、创建一个本类的对象
private static final Single s=new Single();
//3、定义一个方法返回本类的对象
public static Single getInstance()
{
return s;
}
//测试方法
public void test()
{
System.out.println("测试方法");
}
}
class SingleDemo
{
public static void main(String[] args)
{
/*
要想获取到Single类的对象,调用getInstance方法
既然不能通过对象来调用,那么只能通过类名来调用
如果要想通过类名来调用方法,那么被调用的方法必须用static来修饰
*/
Single s1=Single.getInstance();
s1.test();
//Single s2=Single.getInstance();
}
}
懒汉式
比较懒惰,什么时候用对象,就什么时候创建
public class Single {
//比较懒惰,什么时候用对象,就什么时候创建
//构造方法私有化
private Single(){
}
//创建一个唯一的对象
private static Single s = null;
//获取对象的方法
public static synchronized Single getSingle(){
//如果值是null,说明没有创建过这个对象
if(s == null){//t1 t2
s = new Single();
}
return s;
}
}
注意:懒汉单例设计模式在多线程环境下可能会实例化出多个对象,不能保证单例的状态,所以加上关键字:synchronized,保证其同步安全。
单例模式可以保证系统中一个类只有一个对象实例。
实现单例模式的步骤:
多例模式,是一种常用的软件设计模式。通过多例模式可以保证系统中,应用该模式的类有固定数量的实例。多例类要自我创建并管理自己的实例,还要向外界提供获取本类实例的方法。
作用
一个类可以创建多个对象,有多个实例。
实现步骤
1.创建一个类, 将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
2.在类中定义该类被创建的总数量
3.在类中定义存放类实例的list集合
4.在类中提供静态代码块,在静态代码块中创建类的实例
5.提供获取类实例的静态方法
代码演示
比如一个类只允许创建3个对象。
public class Person {
//这个类一共创建3个对象
//构造方法私有化
private Person(){}
//定义集合用于保存多个对象
private static ArrayList<Person> list = new ArrayList<>();
//静态代码块,只会执行一次,且是在这个类最开始最先执行
static{
//创建3个对象放在集合里
for (int i = 0; i < 3; i++) {
list.add(new Person());
}
}
//定义供外界访问的获取对象的方法
public static Person getPerson(){
//随机一个对象返回给调用者
//创建随机对象
Random r = new Random();
//获取索引
int i = r.nextInt(3);
//根据索引从集合中获取对象
Person person = list.get(i);
//返回给调用者
return person;
}
}
public class Demo {
public static void main(String[] args) {
//获取10次,但是获取到的其实就是3个对象
for (int i = 0; i < 10; i++) {
Person person = Person.getPerson();
System.out.println(person);
}
}
}
代理模式概念
为什么要有“代理”?生活中就有很多例子,例如委托业务等等,代理就是被代理者没有能力或者不愿意去完成某件事情,需要找个人代替自己去完成这件事,这才是“代理”存在的原因。例如,我现在需要出国,但是我不愿意自己去办签证、预定机票和酒店(觉得麻烦 ,那么就可以找旅行社去帮我办,这时候旅行社就是代理,而我自己就是被代理了。
在我们的代码中,假如有以下业务情景:
用户登录到我们的系统后,我们的系统会为其产生一个ArrayList集合对象,内部存储了一些用户信息,而后,这个对象需要被传给后面的很多其它对象,但要求其它对象不能对这个ArrayList对象执行添加、删除、修改操作,只能get()获取元素。那么为了防止后面的对象对集合对象进行添加、修改、删除操作,我们应该怎样办呢?
要想实现这种要求,方案有很多种。"代理模式"就是其中的一种,而且是非常合适的一种。
动态代理概念
动态代理简单来说是:拦截对真实对象(被代理对象)方法的直接访问,增强真实对象方法的功能
动态代理详细来说是:代理类在程序运行时创建的代理对象被称为动态代理,也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。也就是说你想获取哪个对象的代理,动态代理就会动态的为你生成这个对象的代理对象。动态代理可以对被代理对象的方法进行增强,可以在不修改方法源码的情况下,增强被代理对象方法的功能,在方法执行前后做任何你想做的事情。动态代理技术都是在框架中使用居多,例如:Struts1、Struts2、Spring和Hibernate等后期学的一些主流框架技术中都使用了动态代理技术。
jdk的动态代理使用前提
要使用动态代理,代理对象和被代理的对象所属类必须实现共同接口。
演示Java已经实现的代理模式的思想,ArrayList使用工具类的演示:
static <T> List<T> unmodifiableList(List<? extends T> list)
参数:list是传入的被代理对象集合
返回值:返回的是代理对象集合,不能对集合进行增删改,如果增删改将导致抛出
UnsupportedOperationException:不支持操作异常
说明:
unmodifiableList作用:传递List接口,方法内部对List接口进行代理,返回一个被代理后的List接口
对List进行代理之后,调用List接口的方法会被拦截:
如果使用的size,get方法,没有对集合进行修改,则允许执行
如果使用的add,remove,set方法,对集合进行了修改,则抛出运行时异常
代码演示:
public class Demo02 {
public static void main(String[] args) {
//创建集合
ArrayList<String> list = new ArrayList<>();
list.add("柳岩");
list.add("老王");
list.add("石原里美");
//让这个集合不能对内容进行修改,不能调用:1) 添add 2) 删remove 3) 改set的方法
List<String> list2 = Collections.unmodifiableList(list);
//list2此时是一个代理对象,他这个对象不允许调用增删改方法
//unmodifiableList这个方法返回的list2是一个代理对象,该方法内部使用的就是代理模式的思想,我们可以使用动态代理来模拟该方法内部原理
// list2.add("李四");//报异常
// list2.remove("柳岩");//报异常
String s = list2.get(1);
System.out.println(s);
}
}
说明:unmodifiableList这个方法返回的list2是一个代理对象,该方法内部使用的就是代理模式的思想,我们可以使用动态代理来模拟该方法内部原理
测试类代码
public class Demo03 {
public static void main(String[] args) {
//创建集合
ArrayList<String> list = new ArrayList<>();
list.add("柳岩");
list.add("老王");
list.add("石原里美");
//让这个集合不能对内容进行修改,不能调用:1) 添add 2) 删remove 3) 改set的方法
//List list2 = Collections.unmodifiableList(list);
//使用我们自己定义的类和方法以及动态代理技术模拟:让这个集合不能对内容进行修改,不能调用:1) 添add 2) 删remove 3) 改set的方法
List<String> list2 = MyColl.getList(list);
//list2此时是一个代理对象,他这个对象不允许调用增删改方法
//unmodifiableList这个方法返回的list2是一个代理对象,该方法内部使用的就是代理模式的思想,我们可以使用动态代理来模拟该方法内部原理
// list2.add("李四");//报异常
// list2.remove("柳岩");//报异常
//list2.set(2,"老王");
String s = list2.get(1);
System.out.println(s);
int size = list.size();
System.out.println(size);
}
}
MyColl类:动态代理实现对代理对象的增强
那么如何实现动态代理呢?
获取某个被代理类的代理类对象这时必须使用Java中的Proxy这个类完成。
注:在Java中当某个类需要被代理的时候,要求这个类中的被代理的方法必须抽取到一个接口中,然后这个类需要实现那个接口,只有在这个接口中的方法,代理类才能代理它。
在Proxy类中提供了一个方法,可以实现:
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) 返回一个指定接口的代理类实例或者对象。
那么我们如何确定这个类加载器呢?
说明:这个类加载器可以随便给,但是一般情况下我们给被代理的对象的类加载器即可。
举例: 创建被代理对象list的对象: ClassLoader loader = list.getClass().getClassLoader(); 有了这个加 载器了,这样就可以在方法区中给我们加载出来一个代理类的Class对象了。
注:Class[] getInterfaces() 通过字节码文件对象,获取其所有接口的数组。
代码如:Class[] interfaces = list.getClass().getInterfaces();
3) InvocationHandler h:调用处理器 ,是一个接口。
所以我们需要自己定义一个类来实现这个接口,或者我们也可以使用匿名内部类来实现。
代码如:
InvocationHandler h = new InvocationHandler()
{
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
}
};
说明:
A:invoke函数属于调用处理器InvocationHandler 接口中的函数,所以实现类必须复写这个函数;
参数:
1)proxy:就是代理对象本身。这里不用管;
2)method:当前代理对象正在调用的方法;
3)args :当前正在调用的方法的实际参数;
代码实现:
@SuppressWarnings("all")
public class MyColl {
/*
getList方法:
参数代表的是被代理对象
返回值代表是代理对象(中介)
*/
public static List<String> getList(List<String> list) {
/*
* 动态代理演示
* 动态代理:就是在程序运行的过程中,动态的生成一个类,这个类要代理目标业务对象,并且可以动态生成这个代理类的对象。
* 如何实现:
* Proxy类中提供了一个方法,可以实现:
* static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
* 返回一个指定接口的代理类实例
* 参数:
* loader:类加载器,一个类的字节码文件不可能直接就出现在方法区中,必须通过类加载器加载,所以需要一个类加载器。
* 一般给被代理的对象的类加载器
* 注意:这里要保证被代理对象和代理对象所属类的类加载器要一样。而这里都是list,索引类加载器都是根类加载器
* Class[] interfaces:被代理对象的所有接口的数组,说明把接口的字节码文件放在这里即可,因为接口的字节码文件中就存在函数。
* 通过获取到接口获取接口中的方法,这样后面才可以对方法进行增强
Class[] getInterfaces() 通过字节码文件对象,获取其所有接口的数组
* InvocationHandler h:调用处理器 ,是一个接口,所以我们需要自己定义一个类来实现这个接口。
* 返回值:返回的是生成的代理对象
*/
//获取类加载器
ClassLoader classLoader = list.getClass().getClassLoader();
//获取被代理对象的接口
Class<?>[] interfaces = list.getClass().getInterfaces();
//调用处理器 修改代理对象拦截被代理对象的方法
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
/*
invoke()方法:
在使用代理对象调用任何方法时,都会执行到invoke方法中,在invoke方法中给出处理方式,无论是add,remove,get都是这样
参数:
Object proxy 代理对象(在这里没用)
Method method 当前代理对象正在被调用的方法的Method对象
Object[] args 执行方法时传入的实际参数
返回值:
Object 实际执行完方法后得到的返回值.举例:String s = list2.get(1); 那么s接收的就是这个null
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/*
list2.add("李四") :
方法名:add
参数:李四
list2.set(2,"老王");
方法名:set
参数:[2, 老王]
*/
//输出被调用方法名
System.out.println(method.getName());
//输出被调用方法的参数
System.out.println(Arrays.toString(args));
//这个返回值会返回给调用方法的调用者
return null;
}
};
//动态代理
List<String> list2 = (List<String>) Proxy.newProxyInstance(classLoader, interfaces,invocationHandler );
//返回代理对象
return list2;
}
}
最终代码实现:不允许集合增删改
package com.itheima.sh.demo_04;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.List;
@SuppressWarnings("all")
public class MyColl {
/*
getList方法:
参数代表的是被代理对象
返回值代表是代理对象(中介)
*/
public static List<String> getList(List<String> list) {
/*
* 动态代理演示
* 动态代理:就是在程序运行的过程中,动态的生成一个类,这个类要代理目标业务对象,并且可以动态生成这个代理类的对象。
* 如何实现:
* Proxy类中提供了一个方法,可以实现:
* static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
* 返回一个指定接口的代理类实例
* 参数:
* loader:类加载器,一个类的字节码文件不可能直接就出现在方法区中,必须通过类加载器加载,所以需要一个类加载器。
* 一般给被代理的对象的类加载器
* 注意:这里要保证被代理对象和代理对象所属类的类加载器要一样。而这里都是list,索引类加载器都是根类加载器
* Class[] interfaces:被代理对象的所有接口的数组,说明把接口的字节码文件放在这里即可,因为接口的字节码文件中就存在函数。
* 通过获取到接口获取接口中的方法,这样后面才可以对方法进行增强
Class[] getInterfaces() 通过字节码文件对象,获取其所有接口的数组
* InvocationHandler h:调用处理器 ,是一个接口,所以我们需要自己定义一个类来实现这个接口。
* 返回值:返回的是生成的代理对象
*/
//获取类加载器
ClassLoader classLoader = list.getClass().getClassLoader();
//获取被代理对象的接口
Class<?>[] interfaces = list.getClass().getInterfaces();
//调用处理器 修改代理对象拦截被代理对象的方法
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
/*
invoke()方法:
在使用代理对象调用任何方法时,都会执行到invoke方法中,在invoke方法中给出处理方式,无论是add,remove,get都是这样
参数:
Object proxy 代理对象(在这里没用)
Method method 当前代理对象正在被调用的方法的Method对象
Object[] args 执行方法时传入的实际参数
返回值:
Object 实际执行完方法后得到的返回值.举例:String s = list2.get(1); 那么s接收的就是这个null
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/*
list2.add("李四") :
方法名:add
参数:李四
list2.set(2,"老王");
方法名:set
参数:[2, 老王]
*/
//输出被调用方法名
// System.out.println(method.getName());
//输出被调用方法的参数
// System.out.println(Arrays.toString(args));
////////////////////////////////////////////////////////////////////////////////////////
//目的是不让调用增删改的方法
//判断方法名如果是增删改就产生异常
if(method.getName().equals("add") || method.getName().equals("remove") || method.getName().equals("set")){
//产生异常
throw new RuntimeException("不能调用" + method.getName() + "方法");
}
//判断方法名如果是其他方法就正常执行
//method是一个方法
//执行方法
/*
invoke(Object o,Object... obj)
o 代表的是执行的哪个对象
obj 代表的方法的实际参数
*/
//list表示被代理对象,args表示被执行方法的实参 result 表示执行的方法返回的结果
//例如:调用的方法:String s = list2.get(1); ---》args表示1 将获取的结果result给s
Object result = method.invoke(list, args);
//这个返回值会返回给调用方法的调用者
return result;
}
};
//动态代理
List<String> list2 = (List<String>) Proxy.newProxyInstance(classLoader, interfaces,invocationHandler );
//返回代理对象
return list2;
}
}
注意:
上述代码生成的代理类对象必须使用List接口接收,不能使用ArrayList类接收,因为ArrayList和代理类没有关系,只是实现了共同父接口List.
//list2表示代理类对象
List<String> list2 = (List<String>) Proxy.newProxyInstance(classLoader, interfaces,invocationHandler );//正确
//list2表示代理类对象
ArrayList<String> list2 = (ArrayList<String>) Proxy.newProxyInstance(classLoader, interfaces,invocationHandler );//错误
测试类
public class Demo04 {
public static void main(String[] args) {
//创建集合
ArrayList<String> list = new ArrayList<>();
//添加方法
list.add("石原里美");
list.add("新垣结衣");
//用动态代理的方式返回一个代理对象,要求代理对象只能添加长度为4的字符串
List<String> list2 = MyColl2.getList(list);
//添加
list2.add("桥本环奈");
//list2.add("柳岩");
System.out.println(list2);
}
}
动态代理生成的代理类对象
package com.itheima.sh.demo_04;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
@SuppressWarnings("all")
public class MyColl2 {
public static List<String> getList(ArrayList<String> list){
//动态代理
//返回值地方只能接口不能写具体类型
List<String> list2 = (List<String>) Proxy.newProxyInstance(list.getClass().getClassLoader(), list.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//对其他方法没有限制
//对添加方法 只允许添加长度为4的字符串
if(method.getName().equals("add")){
//判断参数的长度是否为4
String s = (String) args[0];
if(s.length() != 4){
//产生异常
throw new RuntimeException("添加的字符串长度必须是4");
}
}
//如果是其他情况就要正常执行
Object result = method.invoke(list, args);
return result;
}
});
return list2;
}
}
动态代理非常的灵活,可以为任意的接口实现类对象做代理
动态代理可以为被代理对象的所有接口的所有方法做代理,动态代理可以在不改变方法源码的情况下,实现对方法功能的增强,
动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。
动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。
动态代理同时也提高了开发效率。
缺点:只能针对接口的实现类做代理对象,普通类是不能做代理对象的。
介绍
工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。之前我们创建类对象时, 都是使用new 对象的形式创建, 除new 对象方式以外, 工厂模式也可以创建对象.
没有使用工厂设计模式创建对象即以前创建对象方式的问题
假设定义三个车类 BaoMa BenChi Wulin ,然后在测试类创建三个类的对象
代码演示:
public class BaoMa {
}
public class BenChi {
}
public class AoDi {
}
public class Test01 {
public static void main(String[] args) {
//创建三个类的对象
BaoMa baoMa = new BaoMa();
BenChi benChi = new BenChi();
AoDi aoDi = new AoDi();
}
}
问题说明:
1.假设当前项目下具有很多个测试类,都要使用这三个类,然后我想统计每个类的对象有多少个,那么 这样统计就会很麻烦
2.测试类和这三个类还耦合在一起了,我们应该降低耦合
使用工厂设计模式创建对象
代码演示
package com.itheima.sh.demo_04;
/*
汽车工厂类
*/
public class CarFactory {
//工厂专门用来创建汽车对象
//name表示调用该方法时指定的车类型
public static Car getCar(String name){
if(name.equals("BaoMa")){
return new BaoMa();
}else if(name.equals("BenChi")){
return new BenChi();
}else if(name.equals("AoDi")){
return new AoDi();
}
return null;
}
}
//汽车
public interface Car {
}
public class BaoMa implements Car{
//宝马
}
public class BenChi implements Car{
//奔驰
}
public class AoDi implements Car{
//奥迪
}
//测试类
public class Test01 {
public static void main(String[] args) {
//创建三个类的对象
// BaoMa baoMa = new BaoMa();
// BenChi benChi = new BenChi();
// AoDi aoDi = new AoDi();
//---------------------------------
//使用工厂创建对象
Car baoMa = CarFactory.getCar("BaoMa");
System.out.println(baoMa);
Car benChi = CarFactory.getCar("BenChi");
System.out.println(benChi);
Car AoDi = CarFactory.getCar("AoDi");
System.out.println(AoDi);
}
}
小结:
工厂模式的存在可以改变创建类的方式
方便管理对象
降低类与类之间的耦合,调用工厂类中的方法直接指定字符串即可,创建对象的事情都交给工厂类
Lombok能通过注解的方式,在编译时自动为属性生成构造器、getter/setter、equals、hashcode、toString方法。出现的神奇就是在源码中没有getter和setter方法,但是在编译生成的字节码文件中有getter和setter方法。这样就省去了手动重建这些代码的麻烦,使代码看起来更简洁些。
1. 添加lombok的jar包:
将lombok.jar(本例使用版本:1.18.10),添加到模块目录下,并添加到ClassPath
2. 为IDEA添加lombok插件(连接网络使用)
注意:一定勾选上Enable annotation processing 按钮才可以使用lombok,否则不能使用。
3.新建一个类:Student
public class Student {
private String name;
private int age;
}
4.lombok常用注解
@Getter和@Setter
@ToString:
@NoArgsConstructor和@AllArgsConstructor
@EqualsAndHashCode
@Data
作用: 生成setter/getter、equals、hashCode、toString方法,如为final属性,则不会为该属性生成setter方法。
注解只能写在类上。
编写代码
package com.itheima.sh.demo_04;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data // setter/getter、equals、hashCode、toString方法
@NoArgsConstructor//无参构造
@AllArgsConstructor//满参构造
public class Student {
private String name;
private int age;
}
public class Test01 {
public static void main(String[] args) {
Student s = new Student();
Student s1 = new Student("张三",20);
System.out.println("s = " + s); // s = Student(name=null, age=0)
System.out.println("s1 = " + s1); // s1 = Student(name=张三, age=20)
System.out.println(s1.getName()); // 张三
}
}