Java 面向对象(三)

封装

什么是封装

面向对象三大特征之一

1、 把对象的状态和行为看成一个统一的整体,将字段和方法放到一个类中。

2、 信息隐藏:把不需要让外界知道的信息隐藏起来。尽可能隐藏对象功能实现细节,向外界暴露方法,保证外界安全访问功能。

封装的好处

1、 保证数据的安全

2、 提高组件的复用性

现假设 提交表单提交,表单内容: 姓名、账号、邮箱、密码、验证码、是否同意协议。

没有用封装:

  • 参数列表各个参数都要写,会很多;
  • 调用时,不确定的参数,要给初始值。
public class Register {
	static void commit(String name, String id, String email, String pwd, int checkCode, boolean isAgree) {
		System.out.println("注册");
	}

	public static void main(String[] args) {
		commit("zs", "", "", "", 5521, true);
	}
}

封装后:

class User {
	String name;
	String id;
	String email;
	String pwd;
	int checkCode;
	boolean isAgree;
}

public class Register {
	static void commit(User user) {
		System.out.println("注册");
	}

	public static void main(String[] args) {
		User user = new User();
		user.name = "zs";
		commit(user);
	}
}

保证数据的安全:

  • 把想要保护的字段用 private 修饰
  • 内部提供方法进行设置,在设置方法当中判断设置的数据是否合法
  • 提供get方法获取修饰的值

封装前:输入错误年龄

class User {
   String name;
   int age;

   void show() {
   	System.out.println("我是" + name + ",年龄" + age);
   }
}

public class Login {
   public static void main(String[] args) {
   	User user = new User();
   	user.name = "zs";
   	user.age = -10;
   	user.show();
   }
}

封装后:

class User {
   String name;
   private int age;

   void show() {
   	System.out.println("我是" + name + ",年龄" + age);
   }

   public int getAge() {
   	return age;
   }

   public void setAge(int age) {
   	if (age>0) {
   		this.age = age;
   	} else {
   		System.out.println("输入数据不合理");
   	}
   }
   
}

public class Login {
   public static void main(String[] args) {
   	User user = new User();
   	user.name = "zs";
   	user.setAge(-10);
   	user.show();
   }
}

访问修饰符

什么是访问修饰符

访问权限修饰符来规定在一个类里,能看到什么,能暴露什么。

访问修饰符

访问修饰符 说明
private 表示私有的,表示类访问权限,只能在本类中访问,离开本类之后,就不能直接访问
默认 表示包访问权限,访问者的包必须和当前定义类的包相同才能访问,没能继承
protected 表示子类访问权限,同包中的可以访问,不同包不能访问,继承也可以访问
public 表示全局的可以公共访问权限,使用了public修饰,则可以在当前项目中任何地方访问

访问权限表:

作用域 当前类 同一包中 子孙类 其它包
private 可以访问 不能访问 不能访问 不能访问
默认 可以访问 可以访问 不能访问 不能访问
protected 可以访问 可以访问 可以访问 不能访问
public 可以访问 可以访问 可以访问 可以访问

this 关键字

在一个方法当中,要给变量赋值,它会先到方法当中去找有没有该变量。

如果有,就给方法内部的变量赋值,不会往上再去找了,如果没有 ,就往它上一级去找。

public class User {
	String name;
	int age;
	
	public User(String name, int age) {
		name = name;
		age = age;
	}

	public static void main(String[] args) {
		User user = new User("zs", 10);
		System.out.println(user.name);
		System.out.println(user.age);
	}
}

输出结果:

null
0

在构造器中,变量赋值给自己,都是构造器的参数,没有给对象当中的字段赋值

在方法当中变量前加上了this,就代表直接给对象当中的字段赋值

public class User {
	String name;
	int age;
	
	public User(String name, int age) {
		this.name = name;
		this.age = age;
	}

	public static void main(String[] args) {
		User user = new User("zs", 10);
		System.out.println(user.name);
		System.out.println(user.age);
	}
}

输出结果:

zs
10

this : "这个" ——当前正在使用对象的地址

public class User {
	String name;
	int age;
	
	public User(String name, int age) {
		this.name = name;
		this.age = age;
		System.out.println(this);
	}

	public static void main(String[] args) {
		System.out.println("use1:");
		User user1 = new User("zs", 10);
		System.out.println(user1);
		
		System.out.println("use2:");
		User user2 = new User("ls", 20);
		System.out.println(user2);
	}
}

输出结果:

use1:
Test.User@70dea4e
Test.User@70dea4e
use2:
Test.User@5c647e05
Test.User@5c647e05

可以看到 this 和当前使用对象的地址是一样的。

this的使用:

(1)帮我们区分成员变量和局部变量的二异性, 必须得要使用 this

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

(2)在同类当中 ,实例方法的调用,前面其实是有this,可以省略

public class User {
	String name;
	int age;
	
	public User(String name, int age) {
		this.name = name;
		this.age = age;
	}
	
	void show() {
		System.out.println(this.name);
	}
	
	void sayHello() {
		this.show();
	}

	public static void main(String[] args) {
		User user1 = new User("zs", 10);
		user1.sayHello();
	}
}

输出结果:zs

(3)可以把this做为参数传递

(4)可以当做返回值 返回

(5)static 不能和 this一起使用

(6)构造器的重载互调,this(); 此时的this代表的是构造器名,必须写到第一行。

public class User {
	String name;
	int age;

	public User(String name) {
		this.name = name;
	}

	public User(String name, int age) {
	    // 必须写在第一行。this代表的是当前构造器
		this(name);
		this.age = age;
	}
}

继承

上面三个类发现有共同的代码

我们可以用继承来解决上面的代码重复问题

父类:

public class Person {
	String name;
	int age;

	public void eat() {

	}
}

子类:

extends 表示继承 。 后面是父类 前面是子类

public class Teacher extends Person {
	String position;
	
	public void teach() {
		
	}
}

public class Student extends Person {
	String sno;
	
	public void study() {
		
	}
}


public class Employee extends Person {
	String hireDate;
	
	public void work() {
		
	}
}

什么是继承

从已有类中,派生出新的类,新的类中吸收已有类当中的状态和行为,并能扩展出新的能力。

Java继承是使用已有类作为基础,建立新的类。

继承是一种从一般到特殊的关系,是一种 “is a” 的关系,即子类是对父类的派生,是一种特殊的父类。

比如:狗是动物的一种特殊情况,狗属于动物;卡车是车的一种特殊情况,卡车属于车…

如何表示继承

基于某个父类对对象的定义加以拓展,在Java语言中,存在多个类的时候,我们使用 ”extends” 关键字来表示子类和父类之间的关系。

语法格式: 在定义子类的时候来表明自己需要拓展于哪一个父类。

public  class  子类类名    extends    父类类名
{
         编写自己特有的状态和行为
}

继承的作用

(1)解决代码重复问题

(2)真正的作用,表示出一个体系

先写好父类还是先写子类?

一般的,我们在开发工程中先编写多个自定义类。写完之后,发现多个类之间存在共同的代码,此时可以抽去出一个父类。

子类与父类的继承关系

子类继承父类之后,可以拥有父类的某一些状态和行为。

子类复用了父类的功能或状态。但并不是父类当中所有的内容,子类都可以直接使用。

子类可以使用父类当中的哪些成员

(1)如果父类中的成员使用public修饰,子类可以继承。

(2)如果父类中的成员使用protected修饰,子类可以继承,不同包也能继承。

(3)如果父类和子类在同一个包中,此时子类可以继承父类中默认的成员. 不同包不能继承默认成员

(4)如果父类中的成员使用private修饰,子类打死都继承不到。因为private只能在本类中访问。

(5)父类的构造器,子类也不能继承。因为构造器必须和当前的类名相同。

方法覆盖

子类扩展了父类,可以获得父类的部分方法和成员变量。可是当父类的某个方法不适合于子类本身的特征时,可以进行覆盖,重新定义父类当中的方法,我们称子类重新定义父类当中方法的过程为方法覆盖 (或方法重写)

方法覆盖原则

(1)子类方法必须得要和父类当中方法的方法签名相同(方法名+方法参数)

(2)子类方法的返回值类型是和父类方法的返回类型相同。

(3)子类方法的访问权限要比父类方法访问权限大或相等。不能比父类的还小。

判断方法是否为覆盖方法

判断是否是覆写方法的必杀技:@Override标签:若方法是覆写方法,在方法前或上贴上该标签, 编译通过,否则,编译出错。

只有方法存在覆盖的概念,字段没有覆盖。

什么时候要使用方法覆盖

当父类的某一个行为不符合子类具体的特征的时候,此时子类需要重新定义父类的方法,并重写方法体。

方法的重载和方法覆盖的区别

方法重载: Overload
方法重写: Override
本身二者一点关系都没有,仅仅只是因为名字很像。

方法重载: Overload

作用: 解决了同一个类中,相同功能的方法名不同的问题。 既然是相同的功能,那么方法的名字就应该相同。

规则: 同类中,方法名相同,方法参数列表不同。

方法重载: Overload

作用: 解决子类继承父类之后,可能父类的某一个方法不满足子类的具体特征。此时需要重新在子类中定义该方法,并重写方法体。

规则:父类和子类的方法签名是相同的,即方法名相同,方法参数列表也相同。

super 关键字

在子类中的某一个方法中,去调用父类被覆盖的方法。此时就要使用super关键字

this:当前对象,谁调用this所在的方法,this就是哪一个对象。
super:当前对象的父类对象。

父类:

public class Bird {	
	
	void fly() {
		System.out.println("飞到天空了");
	}
}

子类:

public class Penguin extends Bird{
	
	@Override
    void fly() {
		System.out.println("企鹅起飞");
	}
	
	void test() {
		this.fly();		// 代表是当前对象, 到自己类当中去找方法
		super.fly();	// 代表是父类对象, 到父类当中去找指定的方法
	}
}

main方法:

class Test {
	public static void main(String[] args) {
		Penguin p = new Penguin();
		p.test();
	}
}

输出结果:

企鹅起飞
飞到天空了

继承内存分析

在类加载字节码时,会先判断有没有父类,如果有,会先把父类加载成字节码放到内存当中,然后再去把自己加载到内存当中。先加载父类,再加载自己。

就上面的例子分析

子类初始化过程

在创建子类对象之前,会先创建父类对象。调用子类构造器之前,在子类构造器中会先调用父类的构造器( super(); ),默认调用的是父类无参数构造器。

(1)如果父类不存在可以被子类访问的构造器,则不能存在子类。

(2)如果父类没有提供无参数构造器,此时子类必须显示通过super语句去调用父类带参数的构造器。

必须先有父类对象,而后才能有子类对象。必须先调用父类构造器,而后再调用子类构造器。

调用父类构造这句话,必须作为子类构造器的第一句话。

Super关键字使用的场景

(1)可以使用 super 解决子类隐藏了父类的字段情况,该情况我们一般不讨论,因为破坏封装。

(2)在子类方法中,调用父类被覆盖的方法

(3)在子类构造器中,调用父类构造器,此时必须使用super语句: super([实参])。和 this() 一样,super() 构造方法必须放在第一行。但不能和 this() 一起使用。

隐藏

(1)满足继承的访问权限下,隐藏父类静态方法:若子类定义的静态方法的签名和父类中的静态方法签名相同,那么此时就是隐藏父类方法。注意:仅仅是静态方法,子类存在和父类一模一样的静态方法。

(2)满足继承的访问权限下,隐藏父类字段:若子类中定义的字段和父类中的字段名相同(不管类型),此时就是隐藏父类字段,此时只能通过 super 访问被隐藏的字段。

(3)隐藏本类字段:若本类中某局部变量名和字段名相同,此时就是隐藏本类字段,此时只能通过 this 访问被隐藏的字段。

注意:static不能和super以及this共存!

Object 类

Object类是Java语言的根类,要么是一个类的直接父类,要么就是一个类的间接父类。

class 类名 { }  
其实等价于  
class 类名 extends Object { }

Object本身指对象的意思,我们发现所有的对象都具有某一些共同的行为,所以,我们抽象出一个类:Object,表示对象类,其他都会继承于Object类,也就拥有Object类中的方法

Object 类常用方法

(1)取得对象信息的方法:toString()

该方法在打印对象时被调用,将对象信息变为字符串返回,默认输出对象地址。

(2)对象相等判断方法:equals()

该方法用于比较对象是否相等,而且此方法必须被重写。默认equals()方法比较的是两个对象的地址,想要比较对象值是否相等,就要重写

(3)对象签名:hashCode()

该方法用来返回其所在对象的物理地址(哈希码值),常会和equals方法同时重写,确保相等的两个对象拥有相等的.hashCode。

(4)返回运行时类:getClass

返回此 Object的运行时类。

多态

准备四个类:

class Person {
	void feedAnimal(Animal animal) {
		animal.eat();
	}
}

class Animal {
	void eat() {
		System.out.println("动物吃东西");
	}
}

class Dog extends Animal {
	void eat() {
		System.out.println("狗啃骨头");
	}
}

class Cat extends Animal {
	void eat() {
		System.out.println("猫吃鱼");
	}
}

什么是多态

既然子类是一种特殊的父类,那么我们可不可以认为,狗对象/猫对象就是动物类型的对象。

Animal dog = new Dog();

一个对象有多种形态,就称它是多态(一个表示自己的类,一个表示自己父类的类型)

多态运行时,还是自己 new 的对象本身类。可以通过 对象.getClass() 来查看对象在运行时是那个类

多态的特点

子类对象赋给父类对象,在运行时期会表现出具体的子类特征调用子类的方法

多态运行时表现出来的还是子类的特征 (编译时,看左边,运行时,看右边)

public class Test {
	public static void main(String[] args) {
		Animal dog = new Dog();
		dog.eat();

		Animal cat = new Cat();
		cat.eat();
	}
}

输出结果:

狗啃骨头
猫吃鱼

多态的作用

准备饲养员类 Person 喂动物吃

class Person {
	void feedDog(Dog dog) {
		dog.eat();
	}
	
	void feedCat(Cat cat) {
		cat.eat();
	}
}

如果有非常多的动物,每个动物都要写一个喂养的方法太麻烦。这时候就用到了多态。

class Person {
	void feedAnimal(Animal animal) {
		animal.eat();
	}
}

测试下:

public class Test {
	public static void main(String[] args) {
		Dog dog = new Dog();
		Cat cat = new Cat();
		Person person = new Person();
		person.feedAnimal(dog);
		person.feedAnimal(cat);
	}
}

输出结果:

狗啃骨头
猫吃鱼

分析:

当把不同的子类对象都当作父类类型来看待,可以屏蔽不同子类对象之间的实现差异,从而写出通用的代码达到通用编程,以适应需求的不断变化。

在这里使用了多态后,只需要写一个方法就能达到相同的功能

类的强制类型转换

子类对象可以声明为父类类型,父类对象不可以声明为子类类型

Animal animal = new Dog();  // 对
Dog dog = new Animal();		// 错

在子类对象声明为父类类型后,可以通过强制转型,转型回来

Animal animal = new Dog();
Dog dog = (Dog) animal;

而父类对象声明为父类类型之后,并不能执行强制类型转化

Animal animal = new Animal();
Dog dog = (Dog) animal;		// 错

因为在子类对象声明为父类类型后,其实对象的真实意义还是子类对象。

类的类型转换:

(1)向上转型:将一个子类的引用赋给一个超类变量,编译器是允许的,不用进行强制类型转换。

格式:  超类 超类变量 = new 子类();

(2)向下转型:但是将一个超类的引用赋给 一个子类变量,必须进行强制类型转换,这样才能够通过运行时的检查

格式: 子类 子类对象变量名 = (子类) 父类对象引用

instanceof 关键字

判断一个对象是否是指定的类, 如果是,返回 true; 如果不是, 就返回 false。

格式:

boolean result = Object instanceof class

Object: 任意对象表达式
class: 任意已定义的对象类

void feedAnimal(Animal anim) {
	if (anim instanceof Dog) {
		Dog dog = (Dog) anim;
		dog.doWork();
	} else if (anim instanceof Cat) {
		Cat cat = (Cat) anim;
		cat.watch();
	}
}

字段不存在多态

字段不存在多态,字段前面对象是什么类型,就调用谁的。 在编译的时候,就已经确定要去调用谁的。

class Animal {
	String name = "动物";

	void eat() {
		System.out.println("动物吃东西");
	}
}

class Dog extends Animal {
	String name = "狗";

	void eat() {
		System.out.println("狗啃骨头");
	}
}

public class Test {
	public static void main(String[] args) {
		// 把子类对象赋值给父类类型
		// 运行时,表现的还是子类的特征,去调用子类的方法
		Animal dog = new Dog();
		dog.eat();

		// 字段不存大多态。
		// 字段前面对象是什么类型,就调用谁的。 在编译的时候,就已经确定要去调用谁的
		System.out.println(dog.name);
	}
}

输出结果:

狗啃骨头
动物

多态的3个必要条件:

(1)继承
(2)重写
(3)父类引用指向子类对象

多态总结

父类类型的引用可以调用父类中定义的所有属性和方法,而对于子类中定义而父类中没有的方法,它是无可奈何的; 同时,父类中的一个方法只有在父类中定义而在子类中没有重写的情况下,才可以被父类类型的引用调用; 对于父类中定义的方法,如果子类中重写了该方法,那么父类类型的引用将会调用子类中的这个方法,这就是动态连接。也可以叫做动态绑定。

动态绑定是指”在执行期间(而非编译期间)“判断所引用对象的实际类型,根据实际的类型调用其相应的方法。

当使用多态方式调用方法时,首先检查父类中是否有该方法,如果有,再去调用子类的同名方法;如果没有,则编译错误。

对于多态,可以总结它为:

(1)使用父类类型的引用指向子类的对象;

(2)该引用只能调用父类中定义的方法和变量;

(3)如果子类中重写了父类中的方法,那么在调用方法的时候,将会调用子类中的方法;(动态连接、动态调用)

多态和继承的对比

class Animal {
	int age = 3;
	String name = "动物";

	int n = 100;

	void eat() {
		System.out.println("动物吃东西");
	}
	
	void work() {
		System.out.println("动物工作");
	}
}

class Dog extends Animal {
	int age = 5;
	String name = "狗";

	int m = 20;

	void eat() {
		System.out.println("狗啃骨头");
	}
	
	void shout() {
		System.out.println("汪汪汪");
	}
}

多态:

public class Test {
	public static void main(String[] args) {
		System.out.println("多态:父类引用子类对象");
		Animal dog = new Dog();

		/* 字段不存在多态 */
		System.out.println(dog.age);				// 3
		System.out.println(dog.name);				// 动物
		
		System.out.println(((Dog) dog).age);		// 5
		System.out.println(((Dog) dog).name);		// 狗

		System.out.println(dog.n);					// 100
		/* System.out.println(dog.m); 父类访问不到子类,要强制类型转换 */
		System.out.println(((Dog) dog).m);			// 20
		
		dog.eat();									// 狗啃骨头
		dog.work();									// 动物工作
		/* dog.shout(); 父类访问不到子类,要强制类型转换 */
		((Dog) dog).shout();						// 汪汪汪
	}
}

继承:

public class Test {
	public static void main(String[] args) {
		System.out.println("继承");
		Dog dog2 = new Dog();
		System.out.println(dog2.age);				// 5
		System.out.println(dog2.name);				// 狗
		
		System.out.println(((Animal) dog2).age);	// 3
		System.out.println(((Animal) dog2).name);	// 动物

		System.out.println(dog2.n);					// 100
		System.out.println(dog2.m);					// 20
		
		dog2.eat();									// 狗啃骨头
		((Animal) dog2).eat();						// 狗啃骨头
		dog2.work();								// 动物工作
		dog2.shout();								// 汪汪汪
	}
}
posted @ 2019-07-08 23:29  Lomen~  阅读(585)  评论(0编辑  收藏  举报