文章目录
一、static
static修饰符可以修饰属性、方法、代码块
1.静态属性
(1)在类中,使用static修饰的属性,就是静态属性。
(2)静态属性是属于类的,可以直接使用类名来访问,也可以使用对象访问,但推荐使用类名访问
非静态属性,是属于对象的,一定要使用对象来访问,没有其他方式!
public class Demo{
static int num;
}
public static void main(String[] args){
Demo.num = 10; // 可以使用类名来访问
Demo demo = new Demo();
demo.num = 20; // 也可以对象来访问,但不推荐
}
(3)静态属性,是属于类的,并且是这个类所有对象共享的
无论是使用类访问静态属性,还是使用这个类的某个对象访问静态属性,效果是一样的,这个属性对这个类的所有对象都是可见的、共享的。
public class Demo{
static int num;
}
public static void main(String[] args){
Demo.num = 10;
Demo demo1 = new Demo();
Demo demo2 = new Demo();
System.out.println(demo1.num);//输出结果为 10
System.out.println(demo2.num);//输出结果为 10
Demo.num = 20;
System.out.println(demo1.num);//输出结果为 20
System.out.println(demo2.num);//输出结果为 20
demo1.num = 30;
System.out.println(demo1.num);//输出结果为 30
System.out.println(demo2.num);//输出结果为 30
}
(4)静态属性的存储位置:
类中的静态属性,跟随着类,一起保存在内存中的方法区。
当创建对象的时候,对象中只会保存类中定义的非静态属性的信息,而静态属性是不会进入到对象中的。
(5)静态属性的初始化:
无论是静态属性还是非静态属性,都必须进行初始化后才能使用,要么是系统给属性初始化赋默认值,要么是我们自己手动给属性赋值。
属性的初始化时间:
- 非静态属性:创建对象后,系统会自动给对象中的非静态属性做初始化赋默认值,也正是因为这个原因,非静态属性只有在创建对象后,使用对象才能访问。
- 静态属性:类加载到内存中(方法区)的时候,系统就会给类中的静态属性做初始化赋默认值,所以,即使还没有创建对象,只要这个类加载到了内存,就可以直接使用类名来访问静态属性,因为这个时候静态属性已经完成了初始化赋默认值的操作。
静态属性是属于类的,只要类加载到内存了,就可以使用类名来访问。
非静态属性是属于对象的,只有创建出对象了,使用对象才可以访问。
只有实例变量(非静态属性)才会保存在对象中,并做初始化操作。静态变量保存在类中,并做初始化操作。
2.静态方法
(1)在类中,使用static修饰的方法,就是静态方法。
(2)静态方法的调用:可以使用类名来调用,也可以使用对象来调用,但推荐使用类名
public class Demo{
public static void test(){ }
}
public static void main(String[] args){
Demo.test();//推荐的方式
Demo demo = new Demo();
demo.test();//可以调用,但是不推荐
}
(3)静态方法中不能调用类中的非静态方法或非静态属性
静态方法中,不能访问this,所有也就不能直接访问类中的非静态属性和非静态方法
在类加载的时候,JVM会优先给类中的静态属性做初始化,给类中的静态方法分配内存空间。而类中非静态属性的初始化,非静态方法的分配空间,是要等到创建对象之后才会进行的。
所以类加载完成之后,就可以直接使用类名访问静态属性和静态方法。
所以创建对象之后,才可以使用对象访问非静态属性和调用非静态方法。
- 在静态方法中,才不能直接调用类中非静态属性和非静态方法的原因:
在类加载完成的时候,往往在内存中,还没有创建这个类的对象,没有对象(也就没有this)就不能访问类中的非静态属性和非静态方法。- 在非静态方法中,可以直接访问类中的静态属性和静态方法的原因:
因为非静态方法都可以调用了,说明肯定已经创建对象了,同时也说明了这个类早就完成了类加载,那么类中的静态属性和静态方法,现在肯定是可以访问和调用的。
public class Demo{
public String num;
public static void test(){
this.num = 10;//编译报错
this.sayHello();//编译报错
}
public void sayHello(){}
}
(4)类中的构造器,可以给非静态属性做初始化,但是不能给静态属性做初始化。因为我们可以绕过创建对象的步骤,直接使用类名访问这个静态属性。
public class Demo {
public static int num;
public Demo(){
num = 10;
}
}
public static void main(String[] args){
System.out.println(Demo.num);//输出结果为 0
}
3.静态代码块
(1)静态代码块
- 静态代码块,也叫做静态初始化代码块,它的作用就是给类中的静态属性做初始化的
- 静态代码块的执行时刻:
由于静态代码块没有名字,我们并不能主动调用,它会在类加载的时候,自动执行。
所以静态代码块,可以更早的给类中的静态属性,进行初始化赋值操作。
并且,静态代码块只会自动被执行一次,因为JVM在一次运行中,对一个类只会加载一次!
public class Demo {
public static int num;
static{
num = 10;
}
}
public static void main(String[] args){
System.out.println(Demo.num);//输出结果为 10
}
(2)匿名代码块:
- 有一种非静态代码块,也叫做匿名代码块,它的作用是给非静态属性做初始化操作。
- 匿名代码块执行的时刻:
由于匿名代码块没有名字,我们并不能主动调用,它会在创建对象的时候,构造器执行之前,自动执行。
并且每次创建对象之前,匿名代码块都会被自动执行。
public class Demo {
public int num; {
num = 10;
}
}
public static void main(String[] args){
Demo demo = new Demo();
System.out.println(demo.num);//输出结果为 10
}
(3)静态代码和匿名代码同时执行
静态代码执行了一次,因为JVM只会加载Demo类一次,而匿名代码块会在每次创建对象的时候,先执行,然后再执行构造器。
public class Demo {
static {
System.out.println("静态代码块执行");
}
{
System.out.println("匿名代码块执行");
}
public Demo(){
System.out.println("构造器执行");
}
}
public static void main(String[] args){
new Demo();
new Demo();
}
//输出结果为:
静态代码块执行
匿名代码块执行
构造器执行
匿名代码块执行
构造器执行
(4)创建和初始化对象的过程:
- 对Student类进行类加载,同时初始化类中静态的属性赋默认值,给静态方法分配内存空间
- 执行类中的静态代码块
- 堆区中分配对象的内存空间,同时初始化对象中的非静态的属性赋默认值
- 调用Student的父类构造器
- 对Student中的属性进行显示赋值,
private String name = "tom";
- 执行匿名代码块
- 执行构造器代码
- =号赋值操作,把对象的内存地址赋给变量s
public class Person{
private String name = "zs";
//父类构造器
public Person() {
System.out.println("Person构造器");
print();
}
//父类的方法
public void print(){
System.out.println("Person print方法: name = "+name);
}
}
class Student extends Person{
private String name = "tom";
//匿名代码块
{
System.out.println("Student匿名代码块");
}
//静态代码块
static{
System.out.println("Student静态代码块");
}
//子类构造器
public Student(){
System.out.println("Student构造器");
}
//子类的方法
public void print(){
System.out.println("student print方法: name = "+name);
}
public static void main(String[] args) {
new Student();
}
}
该代码的运行输出结果为:
Student静态代码块
Person构造器
student print方法: name = null
Student匿名代码块
Student构造器
- 子类重写父类的方法,在创建子类对象的过程中,默认调用的一定是子类中重写后的方法
- 非静态属性的显示赋值,是在父类构造器执行结束之后和子类中的匿名代码块执行之前的时候
- 以上代码中,因为方法的重写,会调用子类中重写后的print方法,同时该方法恰好是在父类构造器执行中调用的,而这个时候子类中的name属性还没有进行显示赋值,所以是输出结果为null
- 如果此时在子类的匿名代码块中也输出name的值,那么就会显示tom,因为已经完成了属性的显 示赋值
4. 静态导入
在自己的类中,要使用另一个类中的静态属性和静态方法,那么可以进行静态导入,导入完成后,可以直接使用这个类中的静态属性和静态方法,而不用在前面加上类名。
(1)没有使用静态导入的情况:
public class Demo {
public void test(){
System.out.println(Math.PI);//访问Math类中的静态属性PI,表示圆周率π
System.out.println(Math.random());//访问Math类中的静态方法random(),生成随机数
}
}
(2)使用静态导入的情况:
import static java.lang.Math.PI;
import static java.lang.Math.random;
public class Demo {
public void test(){
System.out.println(PI);
System.out.println(random());
}
}
二、final
final修饰符,可以用来修饰类、变量、方法
1.修饰类
用final修饰的类不能被继承,也就是说这个类是没有子类的。
public final class Action{ }
//编译报错
class Go extends Action{ }
2. 修饰方法
用final修饰的方法可以被子类继承,但是【不能】被子类的重写
public class Person{
public final void print(){}
}
//编译报错
class Student extends Person{
public void print(){ }
}
3.修饰变量
用final修饰的变量就变成了常量,并且它只能被赋一次值,第二次赋值就会报错
(1)final修饰局部变量
public class Person{
public void print(final int a){
//编译报错,不能再次赋值,传参的时候已经赋过了
a = 1;
}
}
(2)final修饰非静态成员变量
对于这个final成员变量a,只有一次赋值的机会。
JVM不再为其进行默认赋值,我们需要手动在以下地方对其进行赋值:
- 声明的同时赋值
- 匿名代码块中赋值
- 构造器中赋值,此时还有额外要求:类中出现的所有构造器都要赋值,否则报错
public class Person{
private final int a;
}
(3)final修饰静态成员变量
对于这个final静态成员变量a,只有一次赋值的机会。
JVM不再为其进行默认赋值,我们需要手动在以下地方对其进行赋值:
- 声明的同时赋值
- 静态代码块中赋值
public class Person{
private static final int a;
}
(4)final修饰引用类型变量
此时final指的是,引用s的指向的对象【不能】改变,但是可以使用s来操作当前指向的对象属性和方法
final Student s = new Student();
//编译通过,可以修改s指向对象中的属性值
s.setName("tom");
s.setName("zs");
//编译报错,不能修改引用s指向的内存地址
s = new Student();
三、abstract
abstract修饰符,可以修饰类、方法
1.修饰方法
抽象方法的特点:
- 只有方法的声明
- 没有方法的实现
//这就是一个普通方法,既有方法的声明,又有方法的实现
//方法的声明:public void test()
//方法的实现:{}
public void test(){}
//这就是一个只有声明没有实现的方法
public void test();
//这样的方法需要使用abstract修饰符来修饰,说明它是一个抽象方法
public abstract void test();
2.修饰类
(1)抽象类和非抽象类的区别:
- 抽象类使用了abstract修饰符,而非抽象类没有使用
- 抽象类中可以编写抽象方法,而非抽象类中不能编写抽象方法
- 抽象类不能进行实例化创建对象,而非抽象类可以实例化创建对象
(2)抽象类和抽象方法的关系:
- 抽象类中可以没有抽象方法
- 有抽象方法的类,一定要声明为抽象类
3. 意义
- 父类中的一个方法,如果被它的子类们重写,并且每个子类各自的实现又不相同,那么父类中的的这个方法,只有声明还有意义,而它的方法主体就变得没有任何存在的意义了。
这个时候,就可以把这个方法定义为抽象方法,只有方法的声明,没有方法的主体(也就是方法的实现)。 - 既然抽象类不能被实例化创建对象,那么这个抽象类有什么用?
- 抽象类是用来被子类继承的,子类继承抽象类,并且实现抽象类中的抽象方法。
- 实现父类中的抽象方法 和 重写父类中的普通方法,只是说法不同,但是它们的语法要求,操作方式完全是一样的,可以直接把实现抽象方法当做重写方法来看。
- 抽象类不实例化创建对象,那么抽象类中是否有构造器?
有构造器,这个构造器是让子类调用的,子类继承父类,子类创建对象的时候,会先调用父类的构造器! - 如何设计一个类,只想让别人继承它并重写指定方法?
将这个类定义为抽象类即可,同时如果想要求别人继承的时候,一定要重写某个方法的话,只要把这个方法定义为抽象方法,别人在继承这个类的时候,就一定要会对这个抽象方法进行实现! - 子类继承抽象父类,子类是否可以不实现父类中的抽象方法?
可以实现,子类继承抽象父类,子类可以选择实现父类中所有的抽象方法,如果有任何一个抽象方法没有被子类实现,那么这个子类也要将自己声明为抽象类,那么这个子类也就只能等待,再来一个子类继承自己,去实现剩余没有实现的抽象方法,直到所有抽象方法都被实现为止。
四、interface
引用数据类型:类、数组、接口
1.概述
接口是除了类和数组之外,另外一种引用数据类型
接口和类不同,类的内部封装了成员变量、构造方法和成员方法,而接口的内部主要就是封装了方法和静态常量。
- 定义类使用关键字
class
,定义接口使用关键字interface
- 接口中的属性都是公共的静态常量
注意:常量的名字一般需要全大写- 接口中的方法都是抽象方法
注意:JDK8中,还允许在接口中编写静态方法和默认方法
注意:JDK9中,还允许在接口中编写私有方法- 接口的中抽象方法,不需要使用
abstract
修饰符,因为接口中的方法默认就是抽象方法
//使用interface关键字来定义接口
public interface Action {
//接口中的静态常量
public static final String OPS_MODE = "auto";
//接口中的抽象方法
public void start();
//接口的抽象方法
public void stop();
}
(1)在接口中,可把一些修饰符省去不写:
- 接口里面的属性,默认就是
public static final
修饰的 - 接口里面的方法,默认就是
public abstract
修饰的
(2)含有静态方法和默认方法的接口:(JDK8)
public interface Action {
public default void run1() {
// 执行语句
}
public static void run2() {
// 执行语句
}
}
2.接口实现
(1)类和类之间的关系是继承,类和接口之间的关系是实现:一个类实现了一个或多个接口
(2)接口和类不同,类可以实例化创建对象,而接口不能创建对象,只能让其他类来实现。
(3)一个类实现了一个接口,那么该类可以称为这个接口的实现类。类实现一个接口和类继承一个父类的效果类似,格式相仿,只是关键字不同,实现使用 implements
关键字,而继承使用的是extends
关键字。
(4)一个类实现了接口,那么就要实现接口中所有的抽象方法,否则这个类自己就必须声明为抽象类。
(5)一个类实现了多个接口,那么就需要把这多个接口中的抽象想法全都实现。
3.接口继承
java中,类和类之间是单继承,接口和接口之间是多继承
//实现该接口的类,将具有run的功能
public interface Runable {
void run();
}
//实现该接口的类,将具有fly的功能
interface Flyable{
void fly();
}
//实现该接口的类,将具有run的功能,fly的功能,以及Action接口独有的doSomething功能
interface Action extends Runable,Flyable{
void doSomething();
}
//实现类,实现Action接口,就必须要实现Action及其父接口中的所有抽象方法
class Demo implements Action{
@Override
public void run() { }
@Override
public void fly() { }
@Override
public void doSomething() { }
}
4.多态
多态的前提是继承,必须要先有子父类关系才行,而类和接口之间的实现关系,其实也是继承的一种形式,所以在类和接口的实现关系中,也可以使用多态
public interface Action {
void run();
}
class Student implements Action{
@Override
public void run() { }
}
class Teacher implements Action{
@Override
public void run() {}
}
(1)接口的引用指向它的实现类对象:
class StudentTest{
public static void main(String[] args) {
//声明接口的引用
Action a;
//可以指向它任意一个实现类对象
a = new Student();
a = new Teacher();
}
}
(2)接口的引用调用方法,调用到的是实现类中重写的方法:
class StudentTest{
public static void main(String[] args) {
Action a = new Student();
//调用到的方法,是Student中重写(实现)的方法
a.run();
}
}
五、访问控制
public > protected > default > private
- public,公共的,在所有地方都可以访问
- protected,受保护的,当前类中、子类中,同一个包中其他类中可以访问
- default,默认的,当前类中、同一个包中的子类中可以访问
注意,default默认的,指的是空修饰符,并不是default这个关键字
例如,String name; 在类中,这种情况就是默认的修饰符- private,私有的,当前类中可以访问
修饰符 | 类中 | 同包非子类 | 同包子类 | 不同包子类 | 不同包非子类 |
---|---|---|---|---|---|
public | Y | Y | Y | Y | Y |
protected | Y | Y | Y | Y | N |
default | Y | Y | Y | N | N |
private | Y | N | N | N | N |
六、内部类
内部类,不是在一个java源文件中编写俩个平行的类,而是在一个类的内部再定义另外的一个类。
不仅类中可以嵌套接口,接口的内部也可以嵌套其他接口。
class 外部类 {
class 内部类{ }
}
内部类一共分为四种形式:
- 成员内部类
- 静态内部类
- 局部内部类
- 匿名内部类
1.成员内部类
成员内部类中,【不能】编写静态的属性和方法
代码编译成功后,会生成俩个class文件,一个对应外部类,一个对应内部类,编译生成的俩个class文件的名字分别为:
- MemberOuterClass.class
- MemberOuterClass$MemberInnerClass.class
(1)成员内部类和外部类的相互访问
- 成员内部类访问外部的属性和方法:
public class MemberOuterClass {
//外部类的属性
private String name;
private static int age;
//外部类的方法
public void run(){}
public static void go(){}
/* 成员内部类 声明开始 */
public class MemberInnerClass{
private String name;
private int age;
public void run(String name){
//访问当前run方法中的参数name
System.out.println(name);
//访问内部类自己的属性name
System.out.println(this.name);
//访问外部类的非静态属性
System.out.println(MemberOuterClass.this.name);
//访问外部类的静态属性
System.out.println(MemberOuterClass.age);
//访问外部类的非静态方法
MemberOuterClass.this.run();
//访问外部类的静态方法
MemberOuterClass.go();
}
}
/* 成员内部类 声明结束 */
}
- 外部类访问成员内部类的属性和方法
public class MemberOuterClass {
//外部类的方法,访问成员内部类的属性和方法
public void test(){
//需要创建内部类对象,然后才可以访问
MemberInnerClass t = new MemberInnerClass();
System.out.println(t.name);
System.out.println(t.age);
t.run("tom");
}
/* 成员内部类 声明开始 */
public class MemberInnerClass{
private String name;
private int age;
public void run(String name){ }
}
/* 成员内部类 声明结束 */
}
(2)在其他类中使用这个内部类
在其他类中,使用这个非private修饰的成员内部类的时候,需要注意以下几点:
- 这个内部类需要import导入,并且是
外部类.内部类
的形式导入。- 在创建对象的时候,需要先创建出外部类对象,然后使用外部类对象再创建内部类对象。
形式为:外部类对象.new 内部类对象();
package com.briup.day09.demo;
import com.briup.day09.demo.MemberOuterClass.MemberInnerClass;
public class Test {
public static void main(String[] args) {
MemberOuterClass moc = new MemberOuterClass();
MemberInnerClass mic = moc.new MemberInnerClass();
mic.run("tom");
}
}
2.静态内部类
静态内部类和成员内部类是类似的,只是这个内部类,多了static关键字进行修饰。
静态内部类中,【可以】编写静态的属性和方法,另外在四种内部类中,只有静态内部类可 以编写静态属性和方法
编译生成的俩个class文件的名字分别为:
- StaticOuterClass.class
- StaticOuterClass$StaticInnerClass.class
(1)静态内部类和外部类的相互访问
- 静态内部类访问外部的属性和方法:
在静态内部类中访问不了外部类中的非静态属性和方法
public class StaticOuterClass {
//外部类的属性
private String name;
private static int age;
//外部类的方法
public void run(){}
public static void go(){}
/* 静态内部类 声明开始 */
public static class StaticInnerClass{
private String name;
private static int age;
public void run(String name){
//访问当前run方法中的参数name
System.out.println(name);
//访问内部类自己的属性name
System.out.println(this.name);
//访问内部类自己的静态属性age
System.out.println(age);
//静态内部类中,无法访问外部类的非静态属性和方法
//System.out.println(StaticOuterClass.this.name);
//StaticOuterClass.this.run();
//访问外部类的静态属性和方法
System.out.println(StaticOuterClass.age);
StaticOuterClass.go();
}
}
/* 静态内部类 声明结束 */
}
- 外部类访问静态内部类的属性和方法
public class StaticOuterClass {
public void test(){
//外部类中,访问静态类中的静态属性
System.out.println(StaticInnerClass.age);
//外部类中,访问静态内部类中的非静态属性和方法
StaticInnerClass sic = new StaticInnerClass();
System.out.println(sic.name); sic.run("tom");
}
/* 静态内部类 声明开始 */
public static class StaticInnerClass{
private String name;
private static int age;
public void run(String name){ }
}
/* 静态内部类 声明结束 */
}
(2)在其他类中使用这个内部类:
在其他类中,使用这个非private修饰的静态内部类的时候,需要注意以下几点:
- 这个内部类需要import导入,并且是
外部类.内部类
的形式导入。- 在创建对象的时候,直接使用这个静态内部类的名字即可:
new 静态内部类对象();
,不再需要依赖外部类对象了。
import com.briup.sync.StaticOuterClass.StaticInnerClass;
public class Test {
public static void main(String[] args) {
StaticInnerClass sic = new StaticInnerClass();
sic.run("tom");
}
}
3.局部内部类(最不常用)
局部内部类和外部类的相互访问
- 局部内部类访问外部的属性和方法:
public class LocalOuterClass {
//外部类的属性
private String name;
private static int age;
//外部类的方法
public void run(){}
public static void go(){}
public void sayHello(String name){
/* 局部内部类 声明开始 */
class LocalInnerClass{
private String name;
public void test(String name){
//访问当前test方法中的参数name
System.out.println(name);
//访问内部类自己的属性name
System.out.println(this.name);
/*注意,sayHello方法的参数name,无法访问,因为实在没有办法表示了,
换成其他名字后,就可以访问了,不要叫name就行*/
//访问外部类的非静态属性
System.out.println(LocalOuterClass.this.name);
//访问外部类的非静态方法
LocalOuterClass.this.run();
//访问外部类的静态属性和方法
System.out.println(LocalOuterClass.age);
LocalOuterClass.go();
}
}
/* 局部内部类 声明结束 */
}
}
- 局部内部类中,访问当前方法中的变量,这个变量必须是final修饰的
在JDK1.8中,一个局部变量在局部内部类中进行访问了,那么这个局部变量自动变为final修饰
public void sayHello(final String name){
final int num = 1;
/* 局部内部类 声明开始 */
class LocalInnerClass{
public void test(){
System.out.println(name);
System.out.println(num);
//编译报错,final修饰的变量,只能赋值一次
//name = "tom";
//num = 2;
}
}
/* 局部内部类 声明结束 */
}
- 外部类访问局部内部类的属性和方法
局部内部类,只能在当前声明的方法中进行使用。
public void sayHello(String name){
/* 局部内部类 声明开始 */
class LocalInnerClass{
private int num;
public void test(){ }
}
/* 局部内部类 声明结束 */
//创建局部内部类对象
LocalInnerClass lic = new LocalInnerClass();
//对象访问属性
System.out.println(lic.num);
//对象调用方法
lic.test();
}
4.匿名内部类(很重要)
匿名内部类,是一种没有名字的内部类,它是内部类的一种简化写法。
在普通的代码中,使用一个接口的步骤如下:
- 声明一个类,去实现这个接口
- 实现这个接口中的抽象方法(重写)
- 在其他代码中,创建这个类的对象
- 调用类中实现(重写)后的方法
格式:
父类或者接口类型 变量名 = new 父类或者接口(){
// 方法重写
@Override
public void method() {
// 执行语句
}
};
//调用实现(重写)后的方法
变量名.method();
(1)匿名内部类的俩种形式:
- 利用一个父类,进行声明并创建匿名内部类对象,这个匿名内部类默认就是这个父类的子类型
如果利用父类型声明这个匿名内部类,那么这个匿名内部类默认就是这个父类型的子类
public abstract class Animal {
public abstract void run();
}
class Test{
public static void main(String[] args) {
Animal animal = new Animal(){
@Override
public void run() {
System.out.println("匿名内部类中的默认实现");
}
};
animal.run();
}
}
- 利用一个接口,进行声明并创建匿名内部类对象,这个匿名内部类默认就是这个接口的实现类
如果利用接口声明这个匿名内部类,那么这个匿名内部类默认就是这个接口的实现类
public interface Action {
void run();
}
class Test{
public static void main(String[] args) {
Action a = new Action(){
@Override
public void run() {
System.out.println("匿名内部类中的默认实现");
}
};
a.run();
}
}
(2)匿名内部类因为没有类名:
- 匿名内部类必须依托于一个父类型或者一个接口
- 匿名内部类在声明的同时,就必须创建出对象,否则后面就没法创建了
- 匿名内部类中无法定义构造器
5.内部类的选择
- 考虑这个内部类,如果需要反复的进行多次使用(必须有名字)
- 在这个内部类中,如果需要定义静态的属性和方法,选择使用静态内部类
- 在这个内部类中,如果需要访问外部类的非静态属性和方法,选择使用成员内部类
- 考虑这个内部类,如果只需要使用一次(可以没有名字)
- 选择使用匿名内部类
- 局部内部类,几乎不会使用
七、包装类
1.概述
针对这八种基本类型,JavaAPI又专门提供了对应的类类型,目的就是为了分别把这八种基本类型的数据,包装成对应的类类型,这时候就变成对象了,就可以调用方法了或者访问属性了。
基本类型 | 包装类型 |
---|---|
boolean | java.lang.Boolean |
byte | java.lang.Byte |
short | java.lang.Short |
int | java.lang.Integer |
long | java.lang.Long |
float | java.lang.Float |
double | java.lang.Double |
char | java.lang.Character |
2.自动装箱/拆箱
(1)JDK1.5或以上,可以支持基本类型和包装类型之间的自动装箱、自动拆箱:
//JKD1.5 之前
Integer o = new Integer(1);
Integer o = Integer.valueOf(1);
//JDK1.5 之后
//自动装箱,这里会自动把数字1包装成Integer类型的对象
Integer o = 1;
//JKD1.5 之前
Integer o = new Integer(1);
int i = o.intValue();
//JDK1.5 之后 Integer o = new Integer(1); //自动拆箱,这里会自动把对象o拆箱为一个int类型的数字,并把数字赋值给int类型的变量i int i = o;
(2)
public class Test{
public void test1(int i){}
public void test2(Integer i){} p
ublic void test3(long i){}
public void test4(Long i){}
public static void main(String[] args){
t.test1(1);//编译通过 int i = 1; 正常赋值
t.test2(1);//编译通过 Integer i = 1; 自动装箱
t.test3(1);//编译通过 long i = 1; 隐式类型转换
//编译报错
//错误的代码:Long i = 1;
//int和Long 之间没有任何关系
t.test4(1);
t.test4(1L);//编译通过 Long i = 1L; 自动装箱
}
}
八、Object中常用方法
1.toString方法
该方法可以返回一个对象默认的字符串形式
(1)子类中可以对该方法进行重写:
public class Student{
private String name;
private int age;
//get
//set
public String toString() {
return "Student[name="+name+",age="+age+"]";
}
}
(2)toString方法的调用:
public static void main(String[] args){
Student stu = new Student(); //这个俩个输出语句的效果是一样的
System.out.println(stu); //推荐使用
System.out.println(stu.toString());
}
2.getClass方法(非常重要)
它可以返回一个引用在运行时所指向的对象,具体类型是什么
子类中不能重写getClass,调用的一定是Object中的getClass方法:
public void test(Object obj){
//obj可以接收任意类型的对象
//getClass方法可以返回obj指向的对象具体是什么类型的
//也就是该对象到底是使用哪个类创建出的对象
System.out.println(obj.getClass());
}
3. equals方法
Object中的equals方法,是直接使用的==号进行的比较,比较俩个对象的地址值是否相等
(1)子类中可以对该方法进行重写:
对equals方法重写,一般需要注意以下几点:
- 自反性:对任意引用obj,obj.equals(obj)的返回值一定为true.
- 对称性:对于任何引用o1、o2,当且仅当o1.equals(o2)返回值为true时,o2.equals(o1)的返回值一 定为true;
- 传递性:如果o1.equals(o2)为true, o2.equals(o3)为true,则o1.equals(o3)也一定为true
- 一致性:如果参与比较的对象没任何改变,则对象比较的结果也不应该有任何改变
- 非空性:任何非空的引用obj,obj.equals(null)的返回值一定为false
public class Student {
private String name;
private int age;
public Student(){}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object obj) {
//如果obj为null,直接返回false
if(obj == null){
return false;
}
//如果obj和this的地址值相等,直接返回true
if(this == obj){
return true;
}
//如果obj不属于Student类型的对象,直接返回false
if(!(obj instanceof Student)){
return false;
}
//obj属于Student类型的对象,做类型强制转换
Student other = (Student) obj;
//如果obj的name和age分别等于this的name和age,直接返回true
if(this.name.equals(other.name) && this.age==other.age){
return true;
}
//其他情况,直接返回false
return false;
}
}
class StudentTest{
public static void main(String[] args) {
Student s1 = new Student("tom",20);
Student s2 = new Student("tom",20);
System.out.println(s1 == s2);//输出false,因为俩对象的内存地址不同
System.out.println(s1.equals(s2));//输出true,因为重写了equals的比较规则,name 和age相等就算俩对象相等
}
}
(2)equals方法的调用
public static void main(String[] args){
Student o1 = new Student();
Student o2 = new Student();
boolean f = o1.equals(o2);
System.out.println(f);
}
4. hashCode方法
(1)该方法返回一个int值,该int值是JVM根据对象在内存的中的特征(地址值),通过hash算法计算出的一个结果。
(2)Hash,就是把任意长度的数据输入,通过散列算法,变换成固定长度的输出,该输出就是散列值。
(3)Object中的hashCode方法默认返回的是对象的内存地址,但是实际上可能并不是。
- 对于俩个对象的hashCode值: 相等的俩个对象,hashCode值一定相等
- hashCode值相同,俩个对象有可能相等,也可能不同等
- hashCode值不同,俩个对象一定不同
九、关于String对象
JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化
- 为字符串开辟一个字符串常量池,类似于缓存区
- 创建字符串常量时,首先会检查字符串常量池中是否存在该字符串
- 如果存在该字符串,则返回该实例的引用
- 如果不存在,则实例化创建该字符串,并放入池中
- 只有用双引号,里面加入文字的方式,才会利用到内存中的字符串常量池
(1)
//只有用双引号,里面加入文字的方式,才会利用到内存中的字符串常量池。
String str1 = "hello";
String str2 = new String("hello");
//String中对equals进行了重写,比较的是字符串中每一个字符是否相等
System.out.println(str1.equals(str2));//true
//str1指向的是常量池中的hello对象
//str2指向的是堆区中新创建的hello对象
System.out.println(str1 == str2);//false
(2)
String str1 = "hello";
String str2 = "hello";
//String中对equals进行了重写,比较的是字符串中每一个字符是否相等
System.out.println(str1.equals(str2));//true
/
/str1指向的是常量池中的hello对象
//str2指向的是常量池中的hello对象
//str1和str2指向的是同一个对象
System.out.println(str1 == str2);//true
- 使用+拼接的字符串也会利用字符串常量池,但是参与+号拼接的必须是双引号的形式才可以
(1)
String s1 = "a";
String s2 = "b";
//使用+拼接的字符串也会利用字符串常量池
//但是有要求:参与+号拼接的必须是双引号的形式才可以。
String s3 = "a"+"b";
String s4 = s1+s2;
System.out.println(s3.equals(s4));//true
//s3指向的是常量池中的"ab"对象
//s4指向的堆区中新建的"ab"对象
//因为s4是由s1和s2使用+号连接得到的,而s1和s2都是变量
//有变量参与拼接字符串,那么就不会使用常量池了
System.out.println(s3 == s4);//false
(2)
final String s1 = "a";
final String s2 = "b";
//使用+拼接的字符串也会利用字符串常量池
//但是有要求:参与+号拼接的必须是双引号的形式才可以。
String s3 = "a"+"b";
String s4 = s1+s2;
System.out.println(s3.equals(s4));//true
//s3指向的是常量池中的"ab"对象
//s4指向的是常量池中的"ab"对象
//因为s4是由s1和s2使用+号连接得到的,而s1和s2都是final修饰的【常量】
//常量是固定不会变的,在编译期间就能计算出s4的值
//这时候又可是有到字符串常量池了
System.out.println(s3 == s4);//true
3.当调用 intern() 方法时,JVM会将字符串添加到常量池中,并返回指向该常量的引用
String s1 = "a";
String s2 = "b";
//使用+拼接的字符串也会利用字符串常量池
//但是有要求:参与+号拼接的必须是双引号的形式才可以。
String s3 = "a"+"b";
String s4 = (s1+s2).intern();
System.out.println(s3.equals(s4));//true
//s3指向的是常量池中的"ab"对象
//s4指向的是常量池中的"ab"对象
//intern方法可以在JVM在运行期间,强行使用字符串常量池
//检查当前调用intern方法的字符串,是否在常量池中,如果在那么就返回常量池中的这个对象,如果不在,那么就把当前这个调用intern方法的字符串存到常量池,供后面的代码使用。
System.out.println(s3 == s4);//true