Java【 final、权限、内部类、引用类型】学习笔记

final、权限、内部类、引用类型

final

概述

  • final: 不可改变。可以用于修饰类、方法和变量。

    • 类:被修饰的类,不能被继承。

    • 方法:被修饰的方法,不能被重写。

    • 变量:被修饰的变量,不能被重新赋值。

    对于基本类型来说,不可变说的是变量当中的数据不可改变

    对于引用类型来说,不可变说的是变量当中的地址值不可改变

使用方式

修饰类

格式如下:

 final class 类名 {
 }

查询API发现像 public final class String 、 public final class Math 、 public final class Scanner 等,很多我们学习过的类,都是被final修饰的,目的就是供我们使用,而不让我们所以改变其内容。

修饰方法

格式如下:

 修饰符 final 返回值类型 方法名(参数列表){
  //方法体
 }

重写被 final 修饰的方法,编译时就会报错。

修饰变量

  1. 局部变量——基本类型

基本类型的局部变量,被final修饰后,只能赋值一次,不能再更改。代码如下:

 public class FinalDemo1 {
  public static void main(String[] args) {
  // 声明变量,使用final修饰
  final int a;
  // 第一次赋值
  a = 10;
  // 第二次赋值
  a = 20; // 报错,不可重新赋值
  // 声明变量,直接赋值,使用final修饰
  final int b = 10;
  // 第二次赋值
  b = 20; // 报错,不可重新赋值
  }
 }
  1. 局部变量——引用类型

引用类型的局部变量,被final修饰后,只能指向一个对象,地址不能再更改。但是不影响对象内部的成员变量值的 修改,代码如下:

 public class FinalDemo2 {
  public static void main(String[] args) {
  // 创建 User 对象
  final User u = new User();
  // 创建 另一个 User对象
  u = new User(); // 报错,指向了新的对象,地址值改变。
  // 调用setName方法
  u.setName("张三"); // 可以修改
  }
 }
  1. 成员变量

成员变量涉及到初始化的问题,初始化方式有两种,只能二选一:

  • 显示初始化;

 public class User {
  final String USERNAME = "张三";
  private int age;
 }
  • 构造方法初始化。

     public class User {
      final String USERNAME ;
      private int age;
      public User(String username, int age) {
      this.USERNAME = username;
      this.age = age;
      }
     }

    被final修饰的常量名称,一般都有书写规范,所有字母都大写。

四种权限修饰符

 publicprotecteddefault(空的)private
同一类中(我自己)
同一包中(子类与无关类)(我邻居)  
不同包的子类(我儿子)    
不同包中的无关类(陌生人)      

可见,public具有最大权限。private则是最小权限。

编写代码时,如果没有特殊的考虑,建议这样使用权限:

  • 成员变量使用 private ,隐藏细节。

  • 构造方法使用 public ,方便创建对象。

  • 成员方法使用 public ,方便调用方法。

小贴士:不加权限修饰符,其访问能力与default修饰符相同

内部类

概述

将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类。

/*

如果一个事物的内部包含另一个事物,那么这就是一个类内部包含另一个类。

例如:身体和心脏的关系。又如:汽车和发动机的关系。

分类:

  1. 成员内部类

  2. 局部内部类(包含匿名内部类)


成员内部类

  • 成员内部类 :定义在类中方法外的类。

 修饰符 class 外部类 {
  修饰符 class 内部类{
         //...
  }
     //...
 }

访问特点

  • 内部类可以直接访问外部类的成员,包括私有成员。

  • 外部类要访问内部类的成员,必须要建立内部类的对象。

内用外,随意访问;外用内,需要内部类对象。

创建内部类对象格式:

 外部类名.内部类名 对象名 = new 外部类型().new 内部类型()

访问演示,代码如下:

定义类:

 public class Person {
  private boolean live = true;
  class Heart {
  public void jump() {
  // 直接访问外部类成员
  if (live) {
  System.out.println("心脏在跳动");
  } else {
  System.out.println("心脏不跳了");
  }
  }
  }
  public boolean isLive() {
  return live;
  }
  public void setLive(boolean live) {
  this.live = live;
  }
 }

定义测试类:

 public class InnerDemo {
     public static void main(String[] args) {
  // 创建外部类对象
  Person p = new Person();
  // 创建内部类对象
  Heart heart = p.new Heart();
  // 调用内部类方法
  heart.jump();
  // 调用外部类方法
  p.setLive(false);
  // 调用内部类方法
  heart.jump();
  }
 }
 输出结果:
 心脏在跳动
 心脏不跳了

内部类仍然是一个独立的类,在编译之后会内部类会被编译成独立的.class文件,但是前面冠以外部类的类名 和$符号 。 比如,Person$Heart.class

局部内部类的final问题

/*

局部内部类,如果希望访问所在方法的局部变量,那么这个局部变量必须是【有效fianal】的。

备注:从java 8+开始,只要局部变量事实不变,那么final关键字可以省略。

原因:

  1. new出来的对象在堆内存中。

  2. 局部变量是跟着方法走的,在栈内存当中。

  3. 方法运行结束之后,立刻出栈,局部变量就会立刻消失,此时,堆中的对象还想使用局部变量就没办法了。于是需要copy一份,故不可变。

  4. 但是new出来的对象会在堆当中持续存在,直到垃圾回收消失。

*/

 public class MyOuter {
     public void methodOuter(){
         int num = 10;//所在方法的局部变量
         //错误写法!
         //num = 20;
 
         class MyInner{
             public void methInner(){
                 System.out.println(num);
            }
        }
    }
 }

匿名内部类(重点)

  • 匿名内部类 :是内部类的简化写法。它的本质是一个 带具体实现的 父类或者父接口的 匿名的 子类对象。 开发中,最常用到的内部类就是匿名内部类了。以接口举例,当你使用一个接口时,似乎得做如下几步操作:

  1. 定义子类

  2. 写接口中的方法

  3. 创建子类对象

  4. 调用重写后的方法

我们的目的,最终只是为了调用方法,那么能不能简化一下,把以上四步合成一步呢?匿名内部类就是做这样的快 捷方式。

前提 : 匿名内部类必须继承一个父类或者实现一个父接口。

格式

     new 父类名或者接口名(){
  // 方法重写
  @Override
  public void method() {
  // 执行语句
  }
 }

使用方式

以接口为例,匿名内部类的使用,代码如下:

定义接口:

 public abstract class FlyAble{
  public abstract void fly();
 }

创建匿名内部类,并调用:

 public class InnerDemo {
  public static void main(String[] args) {
  /*
  1.等号右边:是匿名内部类,定义并创建该接口的子类对象
  2.等号左边:是多态赋值,接口类型引用指向子类对象
  */
  FlyAble f = new FlyAble(){
  public void fly() {
  System.out.println("我飞了~~~");
  }
  };
  //调用 fly方法,执行重写后的方法
  f.fly();
  }
 }

通常在方法的形式参数是接口或者抽象类时,也可以将匿名内部类作为参数传递。代码如下:

 public class InnerDemo2 {
  public static void main(String[] args) {
  /*
  1.等号右边:定义并创建该接口的子类对象
  2.等号左边:是多态,接口类型引用指向子类对象
  */
  FlyAble f = new FlyAble(){
  public void fly() {
  System.out.println("我飞了~~~");
  }
  };
  // 将f传递给showFly方法中
  showFly(f);
  }
  public static void showFly(FlyAble f) {
  f.fly();
  }
 }

以上两步,也可以简化为一步(匿名内部类+匿名对象),代码如下:

 public class InnerDemo3 {
  public static void main(String[] args) {
  /*
  创建匿名内部类,直接传递给showFly(FlyAble f)
  */
  showFly( new FlyAble(){
  public void fly() {
  System.out.println("我飞了~~~");
  }
  });
  }
  public static void showFly(FlyAble f) {
  f.fly();
  }
 }

引用类型用法总结

实际的开发中,引用类型的使用非常重要,也是非常普遍的。我们可以在理解基本类型的使用方式基础上,进一步 去掌握引用类型的使用方式。基本类型可以作为成员变量、作为方法的参数、作为方法的返回值,那么当然引用类 型也是可以的。

class作为成员变量

 class Role {
  int id; // 角色id
  int blood; // 生命值
  String name; // 角色名称
 }
 

使用 int 类型表示 角色id和生命值,使用 String 类型表示姓名。此时, String 本身就是引用类型,由于使用 的方式类似常量,所以往往忽略了它是引用类型的存在。如果我们继续丰富这个类的定义,给 Role 增加武器,穿 戴装备等属性,我们将如何编写呢? 定义武器类,将增加攻击能力:

 class Weapon {
  String name; // 武器名称
  int hurt; // 伤害值
 }

定义穿戴盔甲类,将增加防御能力,也就是提升生命值:

 class Armour {
  String name;// 装备名称
  int protect;// 防御值
 }

定义角色类:

 class Role {
     int id;
     int blood;
     String name;
     // 添加武器属性
     Weapon wp;
     // 添加盔甲属性
     Armour ar;
 
     // 提供get/set方法
     public Weapon getWp() {
         return wp;
    }
 
     public void setWeapon(Weapon wp) {
         this.wp = wp;
    }
 
     public Armour getArmour() {
         return ar;
    }
 
     public void setArmour(Armour ar) {
         this.ar = ar;
    }
 
     // 攻击方法
     public void attack() {
         System.out.println("使用" + wp.getName() + ", 造成" + wp.getHurt() + "点伤害");
    }
 
     // 穿戴盔甲
     public void wear() {
  // 增加防御,就是增加blood值
         this.blood += ar.getProtect();
         System.out.println("穿上" + ar.getName() + ", 生命值增加" + ar.getProtect());
    }
 }

测试类:

 public class Test {
     public static void main(String[] args) {
  // 创建Weapon 对象
         Weapon wp = new Weapon("屠龙刀" , 999999);
  // 创建Armour 对象
         Armour ar = new Armour("麒麟甲",10000);
  // 创建Role 对象
         Role r = new Role();
  // 设置武器属性
         r.setWeapon(wp);
  // 设置盔甲属性
         r.setArmour(ar);
  // 攻击
         r.attack();
  // 穿戴盔甲
         r.wear();
    }
 }
 输出结果:
 使用屠龙刀,造成999999点伤害
 穿上麒麟甲 ,生命值增加10000

类作为成员变量时,对它进行赋值的操作,实际上,是赋给它该类的一个对象。

interface作为成员变量

接口是对方法的封装,对应游戏当中,可以看作是扩展游戏角色的技能。所以,如果想扩展更强大技能,我们在 Role 中,可以增加接口作为成员变量,来设置不同的技能。

定义接口:

 // 法术攻击
 public interface FaShuSkill {
 public abstract void faShuAttack();
 }

定义角色类:

 public class Role {
     FaShuSkill fs;
     public void setFaShuSkill(FaShuSkill fs) {
         this.fs = fs;
    }
     // 法术攻击
     public void faShuSkillAttack(){
         System.out.print("发动法术攻击:");
         fs.faShuAttack();
         System.out.println("攻击完毕");
    }
 

定义测试类:

 public class Test {
     public static void main(String[] args) {
  // 创建游戏角色
         Role role = new Role();
  // 设置角色法术技能
         role.setFaShuSkill(new FaShuSkill() {
             @Override
             public void faShuAttack() {
                 System.out.println("纵横天下");
            }
        });
  // 发动法术攻击
         role.faShuSkillAttack();
  // 更换技能
         role.setFaShuSkill(new FaShuSkill() {
             @Override
             public void faShuAttack() {
                 System.out.println("逆转乾坤");
            }
        });
  // 发动法术攻击
         role.faShuSkillAttack();
    }
 }
 输出结果:
 发动法术攻击:纵横天下
 攻击完毕
 发动法术攻击:逆转乾坤
 攻击完毕

我们使用一个接口,作为成员变量,以便随时更换技能,这样的设计更为灵活,增强了程序的扩展性。 接口作为成员变量时,对它进行赋值的操作,实际上,是赋给它该接口的一个子类对象。


interface作为方法参数和返回值类型

 

当接口作为方法的参数时,需要传递什么呢?当接口作为方法的返回值类型时,需要返回什么呢?对,其实都是它的 子类对象。 ArrayList 类我们并不陌生,查看API我们发现,实际上,它是 java.util.List 接口的实现类。所 以,当我们看见 List 接口作为参数或者返回值类型时,当然可以将 ArrayList 的对象进行传递或返回。

 import java.util.ArrayList;
 import java.util.List;
 /*
 java.util.List正是ArrayList所实现的接口
  */
 public class Role {
     public static void main(String[] args) {
         //左边是接口名称,右边是实现类名称,这就是多态写法
         List<String> list = new ArrayList<>();
 
         List<String> result = addNames(list);
         for (int i = 0; i < result.size(); i++) {
             System.out.println(result.get(i));
        }
    }
 
     public static List<String> addNames(List<String> list){
         list.add("迪丽热巴");
         list.add("古力娜扎");
         list.add("马儿扎哈");
         list.add("啥哟娜拉");
         return list;
    }
 }
 运行结果:
 迪丽热巴
 古力娜扎
 马儿扎哈
 啥哟娜拉

接口作为参数时,传递它的子类对象。

接口作为返回值类型时,返回它的子类对象。

posted @ 2020-06-21 00:42  雷神宙斯  阅读(176)  评论(0编辑  收藏  举报