初始化和清理/复用/多态/接口/集合/函数式编程/流/异常/测试

数组长度是length-1,如果越界,C和C++会默默接收,并允许你访问所有内存,这是许多臭名昭著错误的根源,而Java会抛出运行时异常。

可变数组参数:Object... args,记得之前是老大教我的,怀念怀念

JDK11提供了和javaScript一样的var弱类型关键字,引以为豪的强约束现在也没了;另外,类字段中不能用var,无初始化变量不能用var,返回参数类型不能用var;

复用

  • 在新类中创建现有类的对象称为组合(composition)。
  • 复制现有类的能力并进行扩展叫做继承(extend)

除非有明确的从属关系,一般建议使用组合,比如:
如果创建的是车/货车/小汽车/大卡车类,车的通用行为是相同的,货车/小汽车/大卡车都需要向上转型到车来使用,就可以使用继承,其他一般业务情况使用组合就行了。

final

final数据

final修饰的对象的引用(对象本身可以修改)和基本数据类型恒定不变,在编译时写入常量值。其他和正常的变量一样。

final方法

final方法无法通过继承/实现重写,一般是你觉得这个方法不应该也不能被重写的时候用。

比如一个核心算子的方法,模板设计模式的apply方法等。

final类

final类无法被继承。

多态

多态提供了另一个维度的接口与实现分离,将做什么怎么做解耦。多态改善了代码的组织结构与可读性,并且还能创建可扩展的程序。

编译器如何知道谁是【实现】

我们调用基类的play方法,编译器如何知道这个方法调用绑定的是哪个实现类的方法体呢?
Java编译器实现了后期绑定,在运行时通过某种机制确定对象的类型,并调用恰当的方法体,Java中所有方法绑定都是后期绑定。
在面向过程语言中,例如C,在程序运行前就进行了方法绑定,这种叫做前期绑定
要区分前后期绑定和动态静态链接的区别,方法绑定是指我应该用哪个方法实现来执行指定的方法调用,动态静态链接是指用代码直接嵌入还是引用跳转的方式来执行这段方法。
值得说的是,C可以用函数指针来实现后期绑定。

构造器和多态

构造器其实是隐式的静态方法,而且基类的构造器总是在子类的构造过程中被调用,初始化会在继承层次结构里自动向上移动,因此每个基类的构造器都会被调用(子类初始化字段时,必须知道父类的初始化字段)。

协变返回类型

子类重写方法返回值可以是基类方法返回值的子类型。

接口

普通类 => 抽象类 => 接口

接口可以创建变量(默认static和final),在Java8中已经可以创建默认方法、静态方法,当然也会有菱形继承问题,在java9中,接口的default和static方法还可以是private的,可以选择性暴露方法给实现类。

JDK17引入了密封类(sealed class),它运行基类或者接口限制自己能够派生出的类,具体语法如下:

sealed class Base permits D1,D2

而sealed的子类只能通过final、sealed、non-sealed(允许任何子类继承)来修饰。

集合

JDK16提供了record类,相当于从头到脚都是final的类。
一般用于只读集合,比如从数据库查询的DO对象,用作Map的key等。

函数式编程

为了让程序适应有限内存,程序员往往在程序执行时修改内存中的代码,来节省代码空间,这就是自修改代码技术,随着内存越来越便宜,函数式编程代替了这种实现,它抽象了行为。

lambda中外部变量是final,是为了保证并发时不出现异常

高阶函数只是一个能够接收函数作为参数或者能把函数当成返回值得函数,JS中用的就很多。

闭包,就是指语言是否能够自动处理逃脱作用域的变量,Java8是支持闭包的,Js也支持。

局部作用域变量是事实上final的,这点是因为java开发者为了防止可能出现的并发错误(新手友好,在java官方doc里有说过),而全局变量是没有这个限制的。

  • Lambda函数链
    Lambda支持用多个小Lambda拼接成新的Lambda,谓词有:andThen(arg) 后参数、compose(arg) 先参数、and(arg) 与短路 、or(arg) or短路、negate(arg) 取反。
public static void main(String[] args) {
        Function<String,String>  fun1  = (a) -> a.toLowerCase(Locale.ROOT);
        Function<String,String>  fun2  = (a) -> "["+a+"]";
        Function<String,String>  fun3  = (a) -> a+",how old are you.";
        Function<String,String>  chain  = fun1.andThen(fun2).andThen(fun3);

        System.out.println(chain.apply("LaiHuanMING"));

        Predicate<Integer> pre1  = (a) -> a<10;
        Predicate<Integer> pre2  = (a) -> a>0;
        Predicate<Integer> pre3  = (a) -> a!=5;
        Predicate<Integer> chain2  = pre1.and(pre2).and(pre3);

        System.out.println(chain2.test(6));

    }

//[laihuanming],how old are you.
//true

柯里化就是将多个参数的方法转变为一系列只接受一个参数的函数。(可以粒子化)

流是惰性的内部迭代,优化器会对遍历方式采用更高效的方式。
流的内容在java8实战的书里已经看过很多遍了,就不赘述了。

异常

要创建一个稳健的系统,每个组件都必须是稳健的。
尽可能使用try-with-resources。

测试

  1. 如果可能,尽量保证有单元测试;
  2. 测试覆盖率在65%以上的核心模块就很好了,过少覆盖率意味着程序自动化程度低,过多意味着开发在浪费时间;
  3. 测试代码中可以用断言验证程序所需的条件是否满足,但是生产环境应该用异常。
  4. 测试驱动开发(TDD),就是在开发前就想好测试用例,然后编写单元测试,可以让程序设计更好。
  5. 在97%的情况下,过早优化是万恶之源,基准测试需要在生产环境确实需要的时候再去做(其实这也分场景,一般生产环境有性能问题,确实需要开发使用)
  6. JMH系统可以帮助你更快的实现微基准测试,它支持自定义运行次数、自定义运行环境(以单线程还是多线程)、自定义检测标准(平均时间、最大、最小时间)。
posted @ 2022-08-04 10:36  来焕明  阅读(26)  评论(0)    收藏  举报