Java面试题

Java面试题

基础面试题

1、什么是面向对象?

相对于面向过程,是两种不同的处理问题的角度

面向过程更注重事情的每一个步骤及顺序,面向对象更注重事情有哪些参与者、及各自需要做什么

比如:洗衣机洗衣服

面向过程会将任务拆解成一系列的步骤:

1、打开洗衣机----》2、放衣服----》3、放洗衣粉----》4、清洗----》5、烘干

面向对象会拆出人和洗衣机两个对象:

人:打开洗衣机 放衣服 放洗衣粉

洗衣机:清洗 烘干

面向过程比较直接高效,而面向对象更易于复用、扩展和维护。

面向对象三大特性

封装:封装的意义,在于明确标识出允许外部使用的所有成员函数和数据项,内部细节对外部调用透明,外部调用无需修改或者关心内部实现。

1、javabean的属性私有,提供get和set对外访问,因为属性的赋值或者逻辑只能由javabean本身决定。而不能由外部胡乱修改。

继承:继承基类的方法,并做出自己的改变或者扩展

子类直接使用父类的方法,而不需要自己再定义,只需扩展自己个性化的东西

多态:基于对象所属类的不同,外部对同一方法的调用,实际执行的逻辑不同。

条件:继承、方法重写、父类引用指向子类对象

弊端:父类无法调用子类特有的功能

 

2、JDK、JRE、JVM三者的联系与区别

JDK:

Java Development Kit java开发工具

JRE:

Java Runtime Environment java运行时环境

JVM:

java Virtual Machine java虚拟机

JVM:充当“翻译官”将字节码文件翻译为当前操作系统可以执行的文件格式

将java文件即源文件编译成class文件即字节码文件使用javac命令,执行class文件即字节码文件使用java命令,其底层会动态的调用JVM。JVM将字节码文件一行一行的解释为当前操作系统可执行的文件,因此java也可以称之为“解释型”语言。实现一次编译到处运行。

image-20210706205513883

3、==和equals的区别

==比较的是值

比较基本的数据类型,比较的是栈中的变量值

比较引用类型:比较的是堆中内存对象的地址

equals

默认比较也是采用==比较,因为这个方法的最初定义在Object上,默认的实现就是比较地址,通常会重写

自定义的类,如果需要比较的是内容,就需要重写equals方法。

String类中被复写的equals()方法其实是比较两个字符串的内容

 package com.wkf;
 
 public class Test {
     public static void main(String[] args) {
         String s1 = new String("zs");
         String s2 = new String("zs");
         System.out.println(s1 == s2);//false
         String s3 = "zs";
         String s4 = "zs";
         System.out.println(s3 == s4);//true
         System.out.println(s3 == s1);//false
         String s5 = "zszs";
         String s6 = s3 + s4;
         System.out.println(s5 == s6);//false
         final String s7 = "zs";
         final String s8 = "zs";
         System.out.println(s7 == s8);//true
         String s9 = s7 + s8;
         System.out.println(s5 == s9);//true
         final String s10 = s3+s4;
         System.out.println(s5 == s10);//false
    }
 }

 

4、final的作用

final修饰符,最终的

  • 修饰类:表示类不可变,不可继承

    比如,String,不可变性

  • 修饰方法,表示该方法不可重写,不可被子类覆盖,但是可以重载

    比如模板方法,可以固定我们的算法

  • 修饰变量,这个变量就是常量

(1)修饰成员变量

  • 如果final修饰的是类变量,只能在静态初始化块中指定初始值或声明该类变量时指定初始值

  • 如果final修饰的是成员变量,可以在非静态初始化块、声明该变量或者构造器中执行初始值

(2)修饰局部变量

  • 系统不会为局部变量进行初始化,局部变量必须由程序员显示初始化。因此使用final修饰局部变量时,即可以在定义时指定默认值(后面的代码不能对变量再赋值),也可以不指定默认值,而在后面的代码中对final变量赋值(仅一次)

(3)修饰基本类型数据和引用类型数据

  • 如果是基本数据类型的变量,则其数值一旦在初始化之后便不能修改。

  • 如果是引用数据类型的变量,则在对其初始化之后便不能在让其指向另一个对象,但是引用得值是可变的

注意:

修饰的是基本数据类型,这个值本身不能修改。

修饰的是引用类型,引用的指向不能修改,但是其中的值可以修改

比如:

 final Student student = new Student(1,"jack");
 student.setAge(18);//可行
 student = new Student//不可行

 

5、String,StringBuffer,StringBuilder的区别

String是final类型

  • 每次声明的都是不可变的对象,所以每次操作都会产生新的String对象,然后将指针指向新的String对象。

  • 字符串不可变,因此字符串可以共享,例如:String str1 = “abc”,String str2 = “abc”是相同的,他们公用一个对象

  • JDK1.8字符串的底层实现是字符数组(char[]),JDK1.9开始底层实现改成了字节数组(byte[])

StringBuffer,StringBulider都是在原有对象上进行操作,所以如果需要经常改变字符串内容,则建议采用这两者。

注意:两者均没有实现equals方法,所欲不可以直接比较,需要将他们转成String类型进行比较

StringBuffer,StringBulider的区别

StringBuffer是线程安全的

StringBuffer方法都是synchronized修饰的

StringBulider是线程不安全的,线程不安全的性能更高。所以在开发中,优先采用StringBulider,多线程使用共享变量时使用StringBuffer。

性能:StringBulider > StringBuffer > String

 

6、重载和重写的区别

重载:发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时

重写:发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为private则子类就不能重写该方法。

 

7、接口和抽象类的区别

  • 抽象类可以存在普通成员函数

  • 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的

  • 抽象类只能继承一个,接口可以实现多个

接口的设计目的,是对类的行为进行约束(更准确的说是一种有约束,因为接口不能规定类不可以有什么行为),也就是提供一种机制,可以强制要求不同的类具有相同的行为。他只约束了行为的有无,但不对如何实现行为限制

而抽象类的设计目的,是代码复用。当不同的类具有某些相同的行为(记为行为集合A),且其中一部分行为的实现方式一致时(A的非真子集,记为B),可以让这些类都派生于一个抽象类。在这个抽象类中实现了B,避免让所有的子类来实现B,这就达到了代码复用的目的。而A减B的部分,留给各个子类自己实现。

使用场景:当你关注一个事物的本质的时候,用抽象类;当你关注一个操作的时候,用接口。

 

8、List和Set的区别

  • List:有序,可重复,按对象进入的顺序保存对象,允许多个null元素对象,可以使用iterator取出所有元素,在逐一遍历,还可以使用get(int index)获取指定下标的元素

  • Set:无序,不可重复,最多允许有一个null元素对象,取元素时只能用iterator接口去的所有元素,在逐一遍历各个元素

 

9、hashCode与equals

什么是hashCode?

hashCode()的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode()定义在JDK的Object.java中,java中的任何类都包含hashCode()函数。

散列表存储的是键值对(key-value),他的特点是:能根据”键“快速的检索出对应的值。这其中就利用了散列码。

为什么要有hashCode

以HashSet如何检索重复为例子来说明为什么要有hashCode:

对象加入HashSet是,会先计算对象的hashCode值来判断对象加入的位置,看该位置是否有值,如果没有、HashSet会假设对象没有重复出现。但是如果发现有值,这时会调用equals()方法来检查两个对象是否真的相同。如果两者相同,HashSet就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样就大大减少equals的次数,相应就大大提高了执行速度。

  • 如果两个对象相同,则hashcode一定也是相同的

  • 两个对象相同,对两个对象分别调用equals方法返回true

  • 两个对象有相同的hashcode值,他们也不一定是相等的

  • 因此,equals方法被覆盖过,则hashCode方法也必须被覆盖

  • hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等。

 

10、ArrayList和LinkedList区别,ArrayList、LinkedList、HashMap的初始大小以及如何扩容

ArrayList

  • 基于动态数组连续内存存储,适合下标访问(随机访问)

  • 底层数据结构为线性表、顺序结构

  • 初始容量:

    • 调用无参构造时,初始容量为0,在添加元素时初始化,初始容量变为10

    • 调用有参构造时,初始容量为参数的值,如果参数为1,那么下一次扩容为2,之后扩容增加原长度的一半

  • 扩容机制:因为数组长度固定,超出长度存数据时需要新建数组(为原来数组的1.5倍),然后将老数组的数据拷贝到新数组。如果不是尾部插入数据还会涉及到元素的移动(往后复制一份,插入新元素),使用尾插法并指定初始容量可以极大提升性能、甚至超过LinkedList(需要创建大量的node对象)

LinkedList

  • 基于双向链表(通过Node节点实现),可以存储在分散的内存中,适合做数据插入及删除操作,不适合查询;

  • 底层数据结构为线性表、链式结构

  • 没有初始容量,初始化时头尾节点都是null

  • 扩容方式:头插法、尾插法、中间插入

  • 需要逐一遍历。遍历LinkedList必须使用iterator不能使用for循环,因为每次for循环体内通过get(i)取得某一元素时都需要对list重新进行遍历,性能消耗极大。另外不要试图用indexof等返回元素索引,并利用其进行遍历,使用indexof对list进行了遍历,当结果为空时会遍历整个列表。

 

11、HashMap和HashTable的区别?底层实现是什么?

HashMap

  • 底层实现:散列表(也称哈希表):包含存储节点的数组(称之为桶)以及由存储在这个数组中的节点引出的链表或者红黑树

  • 底层数据结构:

    • JDK1.7数组+链表(使用头插法)

    • JDK1.8数组+链表(使用尾插法),当链表高度到8、数组长度超过64,链表转变为红黑树,元素以内部类node节点存在

  • 初始容量:调用构造方法(参数尾Map的构造方法除外)是没有初始化的,所有此时容量可以看成0,当添加元素时才初始化。默认初始容量为16,负载因子为0.75(即容量达到75%的时候扩容)

  • 扩容方式:当元素个数达到临界值时,创建一个比原来桶大一倍的桶,然后遍历老桶(即老数组)以及他之中节点之后的所有节点,计算这些新节点的新位置(因为计算节点存储位置时使用了桶的大小,所以节点位置改变)存储到新桶里。之后重新计算新的临界值。

  • 计算key的hash值,二次hash然后对数组长度进行取模,对应到数组下标,如果没有产生hash冲突(下标位置没有元素),则直接创建node存入数组。如果产生hash冲突,先进行equal比较,相同则取代该元素,不同,则判断链表高度插入链表,链表高度达到8,并且数组长度到64则转变为红黑树,长度低于6则将红黑树转回链表

  • key为null,存在下标0的位置

 

区别:

  1. HashMap方法没有synchronized修饰,线程不安全,但是效率高。HashTable线程安全

  2. HashMap允许key和value为null,而HashTable都不允许为空

  3. HashMap继承了AbstractMap,HashTable继承了Dictionary抽象类,两者均实现了Map接口

  4. HashMap扩容是一倍,HashTable则是扩容一倍加1

  5. HashMap默认初始容量为16,在添加元素时初始化,HashTable初始容量为11,在构造方法中初始化

  6. HashMap时JDK1.2出现的,HashTable时JDK1.0

 

 

12、ConcurrentHashMap原理,jdk7和jdk8版本的区别

jdk7:

数据结构:ReentrantLock+Segment+HashEntry,一个Segment中包含一个HashEntry数组,每个HashEntry又是一个链表结构

元素查询:二次hash,第一次Hash定位到Segment,第二次Hash定位到元素所在的链表的头部

锁:Segment分段锁,Segment继承了ReentrantLock,锁定操作的Segment,其他的Segment不受影响,并发度为segment个数,可以通过构造函数指定,数组扩容不会影响其他的segment

get方法无需加锁,volatile保证

jdk8:

数据结构:synchronized+CAS+Node——红黑树,Node的val和next都用volatile修饰,保证可见性

查找,替换,赋值操作都是用CAS

锁:锁链表的head节点,不影响其他元素的读写,锁粒度更细,效率更高,扩容时,阻塞所有的读写操作,并发扩容

读操作无锁:Node的val和next使用volatile修饰,读写线程对该变量互相可见,数组用volatile修饰,保证扩容时被线程感知

 

13、如何实现一个IOC容器

  1. 配置文件配置包扫描路径

  2. 递归包扫描获取.class文件

  3. 反射、确定需要给IOC管理得类

  4. 对需要注入的类进行依赖注入

 

  • 配置文件中指定需要扫描的包路径

  • 定义一些注解,分别表示访问控制层、业务服务层、数据持久层、依赖注入注解、获取配置文件注解

  • 从配置文件中获取需要扫描的包路径,获取到当前路径下的文件信息及文件夹信息,我们将当前路径下所有以.class结尾的文件添加到一个Set集合中进行存储

  • 遍历这个set集合,获取在类上有指定注解的类,并将其交给IOC容器,定义一个安全的Map用来存储这些对象

  • 遍历这个IOC容器,获取到每一个类的实例,判断里面是否有依赖其他的类的实例,然后进行递归注入

 

14、什么是字节码,采用字节码的好处是什么

Java中的编译器和解析器:

Java中引入虚拟机的概念,即在机器和编辑程序之间加入了一层抽象的虚拟的机器。这台虚拟的机器在任何平台上都提供给编译程序一个共同的接口。

编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在Java中,这种供虚拟机理解的代码叫做字节码(即扩展名为.class文件),他不面向任何特定的处理器,只面向虚拟机。

采用字节码的好处:

Java语言通过字节码的方式,在一定程度上解决了传统解析型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以Java程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,java程序无序重新编译便可在多种不同的计算机上运行。

 

15、Java类加载器

JDK自带有三个类加载器:BootStrapClassLoader、ExtClassLoader、AppClassLoader

BootStrapClassLoader是ExtClassLoader的父类加载器,默认负责加载%JAVA_HOME%lib下的jar包和class文件

ExtClassLoader是AppClassLoader的父类加载器,负责加载%JAVA_HOME%/lib/ext文件夹下的jar包和class类。

AppClassLoader是自定义类加载器的父类,负责加载classpath下的类文件。系统类加载器,线程上下文加载器

继承ClassLoader实现自定义类加载器

 

16、双亲委派模型

image-20210810183650236

双亲委派模型的好处:

  • 主要是为了安全性,避免用户自己编写的类动态替换Java的一些核心类,比如String

  • 同时也避免了类的重复加载,同时JVM中区分不同类,不仅仅是根据类名,相同的class文件被不同的ClassLoader加载就是不同的两个类

 

17、Java中的异常体系

Java中的所有异常都来自顶级父类Throwable

Throwable下有两个子类Exception和Error

Error是程序无法处理的错误,一旦出现这个错误,则程序将被迫停止运行。

Exception不会导致程序停止,又分为两个部分RunTimeException运行时异常和CheckedException检查异常。

RunTimeException常常发生在程序运行过程中,会导致程序当前线程执行失败。CheckedException常常发生在程序编译过程中,会导致程序编译不通过。

 

18、GC如何判断对象可以被回收

image-20210810185115067

 

19、线程的生命周期和状态

image-20210810185322167

image-20210810185332947

 

20、sleep,wait,join,yield的区别

image-20210810185936702

image-20210810185947741

image-20210810190234487

 

21、Object类自带哪些方法

  • toString() 返回这个对象的字符串表示

  • hashCode()返回这个对象的哈希值

  • equals(Object obj)比较两个对象是否相等

  • wait()这个方法有三个重载

    • 没有参数时让当前线程等待,造成当前线程阻塞,直到被其他线程唤醒

    • 一个参数是让当前线程等待,直到线程被其他线程唤醒或者时间超过这个参数所代表的毫秒数

    • 两个参数时与一个参数类似,第二个参数表示微秒数

    • notify() 唤醒在这个对象监听器上等待的单个线程,如果有多个线程等待唤醒其中一个

    • notifyAll() 唤醒在这个对象监听器上等待的所有线程

    • finalize() (过时):在这个对象要被垃圾回收器(GC)清理时调用的方法

    • clone() 复制这个对象,必须实现Cloneable接口(空实现,标记这个类是可克隆的),克隆方式是深克隆

    • getClass() 获取当前对象的字节码文件

 

22、Collection和Collections的区别

Collection是最基本的集合接口,有List、Set、Queue等,而Collections是集合的工具类,其中包含集合排序等方法。

 

23、Set中的元素不可重复,那么用什么方法来区分重复与否

  • HashSet:底层实现是散列表(数组+链表),使用hashCode+equals方法进行区分。插入元素时首先通过hashCode方法获取这个元素的哈希值,再通过哈希值获取元在数组中的位置(地址值)。如果这个位置没有元素那么就直接插入,反之通过equals方法与这个位置链表上所有的元素比较,如果没有相同的就插入到该位置链表的尾部,如果有相同元素就不做处理

  • TreeSet:底层实现是二叉树,使用compareTo方法,插入元素时首先通过compareTo方法与根节点比较,如果比根节点,判断右节点是否存在,如果存在以右节点为根节点,如果不存在将这个元素存为这个根节点的右节点,如果比根节点小那么就与前者相反,如果一样大就不做处理

 

 

 

 

 

 

 

 

 

 

 

 

Spring面试题

1、Spring是什么?

轻量级的开源的j2EE框架。它是一个容器框架,用来装javabean(java对象),中间层框架(万能胶)可以起一个连接作用,比如说把Struts和hibernate粘合在一起运用,可以让我们的企业开发更快、更简洁

Spring是一个轻量级的控制反转(IOC)和面向切面(AOP)的容器框架

  1. 从大小与开销两方面而言spring都是轻量级的

  2. 通过控制反转(IOC)的技术达到松耦合的目的

  3. 提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务进行内聚性的开发

  4. 包含并管理应用对象(bean)的配置和生命周期,这个意义上是一个容器

  5. 将简单的组件配置、组合成为复杂的应用,这个意义上是一个框架

 

2、对AOP的理解

在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

AOP:将程序中的交叉业务逻辑(比如日志,事务等),封装成一个切面,然后注入到目标对象(具体业务逻辑)中去。AOP可以对某个对象或者某些对象的功能进行增强,比如对象中的方法进行增强,可以在执行某个方法之前额外的做一些事情,在某个方法执行之后额外的做一些事情。

 

3、对IOC的理解

image-20210813193901897

image-20210813194051224

 

 

 

 

 

 

 

 

 

 

笔试题

1、运行下面代码,输出的结果是()

 class A {
     public A() {
         System.out.println("class A");
    }
    { System.out.println("I'm A class"); }
     static { System.out.println("class A static"); }
 }
 public class B extends A {
     public B() {
         System.out.println("class B");
    }
    { System.out.println("I'm B class"); }
     static { System.out.println("class B static"); }
     
     public static void main(String[] args) {
  new B();
  }
 }

答案

 class A static
 class B static
 I'm A class
 class A
 I'm B class
 class B

Java程序初始化顺序:

  1. 父类的静态对象、静态代码块

  2. 子类的静态对象、静态代码块

  3. 父类的普通成员变量和普通代码块

  4. 父类的构造方法

  5. 子类的普通成员变量和普通代码块

  6. 子类的构造方法

2、以下代码执行后输出结果为( )

 public class ClassTest{
      String str = new String("hello");
      char[] ch = {'a','b','c'};
      public void fun(String str, char ch[]){
      str="world";
      ch[0]='d';
  }
  public static void main(String[] args) {
      ClassTest test1 = new ClassTest();
      test1.fun(test1.str,test1.ch);
      System.out.print(test1.str + " and ");
      System.out.print(test1.ch);
      }
  }

答案

 hello and dbc

3、关于下面程序运行结果

 public class ThisTest {
      public static void main(String args[]) {
           String x="7";      
        int y = 2;
           int z=2;              
        System.out.println(x+y+z);
      }  
 }

 

img

 

4、volatile,synchronized的区别

synchronized: 具有原子性,有序性和可见性,可以修饰方法以及代码块 volatile:具有有序性和可见性(不能保证原子性,也就不能保证线程安全),只能用于变量

  • volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized关键字要好。synchronized关键字在JavaSE1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后执行效率有了显著提升,实际开发中使用 synchronized 关键字的场景还是更多一些。

  • 多线程访问volatile关键字不会发生阻塞,而synchronized关键字可能会发生阻塞

  • volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized关键字解决的是多个线程之间访问资源的同步性

 

5、排序算法时间复杂度

image-20210813200517492

6、线程的方法

下列方法中哪个是执行线程的方法?

(run方法)

  1. run()方法用来执行线程体中具体的内容

  2. start()方法用来启动线程对象,使其进入就绪状态

  3. sleep()方法用来使线程进入睡眠状态

  4. suspend()方法用来使线程挂起,要通过resume()方法使其重新启动

 

Java工程师面试宝典

1、Java基础(一)

1、简单描述一下正则表达式及其用途

在编写处理字符串的程序时,常常需要进行查找某些符合复杂规则字符串的需要,而正则表达式就是用于描述这些规则的工具。换句话说,正则表达式就是记录文本规则的代码。正则表达式是进行字符串匹配和处理的时候最为强大的工具。

 

2、谈谈Java是如何支持正则表达式操作的

Java中的String类提供了正则表达式的操作方法,包括:matches()、replaceAll()、replaceFirst()、split()。此外,Java中还可以用Pattern类表示正则表达式对象,它提供了丰富的API进行各种正则表达式操作。

 

3、在Java中如何跳出当前的多重嵌套循环

在最外层循环前添加一个标签,如flag,再通过break flag跳出当前多重循环。但一般不建议使用带标签的break和continue,因为它不会让你的程序更优雅,甚至有相反的作用。

 

4、请你讲讲&和&&的区别

1、&和&&都可以用作逻辑与的运算符,表示逻辑,当运算符两边的表达式的结果都为true时,整个运算结果才为true,否则,只要有一方为false,则结果为false。

2、&&还具有短路的功能,即如果第一个表达式为false,则不再计算第二个表达式。

例如,对于if(str != null && !str.equals(“”))表达式,当str为null时,后面的表达式不会执行,所以不会出现NullPointerException,如果将&&改为&,则会抛出NullPointerException异常。

3、&还可以用作位运算符,当&操作符两边的表达式不是boolean类型时,&表示按位与操作,我们通常使用0x0f来与一个整数进行&运算,来获取该整数的最低4个bit位,例如,0×31 & 0x0f的结果为0×01。

 

5、 int和Integer有什么区别

Integer是int的包装类,从Java5开始引入了自动装箱/拆箱机制,使得二者可以相互转换。如:

   Integer a = new Integer(3);
         Integer b = 3;                  // 将3自动装箱成Integer类型
         int c = 3;
         System.out.println(a == b);     // false 两个引用没有引用同一对象
         System.out.println(a == c);     // true a自动拆箱成int类型再和c比较

当引用类型和原始类型用作某个类的实例数据时所指定的缺省值。对象引用实例变量的缺省值为null,而原始类型实例变量的缺省值与它们的类型有关,如int的为0;

 

6、我们在web应用开发过程中经常遇到输出某种编码的字符,如iso8859-1等,请你讲讲如何输出一个某种编码的字符串?

Public String translate (String str) {
String tempStr = “”;
try {
tempStr = new String(str.getBytes(“ISO- 8859-1″));
tempStr = tempStr.trim();
}
catch (Exception e) {
System.err.println(e.getMessage());
}
return tempStr;
}

 

2、Java基础(二)

2.1、请你讲讲数组(Array)和列表(ArrayList)的区别?什么时候应该使用Array而不是ArrayList?

Array可以包含基本类型和对象类型,ArrayList只有对象类型。

Array大小是固定的,ArrayList的大小是动态变化的。 ArrayList提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。 对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。

 

2.2、 请你解释什么是值传递和引用传递?

值传递是对基本类型变量而言的,方法调用时,实际参数把它的传递给对应的形式参数,后面的方法中的操作都是对这个形参这个值的修改,不影响实际参数的值。

引用传递是将实际参数的引用地址传递给方法中对应的形式参数。在方法执行中,形参和实参内容相同,指向同一块地址,方法执行中对引用的操作将会影响到实际对象

StringIntegerDouble等immutable的类型特殊处理,可以理解为传值,最后的操作不会修改实参对象

一般认为java内的传递都是值传递

 

2.3、 为什么会出现4.0-3.6=0.40000001这种现象?

2进制小数无法精确的表达10进制小数,计算机在计算十进制小数的过程中要先转换为2进制进行计算,这个过程中出现了误差

 

2.4、一个十进制的数在内存中是怎么存的?

整形数据在内存中是以2进制数的补码存在的(是以进制补码形式存储),最高位是符号位(0是正数,1是负数),正数的补码是他的原码负数的补码是他的反码加1,在求反码时符号位不变,其他位取反。

 

2.5、transient变量和下面哪一项有关

Serializable: 和序列化有关,transient关键字修饰的变量不能被序列化,static变量不管加没加transient都不可以被序列化

 

2.6、java运行时内存分为“线程共享”和“线程私有”两部分

线程共享:方法区,java堆

线程私有:java虚拟机栈,程序计数器,本地方法栈

 

2.7、Java中,HashMap是用哪个方法来解决哈希冲突的

链地址法

 

Java8新特性

  • Lambda 表达式 − Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中。

  • 方法引用− 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。

  • 默认方法− 默认方法就是一个在接口里面有了一个实现的方法。

  • 新工具− 新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。

  • Stream API −新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。

  • Date Time API − 加强对日期与时间的处理。

  • Optional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。

  • Nashorn, JavaScript 引擎 − Java 8提供了一个新的Nashorn JavaScript引擎,它允许我们在JVM上运行特定的JavaScript应用。

菜鸟教程链接:https://www.runoob.com/java/java8-new-features.html

1、Lambda表达式

优点:

  • 允许把函数作为一个方法的参数(函数作为参数传递进方法中)

  • 代码简洁紧凑,非常容易并行计算,可能代表未来的编程趋势

  • Lambda表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力

缺点:

  • 若不用并行计算,很多时候计算速度没有比传统的for循环快(并行计算有时需要预热才显示出效率优势)

  • 不容易调试

  • 若其他程序员没有学过lambda表达式,代码难以理解

  • 测试代码:

final  static String str = "Hello";
public static void main(String[] args) {
LambdaTest test = new LambdaTest();
MathOperation add = (int a,int b) -> a+b;
MathOperation sub = (a,b) -> a-b;
MathOperation mul = (int a,int b) ->{return a*b;};
GreetingService gre = (message) -> System.out.println("Hello" + message);
gre.sayMessage("wkf");
System.out.println(test.operate(10,5,add));
System.out.println(test.operate(10,5,sub));
System.out.println(test.operate(10,5,mul));

GreetingService gs = message ->
System.out.println(str + message);
gs.sayMessage("wkf");
}

interface MathOperation{
int operation(int a, int b);
}
interface GreetingService {
void sayMessage(String message);
}

private int operate(int a, int b, MathOperation mathOperation){
return mathOperation.operation(a, b);
}

 

2、方法引用

  • 方法引用通过方法的名字来指向一个方法

  • 方法引用可以使语言的构造更紧凑简介,减少冗余代码

  • 方法引用使用一对冒号::

  • 构造器引用:它的语法是Class::new,或者更一般的Class< T >::new实例如下:

  • final Car car = Car.create( Car::new ); final List< Car > cars = Arrays.asList( car );
  • 静态方法引用:它的语法是Class::static_method,实例如下:

    cars.forEach( Car::collide );
  • 特定类的任意对象的方法引用:它的语法是Class::method实例如下:

  • cars.forEach( Car::repair );
  • 特定对象的方法引用:它的语法是instance::method实例如下:

    final Car police = Car.create( Car::new ); cars.forEach( police::follow );
    public static void main(String[] args) {
    List<String> names1 = new ArrayList<String>();
    names1.add("Google ");
    names1.add("RunOob ");
    names1.add("TaoBao ");
    names1.add("Baidu ");
    names1.add("Sina ");
    for (String s : names1) {
    System.out.print(s);
    }
    System.out.println();
    Collections.sort(names1);
    //System.out::print方法作为静态方法来引用
    names1.forEach(System.out::print);
    }

     

3、函数式接口

定义:函数式接口是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口

  • 函数式接口可以被隐式的转换为lambda表达式

  • Java1.8新增的函数接口:java.util.function,其中有许多类用来支持java的函数式编程

测试:

//Predicate<T>接受一个输入参数,返回一个布尔值结果。
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10,11,12);
// Predicate<Integer> predicate = n -> true
// n 是一个参数传递到 Predicate 接口的 test 方法
// n 如果存在则 test 方法返回 true
eval(list,n-> n%3==0);
}

public static void eval(List<Integer> list, Predicate<Integer> predicate
){
for (Integer n : list) {
if (predicate.test(n)){
System.out.print(n + " ");
}
}
}

 

4、Java 8 默认方法

定义:默认方法就是接口可以有实现方法,而且不需要实现类去实现其方法,我们只需要在方法名前面加个default关键字即可实现默认方法

Java8的另一个特性是接口可以声明(并且可以提供实现)静态方法

例如:

public interface Vehicle {
default void print(){
System.out.println("我是一辆车!");
}
// 静态方法
static void blowHorn(){
System.out.println("按喇叭!!!");
}
}

默认方法的使用

public static void main(String args[]){
Vehicle vehicle = new Car();
vehicle.print();
}
}

interface Vehicle {
default void print(){
System.out.println("我是一辆车!");
}

static void blowHorn(){
System.out.println("按喇叭!!!");
}
}

interface FourWheeler {
default void print(){
System.out.println("我是一辆四轮车!");
}
}

class Car implements Vehicle, FourWheeler {
public void print(){
Vehicle.super.print();
FourWheeler.super.print();
Vehicle.blowHorn();
System.out.println("我是一辆汽车!");
}

 

5、Stream(流)

  • Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据

  • Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码

  • 这种风格将要处理的元素集合看成一种流,流在管道中传输,并且可以在管道的节点上进行处理,比如筛选、排序、聚合。

Stream:流是一个来自数据源的元素队列支持聚合操作

  • 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。

  • 数据源:可以是集合,数组,I/O channel, 产生器 generator 等。

  • 聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。

Stream操作特性:

  • Pipelining:中间操作都会返回流对象本身,这样多个操作可以串联成一个管道,如同流式风格。这样做可以对操作进行优化,如延迟执行和短路

  • 内部迭代:以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。Stream提供了内部迭代的方式,通过访问者模式(Visitor)实现

常用API:

  • 生成流:

    • stream() -- 为集合创建串行流

    • parallelStream() -- 为集合创建并行流。

  • forEach:迭代流中的每个数据

  • map:用于映射每个元素到对应的结果

  • filter:用于通过设置的条件过滤出元素

  • limit:用于获取指定数量的流

  • sorted:用于对流进行排序

  • Collectors:实现了很多归约操作,可用于返回列表或字符串

  • summaryStatistics:一个产生统计结果的收集器,可以计算平均值,最大值,最小值等

测试代码:

public static void main(String[] args) {
List<String> strings = Arrays.asList("aaa","bbb","def","cc"," ","gh","abc" );
List<String> filtered = strings.stream().filter(
string ->!string.isEmpty()).collect(Collectors.toList());
System.out.println(filtered);
Random random = new Random();
random.ints().limit(3).forEach(System.out::println);
List<Integer> numbers = Arrays.asList(1,2,3,6,7,9,1,5,2);
List<Integer> collect = numbers.stream().map(i -> i * i *2).distinct().sorted().collect(Collectors.toList());
System.out.println(collect);
long count = numbers.stream().filter(number -> number >= 3).count();
System.out.println(count);
IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println(stats.getMax());
System.out.println(stats.getMin());
System.out.println(stats.getSum());
System.out.println(stats.getAverage());
Map<Integer,String> map = new HashMap<>();
map = strings.stream().collect(Collectors.toMap(str -> strings.indexOf(str),str->str));
map.forEach((key,value) ->{
System.out.println(key + ":" +value);
});
System.out.println(strings.stream().collect(Collectors.joining(", ")));
}

 

6、Optional类

  • 是一个可以为null的容器对象,如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象

  • 它可以保存T类型的值,或者仅仅保存null。他提供了很多有用的方法,这样我们就不用显式进行空值检查,它的引入很好的解决了空指针异常

  • 测试类:

public static void main(String[] args) {
OptionalTest optional = new OptionalTest();
Integer value1 = null;
Integer value2 = new Integer(10);
Optional<Integer> a = Optional.ofNullable(value1);

Optional<Integer> b = Optional.of(value2);
System.out.println(optional.sum(a,b));
}
public Integer sum(Optional<Integer> a, Optional<Integer> b){
System.out.println("1 :" + a.isPresent());
System.out.println("2 :" + b.isPresent());
Integer value1 = a.orElse(new Integer(0));
Integer value2 = b.get();
return value1 + value2;
}

 

7、Nashorn 一个JavaScript引擎

  • Nashorn JavaScript Engine 在 Java 15 已经不可用了。

  • 从 JDK 1.8 开始,Nashorn取代Rhino(JDK 1.6, JDK1.7) 成为 Java 的嵌入式 JavaScript 引擎。Nashorn 完全支持 ECMAScript 5.1 规范以及一些扩展。它使用基于 JSR 292 的新语言特性,其中包含在 JDK 7 中引入的 invokedynamic,将 JavaScript 编译成 Java 字节码。与先前的 Rhino 实现相比,这带来了 2 到 10倍的性能提升。

 

8、Java 8 日期时间API

旧版本的日期API的问题:

  • 非线程安全 − java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。

  • 设计很差 − Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java. Text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。

  • 时区处理麻烦 − 日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题

Java 8 在java.time包下提供了很多新的API:

  • Local(本地) − 简化了日期时间的处理,没有时区的问题。

  • Zoned(时区) − 通过制定的时区处理日期时间。

新的java.time包涵盖了所有处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操作。

测试:

public static void main(String[] args) {
new LocalDateTest().testLocalDateTime();
}
public void testLocalDateTime(){
LocalDateTime currentTime = LocalDateTime.now();
System.out.println(currentTime);
LocalDate date = currentTime.toLocalDate();
System.out.println(date);
Month month = currentTime.getMonth();
int day = currentTime.getDayOfMonth();
int seconds = currentTime.getSecond();
System.out.println("月: " + month +", 日: " + day +", 秒: " + seconds);
LocalDateTime date2 = currentTime.withDayOfMonth(10).withYear(2012);
System.out.println("date2: " + date2);
// 12 december 2014
LocalDate date3 = LocalDate.of(2014, Month.DECEMBER, 12);
System.out.println("date3: " + date3);

// 22 小时 15 分钟
LocalTime date4 = LocalTime.of(22, 15);
System.out.println("date4: " + date4);

// 解析字符串
LocalTime date5 = LocalTime.parse("20:15:30");
System.out.println("date5: " + date5);
}
public void testZonedDateTime(){

// 获取当前时间日期
ZonedDateTime date1 = ZonedDateTime.parse("2015-12-03T10:15:30+05:30[Asia/Shanghai]");
System.out.println("date1: " + date1);

ZoneId id = ZoneId.of("Europe/Paris");
System.out.println("ZoneId: " + id);

ZoneId currentZone = ZoneId.systemDefault();
System.out.println("当期时区: " + currentZone);
}

 

9、Java 8 Base64

在Java 8中,Base64编码已经成为Java类库的标准

Java 8 内置了 Base64 编码的编码器和解码器。

Base64工具类提供了一套静态方法获取下面三种BASE64编解码器:

  • 基本:输出被映射到一组字符A-Za-z0-9+/,编码不添加任何行标,输出的解码仅支持A-Za-z0-9+/。

  • URL:输出映射到一组字符A-Za-z0-9+_,输出是URL和文件。

  • MIME:输出隐射到MIME友好格式。输出每行不超过76字符,并且使用'\r'并跟随'\n'作为分割。编码输出最后没有行分

posted @   KDking  阅读(163)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· winform 绘制太阳,地球,月球 运作规律
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示