Index: trunk/src/org/openstreetmap/josm/actions/upload/DiscardTagsHook.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/upload/DiscardTagsHook.java	(revision 17583)
+++ trunk/src/org/openstreetmap/josm/actions/upload/DiscardTagsHook.java	(revision 17584)
@@ -27,5 +27,5 @@
         Collection<String> discardableKeys = new HashSet<>(AbstractPrimitive.getDiscardableKeys());
 
-        boolean needsChange = objectsToUpload.stream().flatMap(osm -> osm.keySet().stream())
+        boolean needsChange = objectsToUpload.stream().flatMap(AbstractPrimitive::keys)
                 .anyMatch(discardableKeys::contains);
 
Index: trunk/src/org/openstreetmap/josm/data/osm/AbstractPrimitive.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/AbstractPrimitive.java	(revision 17583)
+++ trunk/src/org/openstreetmap/josm/data/osm/AbstractPrimitive.java	(revision 17584)
@@ -19,4 +19,6 @@
 import java.util.concurrent.TimeUnit;
 import java.util.function.BiPredicate;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
 
 import org.openstreetmap.josm.data.gpx.GpxConstants;
@@ -657,4 +659,16 @@
         }
         return result;
+    }
+
+    @Override
+    public Stream<String> keys() {
+        final String[] k = this.keys;
+        if (k == null) {
+            return Stream.empty();
+        } else if (k.length == 1) {
+            return Stream.of(k[0]);
+        } else {
+            return IntStream.range(0, k.length / 2).mapToObj(i -> k[i * 2]);
+        }
     }
 
Index: trunk/src/org/openstreetmap/josm/data/osm/DefaultNameFormatter.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/DefaultNameFormatter.java	(revision 17583)
+++ trunk/src/org/openstreetmap/josm/data/osm/DefaultNameFormatter.java	(revision 17584)
@@ -454,5 +454,5 @@
                 return relation.getName();
         } else if (":LocationCode".equals(nameTag)) {
-            return relation.keySet().stream()
+            return relation.keys()
                     .filter(m -> m.endsWith(nameTag))
                     .findFirst()
Index: trunk/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java	(revision 17583)
+++ trunk/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java	(revision 17584)
@@ -5,5 +5,4 @@
 
 import java.text.MessageFormat;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -570,11 +569,10 @@
         // 'area' is not really uninteresting (putting it in that list may have unpredictable side effects)
         // but it's clearly not enough to consider an object as tagged (see #9261)
-        updateFlagsNoLock(FLAG_TAGGED, keySet().stream()
+        updateFlagsNoLock(FLAG_TAGGED, keys()
                 .anyMatch(key -> !isUninterestingKey(key) && !"area".equals(key)));
     }
 
     private void updateAnnotated() {
-        updateFlagsNoLock(FLAG_ANNOTATED, keySet().stream()
-                .anyMatch(getWorkInProgressKeys()::contains));
+        updateFlagsNoLock(FLAG_ANNOTATED, hasKeys() && getWorkInProgressKeys().stream().anyMatch(this::hasKey));
     }
 
@@ -1072,10 +1070,6 @@
     @Override
     public Collection<String> getTemplateKeys() {
-        Collection<String> keySet = keySet();
-        List<String> result = new ArrayList<>(keySet.size() + 2);
-        result.add(SPECIAL_VALUE_ID);
-        result.add(SPECIAL_VALUE_LOCAL_NAME);
-        result.addAll(keySet);
-        return result;
+        return Stream.concat(Stream.of(SPECIAL_VALUE_ID, SPECIAL_VALUE_LOCAL_NAME), keys())
+                .collect(Collectors.toList());
     }
 
Index: trunk/src/org/openstreetmap/josm/data/osm/Tagged.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/Tagged.java	(revision 17583)
+++ trunk/src/org/openstreetmap/josm/data/osm/Tagged.java	(revision 17584)
@@ -6,4 +6,5 @@
 import java.util.Map;
 import java.util.Objects;
+import java.util.stream.Stream;
 
 /**
@@ -186,6 +187,18 @@
      *
      * @return the set of keys
+     * @see #keys()
      */
     Collection<String> keySet();
+
+    /**
+     * Replies the keys as stream
+     *
+     * @return the keys as stream
+     * @see #keySet()
+     * @since 17584
+     */
+    default Stream<String> keys() {
+        return keySet().stream();
+    }
 
     /**
Index: trunk/src/org/openstreetmap/josm/data/osm/search/SearchCompiler.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/search/SearchCompiler.java	(revision 17583)
+++ trunk/src/org/openstreetmap/josm/data/osm/search/SearchCompiler.java	(revision 17584)
@@ -739,5 +739,5 @@
                     // But since we're doing a regex match we'll have to loop over all the keys to see if they match our regex,
                     // and only then try to match against the value
-                    return osm.keySet().stream()
+                    return osm.keys()
                             .anyMatch(k -> keyPattern.matcher(k).find() && valuePattern.matcher(osm.get(k)).find());
                 }
@@ -760,5 +760,5 @@
                 mv = osm.get(key);
                 if (!caseSensitive && mv == null) {
-                    mv = osm.keySet().stream().filter(key::equalsIgnoreCase).findFirst().map(osm::get).orElse(null);
+                    mv = osm.keys().filter(key::equalsIgnoreCase).findFirst().map(osm::get).orElse(null);
                 }
             }
@@ -972,8 +972,8 @@
             case ANY_VALUE_REGEXP:
             case EXACT_REGEXP:
-                return osm.keySet().stream().anyMatch(k -> keyPattern.matcher(k).matches()
+                return osm.keys().anyMatch(k -> keyPattern.matcher(k).matches()
                         && (mode == Mode.ANY_VALUE_REGEXP || valuePattern.matcher(osm.get(k)).matches()));
             case MISSING_KEY_REGEXP:
-                return osm.keySet().stream().noneMatch(k -> keyPattern.matcher(k).matches());
+                return osm.keys().noneMatch(k -> keyPattern.matcher(k).matches());
             }
             throw new AssertionError("Missed state");
Index: trunk/src/org/openstreetmap/josm/data/validation/tests/Lanes.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/validation/tests/Lanes.java	(revision 17583)
+++ trunk/src/org/openstreetmap/josm/data/validation/tests/Lanes.java	(revision 17584)
@@ -42,5 +42,5 @@
     protected void checkNumberOfLanesByKey(final OsmPrimitive p, String lanesKey, String message) {
         final Set<Integer> lanesCount =
-                p.keySet().stream()
+                p.keys()
                 .filter(x -> x.endsWith(":" + lanesKey))
                 .filter(x -> !Arrays.asList(BLACKLIST).contains(x))
Index: trunk/src/org/openstreetmap/josm/data/validation/tests/TagChecker.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/validation/tests/TagChecker.java	(revision 17583)
+++ trunk/src/org/openstreetmap/josm/data/validation/tests/TagChecker.java	(revision 17584)
@@ -690,5 +690,5 @@
 
     private void checkMultipolygonTags(OsmPrimitive p) {
-        if (p.isAnnotated() || p.keySet().stream()
+        if (p.isAnnotated() || p.keys()
                 .anyMatch(k -> k.matches("^(abandoned|construction|demolished|disused|planned|razed|removed|was).*")))
             return;
Index: trunk/src/org/openstreetmap/josm/gui/conflict/pair/tags/TagMergeModel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/conflict/pair/tags/TagMergeModel.java	(revision 17583)
+++ trunk/src/org/openstreetmap/josm/gui/conflict/pair/tags/TagMergeModel.java	(revision 17584)
@@ -8,5 +8,7 @@
 import java.util.List;
 import java.util.Set;
+import java.util.stream.Collectors;
 import java.util.stream.IntStream;
+import java.util.stream.Stream;
 
 import javax.swing.table.DefaultTableModel;
@@ -101,7 +103,5 @@
     public void populate(OsmPrimitive my, OsmPrimitive their) {
         tagMergeItems.clear();
-        Set<String> keys = new HashSet<>();
-        keys.addAll(my.keySet());
-        keys.addAll(their.keySet());
+        Set<String> keys = Stream.concat(my.keys(), their.keys()).collect(Collectors.toSet());
         for (String key : keys) {
             String myValue = my.get(key);
Index: trunk/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java	(revision 17583)
+++ trunk/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java	(revision 17584)
@@ -832,5 +832,5 @@
 
     private static boolean containsOnlyGpxTags(Tagged t) {
-        return t.getKeys().keySet().stream()
+        return t.keys()
                 .allMatch(key -> GpxConstants.WPT_KEYS.contains(key) || key.startsWith(GpxConstants.GPX_PREFIX));
     }
Index: trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/ConditionFactory.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/ConditionFactory.java	(revision 17583)
+++ trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/ConditionFactory.java	(revision 17584)
@@ -556,5 +556,5 @@
                     return e.osm.isKeyFalse(label) ^ negateResult;
                 case REGEX:
-                    return e.osm.keySet().stream().anyMatch(containsPattern) ^ negateResult;
+                    return e.osm.keys().anyMatch(containsPattern) ^ negateResult;
                 default:
                     return e.osm.hasKey(label) ^ negateResult;
@@ -580,5 +580,5 @@
             String key = label;
             if (KeyMatchType.REGEX == matchType) {
-                key = p.keySet().stream().filter(containsPattern).findAny().orElse(key);
+                key = p.keys().filter(containsPattern).findAny().orElse(key);
             }
             return new Tag(key, p.get(key));
