docs: @Override
    public <K, S, T> KVStreamlet<KeyedWindow<K>, T>
          join(Streamlet<S> otherStreamlet, SerializableFunction<R, K> thisKeyExtractor,
               SerializableFunction<S, K> otherKeyExtractor, WindowConfig windowCfg,
               JoinType joinType, SerializableBiFunction<R, S, ? extends T> joinFunction) {
      checkNotNull(otherStreamlet, "otherStreamlet cannot be null");
      checkNotNull(thisKeyExtractor, "thisKeyExtractor cannot be null");
      checkNotNull(otherKeyExtractor, "otherKeyExtractor cannot be null");
      checkNotNull(windowCfg, "windowCfg cannot be null");
      checkNotNull(joinType, "joinType cannot be null");
      checkNotNull(joinFunction, "joinFunction cannot be null");
  
      StreamletImpl<S> joinee = (StreamletImpl<S>) otherStreamlet;
      JoinStreamlet<K, R, S, T> retval = JoinStreamlet.createJoinStreamlet(
          this, joinee, thisKeyExtractor, otherKeyExtractor, windowCfg, joinType, joinFunction);
      addChild(retval);
      joinee.addChild(retval);
      return new KVStreamletShadow<KeyedWindow<K>, T>(retval);
    }