当前位置: 首页 > article >正文

单词反转和数组去重,附经典面试题一份

博彦科技笔试:
给定字符,拼接成单词进行反转单词;

package org.example;

public class Main {
    public static void main(String[] args) {
        char[] input = {'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', ' ', 'J', 'a', 'v', 'a'};
        String inputToString = new String(input);
        System.out.println("Input: " + inputToString);
        String[] words = inputToString.split(" ");
        String res = "";
        for (int i= words.length-1; i>=0; i--){
            res+=words[i];
            if (i != 0){
                res+=" ";
            }
        }
        char[] output = res.toCharArray();
        System.out.println("----------------------------------");
        for(char c: output)
            System.out.print(c);
    }

}

起初只想到了用split进行分割,忘记了是String 类型才有的,结果是String 与char类型的数组转化,用到了.toCharArray();

在这里插入图片描述
在这里插入图片描述

import java.util.*;

public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * @param s string字符串 
     * @param n int整型 
     * @return string字符串
     */
    public String trans(String s, int n) {
        // 用来存储最终的结果,初始化一个空的 StringBuilder 实例
        StringBuilder res = new StringBuilder();
        
        // 去掉字符串两端的空白字符,使用正则分割字符串,处理多个空格
        String[] words = s.split(" ");
        
        // 从后往前遍历单词数组
        for (int i = words.length - 1; i >= 0; i--) {
            // 将当前单词转换为字符数组
            char[] word = words[i].toCharArray();
            
            // 遍历当前单词的每个字符,反转大小写
            for (char c : word) {
                if (Character.isLowerCase(c)) {
                    res.append(Character.toUpperCase(c));
                } else {
                    res.append(Character.toLowerCase(c));
                }
            }
            
            // 如果不是最后一个单词,则在后面加一个空格
            if (i != 0) {
                res.append(" ");
            }
        }
        
        // 返回最终的结果字符串
        return res.toString();
    }
}

没有AC

保留两位小数的格式

package org.example;

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner  = new Scanner(System.in);
        Double d = scanner.nextDouble();
        System.out.println(String.format("%.2f",d));
    }

}

package org.example;

import java.text.DecimalFormat;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        Double d = scanner.nextDouble();
        DecimalFormat df = new DecimalFormat("#.00");
        System.out.println(df.format(d));
    }
}


(北京)有限公司:

*1. package testpkg.p1;
public class ParentUtil

{

    public int x = 420;

    protected int doStuff() { return x; }

}

package testpkg.p2;

import testpkg.p1.ParentUtil;

public class ChildUtil extends ParentUtil

{

    public static void main(String [] args)

    {

        new ChildUtil().callStuff();

    }

    void callStuff()

    {

        System.out.print("this " + this.doStuff() ); /* Line 18 */

        ParentUtil p = new ParentUtil();

        System.out.print(" parent " + p.doStuff() ); /* Line 20 */

    }

}有错误吗

解释:
这个代码有两个包(testpkg.p1testpkg.p2)和继承关系。我们来逐步分析代码,看看是否有错误。

代码结构分析:

1. ParentUtil 类(位于 testpkg.p1 包)
package testpkg.p1;

public class ParentUtil {
    public int x = 420;

    protected int doStuff() {
        return x;
    }
}
  • ParentUtil 类定义了一个公开字段 x 和一个受保护的 doStuff 方法。
  • doStuff 方法是 protected 的,意味着它只能在同一个包内或者子类中访问。
  • 没有错误,代码正确。
2. ChildUtil 类(位于 testpkg.p2 包)
package testpkg.p2;

import testpkg.p1.ParentUtil;

public class ChildUtil extends ParentUtil {

    public static void main(String[] args) {
        new ChildUtil().callStuff();
    }

    void callStuff() {
        System.out.print("this " + this.doStuff()); /* Line 18 */

        ParentUtil p = new ParentUtil();
        System.out.print(" parent " + p.doStuff()); /* Line 20 */
    }
}
  • ChildUtil 类继承自 ParentUtil
  • main 方法创建了 ChildUtil 实例并调用了 callStuff 方法。
  • callStuff 方法中,首先打印了 this.doStuff(),然后创建了一个 ParentUtil 对象并打印了 p.doStuff()

错误分析:

1. Line 18 (this.doStuff() 调用)
  • this.doStuff() 调用是合法的,因为 doStuffprotected 的,而 ChildUtilParentUtil 的子类。
  • protected 方法可以在子类中访问,所以 this.doStuff() 是合法的,并且能够访问父类的 doStuff 方法。
2. Line 20 (p.doStuff() 调用)
  • 在这一行,创建了一个 ParentUtil 类型的对象 p,然后尝试调用 p.doStuff()
  • 问题出在这里:doStuff 方法是 protected 的,它只能在同一个包内或子类中被访问。
  • pParentUtil 类型的对象,且 ParentUtilChildUtil 处于不同的包(分别在 testpkg.p1testpkg.p2 中)。因此,在 testpkg.p2 中直接访问 p.doStuff() 是不合法的。

错误的原因:

  • 由于 doStuffprotected,它无法在不同包的非子类中被访问,因此 p.doStuff() 会导致编译错误。

解决方案:

有两种可能的解决方法来解决 Line 20 的错误。

  1. 更改 doStuff 方法的访问修饰符
    如果希望在 testpkg.p2 包中访问 doStuff 方法,可以将 doStuff 方法的访问修饰符更改为 public,这样它就可以被其他包的类访问。

    public int doStuff() {
        return x;
    }
    
  2. 通过继承调用 doStuff
    如果你不希望修改 doStuff 的访问级别,可以通过子类对象来调用 doStuff 方法。例如,将 p 的类型改为 ChildUtil,这样就能通过子类访问受保护的方法。

    ParentUtil p = new ChildUtil();
    System.out.print(" parent " + p.doStuff());
    

    这种方法可以确保 doStuff 在子类中被访问,从而不会导致权限错误。

结论:

  • Line 18 没有错误,因为在子类中可以访问 protected 方法。
  • Line 20 存在错误,因为 doStuffprotected 的,ParentUtilChildUtil 在不同的包中,导致 p.doStuff() 无法访问。

通过上述两种解决方案中的任何一种,你可以修复代码中的错误。


  • *2. 哪个集合类允许扩大或缩小其大小,并提供对其元素的索引访问,但其方法不是同步的(synchronized)?

A.java.util.HashSet

B.java.util.LinkedHashSet

C.java.util.List

D.java.util.ArrayList

答案是 D. java.util.ArrayList

解释:

让我们逐一分析每个选项:

A. java.util.HashSet
  • HashSet 是一个集合类,它存储不重复的元素,并且没有提供按索引访问元素的功能。它是一个 无序集合,因此不能按索引访问元素。
  • 此外,HashSet 并没有提供同步机制,它的所有方法不是同步的。
B. java.util.LinkedHashSet
  • LinkedHashSet 是一个继承自 HashSet 的集合类,它也存储不重复的元素,并且保持元素插入的顺序。与 HashSet 一样,它不提供按索引访问元素的功能。
  • 同样,LinkedHashSet 也没有同步机制,方法也不是同步的。
C. java.util.List
  • List 是一个接口,它定义了一个有序的集合,并允许通过索引访问元素。它的子接口有 ArrayListLinkedList 等。虽然 List 确实提供索引访问,但这个选项本身是一个接口,不是一个具体的实现类,因此不适合作为答案。
D. java.util.ArrayList
  • ArrayList 是一个实现了 List 接口的集合类,它提供了可变大小的数组,并允许通过索引访问元素。
  • ArrayList 的大小是可以自动扩展和缩小的。它在添加或删除元素时,会动态调整其内部数组的大小。
  • ArrayList 的方法 不是同步的,这意味着它不是线程安全的,多个线程同时修改同一个 ArrayList 时需要额外的同步机制来保证线程安全。

结论:

ArrayList 是唯一符合题目描述的集合类,它允许扩展或缩小大小,提供索引访问,并且其方法不是同步的。

所以,正确答案是 D. java.util.ArrayList


3、哪种方法不会直接导致线程停止?

A.notify()

B.wait()

C. InputStream access

D. sleep()

正确答案是 A. notify()

解释:

让我们逐一分析每个选项:

A. notify()
  • notify() 方法是用于唤醒在同步代码块或方法中等待的线程,但它不会直接导致线程停止。它仅仅是唤醒等待的线程,并允许线程继续执行。线程在调用 notify() 时并不会停止,它只是将线程的状态从等待中唤醒,使其有机会重新获取锁并继续执行。

  • 总结notify() 不会直接导致线程停止,它的作用是唤醒其他线程。

B. wait()
  • wait() 方法是用于使当前线程进入等待状态,直到其他线程通过 notify()notifyAll() 唤醒它。调用 wait() 后,线程会被挂起并进入 等待队列,直到它被唤醒后才会继续执行。因此,wait() 会导致线程停顿,直到被唤醒。

  • 总结wait() 会直接导致线程停顿,直到被唤醒。

C. InputStream access
  • InputStream 访问(例如 read() 方法)会使当前线程在等待输入数据时阻塞,直到数据可用或发生错误。然而,阻塞并不意味着线程停止,它只是挂起当前线程,等待输入流中的数据。线程会在等待期间被阻塞,但它不会像 wait() 那样主动进入 “等待” 状态;它只是等待数据的到来。

  • 总结InputStream 的访问可能会阻塞线程,但它不会直接导致线程停止。

D. sleep()
  • sleep() 方法使当前线程暂停执行指定的时间。它不会释放锁,仅仅使当前线程进入 睡眠状态,在指定时间后自动恢复执行。虽然线程暂停,但线程并没有停止,它仍然存在,并会在 sleep() 时间到期后继续执行。

  • 总结sleep() 会使线程暂停一段时间,但不会直接停止线程,它会在时间结束后恢复执行。

结论:

  • notify() 只是唤醒等待的线程,并不会直接导致线程停止。其他方法(wait()sleep()InputStream access)都可能导致线程停止或挂起。

因此,正确答案是 A. notify()


4、

 class X implements Runnable
{

    public static void main(String args[])

    {

        /* Missing code? */

    }

    public void run() {}

}

下列哪种方法可以启动线程?


A.Thread t = new Thread(X);

B. Thread t = new Thread(X); t.start();

C. X run = new X(); Thread t = new Thread(run); t.start();

D. Thread t = new Thread(); x.run();

正确答案是 C. X run = new X(); Thread t = new Thread(run); t.start();

解释:

  1. A. Thread t = new Thread(X);

    • 这行代码是错误的。X 是一个类,不是一个对象。要将 Runnable 对象传递给 Thread 构造函数,必须创建 X 类的实例。所以,直接将 X 类传递给 Thread 构造函数会导致编译错误。
  2. B. Thread t = new Thread(X); t.start();

    • 这也是错误的,原因同上,Thread 的构造函数需要一个 Runnable 对象作为参数,而不能直接传递类名。应该传递 Runnable 实例。
  3. C. X run = new X(); Thread t = new Thread(run); t.start();

    • 这是正确的做法。首先,创建了一个 X 类的实例 run,它实现了 Runnable 接口。然后,将该实例作为参数传递给 Thread 构造函数,并调用 start() 启动线程。start() 方法会调用 run() 方法,开始线程的执行。
  4. D. Thread t = new Thread(); x.run();

    • 这行代码是错误的。首先,Thread 类的构造函数没有传递 Runnable 实例。此外,直接调用 x.run() 并不会启动新线程,而是会在当前线程中执行 run() 方法,导致线程未真正启动。

结论:

为了启动线程,正确的方式是先创建一个实现了 Runnable 接口的实例,然后将其传递给 Thread 类的构造函数,最后调用 start() 方法启动线程。

因此,正确答案是 C. X run = new X(); Thread t = new Thread(run); t.start();


5、

*5. 哪三个保证一个线程会离开运行状态
yield()

wait()

notify()

notifyAll()

sleep(1000)

aLiveThread.join()

Thread.killThread()

要确保一个线程离开运行状态并进入其他状态(如 等待状态阻塞状态终止状态 等),我们可以使用以下方法:

1. yield()

  • 作用yield() 是一个静态方法,表示当前线程主动让出 CPU 时间片,但不保证线程会立即停止运行。调用 yield() 后,当前线程会被挂起,操作系统会重新调度其他线程。然而,线程的状态将变为 就绪状态,而不是 阻塞状态
  • 结论yield() 只会让当前线程 暂停执行,但不会离开运行状态,线程会在稍后的时间恢复执行。

2. wait()

  • 作用wait() 会使当前线程进入 等待状态,直到它被其他线程唤醒(通常通过 notify()notifyAll())。在等待期间,线程不再占用 CPU,因此它会离开运行状态。
  • 结论wait() 确实会导致线程离开运行状态,进入 等待状态

3. notify()

  • 作用notify() 是唤醒一个在当前对象监视器上等待的线程,但不会直接影响当前线程的状态。它只是通知等待线程有条件去执行,并不会让当前线程离开运行状态。
  • 结论notify() 不会导致当前线程离开运行状态,它只会唤醒等待线程。

4. notifyAll()

  • 作用notifyAll() 是唤醒当前对象监视器上所有等待的线程,同样,它并不影响当前线程的状态。
  • 结论notifyAll() 只会唤醒所有等待线程,并不会让当前线程离开运行状态。

5. sleep(1000)

  • 作用sleep(1000) 使当前线程进入 阻塞状态,并且会在指定时间(此处为 1000 毫秒)后自动恢复执行。
  • 结论sleep() 会使线程离开运行状态,进入 阻塞状态,直到睡眠时间结束。

6. aLiveThread.join()

  • 作用join() 方法使当前线程等待 aLiveThread 执行完毕。如果 aLiveThread 还没有执行完,调用 join() 的线程会进入 阻塞状态,直到 aLiveThread 完成。
  • 结论join() 会让当前线程离开运行状态,进入 阻塞状态,直到目标线程执行完成。

7. Thread.killThread()

  • 作用Thread.killThread() 并不是 Java 中的有效方法。实际上,Java 中并没有提供直接杀死线程的 API。线程的停止应通过其他方式(如设置标志位或让线程自然终止)。
  • 结论:没有 killThread() 方法,不适用。

总结:

根据线程离开运行状态的标准,我们可以得到以下结论:

  • wait():会导致线程进入等待状态。
  • sleep(1000):会导致线程进入阻塞状态。
  • aLiveThread.join():会导致当前线程进入阻塞状态,直到目标线程执行完毕。

正确答案:

  • wait()
  • sleep(1000)
  • aLiveThread.join()

这三种方法能够确保一个线程离开运行状态。

线程的几种状态分别是?


6. int i = l, j = -1;

switch (i)

{

    case 0, 1: j = 1; /* Line 4 */

    case 2: j = 2;

    default: j = 0;

}

System.out.println("j = " + j);有错误吗,结果是

错误分析:

这段代码存在语法错误:

int i = l, j = -1;

switch (i) {

    case 0, 1: j = 1; /* Line 4 */

    case 2: j = 2;

    default: j = 0;

}

System.out.println("j = " + j);

1. 语法错误

  • 问题 1switch 语句中,case 标签通常只能列出一个常量值。Java 14 引入了 增强的 switch 语法(使用逗号分隔多个值),但只有从 Java 14 开始才支持 case 0, 1: 的写法,前提是启用了 switch 表达式 或使用了增强版 switch
  • 问题 2:在该代码中,l 是一个变量,而不是常量。如果 l 没有定义,这将导致编译错误。i 需要是一个有效的整数常量值。

2. 修正

  • 修改 1:首先,确保 ij 的定义是正确的,并且 i 的值是常量。假设 i = 1
  • 修改 2:如果使用传统的 switch 语法(即没有启用 Java 14+ 的增强 switch),每个 case 必须单独指定一个常量。

3. 代码修复

int i = 1, j = -1;

switch (i) {
    case 0:
    case 1: 
        j = 1; 
        break; // Adding break to prevent fall-through
    case 2: 
        j = 2;
        break;
    default: 
        j = 0;
}

System.out.println("j = " + j);

4. 结果分析

  1. i = 1 时,switch 会检查 case 1,并进入该分支。
  2. case 1 执行后将 j = 1,然后执行 break 语句,跳出 switch 语句。
  3. 因此,j = 1,最后 System.out.println("j = " + j); 打印出 j = 1

5. 增强 switch 语法(如果你使用 Java 14+):

如果你启用了增强 switch 语法,代码可以写成这样:

int i = 1, j = -1;

switch (i) {
    case 0, 1: 
        j = 1; 
        break; // break is optional in switch expressions
    case 2: 
        j = 2;
        break;
    default: 
        j = 0;
}

System.out.println("j = " + j);

在这种语法下,case 0, 1: 表示 i01,都将执行 j = 1

总结:

  • 错误:原始代码中有一个语法错误,case 0, 1: 是增强 switch 语法的一部分,只有在 Java 14+ 启用该语法时才有效。
  • 修复后的结果:修正后的代码中,假设 i = 1,最终输出 j = 1

7. public class SwitchTest
{

    public static void main(String[] args)

    {

        System.out.println("value =" + switchIt(4));

    }

    public static int switchIt(int x)

    {

        int j = 1;

        switch (x)

        {

            case l: j++;

            case 2: j++;

            case 3: j++;

            case 4: j++;

            case 5: j++;

            default: j++;

            }

        return j + x;

    }

}

错误分析:

这段代码有几个问题,首先我们逐一分析:

1. case l: 语法错误

  • switch 语句中,l 是一个变量,而 switchcase 标签必须是常量。因此,case l: 会导致编译错误。
  • 如果 l 是一个数字常量(比如 l = 1;),则 case 应该是常量值 1,而不是变量名 l

2. 缺少 break 语句

  • 由于没有 break 语句,每个 case 会出现 “fall-through”(掉入下一个 case)的现象。这意味着,如果 x 为某个 case,程序将继续执行该 case 后面所有的代码,直到遇到 break(或者到达 switch 末尾)。

3. 修正代码

  • 我们假设 l 是一个常量 1,并且修正其他潜在问题。

修正后的代码:

public class SwitchTest {

    public static void main(String[] args) {
        System.out.println("value = " + switchIt(4));
    }

    public static int switchIt(int x) {
        int j = 1;

        switch (x) {
            case 1: // 修正了 l 为常量 1
                j++; // 进入 case 1 后,j 会加 1
                break; // 加入 break 防止继续 fall-through
            case 2:
                j++;
                break;
            case 3:
                j++;
                break;
            case 4:
                j++;
                break;
            case 5:
                j++;
                break;
            default:
                j++; // 默认情况下 j 也会增加
                break;
        }

        return j + x;
    }
}

4. 代码解释

  1. switchIt(4) 被调用时,x 的值是 4。
  2. switch 语句开始时,检查 x 的值是否匹配各个 case 标签。
  3. case 4: 匹配 x = 4,进入 case 4j 的值变为 2。
  4. 由于每个 case 后都有 break,程序会跳出 switch 语句,不会继续执行 case 5default
  5. 最终,switchIt(4) 返回 j + x,即 2 + 4 = 6

5. 输出

value = 6

6. 总结

  • 问题:原始代码中的 case l: 语法错误,且没有 break,会导致 fall-through
  • 修正:将 case l: 改为常量 case 1: ,并且为每个 case 添加 break 语句。
  • 结果:修正后的代码输出 value = 6

关于java.util.HashSet下列说法正确的是?

A.集合中的元素是有序的。

B. 集合保证是不可变的。

C.集合中的元素保证是唯一的。

D.使用唯一键访问集合中的元素。

关于 java.util.HashSet,正确的说法是 C

逐项分析:

  • A. 集合中的元素是有序的。

    • 错误HashSet 不保证元素的顺序。它是一个 无序 集合,意味着插入元素的顺序和遍历顺序可能不一致。如果你需要元素保持插入顺序,可以使用 LinkedHashSet
  • B. 集合保证是不可变的。

    • 错误HashSet 本身是可变的,可以添加和删除元素。如果你需要不可变的集合,可以使用 Collections.unmodifiableSet()Set.of() (在 Java 9 及以后版本)。
  • C. 集合中的元素保证是唯一的。

    • 正确HashSet 保证集合中的元素是唯一的。如果你试图向 HashSet 中添加重复的元素,它将不会被添加。重复元素的定义是根据元素的 equals() 方法和 hashCode() 方法来判定的。
  • D. 使用唯一键访问集合中的元素。

    • 错误HashSet 并不像 HashMap 那样使用键(key)来访问元素。HashSet 是基于集合的,没有 “键” 的概念,它只关心元素是否存在。HashMap 才是基于键值对的结构,提供基于键的访问。

正确答案:

C. 集合中的元素保证是唯一的。


9. public class RTExcept
{

    public static void throwit ()

    {

        System.out.print("throwit ");

        throw new RuntimeException();

    }

    public static void main(String [] args)

    {

        try

        {

            System.out.print("hello ");

            throwit();

        }

        catch (Exception re )

        {

            System.out.print("caught ");

        }

        finally

        {

            System.out.print("finally ");

        }

        System.out.println("after ");

    }

}结果是

让我们逐步分析这段代码,理解它的执行过程,并得出结果。

代码分析:

public class RTExcept {

    public static void throwit() {
        System.out.print("throwit ");
        throw new RuntimeException(); // 抛出运行时异常
    }

    public static void main(String[] args) {
        try {
            System.out.print("hello ");
            throwit(); // 调用 throwit() 方法,它会抛出异常
        }
        catch (Exception re) {
            System.out.print("caught "); // 捕获异常并打印 "caught"
        }
        finally {
            System.out.print("finally "); // 无论如何都会执行,打印 "finally"
        }

        System.out.println("after "); // 这行代码在 finally 执行后执行
    }
}

执行流程:

  1. 进入 main 方法

    • 首先打印 "hello "
  2. 调用 throwit() 方法

    • throwit() 被调用,打印 "throwit "
    • 然后,throwit() 抛出一个 RuntimeException
  3. 异常被 catch 捕获

    • RuntimeExceptionException 类的子类,因此它被 catch 块中的 Exception 捕获。
    • 捕获异常后,打印 "caught "
  4. finally 块执行

    • 不管是否有异常,finally 块都会执行,打印 "finally "
  5. after 被打印

    • finally 块执行完后,程序继续执行 System.out.println("after ");,打印 "after"

输出结果:

hello throwit caught finally after 

解释:

  • "hello " 是在 try 块中执行时打印的。
  • "throwit " 是在调用 throwit() 方法时打印的。
  • "caught " 是在 catch 块中捕获异常后打印的。
  • "finally " 是在 finally 块中执行的。
  • "after " 是在 finally 块执行完后,main 方法中最后一行代码打印的。

因此,最终输出是:

hello throwit caught finally after 

10. class A
{

    public A(int x){}

}

class B extends A { }

public class test

{

    public static void main (String args [])

    {

        A a = new B();

        System.out.println("complete");

    }

}
### 代码分析:

让我们逐步解析这段代码的行为:

```java
class A {
    public A(int x) {
        // 构造函数 A,接受一个整数参数
    }
}

class B extends A {
    // 类 B 继承自类 A
    // 由于类 A 的构造函数需要一个 int 参数,B 必须调用 A 的构造函数。
}
 
public class test {
    public static void main(String args[]) {
        A a = new B();  // 创建一个 B 类型的对象,并赋值给 A 类型的引用
        System.out.println("complete");
    }
}

关键点分析:

  1. 类 A 的构造函数

    • class A 中定义了一个构造函数 public A(int x),它接受一个 int 参数。
    • 这意味着类 A 的对象必须通过传递一个整数来进行初始化。
  2. 类 B 继承自类 A

    • B 继承自 A,但没有定义自己的构造函数。
    • 因为类 B 没有显式定义构造函数,编译器会默认生成一个无参构造函数。
  3. 问题所在

    • class B 中,默认的无参构造函数会自动调用 super(),即 A 类的无参构造函数。
    • 然而,class A 中并没有提供无参构造函数,只有一个带 int 参数的构造函数。所以,B 的默认无参构造函数无法正常编译。
  4. 解决方法

    • 由于 A 类没有无参构造函数,类 B 必须显式调用 super(int x),并提供一个 int 参数给类 A 的构造函数。

修改后的代码:

class A {
    public A(int x) {
        // 构造函数 A,接受一个整数参数
        System.out.println("A's constructor with int: " + x);
    }
}

class B extends A {
    public B() {
        super(10);  // 显式调用 A 的构造函数,传递一个整数
        System.out.println("B's constructor");
    }
}

public class Test {
    public static void main(String args[]) {
        A a = new B();  // 创建 B 的对象,会调用 B 的构造函数
        System.out.println("complete");
    }
}

输出结果:

A's constructor with int: 10
B's constructor
complete

总结:

在原代码中,类 B 没有显式调用 super(int x),导致编译错误。解决方法是,在 B 的构造函数中显式调用 super(10),并传递一个合适的参数,这样程序就可以正常编译和运行。


11. String x = "xyz";
x.toUpperCase(); /* Line 2 */

String y = x.replace('Y', 'y');

y = y + "abc";

System.out.println(y);

程序的输出是

让我们逐步分析这段代码,了解每一行的作用以及最终输出。

String x = "xyz";  // 第1行,定义一个字符串变量 x 并赋值为 "xyz"
x.toUpperCase();    // 第2行,调用 toUpperCase() 方法,但结果没有保存

String y = x.replace('Y', 'y');  // 第3行,尝试将字符 'Y' 替换为 'y',但 'Y' 不在字符串中

y = y + "abc";  // 第4行,将 "abc" 拼接到字符串 y 上

System.out.println(y);  // 输出 y 的值

逐行解析:

  1. 第1行:

    String x = "xyz";
    
    • 这行定义了一个字符串 x,并将其初始化为 "xyz"
  2. 第2行:

    x.toUpperCase();
    
    • toUpperCase() 方法将返回一个将字符串全部转为大写的新字符串。
    • 但是,这里 没有将返回值赋给任何变量,所以这个操作没有实际影响。
    • 原字符串 x 依然是 "xyz",并没有改变。
  3. 第3行:

    String y = x.replace('Y', 'y');
    
    • replace('Y', 'y') 方法会尝试将 x 字符串中的字符 'Y' 替换为 'y'
    • 但是,字符串 "xyz" 中并没有字符 'Y'(它是小写的 'y'),因此 replace 操作不会做任何替换,y 仍然是 "xyz"
  4. 第4行:

    y = y + "abc";
    
    • 这行代码将 "abc" 字符串拼接到 y 字符串的末尾。
    • 由于 y"xyz",所以拼接后,y 的值变为 "xyzabc"
  5. 第5行:

    System.out.println(y);
    
    • 这行代码将打印 y 的值,即 "xyzabc"

输出结果:

xyzabc

总结:

  • x.toUpperCase(); 没有影响,因为返回的值没有被保存。
  • x.replace('Y', 'y'); 没有进行任何替换,因为 "xyz" 中没有大写字母 'Y'
  • 最终,y

13. public void foo( boolean a, boolean b)
{

    if( a )

    {

        System.out.println("A"); /* Line 5 */

    }

    else if(a && b) /* Line 7 */

    {

        System.out.println( "A && B");

    }

    else /* Line 11 */

    {

        if ( !b )

        {

            System.out.println( "notB") ;

        }

        else

        {

            System.out.println( "ELSE" ) ;

        }

    }

}


A.如果 a 为真且 b 为真,则输出为“A && BB.如果 a 为真且 b 为假,则输出为“notB”

C.如果 a 为假且 b 为真,则输出为“ELSE”

D.如果 a 为假且 b 为假,则输出为“ELSE”
让我们逐步分析这段代码,并确定每种情况下的输出。

### 代码分析:

public void foo(boolean a, boolean b) {
    if (a) {  // Line 3
        System.out.println("A");  // Line 5
    }
    else if (a && b) {  // Line 7
        System.out.println("A && B");
    }
    else {  // Line 11
        if (!b) {  
            System.out.println("notB"); 
        }
        else {
            System.out.println("ELSE");
        }
    }
}

逐步分析:

1. 如果 a 为真且 b 为真
  • atrue 时,进入第一个 if (a) 语句块。
  • 无论 b 是什么值,if (a) 会执行,输出 "A"

输出:

A
2. 如果 a 为真且 b 为假
  • atruebfalse 时,进入第一个 if (a) 语句块。
  • 无论 b 的值,if (a) 会执行,输出 "A"

输出:

A
3. 如果 a 为假且 b 为真
  • afalsebtrue 时,进入 else if (a && b)
  • 由于 afalsea && b 会返回 false,因此跳过这个 else if 块,进入 else 语句。
  • else 语句中,检查 if (!b)
    • !bfalse,因为 btrue
    • 所以执行 else 块,输出 "ELSE"

输出:

ELSE
4. 如果 a 为假且 b 为假
  • afalsebfalse 时,进入 else if (a && b)
  • 同样地,a && bfalse,跳过这个 else if,进入 else 块。
  • else 语句中,检查 if (!b)
    • !btrue,因为 bfalse
    • 所以执行 if (!b) 块,输出 "notB"

输出:

notB

总结选项:

  • A: 如果 a 为真且 b 为真,则输出为 "A",因为 if (a) 会被执行,无论 b 是否为真。
  • B: 如果 a 为真且 b 为假,则输出为 "A",因为 if (a) 会被执行。
  • C: 如果 a 为假且 b 为真,则输出为 "ELSE",因为进入 else 块后执行 "ELSE"
  • D: 如果 a 为假且 b 为假,则输出为 "notB",因为 if (!b) 会被执行。

最终答案:

  • A: 错误 — 因为 a 为真时,输出 "A"
  • B: 错误 — 因为 a 为真时,输出 "A",不需要检查 b
  • C: 正确 — 当 a 为假且 b 为真时,输出 "ELSE"
  • D: 正确 — 当 a 为假且 b 为假时,输出 "notB"

*14. class Bitwise
{

    public static void main(String [] args)

    {

        int x = 11 & 9;

        int y = x ^ 3;

        System.out.println( y | 12 );

    }

}

让我们逐步分析这段代码,理解每个位运算的操作,并确定最终的输出。

代码分析:

class Bitwise {

    public static void main(String [] args) {
        int x = 11 & 9;  // 第1步
        int y = x ^ 3;   // 第2步
        System.out.println(y | 12);  // 第3步
    }
}

步骤 1: x = 11 & 9

  • 这是一个按位与 (&) 操作。

  • 首先,我们将 119 转换为二进制:

    • 11 的二进制表示是 1011
    • 9 的二进制表示是 1001
    1011
    1001
    ----
    1001   // 结果是按位与操作后的值
    

    解释:按位与 (&) 操作是逐位比较两个二进制数的对应位。如果两个对应位都为 1,则结果为 1,否则为 0。所以:

    1011
    & 1001
    -----
    1001   // 结果是 9
    
  • 因此,x = 9

步骤 2: y = x ^ 3

  • 这是一个按位异或 (^) 操作。

  • x 现在是 9,我们将 93 转换为二进制:

    • 9 的二进制表示是 1001
    • 3 的二进制表示是 0011
    1001
    0011
    ----
    1010   // 结果是按位异或操作后的值
    

    解释:按位异或 (^) 操作是逐位比较两个二进制数的对应位。如果对应位相同,则结果为 0;如果不同,则结果为 1。所以上述操作结果为:

    1001
    ^ 0011
    -----
    1010   // 结果是 10
    
  • 因此,y = 10

步骤 3: y | 12

  • 这是一个按位或 (|) 操作。

  • y 现在是 10,我们将 1012 转换为二进制:

    • 10 的二进制表示是 1010
    • 12 的二进制表示是 1100
    1010
    1100
    ----
    1110   // 结果是按位或操作后的值
    

    解释:按位或 (|) 操作是逐位比较两个二进制数的对应位。如果两个对应位中至少有一个为 1,则结果为 1,否则为 0。所以:

    1010
    | 1100
    -----
    1110   // 结果是 14
    
  • 因此,y | 12 = 14

最终输出:

程序的最终输出是 14

结论:

14

15. public class X
{

    public static void main(String [] args)

    {

        String names [] = new String[5];

        for (int x=0; x < args.length; x++)

            names[x] = args[x];

        System.out.println(names[2]);

    }

}

运行如下命令:java X a b

程序的输出是?

让我们逐步分析这段代码,并解释程序的执行过程。

代码分析:

public class X {

    public static void main(String [] args) {

        String names [] = new String[5];  // 第1步

        for (int x = 0; x < args.length; x++)  // 第2步
            names[x] = args[x];  // 第3步

        System.out.println(names[2]);  // 第4步
    }
}

步骤 1: String names[] = new String[5];

  • 这里定义了一个 String 类型的数组 names,并为它分配了长度为 5 的空间。
  • 这意味着数组 names 可以存储 5 个字符串元素,初始化时,数组中的每个元素的值是 null

步骤 2: for (int x = 0; x < args.length; x++)

  • 这个循环遍历命令行参数 args,并将这些参数依次赋值给 names 数组。
  • args.length 是传入程序的命令行参数的数量。根据问题描述,命令行参数是 ab

步骤 3: names[x] = args[x];

  • 这个循环的作用是将 args 数组中的元素复制到 names 数组的对应位置。

对于命令行输入 java X a b

  • args[0]"a"args[1]"b"args.length 是 2。
  • 因此,names[0] = "a"names[1] = "b",其他位置(names[2]names[3]names[4])仍然是 null

步骤 4: System.out.println(names[2]);

  • 这里,程序打印 names[2]
  • 根据上面的分析,names[2] 没有被赋值,因此它的值是 null

运行命令:

命令是 java X a b,即传入了两个命令行参数:ab。因此:

  • args[0]"a"
  • args[1]"b"
  • args[2]args[3]args[4] 默认值为 null

结果:

程序会打印 names[2],但是 names[2]null,因此输出:

null

结论:

null

*16. 您希望一个类可以访问同一个包中另一个类的成员。 实现这一目标的最严格的访问是什么?

A.public

B. private

C. protected

D. default access

在Java中,类的成员(如字段、方法等)的访问权限通过访问修饰符进行控制。问题要求你选择最严格的访问权限,以便一个类能够访问同一个包中的另一个类的成员。

我们逐一分析选项:

A. public

  • public 访问修饰符表示成员可以被任何其他类访问,不受包或继承结构的限制。
  • 这意味着一个类可以从任何包中访问该成员,因此这不是最严格的访问权限。

B. private

  • private 访问修饰符表示成员只能在其所属类内部访问,其他类(即使在同一个包中)也无法访问。
  • 因此,private 不是一个适合在同一个包中访问的权限。它是最严格的访问控制,但不符合问题中的需求。

C. protected

  • protected 访问修饰符意味着成员可以被同一个包中的类以及所有继承该类的子类访问。
  • 但对于同一个包中的类,protected 的权限并不严格,因此它不是最严格的访问修饰符。

D. default access (包访问权限)

  • 当没有显式指定访问修饰符时,Java 中的成员具有 默认访问权限,即“包访问权限”(也称为 包级私有)。
  • 这意味着成员只能被同一个包中的其他类访问,不能被包外的类访问,也不能被子类在不同包中访问。
  • 这是一种相对严格的访问权限,因为它限制了类成员的访问范围,但仍允许同一包中的其他类访问。

正确答案:

最严格的访问控制是 private,但问题要求的是“同一个包中”能够访问成员。为了能够在同一个包中访问而不暴露给其他包,最严格的控制是 默认访问权限(即没有指定访问修饰符)。

因此,最严格的访问权限,同时允许同一个包内的其他类访问,是 D. default access


17. 程序的输出是?

public class Test107 implements Runnable

{

    private int x;

    private int y;

    public static void main(String args[])

    {

        Test107 that = new Test107();

        (new Thread(that)).start();

        (new Thread(that)).start();

    }

    public synchronized void run()

    {

        for(int i = 0; i < 10; i++)

        {

            x++;

            y++;

            System.out.println("x = " + x + ", y = " + y); /* Line 17 */

        }

    }

} 



A.编译错误

B. 以如下顺序打印: x = 1 y = 1 x = 2 y = 2 x = 3 y = 3 x = 4 y = 4 x = 5 y = 5... 但输出将由同时运行的两个线程产生.

C. 以如下顺序打印: x = 1 y = 1 x = 2 y = 2 x = 3 y = 3 x = 4 y = 4 x = 5 y = 5... 但输出将由第一个线程产生,然后由另一个线程产生。 这是由同步代码保证的。

D. 以如下顺序打印: x = 1 y = 2 x = 3 y = 4 x = 5 y = 6 x = 7 y = 8...

代码分析

我们来看这段代码:

public class Test107 implements Runnable {

    private int x;
    private int y;

    public static void main(String args[]) {
        Test107 that = new Test107();
        (new Thread(that)).start();  // 创建并启动第一个线程
        (new Thread(that)).start();  // 创建并启动第二个线程
    }

    public synchronized void run() {
        for (int i = 0; i < 10; i++) {
            x++;   // 增加 x 的值
            y++;   // 增加 y 的值
            System.out.println("x = " + x + ", y = " + y);  // 打印当前的 x 和 y
        }
    }
}

关键点:

  • Test107 实现了 Runnable 接口,并且 run() 方法是 synchronized 的。
  • main 方法中创建了两个线程,并分别启动它们。
  • 这两个线程共享同一个 Test107 实例,因此它们会同时执行 run() 方法中的代码。
  • 由于 run() 方法是 synchronized 的,只有一个线程可以在任何时刻进入 run() 方法,另一个线程必须等待当前线程执行完毕后才能进入。

分析每个选项:

A. 编译错误
  • 代码不会编译出错,因为 Test107 正确实现了 Runnable 接口,且同步机制也正确使用。因此,选项 A 不是正确答案。
B. 以如下顺序打印: x = 1 y = 1 x = 2 y = 2 x = 3 y = 3 x = 4 y = 4 x = 5 y = 5… 但输出将由同时运行的两个线程产生。
  • 这个描述不完全准确。虽然 run() 方法在多个线程中执行,但由于 run()synchronized 的,所以 两个线程不能同时执行 run() 方法。它们会交替执行,且每次只有一个线程可以打印 xy 的值。因此,输出将会由一个线程按顺序打印,另一个线程在第一个线程执行完当前循环的迭代后才会执行。
C. 以如下顺序打印: x = 1 y = 1 x = 2 y = 2 x = 3 y = 3 x = 4 y = 4 x = 5 y = 5… 但输出将由第一个线程产生,然后由另一个线程产生。这是由同步代码保证的。
  • 这个选项描述了同步的行为是正确的。由于 run() 方法是同步的,两个线程会交替执行每个循环,而每个线程对 xy 的增加都是互斥的。
  • 因此,输出将由一个线程先连续打印出 10 次 x = n, y = n,然后第二个线程打印接下来的 10 次输出。最终的输出顺序将是由两个线程交替产生的。
D. 以如下顺序打印: x = 1 y = 2 x = 3 y = 4 x = 5 y = 6 x = 7 y = 8…
  • 这个选项是不正确的。由于每个线程每次都递增 xy,它们会同步递增,而不是 xy 交替递增。因此,xy 的值会是相等的,而不是交替的。

结论

正确的答案是 C

因为 run() 方法是 synchronized 的,两个线程会交替执行,并且每次执行时都会按顺序打印出 xy 的递增值,而不会发生并发问题。

在Java中,synchronized 是一个关键字,用于实现 同步(synchronization)。它用于控制对共享资源的访问,以确保多个线程在执行某段代码时,能够按照一定的顺序进行,从而避免并发问题。

1. synchronized 的作用

当一个方法或代码块被声明为 synchronized 时,Java 会确保 同一时刻只有一个线程 能够执行这段同步代码。其他线程必须等待当前线程执行完毕才能进入同步方法或同步代码块。

这对于防止多个线程同时访问并修改共享资源(比如变量、数据结构等)时引发的数据不一致或竞争条件(race condition)非常重要。

2. 如何使用 synchronized

a. 同步方法

synchronized 可以用于方法的声明,表示该方法是同步的。

public synchronized void exampleMethod() {
    // 只有一个线程可以在任何时刻执行这个方法
    x++;  // 访问共享资源
    y++;
    System.out.println(x + ", " + y);
}

在这种情况下,同一时刻只能有一个线程 进入 exampleMethod() 方法。其他线程在方法执行完之前,必须等待。

b. 同步代码块

synchronized 还可以用于方法内部的代码块。通过指定一个对象锁,线程在执行同步代码块时,必须首先获得该对象的锁。

public void exampleMethod() {
    synchronized(this) {
        // 只有一个线程可以在任何时刻执行这个代码块
        x++;
        y++;
        System.out.println(x + ", " + y);
    }
}

在这个例子中,只有获得 this 对象锁的线程才能执行 synchronized 代码块。如果多个线程同时调用该方法,它们必须争夺 this 对象的锁。

c. 静态同步方法

synchronized 还可以用于类的静态方法,表示该方法在类级别上进行同步。

public static synchronized void exampleStaticMethod() {
    // 同样,只有一个线程可以执行这个静态同步方法
}

静态同步方法使用 类对象(而不是实例对象)作为锁。

3. 为什么使用 synchronized

Java中的多线程程序常常需要访问共享资源。如果多个线程同时访问共享资源且没有适当的同步机制,就可能会发生并发问题,比如:

  • 数据不一致:多个线程同时修改共享变量,导致无法预测的结果。
  • 竞态条件(Race Condition):两个线程竞争某些资源,导致程序表现出不稳定的行为。

synchronized 通过使得同一时刻只有一个线程能执行关键代码,从而避免这些并发问题。

4. synchronized 的工作原理

当多个线程试图执行一个同步方法时,锁机制会确保同一时刻只有一个线程可以进入该方法或代码块。锁可以是:

  • 实例锁:针对对象实例(this),通常是方法中的同步块或非静态方法。
  • 类锁:针对类本身,通常是静态方法。
举例说明:

假设有两个线程 T1T2,它们同时调用一个同步方法。由于同步方法的锁机制,T1 会先获取锁并执行该方法,而 T2 必须等到 T1 执行完后才能进入该方法。

5. 性能考虑

虽然 synchronized 关键字能有效解决线程同步问题,但它也带来了一些性能开销。因为:

  • 获取和释放锁需要时间。
  • 当多个线程频繁访问同步方法时,会出现线程竞争,可能导致线程的阻塞和上下文切换,影响性能。

6. 小结

  • synchronized 是一种用于实现线程同步的机制,确保在多线程环境下对共享资源的访问是安全的。
  • 它可以用在方法和代码块上。
  • 使用 synchronized 会引入一定的性能开销,因此需要小心使用,尤其是在高并发场景中。

下列说法正确的是

A.有时明确抛出 AssertionError 是一种很好的做法。

B. 私有 getter() 和 setter() 方法不应使用断言来验证参数。

C. 如果在 try-catch 块中抛出 AssertionError,finally 块将被绕过。

D. 使用 catch (AssertionException ae) 块处理断言语句失败是正确的

我们来看一下每个选项的分析,并解释正确与否:

A. 有时明确抛出 AssertionError 是一种很好的做法。

  • 错误。一般来说,断言(assertion) 用于开发阶段进行内部错误检查,确认程序的假设条件,而不是用作正常的错误处理机制。AssertionError 通常是在程序不符合预期时自动抛出的,而不应该主动抛出。主动抛出 AssertionError 会混淆代码的意图和错误的处理方式,可能会让代码不易理解和维护。

    正确做法

    • assert 语句本身用于断言条件,不应该通过代码显式抛出 AssertionError。如果需要进行错误处理,应使用适当的异常(如 IllegalArgumentException 等)。

B. 私有 getter() 和 setter() 方法不应使用断言来验证参数。

  • 正确getter()setter() 方法通常是公开访问对象状态的方式,而断言的目的是用于验证假设条件,通常是在开发过程中用于调试和检测内部错误。对于公共方法的参数验证,应该使用异常处理来进行,而不是使用断言。
    • 断言 在生产环境中默认是关闭的,所以依赖断言进行参数验证会导致程序行为不可预测。应该使用如 IllegalArgumentException 等标准异常来验证方法参数。

C. 如果在 try-catch 块中抛出 AssertionError,finally 块将被绕过。

  • 错误。无论 try 块中的代码抛出什么异常,finally 块总是会被执行。finally 块无论是否发生异常都会执行,除非程序在执行期间被终止(如调用 System.exit() 或发生严重错误导致JVM崩溃)。

    • 所以,即使在 try-catch 块中抛出 AssertionErrorfinally 仍然会执行。finally 块的主要目的是释放资源等清理工作。

D. 使用 catch (AssertionException ae) 块处理断言语句失败是正确的

  • 错误。Java 中并没有 AssertionException 这个异常类。assert 语句失败时会抛出一个 AssertionError,而不是 AssertionException。因此,应该使用 catch (AssertionError ae) 来捕获断言失败抛出的错误,而不是 AssertionException

    另外,断言失败的目的是标识程序中的逻辑错误,通常不应该通过 try-catch 块来捕获它。一般情况下,AssertionError 应该是由程序员在调试时用来检查假设错误的,正常的错误处理应该使用合适的异常类型。

总结:

  • A. 错误:不应显式抛出 AssertionError
  • B. 正确:不应使用断言来验证 gettersetter 方法的参数。
  • C. 错误finally 块无论如何都会执行。
  • D. 错误:应该捕获 AssertionError,而不是 AssertionException

正确的答案是:B


下列说法正确的是

A.调用 Runtime.gc() 将导致符合条件的对象被垃圾回收。

B.垃圾收集器使用标记和清除算法。

C. 如果一个对象可以从一个活动线程中访问,它就不能被垃圾回收。

D. 如果对象 1 引用对象 2,则对象 2 不能被垃圾回收

让我们分析每个选项,找出正确的说法。

A. 调用 Runtime.gc() 将导致符合条件的对象被垃圾回收。

  • 错误Runtime.gc() 提示 JVM 进行垃圾回收,但它并不是强制性的,并不能保证垃圾回收一定会发生。调用 gc() 方法只是给垃圾收集器发出了建议,实际上,是否执行垃圾回收、何时执行垃圾回收完全取决于垃圾收集器的实现和当前的内存状况。垃圾回收的决定是由 JVM 根据内存压力来做的,而不是单纯由 gc() 方法的调用决定

B. 垃圾收集器使用标记和清除算法。

  • 正确标记-清除(Mark-and-Sweep)是垃圾回收的一种经典算法。其过程通常如下:

    1. 标记阶段:首先标记所有活动对象,这些对象是可达的(即从根对象可访问的对象)。
    2. 清除阶段:在标记之后,垃圾收集器会回收那些没有被标记的对象,即不可达的对象。

    这种算法的优点是简单易懂,但也有一些缺点,比如会导致内存碎片问题。现代的垃圾收集器(如 G1、ZGC 等)通常在标记-清除的基础上进行了优化。

C. 如果一个对象可以从一个活动线程中访问,它就不能被垃圾回收。

  • 正确。如果一个对象是可达的(即可以通过活动线程或者其他任何途径访问到该对象),那么它不会被垃圾回收器回收。垃圾回收的主要依据是对象是否可达(reachable)。如果对象不再被任何活动线程或其他对象引用,那么它就变成了不可达对象,有可能会被垃圾回收。

D. 如果对象 1 引用对象 2,则对象 2 不能被垃圾回收。

  • 正确。如果对象 1 引用了对象 2,这意味着对象 2 是可达的。因此,对象 2 在垃圾回收时不会被回收,直到对象 1 不再引用对象 2 为止。只有那些没有任何引用(即不可达)的对象,才会被垃圾回收。

总结:

  • A. 错误Runtime.gc() 只是一个建议,不保证垃圾回收会发生。
  • B. 正确:垃圾收集器使用标记和清除算法。
  • C. 正确:如果对象可以从活动线程中访问,它就不能被垃圾回收。
  • D. 正确:如果对象 1 引用对象 2,对象 2 就不会被垃圾回收。

正确的答案是:B, C, D


20. public class Test
{

    public void foo()

    {

        assert false; /* Line 5 */

        assert false; /* Line 6 */

    }

    public void bar()

    {

        while(true)

        {

            assert false; /* Line 12 */

        }

        assert false; /* Line 14 */

    }

}

哪一个会导致编译失败


A.Line 5

B. Line 6

C. Line 12

D. Line 14

让我们仔细分析代码中涉及断言(assert)的部分。

public class Test
{
    public void foo()
    {
        assert false; /* Line 5 */
        assert false; /* Line 6 */
    }

    public void bar()
    {
        while(true)
        {
            assert false; /* Line 12 */
        }
        assert false; /* Line 14 */
    }
}

断言(assert)的工作原理

在 Java 中,断言是通过 assert 语句实现的,通常用于在开发过程中检查程序的假设条件。断言默认在运行时是禁用的,只有通过 -ea-enableassertions)选项显式启用时,断言才会执行。如果没有启用断言,assert 语句将不会影响程序的行为。

编译阶段

  • assert 语句本身并不会导致编译错误。编译器会接受包含 assert 语句的代码,只要没有语法错误。

运行时

  • 只有在运行时启用断言时,assert 语句才会生效。否则,它们会被忽略。

逐行分析

  1. Line 5 和 Line 6 (assert false;)

    • 这些是标准的断言语句,不会导致编译错误。无论是启用断言还是禁用断言,都会被编译器接受。
  2. Line 12 (assert false;)

    • while(true) 循环内部,assert false 不会导致编译失败。assert 语句本身是有效的。循环体内部的断言会在循环中每次执行时被执行,但它不会导致编译错误。
  3. Line 14 (assert false;)

    • 这一行是位于 while(true) 循环外部的断言。这不会导致编译错误,除非有语法问题或者错误的使用方式。但是在 Java 中,assert 语句本身并不会因其位置或上下文而导致编译失败。

结论

没有一行代码会导致编译错误。Java 编译器会接受所有的 assert 语句。关键在于是否启用了断言(-ea),但即使不启用,assert 语句本身也不会阻止程序编译。

正确答案是: 没有选项会导致编译失败


  1. 请用java实现一个函数。输入是一个数组,数组的元素都是数字,里面的数字有重复出现的。函数返回一个新的数组,是对输入数组去重的结果??????
package org.example;

import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;

public class RemoveDuplicates {

    public static int[] removeDuplicates(int[] inputArray) {
        // 创建一个 HashSet 来去重
        Set<Integer> uniqueSet = new HashSet<>();

        // 将数组元素添加到 HashSet 中,自动去除重复元素
        for (int num : inputArray) {
            uniqueSet.add(num);
        }

        // 将 Set 转换为新的数组
        int[] resultArray = new int[uniqueSet.size()];
        int index = 0;
        for (int num : uniqueSet) {
            resultArray[index++] = num;
        }

        // 返回去重后的数组
        return resultArray;
    }

    public static void main(String[] args) {
        // 示例输入
//        int[] inputArray = {1, 2, 2, 3, 4, 4, 5, 5, 5};
        Scanner scanner  = new Scanner(System.in);
        System.out.println("请输入数组长度");
        int n  = scanner.nextInt();
        int[] inputArray = new int[n];
        System.out.println("请输入数组元素");
        for (int i = 0; i < n; i++) {
            inputArray[i] = scanner.nextInt();
        }

        // 调用去重函数
        int[] resultArray = removeDuplicates(inputArray);

        // 输出结果
        System.out.print("去重后的数组: ");
        for (int num : resultArray) {
            System.out.print(num + " ");
        }
    }
}


在 Java 中,线程的生命周期包括多个不同的状态。Java 线程的状态是通过 Thread.State 枚举类来定义的。下面是 Java 线程的几种常见状态:

1. NEW (新建)

  • 线程刚创建,但还未开始执行时的状态。
  • 线程对象被创建之后,调用 start() 方法之前,线程处于此状态。
  • 例如:
    Thread thread = new Thread();
    System.out.println(thread.getState()); // 输出:NEW
    

2. RUNNABLE (可运行)

  • 线程已经启动,但还未开始执行,或者正在执行的状态。
  • 线程在操作系统的线程调度器中处于等待执行的队列中。当 CPU 分配时间片后,线程将变为 RUNNABLE 状态。
  • 注意:Java 中没有明确的 Running 状态,只有 RUNNABLE 状态。即使线程正在运行,它仍然处于 RUNNABLE 状态。
  • 例如,线程调用 start() 后进入 RUNNABLE 状态。
    Thread thread = new Thread(() -> System.out.println("Running..."));
    thread.start(); // 线程进入 RUNNABLE 状态
    

3. BLOCKED (阻塞)

  • 线程因尝试获取一个对象的锁而被阻塞,直到能够成功获取锁。
  • 当一个线程想访问同步方法或同步块时,如果其他线程已经持有锁,当前线程将被阻塞,直到锁可用为止。
  • 例如:
    synchronized (lock) {
        // 当前线程如果无法获得 lock 锁,就会进入 BLOCKED 状态
    }
    

4. WAITING (等待)

  • 线程正在等待其他线程执行特定操作,通常是调用 Object.wait() 方法后进入此状态,或者调用 Thread.join() 等方法等待其他线程完成。
  • 线程进入 WAITING 状态后,必须等待其他线程通过 notify()notifyAll() 方法唤醒它。
  • 例如:
    synchronized (lock) {
        lock.wait(); // 当前线程进入 WAITING 状态
    }
    

5. TIMED_WAITING (定时等待)

  • 线程在一个固定的时间段内等待。线程调用 Thread.sleep(milliseconds)Object.wait(time)Thread.join(time) 等方法时进入此状态。
  • 线程会等待指定的时间,超时后自动回到 RUNNABLE 状态。
  • 例如:
    Thread.sleep(1000); // 线程进入 TIMED_WAITING 状态,等待 1000 毫秒
    

6. TERMINATED (已终止)

  • 线程执行完毕或由于异常终止时的状态。
  • 当线程的 run() 方法执行完毕或线程因未捕获的异常退出时,它进入 TERMINATED 状态。
  • 线程不能再返回到其他状态,已经结束生命周期。
  • 例如:
    Thread thread = new Thread(() -> {
        // 执行完毕
    });
    thread.start();
    // 等待线程终止
    thread.join(); // 线程进入 TERMINATED 状态
    

总结:线程状态的转换

  • NEWstart()RUNNABLE(进入就绪队列等待 CPU 分配)
  • RUNNABLEsynchronizedjoin() 等 → BLOCKED
  • RUNNABLEwait()WAITING
  • RUNNABLEsleep()join(time) 等 → TIMED_WAITING
  • RUNNABLE → 执行完毕或因异常退出 → TERMINATED

状态图

    NEW
     |
     v
  RUNNABLE <-- BLOCKED
     |          ^
     v          |
  WAITING <---- TIMED_WAITING
     |
     v
 TERMINATED

这些是 Java 线程生命周期中的主要状态,它们由 JVM 和操作系统的线程调度机制共同管理。


http://www.kler.cn/a/392899.html

相关文章:

  • 前端框架大比拼:React.js, Vue.js 及 Angular 的优势与适用场景探讨
  • 什么是数字图像?
  • 94个属于一区且接受医工交叉领域投稿的期刊汇总|个人观点·24-11-13
  • webpack loader全解析,从入门到精通(10)
  • AI绘画经验(stable-diffusion)
  • 彻底理解ARXML中的PDU
  • C/C++内存管理 | new的机制 | 重载自己的operator new
  • Mysql:使用binlog的一些常用技巧
  • TreeSet是什么
  • 分享一些Kafka集群优化的最佳实践?
  • DeepSpeed:PyTorch优化库,使模型分布式训练能高效使用内存和更快速
  • flink+kafka 如何保证精准一次
  • Java 中的字符输入流详解
  • IOS开发之AR问题汇总
  • web安全漏洞之命令注入
  • 035集——BOUNDARY获取图形外轮廓(CAD—C#二次开发入门)
  • 从五种架构风格推导出HTTP的REST架构
  • 单片机工程师面试常见问题解析
  • 一、机器学习算法与实践_07支持向量机与集成学习算法笔记
  • 【启明智显分享】5G CPE与5G路由器到底有什么区别?
  • 相机光学(四十二)——sony的HDR技术
  • 微型导轨在自动化生产线中起什么作用?
  • 【Windows】CMD命令学习——系统命令
  • 将单色像素值转换成灰阶屏的灰度序列的算法
  • 深度学习神经网络创新点方向
  • 揭开基础动销方案的神秘面纱