JAVASE的学习笔记(三)
继承与多态
继承
作用:
- 复用性
- 扩展性
A.一个Java类只能出现一次直接的父类
B.一个Java类可以由多个间接的父类
子类继承了什么
- 子类“完完全全”继承了父类中的属性和方法,不管使用什么访问修饰符,没有继承的是父类的构造方法
- 子类无法“直接”操作使用private修饰的属性和方法,private修饰只能在父类范围中使用
- 子类可以“间接”操作private修饰的属性和方法,但是需要父类提供可以被继承的方法操作
java.lang.Object
类,每个class
都有Object作为超类
子类重写父类中的方法,需要满足以下条件
- A.方法名称必须相同
- B.形参列表必须相同(个数和类型)
- C.返回值类型必须相同,基本数据类型
- 引用数据类型,子类中的类型需要小于父类中的类型
重写方法特点
- 方法的重写也可以叫做方法的覆盖
- 方法的重写:必须具有类和类的继承关系
- 子类重写父类中的方法,需要满足以下条件
A.方法名称必须相同
B.形参列表必须相同(个数和类型)
C.返回值类型必须相同,基本数据类型
引用数据类型,子类中的类型需要小于父类中的类型 - 子类重写方法的访问修饰符必须大于等于父类的访问修饰符,
private
除外 - 子类无法重写父类中的构造方法,原因:子类无法继承父类中构造方法的
- 重写方法
是针对于父类可以
被继承`的 实例方法 - 子类无法重写父类中使用
private
修饰的方法 - 子类无法重写父类中使用static修饰的静态方法(类方法)
- 子类抛出的异常需要小于等于父类中抛出的异常
final关键字
- 最终,不可变
- 修饰类,无法被继承
- 修饰方法,无法被重写
- 修饰变量
- 必须显性初始化
- 值不可变
- 基本数据类型
- 引用数据类型,地址值不能变,但是内部变量可以修改
super关键字
- super不是引用数据类型,也不代表内存地址
- super代表的是 所有父类的特征,并不是父类对象,super并没有在堆中创建对象。
// A code block
class person extends father(){
System.out.println(super.name);//有结果
System.out.println(super.super.name);//错误
}
- super关键字依然无法在static修饰的方法中使用
- 可以调用父类中的构造方法,但是必须放置在第一语句
super的使用范围
- 绝大部分情况,是在继承关系中使用
- 小部分情况下,是在泛型中使用
java规定,在执行构造函数之前必须执行父类的构造函数,直到这个类是java.lang.Object类的构造函数
super和this为什么不能同时出现:
因为在构造的时候只需要调用父类的super()作为初始化父类一次,如果super(…)和this(…)同时出现的话,那么就会出现初始化父类两次的不安全操作,因为当super(…)和this(…)同时出现的时候,在调用完了super(…)之后还会执行this(…),而this(…)中又会自动调用super(),这就造成了调用两次super()的结果(这是假设super(…)和this(…)同时出现时候,super(…)在this(…)前面,反过来出现也一样
重要:子类创建对象,必须先执行父类中的构造方法
如果子类想使用父类中的属性(在不同的包),那么我们需要使用`protected`
对属性进行修饰,但是这句话破坏了面向对象编程的封装性,我们实际开发
中绝大部分的属性都应该设置为`private`,很少使用继承的复用性。
多态
多态:个人理解-是针对于引用数据类型,因为=
两端的数据类型不一致,才产生了多态(本质就是数据类型转换),需要存在继承关系
- 父类声明 变量 = 引用子类对象的地址 ,对象上转型
- 不能将父类对象直接转换为子类
- 对象下转型的使用前提对象上转型 : 子类声明 变量 = (强制转换符子类类型)父类引用子类对象
- 可以通过instantceof来判断类与类之间是否存在继承关系
注意:
public class Demo03{
public static void main(String...arguments){
Person p1 = new Student();//对象上转型
//p1.study();//错误原因如下:
//在编译阶段(javac.exe),p1所属的类型为Person类型,只能使用Person中定义的方法或者属性
//javac.exe 检查p1是Person类型
//但是,如果当我们处于运行阶段(java.exe),p1“真正”所有的类型为Student
System.out.println("检查变量真正的类型:"+p1.getClass().getName());
System.out.println(p1.name);
p1.say();//调用的是继承过来的方法
//重写方法
p1.eat();//第一步:检查编译阶段Person类中是否存在该方法,第二步:运行阶段Student对象
}
}
class Person{
String name = "悟空";
public void say(){
System.out.println("人说话");
}
public void eat(){
System.out.println("人吃饭");
}
}
class Student extends Person{
String name = "八戒";
public void study(){
System.out.println("学生学习");
}
public void eat(){
System.out.println("学生吃便当");
}
}
这里对象上转型找不到子类中的方法原因是:
在编译阶段(javac.exe),p1所属的类型为Person 类型,只能使用Person 中定义的方法或者属性
注:java中变量按照变量的阶段划分:可分为编译阶段变量和运行阶段变量
上转型对象在编译期间检查父类有无该方法,在运行期间使用子类方法
使用对象上转型:
1.在编译阶段,无法使用子类中扩展的新方法student()
2.在编译阶段,使用的属性和方法在父类中必须存在
3.在运行阶段,
A.属性依然还是使用父类中值
B.检查调用的方法,在子类中是否重写,如果重写执行的是重写后的方法,如果没有重写,执行的是继承的方法
使用对象下转型:
一定要判断所有类的类型,容易造成类型转换异常
public class Demo04{
public static void main(String...arguments){
Animal animal = new Dog();
//需要运算符 instanceof 判断类的类型
System.out.println(animal instanceof Cat);
System.out.println(animal instanceof Animal);
System.out.println(animal instanceof Object);
if(animal instanceof Cat){
Cat cat = (Cat)animal;
cat.run();
}
if(animal instanceof Dog){
Dog dog = (Dog)animal;
dog.run();
}
//类型转换异常
//Dog dog = (Dog)animal;
//java.lang.ClassCastException: Cat cannot be cast to Dog
//dog.run();
//cat类和dog并无继承关系
}
}
class Animal{
public void run(){
System.out.println("动物跑");
}
}
class Cat extends Animal{
public void run(){
System.out.println("猫。。。跳");
}
}
class Dog extends Animal{
public void run(){
System.out.println("狗。。。跑");
}
}
隐藏代码
注释内容为隐藏代码
public class Demo03 /*extends Object*/{
/*
public Demo03{
super();
}
*/
public static void main(String...args){
// return;
}
}
public interface Demo01{
/*public abstract*/ int m2();//个人推荐
public /*abstract*/ int m3();
}
getClass():方法返回的是运行阶段的堆内存中实际使用的类
方法
equals()方法
对于字符串变量来说:
-
"=="比较两个变量本身的值,即两个对象在内存中的首地址。
-
“equals()”比较字符串中所包含的内容是否相同。
对于非字符串变量来说,"=="和"equals"方法的作用是相同的,都是用来比较其对象在堆内存的首地址,即用来比较两个引用变量是否指向同一个对象。 -
如果是基本类型比较,那么只能用==来比较,不能用equals
-
对于基本类型的包装类型,比如Boolean、Character、Byte、Shot、Integer、Long、Float、Double等的引用变量,==是比较地址的,而equals是比较内容的
若是想比较内容,则要重写equals方法:
public boolean equals(Object obj) {
System.out.println("重写后的equals方法");
if(this==obj){
return true;
}
Student student = (Student)obj;//对象下转型,必须先有对象上转型
boolean flag = false;
if(this.name.equals(student.name)){
flag = true;
}else{
flag = false;
}
if(this.age==student.age){
flag = true;
}else{
flag = false;
}
return flag;
}
hashCode()方法
Java对象的eqauls方法和hashCode方法是这样规定的:
1、相等(相同)的对象必须具有相等的哈希码(或者散列码)。
2、如果两个对象的hashCode相同,它们并不一定相同。
在集合查找时,hashcode能大大降低对象比较次数,提高查找效率!
当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。所以这里存在一个冲突解决的问题。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。
注意:
- 重写equals方法,必须重写hashcode方法,提高比较的效率
- 重写hashcode方法,可以不用重写equals方法
static与单例设计模式
单例模式:不允许使用new来创建对象,就是任何方法调用此类生成的实例只有一个。需要将默认的构造方法设置成private。然后使用一个静态方法生产实例。
单例模式的优点:
1,它能够避免对象的重复创建,不仅可以减少每次创建对象的时间开销,还可以节约内存空间。
2,能够避免由于操作多个实例导致的逻辑错误。如果一个对象有可能贯穿整个应用程序,而且起到了全局统一管理控制的作用,那么单例模式也许是一个值得考虑的选择。
main()主方法:
public class Demo02 {
public static void main(String...args){
//错误: Student() 在 Student 中是 private 访问控制
//Student s1 = new Student();
Student s2 = Student.getInstance();
System.out.println(s2);
s2.setName("悟空");
s2.setAge(100);
System.out.println(s2);
Student s3 = Student.getInstance();
System.out.println(s3);
s3.setName("八戒");
s3.setAge(200);
//输出结果:特别重要
System.out.println(s2);
System.out.println(s3);
}
}
单例设计模式-饿汉设计方式
class Student{
//私有的构造方法:外部无法对该类进行实例化操作
private Student(){}
private String name;
private int age;
private static Student student = new Student();//单例设计模式-饿汉设计方式
public static Student getInstance(){
return student;
}
public void setName(String name){
this.name = name;
}
public void setAge(int age){
this.age = age;
}
public String toString() {
return "name="+name+",age="+age;
}
}
该类的构造函数是私有的,所以保证其他类不能实例化这个类,然后提供了一个静态实例并返回给调用者。饿汉模式是最简单的一种设计单例模式,在类加载的时候就创建实例,实例在整个程序周期都会存在。
饿汉模式的优缺点:
优点:在类加载的时候创建一次实例,不会存在多个线程创建多个实例的情况,避免了多线程同步的问题。
缺点:即使该单例没有用到也会被创建,而且在类加载之后就被创建,内存被浪费。
饿汉模式适合单例占用内存比较小,在初始化时就会被用到的情况。
单例设计模式-懒汉设计方式
class Student{
//私有的构造方法:外部无法对该类进行实例化操作
private Student(){}
private String name;
private int age;
public static Student getInstance(){
if(student==null){//单例设计模式-懒汉设计方式
student = new Student();
}
return student;
}
public void setName(String name){
this.name = name;
}
public void setAge(int age){
this.age = age;
}
public String toString() {
return "name="+name+",age="+age;
}
}
懒汉模式就是在需要的时候进行实例化,如果单例已经创建,再次调用获取接口将不会重新创建新的对象,而是直接返回之前创建的对象。
缺点:安全性无法保证
懒汉模式适合某个单例使用的次数少,并且创建单例消耗的资源较多。