(#) HashMap can be replaced with SparseArray !!! WARNING: HashMap can be replaced with SparseArray This is a warning. Id : `UseSparseArrays` Summary : HashMap can be replaced with SparseArray Severity : Warning Category : Performance Platform : Android Vendor : Android Open Source Project Feedback : https://issuetracker.google.com/issues/new?component=192708 Since : Initial Affects : Kotlin and Java files Editing : This check runs on the fly in the IDE editor Implementation : [Source Code](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/JavaPerformanceDetector.java) Tests : [Source Code](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/JavaPerformanceDetectorTest.java) Copyright Year : 2012 For maps where the keys are of type integer, it's typically more efficient to use the Android `SparseArray` API. This check identifies scenarios where you might want to consider using `SparseArray` instead of `HashMap` for better performance. This is **particularly** useful when the value types are primitives like ints, where you can use `SparseIntArray` and avoid auto-boxing the values from `int` to `Integer`. If you need to construct a `HashMap` because you need to call an API outside of your control which requires a `Map`, you can suppress this warning using for example the `@SuppressLint` annotation. (##) Example Here is an example of lint warnings produced by this check: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~text src/test/pkg/JavaPerformanceTest.java:182:Warning: Use new SparseIntArray(...) instead for better performance [UseSparseArrays] new SparseArray<Integer>(); // Use SparseIntArray instead -------------------------- src/test/pkg/JavaPerformanceTest.java:184:Warning: Use new SparseBooleanArray(...) instead for better performance [UseSparseArrays] new SparseArray<Boolean>(); // Use SparseBooleanArray instead -------------------------- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Here is the source file referenced above: `src/test/pkg/JavaPerformanceTest.java`: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~java linenumbers package test.pkg; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.LinearGradient; import android.graphics.Rect; import android.graphics.Shader.TileMode; import android.util.AttributeSet; import android.util.SparseArray; import android.widget.Button; import java.util.HashMap; import java.util.Map; /** Some test data for the JavaPerformanceDetector */ @SuppressWarnings("unused") public class JavaPerformanceTest extends Button { public JavaPerformanceTest(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } private Rect cachedRect; @Override protected void onDraw(android.graphics.Canvas canvas) { super.onDraw(canvas); // Various allocations: new String("foo"); String s = new String("bar"); // This one should not be reported: @SuppressLint("DrawAllocation") Integer i = new Integer(5); // Cached object initialized lazily: should not complain about these if (cachedRect == null) { cachedRect = new Rect(0, 0, 100, 100); } if (cachedRect == null || cachedRect.width() != 50) { cachedRect = new Rect(0, 0, 50, 100); } boolean b = Boolean.valueOf(true); // auto-boxing sample(1, 2); // Non-allocations super.animate(); sample2(1, 2); int x = 4 + '5'; // This will involve allocations, but we don't track // inter-procedural stuff here someOtherMethod(); } void sample(Integer foo, int bar) { sample2(foo, bar); } void sample2(int foo, int bar) { } void someOtherMethod() { // Allocations are okay here new String("foo"); String s = new String("bar"); boolean b = Boolean.valueOf(true); // auto-boxing } protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec, boolean x) { // wrong signature new String("not an error"); } protected void onMeasure(int widthMeasureSpec) { // wrong signature new String("not an error"); } protected void onLayout(boolean changed, int left, int top, int right, int bottom, int wrong) { // wrong signature new String("not an error"); } protected void onLayout(boolean changed, int left, int top, int right) { // wrong signature new String("not an error"); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { new String("flag me"); } @SuppressWarnings("null") // not real code @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { new String("flag me"); // Forbidden factory methods: Bitmap.createBitmap(100, 100, null); android.graphics.Bitmap.createScaledBitmap(null, 100, 100, false); BitmapFactory.decodeFile(null); Canvas canvas = null; canvas.getClipBounds(); // allocates on your behalf canvas.getClipBounds(null); // NOT an error final int layoutWidth = getWidth(); final int layoutHeight = getHeight(); if (mAllowCrop && (mOverlay == null || mOverlay.getWidth() != layoutWidth || mOverlay.getHeight() != layoutHeight)) { mOverlay = Bitmap.createBitmap(layoutWidth, layoutHeight, Bitmap.Config.ARGB_8888); mOverlayCanvas = new Canvas(mOverlay); } if (widthMeasureSpec == 42) { throw new IllegalStateException("Test"); // NOT an allocation } // More lazy init tests boolean initialized = false; if (!initialized) { new String("foo"); initialized = true; } // NOT lazy initialization if (!initialized || mOverlay == null) { new String("foo"); } } void factories() { Integer i1 = new Integer(42); Long l1 = new Long(42L); Boolean b1 = new Boolean(true); Character c1 = new Character('c'); Float f1 = new Float(1.0f); Double d1 = new Double(1.0); // The following should not generate errors: Object i2 = new foo.bar.Integer(42); Integer i3 = Integer.valueOf(42); } private boolean mAllowCrop; private Canvas mOverlayCanvas; private Bitmap mOverlay; private abstract class JavaPerformanceTest1 extends JavaPerformanceTest { @Override public void layout(int l, int t, int r, int b) { // Using "this." to reference fields if (this.shader == null) this.shader = new LinearGradient(0, 0, getWidth(), 0, GRADIENT_COLORS, null, TileMode.REPEAT); } } private abstract class JavaPerformanceTest2 extends JavaPerformanceTest { @Override public void layout(int l, int t, int r, int b) { int width = getWidth(); int height = getHeight(); if ((shader == null) || (lastWidth != width) || (lastHeight != height)) { lastWidth = width; lastHeight = height; shader = new LinearGradient(0, 0, width, 0, GRADIENT_COLORS, null, TileMode.REPEAT); } } } private abstract class JavaPerformanceTest3 extends JavaPerformanceTest { @Override public void layout(int l, int t, int r, int b) { if ((shader == null) || (lastWidth != getWidth()) || (lastHeight != getHeight())) { } } } public void inefficientSparseArray() { new SparseArray(); // Use SparseIntArray instead new SparseArray(); // Use SparseLongArray instead new SparseArray(); // Use SparseBooleanArray instead new SparseArray(); // OK } public void longSparseArray() { // but only minSdkVersion >= 17 or if has v4 support lib Map myStringMap = new HashMap(); } public void byteSparseArray() { // bytes easily apply to ints Map myByteMap = new HashMap(); } protected LinearGradient shader; protected int lastWidth; protected int lastHeight; protected int[] GRADIENT_COLORS; private static class foo { private static class bar { private static class Integer { public Integer(int val) { } } } } public JavaPerformanceTest() { super(null); } } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can also visit the [source code](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/JavaPerformanceDetectorTest.java) for the unit tests for this check to see additional scenarios. The above example was automatically extracted from the first unit test found for this lint check, `JavaPerformanceDetector.test`. To report a problem with this extracted sample, visit https://issuetracker.google.com/issues/new?component=192708. (##) Suppressing You can suppress false positives using one of the following mechanisms: * Using a suppression annotation like this on the enclosing element: ```kt // Kotlin @Suppress("UseSparseArrays") fun method() { problematicStatement() } ``` or ```java // Java @SuppressWarnings("UseSparseArrays") void method() { problematicStatement(); } ``` * Using a suppression comment like this on the line above: ```kt //noinspection UseSparseArrays problematicStatement() ``` * Using a special `lint.xml` file in the source tree which turns off the check in that folder and any sub folder. A simple file might look like this: ```xml <?xml version="1.0" encoding="UTF-8"?> <lint> <issue id="UseSparseArrays" severity="ignore" /> </lint> ``` Instead of `ignore` you can also change the severity here, for example from `error` to `warning`. You can find additional documentation on how to filter issues by path, regular expression and so on [here](https://googlesamples.github.io/android-custom-lint-rules/usage/lintxml.md.html). * In Gradle projects, using the DSL syntax to configure lint. For example, you can use something like ```gradle lintOptions { disable 'UseSparseArrays' } ``` In Android projects this should be nested inside an `android { }` block. * For manual invocations of `lint`, using the `--ignore` flag: ``` $ lint --ignore UseSparseArrays ...` ``` * Last, but not least, using baselines, as discussed [here](https://googlesamples.github.io/android-custom-lint-rules/usage/baselines.md.html).