- 1.Java语言跨平台原理
- 2.JRE和JDK
- 3.常用DOS命令
- 4. Java语言中的注释
- 5.关键字
- 6.常量和变量
- 7.数据类型
- 8.标识符
- 9.类型转换
- 10.运算符
- 11.数据输入与输出
- 12.顺序结构
- 13.选择结构
- 14.循环结构
- 15.跳转控制语句(中断流程控制语句)
- 16.产生随机数
- 17.数组
- 18.方法
- 19.类和对象
- 20.成员变量和局部变量
- 21.封装
- 22.构造方法
- 23.String
- 24.StringBuilder(字符串构建器)
- 25.类初始化以及实例初始化
- 26.继承
- 27.修饰符
- 28.final关键字
- 29.static关键字
- 30.多态
- 31.抽象类
- 32.接口
- 33.形参和返回值
- 34.内部类
- 35.Object类
- 36.基本类型的封装类
- 37.日期相关类
- 38.异常
- 39.可变参数
- 40.集合框架
- 41.List集合
- 42.ListIterator:列表迭代器
- 43.ArrayList,Vector
- 44.LinkedList
- 45.set集合(内部本质还是Map)
- 46.HashSet
- 47.LinkedHashSet
- 48.TreeSet
- 49.Map
- 50.Collections集合(工具类)
- 51.泛型
- 52.IO流
1.Java语言跨平台原理
- 平台指的是不同类型的操作系统。Java语言可以跨平台是因为Java语言使用Java虚拟机屏蔽了具体的操作系统信息,不同的操作系统对应着不同的Java虚拟机。不同的操作系统只要安装对应该平台的Java虚拟机,就可以运行由Java编译程序生成的字节码,这就是所谓的一次编译,到处运行。Java语言是跨平台的,但是Java虚拟机不跨平台。
2.JRE和JDK
- JDK = JRE+开发工具
- JRE = JVM+核心类库
JVM:Java虚拟机,可以实现Java语言的跨平台性,主要负责jvm字节码的解释运行
JRE:Java运行环境,包含JVM和核心类库
JDK:Java开发工具包,包含JRE和开发工具
3.常用DOS命令
- cls:清屏
- 盘符名称:表示盘符切换。比如说切换到E盘:
E:
- cd 目录:进入单极目录
- cd ..:回退到上一级目录
4. Java语言中的注释
- 单行注释://
- 多行注释: /**/
- 文档注释:格式为:/** 注释信息*/
/**
* This is the first simple program
* @version 1.0
* @author NrvCer
*/
5.关键字
- 关键字:Java语言中已经赋予特殊含义的英文单词。比如说break、public等。
6.常量和变量
- 空常量:null,null是任何引用类型的默认值,其值赋值给引用变量,表示该引用变量不引用任何对象,不能直接输出空常量。
System.out.println(null); // error
- 变量:在程序运行过程中,其值可以改变的量。
7.数据类型
数据类型包括基本数据类型和引用数据类型
1.基本数据类型
注意:Java中没有任何无符号形式的int,short,long,byte类型。
基本数据类型如下:
- 整数:byte(1个字节,范围-128~127),int,short,long(整数默认是int类型,123564L表示long类型的整数)
- 浮点数:double(8个字节),float(4个字节)(浮点数默认是double类型,13.14F表示float类型的浮点数)
- double 类型除于0,不会出现编译错误,结果是无穷大的(Infinty);整数除零将产生一个异常。
- 基本的整数和浮点数精度不能满足需求时,就可以使用Java.math包下的两个类BigInteger和BigDecimal类。
- 字符:char
- 非数值型(布尔型):boolean
注意:字符类型的变量需要的字节数是2(对于大多数常用的Unicode字符来说是这样的,有的辅助字符需要四个字节),使用的字符编码是unicode。char类型的值可以表示为十六进制值,范围从\u0000到\uffff。
// 基本数据类型的封装类型的SIZE常量,该常量用来以二进制补码形式表示基本数据类型值的比特位数。
int charSize = Character.SIZE; // 16
System.out.println(" char size: " + (charSize/8) + "Byte" );
System.out.println(Character.BYTES); // 2
2.引用数据类型
引用数据类型如下:
- 类:class
- 接口:interface
- 数组:[]
8.标识符
标识符的命名规则中,可以使用$(美元符)开头。
9.类型转换
- 自动类型转换:把一个表示数据范围小的数值或者变量转换为另一个表示数据范围大的变量,就会进行自动类型转换。例如:
double d = 10 // 将整形转化为double型
表示数据范围从小到大转换关系图如下:
byte a = 10;
// byte转换为char类型不兼容,不可以进行转换
char b = a; // error
// int转换为float可能造成精度的损失
int n = 123456789;
float f = n;
- 强制类型转换:把一个表示数据范围大的数值或者变量转换为另一个表示数据范围小的变量。例如:
int k = (int)88.88 // k为88
note:不建议进行数据类型的强制转换,这样会造成数据的丢失 - 基本类型与String之间的转换
1. 基本类型转化为String:直接拼接空字符串即可
int a = 1024;
String str = a + "";
System.out.println(str); // 1024
2. String转化为基本类型:目标类型.parseXXX(待转换的内容);
String str = "1024";
int a = Integer.parseInt(str);
System.out.println(a); // 1024
- 在Java中,整数值和布尔值之间不能进行相互转换。
10.运算符
- 算术运算符:比如说加减乘除、取余数、取模
- 字符的+操作与字符串的+操作
// 字符的+操作,字符与整数的相加操作,字符自动提升为整数
int c = 10;
char d = 'a';
System.out.println(c + d); // 107
// 字符串的+操作(字符串的拼接操作)
System.out.println("Hello" + "world"); // Helloworld
System.out.println("Hello" + 6 + 66); // Hello666
// 注意拼接运算,看到底是先运算还是先拼接
System.out.println('a' + 12 + "Hello");// 109Hello
- 赋值运算符:复合赋值运算符中,底层做了优化,进行了内部强转,效率更高。
int x = 10;
x += 10; // 等价于x = (int)(x + 10);
// 使用赋值运算符的话
int a = 10;
a = a + 10.1d; // error
a = (int)(a + 10.1d); // no problem
- 自增自减运算符:编译器做了内部的优化,会进行自动的类型转换,而且效率也提高
byte b = 1;
b++; //不会报编译错误!
b = b + 1; //类型不匹配:不能从 int 转换为 byte
b = (byte)(b + 1); //no problem
- 关系运算符:关系运算符的运算结果是boolean类型,要么是true,要么是false
- 逻辑运算符:逻辑运算符与按位运算符效果一样,但是具有短路效果。
- 逻辑与&&:也叫做短路与
- 逻辑或||:也叫做短路或
- 按位操作符:按位操作符用来操作整数中的二进制位。
- 按位与操作符:&,两个输入位都是1,则该操作符生成1.否则为0
- 按位或操作符:|,只要两个输入位有一个是1,那么该操作符生成1
- 按位与或操作符:^,如果输入位有一个是1但不全是1,那么该操作符生成1(相同为0,不同为1)
- 按位取反操作符:~,生成与输入位相反的值。
- 已知一个数的原码求其补码
1. 如果该数是正整数,则补码和反码,原码相同。 2. 如果该数是负数,就是原码的各位取反(符号位除外)(这一步得到的也就是反码),然后末位再加一。 package charpter3; class HelloWorld { public static void main(String [] args) { int a = 100; int b = 200; System.out.println(a & b); //64 System.out.println(a | b); //236 System.out.println(a ^ b); //172 System.out.println(~(b)); //-201 System.out.println(~a); //-101,对100的补码各位取反后,得到的是符号位为1,求其原码。 } }
- 移位操作符:移位操作符只能用来操作整数的二进制位。
<<
:左移位操作符按照操作符右侧指定的位数对操作数左移动,低位补0.>>
:右移位操作符按照操作符右侧指定的位数对操作数右移动,若操作数符号为正,则在高位补0,否者高位补1.>>>
:无符号右移位操作符:JAVA新增的,无论操作数的正负都在高位补0.- 示例1:
package charpter3; class HelloWorld { public static void main(String [] args) { int a = 100; int b = -100; System.out.println(a << 2); //400 System.out.println(a >> 2); //25 System.out.println(a >>> 2); //25 System.out.println(b >>> 2); //1073741799 } }
- 示例2:如何将一个给定的int类型的整数转化为十六进制数?你可能会使用Java提供好的类解决,但是现在呢要求不使用Java提供好的类来解决这个问题。解决方法::每次将一个整数&15就取得一个十六进制整数,然后将该整数右移>>>四位。
package charpter3; import java.util.Scanner; public class HelloWorld { public static void main(String [] args) { Scanner scan = new Scanner(System.in); //-60 int a = scan.nextInt(); System.out.println(Integer.toHexString(a)); //ffffffc4 } }
- 三元运算符(条件运算符)
11.数据输入与输出
1.数据的输入(从标准输入流)
- Scanner类的使用基本步骤
1. 导包:import java.util.Scanner;
2. 创建对象:Scanner sc = new Scanner(System.in);
3. 接收数据:int i = sc.nextInt();
- Scanner类的常用方法
1.nextLine方法:读取一行
2.next方法:读取一个单词(以空白符分割)
2.数据的输出(输出到标准输出流)
- 使用对象System.out对象的print或者println方法
- 使用对象System.out对象的printf方法:Java中沿用了C语言中的库函数中的printf函数,所以用于printf方法的转换符与C语言类似。
3.文件的输入(读文件)
Scanner sc = new Scanner(Paths.get("C:\\Users\\lenovo\\Desktop\\example.txt"), "UTF-8");
String lineStr = sc.nextLine(); // 读取指定文件中的一行
4.输出到文件(写文件)
PrintWriter pw = new PrintWriter("C:\\Users\\lenovo\\Desktop\\example.txt","UTF-8");
pw.print("my name is NrvCer");
pw.append("hi");
pw.close();
String dir = System.getProperty("user.dir"); // 获取用户的当前工作目录
System.out.println(dir); //E:\practice\Java practice\javase_practice
12.顺序结构
顺序结构:代码从上往下,依次执行
13.选择结构
- if...else if...else语句
- switch语句
14.循环结构
- for循环
- while循环
- do...while循环
- for each循环(增强for循环)
15.跳转控制语句(中断流程控制语句)
- continue:continue语句分为带标签的continue和不带标签的continue
- break:break也分为带标签的break和不带标签的break
//break语句分为带标签的break和不带标签的break
//标签的格式:标签名:
label:
{
if(condition) {
break label;
}
}
16.产生随机数
- Random类的使用基本步骤
1. 导包
import java.util.Random;
2. 创建对象
Random r = new Random();
3. 获取随机数
int number = r.nextInt(10); // 获取数据的范围[0,10)
17.数组
Java中允许在运行期间确定数组的大小。
Scanner sc = new Scanner(System.in);
int length = sc.nextInt();
int[] arr = new int[length]; // no problem
1.一维数组
- 数组的定义格式
- 格式一:数据类型[] 变量名
- 格式二:数据类型 变量名[]
- 数组初始化方式
- 动态初始化:初始化时只指定数组长度,由系统为数组分配初始值。格式为:数据类型 [] 变量名 = new 数据类型[数组长度],例如int [] arr = new int[3];
- 静态初始化:初始化时指定每个数组元素的初始值,由系统决定数组长度。格式为 数据类型 []变量名 = new 数据类型 []{数据1,数据2,数据3,....};其简化格式为 数据类型 [] 变量名 = {数据1,数据2,...};
- 数组元素访问:通过索引下标
- 数组的遍历:数组遍历的过程中需要获取数组元素的数量,使用
数组名.length
- 使用for循环/while循环/do...while循环
- for each循环(增强型for循环,其内部是一个Iterator迭代器)
for each循环的格式为:for(variable:collection)statement // 例如 int[] arr = {100,200,300}; for(int element:arr) { System.out.print(element + ","); }
- 使用Arrays.toString方法
String[] str = {"Hello","World","never"}; // [Hello, World, never] System.out.println(Arrays.toString(str));
- 数组元素的默认值:当使用动态初始化创建数组时,数组中的元素具有默认值。
不同数据类型的默认初始值如下:
1. byte,short,int:0
2. long:0L
3. float:0.0F
4. double:0.0
5. char:0
6. boolean:false
7. 引用数据类型:null
- 数组的扩容,备份(将数组中的数据元素保存到另一个数组中),截取,反转
注意:在Java中允许数组长度为0.int[] arr = new int[0];// no problem
2.二维数组
note:二维数组中的每一个一维数组中的个数可能不同。将二维数组看成一个二维表,即每一行的列数可能不同。
- 二维数组的静态初始化
int[][] arr1 = {{1,3,5},{2,4},{3}};
int[][] arr2 = new int[][] {{1,3,5},{2,4},{3}};
- 二维数组的动态初始化
// 二维数组的动态初始化
// 1.规则二维表:每一行的列数相同
int[][] arr3 = new int[3][2];
System.out.println(arr3[2][1]); // 0
// 2.不规则二维表:每一行的列数不一样
int[][] arr4 = new int[3][];
// 为二维表中的每一行分配列数
arr4[0] = new int[1];
arr4[1] = new int[2];
arr4[2] = new int[3];
//0
//00
//000
- 二维数组的行数:数组名.length
- 二维数组的遍历:与一维数组一样
//示例:1.使用for each循环遍历二维数组
int[][] arr = {
{100,200,300},
{400,500,600},
{}
};
//100,200,300,
//400,500,600,
for(int[] rowArr:arr) {
for(int element:rowArr) {
System.out.print(element + ",");
}
System.out.println();
}
2. 使用Arrays的deepToString方法
note:Java中的一个二维数组int[][] arr = new int[2][3];等同于C++中的如下语句:
//arr即为数组变量,其值为一个指针数组的首地址
// 指针数组的每一个元素的值又是一个一维数组的首地址
int** arr = new int*[2];
18.方法
1.方法的定义和调用
- 方法的定义:
访问权限修饰符 方法返回值 方法名(形式参数){}
- 方法的调用:
实例变量.实例方法
实例变量.类方法
类名.类方法
2.实参与形参
- 实参:传递给被调用方法的参数
- 形参:方法定义时的参数
3.方法重载
note:不能通过不同的返回值判定两个方法是否构成重载;多个相同方法名和返回值类型的方法的参数不同可以判定两个方法是否构成重载。其中参数不同又分为参数的数据类型不同和形参的个数不同。
4.方法的参数传递
- 方法参数中传递基本类型
- 方法参数中传递引用类型:实参和形参的值为同一个实例对象的地址,改变形参的值对实参没有影响;改变形参指向的实例对象,则实参指向的实例对象也会改变。
5.成员方法的分类
- 实例方法:属于对象的方法,由对象来调用。每一个实例方法中,含有一个隐式参数指向该对象,用关键字this表示隐式参数。
- 静态方法:也叫类方法,属于整个类的,不是属于某个实例,由类名来调用。也可以通过对象来调用
6.方法重写
在方法重写中,子类方法的权限必须大于或者等于父类方法的权限修饰符;子类方法的返回值类型必须小于等于父类方法的返回值类型。(小于其实就是子类类型)
19.类和对象
1.类的定义
访问权限修饰符 class 类名{
...
}
2.对象的使用
- 创建实例对象:格式为 类名 引用变量名 = new 类名([实参]);
- 使用对象
1. 使用成员变量:引用变量名.成员变量
2. 使用成员方法:引用变量名.方法名([实参])
- 匿名对象:new 类名();
20.成员变量和局部变量
- 成员变量的分类
- 实例变量:也叫对象属性,属于某个对象的,通过对象来使用
- 类变量:也叫类变量,属于整个类的,不是属于某个实例
- 局部变量:一般定义在方法中,比如说方法的参数,方法内定义的变量
note:成员变量位于堆内存上,具有默认值;局部变量位于栈内存上,没有默认的初始值,必须先定义,赋值,才能使用。
21.封装
1.private关键字
被private修饰的成员(包括成员变量和成员方法)只能在本类中被访问。
2.this关键字
this代表所在类的对象的引用。
22.构造方法
- 构造方法的定义形式
修饰符 类名([形参]) {
}
- 构造方法的注意事项
- 如果没有定义构造方法,系统将会给出一个默认的无参构造方法
- 如果定义了构造方法,系统不再提供一个默认的无参构造方法,需要我们手动定义一个无参构造函数。
23.String
- String对象的特点
// 以" "方式给出的字符串,只要字符序列相同(顺序和大小写),不管在程序代码中出现几次,JVM都只会建立一个String对象,并在字符串池中维护。
String str1 = "Hello";
String str2 = "Hello";
String str3 = "Hello";
System.out.println(str1 == str2); // true
System.out.println(str3 == str2); // true
- 字符串的比较
1. 使用等号进行比较:对于基本类型来说比较的是数据值是否相同;对于引用类型来说,比较的是地址值。
(str1 == str2) // 比较的是两者的地址值是否相同
2. String的equals方法比较内容是否相同(区分大小写)
String str1 = new String("Hello");
String str2 = "Hello";
System.out.println(str1 == str2); // false
System.out.println(str1.equals(str2));// true
3. String的equalsIgnoreCase方法不区分大小写
System.out.println("hello".equalsIgnoreCase("HELLO")); // true
4. compareTo方法:按照字典顺序比较两个字符串
5. compareToIgnoreCase:不区分大小写按照字典顺序比较两个字符串
- 字符串的遍历
1. 使用for循环
for(int i = 0; i < str.length(); i++)
{
str.charAt(i); //charAt方法获取指定索引处的字符值
}
2. 使用增强for循环
String str = "Hello@";
for (char c : str.toCharArray()) {
System.out.print(c);
}
- 字符串的拼接:任何数据类型与空串进行拼接结果都是字符串
拼接结果的存储:
1. 常量+常量:结果是常量池
2,常量与变量 或 变量与变量:结果是堆
3. 拼接后调用intern方法:结果在常量池
String s1 = "Hello"; // s1位于常量池
String s2 = s1 + ""; // s2位于堆
System.out.println(s1 == s2); // false
字符串的拼接
// 1.使用拼接符
String str0 = "Hello";
String str1 = "Hello" + "World";
// 2.使用concat函数
String str2 = "Hello".concat("World");
String str3 = "Hello".concat("");
// 对于concat函数,如果形参表示的字符串长度不为0,底层有一个new动作
System.out.println(str1 == str2); // false
System.out.println(str0 == str3); // true
- 字符串的反转
- 统计字符的次数
- int和String的相互转换
int转为String
// 方式1:使用拼接符
int number = 100;
String str = "" + number;
System.out.println(str); // 100
// 方式2:使用String中的valueOf方法
String s = String.valueOf(number);
System.out.println(str); // 100
String转为Int
// 方式1:String先转为Integer,再转为int
String str = "100";
Integer i = Integer.valueOf(str);
int number = i.intValue();
System.out.println(number); // 100
// 方式2:使用parseInt方法
int num = Integer.parseInt(str);
System.out.println(num); // 100
- 子串:可以从一个较大的字符串中提取出一个子串
// 使用subString方法
String str = "Hello";
String subStr = str.substring(0, 3); // Hel
String fileName = "example.txt";
//截取文件名
System.out.println("文件名:" + fileName.substring(0,fileName.lastIndexOf("."))); // example
- 空串与null串的区别
空串:长度为0的字符串,即""
null串:String str = null;
其中空串是一个Java对象,而null串中没有Java对象与变量关联。
判断某个字符串为空串:"".equals(str),防止空指针异常
10.字符串与字节数组的互相转化(涉及到encode,decode)
1. 字符串转字节数组:使用getBytes方法
byte[] getBytes(String charsetName):使用指定的字符集
byte[] getBytes(Charset charset):使用指定的字符集
byte[] getBytes():使用平台默认的字符集
2. 字节数组转为字符串:使用String的构造方法
// 示例:
String str = "中国";
// encode
byte[] bys = str.getBytes(StandardCharsets.UTF_8);
// decode
System.out.println(new String(bys,"UTF-8"));// 中国
24.StringBuilder(字符串构建器)
1.String与StringBuilder类的区别
- StringBuilder是一个可变的字符串类,StringBuilder对象中的内容是可变的。而String中的内容是不可变的。
// 示例:字符串的拼接操作
// 下面的代码进行字符串拼接操作,每次拼接会在常量池中构建一个新的String对象
// 这样既耗时又浪费内存空间,StringBuilder类可以解决这个问题
// 字符串"Hello","World","HelloWorld"
String str = "Hello";
// 69609650
System.out.println(str.hashCode());
str += "world";
// 468881952
System.out.println(str.hashCode());
- StringBuilder类的构造方法
- StringBuilder类的添加append和反转reverse方法
- StringBuilder与String的相互转换
1. StringBuilder转为String:使用toString方法
2. String转为StringBuilder:通过StringBuilder的一个构造方法
- 字符串的拼接:由于使用String类完成拼接操作既耗时又浪费内存空间,所以使用StringBuilder来完成
// 字符串的拼接,将数组{1,2,3}拼接成字符串[1,2,3]
public static String concat(int [] arr) {
StringBuilder sb = new StringBuilder();
sb.append("[");
for(int i = 0; i < arr.length; i++) {
if(i != arr.length - 1) {
// append方法返回对象所以支持链式编程
sb.append(arr[i]).append(",");
} else {
sb.append(arr[i]);
}
}
sb.append("]");
return sb.toString();
}
- 字符串的反转:与字符串的拼接操作同理
// 字符串的反转
public static String reverse(String str) {
StringBuilder sb = new StringBuilder(str);
sb.reverse();
return sb.toString();
// return new StringBuilder(str).reverse().toString();
}
25.类初始化以及实例初始化
1.类初始化
- 静态代码块:优先于构造函数执行。
- 位置:在类中的方法外,一个类中可以有多个
- 作用:完成类的初始化
- 语法格式如下:
修饰符 class 类名 { static { 静态代码块语句 } }
2.实例初始化
- 非静态代码块:非静态代码块先于构造器执行
- 位置:在类中方法外,一个类中可以有多个
- 作用:完成实例初始化
- 应用场景:如果每个构造器中有相同的初始化代码,且这些初始化代码无须接收参数,就可以把它们放在非静态代码块中定义。通过把多个构造器中相同代码提取到非静态代码块中定义,提高复用性。
- 语法格式如下:
修饰符 class 类名 { { 非静态代码块语句 } }
26.继承
1.继承概述
- 继承的格式:[修饰符]class 子类名 extends 父类名{
} - 继承中构造方法的访问特点:子类中所有的构造方法都会默认访问父类中无参的构造方法。即每个子类中的构造方法的第一条语句默认是super();
- 继承中成员方法的访问特点:见27权限修饰符符的访问情况
- Java中继承的注意事项
- Java中类只支持单继承,不支持多继承
- Java中类支持多层继承
- 在Java中所有的继承都是公有继承,而没有C++中的私有继承以及保护继承。
2.super关键字
- super与this的区别
- super:代表父类存储空间的标识(可以理解为父类对象的引用)this:this为本类对象的引用
- 访问构造方法(一个构造方法调用另一个构造方法):super([参数])表示访问父类构造方法,this([参数])表示访问本类构造方法。
- 访问成员方法:super.成员方法(...)表示访问从父类继承的在子类中仍然可见的成员方法,this.成员方法(...)表示访问本类的成员方法
- 访问成员变量:super.父类成员变量:表示访问父类中的成员变量,this.当前类的成员变量表示访问当前类中的成员变量。
3.方法重写
- 方法重写注意事项
- 私有方法不能被重写(因为父类中的私有成员子类不能继承)
- 方法重写时,子类方法的方法权限不能更低(pulic > 默认 > private)
- final修饰的方法不能被重写
27.修饰符
1.包
- 包的概述:包其实就是文件夹,作用是对类进行分类管理
- 包的定义格式
// 下面这条语句必须在源文件的首行,并且每个源文件只能有一个包定义语句。
package 包名;(多级包用.分开)
例如:package com.pak;
2.导包
- 导包的概述:使用不同包下的类时,使用的时候需要写类的全路径;为了简化带包的操作,Java提供导包的功能。
- 导包的格式
// 导入某个包下的所有类
import 包名.*;
// 导入某个包下特定的类
import 包名.类名; //例如import java.util.Scanner;
// 导入某个类变量或者静态方法
import static 包.类名.静态成员
3.修饰符
- 修饰符的分类:四种访问级别
- private
- 默认(缺省)
- protected
- public
note:某个类型或者成员的权限修饰符为缺省的话,那么其仅限于那个类型或者成员所在的包使用,而无法从外界软件包访问。因此如果是为了能够使用跨包的自定义类,建议将自定义类类型的权限修饰符设置为大于缺省的。
package test;
class Example {
public void show() {
System.out.println("HelloWorld");
}
}
//在另一个包javase_practice下将无法使用上述这个类,即使上述类已经导入。
- 权限修饰符符的访问情况:public > protectded > 缺省 > private
28.final关键字
1.声明常量
- final修饰局部变量(指示常量,常量名一般大写)
- final修饰基本类型变量:则基本类型变量的数据值不能变
- final修饰引用类型变量:则引用变量指向的实例对象的地址值不能变,但是地址值中的内容可以改变的
- final修饰静态的类变量和非静态的实例变量
2.final修饰方法
final修饰方法表示这个方法不能被重写
3.final修饰类
final修饰类表示这个类不能被继承,没有子类。如果一个类声明为final,其中的方法自动称为final,但是不包括域(即成员变量)。
29.static关键字
- static修饰成员变量和成员方法的特点:被类的所有对象共享
- static的作用:
- 修饰成员变量,使用static修饰的成员变量就叫做类变量(也叫类域)
- 修饰成员方法,使用static修饰的方法叫做静态方法,可以通过类名或者引用变量调用。
- 静态成员变量的使用:类名.静态成员变量名或者通过对象名调用(即对象名.静态成员变量名)。静态成员方法的使用同理。
- static访问的特点:静态成员方法只能访问静态成员(静态的成员变量和成员方法)
结合final和static关键字修饰的成员变量就叫做类常量。
30.多态
1. 多态的前提
- 需要有继承关系
- 方法重写(静态方法和成员变量是没有多态性的,运行结果直接看编译时的类型)
- 父类引用指向子类对象或者父接口指向实现类对象
2.多态中成员访问特点
- 成员变量:编译看左边,运行看左边
- 成员方法:编译看左边,运行看右边
3.多态的弊端
不能使用子类独有的功能,需要通过向下转型实现这个弊端。
4.多态中的转型
- 向上转型:父类引用指向子类对象
- 向下转型:父类引用转为子类对象,向下转型可以解决多态中的上一个弊端
Animal dog = new Dog();// 向上转型(子类类型转为父类类型)
dog.eat(); // Dog eating
//dog.watchDoor(); // error,多态中不能调用子类中独有的方法
// 向下转型可以解决多态中的这一个弊端
Dog dg = (Dog)dog; // 向下转型
dg.watchDoor(); // watch door
5.instanceof运算符
- 语法格式:变量名 instanceof 数据类型,如果变量属于该数据类型则返回true,否则返回false。
- 应用示例:在进行强制类型转换之前,使用instanceof运算符检验一下是否为指定的数据类型。
//先来看一个类型转换异常
Animal cat = new Cat();
cat.eat();
Dog c = (Dog)cat; // ClassCastException
// 使用instanceof运算符改进如下
if(cat instanceof Cat) {
Cat c = (Cat)cat;
c.catchMouse();
} else if(cat instanceof Dog) {
Dog d = (Dog)cat;
d.watchDoor();
}
31.抽象类
1.抽象类的概述
在Java中,一个没有方法体的方法应该定义为抽象方法。而含有抽象方法的类必须定义为抽象类。含有抽象方法的类必须是抽象类。一个抽象类Animal如下:
修饰符 abstract class Animal {
public abstract void eat();
}
2.抽象类的特点
- 抽象类和抽象方法必须使用abstract关键字修饰
- 抽象类中可以含有非抽象方法,也可以不含有抽象方法
- 抽象类不能直接实例化,但是可以参照多态的形式使用非抽象具体子类对象初始化,这叫抽象类多态。
- 对于抽象类中的子类,要么重写抽象类中的所有抽象方法,要么声明为抽象类。
3.抽象类的成员特点
- 成员变量:可以是变量,可以是常量
- 构造方法:可以有构造方法,但是不能实例化。构造方法的作用是用于子类创建对象时,为了父类数据的初始化
- 成员方法:可以有非抽象方法
32.接口
1.接口的特点
- 接口用关键字interface修饰
权限修饰符 interface 接口名 {
}
- 类实现接口用implements关键字,而不是extends关键字
class 类名 implements 接口名 {
}
- 接口不能实例化;可以参照多态的方式,通过实现类对象实例化,这叫接口多态。
- 接口的实现类:要么重写接口类中的所有抽象方法;要么继续声明为一个接口。
权限修饰符 interface 接口名 extends 接口名 {
}
2.接口的成员特点
- 成员变量:接口中的成员变量只能是类常量;该成员变量具有默认修饰符public static final
- 成员方法:可以有抽象方法,静态成员方法,默认方法,私有方法;一个方法具有默认修饰符public abstract
- 构造方法:接口中没有构造方法,因为接口主要是对行为进行抽象的
3.类和接口的关系
实现关系:可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口。
4.接口和接口的关系
继承关系:可以单继承,也可以多继承。
5.抽象类与接口的区别
- 成员区别:对于抽象类来说:含有变量,常量,抽象方法,非抽象方法,有构造方法;对于接口来说:含有常量和抽象方法,没有构造方法。
- 关系区别
- 类与类:继承关系,单一继承
- 类与接口:实现关系,可以单实现,也可以多实现
- 接口与接口:继承关系,可以单一继承,也可以多继承。
抽象类和接口都不能实例化
3. 设计理念区别:对于抽象类来说:对类抽象,包括属性和行为;对于接口来说:对行为抽象,主要是行为。
6.思考
- 既然有了抽象类,为啥Java中引入接口呢?
- Java中不支持多继承:如果一个具体类继承了一个抽象类,那么这个具体类就不能继承自其他类,这样对该具体类产生了限制。
- Java中引入接口可以提供CPP中多重继承的大多数好处(一个接口实现类可以实现多个接口),同时还能避免多重继承的复杂性和低效性。
7.接口中的默认方法(JDK8中新增)
- 接口中默认方法的定义格式:
public default 返回值类型 方法名 (参数列表){}
- 接口中默认方法的注意事项:
- 默认方法不是抽象方法,所以不强制被重写,但是可以被重写,重写的时候去掉default关键字。
- 默认方法的权限修饰符默认为public,可以省略。
接口中允许存在默认方法,这一特性,使得我们在实际应用中的时候不需要重写接口中全部的抽象方法,可以根据需求重写;并且是向后兼容的。
8.接口中的静态方法(JDK8中新增)
- 接口中静态方法的定义格式
public static 返回值类型 方法名(参数列表){}
- 接口中静态方法的注意事项:
- 静态方法只能通过接口名调用,不能通过接口实现类名或者对象名调用
- 静态方法的默认权限修饰符为public,可以省略。
9.接口中私有方法(JDK9中新增)
- 接口中私有方法的定义格式:
// 静态或者非静态
private [static] 返回值类型 方法名(参数列表){}
- 接口中私有方法的注意事项
- 默认方法可以调用私有静态方法和私有非静态方法
- 静态方法只能调用私有静态方法
33.形参和返回值
- 类名作为形参和返回值:方法的形参是类,其实需要的是该类的对象或者子类对象;方法的返回值是类,其实返回的是该类的对象或者子类对象。
- 抽象类作为形参和返回值:方法的形参是抽象类,其实需要的是该抽象类的子类对象;方法的返回值是抽象类名,其实返回的是该抽象类的子类对象。
- 接口名作为形参和返回值:方法的形参是接口名,其实需要的是该接口的实现类对象;方法的返回值是接口名,其实返回的是该接口的实现类对象。
34.内部类
1.内部类概述
- 内部类:在一个类中定义一个类。
- 内部类的访问特点:内部类可以直接访问外部类的成员,包括私有;外部类要访问内部类的成员,必须创建内部类类型的对象。
- 内部类可以对同一个包中的其他类隐藏起来
public class Outer {
private int number = 100;
public class Inner {
public void show() {
System.out.println(number);
}
}
public void method() {
//外部类要访问内部类的成员需要创建内部类对象
Inner in = new Inner();
in.show();
}
}
- 内部类的定义格式
[修饰符] class 类名 {
[修饰符] class 类名 {
}
}
- 内部类的分类:根据内部类声明的位置可以分为成员内部类,局部内部类。
2.成员内部类
- 成员内部类的位置:在类的成员位置
- 外界创建成员内部类对象:外部类名.内部类名 引用变量名 = 外部类对象.内部类对象,例如
Outer.Inner oi = new Outer().new Inner();
- 成员内部类的分类:分为静态成员内部类和非静态成员内部类
1. 非静态成员内部类语法格式如下:
修饰符 class 外部类名 {
修饰符 class 内部类名{
}
}
- 非静态成员内部类的特点:
- 和外部类一样,它只是定义在外部类里面的另一个完整的类结构。
- 可以继承自己想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关
- 可以在非静态内部类中声明属性、方法、构造器等结构,但是不允许声明静态成员,但是可以继承父类的静态成员,而且可以声明静态常量。
- 可以使用abstract修饰,因此它也可以被其他类继承
- 可以使用final修饰,表示不能被继承
- 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名和$符号。
- 和外部类不同的是,它可以允许四种权限修饰符:public,protected,缺省,private
- 外部类只允许public或缺省的。
- 还可以在非静态内部类中使用外部类的所有成员,哪怕是私有的
- 在外部类的静态成员中不可以使用非静态内部类,就如同静态方法中不能访问本类的非静态成员变量和非静态方法一样
- 在外部类的外面必须通过外部类的对象才能创建非静态内部类的对象,因此在非静态内部类的方法中有两个this对象,一个是外部类的this对象,一个是内部类的this对象
3.局部内部类
- 局部内部类的特点:
- 和外部类一样,它只是定义在外部类的某个方法中的另一个完整的类结构
- 可以继承自己想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关
- 可以在局部内部类中声明属性、方法、构造器等结构,但不包括静态成员,除非是从父类继承的静态常量
- 可以使用abstract修饰,因此它也可以被同一个方法的在它后面的其他内部类继承
- 可以使用final修饰,表示不能被继承
- 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名、$符号、编号。这里有编号是因为同一个外部类中,不同的方法中存在相同名称的局部内部类
- 和成员内部类不同的是,它前面不能有权限修饰符等
- 局部内部类如同局部变量一样,有作用域
- 局部内部类中是否能访问外部类的静态还是非静态的成员,取决于所在的方法是静态的还是非静态的。
- 局部内部类中还可以使用所在方法的局部常量,即用final声明的局部变量,JDK1.8之后,如果某个局部变量在局部内部类中被使用了,自动加final
- 局部内部类的使用:例如匿名内部类
- 匿名内部类:一种特色的局部内部类
- 匿名类的格式
new 类名或者接口名() { 重写方法 } // 示例: class Outer { public void method() { // 创建一个匿名内部类,父接口指向实现类对象 Inter i = new Inter() { public void show() { System.out.println("匿名内部类"); } }; i.show(); } }
- 匿名内部类的本质:是一个继承了该类或者实现该接口的子类匿名对象
- 匿名内部类在开发中的使用
// 跳高接口 interface Jumpping { public abstract void jump(); } // 接口操作类 class JumppingOperator { // 调用者需要传递接口实现类对象 public void method(Jumpping j) { j.jump(); } } // test // 创建接口操作类的对象,调用method方法 JumppingOperator jo = new JumppingOperator(); jo.method(new Jumpping() { public void jump() { System.out.println("cat can jump"); } });
35.Object类
- equals:对于引用类型的变量默认比较的是地址,建议所有子类重写这个方法,定制比较的规则。
- toString:建议所有子类重写这个方法。
- hashcode:作用是返回一个对象的散列码,散列码是由对象导出的一个整型值。一般在一个自定义类中同时重写equals和hashcode方法。
- wait:导致当前线程等待,直到另一个线程调用该对象的notify方法或者notifyAll方法
- notify:唤醒正在等待对象监视器(锁)的单个线程
- notifyAll:唤醒正在等待对象监视器(锁)的所有线程
36.基本类型的封装类
1.基本数据类型对应的包装类如下:
Double:double
Float:float
Long:long
Integer:int
Short:short
Byte:byte
Character:char
Boolean:boolean
Void:void
2.Integer类的使用
- 静态方法valueOf获取对象
Integer i = Integer.valueOf(100);
System.out.println(i); // 100
Integer s = Integer.valueOf("100");
System.out.println(s); // 100
- 构造方法创建Integer对象
3.自动装箱和拆箱
- 装箱:将基本数据类型转换为对应的包装类类型。这些包装类称为包装器
- 拆箱:将包装类类型转换为对应的基本数据类型
// 示例如下:
// JDK1.5之后实现自动装箱和自动拆箱
// 自动装箱
Integer ii = 100; // Integer.valueOf(100);
// 自动拆箱和自动装箱
ii += 20; // ii = ii.intValue() + 20;
自动装箱规范要求boolean类型的值,0 <= character <= 127,short,int,long,byte 介于-128到127之间,都将会包装到固定的对象中。这个固定的的对象就是缓存好的常量对象。
// 根据上述特性,引用变量a和b引用堆上的同一个对象
Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true
// 根据上述特性,引用变量c和d不是引用堆上的同一个对象
Integer c = 128;
Integer d = 128;
System.out.println(c == d); // false
当包装类与基本数据类型进行比较、运算时,会将包装类自动拆箱,然后计算.
int a = 100;
Integer b = 100;
System.out.println(a == b);// 先将b拆箱为整形再进行比较
int a = 100;
Integer b = 200;
int c = a + b; // 先将b拆箱为整形再运算
37.日期相关类
1.Date类
- 构造方法Date();表示当前系统时间
- 构造方法Date(long date);使用给定的毫秒时间值创建Date对象
- getTime方法:返回日期对象距离1970-1-1 00:00: 00之间的毫秒值
- setTime方法:
2.SimpleDateFormat类
- SimpleDateFormat进行格式化和解析日期
1. format方法进行格式化(从Date到String)
// 格式化:从Date到String
Date d = new Date();
// 使用默认模式和日期格式
SimpleDateFormat sdf = new SimpleDateFormat();
String str = sdf.format(d);
System.out.println(str); // 21-9-14 下午3:51
SimpleDateFormat s = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
String dateStr = s.format(d);
System.out.println(dateStr); // 2021年09月14日 15:54:25
2. parse方法进行解析(从String到Date)
// 解析:从String到Date
String ss = "2021年09月14日 15:54:25";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
Date dd = sdf.parse(ss);
System.out.println(dd); // Tue Sep 14 15:54:25 CST 2021
3.Calender类
- Calender类的常用方法
1. get():返回给定日历字段的值
2. add():对指定的日历字段添加或者减去一个数值
3. set():设置当前日历的年月日
4.LocalDate类
38.异常
Java中的异常分为编译时异常(checked异常,在编译时期就会检查)和运行时异常(runtime异常,unchecked异常,在运行时期检查异常,编译时期该异常不会被检测到)。异常处理的流程:产生异常,抛出异常,捕获异常,处理异常。
1.异常体系
异常的根类是java.lang.Throwable
,其下有两个子类:java.lang.Error
与java.lang.Exception
,平常所说的异常指java.lang.Exception
。
2.异常处理
- 方案一:使用try...catch
格式为:
try {
可能出现异常的代码
} catch(异常类名) {
异常的处理代码
}
// 举例如下:
private static void method() {
try {
int[] arr = {1,3,5};
System.out.println(arr[3]);
} catch(ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
}
}
- 方案二:使用throws关键字,有些异常处理不了,Java提供了throws的处理方案来声明可能抛出的受查异常(checked异常),非受查异常应当在程序中避免发生。
格式:
throws 异常类名 // 这条代码跟在方法的括号后面的
- throws和throw的区别
throws:
1. 用在方法声明后面,跟的是异常类名
2. 表示抛出异常,由方法的调用者处理(当前方法不处理异常)
3. 表示出现异常的一种可能性,并不一定会发生异常
throw:
1. 用在方法体内,跟的是异常对象名。例如throw new 类名([形参]);
2. 表示抛出异常,由方法体内的语句处理
3. 执行throw一定抛出了异常
// 示例:
public static int getElement(int[] arr, int index) throws ArrayIndexOutOfBoundsException{
if(index < 0 || index > arr.length - 1) {
throw new ArrayIndexOutOfBoundsException("index outof bound");
}
return arr[index];
}
- finally:在异常处理时提供finally块来执行清除操作,比如说IO流中的释放资源。
特点:被finally控制的语句一定会执行,除非JVM退出
异常变量名是final变量(即常量)
try {
可能出现异常的代码
} catch(异常类名 异常变量名) {
处理异常的代码
} finally {
执行所有清除操作
}
- 带资源的try语句:带资源地try块语句,当这个块存在异常或者正常退出时,都会释放资源。
// 读取一个文件地数据,写到屏幕上
try (Scanner scanner = new Scanner(new FileInputStream("hello.txt"), "utf-8")) {
while (scanner.hasNext()) {
System.out.println(scanner.next());
}
}
3.Throwable的成员方法
- getMessage方法:返回此Throwbale的详细消息字符串,比如产生异常的原因
- toString:返回此Throwable的简短描述
- printStackStrace:将异常的详细信息输出到控制台,包含异常的类型,异常的原因,异常出现的位置。
int[] arr = {1,3,5};
try {
arr[3] = 100;
} catch(ArrayIndexOutOfBoundsException exception) {
//exception.printStackTrace();
System.out.println(exception.getMessage()); // 3
}
4.自定义异常
- 格式
class 异常类名 extends Exception {
无参构造
带参构造
}
- 从Exception类或者它的子类派生一个子类即可。习惯上,自定义异常类应该包含2个构造器:一个是无参构造,另一个是带有详细信息的构造器
5.断言
- 断言机制:断言机制允许在测试期间向代码中插入一些检查语句。当代码发布时这些插入地检测语句将会被自动移走。断言只应该用于在测试阶段确定程序内部地错误位置。
- 语法格式:下面两种形式都会对条件进行检测,如果结果为false,则抛出一个AssertionError异常
- assert 条件
- assert 条件:表达式 // 表达式地值将传入AssertionError地构造器,并转换成一个消息字符串。
- 启用和禁用断言
默认情况下,断言被禁用。可以在运行程序时用java -enableassertions 程序名称
启用
39.可变参数
- 应用场景:形参的个数不确定就可以使用可变参数。可变参数其实是一个数组。
- 格式:修饰符 返回值类型 方法名(参数类型...形参名){}
- 要求
- 一个方法只能有一个可变参数
- 形参列表可以有多个形参,但是可变参数必须是形参列表的最后一个
- 示例
public static int sum(int...arr) {
int sum = 0;
for(int i:arr) {
sum += i;
}
return sum;
}
// 调用
int sum = sum(new int[] {100,200,300,400,500});
note:可以将已经存在且最后一个参数是数组的方法重新定义为可变参数的方法,而不会破坏任何已经存在的代码。
40.集合框架
1.概述
集合主要分为两大系列:Collection和Map,Collection 表示一组对象,Map表示一组映射关系或键值对。因此我们说的集合分为两大类:Collection系列和Map系列。
2.Java中的集合框架结构如下:
- Collection系列集合体系:
- Map系列集合体系:
3.Collection集合中的常用方法
- 添加元素
- add(E e):增加元素到集合中
- addAll(Collection<? extends E> c):添加另一个集合c中的所有元素到当前this集合中
- 删除元素:
- remove(Object o):从当前集合中删除第一个找到的obj对象
- removeAll(Collection<?> c):从当前集合中删除所有与c集合中相同的元素
- removeIf(Predicate<? super E> filter):删除集合中满足给定谓词的所有元素
- 判断元素
- 查询
- 交集
4.Collection集合的遍历
// 1.使用迭代器iterator
Collection<String> c = new ArrayList<String>();
c.add("Hello");
c.add("World");
c.add("Java");
Iterator<String>i = c.iterator();
while(i.hasNext()) {
String str = i.next();
System.out.print(str + ",");
}
// 2.普通for循环
for (int i = 0; i < c.size(); i++) {
System.out.println(c.toArray()[i]);
}
// 3.增强型for循环
for (String str : c) {
System.out.println(str);
}
5.Iterator迭代器
Iterator主要用于迭代访问(即遍历)Collection中的元素,因此Iterator对象也被称为迭代器。Iterator是一个接口。
- 获取迭代器的方法
Iterator<E> iterator():获取集合的迭代器,可以用来遍历集合中的元素
- Iterator接口中常用的方法
1. E next():返回迭代中的下一个元素
2.boolean hasNext():如果还有更多的元素可以迭代返回true
// 示例:使用Iterator遍历集合中的元素
Collection<String> c = new ArrayList<String>();
c.add("Hello");
c.add("World");
c.add("hi");
Iterator<String> i = c.iterator();
while (i.hasNext()) {
String str = i.next();
System.out.println(str);
}
- 使用Iterator迭代器删除元素
// 接口Iterator中有一个方法remove
Collection<String> c = new ArrayList<String>();
c.add("Hello");
c.add("World");
c.add("Hi");
Iterator<String> i = c.iterator();
while (i.hasNext()) {
String str = i.next();
// 删除集合中以H开头的元素
if (str.startsWith("H")) {
i.remove();
}
}
System.out.println(c); // World
6.Java.lang.Iterable接口
实现这个接口,允许接口实现类的对象成为”for each“语句的目标
- 接口的抽象方法
1. Iterator<T> iterator():获取对应的迭代器
7.并发修改异常
- 并发修改异常(ConcurrentModificationException):使用Iterator迭代器或者增强for循环遍历集合的过程中,通过集合对象的方法(比如add和remove)修改了集合中元素的长度,造成了迭代器中的next方法中判断预期修改次数值与实际修改次数值不一致,就会报ConcurrentModificationException。
41.List集合
1.List接口概述
- List是一个接口,单列集合的一个重要分支。
- List集合中的元素存储有序,即元素的存入顺序和取出顺序一致
- list集合中可以有重复的元素
- 带有索引
- 以线性方式存储
2.list集合的特有方法
- 添加元素
- 获取元素
- 获取元素索引
- 删除和替换元素
42.ListIterator:列表迭代器
- 为遍历list集合准备的迭代器接口。
- 获取ListIterator这个list迭代器:ListIterator
listIterator() - ListIterator中专门操作list的方法
void add():通过迭代器添加元素到对应集合
void set(E e):通过迭代器替换正迭代的元素
void remove():通过迭代器删除刚迭代的元素
boolean hasPrevious():如果以逆向遍历列表,往前是否还有元素。
E previous():返回列表中的前一个元素,游标位置指向前一个位置
int previousIndex():返回列表中的前一个元素的索引
boolean hasNext():正向遍历list集合中还有元素则返回true。next方法返回元素则hasNext返回true
E next():返回列表中的下一个元素,游标位置指向下一个位置
int nextIndex():返回列表中的后一个元素的索引
43.ArrayList,Vector
- 底层结构都是数组,被称为动态数组。查询快,增删慢
- 两者的区别
- ArrayList是新版的动态数组,线程不安全,效率高,Vector是旧版的动态数组,线程安全,效率低。
- 动态数组的扩容机制不同,ArrayList扩容为原来的1.5倍,Vector扩容增加为原来的2倍。
- 数组的初始化容量,如果在构建ArrayList与Vector的集合对象时,没有显式指定初始化容量,那么Vector的内部数组的初始容量默认为10,而ArrayList在JDK1.6及之前的版本也是10,而JDK1.7之后的版本ArrayList初始化为长度为0的空数组,之后在添加第一个元素时,再创建长度为10的数组。
- Vector因为版本古老,支持Enumeration 迭代器。但是该迭代器不支持快速失败。而Iterator和ListIterator迭代器支持快速失败。如果在迭代器创建后的任意时间从结构上修改了向量(通过迭代器自身的 remove 或 add方法之外的任何其他方式),则迭代器将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就完全失败,而不是冒着在将来不确定的时间任意发生不确定行为的风险。
44.LinkedList
- 底层结构是双链表,查询慢,增删快
- LinkedList集合的特有功能
1. addFirst
2. addLast
3. getFirst
4. getLast
5. removeFirst
6. removeLast
7. peekFirst
8. peekLast
JDK1.6之后LinkedList实现了Deque接口.如果要使用堆栈结构的集合,可以考虑使用LinkedList,而不是Stack。
45.set集合(内部本质还是Map)
1.set集合的特点:
- 不包含重复元素的集合,集合中最多有一个null元素;
- 没有带索引的方法,不能使用普通for循环遍历。
- Collection的子接口
46.HashSet
1.HashSet集合的特点:
- 底层数据结构是哈希表;
- 对集合的迭代顺序不做任何保证。
- 没有带索引的方法,不能使用普通for循环遍历。
- 要保证元素唯一性,需要重写equals和hashCode方法
- 集合中允许null元素
47.LinkedHashSet
1.LinkedHashSet集合的特点:
- 底层数据结构是哈希表加上链表;
- 由链表保证元素有序,元素的存储和取出顺序一致。
- 由哈希表保证元素唯一,也就是说没有重复的元素。
- HashSet的子类
48.TreeSet
1.TreeSet集合的特点:
- 元素有序:这里的顺序不是指存储和取出的顺序。而是按照一定的规则进行排序,具体排序方法取决于构造方法
1. TreeSet():根据其元素的自然排序进行排序
2. TreeSet(Comparator comparator):根据指定的比较器进行排序
- 底层结构:里面维护了一个TreeMap,都是基于红黑树实现的
- 由于是set集合,所以集合元素不重复。去重的实现如下:
- 如果使用的是自然排序,则通过调用实现的compareTo方法(Comparable接口中的抽象方法)
- 如果使用的是定制排序,则通过调用比较器的compare方法
2.自然排序Comparable的使用
用TreeSet集合存储自定义对象,无参构造方法使用的是自然排序对元素进行排序的。自然排序就是让元素所属的类实现Comparable接口,重写compareTo方法.判断两个对象是否相等的唯一标准是:两个对象通过compareTo(Object obj) 方法比较返回值是否为0。
package javase_practice;
class Student implements Comparable{
private String name;
private int age;
Student() {
name = "";
age = -1;
}
Student(String name,int age) {
this.name = name;
this.age = age;
}
// getter, setter
//按照年龄进行从小到大排序,如果年龄相同就按照姓名排序
@Override
public int compareTo(Object o) {
Student s = (Student)o;
int num = this.age - s.age;
int ret = num == 0 ? this.name.compareTo(s.name):num;
return ret;
}
}
// main方法中
TreeSet<Student>ts = new TreeSet<Student>();
Student s1 = new Student("张三",23);
Student s2 = new Student("李四",25);
Student s3 = new Student("王五",23);
Student s4 = new Student("李四",23);
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
// 使用增强for循环对集合进行遍历
for(Student s:ts) {
System.out.println(s.getName() + "," + s.getAge());
}
3.比较器排序Comparator接口(定制排序)的使用
- Comparator是一个函数式接口,其中只有一个抽象方法
int compare(T o1, T o2)
.判断两个对象是否相等的唯一标准是:两个对象通过 compareTo(Object obj) 方法比较返回值是否为0。
// 示例:
public class LengthComparator implements Comparator<String> {
// 对一个字符串数组进行长度排序
@Override
public int compare(String o1, String o2) {
return o1.length() - o2.length();
}
}
public static void main(String[] args) throws ArrayIndexOutOfBoundsException{
String[] str = {"hello","you","hi","worldis"};
Arrays.sort(str, new LengthComparator());
for(String s : str) {
System.out.print(s + " "); //hi you hello worldis
}
}
4.HashSet和TreeSet,LinkedHashSet的选用问题
- HashSet:如果不需要保证添加顺序,只是不可重复,就用HashSet。因为如果你每次添加删除时,还要维护前后元素的关系,就效率低了。不可重复这个特性要依赖于元素的hashCode()和equals()方法,底层的存储是哈希表。
- TreeSet:只有当你要求元素按照大小顺序,并且不可重复,那么才使用它。要依赖于元素的compareTo()或定制比较器的compare()。底层的存储是红黑树(自平衡的二叉树)
- LinkedHashSet:当你如果遇到,既要保证元素的添加顺序,又要保证元素不可重复。要依赖于元素hashCode()和equals()。它是HashSet的子类。比HashSet中的结点类型,多维护了一个前后结点的引用。
5.Set系列集合小结
- Set系列的集合的特点:不可重复的。是否有序:
- 如果从元素的存储位置来看,是无序的;
- 如果从遍历的结果来看,其中TreeSet是按照大小顺序,LinkedHashSet是按照添加的顺序。
- Set的实现类们:
- HashSet:依赖于元素的hashCode和equals方法
- TreeSet:依赖于元素的compareTo()或定制比较器对象的compare()
- LinkedHashSet:依赖于元素的hashCode和equals方法
- 底层实现
- HashSet:内部维护的是HashMap,添加到HashSet中的元素是作为HashMap的key,value使用一个Object类型的PRESENT常量对象。
- LinkedHashSet:内部维护的是LinkedHashMap,添加到LinkedHashSet中的元素是作为LinkedHashSet的key,value使用一个Object类型的PRESENT常量对象。
- TreeSet:内部维护的是TreeMap,添加到TreeSet中的元素是作为TreeMap的key,value使用一个Object类型的PRESENT常量对象。
49.Map
1.Map集合概述
- Interface Map<K,V> K:为键的类型,V:值的类型
- 将键映射到值的对象
- 不能包含重复的键,值可以重复。每个键可以映射到最多一个值
- Map中的集合称为双列集合,Collection中的集合称为单列集合。
- Map接口的实现类
1. HashMap
2. TreeMap
3. LinkedHashMap:HashMap的子类
4. Properties:HashTable的子类
5. HashTable
HashMap与HashTable的区别:
HashMap和Hashtable都是哈希表。
HashMap和Hashtable判断两个 key 相等的标准是:两个 key 的hashCode 值相等,并且 equals() 方法也返回 true。因此,为了成功地在哈希表中存储和获取对象,用作键的对象必须实现 hashCode 方法和 equals 方法。
Hashtable是线程安全的,任何非 null 对象都可以用作键或值。
HashMap是线程不安全的,并允许使用 null 值和 null 键。
LinkedHashMap与HashMap的区别:
LinkedHashMap 是 HashMap 的子类。此实现与 HashMap 的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序通常就是将键插入到映射中的顺序(插入顺序)。
TreeMap:
基于红黑树(Red-Black tree)的 NavigableMap 实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的Comparator进行排序,具体取决于使用的构造方法。
HashTable与TreeMap,LinkedHashMap的区别:
哈希表,遍历的顺序和添加的顺序无关,即不保证添加顺序
TreeMap:是按照key的大小顺序遍历.说明TreeMap的key要么实现java.lang.Comparable接口,要么在创建TreeMap时,指定Comparator的对象
LinkedHashMap:遍历的顺序保证添加顺序
2.Map集合的基本功能
- put方法:添加元素
- remove方法:根据键值对删除元素
- containsKey方法:判断集合中是否包含指定的键
- containsValue方法:判断集合中是否包含指定的值
- isEmpty方法:判断集合是否为空
- size方法:集合中键值对的个数
- clear方法:移除所有的键值对元素
- putAll:添加元素
3.Map集合的获取功能
- get方法:根据建获取值
- keySet方法:获取所有键的集合
- values方法:获取所有值的集合
- entrySet方法:获取所有键值对对象的集合
4.Map集合的遍历
- 方式一:使用keySet方法,get方法
1.获取所有键的集合,用keySet方法实现
2.遍历键的集合,获取到每一个键,用增强for循环实现
3.根据建获取到值,用get方法实现
// 示例如下:
Map<String,String>map = new HashMap<String,String>();
map.put("001", "zhangsan");
map.put("002","lisi");
map.put("006","wangwu");
Set<String>set = map.keySet();
for(String str:set) {
String value = map.get(str);
System.out.println(str + ":" + value);
}
- 方式二:使用entrySet方法(Entry是Map接口的内部接口)
1.获取所有键值对对象的集合,用entrySet方法实现
2.遍历键值对对象的集合,得到每一个键值对对象,即Map.Entry,用增强for循环实现
3.根据键值对对象获取键和值
// 示例
Map<String,String>map = new HashMap<String,String>();
map.put("001", "zhangsan");
map.put("002","lisi");
map.put("006","wangwu");
// 获取所有键值对对象的集合
Set<Map.Entry<String,String>>set = map.entrySet();
for(Map.Entry<String, String>me:set) {
String key = me.getKey();
String value = me.getValue();
System.out.println(key + ":" + value);
}
5.Map实现类的选择
- HashMap:如果不需要保证添加顺序(存取顺序),只是不可重复,就用HashMap。
- TreeMap:只有当你要求元素按照大小顺序,并且不可重复,那么才使用它。
- LinkedHashMap:当你如果遇到,既要保证元素的添加顺序,又要保证元素不可重复,那么使用LinkedHashMap。它是HashMap的子类。
50.Collections集合(工具类)
1.Collections类的概述
针对集合操作的工具类。这些集合包括Collection,Map系列的集合
2.Collections类的常用方法
- sort方法:对指定的列表按照顺序排列
- reverse方法:反转指定列表中元素的次序
- shuffle方法:随机排列指定的列表
51.泛型
1.泛型概述
泛型的本质是参数化类型,就是将原来的具体的类型参数化,然后再使用或者说调用时传入具体的类型。这种参数类型用在类,方法,接口中,分别称为泛型类,泛型方法,泛型接口。
2.泛型类
- 泛型类的定义格式:修饰符 class 类名<类型参数,...,类型参数>{}.
// 一个泛型类示例如下
public class Generic<T> {
private T t;
public void setT(T t) {
this.t = t;
}
public T getT() {
return t;
}
}
3.泛型方法
- 泛型方法的定义格式:修饰符<类型参数>返回值类型 方法名(形参列表){}
public class Generic {
// 泛型方法示例
public <T>void show(T t) {
System.out.println(t);
}
}
Generic g = new Generic();
g.show("Hello"); // Hello
g.show(100); // 100
4.泛型接口
- 泛型接口的定义格式:修饰符 interface 接口名<类型>{}
// 一个泛型接口示例如下
public interface GenericInterface<T> {
public abstract void show(T t);
}
// 泛型接口的实现类如下
public class GenericImpl<T> implements GenericInterface<T> {
@Override
public void show(T t) {
System.out.println(t);
}
}
// test
GenericInterface<String> g = new GenericImpl<String>();
g.show("Hello"); // Hello
5.类型通配符
- 类型通配符:例如List表示元素类型未知的List,他的元素可以匹配任何类型。这种带通配符的List仅表示他是各种泛型List的父类,并不能将元素添加到其中。
- 指定类型通配符的上限:<?extends 类型>,例如
List<?extends Number>
表示的类型是Number或者其子类型。如果有多个上限,则:<类型变量 extends 上限1 & 上限2> - 指定类型通配符的下限:<?super 类型>,例如
List<?super Number>
表示的类型是Number或者其父类型。
List<?>list1 = new ArrayList<Object>();
List<?>list2 = new ArrayList<Number>();
List<?>list3 = new ArrayList<Integer>();
// 其中Integer是Number的子类
//List<?extends Number>list4 = new ArrayList<Object>(); // error
List<?extends Number>list5 = new ArrayList<Number>();
List<?extends Number>list6 = new ArrayList<Integer>();
List<?super Number>list7 = new ArrayList<Number>();
List<?super Number>list8 = new ArrayList<Integer>(); // error
6.类型擦除
- 概念:虚拟机中没有泛型类型对象,所有对象都属于普通类,因为存在类型擦除的概念。类型擦除:无论何时定义了一个泛型,都自动提供了一个相应的原始类型。原始类型的名字就是删去类型参数后的泛型类型名。擦除类型变量,并替换为限定类型(比如说<? extends Number>则替换为Number这个限定类型),无限定的变量用Object.
- 替换规则:每一个泛型对应的原始类型用第一个限定的类型变量来替换(可能有多个上限或者下限),如果没有给定限定就用Object替换。
- 优点:可以避免模板代码膨胀,不像C++中每个模板的实例化产生不同的类型
- 泛型中如何保持多态:由编译器合成桥方法
- 不能用基本类型实例化类型参数(理解了类型擦除后很好理解)
7.不能创建泛型数组
52.IO流
0.IO流分类
- 按照数据的流向:输入流和输出流。输入流读数据,输出流写数据。输入流以InputStream或者Reader结尾,输出流以outputStream或者writer结尾
- 按照数据类型:字节流和字符流;字节流又有字节输入流,字节输出流。以InputStream和OutputStream结尾;字符流也有字符输入流,字符输出流。以Reader或者Writer结尾
- 字节流和字符流的区别:字节流可以操作任意类型的文件的数据;字符流专门用于处理文本文件
1.File类
- File类概述:File是文件和目录路径名的抽象表示。无论该路径下是否存在文件或者目录,都不影响File对象的创建。
- 文件和目录可以通过File封装成对象
- 对于File而言,封装的并不是一个真正存在的文件,仅仅是一个路径名而已。它可以是存在的,也可以是不存在的。
- File类的构造函数
1. File(String pathname): // pathname为路径名字符串
2. File(String parent,string child)// 父路径名和子路径名
3. File(File parent,String child)
// 构造函数示例:
File f1 = new File("E:\\example\\hi.txt");
System.out.println(f1); //E:\example\hi.txt
File f2 = new File("E:\\example","hi.txt");
System.out.println(f2); //E:\example\hi.txt
File f3 = new File("E:\\example");
File f4 = new File(f3,"hi.txt");
System.out.println(f4); //E:\example\hi.txt
- File类的创建功能
- createNewFile方法:当具有该名称的路径名不存在时,创建一个由该抽象路径名命名的新空文件。如果文件存在就不创建,并返回false。
- mkdir方法:创建由此抽象路径名命名的目录。如果目录存在就不创建目录并返回false。
- mkdirs方法:创建由此抽象路径名命名的目录,包括任何必须但不存在的父目录。(用于创建多级目录)如果目录存在就不创建目录并返回false。
// 示例
// 1.在已有的E:\\dir目录下创建一个hello.txt文件
File f1 = new File("E:\\dir\\hello.txt");
System.out.println(f1.createNewFile()); // true,再次运行为false,下同
// 2.在已有的E:\\dir目录下创建一个目录javaSe
File f2 = new File("E:\\dir\\javaSe");
System.out.println(f2.mkdir()); // true
// 3.在已有的E:\\dir目录下创建一个多级目录javaEe\\Mysql
File f3 = new File("E:\\dir\\javaEe\\Mysql");
System.out.println(f3.mkdirs()); // true
- File类的判断和获取功能
- isDirectory方法:测试此抽象路径名表示的File是否为目录
- isFile方法:测试此抽象路径名表示的File是否为文件
- exits方法:测试此抽象路径名表示的File是否存在
- getAbsolutePath方法:返回此抽象路径名的绝对路径名字符串
- getPath方法:将此抽象路径名转换为路径名字符串
- getName方法:返回此抽象路径名表示的文件或者目录的名称
- list方法:返回此抽象路径名表示的目录中的文件和目录的名称字符串数组
- listFiles方法:返回此抽象路径名表示的目录中的文件和目录的File对象数组
// 示例
// 我们在当前工作目录下新建一个hello.txt文件进行测试
File f1 = new File("hello.txt");
System.out.println(f1.isDirectory()); // false
System.out.println(f1.isFile()); // true
System.out.println(f1.exists()); // true
System.out.println(f1.getAbsolutePath()); //E:\practice\Java practice\javase_practice\hello.txt
System.out.println(f1.getPath()); //hello.txt
System.out.println(f1.getName()); //hello.txt
// E:\\dir目录下既有文件也有目录
File f2 = new File("E:\\dir");
String[] str = f2.list();
// hello.txt
// javaEe
// javaSe
for(String s:str) {
System.out.println(s);
}
File[] file = f2.listFiles();
// E:\dir\hello.txt
// E:\dir\javaEe
// E:\dir\javaSe
for(File f:file) {
System.out.println(f);
}
- File类的删除功能
- delete方法:删除由此抽象路径名表示的文件或者目录。注意:如果目录中有内容,应该先删除目录下的内容再删除目录
// 在指定包下下创建文件并删除文件
File f1 = new File("src\\test\\example.txt");
System.out.println(f1.createNewFile()); // true
System.out.println(f1.delete()); // true
// 在指定包下创建目录并删除目录(目录下无文件)
File f2 = new File("src\\test\\dir");
System.out.println(f2.mkdirs()); //true
System.out.println(f2.delete()); //true
- 遍历目录:通过递归完成遍历指定目录下的所有内容
- 根据给定的路径创建一个File对象
- 定义一个方法,用于获取给定目录下的所有内容,参数为第一步创建的File对象
- 获取给定的File目录下所有文件和目录的File数组
- 遍历该File数组,获取每一个File对象
- 判断该File对象是否为目录,是就递归调用,不是就获取绝对路径输出在控制台
- 调用方法
public static void main(String [] args) throws ParseException, IOException {
File srcFile = new File("E:\\dir");
getAllFilePath(srcFile);
}
// E:\\dir目录下的所有文件
// E:\dir\hello.txt
// E:\dir\javaEe\Mysql\ddd.rtf
// E:\dir\javaSe\Mysql\example.docx
public static void getAllFilePath(File srcFile) {
File[] fileArray = srcFile.listFiles();
if(fileArray != null) {
for(File file : fileArray) {
if(file.isDirectory()) {
getAllFilePath(file);
} else {
System.out.println(file.getAbsolutePath());
}
}
}
}
2.字节流
- 字节流抽象基类
- InputStream:这个抽象类是表示字节输入流的所有类的超类
- OutputStream:这个抽象类是表示字节输出流的所有类的超类
- FileOutputStream:文件输出流用于将数据写入文件
- FileOutputStream(String name):创建文件输出流以指定的名称写入文件
- 使用字节输出流写数据的步骤:
- 创建字节输出流对象
- 调用字节输出流对象的写数据方法
- 释放资源
FileOutputStream fs = new FileOutputStream("hello.txt"); fs.write(97); // 将指定的字节写入此文件输出流,即a fs.close();
- 字节流写数据的三种方式:
- void write(int b):将指定的字节写入此文件输出流,一次一个字节
- void write(byte[] b):将b.length个字节从指定的字节数组写入此文件输出流,一次写一个字节数组数据
- void write(byte[]b,int off,int len):将len个字节从指定的数组开始,从偏移量off开始写入此文件输出流,一次写一个字节数组的部分数据。
// 示例如下
FileOutputStream fs = new FileOutputStream("hello.txt");
// 一次写一个字节,写入一个字符a
fs.write(97);
// 一次写入一个字节数组,写入HelloWorld
byte[] b = "HelloWorld".getBytes();
fs.write(b);
// 一次写入一个字节数组的部分数据,写入ell
fs.write(b,1,3);
fs.close();
字节流写数据的两个小问题:
1. 字节流写数据实现换行:写完数据后,加换行符
对于Windows:\r\n
对于Linux:\n
对于Mac:\r
FileOutputStream fs = null;
try {
fs = new FileOutputStream("hello.txt",true);
for(int i = 0; i < 10; i++) {
fs.write("HelloWorld".getBytes());
// 需求,写入每个HelloWorld后换行
fs.write("\n".getBytes());
}
} catch(IOException e) {
e.printStackTrace();
} finally {
if(fs != null) {
try {
fs.close();
} catch(IOException e) {
e.printStackTrace();
}
}
}
2. 字节流写数据实现追加写入:public FileOutputStream(String name,boolean append)本构造方法的第二参数指定为true
FileOutputStream fs = new FileOutputStream("hello.txt",true);
- 字节流读数据
1. int read():一次读一个字节的数据。返回下一个字节的数据或者-1,表示到达文件末尾
2. int read(byte[] b):一次读一个字节数组的数据。返回值为读入字节数组b的字节数或者-1,表示到达文件末尾
3. int read(byte[] b,int off,int len):一个读部分字节数组长度的数据。返回值同上
FileInputStream fis = new FileInputStream("Hello.txt");
byte[] bys = new byte[1024];
int len;
while((len = fis.read(bys)) != -1) {
// 将字节数组转为字符串输出
System.out.println(new String(bys,0,len));
}
fis.close();
- 字节缓冲流:同样包含字节缓冲输入流和字节缓冲输出流.通过建立缓冲输入流或者缓冲输出流,这样就无需每写入一个字节或者读出一个字节就导致系统调用。 。字节缓冲输入流和字节缓冲输出流的常用方法如下:
- void write(int b):将指定的一个字节写入字节缓冲输出流
- void write(byte[] b,int off,int len):将字节数组的部分数据写入字节缓冲输出流
- int read():从输入字节流中读取一个字节的数据。返回值为下一个字节数据或者-1,表示没数据了
- read(byte[] b,int off,int len):从输入字节流中读取数据到字节数组的指定位置。返回读取到的字节数或者-1,表示读到文件末尾了
// 写数据
// 创建字节缓冲输出流对象
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("hello.txt"));
bos.write("Hello\r\n".getBytes());
bos.write("World\r\n".getBytes());
bos.close();
// 读数据
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("hello.txt"));
// 一次读取一个字节数组的数据
byte[] b = new byte[1024];
int len;
while((len = bis.read(b)) != -1) {
System.out.println(new String(b,0,len));
}
// // 一次读一个字节的数据
// int by;
// while((by = bis.read()) != -1) {
// System.out.print((char)by);
// }
bis.close();
案例:复制文件(将一个目录下的文件复制到另一个目录)
1. 根据数据源创建字节输入流对象
2. 根据目的地创建字节输出流对象
3. 读写数据,复制文本文件(一次读取一个字节,一次写入一个字节)
4. 释放资源
// 根据数据源创建字节输入流对象
FileInputStream fis = new FileInputStream("hello.txt");
// 根据目的地创建字节输出流对象
FileOutputStream fos = new FileOutputStream("E:\\dir\\hello.txt");
int by;
while((by = fis.read()) != -1) {
fos.write(by);
}
// 释放资源
fis.close();
fos.close();
案例:字节流复制图片
1. 根据数据源创建字节输入流对象
2. 根据目的地创建字节输出流对象
3. 读写数据,复制文本文件(一次读取一个字节数组,一次写入一个字节数组)
4. 释放资源
// 根据数据源创建字节输入流对象
FileInputStream fis = new FileInputStream("E:\\dir\\20190726192034462 - 副本.jpg");
// 根据目的地创建字节输出流对象
FileOutputStream fos = new FileOutputStream("20190726192034462 - 副本.jpg");
byte[]bys = new byte[1024];
int len;
while((len = fis.read(bys)) != -1) {
fos.write(bys, 0, len);
}
// 释放资源
fis.close();
fos.close();
案例:复制视频:使用字节缓冲流
四种方式复制视频
1. 基本字节流一次读写一个字节
2. 基本字节流一次读写一个字节数组
3. 字节缓冲流一次读写一个字节
4. 字节缓冲流一次读写一个字节数组
//其中方式四最快,实现如下
// 创建字节缓冲输入输出流对象
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:\\dir\\41一个简单的线程池实现.avi"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("41一个简单的线程池实现.avi"));
// 一次读写一个字节数组
byte[] bys = new byte[1024];
int len;
while((len = bis.read(bys)) != -1) {
bos.write(bys,0,len);
}
// 释放资源
bis.close();
bos.close();