I was unable to find any issue or discussion related to what I described below and so decided to create a ticket.

After change 73e382fa877f80994817a136b0adcc4365ccd904 `LocalCache` implements its own versions of methods like `computeIfAbsent` and `computeIfPresent`. These replace previous implementations inherited from `ConcurrentMap` with ones that delegate to unconditional call to `compute` with customised lambda e.g. `computeIfAbsent`:

```java
 return ((v = get(key)) == null &&
                (newValue = mappingFunction.apply(key)) != null &&
                (v = putIfAbsent(key, newValue)) == null) ? newValue : v;
```
becomes:

```java
compute(key, (k, oldValue) -> (oldValue == null) ? function.apply(key) : oldValue);
```

This has side effect that even if value is already present it is replaced by itself which confuses removal listener that will report value being removed with `RemovalCause.REPLACED`. Stats are possibly also incorrect (not used them to date so I am not sure).

Below test case reproduces this behaviour.

```java
List<RemovalNotification> notifications = new ArrayList<>();

Cache<Integer, Integer> cache = CacheBuilder.<Integer, Integer>newBuilder()
        .removalListener((RemovalListener<Integer, Integer>) notifications::add)
        .recordStats()
        .build();

cache.asMap().computeIfAbsent(1, integer -> integer + 1);
cache.asMap().computeIfAbsent(1, integer -> integer + 1);

assert notifications.size() == 1;
assert notifications.get(0).getCause() == RemovalCause.REPLACED;
assert cache.stats().evictionCount() == 0;
assert cache.stats().loadCount() == 2;
```
unlock();
        postWriteCleanup();
      }
    }
    
    @GuardedBy("this")
    void expand() {
      AtomicReferenceArray<ReferenceEntry<K, V>> oldTable = table;
      int oldCapacity = oldTable.length();
      if (oldCapacity >= MAXIMUM_CAPACITY) {
        return;
      }
      /*
       * Reclassify nodes in each list to new Map. Because we are using power-of-two expansion, the
       * elements from each bin must either stay at same index, or move with a power of two offset.
       * We eliminate unnecessary node creation by catching cases where old nodes can be reused
       * because their next fields won't change. Statistically, at the default threshold, only about
       * one-sixth of them need cloning when a table doubles. The nodes they replace will be garbage
       * collectable as soon as they are no longer referenced by any reader thread that may be in
       * the midst of traversing table right now.
       */
      int newCount = count;
      AtomicReferenceArray<ReferenceEntry<K, V>> newTable = newEntryArray(oldCapacity << 1);
      threshold = newTable.length() * 3 / 4;
      int newMask = newTable.length() - 1;
      for (int oldIndex = 0; oldIndex < oldCapacity; ++oldIndex) {
        
        
        ReferenceEntry<K, V> head = oldTable.get(oldIndex);
        if (head != null) {