Assign value to final variable in Kotlin and bitwise operator

WindSekirun (wind.seo)·2022년 4월 26일
0

이 글은 기존 운영했던 WordPress 블로그인 PyxisPub: Development Life (pyxispub.uzuki.live) 에서 가져온 글 입니다. 모든 글을 가져오지는 않으며, 작성 시점과 현재 시점에는 차이가 많이 존재합니다.

작성 시점: 2018-03-16

도입

일반적 상식으로는 final 변경자가 달린 변수는 값을 설정할 수 없다.

하지만 Reflection 을 잘 활용하면 final 변경자가 달린 변수에 멀쩡하게 값을 설정할 수 있다.

이 과정을 위해 필요한 것은 다음과 같다.

  • 해당 기능을 실행할 클래스의 인스턴스 (this)
  • 해당 필드 이름
  • 설정할 값

필드 검색

val field = findField(receiver.javaClass, fieldName) ?: return

private fun findField(cls: Class<*>, fieldName: String): Field? {
        // Locate the field going back to parent.
        // for extension uses.
        // 확장성을 위해 부모 클래스까지 거슬러 올라가 필드를 찾음
        var targetClass = cls
        while (true) {
            try {
                val field = targetClass.getDeclaredField(fieldName)
                // getDeclaredField checks for non-public scopes as well and it returns accurate results
                // public 범위가 아닌 declaredField를 체크하고, 정확한 결과를 반환한다.
                if (!Modifier.isPublic(field.modifiers)) {
                    field.isAccessible = true
                }
                return field
            } catch (ex: NoSuchFieldException) {
                // ignored
            }

            targetClass = cls.superclass
        }
}

이미 필드 이름이 주어져 있으므로, 해당 클래스의 Class 객체에서 getDeclaredField 를 사용하면 된다.

만일 필드 이름이 public 가 아닌 경우에는 accessible 를 설정한다.

Final 제거

private fun removeFinalModifierAndSet(receiver: Any, field: Field, value: Any) {
        try {
            if (Modifier.isFinal(field.modifiers)) {
                val modifiersField = Field::class.java.getDeclaredField("modifiers")
                if (!modifiersField.isAccessible) {
                    modifiersField.isAccessible = true
                }

                try {
                    modifiersField.setInt(field, field.modifiers and Modifier.FINAL.inv())
                } finally {
                    if (!modifiersField.isAccessible) {
                        modifiersField.isAccessible = false
                    }
                }
            }
        } catch (ex: NoSuchFieldException) {
            // ignored
        } catch (ex: IllegalAccessException) {
            // ignored
        }
    }

Field 클래스에 선언된  modifiers 라는 필드가 public 가 아닐 경우 다시 한번 accessible를 설정하고, field.modifiers & ~Modifier.FINAL 를 적용한다.

여기서 사용된 비트 연산자는 & 연산자, ~ 연산자가 있는데, &는 그렇다 쳐도 ~는 잘 모르고 사용했던 기억이 있기 때문에 한번 정리해보려고 한다.

~ 연산자란?

~ 연산자, 비트 연산자, Bitwise operator라고 불리는 이 연산자는 bit 값이 1일 경우 0, 0일 경우 1로 하는 연산자이다.  Kotlin에서는 inv() 메서드로 사용된다.

예를 들자면, 큿 의 사이즈를 바이너리 값으로 표현해보자.

72 = 01001000

그렇다면, 이 72에 ~를 적용해보자.

~72 = 10110111 = 183

이런 식으로 비트 단위로 직접 반대로 설정하게 하여 Final 값만 제거하고 설정하는 것이다.

값 설정

if (Modifier.isPrivate(field.modifiers)) {
    field.isAccessible = true
}

try {
    field.set(receiver, value)
} finally {
    if (Modifier.isPrivate(field.modifiers)) {
        field.isAccessible = false
    }
}

다시 한번, field 가 private 면 accessible를 true로 설정하고, 해당 필드에 value를 적용한다.

마무리

전체 코드는 다음과 같다.

package pyxis.uzuki.live.attribute.parser

import java.lang.reflect.Field
import java.lang.reflect.Modifier

/**
 * FieldModifier will execute extremely evil things...
 *
 * 1. Find fields in Class<*> (in demo, it will be StyleViewAttributes)
 * 2. remove final modifier of field
 * 3. write value into field
 *
 *
 * reference: https://github.com/apache/commons-lang/blob/master/src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java
 */
object FieldModifier {

    @JvmStatic
    fun process(receiver: Any, fieldName: String, value: Any) {
        // It can be nullable cause field doesn't exists.
        // for extension uses.
        val field = findField(receiver.javaClass, fieldName) ?: return
        removeFinalModifierAndSet(receiver, field, value)
    }

    private fun findField(cls: Class<*>, fieldName: String): Field? {
        // Locate the field going back to parent.
        // for extension uses.
        var targetClass = cls
        while (true) {
            try {
                val field = targetClass.getDeclaredField(fieldName)
                // getDeclaredField checks for non-public scopes as well and it returns accurate results
                if (!Modifier.isPublic(field.modifiers)) {
                    field.isAccessible = true
                }
                return field
            } catch (ex: NoSuchFieldException) {
                // ignored
            }

            targetClass = cls.superclass
        }
    }

    private fun removeFinalModifierAndSet(receiver: Any, field: Field, value: Any) {
        try {
            if (Modifier.isFinal(field.modifiers)) {
                val modifiersField = Field::class.java.getDeclaredField("modifiers")
                if (!modifiersField.isAccessible) {
                    modifiersField.isAccessible = true
                }

                try {
                    modifiersField.setInt(field, field.modifiers and Modifier.FINAL.inv())
                } finally {
                    if (!modifiersField.isAccessible) {
                        modifiersField.isAccessible = false
                    }
                }
            }

            if (Modifier.isPrivate(field.modifiers)) {
                field.isAccessible = true
            }

            try {
                field.set(receiver, value)
            } finally {
                if (Modifier.isPrivate(field.modifiers)) {
                    field.isAccessible = false
                }
            }
        } catch (ex: NoSuchFieldException) {
            // ignored
        } catch (ex: IllegalAccessException) {
            // ignored
        }
    }
}

사용할 때에는 FieldModifier.process(this, "test", "abc"); 이렇게 사용하면 된다.

물론, 실제로 사용할 일이 없어야 하는 것은 맞지만서도(..)

참고 링크

https://github.com/apache/commons-lang/blob/master/src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java

profile
Android Developer @kakaobank

0개의 댓글