关于java:硬肝44w字为你写成Java开发手册

47次阅读

共计 44300 个字符,预计需要花费 111 分钟才能阅读完成。

先来看一下本篇文章的思维导图吧,我会围绕上面这些内容进行解说。内容很干,小伙伴们看完还心愿不吝转发。(高清思维导图版本关注作者公众号 Java 建设者 回复 Java666 获取,其余思维导图获取形式在文末)。

上面开始咱们的文章。

Java 概述

什么是 Java?

Java 是 Sun Microsystems 于 1995 年首次公布的一种 编程语言 和计算平台。编程语言还比拟好了解,那么什么是 计算平台 呢?

计算平台是在电脑中运行应用程序(软件)的环境,包含 硬件环境 软件环境。个别零碎平台包含一台电脑的硬件体系结构、操作系统、运行时库。

Java 是疾速,平安和牢靠的。从笔记本电脑到数据中心,从游戏机到迷信超级计算机,从手机到互联网,Java 无处不在!Java 次要分为三个版本

  • JavaSE(J2SE)(Java2 Platform Standard Edition,java 平台标准版)
  • JavaEE(J2EE)(Java 2 Platform,Enterprise Edition,java 平台企业版)
  • JavaME(J2ME)(Java 2 Platform Micro Edition,java 平台微型版)。

Java 的特点

  • Java 是一门 面向对象 的编程语言

什么是面向对象?面向对象(Object Oriented) 是一种软件开发思维。它是对事实世界的一种形象,面向对象会把相干的数据和办法组织为一个整体来对待。

绝对的另外一种开发思维就是面向过程的开发思维,什么面向过程?面向过程(Procedure Oriented) 是一种以过程为核心的编程思维。举个例子:比方你是个学生,你每天去上学须要做几件事件?

起床、穿衣服、洗脸刷牙,吃饭,去学校。个别是程序性的实现一系列动作。

class student {void student_wakeUp(){...}
      void student_cloth(){...}
      void student_wash(){...}
      void student_eating(){...}
      void student_gotoSchool(){...}
} 

而面向对象能够把学生进行形象,所以这个例子就会变为

class student(){void wakeUp(){...}
      void cloth(){...}
      void wash(){...}
      void eating(){...}
      void gotoSchool(){...}
} 

能够不必严格依照程序来执行每个动作。这是特点一。

  • Java 摒弃了 C++ 中难以了解的多继承、指针、内存治理等概念;不必手动治理对象的生命周期,这是特色二。
  • Java 语言具备功能强大和简略易用两个特色,当初企业级开发,疾速麻利开发,尤其是各种框架的呈现,使 Java 成为越来越火的一门语言。这是特点三。
  • Java 是一门动态语言,动态语言指的就是在编译期间就可能晓得数据类型的语言,在运行前就可能查看类型的正确性,一旦类型确定后就不能再更改,比方上面这个例子。
public void foo() {
    int x = 5;
    boolean b = x;
} 

动态语言次要有 Pascal, Perl, C/C++, JAVA, C#, Scala 等。

绝对应的,动静语言没有任何特定的状况须要指定变量的类型,在运行时确定的数据类型。比方有 Lisp, Perl, Python、Ruby、JavaScript 等。

从设计的角度上来说,所有的语言都是设计用来把人类可读的代码转换为机器指令。动静语言是为了可能让程序员进步编码效率,因而你能够应用更少的代码来实现性能。动态语言设计是用来让硬件执行的更高效,因而须要程序员编写准确无误的代码,以此来让你的代码尽快的执行。从这个角度来说,动态语言的执行效率要比动静语言高,速度更快。这是特点四。

  • Java 具备平台独立性和可移植性

Java 有一句十分驰名的口号:Write once, run anywhere,也就是一次编写、到处运行。为什么 Java 可能吹出这种牛批的口号来?外围就是 JVM。咱们晓得,计算机应用程序和硬件之间会屏蔽很多细节,它们之间依附操作系统实现调度和协调,大抵的体系结构如下

那么加上 Java 利用、JVM 的体系结构会变为如下

Java 是跨平台的,已编译的 Java 程序能够在任何带有 JVM 的平台上运行。你能够在 Windows 平台下编写代码,而后拿到 Linux 平台下运行,该如何实现呢?

首先你须要在利用中编写 Java 代码;

Eclipse 或者 javac 把 Java 代码编译为 .class 文件;

而后把你的 .class 文件打成 .jar 文件;

而后你的 .jar 文件就可能在 Windows、Mac OS X、Linux 零碎下运行了。不同的操作系统有不同的 JVM 实现,切换平台时,不须要再次编译你的 Java 代码了。这是特点五。

  • Java 可能容易实现多线程

Java 是一门高级语言,高级语言会对用户屏蔽很多底层实现细节。比方 Java 是如何实现多线程的。从操作系统的角度来说,实现多线程的形式次要有上面这几种

在用户空间中实现多线程

在内核空间中实现多线程

在用户和内核空间中混合实现线程

而我认为 Java 应该是在 用户空间 实现的多线程,内核是感知不到 Java 存在多线程机制的。这是特点六。

  • Java 具备高性能

咱们编写的代码,通过 javac 编译器编译称为 字节码(bytecode),通过 JVM 内嵌的解释器将字节码转换为机器代码,这是解释执行,这种转换过程效率较低。然而局部 JVM 的实现比方 Hotspot JVM 都提供了 JIT(Just-In-Time) 编译器,也就是通常所说的动静编译???? 器,JIT 可能在运行时将???? 热点代码编译机器码,这种形式运行效率比拟高,这是编译执行。所以 Java 不仅仅只是一种解释执行的语言。这是特点七。

  • Java 语言具备健壮性

Java 的强类型机制、异样解决、垃圾的主动收集等是 Java 程序健壮性的重要保障。这也是 Java 与 C 语言的重要区别。这是特点八。

  • Java 很容易开发分布式我的项目

Java 语言反对 Internet 利用的开发,Java 中有 net api,它提供了用于网络应用编程的类库,包含 URL、URLConnection、Socket、ServerSocket 等。Java 的 RMI(近程办法激活)机制也是开发分布式应用的重要伎俩。这是特点九。

Java 开发环境

JDK

JDK(Java Development Kit)称为 Java 开发包或 Java 开发工具,是一个编写 Java 的 Applet 小程序和应用程序的程序开发环境。JDK 是整个 Java 的外围,包含了Java 运行环境(Java Runtime Environment),一些Java 工具Java 的外围类库(Java API)

咱们能够认真钻研一下这张图,它简直包含了 Java 中所有的概念,我应用的是 jdk1.8,能够点进去 Description of Java Conceptual Diagram,能够发现这外面包含了所有对于 Java 的形容

Oracle 提供了两种 Java 平台的实现,一种是咱们下面说的 JDK,Java 开发规范工具包,一种是 JRE,叫做 Java Runtime Environment,Java 运行时环境。JDK 的性能要比 JRE 全很多。

JRE

JRE 是个运行环境,JDK 是个开发环境。因而写 Java 程序的时候须要 JDK,而运行 Java 程序的时候就须要 JRE。而 JDK 外面曾经蕴含了 JRE,因而只有装置了 JDK,就能够编辑 Java 程序,也能够失常运行 Java 程序。但因为 JDK 蕴含了许多与运行无关的内容,占用的空间较大,因而运行一般的 Java 程序毋庸装置 JDK,而只须要装置 JRE 即可。

Java 开发环境配置

这个中央不再多说了,网上有很多教程配置的材料可供参考。

Java 根本语法

在配置完 Java 开发环境,并下载 Java 开发工具(Eclipse、IDEA 等)后,就能够写 Java 代码了,因为本篇文章是从头梳理 Java 体系,所以有必要从根底的概念开始谈起。

数据类型

在 Java 中,数据类型只有 四类八种

  • 整数型:byte、short、int、long

byte 也就是字节,1 byte = 8 bits,byte 的默认值是 0;

short 占用两个字节,也就是 16 位,1 short = 16 bits,它的默认值也是 0;

int 占用四个字节,也就是 32 位,1 int = 32 bits,默认值是 0;

long 占用八个字节,也就是 64 位,1 long = 64 bits,默认值是 0L;

所以整数型的占用字节大小空间为 long > int > short > byte

  • 浮点型

浮点型有两种数据类型:float 和 double

float 是单精度浮点型,占用 4 位,1 float = 32 bits,默认值是 0.0f;

double 是双精度浮点型,占用 8 位,1 double = 64 bits,默认值是 0.0d;

  • 字符型

字符型就是 char,char 类型是一个繁多的 16 位 Unicode 字符,最小值是 u0000 (也就是 0),最大值是 uffff (即为 65535),char 数据类型能够存储任何字符,例如 char a = ‘A’。

  • 布尔型

布尔型指的就是 boolean,boolean 只有两种值,true 或者是 false,只示意 1 位,默认值是 false。

以上 x 位 都指的是在内存中的占用。

根底语法

  • 大小写敏感:Java 是对大小写敏感的语言,例如 Hello 与 hello 是不同的,这其实就是 Java 的字符串示意形式
  • 类名:对于所有的类来说,首字母应该大写,例如 MyFirstClass
  • 包名:包名应该尽量保障小写,例如 my.first.package
  • 办法名:办法名首字母须要小写,前面每个单词字母都须要大写,例如 myFirstMethod()

运算符

运算符不只 Java 中有,其余语言也有运算符,运算符是一些非凡的符号,次要用于数学函数、一些类型的赋值语句和逻辑比拟方面,咱们就以 Java 为例,来看一下运算符。

  • 赋值运算符

赋值运算符应用操作符 = 来示意,它的意思是把 = 号左边的值复制给右边,左边的值能够是任何常数、变量或者表达式,但右边的值必须是一个明确的,曾经定义的变量。比方 int a = 4

然而对于对象来说,复制的不是对象的值,而是对象的援用,所以如果说将一个对象复制给另一个对象,实际上是将 一个对象的援用赋值给另一个对象

  • 算数运算符

算数运算符就和数学中的数值计算差不多,次要有

算数运算符须要留神的就是 优先级问题,当一个表达式中存在多个操作符时,操作符的优先级程序就决定了计算程序,最简略的规定就是先乘除后加减,() 的优先级最高,没必要记住所有的优先级程序,不确定的间接用 () 就能够了。

  • 自增、自减运算符

这个就不文字解释了,解释不如间接看例子明确

int a = 5;
b = ++a;
c = a++; 
  • 比拟运算符

比拟运算符用于程序中的变量之间,变量和自变量之间以及其余类型的信息之间的比拟。

比拟运算符的运算后果是 boolean 型。当运算符对应的关系成立时,运算的后果为 true,否则为 false。比拟运算符共有 6 个,通常作为判断的根据用于条件语句中。

  • 逻辑运算符

逻辑运算符次要有三种,与、或、非

上面是逻辑运算符对应的 true/false 符号表

  • 按位运算符

按位运算符用来操作整数根本类型中的每个 比特 位,也就是二进制位。按位操作符会对两个参数中对应的位执行布尔代数运算,并最终生成一个后果。

如果进行比拟的单方是数字的话,那么进行比拟就会变为按位运算。

按位与:按位进行与运算(AND),两个操作数中位都为 1,后果才为 1,否则后果为 0。须要首先把比拟单方转换成二进制再按每个位进行比拟

按位或:按位进行或运算(OR),两个位只有有一个为 1,那么后果就是 1,否则就为 0。

按位非:按位进行异或运算(XOR),如果位为 0,后果是 1,如果位为 1,后果是 0。

按位异或:按位进行取反运算(NOT),两个操作数的位中,雷同则后果为 0,不同则后果为 1。

  • 移位运算符

移位运算符用来将操作数向某个方向(向左或者右)挪动指定的二进制位数。

  • 三元运算符

三元运算符是相似 if...else... 这种的操作符,语法为:条件表达式?表达式 1:表达式 2。问号后面的地位是判断的条件,判断后果为布尔型,为 true 时调用表达式 1,为 false 时调用表达式 2。

Java 执行管制流程

Java 中的管制流程其实和 C 一样,在 Java 中,流程管制会波及到包含 if-else、while、do-while、for、return、break 以及抉择语句 switch。上面以此进行剖析

条件语句

条件语句可依据不同的条件执行不同的语句。包含 if 条件语句与 switch 多分支语句。

if 条件语句

if 语句能够独自判断表达式的后果,示意表白的执行后果,例如

int a = 10;
if(a > 10){return true;}
return false; 

if…else 条件语句

if 语句还能够与 else 连用,通常体现为 如果满足某种条件,就进行某种解决,否则就进行另一种解决

int a = 10;
int b = 11;
if(a >= b){System.out.println("a >= b");
}else{System.out.println("a < b");
} 

if 后的 () 内的表达式必须是 boolean 型的。如果为 true,则执行 if 后的复合语句;如果为 false,则执行 else 后的复合语句。

if…else if 多分支语句

下面中的 if…else 是单分支和两个分支的判断,如果有多个判断条件,就须要应用 if…else if

int x = 40;
if(x > 60) {System.out.println("x 的值大于 60");
} else if (x > 30) {System.out.println("x 的值大于 30 但小于 60");
} else if (x > 0) {System.out.println("x 的值大于 0 但小于 30");
} else {System.out.println("x 的值小于等于 0");
} 

switch 多分支语句

一种比 if…else if 语句更优雅的形式是应用 switch 多分支语句,它的示例如下

switch (week) {
  case 1:
    System.out.println("Monday");
    break;
  case 2:
    System.out.println("Tuesday");
    break;
  case 3:
    System.out.println("Wednesday");
    break;
  case 4:
    System.out.println("Thursday");
    break;
  case 5:
    System.out.println("Friday");
    break;
  case 6:
    System.out.println("Saturday");
    break;
  case 7:
    System.out.println("Sunday");
    break;
  default:
    System.out.println("No Else");
    break;
} 

循环语句

循环语句就是在满足肯定的条件下重复执行某一表达式的操作,直到满足循环语句的要求。应用的循环语句次要有 for、do…while()、while

while 循环语句

while 循环语句的循环形式为利用一个条件来管制是否要持续重复执行这个语句。while 循环语句的格局如下

while(布尔值){表达式} 

它的含意是,当 (布尔值) 为 true 的时候,执行上面的表达式,布尔值为 false 的时候,完结循环,布尔值其实也是一个表达式,比方

int a = 10;
while(a > 5){a--;} 

do…while 循环

while 与 do…while 循环的惟一区别是 do…while 语句至多执行一次,即便第一次的表达式为 false。而在 while 循环中,如果第一次条件为 false,那么其中的语句基本不会执行。在理论利用中,while 要比 do…while 利用的更广。它的个别模式如下

int b = 10;
// do···while 循环语句
do {System.out.println("b ==" + b);
  b--;
} while(b == 1); 

for 循环语句

for 循环是咱们常常应用的循环形式,这种模式会在第一次迭代前进行初始化。它的模式如下

for(初始化; 布尔表达式; 步进){} 

每次迭代前会测试布尔表达式。如果取得的后果是 false,就会执行 for 语句前面的代码;每次循环完结,会依照步进的值执行下一次循环。

逗号操作符

这里不可疏忽的一个就是逗号操作符,Java 里惟一用到逗号操作符的就是 for 循环管制语句。在表达式的初始化局部,能够应用一系列的逗号分隔的语句;通过逗号操作符,能够在 for 语句内定义多个变量,但它们必须具备雷同的类型

for(int i = 1;j = i + 10;i < 5;i++, j = j * 2){} 

for-each 语句

在 Java JDK 1.5 中还引入了一种更加简洁的、不便对数组和汇合进行遍历的办法,即 for-each 语句,例子如下

int array[] = {7, 8, 9};

for (int arr : array) {System.out.println(arr);
} 

跳转语句

Java 语言中,有三种跳转语句: break、continue 和 return

break 语句

break 语句咱们在 switch 中曾经见到了,它是用于终止循环的操作,实际上 break 语句在 for、while、do···while 循环语句中,用于强行退出以后循环,例如

for(int i = 0;i < 10;i++){if(i == 5){break;}
} 

continue 语句

continue 也能够放在循环语句中,它与 break 语句具备相同的成果,它的作用是用于执行下一次循环,而不是退出以后循环,还以下面的例子为主

for(int i = 0;i < 10;i++){System.out.printl("i =" + i);
    if(i == 5){System.out.printl("continue ...");
    continue;
  }
} 

return 语句

return 语句能够从一个办法返回,并把控制权交给调用它的语句。

public void getName() {return name;} 

面向对象

上面咱们来探讨面向对象的思维,面向对象的思维曾经逐渐取代了过程化的思维 — 面向过程,Java 是面向对象的高级编程语言,面向对象语言具备如下特色

  • 面向对象是一种常见的思维,比拟合乎人们的思考习惯;
  • 面向对象能够将简单的业务逻辑简单化,加强代码复用性;
  • 面向对象具备形象、封装、继承、多态等个性。

面向对象的编程语言次要有:C++、Java、C# 等。

所以必须相熟面向对象的思维能力编写出 Java 程序。

类也是一种对象

当初咱们来意识一个面向对象的新的概念 — 类,什么是类,它就相当于是一系列对象的形象,就比方书籍一样,类相当于是书的封面,大多数面向对象的语言都应用 class 来定义类,它通知你它外面定义的对象都是什么样的,咱们个别应用上面来定义类

class ClassName {// body;} 

代码段中波及一个新的概念 //,这个咱们前面会说。下面,你申明了一个 class 类,当初,你就能够应用 new 来创立这个对象

ClassName classname = new ClassName(); 

个别,类的命名遵循 驼峰准则,它的定义如下

骆驼式命名法(Camel-Case)又称驼峰式命名法,是电脑程式编写时的一套命名规定(常规)。正如它的名称 CamelCase 所示意的那样,是指混合应用大小写字母来形成变量和函数的名字。程序员们为了本人的代码能更容易的在同行之间交换,所以多采取对立的可读性比拟好的命名形式。

对象的创立

在 Java 中,万事万物都是对象 。这句话置信你肯定不生疏,只管所有都看作是对象,然而你操纵的却是一个对象的 援用 (reference)。在这里有一个很形象的比喻:你能够把车钥匙和车看作是一组 对象援用和对象 的组合。当你想要开车的时候,你首先须要拿出车钥匙点击开锁的选项,停车时,你须要点击加锁来锁车。车钥匙相当于就是援用,车就是对象,由车钥匙来驱动车的加锁和开锁。并且,即便没有车的存在,车钥匙也是一个独立存在的实体,也就是说,你有一个对象援用,但你不肯定须要一个对象与之关联,也就是

Car carKey; 

这里创立的只是援用,而并非对象,然而如果你想要应用 s 这个援用时,会返回一个异样,通知你须要一个对象来和这个援用进行关联。一种平安的做法是,在创建对象援用时同时把一个对象赋给它。

Car carKey = new Car(); 

在 Java 中,一旦创立了一个援用,就心愿它能与一个新的对象进行关联,通常应用 new 操作符来实现这一目标。new 的意思是,给我一个新 对象,如果你不想相亲,本人 new 一个对象就好了。祝你下辈子幸福。

属性和办法

类一个最根本的因素就是有属性和办法。

属性也被称为字段,它是类的重要组成部分,属性能够是任意类型的对象,也能够是根本数据类型。例如下

class A{
  int a;
  Apple apple;
} 

类中还应该包含办法,办法示意的是 做某些事件的形式。办法其实就是函数,只不过 Java 习惯把函数称为办法。这种叫法也体现了面向对象的概念。

办法的根本组成包含 办法名称、参数、返回值和办法体,上面是它的示例

public int getResult(){
  // ...
  return 1;
} 

其中,getResult 就是办法名称、() 外面示意办法接管的参数、return 示意办法的返回值,留神:办法的返回值必须和办法的 参数 类型保持一致。有一种非凡的参数类型 — void 示意办法无返回值。{} 蕴含的代码段被称为办法体。

构造方法

在 Java 中,有一种非凡的办法被称为 构造方法,也被称为构造函数、结构器等。在 Java 中,通过提供这个结构器,来确保每个对象都被初始化。构造方法只能在对象的创立期间调用一次,保障了对象初始化的进行。构造方法比拟非凡,它没有参数类型和返回值,它的名称要和类名保持一致,并且构造方法能够有多个,上面是一个构造方法的示例

class Apple {
  
  int sum;
  String color;
  
  public Apple(){}
  public Apple(int sum){}
  public Apple(String color){}
  public Apple(int sum,String color){}} 

下面定义了一个 Apple 类,你会发现这个 Apple 类没有参数类型和返回值,并且有多个以 Apple 同名的办法,而且各个 Apple 的参数列表都不一样,这其实是一种多态的体现,咱们前面会说。在定义实现构造方法后,咱们就可能创立 Apple 对象了。

class createApple {public static void main(String[] args) {Apple apple1 = new Apple();
        Apple apple2 = new Apple(1);
        Apple apple3 = new Apple("red");
        Apple apple4 = new Apple(2,"color");

    }
} 

如下面所示,咱们定义了四个 Apple 对象,并调用了 Apple 的四种不同的构造方法,其中,不加任何参数的构造方法被称为默认的构造方法,也就是

Apple apple1 = new Apple(); 

如果类中没有定义任何构造方法,那么 JVM 会为你主动生成一个构造方法,如下

class Apple {
  
  int sum;
  String color;
  
}

class createApple {public static void main(String[] args) {Apple apple1 = new Apple();

    }
} 

下面代码不会产生编译谬误,因为 Apple 对象蕴含了一个默认的构造方法。

默认的构造方法也被称为默认结构器或者无参结构器。

这里须要留神一点的是,即便 JVM 会为你默认增加一个无参的结构器,然而如果你手动定义了任何一个构造方法,JVM 就不再为你提供默认的结构器,你必须手动指定,否则会呈现编译谬误

显示的谬误是,必须提供 Apple 带有 int 参数的构造函数,而默认的无参构造函数没有被容许应用。

办法重载

在 Java 中一个很重要的概念是办法的重载,它是类名的不同表现形式。咱们下面说到了构造函数,其实构造函数也是重载的一种。另外一种就是办法的重载

public class Apple {

    int sum;
    String color;

    public Apple(){}
    public Apple(int sum){}
    
    public int getApple(int num){return 1;}
    
    public String getApple(String color){return "color";}

} 

如下面所示,就有两种重载的形式,一种是 Apple 构造函数的重载,一种是 getApple 办法的重载。

然而这样就波及到一个问题,要是有几个雷同的名字,Java 如何晓得你调用的是哪个办法呢?这里记住一点即可,每个重载的办法都有举世无双的参数列表。其中包含参数的类型、程序、参数数量等,满足一种一个因素就形成了重载的必要条件。

请记住上面重载的条件

  • 办法名称必须雷同。
  • 参数列表必须不同(个数不同、或类型不同、参数类型排列程序不同等)。
  • 办法的返回类型能够雷同也能够不雷同。
  • 仅仅返回类型不同不足以成为办法的重载。
  • 重载是产生在编译时的,因为编译器能够依据参数的类型来抉择应用哪个办法。

办法的重写

办法的重写与重载尽管名字很类似,但却齐全是不同的货色。办法重写的形容是对 子类和父类 之间的。而重载指的是同一类中的。例如如下代码

class Fruit {public void eat(){System.out.printl('eat fruit');
  }
}

class Apple extends Fruit{
  
  @Override
  public void eat(){System.out.printl('eat apple');
  }
} 

下面这段代码形容的就是重写的代码,你能够看到,子类 Apple 中的办法和父类 Fruit 中的办法同名,所以,咱们可能推断出重写的准则

  • 重写的办法必须要和父类保持一致,包含 返回值类型, 办法名, 参数列表 也都一样。
  • 重写的办法能够应用 @Override 注解来标识
  • 子类中重写办法的拜访权限不能低于父类中办法的拜访权限。

初始化

类的初始化

下面咱们创立进去了一个 Car 这个对象,其实在应用 new 关键字创立一个对象的时候,其实是调用了这个对象无参数的构造方法进行的初始化,也就是如下这段代码

class Car{public Car(){}} 

这个无参数的构造函数能够暗藏,由 JVM 主动增加。也就是说,构造函数可能确保类的初始化。

成员初始化

Java 会尽量保障每个变量在应用前都会取得初始化,初始化波及两种初始化。

  • 一种是编译器默认指定的字段初始化,根本数据类型的初始化

    一种是其余对象类型的初始化,String 也是一种对象,对象的初始值都为 null,其中也包含根本类型的包装类。

  • 一种是指定数值的初始化,例如
int a = 11 

也就是说,指定 a 的初始化值不是 0,而是 11。其余根本类型和对象类型也是一样的。

结构器初始化

能够利用结构器来对某些办法和某些动作进行初始化,确定初始值,例如

public class Counter{
  int i;
  public Counter(){i = 11;}
} 

利用构造函数,可能把 i 的值初始化为 11。

初始化程序

首先先来看一下有哪些须要探讨的初始化程序

  • 动态属性:static 结尾定义的属性
  • 静态方法块:static {} 包起来的代码块
  • 一般属性:非 static 定义的属性
  • 一般办法块:{} 包起来的代码块
  • 构造函数:类名雷同的办法
  • 办法:一般办法
public class LifeCycle {
    // 动态属性
    private static String staticField = getStaticField();
    // 静态方法块
    static {System.out.println(staticField);
        System.out.println("静态方法块初始化");
    }
    // 一般属性
    private String field = getField();
    // 一般办法块
    {System.out.println(field);
    }
    // 构造函数
    public LifeCycle() {System.out.println("构造函数初始化");
    }

    public static String getStaticField() {
        String statiFiled = "Static Field Initial";
        return statiFiled;
    }

    public static String getField() {
        String filed = "Field Initial";
        return filed;
    }   
    // 主函数
    public static void main(String[] argc) {new LifeCycle();
    }
} 

这段代码的执行后果就反馈了它的初始化程序

动态属性初始化
静态方法块初始化
一般属性初始化
一般办法块初始化
构造函数初始化

数组初始化

数组是雷同类型的、用一个标识符名称封装到一起的一个对象序列或根本类型数据序列。数组是通过方括号下标操作符 [] 来定义应用。

个别数组是这么定义的

int[] a1;

// 或者

int a1[]; 

两种格局的含意是一样的。

  • 间接给每个元素赋值 : int array[4] = {1,2,3,4};
  • 给一部分赋值,前面的都为 0:int array[4] = {1,2};
  • 由赋值参数个数决定数组的个数:int array[] = {1,2};

可变参数列表

Java 中一种数组冷门的用法就是 可变参数,可变参数的定义如下

public int add(int... numbers){
  int sum = 0;
  for(int num : numbers){sum += num;}
  return sum;
} 

而后,你能够应用上面这几种形式进行可变参数的调用

add();  // 不传参数
add(1);  // 传递一个参数
add(2,1);  // 传递多个参数
add(new Integer[] {1, 3, 2});  // 传递数组 

对象的销毁

尽管 Java 语言是基于 C++ 的,然而它和 C/C++ 一个重要的特色就是不须要手动治理对象的销毁工作。在驰名的一书《深刻了解 Java 虚拟机》中提到一个观点

在 Java 中,咱们不再须要手动治理对象的销毁,它是由 Java 虚拟机 进行治理和销毁的。尽管咱们不须要手动治理对象,然而你须要晓得 对象作用域 这个概念。

对象作用域

J 少数语言都有 作用域(scope) 这个概念。作用域决定了其外部定义的变量名的可见性和生命周期。在 C、C++ 和 Java 中,作用域通常由 {} 的地位来决定,例如

{
  int a = 11;
  {int b = 12;}
} 

a 变量会在两个 {} 作用域内无效,而 b 变量的值只能在它本人的 {} 内无效。

尽管存在作用域,然而不容许这样写

{
  int x = 11;
  {int x = 12;}
} 

这种写法在 C/C++ 中是能够的,然而在 Java 中不容许这样写,因为 Java 设计者认为这样写会导致程序凌乱。

this 和 super

this 和 super 都是 Java 中的关键字

this 示意的以后对象,this 能够调用办法、调用属性和指向对象自身。this 在 Java 中的应用个别有三种:指向以后对象

public class Apple {

    int i = 0;

    Apple eatApple(){
        i++;
        return this;
    }

    public static void main(String[] args) {Apple apple = new Apple();
        apple.eatApple().eatApple();
    }
} 

这段代码比拟精妙,精妙在哪呢,我一个 eatApple() 办法居然能够调用屡次,你在前面还能够持续调用,这就很神奇了,为啥呢?其实就是 this 在作怪了,我在 eatApple 办法中加了一个 return this 的返回值,也就是说哪个对象调用 eatApple 办法都能返回对象的本身。

this 还能够润饰属性,最常见的就是在构造方法中应用 this,如下所示

public class Apple {

    private int num;
    
    public Apple(int num){this.num = num;}

    public static void main(String[] args) {new Apple(10);
    }
} 

main 办法中传递了一个 int 值为 10 的参数,它示意的就是苹果的数量,并把这个数量赋给了 num 全局变量。所以 num 的值当初就是 10。

this 还能够和构造函数一起应用,充当一个全局关键字的成果

public class Apple {

    private int num;
    private String color;

    public Apple(int num){this(num,"红色");
    }
    
    public Apple(String color){this(1,color);
    }

    public Apple(int num, String color) {
        this.num = num;
        this.color = color;
    }
    
} 

你会发现下面这段代码应用的不是 this, 而是 this(参数)。它相当于调用了其余构造方法,而后传递参数进去。这里留神一点:this() 必须放在构造方法的第一行,否则编译不通过

如果你把 this 了解为指向本身的一个援用,那么 super 就是指向父类的一个援用。super 关键字和 this 一样,你能够应用 super. 对象 来援用父类的成员,如下

public class Fruit {

    int num;
    String color;

    public void eat(){System.out.println("eat Fruit");
    }
}

public class Apple extends Fruit{

    @Override
    public void eat() {
        super.num = 10;
        System.out.println("eat" + num + "Apple");
    }

} 

你也能够应用 super(参数) 来调用父类的构造函数,这里不再举例子了。

上面为你汇总了 this 关键字和 super 关键字的比拟。

访问控制权限

访问控制权限又称为 封装 ,它是面向对象三大个性中的一种,我之前在学习过程中常常会疏忽封装,心想这不就是一个拜访修饰符么,怎么就是三大个性的必要条件了?起初我才晓得, 如果你信赖的上司对你瞒哄 bug,你是基本不晓得的

访问控制权限其实最外围就是一点:只对须要的类可见。

Java 中成员的拜访权限共有四种,别离是 public、protected、default、private,它们的可见性如下

继承

继承是所有 OOP(Object Oriented Programming) 语言和 Java 语言都不可或缺的一部分。只有咱们创立了一个类,就隐式的继承自 Object 父类,只不过没有指定。如果你显示指定了父类,那么你继承于父类,而你的父类继承于 Object 类。

继承的关键字是 extends,如上图所示,如果应用了 extends 显示指定了继承,那么咱们能够说 Father 是父类,而 Son 是子类,用代码示意如下

class Father{}

class Son extends Father{} 

继承单方领有某种共性的特色

class Father{public void feature(){System.out.println("父亲的特色");
  }
}

class Son extends Father {} 

如果 Son 没有实现本人的办法的话,那么默认就是用的是父类的 feature 办法。如果子类实现了本人的 feature 办法,那么就相当于是重写了父类的 feature 办法,这也是咱们下面提到的重写了。

多态

多态指的是同一个行为具备多个不同表现形式。是指一个类实例(对象)的雷同办法在不同情景下具备不同表现形式。封装和继承是多态的根底,也就是说,多态只是一种表现形式而已。

如何实现多态?多态的实现具备三种充要条件

  • 继承
  • 重写父类办法
  • 父类援用指向子类对象

比方上面这段代码

public class Fruit {

    int num;

    public void eat(){System.out.println("eat Fruit");
    }
}

public class Apple extends Fruit{

    @Override
    public void eat() {
        super.num = 10;
        System.out.println("eat" + num + "Apple");
    }

    public static void main(String[] args) {Fruit fruit = new Apple();
        fruit.eat();}
} 

你能够发现 main 办法中有一个很神奇的中央,Fruit fruit = new Apple(),Fruit 类型的对象居然指向了 Apple 对象的援用,这其实就是多态 -> 父类援用指向子类对象,因为 Apple 继承于 Fruit,并且重写了 eat 办法,所以可能体现进去多种状态的模式。

组合

组合其实不难理解,就是将对象援用置于新类中即可。组合也是一种进步类的复用性的一种形式。如果你想让类具备更多的扩大性能,你须要记住一句话 多用组合,少用继承

public class SoccerPlayer {
    
    private String name;
    private Soccer soccer;
    
}

public class Soccer {private String soccerName;} 

代码中 SoccerPlayer 援用了 Soccer 类,通过援用 Soccer 类,来达到调用 soccer 中的属性和办法。

组合和继承是有区别的,它们的次要区别如下。

对于继承和组合孰优孰劣的争执没有后果,只有施展各自的短处和长处即可,个别状况下,组合和继承也是一对能够连用的好兄弟。

代理

除了继承和组合外,另外一种值得探讨的关系模型称为 代理。代理的大抵形容是,A 想要调用 B 类的办法,A 不间接调用,A 会在本人的类中创立一个 B 对象的代理,再由代理调用 B 的办法。例如如下代码

public class Destination {public void todo(){System.out.println("control...");
    }
}

public class Device {

    private String name;
    private Destination destination;
    private DeviceController deviceController;

    public void control(Destination destination){destination.todo();
    }

}

public class DeviceController {

    private Device name;
    private Destination destination;

    public void control(Destination destination){destination.todo();
    }
} 

向上转型

向上转型代表了父类与子类之间的关系,其实父类和子类之间不仅仅有向上转型,还有向下转型,它们的转型后的范畴不一样

  • 向上转型 :通过子类对象(小范畴) 转化为父类对象(大范畴),这种转换是主动实现的,不必强制。
  • 向下转型 : 通过父类对象(大范畴) 实例化子类对象(小范畴),这种转换不是主动实现的,须要强制指定。

static

static 是 Java 中的关键字,它的意思是 动态的,static 能够用来润饰成员变量和办法,static 用在没有创建对象的状况下调用 办法 / 变量。

  • 用 static 申明的成员变量为动态成员变量,也成为类变量。类变量的生命周期和类雷同,在整个应用程序执行期间都无效。
static String name = "cxuan"; 
  • 应用 static 润饰的办法称为静态方法,静态方法可能间接应用 类名. 办法名 进行调用。因为静态方法不依赖于任何对象就能够间接拜访,因而对于静态方法来说,是没有 this 关键字的,实例变量都会有 this 关键字。在静态方法中不能拜访类的非动态成员变量和非静态方法,
static void printMessage(){System.out.println("cxuan is writing the article");
} 

static 除了润饰属性和办法外,还有 动态代码块 的性能,可用于类的初始化操作。进而晋升程序的性能。

public class StaicBlock {
    static{System.out.println("I'm A static code block");
    }
} 

因为动态代码块随着类的加载而执行,因而,很多时候会将只须要进行一次的初始化操作放在 static 代码块中进行。

final

final 的意思是最初的、最终的,它能够润饰类、属性和办法。

  • final 润饰类时,表明这个类不能被继承。final 类中的成员变量能够依据须要设为 final,然而要留神 final 类中的所有成员办法都会被隐式地指定为 final 办法。
  • final 润饰办法时,表明这个办法不能被任何子类重写,因而,如果只有在想明确禁止该办法在子类中被笼罩的状况下才将办法设置为 final。
  • final 润饰变量分为两种状况,一种是润饰根本数据类型,示意数据类型的值不能被批改;一种是润饰援用类型,示意对其初始化之后便不能再让其指向另一个对象。

接口和抽象类

接口

接口相当于就是对外的一种约定和规范,这里拿操作系统举例子,为什么会有操作系统?就会为了屏蔽软件的复杂性和硬件的简略性之间的差别,为软件提供对立的规范。

在 Java 语言中,接口是由 interface 关键字来示意的,比方咱们能够向上面这样定义一个接口

public interface CxuanGoodJob {} 

比方咱们定义了一个 CxuanGoodJob 的接口,而后你就能够在其外部定义 cxuan 做的好的那些事件,比方 cxuan 写的文章不错。

public interface CxuanGoodJob {void writeWell();
} 

这里隐含了一些接口的特色:

  • interface 接口是一个齐全形象的类,他不会提供任何办法的实现,只是会进行办法的定义。
  • 接口中只能应用两种拜访修饰符,一种是 public,它对整个我的项目可见;一种是 default 缺省值,它只具备包拜访权限。
  • 接口只提供办法的定义,接口没有实现,然而接口能够被其余类实现。也就是说,实现接口的类须要提供办法的实现,实现接口应用 implements 关键字来示意,一个接口能够有多个实现。
class CXuanWriteWell implements CxuanGoodJob{

    @Override
    public void writeWell() {System.out.println("Cxuan write Java is vary well");
    }
} 
  • 接口不能被实例化,所以接口中不能有任何构造方法,你定义构造方法编译会出错。
  • 接口的实现比方实现接口的全副办法,否则必须定义为 抽象类,这就是咱们上面要说的内容

抽象类

抽象类是一种形象能力弱于接口的类,在 Java 中,抽象类应用 abstract 关键字来示意。如果把接口形容为狗这个物种,那么抽象类能够说是毛发是红色、小体的种类,而实现类能够是具体的类,比如说是博美、泰迪等。你能够像上面这样定义抽象类

public interface Dog {void FurColor();

}

abstract class WhiteDog implements Dog{public void FurColor(){System.out.println("Fur is white");
    }

    abstract void SmallBody();} 

在抽象类中,具备如下特色

  • 如果一个类中有形象办法,那么这个类肯定是抽象类,也就是说,应用关键字 abstract 润饰的办法肯定是形象办法,具备形象办法的类肯定是抽象类。实现类办法中只有办法具体的实现。
  • 抽象类中不肯定只有形象办法,抽象类中也能够有具体的办法,你能够本人去抉择是否实现这些办法。
  • 抽象类中的束缚不像接口那么严格,你能够在抽象类中定义 构造方法、形象办法、一般属性、办法、动态属性和静态方法
  • 抽象类和接口一样不能被实例化,实例化只能实例化 具体的类

异样

异样是程序常常会呈现的,发现错误的最佳时机是在编译阶段,也就是你试图在运行程序之前。然而,在编译期间并不能找到所有的谬误,有一些 NullPointerExceptionClassNotFoundException 异样在编译期找不到,这些异样是 RuntimeException 运行时异样,这些异样往往在运行时能力被发现。

咱们写 Java 程序常常会呈现两种问题,一种是 java.lang.Exception,一种是 java.lang.Error,都用来示意呈现了异常情况,上面就针对这两种概念进行了解。

意识 Exception

Exception 位于 java.lang 包下,它是一种顶级接口,继承于 Throwable 类,Exception 类及其子类都是 Throwable 的组成条件,是程序呈现的正当状况。

在意识 Exception 之前,有必要先理解一下什么是 Throwable

什么是 Throwable

Throwable 类是 Java 语言中所有 谬误 (errors)异样 (exceptions) 的父类。只有继承于 Throwable 的类或者其子类才可能被抛出,还有一种形式是带有 Java 中的 @throw 注解的类也能够抛出。

在 Java 标准中,对非受查异样和受查异样的定义是这样的:

The unchecked exception classes are the run-time exception classes and the error classes.

The checked exception classes are all exception classes other than the unchecked exception classes. That is, the checked exception classes are Throwable and all its subclasses other than RuntimeException and its subclasses and Errorand its subclasses.

也就是说,除了 RuntimeException 和其子类,以及 error 和其子类,其它的所有异样都是 checkedException

那么,依照这种逻辑关系,咱们能够对 Throwable 及其子类进行归类剖析

能够看到,Throwable 位于异样和谬误的最顶层,咱们查看 Throwable 类中发现它的办法和属性有很多,咱们只探讨其中几个比拟罕用的

// 返回抛出异样的详细信息
public string getMessage();
public string getLocalizedMessage();

// 返回异样产生时的简要形容
public public String toString();// 打印异样信息到规范输入流上
public void printStackTrace();
public void printStackTrace(PrintStream s);
public void printStackTrace(PrintWriter s)

// 记录栈帧的的以后状态
public synchronized Throwable fillInStackTrace(); 

此外,因为 Throwable 的父类也是 Object,所以罕用的办法还有继承其父类的getClass()getName() 办法。

常见的 Exception

上面咱们回到 Exception 的探讨上来,当初你晓得了 Exception 的父类是 Throwable,并且 Exception 有两种异样,一种是 RuntimeException;一种是 CheckedException,这两种异样都应该去 捕捉

上面列出了一些 Java 中常见的异样及其分类,这块面试官也可能让你举出几个常见的异常情况并将其分类

RuntimeException

UncheckedException

与 Exception 无关的 Java 关键字

那么 Java 中是如何解决这些异样的呢?在 Java 中有这几个关键字 throws、throw、try、finally、catch 上面咱们别离来探讨一下

throws 和 throw

在 Java 中,异样也就是一个对象,它可能被程序员自定义抛出或者应用程序抛出,必须借助于 throwsthrow 语句来定义抛出异样。

throws 和 throw 通常是成对呈现的,例如

static void cacheException() throws Exception{throw new Exception();

} 

throw 语句用在办法体内,示意抛出异样,由办法体内的语句解决。
throws 语句用在办法申明前面,示意再抛出异样,由该办法的调用者来解决。

throws 次要是申明这个办法会抛出这种类型的异样,使它的调用者晓得要捕捉这个异样。
throw 是具体向外抛异样的动作,所以它是抛出一个异样实例。

try、finally、catch

这三个关键字次要有上面几种组合形式 try…catch、try…finally、try…catch…finally

try…catch 示意对某一段代码可能抛出异样进行的捕捉,如下

static void cacheException() throws Exception{

  try {System.out.println("1");
  }catch (Exception e){e.printStackTrace();
  }

} 

try…finally 示意对一段代码不论执行状况如何,都会走 finally 中的代码

static void cacheException() throws Exception{for (int i = 0; i < 5; i++) {System.out.println("enter: i=" + i);
    try {System.out.println("execute: i=" + i);
      continue;
    } finally {System.out.println("leave: i=" + i);
    }
  }
} 

try…catch…finally 也是一样的,示意对异样捕捉后,再走 finally 中的代码逻辑。

什么是 Error

Error 是程序无奈解决的谬误,示意运行应用程序中较重大问题。大多数谬误与代码编写者执行的操作无关,而示意代码运行时 JVM(Java 虚拟机)呈现的问题。这些谬误是不可查看的,因为它们在应用程序的管制和解决能力之 外,而且绝大多数是程序运行时不容许呈现的情况,比方 OutOfMemoryErrorStackOverflowError异样的呈现会有几种状况,这里须要先介绍一下 Java 内存模型 JDK1.7。

其中包含两局部,由所有线程共享的数据区和线程隔离的数据区 组成,在下面的 Java 内存模型中,只有程序计数器 是不会产生 OutOfMemoryError 状况的区域,程序计数器管制着计算机指令的分支、循环、跳转、异样解决和线程复原,并且程序计数器是每个线程公有的。

什么是线程公有:示意的就是各条线程之间互不影响,独立存储的内存区域。

如果应用程序执行的是 Java 办法,那么这个计数器记录的就是 虚拟机字节码 指令的地址;如果正在执行的是 Native 办法,这个计数器值则为 空(Undefined)

除了程序计数器外,其余区域:办法区 (Method Area) 虚拟机栈 (VM Stack) 本地办法栈 (Native Method Stack) 堆(Heap) 都是可能产生 OutOfMemoryError 的区域。

  • 虚拟机栈:如果线程申请的栈深度大于虚拟机栈所容许的深度,将会呈现 StackOverflowError 异样;如果虚拟机动静扩大无奈申请到足够的内存,将呈现 OutOfMemoryError
  • 本地办法栈和虚拟机栈一样
  • 堆:Java 堆能够处于物理上不间断,逻辑上间断,就像咱们的磁盘空间一样,如果堆中没有内存实现实例调配,并且堆无奈扩大时,将会抛出 OutOfMemoryError。
  • 办法区:办法区无奈满足内存调配需要时,将抛出 OutOfMemoryError 异样。

在 Java 中,你能够把异样了解为是一种可能进步你程序健壮性的机制,它可能让你在编写代码中留神这些问题,也能够说,如果你写代码不会留神这些异常情况,你是无奈成为一位硬核程序员的。

外部类

距今为止,咱们理解的都是一般类的定义,那就是间接在 IDEA 中间接新建一个 class。

新建实现后,你就会领有一个 class 文件的定义,这种操作太简略了,工夫长了就会干燥,咱们年轻人多须要更新潮和骚气的写法,好吧,既然你提到了那就应用 外部类 吧,这是一种有用而且骚气的定义类的形式,外部类的定义非常简单:能够将一个类的定义放在另一个类的外部,这就是外部类

外部类是一种十分有用的个性,定义在类外部的类,持有外部类的援用,但却对其余外部类不可见,看起来就像是一种暗藏代码的机制,就和 弗兰奇将军 似的,弗兰奇能够和弗兰奇将军进行通信,然而里面的敌人却无奈间接攻打到弗兰奇本体。

上面咱们就来聊一聊创立外部类的形式。

创立外部类

定义外部类非常简单,就是间接将一个类定义在外围类的外面,如下代码所示

public class OuterClass {
    private String name ;
    private int age;
    
    class InnerClass{public InnerClass(){
            name = "cxuan";
            age = 25;
        }
    }
} 

在这段代码中,InnerClass 就是 OuterClass 的一个外部类。也就是说,每个外部类都能独立地继承一个(接口的)实现,所以无论外围类是否曾经继承了某个(接口的)实现,对于外部类都没有影响。这也是暗藏了外部实现细节。外部类领有外部类的拜访权

外部类不仅仅可能定义在类的外部,还能够定义在办法和作用域外部,这种被称为 部分外部类 ,除此之外,还有匿名外部类、外部类能够实现 Java 中的 多重继承。上面是定义外部类的形式

  • 一个在办法中定义的类(部分外部类) 
  • 一个定义在作用域内的类,这个作用域在办法的外部(成员外部类) 
  • 一个实现了接口的匿名类(匿名外部类) 
  • 一个匿名类,它扩大了非默认结构器的类 
  • 一个匿名类,执行字段初始化操作 
  • 一个匿名类,它通过实例初始化实现结构 

因为每个类都会产生一个 .class 文件,其中蕴含了如何创立该类型的对象的全副信息,那么,如何示意外部类的信息呢?能够应用 $ 来示意,比方 OuterClass$InnerClass.class

汇合

汇合在咱们的日常开发中所应用的次数几乎太多了,你曾经把它们都用的熟透了,然而作为一名合格的程序员,你不仅要理解它的根本用法,你还要理解它的源码;存在即正当,你还要理解它是如何设计和实现的,你还要理解它的衍生过程。

这篇博客就来具体介绍一下 Collection 这个宏大汇合框架的家族体系和成员,让你理解它的设计与实现。

是时候祭出这张神图了

首先来介绍的就是列表爷爷辈儿的接口 - Iterator

Iterable 接口

实现此接口容许对象成为 for-each 循环的指标,也就是加强 for 循环,它是 Java 中的一种 语法糖

List<Object> list = new ArrayList();
for (Object obj: list){} 

除了实现此接口的对象外,数组也能够用 for-each 循环遍历,如下:

Object[] list = new Object[10];
for (Object obj: list){} 

其余遍历形式

jdk 1.8 之前 Iterator 只有 iterator 一个办法,就是

Iterator<T> iterator(); 

实现次接口的办法可能创立一个轻量级的迭代器,用于平安的遍历元素,移除元素,增加元素。这外面波及到一个 fail-fast 机制。

总之一点就是能创立迭代器进行元素的增加和删除的话,就尽量应用迭代器进行增加和删除。

也能够应用迭代器的形式进行遍历

for(Iterator it = coll.iterator(); it.hasNext();){System.out.println(it.next());
} 

顶层接口

Collection 是一个顶层接口,它次要用来定义汇合的约定

List 接口也是一个顶层接口,它继承了 Collection 接口,同时也是 ArrayList、LinkedList 等汇合元素的父类

Set 接口位于与 List 接口同级的档次上,它同时也继承了 Collection 接口。Set 接口提供了额定的规定。它对 add、equals、hashCode 办法提供了额定的规范。

Queue 是和 List、Set 接口并列的 Collection 的三大接口之一。Queue 的设计用来在解决之前放弃元素的拜访秩序。除了 Collection 根底的操作之外,队列提供了额定的插入,读取,查看操作。

SortedSet 接口间接继承于 Set 接口,应用 Comparable 对元素进行天然排序或者应用 Comparator 在创立时对元素提供定制的排序规定。set 的迭代器将按升序元素程序遍历汇合。

Map 是一个反对 key-value 存储的对象,Map 不能蕴含反复的 key,每个键最多映射一个值。这个接口代替了 Dictionary 类,Dictionary 是一个抽象类而不是接口。

ArrayList

ArrayList 是实现了 List 接口的 可扩容数组(动静数组),它的外部是基于数组实现的。它的具体定义如下:

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {...} 
  • ArrayList 能够实现所有可抉择的列表操作,容许所有的元素,包含空值。ArrayList 还提供了外部存储 list 的办法,它可能齐全代替 Vector,只有一点例外,ArrayList 不是线程平安的容器。
  • ArrayList 有一个容量的概念,这个数组的容量就是 List 用来存储元素的容量。
  • ArrayList 不是线程平安的容器,如果多个线程中至多有两个线程批改了 ArrayList 的构造的话就会导致线程平安问题,作为代替条件能够应用线程平安的 List,应应用 Collections.synchronizedList
List list = Collections.synchronizedList(new ArrayList(...)) 
  • ArrayList 具备 fail-fast 疾速失败机制,可能对 ArrayList 作出失败检测。当在迭代汇合的过程中该汇合在结构上产生扭转的时候,就有可能会产生 fail-fast,即抛出 ConcurrentModificationException 异样。

Vector

Vector 同 ArrayList 一样,都是基于数组实现的,只不过 Vector 是一个线程平安的容器,它对外部的每个办法都简略粗犷的上锁,防止多线程引起的安全性问题,然而通常这种同步形式须要的开销比拟大,因而,拜访元素的效率要远远低于 ArrayList。

还有一点在于扩容上,ArrayList 扩容后的数组长度会减少 50%,而 Vector 的扩容长度后数组会增加一倍。

LinkedList 类

LinkedList 是一个双向链表,容许存储任何元素(包含 null)。它的次要个性如下:

  • LinkedList 所有的操作都能够体现为双向性的,索引到链表的操作将遍历从头到尾,视哪个间隔近为遍历程序。
  • 留神这个实现也不是线程平安的,如果多个线程并发拜访链表,并且至多其中的一个线程批改了链表的构造,那么这个链表必须进行内部加锁。或者应用
List list = Collections.synchronizedList(new LinkedList(...)) 

Stack

堆栈是咱们常说的 后入先出 (吃了吐) 的容器。它继承了 Vector 类,提供了通常用的 push 和 pop 操作,以及在栈顶的 peek 办法,测试 stack 是否为空的 empty 办法,和一个寻找与栈顶间隔的 search 办法。

第一次创立栈,不蕴含任何元素。一个更欠缺,可靠性更强的 LIFO 栈操作由 Deque 接口和他的实现提供,应该优先应用这个类

Deque<Integer> stack = new ArrayDeque<Integer>() 

HashSet

HashSet 是 Set 接口的实现类,由哈希表反对(实际上 HashSet 是 HashMap 的一个实例)。它不能保障汇合的迭代程序。这个类容许 null 元素。

  • 留神这个实现不是线程平安的。如果多线程并发拜访 HashSet,并且至多一个线程批改了 set,必须进行内部加锁。或者应用 Collections.synchronizedSet() 办法重写。
  • 这个实现反对 fail-fast 机制。

TreeSet

TreeSet 是一个基于 TreeMap 的 NavigableSet 实现。这些元素应用他们的天然排序或者在创立时提供的 Comparator 进行排序,具体取决于应用的构造函数。

  • 此实现为基本操作 add,remove 和 contains 提供了 log(n) 的工夫老本。
  • 留神这个实现不是线程平安的。如果多线程并发拜访 TreeSet,并且至多一个线程批改了 set,必须进行内部加锁。或者应用
SortedSet s = Collections.synchronizedSortedSet(new TreeSet(...)) 
  • 这个实现持有 fail-fast 机制。

LinkedHashSet 类

LinkedHashSet 继承于 Set,先来看一下 LinkedHashSet 的继承体系:

LinkedHashSet 是 Set 接口的 Hash 表和 LinkedList 的实现。这个实现不同于 HashSet 的是它保护着一个贯通所有条目标双向链表。此链表定义了元素插入汇合的程序。留神:如果元素从新插入,则插入程序不会受到影响。

  • LinkedHashSet 有两个影响其形成的参数:初始容量和加载因子。它们的定义与 HashSet 完全相同。但请留神:对于 LinkedHashSet,抉择过高的初始容量值的开销要比 HashSet 小,因为 LinkedHashSet 的迭代次数不受容量影响。
  • 留神 LinkedHashSet 也不是线程平安的,如果多线程同时拜访 LinkedHashSet,必须加锁,或者通过应用
Collections.synchronizedSet 
  • 该类也反对 fail-fast 机制

PriorityQueue

PriorityQueue 是 AbstractQueue 的实现类,优先级队列的元素依据天然排序或者通过在构造函数期间提供 Comparator 来排序,具体依据结构器判断。PriorityQueue 不容许 null 元素。

  • 队列的头在某种意义上是指定程序的最初一个元素。队列查找操作 poll,remove,peek 和 element 拜访队列头部元素。
  • 优先级队列是无限度的,但具备外部 capacity,用于管制用于在队列中存储元素的数组大小。
  • 该类以及迭代器实现了 Collection、Iterator 接口的所有可选办法。这个迭代器提供了 iterator() 办法不能保障以任何特定程序遍历优先级队列的元素。如果你须要有序遍历,思考应用 Arrays.sort(pq.toArray())
  • 留神这个实现不是线程平安的,多线程不应该并发拜访 PriorityQueue 实例如果有某个线程批改了队列的话,应用线程平安的类 PriorityBlockingQueue

HashMap

HashMap 是一个利用哈希表原理来存储元素的汇合,并且容许空的 key-value 键值对。HashMap 是非线程平安的,也就是说在多线程的环境下,可能会存在问题,而 Hashtable 是线程平安的容器。HashMap 也反对 fail-fast 机制。HashMap 的实例有两个参数影响其性能:初始容量 和加载因子。能够应用 Collections.synchronizedMap(new HashMap(...)) 来结构一个线程平安的 HashMap。

TreeMap 类

一个基于 NavigableMap 实现的红黑树。这个 map 依据 key 天然排序存储,或者通过 Comparator 进行定制排序。

  • TreeMap 为 containsKey,get,put 和 remove 办法提供了 log(n) 的工夫开销。
  • 留神这个实现不是线程平安的。如果多线程并发拜访 TreeMap,并且至多一个线程批改了 map,必须进行内部加锁。这通常通过在天然封装汇合的某个对象上进行同步来实现,或者应用 SortedMap m = Collections.synchronizedSortedMap(new TreeMap(...))
  • 这个实现持有 fail-fast 机制。

LinkedHashMap 类

LinkedHashMap 是 Map 接口的哈希表和链表的实现。这个实现与 HashMap 不同之处在于它保护了一个贯通其所有条目标双向链表。这个链表定义了遍历程序,通常是插入 map 中的程序。

  • 它提供一个非凡的 LinkedHashMap(int,float,boolean) 结构器来创立 LinkedHashMap,其遍历程序是其最初一次拜访的程序。
  • 能够重写 removeEldestEntry(Map.Entry) 办法,以便在将新映射增加到 map 时强制删除过期映射的策略。
  • 这个类提供了所有可抉择的 map 操作,并且容许 null 元素。因为保护链表的额定开销,性能可能会低于 HashMap,有一条除外:遍历 LinkedHashMap 中的 collection-views 须要与 map.size 成正比,无论其容量如何。HashMap 的迭代看起来开销更大,因为还要求工夫与其容量成正比。
  • LinkedHashMap 有两个因素影响了它的形成:初始容量和加载因子。
  • 留神这个实现不是线程平安的。如果多线程并发拜访 LinkedHashMap,并且至多一个线程批改了 map,必须进行内部加锁。这通常通过在天然封装汇合的某个对象上进行同步来实现 Map m = Collections.synchronizedMap(new LinkedHashMap(...))
  • 这个实现持有 fail-fast 机制。

Hashtable 类

Hashtable 类实现了一个哈希表,可能将键映射到值。任何非空对象都能够用作键或值。

  • 此实现类反对 fail-fast 机制
  • 与新的汇合实现不同,Hashtable 是线程平安的。如果不须要线程平安的容器,举荐应用 HashMap,如果须要多线程高并发,举荐应用 ConcurrentHashMap

IdentityHashMap 类

IdentityHashMap 是比拟小众的 Map 实现了。

  • 这个类不是一个通用的 Map 实现!尽管这个类实现了 Map 接口,但它成心违反了 Map 的约定,该约定要求在比拟对象时应用 equals 办法,此类仅实用于须要援用相等语义的极少数状况。
  • 同 HashMap,IdentityHashMap 也是无序的,并且该类不是线程平安的,如果要使之线程平安,能够调用 Collections.synchronizedMap(new IdentityHashMap(...)) 办法来实现。
  • 反对 fail-fast 机制

WeakHashMap 类

WeakHashMap 类基于哈希表的 Map 根底实现,带有弱键。WeakHashMap 中的 entry 当不再应用时还会主动移除。更精确的说,给定 key 的映射的存在将不会阻止 key 被垃圾收集器抛弃。

  • 基于 map 接口,是一种弱键相连,WeakHashMap 外面的键会主动回收
  • 反对 null 值和 null 键。
  • fast-fail 机制
  • 不容许反复
  • WeakHashMap 常常用作缓存

Collections 类

Collections 不属于 Java 框架继承树上的内容,它属于独自的分支,Collections 是一个包装类,它的作用就是为汇合框架提供某些性能实现,此类只包含静态方法操作或者返回 collections。

同步包装

同步包装器将主动同步(线程安全性)增加到任意汇合。六个外围汇合接口(Collection,Set,List,Map,SortedSet 和 SortedMap)中的每一个都有一个动态工厂办法。

public static  Collection synchronizedCollection(Collection c);
public static  Set synchronizedSet(Set s);
public static  List synchronizedList(List list);
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m);
public static  SortedSet synchronizedSortedSet(SortedSet s);
public static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m); 

不可批改的包装

不可批改的包装器通过拦挡批改汇合的操作并抛出 UnsupportedOperationException,次要用在上面两个情景:

  • 构建汇合后使其不可变。在这种状况下,最好不要去获取返回 collection 的援用,这样有利于保障不变性
  • 容许某些客户端以只读形式拜访你的数据结构。你保留对返回的 collection 的援用,但散发对包装器的援用。通过这种形式,客户能够查看但不能批改,同时放弃齐全拜访权限。

这些办法是:

public static  Collection unmodifiableCollection(Collection<? extends T> c);
public static  Set unmodifiableSet(Set<? extends T> s);
public static  List unmodifiableList(List<? extends T> list);
public static <K,V> Map<K, V> unmodifiableMap(Map<? extends K, ? extends V> m);
public static  SortedSet unmodifiableSortedSet(SortedSet<? extends T> s);
public static <K,V> SortedMap<K, V> unmodifiableSortedMap(SortedMap<K, ? extends V> m); 

线程平安的 Collections

Java1.5 并发包 (java.util.concurrent) 提供了线程平安的 collections 容许遍历的时候进行批改,通过设计 iterator 为 fail-fast 并抛出 ConcurrentModificationException。一些实现类是CopyOnWriteArrayListConcurrentHashMapCopyOnWriteArraySet

Collections 算法

此类蕴含用于汇合框架算法的办法,例如二进制搜寻,排序,重排,反向等。

汇合实现类特色图

下图汇总了局部汇合框架的次要实现类的特色图,让你能有清晰明了看出每个实现类之间的差异性

还有一种类型是对于强援用、弱援用、虚援用的文章,请参考

https://mp.weixin.qq.com/s/ZflBpn2TBzTNv_-G-zZxNg

泛形

在 Jdk1.5 中,提出了一种新的概念,那就是泛型,那么什么是泛型呢?

泛型其实就是一种参数化的汇合,它限度了你增加进汇合的类型。泛型的实质就是一种参数化类型。多态也能够看作是泛型的机制。一个类继承了父类,那么就能通过它的父类找到对应的子类,然而不能通过其余类来找到具体要找的这个类。泛型的设计之处就是心愿对象或办法具备最宽泛的表达能力。

上面来看一个例子阐明没有泛型的用法

List arrayList = new ArrayList();
arrayList.add("cxuan");
arrayList.add(100);

for(int i = 0; i< arrayList.size();i++){String item = (String)arrayList.get(i);
        System.out.println("test ===", item);
} 

这段程序不能失常运行,起因是 Integer 类型不能间接强制转换为 String 类型

java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String 

如果咱们用泛型进行改写后,示例代码如下

List<String> arrayList = new ArrayList<String>();

arrayList.add(100); 

这段代码在编译期间就会报错,编译器会在编译阶段就可能帮咱们发现相似这样的问题。

泛型的应用

泛型的应用有多种形式,上面咱们就来一起探讨一下。

用泛型示意类

泛型能够加到类下面,来示意这个类的类型

// 此处 T 能够轻易写为任意标识,常见的如 T、E、K、V 等模式的参数罕用于示意泛型
public class GenericDemo<T>{ 
    //value 这个成员变量的类型为 T,T 的类型由内部指定 
    private T value;

    public GenericDemo(T value) {this.value = value;}

    public T getValue(){ // 泛型办法 getKey 的返回值类型为 T,T 的类型由内部指定
        return value;
    }
 
         public void setValue(T value){this.value = value}
} 

用泛型示意接口

泛型接口与泛型类的定义及应用基本相同。

// 定义一个泛型接口
public interface Generator<T> {public T next();
} 

个别泛型接口罕用于 生成器(generator) 中,生成器相当于对象工厂,是一种专门用来创建对象的类。

泛型办法

能够应用泛型来示意办法

public class GenericMethods {public <T> void f(T x){System.out.println(x.getClass().getName());
  }
} 

泛型通配符

List 是泛型类,为了 示意各种泛型 List 的父类,能够应用类型通配符,类型通配符应用 问号 (?) 示意,它的元素类型能够匹配任何类型。例如

public static void main(String[] args) {List<String> name = new ArrayList<String>();
    List<Integer> age = new ArrayList<Integer>();
    List<Number> number = new ArrayList<Number>();
    name.add("cxuan");
    age.add(18);
    number.add(314);
    generic(name);
    generic(age);
    generic(number);   
}

public static void generic(List<?> data) {System.out.println("Test cxuan :" + data.get(0));
} 

下界通配符 : <? extends ClassType> 该通配符为 ClassType 的所有子类型。它示意的是任何类型都是 ClassType 类型的子类。

上界通配符:<? super ClassType> 该通配符为 ClassType 的所有超类型。它示意的是任何类型的父类都是 ClassType。

反射

反射是 Java 中一个十分重要同时也是一个高级个性,基本上 Spring 等一系列框架都是基于反射的思维写成的。咱们首先来认识一下什么反射。

Java 反射机制是在程序的运行过程中,对于任何一个类,都可能晓得它的所有属性和办法;对于任意一个对象,都可能晓得调用它的任意属性和办法,这种动静获取信息以及动静调用对象办法的性能称为 java 语言的反射机制。(来源于百度百科)

Java 反射机制次要提供了以下这几个性能

  • 在运行时判断任意一个对象所属的类
  • 在运行时结构任意一个类的对象
  • 在运行时判断任意一个类所有的成员变量和办法
  • 在运行时调用任意一个对象的办法

这么一看,反射就像是一个掌控全局的角色,不论你程序怎么运行,我都可能晓得你这个类有哪些属性和办法,你这个对象是由谁调用的,嗯,很屌。

在 Java 中,应用 Java.lang.reflect包实现了反射机制。Java.lang.reflect 所设计的类如下

上面是一个简略的反射类

public class Person {
    public String name;// 姓名
    public int age;// 年龄
 
    public Person() {super();
    }
 
    public Person(String name, int age) {super();
        this.name = name;
        this.age = age;
    }
 
    public String showInfo() {return "name=" + name + ", age=" + age;}
}

public class Student extends Person implements Study {
    public String className;// 班级
    private String address;// 住址
 
    public Student() {super();
    }
 
    public Student(String name, int age, String className, String address) {super(name, age);
        this.className = className;
        this.address = address;
    }
 
    public Student(String className) {this.className = className;}
 
    public String toString() {
        return "姓名:" + name + ", 年龄:" + age + ", 班级:" + className + ", 住址:"
                + address;
    }
 
    public String getAddress() {return address;}
 
    public void setAddress(String address) {this.address = address;}
}

public class TestRelect {public static void main(String[] args) {
        Class student = null;
        try {student = Class.forName("com.cxuan.reflection.Student");
        } catch (ClassNotFoundException e) {e.printStackTrace();
        }
 
        // 获取对象的所有私有属性。Field[] fields = student.getFields();
        for (Field f : fields) {System.out.println(f);
        }
        System.out.println("---------------------");
        // 获取对象所有属性,但不蕴含继承的。Field[] declaredFields = student.getDeclaredFields();
        for (Field df : declaredFields) {System.out.println(df);
        }
      
          // 获取对象的所有公共办法
        Method[] methods = student.getMethods();
        for (Method m : methods) {System.out.println(m);
        }
        System.out.println("---------------------");
        // 获取对象所有办法,但不蕴含继承的
        Method[] declaredMethods = student.getDeclaredMethods();
        for (Method dm : declaredMethods) {System.out.println(dm);
        }
                
          // 获取对象所有的公共构造方法
        Constructor[] constructors = student.getConstructors();
        for (Constructor c : constructors) {System.out.println(c);
        }
        System.out.println("---------------------");
        // 获取对象所有的构造方法
        Constructor[] declaredConstructors = student.getDeclaredConstructors();
        for (Constructor dc : declaredConstructors) {System.out.println(dc);
        }
      
          Class c = Class.forName("com.cxuan.reflection.Student");
          Student stu1 = (Student) c.newInstance();
          // 第一种办法,实例化默认构造方法,调用 set 赋值
        stu1.setAddress("河北石家庄");
        System.out.println(stu1);

        // 第二种办法 获得全副的构造函数 应用构造函数赋值
        Constructor<Student> constructor = c.getConstructor(String.class, 
                                                            int.class, String.class, String.class);
        Student student2 = (Student) constructor.newInstance("cxuan", 24, "六班", "石家庄");
        System.out.println(student2);

        /**
        * 獲取办法并执行办法
        */
        Method show = c.getMethod("showInfo");// 获取 showInfo()办法
        Object object = show.invoke(stu2);// 调用 showInfo()办法}
} 

有一些是比拟罕用的,有一些是我至今都没见过怎么用的,上面进行一个归类。

与 Java 反射无关的类次要有

Class 类

在 Java 中,你每定义一个 java class 实体都会产生一个 Class 对象。也就是说,当咱们编写一个类,编译实现后,在生成的 .class 文件中,就会产生一个 Class 对象,这个 Class 对象用于示意这个类的类型信息。Class 中没有公共的结构器,也就是说 Class 对象不能被实例化。上面来简略看一下 Class 类都包含了哪些办法

toString()

public String toString() {return (isInterface() ? "interface" : (isPrimitive() ? "":"class "))
    + getName();} 

toString() 办法可能将对象转换为字符串,toString() 首先会判断 Class 类型是否是接口类型,也就是说,一般类和接口都可能用 Class 对象来示意,而后再判断是否是根本数据类型,这里判断的都是根本数据类型和包装类,还有 void 类型。

所有的类型如下

  • java.lang.Boolean : 代表 boolean 数据类型的包装类
  • java.lang.Character: 代表 char 数据类型的包装类
  • java.lang.Byte: 代表 byte 数据类型的包装类
  • java.lang.Short: 代表 short 数据类型的包装类
  • java.lang.Integer: 代表 int 数据类型的包装类
  • java.lang.Long: 代表 long 数据类型的包装类
  • java.lang.Float: 代表 float 数据类型的包装类
  • java.lang.Double: 代表 double 数据类型的包装类
  • java.lang.Void: 代表 void 数据类型的包装类

而后是 getName() 办法,这个办法返回类的全限定名称。

  • 如果是援用类型,比方 String.class.getName() -> java.lang.String
  • 如果是根本数据类型,byte.class.getName() -> byte
  • 如果是数组类型,new Object[3]).getClass().getName() -> [Ljava.lang.Object

toGenericString()

这个办法会返回类的全限定名称,而且包含类的修饰符和类型参数信息。

forName()

依据类名取得一个 Class 对象的援用,这个办法会使类对象进行初始化。

例如 Class t = Class.forName("java.lang.Thread") 就可能初始化一个 Thread 线程对象

在 Java 中,一共有三种获取类实例的形式

  • Class.forName(java.lang.Thread)
  • Thread.class
  • thread.getClass()

newInstance()

创立一个类的实例,代表着这个类的对象。下面 forName() 办法对类进行初始化,newInstance 办法对类进行实例化。

getClassLoader()

获取类加载器对象。

getTypeParameters()

依照申明的程序获取对象的参数类型信息。

getPackage()

返回类的包

getInterfaces()

取得以后类实现的类或是接口,可能是有多个,所以返回的是 Class 数组。

Cast

把对象转换成代表类或是接口的对象

asSubclass(Class clazz)

把传递的类的对象转换成代表其子类的对象

getClasses()

返回一个数组,数组中蕴含该类中所有公共类和接口类的对象

getDeclaredClasses()

返回一个数组,数组中蕴含该类中所有类和接口类的对象

getSimpleName()

取得类的名字

getFields()

取得所有私有的属性对象

getField(String name)

取得某个私有的属性对象

getDeclaredField(String name)

取得某个属性对象

getDeclaredFields()

取得所有属性对象

getAnnotation(Class annotationClass)

返回该类中与参数类型匹配的私有注解对象

getAnnotations()

返回该类所有的私有注解对象

getDeclaredAnnotation(Class annotationClass)

返回该类中与参数类型匹配的所有注解对象

getDeclaredAnnotations()

返回该类所有的注解对象

getConstructor(Class…<?> parameterTypes)

取得该类中与参数类型匹配的私有构造方法

getConstructors()

取得该类的所有私有构造方法

getDeclaredConstructor(Class…<?> parameterTypes)

取得该类中与参数类型匹配的构造方法

getDeclaredConstructors()

取得该类所有构造方法

getMethod(String name, Class…<?> parameterTypes)

取得该类某个私有的办法

getMethods()

取得该类所有私有的办法

getDeclaredMethod(String name, Class…<?> parameterTypes)

取得该类某个办法

getDeclaredMethods()

取得该类所有办法

Field 类

Field 类提供类或接口中独自字段的信息,以及对独自字段的动静拜访。

这里就不再对具体的办法进行介绍了,读者有趣味能够参考官网 API

这里只介绍几个罕用的办法

equals(Object obj)

属性与 obj 相等则返回 true

get(Object obj)

取得 obj 中对应的属性值

set(Object obj, Object value)

设置 obj 中对应属性值

Method 类

invoke(Object obj, Object… args)

传递 object 对象及参数调用该对象对应的办法

ClassLoader 类

反射中,还有一个十分重要的类就是 ClassLoader 类,类装载器是用来把 类(class) 装载进 JVM 的。ClassLoader 应用的是双亲委托模型来搜寻加载类的,这个模型也就是双亲委派模型。ClassLoader 的类继承图如下

枚举

枚举可能是咱们应用次数比拟少的个性,在 Java 中,枚举应用 enum 关键字来示意,枚举其实是一项十分有用的个性,你能够把它了解为具备特定性质的类。enum 不仅仅 Java 有,C 和 C++ 也有枚举的概念。上面是一个枚举的例子。

public enum Family {

    FATHER,
    MOTHER,
    SON,
    Daughter;

} 

下面咱们创立了一个 Family的枚举类,它具备 4 个值,因为枚举类型都是常量,所以都用大写字母来示意。那么 enum 创立进去了,该如何援用呢?

public class EnumUse {public static void main(String[] args) {Family s = Family.FATHER;}
} 

枚举个性

enum 枚举这个类比拟有意思,当你创立完 enum 后,编译器会主动为你的 enum 增加 toString() 办法,可能让你不便的显示 enum 实例的具体名字是什么。除了 toString() 办法外,编译器还会增加 ordinal() 办法,这个办法用来示意 enum 常量的申明程序,以及 values() 办法显示程序的值。

public static void main(String[] args) {for(Family family : Family.values()){System.out.println(family + ", ordinal" + family.ordinal());
  }
} 

enum 能够进行动态导入包,动态导入包能够做到不必输出 枚举类名. 常量 ,能够间接应用常量,神奇吗? 应用 ennum 和 static 关键字能够做到动态导入包

下面代码导入的是 Family 中所有的常量,也能够独自指定常量。

枚举和一般类一样

枚举就和一般类一样,除了枚举中可能方便快捷的定义 常量,咱们日常开发应用的 public static final xxx 其实都能够用枚举来定义。在枚举中也可能定义属性和办法,千万不要把它看作是异类,它和万千的类一样。

public enum OrdinalEnum {WEST("live in west"),
    EAST("live in east"),
    SOUTH("live in south"),
    NORTH("live in north");

    String description;

    OrdinalEnum(String description){this.description = description;}

    public String getDescription() {return description;}

    public void setDescription(String description) {this.description = description;}

    public static void main(String[] args) {for(OrdinalEnum ordinalEnum : OrdinalEnum.values()){System.out.println(ordinalEnum.getDescription());
        }
    }
} 

个别 switch 能够和 enum 一起连用,来结构一个小型的状态转换机。

enum Signal {GREEN, YELLOW, RED}

public class TrafficLight {
    Signal color = Signal.RED;

    public void change() {switch (color) {
        case RED:
            color = Signal.GREEN;
            break;
        case YELLOW:
            color = Signal.RED;
            break;
        case GREEN:
            color = Signal.YELLOW;
            break;
        }
    }
} 

是不是代码登时感觉优雅整洁了些许呢?

枚举神秘之处

在 Java 中,万事万物都是对象,enum 尽管是个关键字,然而它却隐式的继承于 Enum 类。咱们来看一下 Enum 类,此类位于 java.lang 包下,能够主动援用。

此类的属性和办法都比拟少。你会发现这个类中没有咱们的 values 办法。后面刚说到,values() 办法是你应用枚举时被编译器增加进来的 static 办法。能够应用反射来验证一下

除此之外,enum 还和 Class 类有交加,在 Class 类中有三个对于 Enum 的办法

后面两个办法用于获取 enum 常量,isEnum 用于判断是否是枚举类型的。

枚举类

除了 Enum 外,还须要晓得两个对于枚举的工具类,一个是 EnumSet,一个是 EnumMap

EnumSet 和 EnumMap

EnumSet 是 JDK1.5 引入的,EnumSet 的设计充分考虑到了速度因素,应用 EnumSet 能够作为 Enum 的替代者,因为它的效率比拟高。

EnumMap 是一种非凡的 Map,它要求其中的 key 键值是来自一个 enum。因为 EnumMap 速度也很快,咱们能够应用 EnumMap 作为 key 的疾速查找。

总的来说,枚举的应用不是很简单,它也是 Java 中很小的一块性能,但有时却可能因为这一个小技巧,可能让你的代码变得优雅和整洁。

I/O

创立一个良好的 I/O 程序是非常复杂的。JDK 开发人员编写了大量的类只为了可能创立一个良好的工具包,想必编写 I/O 工具包很吃力吧。

IO 类设计进去,必定是为了解决 IO 相干操作的,最常见的 I/O 读写就是网络、磁盘等。在 Java 中,对文件的操作是一个典型的 I/O 操作。上面咱们就对 I/O 进行一个分类。

公号回复 IO获取思维导图

I/O 还能够依据操作对象来进行辨别:次要分为

除此之外,I/O 中还有其余比拟重要的类

File 类

File 类是对文件系统中文件以及文件夹进行操作的类,能够通过面向对象的思维操作文件和文件夹,是不是很神奇?

文件创建操作如下,次要波及 文件创建、删除文件、获取文件描述符等

class FileDemo{public static void main(String[] args) {File file = new File("D:file.txt");
       try{f.createNewFile(); // 创立一个文件
         
         // File 类的两个常量
         // 门路分隔符(与零碎无关的)<windows 外面是 ; linux 外面是:>
        System.out.println(File.pathSeparator);  //   ;
        // 与零碎无关的门路名称分隔符 <windows 外面是  linux 外面是 / >
        System.out.println(File.separator);      //  
         
         // 删除文件
         /*
         File file = new File(fileName);
         if(f.exists()){f.delete();
         }else{System.out.println("文件不存在");
         }   
         */

         
       }catch (Exception e) {e.printStackTrace();
       }
    }
} 

也能够对文件夹进行操作

class FileDemo{public static void main(String[] args) {
    String fileName = "D:"+ File.separator + "filepackage";
    File file = new File(fileName);
    f.mkdir();
    
        // 列出所有文件
    /*
    String[] str = file.list();
    for (int i = 0; i < str.length; i++) {System.out.println(str[i]);
    }
    */
    
    // 应用 file.listFiles(); 列出所有文件,包含暗藏文件
    
    // 应用 file.isDirectory() 判断指定门路是否是目录}
} 

下面只是举进去了两个简略的示例,实际上,还有一些其余对文件的操作没有应用。比方创立文件,就能够应用三种形式来创立

File(String directoryPath);
File(String directoryPath, String filename);
File(File dirObj, String filename); 

directoryPath 是文件的路径名,filename 是文件名,dirObj 是一个 File 对象。例如

File file = new File("D:javafile1.txt");  // 双是本义
System.out.println(file);
File file2 = new File("D:java","file2.txt");// 父门路、子门路 -- 能够实用于多个文件的!System.out.println(file2);
File parent = new File("D:java");
File file3 = new File(parent,"file3.txt");//File 类的父门路、子门路
System.out.println(file3); 

当初对 File 类进行总结

根底 IO 类和相干办法

尽管. IO 类有很多,然而最根本的是四个抽象类,InputStream、OutputStream、Reader、Writer。最根本的办法也就是 read()write() 办法,其余流都是下面这四类流的子类,办法也是通过这两类办法衍生而成的。而且大部分的 IO 源码都是 native 标记的,也就是说源码都是 C/C++ 写的。这里咱们先来认识一下这些流类及其办法

InputStream

InputStream 是一个定义了 Java 流式字节输出模式的抽象类。该类的所有办法在出错条件下引发一个 IOException 异样。它的次要办法定义如下

OutputStream

OutputStream 是定义了流式字节输入模式的抽象类。该类的所有办法返回一个 void 值并且在出错状况下引发一个 IOException 异样。它的次要办法定义如下

Reader 类

Reader 是 Java 定义的流式字符输出模式的抽象类。类中的办法在出错时引发 IOException 异样。

Writer 类

Writer 是定义流式字符输入的抽象类。所有该类的办法都返回一个 void 值并在出错条件下引发 IOException 异样

InputStream 及其子类

FileInputStream 文件输出流:FileInputStream 类创立一个能从文件读取字节的 InputStream 类

ByteArrayInputStream 字节数组输出流:把内存中的一个缓冲区作为 InputStream 应用

PipedInputStream 管道输出流:实现了 pipe 管道的概念,次要在线程中应用

SequenceInputStream 程序输出流:把多个 InputStream 合并为一个 InputStream

FilterOutputStream 过滤输出流:其余输出流的包装。

ObjectInputStream 反序列化输出流:将之前应用 ObjectOutputStream 序列化的原始数据复原为对象,以流的形式读取对象

DataInputStream : 数据输出流容许应用程序以与机器无关形式从底层输出流中读取根本 Java 数据类型。

PushbackInputStream 推回输出流 :缓冲的一个新鲜的用法是实现 推回(pushback)。Pushback 用于输出流容许字节被读取而后返回到流。

OutputStream 及其子类

FileOutputStream 文件输入流:该类实现了一个输入流,其数据写入文件。

ByteArrayOutputStream 字节数组输入流:该类实现了一个输入流,其数据被写入由 byte 数组充当的缓冲区,缓冲区会随着数据的一直写入而主动增长。

PipedOutputStream 管道输入流:管道的输入流,是管道的发送端。

ObjectOutputStream 根本类型输入流:该类将实现了序列化的对象序列化后写入指定中央。

FilterOutputStream 过滤输入流:其余输入流的包装。

PrintStream 打印流 通过 PrintStream 能够将文字打印到文件或者网络中去。

DataOutputStream : 数据输入流容许应用程序以与机器无关形式向底层输入流中写入根本 Java 数据类型。

Reader 及其子类

FileReader 文件字符输出流:把文件转换为字符流读入

CharArrayReader 字符数组输出流:是一个把字符数组作为源的输出流的实现

BufferedReader 缓冲区输出流:BufferedReader 类从字符输出流中读取文本并缓冲字符,以便无效地读取字符,数组和行

PushbackReader : PushbackReader 类容许一个或多个字符被送回输出流。

PipedReader 管道输出流:主要用途也是在线程间通信,不过这个能够用来传输字符

Writer 及其子类

FileWriter 字符输入流:FileWriter 创立一个能够写文件的 Writer 类。

CharArrayWriter 字符数组输入流:CharArrayWriter 实现了以数组作为指标的输入流。

BufferedWriter 缓冲区输入流 :BufferedWriter 是一个减少了flush() 办法的 Writer。flush() 办法能够用来确保数据缓冲器的确被写到理论的输入流。

PrintWriter :PrintWriter 实质上是 PrintStream 的字符模式的版本。

PipedWriter 管道输入流:主要用途也是在线程间通信,不过这个能够用来传输字符

Java 的输入输出的流式接口为简单而沉重的工作提供了一个简洁的形象。过滤流类的组合容许你动静建设客户端流式接口来配合数据传输要求。继承高级流类 InputStream、InputStreamReader、Reader 和 Writer 类的 Java 程序在未来(即便创立了新的和改良的具体类)也能失去正当使用。

注解

Java 注解(Annotation) 又称为 元数据,它为咱们在代码中增加信息提供了一种形式化的办法。它是 JDK1.5 引入的,Java 定义了一套注解,共有 7 个,3 个在 java.lang 中,剩下 4 个在 java.lang.annotation 中。

作用在代码中的注解有三个,它们别离是

  • @Override:重写标记,个别用在子类继承父类后,标注在重写过后的子类办法上。如果发现其父类,或者是援用的接口中并没有该办法时,会报编译谬误。
  • @Deprecated:用此注解正文的代码曾经过期,不再举荐应用
  • @SuppressWarnings:这个注解起到疏忽编译器的正告作用

元注解有四个,元注解就是用来标记注解的注解。它们别离是

  • @Retention: 标识如何存储,是只在代码中,还是编入 class 文件中,或者是在运行时能够通过反射拜访。

RetentionPolicy.SOURCE:注解只保留在源文件,当 Java 文件编译成 class 文件的时候,注解被遗弃;

RetentionPolicy.CLASS:注解被保留到 class 文件,但 jvm 加载 class 文件时候被遗弃,这是 默认的 生命周期;

RetentionPolicy.RUNTIME:注解不仅被保留到 class 文件中,jvm 加载 class 文件之后,依然存在;

  • @Documented: 标记这些注解是否蕴含在 JavaDoc 中。
  • @Target:标记这个注解阐明了 Annotation 所润饰的对象范畴,Annotation 可被用于 packages、types(类、接口、枚举、Annotation 类型)、类型成员(办法、构造方法、成员变量、枚举值)、办法参数和本地变量(如循环变量、catch 参数)。取值如下
public enum ElementType {
    TYPE,
    FIELD,
    METHOD,
    PARAMETER,
    CONSTRUCTOR,
    LOCAL_VARIABLE,
    ANNOTATION_TYPE,
    PACKAGE,
    TYPE_PARAMETER,
    TYPE_USE 
  • @Inherited:标记这个注解是继承于哪个注解类的。

从 JDK1.7 开始,又增加了三个额定的注解,它们别离是

  • @SafeVarargs:在申明可变参数的构造函数或办法时,Java 编译器会报 unchecked 正告。应用 @SafeVarargs 能够疏忽这些正告
  • @FunctionalInterface: 表明这个办法是一个函数式接口
  • @Repeatable:标识某注解能够在同一个申明上应用屡次。

留神:注解是不反对继承的。

对于 null 的几种解决形式

对于 Java 程序员来说,空指针始终是宜人的问题,咱们在开发中常常会受到 NullPointerException 的践踏和壁咚。Java 的发明者也抵赖这是一个微小的设计谬误。

那么对于 null,你应该晓得上面这几件事件来无效的理解 null,从而防止很多由 null 引起的谬误。

大小写敏感

首先,null 是 Java 中的 关键字 ,像是 public、static、final。 它是大小写敏感的,你不能将 null 写成 Null 或 NULL,编辑器将不能辨认它们而后报错。

这个问题曾经简直不会呈现,因为 eclipse 和 Idea 编译器曾经给出了编译器提醒,所以你不必思考这个问题。

null 是任何援用类型的初始值

null 是所有援用类型的默认值,Java 中的任何援用变量都将 null 作为默认值,也就是说所有 Object 类下的援用类型默认值都是 null。这对所有的援用变量都实用。就像是根本类型的默认值一样,例如 int 的默认值是 0,boolean 的默认值是 false。

上面是根本数据类型的初始值

null 只是一种非凡的值

null 既不是对象也不是一种类型,它仅是一种非凡的值,你能够将它赋予任何类型,你能够将 null 转换为任何类型

public static void main(String[] args) {
  String str = null;
  Integer itr = null;
  Double dou = null;

  Integer integer = (Integer) null;
  String string = (String)null;

  System.out.println("integer =" + integer);
  System.out.println("string =" + string);
} 

你能够看到在编译期和运行期内,将 null 转换成任何的援用类型都是可行的,并且不会抛出空指针异样。

null 只能赋值给援用变量,不能赋值给根本类型变量

持有 null 的包装类在进行主动拆箱的时候,不能实现转换,会抛出空指针异样,并且 null 也不能和根本数据类型进行比照

public static void main(String[] args) {
  int i = 0;
  Integer itr = null;
  System.out.println(itr == i);
} 

应用了带有 null 值的援用类型变量,instanceof 操作会返回 false

public static void main(String[] args) {
  Integer isNull = null;
  // instanceof = isInstance 办法
  if(isNull instanceof Integer){System.out.println("isNull is instanceof Integer");
  }else{System.out.println("isNull is not instanceof Integer");
  }
} 

这是 instanceof 操作符一个很重要的个性,使得对类型强制转换查看很有用

动态变量为 null 调用静态方法不会抛出 NullPointerException。因为静态方法应用了动态绑定

应用 Null-Safe 办法

你应该应用 null-safe 平安的办法,java 类库中有很多工具类都提供了静态方法,例如根本数据类型的包装类,Integer , Double 等。例如

public class NullSafeMethod {

    private static String number;

    public static void main(String[] args) {String s = String.valueOf(number);
        String string = number.toString();
        System.out.println("s =" + s);
        System.out.println("string =" + string);
    }
} 

number 没有赋值,所以默认为 null,应用String.value(number) 静态方法没有抛出空指针异样,然而应用 toString() 却抛出了空指针异样。所以尽量应用对象的静态方法。

null 判断

你能够应用 == 或者 != 操作来比拟 null 值,然而不能应用其余算法或者逻辑操作,例如小于或者大于。跟 SQL 不一样,在 Java 中 null == null 将返回 true,如下所示:

public class CompareNull {

    private static String str1;
    private static String str2;

    public static void main(String[] args) {System.out.println("str1 == str2 ?" + str1 == str2);
        System.out.println(null == null);
    }
} 

对于思维导图

我把一些罕用的 Java 工具包的思维导图做了汇总,不便读者查阅。

Java.IO

Java.lang

Java.math

Java.net

这本 PDF 的百度网盘我曾经给你放进去了,你能够间接下载

链接:https://pan.baidu.com/s/1mYAeS9hIhdMFh2rF3FDk0A 明码: p9rs

正文完
 0