Tworzę klient-serwer w Javie i napotkałem problem z przekazaniem niestandardowej implementacji Runnable jako argumentu z obiektu do innego. Problem polega na tym, że kod Runnable jest oceniany (nie wykonywany) w definicji, ale chcę, aby był oceniany podczas wywołania. Czy jest jakiś sposób na osiągnięcie takiego zachowania?

Tutaj kod, którego dotyczy ten problem:

  • Niestandardowa implementacja Runnable
public abstract class ChallengeReportDelegation implements Runnable
{
    private ChallengeReport fromChallengeReport = null;
    private ChallengeReport toChallengeReport = null;

    @Override
    public abstract void run();

    public ChallengeReport getFromChallengeReport()
    {
        return fromChallengeReport;
    }

    public ChallengeReport getToChallengeReport()
    {
        return toChallengeReport;
    }

    public void setFromChallengeReport(ChallengeReport fromChallengeReport)
    {
        this.fromChallengeReport = fromChallengeReport;
    }

    public void setToChallengeReport(ChallengeReport toChallengeReport)
    {
        this.toChallengeReport = toChallengeReport;
    }
}
  • Tutaj, gdzie Runnable jest przekazywana jako argument:
// Record challenge
this.challengesManager.recordChallenge(whoSentRequest, whoConfirmedRequest,
                            new ChallengeReportDelegation()
                            {
                                @Override
                                public void run()
                                {
                                    ChallengeReport fromReport = getFromChallengeReport();
                                    ChallengeReport toReport = getToChallengeReport();
                                    sendMessage(whoSentRequest, new Message(MessageType.CHALLENGE_REPORT, String.valueOf(fromReport.winStatus), String.valueOf(fromReport.challengeProgress), String.valueOf(fromReport.scoreGain)));
                                    sendMessage(whoConfirmedRequest, new Message(MessageType.CHALLENGE_REPORT, String.valueOf(toReport.winStatus), String.valueOf(toReport.challengeProgress), String.valueOf(toReport.scoreGain)));
                                }
                            });
  • Obiekt odbierający przechowuje instancję ChallengeReportDelegation jako completionOperation, poczekaj na upływ czasu, a następnie wykonaj ten kod.
private void complete()
    {
        stopTranslations();

        int fromStatus;
        int toStatus;
        if (this.fromScore > this.toScore)
        {
            fromStatus = 1;
            toStatus = -1;
        }
        else if (this.fromScore < this.toScore)
        {
            fromStatus = -1;
            toStatus = 1;
        }
        else
        {
            fromStatus = 0;
            toStatus = 0;
        }

        this.completionOperation.setFromChallengeReport(new ChallengeReport(this.from, fromStatus,this.fromTranslationsProgress, this.fromScore));
        this.completionOperation.setToChallengeReport(new ChallengeReport(this.to, toStatus, this.toTranslationsProgress, this.toScore));
        this.completionOperation.run();
    }

Powyższy kod wywołuje NullPointerException przy wykonaniu ostatniej części kodu w metodzie run.

[EDYTOWAĆ]

Wyjątek NullPointerException jest generowany, ponieważ zarówno getFromChallengeReport(), jak i getToChallengeReport() (druga część kodu) początkowo zwracają wartość null (gdy element Runnable jest zdefiniowany i przekazany jako argument), ale zwróciłyby spójne wartości w czasie wywołania run() (trzecia część kodu)

[EDIT2]

Odtworzyłem sytuację w tym prostym kodzie:

public class TestEvaluation
{
    public static void main(String[] args) throws InterruptedException
    {
        Middle middle = new Middle();

        middle.register(new Task() {
            @Override
            public void run() {
                System.out.println("a is: " +  getA());
                System.out.println("b is: " +  getB());
            }
        });

        Thread.sleep(2000);
    }

    abstract static class Task implements Runnable
    {
        private int a = 0;
        private int b = 0;

        public int getA() {
            return a;
        }

        public void setA(int a) {
            this.a = a;
        }

        public int getB() {
            return b;
        }

        public void setB(int b) {
            this.b = b;
        }

        @Override
        abstract public void run();
    }

    static class Middle
    {
        private ScheduledThreadPoolExecutor pool = new ScheduledThreadPoolExecutor(1);

        public void register(Task task)
        {
            Leaf leaf = new Leaf(new Task() {
                @Override
                public void run() {
                    System.out.println("Middle");
                    task.run();
                }
            });
            pool.schedule(leaf, 1, TimeUnit.SECONDS);
        }
    }

    static class Leaf implements Runnable
    {
        public Task task;

        public Leaf(Task task)
        {
            this.task = task;
        }

        @Override
        public void run()
        {
            task.setA(5);
            task.setB(5);
            System.out.println("Leaf");
            task.run();
        }
    }
}

Zachowanie, które chcę osiągnąć, to drukowanie

Leaf
Middle
a is: 5
b is: 5

Ale to właśnie otrzymuję

Leaf
Middle
a is: 0
b is: 0
0
Alessandro Meschi 2 kwiecień 2020, 12:59

3 odpowiedzi

Najlepsza odpowiedź

Znalazłem działające rozwiązanie tego problemu, być może trywialne dla osób pochodzących z programowania funkcjonalnego.

Zgodnie z przykładem w ostatniej edycji ([EDIT2])

import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

public class TestEvaluation
{
    public static void main(String[] args) throws InterruptedException
    {
        Middle middle = new Middle();

        middle.register(new Consumer<Values>() {
            @Override
            public void accept(Values values) {
                System.out.println("a is: " +  values.getA());
                System.out.println("b is: " +  values.getB());
            }
        });

        Thread.sleep(2000);
    }

    static class Values
    {
        private int a = 0;
        private int b = 0;

        public int getA() {
            return a;
        }

        public void setA(int a) {
            this.a = a;
        }

        public int getB() {
            return b;
        }

        public void setB(int b) {
            this.b = b;
        }
    }

    static class Middle
    {
        private ScheduledThreadPoolExecutor pool = new ScheduledThreadPoolExecutor(1);

        public void register(Consumer<Values> passed)
        {
            Consumer<Values> middleConsumer = new Consumer<Values>() {
                @Override
                public void accept(Values values) {
                    System.out.println("Middle");
                    passed.accept(values);
                }
            };

            Leaf leaf = new Leaf(middleConsumer);
            pool.schedule(leaf, 1, TimeUnit.SECONDS);
        }
    }

    static class Leaf implements Runnable
    {
        public Consumer<Values> task;

        public Leaf(Consumer<Values> task)
        {
            this.task = task;
        }

        @Override
        public void run()
        {
            Values values = new Values();
            values.setA(5);
            values.setB(5);
            System.out.println("Leaf");
            task.accept(values);
        }
    }
}

Ten kod zapewnia zachowanie, które chcę. Mam nadzieję, że to komuś pomoże. Twoje zdrowie!

-1
Alessandro Meschi 2 kwiecień 2020, 12:11

Jeśli chcesz od razu coś ocenić. Sugerowałbym w ogóle nie używać runnable. To brzmi jak wzorzec apti, próbujący przekazać kod, gdy wszystko, czego chcesz, to wartość / wywołanie.

Ponadto spróbuj zamiast tego użyć wywoływalnego lub dostawcy, ponieważ jesteś wyraźnie zainteresowany zwróceniem niektórych wartości z podprogramów.

-1
Johan Tidén 2 kwiecień 2020, 10:08

Bardzo prosty przykład. Stwórzmy runnable z polem.

public static void main (String[] args) {
    var x = new Runnable(){
        int a = 0;
        int getA(){
               return a;
        }
        void setA(int v){
            a = v;
        }
        public void run(){
            System.out.println("A : " + getA());
        }
    };
    x.run();
    x.setA(5);
    x.run();
}

Za pierwszym razem jest to 0, za drugim razem 5, ponieważ getA jest obliczane po wywołaniu run.

0
matt 2 kwiecień 2020, 11:44