Repository: netty/netty
Issue: Have (Int,Long,Char,...)ObjectMap implement Map interface.
Labels: improvement

I just noticed that Netty's primitive hash map implementations don't implement the JDK's `Map` interface. I think they would be much more useful if they did. 


Issue comments:
Just do something like this:

``` java
class IntObjectHashMap implements Map<Integer, Object> {
  public Object put(Integer key, Object value) {
    put(key.intValue(), value);
  }

  public Object put(int key, Object value) {
    // implementation goes here.
  }
}
```

That way one can use this as a normal `Map` and all those `IntObjectMap`, `LongObjectMap` interfaces can be deleted.

@nmittler @normanmaurer 

@buchgr the problem is that this will add extra boxing. 

I think it would be fine if there were boxing methods in addition to the existing non-boxing methods.

@normanmaurer No it doesn't in my example above ?

@nmittler @buchgr right if we keep both it would be fine

@normanmaurer @nmittler I think we should do it before 4.1 is released. Map implementation would be much more elegant and useful. We should also get rid of the custom interfaces. WDYT?

@buchgr Absolutely, I can't wait to review the PR that you put together without any help from us whatsoever! :stuck_out_tongue_winking_eye:

lol

@nmittler well I would really love to do that, but most unfortunately my hands are tied due to the nice folks at legal :-). 

However, to our rescue I do know a great and highly motivated dev at Google who would be perfect for the job _wink_ _wink_! And you know ... I am a nice guy, so I am happy to with extreme care and in most detail review and comment on every line of that sweet PR :laughing: 

_sigh_ ... I guess that would be "googly" of me :/

@buchgr I've thrown together #3984 ... I await my free beer! ;)

@nmittler thanks! I ll make it a pitcher boss :laughing: 

+1 :)


Pull request review comments:
~~@nmittler those cast's aren't necessary no?~~

@nmittler I would be in favor of getting rid of `PEntry`. Don't think it's really worth it. WDYT? // @normanmaurer @Scottmitch 

~~@nmittler can we do something like `containsKey(@primitiveType@)` i.e. `containsKey(int key)`?~~

Yes they are necessary ... see my previous comment.  If I don't cast, this triggers autoboxing on the method and leads to ambiguity in the method prototype.

@nmittler that shouldn't be necessary. if it's not of correct type the cast will fail anyway. `get, put, etc.` are quite critical to performance, I think we shouldn't do `instanceof` checks there?

On the fence.  The code would certainly be simpler. Having it does provide a lower cost means of iteration, however.

@nmittler yeah I think we should get rid of it ... cause ultimately we need to convert to `Entry<K, V>` anyway as there are no primitive generics. So that doesn't buy us anything?

@nmittler oh you have a `entries()` method ... hmm my guess would be that escape analysis will get rid of the and it won't have any impact, but I have nothing to back that up. So If you wanna keep it, let's keep it :-).

@nmittler s/pPut/put/

@nmittler s/pRemove/remove/

@nmittler s/pGet/get/

@nmittler I have to read up on auto boxing rules T_T

@nmittler alternatively have the primitive map `implement Iterable<PEntry<V>>` and make this an Iterator?

@buchgr you may be right ... I don't feel strongly one way or the other.  It would certainly clean up the code to get rid of it.

@normanmaurer @Scottmitch any thoughts?

@buchgr yeah I was considering that as well ... I guess we can live with a `ClassCastException`.

done.

done.

done.

@nmittler remove `.entries()`. 

why this double cast ?

I guess remove it should be fine

@nmittler I'm not sure I understand why the casts are needed. Can you give more details what exactly happens if you not cast ? Does it not compile ? 

@normanmaurer yes it fails to compile. My understanding of what happens is: the compiler searches for `put(int, int)`. Can't find it. Now it falls back to auto boxing. Both `put(Integer, Integer)` and `put(int, Integer)` are possible due to auto boxing  - thus the call is ambiguous. If one casts to `Integer` however, the compiler can immediately find `put(int, Integer)` and doesn't use auto boxing at all.

I think this should be `Long.valueOf(value)`?

Should we make this a static final variable? 

@buchgr @normanmaurer yeah that's the behavior I was seeing.  Didn't see anything online describing that behavior, but it does fail to compile.

Yes it should be `Long.valueOf(value)` ... done.

done.

done.

`iterator` -> `EMPTY_ITERATOR`?

Also is this needed anymore?  If so should we expose it via a `public static` method of some kind?

if this is `Unmodifiable`...should we `.clone()`?

if this is `Unmodifiable`...should we `.clone()`?

Should we cache and lazy initialize this like `UnmodifiableMap` does?

``` java
public Set<K> keySet() {
            if (keySet==null)
                keySet = unmodifiableSet(m.keySet());
            return keySet;
        }
```

Should we cache and lazy initialize this like `UnmodifiableMap` does?

Should this be unmodifiable?  Also we should consider caching this.

Should `EmptyIntObjectMap` be "script templated" like the other classes?  Also the associated accessors in this class?

Should `UnmodifiableIntObjectMap` be "script templated" like the other classes?  Also the associated accessors in this class?

Sry I'm a bit late to the discussion on this one.  Do we have any benchmarking to determine if getting rid of this is "acceptable"?  I agree it makes things simpler/cleaner, but wondering what getting rid of the "native" entry will cost us.

add `@Override`?

Is this cast safe?

`objectToKey` currently assumes the object will be of the correct type to translate to a native type.  For example in `IntObjectHashMap` we have:

``` java
    private int objectToKey(Object key) {
        return (int) ((Integer) key).intValue();
    }
```

However what if the `Object` is not of the correct type?

Can we unroll this `sb.length() == 0` check?  I think we just want to unconditionally append a `'{'` before the loop anyways right?

`size == 0` -> `isEmpty()`?

@Scottmitch I don't believe we're using this.  Should we just get rid of it?

No ... do we care? One way or another the call will get an exception.

I had previously had a check for `instanceof`, but @buchgr asked that it be removed for performance.  I don't think we care since it will throw an exception one way or another.

I think it's fair to assume that iteration over a map is slow :).  With that said, I think the places where it's done aren't frequently called or terribly time-sensitive.

Is this used?

@nmittler agree.

Do we need this anymore?  I wonder if it is better to just use the `Collection<V>` from `Map`? This way you are not forced to allocate the entire structure in memory.

Now that we no longer have `entries()` this option seems like the only way to "iterate" over a native array/collection.  This method may be a bit intensive and require a bunch of memory allocation.  Is it worth supporting this at the interface level?

I see map allows a `ClassCastException`. Fine as is.

Fine as is.

Yes this makes sense. Map allows `ClassCastException` to be thrown.  I was originally assuming we should not throw in all cases...but it seems OK where we are using this method currently.

Hum not sure if we are using it...but it may be good for completeness to have this. We are using the empty map...and the unmodifiable should follow the same pattern...so shouldn't be too much extra work?

> I think it's fair to assume that iteration over a map is slow :)

Are we adding boxing penalties on top of the iteration though?

> With that said, I think the places where it's done aren't frequently called or terribly time-sensitive.

One interesting place where we iterate over the entries is in `DefaultHttp2RemoteFlowController` every time we allocate bytes. It would be good to understand the impact in this case.

+1

Yes, but let's address that in a follow-on PR?

No it's not used ... removed.

Agreed. https://github.com/netty/netty/issues/4001

See https://github.com/netty/netty/issues/4001

+1 thanks!

done.

done.

It is already a copy so it's not a problem if they change it.

It is already a copy so it's not a problem if they change it.

done.

done.

done.

done.

Yes, good catch. Done.

sgtm. thanks!

sgtm. thanks!

Sounds reasonable. Done.

Done.

final  ?

done.

@nmittler - Can we leave the `collection` terminology out of this comment? Maybe something like...

```
Get an iterator to traverse over the primitive entries contained in this map.
```

nit: Maybe `entryIterator()` to be more consistent with the `Map` interface of `entrySet()`?

add `@SuppressWarnings("unchecked")`

@nmittler - Add a `@SuppressWarnings("unchecked")`?

add `private` on all of these members?

@nmittler - I'm wondering if we should just be verbose and rename `PEntry` to `PrimitiveEntry`?

I don't think this is used anymore.

Do we have to have this as part of the exposed interface?  Consider making `private`.

should you do a `toExternal(value)` here?

line 467: `values[index] = toInternal(oldVal);` do you need the `toInternal` here?

@nmittler - Since this involves iterator over the map....and we have seen there may be benefits to using the "primitive" iterator... should we keep this in the interface?

`final`?

The class is already private.

@Scottmitch I don't have a strong opinion here.  Anyone else have an opinion?

@normanmaurer / @buchgr ?

done.

Not needed ... the cast seems to be fine.

@Scottmitch this shouldn't be needed ... are you seeing an issue in your IDE?

Just to close the loop ... we've reverted to using primitive entries.

@Scottmitch what line are you referring to?

done.

done.

done.

@Scottmitch the other `putAll` method is already optimized for this.

done.

It's not returning an `Iterator`.  This method is more analogous to `entrySet`, which returns a `Set` ... this returns an `Iterable`. 

@nmittler - Yip. Eclipse doesn't like it.

You are right...fine as is. Read too quickly...thought we were returning an iterator.

The class visibility and the member visibility are independent.  Unless you are using these members outside the scope of the inner class (intentionally package private to avoid the potential of synthetic method generate) I think we should set the accessor level like we would for "regular" classes.

@nmittler - Sorry I could not leave a comment on the line but I was talking about the `toInternal` call in the `rehash(..)` method.

@nmittler - I'm not sure what we are gaining by abbreviating here. Is `Primitive` overly verbose?

@nmittler no strong opinion. IMO `PrimitiveEntry` sounds better though.

@nmittler - Aren't we casting without checking? Seems like this is an appropriate warning.

@nmittler - Disregard my comment :) I see the `instanceof` check now.

@nmittler - Eclipse doesn't like it :)

```
Type safety: Unchecked cast from Map<capture#4-of ? extends Integer,capture#5-of ? extends V> to IntObjectHashMap<V>
```

@Scottmitch do you have an example of how one can access a package private field of a private class outside of the private scope? It's not obvious to me that this is possible?

+1 ... this seems strange to me.  An example (or a link) would be helpful.

Good enough ... `PrimitiveEntry` it is :)

done.

Ah .. no, shouldn't need that.  Done.

done.

The point I am trying to focus on is just because this is an inner class doesn't mean we should use different rules for the internals (unless there is a reason to... i.e. avoid synthetic method generation).

[DefaultHttp2Connection.java#L839](https://github.com/netty/netty/blob/master/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2Connection.java#L839).  The access level does not have to be package private to access them outside the scope, but this is typically done if the member is used outside the scope of the inner class to avoid the potential of synthetic member generation to access an inner private member of the class.

Here is another example:

``` java
public class AccessLevelTest {
    private AccessLevelTest() {
        InnerClass inner = new InnerClass();
        int x = inner.a + inner.b + inner.c + inner.d;
        System.out.println("x=" + x);
    }

    private static final class InnerClass {
        private int a = 1;
        int b = 2;
        protected int c = 3;
        public int d = 4;
    }

    public static void main(String[] args) {
        new AccessLevelTest();
    }
}
```

@Scottmitch are you confident that the example you gave does not cause synthetic member generation for `numActiveStreams`? I would expect it would since the class is still private, but perhaps there is some hackery going on in the compiler for this case?

Regardless, I'm fine making the change ... done.

@Scottmitch I don't see why we should care about synthetic method generation. It's come up in a couple of code reviews and I would like to understand why we spend time discussing it? Like what good comes from trying to avoid it? :-)


Patch that resolved the issue:
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2Connection.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2Connection.java
index 71a574f6cc1..c437282f62e 100644
--- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2Connection.java
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2Connection.java
@@ -387,8 +387,7 @@ public final int numChildren() {
 
         @Override
         public Http2Stream forEachChild(Http2StreamVisitor visitor) throws Http2Exception {
-            for (IntObjectHashMap.Entry<DefaultStream> entry : children.entries()) {
-                Http2Stream stream = entry.value();
+            for (DefaultStream stream : children.values()) {
                 if (!visitor.visit(stream)) {
                     return stream;
                 }
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameReader.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameReader.java
index 15568344ac4..035fc949e81 100644
--- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameReader.java
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameReader.java
@@ -484,7 +484,7 @@ private void readSettingsFrame(ChannelHandlerContext ctx, ByteBuf payload,
                 char id = (char) payload.readUnsignedShort();
                 long value = payload.readUnsignedInt();
                 try {
-                    settings.put(id, value);
+                    settings.put(id, Long.valueOf(value));
                 } catch (IllegalArgumentException e) {
                     switch(id) {
                     case SETTINGS_MAX_FRAME_SIZE:
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameWriter.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameWriter.java
index 036537aa2d9..20fc26baa29 100644
--- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameWriter.java
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameWriter.java
@@ -213,7 +213,7 @@ public ChannelFuture writeSettings(ChannelHandlerContext ctx, Http2Settings sett
             int payloadLength = SETTING_ENTRY_LENGTH * settings.size();
             ByteBuf buf = ctx.alloc().buffer(FRAME_HEADER_LENGTH + settings.size() * SETTING_ENTRY_LENGTH);
             writeFrameHeaderInternal(buf, payloadLength, SETTINGS, new Http2Flags(), 0);
-            for (CharObjectMap.Entry<Long> entry : settings.entries()) {
+            for (CharObjectMap.PrimitiveEntry<Long> entry : settings.entries()) {
                 writeUnsignedShort(entry.key(), buf);
                 writeUnsignedInt(entry.value(), buf);
             }
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2ClientUpgradeCodec.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2ClientUpgradeCodec.java
index 0cdb65b9416..dc13efb5eca 100644
--- a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2ClientUpgradeCodec.java
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2ClientUpgradeCodec.java
@@ -14,28 +14,28 @@
  */
 package io.netty.handler.codec.http2;
 
+import static io.netty.handler.codec.base64.Base64Dialect.URL_SAFE;
+import static io.netty.handler.codec.http2.Http2CodecUtil.HTTP_UPGRADE_PROTOCOL_NAME;
+import static io.netty.handler.codec.http2.Http2CodecUtil.HTTP_UPGRADE_SETTINGS_HEADER;
+import static io.netty.handler.codec.http2.Http2CodecUtil.SETTING_ENTRY_LENGTH;
+import static io.netty.handler.codec.http2.Http2CodecUtil.writeUnsignedInt;
+import static io.netty.handler.codec.http2.Http2CodecUtil.writeUnsignedShort;
+import static io.netty.util.CharsetUtil.UTF_8;
+import static io.netty.util.ReferenceCountUtil.release;
+import static io.netty.util.internal.ObjectUtil.checkNotNull;
+
 import io.netty.buffer.ByteBuf;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.handler.codec.base64.Base64;
 import io.netty.handler.codec.http.FullHttpResponse;
 import io.netty.handler.codec.http.HttpClientUpgradeHandler;
 import io.netty.handler.codec.http.HttpRequest;
-import io.netty.util.collection.CharObjectHashMap;
+import io.netty.util.collection.CharObjectMap;
 
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 
-import static io.netty.handler.codec.base64.Base64Dialect.URL_SAFE;
-import static io.netty.handler.codec.http2.Http2CodecUtil.HTTP_UPGRADE_PROTOCOL_NAME;
-import static io.netty.handler.codec.http2.Http2CodecUtil.HTTP_UPGRADE_SETTINGS_HEADER;
-import static io.netty.handler.codec.http2.Http2CodecUtil.SETTING_ENTRY_LENGTH;
-import static io.netty.handler.codec.http2.Http2CodecUtil.writeUnsignedInt;
-import static io.netty.handler.codec.http2.Http2CodecUtil.writeUnsignedShort;
-import static io.netty.util.CharsetUtil.UTF_8;
-import static io.netty.util.ReferenceCountUtil.release;
-import static io.netty.util.internal.ObjectUtil.checkNotNull;
-
 /**
  * Client-side cleartext upgrade codec from HTTP to HTTP/2.
  */
@@ -105,7 +105,7 @@ private String getSettingsHeaderValue(ChannelHandlerContext ctx) {
             // Serialize the payload of the SETTINGS frame.
             int payloadLength = SETTING_ENTRY_LENGTH * settings.size();
             buf = ctx.alloc().buffer(payloadLength);
-            for (CharObjectHashMap.Entry<Long> entry : settings.entries()) {
+            for (CharObjectMap.PrimitiveEntry<Long> entry : settings.entries()) {
                 writeUnsignedShort(entry.key(), buf);
                 writeUnsignedInt(entry.value(), buf);
             }
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2Settings.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2Settings.java
index 5aaca0555f6..da719e4bca8 100644
--- a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2Settings.java
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2Settings.java
@@ -46,6 +46,8 @@ public final class Http2Settings extends CharObjectHashMap<Long> {
      * the standard settings will not cause the map capacity to change.
      */
     private static final int DEFAULT_CAPACITY = (int) (NUM_STANDARD_SETTINGS / DEFAULT_LOAD_FACTOR) + 1;
+    private static final Long FALSE = 0L;
+    private static final Long TRUE = 1L;
 
     public Http2Settings() {
         this(DEFAULT_CAPACITY);
@@ -84,7 +86,7 @@ public Long headerTableSize() {
      * @throws IllegalArgumentException if verification of the setting fails.
      */
     public Http2Settings headerTableSize(int value) {
-        put(SETTINGS_HEADER_TABLE_SIZE, (long) value);
+        put(SETTINGS_HEADER_TABLE_SIZE, Long.valueOf(value));
         return this;
     }
 
@@ -96,14 +98,14 @@ public Boolean pushEnabled() {
         if (value == null) {
             return null;
         }
-        return value != 0L;
+        return TRUE.equals(value);
     }
 
     /**
      * Sets the {@code SETTINGS_ENABLE_PUSH} value.
      */
     public Http2Settings pushEnabled(boolean enabled) {
-        put(SETTINGS_ENABLE_PUSH, enabled ? 1L : 0L);
+        put(SETTINGS_ENABLE_PUSH, enabled ? TRUE : FALSE);
         return this;
     }
 
@@ -120,7 +122,7 @@ public Long maxConcurrentStreams() {
      * @throws IllegalArgumentException if verification of the setting fails.
      */
     public Http2Settings maxConcurrentStreams(long value) {
-        put(SETTINGS_MAX_CONCURRENT_STREAMS, value);
+        put(SETTINGS_MAX_CONCURRENT_STREAMS, Long.valueOf(value));
         return this;
     }
 
@@ -137,7 +139,7 @@ public Integer initialWindowSize() {
      * @throws IllegalArgumentException if verification of the setting fails.
      */
     public Http2Settings initialWindowSize(int value) {
-        put(SETTINGS_INITIAL_WINDOW_SIZE, (long) value);
+        put(SETTINGS_INITIAL_WINDOW_SIZE, Long.valueOf(value));
         return this;
     }
 
@@ -154,7 +156,7 @@ public Integer maxFrameSize() {
      * @throws IllegalArgumentException if verification of the setting fails.
      */
     public Http2Settings maxFrameSize(int value) {
-        put(SETTINGS_MAX_FRAME_SIZE, (long) value);
+        put(SETTINGS_MAX_FRAME_SIZE, Long.valueOf(value));
         return this;
     }
 
@@ -183,7 +185,7 @@ public Http2Settings maxHeaderListSize(int value) {
             value = Integer.MAX_VALUE;
         }
 
-        put(SETTINGS_MAX_HEADER_LIST_SIZE, (long) value);
+        put(SETTINGS_MAX_HEADER_LIST_SIZE, Long.valueOf(value));
         return this;
     }
 
diff --git a/common/src/main/java/io/netty/util/collection/PrimitiveCollections.java b/common/src/main/java/io/netty/util/collection/PrimitiveCollections.java
index 99030708834..4cf1f53623b 100644
--- a/common/src/main/java/io/netty/util/collection/PrimitiveCollections.java
+++ b/common/src/main/java/io/netty/util/collection/PrimitiveCollections.java
@@ -14,12 +14,12 @@
  */
 package io.netty.util.collection;
 
-import io.netty.util.internal.EmptyArrays;
-
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Iterator;
+import java.util.Map;
 import java.util.NoSuchElementException;
+import java.util.Set;
 
 /**
  * Utility methods for primitive collections.
@@ -61,11 +61,6 @@ public Object put(int key, Object value) {
             throw new UnsupportedOperationException("put");
         }
 
-        @Override
-        public void putAll(IntObjectMap<Object> sourceMap) {
-            throw new UnsupportedOperationException("putAll");
-        }
-
         @Override
         public Object remove(int key) {
             return null;
@@ -81,11 +76,21 @@ public boolean isEmpty() {
             return true;
         }
 
+        @Override
+        public boolean containsKey(Object key) {
+            return false;
+        }
+
         @Override
         public void clear() {
             // Do nothing.
         }
 
+        @Override
+        public Set<Integer> keySet() {
+            return Collections.emptySet();
+        }
+
         @Override
         public boolean containsKey(int key) {
             return false;
@@ -97,24 +102,39 @@ public boolean containsValue(Object value) {
         }
 
         @Override
-        public Iterable<Entry<Object>> entries() {
+        public Iterable<PrimitiveEntry<Object>> entries() {
             return Collections.emptySet();
         }
 
         @Override
-        public int[] keys() {
-            return EmptyArrays.EMPTY_INTS;
+        public Object get(Object key) {
+            return null;
         }
 
         @Override
-        public Object[] values(Class<Object> clazz) {
-            return EmptyArrays.EMPTY_OBJECTS;
+        public Object put(Integer key, Object value) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Object remove(Object key) {
+            return null;
+        }
+
+        @Override
+        public void putAll(Map<? extends Integer, ?> m) {
+            throw new UnsupportedOperationException();
         }
 
         @Override
         public Collection<Object> values() {
             return Collections.emptyList();
         }
+
+        @Override
+        public Set<Entry<Integer, Object>> entrySet() {
+            return Collections.emptySet();
+        }
     }
 
     /**
@@ -122,9 +142,12 @@ public Collection<Object> values() {
      *
      * @param <V> the value type stored in the map.
      */
-    private static final class UnmodifiableIntObjectMap<V> implements IntObjectMap<V>,
-            Iterable<IntObjectMap.Entry<V>> {
-        final IntObjectMap<V> map;
+    private static final class UnmodifiableIntObjectMap<V> implements IntObjectMap<V> {
+        private final IntObjectMap<V> map;
+        private Set<Integer> keySet;
+        private Set<Entry<Integer, V>> entrySet;
+        private Collection<V> values;
+        private Iterable<PrimitiveEntry<V>> entries;
 
         UnmodifiableIntObjectMap(IntObjectMap<V> map) {
             this.map = map;
@@ -140,11 +163,6 @@ public V put(int key, V value) {
             throw new UnsupportedOperationException("put");
         }
 
-        @Override
-        public void putAll(IntObjectMap<V> sourceMap) {
-            throw new UnsupportedOperationException("putAll");
-        }
-
         @Override
         public V remove(int key) {
             throw new UnsupportedOperationException("remove");
@@ -171,42 +189,80 @@ public boolean containsKey(int key) {
         }
 
         @Override
-        public boolean containsValue(V value) {
+        public boolean containsValue(Object value) {
             return map.containsValue(value);
         }
 
         @Override
-        public Iterable<Entry<V>> entries() {
-            return this;
+        public boolean containsKey(Object key) {
+            return map.containsKey(key);
+        }
+
+        @Override
+        public V get(Object key) {
+            return map.get(key);
+        }
+
+        @Override
+        public V put(Integer key, V value) {
+            throw new UnsupportedOperationException("put");
+        }
+
+        @Override
+        public V remove(Object key) {
+            throw new UnsupportedOperationException("remove");
         }
 
         @Override
-        public Iterator<Entry<V>> iterator() {
-            return new IteratorImpl(map.entries().iterator());
+        public void putAll(Map<? extends Integer, ? extends V> m) {
+            throw new UnsupportedOperationException("putAll");
         }
 
         @Override
-        public int[] keys() {
-            return map.keys();
+        public Iterable<PrimitiveEntry<V>> entries() {
+            if (entries == null) {
+                entries = new Iterable<PrimitiveEntry<V>>() {
+                    @Override
+                    public Iterator<PrimitiveEntry<V>> iterator() {
+                        return new IteratorImpl(map.entries().iterator());
+                    }
+                };
+            }
+
+            return entries;
         }
 
         @Override
-        public V[] values(Class<V> clazz) {
-            return map.values(clazz);
+        public Set<Integer> keySet() {
+            if (keySet == null) {
+                keySet = Collections.unmodifiableSet(map.keySet());
+            }
+            return keySet;
+        }
+
+        @Override
+        public Set<Entry<Integer, V>> entrySet() {
+            if (entrySet == null) {
+                entrySet = Collections.unmodifiableSet(map.entrySet());
+            }
+            return entrySet;
         }
 
         @Override
         public Collection<V> values() {
-            return map.values();
+            if (values == null) {
+                values = Collections.unmodifiableCollection(map.values());
+            }
+            return values;
         }
 
         /**
          * Unmodifiable wrapper for an iterator.
          */
-        private class IteratorImpl implements Iterator<Entry<V>> {
-            final Iterator<Entry<V>> iter;
+        private class IteratorImpl implements Iterator<PrimitiveEntry<V>> {
+            final Iterator<PrimitiveEntry<V>> iter;
 
-            IteratorImpl(Iterator<Entry<V>> iter) {
+            IteratorImpl(Iterator<PrimitiveEntry<V>> iter) {
                 this.iter = iter;
             }
 
@@ -216,7 +272,7 @@ public boolean hasNext() {
             }
 
             @Override
-            public Entry<V> next() {
+            public PrimitiveEntry<V> next() {
                 if (!hasNext()) {
                     throw new NoSuchElementException();
                 }
@@ -232,10 +288,10 @@ public void remove() {
         /**
          * Unmodifiable wrapper for an entry.
          */
-        private class EntryImpl implements Entry<V> {
-            final Entry<V> entry;
+        private class EntryImpl implements PrimitiveEntry<V> {
+            final PrimitiveEntry<V> entry;
 
-            EntryImpl(Entry<V> entry) {
+            EntryImpl(PrimitiveEntry<V> entry) {
                 this.entry = entry;
             }
 
diff --git a/common/src/main/script/codegen.groovy b/common/src/main/script/codegen.groovy
index 0fd512a4f5c..4ae4636e3d9 100644
--- a/common/src/main/script/codegen.groovy
+++ b/common/src/main/script/codegen.groovy
@@ -9,17 +9,19 @@ templateDirs.eachWithIndex { templateDir, i ->
 
 void convertSources(String templateDir, String outputDir) {
     String[] keyPrimitives = ["byte", "char", "short", "int", "long"]
-    String[] keyObjects = ["Byte", "Character", "Short", "Integer", "Long"];
+    String[] keyObjects = ["Byte", "Character", "Short", "Integer", "Long"]
+    String[] keyNumberMethod = ["byteValue", "charValue", "shortValue", "intValue", "longValue"]
 
     keyPrimitives.eachWithIndex { keyPrimitive, i ->
-        convertTemplates templateDir, outputDir, keyPrimitive, keyObjects[i]
+        convertTemplates templateDir, outputDir, keyPrimitive, keyObjects[i], keyNumberMethod[i]
     }
 }
 
 void convertTemplates(String templateDir,
                       String outputDir,
                       String keyPrimitive,
-                      String keyObject) {
+                      String keyObject,
+                      String keyNumberMethod) {
     def keyName = keyPrimitive.capitalize()
     def replaceFrom = "(^.*)K([^.]+)\\.template\$"
     def replaceTo = "\\1" + keyName + "\\2.java"
@@ -32,6 +34,7 @@ void convertTemplates(String templateDir,
             filter(token: "K", value: keyName)
             filter(token: "k", value: keyPrimitive)
             filter(token: "O", value: keyObject)
+            filter(token: "KEY_NUMBER_METHOD", value: keyNumberMethod)
             filter(token: "HASH_CODE", value: hashCodeFn)
         }
         regexpmapper(from: replaceFrom, to: replaceTo)
diff --git a/common/src/main/templates/io/netty/util/collection/KObjectHashMap.template b/common/src/main/templates/io/netty/util/collection/KObjectHashMap.template
index 45cb03f8caf..9636e321405 100644
--- a/common/src/main/templates/io/netty/util/collection/KObjectHashMap.template
+++ b/common/src/main/templates/io/netty/util/collection/KObjectHashMap.template
@@ -17,12 +17,14 @@ package io.netty.util.collection;
 
 import static io.netty.util.internal.MathUtil.findNextPositivePowerOfTwo;
 
-import java.lang.reflect.Array;
 import java.util.AbstractCollection;
+import java.util.AbstractSet;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Iterator;
+import java.util.Map;
 import java.util.NoSuchElementException;
+import java.util.Set;
 
 /**
  * A hash map implementation of {@link @K@ObjectMap} that uses open addressing for keys.
@@ -32,7 +34,7 @@ import java.util.NoSuchElementException;
  *
  * @param <V> The value type stored in the map.
  */
-public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectMap.Entry<V>> {
+public class @K@ObjectHashMap<V> implements @K@ObjectMap<V> {
 
     /** Default initial capacity. Used if not specified in the constructor */
     public static final int DEFAULT_CAPACITY = 8;
@@ -57,6 +59,15 @@ public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectM
     private int size;
     private int mask;
 
+    private final Set<@O@> keySet = new KeySet();
+    private final Set<Entry<@O@, V>> entrySet = new EntrySet();
+    private final Iterable<PrimitiveEntry<V>> entries = new Iterable<PrimitiveEntry<V>>() {
+        @Override
+        public Iterator<PrimitiveEntry<V>> iterator() {
+            return new PrimitiveIterator();
+        }
+    };
+
     public @K@ObjectHashMap() {
         this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR);
     }
@@ -139,9 +150,10 @@ public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectM
     }
 
     @Override
-    public void putAll(@K@ObjectMap<V> sourceMap) {
-        if (sourceMap instanceof IntObjectHashMap) {
+    public void putAll(Map<? extends @O@, ? extends V> sourceMap) {
+        if (sourceMap instanceof @K@ObjectHashMap) {
             // Optimization - iterate through the arrays.
+            @SuppressWarnings("unchecked")
             @K@ObjectHashMap<V> source = (@K@ObjectHashMap<V>) sourceMap;
             for (int i = 0; i < source.values.length; ++i) {
                 V sourceValue = source.values[i];
@@ -153,8 +165,8 @@ public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectM
         }
 
         // Otherwise, just add each entry.
-        for (Entry<V> entry : sourceMap.entries()) {
-            put(entry.key(), entry.value());
+        for (Entry<? extends @O@, ? extends V> entry : sourceMap.entrySet()) {
+            put(entry.getKey(), entry.getValue());
         }
     }
 
@@ -193,8 +205,9 @@ public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectM
     }
 
     @Override
-    public boolean containsValue(V value) {
-        V v1 = toInternal(value);
+    public boolean containsValue(Object value) {
+        @SuppressWarnings("unchecked")
+        V v1 = toInternal((V) value);
         for (V v2 : values) {
             // The map supports null values; this will be matched as NULL_VALUE.equals(NULL_VALUE).
             if (v2 != null && v2.equals(v1)) {
@@ -205,38 +218,8 @@ public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectM
     }
 
     @Override
-    public Iterable<Entry<V>> entries() {
-        return this;
-    }
-
-    @Override
-    public Iterator<Entry<V>> iterator() {
-        return new IteratorImpl();
-    }
-
-    @Override
-    public @k@[] keys() {
-        @k@[] outKeys = new @k@[size()];
-        int targetIx = 0;
-        for (int i = 0; i < values.length; ++i) {
-            if (values[i] != null) {
-                outKeys[targetIx++] = keys[i];
-            }
-        }
-        return outKeys;
-    }
-
-    @Override
-    public V[] values(Class<V> clazz) {
-        @SuppressWarnings("unchecked")
-        V[] outValues = (V[]) Array.newInstance(clazz, size());
-        int targetIx = 0;
-        for (V value : values) {
-            if (value != null) {
-                outValues[targetIx++] = value;
-            }
-        }
-        return outValues;
+    public Iterable<PrimitiveEntry<V>> entries() {
+        return entries;
     }
 
     @Override
@@ -245,7 +228,8 @@ public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectM
             @Override
             public Iterator<V> iterator() {
                 return new Iterator<V>() {
-                    final Iterator<Entry<V>> iter = @K@ObjectHashMap.this.iterator();
+                    final PrimitiveIterator iter = new PrimitiveIterator();
+
                     @Override
                     public boolean hasNext() {
                         return iter.hasNext();
@@ -319,6 +303,40 @@ public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectM
         return true;
     }
 
+    @Override
+    public boolean containsKey(Object key) {
+        return containsKey(objectToKey(key));
+    }
+
+    @Override
+    public V get(Object key) {
+        return get(objectToKey(key));
+    }
+
+    @Override
+    public V put(@O@ key, V value) {
+        return put(objectToKey(key), value);
+    }
+
+    @Override
+    public V remove(Object key) {
+        return remove(objectToKey(key));
+    }
+
+    @Override
+    public Set<@O@> keySet() {
+        return keySet;
+    }
+
+    @Override
+    public Set<Entry<@O@, V>> entrySet() {
+        return entrySet;
+    }
+
+    private @k@ objectToKey(Object key) {
+        return (@k@) ((@O@) key).@KEY_NUMBER_METHOD@();
+    }
+
     /**
      * Locates the index for the given key. This method probes using double hashing.
      *
@@ -447,7 +465,7 @@ public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectM
                 for (;;) {
                     if (values[index] == null) {
                         keys[index] = oldKey;
-                        values[index] = toInternal(oldVal);
+                        values[index] = oldVal;
                         break;
                     }
 
@@ -458,10 +476,115 @@ public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectM
         }
     }
 
+    @Override
+    public String toString() {
+        if (isEmpty()) {
+            return "{}";
+        }
+        StringBuilder sb = new StringBuilder(4 * size);
+        sb.append('{');
+        boolean first = true;
+        for (int i = 0; i < values.length; ++i) {
+            V value = values[i];
+            if (value != null) {
+                if (!first) {
+                    sb.append(", ");
+                }
+                sb.append(keyToString(keys[i])).append('=').append(value == this ? "(this Map)" :
+                    toExternal(value));
+                first = false;
+            }
+        }
+        return sb.append('}').toString();
+    }
+
+    /**
+     * Helper method called by {@link #toString()} in order to convert a single map key into a string.
+     * This is protected to allow subclasses to override the appearance of a given key.
+     */
+    protected String keyToString(@k@ key) {
+        return @O@.toString(key);
+    }
+
+    /**
+     * Set implementation for iterating over the entries of the map.
+     */
+    private final class EntrySet extends AbstractSet<Entry<@O@, V>> {
+        @Override
+        public Iterator<Entry<@O@, V>> iterator() {
+            return new MapIterator();
+        }
+
+        @Override
+        public int size() {
+            return @K@ObjectHashMap.this.size();
+        }
+    }
+
     /**
-     * Iterator for traversing the entries in this map.
+     * Set implementation for iterating over the keys.
      */
-    private final class IteratorImpl implements Iterator<Entry<V>>, Entry<V> {
+    private final class KeySet extends AbstractSet<@O@> {
+        @Override
+        public int size() {
+            return @K@ObjectHashMap.this.size();
+        }
+
+        @Override
+        public boolean contains(Object o) {
+            return @K@ObjectHashMap.this.containsKey(o);
+        }
+
+        @Override
+        public boolean remove(Object o) {
+            return @K@ObjectHashMap.this.remove(o) != null;
+        }
+
+        @Override
+        public boolean retainAll(Collection<?> retainedKeys) {
+            boolean changed = false;
+            for(Iterator<PrimitiveEntry<V>> iter = entries().iterator(); iter.hasNext(); ) {
+                PrimitiveEntry<V> entry = iter.next();
+                if (!retainedKeys.contains(entry.key())) {
+                    changed = true;
+                    iter.remove();
+                }
+            }
+            return changed;
+        }
+
+        @Override
+        public void clear() {
+            @K@ObjectHashMap.this.clear();
+        }
+
+        @Override
+        public Iterator<@O@> iterator() {
+            return new Iterator<@O@>() {
+                private final Iterator<Entry<@O@, V>> iter = entrySet.iterator();
+
+                @Override
+                public boolean hasNext() {
+                    return iter.hasNext();
+                }
+
+                @Override
+                public @O@ next() {
+                    return iter.next().getKey();
+                }
+
+                @Override
+                public void remove() {
+                    iter.remove();
+                }
+            };
+        }
+    }
+
+    /**
+     * Iterator over primitive entries. Entry key/values are overwritten by each call to {@link #next()}.
+     */
+    private final class PrimitiveIterator implements Iterator<PrimitiveEntry<V>>, PrimitiveEntry<V> {
         private int prevIndex = -1;
         private int nextIndex = -1;
         private int entryIndex = -1;
@@ -483,7 +606,7 @@ public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectM
         }
 
         @Override
-        public Entry<V> next() {
+        public PrimitiveEntry<V> next() {
             if (!hasNext()) {
                 throw new NoSuchElementException();
             }
@@ -524,26 +647,68 @@ public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectM
         }
     }
 
-    @Override
-    public String toString() {
-        if (size == 0) {
-            return "{}";
+    /**
+     * Iterator used by the {@link Map} interface.
+     */
+    private final class MapIterator implements Iterator<Entry<@O@, V>> {
+        private final PrimitiveIterator iter = new PrimitiveIterator();
+
+        @Override
+        public boolean hasNext() {
+            return iter.hasNext();
         }
-        StringBuilder sb = new StringBuilder(4 * size);
-        for (int i = 0; i < values.length; ++i) {
-            V value = values[i];
-            if (value != null) {
-                sb.append(sb.length() == 0 ? "{" : ", ");
-                sb.append(keyToString(keys[i])).append('=').append(value == this ? "(this Map)" : value);
+
+        @Override
+        public Entry<@O@, V> next() {
+            if (!hasNext()) {
+                throw new NoSuchElementException();
             }
+
+            iter.next();
+
+            return new MapEntry(iter.entryIndex);
+        }
+
+        @Override
+        public void remove() {
+            iter.remove();
         }
-        return sb.append('}').toString();
     }
 
     /**
-     * Helper method called by {@link #toString()} in order to convert a single map key into a string.
+     * A single entry in the map.
      */
-    protected String keyToString(@k@ key) {
-        return @O@.toString(key);
+    final class MapEntry implements Entry<@O@, V> {
+        private final int entryIndex;
+
+        MapEntry(int entryIndex) {
+            this.entryIndex = entryIndex;
+        }
+
+        @Override
+        public @O@ getKey() {
+            verifyExists();
+            return keys[entryIndex];
+        }
+
+        @Override
+        public V getValue() {
+            verifyExists();
+            return toExternal(values[entryIndex]);
+        }
+
+        @Override
+        public V setValue(V value) {
+            verifyExists();
+            V prevValue = toExternal(values[entryIndex]);
+            values[entryIndex] = toInternal(value);
+            return prevValue;
+        }
+
+        private void verifyExists() {
+            if (values[entryIndex] == null) {
+                throw new IllegalStateException("The map entry has been removed");
+            }
+        }
     }
 }
diff --git a/common/src/main/templates/io/netty/util/collection/KObjectMap.template b/common/src/main/templates/io/netty/util/collection/KObjectMap.template
index 3d79c81facb..19466daf8e2 100644
--- a/common/src/main/templates/io/netty/util/collection/KObjectMap.template
+++ b/common/src/main/templates/io/netty/util/collection/KObjectMap.template
@@ -14,21 +14,21 @@
  */
 package io.netty.util.collection;
 
-import java.util.Collection;
+import java.util.Map;
 
 /**
  * Interface for a primitive map that uses {@code @k@}s as keys.
  *
  * @param <V> the value type stored in the map.
  */
-public interface @K@ObjectMap<V> {
+public interface @K@ObjectMap<V> extends Map<@O@, V> {
 
     /**
-     * An Entry in the map.
+     * A primitive entry in the map, provided by the iterator from {@link #entries()}
      *
      * @param <V> the value type stored in the map.
      */
-    interface Entry<V> {
+    interface PrimitiveEntry<V> {
         /**
          * Gets the key for this entry.
          */
@@ -62,11 +62,6 @@ public interface @K@ObjectMap<V> {
      */
     V put(@k@ key, V value);
 
-    /**
-     * Puts all of the entries from the given map into this map.
-     */
-    void putAll(@K@ObjectMap<V> sourceMap);
-
     /**
      * Removes the entry with the specified key.
      *
@@ -76,48 +71,14 @@ public interface @K@ObjectMap<V> {
     V remove(@k@ key);
 
     /**
-     * Returns the number of entries contained in this map.
-     */
-    int size();
-
-    /**
-     * Indicates whether or not this map is empty (i.e {@link #size()} == {@code 0]).
-
+     * Gets an iterable to traverse over the primitive entries contained in this map. As an optimization,
+     * the {@link PrimitiveEntry}s returned by the {@link Iterator} may change as the {@link Iterator}
+     * progresses. The caller should not rely on {@link PrimitiveEntry} key/value stability.
      */
-    boolean isEmpty();
-
-    /**
-     * Clears all entries from this map.
-     */
-    void clear();
+    Iterable<PrimitiveEntry<V>> entries();
 
     /**
      * Indicates whether or not this map contains a value for the specified key.
      */
     boolean containsKey(@k@ key);
-
-    /**
-     * Indicates whether or not the map contains the specified value.
-     */
-    boolean containsValue(V value);
-
-    /**
-     * Gets an iterable collection of the entries contained in this map.
-     */
-    Iterable<Entry<V>> entries();
-
-    /**
-     * Gets the keys contained in this map.
-     */
-    @k@[] keys();
-
-    /**
-     * Gets the values contained in this map.
-     */
-    V[] values(Class<V> clazz);
-
-    /**
-     * Gets the values contatins in this map as a {@link Collection}.
-     */
-    Collection<V> values();
 }
diff --git a/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollEventLoop.java b/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollEventLoop.java
index e01709e545c..0c0fa0f3e0c 100644
--- a/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollEventLoop.java
+++ b/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollEventLoop.java
@@ -28,6 +28,7 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Map;
 import java.util.Queue;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
@@ -291,8 +292,8 @@ private void closeAll() {
         }
         Collection<AbstractEpollChannel> array = new ArrayList<AbstractEpollChannel>(channels.size());
 
-        for (IntObjectMap.Entry<AbstractEpollChannel> entry: channels.entries()) {
-            array.add(entry.value());
+        for (AbstractEpollChannel channel: channels.values()) {
+            array.add(channel);
         }
 
         for (AbstractEpollChannel ch: array) {


Test patch:
diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2RemoteFlowControllerTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2RemoteFlowControllerTest.java
index 327c7c820c2..963c43f9e8f 100644
--- a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2RemoteFlowControllerTest.java
+++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2RemoteFlowControllerTest.java
@@ -46,6 +46,7 @@
 import io.netty.util.concurrent.EventExecutor;
 
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -896,10 +897,10 @@ public void subTreeBytesShouldBeCorrect() throws Http2Exception {
 
         // Send a bunch of data on each stream.
         final IntObjectMap<Integer> streamSizes = new IntObjectHashMap<Integer>(4);
-        streamSizes.put(STREAM_A, 400);
-        streamSizes.put(STREAM_B, 500);
-        streamSizes.put(STREAM_C, 600);
-        streamSizes.put(STREAM_D, 700);
+        streamSizes.put(STREAM_A, (Integer) 400);
+        streamSizes.put(STREAM_B, (Integer) 500);
+        streamSizes.put(STREAM_C, (Integer) 600);
+        streamSizes.put(STREAM_D, (Integer) 700);
 
         FakeFlowControlled dataA = new FakeFlowControlled(streamSizes.get(STREAM_A));
         FakeFlowControlled dataB = new FakeFlowControlled(streamSizes.get(STREAM_B));
@@ -922,11 +923,11 @@ public void subTreeBytesShouldBeCorrect() throws Http2Exception {
                         streamableBytesForTree(stream0));
         assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_A, STREAM_C, STREAM_D)),
                 streamableBytesForTree(streamA));
-        assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_B)),
+        assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_B)),
                 streamableBytesForTree(streamB));
-        assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_C)),
+        assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_C)),
                 streamableBytesForTree(streamC));
-        assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_D)),
+        assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_D)),
                 streamableBytesForTree(streamD));
     }
 
@@ -966,10 +967,10 @@ public void subTreeBytesShouldBeCorrectWithRestructure() throws Http2Exception {
 
         // Send a bunch of data on each stream.
         final IntObjectMap<Integer> streamSizes = new IntObjectHashMap<Integer>(4);
-        streamSizes.put(STREAM_A, 400);
-        streamSizes.put(STREAM_B, 500);
-        streamSizes.put(STREAM_C, 600);
-        streamSizes.put(STREAM_D, 700);
+        streamSizes.put(STREAM_A, (Integer) 400);
+        streamSizes.put(STREAM_B, (Integer) 500);
+        streamSizes.put(STREAM_C, (Integer) 600);
+        streamSizes.put(STREAM_D, (Integer) 700);
 
         FakeFlowControlled dataA = new FakeFlowControlled(streamSizes.get(STREAM_A));
         FakeFlowControlled dataB = new FakeFlowControlled(streamSizes.get(STREAM_B));
@@ -996,9 +997,9 @@ public void subTreeBytesShouldBeCorrectWithRestructure() throws Http2Exception {
                         streamableBytesForTree(streamA));
         assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_B, STREAM_C, STREAM_D)),
                      streamableBytesForTree(streamB));
-        assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_C)),
+        assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_C)),
                 streamableBytesForTree(streamC));
-        assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_D)),
+        assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_D)),
                 streamableBytesForTree(streamD));
     }
 
@@ -1041,11 +1042,11 @@ public void subTreeBytesShouldBeCorrectWithAddition() throws Http2Exception {
 
         // Send a bunch of data on each stream.
         final IntObjectMap<Integer> streamSizes = new IntObjectHashMap<Integer>(4);
-        streamSizes.put(STREAM_A, 400);
-        streamSizes.put(STREAM_B, 500);
-        streamSizes.put(STREAM_C, 600);
-        streamSizes.put(STREAM_D, 700);
-        streamSizes.put(STREAM_E, 900);
+        streamSizes.put(STREAM_A, (Integer) 400);
+        streamSizes.put(STREAM_B, (Integer) 500);
+        streamSizes.put(STREAM_C, (Integer) 600);
+        streamSizes.put(STREAM_D, (Integer) 700);
+        streamSizes.put(STREAM_E, (Integer) 900);
 
         FakeFlowControlled dataA = new FakeFlowControlled(streamSizes.get(STREAM_A));
         FakeFlowControlled dataB = new FakeFlowControlled(streamSizes.get(STREAM_B));
@@ -1072,11 +1073,11 @@ public void subTreeBytesShouldBeCorrectWithAddition() throws Http2Exception {
         assertEquals(calculateStreamSizeSum(streamSizes,
                         Arrays.asList(STREAM_A, STREAM_E, STREAM_C, STREAM_D)),
                 streamableBytesForTree(streamA));
-        assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_B)),
+        assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_B)),
                 streamableBytesForTree(streamB));
-        assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_C)),
+        assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_C)),
                 streamableBytesForTree(streamC));
-        assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_D)),
+        assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_D)),
                 streamableBytesForTree(streamD));
         assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_E, STREAM_C, STREAM_D)),
                 streamableBytesForTree(streamE));
@@ -1106,10 +1107,10 @@ public void subTreeBytesShouldBeCorrectWithInternalStreamClose() throws Http2Exc
 
         // Send a bunch of data on each stream.
         final IntObjectMap<Integer> streamSizes = new IntObjectHashMap<Integer>(4);
-        streamSizes.put(STREAM_A, 400);
-        streamSizes.put(STREAM_B, 500);
-        streamSizes.put(STREAM_C, 600);
-        streamSizes.put(STREAM_D, 700);
+        streamSizes.put(STREAM_A, (Integer) 400);
+        streamSizes.put(STREAM_B, (Integer) 500);
+        streamSizes.put(STREAM_C, (Integer) 600);
+        streamSizes.put(STREAM_D, (Integer) 700);
 
         FakeFlowControlled dataA = new FakeFlowControlled(streamSizes.get(STREAM_A));
         FakeFlowControlled dataB = new FakeFlowControlled(streamSizes.get(STREAM_B));
@@ -1133,11 +1134,11 @@ public void subTreeBytesShouldBeCorrectWithInternalStreamClose() throws Http2Exc
                 streamableBytesForTree(stream0));
         assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_C, STREAM_D)),
                 streamableBytesForTree(streamA));
-        assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_B)),
+        assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_B)),
                 streamableBytesForTree(streamB));
-        assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_C)),
+        assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_C)),
                 streamableBytesForTree(streamC));
-        assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_D)),
+        assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_D)),
                 streamableBytesForTree(streamD));
     }
 
@@ -1174,10 +1175,10 @@ public void subTreeBytesShouldBeCorrectWithLeafStreamClose() throws Http2Excepti
 
         // Send a bunch of data on each stream.
         final IntObjectMap<Integer> streamSizes = new IntObjectHashMap<Integer>(4);
-        streamSizes.put(STREAM_A, 400);
-        streamSizes.put(STREAM_B, 500);
-        streamSizes.put(STREAM_C, 600);
-        streamSizes.put(STREAM_D, 700);
+        streamSizes.put(STREAM_A, (Integer) 400);
+        streamSizes.put(STREAM_B, (Integer) 500);
+        streamSizes.put(STREAM_C, (Integer) 600);
+        streamSizes.put(STREAM_D, (Integer) 700);
 
         FakeFlowControlled dataA = new FakeFlowControlled(streamSizes.get(STREAM_A));
         FakeFlowControlled dataB = new FakeFlowControlled(streamSizes.get(STREAM_B));
@@ -1201,10 +1202,10 @@ public void subTreeBytesShouldBeCorrectWithLeafStreamClose() throws Http2Excepti
                 streamableBytesForTree(stream0));
         assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_A, STREAM_D)),
                 streamableBytesForTree(streamA));
-        assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_B)),
+        assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_B)),
                 streamableBytesForTree(streamB));
         assertEquals(0, streamableBytesForTree(streamC));
-        assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_D)),
+        assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_D)),
                 streamableBytesForTree(streamD));
     }
 
diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2SettingsTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2SettingsTest.java
index aa9f1aba9b4..cb33ca136e0 100644
--- a/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2SettingsTest.java
+++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2SettingsTest.java
@@ -65,14 +65,14 @@ public void standardSettingsShouldBeSet() {
     @Test
     public void nonStandardSettingsShouldBeSet() {
         char key = 0;
-        settings.put(key, 123L);
+        settings.put(key, (Long) 123L);
         assertEquals(123L, (long) settings.get(key));
     }
 
     @Test
     public void settingsShouldSupportUnsignedShort() {
         char key = (char) (Short.MAX_VALUE + 1);
-        settings.put(key, 123L);
+        settings.put(key, (Long) 123L);
         assertEquals(123L, (long) settings.get(key));
     }
 
diff --git a/common/src/test/templates/io.netty.util.collection/KObjectHashMapTest.template b/common/src/test/templates/io.netty.util.collection/KObjectHashMapTest.template
index 5969e0a1b71..ed87c489f50 100644
--- a/common/src/test/templates/io.netty.util.collection/KObjectHashMapTest.template
+++ b/common/src/test/templates/io.netty.util.collection/KObjectHashMapTest.template
@@ -18,9 +18,10 @@ import org.junit.Before;
 import org.junit.Test;
 
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
 import java.util.Random;
 import java.util.Set;
 
@@ -87,6 +88,17 @@ public class @K@ObjectHashMapTest {
         assertEquals(v, map.get(key));
     }
 
+    @Test
+    public void putNewMappingShouldSucceed_mapApi() {
+        Value v = new Value("v");
+        @O@ key = (@O@)(@k@) 1;
+        assertNull(map.put(key, v));
+        assertEquals(1, map.size());
+        assertTrue(map.containsKey(key));
+        assertTrue(map.containsValue(v));
+        assertEquals(v, map.get(key));
+    }
+
     @Test
     public void putShouldReplaceValue() {
         Value v1 = new Value("v1");
@@ -103,6 +115,22 @@ public class @K@ObjectHashMapTest {
         assertEquals(v2, map.get(key));
     }
 
+    @Test
+    public void putShouldReplaceValue_mapApi() {
+        Value v1 = new Value("v1");
+        @O@ key = (@O@)(@k@) 1;
+        assertNull(map.put(key, v1));
+
+        // Replace the value.
+        Value v2 = new Value("v2");
+        assertSame(v1, map.put(key, v2));
+
+        assertEquals(1, map.size());
+        assertTrue(map.containsKey(key));
+        assertTrue(map.containsValue(v2));
+        assertEquals(v2, map.get(key));
+    }
+
     @Test
     public void putShouldGrowMap() {
         for (@k@ key = 0; key < (@k@) 255; ++key) {
@@ -115,6 +143,19 @@ public class @K@ObjectHashMapTest {
         }
     }
 
+    @Test
+    public void putShouldGrowMap_mapApi() {
+        for (@k@ key = 0; key < (@k@) 255; ++key) {
+            @O@ okey = (@O@) key;
+            Value v = new Value(@O@.toString(key));
+            assertNull(map.put(okey, v));
+            assertEquals(key + 1, map.size());
+            assertTrue(map.containsKey(okey));
+            assertTrue(map.containsValue(v));
+            assertEquals(v, map.get(okey));
+        }
+    }
+
     @Test
     public void negativeKeyShouldSucceed() {
         Value v = new Value("v");
@@ -123,12 +164,26 @@ public class @K@ObjectHashMapTest {
         assertEquals(v, map.get((@k@) -3));
     }
 
+    @Test
+    public void negativeKeyShouldSucceed_mapApi() {
+        Value v = new Value("v");
+        map.put((@O@)(@k@) -3, v);
+        assertEquals(1, map.size());
+        assertEquals(v, map.get((@O@)(@k@) -3));
+    }
+
     @Test
     public void removeMissingValueShouldReturnNull() {
         assertNull(map.remove((@k@) 1));
         assertEquals(0, map.size());
     }
 
+    @Test
+    public void removeMissingValueShouldReturnNull_mapApi() {
+        assertNull(map.remove((@O@)(@k@) 1));
+        assertEquals(0, map.size());
+    }
+
     @Test
     public void removeShouldReturnPreviousValue() {
         Value v = new Value("v");
@@ -137,6 +192,14 @@ public class @K@ObjectHashMapTest {
         assertSame(v, map.remove(key));
     }
 
+    @Test
+    public void removeShouldReturnPreviousValue_mapApi() {
+        Value v = new Value("v");
+        @O@ key = (@O@)(@k@) 1;
+        map.put(key, v);
+        assertSame(v, map.remove(key));
+    }
+
     /**
      * This test is a bit internal-centric. We're just forcing a rehash to occur based on no longer
      * having any FREE slots available. We do this by adding and then removing several keys up to
@@ -160,24 +223,84 @@ public class @K@ObjectHashMapTest {
         assertSame(v, map.get(key));
     }
 
+    @Test
+    public void noFreeSlotsShouldRehash_mapApi() {
+        for (@k@ i = 0; i < 10; ++i) {
+            map.put(i, new Value(@O@.toString(i)));
+            // Now mark it as REMOVED so that size won't cause the rehash.
+            map.remove((@O@) i);
+            assertEquals(0, map.size());
+        }
+
+        // Now add an entry to force the rehash since no FREE slots are available in the map.
+        Value v = new Value("v");
+        @O@ key = (@O@)(@k@) 1;
+        map.put(key, v);
+        assertEquals(1, map.size());
+        assertSame(v, map.get(key));
+    }
+
     @Test
     public void putAllShouldSucceed() {
+        @K@ObjectHashMap<Value> other = new @K@ObjectHashMap<Value>();
+
         @k@ k1 = 1;
         @k@ k2 = 2;
         @k@ k3 = 3;
         Value v1 = new Value("v1");
         Value v2 = new Value("v2");
         Value v3 = new Value("v3");
-        map.put(k1, v1);
-        map.put(k2, v2);
-        map.put(k3, v3);
+        other.put(k1, v1);
+        other.put(k2, v2);
+        other.put(k3, v3);
+
+        map.putAll(other);
+        assertEquals(3, map.size());
+        assertSame(v1, map.get(k1));
+        assertSame(v2, map.get(k2));
+        assertSame(v3, map.get(k3));
+    }
 
-        @K@ObjectHashMap<Value> map2 = new @K@ObjectHashMap<Value>();
-        map2.putAll(map);
-        assertEquals(3, map2.size());
-        assertSame(v1, map2.get(k1));
-        assertSame(v2, map2.get(k2));
-        assertSame(v3, map2.get(k3));
+    @Test
+    public void putAllShouldSucceed_mapApi() {
+        @K@ObjectHashMap<Value> other = new @K@ObjectHashMap<Value>();
+
+        @O@ k1 = (@O@)(@k@) 1;
+        @O@ k2 = (@O@)(@k@) 2;
+        @O@ k3 = (@O@)(@k@) 3;
+        Value v1 = new Value("v1");
+        Value v2 = new Value("v2");
+        Value v3 = new Value("v3");
+        other.put(k1, v1);
+        other.put(k2, v2);
+        other.put(k3, v3);
+
+        map.putAll(other);
+        assertEquals(3, map.size());
+        assertSame(v1, map.get(k1));
+        assertSame(v2, map.get(k2));
+        assertSame(v3, map.get(k3));
+    }
+
+    @Test
+    public void putAllWithJavaMapShouldSucceed_mapApi() {
+        Map<@O@, Value> other = new HashMap<@O@, Value>();
+
+        @O@ k1 = (@O@)(@k@) 1;
+        @O@ k2 = (@O@)(@k@) 2;
+        @O@ k3 = (@O@)(@k@) 3;
+        Value v1 = new Value("v1");
+        Value v2 = new Value("v2");
+        Value v3 = new Value("v3");
+        other.put(k1, v1);
+        other.put(k2, v2);
+        other.put(k3, v3);
+
+        map.putAll(other);
+        assertEquals(3, map.size());
+        assertSame(v1, map.get(k1));
+        assertSame(v2, map.get(k2));
+        assertSame(v3, map.get(k3));
     }
 
     @Test
@@ -201,6 +324,14 @@ public class @K@ObjectHashMapTest {
         assertTrue(map.containsValue(null));
     }
 
+    @Test
+    public void containsValueShouldFindNull_mapApi() {
+        map.put((@O@)(@k@) 1, new Value("v1"));
+        map.put((@O@)(@k@) 2, null);
+        map.put((@O@)(@k@) 3, new Value("v2"));
+        assertTrue(map.containsValue(null));
+    }
+
     @Test
     public void containsValueShouldFindInstance() {
         Value v = new Value("v1");
@@ -210,6 +341,15 @@ public class @K@ObjectHashMapTest {
         assertTrue(map.containsValue(v));
     }
 
+    @Test
+    public void containsValueShouldFindInstance_mapApi() {
+        Value v = new Value("v1");
+        map.put((@O@)(@k@) 1, new Value("v2"));
+        map.put((@O@)(@k@) 2, new Value("v3"));
+        map.put((@O@)(@k@) 3, v);
+        assertTrue(map.containsValue(v));
+    }
+
     @Test
     public void containsValueShouldFindEquivalentValue() {
         map.put((@k@) 1, new Value("v1"));
@@ -218,6 +358,14 @@ public class @K@ObjectHashMapTest {
         assertTrue(map.containsValue(new Value("v2")));
     }
 
+    @Test
+    public void containsValueShouldFindEquivalentValue_mapApi() {
+        map.put((@O@)(@k@) 1, new Value("v1"));
+        map.put((@O@)(@k@) 2, new Value("v2"));
+        map.put((@O@)(@k@) 3, new Value("v3"));
+        assertTrue(map.containsValue(new Value("v2")));
+    }
+
     @Test
     public void containsValueNotFindMissingValue() {
         map.put((@k@) 1, new Value("v1"));
@@ -226,6 +374,14 @@ public class @K@ObjectHashMapTest {
         assertFalse(map.containsValue(new Value("v4")));
     }
 
+    @Test
+    public void containsValueNotFindMissingValue_mapApi() {
+        map.put((@O@)(@k@) 1, new Value("v1"));
+        map.put((@O@)(@k@) 2, new Value("v2"));
+        map.put((@O@)(@k@) 3, new Value("v3"));
+        assertFalse(map.containsValue(new Value("v4")));
+    }
+
     @Test
     public void iteratorShouldTraverseEntries() {
         @k@ k1 = 1;
@@ -241,8 +397,8 @@ public class @K@ObjectHashMapTest {
         map.remove(k4);
 
         Set<@O@> found = new HashSet<@O@>();
-        for (@K@ObjectMap.Entry<Value> entry : map.entries()) {
-            assertTrue(found.add(entry.key()));
+        for (@K@ObjectMap.Entry<@O@, Value> entry : map.entrySet()) {
+            assertTrue(found.add(entry.getKey()));
         }
         assertEquals(3, found.size());
         assertTrue(found.contains(k1));
@@ -264,8 +420,8 @@ public class @K@ObjectHashMapTest {
         map.put(k4, new Value("v4"));
         map.remove(k4);
 
-        @k@[] keys = map.keys();
-        assertEquals(3, keys.length);
+        Set<@O@> keys = map.keySet();
+        assertEquals(3, keys.size());
 
         Set<@O@> expected = new HashSet<@O@>();
         expected.add(k1);
@@ -298,25 +454,11 @@ public class @K@ObjectHashMapTest {
 
         // Ensure values() return all values.
         Set<Value> expected = new HashSet<Value>();
-        Set<Value> actual = new HashSet<Value>();
-
         expected.add(v1);
         expected.add(v2);
         expected.add(v3);
 
-        Value[] valueArray = map.values(Value.class);
-        assertEquals(3, valueArray.length);
-        for (Value value : valueArray) {
-            assertTrue(actual.add(value));
-        }
-        assertEquals(expected, actual);
-        actual.clear();
-
-        Collection<Value> valueCollection = map.values();
-        assertEquals(3, valueCollection.size());
-        for (Value value : valueCollection) {
-            assertTrue(actual.add(value));
-        }
+        Set<Value> actual = new HashSet<Value>(map.values());
         assertEquals(expected, actual);
     }
 
@@ -332,6 +474,18 @@ public class @K@ObjectHashMapTest {
         }
     }
 
+    @Test
+    public void mapShouldSupportHashingConflicts_mapApi() {
+        for (int mod = 0; mod < 10; ++mod) {
+            for (int sz = 1; sz <= 101; sz += 2) {
+                @K@ObjectHashMap<String> map = new @K@ObjectHashMap<String>(sz);
+                for (int i = 0; i < 100; ++i) {
+                    map.put((@O@)(@k@)(i * mod), "");
+                }
+            }
+        }
+    }
+
     @Test
     public void hashcodeEqualsTest() {
         @K@ObjectHashMap<@O@> map1 = new @K@ObjectHashMap<@O@>();
@@ -345,23 +499,27 @@ public class @K@ObjectHashMapTest {
         assertEquals(map1.hashCode(), map2.hashCode());
         assertEquals(map1, map2);
         // Remove one "middle" element, maps should now be non-equals.
-        @k@[] keys = map1.keys();
-        map2.remove(keys[50]);
+        Set<@O@> keys = map1.keySet();
+        @O@ removed = null;
+        Iterator<@O@> iter = keys.iterator();
+        for (int ix = 0; iter.hasNext() && ix < 50; ++ix) {
+            removed = iter.next();
+        }
+        map2.remove(removed);
         assertFalse(map1.equals(map2));
         // Put it back; will likely be in a different position, but maps will be equal again.
-        map2.put(keys[50], @O@.valueOf(map1.keys()[50]));
+        map2.put(removed, removed);
         assertEquals(map1, map2);
         assertEquals(map1.hashCode(), map2.hashCode());
         // Make map2 have one extra element, will be non-equal.
-        map2.put((@k@) 100, (@k@) 100);
+        map2.put((@k@) 100, (@O@)(@k@) 100);
         assertFalse(map1.equals(map2));
         // Rebuild map2 with elements in a different order, again the maps should be equal.
         // (These tests with same elements in different order also show that the hashCode
         // function does not depend on the internal ordering of entries.)
         map2.clear();
-        Arrays.sort(keys);
-        for (@k@ key : keys) {
-            map2.put(key, @O@.valueOf(key));
+        for (@O@ key : map1.keySet()) {
+            map2.put(key, key);
         }
         assertEquals(map1.hashCode(), map2.hashCode());
         assertEquals(map1, map2);
@@ -424,14 +582,14 @@ public class @K@ObjectHashMapTest {
         assertEquals(goodMap.size(), map.size());
         @O@[] goodKeys = goodMap.keySet().toArray(new @O@[goodMap.size()]);
         Arrays.sort(goodKeys);
-        @k@ [] keys = map.keys();
+        @O@[] keys = map.keySet().toArray(new @O@[map.size()]);
         Arrays.sort(keys);
         for (int i = 0; i < goodKeys.length; ++i) {
-            assertEquals((@k@) goodKeys[i], keys[i]);
+            assertEquals(goodKeys[i], keys[i]);
         }
 
         // Finally drain the map.
-        for (@k@ key : map.keys()) {
+        for (@k@ key : keys) {
             assertEquals(goodMap.remove(key), map.remove(key));
         }
         assertTrue(map.isEmpty());