在设计类和数据结构时,尽可能优先考虑不变性。不可变对象一旦创建就无法修改,从而产生更安全、更可预测的代码。
以下总结了采用不可变对象设计的场景和建议:
- 将类声明为最终类或将它们设计为不可变的。不变性确保对象的状态在创建后无法更改,从而促进更受控制和可预测的行为。
- 在需要不可变的类中省略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);
}
}
通过支持对象的不可变,可以使我们的代码更健壮、线程安全且可维护。