Java知识点:泛型、类加载器、内部类、可变参数

发布时间:2024年01月06日

1、this关键字

this关键字常见用法和含义:

(1)引用当前对象的成员变量:当类的成员变量与方法的参数或局部变量同名时,使用 this 关键字可以明确地指示要引用的是成员变量。例如:

public class Person {
    private String name;

    public void setName(String name) {
        this.name = name;
    }
}

在上面的例子中,this.name 引用了类的成员变量 name,而 name 是方法的参数。这样可以区分变量名,避免歧义。

局部变量:方法内
成员变量:方法外

this的作用:区分局部变量和成员变量
this的本质:代表方法调用者的地址值

如果不使用this关键字,则变量遵循就近原则,即谁离得近就使用谁的值。

(2)在构造方法中调用其他构造方法:在一个类的构造方法中,可以使用 this 关键字来调用同一个类的其他构造方法。这样可以避免代码重复。例如:

public class Person {
    private String name;
    private int age;

    public Person() {
        this("John Doe", 30); // 调用另一个构造方法
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

在上面的例子中,无参构造方法 Person() 使用 this("John Doe", 30) 调用有参构造方法 Person(String name, int age)

(3)返回当前对象:在方法中,可以使用 this 关键字来返回当前对象的引用,这在实现方法链式调用时很常见。例如:

public class Person {
    private String name;
    private int age;

    public Person setName(String name) {
        this.name = name;
        return this;
    }

    public Person setAge(int age) {
        this.age = age;
        return this;
    }
}

当在一个方法中使用 return this; 语句时,它表示将当前对象作为方法的返回值返回。在上面的代码中,setName()setAge() 方法都返回 Person 对象,以便允许方法的连续调用。使用这种方法,可以以一种链式的方式设置对象的属性,如 :

Person person = new Person()
    .setName("John")
    .setAge(30);

2、泛型

2.1 泛型介绍

泛型是JDK5中引入的特性,它提供了编译时类型安全检测机制

(1)泛型的好处

  • 把运行时期的问题提前到了编译期间
  • 避免了强制类型转换

(2)泛型的定义格式

  • <类型>:指定一种类型的格式,尖括号里面可以任意书写,一般只写一个字母。例如:
  • <类型1,类型2…>:指定多种类型的格式,多种类型之间用逗号隔开。例如:<E,T> <K,V>

(3)泛型的作用

如果我们没有给集合指定类型,默认认为所有的数据类型都是Object类型,此时可以往集合添加任意的数据类型。这样带来一个坏处:我们在获取数据的时候,无法使用他的特有行为。

此时推出了泛型,可以在添加数据的时候就把类型进行统一,而且我们在获取数据的时候,也省的强转了,非常的方便。

(4)注意事项

  • 泛型中不能写基本数据类型
  • 指定泛型的具体类型后,传递数据时,可以传入该类类型或者其子类类型
  • 如果不写泛型,类型默认是Object
  • 迭代器的泛型和集合的泛型要保持一致
  • Java中的泛型是伪泛型,编译为字节码文件后会当做Object处理(泛型的擦除

2.2 泛型分类定义

2.2.1 泛型类

(1)泛型类:在类名后面定义泛型,创建该类对象的时候,确定类型。

修饰符 class 类名<类型>{
}

public class ArrayList<E>{
}

(2)代码示例:

定义泛型类

public class MyArrayList<E> {

    // 指定集合默认初始化的长度为10
    Object[] obj = new Object[10];
    int size;

    /**
     * E : 表示是不确定的类型。该类型在类名后面已经定义过了。
     * e:形参的名字,变量名
     */
    public boolean add(E e){
        obj[size] = e;
        size++;
        return true;
    }

    public E get(int index){
        return (E)obj[index];
    }

    @Override
    public String toString() {
        return Arrays.toString(obj);
    }
}

泛型类测试

public class GenericsDemo02 {
    public static void main(String[] args) {
        MyArrayList<String> list = new MyArrayList<>();

        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        System.out.println(list.size);
        System.out.println(list);

        MyArrayList<Integer> list2 = new MyArrayList<>();
        list2.add(123);
        list2.add(456);
        list2.add(789);

        int i = list2.get(0);
        System.out.println(i);

        System.out.println(list2);
    }
}

image-20240102161016957

2.2.2 泛型方法

(1)泛型方法:在修饰符后面定义方法,调用该方法的时候,确定类型。

修饰符<类型> 返回值类型 方法名(类型变量名){
}

public<T> void show(T t){
}

public static<T> void show(T t){
}

(2)代码示例

泛型方法

public class ListUtil {
    private ListUtil(){}

    /**
     * 泛型方法
     * 类中定义一个静态方法addAll,用来添加多个集合的元素。
     * 参数一:集合
     * 参数二~最后:要添加的元素
     */
    public static<E> void addAll(ArrayList<E> list, E e1, E e2, E e3, E e4){
        list.add(e1);
        list.add(e2);
        list.add(e3);
        list.add(e4);
    }

/*    public static<E> void addAll2(ArrayList<E> list, E...e){
        for (E element : e) {
            list.add(element);
        }
    }*/
}

泛型方法测试

public class GenericsDemo03 {
    public static void main(String[] args) {

        ArrayList<String> list1 = new ArrayList<>();
        ListUtil.addAll(list1, "aaa", "bbb", "ccc", "ddd");
        System.out.println(list1);

        ArrayList<Integer> list2 = new ArrayList<>();
        ListUtil.addAll(list2,1,2,3,4);
        System.out.println(list2);
    }
}

image-20240102161300007

2.2.3 泛型接口

(1)泛型接口:在接口名后面定义泛型,实现类确定类型,实现类延续泛型。

定义

修饰符 interface 接口名<类型>{
}

public interface List<E>{
}

使用

// 实现类给出具体的类型
public class MyList implements List<String> {
}
// 实现类延续泛型,创建实现类对象时再确定类型
public class MyList2<E> implements List<E> {
}

(2)代码示例

实现类:实现类延续泛型,创建实现类对象时再确定类型

public class MyArrayList3<E> implements List<E> {
    @Override
    public int size() {
        return 0;
    }

    @Override
    public boolean isEmpty() {
        return false;
    }

    @Override
    public boolean contains(Object o) {
        return false;
    }

    @Override
    public Iterator<E> iterator() {
        return null;
    }

    @Override
    public Object[] toArray() {
        return new Object[0];
    }

    @Override
    public <T> T[] toArray(T[] a) {
        return null;
    }

    @Override
    public boolean add(E e) {
        return false;
    }

    @Override
    public boolean remove(Object o) {
        return false;
    }

    @Override
    public boolean containsAll(Collection<?> c) {
        return false;
    }

    @Override
    public boolean addAll(Collection<? extends E> c) {
        return false;
    }

    @Override
    public boolean addAll(int index, Collection<? extends E> c) {
        return false;
    }

    @Override
    public boolean removeAll(Collection<?> c) {
        return false;
    }

    @Override
    public boolean retainAll(Collection<?> c) {
        return false;
    }

    @Override
    public void clear() {

    }

    @Override
    public E get(int index) {
        return null;
    }

    @Override
    public E set(int index, E element) {
        return null;
    }

    @Override
    public void add(int index, E element) {

    }

    @Override
    public E remove(int index) {
        return null;
    }

    @Override
    public int indexOf(Object o) {
        return 0;
    }

    @Override
    public int lastIndexOf(Object o) {
        return 0;
    }

    @Override
    public ListIterator<E> listIterator() {
        return null;
    }

    @Override
    public ListIterator<E> listIterator(int index) {
        return null;
    }

    @Override
    public List<E> subList(int fromIndex, int toIndex) {
        return null;
    }
}

实现类:实现类给出具体的类型

public class MyArrayList2 implements List<String> {
    @Override
    public int size() {
        return 0;
    }

    @Override
    public boolean isEmpty() {
        return false;
    }

    @Override
    public boolean contains(Object o) {
        return false;
    }

    @Override
    public Iterator<String> iterator() {
        return null;
    }

    @Override
    public Object[] toArray() {
        return new Object[0];
    }

    @Override
    public <T> T[] toArray(T[] a) {
        return null;
    }

    @Override
    public boolean add(String s) {
        return false;
    }

    @Override
    public boolean remove(Object o) {
        return false;
    }

    @Override
    public boolean containsAll(Collection<?> c) {
        return false;
    }

    @Override
    public boolean addAll(Collection<? extends String> c) {
        return false;
    }

    @Override
    public boolean addAll(int index, Collection<? extends String> c) {
        return false;
    }

    @Override
    public boolean removeAll(Collection<?> c) {
        return false;
    }

    @Override
    public boolean retainAll(Collection<?> c) {
        return false;
    }

    @Override
    public void clear() {

    }

    @Override
    public String get(int index) {
        return null;
    }

    @Override
    public String set(int index, String element) {
        return null;
    }

    @Override
    public void add(int index, String element) {

    }

    @Override
    public String remove(int index) {
        return null;
    }

    @Override
    public int indexOf(Object o) {
        return 0;
    }

    @Override
    public int lastIndexOf(Object o) {
        return 0;
    }

    @Override
    public ListIterator<String> listIterator() {
        return null;
    }

    @Override
    public ListIterator<String> listIterator(int index) {
        return null;
    }

    @Override
    public List<String> subList(int fromIndex, int toIndex) {
        return null;
    }
}

实现类对象

public class GenericsDemo04 {
    public static void main(String[] args) {
        /**
         * 泛型接口的两种使用方式:
         * 1.实现类给出具体的类型
         * 2.实现类延续泛型,创建实现类对象时再确定类型
         */
        
        // 实现类给出具体的类型
        MyArrayList2 list = new MyArrayList2();
        
        // 实现类延续泛型,创建实现类对象时再确定类型
        MyArrayList3<String> list2 = new MyArrayList3<>();
        MyArrayList3<Integer> list3 = new MyArrayList3<>();
        
        // 之后的操作和操作普通list类似,只不过实现类中还没实现具体的方法
    }
}

2.3 泛型通配符

需求:定义一个方法,方法的参数类型为不确定类型,但是希望只能传递Ye Fu Zi类型的数据,不能传递Student类型的数据

(1)仅使用泛型,不使用泛型通配符时

泛型里面写的是什么类型,那么只能传递什么类型的数据。而如果泛型方法设置为类型为T时,就又可以接受任意的数据类型,不符合要求。

(2)使用泛型通配符可以解决这个需求

 ? 不仅可以表示不确定的类型,还可以进行类型的限定
 ? extends E: 表示可以传递E或者E所有的子类类型
 ? super E:表示可以传递E或者E所有的父类类型

(3)泛型通配符应用场景:

  • 如果我们在定义类、方法、接口的时候,如果类型不确定,就可以定义泛型类、泛型方法、泛型接口。
  • 如果类型不确定,但是能知道以后只能传递某个继承体系中的,就可以泛型的通配符
    泛型的通配符。

关键点:可以限定类型的范围。

(4)代码示例

public class GenericsDemo06 {
    public static void main(String[] args) {
        //创建集合的对象
        ArrayList<Ye> list1 = new ArrayList<>();
        ArrayList<Fu> list2 = new ArrayList<>();
        ArrayList<Zi> list3 = new ArrayList<>();
        ArrayList<Student> list4 = new ArrayList<>();

        method(list1);
        method(list2);
        //method(list3);	// 报错
        //method(list4);	// 报错
    }
    // 可以传递Fu及Fu的所有父类类型
    public static void method(ArrayList<? super Fu> list) {

    }
}

class Ye {
}

class Fu extends Ye {
}

class Zi extends Fu {
}

class Student{}

3、可变参数

格式:

属性类型...名字
int...args
  • 可变参数底层就是一个数组
  • 在方法的形参中最多只能写一个可变参数
  • 在方法的形参当中,如果出了可变参数以外,还有其他的形参,那么可变参数要写在最后
public class ArgsDemo3 {
    public static void main(String[] args) {
        int sum = getSum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        System.out.println(sum);
    }
    public static int getSum(int...args){
        int sum = 0;
        for (int i : args) {
            sum = sum + i;
        }
        return sum;
    }
}

image-20240102170919197

4、日志

4.1 使用步骤

  • 把第三方的代码导入到当前的项目当中

    • 新建lib文件夹,把jar粘贴到lib文件夹当中,全选后右键点击选择add as library

    • 检测导入成功:导入成功后jar包可以展开。在项目重构界面可以看到导入的内容

  • 把配置文件粘贴到src文件夹下

  • 在代码中获取日志对象

  • 调用方法打印日志

4.2 日志级别

TRACE, DEBUG, INFO, WARN, ERROR

还有两个特殊的:

  • ALL:输出所有日志
  • OFF:关闭所有日志

日志级别从小到大的关系:TRACE < DEBUG < INFO < WARN < ERROR

4.3 配置文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!--
        CONSOLE :表示当前的日志信息是可以输出到控制台的。
    -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!--输出流对象 默认 System.out 改为 System.err-->
        <target>System.out</target>
        <encoder>
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度
                %msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level]  %c [%thread] : %msg%n</pattern>
        </encoder>
    </appender>

    <!-- File是输出的方向通向文件的 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            <charset>utf-8</charset>
        </encoder>
        <!--日志输出路径-->
        <file>C:/code/itheima-data.log</file>
        <!--指定日志文件拆分和压缩规则-->
        <rollingPolicy
                       class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--通过指定压缩文件名称,来确定分割文件方式-->
            <fileNamePattern>C:/code/itheima-data2-%d{yyyy-MMdd}.log%i.gz</fileNamePattern>
            <!--文件拆分大小-->
            <maxFileSize>1MB</maxFileSize>
        </rollingPolicy>
    </appender>

    <!--

    level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF
   , 默认debug
    <root>可以包含零个或多个<appender-ref>元素,标识这个输出位置将会被本日志级别控制。
    -->
    <root level="info">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE" />
    </root>
</configuration>

5、类加载器

5.1 类加载器

作用:负责将.class文件(存储的物理文件)加载在到内存中

01_类加载器

5.2 类加载的完整过程

5.2.1 类加载时机

简单理解:字节码文件什么时候会被加载到内存中?

有以下的几种情况:

  • 创建类的实例(对象)
  • 调用类的类方法
  • 访问类或者接口的类变量,或者为该类变量赋值
  • 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
  • 初始化某个类的子类
  • 直接使用java.exe命令来运行某个主类

总结而言:用到了就加载,不用不加载

5.2.2 类加载过程

(1)加载

  • 通过包名 + 类名,获取这个类,准备用流进行传输
  • 在这个类加载到内存中
  • 加载完毕创建一个class对象

02_类加载过程加载

(2)链接

  • 验证

    确保Class文件字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身安全

    (文件中的信息是否符合虚拟机规范有没有安全隐患)

03_类加载过程验证

  • 准备

    负责为类的类变量(被static修饰的变量)分配内存,并设置默认初始化值

    (初始化静态变量)

04_类加载过程准备

  • 解析

    将类的二进制数据流中的符号引用替换为直接引用

    (本类中如果用到了其他类,此时就需要找到对应的类)

05_类加载过程解析

(3)初始化

根据程序员通过程序制定的主观计划去初始化类变量和其他资源

(静态变量赋值以及初始化其他资源)

06_类加载过程初始化

  • 当一个类被使用的时候,才会加载到内存
  • 类加载的过程: 加载、验证、准备、解析、初始化

5.3 类加载的分类

(1)分类

  • Bootstrap class loader:虚拟机的内置类加载器,通常表示为null ,并且没有父null
  • Platform class loader:平台类加载器,负责加载JDK中一些特殊的模块
  • System class loader:系统类加载器,负责加载用户类路径上所指定的类库

(2)类加载器的继承关系

  • System的父加载器为Platform
  • Platform的父加载器为Bootstrap

(3)代码演示

public class ClassLoaderDemo1 {
    public static void main(String[] args) {
        //获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();

        //获取系统类加载器的父加载器 --- 平台类加载器
        ClassLoader classLoader1 = systemClassLoader.getParent();

        //获取平台类加载器的父加载器 --- 启动类加载器
        ClassLoader classLoader2 = classLoader1.getParent();

        System.out.println("系统类加载器" + systemClassLoader);
        System.out.println("平台类加载器" + classLoader1);
        System.out.println("启动类加载器" + classLoader2);
    }
}

5.4 双亲委派模型

(1)介绍

如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式

image-20240106132522927

5.5 ClassLoader 中的两个方法

(1)方法介绍

方法名说明
public static ClassLoader getSystemClassLoader()获取系统类加载器
public InputStream getResourceAsStream(String name)加载某一个资源文件

(2)示例代码

public class ClassLoaderDemo2 {
    public static void main(String[] args) throws IOException {
        //static ClassLoader getSystemClassLoader() 获取系统类加载器
        //InputStream getResourceAsStream(String name)  加载某一个资源文件

        //获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();

        //利用加载器去加载一个指定的文件
        //参数:文件的路径(放在src的根目录下,默认去那里加载)
        //返回值:字节流。
        InputStream is = systemClassLoader.getResourceAsStream("prop.properties");

        Properties prop = new Properties();
        prop.load(is);

        System.out.println(prop);

        is.close();
    }
}

6、注解

6.1 注释和注解的区别

共同点:都可以对程序进行解释说明。

不同点:

  • 注释,是给程序员看的。只在Java中有效。在class文件中不存在注释的。当编译之后,会进行注释擦除。
  • 注解,是给虚拟机看的。当虚拟机看到注解之后,就知道要做什么事情了。

6.2 如何使用注解

举例:子类重写父类方法的时候,在重写的方法上面写@Override。当虚拟机看到@Override的时候,就知道下面的方法是重写的父类的。检查语法,如果语法正确编译正常,如果语法错误,就会报错。

6.3 Java中已经存在的注解

@Override:表示方法的重写
@Deprecated:表示修饰的方法已过时
@SuppressWarnings(“all”):压制警告

除此之外,还需要掌握第三方框架中提供的注解,比如在Junit中:

@Test 表示运行测试方法
@Before 表示在Test之前运行,进行数据的初始化
@After 表示在Test之后运行,进行数据的还原

6.4 自定义注解

自定义注解单独存在是没有什么意义的,一般会跟反射结合起来使用,会用发射去解析注解。

关于注解的解析,一般是在框架的底层已经写好了。

6.5 特殊属性

value:当注解中只有“一个属性“,并且属性名是“value“,使用注解时,可以省略value属性名。

//注解的定义
public @interface Anno2 {
    public String value();
    public int age() default 23;
}

//注解的使用
@Anno2("123")
public class AnnoDemo2 {
    @Anno2("123")
    public void method(){
    }
}

6.6 元注解

元注解:可以写在注解上面的注解,如:

  • @Target :指定注解能在哪里使用
  • @Retention :可以理解为保留时间(生命周期)

(1)Target

? 作用:用来标识注解使用的位置,如果没有使用该注解标识,则自定义的注解可以使用在任意位置。

? 可使用的值定义在ElementType枚举类中,常用值如下

  • TYPE,类,接口
  • FIELD, 成员变量
  • METHOD, 成员方法
  • PARAMETER, 方法参数
  • CONSTRUCTOR, 构造方法
  • LOCAL_VARIABLE, 局部变量

(2)Retention

? 作用:用来标识注解的生命周期(有效范围)

? 可使用的值定义在RetentionPolicy枚举类中,常用值如下

  • SOURCE:注解只作用在源码阶段,生成的字节码文件中不存在
  • CLASS:注解作用在源码阶段,字节码文件阶段,运行阶段不存在,默认值
  • RUNTIME:注解作用在源码阶段,字节码文件阶段,运行阶段

6.7 模拟JUnit自带的@Test注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
}

public class MyTestMethod {

    @MyTest
    public void method1(){
        System.out.println("method1");
    }

    public void method2(){
        System.out.println("method2");
    }

    @MyTest
    public void method3(){
        System.out.println("method3");
    }
}

public class MyTestDemo {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException {
        //1,获取class对象
        Class clazz = Class.forName("com.itheima.test2.MyTestMethod");

        //获取对象
        Object o = clazz.newInstance();

        //2.获取所有方法
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            //method依次表示类里面的每一个方法
            method.setAccessible(true);
            //判断当前方法有没有MyTest注解
            if(method.isAnnotationPresent(MyTest.class)){
                method.invoke(o);
            }
        }
    }
}

7、内部类

7.1 概述

7.1.1 什么是内部类

将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类。可以把内部类理解成寄生,外部类理解成宿主。

7.1.2 什么时候使用内部类

一个事物内部还有一个独立的事物,内部的事物脱离外部的事物无法独立使用

  1. 人里面有一颗心脏。
  2. 汽车内部有一个发动机。
  3. 为了实现更好的封装性。

7.2 内部类的分类

按定义的位置来分:

(1)成员内部内:类定义在了成员位置 (类中方法外称为成员位置,无static修饰的内部类)
(2)静态内部类:类定义在了成员位置 (类中方法外称为成员位置,有static修饰的内部类)
(3)局部内部类:类定义在方法内
(4)匿名内部类:没有名字的内部类,可以在方法中,也可以在类中方法外。

7.3 成员内部类

(1)成员内部类特点

  • 无static修饰的内部类,属于外部类对象的。
  • 宿主:外部类对象。

(2)内部类的使用格式

 外部类.内部类。 // 访问内部类的类型都是用 外部类.内部类

(3)获取成员内部类对象的两种方式

方式一:外部直接创建成员内部类的对象

外部类.内部类 变量 = new 外部类().new 内部类();

方式二:在外部类中定义一个方法提供内部类的对象

(4)代码演示

方式一

public class Test {
    public static void main(String[] args) {
        //  宿主:外部类对象。
       // Outer out = new Outer();
        // 创建内部类对象。
        Outer.Inner oi = new Outer().new Inner();
        oi.method();
    }
}

class Outer {
    // 成员内部类,属于外部类对象的。
    // 拓展:成员内部类不能定义静态成员。
    public class Inner{
        // 这里面的东西与类是完全一样的。
        public void method(){
            System.out.println("内部类中的方法被调用了");
        }
    }
}

方式二

public class Outer {
    String name;
    private class Inner{
        static int a = 10;
    }
    public Inner getInstance(){
        return new Inner();
    }
}

public class Test {
    public static void main(String[] args) {
        Outer o = new Outer();
        System.out.println(o.getInstance());
    }
}

7.4 成员内部类的细节

编写成员内部类的注意点:

  • 成员内部类可以被一些修饰符所修饰,比如: private,默认,protected,public,static等
  • 在成员内部类里面,JDK16之前不能定义静态变量,JDK16开始才可以定义静态变量。
  • 创建内部类对象时,对象中有一个隐含的Outer.this记录外部类对象的地址值。

详解:

  • 内部类被private修饰,外界无法直接获取内部类的对象,只能通过3.3节中的方式二获取内部类的对象
  • 被其他权限修饰符修饰的内部类一般用3.3节中的方式一直接获取内部类的对象
  • 内部类被static修饰是成员内部类中的特殊情况,叫做静态内部类下面单独学习。
  • 内部类如果想要访问外部类的成员变量,外部类的变量必须用final修饰,JDK8以前必须手动写final,JDK8之后不需要手动写,JDK默认加上。

7.5 成员内部类面试题

请在?处填写相应的代码,以达到输出的内容

内部类访问外部类对象的格式是:外部类名.this

public class Test {
    public static void main(String[] args) {
        Outer.inner oi = new Outer().new inner();
        oi.method();
    }
}

class Outer {	// 外部类
    private int a = 30;

    // 在成员位置定义一个类
    class inner {
        private int a = 20;

        public void method() {
            int a = 10;
            System.out.println(???);	// 10   答案:a
            System.out.println(???);	// 20	答案:this.a
            System.out.println(???);	// 30	答案:Outer.this.a
        }
    }
}

7.6 成员内部类内存图

内部类内存图

7.7 静态内部类

(1)静态内部类特点

  • 静态内部类是一种特殊的成员内部类。
  • 有static修饰,属于外部类本身的。
  • 静态内部类与其他类的用法完全一样。只是访问的时候需要加上外部类.内部类
  • 静态内部类可以直接访问外部类的静态成员。
  • 静态内部类不可以直接访问外部类的非静态成员,如果要访问需要创建外部类的对象。
  • 静态内部类中没有隐含的Outer.this。

(2)内部类的使用格式

外部类.内部类

(3)静态内部类对象的创建格式

外部类.内部类  变量 = new  外部类.内部类构造器;

(4)调用方法的格式

  • 调用非静态方法的格式:先创建对象,用对象调用
  • 调用静态方法的格式:外部类名.内部类名.方法名();

(5)代码示例

// 外部类:Outer01
class Outer01{
    private static  String sc_name = "黑马程序";
    // 内部类: Inner01
    public static class Inner01{
        // 这里面的东西与类是完全一样的。
        private String name;
        public Inner01(String name) {
            this.name = name;
        }
        public void showName(){
            System.out.println(this.name);
            // 拓展:静态内部类可以直接访问外部类的静态成员。
            System.out.println(sc_name);
        }
    }
}

public class InnerClassDemo01 {
    public static void main(String[] args) {
        // 创建静态内部类对象。
        // 外部类.内部类  变量 = new  外部类.内部类构造器;
        Outer01.Inner01 in  = new Outer01.Inner01("张三");
        in.showName();
    }
}

7.8 局部内部类

局部内部类:定义在方法中的类。

定义格式:

class 外部类名 {
	数据类型 变量名;
	
	修饰符 返回值类型 方法名(参数列表) {
		// …
		class 内部类 {
			// 成员变量
			// 成员方法
		}
	}
}

7.9 匿名内部类

7.9.1 匿名内部类概述

匿名内部类:是内部类的简化写法,是一个隐含了名字的内部类。

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

匿名内部类的特点:

(1)定义一个没有名字的内部类
(2)这个类实现了父类,或者父类接口
(3)匿名内部类会创建这个没有名字的类的对象

7.9.2 匿名内部类格式

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

包含了:

  • 继承或者实现关系

  • 方法重写

  • 创建对象

所以从语法上来讲,这个整体其实是匿名内部类对象。

7.9.3 什么时候使用匿名内部类

如果希望定义一个只要使用一次的类,就可考虑使用匿名内部类。匿名内部类的本质作用是为了简化代码。

之前使用接口时,似乎得做如下几步操作:

  1. 定义子类
  2. 重写接口中的方法
  3. 创建子类对象
  4. 调用重写后的方法
interface Swim {
    public abstract void swimming();
}

// 1. 定义接口的实现类
class Student implements Swim {
    // 2. 重写抽象方法
    @Override
    public void swimming() {
        System.out.println("狗刨式...");
    }
}

public class Test {
    public static void main(String[] args) {
        // 3. 创建实现类对象
        Student s = new Student();
        // 4. 调用方法
        s.swimming();
    }
}

而最终的目的只是为了调用方法,所以可以使用匿名内部类,把以上四步合成一步。

7.9.4 匿名内部类简单使用

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

interface Swim {
    public abstract void swimming();
}

public class Demo07 {
    public static void main(String[] args) {
        // 使用匿名内部类
		new Swim() {
			@Override
			public void swimming() {
				System.out.println("自由泳...");
			}
		}.swimming();

        // 接口 变量 = new 实现类(); // 多态,走子类的重写方法
        Swim s2 = new Swim() {
            @Override
            public void swimming() {
                System.out.println("蛙泳...");
            }
        };
        s2.swimming();
    }
}

image-20240105163109423

7.9.5 匿名内部类作参数

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

举例一:

interface Swim {
    public abstract void swimming();
}

public class Demo07 {
    public static void main(String[] args) {

        // 方法1
        // 匿名内部类使用场景:作为方法参数传递
        Swim s = new Swim() {
            @Override
            public void swimming() {
                System.out.println("蝶泳...");
            }
        };
        // 传入匿名内部类
        goSwimming(s);


        // 方法2:一步到位
        goSwimming(new Swim() {
            public void swimming() {
                System.out.println("蛙泳...");
            }
        });
    }

    // 定义一个方法,方法的形式参数是一个接口
    public static void goSwimming(Swim s) {
        s.swimming();
    }
}

image-20240105164928786

举例二:

interface MyInterface {
    void myMethod();
}

class MyClass {
    void processInterface(MyInterface myInterface) {
        myInterface.myMethod();
    }
}

public class Main {
    public static void main(String[] args) {
        MyClass myClass = new MyClass();

        // 通过匿名内部类实现接口并传递给方法
        myClass.processInterface(new MyInterface() {
            @Override
            public void myMethod() {
                System.out.println("Implementation of myMethod in anonymous inner class");
            }
        });
    }
}
文章来源:https://blog.csdn.net/m0_47114547/article/details/135425328
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。