├── README.md └── java-teach.md /README.md: -------------------------------------------------------------------------------- 1 | # Java-Train / 2019 2 | 2019 级 Android实验室(人工智能+移动互联) Java培训 -------------------------------------------------------------------------------- /java-teach.md: -------------------------------------------------------------------------------- 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 | # Java 基础 (1) 30 | 31 | ## main 32 | 33 | ## 基本类型 34 | 35 | ## 运算符 36 | 37 | | 运算符 | 说明 | 范例 | 返回值类型 | 38 | | ------ | ---- | ---- | ---- | 39 | | instancef | 变量 a 是否是 A 类的实例,或者变量 a 是否实现了接口 I | a instanceof A | boolean | 40 | | % | 取余 | 3 % 2 | int, long | 41 | | ^ | (位运算) 异或 | 1 ^ 2 | int, long | 42 | | ~ | (位运算) 取反 | ~1 | int, long | 43 | | ! | (逻辑) 取反 | !true | boolean | 44 | | == | (逻辑) **值**相等 (注意与 `equals()` 的区别) | 1 != 2 | boolean | 45 | | != | (逻辑) **值**不等 (注意与 `!equals()` 的区别) | 1 != 2 | boolean | 46 | 47 | ### 表示数字字面量 48 | 49 | 和 C 语言相同 50 | 51 | #### 1. 很长的数字 52 | 53 | 为了方便阅读,你可以在数字直接加下划线方便阅读 54 | ```java 55 | public static final int BIG_NUMBER = 1_000_000_000; 56 | ``` 57 | 58 | #### 2. 表示 long 类型 59 | 60 | 默认情况下你直接输入的数字总是表示 `int` 类型,要表示 `long`,在数字字面量后面加 **L** 61 | 62 | ```java 63 | public static final long BIG_NUMBER = 1_000_000_000_000_000_000L; 64 | ``` 65 | 66 | #### 3. 表示各种进制数 67 | 68 | **八进制**(**注意!**) 69 | 70 | 这里主要要强调和十进制的区别,Java 中,0123 **≠** 123 71 | 72 | 在数字字面量前加 `0` 表示八进制数,而不是十进制数,如果你忘了这点,很可能会搞/出事情 73 | 74 | **二进制**(在书写 flag 和位运算时特别有用) 75 | ```java 76 | public static final int NUMBER = 0b01110110; 77 | ``` 78 | 79 | **十六进制** 80 | ```java 81 | public static final int NUMBER = 0xaff211; 82 | ``` 83 | 84 | 85 | 86 | ## 循环 87 | 88 | 若要遍历某个数组或 List,请使用 for-each 语句而不是 for 语句 89 | 90 | ## 条件、作用域 91 | 92 | ## 数组 93 | 94 | ## 字符串 95 | 96 | # 类 Class 97 | 98 | ## 定义一个类 99 | 100 | **请务必**永远只在一个文件定义一个类。(无论是不是 public) 101 | 102 | ## 构造方法、初始化器 103 | 104 | ## 实例方法 105 | 106 | ## 静态方法和静态字段 107 | 108 | 如果一个类的某个方法并不需要对象的应用,请考虑将其设为静态 109 | 110 | ## 静态初始化器 111 | 112 | ## 类的组合 113 | 114 | ## 类的继承 115 | 116 | ### 继承注意事项 117 | 118 | #### 构造方法绝不能直接或间接调用可重写的方法 119 | 120 | 父类构造方法在子类构造方法之前运行。若这样做,会使得在子类构造方法运行之前,子类中的重写方法被调用。 121 | 122 | 如果重写方法依赖于子类构造方法执行的任何初始化,则此方法将不会按预期运行。例如: 123 | 124 | ```java 125 | import java.time.Instant; 126 | 127 | public class Nineteen extends Super { 128 | // Blank final, set by constructor 129 | private final Instant instant; 130 | 131 | Nineteen() { 132 | instant = Instant.now(); 133 | } 134 | 135 | // Overriding method invoked by superclass constructor 136 | @Override 137 | public void overrideMe() { 138 | System.err.println(instant); 139 | } 140 | 141 | public static void main(String[] args) { 142 | Nineteen sub = new Nineteen(); 143 | sub.overrideMe(); 144 | } 145 | } 146 | 147 | class Super { 148 | // Broken - constructor invokes an overridable method 149 | Super() { 150 | this.overrideMe(); 151 | } 152 | 153 | public void overrideMe() { 154 | System.out.println("Parent"); 155 | } 156 | } 157 | ``` 158 | 159 | 你期望的输出: 160 | 161 | ``` 162 | 2019-08-03T09:02:08.232943Z 163 | 2019-08-03T09:02:08.232943Z 164 | ``` 165 | 166 | 实际结果: 167 | 168 | ``` 169 | null 170 | 2019-08-03T09:02:08.232943Z 171 | ``` 172 | 173 | > 解决这个问题的最好办法是,在没有想要安全地子类化的设计和文档说明的类中禁止子类化。 有两种方法禁止子类化。 两者中较容易的是声明类为 final。 另一种方法是使所有的构造方法都是私有的或包级私有的,并且添加公共静态工厂来代替构造方法。 这个方案在内部提供了使用子类的灵活性,在条目 17 中讨论过。两种方法都是可以接受的。 174 | 175 | ## 命名守则 176 | 177 | ## 类和类型、类型转换 178 | 179 | # 方法 Method 180 | 181 | ## 值与引用 182 | 183 | ## 包装类型、装箱和拆箱 184 | 185 | ## 方法重载 Overload 186 | 187 | ## 方法重写 Override 188 | 189 | ## 参数和返回值的设计思想 190 | 191 | ### 推广你的参数! 192 | 193 | 接受接口类型而不是实现类型 194 | 195 | # 保护级别 196 | 197 | # 包 package 198 | 199 | # 异常 Exception 200 | 201 | 指出,必检异常,除了 `EOFException` 此类以及利用异常编程的业务逻辑,其余都是垃圾设计 202 | 203 | ## 抛出和捕获异常 204 | 205 | ## 常用异常 206 | 207 | | 异常 | 使用场合 | 208 | | ----- | ------ | 209 | | IllegalArgumentException | 非 null 的参数值不正确 | 210 | | IllegalStateException | 不适合方法调用的对象状态 | 211 | | NullPointerException | 在禁止使用 null 的情况下参数值为 null | 212 | | IndexOutOfBoundsExecption | 下标参数值越界 | 213 | | ConcurrentModificationException | 在禁止并发修改的情况下,检测到对象的并发修改 | 214 | | UnsupportedOperationException | 对象不支持用户请求的方法 | 215 | 216 | 不要直接抛出 Exception、RuntimeException、Throwable 或者 Error。因为它们是一个方法可能抛出的其他异常的超类。 217 | 218 | ## NullPointerException 问题和防止 219 | 220 | # Java 基础 (2) 221 | 222 | ## List 223 | 224 | ## Map 225 | 226 | ## Set 227 | 228 | ## List/Map/Set 的实现 229 | 230 | ~ 数据结构 231 | 232 | ### Array* 233 | 234 | ### Linked* 235 | 236 | ### Hash* 237 | 238 | ### Sorted* 239 | 240 | ### Tree* 241 | 242 | ### Enum* 243 | 244 | #### EnumSet 245 | 246 | 当你需要描述 `flags` 或者想要使用枚举数组时、或者想要定义几个int常量描述什么东西时,优先考虑使用 `EnumSet` 代替。 247 | 248 | ```java 249 | Set colors = EnumSet.of(MazhaColor.GREEN, MazhaColor.BLUE) 250 | ``` 251 | 252 | #### EnumMap 253 | 254 | 当你需要一个用枚举作为键的 Map 时,用它。这个时候 `EnumMap` 性能最佳。 255 | 256 | ```java 257 | Map> mazhasByType = 258 | new EnumMap<>(MazhaType.class); 259 | ``` 260 | 261 | # 接口、抽象类和 Lambda 初步 262 | 263 | ## 定义一个接口 264 | 265 | 强调接口的无状态性 266 | 267 | 强调接口一开始就要定义好,发布后不要随便修改,即使是默认方法 268 | 269 | ## 两种方法实现一个接口 270 | 271 | ## 匿名类 272 | 273 | 请优先使用 Lambda 或方法引用 而不是匿名类。 274 | 275 | ## Lambda 276 | 277 | ## 默认方法与 Trait 思想 278 | 279 | ## 不要在后期随意添加默认方法 280 | 281 | 名称冲突 282 | 283 | 在默认方法的情况下,接口的现有实现类可以在没有错误或警告的情况下编译,但在运行时会失败。 284 | 285 | ## 函数式编程简介 286 | 287 | # 泛型 Generics 288 | *ClassName* 289 | 290 | 假设有一个类 Fruit 表示水果,另外两个类 Apple、Banana 表示 苹果、香蕉。显然后两者是前者的子类。代码定义如下 291 | 292 | ```java 293 | class Fruit {} 294 | class Apple extends Fruit {} 295 | class Banana extends Fruit {} 296 | 297 | class GreenApple extends Apple {} 298 | class RedApple extends Apple {} 299 | ``` 300 | 301 | ## 泛型概述 302 | 303 | Java 泛型擦除问题(JVM相对于CLI)、钻石操作符 304 | 305 | ## 从泛型角度理解数组 306 | 307 | ```csharp 308 | int[] == Array //注:Java 中基本类型不能用作类型参数 309 | Integer[] == Array //若要装基本类型,必须像这样装箱 310 | String[] == Array 311 | ``` 312 | 313 | ## 协变和逆变 314 | 315 | ``` 316 | 逆变与协变用来描述类型转换(type transformation)后的继承关系,其定义:如果A、B表示类型,f(⋅)表示类型转换,≤表示继承关系(比如,A≤B表示A是由B派生出来的子类) 317 | f(⋅)是逆变(contravariant)的,当A≤B时有f(B)≤f(A)成立; 318 | f(⋅)是协变(covariant)的,当A≤B时有f(A)≤f(B)成立; 319 | f(⋅)是不变(invariant)的,当A≤B时上述两个式子均不成立,即f(A)与f(B)相互之间没有继承关系。 320 | ``` 321 | 322 | 数组是协变的(Java的垃圾设计之一)。泛型是不变的。 323 | 324 | ## 泛型通配符 ? 325 | 326 | `? extends Fruit` 表示一种类型,这种类型 Fruit 的子类型或它本身 327 | `? super GreenApple` 表示一种类型,这种类型 GreenApple 的父类型或它本身 328 | 329 | **永远记住**:泛型通配符表达的意思是我们有一种**确定的**类型,但是现在我们不知道这个类型具体到底是什么。再次强调,这个类型是确!定!的! 330 | 331 | ### List\ 332 | 333 | `List` 表示一个 `List`,里面装有 `Fruit` 和他的所有子类。可以装下 Fruit, Apple, Banana, GreenApple, RedApple .... 334 | 335 | `List` 表示一个 `List`,里面装有 `Apple` 和他的所有子类。可以装下 Apple, GreenApple, RedApple,但不能装下 Banana, Fruit。因为他们不是 Apple 的子类 336 | 337 | ### 辨别 338 | 339 | * List\ 可以装任何东西(任何类都是Object的子类) 340 | * List\ 这里装有一种**暂不确定**的东西和他的子东西。但是由于我们不知道这个东西是什么,万一装错东西就会爆炸,因此不能再装任何东西了。 341 | 342 | ### List\ 343 | `List` 表示一个 `List`,里面装有某种类,这个类可能是 `Fruit`,也可能是 `Apple`, `Banana`...听起来好像和上一个没啥区别。**关键是我们不知道他到底是想限定哪个。** 344 | 345 | 假如 `? extends Fruit` 表示 `Apple`,那么这里可以装下 `Apple` 和他的所有子类。装 `Banana` 和 `Fruit` 都会爆炸。 346 | 347 | 假如 `? extends Fruit` 表示 `GreenApple`,那么这里可以装下 `GreenApple` 和他的所有子类。装 `Apple` 也会爆炸。 348 | 349 | 因为这种**不确定性**,导致我们压根就不清楚这个容器的限定范围是什么,也就不能装任何东西了。不过读东西是可以的,里面的东西一定的 Fruit 的子类。get() 方法返回 Fruit 类型的对象。 350 | 351 | 划重点:`List` 只可读不可写 352 | 353 | ### List\ 354 | 355 | 356 | 357 | # 注解 Annotation 358 | 359 | ## 注解概述 360 | 361 | ## 常用注解 362 | 363 | # IO, NIO/NIO.2 初步 364 | 365 | ## 流(Stream)的概念 366 | 367 | ## 常用流 368 | 369 | ### 控制台标准输入输出流 370 | 371 | ### 文件相关流 372 | 373 | ### 网络相关流 374 | 375 | ### 字符流 376 | 377 | ## RandomAccessFile 378 | 379 | ## NIO 的概念 380 | 381 | ## 常用通道 382 | 383 | ### FileChannel 384 | 385 | ### SocketChannel 386 | 387 | ## NIO.2 的概念 388 | 389 | # 并发 Concurrent 390 | 391 | ## 线程 Thread 392 | 393 | ## 线程池 ThreadPool 394 | 395 | 我们通常不推荐为了一件任务就创建一个线程然后用完就销毁它。创建线程的开销是*很大*的 396 | 397 | ### Executor / Task 398 | 399 | 基本使用方法: 400 | 401 | ```java 402 | ExecutorService exec = Executors.newSingleThreadExecutor(); //开一个任务处理器 403 | exec.execute(runnable); //提交一个任务,需要实现 Runnable 404 | exec.shutdown(); //终止任务 405 | ``` 406 | 407 | 工作队列 408 | 409 | ## 并发相关的基本概念 410 | 411 | ### 原子性 412 | 413 | 即一个操作或者多个操作,要么**全部执行**并且执行的过程不会被**任何**因素*打断*或*干扰*,要么就**都不执行**。 414 | 415 | > 原子性就像数据库里面的事务一样,他们是一个集体,同生共死。(没学过数据库事务可以忽略这句话...) 416 | 417 | ### 可见性 418 | 419 | 可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。 420 | 421 | ### 有序性 422 | 423 | 即程序执行的顺序按照代码的先后顺序执行,而不是乱序的。 424 | 425 | ```java 426 | int i = 0; 427 | boolean flag = false; 428 | i = 1; //语句1 429 | flag = true; //语句2 430 | ``` 431 | 432 | 比如上面的代码中,语句1和语句2谁先执行对最终的程序结果并没有影响,那么就有可能在执行过程中,语句2*先*执行而语句1*后*执行。 433 | 434 | ## 同步简介 435 | 436 | ### 为何需要同步? 437 | 438 | 如果没有同步,同一个对象的一个线程的变化就可能**不能**被其他线程看到。 439 | 440 | 同步不仅可以阻止一个线程看到对象处于不一致的状态之中,它还可以保证进入同步方法或者同步代码块的每个线程,都能看到由同一个锁保护的之前所有的修改效果。 441 | 442 | ### 范例 443 | 444 | lowb开发者可能觉得一般只需要写同步就够了,但很多情况下读也是需要同步的。下面是一种情况: 445 | 446 | ```java 447 | public class StopThread { 448 | private static Boolean stopRequested; 449 | 450 | public static void main(String[] args) 451 | throws InterruptedException { 452 | Thread backgroundThread = new Thread(() -> { 453 | int i = 0; 454 | //注意下面 455 | while (!stopRequested) 456 | i++; 457 | }); 458 | backgroundThread.start(); 459 | TimeUnit.SECONDS.sleep(1); 460 | stopRequested = true; 461 | } 462 | } 463 | ``` 464 | 465 | 事实上,你永远也停不下 `backgroundThread` ,因为我让你注意的那句话被优化成了这样: 466 | 467 | ```java 468 | if (!stopRequested) 469 | while (true) 470 | i++; 471 | ``` 472 | 473 | 解决此问题的办法是对 `stopRequested` 使用同步的 getter/setter 方法,或者为其添加 `volatile` 关键字 474 | 475 | ### 怎么解决同步问题? 476 | 477 | 通常使用以下方法(选一种): 478 | 479 | 1. 如果数据不需要修改,直接让其成为只读的(废话) 480 | 2. 将可变数据限制在单个线程中 481 | 3. 当多个线程共享可变数据的时候,对读写同步 482 | 483 | 注意:除非读和写操作都被同步,否则无法保证同步能起作用。 484 | 485 | 下面所讲的,就是第三条【同步】的常用方法。 486 | 487 | ## 锁 488 | 489 | ## synchronized 490 | 491 | ## 阻塞队列 BlockingQueue 492 | 493 | ## volatile 关键字 494 | 495 | ## wait() 和 notify() 496 | 497 | 现在是 9102 年了,这节的内容已经不怎么重要了。*了解即可* 498 | 499 | # 杂项 500 | 501 | ## 用位操作实现 Flag 502 | 503 | 尽管现在通常不推荐用位操作来实现 Flag (取而代之,使用 `EnumSet` ),但许多老项目,特别是 Android,仍然大量用到了位运算表示 Flag 504 | 505 | # 常用接口 506 | 507 | ## 可序列化 Serializable 508 | 509 | 描述这个类可以被序列化成字符串 510 | 511 | ## 资源可关闭 Closeable 512 | 513 | AutoClosable 514 | 515 | ## 可克隆 Cloneable 516 | 517 | 描述这个类可以被克隆。使我们可以调用类的 `clone()` 方法来克隆某个对象。 518 | 519 | 请谨慎实现此接口,确保你已经非常清楚本节所述的内容。 520 | 521 | 这个接口十分特殊,它并没有定义任何方法。我们要用的 `clone()` 方法是由 Object 类实现的。但 Object 类要求我们必须实现这个接口才能调用此方法,否则会导致 `CloneNotSupportedException`. 这是接口的一种**极端非典型**的用法,不值得学习。(接口应该描述某个类能够做什么,然而这个接口改变了超类的行为) 522 | 523 | 尽管 Object 类实现了 clone() 方法,但它是 protected 的,若要让其可被访问,我们必须再子类覆盖它并将其设为 public。 524 | 525 | **但必须要知道:** 526 | 527 | * Object 的 `clone()` 方法只提供对对象中的字段的浅拷贝。如果某个字段是引用类型,则只会产生一个新引用,原有的对象**不会**被克隆。 528 | * 如果想要克隆某个引用类型字段,必须手工克隆这个字段 529 | * `clone()` 方法不调用类的构造器 530 | * 如果你的这个类不允许继承,则应该处理掉 `CloneNotSupportedException`。若允许继承,则不应该处理此异常,或者压根别实现 Cloneable 接口 531 | 532 | ## 可比较 Comparable\ 533 | 534 | 描述这个类的对象与另一个对象存在大小关系,并且可以相互比较大小。如果你的类携带的信息可以用作比较(例如名字的字母排序),则应该考虑实现一下这个接口了。 535 | 536 | 实现这个接口需要覆盖 `int compareTo(T t)` 方法,并返回一个数字代表运算结果 537 | 538 | a.compareTo(b) 539 | 540 | | 返回的数字 | 意义 | 541 | | ---------- | ---- | 542 | | -1 或其他负整数 | a < b | 543 | | 0 | a = b | 544 | | 1 或其他正整数 | a > b | 545 | 546 | **注意**:通常建议如果 compareTo() 返回 0,则 equals() 也应该返回 true. 除非确实存在某些原因不能这样做。 547 | 548 | 当 compareTo() 返回 0,而 equals() 返回 false 时,有序集合的行为会与其他集合的行为变得不同。当把这两个元素往有序集合和其他集合里面放时,有序集合认为两个元素是相同的而只装其中一个,而其他集合会认为这两个元素不相同而全部装进去。 549 | 550 | ## 内置常用函数式接口 551 | 552 | 553 | 554 | # 反射初步 555 | 556 | ## Class, getClass() 557 | 558 | ## 用反射操控对象 559 | 560 | ## ClassLoader 初步 561 | 562 | # 其他常用类库 563 | 564 | ## 集合操作 Collections 565 | 566 | ## 数组操作 Arrays 567 | 568 | ## StringBuilder/StringBuffer 569 | 570 | ## 时间和日期 571 | 572 | ## 大数 573 | 574 | ## 编码 575 | 576 | ## 常用数据编码方式 577 | 578 | ### JSON 579 | 580 | ### Base64 581 | 582 | # 常用设计方法和思想 583 | 584 | ## 封装 585 | 586 | > 将设计良好的组件与设计不佳的组件区分开来的最重要的因素是,隐藏内部数据和其他实现细节的程度。一个设计良好的组件隐藏了它的所有实现细节,干净地将它的 API 与它的实现分离开来。然后,组件只通过它们的 API 进行通信,并且对彼此的内部工作一无所知。这一概念,被称为信息隐藏或封装,是软件设计的基本原则 587 | 588 | ### 最小化访问权限 589 | 590 | 访问权限:public > protected > 默认(package-private) > private 591 | 592 | 一句话,访问权限越低越好。 593 | 594 | **目的**:提高协作效率,让别人一眼明白哪些是你提供的 API 595 | 596 | ### 最小化可变性 597 | 598 | ## get/set 599 | 600 | 通常不建议在 Java 直接访问属性。因为在 Java 中,我们无法控制这个属性被读写时的行为。如果后期需要进行某些变更,这会很棘手。 601 | 602 | 你应该设计 getter/setter 来访问属性,并将属性的可访问性修改为 private. 603 | 604 | > 这样做的目的是提高项目的可维护性。像 PHP 这些可以随时修改被属性读写时的行为的语言,可以不设计 getter/setter 605 | 606 | 当然,如果这个类是你的私有 API,你从不保证这些 API 不会发生变更,那怎么做随你。 607 | 608 | ## 科学地覆盖 equals() hashCode() toString() 609 | 610 | ### toString() 611 | 612 | 通常我们建议有空就覆盖一下 `toString()`,这不仅方便打印,更方便调试。 613 | 614 | 覆盖 `toString()` 没有什么规定,按你喜欢的格式输出对象的重要信息即可(例如对于马扎,可以输出马扎的名字、大小、重量) 615 | 616 | 但是要注意一旦格式确定后就不要再改变了。未来可能有人会使用此格式来解析数据。 617 | 618 | ### hashCode() 619 | 620 | 我们通常对**所有**能代表对象**特征**的类成员使用异或运算符 **^** 来计算 hashcode 621 | 622 | 例如:代表马扎的 颜色color, 重量weight, 大小size 623 | 624 | 则应这样覆盖马扎类的 `hashCode()` 方法 625 | 626 | ```java 627 | @Override 628 | public int hashCode() { 629 | return color ^ weight ^ size; 630 | } 631 | ``` 632 | 633 | **注意** 634 | 635 | * 如果两个对象 `equals()` 的结果为 true(即逻辑上是相等的),则它们的 hashCode 必须相等 636 | * 如果计算 hashCode 的开销很大,则可以设置一个变量缓存计算结果 637 | 638 | ### equals() 639 | 640 | `equals()` 方法代表两个对象具有逻辑上的“相等”关系,例如 `"aBc" equals "aBc"`。若你的类的对象需要和另一个对象进行逻辑上的比较(例如比较两个马扎是否一样),则应该覆盖 `equals()` 和 `hashCode()` 方法 641 | 642 | **注意** 643 | 644 | * 一定要带上 @Override 注解,以检查覆盖是否正确 645 | * 覆盖 `equals()` 时,请确保你的实现遵循离散数学中的*等价*原则(自反、传递、对称) 646 | * 若你覆盖了 `equals()` 一定要同时覆盖 `hashCode()`,否则这个类将无法用于基于散列的容器(例如 HashMap)。 647 | * 记得检查传入的参数是否为 `null` 648 | 649 | ## 单例模式 650 | 651 | 单例模式是用于那些只需要被实例化一次的类。后续获得的对象都是相同的实例。 652 | 653 | **用途**:多次实例化这个类是没有意义的,甚至或引发错误。 654 | 655 | **通常的实现方法** 656 | 657 | ### 设计 private static final INSTANCE 字段 658 | 659 | ```java 660 | public class Signleton { 661 | private static final Signleton INSTANCE = new Signleton(); 662 | 663 | // 注意:一定要提供 private 的构造器,防止他人new这个类 664 | private Signleton() { 665 | //code 666 | } 667 | 668 | public static Signleton getInstance() { 669 | return INSTANCE; 670 | } 671 | 672 | //code 673 | } 674 | ``` 675 | 676 | ### 使用枚举 (好!) 677 | 678 | ```java 679 | public enum Signleton { 680 | INSTANCE; 681 | 682 | //code 683 | } 684 | ``` 685 | 686 | ## 通常不要使用 finalize() 方法 687 | 688 | ### 如果你是为了把他当成析构器用,那么绝对不要用! 689 | 690 | 这玩意可不是 C++ 中的析构器。`finalize()` 性能**很差**而**不可靠**。 691 | 692 | 如果你想使用 `finalize()` 方法,建议你去实现 `Closeable` 接口、定义一个 `close()` 方法。 693 | 694 | 如果你觉得手工调用 `close()` 很麻烦,你还可以实现 `AutoCloseable` 接口。通过结合 try 语句,可以实现自动关闭。 695 | 696 | > Java 9 中新增了 Cleaner 机制代替 Finalizer 机制,但这个机制仍然是不可靠的。不建议使用。 697 | 698 | ### finalize() 方法的正确用途 699 | 700 | 搞流氓的事情。典型的是防止这个对象被回收。 701 | 702 | ## 惰性加载 703 | 704 | **注意**:除非某个字段初始化的开销比较大,否则不要使用延迟初始化 705 | 706 | ### 并发情况下的惰性加载和惰性单例模式 707 | 708 | 双重检查 709 | 710 | ## 事件、回调和观察者 711 | 712 | ## 面向切面编程 AOP 713 | 714 | ## 控制反转 IoC 715 | 716 | ### 依赖查找 717 | 718 | ### 依赖注入 719 | 720 | # 技巧 721 | 722 | ## 链式操作 723 | 724 | ## 使用静态工厂方法 725 | 726 | **优点** 727 | 728 | * 不必了解每个实现,直接查阅工厂类即可知道 729 | * 可以自行管理对象(不会每次都创建被创建对象) 730 | 731 | ## 使用 Builder 构建类 732 | 733 | **原因**:Java 的函数参数没有名字,只能按照参数顺序和个数调用函数或初始化类。对于参数很多的类或函数,这会让人烦躁。Android 大量使用了这种设计。 734 | 735 | **优点**: 736 | 737 | * 模拟了 Python、Kotlin 等语言的有名参数,易于阅读和使用 738 | * 可以实现可变参数的效果 739 | * (比较JavaBean) 只有最终的 `build()` 方法才会真正生成对象。此时可以进行安全检查,检查各项参数是否合法。 740 | 741 | **建议**:永远不要使用 JavaBean 风格的方法去构建一个类,通常这不安全也不优雅。(无法确定对象的参数是否正确填写完毕、难以调试) 742 | 743 | ## 使用枚举代替数字 744 | 745 | 枚举的类型安全的 746 | 747 | ## 绝对不要出现各种魔法数字 (magic number) 748 | 749 | 不然您的项目就很小圆呢 750 | 751 | ## 防止内存泄漏 752 | 753 | 只要存在某个变量持有某个对象的强引用,这个对象就永远不会被回收,因此有些情况会出现内存泄漏问题。 754 | 755 | **以下情况**应该小心内存泄漏: 756 | 757 | * 你编写了一个类,手动管理内存(管理对象) 758 | * 缓存。缓存请用弱引用 759 | * 回调 760 | 761 | 但你**不**应该总是像写 C 语言那样总是在使用完毕对象后手工销毁,这样会让你的代码变得十分猥琐。 762 | 763 | ## Gradle 管理你的项目依赖 764 | 765 | ## JNI / native 关键字 766 | 767 | 本节仅作了解即可。或者可以*直接跳过*。 768 | 769 | \ **警告:** JNI 十分危险,除非你确实有过人的 C/C++ 能力,否则不要因为不熟悉 Java 而去使用 JNI. 770 | 771 | 使用 JNI,无外乎下面几种情况: 772 | 773 | * 加速算法运行 774 | * 与操作系统本地 API 交互 775 | * 防止核心算法遭逆向工程而暴露 776 | 777 | 基本使用流程如下: 778 | 779 | ### Java class 780 | 781 | ```java 782 | public class NativeInterface { 783 | static { 784 | System.loadLibrary("JNIProject"); //只能为文件名,不含路径和扩展名 785 | } 786 | 787 | public static native String test(); //给方法带上 native 关键字即表示这是本地方法 788 | public native String nonStaticArray(); 789 | } 790 | ``` 791 | 792 | ### 生成头文件 793 | 794 | Java 10+: `javac -h . NativeInterface.java` 795 | Java 旧版本: `javah -jni . NativeInterface.java` 796 | 797 | ### JVM 命令 798 | 799 | 指定库路径: `-Djava.library.path=./JNI/JNIProject/x64/Debug` 800 | 801 | ### C++ 802 | 803 | ```c++ 804 | #include "NativeInterface.h" 805 | #include 806 | 807 | int main() 808 | { 809 | return 0; 810 | } 811 | 812 | JNIEXPORT jstring JNICALL Java_NativeInterface_test(JNIEnv* env, jclass cls) 813 | { 814 | printf("%s", "jni ok\n"); 815 | printf("%d", 666); 816 | return env->NewStringUTF("yooooo STATIC"); 817 | } 818 | 819 | JNIEXPORT jstring JNICALL Java_NativeInterface_nonStaticArray(JNIEnv* env, jobject obj) 820 | { 821 | return env->NewStringUTF("Non static"); 822 | } 823 | ``` 824 | 825 | ## Kotlin 神教! 826 | 827 | 本节仅作了解即可。或者可以*直接跳过*。 828 | 829 | * 空安全 830 | * 自带单例模式 831 | * 自带惰性加载 832 | * 无需手动编写 setter/getter 833 | * setter/getter 更加符合直觉 834 | * 重写运算符 835 | * 扩展方法 836 | * 更优雅的构造器 837 | * 你不需要使用 Builder 838 | * 你可以少用工厂方法了 --------------------------------------------------------------------------------