for 循环的执行顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
static boolean foo(char c){
System.out.print(c);
return true;
}
public static void main(String[] args) {
int i =0;
for(foo('A');foo('B')&&(i<2);foo('C')){
i++;
foo('D');
}
}

// 输出 ABDC BDCB

try catch finally

当Java程序执行try块、catch块时遇到了return或throw语句,这两个语句都会导致该方法立即结束,但是系统执行这两个语句并不会结束该方法,而是去寻找该异常处理流程中是否包含finally块,如果没有finally块,程序立即执行return或throw语句,方法终止;如果有finally块,系统立即开始执行finally块。只有当finally块执行完成后,系统才会再次跳回来执行try块、catch块里的return或throw语句;如果finally块里也使用了return或throw等语句,finally块会终止方法,系统将不会跳回去执行try块、catch块里的任何代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) {
System.out.println("return value of getValue(): " + getValue()); // 2 finally的返回值
}
// 全局 i等于三
public static int getValue() {
int i = 1;
try {
i = 4/0;
}catch (Exception e){
i++;
return i + 1; // 在返回之前如果有finally 会执行完finally的语句
}finally{
i++;
return i - 1; // 最终的返回值
}
}

但是在遇到System.exit(0)语句时finally语句不会执行

1
2
3
4
5
6
7
8
9
	    try {
int i = 4 / 0;
} catch (Exception e) {
System.out.println("0");
System.exit(0);
} finally {
System.out.println("1");
}
// 只会输出 0

同步问题

同步是害怕在操作过程的时候被其他线程也进行读取操作,一旦是原子性的操作就不会发生这种情况。

++x , x++ , x = y 都不是原子操作(都需要先取值在赋值),而 x = 1 原子操作

精度丢失

小范围转化为大范围的数值型变量,jvm在进行编译的过程中将进行类型的自动提升

大范围到小范围(向下转型)会丢失精度

范围大小依次是:byte、char、short、int、long、float、double

负数取绝对值还是负数

1
2
// int 的范围为 -2^31 ~ 2^31 - 1
System.out.println(Math.abs(Integer.MIN_VALUE));//-2147483648

Double

1
2
3
System.out.println(Math.min(Double.MIN_VALUE, 0.0)); // 0.0
System.out.println(1.0 / 0.0); // Infinity
System.out.println(0.0 / 0.0); // NaN

byte

1
2
3
4
5
6
final byte a = 1 , b = 2; 
byte c = a + b; // 被final修饰的变量是常量,这里的c=a+b可以看成是c=3;
b = 127;
byte c1 = a + b // a + b 已经超出byte的范围会被认定为 int 向下转型会出现编译出错
byte a1 = 1 , b1 = 2;
byte c2 = a1 + b1; // 运算出的结果会被转换成int 出现编译错误

三元运算符

1
2
3
Object o1 = true ? 1 : 1.0;
System.out.println(o1); // 1.0
// 其实输出的还是 1 只不过是 返回的类型会自动变成范围更大的类型

BigDecimal的equals方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
BigDecimal bigDecimal1 = new BigDecimal("0.1");
BigDecimal bigDecimal2 = new BigDecimal("0.10");
// 对于0.1和0.10这两个数字,他们的值虽然一样,但是精度是不一样的,所以在使用equals比较的时候会返回false。
System.out.println(bigDecimal2.equals(bigDecimal1)); // false

BigDecimal bigDecimal3 = new BigDecimal(1);
BigDecimal bigDecimal4 = new BigDecimal(1.0);
// BigDecimal一共有以下4个构造方法
// 最简单的就是BigDecimal(long) 和BigDecimal(int),因为是整数,所以标度就是0
// BigDecimal(double),double都会转成一个近似值标度为55
// BigDecimal(String),和string的小数一致,BigDecimal("0.10000"),标度也就是5。
System.out.println(bigDecimal3.equals(bigDecimal4)); // true

// 阿里规范强制使用compareTo进行等值比较
System.out.println(bigDecimal1.compareTo(bigDecimal2)); // 0

跳出多层循环

1
2
3
4
5
6
7
8
9
10
flag:
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
System.out.println("i=" + i + ",j=" + j);
if (j == 5) {
break flag;
}

}
}

子类和父类

  1. 构造函数不能被继承,构造方法只能被显式或隐式的调用。

子类不能继承父类的构造方法原因:

  1. 构造器的目的主要是为了构造对象,很显然父类的构造器只是为了给父类造对象的,但是我们通过继承extends关键字就已经继承到了父类的属性和方法,就不需要父类对象;
  2. 不知道你会不会想着在子类造父类对象,例如 子类类型 变量名 = new 父类类型( ); 先不说这样写有没有问题,我们就看有没有意义,在编译期这是个子类的类型,我们只能调用子类里的属性和方法,如果我们调用子类独有的属性和方法时,运行期发现实际上是父类类型 没有这个调用子类独有的方法和属性那么就会报错,如果这是调用父类中的属性和方法,那可以直接new子类对象就好了 没必要new父类;

不会初始化子类的几种场景

  1. 调用的是父类static方法或者字段
  2. 调用的是父类final方法或者字段
  3. 通过数组来引用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class P {
public static int abc = 123;
public static final String field = "final";
public static String[] fields = {"final1" ,"final2" };
static{
System.out.println("P is init");
}
static void hello(){
System.out.println("hello ....");
}
final static void world(){
System.out.println("world ....");
}
}
public class S extends P {
static{
System.out.println("S is init");
}
}
public class Test {
public static void main(String[] args) {
System.out.println(S.abc); // P is init \n 123
S.hello();//hello ....
System.out.println(S.field); // final
S.world();//world ....
System.out.println(S.fields[0]);//final1
}
}

只要是被子类重写的方法,不被super调用都是调用子类方法

String 对象的引用

String类型的常量池比较特殊。它的主要使用方法有两种:

(1) 直接使用双引号声明出来的String对象会直接存储在常量池中。

1
Stirng a = "a";// 常量池

(2) 会在堆中创建对象,并在没被任何引用时回收

1
2
3
String b = new String("b"); // 堆内存
----- 使用intern 可以获取常量池数据地址 -----
String c = "a".intern();

字符串拼接:

(1) 如果时双引号拼接则相当于直接赋值,存储在常量池

1
String d = "a" + "b" // 相当于 d = "ab" 存在常量池

(2)引用对象拼接对象 在堆中创建对象

1
2
3
4
5
6
7
8
9
String a = "a";
String b = "b";

String str1 = "a" + "b";//常量池中的对象
String str2 = a + b; //在堆上创建的新的对象
String str3 = "ab";//常量池中的对象
System.out.println(str1 == str2);//false
System.out.println(str1 == str3);//true
System.out.println(str2 == str3);//false

查看常量池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
javap -c -verbose test.class
Constant pool:
#1 = Methodref #4.#13 // java/lang/Object."<init>":()V
#2 = String #14 // hello,world
#3 = Class #15 // test
#4 = Class #16 // java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = Utf8 Code
#8 = Utf8 LineNumberTable
#9 = Utf8 main
#10 = Utf8 ([Ljava/lang/String;)V
#11 = Utf8 SourceFile
#12 = Utf8 test.java
#13 = NameAndType #5:#6 // "<init>":()V
#14 = Utf8 hello,world
#15 = Utf8 test
#16 = Utf8 java/lang/Object

烦人的空指针

  • 你是不是还在这样判空
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public String getStreetName( Province province ) {
if( province != null ) {
City city = province.getCity();
if( city != null ) {
District district = city.getDistrict();
if( district != null ) {
Street street = district.getStreet();
if( street != null ) {
return street.getName();
}
}
}
}
return "未找到该道路名";
}

Optional语法专治上面的俄罗斯套娃式 if 判空,因此上面的代码可以重构如下:

1
2
3
4
5
6
7
8
9

public String getStreetName( Province province ) {
return Optional.ofNullable( province )
.map( i -> i.getCity() )
.map( i -> i.getDistrict() )
.map( i -> i.getStreet() )
.map( i -> i.getName() )
.orElse( "未找到该道路名" );
}

Thread

start()来启动线程,实现了真正意义上的启动线程,此时会出现异步执行的效果,即在线程的创建和启动中所述的随机性。 而如果使用run()来启动线程,就不是异步执行了,而是同步执行,不会达到使用线程的意义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 	  Thread thread1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println(i);
}
});

Thread thread2 = new Thread(() ->{
for (int i = 0; i < 5; i++) {
System.out.println(i);
}
});
thread1.run();
thread2.run();
// 输出 0 1 2 3 4 0 1 2 3 4
=================================================================
thread1.start();
thread2.start();
// 输出 0 1 0 1 2 3 4 2 3 4 数据量不是很大会出现很多可能

Object

Object中含有: getClass()、hashCode()、equals()、clone()、toString()、notify()、notifyAll()、wait(long)、wait(long,int)、wait()、finalize()十一个方法

  1. equals

用于比较当前对象与目标对象是否相等,默认是比较引用是否指向同一对象。为 public方法,子类可重写。

  1. clone

此方法返回当前对象的一个副本(生成新对象,但属性是原引用)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public  class demo implements Cloneable {

private String name;
private int age;
// 构造函数和get set方法

@Override
protected demo clone() throws CloneNotSupportedException {
return (demo)super.clone();
}

public static void main(String[] args) throws CloneNotSupportedException {
demo john = new demo("john", 18);
demo clone = john.clone();
System.out.println(clone == john);
System.out.println(clone.getAge() == john.getAge());
System.out.println(clone.getName() == john.getName());
}
}
// 输出 false true true

List

  1. Arrays.asList(不支持添加删除修改会改变原数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
		String[] s = {"1", "2", "3"};
List<String> list = Arrays.asList(s);

list.add("4"); // Exception in thread "main" java.lang.UnsupportedOperationException
// Arrays.asList(s)返回的对象并不是真正的ArrayList

list.set(0 , "0"); // 修改list的数据原数组也同步修改了
System.out.println(Arrays.toString(s)); // [0, 2, 3]
System.out.println(list.toString()); // [0, 2, 3]
// 源码
@SafeVarargs
@SuppressWarnings("varargs")
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}

/**
* @serial include
*/
private static class ArrayList<E> extends AbstractList<E>
implements RandomAccess, java.io.Serializable
{
// 。。。。。实现了自己的ArrayList类
}
// 增加和删除都直接抛异常了
public void add(int index, E element) {
throw new UnsupportedOperationException();
}
public E remove(int index) {
throw new UnsupportedOperationException();
}

修复办法(套娃)

1
List<String> list = new ArrayList<>(Arrays.asList(arrays));
  1. 那原本的ArrayList就没有坑了吗

JDK 另一个方法 List#subList 生成新集合也会与原始 List 互相影响。

1
2
3
4
5
6
7
8
9
10
11
12
13
		List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);

List<Integer> subList = list.subList(0, 1);
subList.set(0, 10);
System.out.println(list.toString()); // 10 2 3 原List集合也受到了影响
System.out.println(subList.toString()); // 10
===================================================================
subList.add(4);
System.out.println(list.toString()); // [1, 4, 2, 3] // 原数组也添加了新元素
System.out.println(subList.toString()); // [1, 4]
  1. unmodifiableList不可集合真的不能变吗
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
	List<String> list = new ArrayList<>(Arrays.asList("1","2","3"));
List<String> unmodifiableList = Collections.unmodifiableList(list);

// 直接修改会报错 UnsupportedOperationException
//unmodifiableList.add("4");
//unmodifiableList.set(0,"10");
//unmodifiableList.remove(1);

// 但是我们修改原数组,可以发现不可变数组也发生了变化
list.add("4");
System.out.println(unmodifiableList.toString()); // [1, 2, 3, 4]
list.set( 0, "10");
System.out.println(unmodifiableList.toString()); // [10, 2, 3, 4]
list.remove( 0);
System.out.println(unmodifiableList.toString()); // [2, 3, 4]