21年-自研-笔试题

背景

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

题目

1、Object的常用方法

●1.getClass()

public final native Class<?> getClass()

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

public native int hashCode()

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

public boolean equals(Object object){
    return (this == obj);
}

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

protected native Object clone() throws CloneNotSupportedException;

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

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

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

 public final native void notify()

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

 public final native void notifyAll()

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

 public final native void wait(long timeout) throws InterruptedException

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);

}

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);
}

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

 protected void finalize() throws Throwable{}

是保护方法,用于在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

代码解读:
因为 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

进入 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;
}

可以看出, 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
	}
}

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

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
}

9、完成下面的代码

public class VO {

	private Integer id;

	public VO(Integer id) {
       super();
       this.id = id;
	}
	//...get、set、toString方法
}

List<VO> volist = mockDatas();

要求:请对集合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<User>>,为什么会变成List<List<User>>?
	 * 原因在于
	 * map是以多个userId为键值,存储多个List<User>,map.values转换成list就会变成
	 * List<List<User>>
	 */
    
    // 解法2——使用Stream流
    Map<Integer, List<VO>> finalMap = voList.stream().collect(Collectors.groupingBy(VO::getId));
}

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;
    }
}
//没有用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)定义一个方法将实例返回。
这个方法的修饰符必须是public static,返回对象也static修饰。
(4)返回对象用private修饰,防止外界随意调用。

//饿汉式
public class Singleton{
    private static Singleton singleton = new Singleton();
    private Singleton(){    
    }
    
    public static Singleton getInstance(){
        return singleton;
    }
}

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

在这里插入图片描述

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

select stu.*, score.kemu, score.chengji
from stu stu
left join score
on stu.id = score.stu_id
where stu.age > 18

在这里插入图片描述

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

 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

结果如下

14、介绍下几种join

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值