main方法
一个程序有且只有一个main方法,main方法是java程序的唯一入口。
修饰符
修饰类 | 修饰方法 | 修饰域 | |
---|---|---|---|
public | 都可以访问 | 都可以访问 | |
private | 私有类 | 只能本类 | 只能本类 |
protected | 子类可以继承、访问,同包下的类也可以访问 | 子类可以继承、访问,同包下的类也可以访问 | |
default | 只有同包下的才可以访问 | 只有同包下的才可以访问 |
static关键字
- static修饰的方法只能调用static修饰的属性或者方法,普通方法可以调用static修饰的
- 静态变量被所有对象共享,在内存中只有一个副本,在类初次加载的时候才会初始化
类的加载顺序
java
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");
}
}
输出结果 :
解释 :
这道题还是有点难度的,答案错了也没关系。耐心看完这篇博客就什么明白了。
- 首先我们需要清除当类加载时,第一步是静态的属性进行创建,此时并不会进行赋值操作,也即
k=0,m1=null,m2=null等等
。 - 然后才是开始进行赋值操作,此时
k=1
;接下来创建实例对象m1,创建对象时是执行所有的非静态属性、代码块以及方法,最后执行构造方法。此时执行第一个静态代码块,并且此时的i和n是还没有赋值的,所以输出1:初始化块,i=0,n=0"
,然后i和n都完成++操作。 - 然后执行
private int j = print("j");
,故而输出2:j,i=1,n=1
,i和n也都完成++操作。 - 当所有的非静态属性、方法等都执行完毕,就执行自己的构造函数,故而输出
3:m1,i=2,n=2
,i和n也都完成++操作。 - 此时完成了m1对象的创建。代码继续向下执行,创建m2对象,其过程同m1对象的过程一样,先完成非静态的所有属性、代码块、方法,然后再执行构造函数。这里就不作过多赘述。
- 当m2对象也创建完成,代码继续向下执行,对i进行赋值操作,也就是执行
print("i")
,故而输出7:i,i=6,n=6
,i和n也都完成++操作。 - 接着向下执行,为n赋值为99。然后是静态代码块
static{print("静态块");n = 100;}
,这里要注意静态代码块也是赋值操作。因此输出8:静态块,i=7,n=99
。 - 然后就是执行我们的main方法,跟创建m1和m2时一样,只不过此时的n以及完成了赋值操作。
总结起来就一句话,类加载时先静态,并且按照顺序从上往下加载,先进行所有静态属性的初始化,然后才是赋值。创建对象时,只执行非静态的,然后再执行构造方法。
final
- final阻止变量二次赋值
- 修饰的类不能被继承。
- 修饰的方法不能被重写。
- 防止指令重排序,保障多线程下的可见性
基本数据类型
byte(8bit,-128~127) short int long float double char boolean
java
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存在精度的缺失
包装器类型
自动拆箱与自动装箱
- 自动装箱是指将基本类型自动转换成对应的包装类类型
- 自动拆箱则是指将包装类自动转换成基本类型
例如:
java
Integer a = 1234; // 自动装箱
int b = a; // 自动拆箱
int c = 1234;
System.out.println(a == c); // true 也是自动拆箱的过程
128陷阱
我们观察这一段代码,发现输出是ttft
java
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);
}
}
运行结果:
先给出结论,当Integer类型的数值在[-128,127]之间时,所有的都是同一个引用,而超出这个范围的就是两个不同的引用。
再给出原理:
我们从参考资料中找到这样一句话:装箱过程实际上是通过调用包装类的valueOf方法实现的。
即:
java
Integer a = 123;
等价于
Integer b = Integer.valueOf(123);
System.out.println(a == b);// true
我们就可以查看valueOf的源码:
java
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:
java
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是线程安全的。
八大排序
- 冒泡
java
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];
}
}
}
- 快排
java
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
- 完全二叉树-->大根堆
遍历无序的数组,每次用一个数去构建完全二叉树然后将这个完全的二叉树转化为大根堆,也即当前节点大于父节点就交换数字,然后继续和其父节点比较,直至小于或者到达根节点。 - 大根堆-->有序数组
将大根堆的根节点与末尾未处理的节点交换位置,然后再将除了已经处理过的节点之前的再变成大根堆,重复以上步骤
- ...
抽象类和接口
单继承多实现
Object
是所有类的父类
Object的方法
hoshcode() equals() notify() notifyAll() wait() toString()
hashcode与equals
为什么重写hashcode要重写equals?/为什么重写equals要重写hashcode/在什么情况下要重写hashcode方法。
equals是用来比较连个对象是否是同一个对象,使用equals判断出来两个对象相同时,那么他们的hashcode就一定是相同的,也就是说在进行hashtables存储时,这两个对象的存储位置是一个位置。但假设我们不重写hashcode就有肯出现我们认定相同的两个对象的hashcode的值不一样,存储在hashtables中的位置就不一样,这样就与我们的预期有了偏差。
内部类
- 局部内部类
- 普通内部类
- 静态内部类
- 匿名内部类:典型是Runnable的使用。
java
Thread t1 = new Thread(() -> System.out.println("12456"), "t1");
t1.start();