一、String类
1、String介绍
String类是代表字符串的类。字符串是常量,创建后值不可更改。因为字符串是常量,所以可以被共享。
- String类实现了序列化接口Serializable 支持序列化和反序列化开发。
- String类实现了比较器接口Comparable 支持自然比较。
- String类实现了字符序列化接口CharSequence 底层采用字符序列存储数据。
- String是一个final类,代表不可以有子类;而底层采取final char[] 存储字符串。由此可以推断出String类代表的是一个不可变字符序列。
2、String类的基本使用
2.1 String类构造方法
-
String() 创建一个空字符串。
-
String(byte[] bytes) 将byte数组按照平台默认编码转为字符串。
-
String(byte[] bytes, String charsetName) 将byte数组按照指定编码转为字符串。
-
String(byte[] bytes, int offset, int length) 将byte数组的部分内容按照平台默认编码转为字符串;需要设置开始下标,长度;
-
String(byte[] bytes, int offset, int length, String charsetName) 将byte数组的部分内容按照指定编码转为字符串;需要设置开始下标,长度;
-
String(char[] value) 将char数组转为字符串。
-
String(char[] value, int offset, int count) 将char数组的部分内容转为字符串;需要指定开始下标,长度;
代码演示:
/**
* String字符串对象的创建演示
*/
public class TestString {
public static void main(String[] args) throws UnsupportedEncodingException {
/**
* 1) 直接赋值字符串字面量
* String是类名, 其实就是一个数据类型, s1变量名,也称为对象名, 双引号引起来的"hello"才是真正的String对象,
* s1变量保存的是"hello"对象的引用
*/
String s1 = "hello";
System.out.println("s1=" + s1);
/**
* 2) String (byte [] bytes ) 根据字节数组创建String对象
*/ 67, 68, 69
byte[] bytes = {65, 66,, 70, 71, 72, 73};//手动定义字节数组
//把bytes字节数组中所有字节, 以当前默认的字符编码UTF-8解析为字符串
String s2 = new String(bytes);
//ABCDEFGHI
System.out.println("s2=" + s2);
/**
* 把bytes数组中从0开始的6个字节解析为字符串
*/
s2 = new String(bytes, 0, 6);
//ABCDEF
System.out.println("s2=" + s2);
/**
* 指定字符编码
* 计算机中, 不管是CPU, 内存, 还是硬盘上,所有的数据都是01二进制形式的, 8个01二进制位组成1个byte字节.
*
* 记事本, Editplus, IDEA文本编辑器中打开文本文件, 显示字符数据, 也可以在文本文件中直接输入字符保存到文件中.
* 这就涉及一个字符与01二进制之间的一个转换问题, 在字符与01序列之间进行转换需要使用字符编码
*
* 字符编码就是字符与一组01序列之间的映射, 这一组01序列称为码值, 8个01二进制位组成1个字节.
* 把字符转换为01序列(码值)过程称为编码, 把一组01序列(码值)转换为字符称为解码,使用文本编辑器打开文本文件时,
* 使用默认的字符编码把文件中的01序列解析为字符显示出来; 在文本编辑器输入文本后, 使用默认的字符编码把这些文本编码为
* 01序列(码值)保存到文件中,将来可能会从文件中读取一组01序列,或者从网络中读取一组01序列, 可以把这一组01序列
* 转换为字符串, 就调用String(byte [] bytes)构造方法,利用字符编码可以巧妙的解决乱码问题
*
* //把bytes数组中所有的字节, 以指定的GB2312编码解析为字符串, 调用String(byte [] bytes, String charsetName)构造方法,
* 会显示语法错误:Unhandled exception: java.io.UnsupportedEncodingException未处理的异常,
* 即这个构造方法有检查异常需要预处理, 当前Alt + Enter, 选择 Add exception to method signature抛出处理,
* 当程序支行后, 如果指定的字符编码Java不支持就会抛出异常
*/
s2 = new String("犯我中华者虽远必诛");
byte[] bs = s2.getBytes("UTF-8");//编码
s2 = new String(bs, "UTF-8");//解码
System.out.println("s2=" + s2);
/**
* 3) String(char [] chars) 把字符数组中的字符连接为字符串
*/
char[] chars = {'a', 'b', '5', '汉', '字', 97};
//char字符类型的变量实际存储的是字符的码值, 给cc变量分配2个字节存储空间, 在这存储空间中存储的是'a'字符的码值97,
//对应的01序列为: 00000000 01100001
char cc = 'a';
//把chars数组中所有的字符连接为字符串
String s3 = new String(chars);
//ab5汉字a
System.out.println(s3);
//把chars数组中从0开始的5个字符连接为字符串
s3 = new String(chars, 0, 5);
System.out.println(s3);
/**
* 4) String()
* new运算符在堆中创建对象, 把对象的引用赋值给数组名
*/
String s4 = new String();
//false
System.out.println(s4 == null);
//s4未赋值,地址中没有存东西,为空白。
/**
* 5) String( String )
*/
String s5 = new String(s1);
//hello
System.out.println(s5);
//false
System.out.println(s1 == s5);
}
}
2.2 从底层来看String对象的创建
3、 String类的内存分布
3.1、字符串常量的存储位置
1.String str1="abc"; String str2=new String("abc");
- JDK1.7之前,字符串常量池放在方法区中,这时的方法区也叫做永久代;
- JDK1.7时,常量池是放在堆空间中的;静态变量(静态域)也放在堆空间中。
- 此时常量池所在的堆空间的这个位置也叫做永久代。
- JDK1.8,常量池还在堆内存中,只不过常量池所在的堆空间的这个位置不叫做永久代了,而是叫做元空间.
- 总结:从JDK1.7开始,字符串常量池就放在堆中
1.为什么要把字符串常量池所在的方法区改为堆空间呢?
垃圾回收(GC):栈中没有垃圾回收的概念,堆中是有的,而且堆空间是我们频繁需要GC的一个区域。 但方法区很少GC,因为方法区中,主要加载的是类的一些信息。这些类的信息回收没有什么用。你也回收不掉。所以方法区很少被回收。但是字符串常量池空间是需要回收的,所以把字符串常量池放在不被回收的方法区中,那么方法区就会越来越大。所以我们就把方法区字符串常量池移出来放在了堆空间中。
2.JDK8为什么把方法区叫做叫元空间呢?
主要原因就是,从JDK8开始使用的是我们本地的物理内存。因为方法区的回收效果不好,那干脆直接使用本地的物理内存。这个本地的物理内存就是元空间。
3.2、 字符串字面量与字符串对象的区别
1、掌握以下3个结论:
- 常量 + 常量: 结果仍然存储在字符串常量池中,返回此字面量的地址。注:此时的常量可能是字面量,也可能是final修饰的常量
- 常量 + 变量 或 变量 + 变量 :都会通过new的方式创建一个新的字符串,返回堆空间中此字符串对象的地址
- 调用字符串的intern(): 返回的是字符串常量池中字面量的地址。
- concat(xxx): 不管是常量调用此方法,还是变量调用,同样不管参数是常量还是变量,总之,调用完concat()方法都返回一个新new的对象。
总结:
- 常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
- 常量与字符串变量拼接会在堆内存创建对象
- 如果拼接的结果调用intern()方法,返回值就在常量池中
- 调用完concat()方法都返回一个新new的对象。
3.3、 不可变性(地址的不可变)
1.当对字符变量重新赋值时,需要重新指定一个字符串常量的位置进行赋值,不能在原有的位置修改。
2.当对现有的字符串进行拼接操作时,需要重新开辟空间保存拼接以后的字符串,不能在原有的位置修改。使用加号+对字符串连接会生成新的字符串对象, 频繁字符串连接,不建议使用String。
String s1 = "hello";
String s2 = "hello";
s2 += "world";
System.out.println(s1); //hello
System.out.println(s2);//helloworld
3.当对现有的字符串进行替换操作时,需要重新开辟空间保存替换以后的字符串,不能在原有的位置修改。
String s1 = "hello";
String s2 = "hello";
String s3 = s2.replace('l', 'w');
System.out.println(s1);//hello
System.out.println(s2);//hello
System.out.println(s3);//hewwo
不可变的本质 :
String底层是字符数组常量,因此String类的所有方法都不会改变源字符串,而是返回一个新的字符串。
因为String底层使用字符数组保存字符串的每个字符, 该字符数组使用final修饰无法重新赋值(地址的改变), String类也没有提供相应的方法修改value数组的元素, 并且String类还是final修饰的,无法定义子类.
4、String类下常用方法
4.1、判断类
boolean contains(CharSequence s) 判断当前字符串中是否包含s。
boolean endsWith(String suffix) 判断当前字符串是否以suffix结尾。
boolean startsWith(String prefix) 判断当前字符串是否以prefix开始。
/**
boolean contains(CharSequence s) 判断当前字符串中是否包含s
boolean startsWith(String prefix) 判断当前字符串是否以prefix开始
boolean endsWith(String suffix) 判断当前字符串是否以suffix结尾
*/
String s = "hello中国";
System.out.println( s.contains("hello"));//true
System.out.println( s.startsWith("hello"));//true
System.out.println( s.endsWith("hello"));//false
4.2、获取类
- 返回字符串长度
int length();
String s = "A small step forward,A big step of civilization";
System.out.println(s.length());
2.将指定的字符串连接到该字符串的末尾。
String concat(String str)
String s1="Hello";
String s2="World";
String s=s1.concat(s2);
System.out.println(s);//HelloWorld
3.返回索引处的char值
char chatAt(int index)
4.int intdexOf(String str)//返回指定字符串第一次出现在该字符串的索引
5.String substring(int beginIndex)//返回一个子字符串,从beginIndex开始截取字符串到字符串结尾。
6. String substring(int beginIndex,int endIndex)//返回一个子字符串,从beginIndex开始截取字符串,含beginIndex,不含endIndeStri
7. int lastIndexOf(String str)//返回str在当前字符串中最后一次出现的索引值。
String path = "D:\\course\\03-JavaSE\\Day04\\Code\\src\\string\\Test01.java";
//把文件名Test01.java取出来, 需要确定反斜杠最后一次出现的索引值.
int slash = path.lastIndexOf("\\");
String filename = path.substring(slash + 1);
System.out.println(filename);
8. String trim() 去掉字符串前后的空白符,返回新的字符串,原来字符串不变。
str = " Hello world ";
String trim = str.trim();
System.out.println("--" + trim + "--");//--Hello world--
System.out.println("--" + str + "--"); //-- Hello world --
4.3、转换类
1. String与基本数据类型、包装类转换:
2.String与字符数组转换:
3.String与字节数组转换:
4.4.分割类的
4.5、比较类的
- int compareTo(String anotherString)//直接比较字符串的大小。
- compareToIgnoreCase(String str) //忽略大小后再比较字符串的大小。
public class TestString01 {
public static void main(String[] args) {
System.out.println("hello".compareTo("hehe"));//4, 正数表示前面字符串大
System.out.println("hello".compareTo("helloworld"));//-5 ,负数表示第二个字符串大
System.out.println("hello".compareTo("z"));//-18, 负数表示第二个字符串大
/**
* int compareTo(String anotherString)
比较当前字符串与参数字符串大小, 如果当前字符串大返回正数,参数字符串大返回负数
int compareToIgnoreCase(String str) 忽略大小写再判断字符串大小
String类实现了Comparable接口, 重写了compareTo()方法, 在该方法中定义了字符串的比较规则:
1)逐个比较两个字符串的每个字符, 遇到第一个不相等的字符码值相减;
2)如果比较过的字符串都一样,再比较两个字符串的长度. 如果compareTo()方法返回正数表示前面第一个字符串大,
3)返回0表示两个字符串相等, 返回负数表示参数第二个字符串大
*/
System.out.println("hello".compareTo("Hello"));//32, 区分大小写
/**
* 如果不区分大小写比较,调用compareToIgnoreCase()
*/
System.out.println("hello".compareToIgnoreCase("HEllo")); //0
String str = "hello";
System.out.println("hello".compareTo(str));//0
str = null;
//NullPointerException, 在调用compareTo()比较两个字符串大小时, 参数字符串不允许为null, 否则会产生空指针异常
System.out.println("hello".compareTo(str));
}
}
4.6 正则表达式类
正则表达式就是一个模式串, 验证字符串是否匹配指定的模式。
正则表达式的终极定义:
正则表达式(英语:Regular Expression,在代码中常简写为regex)。正则表达式是一个字符串,使用单个字符串来描述、用来定义匹配规则,匹配一系列符合某个句法规则的字符串。在开发中,正则表达式通常被用来检索、替换那些符合某个规则的文本。
1.String类中与正则表达式相关的方法
boolean matches(String regex) | 判断字符串是否匹配指定的regex正则表达式。 |
String replaceAll(String regex, String replacement) | 把当前字符串中符合Regex正则表达式的字符串使用replacement替换,返回替换后的字符串。 |
String[] split(String regex) | 把当前字符串使用符合regex正则表达式的字符串拆分 ,返回拆分 后的字符串组成 的数组。 |
2.正则表达式的匹配规则
-
字符类匹配规则
-
边界匹配器
-
数量词
5、String算法
1.模拟一个trim方法,去除字符串两端的空格。
public String myTrim(String str) {
if (str != null) {
int start = 0;// 用于记录从前往后首次索引位置不是空格的位置的索引
int end = str.length() - 1;// 用于记录从后往前首次索引位置不是空格的位置的索引
while (start < end && str.charAt(start) == ' ') {
start++;
}
while (start < end && str.charAt(end) == ' ') {
end--;
}
if (str.charAt(start) == ' ') {
return "";
}
return str.substring(start, end + 1);
}
return null;
}
@Test
public void testMyTrim() {
String str = " a ";
// str = " ";
String newStr = myTrim(str);
System.out.println("---" + newStr + "---");
}
2.将一个字符串进行反转。将字符串中指定部分进行反转。
/ 方式一:
public String reverse1(String str, int start, int end) {// start:2,end:5
if (str != null) {
// 1.
char[] charArray = str.toCharArray();
// 2.
for (int i = start, j = end; i < j; i++, j--) {
char temp = charArray[i];
charArray[i] = charArray[j];
charArray[j] = temp;
}
// 3.
return new String(charArray);
}
return null;
}
// 方式二:
public String reverse2(String str, int start, int end) {
// 1.
String newStr = str.substring(0, start);// ab
// 2.
for (int i = end; i >= start; i--) {
newStr += str.charAt(i);
} // abfedc
// 3.
newStr += str.substring(end + 1);
return newStr;
}
// 方式三:推荐 (相较于方式二做的改进)
public String reverse3(String str, int start, int end) {// ArrayList list = new ArrayList(80);
// 1.
StringBuilder s = new StringBuilder(str.length());
// 2.
s.append(str.substring(0, start));// ab
// 3.
for (int i = end; i >= start; i--) {
s.append(str.charAt(i));
}
// 4.
s.append(str.substring(end + 1));
// 5.
return s.toString();
}
@Test
public void testReverse() {
String str = "abcdefg";
String str1 = reverse3(str, 2, 5);
System.out.println(str1);// abfedcg
}
3.获取一个字符串在另一个字符串中出现的次数。
public int getCount(String mainStr, String subStr) {
if (mainStr.length() >= subStr.length()) {
int count = 0;
int index = 0;
// while((index = mainStr.indexOf(subStr)) != -1){
// count++;
// mainStr = mainStr.substring(index + subStr.length());
// }
// 改进:
while ((index = mainStr.indexOf(subStr, index)) != -1) {
index += subStr.length();
count++;
}
return count;
} else {
return 0;
}
}
@Test
public void testGetCount() {
String str1 = "cdabkkcadkabkebfkabkskab";
String str2 = "ab";
int count = getCount(str1, str2);
System.out.println(count);
}
4.获取两个字符串中最大相同子串。
// 如果只存在一个最大长度的相同子串
public String getMaxSameSubString(String str1, String str2) {
if (str1 != null && str2 != null) {
String maxStr = (str1.length() > str2.length()) ? str1 : str2;
String minStr = (str1.length() > str2.length()) ? str2 : str1;
int len = minStr.length();
for (int i = 0; i < len; i++) {// 0 1 2 3 4 此层循环决定要去几个字符
for (int x = 0, y = len - i; y <= len; x++, y++) {
if (maxStr.contains(minStr.substring(x, y))) {
return minStr.substring(x, y);
}
}
}
}
return null;
}
// 如果存在多个长度相同的最大相同子串
// 此时先返回String[],后面可以用集合中的ArrayList替换,较方便
public String[] getMaxSameSubString1(String str1, String str2) {
if (str1 != null && str2 != null) {
StringBuffer sBuffer = new StringBuffer();
//List<String> list = new ArrayList<String>();
String maxString = (str1.length() > str2.length()) ? str1 : str2;
String minString = (str1.length() > str2.length()) ? str2 : str1;
int len = minString.length();
for (int i = 0; i < len; i++) {
for (int x = 0, y = len - i; y <= len; x++, y++) {
String subString = minString.substring(x, y);
if (maxString.contains(subString)) {
sBuffer.append(subString + ",");
//list.add(subString);
}
}
System.out.println(sBuffer);
if (sBuffer.length() != 0) {
break;
}
}
String[] split = sBuffer.toString().replaceAll(",$", "").split("\\,");
return split;
}
return null;
}
@Test
public void testGetMaxSameSubString() {
String str1 = "abcwerthelloyuiodef";
String str2 = "cvhellobnmiodef";
String[] strs = getMaxSameSubString1(str1, str2);
System.out.println(Arrays.toString(strs));
}
5.对字符串中字符进行自然顺序排序
@Test
public void testSort() {
String str = "abcwerthelloyuiodef";
char[] arr = str.toCharArray();
Arrays.sort(arr);
String newStr = new String(arr);
System.out.println(newStr);
}
二、可变字符串类
1、StringBuffer
可变的字符序列;线程安全的,效率相对String高很多。
1. 1 常用构造方法
StringBuffer() | 构造一个没有字符的字符串缓冲区,并构造了16个字符的初始容量。 |
StringBuffer(int capacity) | 构造一个没有字符的字符串缓冲区,并构造指定的初始容量。 |
1.2 常用方法
- append()连接
- delete()删除
- insert()插入
- replace()替换
- reverse()逆序
public class TestStringBuffer {
@Test
public void StringBufferMethodTest(){
//创建实例
StringBuffer sb = new StringBuffer();
//拼接字符串
sb.append(1);
sb.append(true);
sb.append("hello");
System.out.println(sb);
//从0下标删除到5下标,包含开始下标,不包含结束下标
sb.delete(1,5);
System.out.println(sb);
//替换内容,包含开始,不包含结束下标
sb.replace(1,5,"aaa");
System.out.println(sb);
//插入指定内容
sb.insert(1,"hello");
System.out.println(sb);
//字符串反转
sb.reverse();
System.out.println(sb);
//截取字符串,返回新的字符串
String result = sb.substring(0, 3);
System.out.println(result);
//将StringBuffer转为String类型
System.out.println(sb.toString());
}
}
总结:StringBuffer类的大部分方法都是操作原对象,改变原对象。
2、StringBuilder类
- 是可变字符串, 即它的字符序列可以修改。
- StringBuilder不是线程安全的.但是StringBuilder执行效率高。
2.1 常用方法
- append()连接
- delete()删除
- insert()插入
- replace()替换
- reverse()逆序
代码:
public class TestStringBuilder {
public static void main(String[] args) {
//创建StringBuilder对象
StringBuilder sb = new StringBuilder();
//append()连接字符串
for (int i = 0; i < 10; i++) {
sb.append(i);
}
//0123456789
System.out.println(sb);
//delete()删除
sb.delete(3, 7);
//012789
System.out.println(sb);
//insert()插入
sb.insert(3, "Hello");
//012Hello789
System.out.println(sb);
//replace()替换
sb.replace(3, 8, "World");
//012World789
System.out.println(sb);
//逆序
sb.reverse();
//987dlroW210
System.out.println(sb);
}
}
三、String、StringBuffer、StringBuilder三者的区别
1、相同点
- String、StringBuffer、StringBuilder 三者都实现了字符序列接口,都满足了字符序列规范。
- 都被final修饰的类,禁止这三个类的方法被重写。
2、不同点
- String 底层采用的是final char[] 进行存储字符串,是不可变的字符序列,每次拼接字符串都会开辟新的内存空间。拼接字符串效率非常低下。
-
StringBuffer 底层采用char[] 进行存储字符串,数组的长度默认16。是可变的字符序列,StringBuffer对象会根据使用情况扩充字符缓冲(原来的2倍+2这种算法扩充)。拼接字符串效率较String来说提高很多。方法都是被synchronized修饰的,都是线程安全的。
-
StringBuilder和StringBuffer功能一样,只是底层方法没有被synchronized修饰,是线程不安全的,执行效率高于StringBuffer ;
-
所以开发一般对字符串的增加、删除、插入操作一般用StringBuffer效率更高,其他操作用String更为便。
四、排序接口Comparable
案例:
公司类:
4、需求如下:
/**
* 定义Employee员工对象 , 有姓名, 年龄, 工资等属性
* <p>
* 定义Company公司类, 公司中有若干员工, 使用数组保存员工信息
* -提供方法,添加员工
* -提供方法,显示所有员工信息
* -提供方法, 判断是否存在指定姓名的员工
* -提供方法,删除指定姓名的员工
* <p>
* 定义测试类
* -创建公司对象
* -添加10个员工
* -显示员工信息
* -判断是否存在lisi
* -删除wangwu
*/
public class Company {
//1) 公司有若干员工, 使用数组保存员工信息
//在定义A类时用到了B类, 说明A类与B类有一定关系, 类之间的关系 有四种: 继承, 实现, 关联,依赖. 在A类中定义变量使用到B类,称A类关联B类; 在A类中定义方法使用到B类称A依赖B类
private Employee[] data;
//通常情况下使用对象数组时, 会定义变量记录数组中元素的数量
private int size;
//通常情况下,会在无参构造方法中给数组进行初始化
public Company() {
data = new Employee[100]; //数组初始化大小需要估算
}
//有时, 也会通过构造方法指定数组的大小
public Company(int capacity) {
if (capacity > 0) {
data = new Employee[capacity];
} else {
data = new Employee[0];
}
}
//2) 提供方法,添加员工, 通过参数来接收一个员工对象
public void add(Employee e) {
//通常情况下, 向数组中存储数据前,会判断数组是否有足够容量
if (size == data.length) {
//size数组中元素的数量 等于 数组的长度, 表示数组已满 , 需要对数组扩容
Employee[] bigger = new Employee[data.length * 2];
System.arraycopy(data, 0, bigger, 0, data.length);
data = bigger;
}
//把e接收的员工添加到data数组中
data[size++] = e;
}
//3) 提供方法显示员工信息
public void showAll() {
System.out.println("----------所有员工信息-----------");
for (int i = 0; i < size; i++) {
System.out.println(data[i]);
}
}
//4) 提供方法, 判断是否存在指定姓名的员工
public boolean containsName(String name) {
//在实际开发中, 当形参是引用对象, 在方法体中访问形参对象的属性,或者调用形参对象的方法前,通常会判断形参对象是否为null, 为了避免出现空指针异常
if (name != null) {
//遍历data数组中前size个员工对象,
for (int i = 0; i < size; i++) {
// System.out.println( data[i] + " ------------" + data[i].getName());
if (name.equals(data[i].getName())) {
return true;
}
}
} else {
//遍历前size个数组元素,判断是否存在某个员工的姓名也为null
for (int i = 0; i < size; i++) {
if (data[i].getName() == null) {
return true;
}
}
}
return false;
}
//5)提供方法删除指定姓名的员工
public void deleteByName(String name) {
//找到name姓名的员工在数组中的索引值
if (name != null) {
for (int i = 0; i < size; i++) {
if (name.equals(data[i].getName())) {
delete(i);
i--;//当有重复性元素时需使用
}
}
} else {
for (int i = 0; i < size; i++) {
if (data[i].getName() == null) {
delete(i);
i--; //修正,当有重复性元素时需使用
}
}
}
}
//定义方法删除指定索引值的元素
private void delete(int i) {
//从i+1开始 每个元素逐个前移
System.arraycopy(data, i + 1, data, i, size - i - 1);
//元素个数减1
size--;
//最后元素清空
data[size] = null;
}
//定义方法,根据员工的姓名升序排序
public void sortByName() {
//对data数组中存储的前size个员工对象进行排序
Arrays.sort(data, 0 ,size);//要求data数组中存储的Employee对象自身具有比较大小的功能, 即让Employee类实现Comparable接口
/*Arrays.sort(data, 0, size, new Comparator<Employee>() {
@Override
public int compare(Employee o1, Employee o2) {
return o1.getName().compareTo(o2.getName());
}
});*/
}
//定义方法,根据工资降序排序
public void sortBySalaryDesc() {
//Arrays.sort( 对象数组, from, to, Comparator) 可以对数组中[from, to)范围内的元素进行排序, 排序时,使用指定的第四个Comparator比较器
Arrays.sort(data, 0, size, new Comparator<Employee>() {
//在匿名内部类中重写接口抽象方法, 在该方法中定义一个比较规则
@Override
public int compare(Employee o1, Employee o2) {
//o1 > o2 返回正数, o1 == o2返回0, o1 < o2 返回负数,对应升序
//o1 > o2 返回负数, o1 == o2返回0, o1 < o2 返回正数,对应降序
if (o1.getSalary() > o2.getSalary()) {
return -1;
} else if (o1.getSalary() == o2.getSalary()) {
return 0;
} else {
return 1;
}
}
});
}
//定义方法,根据年龄升序排序, 年龄相等,根据工资降序排序
public void sortByAgeSalary() {
Arrays.sort(data, 0, size, new Comparator<Employee>() {
@Override
public int compare(Employee o1, Employee o2) {
if (o1.getAge() == o2.getAge()) {
if (o1.getSalary() > o2.getSalary()) {
return -1;
} else if (o1.getSalary() == o2.getSalary()) {
return 0;
} else {
return 1;
}
}
return o1.getAge() - o2.getAge(); //o1的年龄大返回正数, 对应年龄升序
}
});
}
}
员工类:
/**
* 员工类
* 按照实体类规范定义, 实体类也称为JavaBean, 就是现实世界中客观存在的物体,如人, 电脑, 书...
* 1) 把属性私有化
* 2) 提供无参构造方法
* 3) 重写equals()/hashCode()
* 4) 重写toString()
* 5) 提供getter/setter方法
*/
public class Employee implements Comparable<Employee>{
private String name;
private int age;
private double salary;
//重写接口的抽象方法,在该方法中定义一个比较规则
@Override
public int compareTo(Employee o) {
//this当前对象的姓名比参数o对象的姓名大, 返回正数, 在排序时对应升序
//比较this当前对象的姓名与 参数o对象的姓名 的大小, 姓名是String字符串类型, 调用compareTo()方法比较大小, String字符串比较大小时, 不允许为null, 否则会产生空指针异常
if ( this.name == null ){
return -1;
}
if ( o.name == null ){
return 1;
}
return this.name .compareTo( o.name );
}
public Employee() {
}
//为了自己测试方便,提供有参数的构造方法
public Employee(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return Objects.equals(name, employee.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
测试类:
public class Test {
public static void main(String[] args) {
//创建公司对象
Company company = new Company();
//添加员工
company.add( new Employee("zhangsan", 25, 9000));
company.add( new Employee("lisi", 26, 3000));
company.add( new Employee("wangwu", 27, 4000));
company.add( new Employee("zhaoliu", 28, 5000));
company.add( new Employee("chenqi", 25, 7000));
company.add( new Employee("zhuba", 24, 8000));
company.add( new Employee("feifei", 23, 6000));
company.add( new Employee("zhaoliu", 22, 9000));
//来了两位新员工 ,但是员工信息还没有录入
company.add( new Employee());
company.add( new Employee());
//显示所有员工
company.showAll();
//判断是否存在指定姓名的员工
System.out.println( company.containsName("lisi"));
System.out.println( company.containsName("lixiaosi"));
//判断是否还有员工没有录入姓名,即判断是否存在姓名 为null的员工
System.out.println(company.containsName(null));
//删除
company.deleteByName("zhaoliu");
company.showAll();
company.deleteByName(null);
company.showAll();
//根据姓名升序排序
company.sortByName();
System.out.println("---");
/*----------所有员工信息-----------
Employee{name='chenqi', age=25, salary=7000.0}
Employee{name='feifei', age=23, salary=6000.0}
Employee{name='lisi', age=26, salary=3000.0}
Employee{name='wangwu', age=27, salary=4000.0}
Employee{name='zhangsan', age=25, salary=9000.0}
Employee{name='zhuba', age=24, salary=8000.0}*/
company.showAll();
company.sortBySalaryDesc();
company.showAll();
company.sortByAgeSalary();
company.showAll();
}
}