Java基础提升5

2019-03-24  本文已影响0人  努力的土豆

今天的内容是关于Java字符串的。字符串,每一个Java开发人员都会用到,但是真的对它熟悉吗?

Java中的String字符串

  1. Java中的String并不是基础数据类型,而是对象,当然这是最简单的认识。其次,还应该清楚 String 是被 final 关键字修饰的,也就是说 String 对象不可变,一旦对象被创建后,对象的内容是不被允许的修改的,如果如果,则会创建一个新的 String 对象,在栈中存在的变量将会指向新创建的对象,之前创建的对象有可能被垃圾回收器回收掉。
public class StringDemo {

    public static void main(String[] args) {
        String s1 = "abc";
        s1 = "sdf";
    }
}

上述代码中,将会在常量池中开辟两块存储区域,s1最终会指向"sdf","abc"将没有任何引用指向它。最终会被回收器回收。

  1. String实际上是使用 数组 来存储数据的,JDK8与JDK11,数组的定义不同,但是从源码可以清晰的看到定义的数组类型。
    JDK11源码
JDK8源码
  1. Sting类中,一旦涉及到修改String值,就会创建一个新的String对象,并返回这个新创的对象。

String对象的创建

public class StringDemo {

    public static void main(String[] args) {
        String s2 = "abc";
        String s1 = new String("abc");
    }
}

上述代码,很多人都会用到,但是它们的差别真的清楚吗?


存储示意图

所以结论:
String s2 = "abc"; 最多创建一个String对象,最少不创建String对象。如果常量池中,存在”abc”,那么s2直接引用,此时不创建String对象。否则,先在常量池先创建”abc”内存空间,再引用。
String s1 = new String("abc"); 最多创建两个String对象,至少创建一个String对象。new关键字绝对会在堆空间创建一块新的内存区域,所以至少创建一个String对象。

匹配相等

使用String类经常需要对两个字符串进行对比,看是否相等。这是又有==和equals两种选择,这两者方法区别很大,可能我们会弄错,下面我们对这两种方法进行详解。

首先要明白这两种方法的用途:

  • 比较类中的数值是否相等使用equals(),前提是这个类重写了equals()方法,否则equals()方法内部依旧使用==实现,比较两个包装类的引用是否指向同一个对象时使用==。
  • equals()是看数值是否相等(前提是这个类重写了equals()方法),比较好理解。而==是看是否属于同一个对象。下面来举例说明==的使用。
  • 先明白这个概念:常量池在Java用于保存在编译期已确定的,已编译的class文件中的一份数据。主要看编译期字符串能否确定。
public class StringDemo {

    public static void main(String[] args) {
        String s1 = "abc";
        String s2 = new String("abc");

        System.out.println(s1 == s2);
        System.out.println(s1.equals(s2));
    }
}
===============
结果
===============
false
true

上述代码阐明了==与equals()的用法。下面分析几个场景:

public class StringDemo {
    public static void main(String[] args) {
        String s1 = "abc";
        String s2 = new String("abc");
       System.out.println(s1 == s2);
    }
}
===============
结果
===============
false

明显不是同一个对象,一个指向字符串常量池,一个指向new出来的堆内存块,new的字符串在编译期是无法确定的。所以输出false。

public class StringDemo {
    public static void main(String[] args) {
        String s1 = "abc1";
        String s2 = "abc" + 1;
       System.out.println(s1 == s2);
    }
}
===============
结果
===============
true

编译期s1和s2都是可以确定的,字符串都是 "abc1",所以s1和s2都指向字符串常量池里的 "abc1"。指向同一个对象,所以为true。

public class StringDemo {
    public static void main(String[] args) {
        String s1 = "abc1";
        int tmp = 1;
        String s2 = "abc" + tmp;
        System.out.println(s1 == s2);

    }
}
===============
结果
===============
false

主要看s1和s2能否在编译期确定,s1是确定的,放进并指向常量池,而s2含有变量导致不确定,所以不是同一个对象。输出false。

public class StringDemo {
    public static void main(String[] args) {
        String s1 = "abc1";
        final int tmp = 1;
        String s2 = "abc" + tmp;
        System.out.println(s1 == s2);

    }
}
===============
结果
===============
true

s1确定,加上final后使得s2也在编译期能够确定,所以输出true。

public class StringDemo {
    public static void main(String[] args) {
        String s1 = "abc1";
        final int tmp = getTmp();
        String s2 = "abc" + tmp;
        System.out.println(s1 == s2);

    }
    public static int getTmp() {
        return 1;
    }
}
===============
结果
===============
false

s1一样是确定的。而s2不能确定,需要运行代码获得tmp,所以不是同一个对象,输出false。

String的insert()方法

前面已经介绍常量池在Java用于保存在编译期已确定的,已编译的class文件中的一份数据。但我们可以通过intern()方法扩展常量池。intern()是扩充常量池的一个方法,当一个String实例str调用intern()方法时,Java会检查常量池中是否有相同的字符串,如果有则返回其引用,如果没有则在常量池中增加一个str字符串并返回它的引用。

public class StringDemo {
    public static void main(String[] args) {
        String s1 = "abc";
        String s2 = new String("abc");
        System.out.println(s1 == s2);
        System.out.println("--------------");
        s2 = s1.intern();
        System.out.println(s1 == s2);
    }
}
===============
结果
===============
false
--------------
true

知识点

final修饰的类使用方式

Java关键字final有“这是无法改变的”或者“终态的”含义,它可以修饰非抽象类、非抽象类成员方法和变量。

  1. final类
    final类不能被继承,因此final类的成员方法没有机会被覆盖,默认都是final的。在设计类的时候,如果这个类不需要有子类,类的实现细节不允许改变,并且确信这个类不会再被扩展,那么就可以设计成final类。
  2. final方法
    如果一个类不允许其子类覆盖某个方法,则可以把这个方法声明成final方法。使用final方法的原因有二:
public class Test1 {
    public void f1() {
        System.out.println("f1");
    }
    public final void f2() {
        System.out.println("f2");
    }
    public void f3() {
        System.out.println("f3");
    }
    private void f4() {
        System.out.println("f4");
    }
}

public class Test2 extends Test1 {
    @Override
    public void f1() {
        System.out.println("Test1父类方法f1被覆盖");
    }
    public static void main(String[] args) {
        Test2 t = new Test2();
        t.f1();
        t.f2();     // 调用从父类继承过来的final方法
        t.f3();     // 调用从父类继承过来的方法
//        t.f4(); //调用失败,无法从父类继承获得
    }
}
===============
结果
===============
Test1父类方法f1被覆盖
f2
f3
  1. final变量(常量)
    用final修饰的成员变量表示常量,值一旦给定就无法修改;final修饰的变量有三种,静态变量,实例变量,局部变量,分别表示三种类型的常量。从下面的例子中可以看出,一旦给final变量初值后,值就不能再改变了。
/**
 * @ClassName: Test3
 * @Description: TODO
 * @Author: kevin
 * @Date: 2019-03-24 22:07
 * @Version: 1.0
 **/
public class Test3 {
    private final String S = "final实例变量S";
    private final int A = 100;
    public final int B = 90;
    public static final int C = 80;
    private static final int D = 70;
    public final int E; // final空白,必须在初始化对象的时候赋值
    public Test3(int x) {
        E = x;
    }

    public static void main(String[] args) {
        Test3 t = new Test3(2);
//        t.A=101; //出错,final变量的值一旦给定就无法改变
//        t.B=91; //出错,final变量的值一旦给定就无法改变
//        t.C=81; //出错,final变量的值一旦给定就无法改变
//        t.D=71; //出错,final变量的值一旦给定就无法改变

        System.out.println(t.A);
        System.out.println(t.B);
        System.out.println(t.C);    //不推荐用对象方式访问静态字段
        System.out.println(t.D);    //不推荐用对象方式访问静态字段
        System.out.println(Test3.C);
        System.out.println(Test3.D);
//        System.out.println(Test3.E);  //出错,因为E为final空白,依据不同对象值有所不同.
        System.out.println(t.E);
        Test3 t1 = new Test3(3);
        System.out.println(t1.E);   //final空白变量E依据对象的不同而不同
    }

    private void test() {
        System.out.println(new Test3(1).A);
        System.out.println(Test3.C);
        System.out.println(Test3.D);
    }

    public void test2() {
        final int a;    //final空白,在需要的时候才赋值
        final int b = 4;    //局部常量--final用于局部变量的情形
        final int c;    //final空白,一直没有给赋值.
        a = 3;
//        a = 4;  //出错,已经给赋过值了.
//        b = 2;  //出错,已经给赋过值了.
    }
}

另外,final变量定义的时候,可以先声明,而不给初值,这中变量也称为final空白,无论什么情况,编译器都确保空白final在使用之前必须被初始化。但是,final空白在final关键字final的使用上提供了更大的灵活性,为此,一个类中的final数据成员就可以实现依对象而有所不同,却有保持其恒定不变的特征。

  1. final参数
    当函数参数为final类型时,可以读取使用该参数,但是无法改变参数的值。


    错误案例
public class Test4 {
    public static void main(String[] args) {
        new Test4().f1(2);
    }
    public void f1(final int i) {
   //     i++;    // i 是final类型的,值是不允许改变的
        System.out.println(i);
    }
}

参考链接

Java的String详解

Java学习笔记(3)—— String类详解

final修饰的类使用方式

上一篇下一篇

猜你喜欢

热点阅读