令狐义卓

令狐义卓 查看完整档案

填写现居城市西南大学  |  计算机 编辑贵州学致  |  Java讲师 编辑填写个人主网站
编辑

十年Java从业者,个人Java学习交流群:3907814

个人动态

令狐义卓 发布了文章 · 今天 14:33

一文告诉你Java日期时间API到底有多烂

前言

你好,我是A哥(YourBatman)。

好看的代码,千篇一律!难看的代码,卧槽卧槽~其实没有什么代码是“史上最烂”的,要有也只有“史上更烂”。

日期是商业逻辑计算的一个关键部分,任何企业的程序都需要正确的处理日期时间问题,否则很可能带来事故和损失。为此本系列仅着眼于这一个点就写了好几篇文章,目的是帮助你系统化的搞定所有问题/难题。

平时我们都热衷于吐槽同事的代码有多烂,今天我们就来玩点狠的:吐槽吐槽JDK,看看它的日期时间API设计得到底有多烂。

说明:本文指的日期时间API是Date/Calendar系列,而非Java 8新的API。毕竟一般我们称后者为JSR 310日期时间,请注意区分哈

本文提纲

版本约定

  • JDK:8

正文

诚然,Java的API绝大多数设计得都是非常优秀且成功的,否则Java也不可能成为编程语言界的常青藤,并且还常年霸榜。但是,JDK也有失手的地方,存在设计得非常烂的API,先来了解下。

最烂API投票

谈到对Java API不满意程度的调研,最出名的当属2010年国外一个大佬Tiago Fernandez发起的一个很有意思的投票,投票结果的数据统计图表如下:

对横向标题栏的各个单词解释一下,从左到右依次为:

计算最终得分的公式为:

Score = (I can live with) + (Painful * 2) + (Crappy * 3) + (Hellish * 4) 

按照此公式,计算出各API的得分,画成直方图直观的展示出来:

好,排名出来了。从最烂 -> 最好的名次依次为:

  1. EJB 2.x,简直“遥遥领先”
  2. Date/Time/Calendar,今天的猪脚
  3. XML/DOM
  4. AWT/Swing
  5. ...

烂归烂,想一想什么样的烂API对你的产生影响会是最大的呢?答:很常用却很烂的。倘若一个API设计得很烂但你很少用或者几乎不用接触,你也不会对它产生很大厌恶感。打个比方,一堆屎本身很臭,但若你并不需要走到它身旁也就闻不到,自然就不会觉得它有多碍眼了。

回到这个统计结果来,EJB 2.x的API设计得最烂这个结果无可厚非,但站在时间维度的现在(2021年)回头来看,是可以完全忽略它了,毕竟现在的我们绝无可能再接触到它,再烂又有何干呢?

EJB 2.x这个老古董,相信在看文章的绝大部分同学都没见过甚至没听过它吧,A哥2015年入行,一上来Spring 4.x嘎嘎就是干,从未接触过EJB。

说明:这个统计是2010年做的,那会EJB2.x的使用量还比较大,因此上了“榜首”

XML/DOM设计得也不好,但已完全被第三库(如dom4j)取代,后者成为了事实的标准;AWT/Swing是市场的抉择,你用Java开发界面才会用到,否则不会接触,属于正常。

最后再看“屈居”第二名的Date/Time/Calendar日期时间API,它就不得了了。毕竟此API有个很大的特点:哪怕到了现在(2021年)依旧非常常用。所以,它设计得烂带来的实际影响是蛮大的。

下面就来具体了解下它有哪些坑爹的设计和槽点,一起不吐不快。

日期时间API的七宗罪

罪状一:Date同时表示日期和时间

java.util.Date被设计为日期 + 时间的结合体。也就是说如果只需要日期,或者只需要单纯的时间,用Date是做不到的。

@Test
public void test1() {
    System.out.println(new Date());
}

输出:
Fri Jan 22 00:25:06 CST 2021

这就导致语义非常的不清晰,比如说:

/**
 * 是否是假期
 */
private static boolean isHoliday(Date date){
    return  ...;
}

判断某一天是否是假期,只和日期有关,和具体时间没有关系。如果代码这样写语义只能靠注释解释,方法本身无法达到自描述的效果,也无法通过强类型去约束,因此容易出错。

说明:本文所有例子不考虑时区问题,下同

罪状二:坑爹的年月日

@Test
public void test2() {
    Date date = new Date();
    System.out.println("当前日期时间:" + date);
    System.out.println("年份:" + date.getYear());
    System.out.println("月份:" + date.getMonth());
}

输出:
当前日期时间:Fri Jan 22 00:25:16 CST 2021
年份:121
月份:0

what?年份是121年,这什么鬼?月份返回0,这又是什么鬼?

无奈,看看这两个方法的Javadoc:

尼玛,原来 2021 - 1900 = 121是这么来的。那么问题来了,为何是1900这个数字呢?

月份,竟然从0开始,这是学的谁呢?简直打破了我认为的只有index索引值才是从0开始的认知啊,这种做法非常的不符合人类思维有木有。

索引值从0开始就算了,毕竟那是给计算机看的无所谓,但是你这月份主要是给人看的呀

罪状三:Date是可变的

oh my god,也就是说我把一个Date日期时间对象传给你,你竟然还能给我改掉,真是太没安全感可言了。

@Test
public void test() {
    Date currDate = new Date();
    System.out.println("当前日期是①:" + currDate);
    boolean holiday = isHoliday(currDate);
    System.out.println("是否是假期:" + holiday);

    System.out.println("当前日期是②:" + currDate);
}

/**
 * 是否是假期
 */
private static boolean isHoliday(Date date) {
    // 架设等于这一天才是假期,否则不是
    Date holiday = new Date(2021 - 1900, 10 - 1, 1);

    if (date.getTime() == holiday.getTime()) {
        return true;
    } else {
        // 模拟写代码时不注意,使坏
        date.setTime(holiday.getTime());
        return true;
    }
}

输出:
当前日期是①:Fri Jan 22 00:41:59 CST 2021
是否是假期:true
当前日期是②:Fri Oct 01 00:00:00 CST 2021

我就像让你帮我判断下遮天是否是假期,然后你竟然连我的日期都给我改了?过分了啊。这是多么可怕的事,存在重大安全隐患有木有。

针对这种case,一般来说我们函数内部操作的参数只能是副本:要么调用者传进来的就是副本,要么内部自己生成一个副本。

在本利中提高程序健壮性只需在isHoliday首行加入这句代码即可:

private static boolean isHoliday(Date date) {
    date = (Date) date.clone();
    ...
}

再次运行程序,输出:

当前日期是①:Fri Jan 22 00:44:10 CST 2021
是否是假期:true
当前日期是②:Fri Jan 22 00:44:10 CST 2021

bingo。

但是呢,Date作为高频使用的API,并不能要求每个程序员都有这种安全意识,毕竟即使百密也会有一疏。所以说,把Date设计为一个可变的类是非常糟糕的设计。

罪状四:无法理喻的java.sql.Date

来,看看java.util.Date类的继承结构:

它的三个子类均处于java.sql包内。且先不谈这种垮包继承的合理性问题,直接看下面这个使用例子:

@Test
public void test3() {
    // 竟然还没有空构造器
    // java.util.Date date = new java.sql.Date();
    java.util.Date date = new java.sql.Date(System.currentTimeMillis());

    // 按到当前的时分秒
    System.out.println(date.getHours());
    System.out.println(date.getMinutes());
    System.out.println(date.getSeconds());
}

运行程序,暴雷了:

java.lang.IllegalArgumentException
    at java.sql.Date.getHours(Date.java:187)
    at com.yourbatman.formatter.DateTester.test3(DateTester.java:65)
    ...

what?又是一打破认知的结果啊,第一句getHours()就报错啦。走进java.sql.Date的方法源码进去一看,握草重写了父类方法:

还有这么重写父类方法的?还有王法吗?这也算是JDK能干出来的事?赤裸裸的违背里氏替换原则等众多设计原则,子类能力竟然比父类小,使用起来简直让人云里雾里。

java.util.Date的三个子类均位于java.sql包内,他们三是通过Javadoc描述来进行分工的:

  • java.sql.Date:只表示日期
  • java.sql.Time:只表示时间
  • java.sql.Timestamp:表示日期 + 时间

这么一来,似乎可以“理解”java.sql.Date为何重写父类的getHours()方法改为抛出IllegalArgumentException异常了,毕竟它只能表示日期嘛。但是这种通过继承再阉割的实现手法你们接受得了?反正我是不能的~

罪状五:无法处理时区

因为日期时间的特殊性,不同的国家地区在同一时刻显示的日期时间应该是不一样的,但Date做不到,因为它底层代码是这样的:

也就是说它表示的是一个具体时刻(时间戳),这个数值放在全球任何地方都是一模一样的,也就是说new Date()和System.currentTimeMillis()没啥两样。

JDK提供了TimeZone表示时区的概念,但它在Date里并无任何体现,只能使用在格式化器上,这种设计着实让我再一次看不懂了。

罪状六:线程不安全的格式化器

关于Date的格式化,站在架构设计的角度来看,首先不得不吐槽的是Date明明属于java.util包,那么它的格式化器DateFormat为毛却跑到java.text里去了呢?这种依赖管理的什么鬼?是不是有点太过于随意了呢?

另外,JDK提供了一个DateFormat的子类实现SimpleDateFormat专门用于格式化日期时间。但是它却被设计为了线程不安全的,一个定位为模版组件的API竟然被设计为线程不安全的类,实属瞎整。

就因为这个坑的存在,让多少初中级工程师泪洒职场,算了说多了都是泪。另外,因为线程不安全问题并非必现问题,因此在黑盒/白盒测试、功能测试阶段都可能测不出来,留下潜在风险。

这就是“灵异事件”:测试环境测试得好好的,为何到线上就出问题了呢?

罪状七:Calendar难当大任

从JDK 1.1 开始,Java日期时间API似乎进步了些,引入了Calendar类,并且对职责进行了划分:

  • Calendar类:日期和时间字段之间转换
  • DateFormat类:格式化和解析字符串
  • Date类:用来承载日期和时间

有了Calendar后,原有Date中的大部分方法均标记为废弃,交由Calendar代替。

Date终于单纯了些:只需要展示日期时间而无需再顾及年月日操作、格式化操作等等了。值得注意的是,这些方法只是被标记为过期,并未删除。即便如此,请在实际开发中也一定不要使用它们。

引入了一个Calendar似乎分离了职责,但Calendar难当大任,设计上依旧存在很多问题。

@Test
public void test4() {
    Calendar calendar = Calendar.getInstance(TimeZone.getDefault());
    calendar.set(2021, 10, 1); // -> 依旧是可变的

    System.out.println(calendar.get(Calendar.YEAR));
    System.out.println(calendar.get(Calendar.MONTH));
    System.out.println(calendar.get(Calendar.DAY_OF_MONTH));
}

输出:
2021
10
1

年月日的处理上似乎可以接受没有问题了。从结果中可以发现,Calendar年份的传值不用再减去1900了,这和Date是不一样的,不知道这种行为不一致会不会让有些人抓狂。

说明:Calendar相关的API是由IBM捐过来的,所以和Date不一样貌似也“情有可原”

另外,还有个重点是Calendar依旧是可变的,所以存在不安全因素,参与计算改变值时请使用其副本变量。

总的来说,Calendar在Date的基础上做了改善,但仅限于修修补补,并未从根本上解决问题。最重要的是Calendar的API使用起来真的很不方便,而且该类在语义上也完全不符合日期/时间的含义,使用起来更显尴尬。

总之,无论是Date,还是Calendar,还是格式化DateFormat都用着太方便,且存在各式各样的安全隐患、线程安全问题等等,这是API没有设计好的地方。

并不孤单

日期时间API属于基础API,在各个语言中都是必备的。然而不仅仅是Java面临着API设计很烂的处境,有些其它流行语言一样如此,涌现出1个(1堆)三方库比乙方库设计更好的情况,比如:

  • Python:日期时间处理库Arrow
  • JavaScript:日期时间处理库Moment.js
  • .Net:日期时间处理库Joda-Time

所以说,Java它并不孤单(自我安慰一把)

自我救赎:JSR 310

因为原生的Date日期时间体系存在“七宗罪”,催生了第三方Java日期时间库的诞生,如大名鼎鼎的Joda-Time的流行甚至一度成为标配。

对于Java来说,如此重要的API模块岂能被第三方库给占据,开发者本就想简单的处理个日期时间还得导入第三方库,使用也太不方便了吧。当时的Java如日中天,因此就开启了“收编”Joda-Time之旅。

2013年9月份,具有划时代意义的Java 8大版本正式发布,该版本带来了非常多的新特性,其中最引入瞩目之一便是全新的日期时间API:JSR 310。

JSR 310规范的领导者是Stephen Colebourne,此人也是Joda-Time的缔造者。不客气的说JSR 310是在Joda-Time的基础上建立的,参考了其绝大部分的API实现,因此若你之前是Joda-Time的重度使用者,现在迁移到Java 8原生的JSR 310日期时间上来几乎无缝。

即便这样,也并不能说JSR 310就完全等于Joda-Time的官方版本,还是有些许诧异的,例举如下:

  1. 首先当然是包名的差别,org.joda.time -> java.time标准日期时间包
  2. JSR 310不接受null值,Joda-Time把Null值当0处理
  3. JSR 310所有抛出的异常是DateTimeException,它是个RuntimeException,而Joda-Time都是checked exception

简单感受下JSR 310 API:

@Test
public void test5() {
    System.out.println(LocalDate.now(ZoneId.systemDefault()));
    System.out.println(LocalTime.now(ZoneId.systemDefault()));
    System.out.println(LocalDateTime.now(ZoneId.systemDefault()));

    System.out.println(OffsetTime.now(ZoneId.systemDefault()));
    System.out.println(OffsetDateTime.now(ZoneId.systemDefault()));
    System.out.println(ZonedDateTime.now(ZoneId.systemDefault()));

    System.out.println(DateTimeFormatter.ISO_LOCAL_DATE.format(LocalDate.now()));
    System.out.println(DateTimeFormatter.ISO_LOCAL_TIME.format(LocalTime.now()));
    System.out.println(DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(LocalDateTime.now()));
}

JSR 310的所有对象都是不可变的,所以线程安全。和老的日期时间API相比,最主要的特征对比如下:

image.png

关于JSR 310日期时间更多介绍此处就不展开了,毕竟前面文章啰嗦过好多次了。总之它是Java的新一代日期时间API,设计得非常好,几乎没有缺点可言,可用于100%替代老的日期时间API。

如果你到现在2021年了还没拥抱它,那么请问你还在等啥呢?

总结

日期时间API因为过于常用,因此你可能都觉得它毫不起眼。坦白的说,如果你没有复杂的日期时间需求要处理,如涉及到时区、偏移量、跨时区转换、国际化显示等等,那么可能觉得Date也能将就。

如果你不想做个将就的人,如果你想拥有更好的日期时间编程体验,弃用Date,拥抱JSR 310吧。

零基础学习Java编程,推荐加入我的十年Java学习园地,技术交流,资源共享,答疑解惑,开发经验分享。

查看原文

赞 0 收藏 0 评论 0

令狐义卓 发布了文章 · 今天 13:47

线上Java程序占用 CPU 过高,请说一下排查方法?

这个问题可以说是 Java 面试的高频面试题了,有很多面试官都喜欢问这个问题,问题可能是下面这样的。

线上一台服务器 CPU 使用率100% 了,如果你碰到这样的情况,如何排查并找到问题原因?

这就是一个套路题,所谓套路题就是有标准的套路解法的,掌握了套路,不仅能解决面试官,还能解决问题。不然真的就掉进套路里了。

当我们真碰到这个问题的时候应该怎么排查呢?

模拟一个高 CPU 场景

先用一段程序创建几个线程,将其中一个线程设置成高 CPU 使用率的。

public static void main(String[] args)  {
  for (int i = 0; i < 10; i++) {
    Thread thread = new Thread(() -> {
      System.out.println(Thread.currentThread().getName());
      try {
        Thread.sleep(30 * 60 * 1000);
      }catch (Exception e){
        e.printStackTrace();
      }
    });
    thread.setName("thread-" + i);
    thread.start();
  }

  Thread highCpuThread = new Thread(() -> {
    int i = 0;
    while (true) {
      i++;
    }
  });
  highCpuThread.setName("HighCpu");
  highCpuThread.start();
}

运行这段程序后,前面 10 个线程都处于休眠状态,只有最后一个线程会持续的占用 CPU 。

运行这段程序,然后就可以开始一些列的操作来发现问题原因了。

排查步骤

第一步,使用 top 找到占用 CPU 最高的 Java 进程

在真实环境中,首先要确认是不是 Java 程序造成的,如果有系统监控工具,可能会直接在预警信息里告诉你是有哪个进程造成的,但也有可能不知道,需要我们手动排查。

如果是在面试场景中,这个问题可能不需要确认,毕竟 Java 面试,面试官可能直接就告诉你是 Java 占用的 CPU 过高。

这一步也非常简单,就是一个 top命令而已,基本上所有同学都用过这个命令吧。
image.png
使用 top命令发现占用 CPU 99.7% 的线程是 Java 进程,进程 PID 为 13731

第二步,用 top -Hp 命令查看占用 CPU 最高的线程

上一步用 top命令找到了那个 Java 进程。那一个进程中有那么多线程,不可能所有线程都一直占着 CPU 不放,这一步要做的就是揪出这个罪魁祸首,当然有可能不止一个。

执行top -Hp pid命令,pid 就是前面的 Java 进程,我这个例子中就是 13731 ,完整命令为:

top -Hp 13731,执行之后的效果如下
image.png
可以看到占用 CPU 最高的那个线程 PID 为 13756

然后将 13756转换为 16 进制的,后面会用到,可以用在线进制转换的网站直接转换,转换结果为 0x35bc

第三步,保存线程栈信息

当前 Java 程序的所有线程信息都可以通过 jstack命令查看,我们用jstack命令将第一步找到的 Java 进程的线程栈保存下来。

jstack 13731 > thread_stack.log 

第四步,在线程栈中查找最贵祸首的线程

第二步已经找到了这个罪魁祸首的线程 PID,并把它转换成了 16 进制的,第三步保存下来的线程栈中有所有线程的 PID 16 进制信息,我们在线程栈中查找这个16进制的线程 id (0x35bc)。
image.png
怎么样,现在一目了然了,线程名称、线程状态、以及哪行代码消耗了最多的 CPU 都很清楚了。

零基础学习Java,推荐加入我的十年Java学习园地

查看原文

赞 0 收藏 0 评论 0

令狐义卓 发布了文章 · 1月26日

技术人员如何提升自己的价值

最近读了“左耳朵耗子”在极客时间分享的两篇文章“程序员如何用技术变现”,对文中的观点非常认同,并且感到很受启发;

文章中提到的很多内容并不仅限于理论,而是在实际工作与实践中具备实际指导意义的;

当然,我们不可能每个人都像陈皓老师那样来致富,但文中经验分享对于提高自己的职场竞争力,让自己变得更有价值是非常有帮助的;
image.png
个人理解+总结:

技术有“贵贱”

都是做技术开发的,为什么在细分领域会有不同的待遇差别呢?说明技术是有贵贱的。

20年前,如果会HTML、CSS、JS的基础知识,那么薪资水平可能就远高于当时的平均薪资;而现在的话,可能连工作都找不到。

当下比较流行的比如:人工智能、大数据处理、分布式系统、docker&kuberneters等

如何区分哪些技术是“贵”的技术呢?贵的技术一般具有以下特点:

  • 难学,大多数人望而却步
  • 会的人少的技术,需求远远大于供给的技术
  • 能帮企业赚钱/省钱的技术

image.png

如何提升自己的价值

  • 价值实际上是技术“贵贱”的体现,所以首先选择“贵”的技术;

关注市场需求、关注技术趋势、去寻找那些有价值的技术;

  • 技术有时候需要专而精

同样的业务,同样的硬件,同样的mysql数据库,有的人可以把QPS做到几万,而有的只能做到一两千;

  • 在有价值的技术上投入精力

这里可以遵循二八原则,将80%的精力投入到20%的有价值的技术上。对于价值量少的工作,尽量通过增加自动化程度、提高代码复用性等方式来提高完成效率;然后将更多的精力放在有价值的事情上

  • 让别人有求于自己

掌握别人不会的技术,让别人有求于你是价值体现的最好方式;

  • 提升自己的能力和经历

从个人到团队、公司,甚至于在整个行业内提升自己的影响力;

比如:参与开源项目、贡献开源项目、贡献技术含量非常高的博客等,积极的帮助别人解决问题等。

零基础学习Java编程,提升编程技术,可以加入我的十年Java学习园地,技术交流,答疑解惑,开发经验分享。

查看原文

赞 0 收藏 0 评论 0

令狐义卓 发布了文章 · 1月22日

零基础学Java可以学得好吗

零基础学习Java现实吗,能学会吗?要回答这个问题,我们应该从多个方面来回答,首先什么人比较适合学习Java?如果单纯从兴趣来说,任何人都适合,这就好比姜子牙70+还出征伐纣,刘邦在沛县聚众响应陈胜&吴广起义已经47岁,古代这岁数,相当于如今我们六十好几的年龄了,画家齐白石也是在56岁画风突转,才名声大噪。

所以说,学习永远不会晚,就像小编见过不同学历、怀着不同目的来学习Java的人,有初中生、有博士生,也有企业高管,等等,所以从学习的角度来看,互联网是一个包容性很强的领域,只要你有心,有一台电脑,随时都可以学习。

有人好奇博士生为什么来学习?小编说过了,每个人怀着不同的目的,别人为的不是就业上岗,也是为了科研,为了增进自己,为了兴趣!

学习Java的年龄瓶颈

如果以学习Java编程作为职场的叩门砖,并且以此作为主要谋生技能的话,那还真得考虑一下年龄的问题,如果你还是花样年华,恭喜你,在学习能力、记忆能力、理解能力最好的年华当中,学习Java可谓是最佳的时候,例如大学生、大专生、中专生、职中生、初中毕业生,相比于以前的学习和未来的工作,这段时间可谓最充足的,而且精力充沛,家庭压力也比较小,这时候就应该尽早、尽可能去学习!

等到35岁后,人的记忆力和学习能力因周围环境和身体的影响,接受能力明显没有年轻人的快,这就是所谓的年龄瓶颈,不过如今经济稍好,人在饮食营养方面有所提高,所以学习Java,职业瓶颈可延迟到37、38岁。

学习Java,兴趣重要吗

入行前,你不会知道兴趣重要还是不重要,只有进行学习了,才知道兴趣还是相当重要的。譬如小李敲代码,敲一个月代码,很痛苦,敲三个月,觉得很新奇,敲了一年,觉得提起敲代码就头大,头脑发热,有种想逃离的感觉,那就叫做兴趣不浓。

如果你耐得住程序员的寂寞和比较常见的加班习性,而且对长年累月敲代码不讨厌,记住,只要是不讨厌就好了,那都叫做有兴趣,因为只要不讨厌才能坚持下来,坚持下来,自然会花更多的时间来研究,从而成长得更好。

这里为什么我说的是不讨厌了,因为把兴趣当职业,本来就会削减兴趣的浓度,这是在所难免的,但只要不讨厌,这活还是能继续的。

Java学费贵吗?

说实话,在培训行业里,学习Java比起学习其他的设计类、测试类课程要贵,不仅因为它的学习时间长,而且难度也比较高,如果你觉得学费贵,大可以找网络上课或速成班,但效果如何,冷暖自知。

许多人上线上课程,还是因各种原因缺席的,觉得有录屏就万事ok,但试问有多少认认真真看了录屏还去研究、还去问老师问题的?不是不可以,只是没有了那种当面鼓、对面锣的氛围,人是很容易分神的。

我从事Java开发近十年,目前全职线上Java一对一辅导学习,如果你想学习Java,可以了解以下我的线上一对一,根据你的基础,学习能力,学习时间,学习进度给你制定学习计划,真正做到因材施教。也可以加入我的十年Java学习群:3907814 ,学习氛围好,技术学习上有什么问题可以在里面问。

学习Java需要精通英语和数学吗

其实对于初级Java程序员的英语要求并不高,差不多有高中水平就差不多了,当然想往更高阶的走,达到四六级英语水平就再好不过,因为很多技术源来自美国。但是这也不是一蹴而就的活,见过不少学员都是一边学习Java,一边学习英语。

至于数学,很多女程序员听到数学就怕,其实大可不必,因为在编程领域里面,除非要搞什么科研,像开发一些企业软件,拥有初中的数学水平真足够了,当然要学习人工智能、大数据开发等,那时再恶补一下,也是可以的。

初学者自学Java可以吗

可以,什么行业,自学都是可以的,但是自学而有所成,必须具备几个条件。首先自学的自制力和约束力,例如今天计划自学两小时,却一通电话过来,就决定出去快活了,一个“今天比较累了”的借口,就放弃了这一天的学习计划,这样的人,大有人在,哦,不是,是很多,所以自学的人往往花费很多时间成本。

IT培训机构4个月的脱产班,自学的人往往需要两年的时间,而且还有很多知识点没有搞懂的,到企业面试时,往往会有点不自信,因为自学的人心里没底。其实,花个4个月时间,然后去工作实践,那一年半的时间,不但让你赚回学费绰绰有余,而且还能获得了很多宝贵的实践经验。

这就像一个人是穷人思维,另一个人是富人思维,穷人不吃不喝存了一万块,而富人大吃大喝,把剩余的一千块来进行投资,结果赚回了一万块,当然这是个极端的例子,例如你的工作方式是喜欢三天打鱼两天晒网、频繁跳槽的,那能不能赚回学费就难说了,不过相信许多程序孩童都是有毅力的好孩纸。

上岗就业进入实践是提升Java技术水平最好的桥梁,而同一起步阶段,自学Java的人还在苦恼那段编程怎么写,那个工具是还有什么功能,其实有时候,一些工具的一些功能压根儿在实践中就没运用多少,而自学的人不知道,偏偏花费大量的时间去研究,得不偿失。

此外,同期培训的人还能形成技术圈子,即你有困难时,还可以找到好友帮助,有时候,别人项目发展得好,还可以拉你一把,这就叫做圈子效应,而自学Java的人,往往缺少这些“社会性”。

查看原文

赞 0 收藏 0 评论 0

令狐义卓 发布了文章 · 1月22日

谈一谈Java类加载相关的方方面面

什么是类加载器

类加载器就是将类的描述加载到虚拟机内存的这样一个模块;典型的类的描述就是java源码编译后的class文件,也可以是其他任何数据形式,比如网络字节流等;

类加载器有哪些

java默认定义了三种类加载器:

  • 启动类加载器(Bootstrap Class-Loade)主要加载jre/lib下面的jar包
  • 扩展类加载器(Extension or Ext Class-Loader)主要加载jre/lib/ext/目录下的jar包;(jre/lib/ext/目录可以通过指定的java.ext.dirs覆盖)
  • 应用类加载器(Application or App Class-Loader),加载 classpath 的内容(classPath是一组目录组合)。

应用类加载器是可以被“覆盖”的,通过-Djava.system.class.loader=com.yourcorp.YourClassLoader 整个参数来覆盖原有的应用类加载器;这里的覆盖加了双引号,实际上自定义的类加载器会称为默认应用类加载器的父亲;

一般在需要改变双亲委派模型的时候会这么做;关于双亲委派模型,看下文;

类加载器的双亲委派模型

什么是双亲委派模型

当类加载器(Class-Loader)试图加载某个类型的时候,除非父加载器找不到相应类型,否则尽量将这个任务代理给当前加载器的父加载器去做。使用委派模型的目的是避免重复加载 Java 类型

为什么采用双亲委派模型

采用双亲委派模型主要出于两点考虑:

  1. 保证类的唯一性,一个类只被加载一次,避免重复加载;(因为在JVM规范里面,同一个class文件被两个相同的类加载,会被视为两个类)
  2. 安全性,保证java核心API不被替换;也就是启动类加载器加载过的类不会被其他类加载器覆盖;

有哪些打破了双亲委派模型,为什么打破

  • JNDI服务
  • Tomcat服务器
  • OSGI实现热更新

以上场景打破双亲委派模型的原因是父类没有能力加载或者完成所需的一些逻辑,为了整体架构设计更加优雅、灵活,便交由子类加载器来进行加载;

类加载的过程

类加载主要分为三个过程,加载、链接、初始化;

加载:Java 将字节码数据从不同的数据源读取到 JVM 中,并映射为 JVM 认可的数据结构(Class 对象)

链接:这是核心的步骤,简单说是把原始的类定义信息平滑地转化入 JVM 运行的过程中。具体又细分为验证、准备、解析三个过程;

  • 验证:验证Class对象是否符合虚拟机规范
  • 准备:创建静态变量并准备内存空间
  • 解析:将常量池中的符号引用修改为直接引用

初始化:执行类的初始化逻辑,包括静态字段赋值,静态块的初始化等;父类型的初始化优先于当前类型的。

自定义类加载器有哪些应用场景

  • 修改类的字节码逻辑,比如:为外部类统一织入通用逻辑;``
  • 根据用户需求,动态的创建类;比如:JDBC的驱动类,不同数据库需要使用不同的驱动类
  • 包名+类名有冲突的时候,一个类加载器不能同时加载,可以用不同的类加载器加载到内存中;
  • 热更新特性,通过自定义类加载器,实现在运行过程中动态的加载、卸载类

自定义类加载器的实现

主要核心在于获取类的字节码阶段,对于将字节码映射为Class对象,这部分一般不会涉及;

public class CustomClassLoader extends ClassLoader {
 
    @Override
    public Class findClass(String name) throws ClassNotFoundException {
        byte[] b = loadClassFromFile(name); //这里是核心逻辑
        return defineClass(name, b, 0, b.length);
    }
 
    private byte[] loadClassFromFile(String fileName)  {
        InputStream inputStream = getClass().getClassLoader().getResourceAsStream(
                fileName.replace('.', File.separatorChar) + ".class");
        byte[] buffer;
        ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
        int nextValue = 0;
        try {
            while ( (nextValue = inputStream.read()) != -1 ) {
                byteStream.write(nextValue);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        buffer = byteStream.toByteArray();
        return buffer;
    }
}

小结

今天跟大家分享了java类加载器的以下知识:

  1. 什么是类加载器
  2. 类加载器具体有哪些
  3. 类加载过程中的双亲委派模型
  4. 类加载的具体过程
  5. 自定义类加载器的应用场景
  6. 类加载器的实现

零基础学习Java编程,可以加入我的十年Java学习园地,技术交流,资源分享,答疑解惑,开发经验总结。

查看原文

赞 0 收藏 0 评论 0

令狐义卓 发布了文章 · 1月21日

过来人写给软件工程师的 30 条建议

就在几天前,我的人生迈入了30岁。在过去的十年中,我做了很多事情,也学习了很多东西。在本文中,我将回顾人生的历程,并将软件工程师的工作和生活公之于众。

下面,让我们进入正题。

1,努力工作

没有人会平白无故地给你升职或加薪。然而,光是努力工作还不够,你还需要提高效率。

努力地工作却没有提供任何价值的人一无是处,不要成为这种把坐在椅子上的小时数当作生产力的人。

顺便说一句,你也需要避免与这种庸庸碌碌的人一起工作,通常他们所在的公司也超级有毒,会让你苦不堪言。值得庆幸的是,这样的人已经在我们这个领域绝迹了。

2,不要执着于编程语言

过于执着某一种语言毫无意义。大多数编程语言都大同小异,而且有各自的优势。这也就是为什么我们有很多种语言,因为没有一种语言是完美的。

但是,请确保你至少了解每种范式内的一种语言。例如,函数式编程、面向对象编程等。

3,每年学习一种新的技术语言

你应该尝试每年学习一种新语言(理想情况下应该选择流行的语言,并强迫自己走出舒适区),只有这样才能保持大脑敏锐并跟上市场趋势。

另外,如果你发现某种语言的价值,则可以将其作为解决特定问题的工具,推荐给公司。

4,对自己的职业生涯负责

你的童年很心酸,你的前任经理(或现任经理)是一个混蛋,你经历了3段婚姻,离婚5次,你付出了2年时间才掌握的Web框架如今却不流行了。你的生活一团糟,看不到一丝曙光。

即便如此,你是成年人,应该靠自己打赢这场战斗。过去的种种艰辛不是不求上进的借口。全力以赴提升自我,才能有更好的明天。

5,不用担心无法控制的事情

你只需要考虑自己力所能及的事情。如前所述,你付出了2年时间才掌握的Web框架如今却不流行了。那么该怎么办?再学一种啊。这一次可以选择一个发展前景更好的框架。你可能需要在职业生涯中经历很多次这样的情形。

也许你(挚爱)的技术主管离开了公司。虽然非常伤心(过去我也有过这样的经历),但现在你需要打起精神,给新主管留下好印象。

如果你做不到,那么也可以离开公司。但是,请千万不要因为无法控制的事情而烦恼。你需要调整你的状态,因为“表演必须继续”(英语:The Show Must Go On,英国摇滚乐队皇后乐队的歌曲)。

6,不要与人结仇

如果某人在某方面比你强,不要恨他们,你应该向他们学习。通常,我们会将比自己优秀的人视作威胁。而我会将他们视作提升自我的动力源泉,你也应该这样做。

我记得过去有几位同事非常擅长应对压力,他们甚至可以在千钧一发之际力挽狂澜。我从他们身上学到了很多东西,我从来不嫉妒他们,因为我知道我也可以培养这样的能力。

如果周围无人能够超过你(在技术上),那么请当心,也许是时候为自己和事业寻找新的机遇了。相信我,除非你身居高职(如CTO),否则你不想成为公司里最强的员工。俗话说得好,宁为凤尾不为鸡头。

仇恨会毁掉你的生活,打击你的生活积极性。仇恨并不能给你带来任何好处。

7,敢于肩负重任,就不用担心薪水的问题

很遗憾的是,很多人眼里只有钱,却没能磨练自己的技术,建立强大的形象。

你认为下面哪种人未来的薪资会更高:是薪水低于平均水平的CTO,还是薪水高于平均水平的初级工程师?所以,要明智地选择自己工作。

8,辜负技术力的人,终将被技术力辜负

很多人以为计算机科学学位可以让自己身价倍增,各大公司会求着你去他们公司,而且就凭着一张纸就可以轻松获得一切。

不要误会我的意思,我相信一个好的学位可以证明你学习了大量有用的知识,但是很多人都止步于此了。

在这方面上,我很欣赏自学成才的程序员。可能他们缺乏对计算机科学理论知识的了解,但是他们知道迎难而上,因为他们成功的点点滴滴都是靠自己的拼搏换来的。

另外,如果你对于流行趋势的一些基本知识缺乏了解,那么就代表你的做法有问题。你无需成为专家即可掌握周围的世界。

9,廉价的硬件不仅质量堪忧,而且对你的健康有害

你应该花钱买一些高质量的键盘、鼠标和显示器。你的事业需要大量依赖于你的手和眼睛。

降噪耳机值得拥有,但如果你没有前面提到的高质量硬件,那么就不要因为盲目跟风。

除非你的工作环境非常嘈杂,否则就没有必要专门买降噪耳机。

10,出去走一走,也许问题就解决了

好吧,我有点夸张,但是我想强调发散模式的必要性,Barbara Oakley在她的课程中讲述了学习的方法,我强烈推荐。

如今,人们不太重视发散的思维方式。至少我知道很少有公司会同意你在上班期间睡觉(或进行其他发散性的活动),但事实上我们都需要发散思维。实际上人们嘴边常挂着的“以后再说”就隐含了发散模式的重要性。

11,将一部分收入投入到专业教育中

如果你的公司愿意支部费用,那就更好了。

YouTube是一种绝佳的学习资源,但是如果你真的想认真学习一门技术,而且收入允许的话,还是应该订阅高质量的培训服务,例如O'Reilly / Pluralsight。

找到适合自己的学习方法,然后学习更多知识。

12,避开没有培训政策的公司

可能我在这一点上持有强硬且带有偏见的看法。如果这家公司相对较新或资金不足,那也可以例外。

由于科技领域需要不断学习新知识,跟上最新的潮流,所以我认为不应该考虑没有适当培训/教育政策的公司。

对我而言,最低限度的教育政策是每位员工都有专门的预算,至少每年可以支付下面的部分费用:

• 参加会议

• 购买书籍

• 购买O'Reilly learning等高质量的培训服务

• 进行认证

当然,由于种种原因,大多数人不会在一年中把上述所有的事情都做一遍。也许是因为他们已进入稳定期,或者是因为他们有家庭,他们不能投入大部分的业余时间,但如果员工有这个意向,公司就应该全力支持。

我对大公司的要求更高,我希望大公司能够举行一些讲座,特别是在领域关键的问题上。例如,如果公司想采用Scrum,那么最好能邀请一名敏捷教练来帮忙做准备。

13,使用金钱可以买到的最好的工具

例如IDE。与硬件同理,不应该在IDE上贪便宜。正如《程序员修炼之道》所说,你需要选择一个编辑器,然后学习如何用好它。高质量的工具可以节省你宝贵的时间。

不要忘记,时间就是金钱。现在你支付了高昂的价格,以后就可以节省时间。

14,忽视动力

人们需要很大动力才能实现重要的人生目标。

动力很重要,但也是一种情感。和所有的情感一样,动力也会忽有忽无。

你需要找到一种更好的方式来指引自己前进的方向,至少在一些重要的事情上明白自己需要做些什么。

当一切顺利时,人们很容易知足常乐,但是当你失去动力或进入倦怠期时,又能做些什么呢?

15,保持活力与热情

虽然我不建议你时刻像打了鸡血一样积极发展事业和生活,但我认为每个人都应该有自己的事业远景规划。

你应该清楚自己的发展方向,只有这样才能知道做出的每个决定是否可以让你的事业更快、更安全地发展。正如2000年英国著名的划船手奖牌获得者所说,这个决定是否可以加速船的前进?

16,了解哪种类型的公司更适合你,并专心做好这类的工作

创业公司、中型公司和大公司,都有各自的福利和消极的方面。

如果你更喜欢在大公司工作,那么不一定能够处理好创业公司的日常工作。

你需要进行一番研究,确保你了解你有哪些选择,而你的每个选择能获得什么以及失去什么。不幸的是,这个问题没有统一的答案。

17,遇上一位好经理是健康的工作及生活的基础

你可能对这一点并不陌生,遇上一位通情达理、能与之有效沟通的经理,而且还能从事自己喜欢的工作,那将是人生一大幸事。

很多人辞职都不是因为公司,而是因为他们的经理。你需要确保自己能和经理愉快地相处。理想情况下,在进入公司之前就要确认这一点。

18,要想给别人一碗水,自己就得有一桶水

这是我最初开始写博客的原因。

如果我得知了一些有趣的事情,那么我可以通过博客分享。

我认为这种方法的效果很好。至少对我来说是如此,虽然我从未问过我的读者:)

19,只有坚持学习才不会被时代淘汰

与软件行业相关的高薪领域的变化相对也很多。该领域的发展如此之快,所以一旦你停止学习,就会被时代淘汰。

这并不意味着你应该将所有的工作时间都用来阅读和编写代码,但也不要走向另一个极端:安于现状,无视周围的变化。

20,学习是一个长期坚持的过程,不要急于求成

至于学习的心态,与平日荒废到了周日就一整天都坐在电脑前相比,每天花30-40分钟学习的效果更好。

上大学时你就对此深有感触,不是吗?如果你平时就按时做作业,那么考试前的压力就会小很多,知识需要一点一点地积累,无法一蹴而成。

21,先让程序跑起来,再考虑正确性,最后再考虑速度

Kent Beck的这句话是我最喜欢的名言之一(还有一句是Unix哲学)。我对软件开发业界缺乏务实的思想感到震惊。

人们过于强调空格与制表符、下划线分割与首字母大写以及接口的命名方式。

我没有说这些问题不重要,只不过我们首先需要保证程序能够运行,不是吗?

我最喜欢观察别人,每每看他们代码都没有通过编译,就加了大量的注释,也挺无语的。朋友,请务实。

22,花在社交媒体上的时间应该物有所值

只关注那些值得你花费时间的名人。我关注了Twitter上的很多名人,包括许多dev.to上的作家。

即使我并不经常使用推特,但我也喜欢读到不同的观点,因为他们能给我很好的视角。

23,勇敢发问

无论你在公司中处于哪个职位,即便你是CTO,也不会有人认为你理应掌握所有信息。相反,研究表明,人们更喜欢你向他们寻求帮助。

而且,计算机科学领域如此之大,没有人能无所不知。就像其他被堆积如山的案牍所累的行业一样。

24,原理和头脑风暴不能决定成败

能够决定成败的只有最终的结果。你已经离开了学校,没有人在乎你浪漫的编程方式,这些理论不适用于行业问题或假设。我并不是说它们并不重要,但是企业界更加重视结果,而不是抽象的讨论。

这是一个可悲的事实。如果你不喜欢,那么也许企业生活不适合你。最好还是找一处以研发为导向的科研或大学吧。

25,尝试软件行业之外的业余爱好

最近,我有点后悔没有遵循这条建议。

我并没有成功地在全职工作、健身训练和硕士学位之间建立平衡,如今正在想法修复。

这条建议可以帮助你避免过度疲劳,而且也能从不同的角度考虑事情。

例如,我见过许多文章探讨发散思想与乐器之间的联系。

26,不要在技术上刚愎自用

这就没必要解释了吧。由于某种原因,软件技术中有很多选择。不要鼓吹某个编程思想或技术,或将其视为唯一的解决方案。这种做法只会让你招人烦,或显得自己很无知。

27,切勿在办公室中触碰不能容忍的行为

性别歧视、种族主义、欺凌行为、反感自己的事业或者老板休假就拖欠工资(我就有过这样的经历)。有些公司的人认真、成熟且专业,他们懂得尊重别人。你需要找到这样的公司。让那些不懂得尊重别人的人们在他们狭小的圈子里寻找优越感和独特性吧。

28,单元测试很无聊,但是...

遇到产品快速增长或大规模重构即将来临等情况时,单元测试可以救你一命。

就像生活中的所有事物一样,只有辛勤播种,才有收获。

29,有效的时间/任务管理非常重要

有效的时间/任务管理与最新和最热门的技术同等重要,甚至比它们更重要。

为什么?因为如果你不能有效利用自己的技术按时提供价值,那么对别人来说你一文不值。如果你觉得自己这方面的能力有待培养,那么可以从《Getting Things Done》(简称GTD)和《15 secrets of time management》入手。

30,重视软技能

除非你是不需要公开竞标的自由职业者,或者将销售和客户处理工作委托他人,否则你会非常需要软技能。

我们每天都需要与人合作,我们需要知道如何与不同的人有效地沟通,并用对方听得懂的语言与之交谈。

如果所有人的软技能都不强,那可能也不会有问题,但是这就有点反乌托邦了,我还是希望你能务实一点,努力培养这方面的能力。

至于如何培养软技能,我推荐你可以从以下三本入手:

• 《Soft skills》

• 《How to win Friends & Influence People》

• 《The charisma myth》

总结

感谢您的阅读,希望你喜欢本文提到的技巧。如果你有其他可以分享的点,请在下方留言。

零基础学习Java编程可以加入我的十年Java学习园地

查看原文

赞 3 收藏 2 评论 1

令狐义卓 发布了文章 · 1月20日

[解锁新姿势] 兄dei,你代码需要优化了

前言

在我们平常开发过程中,由于项目时间紧张,代码可以用就好,往往会忽视代码的质量问题。甚至有些复制粘贴过来,不加以整理规范。往往导致项目后期难以维护,更别说后续接手项目的人。所以啊,我们要编写出优雅的代码,方便你我他,岂不美哉?

下面分享一些我在开发中常用的编码中小建议,如有不妥,欢迎大家一起交流学习。

卫语句

卫语句,就是把复杂的条件表达式拆分成多个条件表达式。比如 多个 if-elseif-else 嵌套, 可以拆分成多个 if。如下面代码

代码:


-------------------- before  --------------------

public void today() {
    if (isWeekend()) {
        if (isFee()) {
            System.out.println("study Android");
        } else {
            System.out.println("play a game");
        }
    } else {
        System.out.println("go to work");
    }
}


 -------------------- after  (建议) --------------------

public void today() {

    // 提前过滤掉`特殊情况`
    if (!isWeekend()) {
        System.out.println("go to work");
        return; // 提前return
    }

    //提前过滤掉`特殊情况`
    if (isFee()) {
        System.out.println("study Android");
        return; // 提前return
    }

    // 更关注于 `核心业务`代码实现。
    System.out.println("play a game");
}

小函数

我们平常开发的时候,应该编写小而美函数,避免函数过长。一般函数最好在15行以内(建议) 我们看看下面代码:


-------------------- before  --------------------

if (age > 0 && age < 18){
    System.out.println("小孩子");
}

if (number.length() == 11){
    System.out.println("符合手机号");
}

-------------------- after (建议) --------------------

private static boolean isChild(int age) {
    return age > 0 && age < 18;
}

private static boolean isPhoneNumber(String number) {
    return number.length() == 11;
}

if (isChild(age)){
    System.out.println("小孩子");
}

if (isPhoneNumber(number)){
    System.out.println("符合手机号");
}
把判断语句抽取成一个个小函数, 这样代码更加清晰明了。

迪米特法则

概念:

迪米特法则(Law of Demeter)又叫作最少知识原则(Least Knowledge Principle 简写LKP),就是说一个对象应当对其他对象有尽可能少了解。例如 当一条语句中 一个对象出现两个 .student.getName().equals("张三")) 就是代码坏味道的表现,如下代码所示。

代码:


-------------------- before  --------------------

public class Student {

    private String name;

    public Student(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

public static void main(String[] args) {

    Student student = new Student("张三");

    // 注意看这里,
    // 这里获取 student的name属性,在根据name属性进行判断
    if (StringUtils.isNotBlank(student.getName()) && student.getName().equals("张三")) {
        System.out.println("我的好朋友是 " + student.getName());
    }
}


 -------------------- after (建议) --------------------
 
 public class Student {

    ... 省略name代码

    // 新增一个 判断是否是我的好朋友方法
    public boolean isGoodFriend(){
        return StringUtils.isNotBlank(this.name) && this.name.equals("张三");
    }
}

public static void main(String[] args) {

    Student student = new Student("张三");

    // 根据迪米特法则,把判断逻辑,抽取到 Student 内部,暴露出方法(isGoodFriend)
    if (student.isGoodFriend()){
        System.out.println("我的好朋友是 " + student.getName());
    }
}
IDEA/Android Studio 抽取方法快捷键: option + command + M

Map 提取对象

我们在平常开发中,会使用到map,但是在面向对象开发理念中,一个 map的使用,往往就会错过了 Java Bean。建议使用 Java Bean 更直观。如下代码:

public static void main(String[] args) {

    -------------------- before  --------------------
        Map<String, String> studentMap = new HashMap<>();
        studentMap.put("张三", "男");
        studentMap.put("小红", "女");
        studentMap.put("李四", "男");

        studentMap.forEach((name, sex) -> {
            System.out.println(name + " : " + sex);
        });

    -------------------- after (建议)  --------------------
    
        List<Student> students = new ArrayList<>();
        students.add(new Student("张三", "男"));
        students.add(new Student("小红", "女"));
        students.add(new Student("李四", "男"));

        for (Student student : students) {
            System.out.println(student.getName() + ":" + student.getSex());
        }
    }

笔者在编写这点时候,有所顾虑。肯定有小伙伴跳出来说,mapbean 不是一样吗?用map 我还可以省去思考如何命名Class呢。但是从代码规范来说,这样代码设计不是更符合 Java 面向对象的思想吗?

Stream

Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。使得代码调用起来更加优雅~ 直接来看代码:

public static void main(String[] args) {
        List<Student> students = new ArrayList<>();
        students.add(new Student("张三", "男"));
        students.add(new Student("李四", "男"));
        students.add(new Student("小红", "女"));
        students.add(new Student("小花", "女"));
        students.add(new Student("小红", "女"));
        
        -------------------- before  --------------------
        //统计男生个数
        //传统的 for each 循环遍历
        long boyCount = 0;
        for (Student student : students) {
            if (student.isBoy()) {
                boyCount++;
            }
        }

        System.out.println("男生个数 = " + boyCount);

        -------------------- after (建议)  --------------------

        //统计男生个数
        //stream 流遍历
        long count = students.stream()
                .filter(Student::isBoy) // 等同于.filter(student -> student.isBoy())
                .count();
                
        System.out.println("男生个数 = " + boyCount);
}

相比与 传统的 For 循环,更推荐大家使用 stream 遍历。 stream 流的链式调用,还有许多骚操作,如 sorted, map, collect等操作符,可以省去不必要if-elsecount等判断逻辑。

多态

Java 三大特性之一,多态,相信大家都不会陌生,多态的好处就是根据对象不同类型采取不同的的行为。我们常常在编写 switch 语句的时候,如果改用多态,可以把每个分支,抽取到一个子类内的覆写函数中,这就更加灵活。

我们有这样一个需求,编写一个简单计算器方法,我们先来看一小段代码:


    -------------------- before  --------------------
    public static int getResult(int numberA, int numberB, String operate) {
        int result = 0;
        switch (operate) {
            case "+":
                result = numberA + numberB;
                break;
            case "-":
                result = numberA - numberB;
                break;
            case "*":
                result = numberA * numberB;
                break;
            case "/":
                result = numberA / numberB;
                break;
        }
        return result;
    }
    
    -------------------- after (建议)  --------------------
    
    abstract class Operate {
        abstract int compute(int numberA, int numberB);
    }
    
    class AddOperate extends Operate {

        @Override
        int compute(int numberA, int numberB) {
            // TODO 在这里处理相关逻辑
            return numberA + numberB;
        }
    }
    
    ... SubOperate, MulOperate, DivOperate 也和 AddOperate一样这里就不一一贴出
    
    public static int getResult(int numberA, int numberB, String operate) {
        int result = 0;
        switch (operate) {
            case "+":
                result = new AddOperate().compute(numberA, numberB);
                break;
            case "-":
                result = new SubOperate().compute(numberA, numberB);
                break;
            case "*":
                result = new MulOperate().compute(numberA, numberB);
                break;
            case "/":
                result = new DivOperate().compute(numberA, numberB);
                break;
        }
        return result;
    }

有小伙伴可能会说,你这不是更复杂了吗?

对比起单纯的switch,我们可以这样理解:

  • 虽然在类上有所增加,但是通过多态,把对应操作的逻辑分离出来,使得代码耦合度降低。
  • 如果要修改对应加法的逻辑, 我们只需要修改对应 AddOperate类就可以了。避免直接修改getResult 方法
  • 代码可读性更好,语义更加明确。

但是这里会存在一些问题,如果我们新增一个平方根平方等计算方式, 就需要修改 switch 里面的逻辑,新增一个条件分支。下面我们再来看看更进一步的优化。

反射

通过上面例子,我们可以进一步优化,通过反射生成对应的 Class,然后在调用compute方法。如下代码:

public static <T extends Operate> int getResult(int numberA, int numberB, Class<T> clz) {
        int result = 0;
        try {
            return clz.newInstance().compute(numberA, numberB);
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
            return result;
        }
}


public static void main(String[] args) {
    // 调用的时候直接传递 class 即可
    System.out.println(getResult(1, 2, SumOpearte.class));
}

根据传入 class 参数,然后生成对应 Opearte处理类, 对比多态方式,我们这里采用反射,使得代码耦合度大大降低,如果在增加平方根平方等计算方式。我们只需要 新增一个 class 继承 Opearte 即可,getResult 不用做任何修改。

需要注意的是,不是所有switch语句都需要这样替换, 在面对简单的 switch语句,就不必要了, 避免过度设计的嫌疑。如下代码:
public String getResult(int typeCode) {
        String type = "";
        switch (typeCode) {
            case 0:
                type = "加法";
                break;
            case 1:
                type = "减法";
                break;
            case 2:
                type = "乘法";
                break;
            case 3:
                type = "除法";
                break;
        }
        return type;
}

最后

以上就是我在编码上的一些小建议,如有不妥,欢迎大家加入我的十年Java学习园地一起交流学习。

查看原文

赞 0 收藏 0 评论 0

令狐义卓 发布了文章 · 1月20日

java:源码解读String类的不可变特性

String类不可变的含义

String对象创建之后便不会再改变,任何看起来的变化都是通过创建新的String对象来完成的。

举例:

String a = new String("abc");
a = a + "d";

第一个语句创建了一个String 对象abc,a是指向这个对象的引用

第二个语句右边创建了另外一个String对象abcd;

执行第二个语句时,并不会修改原先的对象abc;
image.png

不可变是如何实现的

这里有三个关键点:

1、String类被final修饰,不可被继承;因为一旦允许继承的化,那么方法就有可能被重写,也就有可能会破坏不可变性,这就是为什么用final修饰的原因;

2、private final修饰char[] 数组;字符串底层使用字符数组来存储,这个字符数组通过private final修饰,防止外部对字符串做出改变;

3、String类种的任何方法都不会对字符串进行改动;
image.png

为什么设计为不可变的

主要还是为了性能方面的考虑,因为在java语言设计之初,就认为String将会被频繁的使用,所以设定了常量池,目的是为了尽可能的复用已有对象,这就要求已有对象是不可变的;
image.png

当然设计成不可变对象,一定程度上也可以增加代码的安全性,比如可变对象作为hashMap的key时,如果先放入map之后,再改变对象,那么可能就会破坏Map对key的唯一性要求;

零基础学习Java编程,更多Java技巧可以加入我的十年Java学习园地,技术交流,资源共享,问题答疑,开发经验分享。

查看原文

赞 0 收藏 0 评论 0

令狐义卓 发布了文章 · 1月19日

数学,离一个程序员有多近?

一、前言

数学离程序员有多近?

ifelse也好、for循环也罢,代码可以说就是对数学逻辑的具体实现。所以敲代码的程序员几乎就离不开数学,难易不同而已。

那数学不好就写不了代码吗😳?不,一样可以写代码,可以写出更多的CRUD出来。那你不要总觉得是产品需求简单所以你的实现过程才变成了增删改查,往往也是因为你还不具备可扩展、易维护、高性能的代码实现方案落地能力,才使得你小小年纪写出了更多的CRUD

与一锥子买卖的小作坊相比,大厂和超级大厂更会注重数学能力。
image.png
2004年,在硅谷的交通动脉 101 公路上突然出现一块巨大的广告牌,上面是一道数学题:{e 的连续数字中最先出现的 10 位质数}.com。

广告:这里的 e 是数学常数,自然对数的底数,无限不循环小数。这道题的意思就是,找出 e 中最先出现的 10 位质数,然后可以得出一个网址。进入这个网址会看到 Google 为你出的第二道数学题,成功解锁这步 Google 会告诉你,我们或许是”志同道合“的人,你可以将简历发到这个邮箱,我们一起做点改变世界的事情。

计算 e 值可以通过泰勒公式推导出来:e^x≈1 + x + x^2/2! + x^3/3! +……+ x^n/n! (1) 推导计算过程还包括埃拉托色尼筛选法(the Sieve of Eratosthenes)线性筛选法的使用。感兴趣的小伙伴可以用代码实现下。

二、把代码写好的四步

业务提需求、产品定方案、研发做实现。最终这个系统开发的怎么样是由三方共同决定的!

  • 地基挖的不好,楼就盖不高
  • 砖头摆放不巧,楼就容易倒
  • 水电走线不妙,楼就危险了
  • 格局设计不行,楼就卖不掉

这里的地基、砖头、水电、格局,对应的就是,数据结构、算法逻辑、设计模式、系统架构。从下到上相互依赖、相互配合,只有这一层做好,下一层才好做!
image.png

  • 数据结构:高矮胖瘦、长宽扁细,数据的存放方式,是一套程序开发的核心基础。不合理的设计往往是从数据结构开始,哪怕你仅仅是使用数据库存放业务信息,也一样会影响到将来各类数据的查询、汇总等实现逻辑的难易。
  • 算法逻辑:是对数据结构的使用,合适的数据结构会让算法实现过程降低时间复杂度。可能你现在的多层for循环在合适的算法过程下,能被优化为更简单的方式获取数据。_注意:算法逻辑实现,并不一定就是排序、归并,还有你实际业务的处理流程。_
  • 设计模式:可以这么说,不使用设计模式你一样能写代码。但你愿意看到满屏幕的ifelse判断调用,还是喜欢像膏药一样的代码,粘贴来复制去?那么设计模式这套通用场景的解决方案,就是为你剔除掉代码实现过程中的恶心部分,让整套程序更加易维护、易扩展。_就是开发完一个月,你看它你还认识!_
  • 系统架构:描述的是三层MVC,还是四层DDD。我对这个的理解就是家里的三居还是四局格局,MVC是我们经常用的大家都熟悉,DDD无非就是家里多了个书房,把各自属于哪一个屋子的摆件规整到各自屋子里。_那么乱放是什么效果呢,就是自动洗屁屁马桶🚽给按到厨房了,再贵也格楞子!_ 好,那么我们在延展下,如果你的卫生间没有流出下水道咋办?是不这个地方的数据结构就是设计缺失的,而到后面再想扩展就难了吧!

所以,研发在承接业务需求、实现产品方案的时候。压根就不只是在一个房子的三居或者四居格局里,开始随意码砖。

没有合理的数据结构、没有优化的算法逻辑、没有运用的设计模式,最终都会影响到整个系统架构变得臃肿不堪,调用混乱。在以后附加、迭代、新增的需求下,会让整个系统问题不断的放大,当你想用重构时,就有着千丝万缕般调用关系。 重构就不如重写了!

三、for循环没算法快

在《编程之美》一书中,有这样一道题。求:1n中,1出现的次数。比如:110,1出现了两次。

1. for 循环实现

long startTime = System.currentTimeMillis();
int count = 0;
for (int i = 1; i <= 10000000; i++) {
    String str = String.valueOf(i);
    for (int j = 0; j < str.length(); j++) {
        if (str.charAt(j) == 49) {
            count++;
        }
    }
}
System.out.println("1的个数:" + count);
System.out.println("计算耗时:" + (System.currentTimeMillis() - startTime) + "毫秒");

使用 for 循环的实现过程很好理解,就是往死了循环。之后把循环到的数字按照字符串拆解,判断每一位是不是数字,是就+1。这个过程很简单,但是时间复杂很高。

2. 算法逻辑实现

image.png
如图 20-3 所示,其实我们能发现这个1的个数在100、1000、10000中是有规则的循环出现的。11、12、13、14或者21、31、41、51,以及单个的1出现。最终可以得出通用公式:abcd...=(abc+1)*1+(ab+1)*10+(a+1)*100+(1)*1000...,abcd代表位数。另外在实现的过程还需要考虑比如不足100等情况,例如98、1232等。

实现过程

long startTime = System.currentTimeMillis();
int num = 10000000, saveNum = 1, countNum = 0, lastNum = 0;
int copyNum = num;
while (num != 0) {
    lastNum = num % 10;
    num /= 10;
    if (lastNum == 0) {
        // 如果是0那么正好是少了一次所以num不加1了
        countNum += num * saveNum;
    } else if (lastNum == 1) {
        // 如果是1说明当前数内少了一次所以num不加1,而且当前1所在位置
        // 有1的个数,就是去除当前1最高位,剩下位数,的个数。
        countNum += num * saveNum + copyNum % saveNum + 1;
    } else {
        // 如果非1非0.直接用公式计算
        // abcd...=(abc+1)*1+(ab+1)*10+(a+1)*100+(1)*1000...
        countNum += (num + 1) * saveNum;
    }
    saveNum *= 10;
}
System.out.println("1的个数:" + countNum);
System.out.println("计算耗时:" + (System.currentTimeMillis() - startTime) + "毫秒");

在《编程之美》一书中还不只这一种算法,感兴趣的小伙伴可以查阅。_但自己折腾实现后的兴奋感更强哦!_

3. 耗时曲线对比

按照两种不同方式的实现逻辑,我们来计算1000、10000、10000到一个亿,求1出现的次数,看看两种方式的耗时曲线。
image.png

  • for循环随着数量的不断增大后,已经趋近于无法使用了。
  • 算法逻辑依靠的计算公式,所以无论增加多少基本都会在1~2毫秒内计算完成。

那么,你的代码中是否也有类似的地方。如果使用算法逻辑配合适合的数据结构,是否可以替代一些for循环的计算方式,来使整个实现过程的时间复杂度降低。

四、Java中的算法运用

在 Java 的 JDK 实现中有很多数学知识的运用,包括数组、链表、红黑树的数据结构以及相应的实现类ArrayList、Linkedlist、HashMap等。当你深入的了解这些类的实现后,会发现它们其实就是使用代码来实现数学逻辑而已。_就像你使用数学公式来计算数学题一样_

接下来小傅哥就给你介绍几个隐藏在我们代码中的数学知识。

1. HashMap的扰动函数

未使用扰动函数
image.png
已使用扰动函数
image.png
扰动函数公式

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
  • 描述:以上这段代码是HashMap中用于获取hash值的扰动函数实现代码。_HashMap通过哈希值与桶定位坐标_ 那么直接获取哈希值就好了,这里为什么要做一次扰动呢?
  • 作用:为了证明扰动函数的作用,这里选取了10万单词计算哈希值分布在128个格子里。之后把这128个格子中的数据做图表展示。从实现数据可以看到,在使用扰动函数后,曲线更加平稳了。那么,也就是扰动后哈希碰撞会更小。
  • 用途:当你有需要把数据散列分散到不同格子或者空间时,又不希望有太严重的碰撞,那么使用扰动函数就非常有必要了。比如你做的一个数据库路由,在分库分表时也是尽可能的要做到散列的。

2. 斐波那契(Fibonacci)散列法

image.png

  • 描述:在 ThreadLocal 类中的数据存放,使用的是斐波那契(Fibonacci)散列法 + 开放寻址。之所以使用斐波那契数列,是为了让数据更加散列,减少哈希碰撞。具体来自数学公式的计算求值,公式f(k) = ((k * 2654435769) >> X) << Y对于常见的32位整数而言,也就是 f(k) = (k * 2654435769) >> 28
  • 作用:与 HashMap 相比,ThreadLocal的数据结构只有数组,并没有链表和红黑树部分。而且经过我们测试验证,斐波那契散列的效果更好,也更适合 ThreadLocal。
  • 用途:如果你的代码逻辑中需要存储类似 ThreadLocal 的数据结构,又不想有严重哈希碰撞,那么就可以使用 斐波那契(Fibonacci)散列法。其实除此之外还有,除法散列法平方散列法随机数法等。

3. 梅森旋转算法(Mersenne twister)

image.png

// Initializes mt[N] with a simple integer seed. This method is
// required as part of the Mersenne Twister algorithm but need
// not be made public.
private final void setSeed(int seed) {
    // Annoying runtime check for initialisation of internal data
    // caused by java.util.Random invoking setSeed() during init.
    // This is unavoidable because no fields in our instance will
    // have been initialised at this point, not even if the code
    // were placed at the declaration of the member variable.
    if (mt == null) mt = new int[N];
    // ---- Begin Mersenne Twister Algorithm ----
    mt[0] = seed;
    for (mti = 1; mti < N; mti++) {
        mt[mti] = (MAGIC_FACTOR1 * (mt[mti-1] ^ (mt[mti-1] >>> 30)) + mti);
    }
    // ---- End Mersenne Twister Algorithm ----
}
梅森旋转算法(Mersenne twister)是一个伪随机数发生算法。由松本真和西村拓士在1997年开发,基于有限二进制字段上的矩阵线性递归。可以快速产生高质量的伪随机数,修正了古典随机数发生算法的很多缺陷。 最为广泛使用Mersenne Twister的一种变体是MT19937,可以产生32位整数序列。
  • 描述:梅森旋转算法分为三个阶段,获得基础的梅森旋转链、对于旋转链进行旋转算法、对于旋转算法所得的结果进行处理。
  • 用途:梅森旋转算法是R、Python、Ruby、IDL、Free Pascal、PHP、Maple、Matlab、GNU多重精度运算库和GSL的默认伪随机数产生器。从C++11开始,C++也可以使用这种算法。在Boost C++,Glib和NAG数值库中,作为插件提供。

五、程序员数学入门

与接触到一个有难度的知识点学起来辛苦相比,是自己不知道自己不会什么!_就像上学时候老师说,你不会的就问我。我不会啥?我从哪问?一样一样的!_

代码是对数学逻辑的实现,简单的逻辑调用关系是很容易看明白的。但还有那部分你可能不知道的数学逻辑时,就很难看懂了。比如:扰动函数、负载因子、斐波那契(Fibonacci)等,这些知识点的学习都需要对数学知识进行验证,否则也就学个概念,背个理论。

书到用时方恨少,在下还是个宝宝!

那如果你想深入的学习下程序员应该会的数学,推荐给你一位科技博主 Jeremy Kun 花了4年时间,写成一本书 《程序员数学入门》
image.png
这本书为程序员提供了大量精简后数学知识,包括:多项式、集合、图论、群论、微积分和线性代数等。同时在wiki部分还包括了抽象代数、离散数学、傅里叶分析和拓扑学等。
image.png
作者表示,如果你本科学过一些数学知识,那么本书还是挺适合你的,不会有什么难度。书中的前三章是基础数学内容,往后的难度依次递增。

六、总结

  • 单纯的只会数学写不了代码,能写代码的不懂数学只能是CRUD码农。数学知识帮助你设计数据结构和实现算法逻辑,代码能力帮你驾驭设计模式和架构模型。多方面的知识结合和使用才是码农和工程师的主要区别,也是是否拥有核心竞争力的关键点。
  • 学习知识有时候看不到前面的路有多远,但哪怕是个泥坑,只要你不停的蠕动、折腾、翻滚,也能抓出一条泥鳅。知识的路上是发现知识的快乐,还学会知识的成就感,不断的促使你前行

零基础学习Java编程,可以加入我的十年Java学习园地,技术交流,资源共享,开发经验总结。

查看原文

赞 0 收藏 0 评论 0

令狐义卓 发布了文章 · 1月19日

一文读懂程序员如何从初级升级到高级

高级程序员肯定是一部分同学的梦想,跃哥最近经常被群友询问,说如何才能进大厂,如何才能和我一样优秀,如何才能面面俱到等等这些话题,也写了一部分自己的文。那么,这些都是在我眼里的所见所谓,对于远在大洋彼岸的国外程序员,他们又是如何理解,甚至是如何走从初级到高级之路的呢。

就这样,今天这篇文章应运而生。欧阳同学通过自己的资源,找到了这篇名为《How to Go from Junior to Senior Programmer - Level Up Coding》 的文章,翻译成中文就是《如何从初级程序员到高级程序员》,这不就是程序员成长之路的海外版本的解读吗?习惯了我们国内的 style,跃哥带你一同感受下他们的文风。

因为水平有限,有部分解释的不是很清楚,还请大家见谅,想要阅读原文, 可以联系我。文中有部分产生了共鸣,我有添加自己的语录,以后我都会尽量有一个“跃哥语录”的模块,希望大家喜欢,哈哈哈哈。

如何从初级程序员升级到高级

高级程序员是一个专家,他犯了所有在他领域可能犯的错误。

作者:Ravi Shankar Rajan

程序员可以按经验级别来分类,大致可以像如下这几类:

  • 初级:2-3 年的经验
  • 高级:10 年以上经验
  • 中级或“中级”:处在初级和高级之间

多年工作经验真的是一个难题。它并没有提到软件开发的质量问题。这几年你积累了多少经验和技能呢?这就是为什么针对开发人员的求职面试是如此复杂的原因。这是一项很难衡量的技能,因此我们最终在面试中对开发人员进行了很困难的测试。但是这些测试其实也就是一个近似值,无法衡量做这份工作所需的工作或专业知识。

进而引出了下一个问题。

一旦你不再是一个初级工程师,你什么时候能成为高级工程师呢?

多年的工作经验会自动让你成长为高级吗?不存在的。

以我的情况为例。当我还是一个年轻的初级软件工程师时,我以为我什么都知道。 我很粗鲁,自大和自信。我认为自己是“编码的王者”。我不喜欢与他人合作,我认为编写出色的代码是唯一重要的事情。

我发现我错了。是的,编码很重要。归根结底,程序员必须编写代码。但是编写代码并不是唯一重要的事情。

当我为第一个客户工作时,我很难学到这一点。我跟客户花了“15”分钟,收集了需求的“要点”,并假设我明确了解客户的需求。我开始像个疯子一样疯狂的写代码,享受编写代码的行为。我在3天的时间内提交了申请然而被客户立马拒绝。因为那不是他所想要的。

结果不用说,我很忧桑。我的自尊受到伤害,而且我还责怪客户没有提供足够的信息。我当时太年轻了,其实客户永远是对的。如果我花更多的时间分析客户的需求,情况可能会大不相同。我学这个很难,很难学会。

也就是说,程序员不仅仅是程序员,因为他会编程。他是个程序员,因为他的工作是在开始任何事情之前分析所有的事情。必须在多个层次上进行分析。

  • 自我分析,以此来提高
  • 分析客户需求以提供更好的服务
  • 分析整个项目,以帮助每个人表现更好

如果您想从初级到高级开发人员,则需要培养这些分析技能,使之蜕变成一个真正优秀的高级开发人员,他以专业知识而闻名,而不是多年的经验。

一个优秀的高级开发人员就像一个已经长大的成年人,可以照顾自己的人。他的生活不再是不稳定的,自发的和实验性的。他从错误中吸取教训,在生活中创造了坚实的专业基础,他可以回过头来为之骄傲。他还可以“年轻”,但他所拥有的是丰富的实用主义和有效性,远比他多年的实际经验更有价值。

下面是一些从初级程序员蜕变到高级程序员的方法

克服 Dunning-Kruger 效应

Dunning-Kruger 效应是一种认知偏见,人们认为自己比实际更聪明、更有能力。从本质上讲,低能力的人不具备识别自身能力不足所需的技能,这导致他们高估自己的能力。

作为一个初级程序员,这无疑是一个灾难的处方。你可能认为你是一个摇滚明星程序员,什么都知道,但事实是你知道的很少,仍然远远没有达到卓越。这是一个陷阱,你需要避免陷入进入。(这里还推荐一本书,叫《能力陷阱》

初级程序员和高级程序员的区别在于,初级程序员认为自己什么都知道,高级程序员知道自己还有很多东西要学。初级程序员往往高估自己的知识和能力,无法认识到其他人的技能和能力水平,这就是为什么他们总是认为自己比别人更有能力,更有知识。

正如 David Dunning 所说的那样:

“在许多情况下,没能力不会让人迷失方向、困惑或谨慎。相反,不称职的人往往被一种不适当的自信所鼓舞,这种自信在他们看来是知识。”

Dunning 和 Kruger 认为,随着工作经验的增加,过度自信通常下降到更现实的水平。随着程序员开始深入学习,他们意识到自己缺乏知识和能力。随着他们不断地获取知识,他们的专业知识不断增强,信心水平开始再次提高。

他们提出了以下克服过度自信的方法。

  • 不断学习和练习。一旦你对某个主题有了更深刻的了解,你就会认识到仍有很多东西需要学习。即使你不是专家,这也能阻止你认为自己是专家的倾向。
  • 问问别人做的怎么样。向别人提出建设性的意见可以为别人如何看待你的能力提供有价值的见解。
  • 询问你所知道的。不断挑战你的信念和期望。寻找挑战你想法的信息。你的问题越多,你就学到更多。

记住,知根知底的感觉是愉快的。但你需要不断提高自己的标准。为此,你需要更深入地挖掘,以便更好地理解一个特定的主题。它让你认识到还有多少东西要学。

知道什么时候不该做什么

Mark Manson 在《The Subtle Art of Not Giving a Fuck》一书中谈到了保持由尽可能少的定义身份的重要性。这是因为当我们涉及到我们的身份时——当我们决定某些行为或事件代表我们作为一个人的价值时。

简单地说,我们经常决定做一些事情是基于它如何满足我们的自我或孩子气的兴奋感,而不是真正需要做同样的事情。Manson 告诉我们,最好的决定是当我们把“自我”排除在决定之外时做出的,因为这是最有可能,不是“你”的问题。简单的问问自己,“这是件好事吗?”是的“?那就勇敢去做吧。

这也适用于程序员。事实上,大多数程序员天生就像喜鹊一样,总是收集闪亮的东西,把它们存储起来,寻找联系。如果你不知道这一现象,闪亮玩具综合症的典型特征是想要拥有最新的玩具,通常不考虑实际或功能的需要,或者在转移到其他东西之前被强烈但非常短暂的所有权所吸引。

如果你的目标是成为一名高级程序员,你需要不惜任何代价避免这种疾病。更好的高级程序员知道什么时候不做什么。他们知道从头开始重写一个库只是为了使它更可读,或者从旧的框架切换到最新的框架并不总是好的决定。代码的目的应该足够清楚,以便在几分钟甚至几秒钟内掌握。浏览代码应该很容易,即使没有复杂的技巧。

关键不是要规避风险,而要谨慎选择正确的战斗。

充满好奇

您是否想知道“application”一词是什么意思?

为什么我们在智能手机中称这些小图标为applications?因为他们将给定的工作流程或算法应用于存在的问题,并帮助我们解决我们的需求。

也就是说,如果你要构建某些东西,那么你肯定会犯错误。反思你的工作并不断地改进他会促使创新,而创新的根源在于好奇心,去发现事物是如何工作的。记住,这是在自我完善的整个周期中一个重要的阻碍。

错误->见解->好奇心->创新->错误……。重复……

如果你想继续前进并成为一名优秀的高级程序员,那么你需要有疯狂的好奇心去投入到你所做的每一件事中。好奇心是一种工具,你用得越多越好,这正是人们对一个优秀的高级程序员的期望。优秀的高级开发人员以结构化的方式引导他们的好奇心,这样他们就可以使用在危机情况下积累的信息。

您可以通过以下几种方式来激发好奇心并树立你的品牌。

  • 持续学习。选择一门课程,一本书,一个在线课程,并利用它来丰富你已拥有的想法,并获得新的想法。
  • 聚焦基础知识。确保你了解它们的工作原理,这样你就可以把它作为你工作的基础。
  • 不要说出你的想法,请展示你的项目。无论如何,想法都被高估了。当你的想法被使用和传播时,你可以建立你的品牌。
  • 在新的和既定的想法之间取得平衡。不要盲目地接受你“应该”知道的东西。挑战现状。
  • 不仅仅是使它起作用。使其可扩展、可重用和可插入。这就是你建立专业知识的方式。

一切都始于好奇。如果你不好奇的话,你最好选择退出。正如 Albert Einstein 所说:

"I hava no special talent. I am only passionately curious."

“我没有特别的天赋。我仅仅是出于好奇。”

结语

跃哥在这里还安利大家一个知识点,叫做:四象限法则,是美国管理学大师史蒂芬·柯维提出,用户时间管理的基础理论。他用重要和急迫两个维度,将事项分为四个象限:重要且紧急,重要但不紧急,不重要但紧急,不重要且不紧急。这里衍生出了很多其他的四象限,比如知识四象限,知识自己知道,知道自己不知道,不知道自己知道,不知道自己不知道

利用这个四象限法则,可以根据自己的实际情况来很好的分析当下比较重要的事情,也能很好的分析自己所处的环境,把自己的优先级分析出来,把自己的知识点分析出来,方便自己对症下药以便更进一步的学习,是不是很赞?

不要问跃哥为什么知道的这么多,不要问跃哥为什么在文中有这么多感慨,因为我今年读的书变多了,无论从获取知识的角度还是消化的角度都有了一个长足的进步,可惜从金钱的角度来看,还任重道远,这也是比较尴尬的地方,也是我还要继续努力的地方。

师傅领进门,修行在个人。最近分享了很多关于校招、面试、成长、翻译的文章,你不需要每篇都看,但请你挑选适合自己的好好看,因为我们都还在一个有无限进步的空间里生存,我们需要进步的点还有很多。

零基础学习Java编程,可以加入我的十年Java学习园地

查看原文

赞 0 收藏 0 评论 0

认证与成就

  • 获得 12 次点赞
  • 获得 1 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 1 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2020-11-05
个人主页被 2k 人浏览