看本篇博客需要对JVM有一点点了解,当然可以在不懂的地方自己查也是可以的。另外如果有错误的地方,请评论区指正^^^
1、单例模式(Singleton Pattern)
单例模式呢,顾名思义,在一个JVM运行实例中,某个类只有这么一个对象,无论在哪里获取该类的对象,得到的都是同一个对象,我们称这样的类的设计模式属于单例模式。
单例模式分类有两种:
饿汉式:在类的加载(类的加载又分为主动使用和被动使用,一般被动使用不会触发clinit方法)时一般是在<clinit>阶段该单例对象就会被创建。(饿汉给人的感觉就是上来就吃嘛…)
懒汉式:顾名思义,听过懒加载的可能都知道懒的意思,就是用的时候再加载,放在这也就是第一次使用该单例对象的时候会被加载。
1.1、饿汉式--(静态变量/静态代码块方式)--代码块里有评价
public class Test_01_StaticVar {
//将构造器私有
private Test_01_StaticVar(){}
//定义一个私有的静态变量
private static Test_01_StaticVar instance = new Test_01_StaticVar();
//提供获取单例对象的静态方法
public static Test_01_StaticVar getInstance(){
return instance;
}
}
或是这样子写,其实在字节码中都体现在<clinit>方法中
public class Test_01_StaticVar {
//将构造器私有
private Test_01_StaticVar(){}
//定义一个私有的静态变量
private static Test_01_StaticVar instance = null;
static {
instance = new Test_01_StaticVar();
}
public static Test_01_StaticVar getInstance(){
return instance;
}
}
//这种方式会在类加载时直接被创建,有关的评价说是因为可能被加载之后一直不使用造成内存浪费,
//很多人包括我也很纳闷,不主动使用怎么会导致类的加载。
1.2、懒汉式--基础版本
public class Test_02_Lazy01 {
private Test_02_Lazy01(){}
private static Test_02_Lazy01 instance;
public static Test_02_Lazy01 getInstance(){
if(instance == null){
instance = new Test_02_Lazy01();
}
return instance;
}
}
//缺点就是在并发获取的情况下会造成不是单例的情况。
//例如但不限于以下一种情况:假设instance是null,当A线程指向到判断语句进入if开始new对象之前此线程的调度时间结束开始调度其他线程,
//另一个线程B在这段时间也执行了这个if语句并且也成功的进入if代码块内,之后就会导致两次创建这个对象。造成的结果就不符合单例这一条件。
一种解决方式如下:
public class Test_02_Lazy01 {
private Test_02_Lazy01(){}
private static Test_02_Lazy01 instance;
public static synchronized Test_02_Lazy01 getInstance(){ <---不同点在这行
if(instance == null){
instance = new Test_02_Lazy01();
}
return instance;
}
}
//synchronized写方法头上的缺点是性能不高,获取该对象就会获取锁。大多数是只需要读就可以的。只有第一次创建时获取锁才有意义
1.3、懒汉式--升级版本--双重检查锁
public class Test_02_Lazy02 {
private Test_02_Lazy02(){}
private static Test_02_Lazy02 instance;
public static Test_02_Lazy02 getInstance(){
if(instance == null){
synchronized (Test_02_Lazy02.class) {
if(instance == null)
instance = new Test_02_Lazy02();
}
}
return instance;
}
}
//是不是感觉这样就很完美,其实不然
//还是有问题,可能会空指针异常,因为虚拟机优化的问题指令序列会重排(据说学过并发编程的应该知道。博主暂时没学)
睁大眼睛了,最终版本如下:
public class Test_02_Lazy02 {
private Test_02_Lazy02(){}
private static volatile Test_02_Lazy02 instance;
public static Test_02_Lazy02 getInstance(){
if(instance == null){
synchronized (Test_02_Lazy02.class) {
if(instance == null)
instance = new Test_02_Lazy02();
}
}
return instance;
}
}
volatile 关键字可以保证可见性和有序性。
1.4、懒汉式--静态内部类方式
静态内部类在主类加载的时候是不会被加载的,利用这个特点我们可以这样做:
public class Test_02_LazyInnerClass {
private Test_02_LazyInnerClass(){}
private static class TestHolder{
private static final Test_02_LazyInnerClass INSTANCE = new Test_02_LazyInnerClass();
}
public static Test_02_LazyInnerClass getInstance(){
return TestHolder.INSTANCE;
}
}
//这是比较推荐的方式,很多开源项目在用这种方式
1.5、饿汉式--枚举方式
前提知识:
//枚举的学习
public class TheEnumClass {
public enum Enum1{
E1("E1"){
@Override
public void SayHello() {
System.out.println("我是E1");
}
}, //反编译后会被显示为 public static final TheEnumClass E1; ,
// 并且其初始化是在静态代码块中 static{ E1 = new Enum1("E1") }
E2 {
@Override
public void SayHello() {
System.out.println("我是E2");
}
},
E3(){
@Override
public void SayHello() {
System.out.println("我是E4");
}
},
E4(){
@Override
public void SayHello() {
System.out.println("我才是E4");
}
}; //分号可加可不加
private String name;
private Enum1(String a){
name = a;
System.out.println("一个实例被创建");
}
private Enum1(){
System.out.println("一个实例被创建");
}
//直接调用
public void fun(){
System.out.println("testFunc");
this.SayHello();
}
public abstract void SayHello();
}
public static void main(String[] args) {
Enum1 enum1 = Enum1.E2;
enum1.fun();
}
}
//1、注意枚举类是不可继承拓展的
//2、枚举类的对象默认都是 public static final
//3、枚举类的构造器只能是私有的,但是其他的没用限制,你可以重写,包括重载多个
//4、enum类默认extends java.lang.Enum,所以无法再继承其他类
//5、一般来说枚举类经过反编译之后是非抽象的,但是拥有抽象方法的枚举类,你必须在定义枚举类的时候在内部的枚举项也就是那些被psf修饰的变量中直接实现,如上面例子中的那样
idea 反编译的 class文件
public class TheEnumClass {
public TheEnumClass() {
}
public static void main(String[] args) {
TheEnumClass.Enum1 enum1 = TheEnumClass.Enum1.E2;
enum1.fun();
}
public static enum Enum1 {
E1("E1") {
public void SayHello() {
System.out.println("我是E1");
}
},
E2 {
public void SayHello() {
System.out.println("我是E2");
}
},
E3 {
public void SayHello() {
System.out.println("我是E4");
}
},
E4 {
public void SayHello() {
System.out.println("我才是E4");
}
};
private String name;
private Enum1(String a) {
this.name = a;
System.out.println("一个实例被创建");
}
private Enum1() {
System.out.println("一个实例被创建");
}
public void fun() {
System.out.println("testFunc");
this.SayHello();
}
public abstract void SayHello();
}
}
使用枚举创建:
enum Test_05_HangryEnum {
INSTANCE;
public void testmethod1(){
System.out.println("我是实例方法一");
}
public void testmethod2(){
System.out.println("我是实例方法二");
}
public Test_05_HangryEnum getInstance(){
return INSTANCE;
}
}
1.6、存在的问题及解决方法
序列化破坏单例模式
//序列化破坏单例模式
public class Test_06_TheProblems {
@Test
public void Test() throws Exception {
Test_04_LazyInnerClass test_04_lazyInnerClass = Test_04_LazyInnerClass.getInstance();
System.out.println(test_04_lazyInnerClass); //Test_04_LazyInnerClass要序列化
writeObjectTofile(test_04_lazyInnerClass);
Object o = readObjectTofile(); // ctrl + alt +v 自动赋值
System.out.println(o);
}
public Object readObjectTofile() throws Exception {
ObjectInputStream osi = new ObjectInputStream(new FileInputStream("Y:abc.txt"));
Object o = osi.readObject();
return o;
}
public void writeObjectTofile(Object o) throws Exception {
File f = new File("Y:abc.txt");
if(!f.exists()){f.createNewFile();}
ObjectOutputStream owi = new ObjectOutputStream(new FileOutputStream(f));
owi.writeObject(o);
}
}
//com.L.singnalton.Test_04_LazyInnerClass@6cc7b4de
//com.L.singnalton.Test_04_LazyInnerClass@2d127a61
//可以看出两个对象并不是同一个,说明序列化可以破坏单例模式
//解决方法是在单例类中加一个方法(这个方法不可以是静态的),返回单例对象,该方法的内容写成和getInstance方法一样即可。
//这样在反序列化时会将该方法的返回值返回
//测试4中内部类方式创建单例的类中加入下面这个方法即可,其他的类似,只要符合单例逻辑
public Object readResolve(){
return TestHolder.INSTANCE;
}
//序列化破坏单例模式
public class Test_07_TheProblems {
@Test
public void Test() throws Exception {
Class clazz = Test_04_LazyInnerClass.class;
//获取Class三种方法,1,Class.forname 对象.getClass() 直接使用类名.class
Constructor constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
System.out.println(constructor.newInstance()); //ctrl + D 快速复制当前行到下一行
System.out.println(constructor.newInstance());
}
}
//com.L.singnalton.Test_04_LazyInnerClass@276438c9
//com.L.singnalton.Test_04_LazyInnerClass@588df31b
//可以看出调用了本来被隐藏的构造方法,得到的两个对象并不一样。
//那么聪明的你一定想到解决方法了,没错就是在构造方法里面搞了,怎么搞你随便,就是阻止这么创建对象,例如你可以在构造方法里面抛异常,随便,就是不让成功创建。
2、工厂模式(Factory Pattern)
在java中万物皆对象,这些对象都需要创建,如果创建的时候使用new的方式,就会造成严重的耦合,假如我们需要对某个接口使用其他的实现类替换,那么我们就需要对代码中所有的new的地方替换右侧的new到的对象,这显然是很麻烦的。如果使用工厂模式来设计,我们只需要和工厂打交道,如果更换对象,只需要在工厂中改变实现类即可。
2.1、简单工厂模式
//抽象产品角色,可以是接口或者抽象类
public abstract class TheAbstractFoodProduct {
public abstract String getFoodName();
}
//具体产品1
public class TheNoodles extends TheAbstractFoodProduct {
public String getFoodName() {
return "面条";
}
}
//具体产品2
public class TheRice extends TheAbstractFoodProduct {
public String getFoodName() {
return "大米";
}
}
//工厂角色
public class SimpleFactory {
private SimpleFactory(){}
public static TheAbstractFoodProduct getFood(String type){ //因为这个类并没用什么其他的作用,声明称静态方法更合适
if(type==null){
return null;
}
if(type.equals("面条")){
return new TheNoodles();
}
else if(type.equals("米饭")){
return new TheRice();
}
else return null;
}
}
//测试类
public class Use_Test {
@Test
public void test1() {
TheAbstractFoodProduct food = SimpleFactory.getFood("米饭");
System.out.println(food.getFoodName());
}
}
其实还是可以体会出来,如果在多个类中使用到抽象产品,那么我们一个个的new的话,将来不改的话挺好,如果改起来的话就很麻烦,这里的改包括具体产品的类名、所处的包的位置等的变化,使用简单工厂可以以后只修改工厂类这一个类。
不过,简单工厂的缺点也是显而易见的,那就是如果增加新的产品实现,必然要更改工厂里面的逻辑(不符合开闭原则),所以呢就出现了下面这种方式,那就是将工厂类也抽象出来,形成抽象工厂。
2.2、工厂方法模式
就如同上面结束所说,工厂方法模式将具体的工厂类和具体的产品对应起来,所以缺点也是明显的那就是类太多出一类产品就要多一个工厂。有如下角色
抽象工厂:提供了创建产品的接口,调用者通过它访问具体的工厂创建产品。
具体工厂:主要是实现抽象工厂中的抽象方法,完成具体的产品的创建。
抽象产品:定义产品的规范,描述了产品的主要特性和功能。
具体产品:实现了抽象产品角色所定义的接口,由具体的工厂创建,它同具体的工厂之间一一对应。
//抽象产品角色,可以是接口或者抽象类
public interface TheAbstractFoodProduct {
String getFoodName();
}
//具体产品1
public class TheNoodles implements TheAbstractFoodProduct {
public String getFoodName() {
return "面条";
}
}
//具体产品2
public class TheRice implements TheAbstractFoodProduct {
public String getFoodName() {
return "大米";
}
}
//抽象工厂角色
public interface FoodFactory {
TheAbstractFoodProduct createFood();
}
//具体工厂1
public class NoodlesFactory implements FoodFactory {
public TheAbstractFoodProduct createFood() {
return new TheNoodles();
}
}
//具体工厂2
public class RiceFactory implements FoodFactory {
public TheAbstractFoodProduct createFood() {
return new TheRice();
}
}
//测试类
public class Use_Test {
@Test
public void test1() {
TheAbstractFoodProduct food = new NoodlesFactory().createFood();
System.out.println(food.getFoodName());
}
}
正如简单工厂的问题,当我们增加新的产品的时候,我们需要修改原来的工厂类,而使用工厂方法模式之后我们增加新的一个产品,就需要增加一个工厂类,这样也是有问题的,这样产生的类就太多了,我们生活中的很多产品其实有这样一种规律,一个工厂其实是可以生产一类属性相近产品,如华为的电脑,手机,平板等,下面讲的抽象工厂模式就是这种思想,将生产一类产品的工厂抽象出来。