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

文章目录

  • 1、this关键字
  • 2、泛型
    • 2.1 泛型介绍
    • 2.2 泛型分类定义
      • 2.2.1 泛型类
      • 2.2.2 泛型方法
      • 2.2.3 泛型接口
    • 2.3 泛型通配符
  • 3、可变参数
  • 4、日志
    • 4.1 使用步骤
    • 4.2 日志级别
    • 4.3 配置文件
  • 5、类加载器
    • 5.1 类加载器
    • 5.2 类加载的完整过程
      • 5.2.1 类加载时机
      • 5.2.2 类加载过程
    • 5.3 类加载的分类
    • 5.4 双亲委派模型
    • 5.5 ClassLoader 中的两个方法
  • 6、注解
    • 6.1 注释和注解的区别
    • 6.2 如何使用注解
    • 6.3 Java中已经存在的注解
    • 6.4 自定义注解
    • 6.5 特殊属性
    • 6.6 元注解
    • 6.7 模拟JUnit自带的@Test注解
  • 7、内部类
    • 7.1 概述
      • 7.1.1 什么是内部类
      • 7.1.2 什么时候使用内部类
    • 7.2 内部类的分类
    • 7.3 成员内部类
    • 7.4 成员内部类的细节
    • 7.5 成员内部类面试题
    • 7.6 成员内部类内存图
    • 7.7 静态内部类
    • 7.8 局部内部类
    • 7.9 匿名内部类
      • 7.9.1 匿名内部类概述
      • 7.9.2 匿名内部类格式
      • 7.9.3 什么时候使用匿名内部类
      • 7.9.4 匿名内部类简单使用
      • 7.9.5 匿名内部类作参数

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> {// 指定集合默认初始化的长度为10Object[] 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];}@Overridepublic 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> {@Overridepublic int size() {return 0;}@Overridepublic boolean isEmpty() {return false;}@Overridepublic boolean contains(Object o) {return false;}@Overridepublic Iterator<E> iterator() {return null;}@Overridepublic Object[] toArray() {return new Object[0];}@Overridepublic <T> T[] toArray(T[] a) {return null;}@Overridepublic boolean add(E e) {return false;}@Overridepublic boolean remove(Object o) {return false;}@Overridepublic boolean containsAll(Collection<?> c) {return false;}@Overridepublic boolean addAll(Collection<? extends E> c) {return false;}@Overridepublic boolean addAll(int index, Collection<? extends E> c) {return false;}@Overridepublic boolean removeAll(Collection<?> c) {return false;}@Overridepublic boolean retainAll(Collection<?> c) {return false;}@Overridepublic void clear() {}@Overridepublic E get(int index) {return null;}@Overridepublic E set(int index, E element) {return null;}@Overridepublic void add(int index, E element) {}@Overridepublic E remove(int index) {return null;}@Overridepublic int indexOf(Object o) {return 0;}@Overridepublic int lastIndexOf(Object o) {return 0;}@Overridepublic ListIterator<E> listIterator() {return null;}@Overridepublic ListIterator<E> listIterator(int index) {return null;}@Overridepublic List<E> subList(int fromIndex, int toIndex) {return null;}
}

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

public class MyArrayList2 implements List<String> {@Overridepublic int size() {return 0;}@Overridepublic boolean isEmpty() {return false;}@Overridepublic boolean contains(Object o) {return false;}@Overridepublic Iterator<String> iterator() {return null;}@Overridepublic Object[] toArray() {return new Object[0];}@Overridepublic <T> T[] toArray(T[] a) {return null;}@Overridepublic boolean add(String s) {return false;}@Overridepublic boolean remove(Object o) {return false;}@Overridepublic boolean containsAll(Collection<?> c) {return false;}@Overridepublic boolean addAll(Collection<? extends String> c) {return false;}@Overridepublic boolean addAll(int index, Collection<? extends String> c) {return false;}@Overridepublic boolean removeAll(Collection<?> c) {return false;}@Overridepublic boolean retainAll(Collection<?> c) {return false;}@Overridepublic void clear() {}@Overridepublic String get(int index) {return null;}@Overridepublic String set(int index, String element) {return null;}@Overridepublic void add(int index, String element) {}@Overridepublic String remove(int index) {return null;}@Overridepublic int indexOf(Object o) {return 0;}@Overridepublic int lastIndexOf(Object o) {return 0;}@Overridepublic ListIterator<String> listIterator() {return null;}@Overridepublic ListIterator<String> listIterator(int index) {return null;}@Overridepublic 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><!--指定日志文件拆分和压缩规则--><rollingPolicyclass="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 {@MyTestpublic void method1(){System.out.println("method1");}public void method2(){System.out.println("method2");}@MyTestpublic 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   答案:aSystem.out.println(???);	// 20	答案:this.aSystem.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 = "黑马程序";// 内部类: Inner01public 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. 重写抽象方法@Overridepublic 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() {@Overridepublic void swimming() {System.out.println("自由泳...");}}.swimming();// 接口 变量 = new 实现类(); // 多态,走子类的重写方法Swim s2 = new Swim() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic void myMethod() {System.out.println("Implementation of myMethod in anonymous inner class");}});}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/601066.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【开源项目】超经典开源项目实景三维数字孪生智慧工厂

数字孪生工厂&#xff0c;以模型驱动的自动化&#xff0c;与数据驱动的人工智能技术紧密融合与协同&#xff0c;实现机器、工件与组件间全面的和点对点的数据通信。飞渡科技基于自研DTS平台&#xff0c;将物联网IOT、人工智能、大数据、云计算等技术应用于工厂&#xff0c;实现…

期货日数据维护与使用_日数据维护_模块运行演示

写在前面&#xff1a; 本文默认已经创建了项目&#xff0c;如果不知道如何创建一个空项目的&#xff0c;请参看以下两篇博文 PyQt5将项目搬到一个新的虚拟环境中 https://blog.csdn.net/m0_37967652/article/details/122625280 python_PyQt5开发工具结构基础 https://blog.cs…

TMC4671闭环调试步进、伺服、音圈、永磁、无刷电机

一、IDE 连接开发板 下 面 讲 解 IDE 和 开 发 板 连 接 的 详 细 操 作 。 这 里 我 们 选 择 用 主 控 板 TMC671-EVALTMC6200-EVAL 开发板做讲解。其它型号的开发板也是大同小异 的操作步骤。 1&#xff0e;首先我连接好开发板&#xff0c;并给开发板上电 连接好的开发板如下…

【激活函数】SELU 激活函数

1、介绍 SELU (Scaled Exponential Linear Unit) SELU是对ELU激活函数的改进&#xff0c;通过引入自动标准化机制&#xff0c;使得神经网络的隐藏层在训练过程中可以自动地保持输出的均值和方差接近于1。 # 定义 SELU 激活函数 def selu(x, alpha1.67326, lambda_1.0507):retu…

【Python百宝箱】数据清洗艺术:Python库助力打磨完美数据

数据清洗与预处理&#xff1a;Python库大揭秘 前言 在数据科学领域&#xff0c;数据清洗和预处理是构建可靠模型的关键步骤。本文深入探讨了一系列强大的Python库&#xff0c;它们在处理重复数据、字符串匹配、数据整理以及降维等方面发挥着重要作用。通过学习这些库&#xf…

单片机相关知识点

在STM32上运行FreeRTOS&#xff0c;十分简练的小文章FreeRTOS&#xff08;STM32CubeMX&#xff09;_cubemx freertos-CSDN博客 STM32CubeMX转Keil使用STM32CubeMX生成Keil工程并完成流水灯-CSDN博客

unity PDFRender Curved UI3.3

【PDF】PDFRender 链接&#xff1a;https://pan.baidu.com/s/1wSlmfiWTAHZKqEESxuMH6Q 提取码&#xff1a;csdn 【曲面ui】 Curved UI3.3 链接&#xff1a;https://pan.baidu.com/s/1uNZySJTW0-pPwi2FTE6fgA 提取码&#xff1a;csdn

【unity小技巧】FPS游戏实现相机的震动、后坐力和偏移

最终效果 文章目录 最终效果前言相机的震动实现后坐力和偏移相机震动相机震动脚本换弹节点震动 武器射击后退效果完结 前言 关于后坐力之前其实已经分享了一个&#xff1a;FPS游戏后坐力制作思路 但是实现起来比较复杂&#xff0c;如果你只是想要简单的实现&#xff0c;可以看…

Linux ssh 实现远程免密登录

一、背景 我搭建了一个 zookeeper 集群&#xff0c;写了一个 shell 脚本来控制集群的启动和关闭&#xff0c;但是我发现每次我执行 shell 脚本的时候&#xff0c;都需要我输入各个服务器的密码才可以运行&#xff0c;感觉很麻烦。shell 脚本里面连接其他服务器用的就是 ssh 的方…

Linux安装JDK和Maven并配置环境变量

文章目录 一、安装JDK并配置环境变量二、安装maven并配置环境变量 一、安装JDK并配置环境变量 将JDK的安装包上传到Linux系统的usr/local目录 使用xftp上传文件 解压JDK的压缩包 xshell连接到云主机 [roottheo ~]# cd /usr/local[roottheo local]# ls aegis apache-tomcat-…

equals()方法和“==”运算符

equals()equals()方法和“”运算符比较 回到顶部 equals() 超类Object中有这个equals()方法&#xff0c;该方法主要用于比较两个对象是否相等。该方法的源码如下&#xff1a; public boolean equals(Object obj) {return (this obj);} 我们知道所有的对象都拥有标识(内存…

使用LVM分区方式安装Manjaro发行版

使用LVM分区方式安装Manjaro发行版 为什么单独介绍LVM方式呢&#xff1f; 主要是由于使用系统的图形安装工具创建卷组会出问题&#xff0c;会导致图形安装工具直接挂掉&#xff0c;唯一的方法是提前手动创建好卷组。 GPT分区表 分区表有&#xff1a; MBR(主引导记录分区表)…

【华为OD真题 Python】两数之和绝对值最小

文章目录 题目描述输入描述输出描述示例1输入输出说明代码实现题目描述 给定一个从小到大的有序整数序列(存在正整数和负整数)数组 nums ,请你在该数组中找出两个数,其和的绝对值(|nums[x]+nums[y]|)为最小值,并返回这个绝对值。 每种输入只会对应一个答案。但是,数组中…

性能优化-OpenMP概述(一)-宏观全面理解OpenMP

本文旨在从宏观角度来介绍OpenMP的原理、编程模型、以及在各个领域的应用、使用、希望读者能够从本文整体上了解OpenMP。 &#x1f3ac;个人简介&#xff1a;一个全栈工程师的升级之路&#xff01; &#x1f4cb;个人专栏&#xff1a;高性能&#xff08;HPC&#xff09;开发基础…

【算法】链表-20240105

这里写目录标题 一、LCR 023. 相交链表二、142. 环形链表 II 一、LCR 023. 相交链表 给定两个单链表的头节点 headA 和 headB &#xff0c;请找出并返回两个单链表相交的起始节点。如果两个链表没有交点&#xff0c;返回 null 。 提示&#xff1a; listA 中节点数目为 m list…

window 下载安装 Studio 3T

这里 我们先访问官网 https://studio3t.com/ 然后 他会弹出提示 如果您同意使用cookies和类似技术进行市场研究或广告目的&#xff0c;并希望获得额外功能&#xff0c;您可以点击“Accept all”按钮。 如果您不同意使用cookies和类似技术进行上述目的&#xff0c;您可以点击“…

大模型的实践应用16-一种针对大型预训练模型的微调技巧:Adapter-Tuning方法的实战应用,原理详细介绍

大家好,我是微学AI,今天给大家介绍一下大模型的实践应用16-一种针对大型预训练模型的微调技巧:Adapter-Tuning方法的实战应用,原理详细介绍。Adapter-Tuning 是一种针对大型预训练模型微调的技术,它可以在保持模型参数数量较小的情况下增加模型的表现。该技术将适配器插入…

ffmpeg 改变帧率,分辨率,时长等命令

ffmpeg -i elva.mp4 -ss 00:00:20 -t 00:00:30 -c:v copy -c:a copy output1.mp4 视频截取&#xff0c;开始时间和时长,-ss 00:00:20 -t 00:00:30 ffmpeg -i output1.mp4 -c:v libx265 output265.mp4 -c:v libx265,264转265 ffmpeg -i output1.mp4 -c:v libx264 output264.mp4 …

Java面试项目推荐,异构数据源数据流转服务DatalinkX

前言 作为一个年迈的夹娃练习生&#xff0c;每次到了春招秋招面试实习生时都能看到一批简历&#xff0c;十个简历里得有七八个是写商城或者外卖项目。 不由得想到了我大四那会&#xff0c;由于没有啥项目经验&#xff0c;又想借一个质量高点的项目通过简历初筛&#xff0c;就…

CTFhub-Web-Web前置技能-“302跳转“

题目信息 HTTP临时重定向&#xff0c;题目截图如下所示&#xff1a; 分析过程 看到跳转路径为&#xff1a;http://challenge-d1a96d97eaecf029.sandbox.ctfhub.com:10800/index.html 分析可能存在重定向问题&#xff0c;如果要想获得flag&#xff0c;则可能存在http://chal…