финальный блокпост с builder

#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. Мне нужно некоторое время, чтобы взглянуть на это.