Day15:三大特性:封装、继承、多态
封装
该露的露,该藏的藏
我们程序设计要追求“高内聚,低耦合”。高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;低耦合:仅暴露少量的方法给外部使用
封装(数据的隐藏)
通常,应禁止直接访问一个对象中数据的实际表示,而应通过操作接口来访问,这称为信息隐藏
记住这句话就够了:属性私有,get/set
封装大多时候是针对属性,private私有成员变量在本类中可以随意访问,但超出了类的范围就不能访问了。
要想访问私有成员变量,必须定义一对Getter/Setter方法,当然,还可以使用反射且方法必须以getXxx/setXxx规则命名对于getXxx来说,不能有参数,返回值的类型要与成员变量类型一致对于setXxx来说,不能有返回值,参数类型要与成员变量类型一致
对于boolean类型,它的Getter方法必须写成 isXxx 的形式,而 setXxx 规则不变。原文链接:https://blog.csdn.net/qq_34565684/article/details/106334769
//Student
package com.oop.demo04;
//类 private :私有
public class Student {
//属性私有
private String name;//名字
private int id;//学号
private char sex; //性别
private int age;//年龄
//提供一些可以操作这个属性的方法!
//提供一些public的get、set方法
//get 获得这个数据
public String getName(){
return this.name;
}
//set 给这个数据设置值
public void setName(String name){
this.name = name;
}
//alt+insert 快捷键
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
if(age>120||age<0) {//不合法
this.age = 3;
}else{
this.age = age;
}
}
//学习()
//睡觉()
}
//使用
package com.oop;
import com.oop.demo03.Pet;
import com.oop.demo04.Student;
/*
1.提高程序的安全性,保护数据
2.隐藏代码的实现细节
3.统一接口
4.系统的可维护性增加
*/
public class Application {
public static void main(String[] args) {
Student s1= new Student();
s1.setName("安言");
System.out.println(s1.getName());
s1.setAge(10);//不合法的
System.out.println(s1.getAge());
}
}
继承
继承的本质是对某一批类的抽象,从而实现对现实世界更好的建模
extends的意思是"扩展"。子类是父类的扩展。
Java中类只有单继承,没有多继承(一个儿子只能有一个爹,但是一个爹可以有多个儿子)
继承是类和类之间的一种关系。除此之外,类和类之间的关系还有依赖、组合、聚合等。
继承关系的两个类,一个为子类(派生类),一个为父类(基类)。子类继承父类,使用关键字extends来表示
子类和父类之间,从意义上讲应该具有"is a"的关系
public class Student extends Person //继承
Person person;//Student类里面包含了Person
在Java中,所有类,都默认直接或者间接继承Object
//public
//private
//default 默认的
//protected 受保护的
//ctrl+h 快捷键 查看父子关系
//使用
//Person 人:父类
public class Person /*extends Object*/ {
//public
//private
//default 默认的
//protected 受保护的
private int money = 10_0000_0000;
public void say(){
System.out.println("说话");
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
}
//学生 is 人 :子类
public class Student extends Person{
//ctrl+h
}
public static void main(String[] args) {
Student student = new Student();
student.say();
student.setMoney(1_0000_0000);
System.out.println(student.getMoney());
}
Super
和this对比看
this访问本类的属性
super是子类通过super访问父类的属性
public Student() {
System.out.println("Student无参执行了");
}
public Person() {
System.out.println("Person无参执行了");
}
public static void main(String[] args) {
Student student = new Student();
/*student.test("安言");
student.test1();*/
}
输出结果是:
"Person无参执行了
Student无参执行了
因为子类中隐藏代码:调用了父类的无参构造
super注意点:
1.super调用父类的构造方法,必须在构造方法的第一个
public Student() {
super();//调用父类的构造器,必须要在子类构造器的第一行
System.out.println("Student无参执行了");
}ok!!!
public Student() {
System.out.println("Student无参执行了");
super();
}//报错
2.super必须只能出现在子类的方法或者构造方法中!
3.super和this不能同时调用构造方法
Vs this
代表的对象不同:
this:本身调用者这个对象
super:代表父类对象的应用
前提:
this:没有继承也可以使用
super:只能在继承条件下才可以使用
构造方法
this();本类的构造
super();父类的构造
父类
//Person 人:父类
public class Person /*extends Object*/ {
public Person() {
System.out.println("Person无参执行了");
}
public Person(String name) {
System.out.println("Person无参执行了");
}
//public
//private
//default 默认的
//protected 受保护的
protected String name = "anyan";
//私有的东西无法被继承
private void print1(){
System.out.println("Person");
}
public void print(){
System.out.println("Person");
}
}
子类
package com.oop.demo05;
//学生 is 人 :子类
public class Student extends Person{
//ctrl+h
//隐藏代码:调用了父类的无参构造
public Student() {
super();//调用父类的构造器,必须要在子类构造器的第一行
System.out.println("Student无参执行了");
}
private String name = "tianyu";
public void print(){
System.out.println("Student");
}
public void test1(){
print();
this.print();
super.print();
}
public void test(String name){
System.out.println(name);
System.out.println(this.name);
System.out.println(super.name);
}
}
如果父类里面只有有参构造方法
public Person(String name) {
System.out.println("Person无参执行了");
}
在子类里面
public Student() {
super();//调用父类的构造器,必须要在子类构造器的第一行
System.out.println("Student无参执行了");
}
就会报错,因为一旦定义了有参构造方法,那么无参构造方法就没有了,必须显示出来,即使什么也不写
public Person() {
}
public Person(String name) {
System.out.println("Person无参执行了");
}
方法重写
静态
public class A extends B {
public static void test(){
System.out.println("A=>test()");
}
}
//重写都是方法的重写,和属性无关
public class B {
public static void test(){
System.out.println("B=>test()");
}
}
public class Application {
public static void main(String[] args) {
//方法的调用只和定义的数据类型有关
A a = new A();
a.test();//A
//父类的引用指向了父类
B b = new A();
b.test();//B
}
}
非静态
//重写都是方法的重写,和属性无关
public class B {
public void test(){
System.out.println("B=>test()");
}
}
//重写
public class Application {
//静态方法和非静态方法区别很大
public static void main(String[] args) {
//静态方法的调用只和定义的数据类型有关
//非静态方法:重写
A a = new A();
a.test();//A
//父类的引用指向了父类
B b = new A();//子类重写了父类的方法
b.test();//A
}
}
因为静态方法是类的方法,而非静态方法是对象的方法。
有static时,b调用了B类的方法,因为b是用b类定义的
没有static时,b调用的是对象的方法,而b是用A类new的
重写:需要有继承关系,子类重写父类的方法!
1.方法名必须相同
2.参数列表必须相同
3.修饰符:范围可以扩大: public>protected>default>private
子类不能重写父类声明为private权限的方法。
4.抛出的异常,范围,可以被缩小,但不能扩大;例如:父类的一个方法申明了一个检查异常IOException,在重写这个方法是就不能抛出Exception,只能抛出IOException的子类异常,可以抛出非检查异常。
重写,子类的方法和父类的方法必须要一致;方法体不同!
5.返回值类型
父类被重写的方法的返回值是void,则子类重写的方法的返回值类型只能是void
父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类
父类被重写的方法的返回值类型是基本数据类型(比如double),则子类重写的方法的返回值必须是相同的基本数据类型(必须是double)
为什么需要重写:
1.父类的功能,子类不一定需要,或者不一定满足!
Alt + Insert:override
//考察多态的笔试题
public class InterviewTest{
public void main (String[] args){
//编译看左,运行看右
Base1 base = new Sub1();
base.add(1,2,3);
Sub1 s = (Sub1)base;
s.add(123);
}
class Base1{
//第一步
public void add(int a,int...arr){
System.out.println("base1");
}
}
class Sub1 extends Base1{
//第二步
public void add(int a,int[] arr){
System.out.println("sub_1");
}//是重写
public void add(int a,int b,int c){
System.out.println("sub_2");
}
}
}
//运行结果:
//sub_1
//sub_2
多态
对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用)
多态的变量,想调用子类的方法,必须在父类有,并且在子类重写父类的方法
动态编译:类型只有在执行过程中才能确定,可扩展性更强
即同一方法可以根据发送对象的不同而采用多种不同的行为方式
一个对象的实际类型是确定的,但可以指向对象的引用的类型有很多(父类,有关系的类)
编译看左,运行看右
有了对象的多态性后,我们在编译器,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法。
多态存在的条件
有继承关系
子类重写父类方法
父类引用指向子类对象
多态的使用:当调用子父类同名同参数的方法时,实际执行的是子类重写父类的方法---虚拟方法调用
注意:多态是方法的多态,属性没有多态性
instanceof 类型转换---引用类型
package com.oop.demo06;
public class Person {
public void run(){
System.out.println("run");
}
}
package com.oop.demo06;
public class Student extends Person{
public void run(){
System.out.println("son");
}
public void eat(){
System.out.println("eat");
}
}
/*
多态注意事项:
1.多态是方法的多态,属性没有多态
2.父类和之类,有联系, 类型转换异常,必须存在继承关系 ClassCastException!
3.存在条件:继承关系,方法需要重写,父类引用指向子类对象 Father f1 = new Son();
1.static方法 属于类,它不属于实例
2.final 是常量,不能
3.private方法:私有的,不能
*/
package com.oop;
import com.oop.demo05.A;
import com.oop.demo05.B;
import com.oop.demo06.Student;
import com.oop.demo06.Person;
/*
1.提高程序的安全性,保护数据
2.隐藏代码的实现细节
3.统一接口
4.系统的可维护性增加
*/
public class Application {
public static void main(String[] args) {
//一个对象的实际类型是确定的
// new Student();
//new Person();
//可以指向的引用类型就不确定了
//Student 能调用的方法都是自己的或者继承父类的
Student s1= new Student();
//Person 父类型,可以指向子类,但是不能调用子类独有的方法
Person s2 = new Student();//父类的引用指向子类
Object s3 = new Student();
//对象能执行哪些方法,主要看对象左边的类型,和右边关系不大
s2.run();//子类重写了父类的方法,执行子类的方法
s1.run();
//s2.eat();不行
((Student) s2).eat();
}
}
重写是运行时多态,重载是编译时多态
虚拟方法调用(多态情况下)
子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。
多态是运行时行为
class Animal{
protected void eat(){
System.out.println("animal eat food");
}
}
class Cat extends Animal{
protected void eat(){
System.out.println("cat eat fish");
}
}
class Cat extends Animal{
protected void eat(){
System.out.println("cat eat fish");
}
}
class Dog extends Animal{
public void eat(){
System.out.println("dog eat bone");
}
}
class Sheep extends Animal{
public void eat(){
System.out.println("dog eat bone");
}
}
public static Animal getInstance(int key){
switch(key){
case 0:
return new Cat();
case 1:
return new Dog();
case 2:
return new Sheep();
}
}
public static void main(String[] args){
int key = new Random.nextInt(3);
System.out.println(key);
Animal animal = getInstance(key);
animal.eat();
}
方法的重载与重写
从编译和运行的角度看:
重载,是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了。Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。
所以:对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为”早绑定“或”静态绑定“
而对于多态,只有等到方法调用的那一刻,解释运行器才会确定要调用的具体方法,这称为”晚绑定“或”'动态绑定'。
引用一句Bruce Ecke的话:“不要犯傻,如果它不是晚绑定,他就不是多态”。
//==,对于引用数据类型来讲,比较的是两个引用数据类型变量的地址值是否相等
System.out.println(b==s)
//Sub s = new Sub()
//Base b =s
//Base是父类,Sub是子类
instanceof
当进行向下转型之前进行判断。
可以判断两个类之间是否存在父子关系。(类型转换)引用类型,判断一个对象是什么类型
instanceof 左边是对象,右边是类;当对象是右边类或子类所创建的对象时,返回true,否则,返回false
说明:
类的实例包含本身的实例,以及所有直接或间接子类的实例
instanceof左边显式声明的类型与右边操作元必须是同种类或存在继承关系,也就是说需要位于一个继承树,否则会编译错误。
System.out.println(x instanceof y);//能不能编译通过
要看编译能不能通过,就看x和y有没有父子关系
public class Application {
public static void main(String[] args) {
//Object > Person >Student
//Object > Person >Teacher
//Object > String
Object object = new Student();
System.out.println(object instanceof Student);//true
System.out.println(object instanceof Person);//true
System.out.println(object instanceof Object);//true
System.out.println(object instanceof Teacher);//false
System.out.println(object instanceof String);//false
System.out.println("=========================");
Person person = new Student();
System.out.println(person instanceof Student);//true
System.out.println(person instanceof Person);//true
System.out.println(person instanceof Object);//true
System.out.println(person instanceof Teacher);//false
System.out.println(person instanceof String);//编译报错
System.out.println("=========================");
Student studnet = new Student();
System.out.println(studnet instanceof Student);//true
System.out.println(studnet instanceof Person);//true
System.out.println(studnet instanceof Object);//true
System.out.println(studnet instanceof Teacher);//编译报错
System.out.println(studnet instanceof String);//编译报错
}
}
几个注意点:
创建子类时,会加载所有的父类,转型只能转为已经加载了的类
问题一:编译时通过,运行时不通过
举例一:
Person p3 = new Woman();
Man m3 = (Man)p3;
举例二:
Person p4 = new Person();
Man m4 = (Man)p4;
问题二:编译通过,运行也通过
Object obj = new Woman();
Person p = (Person)obj;
问题三:编译不通过
Man m5 = new Woman();
向上转型:多态
向下转型:使用instanceof进行判断