面向对象编程高级部分知识汇总(类变量与类方法详解、main方法详解、代码块详解、单例设计模式介绍、final关键字详解、抽象类详解、接口详解、内部类详解)
类变量和类方法
类变量
在类属性中加一个变量conut,在构造时+1,来统计该类的对象个数
public class staticbianl {
public static void main(String[] args) {
Child c1 = new Child("mxy");
Child c2 = new Child("yll");
Child c3 = new Child("Yll");
System.out.println(c3.count);
//输出3
}
}
class Child{
private String name;
public static int count = 0;//会被所有对象共享
public Child(String name) {
this.name = name;
count++;
}
}
在JDK8之前存在方法区的静态域中,JDK8以后存放在堆中的这个类对应的class对象的最后。
不管static在哪里,所有对象使用没区别,且都符合以下共识:
被同一个类的所有对象共享
在类加载时就形成
什么是类变量
静态变量/静态属性,一个类的所有对象共享的变量,任一对象访问均取到相同值,任一对象修改也修改同一变量。
定义语法
- 访问修饰符 static 数据类型 变量名(推荐)
- static 访问修饰符 数据类型 变量名
访问
- 类名.类变量名(推荐)
- 对象名.类变量名
- 注意访问必须遵守其访问权限
细节
- 需要让所有对象共享一个属性时,使用类对象
- 加上static称为类变量或者静态变量,否则是普通变量
- 推荐使用 类名.类变量名来访问(满足访问权限情况下)
- 实例变量不能通过类名.变量名来访问
- 静态变量在类加载的时候就已经创建了,所以我们在没有创建对象实例的时候就能通过类名.类变量来使用
- 类变量的生命周期随着类的加载而开始,随着类的消亡而销毁
- 普通成员方法和静态成员方法都可以修改静态变量
类方法
创建:访问修饰符 static 数据返回类型 方法名(){} (推荐)
调用:类名.类方法名 或 对象名.类方法名
使用场景:方法中不涉及到任何和对象相关的成员,可以将方法设计成静态方法,这样不创建实例就能调用某个方法(当作工具使用),提高开发效率
好处:不用实例化对象就可以调用,在内存中不用重新开辟空间
可以参考Math类,其中有很多类方法
注意事项
类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区
类方法中没有this参数,但是普通方法隐含this参数
类方法可以通过类名或对象名调用
普通方法和对象有关,需要通过对象名来调用,比如对象名.方法名(参数),不能通过类名调用
类方法中只能访问静态变量或静态方法 不能 访问非静态变量或非静态方法
一个小用法:
public static void payFee(double fee){
//this.fee = fee; 静态方法无this参数,不能使用this
//但是可以使用类名.静态属性来调用,避免命名冲突
Child.fee = fee;
}
小结:
1. 静态方法只能访问静态成员
2. 非静态方法可以访问所有成员
3. 注意要遵守访问权限规则
理解main方法语法
public static void main(String[] args){}
细节
-
main方法由JVM调用
-
JVM需要调用main方法,因此访问权限必须是public
-
JVM在执行main方法时不必创建对象,因此该方法必须是static
-
接收String类型的数组参数,该参数中保存执行Java命令时传递给所允许的类的参数
-
在main方法中可以使用当前类中的所有静态方法或静态属性,但是非静态方法和非静态属性需要通过创建对象来使用
-
args 数组参数从何处传入?在执行过程中输入的(执行程序时通过命令行控制)
public class Main01 {
public static void main(String[] args) {
for(String arg: args){
System.out.println(arg);
}
}
}
代码块
语法
[修饰符]{
代码
};
public class CodeBlock1 {
public static void main(String[] args) {
Movie movie = new Movie("三体");
}
}
class Movie{
private String name;
private double price;
private String author;
//若需要在三个构造器中都执行相同语句,可以把相同语句防在一个代码块中
//这样不管要用哪个构造器创建对象都会先调用代码块的内容
{
System.out.println("电影开始放映了...");
}
public Movie(String name) {
System.out.println("构造器被调用");
this.name = name;
}
public Movie(String name, double price) {
this.name = name;
this.price = price;
}
public Movie(String name, double price, String author) {
this.name = name;
this.price = price;
this.author = author;
}
}
//输出:
//电影开始放映了...
//构造器被调用
//可以看出代码块是先于构造器调用的
//有利于重用
注意
-
修饰符可选,写的话只能写static
-
代码块分为两类,一类是用static修饰的静态代码块,一类是没有static修饰的 普通代码块
-
代码可以为任何逻辑语句
-
;号可以写上,可以用省略
-
代码块先于构造器调用
-
static代码块 随着类的加载而执行,只会执行一次,如果是普通代码块,每创建一个对象就执行一次
-
注意普通代码块在对象创建时执行,在类加载时不执行,比如A.a1;时,普通代码块就不会执行
public class CodeBlock1 {
public static void main(String[] args) {
Movie movie = new Movie("三体");
Movie movie1 = new Movie("LaLa Land");
Movie movie2 = new Movie("让子弹飞");
}
}
class Movie{
private String name;
private double price;
private String author;
//若需要在三个构造器中都执行相同语句,可以把相同语句防在一个代码块中
//这样不管要用哪个构造器创建对象都会先调用代码块的内容
{
System.out.println("普通代码块");
}
static{
System.out.println("static代码块");
}
public Movie(String name) {
System.out.println("构造器被调用");
this.name = name;
}
public Movie(String name, double price) {
this.name = name;
this.price = price;
}
public Movie(String name, double price, String author) {
this.name = name;
this.price = price;
this.author = author;
}
}
//运行结果
static代码块
普通代码块
构造器被调用
普通代码块
构造器被调用
普通代码块
构造器被调用
-
类什么时候被加载 (重要)
-
public class CreateTest { public static void main(String[] args) { new A(); System.out.println(A.a1); } } class A extends B{ public static int a1 = 699; static{ System.out.println("类A被加载"); } } class B{ static { System.out.println("类B被加载"); } } //输出: //类B被加载 //类A被加载 //699
执行顺序问题
-
1. 调用静态代码块和静态属性的初始化(静态属性和静态代码块初始化调用的优先级一致,若有多个,按定义顺序调用)
public class CodeBlockDetail { public static void main(String[] args) { AA a = new AA(); } } class AA{ private static int n1 =getVal(); static { System.out.println("静态代码块被调用"); } public static int getVal(){ System.out.println("getVal被调用"); return 699; } } //输出: //getVal被调用 //静态代码块被调用
public class CodeBlockDetail { public static void main(String[] args) { AA a = new AA(); } } class AA{ static { System.out.println("静态代码块被调用"); } private static int n1 =getVal(); public static int getVal(){ System.out.println("getVal被调用"); return 699; } } //输出: //静态代码块被调用 //getVal被调用
-
2. 普通代码块和普通属性初始化调用的优先级一致,若有多个,按定义顺序调用
-
3. 调用构造方法
构造器 最前面隐藏了super()和调用普通代码块
创建子类对象时,静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序:
public class CodeBlockDetail {
public static void main(String[] args) {
AA a = new AA();
}
}
class AA extends A{
private static int n1 =getVal();
private int n2;
{
System.out.println("AA的普通代码块被调用");
}
public AA(){
//super();
//调用本类普通代码块
System.out.println("AA的无参构造器");
}
public AA(int n2) {
//super();
//调用普通代码块
this.n2 = n2;
}
static {
System.out.println("AA的静态代码块被调用");
}
public static int getVal(){
System.out.println("AA的静态方法被调用");
return 699;
}
}
class A extends B{
public static int a1 = 699;
static{
System.out.println("类A静态代码块");
}
public A() {
System.out.println("A的构造器");
}
}
class B{
static {
System.out.println("类B静态代码块");
}
}
//输出:
//静态成员执行之前先执行父类静态成员的内容
类B静态代码块
类A静态代码块
//然后执行本类静态成员
AA的静态方法被调用
AA的静态代码块被调用
//然后执行父类构造方法super()
A的构造器
//然后执行本类普通代码块
AA的普通代码块被调用
//然后执行本类无参构造器
AA的无参构造器
单例设计模式
设计模式:大量实践中总结和理论化之后优选的代码结构
单例模式:采取一定方法,保证在整个软件系统中,对某个类只能存在一个对象实例(不能再有第二个),并且该实例只能提供一个获得其对象实例的方法。
两种模式
饿汉式单例模式的实现:
public class singleTest {
public static void main(String[] args) {
//通过静态方法可以直接使用非静态方法
Single01.getInstance().sayHi();
//通过方法可以获取对象
Single01 single01 = Single01.getInstance();
single01.sayHi();
Single01 single011 = Single01.getInstance();
//无论用instance获取几次对象,得到的都是同一个对象
System.out.println(single01==single011);
}
}
//输出:
//Hi~
//Hi~
//true
class single01 {
//构造器私有化
private single01(){};
//提供一个静态属性类型 single01
//先创建一个这个对象的实例
private static single01 instance = new single01();
//创建一个public的静态方法,用于访问该实例
public static single01 getInstance(){
return instance;
}
public void sayHi(){
System.out.println("Hi~");
}
}
注意一定要用static对instance与getInstance进行修饰,这样在使用时才能不new一个对象就能使用此方法与此成员。
为什么叫做 “饿汉式” 还没有使用实例的时候就创建好了(着急) 比较占用内存空间
懒汉式单例模式的实现:
饿汉式可能造成创建了对象但是没有使用,造成较大的内存浪费。为了弥补这一弊端,使用懒汉式(使用了才会出现)
实现步骤
-
1、构造器私有化
-
2、定义一个私有静态对象,但是不new
-
3、暴漏一个静态公共方法getInstance(),判断私有静态对象是否被创建,被创建就原封不动的返回,未创建就创建一个(由于构造器私有化,只能在getInstance()中创建对象,不会在外部创建)。
实现示例:
package com.single_design.lazy;
public class single02{
public static void main(String[] args) {
//不new没法用
// new Cat("mxy");
Cat cat1 = Cat.getInstance();
System.out.println(cat1.n);
Cat cat2 = Cat.getInstance();
System.out.println(cat1 == cat2);
}
}
//输出:
//构造器被使用
//999
//true
class Cat {
private String name;
//1、构造器私有化
private Cat(String name) {
System.out.println("构造器被使用");
this.name = name;
}
public static int n = 999;
//2、定义一个静态属性对象,但不new
private static Cat cat;
//3、暴漏一个静态公共方法,可以返回一个Cat对象
public static Cat getInstance(){
//如果还没有创建猫,就new 一个
if(cat == null){
cat = new Cat("mxy");
}
return cat;
}
}
饿汉式与懒汉式对比:
-
主要区别是对象创建的时机不同,饿汉式是在类加载的时候就创建对象,而懒汉式是在使用时才创建对象
-
饿汉式不存在线程安全问题,懒汉式存在线程安全问题(同时有多个线程进入getInstance方法时,在一瞬间会创建多个对象,没有真正实现单例模式)
-
饿汉式存在资源浪费的可能性,若一个对象没使用就会浪费资源
-
java.lang.Runtime就是经典的单例模式
final关键字
可修饰类、属性、方法和局部变量
用途:
使用细节:
-
final修饰的属性又称为常量, 名字要按照XX_XX_XX来命名
-
final修饰的属性在定义时必须赋初值,赋值可选以下位置:
-
如果final修饰的属性是静态(public final static int MAX_NUM = 23435465;)的,则初始化的位置只能是
-
final类不能继承,但是可以实例化对象(“能使用”)
-
类不是final类但是有final方法,则该方法虽不能重写,但是可以被继承(“能使用”)
-
如果一个类已经是final类了,就不用再把方法也定义成final方法了
-
final不能修饰构造器
-
final往往和static搭配使用,底层编译器对此做了优化处理(用类中的属性不会导致类加载),效率更高
public class StaticFinal {
public static void main(String[] args) {
System.out.println(AA.num);
}
}
class AA{
public final static int num = 1000;
static {
System.out.println("类AA被加载了");
}
}
//输出
1000
//若不加final,会使得类被加载,浪费内存空间
抽象类
父类的某些方法不能确定如何实现时,可以将其声明为抽象方法,那么这个类就是抽象类
抽象方法:没有实现的方法,即没有方法体
//方法声明为抽象方法的时候也要将其类声明为抽象类
abstract class Animal {
private String name;
public abstract void eat();
}
注意:
-
抽象方法不能有方法体
-
一旦一个方法被声明为抽象方法,其所在的类必须被声明为抽象类
-
子类一定要实现抽象方法,否则会报错
-
抽象类不能被实例化(不能new)
-
抽象类可以没有抽象方法
-
abstract只能修饰类或者方法,不能修饰属性
-
抽象类可以拥有任意成员(因为其本质还是类,可以有类的各种成员)构造器、属性、非抽象方法、代码块
-
若一个类继承了抽象类,则这个类必须要实现这个抽象类所有的抽象方法,除非这个子类也是抽象的
-
抽象类、方法不能使用private、final和static来修饰,这些关键字都是和重写相悖的
抽象类最佳实践:模板设计模式
好处:提高代码复用性
示例:若不使用抽象类,则需要在每个类中都写calculateTime
public abstract class TestTemplate {
public abstract void job();
public void calculateTime(){
long start = System.currentTimeMillis();
job();//动态绑定机制,优先寻找子类的job()方法
long end = System.currentTimeMillis();
System.out.println("job()方法执行时间:"+(end - start));
}
}
public class TestFile {
public static void main(String[] args) {
A a = new A();
B b = new B();
a.calculateTime();
b.calculateTime();
}
}
class A extends TestTemplate{
public void job(){
long sum = 0;
for (int i = 0; i < 1000000; i++) {
sum += i;
}
}
}
class B extends TestTemplate{
public void job(){
int sum = 1;
for (int i = 0; i < 100000; i++) {
sum *= i;
}
}
}
接口
快速入门:
基本语法:
interface 接口名{
//属性
//方法
}
class 类名 implements 接口{
//自己的属性
//自己的方法
//必须实现的接口的抽象方法
}
public interface UsbInterface {
public void start();
public void stop();
}
public class Phone implements UsbInterface {
@Override
public void start() {
System.out.println("Phone start");
}
@Override
public void stop() {
System.out.println("Phone stop");
}
}
public class Camera implements UsbInterface {
@Override
public void start() {
System.out.println("Camera start");
}
@Override
public void stop() {
System.out.println("Camera stop");
}
}
public class Computer {
public void work(UsbInterface usbInterface){
usbInterface.start();
usbInterface.stop();
}
}
public class Test01 {
public static void main(String[] args) {
Phone phone = new Phone();
Camera camera = new Camera();
Computer computer = new Computer();
computer.work(phone);
computer.work(camera);
}
}
//输出:
Phone start
Phone stop
Camera start
Camera stop
应用场景:
便于控制与管理,保证程序的规范性
注意事项:
-
在Jdk7.0之前,接口里的所有方法都没有方法体
-
在Jdk8.0后,接口类中可以有静态方法,默认方法,即可以有方法的具体实现(要用default关键字修饰)
public interface Interface01 { public void hi(); //默认方法 default public void ok(){ System.out.println("ok!"); } //静态方法 public static void happy(){ System.out.println("Happy!"); } }
-
接口中的所有未实现的方法都是抽象方法,可以省略abstract
-
接口不能被实例化,继承接口的子类才能被实例化
-
接口中所有方法都默认是public方法,接口中的抽象方法可以不被abstract修饰
-
抽象类实现接口,可以不实现接口的方法(都是抽象方法)
public abstract class Up implements UsbInterface { }
-
一个类可以同时实现多个接口(一个人只能有一对血缘双亲,但是可以有很多个项目经理)
interface IA{ void hi(); } interface IB{ void say(); } class Pp implements IA, IB{ @Override public void hi() { System.out.println("hi"); } @Override public void say() { System.out.println("say"); } }
-
接口中的属性只能是final的,而且因此的修饰符是public static final修饰符,例如:public static final int a = 10;(必须初始化)
public class Interface02 { public static void main(String[] args) { //证明是static System.out.println(IA.a); //证明是final: //IA.a = 20;//报错 } } interface IA{ int a = 10;//等价于public static final int a = 10; void hi(); }
-
接口中属性的访问形式:接口名字.属性名
-
接口不能继承其他类,但是可以继承其他接口
interface IA{ int a = 10;//等价于public static final int a = 10; void hi(); } interface IB{ void say(); } interface IC extends IB, IA{ void hello(); }
-
接口的修饰符只能是public 与默认
-
接口的实现与继承类似,可以继承接口中的属性,例如:
public class Test02 { public static void main(String[] args) { B b = new B(); System.out.println(A.a); System.out.println(b.a); System.out.println(B.a); } } interface A{ int a = 33; } class B implements A{ }
接口与继承对比
继承一个类,类中的方法就自然的能被子类使用
接口相当于对单继承模式的一个补充,例子:
public class Test03 {
public static void main(String[] args) {
LittleMonkey sun = new LittleMonkey("孙行者");
sun.climb();
sun.fly();
sun.swim();
}
}
class Monkey{
public String getName() {
return name;
}
public void setName(String name) {
setName(name);
}
private String name;
public Monkey(String name) {
this.name = name;
}
}
class LittleMonkey extends Monkey implements Bird, Fish{
public LittleMonkey(String name) {
super(name);
}
public void climb(){
System.out.println(getName()+" 会爬树");
}
@Override
public void fly() {
System.out.println(getName()+" 会飞");
}
@Override
public void swim() {
System.out.println(getName()+" 会游泳");
}
}
interface Bird{
void fly();
}
interface Fish{
void swim();
}
总结:
-
子类继承父类就自动拥有父类的功能
-
如果子类需要拓展功能,就可以通过实现接口的方式实现拓展
-
解决问题的区别
-
接口比继承更加灵活,继承需要is - a,而接口只需要 like - a
-
接口在一定程度上实现代码解耦
接口的多态特性
多态参数
public class Test01 {
public static void main(String[] args) {
Phone phone = new Phone();
Camera camera = new Camera();
Computer computer = new Computer();
computer.work(phone);
computer.work(camera);
}
}
public class Computer {
//解读:
//usbInterface形参是接口类型UsbInterface
//此方法接收实现了UsbInterface接口的类的对象实例
//只要是实现了UsbInterface的对象都可以传入
public void work(UsbInterface usbInterface){
usbInterface.start();
usbInterface.stop();
}
}
public class Camera implements UsbInterface {
@Override
public void start() {
System.out.println("Camera start");
}
@Override
public void stop() {
System.out.println("Camera stop");
}
}
public class Phone implements UsbInterface {
@Override
public void start() {
System.out.println("Phone start");
}
@Override
public void stop() {
System.out.println("Phone stop");
}
}
接口引用可以指向实现了接口的类的对象
public class Test04 {
public static void main(String[] args) {
//接口引用可以指向实现了接口的类的对象
UsbInterface usb1 = new Phone();
usb1.start();
usb1 = new Camera();
usb1.stop();
}
}
多态数组
接口同样可以向下转型
public class PolyArray {
public static void main(String[] args) {
UsbInterface[] usbArray = new UsbInterface[5];
usbArray[0] = new Phone();
usbArray[1] = new Camera();
usbArray[2] = new Camera();
usbArray[3] = new Phone();
usbArray[4] = new Camera();
for (int i = 0; i < usbArray.length; i++) {
usbArray[i].start();
//若是Phone类,调用独有call方法
if(usbArray[i] instanceof Phone){
//向下转型
Phone phone = (Phone)usbArray[i];
phone.call();
}
usbArray[i].stop();
}
}
}
接口的多态传递现象
public class MoreGive {
public static void main(String[] args) {
IG ig = new Teacher();
IH ih = new Teacher();
ig.hi();
}
}
interface IH{
void hi();
}
interface IG extends IH{}
class Teacher implements IG{
@Override
public void hi() {
System.out.println("hi");
}
}
当继承的父类与实现的接口有重复属性时:
public class Test06 {
public static void main(String[] args) {
C1 c1 = new C1();
c1.PrintX();
}
}
interface A1{
int x = 10;
}
class B1{
int x = 20;
}
class C1 extends B1 implements A1{
public void PrintX(){
//System.out.println(x);//错误,原因不明确的x
//可以明确的指定x
//访问接口的x就使用A1.x,父类的就super.x
System.out.println(A1.x);
System.out.println(super.x);
}
}
内部类(重难点)
一个类的内部又完整的嵌套了另一个类的结构,被嵌套的类称为内部类,嵌套它的类称为外部类
类的五大成员:
基本语法
class Outer{
class Inner{
}
}
class Other
内部类的分类:
局部内部类的使用
定义在方法中或者代码块里,作用域在方法体或者代码块中,本质仍是一个类
-
可以直接访问外部类的所有成员,包括私有成员
-
不能添加访问修饰符,因为它的地位与一个局部变量相同,局部变量是不可以用修饰符的。但是与局部变量相同,可以用final 修饰
-
作用域:仅仅在定义它的方法或者代码块中
-
局部内部类访问外部类的成员可以直接访问,而外部类访问局部内部类的成员需要创建对象再访问,且必须在作用域内
使用示例:
package com.innerclass_;
public class Test01 {
public static void main(String[] args) {
Outer outer = new Outer(101);
outer.m1();
outer.inner01.speak();
}
}
class Outer{
class Inner01{//内部类,在Outer类的内部
public void speak(){
System.out.println("Inner01中的n1:" + n1);
}
}
Inner01 inner01 = new Inner01();
//作用域仅在定义它的方法中
private int n1 = 100;
public void m1(){
System.out.println("方法m1");
//可以用final修饰
final class Inner02{//局部内部类
//可以直接访问外部类的所有成员
public void f1(){
System.out.println("Inner02中的n1:"+n1);
System.out.print("Inner02中的:");
m2();
}
}
Inner02 inner02 = new Inner02();
//在方法中使用内部类需要创建Inner02对象,然后调用内部类方法即可
inner02.f1();
}
private void m2(){
System.out.println("方法m2");
}
{
System.out.println("Outer代码块");
}
public Outer(int n1) {
System.out.println("Outer构造器");
setN1(n1);
}
public int getN1() {
return n1;
}
public void setN1(int n1) {
this.n1 = n1;
}
}
//输出:
Outer代码块
Outer构造器
方法m1
Inner02中的n1:101
Inner02中的:方法m2
Inner01中的n1:101
-
外部其他类不能访问局部内部类(因为局部内部类是一个局部变量)
-
如果外部类和局部内部类的成员重名,默认遵守就近原则,如果想访问外部类的成员,可以使用外部类名.this.成员去访问。
示例:
public class Test01 { public static void main(String[] args) { Outer outer = new Outer(101); outer.inner01.getName(); outer.inner01.getInner01(); outer.inner01.sayHi(); outer.inner01.getOuterSayHi(); } } class Outer{ class Inner01{//内部类,在Outer类的内部 private String name = "yll"; public void speak(){ System.out.println("Inner01中的n1:" + n1); } public void getName(){ System.out.println("Inner01中的成员name:"+ name); //Outer本质是外部类的对象,即哪个对象调用了m1,Outer.this就是哪个对象 System.out.println("Inner02中的成员name:"+Outer.this.name); } public void getInner01(){ System.out.println(inner01); } public void sayHi(){ System.out.println("Inner01 say hi."); } public void getOuterSayHi(){ System.out.print("Inner01中访问sayHi:"); Outer.this.sayHi(); } } Inner01 inner01 = new Inner01(); //作用域仅在定义它的方法中 //Inner02 inner02 = new Inner02(); private String name = "mxy"; public void sayHi(){ System.out.println("Outer say hi"); } } //输出: Outer代码块 Outer构造器 Inner01中的成员name:yll Inner02中的成员name:mxy com.innerclass_.Outer$Inner01@1b6d3586 Inner01 say hi. Inner01中访问sayHi:Outer say hi
匿名内部类
匿名内部类是定义在外部类的局部位置中的类,且没有类名
new 类或接口(参数列表){
类体
};
匿名内部类解决的问题:
//基于接口的匿名内部类
//需求:想使用接口A,并创建对象,且此对象只使用一次,以后不再使用
//传统方式:写一个类实现接口,并创建对象使用
//不足之处:
// 假如需要用许多调用不同的cry方法的对象话需要创建许多类,不方便
// A a = new Tiger();
// a.cry();
// a = new Cat();
// a.cry();
//由此引出匿名类
例子(基于接口的匿名内部类):
public class AnonymousInnerClass {
public static void main(String[] args) {
Outer2 outer2 = new Outer2();
outer2.method();
}
}
class Outer2{
private int n1 = 10;
public void method(){
//基于接口的匿名内部类
//需求:想使用接口A,并创建对象,且此对象只使用一次,以后不再使用
//传统方式:写一个类实现接口,并创建对象使用
//不足之处:
// 假如需要用许多调用不同的cry方法的对象话需要创建许多类,不方便
// A a = new Tiger();
// a.cry();
// a = new Cat();
// a.cry();
//由此引出匿名类,使用匿名类简化开发
A tiger = new A(){
@Override
public void cry() {
System.out.println("Tiger cry~");
}
};
//tiger的运行类型是什么?就是匿名内部类
//底层:
// class XXXX implements A{
//
// @Override
// public void cry() {
// System.out.println("Tiger cry~");
// }
// }
//可以根据此方法来查看类名:(类名为外部类加$加序号)
System.out.println("tiger的运行类型="+tiger.getClass());
//输出为tiger的运行类型=class com.anonymous_.Outer2$1
//new 表示在底层创建了匿名类Outer2$1后立马就创建了实例,并把地址返回给tiger
//匿名类不能重复使用,但是对象可以
tiger.cry();
}
}
interface A{
public void cry();
}
class Tiger implements A{
@Override
public void cry() {
System.out.println("Tiger Cry~");
}
}
class Cat implements A{
@Override
public void cry() {
System.out.println("Cat meow meow~");
}
}
基于类的匿名内部类:
class Outer3{
public void sayHi(){
//基于类的匿名内部类
//father的编译类型:Father
//运行类型:Outer2$2
//不带大括号运行类型就是Father
//注意("mxy")会传递给Father构造器,但是不能重写Father构造器
Father father = new Father("mxy"){
@Override
public void speak() {
System.out.println("内部匿名类重写了speak方法");
}
};
//输出:class com.anonymous_.Outer2$1
System.out.println("father对象的运行类型:"+ father.getClass());
}
}
class Father{
private String name;
public void speak(){
System.out.println("类Father speak");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Father(String name) {
setName(name);
}
}
基于抽象类的匿名内部类:
抽象类的匿名类必须实现,原因与抽象类的子类必须实现相同
public class AnonymousTest {
public static void main(String[] args) {
Outer.play();
}
}
class Outer{
public static void play(){
Animal animal = new Animal() {
//必须实现
@Override
public void eat() {
System.out.println("小狗吃骨头...");
}
};
animal.eat();
}
}
abstract class Animal{
public abstract void eat();
}
匿名内部类细节:
-
匿名内部类即是类的定义,其本身也是一个对象
-
可以直接调用
class Outer{ public static void play(){ new Animal() { @Override public void eat() { System.out.println("小猫吃鱼..."); } }.eat(); //只调用一次eat方法可以这么写 } }
-
可以访问外部类的所有成员,包括私有的
-
不能添加访问修饰符,因为其地位就是局部变量
-
外部其他类不能访问匿名内部类
-
若外部类和匿名内部类的成员重名,匿名内部类访问时遵循就近原则,若非要访问外部类成员,可以用 外部类名.this.成员去访问
匿名内部类的实践:
-
当作实参直接传递,简洁高效
public class Use { public static void main(String[] args) { show(new AA(){ @Override public void cry() { System.out.println("I'm crying..."); } }); } public static void show(AA a){ a.cry(); } } //若不使用匿名内部类,需要对接口进行实现,然后再new对象传参,很麻烦(硬编码) interface AA{ public void cry(); }
成员内部类
成员内部类定义在外部类的成员位置,并且无static修饰
注意:
-
可以访问外部类所有成员,包括私有的
public class Test1 { public static void main(String[] args) { Outer outer = new Outer(); outer.t1(); } } class Outer{ private int n1 = 10; class Inner{ public void say(){ System.out.println("Outer的n1:"+n1); } } public void t1(){ Inner inner = new Inner(); inner.say(); } }
-
可以添加任意的访问修饰符(public protected 默认 private),因为其地位就是一个成员
-
作用域:
-
与外部类其他成员一致,为整个类体
-
成员内部类访问外部类可以直接访问
-
外部类访问成员内部类需要先创建再访问
-
外部其他类访问成员内部类(可以的),访问方式如下:
-
创建外部对象后new来访问
public class Test1 { public static void main(String[] args) { Outer outer = new Outer(); //1、 Outer.Inner inner = outer.new Inner(); //new Inner();Inner是成员 因此需要先创建Outer后再创建 } }
-
在外部类中创建一个返回new内部类的方法,通过调用外部类的方法来访问
public class Test1 { public static void main(String[] args) { Outer outer = new Outer(); outer.t1(); //2、 Outer.Inner inner1 = outer.getInner(); } }
-
-
-
如果外部类和内部类的成员重名时,内部类访问会遵顼就近原则, 因此若想访问外部类的成员,需要(外部类名.this.成员)去访问
静态内部类
定义在外部类的成员位置,有static修饰
注意:
-
可以直接访问外部类的所有静态成员,包括私有的,但是不能访问非静态成员
-
可以添加任意访问修饰符,因为其地位就是一个成员
-
作用域:同其他的成员一样,为整个类体
public class Test1 { public static void main(String[] args) { Outer outer = new Outer(); outer.useInner(); } } class Outer{ private static int n1 = 1000; private double nt = 99.99; public static class Inner{ public void say(){ System.out.println("Outer类中的n1:"+n1); //System.out.println("Outer类中的nt"+ nt); //不可访问非静态成员 } } public void useInner(){ Inner inner = new Inner(); inner.say(); } }
-
访问:
-
内部类可以访问外部类所有的静态成员,但是不能访问非静态成员
-
外部类访问内部类可以通过类名.静态成员来访问,或创建内部类访问其非静态成员
-
外部其他类访问内部类的方法:
-
1、由于是静态内部类,可以直接通过类名访问(在访问权限内)
public class Test1 { public static void main(String[] args) { //外部其他类使用Inner不需要新建Outer Outer.Inner inner = new Outer.Inner(); } }
-
2、编写一个方法,返回静态内部类(尽量写为静态方法)
public class Test1 { public static void main(String[] args) { //静态方法返回Inner Outer.Inner inner1 = Outer.getInner(); } }
-
-
-
重名:依然遵守就近原则,若要访问外部类,直接用外部类名.成员去访问(只能是静态成员)