Java核心技术卷一基础知识-笔记

笔记

ArrayList
  1. 已经清楚或能够估计出元素容量,可以在添加元素前调用【ensureCapacity(100)】函数,或在构造函数中传入【new ArrayList<>(100)】
  2. 添加完元素后明确不再添加任何元素时,可调用【trimToSize()】,以释放多余空间
带资源try语句

普通try-catch方式:

1
2
3
4
5
6
7
8
9
10
11
//open a resource
try {
//do something
} catch(Exception e) {
// catch error
} finally {
//close the resource
}
// 缺点:
// 当try块发生非catch能处理的异常,如果此时finally块也发生异常,那么会丢失原始异常,转而抛出finally产生的异常
// 这样会有一个问题,如果需要将try块的异常向外抛出,需要对try块异常通过变量存起来,并在finally中判断是否需要向外抛出

带资源try方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
try(Recource res = ...) {
//do something
}
// 说明:
// 资源需要属于AutoCloseable的子接口的子类才能使用这种方式,其中Closeable接口为AutoCloseable的子接口
// try块正常退出或者存在一个异常时,都会自动调用res.close()方法,同时还可以指定多个资源
try(InputStream in = new FileInputStream("");
OutputStream out = new FileOutputStream("")) {
}
// 说明:
// 上面提到,原始try-catch方式当try抛出异常,finally块也抛出异常,会产生一个难题
// 带资源的try方式能很好的处理这种情况,原来的异常会重新抛出,close产生的异常会被try异常通过addSuppressed添加进去,该过程称为“抑制”
// 如果对close产生的异常感兴趣,可以调用getSuppressed获取

try(Recource res = ...) {
//do something
} catch(Exception e) {
// catch error
} finally {
// do finally block
}
// 说明:
// 带资源的try语句同样也可以添加catch子句和finally子句
// 执行顺序为 try块 -> close -> catch块(如果有异常的话) -> finally块
分析堆栈轨迹元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 可以获取所有线程的堆栈轨迹
Thread.getAllStackTraces();

// 打印堆栈轨迹:内部实际是new Exception("Stack trace").printStackTrace();
Thread.dumpStack();

// 异常捕获处理顺序:线程 -> 线程对象 -> 未捕获异常Handler
// 设置未捕获异常处理器,用于捕获线程未捕获的异常
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
}
});

// 增加一个“抑制”异常,如在带资源try中会出现
addSuppressed(Throwable t)
// 将该异常对象设置为“原因”
initCause(Throwable t)
// 例:
try{}catch(Exception e) {
Exception r = new RuntimeException();
r.initCause(e);
throw r;
}
Java日志

全局日志记录器:
Logger.getGlobal().info("msg");

自定义日志记录器:
private static final Logger myLogger = Logger.getLogger("com.mycompany.myapp");

内联

消除函数调用,采用类变量直接访问,可以减少函数栈的调用,提高效率

从【c.getName()】 变更为【c.name】的形式

即时编译器可以监控经常执行哪些代码并优化这些代码以提高速度。更为复杂的优化是消除函数调用(即“ 内联”)。即时编译器知道那些类已经加载。基于当前加载的类集,如果特定的函数不会被覆盖,就可以使用内联。必要时,还可以撤销优化。

是否将某个方法设置为内联方法是Java 虚拟机的任务。即时编译器会监视调用那些简洁经常被调用没有被重载以及可优化的方法。

在早期的Java 中, 有些程序员为了避免动态绑定带来的系统开销而使用final 关键字。如果一个方法没有被覆盖并且很短, 编译器就能够对它进行优化处理, 这个过程为称为内联( inlining )。例如,内联调用e.getName( ) 将被替换为访问e.name 域。这是一项很有意义的改进, 这是由于CPU 在处理调用方法的指令时, 使用的分支转移会扰乱预取指令的策略, 所以,这被视为不受欢迎的。然而, 如果getName 在另外一个类中被覆盖, 那么编译器就无法知道覆盖的代码将会做什么操作, 因此也就不能对它进行内联处理了。

幸运的是, 虚拟机中的即时编译器比传统编译器的处理能力强得多。这种编译器可以准确地知道类之间的继承关系, 并能够检测出类中是否真正地存在覆盖给定的方法。如果方法很简短、被频繁调用且没有真正地被覆盖, 那么即时编译器就会将这个方法进行内联处理。如果虚拟机加载了另外一个子类,而在这个子类中包含了对内联方法的覆盖, 那么将会发生什么情况呢? 优化器将取消对覆盖方法的内联。这个过程很慢, 但却很少发生。

枚举
1
2
3
4
5
6
7
8
public enum TestEnum {
Red("red"), Blue("blue"), Green("green");

private String color;
TestEnum(String color) {
this.color = color;
}
}

原理:

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
public final class TestEnum extends Enum{
public static TestEnum[] values(){
return (TestEnum[])$VALUES.clone();
}
public static TestEnum valueOf(String s){
return (TestEnum)Enum.valueOf(com/benben/TestEnum, s);
}
private TestEnum(String s, int i, String s1){
super(s, i);
mColor = s1;
}
public static final TestEnum Red;
public static final TestEnum Blue;
public static final TestEnum Green;
private String mColor;
private static final TestEnum $VALUES[];
static {
Red = new TestEnum("Red", 0, "red");
Blue = new TestEnum("Blue", 1, "blue");
Green = new TestEnum("Green", 2, "green");
$VALUES = (new TestEnum[] {
Red, Blue, Green
});
}
}

注意一:从上面可以看出,枚举会消耗更多的内存,类似于饿汉式的单例

注意二:Proguard以及Android的R8也会对简单的枚举(即不实现接口也没有额外成员的枚举)做优化

反射

对于final的基本变量类型,在编译字节码的时候会被认为不会再改变,会将值直接放入调用处,所以反射对它无效

如果是static final的基本变量类型,修改值会产生异常,必须要修改可以再反射修改它的modifiers值以达到能修改的目的,注意:目前jvm并没有对该字段有限制

反射效率低

java反射效率低的主要原因是:

  1. Method#invoke 方法会对参数做封装和解封操作
  2. 需要检查方法可见性。原因:反射时每次调用都必须检查方法的可见性(在Method.invoke里)
  3. 需要校验参数。反射时也必须检查每个实际参数与形式参数的类型匹配性(在NativeMethodAccessorImpl.invoke0 里或者生成的 Java 版 MethodAccessor.invoke 里)
  4. 反射方法难以内联。参考:https://www.iteye.com/blog/rednaxelafx-548536
  5. JIT 无法优化。原因:因为涉及到动态解析的类型,所以无法优化

提高反射性能的方式:

  1. 将反射获取到的类,构造函数,函数,对象实例缓存起来,不用每次都全新查找
  2. 可通过调用method.setAccessible(true)的方式来关闭Method.invoke可见性检查
  3. 需要更极致的提高效率可以通过字节码生成的方式来实现反射机制。参考:https://github.com/EsotericSoftware/reflectasm
Lambda高阶参考

https://baijiahao.baidu.com/s?id=1606476168883238803&wfr=spider&for=pc