面向对象

类和对象

类的组成是由属性和行为两部分组成

  • 属性:在类中通过成员变量来体现(类中方法外的变量)
  • 行为:在类中通过成员方法来体现
    类是对事物的一种描述,对象则为具体存在的事物

类的定义

一个java文件中可以定义多个class,但只能一个类是public修饰的,而且public修饰的类名必须和文件名相同(不建议一个文件定义多个class),类的五大成员为:属性、方法、构造方法、代码块、内部类

public class 类名 {
// 成员变量
变量1的数据类型 变量1;
变量2的数据类型 变量2;
//成员变量完整定义格式
修饰符 数据类型 变量名称 = 初始化值;//一般无需指定初始化值,存在默认值

// 成员方法
方法1;
方法2;
//构造器、代码块、内部类
}

对象的使用

  • 创建对象的格式:
    • 类名 对象名 = new 类名();
  • 调用成员的格式:
    • 对象名.成员变量
    • 对象名.成员方法();

封装

是面向对象三大特征之一(封装,继承,多态),对象代表什么,就得封装对应的数据,并提供数据对应的行为

private关键字

private是一个修饰符,可以用来修饰成员(成员变量,成员方法)

  • 被private修饰的成员,只能在本类进行访问,针对private修饰的成员变量,如果需要被其他类使用,可以提供相应的操作
    • 提供“get变量名()”方法,用于获取成员变量的值,方法用public修饰
    • 提供“set变量名(参数)”方法,用于设置成员变量的值,方法用public修饰

this关键字

  • this修饰的变量用于指代成员变量,其主要作用是(区分局部变量和成员变量的重名问题)
    • 方法的形参如果与成员变量同名,不带this修饰的变量指的是形参,而不是成员变量
    • 方法的形参没有与成员变量同名,不带this修饰的变量指的是成员变量
  • 就近原则
    public claa Something{
    private int age;
    public void method(){
    int age = 10;
    sout(age) //就近原则,打印结果为10
    sout(this.age) //打印结果为null
    }
    }
    -this关键字的使用
    public class Student {
    private String name;
    private int age;

    public void setName(String name) {
    this.name = name;
    }

    public void setAge(int age) {
    this.age = age;
    }
    }

构造方法

构造方法是一种特殊的方法,每创建一次对象,就会调用一次构造方法,由jvm调用,不能手动调用

  • 作用:创建对象 Student stu = new Student();
  • 格式:方法名与类名相同,没有返回值类型,没有具体返回值
    public class 类名{
    ​ 修饰符 类名( 参数 ) {
    ​ }
    }
  • 功能:主要是完成对象数据的初始化
  • 示例代码:
    class Student {
    private String name;
    private int age;

    //构造方法
    public Student() {
    System.out.println("无参构造方法");
    }
  • 构造方法的创建
    如果没有定义构造方法,系统将给出一个默认的无参数构造方法
    如果定义了构造方法,系统将不再提供默认的构造方法
  • 构造方法的重载
    /*
    学生类
    */
    class Student {
    private String name;
    private int age;

    public Student() {} //无参数构造方法,无论是否使用,推荐手工书写无参数构造方法

    public Student(String name) {
    this.name = name;
    }

    public Student(int age) {
    this.age = age;
    }

    public Student(String name,int age) {
    this.name = name;
    this.age = age;
    }

    public void show() {
    System.out.println(name + "," + age);
    }
    }
    /*
    测试类
    */
    public class StudentDemo {
    public static void main(String[] args) {
    //创建对象
    Student s1 = new Student();
    s1.show();

    //public Student(String name)
    Student s2 = new Student("林青霞");
    s2.show();

    //public Student(int age)
    Student s3 = new Student(30);
    s3.show();

    //public Student(String name,int age)
    Student s4 = new Student("林青霞",30);
    s4.show();
    }
    }

标准JavaBean

  • 类名需要见名知意
  • 成员变量使用private修饰
  • 提供至少两个构造方法
    • 无参构造方法
    • 带全部参数的构造方法
  • get和set方法
    ​ 提供每一个成员变量对应的setXxx()/getXxx()
  • 如果还有其他行为,也需要写上

对象内存图

一个对象内存加载进程
一个对象的内存图
第二次创建同一种对象,class字节码不需要再加载,其已经存在方法区中

this的内存原理

this的本质:代表方法调用者的地址
this的内存原理

成员变量和局部变量

  • 类中位置不同:成员变量(类中方法外)局部变量(方法内部或方法声明上)
  • 内存中位置不同:成员变量(堆内存)局部变量(栈内存)
  • 生命周期不同:成员变量(随着对象的存在而存在,随着对象的消失而消失)局部变量(随着方法的调用而存在,醉着方法的调用完毕而消失)
  • 初始化值不同:成员变量(有默认初始化值)局部变量(没有默认初始化值,必须先定义,赋值才能使用)
  • 作用域不同: 成员变量在整个类中有效,局部变量在当前方法有效

static关键字

关于 static 关键字的使用,它可以用来修饰的成员变量和成员方法,被static修饰的成员是属于类的是放在静态区中,没有static修饰的成员变量和方法则是属于对象的。

  • 静态变量及其访问
    有static修饰成员变量,说明这个成员变量是属于类的,这个成员变量称为类变量或者静态成员变量,存储在静态区中(堆),随着类的加载而加载,优先于对象出现。 直接用类名访问即可。因为类只有一个,所以静态成员变量在内存区域中也只存在一份。所有的对象都可以共享这个变量
  • 实例变量及其访问
    无static修饰的成员变量属于每个对象的, 这个成员变量叫实例变量
  • 静态方法及其访问
    有static修饰成员方法,说明这个成员方法是属于类的,这个成员方法称为类方法或者静态方法,静态方法在内存区域中也只存在一份。所有的对象都可以共享这个方法。
    1. 静态方法只能访问静态变量和静态方法
    2. 非静态方法可以访问静态变量或者静态方法,也可以访问非静态的成员变量和非静态的成员方法
    3. 静态方法中是没有this关键字的
  • 实例方法
    实例方法是属于每个对象,必须创建类的对象才可以访问。

继承

就是子类继承父类的属性行为,使得子类对象可以直接具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。Java是单继承的,不支持多继承,但支持多层继承。每个类都直接或间接继承与Object(没有extends默认继承Object

class 父类 {
...
}

class 子类 extends 父类 {
...
}

子类继承父类的内容

子类继承父类的内容
私有的成员变量虽然能继承,但因为被private修饰,所以不能被实例调用,要设置相关的get()、set()方法

继承的内存图

继承的内存图
方法调用是到虚方法表中查询,不需要到每个类中查找
虚方法表

变量和方法访问

  • 成员变量访问,父类和子类中的成员变量按照就近原则访问,父类变量访问可以用super.
  • 成员方法访问,同样就近原则,this调访问从本类查找,再到父类,super调用直接访问父类

重写

方法重写 :子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,其覆盖原理是覆盖虚方法表中的相同方法。

  • @Override重写注解
    @Override是放在重写后的方法上,校验子类重写语法是否正确
  • 注意事项
    1. 方法重写是发生在子父类之间的关系。
    2. 子类方法覆盖父类方法,必须要保证权限大于等于父类权限。
    3. 子类方法覆盖父类方法,函数名和参数列表都要一模一样,返回值类型必须小于等于父类。
    4. 只有虚方法表中的方法才能被重写

构造方法的访问

构造方法的作用是初始化对象成员变量数据的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个super() ,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。

class Person {
private String name;
private int age;

public Person() {
System.out.println("父类无参");
}

// getter/setter省略
}

class Student extends Person {
private double score;

public Student() {
//super(); // 调用父类无参,默认就存在,可以不写,必须再第一行
System.out.println("子类无参");
}

public Student(double score) {
//super(); // 调用父类无参,默认就存在,可以不写,必须再第一行
this.score = score;
System.out.println("子类有参");
}

}

public class Demo07 {
public static void main(String[] args) {
Student s1 = new Student();
System.out.println("----------");
Student s2 = new Student(99.9);
}
}

输出结果:
父类无参
子类无参
----------
父类无参
子类有参

super()和this()调用构造方法

super(...) -- 调用父类的构造方法,根据参数匹配确认
this(...) -- 调用本类的其他构造方法,根据参数匹配确认

子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。
super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。
在每次创建子类对象时,先初始化父类空间,再创建其子类对象本身
super、this图解

/**
* this(...):
* 默认是去找本类中的其他构造方法,根据参数来确定具体调用哪一个构造方法。
* 为了借用其他构造方法的功能。
*
*/
public class ThisDemo01 {
public static void main(String[] args) {
Student xuGan = new Student();
System.out.println(xuGan.getName()); // 输出:徐干
System.out.println(xuGan.getAge());// 输出:21
System.out.println(xuGan.getSex());// 输出: 男
}
}

class Student{
private String name ;
private int age ;
private char sex ;

public Student() {
// 很弱,我的兄弟很牛逼啊,我可以调用其他构造方法:Student(String name, int age, char sex)
this("徐干",21,'男');
}

public Student(String name, int age, char sex) {
this.name = name ;
this.age = age ;
this.sex = sex ;
}

多态

多态的前提:有继承关系,子类对象是可以赋值给父类类型的变量。例如Animal是一个动物类型,而Cat是一个猫类型。Cat继承了Animal,Cat对象也是Animal类型,自然可以赋值给父类类型的变量Aniaml a = new Cat()。其意义在于使用父类型接收多种子类型,为子类不同方法重写的应用提供便利性。

多态调用成员的特点

调用成员变量时:编译看左边,运行看左边
调用成员方法时:编译看左边,运行看右边

Fu f = new Zi();
//编译看左边的父类中有没有name这个属性,没有就报错
//在实际运行的时候,把父类name属性的值打印出来
System.out.println(f.name);
//编译看左边的父类中有没有show这个方法,没有就报错
//在实际运行的时候,运行的是子类中的show方法
f.show();

多态编译阶段是看左边父类类型的,如果子类有些独有的功能,此时多态的写法就无法访问子类独有功能了

引用类型转换

  • 向上转型(自动转换):多态本身是子类类型向父类类型向上转换(自动转换)的过程,这个过程是默认的。
    父类类型  变量名 = new 子类类型();
    如:Animal a = new Cat();
  • 向下转型(强制转换):父类类型向子类类型向下转换的过程,这个过程是强制的。一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型。
    子类类型 变量名 = (子类类型) 父类变量名;
    如:Aniaml a = new Cat();
    Cat c =(Cat) a;

instanceof关键字

为了避免ClassCastException(类型转换异常)的发生,Java提供了 instanceof 关键字,给引用变量做类型的校验,格式如下:

if (a instanceof Cat){ //为true则强制转换
Cat c = (Cat)a;
}
//jdk14新特性,把判断和强转合并成了一行
if(a instanceof Cat c){ //先判断a是否为Cat类型,如果是,则强转成Cat类型,转换之后变量名为c
}

final关键字

不可改变,最终的含义。可以用于修饰类、方法和变量

  • 类:被修饰的类,不能被继承。
  • 方法:被修饰的方法,不能被重写。
  • 变量:被修饰的变量,有且仅能被赋值一次。
    1. final修饰的是基本类型变量,那么变量存储的数据值不能改变
    2. final修饰的是引用类型变量,那么变量存储的地址值不能改变,对象内部的变量可以改变
      final常用于修饰常量
      public static final double PI = 3.14159265358979323846;

权限修饰符

在Java中提供了四种访问权限,使用不同的访问权限修饰符修饰时,被修饰的内容会有不同的访问权限

  • public:公共的,所有地方都可以访问。
  • protected:本类 ,本包,其他包中的子类都可以访问。
  • 默认(没有修饰符):本类 ,本包可以访问。
    注意:默认是空着不写,不是default
  • private:私有的,当前类可以访问。
  • public > protected > 默认 > private

不同权限的访问能力

public protected 默认 private
同一类中
同一包中的类
不同包的子类
不同包中的无关类

可见,public具有最大权限。private则是最小权限。
声明为protected权限的成员变量和成员方法,可以被同一包中的所有类和不同包中的子类访问。
但是,在实际使用中,不同包中的子类要访问父类中protected权限的成员,却不是那么随意的调用,同包子类权限更大

  1. 子类可以通过继承获得不同包父类的protected权限成员变量和成员方法,在子类中可以直接访问
  2. 在子类中可以通过子类的对象访问父类的protected成员变量和方法
  3. 在异包子类中反而不能通过父类的对象访问父类的protected成员变量和方法,同包子类可以
  4. 在异包子类中不能通过父类包中子类的对象访问父类的protected成员变量和方法,同包子类可以
  5. 在异包中,与子类同包的其他类中不能通过子类的对象访问父类的protected成员变量和方法,同包中可以
  6. 子类中可以用super.调用父类protected变量和方法
    但是,在实际使用中,不同包中的子类要访问父类中protected权限的成员,却不是那么随意的调用
    编写代码时,如果没有特殊的考虑,建议这样使用权限:
  • 成员变量使用private ,隐藏细节。
  • 构造方法使用 public ,方便创建对象。
  • 成员方法使用public ,方便调用方法(如果方法中的代码是抽取其他方法中共性代码,这个方法一般也私有。

代码块

  • 局部代码块(写在方法里面的{}): 提前结束变量的生命周期(由于硬件发展内存已经很大,不常用了)
  • 构造代码块(写在成员位置的{}): 创造本类对象时,会优先与构造方法执行(所有构造方法之前都会执行,不过灵活,可以用this()或抽取方法替代)
  • 静态代码块(在构造代码块前加上static): 随着类的加载而自动加载,只执行一次(用于初始化数据)

抽象

使用abstract 关键字修饰,把没有方法体的方法称为抽象方法。Java语法规定,如果一个类包含抽象方法,那么该类必须是抽象类

抽象方法

修饰符 abstract 返回值类型 方法名 (参数列表);

抽象类

  • 继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类.
  • 此时的方法重写,是子类对父类抽象方法的完成实现,也叫做实现方法
  • 抽象类不能实例化
    abstract class 类名字 { 

    }

接口

  • 接口是更加彻底的抽象,是一种行为方法的抽象,JDK7之前,包括JDK7,接口中全部是抽象方法。接口同样是不能创建对象的。其功能主要是把部分子类的行为共性抽取出来
    //接口的定义格式:
    interface 接口名称{
    // 抽象方法
    }
  • JDK8以后允许定义默认方法,使用default关键字修饰,其作用是为了程序在接口升级过程中,不会因为实现类没有重写方法而受到影响。如果以后要重写需去掉default
    public default 返回值类型 方法名(参数列表){方法体···}  //public不写默认存在
  • JDK8以后允许在接口中定义静态方法,用static修饰,静态方法只能通过接口名调用,不能通过实习类名,实现类名对象调用
    public static 返回值类型 方法名(参数列表){方法体···}
  • JDK9新增了私有方法定义,这些私有方法只为接口本类服务,其作用是减少默认方法或者静态方法的代码重复,不需要外类访问
    privat void show(){} //普通私有方法,给默认方法服务的

    privat static void show(){} //静态私有方法,给静态方法服务的
  • 接口中的抽象方法默认会自动加上public abstract修饰,无需自己手写
  • 接口中没有构造方法在接口中定义的成员变量默认会加上: public static final修饰。也就是说在接口中定义的成员变量实际上是一个常量。这里是使用public static final修饰后,变量值就不可被修改,并且是静态化的变量可以直接用接口名访问,所以也叫常量。常量必须要给初始值

实现接口

/**接口的实现:
在Java中接口是被实现的,实现接口的类称为实现类。
实现类的格式:*/
class 类名 implements 接口1,接口2,接口3...{

}
  1. 必须重写实现的全部接口中所有抽象方法。
  2. 如果一个类实现了接口,但是没有重写完全部接口的全部抽象方法,这个类也必须定义成抽象类。
  3. 意义:接口体现的是一种规范,接口对实现类是一种强制性的约束,要么全部完成接口申明的功能,要么自己也定义成抽象类。这正是一种强制性的规范。
  4. 类与接口之间的关系是多实现的,一个类可以同时实现多个接口
    /**
    * Java中接口是可以被多实现的:
    * 一个类可以实现多个接口: Law, SportMan
    *
    * */
    public class JumpMan implements Law ,SportMan {
    @Override
    public void rule() {
    System.out.println("尊长守法");
    }
    }
  5. 接口与接口之间是可以多继承的:也就是一个接口可以同时继承多个接口 ,接口继承接口就是把其他接口的抽象方法与本接口进行了合并。注意:类与接口是实现关系接口与接口是继承关系
    public interface SportMan extends Law , Abc {
    void run();
    }

接口的应用

方法的参数是接口,就可以传递所有的实现类对象,这种方式为接口多态,其规则类似与继承多态

适配器

  1. 如果一个接口中,有10个抽象方法,但是我在实现类中,只需要用其中一个,该怎么办?

    可以在接口跟实现类中间,新建一个中间类(适配器类)
    让这个适配器类去实现接口,对接口里面的所有的方法做空重写。
    让子类继承这个适配器类,想要用到哪个方法,就重写哪个方法。
    因为中间类没有什么实际的意义,所以一般会把中间类定义为抽象的,不让外界创建对象

内部类

将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类。可以把内部类理解成寄生,外部类理解成宿主。内部类表现的事物是外部类的一部分,单独出现没有意义。但内部类和外部类在编译的时候是两个独立的字节码文件

访问特点

内部类方法体可以直接访问外部类的成员,包括私有。外部类方法体要访问内部类必须创建对象

成员内部类

  1. 成员内部类,类定义在了成员位置 (类中方法外称为成员位置,无static修饰的内部类)

    • 成员内部类可以被一些修饰符所修饰,比如: private,默认,protected,public等
    • 在成员内部类里面,JDK16之前不能定义静态变量,JDK16开始才可以定义静态变量。
  2. 获取成员内部类对象

    外部类.内部类 变量 = new 外部类().new 内部类();//外部直接创建成员内部类的对象
    // 在外部类中定义一个方法提供内部类的对象
    public class Outer {
    String name;
    private class Inner{
    static int a = 10;
    }
    public Inner getInstance(){
    return new Inner();
    }

    内部类被private修饰,外界无法直接获取内部类的对象,只能靠外部类方法提供内部类的对象

  3. 内部类访问外部类的成员变量

    • 创建内部类对象时,对象中有一个隐含的外部类名.this记录外部类对象的地址值,可以用外部类名.this访问外部类成员变量
      内部类访问变量内存图
  4. 静态内部类,类定义在了成员位置 (类中方法外称为成员位置,有static修饰的内部类)

    • 内部类只能调用外部类的静态变量和静态方法,如果想要访问非静态的要创建对象
    • 创建内部静态类对象格式:外部类.内部类 变量 = new 外部类.内部类构造器;
    • 调用静态内部类静态方法格式:外部内名.内部类名.方法名
    • 静态内部类中没有隐藏的Outer.this
  5. 局部内部类,类定义在方法内

    • 外界无法直接使用局部内部类,需要在方法内创建对象并使用
    • 该类可以直接访问外部类成员,也可以访问方法内的局部变量
  6. 匿名内部类,没有名字的内部类,可以在方法中,也可以在类中方法外。包含了* 继承或者实现关系 方法重写 创建对象
    匿名内部类必须继承一个父类或者实现一个父接口,格式如下

    new 父类名或者接口名(){
    // 方法重写
    @Override
    public void method() {
    // 执行语句
    }
    };

    如果我们希望定义一个只要使用一次的类,就可考虑使用匿名内部类。匿名内部类的本质作用是为了简化代码。

    interface Swim {
    public abstract void swimming();
    }

    public class Demo07 {
    public static void main(String[] args) {
    // 使用匿名内部类
    new Swim() {
    @Override
    public void swimming() {
    System.out.println("自由泳...");
    }
    }.swimming();

    // 接口 变量 = new 实现类(); // 多态,走子类的重写方法
    Swim s2 = new Swim() {
    @Override
    public void swimming() {
    System.out.println("蛙泳...");
    }
    };

    s2.swimming();
    s2.swimming();
    }
    }

    特点:

  7. 定义一个没有名字的内部类

  8. 这个类实现了父类,或者父类接口

  9. 匿名内部类会创建这个没有名字的类的对象
    通常在方法的形式参数是接口或者抽象类时,也可以将匿名内部类作为参数传递。代码如下:

    interface Swim {
    public abstract void swimming();
    }

    public class Demo07 {
    public static void main(String[] args) {
    // 普通方式传入对象
    // 创建实现类对象
    Student s = new Student();

    goSwimming(s);
    // 匿名内部类使用场景:作为方法参数传递
    Swim s3 = new Swim() {
    @Override
    public void swimming() {
    System.out.println("蝶泳...");
    }
    };
    // 传入匿名内部类
    goSwimming(s3);

    // 完美方案: 一步到位
    goSwimming(new Swim() {
    public void swimming() {
    System.out.println("大学生, 蛙泳...");
    }
    });

    goSwimming(new Swim() {
    public void swimming() {
    System.out.println("小学生, 自由泳...");
    }
    });
    }

    // 定义一个方法,模拟请一些人去游泳
    public static void goSwimming(Swim s) {
    s.swimming();
    }
    }