目录
2、final 修饰的方法不可以被覆盖,但子类的方法可以用final修饰。
4、final修饰的引用数据类型变量,变量中的地址不能改变,但是指向堆中对象的属性可以修改。
一、final关键字
1、final的概念
继承的出现提高了代码的复用性,并方便开发。但随之也有问题,有些类在描述完之后,不想被继承,或者有些类中的部分方法功能是固定的,不想让子类重写。可是当子类继承了这些特殊类之后,就可以对其中的方法进行重写,那怎么解决呢?
要解决上述的这些问题,需要使用到一个关键字final,final的意思为最终,不可变。final是个修饰符,它可以用来修饰类、类中的属性和方法和局部变量,但是不能修饰构造方法。
2、final的特点
1、final 修饰类不可以被继承,但是可以继承其它类。
class AA {}
final class BB extends AA {} // final修饰类可以继承其他类
class CC extends BB {} // 编译错误,final修饰类不可以被继承
2、final 修饰的方法不可以被覆盖,但子类的方法可以用final修饰。
class AA {
final public void show1() {}
void public show2() {}
}
class BB extends AA {
final public void show1() {} // 编译错误,final修饰的方法不可以被覆盖
final public void show2() {} // 父类中没有被final修饰方法,子类覆盖后可以加final
}
3、final 修饰的变量称为常量,这些变量只能赋值一次。
final修饰成员变量和静态变量,必须显示的设置初始值,JVM不会设置默认值
常量命名规则:多个有意义的单词连接,所有字符大写,单词之间用下划线分割。
public class FinalDemo {
// 成员变量必须显示的设置初始值,JVM不会不会设置默认值。
final String USER_NAME = "xiaoming";
public static void main(String[] args) {
// 多个有意义的单词连接,所有字符大写,单词之间用下划线分割.
final int MAX_VALUE = Integer.MAX_VALUE;
// 编译错误,final 修饰的变量称为常量,这些变量只能赋值一次。
MAX_VALUE = 120;
}
}
4、final修饰的引用数据类型变量,变量中的地址不能改变,但是指向堆中对象的属性可以修改。
class Person {
String name = "小明";
}
public class FinalDemo {
public static void main(String[] args) {
final Person p = new Person();
p = new FinalDemo(); // 编译错误,地址值不能更改
p.name = "小花"; // 地址内的对象属性值可以修改
}
}
二、Object类
1、Object类介绍
什么是Object类?Object类存储在java.lang包中,是所有java类(Object类除外)的终极父类。当然,数组也继承了Object类。如果在类的声明中未使用extends关键字指明其基类,则默认基类为Object类。
Object类中方法如下:

Java中的任何类都继承了这些方法,并且可以覆盖不被final修饰的方法。例如:没有final修饰的toString()函数可以被覆盖,但是final wait()函数就不行。
Object类中方法简单介绍:
public String toString() 返回该对象的字符串表示。
public boolean equals(Object obj) 判断两个对象的地址是否相同。
public native int hashCode() 返回该对象的哈希码值(集合讲)。
public final native Class<?> getClass() 得到一个对象或者类的结构信息(反射讲)。
public final void wait() throws InterruptedException;
public final native void wait(long timeout) throws InterruptedException;
public final void wait(long timeout, int nanos) throws InterruptedException;
以上三个方法作用:等待,需要notify或notifyAll才能唤醒(多线程讲)
public final native void notify();
public final native void notifyAll();
以上两个方法作用:唤醒(多线程讲)
native关键字解释:
native代表本地方法,所有用native修饰的方法都没有方法体。Java不是完美的,Java的不足除了体现在运行速度上要比传统的C++慢许多之外,Java无法直接访问到操作系统底层(如系统硬件等),为此Java使用native方法来扩展Java程序的功能。可以将native方法比作Java程序同C程序的接口,使用native修饰的方法来访问底层的操作系统。
2、toString方法
toString 方法返回该对象的字符串表示,其实该字符串内容就是:对象的类型+@+哈希码。
【例】toString 方法底层代码实现
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
由于 toString 方法返回的结果是内存地址,而在开发中,经常需要按照对象的属性得到相应的字符串表现形式,因此也需要重写它。
【例】重写toString方法案例
class TestDemo {
String name;
int age;
public TestDemo(String name, int age) {
this.name = name;
this.age = age;
}
// 重写toString方法
@Override
public String toString() {
return "TestDemo[name:" + name + " age:" + age + "]";
}
}
public class ToStringDemo {
public static void main(String[] args) {
TestDemo demo = new TestDemo("小明", 18);
// 输出:TestDemo[name:小明 age:18]
System.out.println(demo);
}
}
3、equals方法
Object类中的equals 方法,用于比较两个对象是否相同,它其实就是使用两个对象的内存地址在比较。Object类中的 equals 方法内部使用的就是==比较运算符。
【例】equals 方法底层代码实现
public boolean equals(Object obj) {
return (this == obj);
}
【例】equals 方法使用案例
// Demo类
class Demo {
String name;
int age;
public Demo(String name, int age) {
this.name = name;
this.age = age;
}
}
// 测试类
public class EqualsDemo {
public static void main(String[] args) {
Demo d1 = new Demo("小明", 18);
Demo d2 = new Demo("小明", 18);
// d1和d2是不同的两个对象,那么在堆中的地址肯定不相同
System.out.println(d1 == d2); // 返回:false
System.out.println(d1.equals(d2)); // 返回:false
// 此时,d3和d1都指向堆中同一块地址
Demo d3 = d1;
System.out.println(d1.equals(d3)); // 返回:true
}
}
在开发中要比较两个对象是否相同,经常会根据对象中的属性值进行比较,也就是在开发经常需要子类重写 equals 方法根据对象的属性值进行比较。
【例】判断Demo类中的属性值是否相同
// Demo类
class Demo {
String name;
int age;
public Demo(String name, int age) {
this.name = name;
this.age = age;
}
/**
* 重写equals方法,判断属性是否全部相同
*/
@Override
public boolean equals(Object obj) {
// 1.判断传入的obj是否为null,为null则返回false
if(obj == null) {
return false;
}
// 2.判断地址是否相同,相同则返回true,否则继续判断属性是否相同
if(this == obj) {
return true;
}
// 3.依次判断对象中属性值是否相同,相同则返回true,否则返回false
Parent p = (Parent)obj; // 强转
if(this.name.equals(p.name) && this.age == p.age) {
return true;
}
return false;
}
}
// 测试类
public class EqualsDemo {
public static void main(String[] args) {
Demo d1 = new Demo("小明", 18);
Demo d2 = new Demo("小明", 18);
// 判断属性是否全部相同
System.out.println(d1.equals(d2)); // 返回:true
}
}
4、hashCode方法
hashCode方法返回该对象的哈希码值,支持该方法是为哈希表提供一些优点,例如,java.util.Hashtable 提供的哈希表。
我们直接输出一个对象,@之后接的就是哈希码值的16进制。
【例】使用hashCode方法案例
Student stu = new Student();
// 输出:2018699554
System.out.println(stu.hashCode());
// 输出:com.yjxxt.objectClass.Studnet@7852e922
System.out.println(stu);
// Integer.toHexString()方法是把哈希值转化为16进制
// 输出:7852e922
System.out.println(Integer.toHexString(p.hashCode()));
因为hashCode方法没有用final修饰,所有我们可以重写hashCode方法。一般重写了equals方法都要重写hashCode方法。如果obj1.equals(obj2)的结果为true,那么可以推出obj1对象和obj2对象的hashCode肯定相等,但是hashCode相等不一定就满足equals。不过为了提高效率,应该尽量使上面两个条件接近等价。
三、多态
1、多态的概念
多态就是同一种事物的不同形态
多态指的是同一个方法调用,由于对象不同可能会有不同的行为。现实生活中,同一个方法,具体实现会完全不同。
多态的必要条件:
- 继承是多态的前提
- 子类重写父类方法
- 父类引用指向子类对象
多态的使用场合:
- 使用父类做方法的形参,实参可以是任意子类类型
- 使用父类做方法的返回值类型,返回值可以是任意子类的对象
2、多态的实现
多态的实现主要表现在父类和继承该父类的一个或多个子类对某些方法的重写,多个子类对同一方法的重写可以表现出不同的行为。
【例】基于继承实现的多态---管理员给动物喂食
class Animal {
public void eat() {
System.out.println("动物 eat");
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("dog 吃骨头");
}
}
class Pig extends Animal {
@Override
public void eat() {
System.out.println("Pig 吃饲料");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("cat 吃鱼儿");
}
}
// 管理员
class Admin {
/**
* 多态满足条件
* 1.Cat、Dag和Pig继承Animal
* 2.Cat、Dag和Pig重写了Animal的eat()方法
* 3.父类引用指向子类对象(实参赋值给形参)
* 此处如果没有使用多态,管理员需要分别给每一个动物添加一个喂食的方法!!!
*/
public void feedAnimal(Animal a) {
a.eat(); // 动态绑定
}
}
public class PolymorphismDemo {
public static void main(String[] args) {
// 初始化一个管理员对象
Admin admin = new Admin();
// 管理员给动物喂食
admin.feedAnimal(new Dog());
admin.feedAnimal(new Pig());
admin.feedAnimal(new Cat());
}
}
3、引用数据类型
回顾基本数据类型转换:
【例】基本数据类型转换案例
// 整数(int)转换成小数(double)
int x = 10;
double y = x;
// 小数(double)转换成整数(int)
double n = 2.2;
int m = (int)n;
而引用数据类型转换主要包括向上转型和向下转型。
向上转型:父类引用指向子类对象,属于自动类型转换。
格式:父类类型 变量名 = 子类对象;
向下转型:子类引用指向父类对象,属于强制类型转换。
格式:子类类型 变量名 = (子类类型) 父类对象;
【例】引用类型转换案例
// 父类
class Person {
public void eat() {
System.out.println("person eat ...");
}
}
// 子类
public class Student extends Person {
public void study() {
System.out.println("Student study ...");
}
}
// 测试类
public class PolymorphismDemo {
public static void main(String[] args) {
// 向上转型:父类引用指向子类对象
Person p = new Student();
p.eat(); // 调用Person的eat()方法
// p.study(); // 编译失败,不能调用Student的study()方法
// 向下转型:子类引用指向父类对象
Student stu = (Student)p;
stu.eat(); // 调用Person的eat()方法
stu.study(); // 调用Student的study()方法
}
}
向上转型:
优点:隐藏了子类类型,提高了代码的扩展性,多态本身就是向上转型的过程。
缺点:只能使用父类共性的内容,不能调用子类特有方法!
向下转型:
优点:向下转型之后,可以调用子类特有方法
缺点:但是向下转型有风险,容易发生 异常!
4、instanceof运算符
当要使用子类特有功能时,就需要使用向下转型。但是面对具体的子类对象,在向下转型时容易发生ClassCastException类型转换异常,在转换之前必须做类型判断。
java中的instanceof运算符,用来在运行时指出对象是否是特定类的一个实例。instanceof通过返回一个布尔值来指出,这个对象是否是这个特定类或者是它的子类的一个实例。
语法格式:boolean result = object instanceof class
注意:class可以是类,也可以是接口!
instanceof在编译状态和运行状态的区别:
在编译状态中:
右边的“类或接口”是左边“对象”的父类、本身类和子类时,编译通过,否则编译失败!
本身类:此处的本身类指的就是“编译时”对应的类。
例如:Animal animal = new Tiger(); -->本身类就是Animal类
在运行转态中:
a)当左边“对象”不为null的时候
右边的“类或接口”是左边“对象”的父类、本身类时,这是返回结果就是true。
右边的“类或接口”是左边“对象”的兄弟类、子类时,这是返回结果就是false 本身类:此处的本身类指的就是“运行时”对应的类。
例如:Animal animal = new Tiger(); -->本身类就是Tiger类
b)当左边“对象”就是null的时候
如果左边“对象”为null,那么instanceof运算的结果就是false!
【例】instanceof使用案例
class Animal {
public void eat() {
System.out.println("Animal eat ...");
}
}
class Dog extends Animal {
public void lookHome() {
System.out.println("Dog look home ...");
}
}
class ShepherdDog extends Dog {
public void introduction() {
System.out.println("Shepherd dog introduction ...");
}
}D
class Cat extends Animal {
public void catchMouse() {
System.out.println("Cat catch mouse ...");
}
}
public class InstanceofDemo {
public static void main(String[] args) {
Animal animal = new Dog();
// 判断animal是否是Animal类的实例
if(animal instanceof Animal) { // 返回true
Animal an = (Animal)animal;
an.eat();
}
// 判断animal是否是Dog类的实例
if(animal instanceof Dog) { // 返回true
Dog dog = (Dog)animal;
dog.lookHome();
}
// 判断animal是否是ShepherdDog类的实例
if(animal instanceof ShepherdDog) { // 返回false
ShepherdDog shepherdDog = (ShepherdDog)animal;
shepherdDog.introduction();
}
// 判断animal是否是Cat类的实例
if(animal instanceof Cat) {// 返回false
Cat cat = (Cat)animal;
cat.catchMouse();
}
// boolean flag = animal instanceof String; // 编译失败
}
}
5、多态中成员变量特点
无论编译和运行,都参考等号左边(引用类型变量所属的类)。
【例】多态中成员变量特点
class Father {
int num = 10;
}class Son extends Father {
int num = 20;
String name = "小明";
}
public class InstanceDemo {
public static void main(String[] args) {
Father father = new Son();
// 多态中的成员变量:无论编译和运行,都参考等号左边的类
System.out.println("num:" + father.num); // 编译通过,输出父类中的num值
// System.out.println("name:" + father.name); 编译错误
}
}
6、多态中成员方法特点
编译看左边,参考等号左边引用对象所属的类是否有该方法。
运行看右边,在执行期间判断引用对象的实际类型,然后根据其实际的类型调用其相应的方法,又称为动态绑定。
class Father {
public void eat() {
System.out.println("父类中的eat方法");
}
}
class Son extends Father {
public void study() {
System.out.println("子类中的study方法");
}
public void eat() {
System.out.println("子类中的eat方法");
}
}
public class InstanceDemo {
public static void main(String[] args) {
Father father = new Son();
father.eat(); // 编译通过,调用子类中的eat方法
// father.study(); 编译错误
}
}
总结:字段的访问由编译时类型决定,方法调用则具有多态性(除非是静态方法或私有方法等)。但是,在方法内部访问字段时,访问的是当前类(即定义该方法的类)中的字段,或者如果子类重写了方法,则按照运行时类型调用子类的方法,但在子类方法中访问的字段是子类的字段。
&spm=1001.2101.3001.5002&articleId=157220941&d=1&t=3&u=0b1c440e48e2440c985899ca282ce631)
1514

被折叠的 条评论
为什么被折叠?



