Java学习笔记--面对对象OOP
阅读原文时间:2023年07月09日阅读:1

面向对象编程 OOP

面向对象编程的本质:以类的方式组织代码,以对象的方法组织数据

面向对象编程的三大特征:

  • 封装

  • 继承

  • 多态

  • 静态方法

    通过 static 关键词说明

    调用方法:通过方法名直接调用

  • 动态方法

    无static关键词

    调用方法:实例化后通过实例直接调用

    Student student = new Student();
    student.say();

值传递(pass by value)是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。

引用传递(pass by reference)是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

public class OopDemo04 {
    public static void main(String[] args) {
        int a=1;
        System.out.println(a);
        change(a);           // 值传递
        System.out.println(a);
        System.out.println("============");
        Students students =new Students();
        System.out.println(students.num);
        add(students);        // 引用传递
        System.out.println(students.num);
    }
    public static void change(int a){
        a = 10;
        return ;
    }
    public static void add(Students students){
        students.num++;
    }
}
class Students { // 一个java文件里面只有一个 public class
        int num = 10;
}
  • 类是一种抽象的数据类型,它是对某一类事物的整体描述,但并不代表某个具体事物

  • 对象是抽象概念的具体实例

使用new关键字创建对象

创建时除了分配内存空间外,还会给创建好的对象进行默认初始化以及对类中的构造器进行调用

类中构造器也称构造方法,在创建对象时必须进行调用,并且构造器有以下连个特点:

  • 必须和类的名字相同

  • 必须没有返回类型,也无void

    public class Person {
        public Person() {
        }
    }

创建实例

public class Application {
    public static void main(String[] args) {
        Student xiaoming = new Student();
        Student xiaohong = new Student();
        xiaoming.name = "xiaoming";
        xiaohong.name = "xiaohong";
        xiaohong.age = 10;
        xiaoming.age = 11;
        System.out.println(xiaohong.name);
        System.out.println(xiaoming.age);
        xiaohong.study();
    }
}

定义一个显示构造器

  • 无参构造

    public class Person {
        String name;
        // 一个类必须有构造器
    // 显示构造
    // 无参构造
    public Person(){
        this.name = "NiDie";
    }
    }
  • 有参构造

    public class Person {
        String name;
        // 一个类必须有构造器
    // 显示构造
    // 有参构造,必须显示定义
    public Person(String name){
        this.name = name;
    }
    
    // 定义了一个有参构造后,要调用无参构造,必须显示定义一个无参构造
    public Person() {
    }
    // Alt + insert <Conductor> 生成构造器
    } public class Application { public static void main(String[] args) { Pet dog = new Pet(); dog.name = "WangCai"; dog.age = 3; Pet cat = new Pet(); } } public class Pet { String name; int age; }

    内存状况

封装的好处

  • 提高系统的安全性

  • 隐藏代码的实现细节

  • 统一接口

  • 增加系统的可维护性

    public class Student {
        private String name;
        private String id;
        private char sex;
        // 私有属性可以通过一些public的方法进行操作--get/set
        public String getName(){
            return this.name;
        }
        public void setName(String name){
            this.name = name;
        }
        // Alt + Insert +<Sitter and Getter>生成get和set方法
        public String getId() {
            return id;
        }
    
        public void setId(String id) {
        if(id.length()==10)
            this.id = id;    // 合法性检验
        else
            System.out.println("Wrong input");
        }
    }</code></pre></li>

在Java中所有类直接或间接的继承Object类

一个类只有一个父类,可以有多个子类

public class Person {
    int age;
    String name;
    char sex;
    // public
    // protected
    // default
    // private
    private double money;

    public void say(){
        System.out.println("Fuck!");
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }
    // Ctrl + H 打开类的层次结构
}


// Student子类
// 子类继承了父类就会有父类的部分方法和属性
// 私有属性无法被继承
public class Student extends Person{
}

super注意点

  • super调用父类的构造方法,必须在构造方法的第一个

  • super必须只能出现在子类的方法或构造方法中

  • super和this不能同时调用构造方法

  • this和super代表的对象不同,this时本身调用者这个对象,super代表父类的应用

  • this没有继承也可以使用,super必须在继承时才能使用

  • this(); --本类的构造,super(); --父类的构造

    // Student子类
    // 子类继承了父类就会有父类的全部方法和部分属性
    // 私有属性无法被继承
    public class Student extends Person{

    public Student() {
        // 隐藏代码:调用父类构造器super();
        super();//调用父类或自己的构造器,必须在子类或自己构造器的第一行
    }
    
    String name = "WhiteDog";
    public void test(String name){
        System.out.println(name);
        System.out.println(this.name);
        System.out.println(super.name);
    }
    public void say(){
        System.out.println("You!");
    }
    public void say1(){
        say();
        this.say();
        super.say();
    }

    }

重写注意

  • 需要有继承关系,子类重写父类的方法

  • 子类和父类的方法必须一致:方法体不同

  • 为什么要重写

    1. 父类的功能不一定需要,或不满足

      public class A {
      public void test(){
      System.out.println("A->test");
      }
      }

      //重写都是方法的重写,和属性无关
      public class B extends A{
      //override 重写
      // Alt + Insert + 实现重写
      @Override//注解:有功能的注释
      public void test() {
      System.out.println("B->test");
      }
      }

      public class Application {
      public static void main(String[] args) {
      // 静态方法的调用只和左边定义的数据类型有关
      // 非静态方法才能实现方法重写
      // B b = new B();
      // b.test();
      // //父类的引用可以指向指向子类
      // A a = new A();
      // a.test();
      B b = new B();
      b.test();
      //父类的引用可以指向指向子类
      A a = new A();
      a.test();
      }
      }

Java的实例方法调用是基于运行时的实际类型的动态调用,而非变量的声明类型。

多态存在允许添加更多类型的子类实现功能扩展,却不需要修改基于父类的代码。

public class Person {
    public void fuck(){
        System.out.println("Fuck");
    }
}

public class Student extends Person{
    @Override
    public void fuck() {
        System.out.println("Fuck You");
    }
    public void eat(){
        System.out.println("Shit!");
    }
}
public class Application {
    public static void main(String[] args) {
        // 一个类型的实际类型时确定的
        //new Person();
        // new Student();
        // 指向的引用类不确定:父类的引用指向子类

        // Student 能调用的方法都是自己的或者继承的父类
        Student s1 = new Student();
        // Person 父类型 可以指向子类,但不能调用子类独有的方法
        Person s2 = new Student();
        Object s3 = new Student();

        //对象能执行哪些方法,主要看左边的类型
        s1.fuck();

        s2.fuck(); // 子类重写了父类的方法,执行子类的方法
        ((Student) s2).eat(); // 类型强制转换
    }
}

多态注意事项

  1. 多态的方法是多态的

  2. 父类不能执行子类的方法,会出啊先类型转换异常

  3. 多态存在条件:继承关系;方法重写;父类引用指向子类对象

  4. 以下方法不能重写

    • static方法,属于类但不属于实例
    • final常量
    • private方法
  5. 继承可以允许子类覆写父类的方法。如果一个父类不允许子类对它的某个方法进行覆写,可以把该方法标记为final。用final修饰的方法不能被Override

  6. 如果一个类不希望任何其他类继承自它,那么可以把这个类本身标记为final。用final修饰的类不能被继承

  7. 对于一个类的实例字段,同样可以用final修饰。用final修饰的字段在初始化后不能被修改

instanceof关键字

instanceof 严格来说是Java中的一个双目运算符,用来测试一个对象是否为一个类的实例

boolean result = obj instanceof Class

其中 obj 为一个对象,Class 表示一个类或者一个接口,当 obj 为 Class 的对象,或者是其直接或间接子类,或者是其接口的实现类,结果result 都返回 true,否则返回false。

注意:编译器会检查 obj 是否能转换成右边的class类型,如果不能转换则直接报错,如果不能确定类型,则通过编译,具体看运行时定。

类型转换

public class Application {
    public static void main(String[] args) {
        // 子类转换为父类,可能会丢失部分方法
        Student student = new Student();
        student.eat();
        Person person = student;
       // person.eat();报错,丢失方法
        ((Student) person).eat();//再向子类强制转换
    }
}

父类向子类转型需要强制转换

类型转换方便方法的调用,减少重复代码

static关键字

静态变量

public class Student {
    static int age; //静态变量
    private double score; //非静态变量

    public static void main(String[] args) {
        Student s1 = new Student();
        System.out.println(s1.score);//非静态变量只能实例化后引用,对于方法也一样
        System.out.println(s1.age); //静态变量可直接在类中直接引用,也可实例化后应用
        System.out.println(age);
        say();
        s1.piss();
    }
    public static void say(){
        System.out.println("Hi");
    }
    public  void piss(){
        System.out.println("Pee");
    }
}

Java代码块

public class Person {
    //2:每次都执行,可用于赋初值
    {
        System.out.println("匿名代码");
    }
    //1:只执行一次
    static {
        System.out.println("静态代码");
    }
    //3
    public Person(){
        System.out.println("构造方法");
    }

    public static void main(String[] args) {
        Person person = new Person();
        System.out.println("======");
        Person person1 = new Person();

    }
}



//静态导入包
import static java.lang.Math.random;
import static java.lang.Math.PI;
public class Application {
    public static void main(String[] args) {
        System.out.println(Math.random());
        System.out.println(random());
        System.out.println(PI);
    }
}


// abstract 抽象类
public abstract class Action {
    //abstract 抽象类只有方法没有实现
    public abstract void doIt();
}


//抽象类的所有方法,继承了它的子类,都必须实现它的方法
public class A extends Action{
    @Override
    public void doIt() {

    }
}

抽象类的特点

  1. 不能被实例化new,只能靠子类去实现,相当于约束
  2. 抽象类可以写普通方法
  3. 抽象方法必须定义在抽象类中
  4. 抽象的抽象:约束

接口:只有规范,定义了一组规则

关键字:interface

接口没有构造方法,不能被实例化

public interface UserService {
    int AGE = 1;// 接口可以定义静态常量

    // 抽象类中定义都是抽象的 public abstract
    public abstract void run();
    void add(String name);
    void  delete(String name);
    void  update(String name);
    void  query(String name);

}
public interface TimeService {
    void timer();
}



// 类 可以继承接口,关键子implement
// Class implement Interface
// 继承接口后,需要重写接口的所有方法
// 多继承,一个类可以继承多个接口
public class UserServiceImp implements UserService,TimeService{
    @Override
    public void run() {

    }

    @Override
    public void add(String name) {

    }

    @Override
    public void delete(String name) {

    }

    @Override
    public void update(String name) {

    }

    @Override
    public void timer() {

    }

    @Override
    public void query(String name) {

    }
}

在一个类中创建另外一个类,叫做成员内部类。这个成员内部类可以静态的(利用static关键字修饰),也可以是非静态的。

成员内部类

定义

class C{
    class D{

    }
}

成员内部类可以无条件访问外部类的属性和方法,但是外部类想要访问内部类属性或方法时,必须要创建一个内部类对象,然后通过该对象访问内部类的属性或方法

class C{
    private String name = "外部类";
    public void run(){
        System.out.println("外部类奔跑");
    }
    class D{
        public void say(){
            System.out.println(name);
            run();
        }
    }
}
class C{
    private String name = "外部类";
    public void run(){
        System.out.println("外部类奔跑");
    }
    /*使用内部类的属性和方法*/
    public void eat(){
        D d = new D();
        System.out.println(d.value);
        d.say();
    }
    class D{
        private String value = "DDD";
        public void say(){
            System.out.println(name);
            run();
        }
    }
}

外部类属性或方法隐藏

如果成员内部类的属性或者方法与外部类的同名,将导致外部类的这些属性与方法在内部类被隐藏,也可按照该格式调用,外部类.this.属性/方法。

class C{
    private String name = "外部类";
    public void run(){
        System.out.println("外部类奔跑");
    }
    /*使用内部类的属性和方法*/
    public void eat(){
        D d = new D();
        System.out.println(d.value);
        d.say();
    }
    class D{
        private String value = "DDD";
        private String name = "内部类";
        public void say(){
            System.out.println(C.this.name);
            System.out.println(name);
            run();
        }
    }
}

创建内部类对象

public class Test10 {
    public static void main(String[] args) {
        /*方式1创建成员内部类对象*/
        C c = new C();
        C.D d = c.new D();
        /*方式2创建成员内部类对象*/
        C.D d1 = c.getClass();
    }
}

成员内部类的访问权限

成员内部类前可加上四种访问修饰符。

  • private:仅外部类可访问。
  • protected:同包下或继承类可访问。
  • default:同包下可访问。
  • public:所有类可访问。

局部内部类

局部内部类存在于方法中。

他和成员内部类的区别在于局部内部类的访问权限仅限于方法或作用域内

class K{
    public void say(){
        class J{

        }
    }
}

匿名内部类

public class Test13 {
    public static void main(String[] args) {
        driveCar(new Car(){
            @Override
            public void drive() {
                System.out.println("驾驶着BMW汽车");
            }
        });
    }
    public static void driveCar(Car car){
        car.drive();
    }
}

interface Car {
    void drive();
}

分析以上代码知道静态方法driveCar需要一个Car对象,我们通过实现接口创建一个匿名类对象传递过去。事实上还可以通过继承类来创建一个匿名内部类对象。

注意事项

  • 匿名内部类没有构造方法。也是唯一没有构造方法的内部类。

  • 匿名内部类和局部内部类只能访问外部类的final变量。

静态内部类

静态内部类和成员内部类相比多了一个static修饰符。它与类的静态成员变量一般,是不依赖于外部类的。同时静态内部类也有它的特殊性。因为外部类加载时只会加载静态域,所以静态内部类不能使用外部类的非静态变量与方法。

同时可以知道成员内部类里面是不能含静态属性或方法的。

class U {
    static class I {

    }
}

内部类好处

  1. 完善了Java多继承机制,由于每一个内部类都可以独立的继承接口或类,所以无论外部类是否继承或实现了某个类或接口,对于内部类没有影响。
  2. 方便写事件驱动程序。