Java期末客观题复习大纲

考点

      • 1、命令行如何编译运行java程序
      • 2、JDK和IDE的区别
      • 3、数据类型=基本数据类型+引用数据类型
      • 4、变量名的命名规则
      • 5、常见的几种循环及有限循环和无限循环的验证
      • 6、Switch语句的用法、break和无break的区别以及default的作用
      • 7、`Random`类和`Integer`类的异常处理及相关判断
      • 8、一维数组的属性、取值和遍历
      • 9、Java中的泛型有什么用?怎么用?(难)
      • 10、面向对象中`equals`与`==`的区别
      • 11、对于类的构造方法,三种修饰构造方法的`public`、`private`、无修饰符的区别
      • 12、覆盖与重载的区别
      • 13、怎么创建一个对象
      • 14、访问修饰符的简单了解
      • 15、多态的表现
      • 16、接口实现一个类的注意事项
      • 17、`Object`类要注意哪些方法,比如`hashCode`,`equals`等等
      • 18、编写图形界面要导入什么,`import……`?
      • 19、集合框架和数组的区别?
      • 20、对于异常,`try`,`catch`,`finally`里面分别放什么代码?
      • 21、常见的异常类?
      • 22、多线程继承自`Thread`接口/实现`Runnable`接口的优缺点
      • 23、`Thread`类中的常见方法
      • 24、数据库常用的接口`Statement`,`PreparedStatement`的区别
      • 25、为了更快地读写文件要用什么流?
      • 26、编写Java网络相关要导入什么包?

1、命令行如何编译运行java程序

  1. 编写java代码
    • 文件名需遵循Java命名规范,以.java为扩展名,例如HelloWorld.java
  2. 打开命令行窗口,cd命令到达文件所在目录
    • 在Windows系统中,可通过cmd打开命令行窗口,然后使用cd命令切换目录。例如,如果文件在D:\java_project\src目录下,可在命令行输入cd D:\java_project\src
    • 在Linux或Mac系统中,可通过终端进入相应目录。
  3. 编译程序
    • 在命令行输入javac命令,后面跟上要编译的.java文件名,如javac HelloWorld.java。如果代码没有语法错误,会在当前目录生成对应的.class字节码文件。
  4. 运行程序
    • 使用java命令运行程序,后面跟上类名(注意不是文件名)。例如,如果HelloWorld.java文件中有一个public class HelloWorld,则运行命令为java HelloWorld

2、JDK和IDE的区别

  1. JDK(Java Development Kit)
    • 是Java开发的基础环境,包含了Java编译器(javac)、Java虚拟机(JVM)、Java核心类库以及各种开发工具(如调试器jdb等)。
    • 提供了编写、编译和运行Java程序的基本能力。开发人员可以使用文本编辑器编写Java代码,然后通过JDK提供的命令行工具进行编译和运行。
    • 例如,在命令行中使用javac编译.java源文件,使用java命令运行字节码文件。
  2. IDE(Integrated Development Environment)
    • 是集成开发环境,它整合了代码编辑器、编译器、调试器、图形化界面设计工具等多种功能于一体。
    • 常见的Java IDE有Eclipse、IntelliJ IDEA等。它们提供了代码自动补全、语法检查、项目管理、版本控制集成等高级功能,大大提高了开发效率。
    • 例如,在Eclipse中,开发人员可以方便地创建Java项目,通过图形化界面操作进行代码编写、调试和运行,无需在命令行中频繁输入命令。

3、数据类型=基本数据类型+引用数据类型

  1. 基本数据类型
    • 整数类型
      • byte:字节型,占1个字节,用于表示较小范围的整数,如存储文件中的字节数据等。
      • short:短整型,占2个字节,可用于节省内存空间的整数存储,如某些嵌入式系统中的数据存储。
      • int:整型,占4个字节,是最常用的整数类型,适用于一般的整数计算和存储。
      • long:长整型,占8个字节,用于表示较大范围的整数,如处理时间戳(以毫秒为单位)等。
    • 浮点类型
      • float:单精度浮点型,占4个字节,精度相对较低,可用于对精度要求不高的科学计算或图形处理中的简单数据表示。
      • double:双精度浮点型,占8个字节,精度较高,是默认的浮点型,常用于金融计算等对精度要求较高的场景。
    • 字符类型
      • char:字符型,占2个字节,用于存储单个字符,如字母、数字、标点符号等,在处理文本数据时经常使用。
    • 布尔类型
      • boolean:布尔型,占1位(实际大小由虚拟机决定),只有truefalse两个值,用于逻辑判断,如控制程序流程中的条件判断。
  2. 引用数据类型
    • 类(Class):用于定义对象的模板,包含属性和方法。例如定义一个Person类来表示人的信息,包括姓名、年龄等属性,以及获取姓名、设置年龄等方法。
    • 接口(Interface):定义了一组方法签名,但没有具体的实现,用于实现多态性和规范类的行为。例如Runnable接口,定义了run方法,实现该接口的类必须实现run方法来定义线程的执行逻辑。
    • 数组(Array):是一种容器,可以存储多个相同类型的数据元素,如int[]数组可以存储多个整数。
    • 枚举(Enum):用于定义一组命名的常量,如定义一个表示星期几的枚举enum Week {MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY}
    • 注解(Annotation):提供了一种关于程序代码的元数据,用于在编译、类加载、运行时等阶段对代码进行说明和处理,如@Override注解用于表示方法重写。

4、变量名的命名规则

  1. 合法变量名规则
    • 变量名只能由字母、数字、下划线(_)和美元符号( )组成。例如: ‘ m y V a r i a b l e ‘ 、 ‘ c o u n t ‘ 、 ‘ t o t a l 123 ‘ 、 ‘ )组成。例如:`myVariable`、`_count`、`total123`、` )组成。例如:myVariablecounttotal123‘price`都是合法的变量名。
    • 第一个字符必须是字母、下划线或美元符号。不能以数字开头,如2number是非法的。
    • 不能使用Java关键字作为变量名,如finalclass等都是关键字,不能用作变量名。不过,Java 8以后,有些关键字可以在特定的上下文中使用,如var用于局部变量类型推断,但一般情况下仍应避免使用关键字作为普通变量名。
    • 变量名区分大小写,例如countCount是两个不同的变量。
    • 虽然可以使用中文等Unicode字符作为变量名(如姓名),但不推荐,因为可能会出现编码问题,且不符合常见编程规范,不利于代码维护和交流。
  2. 非法变量名示例及原因
    • 2number:以数字开头,违反了变量名的命名规则。
    • final:是Java关键字,不能用作变量名。
    • classVarclass是关键字,不能用于变量命名。

5、常见的几种循环及有限循环和无限循环的验证

  1. 常见循环结构
    • for循环
      • 语法结构for (初始化表达式; 条件表达式; 更新表达式) {循环体}
      • 初始化表达式用于初始化循环变量,如int i = 0;条件表达式用于判断循环是否继续执行,如i < 10;更新表达式用于在每次循环结束后更新循环变量的值,如i++
      • 例如,计算1到10的整数和:
int sum = 0;
for (int i = 1; i <= 10; i++) {
    sum += i;
}
System.out.println("1到10的和为:" + sum);
  • while循环
    • 语法结构while (条件表达式) {循环体}
    • 先判断条件表达式是否为真,如果为真,则执行循环体。在循环体中必须包含更新循环变量的语句,否则可能导致无限循环。
    • 例如,计算1到10的整数和:
int sum = 0;
int i = 1;
while (i <= 10) {
    sum += i;
    i++;
}
System.out.println("1到10的和为:" + sum);
  • do - while循环
    • 语法结构do {循环体} while (条件表达式);
    • 先执行一次循环体,然后再判断条件表达式。即使条件表达式一开始就不满足,循环体也会至少执行一次。
    • 例如,计算1到10的整数和:
int sum = 0;
int i = 1;
do {
    sum += i;
    i++;
} while (i <= 10);
System.out.println("1到10的和为:" + sum);
  1. 有限循环和无限循环的验证
    • 有限循环
      • 理论分析:对于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 (;;)从语法上看就是无限循环。在whiledo - while循环中,若忘记更新循环变量,导致条件表达式始终满足,也会产生无限循环。
      • 实际验证:如果程序运行后长时间无结束迹象(且非执行耗时操作),或控制台持续输出相同内容不停,很可能进入无限循环。可检查代码循环部分确认,一些IDE有检测无限循环功能,也可使用调试工具暂停程序,查看循环变量和条件表达式的值来判断。

6、Switch语句的用法、break和无break的区别以及default的作用

  1. switch语句用法
    • switch语句根据表达式的值来选择执行不同的代码块。表达式的结果必须是byteshortintcharString(Java 7以后支持)或者枚举类型。
    • 语法结构如下:
switch (表达式) {
    case1:
        // 当表达式的值等于值1时执行的代码块
        break;
    case2:
        // 当表达式的值等于值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;
        }
    }
}
  1. break和无break的区别
    • break
      • switch语句中的某个case分支执行到break语句时,程序会立即跳出switch结构,继续执行switch语句后面的代码。
      • 例如在上述例子中,如果用户输入3,执行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中的任何一个”。这种情况在某些特定逻辑下可能有用,但要注意可能导致意外执行结果。
  1. default的作用
    • default分支是可选的。当switch语句中的表达式的值与所有case的值都不匹配时,就会执行default分支中的代码。
    • 它通常用于处理一些意外情况或提供默认行为。如在菜单选择的switch语句中,如果用户输入不存在的菜单选项,可通过default分支给出错误提示。

7、Random类和Integer类的异常处理及相关判断

  1. 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();
        }
    }
}
  1. 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("不会发生溢出");
        }
    }
}

8、一维数组的属性、取值和遍历

  1. 一维数组的属性
    • 数组长度:可以使用数组名.length获取数组的长度,即数组中元素的个数。例如,int[] arr = new int[5];,则arr.length的值为5。
    • 数据类型:数组中的所有元素必须是相同的数据类型。例如int[]数组只能存储整数,String[]数组只能存储字符串。
  2. 取值
    • 通过索引来访问数组中的元素,索引从0开始。例如,对于int[] arr = new int[5];,可以使用arr[0]arr[1]等来访问数组中的元素。
    • 要注意索引不能越界,否则会抛出ArrayIndexOutOfBoundsException异常。例如,arr[5](对于长度为5的数组)就是越界访问,因为合法的索引范围是0到4。
  3. 遍历
    • 使用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);
} 

9、Java中的泛型有什么用?怎么用?(难)

  1. 泛型的作用
    • 类型安全:在编译时进行类型检查,确保集合等容器中存储的元素类型是预期的类型,避免了类型转换错误。例如,创建一个List来存储String类型的元素,如果试图添加一个Integer类型的对象,在编译时就会报错。
    • 代码复用:可以编写通用的代码,适用于多种类型。比如,定义一个泛型方法来交换两个变量的值,无论变量是IntegerString还是其他类型,都可以使用同一个方法实现。
    • 提高可读性:使代码更清晰地表达意图,通过泛型参数可以明确知道容器或方法所操作的数据类型。
  2. 泛型的使用方法
    • 定义泛型类
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`,但在方法内部需要进行类型判断来正确处理元素。

10、面向对象中equals==的区别

  1. ==运算符
    • 基本数据类型比较:对于基本数据类型(如intdoublechar等),==比较的是它们的值是否相等。例如,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所指向的同一个对象。
  1. equals方法
    • equals方法是在Object类中定义的,默认情况下,它的行为与==相同,即比较对象的引用地址。但是,在很多类中(如StringInteger等),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等)中被重写用于比较对象内容是否相等,若未重写则比较引用地址,重写后可根据对象的具体属性来判断两个对象是否逻辑相等。

11、对于类的构造方法,三种修饰构造方法的publicprivate、无修饰符的区别

  1. public修饰符
    • 可以在类的外部任何地方被访问和调用,用于创建类的对象。这是最常见的构造方法修饰符,允许其他类实例化该类。例如:
public class PublicConstructorExample {
    public PublicConstructorExample() {
        System.out.println("这是一个public构造方法");
    }

    public static void main(String[] args) {
        PublicConstructorExample example = new PublicConstructorExample();
    }
}
  1. 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方法获取唯一的实例。
  1. 无修饰符(默认访问级别)
    • 只能在同一个包内被访问。如果类的构造方法没有修饰符,那么只有在同一个包中的其他类可以访问和调用这个构造方法。例如:
package com.example;

class DefaultConstructorExample {
    DefaultConstructorExample() {
        System.out.println("这是一个默认访问级别的构造方法");
    }
}
  • 在另一个类中,如果不在com.example包中,就无法直接访问DefaultConstructorExample类的构造方法。

12、覆盖与重载的区别

  1. 覆盖(Override)
    • 定义:发生在子类中,子类重写了父类中具有相同方法签名(方法名、参数列表、返回类型相同,子类返回类型可以是父类返回类型的子类)的方法,以实现不同的行为。
    • 规则
      • 方法签名必须相同(包括方法名、参数列表、参数顺序和参数类型)。
      • 子类方法的返回类型必须与父类方法的返回类型兼容(可以是父类返回类型的子类)。
      • 子类方法不能比父类方法有更严格的访问限制(可以相同或更宽松)。
    • 例如:
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类中重写后的版本。
  1. 重载(Overload)
    • 定义:在同一个类中,定义了多个方法名相同但参数列表不同(参数个数、参数类型或参数顺序不同)的方法。
    • 规则
      • 方法名必须相同。
      • 参数列表必须不同。
      • 返回类型可以不同,但不构成重载的关键因素。
    • 例如:
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方法,根据传入参数的不同,会调用相应的重载版本。
    3.主要区别
    覆盖(Override)发生在子类与父类之间,子类重写父类方法,要求方法签名(方法名、参数列表、返回类型等)完全相同,且访问修饰符不能更严格,重写后的方法实现不同功能;
    重载(Overload)是在同一类中,方法名相同但参数列表不同(参数个数、类型、顺序至少一项不同),返回类型可不同,通过不同参数列表来实现同名方法的多种功能变体,两者主要区别在于发生位置和方法签名的要求不同。

13、怎么创建一个对象

  1. 使用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是传递给构造方法的参数,用于初始化对象的属性。
  1. 通过反射机制(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关键字创建对象,性能较低且代码相对复杂。

14、访问修饰符的简单了解

  1. public
    • 具有最大的访问权限,可以被任何类访问,无论是在同一个包中还是不同的包中。例如,一个公共类的公共方法可以被其他任何类调用。
  2. protected
    • 可以被同一个包中的类访问,以及被不同包中的子类访问。例如,在继承关系中,子类可以访问父类中受protected修饰的成员。
  3. 无修饰符(默认访问级别)
    • 只能被同一个包中的类访问。如果一个类或成员没有修饰符,它的访问范围仅限于所属的包内。
  4. private
    • 具有最小的访问权限,只能在类的内部被访问,外部类无法访问private修饰的成员。通常用于隐藏类的内部实现细节,如类的私有属性和私有方法。

15、多态的表现

  1. 方法重写(Override)实现多态
    • 如前面提到的,子类重写父类的方法,在运行时根据对象的实际类型来决定调用哪个类的重写方法。例如:
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类是父类,CircleRectangle是子类,它们都重写了draw方法。在main方法中,创建了CircleRectangle对象并赋值给Shape类型的变量,当调用draw方法时,根据对象的实际类型(CircleRectangle)来执行相应类中的重写方法。
  1. 方法重载(Overload)体现多态的一种形式
    • 虽然方法重载主要是在同一个类中提供多个同名方法的不同实现,但也可以看作是一种多态性的体现。通过不同的参数列表来调用不同版本的方法,根据传入的参数类型和个数来决定执行哪个重载方法。例如:
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);
    }
}
  1. 对象类型转换实现多态
    • 可以将子类对象赋值给父类变量(向上转型),在需要时再将父类变量强制转换为子类类型(向下转型)。例如:
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方法。

16、接口实现一个类的注意事项

  1. 实现接口的类必须实现接口中定义的所有方法
    • 接口定义了一组方法签名,但没有实现。实现接口的类必须提供这些方法的具体实现,否则该类必须被定义为抽象类。例如:
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被定义为抽象类。
  1. 一个类可以实现多个接口
    • 这使得类可以具有多种行为。例如:
interface Printable {
    void print();
}

class Document implements Shape, Printable {
    @Override
    public void draw() {
        System.out.println("绘制文档相关图形");
    }

    @Override
    public void print() {
        System.out.println("打印文档");
    }
}
  • Document类实现了ShapePrintable两个接口,需要实现这两个接口中定义的所有方法。
  1. 接口中的方法默认是publicabstract
    • 实现接口的类在实现接口方法时,必须使用public访问修饰符(除非在接口中方法被定义为defaultstatic方法,Java 8引入),因为接口方法的访问权限要求是公开的,且没有方法体(抽象的)。例如:
interface Movable {
    void move();
}

class Car implements Movable {
    @Override
    public void move() {
        System.out.println("汽车在行驶");
    }
}
  • 这里Car类实现Movable接口的move方法时,必须使用public修饰符,否则会导致编译错误。
  1. 接口中可以定义常量(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常量,不需要重新定义或初始化。
  1. 接口的继承性
    • 一个接口可以继承另一个接口,实现类需要实现继承链上所有接口的方法。例如:
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中定义的所有方法。
  1. 接口中方法的冲突解决(Java 8引入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方法冲突。

17、Object类要注意哪些方法,比如hashCodeequals等等

  1. equals方法
    • 用于比较两个对象是否相等。默认情况下,它的行为与==相同,即比较对象的引用地址。但在很多类中(如StringInteger等)被重写,用于比较对象的内容是否相等。自定义类如果需要比较对象内容,通常需要重写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);
    }
}
  1. hashCode方法
    • equals方法密切相关,用于返回对象的哈希码值。哈希码主要用于在哈希表(如HashMapHashSet等集合类)中快速定位对象。如果两个对象根据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);
    }
}
  1. 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)personPerson类的对象)时,会输出更易读的对象信息,如Person{name='Alice', age=25}
  1. 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方法来实现深拷贝。
  1. 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();
        }
    }
}

18、编写图形界面要导入什么,import……

  1. java.awt包(Abstract Window Toolkit)
    • 提供了创建图形用户界面(GUI)的基本组件和功能,如窗口、按钮、文本框等。例如:
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用于创建按钮,以及相关的事件处理类。
  1. 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.JFramejavax.swing.JButton等相关类来创建图形界面。SwingUtilities.invokeLater用于确保Swing组件在事件分发线程中进行初始化和更新,以避免线程安全问题。
  1. java.awt.event包(如果使用java.awt组件进行事件处理)
    • 提供了各种事件类和监听器接口,用于处理图形界面组件的用户交互事件,如按钮点击、鼠标移动等。在前面的AWTExample中就导入了java.awt.event.ActionEventjava.awt.event.ActionListener来处理按钮的点击事件。
  2. javax.swing.event包(如果使用javax.swing组件进行事件处理)
    • 类似于java.awt.event包,但针对javax.swing组件的特定事件提供了更丰富的支持。例如,对于JList组件的列表选择事件等,可以使用这个包中的相关类和接口进行处理。

19、集合框架和数组的区别?

  1. 长度特性
    • 数组:长度是固定的,在创建数组时就需要指定大小,之后不能改变。例如int[] arr = new int[5];创建了一个长度为5的整数数组,无法直接增加或减少其长度。
    • 集合框架:长度是可变的。例如ArrayList可以根据需要动态地添加或删除元素,不需要预先指定最大长度。
  2. 存储类型
    • 数组:只能存储相同类型的元素。例如int[]数组只能存储整数,String[]数组只能存储字符串。
    • 集合框架:可以存储不同类型的对象(如果没有使用泛型指定具体类型),但在使用泛型后,集合可以确保存储的元素类型符合预期,同时也可以存储不同类型的对象,只要它们是指定类型的子类或实现类。例如List可以存储任何对象,而List只能存储字符串对象。
    • 功能和操作便利性
      • 数组:基本操作相对简单,通过索引可以快速访问元素,但在插入、删除元素时可能需要手动移动其他元素,效率较低。例如,在数组中间插入一个元素,需要将插入位置后面的元素依次向后移动一位。
      • 集合框架:提供了丰富的方法来操作元素,如添加、删除、查找、排序等,并且在插入和删除元素时通常不需要手动处理元素的移动(内部实现会自动处理)。例如ArrayListaddremove方法,TreeSet的自动排序功能等。集合框架还提供了迭代器(Iterator)等方式来方便地遍历元素,而数组遍历通常使用for循环或增强for循环。
    • 内存管理
      • 数组:在内存中是连续存储的,访问速度相对较快,但如果需要扩展数组长度(创建一个新的更大的数组并复制元素),可能会消耗较多的内存和时间。
      • 集合框架:内部的存储结构因具体集合类型而异,如ArrayList内部是数组实现,LinkedList是链表实现,它们在内存管理上有不同的特点。集合框架在动态调整大小时,通常会有更优化的内存管理策略,但也可能会有一定的内存开销(如存储额外的节点信息等)。
    • 20、对于异常,trycatchfinally里面分别放什么代码?

      1. 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块中。
      1. catch
        • 用于捕获并处理try块中抛出的异常。可以有多个catch块来捕获不同类型的异常,catch块中的代码用于处理相应类型的异常,如记录错误信息、提示用户、进行一些恢复操作等。例如,在上面的文件读取例子中,如果出现IOExceptioncatch块中的e.printStackTrace()会打印出异常的堆栈跟踪信息,方便调试。还可以根据具体需求进行更复杂的处理,如提示用户文件不存在并提供重新选择文件的选项等。
      2. finally
        • 无论try块中是否抛出异常,finally块中的代码都会被执行。通常用于释放资源,如关闭文件、关闭数据库连接、释放网络连接等。在上面的文件读取例子中,即使在try块中出现异常,finally块中的bufferedReader.close();fileReader.close();也会被执行,确保文件资源被正确关闭,避免资源泄漏。

      21、常见的异常类?

      1. NullPointerException
        • 当程序试图访问一个空对象的成员(如调用空对象的方法、访问空对象的属性等)时抛出。例如:
      String str = null;
      System.out.println(str.length());
      
      • 这里strnull,调用length方法会抛出NullPointerException
      1. ArrayIndexOutOfBoundsException
        • 当使用非法的索引访问数组时抛出。例如:
      int[] arr = {1, 2, 3};
      System.out.println(arr[3]);
      
      • 数组arr的有效索引范围是0到2,访问arr[3]会抛出ArrayIndexOutOfBoundsException
      1. NumberFormatException
        • 当试图将一个不能转换为数字的字符串转换为数字类型(如Integer.parseIntDouble.parseDouble等方法)时抛出。例如:
      String str = "abc";
      int num = Integer.parseInt(str);
      
      • 因为str不是有效的数字字符串,所以会抛出NumberFormatException
      1. IOException
        • 用于处理输入输出操作(如文件读写、网络通信等)时可能出现的异常。它有多个子类,如FileNotFoundException(当试图打开一个不存在的文件时抛出)、SocketException(网络套接字操作出错时抛出)等。例如:
      try {
          FileReader fileReader = new FileReader("nonexistent.txt");
      } catch (FileNotFoundException e) {
          e.printStackTrace();
      }
      
      1. ClassNotFoundException
        • 当试图加载一个不存在的类时抛出。例如,在使用反射机制时,如果指定的类名不正确,就会抛出此异常:
      try {
          Class.forName("NonExistentClass");
      } catch (ClassNotFoundException e) {
          e.printStackTrace();
      }
      
      1. ArithmeticException
        • 当进行不合法的算术运算时抛出,如除以零。例如:
      int result = 10 / 0;
      
      • 这会抛出ArithmeticException

      22、多线程继承自Thread接口/实现Runnable接口的优缺点

      1. 继承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类的子类,会导致代码结构混乱,并且如果需要修改线程相关的行为,可能需要在多个子类中进行重复修改。
      1. 实现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()等方式来获取当前线程的相关信息,在一定程度上增加了代码的编写难度。

      23、Thread类中的常见方法

      1. start方法
        • 用于启动一个新线程,使线程进入就绪状态(等待获取CPU时间片),当线程获得CPU资源后,会自动调用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异常。
      1. 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();
          }
      }
      
      1. sleep方法(static方法)
        • 使当前正在执行的线程暂停指定的时间(以毫秒为单位)。在暂停期间,线程会释放CPU资源,进入阻塞状态,直到睡眠时间结束,线程重新进入就绪状态等待获取CPU时间片。例如:
      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方法时抛出,表示当前线程的睡眠被中断。
      1. 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();
          }
      }
      
      1. 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方法可以传入一个超时时间参数,表示最多等待指定的时间,如果超过该时间,当前线程会继续执行,而不管被等待的线程是否执行完毕。
      1. interrupt方法
        • 用于中断一个线程。当一个线程处于阻塞状态(如sleepwait等方法导致的阻塞)时,调用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();
          }
      }
      
      1. getNamesetName方法
        • getName方法用于获取线程的名称,setName方法用于设置线程的名称。默认情况下,线程的名称是自动生成的,如Thread - 0Thread - 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();
          }
      }
      
      1. getPrioritysetPriority方法
        • 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();
          }
      }
      

      24、数据库常用的接口StatementPreparedStatement的区别

      1. 语法和参数传递方式
        • Statement
          • 用于执行静态SQL语句,即SQL语句在执行前已经确定,不能包含参数。例如:
      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
        • 用于执行预编译的SQL语句,可以包含参数。在创建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();
      
      1. 性能和安全性
        • 性能
          • 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语句的结构。
      2. 灵活性和可维护性
        • 灵活性
          • PreparedStatement在处理动态SQL语句时更加灵活,只需要改变参数值就可以执行不同条件的查询或更新操作,而不需要重新编写整个SQL语句。对于Statement,如果SQL语句的条件部分需要动态变化,可能需要通过字符串拼接来构建SQL语句,这不仅容易出错,而且代码可读性较差。
        • 可维护性
          • 由于PreparedStatement的SQL语句结构相对固定,参数化处理使得代码更易于理解和维护。在代码中可以清晰地看到SQL语句的逻辑结构,而不需要在大量的字符串拼接中查找SQL语句的关键部分。而Statement的SQL语句如果包含大量的动态拼接部分,维护起来会比较困难,尤其是当SQL语句比较复杂时。

      25、为了更快地读写文件要用什么流?

      1. BufferedInputStreamBufferedOutputStream(缓冲流)
        • 作用原理
          • 缓冲流内部维护了一个缓冲区,在读取文件时,会一次性从文件中读取一块数据到缓冲区,然后程序从缓冲区中逐个获取数据,而不是每次都直接从文件读取一个字节或字符,这样可以减少对文件的实际读取次数,提高读取效率。在写入文件时,数据先写入缓冲区,当缓冲区满了或者手动刷新缓冲区时,才将数据一次性写入文件,减少了对文件的写入操作次数。
        • 使用示例(读取文件)
      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();
      }
      
      1. BufferedReaderBufferedWriter(字符缓冲流,用于处理文本文件)
        • 作用原理
          • 与字节缓冲流类似,字符缓冲流内部也有缓冲区。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();
      }
      
      1. 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();
      }
      
      1. 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();
      }
      
      1. 选择合适流的考虑因素
        • 文件大小
          • 对于小文件(一般指几十MB以下),BufferedInputStream/BufferedOutputStreamBufferedReader/BufferedWriter通常已经能够提供较好的性能提升,并且使用相对简单。
          • 对于大文件(几百MB甚至GB级别),FileChannelMappedByteBuffer可能会更有优势,因为它们能够更有效地利用系统资源进行高效的读写操作,但它们的使用相对复杂一些,需要对内存管理和操作系统的文件处理机制有一定的了解。
        • 数据类型
          • 如果处理的是文本文件,BufferedReader/BufferedWriter是比较方便的选择,它们提供了按行读取和写入文本的功能,更符合文本处理的需求。
          • 如果处理的是二进制文件,BufferedInputStream/BufferedOutputStreamFileChannel配合ByteBuffer可以更好地处理字节数据的读写。MappedByteBuffer也适用于二进制大文件的处理,特别是在需要随机访问文件内容的情况下。
        • 读写模式
          • 如果只是简单的顺序读写操作,上述提到的缓冲流和通道流都可以满足需求。但如果需要随机读写文件(例如在文件的不同位置进行读写操作),FileChannelMappedByteBuffer提供了更方便的随机访问功能。例如,FileChannelposition方法可以设置文件指针的位置,从而实现在指定位置进行读写操作,MappedByteBuffer也可以通过改变索引位置来实现随机访问。

      26、编写Java网络相关要导入什么包?

      1. java.net
        • 提供了与网络通信相关的基本类和接口,是编写Java网络程序的核心包。
        • Socket类和ServerSocket
          • Socket类用于实现客户端套接字,通过它可以与服务器建立连接并进行数据传输。例如,编写一个简单的TCP客户端程序:
          • ServerSocket类用于实现服务器套接字,在指定端口监听客户端连接请求,并创建Socket对象与客户端进行通信。例如,编写一个简单的TCP服务器程序:

      你可能感兴趣的:(java)