常用基础类
Posted pmbb
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了常用基础类相关的知识,希望对你有一定的参考价值。
1. 枚举类型介绍
枚举类型是Java 5中新增特性的一部分,它是一种特殊的数据类型,之所以特殊是因为它既是一种类(class)类型却又比类类型多了些特殊的约束,但是这些约束的存在也造就了枚举类型的简洁性、安全性以及便捷性。下面先来看看什么是枚举?如何定义枚举?
枚举的定义
回忆一下下面的程序,这是在没有枚举类型时定义常量常见的方式
1 public class DayDemo 2 3 public static final int MONDAY =1; 4 5 public static final int TUESDAY=2; 6 7 public static final int WEDNESDAY=3; 8 9 public static final int THURSDAY=4; 10 11 public static final int FRIDAY=5; 12 13 public static final int SATURDAY=6; 14 15 public static final int SUNDAY=7; 16 17
上述的常量定义常量的方式称为int枚举模式,这样的定义方式并没有什么错,但它存在许多不足,如在类型安全和使用方便性上并没有多少好处,如果存在定义int值相同的变量,混淆的几率还是很大的,编译器也不会提出任何警告,因此这种方式在枚举出现后并不提倡,现在我们利用枚举类型来重新定义上述的常量,同时也感受一把枚举定义的方式,如下定义周一到周日的常量
1 //枚举类型,使用关键字enumenum Day 2 MONDAY, TUESDAY, WEDNESDAY, 3 THURSDAY, FRIDAY, SATURDAY, SUNDAY 4
相当简洁,在定义枚举类型时我们使用的关键字是enum,与class关键字类似,只不过前者是定义枚举类型,后者是定义类类型。枚举类型Day中分别定义了从周一到周日的值,这里要注意,值一般是大写的字母,多个值之间以逗号分隔。同时我们应该知道的是枚举类型可以像类(class)类型一样,定义为一个单独的文件,当然也可以定义在其他类内部,更重要的是枚举常量在类型安全性和便捷性都很有保证,如果出现类型问题编译器也会提示我们改进,但务必记住枚举表示的类型其取值是必须有限的,也就是说每个值都是可以枚举出来的,比如上述描述的一周共有七天。那么该如何使用呢?如下:
1 public class EnumDemo 2 3 public static void main(String[] args) 4 //直接引用 5 Day day =Day.MONDAY; 6 7 8 9 //定义枚举类型 10 enum Day 11 MONDAY, TUESDAY, WEDNESDAY, 12 THURSDAY, FRIDAY, SATURDAY, SUNDAY 13
就像上述代码那样,直接引用枚举的值即可,这便是枚举类型的最简单模型。
枚举实现原理
我们大概了解了枚举类型的定义与简单使用后,现在有必要来了解一下枚举类型的基本实现原理。实际上在使用关键字enum创建枚举类型并编译后,编译器会为我们生成一个相关的类,这个类继承了Java API中的java.lang.Enum类,也就是说通过关键字enum创建枚举类型在编译后事实上也是一个类类型而且该类继承自java.lang.Enum类。下面我们编译前面定义的EnumDemo.java并查看生成的class文件来验证这个结论:
//查看目录下的java文件 zejian@zejiandeMBP enumdemo$ ls EnumDemo.java //利用javac命令编译EnumDemo.java zejian@zejiandeMBP enumdemo$ javac EnumDemo.java //查看生成的class文件,注意有Day.class和EnumDemo.class 两个 zejian@zejiandeMBP enumdemo$ ls Day.class EnumDemo.class EnumDemo.java
利用javac编译前面定义的EnumDemo.java文件后分别生成了Day.class和EnumDemo.class文件,而Day.class就是枚举类型,这也就验证前面所说的使用关键字enum定义枚举类型并编译后,编译器会自动帮助我们生成一个与枚举相关的类。我们再来看看反编译Day.class文件:
1 //反编译Day.class 2 final class Day extends Enum 3 4 //编译器为我们添加的静态的values()方法 5 public static Day[] values() 6 7 return (Day[])$VALUES.clone(); 8 9 //编译器为我们添加的静态的valueOf()方法,注意间接调用了Enum也类的valueOf方法 10 public static Day valueOf(String s) 11 12 return (Day)Enum.valueOf(com/zejian/enumdemo/Day, s); 13 14 //私有构造函数 15 private Day(String s, int i) 16 17 super(s, i); 18 19 //前面定义的7种枚举实例 20 public static final Day MONDAY; 21 public static final Day TUESDAY; 22 public static final Day WEDNESDAY; 23 public static final Day THURSDAY; 24 public static final Day FRIDAY; 25 public static final Day SATURDAY; 26 public static final Day SUNDAY; 27 private static final Day $VALUES[]; 28 29 static 30 31 //实例化枚举实例 32 MONDAY = new Day("MONDAY", 0); 33 TUESDAY = new Day("TUESDAY", 1); 34 WEDNESDAY = new Day("WEDNESDAY", 2); 35 THURSDAY = new Day("THURSDAY", 3); 36 FRIDAY = new Day("FRIDAY", 4); 37 SATURDAY = new Day("SATURDAY", 5); 38 SUNDAY = new Day("SUNDAY", 6); 39 $VALUES = (new Day[] 40 MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY 41 ); 42 43
从反编译的代码可以看出编译器确实帮助我们生成了一个Day类(注意该类是final类型的,将无法被继承)而且该类继承自java.lang.Enum类,该类是一个抽象类(稍后我们会分析该类中的主要方法),除此之外,编译器还帮助我们生成了7个Day类型的实例对象分别对应枚举中定义的7个日期,这也充分说明了我们前面使用关键字enum定义的Day类型中的每种日期枚举常量也是实实在在的Day实例对象,只不过代表的内容不一样而已。注意编译器还为我们生成了两个静态方法,分别是values()和 valueOf(),稍后会分析它们的用法,到此我们也就明白了,使用关键字enum定义的枚举类型,在编译期后,也将转换成为一个实实在在的类,而在该类中,会存在每个在枚举类型中定义好变量的对应实例对象,如上述的MONDAY枚举类型对应public static final Day MONDAY;,同时编译器会为该类创建两个方法,分别是values()和valueOf()。ok~,到此相信我们对枚举的实现原理也比较清晰,下面我们深入了解一下java.lang.Enum类以及values()和valueOf()的用途。
枚举的常见方法
Enum抽象类常见方法
Enum是所有 Java 语言枚举类型的公共基本类(注意Enum是抽象类),以下是它的常见方法:
返回类型 方法名称 方法说明
Int compareTo(E o) 比较此枚举与指定对象的顺序
boolean equals(Object other) 当指定对象等于此枚举常量时,返回 true。
Class<?> getDeclaringClass() 返回与此枚举常量的枚举类型相对应的 Class对象
String name() 返回此枚举常量的名称,在其枚举声明中对其进行声明
int ordinal() 返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零)
String toString() 返回枚举常量的名称,它包含在声明中
static<T extends Enum<T>> T static valueOf(Class<T> enumType, String name) 返回带指定名称的指定枚举类型的枚举常量。
这里主要说明一下ordinal()方法,该方法获取的是枚举变量在枚举类中声明的顺序,下标从0开始,如日期中的MONDAY在第一个位置,那么MONDAY的ordinal值就是0,如果MONDAY的声明位置发生变化,那么ordinal方法获取到的值也随之变化,注意在大多数情况下我们都不应该首先使用该方法,毕竟它总是变幻莫测的。compareTo(E o)方法则是比较枚举的大小,注意其内部实现是根据每个枚举的ordinal值大小进行比较的。name()方法与toString()几乎是等同的,都是输出变量的字符串形式。至于valueOf(Class<T> enumType, String name)方法则是根据枚举类的Class对象和枚举名称获取枚举常量,注意该方法是静态的,后面在枚举单例时,我们还会详细分析该方法,下面的代码演示了上述方法:
1 package com.zejian.enumdemo; 2 3 /** 4 * Created by zejian on 2017/5/7. 5 * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创] 6 */ 7 public class EnumDemo 8 9 public static void main(String[] args) 10 11 //创建枚举数组 12 Day[] days=new Day[]Day.MONDAY, Day.TUESDAY, Day.WEDNESDAY, 13 Day.THURSDAY, Day.FRIDAY, Day.SATURDAY, Day.SUNDAY; 14 15 for (int i = 0; i <days.length ; i++) 16 System.out.println("day["+i+"].ordinal():"+days[i].ordinal()); 17 18 19 System.out.println("-------------------------------------"); 20 //通过compareTo方法比较,实际上其内部是通过ordinal()值比较的 21 System.out.println("days[0].compareTo(days[1]):"+days[0].compareTo(days[1])); 22 System.out.println("days[0].compareTo(days[1]):"+days[0].compareTo(days[2])); 23 24 //获取该枚举对象的Class对象引用,当然也可以通过getClass方法 25 Class<?> clazz = days[0].getDeclaringClass(); 26 System.out.println("clazz:"+clazz); 27 28 System.out.println("-------------------------------------"); 29 30 //name() 31 System.out.println("days[0].name():"+days[0].name()); 32 System.out.println("days[1].name():"+days[1].name()); 33 System.out.println("days[2].name():"+days[2].name()); 34 System.out.println("days[3].name():"+days[3].name()); 35 36 System.out.println("-------------------------------------"); 37 38 System.out.println("days[0].toString():"+days[0].toString()); 39 System.out.println("days[1].toString():"+days[1].toString()); 40 System.out.println("days[2].toString():"+days[2].toString()); 41 System.out.println("days[3].toString():"+days[3].toString()); 42 43 System.out.println("-------------------------------------"); 44 45 Day d=Enum.valueOf(Day.class,days[0].name()); 46 Day d2=Day.valueOf(Day.class,days[0].name()); 47 System.out.println("d:"+d); 48 System.out.println("d2:"+d2); 49 50 /** 51 执行结果: 52 day[0].ordinal():0 53 day[1].ordinal():1 54 day[2].ordinal():2 55 day[3].ordinal():3 56 day[4].ordinal():4 57 day[5].ordinal():5 58 day[6].ordinal():6 59 ------------------------------------- 60 days[0].compareTo(days[1]):-1 61 days[0].compareTo(days[1]):-2 62 clazz:class com.zejian.enumdemo.Day 63 ------------------------------------- 64 days[0].name():MONDAY 65 days[1].name():TUESDAY 66 days[2].name():WEDNESDAY 67 days[3].name():THURSDAY 68 ------------------------------------- 69 days[0].toString():MONDAY 70 days[1].toString():TUESDAY 71 days[2].toString():WEDNESDAY 72 days[3].toString():THURSDAY 73 ------------------------------------- 74 d:MONDAY 75 d2:MONDAY 76 */ 77 78 79 enum Day 80 MONDAY, TUESDAY, WEDNESDAY, 81 THURSDAY, FRIDAY, SATURDAY, SUNDAY 82
到此对于抽象类Enum类的基本内容就介绍完了,这里提醒大家一点,Enum类内部会有一个构造函数,该构造函数只能有编译器调用,我们是无法手动操作的,不妨看看Enum类的主要源码:
1 //实现了Comparable 2 public abstract class Enum<E extends Enum<E>> 3 implements Comparable<E>, Serializable 4 5 private final String name; //枚举字符串名称 6 7 public final String name() 8 return name; 9 10 11 private final int ordinal;//枚举顺序值 12 13 public final int ordinal() 14 return ordinal; 15 16 17 //枚举的构造方法,只能由编译器调用 18 protected Enum(String name, int ordinal) 19 this.name = name; 20 this.ordinal = ordinal; 21 22 23 public String toString() 24 return name; 25 26 27 public final boolean equals(Object other) 28 return this==other; 29 30 31 //比较的是ordinal值 32 public final int compareTo(E o) 33 Enum<?> other = (Enum<?>)o; 34 Enum<E> self = this; 35 if (self.getClass() != other.getClass() && // optimization 36 self.getDeclaringClass() != other.getDeclaringClass()) 37 throw new ClassCastException(); 38 return self.ordinal - other.ordinal;//根据ordinal值比较大小 39 40 41 @SuppressWarnings("unchecked") 42 public final Class<E> getDeclaringClass() 43 //获取class对象引用,getClass()是Object的方法 44 Class<?> clazz = getClass(); 45 //获取父类Class对象引用 46 Class<?> zuper = clazz.getSuperclass(); 47 return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper; 48 49 50 51 public static <T extends Enum<T>> T valueOf(Class<T> enumType, 52 String name) 53 //enumType.enumConstantDirectory()获取到的是一个map集合,key值就是name值,value则是枚举变量值 54 //enumConstantDirectory是class对象内部的方法,根据class对象获取一个map集合的值 55 T result = enumType.enumConstantDirectory().get(name); 56 if (result != null) 57 return result; 58 if (name == null) 59 throw new NullPointerException("Name is null"); 60 throw new IllegalArgumentException( 61 "No enum constant " + enumType.getCanonicalName() + "." + name); 62 63 64 //.....省略其他没用的方法 65
通过Enum源码,可以知道,Enum实现了Comparable接口,这也是可以使用compareTo比较的原因,当然Enum构造函数也是存在的,该函数只能由编译器调用,毕竟我们只能使用enum关键字定义枚举,其他事情就放心交给编译器吧。
1 //由编译器调用 2 protected Enum(String name, int ordinal) 3 this.name = name; 4 this.ordinal = ordinal; 5
编译器生成的Values方法与ValueOf方法
values()方法和valueOf(String name)方法是编译器生成的static方法,因此从前面的分析中,在Enum类中并没出现values()方法,但valueOf()方法还是有出现的,只不过编译器生成的valueOf()方法需传递一个name参数,而Enum自带的静态方法valueOf()则需要传递两个方法,从前面反编译后的代码可以看出,编译器生成的valueOf方法最终还是调用了Enum类的valueOf方法,下面通过代码来演示这两个方法的作用:
Day[] days2 = Day.values();
System.out.println("day2:"+Arrays.toString(days2));
Day day = Day.valueOf("MONDAY");
System.out.println("day:"+day);
/**
输出结果:
day2:[MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY]
day:MONDAY
*/
从结果可知道,values()方法的作用就是获取枚举类中的所有变量,并作为数组返回,而valueOf(String name)方法与Enum类中的valueOf方法的作用类似根据名称获取枚举变量,只不过编译器生成的valueOf方法更简洁些只需传递一个参数。这里我们还必须注意到,由于values()方法是由编译器插入到枚举类中的static方法,所以如果我们将枚举实例向上转型为Enum,那么values()方法将无法被调用,因为Enum类中并没有values()方法,valueOf()方法也是同样的道理,注意是一个参数的。
枚举与Class对象
上述我们提到当枚举实例向上转型为Enum类型后,values()方法将会失效,也就无法一次性获取所有枚举实例变量,但是由于Class对象的存在,即使不使用values()方法,还是有可能一次获取到所有枚举实例变量的,在Class对象中存在如下方法:
返回类型 |
方法名称 |
方法说明 |
|
|
返回该枚举类型的所有元素,如果Class对象不是枚举类型,则返回null。 |
|
|
当且仅当该类声明为源代码中的枚举时返回 true |
因此通过getEnumConstants()方法,同样可以轻而易举地获取所有枚举实例变量下面通过代码来演示这个功能:
1 //正常使用 2 3 Day[] ds=Day.values(); 4 5 //向上转型Enum 6 7 Enum e = Day.MONDAY; 8 9 //无法调用,没有此方法 10 11 //e.values(); 12 13 //获取class对象引用 14 15 Class<?> clasz = e.getDeclaringClass(); 16 17 if(clasz.isEnum()) 18 19 Day[] dsz = (Day[]) clasz.getEnumConstants(); 20 21 System.out.println("dsz:"+Arrays.toString(dsz)); 22 23
/**
输出结果:
dsz:[MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY]
*/
正如上述代码所展示,通过Enum的class对象的getEnumConstants方法,我们仍能一次性获取所有的枚举实例常量。
2. 结合switch-case使用枚举
关于枚举与switch是个比较简单的话题,使用switch进行条件判断时,条件参数一般只能是整型,字符型。而枚举型确实也被switch所支持,在java 1.7后switch也对字符串进行了支持。这里我们简单看一下switch与枚举类型的使用:
1 /** 2 * Created by zejian on 2017/5/9. 3 * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创] 4 */ 5 6 enum Color GREEN,RED,BLUE 7 8 public class EnumDemo4 9 10 public static void printName(Color color) 11 switch (color) 12 case BLUE: //无需使用Color进行引用 13 System.out.println("蓝色"); 14 break; 15 case RED: 16 System.out.println("红色"); 17 break; 18 case GREEN: 19 System.out.println("绿色"); 20 break; 21 22 23 24 public static void main(String[] args) 25 printName(Color.BLUE); 26 printName(Color.RED); 27 printName(Color.GREEN); 28 29 //蓝色 30 //红色 31 //绿色 32 33
更多枚举知识:https://blog.csdn.net/javazejian/article/details/71333103
3. String字符串、字符串缓冲区StringBuffer和StringBuilder
String字符串 具体看StringApi文档:https://www.runoob.com/manual/jdk1.6/java/lang/String.html
字符串常用方法:https://www.cnblogs.com/soficircle/p/6653777.html
在学习String类时,API中说字符串缓冲区支持可变的字符串,什么是字符串缓冲区呢?接下来我们来研究下字符串缓冲区。
查阅StringBuffer的API,线程安全的可变字符序列。一个类似于 String 的字符串缓冲区,但不能修改。虽然在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。
- 三者在执行速度方面的比较:StringBuilder > StringBuffer > String
2.String <(StringBuffer,StringBuilder)的原因
String:字符串常量
StringBuffer:字符串变量
StringBuilder:字符串变量
从上面的名字可以看到,String是“字符串常量”,也就是不可改变的对象。对于这句话的理解你可能会产生这样一个疑问 ,比如这段代码:
1 String s =
"abcd";
2 s = s+1;
3 System.out.print(s);// result : abcd1
我们明明就是改变了String型的变量s的,为什么说是没有改变呢? 其实这是一种欺骗,JVM是这样解析这段代码的:首先创建对象s,赋予一个abcd,然后再创建一个新的对象s用来 执行第二行代码,也就是说我们之前对象s并没有变化,所以我们说String类型是不可改变的对象了,由于这种机制,每当用String操作字符串时,实际上是在不断的创建新的对象,而原来的对象就会变为垃圾被GC回收掉,可想而知这样执行效率会有多底。
而StringBuffer与StringBuilder就不一样了,他们是字符串变量,是可改变的对象,每当我们用它们对字符串做操作时,实际上是在一个对象上操作的,这样就不会像String一样创建一些而外的对象进行操作了,当然速度就快了。
3.一个特殊的例子:
1 String str = “This is only a”
+ “ simple” + “ test”;
3 StringBuffer builder = new StringBuilder(“This is
only a”).append(“ simple”).append(“ test”);
你会很惊讶的发现,生成str对象的速度简直太快了,而这个时候StringBuffer居然速度上根本一点都不占优势。其实这是JVM的一个把戏,实际上:
String str = “This is
only a” + “ simple” + “test”;
其实就是:
String str = “This is only
a simple test”;
所以不需要太多的时间了。但大家这里要注意的是,如果你的字符串是来自另外的String对象的话,速度就没那么快了,譬如:
String str2 = “This is only
a”;
String str3 = “ simple”;
String str4 = “ test”;
String str1 = str2 +str3 + str4;
这时候JVM会规规矩矩的按照原来的方式去做。
4.StringBuilder与 StringBuffer
StringBuilder:线程非安全的
StringBuffer:线程安全的
当我们在字符串缓冲去被多个线程使用是,JVM不能保证StringBuilder的操作是安全的,虽然他的速度最快,但是可以保证StringBuffer是可以正确操作的。当然大多数情况下就是我们是在单线程下进行的操作,所以大多数情况下是建议用StringBuilder而不用StringBuffer的,就是速度的原因。
对于三者使用的总结: 1.如果要操作少量的数据用 =
String
2.单线程操作字符串缓冲区 下操作大量数据 = StringBuilder
3.多线程操作字符串缓冲区 下操作大量数据 = StringBuffer
4. 基本类型的包装类型
Java有八种基本类型,byte, short, int, long, float, double, char, boolean。
对应八种包装类,Byte, Short, Integer, Long, Double, Character, Boolean。
以int为例,创建一个基本类型的变量时,基本类型如下:
int a = 0;
包装类型如下:
Integer a = new Integer(0);
内存方式
基本类型属于原始数据类型,变量中存储的就是原始值。包装类型属于引用数据类型,变量中存储的是存储原始值的地址的引用。
我们先不考虑内存空间各种不同区域对应不同的用途,将其看做一个整体,思考以下语句的内存分配过程。
int a = 0;
分配一块内存,即为a,存入的值为0。
Integer b = new Integer(0);
分配一块内存,我们称之为内存1,存储0,再分配一块内存,我们称之为内存2。内存2中存放的是指向内存1的引用。
==和equals
基本类型的比较要用==,包装类型的比较要用equals。==是Java本身的一个操作符,它的作用是比较两个变量在内存中的地址,相同为true,不同为false。equals则是一个方法。Java中,所有的类都有一个根级父类,Ojbect,包括包装类。在不重写的情况下,直接equals实际上就是在调用Ojbect类的equals方法。而Ojbect类的equals方法,也是使用==来作判断的。换句话说,如果没有经过重写,equals和==的结果是一致的。而如果我们想要达到某些其它目的,比如说,我们希望在地址不同,但是值相同的情况下,也能返回true,就需要自己重写equals方法。
看到这里,产生了一个疑惑,如下:
int a = 0;
int b = 0;
System.out.println(a == b);
true还是false?
初看觉得是true,再想觉得是false,程序运行结果是true。
疑惑:两个变量的值虽然是相同的,但是,它们在内存中的地址应该是不同的,只是其中的值相同。按照==的运算规则,理由得到false的结果(内存中的地址不同),结果却是true,为什么?
事实:当定义b时,JVM会先检查内存中是否已经有了0这个值,如果没有,则创建,如果有(如之前已经执行过int a = 0),则不会再创建,而是直接将变量b指向变量a所有内存的地址,就好像执行的语句是int b = a。换句话说,a和b最终指向的内存空间,其实还是一致的。
基于上述事实,又产生了一个疑惑,如下:
int a = 0;
int b = 0;
b = b + 1;
System.out.printlt(a == 2);
疑惑:因为变量b实质上指向的是变量a的内存地址,所以,对b的修改,实质上就是对a的修改,输出结果应该是true,但实际上false。
事实:这里,又有一个误解。当执行b = b + 1时,JVM事实上并不是直接去对变量a的内存地址中的值做操作。事实上是,它先取出了a的值,为1。然后进行 +1 操作,得到2。这时候,JVM并不会直接把变量a内存地址中的值直接改成2,而是会在内存中寻找是否有2,有就将b指向相应地址,没有就把b内存地址中的值修改为2。
自动装箱和自动拆箱
在实际使用时,通过我们在定义包装类型的定义时,不会使用new关键字,而是如下:
Integer a = 0;
思考这条语句为什么成立?难道是说字面量0就相当于一个Integer对象吗?当然不是,这其实是Java为了方便,为我们提供了自动装箱的机制。事实上,这条语句相当于:
Integer a = Integer.values(0);
values是Integer 类提供一个静态方法,返回值是一个Integer实例。
和自动装箱相反,当你把一个包装类型的变量赋值到一个基本类型的变量时,Java会进行自动拆箱的过程,执行intValue方法。
Integer a = 0;(自动装箱)
int b = a;(自动拆箱)
缓存机制
在执行Integer a = new Integer(0)时,JVM会先创建一个对象,值为0,再把这个对象的引用赋值给a。基于节约内存和效率等的考虑,Java对包装类的values方法做了一些优化。如Integer,在执行Integer a = Integer.values(0)时,Java会在缓存中直接找到之前创建好的缓存,直接把0相应的引用给a。
又产生了之前的疑惑吗?其实Java的操作还和之前是一样的。所以,并不会出现不合理的问题。
观察源码发现,Boolean的缓存是true,false,Byte、Short、Integer、Long的缓存都是-128到127。Character是缓存是0~127对应的字符。Float,Double没有缓存。为什么没有?你也这样疑惑吗?
其实答案很简单,思考一下缓存的意义就可以想到,为了使缓存真正有效果,应该把最常用的一部分数据放到缓存里。但是,对于小数来说,选定一个集合,其中元素的个数是无限的。所以,Java可能在想这个问题的时候,实在是想不出来,应该以什么标准判断,把什么元素放到缓存里,于是,就放弃了,干脆不要缓存了。
初始化
基本类型和包装类型的另外一个区别时,在用作类变量时,在初始化的时候,包装类型会被初始化成null,基本类型会被初始化成特定的值,如int为0,boolean为false等。
使用原则
最后我们来整理一下基本类和包装类在实际使用时,应该遵循哪些原则?
1. 尽量使用values方法。最大可能使用缓存,提高程序的效率。
2. 类变量使用包装类。想象有一个和数据库表对应的实体类,如果你使用的基本数据类型,在插入时,可能会插入一些让你意想不到的初始值。
3. 方法的参数要使用包装类。使用包装类意味着你在调用时,可以令若干个参数为null。null是无意义的。但是如果你使用了基本数据类型,那么,你将不得不传入一个值,即使这个值对你来说,没有什么意义。
4. 方法的返回值要根据是否可为null来确定使用包装类还是基本类。当一个方法的返回值,一定不会出现null的情况时,推荐使用基本类来作为返回值。这样,调用者在拿到这个方法的返回值时,就不必担心它是为null了。
5. 方法内部(局部变量)使用基本类型。基本类型的时间效率和效率上来说,都是要优于包装类的。所以,在方法内部,能使用基本类型尽量不要使用包装类。
6. 小数的计算。严格来说,这条不属于基本类型和包装类的内容,但是,这里顺便提交,当涉及到小数的计算时,要考虑到计算的精度问题,可以使用BigDecimal,也可以通过缩小计量单位,达到化零为整的目的,这个,根据具体场景来确定。
5. Date类的使用
1、public Date()
分配 Date 对象并初始化此对象,以表示分配它系统的时间(精确到毫秒)。
2、public Date(long date)
分配 Date 对象并初始化此对象,以表示自从标准基准时间(称为“历元(epoch)”,即 1970 年 1 月 1 日 00:00:00 GMT)以来的指定毫秒数。
方法
1、public boolean after(Date when)
测试此日期是否在指定日期之后。
返回:当且仅当此 Date 对象表示的瞬间比 when 表示的瞬间晚,才返回 true;否则返回 false。
2、public boolean before(Date when)
测试此日期是否在指定日期之前。
返回:当且仅当此 Date 对象表示的瞬间比 when 表示的瞬间早,才返回 true;否则返回 false。
3、public int compareTo(Date anotherDate)
比较两个日期的顺序。
返回:如果参数 Date 等于此 Date,则返回值 0;如果此 Date 在 Date 参数之前,则返回小于 0 的值;如果此 Date 在 Date 参数之后,则返回大于 0 的值。
4、public boolean equals(Object obj)
比较两个日期的相等性。当且仅当参数不为 null,并且是一个表示与此对象相同的时间点(到毫秒)的 Date 对象时,结果才为 true。
5、public long getTime()
返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数。
6、public void setTime(long time)
设置此 Date 对象,以表示 1970 年 1 月 1 日 00:00:00 GMT 以后 time 毫秒的时间点。
7、public String toString()
把此 Date 对象转换为以下形式的 String:
dow mon dd hh:mm:ss zzz yyyy其中:
dow 是一周中的某一天 (Sun, Mon, Tue, Wed, Thu, Fri, Sat)。
mon 是月份 (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec)。
dd 是一月中的某一天(01 至 31),显示为两位十进制数。
hh 是一天中的小时(00 至 23),显示为两位十进制数。
mm 是小时中的分钟(00 至 59),显示为两位十进制数。
ss 是分钟中的秒数(00 至 61),显示为两位十进制数。
zzz 是时区(并可以反映夏令时)。标准时区缩写包括方法 parse 识别的时区缩写。如果不提供时区信息,则 zzz 为空,即根本不包括任何字符。
yyyy 是年份,显示为 4 位十进制数。
6. SimpleDateFormat类的使用
DateFormat类:
功能:将系统默认的时间格式转化为自定义的格式,或者将自定义的格式转化为系统格式
注意:这是一个抽象类,所以我们使用他的时候要创建他子类的对象。SimpleDateFormat类
SimpleDateFormat类:
构造方法:SimpleDateFormat(String pattern)括号内为我们自定义的时间格式。
成员方法:1.public final String format(Date date)
return format(date, new StringBuffer(),
DontCareFieldPosition.INSTANCE).toString();
把我们传进去的date格式时间转化为我们自定义的格式
2.public Date parse(String source) throws ParseException
ParsePosition pos = new ParsePosition(0);
Date result = parse(source, pos);
if (pos.index == 0)
throw new ParseException("Unparseable date: \\"" + source + "\\"" ,
pos.errorIndex);
return result;
把自定义格式的字符串转为date格式
自定义格式:(区分大小写)
y 年
M 月
d 日
H 时
m 分
s 秒
例:"yyyy-mm-dd HH:mm:ss"
1 SimpleDateFormat a = new SimpleDateFormat("yyyy-mm-dd HH:mm:ss"); 2 Date date = new Date(1000L); 3 System.out.println(a.format(date)); 4 System.out.println(a.parse("1970-00-01 08:00:01"));
1970-00-01 08:00:01
Thu Jan 01 08:00:01 CST 1970
7. Math类的使用
1 * log(a) a的自然对数(底数是e) 2 * log10(a) a 的底数为10的对数 3 * log1p(a) a+1的自然对数 4 * 值得注意的是,前面其他函数都有重载,对数运算的函数只能传double型数据并返回double型数据 5 */ 6 System.out.println(Math.log(Math.E));//输出1.0 7 System.out.println(Math.log10(10));//输出1.0 8 System.out.println(Math.log1p(Math.E-1.0));//输出1.0 9 /* 10 * 6.幂 11 * exp(x) 返回e^x的值 12 * expm1(x) 返回e^x - 1的值 13 * pow(x,y) 返回x^y的值 14 * 这里可用的数据类型也只有double型 15 */ 16 System.out.println(Math.exp(2));//输出E^2的值 17 System.out.println(Math.pow(2.0, 3.0));//输出8.0 18 19 /* 20 * 7.随机数 21 * random()返回[0.0,1.0)之间的double值 22 * 这个产生的随机数其实可以通过*x控制 23 * 比如(int)(random*100)后可以得到[0,100)之间的整数 24 */ 25 System.out.println((int)(Math.random()*100));//输出[0,100)间的随机数 26 27 /* 28 * 8.转换 29 * toDegrees(a) 弧度换角度 30 * toRadians(a) 角度换弧度 31 */ 32 System.out.println(Math.toDegrees(Math.PI));//输出180.0 33 System.out.println(Math.toRadians(180));//输出 π 的值 34 /* 35 * 9.其他 36 */ 37 38 //copySign(x,y) 返回 用y的符号取代x的符号后新的x值 39 System.out.println(Math.copySign(-1.0, 2.0));//输出1.0 40 System.out.println(Math.copySign(2.0, -1.0));//输出-2.0 41 42 //ceil(a) 返回大于a的第一个整数所对应的浮点数(值是整的,类型是浮点型) 43 //可以通过强制转换将类型换成整型 44 System.out.println(Math.ceil(1.3443));//输出2.0 45 System.out.println((int)Math.ceil(1.3443));//输出2 46 47 //floor(a) 返回小于a的第一个整数所对应的浮点数(值是整的,类型是浮点型) 48 System.out.println(Math.floor(1.3443));//输出1.0 49 50 //rint(a) 返回最接近a的整数的double值 51 System.out.println(Math.rint(1.2));//输出1.0 52 System.out.println(Math.rint(1.8));//输出2.0 53 54 55 //nextAfter(a,b) 返回(a,b)或(b,a)间与a相邻的浮点数 b可以比a小 56 System.out.println(Math.nextAfter(1.2, 2.7));//输出1.2000000000000002 57 System.out.println(Math.nextAfter(1.2, -1));//输出1.1999999999999997 58 //所以这里的b是控制条件 59 60 //nextUp(a) 返回比a大一点点的浮点数 61 System.out.println(Math.nextUp(1.2));//输出1.2000000000000002 62 63 //nextDown(a) 返回比a小一点点的浮点数 64 System.out.println(Math.nextDown(1.2));//输出1.1999999999999997 65 66
另外,当我尝试这样使用数学类的时候是错误的:
Math m = new Math();
m.sqrt(4.0);
为什么呢?
查了下Math的源码,惊呆了!它的构造方法居然是这样写的:
private Math()
构造方法写成私有的额(⊙o⊙)…
所以根本就不能创建对象啊!
后来仔细想想其实这是很合理的。
在面向对象中,类是抽象的而对象是具体的,数学本身也是抽象的而没法具体,所以这里只有一个数学类而不能实例化对象。
8. Random类的使用
Random类中实现的随机算法是伪随机,也就是有规则的随机。在进行随机时,随机算法的起源数字称为种子数(seed),在种子数的基础上进行一定的变换,从而产生需要的随机数字。
相同种子数的Random对象,相同次数生成的随机数字是完全相同的。也就是说,两个种子数相同的Random对象,第一次生成的随机数字完全相同,第二次生成的随机数字也完全相同。这点在生成多个随机数字时需要特别注意。
查看Random的API文档:
public Random(long seed);
使用单个 long
种子创建一个新的随机数生成器。该种子是伪随机数生成器的内部状态的初始值,该生成器可通过方法 next(int) 维护。
调用 new
Random
(seed)
等效于:
Random rnd = new Random();
rnd.setSeed(seed);
public void setSeed(long seed)
使用单个 long
种子设置此随机数生成器的种子。setSeed
的常规协定是它更改此随机数生成器对象的状态,使其状态好像是刚刚使用参数 seed
作为种子创建它的状态一样。通过将种子自动更新为
(seed ^ 0x5DEECE66DL) & ((1L << 48) - 1)
并清除 nextGaussian() 使用的 haveNextNextGaussian
标志,Random
类可实现 setSeed
方法。
Random
类实现的 setSeed
恰好只使用 48 位的给定种子。但是,通常重写方法可能使用 long
参数的所有 64 位作为种子值。
由于参数seed一样,相同种子数的Random对象,相同次数生成的随机数字是完全相同的。
下面介绍一下Random类的使用,以及如何生成指定区间的随机数组以及实现程序中要求的几率。
1、Random对象的生成
Random类包含两个构造方法,下面依次进行介绍:
a、public Random()
该构造方法使用一个和当前系统时间对应的相对时间有关的数字作为种子数,然后使用这个种子数构造Random对象。
b、public Random(long seed)
该构造方法可以通过制定一个种子数进行创建。
示例代码:
Random r = new Random();
Random r1 = new Random(10);
再次强调:种子数只是随机算法的起源数字,和生成的随机数字的区间无关。
2、Random类中的常用方法
Random类中的方法比较简单,每个方法的功能也很容易理解。需要说明的是,Random类中各方法生成的随机数字都是均匀分布的,也就是说区间内部的数字生成的几率是均等的。下面对这些方法做一下基本的介绍:
a、public boolean nextBoolean()
该方法的作用是生成一个随机的boolean值,生成true和false的值几率相等,也就是都是50%的几率。
b、public double nextDouble()
该方法的作用是生成一个随机的double值,数值介于[0,1.0)之间。
c、public int nextInt()
该方法的作用是生成一个随机的int值,该值介于int的区间,也就是-231到231-1之间。
如果需要生成指定区间的int值,则需要进行一定的数学变换,具体可以参看下面的使用示例中的代码。
d、public int nextInt(int n)
该方法的作用是生成一个随机的int值,该值介于[0,n)的区间,也就是0到n之间的随机int值,包含0而不包含n。
如果想生成指定区间的int值,也需要进行一定的数学变换,具体可以参看下面的使用示例中的代码。
e、public void setSeed(long seed)
该方法的作用是重新设置Random对象中的种子数。设置完种子数以后的Random对象和相同种子数使用new关键字创建出的Random对象相同。
3、Random类使用示例
使用Random类,一般是生成指定区间的随机数字,下面就一一介绍如何生成对应区间的随机数字。以下生成随机数的代码均使用以下Random对象r进行生成:
Random r = new Random();
a、生成[0,1.0)区间的小数
double d1 = r.nextDouble();
直接使用nextDouble方法获得。
b、生成[0,5.0)区间的小数
doubled2 = r.nextDouble() *
5;
因为nextDouble方法生成的数字区间是[0,1.0),将该区间扩大5倍即是要求的区间。
c、生成[1,2.5)区间的小数
doubled3 = r.nextDouble() *
1.5+
1;
生成[1,2.5)区间的随机小数,则只需要首先生成[0,1.5)区间的随机数字,然后将生成的随机数区间加1即可。
同理,生成任意非从0开始的小数区间[d1,d2)范围的随机数字(其中d1不等于0),则只需要首先生成[0,d2-d1)区间的随机数字,然后将生成的随机数字区间加上d1即可。
同理,生成[0,d)区间的随机小数,d为任意正的小数,则只需要将nextDouble方法的返回值乘以d即可。
d、生成任意整数
int n1 = r.nextInt();
直接使用nextInt方法即可。
e、生成[0,10)区间的整数
int n2 = r.nextInt(10);
n2 = Math.abs(r.nextInt() % 10);
以上两行代码均可生成[0,10)区间的整数。
第一种实现使用Random类中的nextInt(int n)方法直接实现。
第二种实现中,首先调用nextInt()方法生成一个任意的int数字,该数字和10取余以后生成的数字区间为(-10,10),然后再对该区间求绝对值,则得到的区间就是[0,10)了。
同理,生成任意[0,n)区间的随机整数,都可以使用如下代码:
int n2 = r.nextInt(n);
n2 = Math.abs(r.nextInt() % n);
f、生成[0,10]区间的整数
int n3 = r.nextInt(11);
n3 = Math.abs(r.nextInt() % 11);
相对于整数区间,[0,10]区间和[0,11)区间等价,所以即生成[0,11)区间的整数。
g、生成[-3,15)区间的整数
int n4 = r.nextInt(18) - 3;
n4 = Math.abs(r.nextInt() % 18) - 3;
生成非从0开始区间的随机整数,可以参看上面非从0开始的小数区间实现原理的说明。
h、几率实现
按照一定的几率实现程序逻辑也是随机处理可以解决的一个问题。下面以一个简单的示例演示如何使用随机数字实现几率的逻辑。
在前面的方法介绍中,nextInt(int n)方法中生成的数字是均匀的,也就是说该区间内部的每个数字生成的几率是相同的。那么如果生成一个[0,100)区间的随机整数,则每个数字生成的几率应该是相同的,而且由于该区间中总计有100个整数,所以每个数字的几率都是1%。按照这个理论,可以实现程序中的几率问题。
示例:随机生成一个整数,该整数以55%的几率生成1,以40%的几率生成2,以5%的几率生成3。实现的代码如下:
int n5 = r.nextInt(100);
int m; //结果数字
if(n5 < 55) //55个数字的区间,55%的几率
m = 1;
else if(n5 < 95)//[55,95),40个数字的区间,40%的几率
m = 2;
else
m = 3;
因为每个数字的几率都是1%,则任意55个数字的区间的几率就是55%,为了代码方便书写,这里使用[0,55)区间的所有整数,后续的原理一样。
当然,这里的代码可以简化,因为几率都是5%的倍数,所以只要以5%为基础来控制几率即可,下面是简化的代码实现:
int n6 = r.nextInt(20);
int m1;
if(n6 < 11)
m1 = 1;
else if(n6 < 19)
m1 = 2;
else
m1 = 3;
在程序内部,几率的逻辑就可以按照上面的说明进行实现。
4、其它问题
a、相同种子数Random对象问题
前面介绍过,相同种子数的Random对象,相同次数生成的随机数字是完全相同的,下面是测试的代码:
Random r1 = new Random(10);
Random r2 = new Random(10);
for(int i = 0;i < 2;i++)
System.out.println(r1.nextInt());
System.out.println(r2.nextInt());
在该代码中,对象r1和r2使用的种子数都是10,则这两个对象相同次数生成的随机数是完全相同的。
如果想避免出现随机数字相同的情况,则需要注意,无论项目中需要生成多少个随机数字,都只使用一个Random对象即可。
b、关于Math类中的random方法
其实在Math类中也有一个random方法,该random方法的工作是生成一个[0,1.0)区间的随机小数。
通过阅读Math类的源代码可以发现,Math类中的random方法就是直接调用Random类中的nextDouble方法实现的。
只是random方法的调用比较简单,所以很多程序员都习惯使用Math类的random方法来生成随机数字。
9.Runtime类的使用
10.System 类的使用
11.Calendar类的使用
12.Java8新特性
参考网址:
枚举:https://blog.csdn.net/javazejian/article/details/71333103
基本类型与包装类型:https://baijiahao.baidu.com/s?id=1635392670198214134&wfr=spider&for=pc
Date类的使用:https://blog.csdn.net/qq_27501261/article/details/79423886
SimpleDateFormat类的使用:https://blog.csdn.net/chen404897439/article/details/92560038
Math类的使用:https://www.cnblogs.com/lukelook/p/11300890.html
Random类的使用:https://blog.csdn.net/huiweizuotiandeni/article/details/70244526
以上是关于常用基础类的主要内容,如果未能解决你的问题,请参考以下文章