跳至主要內容

[JAVA]-基础知识点

holic-x...大约 163 分钟JAVAJAVA

[JAVA]-基础知识点

[TOC]

1.Java基础

Java概述

Java基础概念

概念梳理

程序:计算机执行某些操作或者解决问题而编写的一系列有序指令的集合

JAVA技术体系平台

​ Java SE(Java Standard Edition)标准版(旧称J2SE):支持面向桌面级应用(如Windows下的应用程序)的Java平台,提供了完整的Java核心API,此版本以前称为

​ Java EE(Java Enterprise Edition)企业版(旧称J2EE):是为开发企业环境下的应用程序提供的一套解决方案。该技术体系中包含的技术如:ServletJsp等,主要针对于Web应用程序开发。 ​ Java ME(Java Micro Edition)小型版(旧称J2ME):支持Java程序运行在移动终端(手机、PDA)上的平台,对Java API有所精简,并加入了针对移动终端的支持

Java核心特点

1)面向对象(OOP)

2)健壮性:Java的强类型机制、异常处理、垃圾的自动收集等是Java程序健壮性的重要保证

3)跨平台性:一个编译好的.class文件可以在多个系统下运行,这种特性称为跨平台

4)解释型语言:解释性语言(javascript、PHP、java)编译后的代码不能被机器直接执行,需要借助解释器来指令;而类似C/C++这类编译性语言,其编译后的代码可以直接被机器执行

Java开发工具

editplus、notepad++、submitText

idea、eclipse

Java运行机制&运行过程

Java跨平台性

基于JVM支持,同一个Java程序在三个不同的操作系统中都可以执行,进而实现Java程序的跨平台性

image-20240426172933115

Java核心机制-Java虚拟机(JVM Java Virtual Machine)

​ JVM 是一个虚拟的计算机,具有指令集并使用不同的存储区域。负责执行指令,管理数据、内存、寄存器,包含在JDK 中

​ 对于不同的平台,有不同的虚拟机。

​ Java 虚拟机机制屏蔽了底层运行平台的差别,实现了“一次编译,到处运行(参考上图所示)

JDK VS JRE

​ JDK :(Java Development Kit Java 开发工具包)

​ JDK = JRE + java 的开发工具 [java, javac,javadoc,javap 等]

​ JDK 是提供给 Java 开发人员使用的,其中包含了 java 的开发工具,也包括了 JRE。所以安装了 JDK,就不用在单独安装 JRE 了

​ JRE(Java Runtime Environment Java 运行环境):JRE = JVM + Java 的核心类库[类],包括 Java 虚拟机(JVM Java Virtual Machine)和 Java 程序所需的核心类库等,如果想要运行一个开发好的 Java 程序,

JDK、JRE、JVM

​ JDK = JRE + 开发工具集(javac、java编译工具等)

​ JRE = JVM + Java SE 标准类库(java核心类库),如果想要运行开发好的.class文件只需要JRE环境

Java 入门 demo - HelloWorld

1)创建一个HelloWorld.java文件

public class HelloWorld{
	public static void main(String[] args){
		System.out.println("Hello World!");
	}
}

2)cmd窗口进入指定目录,执行javac编译指令,编译后会生成一个HelloWorld.class文件

javac HelloWorld.java

image-20240426174626518

对象在内存中的存在形式(JVM知识点,重点)

image-20240429072657498

Java核心基础

【1】基本的java语法概念

🔖分隔符

​ Java语言里的分号(😉、花括号({})、方括号([])、圆括号(())、空格、圆点(.)都有特殊的分隔作用,因此被统称为分隔符

分号

​ Java语言里对语句的分隔不是使用回车来完成的,Java语言采用分号(;)作为语句的分隔,因此 每个Java语句必须使用分号作为结Java程序允许一行书写多个语句,每个语句之间以分号隔开即可:一个语句也可以跨多行,只要在最后结束的地方使用分号结束即可。

​ 不仅如此,虽然Java语法允许一行书写多个语句,但从程序可读性角度来看,应该避免在一行书 写多个语句

花括号

花括号的作用就是定义一个代码块,一个代码块指的就足“{和“}所包含的一段代码,代码块 在逻辑上是一个整体。

{
	代码块
}

​ 对Java进言而言,类定义部分必须放在一个代码块中,方法体部分也必须放在 一个代码块。除此之外,条件语句中的条件执行体和循环语句中的循环体通常也放在代码块里。 花括号总是成对出现的,“{ }相互匹配”。

🔖标识符和关键字

标识符就是用于给程序中变量、类、方法命名的符号

使用标识符时,需要注意如下规则

  • 标识符可以由字母、数字、下画线(_)和芡元符($)组成,其中数字不能打头。

  • 标识符不能是Java关键字和保留字,但可以包含关键字和保留字。

  • 标识符不能包含空格。

  • 标识符只能包含美元符($),不能包含@、#等其他特殊字符。

  • java语言区分大小写

EG:判断下列哪些是是合法标识符

User √ public × public_a √ 3dmax × room# × class × CN中国 √

常见的关键字

常见关键字
abstractbooleanbreakbytecasecatch
charclasscontinutedefaultdodouble
elseextendsfalsefinalfinallyfloat
forifimplementsimportinstanceofint
interfacelongnativenewnullpackage
privateprotectedpublicreturnshortstatic
superswitchsynchronizedthisthrowthrows
transienttruetryvoidvolatilewhile

常见关键字分类说明

常见关键字
用于定义数据类型的关键字
classinterfacebyteshortint
longfloatdoublecharboolean
void
用于定义数据类型值的关键字
ifelseswitchcasedefault
whiledoforbreakcontinute
return
用于定义访问权限修饰符的关键字
privateprotectedpublic
用于定义类与类之间关系的关键字
extendsimplements
用于定义建立实例及引用实例,判断实例的关键字
newthissuperinstanceof
用于异常处理的关键字
trycatchfinallythrowthrows
用于包的关键字
packageimport
其他修饰符关键字
nativestricftptransientvolatileassert

🔖注释

注释:单行注释、多行注释、文档注释

对自己的代码进行解释分析,以供他人参考,也给自己提供思路

  • 单行注释和多行注释
public class Test{
    // 单行注释
    public static void main(String[] args){
        /*
        	多行注释
        	......
        */        
        System.out.println("Hello");
    }
}

​ Java语言还提供了一种功能更强的的注释形式:文档注释,如果编写java源代码时添了合适的文档注释,然后通过jdk提供的javadoc工具可以直接将源代码里的文档注释提取成一份系统的API文档(以下是java.lang包下的string文档注释)

​ 通过自己编写生成对应的API文档,在对应的workspace文件夹下建立两个类“JavaDocTest和Test”,添加相应的说明注释

public class Test{
	// 简单成员变量测试
	public int age;
	// Test类的构造器测试
	public Test(){}
}
public class JavaDocTest{
	// 测试成员变量
	private String name;
	// 主函数、测试入口
	public static void main(String[] args){
		System.out.println("JavaDocTest");
	}
}

​ 随后通过cmd下命令行用javadoc命令生成文档注释,显示结果如下

javadoc -d apidoc -windowtitle javadoctest -doctitle study *test.java

​ 通过上述命令,在对应的workspace文件夹下生成了apidoc文件夹,里面有相应的系统自动生成的内容

Javadoc命令的分析:

Javadoc –d [指定目录] –windowtitle [api窗口标题] –doctitle javadoc study –header *Test.java
Javadoc –d apidoc –windowtilte javadocTest –doctitle javadoc study –header *Test.java
-d:该选项指定一个目录
–windowtitle:该选项指定api窗口的标题
-doctitle:用于指定页面的标题
-header:指定页眉

【2】Java的基本数据类型

🔖基本数据类型

Java语言是强类型 (strongly typed)语言,强类型包含两个方面的含义:

  • 所有的变量必须先声明、后使用

  • 指定类型的变量只能接受类型之匹配的值。

常见的数据类型分类

基本数据类型:

  • 数值型:整数类型(byte、short、int、long)

  • 字符型:char

  • 布尔型:boolean

引用数据类型:

​ 类(对象)、接口、数组

数据类型说明字节
int整型4
double双精度浮点型8
float单精度浮点型4
char字符型2
boolean布尔型1
# 参考示例:
public class VarDemo {
	public static void main(String[] args) {
		//定义变量:数据类型   标识符  =  初始化值  ;
		//1.定义整数类型的变量(byte、short、int、long)
		byte b = 10;	     //字节类型   1个字节   8位	    范围:-2^7~2^7-1
		short s = 20;	//短整型     2个字节   16位	范围:-2^15~2^-1
		int i = 30;		//整型       4个字节   32位	范围:-2^31~2^31-1
		long l = 40;	    //长整型      8个字节   64位	范围:-2^63~2^63-1
		//2.定义浮点类型的变量(float、double,其区别在于精确度不同、存储范围不同)
		float f = 1.0f;	//单精度浮点型   4个字节   32位  
		double d = 2.0;	//双精度浮点型   8个字节   64位   
		//3.定义字符类型的变量(char)
		char ce = 'a';	//字符型	2个字节	16位
		char cc = '中';	//char类型的变量可以存储一个中文或者是一个英文
		//4.定义布尔类型的变量(boolean)
		boolean bt = true;	//布尔类型   1个字节   8位
		boolean bf = false;	//boolean类型的变量的值只能是true或者false
		/**	
		 * 基本数据类型注意的几个问题:
		 * 1.String类型并不是基本类型,要区分开来!!!
		 * 2.通常的浮点数据类型在不声明的情况下均为double类型,
		 * 	   如果要表示一个数据是浮点类型,则在数据后加上f进行标识
		 * 3.注意字符‘1’与数字1的区别
		 * 4.ASCII编码表对应的常用数据简单进行记忆
		 * 	 字符‘0’-‘9’:48-57
		 * 	 字符‘A’-‘Z’:65-90
		 * 	 字符‘a’-‘z’:97-122
		 * 5. 单精度浮点型范围:3.402823e+38 ~ 1.401298e-45
		 * 	     双精度浮点型范围:1.797693e+308~ 4.9000000e-324
		 * 	  e+38表示乘以10^38,e-45表示乘以10^-45,以此类推
		 */
	}
}

🔖各种类型定义分析及常见错误

  • 整型
public class IntegerDemo {
	public static void main(String[] args) {
		//定义整型数据需要注意的细节
		//1.赋值的类型不能超出当前操作类型的范围,否则报错
		//byte b = 128 ;   byte类型定义的数据的范围为-128-127
		//2.如果是定义巨大的整数值,则使用L(强制使用long类型)
		long bigValue = 123456789123456789L;
		//3.与进制相关的问题
		//定义两个8位的二进制数(以0b、0B开头,只有0、1)
		int bin1 = 0b11010100;
		int bin2 = 0B00001010;
		int bin3 = 0B11111111111111111111111111111101;
		/**
		 * 定义32位的2进制数时,该二进制数的最高位为符号位
		 * 当符号位为0,则为正数
		 * 当符号位为1,为负数,负数是以补码形式存在,需要转化为相应的原码
		 * 正数:原码=反码=补码
		 * 负数:反码=原码的符号位不变,其他位数按位取反
		 * 	         补码=反码+1
		 */	
		//定义八进制数(以0开头,0-7)
		int o = 07;
		//定义16进制数(以0x、0X开头,有0-9、a-f)
		int h1 = 0x13;
		int h2 = 0XaF;
	}
}
  • 字符型
public class CharDemo {
	public static void main(String[] args) {
		//定义char类型数据需要注意的细节
		//1.可以直接指定单个字符为内容(中文字符、英文字符)
		char eChar = 'a';
		char cChar = '中';
		//2.可以直接把char类型的数据转化为int类型的数据使用(即其对应的ascii码)
		int eValue = eChar;
		int cValue = cChar;
		System.out.println(eValue);
		System.out.println(cValue);
		//3.Char类型只能存储一个中文字符或者英文字符,如果是定义一个字符串则使用String等其它类型
		String str = "haha";
	}
}
  • 浮点型
public class FloatDemo {
	public static void main(String[] args) {
		//定义浮点类型的数据需要注意的细节
		//1.float数据有一定的精确范围,超出这个范围的数据会被舍弃
		float af = 5.234555333444f;
		System.out.println(af);	//得到的值发生了改变
		//2.与非数相关的概念
		/**
		 * java中有内置函数将double变量初始化为正无穷大和负无穷大
		 * Double.POSITIVE_INFINITY 将double变量初始化为 正无穷大
		 * Double.NEGATIVE_INFINITY 将double变量初始化为 负无穷大
		 * java规定:0/0、inf/inf、0*inf(inf代表无穷)等运算会产生非数(Not a Number)
		 * 非数用NaN、nan表示 ,其具有以下性质
		 * 1.只要NaN参加四则运算或函数作用于非数,其结果也一定是非数,即具有传递性。
		 * 2.非数没有大小的概念,因此不能进行逻辑运算也不能比较两个非数的大小。
		 * 3.所有正无穷大的数值都是相等的,所有负无穷大的数值都是相等的,
		 *   而NaN(非数)不与任何数值相等,甚至和NaN也不等。
		 */
		double a = 0.0;
		double infb = Double.POSITIVE_INFINITY;
		double infs = Double.NEGATIVE_INFINITY;
		System.out.println(a/a);	//NaN
		System.out.println(infb/infb);	//NaN
		System.out.println(infs/infs);	//NaN
		System.out.println(infb/infs);	//NaN
		System.out.println(7.0/0==6.0/0);	//true
		System.out.println(-7.0/0==-6.0/0);	//true
		System.out.println(a/a==a/a);	//false
		//System.out.println(6/0);不能是6/0,会抛出异常,报出by zero错误	
	}
}
- Jdk7.0以后引入了一个新的功能,可以通过下划线定义数据
public class UnderscoreTest {
	public static void main(String[] args) {
		//可以通过下划线定义数据,便以区分、不易出错
		//定义一个32位的二进制数据,最高位为符号位
		int binValue = 0b0000_0000_0000_0000_0000_0000_0000_0011;
		System.out.println(binValue);
		//定义一个double类型的数据
		double pia = 3.14_15_92_65_36;
		System.out.println(pia);
		double weight = 82_92.11_3_5;
		System.out.println(weight);
	}
}
  • 布尔型
布尔型只有一个结果 要么是true要么是falseboolean flag1=true;
boolean flag2=false;

🔖Java基本数据类型的类型转换

a.自动类型转换

​ 满足自动类型转换的条件:目标类型能和源类型兼容(如double类型兼容int型,但char类型不能兼容int型),在进行赋值或者运算的时候,精度小的类型自动转化为精度大的数据类型

从小到大精度排序:(结合八种基本数据类型进行记忆)

  • char=>int=>long=>float=>double
  • byte=>short=>int=>long=>float=>double

​ 其中(byte、short)和char之间不会自动转化,他们可以转化为int类型参与计算

int age = 19;
char sex = '女';
char result = age + sex; // int类型不能自动转化为char类型

​ 目标类型大于源类型。(如double类型长度为8字节、int类型长度为4字节,则double类型的变量可以直接存放int类型的数据,反之则不行)

double avg1 = 75.5;
int avg2 = avg1;// double类型不能自动转化为int类型
public class AutoConversion {
	public static void main(String[] args) {
		//1.自动类型转换:可以将一个较小的数据类型赋值给较大的数值类型
		int a = 6;
		//int类型可以自动转化为float类型
		float f = a;
		System.out.println(f);	//6.0
		//将字节类型赋给double类型
		byte b = 9;
		double d = b;
		System.out.println(d);	//9.0
		//2.常见的错误分析
		//String不是基本类型,任何基本类型无法直接赋给String类型
		//String s = 1;   报错
		//+(加号)的作用:任何基本类型与String进行+运算,此时+为拼接作用
		System.out.println(3+4+"hello!");	//7hello!
		System.out.println("hello!"+3+4);	//hello!34
	}
}
b.强制类型转换
public class NarrowConsversion {
	public static void main(String[] args) {
		//1.强制类型转换:将较大的数据类型强制转换为较小的数据类型
		//转换格式:在当前变量前添加“(要转换的数据类型)”
		//将int转化为byte类型
		int i = 4;
		byte b = (byte) i;
		System.out.println(b);	//4
		//将double类型转换为int类型
		double d = 4.0;
		int id = (int) d;
		System.out.println(id);	//4
	}
}
c.表达式类型的自动提升

​ 当一个算术表达式中包含多个基本类型值的时候,整个表达式的数据类型将自动提升。Java中定义了如下自动提升的规则:

  • 所有的byte类型 short类型 和char类型 自动提升到int类型

  • 整个算术表达式数据类型提升到与表达式中最高的操作数相同

public class AutoPromote {
	public static void main(String[] args) {
		//1.所有的byte类型 short类型 和char类型 自动提升到int类型
		byte b1 = 4;
		byte b2 = 5;
	/**
	 * 	分析:
	 * 	byte b3 = b1+b2;  -->更正为byte b4 = (byte)b1+b2;
	 * 	上式错误,b1+b2运算后系统会自动将byte类型提升到int类型,
	 *  如果要用byte类型的变量接收,则需要将int类型强制类型转换为byte类型,否则报错
	 * 	byte b4 = 4+5;
	 *  该式是先将4+5进行运算,得到9后将其赋值给byte类型的变量,合理
	 */
		//2.整个算术表达式数据类型提升到与表达式中最高的操作数相同。
		short sValue = 5;
		/**
		 * 整数的默认类型是int类型,int+short类型的组合的数据类型会提升到int类型
		 * sValue = sValue+5;
		 * 上式表示错误,由short+5(int)这个表达式计算之后数据类型会自动提升到int类型
		 * 但此时接收表达式的变量sValue的类型是short,则此时需要进行强制类型转换
		 * sValue = (short)sValue+5;
		 */
		//3.再次强调+(加号)作用于字符串起到的是拼接作用
		System.out.println('a'+7+"hello!");	//104hello!
		System.out.println("hello!"+'a'+7);	//hello!a7
		/**
		 * 'a'+7+"hello!":算数运算符(+)的运算顺序是从左到右
		 * 因此上式先将‘a’自动转化为相应的ascii码值97然后执行97+7
		 * 得到结果为104,最后执行104+“hello!”,此时+号起的作用是
		 * “拼接”,将104与字符串“hello!”进行拼接,组合成新的字符串
		 * 而"hello!"+'a'+7则是先将字符串“hello!”与字符‘a’进行
		 * 拼接,随后将得到的新字符串再与数字7直接进行拼接,得到新的字符串
		 */
	}
}

【3】Java中的运算符

🔖Java 语言中常用的运算符

算术运算符、赋值运算符、比较运算符、逻辑运算符、位运算符、条件运算符(三元运算符)

a.算数运算符

算术运算符(+、-、*、/、%、++、--)主要用于进行基本的算术运算,如加法、减法、乘法、除法等。

其中,++-- 既可以出现在操作数的左边,也可以出现在右边,但结果是不同的

赋值运算符是指为变量或常量指定数值的符号。如可以使用 “=” 将右边的表达式结果赋给左边的操作数。

算术运算符的需要注意的问题

  • 如果对负数取模,可以把模数负号忽略不记,如:5%-2=1。但被模数是负数就另当别论。

  • 对于除号“/”,它的整数除和小数除是有区别的:整数之间做除法时,只保留整数部分而舍弃小数部分。

    例如:int x=125,则x/10=12(保留整数部分,舍弃小数部分)

  • “+”除字符串相加功能外,还能把非字符串转换成字符串 (连接字符串的功能)

    例如:System.out.println("5+5="+5+5);//打印结果是?

b.赋值运算符

​ Java 支持的常用赋值运算符,如下表所示:

运算符名称举例
=赋值c=a+b:
+=加等于c+=a:c=c+a
-=减等于c-=a:c=c-a
*=乘等于c *= a:c=c *a
/=除等于c/=a:c=c/a
c.比较运算符

​ Java 中常用的比较运算符如下表所示:

比较运算符名称
>大于
<小于
>=大于等于
<=小于等于
==等于
!=不等于

比较运算符易错点分析:

​ 比较运算符用于判断两个数据的大小,其结果是一个布尔值( true 或 false )。例如:大于、等于、不等于。

​ > 、 < 、 >= 、 <= 只支持左右两边操作数是数值类型

​ == 、 != 两边的操作数既可以是数值类型,也可以是引用类型

​ 如果进行比较的两个操作数都是数值类型,即使它们的数据类型不同,只要其值对应相等,返回值为true

​ 如果进行比较的两个操作数都是引用类型,则只有当两个引用类型具有父子关系的时候才能进行比较,且这两个引用必须指向同一个对象,结果才会返回true

d.逻辑运算符

​ 逻辑运算符主要用于进行逻辑运算。Java 中常用的逻辑运算符如下表所示:

运算符运算范例结果
&AND(与)false&truefalse
|OR(或)false|truetrue
^XOR(亦或)true^falsetrue
!NOT(非)!truefalse
&&AND(短路)false&&truefalse
||OR(短路)false||truetrue

短路概念分析:区分|与||、&与&&的区别

​ “短路”分析:譬如:( one > two ) && ( one < three ) 中,如果能确定左边 one > two 运行结果为 false , 则系统就认为已经没有必要执行右侧的 one < three ,直接返回false结果

​ 同理,在( one > two ) || ( one < three ) 中,如果能确定左边表达式的运行结果为 true , 则系统也同样会认为已经没有必要再进行右侧的 one < three ,直接返回true结果

public class LogicOperatorTest2 {
	public static void main(String[] args) {
		/**
		 * &与&&的区别:
		 * 	&:无论左侧计算结果如何,右侧都将参与运算
		 * 	&&:当左侧表达式运算结果为false,则右侧不再执行(直接得到结果false)
		 * |与||的区别:
		 * 	|:无论左侧计算结果如何,右侧都将参与运算
		 * 	||:当左侧表达式运算结果为true,则右侧不再执行(直接得到结果 true)
		 */
		//简单测试,进行分析
		int a=5,b=10,c=15;
		//++a:先将a执行自增(加1)操作,后将a的值作为整个表达式的值,依次类推进行分析
		if(++a>++b && ++b>++c)
		/**(6>11 && 12>16)false	分析错误,这是常见的误区,一头哉进去
		 * 要注意的时&&在使用的时候,一旦左侧的表达式运算结果为false,右侧将
		 * 不再参与运算,因此在思考问题的时候要适当停止,不要一昧地陷进去
		 * 首先先看左侧的表达式++a>++b-->(++a)>(++b)
		 * (++a)先将a的值执行a=a+1操作,随后将a的值作为整个表达式的值,以此类推
		 * (++b)亦按此分析,可以简化式为“6>11”,此式明显不成立返回false,
		 * 此时可知道左侧的式子返回false,因而右侧表达式不会再执行,且结果返回false,
		 * if语句中的执行语句亦不会再执行,输出语句中输出a++的值,此处理解为先将当前a的值
		 * 作为整个表达式的值输出,随后a执行a=a+1操作,以此类推进行分析
		 * 最终得到结果a=6、b=11、c=15
		 */
		{	//if语句中的条件为false,则不执行if语句中的内容
			//a++:先将a作为表达式的值,随后执行自增操作(a=a+1)
			a++;	
			b++;
			c++;
		}
		System.out.println(a++);	//6
		System.out.println(b++);	//11
		System.out.println(c++);	//15
	}
}
e.条件运算符

条件运算符( ? : )也称为 “三元运算符”。

语法形式:布尔表达式 ? 表达式1 :表达式2

运算过程:如果布尔表达式的值为 true ,则返回 表达式1 的值,否则返回 表达式2 的值

boolean b = (8>5)?true:false;
System.out.println(b);	//true

public class ConOperTest {
	public static void main(String[] args) {
		boolean b1 = true?false:true==true?false:true;
		boolean b2 = false?true:false==false?true:false;
		boolean b3 = false?false:true==true?false:true;
		boolean flag = b1==b2?b2:b3;
		System.out.println(b1);
		System.out.println(b2);
		System.out.println(b3);
		System.out.println(flag);
		/**等于运算符(左结合)的优先级高于条件运算符(右结合)的优先级
		 * 条件表达式的应用:条件?语句1:语句2
		 * 如果条件成立则执行语句1,否则执行语句2,依次进行分析
		 * B1:先将true==true进行计算,进一步简化为“true?false:true?false:true”,
		 * 	      由于条件运算符是右结合,先算后面的部分,进一步简化为“true?false:false”-->false,
		 * 	      依次类推,下述亦是如此进行分析,一定要注意优先级的顺序,否则容易出错!!
		 * B2:化简为“false?true:true”-->true
		 * B3:化简为“false?false:false”-->false
		 * Flag:化简为“false?true:false”-->false
		 */
	}
}
f.位运算符
运算符运算范例
'<<'左移3<<2:3 * 2 * 2 = 12
'>>'右移3>>2:3/2=1
'>>>'无符号右移3>>>2:3/2=1
'&'与运算6&3:2
'|'或运算6|3:7
'^'异或运算6^3:5
'~'反码~6:-7
public class BitOperatorTest {
	public static void main(String[] args) {
		/**
		 * 所有的位运算符均是转换为二进制进行运算
		 * 1.&运算符
		 * 		3 & 5
		 *  3:  0 0 1 1
		 *  5:  0 1 0 1
		 *  1:  0 0 0 1(与运算:两个为1才得到1)
		 */
		System.out.println(3&5);	//1
		/**
		 * 2.|运算符
		 * 		3 | 5
		 * 	3:  0 0 1 1
		 * 	5:  0 1 0 1
		 *  7:  0 1 1 1(或运算:其中一个为1便得到1)
		 */
		System.out.println(3|5);	//7
		/**
		 * 3.^运算符
		 * 		3 ^ 5
		 * 	3:  0 0 1 1
		 * 	5:  0 1 0 1
		 *  6:  0 1 1 0(异或运算:两个相同返回false,不同则返回true)
		 */
		System.out.println(3^5);	//6
		/**
		 * 4.<<左移运算符  (相当于乘以2的倍数)
		 *	x<<移动的位数-->x = x * 2^(移动的位数)
		 */
		System.out.println(3<<3);	//24
		/**
		 * 5.>>右移运算符  (相当于除以2的倍数)
		 *	x>>移动的位数-->x = x / 2^(移动的位数)
		 */
		System.out.println(24>>3);	//3
	}
}

正数:原码=反码=补码

负数:补码=反码+1 原码=补码-1,随后除符号位按位取反

无论正数还是负数,取非时符号位都发生变化,取反时符号位不发生变化

练习题分析: 对两个整数变量进行互换(不借助第三方变量)

public class Exercise {
	public static void main(String[] args) {
		//如何实现不借助第三方变量实现两个变量的交换
		int x = 3,y = 5;
		System.out.println("交换前:x="+x+"--y="+y);
		x = x + y;	//x=3+5=8
		y = x - y;	//y=8-5=3
		x = x - y;	//x=8-3=5
		System.out.println("交换后:x="+x+"--y="+y);
		int a = 3,b = 5;
		System.out.println("交换前:a="+a+"--b="+b);
		a = a ^ b;	//第1步
		b = a ^ b;	//第2步
		a = a ^ b;	//第3步
		System.out.println("交换后:a="+a+"--b="+b);
		/**
		 * 		3 ^ 5
		 * 	3:  0 0 1 1
		 * 	5:  0 1 0 1
		 *  6:  0 1 1 0(异或运算:两个相同返回false,不同则返回true)
		 *  第1步执行后:a=6
		 *  6:  0 1 1 0
		 *  5:  0 1 0 1
		 *  3:  0 0 1 1
		 *  第2步执行之后:b=3
		 *  3:  0 0 1 1
		 *  6:  0 1 1 0
		 *  5:  0 1 0 1
		 *  第3步执行完之后:a=5
		 */
	}
}

🔖Java运算符中的优先级

​ 所谓优先级,就是在表达式中的运算顺序。Java 中常用的运算符的优先级如下表所示:级别为1的优先级最高级别11的优先级最低(不需要死记硬背运算符的优先级顺序,实际开发中,一般会使用小括号辅助进行优先级管理)

优先级运算符结合性
1()、[]、从左到右
2!、+(正)、-(负)、++、--从右到左
3*、/、%从左到右
4+(加)、-(减)从左到右
5<、<=、>、>=从左到右
6==、!=从左到右
7^从左到右
8&&从左到右
9||从左到右
10?:从右到左
11=、+=、-=、*=、/=、%=从右到左

括号>单元操作符>算术运算符>移位操作符>关系运算符>位运算符>逻辑运算符>三元操作符>赋值运算符

【4】Java的基本语句分析

🔖流程控制

​ 不论哪一种编程语言,都会提供两种基本的流程控制结构:分支结构和循环结构。其中分支结构用于实现根据条件来选择性地执行某段代码,循环结构则用于实现根据循环条件重复执行某段代码。

​ 针对流程控制结构语法,Java提供了 if和switch两种分支语句,并提供了 while、do while 和for三种循环语句。除此之外,JDK5还提供了一种新的循环:foreach循环,能以更简单的方式来遍 历集合、数组的元素。

​ Java还提供了 break和continue关键字来控制程序的循环结构。

🔖顺序结构

​ 顺序结构是程序中最简单最基本的流程控制,没有特定的语法结构,按照代码的先后顺序依次执行

🔖分支结构

a.if语句
public class IfTest {
	public static void main(String[] args) {
		//if语句的三种形式
		/**
		 * 1.if(条件表达式)
		 * 		执行语句;
		 */
		if(true)
			System.out.println("if语句形式1测试");
		/**
		 * 2.if(条件表达式)
		 * 		执行语句1;
		 * 	 else
		 * 		执行语句2;
		 */
		if(true)
			System.out.println("if语句形式2测试");
		else
			System.out.println("当条件表达式返回值为false执行这条语句");
		/**
		 * 3.if(条件表达式1)
		 * 		执行语句1;
		 * 	 else if(条件表达式2)
		 * 		执行语句2;
		 * 		......
		 * 	 else if(条件表达式n-1)
		 * 		执行语句n-1;
		 * 	 else
		 * 		执行语句n;
		 */
		int age = 18;
		if(age>0&&age<20)
			System.out.println("年轻人...");
		else if(age>=20&&age<30)
			System.out.println("青年人");
		else if(age>=30&&age<60)
			System.out.println("中年人");
		else
			System.out.println("老年人");
	}
}

对于嵌套的 if 语句,只有当外层 if 的条件成立时,才会判断内层 if 的条件

String today="周末";
String weather="晴朗";
if(today.equals("周末")){ // 外层if结构
  if(weather.equals("晴朗")){ // 内层if结构
    System.out.print("去室外");
  }else {
    System.out.print("去室内");
  }
}else{
  System.out.print("上班");
}
b.switch语句

switch条件语句

执行过程:当 switch 后表达式的值和 case 语句后的值相同时,从该位置开始向下执行,直到遇到 break 语句或者 switch 语句块结束;如果没有匹配的 case 语句则执行 default 块的代码。

​ 当需要对选项进行等值判断时,可使用switch语句简单明了。Switch语句由一个控制表达式和多个case标签组成,和if语句不同的是 Switch语句后面的表达式只能是byte、short、int、char四种基本的数据类型 ,jdk1.7之后加强了Switch新增了String类型 。

public class SwitchTest {
	public static void main(String[] args) {
		/**
		 * switch语句测试:
		 * switch语句后面的表达式可以由byte、short、int、char四种基本类型
		 * jdk1.7后新增了String类型
		 */
		char score = 'B';
		switch(score)
		{
			case'A':
				System.out.println("优秀");
				break;
			case'B':
				System.out.println("良好");
				break;
			case'C':
				System.out.println("中等");
				break;
			case'D':
				System.out.println("及格");
				break;
			case'E':
				System.out.println("不及格");
				break;
			default:
				System.out.println("成绩输入格式错误,请重新进行操作!");
				break;
		}
	}
}
public class SwitchStringTest {
	public static void main(String[] args) {
		String season = "夏天";
		switch(season)
		{
			case"春天":
				System.out.println("春暖花开");
				break;
			case"夏天":
				System.out.println("夏日炎炎");
				break;
			case"秋天":
				System.out.println("秋高气爽");
				break;
			case"冬天":
				System.out.println("白雪皑皑");
				break;
			default:
				System.out.println("季节输入格式错误,请重新输入!");
				break;
		}
	}
}

使用Switch语句时的注意事项

  • switch 后面小括号中表达式的值可为byte、short、int、char、String类型

  • case 后面的值可以是常量数值(如1、2);也可以是一个常量表达式(如2+2),但不能是变量或带有变量的表达式(如 a * 2)

  • case 匹配后,执行匹配块里的程序代码,如果没有遇见break会继续执行下一个的case块的内容,直到遇到break 语句或者switch语句块结束

  • 可以将功能相同的case语句合并起来,如:

case 1:
case 2:
	System.out.print("test");

​ 上述表示当判断的数据是1或2是可输出该语句,即如果一个case没有与之对应的语句(基本语句或跳转语句),则继续执行下一步。

  • default 块可以出现在任意位置,也可以省略

🔖循环结构

​ 循环语句可以在满足循环条件的情况下,反执行某一段代码,这段被重复行的代码被称为循环体。当反复执行这个循环体时,需要在合适的时候把循环条件改为假,从而结束循环,否则循环将一直 执行下去,形成死循环。循环语句可能包含如下4个部分。

​ 初始化语句(init_statement):—条或多条语句,这些语句用于完成一些初始化工作,初始化语句在循环开始之前执行。

​ 循环条件(test_expression):这是一个boolean表达式,这个表达式能决定是否执行循环体。

​ 循环体(body_tatement):这个部分是循环的主体,如果循环条件允许,这个代码块将被重复 执行。如果这个代码块只有一行语句,则这个代码块的花括号是可以锊略的。

​ 迭代语句(iteration_statement):这个部分在一次循环体执行结束后,对循环条件求值之前执行, 通常用于控制循环基件中的变量,使得循环在合适的时候结束。

​ 上面4个部分只是一般性的分类,并不是每个循环中都非常清晰地分出了这4个部分。

a.循环语句while

执行过程分析:先判断、后执行

1>先判断while后面的条件是否成立(true、false)

2>当条件成立时,执行循环体内的操作代码,随后重复1、2步骤直到循环条件不成立为止

public class WhileTest {
	public static void main(String[] args) {
		//1.打印1-10中的奇数
		int i = 1;
		while(i<=10)
		{
			if(i%2==1)
				System.out.println(i);
			i++;
		}
		//2.求1-10的总和
		int x = 1;
		int count = 0;
		while(x<=10)
		{
			count+=x;
			x++;
		}
		System.out.println("1-10的总和为:"+count);
	}
}
b.循环语句do...while

执行过程分析:先执行、后判断

1>先执行一遍循环操作,然后判断循环条件是否成立

2>当条件成立时,继续执行1、2步骤直到循环条件不成立为止

即使循环条件不成立,do...while语句保证循环至少被执行一次

public class DoWhileTest {
	public static void main(String[] args) {
		/**
		 * do...while...与while的区别在于
		 * do...while...语句无论如何都会先执行一次
		 * 而while语句只有当括号里的表达式的值返回true时,才执行相关语句
		 */
		int x = 3;
		while(x<3)
		{
			System.out.println("x="+x);
			x++;
		}
		
		int y=3;
		do{
			System.out.println("y="+y);
			y++;
		}while(y<3);
	}
}
c.循环语句for

for( 表达式1 ; 表达式2; 表达式3){

​ 循环体语句4 ;

}

• 表达式1 :一般是一个赋值语句,用于给变量进行赋初值

• 表达式2:是一个布尔类型的表达式,用于决定什么时候退出循环

• 表达式3: 一般是用来修改变量,用于控制每次循环的方式

执行过程:

1>执行循环变量初始化部分,设置循环的初始状态,此部分在整个循环中只执行一次

2>进行循环条件的判断,如果条件为 true ,则执行循环体内代码;如果为 false ,则直接退出循环

3>执行循环变量变化部分,改变循环变量的值,以便进行下一次条件判断

4>依次重新执行2、3、4步骤,直到退出循环

public class ForTest {
	public static void main(String[] args) {
		//循环打印1-10
		for(int i=1;i<=10;i++)
			System.out.println(i);
		//与for循环语句相关的面试题
		int x = 1;
		for(System.out.println("a");x<3;System.out.println("c"))
		{
			System.out.println("d");
			x++;
		}
		/**
		 * 分析:for(初始化语句;循环条件;循环条件控制表达式)
		 * 	   {
		 * 			执行语句;
		 *     }
		 *     初始化语句只执行一次
		 *     循环条件用于判断是否继续执行相关语句
		 *     循环条件控制表达式用以控制循环条件以防止陷入死循环
		 *     执行顺序:
		 *			1.当初始化条件执行完毕后,判断是否满足当前的循环条件
		 *     	2. 如果满足,则执行相关语句,随后执行循环条件控制表达式
		 *     	3.继续判断是否满足循环条件,满足则执行第2步
		 *      4.如果不满足则跳出循环,结束循环语句!
		 * 		按照上述分析,可以得到“a d c d c ”   
		 */
	}
}

使用for循环语句时的注意事项:

  • for 关键字后面括号中的三个表达式必须用 “ ; ” 隔开,三个表达式都可以省略,但 “ ; ” 不能省略。
// 省略“循环变量初始化”,可以在 for 语句之前由赋值语句进行变量初始化操作,如:
int i=0; // 循环变量在for执行之前初始化
for(;i<10;i++){ // 省略循环变量
  System.out.print("...");
}

// 省略“循环条件”,可能会造成循环一直执行下去,从而导致“死循环”现象。如:
for(int i=0;;i++){
  System.out.print("...");
}
# 在编程过程中要避免“死循环”的出现,因此,对于上面的代码可以在循环体中使用 break 强制跳出循环
  
// 省略“循环变量变化”,可以在循环体中进行循环变量的变化,如:
for(int i=0;i<10;){
  System.out.print("...");
  i++; // 在循环体中控制循环变量i的值
}
  • for 循环变量初始化和循环变量变化部分,可以是使用 “,” 同时初始化或改变多个循环变量的值,如:
for(int i=1,j=5;i<=5;i++,j--){
	System.out.print(i+"+"+j+"="+(i+j));
}

# 代码中,初始化变量部分同时对两个变量i和j赋初值,循环变量变化部分也同时对两个变量进行变化,运行结果分析如下:
1+5=6
2+4=6
3+3=6
4+2=6
5+1=6
  • 循环条件部分可以使用逻辑运算符组合的表达式,表示复杂判断条件,但一定注意运算的优先级,如:
# 代码中,必须同时满足变量i小于10,并且i不等于5时才会进行循环,输出变量i的值
for(int i=1;i<10&&i!=5;i++){
	System.out.print(i);
}
d.多重循环

​ 循环体中包含循环语句的结构称为多重循环。三种循环语句可以自身嵌套,也可以相互嵌套,最常见的就是二重循环。在二重循环中,外层循环每执行一次,内层循环要执行一圈。

举例分析:
使用 * 打印长方形:
*****
*****
*****
实现代码为:
//嵌套的循环语句:打印长方形
		for(int i=0;i<5;i++)
		{
			for(int j=0;j<5;j++)
			{
				System.out.print("*");
			}
			System.out.println();
		}
执行流程:当 i = 1 时,外层循环条件成立,进入内层循环,开始打印第一行内容。此时, j 从 1 开始,循环 8 次,内层循环结束后换行,实现第一行 8* 的输出。接下来返回外层循环 i 变为 2 ,准备打印下一行,依此类推,直到完成长方形的打印。
e.foreach语句

在jdk1.5之后Java提供了一种更简单的循环,该种循环可以迭代遍历数组和集合。语法规则如下:

for( 存储是数据类型  标识符 : 要迭代的集合)
{
	// 执行语句;
}
public class ForEachTest {
	public static void main(String[] args) {
		//循环遍历数组、集合中的数据
		String[] hobbies = new String[]{"看电视","打游戏","阅读","打代码"};
		//1.用for循环语句实现
		for(int i=0;i<hobbies.length;i++)
		{
			System.out.println("第"+(i+1)+"本书:"+hobbies[i]);
		}
		//2.用增强后的for语句实现(foreach)
		int count=1;
		for(String s : hobbies)
		{
			System.out.println("第"+count+"本书:"+s);
			count++;
		}
	}
}

🔖控制循环

​ Java中没有goto关键字,而是作为保留字。所谓的保留字是指当前版本中没有使用,但是也禁止你当做标识符使用,以后的版本可能使用。

​ Java语言没有提供goto语句来控制循环跳转。这种做法提高了程序流程的可读性,但是程序的灵活性降低,为了弥补不足java提供了continue和break来控制循环体除此以外还有return语句可以结束整个方法。

a.跳转语句break

break语句是终止当前整个循环体

public class BreakTest {
	public static void main(String[] args) {
		//break语句用于终止当前循环
		for(int x=0;x<5;x++)
		{
			if(x==1)
				break;	//跳出循环、终止循环
			System.out.println("x="+x);
		}
		/**
		 * 上述语句执行后只输出了“x=0”,因为当x取值为1的时候,此时满足if
		 * 条件语句,执行break操作,直接跳出了当前循环,之后的输出内容不再执行
		 * 因此最终只显示了“x=0”的输出信息
		 */
		//对循环体进行命名,可以用break语句指定要跳出来的循环
		outer:for(int i=0;i<5;i++)
			inner:for(int j=0;j<5;j++)
			{
				System.out.println("i="+i);
			//	break outer;
				break inner;
			}
		/**
		 * 分析:上述语句执行结果显示:由break语句指定当前要跳出的循环
		 * break outer:跳出外循环outer-->当输出“i=0”之后,执行break outer语句
		 * 			        直接跳出外循环,且无其他语句可执行,因此输出结果为“i=0”这句话
		 * break inner:跳出内循环inner-->当输出相应的“i=0”之后,执行break inner语句
		 * 			        随后判断还是满足外循环的条件,因此再次执行内循环操作,以此类推进行分析,
		 * 			        最终分别输出每次外循环执行时当前的i值
		 * 			        即“i=0  i=1  i=2  i=3  i=4”
		 */
	}
}
b.跳转语句continue

continue 的作用是跳过循环体中剩余的语句执行下一次循环

public class ContinueTest {
	public static void main(String[] args) {
		//continue语句的作用:跳过当前循环剩余的语句,执行下一次循环(执行下一条语句)
		for(int x=0;x<5;x++)
		{
			if(x==1)
				continue;	//跳过本次循环,进入下一次循环
			System.out.println("x="+x);
		}
		/**
		 * 上述语句执行后输出结果为“x=0 x=2 x=3 x=4”
		 * 当判断到x=1时,满足if语句的条件,因此执行continue语句,从而跳过当前循环,
		 * 进入下一次循环,继续输出相关信息
		 */
		//对循环体进行命名,可以用break语句指定要跳出来的循环
		outer:for(int i=0;i<5;i++)
			inner:for(int j=0;j<5;j++)
			{
				System.out.println("i="+i);
//				continue outer;
				continue inner;
			}
		/**
		 * 分析:上述语句执行结果显示:由continue语句指定当前要跳出的循环
		 * continue outer:
		 *   跳出当前的外循环outer-->当输出“i=0”之后,执行continue outer语句
		 *   直接跳出当前的外循环,从而进入下一次循环,以此类推进行分析,因此输出结果为
		 *   “i=0  i=1  i=2  i=3  i=4”
		 * continue inner:
		 *   跳出内循环inner-->当输出相应的“i=0”之后,执行break inner语句
		 * 	 直接跳出当前的内循环,随后判断还是满足内循环的条件,因此再次执行内循环操作,以此类推进行分析,
		 *   最终输出“i=0(*5)  i=1 (*5) i=2(*5)  i=3(*5)  i=4(*5)”
		 */
	}
}
c.跳转语句return

return语句:结束整个方法

public class ReturnTest {
	public static void main(String[] args) {
		//return:直接结束整个方法
		for(int i=0;i<3;i++)
		{
			System.out.println("i="+i);
			if(i==1)
				return;
		}
		System.out.println("return结束整个方法");
		/**
		 * 分析:上述语句执行分析为,按照for循环语句的执行顺序一步步进行分析
		 *     当i=0时,满足循环条件,输出“i=0”,随后i执行加1操作,进入下一步判断
		 *     当i=1时,满足循环条件,输出“i=1”,随后判断i=1时满足if语句,则执行
		 *     return语句,结束整个方法,最后的输出提示也不会输出,因此最终显示的信
		 *     息时“i=0 i=1”
		 */
	}
}

【5】数组

​ 数组是大部分编程语言所支持的数据结构,Java也支持数组,Java的数组是一种引用类型的变量,Java程序通过数组引用变量来操作数组,包括获取数组的长度,访问数组的值等。

🔖数组的基本概念

​ 数组是用于存储多个数据,每个数组元素存放一个数据,所有的数据元素具有相同的数据类型,因此在一个数组中,数组元素的类型的唯一的,即一个数组里只能存储一种数据类型的数据,而不能存储多种数据类型的数据。

​ 数组—旦数组的初始化完成,数组在内存中所占的空间将被固定下来,因此数组长度将不可改变。即使把某个数组元素的数据清空,但它所占的空间依然被保留,依然属于该数组,数组的长度依然不变。

​ Java的数组既可以存储基本类型的数据,也可以存储引用类型的数据,只要所有的数组元素具有相同的类型即可。值得指出的是,数组也是一种数据类型,它本身是一种引用类型。例如int是一个基本类型,但int[] (这是定义数组的一种方式)就是一种引用类型了。

数组的基本定义和初始化
a.一维数组的定义和初始化
int[] scores = {84,76,35,49,63};# 声明数组
语法:数据类型[] 数组名;   数据类型 数组名[]; 
int[] scores; // 存储成绩列表,数据类型为整型
double height[]; // 存储身高列表,数据类型为浮点型
String[] names;// 存储姓名列表,数据类型为字符串


# 分配空间:分配空间即是指定数组中最多可存储多少个元素
语法:数组名 = new 数据类型[数组长度];
scores = new int[5];
height = new double[5];
names = new String[5];
亦可将上述两个步骤进行合并,在声明数组的同时为其分配空间
int[] scores = new int[5];

# 赋值:分配空间后就可以向数组中存放数据了,数组中元素都是通过下标来进行访问的
eg:向 scores 数组中存放学生成绩
scores[0]=85;
scores[1]=89;

# 处理数组中的数据:对赋值后的数组进行操作和处理
eg:获取并输出数组中元素的值
System.out.pprint(score[2]);Java中还提供了另外一种直接创建数组的方式,它将声明数组、分配空间和赋值合并完成,如
int[] scores = {84,76,35,49,63};// 创建一个长度为5的整型数组并进行初始化
int[] scores = new int[]{84,76,35,49,63}; // 此种指定方式new int[]中的长度指定必须为空,不能指定
public class ArrayTest {
	public static void main(String[] args) {
		//一维数组定义的四种方式
		//1.仅仅声明数组:数据类型[] 数组名称;
		String[] strArr;
		//2.定义数组并初始化: 数据类型 [] 数组名称 = {数组元素1,数组元素2,...};
		String[] strArr1 = {"语文","数学","英语"};
		//3.使用new关键字定义数组大小,并在内存中开辟一个新的空间
		//定义格式:数据类型[] 数组名称 = new 数据类型[数组大小];
		int[] arr = new int[5];
		//4.使用new关键字实现数组的定义并完成初始化
		//定义格式:数据类型[] 数组名称 = new 数据类型[]{数组元素1,数组元素2,...};
	}
}

​ 执行动态初始化,程序员只需要指定的数组的长度,即为每个数组元素指定内存空间,系统将为这些数组元素分配初始值,指定初始值系统会按照如下方式指定:

  • 数组元素的类型是基本类型中整形类型(byte short int 和long)则数组元素的值是0

  • 数组元素的类型是基本类型中的浮点型(float double)则数组元素的值是0.0

  • 数组元素的类型是基本类型的的字符型(char) 则数组的值是’\u0000’

  • 数组元素的类型是基本类型的布尔型(boolean) 则数组的值是 false

  • 数组元素的类型是引用类型(类、接口、数组)则数组元素的值是null

b.使用循环操作 Java 中的数组
// 实际开发中经常使用循环控制数组成员的操作
int[] scores = {84,76,35,49,63};
for(int i=0;i<scores.length;i++){
	System.out.print(scores[i]);
}
// 数组名.length  用于获取数组的长度
📌java中的内存分配

​ 内存是计算机中的重要原件,用于临时存储区域,其作用是运行程序。

​ 编写的程序是存放在硬盘中的,在硬盘中的程序是不会运行的。程序必须放进内存中才能运行,运行完毕后会清空内存。

​ Java虚拟机要运行程序,必须要对内存进行空间的分配和管理。 为了提高运算效率,就对空间进行了不同区域的划分,每一片区域都有特定的处理数据和内存管理的方式

内存的五片区域

​ 寄存器,栈内存,堆内存,方法区,本地方法区

区域名称作用
寄存器CPU使用
本地方法栈JVM在使用操作系统功能的时候使用
方法区存储可以运行的class文件(字节码文件.class加载时进入的内存)
堆内存存储对象或者数组(通过new创建的内容会进入堆内存,并且存放地址)
方法栈方法运行时使用的内存,比如main方法运行,进入方法栈中执行

​ 内存中的数组:数组引用变量只是一个引用,这个引用变量可以指向任意有效的内存,只有当该引用指向有效内存才可以通过数组变量下标来访问数组

public class ArrayInRamTest {
	public static void main(String[] args) {
		//定义并初始化整型数组arr1
		int[] arr1 = {1,2,3,4,5};
		//声明整型数组arr2
		int[] arr2 = new int[3];
		//分别用for语句、foreach语句遍历两个数组当中的数组元素
		for(int i=0;i<arr1.length;i++)
			System.out.print(arr1[i]+"--");
		System.out.println("\n------分隔符------");
		for(int i : arr2)
			System.out.print(i+"--");
		/**arr1是int[]类型的,且arr2也是int[]类型的,因而可以直接将arr1的值直接
		 * 赋给arr2,即是让arr2的引用直接指向arr1
		 */
		System.out.println("\n------分隔符------");
		arr2 = arr1;
		for(int i : arr2)
			System.out.print(i+"--");
		System.out.println("\n"+arr2.length);
	}
}

内存分配-数组案例说明

1个数组的内存图

public class Demo1{
	public static void main(String[] args){
		int[] arr = new int[3];
		System.out.print(arr);
		System.out.print(arr[0]);
		System.out.print(arr[1]);
		System.out.print(arr[2]);
		arr[0] = 1;
		arr[1] = 2;
		arr[2] = 3;
		System.out.print(arr[0]);
		System.out.print(arr[1]);
		System.out.print(arr[2]);
	}
}

多个数组内存图

public class Demo2{
	public static void main(String[] args){
		int[] arr1 = new int[2];
		System.out.print(arr1);
		arr1[0] = 1;
		arr1[1] = 2;
		System.out.print(arr1[0]);
		System.out.print(arr1[1]);
		
		System.out.print("--------------------------");
		int[] arr2 = new int[3];
		System.out.print(arr2);
		arr2[0] = 111;
		arr2[1] = 222;
		arr2[3] = 333;
		System.out.print(arr2[0]);
		System.out.print(arr2[1]);
		System.out.print(arr2[2]);
	}
}

多个数组指向相同内存

public class Demo3{
	public static void main(String[] args){
		int[] arr1 = new int[2];
		arr1[0] = 1;
		arr1[1] = 2;
		
		System.out.print("--------------------------");
		int[] arr2 = arr1;
		arr2[0] = 111;
		System.out.print("-----------arr1内容---------------");
		System.out.print(arr1[0]);
		System.out.print(arr1[1]);
		System.out.print("------------arr2内容--------------");
		System.out.print(arr2[0]);
		System.out.print(arr2[1]);
	}
}

二维数组的相关定义
a.二维数组的概念和定义
# 声明数组并分配空间
方式1:数组类型[][] 数组名 = new 数组类型[行个数][列个数];
方式2:数组类型[][] 数组名; 数组名 = new 数组类型[行个数][列个数];

# 赋值:二维数组的赋值,和一维数组类似,可以通过下标来逐个赋值,注意索引从 0 开始
	数组名[行索引][列索引] =;
也可以在声明数组的同时为其赋值
数组类型[][] 数组名 = new 数组类型[][]{
	{xxx,xxx,xxx},
	{xxx,xxx,xxx},
	......
};

# 处理数组
	二维数组的访问和输出同一维数组一样,只是多了一个下标而已。在循环输出时,需要里面再内嵌一个循环,即使用二重循环来输出二维数组中的每一个元素。如:
// 定义一个两行三列的int二维数组
int[][] num={{1,2,3},{4,5,6}};
// 定位行
for(int i=0;i<num.length;i++){
  // 定位每行的元素
	for(itn j=0;j<num[i].length;j++){
		// 依次输出元素
		System.out.print(num[i][j]);
	}
  // 实现换行
  System.out.println();
}

不规范的二维数组创建

​ 在定义二维数组时也可以只指定行的个数,然后再为每一行分别指定列的个数。如果每行的列数不同,则创建的是不规则的二维数组

public class SecArrayTest {
	public static void main(String[] args) {
		//定义一个四行三列的二维数组,即这个二维数组包括四个一维数组,每个一维数组包括三个数组元素
		int[][] arr = new int[4][3];
		//定义数组大小并直接初始化数据
		int[][] arr2 ={{1,3},{2,5},{3,6}};
		System.out.println(arr);	//获取当前二维数组的地址
		System.out.println(arr[0]);	//获取第一个一维数组的地址
		System.out.println(arr2[0][1]);//根据数组下标输出指定元素的值
	}
}
b.二维数组的操作
public class SecArrayOper {
	public static void main(String[] args) {
		int[][] arr = {{1,2},{2,3,4},{3,4,5,6},{5,6,7}};
		//1.得到二维数组的行数:数组名称.length
		System.out.println(arr.length);	//4
		//2.得到二维数组某一行的列数:数组名称[指定行].length
		System.out.println(arr[0].length);	//2
		//3.遍历二维数组
		for(int i=0;i<arr.length;i++)
		{
			for(int j=0;j<arr[i].length;j++)
				System.out.print(arr[i][j]+"  ");
			System.out.println();
		}
		//4.求二维数组中所有数组元素的总和
		int count = 0;
		for(int i=0;i<arr.length;i++)
		{
			for(int j=0;j<arr[i].length;j++)
				count+=arr[i][j];
		}
		System.out.println("该二维数组的总和为:"+count);
	}
}

🔖数组操作

使用 Arrays 类操作 Java 中的数组

​ Arrays 类是 Java 中提供的一个工具类,在 java.util 包中。该类中包含了一些方法用来直接操作数组,比如可直接实现数组的排序、搜索等。

排序

语法:

import java.util.Arrays;
Arrays.sort(数组名);

​ 可以使用 sort( ) 方法实现对数组的排序,只要将数组名放在 sort( ) 方法的括号中,就可以完成对该数组的排序(按升序排列)

将数组转换为字符串

语法:

import java.util.Arrays;
Arrays.toString(数组名);

​ 可以使用 toString( ) 方法将一个数组转换成字符串,该方法按顺序把多个数组元素连接在一起,多个元素之间使用逗号和空格隔开([xx,xx,xx,xx])

使用 foreach 操作数组

​ foreach 并不是 Java 中的关键字,是 for 语句的特殊简化版本,在遍历数组、集合时, foreach 更简单便捷

for(元素类型 元素变量:遍历对象){
	执行的代码
}

eg:
String[] names = {"aa","bb","cc"};
// 传统for操作
for(int i=0;i<names.length;i++){
  System.out.print(names[i]);
}
// foreach操作
for(String nstr : names){
  System.out.print(nstr);
}

[!attention]

使用数组时常见错误

  • 数组下标从 0 开始。例如 scores[3] ,表示数组中的第 4 个元素,而并非第 3 个元素

  • 数组下标的范围是 0 至 数组长度-1 ,如果越界访问,就会报错(Exception in thread “main” java.lang.ArrayIndexOutOfBoundsException:2 at com.xxx.......),该错误信息的意思是数组下标超过范围,即数组访问越界。在上面的代码中创建一个长度为 2 的数组,因此数组下标范围是 0 到 1 ,而程序中下标出现了 2 ,即 scores[2] ,超过了范围,造成数组访问越界。

public class ArrayProTest {
	public static void main(String[] args) {
		//1.数组下标是从0开始取值的,因此要注意防止数组下标越界
		int[][] arr = new int[2][];
		arr[0] = new int[]{1,2,3};
		arr[1] = new int[3];
		/**编译器不会检查出数组越界错误,但运行时会报异常
		 * 数组下标越界异常:java.lang.ArrayIndexOutOfBoundsException
		 */
		System.out.println(arr[3][2]);
		//2.不能够直接使用未被初始化或初始值为null的数组,会报相应的空指针异常
		//空指针异常:java.lang.NullPointerException
		int[] arr2 = null;
		System.out.println(arr2[0]);
	}
}
数组的排序
选择排序

public class ChooseSort {
	public static void main(String[] args) {
		/**
		 * 选择排序:每次循环将当前的数据与其它数据一一进行比较,
		 * 如果出现比当前数据小的则交换位置
		 */
		int[] arr = {3,2,1,9,5};
		//外层循环:确定参与比较的数据
		//(最后一位数据可以不用比较,因为后面没有其它的数据可进行比较)
		for(int i=0;i<arr.length-1;i++)
			for(int j=i+1;j<arr.length;j++)
				if(arr[i]>arr[j])
				{
					int temp = arr[i];
					arr[i] = arr[j];
					arr[j] = temp;
				}
		for(int i:arr)
			System.out.print(i+"-");
	}
}
冒泡排序

public class BubbSort {
	public static void main(String[] args) {
		/**
		 * 冒泡排序:此处实现从小到大进行排序
		 * 依次比较相邻的两个数,将比较大的数放在后面
		 */
		int[] arr = {3,2,1,9,5};
		//定义外层循环:循环的次数
		for(int i=0;i<arr.length-1;i++)
			for(int j=0;j<arr.length-1-i;j++)
				if(arr[j]>arr[j+1])
				{
					int temp = arr[j];
					arr[j] = arr[j+1];
					arr[j+1] = temp;
				}
		for(int i:arr)
			System.out.print(i+"-");
	}
}

2.面向对象

【1】面向对象的基本概念

🔖面向对象与面向过程

​ 面向过程:是一种以过程为中心的编程思想,面向过程也称之为面向记录编程思想,他们不支持丰富的面向对象的特征(比如继承、多态),并且他们不允许混合持久化状态和域逻辑,就是分析出问题所需的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用这就是面向过程。 C Delphi 面向过程是以事件驱动开发为指导。

​ 面向对象: 把涉及到的对象抽离出来,然后考虑对象之间的关系构建整个项目,面向对象的设计是一种提供符号设计系统的面向对象的实现过程。,面向对象是以领域对象开发为指导。

举例说明:把大象装进冰箱

​ 面向过程的思维: 打开冰箱-->装进大象-->关闭冰箱

​ 面向对象的思想:冰箱打开-->冰箱存储-->冰箱关闭

【2】类和对象

🔖类和对象之间的关系

🔖类与对象的定义及相关概念

​ 类:类是面向对象的一个重要概念,一个类使用class关键字进行标识, 一个类定义了一组对象。类是具有行为的,它描述一个对象能做什么,以及做什么的方法。一个类中可以出现如下代码: 全局变量、方法、构造方法、代码块、类(内部类)

​ 类的语法规则:(修饰符) class 类名 { }

​ 对象的产生是由类产生:类名 对象名 =new 类名(参数列表);拥有了对象,便可通过 对象.属性名 对象.方法名 直接调用相关内容。

public class FirstClass {
	/**
	 * 在类中可以定义下列内容
	 * 变量、构造方法、方法、代码块、内部类
	 */
	/**1.在类中定义成员变量(实例变量、类变量)
	 * 实例变量:不以static修饰
	 * 类变量:以static修饰
	 */
	public String name;
	public static int count;
	//2.在类中定义构造方法(有参、无参)
	public FirstClass()
	{
		
	}
	public FirstClass(String name)
	{
		this.name = name;
	}
	//3.在类中定义普通的方法(具有相应的参数列表)
	public void test()
	{
		System.out.println("在类中定义普通的方法!");
	}
	//4.在类中定义代码块(静态代码块、普通代码块)
	static
	{
		System.out.println("静态代码块测试...");
	}
	{
		System.out.println("普通代码块测试...");
	}
	//5.在类中定义内部类,内部类即为出现在另外一个类中的类
	class Inner{
		//定义内部类的相关属性
	}
}
类和对象的内存分配机制
Person p1 = new Person();
p1.age = 10;
p1.name = "小明";
Person p2 = p1; // 把p1赋值给p2
System.out.println(p2.age);

// 思考p2.age的值是多少,分析其内存图

image-20240429073355859

Java内存结构分析

  • 栈:一般存放基本数据类型(局部变量)

  • 堆:存放对象(Cat cat,数组等)

  • 方法区:常量池(常量,比如字符串),类加载信息

举例说明:Java创建对象的流程简单分析

Person p = new Person();
p.name = "java";
p.age = 10;

// 分析说明
1)先加载Person类信息(属性和方法信息,只会加载一次)
2)在堆中分配空间,进行默认初始化(看规则)
3)把地址赋值给p,p指向对象
4)进行指定初始化,例如p.name="java";p.age=10

参考示例:(对应示例内存布局图)

Person a = new Person();
a.age = 10;
a.name = "小明";
Person b;
b = a;
System.out.println(b.name); // 小明
b.age = 200;
b = null;
System.out.println(a.age);  // 200
System.out.println(b.age); // 异常

1)加载Person类信息(属性和方法信息,只会加载一次)

2)在堆中分配空间,进行默认初始化

3)将地址赋给a,a就指向对象

4)进行指定初始化(age、name初始化,其中name指向的字符串“小明”在方法区的常量池中定义)

5)定义一个Person对象b,将a的引用传递给b,b则指向a对应的内容

6)打印b.name,此处b.name为"小明"

7)执行b.age=200,此时修改对应引用的堆中指定内存的age为200

8)设置b为null,此时b不再引用任何内存地址

9)由于a一直引用的是之前的内存地址,b在第7)步骤中将该区域的age修改为200,因此此时打印a.age为200;

10)由于b在步骤8)中指向为null,如果直接打印b.age会抛出异常

image-20240429075927616

image-20240429080755055

方法调用机制原理

​ 假设有一个getSum(int x,int y)方法定义用于计算两数之和,结合代码示例分析方法的调用机制原理

image-20240429081136570

变量

​ 变量分为成员变量(全局变量)和局部变量

​ 成员变量:实例变量(不以static修饰)、类变量(以static修饰)

​ 局部变量:形参(方法的形式参数定义变量)、方法局部变量(在方法体内定义)、代码块局部变量(在代码块中定义)

对比项成员变量(全局变量)局部变量
定义的位置直接定义在类中局部变量是定义在形参,方法体内,代码块内
内存中的位置存储在堆内存中存储在栈内存中
初始值可以不赋初值,有默认的初始值没有默认的初始值,必须赋初值才可以使用
生命周期成员变量也称为实例变量,随着对象的出现而出现,随着对象的回收而消失一旦作用域结束,立即被回收
public class Person {
	//1.定义一个成员变量(全局变量),其为实例变量(不以static修饰)	
	public String name;
	//2.定义一个成员变量(全局变量),其为类变量(以static进行修饰)
	public static int age;
	//3.在方法中定义局部变量,可以是位于参数列表,也可以位于方法体中
	public void setName(String name)
	{
		//此处name为位于参数列表的局部变量
		this.name = name;
		//此处msg为位于方法体中的局部变量
		String msg = "此处设置姓名为:"+name;
		System.out.println(msg);
	}
	//4.在代码块中定义局部变量
	{
		int count = 0;
	}
	/**
	 * 此处要注意局部变量和全局变量的区别与用法
	 * 全局变量具有默认值,可以不设置初值,但必须是具有值(默认值或初始化值)才能够使用
	 * 局部变量没有默认值,因此局部变量在使用之前一定要进行初始化,否则会报错!
	 */
}
public class PersonTest {
	public static void main(String[] args) {
		//通过创建Person对象进行测试
		Person p = new Person();
		/**1.可以通过对象访问实例变量或者是类变量
		 * 如果没有给成员变量设定初始值,一般会有相应的默认值
		 * 此处String类型对应的默认值为null,int类型对应的默认值为0
		 * 如果在创建对象的时候指定了初始值或是通过某个方法改变了值,则会输出相应的内容
		 */
		System.out.println(p.name+"--"+p.age);//null--0
		p.setName("haha");
		System.out.println(p.name);//haha
		/**
		 * 2.对于类变量还可直接通过类名进行访问
		 */
		System.out.println("年龄:"+Person.age);//0
		/**
		 * 3.类变量与实例变量的区别
		 * 实例变量是专属于某个对象的
		 * 类变量是属于类的,为所有的对象所共用
		 */
		Person p1 = new Person();
		p1.name = "p1";
		p1.age = 18;
		System.out.println("name:"+p1.name+"--age:"+p1.age);
		Person p2 = new Person();
		p2.name = "p2";
		System.out.println("name:"+p2.name+"--age:"+p2.age);
		/**
		 * 分析:
		 * 上述语句输出结果为
		 * name:p1--age:18
		 * name:p2--age:18
		 * 此处并没有设置p2对象的age值,但输出结果显示p2对象的age也为18,但name属性则为
		 * 各自分别设置的属性,从而进一步说明了实例变量与类变量的区别之处
		 */
	}
}

​ 类的变量的作用域比实例变量作用域更大,实例变量随着实例的存在而存在,而类变量是随着类的存在而存在,实例可以访问类变量。同一个类中所以的实例访问类变量时,实际上是访问的是该类本身的一个变量

构造方法

​ 构造函数是一种特殊的方法,这个特殊的方法是用于创建实例的时候执行的,构造器是创建对象的重要的途径(即使使用工厂模式,反射等创建对象,其本质也是依赖于构造器),java类中必须包含一个或者多个构造器。每个类都有自己默认的构造器,系统默认的构造器是无参的构造器,如果一旦人为指定,系统默认的构造器将不再执行,而是执行用户指定的构造器。

a.构造方法的基本概念

构造器的初始化与重载

​ 构造器最大的用处是在于创建对象的时候执行初始化,当创建一个对象的时候,系统会这个对象实例变量进行初始化,这种默认的初始化把所有的基本类型进行赋值0(对数值类型的变量)或fasle(布尔类型的实例变量),null(引用类型的实例变量)

public class Constructor {
	public String name;
	public int age;
	/**
	 * 1.构造器的定义格式:访问修饰符 类名(参数列表){ 方法体  }
	 * 系统一般会有一个默认的无参构造器,一旦人为指定构造器,则系统默认的无参构造器会失效
	 * 因此如果人为指定构造器,则需要自定义无参的构造器
	 */
	/**
	 * 2.同一个类中具有多个构造器,多个构造器的形式参数列表不同,则被称为构造器的重载。
	 * 构造器是允许java类里包含多个初始化的逻辑。
	 */
	public Constructor()
	{

	}
	public Constructor(String name,int age)
	{
		this.name = name;
		this.age = age;
	}
	public static void main(String[] args) {
		//通过相应的构造器创建对象,通过相应的构造器可以为指定的成员变量赋初始值
		Constructor c1 = new Constructor();
		Constructor c2 = new Constructor("haha",18);
		System.out.println("构造器1:"+c1.name+"--"+c1.age);
		System.out.println("构造器2:"+c2.name+"--"+c2.age);
	}
}

构造函数之间也是可以相互调用的

public class Apple {
	public String id;
	public double price;
	public String color;
	public Apple() 
	{
		//无参构造器
	}
	public Apple(String id,double price)
	{
		this.id = id;
		this.price = price;
	}
	//可以在构造器中调用当前类的其它构造器(使用this关键字)
	public Apple(String id,double price,String color)
	{
		this(id,price);
		this.color = color;
	}
}

对象创建的流程分析

classPerson{
	int age = 90;
	String name ;
	Person(String n,int a){
		name = n;
		age = a;
	}
}
Person p = new Person("小钱",20);
  • 加载Person类信息(Person.class),只会加载一次
  • 在堆中分配空间(地址)
  • 完成对象初始化
    • 默认初始化:age=0、name=null
    • 显示初始化:age=90、name=null
    • 构造器初始化:age=20;name="小钱"
  • 将对象在堆中的地址返回给p(p是对象名,也可以理解为对象的引用)
b.this、static、final关键字

this关键字

​ Java提供了一个this关键字,this关键字总是指向调用该方法的对象,根据this出现的位置不同this作为对象的默认引用有两种情况:

​ 构造器中引用该构造器正在初始化的对象

​ 在方法中引用该方法的对象

public class Dog {
	//this关键字的作用:让类中的一个方法,访问该类的另外一个方法或实例变量
	public void jump()
	{
		System.out.println("狗会跳...");
	}
	public void run()
	{
		//new Dog().jump(); 可以使用this引用当前调用run方法的对象
		this.jump();
	}
	public static void main(String[] args) {
		Dog d = new Dog();
		d.run();
	}
}

​ this可以代表任意对象,当this出现在某个方法体内时,它所代表的对象是不确定的,但是他的类型是确定的,它所对比的对象只能是当前类。只有当这个方法被调用的时候,它所代表的对象才能被确定。谁调用这个方法,this就代表谁

public class Student {
	private String name;
	private int age;
	public Student()
	{
		//无参的构造器
	}
	public Student(String name,int age)
	{
		this.name = name;
		this.age = age;
	}
	//定义一个方法,实现Student年龄的比较
	public String compare(Student s)
	{
		if(this.age>s.age)
			return this.name+"的年龄大于"+s.name;
		else if(this.age==s.age)
			return this.name+"与"+s.name+"同龄";
		else
			return this.name+"的年龄小于"+s.name;
	}
	public static void main(String[] args) {
		Student s1 = new Student("haha",18);
		Student s2 = new Student("xixi",19);
		System.out.println(s1.compare(s2));
	}
}

案例分析:进一步理解this含义

class Dog {
    public String name;
    public int age;

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

    public void info() {
        System.out.println(this.name + " " + this.age + " 当前对象的hashCode" + this.hashCode());
    }

    public static void main(String[] args) {
        Dog dog1 = new Dog("大壮",3);
        dog1.info();
        Dog dog2 = new Dog("大黄",2);
        dog2.info();
    }
}

// 输出参考
大壮 3 当前对象的hashCode991505714
大黄 2 当前对象的hashCode385242642

image-20240429124700913

static关键字

静态关键字:static --> 修饰符 修饰成员(成员变量,成员方法)

特点:

  • 被静态修饰的内容可以实现被所有的对象共享

  • 静态数据是随着类的加载而加载,意味着随着类的消失而消失,生命周期过长,优于对象存在的

  • 由于静态数据属于类,所有多了一种调用方式 直接类名.属性名 或者类名.方法名

注意事项:

  • 静态的方法只能访问静态的方法 ,而不能访问非静态方法(被static修饰的方法只能访问被static修饰的方法,static方法不能直接访问非static的方法)

  • 静态方法中不可以使用this关键字 以及super关键字

Final关键字

final是修饰符可以修饰类、修饰方法、修饰变量

  • final修饰的类不能被继承

  • final修饰的方法不能被覆盖(override)

  • final修饰的变量的值不能改变

# 案例:final修饰变量(成员变量、局部变量)
public class FinalVariableTest {
	/**
	 * 1.final定义全局变量:一定要对该全局变量赋初始值,可以有以下三种方式
	 * a.直接等于某个值
	 * b.在构造器中初始化
	 * c.在代码块中初始化
	 * 且以final修饰的成员变量一旦被初始化,其值不可再发生改变
	 */
	//1.直接初始化等于某个值
	public final int  a = 10;
	//2.在构造器中实现初始化
	public final int b;
	public FinalVariableTest()
	{
		b = 20;
	}
	//3.在代码块中实现初始化(用static和final同时修饰的变量可以在static代码块中实现)
	public final int c;
	{
		c = 30;
		//final修饰的变量一经初始化,不可二次赋值
		//a = 10;-->The final field FinalVariableTest.a cannot be assigned
	}
	public static final int d;
	static
	{
		d = 40;
	}
}
public class FinalLocalVariableTest {
	/**
	 * 2.final修饰局部变量,有三种存在形式
	 * a.在方法声明的形式列表中定义final修饰的局部变量,该局部变量不能够被赋值
	 * b.在方法体中声明final修饰的局部变量必须进行初始化,且不可二次赋值
	 * c.在代码块中声明final修饰的局部变量必须进行初始化,且不可二次赋值
	 */
	public void test(final int a)
	{
		/**在方法声明的形式列表中定义的局部变量不能够被赋值,但如果需要使用可以考虑
		 * 用一个相同类型的变量进行接收
		 * 	a = 10;   错误
		 * The final local variable a cannot be assigned. 
		 * It must be blank and not using a compound assignment
		 */
		int b = a;
	}
	public void test()
	{
		//在方法体内定义final修饰的局部变量必须进行初始化,且不能够二次赋值
		final String str = "haha";
		// str = "xixi";
	}
	//代码块中定义final修饰的局部变量必须进行初始化,且不能够二次赋值
	{
		final int i = 1;
	}
}
# 案例:final修饰的方法和类
// Final修的类不能被继承
public final class Father {
	public void test()
	{
		System.out.println("父类测试...");
	}
}
public class Child extends Father{
	/**
	 * Father类被final修饰,因此不能够被继承
	 * The type Child cannot subclass the final class Father
	 */
}

// Final修饰的方法不能被覆盖
public class Father {
	Public final void test()
	{
		System.out.println("父类测试...");
	}
}
public class Child extends Father{
	@Test
	public void test()
	{
		/**
		 * 被final修饰的方法无法被覆盖(重写)
		 * Cannot override the final method from Father
		 */
		System.out.println("子类无法重写父类被final修饰的方法");
	}
}
c.单例模式

​ 设计模式: 就是解决问题行之有效的方法。设计模式是一种思想。 ​ 单例模式:解决保证内存中始终只有一个对象。

如何保证内存中只有一个对象呢?

  • 不让其他程序创建该类的对象

  • 在本类中创建一个自己的对象

  • 对外提供一个获取该对象的方法

实现的步骤:

  • 构造函数私有化

  • 提供一个静态的自己的私有的全局的变量

  • 然后提供一个get方法用来获取该全局变量

单例模式分为两种模式 :懒汉式,饿汉式

​ 懒汉式指的是在提供静态的自己的私有的全局变量的时候初始化为null,即后续相应的get方法返回该对象时需进行判断,如果为null则进行初始化

public class SingleTest1 {
	/**
	 * 懒汉式
	 */
	//1、将构造函数私有化
	private SingleTest1()
	{		
	}
	//2、提供一个静态的自己的私有的全局的变量,并初始化为null
	private static SingleTest1 st = null;
	//3、通过相应的静态的get方法获取该对象
	public static SingleTest1 getSingleTest()
	{
		if(st==null)
			st = new SingleTest1();
			return st;
	}
}

​ 饿汉式指的是在提供静态的自己的私有的全局变量的时候直接创建对象,后续通过相应的get方法返回该对象

public class SingleTest2 {
	/**
	 * 饿汉式
	 */
	//1、构造函数私有化
	private SingleTest2()
	{
	}
	//2、定义一个静态的自己的私有的全局的变量,并通过构造函数初始化对象
	private static SingleTest2 st = new SingleTest2();
	//3、通过相应的静态的get方法直接返回该对象
	public static SingleTest2 getSingleTest()
	{
		return st;
	}
}

​ 通过Go类进行测试,可看到输出结果显示相应的指向都是同一个对象

public class Go {
	/**
	 * 通过Go类进行单例模式的测试
	 */
	public static void main(String[] args) {
		SingleTest1 st1 = SingleTest1.getSingleTest();
		SingleTest1 st2 = SingleTest1.getSingleTest();
		System.out.println(st1);
		System.out.println(st2);
		
		SingleTest2 st3 = SingleTest2.getSingleTest();
		SingleTest2 st4 = SingleTest2.getSingleTest();
		System.out.println(st3);
		System.out.println(st4);	
	}
}
方法

​ 方法是类或对象的行为特征的抽象,方法是类或对象最重要的组成部分,从功能上看方法就是传统的函数,Java的方法不能独立存在,所有的方法必须定义在类里,方法在逻辑上要么属于类,要么属于对象。

​ 方法的概念:方法就是定义在类中具有特定功能的一段独立程序。方法也称为函数。

​ 方法的语法格式:

修饰符  返回值  函数名 (参数类型  形式参数1,参数类型 形式参数2.. .)
{
	执行语句 
	return 返回值;
}
a.方法的定义
public class MethodTest {
	/**方法的定义测试
	 * 1.基本类型的形参
	 * 2.引用类型的形参
	 * 3.可变形参不定长参数
	 */
	/**
	 * 1.基本类型的形参
	 * a.没有返回值、没有形参的方法
	 * b.有返回值、没有形参的方法
	 * c.没有返回值,带有形式参数列表的方法
	 * d.有返回值,带有形式参数列表的方法
	 */
	public void testa()
	{
		System.out.println("这是一个没有返回值、没有形参的方法");
	}
	public boolean testb()
	{
		System.out.println("这是一个有返回值(boolean类型)、没有形参的方法");
		return true;
	}
	public void testc(int a,int b)
	{
		System.out.println("这是一个没有返回值、带有形式参数列表的方法");
		System.out.println("输出a+b的值为:"+(a+b));
	}
	public String testd(int a,int b,int c)
	{
		System.out.println("这是一个有返回值(String类型)、带有形式参数列表的方法");
		String s = "a*b*c的值为:"+a*b*c;
		return s;
	}
	public static void main(String[] args) {
		//方法测试:先定义MethodTest的对象,然后用该对象调用这些方法
		MethodTest mt = new MethodTest();
		mt.testa();
		System.out.println(mt.testb());
		mt.testc(1, 1);
		System.out.println(mt.testd(1, 1, 1));
	}
}
public class SwapTest {
	/**
	 * 2.引用类型的形参
	 * 引用的参数可以是数组、对象、接口(子类实现)
	 * 简单通过交换数据将引用类型与基本类型进行比较
	 */
	public int a;
	public int b;
	//1.定义一个方法baseSwap,用于实现基本数据类型的交换(8种基本数据类型)
	public static void baseSwap(Object a,Object b)
	{
		Object temp = a;
		a = b;
		b = temp;
		System.out.println("交换过程中实现:a="+a+"--b="+b);
	}
	//2.定义一个方法stringSwap,用于实现String数据类型的交换
	public static void stringSwap(String s1,String s2)
	{
		String temp = s1;
		s1 = s2;
		s2 = temp;
		System.out.println("交换过程中实现:s1="+s1+"--s2="+s2);
	}
	//3.定义一个方法stringbufferChange,用以实现StringBuffer数据类型的改变
	public static void stringbufferChange(StringBuffer sb)
	{
		sb.append("changed!!!");
		System.out.println("方法中实现:"+sb);
	}
	//4.定义一个方法arraySwap,用于实现数组类型的数据交换
	public static void arraySwap(Object[] arr)
	{
		Object temp = arr[0];
		arr[0] = arr[1];
		arr[1] = temp;
		System.out.println("交换过程中实现:arr[0]="+arr[0]+"--arr[1]="+arr[1]);
	}
	//5.定义一个方法objSwap,用于实现类的对象的交换(此处用SwapTest类进行测试)
	public static void objSwap(SwapTest st)
	{
		int temp = st.a;
		st.a = st.b;
		st.b = temp;
		System.out.println("交换过程中实现:st.a="+st.a+"st.b="+st.b);
	}
	public static void main(String[] args) {
		//对四种交换方法分别进行测试
		SwapTest st = new SwapTest();
		/**1.baseSwap方法测试
		 * 由测试结果分析可看到在baseSwap方法中a、b的值确实得到交换,
		 * 但其实现的只是按值传递,在baseSwap方法中实现交换,而不会对原
		 * 来的数据造成影响(即原有的a、b值不会发生变化)
		 */
		int a = 0,b=1;
		System.out.println("经过交换之前:a="+a+"--b="+b);//a=0--b=1
		baseSwap(a, b);	//a=1--b=0
		System.out.println("经过交换之后:a="+a+"--b="+b);//a=0--b=1
		/**
		 * 2.stringSwap方法测试
		 * 由测试结果分析可看到在stringSwap方法中s1、s2的值确实得到交换
		 * 但实现的也是按值传递,在stringSwap方法中实现交换,而不会对原
		 * 来的数据造成影响(即原有的s1、s2值不会发生变化)
		 */
		String s1 = "haha";
		String s2 = "xixi";
		System.out.println("经过交换之前:s1="+s1+"--s2="+s2);//s1=haha--s2=xixi
		stringSwap(s1, s2);//s1=xixi--s2=haha
		System.out.println("经过交换之后:s1="+s1+"--s2="+s2);//s1=haha--s2=xixi
		/**
		 * 3.stringbufferedSwap方法测试
		 * 由测试结果分析可看到在stringbufferChange方法中sb的值发生变化
		 * 且其实现的是按引用传递,在stringbufferChange方法中实现数据添加,
		 * 且对原有的数据产生影响(原先传进来的sb也相应的实现数据添加)
		 */
		StringBuffer sb = new StringBuffer("StringBuffer数据类型改变了??");
		System.out.println("方法调用前:"+sb);
		stringbufferChange(sb);
		System.out.println("方法调用后:"+sb);
		/**
		 * 4.arraySwap方法测试
		 * 由测试结果分析可以看到在arrSwap方法中实现数组的前两个元素的交换
		 * 且交换之后的结果显示原来的数据也发生了变化,其实现的是按引用传递,
		 * 从而改变了传进去的引用值(两个数据元素发生交换)
		 * 但如果传进去的是两个数组,实现两个数组的交换,最终也并不会影响原来的数据
		 */
		String[] arr = {"haha","王者荣耀"};
		System.out.println("经过交换之前:arr1[0]="+arr[0]+"--arr[1]="+arr[1]);
		arraySwap(arr);
		System.out.println("经过交换之后:arr[0]="+arr[0]+"--arr[1]="+arr[1]);
		/**
		 * 5.objSwap方法测试
		 * 由测试结果分析可以看到objSwap方法中实现对象的两个成员变量之间的数据交换
		 * 且交换之后的结果显示原来的数据也发生了变化,其实现的是按引用传递,
		 * 从而改变了传进去的引用值(当前对象的两个成员变量的值发生交换)
		 * 但如果传进去的是两个对象,实现两个对象之间的交换,最终也并不会影响原来的数据
		 */
		st.a = 10;
		st.b = 66;
		System.out.println("经过交换之前:st.a="+st.a+"--st.b="+st.b);
		objSwap(st);
		System.out.println("经过交换之后:st.a="+st.a+"--st.b="+st.b);
/**
	 * 由上述结果分析,则可得出结论,对于数据交换,如果传进去的是基本类型或者是String类型,
	 * 则不会对原来传进去的数据造成影响(只是在方法中实现交换),但如果传进去的是引用类型,
	 * 例如StringBuffer、StringBuilder、数组、类对象、指针、等类型, 
	 * 传进去的数据也会随方法中的执行结果发生改变(会改变原来传入的数据)
	 */
	}
}

public class Varargs {
	/**
	 * 3.可变形参不定长参数
	 * 在JDK1.5以后,java运行定义形参个数为可变参数,允许方法指定数量不确定的形参。
	 * 定义格式:数据类型... 变量名称
	 * 1、不定长参数可以不传递参数依然可以使用,而数组必须传递参数。
	 * 2、不定长参数在一个方法里只能有一个 而且必须在形参的最后一个
	 * 	 如果违反规则则报错:The variable argument type String of
	 *  the method f1 must be the last parameter
	 */
	//定义方法f1,其有不定长形参books,不定长参数的个数不确定
	public static void f1(int a,String...books)
	{
		//不定长参数可以当做相应数据类型的数组进行处理
		System.out.println("书的总数为:"+a);
		for(String s : books)
		{
			System.out.print(s+"-");
		}
	}
	public static void f2(int a,String[] books)
	{
		System.out.println("\n书的总数为:"+a);
		for(String s : books)
		{
			System.out.print(s+"-");
		}
	}
	public static void main(String[] args) {
		String[] books = {"语文","数学","英语"};
		//不定长参数可以不传递参数(参数个数为0),但数组必须要传入参数
		f1(books.length);
		f1(books.length,books);
		f2(books.length,books);
	}
}
# 习题练习:
public class CalculactorTest {
	//简单计算器的实现
	public static int add(int a,int b)
	{
		return a+b;
	}
	public static int sub(int a,int b)
	{
		return a-b;
	}
	public static int mul(int a,int b)
	{
		return a*b;
	}
	public static double div(int a,int b)
	{
		if(b==0)
		{
			System.out.println("除数不能为0!!");
			return -1;
		}
		else
			return a/b;
	}
	public static void main(String[] args) {
		System.out.println("4+2="+add(4,2));
		System.out.println("4-2="+sub(4,2));
		System.out.println("4*2="+mul(4,2));
		System.out.println("4/2="+div(4,2));
		System.out.println("4/0="+div(4,0));
		}
}
b.方法的递归

​ 一个方法体内调用它自身的方法这种行为被称为方法的递归,方法的递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无需循环的控制

方法的调用

public class RecursionTest {
	//方法的递归:先理解方法之间的调用
	public static void f1()
	{
		System.out.println("执行f1方法...");
	}
	public static void f2()
	{
		//在f2中调用f1方法
		f1();
		System.out.println("执行f2方法...");
	}
	public static void f3()
	{
		//在f3中调用f1、f2方法
		f1();
		f2();
		System.out.println("执行f3方法...");
	}
	public static void main(String[] args) {
		f3();
	}
}

死循环

public class DeathCirculsion {
	public static void f1()
	{
		System.out.println("方法f1执行...");
		//在方法f1中执行方法f2
		f2();
	}
	public static void f2()
	{
		System.out.println("方法f2执行...");
		//在方法f2中执行方法f1
		f1();
	}
	public static void main(String[] args) {
		//测试,陷入死循环,会导致内存的溢出
		f1();
//		f2();
	}
}

方法的递归的实现

递归核心:

1)执行一个方法时,就创建一个新的受保护的独立空间(栈空间)

2)方法的局部变量是独立的,不会相互影响(例如阶乘实现的n变量)

3)如果方法中使用引用类型的变量(数组、对象),就会共享该引用类型的数据

4)递归必须向退出递归的条件逼近,否则就是无限递归,出现StackOverflowError

5)当一个方法执行完毕或者遇到return就会返回。遵循谁调用就将结果返回给谁

public class RecursionTestExc {
	/**
	 * 1、数学题:f(0)=1、f1(1)=4、f(n+2)=2*f(n+1)+f(n)
	 * 	   其中n是大于0的整数,求f(10)的值:
	 * 2、求n的阶乘  5的阶乘=5*4*3*2*1   求12的阶乘
	 */
	public static int f1(int i)
	{
		if(i==0)
			return 1;
		else if(i==1)
			return 4;
		else
			return 2*f1(i-1)+f1(i-2);
	}
	public static int f2(int n)
	{
		if(n==1)
			return 1;
		else
			return n*f2(n-1);
	}
	public static void main(String[] args) {
		//测试
		System.out.println(f1(0));
		System.out.println(f1(1));
		System.out.println(f2(1));
		System.out.println(f2(3));
	}
}

递归案例:斐波那契数列、猴子吃桃问题、汉诺塔问题

public class RecursionExample01 {

    public static void main(String[] args) {
        RecursionExample01 re  = new RecursionExample01();
        for (int i = 1; i <= 5; i++) {
            System.out.println("第"+i +"个数据:" + re.fibonacci(i) );
        }
    }

    /**
     * 斐波那契数列:
     * 1,2,3,5,8,13
     * 规律:
     * 1 =》 1     =》 1
     * 2 =》 2     =》 2
     * 3 =》 1+2   =》 3
     * 4 =》 2+3   =》 5
     * 5 =》 3+5   =》 8
     */
    public int fibonacci(int n) {
        // 设置递归退出条件
        if(n == 1 || n == 2) {
            return n;
        }else if(n>=3){
            // 前两个数之和
            return fibonacci(n-1) + fibonacci(n-2);
        }else{
            // 其他数据输入判断异常
            System.out.println("输入数据异常");
            return -1;
        }
    }
}
public class RecursionExample02 {


    public static void main(String[] args) {
        RecursionExample02 re = new RecursionExample02();
        for (int i = 1; i <= 10; i++) {
            System.out.println("第"+i+"天剩余桃子数:"+re.peach(i));
        }
    }

    /**
     * 猴子吃桃问题:
     * 一堆桃子,猴子第一天吃了其中的一半并且再吃一个,之后的每天猴子都吃剩下的一半然后多吃一个
     * 当吃到第10天却发现只有1个桃子,问最初有多少个桃子
     * 解题思路:逆推(从第10天开始,逆推前几天的规律)
     * 10:1
     * 9:(1+1)*2 => 4
     * 8:(4+1)*2 => 10
     * 7:(10+1)*2 => 22
     * 6:(22+1)*2 =>46
     * ----- 以此类推
     * 输入参数n为天数,当n为10输出1,且天数必须为证书
     * 前一天的桃子数量 = (后一天桃子数量 + 1) *2
     */

    public static int peach(int n){
        // n为第几天,返回的值是第几天的桃子数量
       if(n>0){
           if(n==10){
               return 1;
           }else{
               return (peach(n+1)+1)*2;
           }
       }else{
           System.out.println("天数必须为正数");
           return -1;
       }
    }
}
c.方法的重载

​ Java允许一个类里定义多个同名的方法,只需要形式参数列表不同即可。如果一个类中包含了两个以上的方法名相同,但是形式参数列表不同那么被称为方法的重载。

​ 方法的重载要求是两同一不同:在同一个类中,方法名相同,形式参数列表不同(a.个数不同,b.位置不同.c 类型不同),和返回值,修饰符无关。

public class OverloadTest {
	/**
	 * 方法的重载:在同一个类中,可以允许一个及以上类名相同而形式参数列表不同的方法
	 * 特点:“两同一不同”
	 * 同一个类中、方法名相同、形式参数列表不同(个数、位置、类型)
	 * 与其返回值和修饰符无关
	 */
	//定义无参的方法test
	public void test()
	{
		System.out.println("这是一个无参的方法test");
	}
	//定义具有一个参数的方法test
	public void test(int a)
	{
		System.out.println("这是具有一个参数的方法test(int a),参数值a为:"+a);
	}
	//定义具有一个参数的方法test(类型与上述不同)
	public void test(String str)
	{
		System.out.print("这是具有一个参数的方法test(String str)");
		System.out.println("参数值str:"+str);
	}
	//定义具有两个参数的方法test
	public void test(int a,String str)
	{
		System.out.print("这是具有两个参数的方法test(int a,String str)");
		System.out.println("参数a:"+a+"-参数str:"+str);
	}
	//定义具有两个参数的方法test(参数位置与上述不同)
	public void test(String str,int a)
	{
		System.out.print("这是具有两个参数的方法test(String str,int a)");
		System.out.println("参数str:"+str+"-参数a:"+a);
	}
	public static void main(String[] args) {
		//简单测试
		OverloadTest ot = new OverloadTest();
		ot.test();
		ot.test(1);
		ot.test("hello");
		ot.test(2, "haha");
		ot.test("xixi", 3);
	}
}
public class OverloadVarargs {
	/**
	 * 不定长参数的方法重载
	 * 不定长参数的方法捕获能力最弱,当没有满足条件的方法才考虑不定长参数的方法
	 */
	//定义一个具有一个String类型参数的普通方法,标记为方法1
	public void test(String str)
	{
		System.out.println("这是具有一个参数的普通方法,参数str:"+str);
	}
	//定义一个具有一个String类型不定长参数的方法,标记为方法2
	public void test(String...books)
	{
		System.out.println("\n这是不定长参数的方法,参数列表如下");
		for(String s : books)
			System.out.print(s+"-");
	}
	public static void main(String[] args) {
		//进行测试
		OverloadVarargs olv = new OverloadVarargs();
		String[] books = {"语文","数学","英语"};
		olv.test();//调用方法2
		olv.test("物理");//调用方法1
		olv.test("生物","化学");//调用方法1
		olv.test(books);//调用方法2
		/**
     * 可以看到当指定传入参数为1个String类型的参数时,此时两个方法都满足要求,
		 * 但考虑到定义不定长参数的方法捕获能力较弱,因此执行的是具有一个String类型
		 * 参数的普通方法(即方法1),只有当没有满足条件的方法时才考虑是否调用具有不定
		 * 长参数的方法
 		 * 大部分时候并不推荐重载长度可变的方法,并且程序的可读性会降低。
		 */
	}
}
代码块

​ 代码块和构造器功能基本类似,是用于给成员变量赋初值。也是在类初始化的时候自动执行,并且优先于构造函数执行。代码块分为局部代码块、普通代码块和静态代码块

局部代码块:在方法中定义

public class Test {
    /*
        局部代码块,在方法中定义
        作用:限定变量的生命周期,及早释放,提高内存利用率
     */
    public static void main(String[] args) {
        {
            int a = 10;
            System.out.println(a);
        }

       // System.out.println(a);
    }
}

普通代码块(构造代码块):在类中的方法外定义

作用:将多个构造方法中相同的代码,抽取到构造代码块中,提高代码的复用性

public class Person {
	//1.普通代码块
	public Person()
	{
		System.out.println("构造器执行...");
	}
	{
		System.out.println("第一个普通代码块执行...");
	}
	{
		System.out.println("第二个普通代码块执行...");
	}
	public static void main(String[] args) {
		//普通代码块测试
		Person p = new Person();
		Person p1 = new Person();
		/**
		 * 执行结果:
		 * 第一个普通代码块执行...
		 * 第二个普通代码块执行...
		 * 构造器执行...
		 * 第一个普通代码块执行...
		 * 第二个普通代码块执行...
		 * 构造器执行...
		 */
		/**
		 * 由执行结果观察可得代码块的执行优先于构造器的执行
		 * 普通代码块是在对象创建的时候执行1次,其执行的先后顺序
		 * 是依据定义的先后顺序而确定的
		 * 而构造器的执行则是在创建对象的时候执行的
		 */
	}
}

静态代码块:在类中的方法外定义

作用: 在类加载的时候做一些数据初始化的操作

public class StaticTest {
	//2.静态代码块
	public StaticTest()
	{
		System.out.println("构造器执行...");
	}
	static
	{
		System.out.println("第一个静态代码块执行...");
	}
	{
		System.out.println("第一个普通代码块执行...");
	}
	static
	{
		System.out.println("第二个静态代码块执行...");
	}
	public static void main(String[] args) {
		StaticTest st = new StaticTest();
		StaticTest st1 = new StaticTest();
		/**
		 * 执行结果:
		 * 第一个静态代码块执行...
		 * 第二个静态代码块执行...
		 * 第一个普通代码块执行...
		 * 构造器执行...
		 * 第一个普通代码块执行...
		 * 构造器执行...
		 */
		/**
		 * 由上述结果分析:执行的优先顺序为
		 * “静态代码块>普通代码块>构造器”
		 * 且相应代码块的执行顺序取决于相应的声明顺序
		 * 静态代码块是在类加载的时候执行的,相应的只执行1次
		 * 普通代码块则是在对象创建的时候调用1次
		 * 构造器是在类的对象创建的时候调用
		 * 综上所述:
		 * 类加载的时候执行静态代码块(static)一次,创建对象的时候先调用非static的初始化代码块,随后调用
		 * 相应的构造器,对象创建1次则执行1次
		 */
	}
}

分析参考

// GrandFather
public class GrandFather{
	public GrandFather(){
		System.out.println("1");
	}
	{
		System.out.println("2");
	}
	static {
		System.out.println("3");
	}
}

// Father
public class Father extends GrandFather{
	public GrandFather(){
		System.out.println("4");
	}
	{
		System.out.println("5");
	}
	static {
		System.out.println("6");
	}
}

// Child
public class Child extends Father{
	public Child(){
		System.out.println("7");
	}
	{
		System.out.println("8");
	}
	static {
		System.out.println("9");
	}
}

// 测试主函数
public class Go{
  public static void main(String[] args){
    Child child = new Child();
    // 执行结果:369 21 54 87
  }
}

代码块是在类初始化的时候自动执行的,其优先于构造函数执行

​ static初始化块则是在类加载的时候自动执行1次,若同一个java类中出现多个static初始化块则按照出现的先后顺序执行(只执行1次)

​ 非static的初始化块语句会在类的对象创建的时候被调用1次

​ 构造方法是在类对象被创建的时候执行

​ 类加载的时候执行static语句块(只执行一次),对象创建的时候先执行非static语句块后执行构造方法,对象创建一次执行一次

​ 程序执行的时候需要创建Child对象,则程序依次加载GrandFather、Father、Child类,其相应static初始化块依次执行,输出“3 6 9”,随后依次执行每个类中的非static初始化块、初始化构造方法,分别依次输出“21 54 87”

内部类
a.内部类的定义

​ 大部分时候,类被定义为一个独立的程序单元,在某些情况下,会把一个类放入到另外一个类的内部,这个定义在其他类内部的类称为内部类。包含内部类的类被称为外部类。

​ 内部类的作用如下:

  • 内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包下其他类的访问。

  • 内部类成员可以访问外部类任意的属性包括私有数据。

  • 内部类分为四种情况:非静态内部类,静态内部类,局部内部类,匿名内部类

b.内部类的分类

非静态内部类

要点分析:

  • 可以访问外部类的任意成员吗?

    非静态内部类可以无条件访问外部类任意的成员

  • 可以和外部类同名吗?

    不可以和自己的外部类同名 但是可以和自己外部类同级目录下的类同名

  • 外部类能否访问内部类的成员?

    不能

  • 非静态内部类能否有static修饰

    不能

  • 内部类如何被实例化(了解)

    new 外部类().new 内部类();

  • 内部类能否被继承

    可以。继承它需要写一个构造方法,并且手动调用外部类

public class Outer {
	/**
	 * 非静态内部类(没有static修饰的类)的4个特点
	 * 1.非静态内部类可以无条件访问外部类任意的成员
	 * 2.非静态外部类不能够访问内部类的成员
	 * 3.非静态内部类中不能够出现static修饰
	 * 4.内部类的实例化是通过相应的外部类实现
	 *    例如此处有:Inner in = new Outer().new Inner();
	 */
	private int i=3;
	class Inner
	{
		public void test()
		{
			//1.非静态内部类可以无条件访问外部类任意的成员
			System.out.println("i="+i);
		}
		String str = "测试...";
		//3.非静态内部类中不能够出现static修饰
		//public static int a =0 ;
	}
	public void getStr()
	{
		//2.外部类无法访问内部类的成员
		//System.out.println(str);
	}
	public static void main(String[] args) {
		//4.内部类的实例化
		Inner in = new Outer().new Inner();
		in.test();
	}
}
/**
 *	定义SubClass类继承外部类.内部类(Outer.Inner)
 *	写一个构造函数,形参是外部类,且手动调用外部类
 */
public class SubClass extends Outer.Inner{
	public SubClass(Outer outer)
	{
		outer.super();
	}
}

静态内部类

要点分析:

  • 静态内部类能否访问外部类的非静态属性

    不能

  • 外部类能否访问静态内部的成员?

    不能

public class OuterStaticTest {
	/**
	 * 静态内部类:被static修饰的内部类
	 * 1.静态内部类无法访问外部类的非静态属性
	 * 2.外部类无法直接访问内部类的实例变量
	 * 3.外部类可以通过类名.属性名的方式调用内部类的静态变量
	 */
	private int i = 3;
	public static int a = 10;
	static class Inner
	{
		public int f = 10;
		public static int s = 10;
		public String str = "haha";
		public void test()
		{
			//1.静态内部类可以访问外部类的静态属性,但不能访问外部类的非静态属性
			System.out.println(a);
			//System.out.println(i);
		}
	}
	public void otest()
	{
		//2.外部类无法直接访问内部类的实例变量
		//System.out.println(f);
		//System.out.println(s);
		//3.外部类可以以类名.属性名的方式调用内部类的静态变量
		System.out.println(Inner.s);
	}
}

局部内部类

public class LocalOuter {
	/**
	 * 局部内部类:把一个类定义在一个方法体内
	 * 具有以下特点:
	 * 1.局部内部类定义在一个方法体内
	 * 2.局部内部类不能有static进行修饰
	 * 3.局部内部类中的属性不能有static修饰
	 */
	public void test()
	{
		class Inner
		{
			//public static int s = 10;
			public int i = 1;
		}
	}
}

Java8改进的匿名内部类

​ 匿名内部类是说只适合使用一次的类。匿名内部类不能被重复使用

interface Product{
	public double getPrice();
	public String getName();
}
public class Computer implements Product {
	public String getName() {
		return "ACER";
	}
	public double getPrice() {
		return 10;
	}
}
public class AnonymousTest {
	public void showProduct(Product product)
	{
		System.out.println("产品名称:"+product.getName()+"价格:"+product.getPrice());
	}
	public static void main(String[] args) {
		AnonymousTest at = new AnonymousTest();
		//匿名内部类,Product是一个接口,需要通过子类实现接口中所有的抽象方法
		//下述{ }就是匿名内部类,相当于前方接口Product的子类
		at.showProduct(new Product(){
			public String getName() {
				return "宏基";
			}
			public double getPrice() {
				return 30;
			}
		});
	}
}

​ 在jdk1.8之前要求被局部内部类、匿名内部类访问的局部变量必须是final修饰的,从jdk1.8之后取消了这个限制, 系统会自动添加一个final修饰

📌对象内存图

单个对象

Student类

public class Student {
    String name;
    int age;
    public void study(){
        System.out.println("学习");
    }
}

TestStudent1类

public class TestStudent1 {
    public static void main(String[] args) {
        Student stu = new Student();
      	System.out.println(stu);
        System.out.println(stu.name + "---" + stu.age);  
        stu.name = "h";
        stu.age = 18;
        System.out.println(stu.name + "---" + stu.age);  
        stu.study();
    }
}

多个对象

TestStudent2类

public class TestStudent2 {
    public static void main(String[] args) {
        Student stu1 = new Student();
      	System.out.println(stu1);
        stu1.name = "h";
        stu1.age = 18;
        System.out.println(stu1.name + "---" + stu1.age);  
        stu1.study();
        System.out.println("-----------------------------");  
        Student stu2 = new Student();
      	System.out.println(stu2);
        stu2.name = "x";
        stu2.age = 28;
        System.out.println(stu2.name + "---" + stu2.age);  
        stu2.study();
    }
}

​ 多个对象在堆内存中都有不同的内存划分,成员变量存储在各自的内存区域中,且多个对象共用一份字节码文件,因此多个对象所引用的都是同一个成员方法

多个对象指向相同内存

TestStudent3

public class TestStudent3 {
    public static void main(String[] args) {
        Student stu1 = new Student();
        stu1.name = "h";
        Student stu2 = stu1;
        stu2.name = "x";
        System.out.println(stu1.name + "---" + stu2.name);  
        stu1 = null;
        System.out.println(stu1.name);  // 异常
        System.out.println(stu2.name);  
        stu2 = null;
    }
}

​ 当多个对象的引用指向同一个内存空间(变量所记录的地址值是一样的),只要有任何一个对象修改了内存中的数据,随后无论使用哪一个对象进行数据获取,都是修改后的数据。

垃圾回收

​ 当堆内存中,对象或数组产生的地址通过任何方式都不能被找到后,就会被判定为内存中的”垃圾”。垃圾会被Java垃圾回收期在空闲的时候自动进行清理

【3】封装与隐藏

🔖理解封装的概念

​ 封装是面向对象三大特质之一(继承和多态),它指的是将对象的状态信息封装在对象内部,不允许外部程序直接访问,而是通过该类所提供的方法实现对内部信息的操作和访问。

对一个类或对象进行封装可以实现以下目的:

  • 隐藏类实现的细节

  • 让使用者只能通过预定的方法访问数据,从而可以在该方法内加入逻辑控制,限制对成员变量不合理的访问

  • 可以进行数据检测,从而利于保证对象信息的完整性

  • 便于修改,提高代码的可维护性

为了实现封装需要从两个方面考虑:

  • 将成员变量和实现细节隐藏起来,不允许外部直接访问(Java提供了访问控制修饰符)

  • 把公有的方法暴露出来,让方法控制这些成员变量进行安全的访问和操作

🔖访问控制修饰符

​ Java提供了3个访问修饰符 private、 protected和public分别代表3个访问控制级别,除此以外系统还有一个不加任何访问修饰符的访问控制级别就是默认defalut,总共提供了4个访问修饰符。Java的访问控制修饰符控制级别由小到大如下: private --->default--->protected--->public

访问控制修饰符的范围

private:私有的,只能本类中使用

efault:默认的访问级别 可以在同一个类中,同一个包中使用

protected:受保护的级别 能在同一个类中,同一个包中,不同包的子类中使用

public:公有的 任意类都可以使用

privatedefaultprotectedpublic
同一个类中
同一个包中
子类中
全局范围内
public class Person {
	/**
	 * 需要注意的问题:Java类里实例变量的setter和getter方法有着重要的意义
	 * 需要按照一定的规则进行设置,比如对name进行操作,则需要使用setName方法和
	 * getName方法,需要符合JavaBean的规范。
	 * java提供快捷键能够快速获得属性相应的set和get构造器:
	 * 右键选择“source”-->“Generate Getters and Setters”
	 * 此外还提供了相应的构造器:
	 * 右键选择“source”-->“Generate Constructor from fields”等
	 * 根据自己的需求选择无参、有参的构造器
	 * 此外还有toString、hashCode、equals等方法可以进行重写
	 */
	private String name;
	private int age;
	public Person()
	{
		
	}
	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	//在对属性进行赋值的时候可以根据用户需求添加一些校验条件
	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;
	}
	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}
	public static void main(String[] args) {
		Person p = new Person();
		p.setName("haha");
		p.setAge(18);
		System.out.println(p);
	}
}

📌this关键字

​ this修饰的变量用于指代成员变量,其主要作用是(区分局部变量和成员变量的重名问题)

  • 方法的形参如果与成员变量同名,不带this修饰的变量指的是形参,而不是成员变量
  • 方法的形参没有与成员变量同名,不带this修饰的变量指的是成员变量
public class Student {
    private String name;
    private int age;
    public void setName(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public int getAge() {
        return age;
    }
    public void show() {
        System.out.println(name + "," + age);
    }
}
this内存原理
public class StudentDemo{
	public static void main(String[] args){
		Student s1 = new Student();
		s1.setName("h");
		Student s2 = new Student();
		s2.setName("x");
	}
}

​ 方法被对象调用,对象中的this则指代该对象

🔖包

​ 包的概念:包是用来区分类的。可以在不同的包中使用相同的类名。

​ Java允许将一组相关的功能放在同一个包下,java提供了多层空间命名,主要是用于解决类命名的冲突。Java的包的命名规则如下:

package  packagename  倒域名的方式进行命名
组织机构名.公司名.项目名.模块名.子模块
com.gs.shopping.product.dao

编译程序:

javac -d . Hello.java

java com.base.Hello

package com.base
public class Hello{
	public static void main(String[] args){
		System.out.println("Hello");
	}
}

-d选项是用于设置编译生成class文件的保存位置,这里是使用(.)就是在当前路径下生成

在不同的包下建立文件Apple:

package com.base
public class Apple{
	public void f1(){
		System.out.println("xxx");
	}
}

package com.base2
public class Test{
	public static void main(String[] args){
    // 使用包名查询
    com.base.Apple a = new com.base.Apple();
    a.f1();
  }
}
a.普通导入包

​ 导包的关键字 import

​ 导入需要类的具体包或者指向具体的类即可。

​ 导包的时候可以使用*替代具体的类名或者包名代表把当前包下所有的类全部都导入

​ Import导包可以出现多次

​ Java默认为所有的类都导入了java.lang.*这个包下所有的类比如System

b.静态导入

​ JDK1.5之后增加了一种静态导入的语法,它用于指定某个类的静态成员变量,方法或者全部的成员变量方法 都导入到指定的类中。

​ 静态导入使用import static packagename;

【4】继承和多态

🔖继承

​ 继承是面向对象的特征,也是实现我们代码重用的重要手段。Java继承具有单继承的特点,每个类只能继承一个直接父类

继承的特点

​ Java的继承,通过extends关键字来实现,实现继承的类被称为子类。被继承的类成为父类。父类也被称为基类、超类。语法规则如下:

修饰符 class  SubClass extends SuperClass
{
	// 定义代码
}

继承的特性:

  • 在java中所有的类默认继承了java.lang.object这个类
  • 继承具有传递性
  • 继承是单继承,只能继承一个父类
  • 当初始化子类时先初始化父类,然后再初始化子类
  • 构造函数是不能被继承的,但是可以在子类中通过super关键字调用

继承的优缺点

  • 继承有点
    • 提高了代码的复用性(多个类相同的成员可以放到同一个类中)
    • 提高了代码的维护性(如果方法的代码需要修改,修改一处即可)
  • 继承弊端
    • 继承让类与类之间产生了关系,类的耦合性增强了,当父类发生变化时子类实现也不得不跟着变化,削弱了子类的独立性
  • 继承的应用场景:
    • 使用继承,需要考虑类与类之间是否存在is..a的关系,不能盲目使用继承
      • is..a的关系:谁是谁的一种,例如:老师和学生是人的一种,那人就是父类,学生和老师就是子类
public class GrandFather {
	public int i = 10;
	public GrandFather()
	{
		i+=++i;
		System.out.println("GrandFather的构造方法执行...");
	}
	public void test()
	{
		System.out.println("GrandFather的test方法执行...");
	}
}
public class Father extends GrandFather{
	public Father()
	{
		i+=i++;
		System.out.println("Father的构造方法执行...");
	}
	//用4种修饰符分别定义4种成员变量
	private int a = 11;
	String str = "hello";
	protected char ch = 'a';
	public float f = 1.0f;
	//用4种修饰符分别定义4种方法
	private void f1()
	{
		System.out.println("father的f1方法:private");
	}
	void f2()
	{
		System.out.println("father的f2方法:default");
	}
	protected void f3()
	{
		System.out.println("father的f3方法:protected");
	}
	public void f4()
	{
		System.out.println("father的f4方法:public");
	}
}
public class Child extends Father {
	public Child()
	{
		System.out.println(++i);
		System.out.println("Child的构造方法执行");
	}
	public static void main(String[] args) {
		//继承测试:先定义一个子类对象(Child)
		Child c = new Child();
		//测试4种访问修饰符
		//c.f1();子类不能直接访问父类用private修饰的变量或者是方法
		c.f2();//如果是在不同的包内,子类是无法访问父类用default修饰的变量或者是方法
		c.f3();//在不同的包下,子类也可以访问父类用protected修饰的变量或者是方法
		c.f4();//子类可以任意访问父类用public修饰的变量或者是方法
		c.test();
		/**
		 * 子类对象的初始化过程:
		 * 子类对象的初始化要先初始化的是其相应的父类对象,首先先依次加载父类、子类
		 * 随后依次创建父类对象、子类对象
		 * 分析:在创建GrandFather对象的时候有i+=++i;
     * 该语句执行完之后i的值为21(i=i+(++i))
		 * 随后创建Father对象,有i+=i++;该语句执行完之后i的值为42(i=i+(i++))
		 * 随后创建Child对象,输出++i,得到最终结果i=43
		 */
	}
}

方法的覆盖

定义:当子类继承父类并重写了父类的方法的这种行为被称为方法的覆盖。

特点:两同两小一大-->两同即方法名相同,形式参数列表相同

​ 两小是指子类方法的返回值类型(协变类型(拥有继承关系))应该比父类更小,子类抛出的异常应该比父类更少更小。(继承关系、抛出异常)

​ 一大是指子类的作用权限比父类的作用权限要大或者相等。

public class Person {
	public void eat()
	{
		System.out.println("人能吃东西");
	}
	public void walk()
	{
		System.out.println("人会走路");
	}
}
public class Student extends Person {
	//Student继承了Person,可以重写相应的方法
	public void eat()
	{
		System.out.println("学生吃肉长身体");
	}
	//此外学生还可以扩展自己的方法
	public void study()
	{
		System.out.println("学生努力上课学习");
	}
}
public class Teacher extends Person{
	//Teacher继承Person,还能够重写相应的方法
//可以用@Override注解注明当前方法是重写父类的方法
	public void eat()
	{
		System.out.println("老师这周吃素");
	}
	//Teacher还能够拥有自己的方法,扩展自己的特性
	public void teach()
	{
		System.out.println("老师会教书");
	}
}
public class ExtendsTest {
	public static void main(String[] args) {
		//执行的时候是什么类型则会执行相应类型的方法
		Person p = new Person();
		p.eat();
		Student s = new Student();
		s.eat();
		Teacher t = new Teacher();
		t.eat();
		Person s1 = new Student();
		s1.eat();
		Person t1 = new Teacher();
		t1.eat();
		/**
		 * 执行结果:
		 * 人能吃东西
		 * 学生吃肉长身体
		 * 老师这周吃素
		 * 学生吃肉长身体
		 * 老师这周吃素
		 */
	}
}

继承的内部布局

image-20240429131203727

Super关键字

​ 如果需要在类中调用父类被覆盖掉的方法 可以使用super关键字,super代表当前对象的父类

class SuperFather
{
	public SuperFather()
	{
		System.out.println("父类SuperFather的无参构造方法执行中...");
	}
	public SuperFather(String str)
	{
		System.out.println("父类SuperFather的带参构造方法执行中...");
	}
	public void test()
	{
		System.out.println("父类SuperFather的test方法...");
	}
}
public class SuperTest extends SuperFather {
	/**
	 * super关键字的使用:
	 * 1.在子类中调用父类的构造器
	 * 2.在子类中调用被覆盖的父类方法
	 */
	public SuperTest()
	{
		/**
		 * 1.通过super关键字调用父类的构造器(只能调用1个构造器)
		 * 且super语句必须放在第一行(第一个语句)
		 * Constructor call must be the first statement in a constructor
		 */
//		super();
		super("haha");
		System.out.println("子类SuperTest的构造方法执行...");
	}
	//重写父类的test方法
	public void test()
	{
		//2.通过super语句调用父类被覆盖的方法,此时super指代当前父类的对象
		super.test();
		System.out.println("子类重写父类方法test...");
	}
	public static void main(String[] args) {
		SuperTest st = new SuperTest();
		st.test();
		/**
		 * 如果没有指定调用父类的哪个构造方法,默认调用无参的构造方法
		 * 执行结果:
		 * 父类SuperFather的无参构造方法执行中...
		 * 子类SuperTest的构造方法执行...
		 * 父类SuperFather的test方法...
		 * 子类重写父类方法test...
		 */
	}
}

🔖多态

多态的特性

​ Java引用变量有两个类型,一个是编译时的类型,一个是运行时的类型。其中编译时的类型由声明该变量时使用的类型决定,而运行时的类型由实际赋值给该变量的对象决定

public class BaseClass {
	public void base()
	{
		System.out.println("父类的普通方法base执行中...");
	}
	public void test()
	{
		System.out.println("父类的普通方法test执行中...");
	}
}
public class SubClass extends BaseClass{
	public void sub()
	{
		System.out.println("子类自定义的普通方法...");
	}
	public void test()
	{
		System.out.println("子类重写/覆盖父类的普通方法test...");
	}
	public static void main(String[] args) {
		//测试
		/**
		 * 1.编译时类型与运行时类型完全一致,因此没有多态
		 */
		BaseClass bc = new BaseClass();
		bc.base();//执行的是父类的base方法
		bc.test();//执行的是父类的test方法
		SubClass sc = new SubClass();
		sc.base();//执行的是父类的base方法
		sc.sub();//执行的是子类的sub方法
		sc.test();//执行的是子类的test方法
		/**
		 * 2.编译时的类型与运行时的类型不一致,因此产生多态
		 */
		//子类声明、父类接收
		BaseClass bcs = new SubClass();
		bcs.base();//调用父类的base方法
		bcs.test();//调用子类的test方法
		/**
		 * bcs.sub();由于在编译时bcs的类型时BaseClass类型,
		 * 不存在Sub方法,因此无法直接调用sub方法,会在编译期间报错
		 */
		/**
		 * 执行结果:
		 * 父类的普通方法base执行中...
		 * 父类的普通方法test执行中...
		 * 父类的普通方法base执行中...
		 * 子类自定义的普通方法...
		 * 子类重写/覆盖父类的普通方法test...
		 * 父类的普通方法base执行中...
		 * 子类重写/覆盖父类的普通方法test...
		 */
	}
}

引用类型的强制类型转换

​ 基本数据类型发生类型转换分为自动类型转换和强制类型转换。引用类型的类型转换分为上溯造型 和向下转型

​ “上溯造型”是指子类声明父类接收:把子类类型转换为父类

​ “向下转型 ”是指把父类的类型强制转换为子类

​ 如果抽象数据类型之间发生关系转换,必须有继承关系

public class Go {
	//通过Go类测试相应的内容
	public static void main(String[] args) {
		//上溯造型:子类声明、父类接收
		Person t1 = new Teacher();
		t1.eat();  //显示的是子类重写后的信息
		t1.walk();
		//t1.teach();在编译(编写代码时)时t1的类型是Person类,此时Person类中并没有teach方法,因此出现错误
		//但t1运行时的类型是Teacher类型
		
		//向下转型:将父类类型强制转换为子类,即将Person强制转换为Teacher
		Teacher t = (Teacher)t1;
		t.teach();//此时编译通过,显示teach方法中的相关信息
		
		//下述语句在编译时出现java.lang.ClassCastException类型转换错误异常
		//不能够将Teacher强制转化为Student类型
		Student s = (Student)t1;
		s.study();
	}
}

Instanceof的使用

​ 通过instanceof实现方法的通用性,利用上述的案例实现测试,使得无论传入的是父类类型还是相应的子类类型,都能够进行判断并执行该类对应的所有方法

​ Instanceof的作用是判断两个比较的对象类型是否相同,相同则返回true,否则返回false

public class InstanceOfTest {
	//定义一个test方法,test方法的形参是父类类型(此处为Person类),这个形参可以接收当前对象或者是当前对象的子类
	//即test方法可以接收Person类的对象或者是Person的子类对象
	public void test(Person p)
	{
		p.eat();
		p.walk();
		if(p instanceof Teacher)
		{
			//((Teacher) p).teach();
			Teacher t = (Teacher)p;
			t.teach();
		}
		else if(p instanceof Student)
		{
			//((Student) p).study();
			Student s = (Student)p;
			s.study();
		}
	}
	public static void main(String[] args) {
		//测试
		InstanceOfTest iot = new InstanceOfTest();
		iot.test(new Person());
		iot.test(new Teacher());
		iot.test(new Student());
	}
}

【5】抽象类与接口

🔖抽象类的相关概念

定义:

​ 抽象类和抽象方法都必须使用abstract修饰符进行定义,有抽象方法的类只能被定义为抽象类,抽象类中可以没有抽象方法。

抽象类和抽象方法的规则:

  • 抽象类和抽象方法必须使用abstract修饰符进行修饰,抽象方法是没有方法体的。

  • Public void f1(){} 抽象方法 public abstract void f2();

  • 抽象类不能被实例化,无法通过new关键字来调用抽象类的构造函数创建实例。

  • 抽象类可以包括成员变量,方法,构造器和初始化代码块以及内部类,这些定义的内容主要是被子类调用。

  • 含有抽象方法的类一定是一个抽象类

作用:

​ 抽象类是从多个具体类中抽象出来的父类,它具有相同特征的类中抽象出一个抽象类,以这个抽象类为模板,从而避免子类的随意设计。抽象类实现的就是一种模板策略,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展和改造

细节问题:

  • abstract能修饰类 能修饰方法但是不能修饰变量

  • 拥有抽象方法的类一定是一个抽象类

  • 抽象类中可以拥有构造方法,而且构造方法还可以重载

  • 抽象类不能和 final private static 共存

  • final 修饰的方法不能被重写 而abstract修饰的方法必须在子类中实现

//带有abstract关键字的类称之为抽象类
public abstract class Shape {
	//1、抽象类中可定义变量(变量不能用abstract修饰)
	public String color;
	//2、抽象类中可以有构造方法,且构造方法还可进行重载
	public void Shape(){ }
	public void Shape(String Color)
	{
		this.color = color;
	}
	//3、抽象类中可以定义抽象方法,抽象方法不能有方法体(此处定义获取面积的抽象方法)
	public abstract void getSize();
	//4、抽象类中可以定义代码块
	{
		System.out.println("代码初始化...");
	}
	//5、抽象类中定义普通方法
	public String getColor() {
		return color;
	}
	public void setColor(String color) {
		this.color = color;
	}
	//6、抽象类中可以定义内部类,以上内容均可供子类进行调用
}
public class Triangle extends Shape {
	//子类继承抽象类,则必须实现抽象类中所有的抽象方法
	public int a;
	public int b;
	public int c;
	public int h;
	public Triangle() {
	}
	public Triangle(String color,int a, int b, int c, int h) {
		super.Shape(color);
		this.a = a;
		this.b = b;
		this.c = c;
		this.h = h;
	}
	@Override
	public void getSize() {
		System.out.println("面积:"+(a*h)/2);
	}
}
public class Go {
	public static void main(String[] args) {
		//抽象类无法被实例化,即无法通过new关键字来调用抽象类的构造函数创建实例:Shape s = new Shape();
		Triangle t = new Triangle("红色",3,4,5,4);
		t.getSize();
	}
}

🔖接口的相关概念

和类的定义不同 接口不再使用class而是使用interface,接口的语法规则如下:

[修饰符]  interface  接口名  extends  父接口1
{
	零个或者多个常量的定义
	零个或者多个抽象方法的定义
	零个或者多个内部类、接口
	零个或者多个默认方法或类方法定义
}

修饰符可以是public 或者省略 ,如果省略采用默认的访问控制修饰符。

接口名只需要遵循标识符的规则即可

接口可以继承接口 但是不能继承类

接口是一种规范,所以接口中不能包含构造器和初始化代码块

接口中可以包含成员变量(只能是静态变量),方法(只能是抽象实例方法,类方法、默认方法),内部类

接口中定义的静态变量,他们是与接口相关的,系统会自动为这些变量增加public、static和final修饰符,接口里的定义如下: “int MAX_SIZE=30;”等价于“public static final int MAX_SIXE =30;”

public interface Outer {
	/**1、接口中定义的成员变量只能是常量
	 *   常量的定义规则为大写字母+下划线_
	 *   系统默认自动添加public、static、final修饰符
	 *   public final static MAX_SIZE = 50;
	 */
	int MAX_SIZE = 50; 
	/**2、接口中不能定义普通的方法
	 * public void getSize()
	 *	{
	 *		
	 *	}
	 */
	//3、接口中可以定义抽象方法,且抽象方法可以重载,其相应的方法修饰符为public或省略不写
	public void out();
	public void out(String msg);
	//4、jdk1.8之后,新增了默认方法(即用default修饰的方法)
	default void print(String ... msgs)  //不定长参数
	{
		for(String s : msgs)
			System.out.println(s);
	}
	default void test()
	{
		System.out.println("test...");
	}
	//5、在接口中定义类方法,需要用static修饰
	static String staticTest()
	{
		return "接口中的默认的类方法";
	}
}
public class OuterTest {
	public static void main(String[] args) {
		//接口是更为纯粹的抽象类,不能够被实例化(不能够通过new创建对象)
		//Outer o = new Outer();
		//可以通过接口名访问接口中的常量以及类方法
		System.out.println(Outer.MAX_SIZE);
		System.out.println(Outer.staticTest());
	}
}

🔖两者之间的对比

相同之处:

  • 接口和抽象类都不能被实例化,他们都是依赖于子类实现

  • 接口和抽象类都可以包含抽象方法,实现接口或者继承抽象类都必须实现这里定义的全部的抽象方法

不同之处:

  • 接口中只能包含抽象方法和默认方法,和类方法,不能为普通方法提供方法的实现

  • 接口中不可以定义构造函数、抽象类中可以定义构造函数

  • 接口中不能定义初始化代码块,抽象类可以包含初始化代码块

  • 一个类最多只能包含一个直接父类,但是接口可以有多个父接口。而且一个类可以实现多个接口

【6】Lambda表达式

🔖匿名内部类

​ 匿名内部类是说只适合使用一次的类。匿名内部类不能被重复使用。

interface Product{
	public double getPrice();
	public String getName();
}
public class Computer implements Product {
	public String getName() {
		return "ACER";
	}
	public double getPrice() {
		return 10;
	}
}
public class AnonymousTest {
	public void showProduct(Product product)
	{
		System.out.println("产品名称:"+product.getName()+"价格:"+product.getPrice());
	}
	public static void main(String[] args) {
		AnonymousTest at = new AnonymousTest();
		//匿名内部类,Product是一个接口,需要通过子类实现接口中所有的抽象方法
		//下述{ }就是匿名内部类,相当于前方接口Product的子类
		at.showProduct(new Product(){
			public String getName() {
				return "宏基";
			}
			public double getPrice() {
				return 30;
			}
		});
	}
}

​ 在jdk1.8之前要求被局部内部类、匿名内部类访问的局部变量必须是final修饰的,从jdk1.8之后取消了这个限制, 系统会自动添加一个final修饰

🔖Lambda表达式

​ Lambda表达式可以简化操创建匿名内部类的对象

(形式参数)->{代码块}
  • 形式参数列表:接口中需要的形参列表

  • 箭头(->):通过英文中划线和大于符号组成,代表指向动作

  • 代码块:表示具体要执行的操作,可实现接口并且重写方法。如果这个接口中只有一个方法则可以省略方法

public interface Command {
	//在Command接口中定义一个抽象方法
	void test(int[] target);
}
public class ProcessArray {
	//封装接口Command中的test方法
	public void process(int[] target,Command cmd)
	{
		cmd.test(target);
	}
}
//使用匿名内部类实现接口
public class CommandTest {
	public static void main(String[] args) {
		//得到ProcessArray的一个对象
		ProcessArray pa = new ProcessArray();
		int[] array = {1,2,3,4,5};
		//调用ProcessArray的方法process,传入相应的参数
		//第一个参数为target整型数组,第二个参数是接口类型,则可使用匿名内部类实现该接口
		pa.process(array, new Command(){
			//实现Command接口中所有的抽象方法
			public void test(int[] target)
			{
				int sum = 0;
				for(int t : target)
				{
					sum += t;
				}
				System.out.println("sum:"+sum);
			}
		});
	}
}
//使用Lambda表达式实现接口
public class CommandTest2 {
	public static void main(String[] args) {
		ProcessArray pa = new ProcessArray();
		int[] array = {1,2,3,4,5};
		/**使用Lambda表达式实现Command接口
		 *  (int[] target)表示形式参数列表,即接口中需要的形式参数列表
		 *  箭头:-> 
		 *  代码块 { } :实现接口并重写相应的方法,如果接口中只有一个方法,可以省略方法,直接实现方法体中的内容
		 */
		pa.process(array, (int[] target)->{
			int sum = 0;
			for(int t : target)
			{
				sum += t;
			}
			System.out.println("sum:"+sum);
		});
	}
}

Lambda表达式和匿名内部类有如下相同点:

  • Lambda表达式和匿名内部类一样 可以直接访问局部变量,以及外部类的成员变量

  • Lambda表达式创建的对象与匿名内部类生成的对象一致的

//基本的Lambda表达式操作
interface Eateable
{
	void taste();
}
interface Flyable
{
	void fly(String weather);
}
interface Addable
{
	int add(int a,int b);
}
public class LambdaTest1 {
	public void eat(Eateable e)
	{
		System.out.println(e);
		e.taste();
	}
	public void driver(Flyable f)
	{
		System.out.println("驾驶ing"+f);
		f.fly("天气不错");
	}
	public void sum(Addable add)
	{
		System.out.println("1+1="+add.add(1, 1));
	}
	public static void main(String[] args) {
		LambdaTest1 lt = new LambdaTest1();
		//1、Lambda表达式的代码块只有一条语句,则可以省略{ }
		lt.eat(()->{System.out.println("haha,很好吃");});
		lt.eat(()-> System.out.println("haha,很好吃"));
		
		//2、Lambda表达式只有一个形式参数,则可以省略( )
		//如果有多个形式参数,则可以相应的省略类型声明,系统会自动匹配相应的数据类型
		lt.driver((String weather)->{ 
			System.out.println("今天"+weather);
			System.out.println("aaaa");
		});
		lt.driver(weather->{ 
			System.out.println("今天"+weather);
			System.out.println("aaaa");
		});
		
		//3、Lambda表达式的代码块只有一条语句,则可以省略{ }
		//   代码块中只有一条语句,需要返回值,亦可以省略return关键字
		lt.sum((a,b)->{return a+b;});
		lt.sum((a,b)-> a+b);
	}
}

🔖函数式接口

​ Lambda表达式是类型也被称为目标类型,Lambda表达式目标类型必须是函数式接口,所谓的函数式接口是指只有一个抽象方法的接口。函数式接口代表只包含一个抽象方法的接口,函数式接口可以包含多个默认方法,类方法,但只能声明一个抽象方法。

​ Java8专门为函数式接口提供了一个注解@FunctionalInterface,该注解通常放在定义接口的前方,该注解对程序没有任何作用,它仅仅是告诉编译器更加严格的检查当前接口是否是函数式接口。

​ Lambda表达式实现的是匿名内部类的替代,只能实现特定的函数式接口, 也就意味着Lambda表达式遵循以下两个限制

  • Lambda表达式目标类型必须是明确的函数式接口

  • Lambda表达式只能为函数式接口创建对象

为了保证Lambda表达式目标类型是一个明确的函数式接口,可以有三种常见的方式

  • 将Lambda表达式赋值给函数式接口的变量

  • 将Lambda表达式作为函数式接口的形参传递给某个方法

  • 使用函数式接口对Lambda表达式进行强制类型转换

@FunctionalInterface
interface FkTest
{
	void run();
}
public class LambdaTest2 {
	public static void main(String[] args)
	{
		//Runnable是一个函数式接口,其只包含一个抽象方法,则可通过Lambda实现该将接口
		Runnable r = ()->{ System.out.println("Runnable是一个函数式接口");};
		//Object本身并不是一个函数式接口,无法通过Lambda表达式实现,
		//但可以通过强制类型转化实现
		Object obj = (Runnable)()->{
			System.out.println("Object并不是一个函数式接口");
		};
		//Lambda表达式的形参列表与函数式接口中的抽象方法中的形参列表相同
		Object obj2 = (FkTest)()->{
			System.out.println("Object强制转化为自定义的函数式FkTest");
		};
	}
}

🔖方法引用与构造器引用

​ 如果labmda表达式代码块只有一条代码,程序就可以省略Lambda表达式中的

​ 如果Lambda表达式只有一条代码,还可以在代码中使用方法引用和构造器引用

​ Lambda表达式支持的方法引用和构造器引用

种类示例说明对应的Lambda表达式
引用类方法类名::类方法函数式接口中被实现方法的全部参数传递给该类方法作为形参(a,b)->类名.类方法
引用特定对象的实例方法特定对象::实例方法函数式接口中被实现方法的全部参数传递给该类方法作为形参(a,b…)->特定对象.实例方法(a,b..)
引用某类对象的实例方法类名::实例方法函数式接口中被实现方法的全部参数传递给该类方法作为形参
引用构造器类名::new
import javax.swing.JFrame;
@FunctionalInterface
//Converter为函数式接口
interface Converter
{
	//形参为String类型,返回值类型为Integer
	Integer convert(String from);
}
@FunctionalInterface
interface MyTest
{
	String test(String a,int b,int c);
}
@FunctionalInterface
interface YourTest
{
	JFrame win(String title);
}
public class MethodRefer {
	public static void main(String[] args) {
		//通过Lambda表达式创建Converter对象
		Converter converter = from -> Integer.valueOf(from);
		Integer i = converter.convert("123");
		System.out.println(i);
		//1、通过方法引用替代Lambda表达式,Conveter函数式接口中被实现的方法的
		//全部参数“(String from)”传递给类方法
		Converter converter1 = Integer::valueOf;
		Integer i1 = converter.convert("123");
		System.out.println(i1);
		
		//2、方法引用代替Lambda表达式引用特定对象的实例方法
		//得到字符串中指定字符的位置
		System.out.println("hellojava".indexOf("l"));
		Converter converter2 = "hellojava"::indexOf;
		Integer i2 = converter2.convert("l");
		
		//截取指定字符串,返回截取后的字符串内容
		System.out.println("hellojava".substring(3,6));
		MyTest mt = (a,b,c) -> a.substring(b, c);
		System.out.println(mt.test("hellojava", 3, 6));
		MyTest mt1 = String::substring;
		System.out.println(mt1.test("hellojava", 3, 6));
		
		//3、引用构造器
		YourTest yt = title -> new JFrame(title);
		//相当于JFrame j = new JFrame(title)的效果
		JFrame jf = yt.win("haha");
		System.out.println(jf);
		YourTest yt1 = JFrame::new;
		JFrame jf1 = yt.win("haha");
		System.out.println(jf1);
	}
}

【7】包装类

🔖基本类型的包装类

​ Java是面向对象的编程语言包含了八种基本数据类型。是八种基本数据类型不支持面向对象的编程机制。基本数据类型不具备对象的特征。基本数据类型没有成员变量也没有方法,所以不方便使用。我们基本数据类型不能使用面向对象的一些特征,为了解决这些问题,定义了包装类。

基本数据类型和包装类

基本数据类型包装类
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booelanBoolean

在jdk1.5之后提供了自动拆箱和自动装箱功能。

  • 所谓的自动装箱,就是把一个基本数据类型直接赋值给对应的包装类

  • 所谓的自动拆箱,是指把包装类直接赋值给基本数据类型。

public class AutoPacking {
	/**
	 * Java(jdk1.5之后)机制提供了自动装箱和自动拆箱功能
	 * 自动装箱:将一个基本数据类型的数值直接赋给对应的包装类
	 * 自动拆箱:将包装类直接赋值给基本数据类型
	 */
	public static void main(String[] args) {
		//自动装箱:此处将int类型的数据直接赋值给相应的包装类Integer,依次类推进行分析
		Integer iObj = 5;
		Object boolObj = true;	//直接把boolean变量赋值给一个Object类型
		//自动拆箱:此处将Integer包装类直接赋值给一个Object类型或int类型
		Object obj = true;
		int i = iObj;
		if(boolObj instanceof Boolean)
		{
			boolean b = (Boolean)boolObj;
			System.out.println(b);
		}
	}
}

public class PackingTest {
	/**
	 * 包装类的作用:
	 * 1.包装类可以实现基本数据类型和字符串之间的转换
	 *   利用包装类提供的parseXxx(String) 把String类型转换为基本类型
	 *   利用包装类的构造函数自动转换
	 * 2.把基本类型转换为字符串
	 *   基本类型+””(空字符串)
	 *   利用String 的方法 String.valueOf
	 */
	public static void main(String[] args) {
		/**
		 * 1.将String类型转化为相应的基本数据类型
		 * 1.1利用包装类提供的parseXxx(String) 把String类型转换为基本类型
		 * 相应的字符串数据转化为相应的数值类型输出,要注意要转化的基本数据类型的范围
		 * 如果转化的字符串数据是数值类型(除去char类型、boolean类型),下述转化均能通过
		 * 但如果转化的数据带有字符,则转化出错
		 *  java.lang.NumberFormatException: For input string: "a"
		 */
		String str = "1";
		byte b = Byte.parseByte(str);
		short s = Short.parseShort(str);
		int i = Integer.parseInt(str);
		long l = Long.parseLong(str);
		float f = Float.parseFloat(str);
		double d = Double.parseDouble(str);
		System.out.println(str+"-"+b+"-"+s+"-"+i+"-"+l+"-"+f+"-"+d);
		/**
		 * 1.2利用包装类的构造函数实现自动转化
		 * 熟练把握对应基本数据类型的相应的包装类
		 * byte->Byte  short->Short  int->Integer  long->Long
		 * float->Float  double->Double  
		 * char->Character  boolean->Boolean 
		 */
		byte b1 = new Byte(str);
		short s1 = new Short(str);
		int i1 = new Integer(str);
		long l1 = new Long(str);
		float f1 = new Float(str);
		double d1 = new Double(str);
		//char c = new Character('a');Character构造器只接收字符类型的数据
		//boolean b = new Boolean(true);Boolean构造器只接收true、false
		System.out.println(str+"-"+b1+"-"+s1+"-"+i1+"-"+l1+"-"+f1+"-"+d1);
		/**
		 * 2.将基本数据类型转化相应的字符串类型
		 * 2.1 基本数据类型+“”(空字符串)
		 * 此处+(加号)的作用相当于连接字符串
		 * 此处注意3+4+"hello"(7hello)与"hello"+3+4(hello34)的区别
		 */
		//此处简单以int类型的数据为例进行分析
		int test = 123;
		String st = test+"";
		System.out.println("经过第1种方式转换后的字符串数据为:"+st);
		/**
		 * 2.2利用String 的方法 String.valueOf
		 * String类的valueOf方法可以接受的类型除了8种基本数据类型,还包括
		 * Object类型、字符数组类型
		 * 虽然byte、short类型并没有明确指出,也可直接代入(限定数值范围内)
		 */
		String st2 = String.valueOf(test);
		System.out.println("经过第2种方式转换后的字符串数据为:"+st2);
	}
}
public class WrapperClassCompare {
	public static void main(String[] args) {
		Integer a = new Integer(6);
		System.out.println(a>5.0);//true
		//双等号==判断引用(看是否引用同一个内存空间),new是在内存中开辟新的空间
		System.out.println(new Integer(2)==new Integer(2));//false
		
		Integer i1 = 2;//直接赋值,相当于是在常量池中
		Integer i2 = 2;//直接赋值,相当于是在常量池中
		System.out.println(i1==i2);//true
		
		Integer i3 = 128;
		Integer i4 = 128;
		System.out.println(i3==i4);//false
		/**
		 * 打印false的原因,可通过查阅Integer的原码的实现进行分析
		 * 当数据大于127是通过new进行创建
		 * 当数据小于等于127则直接进行赋值
		 * 缓存数组的大小是小于128的,当数据小于等于127可以直接从缓冲数组里面取数据,
		 * 然而当数据大于127会重新通过new进行创建
		 * 	static final Integer cache[];
   			cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
		 */
	}
}

🔖处理对象Object

​ Object 是类层次结构的根,每个类都可以将 Object 作为超类,所有类都直接或者间接的继承自该类

a.toString

​ toString方法是Object类中的一个实例方法,所有的类都默认继承了java.lang.Object这个类。所有的对象都可以调用toString方法,toString方法就是一个自我描述的方法,主要是用来打印该对象的详细信息。

public class toStringTest {
	public String name;
	public int age;
	/*@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}*/

	public static void main(String[] args) {
		toStringTest p = new toStringTest();
		/**
		 * 1.如果没有重载toString方法,则下述两种方式均是默认输出对象的内存地址
		 * com.base.pack.Person@6d06d69c
		 */
		System.out.println(p);
		System.out.println(p.toString());
		/**
		 * 2.如果实现重载toString方法,则会调用重载后的toString方法,
		 * 实现用户指定的方式输出对象的相关信息
		 * 可通过@Override对toString方法进行重载标识
		 * Person [name=null, age=0]
		 */
	}
}
b.==和equals的比较

​ 内存的五片区域 :寄存器,栈内存,堆内存,方法区,本地方法区

​ 栈内存:存储的都是局部变量,一旦使用完毕,立即释放

​ 堆内存:存储的是实体(对象)

​ Java程序中有两种方式是两个变量是否相等。一种是利用==判断,另外一种是使用equals方法来判断。

​ 当使用==进行判断的时候,如果两个变量都是基本类型的变量,并且都是数值类型,则只要两个变量的值相同时,便返回true。但如果两个变量的类型是引用类型,则只有两个变量均指向相同的对象(内存地址)才返回true。

​ 使用equals方法进行判断时则以具体equals方法中实际比较的项目为准(使用equals判断两个字符串是否相等),可通过重写equals方法实现自定义的比较方式

public class EqualsTest {
	public static void main(String[] args) {
		//基本数值类型的比较
		int i = 65;
		float d = 65.0f;
		char s = 'A';
		System.out.println(i==d);   //true
		System.out.println(i==s);   //true
		System.out.println(d==s);   //true
		
		//引用类型的比较
		String str1 = new String("haha");
		String str2 = new String("haha");
		System.out.println(str1==str2);   //false
		System.out.println(str1.equals(str2));   //true
	}
}
public class StringCompareTest {
	public static void main(String[] args) {
		String str1 = "hello你好!";
		String str2 ="hello";
		String str3 = "你好!";
		String str4 = "hello"+"你好!";
		String str5 = "hello"+"你好"+"!";
		//上述字符串在编译的时候便可以确定字符串的内容,因此上述均是引用常量池的内容
		String str6 = str2+str3;  
		//str6的内容在编译时无法直接确定,因此str6并不是引用常量池的内容,而是在运行时在堆内存中创建新的对象
		String str7 = new String("hello你好!");
		//str7通过new创建新的对象,存在在堆内存中
		System.out.println(str1==str4);   //true
		System.out.println(str1==str5);   //true
		System.out.println(str4==str5);   //true
		System.out.println(str1==str6);   //false
		System.out.println(str1==str7);   //false
		System.out.println(str1.equals(str4));   //true
		System.out.println(str1.equals(str5));   //true
		System.out.println(str4.equals(str5));   //true
		System.out.println(str1.equals(str6));   //true
		System.out.println(str1.equals(str7));   //true
	}
}
c.重写Equals方法
public class Person {
	public String name;
	public int id;
	public Person() {   }
	public Person(String name, int id) {
		super();
		this.name = name;
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	@Override
	public boolean equals(Object obj) {
		//判断是否指向同一个对象
		if(this==obj)
			return true;
		//判断obj不为null,且两个被比较对象的类型相同
		//(obj!=null&&obj instanceof Person)
		if(obj!=null&&obj.getClass()==Person.class)
		{
			//如果obj是Person类型,则先将其进行强制类型转换
			Person p = (Person)obj;
			//如果两个对象的id相同则可认为两个比较对象相同,从而返回true
			if(this.id==p.getId())
				return true;
		}
		return false;
	}
	public static void main(String[] args) {
		//测试
		Person p1 = new Person("haha",1);
		Person p2 = new Person("bibi",1);
		Person p3 = new Person("haha",2);
		System.out.println(p1.equals(p2));   //true
		System.out.println(p1.equals(p3));   //false
	}
}

【9】垃圾回收

🔖对象和垃圾回收

​ Java的垃圾回收的java语言重要的功能之一,当程序创建对象,数组,或者引用对象,系统都会在内存中为其分配一个空间,对象就是保存在这个内存区域中,当这块内存区域不再被任何变量引用,这个内存就变为垃圾,等待垃圾回收机制进行回收。

垃圾回收具有以下特点:

  • 垃圾回收机制只负责回收堆内存中的对象,不会回收任何物理资源(比如数据库连接,网络,IO等)

  • 程序无法精确的控制垃圾回收的运行,垃圾回收会在合适的时候进行,当对象永久性的失去引用,系统会在合适的时候回收它所占的内存

  • 在垃圾回收机制回收任何对象之前,都会调用它的finalize方法,该方法可能使该对象重新复活,从而导致垃圾回收机制的取消

a.对象在内存中的状态

当一个对象在堆内存中运行的时候,根据它引用的状态,可以把它所处的状态分为三种情况:

​ 可达状态:当创建一个对象后,如果有一个以上的引用变量引用它,则这个对象处于可达状态,程序可以通过对象的实例变量和方法进行调用。

​ 可恢复状态:如果程序中的某个对象不再有任何引用变量引用它,它就进入到了可恢复状态,在这个状态下,系统的垃圾回收机制准备回收这该对象所占的内存。,在回收该对象之前会调用该对象的finalize方法,看是否还有其他变量引用,如果有可以由可恢复状态进入到可达状态。

​ 不可达状态: 当对象与所有引用变量的关联都被切断,并且系统已经调用该对象的finalize方法也无法进入到可达状态, 从而导致进入不可达状态, 系统才会进行垃圾回收。

public class StatusTest {
	public void test() {
		/**
		 * 当执行下方代码:定义一个变量a,并且执行内存的"Spring入门基础"字符串     
		 * 此时"Spring入门基础"处于可达状态
		 */
		String a=new String("Spring入门基础");
		/**
		 * 创建了一个新的对象 "redis基础"字符串对象     
		 * 让a变量引用这个 "redis基础"字符串对象,此时"Spring入门基础"这个字符串
		 * 对象没有任何变量引用 ,进入到可恢复状态。分析可知此时 "redis基础" 是可达状态
		 */
		a=new String("redis基础");
	}
}

对象的状态转化图说明

b.强制垃圾回收

​ 当一个对象失去引用后,系统何时调用它的finalize方法执行资源清理,何时会进入到不可达状态,

​ 对于程序来说,程序员无法精准的控制系统何时进行垃圾回收,也无法明确在什么时间进行垃圾回收,程序只能控制一个对象不再被任何变量引用,绝不能控制它在何时被垃圾回收。

​ 虽然程序无法精准的控制垃圾回收,但是依然可以通过代码指定系统进行强制垃圾回收。但是系统执行垃圾回收仍然是不确定的,大部分时候程序员只能指定强制垃圾回收,但是在何时进行还是由jvm自身决定。

​ 系统有如下方式可以进行强制垃圾回收:

  • 调用System类的gc方法

  • 调用Runtime类的gc方法

public class GcTest {
	public static void main(String[] args) {
		for (int i = 0; i < 5; i++) {
			GcTest gc = new GcTest();
			/**执行强制垃圾回收.. 一旦执行垃圾回收 系统会调用这个对象finalize方法看
			 * 是否可以进入到可达状态虽然指定了强制垃圾回收 ,但是系统会自己觉得什么时候
			 * 执行垃圾回收.. -->System.gc();
			 */
			Runtime.getRuntime().gc();
			//java  -verbose:gc GcTest 这个命令详细查看垃圾回收的执行
		}
	}
	/**
	 * 分析:按理来说强制执行应该是循环执行五次,但从结果显示可知System.gc();并不是立即执行
	 * finalize方法,而是通知系统进行垃圾回收,但具体什么时候执行还是要由系统自行决定
	 * 调用了System.gc()进行垃圾回收,但由系统自行决定执行的时间或者是执不执行,因此每次运行
	 * 结果输出的次数都会小于5(调用的次数)
	 */
	@Override
	protected void finalize() throws Throwable {
		System.out.println("系统正在执行清理GcTest这个对象....");
	}
}

重写finalize方法

public class FinalizeDemo {

    public static void main(String[] args) {
        // 定义汽车对象
        Car bmw = new Car("宝马");

        /**
         * 将bmw指向为null
         * 此时bmw对象是一个垃圾,垃圾回收期会自动回收(销毁对象)
         * 在释放对象之前,会调用对象的finalize方法
         * 如果Car类中重写了finalize方法,则会调用自定义的finalize逻辑
         * 如果Car类没有重写finalize方法,则会默认调用Object提供的finalize
         */
        bmw = null;
        // 主动调用垃圾回收器
        System.gc();
        System.out.println("程序退出...");
    }

}

class Car{
    private String name;
    public Car(String name){
        this.name = name;
    }
    // 重写finzlize
    @Override
    protected void finalize() throws Throwable {
        // 自定义实现资源释放操作
        System.out.println("销毁汽车"+name);
        System.out.println("资源释放...");
    }
}

c.Finalize方法

在垃圾回收机制回收某个对象所占用的内存,会调用这个对象的finalize方法。

Finalize方法有4个特点:

  • 不要主动调用某个对象的finalize方法,把该方法交给垃圾回收机制自动执行

  • Finalize方法何时被调用,是否被调用 都是不确定的,不要把finalize方法当做一定会执行的方法

  • 在jvm执行可恢复对象的finalize方法 可以使该对象或者系统中的其他对象重新进行到可达状态

  • 当jvm执行finalize 如果出现异常, 垃圾回收并不会报告异常,程序可以继续执行

public class FinalizeTest {
	private static  FinalizeTest ft=null;
	public  void info()
	{
		System.out.println("系统正在执行垃圾回收....");
	}
	public static void main(String[] args) 
	{
		//1.创建FinalizeTest对象,没有任何变量引用 ,进入到可恢复状态
		new FinalizeTest();
		//2.通知系统进行强制垃圾回收,但不一定会立即执行finalize方法
		System.gc();
		//3.强制垃圾回收机制 调用可恢复对象的finalize方法,此时ft便初始化完成
		System.runFinalization();
		//4.ft初始化完成,便可执行info方法
		ft.info();
	}
	public void finalize()  {
		ft=this;
	}
}

[!attention]

扩展项:进阶需相应了解垃圾回收相关的算法,至少掌握三种以上

其他扩展

深入理解main方法

public static void main(String[] args) {}
  • main方法是虚拟机调用
  • java虚拟机需要调用类的main 方法,因此该方法的访问权限是public
  • java虚拟机在执行main方法时不必创建对象,所以该方法用static修饰
  • main方法接收String[] args的数组参数,该数组保存执行java命令时传递给所运行的类的参数
  • java执行的程序:java 执行的程序 参数1 参数2 参数3(其中这三个参数分别对应args数组)

idea中传递参数,可以通过配置Configurations的Program arguments

public class MainTest {

    public static void main(String[] args) {
        // 打印参数信息
        for (int i = 0; i < args.length; i++) {
            System.out.println("参数"+ i + ":" + args[i]);
        }
    }
}

image-20240429134556058

3.常用类

【1】API

❓什么是API?

​ API (Application Programming Interface) :应用程序编程接口

​ java中的API:指的就是 JDK 中提供的各种功能的 Java类,这些类将底层的实现封装了起来,使得开发者不需要关心这些类是如何实现的,只需要通过帮助文档学习这些类如何使用即可。

【2】常用类介绍

概述:

  • 与系统相关的常用类(Scanner、System、Runtime)

  • 与字符串相关的常用类(Object、String、StringBuffer、StringBuilder、StringTokenizer)

  • 与数学数字相关的常用类(Math、Random、BigDecimal、BigInteger)

  • 与日期相关的常用类(Date、Calendar、位符格式化:NumberFormat、DateFormat、SimpleDateFormat)

🔖与系统相关的常用类

(1)Scanner类
public class ScannerInt {
	public static void main(String[] args) {
		/**
		 * Scanner类用于读取数据
		 * 1.用于读取键盘上的数据
		 * 2.用于读取键盘上的数据
		 */
		//1.借助Scanner类可从键盘上读取不同类型的数据
		Scanner sc = new Scanner(System.in);
		System.out.println("请输入相应的整型数据,输入-1时退出");
		while(sc.hasNextInt())
		{
			int i = sc.nextInt();
			if(i==-1)
			{
				System.out.println("退出输入操作...");
				System.exit(0);
			}
			System.out.println("输入的数据为:"+i);
		}
	}
}
public class ScannerString {
	/**
	 * 从键盘中接收String类型的数据,输入为“exit”
	 * 时取消输入操作,代码设计时经常用到这种思路
	 */
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		System.out.println("请输入String类型的数据,输入为“exit”的时候退出输入操作");
		while(sc.hasNext())
		{
			String str = sc.next();
			if(str.equals("exit"))
			{
				System.out.println("退出输入操作!");
				System.exit(0);
			}
			System.out.println("输入的Srting类型的数据为:"+str);
		}
	}
}
public class ScannerFile {
	/**
	 * 借助Scanner类从磁盘上读取数据
	 */
	public static void main(String[] args) {
		Scanner sc;
		try {
			sc = new Scanner(new File("d:\\haha.txt"));
			while(sc.hasNext())
			{
				String str = sc.nextLine();
				System.out.println(str);
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}
}
(2)System类

System常用方法

方法名说明
public static void exit(int status)终止当前运行的 Java 虚拟机,非零表示异常终止
public static long currentTimeMillis()返回当前时间(以毫秒为单位)

参考案例:

public class ViewWork {
	public ViewWork() {
		System.out.println("1");
	}
	//err是带有缓冲输出的,并不是立即输出,而是当缓冲区满了,或者程序执行完毕才执行输出
	{
		System.err.println("2");
	}
	
	static {
		System.out.println("3");
	}
	
	public static void main(String[] args) {
		ViewWork  vw =new ViewWork();
		/**在正常情况下在类加载时先执行static代码块,
		 * 随后执行普通代码块,最后执行构造方法中的内容
		 * 但由于err是带缓冲输出,因而输出的时间并不确定,
		 * 从而导致每次输出的结果都不尽相同(err输出是红色显示)
		 * 有时是同时输出,有时是延时输出
		 */
	}
}
public class SystemTest {
	public static void main(String[] args) {
		//1.借助System类得到当前系统的时间(得到的是毫秒数)
		System.out.println("当前系统时间为:"+System.currentTimeMillis());
		
		/**
		 * 2.虽然是不同的两个对象,但String重写了hashCode,
		 * 只要满足两个字符串的字符序列相同则其hashCode也对应
		 * 相同,但这样就无法用hashCode唯一标识相应对象,实际上
		 * 一般使用System类的identityHashCode方法
		 * (System.identityHashCode(对象))对对象进行唯一标识
		 */
		String str1 = new String("hello");
		String str2 = new String("hello");
		System.out.println("str1与str2:"+(str1==str2));//false
		//hashCode标识的是当前对象的内存地址,但此处str1与str2的hashCode相同
		System.out.println(str1.hashCode()+"---"+str2.hashCode());
		System.out.println("str1:"+System.identityHashCode(str1));
		System.out.println("str2:"+System.identityHashCode(str2));

		//举例分析
		String str3 = "haha";
		String str4 = "haha";
		System.out.println("str3与str4:"+(str3==str4));
		System.out.println(str3.hashCode()+"---"+str4.hashCode());
		System.out.println("str3:"+System.identityHashCode(str3));
		System.out.println("str4:"+System.identityHashCode(str4));
		/**
		 * 分析,此处字符串数据str3、str4均执行常量池中的“haha”,因此是指向同一个对象
		 * 因此他们的hashCode相同、identityHashCode也相同
		 */
		/**
		 * 输出结果:
		 * 当前系统时间为:1521287712848
		 * str1与str2:false
		 * 99162322---99162322
		 * str1:1829164700
		 * str2:2018699554
		 * str3与str4:true
		 * 3194802---3194802
		 * str3:1311053135
		 * str4:1311053135
		 */
	}
}
(3)Runtime类
public class RuntimeTest {
	/**
	 * Runtime类代表的java程序的运行环境,每个java程序都有
	 * 与之对应的Runtime实例,应用程序与对象的运行环境进行关联
	 * @throws Exception 
	 */
	public static void main(String[] args) throws Exception {
		//创建Runtime对象
		/**
		 * 1.输出相关的系统信息
		 * a.处理器数量:rt.availableProcessors()
		 * b.虚拟机中空闲的内存:+rt.freeMemory()
		 * c.Java虚拟机试图使用的最大内存量:rt.maxMemory()
		 * d.总内存数:rt.totalMemory()
		 */
		Runtime rt = Runtime.getRuntime();
		System.out.println("处理器数量:"+rt.availableProcessors());
		System.out.println("虚拟机中空闲的内存:"+rt.freeMemory());
		System.out.println("Java虚拟机试图使用的最大内存量:"+rt.maxMemory());
		System.out.println("总内存数:"+rt.totalMemory());
		/**
		 * 2.执行相关的指令
		 * a.mspaint.exe:打开画图工具
		 * b.notepad.exe:打开记事本
		 * c.shutdown.exe  -s -t 500":定时执行关机指令
		 * d.shutdown -a:取消关机指令
		 */
		rt.exec("mspaint.exe");//打开画图工具
		rt.exec("notepad.exe");//打开记事本
		rt.exec("shutdown.exe  -s -t 500");//定时执行关机指令
		rt.exec("shutdown -a");//取消关机指令操作
	}
}

🔖与字符串相关的常用类

(1)Object类和Java7新增的Objects工具类

Object是所有类、数组的父类,也就说允许在任何类对象中调用Obejct的方法。Object常用的方法:

equals(Object obj):判断字符串是否相同、可以通过重载判断是否引用同一个对象

finalize():初始化

hashCode():获取对象的内存地址(哈希码)

getClass():获取当前对象所属类的类名

toString():输出对象的内存地址,可通过重载实现输出对象的相关信息

浅克隆

​ Java中的Object类还提供了一个clone的方法,该方法用于帮助其他类实现自我克隆,也就说自我复制。

public class Address {
	private String city;
	private String street;
	public Address() {}
	public Address(String city, String street) {
		this.city = city;
		this.street = street;
	}
	public String getCity() {
		return city;
	}
	public void setCity(String city) {
		this.city = city;
	}
	public String getStreet() {
		return street;
	}
	public void setStreet(String street) {
		this.street = street;
	}
	public String toString() {
		return  city +street ;
	}
}
/**
 *	如果一个类中要实现克隆,需要实现Cloneable接口
 *	Cloneable接口是个空接口,是标志性接口
 *	如果实现该接口,需要重写clone方法(不需要作任何改动)
 *	此处定义User类实现Cloneable接口 
 */
public class User implements Cloneable {
	private String name;
	private int age;
	private Address address;
	public User()
{
	}
	public User(String name, int age, Address address) {
		this.name = name;
		this.age = age;
		this.address = address;
	}
	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 Address getAddress() {
		return address;
	}
	public void setAddress(Address address) {
		this.address = address;
	}
	/**
	 * 重写clone方法,不需要对原来的内容作任何改动
	 * 可以返回指定的类型,此处返回User类型
	 */
	protected User clone() throws CloneNotSupportedException {
		return (User)super.clone();
	}
}
public class CloneTest {
	public static void main(String[] args) throws Exception {
		Address address = new Address("A城市","A1街道");
		User u1 = new User("xx",18,address);
		User u2 = (User) u1.clone();
	System.out.println("u1:"+u1.getName()+"--"+u1.getAge()+"--"+u1.getAddress());
	System.out.println("u2:"+u2.getName()+"--"+u2.getAge()+"--"+u2.getAddress());
		System.out.println(u1==u2);//false
		System.out.println(u1.getAddress()==u1.getAddress());//true
		/**
		 * 浅克隆的相关概念:
		 * 分析:u2是由u1对象克隆而来,因此u1、u2并不是指向同一个对象
		 * 但两者的信息完全相同(此处为姓名、年龄、地址)
		 * 克隆的含义是指将数据复制一份,由u2指向这份数据,但这份数据中的
		 * address指向还是原来的address,因此有
		 * u1.getAddress()==u1.getAddress()返回的结果为true
		 * 而u1==u2返回的结果为false
		 */
	}
}
Objects工具类

​ Java7新增了一个工具类Objects, 它提供了一些工具方法,这些工具方法大多数是针对安全性操作的。

public class ObjectsTest {
	static ObjectsTest obj;
	public static void main(String[] args) {
		/**
		 * 在无法得知传递的对象是否为null时,如果直接使用对象的hasCode方法
		 * 或者是其它相应的方法,系统会报空指针异常
		 * 因此java7新增了Objects类可以进行安全操作,而不报错
		 */
		/**
		 * System.out.println(obj.hashCode());
		 * System.out.println(obj.toString());
		 * 系统报java.lang.NullPointerException
		 */
		//1.借助Objects类进行安全操作(hashCode方法、toString方法 )
		System.out.println(Objects.hashCode(obj));//0
		System.out.println(Objects.toString(obj));//null
		/**
		 * 2.借助Objects类提供的requireNonNull方法验证obj是否为null
		 * 如果obj为null,则人为抛出相应的异常
		 */
		System.out.println(Objects.requireNonNull(obj,"obj的参数不能为null"));
	}
}
(2)String类

​ 字符串就是一连串的字符序列,Java提供了String和StringBuffer两个封装字符串,并且提供了一系列的方法进行操作字符串。

​ String类是不可变类,就是一个String对象被创建出来以后,包含在这个字符串对象的字符序列的不可改变的,即直到这个对象被销毁,被垃圾回收都不会改变。

创建字符串对象的区别
  • 通过构造方法创建

    ​ 通过 new 创建的字符串对象,每一次 new 都会申请一个内存空间,虽然内容相同,但是地址值不同

  • 直接赋值方式创建

    ​ 以“”方式给出的字符串,只要字符序列相同(顺序和大小写),无论在程序代码中出现几次,JVM 都只会建立一个 String 对象,并在字符串池中维护

字符串的比较
  • == 比较基本数据类型:比较的是具体的值
  • == 比较引用数据类型:比较的是对象地址值
String常用方法
/**
 *	通过查阅API中java.lang包下的与String类、StringBuffer类、
 *	StringBuilder类相关的方法进行测试分析
 */
public class StringTest {
	public static void main(String[] args) {
		/**
		 * 1.String类常见的构造方法
		 * a.String():指向空字符序列
		 * b.String(char[] value):用指定字符数组创建字符串数据
		 * c.String(char[] value, int offset, int count) 
		 *   用指定字符数组的一个字数组创建字符串数据
		 * d.String(String original):用指定字符串创建字符串数据
		 * e.String(StringBuffer buffer):用StringBuffer类型的字符串创建新的字符串数据 
		 * f.String(StringBuilder builder):用StringBuilder类型的字符串创建新的字符串数据
		 * g.还可将字符串常量直接复制给String类型的变量
		 */
		String str = "hellojava";
		/**
		 * 2.String常用的方法
		 * a.得到字符串中指定位置的字符(char)、得到指定字符的位置(int)
		 *   charAt(int index):返回指定索引处的 char值
		 *   indexOf(int ch):返回指定字符在此字符串中第一次出现处的索引
		 *   indexOf(int ch, int fromIndex):返回在此字符串中第一次出现指定字符处的索引,从指定的索引开始搜索 
		 *   indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引
		 *   indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
		 *   lastIndexOf(int ch):返回指定字符在此字符串中最后一次出现处的索引 
		 *   lastIndexOf(int ch, int fromIndex):返回指定字符在此字符串中最后一次出现处的索引,从指定的索引处开始进行反向搜索。 
		 *   lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引
		 *   lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索。 
		 * b.两个字符串之间比较大小
		 *   compareTo(String anotherString):按字典顺序比较两个字符串,返回值为int类型
		 *   compareToIgnoreCase(String str):按字典顺序比较两个字符串,不考虑大小写,返回值为int类型
		 * c.链接字符串(可通过‘+’实现)
		 *   concat(String str):将指定字符串连接到此字符串的结尾,返回值为String类型
		 * d.看指定字符串是否包满足要求(是否为空、是否包含某些内容、是否包含指定的前后缀、是否匹配)
		 *   isEmpty():当 length()为 0 时返回 true。 
		 *   contains(CharSequence s):若字符串包含指定的 char值序列则返回 true
		 *   startsWith(String prefix):若字符串以指定的前缀开始则返回true 
		 *   startsWith(String prefix, int toffset):若从指定索引开始的子字符串以指定前缀开始则返回true 
		 *   endsWith(String suffix):如果此字符串以指定的后缀结束则返回true
		 *   matches(String regex):如果此字符串匹配给定的正则表达式则返回true
		 * g.将字符串与指定的字符串数据进行比较
		 *   equals(Object anObject):如果两个字符串的字符序列相同则返回true
		 *   equalsIgnoreCase(String anotherString):不考虑大小写进行比较
		 * h.得到指定字符串的某些属性(信息)
		 *   hashCode():返回指定字符串的哈希码 
		 *   length():返回此字符串的长度(int类型)
		 *   toString():返回此对象本身
		 *   trim():返回字符串的副本,忽略前导空白和尾部空白
		 * g.用指定的内容替换原字符串数据
		 *   replace(char oldChar, char newChar)用 newChar替换此字符串中出现的所有 oldChar得到新字符串数据 
		 *   replaceAll(String regex, String replacement):用replacement替换此字符串所有匹配给定的正则表达式的子字符串
		 *   replaceFirst(String regex, String replacement):使用给定的 replacement替换此字符串匹配给定的正则表达式的第一个子字符串。 
  		 * h.根据指定要求拆分字符串、 截取字符串数据
  		 *   split(String regex):根据给定正则表达式的匹配拆分此字符串(返回字符串数组String[])
  		 *   substring(int beginIndex):返回一个新的字符串,它是此字符串的一个子字符串。 
		 *   substring(int beginIndex, int endIndex) 
		 * i.将指定字符串进行转化(转化为字符数组、全大写、全小写)
		 *   toCharArray():将此字符串转换为一个新的字符数组(char[]) 
		 *   toLowerCase():将指定字符串的所有字符都转换为小写(String) 
		 *   toUpperCase():将指定字符串的所有字符都转换为大写(String)
		 * j.将基本的数据类型转化为String类型
		 *   valueOf(...):可以直接进行转化的类型有除了byte、short类型的其他六种基本类型
		 *                以及字符数组、Object对象
		 */
		//1.charAt:返回指定索引处的char值
		System.out.println(str.charAt(0));//h
		//2.compareTo:按字典顺序比较两个字符串(得到第一个不相同的字符的ASCII码差值)
		System.out.println(str.compareTo("HellO"));
		System.out.println(str.compareToIgnoreCase("heLLO"));
		//3.concat:拼接字符串
		System.out.println(str.concat("hello"));//hellohello
		//4.contains:判断是否包含某个子字符串
		System.out.println(str.contains("bi"));//true
		System.out.println(str.contains("gg"));//false
		//5.startWith、endWith:是否包含指定前后缀
		System.out.println(str.startsWith("hello"));//true
		System.out.println(str.endsWith("bb"));//false
		//6.查找字符在指定字符串中出现的位置
		String str1 = "haha hello gogo study";
		System.out.println(str1.indexOf("b"));//5
		//indexOf("s",5):跳过5个字符不读,即从索引位置为5处开始查找
		System.out.println(str1.indexOf("s",5));
		//lastIndexOf("u"):得到当前字符串中“u”最后一次出现的的位置
		System.out.println(str1.lastIndexOf("u"));
		//7.isEmpty:判断是否为空
		System.out.println(str.isEmpty());//false
		//8.切割字符串:sqlit
		String str2 = "aa,bbb,cc,ddd";
		String[] strs = str2.split(",");
		for(String s:strs)
			System.out.print(s+"-");//aa-bbb-cc-ddd-gogo
		System.out.println();
		/**
		 * 9.substring:截取字符串
		 * 一个参数:截断n之前的内容,取后边的部分
		 * 两个参数:截断指定区域的字符串,包含左侧区域不包含右侧区域
		 */
		String str3 = "hahagogo";
		System.out.println(str3.substring(4));//截断前4个:gogo
		System.out.println(str3.substring(4,7));//gog
		//10.转换大小写
		String str4 = "HellOJava";
		System.out.println("大写:"+str4.toUpperCase());//HELLOJAVA
		System.out.println("小写:"+str4.toLowerCase());//hellojava
		//11.去掉首尾空格:trim
		String str5 = "  haha xixi   ";
		System.out.println(str5.trim());//haha xixi
		//12.类型转换valueOf:将一些基本的数据类型转化为相应的字符串形式输出
		System.out.println(String.valueOf(123));//123
	}
}
常见应用场景

​ 字符串的处理常见应用和关联的方法可有如下:

  • 字符串比较(比较常见的业务场景,例如业务数据校验等,可借助equals相关)
  • 遍历字符串(charAt(int index):返回对应索引的值;length():返回字符串的长度)
  • 手机号屏蔽(字符串截取:通过substring方法截取指定范围的字符串,随后通过脱敏字符进行拼接)
  • 敏感词替换(字符串替换:通过replace、replaceAll等方法对特殊词语进行替换)
  • 字符串切割(针对一些特殊场景的输入数据,往往需要处理指定字符串联的字符串,则可通过split切割字符串,随后处理切割后的数组元素。但针对一些特殊字符拼接的内容则需要借助StringTokenizer进行处理)

练习1:判断邮箱是否合法

public class EmailCheckTest {
	public static void main(String[] args) {
		/**
		 *	练习1
		 *  判断邮箱是否合法     hello@163.com
		 *  1.必须有且只有一个@
		 *  2.@不能出现在头部或者尾部
		 *  3.@和.不能同时出现
		 *  4.@之后的内容不能超过15位
		 *  5.@之后必须有点
		 *  6.@之后是以 com  或者cn  或者net 或者org结尾
		 */
		String qq = "hello@qq.com";
		check(qq);
	}
	public static void check(String str)
	{
		//1.必须只有1个@:以@切割字符串,如果得到的字符串数组长度为2则满足条件
		String[] ss = str.split("@");
		if(ss.length!=2)
			System.out.println("邮箱必须有且只有一个@...");
		//2.@不能出现在头部或者是尾部
		else if(str.startsWith("@"))
			System.out.println("邮箱中的@不能出现在头部...");
		else if(str.endsWith("@"))
			System.out.println("邮箱中的@不能出现在尾部...");
		//3.@和.不能够同时出现
		else if(str.contains("@."))
			System.out.println("邮箱中@和.不能够同时出现...");
		//4.@之后的内容不能超过15位:直接判断ss[1]的长度是否大于15
		else if(ss[1].length()>15)
			System.out.println("@之后的内容不能够超过15位...");
		//5.@之后要以com、cn、net、org四者之一结尾
		else if(!(ss[1].endsWith("com")||ss[1].endsWith("cn")||ss[1].endsWith("net")||ss[1].endsWith("org")))
			System.out.println("@之后的内容要以com、cn、net、org四者之一结尾...");
		else
			System.out.println("邮箱验证通过:合法!!!");
		/**
		 * 如果一次性要展示所有的问题,则可全部设置成if语句,如此一来每个情况都可以分析判断得到
		 * 然后可通过flag进行标记(初始值位false),如果所有条件均满足,则可将flag置为true
		 * 并输出验证合法通过....
		 */
	}
}

练习2:字符串反转

public class StringReverse {
	/**
	 * 练习2:
	 * 将一个字符串 abcd  反转   dcba 
	 * 1.可以通过将字符串转化为字符数组的形式进行反转(反向遍历)
	 * 2.可以将字符串转化为相应的StrngBuffer、StringBuilder
	 *   然后通过其提供的reverse方法直接实现反转
	 */
	public static String f1(String str)
	{
		//1.将字符串数据转化为相应的字符数组进行操作
		char[] ch = str.toCharArray();
		//2.可以通过反向遍历数组实现反转
		char[] newCh = new char[ch.length];
		int j = 0;
		for(int i = ch.length-1;i>=0;i--)
		{
			newCh[j] = ch[i];
			j++;
		}
		String s = new String(newCh);
		return s;
		/**3.可以借助StringBuilder类直接用append方法追加数据
		 * StringBuilder sb = null;
		 * sb.append(ch[i]);
		 */
	}
	public static String f2(String str)
	{
		StringBuffer sb = new StringBuffer(str);
//		StringBuilder sb = new StringBuilder(str);
		return sb.reverse().toString();
	}
	public static void main(String[] args) {
		System.out.println("方法1实现反转:"+f1("abcd"));
		System.out.println("方法2实现反转:"+f2("abcd"));
	}
}

练习3:字符串字母排序

public class StringSort {
	/**
	 *	练习3:
	 *	对字符串中的字母 进行排序  :ysdfg   -->dfgsy
	 *	1.将字符串转化为字符数组利用相应的排序算法进行排序
	 *	2.将字符串转化为字符数组,借助Arrays工具类进行排序
	 */
	public static void f(String str)
	{
		/**
		 * 1.将字符串转化为相应的字符数据,通过两种方法进行排序
		 *   冒泡排序:bubber
		 *   选择排序:choose
		 */
		char[] ch = str.toCharArray();
		//调用冒泡排序方法
		System.out.println("冒泡排序:"+bubber(ch));
		//调用选择排序方法
		System.out.println("选择排序:"+choose(ch));
		/**
		 * 2.借助Arrays工具类的sort方法直接进行排序
		 */
		System.out.println("借助工具类Arrays:"+f2(ch));
	}
	public static String bubber(char[] ch)
	{
		for(int i=0;i<ch.length-1;i++)
			for(int j=0;j<ch.length-1-i;j++)
				if(ch[j]>ch[j+1])
				{
					char temp = ch[j];
					ch[j] = ch[j+1];
					ch[j+1] = temp;
				}
		String s = new String(ch);
		return s;
	}
	public static String choose(char[] ch)
	{
		for(int i=0;i<ch.length-1;i++)
			for(int j=i+1;j<ch.length;j++)
				if(ch[i]>ch[j])
				{
					char temp = ch[i];
					ch[i] = ch[j];
					ch[j] = temp;
				}
		String s = new String(ch);
		return s;
	}
	public static String f2(char[] ch)
	{
		Arrays.sort(ch);
		String s = new String(ch);
		return s;
	}
	public static void main(String[] args) {
		f("dcba");
	}
}

练习4:获取一个字符串在另外一个字符串中出现的次数

public class StringAppearCounter {
	/**
	 * 练习4: 
	 * 获取一个字符串在另外一个字符串中出现的次数
	 * “rtyui”  ---> “asdasrtyuiaegasrtyuiad rtyuizaf rtyui”  --->4
	 */
	public static int count(String str,String subStr)
	{
		int count = 0;
		/**
		 * 思路:
		 * 先查找到第1个subStr子串所在位置,然后将源字符串以第1个subStr子串位置+长度进行截断
		 * (即截断第1个子串及其之前的所有内容),并执行计数器加1操作。继续依次遍历,依次类推
		 * 如果查找到的最终字符串不包括subStr子串或者是最终子串与subStr相同,则退出遍历
		 */
		boolean flag = true;//定义flag用以控制遍历操作是否退出
		while(flag)
		{
			//1.查找第1个subStr子串出现的位置
			int location = str.indexOf(subStr);
			//2.截断subStr子串及之前的内容
			if(location!=-1)
			{
				count++;//查找成功,计数器加1,继续查找
				str = str.substring(location+subStr.length());
			}
			if(str.length()<subStr.length()||(!str.contains(subStr)))
				flag=false;
		}
		return count;
	}
	public static void main(String[] args) {
		System.out.println(count("abcdgogoabcdgoabcdgogoabcdgo","abcd"));
	}
}

练习5:获取两个字符串最大相同的子串

public class MaxSubString {
	/**
	 * 练习5: 
	 * 获取两个字符串最大相同的子串  
	 * “dsfhkoasdfhasdlfhasdf” ----->”ieqohrfoasdfhasdakwepghspghs”
	 * 输出 最大的相同的字符串 “oasdfhasd”
	 */
	public static String getMaxString(String s1, String s2) {  
		/**
		 * 思路:
		 * 先对比两个字符串的长度,如果max包括min则表示min为当前两个字符串的最大相同子串
		 * 以min的长度为准遍历查找
		 */
	    String max=(s1.length()>s2.length())?s1:s2;  
	    String min=(max.equals(s1))?s2:s1;  //取s1、s2中长度较长的为max,取较短的为min
	    if(max.contains(min)){   //如果max对应字符串包含min则表示两者的最大公共字串为min
	        return min;  
	    }  
	    for(int i=0;i<min.length();i++){   //进行min对应的字符串长度次循环匹配查找,  
	        for(int a=0,b=min.length()-i;b!=min.length()+1;a++,b++){
	        	/**
	        	 * 每次查找,都是从小字符串的起始位置查找,  记录查找的位置
	        	 * a,b代表的是每次查找时的从小字符串中截取的子串的起始位置和末尾位置+1
	        	 * 每次查找将小字符串min进行取子串,然后再将该子串与max匹配,
	        	 * 看看是max否包含该子串,直接返回当前子串
	        	 */
	            String sub=min.substring(a, b);  
	            if(max.contains(sub)){  
	                return sub;  
	            }  
	        }  
	    }  
	    return null;  
	}
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		System.out.println("请输入第一个任意字符串");
		String str1 = sc.nextLine();	
		System.out.println("请输入第二个任意字符串");
		String str2 = sc.nextLine();
		System.out.println("输入两个字符串的最大公共字串为:"+getMaxString(str1,str2));
	}
}
(3)StringBuffer和StringBuilder

​ StringBuffer对象代表的是一个可变字符序列,当一个StringBuffer对象创建出来以后,通过StringBuffer提供的append/insert/reverse/setCharAt等方法改变这个字符串对象的字符序列。一旦通过StringBuffer获得最终字符串,可以通过调用toString方法将字符StringBuffer对象转换为String对象。

​ 在jdk1.5以后,新增了一个StringBuilder类,它代表字符串对象,StringBulider和StringBuffer这两个类功能基本相似,用法基本相似,不同的是StringBuffer是线程安全的,StringBulider不是线程安全的。

public class StringBufferTest {
	public static void main(String[] args) {
		/**
		 * StringBuffer与StringBuilder用法基本类似,
		 * 可以用StringBuilder直接替换StringBuffer,
		 * 但StringBuffer是线程安全的,而StringBuilder
		 * 是线程不安全的
		 */
		/**
		 * 1.StringBuffer/StringBuilder常见的方法
		 *   append:添加(字符串末尾追加),可追加字符、字符数组、字符串、boolean类型的数据
		 *   delete:删除(删除制指定区域的内容)
		 *   deleteCharAt(int index):删除指定索引位置的字符
		 *   insert:插入数据
		 *   toString:得到String类型的数据
		 *   replace:替换制定区域的数据
		 *   reverse:翻转字符串数据
		 */
		StringBuffer sb = new StringBuffer("noob");
		//1.append:在末尾追加内容
		sb.append("hello").append(18).append(true);
		System.out.println(sb);//noobhello18true
		//2.得到StringBuffer的大小(容量)
		System.out.println(sb.capacity());
		//3.插入字符串数据(其他类型参考相应的API文档)
		StringBuffer sb1 = new StringBuffer("haha");
		//insert(2, "xixi"):在第2个索引位置插入字符串xixi
		sb1.insert(2, "xixi");//haxixiha
		//4.delete:删除数据(包括左侧的而不包括右侧的部分)
		sb1.delete(2, 5);//haiha
		//5.反转字符串
		StringBuffer sb2 = new StringBuffer("olleh");
		System.out.println(sb2.reverse());//hello
		//6.打印字符串长度、转化为相应的String类型
		System.out.println(sb2.length());//5
		System.out.println(sb2.toString());//hello
		/**
		 * 2.StringBuffer、StringBuilder与String类型之间的转化
		 * a.StringBuffer、StringBuilder-->String
		 *   利用toString方法
		 *   借助+(连接)符号,加上一个空的字符串
		 * b.String-->StringBuffer、StringBuilder
		 *   利用StringBuffer、StringBuilder各自的构造方法
		 */
		/**
		 * 分析StringBuffer的capacity
		 * 与StringBuffer底层的构造相关
		 * 其构造函数的初始化容量大小为16,一旦超出这个大小,会利用append
		 * 进行扩容,最终的容量为16+length(length为字符串数据的长度)
		 * 参考源代码实现简单理解
		 */
		StringBuffer sbt = new StringBuffer();
		System.out.println(sbt.capacity());//16
		StringBuffer sbt1 = new StringBuffer("a");
		System.out.println(sbt1.capacity());//17
	}
}
StringBuilder和String相互转换
  • StringBuilder转换为String

    ​ public String toString():通过 toString() 把 StringBuilder 转换为 String

  • String转换为StringBuilder

    ​ public StringBuilder(String s):通过构造方法把 String 转换为 StringBuilder

StringBuilder源码分析(AbstractStringBuilder)

value是一个数组

char[] value;

StringBuilder容量

public int capacity() {
	return value.length;
}

通过构造函数进行初始化

AbstractStringBuilder(int capacity) {
	value = new char[capacity];
}

StringBuilder类中构造函数初始化大小容量是16

public StringBuilder() {
	super(16);
}

append进行扩容

 public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }
 
 private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) {
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity));
        }
    }

 private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int newCapacity = (value.length << 1) + 2;
        if (newCapacity - minCapacity < 0) {
            newCapacity = minCapacity;
        }
        return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
            ? hugeCapacity(minCapacity)
            : newCapacity;
    }

    private int hugeCapacity(int minCapacity) {
        if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
            throw new OutOfMemoryError();
        }
        return (minCapacity > MAX_ARRAY_SIZE)
            ? minCapacity : MAX_ARRAY_SIZE;
    }
String、StringBuffer、StringBuilder的区别

​ String类与StringBuffer类、StringBuider类都是用于存储字符串类型的数据,但String类(不可变类)存储的是不可变的字符串序列(即一个String对象被创建之后,包含在这个字符串对象的字符序列不能够被改变,直到该对象被销毁、被垃圾回收都不会改变),而StringBuffer类、StringBuilder类存储的是可变的字符串序列,可通过该类对应提供的append/insert/reverse/setCharAt等方法改变对应字符串对象的字符序列。但StringBuffer是线程安全的,StringBuilder是线程不安全的

(4)StringTokenizer
基础应用
public class StringTokenizerTest {
	/**
	 * 对于特殊字符,无法直接通过String类型的split方法对指定字符串进行分隔,
	 * 由此可以通过StringTokenizer能够实现以指定特殊字符分割字符串,之后
	 * 还会介绍正则表达式相关的内容,借助正则表达式,实现指定格式的字符串分割
	 */
	public static void main(String[] args) {
		String str = "haha /*+%^&*@xixi/*+%^&*@gogo/*+%^&*@study/*+%^&*@lalala";
		String[] strs = str.split("/*+%^&*@");
		for(String s:strs)
			System.out.print(s+"-");
			/**
		 * 上述操作得到的结果是:
		 * haha /*+%^&*@xixi/*+%^&*@gogo/*+%^&*@study/*+%^&*@lalala-
		 * 显然通过String类型split方法无法直接以特殊的字符分割指定字符串
		 */
		//借助StringTokenizer实现
		//第1个参数是要进行分割的字符串,第2个参数是以什么指定字符进行分割
		StringTokenizer st = new StringTokenizer(str,"/*+%^&*@");
		//打印相关的数据信息
		while(st.hasMoreTokens())
			System.out.print(st.nextToken()+"-");
	}
}
五子棋的应用
public class Chess {
	/**
	 * 设计一个基于控制台的五子棋
	 */
	//1.定义棋盘格局10*10
	public static char[][] board = new char[10][10];
	//2.定义下棋标志(一人走一步)
	public static boolean flag = true;
	//3.初始化棋盘(用特殊符号)
	public static void init()
	{
		for(int i=0;i<board.length;i++)
			for(int j=0;j<board.length;j++)
			{
				board[i][j]='+';
			}
	}
	//4.获取用户输入,控制棋盘
	public static void play(int x,int y)
	{
		if(board[x][y]=='+')
		{
			if(flag)
			{
				board[x-1][y-1] = '★';
				System.out.println("白棋已走,转换选手...");
				flag = false;
			}
			else
			{
				board[x-1][y-1] = '●';
				System.out.println("黑棋已走,转换选手");
				flag = true;
			}
		}
		else
			System.out.println("不能重复覆盖棋子,请重新进行操作...");
	}
	//5.每次一步结束后,刷新棋盘
	public static void print()
	{
		for(int i=0;i<board.length;i++)
		{
			for(int j=0;j<board.length;j++)
				System.out.print(board[i][j]);
			System.out.println();
		}
	}
	public static void main(String[] args) {
		//测试基于控制台的五子棋
		init();
		print();
		System.out.println("请输入操作要放置的位置:(输入格式:x-y)");
		Scanner sc = new Scanner(System.in);
		while(sc.hasNext())
		{
			System.out.println("请输入操作要放置的位置:(输入格式:x-y)");
			String[] s = sc.next().split("-");
			int x = Integer.valueOf(s[0]);
			int y = Integer.valueOf(s[1]);
			play(x,y);
			print();
		}
	}
}

🔖与数学数字相关的常用类

(1)Math类

​ Math 包含执行基本数字运算的方法,可通过Math.方法名直接进行调用,常用方法说明如下

方法名说明
public static int abs(int a)返回参数的绝对值
public static double ceil(double a)返回大于或等于参数的最小double值,等于一个整数
public static double floor(double a)返回小于或等于参数的最大double值,等于一个整数
public static int round(float a)按照四舍五入返回最接近参数的int
public static int max(int a,int b)返回两个int值中的较大值
public static int min(int a,int b)返回两个int值中的较小值
public static double pow (double a,double b)返回a的b次幂的值
public static double random()返回值为double的正值,[0.0,1.0)

参考案例:

public class MathTest {
	public static void main(String[] args) {
		int a = -10,b = 30;
		double c = 3.14,d = 3.93;
		/**
		 * 以下一一展示与Math类相关的常用的方法
		 * a.abs:获取某个数的绝对值
		 * b.floor:返回最大的(最接近正无穷大)的double值(小于等于参数,且为整数)
		 * c.round:返回最接近参数的int值
		 * d.求两者之间的最大值和最小值(max、min)
		 * e.常见的三角函数
		 * f.求随机数Math.random()(0-1),可以扩展到0-100
		 */
		//a.求绝对值
		System.out.println(a+"的绝对值为"+Math.abs(a));
		//b.floor:得到最大的(小于等于参数)的整数的double值
		System.out.println(Math.floor(c));//3.0
		System.out.println(Math.floor(d));//3.0
		//c.round:得到最接近参数的int值
		System.out.println(Math.round(c));//3
		System.out.println(Math.round(d));//4
		//d.得到两者之间的最大值和最小值(max、min)
		System.out.println(c+"与"+d+"之间的最大值为:"+Math.max(c, d));
		System.out.println(c+"与"+d+"之间的最小值为:"+Math.min(c, d));
		//e.常见的其它方法(三角函数等)
		System.out.println(Math.sin(Math.toRadians(30)));
		//g.得到0-100的随机数(如果需要整数,则需要进行强制类型转换)
		System.out.println("输出一个0-100的随机数:"+Math.random()*100);
	}
}
(2)Random类
public class RandomTest {
	public static void main(String[] args) {
		/**
		 * 获取随机数:创建Random对象进行操作
		 * Random r = new Random();
		 * 可以根据Random对象调用相应的方法分别取出不同数据类型的随机数
		 */
		Random r = new Random();
		System.out.println("取出一个Boolean类型的随机数:"+r.nextBoolean());
		System.out.println("取出一个int类型(1-100之间的整数)的随机数"+r.nextInt(100));
//		Random r1 =new Random(50);
		Random r1 =new Random(System.currentTimeMillis());
		System.out.println(r1.nextBoolean());
		System.out.println(r1.nextDouble());
		System.out.println(r1.nextInt());
		System.out.println("-------------分隔符----------------");
//		Random r2 =new Random(50);
		Random r2 =new Random(System.currentTimeMillis());
		System.out.println(r2.nextBoolean());
		System.out.println(r2.nextDouble());
		System.out.println(r2.nextInt());
		/**
		 * 如果在创建Random对象的时候指定的时间是相同的则得到的随机数也相应的相同,
		 * 如此以来便丧失了随机数的意义,因此在实际开发中优先考虑用系统时间创建相应的
		 * Random对象,从而避免了连续取得的随机数相同的情况
		 */
	}
}
(3)大数运算与精确运算(BigDecimal与BigInteger)
BigDecimal和BigInteger的运用

构造方法

方法名说明
BigDecimal(double val)参数为double
BigDecimal(String val)参数为String

常用方法

方法名说明
public BigDecimal add(另一个BigDecimal对象)加法
public BigDecimal subtract (另一个BigDecimal对象)减法
public BigDecimal multiply (另一个BigDecimal对象)乘法
public BigDecimal divide (另一个BigDecimal对象)除法
public BigDecimal divide (另一个BigDecimal对象,精确几位,舍入模式)除法

如果对double类型的数据进行精确运算,则需要将其转化为BigDecimal类型

import java.math.BigDecimal;
public class BigDecimalTest1 {
	public static void main(String[] args) {
		//如果直接输出小数之间进行加减乘除运算的结果,可以看到输出结果有时候并不如我们所愿,这是由于不同的数据类型对数据的存储有所限制
		/**System.out.println(0.05+0.01);   //0.060000000000000005
		 * System.out.println(1.0-0.441);   //0.5589999999999999 
		 * System.out.println(233.56/100);  //2.3356
		 * System.out.println(3.24*100);    //324.0
		 */
	/**
* BigDecimal提供不同的构造方法从而实现不同数据的转化
	 * 亦提供了valueOf方法实现将long类型、double类型的数据转化为BigDecimal类型
	 * 还提供了许多方法实现基本的数学运算(具体运算参考相应的api文档 
* java.math.BigDecimal)
	 */
		//此处简单实现两个double类型的数据进行精确运算
		BigDecimal b1 = BigDecimal.valueOf(0.05);
		BigDecimal b2 = BigDecimal.valueOf(0.01);
		BigDecimal sum = b1.add(b2);
		BigDecimal sub = b1.subtract(b2);
		BigDecimal mul = b1.multiply(b2);
		BigDecimal div = b1.divide(b1);
		System.out.println("和:"+sum);
		System.out.println("差:"+sub);
		System.out.println("乘积:"+mul);
		System.out.println("商:"+div);
	}
}

设计一个大数的计算器

import java.math.BigDecimal;
import java.math.BigInteger;
public class BigDecimalTest2 {
	public static void main(String[] args) {
		//设计一个用于计算大数的计算器
		//1、利用String类型存储要进行计算的数据
		String str1 = "1111111111111111111111111111111.333";
		String str2 = "2222222222222222222222222222222";
		//2、将String类型的数据转化为相应的BigDecimal类型
		//此处转化无法直接通过valueOf方法进行转化,因而借助BigDecimal的构造方法实现类型转换
		/*
		BigDecimal b1 = new BigDecimal(str1);
		BigDecimal b2 = new BigDecimal(str2);
		BigDecimal sum = b1.add(b2);
		BigDecimal sub = b1.subtract(b2);
		BigDecimal mul = b1.multiply(b2);
		BigDecimal div = b1.divide(b2);
		System.out.println("和:"+sum);
		System.out.println("差:"+sub);
		System.out.println("乘积:"+mul);
		System.out.println("商:"+div);
		*/
		//BigInteger类型的使用和BigDecimal大同小异,但BigInteger的存储的数据类型需为整型,否则会抛出 java.lang.NumberFormatException异常信息
		BigInteger b1 = new BigInteger(str1);
		BigInteger b2 = new BigInteger(str2);
		BigInteger sum = b1.add(b2);
		BigInteger sub = b1.subtract(b2);
		BigInteger mul = b1.multiply(b2);
		BigInteger div = b1.divide(b2);
		System.out.println("和:"+sum);
		System.out.println("差:"+sub);
		System.out.println("乘积:"+mul);
		System.out.println("商:"+div);
	}
}
简易计算器
import java.math.BigDecimal;
public class Calculator {
	//设计简易计算器,默认计算精度
	private static final int DEF_DIV_SCSLE = 10;
	public static double add(double a,double b)
	{
		BigDecimal a1 = BigDecimal.valueOf(a);
		BigDecimal b1 = BigDecimal.valueOf(b);
		return a1.add(b1).doubleValue();   //将BigDecimal类型转化为Double类型
	}
	public static double sub(double a,double b)
	{
		BigDecimal a1 = BigDecimal.valueOf(a);
		BigDecimal b1 = BigDecimal.valueOf(b);
		return a1.subtract(b1).doubleValue();
	}
	public static double mul(double a,double b)
	{
		BigDecimal a1 = BigDecimal.valueOf(a);
		BigDecimal b1 = BigDecimal.valueOf(b);
		return a1.multiply(b1).doubleValue();
	}
	public static double div(double a,double b)
	{
		BigDecimal a1 = BigDecimal.valueOf(a);
		BigDecimal b1 = BigDecimal.valueOf(b);
		//第一个参数是被除数   第二个参数是保留的位数  第三个参数是进位
		return a1.divide(b1, DEF_DIV_SCSLE, BigDecimal.ROUND_HALF_UP).doubleValue();
	}
	public static void main(String[] args) {
		System.out.println("0.05+0.03的值为"+Calculator.add(0.05, 0.03));
	}
}

🔖与日期相关的常用类

(1)Date类
import java.util.Date;
public class DateTest {
	public static void main(String[] args) {
		//创建date对象,打印当前系统时间
		Date d = new Date();
		System.out.println(d);
		//得到当前时间之后1000ms的时间(延迟1000ms)
		Date d1 = new Date(System.currentTimeMillis()+1000);
		System.out.println(d1);
		//比较d和d1对象
		System.out.println(d.before(d1));   //true
		System.out.println(d1.after(d));   //true
		System.out.println(d.compareTo(d1));   //-1
		//获取某个日期相应的年、月、日
		//由于Date版本的某些方法失效,对于获取年份要在其基础上加上1900,获取月份则要在其基础上加上1
		//其余获取日期、星期则保持不变
		System.out.println("年:"+d.getYear()+1900);
		System.out.println("月:"+d.getMonth()+1);
		System.out.println("日:"+d.getDate());
		System.out.println("星期"+d.getDay());
	}
}
(2)Calendar类

案例1:

import java.util.Calendar;
import static java.util.Calendar.*;
public class CalendarTest {
	public static void main(String[] args) {
		//获取Calendar的一个实例
		Calendar cal = Calendar.getInstance();   
		System.out.println(cal);
		//依次直接得到相应的年月日、星期
		//此处要导包“import static java.util.Calendar.*;”,将相应的静态变量导入
		//月份要执行加1操作,其余内容不作改变
		System.out.println("年:"+cal.get(YEAR));
		System.out.println("月:"+(cal.get(MONTH)+1));
		System.out.println("日:"+cal.get(DATE));
	
		//输出其它相关的信息
		//设置一个指定的日期
		cal.set(2017, 10, 18, 14, 22, 23);   //设置的时间为2017-11-18 14:22:23
		System.out.println(cal.getTime());
		
		//得到下述信息:今天是这周的第几天  今天是这一月的第几天 今天是这一年的第几天
		System.out.println(cal.get(DAY_OF_WEEK));
		System.out.println(cal.get(DAY_OF_MONTH));
		System.out.println(cal.get(DAY_OF_YEAR));
		
		//计算某人出生那天是周几 
		 cal.set(1967, 2, 11);
		 //这周的第几天与这周的星期几有所区别,外国人的星期是从周日开始计算,周日为一周的第一天
		 System.out.println("这个同学出生那天是周:"+(cal.get(DAY_OF_WEEK)-1));
	}
}

案例2:

import java.util.Calendar;
public class CalendarTest2 {
	public static void main(String[] args) {
		Calendar cal = Calendar.getInstance();
		//另一种方式:设置指定日期
		cal.set(Calendar.YEAR, 1998);
		cal.set(Calendar.MONTH, 02);
		cal.set(Calendar.DATE, 11);
		System.out.println(cal.getTime());
		//计算到目前为止活了几天(当前毫秒数减去出生日期的毫秒数,再转化为相应的天数)
		System.out.println((System.currentTimeMillis()-cal.getTimeInMillis())/1000/60/60/24);
	}
}

分析题:下述代码输出结果?

public static void main(String[] args) {
		Calendar cal = Calendar.getInstance();
		cal.set(Calendar.YEAR, 2016);
		cal.set(Calendar.MONTH, 1);
		cal.set(Calendar.DATE, 32);
		System.out.println(cal.getTime());
}

// 结果分析说明
	2016年为闰年,此处的月份本应为1+1=2月,但由于此时日期为32,则有闰年的2月份有29天,则说明32-29=3,正确解析应为201633
(3)位符的格式化

SimpleDateFormat格式

import java.text.SimpleDateFormat;
import java.util.Date;
public class SimpleDateFormatTest {
	public static void main(String[] args) {
		Date d= new Date();
		//格式 其他直接输出 
		//G 公元       yyyy年        D指定日期是这一年的第几天
		SimpleDateFormat sdf =new SimpleDateFormat("Gyyyy年中的第D天");
		String str=sdf.format(d);
		System.out.println(str);			
		SimpleDateFormat sdf2 =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		System.out.println(sdf2.format(d));
	}
}

NumberFormat格式

import