乐趣区

夯实Java基础系列3一文搞懂String常见面试题从基础到实战更有原理分析和源码解析

目录

  • string 基础

    • Java String 类
    • 创建字符串
    • StringDemo.java 文件代码:
  • String 基本用法

    • 创建 String 对象的常用方法
    • String 中常用的方法,用法如图所示,具体问度娘
    • 三个方法的使用:lenth() substring() charAt()
    • 字符串与 byte 数组间的相互转换
    • == 运算符和 equals 之间的区别:
    • 字符串的不可变性
    • String 的连接
    • String、String builder 和 String buffer 的区别
  • String 类的源码分析

    • String 类型的 intern
    • String 类型的 equals
    • StringBuffer 和 Stringbuilder
    • append 方法
    • 扩容

      • [](#)
    • 删除
    • system.arraycopy 方法
  • String 和 JVM 的关系
  • String 为什么不可变?

    • 不可变有什么好处?
  • String 常用工具类
  • 参考文章
  • 微信公众号

    • Java 技术江湖
    • 个人公众号:黄小斜

本系列文章将整理到我在 GitHub 上的《Java 面试指南》仓库,更多精彩内容请到我的仓库里查看

https://github.com/h2pl/Java-…

喜欢的话麻烦点下 Star 哈

文章首发于我的个人博客:

www.how2playlife.com

本文是微信公众号【Java 技术江湖】的《夯实 Java 基础系列博文》其中一篇,本文部分内容来源于网络,为了把本文主题讲得清晰透彻,也整合了很多我认为不错的技术博客内容,引用其中了一些比较好的博客文章,如有侵权,请联系作者。
该系列博文会告诉你如何从入门到进阶,一步步地学习 Java 基础知识,并上手进行实战,接着了解每个 Java 知识点背后的实现原理,更完整地了解整个 Java 技术体系,形成自己的知识框架。为了更好地总结和检验你的学习成果,本系列文章也会提供每个知识点对应的面试题以及参考答案。

如果对本系列文章有什么建议,或者是有什么疑问的话,也可以关注公众号【Java 技术江湖】联系作者,欢迎你参与本系列博文的创作和修订。

[TOC]

<!– more –>

string 基础

Java String 类

字符串广泛应用 在 Java 编程中,在 Java 中字符串属于对象,Java 提供了 String 类来创建和操作字符串。

创建字符串

创建字符串最简单的方式如下:

String greeting = “ 菜鸟教程 ”;

在代码中遇到字符串常量时,这里的值是 “菜鸟教程“”,编译器会使用该值创建一个 String 对象。

和其它对象一样,可以使用关键字和构造方法来创建 String 对象。

String 类有 11 种构造方法,这些方法提供不同的参数来初始化字符串,比如提供一个字符数组参数:

StringDemo.java 文件代码:

public class StringDemo{public static void main(String args[]){char[] helloArray = {'r', 'u', 'n', 'o', 'o', 'b'};       String helloString = new String(helloArray);         System.out.println(helloString);    } }

以上实例编译运行结果如下:

runoob

注意:String 类是不可改变的,所以你一旦创建了 String 对象,那它的值就无法改变了(详看笔记部分解析)。

如果需要对字符串做很多修改,那么应该选择使用 StringBuffer & StringBuilder 类。

String 基本用法

创建 String 对象的常用方法

(1)String s1 = “mpptest”

(2) String s2 = new String();

(3) String s3 = new String(“mpptest”)

String 中常用的方法,用法如图所示,具体问度娘

三个方法的使用:lenth() substring() charAt()

<pre>package com.mpp.string; public class StringDemo1 {public static void main(String[] args) {// 定义一个字符串 ” 晚来天欲雪 能饮一杯无 ”

    String str = "晚来天欲雪 能饮一杯无";
    System.out.println("字符串的长度是:"+str.length()); // 字符串的雪字打印输出  charAt(int index)
    System.out.println(str.charAt(4)); // 取出子串  天欲
    System.out.println(str.substring(2));   // 取出从 index2 开始直到最后的子串,包含 2
    System.out.println(str.substring(2,4));  // 取出 index 从 2 到 4 的子串,包含 2 不包含 4  顾头不顾尾

}
}</pre>

两个方法的使用, 求字符或子串第一次 / 最后一次在字符串中出现的位置:indexOf() lastIndexOf()

<pre>package com.mpp.string; public class StringDemo2 {public static void main(String[] args) {

    String str = new String("赵客缦胡缨 吴钩胡缨霜雪明"); // 查找胡在字符串中第一次出现的位置
    System.out.println("\" 胡 \"在字符串中第一次出现的位置:"+str.indexOf("胡")); // 查找子串 "胡缨" 在字符串中第一次出现的位置
    System.out.println("\" 胡缨 \"在字符串中第一次出现的位置"+str.indexOf("胡缨")); // 查找胡在字符串中最后一次次出现的位置
    System.out.println(str.lastIndexOf("胡")); // 查找子串 "胡缨" 在字符串中最后一次出现的位置
    System.out.println(str.lastIndexOf("胡缨")); // 从 indexof 为 5 的位置,找第一次出现的 "吴"
    System.out.println(str.indexOf("吴",5));
}

}</pre>

字符串与 byte 数组间的相互转换

<pre>package com.mpp.string; import java.io.UnsupportedEncodingException; public class StringDemo3 {public static void main(String[] args) throws UnsupportedEncodingException {// 字符串和 byte 数组之间的相互转换
String str = new String(“hhhabc 银鞍照白马 飒沓如流星 ”); // 将字符串转换为 byte 数组,并打印输出

    byte[] arrs = str.getBytes("GBK"); for(int i=0;i){System.out.print(arrs[i]);
    } // 将 byte 数组转换成字符串

System.out.println();

    String str1 = new String(arrs,"GBK");  // 保持字符集的一致,否则会出现乱码

System.out.println(str1);

}

}</pre>

== 运算符和 equals 之间的区别:

<pre> 引用指向的内容和引用指向的地址 </pre>

<pre>package com.mpp.string; public class StringDemo5 {public static void main(String[] args) {

    String str1 = "mpp";
    String str2 = "mpp";
    String str3 = new String("mpp");
    System.out.println(str1.equals(str2)); //true  内容相同
    System.out.println(str1.equals(str3));   //true  内容相同
    System.out.println(str1==str2);   //true   地址相同
    System.out.println(str1==str3);   //false  地址不同

}
}</pre>

字符串的不可变性

String 的对象一旦被创建,则不能修改,是不可变的

所谓的修改其实是创建了新的对象,所指向的内存空间不变

上图中,s1 不再指向 imooc 所在的内存空间,而是指向了 hello,imooc

String 的连接

@Test
public void contact () {
    // 1 连接方式
    String s1 = "a";
    String s2 = "a";
    String s3 = "a" + s2;
    String s4 = "a" + "a";
    String s5 = s1 + s2;
    // 表达式只有常量时,编译期完成计算
    // 表达式有变量时,运行期才计算,所以地址不一样
    System.out.println(s3 == s4); //f
    System.out.println(s3 == s5); //f
    System.out.println(s4 == "aa"); //t

}

String、String builder 和 String buffer 的区别

String 是 Java 中基础且重要的类,并且 String 也是 Immutable 类的典型实现,被声明为 final class,除了 hash 这个属性其它属性都声明为 final, 因为它的不可变性,所以例如拼接字符串时候会产生很多无用的中间对象,如果频繁的进行这样的操作对性能有所影响。

StringBuffer 就是为了解决大量拼接字符串时产生很多中间对象问题而提供的一个类,提供 append 和 add 方法,可以将字符串添加到已有序列的末尾或指定位置,它的本质是一个线程安全的可修改的字符序列,把所有修改数据的方法都加上了 synchronized。但是保证了线程安全是需要性能的代价的。

在很多情况下我们的字符串拼接操作不需要线程安全,这时候 StringBuilder 登场了,StringBuilder 是 JDK1.5 发布的,它和 StringBuffer 本质上没什么区别,就是去掉了保证线程安全的那部分,减少了开销。

StringBuffer 和 StringBuilder 二者都继承了 AbstractStringBuilder,底层都是利用可修改的 char 数组(JDK 9 以后是 byte 数组)。

所以如果我们有大量的字符串拼接,如果能预知大小的话最好在 new StringBuffer 或者 StringBuilder 的时候设置好 capacity,避免多次扩容的开销。扩容要抛弃原有数组,还要进行数组拷贝创建新的数组。

我们平日开发通常情况下少量的字符串拼接其实没太必要担心,例如

String str = “aa”+”bb”+”cc”;

像这种没有变量的字符串,编译阶段就直接合成 ”aabbcc” 了,然后看字符串常量池(下面会说到常量池)里有没有,有也直接引用,没有就在常量池中生成,返回引用。

如果是带变量的,其实影响也不大,JVM 会帮我们优化了。

1、在字符串不经常发生变化的业务场景优先使用 String(代码更清晰简洁)。如常量的声明,少量的字符串操作(拼接,删除等)。

2、在单线程情况下,如有大量的字符串操作情况,应该使用 StringBuilder 来操作字符串。不能使用 String”+” 来拼接而是使用,避免产生大量无用的中间对象,耗费空间且执行效率低下(新建对象、回收对象花费大量时间)。如 JSON 的封装等。

3、在多线程情况下,如有大量的字符串操作情况,应该使用 StringBuffer。如 HTTP 参数解析和封装等。

String 类的源码分析

String 类型的 intern

public void intern () {
    //2:string 的 intern 使用
    //s1 是基本类型,比较值。s2 是 string 实例,比较实例地址
    // 字符串类型用 equals 方法比较时只会比较值
    String s1 = "a";
    String s2 = new String("a");
    // 调用 intern 时, 如果 s2 中的字符不在常量池,则加入常量池并返回常量的引用
    String s3 = s2.intern();
    System.out.println(s1 == s2);
    System.out.println(s1 == s3);
}

String 类型的 equals

// 字符串的 equals 方法
//    public boolean equals(Object anObject) {//            if (this == anObject) {
//                return true;
//            }
//            if (anObject instanceof String) {//                String anotherString = (String)anObject;
//                int n = value.length;
//                if (n == anotherString.value.length) {//                    char v1[] = value;
//                    char v2[] = anotherString.value;
//                    int i = 0;
//                    while (n-- != 0) {//                        if (v1[i] != v2[i])
//                            return false;
//                        i++;
//                    }
//                    return true;
//                }
//            }
//            return false;
//        }

StringBuffer 和 Stringbuilder

底层是继承父类的可变字符数组 value

/**

- The value is used for character storage.
  */
  char[] value;
  初始化容量为 16

/**

- Constructs a string builder with no characters in it and an
- initial capacity of 16 characters.
  */
  public StringBuilder() {super(16);
  }
  这两个类的 append 方法都是来自父类 AbstractStringBuilder 的方法

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;
}
@Override
public StringBuilder append(String str) {super.append(str);
    return this;
}

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

append 方法

Stringbuffer 在大部分涉及字符串修改的操作上加了 synchronized 关键字来保证线程安全,效率较低。

String 类型在使用 + 运算符例如

String a = “a”

a = a + a; 时,实际上先把 a 封装成 stringbuilder,调用 append 方法后再用 tostring 返回,所以当大量使用字符串加法时,会大量地生成 stringbuilder 实例,这是十分浪费的,这种时候应该用 stringbuilder 来代替 string。

扩容

注意在 append 方法中调用到了一个函数

ensureCapacityInternal(count + len);
该方法是计算 append 之后的空间是否足够,不足的话需要进行扩容

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

如果新字符串长度大于 value 数组长度则进行扩容

扩容后的长度一般为原来的两倍 + 2;

假如扩容后的长度超过了 jvm 支持的最大数组长度 MAX_ARRAY_SIZE。

考虑两种情况

如果新的字符串长度超过 int 最大值,则抛出异常,否则直接使用数组最大长度作为新数组的长度。

private int hugeCapacity(int minCapacity) {if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
        throw new OutOfMemoryError();}
    return (minCapacity > MAX_ARRAY_SIZE)
        ? minCapacity : MAX_ARRAY_SIZE;
}

删除

这两个类型的删除操作:

都是调用父类的 delete 方法进行删除

public AbstractStringBuilder delete(int start, int end) {if (start < 0)
        throw new StringIndexOutOfBoundsException(start);
    if (end > count)
        end = count;
    if (start > end)
        throw new StringIndexOutOfBoundsException();
    int len = end - start;
    if (len > 0) {System.arraycopy(value, start+len, value, start, count-end);
        count -= len;
    }
    return this;
}

事实上是将剩余的字符重新拷贝到字符数组 value。

这里用到了 system.arraycopy 来拷贝数组,速度是比较快的

system.arraycopy 方法

转自知乎:

在主流高性能的 JVM 上(HotSpot VM 系、IBM J9 VM 系、JRockit 系等等),可以认为 System.arraycopy()在拷贝数组时是可靠高效的——如果发现不够高效的情况,请报告 performance bug,肯定很快就会得到改进。

java.lang.System.arraycopy()方法在 Java 代码里声明为一个 native 方法。所以最 naïve 的实现方式就是通过 JNI 调用 JVM 里的 native 代码来实现。

String 的不可变性
关于 String 的不可变性,这里转一个不错的回答

什么是不可变?
String 不可变很简单,如下图,给一个已有字符串 ”abcd” 第二次赋值成 ”abcedl”,不是在原内存地址上修改数据,而是重新指向一个新对象,新地址。

String 和 JVM 的关系

下面我们了解下 Java 栈、Java 堆、方法区和常量池:

Java 栈(线程私有数据区):

    每个 Java 虚拟机线程都有自己的 Java 虚拟机栈,Java 虚拟机栈用来存放栈帧,每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

Java 堆(线程共享数据区):

   在虚拟机启动时创建,此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配。

方法区(线程共享数据区):

   方法区在虚拟机启动的时候被创建,它存储了每一个类的结构信息,例如运行时常量池、字段和方法数据、构造函数和普通方法的字节码内容、还包括在类、实例、接口初始化时用到的特殊方法。在 JDK8 之前永久代是方法区的一种实现,而 JDK8 元空间替代了永久代,永久代被移除,也可以理解为元空间是方法区的一种实现。

常量池(线程共享数据区):

    常量池常被分为两大类:静态常量池和运行时常量池。静态常量池也就是 Class 文件中的常量池,存在于 Class 文件中。运行时常量池(Runtime Constant Pool)是方法区的一部分,存放一些运行时常量数据。

下面重点了解的是字符串常量池:

    字符串常量池存在运行时常量池之中(在 JDK7 之前存在运行时常量池之中,在 JDK7 已经将其转移到堆中)。字符串常量池的存在使 JVM 提高了性能和减少了内存开销。使用字符串常量池,每当我们使用字面量(String s=”1”;)创建字符串常量时,JVM 会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就将此字符串对象的地址赋值给引用 s(引用 s 在 Java 栈中)。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中,并将此字符串对象的地址赋值给引用 s(引用 s 在 Java 栈中)。
    使用字符串常量池,每当我们使用关键字 new(String s=new String(”1”);)创建字符串常量时,JVM 会首先检查字符串常量池,如果该字符串已经存在常量池中,那么不再在字符串常量池创建该字符串对象,而直接堆中复制该对象的副本,然后将堆中对象的地址赋值给引用 s,如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中,然后在堆中复制该对象的副本,然后将堆中对象的地址赋值给引用 s。

String 为什么不可变?

翻开 JDK 源码,java.lang.String 类起手前三行,是这样写的:

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {   
  /** String 本质是个 char 数组. 而且用 final 关键字修饰.*/     
private final char value[];  ...  ...} 

首先 String 类是用 final 关键字修饰,这说明 String 不可继承。再看下面,String 类的主力成员字段 value 是个 char[]数组,而且是用 final 修饰的。

final 修饰的字段创建以后就不可改变。有的人以为故事就这样完了,其实没有。因为虽然 value 是不可变,也只是 value 这个引用地址不可变。挡不住 Array 数组是可变的事实。

Array 的数据结构看下图。

也就是说 Array 变量只是 stack 上的一个引用,数组的本体结构在 heap 堆。

String 类里的 value 用 final 修饰,只是说 stack 里的这个叫 value 的引用地址不可变。没有说堆里 array 本身数据不可变。看下面这个例子,

final int[] value={1,2,3};int[] another={4,5,6};
 value=another;    // 编译器报错,final 不可变 value 用 final 修饰,编译器不允许我把 value 指向堆区另一个地址。但如果我直接对数组元素动手,分分钟搞定。final int[] value={1,2,3};
 value[2]=100;  // 这时候数组里已经是{1,2,100}   所以 String 是不可变,关键是因为 SUN 公司的工程师。在后面所有 String 的方法里很小心的没有去动 Array 里的元素,没有暴露内部成员字段。private final char value[]这一句里,private 的私有访问权限的作用都比 final 大。而且设计师还很小心地把整个 String 设成 final 禁止继承,避免被其他人继承后破坏。所以 String 是不可变的关键都在底层的实现,而不是一个 final。考验的是工程师构造数据类型,封装数据的功力。

不可变有什么好处?

这个最简单地原因,就是为了安全。看下面这个场景(有评论反应例子不够清楚,现在完整地写出来),一个函数 appendStr()在不可变的 String 参数后面加上一段“bbb”后返回。appendSb()负责在可变的 StringBuilder 后面加“bbb”。

总结以下 String 的不可变性。

1 首先 final 修饰的类只保证不能被继承,并且该类的对象在堆内存中的地址不会被改变。

2 但是持有 String 对象的引用本身是可以改变的,比如他可以指向其他的对象。

3 final 修饰的 char 数组保证了 char 数组的引用不可变。但是可以通过 char[0] = ‘a’来修改值。不过 String 内部并不提供方法来完成这一操作,所以 String 的不可变也是基于代码封装和访问控制的。

举个例子

final class Fi {
    int a;
    final int b = 0;
    Integer s;

}
final char[]a = {'a'};
final int[]b = {1};
@Test
public void final 修饰类() {
    // 引用没有被 final 修饰,所以是可变的。//final 只修饰了 Fi 类型,即 Fi 实例化的对象在堆中内存地址是不可变的。// 虽然内存地址不可变,但是可以对内部的数据做改变。Fi f = new Fi();
    f.a = 1;
    System.out.println(f);
    f.a = 2;
    System.out.println(f);
    // 改变实例中的值并不改变内存地址。
Fi ff = f;
// 让引用指向新的 Fi 对象,原来的 f 对象由新的引用 ff 持有。// 引用的指向改变也不会改变原来对象的地址
f = new Fi();
System.out.println(f);
System.out.println(ff);

}

这里的对 f.a 的修改可以理解为 char[0] = ‘a’ 这样的操作。只改变数据值,不改变内存值。

String 常用工具类

问题描述
很多时候我们需要对字符串进行很多固定的操作, 而这些操作在 JDK/JRE 中又没有预置, 于是我们想到了 apache-commons 组件, 但是它也不能完全覆盖我们的业务需求, 所以很多时候还是要自己写点代码的, 下面就是基于 apache-commons 组件写的部分常用方法:

MAVEN 依赖
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>${commons-lang3.version}</version>
 </dependency>

代码成果

public class StringUtils extends org.apache.commons.lang3.StringUtils {

/** 值为 "NULL" 的字符串 */
private static final String NULL_STRING = "NULL";

private static final char SEPARATOR = '_';
/**
 * 满足一下情况返回 true<br/>
 * ①. 入参为空
 * ②. 入参为空字符串
 * ③. 入参为 "null" 字符串
 *
 * @param string 需要判断的字符型
 * @return boolean
 */
public static boolean isNullOrEmptyOrNULLString(String string) {return isBlank(string) || NULL_STRING.equalsIgnoreCase(string);
}

/**
 * 把字符串转为二进制码 <br/>
 * 本方法不会返回 null
 *
 * @param str 需要转换的字符串
 * @return 二进制字节码数组
 */
public static byte[] toBytes(String str) {return isBlank(str) ? new byte[]{} : str.getBytes();}

/**
 * 把字符串转为二进制码 <br/>
 * 本方法不会返回 null
 *
 * @param str     需要转换的字符串
 * @param charset 编码类型
 * @return 二进制字节码数组
 * @throws UnsupportedEncodingException 字符串转换的时候编码不支持时出现
 */
public static byte[] toBytes(String str, Charset charset) throws UnsupportedEncodingException {return isBlank(str) ? new byte[]{} : str.getBytes(charset.displayName());
}

/**
 * 把字符串转为二进制码 <br/>
 * 本方法不会返回 null
 *
 * @param str     需要转换的字符串
 * @param charset 编码类型
 * @param locale  编码类型对应的地区
 * @return 二进制字节码数组
 * @throws UnsupportedEncodingException 字符串转换的时候编码不支持时出现
 */
public static byte[] toBytes(String str, Charset charset, Locale locale) throws UnsupportedEncodingException {return isBlank(str) ? new byte[]{} : str.getBytes(charset.displayName(locale));
}

/**
 * 二进制码转字符串 <br/>
 * 本方法不会返回 null
 *
 * @param bytes 二进制码
 * @return 字符串
 */
public static String bytesToString(byte[] bytes) {return bytes == null || bytes.length == 0 ? EMPTY : new String(bytes);
}

/**
 * 二进制码转字符串 <br/>
 * 本方法不会返回 null
 *
 * @param bytes   二进制码
 * @param charset 编码集
 * @return 字符串
 * @throws UnsupportedEncodingException 当前二进制码可能不支持传入的编码
 */
public static String byteToString(byte[] bytes, Charset charset) throws UnsupportedEncodingException {return bytes == null || bytes.length == 0 ? EMPTY : new String(bytes, charset.displayName());
}

/**
 * 二进制码转字符串 <br/>
 * 本方法不会返回 null
 *
 * @param bytes   二进制码
 * @param charset 编码集
 * @param locale  本地化
 * @return 字符串
 * @throws UnsupportedEncodingException 当前二进制码可能不支持传入的编码
 */
public static String byteToString(byte[] bytes, Charset charset, Locale locale) throws UnsupportedEncodingException {return bytes == null || bytes.length == 0 ? EMPTY : new String(bytes, charset.displayName(locale));
}

/**
 * 把对象转为字符串
 *
 * @param object 需要转化的字符串
 * @return 字符串, 可能为空
 */
public static String parseString(Object object) {if (object == null) {return null;}
    if (object instanceof byte[]) {return bytesToString((byte[]) object);
    }
    return object.toString();}

/**
 * 把字符串转为 int 类型
 *
 * @param str 需要转化的字符串
 * @return int
 * @throws NumberFormatException 字符串格式不正确时抛出
 */
public static int parseInt(String str) throws NumberFormatException {return isBlank(str) ? 0 : Integer.parseInt(str);
}

/**
 * 把字符串转为 double 类型
 *
 * @param str 需要转化的字符串
 * @return double
 * @throws NumberFormatException 字符串格式不正确时抛出
 */
public static double parseDouble(String str) throws NumberFormatException {return isBlank(str) ? 0D : Double.parseDouble(str);
}

/**
 * 把字符串转为 long 类型
 *
 * @param str 需要转化的字符串
 * @return long
 * @throws NumberFormatException 字符串格式不正确时抛出
 */
public static long parseLong(String str) throws NumberFormatException {return isBlank(str) ? 0L : Long.parseLong(str);
}

/**
 * 把字符串转为 float 类型
 *
 * @param str 需要转化的字符串
 * @return float
 * @throws NumberFormatException 字符串格式不正确时抛出
 */
public static float parseFloat(String str) throws NumberFormatException {return isBlank(str) ? 0L : Float.parseFloat(str);
}

/**
 * 获取 i18n 字符串
 *
 * @param code
 * @param args
 * @return
 */
public static String getI18NMessage(String code, Object[] args) {//LocaleResolver localLocaleResolver = (LocaleResolver) SpringContextHolder.getBean(LocaleResolver.class);
    //HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
    //Locale locale = localLocaleResolver.resolveLocale(request);
    //return SpringContextHolder.getApplicationContext().getMessage(code, args, locale);
    return "";
}

/**
 * 获得用户远程地址
 *
 * @param request 请求头
 * @return 用户 ip
 */
public static String getRemoteAddr(HttpServletRequest request) {String remoteAddr = request.getHeader("X-Real-IP");
    if (isNotBlank(remoteAddr)) {remoteAddr = request.getHeader("X-Forwarded-For");
    } else if (isNotBlank(remoteAddr)) {remoteAddr = request.getHeader("Proxy-Client-IP");
    } else if (isNotBlank(remoteAddr)) {remoteAddr = request.getHeader("WL-Proxy-Client-IP");
    }
    return remoteAddr != null ? remoteAddr : request.getRemoteAddr();}

/**
 * 驼峰命名法工具
 *
 * @return toCamelCase("hello_world") == "helloWorld"
 * toCapitalizeCamelCase("hello_world") == "HelloWorld"
 * toUnderScoreCase("helloWorld") = "hello_world"
 */
public static String toCamelCase(String s, Locale locale, char split) {if (isBlank(s)) {return "";}

    s = s.toLowerCase(locale);

    StringBuilder sb = new StringBuilder();
    for (char c : s.toCharArray()) {sb.append(c == split ? Character.toUpperCase(c) : c);
    }

    return sb.toString();}

public static String toCamelCase(String s) {return toCamelCase(s, Locale.getDefault(), SEPARATOR);
}

public static String toCamelCase(String s, Locale locale) {return toCamelCase(s, locale, SEPARATOR);
}

public static String toCamelCase(String s, char split) {return toCamelCase(s, Locale.getDefault(), split);
}

public static String toUnderScoreCase(String s, char split) {if (isBlank(s)) {return "";}

    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < s.length(); i++) {char c = s.charAt(i);
        boolean nextUpperCase = (i < (s.length() - 1)) && Character.isUpperCase(s.charAt(i + 1));
        boolean upperCase = (i > 0) && Character.isUpperCase(c);
        sb.append((!upperCase || !nextUpperCase) ? split : "").append(Character.toLowerCase(c));
    }

    return sb.toString();}

public static String toUnderScoreCase(String s) {return toUnderScoreCase(s, SEPARATOR);
}

/**
 * 把字符串转换为 JS 获取对象值的三目运算表达式
 *
 * @param objectString 对象串
 *                     例如:入参:row.user.id/ 返回:!row?'':!row.user?'':!row.user.id?'':row.user.id
 */
public static String toJsGetValueExpression(String objectString) {StringBuilder result = new StringBuilder();
    StringBuilder val = new StringBuilder();
    String[] fileds = split(objectString, ".");
    for (int i = 0; i < fileds.length; i++) {val.append("." + fileds[i]);
        result.append("!" + (val.substring(1)) + "?'':");
    }
    result.append(val.substring(1));
    return result.toString();}
}

参考文章

https://blog.csdn.net/qq_3449…
https://www.runoob.com/java/j…
https://www.cnblogs.com/zhang…
https://blog.csdn.net/sinat_2…
https://www.cnblogs.com/niew/…

微信公众号

Java 技术江湖

如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号【Java 技术江湖】一位阿里 Java 工程师的技术小站,作者黄小斜,专注 Java 相关技术:SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程,偶尔讲点 Docker、ELK,同时也分享技术干货和学习经验,致力于 Java 全栈开发!

Java 工程师必备学习资源: 一些 Java 工程师常用学习资源,关注公众号后,后台回复关键字 “Java” 即可免费无套路获取。

个人公众号:黄小斜

作者是 985 硕士,蚂蚁金服 JAVA 工程师,专注于 JAVA 后端技术栈:SpringBoot、MySQL、分布式、中间件、微服务,同时也懂点投资理财,偶尔讲点算法和计算机理论基础,坚持学习和写作,相信终身学习的力量!

程序员 3T 技术学习资源: 一些程序员学习技术的资源大礼包,关注公众号后,后台回复关键字 “资料” 即可免费无套路获取。

退出移动版