Java基础提升5

今天的内容是关于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 s1 = new String("abc"); 首先会在堆内存申请一块内存存储字符串 abc,s1指向其内存块对象。同时还会检查字符串常量池中是否含有 abc 字符串,若没有则添加abc到常量池中。所以 new String 可能会创建两个对象
  • String s2 = "abc"; 先检查字符串常量池是否含有 abc 字符串,如果有则直接指向,没有则在字符常量池添加 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

知识点

  • 单独使用""引号创建的字符串都是直接量,编译期就已经确定存储到常量池中。
  • 使用new String("")创建的对象会存储到堆内存中,是运行期才创建。
  • 使用只包含直接量的字符串连接符如"aa" + "bb"创建的也是直接量编译期就能确定,已经确定存储到常量池中(str2和str3)。
  • 使用包含String直接量(无final修饰符)的字符串表达式(如"aa" + s1)创建的对象是运行期才创建的,存储在堆中。
  • 通过变量/调用方法去连接字符串,都只能在运行时期才能确定变量的值和方法的返回值,不存在编译优化操作。

final修饰的类使用方式

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

  • final类不能被继承、没有子类、final类中的方法默认是final的。
  • final方法不能被子类的方法覆盖,但是可以被继承。
  • final成员变量表示常量,只能被赋值一次,赋值后值不再改变。
  • final不能用于修饰构造函数。
    注意,父类的private成员方法不能被子类方法覆盖,因此private类型的方法默认是final类型的。
  1. final类
    final类不能被继承,因此final类的成员方法没有机会被覆盖,默认都是final的。在设计类的时候,如果这个类不需要有子类,类的实现细节不允许改变,并且确信这个类不会再被扩展,那么就可以设计成final类。
  2. final方法
    如果一个类不允许其子类覆盖某个方法,则可以把这个方法声明成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修饰的类使用方式

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,039评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,223评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,916评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,009评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,030评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,011评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,934评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,754评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,202评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,433评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,590评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,321评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,917评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,568评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,738评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,583评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,482评论 2 352