Java基础知识

Java基础知识

🤔 面向对象期末考试前三天速成Java大师

变量

  • 局部变量 方法 语句块中
  • 成员变量 类中 方法外
  • 类变量/静态变量
    • 类中 方法外
    • static
    • 与类相关 不与实例相关

修饰符

在Java中,修饰符是用来修饰类、方法、变量以及其他数据类型的关键字。它们提供了额外的信息,用于控制访问级别、继承性、特定行为等。以下是一些常见的Java修饰符:

  1. 访问修饰符(Access Modifiers):

    • public: 公共的,可以被任何类访问。
    • protected: 受保护的,可以被同一包内的类及其子类访问。
    • default (包级别): 如果没有指定修饰符,默认为包级别,可以被同一包内的类访问。
    • private: 私有的,只能在声明它的类内部访问。
  2. 非访问修饰符:

    • final: 表示最终的,不可更改的。用于修饰类、方法、变量。
    • abstract: 用于声明抽象类和抽象方法。
    • static: 表示静态的,属于类而不是实例。用于方法、变量、代码块。
    • transient: 用于标记不希望序列化的字段。
    • volatile: 用于多线程编程,确保变量的可见性。
  3. 其他修饰符:

    • synchronized: 用于多线程同步,修饰方法或代码块。
    • native: 表示一个方法用其他编程语言(如C、C++)实现,通常与JNI(Java Native Interface)一起使用。
    • strictfp: 用于确保浮点运算在不同平台上产生相同的结果。
    • default (接口中): 用于指定接口中的默认方法实现。
  4. 注解修饰符:

    • @Override: 表示该方法覆盖了父类的方法。
    • @Deprecated: 表示该元素(类、方法等)已过时,不推荐使用。
    • @SuppressWarnings: 抑制编译器警告。
    • 其他自定义注解。

这些修饰符可以根据需要进行组合使用,以满足特定的编程需求。例如,一个方法可以同时使用publicstaticfinal修饰符。

  • 一个源文件中只能有一个 public 类
  • 一个源文件可以有多个非 public 类
  • 源文件的名称应该和 public 类的类名保持一致。例如:源文件中 public 类的类名是 Employee,那么源文件应该命名为Employee.java。

常见类

character类

Java中的Character类是一个包装类,用于表示字符类型数据(Unicode字符)。它提供了许多用于处理字符的方法和常量。

以下是一些Character类的常用方法和常量:

  1. 静态方法:

    • isDigit(char ch):检查字符是否为数字字符。
    • isLetter(char ch):检查字符是否为字母字符。
    • isLetterOrDigit(char ch):检查字符是否为字母或数字字符。
    • isUpperCase(char ch):检查字符是否为大写字母。
    • isLowerCase(char ch):检查字符是否为小写字母。
    • toUpperCase(char ch):将字符转换为大写。
    • toLowerCase(char ch):将字符转换为小写。
  2. 常量:

    • MIN_VALUEchar类型的最小值,即 \u0000
    • MAX_VALUEchar类型的最大值,即 \uffff
    • MIN_RADIX:进制的最小值,即 2。
    • MAX_RADIX:进制的最大值,即 36。
    • SIZEchar类型的位数,通常为 16。

下面是一个示例代码,演示了如何使用Character类的一些方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class CharacterExample {
public static void main(String[] args) {
char ch = 'A';

System.out.println(Character.isDigit(ch)); // false
System.out.println(Character.isLetter(ch)); // true
System.out.println(Character.isLetterOrDigit(ch)); // true
System.out.println(Character.isUpperCase(ch)); // true
System.out.println(Character.isLowerCase(ch)); // false

char lowercaseCh = Character.toLowerCase(ch);
System.out.println(lowercaseCh); // 'a'

char uppercaseCh = Character.toUpperCase(ch);
System.out.println(uppercaseCh); // 'A'
}
}

string类

在Java中,String类是一个非常常用的类,用于表示和操作字符串。它是不可变(immutable)的,意味着一旦创建,就不能更改其内容。String类提供了许多方法来处理字符串,如下所示:

  1. 创建字符串:

    • 使用双引号:String str = "Hello, World!";
    • 使用new关键字:String str = new String("Hello, World!");
  2. 字符串长度:

    • int length(): 返回字符串的长度。
  3. 字符串连接:

    • String concat(String str): 将指定的字符串连接到原始字符串的末尾。
    • 使用加号(+)运算符:String result = str1 + str2;
  4. 字符串提取:

    • char charAt(int index): 返回指定索引位置的字符。
    • String substring(int beginIndex): 返回从指定索引开始到字符串末尾的子字符串。
    • String substring(int beginIndex, int endIndex): 返回从指定的开始索引到结束索引之间的子字符串(不包括结束索引)。
  5. 字符串查找:

    • int indexOf(String str): 返回指定字符串在原始字符串中第一次出现的索引。
    • int lastIndexOf(String str): 返回指定字符串在原始字符串中最后一次出现的索引。
    • boolean contains(CharSequence sequence): 检查原始字符串是否包含指定的字符序列。
  6. 字符串替换:

    • String replace(char oldChar, char newChar): 将原始字符串中的所有旧字符替换为新字符。
    • String replace(CharSequence target, CharSequence replacement): 将原始字符串中的所有目标字符序列替换为指定的替换字符序列。
  7. 字符串拆分:

    • String[] split(String regex): 使用给定的正则表达式将字符串拆分为子字符串数组。
  8. 字符串转换:

    • char[] toCharArray(): 将字符串转换为字符数组。
    • byte[] getBytes(): 将字符串转换为字节数组。
    • int parseInt(String str): 将字符串解析为整数。
  9. 字符串比较:

    • boolean equals(Object obj): 检查字符串是否与指定对象相等。
    • boolean equalsIgnoreCase(String anotherString): 检查字符串是否与指定字符串相等,忽略大小写。
    • int compareTo(String anotherString): 按字典顺序比较两个字符串。
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class StringExample {
public static void main(String[] args) {
String str = "Hello, World!";

int length = str.length();
System.out.println("Length: " + length); // 13

String concatStr = str.concat(" Welcome!");
System.out.println("Concatenated String: " + concatStr); // Hello, World! Welcome!

char charAtIndex = str.charAt(4);
System.out.println("Character at index 4: " + charAtIndex); // o

String subString = str.substring(7);
System.out.println("Substring from index 7: " + subString); // World!

int indexOf = str.indexOf("o");
System.out.println("First index of 'o': " + indexOf); // 4

String replacedStr = str.replace("o", "x");
System.out.println("Replaced String: " + replacedStr); // Hellx, Wxrld!

String[] splitArray = str.split(",");
System.out.println("Split String:");
for (String s : splitArray) {
System.out.println(s);
}
/*
Output:
Split String:
Hello
World!
*/

char[] charArray = str.toCharArray();
System.out.println("Character Array:");
for (char c : charArray) {
System.out.println(c);
}
/*
Output:
Character Array:
H
e
l
l
o
,

W

stringBuffer

在Java中,StringBuffer类是一个可变的字符串类,用于处理可变字符串。与String类不同,StringBuffer类的内容可以修改。StringBuffer类提供了许多方法来对字符串进行修改和操作。

以下是StringBuffer类的一些常用方法:

  1. 创建StringBuffer对象:

    • StringBuffer sb = new StringBuffer();:创建一个空的StringBuffer对象。
    • StringBuffer sb = new StringBuffer("Hello");:使用指定的字符串创建一个StringBuffer对象。
  2. 追加和插入操作:

    • StringBuffer append(String str):在当前字符串的末尾追加指定的字符串。
    • StringBuffer insert(int offset, String str):在指定的偏移量位置插入指定的字符串。
  3. 删除操作:

    • StringBuffer delete(int start, int end):删除指定索引范围内的字符。
    • StringBuffer deleteCharAt(int index):删除指定索引位置的字符。
  4. 修改操作:

    • void setCharAt(int index, char ch):将指定索引位置的字符设置为给定字符。
    • void replace(int start, int end, String str):将指定索引范围内的字符替换为给定字符串。
  5. 反转字符串:

    • StringBuffer reverse():反转当前字符串。
  6. 获取字符串长度:

    • int length():返回当前字符串的长度。
  7. 转换为字符串:

    • String toString():将当前字符串缓冲区转换为String对象。

下面是一个示例代码,演示了如何使用StringBuffer类的一些方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class StringBufferExample {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("Hello");

sb.append(", World!");
System.out.println(sb.toString()); // Hello, World!

sb.insert(5, " Java");
System.out.println(sb.toString()); // Hello Java, World!

sb.delete(5, 9);
System.out.println(sb.toString()); // Hello, World!

sb.setCharAt(6, 'J');
System.out.println(sb.toString()); // Hello, Jorld!

sb.reverse();
System.out.println(sb.toString()); // !dlroJ ,olleH

int length = sb.length();
System.out.println("Length: " + length); // 13
}
}

上述代码中,我们首先创建了一个StringBuffer对象sb,并使用其方法进行追加、插入、删除、修改和反转操作。最后,我们获取了字符串的长度并输出结果。

需要注意的是,StringBuffer类是线程安全的,适用于多线程环境。如果在单线程环境下使用,建议使用效率更高但线程不安全的StringBuilder类。

Java面向对象

继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Animal { 
private String name;
private int id;
public Animal(String myName, int myid) {
name = myName;
id = myid;
}
public void eat(){
System.out.println(name+"正在吃");
}
public void sleep(){
System.out.println(name+"正在睡");
}
public void introduction() {
System.out.println("大家好!我是" + id + "号" + name + ".");
}
}
1
2
3
4
5
6
public class Penguin extends Animal { 
public Penguin(String myName, int myid) {
super(myName, myid);
//可以用super()通过父类的公有方法(public 方法)来访问和设置 private 成员
}
}
  • Java 不支持多继承,但支持多重继承。

  • 子类拥有父类非 private 的属性、方法。

  • extends

1
2
3
4
5
6
7
8
9
10
11
12
public class Animal { 
private String name;
private int id;
public Animal(String myName, int myid) {
//初始化属性值
}
public void eat() { //吃东西方法的具体实现 }
public void sleep() { //睡觉方法的具体实现 }
}

public class Penguin extends Animal{
}
  • implements

使用 implements 关键字可以变相的使java具有多继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口(接口跟接口之间采用逗号分隔)。

1
2
3
4
5
6
7
8
9
10
11
public interface A {
public void eat();
public void sleep();
}

public interface B {
public void show();
}

public class C implements A,B {
}
  • super 和 this
    • super关键字:我们可以通过super关键字来实现对父类成员的访问,用来引用当前对象的父类。
    • this关键字:指向自己的引用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Animal {
void eat() {
System.out.println("animal : eat");
}
}

class Dog extends Animal {
void eat() {
System.out.println("dog : eat");
}
void eatTest() {
this.eat(); // this 调用自己的方法
super.eat(); // super 调用父类方法
}
}

public class Test {
public static void main(String[] args) {
Animal a = new Animal();
a.eat();
Dog d = new Dog();
d.eatTest();
}
}
  • final

    • 使用 final 关键字声明类,就是把类定义定义为最终类不能被继承
    • 或者用于修饰方法,该方法不能被子类重写
  • 构造器

    • 子类是不继承父类的构造器(构造方法或者构造函数)的,它只是调用(隐式或显式)。
    • 如果父类的构造器带有参数,则必须在子类的构造器中显式地通过 super 关键字调用父类的构造器并配以适当的参数列表。
    • 如果父类构造器没有参数,则在子类的构造器中不需要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器

重写

在Java中,方法的重写是指子类定义了一个与其父类中方法签名相同方法名、参数列表、返回类型相同)的方法。重写(Override)发生在继承关系中,子类通过重写父类的方法来提供自己的实现。这样做的主要目的是为了在子类中修改或扩展父类的行为。

以下是方法重写的基本规则:

  1. 方法签名: 重写的方法与父类方法具有相同的方法签名,包括方法名、参数列表和返回类型。

  2. 访问修饰符: 重写的方法的访问修饰符不能比父类中被重写的方法的访问修饰符更严格。例如,如果父类中的方法是public,那么子类中的重写方法也必须是public

  3. 返回类型: 重写的方法的返回类型必须与被重写方法的返回类型相同或是其子类。

  4. 抛出异常: 如果被重写的方法在父类中声明了异常,那么子类中重写的方法的声明异常不能超出父类方法声明的异常。子类可以不抛出异常或者只抛出父类方法声明的异常的子类。

下面是一个简单的例子:

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
class Animal {
void makeSound() {
System.out.println("Animal makes a sound");
}
}

class Dog extends Animal {
// 子类重写父类方法
@Override
void makeSound() {
System.out.println("Dog barks");
}

// 子类可以新增其他方法
void fetch() {
System.out.println("Dog fetches a ball");
}
}

public class Main {
public static void main(String[] args) {
Animal animal = new Dog();
animal.makeSound(); // 调用的是Dog类的makeSound()方法
}
}

在这个例子中,Dog 类继承了 Animal 类,并重写了 makeSound 方法。在 Main 类中,创建了一个 Dog 对象,并通过 Animal 类型的引用调用了 makeSound 方法。这样的调用会执行 Dog 类中重写的方法,而不是 Animal 类中的原始方法。这就是多态的一种体现。

重载

方法的重载(Overloading)是指在一个类中可以定义多个方法,这些方法具有相同的名字但具有不同的参数列表。在方法重载中,方法名相同,但参数类型、参数个数或者参数顺序不同

重载的目的是提高代码的灵活性和可读性,使得开发者可以用一致的方式来命名不同版本的同一种操作。

以下是方法重载的基本规则:

  1. 方法名必须相同。
  2. 参数列表必须不同,包括参数类型、参数个数或者参数顺序。
  3. 返回类型可以相同也可以不同。
  4. 可以有不同的访问修饰符。
  5. 重载方法可以声明新的或更广泛的检查异常。

下面是一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Calculator {
// 两个整数相加
public int add(int a, int b) {
return a + b;
}

// 三个整数相加
public int add(int a, int b, int c) {
return a + b + c;
}

// 两个浮点数相加
public double add(double a, double b) {
return a + b;
}

// 字符串连接
public String concatenate(String str1, String str2) {
return str1 + str2;
}
}

在这个例子中,Calculator 类定义了多个名为 add 的方法,它们的参数列表分别为两个整数、三个整数和两个浮点数。这就是方法的重载。同样,还有一个 concatenate 方法,用于字符串连接,也是方法重载的一种形式。

调用这些方法时,编译器会根据实际参数的数量和类型来确定调用哪个版本的方法。例如:

1
2
3
4
5
Calculator calculator = new Calculator();
int result1 = calculator.add(5, 10); // 调用两个整数相加的方法
int result2 = calculator.add(5, 10, 15); // 调用三个整数相加的方法
double result3 = calculator.add(5.5, 10.5); // 调用两个浮点数相加的方法
String result4 = calculator.concatenate("Hello", " World"); // 调用字符串连接的方法

多态

多态是同一个行为具有多个不同表现形式或形态的能力。

多态就是同一个接口,使用不同的实例而执行不同操作

  • 必要条件

    • 继承
    • 重写
    • 父类引用指向子类对象:Parent p = new Child();
  • 当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法

  • 多态的好处:可以使程序有良好的扩展,并可以对所有类的对象进行通用处理。

抽象类

在Java中,抽象类(Abstract Class)是一种特殊的类,它不能被实例化,用于提供其他类的共同抽象和部分实现,所以抽象类必须被继承,才能被使用。抽象类可以包含抽象方法和非抽象方法。

关键点和特征:

  1. 关键字 abstract 抽象类使用关键字 abstract 声明。在抽象类中可以包含抽象方法和非抽象方法。抽象方法是没有具体实现的方法,需要在具体的子类中被实现

    1
    2
    3
    4
    5
    6
    abstract class Animal {
    abstract void makeSound(); // 抽象方法
    void sleep() {
    System.out.println("Animal sleeps"); // 非抽象方法
    }
    }
  2. 不能实例化: 由于抽象类包含抽象方法,不能被直接实例化。可以通过继承抽象类并提供抽象方法的实现来创建具体的子类

  3. 继承和实现: 子类继承自抽象类,可以选择性地实现抽象方法。如果子类是非抽象类,它必须提供所有抽象方法的具体实现;如果子类也是抽象类,可以选择性地实现抽象方法,或者继续将它标记为抽象。

    1
    2
    3
    4
    5
    class Dog extends Animal {
    void makeSound() {
    System.out.println("Dog barks");
    }
    }
  4. 可以有构造方法: 抽象类可以有构造方法,用于初始化抽象类的成员变量或执行其他初始化操作。子类在实例化时,会先调用父类的构造方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    abstract class Animal {
    int age;

    Animal(int age) {
    this.age = age;
    }

    abstract void makeSound();
    }
  5. 可以包含成员变量和非抽象方法: 除了抽象方法外,抽象类可以包含成员变量、非抽象方法、静态方法等。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    abstract class Shape {
    int sides;

    Shape(int sides) {
    this.sides = sides;
    }

    void displayInfo() {
    System.out.println("This is a shape with " + sides + " sides.");
    }

    abstract double calculateArea(); // 抽象方法
    }

抽象类用于建模一些通用的特征和行为,并要求具体的子类提供实际的实现。在继承层次结构中,抽象类为多态性提供了基础。

  • \1. 抽象类不能被实例化(初学者很容易犯的错),如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象。
  • \2. 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
  • \3. 抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。
  • \4. 构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法
  • \5. 抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。

封装

  • 修改属性的可见性来限制对属性的访问(一般限制为private
  • 对每个值属性提供对外的公共方法访问,也就是创建一对赋取值方法,用于对私有属性的访问
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
/* 文件名: EncapTest.java */
public class EncapTest{

private String name;
private String idNum;
private int age;

public int getAge(){
return age;
}

public String getName(){
return name;
}

public String getIdNum(){
return idNum;
}

public void setAge( int newAge){
age = newAge;
}

public void setName(String newName){
name = newName;
}

public void setIdNum( String newId){
idNum = newId;
}
}

接口

在Java中,接口(Interface)是一种抽象类型,用于定义一组抽象方法的集合,而不包含具体的实现。接口提供了一种将类与类之间以及类与接口之间进行关联的机制,支持多继承和规范化的设计。

以下是Java接口的主要特点和用法:

  1. 定义接口: 使用 interface 关键字来声明接口。接口中的方法默认是抽象的,不包含方法体

    1
    2
    3
    interface MyInterface {
    void myMethod(); // 抽象方法
    }
  2. 实现接口: 通过 implements 关键字,一个类可以实现一个或多个接口实现接口的类必须提供接口中所有抽象方法的具体实现

    1
    2
    3
    4
    5
    6
    class MyClass implements MyInterface {
    @Override
    public void myMethod() {
    System.out.println("Implementing MyInterface");
    }
    }
  3. 多继承: 一个类可以实现多个接口,从而达到多继承的效果。这是Java中实现多继承的一种方式。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    interface InterfaceA {
    void methodA();
    }

    interface InterfaceB {
    void methodB();
    }

    class MyClass implements InterfaceA, InterfaceB {
    @Override
    public void methodA() {
    System.out.println("Implementing InterfaceA");
    }

    @Override
    public void methodB() {
    System.out.println("Implementing InterfaceB");
    }
    }
  4. 接口的默认方法和静态方法: Java 8 引入了接口的默认方法和静态方法,使得接口可以包含具体的方法实现。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    interface MyInterface {
    void myMethod(); // 抽象方法

    default void defaultMethod() {
    System.out.println("Default method in interface");
    }

    static void staticMethod() {
    System.out.println("Static method in interface");
    }
    }

    在实现类中,可以选择性地重写抽象方法,并可以直接使用默认方法和静态方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class MyClass implements MyInterface {
    @Override
    public void myMethod() {
    System.out.println("Implementing MyInterface");
    }

    // 不重写 defaultMethod,使用默认实现

    public static void main(String[] args) {
    MyClass myObject = new MyClass();
    myObject.myMethod();
    myObject.defaultMethod();
    MyInterface.staticMethod();
    }
  5. 接口的继承: 一个接口可以继承另一个接口,通过 extends 关键字。

    1
    2
    3
    4
    5
    6
    7
    interface InterfaceA {
    void methodA();
    }

    interface InterfaceB extends InterfaceA {
    void methodB();
    }

枚举

在Java中,枚举(Enum)是一种特殊的数据类型,用于定义包含固定常量值的有限集合。枚举类型在Java中是一种引用数据类型,它可以包含字段、方法和构造方法。

以下是Java中枚举的基本用法和特点:

  1. 定义枚举: 使用 enum 关键字来定义枚举类型。枚举中的每个值都是枚举类型的一个实例。

    1
    2
    3
    enum Day {
    SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY
    }

    在上面的例子中,Day 枚举包含七个实例,分别代表星期的每一天。

  2. 枚举常量: 枚举的每个值被称为枚举常量。在上面的例子中,SUNDAYMONDAY 等就是枚举常量。

  3. 访问枚举常量: 枚举常量可以通过枚举类型的名称访问。

    1
    Day today = Day.MONDAY;
  4. 枚举可以有字段、方法和构造方法: 与普通类一样,枚举可以包含字段、方法和构造方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    enum Day {
    SUNDAY("Sun"), MONDAY("Mon"), TUESDAY("Tue"), WEDNESDAY("Wed"),
    THURSDAY("Thu"), FRIDAY("Fri"), SATURDAY("Sat");

    private final String abbreviation;

    Day(String abbreviation) {
    this.abbreviation = abbreviation;
    }

    public String getAbbreviation() {
    return abbreviation;
    }
    }

    在这个例子中,Day 枚举包含了一个字段 abbreviation,一个构造方法和一个获取缩写的方法。

  5. 枚举的比较: 枚举常量之间可以使用 == 运算符进行比较。

    1
    2
    3
    4
    5
    6
    Day day1 = Day.MONDAY;
    Day day2 = Day.MONDAY;

    if (day1 == day2) {
    System.out.println("Both are the same day");
    }
  6. switch语句和枚举: 枚举类型特别适合在 switch 语句中使用,因为它可以列举所有可能的情况。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Day day = Day.MONDAY;

    switch (day) {
    case MONDAY:
    System.out.println("It's Monday");
    break;
    case TUESDAY:
    System.out.println("It's Tuesday");
    break;
    // 其他情况...
    }

泛型

泛型(Generics)是Java语言中的一个重要特性,它允许在类、接口和方法的定义中使用一个或多个类型参数,以实现代码的重用和类型安全性。

通过使用泛型,可以编写通用的代码,可以在不指定具体类型的情况下定义类、接口或方法。这使得代码可以适用于不同类型的数据,提高了代码的灵活性和可重用性。

泛型的主要目的是在编译时执行类型检查,以避免在运行时出现类型转换错误。它提供了类型安全性,可以在编译时捕获和修复类型错误,而不是在运行时抛出异常。

使用泛型的常见场景包括:

  1. 泛型类(Generic Class):定义一个类时,可以使用泛型来表示其中的一个或多个类型参数。例如,ArrayList<T> 是一个泛型类,可以在创建对象时指定具体的类型参数,如 ArrayList<String>

  2. 泛型接口(Generic Interface):类似于泛型类,泛型接口允许在接口中使用类型参数。例如,List<T> 是一个泛型接口,可以在实现接口时指定具体的类型参数。

  3. 泛型方法(Generic Method):在方法的定义中使用泛型类型参数。这允许方法在调用时接受不同类型的参数,并且可以在方法内部使用泛型类型进行操作。例如,<T> T getFirst(List<T> list) 是一个泛型方法,可以返回列表中的第一个元素,并且可以适用于不同类型的列表。

泛型的好处包括:

  1. 类型安全性:泛型提供了编译时的类型检查,可以在编译时捕获类型错误,避免在运行时出现类型转换错误。

  2. 代码重用:通过使用泛型,可以编写通用的代码,可以适用于不同类型的数据,提高了代码的灵活性和可重用性。

  3. 代码简洁性:使用泛型可以减少手动的类型转换代码,使代码更加简洁和易读。

一个包内的类可以互相访问,即它们之间的访问级别是包级别(默认级别)。在Java中,默认情况下,如果没有明确指定访问修饰符,类、方法和变量的访问级别就是包级别。

这意味着同一包内的类可以相互访问彼此的包级别成员。下面是一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 在 com.example.myapp 包中的 MyClass.java 文件
package com.example.myapp;

public class MyClass {
// 包级别的成员变量
int packageLevelVariable;

// 包级别的方法
void packageLevelMethod() {
// 可以访问同一包内的其他类的包级别成员
AnotherClass anotherClass = new AnotherClass();
anotherClass.anotherClassMethod();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 在 com.example.myapp 包中的 AnotherClass.java 文件
package com.example.myapp;

public class AnotherClass {
// 包级别的成员变量
int anotherClassVariable;

// 包级别的方法
void anotherClassMethod() {
// 可以访问同一包内的其他类的包级别成员
MyClass myClass = new MyClass();
System.out.println(myClass.packageLevelVariable);
}
}

在上述例子中,MyClassAnotherClass都位于com.example.myapp包内,因此它们可以访问对方的包级别成员。

Java数据结构

Java提供了丰富的数据结构和集合类库,这些类库位于java.util包下。以下是Java中一些常用的数据结构和集合:

  1. ArrayList: 动态数组,可以根据需要动态增长或缩小的数组。

    1
    2
    3
    4
    5
    import java.util.ArrayList;

    ArrayList<String> list = new ArrayList<>();
    list.add("Item 1");
    list.add("Item 2");
  2. LinkedList: 双向链表,支持快速的插入和删除操作。

    1
    2
    3
    4
    5
    import java.util.LinkedList;

    LinkedList<String> linkedList = new LinkedList<>();
    linkedList.add("Item 1");
    linkedList.add("Item 2");
  3. HashMap: 键值对的散列表实现,提供快速的查找和插入。

    1
    2
    3
    4
    5
    import java.util.HashMap;

    HashMap<String, Integer> map = new HashMap<>();
    map.put("Key 1", 1);
    map.put("Key 2", 2);
  4. HashSet: 基于HashMap的集合,不允许重复元素。

    1
    2
    3
    4
    5
    import java.util.HashSet;

    HashSet<String> set = new HashSet<>();
    set.add("Item 1");
    set.add("Item 2");
  5. TreeMap: 基于红黑树的有序映射,按照键的自然顺序或自定义顺序排序。

    1
    2
    3
    4
    5
    import java.util.TreeMap;

    TreeMap<String, Integer> treeMap = new TreeMap<>();
    treeMap.put("Key 1", 1);
    treeMap.put("Key 2", 2);
  6. TreeSet: 基于TreeMap的有序集合,不允许重复元素。

    1
    2
    3
    4
    5
    import java.util.TreeSet;

    TreeSet<String> treeSet = new TreeSet<>();
    treeSet.add("Item 1");
    treeSet.add("Item 2");
  7. Queue接口和LinkedList: 队列的实现,通常用于先进先出(FIFO)的场景。

    1
    2
    3
    4
    5
    6
    import java.util.LinkedList;
    import java.util.Queue;

    Queue<String> queue = new LinkedList<>();
    queue.add("Item 1");
    queue.add("Item 2");
  8. Stack: 栈的实现,通常用于后进先出(LIFO)的场景。

    1
    2
    3
    4
    5
    import java.util.Stack;

    Stack<String> stack = new Stack<>();
    stack.push("Item 1");
    stack.push("Item 2");
  9. PriorityQueue: 优先队列,基于堆实现,可以按照元素的优先级进行排序。

    1
    2
    3
    4
    5
    import java.util.PriorityQueue;

    PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
    priorityQueue.add(3);
    priorityQueue.add(1);

Java集合框架

Java集合框架提供了一套强大且灵活的数据结构和算法,用于存储、检索、操作和处理集合数据。这个框架包含了一系列的接口、实现类和算法,主要位于java.util包中。以下是Java集合框架的主要部分:

  • 接口(Interfaces):
  1. Collection接口: 是集合框架的根接口,表示一组对象的集合ListSetQueue 都继承自 Collection 接口。
  • List:有序的集合,允许重复元素。常用实现类有 ArrayListLinkedListVector
    • Set:不允许重复元素的集合。常用实现类有 HashSetLinkedHashSetTreeSet
    • Queue:代表一组元素的队列,通常用于实现先进先出(FIFO)的数据结构。常用实现类有 LinkedListPriorityQueue
  1. Map接口: 表示键值对的集合,每个键都映射到一个值。常用实现类有 HashMapLinkedHashMapTreeMap
  • 实现类(Classes):
  1. List接口的实现类:

    • ArrayList:动态数组实现,支持随机访问,适用于快速查找和遍历。
    • LinkedList:双向链表实现,支持快速插入和删除,适用于频繁操作集合元素的场景。
    • Vector:类似于 ArrayList,但是是线程安全的。
  2. Set接口的实现类:

    • HashSet:基于哈希表实现,不保证顺序。
    • LinkedHashSet:基于哈希表和链表实现,按照元素插入顺序保证顺序。
    • TreeSet:基于红黑树实现,按照元素的自然顺序或自定义顺序排序。
  3. Queue接口的实现类:

    • LinkedList:实现了 Queue 接口,可用于实现队列。
  4. Map接口的实现类:

    • HashMap:基于哈希表实现,键值对无序。
    • LinkedHashMap:基于哈希表和链表实现,按照键值对插入顺序保证顺序。
    • TreeMap:基于红黑树实现,按照键的自然顺序或自定义顺序排序。
  5. 其他常用类:

    • HashSetLinkedHashSetTreeSet 等都实现了 Set 接口。
    • HashMapLinkedHashMapTreeMap 等都实现了 Map 接口。
  • 工具类(Utilities):

    • Collections类: 提供了一系列静态方法,用于对集合进行操作和算法处理,如排序、反转、洗牌等。
    • Arrays类: 提供了一系列静态方法,用于操作数组,如排序、二分查找等。
  • 并发集合(Concurrent Collections):

    • ConcurrentHashMap:线程安全的哈希表实现。
    • CopyOnWriteArrayList:线程安全的动态数组实现,适用于读多写少的场景。
    • CopyOnWriteArraySet:线程安全的集合,基于 CopyOnWriteArrayList 实现。

Java线程

在Java中,线程相关的操作和指令主要涉及到以下方面:

  1. 创建线程:

    • 使用 Thread 类或实现 Runnable 接口创建线程。

    • 例如:

      1
      2
      Thread myThread = new Thread();
      myThread.start();
  2. 线程调度和控制:

    • Thread.sleep(long millis):使当前线程休眠指定的毫秒数。
    • Thread.yield():暂停当前正在执行的线程,允许其他线程执行。
    • join():等待一个线程终止。
    • interrupt():中断线程的执行。
  3. 同步和互斥:

    • synchronized 关键字:用于同步代码块或方法,确保在同一时刻只有一个线程可以访问。
    • wait()notify()notifyAll():在对象上进行等待和唤醒其他线程的操作,通常与synchronized一起使用。
  4. 线程状态控制:

    • getState():获取线程的状态。
    • isAlive():判断线程是否处于活动状态。
  5. 并发集合和工具类:

  • java.util.concurrent 包提供了一些并发集合和工具类,如 ConcurrentHashMapCountDownLatchCyclicBarrier 等,用于更方便地进行多线程编程。
  1. 线程池:
  • ExecutorServiceThreadPoolExecutor 等类用于管理线程池,提高线程的复用性和效率。
  1. 原子操作和CAS(Compare and Swap):
  • java.util.concurrent.atomic 包提供了一系列原子操作类,如 AtomicIntegerAtomicLong 等,用于在多线程环境中执行原子操作。
  1. 并行流:

    • Java 8 引入的并行流框架允许在多个线程上同时处理流的元素。

这些指令和类库提供了Java中进行多线程编程的基本工具和机制。在编写多线程代码时,要注意线程安全性、死锁和性能等问题,以确保程序的正确性和效率。

这几个方法的具体用法如下:

  1. Thread.sleep(long millis):

    • 使当前线程休眠指定的毫秒数,进入阻塞状态。

    • 用法示例:

      1
      2
      3
      4
      5
      try {
      Thread.sleep(1000); // 休眠1秒
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
  2. Thread.yield():

    • 暂停当前正在执行的线程,允许其他线程执行。

    • yield 是一个静态方法,通过调用 Thread.yield() 可以提示调度器当前线程愿意让出CPU资源。

    • 用法示例:

      1
      Thread.yield(); // 暂停当前线程,让出CPU资源
  3. join():

    • 等待一个线程终止,即等待被调用 join 方法的线程执行完毕。

    • 用法示例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      Thread thread = new Thread(() -> {
      // 线程执行的逻辑
      });
      thread.start();
      try {
      thread.join(); // 主线程等待 thread 线程执行完毕
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
  4. interrupt():

    • 中断线程的执行,给线程发送中断信号。

    • 被中断的线程需要通过检查 Thread.interrupted()isInterrupted() 方法来判断是否被中断,并做相应处理。

    • 用法示例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      Thread myThread = new Thread(() -> {
      while (!Thread.interrupted()) {
      // 线程执行的逻辑
      }
      System.out.println("线程被中断");
      });
      myThread.start();

      // 在其他地方中断线程
      myThread.interrupt();

一些关键字和方法主要用于实现线程之间的协调和同步,确保多个线程能够安全地共享资源。以下是它们的具体用法:

  1. synchronized 关键字:

    • 用于同步代码块或方法,确保在同一时刻只有一个线程可以访问被 synchronized 修饰的代码块或方法。

    • 对象级别的同步:

      1
      2
      3
      public synchronized void synchronizedMethod() {
      // 同步的代码块或方法
      }
    • 代码块级别的同步:

      1
      2
      3
      4
      5
      public void someMethod() {
      synchronized (lockObject) {
      // 同步的代码块
      }
      }
  2. wait()、notify()、notifyAll():

    • 这些方法通常与 synchronized 一起使用,用于实现线程之间的协调和通信。

    • wait():使当前线程进入等待状态,并释放对象锁,直到其他线程调用相同对象的 notify()notifyAll() 方法唤醒它。

      1
      2
      3
      4
      5
      6
      7
      synchronized (lockObject) {
      try {
      lockObject.wait(); // 等待其他线程唤醒
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      }
    • notify():唤醒在相同对象上等待的一个线程。

      1
      2
      3
      synchronized (lockObject) {
      lockObject.notify(); // 唤醒一个等待线程
      }
    • notifyAll():唤醒在相同对象上等待的所有线程。

      1
      2
      3
      synchronized (lockObject) {
      lockObject.notifyAll(); // 唤醒所有等待线程
      }

这些机制可以帮助线程之间进行协同工作,避免竞争条件和确保资源的正确共享。需要注意的是,使用 wait()、notify()、notifyAll() 时,必须在同步块或同步方法中调用,否则会抛出 IllegalMonitorStateException 异常。

Java I/O

在Java中,流(Stream)是用于处理输入和输出(I/O)操作的重要概念。流主要分为输入流和输出流,它们分别用于从外部数据源读取数据和将数据写入到外部目标。

以下是Java流的一些关键概念和总结:

  1. 输入流和输出流:

    • 输入流(InputStream): 用于从外部数据源(例如文件、网络、内存等)读取数据。
    • 输出流(OutputStream): 用于向外部目标(例如文件、网络、内存等)写入数据。
  2. 字节流和字符流:

    • 字节流: 处理字节数据,主要用于处理二进制文件。例如,FileInputStreamFileOutputStream
    • 字符流: 处理字符数据,适用于文本文件。例如,FileReaderFileWriter。字符流使用了字符集,可以更好地处理文本文件中的字符编码。
  3. 节点流和包装流:

    • 节点流: 直接连接到数据源或目标的流。例如,FileInputStreamFileOutputStream
    • 包装流(或处理流): 对节点流进行包装,提供额外的功能,如缓冲、压缩、解压等。例如,BufferedInputStreamBufferedOutputStream
  4. 字符集和编码:

    • 字符流使用字符集来处理字符编码,以确保正确的字符转换。
    • 常见的字符集包括UTF-8、UTF-16、ISO-8859-1等。
  5. 处理流的装饰器模式:

    • 处理流通常通过装饰器模式实现。你可以通过组合多个处理流,以获得更丰富的功能。
  6. 对象流:

    • ObjectInputStreamObjectOutputStream允许直接读写Java对象。这对于序列化和反序列化对象很有用。
  7. 标准I/O:

    • System.inSystem.outSystem.err分别代表标准输入、标准输出和标准错误输出。这些流通常用于从控制台读取输入和输出结果。
  8. try-with-resources:

    • Java 7引入了try-with-resources语句,使得资源的管理更加简便。可以在try语句中自动关闭实现AutoCloseable接口的资源。

Java错误和异常

在 Java 中,错误(Errors)和异常(Exceptions)是用于处理程序运行期间出现的问题的机制。它们都是从 Throwable 类派生的,但在处理方式和用途上有所区别。下面是对 Java 错误和异常的总结:

  1. 错误(Errors)

    • 错误表示严重的问题,通常是无法恢复的情况,例如虚拟机错误、系统错误、内存溢出等。
    • 错误由 JVM 抛出,并且一般不应该被程序捕获和处理
    • Error 类及其子类是用于表示错误的类型,例如 OutOfMemoryErrorStackOverflowError 等。
  2. 异常(Exceptions)

    • 异常是程序在运行过程中遇到的非正常情况,可以被捕获和处理,以便程序继续执行。
    • 异常分为两种类型:已检查异常(Checked Exceptions)和运行时异常(Unchecked Exceptions)。
    • 已检查异常是在编译时强制要求处理的异常,例如 IOExceptionSQLException 等。对于已检查异常,要么在方法中使用 throws 声明抛出,要么使用 try-catch 块进行捕获和处理。
    • 运行时异常是指那些可以在运行时检测到的异常,不需要在编译时进行处理。例如,NullPointerExceptionArrayIndexOutOfBoundsException 等。通常情况下,运行时异常是由程序错误导致的,应该尽量避免发生,但并不强制要求捕获和处理。

在处理异常时,可以使用以下几种关键字和机制:

  • try-catch 块:用于捕获和处理异常。try 块中放置可能抛出异常的代码,而 catch 块用于捕获和处理对应的异常类型。
  • throws 关键字:用于在方法签名中声明可能抛出的异常类型,将异常传递给调用者处理。
  • finally 块:在 try-catch 块之后使用,用于执行无论是否发生异常都需要执行的代码块,例如资源的释放。