Java(九)--面向对象(二)

面向对象的 3 个核心特性:继承、封装和多态;

封装

封装(encapsulation):把抽象出的数据[属性]和对数据的操作[方法]封装在一起,数据被保护在内部,程序的其他部门只能通过被授权的操作[方法],才能对数据进行操作。、

Java 语言的基本封装单位是类;

作用

保护类中的信息; 

  • 它可以阻止在外部定义的代码随意访问内部代码和数据。

隐藏实现细节,仅暴露使用的方式;

有助于建立各个系统之间的松耦合关系,提高系统的独立性

  • 当一个系统的实现方式发生变化时,只要它的接口不变,就不会影响其他系统的使用。

提高软件的复用率,降低成本

  •     每个系统都是一个相对独立的整体,可以在不同的环境中得到使用。
  •     例如,一个 U 盘可以在多台电脑上使用。

实现

1、在属性的前面加private修饰

  •       不能直接修改属性

2、提供一个公共(public)的赋值(setter)方法,用于对属性判断并赋值

public void setXxx(类型 参数名){ //Xxx表示某个属性
	//加入数据验证的业务逻辑
	属性 = 参数名;
}
  •     可以对属性进行限制操作,从而给类中的属性赋予合理的值

3、提供一个公共(public)的取值(getter)方法,用于获取属性的值

public 数据类型 getXxx(){ //权限判断;Xxx表示某个属性

	return xx;
}
  •     获取类中属性的值(也可以直接调用类中的属性名称来获取属性值)

4、在赋值和取值方法中,加入属性控制语句(对属性值的合法性进行判断)


public class Encapsulation01 {

	public static void main(String[] args) {
		Person person = new Person();
		person.setName("韩工");
		person.setAge(30);
		person.setSalary(30000);

		System.out.println(person.info());
		System.out.println(person.getSalary());//需要通过getSalary()方法访问人的工资
		System.out.println(person.name);//可以直接访问人的名字;

		// 如果我们自己使用构造器指定属性
		Person smith = new Person("smith", 80, 50000);
		System.out.println("====smith的信息======");
		System.out.println(smith.info());
	}
}

/*
需求:不能随便查看人的年龄,工资等隐私,并对设置的年龄进行合理的验证。
年龄合理就设置,否则给默认年龄, 必须在 1-120;
name的长度在 2-6字符 之间;
 */
class Person {
	public String name; // 名字公开
	private int age; // age 私有化
	private double salary; //工资 私有化

	public void say(int n, String name) {

	}

	// 构造器 alt+insert
	public Person() {
	}

	// 有三个属性的构造器
	public Person(String name, int age, double salary) {
       this.name = name;
       this.age = age;
       this.salary = salary;
	}

	// 使用快捷键alt+insert 写setXxx 和 getXxx,然后根据要求来完善我们的代码.
	public String getName() {
		return name;
	}

	public void setName(String name) {
		// 加入对数据的校验,相当于增加了业务逻辑
		if (name.length() >= 2 && name.length() <= 6) {
			this.name = name;
		} else {
			System.out.println("名字的长度不对,需要(2-6)个字符,默认名字");
			this.name = "无名人";
		}
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		// 判断
		if (age >= 1 && age <= 120) {// 如果是合理范围
			this.age = age;
		} else {
			System.out.println("你设置年龄不对,需要在 (1-120), 给默认年龄18 ");
			this.age = 18;// 给一个默认年龄
		}
	}

	public double getSalary() {
		// 可以这里增加对当前对象的权限判断
		return salary;
	}

	public void setSalary(double salary) {
		this.salary = salary;
	}

	// 写一个方法,返回属性信息
	public String info() {
		return "信息为 name=" + name + " age=" + age + " 薪水=" + salary;
	}
}

继承

继承(extend):程序中的继承性是指子类拥有父类的全部特征和行为,这是类之间的一种关系;

  • 当多个类之间具有共同的特征,将共同的部分抽取成一个父类,通过继承这个父类,而达到“代码复用”;
  • 模拟,表现现实世界中一个“is-a”的逻辑关系
  • 继承设计的基本思想,父类的构造器完成父类属性初始化,子类的构造器完成子类属性初始化;

Java(九)--面向对象(二)_第1张图片

//语法:使用extends声明继承父类

class 子类名 extends 父类名{

}

说明

1、子类会继承父类的所有的属性和方法,但是不会继承父类的构造器;

  •     子类也会继承父类的私有的属性与方法,但是在子类中不能直接使用父类的私有成员,通过父类提供公共的方法去访问;
  •     子类继承了所有的属性和方法, 非私有的属性和方法可以在子类直接访问

2、子类一定会调用父类的构造器;

  •     如果子类构造器中,没有写super()或super(实参列表)语句,那么默认就是调用父类“无参构造”
  •     子类的构造器中,也可以指定调用父类的构造器:        super()或super(实参列表)
  •     当父类没有无参构造时,子类的构造中必须手动调用父类的有参构造器:       super(实参列表)
  •     super()和 this()都必须放在构造器第一行,因此这两个方法不能共存在一个构造器;[super只能在构造器中使用]
  •     父类构造器的调用不限于一个父类(指直接继承),将一直往上追溯到Object类(顶级父类)
//继承关系分析

/**
 * 继承关系:Sub extends Base ; Base extends TopBase;
 */
public class ExtendsTest01 {
	public static void main(String[] args) {
		// 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器
		Base base = new Base();
		System.out.println("================================");
		// 构造器 TopBase() 被调用...
		// 父类 Base() 无参构造器被调用....

		Base base02 = new Base("jack", 20);
		System.out.println("================================");
		// 构造器 TopBase() 被调用...
		// 父类 Base(String name, int age) 有参构造器01被调用....

		Base base03 = new Base("tom");
		System.out.println("================================");
		// 构造器 TopBase() 被调用...
		// 父类 Base(String name)有参构造器02被调用....

		Sub sub = new Sub();
		// sub.sayOk();
		System.out.println("================================");
		// 构造器 TopBase() 被调用...
		// 父类 Base(String name, int age) 有参构造器01被调用....
		// 子类 Sub()构造器被调用....

		// Sub sub02 = new Sub("jack");
		// System.out.println("================================");

		Sub sub03 = new Sub("king", 10);
		// 构造器 TopBase() 被调用...
		// 父类 Base(String name, int age) 有参构造器01被调用....
		// 子类 Sub(String name, int age)构造器被调用....
	}
}

class Base extends TopBase { // 父类
	// 4 个属性
	public int n1 = 100;
	protected int n2 = 200;
	int n3 = 300;
	private int n4 = 400; // 私有变量

	public Base() { // 无参构造器
		System.out.println("父类 Base() 无参构造器被调用....");
	}

	public Base(String name, int age) {// 有参构造器
		// 默认 super()
		System.out.println("父类 Base(String name, int age) 有参构造器01被调用....");
	}

	public Base(String name) {// 有参构造器
		System.out.println("父类 Base(String name)有参构造器02被调用....");
	}

	// 父类提供一个 public 的方法,返回了 n4
	public int getN4() {
		return n4;
	}

	public void test100() {
		System.out.println("test100");
	}

	protected void test200() {
		System.out.println("test200");
	}

	void test300() {
		System.out.println("test300");
	}

	private void test400() {
		System.out.println("test400");
	}

	// call
	public void callTest400() {
		test400();
	}
}

// 输入 ctrl + H 可以看到类的继承关系
class Sub extends Base { // 子类
	public Sub(String name, int age) {
		// 1. 调用父类的无参构造器, 如下或者 什么都不写,默认就是调用 super()
		// super();//父类的无参构造器
		// 2. 调用父类的 Base(String name) 构造器
		// super("hsp");
		// 3. 调用父类的 Base(String name, int age) 构造器
		super("king", 20);
		System.out.println("子类 Sub(String name, int age)构造器被调用....");
	}

	public Sub() {// 无参构造器
		// super(); //默认调用父类的无参构造器,因此父类有无参构造器才能创建子类的无参构造器;
		super("smith", 10);
		System.out.println("子类 Sub()构造器被调用....");
	}

	public Sub(String name) {
		super("tom", 30);
		// do nothing...
		System.out.println("子类 Sub(String name)构造器被调用....");
	}

	public void sayOk() {// 子类方法
		// 非私有的属性和方法可以在子类直接访问
		// 但是私有属性和方法不能在子类直接访问
		System.out.println(n1 + " " + n2 + " " + n3);
		test100();
		test200();
		test300();
		// test400();错误
		// 要通过父类提供公共的方法去访问
		System.out.println("n4=" + getN4());
		callTest400();//
	}
}

class TopBase { // 父类是 Object
	private int id;

	public TopBase() {
		// super(); Object 的无参构造器
		System.out.println("构造器 TopBase() 被调用...");
	}
}

3、Java中只支持单继承,但是又支持多层继承;

  •     Java类只有一个直接父类【单继承机制】;
  •     Java类又支持代代相传;
  •     Java类的根父类是java.lang.Object;
  •     不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系

4、 子类必须调用父类的构造器,完成父类的初始化 ;

//主程序
public class PCTest01 {
	public static void main(String[] args) {
		PC pc = new PC("I7", 20, 50, "华为");
		pc.printInfo();	// PC信息=
						// cpu=I7 memory=20 disk=50 brand=华为
	}
}


//编写PC子类,继承Computer类,添加特有属性【品牌brand】

public class PC extends Computer{

    private String brand;

    public PC(String cpu, int memory, int disk, String brand) {

        super(cpu, memory, disk);  
//这里IDEA 根据继承的规则,自动把构造器的调用写好

        this.brand = brand;  
//这里也体现: 继承设计的基本思想,父类的构造器完成父类属性初始化,子类的构造器完成子类属性初始化
    }

    public String getBrand() {
        return brand;
    }
    public void setBrand(String brand) {
        this.brand = brand;
    }

    public void printInfo() {
        System.out.println("PC信息=");

        //调用父类的getDetails方法,得到相关属性信息..
        System.out.println(getDetails() + " brand=" + brand);
    }
}



//编写Computer父类,包含CPU、内存、硬盘等属性,getDetails方法用于返回Computer的详细信息

public class Computer {
    private String cpu;
    private int memory;
    private int disk;

    public Computer(String cpu, int memory, int disk) {
        this.cpu = cpu;
        this.memory = memory;
        this.disk = disk;
    }

    //返回Computer信息
    public String getDetails() {
        return "cpu=" + cpu + " memory=" + memory + " disk=" + disk;
    }

    public String getCpu() {
        return cpu;
    }

    public void setCpu(String cpu) {
        this.cpu = cpu;
    }

    public int getMemory() {
        return memory;
    }

    public void setMemory(int memory) {
        this.memory = memory;
    }

    public int getDisk() {
        return disk;
    }

    public void setDisk(int disk) {
        this.disk = disk;
    }
}

分析

当子类对象创建好后,建立查找关系:

  •     (1)首先看子类是否有该属性
  •     (2)如果子类有这个属性,并且可以访问,则返回信息
  •     (3)如果子类没有这个属性,就看父类有没有这个属性(如果父类有该属性,并且可以访问,就返回信息..)
  •     (4)如果父类没有就按照(3)的规则,继续找上级父类,直到Object...

Java(九)--面向对象(二)_第2张图片

应用案例

//类的加载顺序: Object --> B --> A

public class ExtendsExercise01 {
	public static void main(String[] args) {
		B b = new B();//⑦
	}
}

class A {
	A() {//③
		System.out.println("a");//④
	}

	A(String name) {
		System.out.println("a name");
	}
}

class B extends A {
	B() {
		this("abc");//①
		System.out.println("b");//⑥
	}

	B(String name) {//②
		// 默认有 super();
		System.out.println("b name");//⑤
	}
}



//类的加载顺序: Object --> C --> B -->A

public class ExtendsExercise02 {
	public static void main(String[] args) {
		C c = new C(); // 我是A类  hahah我是B类的有参构造 我是c类的有参构造 我是c类的无参构造
	}
}

class A {// A类

	public A() {//④
		System.out.println("我是A类");  //⑤
	}
}

class B extends A { // B类,继承A类
	public B() {
		System.out.println("我是B类的无参构造");
	} //

	public B(String name) {//③
		System.out.println(name + "我是B类的有参构造");//⑥
	}
}

class C extends B {   // C类,继承 B类
	public C() {
		this("hello");//①
		System.out.println("我是c类的无参构造");//⑧
	} 

	public C(String name) {
		super("hahah");//②
		System.out.println("我是c类的有参构造"); //⑦
	}
}

多态

面向对象的多态性,即“一个接口,多个方法”;

多态性体现在父类中定义的属性和方法被子类继承后,可以具有不同的属性或表现方式;

多态性允许一个接口被多个同类使用,弥补了单继承的不足

Java 实现多态有 3 个必要条件:继承、重写和向上转型;

Java(九)--面向对象(二)_第3张图片

动态绑定机制

  • 在调用对象方法时候,该方法会和该对象的内存地址/运行类型绑定;
  • 当调用对象属性时,没有动态调用机制,哪里声明,哪里使用;

作用

  • 属于运行时行为
  • 代码更灵活

应用

  • 1、多态参数:    方法定义的形参类型为父类类型,实参类型允许为子类类型
  • 2、多态数组:    数组的定义类型为父类类型,里面保存的实际元素类型为子类类型
  • 3、多态属性
//方法的重载和重写
public class PloyMethod {
	public static void main(String[] args) {
		// 方法重载体现多态
		A a = new A();
		// 这里我们传入不同的参数,就会调用不同 sum 方法,就体现多态
		System.out.println(a.sum(10, 20));
		System.out.println(a.sum(10, 20, 30));
		// 方法重写体现多态
		B b = new B();
		a.say();
		b.say();
	}
}

class B { // 父类
	public void say() {
		System.out.println("B say() 方法被调用...");
	}
}

class A extends B {// 子类

	public int sum(int n1, int n2) {// 和下面 sum 构成重载
		return n1 + n2;
	}

	public int sum(int n1, int n2, int n3) {
		return n1 + n2 + n3;
	}

	public void say() {
		System.out.println("A say() 方法被调用...");
	}
}

数据类型的转换

Java(九)--面向对象(二)_第4张图片

案例1:
public class DynamicBinding {
	public static void main(String[] args) {
		// a 的编译类型 A, 运行类型 B
		A a = new B();// 向上转型
		System.out.println(a.sum());//?40 -> 30
		System.out.println(a.sum1());//?30-> 20
	}
}

class A {// 父类
	public int i = 10;

	// 动态绑定机制:
	public int sum() {// 父类 sum()
		return getI() + 10;// 20 + 10
	}

	public int sum1() {// 父类 sum1()
		return i + 10;// 10 + 10
	}

	public int getI() {// 父类 getI
		return i;
	}
}

class B extends A {// 子类
	public int i = 20;

	// public int sum() {
	// 	return i + 20;
	// }

	public int getI() {// 子类 getI()
		return i;
	}

	// public int sum1() {
	// 	return i + 10;
	// }
}

//案例2


/**
 * 继承关系:Cat-->Animal;Dog-->Animal;
 */
public class PolyDetail {
	public static void main(String[] args) {
		// 向上转型: 父类的引用指向了子类的对象
		Animal animal = new Cat();
		Object obj = new Cat();// Object 也是 Cat 的父类
		//向上转型调用方法的规则如下:
		//(1)可以调用父类中的所有成员(需遵守访问权限)
		//(2)但是不能调用子类的特有的成员,因为在编译阶段,能调用哪些成员,是由编译类型来决定的
		//animal.catchMouse();错误
		//(3)最终运行效果看子类(运行类型)的具体实现, 即调用方法时,按照从子类(运行类型)开始查找方法,然后调用,规则与前面方法调用规则一致。
		animal.eat();// 猫吃鱼..
		animal.run();// 跑
		animal.show();// hello,你好
		animal.sleep();// 睡


		// 需求:可以调用 Cat 的 catchMouse 方法
		// 多态的向下转型
		//  cat 的编译类型 Cat,运行类型是 Cat
		Cat cat = (Cat) animal;
		cat.catchMouse();// 猫抓老鼠

		//(2)要求父类的引用必须指向的是当前目标类型的对象
		// Dog dog = (Dog) animal; // Cat cannot be cast to com.hspedu.duotai.Dog
		System.out.println("ok~~");
	}
}



class Animal {//父类
	String name = "动物";
	int age = 10;

	public void sleep() {
		System.out.println("睡");
	}

	public void run() {
		System.out.println("跑");
	}

	public void eat() {
		System.out.println("吃");
	}

	public void show() {
		System.out.println("hello,你好");
	}
}

class Cat extends Animal {//子类
	public void eat() {// 方法重写
		System.out.println("猫吃鱼");
	}

	public void catchMouse() {// Cat 特有方法
		System.out.println("猫抓老鼠");
	}
}

class Dog extends Animal {//子类
}

你可能感兴趣的:(java学习,java,开发语言)