zoukankan      html  css  js  c++  java
  • 怎么一本正经地秀技

    前言

    修饰符怎么使用也是Java基础中比较重要的知识点,彻底理解了之后,后面学习更高深的东西才能得心应手。今天,以修饰符中比较常见的final为切入点,来谈谈final的使用的奇淫技巧以及一些相关的知识点。学废了记得三连哦。

    初始化块

    在final的运用中,经常和初始化块和构造器结合起来一起使用。上篇文章已经介绍完什么是构造器,那么现在先来谈谈什么是初始化块。

    Java会使用构造器对对象进行初始化操作,在使用的构造器的时候需要完成初始化整个Java对象的状态的功能,然后再将整个完整的Java对象返回给程序使用。那么,在Java中,有一个与构造器功能类似的东西,就是初始化块,它能够对Java对象实现初始化操作。

    在Java中,一个类中可以有多个初始化块,相同类型的初始化块的执行顺序是有要求的,先定义的先执行(前面的先执行),后面定义的后执行。

    初始化块的语法其实很简单了,就是:

    
    
    修饰符 {
    
      //初始化块中的可执行代码块
      ...
      ...
      ...
      
    }
    
    
    

    那初始化块的分类也很简单,就分为静态初始化块非静态初始化块两种,其中非静态初始化块也叫做普通初始化块

    非静态初始化块

    在生成每个对象的时候都会执行一次,可以初始化类的实例变量。非静态初始化块会在类的构造器之前执行

    先来看一段代码

    
    
    `public class CodeVald {
    
    	`  int a = 6;
    	
        `//第一个初始化块
    
        {
    
    
          int a = 3;
          if (this.a > 4) {
    
            System.out.println("codevald的初始化块:  成员变量a的值大于4");
    
          }
    
    
          System.out.println("codevald的初始化块");
    
    
        }
    
        //第二个初始化块
    
        {
    
          System.out.println("codevald的第二个初始化块");
    
    
        }
    
        //定义一个无参数的构造器
    
        public CodeVald() {
    
          System.out.println("codevald的无参数构造器");
    
        }
    
        public static void main(String[] args) {
    
          new CodeVald();
    
    
        } 
    
    
    
      }
    
      
    }
    
    
    

    上面的代码定义了两个普通初始化块和一个构造器,那么执行的顺序也很简单了,先定义的初始化块先执行,然后执行后定义的初始化块,接着执行构造器的内容

    来看下编译运行结果

    静态初始化块

    使用static定义,当类装载到系统只会执行一次。如果在静态初始化块中想初始化变量的话,就只能初始化类变量了,即是由static修饰的数据成员。

    来看一个静态初始化块、普通初始化块和构造器结合的例子:

    
    public class JingTai_CodeBlock {
    
        public static void main(String[] args) {
    
    
          new C();
          new C();
    
    
        }
    
    }
    
    
    //定义第一个类A,这是父类
    
    class A {
    
    
        static {
    
    
          System.out.println("A的静态初始化块");
    
    
        }
    
        {
    
          System.out.println("A的普通初始化块");
    
        }
    
        public A() {
    
          System.out.println("A的无参数构造器");
    
        }
    
    
    }
    
    //定义一个子类B
    
    class B extends A {
    
        static {
    
          System.out.println("B的静态初始化块");
    
    
        }
    
        {
    
          System.out.println("B的普通初始化块");
    
    
        }
    
        public B() {
    
          System.out.println("B的无参数构造器");
    
        }
    
        public B(String message) {
    
          this();
    
          //通过this()调用无参数的构造器(即重载的构造器)
          System.out.println("B的带参数构造器,传入的信息为: " + message);
    
    
    
        }
    
    }
    
    //定义一个子类C
    
    
    class C extends B {
    
    
        static {
    
          System.out.println("C的静态初始化块");
    
        }
    
        {
    
          System.out.println("C的普通初始化块");
    
    
        }
    
        public C() {
    
          //通过super调用父类中带参数的构造器
    
          super("codevald");
          System.out.println("C的构造器");
    
    
    
        }
    
    }
    
    

    上述代码定义了A、B、C三个类,他们都提供了静态初始化块和普通初始化块,并且在类B中使用this()调用了重载构造器,在C中使用super()显式地调用了其父类指定的构造器,接着在main()函数调用了两次new C(),创建两个C对象。

    那么来猜下会输出什么结果

    先思考五分钟哦

    现在来解释一下,这里定义了静态初始化块,那么会在类地初始化阶段执行静态初始化块,而不是创建对象的时候才执行,所以静态初始化块总是比普通初始化块先执行,接着是构造器

    但是系统在类初始化阶段执行静态初始化块的时候,不仅会执行本类的静态初始化块,还会一直上溯到java.lang.Object类(所有对象的父类),如果它包含静态初始化块,先执行java.lang.Object类的静态初始化块,然后执行其父类的静态初始化块...执行完最后才执行该类的静态初始化块,经过上述过程才能完成该类的初始化过程。

    第一次创建C对象的时候,会先运行静态初始化块的内容,但是会先上溯到顶级父类的静态初始化块,所以会先输出A的静态初始化块,接着才是B的静态初始化块,最后是C的静态初始化块

    执行完静态初始化块,一样先执行顶级父类的普通初始化块,即输出A的普通初始化块,接着执行顶级父类的构造器代码,即输出A的无参数构造器。然后输出父类的普通初始化块,接着是构造器,所以输出B的普通初始化块,因为C的构造器调用的是带参数的父类构造器,所以B中会调用带参数的构造器B,所以会输出B的无参数构造器B的带参数构造器,传入的信息为: codevald,接着执行C的普通初始化块的代码,即输出C的普通初始化块,然后是构造器的代码,即C的构造器

    第二次创建实例C的时候,因为类C已经在虚拟机中存在,所以无需再初始化C类了,所以静态初始化块的代码不再执行,而是重复地执行静态后面的代码。

    final修饰符

    final可以用来修饰类、变量和方法,通过final修饰以后,被修饰的类、方法和变量就表示不可改变的状态。

    修饰成员变量

    成员变量是随着类的初始化或者对象初始化而初始化的。当初始化的时候,就会为类的类属性分配内存,并设置一个默认值;当创建对象时,就会为对象的实例属性分配内存,并分配默认值。一般来说,都是在普通初始化块、静态初始化块、构造器中区指定初始值的。

    那么,final修饰的属性,在哪里声明初始值是有一定的规则的,具体如下:

    • 修饰类属性时:可在静态静态初始化块中声明该属性的初始值
    • 修饰实例属性时: 可在普通初始化块中或者构造器中指定初始值

    修饰局部变量

    在初始化局部变量的时候,局部变量必须由程序员显式地去初始化。但是使用final修饰地局部变脸既可以指定默认值,也可以不指定默认值。假如在定义修饰的局部变量时没有指定默认值,则可以在后面代码中对该变量赋予一个指定的初始值。

    那么,现在就final和初始化块结合起来,来看一段代码

    
    public class UseFinal {
    
    	//定义成员变量时指定默认值
    	
    	final String author = "codevald";
    	final String str;
    	final int a;
    	final static double d;
    	
    	//初始化块,可对没有指定默认值的实例属性指定初始值
    	
    	{
    	
    		str = "Hello";
    		
    		//由于定义author时已经制定了默认值,因此不能为author重新赋值,下列语句会导致编译错误
    		
    		//author = "CodeVald"
    	
    	
    	}
    
    
    	
    
    	static {
    	
    		//在静态初始化块中为类属性指定初始值
    	
    		d = 2.1;
    	
    	
    	}
    
    	public UseFinal() {
    	
    		a = 21;
    	
    	}
    	
    	
    	public void useFinal() {
    	
    		//普通方法不能为fina修饰的成员变量指定初始化值
    		
    		//d = 2.1;
    	
    	
    	}
    
    
    
    	public static void main(String[] args) {
    	
    		UseFinal useFinal = new UseFinal();
    		System.out.println(useFinal.author);
    		System.out.println(useFinal.str);
    		System.out.println(useFinal.a);
    		System.out.println(useFinal.d);
    	
    	
    	
    	}
    
    
    }
    
    

    运行结果也很容易就出来了,但是,这里要注意一点的是,普通方法不能为final修饰的变量赋值,会出现编译错误的问题。

    来看下运行结果

    总结一下,final成员变量(包括实例成员和类成员)必须由程序员显式地初始化,系统不会对final成员进行隐式初始化。如果想在初始化块、构造器中对final的成员变量进行初始化,那么一定要在初始化之前就访问该成员变量的值。

    final方法

    在Java中,经常用final修饰那些不希望被重写的方法。所以,如果我们不希望子类重写父类的某个方法,就可以使用final修饰该方法。我们有时候会希望获取一个Object对象,所用的getClass()方法就是一个final方法,因为它的设计者不希望该方法被重写,就用final将该方法密封起来。

    final修饰的方法只是不能重写,但是可以重载。

    
    public class Incorrect {
    
    	public final void test() {
    	
    	
    	
    	  }
    	
    
    }
    
    
    class Sub extends Incorrect {
    	
    	//下面的写法将导致编译错误,不能重写final修饰的方法
    	
    	@Override 
    	
    	
    	public void test() {
    	
    	
    	
    	
    	  }
    
    
    }
    
    
    

    编译程序,执行结果如下

    在Java程序中,对于private修饰的方法来说,它只在当前类中可见,所以其子类无法访问该方法。所以,如果在子类中定义了一个与父类private方法有相同方法名、形参列表和返回值类型的方法,这不是方法重写,只是重新定义了一个新方法。不会出现编译错误的问题

    例如下面的代码在子类中重写父类的 private final方法

    
    public class Invaild {
    
    	private final void test() {
    	
    	
    	
    	}
    
    }
    
    
    class Sub extends Invaild {
    
    	public void test() {}
    
    
    }
    
    

    在匿名内部类中,很多时候也会用到final的地方,现在先来系统地谈谈内部类是啥东西。

    内部类

    内部类指的是在外部类的内部再定义一个类,内部类作为外部类的一个成员,是依附在外部类而存在的。内部类可以是静态的,非静态的,可以使用protected和private来修饰,而外部类只能使用public和默认的包访问权限。Java中的内部类主要有成员内部类、静态内部类、局部内部类和匿名内部类。

    那么内部类有什么使用的价值呢?

    Java是从JDK1.1开始引入了内部类,内部类的主要作用如下:

    • 内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类
    • 内部类的成员可以直接访问外部类的私有数据,因为内部类被当成了外部类的成员,同一个类中的成员之间是可以互相访问的。但外部类不能访问内部类的实现细节,譬如属性。
    • 匿名内部类适用于那些创建仅使用一次的类

    内部类是一个编译时的概念,一旦编译成功,外部类和内部类就成为完全不同的类,即生成两个类的编译文件,分别是outer.class和outer$inner.class(假如外部类是outer,内部类是inner)。

    成员内部类

    在大多数的情况下,内部类作为成员内部类来定义。成员内部类是一种与属性、方法、构造器和初始化块相似的类成员。局部内部类和匿名内部类都不是类成员。Java中的成员内部类分别是静态内部类和非静态内部类。使用static修饰的就是静态内部类,没有使用static修饰的成员内部类就是非静态内部类.

    非静态内部类

    来看一段代码

    
    
    public class FeiJingTai {
    	
    	private String area;
    	
    	
    	//重载构造器
    	
    	public FeiJingTai() {
    	
    	
    	}
    	
    	public FeiJingTai(String area) {
    	
    		this.area = area;
    	
    	}
    	
    	//定义内部类
    	
    	private class FeiJingTaiInner {
    	
    		//内部类的属性
    		
    		private String name;
    		private String wechat;
    		
    		public FeiJingTaiInner(String name,String wechat) {
    		
    			this.name = name;
    			this.wechat = wechat;
    		
    		
    		}
    		
    		//内部方法
    		
    		public void info() {
    		
    		
    			System.out.println("CodeVald的作者是 " + name + ",微信号是 " + wechat);
    			System.out.println("所属地区是 " + area);
    		
    		
    		
    		}
    
    	
    	}
    	
    		//外部类测试方法
    	
    		public void test() {
    		
    		
    			FeiJingTaiInner a = new FeiJingTaiInner("codevald","valdcode");
    			a.info();
    
    		}
    
    
    		public static void main(String[] args) {
    		
    		
    			FeiJingTai a = new FeiJingTai("广东广州");
    			a.test();
    
    		
    		}
    
    
    
    }
    
    

    在上面的代码中,可以看到在非静态内部类中可以直接访问外部类的私有成员,所以其实就是在类FeiJingTaiInner的方法内直接访问外部类的私有属性。这是因为在类FeiJingTaiInner内部类对象中保存了一个它储存的外部类对象的引用[当调用非静态内部类的实例方法时,必须有一个非静态内部类实例,而非静态内部类实例必须寄居在外部类实例里]

    编译程序,将会看到在文件路径下生成了两个类文件一个是FeiJingTai.class,另一个是FeiJingTai$FeiJingTaiInner.class

    执行后的结果

    再来看一段代码

    
    class MemberInner {// 定义类 MemberInner,这是一个外部类
        private String name = "codevald";
    
        public void execute() {
            // 在外部类中创建成员内部类
            InnerClass innerClass = this.new InnerClass();
        }
    
        /** 成员内部类 */
        public class InnerClass {
            // 内部类可以创建与外部类同名的成员变量
            private String name = "codevald";
    
            public void execute() {
                System.out.println(this.name);
                // 在内部类中使用外部类成员变量的方法
                System.out.println(MemberInner.this.name);
            }
        }
    
        public static void main(String[] args) {
            MemberInner.InnerClass innerClass = new MemberInner().new InnerClass();
            innerClass.execute();
        }
    }
    
    

    在上面的代码中,使用了两种方式创建内部类对象,一种是用外部引用的方式,另一种是调用方法创建,在execute()方法中,this代表的是创建在堆中的外部对象,而在内部类,使用this是分别引用内部类中的属性和外部类中的属性。

    看下编译运行结果

    静态内部类

    如果不需要内部类对象与外部类对象之间有联系,则可以将内部类声明为static。在非静态内部类中,内部类对象通常会保存了一个指向外部类的引用,如果内部类是static时就不用了,非静态内部类通常也称为嵌套类

    嵌套类要注意以下两点:

    • 要创建嵌套类的对象,不需要外部类的对象
    • 不能直接从嵌套类的对象中访问非静态的外部类对象

    从一段具体的代码来分析一下

    
    public class JingTai {
    
    	private String name_1 = "codevald";
    	private static String name_2 = "codevald";
    	
    	static class JingTaiInner {
    	
    		private static String name;
    		
    		public static void main(String[] args) {
    		
    			//可以输出外部类的静态类成员变量
    		
    			System.out.println(name_2);
    			
    			//System.out.println(name_1);
    			//不可以直接输出外部类的非静态类成员变量
    			
    			//得生成对象,再用对象引用去访问
    			
    			JingTai a = new JingTai();
    			System.out.println(a.name_1);
    		
    		
    		
    		
    		}
    
    	
    	}
    
    	public static void main(String[] args) {
    		
    		JingTai.JingTaiInner a = new JingTai.JingTaiInner();
    		
    
    		
    	}
    
    
    
    }
    
    

    运行结果

    上面的代码中,在内部类有输出语句,然后再外部类创建内部类,但是在内部类中,只能直接访问外部类的静态属性,要访问外部类的非静态属性得生成对象,再用对象的引用去访问。

    所以,生成一个静态内部类不需要外部类成员,这是静态内部类和成员内部类的区别。静态内部类可以直接[Outer.Inner inner = new Outer.Inner();],而不需要通过外部类来完成。这样子实际上静态内部类就是一个独立的类。

    局部内部类

    在方法中定义的内部类就是局部内部类。与局部变量相似的是,局部内部类可以访问当前代码中的常量和外部类的所有成员。在Java中,和局部变量一样,不能将局部内部类定义为public、private、protected、和static类型,并且在定义方法时,只能在方法中声明final类型的变量。

    看一段代码

    
    public class LocalInner {
    
    	public static void main(String[] args) {
    	
    	
    		class InnerClass {
    		
    			String name;
    		
    		}
    	
    		class InnerSub extends InnerClass {
    		
    			String des;
    		
    		
    		}
    	
    		//创建局部内部类的对象
    		
    		InnerSub is = new InnerSub();
    		is.name = "codevald";
    		is.des = "想起来了吗?看完就想起来了";
    		System.out.println("author: " + is.name + "
    " + "subject: " + is.des);
    
    	}
    
    }
    
    

    编译运行结果

    匿名类

    在实际的项目动手过程,经常会看到一个很奇怪的写法,直接在代码中随机用new新节一个对象,然后在new;里面直接简单粗暴的加入要执行的代码,这就是匿名类。好处就是代码简洁、紧凑,不会出现一大段繁杂的类定义代码。

    在Java程序中,因为匿名类没有名字,所以它的创建方式初学的时候看起来会很懵逼,创建的格式如下:

    
    new 类/接口名(参数列表) [实现接口()]{
         //匿名内部类的类体部分
    }
    
    

    {...}中可以定义变量的名称、方法,它跟普通的类一样。

    因为Java程序中的匿名类没有名称,所以不能在其他地方引用,也不能实例化,只能使用一次,而且里面不能有构造器。

    来看一段代码

    先定义一个抽象父类

    
    public abstract class Author {
    	
    	private String name;
    	
    	public String getName() {
    	
    		return name;
    	
    	}
    
    
    	public void setName(String name) {
    	
    		this.name = name;
    	
    	}
    
    	public abstract int article();
    
    
    }
    
    

    编写测试类进行测试,在类中,test()方法接收一个Author类型的参数,同时要先实现类才能new新的类实例,在方法中直接使用匿名内部类新建一个Author实例。

    
    public class NiMing {
    
    	public void test(Author author) {
    	
    		System.out.println("这是" + author.getName() + "的第" + author.article() + "篇原创作品.");
    	
    	}
    	
    	
    	public static void main(String[] args) {
    	
    		NiMing test = new NiMing();
    		test.test(new Author() {
    		
    			//使用匿名内部类来创建一个Author实例
    			
    			@Override 
    			
    			public int article() {
    				
    				return 2;
    			
    			}
    		
    		
    			@Override 
    			
    			public String getName() {
    				
    				return "codevald";
    			
    			}
    
    		
    		});
    
    	}
    
    
    }
    
    

    编译运行结果

    终于讲完了,现在要进入主题了,匿名内部类中什么时候会用到final呢?

    使用final形参

    在Java中,当我们需要给匿名内部类传递参数的时候,并且如果在内部类中使用该形参的时候,这个形参则必须由final修饰的。即该匿名内部类所在方法的形参必须加上final修饰符。

    编写一段代码

    
    public class NiMing_Final {
    
    	public static void main(String[] args) {
    	
    		NiMing_Final niming = new NiMing_Final();
    		Inner inner = niming.getInner("codevald",true);
    		System.out.println("这是" + inner.getName() + "的第" + inner.article() + "篇原创作品");
    	
    	
    
    	}
    
    
    
    	public Inner getInner(final String name,boolean isOriginal) {
    	
    		return new Inner() {
    		
    			private String nameStr = name;
    			private int article;
    			
    			{
    			
    				//实例初始化
    				
    				if (isOriginal) {
    				
    					article = 2;
    				
    				} else {
    				
    					article = 0;
    				
    				}
    
    			
    			}
    		
    		
    			@Override 
    			
    			public String getName() {
    			
    				return nameStr;
    			
    			}
    			
    			@Override 
    			
    			public int article() {
    			
    				return article;
    			
    			}
    		
    		
    
    		
    		};
    	
    
    	}
    
    
    }
    
    
    
    interface Inner {
    
    	String getName();
    	int article();
    
    
    }
    
    

    这里通过实例初始化实现类似构造器的功能

    来看下运行结果

    枚举类

    在枚举类中,使用final的频率是最频繁的。什么是枚举类?在大多数情况下,我们要实例化的类对象是有限的而且固定的,例如季节,这种实力数量有限而且固定的类,在Java中被称为枚举类。

    我们先来做个有意思的事情,自己模拟实现一个枚举类,在实现枚举类的时候,有以下几个步骤:

    • 通过private将构造器隐藏起来
    • 把此类需要用到的所有实例都用public static final修饰的形式保存起来
    • 提供一些静态方法允许其他程序根据特定参数获取与之匹配的实例

    那么可以定义一个Season类,在里面分别为4个季节定义4个对象,这样类Season就定义为了一个枚举类。

    
    public class Season {
    
    	//将Season定义成不可变得,将其属性定义成final
    	
    	private final String name;
    	private final String description;
    	
    	public static final Season SPRING = new Season("春天","绿肥红瘦");
    	public static final Season SUMMER = new Season("夏天","骄阳似火");
    	public static final Season FALL = new Season("秋天","天高云淡");
    	public static final Season WINTER = new Season("冬天","惟余莽莽");
    
    	//构造器一定要定义为private属性
    	
    	private Season(String name,String description) {
    	
    		this.name = name;
    		this.description = description;
    	
    	}
    	
    	//也可以通过getSeason()获取枚举常量
    	
    	public static final Season getSeason(int seasonValue) {
    	
    		switch(seasonValue) {
    		
    			case 1:
    				
    				return SPRING;
    			
    			
    			
    			case 2:
    				
    				return SUMMER;
    			
    			
    			case 3:
    				
    				return FALL;
    
    			
    			case 4:
    			
    				return WINTER;
    
    				
    			
    			default:
    			
    				return null;
    
    		}
    	
    
    	}
    
    
    	public String getName() {
    	
    		return this.name;
    	
    	
    	}
    
    
    	public String getDescription() {
    	
    		return description;
    		
    	
    	}
    
    }
    
    

    类Season就成为了一个不可变的类,此类包含了4个static final常量的属性,也就代表了该类所能创建的对象。其他程序需要用到Season对象时,可以用Season.SPRING方式或者getSeason()静态方法获得。

    编写测试类

    
    public class TestSeason {
    
    	public TestSeason(Season s) {
    	
    		System.out.println(s.getName() + ",是一个" + s.getDescription() + "的季节");
    
    	}
    
    
    	public static void main(String[] args) {
    	
    	
    	
    		new TestSeason(Season.SPRING);
    		new TestSeason(Season.SUMMER);
    		new TestSeason(Season.FALL);
    		new TestSeason(Season.WINTER);
    
    	}
    	
    }
    
    

    运行结果

    自己模拟完枚举类后,会发现枚举类其实就是在类编译的时候,就生成了相对应的静态常量,并且构造器是对用户透明的,它会自己进行初始化,我们只需要关心我们需要获取什么样的枚举对象就可以了。

    枚举类型是从JDK1.5开始引入的,Java引入了一个新的关键字enum来定义枚举类。这个enum所定义的类实际上都是继承自类库中Enum(java.lang.Enum)的子类,它继承了Enum中许多有用的方法。

    来继续看一段代码

    
    public enum Color {
    
    	RED(255,0,0),BLUE(0,0,255),BLACK(0,0,0),YELLOW(255,255,0),GREEN(0,255,0);
    
    	private Color(int redValue,int greenValue,int blueValue) {
    	
    		this.redValue = redValue;
    		this.greenValue = greenValue;
    		this.blueValue = blueValue;
    
    	}
    	
    	
    	@Override 
    	
    	public String toString() {
    		
    		//覆盖了父类Enum的toString()方法
    
    		return super.toString() + "(" + redValue + "," + greenValue + "," + blueValue + ")";
    
    	}
    	
    
    
    
    	//自定义数据域
    
    	private int redValue;
    	private int greenValue;
    	private int blueValue;
    
    }
    
    

    上面的Color枚举类是一个不可继承的final类。枚举值(RED...)都是Color类型的静态常量,因为枚举类是class,所以在枚举类型中也可以有构造器、方法和数据域,但是枚举类的构造器是私有的,它会自己调用。

    而且在上面的枚举类中,重写了枚举类Enum的toString()方法,打印出更完整的信息。

    来看下会有什么输出结果

    在上面的代码中,调用了Enum的ordinal()方法,它会返回枚举值在枚举类中的顺序,这个顺序是根据枚举值在声明的顺序中定的,所以会输出"0 1 2 3 4"。

    然后调用了Enum的valueOf()方法,此方法是和toString()方法对应的,返回带指定名称的指定类型的枚举常量,所以会输出"BLUE(0,0,255)"。

    最后,可能大家会疑惑,为什么println输出会调用重写的toString()方法呢?

    别急,让我来一一分析一下。

    直接看Java相关类的源代码就可以分析出来了。

    先来看下System.out.println和System.out.print的源代码

    
    public void println(Object x){ 
      String s = String.valueOf(x); 
         synchronized (this) 
          { 
          print(s); 
    
          newLine();
    
          } 
    
    
    
    
    
       public void print(Object obj) { 
        write(String.valueOf(obj));
      } 
    
    

    可以看到,当要打印一个对象时,会自动调用String.valueOf()这个方法。

    那么我们再来看下valueof()这个方法的源代码

    
    public static String valueOf(Object obj) {
        return (obj == null) ? "null" : obj.toString();
    }
    
    

    这段代码的意思就是,当传入的对象为Null时,会返回一个null,当非null时,会返回这个obj的toString()方法,所以,非null时就会调用toString()方法,原因我们就知道了,这就是当我们调用 print 或者 println 打印一个对象时,它会打印出这个 对象的 toString()的最终根源。

    所以,我觉得平时没事可以多研究JDK的源代码,站在巨人的肩膀上,看下怎么写出更简洁优美的代码。


    今天的内容就到这里了,相信看到这里,你应该明白了final大概是怎么用的,什么时候需要用。“合抱之木,生于毫末。”只有站在设计者的角度,从根本上去理解为什么这么设计,吃透每个基本的知识点,并且深入研究源码,才能让内功更深厚,从而去解决一个又一个更高深的问题。

    十年饮冰,难凉热血。
  • 相关阅读:
    linux下安装nginx
    阿里云CentOS系统配置iptables防火墙
    没用私服,自己安装下本地jar包,制作坐标
    Redis加入Centos Linux开机启动
    CentOS6.5安装redis(3.0.3)
    Spring Boot 第一个demo
    JDK下载与安装
    IDEA-Maven下载及安装
    项目注意:
    idea项目Tomcat 插件 运行报错
  • 原文地址:https://www.cnblogs.com/codevald/p/14375582.html
Copyright © 2011-2022 走看看