(java)面向对象三大特征(封装,继承,多态)

 1.封装(Encapsulation)

(1)封装的简介

从广义的角度来说:将一块经常要使用的代码片段,定义到方法中,是封装。将多个方法和多个状态数据定义到类体中,也是一种封装。定义方法是封装,定义类也是封装

从狭义的角度来说,java的封装,就是把类的属性私有化(private修饰),再通过公有方法(public)(getter/setter方法)进行访问和修改。

(2)属性的封装

 1.为什么封装?

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

比如在一个类中有age(年龄)这个属性,让外界直接操作可能会被赋值成-10000,即不合逻辑。

class Person {
   String name;
   int age;
}
class Program {
   public static void main(String[] args) {
      // 实例化一个Person对象
      Person xiaoming = new Person();
      xiaoming.name = "xiaoming";
      xiaoming.age = -10000;
   }
}

2.如何封装?

1.为了不让外界直接访问某些属性,用关键字private修饰这些属性。

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

以后APerson类为例进行演示:

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 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;   
    }
}

可以通过公有的getter/setter方法来操作私有化的属性

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());
    }
}

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

答:可以通过指定的方法访问属性,在这些方法中,可以添加一些数据处理操作,让赋的值是我们需要的。

代码演示如下:

public void setAge(int age) {
        if (age > 0 && age <= 120) {
            this.age = age;
        }else{
            //如果赋值有问题,可以使用下面的方式让程序中断,并进行提示
            throw  new RuntimeException("--您赋值的年龄不合法--");
        }
    }

 public void setGender(char gender) {
        if(gender == '男' || gender == '女'){
            this.gender = gender;
        }else{
            //如果赋值有问题,可以使用下面的方式让程序中断,并进行提示
            throw  new RuntimeException("您赋值的性别不合法,需要汉字'男'或'女'--");
        }
    }

2.单例设计模式

 (1)简介

 单例模式 :是一种常用的软件设计模式,属于创建型模式之一。它的目的是确保一个类只有一个实例,并提供一个全局访问点。

使用场景:

  • 频繁创建和销毁的对象:如果对象创建和销毁的成本较高,且在程序运行期间需要频繁访问,使用单例模式可以提高效率。

  • 控制资源访问:例如,数据库连接、日志对象、配置管理器等,这些资源通常希望在整个应用中只有一份实例。

  • 工具类:对于一些工具类,如缓存、对话框、注册表设置等,使用单例模式可以简化代码,避免重复实例化。

 (2)单例的饿汉模式

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

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

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

 以BSingleton类进行演示:

public class BSingleton {
    //提供一个私有的,静态的,该类的属性instance
    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,即创建的两个引用变量的地址是一样的,指向的是同一个对象。

 (3)单例懒汉模式

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

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

3. 提供一个公有的,静态的方法,来创建当前类的对象。如果对象不存在,说明对象还未创建。如果对象存在,则返回已存在的对象 

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

 以CSingleton类进行演示:

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,即c1和c2的地址是一样的,指向的是同一个对象。

 总结:单例饿汉模式和单例懒汉模式基本都一样的,都可以获取到一个类的唯一的对象。

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

2 、懒汉式单例在多线程的环境下有问题。需要考虑线程安全(可以在公有的静态方法上加锁synchroniaed)

 3.继承

 (1)简介

 是面向对象最显著的一个特征。继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。 这种技术使得复用以前的代码非常容易,能够大大缩短开发周期,降低开发费用。

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

使用关键字extends 来表示子类继承了父类,语法如下:

修饰词  class  子类名  extends 父类名{    

        //子类的类体

 }

比如:

class A{}  //父类
class B extends A{}  //B是A的子类
class C extends B{}  //C是B的子类

(2)继承特点

1.Java只支持单继承,即一个类只能有一个父类;但是一个类可以有多个子类。

 public class classA{}

public  class  classB exends classA{}

 不同类继承同一个类                    

 public class classA{}

public class classB  extends classA{}

public class classC  extends classA{}

2 、java支持多重继承,即一个类在继承自一个父类的同时,还可以被其他类继承。 可见继承具有传递性

 public class classA{}

public class classB  extends classA{}

public class classC  extends classB{}

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

public class APerson {
    private static String name;
    private int age;
    private char gender;

    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;
    }

    public static void sum(int a,int b){
        System.out.println(a+b);
    }

    public void sub(int a,int b){
        System.out.println(a-b);
    }
}
class Student1 extends APerson{
    private String studentId;
    private String classNo;

    public static void main(String[] args) {
        Student1 s = new Student1();
        //父类的成员是私有的,子类都没有访问权限
        //s.name="xiaoming";
        //s.age = 19;

        //可以通过父类提供的公有的方法访问和设置属性
        s.setName("小张");
        s.setAge(19);
        s.setGender('男');
        //获取父类的属性
        System.out.println(s.getName());
        System.out.println(s.getAge());
        System.out.println(s.getGender());
        //调用父类的静态方法
        s.sum(4,5);
        //调用用父类的普通方法
        s.sub(8,3);
    }

}

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

以上述代码为例:子类Student1的无参构造

 public  Student1(){
        super();
    }

系统给父类提供了一个无参构造器,子类继承时要调用这个无参构造器,但是如果父类的构造器没有重写,那么子类构造器里的super()是隐藏的,可以省略不写。即一定至少有一个子类构造器调用了父类的构造器。

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

5 、子类在拥有父类的成员的基础上,还可以添加新成员。如3.的代码,子类Student1里有tudentId和classNo两个自己的属性。

 (3)继承中的构造器

一个对象在实例化的时候,需要在堆上开辟空间,堆中的空间分为两部分,分别是从父类继承到的属性,子类特有的属性。而实例化父类部分的时候,需要调用父类中的构造方法(默认调用的是父类中的无参构造器 )。如果父类中没有无参构造器,那么子类需要显式调用父类中的某一个有参构造器。

在子类的构造方法中,使用==super(有参传参)==调用父类中存在的构造方法,而且==super(有参传参)==必须放在首行首句的位置上。因此可知,super(有参传参)和this(有参传参)不能在一个构造器中共存。

子类中构造器的调用的代码演示:

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

    //父类中提供了其他构造器,运行时系统不再提供无参构造了,则子类调用时需要显示的调用父类的构造器
    public APerson(String name) {
        this.name = name;
    }

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


}
class Student1 extends APerson{
    private String studentId;
    private String classNo;

    public  Student1(){
        //如果子类构造器中没有参数,显示调用父类的构造器时,使用super(给具体的值)调用父类的构造器
        //super("小张");//调用父类中一个参数的构造器
        //super("小明",19,'男');//调用父类中三个参数的构造器
        super("小红",15);//调用父类中两个参数的构造器
        //如果子类的构造器没有参数,显示调用父类构造器时直接赋值。
    }
    public Student1(String name, String studentId){
        //如果子类构造器中有父类的属性形参,则使用super(有参传参)显示调用父类的构造器
        //super(name);
        super(name,16);
        this.studentId = studentId;
    }
    public Student1(String name,String studentId,String classNo){
        super(name,18,'男');
        this.studentId = studentId;
        this.classNo = classNo;
    }
    public Student1(String name ,int age ,char gender){
        super(name,age,gender);
    }
    public Student1(String name ,int age ,char gender,String studentId,String classNo ){
        this(name,age,gender);//调用本类的构造器
        this.studentId = studentId;
        this.classNo = classNo;
    }
}

注意点:1.如果子类构造器中没有参数,显示调用父类的构造器时,使用super(给具体的值)调用父类的构造器

               2.如果子类构造器中有父类的属性形参,则使用super(有参传参)显示调用父类的构造器

(4)继承中的方法重写 

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

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

重写的特点:

-- 子类只能重写父类中存在的方法。
-- 重写时,子类中的方法名和参数要与父类保持一致。(区别于重载overload)

                如:子类将父类的方法完全一模一样的写出来,就是重写的一种。
-- 返回值类型:必须和父类方法的返回值类型相同,或者是其子类型。


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

                private  < 默认的 < protected < public 

class Animal{

    //公有的,返回值类型Animal
    public Animal getMyClass(){
        return null;
    }
}

public class TDog extends Animal {

    // 子类在重写父类的方法时,返回值类型与父类中的方法可以相同,也可以是其子类型  : TDog就是            Animal的子类型
    @Override
    public TDog getMyClass(){
        return null;
    }

}

@Override  注解只能放在子类重写父类的方法的上面,可以用于检测是不是重写。不是重写,报错。

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

简述 Override 和 Overload 的区别

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

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

方法的重载:

   1.方法名必须相同

   2.参数列表必须不同

   3.其他修饰符可以相同,也可以不同

   4.可以抛出不同异常

方法的重写:

   1.方法名、参数列表、返回值类型都必须相同

   2.访问修饰符必须大于或等于被重写的方法 

     Java中四种权限修饰符的大小排列顺序   :  private<default<producted<public

   3.重写的方法中,不能抛出新的异常或比重写的方法更多、更大的异常,但一定会抛出异常。也就是说,只能抛出相同的异常或是被重写方法异常的子异常,还可以抛出非编译异常(RuntimeException)

   4.重写方法只会存在于具有继承关系的子类中,而当父类中的方法用private修饰时,即使子类中有重名方法,也不叫方法的重写

   5.非静态方法不能被重写成静态方法

(5)Object类型

     1.toString()方法

 该方法的作用是将对象的信息变成字符串。源码的返回值对我们意义不大,所以需要重写我们想要的输出逻辑。

 该方法。在使用输出语句打印对象的变量时,会自动调用。

  2.重写equals方法

重写要遵循一些原则:

        1、如果  obj = null,一定要返回false。
        2、如果  obj = this,一定要返回true。
        3、如果两个对象的类型不同,一定要返回false。

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

 /**
     * 重写equals方法
     * 1. 判断一下,传入的是不是null
     * 2. 判断一下,传入的是不是自己
     * 3. 判断一下,是不是同类型,如果是,就转成同类型进行比较
     * 4. 其他任何情况,都返回false;
     */
    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;
    }

(6)final修饰

final是java语法中的修饰词,可以用来修饰类,属性,方法,局部变量。 final是“最后,最终”的含义

--1.修饰类时,不能再有子类,即不能被继承, 比如String, 八大基本数据类型的包装类

public final class Cat {

}

//public class CatB extends  Cat{  //会报错,Cat是final修饰的


//}


--2. 修饰属性:

被final修饰的变量:三种赋值方式

  1. 在定义时直接赋值。
  2. 声明时不赋值,在constructor中赋值(最常用的方式)
  3. 声明时不赋值,在构造代码块中赋值
  4. final修饰的变量必须赋值,只能赋值一次。

三者中只要有一个出现,如果其他地方在为该变量赋值,则会报编译错误

public class Cat {
    private final String name;
  //  private final String name = "小张";//直接初始化


    public Cat(String name){  //在构造器里初始化
        this.name = name;
    }

    只能存在一种赋值方式,两种同时存在时会出现编译错误

    

    /**
     *  下面的方法给name赋值,已经不是初始化了。因为想要执行该方法,那么
     *  构造器一定先执行了
     */
//    public void setName(String name){
//        this.name = name; //name是final修饰的,只能被赋值一次,构造器先于该方法执行
//    }




     //还可以在构造代码块中赋值
      //  {
      //    name = "小红" ;
      //  }

}


--3. 修饰方法:
         final修饰的方法,不能被重写, 但是能被重载。

public class Cat {
    private final String name;

    public final int sum(int a, int b){

        //测试final修饰局部变量
        final int x;
        x = 100;
        //x = 101;  该变量不能再次赋值。

        return a + b;
    }

}

//研究一下父类里的方法被final修饰,是否还能重写
class BCat  extends Cat{

    //不能重写父类里的final方法,如下代码会报错  是编译错误
    //public int sum(int a,int b){
    //    return a + b;
    //}
}


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

再次赋值会报错,是编译错误。  

public final int sum(int a, int b){

        //测试final修饰局部变量
        final int x;
        x = 100;
       // x = 101; // 该变量不能再次赋值。再次赋值会报错

        return a + b;
    }

(7)static修饰

1. 修饰成员变量:

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

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

2. 修饰方法:

           - 静态方法,属于类的,公共资源,使用类名.调用。在本类main方法里直接方法名(有参传参)

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

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

public class MyUtil {
    private String name;
    public static  int contain = 18;
    public static void sum(int a,int b){
        //不能直接访问非静态成员
        //this.name = "aaaa";
        //addContain();
        contain = 19;
    }

}

                 - static方法不能被重写 (即加上@override进行检测,报错,说明不是重写方法,子类可以提供和父类一样的静态方法,各是各的)

3. 修饰代码块:

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

4. 修饰类:

                 - 可以修饰内部类。

4.多态

(1)简介

多态:指的是一个对象具有多种形态的特点。就是一个对象可以从一种类型转换为另外一种类型。

 下面是三个类:

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

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

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

    public Dog(String color, String name, int age){
        super(color, name, age);
    }

    public void noise(){
        System.out.println("---汪汪汪--");
    }
    public void LookHouse(){
        System.out.println("---会看家----");
    }
}
class Cat extends Aminal {
    public Cat(String color, String name, int age){
        super(color, name, age);
    }

    public void noise(){
        System.out.println("---喵喵喵--");
    }
    public void getMouse(){
        System.out.println("---我会抓老鼠----");
    }
}

(2)向上造型(向上转型)

  • 父类型的变量引用子类型的对象。

        父类型   变量 =  new  子类型();

public class AminalTest {
    public static void main(String[] args) {
        //使用父类型 Aminal声明一个变量,引用一个子类Cat对象
        Aminal a = new Cat("白色","小花",3);
        
}
  • 向上转型肯定会成功,是一个隐式转换。

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

public class AminalTest {
    public static void main(String[] args) {
        //使用Animal声明一个变量,引用一个Cat对象
        Aminal a = new Cat("白色","小花",3);
        //调用方法:
        a.noise(); // 编译期间不会出现问题。因为父类里有该方法。运行期间,执行的是对象的类型里的方法逻辑
        //a.getMouse(); 调用不到该方法,因为a这个变量的类型里没有该方法, (编译期间,看变量类型)
   

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

  • 应用场景:在定义方法时,形式参数是父类型的变量。这样更加灵活,可以传任意子类型的对象,这就是向上造型的优势所在。

public class AminalTest {
    public static void main(String[] args) {
        //使用Animal声明一个变量,引用一个Cat对象
        Aminal a = new Cat("白色","小花",3);
        //调用方法:
        a.noise(); // 编译期间不会出现问题。因为父类里有该方法。运行期间,执行的是对象的类型里的方法逻辑
        //a.getMouse(); 调用不到该方法,因为a这个变量的类型里没有该方法, (编译期间,看变量类型)

        test1(a);
        Dog dog = new Dog("黑色","大黑",8);
        test1(dog);
    }

    //测试:执行动物的叫声   这就是向上造型的优势所在,
    // 父类型的变量作为参数,更加灵活,可以传入不同的子类型对象
    public static void test1(Aminal animal){
        animal.noise();
    }

}

(3)向下转型(向下造型)

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

public class AminalTest2 {
    public static void main(String[] args) {
        //创建一个父类型的变量引用子类型的对象
        Aminal am = new Dog("黄色","大黄",5);
        am.noise();
        //调用一下对象的独有功能
        //am.LookHouse(); //编译期间看变量类型。没有该方法,所以报错
        //只能向下转型才可以,是强制转换,是一个显示的转换。
        Dog d = (Dog)am;
        d.LookHouse();

    }
}
  • 可能会失败,失败的话,会报类造型异常ClassCastException

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

public class AminalTest2 {
    public static void main(String[] args) {
        //创建一个父类型的变量引用子类型的对象
        Aminal 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();
        }
    }
}
  • 为什么向下转型:前提,父类型的变量引用子类型的对象。但是父类型的变量调不了子类里独有的功能,已经不能满足需求了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值