Java自动机实现

本文介绍了如何使用Java枚举来实现有限状态机(FSM),通过枚举实现状态和转换,使得代码更紧凑、易读,同时也便于调试。FSM用于验证正则表达式,枚举方式简化了状态的遍历和管理,特别是当存在多个最终状态时,通过接口和额外的枚举来区分,使得实现更加优雅。
摘要由CSDN通过智能技术生成
这篇文章将解决在Java中实现有限状态机的问题。 如果您不知道什么是FSM或在什么地方可以使用FSM,您可能会热衷于阅读这个这个

如果您发现自己在设计上使用FSM的情况,则可能已经开始为实现相同接口的每个状态编写类。 一个好的设计可能是:

interface State { }
class State_1 implements State {}
class State_2 implements State {}
...

您可能有一个类可以管理这些状态以及它们之间的过渡,而另一个可以实现FSM的上下文(输入带状区域),另一个用于起始状态的接口,另一个用于结束状态的接口,依此类推。 许多类分散在许多文件中,使您无法快速跟踪它们。

还有另一种方法:使用枚举。

枚举本质上是类的列表,并且枚举的每个成员可能具有不同的实现。 假设我们要实现以下状态机:

初始化-('a')-> A
初始化-(else)->失败
A-('b')-> B A-('c')-> C A-('a')-> A A-(”)->结束 A-(其他)->失败 B-('c')-> C B-('b')-> B B-(”)->结束 B-(其他)->失败 C-('c')-> C C-(”)->结束 C-(其他)->失败

该FSM将验证以下正则表达式:^(a +)(b *)(c *)$。 我们可以将状态写成枚举状态的元素,如下所示:

interface State {
    public State next();
}
class Input {
    private String input;
    private int current;
    public Input(String input) {this.input = input;}
    char read() { return input.charAt(current++); }
}
 
enum States implements State {
    Init {
        @Override
        public State next(Input word) {
            switch(word.read()) {
                case 'a': return A;
                default: return Fail;
            }
        }
    },
    A {
        @Override
        public State next(Input word) {
            switch(word.read()) {
                case 'a': return A;
                case 'b': return B;
                case 'c': return C;
                case '': return null;
                default: return Fail;
            }
        }
    },
    B {
        @Override
        public State next(Input word) {
            switch(word.read()) {
                case 'b': return B;
                case 'c': return C;
                case '': return null;
                default: return Fail;
            }
        }
    },
    C {
        @Override
        public State next(Input word) {
            switch(word.read()) {
                case 'c': return C;
                case '': return null;
                default: return Fail;
            }
        }
    },
    Fail {
        @Override
        public State next(Input word) {
               return Fail;
        }
    };
 
    public abstract State next(Input word);
}

我们要做的是定义每个枚举中每个状态的转换。 每个过渡都会返回一个新状态,因此我们可以更有效地循环遍历它们:

State s;
Input in = new Input("aabbbc");
for(s = States.Init; s != null || s != States.Fail; s = s.next(in)) {}
 
if(s == null) {System.out.printlin("Valid!");}
else {System.out.println("Failed");}

此时,我们要么验证了字符串,要么失败了。 这是一个简单而优雅的设计。

我们可以通过将最终状态与主要状态分开来进一步改善实现,以简化遍历的退出条件。 我们将定义另一个名为FinalState的接口,以及一个将包含所需退出状态的第二枚举(相应地更改States枚举):

interface FinalState extends State {}
enum FinalStates implements FinalState {
    Fail {
        @Override
        public State next(Input word) {
               return Fail;
        }
    },
    Ok {
        @Override
        public State next(Input word) {
               return End;
        }
    }
}

这样,遍历将有所简化:

for(s = States.Init; !(s instanceof FinalState); s = s.next(in)) {}
switch(s) {
    case Fail: System.out.printlin("Failed"); break;
    case Ok: System.out.println("Valid!"); break;
    default: System.out.println("Undefined"); break;
}

优点是(除了更简单的遍历之外),我们可以指定两个以上的最终状态。 在大多数情况下,FSM会有多个出口点,因此建议从长远来看最后一种改进。 您还可以将所有这些枚举和接口放在同一源文件中,并将整个逻辑放在一个位置,而不是浏览多个选项卡,以便了解流程的工作原理。

总之,使用枚举可以更紧凑,更有意义地实现FSM。 您将所有逻辑都放在一个文件中,并且所有遍历都是轻松的。 您还将获得更轻松的调试体验,因为已转换的状态的名称将显示在调试器中(变量s将相应地更改其值,并带有新状态的名称),而不必弄清楚您刚刚上过什么课。 总而言之,我认为这是一个好技术。

参考:我们的JCG合作伙伴 Attila-Mihaly Balazs 在Java中实现自动机  Transylvania JUG博客上。


翻译自: https://www.javacodegeeks.com/2012/03/automaton-implementation-in-java.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值