Java基础回顾(一)
0 Views java with
本文字数:3,659 字 | 阅读时长 ≈ 14 min

Java基础回顾(一)

0 Views java with
本文字数:3,659 字 | 阅读时长 ≈ 14 min

对象与类

对象的创建

比如Student s = new Student()实例化一个对象,其实经历了如下几个过程:

  1. Student.class加载到内存中
  2. 栈内存中给s开辟内存空间。
  3. 堆内存Student类申请一个内存空间。
  4. 给成员变量进行默认初始化,0 null false…
  5. 自定义给成员变量初始化赋值
  6. 初始化完毕,把堆内存地址赋值给栈内存的s变量

Main方法剖析

public static void main(String[] args) { ... }

static关键字

static关键字特点:(可以修饰成员变量,也可修饰成员方法)

拓展

静态方法中没有this关键字,因为this代表当前方法对象,但static优于对象存在,所以在对象还未创建完毕static修饰的方法就被调用,此时this代表的对象还未创建。

String

String底层定义为public final class String,说明String是常量,一旦被创建就不能修改。可以查看如Integer Long String这些类的源码:

public final class Integer {}
public final class Long {}
public final class String {}

这些基本类型,在初始化值、赋值时都是先从常量池中取数据,如果常量池中没有该数据,就new对象初始化为新数据。

比如常见的一个面试题:

String s = "ab";
s = "abc";
String ss = "ab";
ss = new String("ab");

这个sss各自创建了几个对象?答案:s创建两个对象;ss创建一个对象。因为s的常量池中有值ab,而重新赋值s = "abc"这个abcs的常量池中不存在,所以new String()创建了一个新对象。ss同理分析。可以通过如下方式验证:

String ss = "ab";
System.out.println(ss.hashCode());
ss = "abc";
System.out.println(ss.hashCode());

StringBuffer

String是不可变的字符串,StringBuffer是线程安全的可变字符串,用StringBuffer做字符串的拼接可以避免资源的浪费,因为String每次拼接新的字符串都是创建一个新的String对象。

String转换为StringBuffer

//方式一
String s = "hello";
StringBuffer sb = new StringBuffer(s);
//方式二
StringBuffer sb = new StringBuffer();
sb.append(s);

StringBuffer转换成String

//方式一
StringBuffer sb = new StringBuffer("hello");
String s = new String(sb);
//方式二
String s = sb.toString();

面试题

String, StringBuffer, StringBuilder 的区别?

StringBuffer和数组的区别?

String和StringBuffer作为参数传递

public class StringBufferDemo {
    public static void main(String[] args) {
        String s1 = "hello";
        String s2 = "world";
        System.out.println(s1 + "---" + s2);// hello---world
        change(s1, s2);
        System.out.println(s1 + "---" + s2);// hello---world

        StringBuffer sb1 = new StringBuffer("hello");
        StringBuffer sb2 = new StringBuffer("world");
        System.out.println(sb1 + "---" + sb2);// hello---world
        change(sb1, sb2);
        System.out.println(sb1 + "---" + sb2);// hello---worldworld

    }

    public static void change(StringBuffer sb1, StringBuffer sb2) {
        sb1 = sb2;
        sb2.append(sb1);
    }

    public static void change(String s1, String s2) {
        s1 = s2;
        s2 = s1 + s2;
    }
}

参数传递

Java中的参数传递:

例如:

public class Demo01_Object {
    public static void main(String[] args) {
        int a = 10;
        int b = 20;
        change(a, b);
        System.out.println("main: a:" + a + ", b:" + b); //10, 20
        int[] arr = {1, 2, 3};
        change(arr);
        System.out.println("main: " + arr[0]); //2
    }

    private static void change(int a, int b) {
        a = b;
        b = a + b;
        System.out.println("change: a:" + a + ", b:" + b); //20, 40
    }

    private static void change(int[] arr) {
        arr[0] = arr[1];
        System.out.println("change" + arr[0]); //2
    }
}

引入概念: 在Java中一个对象变量并没有实际包含一个对象,而仅仅引用一个对象所有的Java对象都储存在堆内存中。例如:Date t = new Date()其中的t就是一个对象变量,new Date()是在堆内存中开辟了一个空间,而t指向new Date()的堆内存地址。

因此,在上述代码中a b都是基本类型,而int[]是一个引用类型,那基本类型形式参数改变对实际参数没有影响对象类型形式参数改变直接影响实际参数

总结

Java程序语言总是采用按值调用,也就是说,方法得到的是所有参数值的一个拷贝,特别的,方法不能修改传递给他的任何变量的内容。

比如:下列是无意义的:

public static void swap(Employee x, Employee y) {
    Employee temp = x;
    x = y;
    y = temp;
}

当调用swap(e1, e2)时并不会改变e1e2的对象引用,swap方法的参数x,y被初始化为两个对象引用的拷贝,这个方法交换的是这两个拷贝。

特别是对于Integer Long String这些类型数据,在初始化、赋值的时候都是从常量池中取数据,比如IntgerCache LongCache,如果常量池中没有就重新new对象,例如:

public static void main(String[] args) {
    String s = "123";
    System.out.println("main: " + s.hashCode()); //48690
    change(s);
    change2(s);
}
private static void change(String s) {
    s = "123";
    System.out.println("change: " + s.hashCode()); //48690
}
private static change2(String s) {
    s = "456";
    System.out.println("change2: " + s.hashCode()); //51669
}

对象类型参数的传递,实际上传递这个对象堆内存地址的拷贝,所以形式参数和原参数操作的都是同一个堆内存地址,即形式参数的改变会直接影响原参数。

成员变量和局部变量

成员变量和局部变量的区别:

构造方法

在Java中,当需要调用构造方法时,若该类没有定义构造方法,系统会自动提供一个无参构造方法;如果该类定义了构造方法(带参构造),系统将不再提供无参构造,必须手动定义。举例:

public class Demo2_Construct {
    public static void main(String[] args) {
        Demo2Student student = new Demo2Student();
        student.show();
        // Demo2School school = new Demo2School(); //error
    }
}

class Demo2Student {
    public void show() {
        System.out.println("this student show");
    }
}

class Demo2School {
    private int size = 1000;
    public Demo2School(int size) {
        this.size = size;
    }
}

final

final可以修改类、方法、变量。

特点:

面试题: final修饰局部变量的问题

初始化时机

final修饰的变量必须在构造方法完毕前被初始化,比如

public class Demo {
    final int WIDTH = 12;
    //final int HEIGHT; //error
    final int AREA;
    {
        AREA = 120;
    }
}

继承

  1. Java支持单继承不支持多继承,但Java支持多层继承
  2. 子类只能继承父类非私有成员(成员变量、成员方法)
  3. 子类不能继承父类的构造方法,但可以通过super关键字访问父类的构造方法。

子类和父类的关系

子类中的所有构造方法都默认访问父类的无参构造方法。因为子类继承父类,并可能使用父类中的数据,所以子类初始化前一定要完成父类的初始化。所以子类每一个构造方法第一行默认都是super()

public class Demo04_Extends {
    public static void main(String[] args) {
        Demo04Son son = new Demo04Son();
        son.show();
    }
}

class Demo04Son extends Demo04Parent{
    private int num = 10;
    public Demo04Son() {
        super();
    }

    public void show() {
        int num = 100;
        System.out.println(num);
        System.out.println(this.num);
        System.out.println(super.num);
    }
}

class Demo04Parent {
    public int num = 1;

    public Demo04Parent() {
        System.out.println("这是父类的无参构造函数");
    }
}

this-super

thissuper关键字的区别和使用场景?

区别:

场景:

加载顺序

public class Demo04_Extends2 {
    public static void main(String[] args) {
        Demo04Zi zi = new Demo04Zi();
    }
}
class Demo04Fu {
    static {
        System.out.println("Fu 静态代码块");
    }
    {
        System.out.println("Fu 构造代码块");
    }
    public Demo04Fu() {
        System.out.println("Fu 构造方法");
    }
}
class Demo04Zi extends Demo04Fu{
    static {
        System.out.println("Zi 静态代码块");
    }
    {
        System.out.println("Zi 构造代码块");
    }
    public Demo04Zi() {
        System.out.println("Zi 构造方法");
    }
}

结果:

Fu 静态代码块
Zi 静态代码块
Fu 构造代码块
Fu 构造方法
Zi 构造代码块
Zi 构造方法

静态代码块 > 构造代码块 > 构造方法

动态绑定

调用对象方法的执行过程:

  1. 编译器首先查看对象的声明类型和方法名。如调用change(a)方法,由于存在多个change()方法,JVM会先列举该类以及其超类中访问属性为public且名为change的方法。
  2. 接下来,JVM将查看调用方法时提供的参数类型,并且JVM会预先为每个类创建一个方法表(method table),JVM会直接从这个方法表中寻找名为change的方法中存在一个与提供的参数类型匹配的方法,这个过程称为重载解析
  3. 如果是privatestaticfinal方法或者构造器,那么JVM就能准确的知道调用哪个方法,我们将这种调用方式称为静态绑定。与此对应,调用的方法依赖于隐式参数的实际类型,并且在运行时实现动态绑定
  4. 当程序运行,并且采用动态绑定调用方法时,JVM就一定调用于此最适合的一个方法,否则从超类中继续寻找。

强制类型转换

将一个类型强制转换为另外一个类型的过程称为类型转换。数值类型直接(int) double这样转换;对象引用的转换也类似,实现将某个类的对象引用转换为另一个类的对象引用。

注意

内部类

一个类存在于另一个类中方法外,这个类就称为内部类;一个类存在于另一个类方法内,这个类称为局部内部类。

局部内部类

局部内部类可以直接访问外部类的成员,在局部位置可以创建内部类对象,通过对象调用内部类成员。

局部内部类访问局部变量注意事项?

​ 局部内部类访问局部变量必须用final修饰。因为局部内部类的声明周期比局部变量长,局部变量随着方法的调用而存在,随着调用完毕而消失;但局部内部类不一定消失,他调用一个消失的变量就会报错。

public class InnerClass {
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.show();
    }
}

class Outer {
    public void show() {
        int num2 = 10;
        class Inner {
            private void show() {
                System.out.println(num2);
            }
        }
        Inner inner = new Inner();
        inner.show();
    }
}

此时调用不会报错,但并没有加final修饰。这个类编译后会生成InnerClass.classOuter.class两个文件,我们来看下Outer.class:

class Outer {
    Outer() {}
    public void show() {
        final int num2 = 10;
        class Inner {
            Inner() {}
            private void show() {
                System.out.println(num2);
            }
        }
        Inner inner = new Inner();
        inner.show();
    }
}

其中的num2被自动加上了final修饰(这是因为JDK1.8的原因),所以如果你再添上num2 = 1000就会报错。

解决办法

上面说过了应该将num2final修饰。其原因就是Inner类的生命周期要比num2的声明周期长,当show()方法调用完毕后num2就已经消失了,但此时Inner类在堆内存中仍然存在,他调用一个不存在的变量就会报错。而用final修饰,这个变量成为常量,在初始化内部类的时候,final num2就在内部类中生成了一份拷贝,这个拷贝和这个内部类的声明周期相同,所以不会报错。


交流

以上仅是个人的见解,可能有些地方是错误的,深知自己的菜鸡技术,欢迎大佬指出。

个人建了一个Java交流群:671017003。 欢迎大佬或是新人入驻一起交流学习Java技术。


联系

If you have some questions after you see this article, you can contact me or you can find some info by clicking these links.

如果你觉得这篇文章帮助到了你,你可以帮作者买一杯果汁表示鼓励