让## string和包装类
string基础
字符串广泛运用在java编程中,在java中字符串也属于对象,java提供了String类来
创建和操作字符串。
java中的字符串不是内置的类型,而是在标准java类库中提供一个自定义类
@创建字符串
创建字符串最简单的方式如下:
String greeting =“菜鸟教程”;
在代码中遇到字符串常量时,这里的值是"菜鸟教程",编译器会自动使用该值创建一个String对象。
和其他对象一样,可以使用关键字和创造方法来创建String对象。
和其他对象一样,可以使用关键字和构造方法来创建String对象。
String类有11种构造方法,这些方法提供不同的参数来初始字符串,比如提供一个字符数组参数:
@String Demo.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”)
基本方法
@取长度:
int length();方法
该方法用于返回字符串对象的长度
@子串:
String类的substring方法可以从一个较大的字符串中提取出一个子串
例如:
String greeting = 'Hello";
String s = greeting.substring(0,3);
创建一个长度为3的子串(Hel)
类似于C_C++,Java字符串中的代码单元和代码点从0开始计数
substring(a,b)获取从a位置到b位置的子串
substring (a)获取从a位置到结束的字符串
由工作机制可以很容易得出子串长度b-a;
@拼接
与大多数程序设计语言一样,jvav允许使用+号拼接两个字符串
String expletive = “Expletive”;
String PG13 = “deleted”;
String message = expletive +PG13;
上述代码的结果是将“Expletivedeleted”赋值给message(即无缝衔接)
当将一个字符串和非字符串对象试图拼接在一起时,后者会被转换为字符串
(任何一个对象都可以转换为字符串形式,在后面学习中会了解到)
例如:
int age = 13;
String rating = "PG"+age;
将rating设置为“PG13”
这种特性常被用在输出语句上,例如:
System.out.println(“the answer is ” + answer);
因为is后设置了一个空格,实际answer的数值是在空格之后开始的
如果需要把多个字符串放在一起,用一个定界符分离,则可以使用静态join方法
String all = String join ("/",“S”,“M”,“L”,“XL”);
(S/M/L/XL)(用第一个参数作为定界符?
java11中提供了一个repeat方法:
String repeated = “java”.repeat(3);//repeated is “javajavajava”
(实际上就是一个重复n次的复读机。。。)
@查找
indexOf()/lastIndexOf()
用法:
对于“ABCDEABCDE“”
字符串.indexOf(“A”)//查找子串"A"第一次出现的位置
indexOf(“AB”)//查找子串“AB”第一次出现的位置
indexOf(“A”,4)//从indexOf为4的位置开始找第一次出现的“A”
lastIndexOf则相反,是查找最后一次出现的位置
//后面的参数可以表示起始位置,而前面的参数若为int类型则·应该看成是对应的字符
!!!拓展:
@
- 字符串与byte数组之间的相互转换
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");
//使用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);
}
}
/*
*
*
*/@重点:字符串“相等”
==运算符和equals之间的区别
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 地址不同
}
}
一般判断字符串“内容”是否相等的话不用==而用equals()
原因在于:==对于字符串相等的判断实际上是对地址是否相等的判断,但如上
例所展示的,同样内容的字符串完全有可能存放在不同的地址上!
内存:
由上图可知,实际上只有创建字符串时在常量池储存的字符字面量是共享地址的,而
new ,+ ,substring等操作形成的字符串并不共享。
【所以说String对象是不可变的,所谓的“变”其实是创建了新的对象或是新建的指向常量池的引用!!!】
回顾刚刚的连接:
@Test 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
总结:字符串加法对常量编译期间计算,对有变量表达式编译后运行时计算。
空串与null串
空串是长度为0的字符串“ ”
检测一个字符串是否为空的代码为:
if(str.length()==0)
或
if (str.equals(“ ”))
空串是一个java对象,有自己的长度(0)和内容(空白符)。
String变量还可以存放一个特殊的值null,表示目前没有任何对象与该变量关联
if(str==null)
(null串表示没有一个实际的对象,所以也无法调用相应的方法,调用时会报错)
有时要检测一个字符串既不是空串也不是null串,这时候就要使用以下语句:
if(str !=null&&str.length()!=0)
码点与代码单元
java字符串是由char值序列完成的,该数据了理性是一个采用UTF-16编码表示Unicode码点的代码单元。最常用的Unicode字符使用一个代码单元就可以表示,而辅助字符需要用一对代码单元表示。
length方法将返回采用UTF-16编码表示的字符串需要的代码单元数量
例如:
String greeting=“Hello”;
int n= greeting.length();//5
调用s.charAt(n)将返回n位置处的代码单元,n介于0和s.length()-1之间。
例如:
char first = greeting.charAt(0);//first is 'H'
char last = greeting.charAt(4);//last is 'o'
要想得到第i个码点,可以使用以下代码:
int index = greeting.offsetByCodePoints(0,i);
int cp = greeting.codePointAt(index);
因为使用Utf-16编码字符表示的一些码点实际占用两个代码单元
(增补类型使用两个char类型表示,第一个叫“高代理部分”,第二个“低代理部分”,
遇到高代理部分,向后读一个char类型,如果读出来是低代理部分,则将它们看成一个整体,否则返回第一个字符,将它们分别处理 )
如果想要遍历一个字符串,并且依次查看每个码点,可以使用以下语句:
int cp = sentence.codePointAt(i);
if(Character.isSupplementaryCodePoint(cp))i+=2;
如果想反向遍历,应该使用以下语句:
i--;
if(Character.isSurrogate(sentence.charAt(i)))i--;
int cp = sentence.codePointAt(i);
显然,这是比较麻烦的一个流程,更简单点的方法是使用codePoints方法,它会生成一个
int值的“流”,每个int值对应一个码点。(流将在后面进行讨论)可以将它转换为一个数组,再完成遍历:
int 【】codePoints = str.codePoints().toArray();
反之,要将一个码点数组转换为一个字符串,则可以使用构造器
String str = new String(codePoints,0,codePoints.length);
虚拟机不一定把字符串实现为代码单元序列,在java9中,只包含单字节代码单元的字符串使用byte数组实现,而所有其他字符串使用char数组。
String 、String builder和String buffer的区别
String类是java中Immutable类的典型实现,除了hash属性其他都被声明为final(为了
其不可变性),这导致拼接的时候会出现很多的无用中间对象,如果频繁的这么操作会对性能有影响
StringBuffer类就是为了解决大量拼接字符串时中间对象问题而提供的类,提供
add和append方法,可以将字符串添加到已有序列的末尾或者指定位置,它的本质是一个线程安全的可以修改的字符序列,把所有修改数据的地方都加上了synchronized。但是保证线程安全会付出性能的代价
很多时候我们不需要线程安全来拼接字符串,于是出现了StringBuilder类
StringBuilder类和StringBuffer类实际上没有什么区别,只是将保证线程安全的部分去掉了而已。减少了开销
StringBuilder和StringBuffer都是继承自AbstractStringBuilder,底层都是利用可修改的char数组(JDK9之后是byte数组)
- 在字符串不经常发生变化的业务场景优先使用String(代码清晰整洁)。如常量的声明,少量的字符串操作(拼接,删除等)。
- 在单线程情况下,如有大量的字符串操作情况,应该使用StringBuilder来操作字符串。尽量不使用String“+”,以免产生大量的中间对象,耗费空间且效率低下(新建对象,回收对象花费大量时间)(如JSON的封装)
- 多线程情况下,如有大量的字符串操作情况,则应该使用StringBuffer。如HTTP参数解析和封装等。
String类源码分析
@intern
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 str1 = "a";
String str2 = "b";
String str3 = "ab";
String str4 = str1 + str2;
String str5 = new String("ab");
String str6 = "a"+"b";
System.out.println(str5.equals(str3));
System.out.println(str5 == str3);
System.out.println(str5.intern() == str3);
System.out.println(str5.intern() == str4);
System.out.println(str5.intern()==str6);
结果:
true值相同
false new出来的不是字面量,5不和3共引用
true 5intern后接受返回的“ab”位置引用,和3共引用
false 字符串变量相加实际上是新建对象,和静态文本的相加不同
true 字面量相加的方式会进入常量池
总结下来就是new相加和带有一个变量的字符串相加不会加入字符串池
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;
}
//从起点开始的内容比较
StringBuilder和StringBuffer
底层是继承父类的可变字符数组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)
ensureCapacityIntery (minimumCapacity);
}
private void ensureCapacityInternal(int minimumCapacity){
//overflow-conscious code
if (minimumCapacity - value.length > 0){
value = Arrays.copyOf(value,newCapacity(minimumCapacity));
}
}
如果新的字符串长度超过了value数组长度则进行扩容,扩容后的长度一般为原来的2倍加2,假如扩容后长度超过了jvm支持的最大数组长度MAX_ARRAY_SIZE。
如果新字符串长度超过int最大值,则抛出异常,否则直接使用数组最大长度作为新数组的长度
private int hugeCapacity(int minCapacity){
if (Integer.MAX_VALUE - minCapacity < 0)
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()在拷贝数组时是可靠高效的–如果发现不够可靠高效的
情况,反馈bug后很快会获得改进
java.lang.System.arraycopy()方法在java代码里声明为一个native方法。所以最native
的实现方式就是通过JNI调用JVM里的native代码来实现
String的不可变性 关于String的不可变性:
什么是不可变?
给一个已有字符串赋值,不是在原内存地址上改变,而是重新指向一个新对象,新地址
String和JVM的关系
下面我们了解下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存在java栈中)。如果字符串不在常量池中,就会实例化该字符串并且放到
常量池中,并将此字符串对象的地址赋值给引用s(引用s在Java栈中)。
String为什么不可变
public final class String implementsjava.io.Serializable,
Comparable<String>,CharSequernce{
/*String本质上是个char数组,而且用final关键字修饰*/
private final char value[];
}
!分析
首先String类用final关键字修饰,这说明String不可继承。再看下面,String类的主力成员
字段value是一个char【】数组,而且用final修饰。
final修饰的字段创建以后就不可改变。但是虽然value本身是不可变,也只是数组的引用地址不可变。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是不可变,
//关键是在后面的String方法中没有操作Array中的元素,没有暴露内部成员字段。
//private final char value [ ] 这一句里,private私有访问权限的作用都比final大。
//设计者还很小心的将整个String设置为禁止继承,所以String不可变的原因其实是
//底层的实现,而不是那个final。考验的是工程师构造数据类型,封装数据的实力
@不可变的好处?
为了安全。。。。
总结以下String的不可变性
- 首先final修饰的类只保证不能被继承,并且该类的对象在堆内存中的地址不会被改变。
- 但是持有String对象的引用本身是可以改变的,比如可以指向其他的对象
- 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.prinltn(f);
//改变实例中的值并不改变内存地址
Fi ff = f;
//让引用指向新的Fi对象,原来的f对象由新的引用ff持有。
//引用的指向改变也不会改变原来对象的地址
f = new Fi();
System.out.println(f);
System.out.println(ff);
}
这里的对f.a的修改可以理解为char[0] = 'a’的操作。只改变数据值,不改变内存值。
拓展:
(再次提醒,本篇大部分来自b站up,公众号黄小斜(java技术江湖),使用请注意版权意识)
String工具类
基于apache-commons组件写的部分常用方法:
MAVEN依赖
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lan3</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();
}
}
如上