资讯详情

java面试

java面试

01_谈谈对面向对象思想的理解

这个问题通常会让很多人有点不知所措。我觉得我一直在编码,但很难解释我的想法。

下面,我来谈谈自己的想法,

首先,我们来谈谈面vs面向对象

我认为这两者是思维角度的差异,面向过程更多的是执行者从的角度思考问题,面向对象更多的是组织者例如,我想在0-10之间产生一个随机数,以面向过程的思维,然后我更注重如何设计算法,然后确保0-10的随机数相对平衡,面对对象的思维会更加关注,我找谁帮我们做这件事,比如Random类,调用提供的方法。

因此,面向对象的思维更多的是考虑如何选择合适的工具,然后组织一起做一件事。

例如,如果一个导演想拍一部电影,他必须先有男猪脚和女猪脚,然后还有其他的,最后组织这些资源,拍一部电影。

回到我们的程序世界,组织者的思维无处不在,例如,我们想开发项目,开发三层架构模式,所以在这个时候,我们不需要重复轮子,只需要选择市场上的主流框架,例如SpringMVC,Spring,MyBatis,这些都是各层的主流框架。

JDK,JRE,JVM有什么区别?

JDK:Java Development Kit,Java开发工具包,提供了Java的开发环境和运行环境。包含了编译Java编译源文件Javac,还有调试和分析的工具。

JRE:Java Runtime Environment,Java运行环境,包含Java虚拟机和一些基本类库

JVM:Java Virtual Machine,Java虚拟机,提供执行字节码文件的能力

所以,如果只是运行Java只需安装程序JRE即可。

另外注意,JVM是实现Java但是JVM不是跨平台的,

不同的平台需要安装不同的平台JVM

[外链图片存储失败,源站可能有防盗链机制,建议保存图片直接上传(img-h8TBD4aT-1631606888922)(…/images/v2-199da3b71771a2ac8a6abca871d2bca2_720w.jpg)]

浅谈Java常量池

一、概述

  • :确定了编译期,*.class文件的一部分包括字面量(Literal)和符号引用(Symbolic Reference)。
  • :文字字符串,声明为final的常量值(int / long / double…)等。
  • :名称和接口的完全限制(Fully Qualified Name)、字段的名称和描述符(Descriptor)、方法的名称和描述符。
  • :方法区的一部分,jvm类装载操作完成后,将完成类装载操作class常量池在文件中载入内存并保存在方法区。
  • JDK1.字符串常量池位于方法区。 JDK1.7字符串常量池已移至堆。 JDK1.8字符串常量池位移到元空间。

二、字符串常量池

字符串常量池是常量池运行的一部分

String s1 = "Hello"; String s2 = "Hello"; String s3 = "Hel"   "lo"; String s4 = "Hel"   new String("lo"); String s5 = new String("Hello"); String s6 = s5.intern(); String s7 = "H"; String s8 = "ello"; String s9 = s7   s8;            System.out.println(s1 = s2);  // true
System.out.println(s1 == s3);  // true
System.out.println(s1 == s4);  // false
System.out.println(s1 == s9);  // false
System.out.println(s4 == s5);  // false
System.out.println(s1 == s6);  // true

java中==比较的是内存地址

  1. s1 == s2 (true),s1、s2赋值时均使用的字符串字面量"Hello",在编译期间,这种字面量会直接放入class文件的常量池中,载入运行时常量池后,s1、s2指向的是同一个内存地址。

  2. s1 == s3 (true),s3虽然是动态拼接出来的字符串,但所有参与拼接的部分都是已知的字面量,在编译期间,这种拼接会被优化,因此String s3 = “Hel” + “lo”;在class文件中被优化成String s3 = “Hello”;,所以s1 == s3成立。

  3. s1 == s4 (false),s4虽然也是拼接出来的,但new String(“lo”)这部分不是已知字面量,编译器不会优化,必须等到运行时才可以确定结果,所以s4指向堆中某个地址

  4. s1 == s9 (false),s9是s7、s8两个变量拼接,都是不可预料的,编译器不作优化,运行时拼接成新字符串存于堆中某个地址。

  5. s4 == s5 (false),二者都在堆中,但地址不同。

  6. s1 == s6 (true),这两个相等完全归功于intern()方法,s5在堆中,内容为"Hello" ,intern方法会尝试将"Hello"字符串添加到常量池中,并返回其在常量池中的地址,因为常量池中已经有了"Hello"字符串,所以intern方法直接返回地址;而s1在编译期就已经指向常量池了,因此s1和s6指向同一地址,相等。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jzwAKopO-1631606888925)(…/images/9643870-b42fcf0c4e6b4918.jpg)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O4ItggZa-1631606888926)(…/images/9643870-d05c492b6e0d506d.png)]

==和equals的区别

== 是判断两个变量或实例是不是指向同一个内存空间,equals() 是判断两个变量或实例所指向的内存空间的值是不是相同

== 是指对内存地址进行比较 , equals() 是对字符串的内容进行比较

== 指引用是否相同, equals() 指的是值是否相同

==.

比较基本的数据类型:比较的是数值.

比较引用类型:比较引用指向的值(地址).

equals

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

代码案例:测试以下的每道题,你是否能够正确得到答案?

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);//ture
System.out.println(s3 == s1);//false
String s5 = "zszs";
String s6 = s3+s4;
System.out.println(s5 == s6);//false 因为字符串是不可变的对象,拼接字符串相当于new了一个对象了
final String s7 = "zs";  //使用final了说明是常量,都在常量池
final String s8 = "zs";
String s9 = s7+s8;  //使用final了说明是常量,常量和常量拼接,不会new一个对象
System.out.println(s5 == s9);//ture
final String s10 = s3+s4; //s3+s4依然是new的,s10只能赋值一次
System.out.println(s5 == s10);//false

final的作用

final修饰类,表示类不可变,不可继承: 比如,String,不可变性

final修饰方法,表示该方法不可重写:比如模板方法,可以固定我们的算法

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

注意:

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

final Student student = new Student(1,"Andy");
student.setAge(18);//注意,这个是可以的!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-csxubhfK-1631606888928)(…/images/image-20210325093939077.png)]

String s = "java"与String s = new String(“java”)

String s = “java”;

String s = new String(“java”);

这两者的内存分配方式是不一样的。

第一种方式,JVM会将其分配到常量池,而第二种方式是分配到堆内存.

String s = new String(“abc”)到底产生几个对象?

​ 栈只是对堆中或常量池中对象的引用,new String(“abc”) 的过程中,会检查常量池中是否有abc,如果没有,在常量池中进行创建,并且在堆中new 一个 string为abc的对象,如果有,只是在堆中new 一个 string为abc的对象。所以可能是一个,也可能是两个.

String,StringBuffer,StringBuilder区别.

什么是线程安全? 一个对象是线程安全的,当通过多线程去访问这个对象的时候,你不需要做额外的操作(额外的同步控制),他依然能表现出正确的数据,就叫做线程安全。

String 跟其他两个类的区别是

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

StringBuffer,StringBuilder都是在原有对象上进行操作

所以,如果需要经常改变字符串内容,则建议采 用这两者。

StringBuffer vs StringBuilder

前者是线程安全的,后者是线程不安全的。 线程不安全性能更高,所以在开发中,优先采用StringBuilder. StringBuilder > StringBuffer > String(性能排序)

什么时候。我们会考虑线程安全的问题?

当单个线程去访问资源的时候,不需要去考虑线程安全

只有当多个线程同时访问这个资源的时候,才需要考虑线程安全。(因为会出现边界的问题)(多线程访问同一个资源才需要考虑)

开发中,你用StringBuilder来解决什么问题?

用来解决字符拼接的问题,在方法内写 StringBuilder.append(" ");

StringBuilder sb = new StringBuilder(); sb.append(" ");

每个线程访问一个StringBuilder,不会出现线程安全问题 【局部变量每个线程都独享。根本不存在线程安全的问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r8xPY51b-1631606888930)(…/images/image-20210325101003101.png)]

我们常说的CURD指的是什么?

CRUD是指在做计算处理时的增加(Create)、更新(Update)、读取(Retrieve)和删除(Delete)几个单词的首字母简写,CRUD主要被用在描述软件系统中数据库或者持久层的基本操作功能。

接口和抽象类的区别

这个问题,要分JDK版本来区分回答:

  • JDK1.8之前:

  • 语法:

  • 抽象类:方法可以有抽象的,也可以有非抽象, 有构造器

    接口:方法都是抽象,属性都是常量,默认有public static final修饰.

  • 设计:

  • 抽象类:同一类事物的抽取,比如针对Dao层操作的封装,如,BaseDao,BaseServiceImpl

    接口:通常更像是一种标准的制定,定制系统之间对接的标准

    • 例子:

      1,单体项目,分层开发,interface作为各层之间的纽带,在controller中注入IUserService,在Service注入IUserDao

      2,分布式项目,面向服务的开发,抽取服务service,这个时候,就会产生服务的提供者和服务的消费者两个角色

      这两个角色之间的纽带,依然是接口

  • JDK1.8之后:

  • 接口里面可以有实现的方法,注意要在方法的声明上加上default或者static

最后区分几个概念:

  • 多继承,多重继承,多实现

  • 多重继承:A->B->C(爷孙三代的关系)

  • 多实现:Person implements IRunable,IEatable(符合多项国际化标准)

  • 多继承:接口可以多继承,类只支持单继承

抽象类 定义:   在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。简单来说,使用关键字 abstract 修饰的类就叫做抽象类。 使用:

abstract class AbstractAnimal { 
        
    public AbstractAnimal() { 
        
        System.out.println("Init AbstractAnimal.");
    }
    static String name = "AbstractAnimal";
    public abstract void eat();
    public void run() { 
        
        System.out.println("AbstractAnimal Run.");
    }
}
class Animal extends AbstractAnimal { 
        
    public static void main(String[] args) { 
        
        AbstractAnimal animal = new Animal();
        animal.run();
        System.out.println(animal.name);
        animal.eat();
    }
  // 必须重写抽象父类方法
    @Override
    public void eat() { 
        
        System.out.println("Animal Eat.");
    }
}

执行的结果:

Init AbstractAnimal. AbstractAnimal Run. AbstractAnimal
Animal Eat.

抽象方法:   使用 abstract 关键字修饰的方法叫做抽象方法,抽象方法仅有声明没有方法体。如下代码:

public abstract void m();

抽象类的特性:

抽象类不能被初始化 抽象类可以有构造方法 抽象类的子类如果为普通类,则必须重写抽象类中的所有抽象方法 抽象类中的方法可以是抽象方法或普通方法 一个类中如果包含了一个抽象方法,这个类必须是抽象类 子类中的抽象方法不能与父类中的抽象方法同名 抽象方法不能为 private、static、final 等关键字修饰 抽象类中可以包含普通成员变量,访问类型可以任意指定,也可以使用静态变量(static)

接口. 定义:   接口(interface)是抽象类的延伸,它允许一个类可以实现多个接口,弥补了抽象类不能多继承的缺陷,接口是对类的描述,使用 interface 关键字来声明。 使用:

interface IAnimal { 
        
    void run();
}
class AnimalImpl implements IAnimal { 
        
    public static void main(String[] args) { 
        
        IAnimal animal = new AnimalImpl();
        animal.run();
    }
    @Override
    public void run() { 
        
        System.out.println("AnimalImpl Run.");
    }
}

相关面试题 1.抽象类中能不能包含方法体? 答:抽象类中可以包含方法体。抽象类的构成也可以完全是包含方法体的普通方法,只不过这样并不是抽象类最优的使用方式。

题目解析:包含了方法体的抽象类示例代码如下:

abstract class AbstractAnimal { 
        
    public void run() { 
        
        System.out.println("AbstractAnimal Run.");
    }
}
class Animal extends AbstractAnimal { 
        
    public static void main(String[] args) { 
        
        AbstractAnimal animal = new Animal();
        animal.run();
    }
}

执行结果: AbstractAnimal Run.

2.抽象类能不能被实例化?为什么?

答:抽象类不能被实例化,因为抽象类和接口的设计就是用来规定子类行为特征的,就是让其他类来继承,是多态思想的一种设计体现,所以强制规定抽象类不能被实例化。

3.抽象方法可以被 private 修饰吗?为什么?

答:抽象方法不能使用 private 修饰,因为抽象方法就是要子类继承重写的,如果设置 private 则子类不能重写此抽象方法,这与抽象方法的设计理念相违背,所以不能被 private 修饰。

4.添加以下哪个选项不会引起编译器报错?

abstract class AbstractAnimal { 
        
    static String animalName = "AbstractAnimal";
      // 添加代码处
}
A:protected abstract void eat();
B: void eat();
C:abstract void eat(){ 
        };
D:animalName += “Cat”;

答:A

题目解析:选项 B 普通方法必须有方法体;选项 C 抽象方法不能有方法体;选项 D 变量赋值操作必须在方法内。

5.以下关于抽象类和抽象方法说法正确的是? A:抽象类中的方法必须全部为抽象方法 B: 抽象类中必须包含一个抽象方法 C:抽象类中不能包含普通方法 D:抽象类中的方法可以全部为普通方法(包含方法体)

答:D

题目解析:抽象类中可以没有方法或者全部为普通方法,都是允许的,如下代码所示:

abstract class AbstractAnimal { 
        
    public void run() { 
        
        System.out.println("AbstractAnimal Run.");
    }
}
class Animal extends AbstractAnimal { 
        
    public static void main(String[] args) { 
        
        AbstractAnimal animal = new Animal();
        animal.run();
    }
}

程序执行的结果为:

AbstractAnimal Run.

6.接口和普通类有什么关系? 答:在 Java 语言设计中,接口不是类,而是对类的一组需求描述,这些类必须要遵循接口描述的统一格式进行定义。

7.接口能不能有方法体? 答:JDK 8 之前接口不能有方法体,JDK 8 之后新增了 static 方法和 default 方法,可以包含方法体。

8.执行以下代码会输出什么结果

interface IAnimal { 
        
    static String animalName = "Animal Name";
}
class AnimalImpl implements IAnimal { 
        
    static String animalName = new String("Animal Name");
    public static void main(String[] args) { 
        
        System.out.println(IAnimal.animalName == animalName);
    }
}

答:执行的结果为 false。

题目解析:子类使用 new String… 重新创建了变量 animalName,又因为使用 == 进行内存地址比较,所以结果就是 false。

9.抽象类和接口有什么区别? 答:抽象类和接口的区别,主要分为以下几个部分。

默认方法: 抽象类可以有默认方法的实现 JDK 8 之前接口不能有默认方法的实现,JDK 8 之后接口可以有默认方法的实现

继承方式 子类使用 extends 关键字来继承抽象类 子类使用 implements 关键字类实现接口

构造器 抽象类可以有构造器 接口不能有构造器

方法访问修饰符 抽象方法可以用 public / protected / default 等修饰符 接口默认是 public 访问修饰符,并且不能使用其他修饰符

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

10.以下抽象方法描述正确的是? A:抽象方法可以是静态(static)的 B:抽象方法可同时是本地方法(native) C:抽象方法可以被 synchronized 修饰 D:以上都不是

答:D

题目解析:

抽象方法需要被子类重写,而静态方法是无法被重写的,因此抽象方法不能被静态(static)修饰;

本地方法是由本地代码实现的方法,而抽象方法没有实现,所以抽象方法不能同时是本地方法;

synchronized 和方法的实现细节有关,而抽象方法不涉及实现细节,因此抽象方法不能被 synchronized 修饰。

算法题-求N的阶乘

这道算法题一般考查的递归的编程技能,那么我们回顾下递归程序的特点:

1,什么是递归?

递归,就是方法内部调用方法自身 递归的注意事项: 找到规律,编写递归公式 找到出口(边界值),让递归有结束边界 注意:如果递归太多层,或者没有正确结束递归,则会出现“栈内存溢出Error”! 问题:为什么会出现栈内存溢出,而不是堆内存溢出?

2,这道题该怎么写?

规律:N!=(n-1)!*n;

出口:n1或n0 return 1;

public static int getResult(int n){ 
        
    if(n<0){ 
        
       throw new ValidateException("非法参数");
    }
    if(n==1 || n==0){ 
        
       return 1;
    }
    return getResult(n-1)*n;
}

算法题-求解斐波那切数列的第N个数是几?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KzOt0mZw-1631606888931)(…/images/v2-43a9a8e07aca193bc79500b36d8d0804_720w.jpg)]

    public static int getFeiBo(int n) { 
        
        if (n < 0) { 
        
            return -1;
        }
        if (n == 1 || n == 2) { 
        
            return 1;
        } else { 
        
            return getFeiBo(n - 1) + getFeiBo(n - 2);
        }
    }

Int和Integer的区别(重点)

	    Integer i1 = new Integer(12);
        Integer i2 = new Integer(12);
        System.out.println(i1==i2);//false

        Integer i3 = 126; //JDK1.5后 自动装箱的操作,查看源码可得,如果数据在(-128,127)的数据范围内,则不会new
        Integer i4 = 126;

        /* //反编译获取的代码 Integer.valueOf(123); public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } * */
        int i5=126;
        System.out.println(i3==i4); //true
        System.out.println(i3==i5); //true 自动拆箱,因为是数值,所以是相等的

        Integer i6 = 128; //根据源码可得,此处就等于,Integer i6 = new Integer(128);
        Integer i7 = 128;
        int i8=128;
        System.out.println(i6==i7);//false
        System.out.println(i6==i8); //true 自动拆箱,数值

以上这些输出的答案是什么?true or false? why?

你可以自己先思考,再看后面的答案分析。

答案揭晓

分情况来比较

  • 都定义为Integer的比较:

new:一旦new,就是开辟一块新内存,结果肯定是false 不new: 看范围 Integer做了缓存,-128至127,当你取值在这个范围的时候,会采用缓存的对象,所以会相等 当不在这个范围,内部创建新的对象,此时不相等

  • Integer和int的比较:

实际比较的是数值,Integer会做拆箱的动作,来跟基本数据类型做比较 此时跟是否在缓存范围内或是否new都没关系

源码分析:

当我们写Integer i = 126,实际上做了自动装箱:Integer i = Integer.valueOf(126); 分析这段源码

public static Integer valueOf(int i) { 
        
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}
//IntegerCache是Integer的内部类
private static class IntegerCache { 
        
    static final int low = -128;
    static final int high;
    static final Integer cache[];
    static { 
        
        // high value may be configured by property
        int h = 127;

自动装箱和自动拆箱

手动装箱过程:

Integer a = new Integer(66); //手动装箱过程:使用Integer包装类就相当于把数字装进箱子里一样

手动拆箱过程:

int i = a.intValue(); //手动拆箱过程:把Integer包装类型的数据转换成int类型的基本数据,intValue()就是将Integer转换为int

自动装箱过程:

Integer b = 88; //自动装箱过程,自动将数值转成Integer类型的数据。

自动拆箱过程:

int d = b; //自动拆箱过程,自动将数值转换成int类型的数据。
Integer i3 = Integer.valueOf(55); // Integer的valueOf()函数的功能就是把int类型转换为Integer。

java基本数据类型范围

整型:

byte:-2^7 ~ 2^7-1,即-128 ~ 127。1字节。Byte。末尾加B

short:-2^15 ~ 2^15-1,即-32768 ~ 32767。2字节。Short。末尾加S

有符号int:-2^31 ~ 2^31-1,即-2147483648 ~ 2147483647。4字节。Integer。

无符号int:0~2^32-1。

long:-2^63 ~ 2^63-1,即-9223372036854774808 ~ 9223372036854774807。8字节。Long。末尾加L。(也可以不加L)

浮点型:

float:4字节。Float。末尾加F。(也可以不加F)

double:8字节。Double。

字符型:

char:2字节。Character。

布尔型:

boolean:Boolean。

方法的重写和重载的区别

一般出现在(笔试题-选择题),下面我们说下重点

  • 重载:发生在一个类里面,方法名相同,参数列表不同(混淆点:跟返回类型没关系)

以下不构成重载 public double add(int a,int b) public int add(int a,int b)​

  • 重写:发生在父类子类之间的,方法名相同,参数列表相同

List和Set的区别

这简直是一道送分题,简单到我都不好意思写出来,但居然有人会搞错,汗!

  • List(有序,可重复)【 ArrayList LinkList】
  • Set(无序,不可重复)【HashSet、TreeSet】

无序 !=可以排序

这里的无序:指的是,加进去的顺序,不一定等于输出的顺序

例子:

Collections 和 Collection 的区别?

在Java里面,工具类的命名,会+s结尾,Collections是工具类,Collection是接口

谈谈ArrayList和LinkedList的区别

ArrayList,数组,连续一块内存空间 LinkedList,双向链表,不是连续的内存空间

虽然不严谨,但也可以应付很多面试了

ArrayList,查找快,因为是连续的内存空间,方便寻址,但删除,插入慢,因为需要发生数据迁移 LinkedList,查找慢,因为需要通过指针一个个寻找,但删除,插入块,因为只要改变前后节点的指针指向即可。

思考后的答案:

ArrayList连续的内存空间,可计算的偏移量LinkedList不连续,无法计算,一个个往下找ArrayList>LinkedList<

标签: 55v2a3b变送器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台