一、类和对象
java作为一门面向对象的程序设计语言,在java中所有的东西都会属于某个类(class),你会建立的是源文件(扩展名为.java),然后将它编译成新的文件(扩展名为.class),实际上真正执行的是类class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| //一个狗的示例类 class dog{ int age; String name; //数据成员 public void eat() { System.out.print("小狗吃东西"); } //抽象行为 public void Needfood(int food) { System.out.print("小狗要吃"+food+"克狗食"); } //成员方法 }
|
在上面的小狗类示例中,可以发现一个类有几种基本元素
- 数据成员(Data)
- 抽象行为(Abstract Behavior)
- 成员方法(Method)
对象的继承(Inheritance)
首先我们想象这么一个场景,两位programmer接到了相同的任务,要求在图形接口画出四方形,圆形与三角形,当用户点选图形时,图形需要顺时针转动360°并依据形状的不同播放不同音频文件,我们分别称呼两位programmer为一号和二号
一号的设计思路是这样的,写出旋转以及播放音频函数:
1 2 3 4 5 6 7 8
| rotate(shapeNum) { //旋转360° }//旋转函数 playsound(shapeNum) { //播放对应音频文件 }//音频播放函数
|
而二号的设计思路是将需求的三个类都写出来并且每一个类都对其旋转和音频播放函数进行定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| class Square{//正方形 rotate(){ //旋转函数 } playsound(){ //音频播放函数 } }
class Circle(){//圆形 rotate(){ //旋转函数 } playsound(){ //音频播放函数 } }
class Triangle(){//三角形 rotate(){ //旋转函数 } playsound(){ //音频播放函数 } }
|
综上可以看出,一号的代码量要少很多,但是可读性以及后期针对不同需求的时候需要修改,比如我们增加一个阿米巴虫形状,一号的程序代码就需要针对阿米巴虫形状进行专门的修改,以至于后期如果增加更多的类和不同情况下的需求,需要在playsound函数中增加大量的分支语句,无论是读起来还是理解起来都非常吃力(如果还不是自己写的)
1 2 3 4 5 6 7
| playsound(shapeNum){ //如果不是阿米巴虫形状 //查询使用哪个音频文件 //播放 //不然 //播放阿米巴虫对应的音频文件 }
|
而二号在可读性以及后期的修改上就要轻松很多,但是代码量要多得多,而且真正修改起来还得寻找相应的对象,也是非常麻烦,只要有新的需求就需要重新写入一个类
由此在这种情况下我们就可以用到java中继承(inheritance)的特性,利用extends关键字,创建最顶层的类shape类,shape类具有包括正方形长方形三角形等几何图形所具有的共同特征以及特性
1 2 3 4 5 6 7 8 9 10 11 12 13
| class shape{ double area;//面积 String name;//图形名称 void rotate() { //图形旋转 } void playsound() { //播放音频文件 } }
|
上面我们定义了顶层的图形类shape,在定义其他图形时可以直接继承shape类的各种数据成员以及方法,例如直接让三角形继承shape类
1 2 3 4 5 6 7 8 9 10
| class Triangle extends shape{ //此处我们可以直接对shape中已有的方法进行重写override void rotate(){ //三角形旋转的具体实现 } void playsound(){ //三角形旋转时播放的音频文件 } }
|
重载(Override)
如果想要继承的对象其中的某一个方法不按主类的具体方法实现,可以采取对方法进行重载的方式,只需要在子类中定义具体的实现方法即可
控制对象
事实上并没有对象变量这样的东西存在
只有引用(reference)到对象的变量
对象引用变量保存的是存取对象的方法
对象的声明、创建与赋值有3个步骤:
1 2
| //以Dog类为例 Dog mydog=new Dog();
|
在其背后的具体实现细节分为三步:
一、声明一个引用变量
Dog myDog=new Dog();
要求Java虚拟机分配空间给引用变量,并将此变量命名为myDog,此引用变量将永远被固定为Dog类型,换句话说,它是个控制Dog的遥控器
二、创建对象
Dog myDog=new Dog();
要求Java虚拟机分配堆空间给新建立的Dog对象
三、连接对象和引用
Dog myDog**=**new Dog();
将新的Dog赋值给myDog这个引用变量
对象中的变量
在Java对象中,有实例变量和局部变量两种变量,实例变量时声明在类中,而局部变量是声明在方法中的
实例变量声明在类中:
1 2 3 4 5
| class Horse{ private double height=15.2; private String breed; //more code...... }
|
局部变量声明在方法中且局部变量没有默认值,如果在变量被初始前就被使用的话,编译器就会显示错误
1 2 3 4 5 6 7
| class Foo{ public void go() { int x; int z=x+3;//无法编译,x并未初始化 } }
|
对象的比较
在Java中,运算符“==”可以用于比较两个引用是否指向同一对象
1 2 3 4 5 6
| Foo a=new Foo(); Foo b=new Foo(); Foo c=a; a==b;//false a==c;//true b==c;//false
|
this关键字
this关键字出现在类的构造方法中时,代表使用该构造方法所创建的对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class People{ int leg,hand; String name; People(String s){ name=s; this.init(); } void init(){ leg=2; hand=2; System.out.println(name+"有"+hand+"只手"+leg+"条腿"); } public static void main(String[] args){ People boshi=new People("布什"); } }
|
垃圾收集
一个类声明的两个对象如果具有相同的引用,那么二者就具有完全相同的实体,而且Java有所谓的“垃圾收集”机制,这种机制周期地检测某个实体是否已不再被任何对象所拥有(引用),如果发现这样的实体,就释放实体占有的内存
类与程序的基本结构
一个Java应用程序(Project)由若干个类构成,这些类可以在一个源文件中,也可以分布在若干个源文件中。
Java应用程序有一个主类,即含有main方法的类,Java应用程序从主类的main方法开始执行。在编写一个Java应用程序时,可以编写若干个Java源文件,每个源文件编译后产生若干个类的字节码文件
实例方法和类方法的区别
1.对象调用实例方法
实例方法中不仅可以操作实例变量,也可以操作类变量。当对象调用实例方法时,该方法中出现的实例变量就是分配给该对象的实例变量,该方法中出现的类变量也是分配给该对象的变量,只不过这个变量和所有的其他对象共享而已
2.类名调用类方法
与实例方法不同,类方法不可以操作实例变量,这是因为在类创建对象之前,实例成员变量还没有分配内存
3.设计类方法的原则
对于static方法,不必创建对象就可以用类名直接调用它,如果一个方法不需要操作类中的任何实例变量,就可以满足程序的需要,考虑将这样的方法设计为一个static方法
1 2 3 4 5 6 7
| public static void main(String[] args){ Scanner sc=new Scanner(System.in); int []a={12,34,9,23,45,6,45,90,123,19,34}; Arrays.sort(a); System.out.println(Arrays.toString(a)); }
|
二、继承
- 规划程序时要考虑未来
- 用继承来防止子类中出现重复的代码
- 寻找更多抽象化的机会
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class Doctor{ boolean worksAtHospital; void treatPatient(){ } }
public class FamilyDoctor extends Doctor{ boolean makesHouseCalls; void giveAdvice(){ } }
public class Surgeon extends Doctor{ void treatPatient(){ } void makeIncision(){ } }
|
instanceof运算符
instanceof是Java独有的双目运算符,其左面的操作元是对象,右面的操作元是类,当左面的操作元是右面的类或其子类所创建的对象时,instanceof运算的结果是true,否则是false
方法重写
重写的语法规则
如果子类可以继承父类的某个方法,那么子类就有权利重写这个方法。所谓方法重写,是指子类中定义一个方法,这个方法的类型和父类的方法的类型一致
重写的目的
重写方法既可以操作继承的成员变量、调用继承的方法,也可以操作子类新声明的成员变量、调用新定义的其他方法,但无法操作被子类隐藏的成员变量和方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class A{ float computer(float x,float y){ return x+y; } public int g(int x,int y){ return x+y; } }
class B extends A{ float computer(float x,float y){ return x*y; } }
public static void main(String[] args){ B b=new B(); double result=b.computer(8,9); System.out.println(result); int m=b.g(12,8); System.out.println(m); }
|
上例中,如果子类如下定义computer方法,将产生编译错误
1 2 3 4 5 6 7
| double computer(float x,float y){ return x*y; } //因为父类的computer方法类型是float,子类的computer方法类型没有和父类的computer方法保持一致
//重写的注意事项 //重写父类的方法时,不允许降低方法的访问权限,但可以提高访问权限(public,protected,friendly,private)
|
super关键字
用super操作被隐藏的成员变量和方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class Sum{ int n; float f(){ float sum=0; for(int i=1;i<=n;i++) sum=sum+i; return sum; } }
class Average extends Sum{ int n; float f(){ float c; super.n=n; c=super.f(); return c/n; } float g(){ float c; c=super.f(); return c/2; } }
|
有人认为super和this引用是类似的概念,实际上这样比较并不恰当因为super不是一个对象的引用,它只是一个指示编译器调用超类方法的关键字
子类中可以增加域、增加方法或覆盖超类的方法,然而绝对不能删除继承类的任何域和方法
阻止继承:final类和方法
有时候,可能希望组织人们利用某个类定义子类。不允许扩展的类被称为final类。如果在定义类的时候使用了final修饰符就表明这个类是final类。
1 2 3 4 5 6 7 8 9 10 11 12 13
| public final class Executive extends Manager { .... }
public class Employee { public final String getName() { return name; } }
|
三、接口(Interface)与多态
抽象类(abstract)
把抽象类放在这一章节是因为抽象类与接口之间具有很高的学习联系价值,对抽象类的理解有助于我们对接口的学习,并且可以更加深刻的理解面向对象编程即面向抽象编程的思维
用关键词abstract修饰的类称为抽象类,例如
1 2 3
| abstract class A{ ........ }
|
用关键词abstract修饰的方法称为抽象方法
在抽象类中,有几点以下原则
- abstract类中可以有abstract方法
- abstract类不能用new运算符创建对象
- 对于abstract类的子类,必须重写父类的abstract的发给发,即去掉父类的abstract的修饰并写出具体的方法体,如果子类也是abstract类,既可以选择重写,也可以选择继承
- 可以使用abstract类声明对象,虽然不能用new运算符创建该对象,但是该对象可以成为其子类对象的上转型对象,该对象可以调用子类重写后的方法
具象化为代码,我们可以想象出以下情况
有一个柱体,我们需要通过代码求出柱体的体积,但是柱体的底面有很多种,可能是三角形,也可能是圆形等等
我们可以发现,无论柱体的底面是什么形状的,都有一个共同的行为,那就是有一个求出自身面积的行为,所以我们可以直接通过一个抽象类服务于不同形状的底面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| public abstract class Geometry{ public abstract double getArea(); }
public class Pillar{ Geometry bottom; double height; Pillar(Geometry bottom,double height){ this.bottom=bottom;this.height=height; } public double getVolume(){ if(bottom==null){ System.out.println("没有底,无法计算体积"); return -1; } return bottom.getArea()*height; } }
public class Rectangle extends Geometry{ double a,b; Rectangle(double a,double b){ this.a=a; this.b=b; } public double getArea(){ return a*b; } }
public class Example{ public static void main(String[] args){ Pillar pillar; Geometry bottom=null; pillar = new Pillar(bottom,100); System.out.println("体积"+pillar.getVolume()); bottom=new Rectangle(12,22); pillar =new Pillar(bottom,58); System.out.println("体积"+pillar.getVolume()); bottom=nnew Circle(10); pillar=new Pillar(bottom,58); System.out.println("体积"+pillar.getVolume()); } }
|
定义接口
使用关键字interface来定义一个接口
1 2 3 4 5 6
| interface Printable{ public static final int MAX=100; public abstract void add(); public abstract float sum(float x,float y); }
|
接口体中包含常量的声明和抽象方法两部分,接口体中只有抽象方法,没有普通的方法,而且接口体中所有的常量的访问权限一定都是public,而且都是static常量,所有的抽象方法的访问权限一定都是public
实现接口
在Java中,类实现接口时使用的是implements关键字,实现多个接口时用逗号分隔开不同的接口
1
| class A implements Printable,Addable
|
接口的特性
如果一个非抽象类实现了某个接口,那么这个类必须重写接口中的所有方法,同时因为接口中的所有方法都是public abstract的修饰,所以当重写时一定要明显地用public修饰重写(不然访问权限降级,这在java中是不允许的)的方法,并且去掉abstract关键字
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public interface Computable{ int MAX=46; int f(int x); }
public class China implements Computable{ int number; public int f(int x){ int sum=0; for(int i=1;i<=x;i++){ sum+=i; } return sum; } }
public class Japan implements Computable{ int number; public int f(int x){ return MAX+x; } }
public class Example{ public static void main(String[] args){ China zhang; Japan henlu; zhang=new China(); henlu=new Japan(); zhang.number=32+Computable.MAX; henlu.number=14+Computable.MAX; System.out.println(zhang.f(100)); System.out.println(henlu.f(100)); } }
|
而对于抽象类,抽象类既可以重写接口中方法,也可以直接拥有接口中的方法
一个类如果实现了接口,那么该类可以直接在类体中使用该接口中的常量
接口回调
接口回调我认为是一个非常重要的概念,他跟多态的实现有着非常紧密的联系,如下有两个类实现了同一接口,同一个接口可以实现两个不同对象的引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| interface ShowMessage{ void 显示商标(String s); }
class TV implements ShowMessage{ public void 显示图标(String s){ System.out.println(s); } } class PC implements ShowMessage{ public void 显示商标(String s){ System.out.println(s); } }
public class Example{ public static void main(String[] args){ ShowMessage sm; sm=new TV(); sm.显示商标("长城牌电视机"); sm=new PC(); sm.显示商标("联想奔月5008PC机"); } }
|
接口与多态
多态和接口有着不可分割的紧密关系,由接口产生的多态就是指不同的类在实现同一个接口时可能具有不同的实现方式,即接口在不同类中重新定义的函数体不同,有点像函数的重载**(或许?)**
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| interface SpeakHello{ public void speak(); }
class Chinese implements SpeakHello{ public void speak(){ System.out.println("你好!"); } }
class British implements SpeakHello{ public void speak(){ System.out.println("Hello!"); } }
class Japanese implements SpeakHello{ public void speak(){ System.out.println("こんにちは"); } }
class HelloSample{ public void Hello(SpeakHello speakhello){ speakhello.speak(); } }
public class Example{ public static void main(String[] args){ HelloSample hi=new HelloSample(); hi.Hello(new Chinese()); hi.Hello(new Japanese()); hi.Hello(new British()); } }
|
程序运行后,结果如下
abstract类与接口的比较
经过学习,我们发现抽象类与接口在某些程度上具有很高的重合度,但是两者也是具有很多区别的,就比如Java不支持类的多重继承,多重继承只能够通过实现多个接口来实现,两者具有如下诸多区别
- abstract类和接口都可以有abstract方法
- 接口中只可以有常量不可以有变量,但是abstract类中既可以有常量也可以有变量
- abstract类中也可以有非abstract方法,但是接口不可以
四、异常类
程序运行时可能会出现一些错误,异常处理将会改变程序的控制流程,让程序能够对错误进行处理
try-catch语句
Java用try-catch语句来处理异常,将可能出现异常的操作放在try下,一旦抛出异常对象,就会跳到相应的catch语句下作出反应,其响应有点像switch-case语句
1 2 3 4 5 6
| try{ 可能发生异常的语句; } catch(ExceptionSubClass e){ ... }
|
这里我们举一个非常简单的例子,其具体是为变量赋值
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class Example{ public static void main(String[] args){ int n=0,m=0,t=100; try{ m=Integer.parseInt("8888"); n=Integer.parseInt("ab89"); t=7777; } catch(NumberFormatException e){ System.out.println("发生异常:"+e.getMessage()); } } }
|
自定义异常类
在Java中,也可以自己定义异常类并且书写具体的方法,自己决定捕捉到异常后具体的应对方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| public class BankException extends Exception{ String message; public BankException(int m,int n){ message="入账资金"+m+"是负数或指出"+n+"是正数,不符合系统要求."; } public String warnMess(){ return message; } }
public class Bank{ private int money; public void income(int in,int out) throws BankException{ if(in<=0||out>=0||in+out<=0){ throw new BankException(in,out); } int netIncomde=in+out; System.out.printf("本次计算出的纯收入是:%d元\n",netIncome); money=monet+netIncome; } public int getMoney(){ return money; } }
public class Example{ public static void main(String[] args){ Bank bank=new Bank(); try{ bank.income(200,-100); bank.income(300,-100); bank.income(400,-100); System.out.printf("银河目前有%d元\n",bank.getMoney()); bank.income(200,100); bank.income(99999,-100); }catch(BankException e){ System.out.println("计算收益的过程中出现如下问题:"); System.out.println(e.warnMess()); } System.out.printf("银行目前有%d元\n",bank.getMoney()); } }
|
finally子语句
finally语句的主要使用如下
1 2 3
| try{} catch(ExceptionSubClass e){} finally{}
|
finally语句有以下特点
- 在执行try-catch语句后,执行finally子语句,无论在try部分是否发生异常,finally语句都会执行
- 但是如果在try-catch语句中执行了return语句,finally子语句仍然会执行
- try-catch中执行了程序退出代码,即执行System.exit(0);,则不执行finally语句
五、集合

Collection超类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| Iterator<E> iterator()
int size()
boolean isEmpty()
boolean contains(Obeject obj)
boolean containsALL(Collection<?> other)
boolean add(Object element) j boolean addAll(Collection<? extends E> other)
boolean remove(Object obj)
boolean removeAll(Collection<?> other)
void clear()
boolean retainALL(Collection<?> other)
Object[] toArray()
|
六、常用类
String类
在Java中,String类具有不可扩展,不可继承的特性,因为在Java中String类被定义为final类
常量池
在Java中,常量池是一个非常重要的概念,因为String常量是对象,所以也有自己的引用和实体,他们都存放在常量池中
可以这样理解常量池:常量池中的数据在程序运行期间再也不允许改变,而凡是通过new构造出的对象都不在常量池中
声明对象
1 2
| String s=new String("we are students"); String t=new String("we are students");
|
我们还可以通过使用字符数组的方式声明String类
1 2 3 4
| char a[]={'J','a','v','a'}; String s=new String(a);
String s=new String(a,0,2);
|

获取字符串的长度
1 2
| String name="Kevin"; int len=name.length();
|
比较两个字符串
public boolean equal(String s)
要注意的是,这里的equal方法只判断两个字符串的内容,并不判断他们在常量池中是否指向同一对象
1 2 3 4
| String tom=new String("Kevin"); String boy=new String("Keivn"); tom==boy; tom.equals(boy);
|
前后缀比较
1 2 3
| String tom="天气预报",jerry="比赛结果"; tom.startsWith("天气"); jerry.endsWith("结果");
|
String类常用API
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| boolean contains(String s); int indexOf(String s); int lastIndexOf(String s); String substring(int startpoint,int endpoint);
int x; String s="876"; x=Integer.parseInt(s);
String str=String.valueOf(1234);
char [] a,c; String s="1945年8月15日是抗战胜利日"; a=new char[4]; s.getChars(11,15,a,0); System.out.println(a); c="十一长假期间,学校都放假了".toCharArray(); for(int i=0;i<c.length();i++){ System.out.print(c[i]); }
|
关于字符串数字之间的交换,这里我们给出一个例子,我个人认为是比较有意义的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class Example{ public static void main(String[] args){ double sum=0,item=0; boolean computable=true; for(String s:args){ try{ item=Double.parseDouble(s); sum+=item; } catch(NumberFormatException e){ System.out.println("您输入了非数字字符:"+e); computable=false; } } if(computable) System.out.printlb("sum="+sum); } }
|
借用牛客网上的一道字符串相关的题目,有几个点需要注意一下
1.在Java中,字符串String是不允许更改的,所以在coding时我们经常用StringBuffer、StringBuilder构造之后再进行操作
2.一般的算法题,因为传递的参数都是String类,但是由于我们一般用StringBuffer、StringBuilder操作,所以在返回时需要用API:**toString()**更改返回值的类型
将一个由英文字母组成的字符串转换成从末尾开始每三个字母用逗号分隔的形式
输入:
输出:
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import java.util.Scanner;
public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); String str = scanner.next();
StringBuilder newstr=new StringBuilder(str); for(int i=str.length()-3;i>=0;i-=3){ newstr.insert(i,','); } System.out.println(newstr.toString()); } }
|
在许多的算法测试用例中,会有如下的输入格式
这种字母与数字在同一行的,我们可以用以下方法读取,并且针对操作
1 2 3
| String op=sc.next(); String[] arr=op.split(" ");
|
以下是模拟栈的完整代码,里面涉及了几个常用的字符串相关API操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| import java.util.Scanner;
public class Main { public static int N=100010; public static int[] stk=new int[N]; public static int n,idx=-1;
public static void push(int x){ stk[++idx]=x; }
public static void pop(){ System.out.println(stk[idx--]); }
public static void top(){ System.out.println(stk[idx]); } public static void main(String[] args){ Scanner sc=new Scanner(System.in); n= sc.nextInt(); while(n-->=0){ String op= sc.nextLine(); String[] str=op.split(" "); if(str[0].equals("push")){ push(Integer.parseInt(str[1])); }else if(str[0].equals("pop")){ if(idx<0){ System.out.println("error"); }else{ pop(); } }else if(str[0].equals("top")){ if(idx<0){ System.out.println("error"); }else{ top(); } } } } }
|
正则表达式