中医认为,海参味甘、微咸,性温,能补肾益精、养血润燥、补虚损、理腰脚,利大小便。据记载,海参具有健阳、滋阴、补血、调经、养胎、利产、促孕等效用,以之治肾虚阳痿、产后或病后体弱、肠燥便秘、糖尿病等,都有一定的辅助疗效。
海参含蛋白质极为丰富,含量高达86.5%。除精氨酸含量高达12% 外,还含有人们所必需的多种氨基酸、钙、铁、磷等物质,其营养价值比鸡肉还略高一筹。海参的肉质细嫩,富有弹性,吃有嚼劲。由其烹调而成的佳肴,如红烧海参、鸡蓉海参、家常海参、奶油海参、葱烧海参等都闻名遐迩。
近年来,我们在不孕不育专科门诊中,要求患者结合中药适当进食海参,每星期三四次,炖、煎、炒、烧、煮均随个人喜爱,对某些精液异常者(如精少、精稀、活力低、畸形精多)多有一定疗效。精液异常或有性功能障碍者,平时不妨多吃海参。
?
==、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
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
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了。
首先去mysql九游会ag官方网站官网下载压缩包
依次找到 downloads -> community -> mysql community downloads -> mysql community server 。
根据开发者系统的位数选择 32 位或者 64 位的 zip 包下载。
如果你觉得麻烦,我这里也有64 位的 zip 包网盘下载地址
链接:https://pan.baidu.com/s/1h-osgu99oyhjsweiacraga
提取码:dnzr
安装步骤
第 1 步:将压缩包解压,复制到指定位置
说明:这里说的指定位置一般是开发者计算机上固定存放软件的路径。注意:这个路径不要带中文、特殊字符和空格。
例如,我把解压以后的文件和文件夹存放在了
d:\software\mysql-5.6.39-winx64
第 2 步:以管理员方式运行cmd程序,并切换到mysql解压目录的bin目录下
比如我的就是:d:\software\mysql-5.6.39-winx64\bin
注意,windows下切换c盘切换到d盘的命令是 d: ,注意有英文冒号。
然后执行命令:?mysqld -install
如果看到success,就说明安装成功了。
接下来就是启动mysql服务,也很简单,
执行命令: net start mysql
稍等几秒,就可以看到启动成功的提示,默认账号 root ,密码为空!
还没有评论,来说两句吧...