面向对象OOP(下)

10 封装

10.1 为什么要封装

封装是为了保护数据,这里的数据是指对象的属性(不是方法的局部变量)。
保护数据中的保护是什么意思?保护是指对数据的读写权限。
保护是如何实现的?或者说封装是如何实现的?是通过访问修饰符实现的。

10.2 访问修饰符

访问修饰符共有四个:

  • public:公共的
  • protected:受保护的
  • 默认|default(什么都不写)
  • private:私有的

10.2.1 public

public修饰的属性没有访问限制,是最宽松的封装。
例如:

public class Person {
    public String name;//公有
}

name可以在任何地方被访问呢,也就是可以读和写

pubilc修饰类:类可以在任何地方被访问
public修饰属性和方法:属性和方法可以在任何地方被访问

10.2.2 private

private修饰的属性只能在类内被访问,类外无法访问,是最严格的封装。
例如:

public class Person {
    private String name;//私有
}

name属性只能在Person类的类内使用,类外无法使用。
private不能修饰类

private class Classinfo{  //报错,private不能修饰类

}
public class Student {

}

再看一个例子:

public class Student {
    int age;
    private class Classinfo{  //不报错,因为Cassinfo类是Student类的成员

    }
}

private可以修饰类的成员,无论类的成员是什么。

10.3 getter and setter

最典型封装是:共有方法封装私有属性
例如:

public class Person {
    private String name;  //私有的属性,类外不能访问
    private int money;  //私有的属性,类外不能访问

    public String getName() {  //共有的读方法,外界可以读取
        return name;
    }

    public void setName(String name) {  //共有的写方法,外界可以写入
        this.name = name;
    }

    public int getMoney() {  //共有的读方法,外界可以读取
        return money;
    }

    public void setMoney(int money) {  //共有的写方法,外界可以写入
        if(money<0){  //封装中可以验证数据有效性
            return;
        }
        this.money = money;
    }
}

11 包

11.1 包和目录的关系

  • 包:逻辑目录
    当我们定义一个包时,会在硬盘上创建与包对应的目录。
  • 目录:物理目录
    目录是我们在硬盘上创建的,用于分离管理文件的东西。
  • 逻辑目录和物理目录相同
    逻辑目录和物理目录是一一对应的关系。

11.2 类和文件的关系

类定义在文件中

  • 一个文件中只能有一个公有类,公有类的类名必须与文件名相同。
  • 一个文件中可以有多个非共有类。
  • 一个文件中的多个非公有类编译后每一个类是一个单独的class文件。

11.3 定义包

使用package定义包

package com.sunmer.teacher;

11.4 导入包

要使用某个包中的类,需要将类导入进来。

导入类:

import com.sunmer.oop.Person;//导入了Person类
import com.sunmer.oop.*;//导入了oop包下的所有类

特别强调
包命名必须小写

11.5 default

默认访问修饰符是指什么都不写。

例如:

class Student {//Student类是默认访问修饰符
    String name; //name是默认访问修饰符
    Student(String name){ //构造函数是默认访问修饰符
        
    }
    void sayHello(){//sayHello方法是默认访问修饰符
        
    }
}

默认访问修饰符修饰的类,属性,方法可以在同包中的类访问。
default可以修饰类,修饰属性,方法可以。

12 static

12.1 static的作用

在同一个类的对象中共享数据。

12.2 static与instance的区别

static:静态(属于类,只有一份)
instance:实例(实例也叫对象,就是new出来的堆的内存空间,实例是每个对象专有的,每new一次就分配一次内存)

  • 实例变量是在new类时在堆中分配内存的。
  • 构造函数可以为实例属性初始化。构造函数不会为静态属性初始化。
  • 由于静态属性是所有对象共有的,所有对象不就是类吗,因此静态属性也称为类属性,或者类变量,或者类成员。
  • 既然静态属性属于类,不属于某个具体的对象,因此在new对象时,不会给静态属性分配内存。那静态时什么时候分配内存呢?
  • 当在程序运行期间,首次出现类名时,会暂时停止程序运行,去为类的静态属性分配内存,然后继续运行。
  • 静态变量被分配在方法区中,常驻内存,永不回收。静态变量只有一份。相当于c语言的全局变量。
  • 静态变量由类名操作,由类名赋值,取值。
  • 类名调用静态属性时,可以省略类名。

12.2.1 static的加载过程。

  • 首次出现类名时,为类的静态属性分配内存

12.2.2 instance的加载过程

  • new的时候分配内存。

12.3 static变量

static可以修饰变量,称为静态变量。

12.4 static方法

static可以修饰方法,称为静态方法。

12.5 static块

还可以定义static块
类名首次出现时,先为静态变量分配内存,然后调用静态块,静态块可以为静态变量初始化。还可以干别的事情。
静态块只在类名首次出现时调用一次,以后再也不调用了。

例如:

public class Demo{
    //构造为实例变量初始化
    public Demo(int i){
        this.i = i;
    }
    //静态块,为static变量初始化,静态块在首次类名出现时调用,但后于分配静态变量。
    static{
        age = 20;
    }
    static int age=0;
    int i;
}

13 继承

13.1 为什么要继承

  • 继承的出现提高了代码的复用性,提高软件开发效率。
  • 继承的出现让类与类之间产生了关系,提供了多态的前提。

13.2 继承的定义格式

在程序中,如果想声明一个类继承另一个类,需要使用extends关键字。

class 子类 extends 父类 {
    
}

13.3 使用继承

代码编写步骤:
第一步:重构父类构造

  • 首先为父类添加构造,父类的构造为父类中定义的属性初始化
  • 子类定义的特有属性不是父类负责初始化的。
    public Deparment(int ID, String name, int amount, String responsibility, String manager) {
        super();
        this.ID = ID;
        this.name = name;
        this.amount = amount;
        this.responsibility = responsibility;
        this.manager = manager;
    }

第二步:重构子类构造

  • 子类构造应该获得所有属性的初始值,本例中共6个属性,其中5个是父类的,1个是自己的
  • 子类构造的第一行必须是调用父类构造,
  • 如果不写super(),那么默认再子类第一行添加调用父类无参的构造super()
  • 子类再调用父类构造代码super()的下面负责给自己的属性赋值
    public PersonalDepartment(int ID, String name, int amount, String responsibility, String manager,int count) {
        super(ID,name,amount,responsibility,manager);
        this.count = count;
    }

第三步:重构实例化子类对象

  • 再测试类中new子类时,要为子类传入属性的参数
PersonalDepartment personalDepartment = new PersonalDepartment(110,"人事部",5,"负责招聘","卡卡",10);

第四步:测试

13.4 继承的注意事项

13.4.1 类只支持单继承,不允许多继承

class A{} 
class B{}
class C extends A,B{}  // C类不可以同时继承A类和B类

13.4.2 多个类可以继承一个父类

class A{}
class B extends A{}
class C extends A{}   // 类B和类C都可以继承类A

13.4.3 允许多层继承

class A{}
class B extends A{}   // 类B继承类A,类B是类A的子类
class C extends B{}   // 类C继承类B,类C是类B的子类,同时也是类A的子类

13.5 继承的体系 之 Object类

Object是所有类的父类。
如果一个类没有显示定义父类,那么默认继承Object类。
Object类中没有定义属性,但是定义了12个方法,并且这些方法都是实例方法。因此每个对象都拥有这14个方法。

序号 方法签名 说明
1 String toString() 返回当前对象本身的有关信息,返回字符串对象
2 boolean equals(Object) 比较两个对象是否是同一个对象,若是,返回true
3 Object clone() 生成当前对象的一个副本,并返回
4 int hashCode() 返回该对象的哈希码值
5 void finalize() 在垃圾收集器将对象从内存中清除出之前做必要的清理工作。
6 void wait() 线程等待
7 void wait(int) 线程等待
8 void wait(long,int) 线程等待
9 void notify() 线程唤醒
10 void notifyall() 线程唤醒
11 Class getClass() 获取类结构信息,返回Class对象

13.6 继承后子类的成员的变化

了解了继承给我们带来的好处,提高了代码的复用性。继承让类与类或者说对象与对象之间产生了关系。那么,当继承出现后,类的成员之间产生了那些变化呢?
子类中的成员包括:

  • 子类自己定义的属性和方法
  • 从父类继承的属性和方法
  • 子类不能使用从父类继承的私有成员

13.7 继承中的this(调用子类成员)

  • this可以调用子类成员。
  • this可以调用子类重载的构造函数。

13.8 继承中的super(调用父类成员)

  • super调用父类成员。
  • super调用父类构造函数,必须是子类构造函数的第一行

13.9 方法重写(方法覆盖|override)

方法重写的注意事项:

  • 子类方法覆盖父类方法,必须要保证权限大于等于父类权限。
class Fu(){ 
    void show(){}
    public void method(){}
}
class Zi() extends Fu{
    public void show(){}  //扩大show的访问权限,编译运行没问题
    void method(){}       //缩小method的访问权限,编译错误
}
  • 方法的返回值类型 方法名 参数列表都要一样。

14 接口

14.1 接口与定义规范

与定义类的class不同,接口定义时需要使用interface关键字。

接口定义语法格式:

public interface 接口名 {
    抽象方法1;
    抽象方法2;
    抽象方法3;
}

定义接口

public interface IShout {

}

定义接口就是定义规范,接口中的抽象方法就是具体的规范。

14.2 实现类的接口

接口实现语法格式

class 类 implements 接口 {
    重写接口中方法
} 

在类实现接口后,该类就会将接口中的抽象方法继承过来,此时该类需要重写该抽象方法,完成具体的逻辑。

14.3 接口的多实现

interface Fu1
{
    void show1();
}
interface Fu2
{
    void show2();
}
class Zi implements Fu1,Fu2// 多实现。同时实现多个接口。
{
    public void show1(){}
    public void show2(){}
}

14.4 类继承类同时实现接口

class Fu {
    public void show(){}
}
interface Inter {
    pulbic abstract void show1();
}
class Zi extends Fu implements Inter {
    public void show1() {
    }
}

接口的出现避免了单继承的局限性。父类中定义的事物的基本功能。接口中定义的事物的扩展功能。

14.5 接口的规定

  1. 接口中可以定义变量,但是变量必须有固定的修饰符修饰,public static final 所以接口中的变量也称之为常量,其值不能改变。
  2. 接口中可以定义方法,方法也有固定的修饰符,public abstract
  3. 接口不可以new,不可以实例化。
  4. 子类必须覆盖掉接口中所有的抽象方法后,子类才可以实例化。否则子类是一个抽象类。
  5. 接口不能继承类
  6. 接口可以继承接口
  7. 接口没有最高层,类的最高层是Object
  8. 接口中不允许有普通方法,所以接口中的方法都是抽象(除了default方法和static方法)
  9. 接口不是类,所以没有构造

14.6 接口的继承

interface Fu1{
    void show();
}
interface Fu2{
    void show1();
}
interface Fu3{
    void show2();
}
interface Zi extends Fu1,Fu2,Fu3{
    void show3();
}

15 多态

15.1 什么是多态

多态就是多种形态,多种形式。
多态的实现方法有两种

  • 方法重写(公认的多态)
  • 方法重载(非公认的多态)

15.2 多态的语法格式

多态的定义格式:就是父类的引用变量指向子类对象

父类类型  变量名 = new 子类类型();
变量名.方法名();
Pet pet = new Cat();
pet.toString();
  • 普通类多态定义的格式
父类 变量名 = new 子类();

例如:

class Fu {}
class Zi extends Fu {}
Fu f = new Zi();//类的多态使用
  • 抽象类多态定义的格式
抽象类 变量名 = new 抽象类子类();

例如:

abstract class Fu {
     public abstract void method();
}
class Zi extends Fu {
    public void method(){
        System.out.println(“重写父类抽象方法”);
    }
}
Fu fu= new Zi();//类的多态使用
  • 接口多态定义的格式
接口 变量名 = new 接口实现类();

例如:

interface Fu {
    public abstract void method();
}
class Zi implements Fu {
    public void method(){
         System.out.println(“重写接口抽象方法”);
    }
}
Fu fu = new Zi();//接口的多态使用
  • 注意事项
    同一个父类的方法会被不同的子类重写。在调用方法时,调用的为各个子类重写后的方法。
Person p1 = new Student();
Person p2 = new Teacher();
p1.work(); //p1会调用Student类中重写的work方法
p2.work(); //p2会调用Teacher类中重写的work方法

当变量名指向不同的子类对象时,由于每个子类重写父类方法的内容不同,所以会调用不同的方法。

15.3 多态的转型

多态的转型分为向上转型与向下转型两种:

  • 向上转型就是父类引用指向子类对象
  • 向下转型就是子类引用指向父类对象

15.3.1 向上转型

向上转型:当有子类对象赋值给一个父类引用时,便是向上转型,多态本身就是向上转型的过程。
使用格式:

父类类型  变量名 = new 子类类型();

例如:

Person p = new Student();

15.3.2 向下转型

向下转型:一个已经向上转型的子类对象可以使用强制类型转换的格式,将父类引用转为子类引用,这个过程是向下转型。如果是直接创建父类对象,是无法向下转型的!
使用格式:

子类类型 变量名 = (子类类型) 父类类型的变量;

例如:

Student stu = (Student) p;  //变量p 实际上指向Student对象

15.4 多态的好处和弊端

当父类的引用指向子类对象时,就发生了向上转型,即把子类类型对象转成了父类类型。向上转型的好处是隐藏了子类类型,提高了代码的扩展性。
但向上转型也有弊端,只能使用父类共性的内容,而无法使用子类特有功能,功能有限制。看如下代码:

//描述动物类,并抽取共性eat方法
abstract class Animal {
    abstract void eat();
}
 
// 描述狗类,继承动物类,重写eat方法,增加lookHome方法
class Dog extends Animal {
    void eat() {
        System.out.println("啃骨头");
    }

    void lookHome() {
        System.out.println("看家");
    }
}

// 描述猫类,继承动物类,重写eat方法,增加catchMouse方法
class Cat extends Animal {
    void eat() {
        System.out.println("吃鱼");
    }

    void catchMouse() {
        System.out.println("抓老鼠");
    }
}

public class Test {
    public static void main(String[] args) {
        Animal a = new Dog(); //多态形式,创建一个狗对象
        a.eat(); // 调用对象中的方法,会执行狗类中的eat方法
        // a.lookHome();//使用Dog类特有的方法,需要向下转型,不能直接使用
        
        // 为了使用狗类的lookHome方法,需要向下转型
        // 向下转型过程中,可能会发生类型转换的错误,即ClassCastException异常
        // 那么,在转之前需要做健壮性判断 
        if( !a instanceof Dog){ // 判断当前对象是否是Dog类型
                System.out.println("类型不匹配,不能转换"); 
                return; 
        } 
        Dog d = (Dog) a; //向下转型
        d.lookHome();   //调用狗类的lookHome方法
    }
}

16 面向接口编程

  1. 接口作为子类实现
  2. 接口作为方法参数
  3. 接口作为方法返回值
  4. 接口作为类属性

16.1 接口作为子类的实现

interface A{
    void method();//1:定义规范
}
class B implements A{
    public void method(){//2:实现规范
        
    }
}

接口作为子类实现使用时,接口用于定义规范,子类用于实现规范。

16.2 接口作为方法参数

interface A{
    void sound();
}
class B implements A{
    public void sound(){
        System.out.println("b sound is running")
    }
}
class C implements A{
    public void sound(){
        System.out.println("c sound is running")
    }
}
class Test {
    //参数a是接口类型
    public static  void call(A a){//可以传入任何实现了A接口的子类实例,表现出多态的特性
        a.sound();
    }
    public static void main(String args[]){
        A a = new B();
        Test.call(a);
          a = new C();
        Test.call(a);
    }
}
  • 接口作为方法参数时,可以传入任何实现了该接口的子类实例,接口对象调用方法时,调用的是传入子类的方法。
  • 这种设计的原理是里氏替换原则,即父类引用指向子类实例。

16.3 接口作为方法返回值

interface A{
    void sound();
}
class B implements A{
    public void sound(){
        System.out.println("b sound is running")
    }
}
class C implements A{
    public void sound(){
        System.out.println("c sound is running")
    }
}
class Test {
    public static A getInstance(String type){//方法返回值是A接口,那么可以返回任何实现该接口的子类实例
        A a = null;//定义接口对象
        if(type==null){
            throw new RuntimeException("非法参数异常")
        }
        if(type.equalsIgnoreCase("b")){
            return new B();
        }else if(type.equalsIgnoreCase("c")){
            return new C();
        }else{
            return null;
        }
    }
    public static void main(String args[]){
        A a = Test.getInstance("b");
        a = Test.getInstance("c");
    }
}
  • 接口作为方法返回值时,可以返回任何实现了该接口的子类实例,接口对象调用方法时,调用的是传入子类的方法。
  • 这种设计的原理是里氏替换原则,即父类引用指向子类实例。

16.4 接口作为属性

interface A{
    void sound();
}
class B implements A{
    public void sound(){
        System.out.println("b sound is running")
    }
}
class C implements A{
    public void sound(){
        System.out.println("c sound is running")
    }
}
class D{
    private A a = null;//接口A作为类D的属性,可以传入任何实现A接口的子类对象
    public void setA(A a){
        this.a = a;
    }
    public A getA(){
        return this.a;
    }
}
class Test {
    public static void main(String args[]){
        D d = new D();
        A a = new B();//创建了A接口的子类B对象
        d.setA(a);//A接口的子类B对象 传入给 D类的属性a;
        d.getA().sound();//调用的是B的sound()
        
          a = new C();//创建了A接口的子类C对象
        d.setA(a);//A接口的子类C对象 传入给 D类 的属性a;
        d.getA().sound();//调用的是C的sound()
        
    }
}
  • 接口作为类的属性时,可以传入任何实现了该接口的子类实例,接口对象调用方法时,调用的是传入子类的方法。
  • 这种设计的原理是里氏替换原则,即父类引用指向子类实例。

面向接口编程的核心是:父类引用指向子类对象,换个角度来理解就是凡是使用父类的地方都可以使用子类替换父类。

17 内部类和匿名类

17.1 内部类

介绍
一个类的内部又完整的嵌套了另一个类结构。被嵌套的类称为内部类(inner class),嵌套其他类的类称为外部类(outer class)。

当一个类只在另外一个类的内部使用,不会在其他类中使用时,应该定义为内部类。

提示:类的成员包括(属性、方法、代码块、内部类)。

语法

class Outer{ //外部类
    class Inner{ //内部类
    }
}

分类

  • 实例内部类
  • 静态内部类

代码
实例内部类示例:

class Outer{ 
    class Inner{ //实例内部类,没有用static修饰的内部类
    }
}

实例内部类在实例外部类对象时分配内存,要想使用实例内部类对象,就需要先实例化外部类对象。

例如:

class Outer{
    class Inner{ //实例内部类,没有用static修饰的内部类
        public void innerClassMethod(){
            System.out.println("innerClassMethod");
        }
    }
}
public class InnerClassDemo {
    public static void main(String[] args) {
        //分开写的写法
        Outer outer = new Outer();
        Outer.Inner inner1 = outer.new Inner();
        inner1.innerClassMethod();
        //合并的写法
        Outer.Inner inner2 = new Outer().new Inner();
        inner2.innerClassMethod();

    }
}

静态内部类示例:

class Outer{ 
    static class Inner{ //实例内部类,没有用static修饰的内部类
    }
}

静态内部类是在外部类首次出现时分配内存的,使用外部类就可以直接创建出内部类的对象,不需要实例化外部类。

例如:

class Outer{
    static class Inner{ //静态内部类,使用static修饰的内部类
        public void innerClassMethod(){
            System.out.println("innerClassMethod");
        }
    }
}
public class InnerClassDemo {
    public static void main(String[] args) {
        //创建静态内部类的实例
        Outer.Inner inner = new Outer.Inner();
        inner.innerClassMethod();
    }
}

17.2 匿名类

介绍

  • 匿名内部类又叫匿名类,匿名类没有类名,我们可以在实例化时声明一个匿名类。

创建方式

  • 匿名类使用接口或抽象类创建

场景

  • 匿名类仅适用于只会使用一次的场景,匿名类不可复用。

语法

接口或抽象类 对象名 = new 接口或抽象类(){
    //实现接口或抽象类中的所有方法
    @override
    public void 方法名(){
        
    }
}

代码

interface Programmer {
    void develop();
}

public class TestAnonymousClass {
    public static void main(String[] args) {
        Programmer anotherProgrammer = new Programmer() {
            @Override
            public void develop() {
                System.out.println("我是在方法中实现了接口的匿名内部类");
            }
        };
        anotherProgrammer.develop();
    }
}

运行结果:
我是在方法中实现了接口的匿名内部类

匿名类也可以使用抽象类,把接口

interface Programmer {
    void develop();
}

改为抽象类

abstract class Programmer {
    abstract void develop();
}

也可以

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容