.java
为扩展名,例如HelloWorld.java
。cmd
打开命令行窗口,然后使用cd
命令切换目录。例如,如果文件在D:\java_project\src
目录下,可在命令行输入cd D:\java_project\src
。javac
命令,后面跟上要编译的.java
文件名,如javac HelloWorld.java
。如果代码没有语法错误,会在当前目录生成对应的.class
字节码文件。java
命令运行程序,后面跟上类名(注意不是文件名)。例如,如果HelloWorld.java
文件中有一个public class HelloWorld
,则运行命令为java HelloWorld
。javac
)、Java虚拟机(JVM)、Java核心类库以及各种开发工具(如调试器jdb
等)。javac
编译.java
源文件,使用java
命令运行字节码文件。byte
:字节型,占1个字节,用于表示较小范围的整数,如存储文件中的字节数据等。short
:短整型,占2个字节,可用于节省内存空间的整数存储,如某些嵌入式系统中的数据存储。int
:整型,占4个字节,是最常用的整数类型,适用于一般的整数计算和存储。long
:长整型,占8个字节,用于表示较大范围的整数,如处理时间戳(以毫秒为单位)等。float
:单精度浮点型,占4个字节,精度相对较低,可用于对精度要求不高的科学计算或图形处理中的简单数据表示。double
:双精度浮点型,占8个字节,精度较高,是默认的浮点型,常用于金融计算等对精度要求较高的场景。char
:字符型,占2个字节,用于存储单个字符,如字母、数字、标点符号等,在处理文本数据时经常使用。boolean
:布尔型,占1位(实际大小由虚拟机决定),只有true
和false
两个值,用于逻辑判断,如控制程序流程中的条件判断。Person
类来表示人的信息,包括姓名、年龄等属性,以及获取姓名、设置年龄等方法。Runnable
接口,定义了run
方法,实现该接口的类必须实现run
方法来定义线程的执行逻辑。int[]
数组可以存储多个整数。enum Week {MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY}
。@Override
注解用于表示方法重写。2number
是非法的。final
、class
等都是关键字,不能用作变量名。不过,Java 8以后,有些关键字可以在特定的上下文中使用,如var
用于局部变量类型推断,但一般情况下仍应避免使用关键字作为普通变量名。count
和Count
是两个不同的变量。姓名
),但不推荐,因为可能会出现编码问题,且不符合常见编程规范,不利于代码维护和交流。2number
:以数字开头,违反了变量名的命名规则。final
:是Java关键字,不能用作变量名。classVar
:class
是关键字,不能用于变量命名。for
循环
for (初始化表达式; 条件表达式; 更新表达式) {循环体}
。int i = 0
;条件表达式用于判断循环是否继续执行,如i < 10
;更新表达式用于在每次循环结束后更新循环变量的值,如i++
。int sum = 0;
for (int i = 1; i <= 10; i++) {
sum += i;
}
System.out.println("1到10的和为:" + sum);
while
循环
while (条件表达式) {循环体}
。int sum = 0;
int i = 1;
while (i <= 10) {
sum += i;
i++;
}
System.out.println("1到10的和为:" + sum);
do - while
循环
do {循环体} while (条件表达式);
。int sum = 0;
int i = 1;
do {
sum += i;
i++;
} while (i <= 10);
System.out.println("1到10的和为:" + sum);
for
循环,根据初始化表达式、条件表达式和更新表达式来确定循环次数。如果条件表达式在有限次更新循环变量后必然不满足,就是有限循环。例如for (int i = 0; i < 10; i++)
,i
从0开始,每次增加1,当i
达到10时,条件i < 10
不满足,循环结束。for
循环中添加System.out.println(i);
,可看到i
从0递增到9,共循环10次,从而验证是有限循环。while (true)
或for (;;)
从语法上看就是无限循环。在while
或do - while
循环中,若忘记更新循环变量,导致条件表达式始终满足,也会产生无限循环。switch
语句用法
switch
语句根据表达式的值来选择执行不同的代码块。表达式的结果必须是byte
、short
、int
、char
、String
(Java 7以后支持)或者枚举类型。switch (表达式) {
case 值1:
// 当表达式的值等于值1时执行的代码块
break;
case 值2:
// 当表达式的值等于值2时执行的代码块
break;
// 可以有多个case分支
default:
// 当表达式的值与所有case的值都不匹配时执行的代码块
break;
}
import java.util.Scanner;
public class SwitchExample {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入一个数字(1 - 7): ");
int day = scanner.nextInt();
switch (day) {
case 1:
System.out.println("星期一");
break;
case 2:
System.out.println("星期二");
break;
case 3:
System.out.println("星期三");
break;
case 4:
System.out.println("星期四");
break;
case 5:
System.out.println("星期五");
break;
case 6:
System.out.println("星期六");
break;
case 7:
System.out.println("星期日");
break;
default:
System.out.println("输入无效,请输入1 - 7之间的数字");
break;
}
}
}
break
和无break
的区别
break
switch
语句中的某个case
分支执行到break
语句时,程序会立即跳出switch
结构,继续执行switch
语句后面的代码。case 3
分支中的代码,输出“星期三”后,遇到break
,就会跳出switch
语句,不会再执行其他case
分支的代码。break
case
分支中没有break
语句,那么在执行完当前case
分支的代码后,程序会继续执行下一个case
分支的代码,而不管下一个case
分支的条件是否满足。int number = 2;
switch (number) {
case 1:
System.out.println("这是1");
case 2:
System.out.println("这是2");
case 3:
System.out.println("这是3");
default:
System.out.println("这不是1、2、3中的任何一个");
}
- 当`number = 2`时,会先执行`case 2`中的代码,输出“这是2”,由于没有`break`,会继续执行`case 3`中的代码,输出“这是3”,然后执行`default`分支的代码,输出“这不是1、2、3中的任何一个”。这种情况在某些特定逻辑下可能有用,但要注意可能导致意外执行结果。
default
的作用
default
分支是可选的。当switch
语句中的表达式的值与所有case
的值都不匹配时,就会执行default
分支中的代码。switch
语句中,如果用户输入不存在的菜单选项,可通过default
分支给出错误提示。Random
类和Integer
类的异常处理及相关判断java.util.Random
类
Random
实例频繁调用方法可能使结果不符合预期。另外,使用nextInt(int bound)
时,若bound
非正数会抛出IllegalArgumentException
。Random
实例会生成相同随机数序列。若要真正随机,避免固定种子或用动态值(如System.currentTimeMillis()
)作种子。nextInt(int bound)
中bound
必须为正数,否则用try - catch
捕获IllegalArgumentException
异常并处理。例如:import java.util.Random;
public class RandomExample {
public static void main(String[] args) {
Random random = new Random();
try {
// 这会抛出异常,因为bound为0是不合法的
System.out.println(random.nextInt(0));
} catch (IllegalArgumentException e) {
System.out.println("参数错误:bound必须为正数");
e.printStackTrace();
}
}
}
java.lang.Integer
类
int
类型范围的字符串转换为整数时,会抛出NumberFormatException
;整数运算可能出现溢出(但本身不直接抛出溢出异常)。try - catch
块捕获NumberFormatException
,处理字符串无法正确转换为整数的情况。例如:public class IntegerConversionExample {
public static void main(String[] args) {
try {
// 尝试将一个非数字字符串转换为整数
String str = "abc";
int num = Integer.parseInt(str);
System.out.println(num);
} catch (NumberFormatException e) {
System.out.println("无法将字符串转换为整数");
e.printStackTrace();
}
}
}
- **整数溢出**:运算时注意溢出情况,可将数据转换为`long`类型提前判断是否会溢出,避免静默的溢出错误。例如:
public class IntegerOverflowCheckExample {
public static void main(String[] args) {
int maxValue = Integer.MAX_VALUE;
if ((long) maxValue + 1 > Integer.MAX_VALUE) {
System.out.println("会发生溢出");
} else {
System.out.println("不会发生溢出");
}
}
}
数组名.length
获取数组的长度,即数组中元素的个数。例如,int[] arr = new int[5];
,则arr.length
的值为5。int[]
数组只能存储整数,String[]
数组只能存储字符串。int[] arr = new int[5];
,可以使用arr[0]
、arr[1]
等来访问数组中的元素。ArrayIndexOutOfBoundsException
异常。例如,arr[5]
(对于长度为5的数组)就是越界访问,因为合法的索引范围是0到4。for
循环遍历int[] arr = {1, 2, 3, 4, 5};
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
for
循环(foreach
循环)遍历int[] arr = {1, 2, 3, 4, 5};
for (int element : arr) {
System.out.println(element);
}
List
来存储String
类型的元素,如果试图添加一个Integer
类型的对象,在编译时就会报错。Integer
、String
还是其他类型,都可以使用同一个方法实现。public class Box<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
}
定义了一个泛型类型参数T
,在类中可以使用T
来表示不确定的类型。可以创建Box
对象并指定具体类型,如Box box = new Box<>();
,这样box
就只能存储String
类型的对象。public class GenericMethodExample {
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element);
}
}
}
声明了一个泛型类型参数T
,方法中的参数T[] array
表示可以接受任何类型的数组。可以这样调用:Integer[] intArray = {1, 2, 3}; GenericMethodExample.printArray(intArray);
或者String[] strArray = {"a", "b", "c"}; GenericMethodExample.printArray(strArray);
。?
通配符可以在某些情况下表示未知类型。例如,定义一个方法来打印List
中元素的长度(假设元素是有长度概念的,如String
或数组):public static void printLength(List<?> list) {
for (Object element : list) {
if (element instanceof String) {
System.out.println(((String) element).length());
} else if (element instanceof int[]) {
System.out.println(((int[]) element).length);
}
}
}
- 这里的`List>`可以接受任何类型的`List`,但在方法内部需要进行类型判断来正确处理元素。
equals
与==
的区别==
运算符
int
、double
、char
等),==
比较的是它们的值是否相等。例如,int a = 5; int b = 5;
,则a == b
的结果为true
。==
比较的是两个引用是否指向同一个对象在内存中的地址。例如,创建两个Person
对象:Person person1 = new Person("Alice");
Person person2 = new Person("Alice");
Person person3 = person1;
person1 == person2
的结果为false
,因为它们是两个不同的对象实例,虽然内容相同但内存地址不同;而person1 == person3
的结果为true
,因为person3
指向了person1
所指向的同一个对象。equals
方法
equals
方法是在Object
类中定义的,默认情况下,它的行为与==
相同,即比较对象的引用地址。但是,在很多类中(如String
、Integer
等),equals
方法被重写了,用于比较对象的内容是否相等。String
类:String str1 = "hello";
String str2 = "hello";
String str3 = new String("hello");
str1 == str2
的结果为true
,因为字符串常量在内存中有一个共享的存储区域(字符串常量池);而str1 == str3
的结果为false
,因为str3
是通过new
关键字创建的新对象。但是str1.equals(str3)
的结果为true
,因为String
类重写了equals
方法来比较字符串的内容。equals
方法。重写equals
方法时,通常需要遵循一些规则,如自反性、对称性、传递性等。例如:class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass()!= o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
}
3.两者的区别
在Java中,“==”
主要用于比较基本数据类型的值是否相等以及引用数据类型的引用地址是否相同,对于引用类型,只有指向同一内存地址时才返回true;而“equals
”方法在Object类中默认行为同“ ==”
,但在很多类(如String、Integer等)中被重写用于比较对象内容是否相等,若未重写则比较引用地址,重写后可根据对象的具体属性来判断两个对象是否逻辑相等。
public
、private
、无修饰符的区别public
修饰符
public class PublicConstructorExample {
public PublicConstructorExample() {
System.out.println("这是一个public构造方法");
}
public static void main(String[] args) {
PublicConstructorExample example = new PublicConstructorExample();
}
}
private
修饰符
public class Singleton {
private static Singleton instance;
private Singleton() {
System.out.println("这是一个private构造方法");
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
private
构造方法确保了外部类不能通过new
关键字创建Singleton
类的实例,只能通过getInstance
方法获取唯一的实例。package com.example;
class DefaultConstructorExample {
DefaultConstructorExample() {
System.out.println("这是一个默认访问级别的构造方法");
}
}
com.example
包中,就无法直接访问DefaultConstructorExample
类的构造方法。class Animal {
public void makeSound() {
System.out.println("动物发出声音");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("汪汪汪");
}
}
Dog
类重写了Animal
类的makeSound
方法,当在Dog
类的实例上调用makeSound
方法时,会执行Dog
类中重写后的版本。class Calculator {
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) {
return a + b;
}
public int add(int a, int b, int c) {
return a + b + c;
}
}
Calculator
类重载了add
方法,根据传入参数的不同,会调用相应的重载版本。new
关键字
new
关键字后跟类的构造方法来创建对象。例如:class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
public class ObjectCreationExample {
public static void main(String[] args) {
Person person = new Person("Alice", 25);
}
}
new Person("Alice", 25)
创建了一个Person
类的对象,"Alice"
和25
是传递给构造方法的参数,用于初始化对象的属性。java.lang.reflect
包)(了解就好)
import java.lang.reflect.Constructor;
public class ReflectionObjectCreation {
public static void main(String[] args) throws Exception {
Class<?> personClass = Class.forName("Person");
Constructor<?> constructor = personClass.getConstructor(String.class, int.class);
Person person = (Person) constructor.newInstance("Bob", 30);
}
}
Class.forName("Person")
获取Person
类的Class
对象,然后获取指定参数类型的构造方法,最后使用newInstance
方法创建对象。反射机制在一些框架和动态编程场景中经常使用,但相比直接使用new
关键字创建对象,性能较低且代码相对复杂。public
protected
protected
修饰的成员。private
private
修饰的成员。通常用于隐藏类的内部实现细节,如类的私有属性和私有方法。class Shape {
public void draw() {
System.out.println("绘制形状");
}
}
class Circle extends Shape {
@Override
public void draw() {
System.out.println("绘制圆形");
}
}
class Rectangle extends Shape {
@Override
public void draw() {
System.out.println("绘制矩形");
}
}
public class PolymorphismExample {
public static void main(String[] args) {
Shape shape1 = new Circle();
Shape shape2 = new Rectangle();
shape1.draw();
shape2.draw();
}
}
Shape
类是父类,Circle
和Rectangle
是子类,它们都重写了draw
方法。在main
方法中,创建了Circle
和Rectangle
对象并赋值给Shape
类型的变量,当调用draw
方法时,根据对象的实际类型(Circle
或Rectangle
)来执行相应类中的重写方法。class Calculator {
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) {
return a + b;
}
}
public class OverloadPolymorphism {
public static void main(String[] args) {
Calculator calculator = new Calculator();
int result1 = calculator.add(2, 3);
double result2 = calculator.add(2.5, 3.5);
}
}
class Animal {
public void makeSound() {
System.out.println("动物发出声音");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("汪汪汪");
}
public void play() {
System.out.println("狗狗在玩耍");
}
}
public class TypeCastingPolymorphism {
public static void main(String[] args) {
Animal animal = new Dog();
animal.makeSound();
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.play();
}
}
}
Dog
对象赋值给Animal
类型的变量animal
(向上转型),然后通过instanceof
判断animal
是否是Dog
类型,如果是则进行向下转型,调用Dog
类特有的play
方法。interface Shape {
void draw();
}
class Circle implements Shape {
@Override
public void draw() {
System.out.println("绘制圆形");
}
}
class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("绘制矩形");
}
}
Triangle
实现Shape
接口但没有实现draw
方法,会导致编译错误,除非Triangle
被定义为抽象类。interface Printable {
void print();
}
class Document implements Shape, Printable {
@Override
public void draw() {
System.out.println("绘制文档相关图形");
}
@Override
public void print() {
System.out.println("打印文档");
}
}
Document
类实现了Shape
和Printable
两个接口,需要实现这两个接口中定义的所有方法。public
和abstract
的
public
访问修饰符(除非在接口中方法被定义为default
或static
方法,Java 8引入),因为接口方法的访问权限要求是公开的,且没有方法体(抽象的)。例如:interface Movable {
void move();
}
class Car implements Movable {
@Override
public void move() {
System.out.println("汽车在行驶");
}
}
Car
类实现Movable
接口的move
方法时,必须使用public
修饰符,否则会导致编译错误。public static final
)
public static final
的,即常量。实现接口的类可以直接访问这些常量。例如:interface Constants {
int MAX_VALUE = 100;
}
class MyClass implements Constants {
public void printMaxValue() {
System.out.println("最大值为:" + MAX_VALUE);
}
}
MyClass
中可以直接使用MAX_VALUE
常量,不需要重新定义或初始化。interface Shape {
void draw();
}
interface ColoredShape extends Shape {
void setColor(String color);
}
class ColoredCircle implements ColoredShape {
@Override
public void draw() {
System.out.println("绘制彩色圆形");
}
@Override
public void setColor(String color) {
System.out.println("设置圆形颜色为:" + color);
}
}
ColoredCircle
类实现了ColoredShape
接口,也就需要实现ColoredShape
接口及其父接口Shape
中定义的所有方法。default
方法后可能出现)
default
方法,那么实现类必须重写该方法来解决冲突。例如:interface InterfaceA {
default void printMessage() {
System.out.println("来自InterfaceA的消息");
}
}
interface InterfaceB {
default void printMessage() {
System.out.println("来自InterfaceB的消息");
}
}
class MyClass implements InterfaceA, InterfaceB {
@Override
public void printMessage() {
System.out.println("解决冲突后的消息");
}
}
MyClass
中重写printMessage
方法来避免因实现多个接口而导致的default
方法冲突。Object
类要注意哪些方法,比如hashCode
,equals
等等equals
方法
==
相同,即比较对象的引用地址。但在很多类中(如String
、Integer
等)被重写,用于比较对象的内容是否相等。自定义类如果需要比较对象内容,通常需要重写equals
方法,并且在重写时要遵循自反性、对称性、传递性等规则。例如:class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass()!= o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
}
hashCode
方法
equals
方法密切相关,用于返回对象的哈希码值。哈希码主要用于在哈希表(如HashMap
、HashSet
等集合类)中快速定位对象。如果两个对象根据equals
方法判断相等,那么它们的hashCode
值必须相同;但如果两个对象的hashCode
值相同,它们不一定相等(哈希冲突)。在重写equals
方法时,通常也需要重写hashCode
方法,以保证一致性。例如:class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass()!= o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
toString
方法
toString
方法可以提供更有意义的对象描述,方便调试和日志记录等。例如:class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
System.out.println(person)
(person
是Person
类的对象)时,会输出更易读的对象信息,如Person{name='Alice', age=25}
。clone
方法
clone
方法是浅拷贝,即只复制对象的基本类型成员变量的值,对于引用类型成员变量,只是复制引用而不复制对象本身。如果需要深拷贝,需要在重写clone
方法时进行额外的处理。例如:class Person implements Cloneable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
Person
类实现了Cloneable
接口并重写了clone
方法,但这只是一个浅拷贝的示例,如果Person
类中有引用类型成员变量,可能需要进一步修改clone
方法来实现深拷贝。finalize
方法
finalize
方法的执行时机是不确定的,而且在现代Java编程中,不推荐过度依赖finalize
方法来管理资源,更好的方式是使用try - with - resources
语句或显式的资源关闭方法。例如:class MyResource {
private File file;
public MyResource(File file) {
this.file = file;
}
@Override
protected void finalize() throws Throwable {
try {
if (file!= null) {
file.close();
}
} finally {
super.finalize();
}
}
}
import……
?java.awt
包(Abstract Window Toolkit)
import java.awt.Frame;
import java.awt.Button;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class AWTExample {
public static void main(String[] args) {
Frame frame = new Frame("AWT示例");
Button button = new Button("点击我");
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("按钮被点击了");
}
});
frame.add(button);
frame.setSize(300, 200);
frame.setVisible(true);
}
}
java.awt.Frame
用于创建窗口,java.awt.Button
用于创建按钮,以及相关的事件处理类。javax.swing
包
java.awt
基础上构建的轻量级GUI工具包,提供了更丰富、更灵活的组件,并且具有更好的外观和可定制性。例如:import javax.swing.JFrame;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class SwingExample {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame("Swing示例");
JPanel panel = new JPanel();
JButton button = new JButton("点击我");
JLabel label = new JLabel("欢迎使用Swing");
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
label.setText("按钮被点击了");
}
});
panel.add(label);
panel.add(button);
frame.add(panel);
frame.setSize(300, 200);
frame.setVisible(true);
}
});
}
}
javax.swing.JFrame
、javax.swing.JButton
等相关类来创建图形界面。SwingUtilities.invokeLater
用于确保Swing组件在事件分发线程中进行初始化和更新,以避免线程安全问题。java.awt.event
包(如果使用java.awt
组件进行事件处理)
AWTExample
中就导入了java.awt.event.ActionEvent
和java.awt.event.ActionListener
来处理按钮的点击事件。javax.swing.event
包(如果使用javax.swing
组件进行事件处理)
java.awt.event
包,但针对javax.swing
组件的特定事件提供了更丰富的支持。例如,对于JList
组件的列表选择事件等,可以使用这个包中的相关类和接口进行处理。int[] arr = new int[5];
创建了一个长度为5的整数数组,无法直接增加或减少其长度。ArrayList
可以根据需要动态地添加或删除元素,不需要预先指定最大长度。int[]
数组只能存储整数,String[]
数组只能存储字符串。List
可以存储任何对象,而List
只能存储字符串对象。ArrayList
的add
、remove
方法,TreeSet
的自动排序功能等。集合框架还提供了迭代器(Iterator
)等方式来方便地遍历元素,而数组遍历通常使用for
循环或增强for
循环。ArrayList
内部是数组实现,LinkedList
是链表实现,它们在内存管理上有不同的特点。集合框架在动态调整大小时,通常会有更优化的内存管理策略,但也可能会有一定的内存开销(如存储额外的节点信息等)。try
,catch
,finally
里面分别放什么代码?try
块
try
块中。例如:try {
FileReader fileReader = new FileReader("example.txt");
BufferedReader bufferedReader = new BufferedReader(fileReader);
String line = bufferedReader.readLine();
System.out.println(line);
bufferedReader.close();
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
IOException
异常,所以放在try
块中。catch
块
try
块中抛出的异常。可以有多个catch
块来捕获不同类型的异常,catch
块中的代码用于处理相应类型的异常,如记录错误信息、提示用户、进行一些恢复操作等。例如,在上面的文件读取例子中,如果出现IOException
,catch
块中的e.printStackTrace()
会打印出异常的堆栈跟踪信息,方便调试。还可以根据具体需求进行更复杂的处理,如提示用户文件不存在并提供重新选择文件的选项等。finally
块
try
块中是否抛出异常,finally
块中的代码都会被执行。通常用于释放资源,如关闭文件、关闭数据库连接、释放网络连接等。在上面的文件读取例子中,即使在try
块中出现异常,finally
块中的bufferedReader.close();
和fileReader.close();
也会被执行,确保文件资源被正确关闭,避免资源泄漏。NullPointerException
String str = null;
System.out.println(str.length());
str
为null
,调用length
方法会抛出NullPointerException
。ArrayIndexOutOfBoundsException
int[] arr = {1, 2, 3};
System.out.println(arr[3]);
arr
的有效索引范围是0到2,访问arr[3]
会抛出ArrayIndexOutOfBoundsException
。NumberFormatException
Integer.parseInt
、Double.parseDouble
等方法)时抛出。例如:String str = "abc";
int num = Integer.parseInt(str);
str
不是有效的数字字符串,所以会抛出NumberFormatException
。IOException
FileNotFoundException
(当试图打开一个不存在的文件时抛出)、SocketException
(网络套接字操作出错时抛出)等。例如:try {
FileReader fileReader = new FileReader("nonexistent.txt");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
ClassNotFoundException
try {
Class.forName("NonExistentClass");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
ArithmeticException
int result = 10 / 0;
ArithmeticException
。Thread
接口/实现Runnable
接口的优缺点Thread
类
Thread
类并重写run
方法,就可以创建一个新的线程并执行自定义的任务。例如:class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程正在运行");
}
}
public class ThreadInheritanceExample {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
- 可以直接在子类中访问`Thread`类的其他方法,如获取线程的名称、优先级等。
Thread
类。例如,如果一个类MyClass
继承了AnotherClass
,就无法再继承Thread
类来实现多线程功能,这可能会限制类的扩展性。Thread
类会将线程相关的代码和业务逻辑代码耦合在一起,不利于代码的维护和复用。如果有多个不同的业务逻辑需要使用多线程,每个业务逻辑都创建一个继承自Thread
类的子类,会导致代码结构混乱,并且如果需要修改线程相关的行为,可能需要在多个子类中进行重复修改。Runnable
接口
Runnable
接口的同时继承其他类,实现了更灵活的类层次结构。例如:class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("线程正在运行");
}
}
public class RunnableImplementationExample {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start();
}
}
- 更适合多个线程共享同一个资源的情况。因为多个线程可以共享实现了`Runnable`接口的同一个对象,通过该对象来访问和操作共享资源,便于实现资源的统一管理和同步控制。例如,多个线程共同对一个计数器进行操作,可以将计数器的操作逻辑放在实现`Runnable`接口的类中,然后多个线程共享这个类的实例。
- 从代码设计角度看,将线程执行的任务(`run`方法中的逻辑)和线程本身的创建和管理分离开来,使得代码结构更清晰,更易于维护和复用。不同的任务可以通过实现`Runnable`接口来定义,然后根据需要创建不同的线程来执行这些任务。
Thread
对象并将实现Runnable
接口的对象作为参数传递给Thread
构造函数,然后再启动线程。相比直接继承Thread
类,代码量可能会稍多一些。Thread
类的一些非静态方法(如获取当前线程对象等),需要通过Thread.currentThread()
等方式来获取当前线程的相关信息,在一定程度上增加了代码的编写难度。Thread
类中的常见方法start
方法
run
方法开始执行线程的任务。例如:class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程开始执行");
}
}
public class ThreadStartExample {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
start
方法,否则会抛出IllegalThreadStateException
异常。run
方法
run
方法来定义线程的具体行为。例如:class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("线程执行中:" + i);
}
}
}
public class ThreadRunExample {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start();
}
}
sleep
方法(static
方法)
class MyThread extends Thread {
@Override
public void run() {
try {
System.out.println("线程开始睡眠");
Thread.sleep(2000);
System.out.println("线程睡眠结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadSleepExample {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
sleep
方法可能会抛出InterruptedException
异常,需要在try - catch
块中处理,该异常通常在其他线程调用当前线程的interrupt
方法时抛出,表示当前线程的睡眠被中断。yield
方法(static
方法)
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) {
Thread.yield();
}
System.out.println("线程执行:" + i);
}
}
}
public class ThreadYieldExample {
public static void main(String[] args) {
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
thread1.start();
thread2.start();
}
}
join
方法
join
方法,会使当前线程阻塞,直到被调用join
方法的线程执行完毕。例如:class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("子线程执行:" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadJoinExample {
public static void main(String[] args) throws InterruptedException {
MyThread thread = new MyThread();
thread.start();
thread.join();
System.out.println("子线程执行完毕,主线程继续执行");
}
}
join
方法可以传入一个超时时间参数,表示最多等待指定的时间,如果超过该时间,当前线程会继续执行,而不管被等待的线程是否执行完毕。interrupt
方法
sleep
、wait
等方法导致的阻塞)时,调用interrupt
方法会抛出InterruptedException
异常,使线程提前结束阻塞状态。如果线程没有处于阻塞状态,interrupt
方法会设置线程的中断标志位,可以通过isInterrupted
方法来检查线程的中断状态。例如:class MyThread extends Thread {
@Override
public void run() {
try {
while (!isInterrupted()) {
System.out.println("线程执行中");
Thread.sleep(1000);
}
} catch (InterruptedException e) {
System.out.println("线程被中断");
}
}
}
public class ThreadInterruptExample {
public static void main(String[] args) throws InterruptedException {
MyThread thread = new MyThread();
thread.start();
Thread.sleep(3000);
thread.interrupt();
}
}
getName
和setName
方法
getName
方法用于获取线程的名称,setName
方法用于设置线程的名称。默认情况下,线程的名称是自动生成的,如Thread - 0
、Thread - 1
等。可以通过setName
方法为线程设置一个更有意义的名称,方便在调试和日志记录中识别线程。例如:class MyThread extends Thread {
public MyThread(String name) {
setName(name);
}
@Override
public void run() {
System.out.println("线程名称:" + getName());
}
}
public class ThreadNameExample {
public static void main(String[] args) {
MyThread thread1 = new MyThread("线程1");
MyThread thread2 = new MyThread("线程2");
thread1.start();
thread2.start();
}
}
getPriority
和setPriority
方法
getPriority
方法用于获取线程的优先级,setPriority
方法用于设置线程的优先级。线程的优先级是一个整数,范围从1(最低优先级)到10(最高优先级),默认优先级是5。优先级较高的线程在竞争CPU资源时更有可能被优先执行,但不能保证高优先级线程一定会先执行,因为线程的调度最终还是由操作系统决定。例如:class MyThread extends Thread {
public MyThread(String name, int priority) {
setName(name);
setPriority(priority);
}
@Override
public void run() {
System.out.println("线程名称:" + getName() + ",优先级:" + getPriority());
}
}
public class ThreadPriorityExample {
public static void main(String[] args) {
MyThread thread1 = new MyThread("线程1", 3);
MyThread thread2 = new MyThread("线程2", 7);
thread1.start();
thread2.start();
}
}
Statement
,PreparedStatement
的区别Statement
Statement statement = connection.createStatement();
String sql = "SELECT * FROM users WHERE age > 25";
ResultSet resultSet = statement.executeQuery(sql);
- 在执行查询时,直接将完整的SQL语句传递给`executeQuery`方法。如果要执行插入、更新或删除操作,可以使用`executeUpdate`方法,例如:
String insertSql = "INSERT INTO users (name, age) VALUES ('John', 30)";
int rowsAffected = statement.executeUpdate(insertSql);
PreparedStatement
PreparedStatement
对象时,SQL语句中的参数使用占位符(通常是?
)表示。例如:PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM users WHERE age >?");
preparedStatement.setInt(1, 25);
ResultSet resultSet = preparedStatement.executeQuery();
- 这里使用`setInt`方法为占位符`?`设置实际的值,参数`1`表示第一个占位符(占位符的索引从1开始)。对于插入、更新和删除操作,也可以类似地使用`setXXX`方法设置参数,然后调用`executeUpdate`方法,例如:
PreparedStatement insertStatement = connection.prepareStatement("INSERT INTO users (name, age) VALUES (?,?)");
insertStatement.setString(1, "Alice");
insertStatement.setInt(2, 28);
int rowsInserted = insertStatement.executeUpdate();
PreparedStatement
通常性能更好,因为它的SQL语句只需要编译一次(在创建PreparedStatement
对象时进行预编译),然后可以多次使用不同的参数值执行。而Statement
每次执行SQL语句时都需要进行编译,当需要多次执行相同结构但参数不同的SQL语句时,PreparedStatement
的性能优势更加明显。PreparedStatement
可以有效防止SQL注入攻击。由于参数是通过占位符传递并由数据库进行类型安全处理,恶意用户无法通过构造特殊的参数值来改变SQL语句的逻辑。而Statement
如果直接将用户输入的内容嵌入到SQL语句中,就容易受到SQL注入攻击。例如,如果用户输入的内容被直接拼接到Statement
的SQL语句中,恶意用户可能输入'; DROP TABLE users; --
这样的字符串,导致数据库表被删除(假设SQL语句是SELECT * FROM users WHERE name = '用户输入内容'
)。而使用PreparedStatement
,用户输入会被正确地作为参数处理,不会影响SQL语句的结构。PreparedStatement
在处理动态SQL语句时更加灵活,只需要改变参数值就可以执行不同条件的查询或更新操作,而不需要重新编写整个SQL语句。对于Statement
,如果SQL语句的条件部分需要动态变化,可能需要通过字符串拼接来构建SQL语句,这不仅容易出错,而且代码可读性较差。PreparedStatement
的SQL语句结构相对固定,参数化处理使得代码更易于理解和维护。在代码中可以清晰地看到SQL语句的逻辑结构,而不需要在大量的字符串拼接中查找SQL语句的关键部分。而Statement
的SQL语句如果包含大量的动态拼接部分,维护起来会比较困难,尤其是当SQL语句比较复杂时。BufferedInputStream
和BufferedOutputStream
(缓冲流)
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("input.txt"))) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = bis.read(buffer))!= -1) {
// 处理读取到的数据
System.out.write(buffer, 0, bytesRead);
}
} catch (IOException e) {
e.printStackTrace();
}
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("output.txt"))) {
String data = "这是要写入文件的数据";
bos.write(data.getBytes());
bos.flush();
} catch (IOException e) {
e.printStackTrace();
}
BufferedReader
和BufferedWriter
(字符缓冲流,用于处理文本文件)
BufferedReader
在读取文本文件时,会从文件中读取一块字符数据到缓冲区,然后按行读取缓冲区中的数据,提供了更高效的文本读取方式,尤其是在处理大文件时。BufferedWriter
在写入文本文件时,将字符数据先写入缓冲区,在合适的时候(如缓冲区满或手动刷新)将数据写入文件,提高了写入效率。try (BufferedReader br = new BufferedReader(new FileReader("input.txt"))) {
String line;
while ((line = br.readLine())!= null) {
// 处理读取到的每行文本
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
try (BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"))) {
bw.write("这是一行文本数据\n");
bw.write("这是另一行文本数据");
bw.flush();
} catch (IOException e) {
e.printStackTrace();
}
FileChannel
(通道流,用于直接内存访问,效率较高)
FileChannel
是基于通道和缓冲区的I/O方式,它可以直接在内存和文件之间进行数据传输,减少了数据在用户空间和内核空间之间的复制次数,从而提高了读写效率。它可以与ByteBuffer
配合使用,通过将数据读取到ByteBuffer
中或从ByteBuffer
写入数据到文件。try (FileChannel channel = new FileInputStream("input.txt").getChannel()) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead;
while ((bytesRead = channel.read(buffer))!= -1) {
buffer.flip();
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
buffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
}
try (FileChannel channel = new FileOutputStream("output.txt").getChannel()) {
ByteBuffer buffer = ByteBuffer.wrap("这是要写入文件的数据".getBytes());
channel.write(buffer);
} catch (IOException e) {
e.printStackTrace
### 25、为了更快地读写文件要用什么流?(续)
- **使用示例(写入文件)**
```java
try (FileChannel channel = new FileOutputStream("output.txt").getChannel()) {
ByteBuffer buffer = ByteBuffer.wrap("这是要写入文件的数据".getBytes());
channel.write(buffer);
} catch (IOException e) {
e.printStackTrace();
}
MappedByteBuffer
(内存映射文件,适用于处理大文件)
MappedByteBuffer
通过将文件直接映射到内存中,使得对文件的读写操作就像在内存中操作数组一样高效。它利用了操作系统的内存映射机制,减少了数据的拷贝和系统调用的开销。当对映射区域进行写入操作时,数据会直接写入文件对应的内存区域,操作系统会在适当的时候将数据同步到磁盘文件中。try (RandomAccessFile file = new RandomAccessFile("input.txt", "r");
FileChannel channel = file.getChannel()) {
MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
} catch (IOException e) {
e.printStackTrace();
}
try (RandomAccessFile file = new RandomAccessFile("output.txt", "rw");
FileChannel channel = file.getChannel()) {
MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 1024);
buffer.put("这是写入内存映射文件的数据".getBytes());
} catch (IOException e) {
e.printStackTrace();
}
BufferedInputStream
/BufferedOutputStream
或BufferedReader
/BufferedWriter
通常已经能够提供较好的性能提升,并且使用相对简单。FileChannel
和MappedByteBuffer
可能会更有优势,因为它们能够更有效地利用系统资源进行高效的读写操作,但它们的使用相对复杂一些,需要对内存管理和操作系统的文件处理机制有一定的了解。BufferedReader
/BufferedWriter
是比较方便的选择,它们提供了按行读取和写入文本的功能,更符合文本处理的需求。BufferedInputStream
/BufferedOutputStream
或FileChannel
配合ByteBuffer
可以更好地处理字节数据的读写。MappedByteBuffer
也适用于二进制大文件的处理,特别是在需要随机访问文件内容的情况下。FileChannel
和MappedByteBuffer
提供了更方便的随机访问功能。例如,FileChannel
的position
方法可以设置文件指针的位置,从而实现在指定位置进行读写操作,MappedByteBuffer
也可以通过改变索引位置来实现随机访问。java.net
包
Socket
类和ServerSocket
类
Socket
类用于实现客户端套接字,通过它可以与服务器建立连接并进行数据传输。例如,编写一个简单的TCP客户端程序:ServerSocket
类用于实现服务器套接字,在指定端口监听客户端连接请求,并创建Socket
对象与客户端进行通信。例如,编写一个简单的TCP服务器程序: