一、基础语法
java语言简单介绍:
Java是一门面向对象编程语言,它吸收了C++语言中抽象化对象与封装继承等优点,摒弃了C++里较为难理解的多继承、指针等概念,因此Java语言不仅功能强大而且简单易用。
Java语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程。
个人来说,java语言相较于C++,确实是比较容易上手,并且在现在这个市场需求下,java的生存环境还是比较好的,各种求职网站给出的薪酬也比较高,是最卷(狗头)但也是最容易找到工作的一种语言。
我将java语言基础分为两部分进行总结,这是上半部分,也是最最基础的一些知识。如果你已经将以上部分学完,可以移步这篇文章,进行下一阶段的学习。
1 java中的标识符
1.1 变量命名规范
① 不可使用java关键字和保留字,但是可以包含关键字和保留字.
② 可以使用26个字母大小写,数字0-9,$和_.
③ 可以使用数字,但不可放在首位.
④ 长度理论上没有限制,但命名最好能反映出其作用,遵循”驼峰形式”,见名知意
⑤ 包名全部小写,类名全部大驼峰式
⑥ 变量名、方法名首字母小写,如果名称由多个单词组成,每个单词的首字母都要大写
⑦ 常量(特指被 final 修饰的常量)名全部大写,如果是单个字母,需要大写,如果是多个单词,需要用下划线分开。并且都大写
总的来说,java中变量的命名规则与其他语言感觉也没有特别大的差异。
变量有两个相关概念,一个是变量的名字,一个是变量的值,即变量在内存中所占的小区域是有名字的,这个小区域里面还装着一个值,根据小区域的名字来访问这个小区域里面的值。不同的变量类型在内存中分配不同大小的存储空间,每一个变量都有自己特定的数据类型,当给变量声明了不同的数据类型它就在内存中占用不同的存储空间。
这些基本学一下操作系统与计算机组成原理的同学都会有一些概念。
1.2 保留字
平常用得到的大概这么多,不懂的可以去看java版本说明文档
2 java中的常量
-
字符常量:凡是用单引号引起来的单个字符,就做字符常量。例如:、‘b’、’9’、‘中‘
-
字符串常量:凡是用双引号引起来的部分,叫做字符串常量。例如:“abc”、“Hello”、“123”
-
整数常量:直接写上的数字,没有小数点。例如:100、200、0、-250
-
浮点数常量:直接写上的数字,有小数点。例如:2.5、-3.14、0.0
-
布尔常量:只有两种取值 true | false
3 java基本数据类型
java中8种基础类型:
-
byte 数据类型是8位、有符号的,以二进制补码表示的整数,默认值为0
表达范围是:-128(-2^7) ~ 127(2^7-1)
取值范围已经定义在包中:Byte.SIZE,Byte.MIN_VALUE,Byte.MAX_VALUE
-
short 数据类型是16位、有符号的以二进制补码表示的整数,默认值为0
表达范围:-32768(-2^15) ~ 32767(2^15 - 1)
取值范围已经定义在包中:Short.SIZE,Short.MIN_VALUE,Short.MAX_VALUE
-
int数据类型是32位、有符号的以二进制补码表示的整数,默认值为0
表达范围:-2,147,483,648(-2^31) ~ 2,147,483,647(2^31 - 1)
取值范围已经定义在包中:Integer.SIZE,Integer.MIN_VALUE,Integer.MAX_VALUE
-
long数据类型是64位、有符号的以二进制补码表示的整数,默认为0L
表达范围:(-2^63) ~ (2^63 -1)
取值范围已经定义在包中:Long.SIZE,Long.MIN_VALUE,Long.MAX_VALUE
-
float 数据类型是单精度、32位、符合IEEE 754标准的浮点数,默认值为0.0f
取值范围已经定义在包中:Float.SIZE,Float.MIN_VALUE,Float.MAX_VALUE
-
double数据类型是双精度、64位、符合IEEE 754标准的浮点数,默认值为0.0d
取值范围已经定义在包中:Double.SIZE,Double.MIN_VALUE,Double.MAX_VALUE
-
boolean数据类型表示一位的信息,只有两个取值:true和false,默认值为false
-
char类型是一个单一的16位Unicode字符
取值范围已经定义在包中:Character.SIZE,Character.MIN_VALUE,Character.MAX_VALUE
总的来说,除byte类型Java独有,布尔类型名称不同,char类型为16位的unicode,其他基本与C++差别不大。
C语言编译好的程序为什么不能够移植,比如把 .exe文件放到Linux下就执行不了了,其中有一个很大的原因就是C语言定义的变量在不同的操作系统下所占的字节大小是不一样的。声明一个int 类型的变量,它在Windows下占32位,而放到Linux下就有可能只占16位,那所表示的数值大小就不一样了,在Windwos下声明一个很大的数它在Linux下就可能会溢出。这也是C语言编译之后不能够移植的一个原因。
4 数组
Java的数组与C++中的数组有明显的区别
int[] arrs1 = new int[5];
int[] arrs2 = {1,2,3,4,5};
int[] arrs3 = new int[]{1,2,3,4,5};
5 字符及字符串
在C++中字符串为string(注意此处的s为小写),而在Java中为String
首先说明的是String与char是完全不同的类型,sizeof(string)时,恒为24,而char时为1
但是,可以通过强行数据转换的方法实现char对String的查看。
string a="123asdasdasd" /*+'\0'*/;
char *c =(char*)&a;
while (c++&&*c) {
cout<<*c;
}
cout<<endl;
输出:
123asdasdasd
由此可见String的存储类型是类似于char[]类型的,他的结尾也为‘\0’。
字符串拼接有concat方法
a = a.concat(“1231231”);
在Java中,字符串有着equals方法进行比较
String a = "hahhaha";
String b = "123";
if(a.equals(b)){
System.out.println("Equal!!");
}
else{
System.out.println("Is not equal!!");
}
6 运算符
基本与c++差不多,有一个instanceOf 运算符,是C++所没有的。
该运算符用于操作对象实例,检查该对象是否是一个特定类型(类类型或接口类型)
使用实例:
例1:
String name = 'James';
boolean result = name instanceOf String; // 由于name是Strine类型,所以返回真
例2:
class Vehicle {}
public class Car extends Vehicle
{
public static void main(String args[])
{
Vehicle a = new Car();
boolean result = a instanceof Car;
System.out.println( result);
}
}
JAVA有个Math类,包含了用于执行基本数学运算的属性和方法,如初等指数、对数、平方根和三角函数。
Math 的方法都被定义为 static 形式,通过 Math 类可以在主函数中直接调用
7 控制语句
C++的控制语句,Java中基本完全兼容
for语句
Java中也有着类似Python/Swift的for循环遍历方法
(不过现在C++11也有auto了,差不多)
int[] arrs = {1,2,3,5};
for (int i : arrs)
System.out.println(i);
//注意此处为输出i而非arrs[i]
8 注释
在程序中,尤其是复杂的程序中,适当地加入注释可以增加程序的可读性,有利于程序的修改、调试和交流。注释的内容在程序编译的时候会被忽视,不会产生目标代码,也就是,注释的部分不会对程序的执行结果产生任何影响。
1.) 单行注释格式。所有从“//”开始到行末的字符都将被忽略
// single line
2.) 段落注释格式。所有在“/* ”和 “*/ ”之间的字符被忽略,这些注释可以扩展到多行。
/* any section */
3.) 文档注释格式。所有在“/** ”和 “ */”之间的字符被忽略,这些注释只能应用在声明语句之前,因为它们将被Java文档生成器用于自动创立文档。
/** any section ,used by javadoc to generate HTML documents */
二、面向对象
面向对象是java最重要的特点,它将跟对象有关的功能都封装在其内,做到“万物皆对象”
虽然很多人根我一样没有对象,但学习java的过程必须要面对对象,所以这也是新手学习道路上的第一道坎。
相信我,只要学好java,什么东西在你眼里,它都是对象!!!
1 面向对象三大特征
- 封装:核心思想就是“隐藏细节”、“数据安全”,将对象不需要让外界访问的成员变量和方法私有化,只提供符合开发者意愿的公有方法来访问这些数据和逻辑,保证了数据的安全和程序的稳定。所有的内容对外部不可见。
- 继承:子类可以继承父类的属性和方法,并对其进行拓展。将其他的功能继承下来继续发展 。
- 多态:同一种类型的对象执行同一个方法时可以表现出不同的行为特征。通过继承的上下转型、接口的回调以及方法的重写和重载可以实现多态。方法的重载本身就是一个多态性的体现。
2 类与对象
类表示一个共性的产物,是一个综合的特征,而对象,是一个个性的产物,是一个个体的特征。 (类似生活中的图纸与实物的概念。)
类必须通过对象才可以使用,对象的所有操作都在类中定义。
对象的定义格式如下: 类名称 对象名称 = new 类名称() ;
如果要想访问类中的属性或方法(方法的定义),则可以依靠以下的语法形式:
访问类中的属性: 对象.属性 ;
调用类中的方法: 对象.方法(实际参数列表) ;
注意点:
- 类必须编写在.java文件中;
- 一个.java文件中,可以存在N个类,但是只能存在一个public修饰的类;
- .java文件的文件名必须与public修饰的类名完全一致;
- 同一个包中不能有重名的类;
2.1 匿名对象
- 没有对象名称的对象就是匿名对象。 即栈内存中没有名字,而堆内存中有对象。
- 匿名对象只能使用一次,因为没有任何的对象引用,所以将称为垃圾,等待被GC回收。
- 只使用一次的对象可以通过匿名对象的方式完成,这一点在以后的开发中将经常使用到。
public static void main(String[] args){
//Math2 m=new Math2();
//int num=m.sum(100,200);
//不通过创建对象名,直接实例对象调用,这就是匿名对象。因为没有对象名指向对象,所以只能调用一次,然后被GC回收。
int num = new Math2().sum(100,200);
System.out.println(num);
}
class Math2{
int sum(int x,int y){
return x+y;
}
}
内存分析图如下:
2.2 内部类
在Java中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。
广泛意义上的内部类一般来说包括这四种:
-
成员内部类
-
局部内部类
-
匿名内部类
-
静态内部类
2.2.1 成员内部类
成员内部类是最普通的内部类,它的定义为位于另一个类的内部,形如下面的形式
public class Demo{
public static void main(String[] args){
//外部使用成员内部类
Outer outter = new Outer(100);
Outer.Inner inner = outter.new Inner();
inner.say(); //输出:200
// 100
}
}
class Outer {
private double x = 0;
public Outer(double x) {
this.x = x;
}
class Inner {
private double x=200;
//内部类
public void say() {
System.out.println(x);
System.out.println(Outer.this.x);
}
}
}
特点: 成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。 不过要注意的是,当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。
如果要访问外部类的同名成员,需要以下面的形式进行访问:
外部类.this.成员变量
外部类.this.成员方法
2.2.2 局部内部类
局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。
例如:
interface Person{
public void say();
}
public class Demo{
public static void main(String[] args){
//局部内部类
class PersonImp implements Person{
@Override
public void say(){
System.out.prinln("新编写的局部内部类的say方法内容");
}
}
PersonImp p=new PersonImp();
//这里想调用haha()方法,但是需要一个Person类,为此专门创建一个class文件类很浪费时间,所以使用局部内部类
haha(p);
}
public static void haha(Person p){ }
}
//窗口关闭
public static void main(String[] args){
Frame f=new Frame("QQ登陆器");
f.setVisible(true);
f.setSize(300,200);
class MyWindowListener implements WindowListener{
@Override
public void windowClosing(WindowEvent e){
System.out.println("哈哈哈");
}
}
MyWindowListener l=new MyWindowListener();
//想要添加一个窗口关闭的事件,可以使用局部类
f.addWindowListener(l);
}
注意:局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的。局部内部类也是只能访问final类型变量。
2.2.3 匿名内部类
匿名内部类由于没有名字,所以它的创建方式有点儿奇怪。匿名内部类创建出来只能使用一次,和匿名对象类似。创建格式如下:
new 父类构造器(参数列表)|实现接口() {
//匿名内部类的类体部分
}
interface Person{
public void say();
}
public class Demo{
public static void main(String[] args){
//匿名内部类
Person p=new Person(){
public void say(){
System.out.println("锄禾日当午");
}
}
haha(p);
}
public static void haha(Person p){ }
}
在这里我们看到使用匿名内部类我们必须要继承一个父类或者实现一个接口,当然也仅能只继承一个父类或者实现一个接口。同时它也是没有class关键字,这是因为匿名内部类是直接使用new来生成一个对象的引用。当然这个引用是隐式的。
在使用匿名内部类的过程中,我们需要注意如下几点:
1、使用匿名内部类时,我们必须是继承一个类或者实现一个接口,但是两者不可兼得,同时也只能继承一个类或者实现一个接口。
2、匿名内部类中是不能定义构造函数的。
3、匿名内部类中不能存在任何的静态成员变量和静态方法。
4、匿名内部类为局部内部类,所以局部内部类的所有限制同样对匿名内部类生效。
5、匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
6、只能访问final型的局部变量。JDK1.8之后变量默认为final类型,但是只要第二次赋值,就不再是final类型的了。
只能访问final类型的局部变量的原因,因为局部类编译的时候是单独编译成一个文件,所以在文件中有final变量的备份。
2.2.4 静态内部类
静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。
静态内部类是不需要依赖于外部类对象的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法。
格式:
public class Demo {
public static void main(String[] args) {
Book.Info info = new Book.Info();
info.say();
}
}
class Book {
static class Info {
public void say(){
System.out.println("这是一本书");
}
}
}
2.3 包装类
在Java中有一个设计的原则“一切皆对象”,那么这样一来Java中的一些基本的数据类型,就完全不符合于这种设计思想,因为Java中的八种基本数据类型并不是引用数据类型,所以Java中为了解决这样的问题,引入了八种基本数据类型的包装类
但是,以上的八种包装类也是分为两种大的类型的:
- Number:Integer、Short、Long、Double、Float、Byte都是Number的子类表示是一个数字。
- Object:Character、Boolean都是Object的直接子类。
拆箱和装箱操作
以下以Integer和Float为例进行操作
将一个基本数据类型变为包装类,那么这样的操作称为装箱操作。
将一个包装类变为一个基本数据类型,这样的操作称为拆箱操作,
因为所有的数值型的包装类都是Number的子类,Number的类中定义了如下的操作方法。
拆箱操作:
装箱操作:
在JDK1.4之前 ,如果要想装箱,直接使用各个包装类的构造方法即可,例如:
int temp = 10 ; // 基本数据类型
Integer x = new Integer(temp) ; // 将基本数据类型变为包装类
在JDK1.5,Java新增了自动装箱和自动拆箱,而且可以直接通过包装类进行四则运算和自增自建操作。例如:
Float f = 10.3f ; // 自动装箱
float x = f ; // 自动拆箱
System.out.println(f * f) ; // 直接利用包装类完成
System.out.println(x * x) ; // 直接利用包装类完成
字符串转换
使用包装类还有一个很优秀的地方在于:可以将一个字符串变为指定的基本数据类型,此点一般在接收输入数据上使用较多。
在Integer类中提供了以下的操作方法:
public static int parseInt(String s);//将String变为int型数据
在Float类中提供了以下的操作方法:
public static float parseFloat(String s);//将String变为Float
在Boolean 类中提供了以下操作方法:
public static boolean parseBoolean(String s);//将String变为boolean
等等操作还有很多
基本数据类型与包装类型的区别
1、包装类是对象,拥有方法和字段,对象的调用都是通过引用对象的地址,基本类型不是
2、包装类型是引用的传递,基本类型是值的传递
3、声明方式不同,基本数据类型不需要new关键字,而包装类型需要new在堆内存中进行new来分配内存空间
4、存储位置不同,基本数据类型直接将值保存在值栈中,而包装类型是把对象放在堆中,然后通过对象的引用来调用他们
5、初始值不同,eg: int的初始值为 0 、 boolean的初始值为false 而包装类型的初始值为null
6、使用方式不同,基本数据类型直接赋值使用就好 ,而包装类型是在集合如 coolection Map时会使用
2.4 抽象类
抽象类使用abstract class 进行声明
一个抽象类中可以没有抽象方法
而抽象方法必须写在抽象类或者接口中
abstract class 类名{ // 抽象类
}
- 抽象方法本身是不能实例化的,它只能被继承,不能被我们创建,但是jvm虚拟器可以创建(抽象类不可以使用new关键字创建对象, 但是在子类创建对象时, 抽象父类也会被JVM实例化 )。
- 一个抽象类必须被子类所继承,被继承的子类(如果不是抽象类)则必须覆写(重写)抽象类中的全部抽象方法(如果有未实现的抽象方法,那么子类也必须定义为 abstract类)。
抽象方法
只进行声明而不进行具体实现的方法(即没有{})
抽象方法要加abstract关键字
// 抽象类
abstract class 类名{
public abstract void 方法名() ; // 抽象方法,只声明而未实现
}
三、接口
如果一个类中的全部方法都是抽象方法,全部属性都是全局常量,那么此时就可以将这个类定义成一个接口。
定义格式:
interface 接口名称{
全局常量 ;
抽象方法 ;
}
使用接口的优点:
-
降低程序的耦合性
-
易于程序的扩展
-
有利于程序的维护
因为接口本身都是由全局常量和抽象方法组成 , 所以接口中的成员定义可以简写:
1、全局常量编写时, 可以省略public static final 关键字,例如:
public static final String INFO = "内容" ;
//简写后:
String INFO = "内容" ;
2、抽象方法编写时, 可以省略 public abstract 关键字, 例如:
public abstract void print() ;
//简写后:
void print() ;
1 接口的实现
使用关键字implements
接口可以多实现,如果一个接口要想使用,必须依靠子类。
子类(如果不是抽象类的话)要实现接口中的所有抽象方法。
class 子类 implements 父接口1,父接口2...{ }
/*以上的代码称为接口的实现。那么如果一个类即要实现接口,
又要继承抽象类的话,则按照以下的格式编写即可: */
class 子类 extends 父类 implements 父接口1,父接口2...{ }
2 接口的继承
使用关键字extends
继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类。
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实力域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
继承的限制:Java中只有单继承,多重继承,没有多继承(即一个子类只能有一个父类)。多重继承通俗来讲就是爷爷、爸爸、孙子。
接口因为都是抽象部分, 不存在具体的实现, 所以允许多继承,例如:
interface C extends A,B{ }
student类实例化时先实例化person,默认调用的person的无参构造方法
public class Demo{
public static void main(String[] args){
Student student = new Student();
student.say();
}
}
class Person{
private String name;
private int age;
public Person(){
supper();//平时supper()可以省略,作用时默认调用父类的无参构造方法
}
public Person(String name,int age){
this.name=name;
this.age=age;
}
public void say(){
System.out.println("姓名:"+name+",年龄:"+age);
}
}
class Student extends Person{
Student(){
supper("张三",1);
}
}
//结果为:
//姓名:张三,年龄:1
2.1 supper关键字
-
supper关键字可以访问父类的构造方法、属性、方法。
-
通过supper调用父类构造方法的代码,必须写在第一行。
-
supper和this调用构造函数时都需要放在第一行,但是两者不会同时使用,因为不可能调用自身构造函数的同时还调用父类的构造方法
2.2 接口与抽象类的区别
1、抽象类要被子类继承,接口要被类实现。
2、接口只能声明抽象方法,抽象类中可以声明抽象方法,也可以写非抽象方法。
3、接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
4、抽象类使用继承来使用, 无法多继承。 接口使用实现来使用, 可以多实现
5、抽象类中可以包含static方法 ,但是接口中不允许(静态方法不能被子类重写,因此接口中不能声明静态方法)
6、接口不能有构造方法,但是抽象类可以有
7、1.8后接口允许出现有方法体的方法
3 多态
多态就是对象的多种表现形式
对象的多态性:
从概念上非常好理解,在类中有子类和父类之分,子类就是父类的一种形态 ,对象多态性就从此而来
方法的多态:
方法的重载 和 重写 也是多态的一种, 不过是方法的多态(相同方法名的多种形态)。
- 重载: 一个类中方法的多态性体现 。
- 重写: 子父类中方法的多态性体现。
多态的使用:对象的类型转换:
类似于基本数据类型的转换:
- 向上转型:将子类实例变为父类实例 |- 格式:父类 父类对象 = 子类实例 ;
- 向下转型:将父类实例变为子类实例 |- 格式:子类 子类对象 = (子类)父类实例 ;
public class Demo{
public static void main(String[] args){
Student student1=new Student();
Nurse nurse1=new Nurse();
//向上转型,父类引用指向子类对象
Person person1=student1;
person1.say(); //输出:我是学生
Person person2=nurse1;
person2.say(); //输出:我是护士
//向下转型
Student student2=(Student)person1;
student2.say(); //输出:我是学生
//向下转型需要注意的是不能把原来是护士的张三转成学生 例如:
Student student3=(Student)person2;
student3.say(); //此处会报错
//向上转型比较高级的用法
Student student4=new Student();
say(student4); //输出:我是学生
}
public static void say(Person person){
person.say();
}
}
abstract class Person{
public abstract void say();
}
class Student extends Person{
@Override
public void say(){
System.out.println("我是学生");
}
}
class Nurse extends Person{
@Override
public void say(){
System.out.println("我是护士")
}
}
向上转型的对象,是通过父类调用子类覆盖或继承父类的方法,不是父类的方法。而且此时父类对象不能调用子类特有的方法。
这里又提到之前说的呢个关键字Instanceof,它可以判断某个对象是否是指定类的实例,返回boolean类型的数据
Object类
Object类是所有类的父类(基类),如果一个类没有明确的继承某一个具体的类,则将默认继承Object类。
使用Object可以接收任意的引用数据类型
public static void main(String[] args){
String text="123";
say(text);
int a=10;
say(a);
}
public static void say(Object o){
System.out.println(o)
}
toString()
建议重写Object中的toString方法。 此方法的作用:返回对象的字符串表示形式 ;
Object的toString方法, 返回对象的内存地址 ;
System.out.println(对象名)一般输出时调用的时对象的toString方法 ;
equals()
建议重写Object中的equals(Object obj)方法,此方法的作用:指示某个其他对象是否“等于”此对象。
***Object的 equals方法:***实现了对象上最具区别的可能等价关系; 也就是说,对于任何非空引用值x和y ,当且仅当 x和y引用同一对象
( x == y具有值true )时,此方法返回true 。
equals方法重写时的五个特性:
自反性 :对于任何非空的参考值x , x.equals(x)应该返回true 。
对称性 :对于任何非空引用值x和y , x.equals(y)应该返回true当且仅当y.equals(x)回报true 。
传递性 :对于任何非空引用值x , y和z ,如果x.equals(y)回报true个y.equals(z)回报true ,然后 x.equals(z)应该返回true 。
一致性 :对于任何非空引用值x和y ,多次调用x.equals(y)始终返回true或始终返回false ,前提是未修改对象上的equals比较中使用的信息。
非空性 :对于任何非空的参考值x , x.equals(null)应该返回false 。
class Person{
private String name;
private int age;
public boolean equals(Object o){
//判断内存地址是否相同
if(this==o){
return true;
}
//非空性
if(o==null){
return false;
}
//判断是否是同一个类
if(o instanceof Person){
//向下转型
Person p2=(Person)o;
//此处调用的是String里的equals()方法,和Object不同
if(this.name.equals(p2.name)&&this.age==p2.age){
return true;
}
}
return false;
}
}
equals和==的区别
前者是比较两个数是否等价,后者是比较地址
可变参数
一个方法中定义完了参数,则在调用的时候必须传入与其一一对应的参数,但是在JDK 1.5之后提供了新的功能,可以根据需要自动传入任意个数的参数。
返回值类型 方法名称(数据类型…参数名称){
//参数在方法内部 , 以数组的形式来接收
}
public class Demo{
public static void main(String[] args){
System.out.println(sum(1)); //输出:1
System.out.println(sum(1,2)); //输出:3
System.out.println(sum(1,2,3)); //输出:6
System.out.println(sum(1,2,3,4)); //输出:10
}
public static int sum(int... nums){
int n=0;
for(int i=0;i<nums.length;i++){
n+=num[i];
}
return n;
}
}
四、容器
java容器是前人为我们提供的一套用于存储数据和对象的工具,类似C++中的STL。总的来说,就是在程序运行过程中,有一些问题无法用固定长度的数组解决,因此需要一些灵活的工具来存储数据。
java容器又可以称为Java Collection Framework(JCF)。里面除了存储对象的容器之外,还提供了一套用于处理和操作容器里面的对象的一套工具类。
在面试中也会经常被问到相关问题
可参考这篇博客 ------>>>容器面试问题
整体框架:
Java容器类库是用来保存对象的,他有两种不同的概念:
Collection,独立元素的序列,这些元素都服从一条或多条规则。List、Set以及Queue都是Collection的一种,List必须按照顺序保存元素,而Set不能有重复元素,Queue需要按照排队规则来确定对象的顺序。
Map,Map是键值对类型,允许用户通过键来查找对象。Hash表允许我们使用另一个对象来查找某个对象。
1 Collection接口
Collection是最基本的集合接口。Java SDK不提供直接继承自Collection的类,Java SDK提供的类都是继承自Collection的“子接口”。所有实现Collection接口的类都必须提供两个标准的构造函数:无参数的构造函数用于创建一个空的Collection,有一个 Collection参数的构造函数用于创建一个新的Collection,这个新的Collection与传入的Collection有相同的元素。后一个构造函数允许用户复制一个Collection。
部分源码:
/**
* @return collection包含元素的个数
*/
int size();
/**
* @return 判断collection是否为空,为空返回true,不为空返回false
*/
boolean isEmpty();
/**
*如果指定的元素的类型与这个集合不兼容,则抛出类型转换异常
*@return 判断collection是否包含元素与o相等,假如 o != null,判断set中是否有元素与o相等,
* 有返回true,没有返回false。假如o == null,抛出空指针异常
*/
boolean contains(Object o);
/**
* 返回包含ollection所有元素的Iterator
*/
Iterator<E> iterator();
/**
* 返回collection所有包含元素的array
*/
Object[] toArray();
/**
* 返回一个包含collection元素的指定类型的数组
*/
<T> T[] toArray(T[] a);
/**
* 插入元素,假如当前collection中存在元素与e相等,那么保持原collection不改变,返回false,
* 否则插入元素,并返回true
*/
boolean add(E e);
/**
* remove类似于这样的元素(o == null? e == null : o.equals(e)),并返回true
*/
boolean remove(Object o);
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean retainAll(Collection<?> c);
boolean removeAll(Collection<?> c);
void clear();
boolean equals(Object o);
int hashCode();
}
1.1 Set
Set:集合,和数学中的集合类似。
特点:
确定性:对任一对象都能判定它是否属于某一个集和
互异性:一个集和中不会存在两个相同(内容相同)的对象
无序性:集和里面的元素没有顺序
HashSet、LinkedHashSet、TreeSet里面存放的都要是对象,不能是基本数据类型。
-
HashSet
-
基于哈希表实现,底层使用HashMap来保存所有元素。
-
不能保证迭代顺序
-
允许使用null元素
-
LinkedHashSet
- LinkedHashSet底层使用LinkedHashMap来保存所有元素,它继承于HashSet。
- 内部使用双向链表维护插入顺序。
- TreeSet
- 基于(TreeMap)红黑树实现
- TreeSet非同步,线程不安全
- TreeSet中的元素支持2种排序方式:自然排序 或者 根据创建TreeSet 时提供的 Comparator 进行排序。
1.2 List
- ArrayList
- 实现 List 接口、底层使用数组保存所有元素。
- 相当于动态数组,支持动态扩容。
- 不同步
- vector
- Vector 可以实现可增长的对象数组。
- Vector 实现 List 接口,继承 AbstractList 类,同时还实现RandmoAccess 接口,Cloneable 接口
- Vector 是线程安全的
- LinkedList
- LinkedList 是基于链表实现的(通过名字也能区分开来),所以它的插入和删除操作比 ArrayList 更加高效。但也是由于其为基于链表的,所以随机访问的效率要比 ArrayList 差。
1.3 Queue
- LinkedList
- 可以用于实现双向队列
- PriorityQueue
- 通过二叉小顶堆实现,可以用一棵完全二叉树表示。
- 可以用于实现优先队列。优先队列的作用是能保证每次取出的元素都是队列中权值最小的(Java的优先队列每次取最小元素,C++的优先队列每次取最大元素)。
2 Map接口
Map也是一个接口,一个map不能包含重复的key,每个key只能映射唯一一个value。Map接口是用来取代Dictionary抽象类的。Map接口提供三个集合视图,1.key的集合 2.value的集合 3.key-value的集合。map内元素的顺序取决于Iterator的具体实现,获取集合视图其实是获取一个迭代器,实现对遍历元素细节的隐藏。
同样,map的实现类应该提供两个“标准”构造器,一个无参构造器用来创建一个空map,一个只有一个参数,参数类型是map的构造器,用来创建一个新的和传入参数有一样key-value映射的map。实际上,后者允许复制任何一个map,这仅仅是一个建议,并没有强制要求,因为接口是无法包含构造器的,不过这个建议在JDK被遵守。
如果一个方法的操作是不被支持的,这个方法指定抛出UnsupportedOperationException异常。如果这个操作对map是没有影响的,那么也可以不抛出UnsupportedOperationException异常。例如,在一个不能被修改的map调用putAll(Map)方法,如果该map的映射是空的,就不要求抛出UnsupportedOperationException异常。
看一下部分源码:
/**
*返回map中key-value映射的数量
*/
int size();
/**
*如果map中没有key-value映射返回true
*/
boolean isEmpty();
/**
*如果map不含key映射,返回false,当key的类型不符合,抛出ClassCastException,当key是
*null且该map不支持key的值是null时,抛出NullPointerException
*/
boolean containsKey(Object key);
/**
*如果map含有一个以上的key映射的参数value,返回true,异常抛出的情况和containKey一样
*/
boolean containsValue(Object value);
/**
*根据key得到对应的value,如果没有对应的映射,返回null,如果map允许value为null,返回
*null可能是有一对key-null的映射或没有对应的映射
*/
V get(Object key);
/**
*往map放入一对key-value映射
*/
V put(K key, V value);
/**
*根据key删除对应映射
*/
V remove(Object key);
/**
*复制一份与参数一样的map
*/
void putAll(Map<? extends K, ? extends V> m);
/**
*清空map中所有的映射
*/
void clear();
/**
*返回map中所有key的集合
*/
Set<K> keySet();
/**
*返回map中所有value的集合
*/
Collection<V> values();
/**
*返回key-value的集合
*/
Set<Map.Entry<K, V>> entrySet();
/**
*比较调用者与参数是否相等
*/
boolean equals(Object o);
/**
*计算map的hash code
*/
int hashCode();
}
2.1 HashTable
- HashTable是遗留类,多数功能与HashMap类似,继承自Dictionary类。
- HashTable是线程安全的。也就是说任意时刻只有一个线程能够写HashTable。
- HashTable的并发性不如ConcurrentHashMap,因为ConcurrentHashMap引入了分段锁。
2.2 HashMap
- HashMap根据键的HashCode来实现,访问速度较快,遍历顺序并不确定。
- HashMap最多只允许一条记录的键为null,允许多条记录的值为null。
- HashMap线程不安全,也就是说任意时刻可以有多个线程同时写HashMap,所以可能会导致数据的不一致。
- 如何确保线程安全?可以用 Collections的synchronizedMap方法使HashMap具有线程安全的能力,或者使用ConcurrentHashMap。
2.3 LinkedHashMap
- 基于哈希表和链表实现,借助双向链表确保迭代顺序是插入的顺序。
2.4 TreeMap
- 基于红黑树实现
- 默认按照键值得升序进行排序。
- 在使用TreeMap时,key必须实现Comparable接口或者在构造TreeMap传入自定义的Comparator,否则会在运行时抛出java.lang.ClassCastException类型的异常。
五、异常
Java异常是Java提供的一种识别及响应错误的一致性机制。
Java异常机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程序健壮性。在有效使用异常的情况下,异常能清晰的回答3个问题:异常类型回答了“什么”被抛出,异常堆栈跟踪回答了“在哪“抛出,异常信息回答了“为什么“会抛出。
相关面试题,可以参考这篇博客,总结的很全面 ----->>> 异常面试题
Java异常机制用到的几个关键字:try、catch、finally、throw、throws
- try – 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。
- catch – 用于捕获异常。catch用来捕获try语句块中发生的异常。
- finally – finally语句块总是会被执行。它主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件)。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。
- throw – 用于抛出异常。
- throws – 用在方法签名中,用于声明该方法可能抛出的异常。
1 异常框架
Java异常架构图如下,异常类有两个主要的子类:IOException 类和 RuntimeException 类。
1.1 Throwable
Throwable是 Java 语言中所有错误或异常的超类。
Throwable包含两个子类: Error 和 Exception。它们通常用于指示发生了异常情况。
Throwable包含了其线程创建时线程执行堆栈的快照,它提供了printStackTrace()等接口用于获取堆栈跟踪数据等信息。
1.2 Error
- 和Exception一样,Error也是Throwable的子类。它用于指示合理的应用程序不应该试图捕获的严重问题,大多数这样的错误都是异常条件。
- 和RuntimeException一样,编译器也不会检查Error。
1.3 Exception
Exception及其子类是 Throwable 的一种形式,它指出了合理的应用程序想要捕获的条件。
1.4 IOException
IO是读写的意思 I 是input, O 是output 意思就是输入输出,一般读写文件会出现这个异常,比如你想从磁盘上读一个文件到你写的程序,如果硬盘上没有这文件,java虚拟机就会报这个异常
1.5 RuntimeException
RuntimeException是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。
编译器不会检查RuntimeException异常。例如,除数为零时,抛出ArithmeticException异常。RuntimeException是ArithmeticException的超类。当代码发生除数为零的情况时,倘若既"没有通过throws声明抛出ArithmeticException异常",也"没有通过try…catch…处理该异常",也能通过编译。这就是我们所说的"编译器不会检查RuntimeException异常"!
如果代码会产生RuntimeException异常,则需要通过修改代码进行避免。例如,若会发生除数为零的情况,则需要通过代码避免该情况的发生!
2 抛出结构
Java将可抛出(Throwable)的结构分为三种类型:被检查的异常(Checked Exception),运行时异常(RuntimeException)和错误(Error)。
2.1 运行时异常
- 定义: RuntimeException及其子类都被称为运行时异常。
- 特点: Java编译器不会检查它。也就是说,当程序中可能出现这类异常时,倘若既"没有通过throws声明抛出它",也"没有用try-catch语句捕获它",还是会编译通过。例如,除数为零时产生的ArithmeticException异常,数组越界时产生的IndexOutOfBoundsException异常,fail-fail机制产生的ConcurrentModificationException异常等,都属于运行时异常。
虽然Java编译器不会检查运行时异常,但是我们也可以通过throws进行声明抛出,也可以通过try-catch对它进行捕获处理。
如果产生运行时异常,则需要通过修改代码来进行避免。例如,若会发生除数为零的情况,则需要通过代码避免该情况的发生!
2.2 被检查的异常
- 概念:Exception类本身,以及Exception的子类中除了"运行时异常"之外的其它子类都属于被检查异常。
- 特点: Java编译器会检查它。此类异常,要么通过throws进行声明抛出,要么通过try-catch进行捕获处理,否则不能通过编译。例如,CloneNotSupportedException就属于被检查异常。当通过clone()接口去克隆一个对象,而该对象对应的类没有实现Cloneable接口,就会抛出CloneNotSupportedException异常。
- 被检查异常通常都是可以恢复的。
2.3 错误
- 定义: Error类及其子类。
- 特点: 和运行时异常一样,编译器也不会对错误进行检查。
当资源不足、约束失败、或是其它程序无法继续运行的条件发生时,就产生错误。程序本身无法修复这些错误的。例如,VirtualMachineError就属于错误。
按照Java惯例,我们是不应该是实现任何新的Error子类的!
对于上面的3种结构,我们在抛出异常或错误时,到底该哪一种?《Effective Java》中给出的建议是:对于可以恢复的条件使用被检查异常,对于程序错误使用运行时异常。
3 监视实现代码
3.1 try-catch
public class Demo1 {
public static void main(String[] args) {
try {
int i = 10/0;
System.out.println("i="+i);
} catch (ArithmeticException e) {
System.out.println("Caught Exception");
System.out.println("e.getMessage(): " + e.getMessage());
System.out.println("e.toString(): " + e.toString());
System.out.println("e.printStackTrace():");
e.printStackTrace();
}
}
}
运行结果:
Caught Exception
e.getMessage(): / by zero
e.toString(): java.lang.ArithmeticException: / by zero
e.printStackTrace():
java.lang.ArithmeticException: / by zero
at Demo1.main(Demo1.java:6)
分析:在try语句块中有除数为0的操作,该操作会抛出java.lang.ArithmeticException异常。通过catch,对该异常进行捕获。
没有执行System.out.println(“i=”+i)。这说明try语句块发生异常之后,try语句块中的剩余内容就不会再被执行了。
3.2 finally
在try-catch的基础上,增加finally:
public class Demo2 {
public static void main(String[] args) {
try {
int i = 10/0;
System.out.println("i="+i);
} catch (ArithmeticException e) {
System.out.println("Caught Exception");
System.out.println("e.getMessage(): " + e.getMessage());
System.out.println("e.toString(): " + e.toString());
System.out.println("e.printStackTrace():");
e.printStackTrace();
} finally {
System.out.println("run finally");
}
}
}
运行结果:
Caught Exception
e.getMessage(): / by zero
e.toString(): java.lang.ArithmeticException: / by zero
e.printStackTrace():
java.lang.ArithmeticException: / by zero
at Demo2.main(Demo2.java:6)
run finally
结果说明:最终执行了finally语句块。
3.3 throws和throw
class MyException extends Exception {
public MyException() {}
public MyException(String msg) {
super(msg);
}
}
public class Demo3 {
public static void main(String[] args) {
try {
test();
} catch (MyException e) {
System.out.println("Catch My Exception");
e.printStackTrace();
}
}
public static void test() throws MyException{
try {
int i = 10/0;
System.out.println("i="+i);
} catch (ArithmeticException e) {
throw new MyException("This is MyException");
}
}
}
运行结果:
Catch My Exception
MyException: This is MyException
at Demo3.test(Demo3.java:24)
at Demo3.main(Demo3.java:13)
结果说明:
MyException是继承于Exception的子类。test()的try语句块中产生ArithmeticException异常(除数为0),并在catch中捕获该异常;接着抛出MyException异常。main()方法对test()中抛出的MyException进行捕获处理。
六、总结
以上便是java语言基础的上半部分,这些是基础的语法与学习这门语言必须了解的一些概念,虽然简单但还是有很多细节需要琢磨。如果总结的有所纰漏的话多多包涵,希望大家读完后有所收获,无限进步!!!
如果你已经将以上部分学完,可以移步这篇文章,进行下一阶段的学习。
动态代理博文