Функция "состав" и тип безопасности в java

Я пытаюсь реализовать класс, который изолирует часть объекта и заменяет часть чем-то другим. Часть объекта может не иметь того же типа, что и сам объект.

Простым примером может быть класс, который берет строку "-12--", изолирует номер ascii и заменяет ее следующим натуральным числом. Таким образом, вся последовательность будет "--12--" -> "12" -> 12 -> 13 -> "13" -> "--13--".

С учетом этого я реализовал следующее:

public abstract class Replacer<Outer, Inner>
    {
    protected abstract Inner decompose(Outer something);
    protected abstract Outer compose(Inner something);
    protected abstract Inner inner_replace(Inner something);
    public Outer replace(Outer something)
        {
        Inner s = decompose(something);
        s = inner_replace(s);
        return compose(s);
        }
    }

Теперь я хочу иметь возможность составлять серию Replacers - складывать их, чтобы каждый из них вычислял свой inner_replace с помощью "нижнего" Replacer:

public abstract class ComposableReplacer<Outer, Inner> extends Replacer<Outer, Inner>
    {
    protected Replacer<Inner, ?> child;

    @Override
    public Outer replace(Outer something)
        {
        Inner s = decompose(something);
        s = inner_replace(s);
        if (child!=null)
            s= child.replace(s);
        return compose(s);
        }

    }

Итак, да, это работает правильно, но теперь я пытаюсь написать метод удобства несколько композитных рельефов и автоматически складывать их:

public static <I, O> ComposableReplacer<I, O> compose(ComposableReplacer<?, ?>... rs)
    {
    for (int i=0; i<rs.length-1; i++)
        rs[i].child= rs[i+1];
    return rs[0];
    }

Это не удается, так как каждый внутренний тип ComposableReplacer должен быть внешним типом своего дочернего элемента, и компилятор не может сделать вывод, что из массива ComposableReplacer<?, ?>.

Возможно ли это сделать в java (и по-прежнему иметь безопасность типов)?

ИЗМЕНИТЬ Чтобы быть ясным, проблема заключается в объявлении метода, который принимает массив ComposableReplacer и стекирует/цепочки, с безопасностью типа.

Ответ 1

Даже если была поддержка общих массивов, ваш код завершится неудачно из-за логической ошибки. Массив состоит из элементов одного типа, но то, что вы хотите сделать, не работает с элементами того же типа. Это становится ясным, если вы попытаетесь реализовать свой метод только с двумя параметрами вместо varargs:

// won’t work
public static <I, O> ComposableReplacer<I, O> compose(
  ComposableReplacer<I, O> rs1, ComposableReplacer<I, O> rs2) {
  rs1.child=rs2;
  return rs1;
}

Этот код до сих пор не компилируется, поскольку rs1.child требует ComposableReplacer<O,?> вместо ComposableReplacer<I,O>, если вы исправите это, ваш метод станет

public static <I, O> ComposableReplacer<I, O> compose(
  ComposableReplacer<I, O> rs1, ComposableReplacer<O,?> rs2) {
  rs1.child=rs2;
  return rs1;
}

Теперь он работает, но два параметра имеют другой тип. Если у Java были массивы с типом, они должны были предотвращать одновременное использование a ComposableReplacer<I, O> и a ComposableReplacer<O,?>. (Если вы не применяете теги O и I, чтобы они были одинаковыми.)

Чтобы проиллюстрировать это далее, вот метод для трех параметров:

public static <I, O, X> ComposableReplacer<I, O> compose(
  ComposableReplacer<I, O> rs1, ComposableReplacer<O,X> rs2,
  ComposableReplacer<X, ?> rs3) {
  rs1.child=rs2;
  rs2.child=rs3;
  return rs1;
}

Здесь вы видите, что каждый аргумент имеет другой тип, и вам нужен дополнительный параметр типа, поэтому используйте "тип безопасного массива" (читайте java.util.List). Самое простое решение - сохранить метод двух аргументов и позволить вызывающему вызывающему его несколько раз. Или n-arg, если вы знаете, что использование аргументов n args потребуется очень часто.

Ответ 2

Я бы сделал что-то вроде этого, чтобы достичь цели

Композиция может быть выполнена:

  • если Inner и Outer различны: передача дочернего заменителя в родительский Заменитель
  • если Inner и Outer равны:

    • ssing child replacer для родительского заменителя,
    • используя сложный статический метод, который устанавливает дочерние элементы по мере необходимости.

.

abstract class Replacer<Outer, Inner> {

    private Replacer<Inner, ?> child;

    protected abstract Inner decompose(Outer something);

    protected abstract Outer compose(Inner something);

    protected abstract Inner inner_replace(Inner something);

    public Replacer(Replacer<Inner, ?> child) {
        this.child = child;
    }

    public Outer replace(Outer something) {
        Inner s = decompose(something);
        s = inner_replace(s);
        if (child != null)
            s = child.replace(s);
        return compose(s);
    }

    public void setChild(Replacer<Inner, ?> child) {
        this.child = child;
    }

    @SafeVarargs
    public static <T> Replacer<T, T> compose(Replacer<T, T>... replacers) {
        if (replacers.length == 0)
            return new DummyReplacer<>();
        else {
            Replacer<T, T> current = replacers[0];
            for (int i = 1; i < replacers.length; ++i) {
                current.setChild(replacers[i]);
                current = replacers[i];
            }
            return replacers[0];
        }
    }
}

class DummyReplacer<Outer> extends Replacer<Outer, Outer> {

    public DummyReplacer(Replacer<Outer, ?> child) {
        super(child);
    }

    public DummyReplacer() {
        super(null);
    }

    @Override
    protected Outer decompose(Outer something) {
        return something;
    }

    @Override
    protected Outer compose(Outer something) {
        return something;
    }

    @Override
    protected Outer inner_replace(Outer something) {
        return something;
    }

}

class Multiply extends Replacer<Integer, Integer> {

    private int factor;

    public Multiply(int factor, Replacer<Integer, ?> child) {
        super(child);
        this.factor = factor;
    }

    public Multiply(int factor) {
        super(null);
        this.factor = factor;
    }

    @Override
    protected Integer decompose(Integer something) {
        return something;
    }

    @Override
    protected Integer compose(Integer something) {
        return something;
    }

    @Override
    protected Integer inner_replace(Integer something) {
        return something * factor;
    }

}

class Negate extends Replacer<String, Integer> {
    public Negate(Replacer<Integer, ?> child) {
        super(child);
    }

    public Negate() {
        super(null);
    }

    @Override
    protected Integer inner_replace(Integer something) {
        return -something;
    }

    @Override
    protected Integer decompose(String something) {
        return Integer.parseInt(something);
    }

    @Override
    protected String compose(Integer something) {
        return something.toString();
    }
}

class SharpToTildeExtract extends Replacer<String, String> {
    public SharpToTildeExtract(Replacer<String, ?> child) {
        super(child);
    }

    public SharpToTildeExtract() {
        super(null);
    }

    @Override
    protected String decompose(String something) {
        return something.substring(1, something.length() - 1);
    }

    @Override
    protected String compose(String something) {
        return "~" + something + "~";
    }

    @Override
    protected String inner_replace(String something) {
        return something;
    }
}

class UpperCaseReplacer extends Replacer<String, String> {

    public UpperCaseReplacer(Replacer<String, ?> child) {
        super(child);
    }

    public UpperCaseReplacer() {
        super(null);
    }

    @Override
    protected String decompose(String something) {
        return something;
    }

    @Override
    protected String compose(String something) {
        return something;
    }

    @Override
    protected String inner_replace(String something) {
        return something.toUpperCase();
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println(new SharpToTildeExtract().replace("#abc#"));
        System.out.println(new SharpToTildeExtract(new UpperCaseReplacer()).replace("#abc#"));
        System.out.println(Replacer.compose(new SharpToTildeExtract(), new UpperCaseReplacer()).replace("#abc#"));

        System.out.println(new SharpToTildeExtract(new Negate(new Multiply(2))).replace("#5#"));
    }
}

Ответ 3

ComposableReplacer - проблема.

Попробуйте следующее:

public abstract class Replacer< Outer, Inner > {
    private static class DelegatingReplacer< Outer, Inner > {
        private final Replacer< Outer, Inner > rplDelegate;
        public DelegatingReplacer( Replacer< Outer, Inner > rplDelegate ) { this.rplDelegate = rplDelegate; }

        @Override protected Inner decompose( Outer something ) { return rplDelegate.decompose( something ); }
        @Override protected Outer compose( Inner something ) { return rplDelegate.compose( something ); }
        @Override protected Inner inner_replace( Inner something ) { return rplDelegate.inner_replace( something ); }
    }
    protected abstract Inner decompose( Outer something );
    protected abstract Outer compose( Inner something );
    protected abstract Inner inner_replace( Inner something );
    public final Outer replace( Outer something ) {
        return compose( inner_replace( decompose( something ) ) );
    }
    public < Innerer > Replacer< Outer, Inner > chain( final Replacer< Inner, Innerer > rplChained ) {
        return new DelegatingReplacer< Outer, Inner >( this ) {
            @Override protected inner_replace( Inner something ) {
                 return rplChained.replace( super.inner_replace( something ) );
            }
        }
    }
}

Теперь вы можете сделать

r1.chain( r2.chain( r3 ) ).replace( foo ); // for r1< O, I >, r2< I, J >, r3< J, K > 
r1.chain( r2 ).chain( r3 ).replace( foo ); // for r1< O, I >, r2< I, J >, r3< I, K >

Вы не можете ожидать, что гетерогенный массив Replacer будет создан статически-безопасным способом. Но если построение упомянутого массива должно было быть гарантировано статически безопасным по типу, это означало бы, что вы могли бы статически определить взаимную совместимость типов элементов и могли бы построить вашу цепочку, как показано выше.

Единственная причина, по которой нужен метод на основе массива, заключается в том, что во время выполнения определялись заменители (и по расширению их типов). Таким образом, ваш вопрос (как гарантировать безопасность во время компиляции типа массива с типами, определенными во время выполнения), кажется, не имеет смысла.