学习目标
final关键字
- 权限
- 内部类
- 引用类型
一、 final关键字
1.1 概述
学习了继承后,我们知道,子类可以在父类的基础上改写父类内容,比如,方法重写。那么我们能不能随意的继承API中提供的类,改写其内容呢?显然这是不合适的。为了避免这种随意改写的情况,Java提供了final
关键字,用于修饰不可改变内容。
- final: 不可改变。可以用于修饰类、方法和变量。
- 类:被修饰的类,不能被继承。即final修饰的类不能有子类。
- 方法:被修饰的方法,不能被子类重写。
- 变量:被修饰的变量,不能更改变量的值。
1.2 使用方式
修饰类
被final修饰的类,是最终类,它不能再有子类。这个类不能被继承。
格式如下:
final class 类名 {
}
查询API发现像 public final class String
、public final class Math
、public final class Scanner
等,很多我们学习过的类,都是被final修饰的,目的就是供我们使用,而不让我们所以改变其内容。
修饰方法
格式如下:
修饰符 final 返回值类型 方法名(参数列表){
//方法体
}
被final修饰的方法,这时这个类没有被final修饰,那么这个类是可以有子类的,但是它中被final修饰的方法,在子类中是不能复写的。
修饰变量
1、局部变量——基本类型
基本类型的局部变量,被final修饰后,只能赋值一次,不能再更改。代码如下:
public class FinalDemo1 {
public static void main(String[] args) {
// 声明变量,使用final修饰
final int A = 10;
// 第二次赋值
A = 20; // 报错,不可重新赋值
}
}
2、局部变量——引用类型
引用类型的局部变量,被final修饰后,只能指向一个对象,地址不能再更改。但是不影响对象内部的成员变量值的修改,代码如下:
public class FinalDemo2 {
public static void main(String[] args) {
// 创建 User 对象
final User u = new User();
// 创建 另一个 User对象
u = new User(); // 报错,指向了新的对象,地址值改变。
// 调用setName方法
u.setName("张三"); // 可以修改
}
}
3、成员变量
- 显示初始化;
public class User {
final String USERNAME = "张三";
private int age;
}
被final修饰的常量名称,一般都有书写规范,所有字母都大写。
二、权限修饰符
2.1 概述
在Java中提供了四种访问权限,使用不同的访问权限修饰符修饰时,被修饰的内容会有不同的访问权限,
public:公共的。没有限制。修饰成员变量 方法 构造方法 类
protected:受保护的。在本类中访问 或 同一个包中的其它类中访问 或 不同包中的子类访问, protected只能修饰成员方法或成员变量,不能修饰类。
默认的权限:就是什么权限修饰符也不写。在本类访问或同一个包中的其它类中访问;
private:私有的。只能在本类中访问。修饰成员变量 方法 构造方法
public > protected>默认的权限>private
2.2 不同权限的访问能力
public | protected | 默认的权限(什么都不写) | private |
---|---|---|---|
同一类中 | √ | √ | √ |
同一包中(子类与无关类) | √ | √ | √ |
不同包的子类 | √ | √ | |
不同包中的无关类 | √ |
可见,public具有最大权限。private则是最小权限。
说明:关于protected(受保护权限)的演示,可以在不同包的子类中访问。
在java中当定义一个类的时候,如果当前这个类中的方法只让自己的子类使用,而其他的类不让使用,这时可以在这个方法前面加上protected关键字。
总结
1) 、public: 在任何情况下都可以访问
2)、default: 只能在本包中访问
3) 、protected: 给本包和子类访问
4)、private:只能在本类中访问
四种访问权限,能够跨包访问的只有 public 、 protected(必须是子类)
编写代码时,如果没有特殊的考虑,建议这样使用权限:
- 成员变量使用
private
,隐藏细节。 - 构造方法使用
public
,方便创建对象。 - 成员方法使用
public
,方便调用方法。
小贴士:不加权限修饰符,其访问能力与default修饰符相同
三、内部类
1.1 概述
什么是内部类
内部类:在其他类中类。
成员内部类
- 成员内部类 :定义在类中方法外的类。
成员内部类定义的时候可以使用不同的修饰关键字进行修饰,不同的修饰方式会有不同的访问方式。
常规成员内部类
定义格式:
class 外部类 {
// 外部类的成员位置
class 内部类{
// 内部类的成员位置
}
}
代码举例:
class Person { //外部类
String name;
class Heart { //内部类
// 成员
}
}
使用分析
成员内部类也是外部类的成员。既然是成员,我们就可以把他当做外部类的成员来对待。
而要想访问类中的成员,就需要创建这个类的对象,然后通过对象去调用类的成员。
所以要想访问到成员内部类,首先要创建外部类的对象,然后通过对象.去访问成员内部类。
创建内部类对象格式:
外部类 外部类对象名 = new 外部类();
外部类.内部类 内部类对象名 = 外部类对象名.new 内部类();
简写方式:
外部类名.内部类名 对象名 = new 外部类型().new 内部类型();
代码演示
//外部类
class Outer {
// 外部类成员变量
private String outer_filed = "我是外部类成员变量";
//成员内部类
class Inner {
// 内部类成员变量
private String inner_filed = "我是内部类成员变量";
//内部类的成员方法
public void inner_method() {
System.out.println("inner_method....run... " + inner_filed );
}
}
// 外部类成员方法
public void outer_method(){
System.out.println("outer_method....run... " + outer_filed );
}
}
// 测试类
public class InnerDemo {
public static void main(String[] args) {
// 创建外部类对象
Outer o = new Outer();
// 创建内部类对象
Outer.Inner i = p.new Inner();//表示通过外部类对象创建内部类对象
// 调用外部类方法
i.outer_method();
// 调用内部类方法
i.inner_method();
}
}
内部类仍然是一个独立的类,在编译之后会内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和$符号 。
比如,Outer$Inner.class
被私有的成员内部类
使用分析
私有成员内部类也是外部类的成员,而成员一旦被私有了,在本类之外是无法访问的。
而外部类中出现了私有的成员方法,在本类之外也是无法访问的,但是可以在本类中的其他没有私有的成员方法中调用。
被私有的成员内部类,只能在外部类的其他成员方法中去创建这个成员内部类的对象,然后通过这个对象去调用被私有的成员内部类中的成员。
代码演示
//外部类
class Outer {
// 外部类成员变量
private String outer_filed = "我是外部类成员变量";
//被私有的成员内部类
private class Inner {
// 内部类成员变量
private String inner_filed = "我是内部类成员变量";
//内部类的成员方法
public void inner_method() {
System.out.println("inner_method....run... " + inner_filed );
}
}
// 外部类成员方法
public void outer_method(){
System.out.println("outer_method....run... " + outer_filed );
}
/*
被私有的外部类成员方法:
当前外部类的成员方法被私有了,因此此方法外界无法访问。但是本类中是可以正常访问的。 我们可以在本类中定义一个公开的方法来调用私有的方法,然后创建对象调用公开的方法即可访问。
*/
// 外部类公开的方法
public void pub_method(){
// 此公开方法可以访问本类私有的成员
Inner inner = new Inner();
inner.inner_method();
// 可以在此方法内访问创建私有内部类的对象,然后调用内部类方法,最后通过外部类对象调用此 // 方法即可完成私有成员内部类方法的间接调用
}
}
// 测试类
public class InnerDemo {
public static void main(String[] args) {
// 创建外部类对象
Outer o = new Outer();
// 调用外部类访问私有内部类的公开方法 , 间接调用私有成员内部类
o.pub_method();
}
}
被静态的成员内部类
使用分析
外部类的成员被静态之后,可以直接使用外部类的类名来调用。
而想要访问被静态的成员内部了,同样需要使用内部类的类名调用
静态内部类类名定义格式
外部类.内部类 变量名 = new 外部类.内部类();
代码演示
//外部类
class Outer {
// 外部类成员变量
private String outer_filed = "我是外部类成员变量";
//被静态的成员内部类
static class Inner {
// 内部类成员变量
private String inner_filed = "我是内部类成员变量";
//内部类的成员方法
public void inner_method() {
System.out.println("inner_method....run... " + inner_filed );
}
}
// 外部类成员方法
public void outer_method(){
System.out.println("outer_method....run... " + outer_filed );
}
}
// 测试类
public class InnerDemo {
public static void main(String[] args) {
// 创建内部类对象
Outer.Inner oi = new Outer.Inner();
// 调用内部类方法
oi.inner_method();
}
}
静态成员内部类的静态方法
使用分析
外部类的静态方法可以使用外部类的类名直接调用,因此静态内部类的静态的方法也可以直接使用类名调用。
定义格式
内部类.外部类.静态成员;
代码演示
//外部类
class Outer {
// 外部类成员变量
private String outer_filed = "我是外部类成员变量";
//被静态的成员内部类
static class Inner {
// 内部类成员变量
private String inner_filed = "我是内部类成员变量";
//内部类的成员方法
public static void static_inner_method() {
System.out.println("静态内部类的静态成员方法static_inner_method....run... " );
}
}
}
// 测试类
public class InnerDemo {
public static void main(String[] args) {
// 直接使用内部类名访问
Outer.Inner.static_inner_method();
}
}
面试题
当外部类成员变量和内部类成员变量以及内部类成员方法中的变量重名时该如何访问区分?
问题分析
1、先不用考虑外部类的成员变量问题,内部类成员变量和局部变量重名时,我们先不要把内部类当成内部类去处理,就把内部类当成一个普通的类。那么普通情况下成员变量和局部变量重名时可以使用this关键字进行区分。
2、真对于外部类来说,成员内部类就相当于外部类的成员。因此内部类的成员变量就相当于外部类的局部变量。因此要想区分成员和局部依然需要使用this关键字。格式如下:
外部类.this.外部类成员变量名;
代码演示
//外部类
class Outer {
// 外部类成员变量x
int x = 10;
//成员内部类
class Inner {
// 内部类成员变量x
int x = 100;
//内部类的成员方法
public void inner_method() {
// 局部变量x
int x = 1000;
System.out.println("外部类 x = " + Outer.this.x);
System.out.println("内部类 x = " + this.x);
System.out.println("局部 x = " + x);
}
}
}
public class ConstructorCodeDemo{
public static void main(String[] args) {
// 创建内部类对象
Outer.Inner oi = new Outer().new Inner();
// 调用内部类方法
oi.inner_method();
}
}
1.2 匿名内部类【重点】
- 匿名内部类 :是内部类的简化写法。它的本质是一个
带具体实现的
父类或者父接口的
匿名的
子类对象。
开发中,最常用到的内部类就是匿名内部类了。以接口举例,当你使用一个接口时,得做如下几步操作,
- 定义实现类并实现接口
- 重写接口中的抽象方法
- 创建子类对象
- 调用重写后的方法
把以上四步合成一步,匿名内部类就是做这样的快捷方式。
前提
匿名内部类必须继承一个父类或者实现一个父接口。
格式
new 父类名或者父接口名(){ // 此语句等于产生了一个父类或接口的子类对象。
// 方法重写
public void method() {
// 执行语句
}
};
使用方式
以接口为例,匿名内部类的使用,代码如下:
定义接口:
public interface 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 InnerDemo {
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 Demo{
int x = 100;
public void method(){
System.out.println("method........");
}
}
public class InnerClassDemo{
public static void main(String[] args) {
// 第一种定义格式
new Demo(){
public void show(){
System.out.println("show...run...");
}
}.show();
// 第二种定义格式
Demo d = new Demo(){
public void show(){
System.out.println("show...run...");
}
};
d.show();
}
}
/*
上述代码第一种定义格式没有任何问题
第二种定义格式会产生程序编译报错。原因是=号右侧的对象创建相当于创建了一个Demo类的子类对象, 然后将子类对象赋值给了父类的引用。最后用父类引用调用show()方法,JVM只关注父类,而父类中没有 show()方法存在,所以报错。简单来说因为多态了。
*/
四 、引用类型用法总结
实际的开发中,引用类型的使用非常重要,也是非常普遍的。我们可以在理解基本类型的使用方式基础上,进一步去掌握引用类型的使用方式。基本类型可以作为成员变量、作为方法的参数、作为方法的返回值,那么当然引用类型也是可以的。
4.1 类作为成员变量
在定义一个类Role(游戏角色)时,代码如下:
class Role {
int id; // 角色id
int blood; // 生命值
String name; // 角色名称
}
使用int
类型表示 角色id和生命值,使用String
类型表示姓名。此时,String
本身就是引用类型,由于使用的方式类似常量,所以往往忽略了它是引用类型的存在。如果我们继续丰富这个类的定义,给Role
增加武器,穿戴装备等属性,我们将如何编写呢?
定义武器类,将增加攻击能力:
/*
定义武器类,将增加攻击能力:
*/
public class Weapon {
//成员变量
//武器名称
String name;
//武器伤害值
int hurt;
//构造方法
public Weapon(String name, int hurt) {
this.name = name;
this.hurt = hurt;
}
//get set
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getHurt() {
return hurt;
}
public void setHurt(int hurt) {
this.hurt = hurt;
}
}
定义穿戴盔甲类,将增加防御能力,也就是提升生命值:
/*
定义穿戴盔甲类,将增加防御能力,也就是提升生命值:
*/
public class Armour {
//成员变量
//表示盔甲名字
String name;
//防御值
int protect;
//构造方法
public Armour(String name, int protect) {
this.name = name;
this.protect = protect;
}
//get set
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getProtect() {
return protect;
}
public void setProtect(int protect) {
this.protect = protect;
}
}
定义角色类:
/*
角色类
*/
public class Role {
//成员位置
//定义成员变量属于引用数据类型
//Person p;
//角色使用的武器
Weapon wp;
//角色穿戴盔甲
Armour am;
//set get
public Weapon getWp() {
return wp;
}
public void setWp(Weapon wp) {
this.wp = wp;
}
public Armour getAm() {
return am;
}
public void setAm(Armour am) {
this.am = am;
}
//定义成员方法
//攻击方法 使用武器 Weapon
public void attack(){
/*
wp.getName() 获取武器名字
wp.getHurt() 表示受到伤害之后,流多少血
*/
System.out.println("使用" + wp.getName() + "干我,我损失了" + wp.getHurt() );
}
//防御方法 穿戴盔甲 Armour
public void wear(){
/*
am.getName() 表示盔甲名字即衣服的名字
*/
System.out.println("穿戴" + am.getName() + ",我少流了"+ am.getProtect() + "滴血");
}
}
测试类:
/*
测试类
*/
public class Test01 {
public static void main(String[] args) {
//创建武器类对象
Weapon wp = new Weapon("菜刀", 10);//菜刀 表示武器名字 10 表示流血10滴
Armour am = new Armour("李宁防爆衣服", 8);//李宁防爆衣服 表示盔甲名字 8 表示少流8滴
//创建角色对象
Role r = new Role();
//给成员变量wp赋值
r.setWp(wp);
//给成员变量am赋值
r.setAm(am);
//使用对象r调用方法
r.attack();//开始攻击
r.wear();//开始防御
}
}
输出结果:
使用菜刀干我,我损失了10
穿戴李宁防爆衣服,我少流了8滴血
类作为成员变量时,对它进行赋值的操作,实际上,是赋给它该类的一个对象。
4.2 interface作为成员变量
接口是对方法的封装,对应游戏当中,可以看作是扩展游戏角色的技能。所以,如果想扩展更强大技能,我们在Role
中,可以增加接口作为成员变量,来设置不同的技能。
定义接口:
// 法术攻击
public interface FaShuSkill {
public abstract void faShuAttack();
}
定义角色类:
public class Role {
//成员变量
//定义接口
/*
FaShuSkill f = new FaShuSkill() {
@Override
public void faShu() {
}
};
*/
FaShuSkill f;
//set get
public FaShuSkill getF() {
return f;
}
public void setF(FaShuSkill f) {
this.f = f;
}
}
定义测试类:
public class Test01 {
public static void main(String[] args) {
//创建角色类对象
Role r = new Role();
FaShuSkill fs = new FaShuSkill() {
//重写FaShuSkill接口中的方法
@Override
public void faShu() {
System.out.println("我受到了惊吓");
}
};
//给Role中的成员变量f赋值
r.setF(fs);
//获取对象
FaShuSkill f = r.getF();
//使用f对象重写之后的方法
f.faShu();
}
}
输出结果:
我受到了惊吓
我们使用一个接口,作为成员变量,以便随时更换技能,这样的设计更为灵活,增强了程序的扩展性。
接口作为成员变量时,对它进行赋值的操作,实际上,是赋给它该接口的一个子类对象。
4.3 interface作为方法参数和返回值类型
当接口作为方法的参数时,当接口作为方法的返回值类型时,其实都是它的子类对象。
ArrayList
类我们并不陌生,查看API我们发现,实际上,它是 java.util.List
接口的实现类。所以,当我们看见List
接口作为参数或者返回值类型时,当然可以将ArrayList
的对象进行传递或返回。
请观察如下方法:获取某集合中所有的偶数。
定义方法:
public static List<Integer> getEvenNum(List<Integer> list) {
// 创建保存偶数的集合
ArrayList<Integer> evenList = new ArrayList<>();
// 遍历集合list,判断元素为偶数,就添加到evenList中
for (int i = 0; i < list.size(); i++) {
Integer integer = list.get(i);
if (integer % 2 == 0) {
evenList.add(integer);
}
}
/*
返回偶数集合
因为getEvenNum方法的返回值类型是List,而ArrayList是List的子类,
所以evenList可以返回
*/
return evenList;
}
调用方法:
public class Test {
public static void main(String[] args) {
// 创建ArrayList集合,并添加数字
ArrayList<Integer> srcList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
srcList.add(i);
}
/*
获取偶数集合
因为getEvenNum方法的参数是List,而ArrayList是List的子类,
所以srcList可以传递
*/
List list = getEvenNum(srcList);
System.out.println(list);
}
}
接口作为参数时,传递它的子类对象。
接口作为返回值类型时,返回它的子类对象。
4.4 引用类型扩展总结
类作为参数: 本类对象和子类对象都可以。 类作为返回值:本类对象和子类对象都可以。
抽象类和接口作为参数: 子类对象。 抽象类和接口作为返回值:子类对象。