• 21年-自研-笔试题


    背景

    21年4月,应聘杭州滨江某自研公司,以下是该公司的笔试题
    题目的答案,是根据我的理解写的。可能有不对的地方,欢迎大家指正。
    这一场的面试题忘了,当时没记录

    题目

    1、Object的常用方法

    ●1.getClass()

    public final native Class<?> getClass()
    
    • 1

    获取对象运行时的class对象,通常和反射机制搭配使用。
    class对象就是描述对象所属类的对象
    ●2.hashCode

    public native int hashCode()
    
    • 1

    获取对象的散列值。Object中该方法默认返回的是对象的堆内存地址。
    ●3.equals

    public boolean equals(Object object){
        return (this == obj);
    }
    
    • 1
    • 2
    • 3

    比较2个对象,如果2个对象引用的是同一个对象,那么返回true。
    一般equals和==是不一样的,但在Object中是一样的。子类一般重写equals方法
    ●4.clone

    protected native Object clone() throws CloneNotSupportedException;
    
    • 1

    该方法是保护方法,实现对象的浅拷贝,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportException。
    默认的clone是浅拷贝。浅拷贝,指的是对象内属性引用的对象只会拷贝引用地址,而不会将引用的对象重新分配内存。
    深拷贝就是会连引用的对象也重新创建。
    ●5.toString

    public String toString(){
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }
    
    • 1
    • 2
    • 3

    返回一个String对象,一般子类都有覆盖。
    默认返回格式:对象的class名称 + @ + hashCode的十六进制字符串。
    ●6.notify

     public final native void notify()
    
    • 1

    final方法,主要唤醒在该对象上等待的某个线程
    ●7.notifyAll

     public final native void notifyAll()
    
    • 1

    final方法,唤醒在该对象上等待的所有线程
    ●8.wait(long timeout)

     public final native void wait(long timeout) throws InterruptedException
    
    • 1

    timeout是毫秒值
    使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。
    这里设置一个超时间隔,如果在规定时间没有获得到锁就返回。
    无参的wait() 方法会一直等待,直到获得锁或者被中断。
    ●9.wait(long timeout, int nanos)

     public final void wait(long timeout, int nanos) throws InterruptedException{
        if (timeout < 0) {
        throw new IllegalArgumentException("timeout value is negative");
      }
     
      if (nanos < 0 || nanos > 999999) {
        throw new IllegalArgumentException(
                  "nanosecond timeout value out of range");
      }
     
      if (nanos >= 500000 || (nanos != 0 && timeout == 0)) {
        timeout++;
      }
     
      wait(timeout);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    timeout:最大等待时间,毫秒
    nanos:附加时间在毫秒范围(0-999999),纳秒/毫微秒,十亿分之一秒
    该方法导致当前线程等待,直到其他线程调用此对象的 notify() 方法或notifyAll()方法,或在指定已经过去的时间。此方法类似于 wait 方法的一个参数,但它允许更好地控制的时间等待一个通知放弃之前的量。实时量,以毫微秒计算,计算公式如下:1000000 * timeout + nanos
    在所有其他方面,这种方法与 wait(long timeout) 做同样的事情。特别是 wait(0, 0) 表示和 wait(0) 相同。
    ●10.wait()

     public final void wait() throws InterruptedException{
        wait(0);
    }
    
    • 1
    • 2
    • 3

    实际调用的是wait(long timeout)方法,只不过timeout是0。
    ●11.finalize

     protected void finalize() throws Throwable{}
    
    • 1

    是保护方法,用于在GC的时候再次被调用。如果实现了这个方法,对象可能在这个方法中再次复活,避免被GC回收。

    2、 == 和 equals 的区别是什么?

    ==

    对于基本类型和引用类型 == 的作用效果是不同的,如下所示:
    基本类型:比较的是值是否相同;
    引用类型:比较的是引用是否相同;
    代码示例:

    String x = "string";
    String y = "string";
    String z = new String("string");
    System.out.println(x==y); // true
    System.out.println(x==z); // false
    System.out.println(x.equals(y)); // true
    System.out.println(x.equals(z)); // true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    代码解读:
    因为 x 和 y 指向的是同一个引用,所以 == 也是 true。
    而 new String()方法则重写开辟了内存空间,所以 == 结果为 false,
    而 equals 比较的一直是值,所以结果都为 true。

    equals

    equals 本质上就是 ==,只不过 String 和 Integer 等重写了 equals 方法,把它变成了值比较。看下面的代码就明白了。
    首先来看默认情况下 equals 比较一个有相同值的对象,代码如下:

    String s1 = new String("老王");
    String s2 = new String("老王");
    System.out.println(s1.equals(s2)); // true
    
    • 1
    • 2
    • 3

    进入 String 的 equals 方法,找到了答案,代码如下:

    public boolean equals(Object anObject) {    
         if (this == anObject) {        
              return true;   
         }    
         if (anObject instanceof String) {        
              String anotherString = (String)anObject;        
              int n = value.length;        
              if (n == anotherString.value.length) {            
                   char v1[] = value;           
                   char v2[] = anotherString.value;            
                   int i = 0;            
                   while (n-- != 0) {                
                       if (v1[i] != v2[i])                    
                           return false;                
                       i++;            
                   }            
                   return true;        
                }    
          }    
          return false;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    可以看出, String 重写了 Object 的 equals 方法,把引用比较改成了值比较。
    总结 :
    == 对于基本类型来说是值比较,对于引用类型来说是比较的是引用;
    equals 默认情况下是引用比较,只是很多类重新了 equals 方法,比如 String、Integer 等把它变成了值比较,
    所以一般情况下 equals 比较的是值是否相等。

    3、以下代码的运行结果

    public class Stu {
    
    	private String name;
    	private int age;
    
    	public Stu(String name, int age) {
    		super();
    		this.name = name;
    		this.age = age;
    	}
     
       public static void main(String[] args) {
    		Stu s1 = new Stu("张三", 18);
    		Stu s2 = new Stu("张三", 18);
    		System.out.println(s1 == s2);// false
    		System.out.println(s1.equals(s2));// false
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    4、以下代码的运行结果

    String s0 = null;
    String s1 = "Tomcat";
    String s2 = "Tom" + "cat";
    String s3 = new String("Tomcat");
    System.out.println(s1==s2);//true
    System.out.println(s1.equals(s2));//true
    System.out.println(s1==s3);//false
    System.out.println(s1.equals(s3));//true
    //intern():返回常量池中这个对象的引用
    System.out.println(s1==s3.intern());//true
    System.out.println(s0==s1);//false
    System.out.println(s0.equals(s1));//空指针异常!
    //null去equals某个值,就会报错。如果非null去equals任意值,就不会错,如下所示
    System.out.println(s1.equals(s0));//false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    5、String, StringBuilder,StringBuffer

    String:定义的不可变字符串。在操作少量数据时使用。
    StringBuilder:定义的可变字符串。线程不安全。性能比StringBuffer快。推荐单线程下使用。
    StringBuffer:定义可变字符串。线程安全,因为它的所有公开方法都用synchronized修饰。推荐多线程下用。
    整体性能上:StringBuilder > StringBuffer > String
    为什么是这样的性能排序?
    String每执行一次+(重载运算符),就要创建一个新的对象
    StringBuffer比StringBuilder相比,少了同步锁。
    为什么不可变?
    String底层代码中,整个类被final修饰,该类不能被继承;其中的value[]属性被final修饰,引用不能被修改。
    为什么StringBuffer线程安全?
    StringBuffer通过synchronized关键字修饰,保证资源不会被抢,从而确保了线程安全。

    6、ArrayList和LinkedList

    ①ArrayList底层是数组,(动态数组),LinkedList底层是双向链表。
    ②随机访问get和set,ArrayList要优于LinkedList。
    ③对于新增和删除,add/remove,LinkedList更快,因为ArrayList要移动数据。

    7、一些常用的线程安全的集合类

    ①Vector:(实现了List接口),比ArrayList多了同步化机制
    ②Stack:栈,继承于Vector。先进后出。
    ③HashTable:比HashMap多了线程安全。(实现了Map接口)
    ④ConcurrentHashMap:高效且线程安全的集合。(继承AbstractMap,实现了ConcurrentMap接口)

    8、以下代码的运行结果

    static class VO implements Cloneable {
    	List<Object> datas = new ArrayList<>();
    
    	@Override
    	public VO clone() throws CloneNotSupportedException {
            return (VO) super.clone();
    	}
    }
    
    @Test	
    public void cloneT() throws CloneNotSupportedException {
        VO vo = new VO();
        vo.datas.add("1");
        VO vo2 = vo.clone();
        vo2.datas.add("2");
        
        System.out.println(vo.datas.size());// 2
        System.out.println(vo2.datas.size());// 2
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    9、完成下面的代码

    public class VO {
    
    	private Integer id;
    
    	public VO(Integer id) {
           super();
           this.id = id;
    	}
    	//...get、set、toString方法
    }
    
    List<VO> volist = mockDatas();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    要求:请对集合voList,根据id进行分组
    分析:就是有个集合,集合内装的是某种对象(泛型),对象有个ID字段。
    根据ID进行分组,同个ID的对象放在一起。
    实现代码如下

    public static void main(String[] args) {
        // 根据题意,准备的测试用的voList
    	VO v1 = new VO(18);
    	VO v2 = new VO(18);
    	VO v3 = new VO(18);
    	VO v4 = new VO(18);
    	List<VO> voList = new ArrayList<>();
    	voList.add(v1);
    	voList.add(v2);
    	voList.add(v3);
    	voList.add(v4);
     
       //解法1
    	// 0.新建map,存放分组后的vo集合数据
    	Map<Integer, List<VO>> map = new HashMap<Integer, List<VO>>();
    	// 1.遍历list
    	for (VO vo: voList) {
    		// 2.获取每个元素的id,判断map中的key有没有这个id
    		if (map.containsKey(vo.getId())) {
    			// 有,就把这个元素放到这个key下的value里
    			map.get(vo.getId()).add(vo);
    		} else {
    			// map的key里没有这个id,
                       // ==>那就以此新建key,再把对应的vo集合放进value
    			List<VO> newVOList = new ArrayList<VO>();
    			newVOList.add(vo);
    			map.put(vo.getId(), newVOList);
    		}
    	}
    	// 现在,已经得到了按id分组的map。
    	// 下一步,根据map,新建list,把分好组的数据放进去
    	List<List<VO>> resultList = new ArrayList<List<VO>>(map.values());
    	// 展示结果
    	for (List<VO> list : resultList) {
    		for (VO vo : list) {
    			System.out.println("User---" + vo.toString());
    		}
    	}
    	/*
        * HashMap.values(): 获取value集合
    	 * 把Map转换成List>,为什么会变成List>?
    	 * 原因在于
    	 * map是以多个userId为键值,存储多个List,map.values转换成list就会变成
    	 * List>
    	 */
        
        // 解法2——使用Stream流
        Map<Integer, List<VO>> finalMap = voList.stream().collect(Collectors.groupingBy(VO::getId));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    10、写一个线程安全的单例模式

    单例模式,一种设计模式,一个类只能创建一个对象。
    单例模式,可以有饿汉式 和 懒汉式。其中饿汉式,是线程安全的。
    懒汉式
    (1)为节省内存,创建对象的步骤放到getInstance()中
    (2)为保证单例,创建对象时加判断。若未创建,就创建;若已创建,就返回之前创建的
    (3)为线程安全,方法上加锁,用synchronized上锁

    //处理后的懒汉式(线程安全), 缺点效率低,
    public static Singleton{
        private static Singleton singleton;
        private Singleton(){
            
        }
        public static synchronized Singleton getInstance(){
            if(singleton == null){
                singleton = new Singleton();
            }
            return singleton;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    //没有用synchronized 的懒汉式, 线程不安全
    public static Singleton{
        private static Singleton singleton;
        private Singleton(){
            
        }
        public static Singleton getInstance(){
            if(singleton == null){
                singleton = new Singleton();
            }
            return singleton;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    饿汉式
    (1)私有化构造器
    (2)内部创建一个实例
    (3)定义一个方法将实例返回。
    这个方法的修饰符必须是public static,返回对象也static修饰。
    (4)返回对象用private修饰,防止外界随意调用。

    //饿汉式
    public class Singleton{
        private static Singleton singleton = new Singleton();
        private Singleton(){    
        }
        
        public static Singleton getInstance(){
            return singleton;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    11、哪些方法保障线程安全

    使用synchronized关键字

    同步代码块

    synchronized 锁对象{
    多条操作共享数据的语句
    }
    锁对象:任意对象,多个线程必须使用同一把锁
    这种方法的好处:多线程下提高安全性
    坏处:运行速率变低

    同步方法

    ●同步非静态方法:
    格式:将synchronized写在方法上。
    锁对象:当前类对象this
    ●同步静态方法:
    锁对象:当前的类对象,类名.class

    使用lock

    lock():加锁 unlock():解锁
    一般使用ReturnLock,是lock的实现类。
    即,Lock lock = new ReturnLock
    释放锁必须在finally中执行,因为锁必须释放

    12、算法,任选一个

    (1)合并2个有序数组,写出代码实现
    (2)从一个有序数组中找一个缺少的元素。数组中至少有一个缺失元素。如数组[1,2,4,5,6,7,],缺少数字3

    13、数据库编程

    学生表stu(id,name,age,class_id)
    学生的成绩表score(id,stu_id,kemu,chengji)

    统计每个班级(class_id)的人数

    select class_id as '班级', count(id) as '人数'
    from stu
    group by class_id
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    查询所有18岁以上学生的信息。如果有课程成绩,查询出最高分的课程成绩

    select stu.*, score.kemu, score.chengji
    from stu stu
    left join score
    on stu.id = score.stu_id
    where stu.age > 18
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    查询每门课程最高分的学生信息

     select b.kemu,b.chengji, c.age, c.name 
     from (
         select kemu, max(chengji) maxc 
         from score group by kemu) a,score b, stu c 
     where 
        a.kemu = b.kemu and 
        a.maxc = b.chengji and
        b.stu_id = c.id
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    结果如下

    14、介绍下几种join

    15、基本类型和包装类型的区别

    (1)包装类型可以为null,基本类型不可以
    使得包装类型可以应用于POJO中,而基本类型不行。
    (2)包装类型可用于泛型,基本类型不可以
    (3)基本类型比包装类型更高效
    基本类型在栈中存储具体的数值。包装类型存储的是堆中的引用.
    (4)自动装箱和自动拆箱
    基本类型 -> 包装类型,自动装箱
    包装类型 -> 基本类型,自动拆箱

  • 相关阅读:
    node-sass改dart-sass and 一些七七八八
    Java并发面试题:(八)AQS原理和Semaphore、CountdownLatch、CyclicBarrier类
    .gitignore 文件
    [emditor] 去掉其中的空行
    自学成为一名黑客(自学笔记)
    HTML+CSS篮球静态网页设计(web前端网页制作课作业)NBA杜兰特篮球运动网页
    测试/开发程序员的成长之路,未来是你们的......
    蓝桥杯备赛Day8——队列
    Go中各种newreader和newbuffer的使用
    Mac 公证失败问题排查
  • 原文地址:https://blog.csdn.net/qq_25844803/article/details/128107255