【04】Effective Java - 类和接口-程序员宅基地

技术标签: java  

1、使类和成员的可访问性最小化

      封装是软件设计的基本原则之一,它的好处就是解除组成系统的各个模块之间的耦合关系,使得这些模块可以独立地开发、测试、优化、使用、理解 修改,同时可以有效地进行性能调节。

(1)可见性

A、私有的

B、包级私有的

C、受保护的

D、公有的


(2)实例域不能是公有的

   包含公有可变域的类并不是线程安全的


(3)公有静态final域

A、常量通常用公有静态域来暴露,大写加下划线

B、如果final域包含可变对象的引用,便具有非final域的所有缺点,虽然引用自身不能被修改,但是所引用对象的内容却可以被修改,这会导致灾难性的后果

C、类具有公有的静态final数据域,或者返回这种域的访问方法,几乎总是错误的,这是安全漏洞的一个常见根源。

//potential security hole
public static final Thing[] VALUES = {...};

改进方法一

private static final Thing[] PRIVATE_VALUES = {...};
public static final List<Thing> VALUES =
   Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));

改进方法二

private static final Thing[] PRIVATE_VALUES = {...};
public static final Thing[] values(){
    return PRIVATE_VALUES.clone();
}


2、在公有类中使用访问方法而非公有域
(1)杜绝退化类

数据域是public,没有提供访问方法

public class Point {
    public double x;
    public double y;
}

  如果类可以在它所在的包外进行访问,或提供访问方法,以保留将来改变该类的内部表示法的灵活性。如果共有类暴露了它的数据域,要想在将来改变其内部表示法是不可能的,因为公有类的客户端代码已经遍布各处了。

public class Point {
    private double x;
    private double y;

    public Point(double x, double y) {
        this.x = x;
        this.y = y;
    }

    public double getX(){
       return x;
    } 
    
    public double getY(){
       return y;
    }
    
    public void setX(double x){
       this.x = x;
    }
    
    public void setY(double y){
       this.y = y;
    }
}

(2)原则

  公有类永远都不应该暴露可变的域。


3、使可变量最小化
(1)使类变为不可变的规则

A、不要提供任何会修改对象状态的方法

B、保证类不会被扩展

C、使所有域都是final的

D、使所有的域都称为私有的

E、确保对任何可变组件的互斥访问

    永远不要用客户端提供的对象引用来初始化可变对象的域


(2)函数风格

该函数返回一个运算结果,该运算并不会改变它的状态。一般不可变类都返回一个新的运算实例,而非修改原来的实例。

public final class Complex {
    private final double re;
    private final double im;

    private Complex(double re, double im) {
        this.re = re;
        this.im = im;
    }

    public static Complex valueOf(double re, double im) {
        return new Complex(re, im);
    }

    public static Complex valueOfPolar(double r, double theta) {
        return new Complex(r * Math.cos(theta),
                           r * Math.sin(theta));
    }

    public static final Complex ZERO = new Complex(0, 0);
    public static final Complex ONE  = new Complex(1, 0);
    public static final Complex I    = new Complex(0, 1);

    // Accessors with no corresponding mutators
    public double realPart()      { return re; }
    public double imaginaryPart() { return im; }

    public Complex add(Complex c) {
        return new Complex(re + c.re, im + c.im);
    }

    public Complex subtract(Complex c) {
        return new Complex(re - c.re, im - c.im);
    }

    public Complex multiply(Complex c) {
        return new Complex(re * c.re - im * c.im,
                           re * c.im + im * c.re);
    }

    public Complex divide(Complex c) {
        double tmp = c.re * c.re + c.im * c.im;
        return new Complex((re * c.re + im * c.im) / tmp,
                           (im * c.re - re * c.im) / tmp);
    }

    @Override public boolean equals(Object o) {
       if (o == this)
           return true;
       if (!(o instanceof Complex))
           return false;
       Complex c = (Complex) o;

       // See page 43 to find out why we use compare instead of ==
       return Double.compare(re, c.re) == 0 &&
              Double.compare(im, c.im) == 0;
    }
 
   @Override public int hashCode() {
        int result = 17 + hashDouble(re);
        result = 31 * result + hashDouble(im);
        return result;
    }

    private int hashDouble(double val) {
        long longBits = Double.doubleToLongBits(re);
        return (int) (longBits ^ (longBits >>> 32));
    }

    @Override public String toString() {
        return "(" + re + " + " + im + "i)";
    }
}

(3)优缺点

A、线程安全,可以被自由共享

B、可以提供静态工厂方法,缓存实例,提升性能

C、无需clone方法,也不该有clone方法

缺点就是:

对于每个不同的值都需要一个单独的对象


(4)坚决不要为每个get方法提供set方法

除非有很好的理由让类成为可变的类,否则就应该是不可变的


4、组合优先于继承
(1)继承破坏封装
public class InstrumentedHashSet<E> extends HashSet<E> {
    // The number of attempted element insertions
    private int addCount = 0;

    public InstrumentedHashSet() {
    }

    public InstrumentedHashSet(int initCap, float loadFactor) {
        super(initCap, loadFactor);
    }

    @Override public boolean add(E e) {
        addCount++;
        return super.add(e);
    }

    //超类方法可能是自引用的,super.addAll引用了add方法,因为子类覆盖了add方法,将改为调用子类的add方法,因而count被重复计数了
    @Override public boolean addAll(Collection<? extends E> c) {
        addCount += c.size();
        return super.addAll(c);
    }

    public int getAddCount() {
        return addCount;
    }

    public static void main(String[] args) {
        InstrumentedHashSet<String> s =
            new InstrumentedHashSet<String>();
        s.addAll(Arrays.asList("Snap", "Crackle", "Pop"));    
        System.out.println(s.getAddCount());
    }
}

(2)组合

public class ForwardingSet<E> implements Set<E> {
    private final Set<E> s;
    public ForwardingSet(Set<E> s) { this.s = s; }

    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<E> iterator()     { return s.iterator();  }
    public boolean add(E e)           { return s.add(e);      }
    public boolean remove(Object o)   { return s.remove(o);   }
    public boolean containsAll(Collection<?> c)
                                   { return s.containsAll(c); }
    public boolean addAll(Collection<? extends E> c)
                                   { return s.addAll(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 <T> T[] toArray(T[] a)      { return s.toArray(a); }
    @Override public boolean equals(Object o)
                                       { return s.equals(o);  }
    @Override public int hashCode()    { return s.hashCode(); }
    @Override public String toString() { return s.toString(); }
}

(2)组合不是委托

InstrumentedHashSet把HashSet包装了起来,成为包装类,属于组合的方式,但不是委托,委托要求包装对象把自身传递给被包装对象。

同时包装类不适合在回调框架里头用。



5、要么为继承而设计,要么禁止
(1)构造器决不能调用可被覆盖的方法

   无论是直接调用,还是间接调用

public class Super {
    // Broken - constructor invokes an overridable method
    public Super() {
        overrideMe();
    }
    public void overrideMe() {
    }
}

public final class Sub extends Super {
    private final Date date; // Blank final, set by constructor

    Sub() {
        date = new Date();
    }

    // Overriding method invoked by superclass constructor
    @Override public void overrideMe() {
        System.out.println(date);
    }

    public static void main(String[] args) {
        Sub sub = new Sub();
        sub.overrideMe();
    }
}

  打印两次,第一次为null,因为overrideMe被super构造器调用的时候,Sub构造器还没有机会初始化date域。


(2)clone还是readObject方法都不可以调用可覆盖的方法

实现Cloneable或者Serializable接口时。

对于readObject方法,覆盖版本的方法将在子类状态被反序列化之前被运行

对于clone方法,覆盖版本的方法则是在子类的clone方法有机会修正被克隆对象的状态之前被运行



6、接口优于抽象类
(1)接口允许我们构造非层次结构的类型框架

    接口是定义mixin的理想选择,避免组合爆炸,类层次臃肿,因为Java是单继承,因而接口优于抽象类


(2)骨架实现
public abstract class AbstractMapEntry<K,V>
        implements Map.Entry<K,V> {
    // Primitive operations
    public abstract K getKey();
    public abstract V getValue();

    // Entries in modifiable maps must override this method
    public V setValue(V value) {
        throw new UnsupportedOperationException();
    }

    // Implements the general contract of Map.Entry.equals
    @Override public boolean equals(Object o) {
        if (o == this)
            return true;
        if (! (o instanceof Map.Entry))
            return false;
        Map.Entry<?,?> arg = (Map.Entry) o;
        return equals(getKey(),   arg.getKey()) &&
               equals(getValue(), arg.getValue());
    }
    private static boolean equals(Object o1, Object o2) {
        return o1 == null ? o2 == null : o1.equals(o2);
    }

    // Implements the general contract of Map.Entry.hashCode
    @Override public int hashCode() {
        return hashCode(getKey()) ^ hashCode(getValue());
    }
    private static int hashCode(Object obj) {
        return obj == null ? 0 : obj.hashCode();
    }
}


7、接口只用于定义类型
(1)常量接口是对接口的不良使用
public interface ObjectStreamConstants {

    /**
     * Magic number that is written to the stream header.
     */
    final static short STREAM_MAGIC = (short)0xaced;

    /**
     * Version number that is written to the stream header.
     */
    final static short STREAM_VERSION = 5;

    /* Each item in the stream is preceded by a tag
     */

    /**
     * First tag value.
     */
    final static byte TC_BASE = 0x70;
    
    ...
}

(2)工具类版本
public class PhysicalConstants {
  private PhysicalConstants() { }  // Prevents instantiation

  // Avogadro's number (1/mol)
  public static final double AVOGADROS_NUMBER   = 6.02214199e23;

  // Boltzmann constant (J/K)
  public static final double BOLTZMANN_CONSTANT = 1.3806503e-23;

  // Mass of the electron (kg)
  public static final double ELECTRON_MASS      = 9.10938188e-31;
}

   现在一般建议用枚举。


8、类层次优于标签类
(1)标签类太过臃肿
class Figure {
    enum Shape { RECTANGLE, CIRCLE };

    // Tag field - the shape of this figure
    final Shape shape;

    // These fields are used only if shape is RECTANGLE
    double length;
    double width;

    // This field is used only if shape is CIRCLE
    double radius;

    // Constructor for circle
    Figure(double radius) {
        shape = Shape.CIRCLE;
        this.radius = radius;
    }

    // Constructor for rectangle
    Figure(double length, double width) {
        shape = Shape.RECTANGLE;
        this.length = length;
        this.width = width;
    }

    double area() {
        switch(shape) {
          case RECTANGLE:
            return length * width;
          case CIRCLE:
            return Math.PI * (radius * radius);
          default:
            throw new AssertionError();
        }
    }
}

(2)类层次简洁
abstract class Figure {
    abstract double area();
}

  类层次

class Circle extends Figure {
    final double radius;

    Circle(double radius) { this.radius = radius; }

    double area() { return Math.PI * (radius * radius); }
}

class Rectangle extends Figure {
    final double length;
    final double width;

    Rectangle(double length, double width) {
        this.length = length;
        this.width  = width;
    }
    double area() { return length * width; }
}

class Square extends Rectangle {
    Square(double side) {
        super(side, side);
    }
}

  杜绝了switch case遗漏的可能,同时也可以便于扩展。


9、用函数对象表示策略
(1)只使用一次
Arrays.sort(stringArray,new Comparator<String>(){
   public int compare(String s1,String s2){
      return s1.length() - s2.length();
   }
});
(2)使用多次
class Export{
   private static class StrLenCmp implements Comparator<String>,Serializable{
      public int compare(String s1,String s2){
         return s1.length() - s2.length();
      }
   }
   
   public static final Comparator<String> STRING_LENGTH_COMPARATOR = new StrLenCmp();
}

   使用匿名类的方式,将会在每次执行调用的时候创建一个新的实例,如果它被重复执行,则考虑将函数对象存储到一个私有的静态final域中,并重用它。


10、优先考虑静态成员类
(1)嵌套类

      嵌套类指被定义在另一个类的内部的类,其存在的目的是只为其外围类提供服务(外围类的辅助类)。除A外,其他都可以称为内部类(inner class)。

A、静态成员类(static member class)

   常见用法是作为公有的辅助类。

public class Calculator {

    public static enum Operation{
        PLUS,MINUS;
    }

    public static class OpEnum{
        public static final String PLUS = "+";
        public static final String MINUS = "-";
    }
}

//for demo
System.out.println(Calculator.Operation.MINUS);
System.out.println(Calculator.OpEnum.MINUS);

   JDK实例

public class LinkedList<E> extends AbstractSequentialList<E> 
   …;
   private static class Entry<E> {
        E element;
        Entry<E> next;
        Entry<E> previous;

        Entry(E element, Entry<E> next, Entry<E> previous) {
            this.element = element;
            this.next = next;
            this.previous = previous;
        }
    }
    …;
}


B、非静态成员类(nonstatic member class)

其每一个实例都隐含着与外围类的一个外围实例相关联,常见的用法就是定义一个Adapter,允许外部类的实例被看做是另外一个不相关的类的实例。

成员类的显著特性就是成员类能访问它的外部类实例的任意字段与方法。方便一个类对外提供一个公共接口的实现是成员类的典型应用。

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
    private class Itr implements Iterator<E> {
         ………;
    }

     public Iterator<E> iterator() {
        return new Itr();
     }
}

以JDK Collection类库为例,每种Collection类必须提供一个与其对应的Iterator实现以便客户端能以统一的方式遍历任一Collection实例。每种Collection类的Iterator实现就被定义为该Collection类的成员类。

注意,如果声明成员类不要求访问外围实例,则用static修饰,否则每一个实例都将包含一个额外的指向外围对象的引用,保存这引用要消耗时间和空间,并且会导致外围实例在符合垃圾回收时却仍然得以保留。


C、匿名类(annoymous class)

没有类名的局部类就是匿名类。用一条语句完成匿名类的定义与实例创建。在使用的同时被声明和实例化。

可以出现在代码中任何允许出现表达式的地方,当且仅当匿名类出现在非静态环境中时,才有外围实例。

常见的用法就是用来动态创建函数对象和过程对象(Runnable,Thread,TimerTask)。

queryBtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String day = dateBtn.getCurrentSimpleDateFormat().format(dateBtn.getDate());
                Integer col = Integer.valueOf(box.getSelectedItem().toString());
                ShifenTableGUI gui = new ShifenTableGUI(day, col);
                gui.show();
            }
});

Arrays.sort(stringArray,new Comparator<String>(){
   public int compare(String s1,String s2){
      return s1.length() - s2.length();
   }
});

  创建过程对象

 public static void main(String[] args){
        Thread[] threads = new Thread[THREADS_COUNTS];

        for(int i=0;i<THREADS_COUNTS;i++){
            threads[i] = new Thread(new Runnable(){
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+",start");
                    for(int j=0;j<10000;j++){
                        increase();
                    }
                    latch.countDown();
                }
            });
            threads[i].start();
        }

        //等待所有累加线程都结束
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(race);
    }


D、局部类(local class)

四种嵌套类中使用最少的类,在任何可以声明局部变量的地方,都可以使用。

对一个静态成员类,去掉其声明中的“static”关键字,将其定义移入其外部类的静态方法或静态初始化代码段中就成为了局部静态成员类。对一个成员类,将其定义移入其外部类的实例方法或实例初始化代码中就成为了局部成员类。

内部类只在定义它的代码段中可见,不能在它所属代码段之外的代码中使用;因此也就没有public/private/default权限修饰符(无意义);不能以局部类形式定义一个接口。局部类只在其所属代码段中可见,定义这样的接口无意义;局部类类名不能与其外部类类名重复。

public class Outer {
    private int instanceField; 
    private static int staticField; 
    
    //define a local member class in instance code block
    {
        int localVirable1 = 0;
        final int localVirable2 = 1;
        class Inner1 {
            public Inner1() {
                //can access its outer class' field and method directly
                instanceField = 1;
                //use OuterClass.this to get its corresponding outer class instance
                Outer.this.instanceField = 1;
                
                //can not access the not final local virable in its containing code block
                //System.out.print(localVirable1);
                
                //can access the final local virable in its containing code block
                System.out.print(localVirable2);
            }
        }        
        
        //local class can not have privilege modifier 
        /*public class inner2 {            
        }*/
    }
    
    // define a local static member class in static code block
    static {
        class Inner2 {
            public Inner2() {
                staticField = 1;
                //can not access instance field and method in a local static member class 
                //intanceField = 2;
            }
        }
    }
    
    public void intanceMethod() {
        //define a local class in its out class' instance method
        class Inner3 {
        }
        
        //local class is visible only in its containning code block
        //Outer.Inner2 inner2;
    }
    
    private static void staticMethod() {
        //define a local static member class in its out class' static method
        class Inner4 {    
            public Inner4() {
                staticField = 2;
            }
        }
        
        //can not define a interface as a local class
        /*interface I {
        }*/
    }
}


转载于:https://my.oschina.net/scipio/blog/288142

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_33966365/article/details/91848463

智能推荐

oracle 12c 集群安装后的检查_12c查看crs状态-程序员宅基地

文章浏览阅读1.6k次。安装配置gi、安装数据库软件、dbca建库见下:http://blog.csdn.net/kadwf123/article/details/784299611、检查集群节点及状态:[root@rac2 ~]# olsnodes -srac1 Activerac2 Activerac3 Activerac4 Active[root@rac2 ~]_12c查看crs状态

解决jupyter notebook无法找到虚拟环境的问题_jupyter没有pytorch环境-程序员宅基地

文章浏览阅读1.3w次,点赞45次,收藏99次。我个人用的是anaconda3的一个python集成环境,自带jupyter notebook,但在我打开jupyter notebook界面后,却找不到对应的虚拟环境,原来是jupyter notebook只是通用于下载anaconda时自带的环境,其他环境要想使用必须手动下载一些库:1.首先进入到自己创建的虚拟环境(pytorch是虚拟环境的名字)activate pytorch2.在该环境下下载这个库conda install ipykernelconda install nb__jupyter没有pytorch环境

国内安装scoop的保姆教程_scoop-cn-程序员宅基地

文章浏览阅读5.2k次,点赞19次,收藏28次。选择scoop纯属意外,也是无奈,因为电脑用户被锁了管理员权限,所有exe安装程序都无法安装,只可以用绿色软件,最后被我发现scoop,省去了到处下载XXX绿色版的烦恼,当然scoop里需要管理员权限的软件也跟我无缘了(譬如everything)。推荐添加dorado这个bucket镜像,里面很多中文软件,但是部分国外的软件下载地址在github,可能无法下载。以上两个是官方bucket的国内镜像,所有软件建议优先从这里下载。上面可以看到很多bucket以及软件数。如果官网登陆不了可以试一下以下方式。_scoop-cn

Element ui colorpicker在Vue中的使用_vue el-color-picker-程序员宅基地

文章浏览阅读4.5k次,点赞2次,收藏3次。首先要有一个color-picker组件 <el-color-picker v-model="headcolor"></el-color-picker>在data里面data() { return {headcolor: ’ #278add ’ //这里可以选择一个默认的颜色} }然后在你想要改变颜色的地方用v-bind绑定就好了,例如:这里的:sty..._vue el-color-picker

迅为iTOP-4412精英版之烧写内核移植后的镜像_exynos 4412 刷机-程序员宅基地

文章浏览阅读640次。基于芯片日益增长的问题,所以内核开发者们引入了新的方法,就是在内核中只保留函数,而数据则不包含,由用户(应用程序员)自己把数据按照规定的格式编写,并放在约定的地方,为了不占用过多的内存,还要求数据以根精简的方式编写。boot启动时,传参给内核,告诉内核设备树文件和kernel的位置,内核启动时根据地址去找到设备树文件,再利用专用的编译器去反编译dtb文件,将dtb还原成数据结构,以供驱动的函数去调用。firmware是三星的一个固件的设备信息,因为找不到固件,所以内核启动不成功。_exynos 4412 刷机

Linux系统配置jdk_linux配置jdk-程序员宅基地

文章浏览阅读2w次,点赞24次,收藏42次。Linux系统配置jdkLinux学习教程,Linux入门教程(超详细)_linux配置jdk

随便推点

matlab(4):特殊符号的输入_matlab微米怎么输入-程序员宅基地

文章浏览阅读3.3k次,点赞5次,收藏19次。xlabel('\delta');ylabel('AUC');具体符号的对照表参照下图:_matlab微米怎么输入

C语言程序设计-文件(打开与关闭、顺序、二进制读写)-程序员宅基地

文章浏览阅读119次。顺序读写指的是按照文件中数据的顺序进行读取或写入。对于文本文件,可以使用fgets、fputs、fscanf、fprintf等函数进行顺序读写。在C语言中,对文件的操作通常涉及文件的打开、读写以及关闭。文件的打开使用fopen函数,而关闭则使用fclose函数。在C语言中,可以使用fread和fwrite函数进行二进制读写。‍ Biaoge 于2024-03-09 23:51发布 阅读量:7 ️文章类型:【 C语言程序设计 】在C语言中,用于打开文件的函数是____,用于关闭文件的函数是____。

Touchdesigner自学笔记之三_touchdesigner怎么让一个模型跟着鼠标移动-程序员宅基地

文章浏览阅读3.4k次,点赞2次,收藏13次。跟随鼠标移动的粒子以grid(SOP)为partical(SOP)的资源模板,调整后连接【Geo组合+point spirit(MAT)】,在连接【feedback组合】适当调整。影响粒子动态的节点【metaball(SOP)+force(SOP)】添加mouse in(CHOP)鼠标位置到metaball的坐标,实现鼠标影响。..._touchdesigner怎么让一个模型跟着鼠标移动

【附源码】基于java的校园停车场管理系统的设计与实现61m0e9计算机毕设SSM_基于java技术的停车场管理系统实现与设计-程序员宅基地

文章浏览阅读178次。项目运行环境配置:Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。项目技术:Springboot + mybatis + Maven +mysql5.7或8.0+html+css+js等等组成,B/S模式 + Maven管理等等。环境需要1.运行环境:最好是java jdk 1.8,我们在这个平台上运行的。其他版本理论上也可以。_基于java技术的停车场管理系统实现与设计

Android系统播放器MediaPlayer源码分析_android多媒体播放源码分析 时序图-程序员宅基地

文章浏览阅读3.5k次。前言对于MediaPlayer播放器的源码分析内容相对来说比较多,会从Java-&amp;amp;gt;Jni-&amp;amp;gt;C/C++慢慢分析,后面会慢慢更新。另外,博客只作为自己学习记录的一种方式,对于其他的不过多的评论。MediaPlayerDemopublic class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal..._android多媒体播放源码分析 时序图

java 数据结构与算法 ——快速排序法-程序员宅基地

文章浏览阅读2.4k次,点赞41次,收藏13次。java 数据结构与算法 ——快速排序法_快速排序法