镇楼图

Pixiv:₩ANKE



六、面向对象特性

(软件)包

包用于类同名发生冲突的情况,比如第一个人开发了Math类,结果第二个人也是Math类。此外Java中的util也是非常常用的包

包的作用

■区分名字相同的类

■用于管理类

■控制访问范围

package <包名>;
//表示从此行代码开始对下述所有类进行打包
import <包名/类名>;
//通过import语句声明要使用的包/类/接口...

不同的包可以进行区分,即使类名一致只要包不同即可

在项目src文件中建立软件包文件,此外会用.区分级,比如创建test.ybb,则会在test文件夹下创建ybb文件夹,然后在文件夹下面则可以创建java文件,比如创建Math类

package用于打包类

这样就可以通过import引入包来使用

包的命名和变量一样,可以用.进行分层,如

com
	a1
		t1
			m1
				1.java
				2.java
				...
			m2
				1.java
				2.java
				...
			...
则可以使用com.a1.t1.m1获取其所有相关包

java常用包

java自带了很多常用的包

■java.lang.*:基本包,默认引入无需再引入

■java.util.*:系统工具包

■java.net.*:网络包以进行网络开发

■java.awt.*:用于做界面开发

具体包怎样使用可以查看JDK文档进行学习,这里不再说明

特征——封装

封装简单来说就是数学中抽象的概念

将属性、方法封装于类,其他地方只能通过被授权的属性、方法

封装的优点

■只需要了解其功能调用即可,具体细节无需考虑

■可对数据进行验证,保证安全合理

特征——继承

继承是针对于多个类的情况

比如大量的类都存在相同的属性、方法,但其具体细节又有诸多不同,能否进一步封装?

简单来说就是子类(派生类)封装在一个更大的类(父类,超类,基类)中,父类中的所有属性、方法,在子类中可以使用而不需要再定义从而减少代码量。父类中所有子类共有的属性方法体现了子类之间的共性,而在其子类中不同的属性方法体现子类之间的不同。

class 子类 extends 父类{
    /*...*/
}

继承的优点

■提高代码复用性,减少代码量

■提高代码的扩展性、维护性

public class Hello {
    public static void main(String[] args) {
        Graduate g = new Graduate();
        g.test();
        /*正常使用时无异
        但见减少了很多因为相同导致的代码量*/
    }
}

public class Student{
    public String name;
    public int age;
    private double score;

    public void setName(String name) {
        this.name = name;
    }

    public void out(){
        System.out.println("学生名:"+name+" 年龄:"+age+" 分数:"+score);
    }
}

public class pupil extends Student{
    public void test(){
        System.out.println("小学生");
    }
}

public class Graduate extends Student{
    public void test(){
        System.out.println("大学生");
    }
}

访问修饰符

用于控制属性、方法的访问权限(范围),只有全局变量才可以加修饰符,总共有四种

(1)public

对外公开

(2)protected

对子类和同一包中类公开

(3)默认

向同一个包的类公开

(4)private

仅类本身可访问

访问修饰符 不同包 同包 子类 同类
public
protected ×
默认 × ×
private × × ×

■对于类只有默认级别和public级别

继承相关的问题

■根据访问权限的修饰符,父类的私有属性方法无法被子类直接访问。可以保证父类的特殊性

class father {
    public int n1;
    protected int n2;
    int n3;
    private int n4;
	public void f(){
        System.out.println(n4);
    }
}

class son extends father {
    public void Test(){
        System.out.println(n1);
        System.out.println(n2);
        System.out.println(n3);
        //System.out.println(n4);
        //无法直接调用private变量
        f();
        //但可以通过父类其他非private方法间接访问private
    }
}

■子要先构造父类,才能有子类。因此创建子类对象时其中参数会先传给父类,然后再传给子类,依次会创建两个类。也就是说参数不仅要满足子类还要满足父类

但一个父类有很多子类,每个子类都有可能有不同的参数,不可能每一种参数都要对应父类再写一遍。因此有其他super方法存在

■创建子类前需创建父类

在创建子类前会默认地使用super关键字来调用父类

class father{
    father(){
        System.out.println(1);
    }
}
class son extends father {
    son(){
        /*super();*/
        //会默认地执行super();
        //因此调用son构造器实际效果是打印1再打印2
        System.out.println(2);
    }
}

■super关键字

this用来指定当前类,而super用来指定父类,语法是一样的。可以通过super调用父类的方法、属性、构造器

class grandfather{
    grandfather(int a){
        System.out.println(1);
    }
}

class father extends grandfather{
    father(){
        /*super();*/
        //如果调用super()会发现并不存在无参的构造器
        //因此会报错
        super(3);
        //可以自己手动定义super
        System.out.println(2);
    }
}
class son extends father {
    son(){
        /*super();*/
        System.out.println(3);
    }
}

但是对于this和super关键字来说如果是调用构造器必须位于第一行,但总不可能两个都在第一行。因此无法共存,你要么用this构造器要么用super构造器,不能同时使用

其实也比较合理,如果使用this那么就转到了其他重载的构造器,如果里面没指定那么就会默认super()。不管是写没写总会存在this或super,最后也一定会创建父类再创建子类

■Object类

Object是Java中最基本的类,因此你写的任何类都会是Object的子类

■单继承机制

在Java中,是不允许多对多的,一个子类只能有一个父类也就是一对多的树结构。其他语言像是Python就会允许子类有多个父类

■查找关系

在查找上,可以简单理解为作用域,会从最小的作用域开始就近查找,然后逐层往上看是否存在

会先查看子类是否存在,然后父类,以此类推,如果到了Object最基本的类都不存在那么会报错

方法重写(覆盖,override)

方法也就是父类的方法可以被子类的方法进行覆盖,在有时可能会有特殊需求以覆盖父类的某一方法,但是子类覆盖的方法其权限可以扩大但无法被降低

class father {
	void f(int a){
        System.out.println(a);
    }
}
class son extends father {
	public/*private会报错*/ void f(int a) {
        //重写是指函数名,参数列表,返回类型完全一样
        //此外还有权限限制,比如父类是default
        //子类可以是default及以上的但就是不能低于default的权限
        //定义private会报错
        System.out.print(a);
        super.f(5);
        //搭配super可以访问被重写的方法
    }
}

备注1:只针对方法(非构造器),不针对属性,因此属性不存在权限限制一说

备注2:返回类型可以不同但必须是父子类的关系,如father类方法返回Object,son类返回Boolean就可以

多态(polymorphic)

同一功能的方法要用到不同类时如果只是使用重载会导致代码量居多,而多态则是解决这种问题

//比如要输入很多类型的数据
//重载显然是可以解决这类问题的
//但代码量还是有些多
void print(short a){/**/}
void print(int a){/**/}
void print(boolean a){/**/}
void print(long a){/**/}
void print(float a){/**/}
void print(double a){/**/}
void print(String a){/**/}
//...

所谓多态指的是方法/对象具有多种形态

方法的重写、重载就体现了多态,同一名字可以具有不同功能

而对象的多态可以允许多个输入类型

即左边的编译类型和右边的运行类型可以不一致

只要右边是左边或是左边的子类即可

那么在设置函数时只要是其父类即可

//假设f类有子类s1,s2,s3
f a = new s1();
f b = new s2();
f c = new s3();

void print(Object a){
    //Object是其他类的子类
    //由此通过对象的多态减少代码量
    System.out.println(a);
}

向上/下转型

class g{}
class f extends g{}
class s extends f{}

f obj1 = new s();
//称为向上转型
//即编译类型父类,运行类型子类
//可以允许对象的多态

(f)obj1;
//称为向下转型
//通过向下转型可以访问子类一般的属性、方法


//动态绑定机制:
//重写方法会与对象的运行类型绑定
//属性、非重写方法则是优先用当前声明类型的属性

Java中有instanceof运算符可以判断对象的关系

x instanceof y;
//判断x是否为y或y的子类

对象数组

Object a[] = new Object[3];
for(int i = 0;i < a.length;i++){
	a[i] = new Object();
}
Integer i1 =  new Integer(100);
Integer i2 =  new Integer(100);
i1 == i2;
//false,仅判断基本类型的地址
//==仅会判断当前类型
i1.equals(i2);
//true,子类会重写方法改成判断值
//具体可看jdk源码

A a1 = new A(10);
A a2 = new A(10);
a1.equals(a2);
//false,没有对应子类方法,则调用Object方法即判断地址

class A{
    int age;
    A(int age){
		this.age = age;
	}
}

Object类

Object是基类,其中有很多最基本的方法

equals(object obj)

作用:判断与其他对象地址是否相等,其他类型可能会重写此方法

hashCode()

作用:返回改对象的哈希值,这一方法用于提高数据结构哈希表的性能

toString()

作用:返回该对象的全类名(包名+类名)@16进制哈希值

静态成员(static)

若使用static指定类的属性或方法,会有特殊的性质

class A{
    public static int a;
}

A.a;

(1)可直接用类来调用,静态成员代表了类的共性,每个对象的静态成员都是同一个,是所有实例的成员

(2)静态成员的生命周期与实例无关,哪怕销毁或是没有实例,静态成员依然可以使用

(3)类方法不允许this、super关键字,且只能访问类方法和类变量

class B{
    static int a;
    int b;
    static int f(){
        a = 3;
        b = 10;//无法访问非静态成员
        this.g();//无法使用this、super
    	return 2;
    }
    static int g(){
        return 1;
    }
}

main方法

(1)因为需要JVM调用因此权限必须为public

(2)因为不需要创建对象因此设定了static

(3)此外java可以接受string数组参数,因此参数采用String[]

代码块

可以使用{}简单地创建代码块,是一种隐式调用

static{
	//...
}

{
    //...
}

代码块可以用于类中,未加static的代码块会在每次创建对象时调用

class A{
	static int a;
    void a(){
		System.out.println("A");
	}
    {
		System.out.println("成功调用了");
    }
}

//调用1
A a[] = new A[5];
for(int i = 0;i < a.length;i++){
	a[i] = new A();
    //代码块总共执行了5次
}

//调用2
A.a = 5;
//如果只使用类的静态成员并不会执行普通代码块

静态代码块只会在类加载的时候而执行即只执行1次

类加载后续会着重说明,简单来说就是在用到类时就会加载

(1)创建对象

(2)创建子类对象,也加载父类

(3)使用类静态成员

class A{
	static int a;
    void b(){
		System.out.println("A");
	}
    static{
		System.out.println("成功调用了");
    }
}

//调用
A.a = 3;

在创建对象时,会进行如下调用顺序:

(备注:多个定义会顺序调用)

(1)调用静态代码块、静态属性

(2)调用普通代码块、普通属性

(3)调用构造方法

在构造方法前面隐藏地存在super和普通代码块

更复杂些,如果是子类会出现父类的情况,调用顺序如下

(1)父类静态代码块、静态属性

(2)子类静态代码块、静态属性

(3)父类普通代码块、普通属性

(4)子类普通代码块、普通属性

(5)父类构造方法

(6)子类构造方法

断点调试(个人理解)

这里以IJ为例,IJ提供了如下的调试机制

在上边有一排按钮表明了如何调试,分别是步过、步入、强制步入、步出、丢帧、运行到光标处

步入(F7)执行到下一应该执行的代码,当前在定义类的语句,那么步入会跳转到类那边也就是创建类时要做的代码

步过(F8)执行到下一行代码,会忽略代码一行中涉及到其他代码块的语句

步出(shift+F8)执行到下一块代码,会忽略这一块很多行的代码

强制步入(Alt+shift+F7)和步入类似,但不仅会涉及到当前文件还会涉及到其他文件,比如调用println函数如果强制步入会跳转到源文件给你指明源代码中println的函数



参考教程

韩顺平 零基础30天学会Java

Java中抽象类和接口的介绍及二者间的区别

posted on 2022-05-01 17:04  摸鱼鱼的尛善  阅读(39)  评论(0编辑  收藏  举报