JAVA面试:JAVA基础

JAVA基础(2021.4.6)

大量参考了https://www.cnblogs.com/bailing80/p/11443409.html

JDK 和 JRE 有什么区别、以及JVM

JDK(Java Development Kit )的简称,Java 开发工具包,提供了 Java 的开发环境和运行环境。

JRE(Java Runtime Environment) 的简称,Java 运行环境,为 Java 的运行提供了所需环境。

具体来说 JDK 其实包含了 JRE,同时还包含了编译 Java 源码的编译器 Javac,还包含了很多 Java 程序调试和分析的工具。简单来说:如果你需要运行 Java 程序,只需安装 JRE 就可以了,如果你需要编写 Java 程序,需要安装 JDK

JAVA面向对象的三大特性:封装、继承、多态
封装:

核心思想就是“隐藏细节”、“数据安全”:将对象不需要让外界访问的成员变量和方法私有化(private),只提供符合开发者意愿的公有方法( publicsetget 方法)来访问这些数据和逻辑,保证了数据的安全和程序的稳定。把只在本类内部使用的方法使用 private,这就是封装的思想,是面向对象最基本的开发规范之一。

Java 的访问权限修饰关键字: private、protected、public 和 default

public :具有最大的访问权限,可以访问任何一个在 CLASSPATH 下的类、接口、异常等。

protected: 保护子类,子类可以访问这些成员变量和方法,其余类不可以

default :本包的类可以访问(子类在本包也可以使用)

private :访问权限仅限于本类内部,实际开发大多成员变量和方法都是使用private 修饰的

本类本包子类外部包
public
protected
default(子类在本包就可以)
private
继承:类只能单继承,接口可以多继承(extends),这跟继承的原理有关,因为接口方法未实现,所以不会出现方法未知

1)单继承多实现

2)可以super调用父类方法,前提是权限不为private

3)子类的构造函数一定会隐式调用父类的无参构造函数,如果没有无参构造函数,则要显示调用其他的构造函数

4)构造函数无法覆盖,父类构造函数的代码一定执行

5)重写父类方法时,权限必须大于父类

6)继承提高了程序的复用性、扩展性,是多态特征的前提

7)实际开发中,是先有子类再根据子类的共同特性提取出父类

多态:

分为重载时多态(编译时多态)【主】重写时多态(运行时多态)

多态指允许不同类的对象对同一“消息”做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。可以用于消除类型之间的耦合关系,Spring 的核心就是多态和面向接口编程。

1)Java 中可以使用父类、接口变量引用子类、实现类对象;

2)在这个过程中,会对子类、实现类对象做自动类型提升,其特有功能就无法访问了(向上转型),如果需要使用,可以做强制类型转换(向下转型)。

讲多态之前先讲一下重载和重写的区别

重载(返回值可以不同)和重写(返回值相同)的要素构成(区别)

重载:在编译时可以知道具体的执行逻辑

1)发生在同一个类中

2)相同的方法名

3)参数列表不同

4)不看返回值,如果出现了只有返回值不同的“重载”,是错的。

重写:在运行时可以知道具体的执行逻辑

1)发生在子类与父类中

2)相同的方法名

3)相同的参数列表

4)返回值相同 或者 子类方法的返回值是父类方法返回值类型的子类

5)访问修饰符相同 或者 子类方法的修饰符范围 大于 父类

6)抛出的异常相同 或者 子类方法抛出的异常 小于父类

多态的三种实现方式:重写、向上转型、继承/实现

向上转型:将子类对象赋给父类对象

特别注意:向上转型之后还是调用的子类方法,可以理解为新建了一个引用指向子类

public class Animal{
    void hello(){
		System.out.println("嗷呜");
    }
}

public class Pig extends Animal{
    void hello(){
        System.out.println("我饿了");
    }
}

//使用
Animal a = new Pig();
//Pig p = new Pig();
//Animal a = (Animal)p;//会提示强制转换是多余的,因为跟上面是一样的写法
a.hello();

a = new Animal();
a.hello();

//结果
我饿了
嗷呜

//可以看到,都是a.hello(),但是却有了不同的反应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。

向下转型:将父类对象强制转换为子类对象,前提是父类对象之前必须是对应的子类对象

//还是拿这个例子,不过我们改一下,如果这个时候子类有一个专属的技能
public class Animal{
    void hello(){
		System.out.println("嗷呜");
    }
}

public class Pig extends Animal{
    void hello(){
        System.out.println("我饿了");
    }
    
    void canEat(){
        System.out.println("不够吃,再来点");
    }
}

//使用
Animal a = new Pig();
a.hello();
a.canEat();//会报错,提示找不到这个方法
//有两种解决办法
//第一种,如果你知道它之前初始化是Pig,你可以强制转换(向下转型)
((Pig)a).canEat();
//第二种,如果不知道,就要判断确定后再强制转换
if(a instanceof Pig){
    ((Pig)a).canEat();
}

//结果
我饿了
不够吃,再来点
不够吃,再来点

总结

这里的父类不一定是实体类,也可以是接口实现的方式


8种基本数据类型

六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型

byteshortintlongfloatdoublecharboolean
位数(bit)81632643264161
默认值0000L0.0f0.0d/false
最值-27~27-1///////

String为引用类型,Integet是包装类型,为什么有Integer?

因为从数据库读取的int数据有可能为Null,所以

Integer f1 = 100, f2 = 100; f1 == f2为true

String s1 = “hello”, s2 = “hello”; s1 == s2为true

==只有在String比较的时候比较特殊

引用类型

强引用:发生 GC的时候不会被回收。

Object object= new Object();

软引用:有用但不是必须的对象,在发生内存溢出之前会被回收。

Object object= new Object();

SoftReference<Object> softReference = new SoftReference<>(object);

Object result = softReference.get();
//通过SoftReference的get方法来获取对象。软引用,在jvm内存不足的情况下会被回收

弱引用: 有用但不是必须的对象,在下一次GC时会被回收。

Object object= new Object();

WeakReference<Object> weakReference= new WeakReference<>(object);

Object result = weakReference.get();

//通过WeakReference的get方法来获取对象。在gc的时候就会被回收,不管内存是否充足。

虚引用(幽灵引用/幻影引用): 无法通过虚引用获得对象,用 PhantomReference 实现虚引用,虚引用的用途是在 GC时返回一个通知。

Object object= new Object();

ReferenceQueue queue = new ReferenceQueue();

PhantomReference pr = new PhantomReference(object, queue);

//虚引用和没有引用是一样的,需要和队列(ReferenceQueue)联合使用。当jvm扫描到虚引用的对象时,会先将此对象放入关联的队列中,因此我们可以通过判断队列中是否存这个对象,来进行回收前的一些处理。
普通for循环和foreach区别

1、for与foreach都可以遍历数组/集合,不过for则在较复杂的循环中效率更高
2、foreach不可以删除/修改集合元素,而for可以
3、foreach和for都可以修改元素里面的属性

throw 和 throws 的区别

throw:是真实抛出一个异常。

throws:是声明可能会抛出一个异常。

try-catch-finally 中哪个部分可以省略

try-catch-finally 其中 catch 和 finally 都可以被省略,但是不能同时省略,也就是说有 try 的时候,必须后面跟一个 catch 或者 finally。

try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?

finally 一定会执行,即使是 catch 中 return 了,catch 中的 return 会等 finally 中的代码执行完之后,才会执行。


== 和 equals 的区别
基本类型引用类型(String ,Integer)Object实体类型
==值相同引用相同(地址)引用相同(地址)
equals值相同值相同(String ,Integer重写了equals方法)引用相同(地址)

String和Integer重写的equals方法代码如下

//String
public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])//区分大小写
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

//Integer
public boolean equals(Object obj) {
    if (obj instanceof Integer) {
        return value == ((Integer)obj).intValue();
    }
    return false;
}
String str="i"String str=new String("i")一样吗

不一样,因为内存的分配方式不一样。String str = "i" 的方式,Java 虚拟机会将其分配到常量池中;而 String str = new String("i") 则会被分到堆内存中。

JDK1.6及以前,常量池在方法区,这时的方法区也叫做永久代;

JDK1.7的时候,方法区合并到了堆内存中,这时的常量池也可以说是在堆内存中;

JDK1.8及以后,方法区又从堆内存中剥离出来了,但实现方式与之前的永久代不同,这时的方法区被叫做元空间,常量池就存储在元空间。

Java内存分配详解(堆内存、栈内存、常量池)

https://blog.csdn.net/jian_sheng_tan/article/details/78323327

String x = "string";
String y = "string";//发现常量池存在,所以指向同一个引用
String z = new String("string");
System.out.println(x==y); // true
System.out.println(x==z); // false
System.out.println(x.equals(y)); // true
System.out.println(x.equals(z)); // true

代码解读:因为 x 和 y 指向的是同一个引用(地址),所以 == 也是 true,而 new String()方法则重写开辟了内存空间,所以 == 结果为 false,而 equals 比较的一直是值,所以结果都为 true。

String、StringBuffer、StringBuilder的区别

操作字符串的类有:String、StringBuffer、StringBuilder

String 和 StringBuffer、StringBuilder 的区别在于 String 声明的是不可变的对象,每次操作都会生成新的 String 对象,然后将指针指向新的 String 对象,而 StringBuffer、StringBuilder 可以在原有对象的基础上进行操作,所以在经常改变字符串内容的情况下最好不要使用 String。

StringBuffer 和 StringBuilder 最大的区别在于,StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,但 StringBuilder 的性能却高于 StringBuffer,所以在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用 StringBuffer。


如何将字符串反转?

使用 StringBuilder 或者 StringBuffer 的 reverse() 方法。

// StringBuffer reverse
StringBuffer stringBuffer = new StringBuffer();
stringBuffer. append("abcdefg");
System. out. println(stringBuffer. reverse()); // gfedcba
// StringBuilder reverse
StringBuilder stringBuilder = new StringBuilder();
stringBuilder. append("abcdefg");
System. out. println(stringBuilder. reverse()); // gfedcba

String 类的常用方法都有那些

function()说明
indexOf()返回指定字符的索引。
charAt()返回指定索引处的字符
replace()字符串替换
trim()去除字符串两端空白
split()分割字符串,返回一个分割后的字符串数组
getBytes()返回字符串的 byte 类型数组
length()返回字符串长度
toLowerCase()将字符串转成小写字母
toUpperCase()将字符串转成大写字符
substring()截取字符串
equals()字符串比较

final 有什么作用

1)final 修饰的类叫最终类,该类不能被继承。

2)final 修饰的方法不能被重写。

3)final 修饰的变量叫常量,常量必须初始化,初始化之后值就不能被修改。

抽象类能使用 final 修饰吗?

不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类。

抽象类必须要有抽象方法吗?

不需要,抽象类不一定非要有抽象方法。

abstract class Cat {
    public static void sayHi() {
        System. out. println("hi~");
    }
}

普通类和抽象类有哪些区别?

普通类不能包含抽象方法,抽象类可以包含抽象方法。

抽象类不能直接实例化,普通类可以直接实例化。

接口和抽象类的区别:接口其实是一种特殊的抽象类(所有方法都抽象就是接口)

实现: 抽象类的子类使用 extends 来继承;接口必须使用 implements 来实现接口。

实例化: 抽象类不可以实例化,接口可以通过向上转型、匿名内部类实例化。

构造函数: 抽象类可以有构造函数;接口不能有。

数据成员: 抽象类可以有数据成员,接口不可以。

实现数量: 类可以实现很多个接口;但是只能继承一个抽象类。

访问修饰符: 接口中的方法默认使用 public 修饰;抽象类中的方法可以是任意访问修饰符。

方法实现: 抽象类可以有成员方法的实现代码,但是接口不能有。

什么是内部类?内部类的作用

将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。

作用:

1)每个内部类都能独立的继承一个接口的实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。内部类使得多继承的解决方案变得完整,

2)方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏。

3)方便编写事件驱动程序

4)方便编写线程代码

匿名类

线程的使用就是匿名内部类

//继承方式  XXX extends Thread{ public void run(){}}
new Thread(){
    @Override
    public void run() {
        super.run();
    }
}.start();

//实现接口方式  XXX implements Runnable{ public void run(){}}
Runnable r = new Runnable(){
    public void run(){
        System.out.println("###");
    }
};
new Thread(r).start();

//使用继承方式和接口实现方式
new Thread(new Runnable(){
    public void run(){
        System.out.println("@@@");
    }
}).start();

Array (数组)和 Arrays 有何区别

Arrayjava.lang.reflect下的一个类,并且不能被new出来,也就是不能被实例化,它的构造器被private所修饰,且由于类被final修饰因此不能被继承。

Arraysjava.util包下的一个工具类,提供了许多对数组操作的静态方法,包括数组的排序,对比,转化等等系列方法,十分的好用。

数组转字符串Arrays.toString
public static void main(String[] args) {
    int[] array = {3,2,1,4,5};
    System.out.println(array);  // 输出 [I@61bbe9ba
    System.out.println(Arrays.toString(array));  // 输出 [3, 2, 1, 4, 5]
}
数组排序Arrays.sort
从小到大排序
public static void main(String[] args) {
    int[] array = {3,2,1,4,5};
    Arrays.sort(array);  // 从小到大排序
    System.out.println(Arrays.toString(array));  // 输出 [1, 2, 3, 4, 5]
}

查看了一下Arrays的方法,并没有提供从大到小的排序方法,那么如何从大到小排序呢?我们就需要重写compare方法来实现从大到小。看到这里,经常使用python来进行编程的朋友可能要骂娘了,我靠,这么麻烦,python中一个sorted函数不就搞定啦?那也没办法,java就是这个亚子,接受吧!

从大到小排序
public static void main(String[] args) {
    Integer[] array = {3,2,1,4,5};
    Comparator<Integer> cmp = new Comparator<Integer>() {
        public int compare(Integer i1, Integer i2) {
            return i2-i1;
        }
    };
    Arrays.sort(array,cmp);  // 从大到小排序
    System.out.println(Arrays.toString(array));  // 输出 [5, 4, 3, 2, 1]
}

其实还有一种方法是这个亚子的,偷偷告诉你们,要牢记于心,不然就要像上面一样重写方法啦,懒人才不会去重写!

public static void main(String[] args) {
    Integer[] array = {3,2,1,4,5};
    Arrays.sort(array, Collections.reverseOrder());  // 从大到小排序
    System.out.println(Arrays.toString(array));  // 输出 [5, 4, 3, 2, 1]
}
数组比较:Arrays.equals 比较一维数组,Arrays.deepEquals 比较多维数组
public static void main(String[] args) {
    int[] array1 = {1,2,3};
    int[] array2 = {1,2,3};
    int[] array3 = {1,2,4};
    System.out.println(Arrays.equals(array1,array2));  // 数组的比较,返回true
    System.out.println(Arrays.equals(array1,array3));  // 数组的比较,返回false
}

public static void main(String[] args) {
    Integer[][] array1 = {{1,2,3}};
    Integer[][] array2 = {{1,2,3}};
    Integer[][] array3 = {{1,2,4}};
    System.out.println(Arrays.equals(array1,array2));  // 多维数组的比较,返回false
    System.out.println(Arrays.equals(array1,array3));  // 多维数组的比较,返回false
    System.out.println(Arrays.deepEquals(array1,array2));  // 多维数组深比较,判断是否是同一个对象,返回true
}
数组和ArrayList互转:toArray,Arrays.asList
public static void main(String[] args) {
    Integer[] array = {1,2,3};
    List<Integer> arrayList = new ArrayList<>(Arrays.asList(array));  // 数组转ArrayList
    System.out.println(arrayList);  // 输出 [1, 2, 3]
    arrayList.toArray();  // ArrayList转数组
}
数组填充:Arrays.fill()
public static void main(String[] args) {
    Integer[] array = new Integer[5];
    Arrays.fill(array,0,3,9); // 右开区间[0,3),给数组下标 0~2的元素填充9
    System.out.println(Arrays.toString(array));  // 输出[9, 9, 9, null, null]
}
数组拷贝:Arrays.copyOf()
public static void main(String[] args) {
    Integer[] array = {1,2,3,4,5};
    Integer[] arrayCopy = Arrays.copyOf(array,10);  // 拷贝并生成一个新的数组对象,并设置长度为10
    System.out.println(Arrays.toString(arrayCopy));
}

JAVA数组转List的三种方式及对比

1)Arrays.asList(strArray)

通过 Arrays.asList(strArray) 方式,将数组转换List后,不能对List增删,只能查改,否则抛异常。

原因解析:

Arrays.asList(strArray)返回值是java.util.Arrays类中一个私有静态内部类java.util.Arrays.ArrayList,它并非java.util.ArrayList类。java.util.Arrays.ArrayList类具有 set(),get(),contains()等方法,但是不具有添加add()或删除remove()方法,所以调用add()方法会报错。

使用场景:Arrays.asList(strArray)方式仅能用在将数组转换为List后,不需要增删其中的值,仅作为数据源读取使用。

2)ArrayList的构造器

通过ArrayList的构造器,将Arrays.asList(strArray)的返回值由java.util.Arrays.ArrayList转为java.util.ArrayList

关键代码:

ArrayList<String> list = new ArrayList<String>(Arrays.asList(strArray)) ;

使用场景:需要在将数组转换为List后,对List进行增删改查操作,在List的数据量不大的情况下,可以使用。

3)Collections.addAll(arrayList, strArray)

通过Collections.addAll(arrayList, strArray)方式转换,根据数组的长度创建一个长度相同的List,然后通过Collections.addAll()方法,将数组中的元素转为二进制,然后添加到List中,这是最高效的方法。

关键代码:

ArrayList< String> arrayList = new ArrayList<String>(strArray.length);
Collections.addAll(arrayList, strArray);

使用场景:需要在将数组转换为List后,对List进行增删改查操作,在List的数据量巨大的情况下,优先使用,可以提高操作速度。

如何实现数组和 List 之间的转换?

数组转 List: 使用 Arrays. asList(array) 进行转换。

List 转数组: 使用 List 自带的 toArray() 方法。

// list to array
List<String> list = new ArrayList<String>();
list. add("王磊");
list. add("的博客");
list. toArray();
// array to list
String[] array = new String[]{"王磊","的博客"};
Arrays. asList(array);

Java异常体系

Thorwable类(表示可抛出)是所有异常和错误的超类,两个直接子类为Error和Exception,分别表示错误和异常。其中异常类Exception又分为**运行时异常(RuntimeException)和非运行时异常, 这两种异常有很大的区别,也称之为不检查异常(Unchecked Exception)和检查异常(Checked Exception)。**下面将详细讲述这些异常之间的区别与联系:

1、Error与Exception

Error是程序无法处理的错误,它是由JVM产生和抛出的,比如OutOf MemoryError、ThreadDeath等。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止
Exception是程序本身可以处理的异常,这种异常分两大类运行时异常和非运行时异常。程序中应当尽可能去处理这些异常。

2、运行时异常和非运行时异常

运行时异常都是RuntimeException类****及其子类异常

如:NullPointerException - 空指针引用异常

IndexOutOfBoundsException - 下标越界异常

ClassCastException - 类型强制转换异常

NumberFormatException - 数字格式异常

这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。

**非运行时异常(编译时异常)**是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。

IOException、

SQLException

以及用户自定义的Exception异常,一般情况下不自定义检查异常。


IO 流分为几种

按功能来分:输入流(input)、输出流(output)

按类型来分:字节流和字符流。

字节流和字符流的区别是:字节流按 8 位传输以字节为单位输入输出数据,字符流按 16 位传输以字符为单位输入输出数据。

BIO、NIO、AIO 有什么区别?

BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低

NIO:Non IO 同步非阻塞 IO,是传统 IO 的升级客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。

AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制

反射

反射是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。

什么是 Java 序列化?什么情况下需要序列化?

Java 序列化是为了保存各种对象在内存中的状态,并且可以把保存的对象状态再读出来。

以下情况需要使用 Java 序列化:

  • 想把的内存中的对象状态保存到一个文件中或者数据库中的时候;
  • 想用套接字网络上传送对象的时候;
  • 想通过RMI(远程方法调用)传输对象的时候。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值