一.基础语法
1.1.常用的DOS命令及注释1.2.命名规则与规范1.3.变量
1.3.1.基本数据类型1.3.2.引用数据类型1.3.3.数据类型运行规则 1.4.运算符
1.4.1.算数运算符1.4.2.赋值运算符1.4.3.比较运算符1.4.4.逻辑运算符1.4.5.位运算符1.4.6.条件运算符 1.5.流程控制
1.5.1.顺序结构1.5.2.循环结构 二.数组
2.1.一维数组2.2.多维数组2.3.常见的数组异常2.4.数组的特点2.5.Arrays工具类的常用方法 三.面向对象
3.1.类的概念与结构
3.1.1.类和对象的区别与联系3.1.2.属性3.1.3.方法3.1.4.构造器3.1.5.代码块3.1.6.内部类3.1.7.类的语法格式 3.2.面向对象的三大特性
3.2.1.封装性3.2.2.继承性3.2.3.多态性 3.3.关键字
3.3.1.接口3.3.2.抽象类3.3.3.其他关键字 3.4.单例模式
3.4.1.饿汉式3.4.2.懒汉式 四.常用类
4.1.字符串相关类
4.1.1.String4.1.2.StringBuffer和StringBuilder 4.2.Object
4.2.1.基本概念4.2.2.常用方法 4.3.包装类
4.3.1.基本概念4.3.2.包装类的转换 4.4.Math类
4.4.1.基本概念4.4.2.常用方法 4.5.时间处理的常用方式4.6.Java比较器
4.6.1.基本概念4.6.2.Comparable4.6.3.Comparator 五.异常处理
5.1.异常概述和异常体系结构5.2.常见的异常5.3.异常处理机制
5.3.1.try-catch-finally5.3.2.throws2.3.3.try-with-resource 5.4.手动抛出异常5.5.自定义异常类 六.枚举类七.泛型
7.1.基本概念7.2.泛型的使用 八.多线程
8.1.基本概念8.2.线程的相关API8.3.线程的创建与使用
8.3.1.继承Thread类8.3.2.实现Runnable接口8.3.3.实现Callable接口8.3.4.使用线程池8.3.5.小结 8.4.线程的生命周期8.5.线程的同步
8.5.1.问题引入8.5.2.线程同步的方式 8.6.线程的通信 九.集合
9.1.集合框架体系及概述
9.1.1.基本概念9.1.2.基本体系 9.2.Collection接口方法9.3.Collection接口
9.3.1.List子接口9.3.2.Set子接口 9.4.Map接口
9.4.1.基本概念9.4.2.常用方法9.4.3.常用实现类 9.5.Collections工具类9.6.源码分析(JDK8)
9.6.1.ArrayList和Vector源码分析9.6.2.linkedList源码分析9.6.3.HashMap源码分析9.6.4.linkedHashMap源码分析9.6.5.Hashtable源码分析 十.IO流
10.1.File
10.1.1.概念和创建10.1.2.常用方法 10.2.各种流
10.2.1.四种基类10.2.2.节点流10.2.3.缓冲流10.2.4.转换流10.2.5.其他流 10.3.序列化与随机存取文件流
10.3.1.对象流10.3.2.随机存取文件流 十一.反射
11.1.反射机制
11.1.1.概念11.1.2.反射API11.1.3.优缺点以及优化: 11.2.Class类
11.2.1.基本介绍11.2.2.Class类的常用方法11.2.3.获取Class类对象的几种方式 11.3.类加载机制
11.3.1.基本说明11.3.2.类加载过程
一.基础语法 1.1.常用的DOS命令及注释①DOS命令:
dir:显示当前目录下的所有文件夹。
md:创建目录 如: md 文件夹名。
rd:删除目录 注意:需要文件夹为空才可以删除。
cd:进入指定的目录 如:cd d:java。
cd…:退回上一级目录。
cd:退回根目录。
exit:退出dos命令行。
del:删除文件 如:del文件名,如果删除文件夹,则是删除文件夹下所有的文件。
②注释
单行注释://注释
多行注释:
文档注释:
1.2.命名规则与规范规则:数字不能开头,不能用空格,不能用关键字和保留字。
规范:
小驼峰命名:方法名,变量名。
大驼峰:类名,接口名。
全大写:常量名,如果有多个单词用_分割。
全小写:包名。
1.3.变量 1.3.1.基本数据类型 八大基本数据类型:byte,char,short,int,long,float,double,boolean。
整型数据默认int型,long型要加L后缀。
浮点型数据默认double型,float型要加F后缀。
1.3.2.引用数据类型 三大引用数据类型:数组,类,接口。
1.3.3.数据类型运行规则 自动类型提升:容量小转容量大的,最低为int。
强制类型转换:容量大的转容量小的,利用强转符转换(),会损失精度。
1.4.运算符 1.4.1.算数运算符 +, - ,* ,/, %, ++, –
1.4.2.赋值运算符 =,+=,-=,*=,/=,%=
注意:拓展赋值运算符,不会改变原来的数据类型,如short a = 10,a+=10;a的数据类型不改变,还是short。
1.4.3.比较运算符 ==,>,<,<=,>=
比较结果都是boolean型。
1.4.4.逻辑运算符 &,|,!,&&,||,^
逻辑与,逻辑或,逻辑非,短路与,短路或,异或。
1.4.5.位运算符 &,|,<<,>>,>>>
按位与,按位或,左移相当于乘以2,右移相当于除以2,无符号右移。
1.4.6.条件运算符 表达式?ture:false
1.5.流程控制 1.5.1.顺序结构 ①if-else
②switch-case-default:switch表达式类型只能是byte,short,char,int,枚举,String。
1.5.2.循环结构 ①for
②while
③do-while
二.数组 2.1.一维数组2.1.1.声明与初始化
①静态初始化
数组类型 数组名 = new 数组类型[ ]{ 元素1,元素2,元素3… }
数组类型 数组名 = { 元素1,元素2,元素3… };
②动态初始化
数组类型 数组名 = new 数组类型 [ 数组的长度 ];
2.1.2.遍历
①普通for遍历
②增强for遍历
2.2.多维数组2.1.1.声明与初始化
①静态初始化
数组类型 数组名 = new 数组类型[ ] [ ]{ { 元素1,元素2,元素3… },{ 元素1,元素2,元素3… } …}
数组类型 数组名 = { { 元素1,元素2,元素3… },{ 元素1,元素2,元素3… } …};
②动态初始化
数组类型 数组名 = new 数组类型 [ 数组的行数 ] [ 数组的列数 ];
2.1.2.遍历
①普通for遍历
②增强for遍历
2.3.常见的数组异常 ①ArrayIndexOutOfBoundsException:数组角标越界异常
②NullPointerException:空指针异常
2.4.数组的特点 ①数组是有序排列的,长度一旦确定就不能修改
②数组中的数据类型可以是任意数据类型
2.5.Arrays工具类的常用方法 ①toString:打印数组元素
②asList:根据数组创建集合
③contains:检查数组中是否包含某个值
④sort:排序
⑤binarySearch:二分查找
三.面向对象 3.1.类的概念与结构 3.1.1.类和对象的区别与联系 区别:
①类是指具有共同特征的某一类事物的抽象的描述
②对象是类的一个具体个体,是类的一个实例( instance )
③两者的区别在于类时抽象的,对象时具体的实际存在的
联系:
类是对象的所属类型,对象是由类经行实例化出来的一个具体实体
3.1.2.属性 ①Field = 属性 = 成员变量 = 域、字段
②语法格式:修饰符 属性数据类型 属性名;
③成员变量和局部变量的异同:
相同点:
a.语法格式都想同,都是数据类型 变量名 = 变量值
b.都是先声明后使用
不同点:
c.声明位置不同,成员变量只能声明在类中,局部变量可以声明在方法内,构造器内,各种形参。
d.修饰符不同,成员变量可以用修饰符,局部变量不可以用修饰符。常用修饰符private、缺省、protected、public
e.成员变量右初始化值,局部变量没有初始化值
f.内存加载位置不同,成员变量加载在堆(heap)中,局部变量加载在栈(stack)中(非static)
④属性赋值的位置及先后顺序
a.默认初始化
b.显式初始化
c.构造器中初始化
d.通过对象.属性或者对象.方法的方式赋值
赋值的先后顺序:a→b→c→d
3.1.3.方法 1.方法的语法格式及概念
①Method = (成员)方法 = 函数
②语法格式:修饰符 返回值 方法名( 形参列表 ){ 方法体 }
③返回值:方法中用return 关键字返回值;void表示没有返回值
2.可变形参
①数据类型 … 形参名
②注意点:多个形参时,可变形参需要放在最后,可用遍历数组的方式遍历可变形参
3.重载
①在一个类中,方法名相同,参数列表不同,就叫做重载
②方法,构造器都可以重载
4.值传递机制
①java中只有值传递,没有引用传递
②对于基本数据类型,传递的是数据值;引用数据类型,传递的是地址值
3.1.4.构造器 ①构造器 = 构造器方法 = constructor
②语法格式:修饰符 类名( 形参列表 ){ }
③作用:用于创建对象,且可以对属性进行赋值
④特点:构造器名和类名一样,如果没有显式的声明一个构造器,就会默认的生成一个空参构造器
3.1.5.代码块 ①分为静态代码块和非静态代码块
②静态代码块只会在类加载的时候执行一次,非静态代码块在每次创建对象是都会执行
3.1.6.内部类 ①分为成员内部类和局部内部类
②作用:方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏,一般仅仅提供给类内部使用
3.1.7.类的语法格式 修饰符 class 类名{
属性:
修饰符 属性数据类型 属性名;
构造器:
修饰符 类名( 形参列表 ){
}
代码块:
static或者空{
代码块
}
方法:
修饰符 返回值 方法名( 形参列表 ){
方法体
}
}
3.2.面向对象的三大特性 3.2.1.封装性 ①封装性就是隐藏内部具体实现,隐藏一个类不需要对外提供的具体细节,只对外公开简单的接口,提高系统的安全性和可维护性。
②具体就是将数据用private修饰,并提供相对应的get,set方法。
③实际应用:单例模式,对构造器进行私有化,对外不提供构造器的获取,只提供一个获取唯一对象的方法。
④四种权限修饰符:private,缺省,protected,public。
3.2.2.继承性 1.基本概念
①多个类存在相同的属性和方法,这时就不需要定义多个类,而是去继承一个类即可,这就是继承性。
②A类被B类继承,A类就叫父类,B类就叫子类,继承了父类的所有结构。
③作用:减少了代码的冗余,提高了代码的复用性,便为多态性提供了前提。
④一个类不可以继承多个类,但可以被多个类继承,即java不允许多继承。
⑤实际应用:JavaBean
public class JavaBean {
private Integer id;
private String name;
private Integer age;
public JavaBean() {
}
public JavaBean(Integer id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "JavaBean{" +
"id=" + id +
", name='" + name + ''' +
", age=" + age +
'}';
}
}
2.重写
①子类在继承父类的方法后,需要对方法经行重新编写,会覆盖继承过来的方法,就是重写。
②注意点:
a.方法名和形参列表需要和父类一样
b.重写的权限修饰符要大于等于父类的权限修饰符
c.重写的返回值是父类返回值的子类或相同
d.重写方法抛出的异常要小于等于父类的异常。
e.static修饰符要一样
3.2.3.多态性 ①子类的对象赋值给父类的引用,是运行时行为,就是多态性。
②虚拟方法调用:有了对象的多态性以后,编译时只能调用父类中声明的方法,运行时执行的是子类重写的方法
③作用:提高了代码的健壮性和通用性
④实际应用:代理模式
3.3.关键字 3.3.1.接口 1.基本概念
①用interface修饰接口
②jdk8之前接口中之后全局常量和抽象方法,jdk8之后增加了静态方法和默认方法
③java不能多继承,但是可以实现多个接口,解决了多继承问题
④开发中类用implements来实现接口
⑤接口实际上是定义了一种规范
⑥接口冲突:一个类中实现了多个接口,多个接口中有一个方法名相同的方法,就是报错,解决办法就是类重写该方法
⑦类优先原则:一个类中继承的类和接口有相同的方法,调用的是父类中的方法
2.接口的应用:代理模式
静态代理:
public class StaticProxy {
interface NetWork{
void browse();
}
class Real implements NetWork{
@Override
public void browse() {
System.out.println("上网");
}
}
class Proxy implements NetWork{
private NetWork netWork;
public Proxy() {
}
public Proxy(NetWork netWork) {
this.netWork = netWork;
}
public NetWork getNetWork() {
return netWork;
}
public void setNetWork(NetWork netWork) {
this.netWork = netWork;
}
public void check(){
System.out.println("检查网络环境");
}
@Override
public void browse() {
check();
netWork.browse();
}
}
public static void main(String[] args) {
StaticProxy staticProxy = new StaticProxy();
Proxy proxy = staticProxy.new Proxy();
proxy.setNetWork(staticProxy.new Real());
proxy.browse();
}
}
3.3.2.抽象类
1.基本概念
①用abstract修饰抽象类
②不可实例化
③抽象类有构造器,便于子类的实例化时调用
④作用:父类中知道子类有什么方法,但是父类又不能给出具体的实现,只能把这个方法设置成抽象的,用于子类重写这个方法
2.抽象方法
①只有方法的声明,没有方法体,而且只能在抽象类中
②抽向类中不一定有抽象方法,但是抽象方法一定时属于抽象类的
③若子类重写了父类中的所有的抽象方法,此子类方可实例化,若子类没有重写父类中的所有的抽象方法,此子类也是抽象类
3.模板方法设计模式
就是在一段代码中,不确定的部分用抽象方法,到时候在重写这个抽象方法即可
public abstract class Template {
//这部分是确定的代码,如下计算代码运行时间
public void spendTime() {
long start = System.currentTimeMillis();
code();
long end = System.currentTimeMillis();
System.out.println("花费的时间是:" + (end - start));
}
//这部分是不确定的,设置成抽象的方法
public abstract void code();
}
3.3.3.其他关键字
①this:表示当前对象,可以用this.属性,this.方法调用
②super:表示继承,用于区分子类和父类中的属性和方法
③static:静态的,属于类,用于修饰属性,方法,代码块,内部类
④final:最终的,修饰属性,方法,类,分别表示常量,不可重载,不可继承
⑤instanceof:a instanceod b 判断a是否是b的实例
⑥extends:继承
⑦transient:瞬时的,表示不可以序列化的,在序列化时,如果类中有不想序列化的属性方法,用transient修饰可以避免序列化
⑧native:本地的,表示调用本地的c/c++代码。
3.4.单例模式 3.4.1.饿汉式public class HungerSingleton {
private static HungerSingleton instance = new HungerSingleton();
private HungerSingleton(){
}
public static HungerSingleton getSingleton(){
return instance;
}
}
3.4.2.懒汉式
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton(){}
public static LazySingleton getSingleton(){
//双重检验
if(instance==null){
synchronized (LazySingleton.getSingleton()){
if(instance==null){
instance = new LazySingleton();
return instance;
}
}
}
return instance;
}
}
四.常用类
4.1.字符串相关类
4.1.1.String
1.String特性
①String是用final修饰的,不能被继承,不可变的字符序列
②String实现了Serializable接口,表示String类是支持序列化的
③String实现了Comparable接口
④String内部定义了final char[ ] value用于储存字符串数据
⑤String是一个不可变的字符序列,体现如下:
a.通过字面量的方式给一个字符串赋值,此时的字符串声明在字符串常量池中的,且字符串常量池中是不会储存相同内容的字符串
b.当对字符串重新赋值时,需要重写指定内存区赋值,不能在原有的value进行赋值
2.String创建字符串
①直接通过字面量赋值创建
②通过new 构造器的方式创建
两者个区别:
①直接字面量赋值的方式是在字符串长两次中创建的对象
②new的方式是在里面创建了对象,如何在常量池中创建了对象引用到堆中
public class TestString {
public static void main(String[] args) {
String a = "Java";
String b = "Java";
String c = new String("Java");
String d = new String("Java");
System.out.println(a==b);//true
System.out.println(a==c);//false
System.out.println(a==d);//false
System.out.println(c==d);//false
}
}
3.不同拼接操作的对比*
①常量和常量的拼接结果在常量池中,且常量池不会存在相同内容的常量
②只要其中一个是变量,结果就在堆当中
③调用intern方法后,不管是在堆当中还是在常量池中,都强制放在常量池中
public void test() {
String a = "Hello";
String b = "Java";
String c = "Hello" + "Java";
String d = a + "Java";
String e = a + b;
String f = (a + b).intern();
System.out.println(c==d);//false
System.out.println(c==e);//false
System.out.println(c==f);//true
System.out.println(d==e);//false
}
4.常用方法
public void test(){
String str = new String(" Hello Java! ");
//1.length:获取长度
System.out.println(str.length());//13
//2.charAt:获取指定索引下的字符
System.out.println(str.charAt(3));//l
//3.isEmpty:判断是否为空
System.out.println(str.isEmpty());//false
//4.equals:比较两个字符串内容是否相等
System.out.println("HelloJava".equals(str));//false
//5.contains:判断字符串中是否包含某个字符串
System.out.println(str.contains("Java"));//true
//6.subString:获取指定索引范围的字串
System.out.println(str.substring(0, 5));// Hell
//7.indexOf:获取指定字符串在字符串中第一次出现的索引
System.out.println(str.indexOf("a"));//8
//8.lastIndexOf:从后往前获取指定字符在字符串中第一次出现的索引
System.out.println(str.lastIndexOf("a"));//10
//9.replace:用指定的字符替换字符串中的某个字符,并返回替换后的字符串
System.out.println(str.replace("Java", "World"));// Hello World!
//10.trim:删除字符串前后的空格
System.out.println(str.trim());//Hello Java!
//11.compareTo:比较两个字符串的大小
System.out.println(str.compareTo(" "));//12
//12.toLowerCase:将所有字符串转换成小写
System.out.println(str.toLowerCase());// hello java!
//13.toUpperCase:将所有字符串转换成大写
System.out.println(str.toUpperCase());// HELLO JAVA!
//14.endsWith:判断是否以指定的字符串结尾
System.out.println(str.endsWith("a"));//false
//15.startWith:判断是否以指定的字符串开头
System.out.println(str.startsWith(" "));//true
//16.equalsIgnoreCase:和equals差不多,忽略两个字符串大小写作比较
System.out.println(" hello java! ".equalsIgnoreCase(str));//true
//17.toCharArray:转换城字符数组
char[] chars = str.toCharArray();
}
4.1.2.StringBuffer和StringBuilder
1.基本概念
①StringBuffer和StringBuilder都是可变类型
②StringBuffer是线程安全的,StringBuilder是线程不安全的,StringBuilder的效率比StringBuffer高
2.创建
①调用不带参数的构造器,默认创建的是长度为16
②调用带参数的构造器,参数为字符串则长度为16+字符串长度,参数为容量则长度就为指定的容量
3.扩容
①StringBuffer的底层数组结构用的是char类型的数组。
②所以,当我们使用StringBuffer对象的append(…)方法追加数据时,如果char类型数组的长度无法容纳我们追加的数据,
StringBuffer就会进行扩容。扩容时会用到Arrays类中的copyOf(…)方法,每次扩容的容量大小是原来的容量的2倍加2。
4.扩容源码分析:
调用append方法追加一个字符串
方法通过super.append调用父类中的append方法
先判断需要追加的字符串是否是空的,不是空就继续执行,ensureCapacityInternal方法就是一个扩容相关的方法,变量count是数组中元素个数,变量len是我们追加进来的字符串的长度。
进入该方法会判断需要添加后的长度是否大于数组长度,大于数组长度说明需要扩容。调用Arrays工具类的copyOf方法进行数据迁移,将就数组中的元素迁移到扩容后的新数组中。进入newCapactity方法
长度为原来的2倍+2,返回改值,copyOf会创建一个新的数组,数组长度为这个返回的值,并将原数组中的数据复制给新数组。
5.常用方法
public static void main(String[] args) {
StringBuffer stringBuffer = new StringBuffer();
//1.append:添加元素
System.out.println(stringBuffer.append("hello"));
//2.reverse:反转元素
System.out.println(stringBuffer.reverse());
//3.delete:删除指定位置元素
System.out.println(stringBuffer.delete(0, 1));
//4.replace:替换指定位置的元素
System.out.println(stringBuffer.replace(0, 10, "hello"));
//5.insert:在指定位置插入元素
System.out.println(stringBuffer.insert(2, "aaa"));
}
4.2.Object
4.2.1.基本概念
①Object类是所有Java类的根父类
②位于java.lang
③Object中只有一个空参构造器
4.2.2.常用方法 ①toString():返回字符串,常常都是需要重写toString方法的,如果不重写输入的是对象的地址值。
②equals():比较两个对象是否相等,需要重写该方法,如果不重写相当于==。
③hastCode():获取对象的hash值,一般和equals一起用,重写规则是如果两个对象内容相等,hash值也相等。
④getClass():获取运行时对象的类
⑤wait():让当前线程进入阻塞状态。直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。
⑥notify():唤醒在该对象上等待的某个线程
⑦notifyAll():唤醒在该对象上等待的所有线程
⑧clone():创建并返回一个对象的拷贝
⑨finalize():当GC确定该对象有引用时,由对象的垃圾回收器调用此方法。
4.3.包装类 4.3.1.基本概念 ①八大基本数据类型对应八大包装类
②包装类:Byte,Short,Character,Integer,Long,Float,Double,Boolean
③除了Character和Boolean外,其他的包装类的父类都是Number类
4.3.2.包装类的转换 1.基本数据类型和包装类的转换
①直接用自动装箱和自动拆箱
②Integer需要注意的是,如果数值在-128到127之间,是直接引用常量池中的对象。
2.包装类和String类之间的转换
①包装类转String类:调用String的ValueOf方法
②String类转包装类:调用需要转换的包装类的parsexxx方法
4.4.Math类 4.4.1.基本概念 ①包含了用于执行基本数学运算的属性和方法,如初等指数、对数、平方根和三角函数。
②Math 的方法都被定义为 static 形式,通过 Math 类可以在主函数中直接调用。
4.4.2.常用方法 ①round:返回四舍五入后的整数
②floor:向下取整
③ceil:向上取整
④random:获取0到1之间的随机浮点数
⑤sqrt:求参数的算术平方根。
⑥min:获取两个参数中的最小值
⑦max:获取两个参数中的最大值
⑧pow:获取第一个参数的第二个参数次幂
public static void main(String[] args) {
double a = 12.86;
int b = (int) a; //强制类型转换
System.out.println("强制类型转换:" + b);//12
long c = Math.round(a); //四舍五入
System.out.println("四舍五入:" + c);//13
double d = Math.floor(a); //返回小于参数a的最大整数
System.out.println("小于a的最大整数:" + d);//12.0
double e = Math.ceil(a); //返回大于参数a的最小整数
System.out.println("大于a的最小整数:" + e);//13.0
double x = Math.random(); //产生[0,1)区间内的随机浮点数
System.out.println("默认随机数:" + x);
int y = (int) (Math.random() * 99); //产生[0,99)区间内的随机整数
System.out.println("0-99之间的随机整数(不包括99):" + y);
System.out.println("求4的算数平方根:"+Math.sqrt(4));
System.out.println("求两个数的最小值:"+Math.min(8,10));
System.out.println("求两个数的最大值:"+Math.max(8,10));
System.out.println("求2的3次幂:"+Math.pow(2,3));
}
4.5.时间处理的常用方式
1.获取时间戳
①System.currentTimeMillis() 常用
②Calendar.getInstance().getTimeInMillis();
③new Date.getTime();
2.获取当前时间
1.new Date():获取当前时间,配合SimpleDateFormat使用可以改变时间的格式
Date d = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String today = sdf.format(d);
System.out.println(today);//2022-09-21 21:51:39
2.调用SimpleDateFormat的parse方法将固定格式的时间字符串转换成Date类对象
String d = "2015-9-21 21:56:36";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 调用parse()方法,将字符串转换为日期
Date date = sdf.parse(d);
3.Calendar类获取时间,是一个抽象类,直接调用getInstance静态方法来获取一个对象。
public static void main(String[] args) {
Calendar c = Calendar.getInstance(); //实例化Calendar对象
int year = c.get(Calendar.YEAR); //通过get方法获得当前年
int month = c.get(Calendar.MONTH) + 1; //月份0-11,需要加1
int day = c.get(Calendar.DAY_OF_MONTH); //日期
int hour = c.get(Calendar.HOUR_OF_DAY); //获取小时
int minute = c.get(Calendar.MINUTE); //获取分钟
int second = c.get(Calendar.SECOND); //获取秒数
System.out.println("当前时间:" + year + "年" + month + "月" + day + "日" + " " + hour +
":" + minute + ":" + second);
}
4.6.Java比较器
4.6.1.基本概念
①基本数据类型作比较用比较运算符,而类与类之间作比较就要用比较器
②比较器的两个接口:
a.自然排序:Comparable
b.定制排序:Comparator
4.6.2.Comparable 重写compareTo( obj )的规则:
①需要重写Comparable中的compareTo方法,如果当前对象this大于形参对象obj,返回正整数
②当前对象this小于形参对象obj,返回负整数
③当前对象this等于形参对象obj,返回零
像String,包装类等实现了Comparable接口,重写了compareTo方法,进行了从小到大的排列
对于自定义类如果需要排序,需要让自定义类实现Comparable接口,重写CompareTo方法,在方法中指明如何排序
public class Person implements Comparable{
private String name;
private Integer age;
public Person() {
}
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + ''' +
", age=" + age +
'}';
}
@Override
public int compareTo(Object o) {
if(o instanceof Person){
Person obj = (Person) o;
if(name.compareTo(obj.getName())==0){
return age.compareTo(obj.getAge());
}
return name.compareTo(obj.getName());
}
return 0;
}
}
public void testComparable(){
Person t1 = new Person("张三",23);
Person t2 = new Person("李四",24);
int res = t1.compareTo(t2);
if(res>0){
System.out.println("张三更强");
}else if(res==0){
System.out.println("张三和李四打成平手");
}else {
System.out.println("李四更强");
}
}
4.6.3.Comparator
使用背景:当元素的类型没实现java.lang.Comparable接口而又不方便修改代码,或者实现了java.lang.Comparable接口的排序规则
不适合当前的操作,那么可以考虑使用 Comparator 的对象来排序
public void testComparator(){
Person t1 = new Person("张三",23);
Person t2 = new Person("李四",24);
Comparator comparator = new Comparator() {
@Override
public int compare(Person o1, Person o2) {
if(o1.getName().compareTo(o2.getName())==0){
return o1.getAge().compareTo(o2.getAge());
}
return o1.getName().compareTo(o2.getName());
}
};
int res = comparator.compare(t1, t2);
if(res>0){
System.out.println("张三更强");
}else if(res==0){
System.out.println("张三和李四打成平手");
}else {
System.out.println("李四更强");
}
}
五.异常处理
5.1.异常概述和异常体系结构
①在Java语言中,将程序执行中发生的不正常情况称为“异常”
②Throwable是异常类根父类,两个子类Exception和Error,一个是异常,一个是错误
③对于Error,一般不进行处理,而要处理的是Exception类及子类
④异常分为运行时异常和编译时异常,运行时异常不是强制要处理的,编译时异常时强制要处理的异常。
5.2.常见的异常运行时异常:java.lang.RuntimeException类及其子类
①NullPointerException:空指针异常
②ArrayIndexOutOfBoundsException:角标越界异常
③ArithmeticException:算术异常
④NumberFormatException:数字格式化异常
⑤ClassCastException:类型不兼容异常
编译时异常:
①IOException:IO异常
②ClassNotFoundException:类找不到异常
③SQLException:sql异常
④FileNotFoundException:文件找不到异常
5.3.异常处理机制 5.3.1.try-catch-finally 1.语法格式:
try{
需要处理异常的代码
}catch(异常类型){
发生异常后处理的代码
}finally{
最后一定要执行的代码,经常使用来处理关闭资源等。
}
2.注意事项:
①finally是一个可选的,一定会被执行的代码
②try将异常代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应的异常类,更具此类去catch中进行匹配
③一旦匹配到某个catch,就跳出当前catch结构,继续执行其后面的代码
④catch中的异常类型如果没有子父类关系,可以任意放置顺序,如果满足子父类关系,子类需要放在上面,父类放在下面否者会报错
⑤catch中常用的两种处理方法:
异常变量名.getMessage():
异常变量名.printStackTrace();
⑥在try结构中声明的变量,出了try结构后不能在被调用
⑦try-catch-finally可以相互嵌套
finally使用:像数据库连接,网络编程等,需要我们手动进行资源释放的,就需要声明在finally中
finally是可选的,且一定会被执行的代码
3.小结:
①使用try-catch-finally处理编译时异常,使得程序在编译期间不会报错,但是运行时仍可能报错,相当于我们使用try-catch-
finally将一个编译时可能出现的异常,延迟到了运行时处理
②开发中由于运行时异常比较常见,所以通常我们就不针对运行时异常编写try-catch-finally了,针对编译时异常,我们一定要 考虑异常的处理
public class TryCatchFinally {
public void testTryCatchFinally(){
try {
int a = 10/0;
}catch (ArithmeticException arithmeticException){
System.out.println("算数异常");
return;
}finally{
System.out.println("最后一定会被执行的代码");
}
}
public static void main(String[] args) {
new TryCatchFinally().testTryCatchFinally();
}
}
5.3.2.throws
1.throws+异常类型的使用
throws+异常类型写在方法的声明处,指明方法执行时,可能抛出的异常类型,一旦当方法体执行时,出现异常,任会在异常代码处 生成一个异常类的对象,此对象满足throws后异常类型时,就会被抛出,异常代码后续的代码不再被执行
2.重写方法异常抛出的规定
①子类抛出的异常不大于父类抛出的异常
②如果父类被重写的方法没有throws方式处理异常,子类重写的方法不能用throws方式处理异常,只能用try-catch-finally方式处理 异常。
public class Throws {
public void testThrows() throws RuntimeException{
int a = 10/0;
}
public static void main(String[] args) {
new Throws().testThrows();
}
}
2.3.3.try-with-resource
①作用:可以自动关闭资源连接,前提是需要关闭的资源实现了Closeable或AutoCloseable接口
②在多个需要关闭连接的场合时,可以使用这种处理方式
③注意点:在try声明时需要将最底层的资源单独拿出来声明
④语法:
try(需要关闭的资源声明在此处){
业务处理
}catch{
异常处理
}
public class TryWithResource {
public static void main(String[] args) {
try(Resource1 r1 = new Resource1(); Resource2 r2 = new Resource2()) {
r1.sayHello();
r2.sayHello();
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Resource1 implements AutoCloseable {
public void sayHello() {
System.out.println("Resource1 hello");
}
@Override
public void close() throws Exception {
System.out.println("Resource is closed");
}
}
class Resource2 implements AutoCloseable {
public void sayHello() {
System.out.println("Resource2 hello");
}
@Override
public void close() throws Exception {
System.out.println("Resource is closed");
}
}
5.4.手动抛出异常
①用throw关键字手动抛出异常
②语法格式:throw 异常类对象;
public class Throw {
public void testThrow(){
throw new RuntimeException("手动抛出异常");
}
public static void main(String[] args) {
new Throw().testThrow();
}
}
5.5.自定义异常类
①一般用户自定义异常类都是RuntimeException的子类,需要继承异常类
② 自定义异常需要提供一个全局常量serialVersionUID
public class MyException extends Exception{
static final long serialVersionUID = 134656235335L;
public MyException(String message) {
super(message);
}
}
class TestMyException{
public void testTestMyception(){
try{
throw new MyException("自定义异常类");
} catch (MyException e) {
System.out.println(e.getMessage());
}
}
public static void main(String[] args) {
new TestMyException().testTestMyception();
}
}
六.枚举类
1.概念
①类的对象是有限个的,确定的,我们称为枚举类
②当需要定义一组常量时,强烈建议使用枚举类
③如果枚举类中只有一个对象,则建议使用单例模式的实现方式
自定义枚举类:
方式一:jdk5.0之前手动定义枚举类
方式二:jdk5.0时可以使用enum关键字定义枚举类
2.enum关键字定义枚举类
public class TestEnum {
public static void main(String[] args) {
Season.show(Season.AUTUMN);
}
}
enum Season{
SPRING("春天","春暖花开"),
SUMMER("夏天","夏日炎炎"),
AUTUMN("秋天","秋高气爽"),
WINTER("冬天","冰天雪地");
private final String seasonName;
private final String seasonDesc;
private Season(String seasonName,String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
public static void show(Season season){
switch (season){
case SPRING:
System.out.println("春天");break;
case SUMMER:
System.out.println("夏天");break;
case AUTUMN:
System.out.println("秋天");break;
case WINTER:
System.out.println("冬天");break;
}
}
@Override
public String toString() {
return "Season{" +
"seasonName='" + seasonName + ''' +
", seasonDesc='" + seasonDesc + ''' +
'}';
}
}
七.泛型
7.1.基本概念
①可以把泛型理解成一个标签
②编译时就会数据检查,保证数据的安全,使用泛型的主要优点是能够在编译时而不是在运行时检测错误。
③避免了强转操作报异常
④泛型只是在编译期间有效,运行时会进行去泛型化
public void test(){
List stringArrayList = new ArrayList();
List integerArrayList = new ArrayList();
Class classStringArrayList = stringArrayList.getClass();
Class classIntegerArrayList = integerArrayList.getClass();
if(classStringArrayList.equals(classIntegerArrayList)){
System.out.println("类型相等");
}
}
7.2.泛型的使用
1.在集合中的使用
①泛型不能是基本数据类型,必须是一个类
②在实例化集合类时,可以指明具体的泛型类型
③指明完以后,在集合类或接口中凡是定义类或接口时,内部结构使用到类的泛型的位置,都指定为实例化的泛型类型
④如果实例化时没有指明泛型的类型,默认类型为Object类型
public static void main(String[] args) {
ArrayList list = new ArrayList<>();//类型推断
list.add(78);
list.add(88);
list.add(77);
list.add(66);
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
2.泛型类
注意点:①不能在static方法中用泛型
②不能在异常类中用泛型
//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型 //在实例化泛型类时,必须指定T的具体类型 class Generic{ //key这个成员变量的类型为T,T的类型由外部指定 private T key; public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定 this.key = key; } public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定 return key; } }
public void test3() {
//泛型的类型参数只能是类类型(包括自定义类),不能是简单类型
//传入的实参类型需与泛型的类型参数类型相同,即为Integer.
Generic genericInteger = new Generic(123456);
//传入的实参类型需与泛型的类型参数类型相同,即为String.
Generic genericString = new Generic("key_vlaue");
System.out.println("genericInteger:" + genericInteger.getKey());
System.out.println("genericString:" + genericString.getKey());
}
3.泛型在继承上的体现
①在继承自定义泛型类时,如果子类继承时指明了泛型类型,则实例化子类对象时,不再需要指明泛型
②在继承自定义泛型类时,如果子类继承时没有指明泛型类型,则子类任然是泛型类,创建对象时需要指明泛型类型
4.泛型方法
①泛型方法所属的类是否是泛型的都可以,自己声明的泛型自己用
②在方法中出现类泛型的结构,泛型参数和类参数无关联
③格式: 返回类型 方法名(E 变量名){ };
④泛型方法可以声明为静态的,原因:泛型参数是在调用方法时确定的,不是实例化时确定的。
5.自定义泛型类泛型接口的注意点
①泛型类有多个泛型时,用逗号隔开
②泛型不同的引用不能相互赋值
③静态方法中不能使用类的泛型
④异常类不能声明为泛型
⑤泛型数组的创建:T[ ] =(T[ ])new Object[ 10 ];
⑥子类保留泛型:不保留,全部保留,部分保留
八.多线程 8.1.基本概念1.概念
①程序(program):一段静态的代码
②进程(process):运行中的程序
③线程(thread):进程的细分,一个进程中有多个线程就说明这个进程是多线程的,是程序内部的一条直行路径
2.进程与线程相关结构
①线程有独立的栈和程序计数器,进程有独立的堆和方法区,一个进程中有多个线程,意味着多个线程要共享一个堆和方法区
②多个线程可以操作共享的数据,就出现了一些安全的隐患
3.一个java程序至少有三个线程
①main主线程,②垃圾回收线程③异常处理线程
并行与并发:①并行指多个cpu同时执行多个任务②并发指一个cpu同时执行多个任务(利用时间片)
4.多线程的优点作用:
①提高应用程序的响应,提高用户体验
②提高CPU利用率
③改善程序结构
5.线程的分类
①守护线程(如垃圾回收线程,异常处理线程)
②用户线程(如主线程)
③Java垃圾回收就是一个典型的守护线程。
④若JVM中都是守护线程,当前JVM将退出。
6.线程的调度
①时间片:同优先级线程组成先进先出队列(先到先服务),使用时间片策略
②抢占式:对高优先级,使用优先调度的抢占式策略
7.线程的优先级
等级:
MAX_PRIORITY:10
MIN_PRIORITY:1 NORM_PRIORITY:5(默认优先级)
方法:
getPriority():返回线程优先级
setPriority(int newPriority):改变线程的优先级
说明:并不是说优先级高的就一定先执行,只是说概率更高
8.2.线程的相关API ①start():1.启动当前线程2.调用线程中的run方法
②run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
③currentThread():静态方法,返回执行当前代码的线程
④getName():获取当前线程的名字
⑤setName():设置当前线程的名字
⑥yield():主动释放当前线程的执行权
⑦join():在线程中插入执行另一个线程,该线程被阻塞,直到插入执行的线程完全执行完毕以后,该线程才继续执行下去
⑧stop():过时方法。当执行此方法时,强制结束当前线程。
⑨sleep(long millitime):线程休眠一段时间
⑩isAlive():判断当前线程是否存活
8.3.线程的创建与使用 8.3.1.继承Thread类public class CreateOne {
public static void main(String[] args) {
Window window1 = new Window();
Window window2 = new Window();
Window window3 = new Window();
window1.setName("窗口一");
window2.setName("窗口二");
window3.setName("窗口三");
window1.start();
window2.start();
window3.start();
}
}
//窗口卖票问题,总票数100张,用静态变量保存票数,会出现重复售卖问题
class Window extends Thread {
public static Integer tickets = 100;//加载在静态域中,所有线程共享
@Override
public void run() {
while (tickets > 0) {
try {
sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf(Thread.currentThread().getName() + "当前售出第%d张票!n", tickets--);
}
}
}
8.3.2.实现Runnable接口
public class CreateTwo {
public static void main(String[] args) {
window1 w = new window1();
//虽然有三个线程,但是只有一个窗口类实现的Runnable方法,由于三个线程共用一个window对象,所以自动共用100张票,不用加static
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class window1 implements Runnable {
private int tickets = 100;
@Override
public void run() {
while (tickets > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "当前售出第" + (tickets--) + "张票");
}
}
}
开发中,优先选择实现Runable接口的方式
原因:①实现的方式没有类的单继承性的局限性
②实现的方式更适合用来处理多个线程有**共享数据**的情况
联系:Thread也是实现自Runable,两种方式都需要重写run()方法,将线程要执行的逻辑声明在run中
8.3.3.实现Callable接口public class CreateThree {
public static void main(String[] args) {
FutureTask task = new FutureTask<>(new Window3());
Thread thread = new Thread(task,"窗口");
thread.start();
System.out.println("主线程");
try {
System.out.println(task.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("主线程被阻塞");
}
}
class Window3 implements Callable {
private Integer tickets = 100;
@Override
public String call() {
while (tickets > 0) {
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "当前售出第" + (tickets--) + "张票");
}
return "卖完了";
}
}
优点:
① runnable重写的run方法不如callaalbe的call方法强大,call方法可以有返回值
②方法可以抛出异常,且支持泛型的返回值
③ 需要借助FutureTask类,比如获取返回结果
注意点:
①在调用get获取返回值时,线程会进入阻塞状态,主线程被阻塞,知道支线程获取了返回值。
8.3.4.使用线程池 8.3.5.小结 8.4.线程的生命周期 ①新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
②就绪:处于新建状态的线程被start后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
③运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run方法定义了线程的操作和功能
④阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时终止自己的执行,进入阻塞状态
⑤死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
8.5.线程的同步 8.5.1.问题引入 ①在上面窗口买票的例子中,出现了窗口一票多买,超卖问题。这就是线程安全问题
②当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。
导致共享数据的错误。
解决办法:对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。
利用锁的特点就可解决这个线程安全问题
8.5.2.线程同步的方式1.同步代码块
public class TestSynchronized {
public static void main(String[] args) {
Window window = new Window();
Thread thread1 = new Thread(window,"窗口一");
Thread thread2 = new Thread(window,"窗口二");
Thread thread3 = new Thread(window,"窗口三");
thread2.setPriority(6);
thread1.start();
thread2.start();
thread3.start();
}
}
class Window implements Runnable{
private Integer tickets = 100;
@Override
public void run() {
while(tickets>0){
synchronized (Object.class){
if(tickets>0){
System.out.printf(Thread.currentThread().getName() + "当前售出第%d张票!n", tickets--);
}
}
}
}
}
关键字:synchronized
语法:synchronized(同步监视器){
需要被同步的代码
}
说明:操作共享数据的代码,即为需要同步的代码
①共享数据指的是多个线程共同操作的变量。
②同步监视器:俗称 ‘ 锁 ’,任何一个类的对象都可以充当锁。
③锁的要求:要求多个线程共同用一把锁
④在实现类方式中,可以考虑用this充当同步监视器
⑤在继承方式中,可以考虑当前类充当同步监视器
同步的方式解决了线程安全问题,但操作同步代码时只能有一个线程参与相当于单线程
2.同步方法
public static void main(String[] args) {
//测试同步方法
Window2 thread1 = new Window2();
Window2 thread2 = new Window2();
Window2 thread3 = new Window2();
thread1.setName("窗口一");
thread2.setName("窗口二");
thread3.setName("窗口三");
thread2.setPriority(6);
thread1.start();
thread2.start();
thread3.start();
}
class Window extends Thread{
private Integer tickets = 100;
@Override
public synchronized void run() {
while (tickets > 0) {
System.out.printf(Thread.currentThread().getName() + "当前售出第%d张票!n", tickets--);
}
}
}
同样也是用synchronized关键字
①如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明为同步的
②同步方法指的是在方法中加入synchronized
③在同步方法中也是有锁的,对于实现方式用的锁是this,继承方式用的是当前类本身
④非静态的同步方法用的锁是this,静态的同步方法用的锁是当前类本身
注意点:多个线程共用同一把锁
3.Reentrant
①创建Lock的实现类ReentrantLock(true),加true就是公平锁
②实例化
③调用lock方法锁住,调用unlock解锁
代码如下:
public class TestReentrant {
public static void main(String[] args) {
Window3 window = new Window3();
Thread thread1 = new Thread(window, "窗口一");
Thread thread2 = new Thread(window, "窗口二");
Thread thread3 = new Thread(window, "窗口三");
thread1.setPriority(1);
thread2.setPriority(10);
thread1.start();
thread2.start();
thread3.start();
}
}
class Window3 implements Runnable {
private Integer tickets = 100;
//1.实例化锁
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (tickets > 0) {
try {
lock.lock();
if(tickets>0){
System.out.printf(Thread.currentThread().getName() + "当前售出第%d张票!n", tickets--);
}
}finally {
lock.unlock();
}
}
}
}
4.线程死锁问题
①不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃,自己需要的同步资源,就形成了线程的死锁
②出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
public static void main(String[] args) {
String s1 = new String();
String s2 = new String();
new Thread(){
@Override
public void run() {
synchronized (s1){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("放开s2");
synchronized (s2){
}
}
}
}.start();
new Thread(){
@Override
public void run() {
synchronized (s2){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("放开s1");
synchronized (s1){
}
}
}
}.start();
}
8.6.线程的通信
线程同步的方法:
①wait:一旦执行这个方法,当前线程就会阻塞,并释放同步监视器
②notify:一旦执行这个方法,就会唤醒一个wait的线程,多个线程被wait就唤醒优先级高的
③notifyAll:一旦执行这个方法,就会唤醒所有wait的线程
注意点:
①这三个方法只能出现在同步代码块和同步方法当中
②这三个方法的调用者必须是同步代码块或同步方法的同步监视器*
③这三个方法时定义在object类中的
//交替打印1-100
public static void main(String[] args) {
Runnable runnable = new Runnable() {
private Integer i = 1;
@Override
public void run() {
while(true) {
synchronized (this) {
notify();
if (i<100){
System.out.println(Thread.currentThread().getName() + i++);
}else {
break;
}
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
};
Thread thread1 = new Thread(runnable,"线程一:");
Thread thread2 = new Thread(runnable,"线程二:");
thread1.start();
thread2.start();
}
九.集合
9.1.集合框架体系及概述
9.1.1.基本概念
①数组的弊端:初始化后长度就确定了,不便于扩展。提供的方法太少不便于CRUD。存储的数据太单一,数据是可重复的。
②集合根据这些问题做出了解决。
9.1.2.基本体系 9.2.Collection接口方法长度:size
添加:add,addAll
清空集合:clear
判断是否为空:isEmpty
判断是否包含:contains,containsAll
删除:remove,removeAll
获取交集:retainAll
转换成数组:toArray
遍历:iterator
获取哈希值:hashCode
判断是否相等:equals
9.3.Collection接口 9.3.1.List子接口1.List接口方法:
增:add
删:remove
改:set
查:get
List接口存储的数据是有序的,可重复的。
2.三个实现类:ArrayList、linkedList和Vector
ArrayList:实质上是一个可变长数组
public static void main(String[] args) {
ArrayList list = new ArrayList<>();
//测试CRUD
list.add(1);
list.add(2);
list.add(3);
list.remove(0);
list.set(0,1);
System.out.println(list.get(1));
//获取长度
int size = list.size();
System.out.println(size);
//获取迭代器,遍历
Iterator iterator = list.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
linkedList:双向链表和队列
方法多继承于Deque接口和List接口
可以实现队列和栈。
Vector:与ArrayList差不多,但是所有方法都是加了synchronized重量级锁的
3.ArrayList、linkedList和Vector的异同
①ArrayList和linkedList都是线程不安全的,ArrayList底层是数组,linkedList底层是双向链表。
②对于随机访问get和set,ArrayList是优于linkedList的,对于新增,删除操作,linkedList是由于ArrayList的。
③ArrayList和Vector几乎相同,但是Vector是线程安全的。扩容方面Vector扩容为原来的两倍,而ArrayList扩容为1.5倍。
④三个类都实现了List接口,存储的特点相同,都是有序的可重复的数据
9.3.2.Set子接口1.HashSet,linkedHashSet,TreeSet三个实现类
HashSet:
特点:①不能保证元素的排列顺序,是无序的
②HashSet 不是线程安全的
③集合元素可以是 null
④添加的元素是不可重复的
注意点:HashSet底层是HashMap,用equals判断两个对象是否相等,所以添加的元素必须要重写equals和hashCode方法
重写hashCode原则:两个对象的equals相同,hashCode值也相同。
linkedHashSet:
特点:①是HashSet的的子类
②它底层维护了一个双向链表,使得插入的顺序可以保存
③不同于HashSet,它因为维护了一条双向链表,所以它是有序的
④同样它也是不可重复的
⑤底层是linkedHashMap,其他的和HashSet一样
TreeSet:
特点:①是SortedSet接口的实现类,可以确保元素处于排序状态
②底层是TreeMap
③排序有两个方式,自然排序和定制排序,分别实现Comparable接口和Comparator接口
9.4.Map接口 9.4.1.基本概念①Map接口和Collection接口并列存在,Map用于保存具有映射关系的键值对
②key-vale可以是任意类型,开发中key常用String类型,保证key的不可变性
③key是不可重复的,且添加的类必须是重写equals和hashCode的类。value是可以重复的
9.4.2.常用方法①添加:put,putAll
②删除:remove
③清空:clear
④查询:get
⑤判断是否包含某个键:containsKey
⑥判断是否包含某个值:containsValue
⑦判断是否为空:isEmpty
⑧获得所有的key构成的Set集合:keySet
⑨获得所有的value构成的Collection集合:values
⑩获得key-value构成的Set集合:entrySet
public static void main(String[] args) {
HashMap map = new HashMap<>();
map.put("张三",23);
map.remove("李四");
map.put("张三",25);
System.out.println(map.get("张三"));
System.out.println(map.containsKey("李四"));
System.out.println(map.isEmpty());
Set keySet = map.keySet();
Collection values = map.values();
Set> entries = map.entrySet();
}
9.4.3.常用实现类
1.HashMap
特点:①允许key和value都为null,但是key不可重复
②key所构成的集合是Set,无序的,不可重复的
③value构成的集合是Collection,无序的,可以重复的
④key-value构成一个entry
存储结构:
①jdk8以前的是数组+链表结构,链表添加元素是链头不动,在链尾添加元素
②jdk8以后的是数组+链表+红黑树,
9.5.Collections工具类1.概念
是一个操作List,Set,Map等集合的工具类
2.常用方法
①reverse():反转
②shuffle():对集合元素进行随机排序
③sort():对集合元素进行自然排序
④swap(List,i,j):将List中的i处元素和j处元素进行交换
⑤max():返回集合中最大元素
⑥min():返回集合中最小元素
⑦frequency(Collection,Object):返回集合中指定元素出现次数
⑧copy(dest,src):将src中的内容复制到dest中,且dest的size不小于src的size
常见写法:List dest = Array.asList( new Object[ src.size() ] );
⑨replaceAll():使用新值替换旧值
⑩提供了一系列处理线程安全的方法
public static void main(String[] args) {
ArrayList list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
//1.reverse:反转
Collections.reverse(list);
System.out.println(list);
//2.shuffle:随机排序
Collections.shuffle(list);
System.out.println(list);;
//3.sort:排序
Collections.sort(list);
System.out.println(list);
//4.swap:交换
Collections.swap(list,0,1);
System.out.println(list);
//5.max:返回最大值
Integer max = Collections.max(list);
System.out.println(max);
//6.min返回最小值
Integer min = Collections.min(list);
System.out.println(min);
//7.frequency:返回指定元素的出现次数
System.out.println(Collections.frequency(list, "asdf"));
//8.copy:复制
List dest = Arrays.asList( new Object[ list.size() ] );
Collections.copy(dest,list);
System.out.println(dest);
//9.replace
Collections.replaceAll(list,1,0);
System.out.println(list);
//10.线程安全操作
List synchronizedList = Collections.synchronizedList(list);
}
9.6.源码分析(JDK8)
9.6.1.ArrayList和Vector源码分析
1.属性
2.方法
构造器:一个空参构造器,一个指定容量的构造器
从构造方法中我们可以看见,默认情况下,elementData 是一个大小为 0 的空数组,当我们指定了初始大小的时候,elementData 的初始大小就变成了我们所指定的初始大小了。
add方法:
add 方法在插入元素之前,它会先检查是否需要扩容,然后再把元素添加到数组中最后一个元素的后面。在 ensureCapacityInternal 方
法中,我们可以看见,如果当 elementData 为空数组时,它会使用默认的大小去扩容。所以说,通过无参构造方法来创建 ArrayList 时,
它的大小其实是为 0 的,只有在使用到的时候,才会通过 grow 方法去创建一个大小为 10 的数组。扩容的话扩容为原数组长度的1.5倍。
其余的get,set,remove方法都是先检查索引有没有越界,然后在通过索引去获取,设置,删除元素
3.小结
①ArrayList 创建时的大小为 0;当加入第一个元素时,进行第一次扩容时,默认容量大小为 10。
②ArrayList 每次扩容都以当前数组大小的 1.5 倍去扩容。
③Vector 创建时的默认大小为 10。默认扩容 2 倍去扩容,若制定了扩容长度则原数组长度+扩容长度
④ArrayList 是非线程安全的,Vector 是线程安全的。
9.6.2.linkedList源码分析1.属性
linkedList底层是用双向链表存储
2.方法
添加:linkedList实现了Deque双端队列接口,用的大多都是它的方法,添加的话在头节点添加,尾节点添加,指定位置添加。只需要将添加结点和首位节点双向连接好即可。
删除:只需要把需要删除的节点的前驱和后驱节点连接好即可
获取:判断是头节点开始遍历还是尾节点开始遍历,然后遍历查询即可
小结:
①linkedList 的底层结构是一个带头/尾指针的双向链表,可以快速的对头/尾节点进行操作。
②相比数组,链表的特点就是在指定位置插入和删除元素的效率较高,但是查找的效率就不如数组那么高了。
9.6.3.HashMap源码分析1.属性
哈希表中的每一个元素都是Node节点,从这里可以看出,它的链表是单向链表,存储了key,value,hash值
2.方法
put:
3.小结:
①先通过hash值计算出key映射到哪个桶。再通过与容量的与运算判断有没有发生碰撞冲突,如果桶上没有碰撞冲突,则直接插入。
②如果发生冲突了就循环判断桶上的链表是否有相同的,相同的就跳出。不相同就判断是否是红黑树节点,是的话就以红黑树的方式插入
③不是红黑树节点就插入到链尾,并判断链表长度是否大于树化临界值8,大于就进入树化方法。
④树化方法先判断容量是否超过树化最小容量64,超过就树化,没超过就扩容。
⑤再判断节点是否相同,相同就进行value覆盖。
⑥size自增并判断是否超过扩容临界值,超过就扩容。
⑦扩容方法,第一次调用扩容方法扩容为初始化容量值16,否者扩容为元素组长的的2倍。
⑧再树化之后,如果桶上的节点树小于退树化临界值6的话,会进行一个退树化,恢复成单向链表
⑨添加方法有三次调用扩容方法:
第一次为刚进方法时判断数组为空,第二次是树化方法中容量值小于最小树化容量64,第三次size长度大于扩容临界值
jdk8和之前的相比:采用数组+链表+红黑树,优势在于如果一个桶上的冲突很严重的话,是会导致哈希表的效率降低至 O(n),而通过
红黑树的方式,可以把效率改进至 O(logn)。相比链式结构的节点,树型结构的节点会占用比较多的空间,所以这是一种以空间换时
间的改进方式。还有就是8以前链表采用头插法并发情况下会导致链表成环,而8之后采用的尾插法。
还有一点在于扩容之后的rehash:
9.6.4.linkedHashMap源码分析1.属性
2.方法
3.小结
①linkedHashMap是基于HashMap+双向链表实现的;
②分为按访问顺序和插入顺序两种方式,LRU是按访问顺序;
③新节点处于链尾。
9.6.5.Hashtable源码分析1.概述与属性
①Hashtable是线程安全的,用的是synchronized重量级锁,单线程效率低下
②不允许插入null的key-value
从构造器中可以看出Hashtable的负载因子默认是0.75,初始化容量值为11,同时可以看出所用的哈希算法也不一样
2.方法
get:
put:
3.小结
①Hashtable是线程安全的,用的是synchronized重量级锁,单线程效率低下
②不允许插入null的key-value
③底层使用哈希表+单向链表存储,链表采用头插法,负载因子0.75,初始化容量11,扩容为原来的2倍+1。
④哈希算法不如HashMap,是对哈希表长度取模运算
十.IO流 10.1.File 10.1.1.概念和创建1.概念
①一个File类对象就代表一个文件或文件目录
②File类声明在java.io包下
③File类中涉及到关于文件或文件目录的创建,删除,重命名等方法,但并未涉及到文件内容的写入,读取操作,如果需要读取或写入文
件内容,必须使用IO流来完成
④后续File类的对象常会作为参数传递到流的构造器中,指明读取或写入的终点
2.创建
创建File类的实例:注意这些仅仅是新建对象,不是创建硬盘中的文件
①File(String filePath)
②File(上一层路径,文件目录)
③File(file对象,文件目录)
路径
①相对路径:相较于某个路径下,指明的路径
②绝对路径:包含盘符在内的文件或文件目录的路径
路径分隔符
①windows:
②unix:/
③调用File.separator
10.1.2.常用方法常用方法—获取功能:
①getAbsolutePath():获取绝对路径
②getPath():获取路径
③getName():获取名称
④getParent():获取上一层目录路径,若无返回null
⑤length():获取文件长度(即字节数),不能获取目录的长度
⑥lastModified():获取最后一次修改时间,毫秒数
返回文件目录
⑦list():获取指定目录下的文件或文件目录的名称数组
⑧listFiles():获取指定目录下的文件或文件目录的File数组
重命名
⑨renameTo(File dest):把文件名重命名为指定的文件路径,要想保证返回true,调用的file在硬盘中是存在的,且被调用的是不存在的
常用方法—判断功能
①isDirectory():判断是否是文件目录
②idFile():判断是否是文件
③exists():判断是否存在
④canRead():判断是否可读
⑤canWrite():判断是否可写
⑥isHidden():判断是否隐藏
常用方法—File的创建,删除,注意:这些是真实在硬盘中的创建删除
①createNewFile():创建文件,若果文件存在,则不创建返回false
②mkdir():创建文件目录,如果此文件目录存在,就不创建了,如果上层目录不存在,也不创建
③mkdirs():创建文件目录,如果上层目录不存在,一并创建
④delete():删除文件或文件夹,且这个文件下不能有子目录或文件才可以删除成功
10.2.各种流 10.2.1.四种基类InputStream,OutputStream,Reader,Writer
字节流和字符流的选用:
①对于文本文件,选用字符流处理,如(.txt .java .c)
②对于非文本文件,选用字节流处理,如(图片,视频)
10.2.2.节点流FileInputStream,FileOutputStream,FileReader,FileWriter
public static void main(String[] args) {
byteCopy("D:\DevelopeTools\Projects\javase\io\src\resource\read\image-20220223103737430.png",
"D:\DevelopeTools\Projects\javase\io\src\resource\write\1.png");
}
public static void byteCopy(String inPath,String outPath){
FileInputStream is = null;
FileOutputStream os = null;
try {
is = new FileInputStream(inPath);
os = new FileOutputStream(outPath);
byte[] bytes = new byte[1024];
int len;
while((len=is.read(bytes))!=-1){
os.write(bytes,0,len);
}
System.out.println("拷贝成功");
} catch (Exception e) {
e.printStackTrace();
}finally {
if (is!=null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(os!=null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
charCopy("D:\DevelopeTools\Projects\javase\io\src\resource\read\dbcp.txt",
"D:\DevelopeTools\Projects\javase\io\src\resource\write\1.txt");
}
public static void charCopy(String inPath,String outPath){
FileReader is = null;
FileWriter os = null;
try {
is = new FileReader(inPath);
os = new FileWriter(outPath);
char[] chars = new char[1024];
int len;
while((len=is.read(chars))!=-1){
os.write(chars,0,len);
}
System.out.println("拷贝成功");
} catch (Exception e) {
e.printStackTrace();
}finally {
if (is!=null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(os!=null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
10.2.3.缓冲流
BufferInputStream,BufferOutputStream,BufferReader,BufferWriter
缓冲流的作用意义:提高流的读写效率速度
原因:内部提供了一个缓冲区
flush()方法:刷新缓冲区
关闭流时只需要关闭最外层的流即可
public static void main(String[] args) {
bufferCopy("D:\DevelopeTools\Projects\javase\io\src\resource\read\2.avi",
"D:\DevelopeTools\Projects\javase\io\src\resource\write\1.avi");
}
public static void bufferCopy(String inPath,String outPath){
long start = System.currentTimeMillis();
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
FileInputStream is = new FileInputStream(inPath);
FileOutputStream os = new FileOutputStream(outPath);
bis = new BufferedInputStream(is);
bos = new BufferedOutputStream(os);
byte[] bytes = new byte[1024];
int len;
while((len=is.read(bytes))!=-1){
os.write(bytes,0,len);
}
System.out.println("拷贝成功");
} catch (Exception e) {
e.printStackTrace();
}finally {
if (bis!=null){
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(bos!=null){
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
long end = System.currentTimeMillis();
System.out.printf("拷贝所花费的时间是%d毫秒n",end-start);
}
10.2.4.转换流
InputSteamReader,OutputStreamWriter
①转换流提供了在字节流和字符流之间的转换
②字节流中的数据都是字符时,转成字符流操作更高效。
③开发中常用作编码解码,字符集转换。
public static void main(String[] args) {
changeCharacter("D:\DevelopeTools\Projects\javase\io\src\resource\read\dbcp.txt",
"D:\DevelopeTools\Projects\javase\io\src\resource\write\2.txt",
"utf8","gbk");
}
private static void changeCharacter(String inPath,String outPath,String needChange,String change){
InputStreamReader isr = null;
OutputStreamWriter osw = null;
try {
isr = new InputStreamReader(new FileInputStream(inPath),needChange);
osw = new OutputStreamWriter(new FileOutputStream(outPath),change);
char[] chars = new char[1024];
int len;
while((len=isr.read(chars))!=-1){
osw.write(chars,0,len);
}
System.out.println("转换成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (isr!=null){
try {
isr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(osw!=null){
try {
osw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
10.2.5.其他流
①标准输入输出流
②打印流
③数据流
10.3.序列化与随机存取文件流 10.3.1.对象流1.概念
①用于存储和读取基本数据类型数据或对象的处理流。可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
②序列化:用ObjectOutputStream类保存基本类型数据或对象的机制
③反序列化:用ObjectInputStream类读取基本类型数据或对象的机制
④static和transient修饰的成员变量
2.序列化要求
①需要对象实现Serializable接口或者
②类中需要有声明private static final long serialVersionUID变量
3.serialVersionUID的作用
Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的
serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化
版本不一致的异常
public static void main(String[] args) {
serialization(new Person(1,"张三",25,'1'),
"D:\DevelopeTools\Projects\javase\io\src\resource\write\serializable.txt");
deserialization("D:\DevelopeTools\Projects\javase\io\src\resource\write\serializable.txt");
}
private static void serialization(Object obj,String outPath){
ObjectOutputStream objectOutputStream = null;
try {
objectOutputStream = new ObjectOutputStream(new FileOutputStream(outPath));
objectOutputStream.writeObject(obj);
objectOutputStream.flush();
System.out.println("序列化成功");
} catch (Exception e) {
e.printStackTrace();
}finally {
if(objectOutputStream!=null){
try {
objectOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private static void deserialization(String inPath){
ObjectInputStream objectInputStream = null;
try {
objectInputStream = new ObjectInputStream(new FileInputStream(inPath));
Object o = objectInputStream.readObject();
Person person = null;
if(o instanceof Person){
person = (Person) o;
}
System.out.println(person);
} catch (Exception e) {
e.printStackTrace();
}finally {
if(objectInputStream!=null){
try {
objectInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
class Person implements Serializable {
private static final long serialVersionUID=4986416561561L;
private Integer id;
private String name;
private Integer age;
private static String email;
private transient Character gender;
public Person() {
}
public Person(Integer id, String name, Integer age, Character gender) {
this.id = id;
this.name = name;
this.age = age;
this.gender = gender;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public static String getEmail() {
return email;
}
public static void setEmail(String email) {
Person.email = email;
}
public Character getGender() {
return gender;
}
public void setGender(Character gender) {
this.gender = gender;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + ''' +
", email="+ email +
", age=" + age +
", gender=" + gender +
'}';
}
10.3.2.随机存取文件流
1.概念
①这个类既可以读也可以写。
②可以直接跳到文件的任意地方来读、写文件
③有一个mode参数,可以指定访问模式
④如果模式为只读r。则不会创建文件,而是会去读取一个已经存在的文件,如果读取的文件不存在则会出现异常。 如果模式为rw读写。
如果文件不存在则会去创建文件,如果存在则不会创建。
开发中可以用这个类实现多线程断点下载的功能
十一.反射 11.1.反射机制 11.1.1.概念开闭原则(OCP):在不修改源码的情况下,修改数据。
功能:
①在运行时判断任意一个对象所属的类
②在运行时构造任意一个类的对象
③在运行时判断任意一个类所具有的成员变量和方法
④在运行时获取泛型信息
⑤在运行时调用任意一个对象的成员变量和方法
⑥运行时处理注解
⑦生成动态代理
11.1.2.反射APIpublic static void main(String[] args) throws Exception {
//1.创建Properties类对象,利用Properties类来获取配置文件中的配置
Properties properties = new Properties();
//2.加载配置文件到Properties类对象中
properties.load(new FileInputStream("D:\DevelopeTools\Projects\javase\reflect\src\api\refection.properties"));
//3.通过Properties类对象获取配置文件中的信息
String classfullname = properties.getProperty("classfullname");
String method = properties.getProperty("method");
//4.查看获取到的信息
System.out.println("classfullname=" + classfullname);
System.out.println("method=" + method);
//获取类的Class类对象,参数是全类名,这里用一个配置文件来配置类的全类名等属性
Class> aClass = Class.forName(classfullname);
//可以用这个类的Class类对象获取类的对象,构造器,属性,方法等
//1.获取类的对象,这种方式可以直接通过类的class类对象获取到类的对象,现已被弃用,建议还是使用获取构造器创建对象。
Object o = aClass.newInstance();
System.out.println("直接通过类的字节码文件过去类对象(已过时):"+o);
//2.获取构造器,获取指定参数构造器,只需要在参数列表中指明要获取构造器的参数的class类。
Constructor> constructor1 = aClass.getDeclaredConstructor();//无参构造器
Constructor> constructor2 = aClass.getDeclaredConstructor(Integer.class, String.class, Integer.class);//带参构造器
System.out.println("无参构造器:"+constructor1);
System.out.println("带参构造器:"+constructor2);
//通过构造器获取类的对象
Object o1 = constructor1.newInstance();
//3.用类的Class类对象获取方法对象,再通过方法对象.invoke(调用者即类对象,参数列表)调用方法,即用方法对象调用方法,万事万物皆对象。
Method method1 = aClass.getDeclaredMethod(method);
method1.invoke(o1);
Method method2 = aClass.getDeclaredMethod(method, String.class);
method2.invoke(o1 ,"牛肉");
//4.用类的Class类对象获取属性对象,再通过属性对象.get(调用者即类对象)获取属性的值,即用属性对象获取属性值,可以对属性进行操作。
Field field = aClass.getDeclaredField("age");
//直接设置属性值
field.set(o1,25);
//获取属性值
System.out.println("age:"+field.get(o1));
}
public void test2() throws Exception {
//1.forName:得到Class对象
Class> aClass = Class.forName("reflection.Student");
//2.getName:获取全类名
System.out.println(aClass.getName());
//3.getSimpleName:获取简单类名
System.out.println(aClass.getSimpleName());
//4.getDeclaredConstructor:获取本类所有构造器
Constructor> constructor = aClass.getDeclaredConstructor();
Object o = constructor.newInstance();
//4.getDeclaredFields:获取本类所有属性
Field[] fields = aClass.getDeclaredFields();
System.out.println(Arrays.toString(fields));
//5.getDeclaredMethods:获取本类所有的方法
Method[] methods = aClass.getDeclaredMethods();
System.out.println(Arrays.toString(methods));
//6.获取本类所有实现的接口
Class>[] interfaces = aClass.getInterfaces();
System.out.println(Arrays.toString(interfaces));
//7.获取本类继承的父类
Class superclass = aClass.getSuperclass();
System.out.println(Arrays.toString(new Class[]{superclass}));
//8.获取私有的属性,方法,构造器,通过setAccessible方法设置暴破,使用反射可以访问私有的数据。
Class> class1 = Class.forName("reflection.Person");
Constructor> constructor1 = class1.getDeclaredConstructor(Integer.class,String.class,Integer.class);
constructor1.setAccessible(true);
}
11.1.3.优缺点以及优化:
①可以动态的创建使用对象,使用灵活,是框架技术的底层支撑。
②使用反射基本是解释执行,对执行速度有影响
③method,field,constructor对象都有setAccessible方法,用于访问安全检查的开关,设为true关闭,可以提高反射效率。
public static void main(String[] args) throws Exception {
test1();//普通方式调用方法,所用时间:3
test2();//反射方式调用方法,所用时间:1776
test3();//反射优化之后调用方法,所用时间:1196
}
public static void test1(){
long start = System.currentTimeMillis();
Person person = new Person();
for (int i = 0; i < 900000000; i++) {
person.eat();
}
long end = System.currentTimeMillis();
System.out.println("普通方式调用方法,所用时间:"+(end-start));
}
public static void test2() throws Exception {
long start = System.currentTimeMillis();
Class> aClass = Class.forName("api.Person");
Object o = aClass.getDeclaredConstructor().newInstance();
Method eat = aClass.getDeclaredMethod("eat");
for (int i = 0; i < 900000000; i++) {
eat.invoke(o);
}
long end = System.currentTimeMillis();
System.out.println("反射方式调用方法,所用时间:"+(end-start));
}
public static void test3() throws Exception {
long start = System.currentTimeMillis();
Class> aClass = Class.forName("api.Person");
Object o = aClass.getDeclaredConstructor().newInstance();
Method eat = aClass.getDeclaredMethod("eat");
eat.setAccessible(true);
for (int i = 0; i < 900000000; i++) {
eat.invoke(o);
}
long end = System.currentTimeMillis();
System.out.println("反射优化之后调用方法,所用时间:"+(end-start));
}
11.2.Class类
11.2.1.基本介绍
①Class类也是一个类,但它不是new出来的,而是系统创建的
②字节码文件通过类加载器ClassLoader类,类加载器再调用loadClass方法,在堆中生成了一个字节码文件对应类的class类对象。
③对于某个类的Class类对象,在内存中只有一份,因为类只加载一次
④每个类的实例都会记得自己是由哪个Class实例所生成的
⑤通过Class对象可以完整的获得一个类的完整结构
⑥Class对象是存放再堆中的
⑦类的字节码二进制数据是放在方法区的,也叫类的元数据。Class对象和方法区里的字节码数据有一种引用的关系。
加载过程:字节码文件先会在方法区中存放字节码的二进制数据,再经过类加载器ClassLoader类调用loadClass方法,在堆中生成一个类对应的Class类对象。
11.2.2.Class类的常用方法public static void main(String[] args) throws Exception {
//1.获取类的Class类对象
Class> aClass = Class.forName("testclass.Person");
//2.getClass方法:获取运行时类
System.out.println(aClass.getClass());//class java.lang.Class
//3.getPackage:获取包的名字
System.out.println(aClass.getPackage());//package testclass
//4.getName:得到全类名
System.out.println(aClass.getName());//testclass.Person
//5.getDeclaredConstructor:获取对象的构造器,接着可以通过构造器去获取对象
Constructor> constructor = aClass.getDeclaredConstructor();
System.out.println(constructor);
Object o = constructor.newInstance();
System.out.println(o);
//6.getDeclaredField:获取指定的属性对象,通过属性对象可以对属性操作,获取,设置都可以,这里的需要用到前面通过构造器获取的对象
Field field = aClass.getDeclaredField("age");
System.out.println(field);
field.set(o,25);
System.out.println(field.get(o));
//7.getDeclaredFields:获取属性对象数组
Field[] fields = aClass.getDeclaredFields();
System.out.println(Arrays.toString(fields));
//8.getDeclaredMethod:获取指定方法对象,可以用方法对象调用方法
Method method = aClass.getDeclaredMethod("eat", null);
method.invoke(o,null);
}
11.2.3.获取Class类对象的几种方式
①在知道类的全类名,且在类路径下时,可以用Class类的静态方法Class.forName的方式获取class类对象
应用场景:配置文件,读取全类名加载类。
②若已知具体的类,通过类的class获取,如 类名.class
应用场景:多用于参数传参
③在运行阶段,可以用类对象.getClass方法获取类对象
④可以直接用类加载器获取类对象,ClassLoader.loadClass
public static void main(String[] args) throws ClassNotFoundException {
//1.Class.forName
Class class1 = Class.forName("java.lang.String");
System.out.println(class1);
//2.类名.class
Class class2 = String.class;
System.out.println(class2);
//3. 对象.getClass
Class class3 = new String().getClass();
System.out.println(class3);
//4.通过类加载器获取类的Class类对象(四种类加载器)
ClassLoader classLoader = new Person().getClass().getClassLoader();//类加载器
Class class4 = classLoader.loadClass("testclass.Person");
System.out.println(class4);
System.out.println(class1.hashCode());
System.out.println(class2.hashCode());
System.out.println(class3.hashCode());
}
11.3.类加载机制
11.3.1.基本说明
①静态加载:编译时加载相关的类,没有则报错,依赖性太强
②动态加载:运行时加载需要的类,运行时不用该类则不报错,降低了依赖性。可以理解为延时加载。
反射就是动态加载,而new 就是静态加载的。
类加载时机:
①new创建对象时,静态加载
②子类被加载时,父类也会被加载,静态加载
③调用类中的静态成员时,静态加载
④通过反射,动态加载
11.3.2.类加载过程类加载分为三个阶段:加载,连接,初始化。
1.加载阶段
①主要是将字节码文件从数据源转换成二进制流加载到内存中,并生成一个代表该类的class类对象
②就是将二进制数据放进方法区,同时在堆中生成class对象
2.连接
验证:确保class文件的字节流中包含的信息符合jvm的要求,并不会危害jvm的自身安全,安全验证。
包括文件格式验证,元数据验证,字节码验证,符号引用验证
可以使用-Xverify:none关闭大部分验证,以缩短虚拟机类加载时间。
准备:jvm会对静态变量分配内存并默认初始化,常量全在这个阶段赋值
解析:jvm将把常量池里的符号引用转换成直接引用
3.初始化:真正执行类中定义的Java程序代码,是执行clinit方法的过程,clinit方法是由编译器按源文件中出出现的顺序,依次收集的所有静态变量的赋值并合并。且在多线程环境中被正确地加锁,同步,保证了内存中只有一个对应的class类对象
438)]
加载过程:字节码文件先会在方法区中存放字节码的二进制数据,再经过类加载器ClassLoader类调用loadClass方法,在堆中生成一个类对应的Class类对象。



