java基础面试题

一、面向对象和面向过程区别
1、面向对象比面向过程性能低,因为类的实例化需要耗费性能。
2、面向对象比面向过程更易复用、易拓展、易维护。
java性能差的主要原因并不是因为它是面向对象语言,而是因为它编译过后并不是能直接能在机器上执行的机器码,而是字节码,还需要边解释边执行,所以效率低。
那么为什么java要采用字节码呢?字节码是给JVM识别的代码,不需要针对特定的OS,实现跨平台的特性,一处编译,处处运行。
.java文件(源文件)---->JDK中的javac编译---->.class文件(JVM可理解的Java字节码)---->JVM解释---->机器可以执行的二进制机器码。
.class--->机器码:JVM类加载器先把字节码加载到内存中,然后通过解释器逐行解释执行,这样会比较慢,后面引入了JIT编译器,运行时编译,可以将热点代码编译成的机器码保存起来,以便下次调用,提高了效率。
二、JDK和JRE区别
JDK包括JRE,它是完整的java SDK,不仅包括java的运行环境还有javac和工具包,能够创建和编译程序。
JRE是java的运行环境,如果java程序已经编译完成了,可以只安装JRE运行
三、java和C++区别
都是面向对象的语言,都支持封装、继承和多态
Java 不提供指针来直接访问内存,程序内存更加安全
Java 的类是单继承的,C++ 支持多重继承;虽然 Java 的类不可以多继承,但是接口可以多继承。
Java 有自动内存管理机制,不需要程序员手动释放无用内存
四、构造器是否可以被override?
父类的私有属性和构造方法是不能被继承的,所以也就不存在重写,但是可以被重载,一个类中可以有多个构造函数。
五、重载和重写
重载发生在同一个类中,方法名相同,参数列表不同(参数个数、类型不同),返回值类型和访问修饰符可以不同。解释一下:只有返回值类型或者访问修饰符不同时,不算重载,因为有些方法的调用是不指定返回值类型的,直接调用,像:new Student().say();如果只有返回值类型不同,那么JVM仍然无法确定调用的是哪个方法。
重写发生在父类子类之间,方法名参数列表必须相同,返回值范围小于父类,抛出异常范围小于等于父类,访问修饰符范围大于等于父类,如果父类访问修饰符是private,那么子类是不能重写该方法的。
六、继承
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码。子类在父类的基础上拓展功能。

关于继承如下 3 点请记住:

子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有。
子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
子类可以用自己的方式实现父类的方法。(以后介绍)。

七、多态
所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。

在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。
八、String 、 StringBuilder、 StringBuffer区别
可变性:String是使用private final char[] string ;final修饰的字符数组存储字符串的,所以String值是不可变的;StringBuffer和StringBuilder都继承自AbstractStringBuilder类,也使用char[] string;没有使用final修饰,所以值是可变的;
线程安全性:String值不可变,是常量,线程安全;StringBuilder和StringBuffer继承自AbstractStringBuilder类,封装了一些常用的方法如append,indexof,insert、expandCapacity,StringBuffer对方法加了同步锁做了同步处理,是线程安全的,StringBuilder没有,所以是线程不安全的。
性能:每次操作String对象,都会生成一个新的String对象,然后将指针指向新的String对象;StringBuilder和StringBuffer都是操作对象本身,但是StringBuffer加了同步锁,所以性能下降。
总结:操作少量String对象使用String即可;单线程环境下操作大量String字符串,采用StringBuilder;多线程环境下操作大量String字符串,采用StringBuffer;
九、Java中定义一个不做事且没有参数的构造方法的作用
实例化子类的时候需要先实例化父类,如果子类构造方法中没有使用super()调用父类构造方法,则会调用父类没有参数的构造方法,如果父类只定义了有参数的构造方法,则会编译报错;
十、成员变量和局部变量区别
成员变量是属于类的,在类中定义;局部变量是属于方法的,在方法中定义;成员变量可以被访问修饰符或static修饰,局部变量不可以,但二者都可以被final修饰;如果成员变量被static修饰,则该成员变量属于类中所以实例的,如果没有被static修饰,则属于某个实例的;对象是在堆内存中的,局部变量在栈内存中。成员变量是对象的一部分,随着对象的创建而存在,生命周期和对象一致;局部变量是随着方法的入栈和出栈而创建和消失。成员变量如果没有被赋初值,会自动以类型的默认值赋值(被final修饰的成员变量必须被显式赋值),局部变量不会自动赋值。
十一、一个类的构造方法的作用是什么? 若一个类没有声明构造方法,该程序能正确执行吗? 为什么?
主要作用是完成对类对象的初始化工作。可以执行。因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。但是一个类一旦声明了构造方法,就不会有默认的不带参数的构造方法了,必须声明不带参数的构造方法,原因参考九。
十二、hashcode和equals
       hashCode() 的作用就是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode()在散列表中才有用,在其它情况下没用。在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。

hashCode()与equals()的相关规定
如果两个对象相等,则hashcode一定也是相同的
两个对象相等,对两个对象分别调用equals方法都返回true
两个对象有相同的hashcode值,它们也不一定是相等的
因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖
hashCode() 的默认行为是对堆上的对象产生独特值如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)

hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。
        虽然,每个Java类都包含hashCode() 函数。但是,仅仅当创建并某个“类的散列表”(关于“散列表”见下面说明)时,该类的hashCode() 才有用(作用是:确定该类的每一个对象在散列表中的位置;其它情况下(例如,创建类的单个对象,或者创建类的对象数组等等),类的hashCode() 没有作用。
       上面的散列表,指的是:Java集合中本质是散列表的类,如HashMap,Hashtable,HashSet。

       也就是说:hashCode() 在散列表中才有用,在其它情况下没用。在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。

十三、 hashCode() 和 equals() 的关系

接下面,我们讨论另外一个话题。网上很多文章将 hashCode() 和 equals 关联起来,有的讲的不透彻,有误导读者的嫌疑。在这里,我自己梳理了一下 “hashCode() 和 equals()的关系”。

我们以“类的用途”来将“hashCode() 和 equals()的关系”分2种情况来说明。

1. 第一种 不会创建“类对应的散列表”

         这里所说的“不会创建类对应的散列表”是说:我们不会在HashSet, Hashtable, HashMap等等这些本质是散列表的数据结构中,用到该类。例如,不会创建该类的HashSet集合。

        在这种情况下,该类的“hashCode() 和 equals() ”没有半毛钱关系的!
        这种情况下,equals() 用来比较该类的两个对象是否相等。而hashCode() 则根本没有任何作用,所以,不用理会hashCode()。

下面,我们通过示例查看类的两个对象相等 以及 不等时hashCode()的取值。
运行结果:

p1.equals(p2) : true; p1(1169863946) p2(1901116749)
p1.equals(p3) : false; p1(1169863946) p3(2131949076)
从结果也可以看出:p1和p2相等的情况下,hashCode()也不一定相等。

2. 第二种 会创建“类对应的散列表”

        当类对象会在hash表数据结构中使用时,需要重写hashcode和equals方法。因为需要判断对象是否是同一个对象。这里所说的“会创建类对应的散列表”是说:我们会在HashSet, Hashtable, HashMap等等这些本质是散列表的数据结构中,用到该类。例如,会创建该类的HashSet集合。

        在这种情况下,该类的“hashCode() 和 equals() ”是有关系的:
        1)、如果两个对象相等,那么它们的hashCode()值一定相同。
              这里的相等是指,通过equals()比较两个对象时返回true。
        2)、如果两个对象hashCode()相等,它们并不一定相等。
               因为在散列表中,hashCode()相等,即两个键值对的哈希值相等。然而哈希值相等,并不一定能得出键值对相等。补充说一句:“两个不同的键值对,哈希值相等”,这就是哈希冲突。

        此外,在这种情况下。若要判断两个对象是否相等,除了要覆盖equals()之外,也要覆盖hashCode()函数。否则,equals()无效。
例如,创建Person类的HashSet集合,必须同时覆盖Person类的equals() 和 hashCode()方法。
        如果单单只是覆盖equals()方法。我们会发现,equals()方法没有达到我们想要的效果。
我们重写了Person的equals()。但是,很奇怪的发现:HashSet中仍然有重复元素:p1 和 p2。为什么会出现这种情况呢?

        这是因为虽然p1 和 p2的内容相等,但是它们的hashCode()不等;所以,HashSet在添加p1和p2的时候,认为它们不相等。
下面,我们同时覆盖equals() 和 hashCode()方法。
  这下,equals()生效了,HashSet中没有重复元素。
十四、java中只有值传递
可以举例证明;
总结
Java程序设计语言对对象采用的不是引用调用,实际上,对象引用是按 值传递的。

下面再总结一下Java中方法参数的使用情况:

一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)。
一个方法可以改变一个对象参数的状态。
一个方法不能让对象参数引用一个新的对象。
所以将一个基本数据类型的变量传递给一个方法,再想返回这个方法操作完成后的变量值,是不可能的。。。
十五、Java序列化中如果有些字段不想进行序列化,怎么办?
对于不想进行序列化的变量,使用transient关键字修饰。

transient关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被transient修饰的变量值不会被持久化和恢复。transient只能修饰变量,不能修饰类和方法。
十六、NIO\BIO
BIO (Blocking I/O): 同步阻塞I/O模式,传统IO,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。
NIO (New I/O): NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。NIO中的N可以理解为Non-blocking,不单纯是New。它支持面向缓冲的,基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发
十七、Java中的异常

在 Java 中,所有的异常都有一个共同的祖先java.lang包中的 Throwable类。Throwable: 有两个重要的子类:Exception(异常) 和 Error(错误) ,二者都是 Java 异常处理的重要子类,各自都包含大量子类。

Error(错误):是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。

这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如Java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java中,错误通过Error的子类描述。

Exception(异常):是程序本身可以处理的异常。Exception 类有一个重要的子类 RuntimeException。RuntimeException 异常由Java虚拟机抛出。NullPointerException(要访问的变量没有引用任何对象时,抛出该异常)、ArithmeticException(算术运算异常,一个整数除以0时,抛出该异常)和 ArrayIndexOutOfBoundsException (下标越界异常)。

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

Java异常类层次结构图

Throwable类常用方法

  • public string getMessage():返回异常发生时的简要描述
  • public string toString():返回异常发生时的详细信息
  • public string getLocalizedMessage():返回异常对象的本地化信息。使用Throwable的子类覆盖这个方法,可以声称本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与getMessage()返回的结果相同
  • public void printStackTrace():在控制台上打印Throwable对象封装的异常信息

异常处理总结

  • try 块: 用于捕获异常。其后可接零个或多个catch块,如果没有catch块,则必须跟一个finally块。
  • catch 块: 用于处理try捕获到的异常。
  • finally 块: 无论是否捕获或处理异常,finally块里的语句都会被执行。当在try块或catch块中遇到return 语句时,finally语句块将在方法返回之前被执行。

在以下4种特殊情况下,finally块不会被执行:

  1. 在finally语句块第一行发生了异常。 因为在其他行,finally块还是会得到执行
  2. 在前面的代码中用了System.exit(int)已退出程序。 exit是带参函数 ;若该语句在异常语句之后,finally会执行
  3. 程序所在的线程死亡。
  4. 关闭CPU。

下面这部分内容来自issue:https://github.com/Snailclimb/JavaGuide/issues/190

注意: 当try语句和finally语句中都有return语句时,在方法返回之前,finally语句的内容将被执行,并且finally语句的返回值将会覆盖原始的返回值。如下:

    public static int f(int value) {
        try {
            return value * value;
        } finally {
            if (value == 2) {
                return 0;
            }
        }
    }

如果调用 f(2),返回值将是0,因为finally语句的返回值覆盖了try语句块的返回值。try块中return之前会先执行finally,finally块return的值会覆盖try中的值。

十八、Java IO流

Java I0流的40多个类都是从如下4个抽象类基类中派生出来的。

 

static 关键字

static 关键字主要有以下四种使用场景:

 

  • InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
  • OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
  • 既然有了字节流,为什么还要有字符流?

    问题本质想问:不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?

    回答:字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还算是非常耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。所以, I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。

  • final 关键字

    final关键字主要用在三个地方:变量、方法、类。

  • 对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。

  • 当用final修饰一个类时,表明这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法。

  • 使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为final。

  • 修饰成员变量和成员方法: 被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。调用格式:类名.静态变量名 类名.静态方法名()
  • 静态代码块: 静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法)。 该类不管创建多少对象,静态代码块只执行一次.
  • 静态内部类(static修饰类的话只能修饰内部类): 静态内部类与非静态内部类之间存在一个最大的区别: 非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:1. 它的创建是不需要依赖外围类的创建。2. 它不能使用任何外围类的非static成员变量和方法。
  • 静态导包(用来导入类中的静态资源,1.5之后的新特性): 格式为:import static 这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法。

 

Collections 工具类和 Arrays 工具类常见方法

  • Collections
    • 排序操作
    • void reverse(List list)//反转
      void shuffle(List list)//随机排序
      void sort(List list)//按自然排序的升序排序
      void sort(List list, Comparator c)//定制排序,由Comparator控制排序逻辑
      void swap(List list, int i , int j)//交换两个索引位置的元素
      void rotate(List list, int distance)//旋转。
    • 查找,替换操作
    • int binarySearch(List list, Object key)//对List进行二分查找,返回索引,注意List必须是有序的
      int max(Collection coll)//根据元素的自然顺序,返回最大的元素。 类比int min(Collection coll)
      int max(Collection coll, Comparator c)//根据定制排序,返回最大元素,排序规则由Comparatator类控制。类比int min(Collection coll, Comparator c)
      void fill(List list, Object obj)//用指定的元素代替指定list中的所有元素。
      int frequency(Collection c, Object o)//统计元素出现次数
      int indexOfSubList(List list, List target)//统计target在list中第一次出现的索引,找不到则返回-1,类比int lastIndexOfSubList(List source, list target).
      boolean replaceAll(List list, Object oldVal, Object newVal), 用新元素替换旧元素
    • 同步控制
    • Collections提供了多个synchronizedXxx()方法·,该方法可以将指定集合包装成线程同步的集合,从而解决多线程并发访问集合时的线程安全问题。

      我们知道 HashSet,TreeSet,ArrayList,LinkedList,HashMap,TreeMap 都是线程不安全的。Collections提供了多个静态方法可以把他们包装成线程同步的集合。

      最好不要用下面这些方法,效率非常低,需要线程安全的集合类型时请考虑使用 JUC 包下的并发集合。

  • Arrays类的常见操作
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值