docs: public Iterable<L> bulkGet(Iterable<?> keys) {
      // Initially using the array to store the keys, then reusing it to store the respective L's
      final Object[] array = Iterables.toArray(keys, Object.class);
      if (array.length == 0) {
        return ImmutableList.of();
      }
      int[] stripes = new int[array.length];
      for (int i = 0; i < array.length; i++) {
        stripes[i] = indexFor(array[i]);
      }
      Arrays.sort(stripes);
      // optimize for runs of identical stripes
      int previousStripe = stripes[0];
      array[0] = getAt(previousStripe);
      for (int i = 1; i < array.length; i++) {
        int currentStripe = stripes[i];
        if (currentStripe == previousStripe) {
          array[i] = array[i - 1];
        } else {
          array[i] = getAt(currentStripe);
          previousStripe = currentStripe;
        }
      }
      /*
       * Note that the returned Iterable holds references to the returned stripes, to avoid
       * error-prone code like:
       *
       * Striped<Lock> stripedLock = Striped.lazyWeakXXX(...)'
       * Iterable<Lock> locks = stripedLock.bulkGet(keys);
       * for (Lock lock : locks) {
       *   lock.lock();
       * }
       * operation();
       * for (Lock lock : locks) {
       *   lock.unlock();
       * }
       *
       * If we only held the int[] stripes, translating it on the fly to L's, the original locks
       * might be garbage collected after locking them, ending up in a huge mess.
       */
      @SuppressWarnings("unchecked") // we carefully replaced all keys with their respective L's
      List<L> asList = (List<L>) Arrays.asList(array);
      return Collections.unmodifiableList(asList);
    }