1.Java面向对象
面向过程思想
- 面向过程思想:步骤清晰简单,第一步做什么,第二步做什么
- 面向过程适合处理一些较为简单的问题
面向对象编程(Object-Oriented Programming,OOP)
面向对象编程的本质是:以类的方式组织代码,以对象的形式组织(封装)数据。
- 物以类聚,分类的思维模式,思考问题前首先会想问题需要哪些分类,然后对这些分类进行单独思考。最后才对某个分类下的细节进行面向过程的思索。
- 面向对象适合处理复杂的问题,适合处理需要多人协作的问题。
对于描述复杂的事物,为了从宏观上把握、从整体上合理分析,我们需要使用面向对象的思路来分析整个系统。但是具体到微观操作,仍然需要面向过程的思路去处理。
比如盖一栋大楼,我需要钢筋,混凝土,木材等,先把这些分类好然后去找人做这些事情,然后这些比如粉刷工,钢筋工,木工再去对自己具体分类的问题进行面向过程的微观处理,比如粉刷工要思考用几号油漆,混凝土工要思考用多少沙子,水和石灰进行搅拌等具体的过程处理。
总结,面向对象相当于一个总体框架,面向过程是一个具体的执行流程。
三大特性:封装,继承,多态。
类与对象区别:类是一个模板,是抽象的,对象是一个实例。狗类是一个抽象的概念,但具体的一条叫旺财的狗是一个对象。具体某一条狗,就是对象。单纯说狗这是类,类其实就是类别, 类别关注共同的属性与行为,比如狗的属性:颜色、体重;狗的行为:啃骨头、撒尿。具体某一条狗是什么颜色、多少体重这就是对象了。
2.Java构造器
public class Person {
/* 一个类即使什么都不写,它也会存在一个方法,即无参构造,用来初始化值,默认为null,
如下person.name在没有赋值的情况下为string等引用类型统统默认为null,int默认为0,boolean为false,char为u000 */
String name;
int age;
}
---------------------------------------------------------
public class Application {
public static void main(String[] args) {
Person person = new Person();
System.out.println(person); //Person@4554617c
System.out.println(person.name); //null
}
}
如下调用是没有问题的,但如果使用来调用,就必须显式定义无参,否则报错,如下会报错,也可以理解为重载,new Person()没有找到自己对应的那个无参的方法
public class Person {
String name;
int age;
public Person(String name){
this.name=name;
}
}
-----------------------------------------------------------
public class Application {
public static void main(String[] args) {
Person person = new Person(); //使用new关键字,本质是在调用构造器
System.out.println(person);
System.out.println(person.name);
}
}
当然,上面的Application类里实例化Person时改为有参就不会报错(类似于重载),即
public class Application {
public static void main(String[] args) {
Person person = new Person("张三"); //但是这种有局限性,平常肯定是new Person()无参调用比较多,
System.out.println(person); //Person@4554617c
System.out.println(person.name); //张三
}
}
构造器:和类名相同,没有返回值;
作用:new 本质是在调用构造方法,初始化对象的值;
注意点:定义有参构造之后,要想使用Person person = new Person();这种无参写法,必须显示定义无参构造,这个就相当于重载的思想。
3.Java值传递与引用传递
在Java中参数的传递主要有两种:值传递和参数传递。
一:值传递
解释:实参传递给形参的是值 形参和实参在内存上是两个独立的变量 对形参做任何修改不会影响实参。通俗的讲法就是:形参只是实参创建的一个副本,副本改变了,实参当然不可能跟着改变;
再通俗的讲法就是:小明去餐厅吃饭,看见别人点的红烧肉挺好吃,九把服务员叫过来,说我要一份红烧肉,服务员从后厨拿来一份红烧肉,小明吃完了,但是他吃的红烧肉跟旁边那个人吃的是一份吗?当然不是。
public class Demo1 {
public static void main(String[] args) {
int b =20;
change(b);// 实参 实际上的参数
System.out.println(b);
}
public static void change(int a){//形参 形式上的参数
a=100;
}
}
//20
二:引用传递
也称为传地址。方法调用时,实际参数的引用(地址,而不是参数的值)被传递给方法中相对应的形式参数,函数接收的是原始值的内存地址;
在方法执行中,形参和实参内容相同,指向同一块内存地址,方法执行中对引用的操作将会影响到实际对象。
public class Demo {
public static void main(String[] args) {
Demo demo=new Demo();
MyObj obj=new MyObj();
demo.test2(obj);//这里传递的参数obj就是引用传递
System.out.println(obj.b);
}
public void test2(MyObj obj){
obj.b=100;
System.out.println(obj.b);
}
}
class MyObj{
public int b=99;
}
/*
100
100
*/
可以看到,int值没有发生变化,但是在test2方法中对obj类做的修改影响了obj这个对象。
结合上面的分析,关于值传递和引用传递可以得出这样的结论:
(1)基本数据类型传值,对形参的修改不会影响实参;
(2)引用类型传引用,形参和实参指向同一个内存地址(同一个对象),所以对参数的修改会影响到实际的对象;
(3)String, Integer, Double等immutable的类型特殊处理,可以理解为传值,最后的操作不会修改实参对象。
4.Java封装
程序设计追求“高内聚,低耦合“,高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;低耦合:仅暴露少量的方法给外部使用。
封装,即数据的隐藏:通常,应禁止直接访问一个对象中数据的实际表示,而应同通过操作接口来访问,这称为信息隐藏。就记住两个特点:属性私有,get/set。
原先写一个类,里面定义一个属性都是public类型的,外部是可以直接访问并且给他的属性赋值,也就是可以直接用这个属性,比如
public class Person {
public String name;
public int age;
}
-----------------------------------------------------------------
public class Application {
public static void main(String[] args) {
Person person = new Person();
person.name="张三"; //外部是可以直接访问并且给他的属性赋值
}
}
如果是封装好的类的属性都是private修饰的,比如
public class Person {
private String name; //属性私有
public int age;
}
----------------------------------------------
public class Application {
public static void main(String[] args) {
Person person = new Person();
person.name="张三"; //此处的name会报红,提示'name' has private access in 'Person',意思是私有属性禁止访问
person.age = 0; //因为是public修饰,所以可以直接访问并赋值
}
}
所以需要提供一些可以访问这个这种私有属性的方法,所以提供了一种public修饰的set/get方法,get表示获取这个数据,set表示给这个属性设置值。
get/set方法也可以使用重载,但是正常情况下不会使用重载的,一般使用构造器重载多一点,
然后关于重载的题外话最经典的就是println源码,他的重载比如
public void println(boolean x),public void println(char x) ,这八大基本数据类型作为形参。
回到正题,封装类如下,getName与setName方法遵循驼峰命名规则,idea通过alt+insert自动生成。
public class Person {
private String name;
public String getName() {
return name;
}
/*
这个name是形式参数,通过Application类使用setName传值,
然后这个name赋值给this.name,this.name就表示Person实体类的name,
所以this.name=name这种写法的意思就是这个原因,而且set方法是void,没有返回值,
只是单单的赋值,获取值由上方get方法负责,由此成功隐藏了类的属性,只能通过提供的set/get方法访问属性。
*/
public void setName(String name) {
this.name = name;
}
}
--------------------------------------------------------------------------------
public class Application {
public static void main(String[] args) {
Person person = new Person();
person.name="张三"; //错误写法
person.setName("张三"); //正确写法
System.out.println(person.getName()); //张三
}
}
另外还可以通过内部封装来提高程序的安全性,直接在一些特殊属性上做些业务逻辑判断,比如
public class Person {
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
if(age>120||age<0){ //不合法
this.age=-1;
}else{
this.age = age;
}
}
}
------------------------------------------------------
public class Application {
public static void main(String[] args) {
Person person = new Person();
person.setAge(150); //不合法
System.out.println(person.getAge()); //-1
}
}
另外对于逆向工程生成的实体类,都是封装好的,这跟mybatis是单例模式有关系。
关于private关键字小结
private修饰的属性是通过get/set方法来访问的,那么同理它修饰方法的时候也是如此,不能直接访问,虽说能继承,但是子类继承过来之后,也不能对它进行直接引用访问,那是父亲的私有部分,只能对父类中的成员开放。一般情况下,父类中会有公用接口get、set之类的方法,可以通过这个方法进行访问。
public class Demo {
public static void main (String args[]){
B b = new B();
b.getShow();
}
}
//父类
class A{
private void show () {
System.out.println("父类私有方法");
}
public void getShow() {
show();
}
}
//子类
class B extends A{
}
/*
父类私有方法
*/
5.Java继承
继承是java的核心,是对宏观的把握,封装是对底层的操作,包括多态都离不开继承。
子类继承父类,就会拥有父类的所有方法;在Java中,所有的类都是直接或间接继承Object类,只是很多时候把extends Object给省略了而已;
java中只有单继承,没有多继承,但是接口可以多继承;单继承指一个儿子只能由一个爸爸,亲生的那种,但是一个爸爸可以有多个儿子;
前提要么父类的属性是public,可以直接student.name=""这样去赋值;如果是private修饰,则必须通过set/get方法赋值与获取值,这也是继承与封装的一种关系。
比如,Person是父类,Student是派生类。
public class Person /*extends Object */{
public void say(){
System.out.println("你好明天");
}
}
------------------------------------------
public class Student extends Person {
}
-----------------------------------------
public class Application {
public static void main(String[] args) {
Student student = new Student();
student.say(); //能正常输出
}
}
6.Java继承之super与this
super是父类的,this是当前类的;
public class Person {
protected String name = "父类"; //这里父类不用private修饰了,因为下面子类要用
}
-----------------------------------------
public class Student extends Person {
private String name = "子类";
public void test(String name){
System.out.println(name); //我是形参
System.out.println(this.name); //子类
System.out.println(super.name); //父类
}
}
--------------------------------------------------
public class Application {
public static void main(String[] args) {
Student student = new Student();
student.test("我是形参");
}
}
上图Student类中name表示传递过来的形参值,this.name代表当前类的属性,super.name表示当前类继承的那个父类的属性name。所以通过super关键字可以调用父类的属性。
同理方法也可以这样调用,如下(如果方法用private修饰,则无法被子类调用,因为私有的方法无法被调用,只允许所在类使用
)
public class Person {
public void print(){ //题外话,此处如果用private修饰,则无法被Student类调用,因为私有的方法无法被调用,只允许所在类使用
System.out.println("父类的方法");
}
}
------------------------------------------------
public class Student extends Person {
public void print(){
System.out.println("子类的方法");
}
public void test(){
print(); //子类的方法
this.print(); //子类的方法
super.print(); //父类的方法
}
}
------------------------------------------------
public class Application {
public static void main(String[] args) {
Student student = new Student();
student.test();
}
}
此处另一个知识点,称为隐藏的代码,上方的Application类是使用的是无参的实例化代码,所以Student与Person类不需要显式定义无参构造函数,但是这个无参的构造函数实际上也是被调用了的,
本文章知识点2讲了java构造器,new 本质是在调用构造方法,初始化对象的值;所以想知道到底是父类还是子类的构造器先被调用呢?结果是父类的构造器先执行,测试代码如下:
public class Person {
public Person(){
System.out.println("父类的构造器");
}
}
------------------------------------------
public class Student extends Person {
public Student(){
System.out.println("子类的构造器");
}
}
--------------------------------------------
public class Application {
public static void main(String[] args) {
Student student = new Student();
/*此类输出结果:
父类的构造器
子类的构造器 */
}
}
可以看到上方只new了一个子类Student,但是却是先调用了父类的无参构造,所以可以判断出Student类的无参构造器中肯定隐藏了一个调用父类无参构造器的方法,而且这个方法是在
System.out.println("子类的构造器");之前执行,这个代码就是super(); 所以调用父类的构造器必须要放在子类构造器的第一行。
所以Student类实际上就是
public class Student extends Person {
public Student(){
super(); //隐藏代码,调用了父类的无参构造,此处不写也是默认调用父类的无参构造,也可调用父类的有参构造,比如super("张三");
//this(); //this()意思是调用本类的无参构造,但是super与this不能同时调用构造方法,因为两者都要求放在第一行,所以不可同时出现
System.out.println("子类的构造器");
//super();
//如果放在这会提示:Call to 'super()' must be first statement in constructor body
//意思是对“super()”的调用必须是构造函数体中的第一条语句
}
}
这个super与this关键字经常出现在各种jar包的源码里,所以必须了解,不至于看到啥也不明白。
总结:
- super调用父类的构造方法,必须在构造方法的第一个
- super必须只能出现在子类的方法或者构造方法中
- super和this不能同时调用构造方法
- this表示本身调用的这个对象
- super代表父类对象的应用
- this没有继承也可以使用,super只能在继承条件才可以使用
- this()表示本类的构造,super()表示父类的构造
7.Java方法重写
重写都是针对方法的,是从继承角度来实现的,父类的成员方法只能被它的子类重写,重写与属性无关。另外重写跟静态方法没有关系,代码如下。
public class Person {
public static void test(){ //使用static修饰
System.out.println("父类");
}
}
--------------------------------------------------
public class Student extends Person {
public static void test(){
System.out.println("子类");
}
}
---------------------------------------------------
public class Application {
public static void main(String[] args) {
Student student = new Student();
student.test();
Person person = new Student(); //父类的引用指向子类
person.test();
}
}
/* 输出:
子类
父类
*/
上面的代码不叫重写,静态方法与非静态方法区别很大,重写只与非静态,非private,非final修饰的方法有关;
静态方法可以被继承,但是,不能被覆盖,即重写。如果父类中定义的静态方法在子类中被重新定义,那么在父类中定义的静态方法将被隐藏。可以使用语法:父类名.静态方法调用隐藏的静态方法。
如果父类中含有一个静态方法,且在子类中也含有一个返回类型、方法名、参数列表均与之相同的静态方法,那么该子类实际上只是将父类中的该同名方法进行了隐藏,而非重写。换句话说,父类和子类中含有的其实是两个没有关系的方法,它们的行为也并不具有多态性
因此,通过一个指向子类对象的父类引用变量来调用父子同名的静态方法时,只会调用父类的静态方法。
如果上方的test去掉static则输出结果都是“子类”。
总结:重写需要有继承关系,子类重写父类的方法;方法名必须相同,形参列表不能改变,方法体(就是方法里的代码)可以不同;形参列表不能改变,返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同);修饰符:子类重写方法的范围可以扩大但不能缩小;抛出的异常可以缩小但不能扩大,例如 父类的一个方法申明了一个检查异常 IOException,但是在重写这个方法的时候不能抛出 Exception 异常,因为 Exception 是 IOException 的父类,只能抛出 IOException 的子类异常。声明为 final 的方法不能被重写;构造方法不能被重写;
子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private,final,static 的方法;子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法;如果不能继承一个类,则不能重写该类的方法。
为什么需要重写:父类的功能,子类不一定需要或者不一定满足!
8.Java多态
多态是方法的多态,属性不存在多态的说法。多态存在的条件:有继承关系,子类重写父类方法,父类引用指向子类对象。
摘自:java提高篇(四)-----理解java的三大特性之多态 - chenssy - 博客园
封装隐藏了类的内部实现机制,可以在不影响使用的情况下改变类的内部结构,同时也保护了数据。对外界而已它的内部细节是隐藏的,暴露给外界的只是它的访问方法。
继承是为了重用父类代码。两个类若存在IS-A的关系就可以使用继承。,同时继承也为实现多态做了铺垫。那么什么是多态呢?多态的实现机制又是什么?请看我一一为你揭开:
所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
比如你是一个酒神,对酒情有独钟。某日回家发现桌上有几个杯子里面都装了白酒,从外面看我们是不可能知道这是些什么酒,只有喝了之后才能够猜出来是何种酒。你一喝,这是剑南春、再喝这是五粮液、再喝这是酒鬼酒….在这里我们可以描述成如下:
酒 a = 剑南春
酒 b = 五粮液
酒 c = 酒鬼酒
这里所表现的的就是多态。剑南春、五粮液、酒鬼酒都是酒的子类,我们只是通过酒这一个父类就能够引用不同的子类,这就是多态——我们只有在运行的时候才会知道引用变量所指向的具体实例对象。
诚然,要理解多态我们就必须要明白什么是“向上转型”。在继承中我们简单介绍了向上转型,这里就在啰嗦下:在上面的喝酒例子中,酒(Win)是父类,剑南春(JNC)、五粮液(WLY)、酒鬼酒(JGJ)是子类。我们定义如下代码:
JNC a = new JNC();
对于这个代码我们非常容易理解无非就是实例化了一个剑南春的对象嘛!但是这样呢?
Wine a = new JNC();
在这里我们这样理解,这里定义了一个Wine 类型的a,它指向JNC对象实例。由于JNC是继承与Wine,所以JNC可以自动向上转型为Wine,所以a是可以指向JNC实例对象的。这样做存在一个非常大的好处,在继承中我们知道子类是父类的扩展,它可以提供比父类更加强大的功能,如果我们定义了一个指向子类的父类引用类型,那么它除了能够引用父类的共性外,还可以使用子类强大的功能。
但是向上转型存在一些缺憾,那就是它必定会导致一些方法和属性的丢失,而导致我们不能够获取它们。所以父类类型的引用可以调用父类中定义的所有属性和方法,对于只存在于子类中的方法和属性它就望尘莫及了。
代码如下,根据上面的java重写,在test方法里又调用了另一个方法test2,然后test方法不符合java重写的思想,因为父类没有形参列表,而子类有;test2符合java重写,即子类重写父类的方法。
public class Person {
public void test(){
System.out.println("父类的test");
test2();
}
public void test2(){
System.out.println("父类的test2");
}
}
--------------------------------------------------------
public class Student extends Person {
public void test(String a){
System.out.println("子类的test");
test2();
}
/**
* 子类重写父类方法
* 指向子类的父类引用调用test2时,必定是调用该方法
*/
public void test2(){
System.out.println("子类的test2");
}
}
---------------------------------------------------------
public class Application {
public static void main(String[] args) {
Person a = new Student(); //父类的引用指向子类
a.test();
}
}
/*
父类的test
子类的test2
*/
从程序的运行结果中我们发现,person.test()首先是运行父类Person中的test().然后再运行子类Student中的test2()。
分析:在这个程序中子类重载了父类的方法test(),重写test2(),而且重载后的test(String a)与 test2()不是同一个方法,由于父类中没有该方法,向上转型后会丢失该方法,所以执行子类的父类类型引用是不能引用test(String a)方法。而子类重写了test2() ,那么指向子类的父类引用会调用子类中test2()方法。所以对于多态我们可以总结如下:
指向子类的父类引用由于向上转型了,它只能访问父类中拥有的方法和属性,而对于子类中存在而父类中不存在的方法,该引用是不能使用的,尽管是重载该方法。若子类重写了父类中的某些方法,在调用该些方法的时候,必定是使用子类中定义的这些方法(动态连接、动态调用)。
对于面向对象而已,多态分为编译时多态和运行时多态。其中编辑时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的函数,通过编辑之后会变成两个不同的函数,在运行时谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是我们所说的多态性。
9.instanceof和类型转换
instanceof在绝大多数情况下并不是推荐的做法,应当好好利用多态;
instanceof是Java中的二元运算符,左边是对象,右边是类;左边的对象不能是基础数据类型,否则会报错;
instanceof 严格来说是Java中的一个双目运算符,用来测试一个对象是否为一个类的实例,用法为:boolean
result = obj
instanceof
Class
其中 obj 为一个对象,Class 表示一个类或者一个接口,当 obj 为 Class 的对象,或者是其直接或间接子类,或者是其接口的实现类,结果result 都返回 true,否则返回false。
如下,object指向Student类,Student类继承自Person,则object与Person为true.
public class Person {
}
-------------------------------
public class Teacher extends Person {
}
----------------------------------------
public class Student extends Person {
}
-------------------------------------------
public class Application {
public static void main(String[] args) {
Object object = new Student(); //这里object引用指向了Student类,则可以视作是Student的实例化对象
System.out.println(object instanceof Person); //true,
System.out.println(object instanceof Teacher); //false
System.out.println(object instanceof Student); //true
System.out.println(object instanceof Object); //true
System.out.println(object instanceof String); //false
}
}
再例如:
public class Application {
public static void main(String[] args) {
Person person = new Student();
System.out.println(person instanceof Object); //true
System.out.println(person instanceof Student); //true
Person p = new Person();
System.out.println(p instanceof Object); //true
System.out.println(p instanceof Student); //false
}
}
10.Java类型转换
public class Person {
public void run(){
System.out.println("父类的run方法");
}
}
------------------------------------------------
public class Student extends Person {
public void go(){
System.out.println("子类的go方法");
}
}
----------------------------------------------------
public class Application {
public static void main(String[] args) {
Person person = new Student(); //这里是父类转换成子类,高转低,会丢失父类被子类所重写掉的方法
// person.go(); //编译不通过,报错
Student student = (Student)person; //将父类向下强转成子类就可以使用子类的方法了
student.run();
student.go();
//或者
( (Student) person).go();
( (Student) person).run();
}
}
/*
父类的run方法
子类的go方法
*/
题外话:另外,父类的引用指向子类,这个是向下转型,会丢失父类被子类重写的方法,也就是Java重写的思想,肯定只会调用子类重写的方法。
如果是向上转型,比如:
public class Application {
public static void main(String[] args) {
Student student = new Student();
Person person = student; //这里是子类转父类,可能会丢失自己本来的一些方法,也就是无法使用子类的独特方法
}
}
成员变量,静态方法看左边(成员变量和静态方法编译和运行都看左边);非静态方法:编译看左边,运行看右边。针对这种说法解释下,成员变量,静态方法,这个不叫多态,多态与属性没关系,与private,static,final修饰的方法也没关系,所以一致看左边,否则即使多态,或者重写,不是重载,那么看右边。
11.static关键字详解
static修饰方法的话则成为静态方法,修饰属性则成为静态属性。
我们可以一句话来概括:方便在没有创建对象的情况下来进行调用,也就是说:被static关键字修饰的不需要创建对象去调用,直接根据类名就可以去访问。
静态变量存放在方法区中,并且是被所有线程所共享的。静态变量随着类的加载而存在随着类的消失而消失,静态变量可以被对象(实例化后的对象)调用,也可以用类名调用。(推荐用类名调用)
被static修饰的成员变量叫做静态变量,也叫做类变量,说明这个变量是属于这个类的,而不是属于是对象,没有被static修饰的成员变量叫做实例变量,说明这个变量是属于某个具体的对象的。
1)静态属性
public class Student {
private static int age; //静态的变量
private double score; //非静态的变量
public static void main(String[] args) {
Student student = new Student();
student.age=1;
student.score=0.1;
//上面通过实例化后可以调用属性直接赋值
//但如果直接用类呢
Student.age=2; //如果是静态变量,推荐直接这样用类去访问
Student.score=2.5; //这里报错,提示不能引用非静态字段“score”
}
}
2)静态方法
public class Student {
public void run(){
}
public static void go(){
//静态方法会随类一起加载
}
public static void main(String[] args) {
Student student = new Student();
student.run();
Student.run(); //这里会报错,因为非静态的方法不能用类直接调用,必须想上面实例化后再调用
Student.go(); //go这个静态方法可以用类直接调用,与静态属性一样
go(); //由于静态方法就在这个类里,所以可以什么都不用写,直接访问
run(); //非静态方法就不行
}
3)代码块
public class Student {
static {
System.out.println("静态代码块"); //随类一起加载,所以会被最早执行,先输出,而且只会执行一次
}
public Student(){
System.out.println("构造方法"); //最后执行
}
{
System.out.println("匿名代码块"); //在构造方法之前输出
}
public static void main(String[] args) {
Student student = new Student();
System.out.println("=================");
Student student1 = new Student();
}
/*
静态代码块
匿名代码块
构造方法
=================
匿名代码块
构造方法
*/
}
如上第二次输出没有了静态方法,因为static只执行一次,而其他的只要对象调用了就会执行。
4)拓展,静态导入包
import static java.lang.Math.random;
import static java.lang.Math.PI;
public class Student {
public static void main(String[] args) {
System.out.println(Math.random());
System.out.println(random()); //导入静态包后可以直接用,不需要Math.了
System.out.println(PI);
}
}
12.抽象类
- abstract修饰符可以用来修饰方法也可以修饰类,如果修饰方法,那么该方法就是抽象方法;如果修饰类,那么该类就是抽象类。抽象类种可以没有抽象方法,但是有抽象方法的类一定要声明为抽象类。
- 抽象类,不能使用new关键字来创建对象,它是用来让子类继承的。
- 抽象方法,只有方法的声明,没有方法的实现,它是用来让子类实现的,这是一种约束,就像法律一样,必须要这样遵守。
- 子类继承抽象类,那么就必须要实现抽象类没有实现的抽象方法,否则该子类也要声明为抽象类。
public abstract class Person {
//抽象方法,只有方法的名字,没有方法的实现
public abstract void something();
}
----------------------------------------------------
public class Student extends Person{
//抽象类的所有方法,继承了它的子类,都必须要实现它的方法,但如果子类是抽象类,则不需要实现父类的抽象方法
@Override
public void something() {
}
}
13.接口的定义与实现
接口的本质就是契约,就像人间法律一样,制定好后大家都要遵守,声明接口的关键子是interface,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)。
接口特点: 就像一个类一样,一个接口也能够拥有方法和属性,但是在接口中声明的方法默认是抽象的,默认是public abstract。接口不能实例化,因为它不存在构造方法。
- 接口指明了一个类必须要做什么和不能做什么,相当于类的蓝图。Java不像C++一样支持多继承,所以Java可以通过实现接口来弥补这个局限。
- 一个接口就是描述一种能力,比如“运动员”也可以作为一个接口,并且任何实现“运动员”接口的类都必须有能力实现奔跑这个动作(或者implement move()方法),所以接口的作用就是告诉类,你要实现我这种接口代表的功能,你就必须实现某些方法,我才能承认你确实拥有该接口代表的某种能力。
- 如果一个类实现了一个接口中要求的所有的方法,然而没有提供方法体而仅仅只有方法标识,那么这个类一定是一个抽象类。(必须记住:抽象方法只能存在于抽象类或者接口中,但抽象类中却能存在非抽象方法,即有方法体的方法。接口是百分之百的抽象类)。
为了声明一个接口,我们使用interface这个关键字,在接口中的所有方法都必须只声明方法标识,而不要去声明具体的方法体,因为具体的方法体的实现是由继承该接口的类来去实现的,因此,接口并不用管具体的实现。接口中的属性默认为public static final修饰,一般没人会在一个接口里定义一个属性.一个类实现这个接口必须实现这个接口中定义的所有的抽象方法。
//使用interface关键字,接口需要有实现类
public interface UserService {
//接口的方法默认是public abstract,默认就是抽象的
void add(String name);
void delete(String name);
//(public static final) int age = 99; //属性默认是public static final修饰
}
-------------------------------------------------------
public interface TimeService {
void run();
}
------------------------------------------------------
//类是通过implements来实现接口,它可以实现多个接口
//实现了接口的类,就必须实现接口的所有方法,有一个方法没有实现就会报错
public class UserServiceImpl implements UserService,TimeService {
@Override
public void add(String name) {
}
@Override
public void delete(String name) {
}
@Override
public void run() {
}
}
这里同时实现了两个接口,这也可以说实现了(伪)多继承,就像伪分布式集群,自己搭着玩玩,锻炼锻炼;抽象类使用extends,只能单继承,具有很大的局限性,使用不多,但是接口使用很常见,很多框架,都是写了很多接口然后去实现业务功能。
题外话:很多人都会Java,但很少人能够做到架构师,因为不具备抽象的思维,这个非常难。
接口和抽象类有什么区别?
实现:抽象类的子类使用 extends 来继承;接口必须使用 implements 来实现接口。
构造函数:抽象类可以有构造函数;接口不能有。
main 方法:抽象类可以有 main 方法,并且我们能运行它;接口不能有 main 方法。
实现数量:类可以实现很多个接口;但是只能继承一个抽象类。
访问修饰符:接口中的方法默认使用 public 修饰;抽象类中的方法可以是任意访问修饰符。
14.Java内部类
内部类就是在一个类的内部再定义一个类,比如,A类中定义一个B类,那么B类相对A类来说就称为内部类,而A类相对B类来说就是外类了。
一个Java类里只允许有一个public class,但允许有多个class类,有内部类的类除外,因为内部类可以用public class
成员内部类以及静态内部类
public class Outer {
private int id=10;
public void out(){
System.out.println("这是外部类的方法");
}
public class Inner{
public void in(){
System.out.println("这是内部类的方法");
}
//获得外部类的私有属性
public void getId(){
System.out.println(id);
/*这是一个很强大的功能,可以访问到外部类的私有属性
另外如果该getId方法使用static修饰,这叫静态内部类,则这里无法获取到id,因为静态方法先加载,而id
还没有加载,所有无法访问到
*/
}
}
}
--------------------------------------------------------------------
public class Application {
public static void main(String[] args) {
//通过这个外部类来实例化这个内部类
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.getId();
}
}
局部内部类,写在方法里
public class Application {
//局部内部类
public void method(){
class Inner{
public void In(){
}
}
}
}
内部类高级玩法,不建议这样写,只是扩展眼界
其中有匿名内部类,即没有实例化类就实现了具体的eat方法,匿名内部类可以用在类、抽象类、接口上
public class Application {
public static void main(String[] args) {
new Apple().eat(); //没有名字初始化类,不用将实例保存到变量中,这就是匿名内部类
/*UserServices userServices =*/ new UserServices() { //匿名内部类用在接口上
@Override
public void run() {
}
}; //这里记得加分号
}
static class Apple { //由于上方在main方法里调用内部类所以需要用static修饰
public void eat() {
System.out.println("1");
}
}
interface UserServices {
void run();
}
}
15.Error和Exception
区别:Error通常是灾难性的致命的错误,是程序无法控制和处理的,当出现这些异常时,Java虚拟机(JVM)一般会选择终止线程;Exception通常情况下是可以被程序处理的,并且在程序中应该尽可能的去处理这些异常。
异常处理五个关键字:try,catch,finally,throw,throws
异常捕获示例:捕获某一行代码idea快捷键为ctrl+alt+t
public class Application {
public static void main(String[] args) {
int a=1;
int b=0;
try { //try里面的代码相当于监控区域
System.out.println(a/b);
} catch (Error e){ //可以捕获多个异常,类似if--else if--else if--else
System.out.println("error");
} catch (Exception e){ //这里多个catch需要注意必须把范围较大的异常放在最后,层层递进
System.out.println("Exception");
} catch (Throwable e){ //用来捕获定义的异常,这里属于Exception异常,catch里的异常为想要捕获的异常类型
System.out.println("Throwable");
} finally { //用来处理善后工作,不管有没有异常都会执行它的代码块,比如关闭资源,连接。。。
System.out.println("fianlly");
}
}
}
/*
Exception
fianlly
*/
throw,用在方法中主动抛出异常
public class Application {
public static void main(String[] args) {
int a=1;
int b=0;
try {
if(b==0){
throw new ArithmeticException(); //主动抛出异常,一般在方法中使用
}
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}
}
/*
java.lang.ArithmeticException
*/
------------------------------------------------------------------------------------------
throws,用在方法上主动抛出异常
如果方法中无法处理一个异常,即没有捕获到,那么可以在方法上抛出异常
public class Application {
public void test(int a,int b) throws ArithmeticException{
System.out.println(a/b);
}
public static void main(String[] args) {
try {
new Application().test(1,0);
} catch (ArithmeticException e) {
System.out.println("捕获到了");
} finally {
}
}
}
/*
捕获到了
*/
16.自定义异常总结
大部分情况不需要自定义异常,但这个东西需要知道。
使用Java内置的异常类可以描述在编程时出现的大部分异常情况,除此之外,用户还可以自定义异常,用户自定义异常类,只需继承Exception类即可。
使用自定义异常类,大体可分为以下几个步骤:
- 创建自定义异常类
- 在方法中通过throw关键字抛出异常对象
- 如果在当前抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理;否则在方法的声明处通过throws关键字指明要抛出给方法调用者的异常,继续进行下一步操作
- 在出现异常方法的调用者中捕获并处理异常
//自定义的异常类需要继承Exception类
public class MyException extends Exception {
private int detail;
public MyException(int a){
this.detail=a;
}
//toString异常的打印信息
@Override
public String toString() {
return "MyException{" +
"detail=" + detail +
'}';
}
}
----------------------------------------------------------------
public class Application {
static void test(int a) throws MyException{
System.out.println("传递的参数为"+a);
if (a>10){
throw new MyException(a); //抛出
}
System.out.println("OK"); //throw抛出异常后这里的代码没有执行
}
public static void main(String[] args) {
try {
test(11);
} catch (MyException e) {
System.out.println("MyException>-"+e);
}
}
}
/*
传递的参数为11
MyException>-MyException{detail=11}
*/
实际应用中的经验总结:
- 处理运行时异常时,采用逻辑去合理规避同时辅助try-catich处理
- 在多重catch块后面,可以加一个catch(Exception e)来处理可能会被遗漏的异常
- 对于不确定的代码,也可以加上try-catch,处理潜在的异常
- 尽量去处理异常,切忌只是简单的调用printStackTrace()去打印输出
- 具体如何处理异常,要根据不同的业务需求和异常类型去决定
- 尽量添加finally语句块去释放占用的资源