==、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中的基本数据类型,用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对象的内容一样,但他们却占着两块地。
图片: 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了。
2、try、catch、finally、return执行顺序
有关try、catch、finally和return执行顺序的题目在面试题中可谓是频频出现。总结一下此类问题几种情况。
写在前面
不管try中是否出现异常,finally块中的代码都会执行;
当try和catch中有return时,finally依然会执行;
finally是在return语句执行之后,返回之前执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,不管finally中的代码怎么样,返回的值都不会改变,仍然是之前保存的值),
finally中如果包含return,那么程序将在这里返回,而不是try或catch中的return返回,返回值就不是try或catch中保存的返回值了。
注:
finally修改的基本类型是不影响 返回结果的。(传值)
修改list,map,自定义类等引用类型时,是影响返回结果的。(传址的)对象也是传址的
但是date类型经过测试是不影响的。
1、try{} catch(){}finally{} return;
按程序顺序运行,如果try中有异常,会执行catch中的代码块,有异常与否都会执行finally中的代码;最终返回。
2、try{ return; }catch(){} finally{} return;
先执行try块中return 语句(包括return语句中的表达式运算),但不返回;
执行finally语句中全部代码
最后执行try中return 返回
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
至于为什么不是:和是 finally块43的原因:
System.out.println("和是:"+test.add(9,34)); 这是进行字符串拼接是一个整体,所以首先是进入add方法,进去之后先不运算result,而是输出finally块。finally语句块,这句话先打印到控制台中。打印完后返回来执行try中的return得到43,此时再将结果与和是:拼接,输出和是:43.所以最终输出finally语句块 和是:43。
3、try{} catch(){return;} finally{} return;
程序先执行try,如果遇到异常执行catch块,最终都会执行finally中的代码块;
有异常:
执行catch中的语句和return中的表达式运算,但不返回
执行finally语句中全部代码,
最后执行catch块中return返回。 finally块后的return语句不再执行。
无异常:执行完try再finally再return…
示例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语句,所以不会去临时栈中的值,此时临时栈中的值仍然是2。finally中的执行完后,执行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
4、try{ return; }catch(){} finally{return;}
执行try块中的代码,和return语句(包括return语句中的表达式运算),但不返回(try中return的表达式运算的结果放在临时栈);
再执行finally块,
执行finally块(和return中的表达式运算,并更新临时栈中的值),从这里返回。
此时finally块的return值,就是代码执行完后的值
5、try{} catch(){return;}finally{return;}
执行try中的语句块,
有无异常
有异常:程序执行catch块中return语句(包括return语句中的表达式运算,,并将结果保存到临时栈),但不返回;
无异常:直接执行下面的
再执行finally块,
执行finally块return中的表达式运算,并更新临时栈中的值,并从这里返回。
示例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
6、try{ 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
7、try{ 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:2。
finally代码块在return中间执行。return的值会被放入临时栈,然后执行finally代码块,如果finally中有return,会刷新临时栈的值,方法结束返回临时栈中的值。
最终结论:
任何执行try 或者catch中的return语句之后,在返回之前,如果finally存在的话,都会先执行finally语句,
如果finally中有return语句,那么程序就return了,所以finally中的return是一定会被return的,
编译器把finally中的return实现为一个warning。
不管有没有异常,finally代码块(包括finally中return语句中的表达式运算)都会在return之前执行
多个return(中的表达式运算)是按顺序执行的,多个return执行了一个之后,后面的return就不会执行。不管return是在try、catch、finally还是之外。
3、修饰符总结
一、总述
类、方法、成员变量和局部变量的可用修饰符
二、类修饰符
访问修饰符:公共类修饰符public(只能是它)
非访问控制符:抽象类修饰符 abstract 、最终类修饰符 final
(1)公共类修饰符 public : Java 语言中类的访问控制符只有 public 即公共的。每个 Java 程序的有且只有一个类是 public,它被称为主类 ,其他外部类无访问控制修饰符,具有包访问性。注意:一个类的内部类可以被其他访问控制修饰符protected、default、 private修饰,相当于类的成员。
(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 、同步方法控制符synchronized
(1)抽象方法控制符 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修饰)不能用private、static、synchronized、native访问修饰符修饰。原因如下:
抽象方法没有方法体,是用来被继承的,所以不能用private修饰;
static修饰的方法可以通过类名来访问该方法(即该方法的方法体),抽象方法用static修饰没有意义;
使用synchronized关键字是为该方法加一个锁。而如果该关键字修饰的方法是static方法。则使用的锁就是class变量的锁。如果是修饰类方法。则用this变量锁。但是抽象类不能实例化对象,因为该方法不是在该抽象类中实现的,是在其子类实现的。所以,锁应该归其子类所有。所以,抽象方法也就不能用synchronized关键字修饰了;
native,这个东西本身就和abstract冲突,他们都是方法的声明,只是一个把方法实现移交给子类,另一个是移交给本地操作系统。如果同时出现,就相当于即把实现移交给子类,又把实现移交给本地操作系统,那到底谁来实现具体方法呢?
3、接口的方法
接口是一种特殊的抽象类,接口中的方法全部是抽象方法(但其前的abstract可以省略),所以抽象类中的抽象方法不能用的访问修饰符(private、static、synchronized、native)这里也不能用。而且protected访问修饰符也不能使用,因为接口可以让所有的类去实现(非继承),不只是其子类(比如如果实现类和接口不在同一个包内就会出现问题)。所以要用public去修饰,接口才可以被实现。
默认写法:public abstract(默认不写)
(1)静态方法(static)
用static修饰的方法就是静态方法,静态方法必须要有实现体(一定要有花括号),并且静态方法是不能被实现类实现的,只能通过接口调用这个方法
静态方法必须要有方法体,即一定要有花括号,是因为静态方法不能被实现类实现,但是接口的方法又必须全部被实现,所以矛盾,所以静态方法必须要有实现
格式:public static 返回数据类型 方法名(参数列表){}
省略了abstract
(2)默认方法(default)
default的加入就是为了解决接口中不能有默认方法的问题,在实现类中可以重写这个default方法也可以不重写。
default修饰的方法跟接口中的静态方法的区别就是default方法可以被实现类重写,这样就可以得到扩展并且不修改原来接口中功能,而静态方法就有点太苛刻了,还不如把静态方法写在实现类中,这样每个实现类都可以自己写自己的功能实现。
格式:default 返回数据类型 方法名(){}
(3)其他抽象方法
四、变量修饰符
1、类的成员变量修饰符
一个类的成员变量的声明必须在类体中,而不能在方法中,方法中声明的是局部变量。
public(公共访问控制符),指定该变量为公共的,他可以被任何对象的方法访问。
private(私有访问控制符)指定该变量只允许自己的类的方法访问,其他任何类(包括子类)中的方法均不能访问。
protected(保护访问控制符)指定该变量可以别被自己的类和子类访问。在子类中可以覆盖此变量。
friendly ,在同一个包中的类可以访问,其他包中的类不能访问。
final,最终修饰符,指定此变量的值不能变。
static(静态修饰符)指定变量被所有对象共享,即所有实例都可以使用该变量。变量属于这个类。
transient(过度修饰符)指定该变量是系统保留,暂无特别作用的临时性变量。
volatile(易失修饰符)指定该变量可以同时被几个线程控制和修改。
抽象类中变量的修饰符与一般类一致
2、接口中的变量
接口中的属性默认是public static final 的,(因为是final的,所以都是常量),只能读不能改
public static final 可以省略不写
即
1:public static final String name = “张三”;
2:String name = “张三”;
以上两种写法都可以
public的话可以理解,需要被实现类使用
如果是非static的话,因一个类可以多继承,容易出现重名导致编译错误
接口的方法都是抽象的,可变的东西都应该归属到实现类中,这样接口才能起到标准化、规范化的作用,因此接口里的属性必须是不变的,即final的,可以不变实现类修改(如果能被实现类任意修改,接口就没有创建这个常量的必要了
3、方法中的局部变量
因为接口中的方法都是抽象方法,而抽象方法没有实现,即没有方法体。所以,只有一般类中的方法和抽象类中的非抽象方法才会有局部变量。
方法中对于变量的修饰符只有两种:
1、缺省(default):即什么都不写,这是一个普通的变量,必须为其设置初始值。
2、final:表示变量值不可以被改变,即常量
一般类和抽象类中的静态方法中的变量自动就是静态的,不需要加static
五、常考修饰符
1、final
(1)修饰类
该类不能被继承
final不能修饰抽象类和接口
final类中的方法不会被覆盖,因此默认都是final的
用途:设计类时,如果该类不需要有子类,不必要被扩展,类的实现细节不允许被改变,那么就设计成final类
(2)修饰方法
该方法可以被继承,但是不能被覆盖
用途:一个类不允许子类覆盖该方法,则用final来修饰
好处:可以防止继承它的子类修改该方法的意义和实现;更为高效,编译器在遇到调用fianal方法转入内嵌机制,提高了执行效率
父类中的private成员方法不能被子类覆盖,因此,父类的private方法默认是final型的(可以查看编译后的class文件)
(3)修饰变量
用final修饰后变为常量。包括静态变量、实例变量和局部变量这三种
特点:只能被赋一次值,必须被显示初始化。可以先声明,不给初值,这种叫做final空白。但是使用前必须被初始化。一旦被赋值,将不能再被改变。
(4)修饰参数
用final修饰参数时,可以读取该参数,但是不能对其作出修改
2、static
1、简介
关于 static 关键字的使用,它可以用来修饰的成员变量和成员方法,被修饰的成员是属于类的,而不是单单是属 于某个对象的。也就是说,既然属于类,就可以不靠创建对象来调用了。
2、定义和使用
1)类变量当 static 修饰成员变量时,该变量称为类变量。
该类的每个对象都共享同一个类变量的值。任何对象都可以更改该类变量的值,但也可以在不创建该类的对象的情况下对类变量进行作。
static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。 static成员变量的初始化顺序按照定义的顺序进行初始化。
定义格式:static 数据类型 变量名;举例:static int numberID;
2)静态方法
当 static 修饰成员方法时,该方法称为类方法 。
静态方法在声明中有 static ,建议使用类名来调用,而不需要创建类的对象。调用方式非常简单。 类方法:使用 static关键字修饰的成员方法,习惯称为静态方法。 定义格式: 修饰符 static 返回值类型 方法名 (参数列表){ // 执行语句}
调用格式:
被static修饰的成员可以并且建议通过类名直接访问。虽然也可以通过对象名访问静态成员,原因即多个对象均属 于一个类,共享使用同一个静态成员,但是不建议,会出现警告信息。
3)静态代码块
定义在成员位置,使用static修饰的代码块{ }。 位置:类中方法外。执行:随着类的加载而执行且执行一次,优先于main方法和构造方法的执行。
3、注意事项:
静态方法可以直接访问类变量和静态方法。
静态方法不能直接访问普通成员变量或成员方法。
反之,成员方法可以直接访问类变量或静态方法。
静态方法中,不能使用this和super关键字。
静态方法不能被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类型。
byte、short、char 运算时直接提升为int 。
byte、short、char‐‐>int‐‐>long‐‐>float‐‐>double
(3)强制转换
将取值范围大的类型强制转换成取值范围小的类型。
比较而言,自动转换是Java自动执行的,而强制转换需要我们自己手动执行。
格式:数据类型 变量名 = (数据类型)被转数据值;
// double类型数据强制转成int类型,直接去掉小数点。
int i = (int)1.5;
5、new String、声明String、String变量常量相加的区别详解
(1)new 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。即结果为false、true。
验证:
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”)会创建2(1)个对象,String s = “hello”创建1(0)个对象。注:当字符串常量池中有对象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了。