Index: trunk/src/org/openstreetmap/josm/data/validation/tests/TagChecker.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/validation/tests/TagChecker.java	(revision 19284)
+++ trunk/src/org/openstreetmap/josm/data/validation/tests/TagChecker.java	(revision 19285)
@@ -421,10 +421,10 @@
                 for (TaggingPresetItem i : p.data) {
                     if (i instanceof KeyedItem) {
-                        if (!"none".equals(((KeyedItem) i).match))
+                        if (!"none".equals(((KeyedItem) i).match()))
                             minData.add(i);
                         addPresetValue((KeyedItem) i);
                     } else if (i instanceof CheckGroup) {
                         for (Check c : ((CheckGroup) i).checks) {
-                            if (!"none".equals(c.match))
+                            if (!"none".equals(c.match()))
                                 minData.add(c);
                             addPresetValue(c);
Index: trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItem.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItem.java	(revision 19284)
+++ trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItem.java	(revision 19285)
@@ -12,4 +12,5 @@
 import java.util.List;
 import java.util.Map;
+import java.util.RandomAccess;
 import java.util.Set;
 
@@ -171,11 +172,30 @@
     public static boolean matches(Iterable<? extends TaggingPresetItem> data, Map<String, String> tags) {
         boolean atLeastOnePositiveMatch = false;
-        for (TaggingPresetItem item : data) {
-            Boolean m = item.matches(tags);
-            if (m != null && !m)
-                return false;
-            else if (m != null) {
-                atLeastOnePositiveMatch = true;
+        if (data instanceof List && data instanceof RandomAccess) {
+            List<? extends TaggingPresetItem> items = (List<? extends TaggingPresetItem>) data;
+            /* This is a memory allocation optimization, mostly for ArrayList.
+             * In test runs, this reduced the memory cost for this method by 99%.
+             * This appears to have also improved CPU cost for this method by ~10% as well.
+             * The big win for CPU cost is in GC improvements, which was around 80%.
+             * Overall improvement: 7.6 hours to 4.5 hours for validating a Colorado pbf extract (40% improvement).
+             */
+            for (int i = 0; i < items.size(); i++) { // READ ABOVE: DO NOT REPLACE WITH ENHANCED FOR LOOP WITHOUT PROFILING!
+                TaggingPresetItem item = items.get(i);
+                Boolean m = item.matches(tags);
+                if (m != null && !m) {
+                    return false;
+                } else if (m != null) {
+                    atLeastOnePositiveMatch = true;
+                }
             }
+        } else {
+            for (TaggingPresetItem item : data) {
+                Boolean m = item.matches(tags);
+                if (m != null && !m) {
+                    return false;
+                } else if (m != null) {
+                    atLeastOnePositiveMatch = true;
+                }
+            }
         }
         return atLeastOnePositiveMatch;
Index: trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/KeyedItem.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/KeyedItem.java	(revision 19284)
+++ trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/KeyedItem.java	(revision 19285)
@@ -52,5 +52,5 @@
      * Default is "keyvalue!" for {@link Key} and "none" for {@link Text}, {@link Combo}, {@link MultiSelect} and {@link Check}.
      */
-    public String match = getDefaultMatch().getValue(); // NOSONAR
+    protected MatchType match = getDefaultMatch(); // NOSONAR
 
     /**
@@ -180,4 +180,31 @@
             return usage;
         }
+    }
+
+    /**
+     * Allows to change the matching process, i.e., determining whether the tags of an OSM object fit into this preset.
+     * If a preset fits then it is linked in the Tags/Membership dialog.<ul>
+     * <li>none: neutral, i.e., do not consider this item for matching</li>
+     * <li>key: positive if key matches, neutral otherwise</li>
+     * <li>key!: positive if key matches, negative otherwise</li>
+     * <li>keyvalue: positive if key and value matches, neutral otherwise</li>
+     * <li>keyvalue!: positive if key and value matches, negative otherwise</li></ul>
+     * Note that for a match, at least one positive and no negative is required.
+     * Default is "keyvalue!" for {@link Key} and "none" for {@link Text}, {@link Combo}, {@link MultiSelect} and {@link Check}.
+     * @param match The match type. One of <code>none</code>, <code>key</code>, <code>key!</code>, <code>keyvalue</code>,
+     *              or <code>keyvalue!</code>.
+     * @since 19285
+     */
+    public void setMatch(String match) {
+        this.match = MatchType.ofString(match);
+    }
+
+    /**
+     * Get the match type for this item
+     * @return The match type
+     * @since 19285
+     */
+    public String match() {
+        return this.match.getValue();
     }
 
@@ -222,5 +249,5 @@
      */
     public boolean isKeyRequired() {
-        final MatchType type = MatchType.ofString(match);
+        final MatchType type = this.match;
         return MatchType.KEY_REQUIRED == type || MatchType.KEY_VALUE_REQUIRED == type;
     }
@@ -244,5 +271,5 @@
     @Override
     public Boolean matches(Map<String, String> tags) {
-        switch (MatchType.ofString(match)) {
+        switch (this.match) {
         case NONE:
             return null; // NOSONAR
