大家好,又见面了,我是你们的朋友全栈君。
成员变量(又叫属性,字段)
成员方法
2、类和对象的内存分配机制
Java 内存的结构分析
-
栈: 一般存放基本数据类型(局部变量)
-
堆: 存放对象(Cat cat , 数组等)
-
方法区:常量池(常量,比如字符串), 类加载信息
对象在内存中存在形式:
分析:age的变量类型为基本数据类型int,值直接存在堆中,而name和color是引用数据类型String,堆中存放其地址,其值存在方法区的常量池,堆中的地址指向方法区的常量池,类加载也在方法区中
习题
class Person {
String name;
int age;
}
public class Test {
public static void main(String[] args) {
Person p1 = new Person();
p1.age = 10;
p1.name = "小明";
Person p2 = p1; //把p1 赋给了 p2 , 让p2指向p1
System.out.println(p2.age); // 10
p2.age = 80;
System.out.println(p1.age); //80
p2 = null;
System.out.println(p1.age); //80
System.out.println(p2.age); //抛出空指针异常 NullPointerException
}
}
3、(方法的传参机制)值传递和引用传递
-
基本数据类型传递的是值本身,形参的任何改变不影响实参
-
引用数据类型传递的是地址(传递也是值,但是值是地址),可以通过形参影响实参!
4、方法的调用机制
5、方法的递归调用
class T {
public void test(int n) {
if (n > 2) {
test(n - 1);
}
System.out.println("n=" + n);
}
}
public class Recursion01 {
public static void main(String[] args) {
T t1 = new T();
t1.test(4);//输出 n=2 n=3 n=4
}
}
/*
运行结果:
n=2
n=3
n=4
*/
注意输出的是 n=2 n=3 n=4,而不是 n=4 n=3 n=2
递归的重要原则
6、方法的重载(overload)
-
定义:java 中允许同一个类中,多个同名方法的存在,但要求形参列表不一致!
-
使用细节
注意返回类型不要求相同
-
习题
7、可变参数
-
定义: Java 允许将同一个类中 多个同名同功能但 参数个数不同的方法,封装成一个方法。
-
基本语法
访问修饰符返回类型方法名(数据类型 … 形参名) { }
-
案例
public class Text01 {
public static void main(String[] args) {
System.out.println(sum(1, 5, 100)); //106
System.out.println(sum(1,19)); //20
}
public static int sum(int... nums) {
int res = 0;
for(int i = 0; i < nums.length; i++) {
res += nums[i];
}
return res;
}
}
8、作用域
9、数据类型的默认赋值
int 0,short 0, byte 0, long 0, float 0.0,double 0.0,char \u0000,boolean false,String null
10、构造器
-
定义:构造方法又叫构造器(constructor),是类的一种特殊的方法,它的主要作用是完成对新对象的初始化。
-
基本语法
[修饰符] 方法名(形参列表){ 方法体; }
-
使用细节
-
构造器的修饰符可以默认, 也可以是public protected private
-
构造器没有返回值
-
方法名和类名字必须一样
-
参数列表和成员方法一样的规则
-
构造器的调用, 由系统完成
-
如果没有给一个类定义构造器,系统会生成一个默认的无参构造器,一旦自己定义了构造器,默认的无参构造器就会被覆盖,想使用无参构造器需要显式定义一下
-
-
对象创建的流程分析
11、this关键字
-
简单说明:哪个对象调用,this就代表哪个对象。(如下图,相当于 this指向调用对象在堆里的地址)
-
使用细节
-
this 关键字可以用来访问本类的属性、方法、构造器
-
this 用于区分当前类的属性和局部变量
-
访问成员方法的语法:this.方法名(参数列表);
-
用this访问构造器语法:this(参数列表); 注意只能在构造器中使用(即只能在构造器中访问另外一个构造器, 必须放在第一条语句)
-
this 不能在类定义的外部使用,只能在类定义的方法中使用。
public class Text {
public static void main(String[] args) {
T t = new T();
}
}
class T{
private String name;
private int age;
public T() {
this("A", 10); //必须放在第一条语句
System.out.println("无参构造器");
}
public T(String name, int age) {
this.name = name;
this.age = age;
System.out.println("T(String name, int age)构造器");
}
}
/*
运行结果:
T(String name, int age)构造器
无参构造器
*/public class Text {
public static void main(String[] args) {
T t = new T();
t.f1();
System.out.println("===========");
t.f2();
}
}
class T {
private int age = 10;
public void f1() {
int age = 20;
System.out.println("this.age:" + this.age);
System.out.println("age:" + age);
}
public void f2() {
System.out.println("this.age:" + this.age);
System.out.println("age:" + age);
}
}
/*
运行结果:
this.age:10
age:20
===========
this.age:10
age:10
*/ -
分析:在f()方法中,无论有没有局部变量age,this.age都是指成员变量age,而f()的age则会根据就近原则,如果f()中有定义局部变量age,则代表局部变量,否则代表成员表量。
12、4种访问修饰符的访问范围
-
级别
-
公开级别:用public 修饰,对外公开
-
受保护级别:用protected 修饰,对子类和同一个包中的类公开
-
默认级别:没有修饰符号,向同一个包的类公开.
-
私有级别:用private 修饰,只有类本身可以访问,不对外公开.
-
-
使用细节
-
修饰符可以用来修饰类中的属性,成员方法以及类
-
只有默认的和public才能修饰类
-
13、封装
-
介绍
-
封装的步骤
-
将构造器和setXxx 结合
public Person(String name, int age, double salary) {
setName(name);
setAge(age);
setSalary(salary);
}
14、继承
-
继承:继承可以解决代码复用,让我们的编程更加靠近人类思维.当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends 来声明继承父类即可。
-
基本语法
-
使用细节
-
子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问
-
子类必须调用父类的构造器, 完成父类的初始化
-
当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过。
-
如果希望指定去调用父类的某个构造器,则显式的调用一下: super(参数列表)
-
super 在使用时,必须放在构造器第一行(super 只能在构造器中使用)
-
super() 和this() 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器
-
java 所有类都是Object 类的子类, Object 是所有类的基类.
-
父类构造器的调用不限于直接父类!将一直往上追溯直到Object 类(顶级父类)
-
子类最多只能继承一个父类(指直接继承),即java 中是单继承机制。 思考:如何让A 类继承B 类和C 类? 【A 继承B, B 继承C】
-
不能滥用继承,子类和父类之间必须满足is-a 的逻辑关系
-
public class Text {
public static void main(String[] args) {
Sub sub1 = new Sub();
System.out.println("========");
Sub sub2 = new Sub(10);
System.out.println("========");
Sub sub3 = new Sub(10, 20);
}
}
class Base {
private int age ;
private int id ;
public Base() {
System.out.println("父类无参构造器被调用");
}
public Base(int age) {
this.age = age;
System.out.println("父类Base(int age)被调用");
}
public Base(int age, int id) {
this.age = age;
this.id = id;
System.out.println("父类Base(int age, int id)被调用");
}
}
class Sub extends Base{
public Sub() {
System.out.println("子类无参构造器被调用");
}
public Sub(int age) {
System.out.println("子类Sub(int age)被调用");
}
public Sub(int age, int id) {
super(age, id);
System.out.println("子类Sub(int age, int id)被调用");
}
}
/*
运行结果:
父类无参构造器被调用
子类无参构造器被调用
========
父类无参构造器被调用
子类Sub(int age)被调用
========
父类Base(int age, int id)被调用
子类Sub(int age, int id)被调用
*/
-
继承的本质
当子类对象创建好后,建立查找关系
按照查找关系来返回信息:
-
首先看子类是否有该属性
-
如果子类有这个属性,并且可以访问,则返回信息
-
如果子类没有这个属性,就看父类有没有这个属性(如果父类有该属性,并且可以访问,就返回信息..)
-
如果父类没有就按照(3)的规则,继续找上级父类,直到Object
-
public class Text {
public static void main(String[] args) {
Son son = new Son();
System.out.println(son.name); //大头儿子
System.out.println(son.hobby); //旅游
System.out.println(son.age); //报错:age 在 Father 中是 private 访问控制
}
}
class GrandPa {
String name = "大头爷爷";
String hobby = "旅游";
int age = 60;
}
class Father extends GrandPa {
String name = "大头爸爸";
private int age = 39;
}
class Son extends Father {
String name = "大头儿子";
}
15、super关键字
-
基本介绍:super 代表父类的引用,用于访问父类的属性、方法、构造器
-
基本语法
-
使用细节
class A {
public void f(){
}
}
class B extends A{
public void f(){
}
public void t(){
/*(1)先找本类,如果有,则调用
(2)如果没有,则找父类(如果有,并可以调用,则调用)
(3)如果父类没有,则继续找父类的父类,整个规则,就是一样的,直到 Object类
提示:如果查找方法的过程中,找到了,但是不能访问, 则报错, cannot access
如果查找方法的过程中,没有找到,则提示方法不存在*/
f();
this.f();
super.f();
// f()和this.f()完全等价,super.f()会直接调用父类的f()
}
}
16、super和this比较
17、方法重写/覆盖(override)
-
基本概念
-
使用细节
18、重写(override)和重载(overload)比较
19、多态
1 多态存在的三个必要条件
-
继承
-
重写
-
父类引用指向子类对象
2 编译类型和运行类型
当使用实例调用方法时,看的是运行类型,即调用运行类型对应的该方法
3 多态的向上转型和向下转型
向上转型
class Animal {
String name = "动物";
int age = 10;
public void sleep(){
System.out.println("睡");
}
public void run(){
System.out.println("跑");
}
public void eat(){
System.out.println("吃");
}
public void show(){
System.out.println("hello,你好");
}
}
class Cat extends Animal {
public void eat(){//方法重写
System.out.println("猫吃鱼");
}
public void catchMouse(){//Cat特有方法
System.out.println("猫抓老鼠");
}
}
public class Text{
public static void main(String[] args) {
Animal animal = new Cat();
animal.catchMouse();//编译报错,catchMouse()是Cat的特有方法,实例animal不能调用
animal.eat();//猫吃鱼..
animal.run();//跑
animal.show();//hello,你好
animal.sleep();//睡
}
代码细节分析:
-
实例animal编译类型是Animal,运行类型是Cat
-
向上转型调用方法的规则如下:
-
可以调用父类中的所有成员(需遵守访问权限)
-
不能调用子类的特有的成员(上面catchMouse()是Cat的特有方法,实例animal不能调用),因为在编译阶段,能调用哪些成员,是由编译类型来决定的
-
最终运行效果看运行类型的具体实现, 即调用方法时,按照从运行类型开始查找方法,然后调用,子类(运行类型)没有,找父类。
-
向下转型
//接上个代码
public class Text{
public static void main(String[] args) {
Animal animal = new Cat(); //向上转型
animal.catchMouse();//编译报错,catchMouse()是Cat的特有方法,实例animal不能调用
animal.eat();//猫吃鱼..
animal.run();//跑
animal.show();//hello,你好
animal.sleep();//睡
Cat cat = (Cat) animal; //向下转型
cat.catchMouse();//猫抓老鼠,成功运行
}
属性没有重写,属性的值看编译类型
public class Test {
public static void main(String[] args) {
//属性没有重写之说!属性的值看编译类型
Base base = new Sub();//向上转型
System.out.println(base.count);// 10
Sub sub = new Sub();
System.out.println(sub.count);//? 20
}
}
class Base {
int count = 10;
}
class Sub extends Base {
int count = 20;
}
4 instanceOf 比较操作符,用于判断对象的 运行类型是否为XX 类型或XX 类型的子类型
public class PolyDetail03 {
public static void main(String[] args) {
BB bb = new BB();
System.out.println(bb instanceof BB);// true
System.out.println(bb instanceof AA);// true
AA aa = new BB();
System.out.println(aa instanceof AA);// true
System.out.println(aa instanceof BB);// true
Object obj = new Object();
System.out.println(obj instanceof AA);//false
String str = "hello";
//System.out.println(str instanceof AA); //报错
System.out.println(str instanceof Object);//true
}
}
class AA {}
class BB extends AA {}
5 动态绑定机制
public class DynamicBinding {
public static void main(String[] args) {
A a = new B();//向上转型
System.out.println(a.sum());//40
System.out.println(a.sum1());//30
}
}
class A {
public int i = 10;
public int sum() {
return getI() + 10;
}
public int sum1() {
return i + 10;
}
public int getI() {
return i;
}
}
class B extends A {
public int i = 20;
public int sum() {
return i + 20;
}
public int getI() {
return i;
}
public int sum1() {
return i + 10;
}
}
将子类的sum()和sum1()删掉
package com.hspedu.poly_.dynamic_;
public class DynamicBinding {
public static void main(String[] args) {
A a = new B();//向上转型
System.out.println(a.sum());//30
System.out.println(a.sum1());//20
}
}
class A {
public int i = 10;
public int sum() {
return getI() + 10;
}
public int sum1() {
return i + 10;
}
public int getI() {
return i;
}
}
class B extends A {
public int i = 20;
public int getI() {
return i;
}
}
代码分析:
-
当子类的sum()被删掉后,再调用sum()方法时,会调用父类的sum(),父类的sum()中有getI()方法,而父类和子类都有getI()方法,根据动态绑定机制,方法与运行类型绑定,此时,会调用子类的getI()方法,子类的getI()方法返回i的值就是子类中属性i的值,因为属性在哪声明,在哪调用。
-
当子类的sum1()被删掉后,再调用sum1()方法时,会调用父类的sum1(),父类中 return i + 10 中的i就是父类中属性i的值。
20、Object类
1 equals 方法
==和equals 的对比
Object中的equals判断的是地址是否相等,String重写了Object中的equals,判断内容是否相等
public class EqualsExercise01 {
public static void main(String[] args) {
Person person1 = new Person("jack", 10, '男');
Person person2 = new Person("jack", 10, '男');
Person person3 = new Person("jack", 20, '男');
System.out.println(person1.equals(person2)); //true
System.out.println(person1.equals(person3)); //false
}
}
class Person { //extends Object
private String name;
private int age;
private char gender;
public boolean equals(Object obj) {
//判断如果比较的两个对象是同一个对象,则直接返回true
if(this == obj) {
return true;
}
//类型判断
if(obj instanceof Person) {//是Person,我们才比较
//进行 向下转型, 因为我需要得到obj的 各个属性
Person p = (Person)obj;
return this.name.equals(p.name) && this.age == p.age && this.gender == p.gender;
}
//如果不是Person ,则直接返回false
return false;
}
public Person(String name, int age, char gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
}
2 hashCode方法
-
提高具有哈希结构的容器的效率!
-
两个引用,如果指向的是同一个对象,则哈希值肯定是一样的!
-
两个引用,如果指向的是不同对象,则哈希值是不一样的
-
哈希值主要根据地址号来的,不能完全将哈希值等价于地址。
3 toString 方法
-
基本介绍:默认返回:全类名+@+哈希值的十六进制
-
重写toString 方法,打印对象或拼接对象时,都会自动调用该对象的toString 形式.
-
当直接输出一个对象时, toString 方法会被默认的调用, 比如System.out.println(monster) ; 就会默认调用monster.toString()
4 finalize 方法
-
当对象被回收时,系统自动调用该对象的finalize 方法。子类可以重写该方法,做一些释放资源的操作
-
什么时候被回收:当某个对象没有任何引用时,则jvm 就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,在销毁该对象前,会先调用finalize 方法。
-
垃圾回收机制的调用,是由系统来决定(即有自己的GC 算法), 也可以通过System.gc() 主动触发垃圾回收机制,
21、类(静态)变量和类(静态)方法
类变量
-
定义
-
语法
-
访问类变量
-
使用细节
静态变量是类加载的时候,就初始化了
类方法
-
基本介绍
-
调用
-
类方法经典的使用场景
-
使用细节
22、代码块
-
基本介绍
-
语法
-
使用
-
使用细节
注意类被加载的3种情况及类的调用顺序
总结: 静态代码块在类加载时执行,普通代码块在实例化对象执行
public class Test {
public static void main(String[] args) {
A a = new A();
}
}
class A {
{ //普通代码块
System.out.println("A 普通代码块01");
}
private int n2 = getN2();//普通属性的初始化
static { //静态代码块
System.out.println("A 静态代码块01");
}
//静态属性的初始化
private static int n1 = getN1();
public static int getN1() {
System.out.println("getN1被调用...");
return 100;
}
public int getN2() { //普通方法/非静态方法
System.out.println("getN2被调用...");
return 200;
}
//无参构造器
public A() {
System.out.println("A() 构造器被调用");
}
}
/*
运行结果:
A 静态代码块01
getN1被调用...
A 普通代码块01
getN2被调用...
A() 构造器被调用
*/
public class Test{
public static void main(String[] args) {
new BBB();
}
}
class AAA { //父类Object
{
System.out.println("AAA的普通代码块");
}
public AAA() {
//(1)super()
//(2)调用本类的普通代码块
System.out.println("AAA() 构造器被调用....");
}
}
class BBB extends AAA {
{
System.out.println("BBB的普通代码块...");
}
public BBB() {
//(1)super()
//(2)调用本类的普通代码块
System.out.println("BBB() 构造器被调用....");
}
}
/*
运行结果
AAA的普通代码块
AAA() 构造器被调用....
BBB的普通代码块...
BBB() 构造器被调用....
*/
当有继承时类的调用顺序
package com.hspedu.codeblock_;
public class CodeBlockDetail04 {
public static void main(String[] args) {
new B02();
}
}
class A02 { //父类
private static int n1 = getVal01();
static {
System.out.println("A02的一个静态代码块..");//(2)
}
{
System.out.println("A02的第一个普通代码块..");//(5)
}
public int n3 = getVal02();//普通属性的初始化
public static int getVal01() {
System.out.println("getVal01");//(1)
return 10;
}
public int getVal02() {
System.out.println("getVal02");//(6)
return 10;
}
public A02() {//构造器
//隐藏
//super()
//普通代码和普通属性的初始化......
System.out.println("A02的构造器");//(7)
}
}
class B02 extends A02 {
private static int n3 = getVal03();
static {
System.out.println("B02的一个静态代码块..");//(4)
}
public int n5 = getVal04();
{
System.out.println("B02的第一个普通代码块..");//(9)
}
public static int getVal03() {
System.out.println("getVal03");//(3)
return 10;
}
public int getVal04() {
System.out.println("getVal04");//(8)
return 10;
}
public B02() {//构造器
//隐藏了
//super()
//普通代码块和普通属性的初始化...
System.out.println("B02的构造器");//(10)
}
}
/*
运行结果:
getVal01
A02的一个静态代码块..
getVal03
B02的一个静态代码块..
A02的第一个普通代码块..
getVal02
A02的构造器
getVal04
B02的第一个普通代码块..
B02的构造器
*/
-
练习题
class Person {
public static int total;//静态变量
static {//静态代码块
total = 100;
System.out.println("in static block!");
}
}
class CodeBlockExercise01 {
public static void main(String[] args) {
System.out.println("total = "+ (Person.total++));
System.out.println("total = "+ (++Person.total));
}
}
/*
运行结果:
in static block!
total = 100
total = 102
*/
代码分析:静态代码块只在类加载时执行,类只加载一次,顾“in static block!”只在第一次时打印,输出语句中,Person.total++也是先输出再加一,++Person.total是先加再输出
public class CodeBlockExercise02 {
public static void main(String str[])
{
Test a=new Test();
}
}
class Sample
{
Sample(String s)
{
System.out.println(s);
}
Sample()
{
System.out.println("Sample默认构造函数被调用");
}
}
class Test{
Sample sam1=new Sample("sam1成员初始化");//
static Sample sam=new Sample("静态成员sam初始化 ");//
static{
System.out.println("static块执行");//
if(sam==null)System.out.println("sam is null");
}
Test()//构造器
{
System.out.println("Test默认构造函数被调用");//
}
}
/*
运行结果:
静态成员sam初始化
static块执行
sam1成员初始化
Test默认构造函数被调用
*/
代码分析:Test a=new Test(),调用Test()构造器前,会按顺序执行Test类的静态属性和静态代码块,普通属性和普通代码块,再执行构造器。
23、final 关键字
-
final可以修饰类、属性、方法和局部变量
-
使用场景
-
使用细节
注意:final static 搭配使用定义的变量,在被调用时,不会导致类的加载,因为底层编译器做了优化
public class Test {
public static void main(String[] args) {
System.out.println(A.n1);
System.out.println("=======");
System.out.println(B.n1);
}
}
class A {
public static int n1 = 1;
static {
System.out.println("静态代码块被调用");
}
}
class B {
public final static int n1 = 1;
static {
System.out.println("静态代码块被调用");
}
}
/*
运行结果:
静态代码块被调用
1
=======
1
*/
代码分析:使用A中的静态变量n1引起了A类的加载,导致静态代码块执行;而B类中的静态变量n1用了final修饰,使用B.n1不会引起类加载。
24、抽象类
-
语法
-
使用细节
25、接口
-
简介
interface A {
default void f1() {
}
static void f2() {
}
void f3();
}
代码分析: jdk8后,接口中的方法可以实现,但必须加上static或default,否则不能有方法体{},不然会报错。
-
使用细节
注意:
-
接口中的方法是抽象的,是public的
-
接口中定义int a实际上是public static final int a
-
接口可以继承多个其他接口
public class Test {
public static void main(String[] args) {
System.out.println(A.a); //说明a是static
//A.a = 2; 报错,A.a是final类型,不能修改
}
}
interface A {
int a = 1;
}
interface B {}
interface C extends A,B{} //接口可以继承多个其他接口*
-
练习
26、实现接口vs 继承类
class Monkey {
private String name;
public Monkey(String name) {
this.name = name;
}
public void climbing() {
System.out.println(name + " 会爬树...");
}
}
interface Fishable {
void swimming();
}
interface Birdable {
void flying();
}
class LittleMonkey extends Monkey implements Fishable,Birdable {
public LittleMonkey(String name) {
super(name);
}
@Override
public void swimming() {
System.out.println(getName() + " 通过学习,可以像鱼儿一样游泳...");
}
@Override
public void flying() {
System.out.println(getName() + " 通过学习,可以像鸟儿一样飞翔...");
}
}
27、内部类
-
分类
定义类在局部位置(方法中/代码块) :(1) 局部内部类(2) 匿名内部类 定义在成员位置(1) 成员内部类(2) 静态内部类
-
简介
-
类的五大成员:属性、方法、构造器、代码块、内部类
-
内部类最大特点:可以直接访问外部类的私有属性
-
基本语法
-
4种内部类使用
1 局部内部类的使用
public class LocalInnerClass {
public static void main(String[] args) {
Outer02 outer02 = new Outer02();
outer02.m1();
System.out.println("outer02的hashcode=" + outer02);
}
}
class Outer02 {//外部类
private int n1 = 100;
public void m1() {
//1.局部内部类是定义在外部类的局部位置,通常在方法
//3.不能添加访问修饰符,但是可以使用final 修饰
//4.作用域 : 仅仅在定义它的方法或代码块中
final class Inner02 {//局部内部类(本质仍然是一个类)
//2.可以直接访问外部类的所有成员,包含私有的
private int n1 = 800;
public void f1() {
//5. 局部内部类可以直接访问外部类的成员,比如下面 外部类n1 和 m2()
/* 7. 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,
使用 外部类名.this.成员去访问*/
//Outer02.this 本质就是外部类的对象, 即哪个对象调用了m1, Outer02.this就是哪个对象
System.out.println("n1=" + n1 + " 外部类的n1=" + Outer02.this.n1);
System.out.println("Outer02.this hashcode=" + Outer02.this);
}
}
//6. 外部类在方法中,可以创建Inner02对象,然后调用方法即可
Inner02 inner02 = new Inner02();
inner02.f1();
}
}
/*
运行结果:
n1=800 外部类的n1=100
Outer02.this hashcode=com.hspedu.innerclass.Outer02@1b6d3586
outer02的hashcode=com.hspedu.innerclass.Outer02@1b6d3586*/
代码分析:
-
局部内部类是定义在外部类的局部位置,通常在方法(或代码块)中
-
使用该局部内部类:外部类在方法中,可以创建内部类对象,然后调用方法即可。
-
外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,使用 外部类名.this.成员(Outer02.this本质就是外部类的对象)去访问。可以看到Outer02.this和创建的Outer02对象的hashcode值是相等的。
2 匿名内部类的使用
-
特点
public class AnonymousInnerClass {
public static void main(String[] args) {
Outer04 outer04 = new Outer04();
outer04.method();
}
}
class Outer04 { //外部类
private int n1 = 10;//属性
public void method() {//方法
//基于接口的匿名内部类
//1.需求: 想使用IA接口,并创建对象
//2.传统方式,是写一个类,实现该接口,并创建对象
//3.该类只是使用一次,后面再不使用
//4. 可以使用匿名内部类来简化开发
//5. tiger的编译类型 ? IA
//6. tiger的运行类型 ? 就是匿名内部类 Outer04$1
/*
底层 会分配 类名 Outer04$1
class Outer04$1 implements IA {
@Override
public void cry() {
System.out.println("老虎叫唤...");
}
}
*/
/*7. jdk底层在创建匿名内部类 Outer04$1,立即马上就创建了 Outer04$1实例,并且把地址
返回给 tiger*/
IA tiger = new IA() {
@Override
public void cry() {
System.out.println("IA的匿名内部类");
}
};
System.out.println("tiger的运行类型=" + tiger.getClass());
tiger.cry();
//演示基于类的匿名内部类
//分析
//1. father编译类型 Father
//2. father运行类型 Outer04$2
//3. 底层会创建匿名内部类
/*
class Outer04$2 extends Father{
@Override
public void test() {
System.out.println("匿名内部类重写了test方法");
}
}
*/
//4. 同时也直接返回了 匿名内部类 Outer04$2的对象
//5. 注意("jack") 参数列表会传递给 构造器
Father father = new Father("jack"){
@Override
public void test() {
System.out.println("father的匿名内部类");
}
};
System.out.println("father对象的运行类型=" + father.getClass());//Outer04$2
father.test();
//基于抽象类的匿名内部类
Animal animal = new Animal(){
@Override
void eat() {
System.out.println("animal的匿名内部类");
}
};
animal.eat();
}
}
interface IA {//接口
public void cry();
}
class Father {//类
public Father(String name) {//构造器
System.out.println("接收到name=" + name);
}
public void test() {}
}
abstract class Animal { //抽象类
abstract void eat();
}
/*
tiger的运行类型=class com.hspedu.innerclass.Outer04$1
IA的匿名内部类
接收到name=jack
father对象的运行类型=class com.hspedu.innerclass.Outer04$2
father的匿名内部类
animal对象的运行类型=class com.hspedu.innerclass.Outer04$3
animal的匿名内部类*/
代码分析:
-
使用场景:有时候我们只需要创建一个对象就不使用一个类了,如果创建该类,就有点浪费,为了简化开发,可以使用匿名内部类
-
匿名内部类创建的对象的运行类型的格式为:外部类的名字$数字(如Outer04$1,Outer04$2,Outer04$3)
-
使用细节
public class AnonymousInnerClass {
public static void main(String[] args) {
Outer05 outer05 = new Outer05();
outer05.f1();
}
}
class Outer05 {
private int n1 = 99;
public void f1() {
Person p = new Person(){
private int n1 = 88;
@Override
public void hi() {
System.out.println("匿名内部类的第一种调用方式");
}
};
p.hi();
new Person(){
@Override
public void hi() {
System.out.println("匿名内部类的第二种调用方式");
}
}.hi();//动态绑定, 运行类型是 Outer05$2
new Person(){}.hi(); //没有重写,调用Person的hi()
}
}
class Person {
public void hi() {
System.out.println("Person hi()");
}
}
/*
运行结果:
匿名内部类的第一种调用方式
匿名内部类的第二种调用方式
Person hi()
*/
代码分析:
-
不能添加访问修饰符,因为它的地位就是一个局部变量, 局部变量不能添加访问修饰符
-
两种方法使用,如上面代码
-
如果匿名内部类中没有重写Person的hi() 方法,则调用Person的hi() 方法;如果重写了,根据 动态绑定机制
调用重写的(运行类型的)hi() 方法
-
最佳使用场景
当做实参直接传递
public class InnerClassExercise01 {
public static void main(String[] args) {
//当做实参直接传递,简洁高效
f1(new IL() {
-
使用细节
package com.hspedu.innerclass;
public class MemberInnerClass01 {
public static void main(String[] args) {
Outer08 outer08 = new Outer08();
outer08.t1();
//外部其他类,使用成员内部类的两种方式
// 第一种方式
// outer08.new Inner08(); 相当于把 new Inner08()当做是outer08成员
// 这就是一个语法,不要特别的纠结.
Outer08.Inner08 inner08 = outer08.new Inner08();
inner08.say();
// 第二方式 在外部类中,编写一个方法,可以返回 Inner08对象
Outer08.Inner08 inner08Instance = outer08.getInner08Instance();
inner08Instance.say();
}
}
class Outer08 { //外部类
private int n1 = 10;
public String name = "张三";
private void hi() {
System.out.println("hi()方法...");
}
//1.注意: 成员内部类,是定义在外部内的成员位置上
//2.可以添加任意访问修饰符(public、protected 、默认、private),因为它的地位就是一个成员
public class Inner08 {//成员内部类
private double sal = 99.8;
private int n1 = 66;
public void say() {
//可以直接访问外部类的所有成员,包含私有的
//如果成员内部类的成员和外部类的成员重名,会遵守就近原则.
//,可以通过 外部类名.this.属性 来访问外部类的成员
System.out.println("n1 = " + n1 + " name = " + name + " 外部类的n1=" + Outer08.this.n1);
hi();
}
}
//方法,返回一个Inner08实例
public Inner08 getInner08Instance(){
return new Inner08();
}
//写方法
public void t1() {
//使用成员内部类
//创建成员内部类的对象,然后使用相关的方法
Inner08 inner08 = new Inner08();
inner08.say();
System.out.println(inner08.sal);
}
}
/*
运行结果:
n1 = 66 name = 张三 外部类的n1=10
hi()方法...
99.8
n1 = 66 name = 张三 外部类的n1=10
hi()方法...
n1 = 66 name = 张三 外部类的n1=10
hi()方法...
*/
代码分析:
-
外部类调用成员内部类,直接创建一个方法,方法中实例化内部类的对象进行调用
-
外部其他类,使用成员内部类的两种方式如上面代码所示
4 静态内部类
-
使用细节
public class StaticInnerClass01 {
public static void main(String[] args) {
//外部类 使用静态内部类
Outer10 outer10 = new Outer10();
outer10.m1();
System.out.println("=========");
//外部其他类 使用静态内部类
//方式1
//因为静态内部类,是可以通过类名直接访问(前提是满足访问权限)
Outer10.Inner10 inner10 = new Outer10.Inner10();
inner10.say();
//方式2
//编写一个方法,可以返回静态内部类的对象实例.
Outer10.Inner10 inner10_ = Outer10.getInner10();
System.out.println("=========");
inner10_.say();
}
}
class Outer10 { //外部类
private int n1 = 10;
private static String name = "外部类";
private static void cry() {}
//Inner10就是静态内部类
//1. 放在外部类的成员位置
//2. 使用static 修饰
//3. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
//4. 可以添加任意访问修饰符(public、protected 、默认、private),因为它的地位就是一个成员
//5. 作用域 :同其他的成员,为整个类体
static class Inner10 {
private static String name = "内部类";
public void say() {
//如果外部类和静态内部类的成员重名时,静态内部类访问的时,
//默认遵循就近原则,如果想访问外部类的成员,则可以使用 (外部类名.成员)
System.out.println(name + " 外部类name= " + Outer10.name);
cry();
}
}
public void m1() { //外部类---访问------>静态内部类 访问方式:创建对象,再访问
Inner10 inner10 = new Inner10();
inner10.say();
}
public static Inner10 getInner10() {
return new Inner10();
}
}
/*
运行结果:
内部类 外部类name= 外部类
=========
内部类 外部类name= 外部类
=========
内部类 外部类name= 外部类
*/
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/155201.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...