这个是一些Java基础知识常见面试题,以及答案,我会逐个写出答案,实际上也是我学习的过程,一步一步来吧。
java中==和equals和hashCode的区别
==如果作用于基本数据类型的变量(byte,short,char,int,long,float,double,boolean ),则直接比较其存储的"值"是否相等;如果作用于引用类型的变量(String),则比较的是所指向的对象的地址(即是否指向同一个对象)。
equal如果直接使用Object继承的方法则和==相同,但是如果equal重写的比较方法,自定义后就按照equal后的定义比较了
hashCode是将bean实体转换为哈希编码的方法
int与integer的区别
int是java的基本类型,非null,初始值0,Integer是int的封装类型,可为null,以及一些方法,max,min等
抽象类的意义
无法实例化,没有足够的信息来描述一个对象,实际是对某类对象的抽象出来的一些共有的定义以及方法。为公共的对象的非抽象方法提供了通用的定义,方便子类的使用。也可以定义抽象方法,不能有方法体,这种方法子类必须重写(这个实际和接口是一样的,如果子类也是抽象类可以不重写)
接口和抽象类的区别
一个类可以实现多个接口,但是一个类只能有一个抽象类的父类,接口所有方法都是抽象方法,必须由实现类实现这些抽象方法,而抽象类可以有非抽象方法,抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的, 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
能否创建一个包含可变对象的不可变对象?
public class TestNotChanged {
public static final TestDto testDto = new TestDto();
public static void main(String[] args) {
testDto.setCommParam(CommParam.builder().paramValue("1111").build());
System.out.println(testDto.getCommParam());
testDto.setCommParam(CommParam.builder().paramValue("2222").build());
System.out.println(testDto.getCommParam());
}
}
//输出
CommParam{paramKey='null', paramValue='1111'}
CommParam{paramKey='null', paramValue='2222'}
谈谈对java多态的理解
继承父类,重写父类的方法,Parent parent = new Child();这时在执行父类与子类都有的方法,实际会调用子类的方法。
String、StringBuffer、StringBuilder区别
String 字符串常量、StringBuffer 字符串变量(线程安全,所有方法都是synchronized)、StringBuilder 字符串变量(非线程安全,效率高,没有synchronized)
泛型中extends和super的区别
extends表示泛型的上限(表示必须是该类型或者该类型的子类),super表示泛型的下限(表示必须是该类型或者该类型的父类)
进程和线程的区别
进程cpu执行任务的一段任务,而线程是这个任务在cpu上执行的具体操作。
序列化的方式
1>对象实现了序列化接口Serializable,2>实现接口Externalizable,实现writeExternal和readExternal方法
string 转换成 integer的方式及原理
第一位可以是符号位(正负),正负值会在最后一步取正反。从左到右按位取值,例如325,第一位为3,则循环取,取出3,将3变为-3放到result中(个人认为这个是避免从正数超过int的最大值溢出),再取第二位2,这时需要先将result*进制(一般为10进制),变为-30,之后-30减去2得到result为-32,再取第三位5,先将result*进制,result变为-320,之后-320减去5得到-325,循环完毕,这个值是正数,所以需要取反,得到325.
静态属性和静态方法是否可以被继承?是否可以被重写?以及原因?
可以被子类继承使用,但是重写后不生效,因为static方法以及属性都是全局的,需要通过具体class名称来使用,即使方法名或者属性名相同,父类的静态方法只能是父类后边带方法,子类的静态方法只能是子类后边带方法。
public class Parent {
public static String aaa = "aaa";
public static String testMethod() {
return aaa;
}
}
public class Child extends Parent {
public static String aaa = "aaaa";
public static String testMethod() {
return aaa;
}
public static void main(String[] args) {
Parent a = new Child();
System.out.println(a.aaa);
Child b = new Child();
System.out.println(b.aaa);
System.out.println(Parent.testMethod());
System.out.println(Child.testMethod());
}
}
//输出
aaa
aaaa
aaa
aaaa
成员内部类、静态内部类、局部内部类和匿名内部类的理解,以及项目中的应用
成员内部类:定义在类内部的非静态类;
静态内部类:定义在类内部的静态类;
局部内部类:定义在方法中的类(只在某个方法中使用);
匿名内部类:没有访问修饰符的直接通过new出来的内部类(例如事件监听的回调方法)
//匿名内部类
public void test() {
Object obj = new Object() {
@Override
public String toString() {
System.out.println(b);
return String.valueOf(a);
}
};
System.out.println(obj.toString());
}
讲一下常见编码方式?
ASCII,ISO-8859-1、GBK/GB2312/UTF-16(双字节)/UTF-8(变长,开头是0表示ASCII,开始是11表示首字节,开始时10表示非首字节,前一个才是首字节)
如何格式化日期?
Date FormatDate = new SimpleDateFormat("YYYY-MM-DD").parse("2020-01-01")
LocalDateTime.now(ZoneId.of("Asia/Shanghai")).format(DateTimeFormatter.ofPattern("YYYY-MM-DD"))
Java的异常体系
819952763-f554356dc18f5215_fix732.png
什么是异常链
不会覆盖原有的异常信息,将之前throw出来的异常加入到新的异常中
// 异常链写法1
Exception e2 = new Exception("第2个异常");
e2.initCause(e); // 异常链信息的传递
throw e2;
// 异常链写法2
// throw new Exception("第2个异常", e);
throw和throws的区别
throw表示创建新的异常并扔出来,而throws表示方法定义的末尾,将内部可能throw出来的异常直接在方法级抛出
反射的原理,反射创建类实例的三种方式是什么。
//第一种,使用 Class.forName 静态方法。当你知道该类的全路径名时,你可以使用该方法获取 Class 类对象。
Class clz = Class.forName("com.aaa.Apple");
Apple apple = (Apple)clz.newInstance();
// 第二种,使用 .class 方法。
//这种方法只适合在编译前就知道操作的 Class。
Class clz = Apple.class;
Constructor constructor = clz.getConstructor();
Apple apple = (Apple)constructor.newInstance();
//第三种,使用类对象的 getClass() 方法。
String str = new String("Hello");
Class clz = str.getClass();
java当中的四种引用
强引用:直接通过=赋值,内存不够也不会被回收,常见用法;
软引用:SoftReference<?>来表示,内存不足时会回收软引用,写完下面代码才理解,实际上这个是定义了一个引用,指向了里面的地址,实际上即使里面的变量设定为null了,引用还在,但是当内存不足时候回收掉,软引用就无法get到值了,变为null了,另外注意一定是new String出来,而不是直接写="123",这样写多少都是一个地址,无法回收,但是实际上这个没有回收成功,软引用的场景是缓存,图片缓存等;
弱引用:WeakReference<?>来表示,和软引用的写法类似,但是只要内存回收,弱引用就会被回收,下面的回收成功了,WeakHashMap,可以被回收的key值(HashMap的key值在删除前在不能被回收),而这种WeakHashMap的key在内存不足时可以被回收,使用前需要判断是否存在;
虚引用(PhantomReference) ,只能通过 ReferenceQueue 引用队列一起使用,创建时放入queue中,使用时通过queue的poll取出来,但是随时可能会被回收。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。实际这个就是看何时发生回收的一个监控,目前没有其他的使用场景。
//弱引用
public static void main(String[] args) throws Exception {
String a = new String("1111");
WeakReference<String> list =new WeakReference(a);
a = null;
while(true) {
System.out.println(list.get());
System.out.println("-----");
System.gc();
System.out.println(list.get());
System.out.println("==========================================================================");
if(list.get() == null){
System.out.println("BREAKBREAKBREAKBREAK");
break;
}
}
}
//输出
[GC (Allocation Failure) 1023K->708K(5632K), 0.0011759 secs]
[GC (Allocation Failure) 1732K->1034K(5632K), 0.0010715 secs]
[GC (Allocation Failure) 2056K->1224K(5632K), 0.0008859 secs]
[GC (Allocation Failure) 2247K->1460K(5632K), 0.0009643 secs]
[GC (Allocation Failure) 2484K->1577K(5632K), 0.0009887 secs]
[GC (Allocation Failure) 2586K->1703K(5632K), 0.0015657 secs]
[GC (Allocation Failure) 2727K->1855K(5632K), 0.0017342 secs]
[GC (Allocation Failure) 2879K->2183K(5632K), 0.0010898 secs]
1111
-----
[GC (System.gc()) 2990K->2264K(5632K), 0.0009776 secs]
[Full GC (System.gc()) 2264K->1588K(5632K), 0.0179253 secs]
null
==========================================================================
BREAKBREAKBREAKBREAK
//虚引用
String status = new String("123");
ReferenceQueue<String> queue = new ReferenceQueue<>();
PhantomReference<String> test = new PhantomReference<String>(status, queue);
status = null;
System.out.println(queue.poll());
System.gc();
Thread.sleep(2000L);
System.out.println(queue.poll());
//输出
null
java.lang.ref.PhantomReference@58372a00
深拷贝和浅拷贝的区别是什么?
这里考点是对基本类型和引用类型的理解,基本类型浅拷贝和深拷贝没什么区别,都是copy出一份到新的内存地址,但是引用类型就不一样了,如果是浅拷贝,一些引用变量还是指向之前的地址,这就导致了修改了拷贝前的Bean的某个值,则另外一个Bean的该变量也会变化,因为内存地址是一样的,因此需要重写clone方法,将该变量copy一份到新内存地址,这样修改任何一个都不互相影响。
什么是编译器常量?使用它有什么风险?
这里考点是对编译器(期)常量和运行时常量的理解
//编译期常量
public static final String TEST = "TEST";
//运行时常量
public static final double TEST_DOUBLE = Math.random();
编译期常量实际上是在编译的时候初始化的变量,而运行时常量只有在运行时候才会初始化,编译时不知道是什么值。
在咱们大型项目中,会有增量打升级包的情况,此时如果A类引用了其B类的一个编译期常量(A类本次没有编译,B类的常量值修改后编译),那么上线后,会发现A类引用的编译期常量还是之前的值,会导致bug的产生,因此一定要注意一起编译。这个其实引入了JVM的知识点,A类引用了B累的编译期常量,实际会在编译时写死在A类中,也就是即使B类编译变化了也不影响A类的引用。
你对String对象的intern()熟悉么?
intern实际上是在常量池里寻找与其相等的String,并把地址返回过来,具体可以看下面的,s1和s4通过+实际上是不同的String,但是由于字符串实际相同,因此intern()也是相同的。
String s1 = "abc";
String s2 = "a";
String s3 = "bc";
String s4 = s2 + s3;
System.out.println(s1 == s4);
System.out.println(s1.intern() == s4.intern());
a=a+b与a+=b有什么区别吗?
其实你注意下,他把类型去掉了?为什么呢?
这就是问题所在,隐去类型就是题目关心的,因为实际上如果类型一致,那么就不会有问题这两个操作是一致的,但是如果类型不一致,那么+=就会多出来一步类型转换。
byte a=1;
//a+=4;这个和下面实际上是相等的操作
a = (byte)(a+4);
//但是如果这么写编译期会报错,认为a+4转换为了int,但是实际上a是byte,两个类型不符合了
a = a+4;
静态代理和动态代理的区别,什么场景使用?
这个有点长,我写了个帖子:
https://www.jianshu.com/p/ca1cf7d31a11
如何将一个Java对象序列化到文件里?
可以查看我的另一个帖子:
https://www.jianshu.com/p/82ee2bbdec6c
说说你对Java反射的理解
可以查看我的另一个帖子:
https://www.jianshu.com/p/6e75e93cf95e
说说你对Java注解的理解
可以查看我的另一个帖子:
https://www.jianshu.com/p/e9f2d837fa8d
说说你对依赖注入的理解
在没有Spring或者在纯java的代码中,都是自己去new出来的对象,这样会导致很多代码中包含了大量的new,set各种代码
例如如果我需要数据库操作先创建Connection连接等一系列操作,很繁琐
出现了Spring以后,Spring容器把这些操作集合了,也就是咱们BeanA需要BeanB的时候,不需要再去new操作了,Spring容器帮助咱们把BeanB创建好了
BeanA想要使用BeanB直接拿来就可以用了(Spring容器帮助把BeanB注入到BeanA中了,BeanA依赖的BeanB就这样被注入进来了),这种操作就是依赖注入了。
其实很多人还说有Ioc控制反转,个人理解就是以前需要BeanB就new出来就好了,现在容器把创建BeanB的过程拿走了,创建BeanB的过程被容器控制了。
DI依赖注入,实际就是BeanA依赖的BeanB被容器帮助下,容器把BeanB注入到了BeanA中。
说一下泛型原理,并举例说明
实际上java的泛型是个伪泛型,使用起来很方便,免去手写转型操作了。
具体原理时编译器在编译前会检测你针对List<String>这种定义的集合中存入内容时候,会去检测你传入的类型是否符合类型,如果不符合就给你提示类型传入的不对。
另外看源码:
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Sfsdf");
String result = list.get(0);
System.out.println(result);
}
反编译后源码的,发现了什么,list取出来之后增加一个String转型,这个就是伪泛型了,实际上泛型编译成class时候,会帮助咱们进行转型操作,而不像C中的泛型,独立的类型,不需要转型操作:
public static void main(String[] args)
{
List<String> list = new ArrayList();
list.add("Sfsdf");
String result = (String)list.get(0);
System.out.println(result);
}
更多的可以查看我的另一个帖子:
https://www.jianshu.com/p/e1086513cf01
Java中String的了解
String类其实是通过char数组来保存字符串的
String类是final类,也即意味着String类不能被继承,并且它的成员方法都默认为final方法
String对象一旦被创建就是固定不变的了,对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象
有两种创建方式直接通过""(这种方式直接使用常量池的常量)创建以及new String("")(这种方式,会将对象存储到堆中)的方式
String str3=str1+str2这个操作实际触发了StringBuilder的append方法以及toString方法。
但是String str4="abc"+"def";这种情况下,abc和def都在常量池内,因此拼接时候直接在常量池内拼接,生成的abcdef还在常量池中
String.intern()会直接从常量池里找到
String是不可变字符串对象,StringBuilder和StringBuffer是可变字符串对象(其内部的字符数组长度可变)
String中的对象是不可变的,也就可以理解为常量,显然线程安全。StringBuffer 与 StringBuilder中的方法和功能完全是等价的,只是StringBuffer 中的方法大都采用了synchronized关键字进行修饰,因此是线程安全的,而StringBuilder没有这个修饰,可以被认为是非线程安全的。
String为什么要设计成不可变的?
如果设计成StringBuilder这种,作为key放在HashSet中,StringBuilder是可变的,如果分别放入了两个不同值的StringBuilder sb1和sb2,放入了后期修改了sb2的值和sb1的值相同,就会违反了HashSet的key值唯一性,另外在大量使用字符串,很多重复的字符串情况下,也是节省了很多存储空间,关键是它不可变所以也不会出现问题。
Object类的equal和hashCode方法重写,为什么?
首先equal直接使用Object的话,是和==表示了相同的意思,因此如果有一些复杂的Bean需要特殊的比较时候,就需要重启equal了。比如修改了eqaul方法,某些情况下表示相同了,如果不重写hashCode时候,再存入HashSet或者HashMap的做为key的时候,可能不相同,这样就会表达出不同的意思。
另外java还有个规定:hashcode()不等,一定能推出equals()也不等;hashcode()相等,equals()可能相等,也可能不等。
Java中实现多态的机制是什么?
Java实现多态有三个必要条件:继承、重写、向上转型。
继承:在多态中必须存在有继承关系的子类和父类。
重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。
只有满足了上述三个条件,我们才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而达到执行不同的行为。
重载(overload)和重写(override)
如果一个子类继承了一个父类,子类中拥有和父类相同方法名称,返回值,参数类型的话,就是重写,会执行子类中的方法。
如果一个类中定义了多个同名方法,他们有不同的参数类型或者参数数量或者返回值类型,那就叫重载
网友评论