1:面向对象

@1:==与equals的区别

== 的作用:
  基本类型:比较的就是值是否相同
  引用类型:比较的就是地址值是否相同
equals 的作用:
  引用类型:默认情况下,比较的是地址值。
注:不过,我们可以根据情况自己重写该方法。一般重写都是自动生成,比较对象的成员变量值是否相同

@2:写算法题的时候边界条件最后考虑

@3:高内聚低耦合

耦合:—> 模块间的联系

模块之间联系越紧密,其耦合性就越强,模块的独立性则越差。

联系越紧密,当你修改一个模块时,就影响到另一个模块,关联性大,工作量大,也容易出错。

内聚:—> 模块内的联系

内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事。它描述的是模块内的功能联系。

@4:父类引用指向子类对象

Father f = new Son();

子类中的属性继承了父类,如果子类中不再写一遍这些属性,那么子类与父类共用这些属性

父类引用只能调用的是:子类中重写的父类方法,父类中没被子类重写的方法,父类中的属性(父类子类共用)在这里插入图片描述

@5:如何重写equals方法

class commodity {
    String commodityID;
    
    @Override
    public boolean equals(Object obj) {
        if(this == obj) {
            return true;
        }
        if(!(obj instanceof commodity)) {
            return false;
        }
        commodity P = (commodity)obj;
        if(P.commodityID.equals(this.commodityID)) {
            return true;
        }else {
            return false;
        }
    }
}    

@6:java是如果实现跨平台的

通过JVM虚拟机实现的跨平台

也就是再windows上整一个windows版的JVM

在MAC系统上整一个MAC版的JVM

在LInux系统上整一个linux版的JVM

在不同的系统上整一个不同版本的JVM

对于同一个字节码文件,不同系统上的JVM调用各自系统上的API实现相同的功能。

@7:HashMap中的重点注意事项

1:Map中存放键值对的Key是唯一的,value是可以重复的

2:Map中键值对的Key不能直接修改,value可以修改,如果要修改key,只能先将该key删除掉,然后再来进行重新插入。

3:put(key,value): 注意key不能为空,但是value可以为空 key如果为空,会抛出空指针异常.

4:put(key, value) , 如果key存在,会使用value替换原来key所对应的value,返回新value

5:containKey(key):检测key是否包含在Map中,时间复杂度:O(logN)

containValue(value): 检测value是否包含在Map中,时间复杂度: O(N)

@8:局部变量必须初始化

数据类型分为基本数据类型与引用数据类型,引用数据类型做成员变量时,初始值是null.

局部变量不初始化会报错

@9:同一个类,类的加载只加载一次

class person {
    。。。。。。。。。。
}

person p1 = new person();

person p2 = new person();

person p3 = new person();

/*
在创建person对象p1的时候首先会进行类的加载,然后再进入类(构造方法)中,进行对象的构造,属性没有初始化的时候进行初始化。在创建person对象p2的时候不会再进行类的加载,直接进入类(构造方法)中,进行对象的构造,属性没有初始化的时候进行初始化,P3亦是如此。
*/

创建对象操作:@@@
首先进行类的加载,(需要注意的是同一个类只加载一次不管这个类创建了多少对象)。然后创建对象并在堆中为这个对象开辟空间。(需要注意的是这一步我们是看不见的),然后进行对象的初始化,(初始化的时候如果属性没有赋初始值,则赋默认的初始值,如果刚开始就已经赋值了则根据类中的值赋值),然后再调用构造方法,对对象的属性进行赋值。(注意调用构造方法并不是创建对象,因为在调用构造器之前对象就已经创建好了,调用构造方法只是对对象进行赋值。)

例子如下:

class person {
    public person() {
        this.name = "222";
        this.age = 20;
    }
    String name = "111";
    int age = 10;
    public void eat() {
        System.out.println("hello MSB");
    }
    
    public static void main(Strng args[]) {
        person p = new person();
    }
}

@10:空构造器注意点

在这里插入图片描述

@11:内存分析

在这里插入图片描述

执行流程:

mian函数是程序的入口,所以先为main方法创建栈帧,所以先进入main函数,然后进行对象的创建:

person p1 = new person( );

首先进行类的加载,会将类的字节码文件放到方法区,然后根据类的字节码文件在堆中创建对象,然后对对象

中的属性进行初始化。然后进入构造方法对对象的属性进行赋值,由于这里是空构造器,没有对对象的属性进

行赋值,然后对象创建完毕,局部变量P1指向堆中的这个对象。

栈:存放的是局部变量

堆:存放的是new出来的对象

方法区:存放的是类的字节码信息


在这里插入图片描述

执行流程:
首先main方法是程序的入口,所以先在栈上为main方法创建栈帧,然后开始类的加载,将类的字节码信息放到方法区,根据类的字节码信息在堆中创建对象,并对对象的属性进行初始化,然后调用类的构造方法,先在栈上为构造方法创建栈帧,并对局部变量进行赋值,(由于string类型是存放在字符串常量池中的,所以局部变量c存放的是字符串常量池中字符串的地址。)然后对对象的属性进行赋值。赋值完之后,构造器的栈帧自动销毁,然后main方法栈帧中有局部变量P1指向该对象。

字符串常量池:同一个字符串在字符串常量池中只存一份。举个例子:比如说有好几个“海淀”要放在字符串常量池中,其实字符串常量池只有一份。用的时候直接取就行。


在这里插入图片描述

@12:this关键字

1:this可以修饰属性

(主要是用在形参与成员变量重名 | | 局部变量与成员变量重名的时候)

2:this可以修饰方法

(主要是用在方法中,用来调用另一个方法,注意方法中可以调用另一个方法,但是方法中不可以再写一个方法)

class Person {
    String name;
    int age;
    public void eat() {
        this.bark();
        ............
    }
    
    public void bark() {
        ............
    }
}

3:this可以修饰构造器

同一个类中构造器之间可以通过this相互调用,注意this修饰构造器必须放在第一行。

class Person {
    String name;
    int age;
    
    public Person(String name, int age) {
        this(age);
        this.name = name;
    }
    
    public Person(int age) {
        this.age = ag
    }
   
    public void eat() {
        ............
    }
}

@13:static关键字

在这里插入图片描述

创建时间上:

静态的属性/静态方法/静态代码块/静态内部类) 早于 对象

一:static修饰变量:

​ 1:随着类的加载一起加载进方法区的静态域中

​ 2:先于对象的存在

​ 3:static修饰的变量是属于这个类的,是通过这个类实例出的对象所共享的。

​ 4:static修饰属性的应用场景:

​ 某些特定的数据想要在内存中共享,只有一块 --》这个情况下,就可以用static修饰的属性

​ 比如说同一类下很多对象的school属性都是一样的,所以school完全可以是静态变量,不用一个一个的赋 值。

二:static修饰方法:

在类加载的同时会将静态的东西(静态的属性/方法/代码块/静态内部类)加载到方法区中的静态域中,这都是

先于对象的创建完成的,而且完全不依托于对象,他是属于类的,没有对象照样可以执行。

在非静态方法中可以调用静态的方法 / 静态的属性 / 非静态的方法 / 非静态的属性。

-> 因为非静态方法的执行是依托于对象的。有了对象才可以执行,而有对象之前早就加载好静态的东西了,所以静态的东西直接用就行。 创建时间上的问题

在静态方法中可以调用静态的方法 / 静态的属性 / 有对象做依靠的非静态的方法和非静态的属性

-> 因为在时间上,在类加载的同时会将静态的东西(静态的属性/方法/代码块/静态内部类)加载到方法区中的静态域中,这都是先于对象的创建完成的,在静态的方法中直接调用非静态的东西,因为非静态的东西是依托于对象的,所以有可能这个对象并没有创建出来。 创建时间上的问题

6.	public class Demo {
7.	    int id;
8.	    static int sid;
9.	
10.	    public void a(){
11.	        System.out.println(id);
12.	        System.out.println(sid);
13.	        System.out.println("------a");
14.	    }
15.	    //1.static和public都是修饰符,并列的没有先后顺序,先写谁后写谁都行
16.	    static public void b(){
17.	        //System.out.println(this.id);//4.在静态方法中不能使用this关键字
18.	        //a();//3.在静态方法中不能访问非静态的方法
19.	        //System.out.println(id);//2.在静态方法中不能访问非静态的属性
20.	        System.out.println(sid);
21.	        System.out.println("------b");
22.	    }
23.	
24.	    //这是一个main方法,是程序的入口:
25.	    public static void main(String[] args) {
26.	        //5.非静态的方法可以用对象名.方法名去调用
27.	        Demo d = new Demo();
28.	        d.a();
29.	        //6.静态的方法可以用   对象名.方法名去调用  也可以 用  类名.方法名 (推荐)
30.	        Demo.b();
31.	        d.b();
32.	        
33.	    }
    }

三:代码块

public class Test {
    //属性
    int a;
    static int sa;

    //方法
    public void a() {
        System.out.println("我是非静态方法a");
        {
            //普通块限制了局部变量的作用范围
            System.out.println("这是普通块");
            System.out.println("普通快中的内容----000000");
        }
        //if(){}
        //while(){}
    }

    public static void b() {
        System.out.println("我是静态方法b");
        {
            System.out.println("我是静态方法里面的普通快");
        }
    }

    //构造块
    {
        System.out.println("------这是构造块");
    }

    //静态块
    static {
        System.out.println("-----这是静态块");
        //在静态块中只能方法:静态属性,静态方法
        System.out.println("我是静态变量" + sa);
        b();
    }


    //构造器
    public Test() {
        System.out.println("这是空构造器");
    }

    public Test(int a) {
        this.a = a;
    }


    //这是一个main方法,是程序的入口:
    public static void main(String[] args) {
        Test t = new Test();
        t.a();

        Test t2 = new Test();
        t2.a();
    }
}

/*
-----这是静态块
我是静态变量0
我是静态方法b
我是静态方法里面的普通快
------这是构造块
这是空构造器
我是非静态方法a
这是普通块
普通快中的内容----000000
------这是构造块
这是空构造器
我是非静态方法a
这是普通块
普通快中的内容----000000
*/

类的组成部分:1:属性,2:方法,3:构造器,4:代码块,5:内部类

1:属性:静态属性,非静态属性

2:方法:静态方法,非静态方法

3:构造器:有参构造器,无参构造器(时时刻刻写着无参构造器,当你写着有参构造器时编译器不会自动加无参构造器)

4:代码块:普通快,构造快,静态块,同步块(多线程)

5:内部类:静态内部类,非静态内部类

代码块:

普通快:写在方法(可以是静态方法也可以是非静态方法)里面的代码块。

构造块:写在方法外面类里面的非静态代码块。

静态块:写在方法外面类里面的非静态代码块。

代码的执行顺序:

最先执行静态块(只在类加载的时候执行一次,所以一般以后实战写项目:创建工厂,数据库的初始化信息都放入静态块。一般用于执行一些全局性的初始化操作)然后再执行构造块(不常用),再执行构造器,再执行方法中的普通快。需要注意的是一个类中的静态块只执行一次,但是构造快每new一个对象的时候都要执行一次,并且其执行顺序在构造器执行之前。

@14:包

为了解决重名问题,解决权限问题

包名的定义:

1:全部小写

2:中间用 . 隔开

3:一般都是公司域名倒着写 : com.jd

4:加上模块名字:

com.jd.login com.jd.register

5:不能使用系统中的关键字:nul,con,com1—com9…

6:同一个包下的类相互之间直接用就行

在Java中的导包没有包含和被包含的关系,都是平级关系

在这里插入图片描述

@15:封装

封装最大的好处:提高代码的安全性

将某些东西进行隐藏,然后提供相应的方式进行获取。

即使外界可以通过方法来访问属性了,但是也不能随意访问,因为咱们在方法中可以加入 限制条件。

通过getter setter方法对属性进行修改,之前是直接获取到属性然后修改属性,现在为什么这么麻烦呢?

因为这个getter setter方法是我们自己写的,我们想让属性被外面访问,我们就写一个getter方法setter方法,不想让外面访问我们就不写,而且如果外面对属性的操作我们不满意我们可以在setter,getter方法里修改成我们满意的。掌握权一直在我们手里,而不是客户手里。比如说下面的 setAge(int age) 方法,如果你传的age大于18,我不满意,我就直接i给你改成18.

总结一下就是:你不可以随随便便的访问我的属性,你必须得先过我的 “方法” 这一关。

public class Student {
    //属性:
    private int age;
    private String name;
    private String sex;

    //加入对应的setter和getter方法:
    public int getAge() {
        return age;
    }

    public void setAge(int age) {
		if(age > 18) {
			this.age = 18;
		}else {
			this.age = age;
		}
    }

    public String getName() {
        return name;
    }

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

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        if ("男".equals(sex) || "女".equals(sex)) {//sex是男 或者 是 女
            this.sex = sex;
        } else {
            this.sex = "男";
        }
    }

    //加入构造器:
    public Student() {

    }

    public Student(int age, String name, String sex) {
        this.age = age;
        this.name = name;
        // 注意:如果set方法里面做了修改,这个构造方法也要跟着修改。
        // 这里如果不修改的化sex传一个乱码,属性就会是这个乱码,而调用setSex则不会。
        //this.sex = sex;
        this.setSex(sex); 
    }
}

我们程序设计追求“高内聚,低耦合”。

➢高内聚:类的内部数据操作细节自己完成,不允许外部干涉;

➢低耦合:仅对外暴露少量的方法用于使用。

隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提

高系统的可扩展性、可维护性。通俗的说,把该隐藏的隐藏起来,该暴露

的暴露出来。这就是封装性的设计思想。

@16:继承

继承最大的好处就是:提高代码的复用性。

Person:属性:age,name,height -----> 这些属性都是private修饰的,被封装好的。

​ 方法:eat( ),sleep()

Student:属性:sno

​ 方法:study( )

Student 继承自Person , 那么Student里面就有:age,name,height ,sno,eat( ),sleep(),study( )

注意:

1:父类中private修饰的属性,其实子类也是继承过来的,只是由于封装的特性,不可以在子类中直接的调用,但是可以通过setter getter接口间接调用。

2:一个父类可以有无数的子类,但是一个子类只能有一个直接父类,但是他可以间接的继承自其他的父类,比如说当其他的孙子类或是重孙子类。

3:所有的类都直接或者间接的继承自Object。

继承的内存分析:(下面连接)

原文链接:https://blog.csdn.net/TYRA9/article/details/128729074

在这里插入图片描述

还需要注意的是:父类中的static修饰的成员变量与成员方法也会被继承到子类中,但是不可以在子类中重写

这些方法,因此不能实现多态。

如果父类的属性被声明为 public 或 protected,那么子类可以继承这些属性,并且可以在子类中访问这些属性。如果父类的属性被声明为 private,那么子类也会继承这些属性,但由于封装的特性,子类无法直接调用。如果父类的属性没有被声明为任何访问限定符修饰符,即使用 default 修饰符,则同包的子类可以继承这些属性,但不同包的子类无法继承这些属性。

需要注意的是,如果父类的属性被声明为 final,那么子类无法覆盖这些属性(常量被放到常量池中),但是子类仍然可以继承这些属性。如果父类的属性被声明为 static,那么子类可以继承这些属性,可以通过子类的实例,子类类名,父类类名访问这些属性。

@17:protected关键字

protected关键字最大权限到不同包的子类。

在这里插入图片描述

类修饰符:public,default

方法/属性修饰符:public,protected,default,private

@18:方法的重写

方法的重写:发生在继承中,子类与父类之间,当子类对父类提供的方法不满意的时候,子类对父类提供的方法进行重写。

重载与重写的区别:

重载:发生在同一个类中,重载的方法的方法名相同,参数不同。

重写:子类与父类之间,当子类对父类提供的方法不满意的时候,子类对父类提供的方法进行重写。

在这里插入图片描述

注意:重写的返回值:当父类与子类的返回值都是基本数据类型的时候(比如说父类返回值类型是int,子类的返回值类型是double)是不可以的,当父类与子类的返回值类型是引用类型时,必须满足父类的返回值范围大于子类才行(比如说父类返回Person类型,子类返回Student类型,person是Student的父类,这样是可以的)。

内存图:

看这个图之前最好看一下继承中的内存分析

在这里插入图片描述

@19:super关键字

看这个之前最好看一下继承中的内存分析

public class M1 {
    int age = 10;
    String name = "000";
    double height = 199.2;

    public void eat() {

    }
}

public class M2 extends M1{
    int oop = 100000;
    
    public void said() {
        this.age = 100;
    }

    public void see() {
        System.out.println(this.age); // 100
        System.out.println(super.age); // 100
        System.out.println(super.height);
        this.oop = 19;
        System.out.println(this.oop);
    }
}

public class M3 extends M2{
    public void sleep() {
        this.age = 1000;
        System.out.println(this.age); // 1000
        System.out.println(super.age); // 1000
        System.out.println(this.oop);
        System.out.println(super.oop);
        this.see();
    }

    public static void main(String[] args) {
        M2 m2 = new M2();
        m2.said();
        m2.see();
        M3 m3 = new M3();
        m3.sleep();
    }
}
/*
100
100
199.2
19
1000
1000
100000
100000
1000
1000
199.2
19
*/

继承条件下构造方法的执行过程

在这里插入图片描述

@20:toString()方法

这个方法的作用就是简单的介绍一下对象。

如果自建的对象中不重写 toString( ) 方法,默认调用的是Object中的 toString( ) 方法。此时返回的结果如下:

(对象所在的类名)@(对象所在堆中的地址通过哈希算法得到的哈希值)

在这里插入图片描述

自建的对象中不重写 toString( ) 方法,直接SOUT(对象的引用) == SOUT(对象的引用 . tostring( )

在这里插入图片描述

子类Student对父类Object提供的toString方法不满意,不满意–》对toString方法进行重写:

	@Override
	    public String toString() {
	        return "Student{" +
	                "name='" + name + '\'' +
	                ", age=" + age +
	                ", height=" + height +
	                '}';
	    }

@21:equals方法和 instanceof()方法

1:==:当比较的是基本数据类型时:比较的是值的大小关系。

​ 当比较的是引用数据类型时:比较的是内存地址。

2:equals( ):当没有重写equals( )方法时:相当于 ==。

在这里插入图片描述

​ 当重写equals( )方法后:按照自己的标准进行比较。

public class Phone {
    //属性:
    private String brand;//品牌型号
    private double price;//价格
    private int year ;//出产年份
    //构造器:

    public Phone() {
    }

    public Phone(String brand, double price, int year) {
        this.brand = brand;
        this.price = price;
        this.year = year;
    }
    
    //方法:
	public String toString() {
        return "Phone{" +
                "brand='" + brand + '\'' +
                ", price=" + price +
                ", year=" + year +
                '}';
    }
    
    //自己写的equals()方法
    public boolean equals(Object obj) {//Object obj = new Phone();
        // 将obj转为Phone类型:
        if(obj instanceof Phone) {
            Phone other = (Phone)obj;//向下转型,为了获取子类中特有的内容
  		   if(this.getBrand()==other.getBrand()&&this.getPrice()==other.getPrice()
                &&this.getYear()==other.getYear()){
                return true;
            }
        }
        return false;
    }
    
    // 系统自写的equals()方法
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Phone phone = (Phone) o;
        return Double.compare(phone.price, price) == 0 && year == phone.year 
               && Objects.equals(brand, phone.brand);
    }
}

A instanceof( B)方法:A:是对象名,B是类名,A对象是不是B的实例,A可以是B的子类实例

@22:重写hashcode与equals

总结:当向hashmap中传入引用类型的时候,通过hashcode来确定位置,通过equals来比较对象

当我们使用 HashMap, HashSet 等集合类时,会通过 hash 值来计算对象在散列表中的位置。

如果我们只重写 hashCode 方法,而不重写 equals 方法,那么两个相等的对象就会存储到同一个位置,但此时 equals 方法没有重写,两个对象就判定为不相等,从而可能存储大量相同对象,无法起到去重的效果。

如果我们只重写 equals 方法,而没有重写 hashCode 方法,HashMap 等集合类的底层逻辑是,先判断 hash 值是否相等,再判断对象值是否相等。此时没有重写 hashCode 方法,那么两个相等的对象就会被存储到散列表中不同的位置(Object 的 hashCode 方法,只是把地址转为一个 int 值), 从而没有 equals 判断的机会,也可能会存储大量相同对象 ,无法起到去重的效果。

但是也不绝对,如果是我们自定义的类,也不需要用到这些集合类,也就没人会去调用 hashCode 方法,此时只需要重写 equals 方法即可,也就没有必要重写 hashCode 方法了。即不调用hashcode就没必要重写

但还是建议重写 equals 方法时,也重写 hashCode 方法。因为不重写可能出错,重写一定不会出错。

而且要做到 equals 相等的对象,hashCode 一定是一致的。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

对象在堆中的地址 —> 对地址进行hash算法 —> hashcode

hashcode的作用就是判断两个对象是不是一样,比如两个对象看上去一样,但实际上并不是一个对象。

@23:多态

原文链接:https://blog.csdn.net/TYRA9/article/details/128745582

1:多态的简单介绍:

多态实现的前提条件:

1:有继承关系 / 实现关系(接口,抽象类)继承是实现多态的前提。

2:有方法的重写。

3:父类引用指向子类对象。

Animal animal = new Cat();

等号左面的animal是一个Animal类型的引用变量,但是,这个Animal类型的引用所指向的对象,即堆空间中真正的对象,却是一个Cat类型。在多态中,我们将等号左边的这个引用变量的类型,称为编译类型。而将等号右边的——在堆空间中new出来的——真正的对象的类型,称为运行类型。其中,编译类型决定了引用变量可以调用哪些属性和行为而运行类型则是在程序运行过程中jvm实际使用的类型。

比方说,现在我们通过animal对象来调用eat() 方法,因为编译类型是Animal类,因此在编译时,编译器要判断Animal类有没有eat() 方法。诶,有eat() 方法,那就可以调用。但在实际调用时,jvm会优先使用运行类型Cat类中的eat() 方法,因此打印结果为“🐱喜欢吃🥩”。

2:多态的使用:

(1):成员方法的使用:

​ 使用规则 :

编译看左(即左边的编译类型有没有这个成员方法,这决定了我们能不能调用目标成员方法)
运行看右(即右边的运行类型中的该成员,才是运行中实际使用的成员)

​ 父类引用也不可以使用子类特有的方法。

(2):成员变量的使用:

​ 使用规则 :

编译看左(即左边的编译类型有没有这个成员,这决定了我们能不能调用目标成员)
运行看左多态关系中,成员变量是不涉及重写的

父类引用不能直接使用子类的特有成员。

​ 编译类型决定了引用变量可以调用哪些属性和行为;而运行类型则是在程序运行过程中jvm实际使用的类型。父类引用,说明编译类型是父类类型,以父类类型编译当然只能使用父类中存在的成员。当然,这里所说的成员包括成员变量和成员方法,这二者在多态关系中的使用略有不同:使用的成员变量必须是在父类中存在的,且成员变量不涉及重写;使用的成员方法也必须是在父类中存在的,但是如果子类重写了父类方法,优先调用子类重写的方法。
​ 从jvm内存的角度解释就是 : .java文件经"javac.exe"命令编译后会生成.class的字节码文件,当代码中需要使用到某个类,该类的字节码文件就会加载进入方法区,而jvm识别并执行的就是字节码文件。因此,编译类型为父类类型,那jvm识别的当然是这个类的字节码文件,子类的特有成员,根本就不在这个字节码文件里面,jvm当然不认识。而对于子类重写的方法,父类字节码文件中包含有被重写方法的信息,jvm能够识别。而因为父类引用真正指向的是堆空间中的子类类型对象,所以此时会优先从堆空间中的子类对象里面找,使用子类重写后的方法,若子类没有重写,根据继承机制,则使用父类的该方法。

3:类型转换:

(1)向上转型:

子类类型转换成父类类型(父类引用指向子类对象)。向上转型是自动进行的,我们的多态就是一种向上转型。

(2)向下转型:

即父类类型转换成子类类型。为什么叫强制类型转化呢? 因为向下转型不会自动发生,需要人为强转。并且,向下转型改变了编译类型,而编译类型决定了我们可以使用哪些成员,当编译类型由父类类型转换为子类类型后,我们当然可以使用子类的特有成员了。因此,我们说要使用子类的特有功能,靠的就是向下转型!

Animal animal = new Cat();
/*
如果使用的是多态的话,animal只能调用Animal类中的成员变量,只能调用Cat中重写的方法以及父类中剩余的方法。
这时候如果我们想用animal调用子类特有的成员变量和成员方法,我们就需要对其进行向下转型。即如下,
综上就可以获取子父类中所有我们想要的东西。
*/
(Cat)animal

注意事项 :
①只有在继承关系的继承上才可以进行类型转换,否则会报出ClassCastException(类型转换异常)。

②在对引用变量进行向下转型之前,必须保证该引用变量指向的——堆空间中真正的对象的类型就是目标类型。(重要)
比如,Animal animal = new Cat(); 现在animal引用指向了一个Cat类型的对象,如果要对animal引用进行强制向下转型,就只能转换成Cat类型的引用;如果想转换成其他类型的引用,就需要先改变animal引用的指向,使其指向目标类型的对象。否则,同样会报出类型转换异常。

③那么,我们在进行向下转型之前,怎么就能知道——当前引用指向的对象是不是我们想要的目标类型的对象呢?
答案是 : 在进行强制类型转化之前,使用instanceof关键字来进行判断。

4:动态绑定:

原文链接:https://blog.csdn.net/TYRA9/article/details/128880552

1:前言:

在继承下,Java 中查找方法的顺序为 : 本类方法—>父类方法—>更高一级的父类—>…Object(顶层父类) 然而,在某些情况下,这样的原则也会被凌驾。今天我们要说的java动态绑定机制,就是这样一个区别于继承机制的例外。

在继承下——即java中查找变量的顺序 : 局部变量—>成员变量—>父类—>更高的父类—>…—>Object

2:特点:

(1)当通过对象的引用调用方法时,该对象的引用会和堆内存中真正的该对象——的内存地址绑定,且这种绑定关系会贯穿方法的执行全过程。这就是所谓的“动态绑定机制”。(重点)

(2)当通过对象的形式调用属性时,不存在动态绑定机制,符合继承机制——即java中查找变量的顺序 : 局部变量—>成员变量—>父类—>更高的父类—>…—>Object 。

动态绑定机制只发生在成员方法上,不发生在成员变量上。

举例说明

class Father {                  /** 父类 */
 int temp = 5;

 public int getTemp() {
     // temp:Father类中temp成员变量
     return temp;
 }

 public int add_temp() {
     return getTemp() + 10;
 }

 public int add_temp_EX() {
     return temp + 10;
 }
}

class Son extends Father {      
 int temp = 11;

 public int getTemp() {
     return temp;
 }
 // 重写的方法1
 public int add_temp() {
     // temp:Son类中的temp成员变量
     return temp + 100; 
 }
 // 重写的方法2      
 public int add_temp_EX() {
     return getTemp() + 1000;
 }
}

public class TestBinding {    
 public static void main(String[] args) {
     //建立多态关系
     Father father = new Son();
     System.out.println(father.add_temp());
     System.out.println(father.add_temp_EX());
 }
}

根据多态中成员方法的使用规则——编译看左,运行看右:父类引用,说明编译类型是父类类型,而父类中定义了这两个方法,因此编译没问题,可调用;又因为多态关系——父类引用此时指向了子类对象,因此运行类型为子类,子类重写了父类的这两个方法,当然要优先调用子类中的方法,加之变量没有动态绑定的机制,因此输出的结果:111,1011

当我们把子类中add_temp( )注释掉后

在这里插入图片描述

现在我们要去调用父类的add_temp() 方法了,但是问题来了 : 父类的add_temp() 方法中调用了getTemp() 方法,那这时候的getTemp() 方法是用谁的呢?

这里就是一个初学者大概率犯错误的地方,如果没有了解过动态绑定机制,它们会想当然的认为 : 现在执行的是Father类中的add_temp() 方法,根据java中查找方法的原则,当然是使用Father类本类的getTemp() 方法了!所以,最后的返回值就是5 + 10 = 15,输出15。但是真的是如此吗 ?结果输出的是:21 1011

输出结果表明父类add_temp() 方法最后返回的不是5 + 10,而是11 + 10,这表明add_temp() 方法中调用的getTemp() 方法是子类中的getTemp() 方法。为什么呢?

原因就是我们说的动态绑定机制,由于测试类中是通过father引用来调用方法,而它指向的对象是Son类对象。根据动态绑定机制,在调用add_temp() 方法时,father引用已经和它指向的对象绑定了,并且这种绑定关系会贯穿方法执行的全过程。因此,这里调用的getTemp() 方法是子类的方法,而不是父类的。

以上都是对链接中的内容进行的总结与截取,详细内容在链接中。

@24:final关键字

final修饰变量:

(1)final修饰一个基本类型的变量,变量的值不可以改变,这个变量也变成了一个字符常量,规定:名字大写。

(2)final修饰一个引用类型的变量,final Dog d = new Dog( ),那么地址值就不可以改变

final Dog d = new Dog();
// wrong,地址值不可以改变
d = new Dog();
// d对象的属性仍然可以改变
d.age = 10;
d.weight = 100.33;

(3):情况三:

public static void method() {
 // 地址值不可以改变
 final Dog d = new Dog();
	a(d);
}
//这种写法是可以的,a方法中的d是一个新建的形参
public static void a(Dog d){
   d = new Dog();
}

(4):情况四:

public static void method() {
 // 地址值不可以改变
 final Dog d = new Dog();
	a(d);
}
//这种写法是错的,a方法中的d是一个新建的形参,但是其地址值不可以改变
public static void a(final Dog d){
   d = new Dog();
}

final修饰方法

final修饰方法,那么这个方法不可以被该类的子类重写:

在这里插入图片描述

final修饰类

final修饰类,代表没有子类,该类不可以被继承:

一旦一个类被final修饰,那么里面的方法也没有必要用final修饰了(final可以省略不写)

在这里插入图片描述

注意:java.lang包下的Math类中的构造方法被private修饰—> 导致Math这个类不可以创建对象

在这里插入图片描述

构造方法被私有化之后不能被创建对象。

同样如果这个类不想被创建对象,其构造器可以用private修饰

@25:抽象类与抽象方法

1:简单的介绍抽象类:

抽象类相当于是在普通的类中加上了抽象方法,但是抽象类不可以创建对象。

2:抽象类的作用:

在抽象类中定义抽象方法,目的是为了为子类提供一个通用的模板,子类可以在模板的基础上进行开发,先重写父类的抽象方法,然后可以扩展子类自己的内容。抽象类设计避免了子类设计的随意性,通过抽象类,子类的设计变得更加严格,进行某些程度上的限制。使子类更加的通用。

3:注意事项:

1:一个中如果有方法是抽象方法,那么这个类必定是一个抽象类
2:在一个类中,会有一类方法,子类对这个方法永远不满意,会对这个方法进行重写,这个方法直接写成抽象方法,类 变成抽象类。
3:一个方法的方法体去掉,然后被abstract修饰,那么这个方法就变成了一个抽象方法
4:抽象类可以被其他类继承
5:一个类继承一个抽象类,那么这个类要么变成抽象类(不用重写 / 重写部分抽象方法)要么把继承的抽象类中的抽 象方法全部重写。
6:一般子类不会加abstract修饰,一般会让子类重写父类中的抽象方法
7:子类继承抽象类,就必须重写全部的抽象方法
8:子类如果没有重写父类全部的抽象方法,那么子类就要变成一个抽象类。
9:抽象类不可以创建对象
10:多态的写法:父类引用只想子类对象:

4:面试题:

(1)抽象类不能创建对象,那么抽象类中是否有构造器?

抽象类中一定有构造器。构造器的作用 给子类初始化对象的时候要先super调用父类的构造器。

(2)抽象类是否可以被final修饰?

不能被final修饰,因为抽象类设计的初衷就是给子类继承用的。要是被final修饰了这个抽象类了,就不存在继承了,就没有子类。

@26:接口

1:类是类,接口是接口,它们是同一层次的概念。

2:接口中没有构造器

3:接口如何声明:interface

4:在JDK1.8之前,接口中只有两部分内容:
(1)常量:固定修饰符:public static final
(2)抽象方法:固定修饰符:public abstract
注意:修饰符可以省略不写,IDE会帮你自动补全

5:类和接口的关系是什么? 实现关系 类实现接口

6:一旦实现一个接口,那么实现类要重写接口中的全部的抽象方法

7:如果没有全部重写抽象方法,那么这个类可以变成一个抽象类。

8:java只有单继承,java还有多实现

9:写法:先继承 再实现:class P extends Person implements Interface01,Interface02 { }

10:接口不能创建对象

11:接口中常量如何访问。

// 直接调用接口中的常量(static修饰)
System.out.println(TestInterface01.NUM);
// 通过接口的实现类调用
System.out.println(Student.NUM);
Student s = new Student();
System.out.println(s.NUM);
TestInterface01 t2 = new Student();
// 多态
System.out.println(t2.NUM);

12:继承与实现的区别

继承:子类对父类的继承

实现:实现类对接口的实现

手机 是不是 照相机

继承:手机 extends 照相机 “is-a”的关系,手机是一个照相机

上面的写法 不好:

实现: 手机 implements 拍照功能 “has-a”的关系,手机具备照相的能力

“实现” 更加的轻量,注重实现那些功能

在JDK1.8之后,新增非抽象方法:

(1)被public default修饰的非抽象方法:

public interface TestInterface {
 public static final int NUM= 10;
 public abstract void a();
 // 被public default修饰的非抽象方法
 public default void b() {
     System.out.println("TestInterface-b()");
 }
}

class A implements TestInterface {
 @Override
 public void a() {
     System.out.println("A-a()");
     // 注意点:
     b();// right
     TestInterface.super.b();// right
     //super.b() //wrong
 }

 public static void main(String[] args) {
     A a = new A();
     a.a();
 }
}

(2):静态方法

注意1:static不可以省略不写

注意2:静态方法不能重写

public interface TestInterface {
 public static final int NUM= 10;
 public abstract void a();
 // 被public default修饰的非抽象方法
 public default void b() {
     System.out.println("TestInterface-b()");
 }
 // 静态方法
 public static void c() {
     System.out.println("TestInterface-c()");
 }
}

疑问:为什么要在接口中加入非抽象方法???

如果接口中只能定义抽象方法的话,那么我要是修改接口中的内容,那么对实现类的影响太大了,所有实现类都会受到影响。

现在在接口中加入非抽象方法,对实现类没有影响,想调用就去调用即可。

@27:内部类

类的组成:1:属性(静态,非静态),2:方法(静态,非静态),3:构造器,4:代码块(静态代码块,构造块,普通块,同步块),5:内部类(成员内部类(静态的,非静态的),局部内部类(方法里,代码块里,构造器里))

内部类 —> 成员内部类(静态的,非静态的)

​ —> 局部内部类(位置:方法里,代码块里,构造器里)

成员内部类:

package P1.package2;

import P1.package1.Test;

public class TestOuter {
    // 属性
    int age = 10;
    // 方法
    public void a() {
        System.out.println("这是a方法");
        {
            System.out.println("这是一个普通块");
            class B {

            }
        }
        class A{

        }
        // 外部类想要访问内部类的东西,需要创建内部类的对象然后进行调用
        // 注意访问的话,你要有实际的东西才能被访问
        D d = new D();
        System.out.println(d.name);
        d.method();
    }
    // 非静态成员内部类
    public class D {
        int age = 20;
        String name;
        public void method() {
            // 内部类可以访问外部类的内容
            System.out.println(age);
            a();
            int age = 30;
            System.out.println(age);// 30
            System.out.println(D.this.age);// 20
            System.out.println(TestOuter.this.age);// 10
        }

    }

    // 静态成员内部类
    static class E {
        public void method() {
            // 静态内部类只能访问外部类中被static修饰的东西(属性,方法)
            // System.out.println(age);
            // a();
        }
    }

    static {
        System.out.println("这是静态代码块");
    }

    {
        System.out.println("这是构造块");
    }
}

class Demo {
    public static void main(String[] args) {
        TestOuter testOuter = new TestOuter();
        testOuter.a();
        // 静态内部类对象的创建
        TestOuter.E e = new TestOuter.E();
        // 非静态内部类对象的创建
        // TestOuter.D d = new TestOuter.D(); wrong
        /*
          TestOuter t = new TestOuter();
          TestOuter.D d = t.new D();   // right
         */
        TestOuter.D x = new TestOuter().new D(); // right
        
    }
}

局部内部类

package P1.package2;
// 局部内部类
public class TestOuter2 {
    public void method() {
        final int num = 10;
        class A {
            public void a() {
                // 在局部内部类中被访问到的变量必须是被final修饰
                //num = 20;
                System.out.println(num);
            }
        }
    }

    // 如果类B在整个项目中只使用一次,那么就没有必要单独创建一个B类,使用内部类就可以了
    public Comparable method2() {
        class B implements Comparable {
            @Override
            public int compareTo(Object o) {
                return 0;
            }
        }
        return new B();
    }
    // 匿名内部类
    public Comparable method3() {
        return new Comparable() {
            @Override
            public int compareTo(Object o) {
                return 0;
            }
        };
    }
}

Q:静态内部类,非静态内部类,静态方法,静态属性,静态代码块在什么时候被加载的。

A:**1:静态内部类:**当外部类被加载或初始化时并不会立即加载静态内部类。静态内部类只有在第一次使用的时候才会被加载。比如说创建静态内部类的实例,调用静态内部类的静态成员时才会被加载。

**2:非静态内部类:**在外部类实例化过程中被加载。只有创建外部类实例之后,才能通过外部类实例访问非静态内部类。

3:静态方法/属性/代码块 -》 类加载的时候进行加载。

静态内部类与静态成员是与外部类实例无关的,可以直接通过类名进行访问。

而非静态内部类需要外部类被实例化之后才能使用。

Q:静态内部类与非静态内部类的区别

A:静态内部类和非静态内部类是Java中的两种内部类,它们在一些方面有一些区别。下面是静态内部类和非静态内部类的主要区别:

  1. 实例化方式:
    • 静态内部类可以直接通过外部类名访问和实例化,不需要依赖外部类的实例。
    • 非静态内部类需要通过外部类的实例来访问和实例化,因为非静态内部类隐含地持有对外部类实例的引用。
  2. 访问权限:
    • 静态内部类可以访问外部类的静态成员(静态方法和静态属性),无法直接访问外部类的实例成员(非静态方法和非静态属性)。如果需要访问外部类的实例成员,需要通过外部类的实例来访问。
    • 非静态内部类可以访问外部类的所有成员,包括静态和实例成员,因为非静态内部类隐含地持有对外部类实例的引用。
  3. 生命周期:
    • 静态内部类的生命周期独立于外部类,即使外部类实例被销毁,静态内部类的实例仍然存在。
    • 非静态内部类的生命周期依赖于外部类的实例。如果外部类实例被销毁,非静态内部类的实例也会被销毁。
  4. 内存占用:
    • 静态内部类的实例不会持有外部类的引用,因此它们的内存占用相对较小。
    • 非静态内部类的实例隐含地持有对外部类实例的引用,因此它们的内存占用相对较大。
  5. 使用场景:
    • 静态内部类适合于独立使用的辅助类,与外部类无关,但又希望能够方便地访问外部类的静态成员。
    • 非静态内部类适合于与外部类密切相关的情况,需要访问外部类的实例成员,并且需要共享上下文和数据。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HackerTerry

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值