一、基本数据类型的包装类
1.包装类基本知识
-
包装类:基本数据类型和对象之间的相互转化。JDK 为每一个基本数据类型提供了相应的包装类
-
public class WrapperClassTest{ public static void main(String[] args){ Integer i = new Integer(10);//从java9开始废弃 Integer j = new Integer(50); } }
2.包装类的用途
对于包装类来说,这些类的用途主要包含两种:
- 作为和基本数据类型对应的类型存在,方便涉及到对象的操作,如Object[]、集合等的操作
- 包含每种基本数据类型的相关属性如最大值,最小值等,以及相关的操作方法。
3.包装类的使用
-
//基本——>对象 Integer i = new Integer(123);//从java9开始废弃了 Integer i2 = new Integer.valueOf(111);//本质上也是执行了new Integer() //包装类——>基本 double d = i2.doubleValue(); int a = i2.intValue(); //字符串——>包装类 Integer i3 = Integer.valueOf("123"); Integer i4 = Integer.parseInt("567"); //包装类——>字符串 String s = i3.toString(); String s2 = String.valueOf(i4);
-
自动装箱(autoboxing)
自动装箱与拆箱的功能是所谓的“编译器蜜糖(Compiler Sugar)”,
-
Integer i = 5;//编译器为我们执行了Integer.valueOf(5);而不是new Integer(5)
-
-
自动拆箱(unboxing)
-
int j = i;//编译器为我们执行了 i.intValue()
-
4.包装类空指针异常
Integer i = null;
int j = i; //编译器:int j = i.intValue();
null表示 i 没有指向任何对象的实体,不可能操作intValue()方法。
5.缓存问题
-
当数在[-128,127]时,返回数组中的某个元素
-
包装类在自动装箱时为了提高效率,对于-128~127 之间的值会进行缓存处理。超过范围后,对象之间不能再使用==进行数值的比较,而是使用 equals 方法。
-
Integer类相关源码
public static Integer valueOf(int i) { if (i >= IntegerCache.low&& i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
-
//包装类的缓存问题 Integer d1 = 4000; Integer d2 = 4000; //当数字在[-128,127]之间的时候,返回缓存数组中的某个元素 Integer d3 = 123; Integer d4 = 123; //Integer.valueOf(123); System.out.println(d1 == d2); //两个不同的对象 false System.out.println(d3 == d4); //true System.out.println(d1.equals(d2)); //值相同 true
-
自定义包装类
public class MyInteger { private int value; private static MyInteger[] cache = new MyInteger[256]; public static final int LOW = -128; public static final int HIGH = 127; static{ //[-128,127] 0-255 for(int i=LOW;i<=HIGH;i++){ //-128:0.-127:1,-126:2 cache[i+128] = new MyInteger(i); } } public static MyInteger valueOf(int i){ if(i>=LOW&&i<=HIGH){ return cache[i+128]; } return new MyInteger(i); } private MyInteger(int i){ this.value = i; } @Override public String toString() { return this.value+""; } public static void main(String[] args) { MyInteger m = MyInteger.valueOf(300); System.out.println(m); } }
二、字符串相关类
1.Java.lang.String类的使用
1.概述
- String:不可变的Unicode字符序列,因此可以将String对象称为"不可变字符对象"
- private final char value[];使用了final类型,即为常量
- String实现了Serializable接口:表示字符串是支持序列化的。实现了Comparable接口:表示String可以比较大小
- 通过字面量的方式(区别于new给一个字符串赋值,此时的字符串值声明在字符串常量池中)。
- 字符串常量池中是不会存储相同内容(使用String类的equals()比较,返回true)的字符串的。
2.String的不可变性
-
当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。
-
当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
-
当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
-
String s1 = "abc";//字面量的定义方式 String s2 = "abc"; s1 = "hello"; System.out.println(s1 == s2);//比较s1和s2的地址值 false System.out.println(s1);//hello System.out.println(s2);//abc System.out.println("*****************"); String s3 = "abc"; s3 += "def"; System.out.println(s3);//abcdef System.out.println(s2);//abc System.out.println("*****************"); String s4 = "abc"; String s5 = s4.replace('a', 'm'); System.out.println(s4);//abc System.out.println(s5);//mbc
-
String实例化的方式不同
- 方式一:通过字面量定义的方式
- 方式二:通过new+构造器的方式
//通过字面量定义的方式:此时的s1和s2的数据javaEE声明在方法区中的字符串常量池中。 String s1 = "javaEE"; String s2 = "javaEE"; //通过new + 构造器的方式:此时的s3和s4保存的地址值,是数据在堆空间中开辟空间以后对应的地址值。 String s3 = new String("javaEE"); String s4 = new String("javaEE"); System.out.println(s1 == s2);//true System.out.println(s1 == s3);//false System.out.println(s1 == s4);//false System.out.println(s3 == s4);//false
-
String s = new String(“abc”);方式创建对象,在内存中创建了几个对象?
两个:一个是堆空间中new结构,另一个是char[]对应的常量池中的数据:“abc”
-
1.常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。2.只要其中一个是变量,结果就在堆中。
-
JVM中字符串常量池存放位置说明:
jdk 1.6 (jdk 6.0 ,java 6.0):字符串常量池存储在方法区(永久区)
jdk 1.7:字符串常量池存储在堆空间
jdk 1.8:字符串常量池存储在方法区(元空间)
3.String类的常用方法
- int length():返回字符串的长度: return value.length
- char charAt(int index): 返回某索引处的字符return value[index]
- boolean isEmpty():判断是否是空字符串:return value.length == 0
- String toLowerCase():使用默认语言环境,将 String 中的所字符转换为小写
- String toUpperCase():使用默认语言环境,将 String 中的所字符转换为大写
- String trim():返回字符串的副本,忽略前导空白和尾部空白(用户名)
- boolean equals(Object obj):比较字符串的内容是否相同
- boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大小写
- String concat(String str):将指定字符串连接到此字符串的结尾。 等价于用“+”
- int compareTo(String anotherString):比较两个字符串的大小(挨个比ASCII码,涉及到字符串排序)
- String substring(int beginIndex):返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串。
- String substring(int beginIndex, int endIndex) :返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。(左闭右开)
- boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
- boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
- boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始
- boolean contains(CharSequence s):当且仅当此字符串包含指定的 char 值序列时,返回 true
- int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引
- int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
- int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引
- int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索
- 注:indexOf和lastIndexOf方法如果未找到都是返回-1
- 替换:
- String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所 oldChar 得到的。
- String replace(CharSequence target, CharSequence replacement):使用指定的字面值替换序列替换此字符串所匹配字面值目标序列的子字符串。
- String replaceAll(String regex, String replacement):使用给定的 replacement 替换此字符串所匹配给定的正则表达式的子字符串。
- String replaceFirst(String regex, String replacement):使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
- 匹配:
- boolean matches(String regex):告知此字符串是否匹配给定的正则表达式。
- 切片:
- String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。
- String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。
4.String类与其他结构的转换
1.与基本数据类型、包装类的转换
-
String——>基本数据类型、包装类:调用包装类的静态方法:parseXxx(str)/调用包装类的valueOf(str)方法
-
Integer i1 = Integer.parseInt("123"); Integer i2 = Integer.valueOf("567");
-
-
基本数据类型、包装类——>String:调用String重载的valueOf(i)/调用i的toString()
-
String str = i1.toString(); String str2 = String.valueOf(i2);
-
2.与字符数组之间的转换
-
String——>char[]:调用String的toCharArray()
-
char[]——>String:调用String的构造器
-
char[] arr = str.toCharArray(); char[] arr1 = new char[]{'h','e','l','l','o'}; String str3 = new String(arr1);
-
3.与字节数组之间的转换
-
编码:String——>byte[]:调用String的getBytes(),字符串—>字节(看得懂—>看不懂的二进制数据)
-
解码:byte[]——>String:调用String的构造器,字节—>字符串(看不懂的二进制数据—>看得懂)
-
String str1 = "abc"; byte[] bytes = str1.getBytes();//使用默认字符串,进行编码 System.out.println(Arrays.toString(bytes)); byte[] gbks = str1.getBytes("gbk");//使用gbk字符集进行编码。 System.out.println(Arrays.toString(gbks)); String str2 = new String(bytes);//使用默认的字符集,进行解码。 System.out.println(str2); String str3 = new String(gbks); System.out.println(str3);//出现乱码。原因:编码集和解码集不一致! String str4 = new String(gbks, "gbk"); System.out.println(str4);//没出现乱码。原因:编码集和解码集一致!
-
2.StringBufffer和StringBuilder
1.概述
StringBuilder与StringBuffer均为可变字符序列,都是抽象类AbstractStringBuilder的子类,方法几乎一模一样。
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
char value[ ];
//以下代码省略
}
源码中也是一个字符数组,但这个字符数组没有用final修饰,随时可以修改。
内存解析:
StringBuffer sb1 = new StringBuffer();//char[] value = new char[16];底层创建了一个长度是16的数组。
System.out.println(sb1.length());//0 value.length是16
StringBuffer sb2 = new StringBuffer("abc");//char[] value = new char["abc".length() + 16];
扩容问题:如果要添加的数据底层数组盛不下了,那就需要扩容底层的数组。
默认情况下,扩容为原来容量的2倍 + 2,同时将原数组中的元素复制到新的数组中
两者区别:
- StringBuffer JDK1.0版本提供的类,线程安全,做线程同步检查,效率较低
- StringBuilder JDK1.5版本提供的类,线程不安全,不做线程同步检查,效率较高,推荐使用
常用方法列表:
-
重载的public StringBuilder append(…)方法
可以为该StringBuilder对象添加字符序列,仍然返回自身对象
-
方法public StringBuilder delete(int start,int end)
可以删除从 start 开始到 end-1 为止的一段字符序列,仍然返回自身对象。
-
方 法 public StringBuilder deleteCharAt(int index)
移除此序列指定位置上的 char,仍然返回自身对象。
-
重载的 public StringBuilder insert(…)方法
可以为该 StringBuilder 对象在指定位置插入字符序列,仍然返回自身对象。
-
方 法 public StringBuilder reverse()
用于将字符序列逆序,仍然返回自身对象。
-
方法 public String toString() 返回此序列中数据的字符串表示形式。
2.使用陷阱
-
String的使用陷阱
String一经初始化后,就不会再改变其内容了,对String字符串的操作实际上是对其副本(原始拷贝)的操作,原来的字符串一点没变,比如:
String s = “a”;创建了一个字符串
s = s+“b”;实际上原来的"a"字符串对象已经丢弃了,现在又产生了另一个字符串s+“b”(也就是"ab")。如果多次执行这些改变串内容的操作,会导致大量副本字符串对象存留在内存中,降低效率。如果这样的操作放在循环中,会极大影响程序的时间和空间性能,甚至导致服务器崩溃。
相反,StringBuilder和StringBuffer是对原字符串本身操作的,可以对字符串进行修改而不产生副本拷贝或者产生少量的副本,因此可以在循环中使用。
String str =""; long num1 =Runtime.getRuntime().freeMemory();//获取系统剩余内存空间 long time1 =System.currentTimeMillis();//获取系统的当前时间 for (int i = 0; i < 5000; i++) { str = str + i;//相当于产生了 5000 个对象 } long num2 = Runtime.getRuntime().freeMemory(); long time2 = System.currentTimeMillis(); System.out.println("String 占用内存 : " + (num1 - num2)); System.out.println("String 占用时间 : " + (time2 - time1)); /**使用 StringBuilder 进行字符串的拼接*/ StringBuilder sb1 = new StringBuilder(""); long num3 = Runtime.getRuntime().freeMemory(); long time3 = System.currentTimeMillis(); for (int i = 0; i < 5000; i++) { sb1.append(i); } long num4 = Runtime.getRuntime().freeMemory(); long time4 = System.currentTimeMillis(); System.out.println("StringBuilder 占用内存 : " + (num3 - num4)); System.out.println("StringBuilder 占用时间 : " + (time4 - time3));
三、时间处理相关类
1970 年 1 月 1 日 00:00:00 定为基准时间,每个度量单位是毫秒(1 秒的千分之一)
使用long类型的变量来表示时间,从基准时间往前几亿年,往后几亿年都可以表示。
获得现在时刻:long now = System.currentTimeMillis();
1.Date时间类(java.util.Data)
在标准 Java 类库中包含一个 Date 类。它的对象表示一个特定的瞬间,精确到毫秒。
- Date() 分配一个 Date 对象,并初始化此对象为系统当前的日期和时间,可以精确到毫秒)。
- Date(long date)分配 Date 对象并初始化此对象,以表示自从标准基准时间(称为“历元(epoch)”,即 1970 年 1 月 1 日 00:00:00 GMT)以来的指定毫秒数。
- boolean after(Date when) 测试此日期是否在指定日期之后。
- booleanbefore(Date when) 测试此日期是否在指定日期之前。
- boolean equals(Object obj) 比较两个日期的相等性。
- long getTime() 返 回 自 1970 年 1 月 1 日 00:00:00 GMT 以 来 此 Date 对象表示的毫秒数。
- String toString() 把此 Date 对象转换为以下形式的 String:dow mon dd hh:mm:ss zzz yyyy 其中: dow 是一周中的某一天 (Sun、Mon、Tue、Wed、 Thu、 Fri、 Sat)。
2.DateFormat类和SimpleDateFormat类
把时间对象转化成指定格式的字符串。反之,把指定格式的字符串转化成时间对象。DateFormat 是一个抽象类,一般使用它的的子类 SimpleDateFormat 类来实现。
字符串——>时间 解析 文本—>日期
日期——>字符串 格式化 日期—>文本
//字符串——>时间 解析 文本—>日期
DateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
//将符合指定格式的字符串转成成时间对象.字符串格式需要和指定格式一致。
String str = "1949-10-1 10:10:10";
Date guoqing = format.parse(str);
//日期——>字符串 格式化 日期—>文本
DateFormat format2 = new SimpleDateFormat("yyyy年MM月dd日");
Date date = new Date();
String str2 = format2.format(date);
时间格式字符也可以为我们提供其他的便利。比如:获得当前时间是今年的第几天。代码如下:
SimpleDateFormat s1 = new SimpleDateFormat("D");
String daytime = s1.format(new Date());
3.Calendar日历类
Calendar 类是一个抽象类,提供了关于日期计算的相关功能,比如:年、月、日、时、分、秒的展示和计算。
一个Calendar的实例是系统时间的抽象表示,通过get(int field)方法来取得想要的时间信息。比如YEAR、MONTH、DAY_OF_WEEK、HOUR_OF_DAY 、MINUTE、SECOND。
GregorianCalendar 是 Calendar 的一个具体子类,提供了世界上大多数国家/地区使用的标准日历系统。
注意:月份的表示:一月是 0,二月是 1,以此类推,12 月是 11。
//1.实例化
//方式一:创建其子类(GregorianCalendar的对象
//方式二:调用其静态方法getInstance()
Calendar calendar = Calendar.getInstance();
//2.常用方法
//get()
int days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
System.out.println(calendar.get(Calendar.DAY_OF_YEAR));
//set()
//calendar可变性
calendar.set(Calendar.DAY_OF_MONTH,22);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
//add()
calendar.add(Calendar.DAY_OF_MONTH,-3);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
//getTime():日历类---> Date
Date date = calendar.getTime();
System.out.println(date);
//setTime():Date ---> 日历类
Date date1 = new Date();
calendar.setTime(date1);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
四、其他常用类
1.Math类
java.lang.Math 提供了一系列静态方法用于科学计算;其方法的参数和返回值类型一般为 double 型。
2.Random类
java.util.Random类,这个类是专门用来生成随机数的,并且 Math.random()底层调用的就是Random 的 nextDouble()方法。
Random rand = new Random();
//随机生成[0,1)之间的 double 类型的数据
System.out.println(rand.nextDouble());
//随机生成 int 类型允许范围之内的整型数据
System.out.println(rand.nextInt());
//随机生成[0,1)之间的 float 类型的数据
System.out.println(rand.nextFloat());
// 随 机 生 成 false 或 者 true
System.out.println(rand.nextBoolean());
//随机生成[0,10)之间的 int 类型的数据
System.out.print(rand.nextInt(10));
//随机生成[20,30)之间的 int 类型的数据
System.out.print(20 + rand.nextInt(10));
//随机生成[20,30)之间的 int 类型的数据(此种方法计算较为复杂)
System.out.print(20 + (int) (rand.nextDouble() * 10));
3.File类
java.io.File 类:代表文件和目录。 在开发中,读取文件、生成文件、删除文件、修改文件的属性时经常会用到本类。
File 类的常见构造方法:public File(String pathname)
以 pathname 为路径创建 File 对象,如果 pathname 是相对路径,则默认的当前路径在系统属性 user.dir 中存储。
System.out.println(System.getProperty("user.dir"));
File f = new File("a.txt"); //相对路径:默认放到 user.dir 目录下面
f.createNewFile();//创建文件
File f2 = new File("d:/b.txt");//绝对路径
f2.createNewFile();
在项目开发中,user.dir 就是本项目的目录。
通过 File 对象可以访问文件的属性:
File f = new File("d:/b.txt");
System.out.println("File 是否存在:"+f.exists());
System.out.println("File 是否是目录:"+f.isDirectory());
System.out.println("File 是否是文件:"+f.isFile());
System.out.println("File 最后修改时间:"+new Date(f.lastModified()));
System.out.println("File 的大小:"+f.length());
System.out.println("File 的文件名:"+f.getName());
System.out.println("File 的目录路径:"+f.getPath());
通过 File 对象创建空文件或目录(在该对象所指的文件或目录不存在的情况下)
使用mkdir 创建目录
File f = new File("d:/c.txt");
f.createNewFile(); // 会在 d 盘下面生成 c.txt 文件
f.delete(); // 将该文件或目录从硬盘上删除
File f2 = new File("d:/电影/华语/大陆");
boolean flag = f2.mkdir(); //目录结构中有一个不存在,则不会创建整个目录树
System.out.println(flag);//创建失败
使用mkdirs 创建目录
File f = new File("d:/c.txt");
f.createNewFile(); // 会在 d 盘下面生成 c.txt 文件
f.delete(); // 将该文件或目录从硬盘上删除
File f2 = new File("d:/电影/华语/大陆");
boolean flag = f2.mkdirs();//目录结构中有一个不存在也没关系;创建整个目录树
System.out.println(flag);//创建成功
递归遍历目录结构和树状展现
import java.io.File;
public class TestFile {
public static void main(String[ ] args){
File f = new File("d:/电影");
printFile(f, 0);
}
/**
* 打印文件信息
* @param file 文件名称
* @param level 层次数(实际就是:第几次递归调用)
*/
static void printFile(File file, int level) {
//输出层次数
for (int i = 0; i < level; i++){
System.out.print("-");
}
//输出文件名
System.out.println(file.getName());//如果 file 是目录,则获取子文件列表,并对每个子文件进行相同的操作
if (file.isDirectory()) {
File[ ] files = file.listFiles();
for (File temp : files) {
//递归调用该方法:注意等+1
printFile(temp, level + 1);
}
}
}
}
4.枚举
枚举类的理解:类的对象只有有限个,确定的。
JDK1.5 引入了枚举类型。枚举类型的定义包括枚举声明和枚举体。格式如下:
enum 枚举名 {
枚举体(常量列表)
}
枚举体就是放置一些常量。
enum Season {
SPRING, SUMMER, AUTUMN, WINDER
}
所有的枚举类型隐性地继承自 java.lang.Enum。枚举实质上还是类!而每个被枚举的成员实质就是一个枚举类型的实例,他们默认都是 public static final 修饰的。可以直接通过枚举类型名使用它们。
当需要定义一组常量时,可以使用枚举类型。
尽量不要使用枚举的高级特性,事实上高级特性都可以使用普通类来实现,没有必要引入枚举,增加程序的复杂性!
//使用enum关键字枚举类
enum Season1 {
//1.提供当前枚举类的对象,多个对象之间用","隔开,末尾对象";"结束
SPRING("春天","春暖花开"),
SUMMER("夏天","夏日炎炎"),
AUTUMN("秋天","秋高气爽"),
WINTER("冬天","冰天雪地");
//2.声明Season对象的属性:private final修饰
private final String seasonName;
private final String seasonDesc;
//3.私化类的构造器,并给对象属性赋值
private Season1(String seasonName,String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
//4.其他诉求1:获取枚举类对象的属性
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
}
- 使用enum定义枚举类之后,枚举类常用方法:(继承于java.lang.Enum类)
//values()方法:返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。
//valueOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”。如不是,会有运行时异常:IllegalArgumentException。
//toString():返回当前枚举类对象常量的名称
Season1 summer = Season1.SUMMER;
//toString():返回枚举类对象的名称
System.out.println(summer.toString());
System.out.println("****************");
//values():返回所的枚举类对象构成的数组
Season1[] values = Season1.values();
for(int i = 0;i < values.length;i++){
System.out.println(values[i]);
}
System.out.println("****************");
Thread.State[] values1 = Thread.State.values();
for (int i = 0; i < values1.length; i++) {
System.out.println(values1[i]);
}
//valueOf(String objName):返回枚举类中对象名是objName的对象。
Season1 winter = Season1.valueOf("WINTER");
//如果没objName的枚举类对象,则抛异常:IllegalArgumentException
//Season1 winter = Season1.valueOf("WINTER1");
System.out.println(winter);
- 使用enum定义枚举类之后,如何让枚举类对象分别实现接口:
interface Info{
void show();
}
//使用enum关键字枚举类
enum Season1 implements Info{
//1.提供当前枚举类的对象,多个对象之间用","隔开,末尾对象";"结束
SPRING("春天","春暖花开"){
@Override
public void show() {
System.out.println("春天在哪里?");
}
},
SUMMER("夏天","夏日炎炎"){
@Override
public void show() {
System.out.println("宁夏");
}
},
AUTUMN("秋天","秋高气爽"){
@Override
public void show() {
System.out.println("秋天不回来");
}
},
WINTER("冬天","冰天雪地"){
@Override
public void show() {
System.out.println("大约在冬季");
}
};
}
5.比较器
-
Java比较器的使用背景:
Java中的对象,正常情况下,只能进行比较:== 或 != 。不能使用 > 或 < 的
但是在开发场景中,我们需要对多个对象进行排序,言外之意,就需要比较对象的大小。
如何实现?使用两个接口中的任何一个:Comparable 或 Comparator
-
自然排序使用Comparable接口
2.1 说明
-
像String、包装类等实现了Comparable接口,重写了compareTo(obj)方法,给出了比较两个对象大小的方式。
-
像String、包装类重写compareTo()方法以后,进行了从小到大的排列
-
重写compareTo(obj)的规则:
如果当前对象this大于形参对象obj,则返回正整数,
如果当前对象this小于形参对象obj,则返回负整数,
如果当前对象this等于形参对象obj,则返回零。
-
对于自定义类来说,如果需要排序,我们可以让自定义类实现Comparable接口,重写compareTo(obj)方法。在compareTo(obj)方法中指明如何排序
-
public class Goods implements Comparable{
private String name;
private double price;
//指明商品比较大小的方式:照价格从低到高排序,再照产品名称从高到低排序
@Override
public int compareTo(Object o) {
// System.out.println("**************");
if(o instanceof Goods){
Goods goods = (Goods)o;
//方式一:
if(this.price > goods.price){
return 1;
}else if(this.price < goods.price){
return -1;
}else{
// return 0;
return -this.name.compareTo(goods.name);
}
//方式二:
// return Double.compare(this.price,goods.price);
}
// return 0;
throw new RuntimeException("传入的数据类型不一致!");
}
// getter、setter、toString()、构造器:省略
}
-
定制排序:使用Comparator接口
3.1 说明
-
背景:
当元素的类型没实现java.lang.Comparable接口而又不方便修改代码,或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,那么可以考虑使用 Comparator 的对象来排序
-
重写compare(Object o1,Object o2)方法,比较o1和o2的大小:
如果方法返回正整数,则表示o1大于o2;
如果返回0,表示相等;
返回负整数,表示o1小于o2。
-
Comparator com = new Comparator() {
//指明商品比较大小的方式:照产品名称从低到高排序,再照价格从高到低排序
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof Goods && o2 instanceof Goods){
Goods g1 = (Goods)o1;
Goods g2 = (Goods)o2;
if(g1.getName().equals(g2.getName())){
return -Double.compare(g1.getPrice(),g2.getPrice());
}else{
return g1.getName().compareTo(g2.getName());
}
}
throw new RuntimeException("输入的数据类型不一致");
}
}
-
两种排序方式对比
-
Comparable接口的方式一旦指定,保证Comparable接口实现类的对象在任何位置都可以比较大小。
-
Comparator接口属于临时性的比较
-