java 分享_Java日常干货分享

系列文描述:

该文章主要记录了开发中如何基于Java使用观察者模式、如何使用不允许改变的容器、如何反射枚举以及类字面常量的特殊性,相信看完肯定有所收获。

基于Java实现观察者模式

作用描述:

在日常研发过程总会遇见业务A随着业务B发生相关变化的情况,这种情况便可以运用观察者模式,而对于观察者模式,Java已经为我们提供了已有的接口和类方便我们使用。

对于订阅者Java为我们提供了一个接口,JDK源码如下:

publicinterfaceObserver{void update(Observable var1,Object var2);}

可以看出,此处仅提供一个update方法用于接收通知者的通知做出相应改变。在实际业务中,实现观察者模式的订阅者只需要实现该接口并实现update接口实现业务即可。

场景代码如下:

9f735b4bceb40c25def042fe7a570fae.png

c152a38a3528e426616c8e14a8130686.png

再来看看Java提供了一个怎样的通知者,可以看到JDK源码如下:

publicclassObservable{privateboolean changed =false;// 存放Observer的容器,本身是安全的,看了源码,内部实现的大部分函数都使用了synchronizedprivateVector obs =newVector();publicObservable(){}// 为通知者添加订阅者的地方publicsynchronizedvoid addObserver(Observer var1){if(var1 ==null){thrownewNullPointerException();}else{if(!this.obs.contains(var1)){this.obs.addElement(var1);}}}publicsynchronizedvoid deleteObserver(Observer var1){this.obs.removeElement(var1);}publicvoid notifyObservers(){this.notifyObservers((Object)null);}publicvoid notifyObservers(Object var1){Object[] var2;synchronized(this){if(!this.changed){return;}var2 =this.obs.toArray();this.clearChanged();}for(int var3 = var2.length -1; var3 >=0;--var3){((Observer)var2[var3]).update(this, var1);}}publicsynchronizedvoid deleteObservers(){this.obs.removeAllElements();}protectedsynchronizedvoid setChanged(){this.changed =true;}protectedsynchronizedvoid clearChanged(){this.changed =false;}publicsynchronizedboolean hasChanged(){returnthis.changed;}publicsynchronizedint countObservers(){returnthis.obs.size();}}

首先我们可以从源码中看出Observable类使用Vector,Vector相比于ArrayList来说,它是线程安全的。其次,在多个函数上使用了synchronized关键字,这都是在为多线程考虑,避免出现在需要做出通知订阅者动作的时候因为数据紊乱出错的问题。在实际业务中,直接继承该类即可,场景代码如下:

d16254583c95369b243d978889774ac4.png

那么如何给监听者注册订阅者呢?场景代码如下:

b8145a03cdc278eaf1f08e343a76e497.png

而监听者通知订阅者的操作如下:

5462ae112e7f4019a4ff527b9baa7b3c.png

可以从上看出直接调用addObserver便可以给监听者注册订阅者,而在监听者发生变化的时候监听者调用setChange修改状态,之后调用notifyObservers通知,订阅者中的update实现便会被触发。

不允许改变的容器

作用描述:

最近在研发中间件期间,由于业务需要,需要通过配置生成全局所有的容器,而该容器对外是不允许外界修改的,为了满足这种需求,使用了不可变集合,顾名思义,该种集合除了get操作,不允许外界增删改。

代码演示:

import java.util.ArrayList;import java.util.Collections;import java.util.List;publicclassTestMain{publicstaticvoid main(String[] args){List originList =newArrayList<>();originList.add(TestBean.valueOf(1));originList.add(TestBean.valueOf(2));originList.add(TestBean.valueOf(3));List unmodifiableList =Collections.unmodifiableList(originList);for(TestBean t : unmodifiableList){System.out.println(t.getA());}unmodifiableList.add(TestBean.valueOf(4));}}classTestBean{privateint a;publicstaticTestBean valueOf(int a){TestBean testBean =newTestBean();testBean.a = a;return testBean;}publicint getA(){return a;}publicvoid setA(int a){this.a = a;}}

代码很简单,先是构建了一个正常的List,后通过 List

bfae0bc7b40dfe746b7dd29a56e40692.png

由于不可变容器不允许对容器做add操作,于是报错了,直接查看jdk源码

77e2372c3f5b6d23792e14b135d4e650.png

一句话总结:

在研发中,如果需要使用到不可变容器来规避风险,那么可以尝试使用jdk提供的不可变容器,除了List外还有Map、Set等,具体可以自己查看。

枚举的反射

作用描述:

最近遇见了将字符串反射为对应枚举的需求,发现java中枚举不允许通过newIntance反射出对象,因为内部自己禁止掉了,如

d886b259a96691dc3198118e127b20d8.png

定位到throw的位置,此处做了限制。为了突破这个限制,我查看了Spring自身转换器的源码,终于找到了方法,果然多看源码是有用的。

代码演示如下:

655676d648a5d4690daf141c8cc6e48a.png

先给个枚举类型

4013c3ca8fa3999ee08e7307f24fa325.png

此处是将int类型的数据转为对应的Enum,该int其实就是相当于你想转换的枚举在所在枚举里的位置。意思是如果int类型的数据是0的话,最后转换的枚举就是Currency。

a39c0ebd0782207e60ff0753bd2a9d0a.png

此处是将str类型的数据转为对应的Enum,该str类型的数据指的是枚举的名字。意思是如果str类型的数据是Currency的话,最后转换的枚举就是Currency。

一句话总结:

反射实例化枚举无法使用newInstance,请用以上两个例子,方便快捷。

关于类字面常量

描述:

最近接触了比较多的反射,了解到类字面常量特殊的地方,故做笔录,同时也和大家分享下类字面常量特殊在哪里。所谓的类字面常量指的是A.class,这是java提供的生成对Class对象的引用。关于类的概念我们都很熟悉,关于类的使用JDK大致为我们做了三步操作,分别是

加载,这是由类加载器执行的,用白话来描述就是查找这个类的字节码,然后构建一个Class对象。链接,在这个阶段会先校验类的字节码,并且为类对象的静态域分配好内存空间。初始化,首先会先初始化类的超类(前提是有超类),以及执行静态初始化器和静态初始化块。了解了三步还不够,我们还要知道jdk其实超级无敌懒的,对类的初始化这一步会延迟到对静态方法或者“非”常静态域进行首次引用时才执行。这里的常静态域指的是被static final修饰的常量,该常量不需要对应类初始化就可以被读取。为了更加清晰的了解到这个过程,可以看以下demo。

代码演示:

package classLoader;import java.util.Random;classInitable1{staticfinalint staticFinal =47;staticfinalint staticFinal2 =(int)(Math.random()*1000);static{System.out.println("Initializing Initable1");}}classInitable2{staticint staticNonFinal =147;static{System.out.println("Initializing Initable2");}}classInitable3{staticint staticNonFinal =74;static{System.out.println("Initializing Initable3");}}publicclassClassInitialization{publicstaticRandom random =newRandom(47);publicstaticvoid main(String[] args)throwsClassNotFoundException{Class initable1 =Initable1.class;System.out.println("After creating Initable1 ref");System.out.println(Initable1.staticFinal);System.out.println(Initable1.staticFinal2);System.out.println(Initable2.staticNonFinal);Class initable3 =Class.forName("classLoader.Initable3");System.out.println("After creating Initable3 ref");System.out.println(Initable3.staticNonFinal);}}

看具体运行结果前,看官先自己用草稿写下输出答案哈,这样比较有效果。运行结果如下:

After creating INitableref47InitializingInitable1604InitializingInitable2147InitializingInitable3After creating Initable3ref74

如果答案一样,那么可以直接忽略掉我接下来的解析了哈,因为分析很绕,主要是验证上面的理论!

47 在 After creating INitable ref 后才输出,证明了 Class initable1 = Initable1.class 这一行代码运行的时候并没有立即初始化类,而 Initializing Initable1 在47后才输出也验证了另一个观点(47对应的Field staticFinal是被static final 修饰的常静态域),那就是常静态域不需要在类Initable1进行初始化就可以被读取。而在打印结果Initable1.staticFinal2(随机数)之前先打印出了Initable1静态代码块中的输出,意味着直到这一步才真正初始化了类Initable1,于是先执行了静态代码块再输出了Initable1.staticFinal2,同样,对Initable2.staticNonFinal的输出先打印了Initable2静态代码块中的Initializing Initable2也是同样的原因。

而在最后使用Class.forName("classLoader.Initable3")取得Initable3类的引用的时候直接打印了代码块中的字符串可以看出,使用Class.forName取得类的引用的时候是立即初始化类的。

结尾说点什么

说好的一周一篇,上个周末沉迷docker的使用导致废了,然后最近又是每天都是差不多十二点下班,所以只能下班后花时间写总结最近的笔记了(  ̄▽ ̄)((≧︶≦)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值