目录
OOP语言的三大主要特点:封装,继承,多态。
1. 封装
封装简单来说就是套壳屏蔽细节,让类的使用者只关注类的功能,而不关注类内的细节。比如笔记本电脑,只需要知道怎么用,而不需要知道电脑内部零件
1.1 封装的语法
//封装前
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类,会报错
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在同一个包
【代码演示】
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修饰成员也会被继承
- 继承后,子类可以复用父类的成员(除构造方法),子类在实现时只需要关注新增的成员即可
【语法】
修饰符 class 子类 extends 父类 {
// ...
}
【代码演示】
//父类
public class Animal {
public String name;
public int age;
}
//子类
public class Dog extends Animal {
}
public class Cat extends Animal {
}
验证继承:Dog类中没有任何成员,但却可以点出age和name,说明确实继承了
2.2 在子类中访问父类
【前提代码】
//父类
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");
}
}
//子类
public class Dog extends Animal{
int b = 12;
public void method1() {
System.out.println("Dog");
}
}
1.子类 与 访问父类 成员名字不同
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
//子类对象调用与父类 不同名的方法
dog.method1(); //Dog
dog.method(); //Animal
//子类对象调用与父类 不同名的成员变量
System.out.println(dog.a); //0
System.out.println(dog.b); //12
}
}
2. 子类 与 访问父类 成员同名 — super
修改前提代码的Dog类
public class Dog extends Animal{
int b = 12;
char a = 97;
@Override
public void method() {
System.out.println("Dog");
}
}
【代码演示】
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() 构造方法
【代码演示】
public class Dog extends Animal{
int b = 12;
char a = 97;
//super要在 方法内部 使用,且是 非静态方法
//super.方法
@Override
public 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()
【代码演示】
public class Animal {
public String name;
public int age;
int a;
public Animal(int age) {
System.out.println("Animal构造方法");
}
}
public class Dog extends Animal{
int b = 12;
char a = 97;
//super() 父类的构造方法
public Dog(int age) {
super(age);
System.out.println("Dog构造方法");
}
}
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 继承后代码的执行顺序
类和对象中讲了类之间没有继承时代码块的执行顺序,现在看一下继承后
继承前:先执行静态代码,再执行实例,再构造方法
继承后:先执行父类的静态代码,后执行子类的静态代码,再执行父类的实例,构造方法,再执行子类的实例,构造方法
【代码演示】
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修饰的成员和类具有常量属性,无法再进行任何操作(不能继承,重写,修改)
【代码演示】
public final class Son extends Father {
}
class Sonner extends Son{ //报错
}
3. 组合
组合,它不是OOP语言的特点,指将一个类的实例作为另一个类的属性,数据结构中用组合比较多
【代码演示】
class Teacher{
}
class Student{
}
public class School{
Teacher teacher;
Student student;
}
4. 多态
多态,指在完成某个行为时,不同的对象会产生不同形态
4.1 产生多态的条件
- 两个类是继承关系
- 子类要重写父类的方法
- 通过父类的引用调用父类的重写方法
代码在运行时,传递不同的子类对象会调用子类的重写方法,因而产生不同的效果
【代码演示】子类重写父类的方法后再用父类对象调用,依旧调用的子类重写的方法
//父类
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;
@Override
public 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();
【使用场景】
- 直接赋值
Animal animal = new Dog();
- 作方法参数
public static void sum(Animal animal){
animal.get();
}
Animal a = new Dog();
sum(a);//多态
- 作方法返回值
public static Animal ret(Dog dog){
return dog;
}
ret(new Dog()).get();//多态
2. 向下转型
子类的引用,引用父类的对象,需要强转,因为是从大范围向小范围转换。
当子类向上转型当成父类使用后,再无法调用子类的方法,但有时有需要调用子类特有的方法,此时需要将父类引用再还原成子类对象,即向下转型
【代码演示】
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的圈复杂度
class Shape{
public void draw(){
}
}
class Rect extends Shape{
@Override
public void draw() {
System.out.println("画◇");
}
}
class Cycle extends Shape{
@Override
public void draw() {
System.out.println("画○");
}
}
class Flower extends Shape{
@Override
public 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 多态缺点
- 属性没有多态性,当向上转型后,只能用父类的属性,无法用子类的属性
- 构造方法没有多态性,构造方法不要调用重写的方法
class B{
public B(){
fun();
}
public void fun(){
System.out.println("B.fun()");
}
}
class D extends B{
private int num = 1;
@Override
public 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