资讯专栏INFORMATION COLUMN

Java™ 教程(不可变对象)

Songlcy / 1630人阅读

不可变对象

如果一个对象的状态在构造后不能改变,则该对象被认为是不可变的,对不可变对象的最大依赖被广泛认为是一种创建简单、可靠代码的合理策略。

不可变对象在并发应用程序中特别有用,由于它们不能改变状态,因此它们不会被线程干扰破坏或在不一致的状态下观察。

程序员通常不愿意使用不可变对象,因为他们担心创建新对象的成本而不是就地更新对象的成本,对象创建的影响经常被高估,并且可以通过与不可变对象相关联的一些效率来抵消,这些包括由于垃圾收集而减少的开销,以及消除保护可变对象免于损坏所需的代码。

以下小节采用其实例可变的类,并从中派生出具有不可变实例的类,通过这样做,它们为这种转换提供了一般规则,并演示了不可变对象的一些优点。

同步类示例

SynchronizedRGB类定义了表示颜色的对象,每个对象将颜色表示为代表主要颜色值的三个整数和一个给出颜色名称的字符串。

public class SynchronizedRGB {

    // Values must be between 0 and 255.
    private int red;
    private int green;
    private int blue;
    private String name;

    private void check(int red,
                       int green,
                       int blue) {
        if (red < 0 || red > 255
            || green < 0 || green > 255
            || blue < 0 || blue > 255) {
            throw new IllegalArgumentException();
        }
    }

    public SynchronizedRGB(int red,
                           int green,
                           int blue,
                           String name) {
        check(red, green, blue);
        this.red = red;
        this.green = green;
        this.blue = blue;
        this.name = name;
    }

    public void set(int red,
                    int green,
                    int blue,
                    String name) {
        check(red, green, blue);
        synchronized (this) {
            this.red = red;
            this.green = green;
            this.blue = blue;
            this.name = name;
        }
    }

    public synchronized int getRGB() {
        return ((red << 16) | (green << 8) | blue);
    }

    public synchronized String getName() {
        return name;
    }

    public synchronized void invert() {
        red = 255 - red;
        green = 255 - green;
        blue = 255 - blue;
        name = "Inverse of " + name;
    }
}

必须小心使用SynchronizedRGB以避免在不一致的状态下被查看,例如,假设一个线程执行以下代码:

SynchronizedRGB color =
    new SynchronizedRGB(0, 0, 0, "Pitch Black");
...
int myColorInt = color.getRGB();      //Statement 1
String myColorName = color.getName(); //Statement 2

如果另一个线程在语句1之后但在语句2之前调用color.set,则myColorInt的值将与myColorName的值不匹配,为了避免这种结果,必须将两个语句绑定在一起:

synchronized (color) {
    int myColorInt = color.getRGB();
    String myColorName = color.getName();
} 

这种不一致只适用于可变对象 — 对于不可变版本的SynchronizedRGB,它不会是一个问题。

一种定义不可变对象的策略

以下规则定义了用于创建不可变对象的简单策略,并非所有记录为“不可变”的类都遵循这些规则。这并不一定意味着这些类的创造者是草率的 — 他们可能有充分的理由相信他们类的实例在构造后永远不会改变,但是,这种策略需要复杂的分析,不适合初学者。

不要提供“setter”方法 — 修改字段或字段引用的对象的方法。

使所有字段为finalprivate

不允许子类重写方法,最简单的方法是将类声明为final,更复杂的方法是使构造函数为private并在工厂方法中构造实例。

如果实例字段包含对可变对象的引用,则不允许更改这些对象:

不要提供修改可变对象的方法。

不要共享对可变对象的引用,永远不要存储对传递给构造函数的外部可变对象的引用,如有必要,创建副本并存储对副本的引用,同样,必要时创建内部可变对象的副本,以避免在方法中返回原始对象。

将此策略应用于SynchronizedRGB会导致以下步骤:

这个类中有两个setter方法,第一个方法set,任意改变对象,在类的不可变版本中不存在,第二个方法invert,可以通过让它创建一个新对象而不是修改现有对象来进行调整。

所有字段都已为private,他们进一步获得final

该类本身被声明为final

只有一个字段引用一个对象,该对象本身是不可变的,因此,不需要防止改变“包含的”可变对象的状态的保护措施。

在这些更改之后,我们有ImmutableRGB:

final public class ImmutableRGB {

    // Values must be between 0 and 255.
    final private int red;
    final private int green;
    final private int blue;
    final private String name;

    private void check(int red,
                       int green,
                       int blue) {
        if (red < 0 || red > 255
            || green < 0 || green > 255
            || blue < 0 || blue > 255) {
            throw new IllegalArgumentException();
        }
    }

    public ImmutableRGB(int red,
                        int green,
                        int blue,
                        String name) {
        check(red, green, blue);
        this.red = red;
        this.green = green;
        this.blue = blue;
        this.name = name;
    }


    public int getRGB() {
        return ((red << 16) | (green << 8) | blue);
    }

    public String getName() {
        return name;
    }

    public ImmutableRGB invert() {
        return new ImmutableRGB(255 - red,
                       255 - green,
                       255 - blue,
                       "Inverse of " + name);
    }
}
上一篇:守护阻塞

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/73050.html

相关文章

  • Java教程(Date-Time)

    Date-Time Java SE 8发行版中引入的Date-Time包java.time提供了全面的日期和时间模型,是在JSR 310:Date and Time API下开发的,尽管java.time基于国际标准化组织(ISO)日历系统,但也支持常用的全球日历。 此课程介绍了使用基于ISO的类来表示日期和时间以及操作日期和时间值的基本原理。 概述 时间似乎是一个简单的主题,即便是便宜的手表也能...

    AlphaGooo 评论0 收藏0
  • Java 11 教程

    摘要:原文链接已于成功发布,不过目前绝大多数人在生产环境仍旧使用的是。这篇以案例为主的教程涵盖了从到的绝大多数重要的语法与特性。当编译器不能正确识别出变量的数值类型时,将不被允许使用。同步请求将会阻塞当前的线程,直到返回响应消息。 showImg(https://segmentfault.com/img/remote/1460000016575203); 原文链接:https://wangw...

    douzifly 评论0 收藏0
  • Java 8 并发教程:同步和锁

    摘要:在接下来的分钟,你将会学会如何通过同步关键字,锁和信号量来同步访问共享可变变量。所以在使用乐观锁时,你需要每次在访问任何共享可变变量之后都要检查锁,来确保读锁仍然有效。 原文:Java 8 Concurrency Tutorial: Synchronization and Locks译者:飞龙 协议:CC BY-NC-SA 4.0 欢迎阅读我的Java8并发教程的第二部分。这份指南将...

    wyk1184 评论0 收藏0
  • Java学习笔记3-Number,String,StringBuilder类

    摘要:创建字符串教程字符串长度用于获取有关对象的信息的方法称为访问器方法。类在中被提出,它和之间的最大不同在于的方法不是线程安全的不能同步访问。然而在应用程序要求线程安全的情况下,则必须使用类。 一般地,当需要使用数字的时候,我们通常使用内置数据类型,如:byte、int、long、double 等。 在实际开发过程中,我们经常会遇到需要使用对象,而不是内置数据类型的情形。为了解决这个问题,...

    pkhope 评论0 收藏0
  • Java教程(字符串)

    字符串 在Java编程中广泛使用的字符串是一系列字符,在Java编程语言中,字符串是对象。 Java平台提供String类来创建和操作字符串。 创建字符串 创建字符串的最直接方法是编写: String greeting = Hello world!; 在这种情况下,Hello world!是一个字符串文字 — 代码中的一系列字符,用双引号括起来,每当它在代码中遇到字符串文字时,编译器就会创建一个带...

    ThreeWords 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<