美文网首页
Java基础提升5

Java基础提升5

作者: 努力的土豆 | 来源:发表于2019-03-24 22:25 被阅读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 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修饰的类使用方式

相关文章

  • Java基础提升5

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

  • Java基础面试大全

    Java基础面试 Java基础面试... 1 1. Java基础知识... 5 1.1. Java源程序的扩展名是...

  • Java基础汇总

    [ 面试题 ] java基础 面试 | java基础 最近5年133个Java面试问题列表 40个Java集合面试...

  • java接口及其新特性——Java基础回炉(三)

    前言 本文是Java基础回炉文集的第三篇,关于文集可通过《Java基础回炉和提升暨文集开篇》了解。 今天我们将从接...

  • 日常坑我

    条件查询 jpa应该继承JpaSpecificationExecutor 5/31 java基础疯狂java...

  • java基础提升篇

    此篇幅是个人整理的一点心得,针对的广大的普通大众人员,还有此篇文章的出发点是从先就业后择业的角度去分析java程序...

  • Java基础提升2

    今天是第二天,感觉有很多东西需要加强,每次看到那些技术大神洋洋洒洒的博客,内心除了崇拜,更多的是鄙视自己,感慨差距...

  • Java基础提升1

    从今天开始进行基本功的提升,(本人很菜,写的可能不好,欢迎大家提出意见,此外,引用别人的东西,我会全部标识出),我...

  • Java基础提升3

    昨天太困了,就没有写东西,因此今天会补两篇。 今天的内容将围绕Java的变量,目的是对变量有更深刻的认识。 Jav...

  • Java基础提升4

    本文将阐述关于Java语言中变量是如何存储的。 Java中数据的存储位置 寄存器 最快的存储区,位于处理器的内部,...

网友评论

      本文标题:Java基础提升5

      本文链接:https://www.haomeiwen.com/subject/mehpvqtx.html