面向对象的三大特征

一、封装

1.1 封装简介

1)从广义的角度上来说

封装就是将一块要使用的代码片段,定义到方法中。

将多个方法和多个状态数据定义到类体中,也是一种封装。

2)从狭义的角度上来说

封装就是把类里面的属性私有化(用private修饰),再通过公有方法(public)进行访问和修改。

1.2 属性封装

1)为什么要封装?

一个类中的某一些属性,不希望直接暴露给外界,让外界直接操作。因为如果让外界直接操作的话,对 这个属性进行的值的设置,可能不是我们想要的(可能不符合逻辑)。此时就需要将这个属性封装起来,不让外界直接访问。

2)如何封装?

1.添加关键字private访问权限修饰词修饰属性,外界就不能直接访问属性了。

2.然后提供公有的getter/setter方法,来操作这个被私有化的属性。

3)为什么私有化起来的属性,不希望外界访问,还需要再提供setter和getter?

可以通过指定的方式访问属性,在这些方法中,可以添加一些数据处理操作。

public class APerson {
    //成员变量私有化
    private String name;
    private int age;
    private char gender;
    //无参构造器
    public APerson(){}
    //全参构造器
    public APerson(String name, int age, char gender) {
      this.name = name;
      this.age = age;
      this.gender = gender;
  }
  //为每个成员变量,提供getter/setter方法
    public void setName(String name){
        this.name = name;
    }
    public String getName(){
        return name;
    }
    public void  setAge(int age){
        if(age > 0&& age <= 120) {
            this.age = age;
        }else{
            System.out.println("--您赋值的年龄不合法--"+age);
        }
    }
    public int getAge(){
        return age;
    }
    public void setGender(char gender){
        if(gender == '男' || gender == '女'){
        this.gender = gender;
        }else{
            System.out.println("--您赋值的性别不合法,需要汉字'男'或'女'--");
            //如果赋值有问题,可以使用下面的方式让程序中断,并提示异常
            throw new RuntimeException("--您赋值的性别不合法,需要汉字'男'或'女'--");
        }
    }
    public char getGender(){
        return gender;
    }
}

测试:

public class APersonTest {
    public static void main(String[] args) {
        //创建对象
        APerson p1 = new APerson();
        //p1.name = "小明";  name私有化了,就不可以直接访问
        //调用公用方法去访问
        p1.setName("小明");
        p1.setAge(18);
        p1.setGender('女');
        //System.out.println(p1.name);  也不能直接访问,需要调用公有的方法
        System.out.println(p1.getName());
        System.out.println(p1.getAge());
        System.out.println(p1.getGender());
        p1.setGender('m');
        System.out.println(p1.getGender());
    }
}

 1.3 单例设计模式

1.3.1 单例的饿汉模式

1. 提供一个该类的,私有的(private),静态的属性, 并在静态代码块里赋值

2. 提供一个私有的构造器, 避免外界直接调用构造器。

3. 提供一个公有的(public),静态的方法,来获取当前类的对象。

public class BSingleton {
    private static BSingleton instance;// instance 实例的含义
    static{
        instance = new BSingleton();
    }
    private BSingleton(){}
    public static BSingleton getInstance(){
        return instance;
    }
}

测试:

public class BSingletonTest {
    public static void main(String[] args) {
        //看是否能直接创建
        //BSingleton b = new BSingleton();   构造器私有化,不能直接调用
        //看是否能直接访问成员
        //BSingleton.instance;  //成员私有化了,不能直接访问

        //只能使用下面的方式获取该类的具体实例
        BSingleton  bs = BSingleton.getInstance();
        //再获取一次
        BSingleton  bs2 = BSingleton.getInstance();
        /**
         *   ==:  双等号,是用来判断引用类型的变量里的地址是否相同
         */
        System.out.println(bs==bs2);
    }
}
true

1.3.2 单例模式的懒汉模式

1.提供一个该类的,私有的(private),静态的属性。

2.提供一个私有的(private)构造器,避免外界直接调用构造器。

3.提供一个公有的(public),静态的方法,来创建当前类的对象,如果对象不存在,说明还没创建对象,那么第一次调用时会创建一个对象,后续调用则直接返回已存在的对象。

public class CSingleton {
    //私有化的静态变量
    private static CSingleton instance;
    //私有化构造器
    private CSingleton() {}
    //公有的静态方法,用于获取对象
    public static CSingleton getInstance() {
        //如果 静态变量里面没有地址,说明还没有创建对象
        if (instance == null) {
            //创建对象,将地址存入静态变量
            instance = new CSingleton();
        }
        //返回之前的对象,如果之前有就用之前的,如果没有,就用新的
        return instance;
    }
}
public class CSingletonTest {
    public static void main(String[] args) {
        //获取两次该类型的对象
        CSingleton c1 = CSingleton.getInstance();
        CSingleton c2 = CSingleton.getInstance();
        //使用 双等号,判断一下,c1和c2指向的是不是同一个对象
        System.out.println(c1 == c2);
    }
}
true

 1.3.3 两者比较

基本上是一样的,都可以获取到一个类的唯一对象。

1. 在没有使用获取当前类对象之前,懒汉式单例比饿汉式单例在内存上有较少的资源占用。

2. 单例懒汉模式存在线程安全隐患问题,比如两次调用的时候都恰好执行到判断等于null。那么都会执行到创建实例那一行代码。那么就会在内存中创建出来两个对象。

二、继承

2.1 继承的简介

 继承是面向对象最显著的一个特征。继承是从已经有的类中派生出新的类,新的类能继承已有类的所有数据属性和行为,并且还可以扩展新的属性和行为。

已有的类称为父类,又叫基类,超类。派生出来的新的类,叫子类,也叫派生类。

使用关键字extends来表示子类继承了父类。

语法:修饰词  class 子类名  extends 父类名{

                            //子类的类体

            }

2.2 继承的特点 

1. Java只直接单继承,即一个类只能有一个父亲,一个子类只能继承一个父类;但一个父亲可以有多个孩子,即一个父类可以被多个子类继承。

2. Java支持多重继承,即一个子类在继承一个父类的同时,还可以被它的子类所继承。可见继承具有传递性。

3. 子类继承了父类的所有成员变量和方法,包括私有的(只不过没有访问权限),当然也包括静态成员。

4. 子类不会继承父类的构造方法,只能调用父类里的构造方法,并且一定至少有一个子类构造器调用了父类的构造器。

5.子类在拥有父类的成员基础上,还可以添加新成员。

2.3 继承中的构造器

定义一个Person父类构造器如下:

public class APerson {
    private String name;
    private int age;
    private char gender;
    public APerson(String name, int age, char gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }
    public APerson(){}

    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 getGender() {
        return gender;
    }

    public void setGender(char gender) {
        this.gender = gender;
    }
}

特点:1. 子类不能继承父类的构造器。

           2. 子类的构造器中至少存在一个构造器调用了父类的构造器。默认调用的是父类中的无参构造器。如果父类中没有无参构造器,那么子类需要显示调用父类中的某一个有参构造器。

           3. 子类的构造器中可以使用super(有参传参)进行显示,调用父类中的某一个构造器。

           4. super(有参传参)与this(有参传参)一致,必须放在首行首局,因此不能共存。

           5. 为什么要至少有一个调用了父类的构造器,因为子类继承过来的属性需要初始化。

public class Student extends APerson{
    private String studentId;
    private String classNo;
    //添加自己的构造器
    public Student(String studentId, String classNo) {
        super("小黑",10,'女');//首行首句
        this.studentId = studentId;
        this.classNo = classNo;
    }

    public String getClassNo() {
        return classNo;
    }

    public void setClassNo(String classNo) {
        this.classNo = classNo;
    }

    public String getStudentId() {
        return studentId;
    }

    public void setStudentId(String studentId) {
        this.studentId = studentId;
    }

    //重载一个构造器
    public Student(String name,int age,char gender,String studentId,String classNo){
        //this.name = name; 父类里的name属性私有化,虽然子类继承了,但是不能直接访问
        //setName方法是继承过来的,并且是公有的,因此是可以直接使用的
        this(studentId,classNo);
        setName(name);
        setAge(age);
        setGender(gender);
    }
    //添加toString方法,用来显示对象的属性值。
    public String toString(){
        return "name:"+getName()
                +",age:"+getAge()
                +",gender:"+getGender()
                +",studentId:"+getStudentId()
                +",classNo:"+getClassNo();
    }
}

 2.4 继承中的方法重写

重写,叫做Override。在子类中,对从父类中继承到的方法进行重新的实现。这个过程中,子类重写该方法,会覆盖掉继承自父类中已经实现的方法。因此,重写又叫做覆写。

为什么要重写?

因为父类的方法逻辑不能满足子类的需求了,因此子类需要修改逻辑(重写)。

重写的特点:1. 子类只能重写父类中存在的方法。

                      2. 重写时,子类中的方法名和参数要与父类保持一致。(与重载overload不同)。

                      3. 返回值类型:必须和父类方法的返回值类型相同,或者是其子类型。

                      4. 访问权限:子类重写方法的访问权限必须大于等于父类方法的访问权限。

注解@Override:这个注解,用在重写的方法之前,表示验证这个方法是否是一个重写的方法。如果是,程序没有问题。如果 不是,程序会报错。因为我们再进行方法的重写时,是没有什么提示的,因此,为了保证写的是重写,在重写之前,最好加上这个注解。

加了@Override就是重写,没有加@Override就不是重写。 这种说法是错误的! @Override只是进行的一个语法校验,与是不是重写无关。

Override 和 Overload 的区别:

Override: 是重写,是子类对父类的方法进行重新实现。

Overload: 是重载,是对同一个类中的同名、不同参数方法的描述。

public class TDog extends Animal {
    public static void main(String[] args) {
        //创建一个子类型对象
        TDog dog = new TDog();
        //调用继承过来的并且有访问权限的方法
        dog.sport();
    }
    //子类dog独有功能
    public void noise() {
        System.out.println("---汪汪汪---");
        }
        //子类将父类的方法完全一模一样的写出来就是重写的一种
        public void sport(){
            System.out.println("--运动中--");
        }
        //子类在重写父类的方法时,返回值类型与父类中的方法可以相同,也可以是其子类型:TDog就是Animal的子类型
        @Override
        public TDog getMyClass(){
            return null;
    }
        //子类在重写父类的方法时,访问权限应该大于等于父类方法的访问权限
        @Override
        public String showInfo(){
            return null;
    }
    //自己独有的方法,只不过是与继承过来的那个是重载关系
    public String showInfo(int a){
        return null;
    }
    }
//动物类型
class Animal{
    private String color;
    //公有的,返回值类型void
    public void sport(){
        System.out.println("--运动中--");
    }
    //公有的,返回值类型Animal
    public Animal getMyClass(){
        return null;
    }
    //默认的,返回值类型String
    String showInfo(){
        return null;
    }

}

2.5 Object类型

Object类,是Java中的根类。所有的类都直接或者间接的继承自Object类。 因为,所有的类都直接或者间接的继承自Object类,因此在Object类中定义的属性、方法,在所有的类中都有包含。 比如常用的方法 hashCode(),equals(),toString(),getClass(),wait(),notify(),notifyAll()等。

1)toString()方法

源码如下:

public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
1. 该方法的作用是将对象的属性信息拼接起来,返回字符串。这样就可以查看每个对象的样子了。
2. Object里提供的该方法的逻辑:返回的是类全名+@+hashCode值的16进制,对于我们来说
   没有太大意义,看不到对象的成员变量的信息。因此自定义类型时,通常都要重写。
3. 该方法在使用输出语句打印对象的变量时,会自动调用。

2)equals()方法

源码如下:

public boolean equals(Object obj){
   return this==obj;  //  == 比较的是地址值。  
}

上述的源码的意义所在:比较this和传进来的obj 是不是同一个对象,如果是,则返回true,不是的话,返回false。

而我们大多时候的需求,并不是比较是不是同一个对象,而是比较两个对象的属性值是否相同。 因此:自定义类型时,应该重写eqauls方法。

重写要遵循一些原则:

1. 判断一下传进来的是不是null,不是,返回true。

2. 判断一下传入的是不是自己,是,返回true。

3. 判断一下是不是同类型,如果不是,就转成同类型进行比较。

4. 其他任何情况,都返回false。

5. 如果  a.equals(b) 成立,则  b.equals(a) 也必须成立。

6. 如果  a.equals(b), b.equals(c) 成立,则  a.equals(c) 也必须成立。

3)hashCode()方法

该方法返回的是一个int类型的值。 表示对象在内存堆中的一个算法值。   在自定义类型时,一般都需要重写该方法。

@Override
public int hashCode() {
        return Objects.hash();
}

4)getClass()方法

方法原型 : public final native Class<?> getClass();
方法作用 : 获取一个用来描述指定类型的Class类对象。获取一个指定的对象的类型。 这个方法,不能被重写。

public class Teacher {
    private String name;
    private int age;
    private char gender;

    public Teacher() {
    }

    public Teacher(String name, char gender) {
        this.name = name;
        this.gender = gender;
    }

    public Teacher(String name, int age, char gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (obj == this) {
            return true;
        }
        if (obj instanceof Teacher) {
            Teacher t = (Teacher) obj;
            return this.name.equals(t.name) && this.age == t.age && this.gender == t.gender;
        }
        return false;
    }

@Override
public int hashCode() {
        return Objects.hash(name, age, gender);
}
    @Override
    public String toString() {
        return "Teacher{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", gender=" + gender +
                '}';
    }
    public static void main(String[] args) {
         Teacher teacher = new Teacher();
         System.out.println(teacher);
        Teacher t1 = new Teacher("michael",39,'男');
        Teacher t2 = new Teacher("michael",39,'男');
        System.out.println(t1==t2);
        boolean r = t1.equals(t2);
        System.out.println("r:"+r);
        //通过对象调用getClass(),来获取Teacher对应的描述类的对象
        Class<? extends Teacher > aClass = t1.getClass();
        System.out.println(aClass.getName());
        //获取类里的所有属性:  Field 字段的含义。
        Field[] fields = aClass.getDeclaredFields();
        for (Field field : fields) {
            System.out.println(field.getName());
        }
    }
}



Teacher{name='null', age=0, gender= }
false
r:true
com.oop.day02._02Extend.Teacher
name
age
gender

 2.6 final修饰词

final:最终,最后的含义。

final是java语法中的修饰词,可以用来修饰类,属性,方法,局部变量。

1. 修饰类时:表示该类不能再有子类,即不能被继承。

2. 修饰属性(成员变量)时:只能初始化一次,即要么直接初始化,要么在构造器中初始化,不能被再次赋值。

3. 修饰方法时:表示该方法不能被重写。

4. 修饰局部变量时,只能赋值一次,不能第二次赋值。

2.7 static修饰词

static修饰的内容,都是属于公有资源,不属于对象,而是属于类的,因此都是类名调用。

1. 修饰成员变量:静态成员变量,属于类的,公共资源,使用类名.调用。

     注意:可以使用引用变量.调用,但是不合理,因为不属于对象。
     因此,static修饰的属性,应该配合 final 一起修饰,来表示该属性是一个常量。

     常量: 命名时,字母都大写,多个单词使用下划线连接。应该使用 public static final修饰。

 2. 修饰方法: 静态方法,属于类的,公有资源,使用类名.调用。

     注意:可以使用引用变量.调用,但是不合理,因为不属于对象。 

     静态方法中不能直接访问非静态成员。

      static方法不能被重写,但是子类和父类可以提供和父类一样的静态方法,各是各的。

3. 修饰代码块:  静态代码块, 类加载时只执行一次。通常用于加载静态资源:图片,音频等。

4. 修饰类: 可以修饰内部类。

public class MyUtil {
    private String name;
    //定义一个水桶的容量,单位是升
    public static int  contain = 18;
    //如果想要达到每时每刻看到的公有资源都是一样的,那么就应该使用final修饰,即常量。
    public static double PI = 3.14;
    public static void sum(int a, int b) {
        //不能直接访问非静态成员
        //this.name = "aaaa";
        //addContain();
        contain = 19;
    }

    public void addContain() {
        this.contain++;
    }
    public void subContain() {
        this.contain--;
    }

    public static void main(String[] args) {
        MyUtil p1 = new MyUtil();
        //查看容量,使用了变量调用,但是不建议
        System.out.println(p1.contain);
        p1.addContain();
        MyUtil p2 = new MyUtil();
        //注意:静态变量要使用类名.调用
        System.out.println(MyUtil.contain);
        p2.subContain();
    }
}
class Sub extends MyUtil {
    //@Override 添加注解后报错,因为静态方法不能被重写
    public static void sum(int a, int b) {
        //不能直接访问非静态成员
        //this.name = "aaaa";方法不能被重写,但是子类和父类可以提供和父类一样的静态方法,各是各的。
        //addContain();
        contain = 19;
    }

}

三、多态 

3.1 简介

多态:从字面上理解,就是多种形态,多种状态的含义,在这里,指的是一个对象具有多种形态的特点。说的再简单点,就是一个对象可以从一种类型转换为另外一种类型。有向上转型和向下转型两种形式。

public class Animal {
    private String name;
    private String color;
    private int age;

    public Animal() {
    }

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

    //动物都会有发出声音的行为
    public void noise() {
        System.out.println("---动物都会发出叫声---");
    }
}
    class Dog extends Animal {
        public void noise() {
            System.out.println("---汪汪汪---");
        }

        public void lookHouse() {
            System.out.println("---会看家---");
        }
        public Dog(String name, String color, int age) {
            super(name, color, age);
        }


    }
    class Cat extends Animal{
        public void noise(){
            System.out.println("---喵喵喵---");
        }
        public void getMouse(){
            System.out.println("---我会抓老鼠---");
    }
    public Cat(String name, String color, int age) {
        }
}

3.2 向上造型(向上转型)

1. 父类型的变量引用子类型的变量。

2. 向上转型肯定会成功,是一个隐式转换。

3. 向上转型后的对象,将只能够访问父类中的成员(编译期间,看变量类型)。

4. 如果调用的是重写过的方法,那么调用的一定是重写方法(运行期间,看对象)。

5.应用场景:在定义方法时,形式参数是父类型的变量。这样更加灵活,可以传任意子类型对象。

public class AinmalTest {
    public static void main(String[] args) {
        //使用Animal声明一个变量,引用一个Cat对象
        Animal a = new Cat("小花","白色",3);
        //调用方法:
        a.noise();//编译期间不会出现问题。因为父类里有该方法。运行期间,执行的是对象的类型里的方法逻辑。
        //a.getMouse();调用不到该方法,因为a这个变量的类型里没有该方法,(编译期间,看变量类型)
        test1(a);
        Dog dog = new Dog("大黑","黑色",8);
        test1(dog);
    }
    //测试:执行动物叫声  这就是向上造型的优势所在,父类型的变量作为参数,更加灵活,可以传入不同的子类型对象。
    public static void test1(Animal animal){
        animal.noise();
    }
}
---喵喵喵---
---喵喵喵---
---汪汪汪---

3.3 向下转型

1. 父类型变量赋值给子类型的变量,需要强制转换,是一个显示转换。

2. 可能会失败,失败的话,会报类造型异常ClassCastException。

3. 为了避免ClassCastException,可以使用instanceof来判断:变量指向的对象是否属于某一个类型。

4. 为什么要向下转型:前提,父类型的变量引用子类型的变量,但是父类型的变量调用不了子类型中独有的功能,已经不能满足需求。

5. 应用场景:多数情况下,定义方法时,形参都是父类型的变量,原因是可以接受任意子类型对象。 但是有的时候,在这样的方法里的逻辑中,可能会用到该子类型对象的独有功能,因此会涉及到向下转型。

语法:子类型   变量名   =   (子类型)父类型变量

public class AnimalTest2 {
    public static void main(String[] args) {
        //创建一个父类型的变量引用子类型的变量
        Animal am = new Dog("大黄","黄色",5);
        am.noise();
        //am.lookHouse(); 编译期间看变量类型。没有该方法,所以报错。
        //只能向下转型才可以
        Dog d = (Dog)am;
        d.lookHouse();
        //上述代码没有问题,但是在正在编程时,有可能写成如下代码:强制转换的类型不正确
        //编译期间不报错。但是运行期间就会报异常。
       // Cat c = (Cat)am;
       // c.getMouse();
        //我们应该避免上述情况发生。只需要使用instanceof即可
        if(am instanceof Cat){//因为am指向的是一个Dog对象,不属于Cat类型,因此进不去分支。所以避免了报错。
            Cat c = (Cat)am;
            c.getMouse();
        }
    }
}

  • 32
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值