java 特性
Java 程序的版本
java SE (标准版)
java EE(企业版)
java ME(精简版)
java 程序可移植性
什么是可移植性?
java 程序可以做到一次编译,到处运行。可就是说可以运行到windows,linux等操作系统上。这个被称作Java程序的可移植性,或者叫跨平台。
如何实现呢?
Windows操作系统于Linux操作系统的内核肯定不一样,系统执行指令的而方式也不一样。 Java程序不能直接和系统打交道,因为Java程序只有一份。操作系统原理都不同
解决:
sum团队想到了一个办法,他们让Java程序运行在一台虚拟的计算机中,这个虚拟计算机叫Java虚拟机,简称JVM。Java虚拟机再和底层操作系统打交道。
这个虚拟机不能直接安装,而是通过安装适合自己操作系统的 java jdk 来实现的
Java 的加载与执行
编译阶段主要任务是检查 Java 源程序是否符合 Java 语法(不进行运算),符合则生成正常字节码文件 (xxx.class)。不符合则无法生成字节码文件
.java -> .class -> 类装载器 -> java 虚拟机(JVM) -> 操作系统 -> 硬件平台
前两步为编译阶段,后面为运行阶段。两步操作可以在不同系统执行
.java 是 java 程序源文件(源代码)必须符合Java 编码 规范
class 编译后的文件(字节码文件),使用 javac 编译源文件生成
javac 是一个java编译工具/命令,一个Java 源文件可编译生成多个 .class 文件。
语法:javac java源文件路径
字节码文件是最终要执行的文件,所以删除Java源文件也不影响Java程序运行效果。但一般不删除源文件,以防需要修改。
class 文件拷贝到其他操作系统也是可以运行的
安装jdk后除了自带 javac.exe, 还有一个工具/命令 java.exe,主要负责运行阶段
语法:java 类名
例如:硬盘上有一个 A.class ,那么执行 java A。运行程序不要带 .class
运行阶段
在DOS窗口运行:java A
Java.exe 命令会启动Java虚拟机(JVM),JVM 会启动类加载器ClassLoader 去硬盘上搜索 A.class文件,找到文件则将该字节码文件装载到JVM当中。JVM然后将A.class字节码文件解释成 二进制10101010这样的数据,最后由操作系统执行二进制文件和底层平台经行交互。
安装jdk,jdk,jre,jvm 关系
- jdk 开发工具包,可单独安装,安装时会自带安装jre
- jre 开发运行环境,包括 jvm,可单独安装
- jvm Java虚拟机
jdk 目录介绍
bin目录: 里面存放了很多命令,如 javac.exe,java.exe
db目录:
jre目录:自带的开发运行环境
lib目录:
include目录:
环境变量
系统环境变量 path
可以通过配置 path,让命令在DOS窗口任意 目录下使用,相当于系统全局变量
java 环境变量 classpath, 不配也没事(默认当前路径)
默认ClassLoader 在当前目录路径下加载 xxx.class 字节码,配置classpath 可以给 ClassLoader 类加载器指定路径。更改默认指定路径
配置 classpath=. ,相当于指定默认目录就是当前路径
Java 注释
工具 javadoc.exe 可以将注释中的文字添加到帮助文档中。注释跟 js 一样
Java SE类库的源码位置?
- SE库字节码:C:\Program Files\Java\jdk1.7.0_51\jre\lib\rt.jar
- SE库源码:C:\Program Files\Java\jdk1.7.0_51\src.zip
编码方式
Java 语言采用 Unicode 编码方式,实际开发中使用 utf-8 较多
javac xxx.java 编译错误: 编码GBK的不可映射字符
解决方法:
以这种格式对文件经行重新编译
javac -encoding utf-8 xxx.java
Java 语法
public class 和 class 的区别
- 一个Java源文件当中可以定义多个class
- 一个Java源文件当中 public class 不是必须的
- 一个class会定义生成一个 xxx.class字节码文件
- 一个Java源文件当中定义公开的类的话,只能有一个,并且该类必须和Java源文件名称一致。
- 每一个class当中都可以编写main方法,都可以设置程序入口。要执行B.class 中的main方法,执行 java B。如果B中没有main主方法,会报错
标识符
Java语言中的标识符。
什么是标识符?
在Java源码程序中,可以自己命名的单词都是标识符。
如:类名,方法名,常量名,接口名…
标识符的命名规则?
一个合法的标识符只能由 数字,字母,下划线_,美元符号$ 组成,不能有其他符号。
不能数字开头
严格区分大小写
关键字不能用
理论上无长度限制
标识符的命名规范?
只是一种规范,不输入语法,不遵守规范编译也可以通过。但是不方便他人理解代码
- 最好见名知意
- 遵守驼峰命名
- 类名,接口名:首字母大写(大驼峰
- 变量,方法名:首字母小写(小驼峰
- 常量名:全部大写
关键字
在Java中关键字都是小写
class | extend | implement | interface | important |
---|---|---|---|---|
package | break | case | continue | default |
do | if | else | for | return |
switch | while | false | true | … |
字面值
java 语言中所有字符串类型字面值必须使用双引号括起来,双引号是英文的
Java 语言中所有的字符型字面量必须使用单引号括起来,单引号是英文的
- 10,1200
- 3.14
- “abc”,”123” 属于字符串
- ‘a’,’1’,’人’ 属于字面值
- true,false
变量
变量要求储存的具体的“数据”必须和变量的“数据类型”一致,当不一致的时候编译报错
数据类型的作用?
- 不同数据类型底层分配不同大小的空间
- 数据类型是指导程序运行阶段应该分配多大的内存空间
声明、定义变量的语法格式:
数据类型 变量名
变量的分类:
局部变量
在方法体当中声明的变量叫局部变量
成员变量
在方法体外【类体之内】声明的变量叫做成员变量
不同作用域下变量名可以相同,Java 遵循就近原则
数据类型
基本数据类型
- 整数型 int,byte,short,long
- 浮点型 float,double
- 布尔型 boolean
- 字符型 char 【表示现实中的文字,通过ASC码表转换】
八种基本数据类型占用空间大小:
基本数据类型 | 占用空间大小(byte) | 取值范围 | 默认值 |
---|---|---|---|
byte | 1 | -2^7~2^7-1(-128,127) | 0 |
short | 2 | -2^15~2^15-1(-32768,32767) | 0 |
int | 4 | -2^31~-2^31-1 | 0 |
long | 8 | -2^63~2^63-1 | 0L |
float | 4 | 大约±3.40282347E+38F(有效位15) | 0 |
double | 8 | 大约±1.79769313486231570E+308(有效位15) | 0.0 |
boolean | 1 | true/false | false |
chart | 2 | 0~2^16-1(0, 65535) | \u0000 |
字节(byte):
1 byte = 8 bit 【一个字节=8个比特位】1个比特位表示一个二进制位:1/0
1 KB = 1024 Byte
1 MB = 1024 KB
1 GB = 1024 MB
byte 类型最大值: 2的7次方 - 1,127
byte 类型最小值:-128
byte 类型可以表示 256 个不同的数字
关于八种基本数据类型的默认值:一切向0看齐
变量遵守这个语法:必须先声明,再赋值,才能访问。
成员变量没有手动赋值系统默认会赋值,但是局部变量不会
引用数据类型
字符串 “abc” 不属于基本数据类型,属于 “引用数据类型”,字符属于基本数据类型:
char 与 转译字符
chart 支持 中文(‘中’),支持转义字符(’\n’), 使用单引号括起来,
1 | // 这个输出命令会换行 |
整数型
Java 语言当中的 “整数型字面量值”被默认当作 int 类型来处理。要让这个 “整数字面量值”后面添加 l/L ,建议使用大写L
整数字面值的三种表示方式:
- 十进制:是一种缺省默认的方式
- 八进制:需要以0开始
- 十六进制:需要以0x开始
类型转换
int 是小容量,long 类型是大容量。小容量可以自动转换成大容量,称为自动类型转换机制
1 | // 1、可以自动转换为大容量 |
浮点型
浮点型存储的值都是近似值,现实世界中有些数字是无限循环小数,计算机存储资源有限,所以只能存储近似值
float 单精度【4个字节】
double 双精度【8个字节,精度较高】
对于财务类型软件,double精度还是过低,所以Java提供了另一个引用类型: Java.math.bigDecima
在Java语言中,所有的浮点型字面值【3.0】,默认被当作 double 类型来处理,要想该字面值当作 float 类型来处理,需要在字面值后面添加 F/f;
1 | double d = 3.0; |
布尔值
只有两个值:true,false.
底层存储的时候Boolean类型占用1个字节,因为实际存储的时候false底层是0,true底层是1
主要使用在逻辑运算和条件控制语句中
类型转换
八种基本数据类型当中,除了Boolean类型其他类型都能互相转换
小容量向大容量转换,称为自动类型转换,容量从小到大:
byte < (short = char) < int < long < float < double
任何浮点类型不管占用多少个字节,都是比整数型容量大
小容量转大容量,必须加强制转换符,编译才能通过,但是运行阶段可能损失精度,慎用
当整数字面量没有超出byte,short,char的取值范围,可以直接赋值给byte,short,char类型的变量
byte,short,char混合运算时,各自先转换成int类型在做运算
多种数据类型混合运算,先转换成容量最大的那种类型再做运算
编写.java文件只考虑编译,不考虑运行
运算符
算数运算符 | +、-、*、/、++、–、% |
---|---|
逻辑运算符 | &(与)、(或)、~(非)、^(异或)、&&(短路与)、||(短路或) |
关系运算符 | <、<=、>=、==、!= |
布尔运算符 | &&、||、|、&、! |
位运算符 | &(与)、|(或)、~(非)、^(按位异)、>>(右移)、>>>(右移,左边空出的位0填充)、<<(左移) |
赋值类运算符 | =、+=、-=、*=、/=、%/ |
字符串连接运算符 | + |
条件运算符 | ?: |
其他运算符 | instanceof、new |
逻辑运算符
逻辑运算符 要求两边的算子都是 真/假(布尔类型),且最终运算结果也是一个布尔类型
异或(两边的算子只要不一样,结果就是 true)
短路与和逻辑与的运算结果相同,只不过短路与存在短路现象
短路或和逻辑或的运算结果相同,只不过短路或存在短路现象
赋值运算符
扩展类的赋值运算符,不改变运算结果类型,假设最初这个变量类型是 byte 类型,无论怎么进行追加,追减,最终该变量的数据类型还是byte类型
+运算符 在Java中的两个作用:
- 加法运算,求和
- 字符串的连接运算
当 “+” 运算符两边的数据都是字符串,一定是字符串连接运算。
- 数字 + 数字 –> 数字
- 数字 + “字符串” —> “字符串” 【字符串连接】
在一个表达式中可以出现多个 “+” 号,如果没有 括号,那么就是从左到右依次运算
控制语句
Java控制语句可以分为7种
控制选择结构语句
- if,else 括号中接受一个Boolean
- switch 括号中接受一个 int/String
控制循环结构语句
- for
- while
- do while
改变控制语句顺序
break
作用在for,while,do..while
continue
方法
语法结构:
[修饰符列表] 返回值类型 方法名 (形参参数列表) {
方法体:
}
返回值类型可以是基本类型也可以是引用类型,当返回类型是viod时,方法体不能 使用 return 语句 返回值
修饰符列表 统一写成: public static
对于方法的修饰符列表中有 static 关键字的:在方法体种调用时可以省略类名.
建议一个Java源文件当中只定义一个 class,清晰易读
方法的内存分配情况
方法的重载
同一个类中功能相似的方法可以共用同一个方法名,然后根据传入参数类型的不同来调用不同的方法
什么条件满足之后构成方法重载?
- 在同一个类
- 方法名相同
- 参数列表不同
- 数量不同
- 顺序不同
- 类型不同
方法重载和什么有关,和什么没有关系?
- 方法重载和方法名+参数列表有关系
- 方法重载和返回值类型无关
- 方法重载和修饰符无关
1 | class OverloadTest1 { |
方法的递归
方法自调用就是递归
1 | // 乘法递归 |
面向对象
面向对象的三大特征:
- 封装
- 继承
- 多态
所有面向对象的编程语言都有这三大特征。
- 面向对象的分析:OOA
- 面向对象的设计:OOD
- 面向对象的编程:OOP
对象的创建和使用
1 | public class OOTest { |
空指针异常
空引用访问“实例”相关数据一定会出现空指针异常
java.lang.NullPointerExption
封装
封装的步骤:
所有属性私有化,使用 private 关键字进行修饰,修饰的数据只能在本类中访问
对外提供简单的操作入口,如:读取和修改
set,get方法命名规范:
public void setAge (int a) {
age = a;
}
public int getAge () {
return age;
}
调用:
对于 有 static 修饰的方法采用 类名.方法名(实参)
对于没有 static 修饰的方法采用 引用.方法名(实参)
1 | public class User { |
构造方法
构造方法又被称为构造函数/构造器/Constructor
构造方法语法结构:
[修饰符列表] 构造方法名(形式参数列表) {
构造方法体;
}
普通方法语法结构
[修饰符列表] 返回值类型 方法名(形式参数列表) {
方法体;
}
当一个类没有定义构造方法,系统默认给该类提供一个无参构造函数,称 缺省构造器
构造方法除了 new 时调用,还可以手动调用 this()
1
2
3
4
5
6
7
8
9
10
11
12
13
14 public class User {
int year;
int month;
int day;
public User(int year, int month, int day ) {
this.year = year;
this.month = month;
this.day = day;
}
// 无参数时调用次构造方法,默认设置日期 2020 12 12
public User() {
this(2020, 12, 12);// 这种语法只能出现在构造函数第一行,类似于 super
}
}
this 关键字
关于Java中的this关键字
this是一个引用,this是一个变量,this变量中保存了内存地址指向自身,this存储在JVM堆内存Java对象内部
this 不能使用在带有static的方法中
那么在 static 方法中怎么访问成员变量?
- 方法中实例化对象获取引用访问
- 通过参数将实例传入
- 用 static 修饰成员变量,但在 非static 方法中不能用 this 访问只能用 类名.属性名
static 关键字
静态变量:在类加载时就初始化,不需要创建对象内存就在方法区开辟了
static 关键字修饰的 属性,方法代表 类级别的和具体对象无关。需要使用 类名调用,引用方式也能调用【不建议】
非 static 关键字修饰的 属性,方法 代表对象级别的。需要引用去调用
可以使用 static 关键字来定义 静态代码块
1、语法格式:
1 | static { |
2、静态代码在类加载时执行,并只执行一次
继承
关于Java 语言中的继承:
继承是面向对象的三大特征之一,三大特征分别是:封装,继承,多态
继承 “基本” 作用是:代码复用。但是继承 最重要 的作用是:有了继承才有以后”方法的覆盖”和多态机制。
继承语法格式:
1
2
3[修饰符列表] class 类名 extends 父类名 {
类体 = 属性 + 方法
}Java 语言当中的继承只支持单继承,一个类不能同时继承很多类,只能继承一个类
关于继承中的一些术语:
B类继承A类,其中:
A类称为:父类,基类、超类、superclass
B类称为: 子类,派生类、subclass
Java语言中子类继承父类都继承哪些数据呢?
私有的不支持
构造方法不支持
其他数据都支持
虽然Java语言只支持单继承,但是一个类也可以简洁继承其他类,例如:
1
2
3
4C extends B {}
B extends A {}
A extends T {}
// C 直接继承B类,但是C类直接继承T、A类。Java语言中假设一个类没有显示的继承任何类,该类默认继承 JavaSE 库当中提供的 Java.lang.Object类。
方法的覆盖
Java 语言中方法的重载的条件
- 在同一个类当中
- 方法名相同
- 参数列表不同:类型,顺序,个数至少一个不同
- 方法重载和什么无关?
- 和方法的而返回值类型无关
- 和方法修饰符列表无关
Java 语言中方法的覆盖
方法覆盖在什么时候使用?
当父类方法无法满足子类的业务需求时,子类有必要重写聪父类继承的方法
什么条件满足之后构成方法重载?
- 方法覆盖发生在具有继承关系的父子类之间
- 返回值类型相同,方法名相同,形参列表相同
- 访问权限不能更低 【protected -> public】
- 抛出异常不能更多,可以更少
方法覆盖和什么无关?
- 和方法的而返回值类型无关
- 和方法修饰符列表无关
- 私有方法不能继承,所以不能覆盖。
- 构造方法不能继承,所以不能覆盖。
- 静态方法不存在覆盖。因为是通过类名访问的,父子类名肯定不同
- 覆盖只针对方法,不谈属性
1 | // 方法的覆盖 |
多态
语法机制:
Cat 继承 Animal,Bird 继承 Animal。Cat 和 Bird 没有任何继承关系。
关于多态涉及的几个概念:
- 向上转型
- 子类型 –> 父类型 自动类型转换
- 向下转型
- 父类型 –> 子类型 强制类型转换【加强制类型转换符】
- 转型的前提是拥有继承关系
1 | Animal a1 = new Cat();// 向上转型,编译过,一定能运行 |
多态在实际开发中的作用:
分析: 主人喂养宠物这个场景需要经行类型抽象:
- 主人【类】
- 主人可以喂养宠物,所有主人都有这个动作
- 宠物【类】
- 宠物可以吃东西,所有宠物都有吃东西的这个动作
- 降低程序耦合度,提高程序扩展力,能使用多态尽量使用多态。父类型引用指向子类型对象
核心: 面向抽象编程,不要面向具体编程
不使用多态
1 | // 主人类 |
使用多态
1 | // 主人类 |
finall 关键字
表示最终的,不可变的。跟 js 中 const 作用有点类似
- final 修饰的类无法被继承
- final 修饰的方法无法被覆盖
- final 修饰的变量一旦赋值,不可重新赋值
- final 修饰的实例变量必须手动赋值,不能默认赋值
- final 修饰的引用虽然指向某个对象之后不能指向其他对象,但是所指向的对象内部的内存是可以被修改的
- final 修饰的实例变量,一般和static联合使用,被称为常量
1 |
|
package,important
包又称为package吗Java 中引入这种语法机制主要是为了方便程序员的管理。不同功能的类被分门别类放到不同的软件包当中,查找比较方便,管理比较方便,易维护。
怎么定义 package?
在Java 源程序的第一行上编写 package 语句。
package 只能编写一个语句
语法结构:
package 包名;
包名的命名规范:
公司域名倒序 + 项目名 + 模块名 + 功能名;
采用这种方式重名概率较低。因为公司域名具有全球唯一性。
包名要求全部小写,包名也是标识符,必须遵守命令规则
一个包将来对应一个目录
1 | package com.bjpowernode.javase.day11.Test01; |
使用 package 机制后,java 类名 中类名 不再是 文件名。而是 com.bjpowernode.javase.day11.Test01;
编译后 需要创建对应目录:com/javase/day11/Test01.class
另一种方式(编译+运行)
javac -d 编译之后存放路径 Java 源文件的路径
1
2// eg: 将 F:\Hello.java 文件编译后存放到 C:\com 目录下
javac -d C:\com F:\Hello.java将当前路径中 *.java 编译之后放到 c:\目录下
1
2// JVM 的类加载器 ClassLoader 默认聪当前路径下加载
javac -d . *.java
import
当前类中访问其他不同当前目录的的程序时,需要使用import导入其他包中的程序
1 | import com.bjpowernode.javase.test002.Cat; |
控制访问权限修饰符
访问控制权限修饰符来控制元素访问范围
访问控制权限修饰符包括:
- public 表示公开的,在任何位置都可以访问
- protected 同包下,子类中可以访问
- 缺省 同包下可以访问
- private 表示私有的,只能在本类中访问
范围从大到小排序: public > protected > 默认 > private
1 | public class User { |
类,接口 只能采用 public 和缺省的修饰符进行修饰。【内部类除外】
1 | public class A {} |
属性 (四个都能用)
方法 (四个都能用)
super 关键字
关键字,全小写
与 this 对比
this:
目的:调用本类的方法
- this 能出现在实例构造方法
- this 的语法是:”this.” ,”this()”【this() 只能出现在构造方法第一行,目的是构造方法代码复用,通用调用本类其他构造方法】
- this 不能在静态方法中使用
- this 大部分情况可以省略(在区分局部变量和成员变量时不能省略)
super:
目的:创建子类对象的时候,先初始化父类型的特征
- super 能出现在实例构造方法和构造方法中
- super 的语法是:”this.” ,”this()”
- super 不能在静态方法中使用
- super 只能出现在构造方法第一行,通过当前方法去调用父类中其他的构造方法,目的是:代码复用
this() 和 super() 不能共存
1 | public class superTest { |
调用子类构造方法必然调用父类构造方法
在Java语言中不管是new什么对象,最后老祖宗的Object类的无参构造方法(处于栈顶部,最后调用,最先结束)一定会执行
抽象类
什么是抽象类?
类和类之间具有共同特征,将这些共同特征提取出来,形成的就是抽象类。
类本身是不存在的,所以抽象类无法创建对象《无法实例化》
抽象类属于什么类型?
抽象类也属于引用数据类型。
抽象类怎么定义?
语法:
[修饰符列表] abstract class 类名 {
类体;
}
抽象类无法实例化,无法创建对象,所有它是用来被子类继承的
1
2
3
4
5
6
7
8
9
10
11
12
13 // 银行账户类
abstract class Account {
}
// 子类继承抽象类,子类可以实例化
class CreditAccount extend Account {
}
// 抽象类的子类也可以是抽象的类
//abstract class CreditAccount extend Account {}
CreditAccount c1 = new CreditAccount();由于final修饰的类不能被继承,所以 final与abstract不能同时出现
抽象类有构造方法,这个方法提供给子类使用
抽象方法:表示没有实现的方法,没有方法体的方法
特点:
- 没有方法体,以分号结尾
- 前面有 abstract 关键字
1 public abstract void dosome();抽象类中不一定有抽象方法,抽象方法必须出现在抽象类中
非抽象子类继承抽象类时,会继承抽象方法。但抽象方法只能出现在抽象类中。所以要把子类改为抽象的,或者重写这个继承的抽象方法(也可以叫实现)避免编译出错
一个非抽象类继承一个抽相类,非抽象类必须要将抽象方法实现
面向抽象编程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 // 动物抽象类
abstract class Animal {
// 抽象方法
public abstract void move();
}
class Bird extends Animal {
// 实现抽象方法
public void move() {
System.out.println("鸟儿在飞!");
}
}
public class AbstractTest02 {
public static void main(String[] args) {
// 多态:编译是一种状态,运行是另一种状态
// 父类型引用指向子类型对象
Animal a = new Bird();// 向上转型
}
}
接口
接口也是一种引用数据类型
接口也是完全抽象的(抽象类是半抽象)或者也可以说接口是特殊的抽象类
接口怎么定义,语法是什么?
[修饰符列表] interface 接口名 {}
1
2
3 interface A {}
interface B {}
interface C {}接口支持多继承,一个接口可以继承多个接口
1
2
3 interface D extends A,B,C {
}接口中只包含两个部分内容,常量和抽象方法
1
2
3
4
5 interface MyMath {
public abstract int sum(int a, int b);
// 省略写法
// int sum(int a, int b);
}接口中的抽象方法都是公开的
接口中的方法都是抽象方法,所以接口中方法都不能有方法体
接口中的常量不能被修改
1
2
3
4
5 interface MyMath {
public static final double PI = 3.1415926;
// 省略写法
// double PI = 3.1415926;
}
接口的基础语法
1 | interface MyMath { |
类和类之间叫做继承,类和接口之间叫做实现(也可以看作”继承“)
继承使用 extends 关键字
实现使用 implements 关键字
当一个非抽象的类实现接口的话,必须将接口中所有方法实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class Test {
public static void mian(String[] args) {
// 面向接口编程
MyMath mm = new MyMathImpl();
mm.sum(1,2);
}
}
class MyMathImpl implements MyMath {
public int sum(int a, int b) {
return a + b;
}
public int sub(int a, int b) {
return a - b;
}
}一个类可以实现多个接口
这种机制弥补了Java类只支持单继承带来的缺陷
类型转换
向下转型养成好习惯,先用 instanceof 判断
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class Test {
public static class void main(String[] args) {
A a = new D();
B b = new D();
C c = new D();
// 多态
B b2 = (B)a;
// 接口 E,D 没有继承关系,强转时编译不会出错,但是运行时可能出现 ClassCastException 异常
E e = (B)a;
}
}
interface A {}
interface B {}
interface C {}
interface E {}
interface D extends A,B,C {}继承和实现同时存在
先继承,再实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class Test {
public static void main(String[] args) {\
// 多态
Flyable f = new Cat();
}
}
class Animal {}
// 插拔的是接口,通常提取的是行为动作
interface Flyable {
void fly();
}
// 给猫插上翅膀
class Cat extends Animal implements Flyable {
public void fly() {}
}
接口在开发中的作用
注意:接口在开发中的作用类似于多态在开发中的作用
强制转换过程中,如果是类转换成接口,那么类和接口之间可以没有继承关系
多态:面向抽象编程,不要面向具体编程。降低程序耦合度。提高程序拓展力。
面向接口编程少不了多态(接口+多态才可以达到降低耦合)
任何一个接口都有调用者和实现者,解耦合是指接触调用者和实现者的耦合
1 | // 顾客 |
类型和类型之间的关系
is a, has a, like a
is a:
Cat is a Animal(猫是一个动物)
凡是满足 is a 的表示 继承关系
1 A extends Bhas a:
I has a Pen (我有一支笔)
凡是能满足 has a 关系的表示 关联关系
关联关系通常以”属性“的形式存在
1
2
3 A {
B b;
}like a:
Cooker like a FoodMemu (厨师像一个菜单一样)
凡是能满足 like a 关系的表示 实现关系
实现关系通常是: 类实现接口
1 A implements B;
抽象类和接口有什么区别?
抽象类是半抽象的,接口是完全抽象的
抽象类中没有构造方法,接口中没有构造方法
接口之间可以多继承,类之间只能单继承
一个类可以同时实现多个接口,一个抽象类只能继承一个类
开发工具 IDE使用
快捷键:
生成main方法: psvm
生成 打印语句:sout
删除一行:Ctrl + Y
编辑窗口变大变小:Ctrl + shift + F12
显示隐藏项目树:alt + 1
新增/新建/添加快捷键: alt + insert
tab 页切换: alt + 左右箭头
运行:Ctrl + shift + F10
纠错: alt + 回车
UML
UML 是一种统一建模语言,一种图标式语言,我们可以通过UML来描述一下继承结构。 就像是建筑师的设计图纸
源码及API文档
JDK 类库的根类:Object
这些方法是所有子类通用的,任何一个类默认继承object,计算没有直接继承,最终也会默认继承。
有哪些常用的文档?
- 去源代码查看 【C:\Program Files\Java\jdk1.8.0_321\src】
- 去查阅Java类库帮助文档
toString
返回对象的字符串表示形式。类名+@+16进制对象哈希码
1 | getClass().getName() + '@' + Integer.toHexString(hashCode()) |
equals
指示一些其他对象是否等于此对象。
1 | public boolean equals(Object obj) { |
默认是通过 == 判断是否相等,== 是判断两个Java对象的内存地址。如果我们需要判断两个对象内容相同则需要重写 equals 方法
finalize
垃圾回收器负责调用,此方法不需要调用,只要重写即可。带系统执行垃圾回收时会触发这个方法
1 | class Person { |
java 中的垃圾回收机制不会轻易启动,jdk9版本后废弃了
clone
对象的克隆
hashCode
获取对象哈希值的一个方法,返回值是经过哈希算法转换的数字,等同于内存地址
匿名内部类
也叫做局部内部类。不推荐使用,但是要看懂别人写的
什么是内部类?
在类的内部又重新定义了一个类,被称为内部类
内部类的分类
- 静态内部类:类似于静态变量
- 实例内部类:类似于实例变量
- 局部内部类:类似于局部变量
1 | class Test { |
匿名内部类可以省略 计算的接口需要实现类
1 | // 负责计算的接口 |
数组
Java 语言中的数组属于引用数据类型。父类是 Object
可以储存 任意数据类型。储存在堆内存,引用类型在数组中存储的是引用 内存地址
数组分类:一维,二维,三维,多维数组
数组中元素的类型要求统一【装苹果的数组就不能装香蕉】
优点:
- 检索数据效率最快
- 元素类型相同,所以占用空间相同
- 储存空间是连续的
缺点:
- 随机增删元素,会影响后边元素向前或向后移动,效率低【对数组最后一个元素增删无影响】
- 内存中连续的大空间少。无法存储大数据
语法
1 | // 定义 |
主函数 main 的参数
1 | class Test { |
扩容
数组长度一旦确定就不可变,那数组满了怎么办?
java中的方式:先建一个大容量数组,再将小容量数组中数组一个一个拷贝过去
结论:效率低,少用
数组拷贝方法:
System.arraycopy(src, srcPos, dest, destPos, length)
1 | int[] a1 = {1,2,3,4,5,6,7,8}; |
二维数组
1 | int[][] a = { |
数组的工具方法
java.utils.Arrays
常用的方法,sort, binarySearch【二分法查找】
binarySearch()
1
2// 返回对应下标,没有返回 -1
Arrays.binarySearch(数组, 查找的值);sort()
String
- 双引号括起来的都是String对象
- 字符串不可变,不能修改
- JDK中字符串都是直接存储在方法区的字符串常量池当中【垃圾回收机制不会回收常量池数据】
1 | /* |
常用构造方法:
```java
String s = new String(“”)1
2
3
2. ```java
String s = ""```java
String s = new String(char数组)1
2
3
4. ```java
String s = new String(char数组,起始下标,长度)```java
String s = new String(byte数组)1
2
3
6. ```java
String s = new String(byte数组,起始下标,长度)
chartAt
返回指定索引处的 char 值
1 "中国人".charAt(1);// 国
compareTo
按照字典(ascll码表值)顺序去比较字符串大小.
前小后大: -1
前后一致:0
前大后小:1
1 "abc".compareTo("abce"); // -1
contains
是否包含某字符串
1 "Hello java".contains("java");// true
endsWith
是否以某字符串结尾
equalsIgnoreCase
忽略大小写的比较字符串相等
getBytes
将字符串对象转为字节数组
1 byte[] bytes = "abcde".getBytes();
isEmpty
判断某个字符串是否为空
toChartArray
将字符串转换为char数组
valueOf
底层调用 toString()
indexOf,lastIndexOf
某个字符串在当前字符串中第一次/最后一次出现的下标索引
stringBuffer
stringBuider 与 stringBuffer 都可以拼接字符串,区别在于前者是线程安全的,后者不是
用 + 拼接字符串会导致方法区字符串常量池创建多个对象。造成空间浪费
1 | // 创建一个初始化容量为16个byte[] 数组。(字符串缓冲区),创建时可以估计给一个容量(字节) |
包装类
Java为8中数据类型提供了8种包装类,父类是Object。
包装类可以将基本数据类型包装成对象
基本数据类型 | 包装类型 |
---|---|
byte | java.lang.Byte(父类Number) |
short | java.lang.Short(父类Number) |
int | java.lang.Integer(父类Number) |
long | java.lang.Long(父类Number) |
float | java.lang.Float(父类Number) |
double | java.lang.Double(父类Number) |
boolean | java.lang.Boolean(父类Object) |
char | java.lang.Character(父类Object) |
包装类还提供了各种类型的拆箱方法,将包装类转为基本数据类型。不过后面都废弃了,jdk1.5后支持了自动装箱拆箱
1 | // 手动装箱 |
java 中为了提高程序效率,将 [-128, 127]数字提前包装创建好了,所以这个区间的的数据不在需要new,直接从常量池中获取
1 | Integer a = 128; |
Number 常用方法
- parseInt 转整形
日期处理
java.utils.Date 对象
格式化时间的库:java.text.SimpleDateFormat
1 | /* |
数字格式化
java.text.DecimalFormat 类
格式:
#:代表任意数字
,:代表千分位
.:代表小数点
1 | double xx = 10023.222502056; |
高精度 BigDecimal
BigDecimal 属于大数据,精度极高。专门用在财务软件当中
1 | BigDecimal bd1 = new BigDecimal(100); |
随机数
产生一个int类型取值范围内的数字
1 | Random random = new Random(); |
枚举
枚举编译之后也是生成 class 文件
枚举是一种引用类型
当一个方法返回结果超过两种且可以一一列举的情况建议使用枚举
1 | public enum Season { |
异常机制
当Java程序运行出现不正常的情况时,Java语言会把程序的异常信息输出到控制台。供程序员参考,以对程序经行修改
所有异常都发生在运行阶段
异常声明
java 内置了异常类,可以通过 创建异常对象 再抛出使用
1 | ArithmeticExcepetion error = new ArithmeticExcepetion("异常信息"); |
异常的处理
如果这个方法你想让调用者处理标错就throws,否则就 try catch
try catch 捕获异常并处理
- catch 后边小括号中得了类型可以是具体类型,也可以是父类型
- catch 可以写多个。便于精确处理
- 编写多个catch 时,捕获错误要从小到大
1
2
3
4
5
6
7
8
9
10
11
12
13try {
...
// return 语句再方法中肯定是 最后执行的,finall 会在return 前执行,不会影响return 的值
return 语句
} catch(错误1|错误2|错误3 e) {
// 多个错误声明 是 jdk8 新特性
// 调用此方法,会在控制台打印异常详情,建议使用
e.printStackTrace();
} finally {
// 除非 System.exit(0); 推出JVM虚拟机。否则 此处代码一定会执行
}方法声明位置上,使用 throws 关键字,抛给上一级
1
2
3
4
5
6
7
8
9
10public class Test {
public static void main(String args) {
doSome();
}
public static void doSome() throws 异常1,异常2{
int a = 10;
int b = 0;
int rs = a/b;
}
}
自定义异常
业务中的异常很多,内置异常不满足。此时需要自定义异常。
自定义异常一定要抛出去让调用者处理,否者没有意义
异常的应用是代替 return 字符串错误提示
如何自定义异常?
- 编写一个类继承 Exception 或者 RuntimeException
- 提供两个构造方法,一个无参,一个待用String 参数的
1 | public class MyException extends Exception { |
集合
集合本身是一个容器,用来存储引用类型的数据,不能直接存储基本数据类型
Java中不同的集合对应不同的数据结构,例如:数组,二叉树,链表,哈希表。。。
- new ArrayList(); 底层是数组
- new LinkedList(); 底层是链表
- new ThreeSet(); 底层是二叉树
在Java中集合分为两大类:
一类是存储单个元素
这类集合祖先是 java.utlls.Collection 【可迭代,可遍历】
- list 有序,可重复,元素有下标
- ArrayList【类】 底层数组,非线程安全
- LinkedList【类】 底层双向链表
- Vector【类】 底层数组,线程安全,使用较少【线程安全有其他方案】
- Set 无序,不可重复,元素无下标
- hashSet【类】 底层new了hashMap【底层哈希表】
- SortedSet 自动排序
- TreeSet【类】 底层new了TreeMap【底层二叉树】
- list 有序,可重复,元素有下标
一类是键值对存储形式
这类集合祖先是 java.utlls.Map, key 是无需不可重复
- HashMap【类】 非线程安全,底层哈希表
- Hashtable【类】 线程安全,底层哈希表,使用较少【线程安全有其他方案】
- Properties【类】 线程安全,键,值要求是String类型
- SortedMap 会自动排序
- TreeMap【类】底层二叉树,key按自动大小排序
Map集合的key部分存储的就是 Set集合
Collection
不使用泛型之前,Collection 中可以存储Object的所有子类型。使用泛型之后,Collection中只能储存某个具体类型
Collection 中常用方法
add 添加元素
size 获取集合中元素个数
void 清空集合
contains(Object o) 判断集合中包含 某对象 o
remove 删除某个元素
isEmpty 集合中数据个数是否为0
toArray 将集合转换为数组
iterator 返回迭代器
dynchronizedList(list l) 让集合变成线程安全的
1
2
3
4// 元素内容发生改变时。需要冲新获取 iterator
Iterator it = c.iterator();
it.hasNext();// 有就返回true
it.next();// 返回迭代的下一个元素
contains 方法解析
1 | public Test { |
按照内存地址,x 不在 C 当中。 contains 实现时调用了 equals() 方法,把x与集合中元素比较
放进集合的元素 一定要重写 equals 方法,非则比较时比较的是内存地址而不是内容
数组坍台
1 | // 迭代过程中不能修改集合结构:例如删除,新增 |
list 接口常用方法
- get(int index) 根据下标获取元素
- indexOf(Object o) 获取指定对象第一次出现的下标索引
- lastIndexOf(Object o) 获取指定对象最后一次出现的下标索引
- remove(int index) 删除指定索引的元素
- set(int index, E element) 修改指定位置的元素
- add(Object element) 新增
ArrayList 指定容量及扩容
默认初始化容量为 10.
扩容: 元素超过初始化容量时,自动扩容,创建新的数组【 原容量 1.5 倍】把数据放进去
1 | // 初始化指定容量,底层先创建一个长度为0的数组,当添加第一个元素时,初始化容量20 |
ArrayList有参构造
1 | Collection c = new HashSet(); |
linkedList
底层是链表结构。随机增删效率较高。检索效率低
HashSet
- 不可重复
- 存储位置和取出位置不一致
- 放到hashSet 集合的元素实际上是放到HashMap的key部分了
Map
Map和Collection没有继承关系
Map集合以键值对放啊是存储
key,value都是引用类型
常用方法
- V put(K key, V value) 新增
- V get(Object key) 通过key获取value
- void clear() 清空Map集合
- boolean containsKey(Object key) 判断Map中是否包含某个key
- boolean containsValue(Object value) 判断Map中是否包含某个value
- isEmpty() 判断Map集合中元素个数是否为0
- Set<K> keyset() 获取Map集合中所有的value(所有的键是一个set集合)
- V remove(Object key) 通过key删除键值对
- int size() 返回集合中键值对的个数
- Collection<V> values() 获取Map集合中所有的value,返回一个Collection
- Set<Map.Entry<K,V>> entrySet() 将Map集合转Set集合
Map对象的遍历
通过遍历key,来获取数据
getKeys, 再遍历得到key,使用 get(key)获取值
通过 entrySet,再使用 foreach
1
2
3
4
5
6
7
8
9
10// 适合大数据量
HashMap<String,String> map = new HashMap<>();
map.put("1", "张三三");
map.put("2", "李思思");
map.put("3", "王五五");
map.put("4", "五六七");
Set<Map.Entry<String,String>> map2 = map.entrySet();
for (Map.Entry item: map2) {
System.out.println(item.getValue());
}
Properties
键值对限制为String类型
增强for循环
foreach,缺点是没有下标
1 | for(int item: arr) { |
哈希表
是数组和单链表的结合体,充分发挥各自的有点
集合工具类
java.util.Collections 集合工具类,方便操作
1 | // 变成线程安全的 |
泛型机制
编译期间的新特性,jdk5新增
1 | /* |
自动类型推断机制
jdk8新增,又称钻石表达式
1 | // 右边的 <> 可以省略 |
自定义泛型
myType 是一个标识符随便命名,java源代码一般用T,E
1 | public class Test <myType>{ |
IO流
文件的输入(硬盘->内存)和输出(内存->硬盘)。通过IO可以完成硬盘文件的读和写
- 字节流:每次读取一个字节,啥类型都能读取
- 字符流:每次读取一个字符,这种流是为了方便读取普通文件存在的。如:.text
IO流的四大家族
- java.io.InputStream 字节输入流
- java.io.OutPutStream 字节输出流
- java.io.Reader 字符输入流
- java.io.Writer 字符输出流
方法名以 Stream 结尾的都是字节流。
四大家族首领都是抽象类。
所有流都有close方法
所有输出流都是可刷新的(实现了java.io.Flushable)刷新的作用就是清空管道,最终输出完记得调一下
6种类型的流:
File 文件专属
*FileInputStream
read() 读
available() 剩余多少字节未读
skip() 跳过多少字节不读
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 字节读取
FileInputStream fis = new FileInputStream("data.text");
int readCount = 0;
while (readCount != -1) {
// 可以一次读5个字节
// byte[] bytes = new byte[4];
// 一次读完,不适合大文件
byte[] bytes = new byte[fis.available()];
readCount = fis.read(bytes);
if (readCount > 0) {
// 将byte转字符串
System.out.println("str = " + new String(bytes,0,readCount));
}
}
fis.close();*FileOutPutStream
write(文件名, 追加模式)
flush() 清空连接管道
1
2
3
4
5
6
7
8
9
10// 第二参数 true 表示追加,不是重写
FileOutputStream fis = new FileOutputStream("data.text", true);
byte[] bt = {100, 120,110,111};
// 字符串转byte在写入
String s = "我是一个中国人";
byte[] bs = s.getBytes();
fis.write(bt);
fis.write(bs);
fis.flush();
fis.close();文件的复制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16FileInputStream fis = null;
FileOutputStream fos = null;
fis = new FileInputStream(getPath("data.text"));
fos = new FileOutputStream("D:/javaSE/daySE01/data1.text");
// 核心:边写边读
byte[] bytes = new byte[1024*1024];// 最多拷贝1M
int readCount = 0;
while ((readCount = fis.read(bytes)) != -1) {
System.out.println("readCount = " + readCount);
fos.write(bytes, 0, readCount);
}
fos.flush();
fis.close();
fos.close();FileReader
文件字符输入流,只能读取普通文本
1
2
3
4
5
6
7
8
9FileReader reader = null;
fr = new FileReader(getPath("data.text"));
char[] chars = new char[4];
int readCount = 0;
while ((readCount = fr.read(chars)) != -1) {
System.out.println(new String(bytes, 0, readCount));
}
fr.close();FileWriter
文件字符输出流,只能写到普通文本()能用记事本打开的都是普通文本
1
2
3
4
5
6
7FileWriter out = null;
out = new FileWriter("D:\\javaSE\\daySE01\\src\\data.text", true);
char[] charts = {'我','是','谁','?'};
out.write(charts);
out.write("我是一个字符串。");
out.flush();
out.close();复制文件
1
2
3
4
5
6
7
8
9
10
11
12FileWriter out = null;
FileReader in = null;
out = new FileWriter("D:\\javaSE\\daySE01\\src\\data.text", true);
in = new FileReader("D:\\javaSE\\daySE01\\src\\target.text");
char[] charts = new char[1024*512];// 限制1M
int readCount = 0;
while ((readCount = in.read(charts)) != -1) {
out.write(charts, 0, readCount);
}
out.flush();
in.close();
out.close();
Buffered 缓冲流 【不需要指定char,byte 缓冲字符】
BufferedInputStream
BufferedOutPutStream
BufferedReader
BufferedReader(Reader) 只能传入字符流
readLine() 读取一行字符
1
2
3
4
5
6
7
8
9
10
11
12
13
14FileReader fr = new FileReader("D:\\javaSE\\daySE01\\src\\data.text");
FileInputStream
// 当一个流构造方法需要传入一个流时:被传入的流叫做节点流 fr
// 外部负责包装的流,叫做包装流/处理流 br
BufferedReader br = new BufferedReader(fr);
String rs = null;
while ((rs = br.readLine()) != null) {
System.out.println(rs);
}
// 节点流不需要手动关闭,会自动关闭
br.close();BufferedWriter
1
2
3
4BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("D:\\javaSE\\daySE01\\src\\data.text",true)));
bw.write("xxx");
bw.flush();
bw.close();
Data 数据流
DataOutPutStream 写入数据,只能用 DataOutPutStream 按写入顺序读取指定类型,才能正常去除数据
*DataInputStream
1
2
3
4
5
6
7DataInputStream in = new DataInputStream(new FileInputStream("D:\\javaSE\\daySE01\\src\\data2.text"));
System.out.println(in.readBoolean());
System.out.println(in.read());
System.out.println(in.readUTF());
System.out.println(in.readChar());
in.close();*DataOutPutStream
1
2
3
4
5
6
7
8
9
10
11
12
13DataOutputStream out = new DataOutputStream(new FileOutputStream("D:\\javaSE\\daySE01\\src\\data2.text"));
boolean flag = false;
int a = 1;
String b = "b";
char c = 'c';
out.writeBoolean(flag);
out.write(a);
out.writeUTF(b);
out.writeChar(c);
out.flush();
out.close();
Print 标准输出流
PrintWriter
PrintStream
1
2
3
4
5
6
7
System.out.println("xxx");
// 将标准输出流方向修改到 log 文件, 标准输出流不需要关闭
PrintStream printStream = new PrintStream(new FileOutputStream("D:\\javaSE\\daySE01\\src\\log.text", true));
printStream.println("日志1");
printStream.println("日志2");
Object 对象专属流
序列化:将内存中的java对象一块一块的写入到硬盘文件的过程
反序列化:将硬盘上的碎片数据在内存中恢复成对象
对象要想参与序列化,需要实现 Serializable 接口
Serializable 接口:
这是一个标志接口,里边啥也没有.实现这个接口的类 java虚拟机会提供一个序列化版本号。【将一个对象序列化后,如果之后类发生了变化,则后边想反序列化对象就会报错】
序列化版本号的作用?
Java区分一个类,先比较类名,再比较序列化版本号
自动生成序列化的缺陷
几年前序列化的一个对象,后续不能更改代码。否则几年前序列化的对象不能反序列化回来
一次序列化多个对象: 使用集合把多个对象装进去,集合与其中对象都需要实现 Serializable 接口
结论:凡是实现Serializable 接口的类应该手动设置,不在改变
1
2
3public Test implemtnts Serializable {
private static final long serialVersionUID = 1L;
}ObjectInputStream
1
2
3
4
5// 反序列化
oos = new ObjectInputStream(new FileInputStream("D:\\javaSE\\daySE01\\src\\student"));
Object obj = oos.readObject();
System.out.println("obj = " + obj);
oos.close();ObjectOutPutStream
1
2
3
4
5
6
7// 序列化
Student s = new Student("www", 16);
ObjectOutputStream oos = null;
oos = new ObjectOutputStream(new FileOutputStream("D:\\javaSE\\daySE01\\src\\student"));
oos.writeObject(s);
oos.flush();
oos.close();如果某个对象中的属性不希望参与序列化可以使用 属性修饰符:transient
转换流(将字节流-> 字符流)
InputStreamReader
1
2
3
4
5
6
7FileInputStream in = null;
InputStreamReader reader = null;
// 字节流
in= new FileInputStream("D:\\javaSE\\daySE01\\src\\data.text");
// 字节流 => 字符流
reader = new InputStreamReader(in);OutPutStreamWriter
1
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("D:\\javaSE\\daySE01\\src\\data.text",true)));
通过properties 读取配置文件信息
1 | username=www |
1 | FileReader reader = new FileReader("D:\\javaSE\\daySE01\\src\\data2.text"); |
Java.io.File 类
FIle 类与流无关,不能读写,File 对象表示 文件和目录的抽象形式。
1 | // 创建 File, |
常用方法
- isFile 判断是否文件
- isDirectory 判断是否目录
- getName 获取文件名
- lastModified 获取上次修改时间
- length 获取文件大小
- listFile 获取当前目录下所有子文件
多线程
什么式进程?线程?
进程是一个应用程序(进程可以启动多个线程)
线程是一个进程的中的执行场景/执行单元
进程,线程关系
进程A和进程B内存独立不共享
线程A和B,堆内存和方法区共享,栈内存独立
真正的多线程并发
t1线程执行t1,t2线程执行t2.两者不会相互影响。这叫做真正的并发
单核CPU 不能做到真正的多线程并发,但可以做到给人多线程并发的 感觉、
实线线程的方式
编写一个类直接继承java.lang.Thread, 重写run方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class ThreadTest {
public static void main(String[] args){
// 创建线程
MyThread td = new MyThread();
// 启动线程.在JVM中开启一个新的栈空间,start方法就结束了。 直接调用run方法不会开辟新空间开启线程
td.start();
// 其他代码
...
}
}
class MyThred implaments Thread {
public void run() {
System.out.println("新的线程");
}
}编写一个类实现java.lang.Runnable接口 (这种方式拓展性好)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class ThreadTest {
public static void main(String[] args){
// 创建线程
MyRunnable td = new MyRunnable();
Thread t = new Thread(td);
t.start();
// 其他代码
...
}
}
class MyRunnable implements Runnable {
public void run() {
System.out.println("新的线程");
}
}匿名内部类方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14public class ThreadTest {
public static void main(String[] args){
// 创建线程
Thread t = new Thread(new MyRunnable() {
run() {
...
}
});
t.start();
// 其他代码
...
}
}实现 Callable 接口
可以获取线程返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class ThreadTest {
public static void main(String[] args){
FutureTask task = new FutureTask(new MyRunnable(){
public Object call() throw Exception {
...
return null;
}
});
// 创建线程
Thread t = new Thread(task);
t.start();
// 获取结果, 此处会阻塞
Object obj = task.get();
}
}
class MyRunnable implements Callable {
public void run() {
System.out.println("新的线程");
}
}
线程的生命周期
- 新建状态:刚 new 出来
- 就绪状态:调用了 start 方法,具有抢夺CPU时间片的权利
- 运行状态:内部调用了 run方法后进入,当抢夺的时间片用完再次回到就绪状态,再次抢夺时间片回来继续执行代码(这个来回切换状态的过程叫做JVM的调度)
- 阻塞状态:此时线程会放弃之前占有的CPU时间片,例如进入到键盘输入,休眠等方法
获取线程对象,名字
默认线程名字 Thread-n 【n从开始递增】
1 | // 创建线程 |
获取当前线程
哪个线程在执行run方法,就获取哪个线程
1 | Thread currentThread = Thread.currentThread(); |
线程的sleep方法
静态方法(跟谁调用没关系),参数 毫秒。让当前线程进组阻塞状态
1 | Thread.sleep(1000*3)'// sleep 3s |
唤醒线程
1 | // 创建线程 |
强行终止线程
容易丢失数据,不建议使用
1 | // 创建线程 |
合理的终止线程
1 | MyRunnable td = new MyRunnable(); |
线程的调度
常见的线程调度模型,Java采用第一种
- 抢占式调度模型:哪个线程优先级高,强夺CPU时间片的概率就高一些
- 均分式调度模型:平均分配CPU时间片,每个线程占用CPU时间片长度一样
线程调度相关方法
setPriority(int newPriority) 设置线程的优先级
int getPriority() 获取线程优先级 [1,10],默认5
static void yield() 让位,暂停当前线程让其他线程先执行(非阻塞),从运行状态退回就绪状态
join() 合并线程
1
2// t合并到当前线程,当前线程受阻塞,t线程执行直到结束
t.join();
线程安全问题
以后在开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了。这些代码我们都不需要编写.
最重要的是:你要知道,你编写的程序需要放到一个多线程的环境下运行,你更需要关注的是这些数据在多线程并发的环境下是否是安全的。(重点: *t )
啥时候数据会在多线程并发存在安全问题
满足以下三个条件就会存在安全问题:
- 多线程并发
- 有数据共享
- 共享数据有修改行为
怎么解决?
线程排队执行,不能并发。(线程同步机制)
异步编程模型:异步就是并发
同步编程模型:同步就是排队
1 | // 线程同步机制语法 |
理解线程同步锁机制 :例如🚾就一个🕳,一个人上时会🔒🚪,第二个人就进不去,只有等第一个人解决完打开🔒开🚪后,第二个人才能进去, 人就像线程,共享的对象就是🕳
死锁现象
两个线程t1, t2,共享🔒s1,s2。 t1 先锁 s1, 再锁s2。t2先锁s2,再锁s1。 结果t1锁住s1,等待s2,t2锁住s2,等待s1。这种现象就叫死锁
1 | public class Test { |
三种变量
- 实例变量:堆
- 静态变量:方法区
- 局部变量:栈
局部变量永远不会有线程安全问题,因为 不共享,一个线程一个栈
开发中如何避免
不能直接使用线程安全的数据类型,很导致程序执行效率低
- 经量使用局部变量代替实例变量和静态变量
- 如果必须是实例变量,可以考虑创建多个对象,不共享内存
- 使用 synchroized,线程同步机制
守护线程
如 System.gc(); 也叫做后台线程。一般守护线程是死循环。只要用户线程结束,守护线程自动结束
1 | // 自定义守护线程 |
定时器
java.util.Timer 很少用,框架大多会提供定时任务功能
1 | // 定时任务 |
wait 和 notify 方法
它们并不是线程对象专属方法,所有对象都能调用
wait
1
2 Object o = new Object();
o.await(); 让正在o对象上活动的线程进入等待状态,无期限等待,百到被唤配为止。
o.wait() ;方法的调用,会让”当前线程(正在o对象上活动的线程)”进入等待状态,
notify
唤醒o对象上沉睡的线程
1 o.notify();
反射机制
通过Java 语言中的反射机制可以操作字节码文件。
相关包的位置: java.lang.reflect.*
相关重要的类
- java.lang.Class 字节码文件
- java.lang.reflect.Method 字节码中的方法
- java.lang.reflect.Constructor 字节码中构造方法字节码
- java.lang.reflect.Field 字节码中属性字节码
获取字节码的三种方式
java.lang.Class
1
Class c1 = Class.forName("java.lang.String");
java 中任意一个对象都有一个方法:getClass()
1
2
3
4String s = "abc";
// x 代表String.class 字节码文件,x代表String 类型
Class x = s.getClass();
c1 = x;// truejava语言中任何一种类型,包括基本数据类型,他都有.class属性
1
2Class z = String.class;// 代表String类型
z==x;// true
通过反射实例化对象
获取到class能干吗?
通过 newInstance 实例化对象,实际上调用了无参构造方法,必须保证无参构造存在。【比普通方式灵活】
1 | Class c = Class.forName("com.xxx.java.bean.User"); |
可以同过配置项,动态加载不同的类
1 | // chapter25/classinfo.properties |
1 | // 创建读取流 |
Class.forName 发生了什么?
Class.forName 会导致类加载。如果你希望一个类的静态代码块长记性,其他一律不执行 可以这样使用 Class.forName(className);
文件路径问题
复制引用的方式只在Idea中有效。代码在其他位置打开可能不一样。
src是类路径的根路径,在src下的都是类路径:
1 | /* |
以流的形式返回
1 | InputStream rd = Thread.currentThread().getContextClassLoader().getResourceAsStream("test.properties"); |
资源绑定器
java.util 包下提供了一个资源绑定器,便于获取属性配置文件中的内容
只能绑定 xxx.properties 文件,绑定时不带后缀。并且这个名,必须在类路径下
1 | ResourceBundle bundle = ResourceBundle.getBundle("test"); |
反射属性
1 | public class reflectTestFiled { |
1 | Class studentClass = Class.forName("com.zkrd.officialwebsite.util.reflectTestFiled"); |
反编译
通过反射机制,反编译一个类
1 | Class studentClass = null; |
怎么通过反射机制访问一个Java对象的属性?
1 | Class studentClass = Class.forName("com.zkrd.officialwebsite.util.reflectTestFiled"); |
反射方法
方法体中的逻辑代码无法反编译
1 | Class userClass = Class.forName("com.zkrd.officialwebsite.util.reflectTestFiled"); |
通过反射机制怎么调用一个方法?
1 | Class userClass = Class.forName("com.zkrd.officialwebsite.util.reflectTestFiled"); |
通过反射机制创建对象
1 | Class c = Class.forName("com.zkrd.officialwebsite.util.reflectTestFiled"); |
通过反射获取一个类的父类,已经实现的接口
1 | Class c = Class.forName("com.zkrd.officialwebsite.util.reflectTestFiled"); |
可变长度参数
语法: int… args。 根js中的拓展运算符有点类似
- 可变长度要求参数个数是:0~N
- 可变长度参数只能有一个,而且只能在最后
- 可变长度可以当作一个数组处理
1 | public static void m1(int a, Sting... args1) {} |
注解
注解Annotation是一种引用数据类型。编译之后也是生成 xxx.class文件
语法格式:
[修饰符列表] @interface 注解类型名 {}
注解可以用在什么地方?
注解使用时的语法格式是:
@注解类型名
注解可以出现在类,属性,方法,变量上…
注解还可以出现在注解类型上
JDK内置的注解
Deprecated
用Deprecated 注释的程序元素,不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择【此代码已过时】
Override
表示一个方法重写,给编译器参考的,运行无用
元注解
用来标注’注解类型的注解,称为元注解
常见的元注解
Target
标示被注解的注解可以使用在哪些地方
1
2 // 只能出现在方法上Retention
表示被注解的注解最终保存在哪个位置
1
2 // 保存在Java源代码中
如果一个注解当中有属性,那么必须给属性赋值,除非有默认值
1 | // 生成注解 |
注解中的属性可以是?
byte,short,int, long,double,boolean,char,string,class,枚举类型。以及以上每一种的数组形式
1 |
|
反射注解
如果希望这个注解可以被反射:
@Retention(Retentionpolicy.RUNTIME})
获取某个类上的注解
1 | Class c = Class.forName('xxxxx.MyAnnotation.class'); |
获取方法上注解自定义属性的值
1 | // 获取类 |
JDK类加载器
负责专门加载类的命令/工具
jdk 中自带了3个类加载器
- 启动类加载器
- 扩展类加载器
- 应用类加载器
假设这样一段代码:
1 | String s = "abc"; |
代码执行前,会将所需要类全部加载到JVM当中。通过类加载器加载,看到以上代码类加载器会找String.class文件,找到就加载,那么是怎么经行加载的呢?
首先启动类加载器
:路径 %JAVA_HOME%/jre/lib/rt.jar [里边都是JDK核心类库]
1加载不到就通过扩展类加载器加载
:路径 %JAVA_HOME%/jre/lib/ext
如果2也找不到就会通过应用类加载器加载
注意:应用类加载器专门加载:classoath中的类
优先从 1(父)2(母)【双亲委派机制】加载,防止类加载代码植入
- 本文作者: 王不留行
- 本文链接: https://wyf195075595.github.io/2023/01/09/programming/java/入门基础/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!