0 0

有关static编译时问题10

从csdn中看到的一个问题:
public class Test {
	static {
		s = "9";//no  error
		System.out.println(s); //非法向前引用error
	}
	
	private static String s;
}




以下纯属个人理解:

假设编译通过,从java生命周期中的装载——连接——初始化中的连接阶段来说,分为3个部分:验证、准备、解析,装载的时候在堆中生成java.lang.Class的实例,验证阶段做些字节码、类型等验证工作,准备阶段就是为类变量分配内存,并设为默认值,也就是说,在这个时候已经为String ss分配了内容,并且设成null值,而此时static块是没有执行的,static块在编译的时候被编译器弄成到<clinit>方法的方法体中,这是类和接口的初始化方法,java语言本身不能调用,这个<clinit>方法在初始化阶段才被java虚拟机调用,也就是说执行了static方法里的内容。

从我个人理解,这个类在运行时不会有任何问题。

但是为什么会在编译的时候报这样的错,我表示很费解,大牛们解释下?



执行javac -verbose Test.java命令出来的结果:
[解析开始时间 Test.java]
[解析已完成时间 31ms]
[源文件的搜索路径: D:\programs\IBM\WebSphere MQ\Java\lib\com.ibm.mqjms.jar,D:\programs\IBM\WebSphere MQ\Java\lib\jms.ja
r,D:\programs\IBM\WebSphere MQ\Java\lib\com.ibm.mq.jmqi.jar,D:\programs\IBM\WebSphere MQ\Java\lib\com.ibm.mq.jmqi.local.
jar,D:\programs\IBM\WebSphere MQ\Java\lib\com.ibm.mq.jmqi.remote.jar,D:\programs\IBM\WebSphere MQ\Java\lib\com.ibm.mq.jm
qi.system.jar,D:\programs\IBM\WebSphere MQ\Java\lib\jta.jar,D:\programs\IBM\WebSphere MQ\Java\lib\com.ibm.mq.ese.jar,D:\
programs\IBM\WebSphere MQ\Java\lib\dhbcore.jar,D:\programs\IBM\WebSphere MQ\Java\lib\rmm.jar,D:\programs\IBM\WebSphere M
Q\Java\lib\jndi.jar,D:\programs\IBM\WebSphere MQ\Java\lib\ldap.jar,D:\programs\IBM\WebSphere MQ\Java\lib\fscontext.jar,D
:\programs\IBM\WebSphere MQ\Java\lib\providerutil.jar,D:\programs\IBM\WebSphere MQ\Java\lib\CL3Export.java,D:\programs\I
BM\WebSphere MQ\Java\lib\CL3Nonexport.jar,D:\programs\IBM\WebSphere MQ\Java\lib\com.ibm.mq.jar,D:\programs\IBM\WebSphere
 MQ\Java\lib\com.ibm.mq.headers.jar,D:\programs\IBM\WebSphere MQ\Java\lib\com.ibm.mq.pcf.jar,D:\programs\IBM\WebSphere M
Q\Java\lib\connector.jar,D:\programs\IBM\WebSphere MQ\Java\lib\com.ibm.mq.commonservices.jar,.,D:\programs\IBM\SQLLIB\ja
va\db2java.zip,D:\programs\IBM\SQLLIB\java\db2jcc.jar,D:\programs\IBM\SQLLIB\java\sqlj.zip,D:\programs\IBM\SQLLIB\java\d
b2jcc_license_cu.jar,D:\programs\IBM\SQLLIB\bin,D:\programs\IBM\SQLLIB\java\common.jar]
[类文件的搜索路径: D:\programs\java\jdk1.6.0_18\jre\lib\resources.jar,D:\programs\java\jdk1.6.0_18\jre\lib\rt.jar,D:\pr
ograms\java\jdk1.6.0_18\jre\lib\sunrsasign.jar,D:\programs\java\jdk1.6.0_18\jre\lib\jsse.jar,D:\programs\java\jdk1.6.0_1
8\jre\lib\jce.jar,D:\programs\java\jdk1.6.0_18\jre\lib\charsets.jar,D:\programs\java\jdk1.6.0_18\jre\classes,D:\programs
\java\jdk1.6.0_18\jre\lib\ext\dnsns.jar,D:\programs\java\jdk1.6.0_18\jre\lib\ext\localedata.jar,D:\programs\java\jdk1.6.
0_18\jre\lib\ext\sunjce_provider.jar,D:\programs\java\jdk1.6.0_18\jre\lib\ext\sunmscapi.jar,D:\programs\java\jdk1.6.0_18
\jre\lib\ext\sunpkcs11.jar,D:\programs\IBM\WebSphere MQ\Java\lib\com.ibm.mqjms.jar,D:\programs\IBM\WebSphere MQ\Java\lib
\jms.jar,D:\programs\IBM\WebSphere MQ\Java\lib\com.ibm.mq.jmqi.jar,D:\programs\IBM\WebSphere MQ\Java\lib\com.ibm.mq.jmqi
.local.jar,D:\programs\IBM\WebSphere MQ\Java\lib\com.ibm.mq.jmqi.remote.jar,D:\programs\IBM\WebSphere MQ\Java\lib\com.ib
m.mq.jmqi.system.jar,D:\programs\IBM\WebSphere MQ\Java\lib\jta.jar,D:\programs\IBM\WebSphere MQ\Java\lib\com.ibm.mq.ese.
jar,D:\programs\IBM\WebSphere MQ\Java\lib\dhbcore.jar,D:\programs\IBM\WebSphere MQ\Java\lib\rmm.jar,D:\programs\IBM\WebS
phere MQ\Java\lib\jndi.jar,D:\programs\IBM\WebSphere MQ\Java\lib\ldap.jar,D:\programs\IBM\WebSphere MQ\Java\lib\fscontex
t.jar,D:\programs\IBM\WebSphere MQ\Java\lib\providerutil.jar,D:\programs\IBM\WebSphere MQ\Java\lib\CL3Export.java,D:\pro
grams\IBM\WebSphere MQ\Java\lib\CL3Nonexport.jar,D:\programs\IBM\WebSphere MQ\Java\lib\com.ibm.mq.jar,D:\programs\IBM\We
bSphere MQ\Java\lib\com.ibm.mq.headers.jar,D:\programs\IBM\WebSphere MQ\Java\lib\com.ibm.mq.pcf.jar,D:\programs\IBM\WebS
phere MQ\Java\lib\connector.jar,D:\programs\IBM\WebSphere MQ\Java\lib\com.ibm.mq.commonservices.jar,.,D:\programs\IBM\SQ
LLIB\java\db2java.zip,D:\programs\IBM\SQLLIB\java\db2jcc.jar,D:\programs\IBM\SQLLIB\java\sqlj.zip,D:\programs\IBM\SQLLIB
\java\db2jcc_license_cu.jar,D:\programs\IBM\SQLLIB\bin,D:\programs\IBM\SQLLIB\java\common.jar]
[正在装入 java\lang\Object.class(java\lang:Object.class)]
[正在装入 java\lang\String.class(java\lang:String.class)]
[正在检查 Test]
Test.java:5: 非法向前引用
                System.out.println(s); //非法向前引用error
                                   ^
[正在装入 java\lang\System.class(java\lang:System.class)]
[正在装入 java\io\PrintStream.class(java\io:PrintStream.class)]
[正在装入 java\io\FilterOutputStream.class(java\io:FilterOutputStream.class)]
[正在装入 java\io\OutputStream.class(java\io:OutputStream.class)]
[总时间 297ms]
1 错误



问题补充
xx521 写道
这样编写是有问题的,因为在同一级别的时候编译是有先后顺序的
可以看下这个贴,以前遇到的错误,后来整理的
http://www.ctaoyu.com/index.php/archives/241
http://www.ctaoyu.com/index.php/archives/245




理论上来说,类的字段放在任何位置都应该没有问题

对于放在static或者非static块后面声明的字段如果在块中被使用,就会报这种非法向前引用的错误

而放在构造方法中则没有问题,其中缘由仍不得解

如下
public class Test {
	{
		System.out.println(s);
	}
	private String s;
}


这个代码还是有向前非法引用的错误,而下面这个则没有错误:
public class Test {
	public Test(){
		System.out.println(s);
	}
	private String s;
}


问题补充:<div class="quote_title">xfei6868 写道</div><div class="quote_div">建议你看一下这个小书:http://wenku.baidu.com/view/1807cf84b9d528ea81c77949.html <br /> <br />关于类初始化,可以这么认为 <br /> <br />static变量&nbsp; 和 static块 是最先初始化的(他们不分顺序) <br /> <br />类变量和非静态块 是第二顺序,应该也不分顺序。 <br /> <br />然后才是类的构造函数。</div> <br /> <br /> <br />这个问题与类的初始化已经没有关系了,且类的装载连接初始化问题我已经按照jvm规范中的理解写在了问题中 <br /> <br />现在是编译通不过

问题补充:首先,你回复的这个里面没有静态变量和静态块 <br /> <br />其次,即使是静态变量和静态块,在静态块中可以对静态变量可以赋值,但不能使用,不解在于此 <br /> <br /> <br /> <br /> <br /><div class="quote_title">xfei6868 写道</div><div class="quote_div">怎么 就理解不了呢? <br /> <br />这个就是因为顺序引起的,静态变量在后面的原因无法报错。 <br /> <br /><div class="quote_title">引用</div><div class="quote_div"> <br />public class Test { <br /> { <br /> System.out.println(s); <br /> } <br /> private String s; <br />} <br /></div> <br />而 <br /> <br /><div class="quote_title">引用</div><div class="quote_div"> <br />public class Test { <br /> public Test(){ <br /> System.out.println(s); <br /> } <br /> private String s; <br />} <br /></div> <br /> <br />能成功的原因是&nbsp; 构造函数是在静态段和静态变量后完成的。 <br />自然没有问题。</div> <br />

问题补充:<div class="quote_title">cantellow 写道</div><div class="quote_div">请参照JVM规范2.16.4 <br /><a href="http://java.sun.com/docs/books/jvms/first_edition/html/Concepts.doc.html#19075" target="_blank">http://java.sun.com/docs/books/jvms/first_edition/html/Concepts.doc.html#19075</a> <br />The intent here is that a type has a set of initializers that put it in a consistent state, and that this state is the first state that is observed by other classes. The static initializers and class variable initializers are executed in textual order and may not refer to class variables declared in the class whose declarations appear textually after the use, even though these class variables are in scope. This restriction is designed to detect, at compile time, most circular or otherwise malformed initializations. <br /> <br />大概意思就是,类变量初始化顺序按照文本顺序执行,并且不可以在类变量声明之前引用(注意是引用)它,即使这些变量是可见的。设计这个限制是为了在编译时期检测绝大多数循环或者损坏的初始化。 <br /> <br />我的理解是,引用类变量会导致类主动初始化,而这个时候正在执行&lt;cinit&gt;方法,所以为了防止此类循环和损坏的初始化发生,java编译器设置了这一条规定。<img src="/images/smiles/icon_wink.gif"/>&nbsp;<img src="/images/smiles/icon_wink.gif"/> </div> <br /> <br /> <br />正如前面几位所说: <br />public class Test {&nbsp; <br />&nbsp;&nbsp;&nbsp; static {&nbsp; <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; s = "9";//no&nbsp; error&nbsp; <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // System.out.println(s); //非法向前引用error&nbsp; <br /> &nbsp;&nbsp; print(); <br />&nbsp;&nbsp;&nbsp; }&nbsp; <br />&nbsp;&nbsp;&nbsp; static void print() { <br /> System.out.println(s); <br /> } <br />&nbsp;&nbsp;&nbsp; private static String s; <br /> <br />} <br /> <br />这算不算声明之前就“引用”呢

问题补充:<div class="quote_title">cantellow 写道</div><div class="quote_div">当然算声明之前就应用了,我想你应该知道,如果把private static String s;放在最前面就没有问题。所以,对s的赋值是可以打乱顺序的,但是只要访问它,就会出现这种问题。下面的代码也会出现向前引用的问题: <br /><pre name="code" class="java">
public class Test { 
    static { 
        s = "9";//no  error 
        String ss = s; //非法向前引用error 
    } 
     
    private static String s; 

</pre></div> <br />但那个代码是可以通过编译的: <br />public class Test {&nbsp; <br />&nbsp;&nbsp;&nbsp; static {&nbsp; <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; s = "9";//no&nbsp; error&nbsp; <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // System.out.println(s); //非法向前引用error&nbsp; <br />&nbsp;&nbsp; print(); <br />&nbsp;&nbsp;&nbsp; }&nbsp; <br />&nbsp;&nbsp;&nbsp; static void print() { <br />System.out.println(s); <br />} <br />&nbsp;&nbsp;&nbsp; private static String s; <br /> <br />}
2010年12月13日 11:05

8个答案 按时间排序 按投票排序

0 0

引用
这个<clinit>方法在初始化阶段才被java虚拟机调用,也就是说执行了static方法里的内容。


并不是执行!!试想一下,这是在编译时期,怎么会是执行呢?
要理解清楚,首先不要用运行的眼光去看待它!

2010年12月29日 20:11
0 0

public class Test {  
    static {  
        s = "9";//no  error  
       // System.out.println(s); //非法向前引用error  
   print(); 
    }  
    static void print() { 
System.out.println(s); 
} 
    private static String s; 

}


java编译器在编译时,会把类变量初始化语句和静态初始化语句的代码都放在class文件中的<cinit>方法,但是当访问s类变量时,有直接引用和间接引用之分, String ss = s;它在编译时期(不是运行时期!!)就去访问s,而print(); 方法并没有在编译时期直接访问s,而是在运行时期访问s,这有本质的区别!!因为在编译时期访问s的话,会导致s的初始化,这就会发生像前面说的循环初始化和损坏初始化。
下面这段代码也会出现同样的编译错误:
static {
	s = "9";// no error
	//System.out.println(s); // 非法向前引用error
	//String ss = s;
	print(s); 
    }
    static void print(String s) { 
	System.out.println(s); 
	} 
    public static String s = "";

2010年12月29日 20:03
0 0

当然算声明之前就应用了,我想你应该知道,如果把private static String s;放在最前面就没有问题。所以,对s的赋值是可以打乱顺序的,但是只要访问它,就会出现这种问题。下面的代码也会出现向前引用的问题:

public class Test {  
    static {  
        s = "9";//no  error  
        String ss = s; //非法向前引用error  
    }  
      
    private static String s;  
}  

2010年12月29日 18:40
0 0

请参照JVM规范2.16.4
http://java.sun.com/docs/books/jvms/first_edition/html/Concepts.doc.html#19075
The intent here is that a type has a set of initializers that put it in a consistent state, and that this state is the first state that is observed by other classes. The static initializers and class variable initializers are executed in textual order and may not refer to class variables declared in the class whose declarations appear textually after the use, even though these class variables are in scope. This restriction is designed to detect, at compile time, most circular or otherwise malformed initializations.

大概意思就是,类变量初始化顺序按照文本顺序执行,并且不可以在类变量声明之前引用(注意是引用)它,即使这些变量是可见的。设计这个限制是为了在编译时期检测绝大多数循环或者损坏的初始化。

我的理解是,引用类变量会导致类主动初始化,而这个时候正在执行<cinit>方法,所以为了防止此类循环和损坏的初始化发生,java编译器设置了这一条规定。 

2010年12月29日 10:14
0 0

简单的可以看出 java的块中的引用跟定义的引用使用了同样的规则,

就是 在其前面的不能被引用 ,就类似于:

int i = j;  //向前引用错误。
int j = 5;


然而同样之所以在方法中可以同样是由于我上面说的一种装载顺序的原因:

static 变量或static块 -->  成员变量或成员块 --> 构造函数

例如这样就能成功:
public class Test {
	{
		System.out.println(s);
	}
	private static String s;
}


同样值得研究的是方法普通方法是永远可以调用对应变量的(无论在其前或者后),而块同样可以调用方法,如果写一个这样的方法就可以实现了楼主的想法:
public class Test {
	{
		s = "9";
                printOut();
	}
        void printOut() {
                System.out.println(s);
        }
	private String s;
}


然而赋值的位置不同造成输出结果也就不同,例如改为:
public class Test {
	{
                printOut();
	}
        void printOut() {
                System.out.println(s);
        }

        {
                s = "9";
        }
	private String s;
}


输出结果就不是9。

同理:static的块,变量,方法,赋值也一样遵循这样的原则。

可以看出来,这样的原则:
1. java 块,变量在引用问题上是同一个层次的,就是不能在定义之前引用。
2. java的方法就可以打破这样的约束,但是并不是所有的变量赋值,方法引用完成后才执行此方法,要看方法“嵌入”的位置。
3. 变量的赋值是可以放在任何地方的,同时定义和赋值并不是一起的,例如:
public class Test {
	{
                printOut();
	}
        void printOut() {
                System.out.println(s);
        }

	private String s="9";
}

这个结果同样会令人不解。

综合以上:

可以看出楼主说的 赋值后输出本身应该没什么问题了,但是由于java对于必须在定义后引用的检查造成这样的做是不能编译通过的。





2010年12月14日 09:30
0 0

怎么 就理解不了呢?

这个就是因为顺序引起的,静态变量在后面的原因无法报错。

引用

public class Test {
{
System.out.println(s);
}
private String s;
}



引用

public class Test {
public Test(){
System.out.println(s);
}
private String s;
}


能成功的原因是  构造函数是在静态段和静态变量后完成的。
自然没有问题。

2010年12月13日 20:20
0 0

建议你看一下这个小书:http://wenku.baidu.com/view/1807cf84b9d528ea81c77949.html

关于类初始化,可以这么认为

static变量  和 static块 是最先初始化的(他们不分顺序)

类变量和非静态块 是第二顺序,应该也不分顺序。

然后才是类的构造函数。

2010年12月13日 11:05
0 0

这样编写是有问题的,因为在同一级别的时候编译是有先后顺序的
可以看下这个贴,以前遇到的错误,后来整理的
http://www.ctaoyu.com/index.php/archives/241
http://www.ctaoyu.com/index.php/archives/245

2010年12月13日 11:05

相关推荐

    Qt 5.4.2 MinGW32 static 静态编译 版本打包

    MinGW(Minimalist GNU for Windows)是一个基于GCC的Windows下的编译器套件,而静态编译意味着Qt库与应用程序一起链接,形成一个独立的可执行文件,无需在目标系统上安装额外的运行时环境。 **一、静态编译的优势*...

    Qt 5.15.1 MSVC static 静态编译包

    在压缩包文件`msvc-static`中,通常会包含所有静态编译的Qt库文件,以及必要的头文件和库文件。这些文件是编译过程的产物,用于构建和运行使用静态Qt库的应用程序。确保在项目设置中指向这个目录,以便正确链接静态...

    qt5.15.9-static 静态编译库,已经编译完毕,直接安装即可

    静态库是指在编译应用程序时,库中的代码会被直接合并到最终的可执行文件中。这样的好处是可执行文件独立,无需在目标系统上安装额外的库,但缺点是生成的文件通常较大,因为包含了所有库代码。 QT5.15.9 版本引入...

    boost1_66_0 + vs2015+ x64 + static编译的库文件

    但是,这也避免了运行时依赖问题,确保程序在没有相应动态库的环境中也能正常运行。 在使用Boost库时,开发人员应遵循C++最佳实践,注意异常安全性和资源管理,充分利用Boost库提供的智能指针、共享内存、多线程等...

    Qt 5.15.1 mingw81-static.7z 静态编译包

    **Qt 5.15.1 Mingw81-Static 静态编译包详解** Qt 是一个功能丰富的开源跨平台应用程序开发框架,广泛应用于桌面、移动和嵌入式平台。Qt 5.15.1 版本是 Qt 5 系列的一个稳定版本,提供了大量的改进和新特性,旨在...

    gdal static library 编译设置

    静态库在编译时会被链接到目标程序中,形成一个完整的可执行文件,无需依赖外部库文件。而动态库在运行时才被加载,可以节省磁盘和内存空间,但需要对应的库文件存在。 **准备工作:** 在开始编译之前,确保你已经...

    Qt6.22 Mingw64静态编译

    此外,静态编译可能会涉及版权问题,尤其是当使用开源库时,确保遵循相关的许可协议。 总的来说,Qt6.2.2 Mingw64静态编译是一项复杂但必要的任务,对于那些需要自包含且不受运行环境限制的应用程序尤其有价值。...

    VS2019刚刚编译的openssl-1.1.1静态编译 static 32位 x86-release

    标题中的“VS2019刚刚编译的openssl-1.1.1静态编译 static 32位 x86-release”表明这是一个使用Visual Studio 2019编译的OpenSSL库,版本为1.1.1,采用的是静态链接方式,并且是针对32位(x86)平台的release版本。...

    alsa static 编译源码,已修改好源码文件和Android.mk

    alsa static 编译源码,已修改好源码文件和Android.mk 可以用来自己做修改定制,快速编译属于自己的静态alsa bin程序

    静态断言(编译时断言)

    它可以帮助程序员预防因逻辑错误导致的运行时问题,并且由于在编译阶段执行,不会影响程序的运行效率。理解并熟练运用静态断言,对于编写可靠、高效的代码至关重要。在不同的编程语言中,静态断言的实现方式有所不同...

    VS2019刚刚编译的openssl-1.1.1静态编译 static 64位 x64-debug

    标题中的“VS2019刚刚编译的openssl-1.1.1静态编译 static 64位 x64-debug”表明这是一个使用Visual Studio 2019编译的OpenSSL库,版本为1.1.1,是针对64位(x64)平台的静态链接版本,并且是调试模式(debug)的构建。...

    ffmpeg win64 static

    在这个“ffmpeg win64 static”资源中,特别针对Windows 64位操作系统提供了预编译的静态版本。 静态编译意味着FFmpeg的二进制文件已经包含了所有必需的库,使得用户无需额外安装依赖即可直接运行。"ffmpeg-...

    全套Windows系统下 QT C++ 6.6 static 静态编译环境

    QT C++ 6.6 Static 编译环境在Windows系统中的搭建是一门涉及多方面技术的知识,主要包括QT库、C++编程语言、Windows操作系统以及静态链接等概念。这里我们将深入探讨这些知识点,以便理解如何在Windows环境下构建一...

    VS2019刚刚编译的openssl-1.1.1静态编译 static 32位 x86-debug

    5. **使用步骤**:在使用这个编译后的OpenSSL库时,开发者需要将编译得到的静态库文件(通常是.lib文件)链接到他们的项目中,并可能需要设置相关的头文件路径。在调试模式下,可以利用VS2019的调试工具来检查代码的...

    Qt 5.15.2 MinGW81 静态编译

    8. **调试与优化**:如果在编译或运行过程中遇到问题,可以开启编译时的调试信息,以便于定位问题。同时,可以调整编译选项来优化编译速度和生成的二进制文件大小。 总的来说,静态编译Qt是一个涉及多步操作的过程...

    libwebsockets的交叉编译

    其中,CMAKE_SYSTEM_NAME 指定目标机所在的操作系统名称,CMAKE_FIND_ROOT_PATH 代表一系列的相关文件夹路径的根路径的变更,CMAKE_FIND_ROOT_PATH_MODE_PROGRAM、CMAKE_FIND_ROOT_PATH_MODE_LIBRARY 和 CMAKE_FIND_...

    MinGW版ACE编译好的库(static版)

    本资源提供的是一份静态编译的版本,这意味着库文件已经与所需的运行时库链接在一起,应用程序在链接到这些库时不需要额外的动态链接库支持。 首先,让我们深入了解一下MinGW。MinGW(Minimalist GNU for Windows)...

    windows下的glog相关编译文件libglog.dll和libglog.lib等

    libglog_static.lib则是一个用于静态链接的库文件,它允许你在编译时将glog的功能直接嵌入到你的代码中,避免运行时对dll的依赖。 描述中提到glog版本为0.3.3,这是glog的一个稳定版本,适用于VS2015编译环境。这...

    staticlib5.6.3静态编译库

    这是我自己编译的可用于windows 32位系统下的QT静态编译库,此库为官方源码完全编译版本,支持QT5.6.3 mingw32位版本开发工具,可用来开发兼容于winxp系统及以上系统的程序,利用此库进行开发,可以编译出体积比较小...

    qt5.12.0静态资源包/Visual Studio 2022 静态编译qt项目/静态与动态编译

    qt5.12.0静态资源包,不支持directx 12版本....就是静态编译出来会将相关用到的qt核心依赖库随编译一起打包出来一个exe文件,无需引入qt core等dll。动态编译要手东windeoloy ,体积几百M,静态编译只需要10M不到

Global site tag (gtag.js) - Google Analytics