首页 > 编程语言 >Java小白一文带你回顾复习Java中的三大特性

Java小白一文带你回顾复习Java中的三大特性

时间:2024-08-23 22:54:18浏览次数:12  
标签:Java 复习 super 子类 类型 父类 方法 public 三大

封装

封装就是把抽象出的数据(属性)和对数据的操作(方法)封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作(方法),才能对数据进行操作。

实现步骤

  • 将属性进行私有化private

  • 提供一个公共的set方法,用于对属性判断并赋值

    • public void setXXX(类型 参数){
      	属性 = 参数名
      }
      
  • 提供一个公共的get方法,用于获取属性的值

    • public XX getXX(){
          return xx
      }
      

顺带一提,快捷键alt+R第一次来跑一段代码的时候,因为没有设置主类,所以跑的可能是上一个程序,此时只需要先用鼠标右键run一次,界面的右上角的主类就会变化为当前的类,之后就可以直接用快捷键跑了;

继承

d目的:解决代码的复用性问题,让编程更加接近人类思维。当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends来声明父类即可
    
子类又叫派生类,父类又叫超类、基类
    

在这里插入图片描述

继承相关的细节问题

  • 子类继承了所有的属性和方法,非私有的属性和方法可以直接访问,但是私有属性和方法不能在子类直接访问,要通过父类提供的公共的方法去访问

  • 子类必须调用父类的构造器,完成父类的初始化

    • 补充一点,这里涉及到了super(),而super()其实是默认存在的,它的存在默认调用父类的无参构造器
  • 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总是会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用super去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过。

    • public class Parent {
          // 带参数的构造器
          public Parent(String message) {
              System.out.println("Parent constructor with message: " + message);
          }
      
          // 无参构造器
          public Parent() {
              System.out.println("Parent default constructor");
          }
      }
      
      public class Child extends Parent {
          // 子类的无参构造器
          public Child() {
              // 默认会调用父类的无参构造器
              System.out.println("Child default constructor");
          }
      
          // 子类的带参数构造器
          public Child(String message) {
              // 需要显式调用父类的带参数构造器,否则会默认调用父类的无参构造器
              super(message);//这一步如果不显式地说明则会编译不通过
              System.out.println("Child constructor with message: " + message);
          }
      }
      
      public class Main {
          public static void main(String[] args) {
              // 创建子类对象,调用子类的无参构造器
              Child child1 = new Child();
              
              // 创建子类对象,调用子类的带参数构造器
              Child child2 = new Child("Hello");
          }
      }
      //输出结果
      Parent default constructor
      Child default constructor
      Parent constructor with message: Hello
      Child constructor with message: Hello
      
      
  • 如果希望指定去调用父类的某个构造器,则显式地调用一下: super(参数列表)

  • super在使用时,必须放在构造器第一行(super只能在构造器中使用)

  • super()和this()都只能放在构造器第一行,因此这两个方法不能共存在一个构造器中

  • Java中所有类都是Object类的子类,Object是所有类的基类

  • 父类构造器的调用不限于直接父类,将一直往上追溯直到Object类(顶级父类)

  • 子类最多只能继承一个父类(指直接继承),即Java中是单继承机制

    • 思考:如何让A类继承B类和C类? A继承B,B继承C即可
  • 不能滥用继承,子类和父类之间必须满足is-a的逻辑关系

继承本质详解

子类创建的内存布局,务必清楚

在这里插入图片描述
在这里插入图片描述

class A{
   A(){
       System.out.println("a");
   } 
   A(String name){
       System.out.println("a name");
   }
}
class B extend A{
    B(){
        //此中有this就没super的哈
        this("abc");
        System.out.println("b");
    }
    B(String name){
        //super()这里潜藏着一个super的哈
        System.out.println("b name");
    }
}
main方法中,B b = new B();会输出什么呢?
    result:    //   a     b name     b
解析:this.abc先调用本类中的带参数构造器,于是跳到B(String name),在这个构造器中首行,有个默认的super(),会去调用父类A的无参构造器,于是先输出a,然后回去,输出b name,最后B(String name)执行完后,回到B的无参构造器中this("abc")之后,打印出b
    //此中的重点就是你要分析出来B(String name)中隐藏的super(),而B中无参构造器中有this就没super了

在这里插入图片描述

所有的构造器,如果没有显式地声明super()或this()之类的,务必记住默认会有个super(),默认调用父类的无参构造器

super关键字

//1.访问父类的属性,但不能访问父类的private属性     super.属性名
//2.访问父类的方法,不能访问父类的private方法       super.方法名(参数名)
//3.访问父类的构造器(之前也提到过),super(参数列表);  只能放在构造器的第一句,只能出现一句!

super 给编程带来的便利
    1.调用父类构造器的好处(分工明确,父类属性由父类初始化,子类属性由子类初始化)
    2.当子类中有和父类中的成员(属性和方法)重名时,为了访问父类的成员,必须通过super。
      如果没有重名,使用super、this、直接访问是一样的效果	
    3.super的访问不限于直接父类,如果爷爷类和本类中有同名的成员,也可以使用super去访问爷爷类的成员;
      如果多个基类(上级类)中都有同名的成员,使用super访问遵循就近原则。   A->B—>C 当然也需要遵守访问权限等相关规则

super和this的区别

在这里插入图片描述

练习题 强烈建议走一遍

在这里插入图片描述

方法重写/覆盖

方法覆盖(重写)就是子类有一个方法,和父类的某个方法的名称、返回类型、参数一样,那么我们就说子类的这个方法覆盖了父类的那个方法。 注意:这里的子类父类不一定是直接的父子关系哈,祖孙关系也算的哈

/**
	1.子类方法的形参列表、方法名称、要和父类方法的参数、方法名称完全一样
	2.子类方法的返回类型和父类方法返回类型一样,或者是父类返回类型的子类
		比如父类的返回类型是Object ,子类方法返回类型是String  ,此中String是Object 的子类,所以也是允许的
		Dad类: public Object m(){}     
		Son类: public String m(){}   
         这两也是构成重写的
	3.子类方法不能缩小父类方法的访问权限    public > protected > 默认 > private 

重写和重载的区别

在这里插入图片描述

多态*

多态的前提是:两个对象存在继承关系

具体体现:
    	1.方法重载、方法重写体现多态 
    	2.对象的多态(核心)
/**重要的几句话:
   1.一个对象的编译类型和运行类型可以不一致
  		 	Animal animal = new dog();    animal的编译类型是Animal 运行类型是dog;
   			animal = new cat();    animal的编译类型还是Animal 但运行类型此时变为了cat;
   2.编译类型在定义对象时,就确定了,不能改变
   3.运行类型是可以改变的
   4.编译类型看定义时 = 号的左边,运行类型看 = 号的右边
 */
多态注意事项及细节讨论
/**
	1.多态的前提是:两个对象(类)存在继承关系
	2.多态的向上转型:
		2.1本质:父类的引用指向了子类的对象
		2.2语法: 父类类型  引用名 = new 子类类型();
		2.3特点:编译类型看左边,运行类型看右边
			可以调用父类中的所有成员(需遵守访问权限)
					---比如你把原先父类的一个eat方法由public改为private,那么就无法通过animal.eat()来调用;
			不能调用子类中特有成员
					---比如Cat类中有个特有的catchMouse的方法,animal.catMouse();这行代码就会报错;
					---这是因为在编译阶段,能调用哪些成员,是由编译类型来决定的
			最终运行效果看子类(运行类型)的具体实现,即调用方法时,按照从子类开始查找方法,然后调用,规则之前已
             经提到过,就是从子类出发,如果没有找到所指定的方法,则层层往上找;
    				---animal.eat();//这行代码首先去看animal的运行类型eat中去找有没有eat()这个方法,发现Cat这个类重写
    				   了eat()方法,小猫吃鱼,那么就会输出小猫吃鱼,反之则到父类animal中去执行其eat()方法,输出“动物
    				   吃食物";
    				---animal.run();//这行代码也是一样,但此时发现Cat类中找不到run()这个方法,那么此时就会到父类Animal
    				   类中去找到run()这个方法去调用,输出"动物在奔跑";
     3.多态的向下转型:
     	 3.1语法: 子类类型  引用名 = (子类类型)父类引用
     	 3.2只能强转父类的引用,不能强转父类的对象
     	 3.3要求父类的引用必须指向的是当前目标类型的对象
     	 			---就是cat.catMouse();这行代码要是能正常跑起来,要求你之前就已经写了Animal animal = new Cat();
     	 			   使得父类引用animal指向了猫Cat类型的对象;
     	 			---试问,如果此时有个Dog类也是继承Animal类,但是直接写Dog dog = (Dog)animal;这行代码有毛病吗?
     	 			   答案:是有的,你之前就没有向上转型,使父类引用指向此时的目标类型的对象Dog,编译时是不会报错的,
     	 			   但跑起来,就会发生ClassCaastException异常,即类转换异常,性质相当于,你可以说猫是动物,狗是动物,
     	 			   但不能说狗是猫,因为Cat cat = (Cat)animal;cat.catMouse;这两行代码执行无异常的本质前提是,之前
     	 			   已经写了Animal animal = new Cat();使父类引用指向了子类对象,建立了这个前提;
     	 3.4当向下转型后,可以调用子类类型中所有的成员
     	 			---  Cat cat = (Cat)animal;
     	 			     cat.catMouse();//这时候,这行代码经过向下转型,就可以调用子类的特有方法catMouse了,不会报错;
     	 			     				此时,编译类型是Cat,运行类型也是Cat;
    				
    				
    				*/
		Animal  animal = new Cat();
		Object obj = new Cat();
		System.out.println("OK~");//代码到这儿也是可以执行的,即上面两行代码都是不会报错的    
/**
	4.属性没有重写之说!属性的值看编译类型
		--父类Base有属性count=10,子类Sub也有属性count=20;
		--Base base = new Sub();//向上转型
		--System.out.println(base.count);//此时的输出就要看编译类型Base,所以最后输出的就是10,而不是20;
		--Sub sub = new Sub();System.out.println(sub.count);  // 此时输出的就是20,而不是10,还是一样看编译类型
		
	5.instanceOf 比较操作符,用于判断对象的[运行]类型是否为XX类型或XX类型的子类型  
		--AA是父类,BB是子类,extends AA
		--BB bb = new BB();    
		--sout.(bb instanceof BB); //输出 true
		--sout.(bb instanceof AA); //输出还是 true
		--AA aa = new BB();
		--sout.(aa instanceof AA);//输出 true
		--sout.(aa instanceof BB);//输出还是true   所以可得其判断的是对象的运行类型是否为XX类型或xx类型的子类型
				--因为如果看编译类型,则aa 是AA类,不是BB的子类,第二句输出的应该是false,假设看运行类型,aa是BB类,
				  那么,aa instanceof BB 为true,aa instance AA 也为true,因为BB extends AA ;
		--Object obj = new Object();
		--sout.(obj instanceof AA);//此时这里输出的是什么呢?   			答案是false,因为看运行类型Object是AA的
																	 父类,父亲怎么会和儿子等同呢?倒反天罡!!
         --String  str = "hello";
         --sout.(str instanceof Object);//输出是true,想对了吗?   因为String的父类是Object,所以儿子instanceof父亲,
         												没毛病,如果反过来,object instance String;那就false
*/         	
      Object obj = "Hello";//向上转型,父类型引用指向子类型对象
	  String objStr = (String)obj;//向下转型

	 Object objPri = new Integer(5);//向上转型,没问题
      String str = (String)objPri;
	 		 //错误,报出ClassCastException,类转换异常,虽然是在尝试向下转型,但因为objPri上一句已经指向了Integer类,
			 //联想之前的猫狗问题,objPri已经和Integer产生羁绊,你个String又怎能来插手二者的感情呢?
      Integer str1 = (Integer)objPri;//正确的,之前Object和Integer已经产生了羁绊,此时再向下转型,进行转化,也是允许的

在这里插入图片描述

Java的动态绑定机制

 //1.当调用对象【方法】的时候,该方法会和该对象的【内存地址/运行类型】绑定
 //2.当调用对象【属性】的时候,【没有动态绑定机制】,哪里声明,哪里使用

未使用动态绑定机制,要清楚结果怎么得到的:

在这里插入图片描述

使用动态绑定机制后,要理解结果怎么得到的

在这里插入图片描述

此中的30是因为先找B类的sum()方法,找不到,然后追溯到A类的sum()方法,然后,此时的getI()方法的调用者是A类对象a,其运行类型是B类型对象,所以,会去调用B类中的getI()方法,得到20,然后回去得到最终的结果30

多态应用

多态数组

多态参数

在这里插入图片描述

public class Employee {
    private String name;
    private double salary;

    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
    public double getAnnual(){
        return 12*salary;
    }
}

public class worker extends Employee{
    public worker(String name, double salary) {
        super(name, salary);
    }
    public void work(){
        System.out.println("工人"+getName()+"在工作");
    }

    @Override
    public double getAnnual() {

        return 12*getSalary();
    }
}
public class manager extends Employee{
    private double bonus;

    public manager(String name, double salary, double bonus) {
        super(name, salary);
        this.bonus = bonus;
    }

    public double getBonus() {
        return bonus;
    }

    public void setBonus(double bonus) {
        this.bonus = bonus;
    }
    @Override
    public double getAnnual() {

        return super.getAnnual()+bonus;
    }
    public void manage(){
        System.out.println("经理"+getName()+"正在管理员工");
    }
}

public class Test {
    public static void main(String[] args) {
        worker wok1 = new worker("A ", 2000);
        manager manager = new manager("Jason", 5000, 10000);
        Test test = new Test();
        test.showEmpAnnual(wok1);//24000.0
        test.showEmpAnnual(manager);//70000.0
        test.testWork(wok1);//工人A 在工作
        test.testWork(manager);//经理Jason正在管理员工
    }
    public void showEmpAnnual(Employee employee){
        System.out.println(employee.getAnnual());
    }
    public void testWork(Employee e){
        if(e instanceof worker){
            ((worker) e).work();//有向下转型的操作,  e.work直接快捷生成
        }else if(e instanceof manager){
            ((manager) e).manage();
        }else{
            System.out.println("你输入的类型有误,不做处理");
        }
    }
}

equals方法

//equals 和 ==之间的对比
    ==是一个比较运算符,既可以判断基本类型,又可以判断引用类型
    如果判断基本类型,判断的是值是否相等
    如果判断引用类型,判断的是地址是否相等,即判断是不是同一个对象
        
    equals是Object类中的方法,只能判断引用类型;
    默认判断的是地址是否相等,子类中往往重写该方法,用于判断内容是否相等
    System.out.println("abc".equals("abc"));   
    以下这是两个字符串通过equals来判断是否相等的底层equals方法的源码逻辑实现
        public boolean equals(Object anObject) {
        if (this == anObject) {//如果是同一个对象,返回true
            return true;
        }
        if (anObject instanceof String) {//判断类型,如果不是字符串,直接就返回false
            String anotherString = (String)anObject;//向下转型
            int n = value.length;//获取传入参数的长度
            if (n == anotherString.value.length) {//如果二者长度相同,则进一步判断是否真的相等
                char v1[] = value;//两个各自转成char数组
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {//进入while循环,逐个char数组的元素来比较是不是全部相等
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;//如果两个字符串的所有字符都相等,则返回true
            }
        }
        return false;
    }
   这是Object的原始equals方法的源代码实现
   public boolean equals(Object obj) {
        return (this == obj);
   }
   此中默认比较的是否是一个对象,比较内存地址是否相同,但是结合实际,我们一般需要对此进行改写;

   这是Integer的equals方法,同样,也可以看到对原始的Object中的equals进行了改写,变成了判断两个值是否相同
   public boolean equals(Object obj) {
        if (obj instanceof Integer) {//判断是不是Integer类型的对象
            return value == ((Integer)obj).intValue();//如果是,就比较这个对象对应的int值和传入的值对象的value值是否相等
        }
        return false;
    }
Integer integer1 = new Integer(100);
Integer integer2 = new Integer(100);
sout.(integer1 == integer2);//输出的结果是false
sout.(integer1.equals(integer2));//输出的结果就是true

重写equals方法

在这里插入图片描述

需求:判断两个Person对象的内容是否相等,如果两个Person对象的各个属性值都一样,则返回true,反之false
  Person p1 = new Person("jack",10,'男');
  Person p2 = new Person("jack",10,'男');
//此时如果不改写Perosn的equals方法,则sout(p1.equals(p2)); 输出的是false,因为它会默认调用Object的原始equals方法,
//即直接比较内存地址,判断是不是同一个对象,但是此时我们的预期应当是比较Person的每一个属性,都相等,则返回true,所以要改写
     要使得    sout.(p1.equals(p2));
  public boolean equals(Object obj){
      //判断如果比较的两个对象是同一个对象,则直接返回true
      if(this == obj){
          return true;
      }
      //类型判断
      if(obj instanceof Perosn){//只有当传入的对象是Person类型的,咱们才比较
          //为了得到obj的各个属性,我们需要进行向下转型
          Person p = (Person)obj;
          //注意,此中的p就是传入的 p2,this指代的就是p1,即下面这行比较p1的各个属性和p2的各个属性是否相等,有一个不等都不行
          return this.name.equals(p.name) && this.age == p.age && this.gender == p.gender;
      }
      //如果连同一类型都不是,还谈啥相等,直接返回false
      return false;
  }

顺带一提:sout.("hello" == new java.sql.Date());
//这里的结果不是false哟,而是直接编译报错了,因为二者都不是同一类型,还比啥,直接false;

hashCode方法

关于其的6个小结
    1.提高具有哈希结构的容器的效率
    2.两个引用,如果指向的是同一个对象,则哈希值肯定是一样的
    3.两个引用,如果指向的是两个不同的对象,则哈希值是绝大部分情况下是不一样的,哈希碰撞时,可能会发生一样的情况
    4.哈希值主要是根据地址号来的,不能完全的将哈希值等价于地址

toString方法

默认返回:全类名+@+哈希值的16进制;
    Object的子类往往重写 toString方法,用于返回对象的属性信息
    Object 的toString方法源码如下
    public String toString() {
    //全类名就是指 	包名 + 类名
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }
打印对象或拼接对象时,都会自动调用该对象的toString形式
当直接输出一个对象时,toString方法会被默认调用

finalize方法

当垃圾回收器确定不存在对该对象的更多引用时,由该对象的垃圾回收器调用此方法

    1.当对象被回收时,系统自动调用该对象的finalize方法,子类可以重写此方法,程序员如有需要,就可以重写,在其中
      实现释放资源,比如关闭文件,断开数据库的连接,这些资源的释放
    2.什么时候被回收:当某个对象没有任何引用时,则JVM就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,在销毁该
      对象前,会先调用finalize方法
    3.垃圾回收机制的调用,是由系统决定的,即有它自己的gc回收算法,也可以通过System.gc()主动触发垃圾回收机制;
    
//补充,    system.gc(),并不会立即执行,如果你在这个语句之后还有sout.(xxx)语句,会先等其执行完再执行
 //在实际开发中,我们几乎不会运用finalize方法,更多是为了应对面试

断点调试

//重要提示:在断点调试的过程中,是运行状态,是以对象的运行类型来执行的

	A extends B ; B b = new A(); b.xx();
	//在调试b.xx()方法时,是按照它的运行类型A来定位这个方法的
断点调试快捷键
   	 F7(跳入)F8(跳过)
   	 F7:跳入方法内
	shift + F8(跳出)
	F9(resume,执行到下一个断点)
	shift+F8:逐行执行代码

在这里插入图片描述

各个访问修饰符的权限范围

在这里插入图片描述

看组多态相关代码练习

在这里插入图片描述

//向上转型
Person p = new Student();
那么此时的p可以调用的方法有父类的run和eat,因为未运行之前看的是编译类型Person;
    p.run();
    p.eat();
但实际运行的时候,会执行动态绑定机制,p.run()这行代码执行的时候,看到运行类型是Student
    就到子类Student去找 run() 方法,找到了,最后输出student run;而第二句 p.eat();
同样执行动态绑定机制,先去子类找eat方法,但没找到,所以去看父类,发现父类有,就输出person eat;


//向下转型------把指向子类对象的父类引用,转成指向子类对象的子类引用
//务必记住,向下转型的前提是已经有向上转型,建立了绑定关系,不然就会出现猫是狗,狗是猫的情况
Student s = (Student)p;

在这里插入图片描述

此时Student s = (Student)p;
此时的s可以调用Student类的run和study方法,当然,因为Student继承Person类,那么父类的eat方法也是可以调用的;
    s.run();//student run
	s.study();//student study
    s.eat();//person eat

在这里插入图片描述

==和equals的区别

在这里插入图片描述

super和this的练习题

在这里插入图片描述
在这里插入图片描述

回顾复习多态

多态是什么?多态的具体体现呢?

多态:方法或对象具有多种形态,是OOP的三大特征,是建立在封装和继承的基础之上;

多态具体体现

  • 方法多态
    • 重载体现多态
    • 重写体现多态
  • 对象多态
    • 对象的编译类型和运行类型可以不一致,编译类型在定义时,就确定,不能变化
    • 对象的运行类型是可以变化的,可以通过getClass()来查看运行类型
    • 编译类型看定义时,=号的左边,运行类型看 = 号的右边
    • 举例说明如下

在这里插入图片描述

Java动态绑定机制回顾复习

  • 当调用对象的方法时,该方法会和对象的内存地址/运行类型绑定
  • 当调用对象的属性时,没有动态绑定机制,哪里声明,哪里使用

在这里插入图片描述

在这里插入图片描述

感谢看到这儿的铁子们,欢迎留言指出文章的不足之处,下期见~~

标签:Java,复习,super,子类,类型,父类,方法,public,三大
From: https://blog.csdn.net/Kerwin_D/article/details/141476241

相关文章