简介:Java是一种面向对象的编程语言,自1995年推出以来,以其高可移植性、安全性和性能著称。本笔记涵盖了Java开发的基础知识点,包括基本语法、类与对象、封装、继承、多态、异常处理、集合框架、输入输出(I/O)、多线程、网络编程、Java虚拟机(JVM)、泛型、注解以及Java 9的模块系统。这些内容是Java编程的基础,并为学习更高级主题如反射、设计模式、并发编程、JDBC操作、Spring框架和微服务架构打下坚实的基础。
1. Java基本语法和类与对象
1.1 Java基本语法简介
Java是一种高级编程语言,具有简单易学、面向对象、跨平台等特性。基本语法是构建Java程序的基石,包括数据类型、运算符、控制流语句等。掌握这些基本元素对于编程至关重要。
1.2 类和对象的概念
在Java中,类是创建对象的模板,对象是类的实例。理解类和对象的关系是面向对象编程的核心。类定义了对象的属性和行为,对象则通过类的实例化来使用。
1.3 基本数据类型与变量
Java提供了八种基本数据类型,分别是四种整型、两种浮点型、一种字符型和一种布尔型。变量是数据的存储空间的表示,每个变量都有其类型,类型决定了变量可以存储什么类型的数据以及它所具有的操作。
1.4 控制流语句
控制流语句用于控制程序执行流程,包括条件判断语句(if, switch)和循环语句(for, while, do-while)。正确使用控制流语句能帮助开发者编写出逻辑更清晰、运行效率更高的代码。
// 示例:一个简单的if语句控制流
int number = 10;
if (number > 5) {
System.out.println("Number is greater than 5.");
}
在本章中,我们将从基础语法的细节开始,逐步探索Java中类和对象的创建与使用,掌握编程的基本概念,为后续章节的学习打下坚实的基础。
2. 封装与继承的实现
在第二章中,我们将深入了解Java编程语言中封装与继承的实现机制。这两个概念是面向对象编程的核心,对理解Java类的设计与使用至关重要。
2.1 Java中的封装机制
封装是面向对象编程的一个基本概念,它指的是将对象的状态(属性)和行为(方法)捆绑在一起,并对外隐藏对象的实现细节。在Java中,通过使用访问修饰符来实现封装。
2.1.1 访问修饰符的作用和使用
Java提供了四种访问修饰符:public、protected、private以及默认(不写修饰符)。这些修饰符控制了类及其成员的访问级别。
public class Person {
private String name; // 私有属性,只能在本类中访问
protected String gender; // 受保护属性,子类与本包内类可访问
public int age; // 公共属性,任何地方都可以访问
String nationality; // 默认访问级别,同包内类可以访问
public void setName(String name) { // 公共方法,允许外部访问
this.name = name;
}
}
2.1.2 封装的概念和好处
封装不仅隐藏了对象内部的实现细节,也要求外部代码通过对象提供的公共接口来访问这些细节。这样做具有以下好处:
- 安全性 :防止对象状态被外部非法修改。
- 独立性 :独立修改内部实现,而不会影响到其他依赖该对象的代码。
- 重用性 :提高代码的重用性,因为对象的行为可以独立于其他部分进行改变和扩展。
2.1.3 包(package)和命名空间
Java的包(package)是类的容器,它提供了命名空间管理机制,防止类名冲突。通过将类组织到不同的包中,可以创建逻辑上相关的类集合。
// 声明包
package com.example.project;
public class MyClass {
// 类内容
}
包名通常是组织名的反转形式,以确保全球唯一性。通过导入相应的包,可以使用包内的类。
2.2 Java中的继承特性
继承是面向对象编程中的另一个核心概念,它允许创建类的层次结构。继承的类称为子类或派生类,被继承的类称为父类或基类。
2.2.1 继承的基本语法和规则
在Java中,使用 extends
关键字实现继承。
class Animal {
void eat() {
System.out.println("I can eat");
}
}
class Dog extends Animal {
void bark() {
System.out.println("I can bark");
}
}
Dog myDog = new Dog();
myDog.eat(); // 输出: I can eat
myDog.bark(); // 输出: I can bark
2.2.2 super关键字的使用和理解
super
关键字用于访问父类的属性和方法。在子类中, super
可以引用父类的成员。
class Animal {
String type = "Animal";
void eat() {
System.out.println("I can eat");
}
}
class Dog extends Animal {
String type = "Dog";
void bark() {
System.out.println("I can bark");
}
void showType() {
System.out.println("The type is " + type); // 输出当前类的type
System.out.println("The parent type is " + super.type); // 输出父类的type
}
}
2.2.3 方法重写与多态的关系
方法重写(Overriding)是子类提供特定实现版本的方法,它具有与父类中声明的方法相同的名称、返回类型和参数列表。这是多态性的基础。
class Animal {
void eat() {
System.out.println("Animal is eating");
}
}
class Dog extends Animal {
@Override
void eat() {
System.out.println("Dog is eating");
}
}
Animal myAnimal = new Dog();
myAnimal.eat(); // 输出: Dog is eating
通过方法重写,我们可以在子类中根据需要修改父类的行为。在上面的例子中,尽管 myAnimal
变量是 Animal
类型,但运行时调用的是 Dog
类中重写的 eat
方法。
在本章节中,我们探讨了Java中的封装和继承机制,并通过代码示例和解释深入理解了它们的应用。下一章,我们将继续探索多态性以及Java异常处理机制,这两个主题是理解和运用Java语言的进一步深化。
3. 多态和异常处理机制
3.1 Java中的多态性
3.1.1 接口和抽象类的应用
多态性是面向对象编程的核心概念之一,它允许我们通过一个共同的接口来使用不同的底层形式。在Java中,接口(Interface)和抽象类(Abstract Class)提供了实现多态性的基础。
接口是完全抽象的,它们定义了一组方法规范,具体实现由实现接口的类来完成。接口可以被多个类实现,使得不同类的对象能够通过相同的接口方法被调用,实现多态。例如,Java中的 java.util.List
接口可以由 ArrayList
、 LinkedList
等不同类实现,但所有实现类的对象都可以被当作 List
使用。
public interface Shape {
void draw();
}
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing Circle");
}
}
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Drawing Rectangle");
}
}
public class TestPolymorphism {
public static void main(String[] args) {
Shape shape1 = new Circle();
Shape shape2 = new Rectangle();
shape1.draw(); // 输出: Drawing Circle
shape2.draw(); // 输出: Drawing Rectangle
}
}
在这个例子中, Circle
和 Rectangle
都实现了 Shape
接口。调用 draw()
方法时,不同类的对象表现出了各自的行为,体现了多态性。
抽象类则与接口有所不同,它们可以包含抽象方法和具体方法。抽象方法没有方法体,需要在子类中实现;具体方法则可以有实现。抽象类通常用于表示实体的共有属性和行为,而具体的实现则由子类完成。
3.1.2 动态绑定机制解析
动态绑定是多态性的实现机制。在Java中,方法的调用是在运行时根据对象的实际类型进行的,而不是声明类型。这意味着,即使使用一个接口或抽象类的引用来引用一个对象,实际调用的方法也是对象实际类型的方法。
public class Animal {
public void makeSound() {
System.out.println("Animal makes sound");
}
}
public class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Dog barks");
}
}
public class TestDynamicBinding {
public static void main(String[] args) {
Animal animal = new Dog();
animal.makeSound(); // 输出: Dog barks
}
}
在上面的例子中, animal
变量被声明为 Animal
类型,但实际引用了一个 Dog
对象。调用 makeSound()
时,实际执行的是 Dog
类中重写的方法。
3.1.3 多态在实际开发中的运用
多态在实际开发中的应用非常广泛。例如,在设计一个图形用户界面(GUI)时,可以使用多态来处理不同类型的组件,如按钮、文本框等,而无需关心它们的具体类型。这样,当添加新的组件类型时,无需修改大量的代码。
在实际开发中,多态通常与其他设计模式,如工厂模式、策略模式等结合使用。这些模式利用了接口或抽象类的多态特性,以提供灵活和可扩展的代码。
3.2 Java异常处理机制
3.2.1 异常类的层次结构
在Java中,异常处理是通过一种称为“try-catch-finally”的机制来实现的。异常对象是 Throwable
类的实例,它位于类层次的顶层,其子类 Error
和 Exception
分别表示错误和可恢复的异常。
Throwable
├── Error: 表示严重的错误,通常是不可恢复的
└── Exception: 表示可恢复的异常
Exception
类又分为两类: RuntimeException
和非 RuntimeException
。 RuntimeException
表示那些在运行时可能发生的异常,通常与程序员的编程错误有关,例如空指针异常( NullPointerException
)。
非 RuntimeException
是由应用程序外部条件引起的异常,如用户输入错误或文件不存在等,需要程序员使用 try-catch
块显式处理。
3.2.2 try-catch-finally语句的使用
try-catch-finally
语句是处理异常的标准方式,它确保了异常能够被适当处理,而不会导致程序突然终止。
try {
// 尝试执行的代码块
} catch (ExceptionType1 e1) {
// 异常类型1的处理器
} catch (ExceptionType2 e2) {
// 异常类型2的处理器
} finally {
// 无论是否捕获到异常,都要执行的代码块
}
在 try
块中,如果发生了异常,则相应的 catch
块会被执行。如果有多个 catch
块,则只有匹配的异常类型会被捕获。 finally
块无论是否发生异常都会被执行,常用于释放资源,如关闭文件流等。
3.2.3 自定义异常与异常链
Java允许开发者自定义异常类。创建自定义异常类时,通常继承自 Exception
或其子类。自定义异常可以包含更多的信息和行为,使得程序能够提供更多关于错误的细节。
public class CustomException extends Exception {
public CustomException(String message) {
super(message);
}
public CustomException(String message, Throwable cause) {
super(message, cause);
}
// 其他方法...
}
异常链是处理异常的一种方法,它涉及在捕获一个异常的同时,将另一个异常作为“原因”保存下来,并一起抛出。这样做的好处是能够保留底层异常的上下文信息,有助于调试和错误分析。
try {
// 某段代码可能抛出异常
} catch (Exception e) {
throw new CustomException("A custom exception occurred", e);
}
通过使用异常链,可以将低层次的异常嵌入到更高级别的异常中,同时保留对原始异常的引用,有助于开发人员理解异常发生的完整情境。
在了解了多态性和异常处理机制之后,我们可以在开发过程中更加灵活地设计类和方法,并能够更加妥善地处理程序运行时可能遇到的各种问题。接下来,我们将探索Java集合框架及其在实际开发中的应用。
4. 集合框架及其应用
集合框架是Java编程中不可或缺的部分,它为数据存储和操作提供了灵活和高效的方法。在这一章节,我们将深入探讨Java集合框架的组成、结构、接口与类,以及在实际开发中如何应用这些集合以提高程序性能和效率。
4.1 Java集合框架概述
4.1.1 集合框架的组成和结构
集合框架(Collections Framework)是Java类库中的一个接口,它为表示和操作集合提供了一套统一的架构。这个框架由一组接口、抽象类和具体类组成,支持单个元素和整个集合的操作。
核心接口 : - Collection :所有集合类的根接口,代表一组对象,称为其元素。 - Set :不允许存在重复元素的集合,它实现Collection接口。 - List :有序集合,可以包含重复元素,允许通过索引访问元素。 - Queue :通常用于表示数据结构的先进先出(FIFO)队列。 - Map :一种映射结构,存储键值对,键不能重复。
核心实现类 : - ArrayList :基于动态数组实现,提供了快速的随机访问。 - LinkedList :基于链表实现,适合频繁插入和删除操作。 - HashSet :基于HashMap实现,提供了快速查找操作。 - HashMap :基于哈希表实现,允许存储null键和值。
接口与实现类的关系 : 集合框架采用了一种“接口与实现分离”的设计策略,允许程序猿根据需求选择最合适的集合类实现。比如,当你需要快速访问元素时,可以选择ArrayList;如果你频繁地进行插入和删除操作,LinkedList可能是一个更好的选择。
4.1.2 集合框架中的接口与类
集合框架中的接口定义了集合的行为,而具体的类则提供了这些行为的实现。理解这些接口和类之间的关系有助于更好地利用集合框架的优势。
- Collection 接口是所有单列集合的根接口。它提供了一些基本的操作,如添加、删除、遍历等。
- Set 接口继承自 Collection 接口,它保证了集合中的元素唯一性。
- List 接口也继承自 Collection,但允许有重复元素,并且保持了元素的插入顺序。
- Queue 接口通常用于实现队列这种先进先出的数据结构。
具体的实现类则提供了这些接口的具体实现。例如,HashMap是Map接口的一个实现,它基于哈希表,提供了快速的键值对映射和检索。
4.1.3 集合框架的使用场景
集合框架的使用场景非常广泛,以下是一些典型的应用:
- 数据存储 :使用List存储一系列的对象,使用Map存储键值对。
- 快速查找 :利用HashSet快速检索元素。
- 排序 :通过TreeSet或TreeMap进行自动排序。
- 并发操作 :使用CopyOnWriteArrayList等并发集合进行线程安全的数据操作。
4.2 集合的应用实践
4.2.1 List、Set、Map的使用场景
集合框架中的List、Set和Map接口提供了不同类型的集合,它们各自有着不同的使用场景:
- List 主要用于存储有序的集合,适合通过索引访问元素,如实现简单的数据库记录存储。
- Set 主要用于存储不重复的元素集合,如存储已见过的数据记录,避免重复操作。
- Map 用于存储键值对,通常用于快速检索数据,如存储配置信息、用户信息等。
4.2.2 集合的排序与数据结构选择
集合在排序和选择合适的数据结构方面也大有文章,这取决于你的具体需求:
- 排序 :如果需要对数据进行排序,可以使用TreeSet或TreeMap,它们在插入时就保持了元素的排序状态。
- 数据结构选择 :针对不同的应用场景,应选择最合适的集合类型以提高性能和效率。
4.2.3 并发集合与线程安全问题
在多线程环境下,集合的线程安全性是一个需要关注的问题:
- 线程安全 :为了在多线程环境下使用集合,可以使用线程安全的集合类,如Vector、Hashtable等。
- 并发集合 :Java提供了专门的并发集合类,如ConcurrentHashMap、CopyOnWriteArrayList等,这些集合通过锁分离等技术减少锁竞争,提升性能。
// 示例代码:使用ConcurrentHashMap
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("key", 1);
map.get("key");
以上代码展示了如何使用ConcurrentHashMap,它是一个线程安全的Map实现,适用于多线程环境。
4.2.4 集合的性能优化
了解集合框架的性能特征可以帮助你优化集合的使用。以下是一些优化建议:
- 选择合适的集合类型 :根据具体需求选择合适的集合类型可以避免性能瓶颈。
- 避免不必要的类型转换 :尽量避免将集合对象转换为数组,因为这会带来性能开销。
- 使用迭代器 :在遍历集合时,使用迭代器比直接使用索引更高效,尤其是在多线程环境中。
4.2.5 分析和处理集合中的数据
在处理集合数据时,通常需要进行数据分析,例如统计、排序、筛选等操作,Java集合框架提供了强大的工具来支持这些需求。
// 示例代码:使用Java 8的流(Stream)API来处理集合中的数据
List<String> list = new ArrayList<>();
// 填充数据到list...
long count = list.stream()
.filter(s -> s.startsWith("a"))
.count();
System.out.println("List中以'a'开头的字符串数量: " + count);
以上代码展示了如何使用流API来统计list中以"a"开头的字符串数量。流API为集合操作提供了简洁且功能强大的方法,能够简化代码并提高执行效率。
4.2.6 集合的深拷贝和浅拷贝
在处理集合时,理解深拷贝和浅拷贝的区别是很重要的。浅拷贝是指拷贝对象的引用,而深拷贝则是完全拷贝对象的所有字段。
// 示例代码:集合的浅拷贝
List<String> originalList = new ArrayList<>();
originalList.add("element");
List<String> shallowCopy = new ArrayList<>(originalList);
以上代码展示了如何创建一个List的浅拷贝。如果需要深拷贝,则需要手动创建对象的副本并添加到新的集合中。
总结来说,Java集合框架是一个功能强大的工具包,它提供了一套丰富的接口和实现类,允许程序猿以各种方式存储和操作数据。合理选择和使用集合,不仅可以提高代码的可读性和可维护性,还可以通过优化来提升程序的性能。
5. Java I/O流模型和多线程编程
5.1 Java I/O流基础
5.1.1 输入/输出流的概念和分类
I/O(Input/Output)流是Java中进行数据输入输出操作的核心机制。它们可以视为数据传输的管道,允许数据从源移动到目的地,无论是文件、网络连接还是内存缓冲区。I/O流在Java中以抽象类的形式存在,并通过继承与多态提供了丰富的方法来处理不同的输入输出需求。
根据数据类型和传输方式的不同,Java I/O流被分为两大类:字节流和字符流。字节流基于字节进行数据传输,主要处理二进制数据,例如文件和网络数据包。字符流则基于字符处理文本数据,它利用了Java的默认字符集,便于处理文本文件和字符串。
5.1.2 字节流与字符流的应用
字节流主要包括 InputStream
和 OutputStream
两个抽象基类,它们的子类如 FileInputStream
、 FileOutputStream
、 BufferedInputStream
和 BufferedOutputStream
分别提供了读写文件和带有缓冲区的读写功能。
字符流则以 Reader
和 Writer
为基类,其子类如 FileReader
、 FileWriter
、 BufferedReader
和 BufferedWriter
等提供文本文件的读写支持。与字节流相比,字符流在处理文本时更加高效,并且可以处理字符编码转换。
下面是一个使用 FileInputStream
和 FileOutputStream
进行文件复制的简单示例:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileCopyExample {
public static void main(String[] args) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("source.txt");
fos = new FileOutputStream("destination.txt");
int data;
while ((data = fis.read()) != -1) {
fos.write(data);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fis != null) fis.close();
if (fos != null) fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
5.1.3 文件操作和路径处理
Java NIO.2 API提供了更为强大和灵活的文件系统访问能力。它引入了 java.nio.file
包,其中 Paths
和 Path
类用于处理文件和目录的路径。 Files
类则提供了用于执行文件操作的方法。
例如,使用 Files
类进行文件的创建、读写和属性获取的代码如下:
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.io.IOException;
public class FileIOExample {
public static void main(String[] args) {
Path path = Paths.get("example.txt");
// 创建文件并写入数据
try {
Files.createFile(path);
Files.write(path, "Hello, NIO.2!".getBytes(), StandardOpenOption.APPEND);
} catch (IOException e) {
e.printStackTrace();
}
// 读取文件内容
try {
byte[] bytes = Files.readAllBytes(path);
System.out.println(new String(bytes));
} catch (IOException e) {
e.printStackTrace();
}
// 获取文件属性
try {
BasicFileAttributes attr = Files.readAttributes(path, BasicFileAttributes.class);
System.out.println("文件大小: " + attr.size() + " 字节");
} catch (IOException e) {
e.printStackTrace();
}
}
}
5.2 Java多线程编程入门
5.2.1 线程的创建和运行
Java多线程编程是Java并发编程的基础。Java通过实现 Runnable
接口或继承 Thread
类来创建线程。每条线程都有一个优先级,它可以通过 setPriority(int)
方法进行设置,并通过 getPriority()
获取。
下面是一个继承 Thread
类来创建线程的示例:
public class MyThread extends Thread {
public void run() {
System.out.println("This is a thread.");
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
通过实现 Runnable
接口创建线程的示例:
public class MyRunnable implements Runnable {
public void run() {
System.out.println("This is a thread created by Runnable.");
}
public static void main(String[] args) {
Thread myThread = new Thread(new MyRunnable());
myThread.start();
}
}
5.2.2 线程同步与协作
在多线程环境中,共享资源的访问可能会导致数据不一致的问题,这是由于多个线程可能在同时尝试修改同一资源。为了保证资源访问的一致性,Java提供了 synchronized
关键字,通过锁机制实现线程同步。
下面的示例展示了如何使用 synchronized
关键字来同步线程:
public class SynchronizedExample {
private int count = 0;
public void increment() {
count++;
}
public synchronized void synchronizedIncrement() {
count++;
}
public static void main(String[] args) throws InterruptedException {
SynchronizedExample example = new SynchronizedExample();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.synchronizedIncrement();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Count value: " + example.count);
}
}
5.2.3 线程池的使用和优势
线程池是一种线程使用模式。它允许线程重用,而不是在完成任务后销毁它们。线程池通过内部维护一定数量的空闲线程来执行提交的任务,有效地减少了创建和销毁线程的开销。
Java的 ExecutorService
接口提供了线程池的实现。 ThreadPoolExecutor
是一个灵活的线程池实现,可以配置线程池的工作参数,如核心线程数、最大线程数、存活时间等。
下面是一个使用 ThreadPoolExecutor
来执行任务的示例:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ThreadPoolExample {
public static void main(String[] args) {
// 创建一个固定大小的线程池
ExecutorService executor = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
// 提交任务给线程池
executor.submit(() -> {
System.out.println("Thread " + Thread.currentThread().getName() + " is running.");
});
}
// 关闭线程池,并处理正在执行的任务
executor.shutdown();
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
}
}
}
线程池的使用不仅减少了资源的消耗,还提高了应用的性能和响应速度,是Java多线程编程中的一个推荐实践。
以上就是Java I/O流和多线程编程的基础入门知识,它们是Java开发中不可或缺的技能点。熟练掌握它们,能够帮助开发者设计出更加高效、稳定的应用程序。
6. 网络编程和Java虚拟机(JVM)
6.1 Java网络编程基础
6.1.1 套接字编程(Socket编程)
Java网络编程中的套接字(Socket)是通信的端点,它是网络通信的基本操作单元。在Java中,我们主要通过 ***.Socket
类和 ***.ServerSocket
类来实现客户端和服务器端的网络编程。
客户端Socket示例代码:
import java.io.*;
***.*;
public class ClientSocketExample {
public static void main(String[] args) {
String host = "localhost";
int port = 12345;
try (Socket socket = new Socket(host, port)) {
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out.println("Hello Server!");
System.out.println("Server replied: " + in.readLine());
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
服务器端ServerSocket示例代码:
import java.io.*;
***.*;
public class ServerSocketExample {
public static void main(String[] args) {
int port = 12345;
try (ServerSocket serverSocket = new ServerSocket(port)) {
System.out.println("Server is listening on port " + port);
try (Socket clientSocket = serverSocket.accept()) {
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
System.out.println("Received: " + in.readLine());
out.println("Hello Client!");
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
6.1.2 URI、URL和URN的使用
URI(Uniform Resource Identifier)是用于标识资源的字符串,URL(Uniform Resource Locator)是URI的一个子集,用于定位网络上的资源。URN(Uniform Resource Name)是另一种URI,它通过名称来标识资源,而不是位置。
URI、URL和URN的示例:
``` .*;
public class URISample { public static void main(String[] args) { try { URI uri = new URI(" "); URL url = uri.toURL(); String urn = "urn:isbn: "; // 示例URN
System.out.println("URI: " + uri);
System.out.println("URL: " + url);
System.out.println("URN: " + urn);
} catch (URISyntaxException | MalformedURLException e) {
e.printStackTrace();
}
}
}
### 6.1.3 常见网络协议在Java中的实现
Java支持多种网络协议,包括TCP/IP、HTTP、FTP等。在Java中,这些协议通过对应的类和方法进行实现和处理。
**使用URL类来处理HTTP协议的示例:**
```***
***.*;
public class HTTPExample {
public static void main(String[] args) {
try {
URL url = new URL("***");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
int responseCode = connection.getResponseCode();
System.out.println("Response Code: " + responseCode);
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
System.out.println(response.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
}
6.2 Java虚拟机(JVM)深入解析
6.2.1 JVM的工作原理和架构
JVM(Java Virtual Machine)是运行Java字节码的虚拟机进程。JVM负责将字节码转换为特定平台上的机器码,并在该平台的硬件和操作系统上执行。JVM架构包括类加载器、运行时数据区、执行引擎等关键组件。
JVM架构简图:
graph LR
A[类加载器 Class Loader]
B[运行时数据区 Runtime Data Area]
C[执行引擎 Execution Engine]
D[本地接口本地库 Native Interface]
A --> B
B --> C
C --> D
6.2.2 垃圾回收机制与性能调优
垃圾回收(Garbage Collection,GC)是JVM的一个重要功能,它负责回收不再使用的对象,以释放内存空间。JVM的垃圾回收机制涉及到不同的垃圾回收算法,如标记-清除、复制、标记-整理、分代收集等。
GC日志分析示例:
[GC (Allocation Failure) [PSYoungGen: 28621K->5120K(30976K)] 48919K->23892K(96768K), 0.0289728 secs] [Times: user=0.08 sys=0.01, real=0.03 secs]
性能调优通常涉及堆内存的调整、垃圾回收器的选择、JVM参数的设置等。
6.2.3 Java内存模型和线程安全问题
Java内存模型定义了共享变量的访问规则,它确保了多线程环境下变量的可见性和有序性。Java中的线程安全问题涉及多个线程访问共享资源时可能引发的数据不一致问题。
线程安全的代码示例:
import java.util.concurrent.*;
public class SynchronizedExample {
private static final int THREAD_COUNT = 10;
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT);
for (int i = 0; i < THREAD_COUNT; i++) {
executorService.execute(new Counter());
}
executorService.shutdown();
executorService.awaitTermination(1, TimeUnit.MINUTES);
}
static class Counter implements Runnable {
private static int count = 0;
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
synchronized (Counter.class) {
count++;
}
}
}
public static int getCount() {
return count;
}
}
}
在多线程环境下,若无适当的同步机制, count++
操作可能会导致线程安全问题。通过 synchronized
关键字,我们可以确保每次只有一个线程能够修改 count
变量,保证了线程安全。
简介:Java是一种面向对象的编程语言,自1995年推出以来,以其高可移植性、安全性和性能著称。本笔记涵盖了Java开发的基础知识点,包括基本语法、类与对象、封装、继承、多态、异常处理、集合框架、输入输出(I/O)、多线程、网络编程、Java虚拟机(JVM)、泛型、注解以及Java 9的模块系统。这些内容是Java编程的基础,并为学习更高级主题如反射、设计模式、并发编程、JDBC操作、Spring框架和微服务架构打下坚实的基础。