【04】Effective Java - 类和接口

为什么80%的码农都做不了架构师?>>>   hot3.png

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 mutatorspublic 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 insertionsprivate 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 methodpublic Super() {overrideMe();}public void overrideMe() {}
}public final class Sub extends Super {private final Date date; // Blank final, set by constructorSub() {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 operationspublic abstract K getKey();public abstract V getValue();// Entries in modifiable maps must override this methodpublic 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 figurefinal Shape shape;// These fields are used only if shape is RECTANGLEdouble length;double width;// This field is used only if shape is CIRCLEdouble radius;// Constructor for circleFigure(double radius) {shape = Shape.CIRCLE;this.radius = radius;}// Constructor for rectangleFigure(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() {@Overridepublic 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(){@Overridepublic 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 directlyinstanceField = 1;//use OuterClass.this to get its corresponding outer class instanceOuter.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 blockSystem.out.print(localVirable2);}}        //local class can not have privilege modifier /*public class inner2 {            }*/}// define a local static member class in static code blockstatic {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 methodclass 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 methodclass Inner4 {    public Inner4() {staticField = 2;}}//can not define a interface as a local class/*interface I {}*/}
}


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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/461157.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java 圆的交点_java – 获取线条和形状的交点

理念您可以使用getPathIterator()方法将GenenralPath解构为其段(移动到,行到,四到,立方到,关闭).现在,您可以搜索每个线段与线的交叉点.public static Point[] getIntersections(Path path, Line line) {List intersections new ArrayList();PathIterator it path.getPathIte…

OpenGL运用辅佐库创立规矩几许目标

辅佐类分类&#xff1a; 1&#xff09;窗口初始化函数 2&#xff09;窗口处置和工作处置函数 3&#xff09;定义场景制造循环函数 4&#xff09;三围物体制造函数 5&#xff09;颜色索引表装入函数 6&#xff09;空闲工作处置函数 下面描写了一个程序&#xff0c;该程序尽可以包…

input子系统详解2——应用层代码实践

以下内容源于朱有鹏嵌入式课程的学习与整理&#xff0c;如有侵权请告知删除。 一、编程步骤总结 步骤1&#xff1a;确定设备文件名字 步骤2&#xff1a;使用标准接口打开与读取设备文件 步骤3&#xff1a;解析struct input_event 二、编程步骤分析 1、确定设备文件名 应用层操作…

构造函数初始化器

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApplication2 {class Program{static void Main(string[] args){cat c new cat("萌萌");//先执行cat(string s,int i)构造…

oracle中的视图详解

1.视图的概述 视图其实就是一条查询sql语句&#xff0c;用于显示一个或多个表或其他视图中的相关数据。视图将一个查询的结果作为一个表来使用&#xff0c;因此视图可以被看作是存储的查询或一个虚拟表。视图来源于表&#xff0c;所有对视图数据的修改最终都会被反映到视图的基…

input子系统详解3——input子系统框架核心层分析

以下内容源于朱有鹏嵌入式课程的学习&#xff0c;如有侵权请告知删除。 一、前言 由input子系统简介可知&#xff0c;input子系统分为三层&#xff1a; 1、上层输入事件驱动层 涉及的文件有x210_kernel\drivers\input\evdev.c、mousedev.c 和 joydev.c文件&#xff0c;分别对应…

java servlet 部署到tomcat_如何把spring boot项目部署到tomcat容器中

把spring-boot项目按照平常的web项目一样发布到tomcat容器下一、修改打包形式在pom.xml里设置 war二、移除嵌入式tomcat插件在pom.xml里找到spring-boot-starter-web依赖节点&#xff0c;在其中添加如下代码&#xff0c;org.springframework.bootspring-boot-starter-weborg.sp…

Gitlab Merge Request Builder Plugin 配置小记

2019独角兽企业重金招聘Python工程师标准>>> 以前在给一些开源项目贡献代码的时候&#xff0c;在github上一提交pull request或者提交的分支代码更新了的时候&#xff0c;jenkins就会自动把代码进行merge并且运行单元测试&#xff0c;当时看了心里就2个字&#xff1…

IOS:屏幕旋转与Transform

IOS&#xff1a;屏幕旋转与Transform iTouch&#xff0c;iPhone&#xff0c;iPad设置都是支持旋转的&#xff0c;如果我们的程序能够根据不同的方向做出不同的布局&#xff0c;体验会更好。 如何设置程序支持旋转呢&#xff0c;通常我们会在程序的info.plist中进行设置Supporte…

input子系统详解4——输入事件驱动层源码分析

以下内容源于朱有鹏嵌入式课程的学习与整理&#xff0c;如有侵权请告知删除。 一、前言 由input子系统简介可知&#xff0c;input子系统分为三层&#xff1a; ​ 1、上层输入事件驱动层 涉及的文件有x210_kernel\drivers\input\evdev.c、mousedev.c 和 joydev.c文件&#xff0…

前端翻译:Activating Browser Modes with Doctype

一、前言                         原本备份&#xff1a; http://www.cnblogs.com/fsjohnhuang/p/3830623.html 由于本人英语能力有限&#xff0c;译本内容难免有误&#xff0c;望各位指正&#xff01; 本译文不含附录部分&#xff0c;请知悉。 二、译…

java公钥加密私钥解密过程_GPG加密解密过程

GPG加密解密过程一、Linux系统下1.安装yum安装[rootPOC-ORACLE ~]# yum install gnupg下载安装包安装https://www.gnupg.org/download/index.en.html查看gpg帮助[rootPOC-ORACLE ~]# gpg --helpgpg (GnuPG) 2.0.14libgcrypt 1.4.5Copyright (C) 2009 Free Software Foundation,…

魔兽世界客户端数据研究(三)

终于决定&#xff0c;还是通过wow model viewer起手&#xff0c;研究一下WOW的数据类型&#xff0c;从另一个角度&#xff0c;体验一把这个唯一让我充过值的游戏。 这将是一系列随笔&#xff0c;即在读代码的时候&#xff0c;顺便记录&#xff0c;以理清思路和加深映象。 其中…

input子系统详解5——参考驱动模板编写按键驱动

以下内容源于朱有鹏嵌入式课程的学习与整理&#xff0c;如有侵权请告知删除。 一、input类设备驱动的开发 &#xff08;1&#xff09;输入事件驱动层和框架核心层不需要动&#xff0c;只需要编写具体硬件驱动层代码。 &#xff08;2&#xff09;具体硬件驱动层的编程接口与调用…

java很多魔法数判断_可别在代码中写那么多魔法值了,脑壳疼!

1. 前言重构老代码中遇到了不少类似下面这种写法&#xff1a;public void attend(String value) {if ("0".equals(value)) {//todo} else if ("1".equals(value)) {//todo} else {//todo}}脑壳疼&#xff01;从 Java 语法上无懈可击&#xff0c;但是从业务…

十分钟让你明白Objective-C的语法(和Java、C++的对比)

2019独角兽企业重金招聘Python工程师标准>>> 很多想开发iOS&#xff0c;或者正在开发iOS的程序员以前都做过Java或者C&#xff0c;当第一次看到Objective-C的代码时都会头疼&#xff0c;Objective-C的代码在语法上和Java, C有着很大的区别&#xff0c;有的同学会感觉…

稀疏多项式的运算

问题描述&#xff1a; 已知稀疏多项式Pn(X)c1x^e1c2x^e2....cmx^em,其中nem>em-1>....>e1>0; ci!0,m>1.试采用存储量同多项式项数m成正比的顺序存储结构&#xff0c;编写求Pn(x0)的算法&#xff08;x0为给定值&#xff09;&#xff0c;并分析你的算法的时间复杂…

I2C子系统详解1——I2C总线设备的驱动框架

以下内容源于朱有鹏嵌入式课程的学习与整理&#xff0c;如有侵权请告知删除。 参考博客 I2C总线驱动框架详解 linux内核I2C子系统详解 一、I2C总线的物理特征 这部分内容的简介可见博客&#xff1a;SPI、I2C、UART&#xff08;即串口&#xff09;三种串行总线详解。 &#x…

sqlite4java下载_使用sqlite4java的UnsatisfiedLinkError,没有sqlite4java-osx-amd64

我对这个问题有类似的问题&#xff1a;我正在运行一个使用sqlite的脚本,虽然我能够通过命令行成功运行sqlite3,但是当我尝试运行脚本时,我总是遇到这个错误&#xff1a;SEVERE: [sqlite] sqliteQueue[master.catalog]: error running job queuecom.almworks.sqlite4java.sqlite…

神经网络编程入门

本文主要内容包括&#xff1a; (1) 介绍神经网络基本原理&#xff0c; (2) AForge.NET实现前向神经网络的方法&#xff0c; (3) Matlab实现前向神经网络的方法 。 第0节、引例 本文以Fisher的Iris数据集作为神经网络程序的测试数据集。Iris数据集可以在http://en.wikipedia.or…