【Java】13. 面向对象

100 阅读11分钟

13. 面向对象

13.1 类和对象

简单理解:类是对象的模板,描述对象将来长什么样,用构造给变量赋值


// 类:属性(成员变量)+ 方法(成员方法)
// 类的定义
public class Phone {
    String brand;
    int price;
    public void call() {
        System.out.println("打电话");
    }
}
// 对象的使用
public class PhoneDemo {
    public static void main(String[] args) {
        Phone p = new Phone();
        p.brand = "小米";
        System.out.println(p.brand);
        p.call();
    }
}

13.2 成员变量和局部变量

成员变量和局部变量的区别

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

13.3 封装


// 学生类 
class Student {
    private String name;
    private int age;
    //get/set方法
    public void setName(String n) {
        name = n;
        //当n=>name时,则this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setAge(int a) {
        age = a;
    }
    public int getAge() {
        return age;
    }
    public void show() {
        System.out.println(name + "," + age);
    }
}
// 学生测试类
public class StudentDemo {
    public static void main(String[] args) {
        Student s = new Student();
        //使用set方法给成员变量赋值
        s.setName("林青霞");
        s.setAge(30);
        s.show();
        //使用get方法获取成员变量的值
        System.out.println(s.getName() + "---" + s.getAge());
    }
}
​
// this 关键字
// this修饰的变量指代成员变量,其主要作用是区分局部变量和成员变量的重名问题

13.4 构造方法

主要是完成对象数据的初始化。

this代表所在类的当前对象的引用(地址值),即代表当前对象。


/* 学生类 */
class Student {
    private String name;
    private int age;
    //如果没有定义构造方法,系统将给出一个默认的无参数构造方法
    /*
        byte short int long char 默认值是 0
        float double 默认值是 0.0
        boolean 默认值是 flase
        剩余类型 默认值是 null
    */
    //无参构造
    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();
    }
}

13.5 static 关键字

被static修饰的成员是属于类的,是放在静态区中,没有static修饰的成员变量和方法则是属于对象的。

有static修饰成员变量或成员方法,说明这个成员变量或成员方法是属于类的,这个成员变量称为类变量或者静态成员变量,成员方法称为类方法或者静态方法。直接用类名访问即可(也可以通过对象调用,但通常不建议这样做,因为静态方法不依赖于特定的对象)。

因为类只有一个,所以静态成员变量或静态方法在内存区域中也只存在一份。所有的对象都可以共享这个变量。例如,静态方法可以用于创建工具类,实现通用的功能,如数学计算、字符串操作、文件处理等。

无static修饰的成员变量或成员方法属于每个对象,这个成员变量叫实例变量,这个成员方法也叫做实例方法,必须创建类的对象才可以访问。只有参数全部来源于外部传入,而不是对象自身,才可以使用static。


public class Student {
    public static String schoolName = "jingmao";
    public static void study(){
        System.out.println("我们都在学习");   
    }
}
public static void  main(String[] args){
    System.out.println(Student.schoolName); // jingmao
    Student.schoolName = "hebeijingmao";
    System.out.println(Student.schoolName); // hebeijingmao
    Student.study();
}

比较四种变量:

  1. 参数变量:作用范围是从方法调用开始,直到方法调用结束
  2. 局部变量:作用范围从定义开始,直到包围它的 {} 结束,必须赋初值才能使用
  3. 对象变量(成员变量):从对象创建开始,直到对象不能使用为止
  4. 静态变量:从类加载开始,直到类卸载为止

13.6 继承

13.6.1 继承及其特点

继承:就是子类继承父类的属性行为,使得子类对象可以直接具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。


//一个类只能继承一个直接父类
public class 父类 {
    ...
}
public class 子类 extends 父类 {
    ...
}

子类不能继承父类的构造方法。子类可以继承父类的私有成员(成员变量,方法),只是子类无法直接访问而已,可以通过getter/setter方法访问父类的private成员变量。

注:

  • 子父类中出现了同名的成员变量时,子类会优先访问自己对象中的成员变量。如果子类想要访问父类的成员变量num,需要使用super 关键字,即super.num

  • 子父类中出现重名的成员方法,则创建子类对象调用该方法的时候,子类对象会优先调用自己的方法。

  • @Override重写注解,子类方法覆盖父类方法,返回值类型、函数名和参数列表都要一模一样。

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

  • 继承后的构造方法

    
    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 Demo {
        public static void main(String[] args) {
            Student s1 = new Student();
            System.out.println("----------");
            Student s2 = new Student(99.9);
        }
    }
    输出结果:
    父类无参
    子类无参
    ----------
    父类无参
    子类有参
    

13.6.2 super(...)和this(...)

super(...)调用父类构造方法,以便初始化继承自父类对象的name和age


class Person {
    private String name ="凤姐";
    private int age = 20;
    public Person() {
        System.out.println("父类无参");
    }
    public Person(String name , int age){
        this.name = name ;
        this.age = age ;
    }
    // getter/setter省略
}
​
class Student extends Person {
    private double score = 100;
    public Student() {
        //super(); // 调用父类无参构造方法,默认就存在,可以不写,必须再第一行
        System.out.println("子类无参");
    }
     public Student(String name , int age,double score) {
        super(name ,age);// 调用父类有参构造方法Person(String name , int age)初始化name和age
        this.score = score;    
        System.out.println("子类有参");
     }
      // getter/setter省略
}
​
public class Demo07 {
    public static void main(String[] args) {
        // 调用子类有参数构造方法
        Student s2 = new Student("张三"2099);
        System.out.println(s2.getScore()); // 99
        System.out.println(s2.getName()); // 输出 张三
        System.out.println(s2.getAge()); // 输出 20
    }
}

注:

  • 子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()
  • 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   ;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public char getSex() {
        return sex;
    }
    public void setSex(char sex) {
        this.sex = sex;
    }
}

13.7 多态

13.7.1 多态的定义和前提

多态: 是指同一行为,具有多个不同表现形式。

注:

  • 当一个方法的形参是一个类,我们可以传递这个类所有的子类对象。
  • 当一个方法的形参是一个接口,我们可以传递这个接口所有的实现类对象。
  • 而且多态还可以根据传递的不同对象来调用不同类中的方法。

前提:

  1. 有继承或者实现关系

  2. 方法的重写【意义体现:不重写,无意义】

  3. 父类引用指向子类对象【格式体现】

    父类类型:指子类对象继承的父类类型,或者实现的父接口类型。


// 父类:Person
public class Person {
    private String name;
    private int age;
    // 空参构造
    // 带全部参数的构造
    // get和set方法
    public void show(){
        System.out.println(name + ", " + age);
    }
}
// 子类1:Administrator
public class Administrator extends Person {
    @Override
    public void show() {
        System.out.println("管理员的信息为:" + getName() + ", " + getAge());
    }
}
// 子类2:Student
public class Student extends Person{
​
    @Override
    public void show() {
        System.out.println("学生的信息为:" + getName() + ", " + getAge());
    }
}
// 测试类:Test
public class Test {
    public static void main(String[] args) {
        Student s = new Student();
        s.setName("张三");
        s.setAge(18);
        
        Administrator admin = new Administrator();
        admin.setName("管理员");
        admin.setAge(35);
        
        register(s);
        register(admin);
    }
    // 这个方法既能接收老师,又能接收管理员
    // 只能把参数写成这两个类型的父类,即父类引用指向子类对象
    public static void register(Person p){
        p.show();
    }
}

13.4.2 多态运行特点

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

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


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

13.4.3 多态的弊端和解决方法

如果子类有些独有的功能,此时多态的写法就无法访问子类独有功能了

解决方法:引用类型转换

多态的转型分为向上转型(自动转换)与向下转型(强制转换)两种

向上转型(自动转换)


父类类型  变量名 = new 子类类型();
如:Animal a = new Cat();

向下转型(强制转换)


子类类型 变量名 = (子类类型) 父类变量名;
如:Aniaml a = new Cat();
   Cat c =(Cat) a;  

转型异常


public class Test {
    public static void main(String[] args) {
        // 向上转型  
        Animal a = new Cat();  
        a.eat();               // 调用的是 Cat 的 eat
        // 向下转型  
        Dog d = (Dog)a;       
        d.watchHouse();        // 调用的是 Dog 的 watchHouse 【运行报错】
    }  
}

这段代码可以通过编译,但是运行时,却报出了 ClassCastException ,类型转换异常!这是因为,明明创建了Cat类型对象,运行时,当然不能转换成Dog对象的。

instanceof关键字做类型校验


public class Test {
    public static void main(String[] args) {
        // 向上转型  
        Animal a = new Cat();  
        a.eat();               // 调用的是 Cat 的 eat
​
        // 向下转型  
        if (a instanceof Cat){
            Cat c = (Cat)a;       
            c.catchMouse();        // 调用的是 Cat 的 catchMouse
        } else if (a instanceof Dog){
            Dog d = (Dog)a;       
            d.watchHouse();       // 调用的是 Dog 的 watchHouse
        }
    }  
}
//JDK14新特性:
/*
if(a instanceof Dog d){
    d.lookHome();
}else if(a instanceof Cat c){
    c.catchMouse();
}else{
    System.out.println("没有这个类型,无法转换");
}
*/

13.8 接口

13.8.1 解决单继承问题


package com.example.demo;
​
/**
 * 接口-解决单继承的问题
 *  1. 放入接口的方法,必须加 default 关键字(默认方法)
 *  2. default 方法只能是 public(可以省略)
 */
public class TestInterface {
    public static void main(String[] args) {
        Duck d = new Duck();
        d.swim();  // 游泳
        d.fly();  // 飞行
    }
}
interface Swimmable {
    default void swim() {
        System.out.println("游泳");
    }
}
interface Flyable {
    default void fly() {
        System.out.println("飞行");
    }
}
class Duck implements Swimmable,Flyable {
​
}

注:每个 java 类文件中,只能有一个公共类,且该类类名必须和文件名相同。

13.8.2 接口在多态中的应用

子类和实现类中的方法修饰符必须 >= 父类和接口中的访问修饰符

public > protected > 默认 > private


package com.example.demo;
​
/**
 * 接口-多态
 * 1. 用父类型代表子类对象,后者用接口类型来代表实现类对象
 * 2. 方法重写
 */
public class TestInterface {
    public static void main(String[] args) {
        A[] array = new A[]{
                new B(),
                new C()
        };
        for (A a : array) {
            a.a();
        }
    }
}
​
interface A {
    // 默认方法
//    default void a() {
//        System.out.println("a");
//    }
    // 抽象方法  只有方法声明,没有方法体
    // 只能是 public(public,abstract都可以省略)
    // 使用抽象方法的时候,必须重写接口的抽象方法
    public abstract void a();
}
​
class B implements A {
    @Override
    public void a() {
        System.out.println("b");
    }
}
​
class C implements A {
    @Override
    public void a() {
        System.out.println("c");
    }
}

13.8.3 接口在封装中的应用


package com.example.demo;
​
public class TestInterface {
    public static void main(String[] args) {
        // 用接口类型代表了实现类对象
        M m = new N();
        // 只能访问接口中的方法,不能访问 name 或者 n()
        m.m();
    }
}
​
interface M {
    void m();
}
class N implements M {
    public String name;
    @Override
    public void m() {
        System.out.println("m");
    }
    public void n() {
        System.out.println("n");
    }
}

13.8 包

包是用来分门别类的管理技术,不同的技术类放在不同的包下,方便管理和维护。

导包

什么时候需要导包?

​ 情况一:在使用Java中提供的非核心包中的类时

​ 情况二:使用自己写的其他包中的类时

什么时候不需要导包?

​ 情况一:在使用Java核心包(java.lang)中的类时

​ 情况二:在使用自己写的同一个包中的类时

使用不同包下的相同类


//使用全类名(例如都有Student类)
com.company.homework.demo1.Student s1 = new com.company.homework.demo1.Student();
com.company.homework.demo2.Student s2 = new com.company.homework.demo2.Student();

13.9 权限修饰符

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

如果没有特殊的考虑,建议这样使用权限:

  • 成员变量使用private ,隐藏细节。
  • 构造方法使用public ,方便创建对象。
  • 成员方法使用public ,方便调用方法。

13.10 final 关键字

final用于修饰类、方法和变量。

  • 类:被修饰的类,不能被继承。
  • 方法:被修饰的方法,不能被重写。
  • 变量:被修饰的变量,有且仅能被赋值一次。

//修饰类
final class 类名 {
}
//修饰方法
修饰符 final 返回值类型 方法名(参数列表){
    //方法体
}
//修饰变量
final int a; // 只能赋值一次

被final修饰的常量名称,一般都有书写规范,所有字母都大写