《Effective Java》第四章:类和接口

第15条:最小化类和成员的可访问性

尽可能地让每个类或者成员不可被外界访问
先复习一下基础:
1.private—成员只能被声明它的顶级类访问。
2.package-private—成员可以被声明它的包下面的所有类访问。从技术上讲,它被称为缺省访问权限(default access),当没有指定访问修饰符时,我们就会得到这种访问级别(接口成员除外,它们默认是公有的)。
3.protected—成员可以被声明它的类的子类访问(但有一些限制[JLS, 6.6.2]),同时,声明它的包下面的所有类也可以访问它。
4.public—成员可以在任何地方被访问。
如果一个顶层类或接口可以做成包级私有的,那么就应该是包私有的。通过把它做成包级私有,我们就把它做成了实现的一部分,而不是导出的API,而且我们即使在接下来的发行版本中修改它,替换它或者删除它,也不用担心会影响到现存的客户端。如果我们把它做成了公有,那么为了维护兼容性,我们将不得不长期去支持它。
且可以看到包限制级别比protected的可访问范围更小。
当final修饰一个引用类型的对象,其引用是不能修改的,但是这个对象内部的值是可以更改的。所以实例域一定不能是公有的(public),同样的,长度非零的数组总是可变的,这时可以利用Connections.unmodifiableXXX: Collection、List、Set、Map,其原理就是初始化时把对象拷贝到一个map,然后将修改操作的方法都抛出异常。或者将数组设为private,然后增加一个方法返回这个数组的拷贝。

信息隐藏之所以很重要有多方面的原因,但大多数原因都是基于这么一个事实,信息隐藏可以对组成系统的组件进行解耦,从而允许这些组件独立开发,测试,优化,使用,理解和修改。


第16条:在公有类中使用访问方法,而不是公有域

如题,公有类中的域应该用getter和setter,而不是直接设置为public的。


第17条:使可变性最小化

封箱:就是把基本类型封装成其所对应的包装类型;
若要让一个类不可变,我们可以遵循以下5条规则:
1.不要提供能修改对象状态的方法(即setter方法)
2.确保这个类不能被扩展。这防止了粗心或者恶意的子类通过改变对象的状态,从而破坏类的不可变行为。一般情况下,可以通过将类设为final,从而防止被继承,但也有另一种方式,我们后面会讨论到。
3.将所有域设为final。通过这种系统强制的方式,可以清晰表达我们的意图。而且,如果一个指向新创建的实例的引用在缺乏同步机制的情况下从一个线程传入另一个线程,就必须确保正确的行为,正如内存模型(memory model)里描述的那样[JLS, 17.5; Goetz06, 16]。
4.将所有域设为私有。这防止客户端获得访问被域引用的可变对象的权限,从而防止了这些可变对象被直接修改。虽然从技术上可以让可变类的公有final域包含基础类型值或指向不可变对象的引用,但这么做也是不推荐的,因为这妨碍了在未来的发布版本中修改类的内部展示。
5.确保对任何可变组件的互斥访问。如果类里包含了指向可变对象的域,则要确保类的客户端不能包含指向这些对象的引用。永远不要用客户端提供的对象引用来初始化这些域,也不要从访问方法中返回该对象引用。在构造器,访问方法和readObject方法(条目88)中,请使用保护性拷贝(defensive copies,条目50)。

函数式编程,不改变对象,而是创建写的对象,从而可以保证传入对象的不变性。不可变对象天生就是线程安全的,它们不要求同步。不可变对象可以自由地被共享。

但每次都创建新的对象,可能带来性能问题。

关于序列化有个警告必须提一下。如果你让你的不可变类实现Serializable接口,同时这个不可变类还包含了一个或多个指向可变对象的域,那么,你必须提供一个显示的readObject方法或readResolve方法,或者使用ObjectOutputStream.writeUnshared方法和ObjectInputStream.readUnshared方法,即使默认的序列化形式是可接受的。不然攻击者就可以创建一个该类的可变实例。

对于创建不可变类,增加final修饰,没有将构造方法设为private的好。


第18条:组合优先于继承

与方法调用不同的是,继承违反了封装原则。换句话说,一个子类依赖于它的父类的实现细节来实现它本身的功能。这样的后果是,子类必须跟着父类一起演化。
继承虽然强大,但它存在一些问题,因为它违反了封装。它只有在子类和父类之间存在真正的子类型关系的时候才使用。
有一个办法可以避免所有上面说到的问题。除了扩展现有类,你还可以让你的新类包含一个私有域,这个域指向现有类的一个实例。这种设计被称为组合,因为现有类成为了新类组件。新类的每个实例方法调用现有类实例的对应方法然后返回值。这种做法被称为转发,新类里的方法也被称为转发方法。这样编写出来的类将会很坚固,因为它不依赖域现有类的实现细节。即使往现有类里添加新方法也不影响新类。
继承只适用于一个类的类型的确是某个父类的子类型的情况。换句话说,只有当类B和类A是“is-a”的关系时,类B才应该扩展类A。如果你打算让类B扩展类A,可以自问一下这个问题:是否每个B都确实是一个A?如果你对这个问题无法肯定地回答yes,那么B就不应该扩展A。如果答案是no,那么通常是B应该包含一个私有的A的实例,同时暴露一个不同的API:A并不是B的一个重要组成部分,而仅仅是B的实现的一个细节。


第19条:若要设计继承,则提供文档说明,否则禁止继承

并且,继承的文档不符合程序文档的格言:好的API文档应该描述一个给定的方法做了什么。首先,必须在这个类的文档里为每个方法说明覆盖带来的影响,而不是描述它如何做到。
为了允许继承,类还必须遵守其他一些约束。构造器决不能调用可被覆盖的方法,无论是直接还是间接调用。


第20条:接口优于抽象类

接口通常是定义允许多个实现的类型的最佳途径。这条规则有个例外,即当演变的的容易性比灵活性和功能更为重要的时候。在这种情况下,应该使用抽象类来定义类型,但前提是必须理解并且可以接受这些局限性。如果你导出了一个重要的接口,就应该间距考虑同时提供骨架实现类。
对于骨架类,好的文档是绝对非常必要的。


第22条 接口只用于定义类型

不用于定义常量


第23条 类层级优先于标签类

类层级:父类--子类(不同特性的子类)
标签类:相同属性构成的的一类,用枚举区分类别
标签类各种类挤在一起,破坏可读性,且类内部的域有些不是通用的,标签类过于冗长、容易出错、且效率底下。不使用这部分属性的时候,仍然要加载。


第24条 用函数对象表示策略

策略类没有域,这个类的所有实例在功能上都是等价的。因此,它作为一个Singleton是非常合适的,可以节省不比较的对象创建开销。
而策略类无法传递任何其他的比较策略。所以在设计策略类的时候,还需要定义一个策略接口。比如:

 public interface Comparator<T> {
        public int  compare(T t1, T t2);
    }

    class StringLengthComparator implements Comparator<String> {

        @Override
        public int compare(String t1, String t2) {
            ...//class body is identical to the one shown above
        }
    }

但是注意,以这种方式使用匿名类的是欧,将会在每次调用的时候创建一个新的实例。如果它被重复执行,考虑将函数对象存储到一个私有的静态final域中,并重用它。这样做的另外一个好处是,可以为这个对象去一个有意义的域名称。
函数指针的主要用途就是实现策略(Strategy)模式。为了再Java中实现这种模式,要声明一个接口来表示该策略,并且为每个具体策略声明一个实现了该接口的类。当一个具体策略只被使用一次时,通常使用匿名类来声明和实例化这个具体策略类。当一个具体策略是设计用来重复使用该的时候,它的类通常就要被实现为私有的静态成员类,并通过共有的静态final域被导出,其类型为该策略接口。


第22条:优先考虑静态成员类

嵌套类是指被定义在另一个类的内部的类。分为四种:静态成员类、非静态成员类、匿名类、局部类。
静态成员类是最简单的一种嵌套类。最好把看作是普通的类,只是碰巧被声明在另一个类的内部而已,它可以访问外围类的所有成员,包括那些声明为私有的成员。
非静态成员类的每个实例都隐含着与外围类的一个外围实例相关联。在非静态类的实例方法内部,可回忆调用外围实例上的方法,或者利用修饰过的this构造获得外围实例的引用。
如果嵌套类的实例可以在它外围类的实例之外独立存在,这个嵌套类就必须是静态成员类:在没有外围实例的情况下,要想创建非静态成员类的实例是不可能的。
如果声明成员类不要求访问外围实例,就要始终把static修饰符放在它的声明中,使它成为静态成员类,而不是非静态成员类。因为,如果省略了static修饰符,则每个实例都将包含一个额外的指向外围对象的引用。保存这份引用要消耗时间和空间,并且会导致外围实例在适合垃圾回收的时候却仍然得以保留。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,692评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,482评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,995评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,223评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,245评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,208评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,091评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,929评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,346评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,570评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,739评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,437评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,037评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,677评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,833评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,760评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,647评论 2 354

推荐阅读更多精彩内容