目录
- 1. 封装
- 1.1 封装的语法
- 1.2 访问修饰限定符
- *包
- 1. 包的使用
- 2. 自定义包
- 3. 包访问权限(只在包内访问)
- 4. 常用的包
- 2. 继承
- 2.1 继承的语法(子类和父类)
- 2.2 在子类中访问父类
- 1.子类 与 访问父类 成员名字不同
- 2. 子类 与 访问父类 成员同名 --- super
- * 如何访问同名时的父类?
- 2.3 子类的构造方法
- 2.4 super和this
- 2.5 protected 关键字
- 2.6 继承后代码的执行顺序
- 2.7 继承的种类 --- final关键字
- 3. 组合
- 4. 多态
- 4.1 产生多态的条件
- 5. 多态的底层原理
- 5.1 重写和动态绑定
- * 动态绑定和静态绑定
- 5.2 向上转型和向下转型
- 1. 向上转型
- 2. 向下转型
- 6. 多态的优缺点
- 6.1 多态优点
- 6.2 多态缺点
OOP语言的三大主要特点:封装,继承,多态。
1. 封装
封装简单来说就是套壳屏蔽细节,让类的使用者只关注类的功能,而不关注类内的细节。比如笔记本电脑,只需要知道怎么用,而不需要知道电脑内部零件
1.1 封装的语法
java">//封装前
class Dog {public String name;public int age;public static String classRoom = "001";
}
//封装后
class Dog {private String name;private int age;private static String classRoom = "001";
}
封装前
封装后
1.2 访问修饰限定符
Java中主要通过类和访问修饰限定符来实现封装,有4种访问修饰限定符:
- public,公有的(哪里都能用)
- private,私有的(只能在类内使用)
- protected,受保护的(主要用于继承,后面讲)
- default,什么也不写,包访问权限(只在包内使用)
*包
为了更好的管理类,把相关的类放在一个文件夹下,这个文件夹就是包。在同一个项目中允许存在相同名称的类,只要处在不同的包中即可
1. 包的使用
Java中使用类需要导入类所在的包。
如使用Date类,import java.util.Date含义是:导入java下的uitl包下的Date类
【注】java.util.*可以导入util下的所有类,但尽量不要用。建议显式指定要导入的类名,不然容易出现冲突,如下面,两个包下都有Data类,会报错
java">import java.util.*;
import java.sql.*;
public class Test {public static void main(String[] args) {java.util.Date date = new java.util.Date();System.out.println(date.getTime());}
}
2. 自定义包
【如何自定义包】
【注】
- 使用包时要在最上方加“package 包名”指定该代码所在的包
- 包名一般以公司域名的颠倒形式命名,比如百度,com.baidu.demo
- 代码路径要和包名相同
- 若一个类没有package语句,则放在默认包中
3. 包访问权限(只在包内访问)
由上图可见,Demo类是包访问权限(没有访问修饰符修饰),Main和Demo不在同一个包,Demo1和Demo在同一个包
【代码演示】
java">public class Demo1 {public static void main(String[] args) {Demo demo = new Demo(); //编译成功}
}
public class Main {public static void main(String[] args) {Demo demo = new Demo(); //语法报错}
}
可见,在Main类中new一个Demo对象,由于是包访问权限,编译报错
4. 常用的包
- java.lang:常用的基础类(String,Object),jdk1.1后自动导入
- java.lang.reflect:反射编程
- java.sql:数据库开发
- java.net:网络编程开发
- java.io:IO编程开发
- java.util:提供工具程序,如集合类
2. 继承
继承是为了实现代码复用。比如写1篇文章,有些内容出自某个文献,为了少写字,只需要在文中说明自己去看文献即可,不用重复写。这里的文献就是父类,每篇作文会继承文献
2.1 继承的语法(子类和父类)
- 继承中,Animal类叫父类/基类/超类,Dog类和Cat类叫子类/派生类。子类会将父类中除构造方法外的所有成员都继承到子类中,被private修饰成员也会被继承
- 继承后,子类可以复用父类的成员(除构造方法),子类在实现时只需要关注新增的成员即可
【语法】
java">修饰符 class 子类 extends 父类 {// ...
}
【代码演示】
java">//父类
public class Animal {public String name;public int age;
}
//子类
public class Dog extends Animal {}
public class Cat extends Animal {}
验证继承:Dog类中没有任何成员,但却可以点出age和name,说明确实继承了
2.2 在子类中访问父类
【前提代码】
java">//父类
public class Animal {public String name;public int age;int a;public void barks() {System.out.println(this.name+ "汪汪");}public void method() {System.out.println("Animal");}
}
java">//子类
public class Dog extends Animal{int b = 12;public void method1() {System.out.println("Dog");}
}
1.子类 与 访问父类 成员名字不同
java">public class Test {public static void main(String[] args) {Dog dog = new Dog();//子类对象调用与父类 不同名的方法dog.method1(); //Dogdog.method(); //Animal//子类对象调用与父类 不同名的成员变量System.out.println(dog.a); //0System.out.println(dog.b); //12}
}
2. 子类 与 访问父类 成员同名 — super
修改前提代码的Dog类
java">public class Dog extends Animal{int b = 12;char a = 97;@Overridepublic void method() {System.out.println("Dog");}
}
【代码演示】
java">public class Test {public static void main(String[] args) {Dog dog = new Dog();//子类对象调用与父类 同名的方法dog.method(); // Dog//子类对象调用与父类 同名的变量System.out.println(dog.a); // a}
}
无论子类与父类有没有同名(不考虑数据类型)的成员,子类访问父类中的成员时,会优先访问子类自己的成员,子类没有则访问父类的
* 如何访问同名时的父类?
用super关键字
super的三种用法:super要在方法内部使用,且是非静态方法
- super.成员方法
- super.成员变量
- super() 构造方法
【代码演示】
java">public class Dog extends Animal{int b = 12;char a = 97;//super要在 方法内部 使用,且是 非静态方法//super.方法@Overridepublic void method() {//System.out.println("Dog");super.method();}//super.变量public void father() {super.a = 10;System.out.println(super.a);}//super() 父类的构造方法public Dog(int age) {//调用父类构造方法,对父类进行构造super(age);System.out.println("Dog构造方法");}
}
super()访问父类的构造方法有什么用?用于子类的构造(子类的构造方法)
2.3 子类的构造方法
如上面super()所说,子类在构造时需要先构造父类,具体方式为:在子类的构造方法中调用父类的构造方法来完成对父类的构造
【那为什么之前没有手动对父类构造,却没有报错?】
编译器在用户没有写时,编译器会自动在子类的构造方法中添加父类的不带参数的构造方法super()
【代码演示】
java">public class Animal {public String name;public int age;int a;public Animal(int age) {System.out.println("Animal构造方法");}
}
java">public class Dog extends Animal{int b = 12;char a = 97;//super() 父类的构造方法public Dog(int age) {super(age);System.out.println("Dog构造方法");}
}
java">public class Test {public static void main(String[] args) {Dog dog = new Dog(8); // Animal构造方法 Dog构造方法 }
}
【注】
- super()必须放在第一行
- 若父类构造方法不带参数,子类构造方法的第一行会有一个隐藏的super()
- 若父类构造方法带参数,子类构造方法的第一行需要手动在第一行调用super(…)
- super()只能在构造方法中被调用一次(子类的构造方法只被调用一次,那么super()也只有一次)
- super不能和this同时出现(this和super都要求放在第一行)
2.4 super和this
【相同点】
- 因为都依赖对象,需要在非静态方法中使用,只能访问非静态的成员
- 在构造方法中用super(…)和this(…)时,都必须放在第一行
【不同点】
- this是当前对象的引用,super是子类对象中从父类继承下来的部分的引用
- this访问当前类的成员,super访问当前类中继承下来的父类的成员
- this(…)调用本类的构造方法,super(…)调用父类的构造方法
- 在构造方法无参的条件下,用户不写super(…)编译器会默认隐含super(),this(…)不写则不会
2.5 protected 关键字
protected和private关键字不能定义类,语法上不支持
protected访问权限最大就是不同包下的子类
【代码演示】
2.6 继承后代码的执行顺序
类和对象中讲了类之间没有继承时代码块的执行顺序,现在看一下继承后
继承前:先执行静态代码,再执行实例,再构造方法
继承后:先执行父类的静态代码,后执行子类的静态代码,再执行父类的实例,构造方法,再执行子类的实例,构造方法
【代码演示】
java">class Parent{public String name = "父亲";static {System.out.println("父类的静态");}{System.out.println("父类的实例");}public Parent(){System.out.println("父类的构造方法");}
}
class Kid extends Parent{public int age;static {System.out.println("子类的静态");}{System.out.println("子类的实例");}public Kid(){super();System.out.println("子类的构造方法");}
}
public class TestStatic {public static void main(String[] args) {Kid kid = new Kid();}
}//运行结果父类的静态
子类的静态
父类的实例
父类的构造方法
子类的实例
子类的构造方法
2.7 继承的种类 — final关键字
- 单继承
- 不同类继承同一个类
- 多层继承,B类继承A类,C类继承B类
- 不支持多继承,C类不能既继承A类,又继承B类(接口解决了这个问题)
继承关系一般不要超过三层,为了从语法上限制,可使用final关键字,被final修饰的成员和类具有常量属性,无法再进行任何操作(不能继承,重写,修改)
【代码演示】
java">public final class Son extends Father {}
class Sonner extends Son{ //报错}
3. 组合
组合,它不是OOP语言的特点,指将一个类的实例作为另一个类的属性,数据结构中用组合比较多
【代码演示】
java">class Teacher{}
class Student{}
public class School{Teacher teacher;Student student;
}
4. 多态
多态,指在完成某个行为时,不同的对象会产生不同形态
4.1 产生多态的条件
- 两个类是继承关系
- 子类要重写父类的方法
- 通过父类的引用调用父类的重写方法
代码在运行时,传递不同的子类对象会调用子类的重写方法,因而产生不同的效果
【代码演示】子类重写父类的方法后再用父类对象调用,依旧调用的子类重写的方法
java">//父类
public class Animal {public String name;public int age;public void get(){System.out.println(this.name + " "+ this.age);}
}
//派生类
public class Dog extends Animal{int b = 12;@Overridepublic void get() {System.out.println("多态");}
}//测试类
public class Test {public static void sum(Animal animal){animal.get();}public static Animal ret(Dog dog){return dog;}public static void main(String[] args) {//方法一Dog dog = new Dog();dog.get();//多态//方法二 向上转型Animal animal = new Dog();animal.get();//多态//方法三 将对象作为参数Animal a = new Dog();Animal b = new Dog();sum(a);//多态sum(b);//多态//方法四ret(new Dog()).get();//多态}
}
为什么会这样?看下面
5. 多态的底层原理
5.1 重写和动态绑定
重写,指方法名,返回值类型,参数类型均相同,只修改方法内核。
若两个类是父子关系,返回值类型可以不相同。
访问修饰限定符必须是子类的权限≥父类的权限
【注】
- 被private修饰的方法无法被重写(只能在其所在的类内访问)
- 被static修饰的方法无法被重写(类方法是独属于某个类的方法)
- 被final修饰的方法无法被重写(具有常量属性的方法无法被再次修改)
- 构造方法无法重写
【重写和重载的区别】
- 重写:方法名相同,参数列表相同,返回值相同
- 重载:方法名相同,参数列表不同,返回值不做要求
* 动态绑定和静态绑定
- 动态绑定:编译时,根据用户传的实参类型就确定了具体调用哪个方法,如方法·重载
- 静态绑定:编译时,不能确定方法的行为,需等程序运行时才能确定具体调用哪个方法
注:
- 不要在构造方法中调用实例方法,容易出bug
- 动态绑定的发生不一定发生在向上转型,只要子类重写了父类的方法,无论有没有发生向上转型,都会发生动态绑定,调用子类重写的方法
5.2 向上转型和向下转型
1. 向上转型
父类的引用,引用子类的对象。换句话说,就是创建一个子类的对象,将其当成父类对象来用。父类类型可以引用子类对象,因为是从小范围向大范围转换。
优点:让代码实现更简单灵活
缺点:不能调到子类特有的方法
【代码演示】
Animal animal = new Dog();
【使用场景】
- 直接赋值
java">Animal animal = new Dog();
- 作方法参数
java"> public static void sum(Animal animal){animal.get();}Animal a = new Dog(); sum(a);//多态
- 作方法返回值
java">public static Animal ret(Dog dog){return dog;
}
ret(new Dog()).get();//多态
2. 向下转型
子类的引用,引用父类的对象,需要强转,因为是从大范围向小范围转换。
当子类向上转型当成父类使用后,再无法调用子类的方法,但有时有需要调用子类特有的方法,此时需要将父类引用再还原成子类对象,即向下转型
【代码演示】
java"> public static void main(String[] args) {Animal animal = new Dog();animal.barks();Dog dog = (Dog) animal;dog.barks();//错误写法Cat cat = (Cat) animal;cat.barks(); //抛异常//正确写法if(animal instanceof Cat){Cat cats = (Cat) animal;cats.barks();}else{System.out.println("该对象不是当前类的实例");}}
向下转型用的比较少,并不安全,一旦转换失败就抛异常。为了提高安全性,需要用instanceof
6. 多态的优缺点
6.1 多态优点
- 降低if else的圈复杂度
java">class Shape{public void draw(){}
}
class Rect extends Shape{@Overridepublic void draw() {System.out.println("画◇");}
}
class Cycle extends Shape{@Overridepublic void draw() {System.out.println("画○");}
}
class Flower extends Shape{@Overridepublic void draw() {System.out.println("画❀");}
}public class MyShape {//没用多态public static void main(String[] args) {Rect rect = new Rect();Cycle cycle = new Cycle();Flower flower = new Flower();String[] shapes = {"cycle","rect","cycle","rect","flower"};//圈复杂度for (String shape:shapes) {if (shape.equals("cycle")) {cycle.draw();} else if (shape.equals("rect")) {rect.draw();}else if(shape.equals("flower")){flower.draw();}}}//有多态public static void main1(String[] args) {Shape[] shapes = {new Rect(),new Cycle(),new Flower()};for (int i = 0; i < shapes.length; i++) {shapes[i].draw();}}
}
- 代码可扩展性强
想画别的图形只需要重新添加一个类即可,不用动原有代码
6.2 多态缺点
- 属性没有多态性,当向上转型后,只能用父类的属性,无法用子类的属性
- 构造方法没有多态性,构造方法不要调用重写的方法
java">class B{public B(){fun();}public void fun(){System.out.println("B.fun()");}
}
class D extends B{private int num = 1;@Overridepublic void fun() {System.out.println("D.fun()"+num);}
}public class Struct {public static void main(String[] args) {D d = new D();}
}//运行结果
//D.fun()0
【代码解析】
- D调用构造方法时,也调用了B的构造方法
- B的构造方法中调用了fun(),fun()又因为重写发生了动态绑定,只能调用到子类的fun(),但fun()里的num还未初始化,此时就会出现bug