JAVA基础个人总结,不喜勿喷

文章目录

Java基础语法

计算机理论基础

进制,进制分类、进制转换、原码、反码、补码

数据类型

基本数据类型:

int(4字节),char(2字节),byte,short(2字节), long(3字节),float(4字节)double(8字节)boolean(8字节)

引用数据类型:

String,数组,类(包装类),接口,(引用类型的内存单元中只存放对象的地址,而对象本身存贮在内存堆中,只是简单的引用对象的地址 )

String.format() 格式化字符串
%d: 占位符,是一个整型的占位符 %f: 占位符,是一个浮点型的占位符 %s: 占位符,是一个字符串类型的占位 符 %c: 占位符,是一个字符型的占位符 %02d : 如果一个整数不够两位,往前补0凑齐两位 %.2f: 保留小数点后面2位数字

转义字符

\a响铃(BEL)007
\b退格(BS) ,将当前位置移到前一列008
\f换页(FF),将当前位置移到下页开头012
\n换行(LF) ,将当前位置移到下一行开头010
\r回车(CR) ,将当前位置移到本行开头013
\t水平制表(HT) (跳到下一个TAB位置)009

标识符:1. 由字母、数字、下划线和$组成 2. 不能以数字开头 3. 不能是系统关键字或者保留

数据类型转换

​ 自动类型转换(隐式,放大转换)

​ 满足自动数据类型转换条件:1)两种类型要兼容:如数值类型(整型和浮点型)2)目标类型大于源类型:例如int型数据可以自动转换为double类型

​ 强制数据类型转换(缩小转换)

​ 在变量前加上括号,在括号中指定要强制转换的类型(强制转换会损失数值精度 )

运算符

±*/%, ++,–,= ,+=, -=, *=, /=, %= ,> < ,== ,!=, >=, <= ,& | ! ^, &&, ||

int/int=int

位运算符 :

(将参与运算的两个整型的变量,求出补码,用每一位的数字进行运算 ,1: 相当于是true, 0: 相当于是false)
& : 位与 ,| : 位或 , ^ : 位异或 ,~ : 按位取反, 和反码不同,每一位都要按位取反,包括符号位

移位运算符 << :

左移运算符, >> : 右移运算符(有符号右移) ( 最高位补符号位 )

​ >>>: 右移运算符(无符号右移) // 最高位补0

三元运算符:boolean变量,或者一个boolean结果的表达式 ? 值1 : 值2

流程控制

分支流程控制语句:

if-else switch-case( switch-case语句具有穿透性 ,如果variable的值和某一个case匹配,此时将会执行从这个case开始往后所有的代码,关键字: break 可以直接跳出switch语句,结束穿透 )

循环流程控制语句:

for(增强for,forEach) whilie( do-while)

方法():

返回值:1.如果一个方法的返回值类型不是void, 那么在方法结束之前,必须要有return

​ 2.如果一个返回值类型不是void的方法中出现了分支结构,需要保证每一个分支上都有结果返回

递归:方法的循环调用

重载和重写

重载是指不同的函数使用相同的函数名,但是函数的参数个数或类型不同。调用的时候根据函数的参数来区别不同的函数。重载是同一个类中方法之间的关系,是水平关系。 重载是多个方法之间的关系。

覆盖(也叫重写)是指在派生类中重新对基类中的虚函数(注意是虚函数)重新实现。即函数名和参数都一样,只是函数的实现体不一样。覆盖是子类和父类之间的关系,是垂直关系;覆盖只能由一个方法或只能由一对方法产生关系 (访问权限>父类,返回值<父类,抛出异常<父类)

数组

数组一旦实例化完成,长度不能修改。是一个定长的容器 注意:数组中元素的下标是从0开始的

动态参数:

当参数是一个数组的时候,我们可以将这个数组中的元素直接写到实参的列表

遍历数组:
  1. 遍历下标;2. 增强for循环( ArrayIndexOutOfBoundsException: 数组下标越界异常)
数组排序:

八种排序:插入排序,快速排序,选择排序,冒泡排序,归并排序,堆排序,希尔排序,基数排序

数组查找:

顺序查找,

二分查找:目的:查询一个指定的元素在数组中出现的下标(如果要查询的元素出现了多次,那么只会查询到一个)
方式:每次取查询的区间中的中间值来和要查询的元素进行比较
对数组的要求:要求数组需要是有序的

二维数组:数组的数组

Arrays工具类:

一个用来操作数组的类,里面提供了很多方便我们对数组进行操作的方法

方法(重载)用途
void sort(…)对数组中的元素进行排序
int binarySearch(…)使用二分查找法查询
int[] copyOf(int[] array, int newLength)拷贝原数组中指定位数的元素
int[] copyOfRange()拷贝原数组中指定范围的元素
void fill(…)使用指定的元素来填充数组
void toString(int[] array)返回数组的字符串表示形式
boolean equals(int[] array1, int[] array2)判断两个数组中的元素是否相同

面向对象

面向对象:
是一种看待问题,解决问题的思维方式。着眼点在于找到一个能够帮助解决问题的实体,然后委托这个实体去 解决问题。

面向过程:
是一种看待问题,解决问题的思维方式。着眼点在于问题是怎样一步步的解决的,然后亲力亲为的去解决这些 问题

类和对象

对象:一个具有特殊的功能,能够去解决特定问题的实体。

类:若干个具有相同的属性和行为的对象的集合。

类和对象的关系: 类是对象的集合,对象是类的实体

在程序中,需要先设计类,然后再去实例化对象。

一个java文件中,是可以写多个类的。
注意事项:

  1. 只有和文件名相同的类可以用public来修饰。
  2. 每一个类在编译的时候都会生成一个class文件

**包 package **:是对一个程序中指定功能的部分代码进行包装

static关键字

表示静态,可以用来修饰类中的属性和方法
被static修饰的部分,称作静态成员,或者类成员
没有被static修饰的部分,称作非静态成员,或者实例成员

用static修饰的属性:静态属性、类属性

没有用static修饰的属性:非静态属性、实例属性、成员变量

用static修饰的方法:静态方法、类方法

没有用static修饰的方法:非静态方法、实例方法、成员方法

关键字final

修饰变量 这个变量的值不能改变,就是常量

修饰类 表示是一个最终类,这个类无法被继承

修饰方法 表示是一个最终方法,这个方法无法被重写

JAVA 中final 允许先声明 后赋值,只能赋一次

final:

finally:是 Java 保证重点代码一定要被执行的一种机制。finally是在异常处理时提供finally块来执行任何清除操作。不管有没有异常被抛出、捕获,finally块都会被执行

finalize 是基础类 java.lang.Object 的一个方法,它的设计目的是保证对象在被垃圾收集前完成特定资源的回收 (析构方法)

属性

实例属性:在堆上开辟的空间,在实例化对象的时候开辟的

静态属性:是在常量池开辟的空间,是这个类第一次被加载到内存中的时候开辟的空间(第一次使用到这个类的时候)

静态成员用类来访问,非静态成员用对象来访问

在静态方法中: 可以直接访问静态成员,不能直接访问非静态成员

在非静态方法中: 可以直接访问静态和非静态成员

在设计类的时候: 属性:如果你希望这个属性的值会随着不同的对象变化。设计为非静态的。例如:姓名,年龄,身高… 方法:

如果你希望这个方法是属于一个对象的行为,设计为非静态的

构造方法

是一个方法
特殊点:

  1. 构造方法没有返回值,不是指的返回值类型是void,而是根本就不写返回值类型 2. 方法名字和类名相同 3. 构造方法不能用static来修饰
    构造方法调用的时机: 1. 通俗来讲:是在实例化一个对象的时候调用的
    一般情况下,我们在构造方法中做什么:
  2. 对对象的某一些属性进行初始化赋值操作

实例化对象的过程:
Person xiaoming = new Person(); new : 表示在堆上开辟空间
执行非静态代码段
然后再执行构造方法:

在构造方法中调用其他的构造方法

可以使用this()的方式来调用其他的构造方法
注意事项:

  1. this() 必须写在构造方法的第一行
  2. 不能循环调用

无参构造方法需要注意的问题

  1. 如果一个类中没有写构造方法,此时这个类具有一个默认的public权限的无参构造方法
  2. 如果一个类中写构造方法了,此时这个默认的public权限的无参构造方法将不再提供
  3. 子类实例化时,默认调用父类的无参构造方法(不管子类的构造器有没有参数,因为子类继承的是父类的属性和方法,只调用父类的无参构造器就可以继承父类的属性和方法,因此不会调用父类的有参构造器),再调用子类的有参/无参构造器。
  4. 如果父类没有无参构造,通过使用super关键字去显示的调用父类的带参构造方法

this 和 super

this关键字是需要用在某一个非静态方法中。

this是自身的一个对象,代表对象本身,可以理解为:指向对象本身的一个指针。

1.普通的直接引用

2.形参与成员名字重名,用this来区分:

3.引用构造函数

super可以理解为是指向自己超(父)类对象的一个指针,而这个超类指的是离自己最近的一个父类。

1.普通的直接引用

2.子类中的成员变量或方法与父类中的成员变量或方法同名

3.引用构造函数

super(参数):调用父类中的某一个构造函数(应该为构造函数中的第一条语句)。
this(参数):调用本类中另一种形式的构造函数(应该为构造函数中的第一条语句)。

静态代码块和非静态代码块

相同点:都是在JVM加载类时且在构造方法执行之前执行,在类中都可以定义多个, 一般在代码块中对一些static变量进行赋值。

不同点:静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法)。静态代码块只在第一次new执行一次,之后不再执行,而非静态代码块在每new 一次就执行一次。非静态代码块可在普通方法中定义(不过作用不大);而静态代码块不行。

面向对象三要素

继承

为什么要用继承?

  1. 为了降低代码冗余度
  2. 为了给一个类拓展功能。

如果某一个类给我们提供的功能不能满足我们的需求了,而且此时我们还不能修改这个类。那么我们要第 一时间想到继承

如果有多个类中有相同的特征和行为(属性和方法),并且这多个类之间从逻辑上讲是有一定的关联的。那么 此时,我们可以将这些共同的部分单独写到一个类中。

此时,被提取出的这个类,称作是–父类,基类,超类
具有相同特征的那些类,称作是–子类,派生类

他们之间的关系,是 继承:子类继承自父类

关键字:extends 用来描述两个类之间的继承关系

特点:

  1. Java语言是单继承的,一个类只能有一个父类,一个类可以有多个子类 在某些语言中是支持多继承的。例如:C++、python… 但是在多继承中,会有一个问题:二义性。 虽然很多语言都抛弃了多继承,但是还是会用其他的方式来间接的实现类似多继承。 例如:在java中是用接口实现的。 Java中所有的类都直接或者简介的继承自 Object 类
  2. 子类可以访问到父类中能看的见的成员
    能看得见的:访问权限
  3. 构造方法不能继承。
封装性

是一种编程思想:

狭义上的封装:

一个类中的某一些属性,我们不希望外界直接访问。(外界赋的值可能不是我们想要的值)。此时,我们可以 将这个属性私有化(private),杜绝了外界直接访问这个属性的可能性。但是我们还需要再去提供用来访问这 个属性的方法。(设置值、获取值)(setter/getter)

private:是访问权限修饰符的其中之一,可以用来修饰属性和方法。被private修饰的成员,只能够在当前的类中访问

多态

​ 一、使用父类类型的引用指向子类的对象;(接口的引用可以指向实现类的对象)

​ 二、该引用只能调用父类中定义的方法和变量;

​ 三、如果子类中重写了父类中的一个方法,那么在调用这个方法的时候,将会调用子类中的这个方法;(动态连接、动态调用)

​ 四、变量不能被重写(覆盖),”重写“的概念只针对方法,如果在子类中”重写“了父类中的变量,那么在编译时会报错

​ 五、向上转型与向下转型

​ 子类引用的对象转换为父类类型称为向上转型。通俗地说就是是将子类对象转为父类对象。(只能调用父类中定义的方法)此处父类对象可以是接口。 (子类转型为父类类型的过程是向上转型)

​ 向下转型需要考虑安全性,如果父类引用的对象是父类本身,那么在向下转型的过程中是不安全的,编译不会出错,但是运行时会出现java.lang.ClassCastException错误。它可以使用instanceof来避免出错此类错误即能否向下转型,**只有先经过向上转型的对象才能继续向下转型 **《声明的父类引用,通过实例化子类转为父类对象,然后才能继续向下转型为子类对象(掉用子类中的方法)》(接口类型转型为实现类类型的过程是向下转型)

​ Java 中的instanceof 运算符是用来在运行时指出对象是否是特定类的一个实例。instanceof通过返回一个布尔值来指出,这个对象是否是这个特定类或者是它的子类的一个实例。

访问权限可以修饰什么可以访问的范围
private类成员只能在当前的类中访问
default类成员、类只能在当前的包中进行访问
protected类成员可以在当前的包中访问,也可以在跨包的子类中访问
public类成员、类可以在项目中任意的位置进行访问

public > protected > default > private

注:包权限没有访问权限修饰符,如果一个方法、属性、类…没有使用任意的访问权限修饰符来修饰,那么 他的访问权限就是包权限

类中成员的默认访问权限时default

​ 接口中所有成员的属性都为public static final,也就是说接口中声明的变量都是常量,不能被继承接口中所有方法的访问属性为public

Object类

常用方法(所有类的父类)

返回一个对象的字符串表示形式。 public String toString() {    return “”; }

获取一个对象的类型(Class对象) */ public Class<?> getClass();

用来比较两个对象是否相同的,在Object类中,默认还是比较两个对象的地址 * 可以重写equals,实现自己的比较的规则 public boolean equals(Object other) {    return (this == other); }

获取一个对象的哈希码、散列码 public int hashCode() ;

析构方法 protected void finalize() throws Throwable { }

抽象类

抽象:abstract

抽象类:用关键字abstract修饰的类,就是抽象类

抽象方法:用关键字abstract修饰的方法,就是抽象方法

抽象方法:

抽象方法使用abstract来修饰,只有声明,没有实现。

抽象方法,只能够写在抽象类中。

抽象类:

抽象类使用abstract来修饰,抽象类不能实例化对象。

抽象类中是可以写非静态的成员的,这时候这些非静态成员是可以继承给子类的。

抽象类中是可以包含构造方法的。

结合抽象类和抽象方法:

非抽象子类在继承一个抽象父类的同时,要实现父类中所有的抽象方法。

注意事项:

final关键字

  1. 抽象类可以用final来修饰吗?
    1. 不能!因为final表示这个类无法被继承。但是对于抽象类来说,如果无法被继承,则这个抽象类没 有任何意义。
  2. 抽象方法可以用final修饰吗?
    1. 不能!因为final修饰的方法无法被重写。但是抽象方法又只能写在抽象类中。如果一个抽象方法用 final来修饰了,此时这个方法将无法被非抽象子类重写,那这个子类就会有问题。

接口

关键字:interface
语法:

[访问权限修饰符] interface 接口名字 {    
// 接口中的成员 
}

和类是比较像的,但是他不是类

  1. 访问权限修饰符:和类一样,只能有 public 和默认的default权限。
  2. 接口不是类,不能实例化对象。
  3. 接口,暂时和类写成平级的关系。
  4. 接口名字是一个标识符,遵循大驼峰命名法
接口中成员的定义:
  1. 属性:接口中的属性,默认的修饰符是 public static final
  2. 构造方法:接口中不能写构造方法
  3. 方法:
    1. 接口中的方法都是抽象方法
    2. 接口中的方法访问权限修饰符都是public
接口的实现

实现接口关键字: implements

让类实现接口:

public class Shunfeng extends Company implements Express {    
}
  1. 一个非抽象类在实现接口后,需要实现接口中所有的抽象方法。
  2. 一个类在继承自一个父类后,还可以再去实现接口。
    1. 如果同时有父类和接口,那么继承父类在前,实现接口在后
  3. 一个类可以实现多个接口
    1. 如果一个类实现的多个接口中有相同的方法,这个方法在实现类中只需要实现一次即可。
  4. 接口之间是有继承关系的,而且接口之间的继承是多继承。
public interface GrilFriend extends HouseKeeping, Takecare {    }

defaul方法

是 jdk_1.8 之后添加的新特性

是一个关键字,可以用来修饰接口中的方法。被default修饰的接口中的方法,可以有实现部分。

在实现类实现接口的时候,default方法不是必须实现的。如果重写这个方法,那么可以用自己的实现覆盖接口中的实现;如果没有重写这个方法,则会采用接口中默认的实现方式。

如果接口中定义的规则,对所有的实现类来说,实现方式基本是一样的。那么此时这个方法就可以用default来添加一个默认的实现。

Lambda表达式

是jdk_1.8 之后添加的新特性

是一个语法糖。想让语法更加简(sha)单(gua)。

Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。

lambda 表达式的语法格式如下:

(parameters) -> expression
或
(parameters) ->{ statements; }

lambda表达式的重要特征:

​ *可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。

​ ***可选的参数圆括号:**一个参数无需定义圆括号,但多个参数需要定义圆括号。

​ ***可选的大括号:**如果主体包含了一个语句,就不需要使用大括号。

​ ****可选的返回关键字:****如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。

内部类

顾名思义:就是在一个类中写的类。在Java中,支持有四种内部类,但是对于我们来说,用的比较多的就是匿名内部类,另外三种使用频率不高。

成员内部类

写在一个类中,与外部类其他的方法平级,并且这个类是非静态的类。

此时,这个类说白了,就是外部类对象的一个成员,类似于属性、方法

特点:

  1. 内部类的访问权限:可以是任意的权限。

  2. 对象的实例化:需要通过外部类对象.new的方式来实例化

  3. 编译后生成OutteerClass$InnerClass.class的字节码文件

  4. 关于属性访问:(仅局限于外部类属性、内部类属性、方法参数同名的情况)

    System.out.println(name);					// 参数
    System.out.println(this.name);				// 内部类属性
    System.out.println(OutterClass.this.name);	// 外部类属性
    
静态内部类

写在一个类中,与外部类其他的方法平级,并且这个类是静态的类。

此时,这个类说白了,就是外部类的一个类成员

特点:

  1. 内部类的访问权限:可以是任意权限。
  2. 对象的实例化:
    1. OutterClass.InnerClass inner = new OutterClass.InnerClass();
    2. import OutterClass.InnerClass;
  3. 编译后生成OutterClass$InnerClass.class字节码文件
  4. 关于属性访问:
    1. 静态内部类中不能直接访问外部类中的非静态成员
局部内部类

写在某一个代码段中的类,例如写在方法中

  1. 内部类的访问权限:不能写访问权限修饰符
  2. 对象的实例化:只能在当前的代码段中进行访问。其他地方无法访问。
  3. 编译后生成的字节码文件:外部类$序号内部类.class字节码文件
  4. 关于属性访问:同成员内部类。
匿名内部类

没有名字的类。

一般情况下是配合抽象类或者接口使用的。

编译后会生成一个外部类$序号.class文件

编译后会生成一个外部类$序号.class文件

// 这一句话:
// 1. 实例化了一个Person类的子类对象
// 2. 将这个子类对象做了一个向上转型,到Person类型。
Person xiaoming = new Person() {
	// 此时,这里是一个匿名类
	// 这个类和Person的关系:是Person的子类
	@Override
	public void show() {
		System.out.println("匿名内部类中的Show方法");
	}
};

常用类

Math类

给我们提供了若干的数学计算方法

详见API

Random类

关于随机数的生成:

​ 随机数的生成,是由一个随机种子,带入到一个固定的随机数算法中,生成一个数字序列。

​ 如果随机种子相同,产生的随机数列也相同。

BigInteger / BigDecimal 类

用来处理非常大的数字的基本运算

Date / SimpleDateFormat 类

Date: 是用来表示一个时间、日期的类

常用方法描述
Date()获取当前时间
Date(long time)获取指定时间戳对应的时间
void setTime(long time)使用指定时间戳设置一个日期对象
long getTime()获取一个指定日期对象的时间戳
equals(Object obj)判断是否与另外一个日期相同
boolean before(Date other)判断是否在另外一个日期之前
boolean after(Date other)判断是否在另外一个日期之后

SimpleDateFormat: 是一个用来格式化日期的类

  1. 可以将一个Date格式化为指定格式的字符串
    2. 可以将一个自定格式的字符串解析为一个Date

yyyy: 年

yy: 年(短)

MM: 月

dd: 日

HH: 时(24小时制)

hh: 时(12小时制)

mm: 分

ss: 秒

常用方法描述
SimpleDateFormat(String pattern)使用指定的格式来实例化一个对象
String format(Date date)将一个日期格式化为指定的字符串
Date parse(String str)将一个指定格式的字符串解析为一个日期
Calendar类

是一个用来操作日期的类。提供了若干个对一个日期进行操作的方法。

常用静态常量,用来描述一些字段

YEAR: 年

MONTH: 月(注意:月份从0开始)

DAY_OF_MONTH: 日

HOUR: 时(12小时制)

HOUR_OF_DAY: 时(24小时制)

MINUTE: 分

SECOND: 秒

方法描述
getInstance()获取一个当前日期的Calenadar对象
get(int filed)获取指定ID的值
set(int field, int value)设置指定ID的值
set(int year, int month, int date)设置年、月、日
set(int year, int month, int date, int hourOfDay, int minute, int day)设置年、月、日、时、分、秒
add(int filed, int amount)对某个id的值进行增(如果想减,将值修改成负数即可)
setTime(Date date)通过一个Date对象,对一个日期进行赋值
getTime()通过一个日期对象,获取一个Date对象
boolean before(Object other)判断是否在另外一个日期之前
boolean after(Object other)判断是否在另外一个日期之后
boolean equals(Object other)判断是否和另外一个日期相同

​ set()方法所设置的值不进行自动较正 add()的时候会进行矫正 获取时间戳 Date.getTiem(),通过时间戳设置时间 Date.setTime(Long Time)

包装类

作用主要有以下两方面:  - 编码过程中只接收对象的情况,比如List中只能存入对象,不能存入基本数据类型;比如一个方法的参数是Object时,不能传入基本数据类型,但可以传入对应的包装类;  - 方便类型之间的转换,比如String和int之间的转换可以通过int的包装类Integer来实现

基本数据类型(值类型):byte, short, int, long, float, double, char, boolean

包装类:将基本数据类型,用一个类进行了一层包装,可以按照引用类型进行使用。同时还提供了若干用来进行数据转换的操作。

包装类按照基本数据类型的包装功能,分为八种:

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

数据的装箱拆箱

**装箱:**由基本数据类型,转型为包装类型

**拆箱:**由包装类型,转型为基本数据类型

在jdk1.5之后,装箱与拆箱都是自动完成的。

关于Integer

在1.5之后的自动装箱中,Integer a = 100; 这一句话相当于自动调用了 Integer a = Integer.valueOf(100);

阅读valueOf原码,发现如果要包装的数字是[-128, 127]之间,那么会从一个预设好的Integer数组中来获取元素(java.lang.Integer.IntegerCache.chache),所以每次获取的元素都是相同的。所以地址也是相同的。但是如果要包装的数字不在这个范围内,那么就会实例化一个新的Integer对象。

枚举(Enum)

是jdk1.5之后新增的特性

是一种数据类型。class、interface, 是用关键字enum来修饰

[访问权限修饰符] enum 枚举名字 {
    枚举中的元素
}

枚举一般情况下是用来描述一些取值范围有限的数据。例如:星期几

实际上,枚举不止是这样使用,一个enum除了不能继承自一个enum之外,我们基本上可以将enum看作一个常规的类。也就是说我们可以向enum中添加方法。

一般来说,我们希望每个枚举实例能够返回对自身的描述。根据这样的一个需求,我们可以提供一个构造器,专门负责处理这个额外的信息,然后添加一个方法,返回这个描述信息。


public enum RealEnum {
Normal("正常态", 1), Update("已更新", 2), Deleted("已删除", 3), Fired("已屏蔽", 4);
String s;
int i;
private  RealEnum(String s,int i) {
    this.i = i;
    this.s = s;
}
public String getS() {
    return s;
}
public void setS(String s) {
    this.s = s;
}
public int getI() {
    return i;
}
public void setI(int i) {
    this.i = i;
};

需要注意,如果你打算定义自己的方法,那么必须在enum实例序列的最后添加一个分号。同时,java要求必须先定义enum的实例。构造器必须生命为private。

异常处理

基本语法

异常:就是程序在运行的过程中遇到的种种不正常的情况。

特点:如果一个程序在运行中遇到了一个未经处理的异常,则这个异常会终止程序的运行。

​ 但是如果程序出现的异常被处理了,此时程序不会被终止。所以我们需要知道怎么去处理异常。

其实在Java中,异常也是一个类。

类的体系

  • Throwable: 是所有的异常的根类
    • Error: 发生在编译器级别的,我们程序无法处理的错误。
    • Exception: 我们可以去处理的异常。
      • RuntimeException:

**异常的分类:**可以分成两种异常:

  • 运行时异常(Runtime Exception)
    • 发生在程序运行的过程中的异常。
    • 如果不处理这种异常,程序可以正常编译,但是当执行到异常产生的时候,会终止程序的运行。
    • java虚拟机能自动处理运行时异常
    • 例如:NullPointerException、IndexOutOfBoundsException、ArithmeticException…
  • 非运行时异常(Non-Runtime Exception)
    • 发生在程序编译的过程中的异常。(编译时异常)
    • 如果不处理这种异常,程序将无法进行编译。
    • 例如:ParseException…
处理异常

需要使用语法:try-catch-finally

语法:

try {

​ // 这里写可能会产生异常的代码。

​ // 注意:

​ // 一旦这里面的代码产生了异常,从异常产生开始往后所有try中的代码都不再执行,直接执行指定的catch

}

catch(需要捕获的异常类型 标识符) {

​ // 捕获异常,如果try中产生的异常类型和我们要捕获的异常类型匹配,此时会执行这个代码段中的内容

​ // 如果执行到这里了,相当于这个异常被捕获、处理了,这个异常将不再终止程序的运行。

}

finally {

​ // 这里的代码始终会执行。

​ // 无论try中的代码有没有异常产生,这里的代码都会执行。

​ // 在这里我们一般情况下是做一些资源释放的工作。

}

备注:

  1. 以上,是完整的try-catch-finally语句。但是实际在用的时候,try后面可以只有catch, 也可只有finally,但是不能什么都没有。
  2. 一般情况下,catch我们是不会省略不写的。
  3. 如果try中的代码可能产生的异常不止一种
    1. 如果需要对产生的不同异常进行不同的处理,可以使用多个catch语句
      1. 多个catch语句的先后顺序
        1. 如果多个catch中的异常,没有继承关系,则先后顺序没有影响
        2. 如果多个catch中的异常,有继承关系,则子类异常在前,父类异常在后
    2. 如果需要对某些异常做同样的处理,可以在同一个catch中,用 | 拼接所有要处理的异常。
      1. 这些用|拼接起来的异常之间,不能有继承关系
    3. 如果需要对所有的异常做同样的处理,可以在一个catch中捕获一个父类异常。
public static int show(int a, int b) {
	int c = 0;
	try {
		c = a / b;
		// 能走到这里,说明上面的除没有异常。
		return c;
	}
	catch (ArithmeticException e) {
		System.out.println("出现了一个算术异常");
		return c;
	}
	finally {
		// 在return之前,执行finally中的代码段
		System.out.println("finally中的代码执行了");
		c = -10;
	}
}

以上代码段,在try和catch中都有return语句。

finally中的代码始终会执行,但是针对这种情况,他的执行时机:

先执行return语句,此时,将需要返回的值缓存起来。然后再去执行finally语句中的代码,执行结束后,返回刚才缓存的那个值。

关键字

throw:

常用在某一个方法中,表示抛出一个异常对象。等在调用这个方法的时候去处理这个异常。

一个异常对象被实例化完成后,不具备任何意义。只有被throw关键字抛出了,才具备异常的功能。

throws

  1. 常用在方法的声明部分,用来描述这个方法可能会抛出什么异常,给调用这个方法的部分看的。
    • 如果在方法中使用throw抛出了一个Runtime Exception:
      • throws可以写,也可以不写
      • 备注:一般情况下,我们还是会按照实际情况进行描述的。
    • 如果在方法中使用throw抛出了一个Non-Runtime Exception:
      • 此时throws必须写
  2. 可以在方法中不去处理异常,将异常处理提到调用这个方法的时候。

注意:在方法重写中

  1. 如果重写的方法抛出的是一个Non-Runtime Exception
    1. 子类方法抛出的异常需要父类方法抛出异常的子类型,或者等同于父类方法抛出的异常类型
    2. 不能让子类重写的方法抛出异常的类型高于父类方法抛出的异常类型
自定义异常

系统给我们提供了很多的异常类,但是这些异常类并不能够满足我们所有的需求。这种情况下,我们就需要去自定义异常。继承自异常类,写一个子类即可。

  1. 自定义RuntimeException

    1. 继承自RuntimeException类,写一个子类。这个子类异常就是一个运行时异常。

      class NumberOfLegException extends RuntimeException {
      	/**
      	 * 通过一个异常描述信息来实例化一个异常对象
      	 * @param message
      	 */
      	public NumberOfLegException(String message) {
      		// 怎么样去设置这个异常信息?
      		super(message);
      	}
      }
      
  2. 自定义Non-Runtime Exception

    1. 继承自Exception类,写一个子类。这个子类异常就是一个非运行时异常。

      class NumberOfLegException extends Exception {
      	/**
      	 * 通过一个异常描述信息来实例化一个异常对象
      	 * @param message
      	 */
      	public NumberOfLegException(String message) {
      		// 怎么样去设置这个异常信息?
      		super(message);
      	}
      }
      

在自定义异常类的时候,类名最好使用Exception作为结尾

字符串操作

字符串类(String)

是若干个字符组成的一个有序序列。字符串String是一个引用数据类型。字符串是在常量池中开辟的空间

对于两个字符串是否相同的判断,最好使用equals方法来判断,而不是直接通过==来判断

方法描述
String()获取一个空字符串
String(String original)通过一个字符串,实例化另外一个字符串
String(char[] arr)将一个字符数组拼接成一个字符串
String(char[] array, int offset, int count)将一个字符数组中的指定部分拼接成一个字符串
String(byte[] arr)将一个字节数组转换成一个字符串
String(byte[] arr, int offset, int count,Charset charset)将一个字节数组的指定部分拼接成一个字符串,charset ,指定字符集

关于字符串的所有操作,都不会直接修改字符串本身,会将修改之后的结果用返回值的形式返回。所以如果需要获取到修改之后的字符串,那么需要去接收返回值。

字符串操作类

(StringBuffer、StringBuilder)

这两个类是用来做字符串操作的类,给我们提供了若干个用来操作字符串的方法。类似于String类中方法。

不同之处:可以直接修改字符串中的内容。

StringBuffer和StringBuilder的区别:

  1. StringBuffer是线程安全的,StringBuilder是线程不安全的。
  2. StringBuilder的执行效率比StringBuffer高
正则表达式(语法)

是用来做校验的。校验一个字符串是否满足我们预设的一些规则。

基础语法 “^([]{})([]{})([]{})$”

正则字符串 = “开始([包含内容]{长度})([包含内容]{长度})([包含内容]{长度})结束”

String.matches("^([]{})([]{})([]{})$")

Pattern、Mather

Pattern.matches("^([]{})([]{})([]{})$", "String");
// 获取一个Pattern对象,不能实例化 
//典型的调用序列
Pattern pattern = Pattern.compile("\\d+");//参数正则表达式
Matcher m = pattern.matcher("");//参数要匹配的字符序列
boolean b = m.matches();

正则中的分组是从1开始的

集合

也是一个容器,用来存储相兼容的数据类型的容器。

集合和数组的区别:

  1. 数组中可以存储基本数据类型和引用数据类型。集合中只能存储引用数据类型。
  2. 集合是一个可变长度的容器。可以随时对集合做增、删操作。数组是一个定长的容器。
  3. 数组中元素操作比较单一。集合提供了很多用来操作元素的方法。

集合框架:

  • Collection
    • List
      • ArrayList
      • LinkedList
      • Vector
        • Stack
    • Set
      • HashSet
        • LinkedHashSet
      • TreeSet
  • Map
    • HashMap
      • LinkedHashMap
    • TreeMap
    • Hashtable
      • Properties

泛型

泛型:是一个泛指的类型。可以将一个类型在类与类、接口、方法之间进行传递。类似于传参。

关于泛型的定义:

  1. 泛型是一个标识符,遵循大驼峰命名法。
  2. 泛型在可以在类、接口、方法中去进行使用。
  3. 在使用类、接口、方法的时候,需要给泛型赋类型。
    1. 泛型仅仅能够表示引用数据类型。
    2. 泛型如果不指定,默认是Object类型。

泛型在类中的使用:

[访问权限修饰符] [其他的修饰符] class 类名<泛型> {
   
}
注意事项:
1. 泛型只能在当前的类中使用,不能被继承。

泛型限定:

可以对泛型进行一个范围约束。

// 限定Human中T的类型可以是Dog类型,也可以是Dog类型的子类型
class Human<T extends Dog> {}
// ? : 表示通配符,此时,作为参数的Human泛型可以是任意类型
// ? extends Animal: 此时,作为参数的Human,泛型类型可以是Animal类型,或者其子类型
// ? extends 接口:此时,作为参数的Human,泛型类型可以是接口的实现类类型
// ? super Dog: 此时,作为参数的Human,泛型类型可以是Dog类型,也可以是Dog类型的父类型
public static void show(Human<? super Dog> human) {
}

<? extends E>和<? super E>

<? extends E> 是 Upper Bound(上限) 的通配符,用来限制元素的类型的上限

​  <? super E> 是 Lower Bound(下限) 的通配符 ,用来限制元素的类型下限

PECS法则

​ 生产者(Producer)使用extends,消费者(Consumer)使用super

​ 1、生产者

​ 如果你需要一个提供E类型元素的集合,使用泛型通配符<? extends E>。它好比一个生产者,可以提供数据。

​ 2、消费者

​ 如果你需要一个只能装入E类型元素的集合,使用泛型通配符<? super E>。它好比一个消费者,可以消费你提供的数据。

​ 3、既是生产者也是消费者

​ 既要存储又要读取,那就别使用泛型通配符。

Collection接口

如果使用增强for循环去遍历一个集合,在遍历的过程中,不允许对集合中的元素进行增、删的操作。

增强for循环的底层实现,就是用迭代器实现的。

迭代器

是一个类,是用来遍历一个集合的。

迭代器的工作原理:

​ boolean hasNext(): 判断当前指向的元素后面还有没有元素。

​ T next(): 指向集合中的下一个元素,并且返回这个元素。

​ 如果指向了一个超出下标范围的地方,会抛出异常 NoSuchElementException

​ 实例化一个新的对象,默认指向集合中一个不存在的位置(-1)然后,使用next方法依次向后指向每一个元素并获取,直到hashNext判断为false,表示最后已经没有元素了,此时停止迭代。

​ remove(): 删除迭代器当前指向的这个元素。可以在迭代的过程中使用这个方法进行元素的删除。

Iterator和ListIterator主要区别在以下方面

  • iterator()方法在set和list接口中都有定义,但是ListIterator()仅存在于list接口中(或实现类中);
  • ListIterator有add()方法,可以向List中添加对象,而Iterator不能
  • ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator就不可以。
  • ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。
  • 都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现。Iierator仅能遍历,不能修改。
List接口以及实现类

List接口是Collection接口的子接口,是用来存储元素的集合。

特点:

  1. 存储的元素是有序的。元素添加的顺序和底层存储的顺序是一致的。
  2. 存储的元素是可以重复的。

实现类

ArrayList

LinkedList

Vector

Stack

关于ArrayList和LinkedList

  1. 他们都是List接口的实现类,都能实现元素的存储,以及List接口中定义的所有的方法。
  2. 区别:
    1. ArrayList底层实现是数组,查询效率高,增删效率低。
    2. LinkedList底层实现是双链表,查询效率低,增删效率高。

关于数组和链表

  1. 他们都是常用的数据结构之一
  2. 数组:
  3. 链表:
    1. 是常见数据结构之一。分为单链表和双链表(单向链表和双向链表)
    2. 链表存储的特点:
      1. 在链表中存储的每一个元素,地址是不连续的。区别于数组。
      2. 每一个节点在存储的时候,除了存储要存储的值之外,还要再存:
        1. 单链表:下一个节点的地址。
        2. 双链表:上一个节点的地址、下一个节点的地址。
Set接口和常用的实现类

Set集合,是Collection框架中非常重要的一种集合。

特点:

  1. 集合中存储的元素是不允许重复的。
  2. HashSet中存储的元素顺序和添加顺序是不一致的。
  3. 在Set集合中,没有下标的概念。

set集合的去重规则:

  1. 需要有两个方法来辅助:hashCode() 、equals()
  2. 先比较两个对象的hashCode(), 如果不同,则不是相同的对象。
  3. 如果hashCode()相同,再比较equals,如果equals也相同,则视为同一个对象。

HashSet

不能保证元素的排列顺序,顺序有可能发生变化

不是同步的

集合元素可以是null,但只能放入一个null

当向HashSet结合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据 hashCode值来决定该对象在HashSet中存储位置。

LinkedHashSet:

是HashSet的子类,是由哈希表和链表实现的一个集合。这个集合中存储的元素是有序的(元素存储的顺序和添加的顺序是一致的)。但是:虽然说LinkedList集合中的元素是有序的,在这个集合中依然没有下标的概念。

LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet。

TreeSet

特点:

  1. 这个集合是Set集合的实现类之一,所以具有所有的Set集合的共性:去重(comparator 返回值为0,默认为相同)
  2. 这个集合是一个自带排序的集合,元素在添加到集合中时,会自动的进行排序。

关于排序:

  1. 无参的构造方法:要求存储的元素对应的类,需要实现Comparable接口。
  2. TreeSet(Comparator<? extends E> c) 在实例化一个TreeSet的同时,定制一个元素比较大小的规则

Map框架

Map<K,V>接口

Map中存储的元素,是以键值对的形式存在的。

键值对:是由一个键(Key)和一个值(Value)组成的一种数据结构。

在Map中,元素是一个个的键值对。一个键对应一个值。不允许一个键对应多个值,也不允许一个键没有值。

Map集合注意事项

  1. 在一个map中,不允许出现重复的Key。
HashMap和Hashtable:
  1. Hashtable是线程安全的,HashMap是线程不安全的。
  2. Hashtable的效率低,HashMap的效率高。
  3. HashMap允许以null作为键,Hashtable不允许以null作为键。
  4. HashMap是Map集合的新实现,父类是AbstractMap;Hashtable是老的实现,父类是Dictionary
  5. 算法效率不同。HashMap的算法比Hashtable的效率高。
TreeMap:

是一个带有排序的集合。所谓的排序,在这里指的是按照Key进行排序。

这里的排序,与TreeSet基本相同。

  1. 让Key对应的类去实现Comparable接口
  2. 在实例化TreeMap的时候,设置一个Comparator接口实现类。

IO/NIO

File

这个类对一个文件(夹)的描述。

关于路径分隔符:

路径分隔符是对目录的分隔。表示一层嵌套关系。

在windows系统下,分隔符是:\

在UNIX体系下,分隔符是:/

构造方法参数
File(String pathName)使用一个指定的文件路径来实例化一个File对象
File(String parent, String name)给定一个父目录路径和一个子文件名字,系统会自动的将其拼接在一起
File(File parent, String child)给定一个父目录和一个子文件的名字,系统自动拼接路径
常用方法描述
boolean exists()用来描述指定的路径下到底有没有文件(夹)存在。
boolean isFile()判断指定路径的内容是不是一个文件
boolean isDirectory()判断指定路径的内容是不是一个文件夹
boolean mkdir()在指定的路径创建文件夹,返回值代表创建成功还是失败
boolean mkdirs()在指定的路径创建文件夹,可以创建多级目录
boolean createNewFile()在指定的路径创建一个空文件,会有一个IOException
boolean isHidden()判断一个文件是否是隐藏的

IO

IO : Iuput 、Output

如果我们需要在程序中,进行文件的处理操作。(读取一个文件中的内容、向一个文件中写入内容)。

分类:

方向:输入流和输出流

数据单元:字节流和字符流

InputStream: 字节输入流(抽象类)

OutputStream: 字节输出流(抽象类)

Reader: 字符输入流(抽象类)

Writer: 字符输出流(抽象类)

无论是使用什么流,在使用结束之后,一定要记得关闭这个流。

InputStream:int read(byte[] arr)

Reader : int read(char[]arr)

参数:从流中读取数据,需要使用到一个字节(字符)数组。read方法会将读取到的数据填充到这个字节(字符)数组中。所以,读取完成后,我们直接从这个字节数组中取数据即可。

返回值:每次读取数据后,返回这一次读取到了多少个字节的数据。如果这个方法返回值为-1,说明本次没有读取到任何数据,读取完成。

OutputStream: void write(byte[] b) 将b.length字节从指定字节写入此输入流

Writer:write(char[] cbuf) 写入一个字符数组

// 先声明
InputStream is = null;
OutputStream os=null;
Reader reader=null;
Writer writer=null;
try {
	// 实例化一个流
	is = FileInputStream(String name) 
    os= FileOutputStream(File file, boolean append) ;
    reader = FileReader(File file) ;
    writer = new FileWriter(String fileName,boolean append); //boolean如果是 true ,那么数据将被写入文件的末尾而不是开头。
	
	// 从管道中读取数据:
	// 实例化一个数组,用来存储每次读取到的数据
	byte[] contents = new byte[100];
    char[] contents=new char[100];
  	// 用来记录每次读取到了多少数据
	int length = 0;
	// 循环读取,将每次读取到的数据长度给length赋值,并判断是否为-1
	while ((length = is.read(contents)) != -1) {
		// 通过一个字节(字符)数组实例化一个字符串
		os.writer(contents, 0, length)
		writer.writer(contents, 0, length)
            .flush()
	}
	
} catch (FileNotFoundException e) {
	e.printStackTrace();
} catch (IOException e) {
	e.printStackTrace();
} finally {
	// 判断是否为空,因为如果最开始实例化的时候,文件不存在,则is实例化失败,
	// 此时,如果is是null,那么再去关闭的时候,就会出现NullPointerException
	if (is != null) {
		try {
			is.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
缓冲流

BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter

缓冲流:是一个用来操作文件的流,对咱们昨天学习的流进行了一个包装。在包装的类中,做了一个缓存数组。当我们在读取或者写文件的时候,直接和这个数组交互。最直观的一点体现:缓冲流,由于这个缓冲数组的存在。他的效率比昨天学的流要高

BufferInputStream、BufferedOutputStream:

// 在实例化一个缓冲字节流的时候,构造方法需要有一个字节流
// 此时,这个字节流存在的意义,只是为了去实例化这个缓冲流。在使用完成后,缓冲流需要我们手动关闭。但是用来实例化这个缓冲流的字节流,我们不需要手动关闭。在缓冲流内部,已经实现了对其进行关闭的方法。
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File("file\\test")));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("file\\out"));

BufferReader、BufferedWriter:

构造方法和流的关闭部分:同上方字节流

BufferedReader reader=new BufferedReader(new FileReader(new File("")));
BufferedWriter writer =new BufferedWriter(new FileWriter(""));
转换流

InputStreamReader、OutputStreamWriter

转换流:保留了字符流对文本进行操作时候的便利性,保留了字节流的安全性。产生的一种新的流,主要用来做关于文本文件的处理。还可以解决:关于采用了不同字符集的文本之间的处理问题。可以使用指定的字符集来读取一个文本,也可以使用指定的字符集来写一个文本。

InputStreamReader is=new InputStreamReader(new FileInputStream("") , "utf8");
OutputStreamWriter os=new OutputStreamWriter(new FileOutputStream(new File("")));
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(new File("file\\test"))));
标准输入输出流

System.in、System.out

PraintStream

Scanner类

PrintStream out = System.out;
PrintStream	ps = new PrintStream(new BufferedOutputStream(new FileOutputStream("file\\out", true)));
System.setOut(ps);
System.setOut(out);//用完设置回去
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("file\\test"));
System.setIn(bis);
Scanner scanner = new Scanner(new File("file\\test"));
对象流

将一个对象序列化和反序列化。

序列化:将一个对象的信息(属性值)以文件的形式保存到磁盘上。

反序列化:读取磁盘上的某一个文件信息,并将其解析为一个对象,并给这个对象的属性进行赋值。

注意事项:

  1. 需要序列化的类,要实现Serializable接口
  2. 如果要序列一个集合,则要保证集合中的每一个元素都必须是Serializable接口的实现类对象
// 1. 声明一个对象输出流
	ObjectOutputStream os = null;
// 2. 实例化(使用一个字节输出流)
	os = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(path)));	
// 3. 将一个对象的信息保存到本地
	os.writeObject(person);
	os.flush();

// 1. 声明一个对象输入流
	ObjectInputStream ois = null;
// 2. 实例化
	ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream(path)));	
// 3. 读取指定路径下序列化的文件
	Object obj = ois.readObject();			
// 4. 
	if (obj instanceof Person) {
		return (Person)obj;
			}
Properties

是一个集合。是Hashtable的子类。

是一个Map,存储的元素也是一个键值对。是将这些键值以一个文件的形式存储的。

		// 实例化一个Properties
		Properties properties = new Properties();
		// 从一个指定的properties文件中读取数据
		properties.load(new BufferedInputStream(new FileInputStream("file\\test.properties")));	
		// 遍历properties
		Set<Object> keys = properties.keySet();
		for (Object k : keys) {
			System.out.println(k + " = " + properties.getProperty((String)k, "abc"));
		}
		
		// 在Properties中,建议使用这一种方式来添加键值对。
		properties.setProperty("age", "20");
		properties.setProperty("height", "170");
		properties.setProperty("weight", "170");
		// 将键值对存储到指定的文件中。
		properties.store(new BufferedOutputStream(new FileOutputStream("file\\test.properties")), "add key weight = 170");

NIO

NIO(New IO):

从 JDK 1.4 开始引入的一个用来替代传统IO的API。NIO与传统的IO具有相同的作用,但是使用的方式是不一样的。NIO是面向缓冲区(Buffe)的、基于通道(Channel)。

从 JDK 1.7 开始加入了一些新的元素。被称作 NIO.2

NIO 和 IO 有什么区别:

  1. IO是面向流(Stream)的,NIO是面向缓冲区(Buffer)的。
  2. IO是阻塞型的,NIO是非阻塞型的。
缓冲区Buffer

是一个用来存储基本数据类型的容器。(类似一个数组)。

按照其存储的数据类型不同,缓冲区有着不同的分类:(没有boolean)

ByteBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer、CharBuffer

以上,这些缓冲区有着相同的方法来进行获取和数据的管理。因为所有以上的缓冲区都是继承自Buffer类的。

常用属性和方法:

  1. 获取缓冲区:不是通过new的方式,而是通过静态方法 allocate(int capacity) 来获取缓冲区。

  2. 属性:

    1. capacity: 容量。表示缓冲区最多可以存储多少数据。一旦设置后,将不能改变。
    2. limit: 界限。表示缓冲区可以操作的数据数量。(实际上存储了多少数据)。
    3. position: 位置。缓冲区中正在进行操作的数据的位置。
    4. mark: 使用mark()方法标记的位置。

    mark <= position <= limit <= capacity

  3. 常用方法:

    1. put():将数据放入缓冲区
    2. get():从缓冲区中获取数据
    3. flip():切换成读模式
    4. rewind():重新读取(position重置为0)
    5. clear():清空缓冲区。将缓冲区中limit和position重置为allocate之后的状态。
      1. clear方法只是重置了一下标记,缓冲区中的数据还在。
    6. mark():在指定的position做一个标记
    7. reset():将position的位置重置为mark时指定的标记处

直接缓冲区和非直接缓冲区:(了解)

非直接缓冲区:通过allocate(int capacity)方法开辟的缓冲区,叫做非直接缓冲区。将缓冲区建立在JVM中。

直接缓冲区:通过allocateDirect(int capacity)方法开辟的缓冲区,叫做直接缓冲区。将缓冲求建立在物理内存。效率高。只有ByteBuffer有。

通道Channel

建立文件和程序的连接。在NIO中负责配合缓冲区进行数据的传输。通道本身是不具备数据传输功能的。

Channel是一个定义在 java.nio.Channels 包下面的接口,常用实现类:

FileChannel:文件操作

SocketChannel、ServerSocketChannel、DatagramChannel

获取通道:

  1. 可以使用支持通道的类,提供的方法 getChannel() 获取

    1. 支持通道的类:
      1. 本地IO:FileInputStream、FileOutputStream
      2. 网络IO:Socket、ServerSocket、DatagramSocket
    FileInputStream fis = new FileInputStream("");
    FileOutputStream fos = new FileOutputStream("");
    // 获取通道
    FileChannel inChannel/*outChannel*/ = /*fos*/fis.getChannel();
    // 1. 获取缓冲区
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    // 2. 将通道中的数据读取到缓冲区,如果返回-1说明读取结束
    while (inChannel.read(buffer) != -1) {
    	// 将buffer切换成读模式
    	buffer.flip();
        // 3. 将缓冲区中的数据写出来
    	outChannel.write(buffer);
    	// 4. 清空缓冲区的数据
    	buffer.clear();
    //关闭流和通道
    inChannel.close();
    fis.close();
    
  2. 在 JDK 1.7 之后 NIO.2 中,针对各种通过提供了静态方法 open()

    //1.使用Channel提供的一个静态方法open()
    FileChannel inChannel/*outChannel*/ 
    = FileChannel.open(Paths.get("C:\\Users\\luds\\Desktop\\1.mp4"), StandardOpenOption.READ);//StandardOpenOption 权限
    // 通道之间的数据传输
    inChannel.transferTo(0, inChannel.size(), outChannel);
    outChannel.transferFrom(inChannel, 0, inChannel.size());
    // 使用直接缓冲区:
    // 内存映射文件:
    MappedByteBuffer inBuffer/*outChannel*/ = inChannel/*outChannel*/.map(MapMode.READ_ONLY, 0, inChannel.size());//MapMode.READ_ONLY, 权限
    // 直接缓冲区数据的读写
    byte[] dst = new byte[inBuffer.limit()];
    inBuffer.get(dst);
    outBuffer.put(dst);
    
    inChannel.close();
    outChannel.close();
    
  3. 在 JDK 1.7 之后 NIO.2 中,Files工具类中 newByteChannel()

分散与聚合

分散读取:将一个通道中的数据读取到多个缓冲区中。

聚合写入:将多个缓冲区中的内容写入到一个通道中。

ByteBuffer[] buffers = { buffer1, buffer2 };
inChannel.read(buffers);
outChannel.write(buffers);
字符集
  • 编码:由字符串转成字节数组。

  • 解码:由字节数组转成字符串。

    // 通过名字,获取指定的字符集
    Charset cs = Charset.forName("GBK");
    CharBuffer buffer = CharBuffer.allocate(1024);
    // 编码器:
    // 获取编码器:
    CharsetEncoder encoder = cs.newEncoder();
    ByteBuffer byteBuffer = encoder.encode(buffer);
    // 解码器:
    CharsetDecoder decoder = cs.newDecoder();
    CharBuffer cb = decoder.decode(byteBuffer);
    

线程

前言:

串行:多个任务排成队,依次执行。

并发:多个任务同时执行。

在程序中实现并发:

最常用的两种方式:使用进程、使用线程。

进程:程序执行需要用到所有资源的抽象描述。

一个进程可以理解为是一个程序,但是反之,一个程序就是一个进程,这句话是错误的。

线程

线程是什么?

线程是进程中的执行单元,一个进程可以包含多个线程,并且一个进程中至少有一个线程。(如果一个进程中没有线程了,那么这个进程会终止)。(一个进程在开辟完成后,会自动创建一个线程。这个线程叫做主线程,在主线程中开辟的所有的其他的线程叫做子线程)(线程与线程之间是并发的关系)。

让任务并发,可以提高程序的执行效率。

因为有时候要处理一些需要并发的任务,所以我们需要使用到线程。

是不是线程开辟的越多,程序的效率就越高?

不是!

  1. 线程的开辟需要消耗资源(内存)
  2. 线程多了,每个线程能抢到的CPU时间片就短了。
如何开辟一个线程?

线程,在Java中,用Thread类来表示。

开辟线程:

  1. 继承自Thread类,写一个Thread的子类。并且在这个子类中,重写run方法,在这个重写run方法中,定义这个线程需要执行的任务。(要设置名字写个构造调用父类的构造super(name))
  2. 实现Runnable接口,写一个Runnable接口的实现类。通过Thread类的构造方法Thread(Runnable r) 实例化一个Thread对象。此时,Runnable接口的实现类需要实现接口中的run方法。

两种方式的对比

  1. 使用继承Thread类的方式:
    1. 优点:代码的可读性较强。
    2. 缺点:单继承,如果一个类继承自Thread类,则无法再继承自其它类。
  2. 使用Runnable实现类的方式:
    1. 优点:不会对类原有的继承关系造成影响。
    2. 缺点:代码的可读性没有Thread子类高。

总结:更多情况下我们使用Runnable接口的实现类方式来开辟线程。

开启一个线程中的任务,需要使用的方法是:start(),而不是run()

如果显示调用run方法,其实是将run中的任务,在当前的线程中执行了,而不会开辟新的线程。

守护线程:
也是一个线程,又叫做后台线程。
如果前台线程都结束了,则后台线程也会自动销毁(无论这个线程中的逻辑有没有执行完)

将这个线程设为守护线程,这个操作要放到start之前

线程常用的方法
方法描述
start()开启一个线程。
+ sleep(int timeMillus)线程休眠。将线程休眠指定的时间。
+ currentThread()获取当前线程。
getName()获取一个线程的名字
setName(String name)设置一个线程的名字
Thread(Runnable r, String name)在实例化一个线程的同时,给他设置一个名字
setPrivority(int c)设置线程的优先级
线程的生命周期

生命周期:指的是一个线程对象从最开始被实例化出来,到最后销毁。中间经历的种种的状态。

New(新生态):一个线程对象被实例化完成。此时这个线程处于新生态。

Runnable(就绪状态):调用start(),此时这个线程可以去争抢CPU时间片;sleep()时间到,notify/notifyALL

Run(运行状态):如果线程抢到CPU时间片,此时就是一个Run状态

Blocked(阻塞状态):线程被挂起 :sleep(), t2.join() , 等待资源, wait()【会释放锁】

1.等待阻塞:运行状态中的线程执行wait()方法,JVM会把该线程放入等待池中。 使本线程进入到等待阻塞状态;

2.同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),则JVM会把该线程放入锁池中。 它会进入同步阻塞状态;

3.其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

Dead(死亡):正常结束/异常退出 break

![](C:\Users\mhy\Pictures\Camera Roll\82ddc293417b1019ea5fbd878592c437.jpg)

临界资源问题

由于线程之间是资源共享的。如果有多个线程,同时对一个数据进行操作,此时这个数据会出现问题。

如何解决临界资源问题

如果有一个线程在访问一个临界资源,在访问之前,先对这个资源“上锁”,此时如果有其他的线程也需要访问这个临界资源,需要先查这个资源有没有被上锁,如果没有被上锁,此时这个线程可以访问这个资源;如果上锁了,则此时这个线程进入阻塞状态,等待解锁。

同步代码段

为了解决并发操作可能造成的异常,java的多线程支持引入了同步监视器来解决这个问题,使用同步监视器的通用方法就是同步代码块。其语法如下:

// 同步代码段
// 小括号:就是锁
// 大括号:同步代码段,一般情况下,写需要对临界资源进行的操作
synchronized (obj) {
	
}
// 关于同步锁:可以分成两种:对象锁、类锁

其中obj就是同步监视器,它的含义是:线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,该线程会释放对该同步监视器的锁定。虽然java程序允许使用任何对象作为同步监视器,但 是同步监视器的目的就是为了阻止两个线程对同一个共享资源进行并发访问,因此通常推荐使用可能被并发访问的共享资源充当同步监视器。

释放同步监视器的锁定

1.任何线程进入同步代码块,同步方法之前,必须先获得对同步监视器的锁定,那么如何释放对同步监视器的锁定呢,线程会在一下几种情况下释放同步监视器:

当前线程的同步方法、同步代码块执行结束,当前线程即释放同步监视器;

当前线程在同步代码块、同步方法中遇到break,return终止了该代码块、方法的继续执行;

当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致了该代码块、方法的异常结束;

当前线程执行同步代码块或同步方法时,程序执行了同步监视器对象的wait()方法,则当前线程暂停,并释放同步监视器;

2.以下几种情况,线程不会释放同步监视器:

线程执行同步代码块或同步方法时,程序调用Thread.sleep(),Thread.yield()方法来暂停当前线程的执行,当前线程不会释放同步监视器;

线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放同步监视器,当然,程序应尽量避免使用suspend()和resume()方法来控制线程。

同步方法

同步方法就是使用synchronized关键字修饰某个方法,这个方法就是同步方法。这个同步方法(非static方法)无须显式指定同步监视器,同步方法的同步监视器是this,也就是调用该方法的对象。通过同步方法可以非常方便的实现线程安全的类,线程安全的类有如下特征:

该类的对象可以方便的被多个线程安全的访问;

每个线程调用该对象的任意方法之后都能得到正确的结果;

每个线程调用该对象的任意方法之后,该对象状态依然能保持合理状态。

**注意 **,synchronized可以修饰方法,修饰代码块,但是不能修饰构造器、成员变量等

// 将一个方法中所有的代码进行一个同步
// 相当于将一个方法中所有的代码都放到一个synchronized代码段中
// 同步方法的锁:
// 1. 如果这个方法是一个非静态方法:锁是this
// 2. 如果这个方法是一个静态方法:锁是类锁(当前类.class)
private synchronized void sellTicket() {
}

1.Synchronized修饰非静态方法,实际上是对调用该方法的对象加锁,俗称“对象锁”。

Java中每个对象都有一个锁,并且是唯一的。假设分配的一个对象空间,里面有多个方法,相当于空间里面有多个小房间,如果我们把所有的小房间都加锁,因为这个对象只有一把钥匙,因此同一时间只能有一个人打开一个小房间,然后用完了还回去,再由JVM 去分配下一个获得钥匙的人。

情况1:同一个对象在两个线程中分别访问该对象的两个同步方法

结果:会产生互斥。

解释:因为锁针对的是对象,当对象调用一个synchronized方法时,其他同步方法需要等待其执行结束并释放锁后才能执行。

情况2:不同对象在两个线程中调用同一个同步方法

结果:不会产生互斥。

解释:因为是两个对象,锁针对的是对象,并不是方法,所以可以并发执行,不会互斥。形象的来说就是因为我们每个线程在调用方法的时候都是new 一个对象,那么就会出现两个空间,两把钥匙,

2.Synchronized修饰静态方法,实际上是对该类对象加锁,俗称“类锁”。

情况1:用类直接在两个线程中调用两个不同的同步方法

结果:会产生互斥。

解释:因为对静态对象加锁实际上对类(.class)加锁,类对象只有一个,可以理解为任何时候都只有一个空间,里面有N个房间,一把锁,因此房间(同步方法)之间一定是互斥的。

注:上述情况和用单例模式声明一个对象来调用非静态方法的情况是一样的,因为永远就只有这一个对象。所以访问同步方法之间一定是互斥的。

情况2:用一个类的静态对象在两个线程中调用静态方法或非静态方法

结果:会产生互斥。

解释:因为是一个对象调用,同上。

情况3:一个对象在两个线程中分别调用一个静态同步方法和一个非静态同步方法

结果:不会产生互斥。

解释:因为虽然是一个对象调用,但是两个方法的锁类型不同,调用的静态方法实际上是类对象在调用,即这两个方法产生的并不是同一个对象锁,因此不会互斥,会并发执行。

总结:

​ 1.对象锁钥匙只能有一把才能互斥,才能保证共享变量的唯一性

​ 2.在静态方法上的锁,和实例方法上的锁,默认不是同样的,如果同步需要制定两把锁一样。

​ 3.关于同一个类的方法上的锁,来自于调用该方法的对象,如果调用该方法的对象是相同的,那么锁必然相同,否则就不相同。比如 new A().x() 和 new A().x(),对象不同,锁不同,如果A的单利的,就能互斥。

​ 4.静态方法加锁,能和所有其他静态方法加锁的 进行互斥

​ 5.静态方法加锁,和xx.class 锁效果一样,直接属于类的

死锁

在解决临界资源问题的时候,我们引入了一个"锁"的概念。我们可以用锁对一个资源进行保护。实际,在多线程的环境下,有可能会出现一种情况:

假设有A和B两个线程,其中线程A持有锁标记a,线程B持有锁标记b,而此时,线程A等待锁标记b的释放,线程B等待锁标记a的释放。这种情况,叫做 死锁

显式锁 Lock

Lock是一个接口,提供了无条件的、可轮询的、定时的、可中断的锁获取操作,所有的加锁和解锁操作方法都是显示的,因而称为显示锁。 Lock的几个实现类ReentrantLock、ReentrantReadWriteLock.ReadLock和ReentrantReadWriteLock.WriteLock

ReentrantLock(可重入锁),是一个互斥的同步器 ,需要显示进行 lock 以及 unlock 操作。

private final ReentrantLock lock = new ReentrantLock();
    public void m(){
        lock.lock();//获得锁
        try{
            //方法体
        }finally{
            lock.unlock();//务必释放锁
        }
    }//注意:在使用ReentrantLock时,一定要有释放锁的操作。

ReadWriteLock(读写锁)是一个接口,提供了readLock和writeLock两种锁的操作,也就是说一个资源能够被多个读线程访问,或者被一个写线程访问,但是不能同时存在读写线程。也就是说读写锁应用的场景是一个资源被大量读取操作,而只有少量的写操作。 源码:

public interface ReadWriteLock {
    Lock readLock();
    Lock writeLock();
}

ReadWriteLock借助Lock来实现读写两个锁并存、互斥的机制

读写锁的机制:

1、读-读不互斥,读线程可以并发执行;

2、读-写互斥,有写线程时,读线程会堵塞;

3、写-写互斥,写线程都是互斥的。

//创建ReentrantReadWriteLock对象
    private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    //抽取读写锁
    private Lock readLock = rwl.readLock();
    private Lock writeLock = rwl.writeLock();
    public int getXXX(){
        readLock.lock();
        try{
            //执行操作
        }finally{
            readLock.unlock();
        }
    }
    public void setXXX(){
        writeLock.lock();
        try{
            //执行操作
        }finally{
            writeLock.unlock();
        }
    }
wait、notify/notifyAll

wait(): 等待。使得当前的线程释放锁标记,进入等待队列。可以使当前的线程进入阻塞状态。

wait和sleep的区别:

  1. 两个方法都可以使一个线程进入阻塞。
  2. 区别:wait方法会释放锁标记,sleep则不会释放锁标记。

notify(): 唤醒,唤醒等待队列中的一个线程。

notifyAll(): 唤醒,唤醒等待队列中所有的线程。

1、wait()、notify/notifyAll() 方法是Object的本地final方法,无法被重写。

2、wait()使当前线程阻塞,前提是 必须先获得锁,一般配合synchronized 关键字使用,即,一般在synchronized 同步代码块里使用 wait()、notify/notifyAll() 方法。

3、 由于 wait()、notify/notifyAll() 在synchronized 代码块执行,说明当前线程一定是获取了锁的。

当线程执行wait()方法时候,会释放当前的锁,然后让出CPU,进入等待状态。

只有当 notify/notifyAll() 被执行时候,才会唤醒一个或多个正处于等待状态的线程,然后继续往下执行,直到执行完synchronized 代码块的代码或是中途遇到wait() ,再次释放锁。

也就是说,notify/notifyAll() 的执行只是唤醒沉睡的线程,而不会立即释放锁,锁的释放要看代码块的具体执行情况。所以在编程中,尽量在使用了notify/notifyAll() 后立即退出临界区,以唤醒其他线程

4、wait() 需要被try catch包围,中断也可以使wait等待的线程唤醒。

5、notify 和wait 的顺序不能错,如果A线程先执行notify方法,B线程在执行wait方法,那么B线程是无法被唤醒的。

6、notify 和 notifyAll的区别

notify方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。notifyAll 会唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。如果当前情况下有多个线程需要被唤醒,推荐使用notifyAll 方法。比如在生产者-消费者里面的使用,每次都需要唤醒所有的消费者或是生产者,以判断程序是否可以继续往下执行。

7、在多线程中要测试某个条件的变化,使用if 还是while?

只有当前值满足需要值的时候,线程才可以往下执行,所以,必须使用while 循环阻塞。注意,wait() 当被唤醒时候,只是让while循环继续往下走.如果此处用if的话,意味着if继续往下走,会跳出if语句块。但是,notifyAll 只是负责唤醒线程,并不保证条件云云,所以需要手动来保证程序的逻辑。

线程池

ThreadPoolExecutor类是线程池最核心的类。这个类的构造方法中的几个参数:

int corePoolSize: 核心线程数量。核心池大小。

int maxmiunPoolSize: 线程池中最多的线程数量。

long keepAliveTime: 核心线程之外的临时线程,能存活的时间。(从这个线程空闲的时候开始算)

TimeUnit unit: 上面的时间单位

​ NANOSECONDS: 纳秒

​ MICROSECONDS: 微秒

​ MILLISEONDS: 毫秒

​ SECONDS: 秒

​ MINUTES: 分

​ HOURS: 时

​ DAYS: 天

BlockingQueue workQueue: 等待队列

​ ArrayBlockingQueue

​ LinkedBlockingQueue

​ SynchronousQueue

RejectedExecutionHandler handler:拒绝访问策略

// 设置拒绝访问策略
ThreadPoolExecutor.AbortPolicy: 丢弃新的任务,并抛出RejectedExecutionException
ThreadPoolExecutor.DiscardPolicy: 丢新新的任务,但是不会抛出异常
ThreadPoolExecutor.DiscardOldestPolicy: 丢弃等待队列中最前面的任务,重新尝试执行任务
ThreadPoolExecutor.CallerRunsPolicy: 由调用线程执行这个任务

new ThreadPoolExecutor ().setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());

	// 将一个任务添加到线程池
	executor.execute(new MyTask());
	// 关闭线程池
	executor.shutdown();	
	// 关闭线程池,尝试结束正在执行的线程。
	// executor.shutdownNow();

网络编程

实现同一个网络中的计算机之间的通信。

互联网中设备之间进行通信所必须要满足的条件

  1. 需要知道对方的IP
  2. 端口号
  3. 通信的双方需要遵循相同的通信协议
    1. 通信协议:HTTP / TCP / UDP
    2. 物理层、数据链路层、网络层、传输层、表示层、会话层、应用层

端口: 数据的发送与接收都需要通过端口。可以理解为一个计算机的门。同一个计算机中两个应用程序不允许占用同一个端口。

端口号:0 ~ 65535

0~1023: 公共端口

1025 ~ 49151: 注册端口

1024 ~ 65535: 动态或私有端口

常见的端口:

8080:tomcat

3306:mysql

1521:oracle

InetAddress

是一个用来描述IP的一个类,有两个常用的子类:Inet4Address、Inet6Address

IPv4: 是用四个字节来描述IP地址,IP地址中的每一位都是一个字节,范围[0, 255]

192.168.1.1

IPv6: 是用六个字节来描述IP地址

A类:1.0.0.1 ~ 126.255.255.254 保留给政府机构

B类:128.0.0.1 ~ 191.255.255.254 分配给大中型企业

C类:192.0.0.1 ~ 223.255.255.254 分配给任何有需要的个人

D类:224.0.0.1 ~ 239.255.255.254 用于组播

E类:240.0.0.1 ~ 255.255.255.254 用于实验

try {
	// 获取主机
	InetAddress local = InetAddress.getLocalHost();
	// 获取主机名字字符串
	String name = local.getHostName();
	// 获取主机地址字符串
	String address = local.getHostAddress();
	// 通过主机名获取
	InetAddress hry = InetAddress.getByName("HRY");
	// 通过一个域名获取
	InetAddress baidu = InetAddress.getByName("www.taobao.com");
	// 也可以通过IP来获取InetAddress对象(无法获取主机名)
	InetAddress ip = InetAddress.getByName("10.0.16.25");
	//给定主机的名称,根据系统上配置的名称服务返回其IP地址数组
	InetAddress[] addresses = InetAddress.getAllByName("www.taobao.com");
	for (InetAddress addr : addresses) {
		System.out.println(addr);
	}
    
} catch (UnknownHostException e) {
	e.printStackTrace();
}

URL地址编码解码

URL: 统一资源定位符,是用来描述一个文件具体的位置,以及用来访问这个文件的时候的属性。

格式:

协议头:// http:// ftp:// smb://

主机名/IP www.baidu.com 192.168.1.1

端口号 一个请求进出一个计算机的出入口

要访问的资源路径

请求参数

http://www.baidu.com:6789/users/user_info.properties?user=abc&passwd=abc

TCP

是一个在传输层的通信协议。

特点:

  1. 面向连接。
  2. 安全的。(不会存在数据丢失)
  3. 传输的数据大小是有限制的。

三次握手:

  1. 客户端向服务端发送一个请求。
  2. 服务端收到请求后,返回给客户端一个响应。
  3. 客户端接收到服务端的响应后,再回服务端一个确认信息。

重点涉及到的类:

Socket类:客户端

ServerSocket类:服务端

Socket socket = new Socket(InetAddress.getByName(ip), port);
// 获取一个输出流,向网络去写数据
OutputStream os = socket.getOutputStream();
socket.close();
// 实例化一个服务端,用来监听指定的端口传入的信息
ServerSocket server = new ServerSocket(port);
// 服务端等待客户端的连接
// 阻塞当前的线程,直到有客户端的连接进来
// 返回值:连接进来的客户端
Socket socket = server.accept();
// 开一个线程,处理一个客户端的请求
new ServerThread(socket).start();
class ServerThread{
// 接收从客户端发送过来的数据
InputStream is = socket.getInputStream();}
String hostName = socket.getInetAddress().getHostName();
String ip = socket.getInetAddress().getHostAddress();
// 获取端口:数据从客户端的哪一个端口出来的
int p = socket.getPort();
server.close();

UDP

是一个无连接的通信协议。数据发送方,将需要发送的所有的数据已经接收方的信息(IP,端口)封装到一个数据报包中,直接发送出去。

// 直接实例化即可,不需要设置接收方IP和端口号
DatagramSocket socket = new DatagramSocket();
// 封装一个数据报包
// byte[] : 需要发送的数据
// int :	数据包的大小
// InetAddress: 接收方IP
// int : 接收方端口号
DatagramPacket packet = new DatagramPacket(message.getBytes(), message.length(), InetAddress.getByName("127.0.0.1"), 6789); 
			
// 直接发送这个数据报包
socket.send(packet);
DatagramSocket socket = new DatagramSocket(6789);
// 接收数据报包
// 1. 先去实例化一个数据报包
byte[] arr = new byte[1024];
DatagramPacket packet = new DatagramPacket(arr, arr.length);
// 2. 在接收数据的时候,实际是将收到的数据都存到这个数据报包中
socket.receive(packet);
// 3. 获取发送方的信息
InetAddress address = packet.getAddress();
int port = packet.getPort();			
// 获取接收到的数据
byte[] array = packet.getData();

反射

JAVA反射机制是在运行状态中,对于任意一个实体类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

JAVA反射(放射)机制:“程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言”。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。但是JAVA有着一个非常突出的动态相关机制:Reflection,用在Java身上指的是我们可以于运行时加载、探知、使用编译期间完全未知的classes。换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其method

Class: 不是用来声明类的时候用到的关键字。(注意:首字母大写)。他是一个类。

是一个比较特殊的类。继承自Object类的。可以用Class对象来描述一个类中的成员。

Class对象的获取

  1. 通过对象.getClass()
  2. 通过类.class
  3. 通过Class类的静态方法 Class.forName(String name)
    1. 注意:参数name需要是类的全限定名
    2. 如果要访问的是一个内部类,则需要写编译后.class文件的名字

Class类常用方法

setAccessible(boolean flag);
// 从字面非常容易被理解为设置一个属性、方法、构造方法的可访问性。
// 这个表示的是是否需要进行访问权限的检查。
// 如果参数是true:则在访问对应的成员的时候,不去进行访问权限的检查,直接访问。
// 如果参数是false: 则在访问对应的成员之前,先去检查访问权限
1、关于实例化对象以及构造方法:
// Class类的非静态方法,通过一个类中可以访问到的(访问权限)无参构造方法来实例化一个对象
Object newInstance();

// 获取一个类中所有的构造方法(只获取public权限的)
Constructor[] getConstructors();
// 获取一个类中所有的构造方法(包含所有的访问权限)
Constructor[] getDeclaredConstructors();

// 获取一个类中指定的构造方法(只能获取public权限)
Constructor getConstructor(Class... parameterTypes);
// 获取一个类中指定的构造方法(任意权限的)
Constructor getDeclaredContructor(Class... parameterTypes);

//使用此 Constructor对象表示的构造函数,使用指定的初始化参数来创建和初始化构造函数的声明类的新实例。
newInstance(Object... initargs) 

备注:

  1. getDeclaredConstructor()这个方法可以获取到任意权限的构造方法,包括public和private
  2. getDeclaredConstructor() / getConstructor() 也可以获取到无参的构造方法
  3. 如果获取到的构造方法的访问权限不够,则需要用setAccessible设置可以访问
2、属性:
Field getField(String name);
// 获取一个非public权限的属性
Field getDeclaredField(String name);

Field[] getFields();
Field[] getDeclaredFields();

// 获取属性的名字
String getName();
// 获取属性的类型
Class getType();
// 设置对象的属性值
// Object obj: 这个属性是属于哪一个对象的
// Object value: 这个属性的值
fieldName.set(obj, "xiaoming");
// 获取作用在属性上的修饰符,返回值是一个int类型的。
// 可以通过Modifier类 toString(int mod);
int getModifiers();
3、方法
  1. 方法的获取,基本和属性的获取是一样的

    Method m = c.getDeclaredMethod("walk");// 		1. 获取方法
    Method m2 = c.getDeclaredMethod("add", int.class, int.class);// 1. 获取一个有参的方法
    Method[] methods = c.getDeclaredMethods();
    m.getName();				// 获取方法名
    m.getModifiers();			// 获取修饰符
    m.getParameterTypes();		// 获取参数类型
    m.getReturnType();			// 获取返回值类型
    
  2. 放法的调用:

    Object invoke(Object obj, Object... parameters);
    // 作用:是执行指定的方法
    // Object obj: 方法的调用方
    // Object... parameters: 调用方法的实参s
    // 返回值:调用方法的结果(指定的方法的返回值)
    

设计模式

工厂模式

1 工厂模式的作用,为什么要用工厂模式?

工厂模式是为了解耦:把对象的创建和使用的过程分开。就是Class A 想调用Class B,那么只是调用B的方法,而至于B的实例化,就交给工厂类。

工厂模式可以降低代码重复。如果创建B过程都很复杂,需要一定的代码量,而且很多地方都要用到,那么就会有很多的重复代码。可以把这些创建对象B的代码放到工厂里统一管理。既减少了重复代码,也方便以后对B的维护。

工厂模式可以减少错误,因为工厂管理了对象的创建逻辑,使用者不需要知道具体的创建过程,只管使用即可,减少了使用者因为创建逻辑导致的错误。

2.工厂模式的一些适用场景

对象的创建过程/实例化准备工作很复杂,需要很多初始化参数,查询数据库等

类本身有好多子类,这些类的创建过程在业务中容易发生改变,或者对类的调用容易发生改变

简单需求 简单工厂

不涉及产品族 工厂方法

涉及不同产品 不同产品族 抽象工厂

适配器模式

实例:实现typc接口转换并接入usb接口

public class Adapter implements USB{ //实现要接入的接口
	Typc typc;                      //声明需要转换的接口
	 public Adapter(Typc typc) {    //通过构造方法 为需要转换的接口赋值
		 this.typc=typc;
	}
	@Override
	public void getDevice() {        //通过重写要接入的接口中的方法
    	typc.getDevice();            //来调用要转换的接口的方法
	}                                //在要接入的类中通过接口来调取此方法
}

单例模式

**意图:**保证一个类仅有一个实例,并提供一个访问它的全局访问点。

**主要解决:**一个全局使用的类频繁地创建与销毁。

**何时使用:**当您想控制实例数目,节省系统资源的时候。

**如何解决:**判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

**关键代码:**构造函数是私有的。

饿汉:直接创建出类的实例化,不管你用还是不用 先实例化一个对象 ,占用了内存

懒汉:在需要的时候再创建类的实例化 ,用的时候在创建,时间换空间

模板方法模式

在一个抽象类中定义一个算法的骨架,而将一些可变步骤定义为抽象方法,从而在子类中通过重写这抽象方法来实现特定的步骤。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中某些步骤的具体实现。

生产者消费者模式

一、两个线程一个生产者一个消费者

需求情景

  • 两个线程,一个负责生产,一个负责消费,生产者生产一个,消费者消费一个。

涉及问题

  • 同步问题:如何保证同一资源被多个线程并发访问时的完整性。常用的同步方法是采用标记或加锁机制。
  • wait() / nofity() 方法是基类Object的两个方法,也就意味着所有Java类都会拥有这两个方法,这样,我们就可以为任何对象实现同步机制。
  • wait()方法:当缓冲区已满/空时,生产者/消费者线程停止自己的执行,放弃锁,使自己处于等待状态,让其他线程执行。
  • notify()方法:当生产者/消费者向缓冲区放入/取出一个产品时,向其他等待的线程发出可执行的通知,同时放弃锁,使自己处于等待状态。

二、多个线程,多个生产者和多个消费者的问题

需求情景

  • 四个线程,两个个负责生产,两个个负责消费,生产者生产一个,消费者消费一个。

涉及问题

  • notifyAll()方法:当生产者/消费者向缓冲区放入/取出一个产品时,向其他等待的所有线程发出可执行的通知,同时放弃锁,使自己处于等待状态。
  • while判断标记,解决了线程获取执行权后,是否要运行!也就是每次wait()后再notify()时先再次判断标记。

流程:生产者消费者交替运行,每次生产后都有对应的消费者,测试类创建实例,如果是生产者先运行,进入run()方法,进入create()方法,flag默认为false,number+1,生产者生产一个产品,flag置为true,同时调用notifyAll()方法,唤醒所有正在等待的线程,接下来如果还是生产者运行呢?这是flag为true,进入while循环,执行wait()方法,接下来如果是消费者运行的话,调用destroy()方法,这时flag为true,消费者购买了一次产品,随即将flag置为false,并唤醒所有正在等待的线程。这就是一次完整的多生产者对应多消费者的问题。

三、使用Lock和Condition来解决生产者消费者问题

​ 上面的代码有一个问题,就是我们为了避免所有的线程都处于等待的状态,使用了notifyAll方法来唤醒所有的线程,即notifyAll唤醒的是自己方和对方线程。如果我需要只是唤醒对方的线程,比如:生产者只能唤醒消费者的线程,消费者只能唤醒生产者的线程。

在jdk1.5当中为我们提供了多线程的升级解决方案:

  • 将同步synchronized替换成了Lock操作。
  • 将Object中的wait,notify,notifyAll方法替换成了Condition对象。
  • 可以只唤醒对方的线程。
    /*资源序号*/
      private int number = 0;
      /*资源标记*/
      private boolean flag = false;  
      private Lock lock = new ReentrantLock();
      //使用lock建立生产者的condition对象
      private Condition condition_pro = lock.newCondition(); 
      //使用lock建立消费者的condition对象
      private Condition condition_con = lock.newCondition(); 
  	
  		lock.lock();
    	//先判断标记是否已经生产了,如果已经生产,等待消费
          while(flag){
            //生产者等待
           condition_pro.await();
           flag = true;
           //生产者生产完毕后,唤醒消费者的线程(注意这里不是signalAll(唤醒全部))
  		condition_con.signal();
        	}finally{
              lock.unlock();

四、总结

1、如果生产者、消费者都是1个,那么flag标记可以用if判断。这里有多个,必须用while判断。

2、在while判断的同时,notify函数可能唤醒本类线程(如一个消费者唤醒另一个消费者),这会导致所有消费者忙等待,程序无法继续往下执行。使用notifyAll函数代替notify可以解决这个问题,notifyAll可以保证非本类线程被唤醒(消费者线程能唤醒生产者线程,反之也可以),解决了忙等待问题。

3、假死指的是全部线程都进入了WAITING状态,那么程序就不再执行任何业务功能了,整个项目呈现停滞状态.

假死出现的原因是因为notify的是同类,所以非单生产者/单消费者的场景,可以采取两种方法解决这个问题:

(1)synchronized用notifyAll()唤醒所有线程、ReentrantLock用signalAll()唤醒所有线程。

(2)用ReentrantLock定义两个Condition,一个表示生产者的Condition,一个表示消费者的Condition,唤醒的时候调用相应的Condition的signal()方法就可以了。

代理模式

代理模式:代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用 。这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,实现对目标功能的扩展。

静态代理:静态代理通过实现目标对象接口,然后调用相同方法来实现代理。这种方式的缺点显而易见,当目标对象接口方法变动时,直接影响到代理类,需要对代理类进行修改,十分不方便。而且如果目标对象接口方法较多时,代理类也十分臃肿,不便维护。

动态代理:动态代理模式主要借助JDK代理对象API  java.lang.reflect.Proxy来实现的,所以也称作JDK代理。我们看一下JDK这个类,其中重要的一个方法如下:

public static Object newProxyInstance(ClassLoader loader, 
                                      //目标对象类加载器 obj.getClass().getClassLoader(),
                                      class<?>[]interfaces,
                                      //目标对象接口类型 obj.getClass().getInterfaces(),
                                      InvocationHandler h)
		/*    事物处理,在这里面可以实现自己想要的逻辑
     new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //执行目标对象方法
                System.out.println("DynamicProxy--------->");
                return method.invoke(obj,args);
            }
        }*/

    throws IllegalArgumentException  {}

装饰器模式

装饰器模式又称为包装(Wrapper)模式。装饰器模式以多客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。 装饰器模式可以在不用创建更多子类的情况下,将对象的功能加以扩展。

装饰器模式中的角色有

1、抽象构件角色

给出一个抽象接口,以规范准备接受附加责任的对象

2、具体构件角色

定义一个将要接受附加责任的类

3、装饰角色

持有一个构建对象的实例,并定义一个与抽象构件接口一致的接口

4、具体装饰角色

负责给构建对象贴上附加的责任

Java 8新特性

1、Lambda 表达式

​ Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。

​ lambda 表达式的语法格式如下:

(parameters) -> expression
或
(parameters) ->{ statements; }

​ lambda表达式的重要特征:

​ *可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。

​ ***可选的参数圆括号:**一个参数无需定义圆括号,但多个参数需要定义圆括号。

​ ***可选的大括号:**如果主体包含了一个语句,就不需要使用大括号。

​ ****可选的返回关键字:****如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。

2、方法引用

​ 方法引用通过方法的名字来指向一个方法 ,方法引用使用一对冒号 :: 。

​ 双冒号运算操作符是类方法的句柄,lambda 表达式的一种简写,这种简写的学名叫 eta-conversion 或者叫 η-conversion。

​ 双冒号运算就是 java 中的[方法引用]。 [方法引用]格式为:类名::方法名

​ 这种 [方法引用] 或者说 [双冒号运算] 对应的参数类型是 Function<T, R> T 表示传入类型,R 表示返回类型。比如表达式 person -> person.getAge(); 传入参数是 person,返回值是 person.getAge() ,那么方法引用Person::getAge 就对应着 Function<Person,Integer> 类型。

3、函数式接口

​ 函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。

​ 函数式接口可以被隐式转换为 lambda 表达式。

​ Lambda 表达式和方法引用(实际上也可认为是Lambda表达式)上。

​ 函数式接口可以对现有的函数友好地支持 lambda。

4、默认方法

​ 简单说,默认方法就是接口可以有实现方法,而且不需要实现类去实现其方法。

​ 我们只需在方法名前面加个 default 关键字即可实现默认方法。

​ 默认方法语法格式如下:

public interface Vehicle {
   default void print(){
      System.out.println("我是一辆车!");
   }
}

5、Stream

Stream(流)是一个来自数据源的元素队列并支持聚合操作

元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。

数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。

聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。

和以前的Collection操作不同, Stream操作还有两个基础的特征:

Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。

内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。

生成流
在 Java 8 中, 集合接口有两个方法来生成流:

​ stream() − 为集合创建串行流。

​ parallelStream() − 为集合创建并行流。

**forEach **Stream 提供了新的方法 ‘forEach’ 来迭代流中的每个数据。

**map ** 方法用于映射每个元素到对应的结果

filter 方法用于通过设置的条件过滤出元素

limit 方法用于获取指定数量的流

sorted 方法用于对流进行排序。

Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。Collectors 可用于返回列表或字符串

Java8中的forEach方法详解

forEach方法是JAVA8中在集合父接口java.lang.Iterable中新增的一个default实现方法

default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

新的API结合lambda表达式使用一行代码即可

补充

1、JAVA 中final 允许先声明 后赋值,只能赋一次

2、Java 中的instanceof 运算符是用来在运行时指出对象是否是特定类的一个实例。instanceof通过返回一个布尔值来指出,这个对象是否是这个特定类或者是它的子类的一个实例。

3、doublefloat 的区别是double精度高,有效数字16位,float精度7位。但double消耗内存是float的两倍,double的运算速度比float慢得多

4、ASCII码先是大写字母后是小写字母

5、**int indexOf(String c)int lastIndexOf(String c)**查询字符串出现的下标只是算第一个字符的下标

6、变量属性是描述变量的作用域,按作用域分类,变量有局部变量、类变量、方法参数和 异常处理参数

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值