构造方法
类的构造方法不需要添加数据类型也不需要返回值,在使用的类的时候就会调用构造方法。默认提供无参构造方法。构造方法只会调用一次!
注意:创建对象必须调用构造方法,且只能调用一次!
public class father {
private int age;
public static void main(String[] args) {
father dad = new father();
}
father()
{
System.out.println("father的构造方法");
}
father(int age)
{
System.out.println("father的有参构造");
this.age = age;
System.out.println(age);
}
}
执行上面的代码时,创建一个 father 对象就会自动调用类的构造方法。
需要注意的是:不写构造方法的话,默认提供无参构造方法,如果写了有参构造方法就不会再提供无参构造的方法,需要自己实现无参构造。
使用有参构造的时候需要在创建的对象的时候传入相应的参数,注意参数的类型要和方法中的参数类型保持一致
public class father {
private int age;
public static void main(String[] args) {
father dad = new father(40);
}
father()
{
System.out.println("father的构造方法");
}
father(int age)
{
System.out.println("father的有参构造");
this.age = age;
System.out.println(age);
}
}
this 关键字
主要的作用就是避免重名歧义,比如类中有一个 age 变量,使用 setAge (int age)
赋值时,函数中的形参与类内的 age 变量就重名了,那么就可以用 this 关键字进行区分
public class father {
private int age;
public void setAge(int age)
{
//this关键字指的就是father这个类的age成员变量
this.age = age;
}
}
toString()
class 是引用数据类型,如果定义一个类,然后再用这个类创建一个对象,如果我们想要打印该对象中的实例变量就需要 tostring 方法,内置的 tostring 方法会打印出 类名@奇怪的数字
。所以要重写 tostring 方法
public class temp {
int x;
int y;
public static void main(String[] args) {
temp t = new temp();
System.out.println(t);
}
@Override
public String toString() {
//返回想要打印的字符串
return "x" + '=' + x +'\n'+'y'+'='+y;
}
}
封装
封装是指隐藏对象的内部实现细节,用户必须通过指定的方法才能操作
对象的属性。在上面的代码中,如果在 father 类外对 age 变量进行赋值,就必须使用 setAge(int age)
函数。
访问权限
继承
继承可以让子类继承父类中的成员变量以及方法。一个父类可以有多个子类,但是一个子类只能拥有一个父类。如果 father 有一个子类 son,son 需要继承父类
//extends 后接父类
public class son extends father{
}
super 关键字
在子类中方法中如果想要调用父类的方法,可以使用 super 关键字。
比如
构造方法
public class son extends father{
son()
{
super();
}
}
这里需要注意,在创建子类的时候是一定会先调用父类的构造函数的,即便将 super()
注释掉,我们在创建 son 这个对象的时候依然会调用父类的无参构造!
那么联系之前讲的构造方法,如果你在父类中只实现了有参构造,那么在实现 son 的构造函数时就必须使用 super 关键字手动调用父类的有参构造
所以建议每个类都自己提供一个无参构造。
还需要注意的是,子类对象的在创建构造函数时,必须首先执行父类的构造函数。在执行父类的构造函数之前执行任何代码都会报错!(其实这个说法不是很准确)
因为创建子类一定会先调用父类的构造函数,下图中的代码因为我将父类的无参构造给注释掉了,那么在 int x
前面就会自动执行无参构造 super(),而我在父类中只实现了有参构造那么他肯定会报错
那么实现了父类的无参构造就不会报错了吗?
答案是也会报错,按照我个人的理解是:因为在子类的构造函数一定会最先调用父类的构造方法再执行后面的操作,那么在 int x
前就一定会先执行 super()
,在前面提到构造方法只能调用一次,那么我们就不能再在后面使用有参构造了。
实际是编译器会提示 super()
的调用必须是构造函数主体中的第一语句
调用父类的方法
在子类的 setAge(int age)
中,调用了 super.setAge(int age)
则执行父类中的 setAge(int age)
方法
方法重写
子类可以继承父类中的成员函数,也可以对父类中的成员函数进行重写
比如,如果在 son 这个子类不写 setAge
函数,那么在创建 son 对象后可以调用父类的 setAge
函数
如果去掉注释,在 son 中再实现 setAge
函数,再执行
多态
- 多态的实现依赖于继承
- 父类引用指向子类对象(向上造型)
- 当子类重写父类对象的时候,通过该引用调用的是子类重写后的对象
向上造型
多用于方法的传参和返回值中,减少大量的方法重载
比如现在如果一个 Person 类、一个 student 类和一个 teacher 类
public class Person {
private int age;
private String name;
Person(int age)
{
this.age = age;
}
Person(){}
Person(String name) {
this.name = name;
}
public void setAge(int age)
{
System.out.println("Dad");
this.age = age;
}
public int getAge(){
return this.age;
}
public String getName() {
return this.name;
}
}
class teacher extends Person{
teacher(String name)
{
super(name);
}
}
class student extends Person{
int age;
student(){
System.out.println("student's init");
}
student(String name)
{
super(name);
}
}
现在如果有一个 test 类,类中定义一个函数需要传入 Person,student,teacher 中任一一个对象,然后输入该对象的 name,如果使用重载需要重载写三个函数体
如果使用多态则只用一个定义一个以父类对象传参的函数,即保留第一个函数即可。使用的时候只需要使父类的引用调用子类的对象。
但是向上造型之后该引用是无法使用子类扩展的功能,比如在 student 中添加一个 study 的函数
public void study()
{
System.out.println("I like study");
}
那么在 test 类中,我们是无法使用 s.study()
的。
如果需要用父类的引用调用子类的扩展功能,就需要向下转型
操作为 ((student)Person).study()
instanceof 关键字
向下转型是不安全的,如果向下转型的类型与实际类型不一致时,就会发生转型失败的异常。为了避免这个问题,需要使用 instanceof 关键字
在向下转型之前先判断,比如 Person s 向 student 转型之前
使用 if(s instanceof student)
判断,若 if 语句为真即 s 是 student 类或者 student 类的子类,则执行向下转型。
静态
- static 关键字修饰的成员(包括属性和方法),叫做类成员,也叫静态成员
- 与静态相对的是实例(非静态),即没有 static 修饰的变量和方法
- 静态成员属于类而不单独属于某个对象
- 类成员可以直接通过
类名.类成员
调用。
静态成员变量
类的成员变量可以分为以下两种:
- 静态变量(或称为类变量),指被 static 修饰的成员变量。
- 实例变量,指没有被 static 修饰的成员变量。
静态变量与实例变量的区别如下:
1)静态变量
- 运行时,Java 虚拟机只为静态变量分配一次内存,加载类过程中完成静态变量的内存分配。
- 在类的内部,可以在任何方法内直接访问静态变量。
- 在其他类中,可以通过类名访问该类中的静态变量。
2)实例变量
- 每创建一个实例,Java 虚拟机就会为实例变量分配一次内存。
- 在类的内部,可以在非静态方法中直接访问实例变量。
- 在本类的静态方法或其他类中则需要通过类的实例对象进行访问。
静态变量在类中的作用如下:
静态变量可以被类的所有实例共享,因此静态变量可以作为实例之间的共享数据,增加实例之间的交互性。
如果类的所有实例都包含一个相同的常量属性,则可以把这个属性定义为静态常量类型,从而节省内存空间。例如,在类中定义一个静态常量 PI。
静态方法
同成员变量,成员方法也可以分为以下两种:
静态方法(或称为类方法),指被 static 修饰的成员方法。
实例方法,指没有被 static 修饰的成员方法。
静态方法与实例方法的区别:
静态方法,属于类,而不属于类的对象。
1)它通过类直接被调用,无需创建类的对象。
2)静态方法中,不能使用 this 关键字,也不能直接访问所属类的实例变量和实例方法;
3)静态方法中,可以直接访问所属类的静态变量和静态方法。
4)同 this 关键字,super 关键字也与类的实例相关,静态方法中不能使用 super 关键字。
实例方法,可直接访问所属类的静态变量、静态方法、实例变量和实例方法。
静态方法与静态变量好处:
- 属于类级别,无需创建对象就即可直接使用,使用方便。
- 全局唯一,内存中唯一,静态变量可以唯一标识某些状态。
- 类加载时候初始化,常驻在内存,调用快捷方便。
静态方法与静态变量缺点:
- 静态方法不能调用非静态的方法和变量。
- 不能使用 this 和 super 关键字。
静态方法与静态变量适用场景:
- 静态方法,最适合工具类中方法的定义;比如文件操作,日期处理方法等。
- 静态方法,适合入口方法定义;比如单例模式,因从外部拿不到构造函数,所以定义一个静态的方法获取对象非常有必要。
- 静态变量适合全局变量的定义;举例:用一个布尔型静态成员变量做控制标志。
静态类
Java 中一个类要被声明为 static 的,只有一种情况,就是静态内部类(内嵌类)。如在外部类声明为 static 的,程序会编译都不会通过。
- 静态内部类,跟静态方法一样,只能访问静态成员变量和方法,不能访问非静态方法和属性。
- 普通内部类,可以访问任意外部类的成员变量和方法。
- 静态内部类,可以声明普通成员变量和方法,而普通内部类不能声明 static 成员变量和方法。
- 静态内部类,可以单独初始化。
Inner i = new Outer.Inner ();
普通内部类初始化:
Outer o = new Outer ();
Inner i = o.new Inner ();
适用场景:当外部类需要使用内部类,而内部类无需外部类资源,并且内部类可以单独创建时,考虑采用静态内部类的设计,在知道如何初始化静态内部类。
Public class Outer {
Private String name;
Private int age;
public static class Builder {
private String name;
private int age;
public Builder(int age) {
this.age = age;
}
public Builder withName(String name) {
this.name = name;
return this;
}
public Builder withAge(int age) {
this.age = age;
return this;
}
public Outer build() {
return new Outer(this);
}
}
private Outer(Builder b) {
this.age = b.age;
this.name = b.name;
}
}
静态内部类,调用外部类的构造函数来构造外部类;静态内部类可被单独初始化,于是在外部就可以有下面的实现:
Public Outer getOuter ()
{
Outer outer = new Outer.Builder (2). WithName ("Yang Liu"). Build ();
Return outer;
}
初始化块
- 初始化块会在创建对象的时候自动调用,先于构造构造方法,后于成员变量初始化
- 一般用来执行一些默认的操作
- 初始化块分为普通初始化块和静态初始化块
- 静态变量的初始化以及静态代码块只会在第一次示例化对象的时候执行,后续再实例化对象不会执行
执行顺序说明:
静态成员变量的初始化 >> 静态初始化块 >> 普通成员变量的初始化 >> 普通初始化块 >> 构造函数
静态变量的初始化以及静态代码块只会在第一次示例化的
public class test {
public static void main(String[] args) {
test t1 = new test();
System.out.println("------------");
test t2 = new test();
}
String ts()
{
System.out.println("成员变量初始化");
return "test";
}
static String init()
{
System.out.println("静态变量初始化");
return "static test";
}
test() {
System.out.println("无参构造函数执行");
}
//普通代码块
{
System.out.println("普通代码块执行");
}
//静态代码块
static {
System.out.println("静态代码块执行");
}
//成员变量初始化
String name = ts();
//静态变量初始化
static String info = init();
}
执行效果:
final关键字
在Java中,final是一个关键字,可以用来修饰类、方法和变量。
- final修饰类:被final修饰的类是最终类,不能被继承。例如:
final class FinalClass {
// 类定义
}
这样的类不能被其他类所继承。
- final修饰方法:被final修饰的方法是最终方法,不能被子类重写。例如:
class ParentClass {
final void finalMethod() {
// 方法定义
}
}
class ChildClass extends ParentClass {
// 这里不能重写finalMethod方法
}
- final修饰变量:被final修饰的变量是一个常量,只能被赋值一次,不能再被修改。例如:
final int number = 10;
一旦给final变量赋值后,就不能再对其进行修改。
需要注意的是,当final修饰的变量是一个引用类型时,该变量的引用地址不可变,但是引用对象的状态是可以被修改的。换句话说,final修饰的变量只保证其引用不发生改变,但是引用对象的内容可以改变。
final关键字在Java中有以下几个使用场景:
- 将类、方法、变量标记为不可改变的,以增加代码的可读性和可维护性。
- 用于性能优化,当编译器确定某个值在运行时始终保持不变时,可以进行一些优化操作。
- 防止子类修改父类的实现,以确保代码的正确性和稳定性。
Object类
所有类都默认继承Object,除非手动指定继承的类型,但是依然改变不了顶层父类是Object的类,所有类都包含Object类中的方法,但是对于Object中默认的方法在实际使用时需要进行重写
Object类中包含的方法如下:
public class Object {
private static native void registerNatives(); //标记为native的方法是本地方法,底层是由C++实现的
static {
registerNatives(); //这个类在初始化时会对类中其他本地方法进行注册,本地方法不是我们SE中需要学习的内容,我们会在JVM篇视频教程中进行介绍
}
//获取当前的类型Class对象,这个我们会在最后一章的反射中进行讲解,目前暂时不会用到
public final native Class<?> getClass();
//获取对象的哈希值,我们会在第五章集合类中使用到,目前各位小伙伴就暂时理解为会返回对象存放的内存地址
public native int hashCode();
//判断当前对象和给定对象是否相等,默认实现是直接用等号判断,也就是直接判断是否为同一个对象
public boolean equals(Object obj) {
return (this == obj);
}
//克隆当前对象,可以将复制一个完全一样的对象出来,包括对象的各个属性
protected native Object clone() throws CloneNotSupportedException;
//将当前对象转换为String的形式,默认情况下格式为 完整类名@十六进制哈希值
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
//唤醒一个等待当前对象锁的线程
public final native void notify();
//唤醒所有等待当前对象锁的线程,同上
public final native void notifyAll();
//使得持有当前对象锁的线程进入等待状态,同上
public final native void wait(long timeout) throws InterruptedException;
//同上
public final void wait(long timeout, int nanos) throws InterruptedException {
...
}
//同上
public final void wait() throws InterruptedException {
...
}
//当对象被判定为已经不再使用的“垃圾”时,在回收之前,会由JVM来调用一次此方法进行资源释放之类的操作,这同样不是SE中需要学习的内容,这个方法我们会在JVM篇视频教程中详细介绍,目前暂时不会用到
protected void finalize() throws Throwable { }
}
在使用 equals 和 toString 方法时,通常需要对方法重写才能达到预期的效果
例如一个 Student 类定义如下
class Student
{
private int id;
private String name;
private int age;
}
如果想通过 toString() 打印一个 Student 的对象的信息,那么就需要重写
class Student
{
private int id;
private String name;
private int age;
public String toString()
{
return "Student{" +
"id=" + id +
", name='"+ name+ '\'' +
", age=" + age +
"}";
}
如果需要通过 equals 方法去对比两个 Student 对象是否相等,就需要重写 equals 方法
public boolean equals(Object obj) {
if(obj == null) return false;
if(obj instanceof Student stu)
{
return this.age == stu.age &&
this.id == stu.id &&
this.name.equals(stu.name);
}
else return false;
}
抽象类
- 抽象类中可以有抽象方法,也可以有非抽象方法
- 抽象类可以有构造方法,但是抽象类是无法实例化的
- 子类继承抽象类需要实现抽象类中声明的所有方法
- 抽象类一般作继承使用,抽象类的子类也可以是抽象类
- 抽象方法不能被 private 修饰
public abstract class Test {
public abstract void test01();//抽象方法
//非抽象方法
public void test02() {
System.out.println("test");
}
}
接口类
接口的声明
public interface Study{ //使用interface表示这是一个接口
void study();
}
接口的继承
public class Student extends Person implements Study { //使用implements关键字来实现接口
public Student(String name, int age, String sex) {
super(name, age, sex, "学生");
}
@Override
public void study() { //实现接口时,同样需要将接口中所有的抽象方法全部实现
System.out.println("我会学习!");
}
}
接口中只定义访问权限为 public 抽象方法,其中 public 和 abstract 关键字可以省略
接口不同于继承,接口可以同时继承多个,不同的接口用逗号隔开
接口不能直接创建对象,但是可以将类的对象以接口的形式去使用,作接口使用时,只能使用接口中定义的方法和Object类的方法,无法调用类本身的方法和父类方法
接口支持向下转型
public static void main(String[] args) { Study study = new Teacher("小王", 27, "男"); if(study instanceof Teacher) { //直接判断引用的对象是不是Teacher类型 Teacher teacher = (Teacher) study; //强制类型转换 teacher.study(); } }
接口中可以存在方法的默认实现,如果该方法默认实现了在实现类中就不强制要求进行实现了
public interface Study { void study(); default void test() { //使用default关键字为接口中的方法添加默认实现 System.out.println("我是默认实现"); } }
接口中不允许存在成员变量和成员方法,但是可以存在静态变量和静态方法,静态变量只能是被
public static final
修饰的接口中的静态内容可以使用
接口名.静态内容
的方式去调用接口可以继承自其他接口,且继承的数量没有限制
克隆方法可见性提升
这个需要使用接口实现,Object提供的克隆方法如下:
package java.lang;
public interface Cloneable { //这个接口中什么都没定义
}
实现接口后,我们还需要把克隆方法的可见提升一下,否则用不了
public class Student extends Person implements Study, Cloneable { //首先实现Cloneable接口,表示这个类具有克隆的功能
public Student(String name, int age, String sex) {
super(name, age, sex, "学生");
}
@Override
public Object clone() throws CloneNotSupportedException { //提升clone方法的访问权限
return super.clone(); //因为底层是C++实现,我们直接调用父类的实现就可以了
}
@Override
public void study() {
System.out.println("我会学习!");
}
}
克隆的使用:
public static void main(String[] args) throws CloneNotSupportedException { //这里向上抛出一下异常,还没学异常,所以说照着写就行了
Student student = new Student("小明", 18, "男");
Student clone = (Student) student.clone(); //调用clone方法,得到一个克隆的对象
System.out.println(student);
System.out.println(clone);
System.out.println(student == clone);
}
可以发现,原对象和克隆对象,是两个不同的对象,但是他们的各种属性都是完全一样的:
克隆分为浅拷贝和深拷贝,原理和C++的差不多,java提供的拷贝方法只是浅拷贝,浅拷贝会复制基本数据类型给拷贝对象,但是引用引用对象复制的是对象的地址,实际指向的还是原来的对象中的内容。
枚举类
基本包装类
Java中的基本类型,如果想通过对象的形式去使用他们,Java提供的基本类型包装类,使得Java能够更好的体现面向对象的思想,同时也使得基本类型能够支持对象操作!
其中能够表示数字的基本类型包装类,继承自Number类,对应关系如下表:
- byte -> Byte
- boolean -> Boolean
- short -> Short
- char -> Character
- int -> Integer
- long -> Long
- float -> Float
- double -> Double
包装类实际上就是把我们的基础数据类型,封装成一个(运用了封装的思想)Integer类中是怎么写的:
private final int value; //类中实际上就靠这个变量在存储包装的值
public Integer(int value) {
this.value = value;
}
包装类支持自动装箱,以及自动拆箱,有了这两个特性,就可以让包装类型参与到基本类型的运算中
//自动装箱 public static void main(String[] args) { Integer i = 10; //将int类型值作为包装类型使用 } //自动装箱的实质 public static void main(String[] args) { Integer i = Integer.valueOf(10); //上面的写法跟这里是等价的 } //自动拆箱 public static void main(String[] args) { Integer i = 10; int a = i; } //自动拆箱的实质 public static void main(String[] args) { Integer i = 10; int a = i.intValue(); //通过此方法变成基本类型int值 } //包装类参与基本运算 public static void main(String[] args) { Integer a = 10, b = 20; int c = a * b; //直接自动拆箱成基本类型参与到计算中 System.out.println(c); }
包装类是一个类,不是基本类型,两个不同的对象,即便值相等但实际上也是不相等的
IntegerCache会默认缓存-128~127之间的所有值,如果直接让-128 ~ 127之间的值自动装箱为Integer类型的对象,那么始终都会得到同一个对象
特殊包装类
BigInteger
BigInteger可以用于计算超大数字,用法如下:
public static void main(String[] args) {
BigInteger i = BigInteger.valueOf(Long.MAX_VALUE); //表示Long的最大值,轻轻松松
System.out.println(i);
}
使用BigInteger计算时,使用类中的方法即可,比如计算long的最大值的一百次方
public static void main(String[] args) {
BigInteger i = BigInteger.valueOf(Long.MAX_VALUE);
i = i.pow(100); //long的最大值来个100次方吧
System.out.println(i);
}
BigDecimal
BigDecimal可用于小数的精确计算
public static void main(String[] args) {
BigDecimal i = BigDecimal.valueOf(10);
i = i.divide(BigDecimal.valueOf(3), 100, RoundingMode.CEILING);
//计算10/3的结果,精确到小数点后100位
//RoundingMode是舍入模式,就是精确到最后一位时,该怎么处理,这里CEILING表示向上取整
System.out.println(i);
}
正则表达式
用于规定给定组件必须要出现多少次才能满足匹配的,我们一般称为限定符,限定符表如下:
字符 | 描述 |
---|---|
* | 匹配前面的子表达式零次或多次。例如,zo* 能匹配 "z" 以及 "zoo" 。***** 等价于 {0,} 。 |
+ | 匹配前面的子表达式一次或多次。例如,zo+ 能匹配 "zo" 以及 "zoo" ,但不能匹配 "z" 。 + 等价于 {1,} 。 |
? | 匹配前面的子表达式零次或一次。例如,do(es)? 可以匹配 "do" 、 "does" 、 "doxy" 中的 "do" 。 ? 等价于 {0,1} 。 |
{n} | n 是一个非负整数。匹配确定的 n 次。例如,o{2} 不能匹配 "Bob" 中的 o,但是能匹配 "food" 中的两个 o。 |
{n,} | n 是一个非负整数。至少匹配n 次。例如,o{2,} 不能匹配 "Bob" 中的 o,但能匹配 "foooood" 中的所有 o。o{1,} 等价于 o+ 。o{0,} 则等价于 o* 。 |
{n,m} | m 和 n 均为非负整数,其中 n <= m。最少匹配 n 次且最多匹配 m 次。例如,o{1,3} 将匹配 "fooooood" 中的前三个 o。o{0,1} 等价于 o? 。请注意在逗号和两个数之间不能有空格。 |
如果我们想要表示一个范围内的字符,可以使用方括号:
public static void main(String[] args) {
String str = "abcabccaa";
System.out.println(str.matches("[abc]*")); //表示abc这几个字符可以出现 0 - N 次
}
对于普通字符来说,我们可以下面的方式实现多种字符匹配:
字符 | 描述 |
---|---|
[ABC] | 匹配 [...] 中的所有字符,例如 [aeiou] 匹配字符串 "google runoob taobao" 中所有的 e o u a 字母。 |
[^ABC] | 匹配除了 [...] 中字符的所有字符,例如 [^aeiou] 匹配字符串 "google runoob taobao" 中除了 e o u a 字母的所有字母。 |
[A-Z] | [A-Z] 表示一个区间,匹配所有大写字母,[a-z] 表示所有小写字母。 |
. | 匹配除换行符(\n、\r)之外的任何单个字符,相等于 [^\n\r] |
[\s\S] | 匹配所有。\s 是匹配所有空白符,包括换行,\S 非空白符,不包括换行。 |
\w | 匹配字母、数字、下划线。等价于 [A-Za-z0-9_] |
当然,这里仅仅是对正则表达式的简单使用,实际上正则表达式内容非常多,如果需要完整学习正则表达式,可以到:https://www.runoob.com/regexp/regexp-syntax.html
成员内部类
在类的内部定义的类就是成员内部类:
public class Test {
public class Inner { //内部类也是类,所以说里面也可以有成员变量、方法等,甚至还可以继续套娃一个成员内部类
public void test(){
System.out.println("我是成员内部类!");
}
}
}
成员内部类和成员方法、成员变量一样,都是属于对象的,使用的时候需要先创建对象
public static void main(String[] args) { Test test = new Test(); //我们首先需要创建对象 Test.Inner inner = test.new Inner(); //成员内部类的类型名称就是 外层.内部类名称 } //使用成员内部类中的方法: public static void main(String[] args) { Test test = new Test(); Test.Inner inner = test.new Inner(); inner.test(); }
内部是可以访问外层的变量的,但是外部是不可以直接访问内部的成员变量(除了静态变量),需要先实例化内部类,才能使用
public class Test { private final String name; public Test(String name){ this.name = name; } public class Inner { public void test(){ System.out.println("我是成员内部类:"+name); //成员内部类可以访问到外部的成员变量 //因为成员内部类本身就是某个对象所有的,每个对象都有这样的一个类定义,这里的name是其所依附对象的 } } }
每个类可以创建一个对象,每个对象中都有一个单独的类定义,可以通过这个成员内部类又创建出更多对象,套娃了属于是。
所以说我们在使用时:
public static void main(String[] args) {
Test a = new Test("小明");
Test.Inner inner1 = a.new Inner(); //依附于a创建的对象,那么就是a的
inner1.test();
Test b = new Test("小红");
Test.Inner inner2 = b.new Inner(); //依附于b创建的对象,那么就是b的
inner2.test();
}
那现在问大家一个问题,外部能访问内部类里面的成员变量吗?
那么如果内部类中也定义了同名的变量,此时我们怎么去明确要使用的是哪一个呢?
public class Test {
private final String name;
public Test(String name){
this.name = name;
}
public class Inner {
String name;
public void test(String name){
System.out.println("方法参数的name = "+name); //依然是就近原则,最近的是参数,那就是参数了
System.out.println("成员内部类的name = "+this.name); //在内部类中使用this关键字,只能表示内部类对象
System.out.println("成员内部类的name = "+Test.this.name);
//如果需要指定为外部的对象,那么需要在前面添加外部类型名称
}
}
}
包括对方法的调用和super关键字的使用,也是一样的:
public class Inner {
String name;
public void test(String name){
this.toString(); //内部类自己的toString方法
super.toString(); //内部类父类的toString方法
Test.this.toString(); //外部类的toSrting方法
Test.super.toString(); //外部类父类的toString方法
}
}
所以说成员内部类其实在某些情况下使用起来比较麻烦,对于这种成员内部类,我们一般只会在类的内部自己使用。
静态内部类
//创建一个静态内部类Inner
public class Test {
private final String name;
public Test(String name){
this.name = name;
}
public static class Inner {
public void test(){
System.out.println("我是静态内部类!");
}
}
}
//实例化一个内部类
public static void main(String[] args) {
Test.Inner inner = new Test.Inner(); //静态内部类的类名同样是之前的格式,但是可以直接new了
inner.test();
}
静态内部类是无法直接访问外部非静态内容
匿名内部类
一种局部内部类的简化版,一般用于抽象类以及接口的实例化过程
//抽象类
public abstract class Student {
public abstract void test();
}
//在方法中使用 匿名内部类,将其中的抽象方法实现,并直接创建实例对象
public static void main(String[] args) {
Student student = new Student() { //在new的时候,后面加上花括号,把未实现的方法实现了
@Override
public void test() {
System.out.println("我是匿名内部类的实现!");
}
};
student.test();
}
Lambda表达式
如果一个接口中有且只有一个待实现的抽象方法,那么我们可以将匿名内部类简写为Lambda表达式
- 标准格式为:
([参数类型 参数名称,]...) ‐> { 代码语句,包括返回值 }
- 和匿名内部类不同,Lambda仅支持接口,不支持抽象类
- 接口内部必须有且仅有一个抽象方法(可以有多个方法,但是必须保证其他方法有默认实现,必须留一个抽象方法出来)
注意,如果方法体中只有一个返回语句,可以直接省去花括号和return
关键字:
Study study = (a) -> {
return "今天学会了"+a; //这种情况是可以简化的
};
Study study = (a) -> "今天学会了"+a;
如果参数只有一个,那么可以省去小括号:
Study study = a -> "今天学会了"+a;
方法引用