抽象类
抽象:即不具体、或无法具体
例如:当我们声明一个几何图形类:圆、矩形、三角形类等,发现这些类都有共同特征:求面积、求周长、获取图形详细信息。那么这些共同特征应该抽取到一个公共父类中。但是这些方法在父类中又无法给出具体的实现,而是应该交给子类各自具体实现。那么父类在声明这些方法时,就只有方法签名,没有方法体,我们把没有方法体的方法称为抽象方法。Java语法规定,包含抽象方法的类必须是抽象类
语法格式
抽象方法 : 没有方法体的方法
抽象类:被abstract所修饰的类
抽象类的语法格式
1 【权限修饰符】 abstract class 类名{ 2 3 } 4 【权限修饰符】 abstract class 类名 extends 父类{ 5 6 }
抽象方法的语法格式
1 【其他修饰符】 abstract 返回值类型 方法名(【形参列表】);
抽象方法没有方法体
注意事项
1、抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象
假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义
2、抽象类中,也有构造方法,是供子类创建对象时,初始化父类成员变量使用的
子类的构造方法中,有默认的super()或手动的super(实参列表),需要访问父类构造方法
3、抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类
未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计
4、抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类
假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义
多态
引入
多态是继封装、继承之后,面向对象的第三大特性
生活中,比如求面积的功能,圆、矩形、三角形实现起来是不一样的。跑的动作,小猫、小狗和大象,跑起来是不一样的。再比如飞的动作,昆虫、鸟类和飞机,飞起来也是不一样的。可见,同一行为,通过不同的事物,可以体现出来的不同的形态。多态,描述的就是这样的状态
定义
多态:是指同一个类别的事物的同一行为,在不同的子类别中具有多个不同表现形式
前提
1、继承父类或者实现接口【二选一】
2、方法的重写【意义体现:不重写,无意义】
3、父类引用指向子类对象【格式体现】
多态的体现
1 父类类型 变量名 = 子类对象; 2 变量名.方法名();//这个方法是父类中声明,子类中重写的方法
父类类型:指子类对象继承的父类类型,或者实现的父接口类型
多态体现出来的现象:
编译时,看“父类”,只能调用父类声明的方法,不能调用子类扩展的方法
运行时,看“子类”,一定是执行子类重写的方法体
多态的应用
1、多态参数
实际开发的过程中,父类类型作为方法形式参数,传递子类对象给方法,进行方法的调用,更能体现出多态的扩展性与便利。代码如下:
定义父类:
1 public abstract class Animal { 2 public abstract void eat(); 3 }
定义子类:
1 class Cat extends Animal { 2 public void eat() { 3 System.out.println("吃鱼"); 4 } 5 } 6 7 class Dog extends Animal { 8 public void eat() { 9 System.out.println("吃骨头"); 10 } 11 }
定义测试类:
1 public class Test { 2 public static void main(String[] args) { 3 // 多态形式,创建对象 4 Cat c = new Cat(); 5 Dog d = new Dog(); 6 7 // 调用showCatEat 8 showCatEat(c); 9 // 调用showDogEat 10 showDogEat(d); 11 12 /* 13 以上两个方法, 均可以被showAnimalEat(Animal a)方法所替代 14 而执行效果一致 15 */ 16 showAnimalEat(c); 17 showAnimalEat(d); 18 } 19 20 public static void showCatEat (Cat c){ 21 c.eat(); 22 } 23 24 public static void showDogEat (Dog d){ 25 d.eat(); 26 } 27 28 public static void showAnimalEat (Animal a){ 29 a.eat(); 30 } 31 }
由于多态特性的支持,showAnimalEat方法的Animal类型,是Cat和Dog的父类类型,父类类型接收子类对象,当然可以把Cat对象和Dog对象,传递给方法
当eat方法执行时,多态规定,执行的是子类重写的方法,那么效果自然与showCatEat、showDogEat方法一致,所以showAnimalEat完全可以替代以上两方法
不仅仅是替代,在扩展性方面,无论之后再多的子类出现,我们都不需要编写showXxxEat方法了,直接使用showAnimalEat都可以完成
所以,多态的好处,体现在,可以使程序编写的更简单,并有良好的扩展
2、多态数组
例如:家里养了两只猫,两条狗,想要统一管理他们的对象,可以使用多态数组
1 public class TestAnimal { 2 public static void main(String[] args) { 3 Animal[] all = new Animal[4];//可以存储各种Animal子类的对象 4 all[0] = new Cat(); 5 all[1] = new Cat(); 6 all[2] = new Dog(); 7 all[3] = new Dog(); 8 9 for (int i = 0; i < all.length; i++) { 10 all[i].eat();//all[i]编译时是Animal类型,运行时看存储的是什么对象 11 } 12 } 13 }
父子类之间的类型转换
多态的转型分为向上转型与向下转型两种:
向上转型
向上转型:多态本身是子类类型向父类类型向上转换的过程,这个过程是默认的
当父类引用指向一个子类对象时,便是向上转型
使用格式:
1 父类类型 变量名 = new 子类类型(); 2 如:Animal a = new Cat();
向下转型
向下转型:父类类型向子类类型向下转换的过程,这个过程是强制的
一个已经向上转型的子类对象,将父类引用重新赋值给子类引用,可以使用强制类型转换的格式,便是向下转型
使用格式:
1 子类类型 变量名 = (子类类型) 父类变量名; 2 如:Cat c =(Cat) a;
instanceof运算符
为了避免ClassCastException的发生,Java提供了 instanceof
关键字,给引用变量做类型的校验,只要用instanceof判断返回true的,那么强转为该类型就一定是安全的,不会报ClassCastException异常
格式如下:
1 变量名/对象 instanceof 数据类型 2 3 如果变量/对象属于该数据类型,返回true 4 如果变量/对象不属于该数据类型,返回false
所以,转换前,我们最好先做一个判断,代码如下:
1 public class Test { 2 public static void main(String[] args) { 3 // 向上转型 4 Animal a = new Cat(); 5 a.eat(); // 调用的是 Cat 的 eat 6 7 // 向下转型 8 if (a instanceof Cat){ 9 Cat c = (Cat)a; 10 c.catchMouse(); // 调用的是 Cat 的 catchMouse 11 } else if (a instanceof Dog){ 12 Dog d = (Dog)a; 13 d.watchHouse(); // 调用的是 Dog 的 watchHouse 14 } 15 } 16 }
哪些情况下instanceof判断返回true
示例代码:
1 class Person{ 2 //方法代码省略... 3 } 4 class Woman extends Person{ 5 //方法代码省略... 6 } 7 class ChineseWoman extends Woman{ 8 //方法代码省略... 9 }
1 public class Test{ 2 public static void main(String[] args){ 3 Person p1 = new Person(); 4 Person p2 = new Woman(); 5 Person p3 = new ChineseWoman(); 6 7 if(p1 instanceof Woman){//false 8 9 } 10 if(p2 instanceof Woman){//true 11 //p2转为Woman类型安全 12 } 13 if(p3 instanceof Woman){//true 14 //p3转为Woman类型安全 15 } 16 } 17 }
静态绑定与动态绑定
1、属性:静态绑定
如果直接访问成员变量,那么只看编译时类型
1 public class TestField { 2 public static void main(String[] args) { 3 Father f = new Son(); 4 System.out.println(f.x);//只看编译时类型 5 } 6 } 7 class Father{ 8 int x = 1; 9 } 10 class Son extends Father{ 11 int x = 2; 12 }
2、静态方法:静态绑定
1 public class TestField { 2 public static void main(String[] args) { 3 Father f = new Son(); 4 f.test();//只看编译时类型 5 } 6 } 7 class Father{ 8 public static void test(){ 9 System.out.println("father"); 10 } 11 } 12 class Son extends Father{ 13 public static void test(){ 14 System.out.println("son"); 15 } 16 }
静态方法不能被重写
调用静态方法最好使用“类名.”
3、非静态方法:动态绑定
1 abstract class Animal { 2 public abstract void eat(); 3 } 4 class Cat extends Animal { 5 public void eat() { 6 System.out.println("吃鱼"); 7 } 8 } 9 10 class Dog extends Animal { 11 public void eat() { 12 System.out.println("吃骨头"); 13 } 14 } 15 16 public class Test{ 17 public static void main(String[] args){ 18 Animal a = new Cat(); 19 a.eat(); 20 } 21 }
4、总结:虚方法动态绑定
静态绑定:编译期间就确定访问哪个类的
成员变量
静态方法
私有方法
final方法
动态绑定:运行期间才确定访问哪个类的
可能被子类重写的非静态方法(也称为虚方法)
native关键字
native:本地的,原生的
用法:只能修饰方法
表示这个方法的方法体代码不是用Java语言实现的,而是由C/C++语言编写的
但是对于Java程序员来说,可以当做Java的方法一样去正常调用它,或者子类重写它
JVM内存的管理:
区域名称 | 作用 |
程序计数器 | 程序计数器是CPU中的寄存器,它包含每一个线程下一条要执行的指令的地址 |
本地方法栈 | 当程序中调用了native的本地方法时,本地方法执行期间的内存区域 |
方法区 | 存储已被虚拟机记载的类信息、常量、静态变量、即时编译器编译后的代码等数据 |
堆内存 | 存储对象(包括数组对象),new来创建的,都存储在堆内存 |
虚拟机栈 | 用于存储正在执行的每个Java方法的局部变量表等。局部变量表存放了编译器可知长度的各个基本数据类型、对象引用,方法执行完,自动释放 |
修饰符一起使用的问题
外部类 | 成员变量 | 代码块 | 构造器 | 方法 | 局部变量 | |
public | √ | √ | × | √ | √ | × |
protected | × | √ | × | √ | √ | × |
private | × | √ | × | √ | √ | × |
static | × | √ | √ | × | √ | × |
final | √ | √ | × | × | √ | √ |
abstract | √ | × | × | × | √ | × |
native | × | × | × | × | √ | × |
不能和abstract一起使用的修饰符:
(1)abstract和final不能一起修饰方法和类
(2)abstract和static不能一起修饰方法
(3)abstract和native不能一起修饰方法
(4)abstract和private不能一起修饰方法
static和final一起使用:
(1)修饰方法:可以,因为都不能被重写
(2)修饰成员变量:可以,表示静态常量
(3)修饰局部变量:不可以,static不能修饰局部变量
(4)修饰代码块:不可以,final不能修改代码块
(5)修饰内部类:可以一起修饰成员内部类,不能一起修饰局部内部类
Object根父类
如何理解根父类
类 java.lang.Object
是类层次结构的根类,即所有类的父类。每个类都使用 Object
作为超类
Object类型的变量与除Object以外的任意引用数据类型的对象都多态引用
所有对象(包括数组)都实现这个类的方法。
如果一个类没有特别指定父类,那么默认则继承自Object类。例如:
1 public class MyClass /*extends Object*/ { 2 // ... 3 }
Object类的API
(1)toString()
public String toString()
①默认情况下,toString()返回的是“对象的运行时类型 @ 对象的hashCode值的十六进制形式"
②通常是建议重写,如果在eclipse中,可以用Alt +Shift + S-->Generate toString()
③如果我们直接System.out.println(对象),默认会自动调用这个对象的toString()
因为Java的引用数据类型的变量中存储的实际上时对象的内存地址,但是Java对程序员隐藏内存地址信息,所以不能直接将内存地址显示出来,所以当你打印对象时,JVM帮你调用了对象的toString()
1 public class Person { 2 private String name; 3 private int age; 4 5 @Override 6 public String toString() { 7 return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; 8 } 9 10 // 省略构造器与Getter Setter 11 }
(2)getClass()
public final Class<?> getClass():获取对象的运行时类型
因为Java有多态现象,所以一个引用数据类型的变量的编译时类型与运行时类型可能不一致,因此如果需要查看这个变量实际指向的对象的类型,需要用getClass()方法
1 public static void main(String[] args) { 2 Object obj = new String(); 3 System.out.println(obj.getClass());//运行时类型 4 }
(3)finalize()
protected void finalize():用于最终清理内存的方法
1 public class TestFinalize { 2 public static void main(String[] args) { 3 for (int i = 0; i < 10; i++) { 4 MyData my = new MyData(); 5 } 6 7 System.gc();//通知垃圾回收器来回收垃圾 8 9 try { 10 Thread.sleep(2000);//等待2秒再结束main,为了看效果 11 } catch (InterruptedException e) { 12 e.printStackTrace(); 13 } 14 } 15 } 16 class MyData{ 17 18 @Override 19 protected void finalize() throws Throwable { 20 System.out.println("轻轻的我走了..."); 21 } 22 23 }
对finalize()的理解:
当对象被GC确定为要被回收的垃圾,在回收之前由GC帮你调用这个方法,不是由程序员手动调用。
这个方法与C语言的析构函数不同,C语言的析构函数被调用,那么对象一定被销毁,内存被回收,而finalize方法的调用不一定会销毁当前对象,因为可能在finalize()中出现了让当前对象“复活”的代码
每一个对象的finalize方法只会被调用一次。
子类可以选择重写,一般用于彻底释放一些资源对象,而且这些资源对象往往时通过C/C++等代码申请的资源内存
(4)hashCode()
public int hashCode():返回每个对象的hash值
hashCode 的常规协定:
如果两个对象的hash值是不同的,那么这两个对象一定不相等
如果两个对象的hash值是相同的,那么这两个对象不一定相等
主要用于后面当对象存储到哈希表等容器中时,为了提高存储和查询性能用的
1 public static void main(String[] args) { 2 System.out.println("Aa".hashCode());//2112 3 System.out.println("BB".hashCode());//2112 4 }
(5)equals()
public boolean equals(Object obj):用于判断当前对象this与指定对象obj是否“相等”
①默认情况下,equals方法的实现等价于与“==”,比较的是对象的地址值
②我们可以选择重写,重写有些要求:
A:如果重写equals,那么一定要一起重写hashCode()方法,因为规定:
a:如果两个对象调用equals返回true,那么要求这两个对象的hashCode值一定是相等的
b:如果两个对象的hashCode值不同的,那么要求这个两个对象调用equals方法一定是false
c:如果两个对象的hashCode值相同的,那么这个两个对象调用equals可能是true,也可能是false
B:如果重写equals,那么一定要遵循如下几个原则:
a:自反性:x.equals(x)返回true
b:传递性:x.equals(y)为true, y.equals(z)为true,然后x.equals(z)也应该为true
c:一致性:只要参与equals比较的属性值没有修改,那么无论何时调用结果应该一致
d:对称性:x.equals(y)与y.equals(x)结果应该一样
e:非空对象与null的equals一定是false
1 class User{ 2 private String host; 3 private String username; 4 private String password; 5 public User(String host, String username, String password) { 6 super(); 7 this.host = host; 8 this.username = username; 9 this.password = password; 10 } 11 public User() { 12 super(); 13 } 14 public String getHost() { 15 return host; 16 } 17 public void setHost(String host) { 18 this.host = host; 19 } 20 public String getUsername() { 21 return username; 22 } 23 public void setUsername(String username) { 24 this.username = username; 25 } 26 public String getPassword() { 27 return password; 28 } 29 public void setPassword(String password) { 30 this.password = password; 31 } 32 @Override 33 public String toString() { 34 return "User [host=" + host + ", username=" + username + ", password=" + password + "]"; 35 } 36 @Override 37 public int hashCode() { 38 final int prime = 31; 39 int result = 1; 40 result = prime * result + ((host == null) ? 0 : host.hashCode()); 41 result = prime * result + ((password == null) ? 0 : password.hashCode()); 42 result = prime * result + ((username == null) ? 0 : username.hashCode()); 43 return result; 44 } 45 @Override 46 public boolean equals(Object obj) { 47 if (this == obj) 48 return true; 49 if (obj == null) 50 return false; 51 if (getClass() != obj.getClass()) 52 return false; 53 User other = (User) obj; 54 if (host == null) { 55 if (other.host != null) 56 return false; 57 } else if (!host.equals(other.host)) 58 return false; 59 if (password == null) { 60 if (other.password != null) 61 return false; 62 } else if (!password.equals(other.password)) 63 return false; 64 if (username == null) { 65 if (other.username != null) 66 return false; 67 } else if (!username.equals(other.username)) 68 return false; 69 return true; 70 } 71 72 }