Java 基础知识点

很多 Java 基础的东西都忘记了, 有必要再复习一些基本的知识点.
本文主要参考 https://github.com/Snailclimb/JavaGuide


===========================
Java 访问限定符的可见性
===========================
参考: https://o7planning.org/en/10319/access-modifiers-in-java
Java 实际上有 private/default/protected/public 四种访问限定符.
如果一个类属性或方法不加任何限定符, 就是 default 限定, default 是一个介于 private 和 protected 之间的限定.
如果接口中的方法没加任何限定符, 其实是 public 级而不是 default.

修饰符 类内能否访问 包内能否访问 包外的子类访问性 包外访问性
private Y      
default Y Y    
protected Y Y Y  
public Y Y Y Y

===========================
logger 的正确使用
===========================
使用 logger 打印出完整的 exception stacktrace, 需要使用 logger.error() 两个参数的重载, 将 exception 对象传给第二个参数.

try {
     // 
    }
} catch (Exception e) {
    logger.error(e.getMessage(), e);
    // e.printStackTrace();
}

如果日志输出有字符串拼接, 最好先做一个 precondition 检查, 而不是直接调用 logger.debug(), 这样能避免字符串对象创建.
推荐:

if (logger.isDebugEnabled()) {
    logger.debug("Some message" + ", message2");
}

不推荐:

logger.debug("Some message" + ", message2");

===========================
Optional<> 类型正确使用场景:
===========================

1. 类属性不应该使用 Optional<T> 类型, Optional<T> 不能序列化.
2. 方法形参不应该使用 Optional<T> 类型, 形参声明为 Optional<T> 其实没有人任何意义.
3. 方法返回值鼓励使用 Optional<T> 类型, 告知方法使用者, 该方法可能"no result".

  

===========================
String/StringBuilder/StringBuffer
===========================
1. String 类型是不可变对象, 所以线程安全.
2. StringBuffer 可以看作是 StringBuilder 的线程安全版, 它对于数据操作访问都加了同步锁.
3. StringBuilder 并不是线程安全. 相同情况下, 使用 StringBuilder 仅比 StringBuffer 有 10~15%的性能提升, 但却要冒线程不安全的风险.
字符串不太变动的场景下, 推荐使用 String.
单线程下需要对字符串有大量的修改, 推荐使用 StringBuilder.
多线程下需要对字符串有大量的修改, 推荐使用 StringBuffer.

===========================
子类构造子中写或不写 super(arg1...) 的区别
===========================
子类的构造子中, 如果没有明确写出 super(arg...), Java 编译器会自动调用父类中的 "无参构造子".
如果一个类没有定义任何构造子, Java 编译器自动提供一个无参构造子, 如果我们已经写了一个构造子, Java 编译器就不会自动提供那个无参构造子.


===========================
接口成员可视级别
===========================
接口访问默认的访问级别是 public
接口中的实例变量默认为 final 类型.

==========================
==比较符 与 equals()
===========================
==比较符, 对于基本数据类型, 只要值相等返回为 true, 对于引用类型, 如果引用的是同一个对象才返回 true.
equals() 成员函数, 本意是用来判断两个对象的内容是否相等. 但如果类没有重写 equals() 方法, 等价于使用 == 比较符.


===========================
hashCode() 与 equals() 方法
===========================
hashCode() 方法是用来返回对象的 hash 值, hashCode() 方法在很多地方被用到, 比如在使用 HashMap 和 HashSet 集合的过程中, 在比较两个元素的时候, 会先比较两个元素的 hash 值, 如果 hash 不相等, 则两元素肯定不相等, 如果 hash 值相等, 才调用 equals() 方法做进一步的检查.
如果我们的类没有实现 hashCode() 方法的话, 默认的 hash 值为堆上对象的一个独特值, 所以如果没有重写 hashCode() 的话, 两个对象的 hash 值是无论如何不相等的. 因此一个类如果重写 了 equals(), 则一定要重写 hashCode() 方法, 否则重写 equals()是没有意义的. 


===========================
final 关键词
===========================
1. final 修饰一个变量, 如果是基本的数据类型, 则数值在初始化之后不允许被修改. 如果是引用类型, 则在初始化之后不能再指向其他对象.
2. final 修饰一个类, 表明这个类不能再被继承.
3. final 修饰一个方法, 表明该方法不能被子类重写.


===========================
static{} 静态代码块 和 {} 构造代码块
===========================
在一个类中, 可以有多个 static {} 代码块, 这些代码块是在构造子之前被执行的, 如果有多个这样的代码块, 按照定义的顺序执行. 一般 static{} 代码块是对 static 变量进行赋值.

类中还可以有一种特殊的代码块, 使用 {} 包着, 被叫做"构造代码块"或"非静态代码块". 如果构造代码块有多个, 也是按照定义的顺序指定的.

代码的执行顺序:
static 初始化语句 --> 静态代码块 --> 构造代码块 --> 构造子
static 初始化语句和静态代码块仅仅在类加载时被执行一次. 构造代码块和构造子是在对象实例化的时候被调用, 一个类可能有多个构造子, 但不管调用了哪个构造子, 在这之前, 构造代码块总会被自动执行.


===========================
Map
===========================
1. Hashtable (线程安全). 内部方法都经过了 synchronized 过, 是线程安全的, 但效率较差, 基本已经被淘汰, 如果需要考虑线程安全问题, 推荐使用 ConcurrentHashMap .
2. HashMap (线程不安全) 因为没有锁机制, 所以不是线程安全. HashMap 允许 key 为 null.
3. LinkedHashMap (线程不安全) 继承自 HashMap, 在 HashMap 基础上, 增加了一条双向链表, 使得可以保持键值对的插入顺序.
3. ConcurrentHashMap (线程安全) 对整个桶数组进行了分段 segement, 然后在每个分段上使用了 lock 锁, 比 Hashtable 的 synchronized 粒度更细, 并发性能更好. ConcurrentHashMap 不允许 key 为 null.
4. TreeMap 红黑树 (自平衡的排序二叉树)
使用场景: 需要线程安全, 选用 ConcurrentHashMap; 如果需要保持插入的顺序, 选用 LinkedHashMap; 如果需要排序, 选用 TreeMap; 其他场景可选用 HashMap.


===========================
Set 类
===========================
HashSet (无序, 唯一) 哈希表. 线程不安全.
LinkedHashSet: 链表和哈希表组成, 由链表保证元素的排序, 由哈希表保证元素的唯一性. 线程不安全
TreeSet: (有序, 唯一) 红黑树 (自平衡的排序二叉树). 线程不安全


===========================
List 类
===========================
ArrayList, (底层是 Object 数组) 查询快,增删慢, 线程不安全, 效率高.
Vector, 查询快, 增删慢, 线程安全. 单线程不推荐,  多线程场景推荐使用, 或者使用 ArrayLIst/LinkedList 自己保证同步
LinkedList,(底层是链表) 查询慢, 增删块, 线程不安全, 效率高.
JDK 中没有专门的排好序LIst类型, 可以使用  Collections.sort() 排序, 或者使用 TreeMap 来模拟一个有序List. 

===========================
ArrayList<T> 转 T[] 数组
===========================
List<String> list= new ArrayList<>();
如何将该 list 转成 String[]?
错误的写法是:
String [] strings = (String[])list.toArray();
原因是: list.toArray() is creating an Object[] rather than a String[], 得到结果后不能直接将Object[]强转为 String[], 因为String[]和Object[]不是基类子类关系.

正确的写法是利用其泛型重载:
String [] strings = list.toArray(new String[0]);
或:
String[] strings = list.stream().toArray(String[]::new);

===========================
巧用 Collections package: 避免在 API 层返回 null 对象
===========================
当API返回值尽量避免使用 null, 下面是推荐写法:
(1)String 类型: 推荐返回 ""
(2)List/Set/Map 类型: 推荐使用 Collections.emptyList(), emptySet(), emptyMap()
(3)Array 类型: 推荐返回一个零长度的数组
(4)其他类型: 推荐使用 Optional 包装

===========================
巧用 Collections package: 返回自读集合类型
===========================
Collections.unmodifiableCollection(c)
Collections.unmodifiableList(list)
Collections.unmodifiableMap(m)
Collections.unmodifiableSet(s)


===========================
线程安全的集合
===========================
如果要考虑线程安全的集合类, 推荐使 java.util.concurency 包下定义的类, 而不是老的 Vector 和 Hashtable 类, 也尽量不要使用 synchronized 来封装.
List: CopyOnWriteArrayList
Map: ConcurrentHashMap
Set: ConcurrentSkipListSet 和 CopyOnWriteArraySet


===========================
Atomic 类型
===========================
Java 中的原子类型, 主要用于多线程场景, 一个操作一旦开始,就不会被其他线程干扰, 保证数据操作是以原子方式进行的. 原子类位于 java.util.concurency.atomic 包, 最常用的有:
AtomicInteger/AtomicIntegerArray
AtomicLong/AtomicLongArray
AtomicReference/AtomicReferenceArray

Atomic 类型比 synchronized 同步访问的开销要小, 使用的是 CAS(compare and set 比较并替换) + volatile + native 的实现机制.


===========================
快速构建数组和List
===========================
构建一个数组
Long[] longs=new Long[] {1L,2L};
String[] strings= new String[] {"a","b","c"};
String[] array = { "1", "kk", "a", "b", "c", "a" };

快速构建一个 ArrayList
List<String> lst=Arrays.asList("a","b","c");


===========================
生成Stream, 将Stream转成List
===========================
构建Stream的方法:
1. 使用Collection 接口的 stream() 方法, 比如 list.stream()
2. 通过Stream接口的静态工厂方法, 比如 Stream<Integer> integerStream = Stream.of(1, 2, 3, 5);


将 Stream 转换为 List
targetLongList = sourceLongList.stream()
.filter(l -> l > 100)
.collect(Collectors.toList());

Stream 使用文档:
http://ifeve.com/stream/
http://www.runoob.com/java/java8-streams.html
https://www.liaoxuefeng.com/article/001411309538536a1455df20d284b81a7bfa2f91db0f223000
https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/


===========================
Java 8中处理日期和时间
===========================
参考:
https://www.liaoxuefeng.com/article/00141939241051502ada88137694b62bfe844cd79e12c32000
http://www.importnew.com/14140.html

Java 8 以后终于可以不使用下面这些类型了, 包括:
java.util.Date 和 java.util.Calendar 和 java.util.TimeZone 和 java.text.SimpleDateFormat.
它们不仅线程不安全, 关键是难用.

Java 8 推出的一系列日期时间类, 都是在 java.time 包下面.
java.time.format.DateTimeFormatter 代替 SimpleDateFormat
java.time.LocalDate, 仅包含日期
java.time.LocalTime, 仅包含时间
java.time.LocalDateTime, 包含日期和时间
java.time.ZoneId, 用来指定时区
java.time.Period, 是一个时间区间, 有开始有结束.
java.time.Duration, 是一个时间长度, 比如几天
java.time.temporal.TemporalAdjusters, LocalDate等类已经包含了日期加加减减等功能, 如果需要更高级的日期推导, 可以调用 LocalDate 中一些带有 TemporalAdjusters 参数的方法.

相关几个类的细微差别:
   Instant 类: UTC 时间线上的瞬时值, 具体取值是: 从1970 epoch起到现在的纳秒数.
   ZonedDateTime 类: 该是有时区概念的时间, ZonedDateTime = ( Instant + ZoneId )
   LocalDateTime,LocalDate,LocalTime 类: 这三个类没有时区概念, 比如全世界的圣诞节都是从"12月25日零点"开始算起.

Java 8 中, 和 JDBC 类型搭配关系是:
JDBC  < -- > Java
date <--> LocalDate
time <--> LocalTime
timestamp <--> LocalDateTime

===========================
调试过程中判断获取对象的类名
===========================
在调试过程中, 经常要看 someObject 的类名, 方法是在 debugger expressions 窗口, 增加一个表达式: someObject.getClass().toString()

原文地址:https://www.cnblogs.com/harrychinese/p/java_basics.html