java基础5-面向对象的三大特征

avatar
作者
猴君
阅读量:0

1.封装

1.1广义封装和狭义封装(java)

封装,单词Encapsulation。

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

1.2 java封装(狭义封装)

封装的原因:

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

封装的方法 :

  1. 为了不让外界直接访问某些属性,用关键字private修饰这些属性。
  2. 提供共有的getter/setter的方法,用来操作这个被私有化的属性。

为什么封装之后还要添加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 String getName() {         return name;     }      public void setName(String name) {         this.name = name;     }      public int getAge() {         return age;     }      public void setAge(int age) {         if (age > 0 && age <= 120) {             this.age = age;         }else{             System.out.println("--您赋值的年龄不合法--"+age);         }     }      public char getGender() {         return gender;     }      public void setGender(char gender) {         if(gender == '男' || gender == '女'){             this.gender = gender;         }else{ //            System.out.println("--您赋值的性别不合法,需要汉字'男'或'女'--");             //如果赋值有问题,可以使用下面的方式让程序中断,并进行提示             throw  new RuntimeException("您赋值的性别不合法,需要汉字'男'或'女'--");         }     } }

测试代码(测试部分测试类)

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方法在idea中有快捷生成方式,如下图所示。

1.

2.

 点击后选中要生成方法的变量就完事了。

2.单例设计模式

2.1设计模式简介

        设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的代码设计的经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性

毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。

总体来说设计模式分为三大类:

  • 创建型模式(5种):工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

  • 结构型模式(7种):适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

  • 行为型模式(11种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

只学习单例模式:

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

使用场景:

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

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

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

2.2单例的饿汉模式

  单例模式的饿汉模式

 1. 提供一个该类的,私有的,静态的属性, 并在静态代码块里赋值
 2. 提供一个私有的构造器, 避免外界直接调用构造器
 3. 提供一个公有的,静态的方法,来获取当前类的对象

 代码如下:

public class BSingleton {     private static BSingleton instance; // instance 实例的含义      static{         instance = new BSingleton();     }     //构造器私有化     private BSingleton(){}      public static BSingleton getInstance(){         return instance;     } }

2.3单例的懒汉模式

单例模式的汉模式

1.提供一个该类的,私有的,静态的属性。
2.提供一个私有的构造器,避免外界直接调用构造器。
3.提供一个共有的,静态的方法,来创建当前类的对象。如果对象不存在,说明对象还未创建。
4.懒汉模式,有线程安全隐患问题,比如两次调用的时候,都恰好执行到判断等于null。都会执行到创建实例那一行代码,那么就会在内存中创建出来两个对象。

代码如下:

public class CSingleton {     //私有化的静态变量     private static CSingleton instance;     //私有化构造器     private CSingleton() {      }     //公有的静态方法,用于获取对象     public static CSingleton getInstance() {         //如果静态变量里没有地址,说明还未创建对象。         if(instance == null) {             //创建对象,将地址存入静态变量             instance = new CSingleton();         }         //返回对象的地址,之前有,就用之前的,没有,就用新的。         return instance;     }  }

2.4 两者的比较 

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

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

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

3. 继承

3.1继承的简介

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

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

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

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

例子:

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

3.2 继承的特点

  1. Java只支持单继承,即一个类只能有一个父类;但是一个类可以有多个子类。
  2. java支持多重继承,即一个类在继承自一个父类的同时,还可以被其他类继承。 可见继承具有传递性
  3. 子类继承了父类的所有成员变量和方法,包括私有的(只不过没有访问权限),当然也包括静态成员
  4. 子类不会继承父类的构造方法只能调用父类里的构造方法并且一定至少有一个子类构造器调用了父类的构造器。
  5. 子类在拥有父类的成员的基础上,还可以添加新成员

 如下图所示:

 3.3 继承中的构造器

 

一个对象在实例化的时候,需要在堆上开辟空间,堆中的空间分为两部分,分别是从父类继承到的属性,子类特有的属性。而实例化父类部分的时候,需要调用父类中的构造方法。

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

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

 再重复一下:

构造器:

  •     子类不能继承父类的构造器
  •     子类的构造器中可以使用super(有参传参)进行显式的调用父类中的某一个构造器
  •     super(有参传参)和this(有参传参)一样,必须放在首行首句,因此不能并存
  •     子类的构造器中,至少存在一个构造器调用了父类的构造器
  •     为什么要至少有一个调用了父类的构造器:因为子类继承过来的属性需要初始化

 代码(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;     }      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;     } }

代码(Student——子类)

定义一个学生Student类型,将人的特征全部继承过来
学生类独有的特征:
    学号: studentId
    班级编号:classNumber

public class Student extends APerson{     private String studentId;     private String classNumber;      //添加自己的构造器     public Student(String studentId, String classNumber) { //        super("小黑",10,'女');         this.studentId = studentId;         this.classNumber = classNumber;     }     public Student(String name,int age,char gender,String studentId,String classNumber) { //        this.name=name;  父类里的name属性私有化,虽然子类继承了,但是不能直接访问。 //        setName方法是继承过来的,并且是公有的,因此可以直接使用         this(classNumber,studentId);         setName(name);         setAge(age);         setGender(gender); //        this.studentId = studentId; //        this.classNumber = classNumber;      }      public String getStudentId() {         return studentId;     }      public void setStudentId(String studentId) {         this.studentId = studentId;     }      public String getClassNumber() {         return classNumber;     }      public void setClassNumber(String classNumber) {         this.classNumber = classNumber;     } //添加toString方法:用来显示对象的属性值。     public String toString() {         return "name: "+getName()+"\nage: "+getAge()+"\ngender: "+getGender()+"\nstudentId: "+studentId+"\nclassNumber: "+classNumber;     } }

3.4 继承中的方法重写

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

为什么要重写

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

 重写的特点:

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

 代码部分:

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

 关于注解@Override

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

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

 面试题 : 简述 Override 和 Overload 的区别

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

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

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

 该方法的作用是将对象的信息变成字符串。 Object里返回的是 “类名@16进制” 。这个返回结果意义不大。看不到对象的成员变量的信息。因次一般都需要重写。

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

 重写:可以在idea里直接生成。

@Override public String toString() {    return "Teacher{" +"name='" + name + '\'' +", age=" + age +", gender=" + gender +'}'; }

 2) equals()方法

源码如下:

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

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

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

重写要遵循一些原则:

  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) 也必须成立。

代码:

public class Teacher {     private String name;     private int age;     private char gender;      public Teacher(){}     public Teacher(String name, int age, char gender) {         this.name = name;         this.age = age;         this.gender = gender;     }     //重写equals     @Override     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;     }

 重写equals方法
    1.判断一下,传入的是不是null
    2.判断一下,传入的是不是自己
    3.判断一下,是不是同类型,如果是就转成同类型进行比较
    4.其他任何情况,都返回false。 

 3)hashCode()方法

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

4)getClass()方法

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

//通过对象调用getClass(),来获取Teacher对应的描述类的副词昂         Class<? extends Teacher> aClass=t1.getClass();         System.out.println(aClass.getName());         //获取类里的所有属性         Field[] fields =aClass.getDeclaredFields();         for(Field f:fields){             System.out.println(f.getName());         }

3.6 final修饰词

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

特点:

  • 修饰类时类不能再有子类,即不能被继, 比如String, 八大基本数据类型的包装类
  • 修饰属性只能赋值一次,即要么直接初始化,要么在构造器初始化
  • 修饰方法:final修饰的方法,不能被重写
  • 修饰局部变量只能赋值一次,不能第二次赋值。
public class Cat {     private final String name;     public Cat(String name) {         this.name = name;     }      public final int sum(int a,int b){         //测试final修饰局部变量         final int x;         x = 100; //        x = 101;          return a+b;     }     //下面的方法:给name赋值已经不是初始化了,因为想要执行该方法,那么构造器一定先执行了。 //    public void setName(String name) { //        this.name=name; //    } } //不能继承Cat,因为Cat是final修饰的 //class ACat extends Cat{}  //探究一下父类里的方法被final修饰,还能不能参与重写 class DCat extends Cat{     public DCat(String name) {         super(name);     }     //不能重写父类里的final方法,否则报错。 //    public int sum(int a, int b){ //        return a+b; //    } }

上述代码注意注释部分的解释。

3.7 static修饰词

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

static 修饰词
1.修饰成员变量
        静态成员变量,属于类的,公共资源,使用类名.调用
        注意:可以使用引用变量.调用,但是不合理,因为不属于对象。
2.修饰方法:
        静态方法,属于类的,公共资源,使用类名.调用
        注意:可以使用引用变量.调用,但是不合理,因为定义的方法不属于对象,属于大家。
        静态方法中,不能直接访问非静态成员
        static方法不能被重写,但是子类里可以写跟父类里一模一样的静态方法不是重写,各是各的
3.修饰代码块,
        静态代码块,类加载时只执行一次。通常用于加载静态资源:图片,音频等

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

 代码解释:

public class MyUtil {     private String name;     //定义一个水桶的容量,单位是1L     public static int contain = 18;      //如果想要达到每时每刻看到的共有资源都是一样的,那么就应该再加上使用final修饰,即常量。     public static final double PI = 3.14159265358979323846;     //final 和 static顺序无所谓。     public void addContain() {         this.contain++;     }     public void subContain() {         this.contain--;     }     public static void sum(int a, int b) {         //不能直接访问非静态成员 //        this.aaaa; //        addContain();         contain = 19;      }      public static void main(String[] args) {         MyUtil p1 = new MyUtil();         //查看容量         //使用变量调用,但是不建议。         System.out.println("contain = " + p1.contain);          p1.addContain();         System.out.println("contain = " + MyUtil.contain);         //注意:静态变量要使用类名.调用         MyUtil p2 = new MyUtil();         p2.subContain();      } }  class sub extends MyUtil { //    @Override    添加注解后报错,因为static方法,不能被重写,但是子类里可以写跟父类里一模一样的静态方法,不是重写,各是各的     public static void sum(int a, int b) {         //不能直接访问非静态成员 //        this.aaaa; //        addContain();         contain = 19;      } }

4.多态 

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

4.1向上转型(向上造型)

  • 方法:父类型的变量引用子类型的对象
  • 向上转型肯定会成功,是一个隐式转换
  • 向上转型后的对象,将只能够访问父类中的成员(编译期间,看变量类型)
  • 如果调用的是重写过的方法,那么调用的一定是重写过的方法(运行期间,看对象)
  • 应用场景:在定义方法时,形式参数是父类型的变量。这样更加灵活,可以传任意子类型的对象

 代码:(父类)

public class Animal {     private String color;     private String name;     private int age;      public Animal() {}      public Animal(String color, String name, int age) {         this.color = color;         this.name = name;         this.age = age;     }      //动物都会叫     public void noise(){         System.out.println("---------动物都会叫--------");      }  }  class Dog extends Animal{     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 Animal{     public Cat(String color, String name, int age) {         super(color, name, age);     }     public void noise(){         System.out.println("----喵喵喵喵----");     }     public void catchMouse(){         System.out.println("---抓老鼠---");     } }

 向上转型的代码测试:

public class AnimalTest {     public static void main(String[] args) {         Animal a1 = new Cat("baide","下滑",3);         Animal a2 = new Dog("sad","白",10);         a1.noise();//编译期间不会出现问题,因为父类型里面有该方法。运行期间执行的是对象的类型里的方法逻辑 //        a1.catchMouse();调用不到该方法,因为a1这个变量的类型里没有该方法,(编译期间看变量类型)          test1(a1);         test1(a2);     }     //测试:执行动物的叫声   这就是向上造型的优势所在,父类型的变量作为参数,更加灵活,可以传入不同的子类型对象。     public static void test1(Animal an){         an.noise();     } }

 

4.2 向下转型

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

多态的另外一种形式:向下转型。
        父类型的变量赋值给子类型的变量,需要强制转换。
        该操作可能会出现失败,失败的话,就会报异常;ClassCastException 类造型异常
         如果想要避免失败,可以使用 instanceof 关键字来进行判断:
                该变量指向的对象是否属于某一个类型。如果是,返回true,否则返回false

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

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

向下转型代码:

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

 

    广告一刻

    为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!