Java基础

方法重载和方法重写的区别

  1. 都是实现多态的方式;
  2. 重载实现编译时的多态性,重写实现运行时的多态性;
  3. 重载发生在同一个类中,方法名相同,参数列表不同,与返回值类型、权限修饰、抛出异常无关(比如参数列表相同但返回值类型不同,不能表示重载且报错);
  4. 重写发生在父类和子类之间,方法名、参数列表相同,子类方法返回值类型与父类相同或其子类、子类方法权限修饰不能比父类更严格、子类方法抛出异常与父类相同或其子异常、子类方法不能比父类方法抛出更广泛的异常;

==和equals()的区别

  1. :分两种情况:基本数据类型比较的是值,引用数据类型==比较的是地址;
  2. equals()只针对引用类型,也分两种情况:2.1类没有重写equals()方法,比较的是地址,例如StringBuilder类、StringBuffer类;2.2类重写了equals()方法,比较的是对象内容,例如String类、基本类型包装类;

按值传递和按引用传递的区别

  1. 基本数据类型按值传递,对形参的修改不会影响实参;
  2. 引用数据类型按地址传递,形参和实参指向同一内存地址,形参改变会导致实参改变;
  3. String、Integer、Double等Immutable(不可变)的类型特殊处理,传递的是该对象引用的副本(独立引用),操作不会改变实参;

static关键字的作用

  1. static是修饰符,用于修饰类的成员变量、成员方法,还可以修饰代码块;
  2. 修饰成员变量:1.1变量被称为静态变量、类变量,属于类,被所有对象共享1.2类初次加载时分配内存并初始化,内存中只有一个副本,而非静态变量在创建对象时分配内存并初始化,属于对象,存在多个副本,各个对象拥有的副本互不影响;
  3. 修饰成员方法:2.1方法被称为静态方法、类方法,属于类,被所有对象共享2.2只能访问静态变量和静态方法,但非静态方法不仅可以访问非静态变量和非静态方法,也可以访问静态变量和静态方法2.3方法中不能使用this;
  4. 修饰代码块:3.1给类变量进行初始化赋值3.2只会在类被初次加载时执行一次;

静态变量、成员变量、局部变量的区别

  1. 静态变量:static修饰,类变量,类中方法外,类初次加载时初始化,类调用,对象调用,存储在方法区,与类共存亡;
  2. 成员变量:类中方法外,对象创建时初始化,对象调用,存储在堆,与对象共存亡;
  3. 局部变量:方法中或作为方法形参,存储在栈,与方法共存亡;

成员变量与局部变量的详细区别

  1. 语法形式:成员变量在类中方法外,局部变量在方法中或者作为方法形参;成员变量可被public、private等访问控制修饰符及static修饰,局部变量不能被访问控制修饰符及static修饰;但是,成员变量和局部变量都能被final修饰;
  2. 存储方式:成员变量是对象的一部分,存放于堆中;局部变量存放于栈中;
  3. 生命周期:成员变量与对象共存亡;局部变量与方法的调用共存亡;
  4. 成员变量如果没有被赋初值,会自动赋以类型的默认值(特殊情况:被final修饰的成员变量必须显式地赋值或者在构造函数中赋值),局部变量不会自动赋值必须显式赋值;

如果两个对象的hashcode相同,equals()一定为true吗?

  1. 不一定,如果两对象的hashcode相等,不代表两对象equals()为true,只能说明两对象根据hashcode算法求取的数值刚好相同;
  2. 两对象先通过hashcode比较,如果hashcode相等,再用equals()来判断两对象的内容是否相等;
  3. 如果两对象equals()为true,则这两对象的hashcode一定相等;若hashCode不同,将直接判定两个对象不同,跳过 equals ,这加快了冲突处理效率;

为什么equals()重写的话,建议也一起重写hashcode方法?

  1. 首先保证一个原则,两对象如果equals()为true,其hashcode一定相等;
  2. 如果一个类重写了equals方法,但没有重写hashcode方法,那么当类的两个对象通过equals比较时相等,但两者的hashcode不一定相等,这就导致了错误;

List、Set、Map

  1. List:有序的Collection,可以包含重复元素;
  2. Set:无序的Collection,不可以包含重复元素;
  3. Map:可以把键(key)映射到值(value)的数据类型,key无序且不能重复;

String,StringBuffer,StringBuilder的区别

  1. 可变性:String不可变,StringBuffer和StringBuilder可变;
  2. 线程安全:String不可变,因此线程安全;StringBuffer内部使用synchronized进行同步,因此线程安全;StringBuilder线程不安全;
  3. 性能:Java对String的操作实际上是一个不断创建新对象并将旧对象回收的过程,性能较差;StringBuffer和StringBuilder每次都会对StringBuffer或StringBuilder对象本身进行操作,而不是生成新对象,性能较好;StringBuilder比StringBuffer高10%-15%左右的性能,但要冒多线程不安全的风险;
  4. 使用总结:操作少量数据,使用String;单线程操作大量数据,使用StringBuilder;多线程操作大量数据,使用StringBuffer;

抽象类和接口(改进)

抽象类:用来捕捉子类的通用特性;接口:抽象方法的集合。

相同点

  1. 都不能实例化;
  2. 都包含抽象方法,其子类都必须覆写这些抽象方法;

不同点

类型抽象类接口
定义abstract classinterface
实现extends(需要提供抽象类中所有声明的方法的实现)interface(需要提供接口中所有声明的方法的实现)
继承抽象类可以继承一个类和实现多个接口,子类只可以继承一个抽象类接口只可以继承接口(一个或多个),子类可以实现多个接口
访问修饰符抽象方法可以有public、protected和default这些修饰符接口方法默认修饰符是public,不可以使用其它修饰符
构造器可以有构造器不可以有构造器
字段声明字段声明是任意的默认是 static final修饰

什么时候用抽象类,什么时候用接口

  1. 分析对象提炼内部共性时形成抽象类,提供调用功能扩充功能时形成接口;
  2. 抽象类和其子类是是不是的关系(is-a)。例如:程序员和产品经理都是员工;
  3. 接口和其子类是有没有的关系(has-a)。例如:鸟和飞机都有飞的特性;

Java实例化对象时的初始化顺序

  1. 父类静态成员和静态初始化块,按在代码中出现的顺序依次执行;
  2. 子类静态成员和静态初始化块,按在代码中出现的顺序依次执行;
  3. 父类实例成员和实例初始化块,按在代码中出现的顺序依次执行;
  4. 父类构造方法;
  5. 子类实例成员和实例初始化块,按在代码中出现的顺序依次执行;
  6. 子类构造方法;

Java8种基本类型及其位数

  1. 整型4种:byte8short16int32long64;
  2. 浮点型2种:float32double64;
  3. 字符型1种:char16;
  4. 布尔型1种:boolean1;

String对象的两种创建方式

  1. 字面量:会创建0或1个对象,如果String Pool中没有“abc”,编译期在String Pool中创建一个"abc"对象,运行期返回String Pool中该“abc"对象的引用;如果String Pool中已有一个“abc”对象,则编译期不创建新的“abc”对象,运行期直接返回String Pool中该“abc”对象的引用;
  2. new:会创建1或2个对象,如果String Pool中没有“abc”,编译期在String Pool中创建一个"abc"对象,运行期使用new的方式在堆中创建另外一个"abc"对象,并返回该对象的引用;如果String Pool中已有一个"abc"对象,则编译期不创建新的“abc”对象,运行期使用new的方式在堆中创建另外一个"abc"对象,并返回该对象的引用;

浅克隆和深克隆的区别

  1. 浅克隆:克隆一个新对象,新对象属性值和被克隆对象属性值完全相同,对于非基本类型属性,仍指向原有属性所指向的对象;
  2. 深克隆:克隆一个新对象,属性中引用的对象也会被克隆(递归性的),不再指向原有对象;
  3. 浅克隆实现:实现Cloneable接口、重写clone()方法(调用super.clone());
  4. 深克隆实现:实现Cloneable和Serializable接口、重写clone()方法(通过对象的序列化和反序列化实现深克隆);

new Integer(123) 与 Integer.valueOf(123)有何区别,请从底层实现分析两者区别

  1. new Integer(123)每次都会创建一个新对象;Integer.valueOf(123)会首先检查缓存池中是否已存在该整数值的Integer对象,如果已存在直接返回缓存中的实例,否则创建一个新的Integer对象并返回;
  2. Integer 缓存池的大小默认为 -128~127 ;
  3. 编译器会在自动装箱过程调用 valueOf() 方法,因此多个Integer实例使用自动装箱来创建并且值相同且范围在128~127,就会引用相同的对象;

JRE和JDK

  1. JRE即Java运行环境,包括JVM和Java程序运行时所需要的类库;
  2. JDK即Java开发工具包,包含JRE和开发工具,例如编译工具(javac.exe)、打包工具(jar.exe);
  3. 如果想要运行一个已有的Java程序,只需安装JRE即可,如果想要开发一个Java程序,必须安装JDK;

Throwable体系

  1. Exception:1 异常,程序可以处理的问题;2 例如RuntimeException中的NullPointerException(空指针异常)、ArithmeticException(算术运算异常,如一个整数除以0时,抛出该异常)和ArrayIndexOutOfBoundsException(下标越界异常);
  2. Error:1 错误,程序无法处理的问题;2 大多数错误与代码执行的操作无关,而是代码运行时JVM出现的问题;3 例如当JVM不再拥有继续执行操作所需的内存资源时,将出现OutOfMemoryError。4 这些错误发生时,JVM一般会选择终止线程;
  3. 异常和错误的区别:异常能被程序处理,错误无法被处理;

try/catch/finally

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

finally代码块不会被执行的4种情况

  1. finally代码块中出现了异常;
  2. 在前面的代码中使用了System.exit()退出程序;
  3. 程序所在线程死亡;
  4. 关闭CPU;

什么是泛型

  1. 可以在类、方法或接口中预支使用的未知类型;
  2. 将数据类型作为参数进行传递,使其灵活地应用到类、方法或接口中;
  3. 泛型提供了编译时类型安全检测机制,该机制将在编译时检测到非法的类型;

泛型擦除

Java的泛型是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程称为类型擦除。

看一段代码

import java.util.*;
public class Main {
    public static void main(String[] args) {
        List<Integer> list1 = new ArrayList<>();
        List<String> list2 = new ArrayList<>();
        System.out.println(list1.getClass() == list2.getClass());
    }
}
true

从以上程序运行结果可以看出,List<Integer>List<String>的原始类型是相同的,在编译成字节码文件后都会变成List,JVM看到的只有List,看不到泛型信息,这就是泛型的擦除。

再看一段代码

public class Main {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.getClass().getMethod("add", Object.class).invoke(list, "a");
        System.out.println(list.get(0));
        System.out.println(list.get(1));
    }
}
1
a

可以看到通过反射进行add操作,List<Integer>竟然可以存储字符串,这是因为反射在运行期调用的add方法,而在运行期泛型信息已经被擦除了。

既然存在泛型擦除,那么Java是如何保证在List<Integer>中添加字符串会报错呢?

Java编译器是通过先检查代码中泛型的类型,然后再进行类型擦除,最后再进行编译。

Classloader.loadClass(className)和Class.forName(className)的区别

  1. Classloader.loadClass(className)得到的是类加载过程第一阶段“加载”后的class对象;
  2. Class.forName(className)得到的是类加载过程第五阶段“初始化”后的class对象;

什么是反射机制(改进)

  1. 指在运行状态中,对任意一个类,都能够知道这个类的所有属性和方法;对任意一个对象,都能够调用这个对象的所有属性和方法;
  2. 作用:2.1 运行时检查类的属性和方法2.2 运行时检查对象的类型2.3 运行时任意调用对象的方法2.4 运行时任意构造一个类的对象;
  3. 使用:3.1 java.lang.reflect包中的三个类(1Field:成员变量 2Method:成员方法 3Constructor:构造方法)3.2对public域的方法:getField、getMethod、getConstructor 3.3对所有域的方法:getDeclaredField、getDeclaredMethod、getDeclaredConstructor 3.4利用反射访问私有属性:使用setAccessible(true);
  4. 不足:性能是一个问题,反射相当于一系列解释操作,通知JVM要做什么,性能比直接的Java慢很多;

子类继承父类的注意事项

  1. 子类构造器会默认调用super()(无论构造器中是否写有super(),除非显式调用super(参数)),用于初始化父类成员;
  2. 当父类中存在有参构造时,默认的无参构造就不存在了,因此强烈建议同时显式提供无参构造,因为子类构造不会自动调用父类有参构造,如果没有调用父类有参构造,仍然默认调用父类无参构造,如果父类没有,就会出错;

Java中的String为什么不可变?不可变的好处?

  1. 不可变的原因;1.1 String被声明为final class,是典型的Immutable类;1.2 String的主要成员变量char value[]被声明为final;
  2. 不可变的好处:2.1hashcode缓存的需要:因为String不可变,所以在它创建的时候 hashcode 就被缓存了,不需要重新计算,这就使得字符串很适合作为 HashMap 中的 key,效率大大提高;2.2String 常量池的需要:Java 中String常量池的存在就是为了性能优化(当创建一个 String 对象时,假如此字符串已经存在于常量池中,则不会创建新的对象,而是直接引用已经存在的对象,提高效率),只有String是不可变的,才能拥有String 常量池;2.3多线程安全:多线程中,可变对象的值很可能被其他线程改变,造成不可预期的结果,而不可变的 String 可以自由在多个线程之间共享,不需要同步处理;

HashMap底层原理解析

  1. 内部包含一个Entry类型的数组,Entry包含四个字段,分别是键、值、键的哈希值、以及下一个结点的引用next;
  2. 从next字段可以看出Entry是一个链表,即数组中的每个位置被当成一个桶,一个桶存放一个链表;
  3. 通过indexFor方法将键的哈希值转换成桶下标,使用拉链法来解决哈希冲突(插入链表头部);
  4. 数组table长度默认16,最大为2 ^ 30;
  5. 默认装载因子loadFactor为0.75,乘以数组长度capacity得到threshold,如果键值对数量大于threshold,数组扩容为原来的2倍,扩容后需重新计算每个元素的桶下标;
  6. 链表长度>8且数组容量>=64时,链表会转化为红黑树,红黑树中元素个数<=6就退化为链表;

HashMap为什么线程不安全?

  1. 数据丢失:put时,线程A计算出其键值对应该放置的桶索引,此时线程A时间片用完,线程B被调度并完成了对同一个桶的插入操作,当线程A再次运行并继续之前的插入操作时,它持有的信息已经过时,将导致线程A插入的数据覆盖线程B插入的数据,导致数据丢失;
  2. (jdk1.7)扩容时会重新计算元素的桶下标,两线程put时同时检测到需要扩容,并且同时添加元素到一个桶中时,可能形成环形链表,并丢失数据;

HashMap1.8的改进

  1. 链表长度>8且数组容量>=64时,链表会转化为红黑树,红黑树中元素个数<=6就退化为链表;
  2. 链表的插入方式由头插法变为了尾插法,这样做是为了避免在多线程对map进行扩容时链表结构形成环形数据结构,造成死循环。但仍是非线程安全的,多线程时可能会造成数据丢失问题;

HashTable与ConcurrentHashMap

  1. HashTable是一个线程安全的类,它使用synchronized来锁住整张Hash表来实现线程安全,即每次锁住整张表让线程独占,相当于所有线程进行读写时都去竞争一把锁,导致效率非常低下;
  2. ConcurrentHashMap可以做到读取数据不加锁,在进行写操作的时候能够将锁的粒度保持尽量地小,允许多个修改操作并发进行,其关键在于使用了锁分段技术;
  3. ConcurrentHashMap在内部细分为若干个小的HashMap,即Segment,默认16个,对每个Segment单独加锁,提高了并发度;
  4. 实现:3.1 ConcurrentHashMap内部包含了一个Segment数组,Segment和HashMap类似,是数组+链表结构;3.2 每个Segment包含并守护一个HashEntry数组,HashEntry是链表结构,在对HashEntry数组里的数据进行修改时,必须首先获得它对应的Segment锁;3.3 在操作ConcurrentHashMap时,如果需要在其中put一个新的数据,并不是将整个ConcurrentHashMap加锁,而是经历两次Hash操作,第一次Hash定位到Segment,执行加锁,第二次Hash定位到元素所在链表头部,然后完成put操作;3.4 在多线程环境下,如果多个线程同时执行put操作,则只要加入的数据被存放在不同的Segment中,在线程间就可以做到并行的线程安全;
  5. HashTable官方已废弃,建议使用ConcurrentHashMap;

LinkedHashMap

  1. 继承自HashMap;
  2. 内部维护了一个双向链表,用来维护插入顺序或LRU(最近最少使用)顺序;
  3. accessOrder决定顺序模式,默认false,维护的是插入顺序;
  4. 如果 accessOrder 为 true,则为 LRU 顺序,在每次访问一个节点时,会将这个节点移到链表尾部,保证链表尾部是最近访问的节点,那么链表首部就是最近最少使用的节点;afterNodeInsertion()在put等操作之后执行,当removeEldestEntry()方法返回 true时会移除链表首部节点; removeEldestEntry() 默认为false,如果需要让它为true,需要继承 LinkedHashMap并重写这个方法,这在实现LRU的缓存中特别有用,通过移除最近最少使用的节点,不仅保证了缓存空间足够,而且缓存的都是热点数据;

*WeakHashMap

  1. WeakHashMap 的 Entry 继承自 WeakReference,被 WeakReference关联的对象在下一次垃圾回收时会被回收;
  2. WeakHashMap 主要用来实现缓存,通过使用 WeakHashMap 来引用缓存对象,由 JVM 对这部分缓存进行回收;

*TreeMap

  1. TreeMap不仅实现了Map接口,还实现了SortedMap接口,因此集合中的映射关系具有一定的顺序(默认按key的自然顺序排列);
  2. 但是在添加、删除和定位映射关系时,TreeMap比HashMap性能稍差;
  3. 由于TreeMap实现的Map集合中的映射关系是根据键对象按照一定顺序排列的,因此不允许键对象是null;

*this和super

  1. this用来指向当前实例对象,它的一个重要作用就是用来区分对象的成员变量与方法的形参(当一个方法的形参与成员变量名字相同时,就会覆盖成员变量);
  2. super可以用来访问离自己最近的父类的成员变量和方法,当子类的成员变量或方法与父类有相同名字时就会覆盖父类成员变量或方法,要想访问父类成员变量或方法只能通过super关键字来访问;
  3. 子类每个构造方法中均隐式调用super(),显式调用父类构造super([参数])会覆盖默认的super() ;
  4. super([参数])和this([参数])均需放在构造方法内第一行,因此不能同时出现在一个构造函数里面;
  5. this和super均不可以在static环境中使用;
  6. 从本质上讲,this是一个指向本对象的指针, 而super是一个Java关键字;

*ArrayList与Vector(改进)

  1. 都是基于数组实现的动态数组;
  2. Vector线程安全,在可能涉及到线程不安全的操作上都进行了synchronized修饰,但性能不如ArrayList; ArrayList线程不安全,但性能优于Vector,单线程下建议使用;

*限定通配符与非限定通配符

  1. 限定通配符对类型进行了限制,有两种:1.1<? extends T>类型必须是T或T的子类1.2<? super T>类型必须是T或T的父类;
  2. 非限定通配符:<T>是任意类型;

*PECS原则

  1. Producer Extends Consumer Super;
  2. 如果需要一个只读List,用来produce T,那么使用<? extends T>,只能向外提供(get)元素, 而不能作为向外获取(add)元素;
  3. 如果需要一个只写List,用来consume T,那么使用<? super T> ,只能向外获取(add)元素, 而不能向外提供(get)元素;

*Comparable与Comparator

  1. 1.1 Comparable对实现它的类的对象进行比较(需要implements Comparable< T >)1.2 只能在类中重写compareTo(T t)一次,不能经常修改类的代码实现自己想要的排序;1.3 实现此接口的类的对象列表(或对象数组)可以通过Collections.sort(或Arrays.sort)进行排序;
  2. 2.1 Comparator对某个类的对象进行比较(不需要implements)2.2 将Comparator匿名内部类对象传递给sort方法(Collections.sort或 Arrays.sort),重写compare(T t1,T t2),从而在排序上实现灵活精准控制;

面向过程和面向对象各自优缺点

面向过程

  • 优点:性能比面向对象高,因为类调用需要实例化,开销比较大,比较消耗资源;

  • 缺点:不如面向对象易维护、易复用、易扩展;

面向对象

  • 优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态的特性,可以设计出低耦合的系统,使系统更加灵活和易于维护;

  • 缺点:性能比面向过程低;

面向对象三大特性(封装、继承、多态)*

封装

隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和写的访问级别。

继承

子类继承父类的特征和行为,使得子类对象具有父类的实例域和方法。

多态

同一个行为具有多个不同表现形式或形态的能力。在Java中,多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发起的方法调用在编程时并不确定,而是在程序运行期间才确定。

实现多态的三个必要条件:继承、重写、向上转型(将子类引用赋给父类对象)。

public class Main {
    public static void main(String[] args) {
        Person person = new Student();
        person.run();
    }
}
class Person {
    public void run() {
        System.out.println("person");
    }
}
class Student extends Person {
    @Override
    public void run() {
        System.out.println("student");
    }
}
student

接口幂等性保证

  1. 同一个接口,多次发出同一个请求,必须保证操作只执行一次。 调用接口发生异常并且重复尝试时,会造成系统所无法承受的损失,所以必须阻止这种现象的发生;
  2. 例如支付接口,重复支付会导致多次扣钱 ;订单接口,同一个订单可能会多次创建;
  3. 接口幂等性产生原因:1 网络波动, 可能会引起重复请求;2 用户重复操作,用户在操作时候可能会无意触发多次下单交易,也可能没有响应而有意触发多次交易应用;
  4. 如何保证接口幂等性:1 前端防抖;2 分布式锁(根据业务的操作和内容生成一个全局唯一ID,加锁);3 防重表(这种方法适用于在业务中有唯一标识的插入场景中,比如在以上的支付场景中,如果一个订单只会支付一次,所以订单ID可以作为唯一标识);
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hellosc01

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值