目录
泛型
概述
泛型类
泛型接口
泛型方法
泛型限制类型
泛型中的通配符 ?
作用
注意
java.util.Objects
常用方法:
Java.lang.Math类
常用的方法
java.util.Arrays
数组转字符串toString
填充数组fill
数组元素排序sort
数组的比较equals、deepEquals
数组复制copyOf、copyOfRange
二分查找返回下标binarySearch
数组转List用asList
对其他对象数组进行排序
自定义排序规则
java.math.BigDecimal
概念
常用构造方法
常用方法
java.util.Date
构造方法
方法
java.text.DateFormat
概念
format
parse
java.util.Calendar
概述
静态常量
静态方法
get方法:返回指定日历字段的值。
set方法:设置日历字段的值。
add方法:基于日历的规则实现日期加减。
getActualMaximum方法
getTime方法
setTime方法
java.lang.System
String
概念
字符串的本质
String对象的空值:
字符串的比较:
字符串拼接:
String 的常用API
String 的创建和转换:
获取字符串信息
字符串比较判断
字符串大小写转换:
StringBuilder/StringBuffer
StringBuilder的常用方法:
泛型,即“参数化类型”。就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定 义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
//定义一个泛型类:
public class ClassName{
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
public interface IntercaceName{
T getData();
}
//实现接口时,可以选择指定泛型类型,也可以选择不指定, 如下:
//指定类型:
public class Interface1 implements IntercaceName {
private String text;
@Override
public String getData() {
return text;
}
}
//不指定类型:
public class Interface1 implements IntercaceName {
private T data;
@Override
public T getData() {
return data;
}
}
private static T 方法名(T a, T b) {}
1. 在使用泛型时, 可以指定泛型的限定区域 ,
- 例如: 必须是某某类的子类或 某某接口的实现类,格式:
类型通配符是使用?代替方法具体的类型实参。
- extends Parent> 指定了泛型类型的上届
- super Child> 指定了泛型类型的下届
- > 指定了没有限制的泛型类型
- 提高代码复用率
- 泛型中的类型在使用时指定,不需要强制类型转换(类型安全,编译器会检查类型)
在编译之后程序会采取去泛型化的措施。
也就是说Java中的泛型,只在编译阶段有效。
在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加 类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。
API文档:https://docs.oracle.com/javase/8/docs/api/java/util/Objects.html
W3C中文API文档:https://www.w3cschool.cn/java/java-objects-class.html
jdk1.7后出现.提供静态方法操作对象.
1.public static boolean equals(Object a,Object b):比较对象a和对象b是否相等.
*比较2个对象是否相等,底层依赖对象重写的equals的方法,如果没有重写,则使用Object的equals()
当出现null直接调用equals时会报错,但用Objects不会
objects实现源码:
2.public static
*可以判断对象是否是空对象.限制参数不能为空.
3.public static boolean nonNull(Object obj):判断对象是否为null,不为返回true,否则返回false
4.public static boolean isNull(Object obj):和nonNull()相反.
Math类位于Java.lang包中,包含用于执行基本数学运算的方法!Math类的所有执行方法都是静态方法,可以直接使用类名.方法名调用,如:Math.round()
Math.round() 返回最接近参数的 int,它表示"四舍五入"
Math.rint() 返回最接近参数并等于某一整数的 double 值,如果有2个数同样接近,则返回偶数的那个
Math.floor() 返回最大的(最接近正无穷大)double 值,该值小于等于参数,并等于某个整数
Math.ceil() 返回最小的(最接近负无穷大)double 值,该值大于等于参数,并等于某个整数
Math.cbrt() 返回 double 值的立方根
Math.sqrt() 返回正确舍入的 double 值的正平方根
Math.pow() 返回第一个参数的第二个参数次幂的值
Math.max() 返回两个 double 值中较大的一个
Math.min() 返回两个 double 值中较小的一个
Math.abs() 返回 double,float,int,long 值的绝对值。
Math.random() 返回带正号的 double 值,该值大于等于 0.0 且小于 1.0。
package 算数Math类;
/**
* @author yyx 2017年6月26日
*/
public class MathExample {
public static void main(String[] args) {
sqrt(10);
System.out.println("******************");
pow(2, 3);
System.out.println("******************");
minAndMax(5, 8);
System.out.println("******************");
rint(4.5);
System.out.println("******************");
round(5.64);
System.out.println("******************");
floorAndCeil(2.35);
System.out.println("******************");
cbrt(8);
}
/**
* 返回最接近参数的 int,它表示"四舍五入"
* @param n
*/
public static void round(double n){
System.out.println(Math.round(n));
}
/**
* Math.rint返回最接近参数并等于某一整数的 double 值,如果有2个数同样接近,则返回偶数的那个
* @param n
*/
public static void rint(double n){
System.out.println(Math.rint(n));
}
/**
* Math.floor返回最大的(最接近正无穷大)double 值,该值小于等于参数,并等于某个整数
* Math.ceil返回最小的(最接近负无穷大)double 值,该值大于等于参数,并等于某个整数
* @param n
*/
public static void floorAndCeil(double n) {
System.out.println(Math.floor(n));
System.out.println(Math.ceil(n));
}
/**
* 返回 double 值的立方根
*
* @param n
*/
public static void cbrt(double n) {
System.out.println(Math.cbrt(n));
}
/**
* 返回正确舍入的 double 值的正平方根
*
* @param n
*/
public static void sqrt(double n) {
System.out.println(Math.sqrt(n));
}
/**
* 返回第一个参数的第二个参数次幂的值
*
* @param m
* @param n
*/
public static void pow(double m, double n) {
System.out.println(Math.pow(m, n));
}
/**
* max返回两个 double 值中较大的一个 min返回两个 double 值中较小的一个
*
* @param m
* @param n
*/
public static void minAndMax(double m, double n) {
System.out.println(Math.min(m, n));
System.out.println(Math.max(m, n));
}
}
运行结果:
3.1622776601683795
******************
8.0
******************
5.0
8.0
******************
4.0
******************
6
******************
2.0
3.0
******************
2.0
int[] array = new int[]{1, 2, 3};
System.out.println(Arrays.toString(array)); //[1, 2, 3]
如果是一维数组,toString方法可以很好的适用。但遇到多维数组时,需要使用deepToString把数组完全转成字符串。
int[][] deepArray = new int[][]{{1, 3},{2, 4}};
System.out.println(Arrays.toString(deepArray)); //[[I@1540e19d, [I@677327b6]
System.out.println(Arrays.deepToString(deepArray)); //[[1, 3], [2, 4]]
array = new int[5];
Arrays.fill(array, 2);
System.out.println(Arrays.toString(array)); //[2, 2, 2, 2, 2]
array = new int[5];
Arrays.fill(array, 1, 4, 2); //部分填充
System.out.println(Arrays.toString(array));//[0, 2, 2, 2, 0]
array = new int[]{3, 10, 4, 0, 2};
Arrays.sort(array);
System.out.println(Arrays.toString(array)); //[0, 2, 3, 4, 10]
array = new int[]{3, 10, 4, 0, 2};
Arrays.parallelSort(array); //和sort相比是这个是并行的
System.out.println(Arrays.toString(array)); //[0, 2, 3, 4, 10]
array = new int[]{3, 10, 4, 0, 2};
Arrays.sort(array, 0, 4); //部分排序
System.out.println(Arrays.toString(array)); //[0, 3, 4, 10, 2]
array = new int[]{1, 2, 3};
int[] array2 = new int[]{1, 2, 3};
System.out.println(Arrays.equals(array, array2)); //true
和toString方法一样,equals方法遇到多维数组时也会出现问题。
int[][] deepArray1 = new int[][]{{1, 3},{2, 4}};
int[][] deepArray2 = new int[][]{{1, 3},{2, 4}};
System.out.println(Arrays.equals(deepArray1, deepArray2)); //false
System.out.println(Arrays.deepEquals(deepArray1, deepArray2)); //true
deepEquals用于判定两个指定数组彼此是否深层相等,此方法适用于任意深度的嵌套数组。
equals用于判定两个数组是否相等,如果两个数组以相同顺序包含相同元素,则返回true。
如果两个数组使用equals返回true,则使用deepEquals必定也返回true,也就是说在比较的两个数组均为一维数组的前提下,equals和deepEquals的比较结果没有差别。
array = new int[]{3, 10, 4, 0, 2};
int[] arrayCopy = Arrays.copyOf(array, 3);
System.out.println(Arrays.toString(arrayCopy)); //[3, 10, 4]
arrayCopy = Arrays.copyOf(array, 7);
System.out.println(Arrays.toString(arrayCopy)); //[3, 10, 4, 0, 2, 0, 0], 多出的长度补0
arrayCopy = Arrays.copyOfRange(array, 1, 4);//从下标1开始到下标4之前即下标1、2、3
System.out.println(Arrays.toString(arrayCopy)); //[10, 4, 0]
array = new int[]{0, 3, 4, 10, 20};
System.out.println(Arrays.binarySearch(array, 10)); //3, array必须是排序的,否则得到的是错误的结果
System.out.println(Arrays.binarySearch(array, 6)); //-4, 找不到的值,从-1开始,6如果存在下标是3, 所以返回-4
System.out.println(Arrays.binarySearch(array, 2, 5, 10)); //3, 返回的还是全局的下标值。
int array = new int[]{3, 10, 4, 0, 2};
System.out.println(Arrays.asList(array).contains(3)); //false
Integer arr[] = new Integer[]{3, 10, 4, 0, 2};
System.out.println(Arrays.asList(arr).contains(3)); //true
这里是比较有意思的地方,实际上拆开来看是这样的
int array = new int[]{3, 10, 4, 0, 2};
List ints = Arrays.asList(array);
Integer arr[] = new Integer[]{3, 10, 4, 0, 2};
List integers = Arrays.asList(arr);
现在就知道区别了,原始数据类型int的数组调用asList之后得到的List只有一个元素,这个元素就是元素类型的数组。而封装类Integer数组调用asList是把数组中每个元素加到了List中。对数组元素采用指定的方法计算
array = new int[]{3, 10, 4, 0, 2};
Arrays.parallelPrefix(array, (x,y)->(x+y)); //[3, 13, 17, 17, 19]
System.out.println(Arrays.toString(array));
array = new int[]{3, 10, 4, 0, 2};
Arrays.parallelSetAll(array, (x)->(x*x)); //[0, 1, 4, 9, 16]
System.out.println(Arrays.toString(array));
Arrays.setAll(array, (x)->(x%3));
System.out.println(Arrays.toString(array)); //[0, 1, 2, 0, 1], 与parallelSetAll相比只是不并行
一个对象数组,排序算法需要重复比较数组中的元素。不同的类比较元素的规则是不同的,但是排序算法只应该调用类提供的比较方法,只要所有的类就比较的时候提供的方法达成一致,那么排序算法就能开始工作。这个在排序时对象之间进行比较方法就可以是一个接口,所有需要比较的对象继承这个接口并且实现比较的方法,就可以对这些对象进行排序。
如果一个类想启用对象排序,那么就应该实现Comparable接口。
public class Test{
public static void main(String[] args){
Employee[] employees = new Employee[3];
employees[0] = new Employee(20);
employees[1] = new Employee(10);
employees[2] = new Employee(30);
Arrays.sort(employees);
for(Employee e : employees){
System.out.println(e); //Employee{id=10} Employee{id=20} Employee{id=30}
}
}
static class Employee implements Comparable{
private int id;
public Employee(int id){this.id = id;}
@Override
public int compareTo(Employee o) {
return this.id - o.id;
}
@Override
public String toString() {
return "Employee{" + "id=" + id + '}';
}
}
}
String[] names = {"tom", "alice", "fred"};
Arrays.sort(names);
System.out.println(Arrays.toString(names));
假如想根据字符串的长度而不是根据字典顺序对字符串排序,但是String类我们是无法修改的。上面的代码对String数组进行排序,只能按照字典顺序对String数组进行排序。
Arrays.sort方法和Collections.sort方法都提供了一个可以接收Comparator实例作为第二个参数的版本。
要按照长度比较字符串,定义一个实现Comparator的类。
public class LengthComparator implements Comparator {
@Override
public int compare(String o1, String o2) {
return o1.length() - o2.length();
}
public static void main(String[] args){
String[] names = {"tom", "alice", "fred"};
Arrays.sort(names, new LengthComparator());
System.out.println(Arrays.toString(names));
}
}
像Comparator、Runable等这=一些接口有一个特点就是只有一个抽象方法(其他的都是static或者default的方法),比如继承Comparator接口只需要重写compare方法,继承Runnable接口只需要重写run方法,这种类型的接口被称为函数式接口,可以被lambda表达式所代替。
比如上面根据字符串的长度进行排序的代码,Arrays.sort的第二个参数是需要实现了Comparator接口的实例,用lambda表达是就可以写成这样:
String[] names = {"tom", "alice", "fred"};
Comparator comp = (first, second) -> first.length() - second.length();
Arrays.sort(names, comp);
或者更加简单点
String[] names = {"tom", "alice", "fred"};
Arrays.sort(names, (first, second) -> first.length() - second.length());
Arrays.sort方法的第二个参数变量接受一个实现了Comparator接口的类的实例,调用该对象的compare方法会执行lambda表达式中的代码,所以这也就是为什么接口只有一个抽象方法的时候可以用lambda表达式代替。
https://www.sxt.cn/math/java_math_bigdecimal.html
java.math.BigDecimal 类提供用于算术,刻度操作,舍入,比较,哈希算法和格式转换操作。
toString()方法提供 BigDecimal 的规范表示形式。
BigDecimal(double val)
BigDecimal(String val)
除法运算:
System.out.println(new BigDecimal(153.5).divide(new BigDecimal(100), 2, BigDecimal.ROUND_HALF_UP));
new BigDecimal(100)除数,2精确的位数,BigDecimal.ROUND_HALF_UP:舍入模式
加法运算:
System.out.println(new BigDecimal("2.005").add(new BigDecimal("0.03")));
减法运算:
System.out.println(new BigDecimal("2.00").subtract(new BigDecimal("1.10")));
乘法运算:
System.out.println(new BigDecimal("2.05").multiply(new BigDecimal("10")));
Date
类表示特定的时刻,精度为毫秒。在JDK 1.1之前,
Date
类还有两个附加功能。 它允许将日期解释为年,月,日,小时,分钟和秒值。 它还允许格式化和解析日期字符串。 不幸的是,这些功能的API不适合国际化。 从JDK 1.1开始,Calendar
类应该用于在日期和时间字段之间进行转换,而DateFormat
类应该用于格式化和解析日期字符串。 不推荐使用Date
中的相应方法。
//创造一个当前时间
Date date = new Date();
DateFormat
是日期/时间格式化子类的抽象类,它以与语言无关的方式格式化和分析日期或时间。日期/时间格式化子类(例如
SimpleDateFormat
)允许格式化(即,日期→文本),解析(文本“日期”)和规范化。日期表示为
Date
对象或自1970年1月1日00:00:00 GMT以来的毫秒数。
public final String format(Date date)
将一个 Date 格式化为日期/时间字符串。
参数:
date
- 要格式化为时间字符串的时间值。返回:
已格式化的时间字符串。
public Date parse(String text,ParsePosition pos)//text格式要与对象中定义的时间格式要一样 解析字符串的文本,生成 Date。如果发生错误,返回 null。
此方法试图解析从
pos
给定的索引处开始的文本。如果解析成功,则将pos
的索引更新为所用最后一个字符后面的索引(不必对直到字符串结尾的所有字符进行解析),并返回解析得到的日期。更新后的pos
可以用来指示下次调用此方法的起始点。如果发生错误,则不更改pos
的索引,并将pos
的错误索引设置为发生错误处的字符索引,并且返回 null。指定者:
类
DateFormat
中的parse
参数:
text
- 应该解析其中一部分的String
。
pos
- 具有以上所述的索引和错误索引信息的ParsePosition
对象。返回:从字符串进行解析的
Date
。如果发生错误,则返回 null。抛出:
NullPointerException
- 如果text
或pos
为 null。
- java.util.Calendar类是一个抽象类,是java日期处理的核心类之一。Calendar类为操作日历字段,及其与特定瞬间之间的转换提供了方法。日历字段包含YEAR、MONTH、DAY_OF_MONTH、HOUR等,它们都是Calendar类的静态常量。
//指示年
Calendar.YEAR // 1
//指示月份
Calendar.MONTH // 2
//指示当前时间为多少号(日历式的多少号)
Calendar.DATE // 5
//指示小时(12小时制)
Calendar.HOUR // 10
//指示小时(24小时制)
Calendar.HOUR_OF_DAY // 11
//指示分钟数
Calendar.MINUTE // 12
//指示秒数
Calendar.SECOND // 13
//指示毫秒
Calendar.MILLISECOND // 14
//当前时间是所在当前月的第几个星期(日历式的第几周)
Calendar.WEEK_OF_MONTH //4
//当前时间是所在当前年的第几个星期
Calendar.WEEK_OF_YEAR //3
//当前时间是所在当前月的第几个星期,以月份天数为标准,一个月的1号为第一周,8号为第二周
Calendar.DAY_OF_WEEK_IN_MONTH //8
//一周7天当中,当前时间是星期几
Calendar.DAY_OF_WEEK //7
//指示一年中的第几天
Calendar.DAY_OF_YEAR //6
//指示一月中的第几天,结果等同于Calendar.DATE
Calendar.DAY_OF_MONTH //5
//指示上午还是下午
Calendar.AM_PM //9
//周天
Calendar.SUNDAY //1
//周一
Calendar.MONDAY //2
//周二
Calendar.TUESDAY //3
//周三
Calendar.WEDNESDAY //4
//周四
Calendar.THURSDAY //5
//周五
Calendar.FRIDAY //6
//周六
Calendar.SATURDAY //7
get方法:返回指定日历字段的值。
//获得年、月、日、时、分、秒、毫秒
ca.get(Calendar.YEAR);
ca.get(Calendar.MONTH);
ca.get(Calendar.DATE);
ca.get(Calendar.HOUR_OF_DAY);
ca.get(Calendar.MINUTE);
ca.get(Calendar.SECOND);
ca.get(Calendar.MILLISECOND);
//当前时间是所在当前月的第几个星期(日历式的第几周)
ca.get(Calendar.WEEK_OF_MONTH);
//当前时间是所在当前年的第几个星期(日历式的第几周)
ca.get(Calendar.WEEK_OF_YEAR));
//当前时间是所在当前月的第几个星期,以月份天数为标准,一个月的1号为第一周,8号为第二周
ca.get(Calendar.DAY_OF_WEEK_IN_MONTH);
//一周7天当中,当前时间是星期几, 返回结果为1-7
ca.get(Calendar.DAY_OF_WEEK);
//一年中的第几天
ca.get(Calendar.DAY_OF_YEAR);
//判断当前时间是AM,还是PM,若是AM返回结果为0,若是PM返回结果为1
ca.get(Calendar.AM_PM);
set方法:设置日历字段的值。
设置年、月、日、时、分、秒、毫秒
月份的正常值范围为0-11,0表示一月,以此类推。日期的正常值范围为1-31,结尾数字视月份而定。
//2019-01-01 00:00:00
Calendar ca = Calendar.getInstance();
ca.set(Calendar.YEAR,2019);
ca.set(Calendar.MONTH,0);
ca.set(Calendar.DATE,1);
ca.set(Calendar.HOUR_OF_DAY,0);
ca.set(Calendar.MINUTE,0);
ca.set(Calendar.SECOND,0);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(ca.getTime()));
所有日历字段都有正常的值范围,但是它们也支持不正常的值范围,以正常值范围为基点,设置向前向后的值都可以。例如所有日历字段都可以设置负值(年份不考虑)。
设置:2019年00月 --> 实际:2019年01月
设置:2019年-1月 --> 实际:2018年12月, 以此类推
设置:2月00日 --> 实际:1月31日
设置:2月-1日 --> 实际:1月30日, 以此类推
设置:1小时-1分钟 --> 实际:0小时59分钟
设置:1分钟-1秒钟 --> 实际:0分钟59秒
//2018-12-31
ca.set(Calendar.YEAR,2019);
ca.set(Calendar.MONTH,0);
ca.set(Calendar.DATE,0); //设置为0天,指向上月的最后一天
将Calendar日期对象指向当前周的某一天
//将日历对象指向当前周的周天
Calendar ca = Calendar.getInstance();
ca.set(Calendar.DAY_OF_WEEK,Calendar.SUNDAY);//第二个参数的取值范围是表示周一到周天的七个静态常量
add方法:基于日历的规则实现日期加减。
为当前Calendar日期对象加上或减去指定的时间量。参数amount表示日期的增量,可以为负值。
Calendar ca = Calendar.getInstance();
ca.set(Calendar.YEAR,2019);
//当前日历年份加一
ca.add(Calendar.YEAR,1);
System.out.println(ca.get(Calendar.YEAR)); //2020
//当前日历年份减一
ca.add(Calendar.YEAR,-1);
System.out.println(ca.get(Calendar.YEAR)); //2019
getActualMaximum方法
返回指定日历字段可能拥有的实际最大值。
Calendar ca = Calendar.getInstance();
//返回当前月最大天数
System.out.println(ca.getActualMaximum(Calendar.DAY_OF_MONTH));
System.out.println(ca.getActualMaximum(Calendar.DATE));
//返回23
System.out.println(ca.getActualMaximum(Calendar.HOUR_OF_DAY));
//返回11
System.out.println(ca.getActualMaximum(Calendar.HOUR));
//返回当前月有多少周
System.out.println(ca.getActualMaximum(Calendar.WEEK_OF_MONTH));
//返回当前年度最大天数
System.out.println(ca.getActualMaximum(Calendar.DAY_OF_YEAR));
getTime方法
将Calendar日期对象转换为Date对象。
Calendar ca = Calendar.getInstance();
Date d = ca.getTime();
setTime方法
将Date对象表示的时间值设置给Calendar日期对象。
Calendar ca = Calendar.getInstance();
ca.setTime(new Date());
引用:https://blog.csdn.net/weixin_42472040/article/details/100108434
System类构造函数由private修饰,不可以被实例化,加载时调用static代码块。
System类提供了标准输入输出流,错误输出流,获取外部属性和系统环境的方法,加载类库和文件的方法,快速copy数组的方法;其中out和err的类型是PrintStream
引用:https://www.cnblogs.com/aben-blog/p/8685818.html
什么是字符串?如果直接按照字面意思来理解就是多个字符连接起来组合成的字符序列。为了更好的理解以上的理论,我们先来解释下字符序列,字符序列:把多个字符按照一定的顺序排列起来;而字符序列就是作为字符串的内容而存在的。所以可以把字符串理解为:把多个字符按照一定的顺序排列起来而构成的排列组合。
字符串的分类,字符串分为可变的字符串和不可变的字符串两种;这里的不可变与可变指的是字符串的对象还是不是同一个,会不会因为字符串对象内容的改变而创建新的对象。
- 不可变的字符串:当字符串对象创建完毕之后,该对象的内容(上述的字符序列)是不能改变的,一旦内容改变就会创建一个新的字符串对象;Java中的String类的对象就是不可变的。
- 可变的字符串:StringBuilder类和StringBuffer类的对象就是可变的;当对象创建完毕之后,该对象的内容发生改变时不会创建新的对象,也就是说对象的内容可以发生改变,当对象的内容发生改变时,对象保持不变,还是同一个。
String 类
String类表示不可变的字符串,当前String类对象创建完毕之后,该对象的内容(字符序列)是不变的,因为内容一旦改变就会创建一个一个新的对象。
String对象的创建:
方式一:通过字面量赋值创建,String s1 = “laofu”; 需要注意这里是双引号:“”,区别与字符char类型的单引号:‘’;方式二:通过构造器创建, String s2 = new String(“laofu”);以上两种创建方式的对象在JVM中又是如何分布的呢? 分别有什么区别呢?
下面就来一一解密:
方式一和方式二在JVM中又是如何分布?
上图中的常量池:用于存储常量的地方内存区域,位于方法区中。常量池又分为编译常量池和运行常量池两种:
编译常量池:当把字节码加载斤JVM的时候,其中存储的是字节码的相关信息(如:行号等)。运行常量池:其中存储的是代码中的常量数据。方式一和方式二有何不同?
方式一:String s1 = “laofu”; 有可能只创建一个String对象,也有可能创建不创建String对象;如果在常量池中已经存在”laofu”,那么对象s1会直接引用,不会创建新的String对象;否则,会先在常量池先创建常量”laofu”的内存空间,然后再引用。
方式二:String s2 = new String(“laofu”); 最多会创建两个String对象,最少创建一个String对象。可使用new关键字创建对象是会在堆空间创建内存区域,这是第一个对象;然后对象中的字符串字面量可能会创建第二个对象,而第二个对象如方式一中所描述的那样,是有可能会不被创建的,所以至少创建一个String个对象。
字符串在底层其实就是char[],char表示一个字符,比如:
String str = "laofu"; 等价于 char[] cs = new char[]{'l','a','o','f','u'};
对象引用为空,即:String s1 = null; 此时s1没有初始化,也在JVM中没有分配内存空间。对象内容为空字符串, 比如:String s2 = “”; 此时对象s2已经初始化,值为“”,JVM已经为其分配内存空间。
使用”==”号:用于比较对象引用的内存地址是否相同。使用equals方法:在Object类中和”==”号相同,但在自定义类中,建议覆盖equals方法去实现比较自己内容的细节;由于String类覆盖已经覆盖了equals方法,所以其比较的是字符内容。
所以可以这样来判断字符串非空:
对象引用不能为空:s1 != null;,字符内容不能为空字符串(“”):“”.equals(s1);如果上述两个条件都满足,说明字符串确实为空!
Java中的字符串可以通过是“+”实现拼接,那么代码中字符串拼接在JVM中又是如何处理的呢?我们通过一个例子说明:通过比较拼接字符串代码编译前后的代码来查看JVM对字符串拼接的处理。
通过上述例子不难发现,JVM会对字符串拼接做一些优化操作,如果字符串字面量之间的拼接,无论有多少个字符串,JVM都会一样的处理;如果是对象之间拼接,或者是对象和字面量之间的拼接,亦或是方法执行结果参与拼接,String内部会使用StringBuilder先来获取对象的值,然后使用append方法来执行拼接。由此可以总结得出:
- 使用字符串字面量创建的字符串,也就是单独使用""引号创建的字符串都是直接量,在编译期就会将其存储到常量池中;
- 使用new String("")创建的对象会存储到堆内存中,在运行期才创建;
- 使用只包含直接量的字符串连接符如"aa" + "bb"创建的也是直接量,这样的字符串在编译期就能确定,所以也会存储到常量池中;
- 使用包含String直接量的字符串表达式(如"aa" + s1)创建的对象是运行期才创建的,对象存储在堆中,因为其底层是创新了StringBuilder对象来实现拼接的;
- 无论是使用变量,还是调用方法来连接字符串,都只能在运行期才能确定变量的值和方法的返回值,不存在编译优化操作。
调用方法的字符串就是当前字符串
先来分别使用String/StringBuilder/StringBuffer来拼接30000次字符串,对比各自损耗的时间,经过测试发现:
String做字符串拼接的时候,耗时最高,性能极低,原因是String内容是不可变的,每次内容改变都会在内存中创建新的对象。
性能最好的是StringBuilder,其次是StringBuffer,最后是String。StringBuilder和StringBuffer区别并不是很大,也有可能是测试次数还不够吧。感兴趣的小伙伴可以增加拼接次数来看看。代码很简单,就不展示出来了。
所以在开发中拼接字符串时,优先使用StringBuffer/StringBuilder,不到万不得已,不要轻易使用String。
StringBuilder以及StringBuffer的区别
StringBuffer和StringBuilder都表示可变的字符串,两种’的功能方法都是相同的。但唯一的区别:
此时该数组只能存储16个字符,如果超过了16个字符,会自动扩容(创建长度更大的数组,再把之前的数组拷贝到新数组),此时性能极低;如果事先知道大概需要存储多少字符,可以通过构造器来设置字符的初始值:
// 创建一个长度为80的char数组.new StringBuilder(80);