java 面向对象(oop)

43 阅读17分钟

修饰符的适用范围

修饰符外部类/接口成员属性方法构造器初始化块成员内部类局部成员
publicYYYYY
protectedYYYY
包访问控制符YYYYOYO
privateYYYY
abstractYYY
finalYYYYY
staticYYYY
strictfpYYY
synchronizedY
nativeY
transientY
volatileY
defaultY

类与对象的关系

类:是一组相关的属性和行为的集合

对象:就是该事物的具体体现

成员变量和局部变量的区别

在类中的位置不同

  • 成员变量,类中方法外
  • 局部变量,方法内或者方法声明上

在内存中的位置不同

  • 成员变量 在堆内存中
  • 局部变量 在栈内存中

声明周期不同

  • 成员变量 随着对象的存在而存在,随对象的消失而消失
  • 局部变量 随着方法的调用而存在,随着方法的调用完毕而消失

初始化值不同

  • 成员变量 有默认的初始化值
  • 局部变量 没用默认的初始化值,必须先定义,赋值,才能使用

修饰符

  • static 用于修饰成员变量和成员方法

    1.被所有的对象共享,静态方法直接通过类名来调用。

    2.静态的加载优先于对象,随着类的加载而加载。

    3.静态方法不属于对象,而是属于类的。

    4.静态不能直接访问非静态,应为再内存当中先有静态内容,后有的非静态内容。

    5.静态方法中不能使用this,this代表当前对象,通过谁调用的方法,谁就是当前对象。

    6.静态代码块,特点:当第一次用到本类,静态代码块执行唯一的一次。

    7.静态代码块的用处:用来一次性的对静态成员变量进行赋值。

    静态变量:类名称.静态变量
    静态方法:类名称.静态方法()
    
  • abstract 用于修饰抽象方法和抽象类

  • final 用于修饰类、成员方法和成员变量

    • 被final修饰的类,不能被继承,不能有子类
    • 被final修饰的方法,不能被重写
    • 被final修饰的变量,不可以修改,是常量
  • 权限修饰符

    • public

    • protected 共子类访问

    • default

    • private 用于私有化

      包(访问权限)publicprotecteddefaultprivate
      同一个类(我自己)yesyesyesyes
      同一个包(我邻居)yesyesyesno
      不同包却是子类(我儿子)yesyesnono
      不同包非子类(陌生人)yesnonono

代码块

代码块是在创建实例时执行,在执行构造器之前执行。

  • 局部代码块
  • 构造代码块:提取构造方法中的共性,每次创建对象都会执行,并且在构造方法执行之前执行。
  • 静态代码块:随着类的加载而加载,只加载一次,加载类时需要做的一些初始化,比如加载驱动。
  • 同步代码块

匿名对象

  • 没有变量引用的对象
  • new className()
  • 匿名对象的应用场景: 1、当方法只调用一次的时候可以使用匿名对象 2、可以当作参数进行传递,但是无法在传参之前做其他的事情
  • 注意:匿名对象可以调用成员变量并赋值,但是赋值并没有意义

方法重载

定义:在同一个类中,出现了方法名相同的情况。 特点:方法名相同,参数列表不同。与返回值无关。 ​ 参数的个数不同, ​ 参数的类型不同

构造器

用于创建实例时执行初始化,构造器是创建对象的重要途径(即使使用工厂模式、反射等方法创建对象,其实质依然依赖于构造器)。

封装

封装是指将对象的实现细节隐藏起来,然后通过一些公用方法来暴露对象的功能。

继承

继承是面向对象实现软件复用的重要手段,当子类继承父类后,子类作为一种特殊的父类,将直接获得父类的属性和方法。

继承是多态的前提,如果没有继承就没有多态。

多个类有共同的成员变量和成员方法,抽取到另外一个类中(父类),在让多个类去继承这个父类, 我们多个类就可以获取到父类中的成员了。

父类也叫基类、超类。

子类也叫派生类

//局部变量:       直接写成员变量名

//本类的成员变量:  this.成员变量名

//父类的成员变量:  super.成员变量名

特点:单一继承,多层继承

继承中成员变量的特点:

  • super 可以获取父类的成员变量和成员方法,用法和this是相似的。
  • extends 继承关键字

方法重写:在子父类中,子类的方法和父类的完全一样,子类重写了父类的方法(覆盖),当子类重写了 ​ 父类的方法之后,使用子类对象调用的就是子类的方法。

​ 方法重写注意事项:

​ 1、必须保证父子类之间方法的名称相同,参数列表也相同

​ @Override 检测方法重写

​ 2、子类方法的返回值必须小于等于分类方法的返回值范围

​ java.lang.Object是所有类的公共最高类

​ 3、子类的权限必须大于等于父类方法的修饰符。

​ public > protected > (default 不是关键字) > private

继承关系中,父子类构造方法的访问特点:

​ 1、子类构造方法当中有一个默认隐含的"super();"调用,所以一定是先调用父类构造,后执行子类构造。

​ 2、子类构造可以通过super()关键字来调用父类重载构造。

​ 3、super的父类构造调用,必须是子类构造方法的第一语句,不能一个子类构造调用多次super构造。

//super关键字的三种用法
1、在子类的成员方法中,访问父类的成员变量
2、在子类的成员方法中,访问父类的成员方法
3、在子类的构造方法中,访问父类的构造方法

//this关键字的三种用法
1、在本类的成员方法中,访问本类的成员变量
2、在本类的成员方法中,访问本类的另一个成员方法
3、在本类的构造方法中,访问本类的另一个构造方法

多态

多态是子类对象可以直接赋给父类变量,但运行时依然表现出子类的行为特征,这意味着同一个类型的对象在执行同一个方法时,可能表现出多种行为特征。

父类引用指向子类对象,叫多态

多态使用:父类引用指向子类对象。

​ 父类名称 对象明 = new 子类名称()

​ 接口名称 对象明 = new 实现类名称()

对象的向上转型:

​ 父类名称 对象名 = new 子类名称();

​ 含义:右侧创建一个子类对象,把它当做父类来看待使用

对象的向下转型:

​ 子类名称 对象名 = (子类名称) 父类对象

​ 含义:将父类对象,【还原】成为本来的子类对象

​ 注意事项:必须保证对象本来创建的时候,就是子类对象,才能向下转型(就是先要有向上转型才能有向下转型)

​ 向下转型先用【instanceof】进行判断

instanceof ——>用于测试指定对象是否是指定类型(类或子类或接口)的实例。

语法: 子类型 instanceof 父类型


Object hello = "hello word";
if(hello instanceof Object){
    String h = (String) hello
}

Father f = new Child();

  • 动态的特点:
    • 成员变量 编译时看的是左边,运行时看的左边
    • 成员方法 编译时看的是左边,运行时看的是右边
    • 静态方法 编译时看的是左边,运行时看的是左边
  • 多态的前提:
    • 子父类的继承关系
    • 方法重写
    • 父类引用指向子类对象
  • 动态绑定:运行期间调用的方法,是根据其具体的类型
  • 多态的优缺点
    • 优点:可以提高可维护性(多态前提所保证的),提高代码的可扩展性
    • 缺点:无法直接访问子类特有的成员

多态实例:

public class InterfaceTest02 {
    public static void main(String[] args) {
			Factory f= new Factory();
			//多态实现
			f.createPhone(new Xiaomi());
			f.createPhone(new Hongmi());
    }
}

//抽象打电话
interface Phone{
    public void call();
}

//生产工厂
class Factory{
    public void createPhone(Phone p) {
			p.call();
    }
}

//小米手机
class Xiaomi implements Phone{
    public void call() {
			System.out.println("小米打电话");
    }
}
//红米手机
class Hongmi implements Phone{
    public void call() {
			System.out.println("红木打电话");
    }
}




组合复用实现继承

Animel.java

public class Animel{
	private void beat(){
		System.out.println("beat...");
	}
	public void breath(){
		beat();
		System.out.println("breath...");
	}
}

Bird.java

public class Bird{
	private Animel a;
	public Bird(Animel a){
		this.a = a;
	}
	public void breath(){
		a.breath();
	}
	public void fly(){
		System.out.println("fly...");
	}
}

Wolf.java

public class Wolf{
	private Animel a;
	public Wolf(Animel a){
		this.a = a;
	}
	public void breath(){
		a.breath();
	}
	public void run(){
		System.out.println("run...");
	}
}

CompositeTest.java

public class CompositeTest{
	public static void main(String[] args){
		Animel a1 = new Animel();
		Bird b = new Bird(a1);
		b.breath();
		b.fly();

		Animel a2 = new Animel();
		Wolf w = new Wolf(a2);
		w.breath();
		w.run();
	}
}


输出结果:
// beat...
// breath...
// fly...
// beat...
// breath...
// run...

抽象类

抽象方法:就是加上abstract关键字,让后去掉大括号,直接分号结束。

抽象类:抽象方法所在的类,必须是抽象类才行,在class之前写上abstract即可。

抽象类的作用:

抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造。

抽象类的特点

  • 抽象方法只能在抽象类里
  • 抽象类和抽象方法必须被 abstract 修饰
  • 抽象类不能创建对象(不能实例化)
  • 抽象类中可以有非抽象的方法
  • 抽象类和类的关系也是继承
  • 一个类继承了抽象类要么重写所有的 抽象方法,要么他自己是抽象类
  • 抽象类的构造器不能用于创建实例,主要用于被子类调用

如何使用抽象类和抽象方法:

​ 1、不能直接创建new抽象类对象

​ 2、必须用一个子类来继承抽象父类

​ 3、子类必须覆盖重写(实现)抽象父类当中所有的抽象方法。

​ 4、创建子类对象进行使用

  • 抽象父类可以只定义需要使用的某些方法,把不能实现的部分抽象成抽象方法,留给子类去实现。
  • 父类中可能包含需要调用其他系列的方法,这些被调用父方法即可以由父类实现,也可以由其子类实现,父类里提供的方法只是定义了一个通用算法,其实现也许并不完全由自身实现,而必须依赖于其子类的辅助。

abstract 和 final 不能同时使用

abstract 和 private 不能同时使用

abstract 和 static 只能同时修饰内部类

接口

定义:接口是一个比抽象类还抽象的类,接口里所有的方法全是抽象方法,接口和类的 ​ 关系是实现,implements

java8中开始接口当中允许定义静态方法

java7中接口包含:

​ 1、常量

​ 2、抽象方法

java8中增加:

​ 3、默认方法

​ 4、静态方法

java9中增加

​ 5、私有方法

==java8语法==

[修饰符] interface 接口名 extends 父接口1,父接口2...{
    零个到多个常量定义。。。
    零个到多个抽象方法定义。。。
    零个到多个内部类、接口、枚举定义。。。
    零个到多个默认方法或类方法定义。。。
}
  • 类与类 ——> 继承关系,单一继承,多层继承

  • 类与接口 ——> 实现关系,多实现,一个类可以实现多个接口

  • 接口与接口 ——> 继承关系,多继承

  • 接口和抽象类的区别?

    • 共性:不断的抽取,抽取出抽象的概念

    • 区别1、与类的关系,类与接口是实现关系,而且是多实现,一个类可以实现多个接口 ​ 类与抽象类是继承关系,java中的继承是单一继承,只能有一个父类,多层继承。

    • 区别2、 ​ 成员变量:抽象类可以有成员变量,也可以有常量 ​ 接口只能有常量 ​ 成员方法:抽象类可以有抽象方法,可以有非抽象方法 ​ 接口只能有抽象方法,而且方法有默认修饰符 public abstract ​ 构造方法:抽象类有构造方法 ​ 接口没有构造方法

接口中的常量:

​ 1、接口当中也可以定义成员变量,但是必须使用public static final 三个关键字进行修饰。

​ 2、可以省略修饰符。

​ 3、必须进行赋值,不能不赋值。

默认方法:

​ 1、public default 返回值类型 方法名称(参数列表){...}

​ 2、接口的默认方法是为了解决接口升级,实现类可以不用实现默认方法。

​ 3、接口的默认方法,可以通过接口实现类对象,直接调用。

​ 4、接口的默认方法,也可以被接口实现类进行覆盖重写。

​ 5、由于默认方法并没有static修饰,因此不能直接使用接口来调用,需要使用接口的实现类的实例来调用默认方法。

类方法:

​ 1、public static 返回值类型 方法名称(参数列表){...}

​ 2、接口里定义的内部类、内部接口、内部枚举默认都采用public static 两个修饰符,不管定义时是否指定这两个修饰符,系统都会自动使用。

​ 3、可以直接使用接口调用。

接口的特点:

​ 1、接口不能创建对象(实例化)

​ 2、Collection 接口的祖宗

使用接口的注意事项:

​ 1、接口当中的抽象方法,修饰符必须是两个固定的关键字 public abstract

​ 2、这两个关键字修饰符,可以选择性的省略

​ 3、接口是没有静态代码块或者构造方法的。

​ 4、一个类的直接父类是唯一的,但是一个类可以同时实现多个接口。

​ 5、如果实现类所有实现的多个接口当中,出现重复的抽象方法,那么只需要覆盖重写一次即可。

​ 6、如果实现类没有覆盖重写所有接口当中的所有抽象方法,那么实现类就必须是一个抽象类。

​ 7、如果实现类在实现的多个接口当中存在重复的默认方法那么实现类一定要对冲突的默认方法进行覆盖重写。

​ 8、一个类如果直接父类当中的方法,和接口当中的默认方法产生冲突,优先用父类当中的方法。

接口的作用:

​ 1、定义变量,也可用于进行强制类型转换

​ 2、调用接口中定义的常量

​ 3、被其他类实现

内部类

非静态内部类:

根据静态成员不能访问非静态成员的规则,外部类的静态方法、静态代码块不能访问非静态内部类,包括不能使用非静态内部类定义变量、创建实例等。总之,不能允许在外部类的静态成员中直接使用非静态内部类。

静态内部类:

静态内部类对象不能寄生在外部类的实例中,而是寄生在外部类的类本身中。当静态内部类对象存在时,并不存在一个被它寄生的外部类对象,静态内部类对象只持有外部类的类引用,没有持有外部类对象的应用。

静态内部类是外部类的一个静态成员,因此外部类的所有方法、所有初始化块中可以使用静态内部类来定义变量、创建对象等。

外部类依然不能直接访问静态内部类的成员,但是可以使用静态内部类的类名作为调用者来访问静态内部类的类成员,也可以使用静态内部类对象作为调用者来访问静态内部类的实例成员。

分类

​ 1、成员内部类

​ 2、局部内部类(包含匿名内部类)

成员内部类的两种使用方式:

​ 1、间接方式:在外部类的方法当中,使用内部类,让后调用外部类的方法。

​ 2、直接方式:外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();

​ Outer.Inner i = new Outer().new Inner();

  • 我们可以使用权限修饰符修饰成员内部类,但是如果使用私有来修饰,则无法在其他类中访问
  • 我们可以使用static修饰成员内部类,不用在创建外部类的对象了

成员内部类的定义格式

​ 修饰符 class 外部类名称{

	修饰符 class 内部类名称{ ... }
	...

​ }

​ 注意:内用外,随意访问。外用内,需要内部类对象。

局部内部类的定义格式

​ 修饰符 class 外部类名称{

​ 修饰符 返回值类型 外部类方法名称(参数列表){

​ class 局部内部类名称{...}

​ }

​ }

定义一个类的时候,权限修饰符规则

​ 1、外部类:public / (default)

​ 2、成员内部类:public / protected / default / private

​ 3、局部内部类:什么都不能写

匿名内部类

如果接口的实现类(或者是父类的子类)只需要使用唯一的一次,那么这种情况下就可以省略掉该类的定义,而改为使用【匿名内部类】

​ 接口名称 对象名 = new 接口名称(){

​ //覆盖重写所有抽象方法

​ }

​ 注意事项:

​ 1、匿名内部类,在《创建对象》的时候,只能使用唯一一次,如果希望多次创建对象,而且类的内容一样的话,那么就必须使用单独定义的实现类。

​ 2、匿名对象,在《调用方法》的时候,只能调用唯一的一次,如果希望同一个对象,调用多次方法,那么必须给对象起个名字。

​ 3、匿名内部类是省略了《实现类/子类名称》,但是匿名对象是省略了《对象名称》,匿名内部类和匿名对象不是一回事。

//匿名内部类
MyInterface objA = new MyInterface() {
	@Override
	public void method1() {
		System.out.println("匿名内部类实现了方法!111-A");
	}
};
objA.method1();


//匿名对象
new MyInterface() {
	@Override
	public void method1() {
		System.out.println("匿名内部类实现了方法!111-B");
	}
}.method1();
//匿名内部类的使用
interface Product{
    public double getPrice();
    public String getName();
}

public class AnonymousTest {
    public void test(Product p){
        System.out.println(p.getName()+","+p.getPrice());
    }

    public static void main(String[] args) {
        AnonymousTest ta = new AnonymousTest();
        ta.test(new Product() {
            @Override
            public double getPrice() {
                return 456;
            }

            @Override
            public String getName() {
                return "AGP 显卡";
            }
        });
    }
}

使用内部类:

1.在外部类内部使用内部类

2.在外部类以外使用非静态内部类

//在外部类以外的地方定义内部类(包括静态和非静态两种)变量的语法格式如下:
OuterClass.InnerClass varName
//在外部类以外的地方创建非静态内部类实例的语法如下:
OuterInstance.new InnerConstructor()
//例: Out.In in = new Out().new In();

3.在外部类以外使用静态内部类

new OuterClass.InnerConstructor()
Out.In in = new Out.In()

随机红包实例

public ArrayList<Integer> divide(int totalMoney, int totalCount){
  ArrayList<Integer> list = new ArrayList<>();
  int leftMoney = totalMoney;
  int leftCount = totalCount;
  Random r = new Random();
  for(int i=0;i<totalMoney-1;i++){
    int money = r.nextInt(leftMoney / leftCount *2) +1;
    list.add(money);
    leftMoney -= money;
    leftCOunt--;
  }
  list.add(leftMoney);
}

斗地主发牌实例(单列)

import java.util.ArrayList;
import java.util.Collections;

public class Poker {
    public static void main(String[] args) {
        /*
        * 1: 准备牌操作
        */
        //1.1 创建牌盒 将来存储牌面的 
        ArrayList<String> pokerBox = new ArrayList<String>();
        //1.2 创建花色集合
        ArrayList<String> colors = new ArrayList<String>();

        //1.3 创建数字集合
        ArrayList<String> numbers = new ArrayList<String>();

        //1.4 分别给花色 以及 数字集合添加元素
        colors.add("♥");
        colors.add("♦");
        colors.add("♠");
        colors.add("♣");

        for(int i = 2;i<=10;i++){
            numbers.add(i+"");
        }
        numbers.add("J");
        numbers.add("Q");
        numbers.add("K");
        numbers.add("A");
        //1.5 创造牌  拼接牌操作
        // 拿出每一个花色  然后跟每一个数字 进行结合  存储到牌盒中
        for (String color : colors) {
            //color每一个花色 
            //遍历数字集合
            for(String number : numbers){
                //结合
                String card = color+number;
                //存储到牌盒中
                pokerBox.add(card);
            }
        }
        //1.6大王小王
        pokerBox.add("小☺");
        pokerBox.add("大☠");	  
        // System.out.println(pokerBox);
        //洗牌 是不是就是将  牌盒中 牌的索引打乱 
        // Collections类  工具类  都是 静态方法
        // shuffer方法   
        /*
         * static void shuffle(List<?> list) 
         *     使用默认随机源对指定列表进行置换。 
         */
        //2:洗牌
        Collections.shuffle(pokerBox);
        //3 发牌
        //3.1 创建 三个 玩家集合  创建一个底牌集合
        ArrayList<String> player1 = new ArrayList<String>();
        ArrayList<String> player2 = new ArrayList<String>();
        ArrayList<String> player3 = new ArrayList<String>();
        ArrayList<String> dipai = new ArrayList<String>();	  

        //遍历 牌盒  必须知道索引   
        for(int i = 0;i<pokerBox.size();i++){
            //获取 牌面
            String card = pokerBox.get(i);
            //留出三张底牌 存到 底牌集合中
            if(i>=51){//存到底牌集合中
                dipai.add(card);
            } else {
                //玩家1   %3  ==0
                if(i%3==0){
                  	player1.add(card);
                }else if(i%3==1){//玩家2
                  	player2.add(card);
                }else{//玩家3
                  	player3.add(card);
                }
            }
        }
        //看看
        System.out.println("令狐冲:"+player1);
        System.out.println("田伯光:"+player2);
        System.out.println("绿竹翁:"+player3);
        System.out.println("底牌:"+dipai);  
	}
}