Java的static关键字在实践中深刻认知

3 篇文章 0 订阅

Java的static关键字的定义

在Java中static是个修饰符,用于修饰类(而非对象)的成员方法,成员变量,被static关键字修饰的方法或者变量不需要依赖于对象来进行访问。
static 修饰的属于类,对于同一类共享内存,而成员变量,普通方法属于对象,不共享内存

示例演示

  1. 以下以最近我在实际生产中写的static关键字的乱用带来的bug演示:
    需求如下:我们做了一个多平台统一的管理系统,现在有一个功能我们需要根据给定时间生成计划,但是目前我们只有其中一个平台支持该功能,但是我们要让程序有足够的扩展性,方便后续其他平台的对接。

  2. 需求分析(内心独白):
    既然是多平台的功能,我们大概率需要用到策略模式,但是现在又只有一个平台支持这个功能,所以简单点了就整个内部的策略模式一锅端了,后面又其他平台进来了我们再把策略接口单独拎出来。

  3. 开始写代码,以下可以看到我定义了一个用于计算plan的聚合类,提供了一个用于计算不同合作方的计划。

@AllArgsConstructor
public class UnifiedPlanAggregateRoot {

    // 计算计划的基准时间
    private LocalDate basedDate;

    // 各个合作方的策略map
    private static Map<String, PartnerPlanPolicy> partnerPoliciesMap = new HashMap<>();

    // 初始化各个合作方的策略
    {
        partnerPoliciesMap.put("Google", new GooglePlanPolicy());
    }

    // 根据给定的合作方,调用对应的策略进行计算计划
    public List<LocalDate> calculatePlan(String partner) {
        return partnerPoliciesMap.get(partner).calculatePlan();
    }

    /**
     * 这里我定义一个内部的policy接口,然后实现了一个google的policy,以后可能会有bing,facebook等等的policy
     */
    interface PartnerPlanPolicy {
        List<LocalDate> calculatePlan();
    }

    /**
     * google的一个计算计划策略实现,我们简单返回一个后面一个星期的日期用来测试
     */
    class GooglePlanPolicy implements PartnerPlanPolicy {

        private static final int GOOGLE_PLAN_LIMITATION = 7;

        @Override
        public List<LocalDate> calculatePlan() {
            List<LocalDate> plans = new ArrayList<>();
            for (int i = 0; i < GOOGLE_PLAN_LIMITATION; i++) {
                plans.add(basedDate.plusDays(1));
            }
            return plans;
        }
    }
}
  1. 单元测试如下,也如预期顺利通过。
    @Test
    void calculatePlan() {
        UnifiedPlanAggregateRoot planAggregateRoot = new UnifiedPlanAggregateRoot(LocalDate.of(2022, 03, 15));
        List<LocalDate> googlePlans = planAggregateRoot.calculatePlan("Google");
        Assert.assertEquals(7, googlePlans.size());
        googlePlans.forEach(plan -> Assert.assertTrue(plan.isBefore(LocalDate.of(2022, 03, 23))));
    }

分析问题

这里我们定义的这个UnifiedPlanAggregateRoot中有一个静态的partnerPoliciesMap,有一段代码块,还有一个成员变量basedDate。

  1. 当你第一次创建这个对象时,首先会初始化这个静态的partnerPoliciesMap
  2. 然后执行了这个代码块,给这个map添加了K,V
  3. 最后执行构造方法初始化了成员变量basedDate
  4. 当你第二次创建这个对象时,静态的partnerPoliciesMap已经初始化过了,不会再运行,但是代码块是非静态的还是会再执行一遍,这会导致一个问题,它重新创建了一次policy对象,然而policy对象并非是个无状态的服务,它内部使用了一个basedDate的成员变量,也就是说现在这个静态的partnerPoliciesMap中的policy对象时是由第二次创建对象时创建的,这时如果第一次创建的UnifiedPlanAggregateRoot执行calculatePlan方法的话,其调用的policy是第二次对象创建的,并引用了第二个UnifiedPlanAggregateRoot的成员变量,导致Plan的基准日期并不是这个对象自己的基准时间,得不到预期值

验证分析

这里我先初始化两个UnifiedPlanAggregateRoot对象后再调用calculatePlan方法,Assert将会报错 googlePlans.forEach(plan -> Assert.assertTrue(plan.isBefore(LocalDate.of(2022, 03, 23))));将会抛异常

    @Test
    void calculatePlanWithError() {
        UnifiedPlanAggregateRoot planAggregateRoot = new UnifiedPlanAggregateRoot(LocalDate.of(2022, 03, 15));
        UnifiedPlanAggregateRoot planAggregateRoot2 = new UnifiedPlanAggregateRoot(LocalDate.of(2022, 04, 15));
        List<LocalDate> googlePlans = planAggregateRoot.calculatePlan("Google");
        Assert.assertEquals(7, googlePlans.size());
        googlePlans.forEach(plan -> Assert.assertTrue(plan.isBefore(LocalDate.of(2022, 03, 23))));
    }

{% asset_img assert_console.png 单测报错信息 %}

总结

  1. static是属于类的,而成员变量是属于对象的,属于类的共享内存,只会初始化一次,属于对象的每次都会初始化。
  2. 永远不要再非静态的代码块中修改静态变量
  3. 对于无状态的服务,不要引用外部有状态的变量。以以上的例子来说就是policy内部不要使用外部的basedDate,应该由调用方传入该参数。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值