类和接口(三)
类和成员的可访问能力最小
如何区别一个设计良好的模块和设计不好的模块,主要看这个模块对于外部的其他模块而言,是否隐藏了内部的数据和其他的实现细节.一个设计良好的模块是会吧内部的所有实现细节都隐藏起来,模块之前通过API进行通讯,这个概念称为信息隐藏或者封装.(解除系统之间的解耦合,使模块可以被并行的开发,测试,优化,理解和修改)
还有一种就是访问控制机制来决定类和接口的课访问性下面是按照课访问性递增的顺序:
- 私有的(private)---声明该成员的顶层内部才可以访问
- 包级私有的)(package-private)---声明该成员的包内部的任何类都可以访问该成员
- 受保护的(protected)---声明高成员的所在子类可以访问这个成员
- 公有的(public)---任何地方都可以访问该成员
非长度的数组是可变的,具有公有静态final数组域几乎总是错误的比如这种:
public stativ final Type[ ] VALUES = {.........};
公有数组应该替换为私有数组,和公有的非可变列表:
private static final Type[ ] PRIVATE_LAVUES = {.........};
public static final List VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_LAVUES ));
如果要求编译时的类型安全,并且愿意损失一点点性能的话可以吧共有的数组替换为一个公有的方法,然后返回私有数组的一份拷贝
private static final Type[ ] PRIVATE_VALUES = {......};
public static fainl Type[ ] values(){
return (Type[ ])PRIVATE_VALUES.clone();
}
确保公有域静态final域所引用的对象是不可变的.
支持不可变性
不可变类:不可变类就是指这个类的实例一旦创建完成后,就不能改变其成员变量值。如JDK内部自带的很多不可变类:Interger、Long和String等。
可变类:相对于不可变类,可变类创建实例后可以改变其成员变量值,开发中创建的大部分类都属于可变类。
不可变类的优点 : 不可变类比可变类更加易于设计,实现和使用,不容易出错,更加安全.(不可变对象是线程安全的,在线程之间可以相互共享,不需要利用特殊机制来保证同步问题,因为对象的值无法改变。可以降低并发错误的可能性,因为不需要用一些锁机制等保证内存一致性问题也减少了同步开销)。
设计不可变类遵循的几条规则:
1.不提供任何会修改对象的方法.也就是改变对象属性的成员函数.
2.保证没有可被子类改写的方法,吧类设为final的.防止粗心或者恶意的接口改变成员的值,破坏该类的不可变.
3.使所有的域都是final的.通过强制的方式,如果一个指向新创建的实例的引用在缺乏同步机制的情况下,被一个线程传递到另一个线程,有可能要必须保证正确的行为
4.使所有的域都成为私有的.防止直接修改域中的信息,(不提倡)
5.保证对于任何可变组件的互斥访问.如果这个域中有可变的对象域,那么就必须要确保该类的客户无法获得指向这些对象的引用,不直接返回对象,而是返回对象的克隆(clone)
不可变对象的优缺点:
非可变对象本质上是线程安全的,他们不需要同步.多个线程访问这样的对象,也不会破坏,所以非可变对象可以被自由的共享,还可以共享内部信息.这样就永远都不需要进行保护性拷贝,其实也根本不需要做保护性拷贝,因为这些拷贝始终等于原始的对象,也不应该为不可变对象提供clone方法或者拷贝构造函数.但是就现在的java来说,还是提供了clone方法,比如String.非可变对象还还未其他的对象(可变的或者是非可变的)提供了大量大构建.
缺点:对每一个不同的值都要求单独的对象.创建的代价比较高.
总结:不可变类是实例创建后就不可以改变成员遍历的值。这种特性使得不可变类提供了线程安全的特性但同时也带来了对象创建的开销,每更改一个属性都是重新创建一个新的对象。JDK内部也提供了很多不可变类如Integer、Double、String等。String的不可变特性主要为了满足常量池、线程安全、类加载的需求。合理使用不可变类可以带来极大的好处。
复合优于继承(这里不包括接口的继承哦)
什么是复合?
在新的类中增加一个私有域,引用了这个已有类的实例,这种设计模式就称为复合.原来已经有的类变成了新类的一部分,新类中的方法都可以调用被包含的已有类实例中对应的方法,并返回结果这就是转发,这样得到的类是非常稳固的.不依赖于已有类的实现细节.
继承的缺点:继承打破了封装性,
如何从继承和复合之间做出选择?
比较抽象的说法是,只有子类和父类确实存在"is-a"关系的时候使用继承,否则使用复合。
或者比较实际点的说法是,如果TypeB只需要TypeA的部分行为,则考虑使用复合。
总结:
继承是实现代码重用的有力手段,但是继承违背了封装的原则,只有当子类和超类正真存在类型关系时,使用继承才最恰当,但是如果子类和超类不在同一个包中,并且超类不是为了扩展二设计的,那么继承会导致脆弱性,避免这种脆弱性可以用复合和转发机制来代替继承.
要么专门为了继承而设计,并给出文档,要么禁止继承
类的文档必须精确地描述改写某个方法所带来的影响:必须有文档说明其可改写的方法的自用性,对于每一个公有的或者受保护的方法或者构造函数,它的文档必须指明它调用了那些可改写的方法,是以什么顺序调用的,每个调用的结果又是如何影响后续的处理过程的方法.
好的API文档应该描述一个方法做了什么工作,并不是描述它是如何做到的.
为了允许继承,一个类要遵守一些约束.构造函数一定不能调用可被改写的方法,无论是直接进行还是间接进行,如果违反了这条规则很有可能导致程序失败,超类的构造函数在子类的构造函数之前运行.
如果为了继承而设计一个类,要求对这个类有一些实质上的限制:最好的解决方法是对于那些并非为了安全地进行子类化而设计和编写文档的类,禁止子类化.
接口优于抽象类
接口和抽象类的区别:
1.最明显的就是接口里面不能有方法的实现,而抽象类里面是可以有方法实现的;2.用extends继承抽象类,接口是用implements来实现接口;3.抽象类可以有构造器,接口不能有构造器;4.抽象类的访问修饰符可以是public,default,protecetd,接口的访问修饰符只能是public;5.抽象类可以有main方法,可以运行,接口不能有main方法,所以不能运行;6.抽象类方法可以继承一个类或实现多个方法,接口只可以继承一个或多个其它接口;7.抽象类比接口速度快,接口稍微慢一点,因为要去寻找它的实现方法;8.抽象类中添加新的方法可以有默认的实现,所以不需要改变现在的代码.接口中添加新方法要改变实现接口的类.
什么时候使用抽象类和接口
- 如果你拥有一些方法并且想让它们中的一些有默认实现,那么使用抽象类吧。
- 如果你想实现多重继承,那么你必须使用接口。由于Java不支持多继承,子类不能够继承多个类,但可以实现多个接口。因此你就可以使用接口来解决它。
- 如果基本功能在不断改变,那么就需要使用抽象类。如果不断改变基本功能并且使用接口,那么就需要改变所有实现了该接口的类。
接口只用于定义类型
当类实现接口时,接口就充当可以引用这个类的实例的类型
常量接口:这种接口不包含任何方法,只包含静态的final域,每个域都导出一个常量。
常量接口模式是对接口的不良使用,类在内部使用某些常量,这只是实现细节,实现常量接口会吧实现细节泄露到该类的导出API中。
在java平台类库中也有几个常量接口,例如:java.io.ObjectStreamConstants
接口应该只被用来定义类型,它们不应该被用来导出常量。
优先考虑静态成员类
嵌套类是指被定义在另一个类的内部的类,嵌套类存在的意义应该是只为它的外围类提供服务。
嵌套类有四种:静态成员类,非静态成员类,匿名类和局部类,除了第一种之外其他的三种都被称为内部类。
静态成员类:静态成员类是一种最简单的嵌套类,也就是普通的类,只是碰巧被声明在另一个类内部而已,可以访问外部类的所有成员,包括私有的成员,如果它被声明为私有的,那么只能在外围的内部才可以被访问。静态成员类和非静态成员类之间唯一的区别是:静态成员类的声明中包含修饰符static,语法非常相似,但是这两种嵌套类有很大的不同,非静态成员类的每一个实例都含有与外围类的一个外围实例紧密关联在一起,如果一个嵌套类的实例可以在它的外围类的实例之外独立存在,那么这个嵌套类不可能是一个非静态成员类,在没有外围实例的情况下,要想创建非静态成员类的实例是不可能的。
非静态成员类:当非静态成员类被创建的时候,它和外围实例之间的关联关系也被建立起来,这种关联关系以后也不能被修改,当外围类的某个实例方法的内部调用非静态成员类的构造函数时,这种关联关系被自动建立起来。非静态成员类的一种通常用法是定义一个Adupter,它允许外部类的一个实例被看做另一个不相关的类的实例,如果声明的成员类不要求访问外围实例,那么请记住把static修饰符放到成员类的声明中,使他成为一个静态成员类,而不是非静态成员类。如果在没有外围实例的情况,也要分配实例的话,则不能使用非静态成员类,因为非静态成员类的实例必须要有一个外围实例。
私有静态成员类的一种通常用法是用来代表外围类对象的组件,使用非静态成员来表示entry是很浪费的,私有的静态成员类是最佳选择,如果省略掉entry中的static。成员类是导出API中的一个元素,要想不违背二进制兼容性,就不能从非静态成员类变成静态成员类。
匿名类:匿名类没有名字,不是外围类的一个成员,不与其他成员一起被声明,而是在被使用的点上同时被声明和实例化,匿名类可以出现在代码中任何允许表达式出现的地方。如果匿名类出现在一个非静态的环境中,则它有一个外围实例。
匿名类:只能被用在代码中它将被实例化的那个点上,匿名类没有名字,所以它被实例化后,就不能够再对它们进行引用,如果不是这种情况就不能使用匿名类,匿名类的通常用法是创建一个函数对象,创建一个过程对象,第三种是在一个静态工厂方法的内部,第四种是在复杂的类型安全枚举类型中,用于公有的静态final域的初始化器中。局部类是最少使用的类,在任何可以声明局部变量的地方都可以声明局部类,并且局部类也遵守同样的作用域规则。 局部类有名字,可以重复使用,当且仅当局部类被用于非静态环境下的时候,才有外围实例,与匿名类一样,它们必须非常简短
总之如果一个嵌套类需要在单个方法之外仍然是可见的,或者太长了,不适合放在一个方法内部,那么应该使用成员类,如果成员类的每个实例都需要一个指向其外围实例的引用,则吧成员类做成非静态的,否则做成静态的。假设一个嵌套类属于一个方法内部,如果你只需要在一个地方创建它的实例,并且已经有了一个预先存在的类型可以说明这个类的特征,则吧它做成匿名类,否则就做成局部类。