Java【13_2】多态、根父类
1. 多态 ★
面向对象三大特性之一!
1.1 定义
是指同一行为,具有多个不同表现形式(重载和重写)
1.2 前提
① 父子类【继承父类或者实现接口】
② 必须有重写
③ 父类引用指向子类对象
1.3 多态的语法
父类引用指向子类对象
举例:Person per1 =new Employee();
per1.eat();//从名义上是调用的Person类的eat方法,但是实际是员工餐
1.4 多态的注意事项
使用不了子类独有的内容!
因为名义是父类类型,就只能调用到父类中存在的,如果子类有重写
这个是可以调用到子类的方法的!如果没有重写就是父类的
总结:多态只能调用父类的内容!
成员变量
子类独有的访问不到,如果有重名属性,调用属性看类型!
1.5 多态的用途
① 多态参数 ★
② 多态数组 -> 对象数组的升级
2. 父子类之间的类型转换
2.1 自动转换(向上转型-多态的提现) 小->大
2.2 强制转换(向下转型) 大->小
因为多态,调用不到子类独有的内容!我就是想调用(很少见)
向下转型是有风险!所以以后能少强转就少强转!
语法:子类类型 对象名=(子类类型)父类引用;
强转之前一定要做判断!
instanceof 判断前面的对象是否属于后面的类型
举例:boolean flag=employee instanceof Employee;
练习:
创建若干个Student或者Employee对象,存储起来!
循环遍历每个成员,调用其eat方法!
如果是员工就调用working
如果是学生就调用学习方法
3. native 本地的、原生的
只能修饰方法
追踪源码的时候,会见到这个关键字!方法不是java代码实现,是c语言实现
native修饰的方法,是可以被重写的!
3.1 不能和abstract一起使用的修饰符?
(1)final:和final不能一起修饰方法和类
(2)static:和static不能一起修饰方法
(3)native:和native不能一起修饰方法 记住
(4)private:和private不能一起修饰方法
3.2 static和final一起使用:
(1)修饰方法:可以,因为都不能被重写
(2)修饰成员变量:可以,表示静态常量
(3)修饰局部变量:不可以,static不能修饰局部变量
(4)修饰代码块:不可以,final不能修饰代码块
(5)修饰内部类:可以一起修饰成员内部类,不能一起修饰局部内部类(后面讲)
4. Object 根父类
默认是类的父类!该类中所有的方法都可以被任意对象继承下去!
根据api去学!
构造器:
Object();
方法:
① toString();
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
② Class getClass();反射位置,会遇到此方法!
功能:返回对象的运行时类型(类加载的时候,会创建一个Class对象)
③ int hashCode();
将当前对象,通过哈希算法,得到一个int值
两对相同对象(地址一样的):经过相同的hash算法,得到的int值肯定是一样的!
两个不同的对象:经过相同的hash算法,得到的int值,也有可能一样,大概率是不一样的
集合-->HashMap集合的时候,会遇到hashCode方法!
④ finalize()
java存在垃圾回收机制的!(自动的机制)
java中什么样的对象,会被认为是垃圾?
没有引用的对象
java中被认为是垃圾的对象,什么时候会被垃圾回收机制回收?
不定时回收
垃圾对象被回收的时候,是会默认调用该对象的finalize方法!
目的:是让这个对象做一个临终遗言(这个方法不是回收的代码)
⑤ equals(Object obj)
判断两个对象是否一致!
== 主要是判断两个地址是否一致!
源码:和==没有区别
public boolean equals(Object obj) {
return (this == obj);
}
5. String类的equals方法
两个对象,this/anObject对比内容是否一致
public boolean equals(Object anObject) {
//地址是否一样,如果地址一样,内容肯定一样
if (this == anObject) {
return true;
}
//判断anObject是否是String类型,如果不是String类型,直接返回false
if (anObject instanceof String) {
//向下转型
String anotherString = (String)anObject;
//char[] value; n->this的字符串长度
int n = value.length;
//判断长度是否一样,如果长度不一样,返回false
if (n == anotherString.value.length) {
//v1是this的字符串内容
char v1[] = value;
//v2是参数字符串的内容
char v2[] = anotherString.value;
//挨个字符的对比,从头到尾,中途有不一样的,返回false
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
//从头到尾都没有不一样的,说明每个字符都一样(内容是一样的)
return true;
}
}
return false;
}
练习:
创建两个Person对象,我认为,只要name和id都一样,就是同一个人!
在Person类中,重写equals方法!(对比内容,id和name)
6. 接口
usb接口、type-c接口...
usb接口的规范又是哪来的?有人定制了这个规范,然后厂商按照这个规范生产
接口-->定义规则
6.1 接口的语法
【修饰符】interface 接口名{} -->类似于类的语法
定义接口的修饰符和类的修饰符知识点一样!
6.2 分类
① JDK1.8之前
a. 接口中所有的成员变量,默认都是公有的静态常量(不允许修改访问修饰符)
b. 接口中所有的成员方法,默认都是公有的抽象方法
c. 接口中是没有构造器的
d. 没有初始化块
② JDK1.8的新特性
默认方法和静态方法
6.3 特点:
a. 接口不能实例化对象
b. 只能作为父级(父接口)
子类继承父类
实现类实现父接口
c. 实现类实现父接口,并实现父接口中所有的抽象方法
public class Aircraft implements Fly {抽象方法实现}
d. 如果类有父接口,还可以有父类吗?可以的!
类只能有一个父类,可以有多个父接口!
public class Aircraft extends Person implements Fly,Attack {
6.4 关系
a. 类与类之间:单继承
b. 类与接口之间:多实现
c. 接口与接口之间:多继承
讲课顺序:
JDK1.8新特性
冲突问题如何解决
介绍经典的接口(感受接口的用途) 好处:解耦合
示例:1. 多态 ★
//是指同一行为,具有多个不同表现形式(重载和重写)
//使用不了子类独有的内容!
public class Person {
public int id;
public String name="父亲";
public Person() {
super();//父类中没有这个构造器
}
public void eat(){
System.out.println("吃饭");
}
public void run(){
System.out.println("奔跑");
}
}
public class Employee extends Person {
public double salary=2000;
public String name="打工人";
@Override
public void eat() {
System.out.println(name+"吃员工餐"+salary);
}
public void working(){
System.out.println("打工人在工作");
}
/*public String toString(){
return name+"\t"+salary;
}*/
//必须要调用才可以!
public String getInfo(){
return name+""+salary;
}
@Override
public String toString() {
return "Employee{" +
"salary=" + salary +
", name='" + name + '\'' +
'}';
}
}
public class Student extends Person {
public double score;
@Override
public void eat() {
System.out.println("吃食堂。。。");
}
public void study(){
System.out.println("在学习");
}
}
public class Demo1 {
public static void main(String[] args) {
//父类的引用指向子类的对象
//per1的类型是Person,但是实际类型是Employee
Person per1 =new Employee();
per1.eat();//从名义上是调用的Person类的eat方法,但是实际是员工餐
//per1.working();//【报错】因为是子类独有方法,调用不到
//per1.run();
//System.out.println(per1.id);
//System.out.println(per1.name);//name调用的是谁的?
//System.out.println(per1.salary);//【报错】这也是子类中的,调用不到
//属性看类型 方法看对象
//per2的类型是Person,但是实际类型是Student
Person per2=new Student();
per2.eat();//名义上是Person,实际是食堂
//per2.study();//【报错】……
}
}
D:\javademo\day13_am\Demo>javac Demo1.java
D:\javademo\day13_am\Demo>java Demo1
打工人吃员工餐2000.0
吃食堂。。。
示例:延续上面代码,多一个Demo2.java测试
【04】38:30 通过传参体现的
//person.eat();//同一个行为,具有不同的体现形式
public class Demo2 {
public static void main(String[] args) {
Employee employee=new Employee();
method(employee);//Employee对象是可以通过参数传递给,Person类型的
Student student=new Student();
method(student);//Student对象也是可以通过参数传递给Person类型的
}
public static void method(Person person){//Person person=new Employee();
person.eat();//同一个行为,具有不同的体现形式
}
/*
安排所有人吃饭
*/
/*public static void method1(Employee employee){
employee.eat();
}
public static void method2(Student stu){
stu.eat();
}*/
}
【08】00:20
属性、静态方法没有多态性
因为多态前提:有重写,属性是没有覆盖的。属性只看它前面的类型是谁。
因为静态方法不允许 重写。
示例:为什么 str.toString() 不是返回地址呢?
【路径:D:\javademo\day13_pm\Employee.java】
package com.atguigu.bean;
public class Employee {
public double salary=2000;
public String name="打工人";
/*@Override
public String toString() {
return "Employee{" +
"salary=" + salary +
", name='" + name + '\'' +
'}';
}*/
}
【路径:D:\javademo\day13_pm\Demo5.java】
package com.atguigu.demo;
import com.atguigu.bean.Employee;
public class Demo5 {
public static void main(String[] args) {
Employee employee=new Employee();
Object obj=new Object();//这个对象没有什么意义(供子类使用)
String s=employee.toString();
System.out.println(s);//输出的是地址经过处理的!地址
System.out.println(employee);
}
}
D:\javademo\day13_pm>javac -d . Employee.java
D:\javademo\day13_pm>javac -d . Demo5.java
D:\javademo\day13_pm>java com.atguigu.demo.Demo5
com.atguigu.bean.Employee@15db9742
com.atguigu.bean.Employee@15db9742
【idea软件中】上面代码中,按住ctrl点击 toString() 跳到源代码如下:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
【结论】所有的对象,在输出或者拼接的时候,都会默认调用该对象的toString方法!(你不写toString,也会调)
该toString方法,把当前对象的地址值算成“哈希值”,然后再转成16进制,
"@" 前面是对象运行时类型
"@" 后面可以看成是地址值
下面,来试试String类的toString
public class Demo5 {
public static void main(String[] args) {
//创建String对象
String str=new String("abc");
System.out.println(str.toString());
System.out.println(str);//为什么输出的不是地址呢?输出的是值
}
}
D:\javademo\day13_pm>java com.atguigu.demo.Demo5
abc
abc
【此时】按住ctrl点击 toString() 跳到源代码如下:
public String toString() {
return this;
}
【思考】为什么调用的是自己的toString呢?为什么不是Object中的toString?
【因为】因为重写了!!!
【因为】因为String觉得Object类中的toString不能满足我的需求,所以重写!
粗略看一下String这个类:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
……
public String toString() {
return this;
}
……
}
【上上面employee对象】
直接输出employee对象的时候,输出其所有属性信息!该如何操作呢?
Object中toString不能满足我的要求,所以我也要重写!
【上上面的灰色注释去掉,相当于定义自己的重写】
D:\javademo\day13_pm>java com.atguigu.demo.Demo5
Employee{salary=2000.0, name='打工人'}
Employee{salary=2000.0, name='打工人'}
【上上面Object的toString中getClass() 转到定义】
public final native Class<?> getClass();
这个是用 native修饰的,没有方法体,但它并不是抽象方法,它是通过C语言去实现的,这种方法只能记住方法的功能就可以了。没办法代码追踪了。
getClass()
功能:返回对象的运行时类型(类加载的时候,会创建一个Class对象)
示例:getClass() 返回类型【反射时会用到】,对象== 判断的是地址
【路径:D:\javademo\day13_pm\Demo6.java】
package com.atguigu.demo;
import com.atguigu.bean.Employee;
public class Demo6 {
public static void main(String[] args) {
Employee employee=new Employee();
System.out.println(employee.getClass());//返回employee对象在运行是的类型
Employee employee1=new Employee();
System.out.println(employee==employee1);//false
System.out.println(employee1.getClass());
System.out.println(employee.getClass()==employee1.getClass());//true
}
}
D:\javademo\day13_pm>java com.atguigu.demo.Demo6
class com.atguigu.bean.Employee
false
class com.atguigu.bean.Employee
true
【上上面Object的toString中hashCode() 转到定义】也是 native修饰的
public native int hashCode();
将当前对象,通过哈希算法,得到一个int值
示例:两对相同对象(地址一样的):经过相同的hash算法,得到的int值肯定是一样的!
【两个不同的对象:经过相同的hash算法,得到的int值,也有可能一样,大概率是不一样的】
public class Demo6 {
public static void main(String[] args) {
Employee employee=new Employee();
System.out.println(employee.hashCode());
Employee employee1=employee;
System.out.println(employee1.hashCode());
Employee employee2=new Employee();
System.out.println(employee2.hashCode());
}
}
D:\javademo\day13_pm>java com.atguigu.demo.Demo6
366712642
366712642
1829164700
示例:finalize() 垃圾回收
【路径:D:\javademo\day13_pm\Student.java】
package com.atguigu.bean;
public class Student {
public String name;
@Override
protected void finalize() throws Throwable {
System.out.println(this.name+"被回收了!");
}
}
【路径:D:\javademo\day13_pm\Demo6.java】
package com.atguigu.demo;
import com.atguigu.bean.Employee;
import com.atguigu.bean.Student;
public class Demo6 {
public static void main(String[] args) {
Student student=new Student();
student.name="张三";
Student student1=new Student();
student1.name="李四";
student=new Student();//王五的地址将张三的地址给覆盖掉了!
student.name="王五";
student1=new Student();//没有姓名的学习将李四的地址给覆盖了!
//张三就是垃圾对象,他就会被垃圾回收机制回收!
//在回收的时候,会自动调用张三的finalize方法!但是Object类继承下来的方法内
//什么都没有写,如果我想查看是不是张三被回收!
//Student类重写finalize方法,重写之后,调用的就是重写之后的!
System.gc();//通知垃圾回收机制来回收垃圾!(也不会立刻来回收垃圾对象!)
//程序会睡眠2秒钟,这个时间段内,垃圾回收机制就会运行!然后就可以看到效果
//【这边垃圾回收挺快的,几十毫秒吧】
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("睡醒了");
}
}
D:\javademo\day13_pm>javac -d . Student.java
D:\javademo\day13_pm>javac -d . Demo6.java
D:\javademo\day13_pm>java com.atguigu.demo.Demo6
李四被回收了!
张三被回收了!
睡醒了
示例:String之==的内存图
public class Demo7 {
public static void main(String[] args) {
String str1="java";
String str2="java";
String str3=new String("java");
System.out.println(str1==str2);//判断两个对象是否相等 true
System.out.println(str1==str3);//判断两个对象是否相等 false
/* 同理,用这种方式输入“java”判断也是 false
java.util.Scanner input=new java.util.Scanner(System.in);
String str3=input.next();//输入:java
System.out.println(str1==str3);//false */
}
}
D:\javademo\day13_pm>javac Demo7.java
D:\javademo\day13_pm>java Demo7
true
false
为什么呢?看看内存图!
常量池-【jdk不同版本,在不同的位置】
1.6方法区内
1.7堆内存
1.8元空间
因为方法区太小,放常量池里面会有很多常量,会放不下;
然后堆内存很大,东西也多呀,后来就研发出元空间。
示例:equals(Object obj)
public class Person { }
public class Demo7 {
public static void main(String[] args) {
Person per1=new Person();
Person per2=per1;
Person per3=new Person();
System.out.println(per2==per1);//true
System.out.println(per1==per3);//false per1和per3的地址是不一样的
System.out.println(per1.equals(per3));//判断per1和per3是否一致 false
System.out.println("------------");
String str1="java";
String str2="java";
String str3=new String("java");
System.out.println(str1==str2);//判断两个对象是否相等 true
System.out.println(str1==str3);//判断两个对象是否相等 false
System.out.println("String-equals:"+str1.equals(str3));//true对比的是内容!
//这是为什么?此equals非彼equals!该equals是String重写后的!
}
}
D:\javademo\day13_pm>javac Demo7.java
D:\javademo\day13_pm>java Demo7
true
false
false
------------
true
false
String-equals:true
【分析】
per1.equals 点进去看看 equals 源码【跟==没有区别】:
public boolean equals(Object obj) {
return (this == obj);
}
str1.equals 点进去看看 equals 源码:【该equals是String重写后的!】
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
下面一行行解释:
public boolean equals(Object anObject) {
//地址是否一样,如果地址一样,内容肯定一样
if (this == anObject) {
return true;
}
//判断anObject是否是String类型,如果不是String类型,直接返回false
if (anObject instanceof String) {
//向下转型
String anotherString = (String)anObject;
//char[] value; n->this的字符串长度
int n = value.length;
//判断长度是否一样,如果长度不一样,返回false
if (n == anotherString.value.length) {
//v1是this的字符串内容
char v1[] = value;
//v2是参数字符串的内容
char v2[] = anotherString.value;
//挨个字符的对比,从头到尾,中途有不一样的,返回false
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
//从头到尾都没有不一样的,说明每个字符都一样(内容是一样的)
return true;
}
}
return false;
}
【练习】
创建两个Person对象,我认为,只要name和id都一样,就是同一个人!
在Person类中,重写equals方法!(对比内容,id和name)
public class Person {
public int id;
public String name;
@Override
public boolean equals(Object obj) {
if(this==obj)
return true;
if(obj instanceof Person){
Person person=(Person)obj;
if(this.id==person.id&&this.name.equals(person.name))
//此处的equals并不是我们正在重写的equals
return true;
}
return false;
}
}
public class Demo7 {
public static void main(String[] args) {
Person per4=new Person();
per4.id=1;
per4.name="张三";
Person per5=new Person();
per5.id=1;
per5.name="张三";
System.out.println(per4==per5);
System.out.println(per4.equals(per5));
}
}
D:\javademo\day13_pm\Demo>javac Demo7.java
D:\javademo\day13_pm\Demo>java Demo7
false
true
示例:接口继承多接口【接口更加纯粹的抽象类】
public interface Fly extends Aa,Bb,Cc{
int id=1;//公有静态常量
//public Fly(){} 没有构造器
//static{}
void method();//公有的抽象方法
int function(int a,String b,Person p);//公有的抽象方法
}
interface Aa{
void aa();
}
interface Bb{
void bb();
}
interface Cc{
void cc();
}
public class Demo8 {
public static void main(String[] args) {
System.out.println(Fly.id);
//接口名.调用到id,说明id是静态的
//跨包调用,说明id是公有的
//Fly.id=10;
//不可以修改id的值,说明是常量
//Fly fly=new Fly();//接口是不能实例化对象的
}
}
D:\javademo\day13_pm>javac Demo8.java
D:\javademo\day13_pm>java Demo8
1