(#) 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