封装继承多态(详解)

封装

封装的哲学思维:合理隐藏,合理暴露

封装最初的目的:提高代码的安全性和复用性,组件化

封装的步骤:

  1. 成员变量应该私有,用 private 修饰,只能在本类中直接访问
  2. 提供成套的 getter 和 setter 方法暴露成员变量的取值和赋值

使用 private 修饰成员变量的原因:实现数据封装,不想让别人使用修改你的数据,比较安全


this

this 关键字的作用:

  • this 关键字代表了当前对象的引用
  • this 出现在方法中:哪个对象调用这个方法 this 就代表谁
  • this 可以出现在构造器中:代表构造器正在初始化的那个对象
  • this 可以区分变量是访问的成员变量还是局部变量

static
基本介绍

Java 是通过成员变量是否有 static 修饰来区分是类的还是属于对象的

按照有无 static 修饰,成员变量和方法可以分为:

  • 成员变量:

    • 静态成员变量(类变量):static 修饰的成员变量,属于类本身,与类一起加载一次,只有一个,直接用类名访问即可
    • 实例成员变量:无 static 修饰的成员变量,属于类的每个对象的,与类的对象一起加载,对象有多少个,实例成员变量就加载多少个,必须用类的对象来访问
  • 成员方法:

    • 静态方法:有 static 修饰的成员方法称为静态方法也叫类方法,属于类本身的,直接用类名访问即可
    • 实例方法:无 static 修饰的成员方法称为实例方法,属于类的每个对象的,必须用类的对象来访问

static 用法

成员变量的访问语法:

  • 静态成员变量:只有一份可以被类和类的对象共享访问

    • 类名.静态成员变量(同一个类中访问静态成员变量可以省略类名不写)
    • 对象.静态成员变量(不推荐)
  • 实例成员变量:

    • 对象.实例成员变量(先创建对象)

成员方法的访问语法:

  • 静态方法:有 static 修饰,属于类

    • 类名.静态方法(同一个类中访问静态成员可以省略类名不写)
    • 对象.静态方法(不推荐,参考 JVM → 运行机制 → 方法调用)
  • 实例方法:无 static 修饰,属于对象

    • 对象.实例方法
    public class Student {
        // 1.静态方法:有static修饰,属于类,直接用类名访问即可!
        public static void inAddr(){ }
        // 2.实例方法:无static修饰,属于对象,必须用对象访问!
        public void eat(){}
        
        public static void main(String[] args) {
            // a.类名.静态方法
            Student.inAddr();
            inAddr();
            // b.对象.实例方法
            // Student.eat(); // 报错了!
            Student sea = new Student();
            sea.eat();
        }
    }
    

两个问题

内存问题:

  • 栈内存存放 main 方法和地址

  • 堆内存存放对象和变量

  • 方法区存放 class 和静态变量(jdk8 以后移入堆)

访问问题:

  • 实例方法是否可以直接访问实例成员变量?可以,因为它们都属于对象
  • 实例方法是否可以直接访问静态成员变量?可以,静态成员变量可以被共享访问
  • 实例方法是否可以直接访问实例方法? 可以,实例方法和实例方法都属于对象
  • 实例方法是否可以直接访问静态方法?可以,静态方法可以被共享访问
  • 静态方法是否可以直接访问实例变量? 不可以,实例变量必须用对象访问!!
  • 静态方法是否可以直接访问静态变量? 可以,静态成员变量可以被共享访问。
  • 静态方法是否可以直接访问实例方法? 不可以,实例方法必须用对象访问!!
  • 静态方法是否可以直接访问静态方法?可以,静态方法可以被共享访问!!

继承

基本介绍

继承是 Java 中一般到特殊的关系,是一种子类到父类的关系

  • 被继承的类称为:父类/超类
  • 继承父类的类称为:子类

继承的作用:

  • 提高代码的复用,相同代码可以定义在父类中
  • 子类继承父类,可以直接使用父类这些代码(相同代码重复利用)
  • 子类得到父类的属性(成员变量)和行为(方法),还可以定义自己的功能,子类更强大

继承的特点:

  1. 子类的全部构造器默认先访问父类的无参数构造器,再执行自己的构造器
  2. 单继承:一个类只能继承一个直接父类
  3. 多层继承:一个类可以间接继承多个父类(家谱)
  4. 一个类可以有多个子类
  5. 一个类要么默认继承了 Object 类,要么间接继承了 Object 类,Object 类是 Java 中的祖宗类

继承的格式:

子类 extends 父类{

}

子类不能继承父类的东西:

  • 子类不能继承父类的构造器,子类有自己的构造器
  • 子类是不能可以继承父类的私有成员的,可以反射暴力去访问继承自父类的私有成员
  • 子类是不能继承父类的静态成员,父类静态成员只有一份可以被子类共享访问,共享并非继承
public class ExtendsDemo {
    public static void main(String[] args) {
        Cat c = new Cat();
        // c.run();
        Cat.test();
        System.out.println(Cat.schoolName);
    }
}

class Cat extends Animal{
}

class Animal{
    public static String schoolName ="seazean";
    public static void test(){}
    private void run(){}
}

变量访问

继承后成员变量的访问特点:就近原则,子类有找子类,子类没有找父类,父类没有就报错

如果要申明访问父类的成员变量可以使用:super.父类成员变量,super指父类引用

public class ExtendsDemo {
    public static void wmain(String[] args) {
        Wolf w = new Wolf();w
        w.showName();
    }
}

class Wolf extends Animal{
    private String name = "子类狼";
    public void showName(){
        String name = "局部名称";
        System.out.println(name); // 局部name
        System.out.println(this.name); // 子类对象的name
        System.out.println(super.name); // 父类的
        System.out.println(name1); // 父类的
        //System.out.println(name2); // 报错。子类父类都没有
    }
}

class Animal{
    public String name = "父类动物名称";
    public String name1 = "父类";
}

方法访问

子类继承了父类就得到了父类的方法,可以直接调用,受权限修饰符的限制,也可以重写方法

方法重写:子类重写一个与父类申明一样的方法来覆盖父类的该方法

方法重写的校验注解:@Override

  • 方法加了这个注解,那就必须是成功重写父类的方法,否则报错
  • @Override 优势:可读性好,安全,优雅

子类可以扩展父类的功能,但不能改变父类原有的功能,重写有以下三个限制:

  • 子类方法的访问权限必须大于等于父类方法
  • 子类方法的返回类型必须是父类方法返回类型或为其子类型
  • 子类方法抛出的异常类型必须是父类抛出异常类型或为其子类型

继承中的隐藏问题:

  • 子类和父类方法都是静态的,那么子类中的方法会隐藏父类中的方法
  • 在子类中可以定义和父类成员变量同名的成员变量,此时子类的成员变量隐藏了父类的成员变量,在创建对象为对象分配内存的过程中,隐藏变量依然会被分配内存
public class ExtendsDemo {
    public static void main(String[] args) {
        Wolf w = new Wolf();
        w.run();
    }
}
class Wolf extends Animal{
    @Override
    public void run(){}//
}
class Animal{
    public void run(){}
}

常见问题
  • 为什么子类构造器会先调用父类构造器?

    1. 子类的构造器的第一行默认 super() 调用父类的无参数构造器,写不写都存在
    2. 子类继承父类,子类就得到了父类的属性和行为。调用子类构造器初始化子类对象数据时,必须先调用父类构造器初始化继承自父类的属性和行为
    3. 参考 JVM → 类加载 → 对象创建
    class Animal {
        public Animal() {
            System.out.println("==父类Animal的无参数构造器==");
        }
    }
    
    class Tiger extends Animal {
        public Tiger() {
            super(); // 默认存在的,根据参数去匹配调用父类的构造器。
            System.out.println("==子类Tiger的无参数构造器==");
        }
        public Tiger(String name) {
            //super();  默认存在的,根据参数去匹配调用父类的构造器。
            System.out.println("==子类Tiger的有参数构造器==");
        }
    }
    
  • 为什么 Java 是单继承的?

    答:反证法,假如 Java 可以多继承,请看如下代码:

    class A{
    	public void test(){
    		System.out.println("A");
    	}
    }
    class B{
    	public void test(){
    		System.out.println("B");
    	}
    }
    class C extends A , B {
    	public static void main(String[] args){
    		C c = new C();
            c.test(); 
            // 出现了类的二义性!所以Java不能多继承!!
    	}
    }
    

super

继承后 super 调用父类构造器,父类构造器初始化继承自父类的数据。

总结与拓展:

  • this 代表了当前对象的引用(继承中指代子类对象):this.子类成员变量、this.子类成员方法、this(…) 可以根据参数匹配访问本类其他构造器
  • super 代表了父类对象的引用(继承中指代了父类对象空间):super.父类成员变量、super.父类的成员方法、super(…) 可以根据参数匹配访问父类的构造器

注意:

  • this(…) 借用本类其他构造器,super(…) 调用父类的构造器
  • this(…) 或 super(…) 必须放在构造器的第一行,否则报错
  • this(…) 和 super(…) 不能同时出现在构造器中,因为构造函数必须出现在第一行上,只能选择一个
public class ThisDemo {
    public static void main(String[] args) {
        // 需求:希望如果不写学校默认就是”张三“!
        Student s1 = new Student("天蓬元帅", 1000 );
        Student s2 = new Student("齐天大圣", 2000, "清华大学" );
    }
}
class Study extends Student {
   public Study(String name, int age, String schoolName) {
        super(name , age , schoolName) ; 
       // 根据参数匹配调用父类构造器 
   }
}

class Student{
    private String name ;
    private int age ;
    private String schoolName ;

    public Student() {
    }
    public Student(String name , int age){
        // 借用兄弟构造器的功能!
        this(name , age , "张三");
    }
	public Student(String name, int age, String schoolName) {
        this.name = name;
        this.age = age;
        this.schoolName = schoolName;
    }
	// .......get + set
}

final
基本介绍

final 用于修饰:类,方法,变量

  • final 修饰类,类不能被继承了,类中的方法和变量可以使用
  • final 可以修饰方法,方法就不能被重写
  • final 修饰变量总规则:变量有且仅能被赋值一次

final 和 abstract 的关系是互斥关系,不能同时修饰类或者同时修饰方法


修饰变量
静态变量

final 修饰静态成员变量,变量变成了常量

常量:有 public static final 修饰,名称字母全部大写,多个单词用下划线连接

final 修饰静态成员变量可以在哪些地方赋值:

  1. 定义的时候赋值一次

  2. 可以在静态代码块中赋值一次

public class FinalDemo {
	//常量:public static final修饰,名称字母全部大写,下划线连接。
    public static final String SCHOOL_NAME = "张三" ;
    public static final String SCHOOL_NAME1;

    static{
        //SCHOOL_NAME = "java";//报错
        SCHOOL_NAME1 = "张三1";
    }
}

实例变量

final 修饰变量的总规则:有且仅能被赋值一次

final 修饰实例成员变量可以在哪些地方赋值 1 次:

  1. 定义的时候赋值一次
  2. 可以在实例代码块中赋值一次
  3. 可以在每个构造器中赋值一次
public class FinalDemo {
    private final String name = "张三" ;
    private final String name1;
    private final String name2;
    {
        // 可以在实例代码块中赋值一次。
        name1 = "张三1";
    }
	//构造器赋值一次
    public FinalDemo(){
        name2 = "张三2";
    }
    public FinalDemo(String a){
        name2 = "张三2";
    }

    public static void main(String[] args) {
        FinalDemo f1 = new FinalDemo();
        //f1.name = "张三1"; // 第二次赋值 报错!
    }
}

抽象类

基本介绍

父类知道子类要完成某个功能,但是每个子类实现情况不一样

抽象方法:没有方法体,只有方法签名,必须用 abstract 修饰的方法就是抽象方法

抽象类:拥有抽象方法的类必须定义成抽象类,必须用 abstract 修饰,抽象类是为了被继承

一个类继承抽象类,必须重写抽象类的全部抽象方法,否则这个类必须定义成抽象类

public class AbstractDemo {
    public static void main(String[] args) {
        Dog d = new Dog();
        d.run();
    }
}

class Dog extends Animal{
    @Override
    public void run() { 
        System.out.println("跑"); 
    }
}

abstract class Animal{
    public abstract void run();
}

常见问题

一、抽象类是否有构造器,是否可以创建对象?

  • 抽象类有构造器,但是抽象类不能创建对象,类的其他成分它都具备,构造器提供给子类继承后调用父类构造器使用
  • 抽象类中存在抽象方法,但不能执行,抽象类中也可没有抽象方法

抽象在学术上本身意味着不能实例化

public class AbstractDemo {
    public static void main(String[] args) {
        //Animal a = new Animal(); 抽象类不能创建对象!
        //a.run(); // 抽象方法不能执行
    }
}
abstract class Animal{
    private String name;
    public static String schoolName = "张三";
    public Animal(){ }

    public abstract void run();
    //普通方法
    public void go(){ }
}

二、static 与 abstract 能同时使用吗?

答:不能,被 static 修饰的方法属于类,是类自己的东西,不是给子类来继承的,而抽象方法本身没有实现,就是用来给子类继承


存在意义

被继承,抽象类就是为了被子类继承,否则抽象类将毫无意义(核心)

抽象类体现的是"模板思想":部分实现,部分抽象,可以使用抽象类设计一个模板模式

//作文模板
public class ExtendsDemo {
    public static void main(String[] args) {
        Student xiaoMa = new Student();
        xiaoMa.write();
    }
}
class Student extends Template{
    @Override
    public String writeText() {return "\t内容"}
}
// 1.写一个模板类:代表了作文模板。
abstract class Template{
    private String title = "\t\t\t\t\t标题";
    private String start = "\t开头";
    private String last = "\t结尾";
    public void write(){
        System.out.println(title+"\n"+start);
        System.out.println(writeText());
        System.out.println(last);
    }
    // 正文部分定义成抽象方法,交给子类重写!!
    public abstract String writeText();
}

接口

基本介绍

接口是 Java 语言中一种引用类型,是方法的集合。

**接口是更加彻底的抽象,**接口中只有抽象方法和常量,没有其他成分

 修饰符 interface 接口名称{
	// 抽象方法
	// 默认方法
	// 静态方法
	// 私有方法
}
  • 抽象方法:接口中的抽象方法默认会加上 public abstract 修饰,所以可以省略不写

  • 静态方法:静态方法必须有方法体

  • 常量:是 public static final 修饰的成员变量,仅能被赋值一次,值不能改变。常量的名称规范上要求全部大写,多个单词下划线连接,public static final 可以省略不写

    public interface InterfaceDemo{
        //public static final String SCHOOL_NAME = "张三";
    	String SCHOOL_NAME = "张三";
        
        //public abstract void run();
        void run();//默认补充
    }
    

实现接口

接口是用来被类实现的。

  • 类与类是继承关系:一个类只能直接继承一个父类,单继承
  • 类与接口是实现关系:一个类可以实现多个接口,多实现,接口不能继承类
  • 接口与接口继承关系:多继承
修饰符 class 实现类名称 implements 接口1,接口2,接口3,....{

}
修饰符 interface 接口名 extend 接口1,接口2,接口3,....{
    
}

实现多个接口的使用注意事项:

  1. 当一个类实现多个接口时,多个接口中存在同名的静态方法并不会冲突,只能通过各自接口名访问静态方法

  2. 当一个类实现多个接口时,多个接口中存在同名的默认方法,实现类必须重写这个方法

  3. 当一个类既继承一个父类,又实现若干个接口时,父类中成员方法与接口中默认方法重名,子类就近选择执行父类的成员方法

  4. 接口中,没有构造器,不能创建对象,接口是更彻底的抽象,连构造器都没有,自然不能创建对象

    public class InterfaceDemo {
        public static void main(String[] args) {
            Student s = new Student();
            s.run();
            s.rule();
        }
    }
    class Student implements Food, Person{
        @Override
        public void eat() {}
        
        @Override
        public void run() {}
    }
    interface Food{
        void eat();
    }
    interface Person{
        void run();
    }
    //可以直接 interface Person extend Food,
    //然后 class Student implements Person 效果一样
    

新增功能

jdk1.8 以后新增的功能:

  • 默认方法(就是普通实例方法)
    • 必须用 default 修饰,默认会 public 修饰
    • 必须用接口的实现类的对象来调用
    • 必须有默认实现
  • 静态方法
    • 默认会 public 修饰
    • 接口的静态方法必须用接口的类名本身来调用
    • 调用格式:ClassName.method()
    • 必须有默认实现
  • 私有方法:JDK 1.9 才开始有的,只能在本类中被其他的默认方法或者私有方法访问
public class InterfaceDemo {
    public static void main(String[] args) {
        // 1.默认方法调用:必须用接口的实现类对象调用。
        Man m = new Man();
        m.run();
        m.work();

        // 2.接口的静态方法必须用接口的类名本身来调用。
        InterfaceJDK8.inAddr();
    }
}
class Man implements InterfaceJDK8 {
    @Override
    public void work() {
        System.out.println("工作中。。。");
    }
}

interface InterfaceJDK8 {
    //抽象方法!!
    void work();
    // a.默认方法(就是之前写的普通实例方法)
    // 必须用接口的实现类的对象来调用。
    default void run() {
        go();
        System.out.println("开始跑步‍");
    }

    // b.静态方法
    // 注意:接口的静态方法必须用接口的类名本身来调用
    static void inAddr() {
        System.out.println("我们在武汉");
    }
    
    // c.私有方法(就是私有的实例方法): JDK 1.9才开始有的。
    // 只能在本接口中被其他的默认方法或者私有方法访问。
    private void go() {
        System.out.println("开始。。");
    }
}

对比抽象类
参数 抽象类 接口
默认的方法实现 可以有默认的方法实现 接口完全是抽象的,jdk8 以后有默认的实现
实现 子类使用 extends 关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。 子类使用关键字 implements 来实现接口。它需要提供接口中所有声明的方法的实现
构造器 抽象类可以有构造器 接口不能有构造器
与正常Java类的区别 除了不能实例化抽象类之外,和普通 Java 类没有任何区别 接口是完全不同的类型
访问修饰符 抽象方法有 publicprotecteddefault 这些修饰符 接口默认修饰符是 public,别的修饰符需要有方法体
main方法 抽象方法可以有 main 方法并且我们可以运行它 jdk8 以前接口没有 main 方法,不能运行;jdk8 以后接口可以有 default 和 static 方法,可以运行 main 方法
多继承 抽象方法可以继承一个类和实现多个接口 接口可以继承一个或多个其它接口,接口不可继承类
速度 比接口速度要快 接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法
添加新方法 如果往抽象类中添加新的方法,可以给它提供默认的实现,因此不需要改变现在的代码 如果往接口中添加方法,那么必须改变实现该接口的类

多态

基本介绍

多态的概念:同一个实体同时具有多种形式同一个类型的对象,执行同一个行为,在不同的状态下会表现出不同的行为特征

多态的格式:

  • 父类类型范围 > 子类类型范围
父类类型 对象名称 = new 子类构造器;
接口	  对象名称 = new 实现类构造器;

多态的执行:

  • 对于方法的调用:编译看左边,运行看右边(分派机制)
  • 对于变量的调用:编译看左边,运行看左边

多态的使用规则:

  • 必须存在继承或者实现关系
  • 必须存在父类类型的变量引用子类类型的对象
  • 存在方法重写

多态的优势:

  • 在多态形式下,右边对象可以实现组件化切换,便于扩展和维护,也可以实现类与类之间的解耦
  • 父类类型作为方法形式参数,传递子类对象给方法,可以传入一切子类对象进行方法的调用,更能体现出多态的扩展性与便利性

多态的劣势:

  • 多态形式下,不能直接调用子类特有的功能,因为编译看左边,父类中没有子类独有的功能,所以代码在编译阶段就直接报错了
public class PolymorphicDemo {
    public static void main(String[] args) {
        Animal c = new Cat();
        c.run();
        //c.eat();//报错  编译看左边 需要强转
        go(c);
        go(new Dog);   
    }
    //用 Dog或者Cat 都没办法让所有动物参与进来,只能用Anima
    public static void go(Animal d){}
    
}
class Dog extends Animal{}

class Cat extends Animal{
    public void eat();
    @Override
    public void run(){}
}

class Animal{
    public void run(){}
}

上下转型

基本数据类型的转换:

  1. 小范围类型的变量或者值可以直接赋值给大范围类型的变量
  2. 大范围类型的变量或者值必须强制类型转换给小范围类型的变量

引用数据类型的自动类型转换语法:子类类型的对象或者变量可以自动类型转换赋值给父类类型的变量

父类引用指向子类对象

  • 向上转型 (upcasting):通过子类对象(小范围)实例化父类对象(大范围),这种属于自动转换
  • 向下转型 (downcasting):通过父类对象(大范围)实例化子类对象(小范围),这种属于强制转换
public class PolymorphicDemo {
    public static void main(String[] args){
        Animal a = new Cat();	// 向上转型
        Cat c = (Cat)a;			// 向下转型
    }
}
class Animal{}
class Cat extends Animal{}

instanceof

instanceof:判断左边的对象是否是右边的类的实例,或者是其直接或间接子类,或者是其接口的实现类

  • 引用类型强制类型转换:父类类型的变量或者对象强制类型转换成子类类型的变量,否则报错
  • 强制类型转换的格式:类型 变量名称 = (类型)(对象或者变量)
    • 有继承/实现关1系的两个类型就可以进行强制类型转换,编译阶段一定不报错,但是运行阶段可能出现类型转换异常 ClassCastException
public class Demo{
    public static void main(String[] args){
		Aniaml a = new Dog();
		//Dog d = (Dog)a;
        //Cat c = (Cat)a; 编译不报错,运行报ClassCastException错误
        if(a instanceof Cat){
            Cat c = (Cat)a; 
        } else if(a instanceof Dog) {
            Dog d = (Dog)a;
        }
    }
}
class Dog extends Animal{}
class Cat extends Animal{}
class Animal{}

你可能感兴趣的:(java,开发语言)