内部类:
内部类的定义:把类定义在其他类的内部,我们称之为内部类
内部类的特点:
1、内部类可以访问外部类的成员,包括私有
2、外部类要想访问内部类的成员,必须要创建内部类的对象
按照内部类在类中定义的位置不同,可以分为如下两种格式:
成员位置(成员内部类)
局部位置(局部内部类)
成员内部类:
1、定义在类的成员位置上
2、内部类可以方法外部类的成员,包括私有的
正确创建内部类的格式:
外部类名.成员内部类名 对象名 = new 外部类名().new 成员内部类名();
class Outer3{
private int num = 10;
class Inner{
public void show(){
System.out.println(num);
}
}
}
public class InnerClassDemo3 {
public static void main(String[] args) {
//外部类名.成员内部类名 对象名 = new 外部类名().new 成员内部类名();
// Outer3.Inner oi3 = new Outer3().new Inner();
// oi3.show();
Outer3 outer3 = new Outer3();
Outer3.Inner inner = outer3.new Inner();
inner.show();
}
}
成员内部类常见的修饰符:
private: 其他类不能直接创建内部类的对象,要想使用被private修饰内部类成员,必须在本类中间接的创建对象调用
static: 内部类如果是被satic修饰的时候,只能访问外部类中静态成员
当内部类是被静态所修饰的时候,出现了另外一种创建内部类的方式
格式如下:外部类名.内部类名 对象名 = new 外部类名.内部类名();
内部类中的访问案例:
class Outer2{
public int num=10;
class Inner2{
public int num=20;
public void fun(){
int num=30;
System.out.println(num);
// 输出30
System.out.println(this.num);
// 访问本类中的num。输出20
// 想要访问Outer中的成员num,两种方式:
System.out.println(new Outer2().num);
// 通过创建对象访问Outer2中的num。输出10
// 这里不可以用super,因为Outer2与Inner2不是继承关系
System.out.println(Outer2.this.num);
// 通过访问Outer中的this.num,同样输出10
}
}
}
public class Test2Demo {
public static void main(String[] args) {
Outer2.Inner2 inner=new Outer2().new Inner2();
inner.fun();
}
}
局部内部类:
1、定义在方法中的类
2、局部内部类可以直接方法外部类中所有成员
3、要想使用局部内部类中的方法,在定义局部内部类的成员方法中,创建局部内部类对象调用方法。
class Out{
private int num=10;
public void fun(){
int num2=20;
class Inner{
int num=30;
public void show(){
System.out.println(num);
System.out.println(num2);
// num2=34;这里不可以进行修改
// 在局部内部类中引用的本地的变量必须是最终变量或者实际上的最终变量
// 通过反编译工具观察发现,存在局部内部类的方法中定义的局部变量自动加上了final关键字
//在JDK1.8之后会自动加上final关键字
num=44;
System.out.println(num);
}
}
Inner i=new Inner();
i.show();
}
}
public class Test3Demo {
public static void main(String[] args) {
Out o=new Out();
o.fun();
}
}
在局部内部类中引用的本地的变量必须是最终变量或者实际上的最终变量
通过反编译工具观察发现,存在局部内部类的方法中定义的局部变量自动加上了final关键字
在JDK1.8之后会自动加上final关键字
匿名内部类:
语句定义格式:
new 类名(可以是抽象类也可以具体的类)/接口(){
要重写的方法;
};
匿名内部类存在的前提: 要存在一个类或者是一个接口,这个类可以是具体的类也可以是抽象的类
匿名内部类的使用:
interface Inter {
public abstract void show();
public abstract void show2();
}
/*
一开始的做法是需要定义一个类去实现接口,然后使用多态创建对象调用方法
class B implements Inter{
@Override
public void show() {
System.out.println("这是show方法");
}
@Override
public void show2() {
System.out.println("这是show2方法");
}
}
Inter inter=new B();
*/
class Outer3{
public void fun(){
// 使用匿名内部类的方式创建对象实现接口inter中的方法
new Inter(){
@Override
public void show() {
System.out.println("这是show方法");
}
@Override
public void show2() {
System.out.println("这是show2方法");
}
}.show();
// 使用匿名内部类的方式创建对象调用show方法
new Inter(){
@Override
public void show() {
System.out.println("这是show方法");
}
@Override
public void show2() {
System.out.println("这是show2方法");
}
}.show2();
// 使用匿名内部类的方式创建对象调用show2方法
// 当将来需要使用匿名内部类去调用方法时,若是方法很多则需要创建很多空间,很麻烦,因此给予匿名内部类一个名字
// 使用接口多态的形式去命名
Inter inter = new Inter() {
@Override
public void show() {
System.out.println("这是show方法");
}
@Override
public void show2() {
System.out.println("这是show2方法");
}
};
inter.show();
inter.show2();
}
}
public class Test4Demo {
public static void main(String[] args) {
Outer3 outer3=new Outer3();
outer3.fun();
}
}
匿名内部类在开发中的使用:
interface Human{ public abstract void sleep(); } class Man{ Human human; public Man() { } public Man(Human human) { this.human = human; } public void run(Human human){ human.sleep(); } } public class Test5Demo { public static void main(String[] args) { // 使用匿名内部类创建对象并实现接口,调用sleep Human human =new Human(){ @Override public void sleep() { System.out.println("赶快碎觉,小命要紧"); } }; human.sleep(); // 使用匿名内部类对象传参(构造方法创建对象,调用run方法) Man man= new Man(new Human(){ @Override public void sleep() { System.out.println("这是带参构造方法"); } }); man.run(new Human() { @Override public void sleep() { System.out.println("赶快碎觉,小命要紧"); } }); // 改进:还是使用带参构造创建对象,但是使用链式编程: new Man(new Human(){ @Override public void sleep() { System.out.println("这是带参构造方法"); } }).run(new Human() { @Override public void sleep() { System.out.println("赶快碎觉,小命要紧"); } }); // 另一种写法:通过Man类中的成员变量访问sleep方法 new Man(new Human() { @Override public void sleep() { System.out.println("这是带参构造方法"); } }).human.sleep(); } }
有关匿名内部类的例题:
interface Inter {
void show();
}
class Outer {
//补齐代码
}
class OuterDemo {
public static void main(String[] args) {
Outer.method().show();
}
} 要求在控制台输出”HelloWorld”
分析:
1、根据main方法调用的代码推出第一个结论:method()是静态的,可以直接通过类名访问/调用
2、根据main方法中调用完method()方法之后,还能继续调用方法,我们判定method()是有返回值的
3、再观察后发现,show()方法恰好是Inter2接口中的方法,所以返回值类型是接口Inter2类型
4、根据调用method()方法的参数是空,所以判定method()方法没有参数
完整代码如下:
interface Inter2{ void show(); } class Outer8{ public static Inter2 method(){ //============这里是需要补齐的代码============== return new Inter2() { @Override public void show() { System.out.println("HelloWorld"); } }; } //============这里是需要补齐的代码=============== } public class InnerClassDemo9 { public static void main(String[] args) { Outer8.method().show(); } }
常用类:
相对路径:将该项目作为根目录(test.Test.src.com.changyonglei)
绝对路径/完整路径:带上盘符: (D:\IdeaProjects\src\test\Test\src\com\changyonglei\Student.java)
Object:API帮助文档中的解释:
Class Object是类Object结构的根。
每个class都有Object作为超类。 所有对象(包括数组)都实现了这个类的方法。 java中每个类都直接或者间接的继承了Object类
Object类中的方法:
public int hashCode()
返回对象的哈希码值。
支持这种方法是为了散列表,如HashMap提供的那样。 注意:这里的哈希码值是根据哈希算法计算出来的一个值。这个值和地址有关系,但是这里返回的地址值并不是实际的地址值
现在就简单理解为地址值的另外一种表现形式。
public final Class getClass()
返回的是该对象的类对象 返回此Object的运行时类。
返回的类对象是被表示类的static synchronized方法锁定的对象。
关于hashCode()和getclass()方法的案例:
public class StduentTest {
public static void main(String[] args) {
Student s1 = new Student();
System.out.println(s1.hashCode()); // 1163157884
Student s2 = new Student();
System.out.println(s2.hashCode()); // 1956725890
Student s3 = s1;
System.out.println(s3.hashCode()); // 1163157884
System.out.println("=========================================");
Student s4 = new Student();
System.out.println(s4.getClass());
// class test.Test.src.com.tys.day13.ketang.changyonglei.Student
Class studentClass = s4.getClass();
// 返回由类对象表示的实体的名称(类,接口,数组类,原始类型或void),作为String 。
System.out.println(studentClass.getName());
// test.Test.src.com.tys.day13.ketang.changyonglei.Student
//链式编程
System.out.println(s4.getClass().getName());
// test.Test.src.com.tys.day13.ketang.changyonglei.Student
}
}
测试结果:
public String toString()
返回对象的字符串表示形式。
一般来说, toString方法返回一个“textually代表”这个对象的字符串。 结果应该是一个简明扼要的表达,容易让人阅读。 建议所有子类覆盖此方法。
该toString类方法Object返回一个由其中的对象是一个实例,该符号字符`的类的名称的字符串@ ”和对象的哈希码的无符号的十六进制表示。
换句话说,这个方法返回一个等于下列值的字符串:
getClass().getName() + '@' + Integer.toHexString(hashCode())
重写之前简单理解为:运行时的类的相对路径+调用hsahCode结果的地址值
Integer: public static String toHexString(int i)
返回整数参数的字符串表示形式,作为16位中的无符号整数。 将哈希值转化一个地址值。
我们虽然掌握了toString()的方法使用,但是呢打印的一个结果是一个我们看不懂的地址值,换句话我们拿到这个结果没有意义
返回对象的字符串表示形式,实际上我们更想去看的是该对象中各个成员变量的值。
恰好toString()方法是被public修饰的,也恰好它的返回值是String类型的,所以我们可以在其他类中对它做重写
重写之后的toString()表示的是打印对象中各个成员变量的值
今后无特殊情况不需要自己手写,自动生成即可。
一个标准类的4.0版本:
成员变量:使用private关键字修饰
构造方法:一个无参。一个带所有参数的方法。
成员方法:setXxx(...)/getXxx()
toString():自动生成即可,替代掉我们之前show方法。
重写之后的toString方法案例
public class Student2 extends Object { private String name; private int age; public Student2() { } public Student2(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } // @Override // public String toString() { // // //========重写之后的toString======== @Override public String toString() { return "Student2{" + "name='" + name + '\'' + ", age=" + age + '}'; } //========重写之后的toString======== } //测试类 public class StudentTest2 { public static void main(String[] args) { Student2 s = new Student2(); System.out.println(s.toString()); // test.Test.src.com.tys.day13.ketang.changyonglei.Student2@4554617c System.out.println("======================================="); System.out.println(s.getClass().getName()); //test.Test.src.com.tys.day13.ketang.changyonglei.Student2 System.out.println("======================================="); //toString()等价于getClass().getName() + '@' + Integer.toHexString(hashCode()) //getClass().getName() + '@' + Integer.toHexString(hashCode()) //s.getClass().getName()+'@'+ Integer.toHexString(s.hashCode()) //this.getClass().getName()+'@'+ Integer.toHexString(this.hashCode()) System.out.println(s.toString()); System.out.println(s.getClass().getName()+"@"+Integer.toHexString(s.hashCode())); System.out.println("========================================"); Student2 s2 = new Student2("小王", 18); System.out.println(s2.toString()); } }
输出结果:
public boolean equals(Object obj)
指示一些其他对象是否等于此。
今后我们想要弄清楚一个方法的实现的时候,想要弄明白结果是为什么的时候,看源码 将鼠标光标放置要看的方法上,按下ctrl+鼠标左键查看源码
通过观察源码发现:Object类中的equals方法实现底层依旧是==
public boolean equals(Object obj) { return (this == obj); }
而==比较引用数据类型的时候,比较是地址值,当地址值不一样的时候,返回的是false
==:
基本数据类型的时候:比较的是两个值是否一样
引用数据类型的时候:比较的是两个对象的地址值是否一样
equals:
只能比较的引用数据类型
实际开发的时候,调用equals方法更希望它比较的是成员变量的值是否一样
所以我们应该在子类中进行重写 不需要我们自己动手,自动生成即可
总结: 重写之前的equals方法比较的是地址值,重写之后的equals比较的是成员变量值
重写之后的equals方法案例:
import java.util.Objects; public class Student3 extends Object { private String name; private int age; public Student3() { } public Student3(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Student2{" + "name='" + name + '\'' + ", age=" + age + '}'; } // ========这是重写之后的equals方法======== @Override public boolean equals(Object o) { //this -- s1 //o -- s2 if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Student3 student3 = (Student3) o; return age == student3.age && Objects.equals(name, student3.name); } // ========这是重写之后的equals方法======== } //测试类: public class StudentTest3 { public static void main(String[] args) { Student3 s1 = new Student3("小刘", 18); Student3 s2 = new Student3("小刘", 18); System.out.println(s1==s2); // false Student3 s3 = s1; System.out.println(s1==s3); // true System.out.println("=========================="); System.out.println(s1.equals(s2)); //true System.out.println(s1.equals(s3)); // true System.out.println(s1.equals(s1)); // true Student3 s4 = new Student3("小王", 19); System.out.println(s1.equals(s4));//false } }
输出结果:
protected void finalize():
当垃圾收集确定不再有对该对象的引用时,垃圾收集器在对象上调用该对象。 一个子类覆盖了处理系统资源或执行其他清理的finalize方法。 简单理解这个方法为用于垃圾回收的,什么时候回收,不确定 GC机制,标记法。
protected Object clone()
创建并返回此对象的副本。 执行特定的克隆工作。
其他包中的子类要想使用被protected修饰的方法,使用super关键字调用。
clone的方法Object执行特定的克隆操作。
首先,如果此对象的类不实现接口Cloneable ,则抛出CloneNotSupportedException 。
一个类要想使用clone(),就必须实现Cloneable接口
通过观察API发现,Cloneable接口中没有常量,也没有抽象方法 今后看到类似于Cloneable一样,里面什么都没有的接口,我们称之为标记接口。
拷贝在IT行业中常见两种:
浅拷贝: 浅拷贝是指我们拷贝出来的对象的内部引用类型变量和原来的对象内部引用类型变量的地址值是一样的(指向的是同一个对象) 但是整个拷贝出来的对象和新对象不是同一个地址值。
深拷贝: 全部拷贝对象的内容,包括内存的引用类型也进行拷贝,拷贝的时候,重新创建一个对象,成员变量值和原来被拷贝的一样。 但是后续再对拷贝后的引用数据类型变量做修改,不会影响到原来被拷贝的。
拷贝案例:
//Cop1实现了标记接口Cloneable,表示这个允许被克隆
class Cop1 extends Object implements Cloneable {
private String name;
private int age;
private Demo demo;//定义引用数据类型变量
public Cop1() {
}
public Cop1(String name, int age, Demo demo) {
this.name = name;
this.age = age;
this.demo=demo;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Demo getDemo() {
return demo;
}
public void setDemo(Demo demo) {
this.demo = demo;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Cop1 cop1 = (Cop1) o;
return age == cop1.age && name.equals(cop1.name) && demo.equals(cop1.demo);
}
@Override
public String toString() {
return "Cop1{" +
"name='" + name + '\'' +
", age=" + age +
", demo=" + demo +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
//测试类:
public class Test7Demo {
public static void main(String[] args) throws CloneNotSupportedException {
Demo demo=new Demo();
Cop1 c1=new Cop1("小黑",24,demo);
Object c2 = c1.clone();//c1克隆对象c2。隐含一个多态
Cop1 c3=(Cop1)c1;//向下转型
System.out.println(c1.toString());//拷贝前对象的成员变量值
System.out.println(c3.toString());//拷贝后对象的成员变量值
System.out.println("=============================");
System.out.println("c1中demo对象的地址值:"+c1.getDemo().hashCode());
System.out.println("c2中demo对象的地址值:"+c3.getDemo().hashCode());
//拷贝前的demo与拷贝后demo的地址值做比较
//发现demo地址值是一样的
System.out.println("=============================");
System.out.println("c1整体对象的地址值:"+c1.hashCode());
System.out.println("c2整体对象的地址值:"+c2.hashCode());
//拷贝前整体对象的地址值与拷贝后整体对象的地址值
//发现拷贝后的地址值与原来对象的地址值不一样
System.out.println("=============================");
}
}
输出结果:
通过观察输出结果:得出结论:clone()是属于浅拷贝的