java基础知识

重载与重写的区别

重载

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

重写

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

String,StringBuffer,StringBuilder的区别,String为什么不可变

可变性

String类中使用final关键字字符数组保存字符串,private final char value[],所以string对象是不可变的,而StringBufferStringBuilder都继承AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,但是没用final关键字修饰,所以这两种对象都是可变的

AbstractStringBuilder.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
abstract class AbstractStringBuilder implements Appendable, CharSequence {
//The value is used for character storage.
//该值用于字符存储。
char[] value;
//The count is the number of characters used.
//使用的字符数。
int count;
//This no-arg constructor is necessary for serialization of subclasses.
//这个无参数构造函数是子类序列化所必需的。
AbstractStringBuilder() {
}
//创建指定容量的AbstractStringBuilder。
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}

线程安全性

String中的对象是不可变的,也可以理解为常量,线程安全

AbstractStringBuilderStringBuilderStringBuffer公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexof等公共方法,StringBuffer对方法加了同步锁或对调用的方法加入了同步锁,所以线程是安全的StringBuilder并没有对方法加同步锁,所以线程是不安全的

性能

每次对String类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String对象

StingBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用StringBuilderStringBuffer性能高10%·15%左右但要冒多线程下不安全的风险

总结

  • 操作量少的数据String

  • 单线程操作字符串缓冲区下操作大量数据StringBuilder

  • 多线程操作字符串缓冲区下操作大量数据StringBuffer

自动装箱与拆箱

装箱:将基本类型用他们对应的引用类型包装起来

拆箱:将包装类型转换为基本类型

注:基本类型引用类型==判断相等时注意空指针异常(https://gpdstudy.club/posts/integer-bug/#more)

1
2
3
4
5
6
7
int a = 1;
Integer b = null;
if(a == b) {//空指针异常
System.out.println(true);
}else {
System.out.println(false);
}

==与equals

==

它的作用是判断两个对象的地址是否相等,即判断两个对象是不是同一个对象基本类型比值,引用数据类型比较的是内存地址

equals()

它的也作用是判断两个对象是否相等,但他一般有两种情况

  • 类没有重写equals方法通过equals()比较该类型的两个对象时,等价于==比较

  • 类重写了equals()方法,一般,我们重写equals方法来比较两个对象的内容相等相等返回true

举个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class test{
public static void main(string[] args){
String a=new String("ab");//a为一个引用
String b=new String("ab");//b为另一个引用,对象内容一样
String c="ab";//放在常量池中
String d="ab";//从常量池中查找
if(a==b)//false
System.out.println("a==b")
if(c==d)//true
System.out.println("c==d")
if(42==42.0)//true
System.out.println("true")
}
}

String中的equals()方法是被重写过的,因为Object的equals方法是比较对象的内存地址String中的equals()方法比较的是对象的内容

创建String类型的对象时,虚拟机会在常量池中查找有没有已存的值和要创建的值相等,如果有就把他给当前对象引用,如果没有就在常量池中重新创建一个String对象

final关键字

final关键字主要用在三个地方:变量,方法,类

  • 对于一个final变量,如果是基本数据的类型的变量,则其数值一旦初始化后不能在更改.如果是引用类型的变量,则在其初始化后便不能再让其指向另一个对象
  • 当用final修饰一个时,表示这个类不能被继承final类中的所有成员方法都会被隐式指定为final方法
  • 用final修饰的方法原因有两个一是因为把方法锁定,以防任何继承类修改它的含义二是因为效率早期java实现版本中,会将final方法转为内嵌调用,但如果方法过于庞大,可能看不到内嵌调用带来的性能提升(现在java版本已不需要final方法进行优化)类中所有private方法都隐式指定为final

    Object类中的常见方法总结

Object是所有类的父类,主要提供了11个方法

  • native方法,用于返回当前运行时对象的Class对象,使用了final关键字,不允许重写

    1
    public final native Class<?> getClass();
  • native方法,用于返回对象的哈希码,主要使用在哈希表中(HshMap)

    1
    public native int hashCode();
  • 用于比较两个对象的地址是否相同,string重写改为了比较两个对象的内容

    1
    2
    3
    public boolean equals(Object obj) {
    return (this == obj);
    }
  • native方法,用于创建并返回当前对象的一份拷贝,一般情况下,对于任何对象x,表达式x.clone()!=xtrue,而x.clone().getClass()==x.getClass()true。Object本身没有实现Cloneable接口,所以不重写clone方法并调用的话会发生CloneNotSupportedException异常

    1
    protected native Object clone() throws CloneNotSupportedException;
  • 返回类的名字@实例的哈希码的16进制的字符串(建议Object子类都重写这个方法)

    1
    2
    3
    public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }
  • native方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念),如果有多个线程在等待只会随机唤醒一个

    1
    public final native void notify();
  • native方法,并且不能重写。与notify方法一样,区别在于会唤醒在此对象监视器上等待的所有线程,而不是其中一个

    1
    public final native void notifyAll();
  • native方法,并且不能重写。暂停线程的执行,注:sleep方法没有释放锁,而wait释放了锁,timeout是等待时间

    1
    public final native void wait(long timeout) throws InterruptedException;
  • 与上个方法一样,只是多了nanos参数,这个参数表示额外时间(以毫秒为单位,0-999999),所以超时时间还要加上nanos毫秒

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public final void wait(long timeout, int nanos) throws InterruptedException {
    if (timeout < 0) {
    throw new IllegalArgumentException("timeout value is negative");
    }

    if (nanos < 0 || nanos > 999999) {
    throw new IllegalArgumentException(
    "nanosecond timeout value out of range");
    }

    if (nanos >= 500000 || (nanos != 0 && timeout == 0)) {
    timeout++;
    }

    wait(timeout);
    }
  • 和之前两个wait方法一样,只不过该方法一直等待,没有超时

    1
    2
    3
    public final void wait() throws InterruptedException {
    wait(0);
    }
  • 实例被垃圾回收器回收时触发的操作

    1
    protected void finalize() throws Throwable { }

java中的异常处理

java异常类型
在java中,所有的异常都有一个共同的父类java.lang.Throwable,Throwable有两个子类Exception(异常)Error(错误)两者都是异常处理的重要子类,各自都包含大量子类

Error(错误)

**Error(错误)是程序无法处理的错误表示运行程序中较严重的问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时JVM出现的问题

  • java虚拟机运行错误(Virtual MachineError)
  • 类定义错误(NoClassDefFoundError)
  • ……

Exception(异常)

Exception(异常)是程序本身可以处理的异常Exception有一个重要的子类RuntimeException。RuntimeExceptioon由java虚拟机抛出

  • NullPointerException,要访问的变量没有任何对象引用时,抛出该异常
  • ArithmeticException,算术运算异常,整数除0时抛出
  • ArrayIndexOutOfBoundsException,下标越界异常

注:异常和错误的区别在于异常能被程序本身处理,而错误不可以

Throwable类常用方法

  • getMessage():返回异常发生时的详细信息

  • toString():返回异常发生时的简要描述

  • getLocalizedMessage():返回异常对象的本地化信息。使用Throwable子类重写这个方法可以声称本地化信息,如果子类没有重写该方法,则返回与getMessage()结果相同

  • printStackTrace():在控制台打印Throwable对象封装的异常信息

    异常处理

  • try块:用于捕捉异常,其后可接零或多个catch块,如果没有catch块则必须跟一个finally块

  • catch块:用于处理try捕捉到的异常

  • finally块:无论是否捕捉到异常都会执行当在try或catch中遇到return语句时,finally语句在返回前执行

    finally不会执行的条件

  • finally中发生了异常

  • 在finally前用System.exit()退出程序

  • 程序所在线程死亡

  • 关闭CPU

    获取键盘输入

通过Scanner

1
2
3
Scanner input = new Scanner(System.in);
String s = input.nextLine();
input.close();

通过BufferedReader

1
2
3
4
5
6
BufferedReader input =new BufferedReader(new InputStreamReader(System.in));
try {
String s = input.readLine();
} catch (IOException e) {
e.printStackTrace();
}

接口与抽象类的区别

  • 接口方法默认public,所有方法在接口中不能有实现在java8开始接口方法可以有默认实现抽象类可以有非抽象方法
  • 接口中的实例变量默认是final类型的而抽象类不一定
  • 一个类可以实现多个接口,但只能实现一个抽象类
  • 接口不能被new实例化,但可以声明,但是必须引用一个实现该接口的对象,从设计层来说,抽象是对类的抽象,是一种模板设计,接口是行为的接口,是一种行为规范

jdk8中,接口也可以定义静态方法,可以直接用接口名调用。实现类和实现是不可以调用的。如果同时实现了两个接口,接口定义了一样的默认方法,必须重写,不然会报错https://github.com/Snailclimb/JavaGuide/issues/146/

java集合

ArrayList与LinkedList异同

  • 线程安全

ArrayList和LinkedList都是不同步的,线程不保证安全

  • 底层数据结构

ArrayList底层是Object数组,LinkedList底层使用的是双向链表数据结构(1.6之前是循环链表,1.7取消了循环)

  • 插入和删除是否受元素位置的影响

ArrayList采用数组存储,所以插入和删除元素的时间复杂度受元素的位置影响,比如:执行add(E e)方法的时候,ArrayList会默认将指定元素追加到末尾,这种情况下时间复杂度为O(1),但如果是在指定位置i插入和删除元素add(int index E e)时时间复杂度为O(n-i)。因为在执行此方法时集合中的第i个元素个i之后(n-i)个元素都要执行向后/前位移一位的操作LinkedList采用链表存储,所以插入删除元素时间复杂度不受元素位置影响,都是近似O(1),而数组为近似O(n)

  • 是否支持快速随机访问

LinkedList不支持高效的随机元素访问,而ArrayList支持get(int index)

  • 内存空间占用

ArrayList空间浪费在list列表结尾会预留一定的容量空间,而LinkedList空间花费在每一个元素,每个元素消耗的空间都比ArrayList更多,因为LinkendList要存放直接后继和直接前驱以及数据

  • list的遍历

实现了RandomAccess接口的list,优先使用for循环,其次foreach
未实现RandomAccess接口的list,优先选择iterator遍历(foreach底层也是有iterator实现的)大size的数据不要使用普通for循环

ArrayList和Vector

Vector类所有方法都是同步的。可以由两个线程安全的访问一个Vector对象,但一个线程访问Vector的话代码要在同步操作上耗费大量的时间

ArrayList不是同步的,在不需要保证线程安全的时候建议使用

-------------本文结束❤️感谢您的阅读-------------
ボ wechat
扫描二维码,可获得菜鸡一枚
打赏测试
0%