跳至主要內容

面向对象

Yang大约 15 分钟JavaJava基础

类和对象

  • 类是模子,确定对象将会拥有的特征(属性)和行为(方法),是对象的类型
  • 对象是类的实例表现,是特定类型的数据,声明对象在 中,实例化对象在
  • 一个 Java 文件中可以存在多个类,多个接口,但是只能存在一个 public 修饰的类或接口,且此时文件名需要与 public 修饰的类或者接口同名

命名规范

  • 由英文小写字母组成
  • 创建包:域名倒序 + 模块 + 功能
  • 一个包中不能存在同名类
  • package + 包名:指明该文件中定义的类所在的包,一个 java 文件中只能有一条 package 语句
  • java 文件中书写顺序:package - import - class
  • 导入包:分别以 * 和 指定类名 进行导入,以类名导入优先级最高
// 加载指定包下的所有直接允许访问的类,无法导入其子包中的类
import com.imooc.animal.*

// 加载指定包下的指定类(优先级最高)
import com.imooc.animal.Cat
  
// 在程序中直接加载类
com.imooc.animal.CatTest test = new com.imooc.animal.CatTest();

常用系统包

包名描述
java.lang包含 java 语言基础的类,该包在系统加载时默认导入
java.util包含 java 语言种常用工具,如:Scanner、Random
java.io包含输入输出相关功能的类,如:File、InputStream

构造方法

用于生成并初始化对象,针对不同的初始化状态,设置不同参数的构造方法

格式:访问修饰符 类名(参数列表){ *** }

  • 方法名与类名相同,包括大小写
  • 没有返回值,也不能用 void 修饰
  • 只能在对象实例化( new )时调用,不能通过对象名、类名等其它形式调用
  • 在非构造方法中,智能结合对象实例化操作被调用,不能通过方法名被访问
  • 当没有指定构造方法时,编译系统会自动添加无参无反的构造方法
  • 当有指定构造方法,无论是有参、无参的构造方法,编译器都不会自动添加无参的构造方法
  • 一个类中可以包含多个构造方法,只要满足参数类型、顺序、个数不同即可,也称为 构造方法重载
  • 多个构造方法之间可以使用 this() 进行调用
    • 带参时需按顺序传入设定的参数
    • 调用动作必须在构造函数的第一行
    • 一个构造方法中只能调用一个构造方法
    • 不能在类中普通成员方法内通过 this() 调用构造方法
    • 不能与 super() 共存
public class Monkey{
    // 构造方法
    public Monkey(){}
}

成员属性默认值

数据类型说明默认值
byte字节型0
short短整型0
int整型0
long长整型0
float单精度浮点型0.0
double双精度浮点型0.0
char字符型空字符
String字符串型null
boolean布尔型false

成员方法

根据类型所需任意功能,进行方法设置

格式:访问修饰符 返回值类型 方法名(参数列表){ *** }

  • 方法内定义的局部变量只能在方法里使用
  • 方法内不能定义静态成员
  • 不能使用 publicprotectedprivate 修饰

this 关键字

  • 代表当前对象本身,可以理解为:指向当前对象的一个引用
  • 可用于调用成员属性、成员方法、构造方法,也可以当做参数进行方法传参以及方法传值
  • 大部分时候,类的方法中访问成员属性时无需使用 this,但如果方法里存在局部变量和成员属性同名,但程序又需要在该方法里访问成员属性时,则必须使用 this 进行调用
  • 类的成员方法访问同类中其他成员方法时,this 关键字可加可不加,效果相同
  • 静态方法中不可使用 this

static 关键字

静态成员

static 修饰的成员变量,通常被称为静态成员、静态属性、类成员、全局属性等,方便在没有创建对象的情况下进行某些操作

  • 属于整个类,由类进行维护,仅在类初次加载时会被初始化,在类销毁时回收

  • 通过该类实例化的所有对象都共享类中的静态资源,任一对象中静态资源的修改都将影响所有对象

  • 由于静态成员在类加载期间就已经完成初始化,存储在 Java Heap(JDK7.0 之前存储在方法区)中静态存储区,因此优先于对象而存在,可以通过类名和对象名两种方式访问

    • 类.成员(推荐)
    • 对象.成员
  • 可以将频繁调用的公共信息、期望加快运行效率的成员设置为静态。但需注意,由于其生命周期长,即资源占用周期长,要慎用。

静态方法

static 修饰的成员方法,通常被称为静态方法、类方法、全局方法等

  • 与静态成员类似,静态方法属于整个类,由类进行维护,优先对象而存在,因此可以通过类名和对象名两种方式访问
  • 静态方法中不能直接访问同一个类中的非静态成员,只能直接调用一个类中的静态成员
  • 不允许在方法内部定义静态局部变量
  • 静态方法中不能使用 this
  • 非静态方法可以通过 类名.成员法成员this.成员 的方式访问类内静态成员/静态方法
  • 应用 this.静态成员/静态方法 时会出现警告,但不影响程序运行

代码块

执行优先级:静态代码块 > 构造代码块 > 构造方法

普通代码块

定义在方法内部,用 {} 括起来的代码段

  • 可以在方法中出现多次,按顺序在方法调用时执行,作用域独立
  • 适用于在方法内进行代码功能拆分

构造代码块

定义在类内部,没有被 static 修饰的,用 {} 括起来的代码段

  • 可以在类中出现多次,按顺序在每个对象实例化时执行
  • 运行顺序(由早到晚):静态代码块 构造代码块 构造方法
  • 可以在构造代码块中直接操作静态和非静态成员
  • 多适用于类中每个对象产生时都需要执行的功能封装。与构造方法的区别在于,构造方法是在 new 执行时有选择性的调用带参或者无参构造,而构造代码块则是在每个对象实例化时都一定会执行

静态代码块

static 修饰的,定义在类内部,用 {} 括起来的代码段

  • 只能出现在类内,不能出现在方法内
  • 可以出现多次,按顺序在类加载时执行
  • 无论该类实例化多少对象,只执行一次
  • 不能在静态代码块中声明静态成员,可以声明非静态成员,静态代码块中声明的成员,在外部无法访问
  • 不能在静态代码块中直接对非静态成员赋值
  • 基于性能优化的考量,多适用于需要在项目启动时执行一次的场景,比如项目整体资源加载等
// 普通代码块
{}

// 构造代码块
public class Book{
    {}
}

// 静态代码块
static {}

抽象类

意义

  • 为子类提供一个公共类型,封装子类中的重复内容(成员变量和方法)
  • 封装子类当中重复的内容(成员变量和方法)
  • 可借由父子关系限制子类设计的随意性,在一定程度上避免了无意义父类的实例化

描述

  • 抽象类不能实例化,只能通过引用指向子类实例(向上转型)
  • 含有抽象方法的类一定是抽象类,但是抽象类中可以没有抽象方法
  • 一个类继承抽象类之后,必须实现其所有的抽象方法,否则也需要设置为抽象类,不同子类对父类的抽象方法可以有不同的实现
  • 子类只能通过 extends 继承一个抽象父类
  • 抽象类中的静态成员和方法可以被子类继承应用
  • 应用场景:某个父类只知道其子类应该包含怎样的方法,但无法知道子类如何实现这些方法
public abstract class Animal{}
abstract public class Animal{}

抽象方法

  • 抽象方法必须定义在抽象类中

  • 抽象方法必须加 abstract 关键字,并且不能有方法体

  • 子类必须重写父类抽象方法(如果不想重写父类方法,则需要将子类也设置为抽象类)

  • 支持 publicprotected 和默认访问权限

  • 不能使用 staticfinalprivate 修饰

public abstract void eat();

内部类

在 Java 中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。与之对应,包含内部类的类被称为外部类

内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类,更好的实现了信息隐藏

成员内部类

  • 内部类中最常见的就是成员内部类,定义在类内部,可以看成是外部类的一个成员,也称为普通内部类
  • 在成员内部类中 无法声明静态成员
  • 内部类在外部使用时,无法直接实例化,需要借由外部类信息才能完成实例化
  • 内部类的访问修饰符可以任意,但是访问范围会受到影响
  • 内部类可以直接访问外部类的成员,如果出现同名属性,优先访问内部类中定义的
  • 可以在内部类中使用 外部类名.this.成员 的方式,访问外部类中的同名信息
  • 外部类访问内部类信息,需要通过内部类实例,无法直接访问
  • 内部类编译后 .class 文件命名:外部类名$内部类名.class
  • 应用场景:当两个类(A,B)彼此之间需要相互访问,且对其中某一类的应用限制较高(譬如B只在A的某种特定应用时才需要),可以将B设置为A的成员内部类
// Person.java
// 外部类
public class Person {
   int age;

   public Heart getHeart() {
      return new Heart();
   }

   // 内部类
   class Heart{
      public String beat() {
         return "心脏在跳动";
      }
   }
}

// PersonTest.java
public class PersonTest {
   public static void main(String[] args) {
      Person zhangsan = new Person();
      // 方式1:new 外部类.new 内部类
      Person.Heart heart1 = new Person().new Heart();
      // 方式2:外部类对象.new 内部类
      Person.Heart heart2 = zhangsan.new Heart();
      // 方式3:外部类对象.获取方法
      Person.Heart heart3 = zhangsan.getHeart();
     
      System.out.println(heart1.beat());
      System.out.println(heart2.beat());
      System.out.println(heart3.beat());
   }
}

静态内部类

  • 内部类加上 static 修饰符,也称为嵌套内部类,它无需依赖外部类的实例
  • 静态内部类中,只能直接访问外部类的静态成员,如果需要调用外部非静态成员,可以通过外部对象实例
  • 可以通过 外部类.内部类.静态成员 的方式,访问内部类中的静态成员
  • 当内部类属性与外部类属性同名时,默认直接调用内部类中的成员
  • 如果需要访问外部类中的静态属性,可以通过 外部类.属性 的方式
  • 如果需要访问外部类中的非静态属性,可以通过 new 外部类().属性 的方式
  • 使用场景:当类 A 需要使用类 B,而 B 并不需要直接访问外部类 A 的成员变量和方法时,可以将 B 作为 A 的静态内部类
public class Person{
  public static class Heart(){
    public void say(){
      System.out.print('Hello world!')
    }
  }
}

// 获取静态内部类对象实例
Person.Heart heart1 = new Person.Heart();

// 测试类中调用静态内部类的静态方法
Person.Heart.say();

方法内部类

  • 定义在外部类方法中的类,作用范围在方法内,也称局部内部类
  • 方法内部类中 不能定义静态成员
  • 和方法内部成员使用规则一样,不能使用 publicprotectedprivate 修饰
  • 方法内部类中可以包含 finalabstract 修饰的成员,但不推荐
  • 类中普通方法如需访问外部方法中的局部变量,则该变量也需定义为final(jdk1.8之后底层默认会给该变量加上final)
  • 内部类编译后 .class 文件命名:外部类+$数字+内部类.class
  • 应用场景:由于类对象只能在当前方法或者代码块中创建、使用,相当于一次性产品,使用场景比较少
// Person.java
public class Person {
    int age = 20;

    public Object getHeart() {
        class Heart {
            public String beat() {
                return Person.this.age + "岁的心脏在跳动";
            }
        }
      	// 这里需要直接返回方法运行结果,因为离开方法之后就访问不到任何方法内部类中的元素
        return new Heart().beat();
    }
}

// PersonTest.java
public class PersonTest {
    public static void main(String[] args) {
        Person person = new Person();
        System.out.println(person.getHeart());
    }
}

匿名内部类

  • 将类的定义与类的创建放到一起完成 new Person(){ ... }

  • 没有类名,没有 class 关键字,也无法产生具体类实例对象名

  • 必须继承或实现一个接口,指定给 new 的类型为匿名类的父类或父接口,但不能有显示的 extendsimplements 子句

  • 编译后的文件命名:外部类$数字.class

  • 无法在类型名称前添加修饰符 public protected private abstract static

  • 不能出现构造方法、静态成员、抽象方法,可添加构造代码块

  • 匿名内部类可以实现接口也可以继承父类,但是不可兼得

  • 适用场景:多可用于作为方法参数或者返回值,然后根据不同类型,无需产生具体类和对象名,返回不同的实现即可。

// Person.java
public abstract class Person {
    private String name;

    public Person() {
    }

    public String getName() {
        return name;
    }

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

    public abstract void read();
}

// PersonTest.java
public class PersonTest {
  
    public void getRead(Person person){
        person.read();
    }

    public static void main(String[] args) {
        PersonTest test = new PersonTest();
        test.getRead(new Person(){
            @Override
            public void read() {
                System.out.println("德玛西亚");
            }
        });
        test.getRead(new Person(){
            @Override
            public void read() {
                System.out.println("万众一心");
            }
        });
    }
}

问答

问:方法内部类中为什么不能有静态成员?

答:由于 static 类型的属性和方法,在类加载的时候就会存在于内存中。因此要想使用某个类的static属性和方法,那么这个类必须要先加载到虚拟机中。但是,普通的方法内部类并不会随外部类一起加载,只有在实例化外部类之后才会加载。因此,如果在外部类并没有实例化,内部类还没有加载的时候,直接调用内部类的静态成员或方法,这明显是矛盾的。所以普通的方法内部类不能有静态成员变量或静态方法。


问:为什么方法中返回值是 Object,返回的是方法调用还不报错?

public Object getHeart(){
    class Heart{
        public String beat(){
            new Person().eat();
            return Person.age + "岁的心脏在跳动";
        }
    }
    return new Heart().beat();
}

答:这里并不是返回方法调用,而是返回内部类Heart对象的beat方法的返回结果,整个方法最后一句的执行顺序是:1、通过 new 实例化内部类对象。 2、调用其 beat 方法。3、将 beat 方法返回值通过 return 带回,也就是此时的 Object 返回值,此处,也因为 Object 是 Java 中的根类,基于向上转型的原则,可以返回具体的子类String 类型的对象


什么是方法签名?

答:在 Java 中,方法签名着重指代:方法名和参数列表(包括:参数的类型、个数以及顺序)


接口和抽象类不能直接实例化,为什么下面代码可以这么写?

// Person.java
public abstract class Person{
    public abstract void read()
}

// 测试
test.getRead(new Person(){
    @Override
    public void read(){
        System.out.println("正在读书");
    }
})

答:此处并不是实例化 Person,可以理解为是创建了一个没有名字的 Person 类型的子类对象,如下面代码与上面的功能是一致的

// TempClass.java
class TempClass extends Person{
    @Override
    public void read(){
        System.out.println("正在读书");
    }
}

// PersonTest.java
class PersonTest{
    public void getRead(Person person){
        person.read();
    }
    
    public static void main(String[] args){
        PersonTest test = new PersonTest();
        test.getRead(new TempClass());
    }
}

基本原则(SOLID)

单一职责原则(SRP)

一个类应该只有一个发生变化的原因

一个类(大到模块、小到方法)承担的职责越多,被复用的可能性就越小,而且当一个类承担的职责过多,就相当于把这些职责耦合在一起,当其中一个职责变化时,可能会影响其他职责的运作,因此要将这些职责进行分离,将不同的职责封装在不同的类中,即将不同的变化原因封装在不同的类中,如果多个职责总是同时发生改变,则可将它们封装在同一类中。

开放封闭原则(OCP)

里氏替换原则(LSP)

接口分离原则(ISP)

依赖倒置原则(DIP)

上次编辑于:
贡献者: sunzhenyang