第一章 final关键字
1.1 概述
子类可以在父类的基础上改写父类内容,比如,方法重写。能不能随意的继承API中提供的类,改写其内容呢?显然这是不合适的。为了避免这种随意改写的情况,Java提供了final 关键字,用于修饰不可改变内容。
final: 不可改变。可以用于修饰类、方法和变量。
- 类:被修饰的类,不能被继承。
- 方法:被修饰的方法,不能被重写。
- 变量:被修饰的变量,不能被重新赋值。
1.2 使用方式
修饰类
格式如下:
当final关键字用来修饰一个类的时候,格式:
public final class 类名称 {
// ...
}
含义:当前这个类不能有任何的子类。(太监类)
注意:一个类如果是final的,那么其中所有的成员方法都无法进行覆盖重写(因为没子类。)
public final class MyClass /*extends Object*/ {
public void method() {
System.out.println("方法执行!");
}
}
-----
// 不能使用一个final类来作为父类
public class MySubClass /*extends MyClass*/ {
}
查询API发现像
public final class String、public final class Math、public final class Scanner等,都是被final修饰的,目的就是供我们使用,而不让我们所以改变其内容。
修饰方法
修饰符 final 返回值类型 方法名(参数列表){
//方法体
}
重写被final修饰的方法,编译时就会报错。
修饰变量
- 局部变量——基本类型
基本类型的局部变量,被final修饰后,只能赋值一次,不能再更改。
public class FinalDemo1 {
public static void main(String[] args) {
// 声明变量,使用final修饰
final int a;
// 第一次赋值
a = 10;
// 第二次赋值
a = 20; // 报错,不可重新赋值
}
----------------
final int c = 0;
for (int i = 0; i < 10; i++) {
c = i;
System.out.println(c);
}
// 为什么不能通过编译呢?根据 final 的定义,写法1报错,一旦赋值,不可更改
----------
for (int i = 0; i < 10; i++) {
final int c = i;
System.out.println(c);
}
//为什么通过编译呢?因为每次循环,都是一次新的变量c。
- 局部变量——引用类型
引用类型的局部变量,被final修饰后,只能指向一个对象,地址不能再更改。但是不影响对象内部的成员变量值的修改。
/*
final关键字代表最终、不可改变的。
常见四种用法:
1. 可以用来修饰一个类
2. 可以用来修饰一个方法
3. 还可以用来修饰一个局部变量
4. 还可以用来修饰一个成员变量
*/
public class Demo01Final {
public static void main(String[] args) {
int num1 = 10;
System.out.println(num1); // 10
num1 = 20;
System.out.println(num1); // 20
// 一旦使用final用来修饰局部变量,那么这个变量就不能进行更改。
// “一次赋值,终生不变”
final int num2 = 200;
System.out.println(num2); // 200
// num2 = 250; // 错误写法!不能改变!
// num2 = 200; // 错误写法!
// 正确写法!只要保证有唯一一次赋值即可
final int num3;
num3 = 30;
// 对于基本类型来说,不可变说的是变量当中的数据不可改变
// 对于引用类型来说,不可变说的是变量当中的地址值不可改变
Student stu1 = new Student("赵丽颖");
System.out.println(stu1);
System.out.println(stu1.getName()); // 赵丽颖
stu1 = new Student("霍建华");
System.out.println(stu1);
System.out.println(stu1.getName()); // 霍建华
System.out.println("===============");
final Student stu2 = new Student("高圆圆");
// 错误写法!final的引用类型变量,其中的地址不可改变
// stu2 = new Student("赵又廷");
System.out.println(stu2.getName()); // 高圆圆
stu2.setName("高圆圆圆圆圆圆");
System.out.println(stu2.getName()); // 高圆圆圆圆圆圆
}
}
- 成员变量
成员变量涉及到初始化的问题,初始化方式有两种,只能二选一:
- 显示初始化
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修饰的常量名称,一般都有书写规范,所有字母都大写。
第二章 权限修饰符
2.1 概述
在Java中提供了四种访问权限,使用不同的访问权限修饰符修饰时,被修饰的内容会有不同的访问权限,
public:公共的。
protected:受保护的
default:默认的
private:私有的
2.2 不同权限的访问能力
| 名称 | public | protected | default(空的) | private |
|---|---|---|---|---|
| 同一类中 | √ | √ | √ | √ |
| 同一包中(子类与无关类) | √ | √ | √ | |
| 不同包的子类 | √ | √ | ||
| 不同包中的无关类 | √ |
可见,public具有最大权限。private则是最小权限。
编写代码时,如果没有特殊的考虑,建议这样使用权限:
- 成员变量使用
private,隐藏细节。 - 构造方法使用
public,方便创建对象。 - 成员方法使用
public,方便调用方法。
不加权限修饰符,其访问能力与default修饰符相同
第三章 内部类
3.1 概述
什么是内部类
将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类。
成员内部类
成员内部类 :定义在类中 方法外的类。
如果一个事物的内部包含另一个事物,那么这就是一个类内部包含另一个类。
例如:身体和心脏的关系。又如:汽车和发动机的关系。
分类:
1. 成员内部类
2. 局部内部类(包含匿名内部类)
成员内部类的定义格式:
修饰符 class 外部类名称 {
修饰符 class 内部类名称 {
// ...
}
// ...
}
注意:内用外,随意访问;外用内,需要内部类对象。
==========================
如何使用成员内部类?有两种方式:
1. 间接方式:在外部类的方法当中,使用内部类;然后main只是调用外部类的方法。
2. 直接方式,公式:
类名称 对象名 = new 类名称();
【外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();】
public class Demo01InnerClass {
public static void main(String[] args) {
Body body = new Body(); // 外部类的对象
// 通过外部类的对象,调用外部类的方法,里面间接在使用内部类Heart
body.methodBody();
System.out.println("=====================");
// 按照公式写:
Body.Heart heart = new Body().new Heart();
heart.beat();
}
}
访问特点
- 内部类可以直接访问外部类的成员,包括私有成员。
- 外部类要访问内部类的成员,必须要建立内部类的对象。
创建内部类对象格式
外部类名.内部类名 对象名 = new 外部类型().new 内部类型();
-------------
public class Demo02InnerClass {
public static void main(String[] args) {
// 外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();
Outer.Inner obj = new Outer().new Inner();
obj.methodInner();
}
}
-----------------
public class Body { // 外部类
public class Heart { // 成员内部类
// 内部类的方法
public void beat() {
System.out.println("心脏跳动:蹦蹦蹦!");
System.out.println("我叫:" + name); // 正确写法!
}
}
// 外部类的方法
public void methodBody() {
System.out.println("外部类的方法");
new Heart().beat();
}
// 外部类的成员变量
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
---------------
// 如果出现了重名现象,那么格式是:外部类名称.this.外部类成员变量名
public class Outer {
int num = 10; // 外部类的成员变量
public class Inner /*extends Object*/ {
int num = 20; // 内部类的成员变量
public void methodInner() {
int num = 30; // 内部类方法的局部变量
System.out.println(num); // 局部变量,就近原则
System.out.println(this.num); // 内部类的成员变量
System.out.println(Outer.this.num); // 外部类的成员变量
}
}
}
内部类仍然是一个独立的类,在编译之后会内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和符号。比如,Person符号 。 比如,Person符号。比如,PersonHeart.class
局部内部类
局部内部类,如果希望访问所在方法的局部变量,那么这个局部变量必须是【有效final的】。
备注:从Java 8+开始,只要局部变量事实不变,那么final关键字可以省略。
原因:
1. new出来的对象在堆内存当中。
2. 局部变量是跟着方法走的,在栈内存当中。
3. 方法运行结束之后,立刻出栈,局部变量就会立刻消失。
4. 但是new出来的对象会在堆当中持续存在,直到垃圾回收消失。
public class MyOuter {
public void methodOuter() {
int num = 10; // 所在方法的局部变量
class MyInner {
public void methodInner() {
System.out.println(num);
}
}
}
}
----------------
如果一个类是定义在一个方法内部的,那么这就是一个局部内部类。
“局部”:只有当前所属的方法才能使用它,出了这个方法外面就不能用了。
定义格式:
修饰符 class 外部类名称 {
修饰符 返回值类型 外部类方法名称(参数列表) {
class 局部内部类名称 {
// ...
}
}
}
小节一下类的权限修饰符:
public > protected > (default) > private
定义一个类的时候,权限修饰符规则:
1. 外部类:public / (default)
2. 成员内部类:public / protected / (default) / private
3. 局部内部类:什么都不能写
class Outer {
public void methodOuter() {
class Inner { // 局部内部类
int num = 10;
public void methodInner() {
System.out.println(num); // 10
}
}
Inner inner = new Inner();
inner.methodInner();
}
}
------------------
public class DemoMain {
public static void main(String[] args) {
Outer obj = new Outer();
obj.methodOuter();
}
}
3.2 匿名内部类【重点】
匿名内部类 :是内部类的简化写法。它的本质是一个带具体实现的 父类或者父接口的 匿名的 子类对象。
开发中,最常用到的内部类就是匿名内部类了。以接口举例,当你使用一个接口时,似乎得做如下几步操作,
- 定义子类
- 重写接口中的方法
- 创建子类对象
- 调用重写后的方法
我们的目的,最终只是为了调用方法,那么能不能简化一下,把以上四步合成一步呢?匿名内部类就是做这样的快捷方式。
前提
匿名内部类必须继承一个父类或者实现一个父接口。
格式:
new 父类名或者接口名(){
// 方法重写
@Override
public void method() {
// 执行语句
}
};
-------------------
public interface MyInterface {
void method1(); // 抽象方法
void method2();
}
----------------------------------
public class MyInterfaceImpl implements MyInterface {
@Override
public void method1() {
System.out.println("实现类覆盖重写了方法!111");
}
@Override
public void method2() {
System.out.println("实现类覆盖重写了方法!222");
}
}
------------------------
如果接口的实现类(或者是父类的子类)只需要【使用唯一的一次】,
那么这种情况下就可以省略掉该类的定义,而改为使用【匿名内部类】。
匿名内部类的定义格式:
接口名称 对象名 = new 接口名称() {
// 覆盖重写所有抽象方法
};
对格式“new 接口名称() {...}”进行解析:
1. new代表创建对象的动作
2. 接口名称就是匿名内部类需要实现哪个接口
3. {...}这才是匿名内部类的内容
另外还要注意几点问题:
1. 匿名内部类,在【创建对象】的时候,只能使用唯一一次。
如果希望多次创建对象,而且类的内容一样的话,那么就需要使用单独定义的实现类了。
2. 匿名对象,在【调用方法】的时候,只能调用唯一一次。
如果希望同一个对象,调用多次方法,那么必须给对象起个名字。
3. 匿名内部类是省略了【实现类/子类名称】,但是匿名对象是省略了【对象名称】
强调:匿名内部类和匿名对象不是一回事!!!
public class DemoMain {
public static void main(String[] args) {
// MyInterface obj = new MyInterfaceImpl();
// obj.method();
// MyInterface some = new MyInterface(); // 错误写法!
// 使用匿名内部类,但不是匿名对象,对象名称就叫objA
MyInterface objA = new MyInterface() {
@Override
public void method1() {
System.out.println("匿名内部类实现了方法!111-A");
}
@Override
public void method2() {
System.out.println("匿名内部类实现了方法!222-A");
}
};
objA.method1();
objA.method2();
System.out.println("=================");
// 使用了匿名内部类,而且省略了对象名称,也是匿名对象
new MyInterface() {
@Override
public void method1() {
System.out.println("匿名内部类实现了方法!111-B");
}
@Override
public void method2() {
System.out.println("匿名内部类实现了方法!222-B");
}
}.method1();
// 因为匿名对象无法调用第二次方法,所以需要再创建一个匿名内部类的匿名对象
new MyInterface() {
@Override
public void method1() {
System.out.println("匿名内部类实现了方法!111-B");
}
@Override
public void method2() {
System.out.println("匿名内部类实现了方法!222-B");
}
}.method2();
}
}
第四章 引用类型用法总结
实际的开发中,引用类型的使用非常重要,也是非常普遍的。我们可以在理解基本类型的使用方式基础上,进一步去掌握引用类型的使用方式。基本类型可以作为成员变量、作为方法的参数、作为方法的返回值,那么当然引用类型也是可以的。
4.1 class作为成员变量
public class Weapon {//武器类
private String code; // 武器的代号
public Weapon() {
}
public Weapon(String code) {
this.code = code;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}
---------------------
// 游戏当中的英雄角色类
public class Hero {
private String name; // 英雄的名字
private int age; // 英雄的年龄
private Weapon weapon; // 引用武器类
public Hero() {
}
public Hero(String name, int age, Weapon weapon) {
this.name = name;
this.age = age;
this.weapon = weapon;
}
public void attack() {
System.out.println("年龄为" + age + "的" + name + "用" + weapon.getCode() + "攻击敌方。");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Weapon getWeapon() {
return weapon;
}
public void setWeapon(Weapon weapon) {
this.weapon = weapon;
}
}
--------------------
public class DemoMain {
public static void main(String[] args) {
// 创建一个英雄角色
Hero hero = new Hero();
// 为英雄起一个名字,并且设置年龄
hero.setName("盖伦");
hero.setAge(20);
// 创建一个武器对象
Weapon weapon = new Weapon("AK-47");
// 为英雄配备武器
hero.setWeapon(weapon);
// 年龄为20的盖伦用多兰剑攻击敌方。
hero.attack();
}
}
4.2 interface作为成员变量
接口是对方法的封装
public interface Skill { //接口
void use(); // 释放技能的抽象方法
}
-------------
public class SkillImpl implements Skill { //接口实现类
@Override
public void use() {
System.out.println("Biu~biu~biu~");
}
}
-----------
public class Hero {
private String name; // 英雄的名称
private Skill skill; // 英雄的技能 ------interface作为成员变量
public Hero() {
}
public Hero(String name, Skill skill) {
this.name = name;
this.skill = skill;
}
public void attack() {
System.out.println("我叫" + name + ",开始施放技能:");
skill.use(); // 调用接口中的抽象方法
System.out.println("施放技能完成。");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Skill getSkill() {
return skill;
}
public void setSkill(Skill skill) {
this.skill = skill;
}
}
-------------------
public class DemoGame {
public static void main(String[] args) {
Hero hero = new Hero();
hero.setName("艾希"); // 设置英雄的名称
//1 设置英雄技能
// hero.setSkill(new SkillImpl()); // 使用单独定义的实现类
//1 还可以改成使用匿名内部类
// Skill skill = new Skill() {
// @Override
// public void use() {
// System.out.println("Pia~pia~pia~");
// }
// };
// hero.setSkill(skill);
// 1 进一步简化,同时使用匿名内部类和匿名对象
hero.setSkill(new Skill() {
@Override
public void use() {
System.out.println("Biu~Pia~Biu~Pia~");
}
});
hero.attack();
}
}
4.3 interface作为方法参数和返回值类型
当接口作为方法的参数时,需要传递什么呢?当接口作为方法的返回值类型时,需要返回什么呢?对,其实都是它的子类对象。 ArrayList类我们并不陌生,查看API我们发现,实际上,它是 java.util.List 接口的实现类。所以,当我们看见List 接口作为参数或者返回值类型时,当然可以将ArrayList 的对象进行传递或返回。
/*
java.util.List正是ArrayList所实现的接口。
*/
public class DemoInterface {
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接口作为方法参数和返回值类型
list.add("迪丽热巴");
list.add("古力娜扎");
list.add("玛尔扎哈");
list.add("沙扬娜拉");
return list;
}
}
接口作为参数时,传递它的子类对象。
接口作为返回值类型时,返回它的子类对象。
------获取某集合中所有的偶数--------
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);
}
}
本文深入讲解Java中的final关键字、权限修饰符、内部类及引用类型用法,涵盖final的多种应用场景,权限修饰符的访问控制,内部类的定义与使用,以及引用类型作为成员变量、方法参数和返回值的实践。
139

被折叠的 条评论
为什么被折叠?



