1. 对象的概念
- 对象(object)也叫实例(instance):在内存中是一个块,用来表示一堆相关联的数据(变量和方法)。
- 类(class):对象的模板,系统根据类的定义来创造对象。
- 属性:用于定义该类或该对象包含的数据或静态特征。属性作用范围是整个类体。
- 在定义成员变量时可以对其初始化,如果不对其初始化,java使用默认的值对其初始化。
- 方法是一段用来完成特定功能的代码片段。用于定义该类的实例的行为特征和功能实现。方法是类和对象行为特征的抽象。面向对象中,整个过程的基本单位是类,方法是从属于类和对象的。
- 代码实例:
package cdj.lx.com;
public class SxtStu {
//属性fields
int id;
String name;
int age;
Computer comp;
//方法
void study(){
System.out.println("我认真学习!!使用电脑:"+comp.brand);
}
void play(){
System.out.println("我在玩游戏!王者荣耀!");
}
//构造方法。用于创建这个类的对象。无参的构造方法可以由系统自动创建。构造方法名必须要和类名一样
SxtStu(){
}
//程序执行的入口,必须要有
public static void main(String[] args) {
SxtStu stu = new SxtStu();
System.out.println(stu);//打印stu对象的地址,每次打印不一定一样
stu.id = 001;
stu.name = "alan";
stu.age = 27;
Computer c1 = new Computer();
c1.brand = "联想";
stu.comp = c1;
stu.study();
stu.play();
}
}
class Computer{
String brand;
}
2. 面向对象内存分析
- Java虚拟机的内存分为三个区域:栈stack,堆heap,方法区method area(方法区也在堆内)。
栈
- 栈:方法执行的内存模型。每个方法被调用都会创建一个栈帧(存储局部变量、操作数、方法出口等)。
- JVM为每个线程创建一个栈,用于存放该线程执行方法的信息(实际参数、局部变量等)。
- 栈属于线程私有,不能实现线程间的共享。
- 栈的存储特性是“先进后出,后进先出”,先调用的方法后释放。
- 栈是由系统自动分配,速度快!栈是一个连续的内存空间。
堆
- 用于存储创建好的对象和数组(数组也是对象)。
- JVM只有一个堆,被所有线程共享。
- 堆是一个不连续的内存空间,分配灵活,速度慢。
- 遇到new关键字,就是在堆中创建了一个对象。
方法区
- JVM只有一个方法区,被所有线程共享。
- 方法区实际是堆,只是用于存储类、常量相关的信息。
- 用来存放程序中永远是不变或唯一的内容(类信息【class对象】,静态变量、字符串常量等)。
3. 构造方法
- 定义:构造器也叫构造方法(constructor),用于对象的初始化。
- 通过new关键字调用
- 构造器虽然有返回值,但是不能定义返回值类型(返回值的类型肯定是本类),不能在构造器里使用return返回某个值
- 如果我们没有定义构造器,则编译器会自动定义一个无参的构造函数。如果已定义则编译器不会自动添加。
- 构造器的方法名必须和类名一致。
- this表示创建好的对象,this.id表示这个对象的成员变量id,如果成员变量和局部变量一样时,就要给成员变量加this,否则两个都表示局部变量。
- 类是可以作为方法的参数进行传递的。
4. 垃圾回收机制(Garbage Collection)
内存管理
- Java的内存管理很大程度指得是对象的管理,其中包括对象空间的分配和释放。
- 分配:使用new关键字创建对象即可。
- 释放:将对象赋值为null即可。垃圾回收器将负责回收所有“不可达”对象的内存空间。
垃圾回收过程
基本事件
- 发现无用对象
- 回收无用对象占用的内存空间。
垃圾回收机制保证可以将“无用的对象”进行回收。无用的对象指的是没有任何变量引用该对象。Java的垃圾回收器通过相关算法发现无用对象,并进行清除和整理。
垃圾回收相关算法
- 引用计数法:堆中的每个对象都有一个计数器。被引用一次,计数加1,被引用变量值变为null,则计数减1,直到计数为0,则表示变成无用对象。优点是算法简单,缺点是“循环引用的无用对象”,无法识别。
- 引用可达法(根搜索算法):程序把所有的引用关系看作一张图,从一个节点GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕后,剩余的节点则被认为是没有被引用的节点,则无用节点。
通用分代垃圾回收详解
- 不同对象的生命周期不一样,因此不同生命周期的对象可以采用不同的回收算法,以便提高回收效率。
- 将对象分为三种状态:年轻代,老年代,持久代。JVM将堆内存划分为Eden,Survivor和Tenured/Old空间。
Minor GC
- 用于清理年轻代。
- Eden区满了就会触发一次Minor GC。
- 清理无用对象,将有用对象复制到Survivor1、Survivor2区中(这两个区,大小空间相同,同一时刻Survivor1、Survivor2只有一个在用,一个为空)
Major GC
用于清理老年代区域
Full GC
用于清理年轻代和老年代,成本较高,会对系统性能产生影响,需要优化。
垃圾回收过程
- 新创建的对象,绝大多数都会在Eden中
- 当Eden满了(达到一定比例)不能创建新对象,则触发GC,将无用对象清理掉,然后剩余对象复制到某个Survivor中,同时清空Eden。
- 当Eden再次满了,会将Survivor1中的不能清空的对象存到另一个survivor中。同时将Eden中的不能清空的对象也复制到survivor2中,保证Eden和survivor1,均被清空
- 重复多次(默认15次)survivor中没有被清理的对象,则会复制到老年代中
- 当old满了,则会触发一个一次完整的垃圾回收Full GC,之前新生代的垃圾回收成为minor GC
5. this的本质
创建对象的四步
- 分配对象空间,并将对象成员变量初始化为0或null。
- 执行属性值的显式初始化。
- 执行构造方法。
- 返回对象的地址给相关的变量。
this的本质
- 本质就是“创建好的对象的地址”。
- 由于在构造方法调用前,对象已经创建。因此在构造方法在也可以使用this代表“当前对象”。
- this不能做static修饰的方法中使用。
- this可以在重载的构造方法中调用另一个构造方法this(a,b),且必须在第一句。
6. static关键字
- 在类中,用static声明的成员变量为静态成员变量,也称类变量。类变量的生命周期和类相同,在整个应用程序执行期间都有效。
- static修饰的成员变量和方法,从属于类。普通变量和方法从属于对象。
- 静态方法中无法使用非静态的方法,非静态的方法可以使用静态的方法和属性
7. 静态初始化块
- 用于类的初始化操作。
- 在静态初始化块中不能直接访问非static成员。
- 上溯到object类,先执行object的静态初始化块,再执行子类的的初始化块,直到我们类的静态初始化块为止。
- 构造方法执行顺序和上面一样。
- 格式static{},不能调用普通的成员变量和方法,因为普通的成员变量和方法从属于对象,不属于类。
8. JAVA 的参数传值机制
- 方法中所有参数都是“值传递”,传递的是值的副本。
- 基本数据类型参数的传值:传递的是值的副本。副本改变不会影响原件。
- 引用类型参数的传值:传递的是值指的是“对象的地址”,因此,副本和原参数都指向了同一个“地址”,改变副本指向地址对象的值,也意味着原参数指向对象的值也发生了改变。
9. 包机制
- 包对于类,相当于文件夹对于文件的作用。
要点
- 通常是类的第一句非注释性语句。
- 包名:域名倒着写即可,再加上模块名,便于内部管理类。
- 写项目时都要加包,不能使用默认的。
- com.gao和com.gao.car,这两个包没有包含关系,是两个完全独立的包,只是逻辑上后者是前者的一部分。
JDK中的主要的包
- java.lang:包含一些java语言的核心类,如String、Math、Integer、System和Thread,提供常用功能,这个包中所有的类不需要导入就可使用。
- java.awt:包含了构成抽象窗口工具类。
- java.net:包含执行与网络相关操作的类。
- java.io:包含能够提供多样输入/输出功能的包。
- java.util:包含一些使用工具类,如日期日历等。
10.import详解
- 使用其他包的类,就需要import,从而可以在本类中直接使用类名来调用。
- java会默认导入java.lang包下所有类,因此可以直接使用。
- 如果导入的两个同名的类,只能用包名+类名来显示调用相关类。
- 代码实例:
java.util.Data data = new java.util.Data();
- 静态导入:导入指定类的静态属性,这样我们可以直接使用惊天属性。(import static java.lang.Math.PI)
11.继承
- 面向对象的三大特征:继承、封装和多态。
- 继承实现了类的扩展和代码的重用。
- 父类也称超类、基类、派生类等。
- Java中只有单继承,没有多继承。
- java类没有多继承,接口有多继承。
- 子类继承父类,可以得到父类的全部属性和方法(除了父类的构造方法),但不见得可以直接访问(比如,父类私有的属性和方法)。
- 如果定义一个类时,没有调用extends,则它的父类是:java.lang.Object。
- instanceof是二元运算符,左边是对象,右边是类;当对象是右面类或子类所创对象时,返回true,否则返回false。
public class TextExtends {
public static void main(String[] args) {
Student stu = new Student();
stu.name = "陈德建";
stu.height = 183;
stu.rest();
Student stu2 = new Student("陈传智",13,"挖掘机");
System.out.println(stu2 instanceof Student);//true
System.out.println(stu2 instanceof Person);//true
System.out.println(stu2 instanceof Object);//true
System.out.println(new Person() instanceof Student);//false
}
}
class Person{
String name;
int height;
public void rest(){
System.out.println("休息一会!");
}
}
class Student extends Person{
String major;
public void study(){
System.out.println("学习俩小时!");
}
public Student(String name,int height,String major)
{
this.name = name;
this.height = height;
this.major = major;
}
public Student(){
}
}
12. 方法的重写
- 重写(override):子类通过重写父类的方法,可以用自身的行为替换父类的行为。
- 三个要点:
- 方法名、形参列表相同。
- 返回值类型和声明异常类型,子类小于等于父类。
- 访问权限,子类等于父类。
13. Object类的用法
- 是所有Java类的根基类,意味着所有的Java对象都拥有object类的属性和方法。
- ==与equals的区别:
- ==:代表比较双方是否相同。如果是基本数据类型则表示值相等,如果是引用类型则表示地址相等,即是同一个对象。
- equals:提供定义对象内容相等的逻辑。
String str1 = new String("sxt");
String str2 = new String("sxt");
System.out.println(str1 == str2);//false
System.out.println(str1.equals(str2));//true
14. super父类对象引用
- super是直接父类对象的引用。可以通过super来访问父类中被子类覆盖的方法或属性。
- 构造方法第一句总是super(...)来调用父类对应的构造方法。所以流程是先向上追溯到Object,然后再依次向下执行类的初始化块和构造方法,直到子类为止。
15. 封装的使用
- 封装的作用:实现代码的高内聚/低耦合
- 含义:需要让用户知道的才暴露出来,不需要的全部隐藏起来。
- 优点:
- 提高代码的安全性
- 提高代码的复用性
- “高内聚”:封装细节,便于修改内部代码,提高可维护性
- “低耦合”:简化外部调用,便于调用者使用,便于扩展和协作。
访问控制符
- Java是使用“访问控制符”来控制哪些细节需要封装,哪些细节需要暴露。
- 4种访问控制符:
- private:表示私有,只有自己类能访问
- default:表示没有修饰符修饰,只有同一个包的类能访问
- Protected:表示可以被同一个包的类以及其他包中的子类访问
- public:表示可以被该项目的所有包中的所有类访问
- 修饰属性、方法和类。
类属性的处理
- 一般使用private访问权限
- 提供相应的get/set方法来访问相关属性,这些方法通常是public修饰的,以提供对属性的赋值与读取操作:boolean变量的get方法是is开头
- 一些只用于本类的辅助性方法可以用private修饰,希望其他类调用的方法用public修饰。
16. 多态(polymorphism)
- 指得是同一个方法调用,由于对象不同可能会有不同的行为。同一个方法,具体的实现会完全不同。
- 多态的要点:
- 多态是方法的多态,不是属性的多态(多态与属性无关)
- 多态的存在要有3个必要的条件:继承、方法的重写、父类引用指向子类对象。
- 父类引用指向子类对象后,用该父类引用调用子类重写方法,此时多态就出现了。
17. 对象的转型(casting)
- 父类引用指向子类对象,我们称为向上转型,属于自用类型转换。
- 向上转型后的父类引用变量只能调用它编译类型的方法,不能调用它编译类型的方法,不能调用它运行时的方法。这时就需要进行类型的强制转换,称之为向下转型。
18. final修饰变量、方法和类
- 修饰变量:被他修饰的变量不可改变。一旦赋了初值,就不能被重新赋值。
- 修饰方法:该方法不可被子类重写,但是可以被重载
- 修饰类:修饰的类不能被继承。比如:Math,String等
19. 数组的使用
- 定义:数组是相同类型数据的有序集合。
- 数组描述的是相同类型的若干个数据,按照一定的先后次序排列组合而成。其中,每一个数据称作一个元素,每一个元素可以通过一个索引(下标)来访问他们。
- 基本特点:
- 长度确定的。数组一旦被创建,它的大小就是不可以改变的。
- 其元素必须是相同类型,不允许出现混合类型。
- 数组类型可以是任何数据类型,包括基本类型和引用类型。
- 数组变量属引用类型,数组也是对象,数组中的每个元素相当于该对象的成员变量。
- 声明一个数组:
- 声明的时候没有实例化任何对象,只有实例化数组对象时,JVM才分配空间,这时才于长度有关
- 声明一个数组的时候并没有真正被创建。
- 构造一个数组,必须指的长度。
int[] arr01;//声明一个数组
String arr02[];
User[] arr03;
- 数组创建:
int[] arr01 = new int[10];
- 数组赋值:
arr01[0] = 11;
- 数组出始化
- 静态初始化
int[] a = {1,2,3};
- 动态初始化
c[0] = 1;
- 默认初始化
int[] b = new int[10];//默认给数组的元素进行赋值。赋值的规则和成员变量默认赋值规则一致
- 数组的遍历-foreach循环
- 专门用于读取数组和集合的所有元素。
int[] a = {1,2,3};
for(int i:a)
{
System.out.println(i);
}
20. 抽象方法和抽象类
抽象方法
- 使用abstract修饰的方法。
- 没有方法体,只有声明。
- 定义的是一种“规范”,就是告诉子类必须要给抽象方法提供具体的实现。
抽象类
- 包含抽象方法的类就是抽象类。
- 通过abstract方法定义规范,然后要求子类必须定义具体实现。
- 通过抽象类,我们可以做到严格限制子类的设计,使子类之间更加通用。
要点
- 有抽象方法的类只能定义成抽象类。
- 抽象类不能实例化,即不能用new来实例化抽象类。
- 抽象类可以包含属性、方法、构造方法。但是构造方法不能用来new实例,只能用来被子类调用。
- 抽象类只能用来被继承。
- 抽象方法必须被子类实现。
- 抽象类的抽象方法没有实现,子类必须实现抽象方法。抽象类可以有普通的方法。
- 抽象类的意义就在于:为子类提供统一的、规范的模板。子类必须实现相关的抽象方法!
21. 接口
- 接口不提供任何的实现。
- 规范和具体实现分离。
接口、普通类、抽象类区别
- 普通类:具体实现。
- 抽象类:具体实现和抽象方法。
- 接口:抽象方法。
接口定义
- [访问修饰符] inteface 接口名 [extends 父接口1,父接口2 ...]{
常量定义;
方法定义;
}
- 访问修饰符:只能是public或默认。
- 接口名:和类名采用相同的命名机制。
- extends:接口可以多继承。
- 常量:接口中的属性只能是常量,总是:public static final 修饰。不写也是。
- 方法:接口中的方法只能是public abstract。
要点
- 子类通过implements来实现接口中的规范
- 接口不能创建实例,但是可用于声明引用变量类型。
- 一个类实现接口,必须实现接口中所有的方法,并且这些方法只能是public的。
- JDK1.7之前,接口中只能包含静态变量、抽象方法,不能有普通属性/构造方法/普通方法。
- JDK1.8后,接口包含普通的静态方法。
22. 内部类
分类
- 成员内部类(非静态内部类、静态内部类)
- 成员内部类:可以使用private、default、Protected、public任意进行修饰。
- 类文件:外部类$内部类.class。
- 非静态内部类:外部类里使用非静态内部类和平时使用其他类没什么不同。
- 非静态内部类必须寄存在一个外部类对象里。因此如果有一个非静态内部类对象那么一定存在对应的外部类对象。非静态内部类对象单独属于外部类的某个对象。
- 非静态内部类可以直接访问外部类的成员,但是外部类不能直接访问静态内部类成员。
- 非静态内部类不能有静态方法、静态属性和静态初始化块。
- 外部类的静态方法、静态代码块不能访问非静态内部类,包括不能使用非静态内部类定义变量、创建实例。
- 成员变量访问要点:
- 内部类里方法的局部变量:变量名。
- 内部类属性:this.变量名。
- 外部类属性:外部类名.this.变量名。
- 静态内部类要点 2. 静态内部类看作外部类的一个静态成员。因此,外部类的方法中可以通过:“静态内部类.名字”的方式访问静态内部类的静态成员,通过new静态内部类()访问静态内部类的实例。
- 匿名内部类:适合只需要使用一次的类。比如键盘监听操作等。
- 局部内部类:
- 定义在方法的内部,作用域只限于本方法。
- 主要用来解决比较复杂的问题,想创建一个类来辅助我们的解决方案,又不希望这个类是公共可用的可以被编译,作用域只是本方法。
//非静态内部类
//创建内部类对象
Outer.Inner inner = new Outer().new Inner();
inner.show();
//静态内部类
Outer2.Inner2 inner = new Outer2.Inner2();
23. String类-常量池-字符串比较
String基础
- String类称作不可变字符序列。
- 位于java.lang包中,Java程序默认导入java.lang包下的所有类。
- Java字符串就是Unicode字符序列。
- java没有内置字符串类型,而是在标准的java类库中提供一个预定义的类String,每个用双引号括起来的字符串都是String类的一个实例。
常量池
- 全局字符串常量池String Pool:存放的内容是类加载完成后存放到String Pool中的,每个JVM中只有一份,存放的是字符串常量的引用值(在堆中生成字符串对象实例)。
- class文件常量池(class Constant pool):在编译的时候每个class都有的,在编译阶段,存放的是常量(文本字符串、final常量等)和符号引用。
- 运行时常量池(Runtime Constant Pool):在类加载完成后,将每个class常量池中的符号引用值转存到运行时常量池中,也就是说,每个class都有一个运行时常量池,类在解析之后,将符号引用替换成直接引用,与全局常量池中的引用值保持一致。
String str10 = "gaoqi";//str10和str11,是常量池中的同一个对象
String str11 = "gaoqi";
String str12 = new String("gaoqi");//str12是新建的一个对象
System.out.println(str10 == str11);
System.out.println(str10 == str12);
System.out.println(str10.equals(str12));//比较的是字符串的内容,以后字符串的比较要用equals
24. 数组的相关操作
数组的拷贝
- System类里包含一个static void arraycopy(object src ,int srcpos,object dest,int destpos ,int length)方法。
- 该方法可以将src数组里的元素值赋给dest数组,其中srcpos指定从src数组的第几个元素开始赋值,length参数指定src数组的多少个元素给dest数组的元素。
//删除数组中指定索引位置的元素,并将原数组返回
public static String[] removeElement(String[] s,int index){
System.arraycopy(s, index+1, s, index,s.length-index-1);
s[s.length-1]=null;
for(String s1:s)
{
System.out.println(s1);
}
return s;
}
//数组的扩容(本质上是:先定义一个更大的数组,然后将原数组内容原封不动拷贝到新数组中)
public static String[] extendRange(String[] s1){
String[] s2 = new String[s1.length+10];
System.arraycopy(s1, 0, s2, 0, s1.length);
for(String s:s2){
System.out.println(s);
}
return s2;
}
//往数组里插入一个元素
public static String[] insertElement(String[] s1,int index,String s){
String[] s2 = new String[s1.length+1];
System.arraycopy(s1, 0, s2, 0, index+1);
s2[index] = s;
System.arraycopy(s1, index, s2, index+1, s1.length-index);
for(String s3:s2)
{
System.out.println(s3);
}
return s2;
}
java.util.Array类
- JDK提供的Java.util.Arrays类,包含了常用的数组操作。包含:排序/查找/填充/打印内容等常见操作
- Arrays.toString()方法是Array类的静态方法,不是Object的toString()方法。
System.out.println(Arrays.toString(a));//打印指定数组的内容
Arrays.sort(a);
System.out.println(Arrays.toString(a));
System.out.println(Arrays.binarySearch(a, 30));
System.out.println(Arrays.binarySearch(a, -30));
多维数组
- 多维数组可以看成以数组为元素的数组。
Object[] emp1 = {1001,"搞起",18};
Object[] emp2 = {1002,"搞2起",18};
Object[] emp3 = {1003,"搞3起",18};
Object[][] tableData = new Object[3][];
tableData[0] = emp1;
tableData[1] = emp2;
tableData[2] = emp3;
for(Object[] b:tableData)
{
System.out.println(Arrays.toString(b));
}
25. 冒泡排序
int[] values = {3,1,6,2,9,0,7,4,5,8};
int temp = 0;
System.out.println(Arrays.toString(values));
for(int i=0;i<values.length-1;i++)
{
boolean flag = true;
for(int j=0;j<values.length-1-i;j++){
//比较大小,换顺序
if(values[j] > values[j+1]){
temp = values[j];
values[j] = values[j+1];
values[j+1] = temp;
flag = false;
}
System.out.println(Arrays.toString(values));
}
if(flag)
{
System.out.println("结束!");
break;
}
System.out.println("******");
}
26. 二分法查找-折半检索
- 基本思想:设数组中的元素从小到大有序的存放在数组中,首先将给定值key与数组中间位置上元素的关键码比较,如果相等,则检索成功,若key小,则在数组前半部分中继续进行二分检索,若key大,则在数组后半部分中继续进行二分法检索。这样经过一次比较就缩小一半的检索区间。如此进行下去,直到检索成功或失败。是一种效率较高的检索方法。
public static void main(String[] args) {
int[] arr = {30,20,50,10,80,9,7,12,100,40,8};
Arrays.sort(arr);
//要找的值
int value = -10;
System.out.println(myBinarySeach(arr, value));
}
public static int myBinarySeach(int[] arr,int value){
int low = 0;
int high = arr.length - 1;
while(low <= high){
int mid = (low+high)/2;
if(value == arr[mid])
{
return mid;
}
if(value > arr[mid]){
low = mid + 1;
}
if(value < arr[mid]){
high = mid - 1;
}
}
return -1;
}