java的多态性
多态性在面向对象中是一个最重要的概念,在Java中面向对象主要有以下两种主要体现:方法的重载与覆写、对象的多态性。实际上对象的多态性体现在对象的向上转型和向下转型。
对象的向上转型
在继承关系中,子类对象向上转型成父类对象的过程就叫做向上转型。向上转型可以自动完成。只需要去定义父类类型的对象去接收就可以了,而在调用方法时,将调用子类覆写以后的方法。并且不能调用单独在子类中定义的方法。
其语法:父类 父类对象 = 子类对象;
父类:
public class Person {
private String name;
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void say(){
System.out.println("姓名:"+this.name+" 年龄:"+this.age);
}
public void play(){
System.out.println("打牌");
}
}
子类:
public class Student extends Person {
private String school;
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
@Override
public void say() {
System.out.println("姓名:"+this.getName()+" 年龄:"+this.getAge()+" 学校:"+this.school);
}
public void study(){
System.out.println("学习");
}
}
测试:
public class Test4 {
public static void main(String[] args) {
Student stu = new Student();
//向上转型
Person per = stu;
//被子类覆写以后的方法
per.say();
per.play();
//不能调用的
//per.study();
}
}
对象的向下转型
对象的向上转型指的是父类对象向子类对象的转换过程,向下转型的过程必须强制完成。而要想强制转换能够成功,那么该对象必须首先发生对象的向上转型。
public class Test4 {
public static void main(String[] args) {
Person per = new Person();
Student stu = (Student)per;
System.out.println(stu);
}
}
如果直接将一个父类的对象转换成子类对象,此时会出现类转换异常:java.lang.ClassCastException
public class Test4 {
public static void main(String[] args) {
Person per = new Student();
Student stu = (Student)per;
System.out.println(stu);
}
}
从以上的代码发现,向下转型的意义是存在问题。
使用instanceof关键字去判断对象的类型
在进行向下转型之前,首先需要去判断向下转型的对象是不是一个子类对象,那么这种判断可以使用instanceof来完成。
在进行对象的向下转型过程中,我们必须首先确认是否发生了对象的向上转型,即要向下转型的对象从实质上是不是一个子类的对象。那么针对于这种对象所属类型的判断,可以使用instanceof关键字来完成。
Java中使用Instanceof关键字判断一个对象到底是哪一个类的对象。那么在对象向下转型前可以使用该关键字来进行对象的验证。
public class Test4 {
public static void main(String[] args) {
Person per = new Person();
//使用instanceof进行判断
if(per instanceof Student){
System.out.println("本质上是子类对象");
Student stu = (Student)per;
System.out.println(stu);
}else{
System.out.println("不是子类对象");
}
}
}
抽象类的使用
如果一个父类只代表一类事物的抽象的概念,那么这个类就可以定义成抽象类。
包含抽象方法的类必须是抽象类,使用abstract关键字生命的方法就叫做抽象方法,抽象方法只需要声明,不需要进行具体的实现,抽象类可以包含抽象方法,也可以包含普通的成员方法。
其语法如下:
访问修饰权限 abstract class 类名{
属性;
方法;
访问修饰权限 abstract 返回值类型 方法名(参数列表);
}
抽象类不能实例化对象,要使用抽象必须去定义抽象类的子类,子类必须去覆写抽象类中全部的抽象方法。
public abstract class Fruit {
public abstract void eat();
}
public class Apple extends Fruit {
@Override
public void eat() {
System.out.println("洗洗就可以吃了");
}
}
public class Test {
public static void main(String[] args) {
Fruit f = new Apple();
f.eat();
}
}
接口的使用
接口是Java中最重要的概念,接口可以理解为一种特殊的类,里面全部是由全局常量和公共的抽象方法所组成。其定义语法:
访问修饰符 interface 接口名称{
全局常量;
公共的抽象方法;
}
public interface A {
//全局常量
public static final String MSG = "info";
//定义公共的抽象方法
public abstract void fun();
}
既然接口中只能有全局常量和公共的抽象方法,以上的定义形式也可以简单化。
public interface A {
//全局常量
String MSG = "info";
//定义公共的抽象方法
void fun();
}
由于接口中存在抽象方法,那么接口不能直接实例化对象,要使用接口那么必须定义接口的实现类。如果实现类不是抽象类,那么必须覆写接口中全部的抽象方法。通过使用implements关键字来完成。
public class AImpl implements A {
@Override
public void fun() {
String msg = this.MSG;
System.out.println(msg);
}
}
接口的使用时相当灵活的,使用接口也可以一定程度上接触代码的耦合性。提高代码的可读性。
一个类可以实现多个接口,来同时获取者多个接口中的功能。
接口:
public interface InterfaceA {
void swim();
}
public interface InterfaceB {
void fly();
}
public class CImpl implements InterfaceA,InterfaceB{
@Override
public void fly() {
}
@Override
public void swim() {
}
}
同时接口可以进行多继承,这也是java中唯一的多继承
public interface A {
void fun1();
}
public interface B {
void fun2();
}
public interface C extends A, B {
void fun3();
}
public class CImpl implements C{
@Override
public void fun1() {
}
@Override
public void fun2() {
}
@Override
public void fun3() {
}
}
在使用接口的同时,一个类可以既实现接口同时也继承另一个类,这样就避免了java单继承的局限性。
public abstract class AbstractD {
public abstract void jump();
}
public class CImpl extends AbstractD implements InterfaceA,InterfaceB{
@Override
public void fly() {
}
@Override
public void swim() {
}
@Override
public void jump() {
}
}
抽象类和接口的对比,在既能使用抽象类又能使用接口的情况下,优先使用,这样可以避免单继承的局限性。
Object类的使用
在Java中所有的类都有一个公共的父类Object,一个类只要没有明显地继承一个类,则肯定是Object类的子类。Object可以接受任意引用类型的对象,Object类中提供了很多的方法供开发者进行调用。
Object接受任意对象
public class Test {
public static void main(String[] args) {
Person per = new Person();
Pet pet =new Pet();
print(pet);
}
public static void print(Object o){
System.out.println(o);
}
}
Object类的常用方法
Object也同时定义了一些常用的方法以供开发人员使用。
Equals()方法,默认是去比较两个对象的地址,如果地址一致,那么返回真,否则返回假,但是比较地址的方式相对不太常用,因为比较地址可以使用“==“,那么在实际开发中经常需要覆写该方法来实现对对象的内容的比较。
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Person(){
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object obj) {
if(this == obj){
return true;
}else if(obj instanceof Person){
Person p = (Person)obj;
if(this.getName().equals(p.getName())&&this.getAge()==p.getAge()){
return true;
}
}
return false;
}
}
toString()在Object中默认是返回当前对象的地址的形式,当我们直接访问类的对象的时候,程序会自动调用该类中的toString方法。
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Person(){
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object obj) {
if(this == obj){
return true;
}else if(obj instanceof Person){
Person p = (Person)obj;
if(this.getName().equals(p.getName())&&this.getAge()==p.getAge()){
return true;
}
}
return false;
}
@Override
public String toString() {
return "name=" + name + ", age=" + age;
}
}
toString主要的作用就是去输出对象中属性的值,String中已经完成对toString方法的覆写,看来实现返回String对象的内容。
包装类
Java是面向对象的,一切都是类和对象为处理问题的基本组成,但是有一类形式比较特殊,就是基本数据类型。
基本数据类型对应的包装类
Integer类型的构造方法的使用:
public class IntegerDemo {
public static void main(String[] args) {
int n = 10;
Integer integer = new Integer(n);
//直接输出包装类的对象,完成自动的拆箱的操作
System.out.println(integer);
}
}
public class IntegerDemo {
public static void main(String[] args) {
String n = "aaa123";
Integer integer = new Integer(n);
//直接输出包装类的对象,完成自动的拆箱的操作
System.out.println(integer);
}
}
以上的操作将出现java.lang.NumberFormatException
自动装箱和拆箱
在了解了包装类的概念以后,下面我们来介绍包装类的装箱和拆箱的概念。将一个基本数据类型变为包装类的对象,这样的过程称为装箱操作,而将一个包装类的对象变为基本数据类型的过程称之为拆箱操作。在JDK1.5以后提供了自动拆箱与装箱操作。
public class IntegerDemo {
public static void main(String[] args) {
int n = 10;
//自动的装箱操作
Integer integer = n;
//自动的拆箱操作
int m = integer;
System.out.println(m);
}
}
包装类中提供了将字符串转换成基本数据类型的方法。
public class IntegerDemo {
public static void main(String[] args) {
String ss = "12345";
int n = Integer.parseInt(ss);
System.out.println(n+1);
}
}
内部类和匿名内部类
内部类就是定义在一个类的内部的类,如果在一个类Outer的内部再定义一个类Inner,那么类Inner就是内部类,Outer则称为外部类。
内部类可以使用private或者public修饰,其访问限制与类中的属性相同。
public class Outter {
private String info;
public void fun(){
System.out.println(info);
}
//声明一个内部类
public class inner{
public void fun2(){
System.out.println("这是内部类的方法");
}
}
}
内部类对象的实例化
从定义的语法来看,内部类相当于外部类中的一部分,类是可以通过构造方法来完成实例化,但是该类处于外部类的内部,一个类的内部的普通的成员变量或者方法是必须通过类的对象来进行调用,此时可以得出结论,如果要实例化内部类的对象,必须首先实例化外部类的对象,由外部类的对象调用内部类的构造方法来完成内部类对象的实例化。
public class Test {
public static void main(String[] args) {
Outter out = new Outter("Hello");
Outter.Inner in = out.new Inner();
in.fun2();
}
}
以上的实例化内部类对象的操作也可以变为一下的形式:
public class Test {
public static void main(String[] args) {
Outter.Inner in = new Outter("Hello").new Inner();
in.fun2();
}
}
以上的代码可以发现,内部类的一个主要的作于可以非常方便地访问外部类中的私有的成员变量,但是也同时发现了问题,这种形式打破了原有的类的结构,似的代码的可读性降低。
使用static修饰内部类
使用static可以声明属性或者方法,那么这里也可以使用static修饰内部类,而使用static修饰的内部类将成为外部类。但是使用static修饰的内部类不能访问非static修饰的外部类的属性。
public class Test {
public static void main(String[] args) {
Outter.Inner in = new Outter.Inner();
}
}
使用static修饰内部类以后,内部类只能访问外部类中的static修饰的属性或者方法。
public class Outter {
private static String info = "Hello";
public Outter(String info){
this.info = info;
}
public static void fun(){
System.out.println(info);
}
//声明一个内部类
public static class Inner{
public void fun2(){
fun();
System.out.println(info);
}
}
}