文章目录
- 1、Java小白踩坑录 - String和char
- 2、Java小白踩坑录 - Random 揭秘
- 3、Java小白踩坑录 - B计划之Java资源如何释放?
- 4、Java小白踩坑录 - 反射到底有多慢?
- 5、Java小白踩坑录 - 数组 & List
- 6、Java小白踩坑录 - Java类型的七十二变揭秘
- 7、Java小白踩坑录 - Java 编程语言中很少被人了解的特性(标号语句)
- 8.Java小白踩坑录 - 难以琢磨的try-catch
- 9.Java小白踩坑录 - 一分钱到底去哪里了?
- 10、Java小白踩坑录 - 反射的小秘密
- 11、Java小白踩坑录 - 猿类分级考试实录
- 12.Java小白踩坑录 - Java有关null的几件小事
1、Java小白踩坑录 - String和char
System.out.println("h"+"i");
System.out.println('h'+'i');
hi
209
解决方案:
//1
System.out.println("" + 'h' + 'i');//2
System.out.printf("%c%c", 'h', 'i');
System.out.println(String.format("%c%c", 'h','i'));//3
StringBuffer sb = new StringBuffer();
sb.append('h');
sb.append('i');
System.out.println(sb);
private static Random rnd = new Random();public static void main(String[] args) {StringBuffer word = null;switch(rnd.nextInt(2)) {case 1: word = new StringBuffer('P');case 2: word = new StringBuffer('G');default: word = new StringBuffer('M');}word.append('a');word.append('i');word.append('n');System.out.println(word);
}
ain
为何?
- Ranmdom API:Random(next):均等返回0-next-1,这里只有0,1,没有2,应该写为3
- 没有break,那就一直执行到最后,也就是default,覆盖前面所有的赋值
- 编译器选择接受int类型,通过拓宽原始类型转换把M转为77,换句话说,new StringBuffer (‘M’) 返回的是一个具有初始容量 77 的空的字符串缓冲区。该程序余下的部分将字符 a、i 和 n 添加到了这个空字符串缓冲区中,并打印出该字符串缓冲区那总是 ain 的内容。
解决方案:
private static Random rnd = new Random();
public static void main(String[] args) {System.out.println("PGM".charAt(rnd.nextInt(3)) + "ain");
}
2、Java小白踩坑录 - Random 揭秘
private static final Random rnd = new Random();
//第一种
static int random(int n) {return n>=0 ? rnd.nextInt(n) : rnd.nextInt(Math.abs(n));
}
//第二种
static int random2(int n) {return Math.abs(rnd.nextInt())%n;
}
public static void main(String[] args) {int n = 1000;int di = 0;int low = 0;for(int i = 0;i < 10_000_000;i++) {if(random(n) < Math.abs(n/2)) {di++;}if(random2(n) < Math.abs(n/2)) {low++;}}System.out.println(di);System.out.println(low);
}
运行数据看结果就会发现:
- random 的数据比较均匀;
- random2 的数据差不多 2/3 落到前半部分,后半部分只有 1/3
nextInt() 这个方法看起来可能不错,但是存在三个缺点。
第一个缺点是: 如果 n 是比较小的 2 的乘方,经过一段相当短的周期之后,它产生的随机数将会重复。
第二个缺点是:如果 n 不是 2 的乘方,那么平均起来,有些数就会比其它的数出现的更频繁。特别是 n 比较大,这个缺点就非常明显。这就是为什么 2*(Integer.MAX_VALUE/3)
中有用2乘的原因。
第三个缺点是:在极少数情况下,会返回一个落在指定范围之外的数或者报错。示例如下:
public static void main(String[] args) {int n=Integer.MIN_VALUE;int di=0;int low=0;for(int i=0;i<10_000_000;i++) {if(random(n)<Math.abs(n/2)) {di++;}if(random2(n)<Math.abs(n/2)) {low++;}}System.out.println(di);System.out.println(low);
}
报错原因 Math.abs() 碰到 Integer.MIN_VALUE 时会返回 Integer.MIN_VALUE。在 abs 的方法说明中有这么一段话:
Note that if the argument is equal to the value of {@link Integer#MIN_VALUE}, the most negative representable{@code int} value, the result is that same value, which is negative.
即注意:Math.abs() 方法中,如果输入值为 Integer.MIN_VALUE,那么会返回同样的结果,就是MIN_VALUE绝对值还是负数,还是它本身
另一方面,也可以看看 abs 的代码实现来理解
public static int abs(int a) { return (a < 0) ? -a : a; }
假设 a=Integer.MIN_VALUE 即 -2147483648(0x80000000),假设返回 int 的值 2147483648 会发生溢出,因为 int 的最大值为 2147483647(0x7fffffff),溢出后又变成了 0x80000000,即 Integer.MIN_VALUE
源码描述:
/*** Returns the absolute value of an {@code int} value.* If the argument is not negative, the argument is returned.* If the argument is negative, the negation of the argument is returned.** <p>Note that if the argument is equal to the value of* {@link Integer#MIN_VALUE}, the most negative representable* {@code int} value, the result is that same value, which is* negative.** @param a the argument whose absolute value is to be determined* @return the absolute value of the argument.*/public static int abs(int a) {return (a < 0) ? -a : a;}
Java 类库提供了一个带 seed 的方法来解决上面的问题,就是 Random.nextInt(n)。
总结
随机数的生成器涉及了很多算法的相关知识,幸运的是,我们并不需要自己来做这些工作,我们可以利用现成的成果为我们所用,如 Random.nextInt(n) 或者 java.security.SecureRandom,或者第三方的 API。注意:我们尽量使用类库,而不是自己去开发。
Linux 系统有 /dev/random,/dev/urandom 向用户提供真随机数。
3、Java小白踩坑录 - B计划之Java资源如何释放?
public void openFile() throws IOException {String line;try (BufferedReader br = new BufferedReader(new FileReader("C:\\testing.txt"))) {while ((line = br.readLine()) != null) {System.out.println(line);}} catch (IOException e) {e.printStackTrace();}
}
从 JDK7 开始,使用 try-with-resources 可以自动释放资源,即把资源放到 try() 内部, JVM 会调用 java.lang.AutoCloseable.close() 方法,自动关闭 try() 内部的资源。
4、Java小白踩坑录 - 反射到底有多慢?
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
public class ReflectionTest {private static final int batchsize=1000000;public static void main(String[] args) {//非反射方式测试long start=System.currentTimeMillis();addWithoutReflect();System.out.println(System.currentTimeMillis()-start);//反射方式测试start=System.currentTimeMillis();addWithReflect();System.out.println(System.currentTimeMillis()-start);}//非反射方式static void addWithoutReflect() {List<String> list=new ArrayList<>();for(int i=0;i<batchsize;i++) {list.add(""+i);}}//反射方式static void addWithReflect() {List<String> list=new ArrayList<>();Method method=null;try {method = list.getClass().getMethod("add", Object.class);} catch (NoSuchMethodException | SecurityException e) {e.printStackTrace();}for(int i=0;i<batchsize;i++) {try {method.invoke(list, ""+i);} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {e.printStackTrace();} }}
}
经过不同级别的测试,测试结果如下(本次测试,不做任何数据的修饰):单位毫秒
方式 \ 数据量 | 10 | 100 | 1000 | 1w | 10w | 100w | 1kw | 10kw |
---|---|---|---|---|---|---|---|---|
普通方式 | 0 | 1 | 1 | 4 | 24 | 96 | 6384 | 107714 |
反射方式 | 1 | 3 | 4 | 9 | 35 | 117 | 2004 | 71285 |
可以大致看出,在百万以下级别,反射方式的耗时是普通方式的 1~4 倍,千万级别以上的话,反而普通方式比较慢,原因未知,一般情况下,1w 次以下的反射对性能的影响微乎其微(0.05 秒内),可以忽略。
总结
如果只是以非常有限的形式使用反射机制,虽然也要付出少许代价,但是可以获得许多好处。使用反射可能会付出的代价:
- 丧失了编译时类型检查的好处,包含异常检查;
- 执行反射访问所需要的代码非常笨拙和冗长;
- 性能损失;
- 对性能敏感的接口尽量不使用反射或者少使用反射。
5、Java小白踩坑录 - 数组 & List
import java.util.ArrayList;
import java.util.List;
public class TestArray {private static final int batch = 10_000;public static void main(String[] args) {long start = System.currentTimeMillis();insert2Array();long end = System.currentTimeMillis();System.out.println(end-start);insert2List();System.out.println(System.currentTimeMillis()-end); }static void insert2Array() {int[] nums = new int[batch];for(int i = 0;i < batch;i++) {nums[i]=i;}}static void insert2List() {List<Integer> nums=new ArrayList<Integer>();for(int i=0;i<batch;i++) {nums.add(i);}}
}
数量级 batch 不同,耗时不同:
batch | 数组耗时 | Arraylist 耗时(单位毫秒) |
---|---|---|
1w | 0 | 1 |
10w | 1 | 4 |
100w | 3 | 18 |
1kw | 18 | 196 |
10kw | 153 | 21880 |
List<Integer> nums=new ArrayList<Integer>();
// ...
nums.add(i);
因 List 只能存放 Integer 对象型的变量,i 需要先转为 Integer,然后才能插入,就是这个动作影响了主要性能,我们可以验证一下这个问题。
这里还有一点是 list 这需要不断扩容也会耗时。
装箱,扩容
public class TestArray2 {private static final int batch = 10_000;public static void main(String[] args) {long start=System.currentTimeMillis();insert2Array();long end=System.currentTimeMillis();System.out.println(end-start);insert2List();System.out.println(System.currentTimeMillis()-end); }static void insert2Array() {String[] nums = new String[batch];for(int i=0;i<batch;i++) {nums[i]=""+i;}}static void insert2List() {List<String> nums=new ArrayList<String>();for(int i=0;i<batch;i++) {nums.add(""+i);}}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WDU8BgPQ-1663421492587)(C:\Users\86191\AppData\Roaming\Typora\typora-user-images\image-20220917183842042.png)]
总结
Java 语言设计的数据结构如 set、list、map 等只能存放引用对象,碰到数值型的问题时,就会发生拆箱、装箱的动作,特别是数据较大时,拆箱、装箱对性能的影响就比较大了,所以写程序时或者设计时需要根据场景选择合适的数据结构。
6、Java小白踩坑录 - Java类型的七十二变揭秘
public class Multicast{public static void main (String[] args){System.out.println((int)(char)(byte) -1);}
}
65535
public static void main(String[] args) {int i = -1;byte b = (byte) i; //1char c = (char) b; //2int r = (int) c; //3System.out.println(r);}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D4zII7mO-1663421492589)(C:\Users\86191\AppData\Roaming\Typora\typora-user-images\image-20220917190234434.png)]
“因为 byte 是一个有符号类型,而 char 是一个无符号类型。在将一个整数类型转换成另一个宽度更宽的整数类型时,通常是可以保持其数值的,但是却不可能将一个负的 byte 数值表示成一个 char。因此,从 byte 到 char 的转换被认为不是一个拓宽原始类型的转换,而是一个拓宽并窄化原始类型的转换:byte 被转换成了 int,而这个 int 又被转换成了 char”。
1.-1 是 int 类型,它的二进制结构
0b1111_1111_1111__1111_1111_1111_1111_1111
int 转成 byte,截取低 8 位 0b1111_1111 值也为 -1
2.byte 转 char,需要先拓展到 int 型,然后转成 char
2.1 byte 转 int
0b1111_1111_1111__1111_1111_1111_1111_1111
2.2 int 转 char
0b1111_1111_1111__1111
值位 65535
3.char 转 int (补零)
0b0000_0000_0000__0000_1111_1111_1111_1111
其值位 65535
负数-5的表示法
现在想知道,-1在计算机中如何表示?在计算机中,负数以原码的补码形式表达。
10000....1->11111111..0+1->11111111..1
7、Java小白踩坑录 - Java 编程语言中很少被人了解的特性(标号语句)
public static void main(String[] args) {System.out.print("baidu site :");https://www.baidu.com;System.out.println(" format");
}
“:” 是 statement label 翻译成标号语句
interface FileNotFound {void run() throws FileNotFoundException;
}interface CloneNotSupported {void run() throws CloneNotSupportedException;
}class TryCatchException implements FileNotFound, CloneNotSupported {public static void main(String[] args) {TryCatchException e = new TryCatchException();e.run();}@Overridepublic void run() {System.out.println("Hello world");}
}
Hello world
8.Java小白踩坑录 - 难以琢磨的try-catch
public static void main(String[] args) {try {System.out.println("Hello world");} catch(IOException e) {System.out.println("抓到一个 IO 异常!");}}
就是 try 里没有能抛出 IOException 异常的语句,catch 该异常就通不过编译。
import java.io.IOException;
public class TryCatchException {public static void main(String[] args) {try {System.out.println("Hello world");throw new IOException();//或者子异常,如throw new FileNotFoundException();} catch(IOException e) {System.out.println("抓到一个IO 异常!");}}
}
public class TryCatchException {public static void main(String[] args) {try {System.out.println("hello world!");} catch(Exception e) {System.out.println("捕获到异常");}}
}
hello world!
不管与其相对应的 try 子句的内容是什么,捕获 Exception 或者 Throwable 的 catch 语句是 ok 的,这点 JSL 并没有说清楚。
public interface FileNotFound {void run() throws FileNotFoundException;
}
public interface CloneNotSupported {void run() throws CloneNotSupportedException;
}
public class TryCatchException implements FileNotFound,CloneNotSupported {public static void main(String[] args) {TryCatchException e=new TryCatchException();e.run();}@Overridepublic void run() {System.out.println("Hello world"); }
}
一个方法可以抛出的受检查异常的集合时,它所适用所有类型声明要抛出的受检查异常的交集,而不是合集。
9.Java小白踩坑录 - 一分钱到底去哪里了?
public static void main(String[] args) { double total=2.00f;double cost=1.10f;System.out.println(total-cost);
}
0.8999999761581421
public static void main(String[] args) {BigDecimal total=new BigDecimal(2.00);BigDecimal cost=new BigDecimal(1.10);System.out.println(total.subtract(cost));
}
0.899999999999999911182158029987476766109466552734375
将 double 值传入 BigDecimal 实例里,double 本身会损失精度,故结果也会损失精度;而使用BigDecimal 实例 total 和 cost 会解析字符串的值,不会损失精度。详细实现你可以看 BigDecimal 的源码”
public static void main(String[] args) {BigDecimal total=new BigDecimal("2.00");BigDecimal cost=new BigDecimal("1.10");System.out.println(total.subtract(cost));
}
0.90
在我们的业务中可以使用 int 结算,毕竟总额和消费金额及余额最小单位为分,可以使用 int 计算出
public static void main(String[] args) { int total=200;int cost=110;System.out.println((200-110)+" cents");
}
总结
在需要精确答案的地方,要避免使用 float 和 double;对于货币计算,要使用 int、long 或 BigDecimal,同时也要考虑业务的本身的使用场景,选择合适的数据类型。
10、Java小白踩坑录 - 反射的小秘密
public static void main(String[] args) throws Exception {Set<String> s = new HashSet<String>();s.add("foo");Iterator<String> it = s.iterator();Method m = it.getClass().getMethod("hasNext");System.out.println(m.invoke(it));
}
Exception in thread "main" java.lang.IllegalAccessException: Class com.javapuzzle.davidwang456.ReflectorTest can not access a member of class java.util.HashMap$HashIterator with modifiers "public final"at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)at java.lang.reflect.Method.invoke(Method.java:491)at com.javapuzzle.davidwang456.ReflectorTest.main(ReflectorTest.java:15)
11、Java小白踩坑录 - 猿类分级考试实录
一阶猿类
public class Counter1 {private static int cnt=0;public int increase() {return ++cnt;}public int decrease() {return --cnt;} }
旁白:实现了功能。
二阶猿类
public class Counter2 {private static long cnt=0;public long increase() {return ++cnt;}public long decrease() {return --cnt;}
}
旁白:考虑了 int 的范围限制,long 的范围更广泛。
三阶猿类
public class Counter3 {private static long cnt=0;public synchronized long increase() {return ++cnt;}public synchronized long decrease() {return --cnt;}
}
旁白:考虑了并发环境下的执行。
四阶猿类
public class Counter4 {private static AtomicLong cnt=new AtomicLong(0);public long increase() {return cnt.getAndIncrement();}public long decrease() {return cnt.getAndDecrement();}
}
旁白:考虑了并发环境下的 CAS 性能更优。
五阶猿类
public class Counter5 {private static LongAdder cnt=new LongAdder();public long increase() {cnt.increment();return cnt.longValue();}public long decrease() {cnt.decrement();return cnt.longValue();}
}
旁白:在单线程下,并发问题没有暴露,两者没有体现出差距;随着并发量加大,LongAdder 的 increment 操作更加优秀,而 AtomicLong 的 get 操作则更加优秀。鉴于在计数器场景下的特点—写多读少,所以写性能更高的 LongAdder 更加适合。
六阶猿类
public class Counter6 {private static JdbcTemplateUtils jdbc=new JdbcTemplateUtils();//JdbcTemplateUtils封装了jdbc的调用private static long cnt=0;public long increase() {cnt=jdbc.getCnt(); return jdbc.setCnt(++cnt);}public long decrease() {cnt=jdbc.getCnt();return jdbc.setCnt(--cnt);;}
}
旁白:考虑了在集群环境下保证数据的唯一性和一致性。
七阶猿类
public class Counter7 {private static RedisclusterUtils redis=new RedisclusterUtils();//RedisclusterUtils封装了Rediscluster的client功能private static long cnt=0;public long increase() { return redis.incr(cnt);}public long decrease() {return redis.decr(cnt);;}
}
旁白:考虑了计数器集群下的并发性能问题,同样的实现可以使用 zk 或者 mongo 等内存数据库。
注:ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是 Google 的 Chubby 一个开源的实现,是 Hadoop 和 Hbase 的重要组件。MongoDB 是一个基于分布式文件存储的数据库。
八阶猿类
public class Counter8 {private static JdbcTempalteUtils jdbc=new JdbcTempalteUtils();private static RedisclusterUtils redis=new RedisclusterUtils();private static long cnt=0;public long increase() { if(redis.exsits(cnt)) {return redis.incr(cnt);}cnt=jdbc.getCnt(key);++cnt;redis.set(key,cnt);return cnt;}public long decrease() {if(redis.exsits(cnt)) {return redis.decr(cnt);}cnt=jdbc.getCnt(key);--cnt;redis.set(key,cnt);return cnt;}
}
旁白:考虑到 Redis 宕机或者不可用的情况下的处理,有备份方案。
九阶猿类
这个要免考的。
12.Java小白踩坑录 - Java有关null的几件小事
Java 中的空 null
我们先看几段代码吧。
1. 例一:null 的对象性
public class NullTest {public static void greet() {System.out.println("Hello world!");}public static void main(String[] args) {((NullTest) null).greet();}
}
上面的程序看起来似乎应该抛出 NullPointerExceptioin 异常,因为其 main 方法是在常量 null 上调用 greet 方法,而你是不可以在 null 上调用方法的,对吗?
其实编译和运行都没有问题。运行结果为:
Hello world!
2. 例二:null 的初始化
public static void main(String[] args) {String str=null;Integer in=null;Double dou=null;String str1=(String)null;Integer in1=(Integer)null;Double dou1=(Double)null;int in2=null;int in3=(int)null;
}
发现 null 可以初始化引用类型,也可以转换为任意的引用类型。但不能给基本类型赋值,或者转换为基本类型。
3. 例三:null 的相等性
public static void main(String[] args) {System.out.println(null==null);System.out.println(null!=null); System.out.println(Double.NaN==Double.NaN);System.out.println(Double.NaN!=Double.NaN);
}
结果该是什么呢?
true
false
false
true
4. 例四:null 不是引用类型
public static void main(String[] args) {Integer in=null;if(in instanceof Integer) {System.out.println("null is integer");}else {System.out.println("null is not integer");}
}
结果是:
null is not integer
5. 例 5:不可传递
public static void main(String[] args) {Integer i=null;int k=i;System.out.println(k);}
报错:
Exception in thread "main" java.lang.NullPointerExceptionNullTest.main(NullTest.java:6)
6. 例 6:null 的数组
public static void main(String[] args) {String[] arr1={"abc","123",null,"sky"};boolean flag=false;for (String s1 : arr1) {if(s1.equals("sky")) {flag=true;break;}}System.out.println(flag);
}
运行时报错
Exception in thread "main" java.lang.NullPointerException
at NullTest.main(NullTest.java:8)
修改成
public static void main(String[] args) {String[] arr1={"abc","123",null,"sky"};boolean flag=false;for (String s1 : arr1) {if("sky".equals(s1)) {//对比前后顺序flag=true;break;}}System.out.println(flag);
}
就没有问题了。
追根到底
JSL 3.10.7 定义了 null
The null type has one value, the null reference, represented by the null literal null, which is formed from ASCII characters.
JSL 4.1 做了补充:
1.There is also a special null type, the type of the expression null (§3.10.7, §15.8.1), which has no name.
Because the null type has no name, it is impossible to declare a variable of the null type or to cast to the null type.
注:null 是一种特殊类型,它的表达式为 null,但没有名称。因为 null 类型没有名称,故不能声明一个 null 类型(如 private null a),也不能将一个类型转为 null 类型。
2.The null reference is the only possible value of an expression of null type.
注:使用 null 类型的唯一方式是使用 null 引用(如 private Integer a = null);
3.The null reference can always be assigned or cast to any reference type (§5.2, §5.3, §5.5).
注:空引用可以赋值给其他任意类型,如 String,Integer,Class 等等。
4.In practice, the programmer can ignore the null type and just pretend that null is merely a special literal that can be of any reference type.
注:其实,程序开发者可以忽略 null 类型,仅仅将它当作一种可以赋值给其他任意类型的特殊引用。