很多 Java 基础的东西都忘记了, 有必要再复习一些基本的知识点.
本文主要参考 ===========================Java 访问限定符的可见性===========================参考: https://o7planning.org/en/10319/access-modifiers-in-javaJava 实际上有 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, 查询快, 增删慢, 线程安全, 效率低. 不推荐使用. 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: ConcurrentHashMapSet: ConcurrentSkipListSet 和 CopyOnWriteArraySet===========================Atomic 类型===========================Java 中的原子类型, 主要用于多线程场景, 一个操作一旦开始,就不会被其他线程干扰, 保证数据操作是以原子方式进行的. 原子类位于 java.util.concurency.atomic 包, 最常用的有:AtomicInteger/AtomicIntegerArray AtomicLong/AtomicLongArrayAtomicReference/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 转换为 ListtargetLongList = 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/001411309538536a1455df20d284b81a7bfa2f91db0f223000https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/ ===========================Java 8中处理日期和时间===========================参考:https://www.liaoxuefeng.com/article/00141939241051502ada88137694b62bfe844cd79e12c32000 http://www.importnew.com/14140.htmlJava 8 以后终于可以不使用下面这些类型了, 包括:
java.util.Date 和 java.util.Calendar 和 java.util.TimeZone 和 java.text.SimpleDateFormat. 它们不仅线程不安全, 关键是难用.Java 8 推出的一系列日期时间类, 都是在 java.time 包下面.
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 < -- > Javadate <--> LocalDate time <--> LocalTimetimestamp <--> LocalDateTime
===========================
调试过程中判断获取对象的类名===========================在调试过程中, 经常要看 someObject 的类名, 方法是在 debugger expressions 窗口, 增加一个表达式: someObject.getClass().toString()