复习了一波 java


oracle 官网: https://docs.oracle.com/en/

选择 JDK 版本进入: https://docs.oracle.com/en/java/javase/

java 官网 API: https://docs.oracle.com/en/java/javase/11/docs/api/index.html

八种数据类型

1、byte: 1 个字节 (-2^7~2^7-1 )
2、short: 2 个字节 (-2^15~2^15-1)
3、int: 4 个字节 (-2^31~2^31-1)
4、long: 8 个字节 (-2^63~2^63-1)
5、float: 4 个字节 (3.410^(-38) ~ 3.410^(+38)) (float 占 4 个字节, 为什么比 long 表示的范围大? 因为虽然 long 占 8 个字节, 但是按整数的规则计算的, float 虽然占 4 个字节, 但是是按小数的规则计算的, 所以 float 表示的范围要比 long 大)
6、double: 8 个字节 (1.710^(-308) ~ 1.710(308))
7、char: 2 个字节 (0~2^16-1)
8、boolean: 1 个字节 (true || false)

计算机存储数据的形式

计算机中最小的存储单元是字节(Byte, 通常用 B 表示), 每个字节包含 8 个(bit, 又叫“比特位” 通常用 b 表示, 值为 0 或者 1)

单位 换算
1B(字节) 8bit
1KB 1024B
1MB 1024KB
1GB 1024MB
1TB 1024GB

所以通常我们获取浏览器当前本地缓存容量剩余可以这么求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(function () {
if (!window.localStorage) {
console.log("浏览器不支持localstorage");
} else {
let size = 0;
for (let item in window.localStorage) {
if (window.localStorage.hasOwnProperty(item)) {
size += window.localStorage.getItem(item).length;
}
}

console.log(`当前localstorage的剩余容量为${(size / 1024).toFixed(2)}KB`);
}
})();

类型转换

不同类型的数据之间可能会进行运算, 而这些数据取值范围不同, 存储方式不同, 直接进行运算可能会造成数据损失, 所以需要将一种类型转换成另外一种类型再进行运算

1、自动(隐式)类型转换

小类型转大类型, 自动提升为大类型, 运算结果是大类型

1
2
3
4
数据类型的范围从小到大如下:
byte, short, char -> int(默认的整形) -> long -> float -> double(默认的浮点型)

boolean型不参与比较, 它的值只有truefloat两个

2、强制(显式)类型转换

手动将大类型转换成小类型, 运算结果是小类型, 转换格式: 小类型 变量名 = (小类型)大类型数据

1
2
目标类型 变量名 = (目标类型) 要转换的值
⚠️注意: 强制类型转换在使用的时候可能会出现丢失精度的问题
一般的类型转换''
一般的类型转换''

方法重载

什么是方法重载

在同一个类中的多个方法, 它们方法名相同, 参数列表不同, 这样的情况称为方法重载. 方法重载与返回值类型无关

参数列表不同:
1
2
1、参数的个数不同
2、对应位置的参数类型不同
方法签名

方法名 + 参数列表

为什么需要方法重载

当实现的功能相同, 但具体的实现方式不同, 我们可以通过定义名称相同, 参数(条件)不同的方法, 来更好的识别和管理类中的方法.

数组初始化

在内存中为数组开辟连续空间并为每个元素赋值的过程

内存

计算机的重要组件, 用于程序运行中临时存储数据

连续空间

数组元素在内存中的存放位置是连续的

程序的内存分配

方法区

存储可运行的 class 文件, 包含方法, 静态成员, 常量等

方法运行时使用的内存,特点是“后进先出”, 即最先进入栈区的方法最后出栈, 比如 main 方法

存储 new 出来的数组或者对象

本地方法栈

JVM 在调用操作系统功能时使用, 与开发无关

寄存器

CPU 使用, 与开发无关

数组类型

变量 arr 存储的是数组在堆内存中的地址值, 而不是数组元素的值, 变量 arr 通过内存地址引用堆内存中的数组, 所以数组是引用类型

final 关键字

final: “最终”的意思; 在 Java 中是一个关键字, 可以用来修饰类, 成员变量, 成员方法
1、修饰的类: 不能被继承(没有子类), 但是可以继承其他类

demo''
demo''

2、修饰的方法: 不能被重写(和 abstract 冲突, 因为 abstract 修饰的类是抽象类, 要求必须重写), System, String 这些关键字其实就是被 final 修饰的, 所以不能重写
3、修饰的变量: 是一个常量, 值只能设置一次; 如果修饰的是一个引用的地址, 那么地址不能改动而属性可以改

static 关键字

用于修饰类的成员, 被修饰的成员变量 = 类变量, 被修饰的成员方法 = 类方法; 然后可以直接类名.xxx 直接调用, 所以通常随意改变 static 修饰的类成员变量或者类方法是有风险的, 所以可以使用 final 关键字对其进行修饰, 使之成为一个不可修改的常量;

接口的概念

用于描述类具有什么功能, 但是不给出具体实现, 类要遵从接口描述的统一规则进行定义, 所以, 接口是对外提供的一组规则、标准

举例: 插座(接口) => 插头(具体实现根据插座是几个孔的规则)

接口的定义

定义接口关键字 interface

1
interface 接口名 {}

类和接口是实现关系, 用 implements 表示

1
class 类名 implements 接口名 {}

⚠️ 在声明接口的时候成员方法如果不写方法体, 会默认加上 public abstract

demo:

1
2
3
4
5
6
public interface Working {
void work(); // 没写方法体. 默认会加上public abstract

// 正常来说是这样的
public abstract void work();
}

此时接口中有抽象方法, 在 work 的实现类就要实现这个抽象方法

1
2
3
4
5
6
public class Person implements Work {
@Override
public void work () {
System.out.println("努力工作");
}
}

⚠️ 还需要注意的点是, 接口不能实例化,但是可以实例化实现的子类, 这种写法也叫多态

成员变量

接口中没有成员变量, 只有公有的, 静态的常量; 原因是接口中所有的变量都默认有一组修饰符, 这是系统默认写的, 如果你写了系统就不写, 如果你不写系统就默认写上, 而 final 的作用可以看上面部分

1
public static final 常量名 = 常量值

成员方法

JDK7 以前, 接口中只有公有的抽象方法, 因为接口中的方法系统都会默认加上

1
public abstract 返回类型 方法名()

JDK8 以后, 可以拥有默认方法和静态方法;
默认方法:

1
public default 返回类型 方法名 () {}

静态方法:
直接写就可以了

1
static 返回类型 方法名() {}

JDK9 以后, 可以有私有方法:

1
private 返回类型 方法名 () {}

接口中构造方法

由于接口不能实例化, 也没有需要初始化的成员变量. 所以接口没有构造方法;

Java 中的 API

java 中组件的层次结构

模块(module) => 包(package) => 类或接口(class / interface)
模块: 在包的基础上又进行了一层封装, 是包的容器

Object 类

类层次结构最顶层的基类, 所有类都直接或间接的继承自 Object 类, 所以, 所有的类都是一个 Object(对象), 继承自 java.base;

常用到的类

Object, Scanner, String, StringBuilder, StringBuffer, Date, Calendar

集合

什么是集合

简称集, 是用来存储多个元素的容器

集合和数组的区别

  • 元素类型
    集合: 存储引用类型数据(存储基本类型时自动装箱)
    数组: 可以存储基本类型和引用类型的数据
  • 元素个数
    集合: 元素个数不固定, 可任意扩容
    数组: 固定, 不能改变容量
  • 集合的好处
    可以不受容器大小限制(写 js 爽的同学用起来也爽), 可以随时添加, 删除元素, 提供了大量操作元素的方法(判断,获取等等, 类似: StringBuilder 和 StringBuffer)

集合体系

单例集合(Collection)
  • List: ArrayList
    可重复, 有序(存取顺序相同)
1
2
3
4
5
6
7
8
9
10
List list = new ArrayList();
// 增加元素
list.add(1);
list.add(2);
list.add(3);

// 输出
for (int i=0;i<list.size();i++){
System.out.print(list.get(i));
}
  • Set: HashSet
    不可重复, 无序
1
Set<Student> set = new HashSet<>();

写一个 demo, 首先声明一个学生累

Class Student
Class Student

然后声明 5 个学生, 用 set 去重

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class SetDemo {
public static void main(String[] args) {
Set<Student> set = new HashSet<>();

Student s1 = new Student("唐三", 15);
Student s2 = new Student("戴沐白", 16);
Student s3 = new Student("马红俊", 17);
Student s4 = new Student("戴沐白", 16);
Student s5 = new Student("马红俊", 17);

set.add(s1);
set.add(s2);
set.add(s3);
set.add(s4);
set.add(s5);

System.out.println(set);

}
}
为什么用了 Set 没有去重?
你会发现无序了,
但是还没有去重''

实际上已经去重完成了, 因为 Set 集合保证元素的唯一性依赖 equals()和 hashCode()两个方法, 我们没有在 student 中重写这两个方法, 默认就会使用 Object 类中的 equals(), Object 类下的 equals()方法默认比较的是内存地址, 5 个 studen 都是 new 出来的, 所以指向的内存地址不一样(这个去掉 toString 方法就可以打出来了)

toString方法注释掉之后打印每个student指向内存地址''
toString方法注释掉之后打印每个student指向内存地址''

  • 解决方案: 在 Student 类中重写 equals()和 hashCode
1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
}

@Override
public int hashCode() {
return Objects.hash(name, age);
}

这时候再打印就是去重之后的了

1
set集合: [Student{name='戴沐白', age=16}, Student{name='马红俊', age=17}, Student{name='唐三', age=15}]
双例集合(Map: key, value)
  • Map: HashMap
    双例集合, 元素由键值对构成, key 不可重复, value 可以重复
1
Map<T1, T2> map = new HashMap<>();

写一个 demo, 还是用上面的 Student 类创建学生

map
demo''

如果key值重复,
则key对应最后一次赋值的value''

⚠️ 双例集合无法直接遍历, 只能先获取所有 key 的集合(keySet), 再根据 key 遍历出 value (迭代器,增强 for )
遍历map''
遍历map''

增强 for 循环
1
2
3
4
for (Object obj:list) {
Integer ii = (Integer)obj;
System.out.println(obj);
}

⚠️ 增强 for 循环的底层依赖的是迭代器(Iterator), 可以理解成增强 for 就是迭代器的简写形式

迭代器

为什么需要迭代器?

对过程的重复, 称为迭代. 迭代器是遍历 Collection(单例子)集合的通用方式, 可以在对集合遍历的同时进行添加, 删除等操作.

迭代器的常用方法

  • next(): 返回迭代的下一个元素对象
  • hasNext(): 如果仍有下一个元素, 返回 true
1
2
3
4
5
Iterator it = list.iterator()
while(it.hasNext()) {
String s = (String)it.next();
System.out.println(s);
}

泛型

即泛指任意类型, 又叫参数化类型, 对具体类型的使用起到辅助作用, 类似于方法的参数

集合类型的解释

表示该集合中存放指定类型的元素

1
List<String> list = new ArrayList<>();

好处: 类型安全, 避免了类型转化

Collections 工具类

针对集合进行操作的工具类
成员方法:

  • sort(List)
    根据元素的自然顺序, 将指定列表按升序排序
  • max(Collection)
    返回集合的最大元素
  • reverse(List)
    反转 List 集合元素
  • shuffle(List)
    使用默认的随机源随机置换指定的列表, 也就是可以将数组进行随机排列

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public class Test {
    public static void main(String[] args) {
    List list = new ArrayList();
    list.add(1);
    list.add(2);
    list.add(3);

    Collections.sort(list);
    System.out.println(list); // []

    System.out.println(Collections.max(list));

    Collections.reverse(list);
    System.out.println(list);

    Collections.shuffle(list);
    System.out.println(list);
    }
    }

    // [1, 2, 3]
    // 3
    // [3, 2, 1]
    // [3, 1, 2]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
> 还有更多的方法, 在 java.utils 下面可以找到


### java 中的异常, 顶层父类(Throwable)
- 子类 (Error 报错)
- 子类 (Exception 异常)
处理方式
- 方式一, 捕获, 自己处理
```java
try{
// TODOS...
} catch(Exception e) {
// TODOS...
} finally {
// 写在这里的代码正常情况下一定会执行, 一般是用来释放资源
}
  • 方式二, throw new Error(…)
  • JVM 的默认处理方式: 在控制台打印异常信息, 并终止程序;

什么是 IO 流

I/O, 即输入输出, IO 流指的是数据像连绵的流体一样进行传输

IO 流能做什么

在本地磁盘和网络上操作数据

IO 流的分类

  • 按数据流向(输入流, 输出流)
  • 按操作方式
    1、字节流
  • InputStream: 字节输入流顶层抽象类(FileInputStream(普通的字节输入流), BufferedInputStream(高效字节输入流))
  • OutputStream: 字节输出流顶层抽象类(FileOutputStream(普通字节输出流), BufferOutputStream(高效字节输出流))
    2、字符流
  • Reader: 字符输入流的顶层抽象类(FileReader(普通字符输入流, BufferedReader(高效字符输入流, 也叫字符缓冲输入流)))
  • Writer: 字符输出流的顶层抽象类(FileWrite(普通字符输出流), BufferedWriter(高效字符输出流, 也叫字符缓冲输出流))

    File 类

反射

什么是反射?

在程序运行过程中分析类的一种能力

反射能做什么?

  • 分析类
    1、加载并初始化一个类
    2、查看类的所有属性和方法
  • 查看并使用对象
    1、查看一个对象的所有属性和方法
    2、使用对象的任意属性和方法

    反射的引用场景: 构建通用的工具

demo: 通过反射的方式创建: Student 类型的对象
创建一个 Student 类

1
2
3
4
5
public class Student1 {
public Student1 (){};
public Student1 (String name) {};
private Student1(int age) {};
}

写一个 ConstructorDemos

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import java.lang.reflect.Constructor;
/**
* @author: Hardy
* @date: 2/28/21
* @description:
*/
public class ConstructorDemos {
public static void main(String[] args) throws Exception {
// 1、 获取Student类的字节码文件
Class clazz = Class.forName("com.study.demos.stringDemos.Student1");
// 2、根据获取到的字节码文件对象,获取指定的构造器对象
// 2。1 获取公共的无参构造
Constructor con1 = clazz.getConstructor(); // 没有参数直接写就好了, 因为有异常, 可以直接抛出, NoSuchMethodException(可能瞎传了参数就会报错)
// 2.2 获取公共的带参构造
Constructor con2 = clazz.getConstructor(String.class);
// 2.3 获取私有的带参构造
// Constructor con3 = clazz.getConstructor(int.class);
// 会报错, Exception in thread "main" java.lang.NoSuchMethodException: com.study.demos.stringDemos.Student1.<init>(int), 因为getConstructor只能获取公共的构造
// 2.4 所以要用获取私有构造函数的方法
Constructor con3 = clazz.getDeclaredConstructor(int.class);

System.out.println(con1);
System.out.println(con2);
System.out.println(con3); // 这个会报错,

// 输出:
// public com.study.demos.stringDemos.Student1()
// public com.study.demos.stringDemos.Student1(java.lang.String)
// private com.study.demos.stringDemos.Student1(int)
}
}

创建一个新的学生

1
2
3
4
5
6
7
Student1 stu = (Student1) con2.newInstance("Hardy");
System.out.println(con2.getName());
System.out.println(stu);

// 输出:
// com.study.demos.stringDemos.Student1
// com.study.demos.stringDemos.Student1@6e0be858

调用类成员的方法

给学生类增加两个公共方法

1
2
3
4
5
6
7
8
9
10
11
12
13
public void method1 () {
System.out.println("调用了空参方法");
}
public void method2 (String arg) {
System.out.println("调用了带参方法: " + arg);
}
private void methodPri1 () {
System.out.println("调用了空参私有方法");
}
private void methodPri2 (int a, int b) {
int count = a+b;
System.out.println("调用了私有的带参方法"+a+"+"+b+"="+ count);
}

在 demo 中去调用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//反射的构造创建一个学生类
Student1 stu = (Student1) con2.newInstance("Hardy");
// 1、声明公共方法
Method method1 = clazz.getMethod("method1");
Method method2 = clazz.getMethod("method2", String.class);

// 2、声明私有方法
Method methodPri1 = clazz.getDeclaredMethod("methodPri1");
Method methodPri2 = clazz.getDeclaredMethod("methodPri2", int.class, int.class);

// 3、调用公共方法
method1.invoke(stu);
method2.invoke(stu, "hardy");

// 4、调用私有方法

// 到这你以为可以像上面一样直接跑就可以跑出来了吗, 其实事实上会报错
// 报错信息: Exception in thread "main" java.lang.IllegalAccessException: Class com.study.demos.stringDemos.ConstructorDemos can not access a member of class com.study.demos.stringDemos.Student1 with modifiers "private"
// at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
// 因为是私有方法, 所以在调用方法之前需要开启暴力反射, 也就是setAccessible() true开启, false关闭
methodPri1.setAccessible(true); // 开启暴力反射
methodPri1.invoke(stu);
methodPri2.setAccessible(true);
methodPri2.invoke(stu, 1,2);


// 输出
// 调用了空参方法
// 调用了带参方法: hardy
// 调用了空参私有方法
// 调用了私有的带参方法1+2=3

获取完单个方法之后, 来批量获取一下方法

1
2
3
4
Method[] methods = clazz.getMethods();
for (Method method: methods) {
System.out.println(method);
}

输出:

1
2
3
4
5
6
7
8
9
10
11
public void com.study.demos.stringDemos.Student1.method1()
public void com.study.demos.stringDemos.Student1.method2(java.lang.String)
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()

因为所有的类都直接或者间接继承于 Object, 所以会将 Object 上的一些方法也获取到了, 如果只需要获取 Student 下的方法, 用 getDeclearMethods

1
2
3
4
Method[] methods = clazz.getDeclaredMethods();
for (Method method: methods) {
System.out.println(method);
}

输出:

1
2
3
4
private void com.study.demos.stringDemos.Student1.methodPri2(i nt,int)
public void com.study.demos.stringDemos.Student1.method2(java.lang.String)
private void com.study.demos.stringDemos.Student1.methodPri1()
public void com.study.demos.stringDemos.Student1.method1()

获取成员变量是 Field, Fileds