第一章 Java概述
什么是Java
Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念,因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程 。
三大版本
Java SE(J2SE,Java 2 Platform Standard Edition,标准版)
Java SE 以前称为 J2SE。**它允许开发和部署在桌面、服务器、嵌入式环境和实时环境中使用的 Java 应用程序。**Java SE 包含了支持 Java Web 服务开发的类,并为Java EE和Java ME提供基础。
Java EE(J2EE,Java 2 Platform Enterprise Edition,企业版)
Java EE 以前称为 J2EE。**企业版本帮助开发和部署可移植、健壮、可伸缩且安全的服务器端Java 应用程序。**Java EE 是在 Java SE 的基础上构建的,它提供 Web 服务、组件模型、管理和通信 API,可以用来实现企业级的面向服务体系结构(service-oriented architecture,SOA)和 Web2.0应用程序。2018年2月,Eclipse 宣布正式将 JavaEE 更名为 JakartaEE
Java ME(J2ME,Java 2 Platform Micro Edition,微型版)
Java ME 以前称为 J2ME。**Java ME 为在移动设备和嵌入式设备(比如手机、PDA、电视机顶盒和打印机)上运行的应用程序提供一个健壮且灵活的环境。**Java ME 包括灵活的用户界面、健壮的安全模型、许多内置的网络协议以及对可以动态下载的连网和离线应用程序的丰富支持。基于 Java ME 规范的应用程序只需编写一次,就可以用于许多设备,而且可以利用每个设备的本机功能。
程序语言的类型
1.编译型语言和解释型语言
编译型语言
定义:在程序运行之前,通过编译器将源程序编译成机器码(可运行的二进制代码),以后执行这个程序时,就不用再进行编译了。
优点:编译器一般会有预编译的过程对代码进行优化。因为编译只做一次,运行时不需要编译,所以编译型语言的程序执行效率高,可以脱离语言环境独立运行。
缺点:编译之后如果需要修改就需要整个模块重新编译。编译的时候根据对应的运行环境生成机器码,不同的操作系统之间移植就会有问题,需要根据运行的操作系统环境编译不同的可执行文件。
总结:执行速度快、效率高;依靠编译器、跨平台性差些。
代表语言:C、C++、Pascal、Object-C以及Swift。
解释型语言
定义:解释型语言的源代码不是直接翻译成机器码,而是先翻译成中间代码,再由解释器对中间代码进行解释运行。在运行的时候才将源程序翻译成机器码,翻译一句,然后执行一句,直至结束。
优点:有良好的平台兼容性,在任何环境中都可以运行,前提是安装了解释器(虚拟机)。灵活,修改代码的时候直接修改就可以,可以快速部署,不用停机维护。
缺点:每次运行的时候都要解释一遍,性能上不如编译型语言。
总结:执行速度慢、效率低;依靠解释器、跨平台性好。
代表语言:JavaScript、Python、Erlang、PHP、Perl、Ruby。
混合型语言
定义:既然编译型和解释型各有缺点就会有人想到把两种类型整合起来,取其精华去其糟粕,就出现了半编译,半解释型语言。
比如C#,C#在编译的时候不是直接编译成机器码而是中间码,.NET平台提供了中间语言运行库运行中间码,中间语言运行库类似于Java虚拟机。.NET在编译成IL代码后,保存在dll中,首次运行时由JIT在编译成机器码缓存在内存中,下次直接执行。严格来说混合型语言属于解释型语言,C#更接近编译型语言。
Java即是编译型的,也是解释型语言,总的来说Java更接近解释型语言。
可以说它是编译型的。因为所有的Java代码都是要编译的,.java不经过编译就什么用都没有。同时围绕JVM的效率问题,会涉及一些如JIT、AOT等优化技术,例如JIT技术,会将热点代码编译成机器码。而AOT技术,是在运行前,通过工具直接将字节码转换为机器码。
可以说它是解释型的。因为Java代码编译后不能直接运行,它是解释运行在JVM上的,所以它是解释运行的。
2.动态类型语言和静态类型语言
动态类型语言
动态类型语言:在运行期间才去做数据类型检查的语言,说的是数据类型。动态类型语言的数据类型不是在编译阶段决定的,而是把类型绑定延后到了运行阶段。
代表语言:Python、Ruby、Erlang、JavaScript、Swift、PHP、Perl。
静态类型语言
静态类型语言的数据类型是在编译期间(或运行之前)确定的,编写代码的时候要明确确定变量的数据类型。
代表语言:C、C++、C#、Java、Object-C。
3.动态语言和静态语言
动态语言
动态类型语言和动态语言是完全不同的两个概念。
动态语言:说的是运行时改变结构,说的是代码结构。在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。
代表语言:Object-C、C#、JavaScript、PHP、Python、Erlang。
静态语言
与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、C++。
JVM、JRE和JDK的关系
JVM
Java Virtual Machine是Java虚拟机,Java程序需要运行在虚拟机上,不同的平台有自己的虚拟机,因此Java语言可以实现跨平台。
JRE
Java Runtime Environment包括Java虚拟机和Java程序所需的核心类库等。核心类库主要是java.lang包:包含了运行Java程序必不可少的系统类,如基本数据类型、基本数学函数、字符串处理、线程、异常处理类等,系统缺省加载这个包
如果想要运行一个开发好的Java程序,计算机中只需要安装JRE即可。
JDK
Java Development Kit是提供给Java开发人员使用的,其中包含了Java的开发工具,也包括了JRE。所以安装了JDK,就无需再单独安装JRE了。其中的开发工具:**编译工具(javac.exe),打包工具(jar.exe)**等
JVM、JRE和JDK的关系图
Java的特点
-
Java是面向对象的
-
Java是健壮的。
java的强类型机制、异常处理、垃圾的自动收集是java程序健壮性的重要保证。
-
Java语言是跨平台性的。
一个编译好的.class文件可以在多系统下运行——通过不同系统中的不同版本(linux window mac)JVM来执行
-
Java语言是解释性的。
解释型是不能被机器直接执行,需要解释器来执行,编译型语言被编译器编译后的代码,可以被机器直接执行。
环境变量的作用
环境变量:1.系统环境变量:对所有用户起作用
2.用户环境变量:只对当前用户起作用
%Systemroot% 系统总目录位置——环境变量
%JAVA_HOME%bin中百分号%%是引用的意思
主要作用:指明操作系统的重要目录
运行窗口输入cmd(System32):系统会尝试在Path指明的目录中查找cmd这个程序,找到就运行,否则提示找不到
Path:是操作系统用的,用来指定操作系统需要使用到的可执行程序的位置,可以任意处运行
JAVA_HOME:`我们可以借用已经设定过的JAVA_HOME,将PATH的内容修改为:…;%JAVA_HOME%/bin。——相对位置
JavaAPI文档
- API是Java提供的基本编程接口
- Java语言提供大量的基础类,为这些基础类提供相应的API文档
- Java类的组织形式
还有枚举在包下
包要进行导入 import 关键字
第一个Java程序
Java开发注意事项
-
Java程序的执行入口为mian()方法
固定书写格式
public class void main(String[], args){....}
-
Java严格区分大小写
-
语法构造以**;(英文)**结束
-
一个源文件中最多只能有一个public类
-
如果源文件中包含一个public类,则文吗,件名比选该类类名命名
-
(可有多个mian程序)可以将mian()方法写在非public类中,然后指定运行非public类(需要指定运行)——非public的mian方法
Java编码规范
- 类、方法的注释要用javadoc的方法来书写
- 非javadoc的注释是用来给代码维护者看的
- 源文件使用utf-8编码
- 行宽不要超过80字符
- 代码编写风格
public class demo{
}
class demo22
{
}
Hello,Java!
//表示Hello是一个公有类
public class Hello{
//编写一个mian方法
//主方法,既程序的入口
public class void main(String[], args){
//调用函数,输出并换行
System.out.println("Hello,Java!");
}
}
Java执行流程分析
- 通过编译将.java文件编译成JVM可以识别的字节码文件。————>编译
- 将.class文件装载到JVM中执行————>运行
- 修改程序后要重新进行编译,进而修改.class文件,否则程序将无改变。
Java标识符
定义
给包,类,方法,变量起名字的符号。
组成规则
标识符由字母、数字、下划线、美元符号组成。
命名原则:见名知意
包名:全部小写,多级包用.隔开。
举例:com.jourwon
类、接口:一个单词首字母大写,多个单词每个单词的首字母大写。
举例:Student,Car,HelloWorld
方法和变量:一个单词首字母小写,多个单词从第二个单词开始每个单词的首字母大写。
举例:age,maxAge,show(),getAge()
常量:如果是一个单词,所有字母大写,如果是多个单词,所有的单词大写,用下划线区分每个单词。
举例:DATE,MAX_AGE
项目名:全部用小写字母,多个单词之间用横杆-分割。
举例:demo,spring-boot
注意事项
- 不能以数字开头
- 不能是Java中的关键字
- Java标识符大小写敏感,长度无限制
- 标识符不能包含空格
Java关键字
Java 8版本
Java 8 以后版本
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IcNM7OwQ-1666831726773)(Java基础.assets/image-20220924201440640.png)]
Java注释
定义
用于解释说明程序的文字
分类
-
单行注释
格式: // 注释文字 -
多行注释
格式: /* 注释文字 */ -
文档注释
格式:/** 注释文字 */
作用
在程序中,尤其是复杂的程序中,适当地加入注释可以增加程序的可读性,有利于程序的修改、调试和交流。注释的内容在程序编译的时候会被忽视,不会产生目标代码,注释的部分不会对程序的执行结果产生任何影响。
注意事项
多行和文档注释都不能嵌套使用。
Java转义字符
定义
转义字符是一种特殊的字符常量。转义字符以反斜线"\"开头,后跟一个或几个字符。转义字符具有特定的含义,不同于字符原有的意义,故称“转义”字符。
常见转义字符表
Java中需要转义的字符
在Java中,不管是String.split(),还是正则表达式,有一些特殊字符需要转义,这些字符是
( [ { / ^ - $ ¦ } ] ) ? * + .
转义方法为字符前面加上"",这样在split、replaceAll时就不会报错。不过要注意,String.contains()方法不需要转义。
Java的输入
Java中输入一般是通过Scanner类来实现的
使用该方法前要先调用Scanner包
import java.util.Scanner;
然后要通过创建输入的对象,然后才能使用输入方法;
创建input输入对象;
Scanner input=new Scanner(System.in);
创建对象后可以使用输入的方法,对应不同的数据类型应使用不同的输入方法;
//String类型
String str = input.next() ;
//或
String str = input.nextLine() ;
//int类型
int sum = input.nextInt() ;
//float类型
float tem = input.nextFloat() ;
//double类型
double d = input.nextDouble() ;
不同输入类型对应不同的方法,在使用中需注意其不同的对应方法。
input.next();和input.nextLine();的区别:
input.next();的含义为接收到“ ”空白字符前的一个字符。
input.nextLine();的含义为接收到“\n”换行字符前的一个字符。
此处nextLine在输入时可根据不同需求使用不同输入,但在我们使用过程中可以多采用nextLine(),因为如果全部采用nextLine()可以减少输入时产生的冲突,如果我们在nextLine()前使用了其他的输入,默认会将上一单位的输入自动输入至nextLine()中,所以如果我们全局都采用nextLine()就会减少这类冲突。
使用String类来接收数据的好处:
在String类中我们可以输入各种类型的数据而不会产生错误,在具体的项目中,通过包装类的方法可以对数据进行转换,配合nextLine方法的使用可以更好的接收各种类型的数据。
注意: 可以使用close()来关闭对象。建议一旦输入,就关闭scanner对象。
-
Java中输入一般是通过Scanner类来实现的
-
使用该方法前要先调用Scanner包
第二章 变量
变量介绍
定义
从本质上讲,变量其实是内存中的一小块区域
Java中基本数据类型是值传递
简而言之,将一个变量a赋值给另一个变量b,是将这个变量a的值,拷贝一份给变量b(如果a是引用类型,就拷贝引用,如果是基本类型,就拷贝原始值)
int a = 1; //把1这个值赋给a
//a会指向一个内存地址,这个地址里面存折一个整型变量1
int b;
b = 6;
变量相当于内存中一个数据存储空间的表示,变量名是门牌号,通过门牌号可以找到房间位置。
变量的注意事项
- 变量表示内存中的一个储存区域(不同类型的变量,占用的空间大小不变)
- 变量要有名称和类型
- 变量必须先声明后使用
- 变量在同一个作用域内不可以重名
- 拥有变量三要素才可以进行使用:变量 = 数据类型 + 变量名 + 值
“+”号的使用
- 当左右两边都是数值的时候,做加法运算
- 当左右两边有一方是字符串,做拼接运算
- 运算顺序从左到右
public class DEMO{
public static void main(String args){
System.out.println(100 + 100); //200
System.out.printlnn("100" + 100); //100100
System.out.println("100" + 100 + 100); //100100100
System.out.println(100 + 100 + "100"); //200100
}
}
数据类型
定义
Java语言是强类型语言,对于每一种数据都定义了明确的具体的数据类型,在内存中分配了不同大小的内存空间。
分类
- 基本数据类型
- 数值型
- 整数类型(byte,short,int,long)
- 浮点类型(float,double)
- 字符型(char)
- 布尔型(boolean)
- 数值型
- 引用数据类型
- 类(class)
- 接口(interface)
- 数组([])
字符串的比较
String name = Tom;
System.out.println(name.equals("Tom"));
System.out.println("Tom".equals(name)); //good 可以避免空指针
String类型是属于引用数据类型中的类
整型类型
//整形类型
byte a = 10; //一个字节 -128~127 = 8 bit
short b = 10; //两个字节
int c = 10; //四个字节
long d = 10; //八个字节
//Java中的整型(值),默认为int型,声明为long型常量在后面加L或l
//字节是基本单位
浮点数
//浮点数在机器中存放形式:符号位+指数位+尾数位
//尾数部分可能丢失,造成精度损失(小鼠都是近似值)
//Java中的整型(值),默认为double,声明float型常量在后面加f或F
float a = 1.0f; //4byte
float a = 1.0;
//等价
double b = 0.99; //8byte
double b = .99;
//科学计数法
b = 0.99;
b = 9.9e-1;
//小数陷阱
//计算机是二进制的,它没办法精确的标识8.1,所以计算结果也是近似值
double i = 8.1/3; //结果为2.6999999999999997
double j = 2.7;
System.out.println(i);
//对运算结果小数进行判断时,要小心是否有陷阱
//应该以两个数的差值的绝对值,在摩格精度范围内进行判断
if(Math.abs(i - j) < 1e-10){
System.out.println("可以忽略!");
}
对运算结果小数进行判断时,要小心是否有陷阱
当对运算结果是小数的进行相等判断的时候,要小心
要以两个数的差值的绝对值,在某个精度范围内进行判断
字符型
//字符型
//字符类型可以用来表示单个字符,char是两个字节(可以存汉字),多个字符String
char a = 'a';
char b = '\n';
char c = '好';
char d = 128; //字符类型可以存放数字,输出时是按字符输出,unicode(兼容ASCII码)编码格式
char s = '9'; // 输出数字9
//在java中,字符本质是一个数字,输出时,会按照unicode字符进行输出
//(数据类型)强制类型转换
//char本质是一个数字,可以进行运算
System.out.println('a' + 3);
//存储 a-->97-->1100001
//读取1100001-->97-->a
本质是一个数字,可以进行运算
布尔型
//布尔类型boolean 1 byte
//只允许获取true和false 无null
//用于逻辑运算
boolean pass = tuew;
if(pass){
System.out.println("pass");
}else{
System.out.println("not pass");
}
//Java中boolean类型不可以用0或者非0的整数代替false或者ture
Java中boolean类型不可以用0或者非0的整数代替false或者ture
编码
ASCII 码
Unicode 码
UTF-8 码
数据类型转换
定义
数据类型的转换是在所赋值的数值类型和被变量接收的数据类型不一致时发生的,它需要从一种数据类型转换成另一种数据类型。
分类
1. 隐式转换
定义
在运算过程中,由于不同的数据类型会转换成同一种数据类型,所以整型、浮点型以及字符型都可以参与混合运算。自动转换的规则是从低级类型数据转换成高级类型数据。
转换规则
数值型数据的转换:byte→short→int→long→float→double。
字符型转换为整型:char→int。
转换条件
自动类型转换的实现需要同时满足两个条件:两种数据类型彼此兼容,目标类型的取值范围大于源数据类型(低级类型数据转换成高级类型数据)。例如 byte 类型向 short 类型转换时,由于 short 类型的取值范围较大,会自动将 byte 转换为 short 类型。
2. 显式转换
定义
当两种数据类型不兼容,或目标类型的取值范围小于源类型时,自动转换将无法进行,这时就需要进行强制类型转换。丢失精度
语法格式
目标类型 变量名 = (目标类型) (被转换的数据);
举例:int b = (byte)(a + b);
注意
如果超出了被赋值的数据类型的取值范围得到的结果会与你期望的结果不同
不建议强制转换,因为会有精度的损失。
基本数据类型与String转换
- 基本数据类型转换位String类型
将基本数据类型加""
int n = 100;
String m = n + "";
-
String转化为基本数据类型
通过基本数据类型==包装类调用parseXX方法==
Integer.parseInt("123");
Double.parseDouble("123.123");
Float.parseFloat("123.123");
Long.parseLong("1234567");
Short.parseshort("123");
Byte.parseByte("21");
Boolean.parseBoolean("ture");
parseInt() 方法用于将字符串参数作为有符号的十进制整数进行解析。
如果方法有两个参数, 使用第二个参数指定的基数,将字符串参数解析为有符号的整数。
字符串转成字符 String----char string.charAt(number);
String str = "1234567890";
char a = str.charAt(2); //得到字符串s中的第三个字符
//得到一个字符
1. <font color = blur>将String类型转换位基本数据类型时,要确保String可以被转换成有效数据。</font>
2. 如果格式不正确,就会抛出异常,程序就h会终止。
String s = "hello!";
int a;
a = (int)s; //Exception异常
第三章 运算符
运算符指明对操作数的运算方式。
算术运算符
- + - * / % ++ – +
注意事项
1. / 左右两端的类型需要一致;
1. %最后的符号和被模数相同;
1. 前++;先+1,后运算 后++;先运算,后+1;
1. +:当String字符串与其他数据类型只能做连接运算;并且结果为String类型;
比较运算符(关系运算符)
*= += -= = /= %=
注意事项
- 比较运算符的两端都是boolean类型,也就是说要么是true,要么是false;
- 比较运算符的"==“与”="的作用是不同的,使用的时候需要小心。
赋值运算符
将某个运算后的值,赋给指定变量
会进行类型转换
基本赋值运算符:=
复合赋值运算符:+= -= *= /= %=
*= += -= = /= %=
a+=b; //a=a+b
逻辑运算符
符号的两端都是boolean类型
& | ^ ! && ||
注意事项
& 与 &&以及|与||的区别:
&:左边无论真假,右边都会进行运算;
&&:如果左边为假,则右边不进行运算;
| 与 || 的区别同上;在使用的时候建议使用&&和||;
(^)与或(|)的不同之处是:当左右都为true时,结果为false。
短路与&&和短路或||效率高(用位运算计算也效率高)
位运算符
效率高!两端都是数值型的数据
&、|、~、^、>>、<<、>>>
按位与&:两位全为一则结果为一
按位或|:两位至少有一个为1,结果为1,否则为0
按位异或^:两位分别为1和0,结果为1,否则为0
按位取反~:1变为0,0变为1
//运算
2&3
/* 2的原码0000 0000 0000 0010
2的反码0000 0000 0000 0010
2的补码0000 0000 0000 0010
3的原码反码补码0000 0000 0000 0011
2&3 = 2
*/
~-2
/*-2的原码1000 0000 0000 0010
反码1111 1111 1111 1101
补码1111 1111 1111 1110
~-2补码0000 0000 0000 0001
~-2 = 1
*/
-2+(-2)//计算出补码,在进行运算
/*1111 1111 1111 1110
+1111 1111 1111 1110
=1111 1111 1111 1100
反=1111 1111 1111 1011
原=1000 0000 0000 0100 = -4
反码 = 原码初第一位按位取反 = 补码 - 1
补码 = 反码 + 1
*/
~2;
/*0000 0000 0000 0010
` 1111 1111 1111 1101 补码
1111 1111 1111 1100 反码
1000 0000 0000 0011 原码 -3
~2 = -3
*/
算术符右移 >>:低位溢出,符号位不变,用符号位补溢出的高位 /2
算术符左移<<:符号位不变,低位补0 *2
逻辑右移(无符号右移):低位溢出,高位补0
System.out.println(1<<2); //4
System.out.println(4>>3); //0
三元运算符
注意事项
- 表达式1与表达式2的类型必须一致;
- 使用三元运算符的地方一定可以使用if…else代替,反之不一定成立;
表达式要为可以赋给变量的类型
运算符的优先级
优先级按照从高到低的顺序书写,也就是优先级为1的优先级最高,优先级14的优先级最低。使用优先级为 1 的小括号可以改变其他运算符的优先级。
第四章 程序控制结构
流程是指程序运行时,各语句的执行顺序。流程控制语句就是用来控制程序中各语句执行的顺序。
顺序结构
顺序结构是程序中最简单最基本的流程控制,没有特定的语法结构,按照代码的先后顺序,依次执行,程序中大多数的代码都是这样执行的
/*
* 顺序结构:从上往下,依次执行
*/
public class OrderDemo {
public static void main(String[] args) {
System.out.println("开始");
System.out.println("语句A");
System.out.println("语句B");
System.out.println("语句C");
System.out.println("结束");
}
}
分支结构(选择结构)
条件语句可根据不同的条件执行不同的语句。包括if条件语句与switch多分支语句。
分类
if分支结构
第一种格式
public class IfDemo {
public static void main(String[] args) {
System.out.println("开始");
// 定义两个变量
int a = 10;
int b = 20;
if (a == b) {
System.out.println("a等于b");
}
int c = 10;
if (a == c) {
System.out.println("a等于c");
}
System.out.println("结束");
}
}
第二种格式
public class IfDemo2 {
public static void main(String[] args) {
System.out.println("开始");
// 判断给定的数据是奇数还是偶数
// 定义变量
int a = 100;
// 给a重新赋值
a = 99;
if (a % 2 == 0) {
System.out.println("a是偶数");
} else {
System.out.println("a是奇数");
}
System.out.println("结束");
}
}
第三种格式
public class IfDemo3 {
public static void main(String[] args) {
// x和y的关系满足如下:
// x>=3 y = 2x + 1;
// -1<=x<3 y = 2x;
// x<=-1 y = 2x – 1;
// 根据给定的x的值,计算出y的值并输出。
// 定义变量
int x = 5;
int y = 0;
if (x >= 3) {
y = 2 * x + 1;
} else if (x >= -1 && x < 3) {
y = 2 * x;
} else if (x <= -1) {
y = 2 * x - 1;
}
System.out.println("y的值是:"+y);
}
}
注意事项
- 一旦满足某个条件表达式,则进入其执行语句块执行,执行完毕后不会执行其一下的条件语句。
- 如果多个条件表达式之间为“互斥”关系,多个语句之间可以上下调换顺序,一旦是包含关系,要求条件表达式范围小的写到范围大的上边;>
switch分支结构
执行流程说明
- 首先计算出表达式的值
- 其次,和case依次比较,一旦有对应的值,就会执行相应的语句,在执行的过程中,遇到break就会结束。
- 最后,如果所有的case都和表达式的值不匹配,就会执行default语句体部分,然后程序结束掉。
public class SwitchDemo {
public static void main(String[] args) {
//创建键盘录入对象
Scanner sc = new Scanner(System.in);
//接收数据
System.out.println("请输入一个数字(1-7):");
int weekday = sc.nextInt();
//switch语句实现选择
switch(weekday) {
case 1:
System.out.println("星期一");
break;
case 2:
System.out.println("星期二");
break;
case 3:
System.out.println("星期三");
break;
case 4:
System.out.println("星期四");
break;
case 5:
System.out.println("星期五");
break;
case 6:
System.out.println("星期六");
break;
case 7:
System.out.println("星期日");
break;
default:
System.out.println("你输入的数字有误");
break;
}
}
}
注意事项
-
swich(表达式)中表达式的返回值必须是以下几种类型之一:
byte,short,char,int,枚举(jdk1.5),String(jdk1.7) -
case子句中的值必须是常量,且所有case子句中的值应是不同的;
-
default子句是可任选的,当没有匹配的case时,执行default;
-
break语句用来在执行完一个case分支后使程序跳出swich语句块;如果没有break程序会顺序执行到swich结尾;
if和switch区别
if和swich语句很像,如果判断的具体数值不多,而且复合byte、short、int、char这四种类型。建议使用swich语句,因为效率稍高;
其他情况:对区间进行判断,对结果为boolean类型进行判断,使用if,if的使用范围比较广泛。
循环结构
循环语句就是在满足一定条件的情况下反复执行某一个操作。包括while循环语句、do···while循环语句和for循环语句。
for循环语句
Demo
public class ForDemo {
public static void main(String[] args) {
//原始写法
System.out.println("HelloWorld");
System.out.println("HelloWorld");
System.out.println("HelloWorld");
System.out.println("HelloWorld");
System.out.println("HelloWorld");
System.out.println("HelloWorld");
System.out.println("HelloWorld");
System.out.println("HelloWorld");
System.out.println("HelloWorld");
System.out.println("HelloWorld");
System.out.println("-------------------------");
//用循环改进
for(int x=1; x<=10; x++) {
System.out.println("HelloWorld");
}
}
}
foreach循环语句
定义
它是Java5后新增的for语句的特殊简化版本,并不能完全替代for语句,但所有foreach语句都可以改写为for语句。foreach语句在遍历数组等时为程序员提供了很大的方便。
Demo
public class Test {
public static void main(String args[]) {
int [] numbers = {10, 20, 30, 40, 50};
for ( int x : numbers ) {
System.out.print( x );
System.out.print(",");
}
System.out.print();
String [] names = {"James", "Larry", "Tom", "Lacy"};
for ( String name : names ) {
System.out.print( name );
System.out.print(",");
}
}
}
while循环语句
定义
while循环语句的循环方式为利用一个条件来控制是否要继续反复执行这个语句。
格式
while(判断条件语句) {
循环体语句;
}
Demo
public class WhileDemo {
public static void main(String[] args) {
//输出10次HelloWorld
/*
for(int x=1; x<=10; x++) {
System.out.println("HellloWorld");
}
*/
//while循环实现
int x=1;
while(x<=10) {
System.out.println("HellloWorld");
x++;
}
}
}
do…while循环语句
格式
do {
循环体语句;
}while((判断条件语句);
Demo
public class DoWhileDemo {
public static void main(String[] args) {
//输出10次 HelloWorld
/*
for(int x=1; x<=10; x++) {
System.out.println("HelloWorld");
}
*/
//do...while改写
int x=1;
do {
System.out.println("HelloWorld");
x++;
}while(x<=10);
}
}
三种循环的区别
- do…while循环至少会执行一次循环体。
- for循环和while循环只有在条件成立的时候才会去执行循环体
- for循环语句和while循环语句的小区别:
控制条件语句所控制的那个变量,在for循环结束后,就不能再被访问到了,而while循环结束还可以继续使用,如果你想继续使用,就用while,否则推荐使用for。原因是for循环结束,该变量就从内存中消失,能够提高内存的使用效率。
跳转语句(控制循环结构)
Java语言中提供了3种跳转语句,分别是break语句、continue语句和return语句。
break
使用场景
- 在选择结构switch语句中
- 在循环语句中
作用
跳出单层循环
注意:离开使用场景是没有意义的。
Demo
/*
* break:中断的意思
* 使用场景:
* A:switch语句中
* B:循环中
* 注意:
* 离开使用场景是没有意义的。
* 作用:
* 跳出循环,让循环提前结束
*/
public class BreakDemo {
public static void main(String[] args) {
//break;
for(int x=1; x<=10; x++) {
if(x == 3) {
break;
}
System.out.println("HelloWorld");
}
}
}
continue
使用场景
在循环语句中
作用
结束一次循环,继续下一次的循环
注意:离开使用场景的存在是没有意义的
Demo
/*
* continue:继续的意思
* 使用场景:
* 循环中
* 注意:
* 离开使用场景是没有意义的
* 作用:
* 结束一次循环,继续下一次的循环
* 区别:
* break:退出循环
* continue:结束一次循环,继续下一次的循环
*/
public class ContinueDemo {
public static void main(String[] args) {
//continue;
for(int x=1; x<=10; x++) {
if(x == 3) {
//break;
continue;
}
System.out.println("HelloWorld");
}
}
}
continue与break区别
break 退出当前循环
continue 退出本次循环
return
使用场景
在循环语句中
在方法中
作用
可以从一个方法返回,并把控制权交给调用它的语句。
直接结束整个方法,从而结束循环。
Demo
public class ReturnDemo {
public static void main(String[] args) {
getStr();
}
public String getStr() {
return "Hello";
}
}
第五章 数组、排序和查找
数组
一维数组
数组就是存储数据长度固定的容器,保证多个数据的数据类型要一致。
数组是引用数据类型的 hens.length //数组的长度
数组初始化
动态初始化
数组存储的数据类型[ ] 数组名字 = new 数组存储的数据类型[数组长度];
数组存储的数据类型 数组名字[ ] = new 数组存储的数据类型[数组长度];
int[] arr = new int[3];
int arr[] = new int[3];
// 可以拆分
int[] arr;
arr = new int[3];
静态初始化
数据类型[] 数组名 = new 数据类型[]{元素1,元素2,元素3…};
int[] arr = new int[]{1,2,3,4,5};
// 可以拆分
int[] arr;
arr = new int[]{1,2,3,4,5};
静态缺省型(不可拆分)
数据类型[] 数组名 = {元素1,元素2,元素3…};
int[] arr = {1,2,3,4,5};
数组基础
默认初始值
-
int/short/long/byte 0
-
float/double 0.0
-
char \u0000
-
boolean false
-
String null
数组的长度属性:
每个数组都具有长度,而且是固定的,Java中赋予了数组的一个属性,可以获取到数组的长度,语句为:数组名.length
,属性length的执行结果是数组的长度,int类型结果。由次可以推断出,数组的最大索引值为数组名.length-1
。
二维数组
初始化
同一维数组一样,共有4总不同形式的定义方法:
int[][] array1 = new int[10][10];
int array2[][] = new int[10][10];
int array3[][] = { { 1, 1, 1 }, { 2, 2, 2 } };
int array4[][] = new int[][] { { 1, 1, 1 }, { 2, 2, 2 } };
不定长二维数组
int[][] array = new int[3][];
array[0] = new int[1];
array[1] = new int[2];
array[2] = new int[3];
获取二维数组的长度
int length1 = array.length;
int length2 = array[0].length;
// 获取二维数组的第一维长度(3)
System.out.println(length1);
// 获取二维数组的第一维的第一个数组长度(1)
System.out.println(length2);
数组原理内存图
内存概述
内存是计算机中的重要原件,临时存储区域,作用是运行程序。我们编写的程序是存放在硬盘中的,在硬盘中的程序是不会运行的,必须放进内存中才能运行,运行完毕后会清空内存。 Java虚拟机要运行程序,必须要对内存进行空间的 分配 和 管理 。
Java虚拟机的内存划分
为了提高运算效率,就对空间进行了不同区域的划分,因为每一片区域都有特定的处理数据方式和内存管理方式。
区域名称 | 作用 |
---|---|
寄存器 | 给CPU使用,和我们开发无关。 |
本地方法栈 | JVM在使用操作系统功能的时候使用,和我们开发无关。 |
方法区 | 存储可以运行的class文件。 |
堆内存 | 存储对象或者数组,new来创建的,都存储在堆内存。 |
方法栈 | 方法运行时使用的内存,比如main方法运行,进入方法栈中执行。 |
数组在内存中的存储
一个数组内存图
public static void main(String[] args) {
int[] arr = new int[3];
System.out.println(arr); // [I@5f150435
}
数组new出来的内容,都是在堆 内存中存储的,而方法中的变量arr保存的是数组的地址。
输出arr[0],就会输出arr保存的内存地址中数组中0索引上的元素
程序执行流程:
- main方法进入方法栈执行
- 创建数组,JVM会在堆内存中开辟空间,存储数组
- 数组在内存中会有自己的内存地址,以十六进制数表示
- 数组中有3个元素,默认值为0
- JVM将数组的内存地址赋值给引用类型变量array
- 变量array保存的是数组内存中的地址,而不是一个具体数值,因此称为引用数据类型。
两个数组内存图
两个变量指向一个数组
数组常见异常
数组越界异常
public static void main(String[] args) {
int[] arr = {1,2,3};
System.out.println(arr[3])
}
创建数组,赋值3个元素,数组的索引就是0,1,2,没有3索引,因此我们不能访问数组中不存在的索引,程序运行后,将会抛出 ArrayIndexOutOfBoundsException 数组越界异常。在开发中,数组的越界
数组空指针异常
public static void main(String[] args) {
int[] arr = {1,2,3};
arr = null;
System.out.println(arr[0]);
}
arr = null 这行代码,意味着变量arr将不会在保存数组的内存地址,也就不允许再操作数组了,因此运行的时候 会抛出 NullPointerException 空指针异常。在开发中,数组的空指针异常是不能出现的,一旦出现了,就必须要修改我们编写的代码。
数组常见操作
数组反转
数组中的元素颠倒顺序,例如原始数组为1,2,3,4,5,反转后的数组为5,4,3,2,1
对称位置的元素交换
- 实现反转,就需要将数组对称元素位置交换
- 定义两个变量,保存数组的最小索引和最大索引
- 两个索引上的元素交换位置
最小索引++
,最大索引--
,再次交换位置 - 最小索引超过了最大索引,数组反转操作结束
实现思路:
- 把数组最小索引元素和数组的最大索引元素交换
- 把数组次小索引元素和数组索引次大索引元素交换
- …
- 定义两个索引,一个指向最小索引,一个指向最大索引
int min = 0; int max = arr.length - 1; - 遍历数组,让两个索引变化
min++,max–-, 交换条件 min < max; - 交换最小索引元素 和最大索引元素
需要定义三方变量
int temp = arr[min];
arr[min] = arr[max];
arr[max] = temp;
Demo
public class Demo{
public static void main(String[] args){
int[] array = {1, 2, 3, 4, 5};
int MAX = 4;
int MIN = 0;
while(MIX > MIN){
int temp;
temp = array[MAX];
array[MAX] = array[MIN];
array[MIN] = temp;
MAX--;
MIM++;
}
for(int i = 0; i++; i < 5){
System.out.print(array[i] + " ");
}
}
}
数组获取最大元素
最大值获取:从数组的所有元素中找出最大值。
实现思路:
- 定义变量 max,保存数组0索引上的元素
- 遍历数组,获取出数组中的每个元素
- 将遍历到的元素和保存数组0索引上值的max变量进行比较
- 如果数组元素的值大于了变量的值,变量记录住新的值
- 数组循环遍历结束,变量保存的就是数组中的最大值
Demo
public class Demo{
public static void main(String[] args){
int[] array = {1, 2, 3, 4, 5};
int max = 0;
for(int i = 0; i < 5; i++){
if(max < array[i]){
max = array[i];
}
}
System.out.println(max);
}
}
数组排序
Arrays.sort()
public static void main(String[] args) {
int[] array = { 3, 2, 1, 4, 5 };
Arrays.sort(array);
System.out.println(Arrays.toString(array)); // [1, 2, 3, 4, 5]
}
Java数组常用API
输出数组 Arrays.toString()
int[] array = { 1, 2, 3 };
System.out.println(Arrays.toString(array));
数组转List Arrays.asList()
String[] array2 = {"a", "b", "c", "d"};
System.out.println(array2); // [Ljava.lang.String;@13b6d03
List list = new ArrayList(Arrays.asList(array2));
System.out.println(list); // [a, b, c, d]
list.add("GG");
System.out.println(list); // [a, b, c, d, GG]
数组转Set Arrays.asList()
String[] array = { "a", "b", "c", "d", "e" };
Set set = new HashSet(Arrays.asList(array));
System.out.println(set);
List转数组 toArray()
List list = new ArrayList();
list.add("a");
list.add("b");
list.add("c");
String[] array = new String[list.size()];
list.toArray(array);
for (String s : array)
System.out.println(s);
数组中是否包含某个值
Arrays.asList(array).contains(“a”)
String[] array = { "a", "b", "c", "d", "e" };
boolean isEle = Arrays.asList(array).contains("a");
System.out.println(isEle);
数组复制
System.arraycopy(array, 0, array1, 0, array.length)
int array[] = new int[] { 1, 2, 3, 4 };
int array1[] = new int[array.length];
System.arraycopy(array, 0, array1, 0, array.length);
数组合并
org.apache.commons.lang.ArrayUtils.addAll(array1, array2);
int[] array1 = { 1, 2, 3, 4, 5 };
int[] array2 = { 6, 7, 8, 9, 10 };
int[] array = org.apache.commons.lang.ArrayUtils.addAll(array1, array2);
//System.out.println(Arrays.toString(array));
//String数组转字符串(使用指定字符拼接)
String[] array = { "a", "b", "c" };
String str = org.apache.commons.lang.StringUtils.join(array, ", ");
System.out.println(str);
数组逆序
org.apache.commons.lang.ArrayUtils.reverse(array);
int[] array = { 1, 2, 3, 4, 5 };
org.apache.commons.lang.ArrayUtils.reverse(array);
System.out.println(Arrays.toString(array));
数组元素移除
org.apache.commons.lang.ArrayUtils.removeElement(array, 3)
int[] array = { 1, 2, 3, 4, 5 };
int[] removed = org.apache.commons.lang.ArrayUtils.removeElement(array, 3);
System.out.println(Arrays.toString(removed));
数组作为方法参数和返回值
数组作为方法参数
数组作为方法参数传递,传递的参数是数组内存的地址。
Demo
public static void main(String[] args) {
int[] arr = { 1, 3, 5, 7, 9 };
//调用方法,传递数组
printArray(arr);
}
/* 创建方法,方法接收数组类型的参数 进行数组的遍历 */
public static void printArray(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
内存图示例
数组作为方法返回值
组作为方法的返回值,返回的是数组的内存地址
Demo
public static void main(String[] args) {
//调用方法,接收数组的返回值
//接收到的是数组的内存地址
int[] arr = getArray();
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
/* 创建方法,返回值是数组类型
创建方法,返回值是数组类型 return返回数组的地址 */
public static int[] getArray() {
int[] arr = { 1, 3, 5, 7, 9 };
//返回数组的地址,返回到调用者
return arr;
}
内存图示例
方法的参数类型区别
代码分析
public static void main(String[] args) {
int a = 1;
int b = 2;
System.out.println(a); // 1
System.out.println(b); // 2
change(a, b);
System.out.println(a); // 1
System.out.println(b); // 2
}
public static void change(int a, int b) {
a = a + b;
b = b + a;
}
public static void main(String[] args) {
int[] arr = {1,3,5};
System.out.println(arr[0]); // 1
change(arr);
System.out.println(arr[0]); // 200
}
public static void change(int[] arr) {
arr[0] = 200;
}
基本数据类型的值传递 和 引用数据类型的值传递
排序
查找
第六章 面向对象编程
面向对象
面向过程只是针对于自己来解决问题。面向过程的操作是以程序的基本功能实现为主,实现之后就完成了,也不考虑修改的可能性,面向对象,更多的是要进行子模块化的设计,每一个模块都需要单独存在,并且可以被重复利用,所以,面向对象的开发更像是一个具备标准的开发模式
三大特征
封装
核心思想就是**“隐藏细节”、“数据安全”**,将对象不需要让外界访问的成员变量和方法私有化,只提供符合开发者意愿的公有方法来访问这些数据和逻辑,保证了数据的安全和程序的稳定。所有的内容对外部不可见。
继承
子类可以继承父类的属性和方法,并对其进行拓展。将其他的功能继承下来继续发展 。
多态
**同一种类型的对象执行同一个方法时可以表现出不同的行为特征。**通过继承的上下转型、接口的回调以及方法的重写和重载可以实现多态。方法的重载本身就是一个多态性的体现
三大思想
面向对象思想从概念上讲分为以下三种:OOA、OOD、OOP
OOA:面向对象分析(Object Oriented Analysis)
OOD:面向对象设计(Object Oriented Design)
OOP:面向对象程序(Object Oriented Programming )
类与对象
概念
一个程序(世界),里面有很多事物(对象[属性,行为])
类是一种类型(自己定义的数据类型)
对象是一个具体的实例。
- 类必须编写在.java文件中;
- 一个.java文件中,可以存在N个类,但是只能存在一个public修饰的类;
- .java文件的文件名必须与public修饰的类名完全一直;
- 同一个包中不能有重名的类;
Demo
class Cat{
String name;
int age;
String color;
public void jiaos(){
System.out.println("喵喵喵!"+name+ age+color);
}
}
//类
Cat cat1 = new Cat();
//对象
cat2.name = "小花";
cat2.age = 2;
cat.color = "red";
cat1.jiaos();
创建对象的内存分析
栈(stack)
Java栈的区域很小 , 大概2m左右 , 特点是存取的速度特别快
栈存储的特点是:先进后出
存储速度快的原因:
栈内存, 通过 ‘栈指针’ 来创建空间与释放空间 !
指针向下移动, 会创建新的内存, 向上移动, 会释放这些内存 !
这种方式速度特别快 , 仅次于PC寄存器 !
但是这种移动的方式, 必须要明确移动的大小与范围 ,
明确大小与范围是为了方便指针的移动 , 这是一个对于数据存储的限制, 存储的数据大小是固定的 , 影响了程序 的灵活性 ~
所以我们把更大部分的数据存储到了堆内存中
堆(heap)
堆存储的是:
基本数据类型的数据以及引用数据类型的引用!
Demo
int a =10;
Person p = new Person();
//10存储在栈内存中 , 第二句代码创建的对象的引用§存在栈内存中
Java是一个纯面向对象语言, 限制了对象的创建方式 :
所有类的对象都是通过new关键字创建
new关键字, 是指告诉JVM , 需要明确的去创建一个新的对象 , 去开辟一块新的堆内存空间:
堆内存与栈内存不同, 优点在于我们创建对象时 , 不必关注堆内存中需要开辟多少存储空间 , 也不需要关注内存占用
时长 !
堆内存中内存的释放是由GC(垃圾回收器)完成的
垃圾回收器回收堆内存的规则
当栈内存中不存在此对象的引用时,则视其为垃圾 , 等待垃圾回收器回收 !
逻辑上分为三部分
新生代(Young Generation,常称为YoungGen)
- 新生代(Young Generation),常称为YoungGen,位于堆空间。
- 新生区又分为Eden区和Survior(幸存区)。
- Eden:新创建的对象
- Survior 0、1:经过垃圾回收,但是垃圾回收次数小于15次的对象。
老年代(Old Generation,常称为OldGen、TenuringGen)
- 老年代常称为OldGen,位于堆空间
- Old:垃圾回收次数超过15次,依然存活的对象。
永久代(Permanent Generation,常称为PermGen)
-
永久代常称为PermGen,位于非堆空间。
-
永久区是一个常驻内存区域,用于存放JDK自身所携带的Class,Interface的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭JVM才会释放此区域所占用的空间。
方法区
方法区(Method Area),又称永久代,又称非堆区(Non-Heap space)
方法区特性
被所有线程共享
- 所有的字段和方法字节码,以及一些特殊方法如构造函数,接口代码也再此定义。
- 简单说,所有定义的方法的信息都保存在该区域,此区属于共享区间。
- 这些区域储存的是:静态变量+常量+类信息(构造方法/接口定义)+运行时常量池。
- 但是,实例变量存在堆内存中,和方法区无关。
方法区仅仅只是逻辑上的独立,实际上还是包含在java堆中,也就是说,方法区在物理上属于java堆区中的一部分,而永久区(Permanent Generation)就是方法区的实现。
方法区的基本理解
1.方法区(Method Area)与Java堆一样,是各个线程共享的内存区域。
2.方法区在JVM启动的时候被创建,并且它的实际的物理内存空间中和Java堆区一样都可以是不连续的。
3.方法区的大小,跟堆空间一样,可以选择固定大小或者可扩展。
4.方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类(加载大量第三方jar包、timcat部署的工程过多、大量动态生成反射类),导致方法区溢出,虚拟机同样会抛出内存溢出错误:java.lang.OutOfMemoryError: PermGen space(jdk7以前)或者java.lang.OutOfMemoryError: Metaspace(jdk8以后)
5.关闭JVM就会释放这个区域的内存。
方法区的实现的演变
jdk1.7之前:hotspot虚拟机对方法区的实现为永久代。
jdk1.8及之后:hotspot移除了永久代用元空间(Metaspace)。
PC寄存器
PC寄存器保存的是当前正在执行的 JVM指令的 地址 ;
在Java程序中, 每个线程启动时, 都会创建一个PC寄存器 ;
本地方法栈
保存本地(native)方法的地址
属性
概念
-
成员变量 = 属性 = field字段
-
属性是类的组成部分,基本数据类型和引用数据类型
-
属性的定义:访问修饰符 属性类型 属性名;
访问修饰符 public proctected private
public int a;
- 属性如果不赋值就是有默认值,规则与数组一致。
创建对象
Cat cat;
cat = new Cat();
Cat cat = new Cat();
调用属性
对象名.属性值;
cat.name;
cat.age;
cat.color;
类与对象的内存分配机制
Person p1 = new Person();
p1.age = 10;
p1.name = "xiaomin";
Person p2 = p1;
System.out.println(p2.age);
//输出:10,共用一块堆空间中的内存空间。
//p1, p2存储的地址值一样
栈:一般存放基本数据类型(局部变量)
堆:存放对象
方法区:常量池,类加载信息
Java创建对象的流程简单分析
- 先加载Person类信息(属性和方法信息,指挥加载一次)
- 在堆中分配空间,进行默认初始化(int–>0)
- 把地址赋给p,p指向对象
- 进行指定的初始化(p1.name)
方法
- 提高代码的复用性
- 可以将实现的细节封装起来,供其他用户来调用
模板
权限修饰符 返回数据类型 方法名(形参列表){
语句; //方法体
返回值;
}
注意事项
- 一个方法最多有一个返回值
- 返回类型可以为任意类型
- 如果方法要求有返回值类型,则方法体中必须有return值;而要求返回值类型必须和return的值类型一致或者兼容———— 只能从低精度到高精度
public double text(){
double d1 = 1.1;
return 10; //可以自动类型转换——兼容
}
- 如果方法是void,则方法中可以没有return语句,或者只写return——退出方法
- 遵循驼峰命名法,见名知意,表达出功能的的基本意思。
参数列表
- 一个方法可以有0个参数,也可以有多个参数。
- 参数可以为任意类型
- 调用带参方法时,一定对应这参数列表传入相同类型或者兼容类型的参数。
- 方法定义的参数称为形式参数(形参);方法调用时的参数称为实际参数(实参),实参和形参的类型要一致或兼容
方法体
输入、输出、变量、运算、分支、循环、方法调用,但方法里面不能再定义方法。—不可以嵌套定义
main()是一个方法,方法里面不能定义方法
方法的调用
- 在同一个类中:直接调用()
- 不在同一个类的方法:需要通过对象名调用。(对象名.方法名(参数))
成员方法传参机制
基本数据类型,传递的是值(值拷贝),形参的任何改变不影响实参。
引用类型传递的是地址(床底的也是值,但是值是地址),可以通过形参影响实参。
方法递归调用
优缺点
-
递归就是方法自己调用自己,每次传用时传入不同的变量,递归有助于解决复杂的问题,同时让代码变简洁。
-
代码更简洁清晰,可读性好
-
由于递归需要系统堆栈,所以空间消耗要比非递归代码要大很多。而且,如果递归深度太大,可能系统撑不住。
递归的要求
- 有反复执行的过程(自己调用自己)
- 有跳出反复执行过程的条件(递归出口)
递归内存分析
T t1 = new T();
t1.test(4);
}}
public class T{
public void test(int n){
if(n > 2){
test(n-1);
}
System.out.println("n = " + n);
}
}
//输出 n = 2,n = 3, n = 4
递归原理
-
执行一个方法时,就创建一个新的受保护的独立空间(栈空间)
-
方法的局部变量是独立的,不会相互影响
-
如果方法中使用的是引用数据类型(数组,对象等),就会共享引用的类型的数据
-
递归得向递归的退出条件逼近、不然就出现无限递归
-
当方法执行完毕,或者遇到return,返回给调用它
Demo
阶乘
//阶乘
public class text {
public static void main(String[] args) {
test t = new test();
int a = t.jiec(5);
System.out.print(a);
}
}
class test{
public int jiec(int a){
if(a != 1)
return a = a * jiec(a - 1);
else
return 1;
}
}
汉诺塔
class hanTower{
char a = 'A';
char b = 'B';
char c = 'C';
public void tower(int numb, char a, char b, char c){
if(numb == 1){
System.out.println(a + "----->" + c); //一个盘
}else{
//多个盘,将其看为numb-1和底部一个盘
tower(numb - 1, a, c, b); //上面所有盘移动到b,借助c 再执行下一句
System.out.println(a + "----->" + c); //把最下面的盘移动到c
tower(numb - 1, b, a, c); //把b中的盘全部移动到c
}
}
}
方法重载(overload)
基本介绍
Java中允许同一个类中,多个同名方法的存在,要求形参列表不一致!
- 减轻了起别名的麻烦
- 减轻了记名的麻烦
注意事项
- 方法名:必须相同
- 参数列表:必须不同(参数类型或者个数或者顺序,至少有一样不同,参数名无要求)
- 返回类型:无要求
Demo
System.out.print("sad");
System.out.print('a');
System.out.print(12);
public class demo1 {
public static void main(String[] args) {
cz c = new cz();
c.name(1.0,1);
c.name(1, 1.0);
}
}
class cz{
public void name(int i, double j){
System.out.println("int i,double j");
}
public void name(double i, int j){
System.out.println("double i, int j");
}
}
构造方法的重载
- 一个类, 可以存在多个构造方法 ;
- 参数列表的长度或类型不同即可完成构造方法的重载 ;
- 构造方法的重载 ,可以让我们在不同的创建对象的需求下, 调用不同的方法来完成对象的初始化 ;
方法重写(overwrite)
定义
- 参数列表必须完全与被重写的方法相同;
- 返回类型必须完全与被重写的返回类型相同;
- 访问权限不能比父类被重写的方法的访问权限更低。例如父类方法为public,子类就不能为protected;
- 父类的成员方法只能被它的子类继承;
- 声明为static和private的方法不能被重写,但是能够被再次声明;
Demo
public class Demo{
public static void main(String[] args){
Student student=new Student();
student.say();
}
}
class Person{
public void say(){
System.out.println("锄禾日当午,汗滴禾下土。");
}
}
class Student extends Person{
public void say(){
System.out.println("锄禾日当午,玻璃好上霜。要不及时擦,整不好得脏。");
}
}
//结果为:
//锄禾日当午,玻璃好上霜。要不及时擦,整不好得脏。
重写与重载的区别
- 重写方法名返回值相同参数相同;
- 重载方法名相同返回值相同参数可以不同,个数可以不同;
- 重写发生在父子类中,重载发生在一个类中;
- 重载与访问权限无关 ;
- 异常处理:重载与异常无关 ; 重写异常范围可以更小,但是不能抛出新的异常 ;
可变参数
定义
Java中允许将同一个类中多个同名同功能但参数个数不同的方法封装成一个方法
基本语法
访问修饰符 返回类型 方法名(数据类型...形参名){//0-多
}
类HspMenthod,方法sum(可以计算n个数的和)
使用可变参数时,可以当作数组来使用
public class demo1 {
public static void main(String[] args) {
HepMenthod H = new HepMenthod();
int a = H.sum(1,8);
System.out.println(a);
}
}
class HepMenthod{
public int sum(int...nums){
int sum = 0;
for(int i = 0; i < nums.length; i++){
sum = sum + nums[i];
}
return sum;
}
}
注意事项
- 可变参数的实参可以为0个或者多个
- 可变参数的实参可以为数组
- 可变参数的本质就是数组
- 可变类型参数可以和普通类型参数放在一起,但必须保证可变类型参数在最后
- 一个形参列表有且已有一个可变类型参数
作用域
定义
- 主要的变量成员变量(属性)和局部变量
- 局部变量一般指在成员方法中定义的变量
- 全局变量:作用域是整个类
- 局部变量:作用域是定义它的代码块中
- 全局变量(属性)可以不赋值,直接使用,因为有默认值,局部变量必须赋值后,才能使用,因为没有默认值。
注意事项
- 属性和局部变量可以重名,访问时遵循就近原则。
- 在同一个作用域中,两个局部变量不能重名
- 属性生命周期长,伴随着对象的创建而创建,伴随对象的死亡而死亡。局部变量生命周期短,伴随着它的代码块的执行而创建,伴随着代码块的结束而死亡。
- 作用域不同
全局变量:可以被本类使用,或其他类使用(通过对象的调用)
局部变量:只可以在本类中对应的方法中使用
-
修饰符不同
全局变量/属性可以加修饰符
局部变量不可以加修饰符
构造器
作用
在创建对象时,直接指定对象的属性。完成对新对象的初始化
基本语法
[修饰符] 方法名(形参列表){
方法体;
}
注意事项
- 构造器的名称必须和类名一致
- 一个类中可以定义多个构造器,但是构造器的参数列表必须不同(重载)
- 如果我们没有手动定义构造器,则Java系统会提供一个默认的构造器给我们使用,一旦我们定义了构造器,则系统会把默认的构造器收回
- 构造器的作用:实例化对象,给对象赋初始值
- 代码游离块优先执行
构造器的继承
子类构造器会默认调用父类无参构造器,如果父类没有无参构造器,则必须在子类构造器的第一行通过 super关键字指定调用父类的哪个构造器,具体看下文例子。final类是不允许被继承的,编译器会报错。很好理解,由于final修饰符指的是不允许被修改,而继承中,子类是可以修改父类的,这里就产生冲突了,所以final类是不允许被继承的。
无继承的情况下的执行顺序静态代码块:只在程序启动后执行一次,优先级最高构造代码块:任何一个构造器被调用的时候,都会先执行构造代码块,优先级低于静态代码块构造器:优先级低于构造代码块总结一下优先级:静态代码块 > 构造代码块 > 构造器
有继承的情况下的执行顺序:父类静态代码块:只在程序启动后执行一次,优先级最高 子类静态代码块:只在程序启动后执行一次,优先级低于父类静态代码块 父类构造代码块:父类任何一个构造器被调用的时候,都会执行一次,优先级低于子类静态代码块父类构造器:优先级低于父类构造代码子类构造代码块:子类任何一个构造器被调用的时候,都会执行一次,优先级低于父类构造器子类构造器:优先级低于子类构造代码块总结一下优先级:父类静态代码块 > 子类静态代码块 > 父类构造代码块 > 父类构造器 > 子类构造代码块 > 子类构造器
this关键字
定义
Java虚拟机会给每个对象分配this,代表当前对象
使用this解决变量命名问题
Demo
Person(String name, int age){
this.name = n;
this.age = a;
}
//this.name,this.age,代表当前对象的属性
注意事项
- this关键字可以用来访问本类的属性、方法、构造器
this.age;
this.f();
this(); //只能在构造器中调用访问另外一个构造器
class T{
public T(){
this("jack", 16);
}
public T(String name, int age){
this.name = name;
this.age = age;
}
}
- this可以区分当前类的属性和局部变量
- 访问成员方法的语法this.方法名(参数列表)
- 访问构造器语法 this.(参数列表);只能在构造器中使用
- this不能再类定义的外部使用,只能再类定义的方法中使用
包
作用
- 区分相同名字的类
- 类很多的时候可以管理类
- 控制访问范围
包的本质
实际上就是创建不同的文件夹来保存文件——文件夹
语法
//包的基本语法
package com.hspedu;
//package 关键字,表示打包
com.hspedu 表示包名
包的命名
规则
只能包含数字、字母、下划线、小圆点,但不能是数字开头,不能是关键字或者保留字
规范
com.公司名.项目名.业务模块名
com.sina.crm.user;//用户模块
com.sina.crm.order; //订单模块
com.sina.crm.utils //工具类
常用的包
java.lang.*;//lang为基本包,默认引入
java.util.*;//util包,系统提供的工具包,使用Scanner
java.net.*;//网络包,网络开发
java.awt.*;//java界面开发,GUI
导入包
语法
import 包;
import java.util Scanner;//引入一个类Scanner
import java.util.*;//将java.util包都引入
注意事项
- package的作用的声明当前类所在的包,需要放在class最上面,一个类中最多只有一句package
- import位置放在package下面,在类定义前面,可以有多句且没有顺序要求。
访问修饰符
Java提供四种访问控制修饰符,用于控制方法和属性(成员变量)的访问权限
- 公开级别:public修饰,对外公开
- 受保护级别:protected修饰,对子类和同一个包中的类公开
- 默认级别:没有修饰符号,向同一个包的类公开
- 私有级别:private修饰,只有类本身可以访问,不对外公开
- 只有public才可以修饰类
- 成员方法的访问规则和属性一致
修饰符 | 当前类 | 同一包内 | 子孙类(同一包) | 子孙类(不同包) | 其他包 |
---|---|---|---|---|---|
public | Y | Y | Y | Y | Y |
protected | Y | Y | Y | Y/N | N |
default | Y | Y | Y | N | N |
private | Y | N | N | N | N |
1、public修饰成员方法
包内:可以在包中任意类中直接创建该方法所在类或子类的对象进行方法的调用;
包外:可以在包外任意类中直接创建该方法所在类或子类的对象进行方法的调用。
包修饰符:被声明为 public 的类和接口能够被任何其他类访问。如果几个相互访问的 public 类分布在不同的包中,则需要导入相应 public 类所在的包。
2、protected修饰成员方法:最想写的就是这个修饰符,它是专门为子类所提供的一个权限修饰符。
包内:可以在包中任意类中直接创建该方法所在类或子类的对象进行方法的调用;
包外:只能创建该方法所在类的子类的对象进行方法的调用,而且子类实例可以访问其从基类继承而来的 protected 方法,而不能访问基类实例的protected方法。方法所在类的对象无法进行调用。
3、default修饰成员方法:则该外方法相当于被封装在包内了。
包内:可以在包中任意类中直接创建该方法所在类或子类的对象进行方法的调用;
包外:无法访问该方法。
包修饰符:被声明为 default 的类只能在本包内被访问,创建,在其他包内,不能被导入和创建
4、private修饰成员方法:则该外方法相当于被封装在类中了。
包内:仅在方法所在类中可以被访问,包中其他类也无法访问;
包外:无法访问该方法。
opp三大特征
封装
封装encapsulation:就是把抽象的数据(属性)和对数据的操作(方法)封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作[方法],才能对数据进行操作。
- 隐藏实现细节:调用(传入参数)–>方法(连接数据库)
- 可以对数据进行验证,保证安全合理
封装实现的步骤
-
将属性进行私有化(不能直接修改属性)
-
提供一个公共的set方法,用于对属性判断并赋值
public void setXxx(类型 参数值){
//加入数据验证业务逻辑
属性 = 参数名;
}
- 提供一个公共的get方法,用于获取属性的值
public 返回类型 getXxx(){
//权限判断
return xx;
}
- set — get快捷键 alt + insert
public double getSalary(){
//可以增加权限判断,比如设置密码验证!!!!
return salary
}
- string.length(); 返回字符串长度
public void setName(name){
//加入对字符的校验
//相当于增加了业务逻辑
if(name.length() >= 2 && name.length() <= 6)
this.name == name;
else{
System.out.println("name请在2-6个字符之内");
this.name = 'null'
}
}
封装与构造器
在构造器中调用set方法
在构造器中调用set方public Person(String name, int age, double salary){
//this.name = name;
//this.age = age;
//this.salary = salary;
//这样把set方法写在构造器中,仍然可以通过 验证
setName(name);
setAge(age);
setSalary(salary);
}
继承(extends)
作用
继承(提高代码复用性)(代码扩展性和维护性提高)
继承可以解决代码的复用,当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends来声明继承父类即可。
基本语法
class 子类 extends 父类{
}
注意事项
-
子类会自动拥有父类定义的属性和方法
-
父类又叫超类,基类
-
子类又叫派生类
-
子类不会继承父类的构造方法
关系图
继承的细节(Ctrl+H类的关系)
- 子类继承了父类所有的属性和方法,但是私有属性和方法 不能在子类中直接访问,要通过父类提供的公共的方法去访问。
- 子类必须调用父类的构造器,完成父类的初始化
public Sub(){
super();//默认调用父类的无参构造器
System.out.println("子类Sub无参构造被调用");
}
- 当创建子类对象时,不管使用子类的那个构造器,默认情况下总会去调用父类的无参构造器。如果父类没有提供无参构造器,则必须在子类的构造器中用super去指定使用父类的哪个构造器完成对父类初始化工作。
public Sub(){
//父类的无参构造器被覆盖
super("张三", 12); //必须用super()调用父类的有参构造器
System.out.println("子类Sub无参构造被调用");
}
- 如果希望指定的去调用父类的某个构造器,则显式的调用一下:super(参数列表)
- super在使用时,必须放在构造器第一行,必须先调用父类构造器
- super只能在构造器中使用
- super();和this()(调用另一个构造器);都只能放在构造器第一行,不能一起使用
- Java所有类都是Object类的子类,Object是除本身外所有类的基类
- 父类的构造器调用不限于直接父类,将一直向上追溯到Object类(顶级)
- 子类最多只能继承一个父类,Java单继承机制
- 不能滥用继承,子类和父类之间必须满足is - a的逻辑关系
student is a person.
cat is anmial
继承流程
-
首先查看子类是否有该属性
-
如果子类有该属性且可以访问,则返回信息
-
如果子类没有该信息**或者不能访问直接报错**,就看父类有没有该信息
-
如果父类有该信息且可以访问,则返回信息
-
如果父类没有该信息或者**不能访问报错**,就看上级父类(直到Object)有没有该信息
-
都不满足则报错
Demo
class A{
A(){System.out.println("a")}
A(String name){System.out.println("a name")}
}
class B extends A{
B(){this("abc"); System.out.println("b")}
B(String name){System.out.println("b name");}
}
//a, b, name, b
super关键字
作用与语法
super代表父类的引用,用于访问父类的属性、方法、构造器
-
访问父类的属性,但不能访问父类的private属性
super.属性名
-
访问父类的方法,不能访问父类的private方法
super.方法名(参数列表);
-
访问父类的构造器
super(参数列表); //只能放在构造器的第一句super
super的好处
-
调用父类构造器的好处:分工明确,父类属性由父类初始化,子类属性由子类初始化
-
当子类中有和父类中的成员(属性或方法)重名时,为了访问父类的成员,必须通过super;如果没有重名,使用super、==this、==直接访问是一样的效果。
-
super的访问不限于直接父类,如果爷爷类和本类中有同名的成员(属性或方法),也可以使用super去访问爷爷类的成员(属性和方法);
-
如果多个基类中都有同名成员,使用super访问遵循就近原则。
多态
基本介绍
- 多态是方法或对象具有多种形态,是面向对象的第三大特征。
- 多态的前提是两个对象(类)存在继承关系,多态是建立在封装和继承基础之上的。
对象的多态是多态的核心和重点。
规则
-
一个对象的编译类型与运行类型可以不一致
-
编译类型在定义对象时,就确定了,不能改变,而**运行类型是可以变化的**
-
编译类型看定义对象时 = 号的左边,运行类型看 = 号的右边
-
一个类型的编译类型和运行类型可以不一致
-
编译类型在定义对象时就确定了
-
运行类型是可以变化的
-
必须是Animal和子类中所共同拥有的方法
方法的重载和重写就是方法多态的体现
@override //表示对父类的重写
Demo
- 定义一个 Person 类 作为父类,定义 Student 类 和 Teacher 类作为子类继承父类;
- Person 类 拥有 mission() 方法;
- Student 类 和 Teacher 类 重写父类的 mission() 方法;
- 最后在 main 函数中利用多态形式创建对象。
public class duotai {
public static void main(String[] args) {
Person P = new Teacher();
P.mission();
Person P1 = new Sudent();
P1.mission();
}
}
class Person{
public void mission(){
System.out.println("Person");
}
}
class Sudent extends Person{
public void mission(){
System.out.println("Sudent");
}
}
class Teacher extends Person{
public void mission(){
System.out.println("Teacher");
}
}
运行结果
Teacher
Sudent
原理
-
那么在内存中,实际 P 对象是一个 Person 类型的引用,但是指向堆中的内存,那一块内存里面存放的是一个 Teacher 类型的对象。
-
所以,所有的方法调用都是通过 Person 类型的引用完成的,所以只能调用 Person 类中有的方法。
-
一个方法调用,因为内存中这个对象实际是 Teacher 类,所以先去Teacher中找是否重写了这个方法,重写了则直接调用 B 的方法,否则从 A 继承这个方法,相当于调用了 A 的方法。
多态的前提关系:两个对象存在继承关系
多态的向上转型
-
父类的引用指向了子类的对象
-
父类类型 引用名 = new 子类类型()
-
可以调用父类的所有成员,不能调用子类中的特有成员,
-
具体实现效果看子类的具体实现。
在编译阶段,能调用哪些类型,是由编译类型决定的
多态的向下转型
本质:一个已经向上转型的子类对象,将父类引用转为子类引用
- 语法
子类类型 引用名 = (子类类型)父类引用;
-
子类类型 引用名 = (子类类型)父类引用;
-
只能强转父类的引用,不能强转父类的对象
-
要求父类引用必须指向的是当前目标类型的对象*
-
父类可以分配子类的空间地址
cat和animal都指向一个cat对象,两者调用的对象不一样
- 可以调用子类类型中的所有成员
向上转型是为了多态,向下转型是为了特色方法
动态绑定机制
//演示动态绑定
public class DynamicBinding {
public static void main(String[] args) {
//向上转型(自动类型转换)
//程序在编译阶段只知道 p1 是 Person 类型
//程序在运行的时候才知道堆中实际的对象是 Student 类型
Person p1 = new Student();
//程序在编译时 p1 被编译器看作 Person 类型
//因此编译阶段只能调用 Person 类型中定义的方法
//在编译阶段,p1 引用绑定的是 Person 类型中定义的 mission 方法(静态绑定)
//程序在运行的时候,堆中的对象实际是一个 Student 类型,而 Student 类已经重写了 mission 方法
//因此程序在运行阶段对象中绑定的方法是 Student 类中的 mission 方法(动态绑定)
p1.mission();
}
}
//父类
class Person {
public void mission() {
System.out.println("人要好好活着!");
}
}
//子类
class Student extends Person {
@Override
public void mission() {
System.out.println("学生要好好学习!");
}
}
- 当调用对象方法时,该方法会和该对象的的内存地址/运行类型绑定——方法先到 运行类型 找,若没有就继承(到到父类找);属性在哪里声明就在哪里使用
- 当调用对象属性时,没有动态绑定机制,哪里声明,那里使用
多态数组
- 多态数组:数组的定义类型为父类类型,里面保存的实际元素类型为子类类型。
- 要使用子类的特殊方法是要进行类型判断instanceof
public class test {
public static void main(String[] args) {
Person[] arryPerson = new Person[5];
arryPerson[0] = new Person("人",0);
arryPerson[1] = new Teacher("cause", 21, 30000);
arryPerson[2] = new Student("in view", 12, 100.0);
arryPerson[3] = new Student("public",12, 99);
arryPerson[4] = new Student("realistic", 12,99.0);
for(int i = 0; i < 5;i++){
arryPerson[i].say();
}
}
}
//Teacher和Student是Person的子类
多态参数
定义
方法定义的形参类型为父类类型,实参类型允许为子类类型。
Demo
- 定义一个 Person 类 作为父类,定义 Student 类 和 Teacher 类作为子类继承父类;
- Person 类 拥有 name(姓名) 属性
- Student 类 和 Teacher 类 拥有各自 特有 的 study() 和 teach() 方法 ;
- 要求:最后在 main 函数中 编写 test() 方法 ,功能是调用 Student 类 的 study() 或 Teacher 类 的 teach() 方法,用于演示 多态参数 的使用
package polyparameter;
//演示多态参数
public class PolyParameter {
public static void main(String[] args) {
Student s1 = new Student("小蓝同学");
Teacher t1 = new Teacher("小绿老师");
//需先 new 一个当前类的实例化,才能调用 test 方法
PolyParameter polyParameter = new PolyParameter();
//实参是子类
polyParameter.test(s1);
polyParameter.test(t1);
}
//定义方法test,形参为 Person 类型(形参是父类)
//功能:调用学生的study或教师的teach方法
public void test(Person p) {
if (p instanceof Student){
((Student) p).study(); //向下转型
}
else if (p instanceof Teacher){
((Teacher) p).teach(); //向下转型
}
}
}
//父类
class Person {
private String name;
//有参构造
public Person(String name) {
this.name = name;
}
// getter 和 setter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//子类
class Student extends Person {
public Student(String name) {
super(name);
}
// study() 方法
public void study() {
System.out.println(super.getName() + "\t" + "正在好好学习");
}
}
class Teacher extends Person {
public Teacher(String name) {
super(name);
}
// teach() 方法
public void teach() {
System.out.println(super.getName() + "\t" + "正在好好教书");
}
}
//小蓝同学 正在好好学习
//小绿老师 正在好好教书
多态的优点
- 代码更加灵活:无论右边new的时候换成哪个子类对象,等号左边调用方法都不会变化。
- 提高程序的拓展性:定义方法的时候,使用父类类型作为参数,将来使用时,使用具体的子类类型操作
instanceof比较操作符
基础
为了避免上述类型转换异常的问题,我们引出 instanceof 比较操作符,用于判断对象的类型是否为XX类型或XX类型的子类型。
格式:对象 instanceof 类名称
解释:这将会得到一个boolean值结果,也就是判断前面的对象能不能当作后面类型的实例
Demo
for(int i = 0; i < 5;i++){
if(arryPerson[i] instanceof Student){
Student student = (Student)arryPerson[i];
student.student();
}
else if(arryPerson[i] instanceof Teacher){
((Teacher)arryPerson[i]).teacher();
}else {
arryPerson[i].say();
}
}
objecct类
定义
bject类是Javajava.lang
包下的核心类,Object类是所有类的父类,何一个类时候如果没有明确的继承一个父类的话,那么它就是Object的子类;
类层次的根类,每个类都使用Object作为超类,所有类都实现这个类的方法(包括数组)
以下两种类的定义的最终效果是完全相同的:
class Person { }
class Person extends Object { }
Object 类属于java.lang
包,此包下的所有类在使用时无需手动导入,系统会在程序编译期间自动导入
equals()
是Object类中的方法,只能判断引用类型
String name = Tom;
System.out.println(name.equals("Tom"));
System.out.println("Tom".equals(name)); //good 可以避免空指针
默认判断的是地址是否相等,子类中往往重写该方法,用于判断内容是否相等。比如Integer和String
//JDK源码 String的equals方法
//重写Object方法,比较两个String的值是否相等
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
//Object的equals方法
//判断两个对象是否是同一个对象
public boolean equals(Object obj) {
return (this == obj);
}
//Integer的equals方法源码
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
重写子类equals方法
public boolean equals(Object object){ //自动向上转型到object
if(this == object){
return true;
}
Agent p = (Agent)object; //将object向下转型到agent,获取agent的属性
if(this.bonus == p.bonus&&this.getName().equals(p.getName())&&this.getAnnual() == p.getAnnual()){
return true;
}
return false;
}
this是当前类的地址值
对象名是当前对象的地址值
hashCode()方法
返回该对象的哈希码值,支持此方法为了提高哈希码的性能
//Object定义的hashCode
public native int hashCode();
-
提高具有哈希结构的容器的效率
-
两个引用,如果指向的是同一个对象,则哈希值肯定是一样的
-
两个引用,如果指向的不是同一个结构,则哈希值肯定是不一样的
-
哈希值主要是根据地址值来的,但是哈希值不能完全等价与的地址值
Java是跑在虚拟机上的,没办法拿到内部地址
Object类定义的hashCode方法会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现,Java不需要这种技巧 )
public class HashCode {
public static void main(String[] args) {
Agent a1 = new Agent("sd",23,123);
Agent a2 = new Agent("as",312,31);
Agent a3;
a3 = a2;
System.out.println("a1的hashCode = " + a1.hashCode() + "\n");
System.out.println("a2的hashCode = " + a2.hashCode() + "\n");
System.out.println("a3的hashCode = " + a3.hashCode() + "\n");
}
}
finalize方法
当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用该方法
- 当对象被回收时,系统自动调用该对象的finalize方法。子类可以重写该方法,做一些释放资源的操作
- 什么时候回收:当某个对象没有任何引用时,则jvm就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,销毁该对象时,会先调用对象的finalize方法
- 垃圾回收机制的调用,是由系统决定(GC算法)的,可以通过System.gc()(建议)主动触发垃圾回收机制。
重写finalize中,写自己业务逻辑代码(比如:数据库的连接、或者打开文件)
如果不重写,调用Object中默认的finalize方法
an = null;
System.gc();
System.out.println("程序退出");
======================================
程序退出
销毁
在实际开发中,基本不会去运用——应付面试
getClass()
获取当前对象的运行类型
toString()
取得对象信息,返回该对象的字符串表示
默认返回:全类名(包名+类名) + @ + 哈希值的十六进制(查看Object的toStrng方法)
子类往往会重写toString方法,用于返回对象的属性信息
//Object中定义的toStrng方法
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
//使用Object类中对的toString()方法
System.out.println(a1.toString());
System.out.println(a1.hashCode());
=====================================
com.demo3.Agent@1b6d3586 //包名+类名+@+hashCode的十六进制
460141958
当直接输出一个对象时,toString方法会默认被调用–Xxx.toString();
public static void main(String[] args) {
Agent an = new Agent("sad",12,21);
System.out.println(an);
}
==================================================
Agent{bonus=21.0}//本类定义的属性,不继承父类的
类变量与类方法
类变量-静态变量
被类对象所共享!
在main中引入count变量,发现count是独立于对象的,访问count很麻烦,引入类变量/静态变量——让count成为所有类对象的共享
//在类中定义类变量/静态变量
public static int count = 0;
//类对象共享
System.out.println(child1.count);
System.out.println(child2.count);
System.out.println(child3.count);
============================================
child1.count == child2.count == child3.count
-
static变量是同一个类的所有对象共享
-
static变量,在类加载的时候就生成了
-
类什么时候加载
类的加载是通过类加载器(Classloader)完成的,它既可以是饿汉式eagerly load加载类,也可以是懒加载lazy load。
JVM内,每个类只对应一个Class对象,但每个类可以创建多个Java对象。
类变量语法
访问修饰符 static 数据类型 变量名;——推荐
static 访问修饰符 数据类型 变量名;
访问类变量
类名.变量名;——推荐
对象名.变量名;
即使没有创建对象实例也可以,类在方法区加载的时候就生成了
类变量细节
-
什么时候需要使用类变量
当我们需要让某个类的所有对象都共享一个变量的时候,就可以考虑使用类变量
-
类变量与实例变量的区别
类变量是所有对象共享的,实例变量是每个对象独有的
类变量的生命周期是随类的加载开始,随类的消亡而销毁
类方法-静态方法
类方法也叫静态方法。
不创建实例可以调用方法——静态方法
往往会将一些通用的方法,设计出静态方法,这样我们不需要创建对象就可以使用——打印一系列数组、冒泡排序…
-
类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区,类方法中无this的参数
普通方法隐藏this参数
-
类方法可以通过类名调用,也可以通过对象名调用
-
类方法不允许使用与对象有关的关键字——super、this
-
类方法中,只能访问 静态变量 和 静态方法
非静态方法既可以访问静态成员又可以访问非静态成员,而静态方法只能访问静态成员;
非静态方法既可以访问静态方法又可以访问非静态方法,而静态方法只能访问静态方法。
原因:因为静态方法和静态数据成员会随着类的定义而被分配和装载入内存中;而非静态方法和非静态数据成员只有在类的对象创建时在对象的内存中才有这个方法的代码段。
- 虚拟机在首次加载Java类时,会对静态代码块、静态成员变量、静态方法进行一次初始化(静态间按顺序执行)。
- 只有在调用new方法时才会创建类的实例。
- 类实例创建过程:父子继承关系,先父类再子类。父类的静态->子类的静态->父类的初始化块->父类的构造方法->子类的初始化块->子类的构造方法
- 类实例销毁时候:首先销毁子类部分,再销毁父类部分。
当类的字节码文件被加载到内存时,类的实例方法不会被分配入口地址,当该类创建对象后,类中的实例方法才分配入口地址,从而实例方法可以被类创建的任何对象调用执行。需要注意的是,当我们创建第一个对象时,类中的实例方法就分配了入口地址,当再创建对象时,不再分配入口地址,也就是说,方法的入口地址被所有的对象共享,当所有的对象都不存在时,方法的入口地址才被取消。
对于类中的类方法,在该类被加载到内存时,就分配了相应的入口地址。从而类方法不仅可以被类创建的任何对象调用执行,也可以直接通过类名调用。类方法的入口地址直到程序退出才被取消。
类方法在类的字节码加载到内存时就分配了入口地址,因此,Java语言允许通过类名直接调用类方法,而实例方法不能通过类名调用。在讲述类的时候我们强调过,在Java语言中,类中的类方法不可以操作实例变量,也不可以调用实例方法,这是因为在类创建对象之前,实例成员变量还没有分配内存,而且实例方法也没有入口地址。
继承与静态方法
通过子类调用父类的静态变量或者静态方法,那么表示为对父类的主动使用,而不是子类的主动使用
静态变量或者静态方法定义在谁身上就表示对谁的主动使用,而不看调用方,所以子类并不会被初始化
//acesss modifier default 静态代码块
static{
System.out.println("MyChild11 loading");
}//加载类时直接执行
main方法
public static void mian(String[] args){
for(int i = 0; i < args.length; i++){
System.out.println(i);
}
}
//args数组可以通过命令行传进去
//Java 执行的程序 参数1 参数2 参数3 ——————命令行传参
在idea中传参
代码块
定义
代码块又称为初始化块,属于类中的成员,类使用类方法,将逻辑语句封装在方法体中,通过{}包围起来
与方法不同,没有方法名,没有返回值,没有参数,只有方法体,而且不用同各国对象或类显式调用,而是加载类时——static 代码块,或创建对象时隐式调用
基本语法
//基本语法
[修饰符]{
代码;
};
1.;可以写上,也可以忽略
2. 逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断等)
3. 代码块分为静态代码块和普通代码块
- 相当于另外一种形式的构造器,可以做初始化操作
- 如果多个构造器有重复语句,可以抽取到初始化块中,他搞代码的重用性
代码块的调用的顺序优先于构造器!!!
注意细节
-
static 代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次。如果是普通代码块,每创建一个对象,就执行。
-
类什么时候被加载
- 创建子类对象实例时(new)
- 创建子类对象实例,父类会被加载(先有父,后有子)
- 使用类的静态成员时(静态属性、静态方法)
-
普通的代码块,只有在创建对象实例时,会被隐式的调用。
被创建一次,就会调用一次
如果只是使用类的静态成员时,普通代码块不会执行
要加载子类,先加载父类。使用子类的静态属性或方法也是如此
//static代码块是只会在类加载的时候执行,并且只执行一次
-
创建一个对象时,在一个类中 调用的顺序是:
- 调用静态代码块和静态属性初始化——等级相同,按顺序执行
- 调用普通代码块和普通属性初始化——等级相同,按顺序执行
- 调用构造方法
-
构造器 在最前面其实隐含了super()和调用普通代码块。静态相关的代码块,属性初始化,在类加载时就执行完毕,因此是优先于构造器和普通代码块执行的
-
创建一个子类时,静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序
-
父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
-
子类的静态代码块和静态属性
-
父类的普通代码块和普通属性初始化
-
父类的构造方法
-
子类的普通代码块和普通属性的初始化
-
子类的构造方法
-
-
静态代码块只能调用静态成员,普通代码块只能调用普通成员
创建对象流程
先加载父类——再加载子类——进入父类无参构造器——父类super()——父类普通代码块和普通属性初始化——父类构造器——子类的代码块和普通属性初始化——子类构造器
Demo
public class test_ {
public static void main(String[] args) {
System.out.println(Class_.age);
}
}
class Class_{
public static int age = 0;
static {
age = 100;
System.out.println(age);
}
}
=================================;
100
100
public class test_ {
public static void main(String[] args) {
DDD ddd = new DDD();
}
}
class CCC{
CCC(){
System.out.println("父类的无参构造器");
}
public static int age;
static {
System.out.println("父类的静态代码块");
}
{
System.out.println("父类的普通代码块");
}
}
class DDD extends CCC{
DDD(){
super();
System.out.println("子类的无参构造器");
}
public static int qwe;
static{
System.out.println("子类的静态代码块");
}
{
System.out.println("子类的普通代码块");
}
}
=========================================================;
1. 父类的静态代码块
2. 子类的静态代码块
3. 父类的普通代码块
4. 父类的构造器
5. 子类的普通代码块
6. 子类的构造器
单例设计模式
所谓类的单例模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个实例对象,并且只提供一个取得其对象实例的方法
将创建对象的过程封装在类在——构造器私有化
饿汉式
- 构造器私有化
- 在类的内部创建对象
- 向外保露一个静态的公共方法
class Example{
//私有化构造器
private Example(){}
//在类内创建对象
private static Example example = new Example();
//向外保留一个静态的公共方法
public static Example getExample(){
return example;
}
}
优缺点:
优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
这种单例模式可用,可能造成内存浪费
是线程安全的
类加载的时候执行
JVM 保证了类加载的过程是线程安全的
懒汉式
存在多线程冲突问题
class Example2{
private Example2(){}
private static Example2 example2;//先不创建实例
public static Example2 getExample(){//需要时再进行调用创建
if(example2 == null){
example2 = new Example2();
}
return example2;
}
}
优缺点:
起到了Lazy Loading的效果,但是只能在单线程下使用。
如果在多线程下,一个线程进入了if (instance == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。
在实际开发中,不要使用这种方式。
final关键字
概念
- 当不希望类被继承时,可以用final修饰类
- 当不希望父类的某个方法被子类覆盖/重写(override)时,可以用final关键字修饰。
- 当不希望类的某个属性被修改,使用final关键字
- 当不希望某个局部变量被修改,使用final关键字
final细节
-
final修饰的属性又叫常量,一般用XXX_XXX_XXX来命名
-
final修饰的属性再定义时,必须赋初始值,并且不能再被修改。
赋值可在如下位置之一
定义时
public final double PIN = 3.1415926;
在构造器中
public final double aaa;
public AAA{
aaa = 2.2
}
在代码块中
public final double aaa;
{
aaa = 2.2;
}
如果final修饰的属性是静态的,则初始化的位置只能是静态中
定义时
public static final double bbb = 1.0;
静态代码块中
private final static double bbb;
static{
bbb = 1.00;
}
-
final类不可以继承,但是可以实例化对象
-
如果类是不final类,但是含有final方法,则该方法虽然不能进行重写,但是可以被继承
-
final不能修饰构造方法
-
final和static搭配使用效率更高,不会导致类加载
-
包装类(Integer\Double\Float\Boolean)都是final,String也是final类
class Demo{
public static final int i = 16;
static{
System.out.println("==================")
}
}
======================================
就使用Demo中的i
System.out.println(Demo.i);
不加载类
===========================
16
抽象类
定义
在所有的普通方法上面都会有一个“{}”,这个表示方法体,有方法体的方法一定可以被对象直接使用。而抽象方法,是指没有方法体的方法,同时抽象方法还必须使用关键字abstract做修饰。
而拥有抽象方法的类就是抽象类,抽象类要使用abstract关键字声明。
Demo
abstract class A{//定义一个抽象类
public void fun(){//普通方法
System.out.println("存在方法体的方法");
}
public abstract void print();//抽象方法,没有方法体,有abstract关键字做修饰
}
抽象类的使用
- 抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public;
- 抽象类不能直接实例化,需要依靠子类采用向上转型的方式处理;
- 抽象类必须有子类,使用extends继承,一个子类只能继承一个抽象类;
- 子类(如果不是抽象类)则必须覆写抽象类之中的全部抽象方法(如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。);
抽象类的细节
-
抽象类继承子类里面有明确的方法覆写要求,而普通类可以有选择性的来决定是否需要覆写;
-
抽象类实际上就比普通类多了一些抽象方法而已,其他组成部分和普通类完全一样;
-
普通类对象可以直接实例化,但抽象类的对象必须经过向上转型之后才可以得到。
-
虽然一个类的子类可以去继承任意的一个普通类,可是从开发的实际要求来讲,普通类尽量不要去继承另外一个普通类,而是去继承抽象类。
抽象类的使用限制
-
抽象类中有构造方法么?
由于抽象类里会存在一些属性,那么抽象类中一定存在构造方法,其存在目的是为了属性的初始化。
并且子类对象实例化的时候,依然满足先执行父类构造,再执行子类构造的顺序。
package com.wz.abstractdemo;
abstract class A{//定义一个抽象类
public A(){
System.out.println("*****A类构造方法*****");
}
public abstract void print();//抽象方法,没有方法体,有abstract关键字做修饰
}
//单继承
class B extends A{//B类是抽象类的子类,是一个普通类
public B(){
System.out.println("*****B类构造方法*****");
}
@Override
public void print() {//强制要求覆写
System.out.println("Hello World !");
}
}
public class TestDemo {
public static void main(String[] args) {
A a = new B();//向上转型
}
}
-
抽象类可以用final声明么?
不能,因为抽象类必须有子类,而final定义的类不能有子类; -
抽象类能否使用static声明?
外部抽象类不允许使用static声明,而内部的抽象类运行使用static声明。使用static声明的内部抽象类相当于一个外部抽象类,继承的时候使用“外部类.内部类”的形式表示类名称。
package com.wz.abstractdemo;
abstract class A{//定义一个抽象类
static abstract class B{//static定义的内部类属于外部类
public abstract void print();
}
}
class C extends A.B{
public void print(){
System.out.println("**********");
}
}
public class TestDemo {
public static void main(String[] args) {
A.B ab = new C();//向上转型
ab.print();
}
}
- 可以直接调用抽象类中用static声明的方法么?
任何时候,如果要执行类中的static方法的时候,都可以在没有对象的情况下直接调用,对于抽象类也一样。
package com.wz.abstractdemo;
abstract class A{//定义一个抽象类
public static void print(){
System.out.println("Hello World !");
}
}
public class TestDemo {
public static void main(String[] args) {
A.print();
}
}
- 有时候由于抽象类中只需要一个特定的系统子类操作,所以可以忽略掉外部子类。这样的设计在系统类库中会比较常见,目的是对用户隐藏不需要知道的子类。
package com.wz.abstractdemo;
abstract class A{//定义一个抽象类
public abstract void print();
private static class B extends A{//内部抽象类子类
public void print(){//覆写抽象类的方法
System.out.println("Hello World !");
}
}
//这个方法不受实例化对象的控制
public static A getInstance(){
return new B();
}
}
public class TestDemo {
public static void main(String[] args) {
//此时取得抽象类对象的时候完全不需要知道B类这个子类的存在
A a = A.getInstance();
a.print();
}
}
模板设计模式
例如,现在有三类事物:
(1)机器人:充电,工作;
(2)人:吃饭,工作,睡觉;
(3)猪:进食,睡觉。
现要求实现一个程序,可以实现三种不同事物的行为。
先定义一个抽象行为类:
package com.wz.abstractdemo;
public abstract class Action{
public static final int EAT = 1 ;
public static final int SLEEP = 3 ;
public static final int WORK = 5 ;
public abstract void eat();
public abstract void sleep();
public abstract void work();
public void commond(int flags){
switch(flags){
case EAT:
this.eat();
break;
case SLEEP:
this.sleep();
break;
case WORK:
this.work();
break;
case EAT + SLEEP:
this.eat();
this.sleep();
break;
case SLEEP + WORK:
this.sleep();
this.work();
break;
default:
break;
}
}
}
定义一个机器人的类:
package com.wz.abstractdemo;
public class Robot extends Action{
@Override
public void eat() {
System.out.println("机器人充电");
}
@Override
public void sleep() {
}
@Override
public void work() {
System.out.println("机器人工作");
}
}
定义一个人的类:
package com.wz.abstractdemo;
public class Human extends Action{
@Override
public void eat() {
System.out.println("人吃饭");
}
@Override
public void sleep() {
System.out.println("人睡觉");
}
@Override
public void work() {
System.out.println("人工作");
}
}
定义一个猪的类:
package com.wz.abstractdemo;
public class Pig extends Action{
@Override
public void eat() {
System.out.println("猪进食");
}
@Override
public void sleep() {
System.out.println("猪睡觉");
}
@Override
public void work() {
}
}
测试主类:
package com.wz.abstractdemo;
public class AbstractDemo {
public static void main(String[] args) {
fun(new Robot());
fun(new Human());
fun(new Pig());
}
public static void fun(Action act){
act.commond(Action.EAT);
act.commond(Action.SLEEP);
act.commond(Action.WORK);
}
}
运行结果:
机器人充电
机器人工作
人吃饭
人睡觉
人工作
猪进食
猪睡觉
接口
概念
接口可以理解为一种特殊的类,里面全部是由全局常量和公共的抽象方法所组成。接口是解决Java无法使用多继承的一种手段,但是接口在实际中更多的作用是制定标准的。或者我们可以直接把接口理解为100%的抽象类,既接口中的方法必须全部是抽象方法
接口的特点
- 就像一个类一样,一个接口也能够拥有方法和属性,但是在接口中声明的方法默认是抽象的。(即只有方法标识符,而没有方法体)。
- 接口指明了一个类必须要做什么和不能做什么,相当于类的蓝图。
一个接口就是描述一种能力,比如“运动员”也可以作为一个接口,并且任何实现“运动员”接口的类都必须有能力实现奔跑这个动作(或者implement move()方法),所以接口的作用就是告诉类,你要实现我这种接口代表的功能,你就必须实现某些方法,我才能承认你确实拥有该接口代表的某种能力。 - 如果一个类实现了一个接口中要求的所有的方法,然而没有提供方法体而仅仅只有方法标识,那么这个类一定是一个抽象类。(必须记住:抽象方法只能存在于抽象类或者接口中,但抽象类中却能存在非抽象方法,即有方法体的方法。接口是百分之百的抽象类)
-
接口不能被实例化
-
接口中所有方法是public方法
-
接口中的抽象方法可以不用abstract进行修饰
-
一个普通类实现接口,就必须将该接口的所有方法都实现
-
抽象类实现接口,可以不用实现接口的方法
-
一个类同时可以实现多个接口
class AAA implements BBB, CCC,DDD;
-
接口中的属性只能是final,而且是public static final modifier
-
接口中属性的访问形式:接口名.属性名
-
接口中不能继承其他类,但可以继承多个别的接口
interface AAA extends BBB,CCC{}
接口被用来描述一种抽象。
- 因为Java不像C++一样支持多继承,所以Java可以通过实现接口来弥补这个局限。
- 接口也被用来实现解耦。
- 接口被用来实现抽象,而抽象类也被用来实现抽象,为什么一定要用接口呢?接口和抽象类之间又有什么区别呢?原因是抽象类内部可能包含非final的变量,但是在接口中存在的变量一定是final,public,static的。
Demo
先声明USB接口:其中规定了要实现USB接口就必须实现接口规定实现的read( )和write( )这两个方法。
interface USB {
void read();
void write();
}
然后在写一个U盘类和一个键盘类,这两个类都去实现USB接口。(实现其中的方法)
class YouPan implements USB {
@Override
public void read() {
System.out.println("U盘正在通过USB功能读取数据");
}
@Override
public void write() {
System.out.println("U盘正在通过USB功能写入数据");
}
}
//这是U盘的具体实现。
class JianPan implements USB {
@Override
public void read() {
System.out.println("键盘正在通过USB功能读取数据");
}
@Override
public void write() {
System.out.println("键盘正在通过USB功能写入数据");
}
}
//这是键盘的具体实现。
public class Main {
public static void main(String[] args) {
//生成一个实现可USB接口(标准)的U盘对象
YouPan youPan = new YouPan();
//调用U盘的read( )方法读取数据
youPan.read();
//调用U盘的write( )方法写入数据
youPan.write();
//生成一个实现可USB接口(标准)的键盘对象
JianPan jianPan = new JianPan();
//调用键盘的read( )方法读取数据
jianPan.read();
//调用键盘的write( )方法写入数据
jianPan.write();
}
}
/*U盘正在通过USB功能读取数据
U盘正在通过USB功能写入数据
键盘正在通过USB功能读取数据
键盘正在通过USB功能写入数据*/
接口vs继承类
子类继承父类,就自动拥有父类的功能
如果子类需要扩展功能,可以通过实现接口的方式扩展
继承的价值在于:解决代码的复用性和可维护性
接口的价值在于:设计好各种规范,让其它类去实现这些方法
接口与抽象类
抽象类
抽象类体现了数据抽象的思想,是实现多态的一种机制。它定义了一组抽象的方法,至于这组抽象方法的具体表现形式由派生类来实现。同时抽象类提供了继承的概念,它的出发点就是为了继承,否则它没有存在的任何意义。所以说定义的抽象类一定是用来继承的,同时在一个以抽象类为节点的继承关系等级链中,叶子节点一定是具体的实现类。
在语法方面:
1.由abstract关键词修饰的类称之为抽象类。
2.抽象类中没有实现的方法称之为抽象方法,也需要加关键字abstract。
3.抽象类中也可以没有抽象方法,比如HttpServlet方法。
4.抽象类中可以有已经实现的方法,可以定义成员变量。
接口
接口是用来建立类与类之间的协议,它所提供的只是一种形式,而没有具体的实现。同时实现该接口的实现类必须要实现该接口的所有方法,通过使用implements关键字。 接口是抽象类的延伸,java为了保证数据安全是不能多重继承的,也就是说继承只能存在一个父类,但是接口不同,一个类可以同时实现多个接口,不管这些接口之间有没有关系,所以接口弥补了抽象类不能多重继承的缺陷,
语法方面:
1.由interface关键词修饰的称之为接口;
2.接口中可以定义成员变量,但是这些成员变量默认都是public static final的常量。
3.接口中没有已经实现的方法,全部是抽象方法。
4.一个类实现某一接口,必须实现接口中定义的所有方法。
5.一个类可以实现多个接口。
接口的多态
多态参数:usb,既可以接受手机对象,又可以接受电脑对象,体现了对象的多态(接口的引用可以指向实现了接口的类的对象)
多态数组:
接口相当于特殊的抽象类_向上转型,向下转型
Use[] uses = new Use[2];
uses[0] = Phone();
uses[1] = Camera();
接口多态传递
interface AAA{}
interface BBB extends AAA{}
class CCC implements BBB{
//CCC要实现AAA和BBB的所有抽象方法
}
===================
AAA aaa = new CCC();
内部类
普通内部类
静态内部类
匿名内部类
局部内部类
调用系统方法
生成随机数函数
Math.radom();
* 由于Math类在java.lang包下,所以不需要导包
* 特点:没有构造方法,因为它的成员全部是静态的
* 掌握一个方法:
* 获取随机数
* public static double random();返回 带正号的double值 ,该值大于等于0.0且小于1.0
格式化时间
日期包
import java.util.Date;
Date date = null;
date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh-mm");//用于日期格式化
sdf.format(date);
e
public void work() {
}
}
**测试主类:**
```java
package com.wz.abstractdemo;
public class AbstractDemo {
public static void main(String[] args) {
fun(new Robot());
fun(new Human());
fun(new Pig());
}
public static void fun(Action act){
act.commond(Action.EAT);
act.commond(Action.SLEEP);
act.commond(Action.WORK);
}
}
运行结果:
机器人充电
机器人工作
人吃饭
人睡觉
人工作
猪进食
猪睡觉
接口
概念
接口可以理解为一种特殊的类,里面全部是由全局常量和公共的抽象方法所组成。接口是解决Java无法使用多继承的一种手段,但是接口在实际中更多的作用是制定标准的。或者我们可以直接把接口理解为100%的抽象类,既接口中的方法必须全部是抽象方法
接口的特点
- 就像一个类一样,一个接口也能够拥有方法和属性,但是在接口中声明的方法默认是抽象的。(即只有方法标识符,而没有方法体)。
- 接口指明了一个类必须要做什么和不能做什么,相当于类的蓝图。
一个接口就是描述一种能力,比如“运动员”也可以作为一个接口,并且任何实现“运动员”接口的类都必须有能力实现奔跑这个动作(或者implement move()方法),所以接口的作用就是告诉类,你要实现我这种接口代表的功能,你就必须实现某些方法,我才能承认你确实拥有该接口代表的某种能力。 - 如果一个类实现了一个接口中要求的所有的方法,然而没有提供方法体而仅仅只有方法标识,那么这个类一定是一个抽象类。(必须记住:抽象方法只能存在于抽象类或者接口中,但抽象类中却能存在非抽象方法,即有方法体的方法。接口是百分之百的抽象类)
-
接口不能被实例化
-
接口中所有方法是public方法
-
接口中的抽象方法可以不用abstract进行修饰
-
一个普通类实现接口,就必须将该接口的所有方法都实现
-
抽象类实现接口,可以不用实现接口的方法
-
一个类同时可以实现多个接口
class AAA implements BBB, CCC,DDD;
-
接口中的属性只能是final,而且是public static final modifier
-
接口中属性的访问形式:接口名.属性名
-
接口中不能继承其他类,但可以继承多个别的接口
interface AAA extends BBB,CCC{}
接口被用来描述一种抽象。
- 因为Java不像C++一样支持多继承,所以Java可以通过实现接口来弥补这个局限。
- 接口也被用来实现解耦。
- 接口被用来实现抽象,而抽象类也被用来实现抽象,为什么一定要用接口呢?接口和抽象类之间又有什么区别呢?原因是抽象类内部可能包含非final的变量,但是在接口中存在的变量一定是final,public,static的。
Demo
先声明USB接口:其中规定了要实现USB接口就必须实现接口规定实现的read( )和write( )这两个方法。
interface USB {
void read();
void write();
}
然后在写一个U盘类和一个键盘类,这两个类都去实现USB接口。(实现其中的方法)
class YouPan implements USB {
@Override
public void read() {
System.out.println("U盘正在通过USB功能读取数据");
}
@Override
public void write() {
System.out.println("U盘正在通过USB功能写入数据");
}
}
//这是U盘的具体实现。
class JianPan implements USB {
@Override
public void read() {
System.out.println("键盘正在通过USB功能读取数据");
}
@Override
public void write() {
System.out.println("键盘正在通过USB功能写入数据");
}
}
//这是键盘的具体实现。
public class Main {
public static void main(String[] args) {
//生成一个实现可USB接口(标准)的U盘对象
YouPan youPan = new YouPan();
//调用U盘的read( )方法读取数据
youPan.read();
//调用U盘的write( )方法写入数据
youPan.write();
//生成一个实现可USB接口(标准)的键盘对象
JianPan jianPan = new JianPan();
//调用键盘的read( )方法读取数据
jianPan.read();
//调用键盘的write( )方法写入数据
jianPan.write();
}
}
/*U盘正在通过USB功能读取数据
U盘正在通过USB功能写入数据
键盘正在通过USB功能读取数据
键盘正在通过USB功能写入数据*/
接口vs继承类
子类继承父类,就自动拥有父类的功能
如果子类需要扩展功能,可以通过实现接口的方式扩展
继承的价值在于:解决代码的复用性和可维护性
接口的价值在于:设计好各种规范,让其它类去实现这些方法
接口与抽象类
抽象类
抽象类体现了数据抽象的思想,是实现多态的一种机制。它定义了一组抽象的方法,至于这组抽象方法的具体表现形式由派生类来实现。同时抽象类提供了继承的概念,它的出发点就是为了继承,否则它没有存在的任何意义。所以说定义的抽象类一定是用来继承的,同时在一个以抽象类为节点的继承关系等级链中,叶子节点一定是具体的实现类。
在语法方面:
1.由abstract关键词修饰的类称之为抽象类。
2.抽象类中没有实现的方法称之为抽象方法,也需要加关键字abstract。
3.抽象类中也可以没有抽象方法,比如HttpServlet方法。
4.抽象类中可以有已经实现的方法,可以定义成员变量。
接口
接口是用来建立类与类之间的协议,它所提供的只是一种形式,而没有具体的实现。同时实现该接口的实现类必须要实现该接口的所有方法,通过使用implements关键字。 接口是抽象类的延伸,java为了保证数据安全是不能多重继承的,也就是说继承只能存在一个父类,但是接口不同,一个类可以同时实现多个接口,不管这些接口之间有没有关系,所以接口弥补了抽象类不能多重继承的缺陷,
语法方面:
1.由interface关键词修饰的称之为接口;
2.接口中可以定义成员变量,但是这些成员变量默认都是public static final的常量。
3.接口中没有已经实现的方法,全部是抽象方法。
4.一个类实现某一接口,必须实现接口中定义的所有方法。
5.一个类可以实现多个接口。
接口的多态
多态参数:usb,既可以接受手机对象,又可以接受电脑对象,体现了对象的多态(接口的引用可以指向实现了接口的类的对象)
多态数组:
接口相当于特殊的抽象类_向上转型,向下转型
Use[] uses = new Use[2];
uses[0] = Phone();
uses[1] = Camera();
接口多态传递
interface AAA{}
interface BBB extends AAA{}
class CCC implements BBB{
//CCC要实现AAA和BBB的所有抽象方法
}
===================
AAA aaa = new CCC();
内部类
普通内部类
静态内部类
匿名内部类
局部内部类
调用系统方法
生成随机数函数
Math.radom();
* 由于Math类在java.lang包下,所以不需要导包
* 特点:没有构造方法,因为它的成员全部是静态的
* 掌握一个方法:
* 获取随机数
* public static double random();返回 带正号的double值 ,该值大于等于0.0且小于1.0
格式化时间
日期包
import java.util.Date;
Date date = null;
date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh-mm");//用于日期格式化
sdf.format(date);