面向对象
每天坚持做一件简单但有意义的事情……滴,打卡!
构造函数
特点
- 函数名与类名相同
- 不用定义返回值类型
- 没有具体的返回值
作用
给对象进行初始化
构造函数与一般函数的区别
构造函数在对象初始化时只调用一次,而一般函数可以调用多次
this关键字
作用
- 代表当前对象
- 区分局部变量和成员变量
细节
this也可以用于在构造函数中调用其他函数,但只能定义在函数的第一行,因为初始化动作要先执行.
static关键字
static的特点
- static是一个修饰符,用于修饰成员(成员变量和成员函数)
- static修饰的成员被所有对象所共享
- 出现的原因是为了解决代码冗余,节约内存消耗
- 为了实现共享,所以也就必须要在对象创建之前出现
- 被static修饰的成员也叫类成员
被修饰后的成员有以下特点
- 随着类的加载而加载
- 优先于对象存在
- 被所有对象所共享
- 可直接被类名调用
静态使用的注意事项
- 静态方法只能访问静态成员
- 静态方法中不可写this,super关键字
- 主函数是静态的
- 非静态既可以访问静态的,又可以访问呢非静态的
成员变量和静态变量(类变量)的区别
1. 两个变量的生命周期不同
- 成员变量随着对象的创建而存在,随着对象的被回收而释放
- 静态变量随着类的加载而存在,随着类的消失而消失
2. 调用方式不同
- 成员变量只能被对象调用
- 静态变量既可以被对象调用,也可以被类名调用
3. 别名不同
- 成员变量也称为实例变量
- 静态变量称为类变量
4. 数据的存储位置不同
- 成员变量数据存储在堆内存中,所以也叫对象的特有数据
- 静态变量数据存储在方法区(共享数据区)静态区,所以也叫对象的共享数据
main函数
main函数的特点
- 格式是固定的
- 被JVM所识别和调用
main函数解析
- public:权限最大
- static:不需要对象,直接用主函数所属类名调用即可
- void:没有具体返回值
- main:函数名,不是关键字,只是一个供JVM识别的固定名称
- String[] args:主函数的参数列表,是一个数组类型的参数,而且元素都是字符串类型
- args:形参,唯一可变但为了标准不变
public class TestMain {
public static void main(String[] args) {// new String[0]
System.out.println(args);// [Ljava.lang.String;@15db9742
System.out.println(args.length);// 0
System.out.println(args[0]);// Exception in thread "main"
// java.lang.ArrayIndexOutOfBoundsException:
// 0
// at com.hoki.test.TestMain.main(TestMain.java:19)
// javac TestMain.java
// java TestMain hehe gege xixi
/*for (int i = 0; i < args.length; i++) {
System.out.println(args[i]);
}*/
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
什么时候用静态
1. 静态变量
当分析对象中所具备的成员变量的值都是相同的,这时这个成员就可以被静态所修饰.只要数据在对象中都是不同的,就是对象的特有数据,必须存储在对象中,是非静态的.如果是相同的数据,对象不需要做修改,只需要使用即可,不需要存储在对象中,定义成静态的.
2. 静态方法
函数是否用静态修饰取决于该函数功能是否需要访问到对象中的特有数据.简单点说,从源代码看,该功能是否需要访问非静态的成员变量,如果需要,该功能就是非静态的;如果不需要,就可将该功能定义为静态的.当然,也可以定义为非静态的,但是非静态需要被对象调用,而仅创建对象调用非静态的没有访问特有数据的方法,该对象的创建是没有意义的.
public class TestStatic {
public static void main(String[] args) {
Demo.speak();
}
}
class Demo {
int age;// 非静态成员变量,对象的特有数据
static int num = 9;// 静态成员变量,共享数据
Demo(int age) {
this.age = age;
}
public static void speak() {
System.out.println("jjjj");// 未访问到对象的特有数据,这时speak方法要用static修饰
// System.out.println(num);//访问的是静态成员变量,这时speak方法要用static修饰
// System.out.println(age);//访问到了对象的特有数据,这时speak方法就应该是非静态的
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
静态代码块
特点
静态成员随着类的加载而加载,而静态代码块则随着类的加载而执行,且只执行一次
作用
给类进行初始化
public class TestStaticCodeBlock {
public static void main(String[] args) {
new StaticCodeBlock().show();
new StaticCodeBlock().show();//静态代码块随着类的加载而执行,且只执行一次
}
}
class StaticCodeBlock {
static {
System.out.println("执行了静态代码块");
}
void show() {
System.out.println("执行了对象的非静态方法");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
public class TestStaticCodeBlock01 {
public static void main(String[] args) {
StaticCodeBlock01.show();
}
}
class StaticCodeBlock01 {
static int num;
static {
num = 10;//也可以在上面进行赋值操作,但是在静态代码块中可以进行更灵活的赋值操作;
System.out.println("执行了静态代码块");
}
static void show() {
System.out.println("静态代码块对类进行了初始化");
System.out.println(num);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
构造代码块
构造代码块是给所有对象进行初始化,而构造方法是给对应的对象进行针对性的初始化;即构造方法具有初始化对象的针对性,而构造代码块具有初始对象的通用性.当然,在方法体内部还可以有局部代码块.
public class TestConstructorCodeBlock {
public static void main(String[] args) {
Person p1 = new Person();
p1.speak();
Person p2 = new Person("亚瑟");
p2.speak();
new Person();
}
}
class Person {
private String name;
{
System.out.println("随着对象的创建,构造代码块执行了");
play();
}
Person() {
System.out.println("无参构造方法对对象的初始化执行了");
name = "三毛";
// play();//重复
}
Person(String name) {
System.out.println("有参构造方法对对象的初始化执行了");
this.name = name;
// play();//重复
}
public void play() {
System.out.println("不管有参数的构造方法还是无参数的构造方法都要执行同一个方法,"
+ "所以干脆写在构造代码就可以避免重复了,这也就是构造代码块的作用所在.");
}
public void speak() {
System.out.println("name:" + name);
System.out.println("--------------------");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
public class TestConstructorCodeBlock01 {
public static void main(String[] args) {
Person01.show();// 通过类名调用,不创建对象
}
}
class Person01 {
// 证明构造代码块随着对象的创建而执行
{
System.out.println("执行了构造代码块");
}
static void show() {
System.out.println("静态show方法执行");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
继承
java支持单继承,不支持多继承,但对C++的多继承机制进行了改良.
继承的好处
- 提高了代码的复用性
- 让类与类之间产生关系,给第三个特征”多态”提供了前提
java不直接支持多继承的原因
如果一个子类有两个父类,同时两个父类有相同的方法,那么将无法确定子类要调用哪个具体的方法,所以java不直接支持多继承(但支持间接继承).
当要使用一个继承体系时
- 查看该体系中的顶层类,了解该体系的基本功能
- 创建该体系中的最子类对象,完成功能的使用
super关键字
当本类的成员和局部变量同名时用this区分;当子父类中的成员变量同名用super区分父类.
super与this的区别
- this代表一个本类对象的引用
- super代表一个父类空间
public class TestExtends {
public static void main(String[] args) {
new Zi().show();
}
}
class Fu {
String str = "调用了父类的成员变量";
}
class Zi extends Fu {
String str = "调用了子类的成员变量";
void show() {
System.out.println(str);// 子类有str就不调用父类的str
System.out.println(super.str);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
细节
子类中不能直接访问父类中私有的成员
函数的两个特性
- 重载:同一个类中
- 覆盖:在子类中
覆盖(override)
当子父类中有一模一样的成员,会优先执行子类的那个
覆盖的注意事项
- 子类方法覆盖父类方法时,子类权限必须要大于等于父类的权限
- 静态只能覆盖静态,或被静态覆盖
什么时候需要使用覆盖操作
当对一个类进行子类的扩展时,子类需要保留父类的功能声明,但是要定义子类中该功能的特有内容时,就使用覆盖操作完成.
public,protected,private以及默认的访问权限作用域
如果在修饰的元素上面没有写任何访问修饰符,用default表示.
作用域 | 当前类 | 同一包 | 子孙类 | 其他包 |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
default | √ | √ | × | × |
private | √ | × | × | × |
java编译器的执行顺序
- 编译Java文件时,遇到不认识的类名时,会去该项目的目录下找
- 如果找不到就找跟类名相同的java文件进行编译
- 然后再调用.class文件
小细节
如果一个类中的方法都是静态的,那么该类是不需要创建对象的,为了保证不让其他程序创建该类对象,可以将该类的构造函数初始化.
单例模式
可保证一个类在内存中的对象唯一性
如何保证对象唯一性?
- 不允许其他程序用new创建该类对象
- 在该类中创建一个本类实例
- 对外提供一个方法让其他程序可以获取该对象
步骤
- 私有化该类的构造函数
- 通过new在本类中创建私有并静态的本类对象
- 定义一个公有并静态的方法,将创建的对象返回
单例-饿汉式-代码演示
类一加载,对象就已经存在了
public class Single {
private static Single s = new Single();
private Single() {
}
public static Single getInstance() {
return s;
}
}
class SingleDemo {
public static void main(String[] args) {
Single ss1 = Single.getInstance();
Single ss2 = Single.getInstance();
System.out.println(ss1==ss2);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
单例-懒汉式-代码演示
类加载进来,没有对象,只有调用了getInstance方法时,才会创建对象,这叫延迟加载
class Single2
{
private static Single2 s = null;
private Single2() {
}
public static Single2 getInstance() {
if(s == null){
s = new Single2();
}
return s;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
子父类中构造函数的特点
在子类的构造函数中的第一行有一条默认的隐式语句:super();.super();调用的就是父类中的空参数的构造函数.
子类的实例化过程:
子类中所有的构造函数默认都会访问父类中的空参数的构造函数
为什么子类实例化的时候要访问父类中的构造函数?
那是因为子类继承了父类,获取到了父类中的内容,所以在使用父类内容之前,要先看父类是如何对自己的内容进行初始化的.所以子类在构造对象时,必须访问父类中的构造函数.为了完成这个必须的动作,就在子类的构造函数中加入了super();语句.如果父类中没有定义空参数的构造函数,那么子类的构造函数必须用super明确调用父类中哪个构造函数.同时,子类构造函数中如果使用this调用了本类构造函数时,super就没有了,因为super和this都只能定义在第一行,所以只能有一个.但是,可以保证的是,子类中肯定会有其他的构造函数访问父类的构造函数.
为什么super语句必须要定义在子类的构造函数的第一行?
因为父类的初始化的动作要先完成.
一个对象实例化的过程
Person p = new Person();
- 1
- JVM会读取指定路径下的Person.class字节码文件,并加载进内存,并会先加载Person的父类(如果有直接的父类的情况下)
- 在堆内存中开辟空间,分配地址
- 并在对象空间中,对对象中的属性进行默认初始化
- 调用对应的构造函数进行初始化
- 在构造函数中,第一行会先到调用父类中构造函数进行初始化
- 父类初始化完毕后,再对子类的属性进行显示初始化
- 然后再进行子类构造函数的特定初始化
- 初始化完毕后,将地址值赋值给引用变量
class Fu {
Fu() {
super();// default
show();
this.show();// 传过来一个子类对象的引用,调用了子类对象的方法
return;// default
}
void show() {
System.out.println("fu show");
}
}
class Zi extends Fu {
int num = 8;
Zi() {
// 通过super初始化父类内容时,子类的成员变量并未显示初始化,
// 等super()父类初始化完毕后,才进行子类的成员变量显示初始化
super();// default
return;// default
}
void show() {
System.out.println("zi show...." + num);
}
}
public class ExtendsDemo5 {
public static void main(String[] args) {
Zi z = new Zi();
z.show();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
final关键字
继承的缺陷
打破了对象封装性,不利于程序的模块化扩展
class Fu{
void method (){
//调用了底层系统的资源
}
}
class Zi extends Fu{
void method (){
System.out.println("此方法覆盖了父类的方法");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
final关键字的使用
- final可修饰类,方法,变量
- final修饰的类不可以被继承
- final修饰的方法不可以被覆盖
- final修饰的变量是一个常量,只能被赋值一次
- 内部类只能访问被final修饰的局部变量
为什么要用final修饰变量?
其实在程序中如果一个数据是固定的,那么直接使用这个数据就可以了,
但是这样阅读性很差,所以给该数据起个名称,且这个变量名称的值不能变化,
所以需要加上final关键字固定.
写法规范
常量所有的字母都大写,多个单词,中间用_连接
抽象类
抽象类的特点
- 方法只有声明,没有实现;需要被abstract关键字修饰,抽象方法必须定义在抽象类中,该类必须也被abstract关键字修饰.
- 抽象类不可以被实例化,也没有将其实例化的意义
- 抽象类必须由其子类覆盖了所有的抽象方法后,该子类才可以实例化,否则,这个子类还是抽象类.
关于抽象类的几个问题
- 抽象类是否有构造函数?
有;用于给子类对象进行初始化.
- 抽象类可以不定义抽象方法吗?
可以;但是很少见,目的就是不让该类创建对象,AWT的适配器类就是这种类.通常,这个类中的方法有方法体,但是却没有内容.
- 抽象关键字不可以和哪些关键字共存?
private:抽象方法需要被子类对象的方法覆盖;
static:调用没有方法体的抽象方法没有意义;
final:这是与abstract完全相反的属性;
- 抽象类与一般类的异同
相同点:
抽象类和一般类都是用来描述事物的,都在内部定义了成员;
不同点:
1. 一般类有足够的信息描述事物;抽象类描述事物的信息有可能不足.
2. 一般类中不能定义抽象方法,只能定义非抽象方法;抽象类中可定义抽象方法,同时也可以定义非抽象方法.
3. 一般类可以被实力化,抽象类不可以被实例化.
- 抽象类一定是父类吗?
是的;因为需要子类覆盖其方法后才可以对子类实例化.
// 描述雇员
abstract class Employee {
private String name;
private String id;
private double pay;
public Employee(String name, String id, double pay) {
this.name = name;
this.id = id;
this.pay = pay;
}
public abstract void work();
}
// 描述程序员
class Programmer extends Employee {
public Programmer(String name, String id, double pay) {
super(name, id, pay);
}
@Override
public void work() {
System.out.println("coding...");
}
}
// 描述经理
class Manager extends Employee {
private int bonus;
public Manager(String name, String id, double pay, int bonus) {
super(name, id, pay);
this.bonus = bonus;
}
@Override
public void work() {
System.out.println("manage");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
接口
接口中常见的成员
- 全局变量: public static final
- 抽象方法: public abstract
接口的特点
- 接口是对外暴露的规则
- 接口是程序的功能扩展
- 接口的出现降低了耦合性
- 接口可以多实现
- 类与接口之间是实现关系,且类继承一个类的同时可以实现多个接口
- 接口与接口之间可以多继承
抽象类与接口的区别
- 相同点: 都是不断向上抽取而来的
- 不同点:
- 抽象类需要被继承,且只能单继承;接口需要被实现,且可以多实现;
- 抽象类中可以定义抽象方法和非抽象方法,子类继承后,可以直接使用非抽象方法;接口中只能定义抽象方法,必须由子类去实现;
- 抽象类的继承是is a的关系,在定义体系的基本共性内容;接口的实现是like a的关系,在定义体系的额外功能;
多态
多态的好处
提高了代码扩展性,前期定义的代码可以使用后期的内容
多态的局限
前期定义的内容不能调用后期子类特有的内容
多态的前提
- 必须有关系,要么继承,要么实现
- 要有覆盖
多态的向上转型和向下转型
- 向上转型是为了限制父类对子类特有方法的访问;
- 向下转型是为了可以使用子类的特有方法.
不管是向上转型还是向下转型都必须是子类对象在做着类型的变化
多态的类型判断instanceof
instanceof关键字: 用于判断对象的具体类型,只能用于引用数据类型判断;
通常在向下转型前用于健壮性的判断;
多态时成员的特点
- 成员变量
class Fu{
String str = "调用了父类的成员变量";
}
class Zi extends Fu{
String str = "调用了子类的成员变量";
}
public class Test {
public static void main(String[] args) {
Fu1 fu = new Zi();// 父类的引用指向了子类的对象
System.out.println(fu.str);// 输出了父类的成员变量
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 成员函数
class Fu {
public void show() {
System.out.println("调用了父类的成员方法");
}
}
class Zi extends Fu {
public void show() {
System.out.println("调用了子类的成员方法");
}
}
public class Test {
public static void main(String[] args) {
Fu fu = new Zi();// 父类的引用指向了子类的对象
fu.show();// 调用了子类的成员方法
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 静态函数
class Fu1 {
static void method() {
System.out.println("fu static method");
}
}
class Zi1 extends Fu1 {
static void method() {
System.out.println("zi static method");
}
}
public class Test {
public static void main(String[] args) {
Fu1 fu1 = new Zi1();// 父类的引用指向了子类的对象
fu1.method();// fu static method
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
内部类
特点
- 内部类可直接访问外部类中的成员
- 外部类要访问内部类,必须建立内部类的对象
修饰符
- 内部类可以访问到外部类的成员变量
class Outer {
private int number = 10;
class Inner {
void show() {
System.out.println("内部类成功访问了外部类的成员:" + number);
}
}
public void method() {
Inner in = new Inner();
in.show();
}
}
public class InnerClassDemo {
public static void main(String[] args) {
Outer outer = new Outer();
outer.method();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 如果内部类是静态的,就相当于一个外部类
class Outer {
static class Inner {
void show() {
System.out.println("show run...");
}
}
}
public class InnerClassDemo {
public static void main(String[] args) {
Outer.Inner in = new Outer.Inner();
in.show();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 如果内部类是静态的,且成员也是静态的情况
class Outer {
class Inner {
void show() {
System.out.println("show run...");
}
//static methods can only be declared in a static or top level type
static void function(){
System.out.println("function run...");
}
}
}
public class InnerClassDemo {
public static void main(String[] args) {
Outer.Inner.function();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19