面向对象编程
访问修饰符
权限从小到大依次为
private | 私有,当前类的内部可见 |
---|---|
default | 啥也不写就是包权限,当前包的内部可见,不包含子包,同级目录下可见 |
protected | 继承,不同包的有继承关系的类之间可见 |
public | 当前项目可见 |
-
Java中的包就是操作系统的文件夹,声明一个包使用package关键字
-
若存在多个文件夹的嵌套,使用"."分隔符,创建多个文件夹
-
类的全名称:包名.类名
-
导入某个包中的某个类
import
导入类只可导入相关包中的某个具体的类 -
import java.util.*//此时将整个util包下的所有类按需加载
当程序用到了两个相同名称的类
- 使用类的全名称
java.util.Date date = new java.util.Date();
java.sql.Date date1 = new java.sql.Date();
- import明确指定导入的是哪个包下的哪个类
import java.util.Date;
静态导入
import static
可以导入包中的静态方法和静态属性
常见的系统包
java.lang | JDK的基础类,System,String,Object都在这个包下 |
---|---|
java.lang.reflect | 反射开发包 |
java.util | 工具包(集合类都在这个包下,Arrays,LinkedList,HashMap) |
java.io | I/O开发包,文件读取和写入 |
java.net | 网路编程开发包,Socket |
java.sql | 数据库开发 |
封装
封装:使用private将属性进行封装(这个属性只在当前类的内部可见,对外部隐藏)
方法重载
什么是方法重载?
在同一个类中,定义了若干个方法名称相同,参数列表不同,与返回值无关的一组方法。这样的一组方法称为方法重载。
继承
package Animal;
public class Animal{
public String name;
public void eat(String food){
System.out.prinlnt(this.name + "正在吃" + food);
}
}
package Dog;
public class Dog{
public String name;
public void eat(String food){
System.out.println(this.name + "正在吃" + food);
}
}
package Cat;
public class Cat{
public String name;
public void eat(String food){
System.out.println(this.name + "正在吃" + food);
}
}
这三个类的代码完全一样
按道理说所有Animal的类都应具备name属性以及eat
Dog is an Animal
Cat is an Animal
继承(extends)
当一个类继承了另一个类,另一个类中所有的属性和方法,子类就天然具备了
Java中使用extends
表示类的继承
Dog extends Animal
继承的规则
- 必须满足is a关系
- 一个字累只能使用extends继承一个父类(单继承)
- 子类会继承父类的所有属性和方法,显式继承(public属性和方法可以直接使用,隐式继承(private属性和方法),子类其实也继承了这个属性和方法,但是无法直接使用
隐式继承,子类需要通过父类提供的方法来操作getter and setter
问答环节
问:静态的成员和方法可以直接继承吗?
答:静态的属性和方法是归于某个类所有
当一个类继承了另一个类,肯定所有的静态属性喝方法就继承
能否使用,看权限是否为public
关于protected访问权限
protected作用域不同包中的子类可见
public class Test{
public static void main(String[] args){
Animal animal = new Animal();
//System.out.println(animal.name);//Test不是Animal的子类且不在一个包中,因此name对于Test类来说不可见
}
}
public class Test{
public static void main(String[] args){
Person per = new Person();
//per.name;//这行代码发生在Test这个类中,protected只在当前类和子类的内部可见,所以出错
}
}
同包下的没有关系的类之间以及不同包的有继承关系的类之间可见
this关键字
this:表示当前对象的引用
修饰属性,表示直接从当前类中找同名属性
修饰方法,表示当前对象的引用
要产生一个子类对象,默认首先产生父类对象
class Dog extends Animal{}
Dog dog = new Dog();//产生一个子类Dog对象
- 当有继承关系时
this关键字默认先在当前类中寻找同名属性,若没找到,继续向上寻找父类中是否有同名属性
直接使用name。编译器默认都是this.name
super关键字
修饰属性,表示直接从父类中去寻找同名属性
修饰方法,表示直接从父类中去寻找方法
子类构造方法默认带有super();
表示调用父类无参构造,且一定在首行(首条语句)
super关键字
super修饰属性 表示从父类中寻找同名属性,若不存在则再向上寻找
this直接从当前类中寻找同名属性,若不存在则再向上搜索。
super修饰构造方法
- super(父类构造方法的参数)
super();//直接父类的无参公祖奥,可写可不写
若父类中不存在无参构造,则子类构造方法的首行必须使用super(有参构造)
在一个构造方法中无法显式使用this()和super()同时出现。
super修饰普通方法,和修饰属性,直接从父类中寻找同名方法
- super不能指代当前父类的对象引用
System.out.println(this);
//System.out.println(super);//java:需要'.'
前方高能!!!
package animal;
public class B {
public B() {
System.out.println("1.B的构造方法---------------");
}
{
System.out.println("2.B的构造块-----------------");
}
static {
System.out.println("3.B的静态块-----------------");
}
}
package animal;
public class D extends B{
public D() {
System.out.println("4.D的构造方法--------------");
}
{
System.out.println("5.D的构造块----------------");
}
static {
System.out.println("6.D的静态块----------------");
}
public static void main(String[] args) {
System.out.println("7.main开始。。。。");
new D();
new D();
System.out.println("8.main结束。。。。");
}
}
问输出结果顺序是什么?
由于main存在于D子类中,若JVM要调用main,首先要加载主类,一加载主类,执行主类的静态块,D继承了B,先加载父类再加载子类
- 3(父类的静态块)- > 6(子类的静态块)类加载结束后,进入主方法
- 产生子类对象,先要产生父类对象,先调用构造块{},下来才是构造方法
- 先调用父类的构造块2,调用父类构造方法1=》父类对象产生完毕,子类构造块5 子类构造方法4=》 子类对象产生完毕
final关键字
final int num = 10;//num值不可修改
final class Person{} //Person无法被继承
多态
多态:一个引用可以表现出多种行为/特征 = > 多态性
向上转型: 最大的意义在于参数统一化,降低使用者的使用难度!
Animal animal = new Dog();
父类名称 父类引用 = new 子类对象();//不一定是直接子类,也可以是孙类。。。
有了向上转型后,最顶尖的父类引用就可以指代所有的子类对象。
package Polymorphism;
public class Animal {//父类
public void eat() {
System.out.println("Animal的eat");
}
public void play() {
System.out.println("Animal的play");
}
}
package Polymorphism;
public class Duck extends Animal {//子类具有父类的属性
public void eat() {
System.out.println("Duck类的eat方法");
}
public void play() {
System.out.println("Duck类的play方法");
}
}
package Polymorphism;
public class Test {//测试输出
public static void main(String[] args) {
fun(new Animal());
fun(new Bird());
fun(new Duck());
fun(new Dog());
}
public static void fun(Animal animal){
animal.eat();
}
}
animal.eat();
fun中animal局部变量的引用调用eat方法时,当传入不同的对象时,表现出来了不同的eat方法行为 => 多态性
同一个引用(变量名称),同一个方法名称根据对象的不同表现出来了不同的行为 =》多态
方法重写(override)
- 方法重载(overload):发生在同一个类中,定义了若干个方法名称相同,参数列表不同的一组方法。
- 方法重写(override):发生在有继承关系的类之间,子类定义了和父类除了权限不同,其他全都相同的方法,这样的一组方法称之为方法重写。
到底调用的是谁的方法呢?
看new的是谁,只要new的这个对象的类中覆写了同名方法,则调用的一定是覆写后的方法
问:若子类没有重写这个方法,调用的是?
就近匹配原则,碰到最接近的调用,子类没有从父类找
当发生重写时,子类权限必须 >= 父类权限才可以重写
private<default<protected<public
public class Animal{
proteted void eat(){
System.out.println("Animal类的eat方法");
}
}
package Dog;
public class Dog extends Animal{
public void eat(){//public>protected可行
System.out.println("Dog类的eat方法");
}
protected void eat(){//protected = protected可行
System.out.println("Dog类的eat方法"))
}
//void eat(){//default<protected 不可行
// System.out.println("Dog类的eat方法"))
//}
}
问:父类使用private子类使用public可以吗
private权限不包含在内
@Override注解
Java中有一个注解@Override
使用这个注解写在重写方法之前,帮你校验你的方法重写是否符合规则。快捷键:alt+insert
或者在extends
后面直接按alt+Enter
自动填补
能否重写static方法?
多态的本质就是因为调用了不同的子类“对象”,这些子类对象所属的类覆写相应的方法
才能表现出不同的行为 而static
与对象无关!!!
不可以重写static方法
No | 区别 | 重载(overload) | 覆写(override) |
---|---|---|---|
1 | 概念 | 方法名称相同,参数类型及个数不同 | 方法名称、返回值类型、参数的类型及个数完全相同 |
2 | 范围 | 一个类 | 继承关系 |
3 | 限制 | 没有权限要求 | 被覆写的方法不能拥有比父类更严格的访问控制权限(不能包含private) |
4 | static | 无要求 | 不能重写static方法 |
向上转型发生的时机
1.引用赋值
Animal animal1 = new Bird();//重写
Animal animal2 = new Duck();//重写
Animal animal3 = new Dog();//重写
Animal animal4 = new Cat();//重写
2.方法传参 - 使用最多的
fun(animal1);
fun(animal2);
fun(animal3);
fun(animal4);
public static void fun(Animal animal){
animal.eat();
}
3.方法返回值
public static Animal test(){
Bird bird = new Bird();
return bird;
}
来做个小题吧
问:输出结果为?
public class B {
public B() {
fun();
}
public void fun() {
System.out.println("B.fun()");
}
}
public class D extends B {
private int num = 10;
public void fun() {
System.out.println("D.fun,num = " + num);
}
public static void main(String[] args) {
D d = new D();
}
}
首先调用了个D,调用D的无参构造,这时存在继承,优先调用B的构造方法先产生父类对象fun()方法,fun()是通过D() new出来的,fun()方法被D类覆写,所以fun()方法调用的是子类覆写后的fun()方法,当调用fun()方法时,这时候还没执行D的构造方法呢,子类对象还没初始化完成呢,D的所有属性都是默认值
D.fun(),num = 0;
public D(){
super();//此时还在super()中
private int num = 10;
}
问:父类没有子类扩展方法会怎样?
public void play(){
System.out.println("Dog类独有的play方法");
}
public static void main(String[] args){
Animal animal = new Dog();
animal.eat();
//animal.play();//父类Animal中没有play方法,不可使用
}
父类Animal中没有play方法,不可使用
向上转型
父类名称 父类引用= new 子类对象();
天然发生的向上转型
使用父类引用调用普通方法时,若子类重写了该方法,则调用该对象所在子类覆写后的方法。
能通过"."访问的方法 类名称说了算
能访问的这些方法必须都在类中定义过,编译器会先在类中查找是否包含指定方法
至于这个方法到底表现出来是哪个类的样子,实例所在的方法说了算。
Animal animal = new Dog();
animal.方法名称()
这个方法能不能调用,看Animal,这个引用还是父类引用
animal.eat()
=>能调用了,到底是啥样子的eat,看new的实例是通过哪个子类new的,该子类是否重写了eat方法
总结:到底能.哪些方法前面说了算,到底.之后这方法长啥样,后面new的说了算
向下转型
Animal animal = new Dog();
animal.play();
animal这个引用是披着狗皮的动物,本质上是dog,披了个Animal的外衣,此时只能调用Animal中定义的方法,
想调用子类拓展的方法该咋办?
脱掉这层外衣,还原为子类引用 = > 向下转型
public static void main(String[] args){
Animal animal = new Dog();
animal.eat();
//play在Animal中不存在
//animal.play();报错,不可用
Dog dog = (Dog) animal;//强转 向下转型
//Animal animal1 = dog;//还可以再 向上转型 变回去
dog.play();//只是换了个名称,对象还是那个对象(new了几个,就还是几个)
}
子类名称 子类引用 = (子类名称)父类引用
Dog dog = (Dog) animal;//脱掉animal对应的对象的外衣还原为具体的子类引用
总结
- 要发生向下转型,首先要发生向上转型
- 重写:返回值完全相同或者至少是向上转型类的返回值
- 毫无关系的两种类型不能作为方法重写的返回值(比如父类int返回值,子类boolean)
问:可以把猫变成狗吗?
Animal animal = new Animal();
//Dog dog = (Dog) animal;//不可以 毫无关系的两个类无法强转
Animal animal = new Animal();//本身就是Animal对象,和Dog毫无关系
Animal animal1 = new Dog();//披着狗皮的Animal,本质上还是一个Dog类的对象
问:啥时候需要用到向下转型?
啥时候发生向上转型,方法接收一个类和当前类的子类,参数指定为相应的父类引用,发生的就是向上转型。
只有某个特殊情况下,需要使用子类拓展的方法,才需要将原本向上转型的引用向下转型还原为子类引用。
做个小题
package interface_test;
class Person {
public void fun() {
this.test();//Person类中test方法可见,被Student覆写
}
Person test() {
System.out.println("1.Person的test方法");
return new Person();
}
}
class Student extends Person {//Student is a Person反过来不可以
public Student test() {//此时子类使用Student作为返回值,父类Person可以
System.out.println("2.Student的test方法");
return new Student();//向上转型
}
}
public class Test {
public static void main(String[] args) {
new Student().fun();
}
}
//2.Student的test方法
- 如果改成
private Person test()
,则输出1.Person的test方法
test方法是private方法,子类根本不知道其存在,也就无法覆写,对子子类来说,这个test方法就是个普通方法,不存在覆写
package a;
class Base{
protected String name = "猴哥";
}
package b;
class SubType extends Base{
public void fun(){
System.out.println(name);//ok
Base base = new Base();
System.out.println(base.name);//t or f此时虽然在子类中,使用的是Base的引用直接访问name属性,还是不行的
}
}
抽象方法(abstract)
向上转型带来最大的好处在于参数统一化,使用一个共同的父类引用,可以接收所有的子类实例
现在有这样的需求:描述形状的类Sharp,三角形,正方形,圆形
要求创建一个方法可以接收Sharp以及所有子类的对象,调用print() =>向上转型用在方法参数
public class Test{
public static void main(String[] args){
fun(new Cycle());
fun(new Square());
fun(new Triangle());
}
public static void fun(Sharp sharp){
sharp.print();
}
}
这三个子类都是Sharp的子类且都覆写了print()
此时多态非常依赖子类覆写方法
普通父类没法强制要求子类覆写方法
若要强制要求子类覆写方法,用到抽象类
- 抽象类是普通类的“超集”,只是比普通类多了一些抽象方法而已
- 抽象方法所在的类必须是抽象类,普通子类若继承了抽象类,必须覆写所有抽象方法
Java中定义抽象类或者抽象方法使用abstract关键字
- 抽象方法所在的类必须使用abstract声明为抽象类。
抽象方法
抽象方法:指的是使用abstract关键字声明,只有函数声明,没有函数实现{}的方法
ps:没有方法体的方法不一定是抽象方法,比如本地方法没有方法体{}
public abstract class Sharp{
public abstract void print();
}
-
一个类若存在抽象方法,必须使用abstract抽象类
-
抽象类中没有具体实现,在子类实现
-
若一个类使用abstract声明为抽象类,不管有没有抽象方法,这个类本身就是一个抽象的概念。只能通过子类向上转型变为抽象父类引用。
-
在IDEA中,类图标带有两个小杠的是抽象类;如果是普通类,则是一个C(class)
Sharp sharp = new Sharp();//error
Person per = new Person();//error
Person per = new China();//ok
- 普通子类继承了抽象类,就必须强制子类覆写抽象类中的所有抽象方法,也满足单继承局限,一个子类只能
extends
一个抽象类。
abstract class A {
abstract void printA();
}
//B是抽象类,可以选择性的覆写父类的抽象方法
abstract class B extends A {
abstract void printB();
}
//C是普通类,必须覆写B中的所有抽象方法(包括继承来的抽象方法)
public class C extends B {
@Override
void printB() {}
@Override
void printA() {}
}
-
抽象类是普通类的超集(普通类有的内容,抽象类都有),只是比普通类多了一些抽象方法而已,抽象类虽然没法直接实例化对象,但是也可以存在构造方法,子类在实例化时,仍遵从继承的规则,先调用父类(抽象类)的构造方法,再调用子类构造方法
-
抽象类只是普通类的超集,只是比普通类多了一些抽象方法而已。若一个需求既可以使用抽象类也可使用接口优先使用接口。
-
抽象类仍然是单继承局限。
-
抽象类虽然没法直接实例化对象,子类仍然满足is a原则,子类和抽象父类之间仍然满足“继承树"的关系 Person 对于 China Sharp 对于 Cycle
接口(interface)
接口的两种表示场景
- 接口表示具备某种能力/行为,子类实现接口时不时is a,而是具备这种行为或者能力
“游泳” -> 能力或者行为, Person满足有用借口,Dog也能满足游泳接口,Duck也能满足游泳接口
- 接口表示一种规范或者标准
"USB接口,5G标准
- 接口中只有全局常量和抽象方法 -> 更加纯粹的抽象概念。其他东西通通没有
- 接口使用关键字interface声明接口,子类使用implements实现接口。
- 一个类可以实现多个接口,但是只能继承一个抽象类
-
USB接口:表示一种规范
package interface_test.usb; //接口使用interface关键字定义:只有全局常量和抽象方法 public interface USB { //插入 public abstract void plugIn(); //工作 public abstract void work(); }
- 子类使用implements实现接口,必须覆写所有的抽象方法
package interface_test.usb;
public class KeyBoard implements USB{
@Override
public void plugIn() {
System.out.println("安装键盘驱动中");
}
@Override
public void work() {
System.out.println("键盘正常工作");
}
}
鼠标、键盘外设都属于USB接口的子类
小问一下
public class Computer {
public void fun(USB usb) {
usb.plugIn();
usb.work();
}
}
此时为何方法的参数用的是USB接口引用?
fun方法就模拟电脑的USB插口
如果fun方法参数Mouse会怎么样?
答:对于电脑的使用者生产者来说,根本不关心到底哪个具体设备插入到我的电脑上,只要这个设备满足了USB接口,都能被电脑识别,就可以实现一个接口可以接受无数种设备,只要这个设备满足USB接口,都可以插入到电脑且被电脑识别。即兼容所有的USB子类对象
fun(Mouse mouse) =》这个插口只能插鼠标,键盘都无法识别,这是两个毫无关系的类
public class Computer {
public static void main(String[] args) {
Computer computer = new Computer();
Mouse mouse = new Mouse();
//插入鼠标 ok
computer.fun(mouse);
KeyBoard keyBoard = new KeyBoard();
computer.fun(keyBoard);
Campera campera = new Campera();
computer.fun(campera);
}
public void fun(USB usb) {
usb.plugIn();
usb.work();
}
}
- 接口表示能力
接口允许多实现,一个类可能具备多个能力,同时实现多个父接口,若实现多个父接口,子类普通类,需要覆写所有的抽象方法
public class Dog implements IRun,ISwim{
@Override
public void run() {
System.out.println("狗娃子在跑");
}
@Override
public void swim() {
System.out.println("狗娃子在狗刨");
}
}
public class Dog implements IRun,ISwim
表示子类同时实现了多个父接口
- 由于接口中只有全局常量和抽象方法,因此接口中
public abstract => 抽象方法
static final => 常量
全都可以省略!!(必须在接口中)
//表示能飞
public interface IFly {//接口中只有全局常量和抽样方法
String test = "nb";//全局常量 等价于 static final String test
//private protected 不允许出现在接口中,public多余,所以只有默认的了
public abstract void fly();//public 和 abstract多余
}
String test = "nb";
等价于static final String test = "nb";
public abstract void fly();
等价于void fly();
写上多余,有警告
instanceof关键字
当发生向下转型时会有风险,类型转换异常,使用instanceof关键字
引用名称 instanceof
类 =》返回布尔值,表示该引用指向的本质是不是该类的对象
Animal animal1 = new Animal();
Animal animal2 = new Dog();
System.out.println(animal1 instanceof Dog);//false
System.out.println(animal2 instanceof Dog);//true
使用instanceof
关键字的返回值搭配的分支语句进行类型转换