Java Language
Oändliga objekt
Sök…
Anmärkningar
Oändliga objekt har fast tillstånd (inga inställningar), så alla tillstånd måste vara kända vid skapandet av objekt.
Även om det inte är tekniskt nödvändigt, är det bästa praxis att göra alla fält final
. Detta kommer att göra den oföränderliga klassen trådsäker (jfr Java Concurrency in Practice, 3.4.1).
Exemplen visar flera mönster som kan hjälpa till att uppnå detta.
Skapa en oföränderlig version av en typ med defensiv kopiering.
Vissa grundtyper och klasser i Java är grundläggande muterbara. Till exempel är alla arraytyper muterbara, och det är också klasser som java.util.Data
. Detta kan vara besvärligt i situationer där en oföränderlig typ krävs.
Ett sätt att hantera det här är att skapa en omvandlingsbar inslagning för den muterbara typen. Här är ett enkelt omslag för en mängd heltal
public class ImmutableIntArray {
private final int[] array;
public ImmutableIntArray(int[] array) {
this.array = array.clone();
}
public int[] getValue() {
return this.clone();
}
}
Den här klassen fungerar genom att använda defensiv kopiering för att isolera det muterbara tillståndet ( int[]
) från vilken kod som kan mutera det:
Konstruktören använder
clone()
att skapa en distinkt kopia av parameteruppsättningen. Om konstruktörens anropare senare ändrade parameterns uppsättning, skulle det inte påverkaImmutableIntArray
.Metoden
getValue()
använder ocksåclone()
att skapa den matris som returneras. Om den som ringer skulle ändra resultatuppsättningen skulle det inte påverkaImmutableIntArray
.
Vi kan också lägga till metoder till ImmutableIntArray
att utföra skrivskydd på den inslagna matrisen; t.ex. få dess längd, få värdet vid ett visst index, och så vidare.
Observera att en immutable omslagstyp som implementeras på detta sätt inte är typkompatibel med den ursprungliga typen. Du kan inte bara ersätta det förra med det senare.
Receptet för en oföränderlig klass
Ett oföränderligt objekt är ett objekt vars tillstånd inte kan ändras. En immutable klass är en klass vars instanser är oföränderliga genom design och implementering. Java-klassen som oftast presenteras som ett exempel på immutability är java.lang.String .
Följande är ett stereotyp exempel:
public final class Person {
private final String name;
private final String ssn; // (SSN == social security number)
public Person(String name, String ssn) {
this.name = name;
this.ssn = ssn;
}
public String getName() {
return name;
}
public String getSSN() {
return ssn;
}
}
En variation på detta är att förklara konstruktören som private
och tillhandahålla en public static
fabriksmetod istället.
Standardreceptet för en immutable klass är enligt följande:
- Alla egenskaper måste ställas in i konstruktören eller fabriksmetoden.
- Det bör inte finnas några bosättare.
- Om det är nödvändigt att inkludera inställare av gränssnittskompatibilitetsskäl, bör de antingen göra ingenting eller kasta ett undantag.
- Alla fastigheter ska deklareras som
private
ochfinal
. - För alla egenskaper som hänvisar till muterbara typer:
- fastigheten bör initialiseras med en djup kopia av värdet som skickas via konstruktören, och
- fastighetens getter bör returnera en djup kopia av fastighetsvärdet.
- Klassen bör förklaras som
final
att förhindra att någon skapar en muterbar underklass av en oföränderlig klass.
Ett par andra saker att notera:
- Oändbarhet förhindrar inte objekt från att vara nollbara; t.ex. kan
null
tilldelas enString
. - Om en immutable klasseregenskaper deklareras som
final
, är instanser i sig gändsäkra. Detta gör att immutable klasser är ett bra byggsten för implementering av flertrådade applikationer.
Typiska brister i konstruktionen som förhindrar att en klass är oföränderlig
Med hjälp av vissa inställningar utan att ställa in alla nödvändiga egenskaper i konstruktören
public final class Person { // example of a bad immutability
private final String name;
private final String surname;
public Person(String name) {
this.name = name;
}
public String getName() { return name;}
public String getSurname() { return surname;}
public void setSurname(String surname) { this.surname = surname); }
}
Det är lätt att visa att Person
inte är oföränderlig:
Person person = new Person("Joe");
person.setSurname("Average"); // NOT OK, change surname field after creation
För att fixa det, helt enkelt ta bort setSurname()
och refactor konstruktören enligt följande:
public Person(String name, String surname) {
this.name = name;
this.surname = surname;
}
Markerar inte instansvariabler som privata och slutliga
Ta en titt på följande klass:
public final class Person {
public String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
Följande utdrag visar att ovanstående klass inte är oföränderlig:
Person person = new Person("Average Joe");
person.name = "Magic Mike"; // not OK, new name for person after creation
För att fixa det, markera helt enkelt namnegenskapen som private
och final
.
Att exponera ett muterbart objekt i klassen i en getter
Ta en titt på följande klass:
import java.util.List;
import java.util.ArrayList;
public final class Names {
private final List<String> names;
public Names(List<String> names) {
this.names = new ArrayList<String>(names);
}
public List<String> getNames() {
return names;
}
public int size() {
return names.size();
}
}
Names
verkar oföränderlig vid första anblicken, men det är inte som följande kod visar:
List<String> namesList = new ArrayList<String>();
namesList.add("Average Joe");
Names names = new Names(namesList);
System.out.println(names.size()); // 1, only containing "Average Joe"
namesList = names.getNames();
namesList.add("Magic Mike");
System.out.println(names.size()); // 2, NOT OK, now names also contains "Magic Mike"
Detta hände på grund av att en ändring av referenslistan som returneras av getNames()
kan modifiera den verkliga listan med Names
.
För att fixa detta, helt enkelt undvika att returnera referenser som referensklassens muterbara objekt antingen genom att göra defensiva kopior, enligt följande:
public List<String> getNames() {
return new ArrayList<String>(this.names); // copies elements
}
eller genom att designa getters på så sätt att endast andra immutable objekt och primitiv returneras, enligt följande:
public String getName(int index) {
return names.get(index);
}
public int size() {
return names.size();
}
Injicera konstruktör med objekt (er) som kan modifieras utanför den immutable klassen
Detta är en variation av den tidigare bristen. Ta en titt på följande klass:
import java.util.List;
public final class NewNames {
private final List<String> names;
public Names(List<String> names) {
this.names = names;
}
public String getName(int index) {
return names.get(index);
}
public int size() {
return names.size();
}
}
Som Names
klass tidigare verkar också NewNames
klassen oföränderlig vid första anblicken, men det är det inte, i själva verket visar följande utdrag tvärtom:
List<String> namesList = new ArrayList<String>();
namesList.add("Average Joe");
NewNames names = new NewNames(namesList);
System.out.println(names.size()); // 1, only containing "Average Joe"
namesList.add("Magic Mike");
System.out.println(names.size()); // 2, NOT OK, now names also contains "Magic Mike"
För att fixa detta, som i föregående fel, helt enkelt göra defensiva kopior av objektet utan att tilldela det direkt till den immutable klassen, dvs. konstruktören kan ändras på följande sätt:
public Names(List<String> names) {
this.names = new ArrayList<String>(names);
}
Låt klassens metoder åsidosättas
Ta en titt på följande klass:
public class Person {
private final String name;
public Person(String name) {
this.name = name;
}
public String getName() { return name;}
}
Person
verkar oföränderlig vid första anblicken, men antar att en ny underklass av Person
definieras:
public class MutablePerson extends Person {
private String newName;
public MutablePerson(String name) {
super(name);
}
@Override
public String getName() {
return newName;
}
public void setName(String name) {
newName = name;
}
}
nu Person
(im) mutabilitet kan utnyttjas genom polymorfism genom att använda den nya underklassen:
Person person = new MutablePerson("Average Joe");
System.out.println(person.getName()); prints Average Joe
person.setName("Magic Mike"); // NOT OK, person has now a new name!
System.out.println(person.getName()); // prints Magic Mike
För att fixa detta, markera antingen klassen som final
så att den inte kan utökas eller förklara alla dess konstruktörer som private
.