【JavaSE基础】03-常用类API(一)

这一章节我们将会解释到为什么一维 int 类型数组名直接打印的是[I@659e0bfd,二维 int 类型数组名直接打印的又是[[I@659e0bfd,而类名打印出来又会是什么呢?又代表的是什么意思呢?

追本溯源,先从根类 Object 开始,看看 Object 赋予了子类多少的共性内容。同时探索目前占用率最高的 JDK8 版本下常用类 API。

1、Java类层次结构的超类:Object类

(1)包:java.lang.Object(java.lang包下,不用使用import导包,直接使用)

(2)描述:public class Object Class Object is the root of th class hierarchy[层次]. Every class has Object as a superclass. All objects, including arrays, implements the method of this class. Object是类层次结构的超类,所有的对象包括数组都继承了它的方法。【Object的修饰符是public并且是在lang包下的原因:要保证所有得类可以访问到并继承自它】

(3)版本:since JDK1.0【表示说:只要得编译器不低于这个版本就可以使用这个类】

(4)构造方法摘要:Constructor Summary包含了构造方法和它的相关得描述【Constructor and Description】仅仅只有一个无参的构造方法,这就是为什么所有类的构造器中默认是super()的原因

(5)方法摘要:Method Summary包含了所有成员方法和它们的相关得描述【Modifier and Type、Method and Description】,主要关注的就是修饰符、返回值类型、方法名以及参数列表。当然最重要的是方法功能描述。

(6)Object作为超类并没有提供成员变量【Field Summary】,这值得思考,也只得我们再设计时去参考。

了解了API第一个类的说明包含哪些内容之后呢,我们重点来看一下类的成员。

0:类的引入及其描述

java.lang不需要导包,编译器高于JDK1.0,描述说Object是superclass,java中所有得对象继承它,哪怕是数组。注意:每个类都直接或者间接得继承自Object。【间接指的是多重继承】

A:成员变量 【Field Summary】

没有

B:构造方法 【Constructor Summary】:

public Object() { } --仅有一个空参构造;创建对象:Object o = new Object();

C:成员方法 【Method Summary】

  • a、public int hashCode() Returns the hash code value for the object[返回对象(数组)的哈希码值:通过该对象(数组)的内部地址(实际地址值)转换为一个整数来实现的,因为所有对象(数组)的转换的公式相同,所以可以将这个哈希码值当作地址值来用]。This method is supported to the benefit of hash tables such as those provided by HashMap[提供此方法是为了提高哈希表的性能]

  • b、public final Class<?> getClass() Returns the runtime class of this object[返回对象的运行时类,物理上也就是对象的类的字节码文件]【final表示不可以重写,子类只可以继承】

    • 走神一下,看一下Class<?> 这个<?>是JDk5.0之后的新特性,暂时不说,只要知道依然是一个类就足够了。

    • Class<?>里面有两个比较重要的方法,我们先来看一下,之后在反射的时候,我们会详细来说明这个Class的
      - ①、public String getName Returns the name of the entity(class, interface, array class, primitive type, or void) represented by the Class object, as a String.[以字符串形式返回此Class对象代表的实体名称(类、接口、数组、基本数据类型或void)]
      - ②、public String getSimpleName Returns the simple name of the underlying class as given in the source code. Returns an empty string if the underlying class is anonymous.[返回源码中底层类的简称,也就是不含包名的类名。如果底层类是匿名的,就返回空字符串]。The simple name of an array is the simple name of the component type with “[]” appended. In particular the simple name of an array whose component type is anonymous is “[]”.[数组的简称是: 组件类型[], 如果组件是匿名的话, 数组的简称是: []]。 SINCE JDK 1.5

  • 示例:getName和getSimpleName的对比

public class Demo
{
	public static void main(String[] args)
	{
		Object o = new Object();
		Object[] os = new Object[12];

		System.out.println(o.getClass().getName());
		// java.lang.Object
		System.out.println(o.getClass().getSimpleName());
		// Object
		System.out.println(os.getClass().getName());
		// [Ljava.lang.Object;
		System.out.println(os.getClass().getSimpleName());
		// Object[]
	}
}
  • c、public final Class<?> getClass() Returns a string representatuon of the object.[发挥该对象的字符串表示]。In general, the toString method returns a string that “textually represents” this object. The result shoule be a concise but informative representation that is easy for a person to read. It is recommended that all subclasses override this method.[通常,toString方法返回的应该是一个“文本方式表示”对象的字符串。结果应该是简单易读懂的信息。建议所有的子类去重写这个方法]。In others words, this method returns a string equal to the value of[其实,该方法的返回值是由以下部分组成的]: getClass().getName()+@+Integer.toHexString(hashCode())
    • Integer类下的方法:public static String toHexString(int i) Returns a string representation of the integer argument as an unsigned integer in base 16.[返回无符号整数的十六进制表示形式的字符串]

PS:重写toString。Eclipse工作区右键--> Source--> Generate toString()Alt + Shift + S --> 键入 S。实际上,每次执行的直接打印对象的操作,实际上是打印了对象的toString方法的返回值。

  • d、public boolean equals(Object obj) Indicates whether some other object is “equal to” this one.[指示对象obj是否与当前对象相同]。For any non-null reference value x, x.equals(null) should return false.[仅当引用类型的x非空,x与null的比较结果才是false]。Note that it is generally necessary to override the hashCode method whenever this method is overridden, so as to maintain the general contract for the hashCode method, which states that equal objects must have equal hash codes.[注意:当此方法被重写时,通常有必要重写 hashCode 方法,以维护hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码]。

PS:重写equals。Eclipse工作区右键--> Source--> Generate hashCode() and equals()Alt + Shift + S --> 键入 H。实际上,重写equals方法,就是将之前的地址值的相等比较,转换成两个对象之间的成员变量的相等比较,如果所有成员变量的值均相等,则认定两个对象是相等的。

重写equals方法并进行代码的优化,下面的代码是直接使用快捷方式,由eclipse生成的Student类的equals方法。使用了较多的return语句代替了本来该使用的if...else提高了代码的执行效率。

    @Override
	public boolean equals(Object obj)
	{
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Student other = (Student) obj;
		if (age != other.age)
			return false;
		if (name == null)
		{
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}

其中对于比较的两者的所属类的比较是为了程序的健壮性考虑。有效阻止了ClassCastException的发生,防止非本类对象参与比较。

@Override
public boolean equals(Objecty obj)
{
	if(!(obj instanceof Student))
	{
		return false;
	}
	/* 这段代码实际上就是相当于
		但是上面的代码明显提高了可阅读性
		因为系统在自动生成的时候,要考虑到
		之后不同的类的适用性,所以牺牲了一定的阅读性
	if (getClass() != obj.getClass())
		return false;
	*/	
}

综上,重写方法的时候,需要实施代码的优化,以提高代码的执行效率,提高程序的健壮性。并且要保证具有良好的阅读性。

超类Object中的equals方法如下,默认只是对引用数据类型的比较,并且比较的是引用数据类型的地址值。这也是equals和==的重要区别所在。

public boolean euqals(Object obj)
{
	return (this == obj);
}
  • e、protected void finalize() throws Throwable Called by garbage collector on an object when garbage collector determines that there are no more references to the object. A subclass overrides the finalize method to dispose of system resources or to perform other cleanup.[当垃圾回收器确定不存在对该对象更多的引用的时候,由对象的垃圾回收器调用此方法。子类可重写finalize方法,以配置系统资源或其他清楚的操作。此方法用于垃圾回收,但是具体什么时候回收不知道]。The finalize method is never invoked more than once by a Java virtual machine for any given object.[对于任何给定的对象,Java虚拟机至多只执行一次该方法]。Any exception thrown by the finalize method cases the finalization of this object to be halted, but is otherwise ignored[finalize方法抛出的任何异常都会导致对象的终结操作的停止。但是可以通过一些方法去忽略异常]。

  • f、protected Object clone() throws CloneNotSupporttedException Creates and returns a copy of this object.The precise meaning of “copy” may depend on the class of this object.The general intent is that, fot any object x, the expression x.clone() != x wil be true, and that the expression x.clone().getClass() == x.getClass() will be true, but these are not absolute requirements. While it is typically the case that x.clone().equals(x) qill be true, this is not an absolute requirement.[创建并返回此对象的一个副本。“副本”的准确含义可能依赖于对象的类。这样做的目的是,对于任何对象 x,表达式:x.clone() != x为 true,表达式: x.clone().getClass() == x.getClass()也为 true,但这些并非必须要满足的要求。一般情况下: x.clone().equals(x)为 true,但这并非必须要满足的要求。 ] Note that the class Object does not itself implement the interface Cloneable, so calling the clone method on an object whose class is Object will result in throwing an exception at run time.[注意:Object 类本身不实现接口 Cloneable,所以在类为 Object 的对象上调用 clone 方法将会导致在运行时抛出异常]。
    Throws:
    CloneNotSupportedException - if the object’s class does not support the Cloneable interface. Subclasses that override the clone method can also throw this exception to indicate that an instance cannot be cloned.[如果对象的类不支持 Cloneable 接口,则重写 clone 方法的子类也会抛出此异常,以指示无法复制某个实例]。

    • Cloneable:此类实现了Cloneable接口,以指示Object.clone()方法可以合法的对该类的示例进行 按字段复制。这个接口是标准的接口(没有任何的方法),告诉我们实现了该接口就可以进行类的对象的复制了。

      x.clone() != x // true
      x.clone().getClass() == x.getClass() // true
      x.clone().equals(x) // true(说明进行了equals方法的重写)

面试题

1、直接输出一个对象,实际上输出的是该对象调用toString方法后的返回值


2、“==”和equals的区别?

  • a、“==” 可以对所有的数据类型进行比较
  • 基本数据类型比较的是数值是否相同
  • 引用数据类型比较的是地址值是否相同
     
  • b、equals只能比较引用数据类型,默认情况下,比较的地址值是否相同
  • 但可以根据具体的需求去重写它,重写的时候,主要考虑:执行效率、程序健壮性、可阅读性。

2、文本扫描器:Scanner类

之前不止一次的使用过该类,只是简单了解Scanner可以用来接受用户的输入,按照指定的数据类型。并且学习过,在JDK5之前用于接受用户输入的方法,就是通过public static void main(String[] args)中参数数组args接收然后装换指定的类型使用。
Throws: InputMismatchException - 用户输入的数据和指定的数据类型不匹配。

0:类的引入及其描述

java.util需要导包,是最终类public final class Scanner编译器高于JDK5.0,描述说Scanner从JDK5.0之后用于接受用户的输入,一个可以使用正则表达式来解析基本类型和字符串的简单文本扫描器。

A:成员变量 【Field Summary】

没有

B:构造方法 【Constructor Summary】

public Scanner(InputStream source) { } –现阶段学习构造 创建对象:Scanner sc = new Scanner(System.in);其中,InputStream是System类中的静态最终变量 public static final InputStream in; // 标准的输入流–对应着键盘录入或另一个输入源(IO流的时候讲解)

C:成员方法 【Method Summary】

  • a、public boolean hasNextXxx() 其中Xxx代表的数据类型(Byte、Short、Int、Long、Double、Float、Boolean、Line、BigInteger、BigDecimal) 判断是否还有下一个Xxx输入项;也可以直接使用 public boolean hasNext()判断是否还有下一个输入

  • b、public Xxx nextXxx() 其中Xxx代表的数据类型(Byte、Short、Int、Long、Double、Float、Boolean、Line、BigInteger、BigDecimal) 获取用户的输入项Xxx类型数据;也可以直接使用 public String nextLine()直接将用户的所有得输入当作是String读入,再进行数据类型的转换。

  • c、public boolean hasNextXxx(int radix)&&public Xxx nextXxx(int radix)主要是按照radix进制进行读入。

  • d、Scanne最常用的两个方法:

    • public int nextInt() : 获取用户输入int类型数据
    • public String nextLine() : 获取一个String类型数据(遇到第一换行符就停止
    • public String next() : 获取一个String类型数据(遇到第一换行符或者空格就停止
  • 注1:Scanner类,默认使用的是空格或换行作为分割符

  • 注2:Scanner类,同一个Scanner对象先扫描int,再扫描String,会导致String的获取值为‘\n’

示例

package com.rupeng.scanner;

import java.util.Scanner;

public class ScannerDemo
{
	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		// 1 -
		System.out.println("Please input your age: ");
		int age = sc.nextInt();
		// 2-
		System.out.println("Please input your name: ");
		String name = sc.nextLine();
		System.out.println("Name: " + name + ", Age: " + age);
	}
}

再接收用户的输入时出现了一个问题,当我们输入age之后换行,之后就直接输出了。并没有提示我们输入name。
在这里插入图片描述

扫描器将扫描的文本,先截取一段转换为int数值。当回车换行时,扫描器将现在的“\n”当作是String的值给了用于接收nextLine方法返回值的变量。

解决方法
1、使用空格分割,一次性输入age name,再回车换行。
在这里插入图片描述
2、使用两个Scanner扫描器分别扫描int类型的age和String类型的name。

3、使用同一个Scanner先扫描String类型数值,再去接收int类型的数值。(将代码中的1-和2-部分调换顺序)
在这里插入图片描述

*4、 使用同一个Scanner扫描器,截取int数值之后,进行判断,若是“\n”则不读取,否则读入作为String变量的值。

String name = sc.nextLine();
if (name != "")    // if(name != "\n")
{
	name = sc.nextLine();
}

*5、 使用同一个Scanner扫描器的nextLine方法扫描所有数据,再进行类型的转换。int age = Integer.parseInt(sc.nextLine());(使用的Integer类的静态方法public static int parseInt(String s),用于将字符串解析成int类型的值)

3、Java最常用类:String类

String类是需要重点学习的类。

0:类的引入及其描述

java.lang不需要导包,编译器高于JDK1.0,是一个最终类public final class String,描述说String代表字符串。Java程序中的所有的字符串字面值都作为本类的实例实现。字符串是常量,它们在创建之后,就不可以改变。字符串缓冲区(StringBuilder、StringBuffer)支持可变的字符串。因为String是不可变的时可以共享的,这个就像是static共享是一样的。String具体的字面值常量是存储在方法区的常量池中。

PS字符串是常量,它们在创建之后,就不可以改变。

Java语言提供的对字符串串联符号(“+”)以及将其它对象转换为字符串的特殊支持。“+”可以用作字符串连接符号,是之前我们强调过的。字符串串联实质上是:StringBuilder类及其append方法实现的。字符串转换则是:通过toString方法实现的,该方法由Object类定义,并可被Java中的所有类继承。

除非特别的说明,否则将一个null参数给此类中的任何一个构造方法或成员方法会造成 NullPointerExcption异常。

字符串定义:字符串是由多个字符组成的字符序列。本质字符数组.

String str = "abc";
//等价于:
char[] chr = new char[]{'a','b','c'};
String str = new String(chr);

A:成员变量 【Field Summary】

没有

B:构造方法 【Constructor Summary】

  • a、public String() 空参构造,创建对象:String str = new String(); 初始化创建一个空字符串序列,因为String的不可变,所以没有必要使用该构造方法

  • b、public String(String original) 初始化新创建一个String对象,使其表示一个与参数相同的字符串序列;换句话说,新创建的字符串是字符串original的副本。由于String的不可变性,所以无需使用此构造。除非需要创建一个original的显示副本。

  • c、public String(byte[] bys) 使用系统默认字符集解码byte数组,将其转换为String字符串。

  • d、public String(byte[] bys,int offset,int length,String charsetName) throws UnsupportedEncodingException 使用指定的字符集从索引offset处开始解码length长度的byte数组,转换成String字符串。指定字符集不支持的话,抛出UnsupportedEncodingException异常。 since JDK 1.1

  • e、public String(byte[] bys,int offset,ing length) 使用默认的系统字符集从offset索引处开始解码length长度的byte数组,转换成String字符串。 since JDK 1.1

  • f、public String(byte[] bys,int offset,int length,Charset charset) 同d,只是异常再charset中。 since JDK 1.6

  • g、public String(byte[] bys,String charsetName) throws UnSupportedException since JDK 1.1

  • h、public String(byte[] bys,CharSet charset) since JDK 1.6

  • i、public String(char[] chs) 使用字符数组chs中的字符创建一个String字符串,创建之后对于字符数组的修改不会映射到String字符串上面。

  • j、public String(char[] chs,int offset,int count) 从字符数组的offset索引处开始获取指定count长度的字符数组内容,创建String字符串,创建之后对于字符数组的修改不会映射到String字符串上面。

  • k、public String(StringBuffer buffer) 将字符串缓冲区的当前的包含的字符串序列转换成一个String字符串,创建之后,两者的修改独立,不影响对方。

  • l、public String(StringBuilder builder) 将字符串生成器参数中当前包含的字符序列转换成String字符串,创建之后,两者的修改独立,不影响对方。提供此构造方法是为了简化到StringBuilder的迁移。通过toString方法从字符串生成器中获取字符串可能运行地更快,因此通常作为首选。

  • **k、String str = 字符串常量;**现阶段使用的最多的方式。

注意三个异常IndexOutOfBoundsException(关于索引) -&- UnsupportedEncodingException(关于自己书写的charset) -&- NullPointerException(关于当前字符串为null),它们的出现极有规律可循。

示例: 解释 – 字符串是常量,它们在创建之后,就不可以改变。

Tips:

  • 只是值不可以改变,引用可以改变。
  • String具体的字面值常量是存储在方法区的常量池中。
     
  • 以字节数组转换成的String字符串,长度不同,具体在IO流时说明。
package com.rupeng.string;

public class ConstructorDemo
{
	public static void main(String[] args)
	{
		String str = "Hello";
		str += "World";
		System.out.println(str);
	}
}

内存图解
在这里插入图片描述

字符串直接赋值的方式是先去方法去中的字符串常量池中去查找,有就直接返回,没有就创建之后再返回。

C:成员方法 【Method Summary】

成员方法按照具体使用中功能项来分类

  • a、String类的判断功能
    • (1)public boolean equals(Object obj) 比较字符串内容是否相同,区分大小写。当且仅当obj不为null且与当前字符串相等,返回true。若当前字符串为null,则抛出NullPointerException异常。
    • (2)public boolean equalsIgnoreCase(String) 比较字符串内容是否相同,不区分大小写。其余同上。
    • (3)public boolean contains(CharSequence s) 当且仅当此字符串包含指定的 char 值序列时,返回 true。
    • (4)public boolean contentEquals(StringBuffer sb) 当且仅当StringBuilder包含的字符串和当前字符串相同是,返回true。 JDK 1.4
    • (5)public boolean contentEquals(CharSequence cs) 当且仅当此 String 与指定序列表示相同的 char 值序列时,结果才为 true。 JDK 1.5
    • (6)public boolean startsWith(String prefix) 测试此字符串是否以指定的前缀开始。
    • (7)public boolean startsWith(String prefix,int toffset) 测试此字符串是否是从toffset索引开始 以prefix为前缀。如果 toffset 为负或大于此 String 对象的长度,则结果为 false;否则结果与以下表达式的结果相同:this.substring(toffset).startsWith(prefix)
    • (8)public boolean endsWith(String str) 测试此字符串是否以指定的后缀结束。注意,如果参数是空字符串,或者等于此 String 对象(用 equals(Object) 方法确定),则结果为 true。
    • (9)pubic boolean matches(String regex) 判断字符串师傅满足正则表达式 regex (疯狂的正则表达式
    • (10)public boolean isEmpty() 当且仅当 length() 为 0 时返回 true。注意:区别空和null。 JDK 1.6
String s1 = "";
System.out.println(s1.isEmpty);    
//true
String s2 = "";
System.out.println(s2.isEmpty);     
// java.lang.NullPointerException 异常

判断时,只要当前字符串是null,则就抛出NullPointerException异常。

示例:模拟登录,成功之后,可以进行猜数字游戏

package com.rupeng.string;

import java.util.Scanner;

/**
 * GameDemo类主要是让如鹏的鹏友们在学习之余有点小娱乐,一个简单的猜数字游戏, 
 *     当然首先你需要是如鹏的合法鹏友,用户名有着内部人员才知晓的命名规则:
 * 	1、用户名类似于 rp_xxxxx@rupeng.com ,其中的xxxxx长度随意, 但是不可以有敏感词mb、wc。
 * 	2、如果你是我们的大家长杨中科老师(rp_yzk@rupeng.com)的话, 你的密码就必须是 handsome_boy。
 * 	3、其他人的话,就随意了,只要你的用户名是按照我们的规则的话,密码长度不小 于6位,就可以了, 
 *		当然用户名的大小写我们不在意,但是密码则必须在意。
 * 
 * 对于用户名和密码的验证,只有三次机会哟,错过了,就赶紧来和我们做鹏友吧! 
 * 经过上面的验证,恭喜你,你是我们鹏友,我们赶紧来一起玩耍吧!
 * 
 * 猜数字小游戏:程序会随机在1-100之间产生一个数字。如果我们猜对了,就会有奖励哦。 
 *	当然第一次才对的概率是很小的,但是 **没关系,我们会给你前进的方向的,
 * 	会相应 给出偏大、偏小提示,让我们一起快乐玩耍吧!
 * 
 * @author Joian
 *
 * @version V 1.0
 */

public class GameDemo
{
	/**
	 * 测试程序的功能,验证用户的合法性(3次机会),成功则进入猜数字小游戏
	 */
	public static void main(String[] args)
	{
		// 创建扫描器的对象,勇于接受用户输入的用户名和密码
		Scanner sc = new Scanner(System.in);
		// 定义String变量name,接受用户输入的用户名
		String name = "";
		// 定义String变量pwd,接受用户输入的密码
		String pwd = "";
		// 定义int变量count,用于判断用户输入错误的次数
		int count = 0;
		// 如果用户输入错误的次数小于3次的话,就继续提醒输入
		// 成功的话就玩游戏,玩完再退出,只有一次游戏的机会
		// 记住 Day Day Up,Good Good Study!
		while (count < 3)
		{
			// 提醒用户输入用户名和密码
			System.out.println("Please enter your username: ");
			name = sc.nextLine();
			System.out.println("Please enter your pwd: ");
			pwd = sc.nextLine();
			// 判断用户的输入合法性,如果合法就玩游戏,不合法再次输入,直到三次机会用完就退出
			if (isLegalUser(name, pwd))
			{
				System.out.println("Hello, "+ name+ " ! Welcome to Rupeng. \n\tLet's play game -- \"Guess number\".");
				playGame();
				System.out.println("Good Good Study! Day Day Up !\nPlease come back tomorrow ");
				break;
			} else
			{
				count++;
				if (count == 3)
				{
					System.out.println("Logon failed three times ! \n Bye.Bye...");
					break;
				} else
				{
					System.out.println("You failed " + count + " times. ");
				}
			}
		}
		sc.close();
	}

	/**
	 * 判定给予的用户名和密码的合法性。用户名类似于 rp_xxxxx@rupeng.com ,
	 *	 其中的xxxxx长度随意,但是不可以有敏感词mb、wc。不区分大小写。
	 * 	如果你是我们的大家长杨中科老师(rp_yzk@rupeng.com)的话,
	 * 	你的密码就必须是 handsome_boy。其他人只要是长度不小于6位就可以。区分大小写
	 * 
	 * @param name
	 *            用户登陆的用户名
	 * @param pwd
	 *            与用户名匹配的密码
	 * @return 当且仅当用户名和密码均部位null,且符合定义时,为true
	 */
	private static boolean isLegalUser(String name, String pwd)
	{
		// 1、如果用户名或者密码的输入是null,则直接退出
		if (name == null || pwd == null)
			return false;
		// 2、如果用户名或者密码是空字符串,则直接退出
		if (name.isEmpty() || pwd.isEmpty())
			return false;
		// 3、如果你用户名不是类似于 rp_xxxxx@rupeng.com,就不是鹏友
		if (!name.toLowerCase().startsWith("rp_"))
			return false;
		if (!name.toLowerCase().endsWith("@rupeng.com"))
			return false;
		// 4、如果你的用户名里面有mb、wc,对不起,我们不能在一起。
		if (name.toLowerCase().contains("mb")
				|| name.toLowerCase().contains("wc"))
			return false;
		// 5、如果密码长度不大于6,强度不够,就不是程序员!
		if (pwd.length() < 6)
			return false;
		// 6、如果你是大家长杨中科老师(rp_yzk@rupeng.com)的话, 你的密码就必须是 handsome_boy。任性不解释。
		if (name.equalsIgnoreCase("rp_yzk@rupeng.com"))
		{
			if (!pwd.equals("handsome_boy"))
				return false;
		}
		// 以上均通过的话,你就是我的好鹏友
		return true;
	}

	/**
	 * 猜数字小游戏:程序会随机在1-100之间产生一个数字。如果我们猜对了,就会有奖励哦。
	 * 当然第一次才对的概率是很小的,但是没关系,我们会给你前进的方向的, 会相应 给出偏大、偏小提示,让我们一起快乐玩耍吧!
	 */
	private static void playGame()
	{
		// 定义一个 sc 用于接收用户猜的数字
		Scanner sc = new Scanner(System.in);
		// 产生一个1-100之间的随机数字,定义变量target
		int target = (int) (Math.random() * 100) + 1;
		while (true)
		{
			System.out.println("Please enter the number you guess:");
			// 定义int变量src,接受用户的输入
			int src = sc.nextInt();
			// 判断猜的数字src和target是否相等,相等就给出提示,退出while;如果偏小了,就给出偏小的提示;偏大同理。
			if (src == target)
			{
				System.out.println("Congratulations! You guessed it!");
				break;
			} else if (src < target)
			{
				System.out.println("Guess the number is too samll. Try again!");
			} else
			{
				System.out.println("Guess the number is too large. Try again!");
			}
		}
		sc.close();
	}
}
  • b、String类的获取功能
    • (1)public int length() 获取String字符串的长度
    • (2)public char chatAt(int index) 获取String字符串的指定索引处的 char 值
    • (3)public int indexOf(char c) 获取字符c在String字符串中第一次出现的索引。如果未出现该字符,则返回 -1。
    • (4)public int indexOf(String str) 获取字符串str在当前字符串中第一次出现的索引。如果未出现该字符串,则返回 -1。
    • (5)public int indexOf(char c,int fromindex) 从指定索引fromindex开始往后搜索字符c第一次出现的索引。如果未出现该字符,则返回 -1。
    • (6)public int indexOf(String str,int fromindex) 从指定索引fromindex开始往后搜索字符串str第一次出现的索引。如果未出现该字符串,则返回 -1。
    • (7)lastIndexOf 表示的是指定的字符或字符串,最后一次出现的索引。如果未出现该字符串,则返回 -1。
      public int lastIndexOf(char c)
      public int lastIndexOf(String str)
      public int lastIndexOf(char c,int fromindex)
      public int lastIndexOf(String str,int fromindex)
    • (8)public String substring(int beginIndex) 从当前字符串指定索引beginindex处的字符开始,直到此字符串末尾,截取一个字符串
      IndexOutOfBoundsException - 如果 beginIndex 为负,或 endIndex 大于此 String 对象的长度,或 beginIndex 大于 endIndex。
      • (9)public String substring(int beginIndex,int endindex) 从当前字符串指定的 beginIndex 处开始,直到索引 endIndex - 1 处,截取一个字符串。因此,该子字符串的长度为 endIndex-beginIndex。
        IndexOutOfBoundsException - 如果 beginIndex 为负,或 endIndex 大于此 String 对象的长度,或 beginIndex 大于 endIndex。

示例1:遍历字符串中的每个字符

package com.rupeng.string;

public class GetDemo1
{
	public static void main(String[] args)
	{
		String str = "hello\tworld\n";
		for (int i = 0; i < str.length(); i++)
		{
			System.out.println(str.charAt(i));
		}
	}
} 

示例2:统计大小写字符和数字以及其他字符在字符串中出现的次数

/**
 *	需求:使用不同的方式实现统计(覆盖上述所有的方法)
 */
// 第一种方式:使用lenth()、charAt(int index)、转换功能中的 toLowerCase()
package com.rupeng.string;

public class GetDemo2
{
	public static void main(String[] args)
	{
		String str = "Hello, rp_12306@rupeng.com ! Welcome to Rupeng. "+
						"Let's play game -- \"Guess number\".";
		int numCount = 0;
		int chrCount = 0;
		int otherCount = 0;
		for (int i = 0; i < str.length(); i++)
		{
			if (str.charAt(i) >= '0' && str.charAt(i) <= '9')
				numCount++;
			else if (str.toLowerCase().charAt(i) >= 'a'
					&& str.toLowerCase().charAt(i) <= 'z')
				chrCount++;
			else
				otherCount++;
		}
		System.out.println(numCount);
		System.out.println(chrCount);
		System.out.println(otherCount);
	}
}

// 第二种方式:使用lenth()、contains(CharSequence c)、
//		substring(int beginindex, int endindex)、转换功能中的 toLowerCase()
package com.rupeng.string;

public class GetDemo3
{
	public static void main(String[] args)
	{
		String str = "Hello, rp_12306@rupeng.com ! Welcome to Rupeng. "+
						"Let's play game -- \"Guess number\".";
		int numCount = 0;
		int chrCount = 0;
		int otherCount = 0;
		for (int i = 0; i < str.length(); i++)
		{
			if ("1234567890".contains(str.substring(i, i + 1)))
				numCount++;
			else if ("qwertyuioplkjhgfdsazxcvbnm".contains(str.substring(i,
					i + 1).toLowerCase()))
				chrCount++;
			else
				otherCount++;
		}
		System.out.println(numCount);
		System.out.println(chrCount);
		System.out.println(otherCount);
	}
}

// 第三种方式:使用lenth()、indexOf(char c)\indexAt(String str)
//		或lastIndexOf(char c)\lastIndexOf(String str)、charAt(int index)、
//		substring(int beginindex, int endindex)、转换功能中的 toLowerCase()
package com.rupeng.string;

public class GetDemo4
{
	public static void main(String[] args)
	{
		String str = "Hello, rp_12306@rupeng.com ! Welcome to Rupeng. "+
						"Let's play game -- \"Guess number\".";
		int numCount = 0;
		int chrCount = 0;
		int otherCount = 0;
		for (int i = 0; i < str.length(); i++)
		{
			if ("0123456789".indexOf(str.charAt(i)) != -1)
				numCount++;
			else if ("qazwsxedcrfvtgbyhnujmiklop".indexOf(str.toLowerCase()
					.substring(i, i + 1)) != -1)
				chrCount++;
			else
				otherCount++;
		}
		System.out.println(numCount);
		System.out.println(chrCount);
		System.out.println(otherCount);
	}
}

// 第四种方式:使用toCharArray方法,再加上数组的遍历
// 避免了对于方法的调用次数随着字符串长度增加而增加所带来的执行效率的降低
package com.rupeng.wrapper;

public class CharDemo2
{
	public static void main(String[] args)
	{
		String str = "Hello, rp_12306@rupeng.com ! Welcome to Rupeng. "
				+ "Let's play game -- \"Guess number\".";
		int numCount = 0;
		int chrCount = 0;
		int otherCount = 0;
		char[] chs = str.toLowerCase().toCharArray();
		for (int i = 0; i < chs.length; i++)
		{
			//if ("0123456789".indexOf(chs[i]) != -1)
			if(chs[i]>='0'&&chs[i]<='9')
			{
				numCount++;
			} else if (chs[i] >= 'a' && chs[i] <= 'z')
			{
				chrCount++;
			} else
			{
				otherCount++;
			}
		}
		System.out.println(numCount);
		System.out.println(chrCount);
		System.out.println(otherCount);
	}
}
  • c、String类的转换功能
    • (1)public byte[] getBytes() 将字符串转换为字节数组(使用平台的默认字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中)
    • (2)public byte[] getBytes(CharSet charset) since JDK 1.6
    • (3)public byte[] getBytes(String charsetName) throws throws UnsupportedEncodingException since JDK 1.1
    • (4)public void getChars(int srcBegin,int srcEnd,char[] dst,int dstBegin) 将字符串[srcBegin,srcEnd)段长度为srcEnd-srcBegin的字符串获取到目标字符数组dst中,从字符数组的dstBegin处开始插入。
    • (5)public char[] toCharArray() 一个新分配的字符数组,它的长度是此字符串的长度,它的内容被初始化为包含此字符串表示的字符序列。
    • (6)public static String valueOf(int i) 返回 int 参数的转换为字符串表示形式(该表示形式恰好是单参数的 Integer.toString 方法返回的结果)
    • (7) public static String valueOf(long l) 返回 long 参数的转换为字符串表示形式(该表示形式恰好是单参数的 Long.toString 方法返回的结果)
    • (8)public static String valueOf(boolean b) 返回 boolean 参数的字符串表示形式(对应的封装类提供了继承自Object类的非静态的 toString 方法)
    • (9)public static Stiring valueOf(char c) 返回 char 参数的字符串表示形式(对应的封装类提供了继承自Object类的非静态的 toString 方法)
    • (10)public static String valueOf(float f) 返回 float 参数的转换为字符串表示形式(该表示形式恰好是单参数的 Float.toString 方法返回的结果)
    • (11)public static String valueOf(double d) 返回 double 参数的转换为字符串表示形式(该表示形式恰好是单参数的 Double.toString 方法返回的结果)
    • (12)public static String valueOf(Object obj) 返回:如果参数为 null,则字符串等于 “null”;否则,返回 obj.toString() 的值(这说明了,两个方法的区别:后者遇到null值调用会抛出NullPointerException
    • (13)public static String valueOf(char[] data) 返回 char 数组参数的字符串表示形式。字符数组的内容已被复制,后续修改不会影响新创建的字符串。
    • (14)public static String valueOf(char[] data, int offset, int count) 返回 char 数组参数的特定子数组的字符串表示形式。 offset 参数是子数组的第一个字符的索引。count 参数指定子数组的长度。字符数组的内容已被复制,后续修改不会影响新创建的字符串。
    • (15)public String toLowerCase( [Locale]-可有可无参数 ) 转换为小写字符串(新的字符串)。此方法与语言环境有关,若省略locale参数,等效于调用toLowerCase(Locale.getDefault())。对于与语言环境有关的字符,要获得正确的结果,请使用 toLowerCase(Locale.ENGLISH)
    • (16)public String toUpperCase( [Locale]-可有可无参数 ) 转换为大写字符串(新的字符串)。其余同toLowerCase方法。
    • (17)public String concat(String str) 将指定字符串连接到此字符串的结尾,得到的新的字符串。 相比之下 +字符串串联操作符 更常用。
    • (18)pubic String[] split(String regex) 根据给定的正则表达式 regex 拆分字符串,得到字符串数组 (疯狂的正则表达式

示例:将短文内容中每一句话的第一个单词首字母大写,其余小写。其余所有单词小写

package com.rupeng.string;

public class ExchangeDemo
{
	public static void main(String[] args)
	{
		String str = "hello,welcome to Rupeng.let us Study together to Create a BETTER TOMORROW.";
		str = str.toLowerCase();
		String dstStr = "";
		for (int i = 0; i < str.length(); i++)
		{
			if (i == 0)
			{
				dstStr += String.valueOf(str.charAt(i)).toUpperCase();
				continue;
			}
			if (str.charAt(i) == '.' && i != (str.length() - 1))
			{
				dstStr += ".\n" + String.valueOf(str.charAt(++i)).toUpperCase();
				continue;
			}
			dstStr += String.valueOf(str.charAt(i));
		}
		System.out.println(dstStr);
	}
}
  • d、String类的其它功能
    • I、替换功能
      • (1)、public String replace(char oldChar, char newChar) 如果 oldChar 在此 String 对象表示的字符序列中没有出现,则返回对此 String 对象的引用。否则,创建一个新的 String 对象,它所表示的字符序列除了所有的 oldChar 都被替换为 newChar 之外,与此 String 对象表示的字符序列相同。
      • (2)、public String replace(CharSequence target, CharSequence replacement) 使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。该替换从字符串的开头朝末尾执行,例如,用 “b” 替换字符串 “aaa” 中的 “aa” 将生成 “ba” 而不是 “ab”。
        NullPointerException - 如果 target 或 replacement 为 null。 since JDK 1.5
      • (3)、public String replaceAll(String regex, String replacement) 使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。 调用此方法的 str.replaceAll(regex, repl) 形式与以下表达式产生的结果完全相同: Pattern.compile(regex).matcher(str).replaceAll(repl)
    • II、去除字符串两端空格
      • (1)、public String trim() 此字符串移除了前导和尾部空白的副本;如果没有前导和尾部空白,则返回此字符串本身。
    • III、比较功能
      • (1)、public int compareTo(String anotherString) 返回的比较结果存在以下三种情况:1、如果两个字符串完全相同,则返回0(只在方法 equals(Object) 返回 true 时才返回 0);2、如果当前字符串是另一个字符串的子串的话,则返回 this.length()-anotherString.length()的值;3、如果当前字符串和另一个字符串前一部分相同,则返回的是第一个不同字符(所在索引为 k)的差值,亦即this.charAt(k)-anotherString.charAt(k)
        - (2)、public int compareToIgnoreCase(String str) 不区分大小写的比较,返回值和compareTo情况相同

分析:compareTo的实现的源码

public final class String
{
	/** The value is used for character storage. */
	/** String字符串的底层实现是一个字符数组value */
	private final char value[];
	
	public int compareTo(String anotherString)
	{
		/** 自己优化的:如果是同一个字符串的,直接返回0(提高代码的执行效率) */
		if(this == anotherString)
			return 0;
		/** null值不予以判断的原因是:遇到null,会抛出NullPointerException */
		
		/** 获取当前字符串的长度 */
		int len1 = this.value.length;
		/** 获取另一个字符串的长度 */
		int len2 = anotherString.value.length;
		/** 获取两个字符串中较小的长度值 */
		int lim = Math.min(len1, len2);
		/** 定义v1数组,获取当前字符串的副本 */
		char v1[] = this.value;
		/** 定义v1数组,获取另一个字符串的副本 */
		char v2[] = anotherString.value;

		int k = 0;
		/** 遍历两个字符串,依次比较每一个字符 */
		while (k < lim)
		{
			char c1 = v1[k];
			char c2 = v2[k];
			/** 直到第一个不同的字符,就返回两个字符的差值 */
			if (c1 != c2)
			{
			    // 表示从前向后遍历时,第一个不相等的字符的ASCII码差值。
				return c1 - c2;
			}
			k++;
		}
		/** 如果不存在不同字符就直接返回两个字符串的长度差,三种情况:
		 *		1、正整数 :代表着另一个字符串是当前字符串的子串
		 *		2、0 :代表着两个字符串完全相同
		 *		3、负整数 :代表着当前字符串是另一个字符串的子串
		 */
		return len1 - len2;
	}
}

注意:方法源码的分析、模仿

示例1:将字符数组按照指定格式转换为String字符串

package com.rupeng.string;

/**
 * 需求:将字符数组按照指定格式转换为String字符串。 
 * 			譬如:给定char[] chr ={'a','b','c'},转换为String字符串“{a,b,c}”
 */
public class OtherDemo1
{
	public static void main(String[] args)
	{
		char[] chr = new char[] { 'r', 'u', 'p', 'e', 'n', 'g', '!' };
		String str = getString(chr);
		System.out.println(str);
	}

	public static String getString(char[] chr)
	{
		String dst = "";
		dst += "{";
		for (int i = 0; i < chr.length; i++)
		{
			if (i == chr.length - 1)
			{
				dst += chr[i] + "}";
			} else
			{
				dst += chr[i] + ",";
			}
		}
		return dst;
	}
}

示例2:反转String字符串

package com.rupeng.string;

/**
 * 需求:反转String字符串。譬如:给定“gnepur”,反转之后是“rupeng”
 */
public class OtherDemo2
{
	public static void main(String[] args)
	{
		String src = "moc.gnepur.www";
		String dst = reverse(src);
		System.out.println(dst);
	}

	private static String reverse(String src)
	{
		String dst = "";
		for (int i = 0; i < src.length(); i++)
		{
			dst += src.charAt(src.length() - 1 - i);
		}
		return dst;
	}
}

示例3:统计大串中,小串出现的次数

package com.rupeng.string;

public class OtherDemo3
{
	public static void main(String[] args)
	{
		String bigStr = "Hello,are you ready? Welcome to rupeng!";
		String smallStr = "el";
		int count = count(smallStr, bigStr);
		System.out.println(count);
	}
	
	/** 
	 * 	// 开始的count方法原版
	 * 	private static int count(String smallStr, String bigStr)
	 *	{
	 * 		int count = 0; 
	 * 		int index = bigStr.indexOf(smallStr)); 
	 * 		while ((index = != -1) 
	 * 		{
	 * 			 count++; 
	 * 			 index += smallStr.length();
	 * 		 	index = bigStr.indexOf(smallStr, index));
	 * 		} 
	 * 		return count;
	 * 	}
	 */
		 
	/**  优化后的最终版本count方法 */
	private static int count(String smallStr, String bigStr)
	{
		int count = 0;
		int index = 0;
		while ((index = bigStr.indexOf(smallStr, index)) != -1)
		{
			count++;
			index += smallStr.length();
			// bigStr = bigStr.substring(smallStr.length() + index); 
			// 此句效果和 index += smallStr.length(); 相同。
			// 但是每次执行都会产生额外的字符串,浪费内存。
		}
		return count;
	}
}

面试题

1、String str = new String(“Hello”) 和 String str = "Hello"的区别?

package com.rupeng.string;

public class InterviewDemo1
{
	public static void main(String[] args)
	{
		String str1 = new String("Hello");
		String str2 = "Hello";
		System.out.println(str1 == str2);    // false
		System.out.println(str1.equals(str2));    //true
	}
}

区别:

  • String str = new String(“Hello”) 创建了两个对象:一个在堆内存中,一个在方法区的字符串常量池中。
  • String str = “Hello”; 则是直接在字符串常量池中创建了一个对象
  • str1 == str2 为false,是因为 str1 和 str2 的地址值不同。本例分析中的 0x01 != 0x001
  • str1.equals(str2) 为true,是因为 str1 和 str2 的内容相同,均为 方法区字符串常量池中的 “Hello”

2、代码的执行结果如下:请逐个解释!

package com.rupeng.string;

public class InterviewDemo2
{
	public static void main(String[] args)
	{
		String str1 = "Hello";
		String str2 = "World";
		String str3 = "HelloWorld";
		System.out.println(str3 == str1 + str2);	// false
		System.out.println(str3.equals(str1 + str2));	// true
		System.out.println(str3 == "Hello" + "World");	// true
		System.out.println(str3.equals("Hello" + "World")); //true
	}
}

使用反编译工具jd-gui将编译后的字节码文件反编译,结果如下:

package com.rupeng.string;

import java.io.PrintStream;

public class InterviewDemo2
{

	public InterviewDemo2()
	{
	}

	public static void main(String args[])
	{
		String str1 = "Hello";
		String str2 = "World";
		String str3 = "HelloWorld";
		System.out.println(str3 == (new StringBuilder(String.valueOf(str1))).append(str2).toString());
		System.out.println(str3.equals((new StringBuilder(String.valueOf(str1))).append(str2).toString()));
		System.out.println(str3 == "HelloWorld");
		System.out.println(str3.equals("HelloWorld"));
	}
}

对比后发现,对于字面值常量的比较在编译的时候,就已经完成了拼接。所以得到的比较结果全为true。

现在主要的问题对于变量的比较,先来分析一下equals,之前我们equals重写之后比较的是实际内容,所以第二个为true,至于第一个的“==”,因为是变量,所以是先开辟空间,再进行拼接。所以是false。

结论

A:字符串如果是常量想加,先拼接,再到方法区的字符串常量池中去查找,如果有就返回,没有就创建之后返回

B:字符串如果是变量相加,先开辟空间,再进行拼接。

C:反编译的结果证明:字符串之间的“+”串联操作底层依赖的是:StringBuilder的append方法,而字符串的转换(StringBuilder转换成String)则是通过更加高效的toStrong方法实现的,并不使用构造方法 String(StringBuilder builder)

4、字符串缓冲区类:StringBuffer类

分析以下的代码片段:

String str0 = "I" + " love" + " Java";
// 编译器在编译时候进行了优化,实际上就是 String str0 = "I love Java";
String str1 = "I";
String str2 = " love";
String str3 = "Java";
String str4 = str1 + str2 + str3;
// 编译器在编译时,由于str1、str2、str3是变量,所以无法优化
// str4需要等到运行时确认,,在运行时,逐个拼接
// 1、先将str1、str2拼接叨叨临时字符串对象"I love"
// 2、再将临时对象与str3拼接得到 "I love Java" 并创递给 str4
// 需要拼接的字符串越多,得到的临时对象就越多,导致内存极大的浪费

编译器给予的基本优化(编译时):

  • 1、忽略注释
  • 2、代码等价优化(自动拆装箱、+=自动类型转换、字符串常量的拼接优化等)
  • 3、常量运算的优化(包括字符串常量的拼接,上例)

String对于字符串的拼接操作,每次拼接都会产生新的字符串对象,既耗时又消耗额外资源。那么有没有可以解决这个问题的类呢?这就是本节内容StringBuffer类。

0:类的引入及其描述

java.lang不需要导包,编译器高于JDK1.0,是一个最终类 public final class StringBuffer,描述说StringBuffer 是多线程安全的可变字符序列。一个字符串缓冲区就像一个字符串,但可以修改。在任何时候,它包含了一些特定的字符序列,但序列的长度和内容通过某些方法调用可以改变。

线程安全(多线程)

  • 1、安全、同步、数据安全(银行)–多线程、效率低
  • 2、不安全、不同步、效率高(论坛)–单线程、效率高

效率和安全是的一对永恒的矛盾,开发中,尽量兼顾求其一最高(单线程高效、多线程安全)
 
StringBuffer .VS. String

  • 1、前者的长度和内容可以改变,后者不可以
  • 2、字符串的拼接操作,前者在自身拼接,后者每次都需要创建新的字符串,前者较后者,节省资源(时间、空间)

A:成员变量 【Field Summary】

没有

B:构造方法 【Constructor Summary】

  • a、public StringBuffer() 空参构造,创建对象:StringBuffer sb = new StringBuffer(); 创建一个空字符串缓冲区,初始容量为 16 个字符。

  • b、public StringBuffer(int capacity) 创建一个空字符串缓冲区,指定容量为capacity个字符。

  • c、public StringBuffer(String str) 创建一个字符串缓冲区,并将其内容初始化为指定的字符串str。初始容量为 16 加上字符串参数的长度。

  • d、public StringBuffer(CharSequence cs) 创建一个字符串缓冲区,并将其内容初始化为指定的字符序列cs。初始容量为 16 加上字符串参数的长度。 since JDK 1.5

C:成员方法 【Method Summary】

成员方法按照具体使用中功能项来分类

  • a、StringBuffer类的添加功能
    • I、append方法
      • (1)public StringBuffer append([int|long|char|boolean|float|double] var)
      • (2)public StringBuffer append(Object obj)
        参数将被转换成字符串,就好象使用了String.valueOf 方法一样。然后,将所得字符串中的字符追加到此序列。
      • (3)public StringBuffer append(char[] chr) 将 char 数组参数的字符串表示形式追加到此序列。
      • (4)public StringBuffer append(char[] chr,int offset,int len) 将 char 数组参数的从offset索引处截取长度为len的子数组,并转换为字符串表示形式追加到此序列。
        **PS:**此两者方法的最终效果与以下操作过程的效果相同:先使用 String.valueOf(char[]) 方法将参数转换为字符串,然后将所得字符串的字符追加到此字符序列。底层依赖的都是 System.arraycopy 方法。
    • I、append方法
      • (5)public StringBuffer append(CharSequence s) 按顺序将 CharSequence 参数中的字符追加到该序列中,使该序列增加该参数的长度。 该方法的结果与调用 this.append(s, 0, s.length()) 的结果完全相同; 该方法在 this(目标)对象上实现同步,但不在源(s)上实现同步。 如果 s 为 null,则追加 4 个 “null” 字符。 since JDK 1.5
      • (6)public StringBuffer append(CharSequence s,int start,int end) 按顺序追加参数 s 中的字符,即从索引 start 开始到索引 end 结束的此序列的内容。此序列增加的长度为 end - start。是(9)的底层实现方式。如果 s 为 null,则追加 4 个 “null” 字符。 since JDK 1.5
      • (7)public StringBuffer append(String str) 按顺序追加 String 变量中的字符,使此序列增加该变量的长度。如果 str 为 null,则追加 4 个字符 “null”。
      • (8)public StringBuffer append(StirngBuffer sb) 按顺序将 StringBuffer 参数中的字符追加到此 StringBuffer 中,并使 StringBuffer 在长度上增加该参数的长度。如果 sb 为 null,则将 4 个 “null” 字符追加到此 StringBuffer 中。 since JDK 1.4
    • II、insert方法
      • (1)public StringBuffer insert(int offset,[int|long|char|boolean|float|double] var)
      • (2)public StringBuffer insert(int offset,Object obj)
        第二个参数将被转换成字符串,就好象使用了String.valueOf 方法一样。然后,将所得字符串中的字符插入到此序列的offset索引处。
    • II、insert方法
      • (3)public StringBuffer insert(int offset,char[] chr) 将 char 数组参数的字符串表示形式插入到此序列的offset索引处。
      • (4)public StringBuffer insert(int offset,char[] chr,int start,int len) 将 char 数组参数的从start索引处截取长度为len的子数组,并转换为字符串表示形式插入到此序列的offset索引处。
        **PS:**此两者方法的最终效果与以下操作过程的效果相同:先使用 String.valueOf(char[]) 方法将参数转换为字符串,然后将所得字符串的字符插入到此序列的offset索引处。
    • II、insert方法
      • (5)public StringBuffer insert(int offset,CharSequence s) 按顺序将 CharSequence 参数中的字符插入到此序列的offset索引处,使该序列增加该参数的长度。 该方法的结果与调用 this.append(s, 0, s.length()) 的结果完全相同; 该方法在 this(目标)对象上实现同步,但不在源(s)上实现同步。 如果 s 为 null,则追加 4 个 “null” 字符。 since JDK 1.5
      • (6)public StringBuffer insert(int offset,CharSequence s,int start,int end) 从此序列的offset索引处,按顺序插入参数 s 中的字符,即从索引 start 开始到索引 end 结束的此序列的内容。如果 s 为 null,则追加 4 个 “null” 字符。 since JDK 1.5
      • (7)public StringBuffer insert(String str) 按顺序追加 String 变量中的字符,使此序列增加该变量的长度。如果 str 为 null,则追加 4 个字符 “null”。

添加功能的操作结果返回的仍是StringBuffer对象本身(链式编程)。

  • b、StringBuffer类的删除功能
    • (1)public StringBuffer delete(int start,int end) 移除此序列的子字符串中的字符。该子字符串从指定的 start 处开始,一直到索引 end - 1 处的字符,如果不存在这种字符,则一直到序列尾部。如果 start 等于 end,则不发生任何更改。
    • (2)public StringBuffer deleteCharAt(int index) 移除此序列指定位置的 char。此序列将缩短一个 char。

删除功能的操作结果返回的仍是StringBuffer对象本身(链式编程)。
清空缓冲区的操作: sb.delete(0,sb.length())

  • c、StringBuffer类的替换功能
    • (1)public StringBuffer replace(int start,int end,String str) 用str替换字符串缓冲区中[start, end)的长度为end-start的字符串。

替换功能的操作结果返回的仍是StringBuffer对象本身(链式编程)。

  • d、StringBuffer类的反转功能
    • (1)public StringBuffer reverse() 将此字符序列用其反转形式取代。

反转功能的操作结果返回的仍是StringBuffer对象本身(链式编程)。

  • e、StringBuffer类的获取功能
    • (1)public int capacity() 获取容量值
    • (2)public int length() 获取长度值
    • (3)public int indexOf(String str) since JDK 1.4
    • (4)public int indexOf(String str,int fromindex) since JDK 1.4
    • (5)public int lastIndexOf(String str) since JDK 1.4
    • (6)public int lastIndexOf(String str,int fromindex) since JDK 1.4
    • (7)public String substring(int start)
    • (8)public String substring(int start,int end)
    • (9)public CharSequence subSequence(int start,int end) 返回一个新的字符序列,该字符序列是此序列的子序列。 调用该方法的形式为: sb.subSequence(begin, end)与下列方法调用完全相同: sb.substring(begin, end)

获取功能得到的是新的对象,字符串缓冲区对象本身不改变。尤其注意最后的sub开头的方法的返回值(使用String类方法的链式编程)。

  • f、StringBuffer类的转换功能
    • (1)public String toString() 返回此序列中数据的字符串表示形式。
    • (2)public void getChars(int srcBegin,int srcEnd,char[] dst,int dstBegin) 将字符从此序列复制到目标字符数组 dst。

转换功能的操作结果返回的是新的String字符串,字符串缓冲区对象本身不改变。(使用String类方法的链式编程)。

  • g、StringBuffer类的设置功能
    • (1)public void setLength(int newLength) 设置字符序列的长度。如果 newLength 参数大于或等于当前长度,则将追加有效的 null 字符 (‘\u0000’),使长度满足 newLength 参数。
    • (2)public void setCharAt(int index,char ch) 将给定索引处的字符设置为 ch。

设置功能改变的是StringBuffer对象本身!

D:常用编程技巧:【Art of Programming】

  • a、String与StringBuffer的相互转换
    • I、String --> StringBuffer : String需要使用StringBuffer类的功能
      • (1)构造方法:public StringBuffer(String str)
      • (2)成员方法:new StringBuffer().appeend(String)(推荐方式)、new StringBuffer().insert(0,String)
    • II、StringBuffer --> String : StringBuffer需要使用String类型结果
      - (1)构造方法:public String(StringBuffer)
      - (2)成员方法:String.valueOf(StringBuffer)sb.toString()(推荐方式)、sb.substring(0)\sb.substring(0,sb.length())

编程语言中,由对象调用方法,并且方法的返回值仍是一个对象,进而继续调用返回的对象的方法,由此构成一个方法链的现象,即为 链式编程

链式编程-3示例

重构-示例1:将字符数组按照指定格式转换为String字符串

package com.rupeng.stringbuffer;

public class StringBufferDemo1
{
	public static void main(String[] args)
	{
		char[] chr = new char[] { 'r', 'u', 'p', 'e', 'n', 'g', '!' };
		String str = getString(chr);
		System.out.println(str);
	}

	private static String getString(char[] chr)
	{
		StringBuffer dst = new StringBuffer();
		dst.append("{");
		for (int i = 0; i < chr.length; i++)
		{
			if (i == chr.length - 1)
			{
				dst.append(chr[i]);
			} else
			{
				dst.append(chr[i]).append(",");
			}
		}
		dst.append("}");
		return dst.toString();
	}
}

重构-示例2:反转字符串

package com.rupeng.stringbuffer;

public class StringBufferDemo2
{
	public static void main(String[] args)
	{
		String src = "moc.gnepur.www";
		StringBuffer dst = new StringBuffer();
		dst.append(src).reverse();
		System.out.println(dst);
		// 等价于: new StringBuffer().append(src).reverse().toString();
		// 亦等价: new StringBuffer(src).reverse().toString(); 
	}
}

示例3:判断字符串是否对称

package com.rupeng.stringbuffer;

public class StringBufferDemo3
{
	public static void main(String[] args)
	{
		String src = "moc.gnepur.www.rupeng.com";
		boolean symmetric = new StringBuffer(src.substring(0,str.length()/2+1)).reverse().toString().equals(src.substring(str.length()/2,str.length()));
		// new StringBuffer().append(src).reverse().substring(0).equals(src);
		System.out.println(symmetric);
	}
}

面试题

1、StringBuilder类和StringBuffer类具有相同的构造和成员方法,选用规则?

解答
(1)StringBuilder是一个可变字符序列,提供与StringBuffer类兼容的API,但不保证同步(线程安全)。该类被设计用来作为StringBuffer类的简单替换,用在字符串缓冲区被单线程调用的时候(这种情况很普遍)。若可能建议优先使用StringBuilder类,因为在大多数的实现中,StringBuilder类更加高效。

(2)主要方法:append和insert(this.append(x)等价于this.insert(this.length,x))
注:对于多线程的应用,建议使用StringBuffer类(一个线程安全的可变字符串:多线程、同步、数据安全),而StringBuiler类(单线程、不同步、高效率)相对不安全。


2、String、StringBuilder、StringBuffer 三者的区别?

解答
(1)String不可变,StringBuilder、StringBuffer内容和长度可变
(2)StringBuilder 不安全、不同步、数据不安全;StringBuffer 安全、同步、数据安全
(3)StringBuilder 单线程高效;StringBuffer 多线程安全


3、StringBuffer 和 数组的区别?

解答
(1)相同点:两者都可以作为容器,装入各种类型的数据
(2)不同点:同一StringBuffer可以装入不同类型数据,长度可变,最终是字符串;同一数组只可以装入同一类型的数据,长度不可变。


4、String作为参数传递与StringBuffer作为参数传递有什么不同之处?

解答
(1)String作为参数传递时,因为其不可变性(当作字符串常量),和基本数据类型一样,只是传递了值的副本。(内存位置:方法区的字符串常量池中)
(2)StringBuffer作为参数传递,简单赋值和判断,值不变。调用方法改变值。


Java中的值传递和引用传递

当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递? 答:是值传递。Java 编程语言只有值传递参数。当一个对象实例作为一个参数被传递到方法中时,参数的值就是该对象引用的一个副本。指向同一个对象,对象的内容可以在被调用的方法中改变,但对象的引用(不是引用的副本)是永远不会改变的。

Java参数,不管是原始类型还是引用类型,传递的都是副本(有另外一种说法是传值,但是说传副本更好理解吧,传值通常是相对传址而言)。如果参数类型是原始类型,那么传过来的就是这个参数的一个副本,也就是这个原始参数的值,这个跟之前所谈的传值是一样的。如果在函数中改变了副本的 值不会改变原始的值。如果参数类型是引用类型,那么传过来的就是这个引用参数的副本,这个副本存放的是参数的地址。如果在函数中没有改变这个副本的地址,而是改变了地址中的 值,那么在函数内的改变会影响到传入的参数。如果在函数中改变了副本的地址,如new一个,那么副本就指向了一个新的地址,此时传入的参数还是指向原来的 地址,所以不会改变参数的值。另外:原始数据类型是没有办法通过void函数的形式改变值的。除非具有非void返回值,并且使用具体变量去接受函数的返回值。


5、数组工具类:Arrays类

在上一个类的示例中,我们实现了将数组的转换为字符串的功能,那么有没有可能在 API 中存在这样的方法可以直接供我们调用呢,就像是有关数学的操作会有 Math 出现一样呢?答案是肯定的,这就是我们要说得 Arrays 类。

0:类的引入及其描述

java.lang不需要导包,编译器高于JDK1.2,不是一个最终类public class Arrays,可以被继承(Math同样是工具类,但是是最终类,不可继承)。描述说Arrays类包含用来操作数组(比如排序和搜索)的各种方法。此类还包含一个允许将数组作为列表来查看的静态工厂asList方法。 除非特别注明,否则如果指定数组引用为 null,则此类中的方法都会抛出NullPointerException。(toString方法除外) since JDK 1.2

如何使用API,这个已经说明了的问题,你还记得吗?当我们看到Arrays直接出现的方法摘要【Method Summary】的时候,我们该想到什么?对,就是它没有字段摘要、也没有构造摘要,说明没有成员变量,而且私有构造。再往下看,我们看到了所有得方法都是静态的,这一切就都有答案了,Arrays是工具类。所有的方法使用类名调用就可以了。

A:成员变量【Field Summary】

没有

B:构造方法【Constructor Summary】

仅存在一个private Arrays() { }私有构造,不可创建子类对象。

C:成员方法 【Method Summary】

按照功能分类讲解

  • a、Arrays类的排序功能
    • (1)public static void sort(数据类型[]) 数据类型可以是 byte、short、int、long、float、double、char。实现排序。底层原理是:快速排序
    • (2)public static void sort(数据类型[],int from,int to) 将数组其中的一段 [ from to ) 进行排序。
    • (3)public static void sort(Object[] o) 根据元素的自然顺序对指定对象数组按升序进行排序。引用类型在使用此方法的时候,通过具体类的多态实现,就是说实际传进方法的是Object的子类对象或者子类的子类的对象…,但是传进方法的参数必须是都必须实现 自然排序接口 Comparable
    • (4)public static void sort(Object[] o,int from,int to) 将数组其中的一段 [ from to ) 进行排序。
    • (5)public static <T> void sort(T[] a, Comparator<? super T> c) 根据指定比较器产生的顺序对指定对象数组进行排序。(后面学习了比较器接口和泛型之后再说明)
    • (6)public static <T> void sort(T[] a,int fromIndex,int toIndex,Comparator<? super T> c) 根据指定比较器产生的顺序对指定对象数组的指定范围进行排序。
  • b、Arrays类的查找功能
    • (1)public static int binarySearch(数据类型[], 数据类型 key) 数据类型可以是 byte、short、int、long、float、double、char。实现查找。底层原理是:二分查找
    • (2)public static int binarySearch(数据类型[],int from,int to, 数据类型 key) 将数组其中的一段 [ from to ) 进行查找。
    • (3)public static int binarySearch(Object[] o, 数据类型 key) 使用二分搜索法来搜索数组,以获得指定对象。
    • (4)public static int binarySearch(Object[] o,int from,int to, 数据类型 key) 使用二分搜索法来搜索指定数组的范围,以获得指定对象。
    • (5)public static <T> int binarySearch(T[] a, Comparator<? super T> c) 使用二分搜索法来搜索指定数组,以获得指定对象。在进行此调用之前,必须根据指定的比较器(通过 sort(T[], Comparator) 方法)对数组进行升序排序。
    • (6)public static <T> int binarySearch(T[] a,int fromIndex,int toIndex,Comparator<? super T> c) 使用二分搜索法来搜索指定的数组范围,以获得指定对象。在进行此调用之前,必须根据指定的比较器(通过 sort(T[], Comparator) 方法)对数组进行升序排序。
      PS: 在使用binarySearch方法之前,必须要先执行对应的sort方法,可以使用自然排序,也可以自己指定排序方式。否则结果是不一定的。

示例:分析底层源码:

/** 不是最终类,可以被继承。(Math同样是工具类,但是是最终类,不可继承)*/
public class Arrays 
{
	/** 构造方法私有,不可创建对象 */
	private Arrays() { }
	
	public static int binarySearch(int[] a, int key)
	{
		return binarySearch0(a, 0, a.length, key);
	}
	
	/** binarySearch方法的底层实现 */
	private static int binarySearch0(int[] a, int fromIndex, int toIndex,
			int key)
	{
		int low = fromIndex;
		int high = toIndex - 1;

		while (low <= high)
		{
			int mid = (low + high) >>> 1;
			int midVal = a[mid];

			if (midVal < key)
				low = mid + 1;
			else if (midVal > key)
				high = mid - 1;
			else
				/** key found,指定的元素就直接返回元素的所在索引位置 */
				return mid;
		}
		/**
		 * key not found, 若是key小于数组中的最小元素的话,low = 0 ,返回 -1;
		 * 							若是key大于数组中的最大元素的话,low = length ,返回 -length -1;
		 * 							若是key介于数组中的 i 索引处的值和 i+1 索引处的值 的话,low = i+1,返回 -i -2
		 */
		return -(low + 1);
	}
}

说明:提高程序的健壮性,方法的返回值要有意义,而不是中文提示之类的

  • c、Arrays类的转换功能
    • (1)public static String toString(数据类型[]) 数据类型可以基本数据类型(原始数据类型) boolean、char、byte、short、int、long、float、double;也可以是引用数据类型的Object。

示例:分析底层源码:

/** 不是最终类,可以被继承。(Math同样是工具类,但是是最终类,不可继承)*/
public class Arrays
{
	/** 构造方法私有,不可创建对象 */
	private Arrays() { }

	/**
	 * @param a
	 *            the array whose string representation to return
	 * @return a string representation of <tt>a</tt>
	 * @since 1.5
	 */
	public static String toString(int[] a)
	{
		if (a == null)
			/** 数组是null时,返回字符串"null",而不是触发异常NullPointerException . 提高代码健壮性*/
			return "null";
		/** 获取数组的最大索引 */
		int iMax = a.length - 1;
		if (iMax == -1)
			/** 如果数组为空,即不存在任何的元素的话,返回 "[]" */
			return "[]";
		/** 如果数组不为null,且飞空的话,使用StringBuilder对象进行字符串的拼接 */
		StringBuilder b = new StringBuilder();
		b.append('[');
		for (int i = 0;; i++)
		{
			/** 非最终元素,先拼接元素的值 */
			b.append(a[i]);
			if (i == iMax)
				/** 如果是最后一个元素的话,拼接上反中括弧,并返回 */
				return b.append(']').toString();
			/** 非最终元素,再拼接"," */
			b.append(", ");
		}
	}
}
  • d、Arrays类的复制功能

    • (1)public static 数据类型[] copyOf(数据类型[],int length) 数据类型可以基本数据类型(原始数据类型) boolean、char、byte、short、int、long、float、double;也可以是引用数据类型的Object。length是指从当前数组的开始索引 0 处开始要复制的数组的元素的个数,也就是新数组的长度。注意常见的数组的异常,以及from和to的值可能引发的异常。譬如 NegativeArraySizeException(length < 0)、NullPointerException(array = null)
    • (2)public static 数据类型[] copyOfRange(数据类型[],int from,int to) 数据类型可以基本数据类型(原始数据类型) boolean、char、byte、short、int、long、float、double;也可以是引用数据类型的Object。from 是指当前数组开始复制的起始位置(包含),to 是指结束位置(不包含)。注意常见的数组的异常,以及from和to的值可能引发的异常。譬如 ArrayIndexOutOfBoundsException(from < 0 或 from > original.length())、NullPointerException(array = null)、IllegalArgumentException (from > to)
  • e、Arrays类的其它方法

    • fill([] a, [int fromindex, int endindex, ] val):填充。使用val全填数组a,亦即是用val初始化a中每个元素,或者是填充指定索引位置的元素。
    • equals():判断相等与否
    • hashCode():获取哈希值(参数列表和binarySearch完全相同(九种:四类八种的基本类型加上引用类型的Object))

小总结:

在这里插入图片描述

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老坛算粉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值