一、泛型
1.泛型的概念
是一种在编程中用来增加代码的灵活性和安全性的机制。它允许我们在类、接口或方法的定义中使用参数化类型,即将类型作为参数传入,以便在使用时动态确定具体的类型。
2.泛型的作用
通过使用泛型,我们可以编写通用的代码,适用于多种数据类型。这样可以减少重复编写相似代码的工作量,提高代码的复用性和可维护性。
在使用泛型时,我们可以指定具体的类型参数,比如List<String>表示一个存储字符串的列表,Map<Integer, String>表示一个存储整数与字符串对应关系的映射表。通过这种方式,我们可以在编译时期进行类型检查,避免了在运行时出现类型转换错误的风险。
3.泛型的类型
① 类型参数化:通过在类或接口的定义中指定一个或多个类型参数,使得这些类型参数可以在类或接口中被使用,实现参数化的灵活性。例如,class MyClass<T>
中的T
就是类型参数。
// 假设有一个类Pair表示一个键值对,我们可以用泛型来表示键和值的类型
public class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
}
// 实例化
Pair<String, Integer> pair = new Pair<>("age", 18);
② 泛型方法:在方法的定义中使用泛型,使得方法的参数、返回值或局部变量可以使用泛型类型。泛型方法可以在普通类中定义,也可以在泛型类中定义。例如,public <T> T myMethod(T t)
就是一个泛型方法,其中的<T>
表示类型参数。
// 假设有一个类Utils,其中有一个方法printArray可以打印任意类型的数组,我们可以使用泛型方法来实现
public class Utils {
// 泛型方法
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.print(element + " ");
}
System.out.println();
}
}
// 实例化
Integer[] intArray = { 1, 2, 3, 4, 5 };
Utils.printArray(intArray);
③ 泛型类:在类的定义中使用泛型,使得类的字段、方法、构造函数等都可以使用泛型类型。泛型类可以直接使用,也可以作为父类或接口被其他类或接口继承或实现。例如,class MyClass<T>
就是一个泛型类。
// 假设有一个类Box表示一个存放任意类型对象的盒子,我们可以使用泛型来表示盒子中对象的类型
public class Box<T> {
private T object;
public Box(T object) {
this.object = object;
}
public T get() {
return object;
}
public void set(T object) {
this.object = object;
}
}
// 实例化
Box<String> box = new Box<>("hello");
String str = box.get();
④ 通配符(Wildcard):通配符表示未知类型,可用于限制泛型的范围或限制传入的参数类型。有上界通配符(? extends T)和下界通配符(? super T)两种形式。例如,List<? extends Number>
表示一个元素类型是Number或Number的子类的列表,List<? super Integer>
表示一个元素类型是Integer或Integer的父类的列表。
// 假设有一个类MyList表示一个列表,其中包含一些元素,我们想要实现一个方法printList,该方法可以打印任意类型的列表中的元素,不过需要限制列表中的元素必须是某种类型或其子类
public class MyList<T> {
private List<T> list;
public MyList(List<T> list) {
this.list = list;
}
// 打印任意类型的列表中的元素
public void printList(List<? extends Number> list) {
for (Number n : list) {
System.out.print(n + " ");
}
System.out.println();
}
}
// 实例化
List<Integer> intList = Arrays.asList(1, 2, 3);
MyList<Integer> myList = new MyList<>(intList);
myList.printList(intList);
二、反射
1.反射的概念
指在程序运行时动态地获取和操作类的信息,包括类名、方法名、属性等。反射机制使得Java语言具有了较高的灵活性和可扩展性,但也会增加代码的复杂性和运行效率。
2.反射的应用场景
反射机制的应用场景包括但不限于以下几个方面:
① 动态加载类:在编码过程中,如果无法确定需要使用哪个类,可以使用反射机制来动态加载类。这样可以在程序运行时根据需要动态地加载类,提高程序的灵活性和可扩展性。
② 调用对象的方法和访问对象的属性:通过反射机制,我们可以在程序运行时获取对象的方法和属性等信息,并且可以动态地调用对象的方法和访问对象的属性。这样可以在程序运行时根据需要动态地修改对象的状态,提高程序的灵活性和可扩展性。
③ 编写通用代码:通过反射机制,我们可以编写一些通用的代码,可以适用于任意类型的对象。例如,可以编写一个通用的日志记录工具,可以记录任意类型的对象的信息。
④实现注解处理器:通过反射机制,我们可以实现注解处理器,可以在编译期和运行期对注解进行解析和处理。这样可以让我们在程序中使用自定义注解来实现一些自动化操作,提高开发效率。
三、注解
1.注解的概念
是Java语言中的一种元数据(Metadata)。它是一种用来对程序代码进行标记的特殊标记符号,可以通过反射机制获取这些标记信息,并根据标记信息执行相应的操作。注解只有被解析之后才会生效。
注解在源代码中以特殊的形式出现,以@
符号开头,跟在@
后面的是注解类型的名称。例如,常见的注解有@Override
、@Deprecated
、@SuppressWarnings
等
2.注解的作用
① 提供编译时检查:通过自定义注解,可以在编译时对代码进行静态检查,发现潜在的错误或者不合规范的代码。
② 实现自动化处理:通过自定义注解处理器,在编译时或者运行时自动地对代码进行处理,生成额外的代码或者执行特定的操作。
③ 生成文档:通过自定义注解,可以为代码添加一些额外的文档信息,然后使用工具生成文档,方便其他开发人员阅读和理解代码。
④ 实现框架扩展:通过自定义注解,可以为框架提供扩展点,让用户可以通过注解来配置和定制框架的行为。
⑤ 与其他框架集成:通过使用注解,可以与其他框架进行集成,实现不同框架间的交互和协作。
3.注解的解析方法
① 编译期直接扫描:在编译Java代码时,编译器会扫描并处理注解。例如,@Override
注解用于检测方法是否重写了父类的方法。编译器在编译阶段会检查标记了@Override
注解的方法是否存在合法的重写关系。
② 运行期通过反射处理:一些框架中的注解(例如Spring框架中的@Value
、@Component
等)需要在运行时通过反射机制来处理。在程序运行时,框架会使用反射读取注解信息,并根据注解中的属性值执行相应的操作,例如将带有@Value
注解的字段注入对应的值,或者根据@Component
注解创建对象并进行依赖注入。
四、I/O(Input/Output)
1.I/O的概念
是指计算机系统与外部环境(包括用户、设备和文件等)进行数据交换的过程。
输入(Input):指从外部环境(例如键盘、鼠标、文件等)向计算机系统传递数据的过程。
输出(Output):指将计算机系统中的数据传递给外部环境的过程。
2.常见输入输出类
在Java中,使用java.io
包提供的类和接口来实现输入和输出操作。常用的输入和输出类包括:
① InputStream
和OutputStream
:字节流输入和输出类,用于读取和写入字节数据。
② Reader
和Writer
:字符流输入和输出类,用于读取和写入字符数据。
③ File
:表示文件或目录的类,用于获取文件信息、创建、删除和重命名文件等。
④ RandomAccessFile
:随机访问文件的类,可以读取和写入文件中的任意位置。
⑤ BufferedReader
和BufferedWriter
:提供缓冲功能的字符流类,可以提高读写效率。
⑥ Scanner
:用于扫描和解析文本数据的类,可以方便地从各种输入源中读取数据。
3.Java IO流分类
① 字节流:以字节为单位进行读写操作,适用于处理二进制数据或者无需考虑字符编码的情况。主要的字节流类有:InputStream(
输入字节流的抽象基类,用于读取字节数据);
OutputStream(
输出字节流的抽象基类,用于写入字节数据)
。具体实现类包括FileInputStream
、FileOutputStream
、ByteArrayInputStream
、ByteArrayOutputStream
等
② 字符流:以字符为单位进行读写操作,适用于处理文本数据,能够自动处理字符编码问题。主要的字符流类有:Reader(
输入字符流的抽象基类,用于读取字符数据);Writer(
输出字符流的抽象基类,用于写入字符数据)。
具体实现类包括FileReader
、FileWriter
、BufferedReader
、BufferedWriter。
4.三种常见的I/O模型
BIO(Blocking I/O):同步阻塞I/O模型。在BIO模型中,每个客户端请求都会创建一个独立的线程来进行处理。当客户端连接请求到达时,服务器线程accept()方法会阻塞,直到有新的连接触发后再创建一个线程来处理该连接。BIO模型适用于连接数较少且处理时间短的情况,例如传统的Web应用程序中的HTTP请求响应。
NIO(Non-Blocking I/O):同步非阻塞I/O模型。在NIO模型中,服务器线程不再阻塞在accept()方法上,而是轮询所有连接的状态,只处理已经就绪的连接。NIO模型使用单线程就可以同时处理多个客户端连接,因此可以支持更高的并发性和吞吐量。NIO适用于需要处理大量并发连接或者需要进行高速传输的场景,例如实时通信、物联网等。
AIO(Asynchronous I/O):异步I/O模型。在AIO模型中,操作系统完成I/O操作后会通知应用程序,应用程序只需要负责数据处理即可,无需阻塞等待I/O操作的完成。AIO模型适用于需要处理大量I/O操作的场景,例如文件上传/下载、多媒体处理等。
虽然NIO相对于BIO和AIO有很大的性能优势,但是使用NIO编写网络应用程序的难度比较大,需要处理复杂的事件驱动模型。为了解决这个问题,Java 7引入了新的AsynchronousSocketChannel类,该类提供了更直接、易于使用的异步I/O方式,可以进一步提高并发和吞吐量。