令狐义卓

令狐义卓 查看完整档案

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

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

个人动态

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

零基础学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

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

谈一谈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

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

java8:stream特性详解,原来如此强大

首先谈谈Stream的概念以及Java为什么引入Stream

Stream是JAVA8引入的重要特性之一,它是对数据源的一个封装,通过这个封装对象可以对数据源进行处理,Stream本身并不是数据存储容器,跟数据结构也没有直接关系。

为什么要引入Stream呢?没有Stream的JAVA7以及之前的版本,也一样可以处理数据啊?

这里要提到“函数式”编程了,熟悉python和Scala的朋友可能对函数式编程有一定的认知,函数式编程的方式在数据处理上非常的高效,java8 在设计的时候应该是重点考虑了支持函数式编程模式,Stream仅仅是其特性之一,像lamda表达式、函数式接口等特性也都是为函数式编程服务的。

如何构造Stream

  1. Stream.of 方法
  2. List对象可以直接获取到Stream;如:stringList.stream()
  3. Stream.builder().build(); 方式

如何利用stream来高效处理数据

1.数据遍历并处理 forEach方法

String[] strs = {"a", "b", "c", "d"};

Stream<String> stringStream = Stream.of(strs);

stringStream.forEach(s -> System.out.println(s));

2. 数据转换 map方法

面向函数式编程,一般倾向于数据对象是不可变的,当需要改变的时候可以生成新的对象,map就是完成对象的映射;比如将小写字母转为大写字母;

List<String> upperCase = stringStream.map(s -> s.toUpperCase()).collect(Collectors.toList());

3. 数据过滤 filter方法

java8:stream特性详解,原来如此强大

stream 的filter方法示例

4. findFirst 找到第一个元素

java8:stream特性详解,原来如此强大

findFirst方法示例

5. Stream 转数组

String[] upperCase = stringStream.map(s -> s.toUpperCase()).toArray(String[]::new);

6. flatMap 处理

7.peek处理

peek也是循环遍历,与forEach的不同之处在于遍历完之后还会返回原有的Stream本身。forEach没有返回;

java8:stream特性详解,原来如此强大

stream peek方法示例

  1. 其他方法:

.count方法、.skip 方法 .limit 方法 .sort方法 .max求最大值 .min 求最小值 .distinct() 去重方法;

元素匹配判断:allMatch, anyMatch, and noneMatch

Stream可以转为set或map,与转List的方式类似,通过Collectors提供的toSet、toMap方法

reduce方法

partitioningBy方法

groupingBy方法

更多使用案例可以关注并私信我来获取。

其他特性

  1. 惰性求值
  2. 支持是否选择并发计算,通过.parallel()方法实现

java8:stream特性详解,原来如此强大

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

查看原文

赞 0 收藏 0 评论 0

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

Java中不可或缺的59个小技巧,贼好用!

《Effective JavaJava》名著,必读。如果能严格遵从本文的原则,以编写API的质量来苛求自己的代码,会大大提升编码素质。

以下内容只记录了我自己整理的东西,还是建议读原文。为了聚焦知识点,一些说明故意忽略掉了。相当于是一篇摘要。

1、考虑用静态工厂方法替代构造函数

例子:

Integer.valueOf(“1”)、Boolean.valueOf(“true”)等。

优势:

  • 可读性高(方法名)
  • 性能(不一定创建对象)
  • 灵活性高

下面针对三个优势进行一些解读。

可读性高

new Point(x,y)和Point.at(x,y)、Point.origin()。构造函数只能看出两个参数,不知其意,后者更易理解。

性能

在某些情况下,可以事先进行实例化一些对象,调用时直接调用即可,不需要进行改变。比如,Boolean。

public final class Boolean implements Serializable, Comparable<Boolean> {
    // 预先设置两个对象
    public static final Boolean TRUE = new Boolean(true);
    public static final Boolean FALSE = new Boolean(false);
 
    public Boolean(boolean var1) {
        this.value = var1;
    }
 
    public Boolean(String var1) {
        this(parseBoolean(var1));
    }
 
    // 工厂方法
    public static Boolean valueOf(boolean var0) {
        return var0?TRUE:FALSE;    // 返回预先设置的对象,而不是创建对象
    }
    // 工厂方法
    public static Boolean valueOf(String var0) {
        return parseBoolean(var0)?TRUE:FALSE;
    }
    // ... other code
}

灵活性高

可根据具体情况,返回子类。相当于更强大的工厂。直接从父类获取到子类。尤其适用于工具类(提供各种API)。例子:Collections。

public class Collections {
    // 私有,典型工厂
    private Collections() {
    }
 
    public static final List EMPTY_LIST = new EmptyList<>();
    // 工厂方法
    public static final <T> List<T> emptyList() {
        return (List<T>) EMPTY_LIST;
    }
    private static class EmptyList<E> extends AbstractList<E> implements RandomAccess, Serializable {
    // code
    }
 
    // 工厂方法
    public static <E> List<E> checkedList(List<E> list, Class<E> type) {
    // 根据具体情况,获取相应子类
        return (list instanceof RandomAccess ?
                new CheckedRandomAccessList<>(list, type) :
                new CheckedList<>(list, type));
    }
 
    // 子类1
    static class CheckedRandomAccessList<E> extends CheckedList<E> implements RandomAccess {
        CheckedRandomAccessList(List<E> list, Class<E> type) {
            super(list, type);
        }
 
        public List<E> subList(int fromIndex, int toIndex) {
            return new CheckedRandomAccessList<>(
                    list.subList(fromIndex, toIndex), type);
        }
    }
 
    // 子类2
    static class CheckedList<E> extends CheckedCollection<E> implements List<E> {
    // code
    }
}

2、多个构造函数时,考虑使用构造器

尤其在进行Android开发时,会碰到这种情况。通常是一个对象,具有多个成员变量可能需要初始化,常规方法,需要提供大量构造函数。例如:

// 非Android中的AlertDialog,便于说明问题,举个例子
public class AlertDialog {
    private int width;
    private int height;
    private String title;
    private String confirmText;
    private String denyText;
 
    private AlertDialog(){}
    public AlertDialog(int width, int height){    // 空白的警告框
         AlertDialog(width,height,null);
    }
 
    // 带标题的警告框
    public AlertDialog(int width, int height, String title){    // 带标题的警告框
        AlertDialog(width, height, title, "确定");
    }
 
    // 带标题的警告框,有确定按钮
    public AlertDialog(int width, int height, String title, String confirm){   
        AlertDialog(width, height, title, confirm, null);
    }
 
    // 带标题的警告框,有确定按钮,取消按钮
    public AlertDialog(int width, int height, String title, String confirm, String denyText){
        // set every thing.
    }
}

有多种样式的警告框,为了调用方便,必须提供多个构造函数。否则用户在调用时,只能使用完整构造函数,容易犯错且无法进行阅读。极不灵活。如果采用另外一种方式,则可以解决,但会花费很多经历处理并发的情况:

 // 非Android中的AlertDialog,便于说明问题,举个例子
public class AlertDialog {
    private int width;
    private int height;
    private String title;
    private String confirmText;
    private String denyText;
 
    public AlertDialog(){}// 空白的构造函数
   
    public void setWidth(int width){
        this.width = width;
    }
    // 其他set方法
}

调用时,通过调用各个参数的set方法进行设置。问题来了:

  1. 并发
  2. 无法进行参数校验。例如,只创建了对象,设置了标题,却没有尺寸,相当于创建了一个没有尺寸的警告框。

在Android中,大量的控件都使用了构造器Builder。

// 非Android中的AlertDialog,便于说明问题,举个例子
public class AlertDialog {
    private int width;
    private int height;
    private String title;
    private String confirmText;
    private String denyText;
 
    // private
    private AlertDialog(){}
 
    // Builder中使用
    protected AlertDialog(Builder b){
        width = b.width;
        height = b.height;
        // .....
        if(width==0||height==0) throws new Exception("size must be set");
    }
 
    // 构造器
    public static class Builder {
        private int width;
        private int height;
        private String title;
        private String confirmText;
        private String denyText;
 
        // 注意:返回的Builder。
        public Builder setTitle(String title) {
            this.title = title;
            return this;
        }
        // 其他set...
        
        public AlertDialog build(){
            return AlertDialog(this);
        }
    }
}

于是,可以根据相应需求,进行相应设置,并在AlertDialog真正构造时,进行参数校验。就像这样:

new AlertDialog.Builder().setTitle("提示").build();

上述例子,会成功抛出异常。

3、用私有化构造器或者枚举型强化Singleton。

Singleton指最多会被实例化一次的类。通常情况下,以前的做法是没有问题的。但是在某些高级情况,通过使用反射的相关知识访问private的构造函数,破坏Singleton。

public class Elvis{
    // 注意,公有final对象
    public static final Elvis INSTANCE = new Elvis();
    private Elvis(){}
}

另一种情况,在序列化的过程中,反序列化得到的对象已经不再是以前的对象(破坏了Singleton),这种情况下,可以通过单元素枚举型处理。

public enum Elvis{
    INSTANCE;
    // some methods
}

4、通过私有化构造器强化不可实例化的能力

有一些工具类,仅仅是提供一些能力,自己本身不具备任何属性,所以,不适合提供构造函数。然而,缺失构造函数编译器会自动添加上一个无参的构造器。所以,需要提供一个私有化的构造函数。为了防止在类内部误用,再加上一个保护措施和注释。

public class Util{
    private Util(){
        // 抛出异常,防止内部误调用
        throw new AssertionError();
    }
}

弊端是无法对该类进行继承(子类会调用super())。

5、避免创建不必要的对象

  • 对象的重用
  • 昂贵的对象,使用对象池
  • 廉价的对象,慎用对象池。现代JVM对廉价对象的创建和销毁非常快,此时不适于使用对象池。

6、消除过期的对象引用

以下三种情况可能会造成内存泄露:

  • 自己管理的内存(数组长度减小后,pop出的对象容易导致内存泄漏)
  • 缓存
  • 监听和回调

自己管理的内存

对于自己管理的内存要小心,比如:

public class Stack{
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;
    
    public Stack(){
         elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }
 
    public void push(Object e){
        ensureCapacity();
        elements[size++]=e;    // allocate新的堆内存和栈内存
    }
 
    public Object pop(){
        if(size==0) throw new EmptyStackException();
        return element[--size];    // pop出element[size],该对象不再有效。内存泄漏原因。
    }
    
    private void ensureCapacity(){
        if(elements.length==size)
            elements = Arrays.copyOf(elements, 2*size+1);
    }
}

弹出的对象不再有效,但JVM不知道,所以会一直保持该对象,造成内存泄露。

解决:

    public Object pop(){
        if(size==0) throw new EmptyStackException();
        elements[size] = null;        // 等待回收
        return element[--size];
    }

缓存

缓存的对象容易被程序员遗忘,需要设置机制来维护缓存,例如不定期回收不再使用的缓存(使用定时器)。某些情况下,使用WeakHashMap可以达到缓存回收的功效。注,只有缓存依赖于外部环境,而不是依赖于值时,WeakHashMap才有效。

监听或回调

使用监听和回调要记住取消注册。确保回收的最好的实现是使用弱引用(weak reference),例如,只将他们保存成WeakHashMap的键。

7、避免显示调用GC

Java的GC有强大的回收机制,可以简单的记住:不要显示调用finalizer。可以这样理解:

jvm是针对具体的硬件设计的,然而程序却不是针对具体硬件设计的,所以,java代码无法很好的解决gc问题(因为他具有平台差异化)。另外,finalizer的性能开销也非常大,从这个角度上考虑也不应该使用它。

8、覆盖equals方法请遵守通用约定

  • 自反性。x.equals(x) == true
  • 对称性。当前仅当y.equals(x)==true时,x.equals(y)==true
  • 传递性。if(x.equals(y)&&y.equals(z)),y.equals(z)==true
  • 一致性。
  • 非空性。x.equals(null)==false

9、覆盖equals方法时总要覆盖hashCode

为了保证基于散列的集合使用该类(HashMap、HashSet、HashTable),同时,也是Object.hashCode的通用约定,覆盖equals方法时,必须覆盖hashCode。

10、始终覆盖toString

Object的toString方法的通用约定是该对象的描述。注意覆盖时,如果有格式,请备注或者严格按照格式返回。

11、谨慎覆盖clone

12、考虑实现Comparable接口

13、使类和成员的可访问性最小化

目的是解耦。简单来讲,使用修饰符的优先级从大到小,private>protected>default(缺省)>public。如果在设计之初,设计为private修饰符后,在之后的编码过程如果不得不扩大其作用于,应该先检查是否设计的确如此。

子类覆盖超类,不允许访问级别低于超类的访问级别。(超类的protected,子类覆盖后不能改为default)。

成员变量决不允许是公有的。一旦设置为公有,则放弃了对他处理的能力。这种类并不是线程安全的。即使是final的,也不允许。除非希望通过public static final来暴露常量。成员变量总是需要使用setter和getter来维护。有一个例外:长度非零的数组。这是安全漏洞的一个根源。

// 安全漏洞!此处的数组,并不是不可变的
public static final Thing[] VALUES = {...}

改进:

private static final Thing[] PRIVATE_VALUES = {...}
// 此时获取到的才是“常量”
public static final List<Thing> VALUS = 
    Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES))

另一种:

private static final Thing[] PRIVATE_VALUES = {...}
// 此时获取到的才是“常量”
public static final Thing[] values(){
    return PRIVATE_VALUES.clone();
}

14、在公有类中使用访问方法而非公有成员变量(类似13)

15、使可变性最小化

16、复合优先于继承

继承有利于代码复用,但是尽可能不要进行跨包的继承。包内的继承是优秀的设计方式,一个包里的文件处在同一个程序员的控制之下。但是继承有其局限性:子类依赖于超类。超类一旦发生更改,将可能破坏子类。并且,如果超类是有缺陷的,子类也会得“遗传病”。

复合,即不扩展已有的类,而是在的类中新增一个现有类的。相当于现有类作为一个组建存在于新类中。如此,将只会用到需要用到的东西,而不表现现有类所有的方法和成员变量。新类也可以称为“包装类”,也就是设计模式中的Decorate模式。

17、要么就为继承而设计,并提供文档说明,要么就禁止继承

18、接口优于抽象类

19、接口只用于定义类型

20、类层次优先于标签类

21、用函数对象表示策略

函数参数可以传入类似listener的对象,目的是使用listener中的方法。如果使用匿名的参数,每一次调用会创建新的对象。可以将listener声明为成员变量,每次都复用同一个对象,并且可以使用静态域(static变量)。比如String类的CASE_INSENSITIVE_ORDER域。

22、优先考虑静态类成员

嵌套类的目的应该只是为了他的外围类提供服务,如果以后还可能用于其他环境中,则应该设计为顶层类。静态类相当于一个普通的外部类,只是恰好声明在了一个类内部。通常的用户是:Calculator.Operation.PLUS等。和普通类的区别只是,在PLUS前,有了2个前缀,来表明其含义。而非静态类必须存在于外部类对象中。不要手动在外部创建一个内部非静态类对象,创建的过程是:instance.New MemberClass()。这非常奇怪。

如果成员类不需要访问外围类,则需要添加static,是他成为静态成员类,否则每个实例都将包含一个额外指向外围对象的引用。将会影响垃圾回收机制。

23、应指定泛型的具体类型,而不是直接使用原生类型。

例如,应该指定List<E>,而不建议直接使用List。

24、消除非首检警告

在使用IDE进行编码时,强大的IDE都会在你编码过程中提示warning,需要尽可能的消除warning,至少,应该小心这些warning。慎用SuppresWarning,如果IDE提示你可以通过添加该注解解决掉warning,请不要那么做。如果实在要使用,请添加注释说明原因。

25、列表优先于数组

类比泛型,数组是有一定缺陷的。List<SuperClass>和List<SubClass>是没有关系的,而Sub[]是Super[]的子类。

// Fails at runtime
Object[] objectArray = new Long[1];
objectArray[0] = "I don't fit in";       // throw exception
 
// won't compile
List<Object> ol = new ArrayList<Long>();   // Incompatible types
ol.add("I don't fit in");

从代码中可以看到,使用泛型,会提前发现错误。

26、优先考虑泛型

27、优先考虑泛型方法

28、利用有限制通配符来提升API的灵活性

PECS,producer-extends,consumer-super。

//public class Stack<E>{
//    public Stack();
//    public void push(E e);
//    public E pop();
//    public boolean isEmpty();
//}
 
public void pushAll(Iterator<? extends E> src){
    for(E e : src)
        push(e);
}
 
public void popAll(Collection<? super E> dst){
    while(!isEmpty()){
        dst.add(pop());
    }
}
 
// Get and Put Principle

所有comparable和comparator都是消费者(Consumer)。

29、优先考虑类型安全的异构容器

30、用enum代替int常量

public enum Apple { FUJI, PIPPIN, GRANNY_SMITH }
public enum Orange { NAVEL, TEMPLE, BLOOD }

枚举型在java中非常强大,当需要一组固定常量时,使用enum比int好很多。比如代码可读性,安全性等。

31、enum用实例域代替序数

// bad solution
public enum Ensemble {
    SOLO, DUET, TRIO, QUARTET, QUINTET, 
    SEXTET, SEPTET, OCTET, NONET, DECTET;
 
    public int numberOfMusicians() { return ordinal() + 1; }
}
// 
 
// improvement
public enum Ensemble {
    SOLO(1), DUET(2), TRIO(3), QUARTET(4), QUINTET(5), 
    SEXTET(6), SEPTET(7), OCTET(8), NONET(9), DECTET(10), TRIPLE_QUARTET(12);
 
    private final int numberOfMusicians;
    Ensemble(int size) { this.numberOfMusicians = size; }
    public int numberOfMusicians() { return numberOfMusicians; }
}

永远不要像第一种的方式,利用序数访问enum,需要在构造函数中使用参数来初始化。

32、用EnumSet代替位域

public class Text{
    public static final int STYLE_BOLD                     = 1 << 0;    // 1
    public static final int STYLE_ITALIC                    = 1 << 1;    // 2
    public static final int STYLE_UNDERLINE          = 1 << 2;    // 4
    public static final int STYLE_STRIKETHROUGH = 1 << 3;    // 8
 
    public void applyStyles(int styles){  
        // ...
    }
}
 
 
 
// 
text.applyStyles(STYLE_BOLD | STYLE_ITALIC);

以上叫做位图法,但是有更好的方案来传递多组常量——EnumSet。

public class Text{
    public enum Style { BOLD, ITALIC, UNDERLINE, STRIKETHROUGH }
 
    // 注意此处,使用的是Set而不是EnumSet
    public void applyStyles(Set<Style> styles){  
        // ...
    }
}
 
 
 
// 
text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC));

33、用EnumMap代替序数索引

任何时候都不要使用enum的ordinal()方法。

34、用接口模拟可伸缩的枚举

35、注解优先于命名模式

36、坚持使用Override注解

38、检查参数的有效性

公有方法检查参数,参数异常需要跑出Exception。私有方法利用断言assertion检查参数。

39、必要时进行保护性拷贝

假设类的客户端会尽其所能来破坏这个类的约束条件,因此你必须保护性的设计程序。以下是一个不可变类的设计。

public Period(Date start, Date end){
    this.start  = new Date(start);        // 使用了值的拷贝,没有使用原对象(指针)
    this.end = new Date(end);
    if(this.start.compareTo(this.end)>0)
        throw new IllegalArgumentException(start + " after " + end)
}

注意:保护性拷贝是在检查参数之前进行的,防止多线程的影响。不要使用clone方法进行保护性拷贝。

以上方法防御了传入参数的修改,但是对于get方法获取到的对象,仍然可以被修改,通过以下方法可以防止这种攻击。

public Date start(){
    return new Date(start);
}
 
public Date end(){
    return new Date(end);
}

40、谨慎设计方法签名

41、慎用重载

42、慎用可变参数

43、返回0长度的数组或者集合,而不是null

null一般用于表示没有被初始化或处理,如果方法返回了null,则需要在上层做更多的处理,以防止NPE。

44、为所有导出的API元素编写文档注释

正确的javadoc文档,需要每个被导出的类、接口、构造器、方法和域之前增加文档注释。注释应该是对实现透明的,只需要简洁的描述它和客户端之间的约定。并且,还应该附上该方法的副作用。

45、将局部变量的作用域最小化

46、for-each优先于for循环

for-each规避掉了for循环的index变量的引用,通常来说它是不必要的——会增加引入错误的风险,并且风险一旦发生,很难被发现。不过有三种情况下,无法使用for-each(注:在jdk1.8中已经很好的解决了这些问题)。

  • 过滤
  • 转换
  • 平行迭代

48、如果需要精确的答案,请避免使用float和double

float和double是执行的二进制浮点运算,目的是在广泛数值范围上使用精确的快速近似计算而设计的。然而他们并没有提供完全精确的计算(实际应用中,经常会碰到出现x.99999等结果)。尤其是,在进行货币计算时,他们并不适用。比如:

System.out.println(1.03-.42);

得到的结果将是:0.610000000001。

为了解决这个问题,需要使用BigDecimal。然而这也有一些问题,相对于普通的运算,它显得更加麻烦,而且也更慢。通常来说后一个缺点可以忽略,但是前者可能会让人很不舒服。有一种做法是将需要处理的数值*10(或更多),使用int进行计算,不过需要你自己处理四舍五入等操作。

49、基本类型优先于装箱基本类型

  • 基本类型只有值,装箱类具有与他们值不同的同一性。
  • 基本类型只有功能完备的值,装箱类还具有非功能值:null。所以你可能会碰到NPE
  • 基本类型省空间省时间

50、如果有更精确的类型,请避免使用字符串

  • 字符串不适合代替其他值的类型。例如:int,boolean等
  • 不适合代替枚举类型(第30条)
  • 不适合聚集类型

51、当心字符串连接的性能

操作符“+”可以将多个字符串进行连接。但是在大规模使用“+”的情况下,连接n个字符串的开销是n的平房级时间。这是由于字符串的不可变性导致的。在这种情况下请使用StringBuilder进行连接。

52、通过接口引用对象

53、接口优先于反射机制

使用反射机制会带来以下的问题:

  • 丧失了编译期类型检查
  • 代码笨拙冗长
  • 性能损失

反射基本上只适合用在编写组件时、代码分析器、RPC等场景下使用。在使用反射机制时,如果可能,尽可能只通过反射机制实例化对象,而访问方法时,使用已知的接口或者超类。

54、谨慎使用JNI

55、谨慎进行优化

很多计算上的过失都被归咎于效率(没有必要达到的效率),而不是任何其他原因——甚至包括盲目的做傻事。

——William A. Wulf

不要去计较效率上的一些小小的得失,在97%的情况下,不成熟的优化才是一切问题的根源。

——Donald E. Knuth

在优化方面,我们应该遵守两条规则:

规则1:不要进行优化。

规则2(仅针对专家):还是不要进行优化——也就是说,在你还没有绝对清晰的优化方案前,请不要进行优化。

——M. A. Jackson

这些格言比java的出现还要早20年。他们讲述了一个关于优化的深刻事实:优化的弊大于利。

要努力编写好的程序,而不是快的程序。低耦合的重要性远远大于性能。当程序编写得足够低耦合后,通过工具发现了性能瓶颈的代码块,才可以保证对其的修改不影响任何外部环境。

56、遵守普遍的命名规则

57、只针对异常情况才使用异常

不要尝试通过异常机制来做正常代码应该做的事情,比如,检查数组下标。

jvm很少对异常进行优化,因为它只用于不正常的情况。并且,如果你将代码放入try-catch代码块,jvm就丧失了本来可以对它进行的优化。

58、对于可恢复的情况使用受检异常,对于编程错误的情况使用运行时异常

  • 如果期望调用者适当的恢复,则需要使用受检异常,强迫调用者食用try-catch代码块,或者将他们抛出去
  • 当调用发生前提违例——违反约定的情况时,使用运行时异常,这个时候程序已经无法再执行下去了。例如调用数组的-1索引。

58、避免不必要的受检异常

完整的Java初级,高级对应的学习路线和资料!专注于java开发。分享java基础、原理性知识、JavaWeb实战、spring全家桶、设计模式、分布式及面试资料、开源项目,助力开发者成长!可以加入我的十年Java学习园地

查看原文

赞 0 收藏 0 评论 0

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

你的毕设我的心之学生成绩管理系统

当年从学校毕业做毕设的时候,网络还没有现在那么普遍,想要找个参考却也不容易,我当时是费了不少功夫才顺利的通过了答辩,所以最近就自己写了一个学生成绩管理系统,希望给做毕设的同学和刚入行做开发不久的同行,做个参考,应该会有一点的启发吧,从表的设计到代码的编写全部都是自己一手弄的,大家点个关注不过分吧!哈哈!

系统介绍

以学生成绩作为主题,辅以班级、学生、老师、课程、选课等管理模块(当然因为是做实例,也没有把所有的模块都涉及到)。

系统角色

1.admin

admin是管理员,可以管理系统所有的模块,权限最大。

2.老师

老师拥有仅次于管理员的权限,除了选课、维护老师信息以外的所有功能。

3.学生

学生是权限最小的角色,仅有成绩查询、选课两个功能。

预览图:

登录

image.png

主页面
image.png

表结构介绍

  学生表

CREATE TABLE `student` (
  `no` varchar(12) NOT NULL COMMENT '学号',
  `pwd` varchar(6) NOT NULL COMMENT '密码',
  `name` varchar(32) default NULL COMMENT '名字',
  `gender` varchar(1) default NULL COMMENT '性别',
  `classes` varchar(4) default NULL COMMENT '班级',
  PRIMARY KEY  (`no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

老师表

CREATE TABLE `teacher` (
  `no` varchar(8) NOT NULL COMMENT '教师号',
  `pwd` varchar(6) NOT NULL COMMENT '密码',
  `name` varchar(32) default NULL COMMENT '名字',
  `gender` varchar(1) default NULL COMMENT '性别',
  `course` varchar(8) default NULL COMMENT '所授课程',
  PRIMARY KEY  (`no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

主菜单表

CREATE TABLE `menu` (
  `id` int(11) NOT NULL,
  `menuCode` varchar(8) default NULL COMMENT '菜单编码',
  `menuName` varchar(16) default NULL COMMENT '菜单名字',
  `menuLevel` varchar(2) default NULL COMMENT '菜单级别',
  `menuParentCode` varchar(8) default NULL COMMENT '菜单的父code',
  `menuClick` varchar(16) default NULL COMMENT '点击触发的函数',
  `menuRight` varchar(8) default NULL COMMENT '权限s表示学生,t表示老师,管理员拥有全部',
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

image.png

班级表

CREATE TABLE `classes` (
  `id` int(11) NOT NULL default '3',
  `classNo` varchar(32) default NULL COMMENT '班级号',
  `className` varchar(32) default NULL COMMENT '班级名',
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

课程表

CREATE TABLE `course` (
  `id` int(11) NOT NULL auto_increment COMMENT '主键',
  `code` varchar(8) NOT NULL COMMENT '科目编码',
  `course` varchar(32) NOT NULL COMMENT '科目名称',
  `courseType` varchar(4) default '2' COMMENT '课程类型,1必修,2选修',
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;

成绩表

CREATE TABLE `score` (
  `id` int(11) NOT NULL auto_increment COMMENT '主键',
  `s_no` varchar(8) NOT NULL COMMENT '对应学号',
  `course` varchar(6) NOT NULL COMMENT '对应科目',
  `counts` varchar(32) default NULL COMMENT '分数',
  `year` varchar(8) default NULL COMMENT '年份',
  `term` varchar(2) default NULL COMMENT '学期',
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8;

选课关联表

CREATE TABLE `student_course_rel` (
  `id` int(11) NOT NULL auto_increment,
  `student_no` varchar(8) default NULL COMMENT '学号',
  `course_code` varchar(8) default NULL COMMENT '课程编码',
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;

系统模块介绍

1.登录模块
image.png
登录模块3个角色来分别登录,当选择管理员的时候用户名密码:admin/admin,因为目前是写的固定值,哈哈(偷懒),选择学生的话会查询student学生表验证,选择老师的话后台会去查teacher老师表来判断用户名密码是否正确(type为t代表 老师,s 代表学生,a代表管理员admin)。
image.png

用学生登录后

image.png

现在页面做的比较简单,用frameset 来分为上、中、下三个部分;

  1. 上面是logo和退出系统
  2. 中间分为2个模块,左边为菜单,右边为主操作窗口,现在没搞东西在上面,哈哈
  3. 下面是日期和当前登录人

    学生的功能比较少

1.成绩查询
image.png

2.选课,列表的后面有选课的按钮(必选课不需要选择,默认了)。
image.png

老师的模块多一些

成绩管理
image.png
老师查询到自己授课内,所有人的成绩,可以添加、修改、删除成绩。

image.png
老师可以看到自己授课的人员选择情况,如果是必选课就能直接看到。

image.png
image.png
班级和课程的管理,目前老师是可以操作的,如果不想给也可以在menu表里面配置,比较方便。

image.png
配置s就学生有权限,如果t就表示老师有权限,同时配置表示老师和同学都有,admin则不需要配置,默认就全部都有。

老师信息维护(管理员才有的权限)
image.png

说说有哪些没有处理的

  1. 功能肯定有些不齐全、因为没有去调查,离开校园也很久了,估计表的设计没有那么合理吧,但是模板模样都有了,要加东西我觉得比较容易了。
  2. 样式什么的,没有怎么去处理,肯定不太好看。
  3. 时间比较仓促,没有花心思去测试,肯定会有些bug(有bug不是很正常吗?程序员都会碰到)。
  4. 文档我没写,自己看着办吧,嘿嘿。

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

查看原文

赞 0 收藏 0 评论 0

认证与成就

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

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

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