• 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏吧

java8中的String

Java基础 来源:Mooneal 1次浏览

ps: 这篇博客是乱写的,笔记的形式,后面有空再整理。

1.加载:生成Class对象,不放在堆中,存放在方法区中的元空间。
2.准备:正式为类变量(static),分配内存,并设置默认初始值(数据类型的0值,比如,int 为 0);
3.至此,类加载结束。但初始化还是要看时机的。

  • 实例化的时候 new
  • 调用其中的静态字段或者静态方法。
  • 反射调用的时候。

4.什么时候被动引用,不引发初始化呢?

子类调用父类静态字段的时候,只初始化父类,不初始化子类。静态字段被调用,只会影响它的当前类。

通过数组定义的时候,不会触发此类的初始化
StringTest[] strs=new StringTest[10];

被final修饰的static字段,由于一开始被编译器优化,存储到NotInitiallization常量池中。

5.初始化发生什么呢?
为所有类变量赋值,并执行静态代码快。是通过编译过程中<clinit>收集的类变量顺序来执行的。

public class StringLoader {
    private static StringLoader stringLoader=new StringLoader();
    private static int a;
    private static int b=0;

    private StringLoader(){
        a++;
        b++;
    }

    public static StringLoader getStringLoader(){
        return stringLoader;
    }

    public static void main(String[] args) {
    /* 调用静态方法,类开始初始化,先执行private static StringLoader stringLoader=new StringLoader(); 因此 此时 a=1 b=1 继续初始化类变量,因为a没有初始值,那么不再给他赋值。 它就是1了,而b赋值,所以b=0; */
        StringLoader.getStringLoader();
        System.out.println(a +" :" + b);
    }
}

这段代码的执行结果是1 : 0;

6.实例化不是初始化,实例化指的是,创建对象。根据方法区中的类信息,在对内存中创建类对象,先把实例变量初始化,然后调用构造函数,调用父类构造函数,再调用子类构造函数。

7.java的引用并不是地址,和指针是两个概念。引用存储的是地址的值,可以有多个引用。而所谓的引用传递,就是复制一个引用。值传递就是复制一个值。复制到方法中,也就是栈中。不知道那本书上写的什么,基本类型在栈中是共享的,int a=1;int b=1;a 和 b 指向同一个地址。也是服了,基本类型不管是成员变量还是局部变量,不管存在栈中,还是存在堆中。都是会开辟出一个新的内存空间出来的。例如 int 是4个字节的,那么int a=1 ,就是开辟4个字节的空间存放1。而int 的包装类型 Integer 实现了常量池技术,如果Integer a=1; Integer b=1;

注意 : “==” 对于基本类型比较的是值是否相等。 但对于引用类型比较的就是存储的地址是否相同。

/*基本类型的包装类型中 Float 和 Double 没有实现常量池技术*/
public class IntDemo {
    private Integer a=3;
    private Integer a1=3;
    public static void main(String[] args){
        IntDemo intDemo=new IntDemo();
        intDemo.test();
    }
    public void test(){
        Float a=3f;
        Float a1=3f;
        System.out.println(a==a1);   //false
        System.out.println(this.a==this.a1);  //true
    }
}

8.java规范的方法区是:存放已经被虚拟机加载的类信息、常量、静态变量、以及编译后的代码。
在jdk7 之前 这些东西都是放在 永久代中的。
jdk7开始了移除永久代计划,把常量(String的字符串信息)和 静态变量放入到堆中。类信息还保留在永久代中。

9.同时jdk7 开始了 一项 支持动态语言的更新。
java虚拟机的字节码指令集的数量增加了一个invokedynamic
java 通常在编译其间,就已经把方法比如 print 的完整的符号引用生成出来了。已经确定这个方法的所属类型值了。
而动态语言的一个很重要的特征就是:变量无类型而变量值才有类型。动态语言一般在运行时才确定类型。

为什么java虚拟机对动态类型语言不支持?
java7之前的字节码指令集中 ,指令的第一个参数都是被调用的方法的符号引用。而方法的符号引用只有在编译时产生放在 .class文件中。jdk7 提供了一个新的动态确定目标方法的机制, MethodHandle
MethodHandle 操作方法,把方法作为参数传给别的方法。这类似于c++函数指针的方法。
MethodHandle 类似于 Reflection,但反射是在模拟java代码层次的方法调用。而MethodHandle是模拟字节码层次的方法调用。 MethodHandle 本质就是 invokedynamic

10.在jdk8 之后,完全完成了永久代的移除计划。常量和静态变量的去处仍旧不变。类信息全部放到了元空间中。 元空间是和本地内存相关的。默认上限大小是本地内存,可以用 -XX:MetaspaceSize=2M 来限制大小。
元空间中把类加载后的信息,分别存放在各自的类加载器空间中,也就是说每个类加载器都有自己独立的空间,且GC不再单独回收某个类,而是在类加载器中已经没有相关引用的时候,回收整个类加载器空间。

11.java8 中的常量池和 String.intern() 方法的改变。

针对常量的常量池技术,也发生了一些改变。常量池不仅仅可以放字符串,同时也可以放字符串的相应的引用。这是一个非常好的处理方式。
例如:

public class Test{
    public static void main(String[] args){
        String str1=new String("zzzzk");
        /*java7之后,字符串常量池中可以放引用了,因此采用 str1.intern() 后回去常量池中找是否有这个字符串, 如果有,返回常量池中的字符串引用。 如果没有,把堆中字符串常量的引用放到常量池中去。 反正,常量池已经移到堆中去了。 */
        String a=str1.intern();
        System.out.print(a == str1);
    }
}

来分析一下这段代码:
编译过后,Test.class 文件中,常量池中有 zzzzk 这个常量。
当 Test.class 加载后,把zzzk放到字符串池(堆中一个StringTable,类似于HashMap集合)中。
在运行过程中:

  • 执行到new String(“zzzzk”); //堆中生成一个String对象。
  • 运行到str1.intern(); //去字符串池中找是否有”zzzzk”这个字符串对象,一检查有了。那么返回字符串池中的”zzzzk” 对象的引用给a

因此,运行结果是false.

再来看一段代码:

public class Test{
    public static void main(String[] args){
    String str1=new String("zzzz")+new String("k");
    String a=str1.intern();
    System.out.print(a == str1);
    String s="zzzzk";
    }
}

按照前面的分析 由于编译期间“zzzzk”已经被放在常量池中去了。那么明显这里应该也输出false。
但结果是输出的是 true. 这说明编译时并没有把“zzzzk”放到常量池中去,而是在 运行到 String s=”zzzzk” 时才把“zzzzk”放到常量池中去。
然而用 javap -verbose Test 得到字节码查看class文件中的常量池中,却是有“zzzzk”

Constant pool:
   #1 = Methodref          #16.#38        // java/lang/Object."<init>":()V
   #2 = String             #39            // zzzzk
   #3 = Fieldref           #15.#40        // StringDemo.s:Ljava/lang/String;
   #4 = Class              #41            // java/lang/StringBuilder
   #5 = Methodref          #4.#38         // java/lang/StringBuilder."<init>":()V
   #6 = Class              #42            // java/lang/String
   #7 = String             #43            // zzzz

很多资料说的并没有错,确实在编译阶段,就把“zzzzk”放到class文件中的常量池中去了。但是,class文件中的常量池和运行时常量池是不一样的。
再来看这段代码

public class StringDemo {
    static String s="zzzzk";   //创建了一个对象放到了
    public static void main(String[] args) {
        String str1=new String("zzzz")+new String("k");
        String a=str1.intern();
        System.out.print(a == str1);  //输出结果false
    }
}

通过上面一段代码,可以确认只有在运行到String s=”zzzzk” 的时候,才会把编译收集到的常量信息,放到运行时常量池中去。
(具体细节,没有找到资料描述-。-)


版权声明:本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系管理员进行删除。
喜欢 (0)