六:File类与IO流:
这里给出三种常见的初始化方法:
File file = new File("C:/example/test.txt");
-
这种方法用于创建一个文件对象,该文件对象表示指定路径的文件或目录。例如:
File file = new File("C:/example/test.txt");
表示创建一个指向 “C:/example/test.txt” 的文件对象。
2. 通过文件路径和文件名初始化:
File file = new File("C:/example", "test.txt");
-
这种方法用于创建一个文件对象,该文件对象表示指定目录中的指定文件。例如:
File file = new File("C:/example", "test.txt");
表示创建一个指向 “C:/example/test.txt” 的文件对象。 -
这里有两个参数,第一个一定是文件目录,可以是String类型,也可以是文件类型的参数,第二个可以是文件,也可以是文件目录。
3. 通过URI初始化:
URI uri = new URI("file:///C:/example/test.txt");
File file = new File(uri);
-
这种方法用于创建一个文件对象,该文件对象表示指定URI的文件或目录。例如:
URI uri = new URI("file:///C:/example/test.txt"); File file = new File(uri);
表示创建一个指向 “C:/example/test.txt” 的文件对象。
这些方法可用于创建Java文件类的实例并指定文件的路径、名称或URI。
要得到Java文件的相对路径和绝对路径,可以使用以下方法:
String relativePath = new File("yourFile.java").getPath();
2. 绝对路径:绝对路径是文件在文件系统中的完整路径。可以使用以下代码来获取绝对路径:
String absolutePath = new File("yourFile.java").getAbsolutePath();
以上代码中的"yourFile.java"是你要获取路径的Java文件名。通过这两种方法,你可以得到Java文件的相对路径和绝对路径。
File类的方法:
(1):基本方法:
举例:
import java.io.File;
public class ListFilesExample {
public static void main(String[] args) {
File directory = new File("path/to/directory");
String[] files = directory.list();
for (String file : files) {
System.out.println(file);
}
}
}
import java.io.File;
public class ListFilesExample {
public static void main(String[] args) {
File directory = new File("path/to/directory");
File[] files = directory.listFiles();
for (File file : files) {
System.out.println(file.getName());
}
}
}
当然,以下是完整的输出示例:
假设目录下有文件 “file1.txt” 和子目录 “subdir”,使用上述两种方法的输出将如下所示:
file1.txt
subdir
(2):命名:
Java中的renameTo
方法用于重命名文件或移动文件到另一个目录。它是File类的一个方法,接受一个File对象作为参数,表示文件的新路径或新名称。如果重命名或移动成功,renameTo
方法将返回true;否则,返回false。
如果你只想改变文件的名称而不改变其路径,你可以使用 renameTo()
方法。以下是一个示例,展示如何仅仅重命名文件而不移动它:
import java.io.File;
public class FileRenameExample {
public static void main(String[] args) {
File originalFile = new File("F:\\oldfile.txt"); // 原始文件的路径
File newFile = new File("F:\\newfile.txt"); // 新文件的路径
if (originalFile.renameTo(newFile)) {
System.out.println("文件已重命名");
} else {
System.out.println("文件无法重命名");
}
}
}
(3):判断:
(4):创建、删除功能:
• public boolean createNewFile() :创建文件。若文件存在,则不创建,返回false。
• public boolean mkdir() :创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。
• public boolean mkdirs() :创建文件目录。如果上层文件目录不存在,一并创建。
• public boolean delete() :删除文件或者文件夹 删除注意事项:① Java 中的删除不走回收站。② 要删除一个文件目录,请注意该文件目录内不能包含文件或者文件目录。
练习 :判断指定目录下是否有后缀名为.jpg 的文件。如果有,就输出该文件名称:
//方法 1:
@Test
public void test1(){
File srcFile = new File("d:\\code");
String[] fileNames = srcFile.list();
for(String fileName : fileNames){
if(fileName.endsWith(".jpg")){
System.out.println(fileName);
}
}
}
//方法 2:
@Test
public void test2(){
File srcFile = new File("d:\\code");
File[] listFiles = srcFile.listFiles();
for(File file : listFiles){
if(file.getName().endsWith(".jpg")){
System.out.println(file.getAbsolutePath());
}
}
}
//方法 3:
/*
* File 类提供了两个文件过滤器方法
* public String[] list(FilenameFilter filter)
* public File[] listFiles(FileFilter filter)
*/
@Test
public void test3(){
File srcFile = new File("d:\\code");
File[] subFiles = srcFile.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".jpg");
}
});
for(File file : subFiles){
System.out.println(file.getAbsolutePath());
}
}
拓展:计算大小/删除所有/遍历目录下面所有文件:
以遍历为例子:
import java.io.File;
public class FileTraversal {
public static void main(String[] args) {
File directory = new File("path_to_directory");
traverseDirectory(directory);
}
public static void traverseDirectory(File directory) {
if (directory.isDirectory()) {
File[] files = directory.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
traverseDirectory(file);
} else {
System.out.println(file.getName());
}
}
}
}
}
}
流:
FileReader与FileWriter:
注意:FileReader是输入流,而FileWriter是输出流,输入流是读取数据的,不要搞反了。输入流是从数据源(如文件、网络连接等)读取数据的流,而输出流是向数据目标(如文件、网络连接等)写入数据的流。
当我们使用FileReader来读取文件时,我们创建一个FileReader对象,并使用它来读取文件中的数据。这意味着数据从文件流向我们的程序,因此FileReader是输入流。
另一方面,如果我们想要向文件中写入数据,我们会使用FileWriter,它是输出流。通过FileWriter,我们可以将数据从程序流向文件,因此FileWriter是输出流。
FileReader:
下面是一个简单的示例,演示如何使用FileReader来读取文件中的数据:
import java.io.FileReader;
import java.io.IOException;
public class FileReaderExample {
public static void main(String[] args) {
try {
// 创建一个FileReader对象,指定要读取的文件路径
FileReader fileReader = new FileReader("example.txt");
// 读取文件中的字符数据
int character;
while ((character = fileReader.read()) != -1) {
System.out.print((char) character);
}
// 关闭文件读取流
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
在上面的示例中,我们首先创建了一个FileReader对象,指定要读取的文件路径(“example.txt”)。然后,我们使用while循环和read()方法来逐个读取文件中的字符数据,并将其打印到控制台上。最后,我们关闭了文件读取流。
需要注意的是,在使用FileReader时,需要处理可能抛出的IOException异常。在示例中,我们使用了try-catch块来捕获并处理异常。最好用finally,确保一定会关闭流。可以用ctrl+AIt+T快捷操作。
比较规范的写法:
@Test
public void test2() {
FileReader fr = null;
try {
//1. 创建 File 类的对象,对应着物理磁盘上的某个文件
File file = new File("hello.txt");
//2. 创建 FileReader 流对象,将 File 类的对象作为参数传递到FileReader 的构造器中
fr = new FileReader(file);
//3. 通过相关流的方法,读取文件中的数据
/*
* read():每次从对接的文件中读取一个字符。并将此字符返回。
* 如果返回值为-1,则表示文件到了末尾,可以不再读取。
* */
int data;
while ((data = fr.read()) != -1) {
System.out.println((char) data);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4. 关闭相关的流资源,避免出现内存泄漏
try {
if (fr != null)
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
if (fr != null)
可以防止流没有创建成功的问题。
当然,read()
一个一个读太慢了,可以用其他的方法读取数据:
read(char[] cbuf)
:
是Java中的一个方法,用于从输入流中读取数据到字符数组cbuf中。该方法会尝试从输入流中读取数据,并将读取的数据存储到cbuf数组中,返回实际读取的字符数。如果输入流中没有数据可供读取,则返回-1。
使用该方法时,需要先创建一个字符数组cbuf,并指定要读取的字符数目。然后调用read(cbuf)方法,将数据读取到数组中。通常在循环中使用该方法,直到返回-1表示数据读取完毕。
例如:
@Test
public void test3() {
FileReader fr = null;
try {
//1. 创建 File 类的对象,对应着物理磁盘上的某个文件
File file = new File("hello.txt");
//2. 创建 FileReader 流对象,将 File 类的对象作为参数传递到FileReader 的构造器中
fr = new FileReader(file);
//3. 通过相关流的方法,读取文件中的数据
char[] cbuf = new char[5];
/*
* read(char[] cbuf) : 每次将文件中的数据读入到 cbuf 数组中,并返回读入到数组中的字符的数。
* */
int len; //记录每次读入的字符的个数
while ((len = fr.read(cbuf)) != -1) {
//处理 char[]数组即可
//错误:
// for(int i = 0;i < cbuf.length;i++){
// System.out.print(cbuf[i]);
// }
//错误:
// String str = new String(cbuf);
// System.out.print(str);
//正确:
// for(int i = 0;i < len;i++){
// System.out.print(cbuf[i]);
// }
//正确:
String str = new String(cbuf, 0, len);
System.out.print(str);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4. 关闭相关的流资源,避免出现内存泄漏
try {
if (fr != null)
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
FileWriter:
文件不在,会自动创建。文件如果存在,会根据构造器的声明判断,比如:fw=new FileWriter(file),fw=new FileWriter(file,false),都是覆盖,fw=new FileWriter(file,true)是追加等。
下面是 FileWriter
的一些常用方法:
-
public void write(int c) throws IOException
:将单个字符c
写入文件。 -
public void write(char[] c, int offset, int len)
:将字符数组中从索引offset
开始、长度为len
的一部分写入文件。 -
public void write(String s, int offset, int len)
:将字符串中从索引offset
开始、长度为len
的一部分写入文件。
当然,还有一些其他的方法,比如直接写入String或者char []。
FileInputStream与FileOutStream:
以上均为字节流:
字节流是以字节为单位进行读写操作的流,它主要用于处理二进制数据,如图像、音频、视频等。在Java中,字节流的基本类是InputStream和OutputStream,它们提供了读取和写入字节的方法。
字符流是以字符为单位进行读写操作的流,它主要用于处理文本数据。在Java中,字符流的基本类是Reader和Writer,它们提供了读取和写入字符的方法。
另外,字符流通常会使用指定的字符编码来处理文本数据,而字节流则不会考虑字符编码的问题。因此,在处理文本数据时,通常会使用字符流来避免出现乱码等问题。
也就是说,字符流无法实现图片的拷贝。
举例:
import java.io.*;
public class ImageCopy {
public static void main(String[] args) {
FileInputStream inputStream = null;
FileOutputStream outputStream = null;
try {
File inputFile = new File("input.jpg");
File outputFile = new File("output.jpg");
inputStream = new FileInputStream(inputFile);
outputStream = new FileOutputStream(outputFile);
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, length);
}
System.out.println("图片复制成功");
} catch (IOException e) {
System.out.println("发生IO异常:" + e.getMessage());
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
} catch (IOException e) {
System.out.println("关闭流时发生异常:" + e.getMessage());
}
}
}
}
以上都是节点流,下面来看看几种处理流 :
处理流:
(1):缓冲流:
缓冲流的作用是在数据传输过程中提供一个缓冲区,可以暂时存储数据并进行批量处理,从而提高数据传输的效率和性能。缓冲流可以减少对底层资源的频繁访问,减少I/O操作的次数,减少数据传输的延迟,提高数据传输的速度。
另外,缓冲流还可以提供一种更方便的方式来处理数据,比如可以使用缓冲流的缓冲区来进行数据的预读取和预写入,从而提高数据的读写效率。同时,缓冲流还可以提供一种更灵活的方式来控制数据的传输,比如可以设置缓冲区的大小,调整数据传输的速度,实现数据的流控制等功能。
注意:同一基类具有对应关系。
举例:
import java.io.*;
public class ImageCopyExample {
public static void main(String[] args) throws IOException {
FileInputStream in = null;
FileOutputStream out = null;
try {
File inputFile = new File("input.jpg");
File outputFile = new File("output.jpg");
in = new FileInputStream(inputFile);
out = new FileOutputStream(outputFile);
BufferedInputStream bufferedInput = new BufferedInputStream(in);
BufferedOutputStream bufferedOutput = new BufferedOutputStream(out);
int byteRead;
while ((byteRead = bufferedInput.read()) != -1) {
bufferedOutput.write(byteRead);
}
System.out.println("Image copied successfully.");
} finally {
if ( bufferedInput!= null) {
bufferedInput.close();
}
if (bufferedOutput != null) {
bufferedOutput.close();
}
}
}
}
注意,事实上,就是在后面把字节流的名字改为缓冲流的名字,注意,在进行流的关闭时,要先关闭外层流,再关闭内层流,即先缓冲后字节,注意,外层流关闭会自动关闭内层,所以也可以忽略内层流。
补充:readLine():
readLine()
是 Java 中用于读取文本行的方法。它通常与 BufferedReader
类一起使用。以下是使用 readLine()
方法来读取文件内容的示例代码:
import java.io.*;
public class Main {
public static void main(String[] args) {
try {
BufferedReader in = new BufferedReader(new FileReader("test.log"));
String str;
while ((str = in.readLine()) != null) {
System.out.println(str);
}
// Note: The last value of 'str' will be null.
} catch (IOException e) {
// Handle any exceptions here.
}
}
}
注意,该方法会忽略掉换行,所以如果需要输出显示器或者写入其他文件要加上换行符或者调用BufferedWriter对象的newLine()方法。
补充:flush():
因为内置缓冲区的原因,如果 FileWriter 不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要 flush() 方法了。
• flush() :刷新缓冲区,流对象可以继续使用。
• close():先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。
注意:即便是 flush()方法写出了数据,操作的最后还是要调用 close 方法,释放系统资源。flush相当于把缓冲区已经有的内容全部写入文件,相当于保存了。注意,只用reader类才有该方法。
(2):转换流:
转换流是指将一个流转换成另一个流的过程。在Java中,可以通过InputStreamReader和OutputStreamWriter来进行流的转换。
InputStreamReader可以将字节流转换成字符流,它接受一个字节流作为输入,并根据指定的字符编码将其转换成字符流。这样就可以方便地进行字符的读取和处理。
OutputStreamWriter则是将字符流转换成字节流,它接受一个字符流作为输入,并根据指定的字符编码将其转换成字节流。这样就可以方便地将字符输出到文件或网络中。
通过使用转换流,可以在不改变原有流的情况下,对流进行字符编码的转换,从而方便地进行字符的读写操作。这在处理文件和网络数据时非常有用。
要先解码,再进行编码。
举例:
同理,只用关闭外层即可。
注意,InPutStreamReader,OutPutStreamReader,还可以接受一个字符集参数,在本例中,输出正常,因为IDEA默认就是UTF-8,不需要显式的写出来。
举例:GBK->UTF-8编码的转换:
/**
\* @author
\* @create 9:06
*/
public class InputStreamReaderDemo {
@Test
public void test() {
InputStreamReader isr = null;
OutputStreamWriter osw = null;
try {
isr = new InputStreamReader(new FileInputStream("康师傅的话.txt"),"gbk");
osw = new OutputStreamWriter(newFileOutputStream("C:\\Users\\shkstart\\Desktop\\寄语.txt"),"utf-8");
char[] cbuf = new char[1024];
int len;
while ((len = isr.read(cbuf)) != -1) {
osw.write(cbuf, 0, len);
osw.flush();
}
System.out.println("文件复制完成");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (isr != null)
isr.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (osw != null)
osw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
注意,最初的文件编码方式为GBK,所以只能使用GBK解码。
字符总结:
(3):数据流与对象流:
数据流:
数据流是Java中用于读写基本数据类型和字符串的一种流。它提供了一种方便的方式来读写Java的基本数据类型,如int、double、boolean等,以及字符串。
数据流包括DataInputStream和DataOutputStream两个类。DataOutputStream可以将基本数据类型和字符串写入到输出流中,而DataInputStream可以从输入流中读取基本数据类型和字符串。
使用数据流进行数据的读写时,可以方便地将各种基本数据类型和字符串写入到输出流中,并且可以从输入流中读取这些数据。这样可以方便地在不同的系统之间传输数据,而不用担心数据类型的兼容性问题。
但在实际中,我们更喜欢用对象流,因为它还可以读取引用类型变量。
对象流:
对象流是Java中用于读写对象的一种流。它可以将对象以二进制的形式进行读写,可以用于在网络上传输对象,或者将对象保存到文件中。
对象流包括ObjectInputStream和ObjectOutputStream两个类。ObjectOutputStream可以将对象写入到输出流中,而ObjectInputStream可以从输入流中读取对象。
使用对象流进行对象的读写时,需要注意对象的序列化和反序列化。对象需要实现Serializable接口,以便可以被序列化成二进制形式,并且可以被反序列化还原成对象。
对象流的使用非常灵活,可以用于传输各种类型的对象,包括自定义的对象。它为Java中对象的读写提供了方便的方式,同时也可以用于实现对象的持久化和网络传输。
以下是一个简单的Java序列化和反序列化的示例。这个例子中,我们有一个名为Person
的类,它实现了Serializable
接口。然后我们创建了一个Person
对象,将其序列化到一个文件中,然后再从该文件中反序列化该对象。
import java.io.*;
class Person implements Serializable {
public String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
public class Main {
public static void main(String[] args) {
Person person = new Person("John Doe", 30);
// 序列化
try {
FileOutputStream fileOut = new FileOutputStream("./person.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(person);
out.close();
fileOut.close();
System.out.printf("Serialized data is saved in ./person.ser");
} catch (IOException i) {
i.printStackTrace();
}
// 反序列化
Person deserializedPerson = null;
try {
FileInputStream fileIn = new FileInputStream("./person.ser");
ObjectInputStream in = new ObjectInputStream(fileIn);
deserializedPerson = (Person) in.readObject();
in.close();
fileIn.close();
} catch (IOException i) {
i.printStackTrace();
return;
} catch (ClassNotFoundException c) {
System.out.println("Person class not found");
c.printStackTrace();
return;
}
System.out.println("Deserialized Person...");
System.out.println("Name: " + deserializedPerson.name);
System.out.println("Age: " + deserializedPerson.age);
}
}
ObjectOutputStream out = new ObjectOutputStream(fileOut);
这行代码只是创建了一个 ObjectOutputStream
对象,这个对象可以用来将Java对象序列化并写入到一个输出流中。在这个例子中,输出流是一个文件输出流,它指向 ./person.ser
文件。
真正的序列化过程发生在 out.writeObject(person);
这行代码执行时。这行代码将 person
对象序列化,并将序列化后的数据写入到 ObjectOutputStream
关联的输出流中,也就是 ./person.ser
文件。
所以,当 out.writeObject(person);
这行代码执行完成后,person
对象才被序列化并写入到 ./person.ser
文件中。在这之前,./person.ser
文件要么不存在,要么是空的(或者包含了之前写入的数据)。
注意,ObjectOutputStream也是输出流,所以也有flush方法,道理同上。
注意:类中静态的属性或者是声明为transient的属性,不会序列化。
(4):其他流:
4.1.标准输入,输出流:
如图:
即不用Scanner方法也实现了读入。
4.2.打印流:
如图:
即向io.txt文件中写入,注意,调用了setOut方法,让输出不在控制台上面显示,而是直接写进文件中。
apache-common包:
Apache Commons是一个可重复使用的Java组件集合,为Java应用程序提供常用功能。Apache Commons项目是Apache软件基金会的一部分,旨在提供高质量、可重复使用的Java组件,这些组件是免费提供的。
Apache Commons包括各种组件,如集合、配置、IO、语言、数学等。这些组件提供了常见编程问题的解决方案,并帮助开发人员编写更干净、更高效的代码。
Apache Commons中的组件通常被打包成JAR(Java Archive)文件,以便于在Java应用程序中使用。JAR文件是一种用于打包Java类、资源和元数据的标准文件格式。当开发人员需要使用Apache Commons中的组件时,他们通常会将相应的JAR文件添加到他们的项目中,并在代码中引用这些组件。
因此,Apache Commons包实际上是一组JAR文件的集合,每个JAR文件包含一个或多个相关的组件。开发人员可以通过将这些JAR文件添加到他们的Java项目中,来使用Apache Commons中的组件,从而为他们的应用程序提供各种常用功能和解决方案。
七:网络编程:
7.1.软件架构:
客户端-服务器(CS)架构是一种传统的架构模式,其中客户端应用程序通过网络连接到服务器,客户端负责处理用户界面和用户输入,而服务器负责处理业务逻辑和数据存储。这种架构适用于需要复杂业务逻辑和数据处理的应用,如企业级应用、数据库系统等。
浏览器-服务器(BS)架构是一种Web应用程序的架构模式,其中浏览器作为客户端,通过HTTP协议向服务器请求页面和资源,服务器端负责处理业务逻辑和数据存储,并将结果以HTML等格式返回给客户端。这种架构适用于互联网应用、电子商务网站等Web应用。
两种架构的主要区别在于CS架构更加灵活和复杂,适用于需要处理复杂业务逻辑和数据的应用,而BS架构更加简单和易于部署,适用于互联网应用和Web网站。同时,CS架构需要安装客户端应用程序,而BS架构只需要浏览器即可访问应用。
7.2.计算机网络:
计算机网络是指将多台计算机通过通信设备连接起来,使它们之间可以相互传输数据和共享资源的系统。计算机网络可以是局域网(LAN)、城域网(MAN)、广域网(WAN)或者因特网(Internet)。它们通过各种通信协议和技术来实现数据的传输和通信。计算机网络的应用非常广泛,包括文件共享、打印共享、电子邮件、网上购物、在线游戏等。
三大要素:
7.2.1.IP地址:
IP是Internet Protocol的缩写,是互联网协议的一种。它是一种用于在网络上进行数据传输的协议,通过它,数据包可以在网络上进行传输。IP地址是用来标识网络上的设备的唯一地址,类似于人类的家庭地址。
IP地址根据不同的特征可以分为IPv4和IPv6两种类型。IPv4地址是32位的地址,通常用四个十进制数表示,每个数的取值范围是0-255,例如192.168.1.1。而IPv6地址是128位的地址,通常用八组十六进制数表示,每组数之间用冒号分隔,例如2001:0db8:85a3:0000:0000:8a2e:0370:7334。IPv6地址是128位的,128位等于16字节,因为1字节等于8位,所以128位就是16字节。
根据IP地址的分配方式,IP地址又可以分为公网IP(万维网)和私网IP(局域网)。公网IP是可以直接被互联网访问的IP地址,而私网IP是在局域网内使用的IP地址,不能直接被互联网访问。
补充:本地回路地址:127.0.0.1。
补充:域名:
7.2.2.端口号:
端口号是用于标识不同网络应用程序的数字标识符。在计算机网络中,端口号被用于区分不同的网络应用程序或服务,以便数据包可以被正确地路由到目标应用程序。
端口号的范围是从0到65535,其中0到1023是被称为“系统端口”或“众所周知的端口”,通常用于一些常见的网络服务,比如HTTP(端口号80)、FTP(端口号21)、SSH(端口号22)等。而1024到49151之间的端口号被称为“注册端口”,用于一些常见的应用程序。而49152到65535之间的端口号则被称为“动态或私有端口”,通常用于客户端应用程序或临时服务。
端口号的作用是确保数据包能够被正确地传送到目标应用程序,从而实现网络通信和数据交换。在网络通信中,发送端和接收端的应用程序需要使用相同的端口号才能进行通信。因此,端口号在网络通信中起着非常重要的作用。
7.2.3.网络通信协议:
网络通信协议是计算机网络中用于在不同设备之间传输数据的规则和约定。它定义了数据的格式、传输方式、错误检测和纠正等方面的规范,以确保不同设备之间能够相互通信。
常见的网络通信协议包括TCP/IP协议、HTTP协议、FTP协议、SMTP协议、POP3协议等。其中,TCP/IP协议是最为重要和基础的网络通信协议,它是互联网的基础协议,用于在不同设备之间进行数据传输和通信。
7.3.InetAddress:
getByName()
和getLocalHost()
是Java中两个不同的方法,它们在不同的类和场景中使用。
-
getByName()
方法- 这是
java.net.InetAddress
类中的一个方法,用于获取与指定主机名或IP地址对应的InetAddress
对象。 - 该方法可以接受一个主机名或IP地址字符串作为参数,并返回一个表示该地址的
InetAddress
对象。 - 如果传入的参数为null,则返回表示本地主机地址的
InetAddress
对象。
- 这是
try {
InetAddress ia = InetAddress.getByName("46.21.29.40");
System.out.println(ia.getHostName());
} catch (Exception ex) {
System.err.println(ex);
}
注意,也可以传入域名。
2.getLocalHost()
方法:这个方法用于获取表示本地主机的InetAddress
对象。例如:
InetAddress localHost = InetAddress.getLocalHost();
System.out.println("本机的IP地址:" + localHost.getHostAddress());
再来看看两个常用方法:
getHostName()
和 getHostAddress()
是 Java 中 InetAddress
类的两个方法。
getHostName()
方法用于获取主机名,例如,如果我们有一个InetAddress
对象ip
,我们可以通过调用ip.getHostName()
来获取该主机的主机名。getHostAddress()
方法用于获取 IP 地址,同样,如果我们有一个InetAddress
对象ip
,我们可以通过调用ip.getHostAddress()
来获取该主机的 IP 地址。
import java.net.InetAddress;
import java.net.UnknownHostException;
public class Address {
public static void main(String[] args) {
InetAddress ip;
try {
ip = InetAddress.getLocalHost();
String localname = ip.getHostName(); // 获取本机名
String localip = ip.getHostAddress(); // 获取本机地址
System.out.println("本机名:" + localname);
System.out.println("本机地址:" + localip);
} catch (UnknownHostException e) {
System.out.println("主机不存在或网络连接错误");
e.printStackTrace();
}
}
}
7.4.传输层协议:TCP与UDP协议:
TCP(传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层协议。它提供了数据传输的可靠性和顺序性,通过三次握手建立连接和四次挥手断开连接来确保数据的可靠传输。TCP还具有拥塞控制和流量控制的功能,可以根据网络状况调整数据传输的速率,以保证网络的稳定性和效率。TCP适用于对数据传输可靠性要求较高的应用场景,如网页浏览、文件传输等。
UDP(用户数据报协议)是一种面向无连接的、不可靠的传输层协议。它通过数据包的形式进行数据传输,不保证数据的可靠性和顺序性。UDP的优点是传输速度快,适用于对数据传输实时性要求较高的应用场景,如音视频传输、实时游戏等。但由于UDP不提供可靠性保证,因此在数据传输过程中可能会丢包或出现乱序,需要应用层自行处理。UDP适用于对数据传输实时性要求较高、对数据丢失和乱序容忍度较高的应用场合。
7.5.Socket类:
7.5.1.Socket 相关类 API:
7.5.1.1.ServerSocket 类:
//ServerSocket 类的构造方法:
• ServerSocket(int port) :创建绑定到特定端口的服务器套接字。
//ServerSocket 类的常用方法:
• Socket accept():侦听并接受到此套接字的连接。
7.5.1.2.Socket 类
//Socket 类的常用构造方法:
• public Socket(InetAddress address,int port):创建一个流套接字并将其连接到指定IP地址的指定端口号。
• public Socket(String host,int port):创建一个流套接字并将其连接到指定主机上的指定端口号。
//Socket 类的常用方法:
• public InputStream getInputStream():返回此套接字的输入流,可以用于接收消息
• public OutputStream getOutputStream():返回此套接字的输出流,可以用于发送消息
• public InetAddress getInetAddress():此套接字连接到的远程 IP 地址;如果套接字是未连接的,则返回 null。
• public InetAddress getLocalAddress():获取套接字绑定的本地地址。
• public int getPort():此套接字连接到的远程端口号;如果尚未连接套接字,则返回0。
• public int getLocalPort():返回此套接字绑定到的本地端口。如果尚未绑定套接字,则返回 -1。
• public void close():关闭此套接字。套接字被关闭后,便不可在以后的网络连接中使用(即无法重新连接或重新绑定)。需要创建新的套接字对象。 关闭此套接字也将会关闭该套接字的 InputStream 和 OutputStream。
• public void shutdownInput():如果在套接字上调用 shutdownInput() 后从套接字输入流读取内容,则流将返回 EOF(文件结束符)。 即不能在从此套接字的输入流中接收任何数据。
• public void shutdownOutput():禁用此套接字的输出流。对于 TCP 套接字,任何以前写入的数据都将被发送,并且后跟 TCP 的正常连接终止序列。 如果在套接字上调用 shutdownOutput() 后写入套接字输出流,则该流将抛出 IOException。 即不能通过此套接字的输出流发送任何数据。
注意:先后调用 Socket 的 shutdownInput()和 shutdownOutput()方法,仅仅关闭了输入流和输出流,并不等于调用 Socket 的 close()方法。在通信结束后,仍然要调用 Scoket 的 close()方法,因为只有该方法才会释放 Socket 占用的资源,比如占用的本地端口号等。
7.6.TCP网络编程:
客户端程序包含以下四个基本的步骤 :
• 创建 Socket :根据指定服务端的 IP 地址或端口号构造 Socket 类对象。若服务器端响应,则建立客户端到服务器的通信线路。若连接失败,会出现异常。
• 打开连接到 Socket 的输入/ 出流: 使用 getInputStream()方法获得输入流,使用getOutputStream()方法获得输出流,进行数据传输
• 按照一定的协议对 Socket 进行读/ 写操作:通过输入流读取服务器放入线路的信息(但不能读取自己放入线路的信息),通过输出流将信息写入线路。
• 关闭 Socket :断开客户端到服务器的连接,释放线路
服务器端程序包含以下四个基本的 步骤:
• 调用 ServerSocket(int port) :创建一个服务器端套接字,并绑定到指定端口上。用于监听客户端的请求。
• 调用 accept() :监听连接请求,如果客户端请求连接,则接受连接,返回通信套接字对象。
• 调用 该 Socket 类对象的 getOutputStream() 和 getInputStream () :获取输出流和输入流,开始网络数据的发送和接收。
• 关闭 Socket 对象:客户端访问结束,关闭通信套接字。
举例:客户端发送内容给服务端,服务端将内容打印到控制台上。
客户端代码:
public class Client {
public static void main(String[] args) throws Exception {
// 1、准备 Socket,连接服务器,需要指定服务器的 IP 地址和端口号
Socket socket = new Socket("127.0.0.1", 8888);
// 2、获取输出流,用来发送数据给服务器
OutputStream out = socket.getOutputStream();
// 发送数据
out.write("lalala".getBytes());
//会在流末尾写入一个“流的末尾”标记,对方才能读到-1,否则对方的读取方法会一致阻塞
socket.shutdownOutput();
//3、获取输入流,用来接收服务器发送给该客户端的数据
InputStream input = socket.getInputStream();
// 接收数据
byte[] data = new byte[1024];
StringBuilder s = new StringBuilder();
int len;
while ((len = input.read(data)) != -1) {
s.append(new String(data, 0, len));
}
System.out.println("服务器返回的消息是:" + s);
//4、关闭 socket,不再与服务器通信,即断开与服务器的连接
//socket 关闭,意味着 InputStream 和 OutputStream 也关闭了
socket.close();
}
}
注意,socket.shutdownOutput();
必须要有这一步,防止客户端仍在继续尝试发送数据。
服务端代码:
public class Server {
public static void main(String[] args)throws Exception {
//1、准备一个 ServerSocket 对象,并绑定 8888 端口
ServerSocket server = new ServerSocket(8888);
System.out.println("等待连接....");
//2、在 8888 端口监听客户端的连接,该方法是个阻塞的方法,如果没有客户端连接,将一直等待
Socket socket = server.accept();
InetAddress inetAddress = socket.getInetAddress();
System.out.println(inetAddress.getHostAddress() + "客户端连接成功!!");
//3、获取输入流,用来接收该客户端发送给服务器的数据
InputStream input = socket.getInputStream();
//接收数据
byte[] data = new byte[1024];
StringBuilder s = new StringBuilder();
int len;
while ((len = input.read(data)) != -1) {
s.append(new String(data, 0, len));
}
System.out.println(inetAddress.getHostAddress() + "客户端发送的消息是:" + s);
//4、获取输出流,用来发送数据给该客户端
OutputStream out = socket.getOutputStream();
//发送数据
out.write("欢迎登录".getBytes());
out.flush();
//5、关闭 socket,不再与该客户端通信
//socket 关闭,意味着 InputStream 和 OutputStream 也关闭了
socket.close();
//6、如果不再接收任何客户端通信,可以关闭 ServerSocket
server.close();
}
}
注意:可以通过socket.getInetAddress().getHostAddress()获取到是谁在进行连接。
注意,有些边读边输出的方法可能会出现乱码,比如字节流存入数组对原来的文本有截断,则会出现乱码,可以通过ByteArrayOutputStream
解决,代码如下:
byte[] data = new byte[1024];
int len;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while ((len = input.read(data)) != -1) {
baos.write(data,0,len);
}
System.out.println(inetAddress.getHostAddress() + "客户端发送的消息是:" + baos.toString());
相当于把每部分写入baos中,最后一起输出。
7.7.UDP网络编程:
UDP(User Datagram Protocol,用户数据报协议):是一个无连接的传输层协议、提供面向事务的简单不可靠的信息传送服务,类似于短信。
发送端程序包含以下四个基本的步骤:
• 创建 DatagramSocket :默认使用系统随机分配端口号。
• 创建 DatagramPacket:将要发送的数据用字节数组表示,并指定要发送的数据长度,接收方的IP 地址和端口号。
• 调用 该 DatagramSocket 类对象的 send 方法 :发送数据报 DatagramPacket 对象。
• 关闭 DatagramSocket 对象:发送端程序结束,关闭通信套接字。
接收端程序包含以下四个基本的步骤 :
• 创建 DatagramSocket :指定监听的端口号。
• 创建 DatagramPacket:指定接收数据用的字节数组,起到临时数据缓冲区的效果,并指定最大可以接收的数据长度。
• 调用 该 DatagramSocket 类对象的 receive 方法 :接收数据报 DatagramPacket 对象。
• 关闭 DatagramSocket :接收端程序结束,关闭通信套接字。
举例:
发送端:
DatagramSocket ds = null;
try {
ds = new DatagramSocket();
byte[] by = "hello,atguigu.com".getBytes();
DatagramPacket dp = new DatagramPacket(by, 0, by.length,
InetAddress.getByName("127.0.0.1"), 10000);
ds.send(dp);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (ds != null)
ds.close();
}
接收端:
DatagramSocket ds = null;
try {
ds = new DatagramSocket(10000);
byte[] by = new byte[1024*64];
DatagramPacket dp = new DatagramPacket(by, by.length);
ds.receive(dp);
String str = new String(dp.getData(), 0, dp.getLength());
System.out.println(str + "--" + dp.getAddress());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (ds != null)
ds.close();
}
7.8.URL网络编程:
URL(Uniform Resource Locator):统一资源定位符,它表示 Internet 上某一资源的地址。
• 通过 URL 我们可以访问 Internet 上的各种网络资源,比如最常见的 www,ftp 站点。浏览器通过解析给定的 URL 可以在网络上查找相应的文件或其他资源。
• URL 的基本结构由 5 部分组成:<传输协议>://<主机名>:<端口号>/<文件名>#片段名?参数列表
• 例如:http://192.168.1.100:8080/helloworld/index.jsp#a?username=shkstart&password=123
URL 类常用方法:
一个 URL 对象生成后,其属性是不能被改变的,但可以通过它给定的方法来获取这些属性:
• public String getProtocol( ) 获取该 URL 的协议名
• public String getHost( ) 获取该 URL 的主机名
• public String getPort( ) 获取该 URL 的端口号
• public String getPath( ) 获取该 URL 的文件路径
• public String getFile( ) 获取该 URL 的文件名
• public String getQuery( ) 获取该 URL 的查询名
八:反射机制:
Java 程序中,所有的对象都有两种类型:
编译时类型和运行时类型,而很多时候对象的编译时类型和运行时类型不一致。 Object obj = new String(“hello”);obj.getClass()。
例如:某些变量或形参的声明类型是 Object 类型,但是程序却需要调用该对象运行时类型的方法,该方法不是 Object 中的方法,那么如何解决呢?
解决这个问题,有两种方案:
方案 1:在编译和运行时都完全知道类型的具体信息,在这种情况下,我们可以直接先使用 instanceof 运算符进行判断,再利用强制类型转换符将其转换成运行时类型的变量即可。
方案 2:编译时根本无法预知该对象和类的真实信息,程序只能依靠运行时信息来发现该对象和类的真实信息,这就必须使用反射。
反射概述
Reflection(反射)是被视为动态语言的关键,反射机制允许程序在运行期间借助于 Reflection API 取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
反射可以打破封装性。
Java 反射机制提供的功能:
• 在运行时判断任意一个对象所属的类
• 在运行时构造任意一个类的对象
• 在运行时判断任意一个类所具有的成员变量和方法
• 在运行时获取泛型信息
• 在运行时调用任意一个对象的成员变量和方法
• 在运行时处理注解
• 生成动态代理
下面都可以获取Class类:
(1)class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部
类 (2)interface:接口 (3)[]:数组 (4)enum:枚举 (5)annotation
:注解@interface (6)primitive type:基本数据类型 (7)void
8.1.获取 Class 类的实例(四种方法):
方式 1:要求编译期间已知类型:
前提:若已知具体的类,通过类的 class 属性获取,该方法最为安全可靠,程序性能最高。
实例:
Class clazz = String.class;
方式 2:获取对象的运行时类型:
前提:已知某个类的实例,调用该实例的 getClass()方法获取 Class 对象
实例:
Class clazz = "www.atguigu.com".getClass();
方式 3:可以获取编译期间未知的类型:
前提:已知一个类的全类名,且该类在类路径下,可通过 Class 类的静态方法forName()获取,可能抛出 ClassNotFoundException:
实例:
Class clazz = Class.forName("java.lang.String");
当使用Class.forName()
方法时,我们需要传入一个字符串参数,该参数是类的全限定名(包括包名)。该方法会返回一个Class
对象,该对象代表了对应类的运行时类型信息。
方式 4:其他方式(不做要求):
前提:可以用系统类加载对象或自定义加载器对象加载指定路径下的类型
实例:
ClassLoader cl = this.getClass().getClassLoader();
Class clazz4 = cl.loadClass("类的全类名");
注意,两个相同的类的Class对象是相等的,因为类只会加载一次。
8.2.Class类的理解:
使用了类的加载器加载到内存的方法区。
8.3.类的加载过程与类的加载器:
同样的加载器只能加载一次,不同的加载器可以加载多次。
查看某个类的类加载器对象:
(1)获取默认的系统类加载器
ClassLoader classloader = ClassLoader.getSystemClassLoader();
(2)查看某个类是哪个类加载器加载的
ClassLoader classloader =Class.forName("exer2.ClassloaderDemo").getClassLoader();
//如果是根加载器加载的类,则会得到 null
ClassLoader classloader1 =
Class.forName("java.lang.Object").getClassLoader();
(3)获取某个类加载器的父加载器
ClassLoader parentClassloader = classloader.getParent();
使用 ClassLoader 获取流
关于类加载器的一个主要方法:getResourceAsStream(String str):获取类路径下的指定文件的输入流:
@Test
public void test5() throws IOException {
Properties pros = new Properties();
//方式 1:此时默认的相对路径是当前的 module
// FileInputStream is = new FileInputStream("info.properties");
// FileInputStream is = new FileInputStream("src//info1.properties");
//方式 2:使用类的加载器
//此时默认的相对路径是当前 module 的 src 目录
InputStream is =
ClassLoader.getSystemClassLoader().getResourceAsStream("info1.properties");
pros.load(is);
//获取配置文件中的信息
String name = pros.getProperty("name");
String password = pros.getProperty("password");
System.out.println("name = " + name + ", password = " + password);
}
8.4.反射的运用:
4.1 应用 1:创建运行时类的对象:
这是反射机制应用最多的地方。创建运行时类的对象有两种方式:
方式 1:直接调用 Class 对象的 newInstance()方法:
要 求: 1)类必须有一个无参数的构造器。2)类的构造器的访问权限需要足够。
方式 2:通过获取构造器对象来进行实例化:
方式一的步骤:
1)获取该类型的 Class 对象 2)调用 Class 对象的 newInstance()方法创建对象
方式二的步骤:
1)通过 Class 类的 getDeclaredConstructor(Class … parameterTypes)取得本类的指定形参类型的构造器 2)向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。 3)通过 Constructor 实例化对象。如果构造器的权限修饰符修饰的范围不可见,也可以调setAccessible(true)
示例代码:
package com.atguigu.reflect;
import org.junit.Test;
import java.lang.reflect.Constructor;
public class TestCreateObject {
@Test
public void test1() throws Exception{
// AtGuiguClass obj = new AtGuiguClass();//编译期间无法创建
Class<?> clazz = Class.forName("com.atguigu.ext.demo.AtGuiguClass");
//clazz 代表 com.atguigu.ext.demo.AtGuiguClass 类型
//clazz.newInstance()创建的就是 AtGuiguClass 的对象
Object obj = clazz.newInstance();
System.out.println(obj);
}
@Test
public void test2()throws Exception{
Class<?> clazz = Class.forName("com.atguigu.ext.demo.AtGuiguDemo");
//java.lang.InstantiationException:
com.atguigu.ext.demo.AtGuiguDemo
//Caused by: java.lang.NoSuchMethodException:
com.atguigu.ext.demo.AtGuiguDemo.<init>()
//即说明 AtGuiguDemo 没有无参构造,就没有无参实例初始化方法<init>
Object stu = clazz.newInstance();
System.out.println(stu);
}
@Test
public void test3()throws Exception{
//(1)获取 Class 对象
Class<?> clazz = Class.forName("com.atguigu.ext.demo.AtGuiguDemo");
/*
\* 获取 AtGuiguDemo 类型中的有参构造
\* 如果构造器有多个,我们通常是根据形参【类型】列表来获取指定的一个
构造器的
\* 例如:public AtGuiguDemo(String title, int num)
*/
//(2)获取构造器对象
Constructor<?> constructor =
clazz.getDeclaredConstructor(String.class,int.class);
//(3)创建实例对象
// T newInstance(Object... initargs) 这个 Object...是在创建对象时,给有参构造的实参列表
Object obj = constructor.newInstance("尚硅谷",2022);
System.out.println(obj);
}
}
4.2. 应用 2:获取运行时类的完整结构:
可以获取:包、修饰符、类型名、父类(包括泛型父类)、父接口(包括泛型父接口)、成员(属性、构造器、方法)、注解(类上的、方法上的、属性上的)。
//1.实现的全部接口
public Class<?>[] getInterfaces()
//确定此对象所表示的类或接口实现的接口。
//2.所继承的父类
public Class<? Super T> getSuperclass()
//返回表示此 Class 所表示的实体(类、接口、基本类型)的父类的 Class。
//3.全部的构造器
public Constructor<T>[] getConstructors()
//返回此 Class 对象所表示的类的所有 public 构造方法。
public Constructor<T>[] getDeclaredConstructors()
//返回此 Class 对象表示的类声明的所有构造方法。
//Constructor 类中:
//取得修饰符:
public int getModifiers();
//取得方法名称:
public String getName();
//取得参数的类型:
public Class<?>[] getParameterTypes();
//4.全部的方法
public Method[] getDeclaredMethods()
//返回此 Class 对象所表示的类或接口的全部方法
public Method[] getMethods()
//返回此 Class 对象所表示的类或接口的 public 的方法
//Method 类中:
public Class<?> getReturnType()
//取得全部的返回值
public Class<?>[] getParameterTypes()
//取得全部的参数
public int getModifiers()
//取得修饰符
public Class<?>[] getExceptionTypes()
//取得异常信息
//5.全部的 Field
public Field[] getFields()
//返回此 Class 对象所表示的类或接口的 public 的 Field。
public Field[] getDeclaredFields()
//返回此 Class 对象所表示的类或接口的全部 Field。
//Field 方法中:
public int getModifiers()
//以整数形式返回此 Field 的修饰符
public Class<?> getType()
//得到 Field 的属性类型
public String getName()
//返回 Field 的名称。
//6. Annotation 相关
get Annotation(Class<T> annotationClass)
getDeclaredAnnotations()
//7.泛型相关
//获取父类泛型类型:
Type getGenericSuperclass()
//泛型类型:ParameterizedType
//获取实际的泛型类型参数数组:
getActualTypeArguments()
//8.类所在的包
Package getPackage()
4.3. 应用 3:调用运行时类的指定结构:
4.3.1 调用指定的属性
在反射机制中,可以直接通过 Field 类操作类中的属性,通过 Field 类提供的set()和 get()方法就可以完成设置和取得属性内容的操作。
(1)获取该类型的 Class 对象
Class clazz = Class.forName("包.类名");
(2)获取属性对象
Field field = clazz.getDeclaredField("属性名");
getDeclaredField可以获取所以属性,而getField只能获取public属性。
(3)如果属性的权限修饰符不是 public,那么需要设置属性可访问
field.setAccessible(true);
(4)创建实例对象:如果操作的是非静态属性,需要创建实例对象
Object obj = clazz.newInstance(); //有公共的无参构造
Object obj = 构造器对象.newInstance(实参...);//通过特定构造器对象创建实例对象
(4)设置指定对象 obj 上此 Field 的属性内容
field.set(obj,"属性值");
如果操作静态变量,那么实例对象可以省略,用 null 表示
(5)取得指定对象 obj 上此 Field 的属性内容
Object value = field.get(obj);
如果操作静态变量,那么实例对象可以省略,用 null 表示
4.3.2 调用指定的方法:
(1)获取该类型的 Class 对象
Class clazz = Class.forName("包.类名");
(2)获取方法对象
Method method = clazz.getDeclaredMethod("方法名",方法的形参类型列表);
形参列表要写成String.class等。
(3)创建实例对象
Object obj = clazz.newInstance();
(4)调用方法
Object result = method.invoke(obj, 方法的实参值列表);
如果方法的权限修饰符修饰的范围不可见,也可以调用setAccessible(true),如果方法是静态方法,实例对象也可以省略,用 null 代替。
4.3.3.调用指定构造器:
在Java的反射机制中,可以使用Class类的newInstance()方法来调用指定构造器来创建对象。newInstance()方法会调用类的默认构造器来创建对象,如果需要调用指定的构造器,可以使用Constructor类的newInstance()方法来实现。
常用的方法包括:
-
getConstructor(Class… parameterTypes):根据参数类型获取指定的构造器。
-
getConstructors():获取所有公共的构造器。
-
getDeclaredConstructor(Class… parameterTypes):根据参数类型获取指定的构造器,包括私有构造器。
-
getDeclaredConstructors():获取所有的构造器,包括私有构造器。
-
newInstance(Object… initargs):使用指定的参数调用构造器来创建对象。
-
setAccessible(boolean flag):设置构造器的可访问性,如果构造器是私有的,需要设置为true才能调用。
用指定构造器时,可以通过以下示例代码来实现:
import java.lang.reflect.Constructor;
public class ReflectionExample {
public static void main(String[] args) {
try {
// 获取Class对象
Class<?> clazz = Class.forName("com.example.MyClass");
// 获取指定参数类型的构造器
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
// 使用构造器创建对象
Object obj = constructor.newInstance("example", 123);
// 打印对象
System.out.println(obj);
} catch (Exception e) {
e.printStackTrace();
}
}
}
在上面的示例中,我们首先获取了MyClass类的Class对象,然后通过getConstructor()方法获取了一个带有String和int参数的构造器。接着使用newInstance()方法调用该构造器来创建一个MyClass对象,并将其打印出来。
4.3.4.调用注解:
一个完整的注解应该包含三个部分: (1)声明 (2)使用 (3)读取。
(1)声明自定义注解:
package com.atguigu.annotation;
import java.lang.annotation.*;
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
String value();
}
让我们来分解这段代码:
-
@Inherited
: 这个注解表明该注解可以被子类继承。如果一个类使用了带有@Inherited注解的注解,那么它的子类也会继承这个注解。 -
@Target(ElementType.TYPE)
: 这个注解指定了注解的作用目标。在这里,ElementType.TYPE表示这个注解可以用在类、接口、枚举或注解类型上。 -
@Retention(RetentionPolicy.RUNTIME)
: 这个注解指定了注解的生命周期。在这里,RetentionPolicy.RUNTIME表示这个注解在运行时仍然可用,这意味着可以通过反射来获取这个注解的信息。 -
public @interface Table { String value(); }
: 这是注解的声明部分。它使用了关键字@interface来声明一个注解,并定义了一个名为Table的注解。在这个注解中,有一个名为value的成员,它没有指定默认值,因此在使用这个注解时,需要为value成员提供值。
总的来说,这段代码定义了一个可以用在类上的注解@Table,它可以被子类继承,在运行时可用,并且需要提供一个value值。这个注解可以用来为类添加表名的元数据信息,例如:
@Table("user_table")
public class User {
// 类的定义
}
• 自定义注解可以通过四个元注解@Retention,@Target,@Inherited,@Documented,分别说明它的声明周期,使用位置,是否被继承,是否被生成到 API 文档中。
• Annotation 的成员在 Annotation 定义中以无参数有返回值的抽象方法的形式来声明,我们又称为配置参数。返回值类型只能是八种基本数据类型、String 类型、Class 类型、enum 类型、Annotation 类型、以上所有类型的数组。
• 可以使用 default 关键字为抽象方法指定默认返回值。
• 如果定义的注解含有抽象方法,那么使用时必须指定返回值,除非它有默认值。格式是“方法名 = 返回值”,如果只有一个抽象方法需要赋值,且方法名为 value,可以省略“value=”,所以如果注解只有一个抽象方法成员,建议使用方法名 value。
(2)使用注解:
@Table("t_stu")
public class Student {
@Column(columnName = "sid",columnType = "int")
private int id;
@Column(columnName = "sname",columnType = "varchar(20)")
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +"id=" + id +", name='" + name + '\'' +'}';
}
}
这段代码展示了一个使用自定义注解的Java类。让我们逐步解释这段代码的含义:
@Table("t_stu")
public class Student {
在这里,@Table(“t_stu”)注解被应用在Student类上,它为Student类添加了一个名为"t_stu"的元数据信息,可能表示这个类对应数据库中的"t_stu"表。
@Column(columnName = "sid", columnType = "int")
private int id;
@Column(columnName = "sname", columnType = "varchar(20)")
private String name;
在这里,@Column注解被应用在类的成员变量上,它为id和name成员变量添加了元数据信息,包括字段名和字段类型。
(3):读取和处理自定义注解
自定义注解必须配上注解的信息处理流程才有意义。我们自己定义的注解,只能使用反射的代码读取。所以自定义注解的声明周期必须是 RetentionPolicy.RUNTIME。
package com.atguigu.annotation;
import java.lang.reflect.Field;
public class TestAnnotation {
public static void main(String[] args) {
Class studentClass = Student.class;
Table tableAnnotation = (Table) studentClass.getAnnotation(Table.class);
String tableName = "";
if(tableAnnotation != null){
tableName = tableAnnotation.value();
}
Field[] declaredFields = studentClass.getDeclaredFields();
String[] columns = new String[declaredFields.length];
int index = 0;
for (Field declaredField : declaredFields) {
Column column = declaredField.getAnnotation(Column.class);
if(column!= null) {
columns[index++] = column.columnName();
}
}
String sql = "select ";
for (int i=0; i<index; i++) {
sql += columns[i];
if(i<index-1){
sql += ",";
}
}
sql += " from " + tableName;
System.out.println("sql = " + sql);
}
}