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;//封装agechar 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赋给子类namesuper.name = name;//name赋给父类中的namethis.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=1double 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{@Overridepublic void sum(int a, int b){System.out.println( a * b);}public void sum(int a,int b,int c){}@Overridepublic String getName(){return "小红";}@Overridepublic 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对象,不能进行向下转型 "); }