首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >3分钟快速阅读-《Effective Java》(七)

3分钟快速阅读-《Effective Java》(七)

作者头像
cwl_java
发布2019-10-26 20:48:21
3350
发布2019-10-26 20:48:21
举报
文章被收录于专栏:cwl_Javacwl_Java
61.抛出与抽象相对应的异常

总而言之,如果不能阻止或者处理来自更底层的异常,一般的做法就是进行异常转译,异常转译就是高层捕获底层异常进行处理,或者把它转化层高层相同业务逻辑的异常.

62.每个方法抛出的异常都要有文档

简单来说对于异常可能出现的情况进行尽可能的声明,这样让调用你的人才能知道要怎么来使用对应的方法

63.在细节消息中包含能捕获失败的信息

简单来说,对于可能出现业务逻辑异常处应当做好对应的日志记录,这样才能更好的跟踪有些我们无法预料捕获而是由程序帮我们抛出的异常信息

64.努力使失败保持原子性

总而言之,当异常出现的时候肯定存在某个对象出现了某个问题,那么此时当我们捕获异常的时候需要让这个对象保持它出现异常的状态,不应该去改变它的结构,从而破坏了原子性,最终导致异常无法重现

65.不要忽略异常

简单来说,不要让catch代码块当中什么事情都不做,应该要么捕获异常打印日志,要么记录异常改变状态.总需要在这里做点什么,不能放空

66.同步访问共享的数据

当开启多线程去访问同一个共享的数据资源时,需要使用synchronized修饰.笔者推荐BlockingQueue也是可以作为一个保证消费顺序的队列

67.避免过度同步

不要试图在所有的方法当中都加上synchronized修饰,而且使用synchronized修饰的方法操作的逻辑应该越短越好

68.executor和task优先于线程

从JDK1.5开始,Java平台发布了Exceutor Framework,它创建了一个工作队列.只需要一行代码即可实现

//单线程执行器
ExecutorService executorService = Executors.newSingleThreadExecutor();
//多线程执行器,自定义线程的数量
ExecutorService executorService = Executors.newFixedThreadPool(10);
//线程执行完成之后,需要关闭当前线程,但并不是退出JVM
executorService.shutdown();

总的来说使用Exceutor Framework会比以往的Thread执行要方便的的多,具体可以参照书籍学习

69.并发工具优先于wait和notify

当我们要去保证线程的顺序执行时,并发工具帮我们提供了很多队列帮助我们让线程的执行顺序保持一致且快速.如果一定要使用wait()方法,那么需要在while循环中使用,保证线程的等待是有条件的,如果是需要使用notify方法,那么优先使用notifyAll方法,避免死锁

70.线程安全性的文档化

我们需要对一个类是否是线程安全的进行对应的文档注释,提供以下几种可选类型

  • 70.1 不可变类.该类的实例是完全不可改变的.类似枚举类
  • 70.2 无条件的线程安全.这个类的实例是可变的,但是访问的方法都是同步方法.
  • 70.3 有条件的线程安全,这个类的实例是可变的,存在不是同步的方法可供外部调用
  • 70.4 非线程安全的,该类是可变的且不存在同步方法
71.慎用延迟初始化

延迟初始化是消耗启动性能来换回运行性能,只有当我们创建的对象确实很大,创建多个可能影响总体运行效率.那么此时建议使用双重检查,代码如下

public class DoubleChecked {

    private volatile Client client;

    private Client getFieldType(){
        Client result = client;
        if(result == null){
            synchronized (this){
                result = client;
                if(result == null){
                    client = result = buildFiled();
                }
            }
        }
        return result;
    }

    private Client buildFiled() {

        return new Client();
    }
}
class Client {

}
72.不要依赖于线程调度器

程序依赖于线程调度器会导致程序不够健壮也不具有移植性,线程任务的优先级可以提升工作服务的质量,但是却不能修复原本不能正常运行的程序

73.避免使用线程组
  • 73.1 线程组不保证线程安全,只是将多条线程合并成一个数组形式
  • 79.2 线程组API使用复杂,且代码量较大.不易阅读
74.谨慎实现序列化接口
  • 74.1 实现Serializable接口,一旦这个类被发布了,就降低了改变这个类的实现的灵活性
  • 74.2 增加了出现Bug和安全漏洞的可能性
  • 74.3 增加了测试负担
75.考虑使用自定义序列化
public class PersonSingleton implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private PersonSingleton(String name) {
        this.name = name;
    };
    private static PersonSingleton person = null;

    public static synchronized PersonSingleton getInstance() {
        if (person == null){
            return person = new PersonSingleton("cgl");
        }
        return person;
    }

    private Object writeReplace() throws ObjectStreamException {
        System.out.println("1 write replace start");
        return this;//可修改为其他对象
    }

    private void writeObject(java.io.ObjectOutputStream out) throws IOException {
        System.out.println("2 write object start");
        out.defaultWriteObject();
        //out.writeInt(1);
    }

    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
        System.out.println("3 read object start");
        in.defaultReadObject();
        //int i=in.readInt();
    }

    private Object readResolve() throws ObjectStreamException {
        System.out.println("4 read resolve start");
        return PersonSingleton.getInstance();//不管序列化的操作是什么,返回的都是本地的单例对象
    }

    public static void main(String[] args) throws Exception {

        FileOutputStream out = new FileOutputStream(new File("D://person.dat"));
        ObjectOutputStream op = new ObjectOutputStream(out);
        op.writeObject(PersonSingleton.getInstance());
        op.close();

        FileInputStream in = new FileInputStream(new File("D://person.dat"));
        ObjectInputStream oi = new ObjectInputStream(in);
        Object person = oi.readObject();
        in = new FileInputStream(new File("D://person.dat"));
        oi = new ObjectInputStream(in);
        PersonSingleton person1 = (PersonSingleton) oi.readObject();

        System.out.println("sington person hashcode:" + person.hashCode());
        System.out.println("sington person1 hashcode:" + person1.hashCode());
        System.out.println("singleton getInstance hashcode:" + PersonSingleton.getInstance().hashCode());
        System.out.println("singleton person equals:" + (person == PersonSingleton.getInstance()));
        System.out.println("person equals1:" + (person1 == person));
    }
}
76.保护性编写readObject方法

编写readObject注意事项

  • private修饰每个字段
  • 对于任意约束条件,如果检查失败.则抛出一个InvalidObjectException异常
  • 反序列化验证使用ObjectInputValidation接口
  • 不要调用类中任何可被覆盖的方法
77.对于实例控制,枚举类优先于readResolve

当实现序列化接口时使用枚举来替代数组,错误做法

private String[] HOBBIES = {"CLIMB","READ","SONG"};

正确做法

    enum HOBBIES{
        CLIMB,READ,SONG
    }
78.考虑使用序列化代理模式

代码示例

/**
 * 1. 序列化Person时, 会调用调用writeReplace()生成一个PersonProxy对象, 然后对此对象进行序列化 (不是对Person类对象进行序列化,
 *      由序列化文件的内容可以得知, 可以查看序列化生成的文件, 文件中内容为如下图 (代码之后的图)
 * 2. 反序列化时, 会调用PersonProxy的readResolve()方法生成一个Person对象, 
 *      最后返回此对象的拷贝 (通过PersonProxy类的readResolve方法和main方法中的输出可以看出)
 * 3. 因此, Person类的序列化工作完全交给PersonProxy类, 正如此模式的名称所表达的一样
 */
public class Person implements Serializable {
    private final String name;
    private final String hobby;
    private final int age;

    public Person(String name, String hobby, int age) {
        System.out.println("Person(String name, String hobby, int age)");

        //约束条件
        if(age < 0 || age > 200) {
            throw new IllegalArgumentException("非法年龄");
        }

        this.name = name;
        this.hobby = hobby;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public String getHobby() {
        return hobby;
    }

    public int getAge() {
        return age;
    }


    private static class PersonProxy implements Serializable {
        private final String name;
        private final String hobby;
        private final int age;

        public PersonProxy(Person original) {
            System.out.println("PersonProxy(Person original)");
            this.name = original.getName();
            this.hobby = original.getHobby();
            this.age = original.getAge();
        }

        private Object readResolve() {
            System.out.println("PersonProxy.readResolve()");
            Person person = new Person(name, hobby, age);
            System.out.println("resolveObject: " + person);
            return person;
        }
    }

    private Object writeReplace() {
        System.out.println("Person.writeReplace()");
        return new PersonProxy(this); //readObject的时候是调用, PersonProxy的readResolve()
    }

    //此方法不会执行,
    private void writeObject(ObjectOutputStream out) {
        System.out.println("Person.writeObject()");
    }

    //防止攻击者伪造数据, 企图违反约束条件 (如: 违反年龄约束)
    private Object readObject(ObjectInputStream in) throws InvalidObjectException {
        System.out.println("Person.readObject()");
        throw new InvalidObjectException("Proxy required");
    }
}

测试用例

    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        Person person = new Person("张三", "足球" ,25);
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("serial.ser"));
        out.writeObject(person);
        out.flush();
        out.close();

        Thread.sleep(1000);

        ObjectInputStream in = new ObjectInputStream(new FileInputStream("serial.ser"));
        Person deserPerson = (Person) in.readObject();
        System.out.println("main: " + person);
        in.close();

        if(person == deserPerson) {
            System.out.println("序列化前后是同一个对象");
        } else {
            //程序会走这一段, 反序列化会创建对象, 但是不会执行类的构造方法, 而是使用输入流构造对象
            System.out.println("序列化前后不是同一个对象, 哈哈哈");
        }
    }

本书完结

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-05-10 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 61.抛出与抽象相对应的异常
  • 62.每个方法抛出的异常都要有文档
  • 63.在细节消息中包含能捕获失败的信息
  • 64.努力使失败保持原子性
  • 65.不要忽略异常
  • 66.同步访问共享的数据
  • 67.避免过度同步
  • 68.executor和task优先于线程
  • 69.并发工具优先于wait和notify
  • 70.线程安全性的文档化
  • 71.慎用延迟初始化
  • 72.不要依赖于线程调度器
  • 73.避免使用线程组
  • 74.谨慎实现序列化接口
  • 75.考虑使用自定义序列化
  • 76.保护性编写readObject方法
  • 77.对于实例控制,枚举类优先于readResolve
  • 78.考虑使用序列化代理模式
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档