Java基础---复习01

main方法

一个程序有且只有一个main方法,main方法是java程序的唯一入口。

修饰符

修饰类修饰方法修饰域
public都可以访问都可以访问
private私有类只能本类只能本类
protected子类可以继承、访问,同包下的类也可以访问子类可以继承、访问,同包下的类也可以访问
default只有同包下的才可以访问只有同包下的才可以访问

static关键字

  1. static修饰的方法只能调用static修饰的属性或者方法,普通方法可以调用static修饰的
  2. 静态变量被所有对象共享,在内存中只有一个副本,在类初次加载的时候才会初始化

类的加载顺序

public class Main {
    private static int k = 1;
    private static Main m1 = new Main("m1");
    private static Main m2 = new Main("m2");
    private static int i = print("i");
    private static int n = 99;
    {
        print("初始化块");
        j = 100;
    }
    public Main(String name) {
        System.out.println((k ++) + ":" + name + ",i=" + i + ",n=" + n);
        ++ i;
        ++ n;
    }
    static {
        print("静态块");
        n = 100;
    }
    private int j = print("j");
    public static int print(String str) {
        System.out.println((k ++) + ":" + str + ",i=" + i + ",n=" + n);
        ++ n;
        return ++ i;
    }

    public static void main(String[] args) {
        Main m = new Main("m");
    }
}

输出结果
在这里插入图片描述

解释
这道题还是有点难度的,答案错了也没关系。耐心看完这篇博客就什么明白了。

  1. 首先我们需要清除当类加载时,第一步是静态的属性进行创建,此时并不会进行赋值操作,也即k=0,m1=null,m2=null等等
  2. 然后才是开始进行赋值操作,此时k=1;接下来创建实例对象m1,创建对象时是执行所有的非静态属性、代码块以及方法,最后执行构造方法。此时执行第一个静态代码块,并且此时的i和n是还没有赋值的,所以输出1:初始化块,i=0,n=0",然后i和n都完成++操作。
  3. 然后执行private int j = print("j");,故而输出2:j,i=1,n=1,i和n也都完成++操作。
  4. 当所有的非静态属性、方法等都执行完毕,就执行自己的构造函数,故而输出3:m1,i=2,n=2,i和n也都完成++操作。
  5. 此时完成了m1对象的创建。代码继续向下执行,创建m2对象,其过程同m1对象的过程一样,先完成非静态的所有属性、代码块、方法,然后再执行构造函数。这里就不作过多赘述。
  6. 当m2对象也创建完成,代码继续向下执行,对i进行赋值操作,也就是执行print("i"),故而输出7:i,i=6,n=6,i和n也都完成++操作。
  7. 接着向下执行,为n赋值为99。然后是静态代码块static{print("静态块");n = 100;}这里要注意静态代码块也是赋值操作。因此输出8:静态块,i=7,n=99
  8. 然后就是执行我们的main方法,跟创建m1和m2时一样,只不过此时的n以及完成了赋值操作。

总结起来就一句话,类加载时先静态,并且按照顺序从上往下加载,先进行所有静态属性的初始化,然后才是赋值。创建对象时,只执行非静态的,然后再执行构造方法。

final

  1. final阻止变量二次赋值
  2. 修饰的类不能被继承。
  3. 修饰的方法不能被重写。
  4. 防止指令重排序,保障多线程下的可见性

基本数据类型

byte(8bit,-128~127) short int long float double char boolean

float x = 1;
float y = 0.9f;
System.out.println(x - y);
double a = 1;
double b = 0.9;
System.out.println(a - b);

我们会发现上边两个的结果没有一个是0.1,这是因为float和double存在精度的缺失

包装器类型

自动拆箱与自动装箱

  • 自动装箱是指将基本类型自动转换成对应的包装类类型
  • 自动拆箱则是指将包装类自动转换成基本类型

例如:

Integer a = 1234; // 自动装箱
int b = a; // 自动拆箱
int c = 1234;
System.out.println(a == c); //  true 也是自动拆箱的过程

128陷阱

我们观察这一段代码,发现输出是ttft

public class Main{
    public static void main(String[] args){
        Integer a = 120;
        Integer b = 120;
        int c = 120;
        System.out.println(a == b);
        System.out.println(a == c);
        Integer x = 1234;
        Integer y = 1234;
        int w = 1234;
        System.out.println(x == y);
        System.out.println(x == w);
    }
}

运行结果:
![[Pasted image 20240326081752.png]]

先给出结论,当Integer类型的数值在[-128,127]之间时,所有的都是同一个引用,而超出这个范围的就是两个不同的引用。
再给出原理:
我们从参考资料中找到这样一句话:装箱过程实际上是通过调用包装类的valueOf方法实现的。
即:

Integer a = 123; 
等价于 
Integer b = Integer.valueOf(123);
System.out.println(a == b);// true

我们就可以查看valueOf的源码:

    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

对这个源码的解释:当给定的参数在IntegerCache.low与IntegerCache.high之间时,我们就返回一个以及创建好的对象,否则就重新new一个对象。接着我们我们找到内部静态类IntegerCache:

    private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }

我们观察源码发现,当数值范围在[-128,127]是,都是存在一个叫作cache的Integer数组中,并且创建好了数组的长度是256,也就是说我们已经new了256个对象,他们的值是从-128到127的,然后存储到cache数组中。
因此我们就得到一个结论:当Integer的范围在[-128,127]时,使用的时已经创建好的对象,是同一个对象,而超出这个范围的都是需要程序new一个对象,他们就是不同的对象,因此使用==时就会出现218陷阱。此时我们就可以使用equals来比较大小。

String

不可变性:在String中有一个final修饰的char数组,也就是说其不可以赋值,但是char数组中的值是可以变的。但是在String中没有提供任何修改其值的方法。并且String是final修饰的类,也就是不可以被其他类继承之后而去修改char的内容。
StringBuffer是线程安全的,方法都加上了synchronized,StringBuilder是线程安全的。

八大排序

  • 冒泡
int[] x = new int[]{4, 48, 98, 78, 2, 0, 3, 1, 78};
int n = x.length;
for (int i = 0; i < n; i++) {
    for (int j = 0; j < n - i - 1; j ++) {
        if (x[j] > x[j + 1]) {
            x[j] ^= x[j + 1];
            x[j + 1] ^= x[j];
            x[j] ^= x[j + 1];
        }
    }
}
  • 快排
public static void QuickSort(int[] arr,int start,int end){
    if(start<end){
        int index=partition(arr,start,end);
        QuickSort(arr,start,end-1);
        QuickSort(arr,end+1,end);
    }
}
public static int partition(int[] arr,int low,int high){//划分算法
    int temp=arr[low];
    while(low!=high){
        //从尾开始找到第一个比temp小的,把他移到前边
        while(low<high&&arr[high]>=temp){
            high--;
        }
        arr[low]=arr[high];
        //从头开始找到第一个比temp大的,放到后边
        while(low<high&&arr[low]<=temp){
            low++;
        }
        arr[high]=arr[low];
    }
    arr[low]=temp;//此时的low就是temp的最终位置
    return low;
}
  • 推排序

完全二叉树:下标为n的左孩子是2n+1,右孩子是2n+2(n从0开始);左右孩子的父亲都是其下标减1再除以2

  1. 完全二叉树–>大根堆
    遍历无序的数组,每次用一个数去构建完全二叉树然后将这个完全的二叉树转化为大根堆,也即当前节点大于父节点就交换数字,然后继续和其父节点比较,直至小于或者到达根节点。
  2. 大根堆–>有序数组
    将大根堆的根节点与末尾未处理的节点交换位置,然后再将除了已经处理过的节点之前的再变成大根堆,重复以上步骤

抽象类和接口

单继承多实现

Object

是所有类的父类

Object的方法

hoshcode() equals() notify() notifyAll() wait() toString()

hashcode与equals

为什么重写hashcode要重写equals?/为什么重写equals要重写hashcode/在什么情况下要重写hashcode方法。
equals是用来比较连个对象是否是同一个对象,使用equals判断出来两个对象相同时,那么他们的hashcode就一定是相同的,也就是说在进行hashtables存储时,这两个对象的存储位置是一个位置。但假设我们不重写hashcode就有肯出现我们认定相同的两个对象的hashcode的值不一样,存储在hashtables中的位置就不一样,这样就与我们的预期有了偏差。

内部类

  • 局部内部类
  • 普通内部类
  • 静态内部类
  • 匿名内部类:典型是Runnable的使用。
   Thread t1 = new Thread(() -> System.out.println("12456"), "t1");
   t1.start();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值