Java小白踩坑录上

Java小白踩坑录上,第1张

文章目录 1、Java小白踩坑录 - String和char2、Java小白踩坑录 - Random 揭秘3、Java小白踩坑录 - B计划之Java资源如何释放?4、Java小白踩坑录 - 反射到底有多慢?5、Java小白踩坑录 - 数组 & List6、Java小白踩坑录 - Java类型的七十二变揭秘7、Java小白踩坑录 - Java 编程语言中很少被人了解的特性(标号语句)8.Java小白踩坑录 - 难以琢磨的try-catch9.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.
 *
 * 

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();
			}			
		}
	}
}

经过不同级别的测试,测试结果如下(本次测试,不做任何数据的修饰):单位毫秒

方式 \ 数据量1010010001w10w100w1kw10kw
普通方式011424966384107714
反射方式134935117200471285

可以大致看出,在百万以下级别,反射方式的耗时是普通方式的 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 耗时(单位毫秒)
1w01
10w14
100w318
1kw18196
10kw15321880
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);
		}
	}
}

总结

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; //1
        char c = (char) b; //2
        int r = (int) c; //3
        System.out.println(r);
    }

“因为 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();
    }

    @Override
    public 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();
 }
 @Override
 public 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.NullPointerException



NullTest.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 类型,仅仅将它当作一种可以赋值给其他任意类型的特殊引用。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/web/2990375.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-09-23
下一篇 2022-09-23

发表评论

登录后才能评论

评论列表(0条)

保存