“任何时候我也不会满足,越是多读书,就越是深刻地感到不满足,越感到自己知识贫乏。科学是奥妙无穷的。” ——马克思
目录
一、方法&方法重载
二、运算符
三、数据类型
四、面向对象
1. 面向对象思想
2. 引用传递
3. 访问权限修饰符
4. 封装 、 构造方法 与 this
4-1 封装、构造方法
4-2 this
5. static 关键字 、 代码块
6. 继承、方法重写 、 super 与 final
6-1 继承
6-2 方法重写
6-3 super
6-4 final
7.抽象类 、 接口 、 多态
7-1 抽象类 - abstract
7-2 接口 interface
7-3 多态
7-4 instanceof - 对象是哪个类(接口)的实例
8. Object 类 、 “equal和==”
五、内部类
1. 成员内部类
2. 局部内部类
3. 静态内部类
4. 匿名内部类
六、异常
七、反射机制
1:实现动态创建对象
2:动态调用方法
3:访问和修改私有成员(字段、方法等
一、方法&方法重载
- 就是一段可以重复调用的代码
修饰符 返回值类型 方法名 (参数类型 参数名1,参数类型 参数名2,...){
执行语句;
return 返回值;
}
public static int add(int a,int b){
int c = a + b;
return c;
}
方法重载
- 定义:同一个作用域内,方法名相同但 参数个数 或者 参数类型 不同的方法
- 换句话说:参数不同的方法有相同的名字,调用时根据参数不同确定要调用哪个方法
public class Example01 {
public static void main(String[] args) {
int x = 1, y = 2;
System.out.println(add(x, y));
int z = 3;
System.out.println(add(x, y, z));
double a = 1.0, b = 2.0;
System.out.println(add(a, b));
}
// 两个整数相加
public static int add(int x, int y) {
return x + y;
}
// 三个整数相加
public static int add(int x, int y, int z) {
return x + y + z;
}
// 两个浮点数相加
public static double add(double a, double b) {
return a + b;
}
}
二、运算符
- 赋值运算符
重点是 += 、 -=、/=、*=、%=
这些赋值运算符都不会改变原有的数据类型(但实际过程是先转为大的数据类型,然后再强转为原有数据类型)
- 运算符的优先级表格:
-
逻辑运算符中 异或(
^
):它的功能其实和!=
差不多,成立的话为true
,否则false
-
补充一个知识点:
当 id 依次递增 ( 1, 2 , 3 , 4 , 5 , 6 …) 时, 使用
(id+1)^1+1
可以得到相邻奇偶替换的结果原: 1, 2 , 3 , 4 , 5 , 6 …
每个数字经过
(id+1)^1+1
异或计算后得到 : 2, 1 , 4 , 3 , 6 , 5 …
-
-
与(
&
)、或(|
):不管条件是false还是true,两边条件都继续判断; -
短路与(
&&
):从左到右判断,左边出现false
就不再判断后续条件; -
短路或(
||
):从左到右判断,左边出现true
就不再判断后续条件;
三、数据类型
byte - 1字节
short char - 2字节
int float - 4字节
long double - 8字节
boolean - 至少1字节(JVM中占4字节,数组的话则为1字节)
四、面向对象
1. 面向对象思想
面向对象思想:将构成问题的事物按照一定的规则划分为多个独立的对象,通过调用对象来解决问题,其中对象包含属性和方法。
特点:封装、继承、多态
封装是指将数据和操作数据的方法封装在一个类中,对外提供特定的接口来访问这些数据和方法,而隐藏内部的实现细节。 -
private
继承是指在原有类(也就是父类)的属性和方法上进行继承以及进行功能扩展。 -
extend
多态是指不同子类继承父类后,属性和行为各有特点。
2. 引用传递
引用传递:
实际上所谓的引用传递就是,将一个堆内存空间的使用权分配给多个栈内存空间使用,每个栈内存空间都可以修改堆内存空间的内容。
注意:一个 栈
内存空间只能指向一个 堆
内存空间,如果想要再指向其他堆内存空间,就必须断开已有的指向,才能分配新的指向。
3. 访问权限修饰符
访问权限:(类中的属性和方法)
// 同包下的Test01类
public class Test01 {
// 可以被所有类访问
public int public_int;
// 可以被所有子类及本包下的类访问
protected boolean protected_boolean;
// 默认访问权限,能在本包内访问
void default_void(){
System.out.println("在同一个包下,访问成功");
}
// 私有类,只能在本类访问
private class private_class{ }
}
// 和Test01
public class Example03 {
public static void main(String[] args) {
Test01 test01 = new Test01();
System.out.println("本包下访问情况:");
// public : 所有类都可以访问
System.out.println("test01.public_int = " + test01.public_int);
test01.public_int = 1;
System.out.println("test01.public_int = " + test01.public_int);
// protected : 子类或者同包下
System.out.println("test01.protected_boolean = " + test01.protected_boolean);
test01.protected_boolean = true;
System.out.println("test01.protected_boolean = " + test01.protected_boolean);
// default : 同包下
test01.default_void();
// private : 不能访问,只能在类中访问
// test01.private_class;
}
}
public > protected > default > private
全局 > 子类 > 同包下 > 本类
注意:局部变量没有访问控制权限
4. 封装 、 构造方法 与 this
// 学生类
public class Student {
private String name;
private int age;
/**
* 构造方法注意事项:
* ① 构造方法名字一定要和类名一致
* ② 构造方法名称前面不能有 任何返回值 类型 (什么什么int呀,boolean呀,String呀等等...)
* ③ 不能在构造方法里面用 `return` !! 要用的话也是 `return;` (也没什么意义);
*/
public Student() {
System.out.println("使用了无参构造方法");
return ;
}
/**
* 有参构造
* @param name
* @param age
*/
public Student(String name,int age) {
this.name = name;
this.age = age;
System.out.println("使用了有参构造方法");
}
/**
* 构造方法重载,但是只能是构造方法 参数类型 或者 参数个数 不同
* @param name
*/
public Student(String name){
this.name = name;
System.out.println("构造方法重载 - 参数个数不同");
}
public Student(float age){
System.out.println("构造方法重载 - 参数类型不同");
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
public void setAge(int age) {
// 加一层限制,防止年龄异常
// 为什么在这里加,是因为如果直接让用户访问对象的话,他哪管什么规则不规则,来了就乱设
// 所以封装体现了面向对象的安全性
if (age < 0){
System.out.println("年龄不能小于0岁");
} else if (age > 150){
System.out.println("年龄不能超过150岁");
} else {
this.age = age;
}
}
public void setName(String name) {
this.name = name;
}
public void read() {
System.out.println("大家好,我是" + name + ",今年我" + age + "岁,很高兴认识大家!");
}
}
// 实现类
public class Example04 {
public static void main(String[] args) {
// 实现封装特性:private 关键字,将对象的信息隐藏起来,只能使用对象的方法来操作对象的属性,防止恶意修改对象数据。
Student student = new Student();
// 这种什么什么setXxx呀,统称为setter方法
student.setName("萌神想");
student.setAge(22);
// 调用对象的方法
student.read();
// 这种什么什么getXxx呀,统称为getter方法
String name = student.getName();
int age = student.getAge();
System.out.println("大家好,我是" + name + ",今年我" + age + "岁,很高兴认识大家!");
}
}
4-1 封装、构造方法
注意:构造方法、setter、getter、封装特性【private】、this关键字
构造方法注意事项:
① 构造方法名字一定要和类名一致
② 构造方法名称前面不能有 任何返回值 类型 (什么什么int呀,boolean呀,String呀等等…)
③ 不能在构造方法里面用 return
!! 要用的话也是 return;
(也没什么意义);
封装特性:
private
关键字,将对象的信息隐藏起来,只能使用对象的方法来操作对象的属性,防止恶意修改对象数据。
4-2 this
this 关键字:
① 本类的属性;
② 本类的成员方法;
③ 静态方法中不能有 this
;
④ 本类的构造方法 - 长这个样子: this()
注意1: this()
只能在构造方法里面使用,且它本身是一个无参构造;
注意2:在其它构造方法内,必须放在第一位;
注意3:不能在一个类中两个构造方法内使用 this
相互调用(否则会出现递归调用,会报错)
构造块:
构造块先于构造方法执行(与次序无关)
5. static 关键字 、 代码块
public class StudentSchool {
private String name;
private int age;
private static String school = "东华理工大学";
public StudentSchool() {
System.out.println("使用了无参构造方法");
}
public StudentSchool(String name, int age, String school) {
this.name = name;
this.age = age;
System.out.println("使用了有参构造方法");
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
public static String getSchool() {
return school;
}
public void setAge(int age) {
// 加一层限制,防止年龄异常
// 为什么在这里加,是因为如果直接让用户访问对象的话,他哪管什么规则不规则,来了就乱设
// 所以封装体现了面向对象的安全性
if (age < 0) {
System.out.println("年龄不能小于0岁");
} else if (age > 150) {
System.out.println("年龄不能超过150岁");
} else {
this.age = age;
}
}
public void setName(String name) {
this.name = name;
}
public static void setSchool(String school) {
StudentSchool.school = school;
}
public void read() {
System.out.println("大家好,我是" + name + ",今年我" + age + "岁,我来自" + school + ",很高兴认识大家!");
}
}
public class Example05 {
public static void main(String[] args) {
// Static 静态方法
StudentSchool studentSchool = new StudentSchool();
studentSchool.setAge(22);
studentSchool.setName("萌神想");
studentSchool.read();
// 要用类名去修改静态成员变量
StudentSchool.setSchool("华中科技大学");
studentSchool.read();
}
}
注意:
① 静态属性也称为全局属性,可以使用类名直接访问;
② static不能修饰局部变量;
③ 静态方法只能访问静态成员(非静态成员需要先创建对象才能访问,即随对象的创建,非静态成员才会分配内存,而静态方法在被调用时可以不创建任何对象);
④ 静态代码块 → 构造代码块 → 构造方法;(静态代码块中的内容只输出一次,因为静态代码在类第一次使用时才会被加载,并且只被加载一次。)
public class StudentTest {
private static String name;
private static int n;
// 静态构造块
static {
name = "执行顺序 ";
n++;
System.out.println(name + n +". 静态构造块");
}
// 主函数
public static void main(String[] args){
name = "执行顺序 ";
n++;
System.out.println(name + n + ". 主函数");
StudentTest studentTest = new StudentTest();
StudentTest studentTest1 = new StudentTest();
}
// 构造块
{
name = "执行顺序 ";
n++;
System.out.println(name + n + ". 构造块");
}
}
执行顺序 1. 静态构造块
执行顺序 2. 主函数
执行顺序 3. 构造块
执行顺序 4. 构造块
6. 继承、方法重写 、 super 与 final
6-1 继承
继承:
类的继承是指在一个原有类的基础上构建一个新的类,构建的新类被称为子类,原有类称为父类;子类也可以定义自己的属性和方法。
子类会自动继承父类的属性和方法,关键字是 extend
;但是子类只能访问父类的 public、protected 修饰的属性或方法,不能访问 default、private 修饰的。
父类也被称为超类。
继承中的注意事项:
- 类只支持单继承,不允许多继承
class a{}
class b{}
class c extend a,b {}
// 这是错误的
- 多个类可以继承一个父类
class a{}
class b extend a {}
class c extend a {}
// 正确
- 多层继承也是可以的( 爷 → 父 → 子 → 孙)
6-2 方法重写
方法重写:
重写:子类对父类的方法进行重写。子类中重写父类的方法需要和父类中被重写的方法具有相同的方法名、参数列表、返回值类型。而且访问权限还不能升级(public 之后就一直是 public):**父类的方法被public修饰了,子类不能比public还严格!**这个意思。
6-3 super
super关键字:
使用super关键字可以在子类中访问父类的非私有方法、非私有属性以及构造方法。(除了private之外,其他default、protected、public都可以访问,这句话还是很严谨的)
通过super调用父类构造方法的代码必须位于子类构造方法的第一行,并且只能出现一次。
super 与 this 的区别:
区别点 | super | this |
---|---|---|
访问属性 | 直接访问父类非私有属性 | 访问本类中的属性。如果本类没有则在父类上找 |
调用方法 | 直接访问父类非私有方法 | 调用本类中的方法。如果没有则在父类上找 |
调用构造方法 | 调用父类构造方法,必须放在子类构造方法的首行 | 调用本类构造方法,必须放在构造方法的首行 |
注意:super与this不能同时出现在同一个构造方法内
6-4 final
final关键字:可以修饰的有:类、属性、方法
final注意:
-
使用final关键字修饰的类不能有子类,也就是不能被继承
-
使用final关键字修饰的方法不能被重写,也就是子类不能重写父类中被final修饰的方法
-
使用final关键字修饰的属性是常量,常量不可被修改
-
和static一起用的时候,如下
// 全局常量
public static final MAX_LENGTH = 100;
7.抽象类 、 接口 、 多态
7-1 抽象类 - abstract
抽象类产生原因:定义一个类时,常常需要定义一些成员方法描述类的行为特征,但有时这些方法的实现方法是不确定的,所以抽象类必然要满足这个要求。
抽象类包含:成员变量、抽象方法。(然后没了)
抽象方法是使用 abstract
关键字修饰的成员方法,抽象方法在定义时,不需要实现方法体。
抽象方法定义规则:
- 包含抽象方法的类必然是抽象类;
- 声明抽象类和抽象方法时都需要使用
abstract
关键字修饰; - 抽象方法只需要声明而不需要实现;
- 如果一个非抽象类继承了抽象类后,那么该类必须重写抽象类中的全部抽象方法;
// 定义抽象动物类
abstract class Animal08{
// 定义抽象类成员变量
public String name;
// 定义抽象方法
abstract void shout();
}
// 定义小狗类
class Dog extends Animal08{
// 重写抽象方法
@Override
public void shout(){
System.out.println("汪汪汪...");
}
}
public class Example08 {
public static void main(String[] args) {
Dog dog = new Dog();
dog.shout();
}
}
注意:不能使用 private
修饰 abstract ,因为抽象方法你要重写才能有用,如果把方法私有,那子类就没有权限重写方法,也就没意义了。
7-2 接口 interface
接口很重要,你不懂不行。
接口是一种用来定义程序的协议,它用来描述类或结构的一组相关行为。
面向接口编程:将程序不同的业务逻辑分离,以接口的形式对接不同的业务模块。
使用接口的目的是为了克服单继承的限制,因为一个类只能继承一个父类,而一个类可以同时实现多个父接口。
在 JDK8 之前,接口都是由全局常量和抽象方法组成。
在 JDK8 之后,接口除了全局常量、抽象方法外,还可以定义默认方法和静态方法,默认方法用default修饰,静态用static修饰,而且这两种方法都允许有方法体。
[public] interface 接口名 [extend 接口1,extend 接口2 ,...]{
// 这两个是 jdk8 之前的
[public] [static] [final] 数据类型 常量名 = 常量;
[public] [abstract] 返回值的数据类型 方法名(参数列表);
// 接下来两个是 jdk8 新加的
[public] static 返回值的数据类型 方法名(参数列表) {}
[public] default 返回值的数据类型 方法名(参数列表) {}
}
extend 接口1,extend 接口2 ,...
表示 一个接口可以继承多个父接口;- 接口中变量默认使用
public static final
进行修饰,即全局变量,且一定要赋初始值; - 接口中定义的抽象方法默认使用
public abstract
修饰;
接口本身不能直接实例化,接口中的抽象方法和默认方法只能通过接口实现类的实例对象进行调用
实现类通过 implements
关键字实现接口,并且实现类必须重写接口中所有的抽象方法
接口不允许继承抽象类(interface 接口名 extend 抽象方法 ×,抽象方法 implements 接口 √)但是允许接口继承接口,并且一个接口可以同时继承多个接口
// 定义接口动物
interface Animal09{
// 定义全局常量
String NAME = "牧羊犬";
String SEX = "男";
// 默认 公共抽象方法
void shout();
void info();
// jdk8 中的静态方法
static String getNAME(){
return Animal09.NAME;
};
// jdk8 中的默认方法
default void getSEX(){
System.out.println(Animal09.SEX);
}
}
// 定义抽象类 Action
abstract class Action09{
// 抽象方法
public abstract void eat();
}
// 定义小狗类
class Dog09 extends Action09 implements Animal09{
// 重写抽象方法 eat()
@Override
public void eat(){
System.out.println("喜欢吃骨头");
}
// 重写接口抽象方法 shout()
@Override
public void shout(){
System.out.println("汪汪汪...");
}
@Override
public void info(){
System.out.println(Animal09.getNAME());
}
}
public class Example09 {
public static void main(String[] args) {
Dog09 dog = new Dog09();
// 接口 公共抽象方法 实现
dog.info();
// 接口 默认方法 实现(子类可以不用重写这个方法,因为是default修饰的)
dog.getSEX();
// 接口 公共抽象方法 实现
dog.shout();
// 抽象类 公共抽象方法 实现
dog.eat();
}
}
7-3 多态
多态的概念很重要,也很抽象,希望你一定要掌握!
多态指不同类的对象在调用同一个方法时表现出的多种不同行为。换句话说:在同一个方法中,这种由于参数类型不同而导致执行效果不同的现象就是多态。
主要有以下两种形式:
- 方法的重载
- 对象的多态(方法的重写)
// 定义抽象类 Animal10
abstract class Animal10 {
// 抽象方法
public abstract void shout();
}
// 定义小狗类 Dog10
class Dog10 extends Animal10{
@Override
public void shout(){
System.out.println("汪汪汪...");
}
}
// 定义小猫类 Cat10
class Cat10 extends Animal10{
@Override
public void shout(){
System.out.println("喵喵喵...");
}
}
public class Example10 {
public static void main(String[] args) {
System.out.println("输出的结果:");
Animal10 dog = new Dog10();
Animal10 cat = new Cat10();
dog.shout();
cat.shout();
}
}
// 输出的结果:
// 汪汪汪...
// 喵喵喵...
为了让你一定明白,我这么说吧,先把定义背下来:多态指不同类的对象在调用同一个方法时表现出的多种不同行为。然后思考,不同类(上面代码中的 dog10
类 和 cat10
类 )在main方法中创建了对象,但是声明的类型是父类( Animal10
类),在调用同一个方法( shout()
)的时候,表现出了不同的结果(汪汪汪...
和 喵喵喵...
),这就是多态。父类的引用指向自己子类的对象时多态的一种体现形式。
对象类型的转化:
- 向上转型:子类对象 → 父类对象
- 向下转型:父类对象 → 子类对象
向上转型:父类对象可以调用子类重写父类的方法,这样当添加新功能的时候,只需要新增一个子类,在子类中对父类的功能进行扩展,而不用更改父类的代码,保证了程序的安全性。
格式: 父类类型 父类对象 = 子类实例 ;
特别注意:父类对象不能用子类特有的方法,因为父类没有定义那个方法。
向下转型:一般是为了重新获得因为向上转型而失去的子类特性。对象在进行向下转型前,需要先进行向上转型,才能进行向下转型。
格式: 父类类型 父类对象 = 子类实例 ; 子类类型 子类对象 = (子类)父类对象 ;
特别注意:尽管你是向下转型,但是只要子类改了你父类的方法,那你父类再调用那个方法的时候,已经是子类修改后的方法了(你调用不了你之前的方法)。
多态的作用: 使得面向对象编程更加灵活、可扩展和易于维护。
- 代码可扩展性:通过多态,可以在不修改现有代码的情况下添加新的类和功能。只需要实现相同的接口或继承自同一基类即可。
- 灵活性:多态允许同一方法根据对象的具体类型表现出不同的行为。这使得程序在运行时可以决定使用哪种具体实现。
- 可维护性:多态性降低了程序的复杂性,使得代码更易于理解和维护。程序中的变化可以集中在某些地方,而不需改动整个系统。
- 提高代码重用率:通过定义通用的接口,开发者可以编写更具复用性的代码,减少重复实现。
- 实现抽象:多态与抽象密切相关,允许程序员基于接口或抽象类来编写代码,而不是关注具体的实现细节。
- 简化条件语句:通过多态,在处理不同类型的对象时,可以避免使用大量的条件语句,从而使代码更加简洁和清晰。
7-4 instanceof - 对象是哪个类(接口)的实例
instanceof 关键字:判断一个对象是否是某个类(或接口)的实例
// instanceof 格式 - 相同则为true,否则为false
实例后的对象 instanceof 待验证的类(或接口);
8. Object 类 、 “equal和==”
Object类是所有类的父类,每个类都直接或间接继承了Object类,因此这个类通常被称为超类(基类)。当定义一个类时,如果没有使用extends关键字为这个类显式地指定父类,那么该类会被默认继承Object类。
方法名称 | 方法说明 |
---|---|
boolean equal() | 判断两个对象是否“相等” |
int hashCode() | 返回对象的哈希值 |
String toString() | 返回对象的字符串表示形式 |
通常会重写 toString()
并将它用来表示此类的基本信息。
扩展:在 Java 中,“==” 和 “equals ()” 方法都可以用于比较,但它们的比较方式有所不同。
一、“==” 运算符
- 基本数据类型比较:
- 当用于比较基本数据类型(如 int、double、char 等)时,“==” 比较的是两个值是否相等。例如,
int a = 5; int b = 5;
,此时a == b
会返回 true,因为两个变量存储的值都是 5。- 引用数据类型比较:
- 当用于比较引用数据类型(如对象)时,“==” 比较的是两个引用是否指向同一个对象。例如:
String s1 = new String("hello"); String s2 = new String("hello"); System.out.println(s1 == s2); // 输出 false
在这个例子中,虽然
s1
和s2
的内容都是 “hello”,但它们是两个不同的对象,所以 “==” 比较返回 false。二、“equals ()” 方法
- 对于自带的类,如
String
、Integer
等包装类,equals()
方法被重写,比较的是对象的内容是否相等。例如:String s1 = new String("hello"); String s2 = new String("hello"); System.out.println(s1.equals(s2)); // 输出 true
这里
s1
和s2
虽然是不同的对象,但它们的内容相同,所以 “equals ()” 比较返回 true。
- 对于自定义类,如果没有重写
equals()
方法,那么默认的行为与 “==” 相同,也是比较引用是否指向同一个对象。如果需要比较自定义类对象的内容是否相等,就需要重写equals()
方法。例如:class Person { private String name; private int age; Person(String name, int age) { this.name = name; this.age = age; } @Override public boolean equals(Object o) { // 如果当前对象(this)和传入的对象(o)是同一个对象引用,那么直接返回true if (this == o) return true; // 传入的对象(o)是空或者类型与当前对象类型不一样,则返回false if (o == null || getClass()!= o.getClass()) return false; Person person = (Person) o; return age == person.age && Objects.equals(name, person.name); } @Override public int hashCode() { return Objects.hash(name, age); } }
在这个例子中,重写了
Person
类的equals()
方法,使得可以比较两个Person
对象的内容是否相等。
五、内部类
1. 成员内部类
成员内部类可以访问外部类的所有成员,无论外部类的成员是何种访问权限。如果想外部类访问内部类,则需要通过创建内部类对象才能进行访问。
// 内部类
public class Example11 {
public static void main(String[] args) {
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.show1();
outer.test2();
}
}
class Outer {
int m = 0;
// 外部类方法 test1()
void test1() {
System.out.println("外部类成员方法test1()");
}
// 内部类Inner
class Inner {
int n = 1;
void show1() {
// 访问外部类的成员变量 m
System.out.println("外部类的成员变量 m = " + m);
// 访问外部类的方法 test1()
test1();
}
void show2() {
// 访问内部类的成员变量n
System.out.println("内部类的成员方法show2()");
}
}
// 外部类方法 test2()
void test2() {
Inner inner = new Inner();
System.out.println("内部类成员变量 n = " + inner.n);
inner.show2();
}
}
注意:内部类的定义方式
2. 局部内部类
局部内部类,也称为方法内部类,是指定义在某个局限范围中的类,它和局部变量都是在方法中定义,有效范围只限于方法内部。
局部内部类权限和成员内部类一样,都可以直接访问外部类的所有成员和成员方法,无论权限。如果外部类想访问内部类,只能在局部内部类所在的方法中去创建对象并访问。
// 内部类
public class Example12 {
public static void main(String[] args) {
Outer1 outer = new Outer1();
outer.test2();
}
}
class Outer1 {
int m = 0;
// 外部类方法 test1()
void test1() {
System.out.println("外部类成员方法test1()");
}
// 外部类方法 test2()
void test2() {
// 局部内部类Inner
class Inner {
int n = 1;
void show() {
// 访问外部类的成员变量 m
System.out.println("外部类的成员变量 m = " + m);
// 访问外部类的方法 test1()
test1();
}
}
Inner inner = new Inner();
System.out.println("内部类成员变量 n = " + inner.n);
inner.show();
}
Inner inner = new Inner();
}
注意:局部内部类
只能在 局部内部类所在的方法
里面创建 局部内部类对象
,否则报错。
3. 静态内部类
静态内部类,使用static关键字修饰的成员内部类。与成员内部类相比,形式上只是加了个static,但是功能上有点阉割了,因为只能访问外部类的静态成员。外部类访问静态内部类时,因为程序已经提前在静态常量区为静态内部类分配好了内存,所以直接创建一个静态内部类没问题。
// 内部类
public class Example13 {
public static void main(String[] args) {
Outer2.Inner inner = new Outer2.Inner();
inner.show1();
System.out.println("静态内部类的成员变量 n = " + inner.n);
}
}
class Outer2 {
static int m = 0;
// 内部类Inner
static class Inner {
int n = 1;
void show1() {
// 访问外部类的成员变量 m
System.out.println("外部类的成员变量 m = " + m);
}
}
}
注意:静态内部类的定义方式
4. 匿名内部类
调用某个方法时,如果该方法的参数时接口类型,那么在传参时,除了可以传入一个接口实现了类,还可以传入一个实现接口的匿名内部类作为参数,在匿名内部类实现(重写)接口的方法。
// 内部类
public class Example15 {
public static void main(String[] args) {
animalShout(new Animal15() {
@Override
public void shout() {
System.out.println("匿名内部类来重写你");
}
});
}
public static void animalShout(Animal15 animal15){
animal15.shout();
}
}
interface Animal15 {
void shout();
}
也可以替换成 Lambda 表达式
// 内部类
public class Example15 {
public static void main(String[] args) {
animalShout(() -> System.out.println("匿名内部类来重写你"));
}
public static void animalShout(Animal15 animal15){
animal15.shout();
}
}
interface Animal15 {
void shout();
}
Lambda 表达式扩展( JDK8 之后引入的):
在 Java 中,Lambda 表达式是一种简洁的方式来表示可传递给方法或存储在变量中的匿名函数。
一、作为函数式接口的实现
假设你有一个需求,要对一组数字进行操作,比如将每个数字乘以 2 后再打印出来。可以使用
Consumer<Integer>
函数式接口和 Lambda 表达式来实现这个功能。import java.util.Arrays; import java.util.List; import java.util.function.Consumer; public class LambdaExample { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); // 使用 Lambda 表达式实现 Consumer 接口,打印每个数字乘以 2 的结果 Consumer<Integer> doubleAndPrint = num -> System.out.println(num * 2); numbers.forEach(doubleAndPrint); } }
在这个例子中:
Consumer<Integer>
表示一个接受整数参数但不返回结果的操作。doubleAndPrint
是一个使用 Lambda 表达式创建的Consumer
接口的实现,它接受一个整数参数num
,将其乘以 2 后打印出来。numbers.forEach(doubleAndPrint)
遍历列表中的每个数字,并将每个数字传递给doubleAndPrint
这个操作进行处理。二、在集合操作中的使用
假设你有一个班级学生名单,你想找出名字长度大于 4 的学生并打印他们的名字。
import java.util.ArrayList; import java.util.List; public class LambdaInCollectionExample { public static void main(String[] args) { List<String> students = new ArrayList<>(); students.add("Tom"); students.add("Jerry"); students.add("Alice"); students.add("Bob"); students.add("Eve"); // 使用 Lambda 表达式和流操作筛选并打印名字长度大于 4 的学生 students.stream() .filter(name -> name.length() > 4) .forEach(System.out::println); } }
在这个例子中:
- 首先创建了一个包含学生名字的列表
students
。- 然后使用
stream()
方法将列表转换为流。filter(name -> name.length() > 4)
使用 Lambda 表达式作为筛选条件,只保留名字长度大于 4 的元素。forEach(System.out::println)
使用方法引用作为 Lambda 表达式的简化形式,打印每个满足条件的学生名字。三、作为方法参数传递
假设你正在开发一个游戏,游戏中有不同类型的角色,每个角色都有不同的攻击方式。你可以定义一个方法来执行角色的攻击动作,而这个攻击动作可以通过 Lambda 表达式作为参数传递进去。
public class LambdaAsMethodParameterExample { public static void main(String[] args) { Warrior warrior = new Warrior(); Mage mage = new Mage(); // 使用方法引用作为 Lambda 表达式传递给 performAttack 方法 performAttack(warrior::attackWithSword); performAttack(mage::attackWithMagic); } static void performAttack(Runnable attackAction) { System.out.println("Preparing to attack..."); // 执行传入的 Runnable(即 Lambda 表达式或方法引用代表的行为) attackAction.run(); System.out.println("Attack completed."); } } class Warrior { void attackWithSword() { System.out.println("Warrior attacks with sword."); } } class Mage { void attackWithMagic() { System.out.println("Mage attacks with magic."); } }
在这个例子中:
- 定义了一个方法
performAttack
,它接受一个Runnable
类型的参数attackAction
,这个参数代表一个可以执行的攻击动作。- 在
main
方法中,分别为战士(Warrior
)和法师(Mage
)创建实例,并将他们的攻击方法(分别是attackWithSword
和attackWithMagic
)作为 Lambda 表达式传递给performAttack
方法。- 当
performAttack
方法被调用时,它会先打印“Preparing to attack…”,然后执行传入的攻击动作,最后打印“Attack completed.”。通过这种方式,使用 Lambda 表达式作为方法参数,可以使代码更加灵活,能够适应不同的行为需求,而无需为每个具体的行为编写单独的方法。
四、在并行流中的使用
假设你有一个任务是统计一个包含很多整数的列表中所有偶数的平方和。如果使用传统的方法,可能需要遍历整个列表,找到偶数,计算平方,然后累加求和,这可能会比较耗时。而使用并行流和 Lambda 表达式可以充分利用多核处理器的优势,加快计算速度。
import java.util.ArrayList; import java.util.List; import java.util.stream.IntStream; public class ParallelStreamExample { public static void main(String[] args) { List<Integer> numbers = new ArrayList<>(); for (int i = 1; i <= 10000; i++) { numbers.add(i); } int sumOfEvenSquares = numbers.parallelStream() .filter(n -> n % 2 == 0) .map(n -> n * n) .sum(); System.out.println("Sum of squares of even numbers: " + sumOfEvenSquares); } }
在这个例子中:
- 首先创建了一个包含 1 到 10000 的整数列表
numbers
。- 然后使用
parallelStream()
方法将列表转换为并行流。filter(n -> n % 2 == 0)
使用 Lambda 表达式筛选出偶数。map(n -> n * n)
使用 Lambda 表达式将每个偶数转换为其平方。- 最后,
sum()
方法计算所有偶数平方的总和。通过使用并行流和 Lambda 表达式,Java 可以自动将这个任务分配到多个处理器核心上同时执行,从而大大提高计算效率。特别是当处理大量数据时,这种优势会更加明显。
六、异常
所有异常都继承自 Throwable
类 ,主要分为 Error类(程序中产生的错误) 、 Exception类(程序中产生的异常)
-
Error类 : 比较严重,表示java程序运行时产生的系统内部错误或资源耗尽的错误。仅修改程序不能解决问题;
-
Exception类 : 重点是 运行时异常( RuntimeException ) ,修改程序本身可以解决问题
Throwable类的常用方法
方法声明 | 功能描述 |
---|---|
String getMessage() | 返回异常的消息字符串 |
String toString() | 返回异常的简单信息描述 |
void printStackTrace() | 获取异常类名和异常信息以及异常出现在程序中的位置,并把信息输出 |
异常处理五个关键字:try、catch、finally、throws、throw
重点是 : 自定义异常
public class Example19 {
public static void main(String[] args) {
try{
divide(3,0);
} catch (DivideByMinusException e){
System.out.println("捕获异常:" + e.getMessage());
}
}
public static void divide(int x,int y) throws DivideByMinusException{
if (y == 0) {
throw new DivideByMinusException("除数不能等于0");
}
System.out.println(x/y);
}
}
class DivideByMinusException extends Exception{
public DivideByMinusException(){
super();
}
public DivideByMinusException(String message){
super(message);
}
}
System 类中 返回 当前以毫秒为单位的当前时间 static void currentTimeMillis();
七、反射机制
反射机制在Java中有以下几个重要作用,下面为你举例说明:
1:实现动态创建对象
- 作用:在编写代码时,有时候我们没办法提前确定要创建哪个类的对象,反射就可以让程序在运行时根据条件动态地去创建不同类的实例。
- 举例:假设你在开发一个游戏,游戏里有不同类型的角色,比如“战士”“法师”“刺客”等,每个角色对应一个类,它们都继承自一个公共的“角色”基类。游戏开始前,玩家可以选择要使用的角色类型,而你在代码里事先并不知道玩家会选哪种角色。这时候就可以利用反射,根据玩家的选择来动态创建对应的角色对象。比如下面这样简单的代码示意:
import java.lang.reflect.Constructor;
class Warrior {
public Warrior() {
System.out.println("创建了战士角色");
}
}
class Mage {
public Mage() {
System.out.println("创建了法师角色");
}
}
class Game {
public static void createRole(String roleClassName) throws Exception {
Class<?> roleClass = Class.forName(roleClassName);
Constructor<?> constructor = roleClass.getConstructor();
constructor.newInstance();
}
}
public class ReflectionRoleExample {
public static void main(String[] args) throws Exception {
// 假设玩家选择了"Warrior"这个角色类型
String selectedRole = "Warrior";
Game.createRole("com.example." + selectedRole);
}
}
这里根据玩家选择的角色类型字符串(假设类都在com.example
包下),通过反射找到对应的类并创建实例,如果玩家选的是“法师”,把selectedRole
改成"Mage"
就能创建出法师角色的对象了。
2:动态调用方法
- 作用:能在运行时根据具体情况调用不同类中的不同方法,不需要在编译阶段就把调用的方法固定下来。
- 举例:有一个图形绘制的程序,里面有圆形、矩形等不同图形类,每个类都有自己的
draw
方法来进行图形绘制。程序中有个功能是根据用户输入来绘制相应图形,通过反射就可以动态调用对应的draw
方法。示例代码如下:
import java.lang.reflect.Method;
class Circle {
public void draw() {
System.out.println("正在绘制圆形");
}
}
class Rectangle {
public void draw() {
System.out.println("正在绘制矩形");
}
}
class Drawer {
public static void drawShape(String shapeClassName) throws Exception {
Class<?> shapeClass = Class.forName(shapeClassName);
Object shapeObj = shapeClass.getConstructor().newInstance();
Method drawMethod = shapeClass.getMethod("draw");
drawMethod.invoke(shapeObj);
}
}
public class ReflectionDrawExample {
public static void main(String[] args) throws Exception {
// 假设用户选择绘制圆形
String selectedShape = "Circle";
Drawer.drawShape("com.example." + selectedShape);
}
}
根据用户选择的图形类名字符串(同样假设类都在com.example
包下),利用反射找到对应的类、创建实例并调用其draw
方法,实现动态绘图的操作。
3:访问和修改私有成员(字段、方法等)
- 作用:正常情况下,类的私有成员是不能在外部类直接访问和修改的,但反射可以突破这个限制,在一些特定场景下,比如做单元测试、框架底层实现等时很有用。
- 举例:假设有个
Person
类,里面有个私有字段private String password
,在做单元测试时,想要验证修改密码的方法是否正确,就可以通过反射来访问和修改这个私有字段进行测试。示例代码如下:
import java.lang.reflect.Field;
class Person {
private String password;
public Person(String password) {
this.password = password;
}
public String getPassword() {
return password;
}
}
class TestPerson {
public static void main(String[] args) throws Exception {
Person person = new Person("123456");
Field passwordField = Person.class.getDeclaredField("password");
passwordField.setAccessible(true);
passwordField.set(person, "654321");
System.out.println("修改后的密码: " + person.getPassword());
}
}
这里通过反射获取到Person
类的私有字段password
,设置其可访问后就能修改它的值了,便于进行相关的测试等操作。