javaSE中级篇2 — 工具类篇 — 更新完毕
1、工具类(也叫常用类)—— 指的是别人已经写好了的,我们只需要拿来用就行了
官网网址:Overview (Java Platform SE 8 ) (oracle.com) ———— 但是这个是英文版的( 是JDK1.8的 )
- 要是想看其他版本的话:( 进到Oracle官网去找 )
2、枚举类+Runtime类
- 1、枚举类 —— JDK1.5之后有的玩法 ———— 很简单,但是很重要啊,和以后的学习息息相关!!!
- 定义:就是把 某一类型的对象 全部列举出来 ———— 这种类型的对象是固定且个数有限的
- 比如:
- 星期类 ———— 只有周一到周天
- 性别类 ———— 只有男和女( 其他哪个不算啊,正常来说 )
- 季节类 ———— 只有春夏秋冬
- 像这种类就可以使用枚举类来定义,然后放进去,只需要用的时候遍历出来就行( 遍历————所以就和数组很像了,枚举类里面也有值( 存的对象 ) 和 索引
- 枚举类怎么创建? ———— 把class关键字改成enum关键字即可
举个例子:
public enum Sex { // 这就定义了一个枚举类 }
- 枚举类里面有什么?( 自我理解版 )
- 这个类型的对象 —— 对象与对象直接用 , 隔开,最后用 ; 结束( 其实不要这个分号也得吃 )
- 注:对象列表必须写在最前面
- 原因:枚举类里面其实也可以有普通类的属性 —— 但这个属性必须是private final修饰的,如:private final String NAME = “邪公子";
- 也可以有构造——但是这个构造也得是private修饰
- 也可以有方法——但是也要用private来修饰( 不是说不用private修饰就不得吃,只是定义的是枚举类就得这么弄( 那种不用这么玩但是得吃的牛角尖就不用钻了 )
- 原因:枚举类里面其实也可以有普通类的属性 —— 但这个属性必须是private final修饰的,如:private final String NAME = “邪公子";
- 注:对象列表必须写在最前面
- 注:只是枚举类基本上都是用来放 类型相同且个数有限的对象,所以其他那些普通类的东西可以不用考虑 ———— 可以理解为枚举类是一个特殊的容器
举个例子:
public enum Color{ // 这就定义了一个枚举类
red,green,blue,black,yellow; // 为了方便,只展示一部分的对象,这些对象名记得全大写啊,我是为了偷懒
}
- 两个属性( 官方版 )
- name —— 即:枚举对象的名字,系统中会调用对应的方法name(),帮助我们获取需要的对象名字 —— 我们自己不会用,但是我们可以调到这个方法的
- ordinac —— 即:枚举对象在类中罗列的顺序,类似于数组的下标,也是从0开始,系统中也有ordinac()方法 —— 获取序号
- 两个属性( 官方版 )
- 怎么取枚举类中的对象?
- 在类加载的过程中,底层已经帮我们建好了枚举类的对象,所以我们想要取枚举类里面的对象时:
- 枚举类名 我们所要用的枚举对象名( 可以自定义,只是不建议 ) = 枚举类名.枚举中要取的那个对象名
举个例子:
public enum Color { red,white,black,yellow,green; } class Test{
public static void main( String[] args ) {
Color black = Color.black; // 取枚举类中的对象
}
}
- 要是没有枚举这个东西,那我们自己怎么写一个枚举类?
- private修饰的构造方法
- public final static 类型 常量名 = new 类型();
- 举个例子:
package cn.xieGongZi.testEnum.customEnum; public class Color { // 1、private修饰的构造方法
private Color(){} // 2、public fianl static 类型 常量名 = new 类型()
public final static Color WHITE = new Color();
public final static Color GREEN = new Color();
public final static Color BLACK = new Color();
public final static Color YELLOW = new Color();
} class Test{ public static void main(String[] args) { // 拿自定义的枚举对象
System.out.println( Color.BLACK );
System.out.println( Color.WHITE );
System.out.println( Color.GREEN );
System.out.println( Color.YELLOW );
}
}
- 从这个例子中也可以看出;枚举 其实就是把 对象常量化了嘛( 这种思想是很重要滴 )
- 举个例子:
- 枚举类中常用的方法是哪些?
- values() ———— 获取枚举类中的全部对象( 注意:返回值是一个对应枚举类型的数组 )
举个例子:
public enum Color { red,white,black,yellow,green; } class Test{
public static void main( String[] args ) {
Color[] colors = Color.values(); for (Color color : colors) {
System.out.println(color);
}
}
}
- valueOf( String name ) ———— 获取枚举类中 自己给定name的对象
举个例子:
public enum Color { red,white,black,yellow,green; } class Test{
public static void main( String[] args ) { Color.valueOf("red");
}
}
- toString() ——— 把枚举对象转成一个字符串( 马上就会说明这个字符串String ),源码中直接就是return name,它底层中没有用final修饰,所以是可以重写的
- compareTo() ———— 比较两个枚举类对象大小( 在底层中返回值类型是int ,应用了反射机制 —— 这个会在后续进行说明 )———— 实质比的是:对象在内存中位置的先后顺序
- 注:需要先获取到对象才可以调用它
public enum Color { red,black,blue,yello,green; } class Demo{ public static void main(String[] args) { Color red = Color.valueOf("red");
Color green = Color.valueOf("green"); System.out.println( red.compareTo(green) );
}
}
- 注:需要先获取到对象才可以调用它
注意事项:
- 我们自定义的enum类型默认继承Enum类( Enum是在java.lang包下 )
- 我们自定义的enum类型不能再写extends关键字( 但是可以多实现【 implements 】 )
- 2、Runtime类 ———— 主要就是用来管理内存的,看JVM虚拟机内存的大小( 现在写程序基本上都不太考虑JVM虚拟机内存这个东西 ———— 因此:是为了给自己装点墨水儿 )
- 这个类在底层中的实现原理
- 单例模式的懒汉式
- private static的属性
- private static的构造方法
- public修饰的 获取对象的方法
——————>推导出一个结论:我们想要调用Runtime时,不能直接new Runtime,而是:Runtime 起个引用名字 = Runtime.getRuntime()
- 知识引申:设计模式之单例模式( 最简单的一种 )
- 什么是单例模式?
- 指:类的对象在全局唯一
- 即:无论谁要当前类的对象,它们所拥有的对象都是同一个。所以这就需要做到:
- 在另外类中不能创建当前类的对象;想要对象,只能通过当前类自己提供出来。所以单例模式就是一种创建型模式,这种模式专门用来处理对象的创建( 设计模式有23种,分为创建型——5种;结构型——7种;行为型11种,后续会单独整理成一个知识体系出来 )
- 即:无论谁要当前类的对象,它们所拥有的对象都是同一个。所以这就需要做到:
- 指:类的对象在全局唯一
- 什么是单例模式?
- 怎么实现单例模式?
- 1、饿汉式加载 ———— 也叫立即性加载,即:还没用到当前类的对象,系统就已经提前帮忙加载好了
- 设计步骤如下
- 私有的 静态的 当前类对象作为当前类的属性
- 私有的构造方法
- 公有的 静态的 方法返回当前类的对象
- 实例:
package cn.xieGongZi.singleton; // 单例模式设计————单例模式(饿汉式)
public class Person { // 1、私有的 静态的 当前类的对象 作为 属性
private static Person person = new Person(); private String name;
private char sex; // 2、私有的 构造方法
private Person() {
}
// 3、向外提供一个获取对象的方法
public static Person getInstance() {
return person;
}
// get和set方法自行添加,这里只是为了理解单例模式之饿汉式
}
- 饿汉式单例模式的优缺点
- 优点:不会产生对象没有就拿来使用的问题,从而造成空指针异常 ( 异常在后续会进行说明 )
- 缺点:启动项目时,可能会导致加载的对象过多,有些还没有使用,从而造成承载压力过大
- 实例:
- 2、懒汉式加载 ———— 也叫延迟性加载,即:对象什么时候用,什么时候加载
- 设计步骤如下:
- 私有的 静态的 当前类对象作为属性(不实例化)
- 私有的 构造方法
- 公有的 静态的 方法
- 创建的对象作为前面的属性值
- 返回当前类对象
- 设计步骤如下:
- 2、懒汉式加载 ———— 也叫延迟性加载,即:对象什么时候用,什么时候加载
- 实例:
package cn.xieGongZi.singleton; // 单例设计模式————懒汉式
public class People { private String name;
private Integer age; // 1、私有的 静态的 当前类属性(在这里不实例化)
private static People people = null; // 2、私有的构造方法
private People() {
} // 3、公有的 静态的 获取当前类对象的方法
public static People getInstance() { people = new People();
return people; // 这里不严谨( 应该需要做判断 ———— 判断对象为不为null 再进行创建)
// 但是这里是为了好理解,所以没加
}
}
- 懒汉式单例模式的优缺点:
- 优点:启动项目时,只有需要对象时才加载,不需要就不加载,不会造成空间的浪费
- 缺点:可能会由于没有操作好,从而导致异常
- 实例:
- 回到正题:Runtime类需要知道的三个方法:
- maxMemory() ———— 最大内存的大小
- totalMemory() ———— 已用内存的大小
- freememory() ———— 可用内存的大小
举个例子:
class Test{
public static void main( String[] args ){
//Runtime类学习
Runtime runtime = Runtime.getRuntime(); // 创建Runtime类的对象
System.out.println(runtime.maxMemory()); // 查看最大内存的大小
System.out.println(runtime.totalMemory()); // 查看已用内存的大小
System.out.println(runtime.freeMemory()); // 查看可用内存的大小
}
}
3、String类( 字符串 )———— 这是不可变字符串,很重要也很灵活,只要搞开发就会一直打交道
- 这个类是在java.lang包下,这个包下的类都不需要import导入
- 它实现了:Serializable(序列化接口), Comparable(比较), CharSequence(字符序列)
String如何创建对象与赋值 ———— 有很多种,可以参考API文档,但:最常用的是以下两种
无参构造
string str = new string();// 创建sstring对象
str = "邪公子"; // 给string对象赋值
有参构造
string str =new string( “邪公子”);
注:字符串变量 必须 经过初始化之后才能使用
感受一下:String为什么叫不可变字符串?它不可变的是什么?
- 先来看一个源码
- 从这个源码中发现: 有一个属性value
- value这个属性:是一个字符类型的数组( 即:char[ ] value ) ————> 得出一个结论:字符串就是基于字符实现的
- value这个属性:是private修饰的 ————> private的特征就出来了,封装起来了嘛,所以别人怎么进行修改它的内容( 也没有提供公有的操作方法 )( 即:不可变的其中一个点:内容不可变 )
那问题来了:貌似好像可以改变涩,如图所示 —— 这样一看,貌似可以改变啊,输出的内容都变了嘛
但是:不是这么回事 —— 来个图分析一手儿( 图中栈内存的String s是同一个啊,只是为了怕看懵逼了,所以画了两份 )
从这个图中可以看出:后面的这个值,是重新创建了一份给它,所以并不是在原来的基础上,即:”邪公子“这个常量上进行修改
- 回到正题,接着来分析value这个属性:发现这个属性,同时还被final修饰了,而且value又是一个数组,所以final这个修饰符的特征又出来了:创建完这个数组之后,它的长度就不可以改变了
- 插入一个问题:String的长度在底层中有限制吗?
- 有。因为底层中它是一个char[ ] value,所以:
- String的长度是什么类型的? ———— int类型的
- String的这个数组索引、数组的长度是什么类型的? ———— 还是int类型的
- 继而:推导出int类型的最大范围就是它的包装类Integer的范围,即:2^31 - 1,而数组是从0开始,因此这个范围就是 0 ~ 2^31 - 1( 即:0 ~ 4GB )
- 但是:有可能远远达不到这个范围就报错了 ———— 这个涉及到很多,有.class文件格式的定义要求、常量池中对String类的结构体定义、索引定义的大小( 这个可以说:是u2,就是无符号占2个字节,所以2个字节占用的最大范围就是2^16 - 1 = 65535,但是又因为JVM需要一个字节表示结束指令,所以这个范围就成65534( 这个里面涉及到了太多知识,计算机底层各种各样的,所以这里不把点扩大了 )
- 总之就是:在编译时的范围是65534。在运行时拼接或赋值就是Integer的范围,即:0 ~ 4GB
- 总结:
- 为什么String字符串是不可变字符串?不可变体现在哪里?
- 1、因为它底层中有一个char[ ] value属性,而这个字符数组被final修饰了,所以它的长度不可变
- 2、因为它底层中的char[ ] value属性,同时还被private修饰了,所以它的内容不可变
- 为什么String字符串是不可变字符串?不可变体现在哪里?
String字符串的一些常用方法
- 必须熟练掌握的方法:
- int i = hashCode() ———— 这个方法重写了的,不再是Object类中的那个hashCode()方法
- 这个方法在底层中的设计原理很有意思:
- 它是通过把字符串中的每个char字符拆开,放到一个数组中去,然后用这个字符数组中的每个字符对应的ASCII值 + 31 * 一个hash值( hash值一开始是0 ),之后这样不断累加,直到所有字符加完之后,得到一个地址值
- String的hashCode源码如下:
public int hashCode() {
int h = hash; // 这个hash默认值是0
if (h == 0 && value.length > 0) {
char val[] = value; for ( int i = 0; i < value.length; i++ ) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
- String的hashCode源码如下:
- String s = toString() ———— 这个方法也是重写了的,也不是Object类里面的那个,作用没变:还是转化成一个字符串
- boolean b = equals( Object obj ) ———— 这个也是重写了的,也不是Object类里面的方法,作用没变
- int i = CompareTo( String str ) ———— 这个方法是:比较两个字符串是否相等,但是它是通过Unicode码来比较,就是看这两个字符串在Unicode码中的先后顺序
- 其具体比较规则如下:
- 是按照两个字符串中 长度较少的那个字符串 来作为循环次数( 长的那个字符串后面的字符就不用比较了 ),两个字符串中的字符以索引顺序依次比较(如:arr1[0]和arr2[0],arr1[1]和arr2[1],依次这样比较
- 若:比较的时候有一个字符不一样,则:返回这两个不同字符对应的Unicode码之差
- 若:比较完之后,发现每个字符都一样,则:返回这两个字符串的长度之差(有可能是 长的那个字符串长度 - 短的那个字符串长度,也有可能是 短的那个字符串长度 - 长的那个字符串长度【这要看调用这个方法的那个字符串是长的那个还是短的那个字符串了】)
- String的CompareTo()方法在底层中的源码如下:
public int compareTo( String anotherString ) {
int len1 = value.length; // 调用这个方法的那个字符串长度
int len2 = anotherString.value.length; // 这个方法里面传的那个字符串长度
int lim = Math.min( len1, len2 ); // 获取两个字符串中最小长度的那个字符串 ———— 为了当做循环次数
char v1[] = value; // 把调用这个方法的字符串 放到 一个字符数组中
char v2[] = anotherString.value; // 把这个方法中传递的那个字符串 放到 一个字符数组中 int k = 0;
while ( k < lim ) {
char c1 = v1[k];
char c2 = v2[k];
if ( c1 != c2 ) {
return c1 - c2;
}
k++;
}
return len1 - len2;
}
- int i = length() ———— 这个方法是:返回字符串的长度大小
- int i = indexOf( )————这个方法是:查询给定字符 / 字符串 在原字符串中第一次出现的索引值大小
- 另外:这个方法有重载——indexOf( String str , int fromIndex )————即:从哪个索引位置(fromIndex)开始 往后查询 给定字符串(String str )第一次出现的索引值大小
- 注: 看源码的时候别被那个传递的参数类型误导了,参数有输入型和输出型,源码中的那个类型是指输出型参数的数据类型
- 输入型就是我们平时用一个方法,然后传递进去的参数类型
- 输出型就是我们传递参数给一个方法,它运算完之后给我们的结果 的 数据类型
- int i = lastIndexOf()————这个方法是:查询给定字符 / 字符串 在原字符串中最后一次出现的索引值大小
- 另外:这个方法有重载————lastIndexOf( String str , int fromIndex )————即:从哪个索引位置 开始 从前往后查询 给定字符串最后一次出现的索引值大小
- 或者是指:从哪个索引位置 开始从后往前查询 给定字符串最后一次出现的索引 在原字符串中的索引值大小
- 为什么会出现这两种情况?
- 如下图1所示:我要找的那个字符 / 字符串的第一个字母 刚好等于 我要开始查询的索引位置的字符,则:系统会采用从后往前查询 到 我给定索引位置是否出现满足要查询的字符 / 字符串,有的话再看这个索引位置在整个字符串从前往后的索引位置是多少( 有多个满足的话,就会看从后往前最后出现的那一次索引位置 )
- 如下图2所示:我要找的那个字符 / 字符串的第一个字母 不等于 我要开始查询的索引位置的字符,系统就会采用:从前往后查询
- String s = subString( int startIndex )————这个方法是:从开始位置( StartIndex ),往后截取字符串,此方法是默认截取到原字符串的最后(截取出来的是一个全新的字符串)
- 另外:这个方法有重载————String s = subString( int startIndex , int endIndex )————这个方法是:从startIndex位置开始 截取到 endIndex位置的字符串————注意:区间是[ ),前开后闭( 这都是默认的行规 )
- String[ ] s = split( String regex )————这个方法是:按照给定的正则表达式(regex),把原来的字符串拆分开,把分开的每部分放在String类型的一个数组里
- 另外:实际开发中传递的正则表达式一般就是:破折号 “ - ” , 逗号 “ , ”之类的
- String[ ] s = split( String regex )————这个方法是:按照给定的正则表达式(regex),把原来的字符串拆分开,把分开的每部分放在String类型的一个数组里
- boolean b = replace( String oldString , String newString )————这个方法是:把旧的字符串( oldString ) 替换成 新的字符串( newString )—— 这个参数是字符和字符串都可以的啊,别被下面截图中的那个参数误导了
- 另外:这个方法有重载,意思就是按照参数的字面意思理解
- char[ ] c = toCharArray()————这个方法是:将字符串转化为一个字符数组
- String s = trim()————这个方法是:去掉字符串前后的空格(注:字符串中间的空格不能去掉)
- byte[ ] b = getBytes()————这个方法是:把字符串 转换为 一个字节数组(在流技术中会用)
- boolean b = endWith( String str )
- boolean b = startWith( String str )————这两个方法是:查看字符串是否是以什么开始 / 结尾
- String s = valueOf( Object obj )————把一个任意类型、或char类型的数组 转为 字符串 )——这是一个静态方法,即:直接通过 类名. 调用 ———— 这个方法还蛮重要的( 不注意有可能就会忘掉了 )
- 这个方法有重载
- 需要了解的方法( 不是说这些方法就不重要,也重要【 String相关都重要 】,只是相对前面的来说这些方法用的次数稍微少一点 ):
- char c = charAt( int index )————这个方法是:返回给定索引位置的字符
- int i = codePointAt( int index )————这个方法是:返回给定index位置对应字符的code码
- boolean b = contains( String str )————这个方法是:判断给定的字符串在原字符串中是否存在
- boolean b = isEmply()————这个方法是:判断一个字符串是否为空————它的判断实质是:看其length是否为0
- 另外:isEmply和null是有区别的
- null是指它的引用地址为空,如:String s = null;
- isEmply是指有引用地址,有空间,但是空间里面的值是空的,如:String s = “ ”
- String s = toUpperCase()
- String s = toLowerCase()————这两个方法是把字符串中的字母 全部转为大 / 小写
- String s = concat( String str )————这个方法是:将给定的字符串拼接到当前String对象的后面
- 注:方法执行完之后得到的是一个全新的字符串
- 另:concat()方法 和 直接用" + " 拼接有区别
- 前者性能高,后者低
- 因为:后者是不断创建对象,然后把拼接的内容再加上去,前者在底层中创建的对象少( 所以效率更高 )
- 前者性能高,后者低
测试一下:重点掌握的方法是 —— 在以上这些方法中,着重掌握的是:长度 截取 查找下标 替换 转数组 拆分这几个方法,这几个方法实际开发中使用频率最高
package string; public class StringAPI {
public static void main(String[] args) { String s1 = "1abcb1abcd";
// 实例方法
System.out.println( "-----1. 获得长度 length()" );
int length = s1.length();
System.out.println( "length = " + length );
char c = s1.charAt(1);
System.out.println( "c = " + c ); System.out.println( "--- 2. 查找下标 indexOf()--- " );
System.out.println( s1.indexOf('上') );
System.out.println( s1.indexOf("天天") );
System.out.println( s1.indexOf("good",1) );
System.out.println( "----3. 反向查找下标 lastIndexof()----" );
System.out.println( s1.lastIndexOf("abc",6) ); System.out.println( "----4. 截取字符串 substring()----" );
String substring = s1.substring(2,7);
System.out.println( substring );
// 实现截取qq号
String qq = "12344343344@qq.com";
int end = qq.indexOf('@');
System.out.println(qq.substring(0, end)); System.out.println( "------5. 拆分 split()------" );
String yy= "张三锋,男,UI工程师";
String[] arr = yy.split(",");
System.out.println( arr[0]) ;
System.out.println( arr[1] );
System.out.println( arr[2] ); System.out.println( "-------6 替换 replcae()-----" );
String replace = s1.replace("abc", "LOL");
System.out.println( replace ); System.out.println( "---- 7 转小写 toLowerCase()- 转大写 toUpperCase() ----" );
String s7 = "HelloWORLD";
String xiaoxie = s7.toLowerCase();
System.out.println( xiaoxie );
String daxie = s7.toUpperCase();
System.out.println( daxie ); System.out.println( "-----8 忽略大小写比较 equalsIgnoreCase()----" );
String code = "A386f";
String code2 = "a386F";
System.out.println( code.equalsIgnoreCase(code2) ); System.out.println( "---- 9 匹配结尾 endsWith()---" );
String y1 ="面向对象.png";
System.out.println( y1.endsWith(".jpg") ); System.out.println( "----- 10 匹配开头 startWith()----" );
String y2 = "html面向对象";
System.out.println( y2.startsWith("java") ); System.out.println( "----- 11 连接 concat()------" );
String y3= "hello";
String y4= "world";
String y5 = y3.concat( y4 );
System.out.println( y5 ); System.out.println( "------12 检查包含 contains()------" );
System.out.println( y5.contains("java") ); System.out.println( "------ 13 去掉前导后导空格trim()-------" );
String y6 = " hello ";
System.out.println(y6);
System.out.println( y6.trim() ); System.out.println( "-------14 判断空 isEmpty()" );
String y7 = " ";
System.out.println( y7.isEmpty() ); System.out.println( "------ 15 转字符数组 toCharArray()" );
String y8 = "abcdefg";
char[] chars = y8.toCharArray();
System.out.println( chars[0] );
System.out.println( chars[2] );
System.out.println( chars[4] ); char[] v9 = { '东','京','奥','运','会' };
String v10 = new String( v9 );
System.out.println( v10 ); // 静态方法
String s = String.copyValueOf( v9,2,2 );
System.out.println(s); // 连接
String join = String.join( "-", "FWEFWFW", "223FW", "WEFWFWF" );
System.out.println( join ); // 数值类型转字符串
double price = 998.05;
String xx = String.valueOf (price );
System.out.println( xx+1 ); String zz = null;
boolean nullOrEmpty = StringUtils.isNullOrEmpty(zz);
System.out.println( nullOrEmpty ); }
}
// 自己编写String字符串工具类——————这也是实际开发中会干的事情,
// 在实际开发中基本上不会单独来用这个相关的技术,都是封装起来
class StringUtils{
// 检查一个字符串是为null 或者为 空串
public static boolean isNullOrEmpty( String str ){
if( str==null ){
return true;
}
if( str.isEmpty()){
return true;
}
return false;
}
// 截取一个字符串的前n个字符
public static String toN( String s , int n){
return s.substring(0,n);
}
}
补充:String中愚蠢又扯犊子的问题
- 1、问:String a = “123”,在底层中创建了几个对象?
- 答案:一个。就是常量池中的 123 这一个
- 2、问:String s = new String( "789" ),底层中创建了几个对象?
- 答案:有可能1个,也有可能2个
- 原因:
- 如果常量池中有789这个值,则:直接拿过来,所以就只创建了new String()这一个对象————堆内存中的
- 如果常量池中没有789这个值,则:new String()创建了对象之后,再在常量池中创建一个789的对象(这个对象存的就是789)——常量池中的
- 原因:
- 答案:有可能1个,也有可能2个
- 3、问:String s = “A" + "B" + "C" + "D",底层中创建了几个对象?
- 答案:一共是7个————不知道原理的答案:字符串个数(即:本例中的A、B、C、D 4个)+ 拼接符的个数(即:本例中的 + 号,本例中共3个 + 号)
- 原理性解析:因为计算机这个东西并不是很牛逼,所以加法、String拼接不是一下就搞出来的,所以设计的时候是采用了累加的思想。什么意思?就好比从1 + 2 + 3 ......+ 100一样,是先要算出1 + 2的值,然后用这个值 + 3.又得到一个值,用这个值又继续拼接下一个字符.....依次这样下去。
- 而:String的拼接就是这么个原理
- 所以:计算机是先创建了"A" , "B" , "C" , "D"这四个字符串对象
- 接下来的实质是JVM对String做优化的时候干的事情
- 然后:"A" + "B"又是一个对象;"A" + "B" + "C"又是一个对象;"A" + "B" + "C" + "D"又是一个对象
- 因此:结果就是————4+1+1+1 = 7个对象
- 而:String的拼接就是这么个原理
- 原理性解析:因为计算机这个东西并不是很牛逼,所以加法、String拼接不是一下就搞出来的,所以设计的时候是采用了累加的思想。什么意思?就好比从1 + 2 + 3 ......+ 100一样,是先要算出1 + 2的值,然后用这个值 + 3.又得到一个值,用这个值又继续拼接下一个字符.....依次这样下去。
- 4、有如下字符串
- String a = "abc"
- String b = "abc"
- String c = new String( "abc" )
- 问:输出 a == b 、 a.equals( c ) 这两个的结果是什么?
- 解析:
- a == b,答案为true。因为在这里 == 比的是值————补充:== 可以比较基本数据类型,这种是比的值;==也可以比较引用数据类型,这种比的是地址
- a.equals( c ),答案为true。补充:equals只能比较地址,虽然String的equals底层中是重写了equals,可是:它比的还是值(底层中是this == obj,所以还是值),底层源码如下图所示:
- 解析:
- 5、有两句代码:
- String a = new String( " abc")
- a += "123"
- 问:这两句代码创建了几个对象?
- 答案:2个、3个 或者 4个
- 解析:根据第2个问题中的分析
- 若:new String("abc")是1个对象,同时常量池中也有“123”这个对象 ( 如果是没有的情况的话,则:需要再创建1个“123”的对象 )
- 那么:再加上二者( + ) 拼接的时候创建的1个对象,则:结果就为————1 + 0 + 1 = 2 或者 1 + 1 + 1 = 3
- 解析:根据第2个问题中的分析
- 答案:2个、3个 或者 4个
- 若:new String("abc")创建的是2个对象,常量池中没有“123”这个对象( 这时:就需要创建1个“123”对象 )——有“123”这个对象的话,这里就是new String()的2个对象
- 那么:再加上二者( + ) 拼接时的1个对象,则:结果就为————2 + 1 + 1 = 4 或者 2 + 0 + 1 = 3
- 因此:最终的结果就是————2 或 3 或 4个对象
4、StringBuilder和StringBuffer类——可变字符串
- 这两个类都在java.lang包下
- 这两个类都继承了AbstractStringBuilder,实现了Serializable、CharSequence、Appendable接口
- 这二者都是可变字符串,即:可以对char[] value属性进行动态扩容
- 二者的联系与区别:
- StringBuffer:早期版本(JDK1.0的时候出现的 )。StringBuilder: 后来版本( JDK1.5出现的 )
- StringBuffer:解决线程同步的问题,安全性高,但效率低。StringBuilder:解决线程非同步问题,安全性低,但是效率高
- 什么时候需要用到StringBuilder类?
- 字符串频繁拼接、修改字符信息时会用到,提高性能,同时长度和内容都可以改变
4.1、StringBuilder —— 主要用的也是这个
- StringBuilder类如何创建对象?
- 无参构造——注:默认长度是16,即:char[] value中,value.length() = 16
- StringBuilder s = new StringBuilder();
- 利用给定的参数,创建一个自定义空间大小的对象
- StringBuilder s = new StringBuilder( 20 );
- 利用带String参数的构造
- StringBuilder s = new StringBuilder( "abc" );
- 注:这种方式创建的对象,默认大小为:传入的字符串长度 + 16,如:本例中的3 + 16 = 19
- StringBuilder类的常用方法
- append()——拼接字符串(支持频繁拼接——这种情况性能很高)————这个方法也是最核心的方法,没有之一
————>另外:从这个示例中也可以看出:这两个字符串为什么可以变了?
- 是直接操作原来的对象,不再和String一样:是看常量池中是否有那个值,有则复制,没有则重新建一个,然后把地址指给String的那个对象
- 但是以上不是这个方法真正的玩法,这个方法的主要作用就是为了不断拼接,所以Builder是一种设计模式,这种模式支持链式调用,如下图所示:
- 其他的方法和String一样,String可以干的事情,它也可以——因为:它们都是实现了Serializable、CharSequence接口,所以提供的方法都是一样的,干的事情也是一样的
- StringBuilder这个类 特别的几个方法
- StringBuilder s = delete( int StartIndex , int endStart )——注:删除之后,得到的是一个全新的字符串,不是原来的那个字符串
- StringBuilder s = deleteCharAt( int index )————把index位置的字符删掉
- reverse()————把字符串进行反转,如下图所示:
- String和StringBuilder之间相互转换问题
// String --> StringBuilder
String str = "hello";
StringBuilder xx = new StringBuilder( str );
xx.append( "world" );
System.out.println( xx ); // StringBuilder - > String
String yy = xx.toString();
System.out.println( yy );
5、包装类
- 作用:
- 1、就是为了把基本数据类型 转成 引用数据类型(装箱)
- 2、把引用数据类型 转成 基本数据类型(拆箱)
一共有8个包装类,即:
- 整型:byte——Byte、short——Short、int——Integer(特殊的一个,不是和其他一样,直接首字母变大写)、long——Long
- 浮点型:float——Float、double——Double
- 布尔型:boolean——Boolean
- 字符型:char——Character(也是特殊的一个)
- 其中有6个是和数字相关的(除了character、Boolean),都默认继承Number类
- 8个包装类都实现了Comparable、Number接口
- 8个包装类都在java.lang包下,所以我们不用import导包
- 8个包装类 都有自己对应基本类型的参数 的 构造方法——如:Integer中有 Integer( int i )
- 同时8个包装类中有7个有构造方法重载(除了character)——这个重载方法的参数类型是String类型的——如:byte中有 Byte( String s )
- 8个包装类怎么创建自己相应的对象?
- 直接 new 自己对应的包装类( ) ————这个参数只有两种,在上一步讲了:一是自己对应基本类型的参数、二是String类型的参数,如:
- Integer i = new Integer( 10 ); // 这样就创建了Integer包装类的对象了
- Integer num = new Integer( “10” ); // 这样创建的效果和第一种一样,因为底层中调了parseInt()方法【下面马上提到】,把“10”转成了10
- 8个包装类的常用方法
- XxxValue()————这个方法是:将包装类型 转化为 对应的基本数据类型( Number类提供了6个这种拆箱方法【除了Boolean和character】)
- 其中:xxx 是指的要转的包装类名——如:ShortValue()
举个例子:
public class PackingClass { public static void main( String[] args ) {
int a = 10;
Integer integer = new Integer( a ); // 装箱 integer.intValue(); // 拆箱————————JDK1.5之前的玩法
// JDK1.5之后:自动拆箱、装箱
int b = 20;
Integer B = 20;
int c = B; // 直接赋值,装箱和拆箱自动完成了
}
}
- 感受一下:为什么要有这些包装类,因为貌似看起来基本数据类型也可以实现想要的东西
package cn.xieGongZi.test; public class Demo { public static void main(String[] args) { System.out.println( new Person().age ); /* 这里结果会成为什么?—————— 0
但是查询一个人的年龄,结果是个0,合理吗?
不合理,有就是有,没有就是没有,搞个0出来,这不得把不明白的人给搞懵了,以为就是刚出生的人呢
所以:包装类来了————把Person类中的数据类型改为Integer
*/ }
} class Person{ // int age; // 不合理 Integer age; // 利用这样的话,输出结果就为 null,这表明还没有赋值嘛,这就合理了涩
}
- parseXxx()————这个方法是:把字符串 转化为 对应的基本数据类型( xxx指的就是对应想转的基本数据类型名字 )————很关键(实际开发这个东西要经常碰到、用到)
- 怎么调用这个方法?
- 通过 对应的包装类名字.parsexxx( 传递参数 ) ————xxx就是对应包装类的名字
来个例子:
package cn.xieGongZi.test; public class Demo { public static void main(String[] args) { String s = "99.005"; double result = Double.parseDouble(s); // 因为s中的数值是double类型的,所以这里就用Double包装类 }
}
- 问题又来了:为什么需要把字符串转成数值?————这个问题很重要
- 在实际开发的时候,前端传过来的数据统统是字符串,所以呢?做最基本的四则运算都不行————因此:需要把 字符串 转成 数值
来个例子感受一下:
package cn.xieGongZi.test; public class Demo { public static void main(String[] args) { String s = "99.005"; // 假如在这里我想实现:对s做一下 + 法
// String s = s + "10"; // 这样不行涩,直接不得吃 // 所以需要包装类————s的数值是浮点数,那么就通过Double包装类转化
double value = Double.parseDouble(s); // 在这里就可以进行四则运算这些了涩
double result = value + 30;
System.out.println( result ); }
}
总结:什么时候用包装类,什么时候用基本数据类型?
- 如果是进行正常的四则运算之类的,用基本数据类型
- 如果是属性之类的数据类型,就用对应的包装类
6、与数学相关的工具类
- 1、Math类
- 这个类是在java。lang包下
- 这个类的构造方法是私有的,所以不能直接调用和创建对象————直接通过 类名. 进行调用————还因为:这个类底层之中属性和方法都是用static修饰的
- 这个类中的常用方法
- abs()————这个方法是:返回给定数字的绝对值(有重载,参数有int、long、float、double)
- double d = ceil()————这个方法是:是返回一个向上取整的double类型的数
- double = floor()————这个方法是:返回一个向下取整的double类型的数(也有重载)
- double = rint()————这个方法是:返回一个给定数字的 double类型的临近整数( 若:传的这个数小数部分刚好为:0.5、及0.5以上,则:返回的是偶数)
- 注:直接传个0.5进去的话。结果是0.0
- int = round()————这个方法是:返回一个int类型的四舍五入的数(有重载)
- max( a , b )
- min( a , b )————这两个方法是:返回给定两个数的最大数 / 最小数(有重载)
- double = pow( a , b )————这个方法是:返回一个double类型的a的b次方
- double = sqrt()————这个方法是:获取一个double类型的给定数字的平方根
- double = random()————这个方法是:随机产生一个double类型的[0.0 , 1.0)之间的随机数
开始延伸:
- 有这么一个需求:我想要0——9之间的一个随机int类型的数
————>int value = ( int ) (Math.random() * 10 )
这样可以满足
- 如果再有一个需求:我想要5.0——10.9之间的小数
————>( Math.random() * 6 ) + 5
—结果为—>( 0.0.............到..........0.9999 无限循环之间 )* 6 + 5
—结果为—>( 0.0..............到...........5.4999999 无限循环之间 ) + 5
—结果为—> 5.0..................到...........10.499999 无限循环之间
所以:这个结果不是我们所要的到 10.9 之间
因此:精度损失了————>怎么办?
这就引出另一个类:Random类
- 2、Random类
- 这个类也是在java.util包下————即:需要通过import导入
- 除了默认继承Object类,没有继承其他的类
- Random类 如何创建对象?
- 直接通过new关键字创建————Random random = new Random()————>还有一个重载的方法,参数类型是long
- Random类的常用方法
- nextInt()————这个方法是:随机产生int取值范围内的int类型的数————它也有方法重载
- nextInt( int bound )————是指:随机产生一个[ 0 , bound )范围内的整数)———这个方法最常用
- nextFloat()———这个方法是:随机产生一个[ 0.0 , 1.0 )范围内的float类型的数
- nextBoolean()————这个方法是:随机产生一个boolean类型的值
- 3、UUID类
- 在java.util包下
- 只继承了Object类,没有继承其他类
- 有构造方法(但是:没有无参构造)
- UUID类 如何创建对象
- 直接通过 UUID. 进行调用相应的方法————如:
UUID uuid = UUID.random();
System.out.println( uuid.toString() );
- 直接通过 UUID. 进行调用相应的方法————如:
- 补充一点:UUID类什么时候会用?
- 数据库生成一个随机主键(Primary Key)——产生的数是一个32位的,每一个位置是一个16进制的数字,当然也有其他用途
- UUID类的常用方法:就几个而已
- fromString()————这个方法是:用来根据标准字符串形式,创建一个UUID
- 什么是标准字符串形式?
- 字符串必须是
xxx-xxx-xxx-xxx-xxx
的形式,即:正好可以被 ‘-
’ 分割成5块 - 其中:
xxx
可以被转换成16进制的数据,即:每一个的取值范围都在0~9 || a~e
之中 - 如:46400000-8cc0-11bd-b43e-10d46e4ef14d
- 字符串必须是
- 什么是标准字符串形式?
- fromString()————这个方法是:用来根据标准字符串形式,创建一个UUID
- randomUUID()————随机生成一个UUID号————用的最多的是这个方法
举个例子:
- 4、BigInteger类——大的整数——什么时候会用这个类( 就是int类型不够了,如:两个long类型的数相加,所以就会采用——即:大数据运算)————了解即可
- 这个类是在java.Math包下,所以需要import导入
- 这个类继承Number类
- BigInteger类如何创建对象?
- 直接 用new关键字 进行创建
- 注:这个类没有无参构造,全是有参构造——另:参数必须是String类型的,原因如下:
- 因为:这样创建的浮点数刚好和传进去的值是相等的
- 如果是直接传一个浮点值进去,则:创建出来的这个数 并不等于 这个传进去的数,而是 近似于 传进去的这个数,故而:会造成精度丢失
- 因为:这样创建的浮点数刚好和传进去的值是相等的
- 注:这个类没有无参构造,全是有参构造——另:参数必须是String类型的,原因如下:
- 直接 用new关键字 进行创建
BigInteger bigInteger= new BigInteger("12.0"); // 这样创建是可以的,创建出来的这个对象值刚好为12.0 BigInteger integer= new BigInteger(12.0); // 这样创建就不行,创建出来的这个值是近似于12.0
double d = 12.0; // 如果创建的是这样的一个数
BigInteger integer1= new BigInteger( String.valueOf(d) ); // 则:创建BigInteger对象时,必须转成字符串类型
- BigInteger类 的 常用方法——主要用来做四则运算
- add()————做加法
- subtract()————做减法
- multiple()————做乘法
- divide()————做除法
来个例子:
设计一个方法,用来算一个数的阶乘
class Demo{ // 算阶乘
public BigInteger facterial( int number ){ BigInteger result = new BigInteger("1"); for (int i = 1; i <= number ; i++) { result = result.multiply(new BigInteger(i + ""));
} return result;
}
}
- 5、BigDecimal类————大的小数(即:超过了double范围——用来做高精度运算)————了解即可
- 在java.Math包下
- 继承了Number类
- 这个类对照着BigInteger类来学就可以了,因为它们完全一致,创对象、方法都一样:加减乘除
- 但是这里面有一个方法需要了解:
- setScale( int newScale , int RoundingMode )————这个方法可以设置:要保留的小数为几位(即:newScale) 和 参数的设置模式(即:RoundingMode)——也就是想要小数部分的结果是怎样保留的(四舍五入、向上取值.....还是什么)
- 注意:后面那个参数【即:RoundingMode】是一个静态的,所以需要通过 类名. 调出来
bigDecimal.setScale(2, BigDecimal.ROUND_DOWN); // 小数保留两位,采用四舍五入取值
- 注意:后面那个参数【即:RoundingMode】是一个静态的,所以需要通过 类名. 调出来
7、与日期相关
- 1、Data类
- 注意:这个类用的是java.util包下的那个,不是java.sql包下的data(虽然这个后续也会用到)
- Data类怎么创建对象?
- 通过 new 关键字进行创建,只需注意:别选错包了
// 创建一个Data对象
Date date = new Date();
System.out.println( date ); // 但是这种时间格式是:Tue Aug 10 10:42:33 GMT+08:00 2021 这样的
- 通过 new 关键字进行创建,只需注意:别选错包了
- 注意:这个类用的是java.util包下的那个,不是java.sql包下的data(虽然这个后续也会用到)
- Data类的常用方法(但是实际开发中貌似都没用过以下方法 ^ _ ^,因为它其他方法都被淘汰了,所以只剩下下面这些————但是Data对象在开发中会用)
- before()
- after()————这两个方法是:查看对象1 是否 在对象2之前 / 之后————原理是比较对象的时间戳,时间戳是从:1970-01-01 00:00:00开始到今天
// 创建一个Data对象
Date date1 = new Date();
Date date2 = new Date(); System.out.println( date2.before( date1 ) );
- void = setTime( long L )
- getTime()————这两个方法是:设置 / 获取时间————但是结果是一个long类型的毫秒值
-
- compareTo( Data anotherData )————这个方法是:按照顺序比较两个对象的先后顺序
- 返回值为-1,表示:调用的这个方法的对象在前,参数中的对象在后
- 返回值为1,表示:和上一步刚好相反
- 返回值为0,表示:两个对象一样
- 从以上可以得出:好像这个Data的时间也不是平时显示的那种格式,所以引申出另一个类:DataFormat类
- 2、DataFormat类————可以处理日期的格式
- 在java.text包下
- 此类是抽象类,故不能创建对象,所以通过子类来创建对象
- SimpleDataFormat类 是 DataFormat类的子类,所以创建对象用SimpleDataFormat类来实现
- SimpleDataFormat类
- 这个类怎么创建对象?————通过String类型的有参构造
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm;ss");
/*
这个对象参数里面的格式,就是想要显示的日期格式,区分大小写的,所以需要用到这里面相关的内容时:记得按标准来,别全大写,或全小写
yyyy 表示年,year
MM 大妹妹M,表示月 mouth
dd 小弟弟d,表示日 day
hh 表示小时,hour
........
*/示例:
// SimpleDataFormat类
Date date = new Date(); // 先得到当前系统时间 , 这个是一个标准时间
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm;ss"); // 设置我想要显示的时间格式
System.out.println( dateFormat.format( date ) ); // 把标准时间 转换成 自己的时间格式
- 这个类怎么创建对象?————通过String类型的有参构造
- 跟日期相关的类只有如上这些的话,好像还是不够,所以继续引申
- SimpleDataFormat类
- 3、Calendar类————日历
- 在java.util包下
- 这个类创建对象的方式很特殊
- Calender类有构造方法,但是是用protected修饰的,所以需要通过如下方式进行对象的创建
- 通过 getInstance() 方法获取对象
// Calender类
Calendar calendar = Calendar.getInstance();
- 通过 getInstance() 方法获取对象
- Calender类有构造方法,但是是用protected修饰的,所以需要通过如下方式进行对象的创建
- Calender类的核心方法如下:
- get( int field )————这个方法是:获取日历的一个字段——年、月、日.....,其中field是通过静态方法来调用,即:Calender.
// Calender类
Calendar calendar = Calendar.getInstance();
int year = calendar.get( Calendar.YEAR );
System.out.println( year );
- set( int field , int value )————这个方法是:修改日历中的字段————年、月、日
Calendar calendar = Calendar.getInstance();
int year = calendar.get(Calendar.YEAR);
System.out.println( year );
System.out.println(); calendar.set(Calendar.YEAR, 2028);
System.out.println( calendar.get(Calendar.YEAR) );
- get( int field )————这个方法是:获取日历的一个字段——年、月、日.....,其中field是通过静态方法来调用,即:Calender.
- 这个类创建对象的方式很特殊
- add( int field , int amcount )————这个方法是:给日历中的某一个字段 加上 一个值(即:amcount的大小)
// 给日历中的某一个字段加上一个字
calendar.add(Calendar.YEAR,1);
System.out.println( calendar.get( Calendar.YEAR ));
- add( int field , int amcount )————这个方法是:给日历中的某一个字段 加上 一个值(即:amcount的大小)
- getActualMaxmum( int field )
- getActualMinmum( int field )————这两个方法是:获取日历中的最大、最小值
// 获取日历中的最大、最小值
System.out.println( calendar.getActualMaximum( Calendar.DAY_OF_MONTH ) ); // 结果为:31
System.out.println( calendar.getActualMinimum( Calendar.DAY_OF_MONTH ) ); // 结果为:1
- 自己模仿做一个日历
public static void main(String[] args) { // 获得日历
Calendar calendar = Calendar.getInstance();
calendar.set(2021,5,1);
// 输出日历头部
String[] days = {"一","二","三","四","五","六","日"};
for ( String day : days){
System.out.print(day+"\t");
}
System.out.println();
// 输出日历身体
int min = calendar.getActualMinimum(Calendar.DAY_OF_MONTH);
int max = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
int xq = calendar.get(Calendar.DAY_OF_WEEK);
// 输出首行空格
for(int i=1; i<=xq-2;i++){
System.out.print("\t");
}
// 输出全部号数
for(int i=min; i<=max; i++){
System.out.print(i+"\t");
calendar.set(Calendar.DAY_OF_MONTH,i); // 把日历指向这一天
int x= calendar.get(Calendar.DAY_OF_WEEK) ; // 如果这一天是星期天 则换行
if (x==1){
System.out.println();
}
}
}
8、额外补充的一个工具类————这个类还蛮有用^ _ ^
- Arrays类——这个类在基础篇中已经用过部分方法了
- Arrays类在java.util包下
- Arrays这个类是用来干嘛的?
- 看名字就知道:这是一个跟数组相关的类,所以这个类里面就是提供一些跟数组操作相关的方法
- Arrays这个类是用来干嘛的?
- toString()——————就是将数组 转为 字符串
- copyOf( Object[ ] , newArrayLength ) ———— 这个方法是:数据拷贝,其实在入门篇已经用过了————这个方法有重载,支持各种类型的数组
// 数据拷贝
int[] array = { 3,5,1,6,8,2 };
int[] newArray = Arrays.copyOf( array, 10 );
System.out.println( "其长度为:" + newArray.length );
- sort( Object[ ] )————这个方法是:可以对一个数组里面的元素进行排序————这个方法的底层是用了快速排序————这个方法也有重载,支持各种类型数组
int[] array = {3, 5, 2, 6, 1, 8, 4}; // 对数组里面的元素进行排序
Arrays.sort( array );
for (int element : array) {
System.out.print(element + " ");
}
binarySearch( Object[], Object key )————二分查找(要求:这个传进来的数组必须是有序的)
指的是:在指定数组中查找key、返回的是要找的key在数组中的下标,也有重载
// 二分查找
int[] arr = {1, 2, 3, 4, 5, 6};
System.out.println( Arrays.binarySearch( arr, 5 ) ); // 这是 在arr中 查找 5 这个元素的索引位置
- copyRange( Object[ ] , int startIndex , int endIndex)————这个方法是:拷贝数组 [ startIndex , endIndex )的元素————这生成出来的是一个新数组
// 拷贝数组一个指定区间的元素
int[] a = {1, 2, 3, 4, 5, 6};
int[] copyArray = Arrays.copyOfRange(array, 2, array.length);
for (int element : copyArray) { System.out.print( element + " ");
}
- equals( Object[ ] a , Object[ ] a2 )————这个方法是:比较两个数组是否相等,支持各种类型的数组,方法重写了的,不再是Object类里面的那个equals()方法
- 底层中源码:
public static boolean equals(int[] a, int[] a2) {
if (a==a2)
return true;
if (a==null || a2==null)
return false; int length = a.length;
if (a2.length != length)
return false; for (int i=0; i<length; i++)
if (a[i] != a2[i])
return false; return true;
}
- 底层中源码:
- fill( Object[ ] arr ,int startIndex , int endIndex , Object value )————这个方法是:给数组填充元素
- 注:是填充,不是添加,这个方法有重载,类似于替换,即:不给填充位置,则:默认是把所有元素替换成给定的value
至此,javaSE的工具类篇结束
但是:上面这些只是简单的罗列出来的,也就是简单了解而已
所以在实际开发中不知道的方法,但是知道在什么工具类里面有,这时就直接看API文档(在本篇博客的首页已说明在哪里找了)
学会随时看API文档
我上面有些说的是没什么卵用,但是别人设计出来了就一定有用,只是自己没遇到需要用的那种情况而已
因此:API才是大哥,我只是搬运工
最新文章
- vue中v-bind:class动态添加class
- OpenFileDialog - 设置 - Filter 笔记
- lucene join解决父子关系索引
- java 模拟消息的发送功能
- .gitignore规则不生效的解决办法
- bootstrapValidator对于隐藏域验证和程序赋值即时验证的问题
- javascript事件详解1
- Building Web Apps with SignalR, Part 1
- FTP上传文件时 System.Net.WebException: 基础连接已经关闭: 接收时发生错误。
- unity游戏设计之背包系统
- Android执行时ART载入类和方法的过程分析
- levmar ndk 编译
- 【Bootstrap】bootstrap-datetimepicker日期时间插件
- EJB通过注解方式注入并使用其他EJB或者服务、配置JBoss数据源
- 当Ucenter和应用通信失败
- MyIsam与InnoDB存储引擎主要区别
- 基于vue-cli配置移动端自适应
- Java线程池应用及原理分析(JDK1.8)
- 【题解】Luogu P2057 [SHOI2007]善意的投票
- SpringBoot------全局异常捕获和自定义异常