基础
# java 语言特性
- 简单性:相对于其他编程语言而言,java 较为简单,例如:java 不再支持多继承,C++ 是支持多继承的,多继承比较复杂,C++ 中有指针,java 中屏蔽了指针的概念,避免了绝大部分的指针越界和内存泄露的问题,这里说明一下,java 语言低层是用 C++ 实现的,并不是 C 语言。
- 面向对象:java 是纯面向对象的,更符合人的思维模式,易于理解。
- 健壮性:java 的健壮性与自动垃圾回收机制有关,自动垃圾回收机制简称 GC 机制,java 语言运行过程中产生的垃圾是自动回收的,不需要程序员关心。
- 可移植性:java 程序可以做到一次编译,到处运行。在 Windows 操作系统上运行的 java 程序,不做任何修改,可以直接放到 Linux 操作系统上运行,这个被称为 java 程序的可移植性(跨平台)。java 的跨平台性是通过 JVM(java 虚拟机)实现的,java 代码不直接与底层操作系统打交道,而是通过 JVM 这个中间介质间接与底层操作系统交互,JVM 屏蔽了各操作系统之间的差异,不同版本的操作系统就有不同版本的 JVM,只有在 JVM 这个环境下的 java 程序才能运行。
- 多线程
# java 的编译与运行
# JDK、JRE、JVM
- JDK:java 开发工具包
- JRE:java 运行时环境
- JVM:java 虚拟机
说明:
1、JDK 和 JRE 都可以单独安装,若不需要开发 Java 程序,只需要运行的话,那么,只用安装 JRE 即可。
2、JVM 不能单独安装。
以下为三者关系图:
# 字符编码
为了让计算机可以表示现实世界中的文字,需要人提前制定好 “文字” 和“二进制”之间的对照关系,这种对照转换关系被称为“字符编码”。
- ASCII 码:采用一个字节编码,主要针对英文编码
- GBK:主要是简体汉字编码
- Unicode: 统一了全世界的所有文字编码,采用这三种方式实现(UTF-8、UTF-16、UTF-32)
说明:
1、Java 采用 Unicode 编码方式
2、在实际开发中,一般使用 UTF-8 编码方式实现
# 数据类型
- Java 程序中最基本的单位是类
- Java 中变量赋值时必须类型对应,否则不兼容,编译不过,可用强转
- Java 中局部变量不赋值不能使用
说明:整数默认为 int,浮点数默认为 double,布尔类型默认为 false
# 数据类型取值范围
# 数据类型默认转换
byte、short、char-->int-->long-->float-->double
注:为什么 long 比 float 所占字节长度更大却是 long 转换为 float 呢?
因为它们底层的存储结构不同,float 表示的数据范围要比 long 的范围更大。
# 标识符命名方法
- 小驼峰命名法:标识符是由一个单词组成时首字母小写,多个单词组成时第一个单词首字母小写,其余单词首字母大写。
- 大驼峰命名法:标识符是由一个单词组成时首字母大写,多个单词组成时每一个单词首字母大写。
说明:小驼峰命名法用于方法、变量的命名,大驼峰命名法用于类的命名。
# 数组
# 一维数组 (opens new window)
格式如下:
/*动态初始化*/
数组类型[] 数组名 = new 数组类型[元素个数(数组长度)];
/*静态初始化*/
数组类型[] 数组名 = {元素1, 元素2, 元素3, .....} = new 数组类型[]{元素1, 元素2, 元素3, .....};
2
3
4
5
举例:
String[] array1 = {"abc", "a", "ad"};//字符串数组中不能有字符('')
char[] array2 = {'A', 'b'};//字符数组中不能有字符串("")
int[] array3 = {23, 12, 90};
2
3
说明:以上例子中可以把 “[]” 放在数组名后面(array1[]), 但是 Java 中不建议这样,放在数组名前面更易于理解。
- 用 “数组名. length” 的方式来获得数组长度
- 字符数组默认初始值为一个空字符(“\u000”)
- boolean 数组默认初始值为 false,其余数组默认值为 0
- Java 中的数组必须先初始化(赋值后)才能使用
# 二维数组 (opens new window)
格式如下:
/*动态初始化*/
写法一:数据类型[][] 数组名 = new 数据类型[m][n];//m表示这个二维数组有m个一维数组, n表示每一个一维数组有n个元素
写法二:数据类型 数组名[][] = new 数据类型[m][n];
写法三:数据类型[] 数组名[] = new 数据类型[m][n];
数据类型[][] 数组名 = new 数据类型[m][];//如果没有给出每个一维数组的元素个数,就表示这个二维数组是变化的,但不能没有一维数组的个数
/*静态初始化*/
数据类型[][] 数组名 = {{元素...}, {元素...}, {元素...}......} = new 数据类型[][] {{元素...}, {元素...}, {元素...}......};
/*注明*/
int[] x, y[];//x是一维数组,y是二维数组
2
3
4
5
6
7
8
9
10
11
# 数组排序算法
- 选择排序:把 0 索引的元素和 1 索引后的元素进行比较
- 冒泡排序:相邻元素比较
# 数组查找算法
- 二分查找(折半查找):针对数组有序的情况,切记不要将已给出的数组先排序后再查找
# 数组工具类(Arrays)
- 数组转字符串功能
public static String toString(int[] array);
- 快速排序功能
public static void sort(int[] array);
- 二分查找功能
public static int binarySearch(int[] array, int key);
# 逻辑运算符 (opens new window)
- 短路运算符:“&&” 、 “||”
- 非短路运算符:“&” 、 "|"
- 短路运算符与非短路运算符的区别:短路运算符两边表达式有可能只执行一边的表达式,效率较高,最常用;非短路运算符两边表达式无论什么情况都会执行。
- 最最常用的逻辑运算符:“&&” 、 “||” 、 "!"
# 输入操作
步骤如下:
- 导包(必须放在类 class 与包名 package 的中间)
import java.util.Scanner;
- 创建 Scanner 对象
Scanner input = new Scanner(System.in);
- 接收数据
int n = input.nextInt();
注:
整型:input.nextInt();
单精度浮点型:input.nextFloat();
双精度浮点型:input.nextDouble();
字符串类型:input.nextLine();//这种写法可以得到带空格的字符串
input.next();//此写法在读取内容时会过滤掉有效字符前面的无效字符,所以这种写法不能得到带空格的字符串
短整型:input.nextShort();
字节型:input.nextByte();
字符型:input.next().charAt(i);//i是指从用户输入的字符串中选取第i个单个字符输入到内存中,i从0开始
2
3
4
5
6
7
8
# Java 中的命名规则
- 包:实质上是文件夹,用于保证类名的唯一性(区分相同类名)。为了保证包名的唯一性,一般采用公司域名以逆序的形式作为包名,然后对于不同的工程使用不同的子包(域名. 项目名. 模块名)例:com.horstmann.corejava,且包名全部小写
- 类或接口:大驼峰命名法
- 方法或变量:小驼峰命名法
- 常量: 一个单词组成时全部大写,多个单词组成时每个单词大写且单词间用_隔开
- 二进制数:以 0b 开头
- 八进制数:以 0 开头
- 十六进制数:以 0x 开头
# 有符号数据表示法
在计算机内,有符号数据有三种表示法:原码、反码、补码,所有数据的运算都是采用补码进行的
- 原码:二进制定点表示法,即最高位为符号位,“0” 表示正,“1” 表示负,其余位数表示数值大小
- 反码:正数的反码与其原码相同,负数的反码是对其原码逐位取反,但符号位除外
- 补码:正数的补码与其原码相同,负数的补码是在其反码的末位加 1
# 面向对象与面向过程
面向过程:主要关注实现的具体过程,语句之间是因果关系
- 优点:对于业务逻辑比较简单的程序,可以达到快速开发,前期投入成本较低
- 缺点:采用面向过程的方式开发很难解决非常复杂的业务逻辑,另外面向过程的方式导致软件元素之间的耦合度非常高,只要其中一环出现问题整个系统都将受到影响,导致最终的软件扩展力差,由于没有对象的概念,所以无法达到组件复用
面向过程程序设计也称作结构化程序设计或结构化编程,它是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用时一个一个依次调用即可。
面向对象:它是基于面向过程的编程思想,也是一种思考问题的方式,主要关注对象能完成哪些功能, 这种思维方法其实就是我们在现实生活中习惯的思维方式,是从人类考虑问题的角度出发,把人类解决问题的思维方式逐步翻译成程序能够理解的思维方式的过程。
面向对象是把构成问题的事物分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描述某个事物在整个解决问题的步骤中的行为
- 优点:一种更符合我们思想习惯的思想,将我们从执行者变成指挥者,从而达到将复杂问题简单化的目的。耦合度低,扩展力强,更容易解决现实世界中复杂的业务逻辑,组件复用性强
- 缺点:前期投入成本高,需要进行对象的抽取,大量的系统分析与设计
- 如何建立面向对象思维呢?1、首先分析有哪些类;2、分析每个类应该有什么;3、最后分析类与类之间的关系
- 面向对象开发就是不断的创建对象、使用对象、指挥对象做事情
- 面向对象设计就是在管理和维护对象之间的关系
- 所有面向对象的编程语言都具有的三大特征:封装、继承、多态
注:采用面向对象的方式开发一个软件的生命周期
- 面向对象的分析(OOA)
- 面向对象的设计(OOD)
- 面向对象的编程(OOP)
# 包
- 包的定义:包其实就是文件夹
- 包的作用:1、把相同的类名放到不同包中;2、对类进行分类管理(分类方案:1、按照功能分,如增删改查;2、按照模块分,如老师、学生,每个模块中有对应的功能)
- package 语句必须是程序的第一条可执行的代码,注释除外
- package 语句在一个 java 文件中只能有一个
- 如果没有 package,默认无包名(default)
- 拥有包访问权限的类才能访问某个包中的类
- 同一个包中不允许有相同名字的类,不同包中类的名字可以相同,当同时调用两个不同包中的相同类名的类时,应当加上包名加以区分
# 类
# 类的描述
对一个事物的描述:
- 属性:该事物的描述信息
- 行为:该事物能够做什么
- 类是一组相关的属性和行为的集合,是构造对象的模板和蓝图,是一个抽象的概念
- 由类构造对象的过程称为类的实例化
# 类的导入
一个类可以使用所属包中的所有类,以及其他包中的公共类,有两种方式访问另一个包中的公共类:
- 使用完全限定名(包名后面跟着类名)如:java.time.LocalDate
- 使用 import 语句
# 自定义类的使用
- 创建对象的格式:
类名 对象名 = new 类名();
- 使用成员变量的格式:
对象名.变量名
- 使用成员方法的格式:
对象名.方法名(参数...)
# 类的初始化过程
以下代码在内存中做了哪些事情?
Student s = new Student();
分五步:
- 加载 Student.class 文件进内存
- 在栈内存中为引用变量 s 开辟空间
- 在堆内存中为学生对象开辟空间
- 对学生对象的成员变量进行默认初始化
- 对学生对象的成员变量进行显示初始化
对上述语句的解析:
- 右边的 “new Student()” 是以 Student 类为模板,在堆空间里创建一个 Student 对象
- 末尾的 “()” 意味着在对象创建后立即用 Student 类的构造方法对刚生成的对象进行初始化
- 左边的 “Student s” 创建了一个 Student 类引用变量,它存在于栈空间中,也就是用来指向 Student 对象的对象引用
- “=” 操作符使对象引用指向刚创建的那个 Student 对象
# 类的设计技巧
- 一定要保证数据私有化
- 一定要对数据进行初始化
- 不要在类中使用过多的基本类型
- 不是所有的字段都需要单独的字段访问器和字段更改器
- 分解有过多职责的类
- 类名和方法名的命名要起到见名知意的效果(类名应当是一个名词或动名词,访问器方法用小写 get 开头,更改器方法用小写 set 开头)
# 类的加载
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载、连接、初始化三步来实现对这个类进行的加载
加载:将. class 文件读入内存,并为之创建一个 class 对象,任何类被使用时系统都会建立一个 class 对象
连接:
- 验证:是否有正确的内部结构,并和其他类协调一致
- 准备:负责为类的静态成员分配内存,并设置默认初始值
- 解析:将类的二进制数据中的符号引用替换为直接引用
# 类的加载时机
什么时候类会加载?
- 创建类的实例
- 访问类的静态变量,或者为静态变量赋值
- 调用类的静态方法
- 使用反射方式来强制创建某个类或接口对应的 java.lang.class 对象
- 初始化某个类的子类
- 直接使用 java.exe 命令来运行某个主类
# 类加载器
- 根类加载器(Bootstrap ClassLoader):也叫引导类加载器,负责 Java 核心类的加载,比如 System、String 等,在 JDK 中 JRE 的 lib 目录下的 rt.jar 文件中
- 扩展类加载器(Extension ClassLoader):负责 JRE 的扩展目录中 jar 包的加载,在 JDK 中 JRE 的 lib 目录下的 ext 目录中
- 系统类加载器(System ClassLoader):负责在 JVM 启动时加载来自 Java 命令的 class 文件,以及 classpath 环境变量所指定的 jar 包和类路径(我们自定义的类就是通过它加载的)
# 对象
- 对象是该类事物的具体表现形式,具体存在的个体,如:学生是一个类,那么,班长就是一个对象
# 匿名对象
匿名对象是指没有名字的对象,只创建对象但是不用变量来接收
//匿名对象举例
new student();
new student().show();
2
3
匿名对象的应用场景:
- 该方法仅调用一次的时候
- 匿名对象可以作为实际参数传递
匿名对象的优点:匿名对象创建的方式能够减少栈帧的分配和指向,且在调用完毕后能够被 GC 机制(垃圾回收机制)快速的回收
# 方法
# 方法概述
- 方法是完成特定功能的代码块,Java 中的方法就是 C 语言中的函数
- 在 Java 中,所有的方法都必须在类的内部定义
- 如下代码中,像这里的 e 出现在方法名前面的叫隐式参数,可以用 this 指示;像这里的 5 出现在方法名后面括号中的数值叫显式参数
Employee e = new Employee("xl"...);
e.raiseSalary(5);
2
- 如果方法需要返回一个可变对象的引用,首先应该对它进行克隆(clone,指存放在另一个新位置上的对象副本)再返回,而不是直接返回可变对象本身
class Employee
{
...
public Date getHireDay()
{
return (Date)hireDay.clone();
}
...
}
2
3
4
5
6
7
8
9
- 方法可以访问所属类任何对象的私有特性,而不仅限于隐式参数
- 按值调用:方法接收的是调用者提供的值。按引用调用:方法接收的是调用者提供的变量地址
- 方法可以修改按引用传递的变量值,但是不能修改按值传递的变量值
- 方法不能修改基本数据类型的参数(即数值型或布尔型)
- 方法可以改变对象参数的状态
- 方法不能让一个对象参数引用一个新的对象
- 方法签名只包括方法名和参数类型,不包括返回值类型,通过方法名来区分不同的方法
- 不要使用 finalize 方法来进行清理,这个方法原本要在垃圾回收器清理对象之前调用,但是如果使用这个方法就不能确定到底什么时候调用了,而且该方法已经被废弃
- 只访问对象而不修改对象的方法称为访问器方法,相反的有更改器方法
# Java 中值传递和引用传递
按值传递:值传递是指在调用方法时将实际参数复制一份传递到方法中,这样在方法中如果对参数进行修改,将不会影响到实际参数
按引用传递:引用传递就是直接把内存地址传过去,也就是说引用传递时,操作的其实都是源数据,有可能影响原数据,除了基本类型的参数以外,其它的都是引用传递,比如:Object,二维数组,List,Map 等
java 中所有的参数传递都是传递变量所代表的值的副本,因此,Java 中的对象引用还是按值传递的,并不是按引用传递
# 方法的内存分配与变化
- 方法只定义不调用是不会执行的,并且在 JVM 中也不会给方法分配 “运行所属” 的内存空间,只有在调用方法时才会动态的给这个方法分配所属的内存空间
- JVM 内存划分上有这三块主要的内存空间:方法区内存、栈内存、堆内存
- 方法代码片段属于. class 字节码文件的一部分,字节码文件在类加载的时候被放到了方法区当中,所以 JVM 中的三块主要的内存空间中方法区内存最先有数据——方法代码片段
- 栈内存中分配方法运行的所属内存空间
- 方法在调用的瞬间,给该方法分配内存空间,在栈中发生压栈动作,方法调用结束之后,给该方法分配的内存空间全部释放,此时发生弹栈动作
- 局部变量运行阶段内存在栈中分配
- 堆内存中的数据使用完毕后就变成了垃圾,但是并没有立即回收,会在垃圾回收器空闲的时候回收
# 方法重载
- 方法重载(overload):在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可
- 方法重载的特点:1、与方法返回值无关,只看方法名与参数列表;2、在调用时,Java 虚拟机通过参数列表的不同自动区分同名方法,这一过程被称为重载解析
# 方法重写
- 方法重写(override):在子类中出现和父类中一模一样的方法声明,其也被称为方法覆盖或方法复写
- 方法重写的作用:当子类需要父类的功能,而功能主体子类有自己特有的内容时,这就可以重写父类中的方法,这样既沿袭了父类的功能,又定义了子类特有的内容(在子类中重写方法时如果想用父类中的方法则需使用 super 关键字调用,不然只有子类中重写的功能)
- 父类中的私有方法不能被重写,因为父类的私有方法子类根本无法继承
- 子类重写父类方法时,访问权限不能更低,即大于或等于,最好权限保持一致
# main 方法的格式讲解
public static void main(String[] args)
{
.....
}
2
3
4
- public:公共的,访问权限最大的修饰符,由于 main 方法是被 JVM 调用,所以权限要足够大
- static:静态的,不需要创建对象,方便 JVM 调用
- void:被 JVM 调用,不需要给 JVM 返回值
- main:被 JVM 识别,程序的执行入口
- String[] args:早期是为了接收键盘录入的数据所设置的参数,现在用 Scanner 了
# 构造器
- 构造器是一种特殊的方法(构造方法),用来构造并初始化对象
- 构造器与类同名,每个类可以有一个以上的构造器,构造器分无参构造和带参构造
- 构造器没有返回值
- 构造器总是伴随着 new 操作符一起调用
- 对于一个类来说,当你没有写无参构造方法时,系统会默认给出(因为所有类都继承 Object 类),写了无参构造方法后系统就不会给了,但两者效果一样,唯独当你只写了带参构造方法时,那么,这个类就没有无参构造方法了
在构造器中防止初始化字段为空,有两种方法:
- “宽容性”,这种方法是当检测到传入进来的值为空(null)时,便会用一个不为空的值赋值,如下代码:
//构造方法
public Employee(String name)
{
this.name = Objects.requireNonNullElse(name, "空名字");
}
2
3
4
5
- “严格型”,这种方法是当检测到传入进来的值为空时将自动终止程序,并抛出异常,这种方法的优点:1、异常报告会提供这个问题的描述;2、异常报告会准确地指出代码出错所在的位置,方便改错,如下代码抛出异常并提示 “名字不能为空”
public Employee(String name)
{
Objects.requireNonNUll(name, "名字不能为空");
this.name = name;
}
2
3
4
5
# 变量
# Java 中的变量类型
# 成员变量与局部变量
两者区别:
- 在类中位置不同:成员变量在方法外。局部变量在方法内或者方法声明上
- 在内存中的位置不同:成员变量在堆内存中。局部变量在栈内存中
- 生命周期不同:成员变量随着对象的创建而存在,随着对象的消失而消失。局部变量随着方法的调用而存在,随着方法调用完毕而消失
- 初始化值不同:成员变量有默认的初始值(被 final 修饰的实例变量必须手动赋值,不能采用系统默认值)。局部变量没有默认的初始值,必须在定义赋值后才能使用
注:局部变量名可以与实例变量名相同,在方法被调用时采用就近原则
# 访问控制权限修饰符
- 访问控制权限修饰符是用来控制元素访问范围的
- 访问控制权限修饰符包括:
访问权限修饰符 | 功能 |
---|---|
public | 对外部完全可见 |
protected | 对本包和所有子类可见 |
缺省(不写修饰符) | 对本包可见 |
private | 仅对本类可见 |
- 访问控制权限修饰符可以修饰类、实例变量、方法…
- 访问控制权限修饰符不能修饰局部变量
- 当希望某个数据只给子类使用时,就可以用 protected 修饰
- 访问控制权限修饰符的范围:
private < 缺省 < protected < public
# 封装
- 封装:隐藏对象的属性和实现细节,仅对外提供公共访问方法
- 封装是一种信息隐藏技术, 在 java 中通过关键字 private 实现封装
- 封装的好处:1、提高了代码的复用性和安全性;2、保证内部结构的安全
- 封装的原则:屏蔽复杂,暴露简单
# this 关键字
- this 关键字指向的是当前类的对象的引用,用来区分同名称的成员变量和局部变量
- this 只能在类中的非静态方法中使用,静态方法和静态代码块中绝对不能出现 this,并且 this 只和特定的对象关联,而不和类关联,同一类的不同对象有不同的 this
- this 关键字有两个含义:1、指示隐式参数的引用;2、调用该类的其它构造器
# static 关键字
- static(静态关键字),可以修饰成员变量和成员方法
- static 关键字的特点:1、随着类的加载而加载;2、优先于对象存在;3、被类的所有对象共享(判断是否使用静态关键字 static 的条件);4、可以通过类名调用(推荐使用)
- static 方法:static 方法也叫作静态方法,由于静态方法不依赖于任何对象就可以进行访问,因此对于静态方法来说是没有 this 关键字的使用的,因为 this 关键字要依附于对象,在静态方法中不能访问类的非静态成员,但是,在非静态成员方法中是可以访问静态成员方法或静态变量的
- static 变量:static 变量也叫作静态变量或类变量,静态变量与非静态变量的区别在于:静态变量被所有对象共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化;非静态变量是对象所拥有的,在创建对象的时候被初始化,在内存中存在多个副本,且各对象拥有的副本互不影响。static 成员变量的初始化顺序按照定义的顺序进行
注: - Java 中的 static 关键字不会影响到变量或者方法的作用域,在 Java 中能够影响访问权限的只有访问权限修饰符
- 静态成员变量虽然独立于对象,但是仍然可以通过对象进行访问(只要访问权限足够)
- static 是不允许用来修饰局部变量的
- 可以理解为静态元素与对象没有关系,它属于类
- 静态元素中不可以使用 this、super 关键字
- 静态变量存储在方法区中的静态区
# final 关键字
- final 关键字可以用来修饰类、方法和变量
- 修饰类:当用 final 修饰一个类时表明这个类不能被继承,final 类中的成员变量并不是默认 final 修饰的,可以根据需要设为 final,但 final 类中的所有方法却默认 final 修饰
- 修饰方法:对于重写问题而言,当父类中的某个方法被 final 修饰时,就表明父类中的这个方法不能被子类重写,也就是禁止子类重写此方法(主要目的是防止该方法的内容被修改)
注:重写的前提是子类可以从父类中继承此方法,如果父类中 final 修饰的方法同时又被 private 修饰,此时不会产生重写与 final 的矛盾,因为子类根本就没有继承这个方法,这个方法被私有化了,既然没有继承何来的重写,final 就无作用了,所以当父类中的某个方法的修饰符上同时有 private 和 final 时,在子类中依然可以出现同样声明的方法,因为这被视为在子类中重新定义了新的方法 - 修饰变量:被 final 修饰的变量就变成了常量,常量不能被重新赋值,只读不可写,Java 中定义常量时一般会加上 static 修饰,因为常量是不变的,任何对象拥有的都一样,如果一直 new 一样的东西就会浪费内存
注:被定义为 final 的成员变量必须在构造对象时就被初始化,并且以后不能再修改 - final 修饰基本数据类型时是值不能被改变,而 final 修饰引用类型数据时是地址值不能被改变,但是该对象的内容是可以变的
- final 修饰的实例变量必须手动赋值不能采用系统默认值
- 父类中的 final 方法可被子类继承,但是不能被子类重写
- final 修饰的引用指向的对象无法被垃圾回收器回收
注:当变量被 final 修饰后,这个变量就变成了常量,既然是常量,那么它在内存中存储的就只是数值了,与之前的变量内存就无关系了,即当变量消失时,常量不会消失,依旧是那个数值在运算,所以,若想某个数据不会因变量消失而消失,就将它修饰为常量
# 代码块
在 Java 中使用 { } 括起来的代码被称为代码块,根据其位置和声明的不同可以分为:局部代码块、构造代码块、静态代码块、同步代码块
- 局部代码块:其又叫普通代码块,在方法中出现,限定变量生命周期,主要用于解决当前方法中变量名重复的问题。若想要在一个方法中多次使用同一个变量名,并且互不影响,这时就可以将该变量放入不同局部代码块当中,因局部代码块中的变量生命周期只限于该代码块中
- 构造代码块:在类中方法外出现,多个构造方法中相同的代码存放到一起,每次调用构造都执行,只要创建对象就会执行构造代码块,主要作用是对对象进行初始化
- 静态代码块:在类中方法外出现,加了 static 修饰符,最先被执行,且对于一个类的多个对象只执行一次,其主要作用是对类进行初始化,随着类的加载而执行,与创不创建对象无关
- 同步代码块:在方法中出现,使用 synchronized 关键字修饰,在多线程环境下,对共享数据的读写操作是需要互斥进行的,否则会导致数据的不一致性
以上代码块执行顺序:
静态代码块-->构造代码块-->构造方法-->局部代码块
# 继承
继承:多个类中存在相同属性(成员变量)和行为(成员方法)时,将这些内容抽取到单独一个类中,那么多个类就无需再定义这些属性和行为了,只要继承那个类即可
通过 extends 关键字可以实现类与类的继承
class 子类名 extends 父类名{}
- 单独的类称为父类、基类或超类,子类也叫派生类,父类中的内容是多个子类重复的内容
- 继承的好处:1、提高了代码的复用性,多个类相同的成员可以放到同一个类中;2、提高了代码的维护性,如果功能的代码需要修改,只需要修改父类这一处即可;3、让类与类之间产生了关系,这是多态的前提(这也是继承的缺点),使得类的耦合性增强
- 开发的原则:低耦合、高内聚
- 继承的缺点:1、破坏了封装,子类与父类之间紧密耦合,子类依赖父类的实现,造成子类缺乏独立性;2、支持扩展,但是往往以增强系统结构的复杂度为代价;3、不支持动态继承,在运行时子类无法选择不同的父类;4、子类不能改变父类的接口
- 继承的特点:1、Java 只支持单继承,不支持类的多继承,一个类只能有一个父类,不可以有多个父类;2、Java 支持多层继承(继承体系)
class A{}
class B extends A{}
class C extends B{}
2
3
- 子类只能继承父类中所有非私有的成员方法和成员变量
- 子类不能继承父类的构造方法,但是可以通过 super 关键字去访问父类的构造方法
- 不要为了部分功能而去继承
- 在 Java 中,所有的继承都是公共继承
- 继承绝对不会删除任何字段或方法
- Object 类是 Java 中所有类的父类,如果某个类没有明确地指出父类,那么 Object 类就被认为是这个类的父类,这个类就默认访问 Object 类的无参构造
在子类中访问一个变量的执行顺序:首先在子类的局部范围中找,然后在子类成员范围中找,最后在父类成员范围中找(不能访问到父类的局部范围),如果还是没有就报错
# super 关键字
- super 有两个含义:1、调用父类的方法;2、调用父类的构造器
- 调用父类成员变量
super.成员变量
- 调用父类的构造方法(使用 super 调用构造器的语句必须是子类构造器的第一条语句)
super(参数)
- 调用父类的成员方法
super.成员方法
注:
- super 不是一个对象的引用,不能将值 super 赋给另一个对象变量,它只是一个指示编译器调用父类方法的特殊关键字
- this 代表本类对应的引用,通过其操作本类的成员,super 代表父类存储空间的标识
# 继承中构造方法的关系
- 子类中所有的构造方法默认都会访问父类中的无参构造方法,因为子类会继承父类中的数据,可能还会使用父类的数据,所以子类初始化之前一定要先完成父类数据的初始化(先进行父类的初始化再进行子类的初始化,这叫分层初始化)
- 子类每一个构造方法的第一条语句都是 super(),这是系统默认的,写不写都有,但如果父类没有无参构造方法,这时在子类中系统就不会自动调用父类的无参构造了,需手动调用父类的构造方法,不然会报错
# 继承使用场景
采用假设法:如果有两个类 A、B,只要它们符合 A 是 B 的一种或者 B 是 A 的一种(A is B)或(B is A)这样的关系,就可以考虑使用继承
# 继承的设计技巧
- 通过父类定义子类时,只需要在子类中指出子类与父类的不同之处即可,将通用的字段和方法(不管是否是抽象的)都放在父类(不管是不是抽象类)中,而更特殊的方法就放在子类中
# 多态
- 多态:一个对象变量可以指示多种实际类型的现象(某一个对象在不同时刻表现出来的多种状态)
- 多态的使用前提:1、要有继承关系;2、要有方法重写;3、要有父类引用指向子类对象
父类名 f = new 子类名();
- 多态的好处:1、提高了代码的维护性;2、提高了代码的扩展性
- 向上转型
fu f = new zi();
- 向下转型
zi z = (zi)f;//要求该f必须是能够转换为zi的
注:在进行以上两种转型之前应当对两对象之间进行检查,这是编程的好习惯,instanceof 操作符:检查对象之间是否能成功地进行转换
例:
/*
Manager类继承了Employee类
*/
Employee[] staff = new Employee[3];
Manager boss = new Manager();
if(staff[1] instanceof Manager)
{
boss = (Manager)staff[1];
}
2
3
4
5
6
7
8
9
10
# 多态中的成员访问特点
- 成员变量:编译看父类,运行看父类
- 成员方法:编译看父类,运行看子类(由于只有成员方法存在方法重写,所以它运行看子类)
- 静态方法:编译看父类,运行看父类(静态和类相关,算不上重写,所以访问的还是父类的)
- 动态绑定(后期绑定):在运行时能够自动的选择适当的方法,java 中的动态绑定是默认行为,动态绑定是多态得以实现的重要因素
- 静态绑定(前期绑定):在程序执行前已经被绑定,即在编译过程中就已经知道这个方法是哪个类的方法,此时由编译器获取其它连接程序实现。在 Java 中,final、private、static 修饰的方法以及构造函数都是静态绑定的,不需程序运行,不需具体的实例对象就可以知道这个方法的具体内容。
# 抽象类
- 抽象:所谓抽象就是不是一种具体的事物而是一类事物的总和,如人分男人和女人,那么男人和女人相对于人来说就是具体的,人就是抽象的
- 抽象类:被 abstract 修饰的类叫抽象类,抽象类没有实体的东西,其无法直接创建对象(不能被实例化),因为调用抽象方法无意义,如果父类为抽象类,那么子类只有重写了父类中的所有方法抽象方法后才可以创建对象,否则该子类还是一个抽象类,因为假如子类没有对抽象类的所有方法重写,那么子类也就会继承了抽象类中的抽象方法,只要有抽象方法的类就一定是抽象类,但抽象类不一定有抽象方法
- 抽象类的思想:强制子类重写抽象方法
- 抽象类的作用:降低接口实现类对接口的实现难度,将接口中不需要使用的方法交给抽象类实现,这样接口实现类只需要对要使用的方法进行重写
- 抽象方法:被 abstract 修饰的方法叫抽象方法,其没有方法体,且抽象方法必须存在于抽象类中
注:抽象方法是没有方法体,没有方法体不代表空方法体
public abstract void eat(){...}//这叫空方法体,会报错
public abstract void eat();//这才叫无方法体
2
- 抽象类不可被实例化
- 抽象类是有构造器的
- 抽象类可以没有抽象方法
- 抽象类的使用场景一般在运用多态时比较适用
- abstract 不能与这几个关键字共用:private(私有的方法子类是无法继承的,但是 abstract 又要求子类需要实现抽象方法,这是矛盾的)、final(类、方法被 final 修饰后不能被继承和重写,矛盾)、static(通过类名访问抽象方法是没有意义的,因为抽象方法没有方法体)
- 抽象类中的抽象方法是强制要求子类做的事情,非抽象方法是子类继承的事情,提高代码的复用性
# 接口
- 接口:接口是 Java 语言中的一种引用类型,它是抽象方法的集合,如果说类的内部封装了成员变量、构造方法和成员方法,那么接口的内部主要就是封装了方法
- 接口用关键字 interface 修饰
public interface 接口名{}
- 接口不是类,它是另外一种引用数据类型
- 接口不能创建对象,但是可以被实现,一个实现接口的类需要实现接口中所有的抽象方法,否则它就是抽象类
- 类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类或接口的子类,实现用 implements 关键字修饰
class 类名 implements 接口名{}
- 接口不能定义构造方法(因为接口的作用主要是为了扩展功能,并不具体存在,没必要初始化)且接口中只有常量(隐式修饰,public static final)
- 接口不能创建对象,只能通过其实现类来使用
- 一个接口可以有多个方法且接口中所有的方法必须是抽象方法,默认修饰符 public abstract(隐式修饰),如果需要定义具体方法实现,则此时方法需要使用 default 修饰
- 接口不是被类继承而是被类实现
- 一个接口能继承另一个接口且接口支持多继承
public interface 接口1 extends 接口2, 接口3{}
- 接口中不能含有静态代码块以及静态方法(这里编译器不会报错,只是在实际开发中这样做是没有意义的)
- 一个类可以实现多个接口
- 接口中的方法都是公有的
- 接口是隐式抽象的,当声明一个接口的时候不必使用 abstract 修饰
- 接口中每一个方法是隐式抽象的,声明同样不需要 abstract 关键字
- 接口的作用是降低耦合度和扩展功能
注:开发中实现接口的类的类名命名格式:接口名 + Impi
- 接口与抽象类的区别:抽象类中定义的是该继承体系的共性功能,而接口中定义的是该继承体系中的扩展功能(特性功能)
- 当引用类型作形式参数或返回值时:
类:需要的是该类的对象
抽象类:需要的是该抽象类的子类对象(多态)
接口:需要的是该接口的实现类对象(多态)
# 简单修饰符归纳
类的修饰符可有:public、缺省、final、abstract
注:成员内部类可有 private 和 static 修饰符,其余的普通类不行,因为成员内部类可以看作是外部类的成员成员变量的修饰符可有:private、缺省、protected、public、static、final
构造方法的修饰符可有:private、缺省、protected、public
成员方法的修饰符可有:private、缺省、protected、public、static、final、abstract
# 内部类
- 内部类:一个类定义在另一个类里面或一个方法里面,这样的类称为内部类,内部类一般包括:成员内部类、局部内部类、匿名内部类、静态内部类
- 内部类和外部类之间没有继承关系
直接访问内部类成员的语法格式:
外部类名.内部类名 对象名 = new 外部类名().new 内部类名();
# 成员内部类
- 成员内部类是最普通的内部类,位于另一个类的内部
- 成员内部类可以无条件的访问外部类的所有成员变量和成员方法(包括 private 成员和静态成员)
- 当成员内部类拥有和外部类同名的成员变量或方法时,默认情况下访问的是内部类的成员(就近原则),若要访问外部类的同名成员,语法格式为:
外部类名.this.成员变量
外部类名.this.成员方法
2
- 在外部类如果要访问成员内部类的成员就必须先创建一个内部类的对象,再通过指向这个对象的引用来访问
- 内部类是依附于外部类存在的,如果要创建内部类的对象,前提是必须存在一个外部类的对象
# 局部内部类
- 局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的作用范围只限于方法内或该作用域内
- 局部内部类可以无条件的访问外部类的所有成员变量和成员方法(包括 private 成员和静态成员)
- 局部内部类就像是方法里面的一个局部变量一样是不能有 public、protected、private 以及 static 修饰的
# 匿名内部类
- 匿名内部类是没有名字的内部类且它只能使用一次(和匿名对象相似),通常用来简化代码
- 匿名内部类的实质是对象(可将匿名内部类当作对象使用),并不是类,它是一个类或子类或实现接口类的一个对象,是一个继承了该类或实现了该接口的子类匿名对象
- 匿名内部类不能有访问修饰符和 static
- 匿名内部类使用条件:必须继承一个父类或实现一个接口
- 匿名内部类是唯一一种没有构造方法的类,因此它的使用范围非常有限,大部分匿名内部类用于接口回调
注:一般来说,匿名内部类用于继承其他类或实现接口,并不需要增加额外的方法,只是对继承方法的实现或重写,语法格式:
new 类名或者接口名(){重写方法}//可以理解为一个对象
# 静态内部类
- 静态内部类是被 static 修饰的内部类
- 当外部类被加载的时候,静态内部类是不会被加载的
- 当使用静态内部类中的成员时,静态内部类才会被加载且仅仅加载一次,不会有线程安全的问题
- 与静态成员变量不一样的是静态内部类是不能通过外部类对象访问的
- 静态内部类不能使用外部类的非静态成员变量和方法,只能访问外部类的静态成员
静态内部类的唯一访问语法格式:
外部类名.内部类名 对象名 = new 外部类名.内部类名();
# JVM 的类加载规则
- static 类型的变量和方法在类加载的时候就会存在于内存中
- 要想使用某个类的 static 变量或方法,那么这个类就必须要加载到 Java 虚拟机中
- 非静态内部类并不随外部类一起加载,只有实例化外部类之后才会加载
注:现在考虑这个情况:外部类并没有实例化,内部类还没有加载,这时候如果调用内部类的静态成员或方法,内部类还没有加载却试图在内存中创建该内部类的静态成员,这是明显矛盾的,所以非静态内部类不能有静态成员变量或静态方法(这里就和非静态方法不一样了)
# Java 中的 String
- 在 Java 中字符串(String)属于对象,并不是基础的数据类型
- 字符串直接赋值的方式是先到字符串常量池(方法区中)里面去找,如果有就返回,没有就创建对象并返回
//以下两者的区别
String s1 = new String("hello");//创建2个或1个对象,new操作符是一定要创建对象的
String s2 = "hello";//创建1个或0个对象
2
3
- String 作参数传递时,虽然它是引用类型,但是效果和基本类型作参数传递是一样的,要将它看作基本类型
# Java 中的 StringBuffer 和 StringBuilder
- 它们与 String 不同的是 StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象(除截取功能外)。
- StringBuffer 和 StringBuilder 相同点:1、两个类都是用来解决大量字符串拼接时而产生的中间对象问题;2、都需要实例化创建对象;3、都可以看作可变长度字符串,初始化容量都为 16
- StringBuffer 和 StringBuilder 不同点:StringBuffer 是线程安全的(数据安全),运行速度较慢,StringBuilder 是非同步的(不安全),效率高,运行速度较快
三者使用场景
- String:适用于少量的字符串操作的情况
- StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况(多数情况下使用)
- StringBuffer:适用于多线程下在字符缓冲区进行大量操作的情况
以下说明用 StringBuilder 类举例,对于 StringBuffer 类同等适用
StringBuilder 的构造方法:
StringBuilder();
StringBuilder(int size);//指定初始容量的字符串缓冲区(不一定是实际长度)
StringBuilder(String str);//指定字符串内容的字符串缓冲区
2
3
StringBuilder 类的常见功能(除截取功能外,其余功能返回 StringBuilder 本身):
1、添加功能
public StringBuilder append(String str);//可以把任意类型添加到字符串缓冲区里
public StringBuilder insert(int offset, String str);//在指定位置把任意类型的数据插入到字符串缓冲区里
2、删除功能
public StringBuilder deleteCharAt(int index);//删除指定位置的字符
public StringBuilder delete(int start, int end);//删除从指定位置开始到指定位置结束的这一范围的内容
3、替换功能
public StringBuilder replace(int start, int end, String str);
4、翻转功能
public StringBuilder reverse();
5、截取功能
public String subString(int start);//从start开始到末尾
public String subString(int start, int end);//从start到end
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
注:StingBuilder 类中的截取方法不再是返回本身了,而是重新开辟空间
String 与 StringBuilder 相互转换
- String 转 StringBuilder
1、通过构造方法
StringBuilder s1 = new StringBuilder("字符串");
2、通过append()
StringBuilder s3 = new StringBuilder();
s3.append("字符串");
2
3
4
5
6
- StringBuilder 转 String
StringBuilder builder = new StringBuilder("字符串");
1、通过构造方法
String str = new String(builder);
2、通过toString()
String str = builder.toString();
2
3
4
5
6
7
String 和 int 的相互转换
- String 转 int
Integer.parseInt("数字字符串");
- int 转 String
String.valueof(int型数据);
# 增强 for
Java 中的增强 for(for each 循环)是一种很强的循环结构,可以用来依次处理数组中的每一个元素,不必考虑下标的问题
for(int element : a)//这里的a是一个数组名或集合
{
System.out.println(element);//输出每一个元素
}
2
3
4
- 增强 for 的循环变量将会遍历数组中的每一个元素而不需要使用下标值
- for each 循环的局限性:该循环只能遍历整个数组,每个元素都会用到且循环内部没有下标处理
- 增强 for 的目标不能为 null,所以要在使用前考虑做判断
- 增强 for 其实是用来替代迭代器的
# 包装类
- 包装类类型:为了对基本数据类型进行更多更方便的操作,Java 就针对每一种基本类型数据提供了对应的类类型,即将基本类型转化为对象操作
基本类型 | 引用类型(类) |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
Integer 类中常用的方法:
1、String转int
public static int parseInt(String s);//字符串s必须为由数字组成的字符串,字母的不行
2、进制转换(radix的范围:2<= radix <= 36)
public static String toString(int i, int radix);//i表示被转换的数(十进制数),radix表示进制数
2
3
4
5
注:
- 包装类都是被 final 修饰的,因此不能派生它们的子类
- 自动装箱规范要求 boolean、byte、char<= 127
- 介于 - 128 和 127 之间的 short 和 int 被包装到固定的对象中,不会创建新的对象
- 如果在一个条件表达式中混合使用 Integer 和 Double 类型,Integer 值会拆箱,提升为 double 再装箱为 Double
- 装箱和拆箱是编译器要做的工作而不是虚拟机的,编译器在生成类的字节码时会插入必要的方法调用,虚拟机只是执行这些字节码
Character 类中常用的方法:
1、判断给定的字符大小写
public static boolean isUpperCase(char ch);//大写返回true,小写返回false
public static boolean isLowerCase(char ch);//大写返回false,小写返回true
2、判断给定的字符是否是数字
public static boolean isDigit(char ch);
3、把给定的字符转成大小写
public static char toUpperCase(char ch);
public static char toLowerCase(char ch);
2
3
4
5
6
7
8
9
10
# Math 类
Math 类常用方法:
1、取绝对值
public static int abs(int a);
2、向上取整
public static double ceil(double a);
3、向下取整
public static double floor(double a);
4、取最大值
public static int max(int a, int b);
5、a的b次幂
public static double pow(double a, double b);
6、取随机值
public static double random();//返回值范围[0.0, 1.0)
7、四舍五入
public static int round(float a);
8、正平方根(开方)
public static double sqrt(double a);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Random 类
- Random 类作用:产生随机数的类
Random 类的构造方法:
Random();//默认种子,每次产生的随机数不同
Random(long seed);//指定种子,每次种子相同,随机数就相同
2
Random 类常用方法:
int nextInt();//返回int范围内的随机数
int nextInt(int n);//返回[0, n)范围内的随机数
int nextDouble();//随机获得一个0-1的double类型数据,括号里不填
int nextFloat();//随机获得一个0-1的float的类型数据
int nextBoolean();//随机获得true或false
2
3
4
5
# System 类(系统类)
System 类常用方法:
1、运行垃圾回收器
public static void gc();
2、退出JVM
public static void exit(int status);//status - 退出状态。
3、获取当前时间的毫秒值
public static long currentTimeMillis();
4、复制数组
public static void arraycopy(Object src, int srcpos, Object dest, int destpos, int length);
参数:
src:源数组
srcpos:源数组中的起始位置
dest:目标数组
destpos:目标数组中的起始位置
length:要复制的数组元素的数量
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# BigInteger 类
- BigInteger 类是针对大整数的运算
- BigInteger 类的构造方法(以下只举一种)
public BigInteger(String val);//字符串val必须是数字字符串
- BigInteger 类中常用的成员方法
1、加法
public BigInteger add(BigInteger val);
2、减法
public BigInteger subtract(BigInteger val);
3、乘法
public BigInteger multiply(BigInteger val);
4、除法
public BigInteger divide(BigInteger val);
5、返回商和余数的数组,该数组中只有两个元素:商array[0]、余数array[1]
public BigInteger[] divideAndRemainder(BigInteger val);
2
3
4
5
6
7
8
9
10
11
12
13
14
# BigDecimal 类
- 用于浮点数运算,避免丢失精度
- BigDecimal 类的构造方法(以下只举一种)
public BigDecimal(String val);//字符串val必须是数字字符串
- BigDecimal 类中常用的方法
1、加法
public BigDecimal add(BigDecimal augend);
2、减法
public BigDecimal subtract(BigDecimal subtrahend);
3、乘法
public BigDecimal multiply(BigDecimal multiplicand);
4、除法
public BigDecimal divide(BigDecimal divisor);
5、对除法运算结果的操作
public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode);
参数:
divisor:被除数
scale:小数点后位数
roundingMode:舍取模式,一般用四舍五入模式(BigDecimal.ROUND_HALF_UP)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Date 类
- Date 类是日期类,可以精确到毫秒
- Date 类的构造方法
public Date();//根据当前的默认毫秒值创建对象
public Date(long date);//根据给定的毫秒值创建对象,data是毫秒值,日期是从1970年1月1日0:0:0开始计算,data加上这个时间即可
2
- Date 类中常用的方法
1、获取时间,以毫秒为单位返回
public long getTime();
2、设置时间
public void setTime(long time);
2
3
4
5
# DateFormat 类
- DateFormat 类是针对日期进行格式化和针对字符串进行解析的类,但是该类是抽象类,所以使用其子类 SimpleDateFormat
- SimpleDateFormat 类构造方法
public SimpleDateFormat(String pattern);//指定日期的格式(如:yyyy-MM-dd HH:mm:ss)
public SimpleDateFormat();//默认格式表示日期
2
- SimpleDateFormat 类的常用方法
1、Date转String(格式化)
public final String format(Date date);
2、String转Date(解析)
public Date parse(String source);
2
3
4
5
# Calendar 类
- Calendar 类是日历类,它是一个抽象类,其封装了所有的日历字段值,通过统一的方法根据传入不同的日历字段可以获取值
- Calendar 类中常用的方法
1、获取一个日历对象
Calendar rightNow = Calendar.getIntstance();
2、返回给定日历字段的值,日历字段中的每个日历字段都是静态的成员变量,并且是int型
public int get(int field);
3、根据给定的日历字段和对应的时间来对当前的日历进行操作
public void add(int field, int amount);
4、设置当前日历的年月日
public final void set(int year, int month, int date);
2
3
4
5
6
7
8
9
10
11