In short
You have understood correctly.
In long
Analysis of the standard
15.3.3.3
says it very clearly:
A ForkNode is a ControlNode that splits a flow into multiple
concurrent flows. A ForkNode shall have exactly one incoming
ActivityEdge, though it may have multiple outgoing ActivityEdges.
The important thing here is that a ForkNode cannot have no outgoing activity edge, because this would cause an inconsistency. If such a nonsense would be possible, the flow of tokens would be interrupted, but the would never end. If a partial flow is to be ended, a FlowFinalNode must be used.
If a ForkNode has only one output, it is not very useful. However, it would create no inconsistency:
|
----->|-----> is equivalent to ---------->
|
Similarly, 15.3.3.4
gives the full picture:
A JoinNode is a ControlNode that synchronizes multiple flows. A
JoinNode shall have exactly one outgoing ActivityEdge but may have
multiple incoming ActivityEdges.
The reasoning is the same than before. It is important that there is at least one input. It is expected to have several, but if there'd be only one, it wouldn't cause any inconsistency.
The following sentence in the same section, also shows that the multiple input is the expectation, because an and
needs two operands. But the reformulation in plain text makes it work with only one input:
If a JoinNode does not have a joinSpec, then this is equivalent to a
joinSpec Expression with the Boolean operator “and.” That is, the
implicit default joinSpec condition is that there is at least one
token offered on each incoming ActivityEdge.
Note that a join with a single input may have some purpose, depending on how the joinSpec is formulated, and especially if the input is an ObjectFlow.
Expressivity
Having only one output to a fork has no practical effect on concurrency (the sole flow is concurrent with itself). If you don't beliee this, you'll have at least to agree that it has no more effect than having only a single start node, since 15.3.3.1
tells us that:
If an Activity has more than one InitialNode, then invoking the
Activity starts multiple concurrent control flows, one for each
InitialNode.
The fact that UML allows the modeller to consistently express something, doesn't mean that this thing will be useful. For example, UML allows to have an activity diagram made solely of a start node having a flow to an end node. Just another elaborate way to say that the activity consists of doing nothing.
The possibility to have only one input on a join or one output on a fork has some advantages in terms of expressivity, without affecting the semantic. By using this kind of construct:
- You can highlight that it's a partial diagram of the model, being understood that a part of the activity model is not represented or still needs to be designed;
- You can also suggest that some degree of concurrency could be expected between the fork and the join in future.
- You can isolate visually a sequence of actions