JAVA面向对象2(三大特征)

面向对象的三大特征:封装、继承、多态

封装:

封装,英文单词Encapsulation。

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

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

属性封装:

只需要使用访问权限修饰词private来修饰即可

public class Person {
   String name;
   private int age;        // 将属性私有化起来,不让外界直接访问

1:  为什么要封装成员变量(属性)
      因为外界直接访问成员变量,可能会进行修改,也就会可能发生修改的值不合理。比如调用者将人的年龄这个属性可以设置10000。


2: 但是外界可能还是需要修改或者访问成员变量的。 一旦私有化成员变量,外界不能做到访问和修改。 那么如何处理呢?
       为成员变量提供public修饰的get/set方法。 为了防止外界设置的值可能不合理,可以在这些方法中进行限定。

  get方法:  用于获取成员变量的值
            public 返回值类型  getName(){        //get+属性的大驼峰命名法(-Bean)
                return this.成员变量
            }
            -  方法名get后是成员变量的大驼峰命名规则


  set方法:  用来修改成员变量的值
           public  void  setName(String name){
               this.name = name;
           }
           - 方法名set后是成员变量的大驼峰命名规则

  // 给要访问的属性,设置对应的  setter/getter 方法
   public void setAge(int age) {

        if (age >= 0 && age <= 120) {
              this.age = age;
           }

   }

   public int getAge() {
      return this.age;
   }
}

 单例设计模式:

设计模式 是一套被反复使用、多数人知晓的、经过分类编目的代码设计的经验的总结。

使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。

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

即:整个应用程序中,该类型的对象只有一个。

使用场景:

  • 频繁创建和销毁的对象:如果对象创建和销毁的成本较高,且在程序运行期间需要频繁访问,使用单例模式可以提高效率。
  • 控制资源访问:例如,数据库连接、日志对象、配置管理器等,这些资源通常希望在整个应用中只有一份实例。
  • 工具类:对于一些工具类,如缓存、对话框、注册表设置等,使用单例模式可以简化代码,避免重复实例化。

单例模式有两种设计方式: 饿汉模式和懒汉模式

单例饿汉模式:

简单理解:着急创建那个唯一的对象(*.java文件被加载到方法区是就创建该对象)

过程:

1.提供一个private权限的静态当前类属性,并在静态代码段中进行实例化。

2.构造方法私有化,杜绝从外界通过new的方式实例化对象的可能性。(保证这个对象只有一个,外界不能创建新的对象)

3.提供一个public权限的静态方法,获取唯一一个当前类的对象


public class Boss {
   // 1、设计一个私有的、静态的、当前类的对象
   private static Boss instance;
   static {
      // 对instance静态对象进行实例化   静态代码区 加载到方法区是就创建对象 只运行一次
      instance = new Boss();
   }
   
   // 2、将单例类的构造方法私有化,杜绝从外界通过new的方式实例化对象的可能性。
   private Boss() {
      System.out.println("一个Boss对象出现了 "); 
   }

   // 3、需要提供一个public权限的静态方法,可以获取一个当前类的对象。
   public static Boss getCurrentBoss() {
      return instance;
   }
}

主函数里:
    //使用==来判断引用变量里的地址是否时同一个地址(同一个对象)
    Boss i1=Boss.getCurrentBoss();
    Boss i2=Boss.getCurrentBoss();
    System.out.println(i1==i2);    //地址相同,即对象只创建了一个

单例懒汉模式:

简单理解:不着急创建唯一对象 ,不需要在加载期间就创建对象,第一次访问时才创建对象。

过程:

1.提供一个private权限的静态当前类属性。

2.构造方法私有化,杜绝从外界通过new的方式实例化对象的可能性。(保证这个对象只有一个,外界不能创建新的对象)

3.提供一个public权限的静态方法,负责创建单例对象:

        第一次调用时会初始化单例,后续调用则直接返回以及存在的单例。


public class Master {

//1.提供一个private权限的静态当前类属性。

   private static Master instance;
 // 2.构造方法私有化 
   private Master() {
      System.out.println("一个Chairman对象被实例化了 "); 
   }
   
   public static Master getMaster() {
       //3.访问时才初始化
      // 使用到instance对象的时候,判断是不是null,instance里面存储地址
      if (instance == null) {
         // 实例化
         instance = new Master();
      }
      return instance;//第一次调用时会初始化单例,后续调用则直接返回以及存在的单例。
   }
}
主函数里:
    使用==来判断引用变量里的地址是否时同一个地址(同一个对象)
    Master m1=Master.getMaster();
    Master m2=Master.getMaster();
    System.out.println(m1==m2);    

两者比较:

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

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

2 、懒汉式单例在多线程的环境下有问题。需要考虑线程安全

 继承:

是面向对象最显著的一个特征。

继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。

为什么引用继承这个概念:
           可以重复使用父类的中代码,派生出来的子类少写了很多代码,同时还可以进行扩展其他的成员。 提高代码的开发效率。

 已有的类:父类,基类,超类

派生出来的新类:子类,派生类


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

语法:

修饰词 class  子类名  extends 父类名{
     //子类的类体
}


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

 继承的特点

  • Java只支持单继承,即:一个类只有一个父类,但是一个类可以有多个子类
  • Java支持多重继承,即:一个类在继承一个父类的同时,还可以被其他类继承,继承具有传递性
  • 父类中的所有成员(成员变量,成员方法,静态变量,静态方法,及其私有的)都被子类继承,但私有的不能被子类访问,没有权限
  • 子类不能继承父类的构造器,只能调用父类的构造器,而且子类中至少有一个构造器一定调用了父类中的某一个构造器(super ())
  • 子类在继承的时候,可以有自己独有的成员变量和成员方法

单继承: 

多重继承:

不同类继承同一个类:

多继承(Java不支持):

 这个创建一个父类:Human

public class Human {
    String name;
    private int age;//封装age
    char gender;
    public static int count = 0;//静态属性

    public Human() {}//无参构造
    public Human(String name, int age, char gender) {//全参构造
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    public String getName() {//get方法
        return name;
    }

    public void setName(String name) {//set方法
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
    public String toString(){
        return name+","+age+","+gender;
    }
    public static double getPI(){
        System.out.println(count);
        return Math.PI;
    }
    private double sum(){//私有方法
        return 1+2;
    }
}

 继承中的构造器

一个对象在实例化的时候,需要在堆上开辟空间:

堆中的空间分为两部分:

分别是:从父类继承到的属性    和    子类特有的属性

而实例化父类部分的时候,需要调用父类中的构造方法。

强调:子类在创建对象时,从父类中继承过来的属性,是由调用父类中的构造器开辟内存空间的。


默认调用的是父类中的无参构造器。如果父类中没有无参构造器,那么子类需要显式调用父类中的某一个有参构造器。


在子类的构造方法中,使用super(有参传参)调用父类中存在的构造方法,

而且super(有参传参)必须放在首行首句的位置上。

因此,super(有参传参)和this(有参传参)不能在一个构造器中共存(两个都首行首句会冲突)


这里创建个Employee类继承Human类:

public class Employee extends Human{
    private String name;//子类新定义的属性可以和父类同名
    private double salary;//子类新增salary属性
    public Employee(){//无参构造
        super("xiaohong",23,'女');//传参调用父类中的构造方法
    }
    public Employee(String id, double salary, String name,int age,char gender){//全参构造
        this.name = id;//id赋给子类name
        super.name = name;//name赋给父类中的name
        this.salary = salary;//salary赋给salary
        //this.name = "aaaa";  //父类的私有的成员不能直接访问
        //setName(name);   //  因为直接调用没有报错,说明继承过来了,前面隐藏了this.
        setAge(age);//封装属性需要set方法传值
        this.gender = gender;
    }

    public void work(){
        //验证的静态变量
        Human.count++;
        int count1 = Employee.count;
        System.out.println(count1);// 结果:1  证明子类调用的count值同父类静态count值一样

        double pi = Human.getPI();// 调用父类的方法访问PI和count值 count=1
        double pi1 = Employee.getPI();//调用子类继承的父类方法访问 count=1

        //this.sum(); // 私有的方法没有访问权限, 但是子类已经继承过来了
    }

    public String toString(){
        return name+","+salary+","+getName()+","+getAge()+","+gender;
    }
    public static void main(String[] args) {
        Employee e  = new Employee("1001",2000.0,"小红",23,'女');
        System.out.println(e.toString());
        e.work();
    }
}

 继承中的方法重写

重写,叫做override。

在子类中,对从父类继承到的方法进行重新的实现。

这个过程中,子类重写该方法,会覆盖掉继承自父类中的实现方法,因此,重写又叫做覆写。

为什么重写呢?

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


重写的特点:

-- 子类只能重写父类中存在的方法。
-- 重写时,子类中的方法名和参数要与父类保持一致。(区别于重载overload)
-- 返回值类型:必须和父类方法的返回值类型相同,或者是其子类型。
-- 访问权限:子类重写方法的访问权限必须大于等于父类方法的访问权限。


注解@Override

用在重写的方法之前,表示验证这个方法是否是一个重写的方法

如果是,程序没有问题。如果不是,程序会报错。

因为我们在进行方法重写的时候,没有什么提示的,因此,在进行重写之前,最好加上去这个注解。

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

简述 Override 和 Overload 的区别

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

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


public class Person extends Object {
    public void sum(int a, int b) {
        System.out.println(a + b);
    }
    protected String getName(){
        return "小红";
    }
    public Animal getAge(){
        return null;
    }
    private Dog getOther(){
        return null;
    }
}
//重写
class Student extends Person{
    @Override
    public void sum(int a, int b){
        System.out.println( a * b);
    }
    public void sum(int a,int b,int c){

    }
    @Override
    public String getName(){
        return "小红";
    }
    @Override
    public Dog getAge(){
        return null;
    }
    //@Override    不是重写。
    protected Dog getOther(){
        return null;
    }
}



class Animal{}
class Dog extends Animal{}
class Cat extends Animal{}

 Object类型

是所有引用类型的顶级父类(根类)

Object中提供了常用的共有的方法,比如toString,hashCode(),equals等方法

所有的引用类型,包括自定义的类型,都会默认直接或者间接的继承Object.

 toString()方法:

 用来返回对象的属性信息的。
      默认源码如下:   返回的是对象的类全名@hashCode的16进制
       public String toString() {
          return getClass().getName() + "@" + Integer.toHexString(hashCode());
       }
 
      源码的提供的逻辑并不是我们程序要想要的信息(一般不看变量的地址信息)
      因此:需要重写。
      而且该方法不需要手动调用,当对象的变量直接书写在输出语句中,会默认调用toString().


 @Override
    public String toString(){
        return "["+ tid +" "+name +" "+age + "]";
    }

hashCode()方法:

该方法返回的是一个int类型的值。表示对象在内存堆中的一个算法值。
    在自定义类型时,一般都需要重写,减少哈希冲突
    重写原因:
    1.返回的是int类型,值最多是42亿左右。 每个对象都有一个hash值,如果不是自定义,碰撞概率不可控
    2.尽量让对象的所有成员参与运算
    3.尽量自己控制hash值 减少碰撞概率


@Override
    public int hashCode() {
        int hash = 7;
        hash = hash+tid.hashCode();
        hash = hash+name.hashCode();
        hash = hash+age;
        return hash;
    }

equals(Object obj)方法:

用来比较两个对象的属性是否完全相同

源码的逻辑:是比较两个是不是同一个对象,该意义并不大。

 public boolean equals(Object obj) {
              return (this == obj);
         }

重写规则:
         1.  如果 obj == null , 直接返回false
         2.  如果 obj的类型和this的类型不是同一个,直接返回false
         3.  如果传入obj == this. 直接返回true
         4.  然后再比较两个对象的各个属性。

注意:

==   用来比较两边的变量里存储的是否为同一个地址
equals  一般用于比较两个对象的属性。


 @Override
    public boolean equals(Object obj) {
        if(obj == null){
            return false;
        }
       if(obj.getClass() !=this.getClass()){
           return false;
       }
        if(obj == this){
            return true;
        }
       Teacher t = (Teacher) obj;
       return this.tid.equals(t.getTid()) && this.name.equals(t.getName()) && this.age == t.getAge();
    }

 多态:

多态:从字面上理解,就是多种形态,多种状态的含义, 这里指的是一个对象具有多种形态的特点。

简单理解:就是一个对象可以从一种类型转换为另外一种类型。

有向上转型和向下转型两种形式

向上造型(向上转型):

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


  • 父类型的变量引用子类型的对象。 Animal a=new Cat();

  • 向上转型肯定会成功,是一个隐式转换。 (小--->大)

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

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

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

向下转型:

子类型   变量名 = (子类型)父类型变量


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

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

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

  • 为什么向下转型:

                后续代码可能要用到对象的独有行为

// 1、实例化一个Dog对象,并且向上转型
Animal animal = new Dog();

// 2、判断类型,判断animal指向的对象是不是一个Cat类型
if (animal instanceof Cat) {
   System.out.println("animal 的确是一个Cat对象,可以进行向下转型 "); }
else {
   System.out.println("animal不是一个  Cat对象,不能进行向下转型 "); }

 

import java.util.Objects;

/**
 * 动物类型:
 *     颜色
 *     年龄
 *     名字
 */
public class Animal {
    private String name;
    private int age;
    private String color;
    public Animal(){}
    public Animal(String name, int age, String color) {
        this.name = name;
        this.age = age;
        this.color = color;
    }

    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 String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public void run(){
        System.out.println("--------动物都会跑---------");
    }


    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Animal animal = (Animal) o;
        return age == animal.age && Objects.equals(name, animal.name) && Objects.equals(color, animal.color);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age, color);
    }

}
/**
 * 猫类型:是Animal的子类型
 *
 */
public class Cat extends Animal{
    public void eatMouse(){
        System.out.println("猫吃老鼠");
    }
    @Override
    public void run(){
        System.out.println("---猫会跳着跑---");
    }
}
public class Dog extends Animal{
    public void lookHome(){
        System.out.println("狗会看家");
    }
    public void run(){
        System.out.println("------狗撒腿就跑--------");
    }
}
public class TestAnimal {
    public static void main(String[] args) {
        System.out.println("----------向上造型--------------");
        Cat c =  new Cat();
        Animal a = c;
        //
        //a.eatMouse(); //编译异常,因为编译期间,看变量的类型,只能调到变量类型里的有访问权限成员。
        a.run();  //能调用,因为run是a的方法。

        testRun(c);
        testRun(new Dog());

        System.out.println("----------向下造型--------------");
        //Dog d = (Dog)a;   //a里的是猫的地址,a是猫对象,赋值到Dog的变量里。不合理,Cat和Dog是非父子关系。
        if(a instanceof Dog){
            Dog d = (Dog)a;
        }else if(a instanceof Cat){
            Cat c1 = (Cat)a;
        }

    }

    /**
     * 形参是父类型的变量,a可以存储任何子类型对象的地址。 非常灵活。
     * @param a
     */
    public static void testRun(Animal a){
        a.run();
        //为什么要向下转型。 后续的代码可能要用到对象的独有行为。
        if(a instanceof Dog){
            Dog d = (Dog)a;
            d.lookHome();
        }else if(a instanceof Cat){
            Cat c1 = (Cat)a;
            c1.eatMouse();
        }
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值