1.一个Java程序有且仅有一个main方法作为程序的入口
由main方法所关联的
2.权限修饰符
修饰类 | 修饰方法 | 修饰域 | ||
public | 都可以访问 | 都可以访问 | 都可以访问 | |
protected | 不能修饰类 | 子类可以继承,可以访问,同包下的类也可以访问。可以直接访问父类,但是不能访问类的对象 | 可以访问同包下的类,也可以访问在同一个包中,继承有protected修饰的类的子类中 | |
default | 只有同包的可以访问 | 只有同包的可以访问 | 只有同包的可以访问 | |
private | 不能修饰类(外部类) | 不会被其他类访问,只能在本类中访问但是被private修饰的成员可以通过set和get方法向外界提供访问方式。 | 不会被其他类访问,只能在本类中访问 |
3.static关键字
1)静态类:静态内部类
2)修饰静态域:所有这个类的对象共享这一个域
3)修饰一个方法:类名可以直接调用,只能访问静态属性,不能访问普通类,不能调用普通方法
4.final关键字(内容自己整理):
final关键字可以用来修饰类,方法,变量
1)final修饰类:那么这个方法就不能被继承,final修饰的类中所有的成员方法都会隐式的定义为final方法
2)final修饰方法:表明此方法不可以被重写, 把方法锁定,以防止继承类对其进行更改。该方法不可被子类重写。但是可以被重载!
3)final修饰变量:尤其是使用final修饰形参时,表明此形参是一个常量。当我们调用此方法时,不会被默认初始化,所以必须是显式赋值,给常量形参赋一个实参,一旦赋值之后,就只能在方法体内使用此形参的值,不能重新进行赋值。否则通不过编译,如果final修饰一个引用类型时,则在对其初始化之后便不能再让其指向其他对象了或者说他的地址不能发生变化了,但该引用所指向的对象的内容是可以发生变化的。
5.基本数据类型:
1).整数型:byte(-128~127),short,int,long
2).浮点型:float,double
3).其他:boolean,char
6.包装类
java中的八种基本类型:byte、short、int、long、float、double、boolean、char
对应八种包装类:Byte、Short、Integer、Long、Float、Double、Boolean、Character
1).为啥要有包装类(包装类的作用)?
Java是一门面向对象的语言,而基本数据类型不属于对象,从而提供了包装类,在某些特定的情况下要用到包装类,比如,在集合类中,无法将int 、double等类型放进去的,因为集合的容器要求元素是Object类型。这时候就需要用到包装类了,此外,包装类还为基本类型添加了属性和方法,就能够更好的操作数据。
保留基本数据类型的必要:基本数据类型操作更见的简便,不用new对象。
2).什么是拆箱什么是装箱?
包装类和基本数据类型的装换主要就是通过自动装箱和自动拆箱来完成的,Java自动拆装箱这一过程编译器干的事
装箱:valueof()
拆箱:intvalue longvalue
//创建包装类对象有两种方式:new关键字、valueOf()方法。 Integer num1 = new Integer(1); //基本数据类型转为包装类 Integer integer = Integer.valueOf(10);//基本数据类型转为包装类 int num2 = num1.intValue(); //包装类型转为基本数据类型
自动装箱:将基本数据类型转换为对应的包装类对象。
自动拆箱:将包装类对象转换为对应的基本数据类型。
7.128陷阱(博客)
先来看一种情况
基本数据类型和包装类最大的区别就是,基本数据类型不是一个类,而包装类是一个类可以创建对象,对于基本数据类型==是直接比较值的,对于包装类来说,==比较的是地址c和d表示的是两个地址,c和d是两个对象,地址不同自然返回了false。
那为啥a==b就是true呢?
因为当值在[-128, 127] 之间时,valueOf 方法不会创建新的Integer对象,而是从缓存中获取,这样一来,值相同的int,计算所得的下标是一样的,自然会获取到同一个Integer对象。而超出这个范围就不会从缓存中获取了,每次都new一个新的Integer对象,两个不同的对象的内存地址不同,用==自然会得到false。
128陷阱指的是2个integer对象直接用==判定,在某个范围内可以判true,而超出这个范围则为false。
只有在两个包装类比较的时候会出现这个问题
8. String是一个不可变类型;
原因:
final修饰了类不可以被继承,不能通过继承的方式对类做出一定的修改
final修饰的char类型的数组,保证了一旦被赋值就不可改变
类中所有的方法没有修改数组内容的方法
StringBuffer,StringBuilder 提升String拼接效率的两个类;
StringBuffer是线程安全的,StringBuffer中的所有方法就加了synchronized 的锁
9.数组
每一个数组都是一个新的类型的
int数组就是不是int类型的就是一种int数组类型
student数组不是student类型的就是student数组类型
10.数组排序
1)冒泡排序
public static void main(String []args) {
int[] arr = {18,13,50,15,4,17,18};
System.out.println("arr的排序前:\n18 13 50 15 4 17 18 ");
int temp = 0 ;
for(int i = 0 ;i< arr.length -1; i++){
for(int j = 0; j<arr.length-1-i; j++){
if(arr[j]>arr[j+1]){
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
System.out.println("arr排序后:");
for(int i = 0; i<arr.length; i++){
System.out.print(arr[i]+"\t");
}
}
2)快排
public static void quickSort(int[] array, int low, int high) {
/**
* 分析:
* 1.选定一个基准值,array[low]
* 2.右指针从右向左遍历high--,查找比基准值小的数据,左指针从左向右low++,查找比基准值大的数据
* 3.如果指针未相遇,交换左右两值位置,如果指针相遇,则替换基准值的位置
* 4.左递归,右递归
*/
// 方法退出条件,指针相遇或错过
if (low >= high) {
return;
}
// 1. 指定基准值和左右指针记录位置
int pivot = array[low];
int l = low;
int r = high;
int temp = 0;
// 2. 遍历条件,左右指针位置
while (l < r) {
// 2.1 右侧遍历
while (l < r && array[r] >= pivot) {
r--;
}
// 2.2 左侧遍历
while (l < r && array[l] <= pivot) {
l++;
}
// 2.3 l指针还在r指针左侧,尚未相遇
if (l < r) {
temp = array[l];
array[l] = array[r];
array[r] = temp;
}
}
// 3. 当左右指针相遇,交换基准值位置
array[low] = array[l];
array[l] = pivot;
// 4. 根据条件左侧递归遍历
if (low < l) {
quickSort(array, low, l - 1);
}
// 5. 根据条件右侧递归遍历
if (r < high) {
quickSort(array, r + 1, high);
}
}
3)堆排序
public static void main(String[] args) {
int arr[] = {4, 6, 8, 5, 9};
System.out.println("排序前" + Arrays.toString(arr));
heapSort(arr);
System.out.println("排序后" + Arrays.toString(arr));
}
public static void heapSort(int arr[]) {
int temp = 0;
for (int i = arr.length / 2 - 1; i >= 0; i--) {
adjustHeap(arr, i, arr.length);
}
/**
* 将堆项元素与末尾元素交换,将最大元素"沉"到数组末端;
* 重新调整结构,使其满足堆定义,然后继续交换堆项元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。
*/
for (int j = arr.length - 1; j > 0; j--) {
temp = arr[j];
arr[j] = arr[0];
arr[0] = temp;
adjustHeap(arr, 0, j);
}
}
/**
* 将一个数组(二叉树)调整成一个大根堆
* 功能:完成将以i对应的非叶子结点的树调整成大顶堆
* 举例int arr[]={4, 6,8,5,9};=>i=1=> adjustHeap=>得到{4,9,8,5, 6}
* 如果我们再次调用adjustHeap 传入的是i=0=>得到{4,9, 8,5,6}=> {9,6,8,5, 4}
*
* @param arr 待调整的数组
* @param i 表示非叶子结点在数组中索引
* @param length 表示对多少个元素继续调整,length 是在逐渐的减少
*/
public static void adjustHeap(int arr[], int i, int length) {
int temp = arr[i];//先取出当前元素的值,保存在临时变量
//开始调整.
//说明:k=i*2+1k是i结点的左子结点
for (int k = i * 2 + 1; k < length; k = k * 2 + 1) {
if (k + 1 < length && arr[k] < arr[k + 1]) {
k++;
}
if (arr[k] > arr[i]) {
arr[i] = arr[k];
i = k;
} else {
break;
}
}
arr[i] = temp;
}
11.类
类没有定义构造函数的时候会自己提供一个无参的构造函数
抽象类:
抽象方法不能被 final 修饰,因为抽象方法就是被子类实现的
采用 abstract 关键字定义的类就是抽象类,采用 abstract 关键字定义的方法就
是抽象方法
抽象的方法只需在抽象类中,提供声明,不需要实现
如果一个类中含有抽象方法,那么这个类必须定义成抽象类。抽象类中不一定有抽象方法,抽象方法必须出现在抽象类中
final和abstract不能同时同时使用,这两个关键字是对立的
抽象类的子类可以是抽象类。也可以是非抽象类
一个非抽象的类,继承抽象类,必须将抽象类中的抽象方法进行覆盖/重写/实现
接口:
接口是特殊的抽象类,类与类是继承extends,类与接口是实现implements,其实都是继承
接口是一种“引用数据类型”,完全抽象的,支持多继承,且一个接口可以继承多个接口,只有常量+抽象方法
所有的元素都是public修饰的,抽象方法的public abstract可以省略,常量的public static final可以省略,方法不能有方法体
抽象类和接口的区别:
接口更多像是描述行为
抽象类更多的是描述某个类型的特性
抽象类是半抽象的,有构造方法,抽象类可以包含抽象方法和非抽象方法,也可以有成员变量(不仅仅是常量)
类和类之间只能单继承,一个抽象类只能继承一个类(单继承)
接口是完全抽象的,没有构造方法,接口和接口之间支持多继承,一个类可以同时实现多个接口
这在Java 8之前基本正确,但在Java 8之后,接口可以包含默认方法和静态方法
12.面向对象编程的特点:
1)封装:
是把对象的属性和操作(或服务)结合为一个独立的整体,并尽可能隐藏对象的内部实现细节。
当我们创建一个对象之后就可以通过 的方式进行赋值,但是实际过程中往往会不会直接赋值,我们会把属性设置为私有,然后通过get和set方法进行获取值和赋值。
作用:
- 保护我们的代码不会被我们无意间修改破坏
- 使得代码高内聚和低耦合
2)继承:
有两个类一个是动物还有一个类是猫,程序中可以描述为猫是继承(extends)自动物这个类的,那么这个动物类就可以称为猫的父类,猫就是动物的子类。
一个父类可以有多个子类但一个子类只能有一个父类就像一个人不能有多个亲爹一样,但一个父可以有多个孩子一样。父母对于孩子的爱是 无私的,所以在父类中的方法和属性都是非私有的。
多个类都有共同的属性,行为时,就可以向上抽取相同的属性行为,就不用在重新定义了。所以可以说,父类是由子类向上抽取共性而来的。
好处:
①减少了代码的冗余
②便于功能的扩展
③为之后的多态性的使用提供了前提
3)多态:
多态的前提
-
- 必须是父类引用子类的对象
- 要有方法的重写
- 有继承性关系
子类继承父类,可以重写父类的方法,子类对象 可以调用父类的方法
父类的引用指向子类的对象,可以直接应用在抽象类和接口上
JAVA引用变量有两个类型:编译时类型和运算时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。简称:编译时,看左边,运行时看右边
若编译时类型和运行时的类型不一样,就出现了对象的多态性(Polymorphism)
多态情况下,“看左边”:看得是父类的引用(父类中不具备子类特有的方法)
“看右边”:看得是子类的对象(实际运行的是子类重写父类的方法)
13.类加载的先后顺序:
执行顺序:(优先级从高到低。)静态代码块>mian方法>构造代码块>构造方法。
其中静态代码块只执行一次。构造代码块在每次创建对象是都会执行。
1 类加载从上往下执行,依次执行静态的初始化语句和初始化块,且类加载优先于对象创建。(静态初始化语句和静态初始化块只加载一次)
2 创建本类的对象时:从上往下执行一次非静态的初始化语向和初始化块,最后执行构造函数。
3 创建另一个类对象时:先加载另一个类的静态语句和静态初始化块(同样也只是第一次才会加载他的静态语句和初始化块,同样也只加载一次)。然后再加载其他类的非静态的初始化语向和初始化块,最后执行构造函数。
4 静态语句初始化时可以创建类对象。
5 静态方法可以只加载而不调用,不调用不执行。
首先,类加载顺序为从上往下执行,最先执行静态的初始化语句和初始化块(静态语句也是从上往下一行行执行),且所有的静态的初始化语句和初始化块只执行一次(总结中第1条)
下面类开始加载:
先给k赋初始值1,然后创建本类对象t1,由于在创建本类的对象时:从上往下执行一次所有的非静态的初始化语向和初始化块,最后执行该对象对应的构造函数。(总结中第2条)。
所以先执行代码中存在的非静态初始化块,这时候会调用块内的print函数,即输出 1:初始化块 i=0 n=0 (这里i和n都是默认值0是因为还没有对其进行初始化操作) 接着给j赋初始值100,由于最后执行构造函数且当前static语句还没有执行完(即11行代码),所以就该执行第30行代码了,及调用print函数,也就输出 2:j i=1 n=1,由于该11行static语句还没有执行完,所以main方法也不执行。到此第11行代码执行完毕。
接着就该第12行代码,也就是创建本类对象t2,和上面创建本类对象t1的步骤一致,也就把运行结果中的4 到6输出出来了。
接着执行第13行代码,也就会调用print函数,即输出7:i i=6 n=6。
接着执行第14行代码,也就是给n赋初始值99。
接着执行第26行静态块,也就会调用print函数,即输出8:静态块 i=7 n=99(因为此时n已被赋初始值99),然后n被赋值为100。这个静态块也就执行完了。
接着就是执行下一个静态,也就是到第37行main方法了,main方法里又是创建一个本类对象,那步骤就和上面创建t1和t2的一样了。首先执行一遍非静态初始化块,调用print函数,即输出9:初始化块 i=8 n=100,由于方法内执行了++n操作,此时n的值为101了。
接着执行第30行代码,也就是调用print函数,也就输出了 10:j i=9 n=101,++n后,n的值变为102.
最后就只剩下执行构造函数了,也就是输出 11:test i=10 n=102
然后执行++n和++i操作后,i=11,n=103。
14.Object类:
equals和==的区别:
在基础数据类型上是一样的都是比较值;
对于类来说,重写之后的equals是比较值,==是比较地址
为什么重写equals是要重写hashcode?
hashcode是可以理解为对象hashset或者hashmap中的唯一标识符
object类中的equal是根据地址比较是否一致,重写equals是为了比较值是否一致,euqals判断想等的话输出true的话有两个条件,一个是equals判断要相等,还要hashcode一致,如果我们创建了两个对象,两个对象的值相等,两个对象的地址不同hashcode也不相同,使用重写前的equals方法就会输出fasle,重写之后就会就变成了值得比较,这时候equals应该输出true,但是hashcode还是还是不同,就有冲突,所以这时候就需要重写hashcode。
Object类基本方法
1 toString⽅法
2 hashCode()
3 equals⽅法
4 Class getClass()
5 Object clone()-克隆
6 void finalize() -Gc垃圾回收机制
7.wait(long timeout)
8.wait(long timeout, int nanos)
9.wait()
10.notify()
11.notifyAll()
内部类:
1.局部内部类
2.普通内部类
3.静态内部类
4.匿名内部类:new一个抽象类和一个接口一个把
lamba表达式
public static void main(String[] args) {
Thread a= new Thread(new Abc());
a.start();
Thread b = new Thread(() -> System.out.println("234"));
b.start();
}
}
class Abc implements Runnable{
@Override
public void run(){
System.out.println("123");
}