四时宝库

程序员的知识宝库

一文了解Java中什么是不可变对象和使用场景

在设计类和数据结构时,尽可能优先考虑不变性。不可变对象一旦创建就无法修改,从而产生更安全、更可预测的代码。

以下总结了采用不可变对象设计的场景和建议:

  • 将类声明为最终类或将它们设计为不可变的。不变性确保对象的状态在创建后无法更改,从而促进更受控制和可预测的行为。
  • 在需要不可变的类中省略setter方法。Setter 方法允许修改对象状态,这与不变性原则相矛盾。
  • 如果在类中有对可变对象的引用,请确保返回的是这些对象的副本而不是原始对象。这可以防止外部实体修改引起内部状态的修改。
import java.util.ArrayList;
import java.util.List;

// Mutable Book class
class Book {
    private String title;
    public Book(String title) {
        this.title = title;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
}
// Library class holding references to mutable objects
public class Library {
    private List<Book> books;
    public Library() {
        this.books = new ArrayList<>();
    }
    public void addBook(Book book) {
        // Adding a copy of the book to the internal list
        books.add(new Book(book.getTitle()));
    }
    public List<Book> getBooks() {
        // Returning copies of the books to prevent external modification
        List<Book> copyOfBooks = books.stream()
                              .map(book -> new Book(book.getTitle()))
                              .collect(Collectors.toList());
        return copyOfBooks;
    }
    public static void main(String[] args) {
        Library library = new Library();
        // Adding a book to the library
        Book originalBook = new Book("The Great Gatsby");
        library.addBook(originalBook);
        // Modifying the original book after adding it to the library
        originalBook.setTitle("Modified Title");
        // Retrieving books from the library and printing their titles
        library.getBooks().forEach(book -> 
          System.out.println("Book Title: " + book.getTitle()));
    }
}

在此示例中,Library类保存一个书籍列表,当提供对内部状态(方法getBooks)的访问时,它返回书籍的副本而不是原始对象。这可以防止外部实体无意中修改Library的内部状态。类Book本身是可变的,但通过返回副本,我们确保了一定程度的封装并保护内部状态的完整性。

  • 处理集合时,使用Collections.unmodifyingList()创建不可修改的视图。只允许提供对集合的只读访问,而不允许修改。
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

// Mutable Player class
class Player {
    private String name;
    public Player(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
// Team class managing a list of players
public class Team {
    private List<Player> players;
    public Team() {
        this.players = new ArrayList<>();
    }
    public void addPlayer(Player player) {
        players.add(player);
    }
    public List<Player> getPlayers() {
        // Returning an unmodifiable view of the internal list
        return Collections.unmodifiableList(players);
    }
    public static void main(String[] args) {
        Team team = new Team();
        // Adding players to the team
        team.addPlayer(new Player("Alice"));
        team.addPlayer(new Player("Bob"));
        // Retrieving an unmodifiable view of the players and trying to modify it
        List<Player> unmodifiablePlayers = team.getPlayers();
        // Attempting to add a player to the unmodifiable list (will throw UnsupportedOperationException)
        // unmodifiablePlayers.add(new Player("Charlie"));
        // Modifying the original player after retrieving the unmodifiable view
        team.getPlayers().get(0).setName("Alicia");
        // Printing the names from the unmodifiable view
        for (Player player : unmodifiablePlayers) {
            System.out.println("Player Name: " + player.getName());
        }
    }
}

在此示例中,Team类用Collections.unmodifiableList()创建内部玩家列表的不可修改视图。getPlayers()方法返回这个不可修改的视图,防止外部实体修改列表。当尝试修改列表时会导致UnsupportedOperationException。Team类确保了在提供对玩家列表的只读访问时,内部状态保持不变。

  • 不可变对象本质上是线程安全的。如果类被设计为不可变的,那么就可以消除对多线程环境中同步的的问题
  • 如果类包含可变对象,请使用防御性复制或克隆以避免意外修改。这确保了外部变化不会影响内部状态。
// Mutable Address class
class Address implements Cloneable {
    private String street;
    private String city;

    public Address(String street, String city) {
        this.street = street;
        this.city = city;
    }
    public String getStreet() {
        return street;
    }
    public void setStreet(String street) {
        this.street = street;
    }
    public String getCity() {
        return city;
    }
    public void setCity(String city) {
        this.city = city;
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

// Person class containing a mutable Address object
public class Person {
    private String name;
    private Address address;

    public Person(String name, Address address) {
        this.name = name;
        // Using defensive copying or cloning to avoid unintended modifications
        this.address = new Address(address.getStreet(), address.getCity());
    }

    public String getName() {
        return name;
    }

    public Address getAddress() {
        // Using defensive copying to avoid exposing the original mutable object
        try {
            return (Address) address.clone();
        } catch (CloneNotSupportedException e) {
            // Handle the exception or log an error
            e.printStackTrace();
            return null;
        }
    }
    public static void main(String[] args) {
        // Creating a Person with a mutable Address
        Address originalAddress = new Address("123 Main St", "Cityville");
        Person person = new Person("John Doe", originalAddress);
        // Modifying the original Address after creating the Person
        originalAddress.setStreet("456 New St");
        // Retrieving the Address from the Person and printing its details
        Address personAddress = person.getAddress();
        System.out.println("Person's Address: " + personAddress.getStreet() + ", " + personAddress.getCity());
    }
}

在此示例中,Person类包含一个可变对象Address。构造函数在初始化字段时Person使用防御性复制来创建新对象。该方法还通过克隆对象来使用防御性复制,以避免意外修改。这确保了对原始对象的外部更改不会影响类的内部状态。

  • 在处理日期和时间时,使用java.time API,它提供了用于表示日期、时间和持续时间的不可变类。
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.LocalDateTime;
import java.time.Duration;
import java.time.Period;

public class DateTimeExample {
    public static void main(String[] args) {
        // LocalDate - Represents a date without time
        LocalDate currentDate = LocalDate.now();
        System.out.println("Current Date: " + currentDate);
        // LocalTime - Represents a time without date
        LocalTime currentTime = LocalTime.now();
        System.out.println("Current Time: " + currentTime);
        // LocalDateTime - Represents both date and time
        LocalDateTime currentDateTime = LocalDateTime.now();
        System.out.println("Current Date and Time: " + currentDateTime);
        // Duration - Represents a duration of time
        LocalTime startTime = LocalTime.of(9, 0);
        LocalTime endTime = LocalTime.of(12, 30);
        Duration duration = Duration.between(startTime, endTime);
        System.out.println("Meeting Duration: " + duration.toMinutes() + " minutes");
        // Period - Represents a period of time in terms of years, months, and days
        LocalDate startDate = LocalDate.of(2022, 1, 1);
        LocalDate endDate = LocalDate.of(2023, 1, 1);
        Period period = Period.between(startDate, endDate);
        System.out.println("Time Period: " + period.getYears() + " years, " +
                period.getMonths() + " months, " + period.getDays() + " days");
    }
}

java.time API中的所有类都是不可变的,这意味着一旦创建,它们的状态就无法更改。这种不变性有助于 Java 程序中更安全、更可靠的日期和时间处理。

  • 如果类包含嵌套对象,请确保深度不变性以防止任何级别的修改。在处理复杂的对象图时,这一点非常重要。
// Deeply immutable Address class
final class Address {
    private final String city;
    private final String street;

    public Address(String city, String street) {
        this.city = city;
        this.street = street;
    }
    public String getCity() {
        return city;
    }
    public String getStreet() {
        return street;
    }
    // No setters or methods that modify state
}

// Person class with deeply immutable Address
final class Person {
    private final String name;
    private final Address address;
    public Person(String name, Address address) {
        this.name = name;
        // Returning a copy of the Address to ensure deep immutability
        this.address = new Address(address.getCity(), address.getStreet());
    }
    public String getName() {
        return name;
    }
    public Address getAddress() {
        // Returning a copy of the Address to ensure deep immutability
        return new Address(address.getCity(), address.getStreet());
    }
    // No setters or methods that modify state
}

public class ImmutableExample {
    public static void main(String[] args) {
        // Creating an immutable Person with an immutable Address
        Address address = new Address("Cityville", "123 Main St");
        Person person = new Person("John Doe", address);
        // Attempting to modify the original Address after creating the Person
        // This will have no effect on the internal state of the Person
        address = new Address("NewCity", "456 Other St");
        // Printing the details of the immutable Person
        System.out.println("Name: " + person.getName());
        System.out.println("Address: " + person.getAddress().getStreet() + ", " + person.getAddress().getCity());
    }
}
  • 采用函数式编程时使用不变性原则,有助于提高代码流的可预测性。
import java.util.List;
import java.util.stream.Collectors;

// Immutable class representing a person
final class Person {
    private final String name;
    private final int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
}

// Functional programming example with immutable data and functions
public class FunctionalProgrammingExample {
    public static void main(String[] args) {
        // Creating a list of immutable Person objects
        List<Person> people = List.of(
                new Person("Alice", 25),
                new Person("Bob", 30),
                new Person("Charlie", 22)
        );

        // Using functional programming to filter and map the data
        List<String> namesOfAdults = people.stream()
                .filter(person -> person.getAge() >= 18)
                .map(Person::getName)
                .collect(Collectors.toList());

        // Printing the result
        System.out.println("Names of adults: " + namesOfAdults);
    }
}

通过支持对象的不可变,可以使我们的代码更健壮、线程安全且可维护。

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接