首页 文章

在从构造函数或init块调用的重写抽象函数中初始化变量时,变量未正确初始化

提问于
浏览
1

我遇到了一些Kotlin代码的问题,我发现它与调用一个方法有关,该方法从init块(或辅助构造函数)中分配一些变量,或者重现问题 .

MCVE:

abstract class Shader(/*Input arguments omitted for the sake of an MCVE*/){

    init{
        //Shader loading and attaching, not relevant
        bindAttribs()//One of the abstract methods. In my actual program, this uses OpenGL to bind attributes
        //GLSL program validation
        getUniforms()//Same as the previous one: abstract method using GL calls to get uniforms. This gets locations so an integer is set (the problem)

    }
    abstract fun getUniforms();//This is the one causing problems
    abstract fun bindAttribs();//This would to if primitives or non-lateinit vars are set
}

abstract class BoilerplateShader() : Shader(){
    var loc_projectionMatrix: Int = 404//404 is an initial value. This can be anything though
    var loc_transformationMatrix: Int = 404
    var loc_viewMatrix: Int = 404

    override fun getUniforms(){
        //These would be grabbed by using glGetUniformLocations, but it's reproducable with static values as well
        loc_projectionMatrix = 0
        loc_transformationMatrix = 1
        loc_viewMatrix = 2
        println(loc_projectionMatrix.toString() + ", " + loc_transformationMatrix + ", " + loc_viewMatrix)
    }

    //debug method, only used to show the values
    fun dump(){
        println(loc_projectionMatrix.toString() + ", " + loc_transformationMatrix + ", " + loc_viewMatrix)
    }

}

class TextureShader() : BoilerplateShader(){

    override fun bindAttribs() {
        //This doesn't cause a problem even though it's called from the init block, as nothing is assigned
        //bindAttrib(0, "a_position");
        //bindAttrib(1, "a_texCoord0");
    }
}

//Other repetitive shaders, omitted for brevity

然后做:

val tx = TextureShader()
tx.dump()

打印:

0, 1, 2
404, 404, 404

从getUniforms到最后的转储调用按顺序调用print语句 . 它在 getUniforms 方法中分配得很好,但是在几毫秒之后调用它们时,它们是一个我知道我将不会用于在这个特定MCVE中进行测试的值 .

我正在使用一个严重依赖抽象类的系统,但调用其中一些方法( getUniforms 非常重要)是必须的 . 如果我通过调用 getUniformsBoilerplateShaderTextureShader 中添加一个init块,它可以正常工作 . 使用在创建对象后调用的init函数(不是init块)执行变通方法:

fun init(){
    bindAttribs();
    getUniforms();
}

工作良好 . 但这将涉及创建的实例手动调用它:

val ts = TexturedShader();
ts.init();
ts.dump()

这不是一个选择 . 编写导致Java中Kotlin出现问题的代码就像预期的那样(代码大大缩短,但仍然可以重现):

abstract class Shader{
    public Shader(){
        getUniforms();
    }

     public abstract void getUniforms();
}

abstract class BoilerplateShader extends Shader{
    int loc_projectionMatrix;//When this is initialized, it produces the same issue as Kotlin. But Java doesn't require the vars to be initialized when they're declared globally, so it doesn't cause a problem
    public void getUniforms(){
        loc_projectionMatrix = 1;
        System.out.println(loc_projectionMatrix);
    }
    //and a dump method or any kind of basic print statement to print it after object creation
}

class TextureShader extends BoilerplateShader {
    public TextureShader(){
        super();
    }
}

并且在初始化变量和类之后打印变量的值,如预期的那样打印0 .

尝试使用对象重现相同的事物会产生与数字 when the var isn't lateinit 相同的结果 . 所以这:

var test: String = ""

打印:

0, 1, 2, test
404, 404, 404,

最后一行与打印完全一样:默认情况下 test 的值设置为空String,因此显示为空 .

但是如果var被声明为 lateinit var

lateinit var test: String

它打印:

0, 1, 2, test
404, 404, 404, test

can't declare primitives with lateinit . 因为它在构造函数外部调用,所以它需要初始化或声明为 lateinit .

那么,是否可以从重写的抽象方法初始化基元而无需创建调用它的函数?


编辑:

评论提出了一种工厂方法,但由于抽象,这不会起作用 . 由于尝试的目标是从基类( Shader )调用方法,并且因为抽象类可以在不在每个类中创建手动实现的情况下工作,这是过度的 . 如果构造函数是私有的以使其工作(避免在工厂方法之外进行初始化),则扩展将不起作用( <init> is private in Shader ) .

因此构造函数被强制公开(无论Shader类是否具有主构造函数或辅助构造函数,子类必须具有初始化它的初始化),这意味着可以在绕过工厂方法的同时创建着色器 . 并且,抽象再次导致问题,工厂方法(必须是抽象的)将在每个子类中手动实现,再次导致初始化并手动调用 init() 方法 .

问题仍然是在从构造函数调用抽象方法时是否可以确保初始化非lateinit和primitives . 如果没有涉及抽象,创建工厂方法将是一个完美的解决方案 .

1 回答

  • 0

    注意:绝对最好的想法是避免在抽象类的构造函数方法中调用抽象函数中声明对象/基元,但有些情况下它很有用 . 尽可能避免使用它 .


    我找到的唯一解决方法是使用 by lazy ,因为涉及原语,我可以将赋值转换为块中的工作 .

    lateinit 会使它稍微容易一些,所以创建对象包装器当然可以是一个选项,但在我的情况下使用 by lazy .

    无论如何,这里发生的事情是,构造函数中赋值给int的值稍后会被固定值覆盖 . 伪代码:

    var x /* = 0 */
    constructor() : super.constructor()//x is not initialized yet
    super.constructor(){
        overridden function();
    }
    abstract function()
    overridden function() {
        x = 4;
    }
    // The assignment if `= 0` takes place after the construction of the parent, setting x to 0 and overriding the value in the constructor
    

    随着时间的推移,问题被删除:

    lateinit var x: Integer//x exists, but doesn't get a value. It's assigned later
    constructor() : super.constructor()
    super.constructor(){
        overridden function()
    }
    abstract function()
    overridden function(){
        x = Integer(4);//using an object here since Kotlin doesn't support lateinit with primtives
    }
    //x, being lateinit and now initialized, doesn't get re-initialized by the declaration. x = 4 instead of 0, as in the first example
    

    当我写这个问题时,我认为Java的工作方式不同 . 这是因为我没有在那里初始化变量(有效地,使它们成为lateinit) . 当该类完全初始化时, int x; 不会被赋值 . 如果它被声明为 int x = 1234; ,则Java中出现的问题与此处相同 .

    现在,问题可以追溯到最近和原始;原始人不能迟到 . 一个相当基本的解决方案是使用数据类:

    data class IntWrapper(var value: Int)
    

    由于可以解压缩数据类的值:

    var (value) = intWrapperInstance//doing "var value = ..." sets value to the intWrapperInstance. With the parenthesis it works the same way as unpacking the values of a pair or triple, just with a single value.
    

    现在,因为有一个对象的实例(不是原始),lateinit可以使用 . 但是,这并不是特别有效,因为它涉及创建另一个对象 .

    唯一剩下的选择: by lazy .

    只要有可能将初始化作为函数创建,这是最佳选择 . 问题中的代码是OpenGL着色器的简化版本(更具体地说,是制服的位置) . 这意味着这个特定的代码很容易转换为 by lazy 块:

    val projectionMatrixLocation by lazy{
        glGetUniformLocation(program, "projectionMatrix")
    }
    

    但视情况而定,这可能不太可行 . 特别是因为 by lazy 需要一个 val ,这意味着如果它不会改变就不会出现问题 .

相关问题