怒肝俩月,新鲜出炉史上最有趣的Java小白手册,第一版(第一部分),每个 Java 初学者都应该收藏

这么说吧,在我眼里,Java 就是最流行的编程语言,没有之一(PHP 往一边站)。不仅岗位多,容易找到工作,关键是薪资水平也到位,不学 Java 亏得慌,对吧?

那可能零基础学编程的小伙伴就会头疼了,网上关于 Java 的大部分技术文章都不够幽默,不够风趣,不够系列,急需要一份能看得进去的学习手册,那我觉得我肝的这份手册正好符合要求,并且会一直持续更新下去。

第一版的内容暂时包含两方面,Java 基础和 Java 面向对象编程。来吧,先上目录,一睹为快。

01、Java 基本语法简介
02、Java 基本数据类型简介
03、Java main() 方法简介
04、Java 的流程控制语句
05、Java 包的简介
06、Java 到底是值传递还是引用传递
07、Java 的类和对象
08、Java 构造方法
09、Java 抽象类
10、Java 接口
11、Java 继承
12、this 关键字
13、super 关键字
14、重写和重载
15、static 关键字
16、Java 枚举
17、final 关键字

目录欣赏完了,接下来就是拜读精华内容的时间,搬个小板凳,认认真真好好学吧,学到就是赚到!

一、Java 基本语法简介

01、数据类型

Java 有 2 种数据类型,一种是基本数据类型,一种是引用类型。

基本数据类型用于存储简单类型的数据,比如说,int、long、byte、short 用于存储整数,float、double 用于存储浮点数,char 用于存储字符,boolean 用于存储布尔值。

不同的基本数据类型,有不同的默认值和大小,来个表格感受下。

数据类型 默认值 大小
boolean false 1比特
char '\u0000' 2字节
byte 0 1字节
short 0 2字节
int 0 4字节
long 0L 8字节
float 0.0f 4字节
double 0.0 8字节

引用类型用于存储对象(null 表示没有值的对象)的引用,String 是引用类型的最佳代表,比如说 String cmower = "沉默王二"

02、声明变量

要声明一个变量,必须指定它的名字和类型,来看一个简单的示例:

int age;
String name;

count 和 name 在声明后会得到一个默认值,按照它们的数据类型——不能是局部变量(否则 Java 编译器会在你使用变量的时候提醒要先赋值),必须是类成员变量。

public class SyntaxLocalVariable {
    int age;
    String name;

    public static void main(String[] args) {
        SyntaxLocalVariable syntax = new SyntaxLocalVariable();
        System.out.println(syntax.age); // 输出 0
        System.out.println(syntax.name);  // 输出 null
    }
}

也可以在声明一个变量后使用“=”操作符进行赋值,就像下面这样:

int age = 18;
String name = "沉默王二";

我们定义了 2 个变量,int 类型的 age 和 String 类型的 name,age 赋值 18,name 赋值为“沉默王二”。

每行代码后面都跟了一个“;”,表示当前语句结束了。

在 Java 中,变量最好遵守命名约定,这样能提高代码的可阅读性。

  • 以字母、下划线(_)或者美元符号($)开头
  • 不能使用 Java 的保留字,比如说 int 不能作为变量名

03、数组

数组在 Java 中占据着重要的位置,它是很多集合类的底层实现。数组属于引用类型,它用来存储一系列指定类型的数据。

声明数组的一般语法如下所示:

type[] identiier = new type[length];

type 可以是任意的基本数据类型或者引用类型。来看下面这个例子:

public class ArraysDemo {
    public static void main(String[] args) {
        int [] nums = new int[10];
        nums[0] = 18;
        nums[1] = 19;
        System.out.println(nums[0]);
    }
}

数组的索引从 0 开始,第一个元素的索引为 0,第二个元素的索引为 1。为什么要这样设计?感兴趣的话,你可以去探究一下。

通过变量名[索引]的方式可以访问数组指定索引处的元素,赋值或者取值是一样的。

04、关键字

关键字属于保留字,在 Java 中具有特殊的含义,比如说 public、final、static、new 等等,它们不能用来作为变量名。为了便于你作为参照,我列举了 48 个常用的关键字,你可以瞅一瞅。

  1. abstract: abstract 关键字用于声明抽象类——可以有抽象和非抽象方法。

  2. boolean: boolean 关键字用于将变量声明为布尔值类型,它只有 true 和 false 两个值。

  3. break: break 关键字用于中断循环或 switch 语句。

  4. byte: byte 关键字用于声明一个可以容纳 8 个比特的变量。

  5. case: case 关键字用于在 switch 语句中标记条件的值。

  6. catch: catch 关键字用于捕获 try 语句中的异常。

  7. char: char 关键字用于声明一个可以容纳无符号 16 位比特的 Unicode 字符的变量。

  8. class: class 关键字用于声明一个类。

  9. continue: continue 关键字用于继续下一个循环。它可以在指定条件下跳过其余代码。

  10. default: default 关键字用于指定 switch 语句中除去 case 条件之外的默认代码块。

  11. do: do 关键字通常和 while 关键字配合使用,do 后紧跟循环体。

  12. double: double 关键字用于声明一个可以容纳 64 位浮点数的变量。

  13. else: else 关键字用于指示 if 语句中的备用分支。

  14. enum: enum(枚举)关键字用于定义一组固定的常量。

  15. extends: extends 关键字用于指示一个类是从另一个类或接口继承的。

  16. final: final 关键字用于指示该变量是不可更改的。

  17. finally: finally 关键字和 try-catch 配合使用,表示无论是否处理异常,总是执行 finally 块中的代码。

  18. float: float 关键字用于声明一个可以容纳 32 位浮点数的变量。

  19. for: for 关键字用于启动一个 for 循环,如果循环次数是固定的,建议使用 for 循环。

  20. if: if 关键字用于指定条件,如果条件为真,则执行对应代码。

  21. implements: implements 关键字用于实现接口。

  22. import: import 关键字用于导入对应的类或者接口。

  23. instanceof: instanceof 关键字用于判断对象是否属于某个类型(class)。

  24. int: int 关键字用于声明一个可以容纳 32 位带符号的整数变量。

  25. interface: interface 关键字用于声明接口——只能具有抽象方法。

  26. long: long 关键字用于声明一个可以容纳 64 位整数的变量。

  27. native: native 关键字用于指定一个方法是通过调用本机接口(非 Java)实现的。

  28. new: new 关键字用于创建一个新的对象。

  29. null: 如果一个变量是空的(什么引用也没有指向),就可以将它赋值为 null。

  30. package: package 关键字用于声明类所在的包。

  31. private: private 关键字是一个访问修饰符,表示方法或变量只对当前类可见。

  32. protected: protected 关键字也是一个访问修饰符,表示方法或变量对同一包内的类和所有子类可见。

  33. public: public 关键字是另外一个访问修饰符,除了可以声明方法和变量(所有类可见),还可以声明类。main() 方法必须声明为 public。

  34. return: return 关键字用于在代码执行完成后返回(一个值)。

  35. short: short 关键字用于声明一个可以容纳 16 位整数的变量。

  36. static: static 关键字表示该变量或方法是静态变量或静态方法。

  37. strictfp: strictfp 关键字并不常见,通常用于修饰一个方法,确保方法体内的浮点数运算在每个平台上执行的结果相同。

  38. super: super 关键字可用于调用父类的方法或者变量。

  39. switch: switch 关键字通常用于三个(以上)的条件判断。

  40. synchronized: synchronized 关键字用于指定多线程代码中的同步方法、变量或者代码块。

  41. this: this 关键字可用于在方法或构造函数中引用当前对象。

  42. throw: throw 关键字主动抛出异常。

  43. throws: throws 关键字用于声明异常。

  44. transient: transient 关键字在序列化的使用用到,它修饰的字段不会被序列化。

  45. try: try 关键字用于包裹要捕获异常的代码块。

  46. void: void 关键字用于指定方法没有返回值。

  47. volatile: volatile 关键字保证了不同线程对它修饰的变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

  48. while: 如果循环次数不固定,建议使用 while 循环。

怒肝俩月,新鲜出炉史上最有趣的Java小白手册,第一版(第一部分),每个 Java 初学者都应该收藏_第1张图片

05、操作符

除去“=”赋值操作符,Java 中还有很多其他作用的操作符,我们来大致看一下。

①、算术运算符

  • +(加号)
  • –(减号)
  • *(乘号)
  • /(除号)
  • %(取余)

来看一个例子:

public class ArithmeticOperator {
    public static void main(String[] args) {
        int a = 10;
        int b = 5;
        
        System.out.println(a + b);//15  
        System.out.println(a - b);//5  
        System.out.println(a * b);//50  
        System.out.println(a / b);//2  
        System.out.println(a % b);//0  
    }
}

“+”号比较特殊,还可以用于字符串拼接,来看一个例子:

String result = "沉默王二" + "一枚有趣的程序员";

②、逻辑运算符

逻辑运算符通常用于布尔表达式,常见的有:

  • &&(AND)多个条件中只要有一个为 false 结果就为 false
  • ||(OR)多个条件只要有一个为 true 结果就为 true
  • !(NOT)条件如果为 true,加上“!”就为 false,否则,反之。

来看一个例子:

public class LogicalOperator {
    public static void main(String[] args) {
        int a=10;
        int b=5;
        int c=20;
        System.out.println(ab||a

③、比较运算符

  • < (小于)
  • <= (小于或者等于)
  • > (大于)
  • >= (大于或者等于)
  • == (相等)
  • != (不等)

06、程序结构

Java 中最小的程序单元叫做类,一个类可以有一个或者多个字段(也叫作成员变量),还可以有一个或者多个方法,甚至还可以有一些内部类。

如果一个类想要执行,就必须有一个 main 方法——程序运行的入口,就好像人的嘴一样,嗯,可以这么牵强的理解一下。

public class StructureProgram {
    public static void main(String[] args) {
        System.out.println("没有成员变量,只有一个 main 方法");
    }
}
  • 类名叫做 StructureProgram,在它里面,只有一个 main 方法。
  • {} 之间的代码称之为代码块。
  • 以上源代码将会保存在一个后缀名为 java 的文件中。

07、编译然后执行代码

通常,一些教程在介绍这块内容的时候,建议你通过命令行中先执行 javac 命令将源代码编译成字节码文件,然后再执行 java 命令指定代码。

但我不希望这个糟糕的局面再继续下去了——新手安装配置 JDK 真的蛮需要勇气和耐心的,稍有不慎,没入门就先放弃了。况且,在命令行中编译源代码会遇到很多莫名其妙的错误,这对新手是极其致命的——如果你再遇到这种老式的教程,可以吐口水了。

好的方法,就是去下载 IntelliJ IDEA,简称 IDEA,它被业界公认为最好的 Java 集成开发工具,尤其在智能代码助手、代码自动提示、代码重构、代码版本管理(Git、SVN、Maven)、单元测试、代码分析等方面有着亮眼的发挥。IDEA 产于捷克(位于东欧),开发人员以严谨著称。IDEA 分为社区版和付费版两个版本,新手直接下载社区版就足够用了。

安装成功后,可以开始敲代码了,然后直接右键运行(连保存都省了),结果会在 Run 面板中显示,如下图所示。

怒肝俩月,新鲜出炉史上最有趣的Java小白手册,第一版(第一部分),每个 Java 初学者都应该收藏_第2张图片

想查看反编译后的字节码的话,可以在 src 的同级目录 target/classes 的包路径下找到一个 StructureProgram.class 的文件(如果找不到的话,在目录上右键选择「Reload from Disk」)。

可以双击打开它。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.cmower.baeldung.basic;

public class StructureProgram {
    public StructureProgram() {
    }

    public static void main(String[] args) {
        System.out.println("没有成员变量,只有一个 main 方法");
    }
}

IDEA 默认会用 Fernflower 将 class 字节码反编译为我们可以看得懂的 Java 代码。实际上,class 字节码(请安装 show bytecode 插件)长下面这个样子:

// class version 57.65535 (-65479)
// access flags 0x21
public class com/cmower/baeldung/basic/StructureProgram {

  // compiled from: StructureProgram.java

  // access flags 0x1
  public ()V
   L0
    LINENUMBER 3 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object. ()V
    RETURN
   L1
    LOCALVARIABLE this Lcom/cmower/baeldung/basic/StructureProgram; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x9
  public static main([Ljava/lang/String;)V
   L0
    LINENUMBER 5 L0
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "\u6ca1\u6709\u6210\u5458\u53d8\u91cf\uff0c\u53ea\u6709\u4e00\u4e2a main \u65b9\u6cd5"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L1
    LINENUMBER 6 L1
    RETURN
   L2
    LOCALVARIABLE args [Ljava/lang/String; L0 L2 0
    MAXSTACK = 2
    MAXLOCALS = 1
}

新手看起来还是有些懵逼的,建议过过眼瘾就行了。

怒肝俩月,新鲜出炉史上最有趣的Java小白手册,第一版(第一部分),每个 Java 初学者都应该收藏_第3张图片

二、Java 基本数据类型简介

01、布尔

布尔(boolean)仅用于存储两个值:true 和 false,也就是真和假,通常用于条件的判断。代码示例:

boolean flag = true;

02、byte

byte 的取值范围在 -128 和 127 之间,包含 127。最小值为 -128,最大值为 127,默认值为 0。

在网络传输的过程中,为了节省空间,常用字节来作为数据的传输方式。代码示例:

byte a = 10;
byte b = -10;

03、short

short 的取值范围在 -32,768 和 32,767 之间,包含 32,767。最小值为 -32,768,最大值为 32,767,默认值为 0。代码示例:

short s = 10000;
short r = -5000;

04、int

int 的取值范围在 -2,147,483,648(-2 ^ 31)和 2,147,483,647(2 ^ 31 -1)(含)之间,默认值为 0。如果没有特殊需求,整形数据就用 int。代码示例:

int a = 100000;
int b = -200000;

05、long

long 的取值范围在 -9,223,372,036,854,775,808(-2^63) 和 9,223,372,036,854,775,807(2^63 -1)(含)之间,默认值为 0。如果 int 存储不下,就用 long,整形数据就用 int。代码示例:

long a = 100000L; 
long b = -200000L;

为了和 int 作区分,long 型变量在声明的时候,末尾要带上大写的“L”。不用小写的“l”,是因为小写的“l”容易和数字“1”混淆。

06、float

float 是单精度的浮点数,遵循 IEEE 754(二进制浮点数算术标准),取值范围是无限的,默认值为 0.0f。float 不适合用于精确的数值,比如说货币。代码示例:

float f1 = 234.5f;

为了和 double 作区分,float 型变量在声明的时候,末尾要带上小写的“f”。不需要使用大写的“F”,是因为小写的“f”很容易辨别。

07、double

double 是双精度的浮点数,遵循 IEEE 754(二进制浮点数算术标准),取值范围也是无限的,默认值为 0.0。double 同样不适合用于精确的数值,比如说货币。代码示例:

double d1 = 12.3

那精确的数值用什么表示呢?最好使用 BigDecimal,它可以表示一个任意大小且精度完全准确的浮点数。针对货币类型的数值,也可以先乘以 100 转成整形进行处理。

Tips:单精度是这样的格式,1 位符号,8 位指数,23 位小数,有效位数为 7 位。

怒肝俩月,新鲜出炉史上最有趣的Java小白手册,第一版(第一部分),每个 Java 初学者都应该收藏_第4张图片

双精度是这样的格式,1 位符号,11 位指数,52 为小数,有效位数为 16 位。

怒肝俩月,新鲜出炉史上最有趣的Java小白手册,第一版(第一部分),每个 Java 初学者都应该收藏_第5张图片

取值范围取决于指数位,计算精度取决于小数位(尾数)。小数位越多,则能表示的数越大,那么计算精度则越高。

一个数由若干位数字组成,其中影响测量精度的数字称作有效数字,也称有效数位。有效数字指科学计算中用以表示一个浮点数精度的那些数字。一般地,指一个用小数形式表示的浮点数中,从第一个非零的数字算起的所有数字。如 1.24 和 0.00124 的有效数字都有 3 位。

08、char

char 可以表示一个 16 位的 Unicode 字符,其值范围在 '\u0000'(0)和 '\uffff'(65,535)(包含)之间。代码示例:

char letterA = 'A'; // 用英文的单引号包裹住。

三、Java main() 方法简介

每个程序都需要一个入口,对于 Java 程序来说,入口就是 main 方法。

public static void main(String[] args) {}

public、static、void 这 3 个关键字在前面的内容已经介绍过了,如果觉得回去找比较麻烦的话,这里再贴一下:

  • public 关键字是另外一个访问修饰符,除了可以声明方法和变量(所有类可见),还可以声明类。main() 方法必须声明为 public。

  • static 关键字表示该变量或方法是静态变量或静态方法,可以直接通过类访问,不需要实例化对象来访问。

  • void 关键字用于指定方法没有返回值。

另外,main 关键字为方法的名字,Java 虚拟机在执行程序时会寻找这个标识符;args 为 main() 方法的参数名,它的类型为一个 String 数组,也就是说,在使用 java 命令执行程序的时候,可以给 main() 方法传递字符串数组作为参数。

java HelloWorld 沉默王二 沉默王三

javac 命令用来编译程序,java 命令用来执行程序,HelloWorld 为这段程序的类名,沉默王二和沉默王三为字符串数组,中间通过空格隔开,然后就可以在 main() 方法中通过 args[0]args[1] 获取传递的参数值了。

public class HelloWorld {
    public static void main(String[] args) {
        if ("沉默王二".equals(args[0])) {
            
        }
        
        if ("沉默王三".equals(args[1])) {
            
        }
    }
}

main() 方法的写法并不是唯一的,还有其他几种变体,尽管它们可能并不常见,可以简单来了解一下。

第二种,把方括号 [] 往 args 靠近而不是 String 靠近:

public static void main(String []args) { }

第三种,把方括号 [] 放在 args 的右侧:

public static void main(String args[]) { }

第四种,还可以把数组形式换成可变参数的形式:

public static void main(String...args) { }

第五种,在 main() 方法上添加另外一个修饰符 strictfp,用于强调在处理浮点数时的兼容性:

public strictfp static void main(String[] args) { }

也可以在 main() 方法上添加 final 关键字或者 synchronized 关键字。

第六种,还可以为 args 参数添加 final 关键字:

public static void main(final String[] args) { }

第七种,最复杂的一种,所有可以添加的关键字统统添加上:

final static synchronized strictfp void main(final String[] args) { }

当然了,并不需要为了装逼特意把 main() 方法写成上面提到的这些形式,使用 IDE 提供的默认形式就可以了。

四、Java 的流程控制语句

在 Java 中,有三种类型的流程控制语句:

  • 条件分支,用于在两个或者多个条件之间做出选择,常见的有 if/else/else if、三元运算符和 switch 语句。

  • 循环或者遍历,常见的有 for、while 和 do-while。

  • break 和 continue,用于跳出循环或者跳过进入下一轮循环。

if 语句

if 语句的格式如下:

if(布尔表达式){  
// 如果条件为 true,则执行这块代码
} 

画个流程图表示一下:

怒肝俩月,新鲜出炉史上最有趣的Java小白手册,第一版(第一部分),每个 Java 初学者都应该收藏_第6张图片

来写个示例:

public class IfExample {
    public static void main(String[] args) {
        int age = 20;
        if (age < 30) {
            System.out.println("青春年华");
        }
    }
}

输出:

青春年华

if-else 语句

if-else 语句的格式如下:

if(布尔表达式){  
// 条件为 true 时执行的代码块
}else{  
// 条件为 false  时执行的代码块
}  

画个流程图表示一下:

怒肝俩月,新鲜出炉史上最有趣的Java小白手册,第一版(第一部分),每个 Java 初学者都应该收藏_第7张图片

来写个示例:

public class IfElseExample {
    public static void main(String[] args) {
        int age = 31;
        if (age < 30) {
            System.out.println("青春年华");
        } else {
            System.out.println("而立之年");
        }
    }
}

输出:

而立之年

除了这个例子之外,还有一个判断闰年(被 4 整除但不能被 100 整除或者被 400 整除)的例子:

public class LeapYear {
    public static void main(String[] args) {
        int year = 2020;
        if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)) {
            System.out.println("闰年");
        } else {
            System.out.println("普通年份");
        }
    }
}

输出:

闰年

如果执行语句比较简单的话,可以使用三元运算符来代替 if-else 语句,如果条件为 true,返回 ? 后面 : 前面的值;如果条件为 false,返回 : 后面的值。

public class IfElseTernaryExample {
    public static void main(String[] args) {
        int num = 13;
        String result = (num % 2 == 0) ? "偶数" : "奇数";
        System.out.println(result);
    }
}

输出:

奇数

if-else-if 语句

if-else-if 语句的格式如下:

if(条件1){  
// 条件1 为 true 时执行的代码
}else if(条件2){  
// 条件2 为 true 时执行的代码
}  
else if(条件3){  
// 条件3 为 true 时执行的代码
}  
...  
else{  
// 以上条件均为 false 时执行的代码
} 

画个流程图表示一下:

怒肝俩月,新鲜出炉史上最有趣的Java小白手册,第一版(第一部分),每个 Java 初学者都应该收藏_第8张图片

来写个示例:

public class IfElseIfExample {
    public static void main(String[] args) {
        int age = 31;
        if (age < 30) {
            System.out.println("青春年华");
        } else if (age >= 30 && age < 40 ) {
            System.out.println("而立之年");
        } else if (age >= 40 && age < 50 ) {
            System.out.println("不惑之年");
        } else {
            System.out.println("知天命");
        }
    }
}

输出:

而立之年

if 嵌套语句

if 嵌套语句的格式如下:

if(外侧条件){    
     // 外侧条件为 true 时执行的代码 
          if(内侧条件){  
             // 内侧条件为 true 时执行的代码
    }    
}  

画个流程图表示一下:

怒肝俩月,新鲜出炉史上最有趣的Java小白手册,第一版(第一部分),每个 Java 初学者都应该收藏_第9张图片

来写个示例:

public class NestedIfExample {
    public static void main(String[] args) {
        int age = 20;
        boolean isGirl = true;
        if (age >= 20) {
            if (isGirl) {
                System.out.println("女生法定结婚年龄");
            }
        }
    }
}

输出:

女生法定结婚年龄

switch 语句的格式:

switch(变量) {    
case 可选值1:    
 // 可选值1匹配后执行的代码;    
 break;  // 该关键字是可选项
case 可选值2:    
 // 可选值2匹配后执行的代码;    
 break;  // 该关键字是可选项
......    
    
default: // 该关键字是可选项     
 // 所有可选值都不匹配后执行的代码 
}    
  • 变量可以有 1 个或者 N 个值。

  • 值类型必须和变量类型是一致的,并且值是确定的。

  • 值必须是唯一的,不能重复,否则编译会出错。

  • break 关键字是可选的,如果没有,则执行下一个 case,如果有,则跳出 switch 语句。

  • default 关键字也是可选的。

画个流程图:

怒肝俩月,新鲜出炉史上最有趣的Java小白手册,第一版(第一部分),每个 Java 初学者都应该收藏_第10张图片

来个示例:

public class Switch1 {
    public static void main(String[] args) {
        int age = 20;
        switch (age) {
            case 20 :
                System.out.println("上学");
                break;
            case 24 :
                System.out.println("苏州工作");
                break;
            case 30 :
                System.out.println("洛阳工作");
                break;
            default:
                System.out.println("未知");
                break; // 可省略
        }
    }
}

输出:

上学

当两个值要执行的代码相同时,可以把要执行的代码写在下一个 case 语句中,而上一个 case 语句中什么也没有,来看一下示例:

public class Switch2 {
    public static void main(String[] args) {
        String name = "沉默王二";
        switch (name) {
            case "詹姆斯":
                System.out.println("篮球运动员");
                break;
            case "穆里尼奥":
                System.out.println("足球教练");
                break;
            case "沉默王二":
            case "沉默王三":
                System.out.println("乒乓球爱好者");
                break;
            default:
                throw new IllegalArgumentException(
                        "名字没有匹配项");

        }
    }
}

输出:

乒乓球爱好者

枚举作为 switch 语句的变量也很常见,来看例子:

public class SwitchEnumDemo {
    public enum PlayerTypes {
        TENNIS,
        FOOTBALL,
        BASKETBALL,
        UNKNOWN
    }

    public static void main(String[] args) {
        System.out.println(createPlayer(PlayerTypes.BASKETBALL));
    }

    private static String createPlayer(PlayerTypes playerType) {
        switch (playerType) {
            case TENNIS:
                return "网球运动员费德勒";
            case FOOTBALL:
                return "足球运动员C罗";
            case BASKETBALL:
                return "篮球运动员詹姆斯";
            case UNKNOWN:
                throw new IllegalArgumentException("未知");
            default:
                throw new IllegalArgumentException(
                        "运动员类型: " + playerType);

        }
    }
}

输出:

篮球运动员詹姆斯

循环语句比较

比较方式 for while do-while
简介 for 循环的次数是固定的 while 循环的次数是不固定的,并且需要条件为 true do-while 循环的次数也不固定,但会至少执行一次循环,无聊条件是否为 true
何时使用 循环次数固定的 循环次数是不固定的 循环次数不固定,并且循环体至少要执行一次
语法 for(init:condition;++/--) {// 要执行的代码} while(condition){// 要执行的代码} do{//要执行的代码}while(condition);

普通的 for 循环

普通的 for 循环可以分为 4 个部分:

1)初始变量:循环开始执行时的初始条件。

2)条件:循环每次执行时要判断的条件,如果为 true,就执行循环体;如果为 false,就跳出循环。当然了,条件是可选的,如果没有条件,则会一直循环。

3)循环体:循环每次要执行的代码块,直到条件变为 false。

4)自增/自减:初识变量变化的方式。

来看一下普通 for 循环的格式:

for(初识变量;条件;自增/自减){  
// 循环体
}  

画个流程图:

怒肝俩月,新鲜出炉史上最有趣的Java小白手册,第一版(第一部分),每个 Java 初学者都应该收藏_第11张图片

来个示例:

public class ForExample {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            System.out.println("沉默王二好帅啊");
        }
    }
}

输出:

沉默王二好帅啊
沉默王二好帅啊
沉默王二好帅啊
沉默王二好帅啊
沉默王二好帅啊

循环语句还可以嵌套呢,这样就可以打印出更好玩的呢。

public class PyramidForExample {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            for (int j = 0;j<= i;j++) {
                System.out.print("❤");
            }
            System.out.println();
        }
    }
}

打印出什么玩意呢?

❤
❤❤
❤❤❤
❤❤❤❤
❤❤❤❤❤

for-each

for-each 循环通常用于遍历数组和集合,它的使用规则比普通的 for 循环还要简单,不需要初始变量,不需要条件,不需要下标来自增或者自减。来看一下语法:

for(元素类型 元素 : 数组或集合){  
// 要执行的代码
}  

来看一下示例:

public class ForEachExample {
    public static void main(String[] args) {
        String[] strs = {"沉默王二", "一枚有趣的程序员"};

        for (String str : strs) {
            System.out.println(str);
        }
    }
}

输出:

沉默王二
一枚有趣的程序员

无限 for 循环

想不想体验一下无限 for 循环的威力,也就是死循环?

public class InfinitiveForExample {
    public static void main(String[] args) {
        for(;;){
            System.out.println("停不下来。。。。");
        }
    }
}

输出:

停不下来。。。。
停不下来。。。。
停不下来。。。。
停不下来。。。。

一旦运行起来,就停不下来了,除非强制停止。

while 循环

while(条件){  
//循环体  
}  

画个流程图:

怒肝俩月,新鲜出炉史上最有趣的Java小白手册,第一版(第一部分),每个 Java 初学者都应该收藏_第12张图片

来个示例:

public class WhileExample {
    public static void main(String[] args) {
        int i = 0;
        while (true) {
            System.out.println("沉默王二");
            i++;
            if (i == 5) {
                break;
            }
        }
    }
}

猜猜会输出几次?

沉默王二
沉默王二
沉默王二
沉默王二
沉默王二

do-while 循环

do{  
// 循环体
}while(提交);  

画个流程图:

怒肝俩月,新鲜出炉史上最有趣的Java小白手册,第一版(第一部分),每个 Java 初学者都应该收藏_第13张图片

来个示例:

public class DoWhileExample {
    public static void main(String[] args) {
        int i = 0;
        do {
            System.out.println("沉默王二");
            i++;
            if (i == 5) {
                break;
            }
        } while (true);
    }
}

程序输出结果如下所示:

沉默王二
沉默王二
沉默王二
沉默王二
沉默王二

break

break 关键字通常用于中断循环或 switch 语句,它在指定条件下中断程序的当前流程。如果是内部循环,则仅中断内部循环。

可以将 break 关键字用于所有类型循环语句中,比如说 for 循环,while 循环,以及 do-while 循环。

来画个流程图感受一下:

怒肝俩月,新鲜出炉史上最有趣的Java小白手册,第一版(第一部分),每个 Java 初学者都应该收藏_第14张图片

用在 for 循环中的示例:

for (int i = 1; i <= 10; i++) {
    if (i == 5) {
        break;
    }
    System.out.println(i);
}

用在嵌套 for 循环中的示例:

for (int i = 1; i <= 3; i++) {
    for (int j = 1; j <= 3; j++) {
        if (i == 2 && j == 2) {
            break;
        }
        System.out.println(i + " " + j);
    }
}

用在 while 循环中的示例:

int i = 1;
while (i <= 10) {
    if (i == 5) {
        i++;
        break;
    }
    System.out.println(i);
    i++;
}

用在 do-while 循环中的示例:

int j = 1;
do {
    if (j == 5) { 
        j++;
        break;
    }
    System.out.println(j);
    j++;
} while (j <= 10);

continue

当我们需要在 for 循环或者 (do)while 循环中立即跳转到下一个循环时,就可以使用 continue 关键字,通常用于跳过指定条件下的循环体,如果循环是嵌套的,仅跳过当前循环。

来个示例:

public class ContinueDemo {
    public static void main(String[] args) {
        for (int i = 1; i <= 10; i++) {
            if (i == 5) {
                // 使用 continue 关键字
                continue;// 5 将会被跳过
            }
            System.out.println(i);
        }
    }
}

输出:

1
2
3
4
6
7
8
9
10

5 真的被跳过了。

再来个循环嵌套的例子。

public class ContinueInnerDemo {
    public static void main(String[] args) {
        for (int i = 1; i <= 3; i++) {
            for (int j = 1; j <= 3; j++) {
                if (i == 2 && j == 2) {
                    //  当i=2,j=2时跳过
                    continue;
                }
                System.out.println(i + " " + j);
            }
        }
    }
}

打印出什么玩意呢?

1 1
1 2
1 3
2 1
2 3
3 1
3 2
3 3

“2 2” 没有输出,被跳过了。

再来看一下 while 循环时 continue 的使用示例:

public class ContinueWhileDemo {
    public static void main(String[] args) {
        int i = 1;
        while (i <= 10) {
            if (i == 5) {
                i++;
                continue;
            }
            System.out.println(i);
            i++;
        }
    }
}

输出:

1
2
3
4
6
7
8
9
10

注意:如果把 if 条件中的“i++”省略掉的话,程序就会进入死循环,一直在 continue。

最后,再来看一下 do-while 循环时 continue 的使用示例:

public class ContinueDoWhileDemo {
    public static void main(String[] args) {
        int i=1;
        do{
            if(i==5){
                i++;
                continue;
            }
            System.out.println(i);
            i++;
        }while(i<=10);
    }
}

输出:

1
2
3
4
6
7
8
9
10

注意:同样的,如果把 if 条件中的“i++”省略掉的话,程序就会进入死循环,一直在 continue。

五、Java 包的简介

在 Java 中,我们使用 package(包)对相关的类、接口和子包进行分组。这样做的好处有:

  • 使相关类型更容易查找
  • 避免命名冲突,比如说 com.itwanger.Hello 和 com.itwangsan.Hello 不同
  • 通过包和访问权限控制符来限定类的可见性

01、创建一个包

package com.itwanger;

可以使用 package 关键字来定义一个包名,需要注意的是,这行代码必须处于一个类中的第一行。强烈建议在包中声明类,不要缺省,否则就失去了包结构的带来的好处。

包的命名应该遵守以下规则:

  • 应该全部是小写字母
  • 可以包含多个单词,单词之间使用“.”连接,比如说 java.lang
  • 名称由公司名或者组织名确定,采用倒序的方式,比如说,我个人博客的域名是 www.itwanger.com,所以我创建的包名是就是 com.itwanger.xxxx

每个包或者子包都在磁盘上有自己的目录结构,如果 Java 文件时在 com.itwanger.xxxx 包下,那么该文件所在的目录结构就应该是 com->itwanger->xxxx

02、使用包

让我们在名为 test 的子包里新建一个 Cmower 类:

package com.itwanger.test;

public class Cmower {
    private String name;
    private int age;
}

如果需要在另外一个包中使用 Cmower 类,就需要通过 import 关键字将其引入。有两种方式可供选择,第一种,使用 * 导入包下所有的类:

import com.itwanger.test.*;

第二种,使用类名导入该类:

import com.itwanger.test.Cmower;

Java 和第三方类库提供了很多包可供使用,可以通过上述的方式导入类库使用。

package com.itwanger.test;

import java.util.ArrayList;
import java.util.List;

public class CmowerTest {
    public static void main(String[] args) {
        List list = new ArrayList<>();
        list.add(new Cmower());
    }
}

03、全名

有时,我们可能会使用来自不同包下的两个具有相同名称的类。例如,我们可能同时使用 java.sql.Datejava.util.Date。当我们遇到命名冲突时,我们需要对至少一个类使用全名(包名+类名)。

List list1 = new ArrayList<>();
list.add(new com.itwanger.test.Cmower());

六、Java 到底是值传递还是引用传递

将参数传递给方法有两种常见的方式,一种是“值传递”,一种是“引用传递”。C 语言本身只支持值传递,它的衍生品 C++ 既支持值传递,也支持引用传递,而 Java 只支持值传递。

01、值传递 VS 引用传递

首先,我们必须要搞清楚,到底什么是值传递,什么是引用传递,否则,讨论 Java 到底是值传递还是引用传递就显得毫无意义。

当一个参数按照值的方式在两个方法之间传递时,调用者和被调用者其实是用的两个不同的变量——被调用者中的变量(原始值)是调用者中变量的一份拷贝,对它们当中的任何一个变量修改都不会影响到另外一个变量。

而当一个参数按照引用传递的方式在两个方法之间传递时,调用者和被调用者其实用的是同一个变量,当该变量被修改时,双方都是可见的。

Java 程序员之所以容易搞混值传递和引用传递,主要是因为 Java 有两种数据类型,一种是基本类型,比如说 int,另外一种是引用类型,比如说 String。

基本类型的变量存储的都是实际的值,而引用类型的变量存储的是对象的引用——指向了对象在内存中的地址。值和引用存储在 stack(栈)中,而对象存储在 heap(堆)中。

怒肝俩月,新鲜出炉史上最有趣的Java小白手册,第一版(第一部分),每个 Java 初学者都应该收藏_第15张图片

之所以有这个区别,是因为:

  • 栈的优势是,存取速度比堆要快,仅次于直接位于 CPU 中的寄存器。但缺点是,栈中的数据大小与生存周期必须是确定的。
  • 堆的优势是可以动态地分配内存大小,生存周期也不必事先告诉编译器,Java 的垃圾回收器会自动收走那些不再使用的数据。但由于要在运行时动态分配内存,存取速度较慢。

02、基本类型的参数传递

众所周知,Java 有 8 种基本数据类型,分别是 int、long、byte、short、float、double 、char 和 boolean。它们的值直接存储在栈中,每当作为参数传递时,都会将原始值(实参)复制一份新的出来,给形参用。形参将会在被调用方法结束时从栈中清除。

来看下面这段代码:

public class PrimitiveTypeDemo {
    public static void main(String[] args) {
        int age = 18;
        modify(age);
        System.out.println(age);
    }

    private static void modify(int age1) {
        age1 = 30;
    }
}

1)main 方法中的 age 是基本类型,所以它的值 18 直接存储在栈中。

2)调用 modify() 方法的时候,将为实参 age 创建一个副本(形参 age1),它的值也为 18,不过是在栈中的其他位置。

3)对形参 age 的任何修改都只会影响它自身而不会影响实参。

怒肝俩月,新鲜出炉史上最有趣的Java小白手册,第一版(第一部分),每个 Java 初学者都应该收藏_第16张图片

03、引用类型的参数传递

来看一段创建引用类型变量的代码:

Writer writer = new Writer(18, "沉默王二");

writer 是对象吗?还是对象的引用?为了搞清楚这个问题,我们可以把上面的代码拆分为两行代码:

Writer writer;
writer = new Writer(18, "沉默王二");

假如 writer 是对象的话,就不需要通过 new 关键字创建对象了,对吧?那也就是说,writer 并不是对象,在“=”操作符执行之前,它仅仅是一个变量。那谁是对象呢?new Writer(18, "沉默王二"),它是对象,存储于堆中;然后,“=”操作符将对象的引用赋值给了 writer 变量,于是 writer 此时应该叫对象引用,它存储在栈中,保存了对象在堆中的地址。

每当引用类型作为参数传递时,都会创建一个对象引用(实参)的副本(形参),该形参保存的地址和实参一样。

来看下面这段代码:

public class ReferenceTypeDemo {
    public static void main(String[] args) {
        Writer a = new Writer(18);
        Writer b = new Writer(18);
        modify(a, b);

        System.out.println(a.getAge());
        System.out.println(b.getAge());
    }

    private static void modify(Writer a1, Writer b1) {
        a1.setAge(30);

        b1 = new Writer(18);
        b1.setAge(30);
    }
}

1)在调用 modify() 方法之前,实参 a 和 b 指向的对象是不一样的,尽管 age 都为 18。

怒肝俩月,新鲜出炉史上最有趣的Java小白手册,第一版(第一部分),每个 Java 初学者都应该收藏_第17张图片

2)在调用 modify() 方法时,实参 a 和 b 都在栈中创建了一个新的副本,分别是 a1 和 b1,但指向的对象是一致的(a 和 a1 指向对象 a,b 和 b1 指向对象 b)。

怒肝俩月,新鲜出炉史上最有趣的Java小白手册,第一版(第一部分),每个 Java 初学者都应该收藏_第18张图片

3)在 modify() 方法中,修改了形参 a1 的 age 为 30,意味着对象 a 的 age 从 18 变成了 30,而实参 a 指向的也是对象 a,所以 a 的 age 也变成了 30;形参 b1 指向了一个新的对象,随后 b1 的 age 被修改为 30。

怒肝俩月,新鲜出炉史上最有趣的Java小白手册,第一版(第一部分),每个 Java 初学者都应该收藏_第19张图片

修改 a1 的 age,意味着同时修改了 a 的 age,因为它们指向的对象是一个;修改 b1 的 age,对 b 却没有影响,因为它们指向的对象是两个。

程序输出的结果如下所示:

30
18

果然和我们的分析是吻合的。

七、Java 的类和对象

类和对象是 Java 中最基本的两个概念,可以说撑起了面向对象编程(OOP)的一片天。对象可以是现实中看得见的任何物体(一只特立独行的猪),也可以是想象中的任何虚拟物体(能七十二变的孙悟空),Java 通过类(class)来定义这些物体,有什么状态(通过字段,或者叫成员变量定义,比如说猪的颜色是纯色还是花色),有什么行为(通过方法定义,比如说猪会吃,会睡觉)。

来,让我来定义一个简单的类给你看看。

public class Pig {
    private String color;

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

默认情况下,每个 Java 类都会有一个空的构造方法,尽管它在源代码中是缺省的,但却可以通过反编译字节码看到它。

public class Pig {
    private String color;

    public Pig() {
    }

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

没错,就是多出来的那个 public Pig() {},参数是空的,方法体是空的。我们可以通过 new 关键字利用这个构造方法来创建一个对象,代码如下所示:

 Pig pig = new Pig();

当然了,我们也可以主动添加带参的构造方法。

public class Pig {
    private String color;

    public Pig(String color) {
        this.color = color;
    }

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

这时候,再查看反编译后的字节码时,你会发现缺省的无参构造方法消失了——和源代码一模一样。

public class Pig {
    private String color;

    public Pig(String color) {
        this.color = color;
    }

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

这意味着无法通过 new Pig() 来创建对象了——编译器会提醒你追加参数。

怒肝俩月,新鲜出炉史上最有趣的Java小白手册,第一版(第一部分),每个 Java 初学者都应该收藏_第20张图片

比如说你将代码修改为 new Pig("纯白色"),或者添加无参的构造方法。

public class Pig {
    private String color;

    public Pig(String color) {
        this.color = color;
    }

    public Pig() {
    }

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

使用无参构造方法创建的对象状态默认值为 null(color 字符串为引用类型),如果是基本类型的话,默认值为对应基本类型的默认值,比如说 int 为 0,更详细的见下图。

怒肝俩月,新鲜出炉史上最有趣的Java小白手册,第一版(第一部分),每个 Java 初学者都应该收藏_第21张图片

(图片中有一处错误,boolean 的默认值为 false)

接下来,我们来创建多个 Pig 对象,它的颜色各不相同。

public class PigTest {
    public static void main(String[] args) {
        Pig pigNoColor = new Pig();
        Pig pigWhite = new Pig("纯白色");
        Pig pigBlack = new Pig("纯黑色");
    }
}

你看,我们创建了 3 个不同花色的 Pig 对象,全部来自于一个类,由此可见类的重要性,只需要定义一次,就可以多次使用。

那假如我想改变对象的状态呢?该怎么办?目前毫无办法,因为没有任何可以更改状态的方法,直接修改 color 是行不通的,因为它的访问权限修饰符是 private 的。

最好的办法就是为 Pig 类追加 getter/setter 方法,就像下面这样:

public String getColor() {
    return color;
}

public void setColor(String color) {
    this.color = color;
}

通过 setColor() 方法来修改,通过 getColor() 方法获取状态,它们的权限修饰符是 public 的。

Pig pigNoColor = new Pig();
pigNoColor.setColor("花色");
System.out.println(pigNoColor.getColor()); // 花色

为什么要这样设计呢?可以直接将 color 字段的访问权限修饰符换成是 public 的啊,不就和 getter/setter 一样的效果了吗?

因为有些情况,某些字段是不允许被随意修改的,它只有在对象创建的时候初始化一次,比如说猪的年龄,它只能每年长一岁(举个例子),没有月光宝盒让它变回去。

private int age;

public int getAge() {
    return age;
}

public void increaseAge() {
    this.age++;
}

你看,age 就没有 setter 方法,只有一个每年可以调用一次的 increaseAge() 方法和 getter 方法。如果把 age 的访问权限修饰符更改为 public,age 就完全失去控制了,可以随意将其重置为 0 或者负数。

访问权限修饰符对于 Java 来说,非常重要,目前共有四种:public、private、protected 和 default(缺省)。

一个类只能使用 public 或者 default 修饰,public 修饰的类你之前已经见到过了,现在我来定义一个缺省权限修饰符的类给你欣赏一下。

class Dog {
}

哈哈,其实也没啥可以欣赏的。缺省意味着这个类可以被同一个包下的其他类进行访问;而 public 意味着这个类可以被所有包下的类进行访问。

假如硬要通过 private 和 protected 来修饰类的话,编译器会生气的,它不同意。

怒肝俩月,新鲜出炉史上最有趣的Java小白手册,第一版(第一部分),每个 Java 初学者都应该收藏_第22张图片

private 可以用来修饰类的构造方法、字段和方法,只能被当前类进行访问。protected 也可以用来修饰类的构造方法、字段和方法,但它的权限范围更宽一些,可以被同一个包中的类进行访问,或者当前类的子类。

可以通过下面这张图来对比一下四个权限修饰符之间的差别:

怒肝俩月,新鲜出炉史上最有趣的Java小白手册,第一版(第一部分),每个 Java 初学者都应该收藏_第23张图片
  • 同一个类中,不管是哪种权限修饰符,都可以访问;
  • 同一个包下,private 修饰的无法访问;
  • 子类可以访问 public 和 protected 修饰的;
  • public 修饰符面向世界,哈哈,可以被所有的地方访问到。
怒肝俩月,新鲜出炉史上最有趣的Java小白手册,第一版(第一部分),每个 Java 初学者都应该收藏_第24张图片

八、Java 构造方法

假设现在有一个 Writer 类,它有两个字段,姓名和年纪:

public class Writer {
    private String name;
    private int age;

    @Override
    public String toString() {
        return "Writer{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

重写了 toString() 方法,用于打印 Writer 类的详情。由于没有构造方法,意味着当我们创建 Writer 对象时,它的字段值并没有初始化:

Writer writer = new Writer();
System.out.println(writer.toString());

输出结果如下所示:

Writer{name='null', age=0}

name 是字符串类型,所以默认值为 null,age 为 int 类型,所以默认值为 0。

让我们为 Writer 类主动加一个无参的构造方法:

public Writer() {
    this.name = "";
    this.age = 0;
}

构造方法也是一个方法,只不过它没有返回值,默认返回创建对象的类型。需要注意的是,当前构造方法没有参数,它被称为无参构造方法。如果我们没有主动创建无参构造方法的话,编译器会隐式地自动添加一个无参的构造方法。这就是为什么,一开始虽然没有构造方法,却可以使用 new Writer() 创建对象的原因,只不过,所有的字段都被初始化成了默认值。

接下来,让我们添加一个有参的构造方法:

public Writer(String name, int age) {
    this.name = name;
    this.age = age;
}

现在,我们创建 Writer 对象的时候就可以通过对字段值初始化值了。

Writer writer1 = new Writer("沉默王二",18);
System.out.println(writer1.toString());

来看一下打印结果:

Writer{name='沉默王二', age=18}

可以根据字段的数量添加不同参数数量的构造方法,比如说,我们可以单独为 name 字段添加一个构造方法:

public Writer(String name) {
    this.name = name;
}

为了能够兼顾 age 字段,我们可以通过 this 关键字调用其他的构造方法:

public Writer(String name) {
    this(name,18);
}

把作者的年龄都默认初始化为 18。如果需要使用父类的构造方法,还可以使用 super 关键字,手册后面有详细的介绍。

。。。。。。

有些小伙伴可能就忍不住了,这份小白手册有没有 PDF 版可以白嫖啊,那必须得有啊,直接微信搜「沉默王二」回复「小白」就可以了,不要手软,觉得不错的,请多多分享——赠人玫瑰,手有余香哦。

本文已收录 GitHub,传送门~ ,里面更有大厂面试完整考点,欢迎 Star。

我是沉默王二,一枚有颜值却靠才华苟且的程序员。关注即可提升学习效率,别忘了三连啊,点赞、收藏、留言,我不挑,嘻嘻

你可能感兴趣的:(怒肝俩月,新鲜出炉史上最有趣的Java小白手册,第一版(第一部分),每个 Java 初学者都应该收藏)