java基础(interface,访问控制,内部类,包装类,Object常用方法)

本文详细介绍了Java中的接口,包括接口的概述、实现、继承和多态,强调了接口在实现多态中的作用。此外,文章还涵盖了访问控制,包括概述和案例,以及内部类的四种类型:成员内部类、静态内部类、局部内部类和匿名内部类。通过实例展示了如何使用这些特性,并探讨了它们在实际编程中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.interface

引用数据类型:类、数组、接口

1.1 概述

接口是除了类和数组之外,另外一种引用数据类型

接口和类不同,类的内部封装了成员变量、构造方法和成员方法,而接口的内部主要就是封装了方法静态常量

接口的定义和类很类似,但是接口需要使用 interface 关键字来定义

接口最终也会被编译成.class文件,但一定要明确接口并不是类,而是另外一种引用数据类型

例如,

//使用interface关键字来定义接口
public interface Action {
	//接口中的静态常量
    public static final String OPS_MODE = "auto";
	
    //接口中的抽象方法
    public void start();
	
    //接口的抽象方法
    public void stop();

}

注意1, 定义类使用关键字class,定义接口使用关键字interface

注意2,接口中的属性都是公共的静态常量
注意:常量的名字一般需要全大写

注意3,接口中的方法都是抽象方法
注意:JDK8中,还允许在接口中编写静态方法默认方法
注意:JDK9中,还允许在接口中编写私有方法

注意4,接口的中抽象方法,不需要使用abstract修饰符,因为接口中的方法默认就是抽象方法

在接口中,可把一些修饰符省去不写

//使用interface关键字来定义接口
public interface Action {
	//接口中的静态常量
    String OPS_MODE = "auto";
	
    //接口中的抽象方法
    void start();
	
    //接口的抽象方法
    void stop();

}

接口里面的属性,默认就是public static final修饰的

接口里面的方法,默认就是public abstract修饰的

所以这些都可以省去不写的

含有静态方法和默认方法的接口:(JDK8

public interface Action {
    public default void run1() {
    	// 执行语句
    }
    public static void run2() {
    	// 执行语句
    }
}

含有私有方法的接口:(JDK9)

public interface Action {
    private void run() {
    	// 执行语句
    }
}

注意,我们一般所使用的接口,大多数都只是含义抽象方法

1.2 接口实现

类和类之间的关系是继承,类和接口之间的关系是实现:一个类实现了一个或多个接口

接口和类不同,类可以实例化创建对象,而接口不能创建对象,只能让其他类来实现。

一个类实现了一个接口,那么该类可以称为这个接口的实现类。类实现一个接口 和 类继承一个父类 的效果类似,格式相仿,只是关键字不同,实现使用 implements 关键字,而继承使用的是extends关键字。

一个类实现了接口,那么就要实现接口中所有的抽象方法,否则这个类自己就必须声明为抽象类。

例如,实现一个接口

public interface Action {

    void run();
    void sayHello();

}
//类实现接口,没有实现接口中的抽象方法,那么这个类就必须声明为抽象类
abstract class Student implements Action{
    
}

public interface Action {

    void run();
    void sayHello();

}
//类实现接口,并且实现了接口中所有的抽象方法
class Student implements Action{
    public void run(){
        //...
    }
    
    public void sayHello(){
        //...
    }
}

例如,实现多个接口

public interface Action {

    void run();
    void sayHello();

}

public interface Mark{
    void star();
}

//类实现接口,并且实现了接口中所有的抽象方法
class Student implements Action, Mark{
    public void run(){
        //...
    }
    
    public void sayHello(){
        //...
    }
    
    public void star(){
        //...
    }
    
}

一个类实现了多个接口,那么就需要把这多个接口中的抽象想法全都实现

JavaAPI中的String类,也实现了多个接口:

可以看出,String类继承Object类的同时,又实现了三个接口

1.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() {

    }
}

1.4 多态

多态的前提是继承,必须要先有子父类关系才行,而类和接口之间的实现关系,其实也是继承的一种形式,所以在类和接口的实现关系中,也可以使用多态

接口的引用执行它的实现类对象:

public interface Action {
    void run();
}

class Student implements Action{

    @Override
    public void run() {
        
    }
}

class Teacher implements Action{

    @Override
    public void run() {
        
    }
}

class StudentTest{
    public static void main(String[] args) {
        //声明接口的引用
        Action a;
        
        //可以指向它任意一个实现类对象
        a = new Student();
        a = new Teacher();
      
    }
}


接口的引用调用方法,调用到的是实现类中重写的方法

class StudentTest{
    public static void main(String[] args) {
        
        Action a = new Student();;   
        //调用是到的方法是Student中重写(实现)的方法
        a.run();
    }
}

注意,抽象方法的实现,也算是一种方法的重写,形式、语法完全一致。

1.5 案例

public interface Action {
    void run();
}

interface Mark{
    void star();
}

class Student implements Action,Mark{

    @Override
    public void run() {
        System.out.println("student run...");
    }

    @Override
    public void star() {
        System.out.println("student star...");
    }
}

思考,下面代码是否正确?:

class StudentTest{
    public static void main(String[] args) {
		Action a = new Student();
        a.run();
        a.star();
    }
}

mian方法中,a.run()编译运行都是正确的,但是 a.star()方法的调用是编译错误的。

因为,在编译的时候,编译器会先检查引用a所属的类型(Action)中,是否存在当前要调用的方法(star),如果没有那么就直接编译报错。

那么在这种情况下,该如何修改代码,才能正确调用到star方法?

class StudentTest{
    public static void main(String[] args) {
		Action a = new Student();
        a.run();
        Mark m = (Mark)a;
        m.star();
    }
}

在这里,可以先做类型强制转换,把Action类型的引用a,转账转为Mark类型的引用m,然后使用引用m,就可以调用到Mark接口中的star方法,同时Student中对star进行了实现(重写),那么最后调用的是Student中的star方法。

思考,上面代码中,Action类型的引用a,为什么可以转换为Mark类型的引用m?注意,Action和Mark之间并没有任何关系

Action a = new Student();
Mark m = (Mark)a;

引用a,能否转账转为Mark类型,主要是看引用a,所指向的对象,是否实现了接口Mark,如果是的话,那么就可以转换成功。

同时,也可以是instanceof关键字,对引用a所指向的对象进行判断,是否属于Mark类型,如果是就说明该对象实现了Mark接口,属于Mark类型的,例如

Action a = new Student();
//返回true,则表示a指向的对象,也同时实现了Mark接口,属于Mark类型,那么就可以做强制转换
if(a instanceof Mark){
    Mark m = (Mark)a;
}

思考,如何让开发人员,一定能按照我们的预先设计好的方法,去编写代码?

把所有要实现的方法都放在接口里面,再让开发人员去实现接口

==思考,如果想要实现一个接口,但是不想重写里面的个别方法?

定义一个中间类,空实现接口中所有的类,然后再写个类去继承中间类,然后重写你想要的方法。

2 访问控制

对象中的属性和方法,是可以根据指定修饰符来进行访问控制的。具体控制的就是,这些属性和方法可以在什么地方被访问,以及在什么地方不能被访问。

2.1 概述

类中的属性和方法,可以使用以下四种修饰符进行访问控制:

public > protected > default > private

  • public,公共的,在所有地方都可以访问

  • protected,受保护的,当前类中、子类中,同一个包中其他类中可以访问

  • default,默认的,当前类中、同一个包中的子类中可以访问
    注意,default默认的,指的是空修饰符,并不是default这个关键字

    例如,String name; 在类中,这种情况就是默认的修饰符

  • private,私有的,当前类中可以访问

修饰符类中同包非子类同包子类不同包子类不同包非子类
publicYYYYY
protectedYYYYN
defaultYYYNN
privateYNNNN

2.2 案例

例如,

package com.briup.day09.demo;


public class AccessControl {

    public      String public_str    = "public_str";
    protected   String protected_str = "protected_str";
                String default_str   = "default_str";
    private     String private_str   = "private_str";


    //在当前类中访问
    public void test(){
        System.out.println(public_str);
        System.out.println(protected_str);
        System.out.println(default_str);
        System.out.println(private_str);
    }


}

class SamePackage{
    //在同包的非子类中访问
    public void test(){
        //需要先创建对象,然后再访问
        AccessControl ac = new AccessControl();
        System.out.println(ac.public_str);
        System.out.println(ac.protected_str);
        System.out.println(ac.default_str);

        //编译报错,无法访问
        //System.out.println(ac.private_str);
    }
}

class SamePackageSubClass extends AccessControl{
    //在同包的子类中访问
    public void test(){
        //继承后,可直接访问
        System.out.println(public_str);
        System.out.println(protected_str);
        System.out.println(default_str);

        //编译报错,无法访问
        //System.out.println(ac.private_str);
    }
}



在另一个包中,创建类进行访问测试:

package com.briup.day09.other;

import com.briup.day09.demo.AccessControl;


public class OtherPackage {
    //在不同包的非子类中访问
    public void test(){
        //需要先创建对象,然后再访问
        AccessControl ac = new AccessControl();
        System.out.println(ac.public_str);

        //编译报错,无法访问
        //System.out.println(ac.protected_str);
        //System.out.println(ac.default_str);
        //System.out.println(ac.private_str);
    }
}

class OtherPackageSubClass extends AccessControl{
    //在不同包的子类中访问
    public void test(){
        //继承后,可直接访问
        System.out.println(public_str);
        System.out.println(protected_str);

        //编译报错,无法访问
        //System.out.println(default_str);
        //System.out.println(ac.private_str);
    }
}

思考,正常情况下,编写一个类,都可以使用哪些权限控制修饰符?

正常编写的类,可以使用俩种权限控制修饰符:public和default

例如,

public class Person{}

class Student extends Person{}

但是,如果是内部类的话,则可以使用四种权限控制修饰符:

例如,

public class Test{
    private class A{}
    class B{}
    protected class C{}
    public class D{}
}

在Test类的内部,嵌套了四个内部类:A B C D,分别使用了private、default、protected、public进行了修饰

3 内部类

内部类,不是在一个java源文件中编写俩个平行的类,而是在一个类的内部再定义另外的一个类。

例如,这种情况是不是内部类,而是俩个独立的类

public class Test{
    
    
}

class A{
    
}

例如,这种情况是,是内部类,一个类内部嵌套了另一个类

//Test是外部类
public class Test{
    //A是Test类中的内部类
    public class A{
    
	}
    
}

内部类一共分为四种形式:

  • 成员内部类
  • 静态内部类
  • 局部内部类
  • 匿名内部类

注意,在A类中,嵌套声明了一个类B,以下都会将A类称为外部类,将B类称为内部类

class 外部类 {
    class 内部类{
    }
}

3.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){}

    }
    /* 成员内部类 声明结束 */

}

注意,成员内部类中,【不能】编写静态的属性和方法

注意,当前这个代码,编译成功后,会生成俩个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();

        }

    }
    /* 成员内部类 声明结束 */
}
  1. 外部类访问成员内部类的属性和方法
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){
            
        }

    }
    /* 成员内部类 声明结束 */
}

在其他类中使用这个内部类

如果这个成员内部类不是private修饰的,那么在其他类中就可以访问到这个内部类

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");

    }

}

在其他类中,使用这个非private修饰的成员内部类的时候,需要注意以下几点:

  1. 这个内部类需要import导入,并且是外部类.内部类的形式导入。
  2. 在创建对象的时候,需要先创建出外部类对象,然后使用外部类对象再创建内部类对象。
    形式为:外部类对象.new 内部类对象();

JavaAPI中的使用的成员内部类:

java.util.ArrayList类中,就定义了好几个成员内部类,并且还是private修饰的

思考,类的内部除了嵌套另一个类之外,是否还可以嵌套接口?

可以,不仅类中可以嵌套接口,接口的内部也可以嵌套其他接口。

例如,参考java.util.Map接口中的内部接口Entry

思考,什么情况下会使用内部类?

在对事物进行抽象的时候,若一个事物内部还包含其他事物,就可以考虑使用内部类这种结构。

例如,汽车(Car)中包含发动机(Engine) ,这时, Engine 类就可以考虑(非必须)使用内部类来描述,定义在Car类中的成员位置。

这样设计,既可以表示Car和Engine的紧密联系的程度,也可以在Engine类中很方便的使用到Car里面的属性和方法

例如,

public class Car { //外部类
    class Engine { //内部类
    }
}

例如,人(Person)中包含心脏(Heart),这时,Heart类可以考虑(非必须)使用内部类来描述,定义在Person类中的成员位置。

这样设计,既可以表示Person和Heart的紧密联系的程度,也可以在Heart类中很方便的使用到Person里面的属性和方法

例如,

public class Person { //外部类
    class Heart { //内部类
    }
}

注意,这是从程序中,类和类之间的关系和意义进行考虑而设计的,其实这里即使不使用内部类的结构,使用普通的俩个类也能完成功能,但是内部类的结构设计会加符合实际意义,也能够好的完成功能,因为内部类访问外部的属性和方法会更加容易。

3.2 静态内部类

静态内部类和成员内部类是类似的,只是这个内部类,多了static关键字进行修饰。

例如,

//外部类
public class StaticOuterClass{

    /* 静态内部类 声明开始 */
    public static class StaticInnerClass{	


    }
    /* 静态内部类 声明结束 */

}

注意,静态内部类中,【可以】编写静态的属性和方法,另外在四种内部类中,只有静态内部类可以编写静态属性和方法

编译生成的俩个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();

        }

    }
    /* 静态内部类 声明结束 */

}

注意,在静态内部类中访问不了外部类中的非静态属性和方法

  1. 外部类访问静态内部类的属性和方法
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){

        }

    }
    /* 静态内部类 声明结束 */

}

在其他类中使用这个内部类

如果这个静态内部类不是private修饰的,那么在其他类中就可以访问到这个内部类

import com.briup.sync.StaticOuterClass.StaticInnerClass;

public class Test {

    public static void main(String[] args) {

        StaticInnerClass  sic = new StaticInnerClass();
        sic.run("tom");

    }

}

在其他类中,使用这个非private修饰的静态内部类的时候,需要注意以下几点:

  1. 这个内部类需要import导入,并且是外部类.内部类的形式导入。
  2. 在创建对象的时候,直接使用这个静态内部类的名字即可:new 静态内部类对象();,不再需要依赖外部类对象了。

JavaAPI中的使用的静态内部类:

java.lang.Integer类中,就定义了一个静态内部类,并且还是private修饰的

思考,观察Integer类中私有静态内部类IntegerCache,它的作用是什么?

Integer num1 = Integer.valueOf(127);
Integer num2 = Integer.valueOf(127);

System.out.println(num1 == num2); //输出结果为 true

Integer num3 = Integer.valueOf(128);
Integer num4 = Integer.valueOf(128);

System.out.println(num3 == num4);//输出结果为 false

注意,==表示要比较这个俩个对象的内存地址是否相等

3.3 局部内部类

局部内部类,是另一种形式的内部,在声明在外部类的方法中,相当于方法中的局部变量的位置,它的作用范围只是在当前方法中。

局部内部类是最不常用的一种内部类。

例如,

public class LocalOuterClass{
					
    public void sayHello(String name){

        /* 局部内部类 声明开始 */
        class LocalInnerClass{

        }
        /* 局部内部类 声明结束 */

    }
}

局部内部类和外部类的相互访问

  1. 局部内部类访问外部的属性和方法
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();

            }

        }
        /* 局部内部类 声明结束 */

    }

}
  1. 局部内部类中,访问当前方法中的变量,这个变量必须是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;

        }

    }
    /* 局部内部类 声明结束 */

}

在JDK1.8中,一个局部变量在局部内部类中进行方法了,那么这个局部变量自动变为final修饰

  1. 外部类访问局部内部类的属性和方法
public void sayHello(String name){

    /* 局部内部类 声明开始 */
    class LocalInnerClass{
        private int num;
        public void test(){

        }

    }
    /* 局部内部类 声明结束 */

	//创建局部内部类对象
    LocalInnerClass lic = new LocalInnerClass();
    //对象访问属性
    System.out.println(lic.num);
    //对象调用方法
    lic.test();

}

局部内部类,只能在当前声明的方法中进行使用。

3.4 匿名内部类

匿名内部类,是一种没有名字的内部类,它是内部类的一种简化写法。在之后的代码中,匿名内部类是使用最多的一种内部类。(很重要

在普通的代码中,使用一个接口的步骤如下:

  • 声明一个类,去实现这个接口
  • 实现这个接口中的抽象方法(重写)
  • 在其他代码中,创建这个类的对象
  • 调用类中实现(重写)后的方法

其实,在这个过程中,我们的目的就是把接口中的抽象方法给实现(重写)了,最后再调用到这个实现后(重写)的方法。

那么,使用匿名内部类,就可以把这个过程给给简化了,让我们更加方法的调用到实现(重写)后的方法!

格式:

父类或者接口类型 变量名 = new 父类或者接口(){
    // 方法重写
    @Override
    public void method() {
    	// 执行语句
    }
};

//调用实现(重写)后的方法
变量名.method();

匿名内部类的俩种形式:

  • 利用一个父类,进行声明并创建匿名内部类对象,这个匿名内部类默认就是这个父类的子类型
  • 利用一个接口,进行声明并创建匿名内部类对象,这个匿名内部类默认就是这个接口的实现类

匿名内部类因为没有类名:

  • 匿名内部类必须依托于一个父类型或者一个接口
  • 匿名内部类在声明的同时,就必须创建出对象,否则后面就没法创建了
  • 匿名内部类中无法定义构造器

例如,利用父类型来声明并创建匿名内部类对象

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();

    }
}

注意,如果利用接口声明这个匿名内部类,那么这个匿名内部类默认就是这个接口的实现类

思考,对比之前普通的方式,匿名内部类是不是在调用到重写方法的同时,简化了之前很多步骤?

案例:

//Algorithm 算法接口
public interface Algorithm{
    void sort(int[] arr);
} 

class Test{
    //使用指定算法,对数组arr进行排序
    public void sort(int[] arr,Algorithm alg){
        alg.sort(arr);
    }
}

public static void main(String[] args){
    Test t = new Test();
    
    int[] arr = {4,1,6,3,8,5,9};
    
    Algorithm alg = new Algorithm(){
        public void sort(int[] arr){
            //使用当前需要的排序算法
            //例如,这里简单的使用Arrays工具类中的排序方法
            java.util.Arrays.sort(arr);
        }
    };
    
    t.sort(arr,alg);
    
}


内部类的选择:

假设现在已经确定了要使用内部类,那么一般情况下,该如何选择?

  1. 考虑这个内部类,如果需要反复的进行多次使用(必须有名字)
    • 在这个内部类中,如果需要定义静态的属性和方法,选择使用静态内部类
    • 在这个内部类中,如果需要访问外部类的非静态属性和方法,选择使用成员内部类
  2. 考虑这个内部类,如果只需要使用一次(可以没有名字)
    • 选择使用匿名内部类
  3. 局部内部类,几乎不会使用

4 包装类

java中的八种基本数据类型,它们只能表示一些最简单的数字,这些数字最小的在内存中占8位,最大占64位。这些都是简单的数字,不是对象,所以也不能用来调用方法或者属性。

4.1 概述

针对这八种基本类型,JavaAPI又专门提供了对应的类类型,目的就是为了分别把这八种基本类型的数据,包装成对应的类类型,这时候就变成对象了,就可以调用方法了或者访问属性了。

基本类型包装类型
booleanjava.lang.Boolean
bytejava.lang.Byte
shortjava.lang.Short
intjava.lang.Integer
longjava.lang.Long
floatjava.lang.Float
doublejava.lang.Double
charjava.lang.Character

4.2 案例

在这些包装类型中,都定义相关的属性和方法,例如Integer中:

public static void main(String[] args) {

    int i = 1;
    //编译报错,因为i不是对象
    //i.toString();

    //int --> Integer
    //Integer o = new Integer(i);
    Integer o = Integer.valueOf(1);

    //Integer --> int
    int j = o.intValue();

    System.out.println(o.toString());

    System.out.println(Integer.MIN_VALUE);
    System.out.println(Integer.MAX_VALUE);


    //把100分别转为十进制、二进制、八进制、十六进制的字符串形式
    System.out.println(Integer.toString(100));
    System.out.println(Integer.toString(100,2));
    System.out.println(Integer.toString(100,8));
    System.out.println(Integer.toString(100,16));

    //把100转为二进制形式
    System.out.println(Integer.toBinaryString(100));

    //把字符串"100"转为int类型的100
    int num = Integer.parseInt("100");
    System.out.println(num);


}

4.3 自动装箱/拆箱

JDK1.5或以上,可以支持基本类型和包装类型之间的自动装箱、自动拆箱:

简化了基本类型和包装类型之间的转换,这里以int和Integer为例说明

//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;


其他的基本类型和包装类型之间的转换,与此类似

注意事项:

public class Test{
    
    public void test1(int i){}
	public void test2(Integer i){}
	public 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; 自动装箱
        
    }
    
}

5 Object中常用方法

Object类是所有类的父类型,类中定义的方法,java中所有对象都可以调用

5.1 toString方法

该方法可以返回一个对象默认的字符串形式:

public class Object{
    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }
}

子类中可以对该方法进行重写:

public class Student{
    private String name;
    private int age;
    
    //get
    //set
    
    public String toString() {
        return "Student[name="+name+",age="+age+"]";
    } 
}

toString方法的调用:

public static void main(String[] args){
    Student stu = new Student();
    //这个俩个输出语句的效果是一样的
    System.out.println(stu);
    System.out.println(stu.toString());
}

注意,默认清下,println方法会调用这个对象的toString方法

注意,推荐使用第一种,因为当stu的值为null时,第二种输出方式会报错,空指针异常

5.2 getClass方法

该方法是非常重要的一种方法,它可以返回一个引用在运行时所指向的对象,具体类型是什么

该方法是native修饰的本地方法,不是java语言实现的。

public class Object{
    public final native Class<?> getClass();
}

子类中不能重写getClass,调用的一定是Object中的getClass方法:

public void test(Object obj){
    //obj可以接收任意类型的对象
    //getClass方法可以返回obj指向的对象具体是什么类型的
    //也就是该对象到底是使用哪个类创建出的对象
    System.out.println(obj.getClass());
}

5.3 equals方法

该方法可以比较俩个对象是否相等

public class Object{
    public boolean equals(Object obj) {
        return (this == obj);
    }
}

可以看出,Object中的equals方法,是直接使用的==号进行的比较,比较俩个对象的地址值是否相等

例如,

public static void main(String[] args){
    
    Student o1 = new Student();
    Student o2 = new Student();
    
    boolean f = o1.equals(o2);
    System.out.println(f);
}

子类中可以对该方法进行重写:

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相等就算俩对象相等

    }
}

按照重写后的equals比较规则,只要俩个对象的name和age属性值都相等,那么就返回true,说明俩对象相等

对equals方法重写,一般需要注意以下几点:

  1. 自反性:对任意引用obj,obj.equals(obj)的返回值一定为true.
  2. 对称性:对于任何引用o1、o2,当且仅当o1.equals(o2)返回值为true时,o2.equals(o1)的返回值一定为true;
  3. 传递性:如果o1.equals(o2)为true, o2.equals(o3)为true,则o1.equals(o3)也一定为true
  4. 一致性:如果参与比较的对象没任何改变,则对象比较的结果也不应该有任何改变
  5. 非空性:任何非空的引用obj,obj.equals(null)的返回值一定为false

5.4 hashCode方法

该方法返回一个int值,该int值是JVM根据对象在内存的中的特征(地址值),通过hash算法计算出的一个结果。

Hash,一般翻译做“散列”,也可以音译为“哈希”,就是把任意长度的数据输入,通过散列算法,变换成固定长度的输出,该输出就是散列值。

一个任意长度的输入转为一个固定长度的输出,是一种压缩映射,也就是说,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值。

所以,我们可以认为,Object中的hashCode方法默认返回的是对象的内存地址,但是实际上可能并不是

public class Object{
    public native int hashCode();
}

对于俩个对象的hashCode值:

  • 相等的俩个对象,hashCode值一定相等
  • hashCode值相同,俩个对象有可能相等,也可能不同等
  • hashCode值不同,俩个对象一定不同

对象和它的hashCode值的关系,就相当于人和他的姓氏的关系:

  • 相等的俩个人,姓氏一样的相同
  • 姓氏相同的俩个人,可能是同一个人(相等),也可能不同的俩个人(不相等)
  • 姓氏不同的俩个人,一定是不同的俩个人

思考,hashCode在代码中有什么用?

现在有100个对象,突然又来了一个对象obj,那么怎么判断这个obj对象和之前的100个对象是否有相等的呢?

除了使用obj对象的equals方法和之前的100个对象比较100次之外,是否还有更好的方式?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值