day6(多态、接口)
多态
概述
是指同一行为,具有多个不同表现形式。
概念及语法
概念
多态性,是面向对象中最重要的概念,在Java中的体现:父类的引用指向子类的对象
-
可以直接应用在抽象类和接口上
-
Java引用变量有两个类型:编译时类型和运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。简称:编译时,看左边;运行时,看右边。
-
若编译时类型和运行时类型不一致,就出现了对象的多态性(Polymorphism)
-
多态情况下,“看左边”:看的是父类的引用(父类中不具备子类特有的方法)“看右边”:看的是子类的对象(实际运行的是子类重写父类的方法)
-
语法格式
父类类型 引用 = new 子类对象;
引用.方法名();
示例代码
public abstract class Animal { public abstract void eat(); } class Cat extends Animal { public void eat() { System.out.println("吃鱼"); } } class Dog extends Animal { public void eat() { System.out.println("吃骨头"); } } public class Test { public static void main(String[] args) { // 多态形式,创建对象 Animal a1 = new Cat(); // 调用的是 Cat 的 eat a1.eat(); // 多态形式,创建对象 Animal a2 = new Dog(); // 调用的是 Dog 的 eat a2.eat(); } }
1.3 实现原理
1需要存在继承或者实现关系
2有方法的重写
虚拟方法调用(virtual method invocation)
正常的方法调用
Person e = new Person(); e.getInfo(); Student e = new Student(); e.getInfo();
多态情况下
子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。这也叫做方法的动态绑定
Person e = new Student();
e.getInfo(); //调用Student类的getInfo()方法
编译时类型和运行时类型
编译时e为Person类型,而方法的调用是在运行时确定的,所以调用的是Student类的getInfo()方法。——动态绑定
左边类型是编译时类型,右边类型是运行时类型,调用方法要通过编译,所以父类的必须有调用方法,否者instanceof强转运行时类型调用运行时类型的方法
但是父类是左边可以确定,实例化具体的子类对象不能确定,这就是多态,动态绑定技术。类似于给你一个字,喊你说出这个字开头的成语,成语却不止一种。
内存图
属性不具有多态性
多态的好处
-
可替换性(substitutability)。多态对已存在代码具有可替换性。例如,多态对圆Circle类工作,对其他任何圆形几何体,如圆环,也同样工作。
-
可扩充性(extensibility)。多态对代码具有可扩充性。增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。例如,在实现了圆锥、半圆锥以及半球体的多态基础上,很容易增添球体类的多态性。
-
接口性(interface-ability)。多态是超类通过方法签名,向子类提供了一个共同接口,由子类来完善或者覆盖它而实现的。
-
灵活性(flexibility)。它在应用中体现了灵活多样的操作,提高了使用效率。
-
简化性(simplicity)。多态简化对应用软件的代码编写和修改过程,尤其在处理大量对象的运算和操作时,这个特点尤为突出和重要。
instanceof
操作符
x instanceof A:检验x是否为类A的对象,返回值为boolean型。
-
要求x所属的类与类A必须是子类和父类的关系,否则编译错误。
-
如果x属于类A的子类B,x instanceof A值也为true。
对象类型转换
对Java对象的强制类型转换称为造型(Casting)
-
从子类到父类的类型转换可以自动进行(多态)
-
从父类到子类的类型转换必须通过造型(强制类型转换)实现
-
无继承关系的引用类型间的转换是非法的
-
在造型前可以使用instanceof操作符测试一个对象的类型
示例代码
public class Test { public void method(Person e) { // 设Person类中没有getschool() 方法 // System.out.pritnln(e.getschool()); //非法,编译时错误 if (e instanceof Student) { Student me = (Student) e; // 将e强制转换为Student类型 System.out.pritnln(me.getschool()); } } public static void main(String[] args){ Test t = new Test(); Student m = new Student(); t.method(m); } }
代码块
-
代码块(或初始化块)的作用:
-
对Java类或对象进行初始化
-
-
代码块(或初始化块)的分类:
-
一个类中代码块若有修饰符,则只能被static修饰,称为静态代码块(static block),没有使用static修饰的,为非静态代码块。
-
-
static代码块通常用于初始化static的属性
class Person { public static int total; static { total = 100;//为total赋初值 } …… //其它属性或方法声明 }
-
静态代码块:用static修饰的代码块
-
可以有输出语句。
-
可以对类的属性、类的声明进行初始化操作。
-
不可以对非静态的属性初始化。即:不可以调用非静态的属性和方法。
-
若有多个静态的代码块,那么按照从上到下的顺序依次执行。
-
静态代码块的执行要先于非静态代码块。
-
静态代码块随着类的加载而加载,且只执行一次。
-
-
非静态代码块:没有static修饰的代码块
-
可以有输出语句。
-
可以对类的属性、类的声明进行初始化操作。
-
除了调用非静态的结构外,还可以调用静态的变量或方法。
-
若有多个非静态的代码块,那么按照从上到下的顺序依次执行。
-
每次创建对象的时候,都会执行一次。且先于构造器执行。
-
示例代码
-
变量赋值总结
Object
类
介绍
-
Object类是所有Java类的根父类
-
如果在类的声明中未使用extends关键字指明其父类,则默认父类为
java.lang.Object
类
public class Person { ... } 等价于: public class Person extends Object { ... }
主要结构
equals()方法
-
equals():所有类都继承了Object,也就获得了equals()方法。还可以重写。
-
只能比较引用类型,其作用与“==”相同,比较是否指向同一个对象。
-
格式:obj1.equals(obj2)
-
-
特例:当用equals()方法进行比较时,对类File、String、Date及包装类(Wrapper Class)来说,是比较类型及内容而不考虑引用的是否是同一个对象;
-
原因:在这些类中重写了Object类的equals()方法。
-
-
当自定义使用equals()时,可以重写。用于比较两个对象的“内容”是否都相等
-
重写equals()方法的原则
-
对称性:如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”。
-
自反性:x.equals(x)必须返回是“true”。
-
传递性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。
-
一致性:如果x.equals(y)返回是“true”,只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是“true”。
-
任何情况下,x.equals(null),永远返回是“false”;x.equals(和x不同类型的对象)永远返回是“false”。
-
面试题:==和equals的区别
-
==既可以比较基本类型也可以比较引用类型。对于基本类型就是比较值,对于引用类型就是比较内存地址
-
equals的话,它是属于java.lang.Object类里面的方法,如果该方法没有被重写过默认也是==;我们可以看到String等类的equals方法是被重写过的,而且String类在日常开发中** 用的比较多,久而久之,形成了equals是比较值的错误观点。
-
具体要看自定义类里有没有重写Object的equals方法来判断。
-
通常情况下,重写equals方法,会比较类中的相应属性是否都相等。
toString()方法
-
toString()方法在Object类中定义,其返回值是String类型,返回类名和它的引用地址。
-
在进行String与其它类型数据的连接操作时,自动调用toString()方法
Date now=new Date();
System.out.println(“now=”+now);相当于System.out.println(“now=”+now.toString()); -
可以根据需要在用户自定义类型中重写toString()方法。如String类重写了toString()方法,返回字符串的值。
s1=“hello”;
System.out.println(s1);//相当于System.out.println(s1.toString()); -
基本类型数据转换为String类型时,调用了对应包装类的toString()方法
int a=10; System.out.println(“a=”+a);
接口
概述
-
一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果。
-
另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有is-a的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3机、手机、数码相机、移动硬盘等都 支持USB连接。
-
接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要...则必须能...”的思想。继承是一个"是不是"的关系,而接口实现则是 "能不能"的关系。
-
接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守。
语法
-
接口(interface)是抽象方法和常量值定义的集合。
-
接口的特点:
-
用interface来定义。
-
接口中的所有成员变量都默认是由public static final修饰的。
-
接口中的所有抽象方法都默认是由public abstract修饰的。
-
接口中没有构造器。
-
接口采用多继承机制。
-
-
定义Java类的语法格式:先写extends,后写implements
class SubClass extends SuperClass implements InterfaceA{ }
-
一个类可以实现多个接口,接口也可以继承其它接口。
-
实现接口的类中必须提供接口中所有方法的具体实现内容,方可实例化。否则,仍为抽象类。
-
接口的主要用途就是被实现类实现。(面向接口编程)
-
与继承关系类似,接口与实现类之间存在多态性
-
接口和类是并列关系,或者可以理解为一种特殊的类。从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义(JDK7.0及之前),而没有变量和方法的实现。
接口与抽象类
接口的改进
Java 8中,可以为接口添加静态方法和默认方法。从技术角度来说,这是完全合法的,只是它看起来违反了接口作为一个抽象定义的理念。
-
静态方法:使用 static 关键字修饰。可以通过接口直接调用静态方法,并执行其方法体。我们经常在相互一起使用的类中使用静态方法。你可以在标准库中找到像Collection/Collections或者Path/Paths这样成对的接口和类。
-
默认方法:默认方法使用 default 关键字修饰。可以通过实现类对象来调用。我们在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。比如:java 8 API中对Collection、List、Comparator等接口提供了丰富的默认方法。
内部类
概述
-
当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内部类。
-
在Java中,允许一个类的定义位于另一个类的内部,前者称为内部类,后者称为外部类。
-
Inner class一般用在定义它的类或语句块之内,在外部引用它时必须给出完整的名称。
-
Inner class的名字不能与包含它的外部类类名相同;
-
-
分类:
-
成员内部类(static成员内部类和非static成员内部类)
-
局部内部类(不谈修饰符)、匿名内部类
-
-
成员内部类作为类的成员的角色:
-
和外部类不同,Inner class还可以声明为private或protected;
-
可以调用外部类的结构
-
Inner class 可以声明为static的,但此时就不能再使用外层类的非static的成员变量;
-
-
成员内部类作为类的角色:
-
可以在内部定义属性、方法、构造器等结构
-
可以声明为abstract类 ,因此可以被其它的内部类继承
-
可以声明为final的
-
编译以后生成OuterClass$InnerClass.class字节码文件(也适用于局部内部类)
-
注意
-
非static的成员内部类中的成员不能声明为static的,只有在外部类或static的成员内部类中才可声明static成员。
-
外部类访问成员内部类的成员,需要“内部类.成员”或“内部类对象.成员”的方式
-
成员内部类可以直接使用外部类的所有成员,包括私有的数据
-
当想要在外部类的静态成员部分使用内部类时,可以考虑内部类声明为静态的
示例代码
class Outer { private int s; public class Inner { public void mb() { s = 100; System.out.println("在内部类Inner中s=" + s); } } public void ma() { Inner i = new Inner(); i.mb(); } } public class InnerTest { public static void main(String args[]) { Outer o = new Outer(); o.ma(); } }
内部类的使用语法
如何实例化成员内部类
如何在成员内部类中调用外部类的结构
//创建Dog实例(静态的成员内部类): Person.Dog dog = new Person.Dog(); dog.show(); //创建Bird实例(非静态的成员内部类): //Person.Bird bird = new Person.Bird();//错误的 Person p = new Person(); Person.Bird bird = p.new Bird(); bird.sing();
匿名内部类
匿名内部类不能定义任何静态成员、方法和类,只能创建匿名内部类的一个实例。一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。
格式:
new 父类构造器(实参列表)|实现接口(){ //匿名内部类的类体部分 } new Animal(){ // 方法重写 }
匿名内部类的特点
-
匿名内部类必须继承父类或实现接口
-
匿名内部类只能有一个对象
-
匿名内部类对象只能使用多态形式引用
示例代码
interface A{ public abstract void fun1(); } public class Outer{ public static void main(String[] args) { new Outer().callInner(new A(){ //接口是不能new但此处比较特殊是子类对象实现接口,只不过没有为对象取名 public void fun1() { System.out.println(“implement for fun1"); } });// 两步写成一步了 } public void callInner(A a) { a.fun1(); } }
我的练习:
package Demo1.JavaSe.NeusoftEdu.zuoye; import lombok.AllArgsConstructor; import lombok.Data; /** * @program: FirstDemo * @description: 东软实训第六天 * @author: GuoTong * @create: 2020-08-18 08:43 **/ public class day6 extends TestDay6 { public static void main(String[] args) { day6 day6 = new day6(); //Class 代表.class文件加载到内存中代表这个类的对象的类型。 Class<? extends Demo1.JavaSe.NeusoftEdu.zuoye.day6> aClass = day6.getClass(); //包装类 int i = Integer.parseInt("349"); Integer integer = Integer.valueOf(10); Integer dsfs = Integer.getInteger("dsfs"); //instanceof 关键字:在运行时(动态监测)所属对象 System.out.println(day6 instanceof TestDay6); //配合多态 TestDay6 testDay6 = new day6(); System.out.println(testDay6 instanceof day6); } } class TestDay6 { private String name; private int number; public static void main(String[] args) { /*String str1= “abc”; 在编译期,JVM会去常量池来查找是否存在“abc”, 如果不存在,就在常量池中开辟一个空间来存储“abc”; 如果存在,就不用新开辟空间。 然后在栈内存中开辟一个名字为str1的空间,来存储“abc”在常量池中的地址值。 String str3 = new String("abcd")的实现过程:直接在堆中创建对象。 如果后来又有String str4 = new String("abcd"),str4不会指向之前的对象, 而是重新创建一个对象并指向它,所以如果此时进行str3==str4返回值是false, 因为两个对象的地址不一样,如果是str3.equals(str4),返回true,因为内容相同。 * */ //String 重写了equals String index = "123"; String index1 = "123"; System.out.println(index == index1);//true System.out.println(index.equals(index1));//true String index3 = new String("123456"); String index4 = new String("123456"); System.out.println(index3 == index4);//false //==既可以比较基本类型也可以比较引用类型。对于基本类型就是比较值,对于引用类型就是比较内存地址 System.out.println(index3.equals(index4));//true } } class TestClassNX { public static void main(String[] args) { int it = 65; float fl = 65.0f; System.out.println("65和65.0f是否相等?" + (it == fl));//true char ch1 = 'A';//ASCII:65 char ch2 = 12; System.out.println("65和'A'是否相等?" + (it == ch1));//true System.out.println("12和ch2是否相等?" + (12 == ch2));//true String str1 = new String("hello"); String str2 = new String("hello"); System.out.println("str1和str2是否相等?" + (str1 == str2));//比较地址:false System.out.println("str1是否equals str2?" + (str1.equals(str2)));//true,String 重写过equals // System.out.println("hello" == new java.util.Date());//编译报错 System.out.println("hello" .equals(new java.util.Date()) ); } } @Data @AllArgsConstructor class GeometricObject { private String color; private double weight; public double findArea() { return 1; } public static void main(String[] args) { GeometricObject geometricObject = new Circle11("红色", 10, 20); GeometricObject geometricObject1 = new MyRectangle("绿色", 10, 10, 10); boolean b = equalsArea(geometricObject, geometricObject1); System.out.println(b); } public static boolean equalsArea(GeometricObject geometricObject, GeometricObject geometricObject1) { double area = 0; double area1 = 0; if (geometricObject instanceof Circle11) { area = ((Circle11) geometricObject).findArea(); System.out.println(area); } if (geometricObject1 instanceof MyRectangle) { area1 = ((MyRectangle) geometricObject1).findArea(); System.out.println(area1); } return area == area1; } } class Circle11 extends GeometricObject { private double redius; public Circle11(String color, double weight, double redius) { super(color, weight); this.redius = redius; } @Override public double findArea() { System.out.println("圆形"); return Math.PI * redius * redius; } } class MyRectangle extends GeometricObject { private double width; private double height; public MyRectangle(String color, double weight, double width, double height) { super(color, weight); this.width = width; this.height = height; } @Override public double findArea() { System.out.println("矩形"); return width * height; } } class Personx { protected String name = "person"; protected int age = 50; public String getInfo() { return "Name: " + name + "\n" + "age: " + age; } } class Studentx extends Personx { protected String school = "pku"; public String getInfo() { return "Name: " + name + "\nage: " + age + "\nschool: " + school; } } class Graduatex extends Studentx { public String major = "IT"; public String getInfo() { return "Name: " + name + "\nage: " + age + "\nschool: " + school + "\nmajor:" + major; } } class InstanceTest { public static void method(Personx e) { System.out.println(e.getInfo()); if (e instanceof Personx) System.out.println("a person"); if (e instanceof Studentx) System.out.println("a student"); if (e instanceof Graduatex) System.out.println("a graduated student"); } public static void main(String[] args) { Personx e = new Personx(); method(e); System.out.println("==========="); Personx e1 = new Studentx(); method(e1); System.out.println("==========="); Personx e2 = new Graduatex(); method(e2); } }
个性签名:独学而无友,则孤陋而寡闻。做一个灵魂有趣的人!
如果觉得这篇文章对你有小小的帮助的话,记得在右下角点个“推荐”哦,博主在此感谢!
Java入门到入坟
万水千山总是情,打赏一分行不行,所以如果你心情还比较高兴,也是可以扫码打赏博主,哈哈哈(っ•̀ω•́)っ✎⁾⁾!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端