《Effective Java》读书笔记,2024年阿里Android高级面试题分享

如果你想要演变的容易性比功能、灵活性更重要,而且你接受抽象类的局限性,就用抽象类。

接口->骨架实现类->简单实现类。

19 接口只用于定义类型

除此之外,为了任何其他的目的而定义接口是不恰当的。

说白了,定义接口是为了接口的行为。

有一种接口是常量接口,这是对接口的不良使用。因为常量是实现细节,不应该泄漏出去。对类的用户来说没有价值,反而会干扰。而且如果非final类实现了常量接口,它的所有子类的命名空间也会被接口中的常量所“污染”。

定义常量应该用枚举或者不可实例化的工具类。

20 类层次优于标签类

想到了自己项目里 群聊创建模块。 原本就是一个标签类,改造成了类层次。

类层次好处:

简单清晰,没有样板代码,不受无关数据域的拖累,多个程序员可独立扩展层次结构。而且类层次可以反映类型之间本质上的层次关系,有助于增强灵活性。

标签类有很多缺点:

  1. 可读性差

  2. 内存占用增加因为实例承担着属于其他风格的不相关的域。

  3. 域不能是final的。

总之它过于冗长,容易出错,效率低下。

其实标签类是对类层次的一种简单的效仿。

将标签类转化成类层次:

  1. 为标签类的每个方法都定义一个包含抽象方法的抽象类,这里的每个方法的行为都依赖于 标签值。

  2. 如果有行为不依赖于标签值的方法,就将这些方法放在抽象类里。

  3. 如果所有的方法都用到了某些域,则将域也放在抽象类里。

  4. 为每种原始标签书写相应的子类。

21 用函数对象表示策略

如果一个类,它只有一个方法,这个方法执行其他对象(通过参数传入)上的操作。

这个类的实例实际上等同于一个指向该方法的指针。

这样的实例称之为函数对象。也是一个具体策略类。

我们在设计具体的策略类时,还需要定义一个策略接口。

总结:函数指针的主要用途就是实现策略模式。

为了在java中实现这个模式,要声明一个接口来表示该策略,并且为每个具体策略实现该接口。

当具体策略只使用一次时,通常用匿名内部类来做。

当重复使用时,通常实现为某个类私有的静态成员类,并通过 公有 静态 final 域 被导出,域类型是策略接口类型。

22 优先考虑静态成员类

嵌套类指定义在另一个类内部的类。目的是 只为它的外围类服务。

如果嵌套类将来可能用于其他的环境中,它就应该是顶层类。

嵌套类四种:

  1. 静态成员类(优先)

  2. 非静态成员类

  3. 匿名类(不能拥有静态成员,如果只有一处创建的地方)

  4. 局部类(有多处创建的地方)

后三种被称为内部类

静态成员类是最简单的一种嵌套类,最好把它看作是普通的类,只是碰巧被声明在另一个类的内部而已。

如果声明内部类不要求访问外围实例,就要声明称 静态成员类。

内部类会包含一个额外的指向外围对象的引用,保存这份引用要消耗时间和空间,也会影响GC.

匿名类的常见用法:

  1. 动态创建函数对象。(21条)

  2. 创建过程对象,比如Runnable、Thread。

  3. 静态工厂方法的内部。

第五章 泛型


出错之后应该尽快发现,最好是在编译时就发现。

23 请不要在新代码中使用原生态类型、

List<T>对应的原生态类型就是List,如果使用原生态类型,就丢掉了泛型在安全性和表述性方面的所有优势。它只是为了兼容遗留代码的。

无限制的通配符类型:List<?>,等于List,它们和List<Object>都不相等。

在类文字 中必须使用原生态类型List.class String[].class ini.class都合法,List<String>.class 和List<?>.class都不合法。

24 消除非受检警告

尽可能地消除每一个非受检警告。 如果消除所有警告,则可以确保代码是类型安全的。意味着在运行时不会出现ClassCastException异常。

如果无法消除警告,同时可以证明引起警告的代码是类型安全的,只有在这种情况之下,才可以用一个@SuppressWarnings(“unchecked”)注解来禁止这条警告。

应该在尽可能小的范围里使用该注解。并添加注释解释为什么这么做是安全的。

25 列表优先于数组

数组与泛型相比,有两个重要的不同:

一 数组是协变的,泛型是不可变的。

如果Sub是Super的子类型,那么Sub[]就是Super[]的子类型。

然而泛型则不是。对于任意两个不同的类型Type1,Type2,List<Type1>和List<Type>不可能是子类型或父类型的关系。

实际上,这不是泛型的缺陷,反而是数组的缺陷:

//fails at runtime!

Object[] objectArray = new Long[1];

objectArray[0] = “I don’t fit in”;//Throws ArrayStoreException

//Won’t compile

List listObject = new ArrayList();//Incompatible types

上述代码,无论哪种方法,都不能将String放入Long容器中。但是利用列表,可以在编译时发现错误。

二 数组是具体化的 在运行时才知道自己的类型,泛型通过擦除,在运行时丢弃类型信息

一般来说,数组和泛型不能很好地混合使用。

如果混合使用出现了编译时错误,应该用列表替代数组。

26 优先考虑泛型

不能直接创建 new T[16],可以通过创建 (T[])new Object[16];强转。也可以在 取出数据时强转。

有些泛型如ArrayList,必须在数组上实现。

为了提升性能,其他泛型如HashMap也在数组上实现。

27 优先考虑泛型方法

静态工具方法尤其适用于泛型化。

泛型方法一个显著特征,可以配合 类型推导,无需明确指定类型参数的值。

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

PECS表示,producer(取)-extends,consumer(存)-super。

还要记住所有的comparable和comparator都是消费者。

为了获得最大限度的灵活性,要在表示生产者或者消费者的输入参数使用通配符类型

如果某个输入参数 即是生产者又是消费者,则通配符类型对你没有好处,需要严格的类型匹配。

不要用通配符作为返回类型。

如果类的用户必须考虑通配符类型,类的API或许就会出错。

显式的类型参数:

Set numbers = Union.union(integers,doubles);

一个复杂的例子:

public static <T extends Comparable<? super T>> T max(List<? extends T> list)

类型参数和通配符之间具有双重性,许多方法都可以利用其中一个或者另一个进行声明。

一般来说,如果类型参数只在方法声明中出现一次,就可以用通配符取代它

在公共API中,推荐第二种。

例如:

//类型参数和通配符之间具有双重性,许多方法都可以利用其中一个或者另一个进行声明

//一般来说,如果类型参数只在方法声明中出现一次,就可以用通配符取代它。

public static void swap1(List list, int i, int j) {

}

public static void swap2(List<?> list, int i, int j) {

}

public static void swap3(E list, int i, int j) {

}

//这个是错误的, 因为E不是类型参数

/* public static void swap4(? list, int i, int j) {

}*/

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

类型安全:当你向它请求String时,它从来不会返回一个Integer给你

异构容器:不像普通的map,它的所有key都是不同类型的。

泛型最常用于集合。

当一个类的字面文字(Class<T>)被用在方法中,来传达编译时和运行时的类型信息时,就被称作type token

注解API广泛利用了有限制的类型令牌。 被注解的元素,本质上是个类型安全的异构容器。

第六章 枚举和注解


1.5中增加了 两个新的引用类型:枚举类型、注解类型

30 用enum代替 int 常量。

枚举易读,提供了编译时的类型安全

允许添加任意的方法和域(本质上是个类)。

如果枚举具有普适性,应该成为一个顶层类。如果用在特定顶层类中,应该成为该顶层类的一个成员类。

但枚举有小小的性能缺点,装载和初始化枚举时会有空间和时间的成本。

31 用实例域 代替序数(ordinal())

永远不要根据枚举的序数导出与它关联的值,而是要将它保存在一个实例域中。

最好完全避免ordinal方法的使用。

32 用EnumSet 代替位域

位域有更多缺点,打印时翻译位域更困难,遍历位域也困难。

EnumSet内部用单个long保存,(枚举类型小于等于64个)

枚举类型要用在集合中,所以没有理由用位域来表示它。

用EnumSet,简洁、性能也有优势。

public static void main(String[] args) {

EnumSet

System.out.println(bold);

}

enum Style {

BOLD, ITALIC, UNDERLINE;

}

//[BOLD, ITALIC]

33 用EnumMap 代替序数(ordinal())索引

按Enum分组索引,key是Enum。

最好不要用序数来索引数组,而要使用EnumMap。

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

用接口模拟可伸缩枚举有个小小的不足,无法从一个枚举类型继承到另一个枚举类型。

虽然无法便携可扩展的枚举类型,却可以通过编写接口以及实现该接口的基础枚举类型,对它进行模拟

35注解优先于 命名模式

命名模式缺点:

  1. 文字拼写错误会导致失败,且没有任何提示。

  2. 无法确保它们只用于相应的程序元素上。

  3. 没有提供将参数值与程序元素关联起来的好办法。

36坚持使用Override注解

37用标记接口定义类型

标记接口是没有包含方法声明的接口,而只是知名一个类实现了具有某种属性的接口。

例如Serializable。

如果想要定义类型,一定要使用接口

第七章方法


38检查参数的有效性

应该在文档中清楚的指明参数的限制 以及 抛出的异常。

并在方法体的开头检查参数。

这是“应该在发生错误之后尽快检测出错误”原则的具体情形。

检查构造器参数的有效性是非常重要的。

39必要时进行保护性拷贝

编写一些面对客户的不良行为时仍能保持健壮性的类,是非常值得的。

对构造器的每个可变参数进行保护性拷贝是必要的。

保护性拷贝是在检查参数的有效性(上一条38)之前进行的,并且有效性检查是针对拷贝之后的对象,而不是针对原始的对象。 这样做可以避免在“危险阶段”(从检查参数开始,直到拷贝参数之间的时间段)期间从另一个线程改变类的参数。

对于参数类型可以被不可信任方子类化的参数,请不要使用clone方法进行保护性拷贝。

返回可变内部域的保护性拷贝

总结:

如果拷贝的成本受到限制,并且类信任它的客户端不会不恰当的修改组件,就可以在文档中说明客户端的职责,代替保护性拷贝。

40谨慎设计方法签名

本条目是若干API设计技巧的总结。

  • **谨慎地选择方法的名称。**易于理解、风格一致、大众认可,可参考Java类库。

  • **不要过于追求提供便利的方法。**方法太多会使类难以学习、使用、文档化、测试和维护。为每个动作提供一个功能齐全的方法,只有当一项操作被经常用到的时候,才考虑为它提供快捷方式,如果不确定,还是不提供快捷为好。

  • **避免过长的参数列表。**目标是四个参数或更少。 相同类型的长参数序列格外有害。(拆分、辅助类、Builder)

  • 对于参数类型,要优先使用接口而不是类。

  • **对于boolean参数,要优先使用两个元素的枚举类型。**易于阅读和编写,更易于后期添加更多的选项。

41 慎用重载

重载是发生在编译时的,所以严格的说,它并不是多态。

要调用哪个重载方法是在编译时做出决定的。

对于重载方法的选择是静态(编译时的对象类型决定)的,对于覆盖的方法的选择则是动态(运行时的对象类型决定)的。

避免胡乱地使用重载机制。

安全而保守的策略是,永远不要导出两个具有相同参数数目的重载方法。

如果方法使用可变参数,保守的策略是根本不要重载它。

例如,考虑ObjectOutputStream类,对于不同类型,提供诸如writeBoolean(boolean)、writeInt(int)方法,好处还可以提供对应的readBoolean()readInt()方法。

导致混乱主要是由于 参数是 有继承关系的类型。应该避免 同一组参数只许经过类型转换就可以被传递给不同的重载方法。

对于每一对重载方法,至少有一个对应的参数在两个重载方法中具有“根本不同”的类型

但是当自动装箱和泛型成了Java语言的一部分之后,谨慎重载显得更加必要了。

例如List的remove()方法。如果配合Integer,会搞不清是要按下标删除还是删除对应的元素。

如果被迫违反本规则,要保证让更具体化的重载方法把调用转发给更一般化的重载方法。

例如:

public boolean contentEquals(StringBuffer sb) {

return contentEquals((CharSequence)sb);

}

42慎用可变参数

可变参数方法,接受,0个或多个指定类型的参数。

可变参数也会影响性能,每次方法调用都会导致数组进行一次分配和初始化。

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

除非分析表明这个方法正是造成性能问题的真正源头。

且返回同一零长度数组是可能的,因为零长度数组不可变,可以被自由共享(15条)。

不可变的空集合:Collections.emptySet(),Collections.emptyList(),Collections.emptyMap(),

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

第八章 通用程序设计


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

本条目与13条(使类和成员的可访问性最小化)本质上是类似的。

  • 在第一次使用它的地方声明。

  • 如果在循环终止之后不再需要循环变量的内容,for循环优先于while循环

  • 使方法小而集中。

46for-each循环优先于传统的for循环

集合数组,以及任意实现Iterable接口的对象,都能用for-each。

嵌套迭代时,优势更明显。

三种常见情况无法使用for-each:

  1. 过滤—-遍历集合时要删除选定的元素,需要用迭代器。

  2. 转换—-遍历列表或数组时,需要取代它部分或者全部的元素值。需要用迭代器或者数组索引。

  3. 平行迭代—–故意的嵌套迭代(很少使用)

47:了解和使用类库

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

它们尤其不适合于货币就算。

使用BigDecimal、int、long进行货币计算

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

它们的区别:

  1. 基本类型只有值,装箱基本类型是引用

  2. 基本类型只有值,而装箱基本类型还有null

  3. 基本类型通常更节省时间、空间

对装箱基本类型运用 == 操作符,几乎总是错误的。

当在一项操作中混合使用基本类型和装箱基本类型时,装箱基本类型就会 自动拆箱。 null被拆箱就抛出NPE。

合理使用装箱基本类型的地方:

  1. 集合中的元素、键、值。因为基本类型不能放在集合中。

  2. 参数化类型中

  3. 反射的方法调用时

50:如果其他类型更适合,则尽量避免使用字符串。

  • 字符串不适合代替其他的值类型。(从网络接受一段数据)

  • 字符串不适合代替枚举类型

  • 字符串不适合代替聚集类型。例如String key = className + "#" + i.next();应该用一个类来描述这个数据集,通常是一个私有的静态成员类。

  • 字符串也不适合代替能力表。

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

为连接n个字符串而重复地使用字符串连接操作符,需要n的平方级的时间。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
img

受一段数据)

  • 字符串不适合代替枚举类型

  • 字符串不适合代替聚集类型。例如String key = className + "#" + i.next();应该用一个类来描述这个数据集,通常是一个私有的静态成员类。

  • 字符串也不适合代替能力表。

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

为连接n个字符串而重复地使用字符串连接操作符,需要n的平方级的时间。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-cLP9wlDo-1710930011273)]
[外链图片转存中…(img-kJ9mQRhl-1710930011274)]
[外链图片转存中…(img-xAM701nY-1710930011274)]
[外链图片转存中…(img-FjMMb4l0-1710930011274)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
[外链图片转存中…(img-QjPkzXTV-1710930011275)]

  • 7
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值