Java基础——面向对象之三大特征

一、三大特征

1、封装性

面向对象的特征之一:封装与隐藏

1.1 问题的引入:

  • 实际情况下,一个类的属性的取值通常是有约束的(比如年龄不能是负数等),
  • 但是在属性层面,只能限定其数据类型,无法进行更复杂的限制,
  • 想要添加逻辑语句进行复杂的控制,就必须在方法内进行,因此对这类属性的赋值要新建方法,并在方法内进行控制,
  • 但是单单如此还不够,用户依然可以通过 “对象.属性” 的方式对属性进行直接修改,因此要屏蔽这种操作,
  • 屏蔽的方式就是:使用private权限修饰符进行修饰,封装类的属性

1.2 封装性的体现:

  • 将类的属性私有化,同时提供公共的get()、set()方法,来获取和设置私有化属性的值
    (注意,这只是"封装性的一个体现",并不是"就是封装性")

  • 拓展:封装性的体现:①如上;②私有化方法,供类内部使用,不对外暴露;③单例模式,私有化构造器

1.3 封装性的体现,需要权限修饰符来配合

案例:

public class PersonTest {
	public static void main(String[] args) {
		Person p = new Person();
		p.age = 18;
		p.study();
		
		Person p1 = new Person("小明", 19);
		System.out.println(p1.getName());
		System.out.println(p1.getAge());
	}
}

class Person{
	//属性
	public String name;
	public int age;
	
	//构造器
	public Person(){
		this.age = 18;
		System.out.println("Person()....");
	}
	
	public Person(String name){
		this.name = name;
	}
	
	public Person(String name, int age){
		this.name = name;
		this.age = age;
	}
	
	//方法
	public void eat(){
		System.out.println("人吃饭");
	}
	
	public void study(){
		System.out.println("人学习");
	}
	
	public void setName(String name){
		this.name = name;
	}
	
	public String getName(){
		return this.name;
	}
	
	public void setAge(int age){
		if(age < 0 || age > 130){
			System.out.println("输入有误");
			return;
		}
		
		this.age = age;		
	}
	
	public int getAge(){
		return this.age;
	}
}

2、继承性

面向对象的特征之二:继承性

2.1 问题的引入

  • 多个类中存在相同属性和行为时,每个类中都写一遍很麻烦,而且一旦需要修改,需要每个类都改,(不便于修改和扩展功能)
  • 将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只需要继承那个抽象出来的类即可;

2.2 继承性的好处( why?)

  • 减少代码冗余,提高代码复用性
  • 便于功能的扩展
  • 为之后多态性的使用提供了前提

2.3 继承性的体现:class A extends B{}

  • extends关键字(扩展)
    A:子类、派生类、subclass
    B:父类、超类、基类、superclass

  • 一旦子类A继承了父类B以后,子类A中就获取了父类B中声明的结构(所有的属性、方法)
    父类中声明为private的属性、方法,子类也是能继承到的,只是由于封装性的影响,不能直接访问而已
    【(验证:不妨给父类提供get/set方法,创建一个子类对象,进行set和get,发现是有的,而且赋值成功了)】

  • 子类继承父类以后,还可以声明自己特有的属性、方法,实现功能的扩展
    【子类的功能,比父类丰富(不同于集合和子集的关系)】

2.4 Java中关于继承性的规定:

  • 一个类可以被多个子类继承
  • Java中类的单继承性:一个类只能有一个父类,(不同于C++中支持多继承)(但Java中可以实现多个接口)(而且接口本身支持多继承,即一个接口可以继承多个接口)
  • Java支持多层继承,(子父类是相对概念)(直接父类、间接父类)

2.5 Java中所有类的根父类:java.lang.Object类

  • 比如:toString()、wait()等方法

案例:

public class ExtendsTest {
	public static void main(String[] args) {
		Person p1 = new Person();
		p1.age = 1;
		p1.eat();

		Student s1 = new Student();
		s1.eat();
		s1.sleep();
		
		s1.breath();
	}
}

//生物类
class Creature{
	public void breath(){
		System.out.println("呼吸");
	}
}

//人类
class Person extends Creature{
	String name;
	int age;

	public Person() {
		super();
	}

	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}

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

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

//学生类
class Student extends Person {
	// String name;
	// int age;
	String major;

	public Student() {
		super();
	}

	public Student(String name, int age, String major) {
		super();
		this.name = name;
		this.age = age;
		this.major = major;
	}

	// public void eat(){
	// System.out.println("吃饭");
	// }
	//
	// public void sleep(){
	// System.out.println("睡觉");
	// }

	public void study() {
		System.out.println("学习");
	}
}

注1:方法的重写(override / overwrite)

  1. 重写:子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作

  2. 应用:重写以后,当创建子类对象以后,通过子类对象调用同名同参数的方法时,实际执行的是子类重写父类的方法

  3. 重写的规定:
    ① 方法名&形参列表:子类重写的方法,方法名和形参列表,必须与父类中被重写的方法完全一致
    ② 权限修饰符:子类重写的方法,权限修饰符,不能小于父类中被重写的方法
    —> 特殊情况:子类不能重写父类中声明为private权限的方法(子类根本都看不到,当然无所谓覆盖)
    —> 只要是子类中重写的方法,通过子类对象调用时,执行的一定是子类的方法
    (例如一种可能的情况:调用了A方法,A方法没有重写过,但A方法中调用了B方法,B方法重写过,那么B方法执行的依然是子类重写的B方法)
    ③ 返回值类型
    —> 父类中方法的返回值类型是void / 基本数据类型,子类重写后返回值类型也必须是void/相同的基本数据类型
    —> 父类中方法的返回值类型是A类型,子类重写后返回值类型必须是A类或A的子类
    ④ 抛出异常:子类重写的方法,抛出的异常类型,不能大于父类被重写的方法抛出的异常类型

总结:【子类:权限要"大",返回值要"小",抛出异常要"小"】

【子类和父类中同名同参数的方法,要么都声明为非static(非静态才谈重写这个事),要么都声明为static(不是重写,静态方法随着类的加载而加载,不能被覆盖)】


面试题:区分方法的重载与重写

注意:属性不存在覆盖一说,例如:父类中有int id,子类中也有int id,那么这两个id都是存在的,即,子类中有"两个属性"
【实际开发过程中,子父类中通常都不会定义同名是属性】


注2:子类对象实例化的全过程

  1. 从结果上来看:(继承性)
    ① 子类继承父类以后,就获取了父类中声明的属性和方法
    ② 创建子类的对象,在堆空间中,就会加载所有父类中声明的属性

  2. 从过程上来看:
    == 当我们通过子类的构造器创建子类对象时,我们一定会直接或间接地调用其父类的构造器==,进而调取父类的父类的构造器,直到调用了java.lang.Object类中空参的构造器为止;
    正是因为加载过所有的父类中的结构,所以才可以看到内存中有父类的结构,子类对象才可以考虑调用

注意:【虽然在内存中调用过父类的构造器,但内存中并没有创建过父类的对象,因为没有new;自始至终只创建过一个对象,即当前new的这个子类对象


3、多态性

面向对象特征之三:多态性

3.1 理解多态性:可以理解为一个事物的多种形态

3.2 何为多态性(what)

对象的多态性:父类的引用指向子类的对象

3.3 多态的使用:虚拟方法调用

  1. 有了对象的多态以后,我们在编译期,只能调用父类中声明的方法;但在运行期,我们实际执行的是子类重写父类的方法
    总结:编译,看左边;运行,看右边

  2. 子类中定义了与父类同名同参数的方法,在多态的情况下,将此时父类的方法称为虚拟方法)
    【编译的时候,编译器还认为是调用的父类的虚拟方法,(实际执行却不是了,看起来比较虚,所以称为虚拟方法),父类根据赋给他的不同子类对象,动态调用属于子类的该方法,这样的方法调用在编译期是无法确定的,(可见多态是"运行时行为",整个过程也称为动态绑定)

  3. 多态性的使用前提
    ① 要有类的继承关系(才能有父类引用指向子类对象)
    ② 要有方法的重写(虚拟方法调用,否则直接new一个父类对象不就得了)

  4. 对象的==多态性只适用于方法,不适用于属性==(属性根本都不存在覆盖一说,堆空间中两个同名属性都存在)

案例:

public class AnimalTest {
	public static void main(String[] args) {
		AnimalTest test = new AnimalTest();
		test.func(new Dog());
		test.func(new Cat());
	}
	
	public void func(Animal animal){ //Animal animal = new Dog() //对象的多态性:父类的引用指向子类的对象
		//多态的使用:当调用子类同名同参数的方法时,实际执行的是子类重写父类的方法 ---> 虚拟方法调用
		animal.eat();
		animal.shout(); 
		//【对外是作为Animal类型暴露的,编译器认为这是一个Animal类型】
	}
	
	//如果没有多态性的话,每个类都得单独写一个自己的方法
//	public void func(Dog dog){
//		dog.eat();
//		dog.shout();
//	}
//	
//	public void func(Cat cat){
//		cat.eat();
//		cat.shout();
//	}
}

class Animal{
	public void eat(){
		System.out.println("动物:进食");
	}
	
	public void shout(){
		System.out.println("动物:叫");
	}
}

class Dog extends Animal{
	public void eat(){
		System.out.println("狗:吃骨头");
	}
	
	public void shout(){
		System.out.println("汪!汪!汪!");
	}
}

class Cat extends Animal{
	public void eat(){
		System.out.println("猫:吃鱼");
	}
	
	public void shout(){
		System.out.println("喵!喵!喵!");
	}
}

注1:instanceof关键字

instanceof关键字的使用情境:
向下转型(不使用instanceof加以判断的话,容易出现ClassCastException类型转换异常)

public class PersonTest {
	public static void main(String[] args) {
		
		//对象的多态性:父类的引用指向子类的对象
		Person p1 = new Man();
		Person p2 = new Woman();
		
		//多态的使用:当调用子类同名同参数的方法时,实际执行的是子类重写父类的方法 ---> 虚拟方法调用
		p1.eat();
		p1.walk();

		
		System.out.println("**************************");
		//p1.earnMoney(); //无法调用子类所特有的方法或属性(提示的是没有定义,而不是不可见)
		//【多态:编译的时候,看左边声明的类型;运行的时候,看右边具体的类型】
		//【对外是作为Person类型暴露的,编译器认为这是一个Person类型】
		
		//有了对象的多态性以后,内存中实际是加载了子类特有的属性、方法的,
		//只是因为声明为Person类型,编译器认为是Person类型,导致编译时只能调用父类中声明的属性和方法
		//(编译器认为Person就这么大,可访问的范围小,访问时看不到子类特有的方法)
		
		/*
		 * instanceof关键字的使用情境:
		 * 向下转型(不使用instanceof加以判断的话,容易出现ClassCastException类型转换异常)
		 */
		if(p1 instanceof Man){
			Man m1 = (Man)p1;
			//【解析:之前说过,m1这个局部变量存的是地址值,实际上包含两部分:类型@地址值,强转是把类型转了,地址不变,现在编译器认为这是一个Man类型了】
			m1.earnMoney();
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值