面试-Java基础

面试-Java基础

    文章内容是根据牛客、leetcode等平台上的面经总结出来的常见基础面试题,篇幅将近6000字,可能需要花20min左右。
1、为什么Java支持跨平台
    与其他语言相比,Java程序能够做到 “编译一次,到处运行”,可见它的跨平台性非常强。java的这种跨平台到底是实现的?
    在这之前,我们先了解一下C或者C++等这些高级语言为什么不能实现跨平台?对于计算机,它们只能识别0、1序列组成的机器指令。所以需要把C或者C++语言编写的程序翻译成计算机能识别的0、1序列组成的机器指令,而担任这种翻译工作的就是“编译程序”。但是,不同平台上可执行的0、1序列可能存在差异。某一个指令可能在Windows上可能是0101,但是在Linux下可能是1010,因此必须使用不同的编译程序为不同平台编译出可执行的机器指令,在Windows上编译好的程序,不能直接拿到Linux等其他平台执行。也就是说用C或者C++语言编写的程序,无法达到"编译一次,到处运行跨平台性的目的。
    Java也是一种高级语言,要让计算机执行你撰写的Java程序,也得通过编译程序进行编译。但是Java编译程序并不直接将Java源代码编译为相依于计算机平台的0、1序列,而是将其 编译为字节码。Java源代码的扩展名为.java,经过编译程序编译之后生成扩展名为.class的字节码。如果想要执行字节码文件,目标平台必须要安装JVM(JAVA虚拟机),JVM会将字节码翻译为平台可执行的计算机指令,即:0、1序列。必须注意:不同的平台一定要安装属于该平台的JVM。JVM可以将字节码文件翻译为相应平台看得懂的0、1序列,有了JVM你的Java程序就达到了“编译一次到处运行”的跨平台目的。
2、Java语言的三大特性?了解什么是多态吗?
    Java语言主要包括封装、继承、多态三大特性。多态可以描述为同一对象表现出不同的形态。多态分为编译时多态和运行时多态,编译时多态主要体现在方法重载上,运行时多态体现在方法重写上。
3、什么是方法重载和方法重写?
   方法重载和方法重写都是多态的表现形式。前者属于 编译时多态 ,而后者属于 运行时多态 。方法重载指的是在同一个类中定义了多个同名的方法,但是它们的参数不同(参数个数、参数类型或者二者不同)。方法重载对返回类型没有要求,不能通过类型进行区分。方法重写体现在继承关系中,子类重写方法与父类被重写方法要有相同参数、返回类型、以及更好的访问级别(比如父类方法被protected修饰,子类重写方法的访问级别就不能低于protected)。
4、抽象类和接口有什么区别?
   1)定义抽象类时用abstract关键字修饰,而接口用interface修饰;
   2)抽象类可以有构造函数,而接口没有构造函数;
   3)抽象类的方法既可以是抽象的,也可以是具体的。而接口在jdk1.7之前,它的方法都是抽象的(访问级别只能为public,不可以用其它访问级别修饰),在jdk1.8之后它可以有具体方法,但是访问级别必须为default;
   4)子类通过extends关键字实现抽象类,如果子类不是抽象的,那么子类需要实现抽象类中申明的所有抽象方法;子类使用implement关键字来实现接口,除了default访问级别具体方法之外,它需要实现接口中申明的所有抽象方法;
   5)一个类只能继承一个抽象类,但是可以实现多个接口。
​5、Java基础类型和包装类
在这里插入图片描述
   包装类使基本类型具有类的特征。针对boolean类型,JAVA规范中并没有明确指出其大小:

http://www.360doc.com/content/18/0618/20/56263195_763369858.shtml

   在开发中,我们要谨慎使用==判断包装类是否相等,比如:

public class IntegerTest {
      public static void main(String[] args) {
         Integer i1 = 1;
         Integer i2 = 1;
         System.out.println(i1 == i2);//输出 true
         Integer i3 = 128;
         Integer i4 = 128;
         System.out.println(i3 == i4); //输出false
     }
}

   Integer i1=1等价于Integer i1 = Integer.valueOf(1); 从valueOf函数我们可以看到,包装类使用了 缓存机制-IntegeCache ,它会将大于IntegerCache.low,小于IntegerCache.high的对象缓存起来。因此出现i1是否等于i2为true,i3是否等于i4为false的情况。(备注:==实际上判断的是对象的地址)

public static Integer valueOf(int i) {
      if (i >= IntegerCache.low && i <= IntegerCache.high)
          return IntegerCache.cache[i + (-IntegerCache.low)];
      return new Integer(i);
}

6、== 和 equals方法的区别?
   ==用于判断两个对象在内存中是不是同一个对象,即判断对象在内存中的地址是否相同。两个String对象的值相同,但是在内存中的地址可能不同。比如:

String s1 = "abc"; 
String s2 = new String("abc"); 
System.out.println(s1 == s2); //输出false

   == 比较的是引用对象的地址,而equals方法是比较对象的内容。equals方法是由Object对象提供,如果子类没有重写equals()方法,那么它就等价于==。String类就重新实现了equals方法,会判断字符串中的内容是否相等。

String s1 = "abc";
String s2 = new String("abc"); 
System.out.println(s1.equals(s2)); //输出true

7、Object对象有哪些方法?
   1)getClass():获取对象运行时类型;
   2)hashCode(): 获取对象的hash值,该方法在hash数据结构中经常用到,比如Map;
   3)equals(): 用于比较两个对象是否相等,子类一般会重写该方法。重写该方法是一般都需要重写hashCode()方法;
   4)clone(): 进行对象的浅复制,只有实现了Cloneable接口的类才能调用clone方法,否则会抛出CloneNotSupportedException;
   5)wait(): 使当前线程等待该对象的锁。调用wait()方法之前,当前线程必须是该对象的拥有者,即获取了该对象的锁;wait()方法一直等待,直到获得锁或者被中断。wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。

 public static void main(String[] args) throws InterruptedException {
     //主线程获取了LockWaitTest对象的锁,可以正常执行wait()方法
     LockWaitTest lock = new LockWaitTest();
     synchronized (lock) {
        System.out.println("调用前");
        integerTest.wait(1000);
        System.out.println("调用后");
    }
    ----------
    //主线程获取的是OtherLock对象的锁,此时执行LockWaitTest对象的wait方法会报错
    synchronized (OtherLock.class) {
        System.out.println("调用前");
        integerTest.wait(1000);
        System.out.println("调用后");
    }
}

   6)notify()/notifyAll(): 前者表示随机唤醒该对象上等待的某个线程,后者表示唤醒该对象上等待的所有线程;该方法用于配合wait()方法使用;
   7)toString(): 将对象转成字符串;
8、深复制与浅复制的区别?
   浅复制是只复制当前对象的基本类型,不复制引用对象,而是保持引用关系;深复制对基本类型和引用对象,都会进行复制;如下图:
在这里插入图片描述
9、Java访问控制级别有哪些?
   在Java中,针对类、属性、方法提供了4种访问级别,分别是private、default、protected、public,它们的访问控制级别如下:
在这里插入图片描述
10、关于short的运算,以下代码可以正常编译吗?

short i = 1;
i += 1; //正常编译
i = i + 1; //错误,无法编译

   第3行会提示错误,因为1是int类型,在进行i+1时,i会向上转型为int,此时计算结果为int类型再赋值给short类型就会报错;而第2行i += 1可以正常编译,因为编译器会对它进行特殊处理,使i += 1 <=> i = (short) (i + 1)。
​11、final、finally、finalize三者之间的区别和用法?

   final是Java的关键字,可以用来修饰类、方法、属性。被final修饰的类,表示它不能被继承;被final修饰的方法,表示它不能被重写;被final修饰的属性,表示它指向的对象地址不可变,但对象内容可变。比如:

final  Object obj = new Object();
Object otherObj = new Object();
obj.setObj(1);//正确,可改变对象的内容
obj = otherObj;//编译报错,不能改变final修饰的变量的引用地址

   finally用于try/catch/finallyfinally异常处理结构中,try需要配合catch/finally(至少要二者中的一个)使用,finally里的代码块一定会被执行;finally代码块中的return会覆盖try/catch代码块中的return,比如:

try{
    return 1;
} finally {
    return 2}
//最终返回结果为2

   finalize是Object对象中的方法,JVM进行垃圾回收之前会调用对象的finalize方法;
12、String、StringBuffer、StringBuilder三者之间的区别?
   三者底层都是基于数组实现。String类和它的底层数组都被final关键字修饰,表示它是不可变的 ,字符串一旦确定,它的长度就不可变。StringBuffer和StringBuilder它们的实现类似,长度是可变,不同的是StringBuffer的所有方法都被sychronized关键字修饰,它是线程安全的。对于三者的使用:如果字符串不用改变可以使用String; 如果要保证线程安全,则使用StringBuffer,性能会稍差一些;如果字符串经常变化且不关心线程安全问题,则使用StringBuilder。
   对于String的不可变我们举个例子:

String s1 =  "abc";
s1 = s1 + "de";
StringBuilder s2 = new StringBuilder("abc);
s2 = s2.append("de");

   对于s1引用,这个例子实际上在内存出现了两个对象,因为String是不可变的,因此会新建一个"abcde"对象,使s1引用指向新的内存地址。对于s2引用,内存中只会出现一个对象,它是在原来对象基础上增加字符。
13、为什么重写equals方法时需要重写hashcode方法?
   对于Map,Set这样的hash数据结构,它们的Key或者元素必须是唯一的,它的唯一性需要满足1) 通过equals方法比较是相等的;2) hash值是相同的;当equals方法没有被重写时,它是通过==判断相等性,如果对象判断为相等,那么它的hashcode就一定相等,这样就可以保证唯一性。但是,当equals方法被重写后,通过equals判断为相等的对象,它们的hashcode不一定相等,那么就可能打破上述的唯一性。因此,我们需要重写hashcode方法来保证用equals方法判断相等的对象,它们的hashcode也是相等的。举个例子:

String s1 = "abc"; 
String s2 = new String("abc");

   equals方法不重写的情况下,s1. equals(s2s)为false,显然,这不是我们期望的。因此String类重写了equals方法,保证了s1.equals(s2)为true,但是如果不重写hashcode方法,那么s1和s2它们的hashcode可能不同,依然会打破上述hash数据结构的唯一性,因此String类也重写了hashcode方法。
14、wait()方法和sleep()方法的区别
   wait()方法是Object类中的方法,sleep()是Tread类中的静态方法。二者都会是当前线程由运行态转换为阻塞态,区别在于,前者会释放线程所持有的锁资源,并释放所占有的CPU资源,需要通过notify()/notifyAll()方法唤醒;而后者不会释放CPU资源,休眠一段时间后自动从阻塞态转换为运行态。
15、创建线程有哪几种方法?

  1. 通过继承Thread类重写run()方法的方式创建线程;
public class MyThread extends Tread{
    @overide
    public void run() {
       }
    }
    public class Main {
    public static void main(String ) {
           MyThread thread = new MyThread()
           thread.start();
    }
}
  1. 通过实现Runnable接口创建线程;
public class MyRunnable implement RunRunable{
   @overide
   public void run() {
   }
}
public class Main {
   public static void main(String ) {
       Thread thread = new Thread(new MyRunnable());
       thread.start();
   }
}
  1. 通过实现Callable接口和Feature接口来创建线程;
public class MyCallable implement Callable{
   @overide
   public T call() {
   }
}
public class Main {
   public static void main(String ) {
       FeatureTask feature = new FeatureTask(new MyCallable);
       MyThread thread = new MyThread(feature);
      thread.start();
      feature.get();//等待执行结果
   }
}

   区别于1)和2)两种创建线程的方式,第三种支持有返回值。
4) 通过线程池ThreadPoolEexcutor创建.
16、值传递和引用传递有什么区别?
   值传递针对基础数据类型,传的是副本数据,接收参数改变,不影响原数据;而引用传递是针对对象型类型而言,传的是对象在内存中的地址,接收参数改变对象的内容会导致原对象的内容改变,因为传入参数和接受参数引用的是同一个对象。
17、satics关键字的作用,被satics或private关键字能否被重写?
   Java中的类、方法和属性都可以被static关键字修饰,当它们被static修饰时,表示它们是属于类的,当类被加载但JVM时,它们也会被分配相应的内存。同一个类中,静态方法是无法直接访问非静态方法或属性的,因为非静态方法或属性它们属于对象,只有当类被的实例化后,它们才会被分配相应的内存地址,所以静态方法无法直接访问,而非静态方法是可以直接访问静态方法的。(简单说就是,静态方法被分配内存时,非静态方法还没有分配,所以它无法访问在内存中还没有出现的非静态方法)。被static或privacy修饰的方法是无法被重写的,static修饰的方法属于类方法,不存在继承的说法,而privacy修饰的方法它的访问级别为类,不可被子类继承。
   static不能用于修饰局部变量,产量同时被final和static 修饰时表示它为一个常量。

public class StaticTest {
   privacy static int v = 10;
   //静态方法
   public static void staticMethod() {
       static int = 1//报错
       normalMethod()//报错
       //以下正确
       StaticTest staticTest = new StaticTest();
       staticTest.normalMethod();
    }
   //非静态方法
   public void normalMethod() {
      staticMethod();//正常
   }
}

18、Java原子类有哪些?
   原子表示一个密不可分的动作。在高并发场景下,很多操作都会面临线程安全问题,比如i++,看似是一个操作,实际上底层对应多条机器码,使它不在线程安全。我们可以使用sychronized或lock来保证这些操作的线程安全性。对于部分基础数据类型,Java提供了相应的原子类AtomicInteger、AtomicLong、AtomicReference等来保证一些非原子操作的安全性。
19、Java的引用了解吗?
   Java引用分为四种类型,分别是强引用、软引用、弱引用和虚引用。
   强引用:是我们接触最多且最常用的一种引用方式,比如Object obj = new Object(),obj就属于强引用,强引用对象只有通过GC-root垃圾回收算法判定为可回收,内存才会被释放;
   **软引用:**描述重要而非必需的对象,Java种使用SoftReference类来定义,比如

WeakReference<Object> obj = new WeakReference(new Object());

   *obj软引用关联着Object对象。区别于强引用,它不会发生Out of Memory(OOM)问题,当内存不足时,如果软引用关联的对象只被软引用关联时,那么它都会被JVM强制执行回收。软引用关联的对象从某种程度来说,它的生命周期被延长,因为它一直被软引用关联,导致它一直存在引用链,直到内存不足时才被回收。它的适用对内存敏感的高速缓存场景:图片缓存或者浏览器缓存;
   **弱引用:**描述非必需的对象,Java使用的WeakReference类来定义,比如:

WeakReference<Object> obj = new WeakReference(new Object());

   *obj弱引用关联着Object对象。弱引用与软引用功能相似,区别在于软引用,无论内存是否充足,如果弱引用关联的对象只被弱引用使用,那么它将被JVM回收。
   虚引用: "形同虚设"的引用,Java中使用PhantomReference来定义,比如:

PhantomReference<Object> obj = new PhantomReference(new Object());

   *obj虚引用关联着Object对象。它的回收时机与弱引用类似,但是需要配合ReferenceQueue使用。

20、什么是泛型?为什么需要泛型?
   泛型是参数化类型,就是将类型进行参数化,在使用时再传入具体的类型。类似方法中的参数产量,在调用时才指定具体参数值。
   为什么需要泛型?我们先来看个例子:

List list = new ArrayList();
list.add("abc");
list.add(1000);
for(int i=0;i < list.size();i++){
  Object obj = list.get(i);
  if (obj instanceOf String) {
      String s = (String)obj;
   }
   if (obj instanceOf Integer) {
      Integer i = (Integer)obj;
   }
}

   我们向容器添加了String和Integer两种数据类型,当我们取出来时,首先需要先判断属于哪种数据类型,然后再对它做类型转换。想象一下,如果此时我们向list放各种各样的类型,那么我们取元素时是不是特别麻烦,万一我们忘记了某一种类型,那么就会产生不可预料的问题。jdk1.5后引入了泛型来解决这一类问题。引入泛型后,使用List时,我们可以为它指定类型:

List<String> list = new ArrayList(); 
list.add("abc");
for(int i=0;i < list.size();i++){  
    String obj = list.get(i); 
} 
list. add(1000)//报错,此时list只能放StringString类型

https://blog.csdn.net/caihaijiang/article/details/6403349

21、Java异常?Error和Exception有什么区别?
在这里插入图片描述
   上图大致描述了Java异常体系结构。异常主要分为Error和Exception两大类。其中,Error属于系统级错误,由JVM抛出,它无法通过修改程序来修复,会导致应用直接退出。常见的Error主要包括:OutOfMemeryError、NoSuchClassError、NoSuchMethodError等等。Exception属于程序需要捕获或处理的异常,它又分为编译时异常(受检查)和运行时异常(不受检查),编译时异常需要强制捕获或者抛出,比如IOException、InteruptedException等,而运行是异常通常是由于程序运行错误而时抛出来的异常如NullPointException、IllegalArgumentException等。
22、说说你知道的RuntimException?
   NullPointException:空指针异常,调用了未初始化或不存在的对象导致;
   IndxOutOfBoundsException: 数组越界异常,操作数组或集合对象时经常发生;
   IllegalArgumentException: 参数错误异常,传参有误导致;
   NumberFromatException: 字符串转数字时抛出的异常,字符串中含非数字字符;
   ClassCastException: 类型转换异常;
   ConcurrentModifiedException:集合并发修改导致的异常;
23、throw和throws之间有什么区别?
   throw是通过语句将具体异常抛出;
   throws用于方法上,将异常抛出给方法调用方处理;

//throws将异常直接抛给调用方
public void error() throws Exception{
    try {
        int i = 1/0;
    } catch (Exception e) {
        throw new RuntionException("分母不能为0");//throw关键字抛出异常
    }
}

   感谢你花时间阅读完本文,如果觉得写得不错,辛苦帮忙分享给其他的朋友哦!也可以关注我的公众号!如果有写得不正确的地方,也辛苦指正呀!我会持续更新面试相关的内容,希望可以帮助各位找工作的朋友。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值