目录
学习笔记
String类特点
在实际开发之中,没有一个项目不使用String,String类是开发之中至关重要的组成部分,在Java程序中,所有的字符串通过“”进行定义,同时使用“+”进行字符串的连接处理。String类也有一些自身的特点。
String简介
字符串严格意义上讲并不能算是一个基本数据类型,没有任何一种语言提供字符串数据类型的,而Java中为了方便开发者项目的书写,所以利用其JVM的支持能够使用简单的String类,并且可以像基本数据类型一样,进行赋值处理,如下:
public class StringDemo{
public static void main(String[] args){
String str = "57" ;
System.out.println(str);
}
}
但是需要注意的是,String的类中能保存字符串的原因是其中保存了数组,即String中字符串的每一个数据都是保存在数组之中。
提示:观察String源代码的实现。源代码目录:D:\java\jdk-10\lib\src.zip
JDK1.9之后String类之中的数组类型采用了byte类型,原来所谓的字符串是对数组的一种特殊的包装应用,同时也应该清楚既然包装的是数组,所以字符串内容是无法进行改变的,之后解释。
另外需要注意,在String类中,除了可以直接赋值形式为对象直接赋值以外,也可以通过按照传统的方式利用构造方法进行赋值处理:public String(String str) ;
范例:利用构造方法进行实例化
public class StringDemo{
public static void main(String[] args){
String str = new String("57") ;
System.out.println(str);
}
}
String本身包装的是一个数组,并且有两种对象的实例化形式:直接赋值,构造方法实例化。区别之后解释。
字符串比较
判断两个int型变量是否相等,使用“==”,这个是由程序直接提供的比较相等的运算符。但是String类也涉及相等的判断,也可以使用“==”,只不过判断的不准确而已,下面通过代码观察:
范例:字符串的相等判断
public class StringDemo{
public static void main(String[] args){
String strA = new String("57") ;
String strB = "57" ;
System.out.println(strA == strB); \\false
}
}
此时比较并没有成功,所以发现虽然两个字符串对象的内容是相同的,但是“==”也无法得到准确的相等判断。这种情况下,要想实现准确的相等判断,可以使用String类中提供的方法;
- public boolean equals(String str)
范例:使用equals()实现字符串的比较
public class StringDemo{
public static void main(String[] args){
String strA = new String("57") ;
String strB = "57" ;
System.out.println(strA.equals(strB));
}
}
结果正确,可以实现对象的内容比较。
面试题:请解释“==”与“equals ”的区别。
- “==”:进行的是数值比较,如果用于对象比较时,比较的是两个内存的地址;
- “equals” : 是类提供的一种比较方法,可以进行字符串内容的比较;
字符串常量
下面需要给出一个明确的定义,在程序开发之中任何一个整数都是int,任意的小数都是double,但是对于字符串而言,首先明确程序之中没有提供字符串这种数据类型,那么可以提供的只是String类,所以使用“”定义的是描述一个String类的匿名对象,
范例:String str = "mldn" ;
所谓的直接赋值是将一个匿名的引用对象设置一个具体的名字。
范例:观察匿名对象的存在
public class StringDemo{
public static void main(String[] args){
String strB = "57" ;
System.out.println("57".equals(strB));
}
}
可以发现字符串常量可以明确的调用equals()实现对象相等的一个判断,程序中的确没有字符串常量,有的只是String类的匿名对象。
关于对象相等判断的小技巧:
如果某些数据是由用户输入,并且要求这些数据为一指定内容的情况下,建议将字符串产生的常量写在前面。如下:
接受用户输入数据的调用方法时,输入为空:
public class StringDemo{
public static void main(String[] args){
String input = null ; // 用户输入的内容
System.out.println(input.equals("57"));
}
}
这个会产生错误;
换个方式,将字符串的常量写在前面:
public class StringDemo{
public static void main(String[] args){
String input = null ; // 用户输入的内容
System.out.println("57".equals(input));
}
}
equals()方法中具有自动回避null的判断,所以将字符串的常量放在前面,就不会出现指向为空。因为字符串常量为匿名对象已经开辟好堆内空间。
String对象两种实例化方式比较
现在清楚String类对象提供实例化两种方式,下面进行区分。
1. 观察分析直接赋值的对象实例化模式
在程序之中只需将一个字符串赋值给String类对象,就可以实现对象的实例化处理。现在假设只有一行代码:
public class StringDemo{
public static void main(String[] args){
String str = "mldn" ;
}
}
在这种情况下,智慧开辟出一块堆内存空间,如下:
除了这种模式之外,利用直接赋值实例化String的形式,还可以实现同一个字符串对象数据的共享操作。
范例:观察String直接赋值时的数据共享
public class StringDemo{
public static void main(String[] args){
String strA = "mldn" ;
String strB = "mldn" ;
System.out.println(strA == strB) ; // 地址判断
}
}
此时程序的判断结果为true,可以得出结论,这两个对象指向的堆内存空间一样,如图所示:
之所以会有这样的特点,主要的原因在Java程序底层提供有专门的字符串池(字符串数组)。
范例:分析字符串池
public class StringDemo{
public static void main(String[] args){
String strA = "mldn" ;
String strB = "mldnjava" ;
String strC = "mldn" ;
}
}
在采用直接赋值的过程之中,对于字符串而言,可以实现池数据的自动保存,这样如果再有相同数据定义时,可以减少定义,减少相同数据的产生,以提升操作性能。
2. 分析构造方法实例化
构造方法实例化可以说是在进行对象定义的时候的一种常见做法,String类为了满足于设计的结构要求,也提供了构造方法。
范例: 一种形式
public class StringDemo{
public static void main(String[] args){
String strA = new String("mldn" );
}
}
此时通过内存关系图进行观察,如下:
此时会开辟两块堆内存空间,只是用一个,另一个由”“形式产生的匿名对象所在的堆内存将变成垃圾空间。
范例:更换一种形式
public class StringDemo{
public static void main(String[] args){
String strA = "mldn" ;
String strB = new String("mldn" );
}
}
除了以上的特点之外,在使用构造方法实例化String类对象时不会出现自动保存在字符串池的处理。
范例:观察构造方法实例化对象时的操作
public class StringDemo{
public static void main(String[] args){
String strA = "mldn" ;
String strB = new String("mldn" );
System.out.println(strA == strB); \\ false
}
}
可以发现构造方法实例化的对象,实际上属于一种自己专用的内层空间,但是在String类中也提供有帮助开发者手工入池的处理情况,这个方法;public String intern();
范例:观察手工入池
public class StringDemo{
public static void main(String[] args){
String strA = "mldn" ;
String strB = new String("mldn" ).intern();
System.out.println(strA == strB); // true
}
}
在使用构造方法定义对象之后,由于使用了intern()方法,所以即便是构造出来的String内容,也可以进入池中管理,但是做法太罗嗦。
面试题:请解释String两种对象实例化的区别?
-
直接赋值:只会产生一个实例化对象,并且自动保存到对象池之中,以实现该字符串内容的重用;
-
构造方法:会产生两个实例化对象,并且不会入池,无法实现自动重用,可以使用intern()方法手工处理;
以后最好直接赋值。
String对象(常量)池
对象池的主要目的是实现数据的共享处理。以String对象池为例,里面的内容主要是为了重用,而重用是为了共享设计,但是在Java中对象池可以分为:
- 静态常量池:指的是程序(*.class)在加载的时候会自动将此程序中保存的字符串、普通的常量、类和方法的信息等,全部进行分配。
- 运行时常量池:当一个程序(*.class)加载之后,里面可能有一些变量,这个时候提供的常量池。
范例:观察一个程序(静态常量池)
public class StringDemo{
public static void main(String[] args){
String strA = "www.mldn.cn" ;
String strB = "www." + "mldn" + ".cn";
System.out.println(strA == strB); // true
}
}
本程序中所给出的内容全部都是静态常量数据(字符串常量都是匿名对象),所以最终在程序加载的时候都自动帮助处理好相应的连接。
范例:观察另外一种情况
public class StringDemo{
public static void main(String[] args){
String info = "mldn" ;
String strA = "www.mldn.cn" ;
String strB = "www." + info + ".cn";
System.out.println(strA == strB); // false
}
}
结果为false,是因为程序在加载的时候并不确定info是什么内容。在字符串进行连接的时候,info是一个变量,变量的内容是可以修改的,所以它不认为最终的strB的结果就是一个所需要的最终的结果。
字符串内容不可修改
在String类中包含的是一个数组,数组的最大缺点是长度不可改变,当设置 一个字符串之后,会自动进行一个数组空间的开辟,开辟的内容长度固定的。
范例:观察一个程序
public class StringDemo{
public static void main(String[] args){
String str = "www." ;
str += "mldn." ;
str += "cn" ;
System.out.println(str);
}
}
进行内存分析
通过此时的程序可以发现,字符串常量的内容并没有发生改变,改变的只是String类对象的引用,并且带来大量的垃圾空间。
public class StringDemo{
public static void main(String[] args){
String str = "www." ;
for (int i = 0; i <= 1000 ; i++){
str += i;
}
System.out.println(str);
}
}
如果本程序出现在代码中,将产生很多个垃圾空间,并且String类的对象要修改一千次,程序的性能非常的差,以后开发中不要进行String类的频繁修改。
Java中的主方法
Java中的主方法组成非常复杂的: public static void main(String arges[]):,
- public:描述的是访问权限,主方法是一切的开始点,开始点一定是公共的;
- static:程序的执行通过类名称完成的,所以表示此方法是由类调用的;
- void: 主方法是程序的起点,起点一旦开始就没有返回的可能。
- main:是一个系统定义好的方法名称
- String args[] :字符串的数组,可以实现程序启动参数的接收。
范例:输出启动参数
public class StringDemo{
public static void main(String[] args){
//System.out.println(args); // [Ljava.lang.String;@15db9742
for (String arg :args ){
System.out.println(arg) ;
}
}
}
在程序执行的时候可以设置参数,每一个参数之间使用空格分割。
但是要记住一点,参数需要输入空格,需要使用”“标记。
以后可以暂时通过启动参数实习数据的输入的模拟。
String常用方法
在实际的项目开发过程之中,项目一般都存在String类的定义,所以掌握String类的方法,对于开发者是非常的重要。
Java的DOC的简介
在以后进行开发的过程之中,肯定要大量的去使用Java的API文档(JavaDoc),这个文档可以通过oracle的在线访问。网址:https://docs.oracle.com/javase/9/docs/api/overview-summary.html;
在jdk1.9之前,Java中常用的类库都会在JVM启动的时候全部加载,这样实际上性能会有所下降,jdk1.9开始提供有模块化的设计,将一些程序类放在不同的文档里面。
在模块之中会包含大量的程序开发:
如果要查看String类的相关定义,则可以打开Java.lang这个包,String类是系统提供的较为标准的类,以这个类的文档结构进行说明,一般文档中的组成会有以下几个部分:
类的完整定义
类的相关说明
成员属性摘要
构造方法摘要(看见Deprecated表示的方法不建议使用,这个方法就不要用了)
方法的摘要(不同的版本,方法会有所差别):
方法详细说明:
最后部分是对方法与成员的详细解释。
文档一般都会有一些 ”假“的中文翻译版,对于这些翻译版,最好不用,整个Java开发涉及到的文档有几十分甚至上百份,没有中文。
字符串与字符数组
在jdk1.9以前,所有的String都是使用字符数组包装处理,所以在String类中包含转换处理方法,这些方法包含构造方法与普通方法。
方法名称 类型 作用
- public String(char[] value) 构造 将传入的字符数组变成字符串
- public String(char[] value, int offset, int count) 构造 将部分字符数组变成字符串
- public char charAt(int index) 普通 获取指定索引位置的字符
- public char[] toCharArray() 普通 将字符串转换为字符数组
范例:观察charAt()方法
public class StringDemo{
public static void main(String[] args){
String str = "0123456" ;
char c = str.charAt(5) ;
System.out.println(c) ;
}
}
索引下标从零开始。
在程序语言之中,最早一开始强调字符串应该使用字符数组进行描述,所以这一操作在String方法之中也是由所体现的。
范例:观察toCharArray()字符数组与字符串的转换
public class StringDemo{
public static void main(String[] args){
String str = "hellloworld" ;
char result [] = str.toCharArray() ; //将字符串变为字符数组
for (int i = 0; i < result.length ; i++){
result[i] -= 32 ;
//注意以下两种的区别
//result[i] = (char) (result[i] - 32) ; //强制类型转换
//result[i] = result[i] - 32 ; //编译不通过
}
// 将处理后的字符数组交给String转换为字符串
System.out.println(new String(result)) ;
}
}
现在做一个验证功能,实现字符串中的数据是否为数字组成。那么可以采用如下思路。
- 如果想要判断字符串的每一位最好的做法是将字符串转换为字符数组
- 可以判断每一个字符是否在数字的范围之内(48-57)
public class StringDemo{
public static void main(String[] args){
String str = "546640" ;
System.out.println(isNUmber(str) ? "是由数字所组成" : "不是由数字所组成 ") ;
}
public static boolean isNUmber(String str){
char result [] = str.toCharArray() ;
for (int i = 0; i < result.length ; i++){
if (result[i] < 48 || result[i] > 57){ // 判断是否不在“0”~“9”中
return false ;
}
}
return true ;
}
}
在实际开发中,处理中文时往往使用char类型,因为其包含中文数据。
字符串与字节数组
字符串与字节数组之间可以实现转换操作,但是需要提醒一下,当进行字符串与字节数组之间的转换,主要是为了二进制的数据传输 或者 进行编码转换。
方法 类型 内容
- public String(byte[] bytes) 构造 将全部的字节数组变成字符串
- public String(byte[] bytes, int offset, int length) 构造 将部分字节数组转换为字符串
- public byte[] getBytes() 普通 将字符串转为字节数组
- public byte[] getBytes(String charsetName) throws UnsupportedEncodingException 普通 编码转换
范例:观察字节与字符串的转换
public class StringDemo{
public static void main(String[] args){
String str = "hellowrld" ;
byte data [] = str.getBytes() ;
for (int i = 0; i < data.length; i++ ){
data[i] -= 32 ;
}
System.out.println(new String(data));
System.out.println(new String(data,0,5));
}
}
字节本身是有长度限制的,一个字节最多可以保存的范围:-128~127;
字符串比较
字符串比较最为常用的方法是equals()方法,但是这个方法需要注意的是进行大小写区分的。
范例:观察equals()大小写的比较
public class StringDemo{
public static void main(String[] args){
String strA = "helloworld" ;
String strB = "HELLOWORLD" ;
System.out.println(strA.equals(strB)) ;
}
}
而除了equals()之外,还有很多比较方法。
- public boolean equals(String anotherString) 区分大小写的判断
- public boolean equalsIgnoreCase(String anotherString) 不区分大小写的判断
- public int compareTo(String anotherString) 进行字符串大小比较,返回一个int数据,三种值:大于(>0)、小于(<0)、等于(=0)。
- public int compareToIgnoreCase(String anotherString) 不区分大小写进行字符串的大小比较
范例:不区分大小写的判断
public class StringDemo{
public static void main(String[] args){
String strA = "helloworld" ;
String strB = "HELLOWORLD" ;
System.out.println(strA.equalsIgnoreCase(strB)) ;
}
}
范例:进行字符串的大小比较
public class StringDemo{
public static void main(String[] args){
String strA = "mlddn" ;
String strB = "mlddN" ;
System.out.println(strA.compareTo(strB)) ; // 32
System.out.println(strB.compareTo(strA)) ; // -32
System.out.println("Hello".compareTo("Hello")) ; // 0
}
}
compareTo()后面还会有更加详细的解释,对于此方法很重要。而忽略大小写的比较也可以使用compareToIgnoreCase()方法实现。如下:
public class StringDemo{
public static void main(String[] args){
String strA = "mldn" ;
String strB = "mldN" ;
System.out.println(strA.compareToIgnoreCase(strB)) ; // 0
}
}
字符串查找
从一完整的字符串之中,查找一个完整的字符串存在就属于字符串的查找操作,在String类中一共提供了如下的几个查找方法:
- public boolean contains(String s) 判断子字符串s是否存在
- public int indexOf(String str) 从头查找指定字符串的位置,找不到返回-1
- public int indexOf(String str, int fromindex) 从指定位置从前向后查找指定字符串的位置
- public int lastIndexOf(String str) 由后向前查找指定字符串的位置
- public int lastIndexOf(String str, int fromindex) 从指定位置由后向前查找指定字符串的位置
- public boolean startsWith(String prefix) 判断是否以指定字符串开头
- public boolean startsWith(String prefix, int toffset ) 由直嘀咕位置判断是否以指定的字符串开头
- public boolean endsWith(String suffix ) 判断是否以指定的字符串结尾
范例:判断字符串是否存在
public class StringDemo{
public static void main(String[] args){
String strA = "w.d.mldn" ;
System.out.println(strA.contains("ml")) ; // true
System.out.println(strA.contains("mldi")) ; // false
}
}
但是这个方法在jdk1.5过后追加的功能,在jdk1.5之前要进行数据的一个查询,需要使用indexOf()方法完成。
范例:判断字符串是否存在
public class StringDemo{
public static void main(String[] args){
String strA = "w.d.mldn" ;
System.out.println(strA.indexOf("ml")) ; // 4 在4位置查找
System.out.println(strA.indexOf("mi")) ; // -1 数据不存在
}
}
是为了进行子字符串位置的查询,在进行开发的过程中就可以利用次形式,进行字符串索引位置的确定。indexOf()是由前向后查找的。但也可以由后向前进行查找。
范例:使用lastIndexOf()查找
public class StringDemo{
public static void main(String[] args){
String strA = "w.d.mldnd" ;
System.out.println(strA.lastIndexOf(".")) ; // 3
System.out.println(strA.lastIndexOf(".", 2)) ; // 1
}
}
在进行字符串查找的时候也需要判断开头或结尾的操作。
范例:判断是否以指定字符串的开头或结尾
public class StringDemo{
public static void main(String[] args){
String strA = "**@w.d.mldnd##" ;
System.out.println(strA.startsWith("**")) ; // ture
System.out.println(strA.startsWith("**", 2)) ; // false
System.out.println(strA.endsWith("##")) ; //ture
}
}
后面的许多设计都需要它的支持
字符串替换
字符串的替换指的是,可以以指定的内容进行指定字符串的替换显示,对于替换的方法主要有两个:
- public String replaceAll(String regex, String replacement) 进行全部替换
- public String replaceFirst(String regex, String replacement) 替换首个
范例:实现替换
public class StringDemo{
public static void main(String[] args){
String str = "helloeorld" ;
System.out.println(str.replaceAll("l", "x")) ;
System.out.println(str.replaceFirst("l", "x")) ;
}
}
替换后面会有详细的讲解,并且在开发和设计之中替换尤其的重要。
字符串的拆分
在字符串处理的时候,提供字符串拆分的处理方法,字符串拆分操作主要是根据一个指定的字符串,或者是一个表达式进行字符串的拆分,并且拆分后的数据以字符串数组的形式返回。
- public String [] split(Strign regex) 按照指定的字符串去全部拆分
- public String [] split(Strign regex, int limit) 按照指定字符串拆分为指定个数
范例:观察字符串拆分处理
public class StringDemo{
public static void main(String[] args){
String str = "hello world hello 57" ;
String result [] = str.split(" ") ; // 按照空格拆分
for(int i = 0; i < result.length; i++){
System.out.println(result[i]) ;
}
}
}
除了全部拆分外,也可以拆分为指定个数
范例:拆分为指定个数
public class StringDemo{
public static void main(String[] args){
String str = "hello world hello 57" ;
String result [] = str.split(" ", 2) ; // 按照空格拆分
for(int i = 0; i < result.length; i++){
System.out.println(result[i]) ;
}
}
}
但是进行拆分的时候会出现拆分不了的情况,这个时候最简单的理解是使用”\\“进行转义。
public class StringDemo{
public static void main(String[] args){
String str = "196.168.1.2" ;
String result [] = str.split("\\.") ; // 按照空格拆分
for(int i = 0; i < result.length; i++){
System.out.println(result[i]) ;
}
}
}
对于拆分和替换的更多操作,后续来讲。
字符串截取
字符串截取,从一个完整的字符串中截取子字符串,主要有两个方法:
- public String substring(int beginIndex) 从指定索引截取到结束
- public String substring(int beginIndex, int endIndex) 截取指定范围中的子字符串
范例:观察字符串截取操作
public class StringDemo{
public static void main(String[] args){
String str = "hello woyld " ;
System.out.println(str.substring(2)) ;
System.out.println(str.substring(2,5)) ;
}
}
在实际开发之中,开始或者结束的索引往往是通过indexOf()方法计算而来的。
范例:观察截取
public class StringDemo{
public static void main(String[] args){
String str = "admin-photo-57.jpg " ;
//int beginIndex = str.lastIndexOf("-") ;
int beginIndex = str.indexOf("-", str.indexOf("photo")) + 1 ;
int endIndex = str.lastIndexOf(".") ;
System.out.println(str.substring(beginIndex, endIndex)) ;
}
}
在以后实际开发之中通过计算类确定索引的情况非常常见。
字符串格式化
从jdk1.5开始为了吸引传统的开发人员,Java提供格式化数据的操作,类似C语言之中的格式化输出语句,利用占位符进行输出,对于常用的占位符:字符串(%s)、字符(%c)、整数(%d)、小数(%f)等。
- public static String format(String format,各种类型....args) 根据指定的结构进行文本格式化显示。
范例:观察格式化处理
public class StringDemo{
public static void main(String[] args){
String name = "张三 " ;
int age = 18 ;
double score = 98.4565643 ;
String str = String.format("姓名:%s、年龄:%d、分数:%5.2f。",name,age,score) ;
System.out.println(str) ;
}
}
这种格式化的输出算是String的附加功能。
其它操作方法
在String中还提供了一些比较小的方法提供给开发者使用,这些方法如下:
- public String concat(String str) 字符串连接
- public String intern() 对字符串入池
- public boolean isEmpty() 判断是否为空字符串,但是不是null。在进行字符串定义时,””““与”null“不是一个概念,一个表示有实例化对象,一个表示没有实例化对象,而isEmpty()主要是判断字符串的内容,所以一定要在有实例化对象的时候调用。
- public int length() 计算字符串的长度
- public String trim() 去除左右的空格信息
- public String toUpperCase() 转大写
- public String toLowerCase() 转小写
范例:观察字符串连接
public class StringDemo{
public static void main(String[] args){
String strA = "helloworld!" ;
String strB = "hello".concat("world").concat("!") ;
System.out.println(strB) ;
System.out.println(strA == strB) ;//false
}
}
从整体的运行结果来将,虽然字符串内容相同,但是结果为false,证明此操作并没有实现静态的定义,与”+“的区别没有入池。
范例:判断空字符串
public class StringDemo{
public static void main(String[] args){
String str = "" ;
System.out.println(str.isEmpty()) ;// true
}
}
范例:观察length与trim
public class StringDemo{
public static void main(String[] args){
String str = " Hello World " ;
System.out.println(str.length()) ;
String trimStr = str.trim() ;
System.out.println(trimStr) ;
System.out.println(trimStr.length()) ;
}
}
在进行数据输入的时候,容易输入空格,有空格的时候进行数据的查找会出错,那么必须对输入数据进行空格的处理,不会消除中间空格。
在String类中提供有大小写的转换,但是这种转换的特征可以避免非字母的转换。
范例:观察大小写的转换
public class StringDemo{
public static void main(String[] args){
String str = "Hello World !! " ;
System.out.println(str.toUpperCase()) ;
System.out.println(str.toLowerCase()) ;
}
}
用这种方式进行转换,可以节约开发成本,毕竟自己写麻烦。虽然在Java之中,String提供有大量的方法,但是缺少首字母大写的方法,这个方法由开发者通过方法的组合实现。
范例:自定义一个首字母大写的方法
class StringUtil{
public static String initcap(String str){
if (str == null || "".equals(str)){
return str ;
}
if(str.length() == 1){
return str.toUpperCase() ;
}
return str.substring(0,1).toUpperCase() + str.substring(1) ;
}
}
public class StringDemo{
public static void main(String[] args){
System.out.println(StringUtil.initcap("hello") ) ;
}
}
此代码在以后开发中必定要使用的程序。