10章 面向对象中级
类变量
基本介绍
什么是类变量?
类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的兑现过去访问它时,渠道的都是相同的值;同样,任何一个该类的对象都可以修改它,修改的也是同一个变量。
如何定义
访问修饰符 static 数据类型 变量名;【推荐这种】
static 访问修饰符 数据类型 变量名;
package com.chapter10.static_;
/**
* @author: li
* @version: 1.0
*/
public class ChildGame {
public static void main(String[] args) {
int count = 0;
Child sun1 = new Child("sun1");
sun1.join();
count++;
Child.count1++;
//加入一个小孩 count加一次 未使用类变量
Child sun2 = new Child("sun2");
Child.count1++;
Child sun3 = new Child("sun3");
Child.count1++;
System.out.printf("共有%d个小孩计入游戏",Child.count1);
}
}
class Child {
private String namel;
//定义一个变量count,是一个类变量(静态变量)static 静态
//该变量最大的特点就是会被child类的所有对象实例共享
public static int count1 = 0;
//构造器
public Child(String namel) {
this.namel = namel;
}
public void join(){
System.out.println(this.namel + "加入了游戏");
}
}
访问类变量
类名.列变量名 或者 对象名。类变量名
静态变量的访问修饰符的访问权限和范围 和 普通属性是一样的
package com.chapter10.static_;
/**
* @author: li
* @version: 1.0
*/
public class VisitStatic {
public static void main(String[] args) {
//类名.类变量名
//注意:类变量是随着类加载而船舰,搜易 即使没有创建对象实例也可以访问
System.out.println(A.i);
}
}
class A{
//类变量
//类变量的访问必须遵守相关的访问权限
public static int i = 10;
//普通属性/普通成员变量/非静态属性/非静态成员变量/实例属性*****
private int q = 100;
}
类变量使用注意事项和细节讨论
- 什么时候需要使用类变量
当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量);比如:定义学生类,统计所有学生共交多少学费 - 类变量与实例变量(普通属性)的区别
类变量是该类的所有对象共享的,而实例变量是每个对象独享的 - 加上static称为类变量或静态变量,否则称为实例变量
- 类变量可以通过 类名.类变量名 或者 对象名.类变量名 进行访问 推荐前一种形式,但是要注意访问修饰符的权限问题
- 实例变量不能通过类名.类变量名的形式访问
- 类变量在类加载的时候就初始化了,也就是说,即使没有创建对象,只要类加载了,就可以使用类变量了
- 类变量的生命周期随着类的加载而开始,随着类的消亡而销毁
类方法
基本介绍
类方法 也叫静态方法
形式:
访问修饰符 static 数据返回类型 方法名(){} 【推荐】
static 访问修饰符 数据返回类型 方法名(){}
类方法的调用
类名.类方法 || 对象名.类方法名
类方法应用实例
package com.chapter10.static_;
/**
* @author: li
* @version: 1.0
*/
public class StaticMethod {
public static void main(String[] args) {
Stu zhangsan = new Stu("张三");
zhangsan.payFee(300);
Stu.payFee(100);
System.out.println(Stu.totalFee());
}
}
class Stu{
//普通成员变量
private String name;
//定义一个静态变量,来积累学生的学费
private static double fee = 0;
public Stu(String name) {
this.name = name;
}
//当方法有static修饰后,就是一个静态的芳芳
//静态方法就可以访问静态属性/变量
public static void payFee(double fee){
//将学费累计到静态变量
Stu.fee += fee;
}
public static double totalFee(){
return Stu.fee;
}
}
类方法的经典使用场景
当方法中不涉及到任何和对象相关的成员,则可以将这个方法设计成静态方法,提升开发效率,比如:
工具类中的utils/Math类、Collection集合类。
小结:在程序开发中,往往会讲一些普通的方法,设计成静态方法,这样我们不需要创建实例对象就可以使用了,比图打印一堆数组,冒泡排序,完成某个计算任务等
类方法的注意事项和细节讨论:
- 类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区:类方法中无this的参数;普通方法中隐含着this参数
- 类方法可以由类名调用,也可以通过对象名调用
- 普通方法和对象有关,需要通过对象名调用,比如对象名.方法名(参数),不能通过类名调用
- 类方法不能使用和对象相关的关键字,比如:this super 普通方法可以
- 类方法(静态方法)中 只能访问静态变量或静态方法
- 普通成员方法,既可以访问 非静态成员也可以访问静态成员
小结:静态方法,只能访问静态的成员,非静态的方法,既可以访问静态成员和非静态的成员,必须遵循访问规则 这里的成员就是方法和属性
package com.chapter10.static_;
/**
* @author: li
* @version: 1.0
*/
public class StaticMethodDetail {
public static void main(String[] args) {
D.hi();
// D.say() 非静态成员不能类名.的形式调用
int n3 = D.n3; //静态成员变量 类名.变量名调用
}
}
class D{
private int n1 = 100;
private static int n2 = 200;
public static int n3 = 300;
public void say(){
//非静态方法 普通成员方法
}
public static void hi(){
//类方法中不允许使用和对象相关的关键字 比如下:会报错
// this.n1;
// super.toString();
}
//静态方法只能访问静态成员
public static void hello(){
System.out.println(n2);
System.out.println(D.n2);
//System.out.println(this.n2); this不能使用
hi(); //可以使用 静态方法
//say(); 非静态方法 不能使用
}
//普通成员方法,既可以访问非静态成员,也可以访问静态成员
//非静态方法可以访问静态成员和非静态成员
public void helli(){
System.out.println(n1);
System.out.println(n2);
say();
hello();
}
}
课堂练习
pass
单例设计模式
基本介绍
什么是设计模式
- 静态方法和属性的经典使用
- 设计模式是在大量实践中总结和理论化之后优选的代码结构,编程风格、以及解决问题的思考方式。设计模式就行是经典的棋谱,不通的棋局,我们用不同的棋谱,免去我们再思考和摸索
什么是单例设计模式
单例(单个实例) - 所谓类的单例设计模式,就是采用一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个区的其对象实例的方法
- 单例模式有两种: 1.饿汉式 2.饱汉式
单例模式应用实例
步骤:
- 构造器私有化,防止被new
- 类的内部创建对象
- 向外暴露一个静态的公共方法
代码实现 - 饿汉式
package com.chapter10.singl_;
/**
* @author: li
* @version: 1.0
*/
public class SingTon01 {
public static void main(String[] args) {
}
}
//有一个类 FirlFriend 只能有一个女朋友
class GirlFriend{
//关于饿汉式 实现单例模式
//补充说明:对象,通常量级较大,饿汉式存在一个问题 他会创建对象,但是这个对象可能没被使用
private String name;
//2.在类的内部直接创建对象 该对象是static
//说明: 为了能够在静态方法中,返回gf对象 所以需要将他修饰为static(静态方法只能访问类中的静态成员)
private static GirlFriend gf = new GirlFriend("红");
//步骤
//1.构造器私有化
private GirlFriend(String name) {
System.out.println("私有构造器被调用");
this.name = name;
}
//3.提供一个公共的static方法,返回gf对象
public static GirlFriend getGf(){
return gf;
}
@Override
public String toString() {
return name;
}
}
- 懒汉式
package com.chapter10.singl_;
/**
* @author: li
* @version: 1.0
*/
public class SingleTon02 {
public static void main(String[] args) {
// new Cat("蛋黄");
// System.out.println(Cat.n1);
Cat instance = Cat.getInstance();
System.out.println(instance);
//再次调用getinstance
Cat instance2 = Cat.getInstance();
System.out.println(instance2);
System.out.println(instance == instance2); //true
//指向同一个内存地址
}
}
//希望在程序运行中只创建一个Cat对象
//使用单例模式
class Cat{
//懒汉式说明:
// 只有当用户使用getinstance方法时,才返回cat对象,
// 后面在此调用时,会返回上次创建的cat对象,从而保证单例
private String name;
public static int n1 = 99;
//2.定义一个static静态属性对象
private static Cat cat; // 初始化时 默认为null
//步骤:
//1.构造器私有化
private Cat(String name) {
this.name = name;
}
//3.提供一个public 的static方法,可以返回一个Cat对象
public static Cat getInstance(){
if (cat == null){//如果没有创建cat对象
cat = new Cat("小可爱");
}
return cat;
}
@Override
public String toString() {
return name;
}
}
饿汉式 懒汉式 比较
- 二者最主要的区别在于创建对象的时机不同:饿汉式是在类加载就创建了对象实例,而懒汉式是在使用时才创建
- 饿汉式不存在线程安全问题,懒汉式存在线程安全问题
- 饿汉式存在浪费资源的可能,因为如果程序员一个对象实例都没哟偶使用,那么饿汉式创建的对象就浪费了,懒汉式是使用时才创建,就不存在这个问题
- 在javaSE标准类中,java.lang.Runtime就是经典的单例模式
final关键字
基本介绍
final中文意思 最后的最终的;final可以修饰类、属性、方法和局部变量;当某些情况下,程序员可能有如下需求,就会使用到final:
- 当不希望类被继承时,可以有final修饰
- 当不希望父类的某个方法被子类覆盖/重写(override)时,可以使用final关键字修饰
- 当不希望类的某个属性的值被修改,可以使用final修饰
- 当不希望某个局部变量被修改,可以使用final修饰
代码实现上述描述:
package com.chapter10.final_;
/**
* @author: li
* @version: 1.0
*/
public class Final01 {
public static void main(String[] args) {
}
}
//如果我们要求A类不能被其他类继承,可以使用final修饰A类
final class A{}
//class B extends A{} 报错:不能继承一个final修饰的类
class C{
//如果我们要求hi不能被子类重写,可以使用final修饰hi方法
public final void hi(){}
}
class D extends C{
//当父类方法被final修饰后 子类无法重写改方法 否则会报错
// @Override
// public void hi() {
// super.hi();
// }
}
//当不希望某个类的某个属性值被修改,可以使用final修饰
class E{
//被final修饰一般是常量对象,也因此要大写
public final String STR = "nihao";
}
//当不希望某个局部变量被修改,可以使用fianl修饰
class F{
public void he(){
//局部的常量 局部常量 要大写
final int TCR = 10;
}
}
final使用注意事项和细节讨论
- final装饰的属性又叫常量,一半用XX_XX_XX来命名 例如:GIANT_TCR_ADV3;
- final修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置之一【选择一个位置赋初值即可】
- 定义时:如 public final double TCR_ADV_SL = 0.08;
- 在构造器中
- 在代码块中
- 如果final修饰的属性是静态的,则初始化的位置只能是:
- 定义时;
- 在静态代码块中 不能再构造器中赋值
- final类不能继承,但是可以实例化对象
- 如果类不是final类,但是含有final方法,该方法虽然不能被重写,但是可以被继承
代码实现上述说明细节:
package com.chapter10.final_;
/**
* @author: li
* @version: 1.0
*/
public class FinalDetail01 {
public static void main(String[] args) {
}
}
class AA{
//1.final修饰的属性在定义时必须有初始值,初始值的定义位置:
//1.1 在定义时候
public final double GIANT_OCR = 0.001;
//2.2 再构造器中
public final double GIANT_SCR;
public AA(double GIANT_SCR) {
this.GIANT_SCR = 0.003;
}
//1.3 在代码块中
public final double GIANT_TCR;
{
GIANT_TCR = 0.002;
}
}
class BB{
//2 如果final修饰的属性是静态的,则初始位置只能是定义时、静态代码块中
//不能再构造器中赋值
public static final double GIANT_OCR = 0.001;
public static final double GIANT_SCR;
static {
GIANT_SCR = 0.002;
}
}
//3.final类不不能继承 但是可以实例化对象
final class CC{}
//class DD extends CC{}
//4.如果类不是final的,但是含有final方法,则该方法不可以重新,但是可以继承
class DD{
public final void cal(){
System.out.println("cal()方法");
}
}
class EE extends DD{
}
- 一般来说,如果一个类已经是final类了,就没有必要在建方法修饰成final方法
- final不能修饰构造函数(构造方法)
- final 和static 往往搭配使用,效率更高,不会导致类的加载,编译器底层做了优化处理
- 包装类:Integer Double Float Boolean 等都是final类,String也是final类
package com.chapter10.final_;
/**
* @author: li
* @version: 1.0
*/
public class FinalDetail02 {
public static void main(String[] args) {
System.out.println(BBB.num);
}
}
class BBB{
//final static往往搭配使用,效率更高,不会导致类加载,底层编译器做了优化处理
public final static int num = 1000;
static {
System.out.println("BBB静态代码块");
}
}
final class AAA{
//一般来说 如果一个类已经是final类,就没必要再讲方法修饰成final方法
// public final void say(){}
}
final应用实例
编写一个程序,能够就算圆形的面积。要求圆周率为3.14 赋值的位置 3个 方式都写一下
package com.chapter10.final_;
/**
* @author: li
* @version: 1.0
*/
public class FinalExercise01 {
public static void main(String[] args) {
Circle circle = new Circle(1);
System.out.println(circle.getArea());
}
}
class Circle{
private double radius;
// private final double PI = 3.14;
private final double PI;
public Circle(double radius) {
this.radius = radius;
// this.PI = 3.14;
}
{
PI = 3.14;
}
public double getArea(){
return PI*radius*radius;
}
}
public int addOne(final int x) { //下面的代码是否有误,为什么? 1min
++x; //错误,原因是不能修改 final x 的值
return x + 1; //这里是可以.
}
}
抽象类
当父类的某些方法,需要声明,但是又不能确定如何实现时,就可以将其声明为抽象方法,那么这个类就是抽象类
package com.chapter10.abstract_;
/**
* @author: li
* @version: 1.0
*/
public class Absyract01 {
public static void main(String[] args) {
}
}
abstract class Animal{
private String name;
public Animal(String name) {
this.name = name;
}
//思考:这里的eat 你实现了 其实没有什么意义
//即,弗雷方法的不确定性
//考虑将该方法设计为抽象(abstract)方法
//所谓的抽象方法就是没有实现的方法
//所谓没有实现的方法就是指梅伊欧方法体
//当一个类中存在抽象方法时,需要该类声明为abstract类
//一般来说,抽象类会被继承,有其子类来实现抽象方法
public void eat(){
System.out.println("这个动物不确定吃什么");
}
}
抽象类快速入门
当父类的一些方法不能确定时,可以使用abstract关键字来修饰该方法,这个方法就是抽象方法,用abstract来修饰该类就是抽象类
如何把上文中的Animal做成抽象类,并让子类Cat类实现
abstract class Animal{
public String name;
abstract public void cry()
}
抽象类的介绍
- 用abstract关键字来修饰一个类时,这个类就是抽象类 访问修饰符 abstract 类名{}
- 用abstract关键字来修饰一个方法时,这个方法就是抽象方法 访问修饰符 abstract 返回类型 方法名(形参列表);后面没有方法体
- 抽象类的交织更多作用在于设计,是设计者设计好后,让子类继承并实现抽象类
- 抽象类,是面试比较爱问的问题,在框架和设计模式使用较多
抽象类使用的注意事项和细节讨论
- 抽象类 不能被实例化
- 抽象类不一定包含abstract方法。也就是说,抽象类中可以没有abstract方法
- 一旦类中包含了abstract方法,则这个类必须声明成abstract
- abstract只能修饰类和方法 不能修饰属性和其他的
package com.chapter10.abstract_;
/**
* @author: li
* @version: 1.0
*/
public class AbstractDetail01 {
public static void main(String[] args) {
A a = new A(); //报错
}
}
//1. 抽象类不一定要包含abstract方法,也就是说 抽象类可以没有abstract方法,还可以有实现的方法
abstract class A{
public void hello(){
System.out.println("你好啊");
}
}
//2. 一旦类包含了abstract方法,则这个类必须声明为abstract
abstract class B {
public abstract void hi();
}
//3. abstract 只能修饰类和方法,不能修饰属性和其它的
class C{
//public abstract int n2 = 100;
}
- 抽象类可以有任意成员【抽象类的本质还是类】。比如:非抽象方法、构造器、静态属性等等
- 抽象方法不能有主体,即方法体不能实现
- 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,但如果它本身也是一个abstract类,则可以不实现或者实现部分
- 抽象方法不能使用private final 和 static修饰,因为这些关键字都是和重写相违背的
package com.chapter10.abstract_;
/**
* @author: li
* @version: 1.0
*/
public class AbstractDetail02 {
public static void main(String[] args) {
}
}
//1.抽象方法不能使用private final static修饰,因为这些关键字都是和重写违背的
abstract class H{
//private abstract void say();
//final abstract void say();
//static abstract void say();
public abstract void say();
}
//2.如果一个类继承了抽象类,则必须实现抽象类的所有抽象方法
abstract class E{
public abstract void hi();
public abstract void ha();
public abstract void ho();
}
class F extends E{
@Override
public void hi() {
System.out.println("hi");
}
@Override
public void ha() {
System.out.println("ha");
}
@Override
public void ho() {
System.out.println("ho");
}
}
//当子类也同样声明为抽象类时,则不需要全部实现 可以实现全部/部分/不实现
abstract class G extends E{
@Override
public void hi() {
System.out.println("hi");
}
}
//3.抽象类的本质还是类 他可以具有类的各种成员
abstract class D{
public int n1 = 10;
public static int n2 = 20;
final static int n3 = 30;
public abstract void hi();
public String ho(){
return "ho";
}
}
抽象类最佳实践-模板设计模式
基本介绍
抽象类体现的就是一种模板模式设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上回保留抽象类的行为方式
模板设计模式能解决的问题
- 当功能内部一部分实现是确定的,一部分是不确定的。这时可以把不确定的部分暴露出去,让子类实现
- 编写一个抽象父类,弗雷提供多个子类的通用方法,并把一个或者多个方法留给其子类实现,就是一种模板模式
最佳实践
- 有多个类,完成不同的任务job
- 要求统计得到各自完成任务的时间
分析: - 先用最容易想到的方法--代码实现
- 分析问题,提供使用模板设计模式
package com.chapter10.abstract_;
/**
* @author: li
* @version: 1.0
*/
public class TemplateWorkFocus {
public static void main(String[] args) {
AA aa = new AA();
aa.calculateTime();
BB bb = new BB();
bb.calculateTime();
}
}
//抽象类-模板设计模式
abstract class Template{
//抽象方法
public abstract void job();
public void calculateTime(){
//实现方法,调用job方法
//得到开始时间
long start = System.currentTimeMillis();
job();
/**
* AA的实例对象 aa调用父类的calculateTime方法
* 在calculateTime方法内部调用job方法
* aa的运行类型是AA 也因此job调用的是aa的重写父类的方法*/
long end = System.currentTimeMillis();
System.out.println("AA 执行时间 " + (end - start));
}
}
class AA extends Template{
//实现Template的抽象方法 job
@Override
public void job() {
long num = 0;
for (int i = 0; i < 8000000; i++) {
num *= i;
}
}
}
class BB extends Template{
@Override
public void job() {
long num = 0;
for (int i = 0; i < 8000000; i++) {
num += i;
}
}
}
接口
快速入门
pass
基本介绍
接口就是给出一些没有实现的方法,封装到一期,到某个类要使用的时候,在根据具体情况把这些方法写出来,语法:
interface 接口名{
属性
抽象方法
}
class 类名 implements 接口{
自己的属性;
自己的方法
必须要实现的接口的抽象方法
}
小结
接口是更加抽象的抽象的类,抽象类里的方法可以有方法体,接口里面所有的方法都没有方法体【jdk7.0】 接口体现了程序设计的堕胎和高聚合低耦合的设计思想。
特别说明:jdk8.0后接口可以有静态方法,默认方法,也就是说接口中可以有方法的具体实现
深入讨论
接口的概念不算太难 难的是不知道什么时候使用接口,下面举几个例子:
- 如果现在我们需要制造战斗机,武装直升机。专家只需要将飞机的需要的功能/规格定下来即可,然后让别的人具体实现就可
- 如果说现在有一个项目经理,管理三个程序员,功能开发一个软件,为了控制和管理软件,项目经理可以定义一些接口,然后由程序员具体实现
关于2.的案例代码展示
package com.chapter10.interface_;
/**
* @author: li
* @version: 1.0
*/
public interface DBInterface {
//项目经理定义了方法
public void connect();
public void close();
}
package com.chapter10.interface_;
/**
* @author: li
* @version: 1.0
*/
public class MysqlDB implements DBInterface{
//A程序员操作
@Override
public void connect() {
System.out.println("连接MYSAL");
}
@Override
public void close() {
System.out.println("关闭MYSQL");
}
}
package com.chapter10.interface_;
/**
* @author: li
* @version: 1.0
*/
public class OracleDB implements DBInterface{
//B程序员操作
@Override
public void connect() {
System.out.println("连接ORACLE");
}
@Override
public void close() {
System.out.println("关闭ORACLE");
}
}
package com.chapter10.interface_;
/**
* @author: li
* @version: 1.0
*/
public class Interface03 {
public static void main(String[] args) {
MysqlDB mysqlDB = new MysqlDB();
tet(mysqlDB);
OracleDB oracleDB = new OracleDB();
tet(oracleDB);
}
public static void tet(DBInterface db){
db.connect();
db.close();
}
}
注意事项和细节
- 接口不能被实例化
- 接口中所有的方法都是public方法,接口中抽象方法,可以不用abstract修饰
- 一个普通类实现接口,就需要将改接口的所有方法都实现
- 抽象类实现接口,可以不用实现接口的方法
package com.chapter10.interface_;
/**
* @author: li
* @version: 1.0
*/
public class InterfaceDetail01 {
public static void main(String[] args) {
//1.接口不能被实例化
//IA ia = new IA();
}
}
interface IA{
//2.接口中所有的方法是public方法,接口中的抽象方法可以不应abstract修饰
public abstract void say1();
public void say2();
//private void say3();
}
//3.一个普通类实现接口,就必须把接口中的所有方法都实现,可以使用alt+enter快捷键
class Cat implements IA{
@Override
public void say1() {
}
@Override
public void say2() {
}
}
//4.抽象类去实现接口时,可以不实现接口的抽象方法
abstract class AAAA implements IA{
//抽象类内部为空 编译阶段无任何异常
}
- 一个类可以同时实现多个接口
- 接口中的属性 只能是final的 而且是public static final 修饰;比如:int n1 = 1;实际上是 public static int n1 = 10;
- 接口中属性的访问形式是:接口名.属性名
- 接口不能继承其他类,但是可以继承多个别的接口 interface A extends B,C{}
- 接口的装饰符,只能是public和默认,这点和类的装饰符是一样的
package com.chapter10.interface_;
/**
* @author: li
* @version: 1.0
*/
public class InterfaceDetail02 {
public static void main(String[] args) {
//证明 1.的说明
System.out.println(IB.n1); //证明属性是static的
// IB.n1 = 100; 无法复制修改 证明是final的
}
}
interface IB{
//1.接口中的属性,只能是final的,而且是 public final static修饰的
int n1 = 10; //等价于 public final static int n1 = 10;
void hi();
}
interface IC{
void say();
}
//2.接口不能继承其他的类,但是可以继承多个别的接口
interface ID extends IB,IC{
}
//3.接口的修饰符,只能是public 和默认,这点和类的修饰符是一样的
interface IE {}
// 4.一个类 同时可以实现多个接口
class Pig implements IB,IC{
@Override
public void hi() {
}
@Override
public void say() {
}
}
课堂练习
inteerface A{
int a = 10; // 等价于 public static final int a = 23;
}
class B implements A {
}
main 中:
B b = new B(); //正确
sout(b.a) //正确 子类没有 往父类中去查找
sout(A.a) //正确 类名.变量名 访问静态属性
sout(B.a) //正确 静态属性也是属性,子类没有回去父类查找
实现接口VS继承类
实现接口和继承类的概念我确实有点迷茫,这是在说啥?不是接口和继承吗?怎么跑出个实现接口和继承类,通过字面意思感觉大概好像似乎了解,那么,代码展示吧,加深理解;
package com.chapter10.interface_;
/**
* @author: li
* @version: 1.0
*/
public class ExtendsVsImplements {
public static void main(String[] args) {
LittleMonkey xunwukong = new LittleMonkey("熏悟空");
xunwukong.flying();
xunwukong.swimming();
}
}
//猴子类
class Monkey{
private String name;
public Monkey(String name) {
this.name = name;
}
public void climbing(){
System.out.println(name + "会爬树");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//接口
interface Fish{
void swimming();
}
interface Birdable{
void flying();
}
//小结:当子类继承了父类,就自动拥有了父类的功能
//如果子类需要扩展,可以通过实现接口的方式扩展
//可以理解 实现接口是对 java单继承机制的一种补充
class LittleMonkey extends Monkey implements Fish,Birdable{
public LittleMonkey(String name) {
super(name);
}
@Override
public void swimming() {
System.out.println(getName() + "通过学习,可以游泳了");
}
@Override
public void flying() {
System.out.println(getName() + "通过学习,可以飞行了");
}
}
接口和继承 解决的问题不同
继承的价值主要在于:解决代码的复用性和可维护性
接口的价值主要在于:设计,设计好各种规范(方法),让其他类去实现这些方法。即更加灵活
- 接口比继承更加灵活:
接口比继承更加灵活,继承满足is-a的关系,而接口只需要满足like-a的关系 - 接口在一定成都上实现代码解耦【即:接口规范性+动态绑定机制】
接口的堕胎特性
- 多态参数:
前面的USB接口,入参为(UsbInterface usb)既可以接收手机对象,又可以接受相机对象,这就体现了接口多态(接口的引用可以指向实现了接口的类的对象)
package com.chapter10.interface_;
/**
* @author: li
* @version: 1.0
*/
public class InterfacePolyParameter {
public static void main(String[] args) {
//接口多态的体现
//接口类型的变量if01可以指向 实现了IF接口类的对象实例
//那么,有的朋友可能要问了,接口不是不能实例化嘛,为啥能IF if01呢?
//这个问题,问得好啊,首先 实例化其实就是new XXX 显然这里没有new IF的操作
//举个栗子 我玩完不给钱就不算嫖了啊
IF if01 = new Monster();
if01 = new Car();
//继承体现的多态
//父类类型的变量a 可以指向 继承AAA的子类的对象实例
AAA a = new BBB();
a = new CCC();
}
}
interface IF{}
class Monster implements IF{}
class Car implements IF{}
class AAA{}
class BBB extends AAA{}
class CCC extends AAA{}
- 多态数组
演示案例:给Usb数组中中存放 手机和相机对象,手机对象有特有的call方法,遍历Usb数组,如果是手机对象,除了调用Usb接口定义的方法还需要调用本身特有的call方法
package com.chapter10.interface_;
/**
* @author: li
* @version: 1.0
*/
public class InterfacePolyArr {
public static void main(String[] args) {
//新建一个接口Usb类型的多态数组
Usb[] usbs = new Usb[2];
usbs[0] = new Phone_();
usbs[1] = new Camera_();
/*给Usb数组中,存放手机和相机对象,手机对象还有一个特有的call方法
* 遍历Usb数组,如果是Phone对象,除了调用Usb接口定义的方法外
* 还需要调用手机对象特有的call方法*/
for (int i = 0; i < usbs.length; i++) {
usbs[i].work(); // 这里就是动态绑定机制
//判断usb[i]的运行类型是Phone_
if (usbs[i] instanceof Phone_){
//进行类型的向下转型
((Phone_) usbs[i]).call();
}
}
}
}
interface Usb{
void work();
}
class Phone_ implements Usb{
public void call(){
System.out.println("手机正在打电话");
}
@Override
public void work() {
System.out.println("手机正在工作");
}
}
class Camera_ implements Usb{
@Override
public void work() {
System.out.println("相机正在工作");
}
}
3.接口存在多态传递现象
package com.chapter10.interface_;
/**
* @author: li
* @version: 1.0
*/
public class InterfacePolyPass {
public static void main(String[] args) {
//接口类型的变量,可以指向,实现了该接口的类的对象实例
IG ig = new Teacher();
//如果 IG继承自IH接口,而Teacher实现了IG接口
//那么,实际上相当于Teacher类也实现了IH接口
//这就是所谓的 接口多态传递想想
IH ih = new Teacher();
}
}
interface IH{
void hi();
}
interface IG extends IH{}
class Teacher implements IG{
@Override
public void hi() {
System.out.println("say hi");
}
}
课堂练习
package com.chapter10.interface_;
/**
* @author: li
* @version: 1.0
*/
public class InterfaceExercise02 {
public static void main(String[] args) {
C c = new C();
c.getX();
}
}
interface A {
int x = 0; // 等价于 public static final int x = 0;
}
class B{
int x = 1; //这是B类的普通属性
}
class C extends B implements A{
public void getX(){
//System.out.println(x);
//错误:说明:没有指定是哪个x
//如何明确指定x:
//1.访问接口的x就 A.x
System.out.println(A.x);
//2.访问父类的x 就super.x
System.out.println(super.x);
}
}
内部类
- 如果定义类在局部位置(方法中,代码块中):(1)局部内部类 (2)匿名内部类
- 定义在成员位置: (1)成员内部类 (2)静态内部类
基本介绍
一个类的内部有完整的嵌套了另一个类结构。被嵌套的类称为内部类(inner class),嵌套其他类的类称为外部类(outer class)。是我们类的第五大成员【属性、方法、构造器、代码块、内部类】,内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系,注意:内部类是学习的难点,同时也是在重点,后面看底层源码时候,有大量的内部类
基本语法
class Outer{//外部类
class Inner{//内部类
}
}
class Other{}//外部其它类
快速入门案例
package com.chapter10.innerclass_;
/**
* @author: li
* @version: 1.0
*/
public class InnerClass01 {
public static void main(String[] args) {
}
}
class Outer{
private int n1 = 100;
public Outer(int n1) {
this.n1 = n1;
}
public void m1(){
System.out.println("m1");
}
{
System.out.println("代码块");
}
class Inner{ //内部类 在Outer内部
}
}
内部类的分类
- 定义在外部类局部位置上(比如说方法内)
- 局部内部类(有类名)
- 匿名内部类(没有类名 ※)
- 定义在外部类的成员位置上:
- 成员内部类(没有static修饰)
- 静态内部类(使用static修饰)
局部内部类
说明:
- 可以直接访问外部类的局部位置,比如方法中,并且有类名
- 不能添加访问修饰符,因为他的地位就是一个局部变量。局部变量是不能使用修饰符的,但是可以使用final修饰,因为局部变量也可以使用final
- 作用域:仅仅在定义他的方法或代码块
- 局部内部类---访问---外部类的成员【访问方式:直接访问】
- 外部类---访问---局部内部类的成员【访问方式:创建对象,再访问(注意:必须在作用域内)】
记住:
- 局部内部类定义在方法中/代码块
- 作用域在方法体或者代码块中
- 本质仍是一个类
- 外部其他类---不能访问---局部内部类(因为 局部内部类地位是一个局部变量)
- 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
代码演示:
package com.chapter10.innerclass_;
/**
* @author: li
* @version: 1.0
*/
public class LocalInnerClass {
public static void main(String[] args) {
Outer02 outer02 = new Outer02();
outer02.m1();
System.out.println("outer02的hashcode" + outer02);
}
}
class Outer02{ // 外部类
private int n1 = 100;
public int n2 = 200;
private int n3 = 300;
private void m2(){//私有方法
System.out.println("Outer的m2方法");
}
public void m1(){//局部内部类,本质还是一个类
//1.局部内部类是定义在外部类的局部位置,通畅是在方法、代码块中
//3.不能添加访问修饰符,但是可以使用final (使用或者不写)
//4.作用域:仅仅在定义他的方法或代码块中
final class Inner02{
//2.可以直接访问外部类的所有成员,包含私有的
private int n1 = 800;
public void f1(){
//5.局部内部类可以直接访问外部类的成员 比如下面 外部类的n1和m2()
m2();
System.out.println("外部类的n2" + n2);
System.out.println("外部类的n3" + n3);
//7.如果外部类和局部内部类的成员重名时,默认遵循就近原则
//如果你真的需要去访问外部类的成员,使用 外部类名.this.成员去访问
System.out.println("你猜猜这个n1是多少" + n1);
System.out.println("n1" + n1 + "外部类的n1" + Outer02.this.n1);
}
}
//6.在外部类的方法中,可以创建Inner02对象,然后调用方法即可
Inner02 inner02 = new Inner02();
inner02.f1();
}
}
匿名内部类的使用
- 匿名内部类本质也是类
- 内部类的一种
- 该类没有名字
- 同时还是一个对象
说明:匿名内部类定义在外部类的局部位置,比如方法中,并且没有类名
基本语法:
new 类或接口(参数列表)
package com.chapter10.innerclass_;
/**
* @author: li
* @version: 1.0
*/
public class AnonymousInnerClass {
/**
* 演示匿名内部类的使用*/
public static void main(String[] args) {
Outer04 outer04 = new Outer04();
outer04.method();
Outer04 outer03 = new Outer04();
outer03.method();
}
}
class Outer04{//外部类
private int n1 = 10; //属性
public void method(){ // 方法
//基于接口的匿名内部类
//1.需求:想使用IA接口,并创建对象
//2.传统方式,是写一个类,实现改接口,并创建对象
//3.现在Tiger、Dog类只是使用一次,后面再不使用
//4.使用匿名内部类简化开发
//5.tiger 的编译类型是IA tiger的运行类型是Outer04$1 这个就是匿名内部类的名字
//6.JDK底层在创建匿名内部类Outer04$1,立即马航创建了Outer04$1实例,并且把地址返回给tiger
IA tiger = new IA() {
@Override
public void cry() {
System.out.println("老虎在哭");
}
};
System.out.println("tiger的运行类型是 = " + tiger.getClass());
tiger.cry();
tiger.cry();
tiger.cry();
//8.匿名内部类使用一次,就不能再使用了
//这个没太懂 网上查了查 看懂了 但是没太理解
IA tager = new IA() {
@Override
public void cry() {
}
};
System.out.println("tiger的运行类型是 = " + tiger.getClass());
tiger.cry();
//基于类的匿名内部类
//分析
//1.father的编译类型是Father
//2.father的运行类型是Outer04$2
//3.底层源码会创建内部类Outer04$2继承father类并重写父类方法
//同时也直接返回了内名内部类 Outer04$2的对象
//注意 jack 这个参数列表会传递给构造器
Father father = new Father("jack"){
@Override
public void test() {
System.out.println("基于类的不匿名内部类重写了test方法");
}
};
System.out.println(father);
father.test();
//基于抽象类的匿名内部类
Animal animal = new Animal() {
@Override
void eat() {
System.out.println("抽象类的方法被基于抽象类的匿名内部类实现并重写");
}
};
animal.eat();
}
}
interface IA{//接口
public void cry();
}
//传统方法 定义一个类实现IA接口 重写IA的方法 实例化类调用方法
class Father {
public Father(String name) {
System.out.println("接收到的name=" + name);
}
public void test(){}
}
abstract class Animal{
abstract void eat();
}
- 匿名内部类的语法比较奇特,请各位注意,因为匿名内部类既是一个类的定义,同时本身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特征,对前面代码分析可以看出这个特点,因此可以调用匿名内部类方法
- 可以直接访问外部类的所有成员,包含私有的
- 不能添加访问修饰符,因为他的位置就是一个局部变量
- 作用域:仅仅在定义它的方法或代码块中
- 匿名内部类---访问---外部类成员[访问形式 直接访问]
- 外部其他类---不能访问---匿名内部类 [因为匿名内部类地位是一个局部变量]
- 如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
package com.chapter10.innerclass_;
/**
* @author: li
* @version: 1.0
*/
public class AnonymousInnerClassDetail {
public static void main(String[] args) {
Outer05 outer05 = new Outer05();
outer05.f1();
//外部其他类--不能访问--匿名内部类
System.out.println(outer05);
}
}
class Outer05{
private int n1 = 99;
public void f1(){
//创建一个居于类的匿名内部类
// 不能添加访问修饰符,因为他的地位就是一个局部变量
//作用域:仅仅定义它的方法或代码块中
Person p = new Person(){
private int n1 = 88;
@Override
public void hi() {
//可以直接访问外部类的所有成员 包含私有的
//如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,
//默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
System.out.println("匿名内部类重写了hi方法n1=" + n1 + "外部内的n1" + Outer05.this.n1);
//Outer05.this 就是调用f1的对象
System.out.println("outer05.this hashcode = " + Outer05.this);
}
};
p.hi(); //动态绑定机制 运行类型是 Outer05$1
}
}
class Person{
public void hi(){
System.out.println("Person hi");
}
public void ok(String str){
System.out.println("Person ok" + str);
}
}
匿名内部类最佳实战
当作实参直接传递,简洁高效
package com.chapter10.innerclass_.abstract_;
/**
* @author: li
* @version: 1.0
*/
public class InnerClassExecrise01 {
public static void main(String[] args) {
//传统方法
f1(new Picture());
//当作实参直接传递,简洁高效
//这块有点问题 需要报读查一下
f1(new IL()){
@Override
public void show(){
System.out.println("这是一幅名画");
}
}
}
//静态方法,型参是接口类型
public static void f1(IL li){
li.show();
}
}
//接口
interface IL{
void show();
}
//类-实现IL-编程领域(硬编码)
class Picture implements IL{
@Override
public void show() {
System.out.println("这是一幅名画");
}
}
匿名内部类课堂练习
- 有一个铃声接口bell 里面有个ring方法
package com.chapter10.innerclass_.abstract_;
/**
* @author: li
* @version: 1.0
*/
public class InnerClassExercise02 {
public static void main(String[] args) {
/*
1.有一个铃声接口 Bell,里面有个 ring 方法。(右图)
2.有一个手机类 Cellphone,具有闹钟功能 alarmClock,参数是 Bell 类型(右图)
3.测试手机类的闹钟功能,通过匿名内部类(对象)作为参数,打印:懒猪起床了
4.再传入另一个匿名内部类(对象),打印:小伙伴上课了
*/
CellPhone cellPhone = new CellPhone();
cellPhone.alarmClock(new Bell() {
@Override
public void ring() {
System.out.println("起床");
}
});
}
}
interface Bell{
void ring();
}
class CellPhone{
public void alarmClock(Bell bell){
//形参是Bell接口类型
System.out.println(bell.getClass());
bell.ring(); //动态绑定
}
}
成员内部类的使用
成员内部类是定义在外部类的成员位置,没有static修饰
- 可以直接访问外部类的所有成员,包含私有的
- 可以添加任意访问修饰符(public protected 默认 private),因为它的地位就是一个成员
- 作用域:和外部类的其他成员一样,为为整个类体
- 成员内部类--访问--外部类成员(比如 属性)【访问形式:直接访问】
- 外部类---访问---成员内部类【访问形式:创建对象再访问】
- 外部其他类--访问--成员内部类
- 如果外部类和内部类的成员重名时,内部类访问的话,遵循就近原则,如果想访问外部类的成员,可以使用(外部类名.this.成员)去访问
package com.chapter10.innerclass_;
/**
-
@author: li
-
@version: 1.0
*/
public class MemberInnerClass01 {
public static void main(String[] args) {
Outer08 outer08 = new Outer08();
outer08.t1();
//外部其他类 使用成员内部类的三种方式
//1. 第一种方式
Outer08.Inner08 inner08 = outer08.new Inner08();
inner08.say(); //语法 不用纠结 相当于 把new Inner08()当作是 outer08的成员//2.第二种方式 在外部类中 编写一个方法,可以返回Inner08对象 Outer08.Inner08 inner081 = outer08.getInner08Instance(); inner081.say();
}
}
class Outer08{//外部类
private int n1 = 10;
public String name = "张三";
private void hi(){
System.out.println("hi");
}
//1. 注意 成员内部类 是定义在外部类的成员位置上
//2.可以添加任意访问修饰符(public protected 默认 private)因为他的地位就是一个成员
public class Inner08{ // 成员内部类
private double asl = 99.8;
private int n1 = 66;
public void say(){
// 可以直接访问外部类的所有成员,包含私有的
//如果成员内部类的成员和外部类的成员重名,会遵循就近原则
// 如果想要访问外部类的重名成员 使用 外部类名.this.成员 访问
System.out.println("外部类的n1" + Outer08.this.n1);
System.out.println("内部类的n1" + n1);
}
}
//方法 返回一个Inner08的实例
public Inner08 getInner08Instance(){
return new Inner08();
}//写方法
public void t1(){
//使用成员内部类
//创建成员内部类的对象,然后使用相关的方法
Inner08 inner08 = new Inner08();
inner08.say();
//System.out.println(inner08.say());
}
}
#### 静态内部类的使用
说明:静态内部鳄类是定于在外部类的成员位置,并且有static修饰
1. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
2. 可以添加任意访问修饰符(public protected 默认 private),因为他的位置就是一个成员
3. 作用域:同其他的成员 为整个类体
4. 静态内部类--访问--外部类(比如:静态属性)【访问方式:直接访问所有静态成员】
5. 外部类--访问--静态内部类 访问方式:创建对象 再访问
6. 外部其他类--访问--静态内部类
7. 如果外部类和静态内部类的成员重名时,静态内部类访问的时候,遵循就近原则,如果想要访问逮捕类的成员,则可以使用(外部类名.成员)去访问
package com.chapter10.innerclass_;
/**
-
@author: li
-
@version: 1.0
*/
public class StaticInnerClass01 {
public static void main(String[] args) {
//外部其他类 使用静态内部类
//方式1 因为静态内被雷 是可以通过类名直接访问 (前提是满足访问权限)
Onter10.Inner10 inner10 = new Onter10.Inner10();
inner10.say();
//方式2 编写一个方法 返回静态内部类的实例对象
Onter10 onter10 = new Onter10();
Onter10.Inner10 inner101 = onter10.getInner10();
inner101.say();Onter10.Inner10 inner102 = Onter10.getInner10_(); inner102.say();
}
}
class Onter10{ // 外部类
private int n1 = 10;
private static String name = "张三";
private static void cry(){}
//Inner10 就是 静态内部类
//1.放在外部类的成员位置 2.使用static修饰
//4.可以添加人与访问修饰符(public protected 默认 private) 因为他的位置是一个成员
//5.作用域 同其他的成员 为整个类体
static class Inner10{
private String name = "法外狂徒";
public void say(){
//3.可以访问外部类的所有静态成员,包含私有的,但是不能直接访问非静态成员
//如果外部类和静态内部类成语那重名时候,遵顼就近原则;想访问外部类成员 使用 外部类名.成员 方式
System.out.println("外部类的静态成员n1" + Onter10.name);
}
}
//外部类--访问--静态内部类 访问方式 先创建对象 再访问
public void m1(){
Inner10 inner10 = new Inner10();
inner10.say();
}
public Inner10 getInner10(){
return new Inner10();
}public static Inner10 getInner10_(){
return new Inner10();
}
}
课堂测试题
public class Test {//外部类
public Test() {//构造器
Inner s1 = new Inner();
s1.a = 10;
Inner s2 = new Inner();
System.out.println(s2.a);
}
class Inner { //内部类,成员内部类
public int a = 5;
}
public static void main(String[] args) {
Test t = new Test();
Inner r = t.new Inner();//5
System.out.println(r.a);//5
}
}