【Java SE基础回顾】看这篇就够了!

JavaSE复习

参考视频:【狂神说Java】JavaSE阶段回顾总结

简单的就不讲了,比如什么break和continue区别,甚至一些什么什么继承封装多态的概念等等。 主要写一些Java特有的,重点的基础知识。主要还是例子和代码更多,更有利于理解。

java SE、 java ME、 java EE

  • Java SE (Java Standard Edition):标准版
  • Java EE (Java Enterprise Edition):企业版
  • Java ME (Java Micro Edition):微型版

JDK?JRE?

JDK (Java Development Kit)软件开发工具包

JRE(Java Runtime Environment)运行环境

如果用户只需要运行,不需要开发,那只安装JRE即可。

复习helloworld

要求做到打开记事本能手写,并用javac编译java运行。

package com.zy;public class hello {public static void main(String[] args) {System.out.println("hello world!");}
}

数据类型

在这里插入图片描述

注意C++中char是1个字节的,java中char是2个字节(java内部都是用unicode的,所以java中的char是2个字节),C++中char应该对应byte

扩展,ASCII,Unicode,utf-8的区别

Unicode是字符集,UTF-8是编码规则

<< >> 左移,右移运算符

<<< >>> 无符号左移,右移运算符,高位补0

求-15>>2 和 -15>>>2:-4和1073741820

解释:一个是带符号右移两位,一个是无符号右移两位。

java包机制

域名倒写:规范写法: 公司域名倒着写+模块名字 com.nuc.maneger

例子:比如 com.baidu.video,因为java内部实际上是以文件夹形式存在的,是按com,baidu,video依次生成文件夹的
具体功能的是子文件夹,所以要倒着写。

在这里插入图片描述

package:无论什么时候都在第一行

为了更好地组织类,Java提供了包机制,用于区别类名的命名空间。

  1. 包名与目录结构package 声明的包名需要与文件所在的目录结构严格对应。例如,假设有以下包声明:

    package com.example.myproject;
    

    该文件应该位于以下路径中:

    /path/to/project/com/example/myproject/YourClass.java
    
  2. 默认包:如果你没有指定 package,类会被放入一个默认包中。这时文件可以直接放在项目的根目录下。但在实际开发中,不推荐将类放在默认包中,因为这样会造成组织混乱和潜在的类名冲突。

一个文件只能有一个public类,且必须与文件名一直(大小写也要匹配)

javaDoc:通过注释生成程序帮助文档。

JDK帮助文档就是通过javaDoc生成的。

常见格式

/*** Method description.* @return description of return value*/
public int methodName() {}

可选注释参数:

@author:标签用于指定作者信息
@param:用于标记方法的参数说明。
@return:用于标记方法的返回值说明。
@throws 或 @exception:用于标记方法可能抛出的异常说明。
@see:用于指定参考文档的链接。
@since:用于指定API的版本信息。
@deprecated:用于标记已过时的API。
@inheritDoc:用于继承父类或接口的文档注释。
@link:类似于@see,用于创建链接到其他文档的标签。

javadoc

在这里插入图片描述

encoding指定用于读取源文件的字符编码。
charset 指定生成的 HTML 文档使用的字符集编码

Scanner对象输入文本

package com.zy;import java.util.Scanner;public class hello {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);   // 创建Scanner对象System.out.println("输入文本:");String userInput = scanner.nextLine();  // 读取用户输入的一行文本System.out.println("你输入了:" + userInput);scanner.close();}
}

foreach的使用

package com.zy;public class hello{public static void main(String[] args) {int[] numbers = {1,2,3,4,5};for(int num : numbers) {System.out.println(num);}}
}

java中带标签的break和continue

package com.zy;public class hello{public static void main(String[] args) {outLoop:for(int i=0; i<10;i++) {	// 带个标签outLoopfor(int j=0;j<5;j++) {System.out.printf("%d,%d\n",i,j);break outLoop;	// 直接跳出最外层循环。}}}
}

java可变长参数

package com.zy;class People {int a;
}public class hello{public static void test(Integer number,String...name) {System.out.println(number.getClass());System.out.println(name.getClass());System.out.println(number);for(String s : name) {System.out.println(s);}}public static void main(String[] args) {test(3, "lilin","hali");}
}

输出

class java.lang.Integer
class [Ljava.lang.String;	//[表示一维数组,L表示是String类型。(如果是二位数组则为[[)
3
lilin
hali

Arrays工具类

在java中,数组本身没有方法可以调用,于是在API中提供了工具类Arrays供我们使用。

binarySearch():在已排序的数组中查找指定元素的索引。
copyOf():将一个数组的部分或全部元素复制到一个新数组中。
copyOfRange():将一个数组的指定范围内的元素复制到一个新数组中。

1.toString():将数组转换为一个字符串,便于打印和调试。

        int[] ints = {1,2,3,4};System.out.println(ints);   // 直接打印地址:[I@1540e19dSystem.out.println(Arrays.toString(ints));  // [1, 2, 3, 4]System.out.println(Arrays.toString(ints).charAt(0));	// [

2.fill():将数组的某个范围的所有元素都设置为指定值。

        int[] arr = new int[5];Arrays.fill(arr,1,3,4);	//左开右闭System.out.println(Arrays.toString(arr));   // [0, 4, 4, 0, 0]

3.sort(数组)

        int[] arr = {4,2,6,3,8};System.out.println("升序前:"+Arrays.toString(arr));    // 升序前:[4, 2, 6, 3, 8]Arrays.sort(arr);System.out.println("升序后:"+Arrays.toString(arr));    // 升序后:[2, 3, 4, 6, 8]

4.sort(数组,排序规则)

在 Java 中,对于 基本类型(如 int[]),Arrays.sort() 只能进行升序排序。如果要实现降序排序,你需要先将数组转换为包装类型,如 Integer[],然后使用自定义的比较器 (Comparator) 进行降序排列。

        int[] array = {1, 3, 2, 5, 4};// 1.将int[]转换为Integer[]Integer[] arrInteger = Arrays.stream(array).boxed().toArray(Integer[]::new);// 2.使用 Arrays.sort() 和 Collections.reverseOrder() 进行降序排序Arrays.sort(arrInteger,Collections.reverseOrder());// 3.打印排序后的结果System.out.println(Arrays.toString(arrInteger));

或者是,先升序排序,然后将数组中元素反转。

        int[] array = {1, 3, 2, 5, 4};Arrays.sort(array);for(int i=0;i<array.length/2;i++) {int tmp = array[i];array[i] = array[array.length-i-1];array[array.length-i-1] = tmp;}System.out.println(Arrays.toString(array));

如果我想对自定义的类型数组进行排序呢?(比如:Student类的数组),这里给出两种方法:

1.实现Comparable接口,完成自定义排序

package com.zy;import java.util.Arrays;
import java.util.Collections;// 定义 Student 类,并实现 Comparable 接口
class Student implements Comparable<Student> {String name;int age;public Student(String name, int age) {this.name = name;this.age = age;}@Overridepublic int compareTo(Student other) {return this.age - other.age;}@Overridepublic String toString() {return name + ":" + age;}
}public class hello {public static void main(String[] args) {Student[] students = {new Student("Alice",22),new Student("Bob",18),new Student("Charlie",20),};Arrays.sort(students);System.out.println(Arrays.toString(students));}
}

上面方式看起来不太简洁,需要修改类本身。更灵活的方式如下:

2.利用匿名Comparator 对象,灵活地定义多种不同的排序规则。

package com.zy;import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;class Student{String name;int age;public Student(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return name + ":" + age;}
}public class hello {public static void main(String[] args) {Student[] students = {new Student("Alice",22),new Student("Bob",18),new Student("Charlie",20),};// 按年龄排序Arrays.sort(students, new Comparator<Student>() {@Overridepublic int compare(Student o1, Student o2) {return o1.age - o2.age;}});System.out.println(Arrays.toString(students));// 按姓名排序Arrays.sort(students, new Comparator<Student>() {@Overridepublic int compare(Student o1, Student o2) {return o1.name.compareTo(o2.name);}});System.out.println(Arrays.toString(students));}
}

5.copyOfRange(original,from,to)

original:第一个参数为要拷贝的数组对象
from:第二个参数为拷贝的开始位置(包含)
to:第三个参数为拷贝的结束位置(不包含)

继承,封装,多态

写一遍经典的Animal继承,体会一下Java面向对象三大特性。

package com.zy;class Animal {private String name;private int age;public Animal(String name,int age) {this.name = name;this.age = age;}// 封装:getter 和 setter 方法public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public void describe() {System.out.println("This is an animal named" + name + ", age" + age);}
}// 子类Dog继承父类Animal
class Dog extends Animal {private String breed;// 子类构造函数,调用父类的构造函数public Dog(String name, int age, String breed) {super(name, age);       // 继承:调用父类构造函数this.breed = breed;}public String getBreed() {return breed;}public void setBreed(String breed) {this.breed = breed;}@Overridepublic void describe() {super.describe();   // 调用父类describe方法System.out.println("This is a " + breed + "dog.");}// Dog 类特有方法public void bark() {System.out.println("woof woof!");}
}public class Hello {public static void main(String[] args) {Animal animal = new Animal("Generic Animal", 5);animal.describe();  // 调用 Animal 类的方法Dog dog = new Dog("Buddy",3,"Golden Retriever");dog.describe(); // 调用重写后的describe方法dog.bark();     // 调用Dog类的bark方法// 多态体现Animal animalDog = new Dog("Max",2,"Beagle");animalDog.describe();   // 调用的是子类Dog的describe方法
//        animalDog.bark();   // 错误,Animal没有bark()// 检查对象类型,使用instance ofif(animalDog instanceof Dog) {Dog specificDog = (Dog)animalDog; //向下转型specificDog.bark();}}
}

output:

This is an animal named Generic Animal, age 5
This is an animal named Buddy, age 3
This is a Golden Retriever dog.
Woof Woof!
This is an animal named Max, age 2
This is a Beagle dog.
Woof Woof!
The dog's new name is: Charlie

等等,既然animalDog instanceof Dog为真,也就是animalDog 就是Dog类型,那为什么还需要向下转型(Dog)animalDog??

如果有这种疑问,说明有两个问题:1. 对instanceof不熟悉;2.对向上转型和向下转型不熟悉。

instanceof

instanceof 是 Java 中的一个关键字,用于判断一个对象是否是某个类或其子类的实例

举个例子

现在有类的继承链如下:
Object > Person > Teacher
Object > Person > Student
Object > String
Object obj = new Student();
obj instanceof Student;	// true
obj instanceof Person;	// true
obj instanceof Object;	// true
obj instanceof String;	// false
obj instanceof Teacher;	// falseObject obj = new Person();
obj instanceof Student;	// false
obj instanceof Teacher; // falsePerson person = new Student();
person instanceof Student;	// true
person instanceof Person;	// true;
person instanceof Object; // true;
person instanceof String;	// 编译错误
person instanceof Teacher; // false// null用instanceof跟任何类型比较时都是false
null instanceof Object; // false

所以if(animalDog instanceof Dog)是判断animalDog 是否为Dog类型或其子类。

关于java中向上转型和向下转型

向上转型(Upcasting):子类对象转换为父类类型,把子当父用。向上转型通常是隐式进行的,意味着你不需要手动指定转换操作。

        Dog dog = new Dog();Animal animal = dog;  // 向上转型,隐式进行animal.makeSound();   // 输出 "Bark",即调用子类的重写方法

向上转型是安全的,不会发生 ClassCastException

访问限制:向上转型后,编译器只能识别父类的成员(属性和方法),不能访问子类特有的方法或属性。

向下转型(Downcasting):向下转型是指将父类类型的引用转换为子类类型。这种转换是显式进行的,需要手动指定转换操作,并且存在一定的风险,向下转型时,必须确保父类对象实际上是该子类的实例,否则会抛出 ClassCastException

Animal animalDog = new Dog("Max",2,"Beagle");
if(animalDog instanceof Dog) {Dog specificDog = (Dog)animalDog; //向下转型specificDog.bark();}

向下转型之前,通常使用 instanceof 关键字检查对象是否属于某个子类,以防止 ClassCastException 异常。

接口interface

注意,java是单继承,只能继承一个父类,但是可以实现多个接口。

记得本科时候Java老师说过,类描述一个实体,接口描述一组动作。

接口是 Java 中的一种引用类型,它类似于一个契约或协议,只能定义方法的签名(方法名、返回类型和参数),不能包含方法的具体实现。

class Animal {protected String name;Animal(String name) {this.name = name;}
}interface IFlying {void fly();
}interface IRuning {void run();
}interface ISwimming {void swim();
}// 猫会跑
class Cat extends Animal implements IRuning{Cat(String name) {super(name);}@Overridepublic void run() {System.out.println(this.name + "is running");}
}// 鱼会游泳
class Fish extends Animal implements ISwimming {Fish(String name) {super(name);}@Overridepublic void swim() {System.out.println(this.name + "is swimming");}
}// 狗既能跑,又能游泳
class Dog extends Animal implements IRuning, ISwimming {Dog(String name) {super(name);}@Overridepublic void run() {System.out.println(this.name + "is running");}@Overridepublic void swim() {System.out.println(this.name + "is swimming");}
}// 鸭子 水陆空三栖
class Duck extends Animal implements IRuning,ISwimming,IFlying {Duck(String name) {super(name);}@Overridepublic void run() {System.out.println(this.name + "is running");}@Overridepublic void swim() {System.out.println(this.name + "is swimming");}@Overridepublic void fly() {System.out.println(this.name + "is flying");}
}

default 方法

  • Java 8 引入了 default 方法,允许在接口中提供默认方法实现。这是为了在不破坏现有实现的情况下扩展接口。default 方法必须使用 public 访问级别,因为接口中的所有方法默认都是 public 的(即使不显式声明)
public interface MyInterface {default void doSomething() {System.out.println("Default implementation");}
}

java接口实际上是可以定义变量的,但是他们有一下特性:

隐式 public static final 修饰:接口中的变量默认是 publicstaticfinal

public:意味着接口中的变量是公开的,可以在任何地方访问。

static:意味着变量属于接口,而不是某个具体的实现类。可以通过接口名直接访问。

final:意味着变量是常量,一旦赋值就不能修改

所以,接口中的变量实际上是常量,用于共享和定义全局的常量值。

必须初始化:由于接口中的变量是 final,所以必须在定义时进行初始化。

interface MyInterface {int NUM = 1000;void doSomething();
}public class Hello implements MyInterface{@Overridepublic void doSomething() {System.out.println("NUM:" + NUM);}public static void main(String[] args) {System.out.println(NUM);System.out.println(MyInterface.NUM);}
}

函数式接口(jdk8引入)

  • 只有一个抽象方法的接口(可以有很多默认方法)被称为函数式接口。函数式接口可以使用 lambda 表达式来简化代码。在 Java 中,@FunctionalInterface 可检测接口是否符合函数式接口。

java Lambda表达式:在 Java 8 中引入了 Lambda 表达式,主要用于简化对函数式接口的实现。ambda 表达式允许你以更简洁的方式写出匿名内部类,尤其是对于需要传递行为(如排序、遍历、事件处理等)的场景。Lambda表达式语法:

(parameters) -> expression
或者
(parameters) -> { statements; }

关于Lambda的介绍可参考,要理解Lambda这个表达式实际上是一个接口类型。后面就不难理解了。

使用 Lambda 表达式简化函数式接口的例子:使用 Comparator 排序。

传统方法:

List<String> names = Arrays.asList("John", "Jane", "Doe");
Collections.sort(names, new Comparator<String>() {@Overridepublic int compare(String s1, String s2) {return s1.compareTo(s2);}
});

Lambda简化:

List<String> names = Arrays.asList("John", "Jane", "Doe");
Collections.sort(names, (s1, s2) -> s1.compareTo(s2));

java8中定义了很多函数式接口,供我们使用。用法可参考:https://blog.csdn.net/weixin_58473601/article/details/125231163。

接口与抽象类的区别

接口更抽象。接口中的所有方法默认都是 abstract 的,而抽象类可以包含具体方法,因此接口的抽象程度比抽象类更高。

内部类

局部内部类

  • 局部内部类是在方法或作用域内定义的类。它的作用范围仅限于定义它的代码块中,离开了这个范围,它就不可访问。

静态内部类

  • 静态内部类是用 static 修饰的内部类。它与外部类的实例无关,可以独立创建实例。静态内部类更像是外部类的一个静态成员,可以直接通过外部类访问。

匿名内部类(重点)

  • 匿名内部类是一种没有名字的内部类,常用于简化代码,尤其是在实现接口或继承类时。匿名内部类适合只需要使用一次的类。
  • 它通常用于实现接口的方法或者继承类时,并在实例化的同时重写其中的方法。
interface Animal {void makeSound();
}public class Main {public static void main(String[] args) {Animal cat = new Animal() {@Overridepublic void makeSound() {System.out.println("Meow");}};// 当然,也可以用lambda简化:Animal cat = () -> System.out.println("miao");cat.makeSound();  // 输出:Meow}
}

Java在类中的各种权限:public protected,private, default

访问修饰符类内部同一包内不同包中的子类不同包中的类
public
protected
default
private

注意

  1. 类的修饰符:Java 的类可以使用 publicdefault(无修饰符)。类不能使用 protectedprivate 修饰符。
  2. 接口的修饰符:接口可以是 publicdefault,而接口的所有字段默认是 public static final,所有方法默认是 public abstract(Java 8 及以上允许接口中有 defaultstatic 方法)。

异常

Throwable类(Error和Exception)

参考

Java的Throwable类是Java语言中所有错误和异常的超类。它位于java.lang包下,是Java异常处理机制的核心。Throwable类及其子类实例代表了程序执行中可能出现的异常情况和错误。在Java中,Throwable有两个主要的子类:ErrorException

  1. Error: 表示一些严重的错误,通常是不应该被应用程序尝试捕获的问题,如OutOfMemoryError,StackOverflowError等。
  2. Exception: 表示需要被应用程序捕获的异常条件,它又分为两类:
    检查型异常(Checked Exceptions): 这种异常在编译时强制要求处理,比如IOException、SQLException等。(比较严重)
    非检查型异常(Unchecked Exceptions): 这类异常包括运行时异常(RuntimeException)如NullPointerException、IndexOutOfBoundsException等,以及错误(Error)。

也就是说 检查型异常比较严重,需要处理!非检查型异常不严重,可以不处理。

五个捕获错误关键字

  • try 块:包含可能会抛出异常的代码。
  • catch 块:一旦异常发生,控制权跳到 catch 块,执行其中的异常处理代码。
  • finally 块:无论是否发生异常,finally 块中的代码始终会执行。
  • throw:手动抛出一个异常。
  • throws:在方法签名中声明该方法可能抛出的异常。
public class Hello{// 使用throws字段,方法抛出ArithmeticException错误public static int divide(int a, int b) throws ArithmeticException {if(b == 0) {throw new ArithmeticException("Cannot divide by zero!");}return a / b;}public static void main(String[] args) {try {// 尝试进行除法操作int result = divide(10, 0);System.out.println("Result:" +result);} catch (ArithmeticException e) {System.out.println("Exception caught:" + e.getMessage());} finally {System.out.println("This is the finally block. Always executed!");}}
}

try-with-resources

try-with-resources 是 Java 7 引入的一种语法,用于简化资源管理,特别是处理那些实现了 AutoCloseable 接口的资源,如输入/输出流、数据库连接等。它的主要优点是可以确保在使用完资源后自动关闭,减少了资源泄露的风险。

  1. 自动关闭资源:在 try 代码块结束时,所有在括号内声明的资源都会自动调用其 close() 方法,从而释放资源。
  2. 简化代码:通过自动管理资源的关闭,减少了手动编写 finally 代码块的需要。
  3. 异常处理:如果在 try 块中发生异常,资源依然会被关闭。同时,如果在关闭资源时也发生异常,这些异常会被捕获并可供后续处理。

语法

try (ResourceType resource1 = new ResourceType();ResourceType resource2 = new ResourceType()) {// 使用资源进行操作
} catch (ExceptionType e) {// 处理异常
}
// 资源在此处自动关闭

自定义异常

自定义异常可以继承 Exception(受检异常)或 RuntimeException(非受检异常)

1.自定义异常类,继承自 Exception

class InvalidAgeException extends Exception {public InvalidAgeException(String message) {super(message);}
}public class Hello{// 定义一个方法,抛出自定义异常public static void validateAge(int age) throws InvalidAgeException {	// 注意!!这里throws不能省略if (age < 18) {// 当年龄小于 18 时,抛出自定义异常throw new InvalidAgeException("Age must be greater than or equal to 18");} else {System.out.println("Valid age: " + age);}}public static void main(String[] args) {try {// 调用 validateAge 方法,传入一个无效的年龄validateAge(15);} catch (InvalidAgeException e) {// 捕获并处理自定义异常System.out.println("Exception caught: " + e.getMessage());}}
}

2.使用 RuntimeException 创建非受检异常

class InvalidAgeException extends RuntimeException {public InvalidAgeException(String message) {super(message);}
}

非受检异常不需要在方法签名中声明 throws,也不要求在编译时处理。也就是说,上面的validateAge可以不用throws InvalidAgeException

一些常用类

Object类

在Java中,Object类是所有类的根类。

下面是Object类中定义的一些方法

== 和 equals方法

equals 是 Object 中的方法.

== 是一个运算符,可以判断基本类型也可以判断引用类型。判断基本类型时,就是判断两者的值是否相等;判断引用类型时,就是判断两者的地址是否相等,也就是说判断实际对象是不是同一个。

equals 是 Object 中的方法,只能判断引用类型,也是通过判断两者地址是否相等。一般情况下,子类会重写这个 equals 方法,以添加判断内容是否相等的功能。

        String str1 = "Hello";String str2 = "Hello";System.out.println(str1 == str2);       // trueSystem.out.println(str1.equals(str2));  // trueString str3 = new String("Hello");String str4 = new String("Hello");System.out.println(str3 == str4);       // falseSystem.out.println(str3.equals(str4));  // true

== 操作符用于比较两个引用是否指向同一个对象,而不是比较它们的值是否相同。 所以在第一个判断中,str1str2 都是直接用字面量 "Hello" 初始化的。Java 编译器在加载字面量字符串时会将它们放入**字符串常量池(string pool)**中。如果两个字符串字面量相同,它们会引用池中的同一个对象。

字符串常量池:在使用字面量方式创建字符串时(如 String str = "Hello";),Java 会在字符串常量池中检查是否已经存在相同内容的字符串。如果存在,它会复用池中的对象。

而第二个例子中使用 new 关键字创建字符串时,每次都会在堆中分配一个新的对象,即使两个对象的内容相同。所以str3 == str4false

hashCode 方法

hashCode() 的主要目的是通过返回一个整数来表示对象的散列值(哈希码),这个值用于确定对象在基于哈希的数据结构中的位置。通过 hashCode(),可以提高这些数据结构的性能,使其能够快速插入、查找和删除数据。

Person p1 = new Person();
Person p2 = new Person();
Person p3 = p2;
System.out.println(p1.hashCode());  // 356573597
System.out.println(p2.hashCode());  // 1735600054
System.out.println(p3.hashCode());  // 1735600054

在 Java 中,hashCode()Object 类中的一个方法,所有 Java 类都继承自 Object,因此每个 Java 对象都有一个 hashCode 方法。它的作用是返回对象的散列码(哈希码),它是用于支持基于哈希的数据结构(如 HashMapHashSetHashtable 等)的内部算法的。

hashCode() 的作用

hashCode() 的主要目的是通过返回一个整数来表示对象的散列值(哈希码),这个值用于确定对象在基于哈希的数据结构中的位置。通过 hashCode(),可以提高这些数据结构的性能,使其能够快速插入、查找和删除数据。

hashCode()equals() 的关系

在使用基于哈希的数据结构时,hashCode()equals() 方法通常是一起工作的。它们之间有一个重要的约定:

  1. 如果两个对象根据 equals() 方法是相等的,那么它们的 hashCode() 必须相同
  2. 如果两个对象的 hashCode() 不相同,那么它们肯定不是相等的
  3. 如果两个对象的 hashCode() 相同,它们未必是相等的,但会被认为是在同一个哈希桶中,需要进一步通过 equals() 来确定它们是否真正相等。

来看看String类型的hashCode运行结果。

        String str1 = "Hello";String str2 = "Hello";String str3 = new String("Hello");String str4 = new String("Hello");System.out.println(str1.hashCode()+" "+str2.hashCode());	// 69609650 69609650System.out.println(str3.hashCode()+" "+str4.hashCode());	// 69609650 69609650

在 Java 中,hashCode() 方法的默认实现会根据对象的内存地址来生成哈希值。然而,对于像 String 这样的类,hashCode() 方法已经被重写,基于字符串的内容(字符序列)来生成哈希值,而不是对象的内存地址。这是为什么你会看到这些字符串对象的哈希码是相等的,即使它们是不同的对象。

toString 方法

Object类中,toString的默认实现返回的是对象的全类名(包名加类名)和对象的哈希码。

System.out.println(new Person());	// com.zy.Person@1540e19d

当然,我们也可以重写toString方法。

class A{@Overridepublic String toString() {return "hahaha";}
}public class Hello{public static void main(String[] args) {System.out.println(new A()); // hahaha}
}
clone()方法

clone() 方法默认是浅拷贝,如果对象包含引用类型的数据,只有对象的引用会被复制,实际对象不会被复制。

展示深浅拷贝

getClass()方法

返回运行时对象的类类型。它返回一个 Class 对象,用于表示此对象的类。可以用来获取对象的类型,通常与反射一起使用。

class Person{
}public class Hello{public static void main(String[] args) {Person person = new Person();System.out.println(person.getClass());  // class com.zy.Person}
}
notify()方法

唤醒等待在此对象上的一个线程,必须在同步块中使用。

wait()方法

使当前线程等待,释放锁,直到被唤醒或超时,也必须在同步块中使用。

Math类

Math类很多,大概看一下吧。需要用到再查即可。

abs():返回一个数的绝对值
max():返回两个数中较大的那个数
min():返回两个数中较小的那个数
pow():返回一个数的某个次幂
sqrt():返回一个数的平方根
sin():返回一个数的正弦值
cos():返回一个数的余弦值
tan():返回一个数的正切值
log():返回一个数的自然对数
exp():返回一个数的指数值
ceil():返回一个数的上限整数
floor():返回一个数的下限整数
round():返回一个数的四舍五入整数

Random类

在 Java 中,Random 类是用于生成伪随机数的类。它位于 java.util 包中,提供了一系列的方法来生成不同类型的随机数。Random 类不是线程安全的,因此在多线程环境中可能需要使用 ThreadLocalRandom 或者 SecureRandom 类。

构造方法

Random random = new Random();      // 使用当前时间作为种子
Random random2 = new Random(42L);  // 使用指定的种子

常用方法

        Random random = new Random();System.out.println(random.nextInt());       // 随机生成一个整数,这个整数的范围就是int类型的范围-2^31~2^31-1System.out.println(random.nextInt(100));	// 生成[0,100)之间的一个整数System.out.println(random.nextLong());      // 随机生成long类型范围的整数System.out.println(random.nextFloat());     // 随机生成[0, 1.0)区间的小数System.out.println(random.nextDouble());    // 随机生成[0, 1.0)区间的小数System.out.println(random.nextBoolean());   // 随机生成一个boolean值,生成true和false的值几率相等,也就是都是50%的几率System.out.println(random.nextGaussian());  // //随机生成呈高斯(“正态”)分布的 double 值,其平均值是 0.0,标准差是 1.0byte[] byteArr = new byte[5];random.nextBytes(byteArr);  // 随机生成byte,并存放在定义的数组中,生成的个数等于定义的数组的个数System.out.println(Arrays.toString(byteArr));

File类

实现创建,写入,读取,删除文件。

try {// 1.创建文件File file = new File("example.txt");if(file.createNewFile()) {System.out.println();} else {System.out.println("File already exitsts");}// 2.写入文件FileWriter writer = new FileWriter(file);writer.write("This is a sample text");writer.close();System.out.println("Successfully wrote to the file.");// 3.读取文件FileReader reader = new FileReader(file);// 用BufferedReader更高效的读取数据。并且它还提供了readLine()方法。BufferedReader bufferedReader = new BufferedReader(reader);String line;System.out.println("Reading the file:");while ((line = bufferedReader.readLine()) != null) {System.out.println(line);}bufferedReader.close();// 4.删除文件if (file.delete()) {System.out.println("File deleted: " + file.getName());} else {System.out.println("Failed to delete the file.");}}catch (IOException e) {System.out.println("An error occurred.");e.printStackTrace();}

包装类

基本数据类型包装类
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean
装箱(Boxing)

装箱是将基本数据类型转换为其对应的包装类对象的过程。装箱分为手动装箱和自动装箱(从 Java 5 开始支持)。

手动装箱

int a = 10;
Integer boxedA = new Integer(a);  // 手动装箱

自动装箱

int b = 20;
Integer boxedB = b;  // 自动装箱
拆箱(Unboxing)

手动拆箱

Integer c = new Integer(30);
int unboxedC = c.intValue();  // 手动拆箱

自动拆箱

Integer d = 40;
int unboxedD = d;  // 自动拆箱
装箱与拆箱的应用场景

在使用集合类(如 ArrayList, HashMap 等)时,集合类只能存储对象,不能存储基本数据类型。这时,基本数据类型需要包装成包装类对象。这就是自动装箱的一个常见场景。

ArrayList<Integer> list = new ArrayList<>();
list.add(100);  // 自动装箱,将 100 转换为 Integer 对象
int num = list.get(0);  // 自动拆箱,将 Integer 对象转换为 int
包装类的缓存机制

对于某些数值,包装类会缓存对象。例如,Integer 类会缓存 -128127 之间的数值对象,因此这些范围内的装箱值可以被复用,不会每次都创建新的对象。(好像想起来了,当时Java老师还给我们举了类似下面这个例子)

Integer a = 127;
Integer b = 127;
System.out.println(a == b);  // true,因为 127 在缓存范围内Integer c = 128;
Integer d = 128;
System.out.println(c == d);  // false,因为 128 不在缓存范围内// 注意:== 和 equals() 的区别:对于包装类对象,== 比较的是对象的引用,而 equals() 比较的是对象的值。

Data类

Date 类是 Java 中表示日期和时间的类,它提供了一些基本的日期和时间操作方法。这个类的实例包含一个时间戳,表示自 Unix 纪元(1970年1月1日)以来的毫秒数。

常用方法

  • new Date():获取当前日期和时间。
  • getTime():返回自 1970 年 1 月 1 日 UTC 以来的毫秒数。
import java.util.Date;public class DateExample {public static void main(String[] args) {Date now = new Date();  // 获取当前日期时间System.out.println("当前时间: " + now);long timeInMillis = now.getTime();  // 获取当前时间的时间戳(毫秒数)System.out.println("时间戳: " + timeInMillis);}
}// 当前时间:Thu Sep 26 14:39:35 CST 2024
// 时间戳:1727332775366

SimpleDateFormat类

SimpleDateFormat 是一个用于格式化和解析日期的类。它可以将日期格式化为指定的字符串,或将字符串解析为日期对象。

常用方法:

  • format(Date):将日期格式化为字符串。
  • parse(String):将字符串解析为日期对象。
import java.text.SimpleDateFormat;
import java.util.Date;public class SimpleDateFormatExample {public static void main(String[] args) {Date now = new Date();  // 获取当前时间SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  // 定义日期格式String formattedDate = sdf.format(now);  // 格式化日期System.out.println("格式化后的日期: " + formattedDate);// 解析字符串为日期try {Date parsedDate = sdf.parse("2023-09-21 12:00:00");System.out.println("解析后的日期: " + parsedDate);} catch (Exception e) {e.printStackTrace();}}
}// 格式化后的日期:2024-09-26 14:43:17
// 解析后的日期: Thu Sep 21 12:00:00 CST 2023

Calendar类

Calendar 类是一个更强大的日期类,它不仅可以获取日期和时间,还可以对日期进行操作(如加减年、月、日等)。虽然 Date 类是简单的日期表示,Calendar 提供了更多的操作方法和功能。Calendar 类是抽象类,通常使用 Calendar.getInstance() 来创建一个 GregorianCalendar 对象。

常用方法

  • getInstance():获取一个 Calendar 实例,表示当前时间。
  • get(int field):获取指定时间字段的值,如 Calendar.YEAR, Calendar.MONTH 等。
  • add(int field, int amount):对指定时间字段进行加减操作。
import java.util.Calendar;public class CalendarExample {public static void main(String[] args) {Calendar calendar = Calendar.getInstance();  // 获取当前日期时间的 Calendar 实例int year = calendar.get(Calendar.YEAR);  // 获取年份int month = calendar.get(Calendar.MONTH) + 1;  // 获取月份,月份从 0 开始,因此要加 1int day = calendar.get(Calendar.DAY_OF_MONTH);  // 获取日期System.out.println("当前日期: " + year + "-" + month + "-" + day);// 对日期进行操作calendar.add(Calendar.YEAR, 1);  // 当前时间加 1 年int newYear = calendar.get(Calendar.YEAR);System.out.println("加一年后的年份: " + newYear);}
}

String 类

String 类用于表示不可变的字符串。每当对字符串进行修改时,实际上会创建一个新的 String 对象,而不是修改原有的字符串。

特点:

  • 不可变性String 对象一旦创建,它的内容就不能被改变。每次修改(如拼接、替换)时,都会生成新的 String 对象。
  • final 修饰符String 类被 final 修饰,无法被继承。
  • 效率低(操作多时):由于不可变性,每次修改字符串都会创建新的对象,所以当操作大量字符串时,效率较低。

适用场景:

  • 字符串的内容不需要频繁改变。
  • 操作量较少的字符串,如常量、配置信息等。

StringBuffer 类

StringBuffer 类用于表示可变的字符串。与 String 不同,StringBuffer 可以在不创建新对象的情况下修改其内容。

特点:
  • 可变性StringBuffer 对象的内容可以直接进行修改,如追加、插入、删除等操作。
  • 线程安全StringBuffer 是线程安全的,它的操作是同步的(内部使用 synchronized 关键字进行方法锁定),因此在多线程环境下,多个线程可以安全地修改同一个 StringBuffer 对象。
  • 效率相对低:由于线程安全机制的存在,StringBuffer 在多线程的环境下效率相对较高,但在单线程环境下由于同步操作,效率低于 StringBuilder
适用场景:
  • 需要在多线程环境下频繁修改字符串的场合。
  • 需要保证线程安全的字符串操作。
public class StringBufferExample {public static void main(String[] args) {StringBuffer sb = new StringBuffer("Hello");sb.append(" World");  // 直接修改当前对象System.out.println(sb);  // 输出 "Hello World"}
}

StringBuilder 类

StringBuilder 类也是一个可变的字符串类,与 StringBuffer 类似,能够修改字符串内容。不同的是,StringBuilder 不保证线程安全。

特点:
  • 可变性:与 StringBuffer 一样,StringBuilder 对象的内容可以直接修改。
  • 非线程安全StringBuilder 没有同步机制,因此在多线程环境下是不安全的,但在单线程环境下效率更高。
  • 效率高:由于没有线程同步的开销,在单线程场景中,StringBuilder 的性能要比 StringBuffer 高。
适用场景:
  • 在单线程环境下需要频繁修改字符串时,StringBuilder 是最佳选择。
  • 大量字符串拼接或修改的场合。
public class StringBuilderExample {public static void main(String[] args) {StringBuilder sb = new StringBuilder("Hello");sb.append(" World");  // 直接修改当前对象System.out.println(sb);  // 输出 "Hello World"}
}

String三兄弟对比

特性StringStringBufferStringBuilder
可变性不可变可变可变
线程安全性线程安全(不可变)线程安全(使用同步机制)非线程安全
性能操作大量时性能低操作大量时性能一般(同步)操作大量时性能高(单线程)
适用场景操作少、不变字符串多线程环境中字符串操作单线程环境中字符串操作
// 视频中的一个小测试
// "a" + 1 + 2 = ?
// 'a' + 1 + 2 = ?
// 1 + 2 + "a" = ?public class hello{public static void main(String[] args) {String str1 = "a" + 1 + 2;		// a12
//        String str2 = 'a' + 1 + 2;      // 报错,结果是int类型String str3 = 1 + 2 + "a";		// a3System.out.println(str1+" " +str3);}
}

集合框架(Collection Framework)

后续用Java刷题会接触的更多,所以现在就大概了解一下即可。

集合框架提供了一套用于操作对象集合的标准接口和类,主要包括 CollectionMap 两大核心接口。

  • Collection 专注于 元素的管理。(一个元素)
  • Map 专注于 键值对的映射和查找。(两个元素)

在这里插入图片描述

Collection 接口

List (有序且可重复)List 接口表示元素按插入顺序存储,允许重复元素。常见实现类有:

  • ArrayList:基于动态数组实现,适合频繁读取数据的场合。
  • LinkedList:基于链表实现,适合频繁插入和删除操作的场合。
  • (不常用)Vector:与 ArrayList 类似,也是基于动态数组实现的集合类,但它是线程安全的,效率比 ArrayList 略低。
  • Stack:继承自 Vector,实现了栈的数据结构,遵循后进先出(LIFO)的规则。

Set (无序且不可重复)Set 接口用于存储不重复元素的集合,不保证元素的插入顺序。常见实现类有:

  • HashSet:基于哈希表实现,插入和查找操作的时间复杂度为 O(1)。(实际上HashSet内部是HashMap实现的)
  • TreeSet:基于红黑树实现,元素有序,支持按自然顺序或比较器顺序排序。(内部由TreeMap实现)

Map 接口

Map 接口用于存储键值对。每个键对应唯一的值,不允许重复的键。常见实现类有:

  • HashMap(重点,面试常考):基于哈希表实现的 Map,允许 null 键和 null 值。常用于快速查找、插入和删除。
    • JDK 1.7 中,HashMap 的底层结构是数组和链表的结合。(数组+链表)
    • JDK 1.8 中,当链表长度超过一定阈值(默认为 8)时,HashMap 使用红黑树替代链表,以提高性能。(数组+链表+红黑树)
  • TreeMap:基于红黑树实现的 Map,保证键的有序性,适用于按自然顺序或自定义顺序排序的场景。

Iterator 接口

Iterator 是集合的迭代器接口,提供了遍历集合元素的标准方法。常用方法:

  • hasNext(): 检查是否有下一个元素。
  • next(): 获取下一个元素。
  • remove(): 从集合中移除当前迭代器返回的元素。

Collections 工具类

Collections 是 Java 提供的一个工具类,位于 java.util 包中,包含了对集合进行操作的静态方法。它提供了对集合(如 List, Set, Map 等)进行排序、搜索、线程安全化等功能。

比如:排序

  • sort(List<T> list):对 List 进行自然顺序排序,T 必须实现 Comparable 接口。
  • sort(List<T> list, Comparator<? super T> c):使用 ComparatorList 进行排序。

其他

  • reverse(List<?> list):反转 List 中的元素顺序。
  • shuffle(List<?> list):随机打乱 List 中元素的顺序。

这个就不细讲了,用到再查即可。

泛型 <>

泛型 (Generic) 是 Java 在 JDK 5.0 引入的一项功能,旨在增强代码的类型安全性和可读性。通过使用泛型,可以在编译时进行类型检查,避免了强制类型转换带来的问题。

泛型的基本用法

泛型的好处:

  1. 类型安全:在编译时检查类型,减少了运行时的 ClassCastException
  2. 代码可读性增强:泛型使得代码更清晰易懂,减少了不必要的类型转换。
  3. 代码重用:泛型允许编写更具通用性的代码,适用于多种不同类型。

泛型的基本语法:

class Box<T> {private T value;public T getValue() {return value;}public void setValue(T value) {this.value = value;}
}

在上述例子中,T 是一个类型参数,可以在使用 Box 类时指定具体的类型。

Box<Integer> integerBox = new Box<>();
integerBox.setValue(10);  // 自动装箱,避免了类型转换问题
int value = integerBox.getValue();  // 无需强制类型转换

可以看到,这里初始化的时候,右边是没有指定类型T的,因为编译器会自动推断出是Integer。这里实际上是用了 Java 7 引入的语法糖钻石运算符<>

Box<Integer> integerBox = new Box<>();	//java7前
Box<Integer> integerBox = new Box<Integer>();	// java7后

常见的泛型约束

? extends T

描述: ? extends T 表示该类型可以是 T 本身或者 T 的任何子类。

用途: 主要用于读取操作,即你只打算从数据结构中读取数据,不希望向其中写入。因为它可以保证读取出来的类型至少是 T 的类型或其子类。

List<? extends Number> list = new ArrayList<Integer>();
Number num = list.get(0);  // 可以安全地读取

在这个例子中,list 可以是 List<Integer>List<Double> 等,只要是 Number 的子类即可。我们可以安全地读取 Number 或其子类的对象。

但是不能向 list 中添加元素,因为编译器无法确定具体类型:

list.add(10);  // 编译错误

? super T

描述: ? super T 表示该类型可以是 T 本身或者 T 的任何父类。

用途: 主要用于写入操作,即你打算向数据结构中添加元素,因为它可以保证你添加的元素至少是类型 T 的对象。

List<? super Integer> list = new ArrayList<Number>();
list.add(10);  // 可以安全地写入

在这个例子中,list 可以是 List<Number>List<Object> 等,但不能确定其具体类型。

可以向 list 中添加 IntegerInteger子类对象,因为 list 至少接受 Integer。但是读取操作有一定的限制,只能读取为 Object,因为具体类型未知:

Object obj = list.get(0);  // 只能保证读取为 Object

总结

  • ? extends T: 上界通配符,用于读取数据,保证类型为 T 或其子类,但不适合写入。
  • ? super T: 下界通配符,用于写入数据,保证可以安全地添加类型为 T 或其子类的数据,但读取的数据只能被看作 Object
  • <?>: 无界通配符,用于接受任意类型,多用于通用方法或类,但只能以 Object 类型读取,不能写入。

泛型类与泛型方法

  • 泛型类

    :类定义时使用泛型,可以让类的属性、方法使用多种类型。

    class Pair<T, U> {private T first;private U second;public Pair(T first, U second) {this.first = first;this.second = second;}public T getFirst() { return first; }public U getSecond() { return second; }
    }
    
  • 泛型方法

    :方法定义时使用泛型,允许该方法可以处理多种类型。

    public <T> T getElement(List<T> list, int index) {return list.get(index);
    }
    // <T>:这是在方法名前声明的类型参数。表示该方法是一个泛型方法,并且 T 是一个类型参数。这个类型参数 T 可以是任何类型。
    // T getElement(List<T> list, int index) T表示返回值
    

避免类型转换问题

在没有泛型之前,集合操作需要频繁进行强制类型转换,容易引发类型转换错误。例如:

List list = new ArrayList();  // 未使用泛型
list.add("Hello");
list.add(123);for (Object obj : list) {// 强制类型转换String str = (String) obj;  // 运行时会抛出ClassCastException
}

通过泛型,类型可以在编译时得到检查,从而避免类型转换问题:

List<String> list = new ArrayList<>();
list.add("Hello");
// list.add(123);  // 编译时会报错for (String str : list) {// 不需要强制类型转换System.out.println(str);
}

泛型消除了集合操作中手动进行类型转换的需要,确保了在编译时就能检测出类型错误,而不是等到运行时发生 ClassCastException

总结

  • Collections 工具类提供了大量处理集合的常用方法。
  • 泛型通过在编译时进行类型检查,避免了手动类型转换,增强了代码的安全性和可读性。
  • 泛型约束(如 ? extends T, ? super T)为代码的灵活性提供了更多可能。

IO流

根据流的方向可以把IO流分为 输入流(读取)和输出流(写出)。

根据操作文件类型可以把IO流分为 字节流(所有类型文件)和字符流(纯文本文件)。

根据流的角色的不同分为:节点流处理流

字节流

字节流用于处理原始字节数据,比如图片、音频等非文本数据。Java 提供了 InputStreamOutputStream 作为字节流的基础类。(即字节流进而分类为输入流和输出流)

字节输出流OutputStream

OutputStream 是一个抽象类,提供了向输出目标(如文件、网络连接等)写入字节的基本方法。常见的方法包括:

  • void write(int b):写入一个字节(0-255),b 是字节的整数表示。
  • void write(byte[] b):将字节数组中的所有字节写入输出流。
  • void write(byte[] b, int off, int len):从字节数组的指定位置写入指定长度的字节到输出流。
  • void flush():刷新输出流,确保所有缓存的数据都被写入目标。
  • void close():关闭输出流,释放相关资源。

OutputStream有很多子类,下面举个例子:使用FileOutputStream写入文件。

public class Hello {public static void main(String[] args) throws IOException {String data = "Hello, OutputStream!";FileOutputStream fos = new FileOutputStream("output.txt");fos.write(data.getBytes());//fos.flush();fos.close();}
}

如果想追加写,可以使用构造的第二个参数boolean append

FileOutputStream fos = new FileOutputStream("output.txt",true);

字节输入流InputStream

nputStream 是一个抽象类,提供了从输入源(如文件、网络连接等)读取字节的基本方法。常见的方法包括:

  • int read():读取下一个字节,并返回该字节的整数值(0-255),返回 -1 表示到达流的末尾。
  • int read(byte[] b):从输入流中读取字节并存储到字节数组中,返回实际读取的字节数。
  • int read(byte[] b, int off, int len):从输入流中读取最多 len 个字节,并将其存储到字节数组 b 的指定偏移量 off 中。
  • void close():关闭输入流,释放相关资源。

示例:使用 FileInputStream 读取文件

public class Hello {public static void main(String[] args) throws IOException {try(FileInputStream fis = new FileInputStream("output.txt");) {int content;// 注意fis.read()返回的是int类型while((content = fis.read()) != -1) {System.out.print((char)content);}}catch (IOException e) {e.printStackTrace();}}
}

组合使用,复制一张图片

    public static void main(String[] args) {try(FileInputStream fis = new FileInputStream("source.jpg");FileOutputStream fos = new FileOutputStream("des.jpg")) {byte[] buffer = new byte[1024]; //缓冲区int bytesRead;while((bytesRead = fis.read(buffer)) != -1) {fos.write(buffer, 0 ,bytesRead);}}catch (IOException e) {e.printStackTrace();}}

如果把上面代码缓冲区去掉,变成一个字节一个字节读写,会发现非常慢。

硬盘读写数据的最小单位是块,而不是字节。块的大小通常是 4KB 或更大。如果程序一次只处理一个字节,这与硬盘的最小块大小不匹配,可能会造成效率低下。通过缓冲区,程序一次可以读取多个字节,优化硬盘的读写操作。

大多数文件系统(如 NTFS、EXT4 等)的磁盘块大小通常是 4 KB,也就是 4096 字节。因此,1024 字节的缓冲区大小很容易与文件系统的块大小相结合。虽然 1 KB 小于 4 KB,但它仍然能提供足够的缓冲并减少频繁的读写调用。

字符流

字符流用于处理文本数据,比如文本文件。Java 提供了 ReaderWriter 作为字符流的基础类。

字符输入流Reader

Reader 是一个抽象类,用于读取字符流。常见的方法有:

  • int read():读取单个字符并返回对应的字符编码值(int 类型)
  • int read(char[] cbuf):尝试读取多个字符并将其存储到字符数组 cbuf
  • int read(char[] cbuf, int off, int len)
  • boolean ready():返回是否可以无阻塞地读取字符,通常用于判断流是否准备好进行读操作。
  • long skip(long n):跳过并丢弃流中最多 n 个字符,返回实际跳过的字符数。
  • void close()

String readLine()BufferedReader 独有):读取一行文本并返回一个字符串。如果到达流的末尾

常见的实现类有:

  • FileReader:用于从文件中读取字符。
  • BufferedReader:为其他 Reader 类提供缓冲能力,提高读取效率。

示例:使用FileReader读取文件。

    public static void main(String[] args) {try(FileReader reader = new FileReader("output.txt")) {int character;while((character = reader.read()) != -1) {System.out.print((char) character);}} catch (IOException e) {e.printStackTrace();}}

使用BufferedReader提高读取效率

    public static void main(String[] args) {try(BufferedReader reader = new BufferedReader(new FileReader("output.txt"))) {String line;while((line = reader.readLine()) != null) {System.out.println(line);}} catch (IOException e) {e.printStackTrace();}}
字符输出流Writer

Writer 是一个抽象类,用于写入字符流。

  • void write(int c)
  • void write(char[] cbuf)
  • void write(char[] cbuf, int off, int len)
  • void write(String str)
  • void write(String str, int off, int len)
  • void flush()
  • void close()

常见的实现类有:

  • FileWriter:用于将字符写入文件。
  • BufferedWriter:为其他 Writer 类提供缓冲能力,提高写入效率。

示例:用FileWriter写入

    public static void main(String[] args) {try (FileWriter writer = new FileWriter("output.txt")) {writer.write("Hello, World!\n");writer.write("This is a test.");} catch (IOException e) {e.printStackTrace();}}

使用 BufferedWriter 提高写入效率:

    public static void main(String[] args) {try (BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {writer.write("Hello, World!");writer.newLine(); // 添加换行符writer.write("This is a test.");} catch (IOException e) {e.printStackTrace();}}

节点流

节点流是直接与数据源或数据目的地交互的流。它们可以直接从文件、内存、管道等中读取或向这些位置写入数据。节点流是最基础的流,主要用于物理设备的输入/输出操作。

常见的节点流:

  • FileInputStreamFileOutputStream:处理文件的字节输入和输出。
  • FileReaderFileWriter:处理文件的字符输入和输出。
  • CharArrayReaderCharArrayWriter:将字符数据存储到字符数组中。
  • ByteArrayInputStreamByteArrayOutputStream:处理内存中的字节数据。
  • PipedInputStreamPipedOutputStream:实现线程间的管道通信。

处理流

处理流是一种包装在其他流上,提供额外功能的流。处理流不能直接与数据源或数据目的地交互,而是依赖于节点流来完成实际的输入输出操作。它们主要用于在节点流的基础上进行数据处理,比如缓冲、过滤、数据转换等。

常见的处理流:

  • BufferedInputStreamBufferedOutputStream:通过缓冲区来提高字节输入/输出的效率。
  • BufferedReaderBufferedWriter:通过缓冲区来提高字符输入/输出的效率。
  • DataInputStreamDataOutputStream:允许从流中读写Java的基本数据类型(如int, long, double)。
  • ObjectInputStreamObjectOutputStream:用于序列化和反序列化Java对象。
  • InputStreamReaderOutputStreamWriter:用于将字节流转换为字符流,或将字符流转换为字节流。
  • PrintWriterPrintStream:提供便捷的写入方法,比如 print()println(),主要用于文本数据输出。
DataInputStream 和 DataOutputStream

示例

public static void main(String[] args) {// 写入数据try(DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.dat"))) {dos.writeInt(1234);dos.writeDouble(12.34);dos.writeBoolean(true);}catch (IOException e) {e.printStackTrace();}// 读取数据try(DataInputStream dis = new DataInputStream(new FileInputStream("data.dat"))) {int i = dis.readInt();double d = dis.readDouble();boolean b = dis.readBoolean();System.out.println("Read values: " + i + ", " + d + ", " + b);}catch (IOException e) {e.printStackTrace();}}

DataInputStream/DataOutputStream 提供了对基本数据类型进行读写的方便接口,避免了手动处理字节流。常用于需要将基本数据类型以二进制形式存储或传输的场景,比如保存配置文件、网络通信等。

ObjectInputStream 和 ObjectOutputStream(序列化和反序列化)

ObjectInputStream:用于从输入流中反序列化对象,即将字节流转化为 Java 对象。

ObjectOutputStream:用于将 Java 对象序列化并写入输出流,即将对象转化为字节流。

示例

class Person implements Serializable {String name;int age;Person(String name, int age) {this.name = name;this.age = age;}public String toString() {return "Person{name='" + name + "', age=" + age + "}";}
}public class Hello {public static void main(String[] args) throws IOException, ClassNotFoundException  {// 序列化对象到文件try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.dat"))) {Person person = new Person("Alice", 30);oos.writeObject(person);}// 从文件中反序列化对象try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.dat"))) {Person person = (Person) ois.readObject();System.out.println("Deserialized person: " + person);}}
}
PrintWriter 和 PrintStream
  • PrintWriter:是字符流,用于格式化输出文本数据。它提供了很多便捷的 printprintln 方法,常用于写入文件或显示在控制台。
  • PrintStream:是字节流,类似于 PrintWriter,但它处理字节数据,常用于打印字节流到文件或控制台。
// 使用 PrintWriter 将字符写入文件try (PrintWriter pw = new PrintWriter(new FileWriter("printwriter.txt"))) {pw.println("Hello, PrintWriter!");pw.printf("Formatted string: %d, %.2f\n", 10, 3.14);}// 使用 PrintStream 将字节写入文件try (PrintStream ps = new PrintStream(new FileOutputStream("printstream.txt"))) {ps.println("Hello, PrintStream!");ps.printf("Formatted string: %d, %.2f\n", 10, 3.14);}

PrintWriter 适合处理字符数据,而 PrintStream 处理字节数据。System.out 本质上是一个 PrintStream,可以直接用于输出到控制台。

序列化和反序列化

  • 序列化:ObjectOutputStream,通过 writeObject() 方法进行对象的序列化。
  • 反序列化:ObjectInputStream,通过 readObject() 方法进行对象的反序列化。
Serializable 接口

Serializable 是一个标记接口(没有任何方法),即空接口。当一个类实现 Serializable 接口时,表示该类的对象可以被序列化。

问题:作为一个空接口,那我不implements它,别人是怎么知道并报出对应错误的?

答:在执行序列化时,JVM会使用反射机制来检查一个类是否实现了 Serializable 接口。如果类没有实现 Serializable 接口,序列化过程会直接失败,抛出 NotSerializableException。这就是JVM能够知道一个类是否实现了 Serializable 的原因。

transient 关键字
  • transient 是Java的关键字,表示某个字段不参与序列化。即当对象被序列化时,transient 修饰的字段不会被序列化到字节流中。
  • 当对象反序列化时,transient 字段会被设置为默认值(例如 null0false 等)。

用法

class User implements Serializable {String username;transient String password; // 不序列化此字段
}

多线程

JUC是什么:即java.util .concurrent,在JDK1.5引入。是一个处理线程的工具包。

视频后面还提到了多线程,对于刚做完C++中webServer项目的我,感觉很熟悉hhh。 像视频中提到的什么线程池这些概念,都是认识的,只不过Java写的方式不太一样罢了。

重要的是基础,对于代码层面的,以后需要接触再速成一下即可。

视频中间还有网络编程和GUI,都不是重点内容,就先跳过了

注解和反射

注解(Annotation)

JDK5 时引入的新特性。注解是Java提供的一种元数据机制,用于为代码中的类、方法、字段等元素提供附加信息。在Java中,注解不会直接影响代码的运行逻辑,而是为编译器或其他工具提供额外信息。

元注解

元注解是用于修饰其他注解的注解,例如@Retention, @Target等,用来定义注解的作用范围和生命周期。

  • Target注解的作用是:描述注解的使用范围
  • Reteniton注解的作用是:描述注解保留的时间范围
内置注解

Java中预定义的一些注解,如@Override(表示方法重写)、@Deprecated(标记过时的方法)等。

自定义注解

用户可以定义自己的注解,指定注解的目标和生命周期。自定义注解通过@interface关键字定义。

@Retention(RetentionPolicy.RUNTIME)	 	//元注解注解我们的自定义注解
public @interface MyAnnotation {
}// 使用注解
@MyAnnotation
class Person{
}

反射(Reflection)

反射是Java的一种动态机制,允许在运行时获取类的结构信息(如方法、构造函数、字段等),并能动态调用方法、构造实例等。反射是Java提供的非常强大的功能,但滥用反射可能导致性能问题,并破坏代码的安全性。

推荐视频:Java中的反射 Reflection in Java

获取Class对象

获取Class对象有三种主要方式

1.Class<User> userClass = User.class; 在编译时就确定了具体的类,属于静态引用。使用这种方式获得类对象时,不会立即触发类的静态初始化块。

2.User user = new User(); Class<?> clazz = user.getClass();
这个class实在运行时从User实例获取的,而User的具体类型,只能在运行时创建和确定。在编译阶段无法准确判断Class对象的确切类型。因此使用通配符<?>。

3.Class<?> clazz = Class.forName("com.zy.user");通常用在类名在编译时不可知的场景中。使用这种方式获得类对象时,会触发静态初始化块。

Class对象方法

Class对象有很多方法,这里着重讲几个核心的。

1.Field[] fields = clazz.getDeclaredFields(); 这样可以获得类中所有字段(无论是否私有)但是不包括父类的字段;

2.Field[] fields = clazz.getFields(); 这样可以获得所有public的字段,包括父类的。

后面很多Declared字段和不包括Declared字段的都是一样的区别。

3.Field fields = clazz.getField("name"); 可以获得指定变量。

4.getDeclaredAnnotation(MyAnnotation.class)获得注解。

5.获得类中的值

        Class<?> clazz = Class.forName("com.zy.User");Field field = clazz.getDeclaredField("publicStaticField");field.setAccessible(true);	// 为了访问私有字段System.out.println(field.get(null));	//这里调用的是静态方法,不依附于任何实例,所以用null

6.获得所有方法

Class<?> clazz = Class.forName("com.zy.User");Method[] methods = clazz.getDeclaredMethods();for(Method method : methods) {System.out.println(method.getName());}

7.执行方法 invoke

        Class<?> clazz = Class.forName("com.zy.User");Method method = clazz.getDeclaredMethod("publicStaticMethod");method.invoke(null);    // 同样这里是静态方法

8.执行带参数的方法

        Class<?> clazz = Class.forName("com.zy.User");Method method = clazz.getDeclaredMethod("publicStaticMethod",String.class);	//带String 参数method.invoke(null,"Hi here"); 	//调用时需要传入String 参数

9.获取构造器创建对象

Class<?> clazz = Class.forName("com.zy.User");//获取无参构造器
//        Constructor<?> constructor = clazz.getDeclaredConstructor();//获取有参构造器Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);Object obj = constructor.newInstance("AAA",12);if(obj instanceof User) {User user = (User)obj;}

推荐的java反射视频中,最后有举个例子,利用反射做个小框架,建议看看。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1559210.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

对象存储之阿里云OSS最详细的实现

Hello&#xff0c;大家好&#xff0c;我是Feri&#xff0c;一枚十多年的程序员&#xff0c;同时也是一名在读研究生&#xff0c;关注我&#xff0c;且看一个平凡的程序员如何在自我成长&#xff0c;只为各位小伙伴提供编程相关干货知识&#xff0c;希望在自我蜕变的路上&#x…

【JS】环形链表怎么找入口?

思路 判断是否有环&#xff1a;定义快慢指针&#xff0c;慢指针&#xff08;slow&#xff09;走一步&#xff0c;快指针&#xff08;fast&#xff09;走两步。 无环&#xff1a;快指针最终会到达链表的末尾&#xff08;即fast.next为null&#xff09;,永远不可能相遇有环&#…

C++ 二叉搜索树转换为双向链表

描述 输入一棵二叉搜索树&#xff0c;将该二叉搜索树转换成一个排序的双向链表: 代码 #include <iostream> #include <string> #include <optional>struct TreeNode {int val;struct TreeNode *left;struct TreeNode *right;TreeNode(int x): val(x), left(…

陈零九全新单曲《也曾想走进你的心底》 揭露爱而不得的情感遗憾

图片提供&#xff1a;种子音乐 “创作男神”陈零九于10月9日推出充满深情的全新创作单曲《也曾想走进你的心底》&#xff0c;这首歌再次延续他招牌的“九式情歌”风格&#xff0c;展现其创作魅力。歌曲以一段“爱而不得”的感情故事为主线&#xff0c;深入探讨人们在爱情中的复…

项目启动 | 盘古信息赋能奥尼视讯数字化转型升级,实现全面数智化发展

随着信息技术的飞速发展与全球市场竞争的日益激烈&#xff0c;传统制造业正面临生存和发展的危机&#xff0c;制造企业为谋求发展&#xff0c;纷纷开启数字化转型之路&#xff0c;深度融入数字技术&#xff0c;实现生产流程的智能化、管理模式的精细化以及产品服务的个性化&…

科普|网络准入控制系统是什么?2024年好用的网络准入控制系统推荐!

在宁波的传统习俗中&#xff0c;有一个有趣的谚语故事——“阿旺炒年糕”。 据说&#xff0c;宁波的男子名字中多“旺”&#xff0c;凡名字中有“旺”者&#xff0c;小名就被叫作“阿旺”。炒年糕须用慢火&#xff0c;但有一位不懂家务的男子心急&#xff0c;用旺火炒年糕&…

LSTM(长短时记忆网络)

一、引言 在处理序列数据时&#xff0c;循环神经网络&#xff08;RNN&#xff09;虽然能够处理序列数据并保留历史信息&#xff0c;但在实践中发现它对于捕捉长时间依赖关系的能力有限&#xff0c;尤其是在训练过程中容易遇到梯度消失或梯度爆炸的问题。为了解决这些问题&…

组装首页:其他组件html、css移入JS中再引入首页

组装首页 <!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>组装首页</title><style>* …

【计算机方向】IEEE二区宝刊,IF=9.6,2个月秒速接受,备受国人吹捧!

期刊解析 01 期刊信息✦ 期刊名称&#xff1a;IEEE Transactions on Affective Computing 出版商&#xff1a;Institute of Electrical and Electronics Engineers Inc. ISSN&#xff1a;1949-3045 …

重头开始嵌入式第四十七天(硬件 ARM裸机开发 RS232 RS4885 IIC)

目录 一.什么是RS232&#xff1f; 1. 历史背景&#xff1a; 2. 电气特性&#xff1a; 3. 连接器类型&#xff1a; 4. 通信特点&#xff1a; 5. 应用场景&#xff1a; 二.什么是RS485&#xff1f; 1. 电气特性&#xff1a; 2. 通信模式&#xff1a; 3. 传输距离与速率&…

扫描电镜是用来测什么的?

扫描电镜是一种用于对样品进行微观尺度形貌观测和分析的仪器。它能够提供高分辨率的图像&#xff0c;帮助科学家和工程师了解样品的微观结构和特性。 一、扫描电镜的一般测量功能 微观形貌观测 扫描电镜可以清晰地观察到样品表面的微观形貌&#xff0c;如颗粒的形状、大小、…

GC9113:电子锁领域的革新力量

在现代社会&#xff0c;安全与便捷成为人们对生活品质的重要追求。电子锁作为保障家庭和商业安全的关键设备&#xff0c;不断经历着技术的革新与升级。而 GC9113 的出现&#xff0c;为电子锁领域带来了全新的替代选择。 GC9113 以其卓越的性能和独特的优势&#xff0c;在电子锁…

【嵌入式软件-STM32】STM32简介

目录 一、STM32定义 二、STM32用途 三、STM32特点 四、STM32 四个系列 五、了解ARM 六、芯片解释 七、片上资源 八、命名规则 九、系统结构 内核 Flash DMA 外设种类和分布 十、引脚定义 类型 名称 引脚 十一、启动配置 十二、STM32最小系统电路 STM32及供电 供电引脚 滤波电容…

深度学习:循环神经网络RNN

目录 一、神经网络的历程 1.传统神经网络存在的问题 2.提出一种新的神经网络 二、RNN基本结构 1.RNN基本结构 2.RNN的独特结构 3.RNN的局限性 一、神经网络的历程 1.传统神经网络存在的问题 无法训练出具有顺序的数据。模型搭建时没有考虑数据上下之间的关系。因为传统…

十年网络安全工程师谈学习网络安全的正确顺序

当今数字化时代&#xff0c;网络安全行业如守护数字世界的坚固堡垒&#xff0c;其重要性愈发凸显。随着信息技术的迅猛发展&#xff0c;我们的生活、工作、社交等方方面面都与网络紧密相连&#xff0c;从个人隐私信息到企业核心数据&#xff0c;再到国家关键基础设施乃至全球互…

什么是Cookie 它有什么作用 及如何使用Session-Cookie方案进行身份验证 总结

Cookie 和 Session 都是用来跟踪浏览器用户身份的会话方式&#xff0c;但是两者的应用场景不太一样。 维基百科是这样定义 Cookie 的&#xff1a; Cookies 是某些网站为了辨别用户身份而储存在用户本地终端上的数据&#xff08;通常经过加密&#xff09;。 简单来说&#xff1…

实战千问2大模型第五天——VLLM 运行 Qwen2-VL-7B(多模态)

一、简介 VLLM 是一种高效的深度学习推理库&#xff0c;通过PagedAttention算法有效管理大语言模型的注意力内存&#xff0c;其特点包括24倍的吞吐提升和3.5倍的TGI性能&#xff0c;无需修改模型结构&#xff0c;专门设计用于加速大规模语言模型&#xff08;LLM&#xff09;的…

网站排名,让网站快速有排名的几个方法

要让网站快速获得并提升排名&#xff0c;需要综合运用一系列专业策略和技术&#xff0c;这些策略涵盖了内容优化、技术调整、外链建设、用户体验提升等多个方面。以下是让网站快速有排名的几个方法&#xff1a; 1.内容为王&#xff1a;创造高质量、有价值的内容 -深入…

The Android SDK location cannot be at the filesystem root

win11&#xff0c; 安装启动完Android Studio后&#xff0c;一直显示 The Android SDK location cannot be at the filesystem root因此需要下载SDK包&#xff0c;必须开启代理。 开启代理后&#xff0c;在System下开启自动检测代理&#xff0c;如图 重启Android Studio&a…

Ubuntu双卡训练过程中电脑总是突然重启【解决方法】

本来以为是温度过热造成的&#xff0c;发现不是&#xff0c;因为在重启的瞬间&#xff0c;gpu温度并没有特别高。 参见视频如下&#xff1a; 双卡训练过程中gpu温度监测 然后尝试了另一种方法&#xff1a; 限制gpu显卡的功率 具体操作如下&#xff1a; 先检查当前gpu功率限…