目录
本文承接这篇文章→JavaSE_04_类和对象继续学习面向对象特性
包
-
不写权限访问修饰符,就是包访问权限,在当前包内部可见,只在同级目录下可见,不包含子包。
-
包就是文件夹,项目中的包在本地操作系统中就是文件夹;
-
要创建多级目录,不同包之间用.操作符分隔;
-
类名的全名称是包名.类名,包存在的意义是解决类的重名问题;
-
import语句只能导入某个包中具体某一个类;
-
若要将整个包下的类按需加载,例如
import java.util.*
,则不需要一个一个导入java.util包中的类,但是一般不推荐这种写法,因为可能会造成歧义: -
import java.util.*; import java.sql.*; //上述两个包中都有Date类,在使用Date类时,编译器不知道Date类来自于哪个包
-
解决办法:
1.使用类的全名称:
java.tuil.Date
或java.sql.Date
2.在导入时明确指定Date来自于哪个包
静态导入(不推荐)
//导入一个类下的所有静态方法和属性
import static java.lang.System.*;
//在类中就可以使用out.println()
常见系统包
java.lang:JDK的基础类(System,String,Object), JDK1.1之后这个包下的所有类自动导入
java.lang.reflect:反射开发包
java.util:工具包(集合类都在这个包下,Arrays,LinkedList,HashMap)
java.io:I/O开发包,文件的读取和写入
java.net:网络开发用到的包,Socket
java.sql:数据库开发用到的包
封装
保护性、易用性
方法重载:在同一个类中,方法名称相同,参数列表不同,与返回值类型无关的方法。
继承
减少代码冗余
代码能跑起来是第一步,还要进行CodeReview
,避免出现重复代码。在设计模式中进行代码优化。
public class Animal{
String name;
public void eat(){
//TODO
}
}
public class Dog extends Animal{
//Animal中的name和eat()在Dog中都有
}
继承的规则
-
继承的父子类之间必须是一个包含另一个的关系
-
Java中只有单继承,一个子类只能使用extends继承一个父类,可以多层继承。
class Animal{} class Dog extends Animal{} class Labuladuo extends Dog{} //继承树
-
子类会继承父类的所有属性和方法
-
显示继承:父类中public修饰的属性和方法可以在子类中直接使用;
-
隐式继承:父类中private修饰的属性和方法,子类也继承了这些属性和方法,但是无法直接使用。
class Father{ private int password; public int getter(){ return password; } } class Child extends Father{ //子类对象能通过调用getter()访问password }
protected
不同包具有继承关系的类,父类的属性和方法在子类的内部是可见的,出了子类,就不可见了。
//protected权限的属性在子类中的用法 04_28
//protected权限 > (defaulted)包访问权限,对于包权限可见的范围,protected权限一定包含。
//protected的可见范围 : 同一个包(指的是同一个级别的包:同级目录,不包括子包)下没有关系的类之间、不同包有继承关系的类之间
this
要产生一个子类对象,默认先产生父类对象。
存在继承时,先从本类找,找不到再向上搜寻。
阿里笔试题(点击查看)
super
-
修饰属性,表示直接从父类中寻找同名属性。
-
存在继承关系时,若要访问成员变量,建议加上this,使用this.属性。不加this的话,遵循就近原则,先从本类对象的属性找。若一开始就想使用父类的该属性,使用super.属性
-
super:先从直接父类(爸爸)寻找同名属性,若不存在,向上搜索(爷爷->太爷爷……)
-
存在继承时,使用this.属性,先从当前类中寻找该属性,若找不到,向父类对象中找。
-
**从直接父类开始向上搜索时,只要找到就停止向上搜索;能不能用该属性取决于该属性在其类内部的访问权限。**比如,在向上找的时候找到了该属性,但是被private修饰,只能在当前父类中使用,这时候也应当停止向上搜索。
-
-
修饰方法->构造方法
-
存在多层继承时,产生子类对象时,先产生父类对象;父类还有父类,继续向上产生。
-
super(父类构造方法的参数)
若父类中不存在无参构造,则子类构造方法的首行必须显式地调用super(父类有参构造的参数)。
class Base{ public Base(String s){ System.out.print("B"); } } public class Derived extends Base{ public Derived (String s) { System.out.print("D"); } public static void main(String[] args){ new Derived("C"); } } //编译错误
-
在一个构造方法中,无法显式地调用this()和super()(两者都必须放在第一行,发生矛盾。)
-
-
修饰成员方法
this和super的区别
super不能指代当前父类的对象引用
可以直接打印this,但是不能直接打印super,可以打印super.父类中的属性和方法
继承关系练习题
class X{
Y y=new Y();
public X(){
System.out.print("X");
}
}
class Y{
public Y(){
System.out.print("Y");
}
}
public class Z extends X{
Y y=new Y();
public Z(){
System.out.print("Z");
}
public static void main(String[] args) {
new Z();
}
}
//执行结果为:YXYZ
final
- 修饰属性,表示常量:值定义后无法被修改
- 修饰类,无法被继承(比如String类)
- 修饰方法,没有方法复写
类之间的关系
除了继承关系还有组合关系
class School{
Student student;
Teacher teacher;
}
//学校里面有学生、老师……
多态
向上转型
- 向上转型–>父类引用指向子类对象,不一定是直接子类,也可以是孙子类……
- 向上转型发生在有继承关系的类之间
- 向上转型的意义是:参数统一化(使用同一个父类引用接受不同的子类实例)
方法重载overload
发生在同一个类中,定义了若干个方法名称相同,参数列表不同,与返回值类型无关
方法重写override
发生在有继承关系的类之间,子类定义了和父类除了权限不同,其他都相同的方法
class Animal{
public void eat(){
//1
}
}
class Bird extends Animal{
public void eat(){
//2
}
}
class Duck extends Birds{
public void eat(){
//3
}
public void play(){
//4
}
}
Animal a1 = new Animal();
Animal a2 = new Birds();
Animal a3 = new Duck();
a1.eat();//1
a2.eat();//2
a3.eat();//3
//若Duck类中没有重写eat()方法,则从其直接父类向上搜寻,找到第一个重写eat方法的父类,执行该父类重写的eat方法。
//例如,如果Birds和Duck类都没有重写eat(),a3.eat()执行的是1处;如果Birds类重写了eat(),Duck类没有重写eat(),a3.eat()执行的是2处。
当发生重写时,子类方法权限必须>=父类方法权限才可以重写。但是当父类方法权限是private时,即使子类对应的重写方法的权限大于等于private,也不能发生重写。
权限范围大小比较:public > protected > default(包权限) >private
在重写方法前加@Override校验是否符合重写规则
向上转型发生的时机
-
方法传参
-
父类引用指向子类对象
-
方法返回值
返回值类型为父类,返回一个子类对象的引用
向下转型
Animal animal = new Duck();
animal.play(); //play方法不能调用
//父类引用使用"."操作符调用方法时,能不能调用成功该方法取决于父类中是否包含该方法;调用以后这个方法表现为什么样的状态,取决于new出来的对象是什么类型。
若想调用成功,需借助向下转型
子类名称 子类引用 = (子类名称) 父类引用
Duck duck = (Duck)animal;
duck.play() //这就可以访问了
要发生向下转型,要转的引用首先必须是向上转型得来的!!!(否则就会报出ClassCastException
)
instanceof
在向下转型前,先用if(父类引用 instanceof
子类名称)判断是否可以成功转型。
Animal animal1 = new Animal();
Animal animal2 = new Duck();
animal1 instanceof Duck //返回false
animal2 instanceof Duck //返回true
抽象类
若需要强制要求子类复写方法,用到抽象类。
注意事项
-
抽象方法所在的类必须是抽象类;
-
抽象类可以有多个抽象方法,也可以没有抽象方法;
-
子类(非抽象类)继承了抽象类,必须复写父类中所有抽象方法(包括父类继承自父类的父类中的抽象方法,但是父类并未实现该方法);
-
子类(抽象类)继承了抽象类,可以实现父类中的抽象方法,也可以不实现;
-
Java中定义抽象类或者抽象方法使用abstract关键字;
-
只有方法声明,没有方法体(延迟到子类实现),加abstract关键字
public abstract void fun(); //抽象方法的声明
-
Java中,抽象方法不等于没有方法体的方法:
//本地方法 native public final native void notifyAll(); //由JVM实现,JVM是C++写的,这里是调用C++中的方法
-
若一个类声明为抽象类,无法通过该类实例化对象(哪怕该抽象类中一个抽象方法都没有),只能借助子类向上转型变为抽象父类的引用。
//Person类是一个抽象类,Student类继承自Person类 Person person = new Person(); //错误 Person person = new Student(); //正确
-
抽象类虽然没法直接实例化对象,但是也可以存在构造方法。
abstract class B{ B(){ this.print(); } abstract void print(); } public class D extends B{ //private static int num = 10; private int num = 10; @Override void print() { System.out.println("num = " + num); } public static void main(String[] args) { new D(); } } //输出结果为num = 0,若将num设置为静态属性,则输出:num = 10.
-
若一个需求既可以使用抽象类也可以使用接口,优先使用接口,抽象类仍然受单继承局限。
接口
为什么要有接口?
为了让一个方法需要有多种类的对象传参时,参数能兼容
接口的两种场景
- 接口表示具备某种行为/能力–>
- 接口表示一种规范/标准–>电脑的USB接口和不同的USB设备
编码规范:在接口中,方法不用加public abstract, 全局常量不用加 static final.
注意事项
- 类实现接口,必须复写接口中所有的抽象方法
- 接口允许多实现,一个类可能具备多种能力,多个接口之间用逗号分隔。