#java #immutability #builder #final
#java #неизменяемость #Конструктор #Финал
Вопрос:
У меня есть график (g), который использует builder (стиль J Bloch). График необходимо перевернуть, чтобы запустить определенную статистику, которая затем кэшируется для отчетов и алгоритмов анализа для доступа.
Итак, график g определяет следующие ссылочные переменные:
private final Builder savedBuilder; // save builder for clone build with same properties.
private final Graph gPrime; // must reverse populated graphs BEFORE cache of stats
Примечание: gprrime ссылается на идентичный график, за исключением того, что он заполняется из revesed g.
Gprrime.gprrime должен ссылаться на g, поскольку g является обратным gprrime.
и метод сборки builder:
public Graph build() {
Graph g = new Graph(this);
g.gPrime.gPrime = g;
return g;
}
и конструктор, который принимает builder:
private Graph (Builder builder){ // 'this' used for clarity
this.gType = builder.gType;
this.dropOrphans = builder.dropOrphans;
this.fileHandle = builder.fileHandle;
this.nodes = builder.nodes;
this.edges = builder.edges;
this.delimiter = builder.delimiter;
this.mapCapacity = builder.mapCapacity;
this.mapLoadFactor = builder.mapLoadFactor;
this.savedBuilder = builder; // save builder for cloning
emptyGraph(); // build empty structure for data in this graph
if (this.fileHandle == null) { // no file data
if (this.nodes == 0) { // no sizing info
; // nothing else to do - - - empty graph
} else { // we have # of nodes
if ( this.edges == 0) { // just an edge-less integer graph)
populateEdgeless(nodes) ;
} else { // randomly generated graph
populateRandom(nodes, edges);
}
}
} else { // populate from file
populateFromFile();
}
// To create empty graph to transpose our values into,
// we need to clear out builder settings that would populate a new graph.
savedBuilder.fileHandle = null;
savedBuilder.nodes = 0;
savedBuilder.edges = 0;
// otherwise, everything the same, so just pass modified builder to constructor
// save the reference to this graph ( ready for the reversal method to use )
this.gPrime = new Graph(savedBuilder);
)
Еще раз. Целью являются два объекта graph, в каждом из которых gprrime ссылается на другой.
Последовательность такова: построить g — — — заполнить g — — — преобразовать g в пустой график с теми же характеристиками, что и g
Итак, вот проблема, которую я не совсем понимаю.
Если я назначу g для gprrime.gprrime либо в сборке после того, как g был собран нами, либо в нижней части конструктора, я получаю сообщение об ошибке, в котором говорится, что gprrime является окончательным. Eclipse указывает, что это первое gPrime, о котором идет речь — — — что верно — — — оно окончательное и было назначено. Но gPrime.gprime (с акцентом на втором gPrime ) еще не назначен. ( Я обыскал всю программу. )
Я также попытался поместить назначение в нижней части обратного метода. То же самое.
Я также попробовал g.gPrime.gPrime в builder. То же самое.
Это почти так, как если бы компилятор был сбит с толку тем, какой gPrime получает назначение.
Я уверен, что есть что-то, чего я не вижу или не понимаю — — — но — — — не знаю, как это сделать.
Я могу заставить это работать, если удалю final, но я пытаюсь добраться до immutable.
Ответ №1:
Вам нужны циклические зависимости, которые являются неизменяемыми. Вы должны реализовать это так, чтобы при Build A
(в конструкторе A) вам приходилось вызывать constructor
of B
с this
помощью.
Вот код с Builders (вы должны убедиться, что весь процесс сборки не выходит из текущего потока):
public class A {
private final B b_;
private final String name_;
private A(Builder b) {
b_ = b.bB_.a(this).build();
name_ = b.name_;
}
public String name() {
return name_;
}
public B b() {
return b_;
}
@Override
public String toString() {
return "[" name_ ": " b_.name() " ]";
}
public static class Builder {
private B.Builder bB_;
private String name_;
public Builder bB(B.Builder bB) {
bB_ = bB;
return this;
}
public Builder name(String arg) {
name_ = arg;
return this;
}
public A build() {
return new A(this);
}
}
}
Класс В:
public class B {
private final A a_;
private final String name_;
private B(Builder b) {
a_ = b.a_;
name_ = b.name_;
}
public String name() {
return name_;
}
@Override
public String toString() {
return "[" name_ ": " a_.name() " ]";
}
public static class Builder {
private A a_;
private String name_;
public Builder a(A a) {
a_ = a;
return this;
}
public Builder name(String arg) {
name_ = arg;
return this;
}
public B build() {
return new B(this);
}
}
}
Как это использовать:
public class Main {
public static void main(String[] args) {
A.Builder aBl = new A.Builder().name("I am A1");
B.Builder bBl = new B.Builder().name("I am B1");
A a = aBl.bB(bBl).build();
System.out.println(a);
System.out.println(a.b());
}
}
^
^
^
Если вы хотите иметь один класс и два объекта в циклической зависимости):
public class A {
private final A other_;
private final String name_;
private A(Builder b) {
if (b.otherBulder_ != null) {
other_ = b.otherBulder_.otherInstance(this).build();
} else {
other_ = b.otherInstance_;
}
name_ = b.name_;
}
@Override
public String toString() {
return "[" name_ ": " other_.name() " ]";
}
public String name() {
return name_;
}
public A other() {
return other_;
}
static class Builder {
private Builder otherBulder_;
private A otherInstance_;
private String name_;
Builder name(String name) {
name_ = name;
return this;
}
Builder otherBuilder(Builder other) {
otherBulder_ = other;
return this;
}
Builder otherInstance(A instance) {
otherInstance_ = instance;
return this;
}
A build() {
return new A(this);
}
}
public static void main(String[] args) {
Builder a1B = new Builder().name("A1");
Builder a2B = new Builder().name("A2");
A a = a1B.otherBuilder(a2B).build();
System.out.println(a);
System.out.println(a.other());
}
}
Комментарии:
1. Как это можно изменить, чтобы A и B были одним и тем же классом?
Ответ №2:
Я думаю, что мой ответ — это просто упрощенная версия ответа Op De Cirkel, адаптированная к вашим конкретным потребностям. Идея состоит в том, чтобы иметь экземпляр gprrime в builder, поэтому в конструкторе, если у builder есть ненулевой gprrime, используйте его, в противном случае создайте его:
private Graph(Builder builder){
/* .... setup code omitted....*/
if (builder.gPrime == null){
savedBuilder.gPrime = this;
this.gPrime = new Graph(savedBuilder);
}else{
this.gPrime = builder.gPrime;
}
}
this.gPrime.gPrime = this
нигде не будет работать, потому что gPrime
это поле final экземпляра и может быть инициализировано только при объявлении или в конструкторе для этого экземпляра. Что this.gPrime.gPrime = this
делается, так это инициализация поля final для другого экземпляра, что нарушает «окончательность» gPrime
Ответ №3:
Вы не можете инициализировать неизменяемую циклическую структуру.
Вы можете сделать его эффективно неизменяемым, изменив gPrime
значение не быть final
и убедившись, что вы всегда устанавливаете для него значение перед его использованием.
Благодаря ответу Op De Cirkel:
Вы можете использовать два builder или переключатель в функциональности вашего builder, где у вас есть builder, который содержит ссылку на объект Graph, который вы в данный момент создаете:
public class Graph {
private final Graph gPrime;
private final String name_;
private Graph(PrimeBuilder b) {
gPrime = b.g;
name_ = b.name_;
}
private Graph(Builder b) {
gPrime = new PrimeBuilder(this).name("gPrime").build();
name_ = b.name_;
}
public String name() {
return name_;
}
public Graph gPrime() {
return gPrime;
}
@Override
public String toString() {
return "I am " name_ ", my gPrime is " gPrime.name();
}
public static class PrimeBuilder {
private Graph g;
private String name_;
public PrimeBuilder(Graph g) {
this.g = g;
}
public PrimeBuilder name(String arg) {
name_ = arg;
return this;
}
public Graph build() {
return new Graph(this);
}
}
public static class Builder {
private String name_;
public Builder name(String arg) {
name_ = arg;
return this;
}
public Graph build() {
return new Graph(this);
}
}
}
Пример использования:
public class Main {
public static void main(String[] args) {
Graph g = new Graph.Builder().name("g").build();
System.out.println(g);
System.out.println(g.gPrime());
}
}
Комментарии:
1. NP. В случае, если строгая неизменяемость не требуется (нет многопоточности или способа безопасной публикации эффективно неизменяемого объекта), ваше решение предпочтительнее, поскольку оно менее сложное и менее запутанное.
2. Спасибо всем @Op De Cirkel @Stephen Denne. Мне нужно некоторое время, чтобы взглянуть на это.