1. 对象的概念

  1. 对象(object)也叫实例(instance):在内存中是一个块,用来表示一堆相关联的数据(变量和方法)。
  2. 类(class):对象的模板,系统根据类的定义来创造对象。
  3. 属性:用于定义该类或该对象包含的数据或静态特征。属性作用范围是整个类体。
  4. 在定义成员变量时可以对其初始化,如果不对其初始化,java使用默认的值对其初始化。
  5. 方法是一段用来完成特定功能的代码片段。用于定义该类的实例的行为特征和功能实现。方法是类和对象行为特征的抽象。面向对象中,整个过程的基本单位是类,方法是从属于类和对象的。
  6. 代码实例:
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. 面向对象内存分析

  1. Java虚拟机的内存分为三个区域:栈stack,堆heap,方法区method area(方法区也在堆内)。

  1. 栈:方法执行的内存模型。每个方法被调用都会创建一个栈帧(存储局部变量、操作数、方法出口等)。
  2. JVM为每个线程创建一个栈,用于存放该线程执行方法的信息(实际参数、局部变量等)。
  3. 栈属于线程私有,不能实现线程间的共享。
  4. 栈的存储特性是“先进后出,后进先出”,先调用的方法后释放。
  5. 栈是由系统自动分配,速度快!栈是一个连续的内存空间。

  1. 用于存储创建好的对象和数组(数组也是对象)。
  2. JVM只有一个堆,被所有线程共享。
  3. 堆是一个不连续的内存空间,分配灵活,速度慢。
  4. 遇到new关键字,就是在堆中创建了一个对象。

方法区

  1. JVM只有一个方法区,被所有线程共享。
  2. 方法区实际是堆,只是用于存储类、常量相关的信息。
  3. 用来存放程序中永远是不变或唯一的内容(类信息【class对象】,静态变量、字符串常量等)。

3. 构造方法

  1. 定义:构造器也叫构造方法(constructor),用于对象的初始化。
  2. 通过new关键字调用
  3. 构造器虽然有返回值,但是不能定义返回值类型(返回值的类型肯定是本类),不能在构造器里使用return返回某个值
  4. 如果我们没有定义构造器,则编译器会自动定义一个无参的构造函数。如果已定义则编译器不会自动添加。
  5. 构造器的方法名必须和类名一致。
  6. this表示创建好的对象,this.id表示这个对象的成员变量id,如果成员变量和局部变量一样时,就要给成员变量加this,否则两个都表示局部变量。
  7. 类是可以作为方法的参数进行传递的。

4. 垃圾回收机制(Garbage Collection)

内存管理

  1. Java的内存管理很大程度指得是对象的管理,其中包括对象空间的分配和释放。
  2. 分配:使用new关键字创建对象即可。
  3. 释放:将对象赋值为null即可。垃圾回收器将负责回收所有“不可达”对象的内存空间。

垃圾回收过程

基本事件

  1. 发现无用对象
  2. 回收无用对象占用的内存空间。

垃圾回收机制保证可以将“无用的对象”进行回收。无用的对象指的是没有任何变量引用该对象。Java的垃圾回收器通过相关算法发现无用对象,并进行清除和整理。

垃圾回收相关算法

  1. 引用计数法:堆中的每个对象都有一个计数器。被引用一次,计数加1,被引用变量值变为null,则计数减1,直到计数为0,则表示变成无用对象。优点是算法简单,缺点是“循环引用的无用对象”,无法识别。
  2. 引用可达法(根搜索算法):程序把所有的引用关系看作一张图,从一个节点GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕后,剩余的节点则被认为是没有被引用的节点,则无用节点。

通用分代垃圾回收详解

  1. 不同对象的生命周期不一样,因此不同生命周期的对象可以采用不同的回收算法,以便提高回收效率。
  2. 将对象分为三种状态:年轻代,老年代,持久代。JVM将堆内存划分为Eden,Survivor和Tenured/Old空间。

Minor GC

  1. 用于清理年轻代。
  2. Eden区满了就会触发一次Minor GC。
  3. 清理无用对象,将有用对象复制到Survivor1、Survivor2区中(这两个区,大小空间相同,同一时刻Survivor1、Survivor2只有一个在用,一个为空)

Major GC

用于清理老年代区域

Full GC

用于清理年轻代和老年代,成本较高,会对系统性能产生影响,需要优化。

垃圾回收过程

  1. 新创建的对象,绝大多数都会在Eden中
  2. 当Eden满了(达到一定比例)不能创建新对象,则触发GC,将无用对象清理掉,然后剩余对象复制到某个Survivor中,同时清空Eden。
  3. 当Eden再次满了,会将Survivor1中的不能清空的对象存到另一个survivor中。同时将Eden中的不能清空的对象也复制到survivor2中,保证Eden和survivor1,均被清空
  4. 重复多次(默认15次)survivor中没有被清理的对象,则会复制到老年代中
  5. 当old满了,则会触发一个一次完整的垃圾回收Full GC,之前新生代的垃圾回收成为minor GC

5. this的本质

创建对象的四步

  1. 分配对象空间,并将对象成员变量初始化为0或null。
  2. 执行属性值的显式初始化。
  3. 执行构造方法。
  4. 返回对象的地址给相关的变量。

this的本质

  1. 本质就是“创建好的对象的地址”。
  2. 由于在构造方法调用前,对象已经创建。因此在构造方法在也可以使用this代表“当前对象”。
  3. this不能做static修饰的方法中使用。
  4. this可以在重载的构造方法中调用另一个构造方法this(a,b),且必须在第一句。

6. static关键字

  1. 在类中,用static声明的成员变量为静态成员变量,也称类变量。类变量的生命周期和类相同,在整个应用程序执行期间都有效。
  2. static修饰的成员变量和方法,从属于类。普通变量和方法从属于对象。
  3. 静态方法中无法使用非静态的方法,非静态的方法可以使用静态的方法和属性

7. 静态初始化块

  1. 用于类的初始化操作。
  2. 在静态初始化块中不能直接访问非static成员。
  3. 上溯到object类,先执行object的静态初始化块,再执行子类的的初始化块,直到我们类的静态初始化块为止。
  4. 构造方法执行顺序和上面一样。
  5. 格式static{},不能调用普通的成员变量和方法,因为普通的成员变量和方法从属于对象,不属于类。

8. JAVA 的参数传值机制

  1. 方法中所有参数都是“值传递”,传递的是值的副本。
  2. 基本数据类型参数的传值:传递的是值的副本。副本改变不会影响原件。
  3. 引用类型参数的传值:传递的是值指的是“对象的地址”,因此,副本和原参数都指向了同一个“地址”,改变副本指向地址对象的值,也意味着原参数指向对象的值也发生了改变。

9. 包机制

  1. 包对于类,相当于文件夹对于文件的作用。

要点

  1. 通常是类的第一句非注释性语句。
  2. 包名:域名倒着写即可,再加上模块名,便于内部管理类。
  3. 写项目时都要加包,不能使用默认的。
  4. com.gao和com.gao.car,这两个包没有包含关系,是两个完全独立的包,只是逻辑上后者是前者的一部分。

JDK中的主要的包

  1. java.lang:包含一些java语言的核心类,如String、Math、Integer、System和Thread,提供常用功能,这个包中所有的类不需要导入就可使用。
  2. java.awt:包含了构成抽象窗口工具类。
  3. java.net:包含执行与网络相关操作的类。
  4. java.io:包含能够提供多样输入/输出功能的包。
  5. java.util:包含一些使用工具类,如日期日历等。

10.import详解

  1. 使用其他包的类,就需要import,从而可以在本类中直接使用类名来调用。
  2. java会默认导入java.lang包下所有类,因此可以直接使用。
  3. 如果导入的两个同名的类,只能用包名+类名来显示调用相关类。
  4. 代码实例:
java.util.Data data = new java.util.Data();
  1. 静态导入:导入指定类的静态属性,这样我们可以直接使用惊天属性。(import static java.lang.Math.PI)

11.继承

  1. 面向对象的三大特征:继承、封装和多态。
  2. 继承实现了类的扩展和代码的重用。
  3. 父类也称超类、基类、派生类等。
  4. Java中只有单继承,没有多继承。
  5. java类没有多继承,接口有多继承。
  6. 子类继承父类,可以得到父类的全部属性和方法(除了父类的构造方法),但不见得可以直接访问(比如,父类私有的属性和方法)。
  7. 如果定义一个类时,没有调用extends,则它的父类是:java.lang.Object。
  8. 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. 方法的重写

  1. 重写(override):子类通过重写父类的方法,可以用自身的行为替换父类的行为。
  2. 三个要点:
  1. 方法名、形参列表相同。
  2. 返回值类型和声明异常类型,子类小于等于父类。
  3. 访问权限,子类等于父类。

13. Object类的用法

  1. 是所有Java类的根基类,意味着所有的Java对象都拥有object类的属性和方法。
  2. ==与equals的区别:
  1. ==:代表比较双方是否相同。如果是基本数据类型则表示值相等,如果是引用类型则表示地址相等,即是同一个对象。
  2. 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父类对象引用

  1. super是直接父类对象的引用。可以通过super来访问父类中被子类覆盖的方法或属性。
  2. 构造方法第一句总是super(...)来调用父类对应的构造方法。所以流程是先向上追溯到Object,然后再依次向下执行类的初始化块和构造方法,直到子类为止。

15. 封装的使用

  1. 封装的作用:实现代码的高内聚/低耦合
  2. 含义:需要让用户知道的才暴露出来,不需要的全部隐藏起来。
  3. 优点:
  1. 提高代码的安全性
  2. 提高代码的复用性
  3. “高内聚”:封装细节,便于修改内部代码,提高可维护性
  4. “低耦合”:简化外部调用,便于调用者使用,便于扩展和协作。

访问控制符

  1. Java是使用“访问控制符”来控制哪些细节需要封装,哪些细节需要暴露。
  2. 4种访问控制符:
  1. private:表示私有,只有自己类能访问
  2. default:表示没有修饰符修饰,只有同一个包的类能访问
  3. Protected:表示可以被同一个包的类以及其他包中的子类访问
  4. public:表示可以被该项目的所有包中的所有类访问
  5. 修饰属性、方法和类。

类属性的处理

  1. 一般使用private访问权限
  2. 提供相应的get/set方法来访问相关属性,这些方法通常是public修饰的,以提供对属性的赋值与读取操作:boolean变量的get方法是is开头
  3. 一些只用于本类的辅助性方法可以用private修饰,希望其他类调用的方法用public修饰。

16. 多态(polymorphism)

  1. 指得是同一个方法调用,由于对象不同可能会有不同的行为。同一个方法,具体的实现会完全不同。
  2. 多态的要点:
  1. 多态是方法的多态,不是属性的多态(多态与属性无关)
  2. 多态的存在要有3个必要的条件:继承、方法的重写、父类引用指向子类对象。
  3. 父类引用指向子类对象后,用该父类引用调用子类重写方法,此时多态就出现了。

17. 对象的转型(casting)

  1. 父类引用指向子类对象,我们称为向上转型,属于自用类型转换。
  2. 向上转型后的父类引用变量只能调用它编译类型的方法,不能调用它编译类型的方法,不能调用它运行时的方法。这时就需要进行类型的强制转换,称之为向下转型。

18. final修饰变量、方法和类

  1. 修饰变量:被他修饰的变量不可改变。一旦赋了初值,就不能被重新赋值。
  2. 修饰方法:该方法不可被子类重写,但是可以被重载
  3. 修饰类:修饰的类不能被继承。比如:Math,String等

19. 数组的使用

  1. 定义:数组是相同类型数据的有序集合。
  2. 数组描述的是相同类型的若干个数据,按照一定的先后次序排列组合而成。其中,每一个数据称作一个元素,每一个元素可以通过一个索引(下标)来访问他们。
  3. 基本特点:
  1. 长度确定的。数组一旦被创建,它的大小就是不可以改变的。
  2. 其元素必须是相同类型,不允许出现混合类型。
  3. 数组类型可以是任何数据类型,包括基本类型和引用类型。
  1. 数组变量属引用类型,数组也是对象,数组中的每个元素相当于该对象的成员变量。
  2. 声明一个数组:
  1. 声明的时候没有实例化任何对象,只有实例化数组对象时,JVM才分配空间,这时才于长度有关
  2. 声明一个数组的时候并没有真正被创建。
  3. 构造一个数组,必须指的长度。
int[] arr01;//声明一个数组
        String arr02[];
        User[] arr03;
  1. 数组创建:
int[] arr01 = new int[10];
  1. 数组赋值:
arr01[0] = 11;
  1. 数组出始化
  1. 静态初始化
int[] a = {1,2,3};
  1. 动态初始化
c[0] = 1;
  1. 默认初始化
int[] b = new int[10];//默认给数组的元素进行赋值。赋值的规则和成员变量默认赋值规则一致
  1. 数组的遍历-foreach循环
  1. 专门用于读取数组和集合的所有元素。
int[] a = {1,2,3};
        for(int i:a)
        {
            System.out.println(i);
        }

20. 抽象方法和抽象类

抽象方法

  1. 使用abstract修饰的方法。
  2. 没有方法体,只有声明。
  3. 定义的是一种“规范”,就是告诉子类必须要给抽象方法提供具体的实现。

抽象类

  1. 包含抽象方法的类就是抽象类。
  2. 通过abstract方法定义规范,然后要求子类必须定义具体实现。
  3. 通过抽象类,我们可以做到严格限制子类的设计,使子类之间更加通用。

要点

  1. 有抽象方法的类只能定义成抽象类。
  2. 抽象类不能实例化,即不能用new来实例化抽象类。
  3. 抽象类可以包含属性、方法、构造方法。但是构造方法不能用来new实例,只能用来被子类调用。
  4. 抽象类只能用来被继承。
  5. 抽象方法必须被子类实现。
  6. 抽象类的抽象方法没有实现,子类必须实现抽象方法。抽象类可以有普通的方法。
  7. 抽象类的意义就在于:为子类提供统一的、规范的模板。子类必须实现相关的抽象方法!

21. 接口

  1. 接口不提供任何的实现。
  2. 规范和具体实现分离。

接口、普通类、抽象类区别

  1. 普通类:具体实现。
  2. 抽象类:具体实现和抽象方法。
  3. 接口:抽象方法。

接口定义

  1. [访问修饰符] inteface 接口名 [extends 父接口1,父接口2 ...]{
    常量定义;
    方法定义;

}

  1. 访问修饰符:只能是public或默认。
  2. 接口名:和类名采用相同的命名机制。
  3. extends:接口可以多继承。
  4. 常量:接口中的属性只能是常量,总是:public static final 修饰。不写也是。
  5. 方法:接口中的方法只能是public abstract。

要点

  1. 子类通过implements来实现接口中的规范
  2. 接口不能创建实例,但是可用于声明引用变量类型。
  3. 一个类实现接口,必须实现接口中所有的方法,并且这些方法只能是public的。
  4. JDK1.7之前,接口中只能包含静态变量、抽象方法,不能有普通属性/构造方法/普通方法。
  5. JDK1.8后,接口包含普通的静态方法。

22. 内部类

分类

  1. 成员内部类(非静态内部类、静态内部类)
  1. 成员内部类:可以使用private、default、Protected、public任意进行修饰。
  2. 类文件:外部类$内部类.class。
  3. 非静态内部类:外部类里使用非静态内部类和平时使用其他类没什么不同。
  1. 非静态内部类必须寄存在一个外部类对象里。因此如果有一个非静态内部类对象那么一定存在对应的外部类对象。非静态内部类对象单独属于外部类的某个对象。
  2. 非静态内部类可以直接访问外部类的成员,但是外部类不能直接访问静态内部类成员。
  3. 非静态内部类不能有静态方法、静态属性和静态初始化块。
  4. 外部类的静态方法、静态代码块不能访问非静态内部类,包括不能使用非静态内部类定义变量、创建实例。
  5. 成员变量访问要点:
  1. 内部类里方法的局部变量:变量名。
  2. 内部类属性:this.变量名。
  3. 外部类属性:外部类名.this.变量名。
  4. 静态内部类要点 2. 静态内部类看作外部类的一个静态成员。因此,外部类的方法中可以通过:“静态内部类.名字”的方式访问静态内部类的静态成员,通过new静态内部类()访问静态内部类的实例。
  1. 匿名内部类:适合只需要使用一次的类。比如键盘监听操作等。
  2. 局部内部类:
  1. 定义在方法的内部,作用域只限于本方法。
  2. 主要用来解决比较复杂的问题,想创建一个类来辅助我们的解决方案,又不希望这个类是公共可用的可以被编译,作用域只是本方法。
//非静态内部类
      //创建内部类对象
            Outer.Inner inner = new Outer().new Inner();
            inner.show();
      //静态内部类
      Outer2.Inner2 inner = new Outer2.Inner2();

23. String类-常量池-字符串比较

String基础

  1. String类称作不可变字符序列。
  2. 位于java.lang包中,Java程序默认导入java.lang包下的所有类。
  3. Java字符串就是Unicode字符序列。
  4. java没有内置字符串类型,而是在标准的java类库中提供一个预定义的类String,每个用双引号括起来的字符串都是String类的一个实例。

常量池

  1. 全局字符串常量池String Pool:存放的内容是类加载完成后存放到String Pool中的,每个JVM中只有一份,存放的是字符串常量的引用值(在堆中生成字符串对象实例)。
  2. class文件常量池(class Constant pool):在编译的时候每个class都有的,在编译阶段,存放的是常量(文本字符串、final常量等)和符号引用。
  3. 运行时常量池(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. 数组的相关操作

数组的拷贝

  1. System类里包含一个static void arraycopy(object src ,int srcpos,object dest,int destpos ,int length)方法。
  2. 该方法可以将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类

  1. JDK提供的Java.util.Arrays类,包含了常用的数组操作。包含:排序/查找/填充/打印内容等常见操作
  2. 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));

多维数组

  1. 多维数组可以看成以数组为元素的数组。
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. 二分法查找-折半检索

  1. 基本思想:设数组中的元素从小到大有序的存放在数组中,首先将给定值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;
    }