java面试题错题集(牛客网错题)

时间:2022-03-17 19:11:28

一、关于Object类的说法正确

  • Java中所有的类都直接或间接继承自Object,无论是否明确的指明,无论其是否是抽象类。
  • Object的equals方法,只有一句话,return this==object。
  • equals比较的是指是否相同,而==表示是否指向同一个对象(地址是否一样)

Object中的方法

public final native Class<?> getClass();
public native int hashCode();
public boolean equals(Object obj){}
protected native Object clone() throws CloneNotSupportedException;
public String toString() {}
public final native void notify();
public final native void notifyAll();
public final native void wait(long timeout) throws InterruptedException;
public final void wait(long timeout, int nanos) throws InterruptedException{}
public final void wait() throws InterruptedException {}
protected void finalize() throws Throwable { }

二、关于内部类的使用

java面试题错题集(牛客网错题)

三、static修饰符

  • static不能修饰局部变量

四、JVM

  1. 公共的
  • 堆区:只存放类对象,线程共享;
  • 方法区:又叫静态存储区,存放class文件和静态数据,线程共享;
  1. 私有的
  • 栈区:存放方法局部变量,基本类型变量区、执行环境上下文、操作指令区,线程不共享;
  • 程序计数器:私有线程区域
  • 本地方法栈:私有线程区域

五、方法的执行

  • 静态代码块->普通方法块->构造函数
  • 父静子静,父构子构

六、三目运算符

三元操作符如果遇到可以转换为数字的类型,会做自动类型提升

Object o1 = (false) ? new Double(1.0) : new Integer(2);
System.out.println(o1);//打印2.0

七、执行full GC的条件

  • 老年代满
  • 持久代满
  • System.gc()
  1. 新生代:(1)所有对象创建在新生代的Eden区,当Eden区满后触发新生代的Minor GC,将Eden区和非空闲Survivor区存活的对象复制到另外一个空闲的Survivor区中。(2)保证一个Survivor区是空的,新生代Minor GC就是在两个Survivor区之间相互复制存活对象,直到Survivor区满为止。

  2. 老年代:当Survivor区也满了之后就通过Minor GC将对象复制到老年代。老年代也满了的话,就将触发Full GC,针对整个堆(包括新生代、老年代、持久代)进行垃圾回收。

  3. 持久代:持久代如果满了,将触发Full GC。

八、Collection关系

java面试题错题集(牛客网错题)

1.Map的继承关系

//实现Map 继承自Dictionary
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable
//实现Map
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
//实现Map
public class IdentityHashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, java.io.Serializable, Cloneable
//实现List
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
Collection
├List
│├LinkedList
│├ArrayList
│└Vector
│ └Stack
└Set
Map
├Hashtable
├HashMap
└WeakHashMap

java面试题错题集(牛客网错题)

2.线程安全的类(喂,SHE)

  • 喂(Vector)
  • S(Stack)
  • H(hashtable)
  • E(enumeration)

九、JDK1.8版本之前,抽象类和接口的区别

is-a,理解为是一个,代表继承关系。 如果A is-a B,那么B就是A的父类。

like-a,理解为像一个,代表组合关系。 如果A like a B,那么B就是A的接口。

has-a,理解为有一个,代表从属关系。 如果A has a B,那么B就是A的组成部分。

十、关系数据模型ORMapping

一般关系数据模型和对象数据模型之间有以下对应关系:表对应类,记录对应对象,表的字段对应类的属性

十一、import导包问题

能访问java/util目录下的所有类,不能访问java/util子目录下的所有类

导入java.util.*不能读取其子目录的类,因为如果java.util里面有个a类,java.util.regex里面也有个a类,我们若是要调用a类的方法或属性时,应该使用哪个a类呢。

十二、DBMS事务

DBMS中事务有四个特性,持久性一致性原子性隔离性,持久性实现恢复管理子系统,一致性实现并发控制子系统,原子性实现完整性管理子系统,隔离性实现安全控制管理子系统

十三、java中数据占空间

  • java中只有byte, boolean是一个字节, char是两个字节

十四、java序列化

  • transient和序列化有关,这是一个空接口,起标记作用,具体的序列化由ObjectOutputStream和ObjectInputStream完成。transient修饰的变量不能被序列化,static变量不管加没加transient都不可以被序列化

十五、java数据类型

Java中的四类八种基本数据类型

  1. 整数类型 byte short int long (int是整形,也属于整数类型)
  2. 浮点型 float double
  3. 逻辑型 boolean(它只有两个值可取true false)
  4. 字符型 char string

十六、Spring框架中获取连接池中的四个方法

  • jdbc方式:使用DriverManagerDataSource建立连接,没有用到连接池
  • C3P0连接池:使用的是ComboPooledDataSource
  • DBCP连接池:使用的是BasicDataSource
  • JNDI方式:使用的是JndiObjectFactoryBean

十七、类加载器(ClassLoader)

  • java中类的加载有5个过程,加载、验证、准备、解析、初始化;这便是类加载的5个过程,而类加载器的任务是根据一个类的全限定名来读取此类的二进制字节流到JVM中,然后转换为一个与目标类对应的java.lang.Class对象实例,在虚拟机提供了3种类加载器,引导(Bootstrap)类加载器、扩展(Extension)类加载器、系统(System)类加载器(也称应用类加载器)
  • 一个类,由不同的类加载器实例加载的话,会在方法区产生两个不同的类,彼此不可见,并且在堆中生成不同Class实例。
  • 类加载器是肯定要保证线程安全的
  • 装载一个不存在的类的时候,因为采用的双亲加载模式,所以强制加载会直接报错
  • 双亲委派模式是在Java 1.2后引入的,其工作原理的是,如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式,即每个儿子都很懒,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己想办法去完成,所以默认是父装载
  • 自定义类加载器实现 继承ClassLoader后重写了findClass方法加载指定路径上的class

十八、abstract关键字放置的位置问题

abstract class Animal{abstract  void growl();}//放置在class前面

十九、java的基本单元

  • java的基本编程单元是类,基本存储单元是变量。

二十、String

对于"abc"=="a"+"bc"true,但如果是x="a",y="bc","abc"==x+y则为false,因为编译器不能优化未知的变量

    1)String类是final类,也即意味着String类不能被继承,并且它的成员方法都默认为final方法。在Java中,被final修饰的类是不允许被继承的,并且该类中的成员方法都默认为final方法。

    2)String类底层是char数组来保存字符串的。

    对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象

    字符串常量池

    在class文件中有一部分来存储编译期间生成的字面常量以及符号引用,这部分叫做class文件常量池,在运行期间对应着方法区的运行时常量池。

    JVM为了减少字符串对象的重复创建,其维护了一个特殊的内存,这段内存被成为字符串常量池或者字符串字面量池

    工作原理

    当代码中出现字面量形式创建字符串对象时,JVM首先会对这个字面量进行检查,如果字符串常量池中存在相同内容的字符串对象的引用,则将这个引用返回,否则新的字符串对象被创建,然后将这个引用放入字符串常量池,并返回该引用。

    实现前提

    字符串常量池实现的前提条件就是Java中String对象是不可变的,这样可以安全保证多个变量共享同一个对象。如果Java中的String对象可变的话,一个引用操作改变了对象的值,那么其他的变量也会受到影响,显然这样是不合理的。

    String str1 = "hello";

    这里的str1指的是方法区中的字符串常量池中的“hello”,编译时期就知道的;

    String str2 = "he" + new String("llo");

    这里的str2必须在运行时才知道str2是什么,所以它是指向的是堆里定义的字符串“hello”,所以这两个引用是不一样的。

    如果用str1.equal(str2),那么返回的是true;因为String类重写了equals()方法。

    编译器没那么智能,它不知道"he" + new String("llo")的内容是什么,所以才不敢贸然把"hello"这个对象的引用赋给str2.

    如果语句改为:"he"+"llo"这样就是true了。

    new String("zz")实际上创建了2个String对象,就是使用“zz”通过双引号创建的(在字符串常量池),另一个是通过new创建的(在堆里)。只不过他们的创建的时期不同,一个是编译期,一个是运行期。

    String s = "a"+"b"+"c";

    语句中,“a”,"b", "c"都是常量,编译时就直接存储他们的字面值,而不是他们的引用,在编译时就直接将它们连接的结果提取出来变成"abc"了。

二十一、java构造方法调用其他构造方法

  • 在JAVA中,假设A有构造方法A(int a),则在类A的其他构造方法中调用该构造方法和语句格式应该为this(x)

二十二、java初始化问题

  • java成员变量有自己的默认初始值,但是方法中没有默认初始值
  • java方法中的变量不赋初始值如果不使用的话不报错,但是如果被使用到的话,编译异常
  • 在定义的时候加入final关键字,被声明为常量,会被JVM优化

1.虚拟机规范严格规定了有且只有五种情况必须立即对类进行“初始化”:

  1. 使用new关键字实例化对象的时候、读取或设置一个类的静态字段的时候,已经调用一个类的静态方法的时候。

  2. 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有初始化,则需要先触发其初始化。

  3. 当初始化一个类的时候,如果发现其父类没有被初始化就会先初始化它的父类。

  4. 当虚拟机启动的时候,用户需要指定一个要执行的主类(就是包含main()方法的那个类),虚拟机会先初始化这个类;

  5. 使用Jdk1.7动态语言支持的时候的一些情况。

2.不会引起初始化的情况

  1. 子类引用父类的静态字段,只会触发子类的加载、父类的初始化,不会导致子类初始化

  2. 通过数组定义来引用类,不会触发此类的初始化

  3. 常量在编译阶段会进行常量优化,将常量存入调用类的常量池中, 本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。

二十三、java静态方法调用问题

package NowCoder;
class Test {
public static void hello() {
System.out.println("hello");
}
}
public class MyApplication {
public static void main(String[] args) {
// TODO Auto-generated method stub
Test test=null;
test.hello();
}
}
//能编译通过,并正确运行
/*
*当程序执行Test tset时:jvm发现还没有加载过一个称为”Test”的类,它就开始查找并加载类文件”Test.class”。
*它从类文件中抽取类型信息并放在了方法区中,jvm于是以一个直接指向方法区lava类的指针替换了'test'符号引用,
*以后就可以用这个指针快速的找到Test类了。
*/

二十四、ConcurrentHashMap

  • 现在jdk1.8及以后的版本 ConcurrentHashmap是采用CAS+synchronized关键字来保证线程安全性,在jdk1.7及之前的版本是采用分段锁机制来保证线程安全性。

二十五、JVM回收算法

  • 两个最基本的java回收算法:复制算法和标记清理算法
    • 复制算法:两个区域A和B,初始对象在A,继续存活的对象被转移到B。此为新生代最常用的算法
    • 标记清理:一块区域,标记可达对象(可达性分析),然后回收不可达对象,会出现碎片,那么引出
    • 标记-整理算法:多了碎片整理,整理出更大的内存放更大的对象
  • 两个概念:新生代和年老代
    • 新生代:初始对象,生命周期短的
    • 永久代:长时间存在的对象
  • 整个java的垃圾回收是新生代和年老代的协作,这种叫做分代回收。
  • P.S:Serial New收集器是针对新生代的收集器,采用的是复制算法
  • Parallel New(并行)收集器,新生代采用复制算法,老年代采用标记整理
  • Parallel Scavenge(并行)收集器,针对新生代,采用复制收集算法
  • Serial Old(串行)收集器,新生代采用复制,老年代采用标记整理
  • Parallel Old(并行)收集器,针对老年代,标记整理
  • CMS收集器,基于标记清理
  • G1收集器:整体上是基于标记 整理 ,局部采用复制

综上:新生代基本采用复制算法,老年代采用标记整理算法。cms采用标记清理。

二十六、super与this

super和this都只能位于构造器的第一行,而且不能同时使用,这是因为会造成初始化两次,this用于调用重载的构造器,super用于调用父类被子类重写的方法

1、super()表示调用父类构造函数、this()调用自己的构造函数,而自己的构造函数第一行要使用super()调用父类的构造函数,所以这俩不能在一个构造函数中会出现重复引用的情况

2、super()和this()必须在构造函数第一行,所以这一点也表明他俩不能在一个构造函数中

3、this()和super()都指的是对象,所以,均不可以在static环境中使用。包括:static变量,static方法,static语句块(里面不能使用非static类型的)。

二十七、java关键字

java面试题错题集(牛客网错题)

  • java中true ,false , null在java中不是关键字,也不是保留字,它们只是显式常量值,但是你在程序中不能使用它们作为标识符。

  • 其中const和goto是java的保留字。java中所有的关键字都是小写的,还有要注意true,false,null, friendly,sizeof不是java的关键字,但是你不能把它们作为java标识符用

二十八、线程方法

1.sleep()方法(不会释放资源锁)

 在指定时间内让当前正在执行的线程暂停执行,但不会释放“锁标志”。不推荐使用。 

sleep()使当前线程进入阻塞状态,在指定时间内不会执行。

2.wait()方法 (释放资源锁)

在其他线程调用对象的notify或notifyAll方法前,导致当前线程等待。线程会释放掉它所占有的“锁标志”,从而使别的线程有机会抢占该锁。 

当前线程必须拥有当前对象锁。如果当前线程不是此锁的拥有者,会抛出IllegalMonitorStateException异常。 

 唤醒当前对象锁的等待线程使用notify或notifyAll方法,也必须拥有相同的对象锁,否则也会抛出IllegalMonitorStateException异常。 

 waite()和notify()必须在synchronized函数或synchronized block中进行调用。如果在non-synchronized函数或non-synchronized block中进行调用,虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常。

3.yield方法 (释放资源锁)

暂停当前正在执行的线程对象。 

 yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。 

 yield()只能使同优先级或更高优先级的线程有执行的机会。

4.join方法 (释放资源锁,底层调用了wait方法)

等待该线程终止。 

 等待调用join方法的线程结束,再继续执行。如:t.join();//主要用于等待t线程运行结束,若无此句,main则会执行完毕,导致结果不可预测。
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0; if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
} if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay); //在此调用了wait方法
now = System.currentTimeMillis() - base;
}
}
}

二十九、Exception

注意:异常和错误的区别:异常能被程序本身可以处理,错误是无法处理。

​ 通常,Java的异常(包括Exception和Error)分为 可查的异常(checked exceptions)和不可查的异常(unchecked exceptions)

​ 可查异常(编译器要求必须处置的异常): 正确的程序在运行中,很容易出现的、情理可容的异常状况 。 可查异常虽然是异常状况,但在一定程度上它的发生是可以预计的,而且一旦发生这种异常 状况,就必须采取某种方式进行处理。

​ 除了RuntimeException及其子类以外,其他的Exception类及其子类都属于可查异常。这种异常的特点是Java编译器会检查它,也就是说,当程序中可能出现这类异常,要么用try-catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过。

​ 不可查异常(编译器不要求强制处置的异常):包括运行时异常(RuntimeException与其子类)和错误(Error)。

Exception 这种异常分两大类运行时异常和非运行时异常(编译异常)。程序中应当尽可能去处理这些异常。

运行时异常: 都是RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。

运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。

非运行时异常 (编译异常): 是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。

三十、try catch finally

  1. 若try代码块内含有return,同时存在finally代码块(代码块内无return值)时,先执行finally函数的值。

  2. 若try代码块内含有return,同时存在finally代码块且代码块内含有return值时,此时finally代码块内的return值将直接返回(或覆盖掉try代码块中的return值)。

  3. finally代码块在return中间执行。return的值会被放入临时空间,然后执行finally代码块,如果finally中有return,会刷新临时空间的值,方法结束返回临时空间值。