深入【Java】底层细节知识点

一、究竟何为面向对象?

  • 首先,面向对象是一种思想,它呢是基于面向过程而言的, 这 种 思 想 是 对 数 据 的 一 种 优 化 \color{red}{这种思想是对数据的一种优化}

    其次,理解对象概念,什么是对象?对象就是类的一个实例,什么是类?类就是对一类事物的抽象,这类事物的共同特性就是程序中的属性,共同的行为就是方法

    例如:动物这个类。有属性name,有方法eat()。动物类的对象:狗,狗呢就具有name的属性和吃的行为。

    面 向 对 象 的 三 大 特 征 : 封 装 、 继 承 、 多 态 。 \color{red}{面向对象的三大特征:封装、继承、多态。}

    • 封装:隐藏了对象的具体细节。提高了复用性和安全性
    • 继承:两个类之间有一些的属性和方法的重复。就使用继承,这样既简化了代码,也提高了代码的复用性
    • 多态:继承是多态的前提。体现在:父类对象的引用可以指向子类对象,这样提高了程序的扩展性

二、jvm

图片源于网络在这里插入图片描述

  • 大多数 JVM 将内存区域划分为
    • Method Area(Non-Heap)(方法区),
    • Heap(堆),
    • Program Counter Register(程序计数器) ,
    • VM Stack(虚拟机栈,也有翻译成JAVA 方法栈的),
    • Native Method Stack ( 本地方法栈 ),

其中Method Area 和 Heap 是线程共享的 ,VM Stack,Native Method Stack 和Program Counter Register 是非线程共享的。为什么分为线程共享和非线程共享的呢?请继续往下看。

首先我们熟悉一下一个一般性的 Java 程序的工作过程。一个 Java 源程序文件,会被编译为字节码文件(以 class 为扩展名),每个java程序都需要运行在自己的JVM上,然后告知 JVM 程序的运行入口,再被 JVM 通过字节码解释器加载运行。那么程序开始运行后,都是如何涉及到各内存区域的呢?

概括地说来,JVM初始运行的时候都会分配好 Method Area(方法区) 和Heap(堆) ,而JVM 每遇到一个线程,就为其分配一个 Program Counter Register(程序计数器) , VM Stack(虚拟机栈)和Native Method Stack (本地方法栈)

当线程终止时,三者(虚拟机栈,本地方法栈和程序计数器)所占用的内存空间也会被释放掉。这也是为什么我把内存区域分为线程共享和非线程共享的原因,非线程共享的那三个区域的生命周期与所属线程相同,而线程共享的区域与JAVA程序运行的生命周期相同

这也是系统垃圾回收的场所只发生在线程共享的区域(实际上对大部分虚拟机来说知发生在Heap上)的原因。

当我们创建一个对象(new Object)时,就会调用它的构造函数来开辟空间,将对象数据存储到堆内存中,与此同时在栈内存中生成对应的引用,当我们在后续代码中调用的时候用的都是栈内存中的引用,还需注意的一点,基本数据类型是存储在栈内存中。

h e a p 和 s t a c k 有 什 么 区 别 ? \color{red}{heap和stack有什么区别?} heapstack?
java的内存分为两类,一类是栈内存,一类是堆内存。

  • 栈内存
    • 1.每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象),对象都存放在堆区中
    • 2、每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。
    • 3、栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。、
    • 栈内存是指程序进入一个方法时,会为这个方法单独分配一块私属存储空间,用于存储这个方法内部的局部变量,当这个方法结束时,分配给这个方法的栈会释放,这个栈中的变量也将随之释放。
  • 堆内存
    • 1、存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令)
    • 2、jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身
    • 堆内存是与栈作用不同的内存,一般用于存放不放在当前方法栈中的那些数据,例如,使用new创建的对象都放在堆里,所以,它不会随方法的结束而消失。(堆内存中的成员变量是随着对象的产生而产生。随着对象的消失而消失。)方法中的局部变量使用final修饰后,放在堆中,而不是栈中。

方法区的介绍看18大点:static介绍
更多参考:
转自:http://blog.csdn.net/ns_code/article/details/17565503
【深入Java虚拟机】之一:Java内存区域与内存溢出
【转】JVM介绍
【转】jvm虚拟机内存使用配置及优化

三、java泛型

  • 加入泛型之前:如果我们有如下需求:一个StringArr,你只想放string,但你并不能阻止其他类型数据放入。为了通用性,这样的数组一般都是Object。当我们获取里面的值的时候,就得强制转换,这就是它的缺点。

  • 加入泛型之后: 例如:ArrayList<String> stringValues=new ArrayList<String>();这样,就指定了具体的类型,添加一些数据的时候,如果不符合初定的类型,就会报错,安全性提高! 再有就是指定了具体的类型,提高了代码的质量,可读性提高!

  • 实现原理:
    这里写图片描述
    如上,两者的类型分明不同,输出的结果却是true,这是因为,泛型它不会影响java虚拟机生成的汇编代码,在编译阶段,虚拟机就会把泛型的类型擦除,还原成没有泛型的代码,顶多编译速度稍微慢一些,执行速度是完全没有什么区别的。 这就是为什么,Java的泛型被称为“伪泛型”的原因

    注意点:

    • 泛型变量不允许是基本数据类型,只能是他们的包装类
      这里写图片描述
    • 静态方法和静态变量不可以使用泛型类所声明的泛型类型参数
      这里写图片描述
  • Java泛型中的标记符含义

    • E - Element (在集合中使用,因为集合中存放的是元素)
    • T - Type(Java 类)
    • K - Key(键)
    • V - Value(值)
    • N - Number(数值类型)
    • ? - 表示不确定的java类型
    • S、U、V - 2nd、3rd、4th types
  • Object跟这些标记符代表的java类型有啥区别呢?

    • Object是所有类的根类,任何类的对象都可以设置给该Object引用变量,使用的时候可能需要类型强制转换,但是用使用了泛型T、E等这些标识符后,在实际用之前类型就已经确定了,不需要再进行类型强制转换。

四、String类的深入理解

了解一个类最好的方式就是看源码

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence
{
    /** The value is used for character storage. */
    private final char value[];

    /** The offset is the first index of the storage that is used. */
    private final int offset;

    /** The count is the number of characters in the String. */
    private final int count;

    /** Cache the hash code for the string */
    private int hash; // Default to 0

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;
	
	public String substring(int beginIndex, int endIndex) {
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    if (endIndex > count) {
        throw new StringIndexOutOfBoundsException(endIndex);
    }
    if (beginIndex > endIndex) {
        throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
    }
    return ((beginIndex == 0) && (endIndex == count)) ? this :
        new String(offset + beginIndex, endIndex - beginIndex, value);
}

public String concat(String str) {
    int otherLen = str.length();
    if (otherLen == 0) {
        return this;
    }
    char buf[] = new char[count + otherLen];
    getChars(0, count, buf, 0);
    str.getChars(0, otherLen, buf, count);
    return new String(0, count + otherLen, buf);
}

public String replace(char oldChar, char newChar) {
    if (oldChar != newChar) {
        int len = count;
        int i = -1;
        char[] val = value; /* avoid getfield opcode */
        int off = offset;   /* avoid getfield opcode */

        while (++i < len) {
        if (val[off + i] == oldChar) {
            break;
        }
        }
        if (i < len) {
        char buf[] = new char[len];
        for (int j = 0 ; j < i ; j++) {
            buf[j] = val[off+j];
        }
        while (i < len) {
            char c = val[off + i];
            buf[i] = (c == oldChar) ? newChar : c;
            i++;
        }
        return new String(0, len, buf);
        }
    }
    return this;
}
}

更多的不再展示。

从以上的源码中获得信息:

  • String是final类,这意味着,这个类不能被继承,也不可有子类,其中的方法默认都是final方法
  • String类是通过char数组来保存字符串的
  • String类对字符串的操作都是对新字符串操作。 也 就 是 说 , S t r i n g 对 象 一 旦 被 创 建 就 不 会 改 变 , \color{red}{也就是说,String对象一旦被创建就不会改变,} String
    任 何 改 变 操 作 都 不 会 改 变 原 字 符 串 , 而 是 生 成 新 的 对 象 \color{red}{任何改变操作都不会改变原字符串,而是生成新的对象}

字符串常量池:

  • 每当我们创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。由于String字符串不可变,所以常量池中一定不存在两个相同的字符串

  • 静态常量池和运行时常量池

    • 静态常量池,即.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。
    • 运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。

    这里写图片描述
    虽说,字符串的比较,我们使用equals方法,但用==号就可以看出,a和b指向的同一个对象。而new以后就产生新的对象。

    如果使用equals比较三者,得出的结果,肯定都是true
    这里写图片描述
    由于c是new出来的,所以产生了两个对象,一个是栈区中的c,另一个就是堆中的123,他们的引用关系是c->123->123(常量池中的)

    也就是说,尽管c是创建在堆中,但其value还是常量池中的123

    当我们对字符串,进行拼接,替换等操作时,会创建一个新的对象来操作,之后旧的对象,就会被当作垃圾回收。

2020/03/11补充:
String中的split方法,用一些特殊的字符去分裂字符串的时候需要转义:

"3.14".split("\\.");
"2020/03/11".split("\\/");
"2|3|4".split("\\|");

五、equals()和==的区别

他们最大的区别就是一个是方法,一个是关系操作符。

Object类中的equals方法源码:

public boolean equals(Object obj)
   {
       return this == obj;
   }

可见它直接比较的对象本身。

String类中的equals方法源码(重写Object中的):

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;
}

上面的equals比较当中,都运用到了==,这就说明,equals(不管哪个类中的)是==的扩展。

引用《java编程思想》中的原话:

关系操作符生成的是一个boolean结果,它们计算的是操作数的值之间的关系

总结:

  • 当==比较基本数据类型的时候,就是比较他们本身的值。

  • 而==比较引用数据类型的时候, 就是比较他们在内存的地址

  • equals用来比较引用数据类型一般都要重写该方法。例如String类就重写了这个方法,

  • 如果没有重写equals就直接比较,就是比较他们的内存地址

  • equals不能用于基本数据类型
    这里写图片描述
    上面的str中存储的并不是“a”,而是它所指向的对象的地址。所以将他赋值给str2,比较他俩时返回的就是true
    这里写图片描述
    上面的Student类并没有重写equals方法,结果位false

    还有hashCode()方法
    这里写图片描述

六、int和Integer的区别

基本区别:

  • (1)Integer是int的包装类;int是基本数据类型;
  • (2)Integer变量必须实例化后才能使用;int变量不需要;
  • (3)Integer实际是对象的引用,指向此new的Integer对象;int是直接存储数据值 ;
  • (4)Integer的默认值是null;int的默认值是0。

深入比较:

package com.nuc;

public class Demo {
    public static void main(String[] args) {
        Integer i1 = new Integer(1);
        Integer i2 = new Integer(1);
        Integer i3 = 1;
        Integer i4 = 127;
        Integer i5 = 127;
        Integer i6 = 128;
        Integer i7 = 128;
        int i8 = 1;
        //1、直接比较时,比较两者的地址,每次new生成的都是新的对象
        System.out.println(i1==i2);//false
        //2、integer和int比较时会自动拆箱转换为int类型,然后比较两者的值
        System.out.println(i1==i8);//ture
        //3、非new生成i3指向的是java常量池的对象,new是新生成的
        System.out.println(i1==i3);//false
        //4、127和128问题,JavaAPI中对Integer定义:在-128到127(含)之间的数会缓存,只存在一个对象中,即在此创建只是从缓存中取
            //超过这个每次创建就会new,即产生新的对象
        System.out.println(i4==i5);//true
        System.out.println(i6==i7);//false
    }
}

这里写图片描述
以上是测试结果

七、null和“”的区别

本质区别:

  • null是没有地址的
  • ""是有地址的,里边的内容是空的

具体区别:

  • 1、做成员变量(字段/属性/类变量)时,如果只写String str;那么是默认赋值为null的。null的话,你屏幕输出(toString方法)的结果为字符串null,而且其它调用str的操作,编译可通过,运行时会空指针异常,此时是异常。

  • 2、做局部变量(方法里的变量)时,如果只写String str;是不会默认赋值null的,这里仅声明了一个str变量,在栈内存中有定义,但没有任何值,null其实也是一种值。此时任何调用str的操作,编译时就均会报错,是error,不是异常。

  • 3、“”是空字符串,但也是字符串,没有什么东西。 而null是空的意思,什么都没有,没有地址
    这里写图片描述
    如果你直接输出str1,本质上其实是调用了toString方法。所以就会报错
    也 就 是 说 , 你 对 没 有 地 址 的 东 西 做 做 操 作 ( 除 了 = ) , 就 会 报 错 \color{red}{也就是说,你对没有地址的东西做做操作(除了=),就会报错} 西(=)

    String str = null;String str1; 这两者也是不同的,前者是分配了内存,也就是说,你对他可以进行相关操作,而不会报错,后者是不可以的。

八、final、finally和finalize的区别

f i n a l : \color{red}{final:} final关键字,是个修饰符。final不可以和abstract共存

  • 修饰类: 这样意味着这个类不能有子类,也不能作为父类被继承
  • 修饰方法: 这样意味着这个方法只能使用,不可以被重写,可以被重载
  • 修饰变量: 这样意味着这个变量在使用过程中不可以被改变,声明时必须赋予初始值,后来用的时候只能读取

f i n a l l y : \color{red}{finally:} finally是异常处理时提供finally块来执行一些操作,不管有没有异常被抛出、捕获,finally块都会被执行

  • try,catch,finally,try中的内容如果没有异常时会执行到底,然后去执行finally代码块(如果有的话);catch代码块中的内容是:try中的内容遇到异常时来执行,try中遇到异常那一行代码之后的代码都不会执行,然后继续执行finally代码块(如果有的话);finally中的代码是始终都会执行的

  • t r y 和 c a t c h 是 否 必 须 连 用 ? 不 是 。 t r y 能 否 可 以 独 立 存 在 ? 不 能 。 \color{red}{try和catch是否必须连用?不是。try能否可以独立存在?不能。} trycatchtry

    也就是说:try不可以单独存在,必须和catch和finally的其中一个连用
    在这里插入图片描述

f i n a l i z e : \color{red}{finalize:} finalize是个方法名

  • java允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。 这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在object类中定义的,因此所有的类都继承了它。子类覆盖finalize()方法以整理系统资源或者被执行其他清理工作。finalize()方法是在垃圾收集器删除对象之前,对这个对象调用的

九、异常和错误

  • 异常
    • 异常即exception
    • 异常:可以预知、可以通过修改程序来弥补”错误”,可避免的
  • 错误
    • 错误:error
    • 错误:无法预知的,系统级别的,程序员修改程序是无法修复的;例如:系统宕机,JVM挂掉了
      这里写图片描述
  1. 粉红色的是编译时异常(checked exceptions),其必须被 try{}catch语句块所捕获,或者在方法签名里通过throws子句声明.受检查的异常必须在编译时被捕捉处理,命名为 Checked Exception 是因为Java编译器要进行检查,Java虚拟机也要进行检查,以确保这个规则得到遵守.

  2. 绿色的异常是运行时异常(runtime exceptions),需要程序员自己分析代码决定是否捕获和处理,比如 空指针,被0除…

  3. 而声明为Error的,则属于严重错误,如系统崩溃、虚拟机错误、动态链接失败等,这些错误无法恢复或者不可能捕捉,将导致应用程序中断,Error不需要捕捉。

    常见的异常总结:

    • 1、java.lang.NullPointerException(空指针异常)
        调用了未经初始化的对象或者是不存在的对象
    • 2、java.lang.ClassNotFoundException
        指定的类不存在
    • 3、java.lang.NumberFormatException
      字符串转换为数字异常
    • 4、java.lang.IndexOutOfBoundsException
      数组下标越界异常
    • 5、 java.lang.ArithmeticException
      数学运算异常
    • 6、 java.lang.ClassCastException
      数据类型转换异常
    • 7、java.lang.FileNotFoundException
      文件未找到异常
    • 8、java.lang.IllegalArgumentException
      方法的参数错误
    • 9、 java.lang.NoSuchMethodException
      方法不存在异常
    • 10、java.lang.NoClassDefFoundException
      未找到类定义错误

    违背安全原则异常:SecturityException

    操作数据库异常:SQLException

    输入输出异常:IOException

    通信异常:SocketException

  • 异常的处理

  • 功能抛出几个异常,功能调用如果进行try处理,需要与之对应的catch处理代码块,这样的处理有针对性,抛几个就处理几个。
    特殊情况:try对应多个catch时,如果有父类的catch语句块,一定要放在下面。

  • throws 和throw

  • throws 方法声明时使用,表示该方法可能产生的异常,抛出,谁调用谁捕获处理,throws用在函数上。

  • throw方法体中使用,表示抛出一个具体的异常对象,谁调用谁捕获处理,throw用在函数内。

  • catch中存在return,此时finally中是否被执行?执行。但finally外边不执行。(其实就不能有语句)

十、Java中23种设计模式

篇幅过长,具体请看大神总结的:

Java之美[从菜鸟到高手演变]之设计模式

Java之美[从菜鸟到高手演变]之设计模式二

Java之美[从菜鸟到高手演变]之设计模式三

Java之美[从菜鸟到高手演变]之设计模式四

转自:http://blog.csdn.net/zhangerqing

GIT上关于设计模式的资料

十一、java多线程

  • 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.
  • 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
  • 区别并发和并行: 并发是指:多个事件在同一时间间隔内发生,并行是指:多个事件在同一时刻发生

更多概念参考操作系统基础知识总结整理的第四大点:处理机管理

更多多线程的知识请参考Java中的多线程你只要看这一篇就够了

十二、GC(垃圾收集)

  • 什么是GC?

    • GC是垃圾收集的意思(Gabage Collection)
  • 为什么要有GC?

    • 内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显示操作方法
  • 垃圾回收器的基本原理是什么?

    • 对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是"可达的",哪些对象是"不可达的"。当GC确定一些对象为"不可达"时,GC就有责任回收这些内存空间。
  • 有什么办法主动通知虚拟机进行垃圾回收?

    • 程序员可以手动执行System.gc()或者Runtime.getRuntime().gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行。 我们只是“提醒”一下

    关于Java堆内存结构,分代回收算法,垃圾收集器,GC日志,JVM参数使用,请参考JavaGC介绍

引用Java编程思想中的片段(结论):

1 、 垃 圾 收 集 并 不 等 于 “ 破 坏 ” ! \color{red}{1、垃圾收集并不等于“破坏”!} 1

2 、 我 们 的 对 象 可 能 不 会 当 作 垃 圾 被 收 掉 ! \color{red}{2、我们的对象可能不会当作垃圾被收掉!} 2

有时可能发现一个对象的存储空间永远都不会释放,因为自己的程序永远都接近于用光空间的临界点。若程序执行结束,而且垃圾收集器一直都没有释放我们创建的任何对象的存储空间,则随着程序的退出,那些资源会返回给操作系统。这是一件好事情,因为垃圾收集本身也要消耗一些开销。如永远都不用它,那么永远也不用支出这部分开销。

3 、 垃 圾 收 集 只 跟 内 存 有 关 ! \color{red}{3、垃圾收集只跟内存有关!} 3

十三、Java编译一个.java文件生成的.class文件有多少?

  • 一个.java文件中定义多个类,注意一下几点:
    • (1) public权限类只能有一个(可以一个都没有,但最多只有一个);
    • (2)这个.java文件名只能是public 权限的类的类名;
    • (3)倘若这个文件中没有public 类,则它的.java文件的名字是随便的一个类名;
    • (4)当用javac命令生成编译这个.java 文件的时候,则会针对每一个类生成一个.class文件;
    • (5)内部类也产生.class文件
    • (6)接口也产生.class文件

测试类:

package com.nuc;

public class Demo {
    class a{ }
    public static void main(String[] args) {
        System.out.println("f");
    }
}
class c{ }
interface a{ }

测试结果
这里写图片描述
可以看出,内部类的.class文件的命名格式为,外部类名$内部类名.class,外部类和内部类名使用符号$隔开

如果有两层内部类,如下图:
这里写图片描述

那么继承父类,实现接口的情况呢?

图一:
这里写图片描述
图二:
这里写图片描述

图一是,接口和父类,Demo类在同一包下测试的结果,编译Demo产生的class文件

图二是,不在同一包下的测试结果,Demo类编译时的class文件

那么匿名内部类的情况呢

匿名内部类的创建格式:

new 父类构造器(参数列表)|实现接口()
    {  
     //匿名内部类的类体部分  
    }

结果:
这里写图片描述
可见,匿名内部类,也生成class文件,由于匿名内部类没有名字,所以就按数字来命名,其规则是按照匿名内部类的顺序递增的。命名格式和内部类相似,名字换成数字即可

十四、list ,set,map集合

1、List、Set、Map 的区别
在这里插入图片描述
2、HashMap 和 Hashtable 有什么区别?

  • 存储:HashMap 运行 key 和 value 为 null,而 Hashtable 不允许。
  • 线程安全:Hashtable 是线程安全的,而 HashMap 是非线程安全的。
  • 推荐使用:在 Hashtable 的类注释可以看到,Hashtable 是保留类不建议使用,推荐在单线程环境下使用 HashMap 替代,如果需要多线程使用则用 ConcurrentHashMap 替代。

3、如何决定使用 HashMap 还是 TreeMap?
对于在 Map 中插入、删除、定位一个元素这类操作,HashMap 是最好的选择,因为相对而言 HashMap 的插入会更快,但如果你要对一个 key 集合进行有序的遍历,那 TreeMap 是更好的选择。

4、HashMap 的实现原理?

  • HashMap 基于 Hash 算法实现的,我们通过 put(key,value)存储,get(key)来获取。当传入 key 时,HashMap 会根据 key. hashCode() 计算出 hash 值,根据 hash 值将 value 保存在 bucket 里。当计算出的 hash 值相同时,我们称之为 hash 冲突,HashMap 的做法是用链表和红黑树存储相同 hash 值的 value。当 hash 冲突的个数比较少时,使用链表否则使用红黑树。

5、HashSet 的实现原理?

  • HashSet 是基于 HashMap 实现的,HashSet 底层使用 HashMap 来保存所有元素,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层 HashMap 的相关方法来完成,HashSet 不允许重复的值。

6、ArrayList 和 LinkedList 的区别是什么?

  • 数据结构实现:ArrayList 是动态数组的数据结构实现,而 LinkedList 是双向链表的数据结构实现。
  • 随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找。
  • 增加和删除效率:在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高,因为 ArrayList 增删操作要影响数组内的其他数据的下标。

综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,更推荐使用 LinkedList。

7、数组和 List 之间的转换?

  • 数组转 List:使用 Arrays.asList(array) 进行转换。
  • List 转数组:使用 List 自带的 toArray() 方法。

8、ArrayList 和 Vector 的区别是什么?

  • 线程安全:Vector 使用了 Synchronized 来实现线程同步,是线程安全的,而 ArrayList 是非线程安全的。
  • 性能:ArrayList 在性能方面要优于 Vector。
  • 扩容:ArrayList 和 Vector 都会根据实际的需要动态的调整容量,只不过在 Vector 扩容每次会增加 1 倍,而 ArrayList 只会增加 50%。

9、Array 和 ArrayList 有何区别?

  • Array 可以存储基本数据类型和对象,ArrayList 只能存储对象。
  • Array 是指定固定大小的,而 ArrayList 大小是自动扩展的。
  • Array 内置方法没有 ArrayList 多,比如 addAll、removeAll、iteration 等方法只有 ArrayList 有。

十五、java中的不定参数

先来看俩例子

第一个:

public class VariArgs {  
    public static void main(String[] args) {  
        test();  
        test("aaa");  
        test("aaa", "bbb");  
        test("aaa", "bbb", "ccc");  
    }  
  
    public static void test(String... args) {  
        System.out.println(args.getClass());  
        for (String arg : args) {  
            System.out.println(arg);  
        }  
    }  
}  

第二个:

public class VariArgs {  
    public static void main(String[] args) {  
        test("aaa");  
        test("aaa", "bbb");  
        test("aaa", "bbb", "ccc");  
    }  
  
    public static void test(String arg, String... args) {  
        System.out.println(arg);  
        System.out.println("----------------");  
        for (String s : args) {  
            System.out.println(s);  
        }  
    }  
}  

这俩例子可以自己跑一下,看看结果。
其实,不定参数就是如上面的格式类型... 参数名称

一个这样的不定参数,可以代表这个方法的参数大于等于0个,如第一个例子。

第二个例子就代表参数大于等于1个。

java中不定参数有两个规定:
第一,方法的参数列表中最多只有一个不定长度的参数;
第二,就是不定长度的数组的位置必须是最后一个参数。不然不能通过编译。
对于第一个规定,是因为如果有多于一个不定长度的参数,当真正调用这个方法的时候,JVM、将不知道前面的不定长度参数有多长, 下一个参数是从哪里开始。第二个也是jvm是无法确定长度的

方法重载的注意事项:

下面这种写法是“不对”的:

public static void test(Object... objs, Object obj);  
  
public static void test(Object obj1, Object... objs, Object obj2);  
  
public static void test(Object... objs1, Object... objs2);  

例子:

public class VariArgs {  
    public static void main(String[] args) {  
        test();  
        test("aaa");  
        test("aaa", "bbb");  
        test("aaa", "bbb", "ccc");  
    }  
  
    public static void test(String... args) {  
        System.out.println(args.getClass());  
        for (String arg : args) {  
            System.out.println(arg);  
        }  
    }  
  
    public static void test(String arg, String... args) {  
        System.out.println(arg);  
        System.out.println("----------------");  
        for (String s : args) {  
            System.out.println(s);  
        }  
    }  
}  

也可以跑一下试试,你会发现是编译通过不了的。

不定参数的方法可以重载。(大家都知道,重载就是方法名相同,而方法的参数列表不同)

但,这样写编译到 test("aaa");这行代码时,编译器是不知道取哪个方法的。所以出错

自我思考:如果重载的方法的方法体是完全相同的。那么上面的这个例子,是可以看做:第一个test方法,“囊括了”第二个test方法,也就是说,第二个test能做的,我第一个test照样能做。那么这样的重载将毫无意义。

十六、length、length()、size()的区别

length不是方法,是属性,数组的属性;

size()方法是针对集合(list,map,set)

length()是字符串String的一个方法;

还有数组的一些常用方法:要牢记:

1.concat(): 连接两个数组
2.join(): 将数组拼接成字符串, ()里参数可有可无,默认为“,”
3. toString(): 将数组拼接成字符串,有无参数都是使用“,”
4.push(): 在数组末尾添加
5.pop():  在数组末尾删除
6.unshift(): 在数组开头添加
7.shift(): 在数组开头删除
8.sort(): 排序默认升序
9.reverse(): 反向,倒序
10.slice(start, end): 选择数组, 选取数组组中 [start, end)的元素 ,原数组不改变

字符串常用的一些方法

endsWith(String suffix) 
测试此字符串是否以指定的后缀结尾。

concat(String str) 
将指定的字符串连接到该字符串的末尾。 

charAt(int index) 
返回 char指定索引处的值。

compareTo(String anotherString) 
按字典顺序比较两个字符串。 

contains(CharSequence s) 
当且仅当此字符串包含指定的char值序列时才返回trueequals(Object anObject) 
将此字符串与指定对象进行比较。 

equalsIgnoreCase(String anotherString) 
将此 String与其他 String比较,忽略案例注意事项。

indexOf(String str) 
返回指定子字符串第一次出现的字符串内的索引。 

isEmpty() 
返回 true如果,且仅当 length()0split(String regex) 
将此字符串分割为给定的 regular expression的匹配。 

length() 
返回此字符串的长度。 

substring(int beginIndex, int endIndex) 
返回一个字符串,该字符串是此字符串的子字符串。

trim() 
返回一个字符串,其值为此字符串,并删除任何前导和尾随空格。
//去掉所有的空白可以用replaceAll(" ","");

replaceAll(String regex, String replacement) 
用给定的替换替换与给定的 regular expression匹配的此字符串的每个子字符串。

.....太多了

十七、java中的序列化和反序列化到底是什么鬼?

首先:类的对象会随着程序的终止而被GC销毁。但是我们需要保存下来,所有就用到了序列化,把对象存为字节流

对象序列化: 是一个用于将对象状态转换为字节流的过程,从字节流创建对象的相反的过程称为反序列化。

成为流以后,就可以将其保存到磁盘文件中或通过网络发送到任何其他程序;

在java中只需要,在让该类实现接口java.io.Serializable
Serializable接口,只是一个标记接口,就是说,它没有方法和属性。。。仅用于标识可序列化的语义。好奇的同学可以去看API文档,确实没有。

serialVersionUID的介绍: SerialVersionUID是一个标识符,当它通常使用对象的哈希码序列化时会标记在对象上。说白了就是用来控制对象版本的。 也就是说:

在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是InvalidCastException。

serialVersionUID的两种显示的生成方式:

  • 一是默认的1L,比如:private static final long serialVersionUID = 1L;
  • 二是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,比如:private static final long serialVersionUID = xxxxL;(正儿八经的,是从这儿来控制版本的,当您添加或修改类中的任何方法或者属性时,seriaVersionUID就会发生变化,然后…

Transient 关键字: transient修饰符仅适用于变量,不适用于方法和类。用来标识该关键字不能被序列化。
在这里插入图片描述
由于,父类没有实现序列化接口,所以:attr1、attr2、attr3、attr5 都不会被序列化,放在父类中的好处在于当有另外一个 Child 类时,attr1、attr2、attr3 依然不会被序列化,

十八、static深入分析

基本介绍:static的存在就是为了,不创建对象来调用这些属性或者方法,也是核心思想。

几种使用场景:

  • 1、修饰“内部类”
  • 2、修饰变量
  • 3、修饰方法
  • 4、修饰代码块

首先:1、修饰“内部类”,要注意普通类是不允许声明为静态的,只有内部类才可以。

类似这样:

/**
 * @Author easychill
 * @Date 2019/12/19 15:44
 * @Version 1.0
 */
public class Test {
    public static class InnerClass{
        InnerClass(){
            System.out.println("static innerClass");
        }
        public void innerMethod(){
            System.out.println("static innerMethod");
        }
    }

    public static void main(String[] args) {
        InnerClass innerClass = new Test.InnerClass();
        innerClass.innerMethod();
    }
}

结果:
在这里插入图片描述
2,3,修饰变量和方法,大家应该都清楚,直接类名打点进行调用。

第4点:修饰代码块时,执行的顺序(加载的顺序)如下:

父类静态变量
父类静态代码块
子类静态变量
子类静态代码块
父类普通变量
父类普通代码块
父类构造函数
子类普通变量
子类普通代码块
子类构造函数

总结一下就是,静态的先被加载(在这个基础上,父类优先于子类,在父类优先于子类的基础上,变量优先于代码块优先于构造函数(有的话))
代码块用得着变量的,不先加载不出问题?我是这样理解的,各位可以有自己的记忆方式

也可以自己去做测试:

//静态代码块
 static {
        System.out.println("...");
 }

硬货来了…

通过我们前面的积累,我们知道了jvm的内存是怎么样的。

现在,我们来说,jvm的方法区:

方法区:

1、又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。

2、方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。

静态变量是所有线程共享的,所以不会消失。所以它具有那些特性。

一个小结:

1、static是一个修饰符,用于修饰成员。(成员变量,成员函数)static修饰的成员变量 称之为静态变量或类变量。

2、static修饰的成员被所有的对象共享。

3、static优先于对象存在,因为static的成员随着类的加载就已经存在。

4、static修饰的成员多了一种调用方式,可以直接被类名所调用,(类名.静态成员)。

5、static修饰的数据是共享数据,对象中的存储的是特有的数据。

持 续 更 新 。 。 。 \color{blue}{持续更新。。。}
\color{red}{}

  • 88
    点赞
  • 519
    收藏
    觉得还不错? 一键收藏
  • 12
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值