code: @NonNull
    public <K1 extends K, V1 extends V> Caffeine<K1, V1> expireAfter(
        @NonNull Expiry<? super K1, ? super V1> expiry) {
      requireNonNull(expiry);
      requireState(this.expiry == null, "Expiry was already set to %s", this.expiry);
      requireState(this.expireAfterAccessNanos == UNSET_INT,
          "Expiry may not be used with expiresAfterAccess");
      requireState(this.expireAfterWriteNanos == UNSET_INT,
          "Expiry may not be used with expiresAfterWrite");
  
      @SuppressWarnings("unchecked")
      Caffeine<K1, V1> self = (Caffeine<K1, V1>) this;
      self.expiry = expiry;
      return self;
    }
docs: Specifies that each entry should be automatically removed from the cache once a duration has
  elapsed after the entry's creation, the most recent replacement of its value, or its last
  read. The expiration time is reset by all cache read and write operations (including
  {@code Cache.asMap().get(Object)} and {@code Cache.asMap().put(K, V)}), but not by operations
  on the collection-views of {@link Cache#asMap}.
  <p>
  Expired entries may be counted in {@link Cache#estimatedSize()}, but will never be visible to
  read or write operations. Expired entries are cleaned up as part of the routine maintenance
  described in the class javadoc.
  
  @param expiry the expiry to use in calculating the expiration time of cache entries
  @param <K1> key type of the weigher
  @param <V1> value type of the weigher
  @return this {@code Caffeine} instance (for chaining)
  @throws IllegalStateException if expiration was already set