java面向对象

168 阅读13分钟

1、面向对象基础

(1)、面向对象语言的三大特征

  • 封装
  • 继承
  • 多态

(2)、采用面向对象的方式开发一个软件,生命周期包括

  • 面向对象的分析:OOA
  • 面向对象的设计:OOD
  • 面向对象的编程:OOP

(3)、对象和类的转换

  • 类 ——【实例化】——对象(实例instance)
  • 对象——【抽象化】——类

(4)、访问实例变量的语法格式

  • 读取数据:引用.变量名
  • 修改数据:引用.变量名 = 值

(5)、JVM的三块主要内存

  • 变化最频繁的是栈内存
  • 最先有数据的是方法区内存
  • 垃圾回收器主要针对的是堆内存

(6)、垃圾回收器什么时候考虑将某个java对象的内存回收?

当没有更多的引用指向它的时候,这个对象无法被访问,因为访问对象只能通过引用的方式访问

2、概念

(1)、引用

Student s = new Student();
  • Student是一个类,即引用数据类型

  • s是一个局部变量,称为”引用“,保存了另一个java对象的内存地址(引用也可以为成员变量)

  • new是一个运算符,作用是创建对象,并在JVM堆内存当中开辟新的内存空间

  • new Student()是一个对象,即new运算符在堆内存中开辟的内存空间

(2)、空指针异常

Customer c = new Customer();//Customer为自定义的一个类,里边有一个id属性
System.out.println(c.id);//0
c = null;
//以下程序编译可以通过,因为符合语法
//运行时出现空指针异常:java.lang.NullPointerException
//空引用访问"实例"相关的数据一定会出现空指针异常
System.out.println(c.id);
public class Test
{
    public static void main(String[] args){
        Test.doSome();
        doSome();
        
        //带有static的方法,实际上既可以采用“类名.”的方式访问,也可以采用“引用.”的方式访问
        //但是即使采用“引用.”的方式访问的时候,实际上执行的时候和引用指向的对象无关
        Test t = new Test();
        t.doSome();//编译运行都通过,但会警告

        t = null;
        t.doSome();//编译运行都通过,不会出现空指针异常,但会警告
    }
    
    public static void doSome(){
        System.out.println("do some!");
    }
}

3、封装

(1)、封装的好处

  • 封装之后,数据变得安全,对外提供简单的操作入口,程序员只能通过入口进行操作
  • 封装之后,有了“独立体”概念,意味着程序达到了”可重用性“

(2)、封装的步骤

  • 所有属性私有化,使用private关键字进行修饰,表示私有的,修饰的所有数据只能在本类中访问

    private int age;
    
  • 对外提供简单的操作入口,即外部程序必须通过这些入口访问属性

    • 对外提供两个公开的方法,分别是set方法(修改属性)和get方法(读取属性)

    • set方法命名规范:public void set+属性名(首字母大写) (形参) {

      ​ }

      public void setAge(int a){
          age = a;
      }
      
    • get方法命名规范:public 返回值类型 get+属性名(首字母大写) () {

      ​ }

      public int getAge(){
          return age;
      }
      
    • getter 和 setter方法的调用

      ​ getter 和 setter没有static关键字,所以用引用.方法名(实参);方式调用

      ​ 有static关键字的方法调用用类名.方法名(实参);方式调用

4、构造方法

(1)、语法结构

[修饰符列表] 构造方法名 (形式参数列表) {

​ 构造方法体;

}

构造方法与普通方法不一样,不需要指定返回值类型,也不能写void

构造方法的方法名必须和类名保持一致

(2)、调用

new 构造方法名(实参列表)

(3)、返回值

每一个构造方法实际上执行结束之后都有返回值,并且返回值类型是构造方法所在类的类型

(4)、作用

  • 创建对象
  • 给实例变量赋值

(5)、注意点

  • 当一个类中没有定义任何构造方法的话,系统会默认给该类提供一个无参数的构造方法,这个构造方法被称为缺省构造器

  • 当一个类中将构造方法定义出来了,那么系统默认不再为这个类提供缺省构造器,所以在开发中应尽量为当前类提供无参数构造方法。

    public class User{
        //无参数构造方法
        public User(){}
        
        //有参数构造方法
        public User(int i){}
    }
    
  • 构造方法支持重载机制,可以在一个类当中编写多个构造方法

4、this

this可以出现在“实例方法”中,this指向当前正在执行这个动作的对象,this不能使用在带有static关键字的方法当中

public class Test 
{
    int num = 11;
	public static void main (String[] args){
        //没有“当前对象”this
        //访问“当前对象”的“num”属性
        System.out.println(num);//编译报错
        System.out.println(this.num);//编译报错
        
        //编译通过
        Test Obj1 = new Test();
        System.out.println(obj1.num);
    }    
}

this在大部分情况下都是可以省略的,但是在区分局部变量和实例变量的时候不能省略

public class Test
{
    private int id;//实例变量
    public Test(){
        
    }
    public Test(int id){
        this.id = id;//这里的this不能省略
    }
    public int getId(){
        return id;//这里的this可以省略
    }
    public void setId(int id){
        this.id = id;//这里的this不能省略
    }
}

this可以用来调用构造方法,并且不会创建新的java对象

public Date
{
    private int year;
    private int month;
    private int day;
    //当构造方法有参数时,构建参数中的日期
    public Date(int year, int month, int day){
        this.year = year;
        this.month = month;
        this.day = day;
    }
    //当构造方法无参数时,默认创建的日期是“1970-1-1”
    public Date(){
        this(1970,1,1);
    }
}

综上,this可以用在以下两个地方:

  • 在实例方法当中,代表当前对象【语法格式:this.】
  • 在构造方法当中,通过当前的构造方法调用其他的构造方法。这种方法只能出现在构造函数第一行【语法格式:this(实参)】
public Test
{
    //不带static的变量
    int i = 10;
    
    //带static的方法
    public static void doSome(){
        System.out.println("do some!");
    }
    
    //不带static的方法
    public void doOther(){
        System.out.println("do other!");
    }
    
    //带有static的方法
    public static void method1(){
        //调用doSome
        //使用完整的方式
        Test.doSome();
        //使用省略的方式
        doSome();
        
        //调用doOther
        //使用完整的方式
        Test t = new Test();
        t.doOther();
        //使用省略的方式
        
        //访问i
        //使用完整的方式
        System.out.println(t.i);
        //使用省略的方式
    }
    
    //不带static的方法
    public void method2(){
        //调用doSome
        //使用完整的方式
        Test.doSome();
        //使用省略的方式
        doSome();
        
        //调用doOther
        //使用完整的方式
        this.doOther();
        //使用省略的方式
        doOther();
        
        //访问i
        //使用完整的方式
        System.out.println(this.i);
        //使用省略的方式
        System.out.println(i);
    }
    
    //主方法
    public static void main(String[] args){
        //调用method1
        //使用完整的方式
        Test.method1();
        //使用省略的方式
        method1();
        
        //调用method2
        //使用完整的方式
        Test t = new Test();
        t.method2();
        //使用省略的方式
    }
}

5、static

static修饰的元素成为静态的,都可以用“类名 .”的方式访问,也可以用“引用 .”的方式访问(会警告)

static修饰的元素都是类级别的元素,和具体的对象无关

(1)、静态代码块

  • 语法格式

    static {
        java语句;
    }
    
    
  • 静态代码块在类加载时执行,先于主方法执行,并且只执行一次

  • 静态代码块在一个类中可以编写多个,并且遵循自上而下的顺序执行

  • 通常在静态代码块当中完成预备工作,先完成数据的准备工作,例如:初始化连接池,解析XML配置文件等等

  • 了解:实例代码块

    语法格式:
    {
    	java语句;
    }
    
    

    在调用构造函数的时候执行,并且先于构造函数执行。构造函数执行一次,实例代码块执行一次。

6、继承

  • 作用

    继承最基本的作用是代码的复用,但是继承最重要的作用是有了继承才有“方法覆盖”和“多态机制”

  • 语法格式

    [修饰符列表] class 类名 extends 父类名{
        类体 = 属性 + 方法
    }
    
    
  • 关于继承的一些术语

    • B类继承A类,其中:

      ​ A类称为:父类、基类、超类、superclass

      ​ B类称为:子类、派生类、subclass

  • java子类继承父类的哪部分数据

    • 私有的不支持继承
    • 构造方法不支持继承
    • 其他数据都可以继承
  • 注意点

    • java的继承只支持单继承,一个类不能同时继承很多类,只能继承一个类。c++支持多继承。

    • 虽然java只支持单继承,但是一个类可以间接地继承其他类,如

      C extends B{
          
      }
      B extends A{
          
      }
      A extends T{
          
      }
      
      //C直接继承B类,但是C间接继承A、T类
      
      
    • java中假如一个类没有显示继承任何类,该类默认继承JavaSE库当中的提供的java.lang.Object类。java中任何一个类都有Object类的特征。

7、方法覆盖(方法重写/override)

(1)、方法重写的应用场景

​ 当父类的方法已经无法满足当前子类的业务需求,子类有必要将父类中继承过来的方法进行重新编写,这个重新编写的过程称为方法覆盖/方法重写。

(2)、方法重写需要满足的条件

  • 方法重写发生在具有继承关系的父子类之间
  • 返回值类型相同,方法名相同,形成列表相同
  • 访问权限不能更低,可以更高【private→public】
  • 抛出异常不能更多,可以更少

(3)、注意点

  • 私有方法不能继承,所以不能覆盖
  • 构造方法不能继承,所以不能覆盖
  • 静态方法不存在覆盖
  • 覆盖只针对方法,不谈属性

8、多态

(1)、概念

  • 向上转型(upcasting/自动类型转换):子类型→父类型
  • 向下转型(downcasting/强制类型转换):父类型→子类型(需要加强制类型转换符)

无论是向上转型还是向下转型,两种类型之间必须有继承关系,否则程序无法编译通过

(2)、多态的产生

//以下程序中,Bird是一个类,Animal是一个类,Cat也是一个类,Cat类和Bird类都继承Animal类,Animal类中有move()方法,Cat类中对move()方法进行了重写并且定义了catchMouse()方法,Bird类中对move()方法进行了重写并且定义了fly()方法
Animal a2 = new Cat();//向上类型转换
a2.move();//调用的是Cat中的move方法
a2.catchMouse();//编译报错,因为编译器默认a2是Animal类型,Animal中没有catchMouse方法

Cat c2 = (Cat)a2;//向下类型转换,必须加强制类型转换符号
c2.catchMouse();//编译执行都成功

Animal a3 = new Bird();//父类型引用指向子类型对象(多态)
Cat c3 = (Cat)a3;//编译通过,但运行报错:java.lang.ClassCastException——类型转换异常

if(a3 instanceof Cat){//解决类型转换异常的方法
    Cat c3 = (Cat)a3;
    c3.catchMouse();//调用子类对象中特有的方法
}else if(a3 instanceof Bird){
    Bird b3 = (Bird)a3;
    b3.fly();//调用子类对象中特有的方法
}

  • 静态绑定/编译阶段绑定:在程序编译阶段,编译器检查a2为Animal类型,由于Animal类中有move方法,所以编译通过了。程序只有静态绑定成功之后才有了后续的运行阶段。

  • 动态绑定/运行阶段绑定:在程序运行阶段,JVM堆内存中真实创建的对象是Cat对象,所以该程序在运行阶段调用的是Cat对象的move方法。

无论Cat类中有没有对move方法进行重写,运行阶段调用的一定是Cat对象的move方法,因为真实的对象是Cat对象。

父类型引用指向子类型对象这种机制导致程序在编译阶段绑定和运行阶段绑定两种不同的形态(状态),这种机制可以称为一种多态语法机制。

(3)、什么时候需要向下转型?

当调用的方法是子类型中特有的,在父类型当中不存在,必须进行向下类型转换

(4)、类型转换异常

java.lang.ClassCastException:以上例子中,在编译阶段编译器认为a3的数据类型是Animal,Animal和Cat之间可以向下转型,语法合格,所以编译通过;但是在运行阶段,JVM堆内存当中,a3的真实存在的对象是Bird类型,Bird对象无法转换为Cat对象,因为两种类型之间不存在继承关系,所以运行报错。这种异常总是在“向下转换”的时候发生。

向上转型编译通过,运行一定通过;

向下转型编译通过,运行不一定通过。

(5)、避免类型转换异常的方法——使用instanceof运算符

  • 语法格式:引用 instanceof 数据类型名
  • 运行结果:true/false
  • 在进行强制类型转换之前,应采用instanceof运算符进行判断,避免出现ClassCastException异常。

(6)、多态的作用

降低程序的耦合度,提高程序的扩展力

核心:面向抽象编程,尽量不要面向具体编程

9、final

  • final关键字修饰的类无法被继承
  • final关键字修饰的方法无法被覆盖
  • final关键字修饰的变量一旦被赋值不可重新赋值【不可二次赋值】
  • final关键字修饰的实例变量必须手动赋值,不能采用系统默认值,这种变量一般和static联合使用,被称为“常量”,常量的定义语法格式:public static final 类型 常量名 = 值;常量名字全部大写,每个单词之间使用下划线连接
  • final关键字修饰的引用一旦指向某个对象之后不能再指向其他对象,并且被指向的对象无法被垃圾回收器回收,但是所指向的对象内部是可以被修改的
public class Final{
    final int age;//编译报错,实例变量系统会赋默认值,而final修饰的变量一旦赋值就不能更改,所以必须手动赋值,不能采用系统默认值
    //第一种解决方案
    final int age = 10;
    //第二种解决方案
    final int num;
    public Final(){
        this.num = 10;
    }
    //以上两种解决方案,本质上是一种方式,都是在构造方法执行的过程中给实例变量赋值
    
    public static final String GUO_JI = "中国";
    
    public static void main(String[] args){
        final int a;
        a = 10;
        a = 11;//编译报错,不可二次赋值
    }
}

10、package和import

(1)、package(又称为包机制)

  • 定义package

    在java源程序的第一行编写package语句,并且package只能编写一个语句,语法结构为:package 包名

  • 包名命名规范

    公司域名倒序 + 项目名 + 模块名 + 功能名;

    采用这种方式命名重复的几率较低,因为公司域名具有全球唯一性。

    例如:com.bjpowernode.oa.user.service;

    ​ org.apache.tomcat.core;

    包名要求全部小写,包名也是标识符,必须遵守标识符的命名规范

(2)、import

  • import作用

    import语句用来导入其他类,同一个包下的类不需要导入

  • 什么时候需要import

    不是java.lang包下(java.lang包系统自动引入),并且不在同一个包下的时候,需要使用import进行引入

  • import语法

    import 类名;
    import 包名.*;
    //如:
    import java.util.*;
    Date d = new Date();//若没有import导入直接这样写会报错,因为没有找到Date类
    java.util.Date dd = new Date();//通过,但是较麻烦,建议用import语句导入包,在正确的位置写上`import java.util.*;`语句或者`import java.util.Date;`语句
    
    

    import语句需要编写到package语句之下,class语句之上

11、访问控制权限

访问控制权限修饰符用来控制元素的访问范围,可以修饰类、变量、方法名等等,类的访问控制权限修饰符只能采用public和缺省的修饰符修饰(内部类除外)

(1)、访问控制权限修饰符包括

  • public:公开的,在任何位置都可以访问
  • protected:受保护的,只能在同个包和子类中访问
  • 缺省(不写):只能在同个包中访问
  • private:私有的,只能在本类中访问