Java引用类型(String)

news2024/12/24 22:58:35

目录

String解析

final的作用

String是否有长度限制

StringBuffer解析

StringBuilder解析

关键字、操作类相关


引用数据类型非常多大致包括:类、 接口类型、 数组类型、 枚举类型、 注解类型、 字符串型。String类型就是引用类型。

String解析

JVM运行时会分配一块空间给String,字符串的分配和其他对象分配一样,需要消耗高昂的时间和空间,JVM为了提高性能和减少内存的开销,在实例化字符串的时候进行了一些优化,使用字符串常量池,创建字符串常量时,JVM先检查字符串常量池中有没有,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用,如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。String类是由final关键字修饰的,字符串具有不可变性,常量池中不会存在两个相同的字符串。

      public class App {
          public static void main(String[] args) {
              String a = "111";
              a = "222";
              System.out.println(a);
          }
      }

引用类型声明的变量是指该变量在内存中实际存储的是一个引用地址,实体在堆中。所以上面String a = “111”,表达的是变量a里保存了“111”这个对象的引用地址,变量是可以变的,不能变的是“111”。a="222",先去JVM常量池中查找,如果常量池中存在,就直接把对象的引用地址赋给a,如果不存在就重新创建一个对象,然后把对象的引用地址赋给a。

final的作用

当用final修饰一个类时,表明这个类不能被继承。final修饰的类中的成员变量可以根据需要设为final(类中所有成员方法都会被隐式地指定为final方法)。final修饰的方法表示此方法已经是“最后的、最终的”含义,即此方法不能被重写,但可以重载。重写的前提是子类可以从父类中继承此方法,如果父类中final修饰方法同时访问控制权限为private,会导致子类中不能直接继承到此方法,此时可以在子类中定义相同的方法名和参数(类的private方法会隐式地被指定为final方法)。当final修饰一个基本数据类型时,表示该基本数据类型的值一旦在初始化后便不能发生变化。如果final修饰一个引用类型时,则在对其初始化之后便不能再让其指向其他对象了,但该引用所指向的对象的内容是可以发生变化的。另外final修饰一个成员变量必须要初始化。有两种初始化方式(在声明的时候给其赋值,在其类的所有构造方法中为其赋值)

    public class FinalDemo {
        private final String name;//1

        public FinalDemo(String name) {//2
            this.name = name;//3
        }

        public FinalDemo() {//4
        }
    }

1处不会通过编译,要先给name初始化值才可以通过编译。

String几个常用方法源码

       public String concat(String str) {
              int otherLen = str.length();
              if (otherLen == 0) {
                  //啥都没有,就直接把当前字符串给你
                  return this;
              }
              int len = value.length;
              char buf[] = Arrays.copyOf(value, len + otherLen);
              str.getChars(buf, len);
              //看到了吗?返回的居然是新的String对象
              return new String(buf, true);
          }
          void getChars(char dst[], int dstBegin) {
              System.arraycopy(value, 0, dst, dstBegin, value.length);
          }    
          public String replace(char oldChar, char newChar) {
              //如果两个是一样的,那就必要替换了,所以返回this
              if (oldChar != newChar) {
                  int len = value.length;
                  int i = -1;
                  //把当前的char数组复制给val,然后下面基于val来操作
                  char[] val = value; 
                  while (++i < len) {
                      if (val[i] == oldChar) {
                          break;
                      }
                  }
                  if (i < len) {
                      //创建一个新的char数组
                      char buf[] = new char[len];
                      for (int j = 0; j < i; j++) {
                          buf[j] = val[j];
                      }
                      while (i < len) {
                          char c = val[i];
                          buf[i] = (c == oldChar) ? newChar : c;
                          i++;
                      }
                      //创建一个新的String对象
                      return new String(buf, true);
                  }
              }
              return this;
          }
          public String substring(int beginIndex, int endIndex) {
              if (beginIndex < 0) {
                  throw new StringIndexOutOfBoundsException(beginIndex);
              }
              if (endIndex > value.length) {
                  throw new StringIndexOutOfBoundsException(endIndex);
              }
              int subLen = endIndex - beginIndex;
              if (subLen < 0) {
                  throw new StringIndexOutOfBoundsException(subLen);
              }
              //正常返回的都是新new出来的String对象
              return ((beginIndex == 0) && (endIndex == value.length)) ? this
                      : new String(value, beginIndex, subLen);
          }
          public String trim() {
              int len = value.length;
              int st = 0;
              char[] val = value;    /* avoid getfield opcode */
              while ((st < len) && (val[st] <= ' ')) {
                  st++;
              }
              while ((st < len) && (val[len - 1] <= ' ')) {
                  len--;
              }
              //如果是该字符串中包含了空格,调用substring方法,否则就是啥都没干原本返回
              //就是如果字符串里有空格,那么还是新生一个String对象返回
              return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
          }

无论是concat、replace、substring还是trim方法的操作都不是在原有的字符串上进行的而是重新生成了一个新的字符串对象。也就是说进行这些操作后,最原始的字符串并没有被改变。String对象一旦被创建就是固定不变的了,对String对象的任何改变都不影响到原对象,任何变化性的操作都会生成新的对象。

    public class App {
        public static void main(String[] args) {
            String a = "111";
            String a1 = "111";

            String b = new String("111");

            //对象地址是同一个 
            //==比的是变量的值(值:指向内容的引用地址),equals比的是变量的内容
            System.out.println(a==a1);
            //对象内容是一样的
            System.out.println(a.equals(a1));
            //对象地址不一样
            System.out.println(a==b);
            //对象内容是一样的
            System.out.println(a.equals(b));
        }
    }

结果解析 

输出结果:true true false true 
第一个输出true,a和a1两个变量保存的引用地址是同一个。
第二个输出true,a和a1引用地址中内容是一样的。
String a = "111"在JVM申请内存存放"111"对应的对象,当String a1="111"的时候,先去JVM里寻找是否存在"111",如果存在直接把对象的引用地址给a1。此时的a和a1都保存着同一个引用地址。String b = new String("111")创建一个对象然后把对象引用地址赋给变量b,先去JVM里找 "111",找到了直接存放引用地址。找不到创建一个对象然后把引用地址给String的有参构造方法里。所以第三个中输出false,因为a和b所保存的对象引用是不一样的。
最后一个输出true。那是因为两个变量所保存的引用地址中的内容都是“111”。

String是否有长度限制

在Java中String是有长度限制的,在JVM编译中有规范。String长度限制的场景:将某固定文件转码成Base64的形式用字符串存储,运行时需要的时候在转回来,文件比较大。String a = "ssssssss..."构造的10万个字符的字符串,编译之后虚拟机提示报错,提示字符串长度过长。字符串的内容是由一个字符数组 char[] 来存储的,由于数组的长度及索引是整数且String类中返回字符串长度的方法length()返回值也是int ,通过int类型对应的包装类Integer源码中可以看到其长度最大限制为2^31 -1,说明数组的长度是0~2^31-1,那么大小就是(2^31-1 = 2147483647 = 2GB)。 但是通过翻阅java虚拟机手册对class文件格式的定义以及常量池中对String类型的结构体定义,对于索引定义了u2,就是无符号占2个字节,2个字节可以表示的最大范围是2^16 -1 = 65535, 但是JVM需要1个字节表示结束指令,所以这个范围就为65534了。超出这个范围在编译时期是会报错。

StringBuffer解析

StringBuffer是可变的字符序列,当一个StringBuffer被创建以后,通过StringBuffer提供append()、insert()、reverse()、setCharAt()、setLength()等方法可以改变这个字符串对象的字符序列。一旦通过StringBuffer生成了最终想要的字符串,就可以调用它的toString()方法将其转换为一个String对象。StringBuffer的直接父类是AbstractStringBuilder,实现了Serializable即StringBuffer的对象可以串行化,在父类中AbstractStringBuilder有属性char[] value,不是final,该value数组存放字符串内容,存放在堆中,StringBuffer是一个final 类,不能被继承,因为StringBuffer字符内容是存在char[] value所以在变化(增加/删除) 时,不用每次都更换地址(不用创建新对象)效率高于String。

/**
 * @Author 
 * StringBuffer
 **/
public class StringBuffer01 {
    public static void main(String[] args) {
        //创建一个大小为16的char[],用于存放字符内容
        StringBuffer stringBuffer01 = new StringBuffer();
        
        //2.通过构造器指定char[]的大小
        StringBuffer stringBuffer02 = new StringBuffer(100);

        //通过给一个String 创建 StringBuffer,char[] 大小就是str.length + 16
        StringBuffer stringBuffer03 = new StringBuffer("hello");

        String str = null;
        StringBuffer sb = new StringBuffer();
        sb.append(str);
        System.out.println(sb.length());//4
    }
}

String str = null是成立的,但是在StringBuffer的源码中append()方法:

@Override
    public synchronized StringBuffer append(Object obj) {
        toStringCache = null;
        super.append(String.valueOf(obj));
        return this;
    }

从源码中看出StringBuffer中的append方法调用了父类的append方法,进父类AbstractStringBuilder查看父类的append方法,源码如下:

    public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }

str为空时,调用appendNull()方法,追进appendNull()方法

  private AbstractStringBuilder appendNull() {
        int c = count;
        ensureCapacityInternal(c + 4);
        final char[] value = this.value;
        value[c++] = 'n';
        value[c++] = 'u';
        value[c++] = 'l';
        value[c++] = 'l';
        count = c;
        return this;
    }

把空对象转化为字符数组’null’,故最后输出的结果应该为4。

StringBuilder解析

StringBuilder和StringBuffer相似,两个类的构造器和方法也基本相同。不同的是StringBuffer是线程安全的,StringBuilder没有实现线程安全功能,所以性能略高。StringBuffer类中的方法都添加了synchronized关键字,给方法添加了一个锁,用来保证线程安全。Java9改进了字符串(包括String、StringBuffer、StringBuilder)的实现。在Java9以前字符串采用char[]数组来保存字符,字符串的每个字符占2字节,Java9的字符串采用byte[]数组再加一个encoding-flag字段来保存字符,字符串的每个字符只占1字节,所以Java9的字符串更加节省空间。

StringBuilder源码

    @Override
    @HotSpotIntrinsicCandidate
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }

是什么导致了StringBuilder的线程不安全,进入父类AbstractStringBuilder

    public AbstractStringBuilder append(String str) {
        if (str == null) {
            return appendNull();
        }
        int len = str.length();
        ensureCapacityInternal(count + len);
        putStringAt(count, str);
        count += len;
        return this;
    }

问题在这两行

ensureCapacityInternal(count + len);
putStringAt(count, str); 

ensureCapacityInternal() 是一个扩容方法

    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        int oldCapacity = value.length >> coder;
        if (minimumCapacity - oldCapacity > 0) {
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity) << coder);
        }
    }

coder是字符的编码格式,编码默认是Latin1,对应的coder是0。还有一种编码UTF16,对应的coder为1。用count(已经使用的长度)+len(要拼接的长度)得到需要的最小长度minimumCapacity。如果这个长度比原来的容量大,则触发扩容,把原数组复制到一个容量为(minimumCapacity*2+2)的新数组。在并发情况下,可能有多个线程拿到相同count,导致扩容不充分引起数组下标越界异常。一般要三个以上线程同时拿到count且必须是在程序开始时,数组不大的时候才可能出现这个异常。再来看putStringAt():

    private final void putStringAt(int index, String str) {
        if (getCoder() != str.coder()) {
            inflate();
        }
        str.getBytes(value, index, coder);
    }

在多线程下,可能有多个线程拿到相同count,在执行getBytes时,这几个线程添加的位置是相同的,可能会发生数据覆盖的情况。StringBuilder线程安全测试示例:

    @Test
    public void stringDemo02() throws InterruptedException {
        CountDownLatch count=new CountDownLatch(10000);
        StringBuilder stringBuilder=new StringBuilder();
        for (int i = 0; i <100 ; i++) {
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j <100 ; j++) {
                        stringBuilder.append("q");
                        count.countDown();
                    }
                }
            });
            t.start();
        }
        count.await();
        System.out.println(stringBuilder.length());
    }
9912

关键字、操作类相关

final关键字:final修饰的类叫最终类,该类不能被继承。final 修饰的方法不能被重写。final 修饰的变量叫常量,常量必须初始化,初始化之后值就不能被修改。

操作字符串的类有:String、StringBuffer、StringBuilder。String和StringBuffer、StringBuilder的区别在于String声明的是不可变的对象,每次操作都会生成新的String对象,然后将指针指向新的 String对象,而StringBuffer、StringBuilder可以在原有对象的基础上进行操作,所以在经常改变字符串内容的情况下最好不要使用String。 StringBuffer和StringBuilder最大的区别在于StringBuffer 是线程安全的而StringBuilder是非线程安全的,StringBuilder的性能高于StringBuffer,在单线程环境下推荐使用StringBuilder,多线程环境下推荐使用StringBuffer。String str="i"与String str=new String("i")区别,String str="i"的方式,Java 虚拟机会将其分配到常量池中,String str=new String("i") 则会被分到堆内存中。

字符串反转

              使用 StringBuilder 或者 stringBuffer 的 reverse() 方法。
              // StringBuffer reverse
              StringBuffer stringBuffer = new StringBuffer();
              stringBuffer. append("abcdefg");
              System. out. println(stringBuffer. reverse()); // gfedcba
              // StringBuilder reverse
              StringBuilder stringBuilder = new StringBuilder();
              stringBuilder. append("abcdefg");
              System. out. println(stringBuilder. reverse()); // gfedcba

 String类的常用方法:

              indexOf():返回指定字符的索引。
              charAt():返回指定索引处的字符。
              replace():字符串替换。
              trim():去除字符串两端空白。
              split():分割字符串,返回一个分割后的字符串数组。
              getBytes():返回字符串的 byte 类型数组。
              length():返回字符串长度。
              toLowerCase():将字符串转成小写字母。
              toUpperCase():将字符串转成大写字符。
              substring():截取字符串。
              equals():字符串比较。

普通类和抽象类区别

普通类不能包含抽象方法,抽象类可以包含抽象方法,抽象类不能直接实例化,普通类可以直接实例化。抽象类不能使用final修饰,定义抽象类就是让其他类继承的,如果定义为final该类就不能被继承,这样彼此就会产生矛盾,所以final不能修饰抽象类。

接口和抽象类区别

抽象类的子类使用extends来继承,接口必须使用implements来实现接口。抽象类可以有构造函数,接口不能有。类可以实现很多个接口,但是只能继承一个抽象类。接口中的方法默认使用 public修饰,抽象类中的方法可以是任意访问修饰符。

泛型

泛型就是可以适应不同的类型,这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。保证了类型的安全性:泛型约束了变量的类型,保证了类型的安全性。例如List和ArrayList。List集合只能加入int类型的变量,ArrayList可以Add任何常用类型,编译的时候不会提示错误。泛型能够省去类型强制转换。提高方法、算法的重用性。

            //泛型类
            public class GenericClass<T> {
              private T value;
              public GenericClass(T value) {
                  this.value = value;
              }
              public T getValue() {
                  return value;
              }
              public void setValue(T value) {
                  this.value = value;
              }
             }
             
            //泛型接口
            public interface GenericInterface<T> {
             void show(T value);
           }
           
           //泛型方法
          public class GenericFun {
              public void show(String value) { }
              public void show(Integer value) { }
          }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1233066.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

姿态估计 手势动作实时识别项目(基于mediapipe、keras进行实现)

姿态估计 手势动作实时识别项目(基于mediapipe、keras进行实现) 0、功能展示1、项目原理介绍2、数据集采集脚本3、将采集到的动作数据集利用mediapipe库检测手部关键点信息,转换成数据信息保存到本地4、训练一个效果一般的随机森林分类器5、使用Kreas训练一个效果好点的全连…

linux内核管理

linux内核会占用一定的空间&#xff0c;所以可以清理一下不需要使用的内核. 参考链接 Linux 内核及其关联文件通常存储在 /boot 目录下&#xff0c;内核模块通常存储在 /lib/modules 目录中。 首先查看已安装的列表&#xff1a; dpkg --list | grep linux-image其中&#xff…

Hfish安全蜜罐部署

一、Hfish蜜罐介绍 HFish蜜罐官网 HFish是一款社区型免费蜜罐&#xff0c;侧重企业安全场景&#xff0c;从内网失陷检测、外网威胁感知、威胁情报生产三个场景出发&#xff0c;为用户提供可独立操作且实用的功能&#xff0c;通过安全、敏捷、可靠的中低交互蜜罐增加用户在失陷…

python实战—核心基础4(超市购物小票随机抽奖程序) lv1

目录 一、核心代码解释 二、代码 三、运行截图 一、核心代码解释 1、random() 函数 描述 random() 方法返回随机生成的一个实数&#xff0c;它在[0,1)范围内。 语法 以下是 random() 方法的语法: import randomrandom.random() 注意&#xff1a;random()是不能直接访问…

肉豆蔻酰六肽-16——让皮肤更加光滑、更加柔软

肉豆蔻酰六肽-16 一种合成的脂肪酸连接肽&#xff0c;已知可提高皮肤的弹性&#xff0c;明显镇静&#xff0c;并帮助皮肤看起来和感觉更光滑、更柔软。它是由肉豆蔻酸与六肽 16 结合而成。肉豆蔻酰六肽 16 被归类为蛋白质刺激肽&#xff0c;这意味着它可以帮助皮肤表面&#x…

抽象轻松测试接口API

测试 1.测试环境 2.测试代码 3.代码结构 Maven项目核心依赖 <dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId><version>4.3.0</version></dependenc…

2023年中国涂料用环氧树脂需求量及行业市场规模前景分析[图]

环氧树脂具有力学性能高&#xff0c;内聚力强、分子结构致密&#xff0c;粘接性能优异&#xff0c;固化收缩率小&#xff08;产品尺寸稳定、内应力小、不易开裂&#xff09;&#xff0c;绝缘性、防腐性、稳定性、耐热性好&#xff08;可达200℃或更高&#xff09;等特点&#x…

解锁无限可能性:探索Amazon Lightsail的便捷云计算服务

解锁无限可能性&#xff1a;探索Amazon Lightsail的便捷云计算服务 在数字化时代&#xff0c;云计算成为推动创新和业务发展的关键驱动力。Amazon Lightsail 作为 Amazon Web Services&#xff08;亚马逊云科技&#xff09;家族中的一员&#xff0c;为小型企业和创业公司提供了…

共谋发展,共赢未来 | 江西航天红源农业科技总经理孟凡明一行莅临拓世科技集团考察参观

在时刻充满着变化与机遇的商业世界里&#xff0c;农业的颠覆与重构已成为产业新风口&#xff0c;在日新月异的当下&#xff0c;农业与乡村正被开辟成为推动产业结构升级的新战场。 2023年11月20日江西航天红源农业科技有限公司总经理孟凡明一行抵达拓世科技集团南昌总部进行考…

【鸿蒙应用ArkTS开发系列】- 灌水区,鸿蒙ArkTs开发有问题可以在该帖中反馈

大家好, 这是一篇水贴&#xff0c;给大家提供一个交流沟通鸿蒙开发遇到问题的地方。 新增新增这个文章呢&#xff0c;大家在开发使用ArkTS开发鸿蒙应用或者鸿蒙服务的时候&#xff0c;有遇到疑问或者问题&#xff0c;可以在本文章评论区提问&#xff0c;我看到了如果知道怎么…

基于SSM的学生档案管理系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

SpingBoot原理

目录 配置优先级Bean管理 (掌握)Bean的获取 ApplicationContext.getBeanBean的作用域 Scope("prototype") Lazy第三方Bean Bean Configuration SpringBoot底层原理 起步依赖与自动配置(无需手撸但面试高频知识点)自动配置引入第三方依赖常见方案方案1&#xff1a;Com…

python实战—数据分析与图表1(QQ群聊天数据分析) lv2

目录 一、核心代码解释 二、代码 三、运行截图 一、核心代码解释 1、readlines() 方法 描述 readlines() 方法用于读取所有行(直到结束符 EOF)并返回列表&#xff0c;该列表可以由 Python 的 for... in ... 结构进行处理。 如果碰到结束符 EOF 则返回空字符串。 语法 r…

HINSTANCE是什么?

HINSTANCE 就是 HMODULE&#xff1a;

SVN创建分支

一 从本地创建方式可指定版本号进行分支创建。 1、在本地目录右击 -----> 点击branch/tag(分支/标签) From: 源&#xff0c;可指定具体的版本号&#xff0c; To path: 可通过"..."选择分支路径 最后点击确定&#xff0c;交由服务器执行创建。 二 通过SVN客…

存储配置和挂载方式

存储配置 Iscsi简介 iSCSI 启动器&#xff0c;从本质上说&#xff0c;iSCSI 启动器是一个客户端设备&#xff0c;用于将请求连接并启动到服务器&#xff08;iSCSI 目标&#xff09;。 iSCSI 启动器有三种实现方式&#xff1a;可以完全基于硬件实现&#xff0c;比如 iSCSI H…

探寻欧洲市场的机遇:深度剖析欧洲跨境电商

随着全球化的不断推进&#xff0c;欧洲作为一个经济发达、多元文化共存的大陆&#xff0c;成为跨境电商发展的重要目标。本文将深入剖析欧洲跨境电商的机遇&#xff0c;分析欧洲市场的特点、挑战与前景&#xff0c;为企业提供在这个充满潜力的市场中蓬勃发展的指导。 欧洲市场的…

Notion AI会员订阅付费

一、Notion AI优势&#xff1a; 自动化任务&#xff1a;NotionAI可以自动完成一些重复性任务&#xff0c;例如对内容进行分类和标记&#xff0c;从而提高工作效率和减少人力成本。个性化建议&#xff1a;NotionAI可以根据用户的偏好和行为模式提供个性化的建议和推荐&#xff…

九宫格 图片 自定义 路径

<image :src" ../../static/img/ item.urlname .png " class"u-w-82 u-h-82 u-p-t-36"></image>使用场景&#xff1a;九宫格里含有多张图片 html <view class"u-p-b-46 u-p-x-35"><u-grid :border"false" c…