文章目录
- 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> {
// 指定集合默认初始化的长度为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);
}
}
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);
}
}
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;
}
}
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文件(存储的物理文件)加载在到内存中
5.2 类加载的完整过程
5.2.1 类加载时机
简单理解:字节码文件什么时候会被加载到内存中?
有以下的几种情况:
- 创建类的实例(对象)
- 调用类的类方法
- 访问类或者接口的类变量,或者为该类变量赋值
- 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
- 初始化某个类的子类
- 直接使用java.exe命令来运行某个主类
总结而言:用到了就加载,不用不加载
5.2.2 类加载过程
(1)加载
- 通过包名 + 类名,获取这个类,准备用流进行传输
- 在这个类加载到内存中
- 加载完毕创建一个class对象
(2)链接
-
验证
确保Class文件字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身安全
(文件中的信息是否符合虚拟机规范有没有安全隐患)
-
准备
负责为类的类变量(被static修饰的变量)分配内存,并设置默认初始化值
(初始化静态变量)
-
解析
将类的二进制数据流中的符号引用替换为直接引用
(本类中如果用到了其他类,此时就需要找到对应的类)
(3)初始化
根据程序员通过程序制定的主观计划去初始化类变量和其他资源
(静态变量赋值以及初始化其他资源)
- 当一个类被使用的时候,才会加载到内存
- 类加载的过程: 加载、验证、准备、解析、初始化
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)介绍
如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式

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 什么时候使用内部类
一个事物内部还有一个独立的事物,内部的事物脱离外部的事物无法独立使用
- 人里面有一颗心脏。
- 汽车内部有一个发动机。
- 为了实现更好的封装性。
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 什么时候使用匿名内部类
如果希望定义一个只要使用一次的类,就可考虑使用匿名内部类。匿名内部类的本质作用是为了简化代码。
之前使用接口时,似乎得做如下几步操作:
- 定义子类
- 重写接口中的方法
- 创建子类对象
- 调用重写后的方法
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();
}
}
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();
}
}
举例二:
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");
}
});
}
}