==、equals和new、直接赋值

==、equals和new、直接赋值

我们通过如下实例来说明,先看一个简单的代码:

 
public class Practice1 {
 
	public static void main(String[] args) {
		String str1=new String("hello");
		String str2=new String("hello");
		String str3="hello";
		String str4="hello";
		
		//结果为true
		System.out.println("equals: "+str1.equals(str2));
		//1.结果为true   
		System.out.println("equals: "+str1.equals(str3));
		//2.结果为false  
		System.out.println("str==str1  "+(str1==str3));
		//结果为true
		System.out.println("str2==str1  "+(str3==str4));
    }
}


在回答问题之前,我们需要再来明确某些内容,它们可以帮助我们更好的理解问题的答案。

前文叙述有点长,也可先看第三部分,如果看完之后还不是很懂,可以回来从此再看。

(1)、先谈创建对象的相关内容

虽然我们都知道Java是面向对象的编程,但并非完全的面向对象。比如说Java中的基本数据类型,用int,double等创建的变量都不是对象。一般我们都是通过new 关键字来创建对象,而基本数据类型创建的变量并不能用new 的方式获取。虽然如此,但Java对基本数据类型也有相应的解决办法——封装与其相应的类,即Integer对应int,Double对应double,它们皆是为了解决基本数据类型面向对象用的。

明确这些之后,我们再来看类型是如何分配的,基本数据类型在栈中进行分配,而对象类型在堆中进行分配。基本类型之间的赋值是创建新的拷贝,而对象之间的赋值只是传递引用。所有方法的参数(基本类型除外)传递的都是引用而非本身的值。

现在,再来聊聊String s=“hello”;以及String s = new String(“hello”);

我在之前的一篇博客中简单的提到过String s=“hello”;(变量&数据类型),它与new不同,同时也是java中唯一不需要new就可以产生对象的途径。这种形式的赋值称为——直接量,它被放在一个叫作字符串拘留池(常量池)的地方;而new 创建的对象都放在堆上。String s=“hello” 这种形式的字符串,会在JVM(Java虚拟机)中发生字符串拘留。

那什么是字符串拘留呢?我们通过一个例子来理解这种机制,当我们声明一个字符串String s=“hello”;时,JVM会先从常量池中查找有没有值为"hello"的对象。如果有,会把该对象赋给当前引用,也就是说原来的引用和现在的引用指向同一个对象;如果没有,则在拘留池中创建一个值为"hello"的对象,如果下一次还有类似的语句,例如String str=“hello”;时,又会将str指向"hello"这个对象。以这种形式(直接量赋值,而非new)声明字符串,无论有多少个都指向同一个对象

再来说说String s = new String(“hello”);

这种形式创建的对象就和其他new 创建的对象一样了,每执行一次就生成一个新对象,也就是说String str = new String(“hello”);与s毫无关系,他们是两个独立的对象,只不过巧了,他们的值或是说内容相等

我们也可以简单的理解为:

String str = “hello”; 先在内存中找是否有"hello"这个对象,如果有就可以直接从常量池中拿来用,不用再从内存中创建空间来存储。如果没有,就创建一块新内存存着,以后要是有对象要用就直接给它用了。

String str=new String (“hello”) 就是不管内存里有没有"hello"这个对象,都会在堆新建一个对象保存"hello"。

看几个例子:

String s1 = “qibao”; // 放在常量池中,没找到,新建一个

String s2 = “qibao”; // 从常量池中查找,找到了,直接引用。s1,s2指向同一个对象

String s3 = new String(“qibao”); // s3 为一个引用

String s4 = new String(“qibao”); // s4 也是一个引用。虽然s3,s4对象的内容一样,但他们却占着两块地。
在这里插入图片描述

String s5 = “qi” + “bao”; //字符串常量相加,在编译时就会计算结果(即相当于String s5 = “qi"bao”;),s1 == s5 返回ture

String s6 = “qi”;

String s7 = “bao”

String s8 = s6 + s7; //字符串变量相加,编译时无法计算,s1 == s8 返回false

class Person{	  
    String name;	  
    Person(String name) { 
        this.name = name;
    }	
}	
Person p1 = new Person("qibao");	
Person p2 = new Person("qibao");	
p1.name == p2.name //返回true

在这里插入图片描述

(2)、再说== 跟equals() 的事

先解释几个名词:

  • 寄存器:最快的存储区, 系统分配,程序中无法控制.
  • 栈:基本数据类型变量和对象引用的存储区,对象本身不放在栈中,而是存放在堆或者常量池中。
  • 堆:new创建对象的存储区。
  • 静态域:静态成员变量的存储区。
  • 常量池:基本数据类型常量和字符串常量的存储区。

== 或 != 比较的是栈中存放的对象引用在堆上的地址,

即判断两对象的堆地址是否相同,即是否是指相同一个堆对象。

对于基本类型,== 和 != 是比较值。
对于对象来说,== 和 != 是比较两个引用,即判断两个对象的地址是否相同.

equals()

我们先来看Object中定义的equals()

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

​ Object.equals()使用的算法区分度高,只要两对象不是同一个就是错误的。由于所有的类都继承自Object类,所以equals()适用于所有对象。Object中的equals方法返回 == 的判断,即对象的地址判断。 虽然如此,但可以对Object.equals()进行覆盖,String类则实现覆盖。我们再来看看String.equals():

1==、equals和new、直接赋值
	我们通过如下实例来说明,先看一个简单的代码:
 
public class Practice1 {
 
	public static void main(String[] args) {
		String str1=new String("hello");
		String str2=new String("hello");
		String str3="hello";
		String str4="hello";
		
		//结果为true
		System.out.println("equals: "+str1.equals(str2));
		//1.结果为true   
		System.out.println("equals: "+str1.equals(str3));
		//2.结果为false  
		System.out.println("str==str1  "+(str1==str3));
		//结果为true
		System.out.println("str2==str1  "+(str3==str4));
    }
}

	在回答问题之前,我们需要再来明确某些内容,它们可以帮助我们更好的理解问题的答案。
	前文叙述有点长,也可先看第三部分,如果看完之后还不是很懂,可以回来从此再看。
(1)、先谈创建对象的相关内容
	虽然我们都知道Java是面向对象的编程,但并非完全的面向对象。比如说Java中的基本数据类型,用intdouble等创建的变量都不是对象。一般我们都是通过new 关键字来创建对象,而基本数据类型创建的变量并不能用new 的方式获取。虽然如此,但Java对基本数据类型也有相应的解决办法——封装与其相应的类,即Integer对应int,Double对应double,它们皆是为了解决基本数据类型面向对象用的。
	明确这些之后,我们再来看类型是如何分配的,基本数据类型在栈中进行分配,而对象类型在堆中进行分配。基本类型之间的赋值是创建新的拷贝,而对象之间的赋值只是传递引用。所有方法的参数(基本类型除外)传递的都是引用而非本身的值。
	现在,再来聊聊String s="hello";以及String s = new String("hello");
	我在之前的一篇博客中简单的提到过String s="hello";(变量&数据类型),它与new不同,同时也是java中唯一不需要new就可以产生对象的途径。这种形式的赋值称为——直接量,它被放在一个叫作字符串拘留池(常量池)的地方;而new 创建的对象都放在堆上。String s="hello" 这种形式的字符串,会在JVM(Java虚拟机)中发生字符串拘留。
	那什么是字符串拘留呢?我们通过一个例子来理解这种机制,当我们声明一个字符串String s="hello";时,JVM会先从常量池中查找有没有值为"hello"的对象。如果有,会把该对象赋给当前引用,也就是说原来的引用和现在的引用指向同一个对象;如果没有,则在拘留池中创建一个值为"hello"的对象,如果下一次还有类似的语句,例如String str="hello";时,又会将str指向"hello"这个对象。以这种形式(直接量赋值,而非new)声明字符串,无论有多少个都指向同一个对象。
	再来说说String s = new String("hello");
	这种形式创建的对象就和其他new 创建的对象一样了,每执行一次就生成一个新对象,也就是说String str = new String("hello");与s毫无关系,他们是两个独立的对象,只不过巧了,他们的值或是说内容相等。
	我们也可以简单的理解为:
	String str = "hello"; 先在内存中找是否有"hello"这个对象,如果有就可以直接从常量池中拿来用,不用再从内存中创建空间来存储。如果没有,就创建一块新内存存着,以后要是有对象要用就直接给它用了。
	String str=new String ("hello") 就是不管内存里有没有"hello"这个对象,都会在堆新建一个对象保存"hello"。
	
        看几个例子:
	 String s1 = "qibao"; // 放在常量池中,没找到,新建一个
            String s2 = "qibao"; // 从常量池中查找,找到了,直接引用。s1,s2指向同一个对象
	 String s3 = new String("qibao"); // s3 为一个引用
            String s4 = new String("qibao"); // s4 也是一个引用。虽然s3,s4对象的内容一样,但他们却占着两块地。
图片: https://uploader.shimo.im/f/LgdYFMDyjZ8cWnG7.png
	String s5 = "qi" + "bao"; //字符串常量相加,在编译时就会计算结果(即相当于String s5 = "qi"bao";),s1 == s5 返回ture
	String s6 = "qi"; String s7 = "bao"
	String s8 = s6 + s7; //字符串变量相加,编译时无法计算,s1 == s8 返回false
	class Person{
	    String name;
	    Person(String name) { this.name = name;}
	}
	Person p1 = new Person("qibao");
	Person p2 = new Person("qibao");
	p1.name == p2.name  //返回true
图片: https://uploader.shimo.im/f/gvB9wRrxfqx5Ig76.png
(2)、再说==equals() 的事
 ==
先解释几个名词:
寄存器:最快的存储区, 系统分配,程序中无法控制. 
栈:基本数据类型变量和对象引用的存储区,对象本身不放在栈中,而是存放在堆或者常量池中。
堆:new创建对象的存储区。 
静态域:静态成员变量的存储区。
常量池:基本数据类型常量和字符串常量的存储区。
	==!=   比较的是栈中存放的对象引用在堆上的地址,
           即判断两对象的堆地址是否相同,即是否是指相同一个堆对象。
        
           对于基本类型,==!= 是比较值。
           对于对象来说,==!= 是比较两个引用,即判断两个对象的地址是否相同.
equals()
	我们先来看Object中定义的equals()
public boolean equals(Object obj) {
  return (this == obj);
}     
	Object.equals()使用的算法区分度高,只要两对象不是同一个就是错误的。由于所有的类都继承自Object类,所以equals()适用于所有对象。Object中的equals方法返回 == 的判断,即对象的地址判断。 虽然如此,但可以对Object.equals()进行覆盖,String类则实现覆盖。我们再来看看String.equals()private final char value[];
 
public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }
 
public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

          查看String对equals覆盖的源码会发现,String.equals()相等的条件是:比较二者同为String类型,长度相等,且字符串值完全相同,包括顺序和值,不再要求两者为同一对象。也可以理解为String.equals()将原本的String对象拆分成单个字符之间值的比较,每个字符的比较完之后返回一个最终的boolean类型的值,即将原本可能指向不同堆地址的两个对象  "间接的" 指向了同一个地址,以到达比较值的目的。
(3)、解决问题的时候到了
	看完上边这些内容之后,我们再来看这两行代码:
String str1=new String("hello");
String str3="hello";
 
//1.结果为true   
System.out.println("equals: "+str1.equals(str3));
//2.结果为false  
System.out.println("str==str1  "+(str1==str3));

	现在,我们就可以很清楚的知道为什么1 返回true:
	String.equal() 只看两者是否为String,长度是否相等,以及每个字符的值是否相等,很显然str1和str2满足这三点要求,所以返回结果为真。
	至于2 返回false,我们也明白为何了:
	== 是对对象地址的判断,而这两种声明方式的存储形式是不同的(前者是在堆区创建对象,后者是在常量池),因此它们的地址不同,自然返回false了。
	 
2trycatchfinallyreturn执行顺序
有关trycatchfinallyreturn执行顺序的题目在面试题中可谓是频频出现。总结一下此类问题几种情况。
写在前面
不管try中是否出现异常,finally块中的代码都会执行;
当trycatch中有return时,finally依然会执行;
finally是在return语句执行之后,返回之前执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,不管finally中的代码怎么样,返回的值都不会改变,仍然是之前保存的值),
finally中如果包含return,那么程序将在这里返回,而不是trycatch中的return返回,返回值就不是trycatch中保存的返回值了。
注:
finally修改的基本类型是不影响 返回结果的。(传值)
修改list,map,自定义类等引用类型时,是影响返回结果的。(传址的)对象也是传址的
但是date类型经过测试是不影响的。
1try{} catch(){}finally{} return;
按程序顺序运行,如果try中有异常,会执行catch中的代码块,有异常与否都会执行finally中的代码;最终返回。
2try{ return; }catch(){} finally{} return;
先执行try块中return 语句(包括return语句中的表达式运算),但不返回;
执行finally语句中全部代码
最后执行tryreturn 返回
finally块之后的语句return不执行,因为程序在try中已经return。
示例:
 public class Test{
  public int add(int a,int b){
      try{
          return a+b;
      }catch(Exception e){
          System.out.println("catch语句块")     
      }finally{
          System.out.println("finally语句块")  
      }
      return 0;
  }   
     public static void main(String argv[]){
         Test test=new Test();
         System.out.println("和是:"+test.add(9,34));  
     }
 }
输出是
 finally语句块 和是:43
至于为什么不是:和是 finally43的原因:
System.out.println("和是:"+test.add(9,34));  这是进行字符串拼接是一个整体,所以首先是进入add方法,进去之后先不运算result,而是输出finally块。finally语句块,这句话先打印到控制台中。打印完后返回来执行try中的return得到43,此时再将结果与和是:拼接,输出和是:43.所以最终输出finally语句块 和是:433try{} catch(){return;} finally{} return;
程序先执行try,如果遇到异常执行catch块,最终都会执行finally中的代码块;
有异常:
执行catch中的语句和return中的表达式运算,但不返回
执行finally语句中全部代码,
最后执行catch块中return返回。 finally块后的return语句不再执行。
无异常:执行完tryfinallyreturn…
示例1:有异常
 ​
 public class Test04 {
   public static void main(String[] args) {
     System.out.println(test());
   }
   private static int test() {
     int temp = 1;
     try {
       System.out.println(temp);
       int i=1/0;
     } catch (Exception e) {
       System.out.println(temp);
       return ++temp;
     } finally {
       ++temp;
       System.out.println(temp);
     }
     return temp;
   }
 }
 ​
输出:1132
先执行try中的打印temp=1;有异常,执行catch中的打印,然后执行return中的表达式运算,此时temp=2,并将结果保存在临时栈中,但不返回;执行finally中的语句,temp++,此时temp更新为3,同时打印,但因为finally中的操作不是return语句,所以不会去临时栈中的值,此时临时栈中的值仍然是2finally中的执行完后,执行catch中的返回,即2。
示例2:无异常
 ​
 public class Test04 {
   public static void main(String[] args) {
     System.out.println(test());
   }
   private static int test() {
     int temp = 1;
     try {
       System.out.println(temp);
     } catch (Exception e) {
       System.out.println(temp);
       return ++temp;
     } finally {
       ++temp;
       System.out.println(temp);
     }
     return ++temp;
   }
 }
 ​
输出:123
4try{ return; }catch(){} finally{return;}
执行try块中的代码,和return语句(包括return语句中的表达式运算),但不返回(tryreturn的表达式运算的结果放在临时栈);
再执行finally块,
执行finally块(和return中的表达式运算,并更新临时栈中的值),从这里返回。
此时finally块的return值,就是代码执行完后的值
5try{} catch(){return;}finally{return;}
执行try中的语句块,
有无异常
有异常:程序执行catch块中return语句(包括return语句中的表达式运算,,并将结果保存到临时栈),但不返回;
无异常:直接执行下面的
再执行finally块,
执行finallyreturn中的表达式运算,并更新临时栈中的值,并从这里返回。
示例1:无异常:
 ​
 public class Test04 {
   public static void main(String[] args) {
     System.out.println(test());
   }
   private static int test() {
     int temp = 1;
     try {
       System.out.println(temp);
     } catch (Exception e) {
       System.out.println(temp);
       return ++temp;
     } finally {
       System.out.println(temp);
       return ++temp;
     }
   }
 }
 ​
输出:112
示例2:有异常
 ​
 public class Test04 {
   public static void main(String[] args) {
     System.out.println(test());
   }
   private static int test() {
     int temp = 1;
     try {
       System.out.println(temp);
       int i=1/0;
     } catch (Exception e) {
       System.out.println(temp);
       return ++temp;
     } finally {
       System.out.println(temp);
       return ++temp;
     }
   }
 }
 ​
输出:1123
6try{ return;}catch(){return;} finally{return;}
程序执行try块中return语句(包括return语句中的表达式运算),但不返回;
有异常:
执行catch块中的语句和rreturn语句中的表达式运算,但不返回,结果保存在临时栈;
再执行finally块
执行finally块,有return,更新临时栈的值,并从这里返回。
无异常:
直接执行finally块
执行finally块,有return,更新临时栈的值,并从这里返回。
示例1:无异常:
 package com.jc;public class Test04 {
   public static void main(String[] args) {
     System.out.println(test());
   }
   private static int test() {
     int temp = 1;
     try {
       System.out.println(temp);
 //      int i=1/0;
       return ++temp;
     } catch (Exception e) {
       System.out.println(temp);
       return ++temp;
     } finally {
       System.out.println(temp);
       return ++temp;
     }
   }
 }
 ​
输出:123
示例2:有异常
 package com.jc;public class Test04 {
   public static void main(String[] args) {
     System.out.println(test());
   }
   private static int test() {
     int temp = 1;
     try {
       System.out.println(temp);
       int i=1/0;
       return ++temp;
     } catch (Exception e) {
       System.out.println(temp);
       return ++temp;
     } finally {
       System.out.println(temp);
       return ++temp;
     }
   }
 }
 ​
输出:1123
7try{ return;}catch(){return;} finally{其他}
程序执行try块中return语句(包括return语句中的表达式运算),但不返回;
有异常:
执行catch块中return语句(包括return语句中的表达式运算),但不返回;
再执行finally块
无异常:
再执行finally块
执行finally块,有return,从这里返回。
示例:
public class Test {
    public static void main(String[] args) {
            System.out.println(test());
    }
    private static int test() {
        int temp = 1;
        try {
            System.out.println(temp);
			return ++temp;
			} catch (Exception e) {
			System.out.println(temp);
			return ++temp;
			} finally {
			++temp;
			System.out.println(temp);
			}
		}
}
输出结果为132
执行顺序为:
输出try里面的初始temp:1;
temp=2;
保存return里面temp的值:2;
执行finally的语句temp:3,输出temp:3;
返回try中的return语句,返回存在里面的temp的值:2;
输出temp:2finally代码块在return中间执行。return的值会被放入临时栈,然后执行finally代码块,如果finally中有return,会刷新临时栈的值,方法结束返回临时栈中的值。
最终结论:
任何执行try 或者catch中的return语句之后,在返回之前,如果finally存在的话,都会先执行finally语句,
如果finally中有return语句,那么程序就return了,所以finally中的return是一定会被return的,
编译器把finally中的return实现为一个warning。
不管有没有异常,finally代码块(包括finallyreturn语句中的表达式运算)都会在return之前执行
多个return(中的表达式运算)是按顺序执行的,多个return执行了一个之后,后面的return就不会执行。不管return是在trycatchfinally还是之外。

3、修饰符总结
一、总述
类、方法、成员变量和局部变量的可用修饰符
 
二、类修饰符
访问修饰符:公共类修饰符public(只能是它)
非访问控制符:抽象类修饰符 abstract 、最终类修饰符 final1)公共类修饰符 public : Java 语言中类的访问控制符只有 public 即公共的。每个 Java 程序的有且只有一个类是 public,它被称为主类 ,其他外部类无访问控制修饰符,具有包访问性。注意:一个类的内部类可以被其他访问控制修饰符protecteddefaultprivate修饰,相当于类的成员。
(2)抽象类修饰符 abstract :用 abstract 修饰符修饰的类,被称为抽象类。
(3)最终类修饰符 final :当一个类不能被继承时可用修饰符 final修饰为最终类。被定义为 final 的类通常是一些有固定作用、用来完成某种标准功能的类。final关键字不能用来抽象类和接口
(4)类缺省访问控制符:如果一个类没有访问控制符,说明它具有缺省的访问控制符特性。此时,这个类只能被同一个包中的类访问或引用。这一访问特性又称为包访问性。

1、如果一个类包含抽象方法(用abstract修饰的方法),那么这个类必须是抽象类
2、继承抽象类的子类必须重写父类所有的抽象方法(用abstract修饰的方法)。否则,该子类也必须声明为抽象类(也必须用abstract修饰)。最终,必须有子类实现该父类的抽象方法,否则,从最初的父类到最终的子类都不能创建对象,失去意义。

接口通常是不带修饰符的,一般都是public interface

以上是对于外部类,外部类只能用public、adstract、final修饰,如果是内部类类则可以用 修饰成员变量的修饰符修饰内部类,比如 private, static, protected 修饰。
三、方法修饰符
1、按修饰符分
访问控制修饰符:公共访问控制符public、保护访问控制符protected、缺省默认default、私有访问控制符private
非访问控制符:抽象方法控制符abstract 、静态方法控制符static 、最终方法控制符final 、本地方法控制符native 							、同步方法控制符synchronized1)抽象方法控制符 abstract :用修饰符 abstract 修饰的方法称为抽象方法。抽象方法仅有方法头,没有方法体和操作实现。
(2)静态方法控制符 static :用修饰符 static 修饰的方法称为静态方法。静态方法是属于整个类的类方法;而不使用static 修饰、限定的方法是属于某个具体类对象的方法。 由于 static方法是属于整个类的,所以它不能操纵和处理属于某个对象的成员变量,而只能处理属于整个类的成员变量,即 static 方法只能处理 static的域。
(3)最终方法控制符 final :用修饰符 final修饰的方法称为最终方法。最终方法是功能和内部语句不能更改的方法,即最终方法不能重写覆盖(可以被继承)。final固定了方法所具有的功能和操作,防止当前类的子类对父类关键方法的错误定义,保证了程序的安全性和正确性。所有被 private 修饰符限定为私有的方法,以及所有包含在 final( 最终类) 中的方法,都被认为是最终方法。
(4)本地方法控制符 native :用修饰符 native 修饰的方法称为本地方法。为了提高程序的运行速度,需要用其它的高级语言书写程序的方法体,那么该方法可定义为本地方法用修饰符 native 来修饰。
(5)同步方法控制符 synchronized :该修饰符主要用于多线程程序中的协调和同步。
2、类的方法
类成员的访问控制符:即类的方法和成员变量的访问控制符
一个类作为整体对象不可见,并不代表他的所有域和方法也对程序其他部分不可见,需要有他们的访问修饰符判断。权限如下: 
要注意这两者的区别:
default:只要是外部包,就不允许访问,不管是不是子类
protected:只要是子类就允许访问,即使子类位于外部包,即外部包不能访问,但是,子类在外部包的话,外部包的子类就可以访问

(1)构造方法
构造方法只能用public(所有的类访问)、protected(只能自己和子类访问)、private(只能在本类访问),而不能是abstract, static, final, native, strictfp, 或者synchronized的。原因如下:
构造器不是通过继承得到的,所以没有必要把它声明为final的。
同理,一个抽象的构造器将永远不会被实现。(所以也不能声明为abstract的)
构造器总是关联一个对象而被调用,所以把它声明为static是没有意义的。
没有实际的需要把构造器定义成同步的,因为它将会在构造的时候锁住该对象,直到所有的构造器完成它们的工作,这个构造的过程对其它线程来说,通常是不可访问的。 (synchronized)
本地化的方法情况特别复杂,所以JVM调用起来非常麻烦,需要考虑很多种情况,没有native关键字的情况下,JVM实现起来比较容易。
(2)类方法(静态方法)
类方法:使用static关键字说明的方法(与实例方法区分:是否用static修饰)
1.系统只为该类创建一个版本,这个版本被该类和该类的所有实例共享。
2.类方法只能操作类变量,不能访问实例变量。类方法可以在类中被调用,不必创建实例来调用,当然也可以通过对象来调用。
3.静态方法可以直接访问类变量(静态成员变量)和静态方法。
解释:因为静态方法在加载类的时候就被调用了
4.静态方法不能直接访问普通成员变量或成员方法。反之,成员方法可以直接访问类变量(静态成员变量)或静态方法。 
普通成员变量或成员方法只有在创建对象的时候才会被创建,而静态方法在加载类的时候就被加载了,那时候还没有对象,更不必谈在静态方法中访问其中的普通成员变量和成员方法了
5.静态方法中,不能使用this关键字。
(3)成员方法
public(公共控制符)
private(私有控制符)指定此方法只能有自己类等方法访问,其他的类不能访问(包括子类)
protected(保护访问控制符)指定该方法可以被它的类和子类进行访问。
final,指定该方法不能被重载。
static,指定不需要实例化就可以激活的一个方法。
synchronize,同步修饰符,在多个线程中,该修饰符用于在运行前,对他所属的方法加锁,以防止其他线程的访问,运行结束后解锁。
native,本地修饰符。指定此方法的方法体是用其他语言在程序外部编写的。
(4)抽象类的抽象方法
抽象类中的抽象方法(其前有abstract修饰)不能用privatestaticsynchronizednative访问修饰符修饰。原因如下:
抽象方法没有方法体,是用来被继承的,所以不能用private修饰;
static修饰的方法可以通过类名来访问该方法(即该方法的方法体),抽象方法用static修饰没有意义;
使用synchronized关键字是为该方法加一个锁。而如果该关键字修饰的方法是static方法。则使用的锁就是class变量的锁。如果是修饰类方法。则用this变量锁。但是抽象类不能实例化对象,因为该方法不是在该抽象类中实现的,是在其子类实现的。所以,锁应该归其子类所有。所以,抽象方法也就不能用synchronized关键字修饰了;
native,这个东西本身就和abstract冲突,他们都是方法的声明,只是一个把方法实现移交给子类,另一个是移交给本地操作系统。如果同时出现,就相当于即把实现移交给子类,又把实现移交给本地操作系统,那到底谁来实现具体方法呢?
3、接口的方法
接口是一种特殊的抽象类,接口中的方法全部是抽象方法(但其前的abstract可以省略),所以抽象类中的抽象方法不能用的访问修饰符(privatestaticsynchronizednative)这里也不能用。而且protected访问修饰符也不能使用,因为接口可以让所有的类去实现(非继承),不只是其子类(比如如果实现类和接口不在同一个包内就会出现问题)。所以要用public去修饰,接口才可以被实现。
默认写法:public abstract(默认不写)
(1)静态方法(static)
用static修饰的方法就是静态方法,静态方法必须要有实现体(一定要有花括号),并且静态方法是不能被实现类实现的,只能通过接口调用这个方法
静态方法必须要有方法体,即一定要有花括号,是因为静态方法不能被实现类实现,但是接口的方法又必须全部被实现,所以矛盾,所以静态方法必须要有实现
格式:public static 返回数据类型 方法名(参数列表){}
省略了abstract2)默认方法(defaultdefault的加入就是为了解决接口中不能有默认方法的问题,在实现类中可以重写这个default方法也可以不重写。
default修饰的方法跟接口中的静态方法的区别就是default方法可以被实现类重写,这样就可以得到扩展并且不修改原来接口中功能,而静态方法就有点太苛刻了,还不如把静态方法写在实现类中,这样每个实现类都可以自己写自己的功能实现。
格式:default 返回数据类型 方法名(){}3)其他抽象方法
四、变量修饰符
1、类的成员变量修饰符
一个类的成员变量的声明必须在类体中,而不能在方法中,方法中声明的是局部变量。
public(公共访问控制符),指定该变量为公共的,他可以被任何对象的方法访问。
private(私有访问控制符)指定该变量只允许自己的类的方法访问,其他任何类(包括子类)中的方法均不能访问。
protected(保护访问控制符)指定该变量可以别被自己的类和子类访问。在子类中可以覆盖此变量。
friendly ,在同一个包中的类可以访问,其他包中的类不能访问。
final,最终修饰符,指定此变量的值不能变。
static(静态修饰符)指定变量被所有对象共享,即所有实例都可以使用该变量。变量属于这个类。
transient(过度修饰符)指定该变量是系统保留,暂无特别作用的临时性变量。
volatile(易失修饰符)指定该变量可以同时被几个线程控制和修改。
抽象类中变量的修饰符与一般类一致

2、接口中的变量
接口中的属性默认是public static final 的,(因为是final的,所以都是常量),只能读不能改
public static final 可以省略不写
即
1public static final String name = “张三”;
2:String name = “张三”;
以上两种写法都可以
public的话可以理解,需要被实现类使用
如果是非static的话,因一个类可以多继承,容易出现重名导致编译错误
接口的方法都是抽象的,可变的东西都应该归属到实现类中,这样接口才能起到标准化、规范化的作用,因此接口里的属性必须是不变的,即final的,可以不变实现类修改(如果能被实现类任意修改,接口就没有创建这个常量的必要了
3、方法中的局部变量
因为接口中的方法都是抽象方法,而抽象方法没有实现,即没有方法体。所以,只有一般类中的方法和抽象类中的非抽象方法才会有局部变量。
方法中对于变量的修饰符只有两种:
1、缺省(default):即什么都不写,这是一个普通的变量,必须为其设置初始值。
2final:表示变量值不可以被改变,即常量
一般类和抽象类中的静态方法中的变量自动就是静态的,不需要加static
五、常考修饰符
1final1)修饰类
该类不能被继承
final不能修饰抽象类和接口
final类中的方法不会被覆盖,因此默认都是final的
用途:设计类时,如果该类不需要有子类,不必要被扩展,类的实现细节不允许被改变,那么就设计成final类
(2)修饰方法
该方法可以被继承,但是不能被覆盖
用途:一个类不允许子类覆盖该方法,则用final来修饰 
好处:可以防止继承它的子类修改该方法的意义和实现;更为高效,编译器在遇到调用fianal方法转入内嵌机制,提高了执行效率
父类中的private成员方法不能被子类覆盖,因此,父类的private方法默认是final型的(可以查看编译后的class文件)3)修饰变量
用final修饰后变为常量。包括静态变量、实例变量和局部变量这三种
特点:只能被赋一次值,必须被显示初始化。可以先声明,不给初值,这种叫做final空白。但是使用前必须被初始化。一旦被赋值,将不能再被改变。
(4)修饰参数
用final修饰参数时,可以读取该参数,但是不能对其作出修改
2static
1、简介
关于 static 关键字的使用,它可以用来修饰的成员变量和成员方法,被修饰的成员是属于类的,而不是单单是属 于某个对象的。也就是说,既然属于类,就可以不靠创建对象来调用了。
2、定义和使用
1)类变量当 static 修饰成员变量时,该变量称为类变量。
该类的每个对象都共享同一个类变量的值。任何对象都可以更改该类变量的值,但也可以在不创建该类的对象的情况下对类变量进行作。
static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。  static成员变量的初始化顺序按照定义的顺序进行初始化。
定义格式:static 数据类型 变量名;举例:static int numberID;
 2)静态方法 
当 static 修饰成员方法时,该方法称为类方法 。
静态方法在声明中有 static ,建议使用类名来调用,而不需要创建类的对象。调用方式非常简单。 类方法:使用 static关键字修饰的成员方法,习惯称为静态方法。 定义格式: 修饰符 static 返回值类型 方法名 (参数列表){   // 执行语句}
调用格式:
被static修饰的成员可以并且建议通过类名直接访问。虽然也可以通过对象名访问静态成员,原因即多个对象均属 于一个类,共享使用同一个静态成员,但是不建议,会出现警告信息。
3)静态代码块
定义在成员位置,使用static修饰的代码块{ }。 位置:类中方法外。执行:随着类的加载而执行且执行一次,优先于main方法和构造方法的执行。
3、注意事项: 
静态方法可以直接访问类变量和静态方法。
静态方法不能直接访问普通成员变量或成员方法。
反之,成员方法可以直接访问类变量或静态方法。 
静态方法中,不能使用thissuper关键字。
静态方法不能被abstract修饰
静态方法和静态变量都可以通过类名直接被访问。
当类被加载时,静态代码块只被执行一次。类中不同的静态代码块按它们在类中出现的顺序被依次执行
static 关键字,可以修饰变量、方法和代码块。在使用的过程中,其主要目的还是想在不创建对象的情况 


4、数据类型转换
(1)数据类型
Java的数据类型分为两大类:
基本数据类型:包括 整数、浮点数、字符、布尔。
Java中的四类八种基本数据类型
第一类:整数类型  byte short int long
第二类:浮点型  float double
第三类:逻辑型    boolean(它只有两个值可取true false)
第四类:字符型  char

引用数据类型:包括 类、数组、接口。
(2)自动转换
将取值范围小的类型自动提升为取值范围大的类型
public static void main(String[] args) {
  int i = 1;
  byte b = 2;
  // byte x = b + i; // 报错
  //int类型和byte类型运算,结果是int类型
  int j = b + i;
  System.out.println(j);
}
byte 类型内存占有1个字节,在和int 类型运算时会提升为int 类型 ,自动补充3个字节,因此计算后的结果还是int 类
型。
规则:范围小的类型向范围大的类型提升,
两个数值进行二元操作时,会有如下的转换操作:
如果两个操作数其中有一个是double类型,另一个操作就会转换为double类型。
否则,如果其中一个操作数是float类型,另一个将会转换为float类型。
否则,如果其中一个操作数是long类型,另一个会转换为long类型。
否则,两个操作数都转换为int类型。
 byteshortchar 运算时直接提升为intbyteshortchar‐‐>int‐‐>long‐‐>float‐‐>double3)强制转换
将取值范围大的类型强制转换成取值范围小的类型。
比较而言,自动转换是Java自动执行的,而强制转换需要我们自己手动执行。
格式:数据类型 变量名 = (数据类型)被转数据值;
// double类型数据强制转成int类型,直接去掉小数点。
int i = (int)1.5;
5new String、声明String、String变量常量相加的区别详解
(1new String和声明String区别和实现过程
1)String str1 = "abcd"的实现过程:首先栈区创建str引用,然后在String池(独立于栈和堆而存在,存储不可变量)中寻找其指向的内容为"abcd"的对象,如果String池中没有,则创建一个,然后str指向String池中的对象,如果有,则直接将str1指向"abcd";
如果后来又定义了字符串变量 str2 = "abcd",则直接将str2引用指向String池中已经存在的“abcd”,不再重新创建对象;当str1进行了赋值(str1=“abc”),则str1将不再指向"abcd",而是重新指String池中的"abc",此时如果定义String str3 = "abc",进行str1 == str3操作,返回值为true,因为他们的值一样,地址一样,但是如果内容为"abc"的str1进行了字符串的+连接str1 = str1+"d";此时str1指向的是在堆中新建的内容为"abcd"的对象,即此时进行str1==str2,返回值false,因为地址不一样。
2)String str3 = new String("abcd")的实现过程:先去常量池中查找有没有这个字符串,没有的话在常量池中创建,并在堆中也创建。如果常量池中有这个字符串就不会在常量池中创建,但依然还是会在堆中创建。返回的都是是堆中分配的空间。如果后来又有String str4 = new String("abcd"),str4不会指向之前的对象,而是重新创建一个对象并指向它,所以如果此时进行str3==str4返回值是false,因为两个对象的地址不一样,如果是str3.equals(str4),返回true,因为内容相同。因此,new出来的永远指向堆中的空间,和声明出来的==的结果肯定是false

从编译角度分析两者区别:
String str1 = "abcd":在编译期,JVM会去常量池来查找是否存在“abcd”,如果不存在,就在常量池中开辟一个空间来存储“abcd”;如果存在,就不用新开辟空间。然后在栈内存中开辟一个名字为str1的空间,来存储“abcd”在常量池中的地址值。
String str3 = new String("abcd"):	在编译阶段JVM先去常量池中查找是否存在“abcd”,如果过不存在,则在常量池中开辟一个空间存储“abcd”。在运行时期,通过String类的构造器在堆内存中new了一个空间,然后将String池中的“abcd”复制一份存放到该堆空间中,在栈中开辟名字为str2的空间,存放堆中new出来的这个String对象的地址值。

内存图:
图片: https://uploader.shimo.im/f/p0s7hZLtGNVlKlui.png
说明:
首先,通过main()方法进栈。
然后再栈中定义一个对象s1,去堆中开辟一个内存空间,将内存空间的引用赋值给s1,“hello”是常量,然后去字符串常量池 查看是否有hello字符串对象,没有的话分配一个空间存放hello,并且将其空间地址存入堆中new出来的空间中。
在栈中定义一个对象s2,然后去字符串常量池中查看是否有”hello”字符串对象,有,直接把”hello”的地址赋值给s2.
即s1中存的是堆中分配的空间,堆中分配的空间中存的是字符串常量池中分配空间存放”hello”的空间的地址值。而s2中之间存的是字符串常量池中分配空间存放”hello”的空间的地址值。
由于s1与s2中存放的地址不同,所以输出false。因为,类String重写了equals()方法,它比较的是引用类型的 的值是否相等,所以输出true。即结果为falsetrue。

验证:
  String s1=new String("xyz");
  String s2="xyz";
  Boolean a1 = s1.equals(s2);
  Boolean a2=(s1==s2);
  System.out.println(a1+" "+a2);//true false

  String b1="qwe";
  String b2 = new String("qwe");
  String b3 = b2.intern();
  Boolean c1=b1.equals(b2);
  Boolean c2=(b1==b2);
  Boolean c3=(b1==b3);
  System.out.println(c1+" "+c2+" "+c3);//true false true
c3是true说明:b2(堆中分配的空间)中存放的字符串常量池中分配空间存放“qwe“的空间的地址值(b3)与b1中存放的字符串常量池中分配空间存放”qwe“的地址一致。
即new之前会在常量池中查找是否存在这个字符串,如果存在,就直接把常量池中的地址值存放在新建的堆空间对象中。

inter方法:
 JDK1.6中的intern:   
调用intern方法的时候首先会去常量池中查看是否存在与当前String值相同的值,如果存在的话,则直接返回常量池中这个String值的引用;如果不存在的话,则会将原先堆中的该字符串拷贝一份到常量池中。  
JDK1.7中的intern:   
调用intern方法的时候首先会去常量池中查看是否存在与当前String值相同的值,如果存在的话,则直接返回常量池中这个String值的引用;如果不存在的话,则只会将原先堆中该字符串的引用放置在常量池中,并不会将拷贝整个字符串到常量池中。   
这也就说明,JDK1.6和JDK1.7对于常量池中不存在此字符串的情况处理不同。

(2)拼接、相加问题:
String s1 = "hello";
String s2 = "world";
String s3 = "helloworld";
System.out.println(s3 == s1 + s2);// false
System.out.println(s3.equals((s1 + s2)));// true
System.out.println(s3 == "hello" + "world");//true
System.out.println(s3.equals("hello" + "world"));// true
System.out.println(s3 == s1 + "world");//false
代码详解:
equals()比较方法不解释,比较值,均相等,均为true。
s1与s2相加是先在字符串常量池中开一个空间,然后拼接,这个空间的地址就是s1与s2拼接后的地址。与s3的地址不同,所以输出为false。
s3与”hello”+”world”作比较,”hello”+”world”先拼接成“helloworld”,然后再去字符串常量池中找是否有”helloworld”,有,所以和s3共用一个字符串对象,则为true。
如果拼接的一个是变量一个是常量,依然会在常量池中开一个新的空间,然后拼接,即只要拼接中有变量都会开辟一个新的空间
(3)总结
String s = new String(“hello”)会创建21)个对象,String s = “hello”创建10)个对象。注:当字符串常量池中有对象hello时括号内成立!
字符串如果是变量相加(只要有变量),先开空间,再拼接。
字符串如果是常量相加,是先加,然后在常量池找,如果有就直接返回,否则,就创建。
6、计算器算法
先将中缀表达式转化为后缀表达式,再利用后缀表达式计算出结果
但在中缀表达式转化为后缀表达式的时候有数组越界的问题,尚未解决
package com.jc;

import java.util.Stack;

public class test03 {
  public static void main(String[] args) {
    String s="9+(3-1)*3+10/2";
    System.out.println("将中缀表达式预处理:");
    String s1 = dealInExp(s);
    System.out.println(s1);
    System.out.println("将中缀表达式转化为后缀表达式:");
    String s2 = inExpToSuffixExp(s1);
    System.out.println(s2);
    System.out.println("利用后缀表达式求出最终结果:");
    int ans = calculate(s2);
    System.out.println(ans);

  }

  //将中缀表达式改为后缀表达式。这里的栈是用来存储运算符,依次来每次作比较

  /**
   * 1、若是数字就直接输出,即成为后缀表达式的一部分
   * 2、若是符号,则判断其与栈顶符号的优先级,
   * 2.1、如果是右括号或优先级不高于栈顶符号(乘除优先加减)则栈顶元素依次出栈并输出
   * 2.2、如果是左括号或优先级高于栈顶符号则进栈
   * @param resExp
   * @return ans
   */
  public static String inExpToSuffixExp(String resExp){
    String[] inExp = dealInExp(resExp).split(",");
    Stack<String> stack = new Stack<>();//这个栈用来存放符号,以此来每次和栈顶符号比较优先级
    StringBuilder suffixExp = new StringBuilder();
    for (String s : inExp) {
      if (!isOperator(s)) {
        // 遇到数字,直接输出
        suffixExp.append(s + ",");
      } else if ("(".equals(s)) {
        // 遇到左括号,直接压栈
        stack.push(s);
      } else if (")".equals(s)) {
        // 遇到右括号,将栈顶元素依次弹出,直至遇到左括号
        while (!"(".equals(stack.get(stack.size() - 1))) {
          suffixExp.append(stack.pop() + ",");
        }
        // 弹出左括号但不输出
        stack.pop();
      } else if (isOperator(s)) {
        // 遇到运算符,当前运算符优先级小于等于栈顶运算符优先级,将栈顶运算符弹出并输出
        // 当前运算符继续和新的栈顶运算符比较...
        while (!stack.isEmpty()
          &&getPriority(s) <= getPriority(stack.get(stack.size()-1))){
          suffixExp.append(stack.pop() + ",");
        }
        stack.push(s);
      }
    }
    // 最后,将栈内还没有弹出的运算符依次弹出,并输出
    while (!stack.isEmpty()) {
      suffixExp.append(stack.pop() + ",");
    }
    // 去除最后一个,号
    suffixExp.delete(suffixExp.length() - 1, suffixExp.length());
    return suffixExp.toString();
  }

  /**预处理中缀表达式
   *
   * @param resExp 原始中缀表达式
   * @return 返回处理好后的中缀表达式
   */
  public static String dealInExp(String resExp){
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < resExp.length(); i++) {
      String tmp = resExp.substring(i, i + 1);//取第i个字符
      if (!isOperator(tmp)) {//如果不是运算符
        sb.append(tmp);//入栈
      } else {//如果是运算符,添加,
        if (sb.length() == 0) {
          sb.append(tmp + ",");
        } else if (sb.charAt(sb.length() - 1) == ',') {
          sb.append(tmp + ",");
        } else {
          sb.append("," + tmp + ",");
        }
      }
    }
    return sb.toString();
  }

  /**
   * 判断是不是运算符
   * @param str 待判断的字符
   * @return 返回判断结果
   */
  public static boolean isOperator(String str) {
    return "+-*/()".contains(str);
  }

  /**
   * 获取运算符的优先级
   * @param operator 运算符
   * @return 返回运算符的优先级
   */
  public static int getPriority(String operator) {
    return "()+-*/".indexOf(operator)/2;
  }




  /**利用后缀表达式进行运算,求出结果
   * 1、遇到是数字就进栈
   * 2、遇到是符号就将处于 栈顶的两个数字出栈,进行运算,运算结果入栈
   * 3、一直到最终获得结果
   * @param s
   * @return
   */
  public static int calculate(String s){
    String[] ops = dealInExp(s).split(",");
    Stack<Integer> stack=new Stack();
    for (String op : ops) {
      if(!isOperator(op)){
        //遇到是数字就进栈
        stack.push(Integer.valueOf(op));
      }else{
        //遇到是符号就将处于 栈顶的两个数字出栈,进行运算,运算结果入栈
        int top1=stack.pop();//弹出栈顶值,并返回该值
        int top2=stack.pop();//弹出栈顶值,并返回该值
        stack.push(top1+top2);//将运算结果压入栈

      }
    }
    return stack.pop();//将结果从栈中弹出
  }
}


(3)、解决问题的时候到了

看完上边这些内容之后,我们再来看这两行代码:

String str1=new String("hello");
String str3="hello";
 
//1.结果为true   
System.out.println("equals: "+str1.equals(str3));
//2.结果为false  
System.out.println("str==str1  "+(str1==str3));


​ 现在,我们就可以很清楚的知道为什么1 返回true:

String.equal() 只看两者是否为String,长度是否相等,以及每个字符的值是否相等,很显然str1和str2满足这三点要求,所以返回结果为真。

至于2 返回false,我们也明白为何了:

c static int calculate(String s){
String[] ops = dealInExp(s).split(",");
Stack stack=new Stack();
for (String op : ops) {
if(!isOperator(op)){
//遇到是数字就进栈
stack.push(Integer.valueOf(op));
}else{
//遇到是符号就将处于 栈顶的两个数字出栈,进行运算,运算结果入栈
int top1=stack.pop();//弹出栈顶值,并返回该值
int top2=stack.pop();//弹出栈顶值,并返回该值
stack.push(top1+top2);//将运算结果压入栈

  }
}
return stack.pop();//将结果从栈中弹出

}
}


## (3)、解决问题的时候到了	

看完上边这些内容之后,我们再来看这两行代码:

```java
String str1=new String("hello");
String str3="hello";
 
//1.结果为true   
System.out.println("equals: "+str1.equals(str3));
//2.结果为false  
System.out.println("str==str1  "+(str1==str3));


​ 现在,我们就可以很清楚的知道为什么1 返回true:

String.equal() 只看两者是否为String,长度是否相等,以及每个字符的值是否相等,很显然str1和str2满足这三点要求,所以返回结果为真。

至于2 返回false,我们也明白为何了:

== 是对对象地址的判断,而这两种声明方式的存储形式是不同的(前者是在堆区创建对象,后者是在常量池),因此它们的地址不同,自然返回false了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值