Skip to content

基础

约 3499 字大约 12 分钟

java

2025-02-14

1 jvm监控

在命令行输入 jvisualvm,即可打开

2 String

String 和 StringBuffer、StringBuilder 的区别是什么?String 为什么是不可变的?

  1. 可变性:String类中使用 final 关键字,所以不可变。 StringBuffer、StringBuilder 是可变的。
  2. 线程安全:String 不可变,线程安全。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 线程不安全。
  3. 性能: String 类改变,生成新 String 对象,指针指向新的String。StringBuffer和StirngBuilder 对本身操作,而且StirngBuilder 比 StringBuffer 能有 10%~15% 提升,但有多线程不安全的风险。

new String()

首先明白创建了几个对象就是在说把对象放在了几个地方。那么一共就俩地方,一个是String池,一个是堆。然后开始各种String的测试。定义String,无非两种。1)String s1 = "hello";2)String s2 = new String("hello");第一种,会先验证String池中有没有"hello",有的话s1指向"hello"。没有的话创建新的"hello"存入String池中并指向"hello"。第二种,会先验证String池中有没有"hello",有的话对String池不做任何操作。没有的话创建新的"hello"并存入String池中。然后继续在堆里创建new String("hello"),并指向它。

3 拆箱和装箱

  1. 装箱:将基本类型用它们对应的引用类型包装起来。
  2. 拆箱:将包装类型转换为基本数据类型。

4 ==和equals

  1. ==:基本数据类型比较的是值,引用数据类型比较的是内存地址。
  2. equals:类没有覆盖 equals() 方法时比较的是内存地址。类覆盖了 equals() 方法时,一般都覆写成比较对象内容,类如String,内容相等则返回true

两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对?

这个本质在考 “x和y是两个对象,x.equals(y)是true的时候,x和y的hash code一样吗?”

答:不对,因为java中的规定是——1)如果两个对象相同(equals 方法返回 true),那么它们的hashCode 值一定要相同;2)如果两个对象的 hashCode 相同,它们并不一定相同。


关键就是搞清楚equals判断的是什么,看源码。

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

那么这里引出来 == 和 equals 的区别了。

如果是基本变量,根本没有equals方法,就是用 == ,比较的就是内容。如果是new出来的对象,父类是object的这种,==比较地址,equals也比较地址。如果是java中重写了equals的类,比较什么就看重写内容了。(一般重写都是为了比较内容)举两个例子。

1)String中equals源码

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

2)ArrayList的父类AbstractList的源码

public boolean equals(Object o) {
    if (o == this)
    return true;
if (!(o instanceof List))
    return false;

ListIterator<E> e1 = listIterator();
ListIterator e2 = ((List) o).listIterator();
while (e1.hasNext() && e2.hasNext()) {
    E o1 = e1.next();
    Object o2 = e2.next();
    if (!(o1==null ? o2==null : o1.equals(o2)))
        return false;
}
return !(e1.hasNext() || e2.hasNext());
}

所以,如果非要实现equals 方法返回 true,hashCode还不同的情况,也可以。重写equals方法。再举个例子。

public class Equals {

    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof Equals) {
            return true;
        }
        return false;
    }

    public static void main(String[] args) {
        Equals A = new Equals();
        Equals B = new Equals();
        System.out.println(A.equals(B) == true);//true
        System.out.println(A.hashCode());
        System.out.println(B.hashCode());
    }
}

5 值传递和引用传递

Java中只有值传递

  1. 传递基本类型参数:例如(int a),行参会创建副本,在函数内行参修改影响副本内容,但不会影响实参的内容。
  2. 传递引用类型参数:例如(int[] arr),这里传递的是实参的地址,既然传递的是地址,那么行参也指向实参的地址,所以修改行参值的时候实参也会改变。

当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?

先说结论:java中没有引用传递,只有值传递。所以这种情况也是值传递。

值传递的定义是:方法调用时,实际参数把它的值传递给对应的形式参数,方法执行中形式参数值的改变不影响实际参。引用传递的定义是:也称为传地址。方法调用时,实际参数的引用(地址,而不是参数的值)被传递给方法中相对应的形式参数,在方法执行中,对形式参数的操作实际上就是对实际参数的操作,方法执行中形式参数值的改变将会影响实际参数的值。

看了值传递的定义后,有了疑问。

问:“这种情况应该不是值传递吧?违反了值传递的定义呀,因为对象的属性变化了。”答:“对象属性改了没问题,但是传递的参数并不是对象呀,所以它改不改不违反值传递。”问:“传递的参数不是对象,那是啥?”答:“是对象的引用的一个副本。”问:“这不还是引用吗?为啥不是引用传递?”答:“还是看定义,引用传递 直接传地址。这种情况传的引用,指向地址。所以这是通过传递对象引用副本的方式实现了引用传递的效果,但是,它是值传递。”

问:“确定是值传递的问题可以了,那String对象作为参数传递的话,为什么没有返回变化后的结果呢?”答:“额,那照这么说,Integer,Double等也会有此疑问是吧?”问:“是的。”答:“因为,Integer是int的包装类,Double是double的包装类,String是char[]的包装类。对包装类的值操作实际上是通过对其对用的基本类型操作实现的。”

char型变量中能不能存储一个中文汉字

char类型是用来存储Unicode编码的字符,unicode字符集中包含了汉字。只不过一些特殊的汉字没有被包含在字符集里。

unicode编码占用两个字节,char类型变量也是占用两个字节。

6 final

  1. 修饰基本数据类型变量:数值在初始化后不能更改。
  2. 修饰引用类型变量:初始化后不能指向其他对象。
  3. 修饰一个类:类不能被继承,成员变量隐形指定为final。

7 修饰符

作用域当前类同包子类其他
public
protected
default
private

8 成员变量和局部变量

成员变量不需要显式赋值,局部变量需要显式赋值,否则编译通过不了,为什么

  1. 成员变量不确定什么就会被取值出来用,还有可能在各个方法中是不同的值
  2. 显然局部变量非要有默认值也可以设计出来,不这么设计是因为:局部变量的作用就是在局部方法中某个地方取出来用。在一个方法体内赋值和取值的顺序是固定,先赋值,才能取值。没必要有默认值。

9 System.exit

在一个if-else判断中,如果我们程序是按照我们预想的执行,到最后我们需要停止程序,那么我们使用System.exit(0),而System.exit(1)一般放在catch块中,当捕获到异常,需要停止程序,我们使用System.exit(1)。这个status=1是用来表示这个程序是非正常退出。

10 blob和clob和string

// 由于UTF-8是多字节编码,需要用多个字节来表示一个字符的编码,所以也就出现了在转换之后byte[]数组长度、内容不一致的情况。
// 而ISO-8859-1编码是单字节编码    
String s1="test";  
Clob c = new SerialClob(s1.toCharArray());//String 转 clob  
Blob b = new SerialBlob(s1.getBytes(StandardCharsets.ISO_8859_1));//String 转 blob  

//也可以这样不传字符集名称,默认使用系统的  
//Blob b = new SerialBlob(s1.getBytes());  
String clobString = c.getSubString(1, (int) c.length());//clob 转 String  
String blobString = new String(b.getBytes(1, (int) b.length()),StandardCharsets.ISO_8859_1);//blob 转 String  

//前面若没传入字符集名称,则这里也不需要传入,以免出错  
//String blobString = new String(b.getBytes(1, (int) b.length()));  

System.out.println(clobString);  
System.out.println(blobString);

11 float f=3.4;是否正确?

不正确,java内,整数默认是int,浮点默认是double,支持向上转型,即int自动转long,float自动转double,但不支持自动向下转型。float f = (float)3.4; float f= 3.4f;都是对的。

short s1 = 1; s1 = s1 + 1;有错吗?short s1 = 1; s1 += 1;有错吗?

第一个有错,还是那句话,整数java默认是int。s1是short,1是int,相加,向上转型是int,int不能赋值给short,需要强转。s1 = (short) s1 + 1;第二个没错,s1 += 1;其实就是s1 = (short) s1 + 1;

13 堆,栈,静态区的分配?

heap区:(存储的基本单位)

1.保存对象的实例(new创建的对象和数组),实例属性,属性类型,对象本身的类型标记。2.存储的对象包含一个与之对应的class信息。3.JVM只有一个heap区,被所有的线程共享。4.一般人为释放,否则程序结束时由OS回收。

stack区:(运行的基本单位)

1.stack区存对象的引用,和基础数据类型。2.保存一个4个字节的heap内存地址,用来定位该对象引用的实例在heap区的位置。3.每个线程都有一个stack区,互相之间不能访问。

静态区/方法区:

1.被所有线程共享。2.包含所有class和static变量。3.初始化的全局变量和初始化的静态变量在一个区域,未初始化的全局变量和未初始化的静态变量在一个区域

14 2乘以8?

位运算:System.out.println(2<<3);即可。其实就是2乘(2的三次方)2的二进制往左移两位。2的二进制0010,移动之后,1000。换成十进制就是16。

15 i-=i+=i-=i+=i-=i--

这类题要记住两点,1、i的值都不会在这串运算中改变。2、从右往左算。

public static void main(String[] args) {
    int i = 5;
    //i=i-i+i-i+i-(i-1)
    //5-5+5-5+5-4
    System.out.println(i-=i+=i-=i+=i-=--i);//1
    i = 5;
    //i=i-i+i*i+i-(i-1)
    //5-5+5*5+5-4
    System.out.println(i-=i+=i*=i+=i-=--i);//-30
}

16 类初始化顺序

1 无继承关系

  1. static成员变量和static块(static成员变量和static块两者与前后顺序有关)
  2. 普通成员变量和非static块(普通成员变量和非static块与前后顺序有关)
  3. 构造函数
package test;

public class Father {

    public static String staticField = printStaticField();

    public String field = printField();

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

    {
        System.out.println("【非静态代码块】");
    }

    public static String printStaticField() {
        System.out.println("【静态变量】");
        return "静态变量";
    }

    public String printField() {
        System.out.println("【非静态变量】");
        return "非静态变量";
    }

    public Father() {
        System.out.println("【构造方法代码块】");
    }


    public static void main(String[] args) {
        new Father();
    }
}
/*
结果:
【静态变量】
【静态代码块】
【非静态变量】
【非静态代码块】
【构造方法代码块】
*/

2 有继承关系

  1. 静态初始化——父类static成员变量和父类static块(两者之间与前后顺序有关)
  2. 静态初始化——子类static成员变量和子类static块(两者之间与前后顺序有关)
  3. 父类初始化——父类普通成员变量和父类非static块(两者之间与前后顺序有关)
  4. 父类初始化——父类构造函数
  5. 子类初始化——子类普通成员变量和子类非static块(两者之间与前后顺序有关)
  6. 子类初始化——子类构造函数

2.1 父类

package test;

public class Father {

    public static String staticField = printStaticField();

    public String field = printField();

    static {
        System.out.println("【父类-静态代码块】");
    }

    {
        System.out.println("【父类-非静态代码块】");
    }

    public static String printStaticField() {
        System.out.println("【父类-静态变量】");
        return "父类-静态变量";
    }

    public String printField() {
        System.out.println("【父类-非静态变量】");
        return "父类-非静态变量";
    }

    public Father() {
        System.out.println("【父类-构造方法代码块】");
        show();
    }

    public void show() {
        System.out.println("【父类-普通方法】");
    }
}

2.2 子类

package test;

public class Son extends Father {

    public static String staticField = printStaticField();

    public String field = printField();

    static {
        System.out.println("【子类-静态代码块】");
    }

    {
        System.out.println("【子类-非静态代码块】");
    }

    public static String printStaticField() {
        System.out.println("【子类-静态变量】");
        return "子类-静态变量";
    }

    public String printField() {
        System.out.println("【子类-非静态变量】");
        return "子类-非静态变量";
    }

    public Son() {
        System.out.println("【子类-构造方法代码块】");
        show();
    }

    public void show() {
        System.out.println("【子类-普通方法】");
    }


    public static void main(String[] args) {
        new Son();
    }
}

输出(换行是我人为换行,为了方便观察):

【父类-静态变量】//只有第一次加载时执行
【父类-静态代码块】//只有第一次加载时执行
【子类-静态变量】//只有第一次加载时执行
【子类-静态代码块】//只有第一次加载时执行

【子类-非静态变量】//这里出现 子类 的原因是:子类方法被父类方法 覆盖
【父类-非静态代码块】
【父类-构造方法代码块】
【子类-普通方法】//这里出现 子类 的原因是:子类方法被父类方法 覆盖

【子类-非静态变量】
【子类-非静态代码块】
【子类-构造方法代码块】
【子类-普通方法】

在这里增加一个**继承关系里的方法覆盖知识点。**如果不想让父类方法被覆盖,那么可以用 在父类方法上加 final 关键字,但是加了关键字之后,子类中根本不允许存在和父类方法名相同的方法(方法参数一致的情况)。 那么还可以用 private 关键字,嗯,可以实现了。