面向对象高级
static 关键字
static 关键字是静态的意思,是Java中的一个修饰符,可以修饰成员方法,成员变量
被static修饰的成员变量,一般叫做静态变量
被static修饰的成员方法,一般叫做静态方法
static 修饰的特点
1.被类的所有对象共享:是判断是否使用静态关键字的条件
2.随着类的加载而加载,优先于对象存在:对象需要类被加载后,才能创建
3.可以通过类名调用:也可以通过对象名调用(推荐使用类名调用)
static 关键字的注意事项
1.静态方法只能访问静态的成员
2.非静态方法可以访问静态的成员,也可以访问非静态的成员
3.静态方法中是没有this关键字:this是指当前对象的引用,需要在创建对象后才会存在,静态存在时,对象可能还未被创建。
权限修饰符
继承
概念
让类与类之间产生关系(子父类关系),子类可以直接使用父类中非私有的成员。
继承是面向对象三大特征之一。
可以使得子类具有父类的属性和方法,还可以在子类中重新定义,以及追加属性和方法。
格式
public class 子类名 extends 父类名 { }
范例:public class Zi extends Fu { }
Fu:是父类,也被称为基类、超类
Zi:是子类,也被称为派生类
继承的好处和弊端
继承的好处
提高了代码的复用性
提高了代码的维护性
让类与类之间产生了关系,是多态的前提
继承的弊端
继承是侵入性的
降低了代码的灵活性:继承关系,导致子类必须拥有父类非私有属性和方法,让子类自由的世界中多了些约束
增强了代码的耦合性:代码与代码之间存在关联都可以将其称之为"耦合"。
继承的特点
Java只支持单继承,不支持多继承,但支持多层继承。
super 关键字的用法和 this 关键字的用法相似
this:代表本类对象的引用
super:代表父类存储空间的标识(可以理解为父类对象引用)
this和super的使用分别
成员变量:
this.成员变量 - 访问本类成员变量
super.成员变量 - 访问父类成员变量
成员方法:
this.成员方法 - 访问本类成员方法
super.成员方法 - 访问父类成员方法
构造方法:
this(…) - 访问本类构造方法
super(…) - 访问父类构造方法
继承的成员变量访问特点
在子类方法中访问一个变量
子类局部范围找
子类成员范围找
父类成员范围找
如果都没有就报错(不考虑父亲的父亲…)
注意:如果子父类中,出现了重名的成员变量,通过就近原则,会优先使用子类的成员变量。
如果一定要使用父类的,可以通过super关键字,进行区分。
继承中成员方法的访问特点
通过子类对象访问一个方法
子类成员范围找
父类成员范围找
如果都没有就报错(不考虑父亲的父亲…)
方法重写
在继承体系中,子类出现了和父类中一模一样的方法声明(方法名、参数列表、返回值类型)
方法重写的应用场景
当子类需要父类的功能,而功能主体子类有自己特有内容,可以重写父类中的方法,这样,即沿袭了父类的功能,又定义了子类特有的内容
方法重写注意事项
父类中私有方法不能被重写(父类私有成员子类是不能继承的)
父类非静态方法,子类也必须通过非静态方法进行重写
注意:父类静态方法不能被重写!如果子类中,也存在一个方法声明一模一样的方法,可以理解为:子类将父类中同名的方法,隐藏了起来,并非是方法重写!
子类重写父类方法时,访问权限必须大于等于父类(public > 默认 > 私有)
方法重载
在同一个类中,方法名相同,参数列表不同(类型不同或者数量不同),与返回值无关。
继承中构造方法的访问特点
子类中所有的构造方法默认都会访问父类中无参的构造方法
子类在初始化的时候,有可能会使用到父类中的数据,如果父类没有完成初始化,子类将无法使用父类的数据。
子类初始化之前,一定要先完成父类初始化。
构造方法的第一条语句默认都是:super()
注意:如果我们编写的类,没有手动指定父类,系统也会自动继承Object (Java继承体系中的最顶层父类)
如果父类中没有空参构造方法,只有带参构造方法,会出现什么现象:子类会报错
解决方案:
方式一:子类通过 super,手动调用父类的带参的构造方法
方式二:子类通过 this 去调用本类的其他构造方法,本类其他构造方法再通过 super 去手动调用父类的带参的构造方法(不推荐)
注意:this(…) super(…) 必须放在构造方法的第一行有效语句,并且二者不能共存。
1 class Fu { 2 int age; 3 // 空参数构造方法 4 //public Fu(){ 5 //System.out.println("父类空参数构造方法"); 6 //} 7 // 带参数构造方法 8 public Fu(int age){ 9 this.age = age; 10 } 11 } 12 13 class Zi extends Fu { 14 15 //方式二 16 public Zi(){ 17 this(10); // super(); 18 } 19 20 //方式一 21 public Zi(int age){ 22 super(age); 23 } 24 }
抽象类
抽象方法:将共性的行为(方法)抽取到父类之后,发现该方法的实现逻辑无法在父类中给出具体明确,该方法就可以定义为抽象方法。
抽象类:如果一个类中存在抽象方法,那么该类就必须声明为抽象类
抽象方法的定义格式:
public abstract 返回值类型 方法名(参数列表);
抽象类的定义格式:
public abstract class 类名{}
抽象类注意事项
抽象类不能实例化(创建对象)
抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
可以有构造方法
抽象类的子类
要么重写抽象类中的所有抽象方法
要么可以将自己也变成一个抽象类
final关键字
final 关键字是最终的意思,可以修饰(方法,变量,类)
final 修饰的特点
修饰方法:表明该方法是最终方法,不能被重写
修饰变量:表明该变量是常量,不能再次被赋值
变量是基本类型:final 修饰指的是基本类型的数据值不能发生改变
变量是引用类型:final 修饰指的是引用类型的地址值不能发生改变,但是地址里面的内容是可以发生改变的
修饰类:表明该类是最终类,不能被继承
final修饰成员变量 初始化时机
方式一:在创建的时候,直接赋值(推荐)
方式二:在构造方法结束之前,完成赋值
1 class Student{ 2 //方式一 3 final int a=10; 4 5 //方式二 6 final int b; 7 public Student(){ 8 b=10; 9 } 10 }
代码块
在Java中,使用 { } 括起来的代码被称为代码块
分类
局部代码块
位置:方法中定义
作用:限定变量的生命周期,及早释放,提高内存利用率
1 package com.test.block.local; 2 3 public class Test { 4 5 public static void main(String[] args) { 6 { 7 int a = 10; 8 System.out.println(a); 9 } 10 11 // System.out.println(a); //报错 12 } 13 }
构造代码块
位置:类中方法外定义
特点:每次构造方法执行的时,都会执行该代码块中的代码,并且在构造方法执行前执行
作用:将多个构造方法中相同的代码,抽取到构造代码块中,提高代码的复用性
1 public class Test { 2 public static void main(String[] args) { 3 Student stu1 = new Student(); 4 Student stu2 = new Student(10); 5 } 6 } 7 8 class Student { 9 10 { 11 System.out.println("我是构造代码块"); 12 } 13 14 public Student(){ 15 System.out.println("空参数构造方法"); 16 } 17 18 public Student(int a){ 19 System.out.println("带参数构造方法..........."); 20 } 21 }
执行结果:
我是构造代码块
空参数构造方法
我是构造代码块
带参数构造方法...........
静态代码块
位置:类中方法外定义
特点:需要通过static关键字修饰,随着类的加载而加载,并且只执行一次
作用:在类加载的时候做一些数据初始化的操作
1 public class Test { 2 3 public static void main(String[] args) { 4 Person p1 = new Person(); 5 Person p2 = new Person(10); 6 } 7 } 8 9 class Person { 10 static { 11 System.out.println("我是静态代码块, 我执行了"); 12 } 13 14 public Person(){ 15 System.out.println("我是Person类的空参数构造方法"); 16 } 17 18 public Person(int a){ 19 System.out.println("我是Person类的带...........参数构造方法"); 20 } 21 }
执行结果:
我是静态代码块, 我执行了
我是Person类的空参数构造方法
我是Person类的带...........参数构造方法
接口
当一个类中的所有方法都是抽象方法的时候,我们就可以将其定义为接口。
接口也是一种引用数据类型,它比抽象类还要抽象
接口的定义和特点
接口用关键字interface来定义
public interface 接口名 {}
接口不能实例化
接口和类之间是实现关系,通过implements关键字表示
public class 类名 implements 接口名 {}
接口的子类(实现类)
要么重写接口中的所有抽象方法
要么是抽象类
注意:接口和类的实现关系,可以单实现,也可以多实现。
public class 类名 implements 接口名1 , 接口名2 {}
接口中成员的特点
成员变量:
只能是常量
默认修饰符:public static final
构造方法:
没有
成员方法:(关于接口中的方法,JDK8和JDK9中有一些新特性)
只能是抽象方法
默认修饰符:public abstract
JDK8版中接口成员的特点
一、JDK8版本后:允许在接口中定义非抽象方法,但是需要使用关键字 default 修饰,这些方法就是默认方法。
作用:解决接口升级的问题(涉及到了接口大面积更新方法,而不想去修改每一个实现类,就可以将更新的方法,定义为带有方法体的默认方法)
接口中默认方法的定义格式:
格式:public default 返回值类型 方法名(参数列表) { }
范例:public default void show() { }
接口中默认方法的注意事项:
1、默认方法不是抽象方法,所以不强制被重写。但是可以被重写,重写的时候去掉default关键字。
2、public可以省略,default不能省略。
3、如果实现了多个接口,多个接口中存在相同的方法声明,子类就必须对该方法进行重写。
二、JDK8版本后:接口中允许定义static静态方法
接口中静态方法的定义格式:
格式:public static 返回值类型 方法名(参数列表) { }
范例:public static void show() { }
接口中静态方法的注意事项:
1、静态方法只能通过接口名调用,不能通过实现类名或者对象名调用。
2、public可以省略,static不能省略。
JDK9版中接口成员的特点
接口中私有方法的定义格式:
格式1:private 返回值类型 方法名(参数列表) { }
范例1:private void show() { }
格式2:private static 返回值类型 方法名(参数列表) { }
范例2:private static void method() { }
接口的使用思路
1、如果发现一个类中所有的方法都是抽象方法,那么就可以将该类,改进为一个接口
2、涉及到了接口大面积更新方法,而不想去修改每一个实现类,就可以将更新的方法,定义为带有方法体的默认方法
3、希望默认方法调用的更加简洁,可以考虑设计为static静态方法。(需要去掉default关键字)
4、默认方法中出现了重复的代码,可以考虑抽取出一个私有方法。(需要去掉default关键字)
类和接口的关系
类和类的关系
继承关系,只能单继承,但是可以多层继承
类和接口的关系
实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口
接口和接口的关系
继承关系,可以单继承,也可以多继承
注:
多个父接口当中的抽象方法如果重复,没关系。
多个父接口当中的默认方法如果重复,那么子接口必须进行默认方法的覆盖重写,[而且带着default关键字]。
父接口A:
1 public interface MyInterfaceA { 2 public abstract void methodA(); 3 public abstract void methodCommon(); 4 public default void methodDefault() { 5 System.out.printIn("AAA"); 6 } 7 }
父接口B:
1 public interface MyInterfaceB { 2 public abstract void methodB(); 3 public abstract void methodCommon(); 4 public default void methodDefault() { 5 System.out.println("BBB"); 6 } 7 }
接口与接口之间是多继承的,继承以上两个父接口:
1 public interface MyInterface extends MyInterfaceA, MyInterfaceB { //接口与接口之间的多继承 2 public abstract void method(); 3 //多个父接口当中的默认方法如果重复,那么子接口必须进行默认方法的覆盖重写 4 @Override 5 public default void methodDefault() { 6 7 } 8 }
两个父接口A、B的中有抽象方法重复,没有关系。但是默认方法重复,则需要在子接口中重写。
接口实现类,一个类实现多个接口:
1 public class MyInterfaceImpl implements MyInterface { 2 @Override 3 public void method() { 4 } 5 @Override 6 public void methodA() { 7 } 8 @Override 9 public void methodB() { 10 } 11 @Override 12 public void methodCommon() { 13 } 14 }
父接口的抽象方法不在子接口中覆盖重写,在实现类中覆盖重写;父接口中重复的默认方法在子接口中覆盖重写。
多态
多态概述
同一个对象,在不同时刻表现出来的不同形态。
举例:猫
我们可以说猫是猫:猫 cat = new 猫();
我们也可以说猫是动物:动物 animal = new 猫();
这里猫在不同的时刻表现出来了不同的形态,这就是多态
多态的前提和体现
有继承/实现关系
有方法重写
有父类引用指向子类对象
多态中成员访问特点
构造方法:同继承一样,子类会通过 super 访问父类构造方法
成员变量:编译看左边(父类),执行看左边(父类)
成员方法:编译看左边(父类),执行看右边(子类)
注:为什么成员变量和成员方法的访问不一样呢?
因为成员方法有重写,而成员变量没有
多态的好处和弊端
多态的好处:提高了程序的扩展性
具体体现:定义方法的时候,使用父类型作为参数,该方法就可以接收这父类的任意子类对象
多态的弊端:不能使用子类的特有功能
1 public class TestPolymorpic { 2 public static void main(String[] args) { 3 useAnimal(new Dog()); 4 useAnimal(new Cat()); 5 } 6 7 public static void useAnimal(Animal a){ // Animal a = new Dog(); // Animal a = new Cat(); 8 a.eat(); 9 //a.watchHome(); //子类特有的成员内容 10 } 11 } 12 13 abstract class Animal { 14 public abstract void eat(); 15 } 16 17 class Dog extends Animal { 18 public void eat() { 19 System.out.println("狗吃肉"); 20 } 21 22 public void watchHome(){ 23 System.out.println("看家"); 24 } 25 } 26 27 class Cat extends Animal { 28 public void eat() { 29 System.out.println("猫吃鱼"); 30 } 31 }
多态中的转型
向上转型
从子到父
父类引用指向子类对象
向下转型
从父到子
父类引用转为子类对象
1 public class Test4Polymorpic { 2 public static void main(String[] args) { 3 // 1. 向上转型 : 父类引用指向子类对象 4 Fu f = new Zi(); 5 f.show(); 6 // 多态的弊端: 不能调用子类特有的成员 7 // f.method(); 8 9 // 解决方案(A or B) 10 // A: 直接创建子类对象 11 // B: 向下转型 12 13 // 2. 向下转型 : 从父类类型, 转换回子类类型 14 Zi z = (Zi) f; 15 z.method(); 16 } 17 } 18 19 class Fu { 20 public void show(){ 21 System.out.println("Fu..show..."); 22 } 23 } 24 25 class Zi extends Fu { 26 @Override 27 public void show() { 28 System.out.println("Zi..show..."); 29 } 30 31 public void method(){ 32 System.out.println("我是子类特有的方法, method"); 33 } 34 }
多态中的转型存在的风险
概述:如果被转的引用类型变量,对应的实际类型和目标类型不是同一种类型,那么在转换的时候就会出现ClassCastException
避免强转出现的问题
关键字 instanceof
使用格式:
变量名 instanceof 类型
通俗的理解:判断关键字左边的变量,是否是右边的类型,返回boolean类型结果
1 public class Test3Polymorpic { 2 public static void main(String[] args) { 3 useAnimal(new Dog()); 4 useAnimal(new Cat()); 5 } 6 7 public static void useAnimal(Animal a){ // Animal a = new Dog(); 8 // Animal a = new Cat(); 9 a.eat(); 10 11 // 判断a变量记录的类型, 是否是Dog 12 if(a instanceof Dog){ 13 Dog dog = (Dog) a; 14 dog.watchHome(); 15 } 16 17 // Dog dog = (Dog) a; 18 // dog.watchHome(); // ClassCastException 类型转换异常 19 } 20 } 21 22 abstract class Animal { 23 public abstract void eat(); 24 } 25 26 class Dog extends Animal { 27 public void eat() { 28 System.out.println("狗吃肉"); 29 } 30 31 public void watchHome(){ 32 System.out.println("看家"); 33 } 34 } 35 36 class Cat extends Animal { 37 public void eat() { 38 System.out.println("猫吃鱼"); 39 } 40 }
内部类
在一个类中定义一个类。举例:在一个A类的内部定义一个B类, B类就被称为内部类
内部类的访问特点:
1、内部类可以直接访问外部类的成员,包括私有
2、外部类要访问内部类的成员,必须创建对象
成员内部类
按照内部类在类中定义的位置不同,可以分为如下两种形式
在类的成员位置:成员内部类
在类的局部位置:局部内部类
成员内部类,外界如何创建对象使用呢?
格式:外部类名.内部类名 对象名 = new 外部类对象().new 内部类对象();
范例:Outer.Inner oi = new Outer().new Inner();
1 public class Test1Inner { 2 public static void main(String[] args) { 3 Outer.Inner i = new Outer().new Inner(); 4 System.out.println(i.num); 5 i.show(); 6 } 7 } 8 9 class Outer { 10 private int a = 10; 11 12 class Inner { 13 int num = 10; 14 15 public void show(){ 16 System.out.println("Inner..show"); 17 // 内部类, 访问外部类成员, 可以直接访问, 包括私有 18 System.out.println(a); 19 } 20 } 21 }
成员内部类,也属于(成员),既然是成员就可以被一些修饰符所修饰。
private
私有成员内部类访问:在自己所在的外部类中创建对象访问。
1 public class Test2Innerclass { 2 public static void main(String[] args) { 3 Outer o = new Outer(); 4 o.method(); 5 } 6 } 7 8 class Outer { 9 private class Inner { 10 public void show(){ 11 System.out.println("inner..show"); 12 } 13 } 14 15 public void method(){ 16 Inner i = new Inner(); 17 i.show(); 18 } 19 }
static
静态成员内部类访问格式:外部类名.内部类名 对象名 = new 外部类名.内部类名();
静态成员内部类中的静态方法:外部类名.内部类名.方法名();
1 public class Test3Innerclass { 2 public static void main(String[] args) { 3 Outer.Inner oi = new Outer.Inner(); 4 oi.show(); 5 6 Outer.Inner.method(); 7 } 8 } 9 10 class Outer { 11 static class Inner { 12 public void show(){ 13 System.out.println("inner..show"); 14 } 15 16 public static void method(){ 17 System.out.println("inner..method"); 18 } 19 } 20 }
局部内部类
局部内部类是在方法中定义的类,所以外界是无法直接使用,需要在方法内部创建对象并使用
该类可以直接访问外部类的成员,也可以访问方法内的局部变量
1 public class Test4Innerclass { 2 public static void main(String[] args) { 3 Outer o = new Outer(); 4 o.method(); 5 } 6 } 7 8 class Outer { 9 int a = 10; 10 public void method(){ 11 int b = 20; 12 13 class Inner { 14 public void show(){ 15 System.out.println("show..."); 16 System.out.println(a); 17 System.out.println(b); 18 } 19 } 20 21 Inner i = new Inner(); 22 i.show(); 23 } 24 }
匿名内部类
概述:匿名内部类本质上是一个特殊的局部内部类(定义在方法内部)
前提:需要存在一个接口或类
格式:
new 类名或者接口名() {
重写方法;
};
1 public class Test5Innerclass { 2 /* 3 1. 创建实现类, 通过implements关键字去实现接口 4 2. 重写方法 5 3. 创建实现类对象 6 4. 调用重写后的方法. 7 8 匿名内部类: 9 前提: 需要存在类\接口 10 格式: 11 new 类名 \ 接口名 (){ 12 重写方法 13 } 14 */ 15 public static void main(String[] args) { 16 InterImpl ii = new InterImpl(); 17 ii.show(); 18 19 // 匿名内部类的理解: 将继承/实现, 方法重写, 创建对象, 放在了一步进行. 20 // 解释: 实现了Inter接口的, 一个实现类对象. 21 new Inter() { 22 @Override 23 public void show() { 24 System.out.println("我是匿名内部类中的show方法"); 25 } 26 }.show(); 27 28 // 情况: 接口中存在多个方法 29 Inter2 i = new Inter2() { 30 @Override 31 public void show1() { 32 System.out.println("show1..."); 33 } 34 35 @Override 36 public void show2() { 37 System.out.println("show2..."); 38 } 39 }; 40 41 i.show1(); 42 i.show2(); 43 } 44 } 45 46 interface Inter { 47 void show(); 48 } 49 50 interface Inter2 { 51 void show1(); 52 void show2(); 53 } 54 55 class InterImpl implements Inter { 56 @Override 57 public void show() { 58 System.out.println("InterImpl 重写的show方法"); 59 } 60 }
匿名内部类在开发中的使用
当方法的形式参数是接口或者抽象类时,可以将匿名内部类作为实际参数进行传递。
1 public class TestSwimming { 2 public static void main(String[] args) { 3 goSwimming(new Swimming() { 4 @Override 5 public void swim() { 6 System.out.println("铁汁, 我们去游泳吧"); 7 } 8 }); 9 } 10 11 /** 12 * 使用接口的方法 13 */ 14 public static void goSwimming(Swimming swimming){ 15 /* 16 Swimming swimming = new Swimming() { 17 @Override 18 public void swim() { 19 System.out.println("铁汁, 我们去游泳吧"); 20 } 21 } 22 */ 23 swimming.swim(); 24 } 25 } 26 27 //游泳接口 28 interface Swimming { 29 void swim(); 30 }
Lambda表达式
函数式编程思想
面向对象思想强调“必须通过对象的形式来做事情”
函数式思想则尽量忽略面向对象的复杂语法:“强调做什么,而不是以什么形式去做”
Lambda表达式的标准格式
格式:( 形式参数 ) -> { 代码块 } (组成Lambda表达式的三要素)
形式参数:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可
->:由英文中画线和大于符号组成,固定写法。代表指向动作
代码块:是我们具体要做的事情,也就是以前我们写的方法体内容
Lambda表达式的使用前提:
有一个接口
接口中有且仅有一个抽象方法
Lambda表达式的省略模式
省略规则:
参数类型可以省略,但是有多个参数的情况下,不能只省略一个
如果参数有且仅有一个,那么小括号可以省略
如果代码块的语句只有一条,可以省略大括号和分号,甚至是return
示例
无参数无返回值
1 public class TestLambda { 2 public static void main(String[] args) { 3 useShowHandler(new ShowHandler() { 4 @Override 5 public void show() { 6 System.out.println("我是匿名内部类中的show方法"); 7 } 8 }); 9 10 // Lambda实现 11 useShowHandler( () -> {System.out.println("我是Lambda中的show方法");} ); 12 // Lambda实现-省略模式 13 useShowHandler( () -> System.out.println("我是Lambda中的show方法")); 14 } 15 16 public static void useShowHandler(ShowHandler showHandler){ 17 showHandler.show(); 18 } 19 20 } 21 22 interface ShowHandler { 23 void show(); 24 }
有参数也有返回值
1 public class CalculatorDemo { 2 public static void main(String[] args) { 3 useCalculator(new Calculator() { 4 @Override 5 public int calc(int a, int b) { 6 return a + b; 7 } 8 }); 9 10 // Lambda实现 11 useCalculator( (int a, int b) -> { 12 return a + b; 13 } ); 14 // Lambda实现-省略模式 15 useCalculator( ( a, b) -> 16 a + b 17 ); 18 } 19 20 public static void useCalculator(Calculator calculator){ 21 int result = calculator.calc(10,20); 22 System.out.println(result); 23 } 24 } 25 26 interface Calculator { 27 int calc(int a, int b); 28 }
Lambda表达式和匿名内部类的区别
所需类型不同
匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
Lambda表达式:只能是接口
使用限制不同
如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类
如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式
实现原理不同
匿名内部类:编译之后,产生一个单独的.class字节码文件
Lambda表达式:编译之后,没有一个单独的.class字节码文件。对应的字节码会在运行的时候动态生成