Java高级类特性(一)
一、继承性
1)继承的使用:权限修饰符 class A extends B{};
2)子类:A 父类(基类 SuperClass):B
3)子类继承父类后,父类中声明的属性、方法,子类都可以获取到明确:当父类中有私有的属性或方法时,子类同样可以获取到,只是由于封装性的设计,使得子类不可以直接调用罢了。
4)子类除了通过继承,获取父类的结构之外,还可以定义自己的特有的成分。extends:子类是对父类功能的“拓展”,明确子类不是父类的子集
5)java中类的继承性只支持单继承,一个类只能继承一个父类,但一个父类可以有多个子类。
6)子父类是相对而言的概念。
7)子类继承父类,在创建子类的对象的时候会先调用父类的构造器再调用子类的构造器。
二、方法的重写
子类扩展(extends)了父类,子类是一种特殊的父类。子类可以获得父类所有的属性和方法,达到代码复用的目的。子类总以父类为基础增加新的属性和方法。但是有时候我们还需要重写(覆盖)父类的方法。
子类覆盖父类的方法遵循的是“两同两小一大”原则。两同指的是方法名和形参列表相同。两小指的是子类方法的返回值类型要小于或等于父类被覆盖方法的返回值,子类方法声明抛出的异常应比父类方法抛出的异常要小或相等。一大指的是子类方法的访问权限应该比父类方法要大或相等。尤其要注意的是,覆盖方法和被覆盖方法要么都是类方法,要么都是实例方法,不能一个是类方法,一个是实例方法,否则回报编译错误。代码如下:
class SuperClass{
public void test(){}
public void test1(){}
}
class SubClass extends SuperClass{
//注释掉了,编译时会报This static method cannot hide the instance method from SuperClass
//public static void test(){}
@Override
public void test1(){}
}
如果父类方法具有private访问权限,则该方法对子类是隐藏的,子类是不能访问到的,也不能覆盖该方法。如果子类定义了与父类private方法相同的方法名,则该子类只不过是新定义了一个方法而已。
三、权限修饰符
1、Java中有四种访问权限,private、default(一般省略)、public、protected。
1.private: Java语言中对访问权限限制的最窄的修饰符,一般称之为“私有的”。被其修饰的属性以及方法只能被该类的对象访问,其子类不能访问,更不能允许跨包访问。
2.default:即不加任何访问修饰符,通常称为“默认访问权限“或者“包访问权限”。该模式下,只允许在同一个包中进行访问。
3.protected: 介于public 和 private 之间的一种访问修饰符,一般称之为“保护访问权限”。被其修饰的属性以及方法只能被类本身的方法及子类访问,即使子类在不同的包中也可以访问。
4.public: Java语言中访问限制最宽的修饰符,一般称之为“公共的”。被其修饰的类、属性以及方法不仅可以跨类访问,而且允许跨包访问。
这里需要注意的是,所谓的访问,可以分为两种不同方式:第一,通过对象实例访问;第二,直接访问。
比如说,某父类protected权限的成员,子类是可以直接访问的,换一种说话是子类其实继承了父类的除了private成员外的所有成员,包括protected成员,所以与其说是子类访问了父类的protected成员,不如说子类访问了自己的从父类继承来的protected成员。另一方面,如果该子类与父类不在同一个包里,那么通过父类的对象实例是不能访问父类的protected成员的。
2、下面用表格来展示四种修饰符的访问权限范围
权限 | 类内 | 同包 | 不同包子类 | 不同包非子类 |
---|---|---|---|---|
private | √ | × | × | × |
default | √ | √ | × | × |
protected | √ | √ | √ | × |
public | √ | √ | √ |
√ |
四、super关键字的使用
package com.test.java;
/*
* super可以用来修饰属性、方法、构造器
* 1)当子类与父类中有同名的属性时,可以通过"super.属性"显式的调用父类中声明的属性。用"this.属性"调用子类中声明的属性
* 2)当子类重写父类的方法以后,在子类中想再显式的调用父类的被重写的方法,需要用"super.方法来调用"
* 3)super修饰构造器:通过在子类中使用"super(形参列表)"来显式的调用父类的构造器
* >在构造器内部,"super(形参列表)"必须要声明在首行
* >在构造器内部,"this.(形参列表)"与"super(形参列表)"只能出现一个
* >当构造器中,不显式的调用this()或者super()其中任何一个,默认调用的是父类的空参构造器
* 如果父类没有声明空参构造器,或者只声明了带参数的构造器(这时空参构造器自动作废),在子类
* 中会报错
* 建议:在设计一个类时,尽量提供一个空参构造器。
*/
public class Person {
public String name;
public Person() {//父类构造器
this.name = "Father";
System.out.println(this.name);
}
public void eat() {
System.out.println("父类吃饭");
}
}
class Student extends Person{
public String name;
public Student() {//子类构造器,经过main方法测试,调用子类构造器之前会自动调用父类构造器
//相当于这里有一个 Super();
this.name = "Son";
super.name = "SuperFather";//在子类中调用父类的
System.out.println(this.name+super.name);
}
public void eat() {
System.out.println("子类吃饭");
super.eat();//在子类中用super调用父类的重名方法,重写(覆盖)并不等于将父类的方法删除掉。
}
}
五、子类对象实例化
class Creature{
public Creature(){
System.out.println("Creature无参数的构造器");
}
}
class Animal extends Creature{
public Animal(String name){
System.out.println("Animal带一个参数的构造器,该动物的name为" + name);
}
public Animal(String name , int age){
this(name);
System.out.println("Animal带两个参数的构造器,其age为" + age);
}
}
public class Wolf extends Animal{
public Wolf(){
super("灰太狼", 3);
System.out.println("Wolf无参数的构造器");
}
public static void main(String[] args){
new Wolf();
} }
从创建Wolf对象开始,调用Wolf构造器,Wolf构造器调用父类Animal的两个参数的构造器,两个参数的构造器再调用一个参数的构造器,一个参数的构造器再调用父类Creature的构造器,Creature构造器调用Object类中的构造器,然后再逆向执行回来,最后一个被调用的构造器最先执行,依次向下执行构造器中的内容,像对象中的toString方法等就是再Object类中的某个方法,任何一个类调用构造器都会以最后一个Object类结束。
六、多态性
package com.xijian.java;
/*
* 多态的应用举例
* 总结:通过向上转型来引用父类方法(在子类未重写方法的前提下),通过向下转型来引用子类独有的方法
* 向下转型实际就是在栈空间声明一个子类引用类型的变量指向栈空间的父类引用对象,通过父类引用对象进而指向堆空间,对对象进行操作。
* 访问权限是由引用变量类型决定的。
*/
public class TestAnimal {
public static void main(String[] args) {
Animal a = new Dog();//可以扩大对象被调用的权限
Animal b = new Cat();//可以扩大对象被调用的权限
TestAnimal test = new TestAnimal();
test.func(a);
test.func(b);
}
public void func(Animal a) {
a.eat();
a.jump();
if(a instanceof Dog) {
Dog d = (Dog)a;//向下转型,用新的引用类型去引用子类存在而父类不存在的方法
d.say();
}
if(a instanceof Cat) {
Cat c = (Cat)a;//向下转型,用新的引用类型去引用子类存在而父类不存在的方法
c.say();
c.name = "1";
System.out.println(a.name);//通过测试可以看出,向下转型使用新的引用类型可以对子类对象进行操作
}
}
}
class Animal{
String name;
int age;
public void eat() {
System.out.println("进食");
}
public void jump() {
System.out.println("jump");
}
}
class Dog extends Animal{
public void eat() {
System.out.println("狗进食");
}
public void jump() {
System.out.println("狗急跳墙");
}
public void say() {
System.out.println("狗叫");
}
}
class Cat extends Animal{
public void eat() {
System.out.println("猫进食");
}
public void jump() {
System.out.println("猫跳");
}
public void say() {
System.out.println("猫叫");
}
}
多态性:多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
1)如果我们定义了一个指向子类的父类引用类型,那么它除了能够引用父类的共性外,还可以使用子类强大的功能,这就是向上转型。
父类名称 引用对象名称 = new 子类对象名称();
指向子类的父类引用由于向上转型了,它只能访问父类中拥有的方法和属性,而对于子类中存在而父类中不存在的方法,该引用是不能使用的,尽管是重载该方法。若子类重写了父类中的某些方法,在调用该些方法的时候,必定是使用子类中定义的这些方法(动态连接、动态调用)。()
2)如果在向上转型之后想访问子类中独有的方法,需要向下转型,向下转型实际就是在栈空间声明一个子类引用类型的变量指向栈空间的父类引用对象,通过父类引用对象进而指向堆空间,对对象进行操作。
子类名称 新引用对象名称 = (子类名称)需转型的对象名;
3)子类对象的多态性,并不适用于属性!
在调用属性的时候,只是看的是调用对象的引用类型,如果这个对象的引用类型是父类的,那么调父类的属性,如果是个子类的引用类型,则调子类对应的属性,并不存在多态性。
4)判断对象属于哪种类型的
if(obj instanceof class){}
其返回true情况如下
1.obj是class类的对象
2.obj是class类子类的对象
多态性在Java上有两种体现
①方法的重载和重写:同名方法可以通过形参列表的不同和子父类的继承关系来同时显示。
②对象的多态性:子类的对象可以赋给父类/父接口的引用。
七、所有的类的顶级类Object
package com.xijian.java;
public class Testequals {
public static void main(String[] args) {
//==
//1.基本数据类型:根据基本数据类型的值判断是否相等。相等返回true,否则返回false
//注:两端数据类型可以不同,在不同的情况下也可以返回true
int i = 12;
int j = 12;
System.out.println(i==j);//true
char c = 12;
System.out.println(i==c);//true
float f = 12.0F;
System.out.println(i==f);//true
int k = 65;
char a = 'A';
System.out.println(k==a);//true
//2.引用数据类型:比较引用类型变量的地址值是否相等。
Object obj1 = new Object();
Object obj2 = new Object();
Object obj3 = new Object();
obj3 = obj1;
System.out.println(obj1==obj2);//false
System.out.println(obj1==obj3);//true
//equals():①只能处理引用类型对象,并且比较的是两个对象的地址值是否相等
System.out.println(obj1.equals(obj2));
System.out.println(obj1.equals(obj3));
//像String类 包装类 File类 Date类重写了Object类里的equals方法
//比较的是两个对象中的具体内容是否相同
String str1 = new String("AA");
String str2 = new String("AA");
System.out.println(str1.equals(str2));//true
}
}
Object是Java中所有类的顶级类。
①在Java中,==表示等于,=表示赋值。
当==两侧比较的是基本数据类型时,由基本数据类型的值判断二者是否相等,相等则返回true,不等在返回false。需要注意的是,两侧的基本数据类型即使类型不同,也会返回true,如:int i =65,char j=12;char a ='A';则i==j==a全部返回true。
当两侧是引用数据类型时,两侧比较的是引用变量的地址值,相等返回true,不等返回false。
②equals方法
equals方法只可以处理引用数据类型的变量,在object类中,equals方法仍然是比较两个引用变量的地址值是否相同;所以要想用equals方法比较object类子类的实体内容,就必须要重写object类的equals方法。
③String类在内存中的分析
翻看String类的源代码我们可以知道:它是不可继承的(final修饰类),线程安全的(?????),值不可变(两个成员变量都有final修饰,指针可变),本质上是一个字符数组。
我们知道创建string类对象的时候,一般由三种方式:
使用关键字new,如:String s1 = new String(“myString”);
直接定义,如:String s1 = “myString”;
串联生成,如:String s1 = “my” + “String”
第一种使用关键字new创建的String类对象时,编译程序回先在字符串常量池中查看有没有“myString”这个字符串,若有,则在堆中开辟一块空间存放new出来的实例,指向常量池中的"myString",在栈中开辟一块区域存放s1这个引用变量,指向堆中的new出来的实例;若没有,则在常量池中创建一个"myString"字符串。
第二种方式直接定义过程:在程序编译期,编译程序先去字符串常量池检查,是否存在“myString”,如果不存在,则在常量池中开辟一个内存空间存放“myString”;如果存在的话,则不用重新开辟空间。然后在栈中开辟一块空间,命名为“s1”,存放的值为常量池中“myString”的内存地址
第三种,改变的不是字符串,而是相当于重新创建了一个新的字符串,重新有一个地址值。
相对于new出来的字符串来说,直接赋值的方式效率好,因为它只在字符串常量池开辟了一个内存空间,而new出来的相当于开辟了两个内存空间,耗费内存。
④toString()方法的使用
当我们打印一个引用变量的对象时,默认会调用这个对象的toString方法。
如果对象所在的类没有重写Object中的toString方法,那么调用的就是Object中的toString方法,打印出全类名+@+首地址值。
八、包装类
将8个基础数据类型包装成类之后,就可以调用类中的方法来处理这些数据了。
基本数据类型、包装类、String类之间的转换问题
原则:转换成谁,去谁里边找转换方法或者构造器。
① 基本数据类型和包装数据类型之间的转换:JDK5.0之后加入了自动装箱和拆箱的功能。
②基本/包装数据类型和String数据类型之间的转换
String-->包装数据类型:Integer.parseInt(str)
包装数据类型-->String:i+“ ”