2023应届生能力考试含解析(Java后端开发)

1.以下代码的循环次数是 (   )

public class Test {
    public static void main(String[] args) {
        int i = 7;
        do {
            System.out.println(--i);
            --i;
        } while (i != 0);
        System.out.println(i);
    }
}

A 0    B 1   C 7    D 无限次

    这段代码会导致无限循环的原因是在 do-while 循环中,每次迭代都会先执行一次循环体内的代码,然后再进行循环条件的判断。

    在这个例子中,初始值 i 被设置为 7。循环体内的代码逐步减少 i 的值,并打印出来。但是注意到每次循环体内都有两次 --i 操作,所以每次循环 i 的值会减少2。

    当 i 的值减少到1时,继续进行下一次循环。此时 i 的值变为 -1,不等于0,循环继续。然后又会进行两次 --i 操作,使 i 的值变为 -3。这个过程会一直继续下去,永远不会达到循环结束的条件 i != 0。因此,这段代码会导致无限循环。

2.下面代码的运行结果为:(  )

package itemtest;

import java.io.*;
import java.util.*;

public class Test{
    public static void main (String[] args){
        String s;
        System.out.println("s=" + s);
    }
}

A代码得到编译,并输出“s=”

B代码得到编译,并输出“s=null”

C由于String s没有初始化,代码不能编译通过

D代码得到编译,但捕获到 NullPointException异常

 

3.以下程序的输出是:( ) 

#include <iostream>

using namespace std;

unsigned int f(unsigned int n) {
    if (n == 0 || n == 1) {
        return 1;
    }
    return f(n-1) + f(n-2);
}

void count(int n) {
    unsigned int tmp = n - ((n >> 1) & 0x33333333) - ((n >> 2) & 0x11111111);
    std::cout << ((tmp + (tmp >> 3)) & 0x07070707) % 63 << std::endl;
}

int main() {
    count(f(7));
    count(f(9));
    return 0;
}

改写成Java代码后为:

public class Test{
    public static int f(int n) {
        if (n == 0 || n == 1) {
            return 1;
        }
        return f(n-1) + f(n-2);
    }
    public static void count(int n) {
        int tmp = n - ((n >> 1) & 0x33333333) - ((n >> 2) & 0x11111111);
        System.out.println(((tmp + (tmp >> 3)) & 0x07070707) % 63);
    }
    public static void main(String[] args) {
        count(f(7));
        count(f(9));
    }
}

 

其中对于这段代码的理解如下:

    public static void count(int n) {
        int tmp = n - ((n >> 1) & 0x33333333) - ((n >> 2) & 0x11111111);
        System.out.println(((tmp + (tmp >> 3)) & 0x07070707) % 63);
    }
这段代码实现了一种高效计算一个整数的二进制表示中有多少个1的方法,即计算整数的“汉明重量”(Hamming Weight)。下面逐步解释这段代码的实现过程:

首先,这段代码接受一个整数 n 作为输入。然后,代码通过位运算来分割 n 的二进制表示。具体来说,它使用了一些掩码(mask)来提取不同位置上的比特位。

0x33333333 掩码用于提取每两位的比特位。
0x11111111 掩码用于提取每四位的比特位。
通过右移操作 (n >> k) 和按位与操作 &,可以将 n 的比特位分割成更小的块。

接下来,通过减法和加法操作,将这些被分割出来的比特位进行累加运算。

(n >> 1) & 0x33333333 表示将 n 的每两位的比特位相加,并将结果存储在 tmp 中。
(n >> 2) & 0x11111111 表示将 n 的每四位的比特位相加,并将结果与 tmp 相减。
这样,tmp 的值就是 n 的二进制表示中每两位和每四位的比特位相加的结果。

最后,通过右移操作 (tmp >> 3) 和按位与操作 &,提取每八位的比特位。

(tmp + (tmp >> 3)) & 0x07070707 表示将 tmp 的每八位的比特位相加,并将结果存储在 tmp 中。
最终结果是 tmp 对 63 取模的值,即 tmp % 63。

0x07070707 掩码用于保留 tmp 的每八位的比特位。
通过以上步骤,这段代码实现了高效计算一个整数二进制表示中1的个数的功能。

请注意,这段代码假设整数为32位。如果你要处理不同位数的整数,需要相应调整掩码的值和最终取模的除数。
在 count 函数中,将传入的数 n 进行一系列位运算操作后得到了一个数字 tmp。此外,在最后输出之前,将 tmp 与 0x07070707 进行按位与操作,并再次取模 63,最终得到的是 tmp 的一个压缩版本。

    实际上,这个压缩版本已经足够表征 tmp 的大部分信息了。由于斐波那契数列的增长速度非常快,当 n 较大时,tmp 的值也会变得非常大。因此,通过将 tmp 与 0x07070707 进行按位与操作,可以将其压缩为一个更小的数字。然后,通过对 63 取模,可以确保输出值始终落在 0 到 62 之间,以便更好地控制输出结果的范围。

    需要注意的是,0x07070707 所对应的二进制数是 00000111000001110000011100000111,它实际上就是用四个重复的 00000111 来组成,而 00000111 对应的十进制数为 7。因此,0x07070707 等价于十进制数 119304647。根据这个数字,我们可以使用 & 和取模操作来实现 tmp 的压缩和范围限制。

4.执行下列程序的输出结果为()

public class Test {
    public static void main(String[] args) {
        String s1 = "HelloWorld";
        String s2 = new String("HelloWorld");
        if (s1 == s2) {
            System.out.println("s1 == s2");
        } else {
            System.out.println("s1 != s2");
        }
        if (s1.equals(s2)) {
            System.out.println("s1 equals s2");
        } else {
            System.out.println("s1 not equals s2");
        }
    }
}

    在 Java 中,== 运算符用于比较两个对象的引用是否相等,即它们是否指向同一个内存地址。而 equals() 方法则用于比较两个对象的内容是否相等。

    在这个例子中,s1 是一个字符串常量(String literal),在编译时已经被赋值,并且在 JVM 内部建立了对应的 String 对象。而 s2 是通过 new 关键字创建的一个新的 String 对象,它的值也是 "HelloWorld"。虽然这两个 String 对象的内容相同,但是它们的引用并不相同,因此 s1 == s2 的结果为 false。

    另一方面,equals() 方法比较的是两个对象的内容,它会调用 String 类的 equals() 方法来比较两个字符串的字符序列是否相等。由于 s1 和 s2 的字符序列都是 "HelloWorld",因此 s1.equals(s2) 的结果为 true。

5.下列类定义代码,当用来声明对象car,并用Car car=new Car();实例化后,可以通过car对象直接赋值的字段是()

public class Car {
    public String type;
    String No;
    private int heavy;
    double speed;
    protected String owner;
    public String price;
    private String color;
}

A type,No   B type,price   C heavy,owner    D type,owner,price

通过 new Car() 实例化后,可以通过 car 对象直接赋值的字段是:type、owner 和 price。

   这是因为这三个字段都是定义为 public 访问修饰符,可以通过对象的引用直接访问和修改。而其他字段的访问修饰符是 private 或 protected,无法直接通过对象的引用访问和修改,需要通过类提供的公共方法或者反射等方式才能访问。  具体举例说明如下:
public class Car {
    public String type;
    String No;
    private int heavy;
    double speed;
    protected String owner;
    public String price;
    private String color;

    public void setHeavy(int heavy) {
        this.heavy = heavy;
    }

    public void setSpeed(double speed) {
        this.speed = speed;
    }

    public void setColor(String color) {
        this.color = color;
    }
}
    通过以上的 Car 类定义,我们可以实例化一个 Car 对象并进行字段赋值。假设我们创建了一个 Car 对象 car:   Car car = new Car();
        我们可以直接使用 car 对象来赋值 type、owner 和 price 字段:
        car.type = "Sedan";
        car.owner = "John";
        car.price = "$20000";
     这些字段都是 public 访问修饰符的,因此可以直接通过对象的引用 car 进行赋值操作。

     但是对于其他字段,由于它们的访问修饰符是 private 或 protected,我们无法直接通过对象的引用进行赋值。例如,无法直接通过 car.heavy、car.speed 或 car.color 来赋值。

     如果我们想要修改这些 private 或 protected 字段的值,我们可以为类提供公共的方法(setter 方法)来间接修改它们。例如,为 heavy、speed 和 color 字段添加相应的 setter 方法:
public void setHeavy(int heavy) {
        this.heavy = heavy;
}

public void setSpeed(double speed) {
        this.speed = speed;
}

public void setColor(String color) {
        this.color = color;
}
然后,我们可以使用这些 setter 方法来修改字段的值:
car.setHeavy(2000);
car.setSpeed(120.5);
car.setColor("Blue");
通过这样的方式,我们可以间接修改 private 或 protected 字段的值。

6.以下程序的执行结果是:( )

package itemtest;

public class Test {
    static boolean foo(char c) {
        System.out.print(c);
        return true;
    }

    public static void main(String[] args) {
        int i = 0;
        for (foo('A'); foo('B') && (i < 2); foo('C')) {
            i++;
            foo('D');
        }
    }
}

A、ABDCBDCB   B、ABCDABCD

C、编译时出错     D、运行时抛出异常

在这个代码中,主要有三部分组成:

foo(char c): 这是一个静态方法,它接受一个字符参数 c,并打印该字符并返回 true。

main 方法:这是程序的入口点。它声明了一个整数变量 i,初始值为 0。

循环:使用 for 循环进行迭代。循环的初始化部分调用 foo('A') 方法,并打印字符 'A'。然后,在循环条件部分,调用 foo('B') 方法并打印字符 'B',并检查 i 是否小于 2。如果条件为真,则进入循环体部分。在循环体内, i 的值增加 1,并调用 foo('D') 方法并打印字符 'D'。然后返回到循环条件部分。在循环条件部分调用 foo('C') 方法并打印字符 'C'。如果条件为真,则继续下一次循环。否则,退出循环。

因此,循环将执行两次,输出结果为 "ABDCBDCB"。

7.list是一个ArrayList的对象,哪个选项的代码填到//todo delete处,可以在Iterator遍历的过程中正确并安全的删除一个list中保存的对象?()

Iterator it = list.iterator();
while (it.hasNext()) {
    Object obj = it.next();
    if (needDelete(obj)) {
        it.remove(); // 使用it.remove()进行删除
    }
}

8.执行如下程序,输出结果是( )

    class Test {
        private int data;
        int result = 0;

        public void m() {
            result += 2;
            data += 2;
            System.out.print(result + "  " + data);
        }
    }

    class ThreadExample extends Thread {
        private Test mv;

        public ThreadExample(Test mv) {
            this.mv = mv;
        }

        public void run() {
            synchronized (mv) {
                mv.m();
            }
        }
    }

    class ThreadTest {
        public static void main(String args[]) {
            Test mv = new Test();
            Thread t1 = new ThreadExample(mv);
            Thread t2 = new ThreadExample(mv);
            Thread t3 = new ThreadExample(mv);
            t1.start();
            t2.start();
            t3.start();
        }
    }

9.有必修课成绩表course,每位学生期末考试成绩以及补考成绩都录入到course表中,学号为20190001的同学想查询一下自己未通过课程的课程编号与课程名称,下面正确sql语句是() 

select distinct cid, cname
from course
where cid not in (select cid from course where score > 60) and sid = 20190001


select distinct cid,cname from course where cid not in (select cid from course where score > 60) and sid=20190001

在主查询中,我们使用distinct关键字去重,选择cidcname作为结果。同时,使用子查询select cid from course where score > 60来筛选出成绩大于60的课程编号。然后将该子查询的结果排除在主查询中,并添加条件sid = 20190001来限定学号为20190001的同学。

10.属于同一进程的两个线程 T1和 T2并发执行,共享初值为 0 的全局变量 X。T1和 T2实现对全局变量 x 加 1 的伪代码分别如下:

T1: 
temp1=X; 
temp1=temp1+1; 
X=temp1; 
T2: 
temp2=X; 
temp2=temp2+1; 
X=temp2;

2个线程进行到任意一步都能被对方打断,执行另外一个线程的代码,请问在所有可能的执行序列中,使 x 的值为 2 的序列个数有几种?()

A、1 B、2 C、3 D、4 E、5

在给定的伪代码中,有两个线程T1和T2并发执行,共享变量X的初值为0。每个线程都会执行三个步骤:读取X的值、对临时变量递增1、将递增后的值写回X。现在我们要确定所有可能的执行序列中,使X的值为2的序列个数。考虑两个线程的执行序列:

  1. T1读取X的值,得到0。

  2. T2读取X的值,得到0。

  3. T1对临时变量temp1递增1,temp1变为1。

  4. T2对临时变量temp2递增1,temp2变为1。

  5. T1将temp1的值写回X,X变为1。

  6. T2将temp2的值写回X,X变为1。该序列不满足要求,因为最终X的值为1,而不是2。

另一个执行序列是:

  1. T1读取X的值,得到0。

  2. T1对临时变量temp1递增1,temp1变为1。

  3. T1将temp1的值写回X,X变为1。

  4. T2读取X的值,得到1。

  5. T2对临时变量temp2递增1,temp2变为2。

  6. T2将temp2的值写回X,X变为2。该序列满足要求,因为最终X的值为2。

综上所述,在所有可能的执行序列中,使X的值为2的序列个数为1,即选项A:1

 11.以下类定义中的错误是什么?()

abstract class xy {
    abstract sum (int x, int y) { }
}

A没有错误                      B类标题未正确定义

C方法没有正确定义        D没有定义构造函数

在给定的类定义中,有一个xy抽象类,但是其中的sum方法没有正确定义。在Java中,方法的定义应该包含返回类型、方法名称和参数列表,并且需要以分号结束。因此,正确的定义应该是:

abstract class xy {
    abstract int sum(int x, int y);
}

在修复后的定义中,sum方法包含了返回类型int、方法名称sum和两个整型参数xy,并且以分号结束。所以,选项C是错误的。

12、在Java Web应用程序中,要完成将用户会话中的”counter”计数器的值增加1,下列( )是正确的Servlet代码片段。

    A、HttpSession session = request.getSession(true);
       int ival = session.getAttribute(“counter”);
       if(ival==null){
        ival = 1;
       }else{
        Ival = ival + 1;
        session.setAttribute(“counter”, ival);
      }

    B、HttpSession session = request.getSession(true);
       Integer ival = (Integer) session.getAttribute(“counter”);
       session.setAttribute(“counter”,ival+1);
    
    C、HttpSession session = request.getAttribute(“counter”);
       if(ival==null){
        ival = new Integer(1);
       }else{
        ival = new Integer(ival.intValue() + 1);
       }
       session.setAttribute(“counter”,ival);
    
    D、HttpSession session = request.getSession();
       Integer ival = (Integer) session.getAttribute(“counter”);
       If(ival==null) {
        ival = 1;
       }else{
        ival = ival + 1;
       }
       session.setAttribute(“counter”,newInteger(ival));

        在给定的选项中,只有选项B中包含了正确的逻辑来增加会话中的”counter”计数器的值。其他选项存在一些语法错误或逻辑错误。

        在选项B中,首先获取会话对象,并使用getSession(true)来创建新会话(如果会话不存在)。然后从会话中获取名为"counter"的属性,并将其转换为整型Integer。接下来,根据获取到的值进行判空处理,如果为空则初始化为1,否则将其自增1。最后,将更新后的值重新设置到会话属性"counter"中。

9 以下代码执行后,console.log 输出的信息是?

var a = 0; 
switch(++a) { 
    case 0: ++a; 
    case 1: ++a; 
    case 5: ++a; 
 } 
console.log(a);

        在这段代码中,首先执行 ++a,将 a 的值从 0 变为 1。然后进入 switch 语句,由于没有 break 语句,所以会顺序执行匹配的 case 和后续的 case。因为 ++a 的结果是 1,所以会依次执行 case 1:、case 5: 后面的代码,即将 a 的值分别增加一次,最终 a 的值变为 3。因此,代码执行后,console.log 输出的信息是:3

10 以下代码执行后,console.log 输出的信息是?

function fun(obj) { 
  if(obj === {x: "a"}) { 
    console.log(1) 
} else if(obj == {x: "a"}) { 
    console.log(2) 
 } else { 
    console.log(3) 
    } 
} 
fun({x: "a"});

 JavaScript 中的对象比较是基于引用的。即使两个对象具有相同的属性和值,它们在内存中的位置不同,因此严格相等比较(===)会返回 false。因此,当调用 fun({x: "a"}) 时,会执行 console.log(3),因为对象比较的结果是没有匹配的条件,所以会执行 else 分支的代码。因此,代码执行后,console.log 输出的信息是:3

由于前端直接可被用户访问,攻击者可以轻易得到页面和通讯过程中的相关信息,进而进行恶意的攻击,关于其攻击的方式描述正确的有哪些?()

A.假定站点 foo.com 的服务器架设在公司内网,提供了任意站点截图服务 foo.com/screenshot?url=xxx,恶意修改 url 中的值为内网地址,构成 SSRF 攻击,进而造成数据泄露的风险。

B.网站 foo.com 提供 POST 方法的 /tansfer/to/xxx 的转账服务,由于未做 CSRF 的防范,被攻击者重复伪造该请求,形成重放攻击,造成经济损失。

C.网站 foo.com 使用了非安全的 HTTP 协议,其中转账服务 POST /transfer/to/xxx,转账金额 money 在 payload 上,假定接口层面采用了随机 token 来防范 csrf 攻击,接口参数未做签名校验,此时攻击者通过篡改 money 的数值,构成中间人攻击。

D.借助社会工程学的理论基础,基于用户的贪婪等心理,制作一个 qq.com 的”冒牌”中奖页面,诱导用户输入账号密码进行登录,造成隐私的泄露,属于”钓鱼”的网络欺诈行为。

猜你喜欢

转载自blog.csdn.net/weixin_49171365/article/details/134123916