文章目录
- @1:==与equals的区别
- @2:写算法题的时候边界条件最后考虑
- @3:高内聚低耦合
- @4:父类引用指向子类对象
- @5:如何重写equals方法
- @6:java是如果实现跨平台的
- @7:HashMap中的重点注意事项
- @8:局部变量必须初始化
- @9:同一个类,类的加载只加载一次
- @10:空构造器注意点
- @11:内存分析
- @12:this关键字
- @13:static关键字
- @14:包
- @15:封装
- @16:继承
- @17:protected关键字
- @18:方法的重写
- @19:super关键字
- @20:toString()方法
- @21:equals方法和 instanceof()方法
- @22:重写hashcode与equals
- @23:多态
- @24:final关键字
- @25:抽象类与抽象方法
- @26:接口
- @27:内部类
@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中的两种内部类,它们在一些方面有一些区别。下面是静态内部类和非静态内部类的主要区别:
- 实例化方式:
- 静态内部类可以直接通过外部类名访问和实例化,不需要依赖外部类的实例。
- 非静态内部类需要通过外部类的实例来访问和实例化,因为非静态内部类隐含地持有对外部类实例的引用。
- 访问权限:
- 静态内部类可以访问外部类的静态成员(静态方法和静态属性),无法直接访问外部类的实例成员(非静态方法和非静态属性)。如果需要访问外部类的实例成员,需要通过外部类的实例来访问。
- 非静态内部类可以访问外部类的所有成员,包括静态和实例成员,因为非静态内部类隐含地持有对外部类实例的引用。
- 生命周期:
- 静态内部类的生命周期独立于外部类,即使外部类实例被销毁,静态内部类的实例仍然存在。
- 非静态内部类的生命周期依赖于外部类的实例。如果外部类实例被销毁,非静态内部类的实例也会被销毁。
- 内存占用:
- 静态内部类的实例不会持有外部类的引用,因此它们的内存占用相对较小。
- 非静态内部类的实例隐含地持有对外部类实例的引用,因此它们的内存占用相对较大。
- 使用场景:
- 静态内部类适合于独立使用的辅助类,与外部类无关,但又希望能够方便地访问外部类的静态成员。
- 非静态内部类适合于与外部类密切相关的情况,需要访问外部类的实例成员,并且需要共享上下文和数据。