Java从入门到小有所成

学习Java这一篇就够了!

前言

Java是一门面向对象的编程语言。它可以与C++相媲美,且比起C++更像是面向对象的语言。它是一门编译型语言,相比于解释型语言(如Python)来说运行速度更快。它连续多年成为最受欢迎的语言,是名副其实的“老大哥”。

目录

准备工作

下载JDK

首先,在这里下载JDK(这里用JDK19进行演示,但是不要下载JDK19,它的编码是GBK,后期容易中文乱码,建议下载JDK17),记得选择符合自己系统版本的选项(建议下载.exe文件)。

安装JDK

下载好的文件打开,第一步路径可以不改,
第四步然后等待完成,最后关闭就行了第三步

下载和安装IntelliJ IDEA

IDEA的官网下载:下载IDEA然后跟着引导安装即可,关于使用教程参考华夏天骄的文章

认识Java源码文件及运行机制

Java源码文件是.java的文件,代码都写在这里。编译之后,产生文件后缀为.class的文件,需要使用JVM运行。

Java是通过编译.java文件创建.class文件,然后用JVM运行。

1、Hello World

现在你可以正式开始学习了!
学习一门新的编程语言写的第一个程序一定是Hello World,下面是Java的Hello World示例代码:
HelloWorld.java

public class HelloWorld{
	public static void main(String[] args){
		System.out.println("Hello Java world!");
	}
}//注意:每一行程序末尾都必须是分号;或花括号{}

Tips:
使用两个斜杠“//”可以在Java代码里添加注释,这种注释会被编译器忽略,称为行尾注释。
还有一种注释是用“/*”开始,“*/”结尾的,可以作为连续多行的注释,称为块注释

下面是每一行的解释:

1.1、类的声明、权限修饰符与包——初讲

public class HelloWorld{这一行声明了一个类。Java中大部分的代码都是在类中实现的。声明类的语法:

权限修饰符 class 类名{
	//code...
}

其中,权限修饰符有四种:

权限修饰符作用范围
public任何地方,包括不同包的类,同包的类,子类,本类
protected其他包的不能引用
private不能被引用,只本类可见
[default]跟protected很像,具体见下

default其实就是不写权限修饰符,它声明的类跟protected差不多但是声明其他的东西就不一样了,后面会提到。
这里还提到一个新的概念:包
包是Java中描述类的位置使用的工具,包在文件资源管理器中就是一层层嵌套的文件夹。Java中包名通常用第一层包名.第二层包名.第三层包名的形式来表示。包可以嵌套任意多个,.java文件一般储存在包里。描述一个类的位置通常以第一层包名.第二层包名.······.类名的形式来表示,如:
Demo.java

package com.rimu.demo.main; //package关键字是描述程序包名的关键字,它只能写在程序开头第一行。
public class Demo{
	public void outPuter(){
		System.out.println("My class name:Demo");
	}
}

这时,它的位置可以用com.rimu.demo.main.Demo来表示。

Tip
一般新建一个项目之后都会用 com.作者/公司名.项目名称 做前三个包名,其他的包都在这三个包里。这样做的理由实际上涉及到域名。如果你或你的公司有自己的网站,就可以用你的网站的顶级域名做第一个包名,二级域名做第二个包名,三级域名做第三个包名,以此类推。

import关键字

前面讲了描述Demo类时用到的表达式是com.rimu.demo.main.Demo,为了使代码简洁易读,通常会在程序第二行开始使用import关键字导入类。例如:

import com.rimu.demo.main.Demo;
//或
import com.rimu.demo.main.*;//代表这个包里所有的类都导入。
//一个import最好单独占一行。

所以:
Main.java

public class Main {
	public static void main(String[] args) {
		java.util.Scanner scanner = new java.util.Scanner(System.in);
	}
}

等价于

import java.util.Scanner;

public class Main {
	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
	}
}

当遇到两个包里的类名相同时,应该使用全称(包名.类名)表示。

Tips:
同一个包内的类名不能相同。

1.2、main(String[] args)方法和方法初讲

第二行public static void main(String[] args){是声明了一个方法。它经常被叫做main方法。它是程序的入口,所有的程序都从这里开始执行。有main方法的类才能运行。

方法初讲

方法声明在类里,声明语法:

权限修饰符 静态修饰符(可选的) 返回类型(无返回就是void) 方法名(形参){
	//code...
}

其中,静态修饰符决定了这个方法是否是静态方法。静态方法可以用句点表示法(类名.方法名(实参))直接调用。
返回类型就是一个数据类型或者一个类名。如果返回类型不为void,则方法的末尾必须有一个return 返回值
形参也是可选的。如果有形参,则调用方法时必须传入对应的实参,实参的顺序和类型都必须与形参一样。

1.3、System.out.println()函数

这个程序的第三行System.out.println("Hello Java world!");是一个输出语句。它的作用是向控制台内输出Hello Java world!这段话。print是打印的意思,而lnline的缩写,代表了末尾有一个换行符\n。另一个函数System.out.print()不会在末尾自动加上一个换行符。例如:
Try.java

public class Try{
	//print和println的区别
	public static void main(String[] args){
		System.out.println("========把println()放在前面========");
		System.out.println("ok");
		System.out.print("ok");
		System.out.println();//输出空行
		System.out.println("========把print()放在前面========");
		System.out.print("ok");
		System.out.println("ok");
	}
}

结果:

========把println()放在前面========
ok
ok
========把print()放在前面========
okok

第四行和第五行就是大括号的另一半。

Tips:
Java中括号都是成对的,否则会报错。

2、变量与数据类型

在正常的程序中,很多的数据并不是固定的。这时,就需要变量来储存这些数据。这些数据是可以变化的。

2.1、声明变量

变量的声明语法:

数据类型 变量名 =;
//或
数据类型 变量名;
变量名 =;

变量的命名遵守以下规则:

  1. 不能以数字开头
  2. 不能包含除数字,大小写字母(Java区分大小写)和‘_’以外的字符
  3. 在该变量的 作用域 里,不能重名
  4. 不能与Java关键字重名

数据类型详见下节

2.2、数据类型初讲

Java中一共有8种基础的数据类型:

数据类型占用内存/字节数据范围举例说明
byte1-128~127byte b = 10;
short2-32768~32767short s = 10;
int4-231~231-1int n = 10;
long8-263~263-1long l = 10L;long l = 10l;
float41.4E-45~3.4028235E38float f = 3.14F;float f = 3.14f;
double84.9E-324~1.7976931348623157E308double d = 3.14D;double d = 3.14d;
char2单个字符(包括中文)char c = 'a';
boolean1truefalseboolean b = true;

Tip:
声明long类型时需要在数字末尾加l(一般加大写的L),声明float类型也要加f(同样一般加大写的F)。
声明char类型时要用单引号。
double类型的数据的有效位数 15~16。
float类型的数据有效位数是 6~7。

2.2.1、String类型

String类型是字符串类型,实际上不属于基础数据类型。但是用它创建变量跟基础类型的创建方法是类似的。需要特别注意的是,创建String类型时一定要用双引号""。因为单引号''是声明字符类型的。
下面是一个字符串变量的示例:

package com.rimu.test.main;

public class StringExample{
	public static void main(String[] args){
		String s = "我是字符串";
		String s2 = """
					我是文本块字符串,
					我可以写很多行,
					并且不需要标记换行符。
					"""
		System.out.println(s);
		System.out.println(s2);
	}
}

输出:

我是字符串
我是文本块字符串,
我可以写很多行,
并且不需要标记换行符。
2.2.1.1、String常用方法

以下假设有一个String类型变量叫s,且s不为空。则调用这个字符串的一个方法的语法为:

s.方法名(参数);

其中,参数有几个填几个,类型一一对应,顺序也一一对应。以下是常用的方法

  1. legnth()
    这个方法返回一个int类型的值,表示该字符串的长度。
  2. charAt(int index)
    这个方法返回一个char类型的字符,表示在该字符串的第index个位置上的字符,index编号从0开始。例如,一个长度为5的字符串Hello调用cahrAt(1)返回e(第二个字符)

2.3、使用变量

变量在编程中很重要。几乎所有的知识都是建立在变量的基础上的。下面是使用变量的一个例子:
Test.java

package com.rimu.test.main;

public class Test{
	String s1 = "Hello,";//Java中String是一个类,不是基本数据类型。字符串用双引号表示。
	public static void main(String[] args){
		String s2 = "日暮,";
		System.out.print(s1+s2);//Java中字符串可以用加号拼接。
			        //使用print()函数以防止输出换行符。
		outputer("Mike!");//直接传入字符串的值。
		s2 = "还有Sam!";//给变量重新赋值,改变它的值。
		outputer(s2);
	}
	public static void outputer(String s){//s是形参,只在这个方法里可用。
		System.out.println(s);//会输出换行符。
	}
}

以上代码执行后会输出:

Hello,日暮,Mike!
还有Sam!

2.3.1、变量的作用域

每个变量都有不同的作用域。变量分为成员变量和局部变量。上面的例子中,s1是成员变量,s2是局部变量(形参s也算局部变量)
总结:

  • 在方法外部,类内部声明的是成员变量
  • 在方法内部声明的是局部变量
  • 形参也算局部变量
    在变量的作用域里的代码才能使用这个变量。

2.3.2、权限修饰符与变量

在变量的声明中,可以在前面加上权限修饰符以限制其他类的访问。例如:
Main.java

package com;

public class Main {
	public double pi = 3.14;
	protected int a = 10;
	private int b = 2;
	int c = 5;//权限修饰符是[default],可以省略。
}

Test.java

package com;

public class Test {
	public static void main(String[] args) {
		Main m = new Main(); //创建一个对象(以后会讲)
		out(m.pi);//不会报错
		out(m.a);//不会报错
		//out(m.b);会报错,因为是该变量是private修饰符修饰的变量。
		out(m.c);//不会报错
	}
	public static void out(Object o){//Object是任意对象(包括数据类型)
		System.out.println(o);
	}
}

Test2.java

package com.rimu;

public class Test2 {
	public static void main(String[] args) {
		Main m = new Main(); //创建一个对象
		out(m.pi);//不会报错
		//out(m.a);会报错
		//out(m.b);会报错
		//out(m.c);会报错
	}
	public static void out(Object o){
		System.out.println(o);
	}
}

具体见下:
权限修饰符的访问限制范围

注:子类是继承了另一个类的类,被继承的类叫父类,继承的类叫子类

2.3.3、静态修饰符与变量

静态修饰符static可以修饰成员变量。声明为静态变量的所有变量在程序开始运行后就会进行初始化。它们在内存里都是唯一的,无论创建多少个它所在类的对象,它的值都不便。当变量被声明为静态变量后,其他类在有该变量的访问权限权限(如上例)时可以访问。
访问静态变量有如下的方法:
Main.java

package com;

public class Main {
	public static double pi = 3.14;
}

Test.java

package com;
import static com.Main.pi;//导入静态变量
public class Test {
	public static void main(String[] args) {
		out(Main.pi);//第一种方法
		out(pi);//只有在开头已经导入之后才能使用。
		out(new Main().pi);
		/*创建对象之后使用静态变量,可以但是没必要,不推荐。
		
		除非这个静态变量在此类的构造函数中初始化,即不创建这个类的对象,
		这个静态函数就为null。也就是说,如果这个构造函数对这个静态变量
		没有影响,这种方法就没必要。
		*/
	}
	public static void out(Object o){//Object是任意对象(包括数据类型)
		System.out.println(o);
	}
}

2.3.4、final关键字与变量

声明变量时可以使用final关键字修饰变量,这个变量就叫常量
常量的值在赋值之后就不能更改。例如:
Main.java

package com;

public class Main {
	final double pi = 3.14;
	public static void main(String[] args) {
		//pi = 3.15;报错,常量的值不能改变
	}
}

Tip:
在实际项目开发时,常量往往很重要,例如圆周率π等。

3、运算符

在Java中,对任何变量或数据进行操作都需要使用运算符。运算符的类型有很多,下面我们来认识一下不同的运算符有什么作用。

3.1、算术运算符

运算符含义例子
+将两个操作数相加a+b=12
-用左边的操作数减去右边的操作数a-b=8
*将两个操作数相乘a*b=20
/用左边的操作数除以右边的操作数a/b=5
%左边的操作数除以右边的操作数得到的余数a%b=0;a%c=1
++操作数自增1a++=11
--操作数自减1a- -=9
注:以上a=10,b=2,c=3

Tip:
实际开发时有一个好用的小技巧:
假如你有两个变量a=5,b=6
这时你想让a+b的值赋值给b,于是你写了这串代码:

a = a + b;

为了简洁(方便),通常可以用这个替代:

a += b;

类似地,其他运算也可以这样表示,如:

a = a - b;/*等价于*/a -= b;
a = a * b;/*等价于*/a *= b;
a = a / b;/*等价于*/a /= b;
a = a % b;/*等价于*/a %= b;

注:单个等号(=)是赋值运算符

3.1.1、细讲自增自减运算符(++--

假如我有两个变量a = 5,b = 1
此时:

a += b++;//1
与
a += ++b;//2
等价吗?

答案是不等价。
当我使用1这个式子时,a=6
使用2时,a=7
不难看出:
++--出现在b的前面时,b会先自增1再让a加上自己,这时加上2。反之,当它们在b的后面是,b会先让a加上自己再自增1,这时加上1。

3.2、关系运算符

运算符含义例子返回值
==如果左右操作数的值相等,返回true,否则返回falsea == ctrue
>如果左操作数大于右操作数,返回true,否则返回falsea > btrue
<如果左操作数小于右操作数,返回true,否则返回falsea < bfalse
>=如果左操作数大于或等于右操作数,返回true,否则返回falsea >= ctrue
<=如果左操作数小于或等于右操作数,返回true,否则返回falseb <= ctrue
!=如果左操作数不等于右操作数,返回true,否则返回falsea != cfalse
注:以上a=10,b=2,c=10

3.3、逻辑运算符

运算符名字和含义例子返回值
!逻辑非,将逻辑非后面的语句结果取反!truefalse
&逻辑与(并且),只有当左右操作数都为true时,才会返回truetrue&falsefalse
逻辑或(或者)两个操作数里有一个是true就会返回truetrue│falsetrue
&&短路与,与逻辑与类似,区别是会发生短路现象,详见下文false&&truefalse
││短路或,与逻辑或类似,区别是会发生短路现象,详见下文true││falsetrue

3.3.1、短路

右边表达式不执行,叫做短路。
例如:

int a = 10;
false&&1>a++;
true||1>a++;
//这时a还是10,因为短路导致右边表达式不执行。

那什么时候会发生短路呢?

使用短路与 && 的时候,当左边的表达式为false的时候,右边的表达式不执行
使用短路或 || 的时候当左边的表达式结果是true的时候,右边的表达式不执行

注意:逻辑运算符两边要求都是布尔类型,并且最终的运算结果也是布尔类型

3.4、条件运算符(三目运算符)

三目运算符的语法:

条件表达式  ? 表达式1 : 表达式2;

当条件表达式为true时,整个表达式的值取表达式1的值,否则取表达式2的值。

注意:表达式1和2的类型必须相同,且与被赋值的变量类型相同。

3.5、字符串连接运算符

在Java中,加号+其实还能用来连接字符串,比如说:

String a = "Hello ";
Stirng b = "Java World";
System.out.println(a+b);
a += b;//当然可以用a+=b代替a=a+b
System.out.println(a);

此时,输出为:
···
Hello Java World
Hello Java World
···

3.6、位运算符

位运算符的所有计算都是基于二进制的。
二进制
众所周知,我们在日常生活中,使用的数字是0~9的,也就是十进制,而电脑是看不懂这么多复杂的数字的,于是人类就发明了二进制。二进制只有0和1,它跟十进制不同的是,十进制是逢十进一,二进制是逢二进一。例如:0001+0001=0010(1+1=2;第一位上有2了,所以往第二位进1),0101+0010=0111(5+2=7)

有一句很好玩的话:
世界上只有10种人,一种是懂二进制的,另一种是不懂二进制的
下半句:
那剩下的1000种呢?
(二进制10是2,1000是8)

关于二进制有几点需要记的:

  1. 二进制的高位是符号位:0表示正数、1表示负数。
  2. 正数的原码、反码、补码都一样(三码合一)
  3. 负数的反码=它的原码符号位不变,其它位取反(0–>1,1–>0)
  4. 负数的补码=它的反码 + 1,负数的反码=负数的补码 - 1
  5. 0的反码,补码都是0
  6. Java没有无符合数,换而言之,Java中的数都是有符号的
  7. 在计算机运算的时候,都是以补码的方式来运算的
  8. 当我们看结果的时候,要看它的原码

那么什么是位运算符呢?

运算符名称含义例子
&按位与当左右操作数的同一位上都是1时,此位为113 & 5=5
按位或当左右操作数的同一位上只要有一个是1,此位就是113 │ 5=13
~按位非单目运算符,将操作数的每一位取反(包括符号位)~13=-14 (根据数据类型不同可能会产生不同的结果)
^按位异或当左右操作数的同一位上只有一个是1,此位就是113 ^ 5=8
<<左移运算符将左操作数的每一位数全部向左移动(右操作数)位,符号不变,高位的舍弃,低位补零13 << 2=52
>>右移运算符将左操作数的每一位数(除了符号位)全部向右移动(右操作数)位,符号位不变,低位的舍弃,正数的高位的空位补0,负数的高位的空位补1-13 >> 2=-4
>>>无符号右移运算符将左操作数的每一位数全部向右移动(右操作数)位,低位的舍弃,正数的高位的空位补0,负数的高位的空位补1-13 >>> 2=1073741820
注:13的二进制是00001101,5的二进制是00000101,-13的二进制是10001101(假设数据类型为byte)

Tip:
有几个小技巧:

  • 右移运算符右移n位就除以n次2或是除以2的n次幂
  • 左移运算符左移n位就乘以n次2或是乘以2的n次幂

3.7、运算符优先级

顾名思义,优先级越高的运算符就先执行。
下表中具有最高优先级的运算符在的表的最上面,最低优先级的在表的底部。
下表中具有最高优先级的运算符在的表的最上面,最低优先级的在表的底部。

类别操作符关联性
后缀() [] . (点操作符)从左到右
一元expr++ expr–从左到右
一元++expr --expr + - ~ !从右到左
乘性* /%从左到右
加性+ -从左到右
移位>> >>> <<从左到右
关系> >= < <=从左到右
相等== !=从左到右
按位与从左到右
按位异或^从左到右
按位或从左到右
逻辑与&&从左到右
逻辑或││从左到右
条件?:从右到左
赋值= + = - = * = / =%= >> = << =&= ^ = │=从右到左
逗号从左到右
注:expr=操作数

总体而言:优先顺序为算术运算符>关系运算符>逻辑运算符

可以通过“()”控制表达式的运算顺序,“()”优先级最高。

4、分支结构

4.1、if语句

在实际的程序开发中,一个正常的程序往往会面临多种可能,这时就要对不同的情况分别处理。所以,我们需要使用if语句进行判断,并执行不同的代码。
if语句的语法:

if(条件表达式){
	//codes...
}

例如,我想做一个程序,如果用户的成绩是100以上,我就输出“好样的!”,其他的不输出,流程图如下:

Created with Raphaël 2.3.0 开始 读取分数 是否是100分以上? 输出“好样的!” 结束 yes no

Tip:
我们要养成写程序之前画流程图的好习惯

这里引入一个:
Scanner类的用法
Scanner类可以为我们获取用户在控制台的输入。
Scanner类的对象创建方法:

Scanner s = new Scanner(System.in);

我们可以使用s.next();获取一个String类型的输入,当然可以直接用s.nextInt();获取一个int类型的输入,例如:

Scanner s = new Scanner(System.in);
String s1 = s.next();
int a = s.nextInt();

所以,我们得到了这样的一个成品:
Main.java

package com;

import java.util.Scanner;

public class Main {
	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		int next = scanner.nextInt();
		if (next >= 100){//括号里面是一个返回布尔值的表达式,为true的话就执行大括号里的代码,否则跳过。
			System.out.println("好样的!");
		}
	}
}

4.2、if-else语句

假如,我们需要对上面的程序进行一个修改,当分数不足100分时需要输出:“继续努力!”,先画流程图:

Created with Raphaël 2.3.0 开始 读取分数 是否是100分以上? 输出“好样的!” 结束 输出“继续努力!” yes no

这时,我们就可以使用if-else语句,语法如下:

if(条件表达式){
	//codes...
}else{
	//codes...
}

当条件表达式为false时,if-else语句不会直接跳过,而是执行else语句里面的内容。
改了代码就是:
Main.java

package com;

import java.util.Scanner;

public class Main {
	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		int next = scanner.nextInt();
		if (next >= 100){
			System.out.println("好样的!");
		}else {
			System.out.println("继续努力!");
		}
	}
}

4.3、if-else-if语句

假如,我们还需要对上面的程序进行一个修改,当分数为100~90(不包括100)分时需要输出:“还不错哦!”,其他的不变(90分以下还是输出“继续努力!”),还是先画流程图

Created with Raphaël 2.3.0 开始 读取分数 是否是100分以上? 输出“好样的!” 结束 是否是100到90分之间? 输出“还不错哦!” 输出“继续努力!” yes no yes no

然后上代码:
Main.java

package com;

import java.util.Scanner;

public class Main {
	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		int next = scanner.nextInt();
		if (next >= 100){
			System.out.println("好样的!");
		} else if (next >= 90) {//因为分数不是大于等于100才会执行这个,所以只要检测是不是大于等于90就行了
			System.out.println("还不错哦!");
		} else {
			System.out.println("继续努力!");
		}
	}
}

如上,if-else语句就是在前一个判断不成立之后才会执行的语句,但是在某些情况(比如可能两个判断都要执行)时,也会使用多个if语句。
总结:if语句在一定条件下执行,else-if在前一个if不成立才会判断,else则是在所有if及else-if都不执行才执行,并且不会判断,如下图:

Created with Raphaël 2.3.0 开始 前面的代码 if code1 后面的代码 结束 else-if code2 else code3 yes no yes no

4.4、Switch语句

Switch语句是在需要大量if-else语句且只是判断同一个变量的值进行不同处理时可以使用。比起大量的if语句,Switch语句更简洁。Switch语句的格式:

switch(变量){
	case 一个值:
		//codes..
		break;//不是一定的,但是加和不加是有区别的
	case 另一个值:
		//codes...
		break;
	…………
	default: //在所有case都不执行时才会执行,除非最后一个case没有break语句
		//codes...
		break;//这里建议加break
}

例如,当一场10分制的考试结束后,每个分段有不同的等级,具体如下:

分数等级
9~10A
8B
6~7C
0~5D

这时,我们不仅可以使用多个if实现,还可以使用Switch实现。看代码:
Main.java

package com;

import java.util.Scanner;

public class Main {
	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		int next = scanner.nextInt();
		switch (next){
			case 10:
			case 9:
				System.out.println('A');
				break;
			case 8:
				System.out.println('B');
				break;
			case 7,6://可以合并的话一般都会合并
				System.out.println('C');
				break;
			default:
				System.out.println('D');
				break;
		}
	}
}

大家可以发现,如果不加break,代码将会一直执行直到遇到break;,并且不会判断case后面的数值。

4.4.1、增强的Switch语句/Switch表达式语句

在JDK14及以上,Switch更新了对lambda表达式的支持*(最重要的是不需要加break了!!!)*,具体语法如下:

switch(var){//var是变量的意思
	case value -> {//value是值的意思
		//codes...
	}
	case value -> //只能写一行code
	...
	default -> {
		//codes...
	}//不需要写break了!
}

例如,刚才的代码可以改成:

package com;

import java.util.Scanner;

public class Main {
	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		int next = scanner.nextInt();
		switch (next){
			case 109 -> System.out.println('A');
			case 8 -> System.out.println('B');
			case 7,6 -> System.out.println('C');
			default -> System.out.println('D');
		}
	}
}

5、循环语句

假如我们要打印1~100的所有数怎么办呢?
第一种办法也是最笨的办法:

System.out.println(1);
System.out.println(2);
System.out.println(3);
System.out.println(4);
System.out.println(5);
//......
System.out.println(99);
System.out.println(100);

当然没有人会这么干,所以,我们来学习:循环语句
循环语句在Java中有3种,分别是whiledo-whilefor循环,每一种都有不一样的作用。

5.1、while循环

while循环是比较简单的一种循环。while循环的语法是:

while(condition){
	//codes...
}

其中,condition就是条件表达式。当condition为true时,循环便会执行一次,然后再次判断,如果还是true就会再次执行,直到condition为false。流程图:

Created with Raphaël 2.3.0 开始执行while语句 Condition? while执行的代码 执行后面的代码 yes no

就拿刚才输出的例子吧,用while循环实现就是:
Main.java

package com;

public class Main {
	public static void main(String[] args) {
		int i = 1;//i是index的意思
		while (i <= 100){
			System.out.println(i);
			i++;
		}
	}
}

是不是很短了?while循环在实际应用中还有一个无限循环的用法,例如:

while(true){
	//codes...
}

但是要注意,一定要在循环内部有一个if语句判断,然后break出来。这就涉及到breakcontinue了。

5.1.1、break,continue

想要强制跳出一个循环,可以使用break语句。这会让循环直接结束,执行后面剩余的代码,例如:

Created with Raphaël 2.3.0 开始执行while语句 Condition? break? 执行后面的代码 while执行的代码 yes no yes no

但是,continue就不一样了。
continue执行之后,循环后面的代码不执行,但是循环不会停止,流程图为:

Created with Raphaël 2.3.0 开始执行while语句 Condition? while执行的代码 continue? 执行后面的代码 循环里continue后面的代码 yes no yes no

例如,当我们只需要输出1~100的偶数时,就可以用到continue,例如:
Main.java

package com;

public class Main {
	public static void main(String[] args) {
		int i = 1;//i是index的意思
		while (i <= 100){
			if (i % 2 == 0){//判断i是否为偶数(能否被2整除)
				continue;
			}
			System.out.println(i);
			i++;
		}
	}
}

5.1.2、循环标签

在使用break或continue语句时,可能会用到循环标签。循环标签类似给循环起个名字,例如:
Main.java

package com;

public class Main {
	public static void main(String[] args) {
		int i = 1;//i是index的意思
		int j = 1;
		out:
		while (i <= 100){
			System.out.println(i);
			i++;
			while (j <= 10){
				System.out.println(j);
				j++;
				if (j == 10){
					break out;//break掉的是外面的循环
				}
			}
		}
	}
}

这时,输出为:

1
1
2
3
4
5
6
7
8
9
10

循环标签有时很有用(当你想在嵌套的循环中break外面的循环时)

5.2、do-while循环

do-while循环不常用,它与while循环不同之处在于无论如何它都会先执行一次然后再进行判断,语法如下:

do{
	//codes...
}while(condition);

注意:分号不能丢

流程图如下:

Created with Raphaël 2.3.0 开始执行while语句 while执行的代码 Condition? 执行后面的代码 yes no

看个例子就知道了
Main.java

package com;

public class Main {
	public static void main(String[] args) {
		System.out.println("while和do-while的区别");
		while (false){
			System.out.println("while运行了");
		}
		do{
			System.out.println("do-while运行了");
		} while (false);
	}
}

输出为:

while和do-while的区别
do-while运行了

可以看出,do-while是至少运行一次的,而while在某些情况下一次都不会运行。

5.3、for循环

for循环是最常用的循环,for循环可以很方便的控制次数,它的语法是:

for(语句1;condition;语句3){//语句1一般是声明一个index,语句3一般是对index的值进行操作
	//codes...
}

程序运行时,看到for循环,一般会先运行语句1,然后判断condition是否为true,如果为true就执行一次循环里面的代码,然后执行语句3,再判断condition,以此类推,流程图为:

Created with Raphaël 2.3.0 前面的代码 语句1 Condition? 循环内部代码 语句3 后面的代码 yes no

使用for循环输出1~100内所有的偶数:
Main.java

package com;

public class Main {
	public static void main(String[] args) {
		for (int i = 2;i <= 100;i += 2){
			System.out.println(i);
		}
	}
}

发现了吗?for循环比while更简洁,更方便,这就是为什么大部分人更喜欢for循环。for循环中三个语句都不是必须的,例如:如果没有condition,就是无限循环,如果没有语句1或语句3,就不会执行相应的语句。但是,括号内的两个分号一个都不能丢,即使你要写无限循环,你也要写for(;;)

6、数组

数组在任何编程语言中都是一个非常重要的数据结构。Java中的数组同样重要。

6.1、声明数组

数据类型[] 数组名称 = new 数据类型[数组长度];//这是动态初始化
数据类型[] 数组名称 = new 数据类型[]{1,2,3,4,5,6,7,8,9};//这是静态初始化,不常用,可以拆分:
数据类型[] 数组名称;
数组名称 = new 数据类型[]{1,2,3,4,5,6,7,8,9};
//还可以:
数据类型[] 数组名称 = {1,2,3,4,5,6,7,8,9};//不能拆分

其中,数组名称的命名规则与变量的命名规则相同;数组长度必须是数字或常量且是整数。

6.1.1、Arrays.fill()方法

Java中数组的初始值是0(前提是类型是整数类型),当我们想要批量对数组赋值除了0之外的数据时,可以使用Arrays.fill()方法。语法如下:

Arrays.fill(要赋值的数组,要赋的值);//数组和值的类型必须相同或可以转化

例如:
Main.java

package com;

import java.util.Arrays;

public class Main {
	public static void main(String[] args) {
		int[] a = new int[10];
		Arrays.fill(a,1);//将数组填充为1
		for (int i:a) {
			System.out.print(i + " ");
		}
	}
}

运行结果:

1 1 1 1 1 1 1 1 1 1

6.2、数组的使用

数组可以储存一串的数据,对于大量数据的处理非常有效。我们可以分别对数组内的每一个值进行操作。访问数组的一个值的语法为:

数组名[下标]

其中,下标从0开始,也就是说,一个数组int[] a = new int[5];的真实下标为0,1,2,3,4,访问a[5]时会报错。

6.2.1、遍历数组

遍历数组也就是把数组内的每一个数据都处理一遍。我们一般使用for循环进行数组的遍历。例如:

package com;

public class Main {
	public static void main(String[] args) {
		int[] a = new int[]{1,2,3,4,5,6,7,8,9};
		System.out.println(a);//直接输出
		for (int i = 0;i < a.length;i++){//数组的下标从0开始
			System.out.print(a[i] + " ");//通过遍历输出
		}
	}
}

运行结果:

[I@776ec8df
1 2 3 4 5 6 7 8 9 

直接输出时,不能正常显示是因为输出的是数组的地址,只能通过遍历输出数组的值。

Tips:

数组的length变量是数组的长度,通过数组名称.length访问。

6.2.2、增强的for循环/for-each循环

for-each循环在JDK1.5时就出现了。它是for循环在遍历数组的特殊加强/简化版本。for-each的语法:

for(数据类型 变量名:数组名称){
	//codes...
}

数据类型一般是数组的数据类型,数组名称就是需要遍历的数组。for-each循环在每次循环时,会将数组里的一个值赋值给设定的变量,例如上面的可以简化为:

package com;

public class Main {
	public static void main(String[] args) {
		int[] a = {1,2,3,4,5,6,7,8,9};
		for (int j : a) {//数组的下标从0开始
			System.out.print(j + " ");//通过遍历输出
		}
	}
}

6.2.3、数组越界

仔细看下面的代码:
Main.java

package com;

public class Main {
	public static void main(String[] args) {
		int[] array = {1, 2, 3};
		System.out.println(array[3]);
	}
}

你发现了什么?
没错,第六行的调用a[3],但是索引3不存在(最大索引是长度减一,这里是2),这时如果运行会怎么样呢?

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
	at com.Main.main(Main.java:6)

进程已结束,退出代码1

这里的ArrayIndexOutOfBoundsException就是数组越界异常(直译过来是“数组索引超出范围异常”)这种异常在写代码时一定要避免,一般加个条件判断就可以防止错误的发生了。

Tip:
我们可以把数组的大小开大一点,比如刚才的数组我开成:{0 ,1, 2 , 3},然后我只用索引在1~3范围的值,a[0]抛弃不用。这种开发方式在大型项目的开发中不推荐,但是在写小程序的时候很好用。

6.3、数组的常用方法

程序员秉承着“能坐着绝不站着,能躺着绝不坐着”的理念,开发了许多API所以我们一定要学会利用Java的API为我们的编程提供帮助。下面讲的方法就是Java API的一部分。

6.3.1、输出数组

将数组转化为[值, 值, 值...... 值]的形式输出,需要用到Arrays.toString()方法,核心代码如下:

 int[] array = {1, 2, 3};
 System.out.println(Arrays.toString(array));//可以看出Arrays.toString()的参数是一个数组

6.3.2、数组中是否包含某个值

检查数组中是否包含某个值,需要使用Arrays.asList()方法将数组转化为List,然后使用List示例.contains()方法,核心代码如下:

String[] array = { "a", "b", "c", "d", "e" };
boolean isEle = Arrays.asList(array).contains("a");
//可以看出Arrays.asList()的参数是一个数组,contains()的参数是一个数据值
//返回布尔值,为true代表可以在这个数组中找到这个值
System.out.println(isEle);
//其实也可以遍历数组然后一个个比对,具体代码如下:
for(String s:array){
	if(s.equals("a")){//这里没用==比较是因为字符串比较使用equals()方法更安全
		System.out.println(true);
		return;//返回空值,用于提前退出方法
	}
}
System.out.println(false);

6.3.3、数组复制

int array[] = new int[] { 1, 2, 3, 4 };
int array1[] = new int[array.length];//由于length是一个常量,所以可以使用array.length初始化一个与array长度相同的数组
System.arraycopy(array, 0, array1, 0, array.length);
/*
五个参数:
 1. 本体数组
 2. 开始的索引
 3. 粘贴到的数组
 4. 开始的索引
 5. 复制元素的个数
注:两个数组的数据类型必须相同或可以互相转化
*/

6.4、数组排序

给你一个乱七八糟的数组,你需要把它排列成从小到大的 单调递增数列 这时我们就需要用到排序算法了。

6.4.1、选择排序

原理:
每次选择待排序数组里的一个最小值,将其与已排序数组的后一位数进行交换。
看了这个动图你就懂了:
选择排序

特性:

  • 空间复杂度:O(1),因为不需要其他数组
  • 时间复杂度:最好,最坏,平均情况下都是O(n2)
  • 不稳定,因为它会交换两个数据,很容易导致相同数据的交换

Tips:
算法复杂度:衡量算法好坏的标准,分为空间复杂度和时间复杂度,一般使用大O表示法(O()的表示方法)详情可见百度
稳定性:衡量算法是否稳定,指的是具有相同数据的两个对象的位置是否会被交换。

示例:
SelectSort.java

package com.sort;

import java.util.Arrays;
public class SelectSort {
	public static void main(String[] args) {
		int[] a ={29,10,14,37,14,2,8,1,7,6,5};
		for (int i = 0; i < a.length; i++) {
			//注意minIndex的取值
			int minIndex = i;
			//j=i;意味着i之前的数都是有序的
			for (int j = i; j < a.length; j++) {
				if (a[j]<a[minIndex]){
					minIndex = j;
				}
			}
			//交换,每一次循环的i都是未排序数据的第一个
			int temp = a[i];
			a[i] = a[minIndex];
			a[minIndex] = temp;
		}
		System.out.println(Arrays.toString(a));
	}
}

6.4.2、冒泡排序

冒泡排序可以说是知名度很高的排序算法了。它简单移动还好写,话不多说,看原理。
原理:
从前往后遍历数组,每次都检查这个值是否比下个值大,如果是,就将两数交换,这样遍历一次,最大数就在数组末尾,然后重复,直到完成排序。看个动图:
冒泡排序

特性:

  • 空间复杂度:O(1),因为不需要其他数组
  • 时间复杂度:最好O(n),最坏O(n2),平均情况下是O(n2)
  • 稳定
6.4.2.1、冒泡排序的最简单实现

基于上面的思想,可以写出如下代码:
BubbleSort.java

package com.sort;

import java.util.Arrays;
public class BubbleSort {
	public static void main(String[] args) {
		int[] a ={8, 7, 6, 5, 4, 3};
		for (int i = 0; i < a.length; i++) {
			for (int j = 0; j < a.length-1; j++) {//如果执行a.length次就会越界
				if (a[j] > a[j+1]){//如果前一个数比后一个大就交换
					int tmp = a[j];
					a[j] = a[j+1];
					a[j+1] = tmp;
					//这几行是交换
				}
			}
		}
		System.out.println(Arrays.toString(a));
	}
}

那么我们看看当给定一个初始数组之后会进行哪些操作吧:

初始数组:
[8, 7, 6, 5, 4, 3]

[8, 7, 6, 5, 4, 3]//看8和7,交换了
[7, 8, 6, 5, 4, 3]

[7, 8, 6, 5, 4, 3]//看8和6,交换了
[7, 6, 8, 5, 4, 3]

[7, 6, 8, 5, 4, 3]//看8和5,交换了
[7, 6, 5, 8, 4, 3]

[7, 6, 5, 8, 4, 3]//以此类推
[7, 6, 5, 4, 8, 3]

[7, 6, 5, 4, 8, 3]
[7, 6, 5, 4, 3, 8]
第一次遍历结束
[7, 6, 5, 4, 3, 8]
[6, 7, 5, 4, 3, 8]

[6, 7, 5, 4, 3, 8]
[6, 5, 7, 4, 3, 8]

[6, 5, 7, 4, 3, 8]
[6, 5, 4, 7, 3, 8]

[6, 5, 4, 7, 3, 8]
[6, 5, 4, 3, 7, 8]

[6, 5, 4, 3, 7, 8]
[6, 5, 4, 3, 7, 8]
第二次遍历结束
[6, 5, 4, 3, 7, 8]
[5, 6, 4, 3, 7, 8]

[5, 6, 4, 3, 7, 8]
[5, 4, 6, 3, 7, 8]

[5, 4, 6, 3, 7, 8]
[5, 4, 3, 6, 7, 8]

[5, 4, 3, 6, 7, 8]
[5, 4, 3, 6, 7, 8]

[5, 4, 3, 6, 7, 8]
[5, 4, 3, 6, 7, 8]
第三次遍历结束
[5, 4, 3, 6, 7, 8]
[4, 5, 3, 6, 7, 8]

[4, 5, 3, 6, 7, 8]
[4, 3, 5, 6, 7, 8]

[4, 3, 5, 6, 7, 8]
[4, 3, 5, 6, 7, 8]

[4, 3, 5, 6, 7, 8]
[4, 3, 5, 6, 7, 8]

[4, 3, 5, 6, 7, 8]
[4, 3, 5, 6, 7, 8]
第四次遍历结束
[4, 3, 5, 6, 7, 8]
[3, 4, 5, 6, 7, 8]

[3, 4, 5, 6, 7, 8]
[3, 4, 5, 6, 7, 8]

[3, 4, 5, 6, 7, 8]
[3, 4, 5, 6, 7, 8]

[3, 4, 5, 6, 7, 8]
[3, 4, 5, 6, 7, 8]

[3, 4, 5, 6, 7, 8]
[3, 4, 5, 6, 7, 8]

[3, 4, 5, 6, 7, 8]
[3, 4, 5, 6, 7, 8]
第五次遍历结束
[3, 4, 5, 6, 7, 8]
[3, 4, 5, 6, 7, 8]

[3, 4, 5, 6, 7, 8]
[3, 4, 5, 6, 7, 8]

[3, 4, 5, 6, 7, 8]
[3, 4, 5, 6, 7, 8]

[3, 4, 5, 6, 7, 8]
[3, 4, 5, 6, 7, 8]
第六次遍历结束
[3, 4, 5, 6, 7, 8]

是不是很简单?

6.4.2.2、第一次修改

从上面的程序不难发现,第五次遍历结束之后,数组就已经排列好了,但是程序不知道,于是程序执行了第六次遍历。这显然不必要,所以我们可以再改:
BubbleSort.java

package com.sort;

import java.util.Arrays;
public class BubbleSort {
	public static void main(String[] args) {
		int[] a = {8, 7, 6, 5, 4, 3};
		for (int i = 0; i < a.length-1; i++) {//避免没有必要的最后一次执行
			for (int j = 0; j < a.length-1; j++) {//如果执行a.length次就会越界
				if (a[j] > a[j+1]){//如果前一个数比后一个大就交换
					int tmp = a[j];
					a[j] = a[j+1];
					a[j+1] = tmp;
					//这几行是交换
				}
			}
		}
		System.out.println(Arrays.toString(a));
	}
}
6.4.2.3、第二次修改

再次思考,我们发现,每遍历完一次之后,最大数总是被移到其他小一点的数的后面,也就是说,每遍历完一次,数组最后排列好的部分就会变长一点,并且不需要遍历到排序好的部分,看之前的动图就懂了注意看最后面橙色部分:
之前的动图
所以还可以优化,代码:
BubbleSort.java

package com.sort;

import java.util.Arrays;
public class BubbleSort {
	public static void main(String[] args) {
		int[] a = {8, 7, 6, 5, 4, 3};
		for (int i = 0; i < a.length-1; i++) {//避免没有必要的最后一次执行
			for (int j = 0; j < a.length-1-i; j++) {//每次就不需要再遍历排序好的部分了
				if (a[j] > a[j+1]){//如果前一个数比后一个大就交换
					int tmp = a[j];
					a[j] = a[j+1];
					a[j+1] = tmp;
					//这几行是交换
				}
			}
		}
		System.out.println(Arrays.toString(a));
	}
}
6.4.2.4、第三次修改

假如初始数组是{2, 7, 3, 8, 2, 3},上面的代码会进行怎样的操作呢?

初始数组:
[2, 7, 3, 8, 2, 3]

[2, 7, 3, 8, 2, 3]
[2, 7, 3, 8, 2, 3]

[2, 7, 3, 8, 2, 3]
[2, 3, 7, 8, 2, 3]

[2, 3, 7, 8, 2, 3]
[2, 3, 7, 8, 2, 3]

[2, 3, 7, 8, 2, 3]
[2, 3, 7, 2, 8, 3]

[2, 3, 7, 2, 8, 3]
[2, 3, 7, 2, 3, 8]
第一次循环结束
[2, 3, 7, 2, 3, 8]
[2, 3, 7, 2, 3, 8]

[2, 3, 7, 2, 3, 8]
[2, 3, 7, 2, 3, 8]

[2, 3, 7, 2, 3, 8]
[2, 3, 2, 7, 3, 8]

[2, 3, 2, 7, 3, 8]
[2, 3, 2, 3, 7, 8]
第二次循环结束
[2, 3, 2, 3, 7, 8]
[2, 3, 2, 3, 7, 8]

[2, 3, 2, 3, 7, 8]
[2, 2, 3, 3, 7, 8]

[2, 2, 3, 3, 7, 8]
[2, 2, 3, 3, 7, 8]
第三次循环结束//这个时候其实已经排序好了,但是程序不知道
[2, 2, 3, 3, 7, 8]
[2, 2, 3, 3, 7, 8]

[2, 2, 3, 3, 7, 8]
[2, 2, 3, 3, 7, 8]
第四次循环结束
[2, 2, 3, 3, 7, 8]
[2, 2, 3, 3, 7, 8]
第五次循环结束
[2, 2, 3, 3, 7, 8]

不难看出,在第三次循环结束时,数组已经排好了,但是程序不知道,于是还继续遍历。这显然不好,所以我们只需要判断一次遍历有没有交换(有没有逆序对),如果没有了就直接结束。代码改成:
BubbleSort.java

package com.sort;

import java.util.Arrays;
public class BubbleSort {
	public static void main(String[] args) {
		int[] a = {2, 7, 3, 8, 2, 3};
		for (int i = 0; i < a.length-1; i++) {
			boolean flag = true;//判断是否交换过,一定要放在循环里,因为每次都要让它为true
			for (int j = 0; j < a.length-1-i; j++) {
				if (a[j] > a[j+1]){
					int tmp = a[j];
					a[j] = a[j+1];
					a[j+1] = tmp;
					flag = false;//交换过了,所以设为false
				}
			}
			if (flag) break;
		}
		System.out.println(Arrays.toString(a));
	}
}

这时,它在最好情况下的时间复杂度才是O(n)。
排序算法还有很多,大家有兴趣可以自己学习一下。

6.4.3、sort()方法

Java中的sort方法很好用,一般实际项目开发都会使用sort()方法,语法如下:

Arrays.sort(需要排序的数组);

7、类、对象和方法

前面讲过,Java中所有的程序都是由类组成的,那对象(实例)是什么呢?
当我们创建一个对象,就相当于造了一部手机。我们发现,每部手机都有相同的功能:拍照,发信息等,但是每个人手机里的数据都不一样。对象也是一样,从同一个类创建的对象都有相同的方法,相同的属性(类的成员变量也可以叫属性)但是每个属性的值可以不同,这就是对象。

Tips:
成员变量:
成员变量是指声明在类中,方法外的变量。成员变量可以被类中的所有方法访问,且可以用权限修饰符修饰。当使用public或protected修饰符时,其他类可以通过对象名.成员变量名来访问它。
局部变量:
局部变量是指声明在方法中的变量,它的作用域只限定在这个方法内。

7.1、构造函数

构造函数是一个特殊的方法,它会在对象创建时执行,它的形参就是创建对象时的形参。它的语法:

权限修饰符 类名(形参1,...){
	//codes...
}

形参有几个都可以。类名必须和拥有此函数的类名相同。权限修饰符决定了可以使用此构造函数创建对象的范围

Tips:
Java中任何函数(包括构造函数)都可以有多种不同形式的声明,例如:

public class Dog {
	String name;
	int age;

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

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

	public Dog(){
	}
}	

当没有声明构造函数时,默认的构造函数是没有形参且不执行任何操作的。当声明了带有参数的构造函数时,如果不特殊声明一个不带参数构造函数,则必须使用声明的带参数的构造函数。

7.1.1、形参,实参

方法有时需要一些参数来确定需要执行的操作,就像数学里的函数,自变量x决定了函数的值,方法的形参也就决定了方法的操作。
形参和实参有时会被人搞混。形参是声明方法时,用来标记方法所需的输入值所使用的。声明形参的语法如下:

方法(数据类型 形参名, 数据类型 形参名){

需要注意的有以下几点:

  1. 每个形参之间用逗号,分隔。
  2. 形参不能重名,但是可以与成员变量重名。当它与成员变量重名时,为了表示成员变量,通常需要用this.成员变量名来表示。
  3. 可以没有形参。

实参就是调用带有形参的方法时传入的参数,例如System.out.println("A string");中的"A string"就是一个实参。

7.2、创建对象

我们先来写一个Dog类:
Dog.java

package com.class_test;

public class Dog {
	String name;
	int age;

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

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

	public Dog(){
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public void roll(){
		System.out.println(name + "打滚了");
	}

	public void eat(){
		System.out.println(name + "吃东西了");
	}

	public void sit(){
		System.out.println(name + "坐下了");
	}

	public void tellAge(){
		System.out.println(name + "的主人说:“" + name + age + "岁了”");
	}
}

接着写一个Main类,在里面创建两个Dog类的实例(创建两个对象):
Main.java

package com.class_test;

public class Main {
	public static void main(String[] args) {
		Dog teddy = new Dog("Teddy",2);
		Dog mercy = new Dog("Mercy",3);
		teddy.eat();//调用成员方法必须通过实例调用
		mercy.sit();
		teddy.roll();
		mercy.tellAge();
	}
}

运行结果:

Teddy吃东西了
Mercy坐下了
Teddy打滚了
Mercy的主人说:“Mercy3岁了”

7.2.1、this关键字

this关键字在不同位置有不同的意义。它的一种意义是使用this.成员变量名表示自己的成员变量,特别是当某个局部变量与成员变量重名的时候(如构造函数的形参与需要赋值的成员变量相同)
有时this可以表示本类。例如:
ThisTest.java

public class ThisTest {
	private String name;//成员变量
	public static void main(String[] args) {
		ThisTest t = new ThisTest("This test");
		t.f();
	}
	private ThisTest(String name){
		this.name = name;//这是this的一种用法,this.name表示第2行的name,第二个name表示形参name
	}
	private ThisTest(){
		this("Deafult Name");//这是this的一种用法,调用本类的另一个构造函数
	}
	@Override //重写toString是为了能够输出name变量而不是这个对象的地址
	public String toString(){
		return this.name; //这里的this实际上是亢余的,但是编译的时候也会自动添加上去
	}
	private void f(){
		a(this);//this在这种情况下表示的是这个对象,也就是main方法里的t。静态方法里不能使用this关键字
	}
	private static void a(Object a){
		System.out.println(a.toString());
	}
}

7.3、继承

每个类都可以继承一个类,使用extends关键字表示继承,例如:
Animal.java

package com.class_test;

public class Animal {
	protected int age;//使用protected是因为想要让子类可以访问
	protected String gender;
	protected String name;
	public Animal(String gender,int age,String name){
		this.gender = gender;
		this.age = age;
	}
	public Animal(){
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public String getGender() {
		return gender;
	}

	public void setGender(String gender) {
		this.gender = gender;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public void eat(){
		System.out.println(name + "吃东西了");
	}
	public void sleep(){
		System.out.println(name + "去睡觉了");
	}
}

Cat.java

package com.class_test;

public class Cat extends Animal{
	boolean is_pet;
	public Cat(String name,int age,String gender,boolean is_pet){
		super(name,age,gender);
		//super关键字是调用重写前的父类的方法或构造函数。它必须放在第一行。
		this.is_pet = is_pet;
	}
	public Cat(){
		super();
	}

	public boolean isIs_pet() {
		return is_pet;
	}

	public void setIs_pet(boolean is_pet) {
		this.is_pet = is_pet;
	}

	@Override//这个注解代表重写了父类方法
	public void eat() {
		System.out.println("小猫" + name + "吃东西了");
	}

	@Override
	public void sleep() {
		System.out.println("小猫" + name + "去睡觉了");
	}

	@Override
	public void setName(String name) {
		super.setName(name);//调用重写前的父类方法。这时它并不需要放在第一行。
		System.out.println("你给小猫起名字叫" + name + "了");
		/*
		等价于
		System.out.println("你给小猫起名字叫" + name + "了");
		super.setName(name);
		在一些特殊情况下,你需要注意什么时候调用父类方法。
		*/
	}

	public void sit(){
		System.out.println("小猫" + name + "坐下了");
	}
	
	public void is_it_pet(){
		if (is_pet){
			System.out.println("小猫" + name + "是一只宠物猫");
		}else {
			System.out.println("小猫" + name + "不是一只宠物猫");
		}
	}
}

下面将涉及到的几个知识点拆开讲讲:

7.3.1、父类和子类的概念

当一个类继承了一个另一个类,这个类就叫子类,被继承的那个类就叫父类。子类可以使用父类标记为protected(同包的其他类也可以)的成员,且可以重写父类的方法。

Tip:
Java中所有的类都继承与Object类,即使不声明也会隐式继承Object类。

7.3.2、super关键字

super关键字在重写父类方法或写构造函数时,可以放在方法内部第一行执行,它代表了父类的方法或构造函数,例如:

public class Cat extends Animal{
	public Cat(String name,int age,String gender,boolean is_pet){
		super(name,age,gender);//调用了父类的构造函数
		this.is_pet = is_pet;
	}
	@Override
	public eat(){
		super();//调用了父类的eat方法
	}
}

7.4、内部类

类中也可以有类,创建内部类的便利在于内部类可以直接使用外部类的成员方法和变量,而且一般作为小工具使用(例如一个没必要为public的线程)
内部类的创建语法与类的语法相同。

7.5、静态方法

Java中大部分的代码都放在方法中,方法我们已经会创建了,所以我们来讲讲静态方法。
静态static变量/方法在类加载的过程中被初始化,而且在内存中只存在一份,所以可以把它当作是全局变量/方法
静态方法在程序开始执行之后就可以通过类名.方法名直接调用,静态方法在 标准模板库 中常见于将两个不可直接转换的类之间互相转换或者获取一个抽象类的实例(或者为了偷懒,让程序员可以直接调用某些方法而不需要创建实例,例如Math.ramdon();

Tip:
main函数就是一个静态方法

Java为了安全,对静态方法做了限制:静态方法不能直接使用本类的成员方法(或变量和内部类),需要创建对象后使用(但是可以直接使用声明为静态的方法、变量和内部类)

7.6、final关键字与类

当一个类被声明为final时,它将不能被继承,如:
Animal.java

package com.class_test;

public final class Animal {//不能被继承
	protected int age;//使用protected是因为想要让子类可以访问
	protected String gender;
	protected String name;
	public Animal(String gender,int age,String name){
		this.gender = gender;
		this.age = age;
	}
	public Animal(){
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public String getGender() {
		return gender;
	}

	public void setGender(String gender) {
		this.gender = gender;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public void eat(){
		System.out.println(name + "吃东西了");
	}
	public void sleep(){
		System.out.println(name + "去睡觉了");
	}
}

Cat.java

package com.class_test;

public class Cat extends Animal{//继承了final类,会报错
	boolean is_pet;
	public Cat(String name,int age,String gender,boolean is_pet){
		super(name,age,gender);//super关键字是调用重写前的父类的方法或构造函数
		this.is_pet = is_pet;
	}
	public Cat(){
		super();
	}

	public boolean isIs_pet() {
		return is_pet;
	}

	public void setIs_pet(boolean is_pet) {
		this.is_pet = is_pet;
	}

	@Override//这个注解代表重写了父类方法
	public void eat() {
		System.out.println("小猫" + name + "吃东西了");
	}

	@Override
	public void sleep() {
		System.out.println("小猫" + name + "去睡觉了");
	}

	public void sit(){
		System.out.println("小猫" + name + "坐下了");
	}
	
	public void is_it_pet(){
		if (is_pet){
			System.out.println("小猫" + name + "是一只宠物猫");
		}else {
			System.out.println("小猫" + name + "不是一只宠物猫");
		}
	}
}

因为Animals是final类,不能被Cat类继承,所以编译时就会报错,IDE也会向你发出警告。

结语

到这里,Java的基础你已经学会了。当然后面的路还有很长,敬请期待我的续作吧!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值