扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
三个关键词访问修饰符:private(私有的=类级别的)、未指定(包级私有的)、protected(受保护的=继承级别的+包级别的访问)、pulbic(共有的)
怀远网站建设公司成都创新互联,怀远网站设计制作,有大型网站制作公司丰富经验。已为怀远上千提供企业网站建设服务。企业网站搭建\成都外贸网站建设要多少钱,请找那个售后服务好的怀远做网站的公司定做!备注:其中未指定,使用的是默认的访问级别,包内部的任何类都可以访问这个成员。如果类或者接口是包级私有的,就应该做成包级私有的。包级私有的是这个包实现的一部分,而不是这个报API的一部分,包级私有的可以更改实现、修改或去除,不必担心伤害到客户,如果是共有的,你就要永远支持它,并且保持兼容性。
经验表明,尽可能地使每一个类或成员都不被外界访问。即,在保证软件功能正确的前提下,使用最低的访问级别。
公有类不应该包含公有域,除了后面的共有静态final域的特殊情形——通过公有域的静态final域来暴露类的常量。按照惯例,这样的域的名字由大写字母构成,单词之间用下划线隔开(见三十八)。很重要的一点是,这个域要么包含原语类型的值,要么包含指向非可变对象的引用(见十三)。
注意:非零长度的数组总是可变的,所以具有共有静态final数据域几乎总是错误的。如果一个类包含这样的一个域,客户能够修改数组中的内容。这是安全漏洞的一个常见根源:
//潜在的安全漏洞public static final Type[] VALUES={……};
共有数组应该被替换成私有数组,以及一个共有的非可变列表:
private static final Type[] PRIVATE_VALUES={……};
public static final List VALUES=
Collection.unmodifiableList(Arrays.asList(PRIVATE_VALUES))
非可变性类是一个简单的类,它的实例不能被修改。每个实例中包含的所有信息都必须在该实例被创建的时候就提供出来,并且在对象的整个生命周期保持不变。Java平台库包含许多非可变类,其中String、原语类型的包装类、BigInteger和BigDecimal。
非可变类要遵循的五条规则:
如:public static final Complext ZERO=new Complex(0,0);
这种方法可以进一步扩展,一个非可变对象可以提供一些静态工厂,它们吧频繁用到的实例缓存起来,当请求一个预先存在的实例的时候,可以不再创建新的实例。BigInteger和Boolean都有这样的静态工厂。使用这样的静态工厂可以使得客户之间可以共享已有的实例,而不是创建新的实例,从而降低内存占用和垃圾回收的代价。
注:StringBuffer是String类的可变配套类,在特定的环境下,相对BigInteger而言,BigSet是String类的可变配套类。StringBuffer是可变对象,可以对字符串进行改写,主要是insert和append两个方法,用于多线程。而StringBuilder是JDK1.5之后才加入的,和StringBuffer没有本质区别,但是在单线程的情况下使用,速度更快。
下面是延迟初始化技术的习惯用法:
//不可变对象的缓存,延迟初始化函数private volatile Foo cacheFooVal=UNLIKE_FOO_VALUE
public Foo foo(){
Foo result=cachedFooVal;
if(result==UNLIKE_FOO_VALUE)
result=cachedFooVal=fooVal();
return result;
}
//fool值的私有帮助函数private Fool fooVal(){}
与方法不同的是,继承打破了封装性。能用组合完成的就用组合,组合优先于继承。
用组合的方式可以避免有不合适的继承所带来的问题。使用组合不在扩展一个已有的类,而是在新类中增加一个私有域,它引用了这个类的一个实例。新类中的每个实例方法都可以调用被包含已有实例中对应的方法,并返回它的结果。这被称为转发(forwarding),新类中的方法被称为转发方法(forwarding method)。这样的类将会非常稳固,它不依赖已有类的实现细节。即使已有的类增加了新的方法,也不会影响新的类。
// 使用组合取代继承的包装类public class InstrumentedSet implements Set {
private final Set s;
private int addCount = 0;
public InstrumentedSet(Set s) {
this.s = s;
}
public boolean add(Object o) {
addCount++;
return s.add(o);
}
public boolean addAll(Collection c) {
addCount+= c.size();
return s.addAll(c);
}
public int getAddCount() {
return addCount;
}
// 转发方法 public void clear() { s.clear(); }
public boolean contains(Object o) { return s.contains(o); }
public boolean isEmpty() { return s.isEmpty(); }
public int size() { return s.size(); }
public Iterator iterator() { return s.iterator(); }
public boolean remove(Object o) { return s.remove(o); }
public boolean containsAll(Collection c)
{return s.containsAll(c); }
public boolean removeAll(Collection c)
{return s.removeAll(c); }
public boolean retainAll(Collection c)
{return s.retainAll(c); }
public Object[] toArray() { return s.toArray(); }
public Object[] toArray(Object[] a) { return s.toArray(a); }
public boolean equals(Object o) { return s.equals(o); }
public int hashCode() { return s.hashCode(); }
public String toString() { return s.toString(); }
}
这里的包装类可以用来包装任何一个Set实现,并且可以与任何以前已有的构造函数一起工作。如
Set s1=new InstrumentedSet(new TreeSet(list));
Set s2=new InstrumentedSet(new HashSet(capacity, loadFactor));
因为一个Instrument实例都把另一个 Set实例包装了起来,所以我们称其为包装类。这也正是装饰模式,因为Instrument对一个集合进行了修饰,为它增加了计数器的特性。有时候,修改和转发这两项技术的结合被错误的引用为“委托(delegation)”,从技术角度讲,这不是委托,除非包装类把自己传递给一个被包装的对象。
对于专门为继承而设计的类而言,需要满足:
注:如果违反了第三条规则,很有可能会导致程序失败。超类的构造函数在子类的构造函数之前运行,所以子类中改写版本的方法将会在子类的构造函数运行之前先被调用。如果改写版本的方法依赖于子类构造函数所执行的初始化工作,那么该方法就不会如期执行。
public class Super {
// 违反了规则 -构造函数调用了重写的方法 public Super() {
m();
}
public void m() {
}
}
下面的子类改写了方法m,Super唯一的构造函数就错误的调用了这个方法m:
final class Sub extends Super {
private final Date date; // 空的终结字段,由构造函数设置
Sub() {
date= new Date();
}
// 重写了 Super.m, 被Super的构造函数调用 public void m() {
System.out.println(date);
}
public static void main(String[] args) {
Sub s= new Sub();
s.m();
}
本来期望打印出两个日期,但是第一次打印出Null,因为方法被构造函数Super()调用的时候,造函数Sub还没有机会初始化data域。
这个的执行顺序是父类的构造函数->重写的方法(回到子类)->子类的构造函数。
接口和抽象类都是允许多个实现的类型。两者的区别是抽象类允许包含某些方法的实现,但是接口不允许。实现一个抽象类的类型,它必须成为抽象类的子类。因为Java只允许单继承,所以抽象类作为类型定义收到了极大的限制。
下面是一个静态工厂方法,它包含了一个静态的工厂方法,它包含一个完整的、功能全面的List实现:
//整形数组的List适配器static List intArrayAsList(final int[] a){
if(a==null)
throw new NullPointerException();
return new AbstractList(){
public Object get(int i){
return new Integer(a[i]);
}
public int size(){
return a.length;
}
public Object set(int i, Object o){
int oldVal=a[i];
a[i]=((Integer)o).intValue();
return new Integer(oldVal);
}
};
}
这个例子是一个适配器模式,它使得int数组可以被看做一个Integer实例列表。这个例子只提供了一个静态工厂,并且这个类是一个可被访问的匿名类,它被隐藏在静态工厂的内部。
下面是Map.Entry接口的骨架实现类:
public abstract class AbstractMapEntry implements Map.Entry {
// 基本的 public abstract Object getKey();
public abstract Object getValue();
// 要改变maps的实体必须要重写的方法 public Object setValue(Object value) {
throw new UnsupportedOperationException();
}
// 实现Map.Entry.equals的通用约定 public boolean equals(Object o) {
if (o == this)
return true;
if (! (o instanceof Map.Entry))
return false;
Map.Entry arg= (Map.Entry)o;
return eq(getKey(), arg.getKey()) &&
eq(getValue(), arg.getValue());
}
private static boolean eq(Object o1, Object o2) {
return (o1 == null ? o2 == null : o1.equals(o2));
}
// 实现Map.Entry.hashCode的通用约定 public int hashCode() {
return
(getKey()== null ? 0 : getKey().hashCode()) ^
(getValue()== null ? 0 : getValue().hashCode());
}
}
当一个类实现了一个接口的时候,这个接口被用做一个类型。通过这个类型可以引用这个类的实例。因此,一个类实现了某个接口,就表明客户可以对这个类的实例实施某些动作。为了其他的目的而定义的接口是不合适的。
常量接口是对接口的不良使用,下面是常量接口的例子:
// 常量接口模式- 请勿使用!public interface PhysicalConstants {
// Avogadro's number (1/mol) static final double AVOGADROS_NUMBER = 6.02214199e23;
// Boltzmann constant (J/K) static final double BOLTZMANN_CONSTANT = 1.3806503e-23;
// Mass of the electron (kg) static final double ELECTRON_MASS = 9.10938188e-31;
}
导出常量的几种可行方案:
// 常量工具类public class PhysicalConstants {
private PhysicalConstants() { }// 防止实例化
public static final double AVOGADROS_NUMBER = 6.02214199e23;
public static final double BOLTZMANN_CONSTANT = 1.3806503e-23;
public static final double ELECTRON_MASS = 9.10938188e-31;
}
总之,接口是被用来定义类型的,它们不应该被导出常量。
嵌套类(nested class)是指被定义在一个类的内部的类。嵌套类存在的目的是为外围的类提供服务。
嵌套类有四种:静态成员类(static member class)、非静态成员类(nostatic member class)、匿名类(anonymous class)和局部类(local class)。
除了第一种之外,其他三种都被成为内部类(inner class)。静态成员类是一种简单的嵌套类,最好把它看做一个普通类,只是碰巧被声明在类的内部而已。
非静态成员的另一个用法是定义一个Adapter,它允许外部类的一个实例被看做另一个不相关的实例。如,Map接口的实现往往使用非静态成员类来实现它们的集合视图(collection view),这些集合视图是有Map的keySet、entrySet和Value方法返回的。类似地,诸如Set和List这样的集合接口的实现往往也使用非静态成员类来实现它们的迭代器。
//非静态成员的典型用法public class MySet extends AbstractSet{
……//省去不相关的public Iterator iterator(){
return MyIterator();
}
priavateclass MyIterator implements Iterator{
……
}
}
如果你声明的成员类不要求访问外围实例,那么请记住把static修饰符放到成员类的声明中。
非静态成员类需要访问外围实例,如果省略了static修饰符,则每个实例都将包含一个额外的指向外围实例的引用,维护这份引用需要耗费时间和空间,但又没有相应的好处。
匿名类仅仅在使用的时候被声明和实例化,行为与静态成员类或非静态成员类非常相似,取决于它所处的环境:如果匿名类出现在一个非静态的环境中,则它有一个外围实例。
匿名类通常出现在表达式的中间,可能20行或者更短,太长影响到程序的可读性。
匿名类的一个通用方法是创建一个函数对象(function object),比如Comparator实例。例如,下面的方法调用对一组字符串按照其长度进行排序:
//匿名类的典型使用Arrays.sort(args, new Comparator(){
public int compare(Object o1, Object o2){
return ((String)o1).length()-((String)o2).length();
}
});
匿名类的另一个用法是创建一个过程对象(process object),比如Thread、Runable或者TimeTask实例。第三个常见用法是在一个静态工厂方法的内部(见十六intArrayAsList方法)。第四个常见的用法是在复杂的类型安全枚举类型(它要求为每个实例提供单独的子类)中,用于公有的静态final域的初始化器中(见二十一Operation类)。如果Operation类是Calculator的一个静态成员类,那么Operation类是双重嵌套类。
// 公有静态成员类的典型使用public class Calculator {
public static abstract class Operation {
private final String name;
Operation(String name) {this.name = name; }
public String toString() { return this.name; }
// 通过这一常量进行运算符表示 abstract double eval(double x, double y);
// 双重嵌套匿名类 public static final Operation PLUS =new Operation("+") {
double eval(double x, double y) { return x + y; }
};
public static final Operation MINUS = new Operation("-") {
double eval(double x, double y) { return x - y; }
};
public static final Operation TIMES = new Operation("*") {
double eval(double x, double y) { return x * y; }
};
public static final Operation DIVIDE =new Operation("/") {
double eval(double x, double y) { return x / y; }
};
}
// 返回指定的计算结果 public double calculate(double x, Operation op, double y) {
return op.eval(x, y);
}
}
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流