【狂神说Java】Java零基础学习视频通俗易懂
HelloWorld 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # li @ evpower in ~/humble/blog/source /_posts/java on git:master x [18:27:56] $ cat Hello.java public class Hello{ public static void main(String[] args){ System.out.print("Hello World!"); } } # li @ evpower in ~/humble/blog/source /_posts/java on git:master x [18:28:02] $ javac Hello.java # li @ evpower in ~/humble/blog/source /_posts/java on git:master x [18:28:12] $ ls Hello.class Hello.java # li @ evpower in ~/humble/blog/source /_posts/java on git:master x [18:28:13] $ java Hello Hello World!% # li @ evpower in ~/humble/blog/source /_posts/java on git:master x [18:28:18] $
Java数据类型分类 基本数据类型primitive type 1 2 3 4 5 6 7 8 byte num1 = 20; short num2 = 20; int num4 = 20; long num5 = 20L; //可以用L或l,但l容易跟1弄混 float num6 = 50.1F; double num7 = 3.141592653; char num3 = 'a'; boolean flag = true; //false
引用数据类型reference type(类,接口,数组)
进制 1 2 3 0b10010010 //二进制(0b) 050 //八进制(0) 0x1A //十六进制(0x)
避免使用浮点数比较,建议用类(BigDecimal) 1 2 3 4 5 6 7 float f = 0.1F; double d = 1.0/10; if(f == d) //false float d1 = 123123123123123123F; float d2 = d1 + 1; if(d1 == d2) //true
类型转换 1 2 3 4 5 6 7 8 //byte,short,char -> int -> long -> float -> double //低->高 (高->低:强制类型转换 低->高:自动类型转换) int i = 128; byte b = (byte)i; //-128 强制转换 内存溢出 double d = i; //128 自动转换 int j = (int)23.7; //23 int k = (int)-45.89f; //-45 int money = 10_0000_0000; //1000000000, JDK7新特性,可以用下划线分割数字 long total = money * 12; //int型溢出,改为 long total = (long)money * 12L;
注意:
不能对boolean进行转换
不能把对象类型转成不相干的类型(把猪转人-no,把男转女-yes)
把高容量转低容量时,强制转换
转换时可能存在内存溢出或精度问题
变量
类变量:(static)初始默认值是null
实例变量:从属于对象,若不初始化,默认值是 0/0.0
局部变量:必须声明和初始化
1 2 3 4 5 6 7 8 public class Variable{ static int allClicks = 0; //类变量 String str="hello world"; //实例变量 public void method(){ int i=0; //局部变量 } }
常量 1 2 3 4 5 final double PI=3.14; //常量名建议使用全大写 /* 修饰符关键字不分先后顺序 */ static final double PI=3.14; final static double PI=3.14;
变量的命名规范
类成员变量:首字母小写驼峰命名法
局部变量:首字母小写驼峰命名法
常量:大写字母和下划线
类名:首字母大写驼峰命名法
方法名:首字母小写驼峰命名法
运算符
算数运算符+,-,*,/,%,++,--
赋值运算符=
关系运算符>,<,>=,<=,==,!=,instanceof
逻辑运算符&&,||,!
位运算符&,|,^,~,>>,<<,>>>
条件运算符? :
扩展赋值运算符+=,-=,*=,/=
IDEAtips:Ctrl+d
是复制当前行到下一行
短路运算
1 2 int c=5; boolean d = (c<4)&&(c++<4); //d是false,c是5
字符串连接符号+
1 2 3 4 int a=10; int b=20; ""+a+b //"1020" a+b+"" //"30"
包机制 是为了更好地组织类,并且区别类名的命名空间。常用公司域名倒置作为包名:com.baidu.www
。package用来封装包,import用来导入包(import需要放置在package后面)
1 2 package pkg1[. pkg2[. pkg3... ]]; import package1[.package2...].(classname|*); //*是通配符,表示导入包下所有的类
IDEAtips:需要用外部包的变量或方法时报错忘记import,按Alt+Enter
可查询包方便导入
JavaDoc javadoc命令是用来生成自己API文档的
1 2 3 4 5 6 7 参数信息 @author 作者名 @version 版本号 @since 指明需要最早适用的jdk版本 @param 参数名 @return 返回值情况 @throws 异常抛出情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.humble.base; /** * @author humble * @version 1.0 * @since 1.8 */ public class Hello { String name; /** * @param name * @return * @throws Exception */ public String test(String name) throws Exception{ return name; } }
在idea里右键Hello的类,Show in Files
,在当前目录打开命令行
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 # li @ evpower in ~/IdeaProjects/StudyJAVA/Hello/src/com/humble/base [23:19:05] $ ls Hello.java # li @ evpower in ~/IdeaProjects/StudyJAVA/Hello/src/com/humble/base [23:19:07] $ javadoc -encoding UTF-8 -charset UTF-8 Hello.java 正在加载源文件Hello.java... 正在构造 Javadoc 信息... 标准 Doclet 版本 1.8.0_265 正在构建所有程序包和类的树... 正在生成./com/humble/base/Hello.html... Hello.java:17: 警告 - @return 标记没有参数。 正在生成./com/humble/base/package-frame.html... 正在生成./com/humble/base/package-summary.html... 正在生成./com/humble/base/package-tree.html... 正在生成./constant-values.html... 正在构建所有程序包和类的索引... 正在生成./overview-tree.html... 正在生成./index-all.html... 正在生成./deprecated-list.html... 正在构建所有类的索引... 正在生成./allclasses-frame.html... 正在生成./allclasses-noframe.html... 正在生成./index.html... 正在生成./help-doc.html... 1 个警告 # li @ evpower in ~/IdeaProjects/StudyJAVA/Hello/src/com/humble/base [23:19:47] $ ls allclasses-frame.html Hello.java package-list allclasses-noframe.html help-doc.html script.js com index-all.html stylesheet.css constant-values.html index.html deprecated-list.html overview-tree.html # li @ evpower in ~/IdeaProjects/StudyJAVA/Hello/src/com/humble/base [23:21:36] $
用浏览器打开index.html
即可
Scanner对象 用于获取用户输入
Demo01
:使用next()
1.一定要读取到有效字符后才可以结束输入
2.对输入有效字符前遇到的空白,next()方法会自动将其去掉
3.只有输入有效字符后才将其后面输入的空白作为分隔符或结束符
4.next()不能得到带有空格的字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.humble.scanner; import java.util.Scanner; public class Demo01 { public static void main(String[] args) { //创建扫描器对象,用于接收键盘数据 Scanner scanner = new Scanner(System.in); System.out.println("使用next方式接收:"); if(scanner.hasNext()){ //判断用户有没有输入字符串 //使用next方式接收 String str = scanner.next(); //程序会等待用户输入完毕 System.out.println("输入内容为:" + str); } //凡是属于IO流的类,如果不关闭会一直占用资源,要养成良好习惯,用完就关 scanner.close(); } }
运行Demo01(只输出了空格前的单词,再调用next()可获得下一个单词)
1 2 3 4 5 使用next方式接收: hello world! 输入内容为:hello Process finished with exit code 0
Demo02
:使用nextLine()
1.以Enter为结束符,即返回的是回车之前的全部字符
2.可以获得空白字符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.humble.scanner; import java.util.Scanner; public class Demo02 { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println("使用nextline方式接收:"); if(scanner.hasNextLine()){ //使用nextline方式接收 String str = scanner.nextLine(); //程序会等待用户输入完毕 System.out.println("输入内容为:" + str); } scanner.close(); } }
运行Demo02(输出整行)
1 2 3 4 5 使用nextline方式接收: hello world! 输入内容为:hello world! Process finished with exit code 0
反编译 Project Structure
-Project Settings
-Project
-Project comcompiler output
打开对应目录,子目录里面会有.class
文件,把class文件拷贝到项目目录,然后导入到idea即可看到源码
IDEAtips:输入100.for
即可补全for循环100次
方法的重载 重载就是在一个类中,有相同的方法名称,但形参不同的方法。
重载规则:
1.方法名称必须相同
2.参数列表必须不同(个数不同、类型不同、参数排列不同等)
3.方法的返回类型可以相同也可不同
4.仅仅返回类型不同不足以成为方法的重载
实现理论:方法名称相同时,编译器会根据调用方法的参数个数、参数类型等去逐个匹配,以选择对应的方法,如果匹配失败,则编译器报错。
命令行传参 1 2 3 4 5 6 7 8 9 package com.humble.base; public class Hello { public static void main(String[] args) { for (int i = 0; i < args.length; i++) { System.out.println("arg[" + "] is " + args[i]); } } }
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 # li @ evpower in ~/IdeaProjects/StudyJAVA/Hello/src/com/humble/base [16:19:41] $ ls Hello.java # li @ evpower in ~/IdeaProjects/StudyJAVA/Hello/src/com/humble/base [16:19:41] $ javac Hello.java # li @ evpower in ~/IdeaProjects/StudyJAVA/Hello/src/com/humble/base [16:19:47] $ ls Hello.class Hello.java # li @ evpower in ~/IdeaProjects/StudyJAVA/Hello/src/com/humble/base [16:20:07] $ cd ../../../ # li @ evpower in ~/IdeaProjects/StudyJAVA/Hello/src [16:20:12] $ ls com # li @ evpower in ~/IdeaProjects/StudyJAVA/Hello/src [16:20:13] $ java com.humble.base.Hello # li @ evpower in ~/IdeaProjects/StudyJAVA/Hello/src [16:20:28] $ java com.humble.base.Hello this is argv arg[] is this arg[] is is arg[] is argv # li @ evpower in ~/IdeaProjects/StudyJAVA/Hello/src [16:20:38] $
可变参数
JDK1.5开始,java支持:给一个方法传递同类型的可变参数
在方法声明中,在指定参数类型后面加一个省略号(…)
一个方法中只能指定一个可变参数,它必须是方法的最后一个参数。任何普通的参数必须在它之前声明。
递归 递归结构包括两个部分:
递归头:什么时候不调用自身方法。如果没有头,将陷入死循环。
递归体:什么时候需要调用自身方法。
数组 1 2 3 dataType[] arrayRefVar; //首选 dataType arrayRefVar[]; //只是为了方便C/C++程序员 dataType[] arrayRefVar = new dataType[arraySize];
三种初始化
1 2 3 4 5 6 7 8 9 10 //静态初始化 int[] a = {1,2,3}; Man[] mans = {new Man(1,1), new Man(2,2)}; //动态初始化 int[] a = new int[2]; a[0] = 1; a[1] = 2; //数组默认初始化:数组是引用类型,它的元素相当于类的实例变量,因此数组一经分配空间,其中每个元素也被按照实例变量同样的方式被隐式初始化。
Arrays类 数组本身没有什么方法提供调用,但API中提供了工具类java.util.Arrays
对数据对象进行一些基本操作。Arrays
类中的方法都是Static
修饰的静态方法,使用时可以直接用类名调用,不需要使用对象来调用(想用对象也可以)。
给数组赋值用fill
方法
对数组排序用sort
方法,按升序
比较数组用equals
,比较元素值是否相等
查找数组元素用binarySearch
,对排好序的数组进行二分发查找
冒泡排序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public static int[] sort(int[] a){ int tmp = 0; boolean flag = false; //假设已经排好序,即无需位置交换 for (int i = 0; i < a.length - 1; i++) { for (int j = 0; j < a.length - 1 - i; j++) { if(a[j+1] < a[j]){ tmp = a[j]; a[j] = a[j+1]; a[j+1] = tmp; flag = true; //表示有位置交换 } } /* 确实没有发生位置交换,表示已经排好序 */ if(!flag){ break; } } return a; }
稀疏数组 当数组中大部分元素为0,或者为同一值时,可以使用稀疏数组来保存。处理方式是:
记录数组一共几行几列几个非0值
把具有不同值的元素和行列及值记录在一个小规模数组中,从而压缩数据
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 45 46 47 48 public class Hello { public static void main(String[] args) { int[][] array1 = new int[11][11]; array1[1][2] = 1; array1[2][3] = 2; for (int[] ints : array1) { //打印原数组 for (int anInt : ints) { System.out.print(anInt); } System.out.println(); } System.out.println("==开始转为稀疏数组=="); int sum = 0; for (int[] ints : array1) { //统计非0总数 for (int anInt : ints) { if(anInt != 0) { sum++; } } } //总长、总宽、非0总数 int[][] array2 = new int[sum+1][3]; array2[0][0] = array1[0].length; array2[0][1] = array1.length; array2[0][2] = sum; int count = 0; for (int i = 0; i < array1.length; i++) { for (int j = 0; j < array1[i].length; j++) { if(array1[i][j] != 0){ count++; array2[count][0] = i; array2[count][1] = j; array2[count][2] = array1[i][j]; } } } System.out.println("cow\trow\tvalue"); for (int[] ints : array2) { System.out.println(Arrays.toString(ints)); } System.out.println("==开始还原数组=="); int[][] array3 = new int[array2[0][0]][array2[0][1]]; for (int i = 1; i < array2.length; i++) { array3[array2[i][0]][array2[i][1]] = array2[i][2]; } for (int[] ints : array1) { for (int anInt : ints) { System.out.print(anInt); } System.out.println(); } } }
运行输出
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 00000000000 00100000000 00020000000 00000000000 00000000000 00000000000 00000000000 00000000000 00000000000 00000000000 00000000000 ==开始转为稀疏数组== cow row value [11, 11, 2] [1, 2, 1] [2, 3, 2] ==开始还原数组== 00000000000 00100000000 00020000000 00000000000 00000000000 00000000000 00000000000 00000000000 00000000000 00000000000 00000000000
面向对象
对于描述复杂的事物,为了从宏观上把握、从整体上合理分析,我们需要使用面向对象的思路来分析整个系统。但是具体到微观操作,仍然需要面向过程的思路取处理。
面向对象编程(Object-Oriented Programming),本质是:以类的方式组织代码,以对象的方式组织/封装数据。
三大特性:封装、继承、多态。
从认识的角度考虑是先有对象后有类。对象是具体的事物;类是针对对象的抽象。
从代码运行角度考虑是先有类后有对象。类是对象的模板。
static方法
带static
:方法在类定义时就已经加载,所以其它地方可以直接用类名
调用方法。
非static
:方法在对象定义时才加载,所以其它地方必须先实例化才能用实例(对象
)调用方法。
定义类时,在static
方法内调用自己的非static
方法会失败;在非static
方法内可以直接调用自己的非static
方法。
值传递和引用传递 java里面调用方法都是值传递
,不是引用传递
1 2 3 4 5 6 7 8 9 10 11 12 public class Hello { public static void main(String[] args) { Person person = new Person(); System.out.println(person.name); //未定义所以打印 null Hello.change(person); //传了对象(引用传递,址传递) System.out.println(person.name); //打印出 world } public static void change(Person person){ person.name = "world"; } } class Person{ String name; }
类和对象
类是一种抽象的数据类型,它是对某一类事物整体描述/定义,但不能代表某一个具体的事物。
对象是抽象概念的具体实例。
使用new关键字创建对象时,除了分配内存空间,还会给对象进行默认的初始化以及对类中构造器的调用。
构造器
类中的构造器也称构造方法,是在进行创建对象的时候必须要调用的,目的在于初始化值。
没有人为初始化则默认为:数字(0,0.0),char(u0000),boolean(false),引用(null)
构造器有两个特点:1.必须和类的名字相同,2.必须没有返回类型,也不能写void。
1 2 3 4 5 6 7 8 9 10 /* Filename: Application.java */ package com.oop.demo; public class Application { public static void main(String[] args) { //Person humble = new Person(); //Person()实际上是构造方法 Person humble = new Person("Humble"); System.out.println(humble.name); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 /* Filename: Person.java */ package com.oop.demo; public class Person { //即使留空,默认也会存在一个无参构造方法Person() /* String name; //显式定义无参构造方法 public Person(){ this.name = "hello"; } */ String name; /* 当底下定义了带参构造方法,那其它地方就无法使用无参构造方法来创建实例 如果想保留无参构造方法,只需显式定义一下无参构造方法即可 */ public Person(){ } //定义有参构造方法 public Person(String name){ this.name = name; } }
IDEAtips:Alt+Insert
-Constructor
-选择属性或不选属性-OK
即可光标处快速生成构造方法
创建对象内存分析 1 2 3 4 5 6 内存 ├── 栈(stack) └── 堆(heap) ├── new分配区 └── 方法区 └── 静态方法区
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Hello { public static void main(String[] args) { //new会在堆的new分配区申请一片内存并返回地址 //person变量保存在栈,它的值是一个指向堆的地址 Person person = new Person(); System.out.println(person.name); //未定义所以打印 null Hello.change(person); //传了对象(引用传递,址传递) System.out.println(person.name); //打印出 world } public static void change(Person person){ //person虽然是值传递,但传的却是地址值(引用值) //地址指向的是堆内new分配区的对象空间 person.name = "world"; } } class Person{ String name; }
封装
程序设计应该“高内聚,低耦合”。高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;低耦合是仅暴露少量的方法给外部使用。
信息隐藏:禁止别人直接访问对象中的数据,而应该通过操作接口来访问。
属性私有:get()或set()
封装的好处:
提高程序的安全性,保护数据
隐藏代码实现细节
统一接口
增加系统可维护性
IDEAtips:Alt+Insert
-Getter and Setter
-选择属性或不选属性-OK
即可光标处快速生成get()
和set()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package com.oop.demo; public class Student { private String name; private int age; private int id; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { //用set修改私有属性可以保护数据合法性 if(age > 120 || age < 0){ this.age = 3; } else { this.age = age; } } public int getId() { return id; } public void setId(int id) { this.id = id; } }
继承
继承的本质是对某一批类的抽象,从而实现对现实世界更好的建模。
extends
意思是扩展。子类是父类的扩展。
java中类只有单继承,没有多继承。
继承关系的两个类,一个为子类(派生类),一个为父类(基类)。子类继承父类,使用关键字extends来表示。子类和父类之间,从意义上讲应该具有”is a”的关系。
继承是类和类之间的一种关系。除此之外,类和类之间的关系还有依赖、组合、聚合
等。
1 2 3 4 public class Student extends Person { } //Student继承Person public class Student { //Student和person是组合关系 Person person; }
继承例程 1 2 3 4 5 6 7 8 9 10 11 12 /* Filename: Person.java */ package com.oop.demo; public class Person { //public:可以被子类继承 //protected: 可被子继承 //default:(不加修饰就是这个) //private:不可被子类继承(提供public get()/set()) protected int money = 10_0000_0000; public void say() { System.out.println("I am a Person"); } }
1 2 package com.oop.demo; public class Student extends Person { }
1 2 3 4 5 6 7 8 9 10 /* Filename: Application.java */ package com.oop.demo; public class Application { public static void main(String[] args) { Student s1 = new Student(); System.out.println(s1.money); //1000000000 s1.say(); //I am a Person } }
IDEAtips:Ctrl+h
可以打开继承树
java中所有的类都默认直接或间接继承Object类
super
super()调用父类的构造方法,必须在构造方法的第一行
super只能出现在’子类’的方法或构造方法中
super和this不能同时调用构造方法
用super调用父类 1 2 3 4 5 6 7 /* Filename: Person.java */ package com.oop.demo; public class Person { //如果修饰改为private,那么子类就无法用super直接调用 protected String name = "person1"; public void print(){ System.out.println("Person.print"); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 /* Filename: Student.java */ package com.oop.demo; public class Student extends Person { private String name = "student1"; public void test(String name){ System.out.println(name); //${name} System.out.println(this.name); //student1 System.out.println(super.name); //person1 } public void print(){ System.out.println("Student.print"); } public void test1(){ print(); //Student.print this.print();//Student.print super.print();//Person.print } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 /* Filename: Application.java */ package com.oop.demo; public class Application { public static void main(String[] args) { Student s1 = new Student(); s1.test("hello"); s1.test1(); } } /*运行输出 hello student1 person1 Student.print Student.print Person.print */
super构造器 1 2 3 4 5 6 7 /* Filename: Person.java */ package com.oop.demo; public class Person { public Person(){ System.out.println("Person.Person"); } public Person(String name){ System.out.println("Person.Person:"+name); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 /* Filename: Student.java */ package com.oop.demo; public class Student extends Person { public Student() { //隐藏调用,默认会先调用父类构造器super()。 //如果想显式调用super()则必须放在子类构造器第一行。 //若父类构造器有参,那么调用super()要带参,除非父类构造器重载 //super(); super("hello"); System.out.println("Student.Student"); } }
1 2 3 4 5 6 7 8 9 10 11 /* Filename: Application.java */ package com.oop.demo; public class Application { public static void main(String[] args) { Student s1 = new Student(); } } /*运行输出 Person.Person:hello Student.Student */
方法重写 为什么需要方法重写?(父类的功能,子类不一定需要或不一定满足)
方法重写注意:
需要继承关系,子类重写父类的方法;
只针对方法,不能是属性,并且是‘对象的方法(即非static
修饰的)’,不是‘类的方法’,;
方法名必须相同;
返回值与参数列表及类型必须相同(否则就是重载了);
修饰符:范围可以扩大但不能缩小(即父用public
修饰那么子重写时就不能用protected
或更小的来修饰),public
>protected
>default
>private
;正常情况不会是private
,因为这个方法不能用对象去调用,所以只要不是private
都可以被子重写。
抛出的异常:范围可以被缩小但不能扩大;ClassNotFoundException
(小)–>Exception
(大)
例子1:带static
方法(类的方法)该例子不是方法重写
1 2 3 4 5 6 /* Filename: B.java */ package com.oop.demo; public class B { public static void test(){ System.out.println("B.test"); } }
1 2 3 4 5 6 /* Filename: A.java */ package com.oop.demo; public class A extends B { public static void test(){ System.out.println("A.test"); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 /* Filename: Application.java */ package com.oop.demo; public class Application { public static void main(String[] args) { //类里面的static方法应该用类去调用,这里用对象去调用是为了测试 A a = new A(); a.test(); //最终调用类的方法 //父类的引用指向子类 B b = new A(); b.test(); //最终调用类的方法 } } /*运行输出如下,说明方法的调用只和定义的数据类型有关,跟new A()无关 A.test B.test */
例子2:不带static
方法(对象的方法)该例子是方法重写
1 2 3 4 5 6 /* Filename: B.java */ package com.oop.demo; public class B { public /*static*/ void test(){ System.out.println("B.test"); } }
1 2 3 4 5 6 7 8 9 10 /* Filename: A.java */ package com.oop.demo; public class A extends B { @Override //@表示注解,是有功能的注释。Override表示重写 public void test() { //super.test(); //默认是调用父类的方法,现在重写 System.out.println("A.test"); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 /* Filename: Application.java */ package com.oop.demo; public class Application { public static void main(String[] args) { A a = new A(); a.test(); //最终调用对象的方法 B b = new A(); b.test(); //最终调用对象的方法 } } /*运行输出如下,只要是new A()就是调用重写后的方法 A.test A.test */
IDEAtips:Alt+Insert
选Override Methods
类的方法优先级高于对象的方法。
例子1里面,static
修饰的是类方法。b.test()
调用的是B类
的test()
,(堆里面静态方法区)
例子2里面,无static
修饰是对象方法。b.test()
调用的是对象b
的test()
,但对象b
是A类
new出来的,(堆里面new分配区)
重载与重写区别:
重载是同一类里,方法名相同,参数个数或类型不同。
重写是子父类间,子类重写父类的方法,方法名及参数一模一样,方法体不一样。并且不能是类方法,即不能用static
修饰。
多态
同一个方法根据调用对象的不同而采取不同的行为方式。
其中调用对象的类型需要在执行过程中才能决定。
一个对象的实际类型是确定的(堆的new分配区),但是指向它的引用(栈)可以是其他类型(父类或关系类)
注意事项:
1.多态是针对方法的,不能是属性。
2.必须有父子关系(继承),否则引用不能转换,比如String s4 = new Student();
转换异常ClassCastException
3.存在条件:必须父子关系(继承);方法需要重写(只有运行时才知道调用父方法还是子重写的方法);父类引用指向子类;
不能被子重写的方法:
1.static
方法,属于类,不属于实例
2.final
常量,
3.private
方法(因为不能继承)
1 2 3 4 /* Filename: Person.java */ package com.oop.demo; public class Person { public void run(){ System.out.println("Person.run"); } }
1 2 3 4 5 6 7 8 9 /* Filename: Student.java */ package com.oop.demo; public class Student extends Person { @Override public void run() { System.out.println("Student.run"); } public void eat(){ System.out.println("Student.eat"); } }
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 /* Filename: Application.java */ package com.oop.demo; public class Application { public static void main(String[] args) { Student s1 = new Student(); Person s2 = new Student(); Object s3 = new Student(); //String s4 = new Student(); //报错,引用只能是同类或父类 //如果Student没有Override run方法,那两个都是Person.run //如果Student有Override run方法,那两个都是Student.run s1.run(); s2.run(); //s3.run(); //报错,Object没有run方法 s1.eat(); //s2.eat(); //报错,因为父(Person)没有子(Student)的方法(eat)所以不能调,可以先强转为Student再调用 /** * 所以,左边定义的类里有什么方法就只能调这些方法(自己的或继承的) * 如果这些方法已被子类重写那调用就是重写后的方法 */ } }
instanceof X instanceof Y
,Y 必须是 引用X所指向的实例 的父类/同类才为真
1 2 3 /* Filename: Person.java */ package com.oop.demo; public class Person { public void run(){ System.out.println("Person.run"); } }
1 2 3 /* Filename: Student.java */ package com.oop.demo; public class Student extends Person { }
1 2 3 /* Filename: Teacher.java */ package com.oop.demo; public class Teacher extends Person { }
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 /* Filename: Application.java */ package com.oop.demo; public class Application { public static void main(String[] args) { Object o = new Student(); System.out.println(o instanceof Student); //true System.out.println(o instanceof Person); //true System.out.println(o instanceof Object); //true System.out.println(o instanceof Teacher); //false System.out.println(o instanceof String); //false System.out.println("====="); Person p = new Student(); System.out.println(p instanceof Student); //true(说Person是Student不成立,但引用p指向的实例是Student) System.out.println(p instanceof Person); //true System.out.println(p instanceof Object); //true System.out.println(p instanceof Teacher); //false //System.out.println(p instanceof String); //编译报错 System.out.println("====="); Student s = new Student(); System.out.println(s instanceof Student); //true System.out.println(s instanceof Person); //true System.out.println(s instanceof Object); //true //System.out.println(s instanceof Teacher); //编译报错 //System.out.println(s instanceof String); //编译报错 } }
类的转换
1.类的转换是发生在栈上,也就是转换引用而已
2.把子类‘直接’转为父类,父类的引用(栈)指向子类的对象(堆),但是会丢失子类的方法
3.把父类‘强制’转为子类,子类的引用(栈)指向父类的对象(堆),编译报错或运行报错,因为子类引用(栈)里有些方法在父类对象(堆)内找不到
4.类的转换方便方法的调用,减少重复的代码
1 2 3 /* Filename: Person.java */ package com.oop.demo; public class Person { public void run(){ System.out.println("Person.run"); } }
1 2 3 /* Filename: Student.java */ package com.oop.demo; public class Student extends Person { public void go(){ System.out.println("Student.go"); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 /* Filename: Application.java */ package com.oop.demo; public class Application { public static void main(String[] args) { Person p = new Person(); //Student conversion2s1 = p; //编译报错,因为堆内的对象为Person(父),不能用栈内Student(子)引用去指向它,需要强转 //Student conversion2s2 = (Student) p; //编译通过但运行报错,原因同上 Student s = new Student(); Person conversion2p = s; //子类转父类 //conversion2p.go(); //编译报错,因为栈内引用Person没有go方法,也就是丢失了Student(子)的方法go Student reset2s = (Student)conversion2p; reset2s.go(); //打印Student.go,证明转为父类会丢失子类方法go,再转回子类又恢复了方法go } }
static 属性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 /* Filename:Student.java */ package com.oop.demo; public class Student { private static int age; private double score; public static void main(String[] args) { Student s1 = new Student(); Student.age = 10; //类成员 s1.age = 20; //此处访问的也是类成员 System.out.println(s1.age); //20 System.out.println(Student.age); //20 //Student.score = 10; //编译报错,类被加载时不会分配非staitc的属性,非static属性是属于对象的 s1.score = 20; } }
方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 /* Filename:Student.java */ package com.oop.demo; public class Student { public void run(){ go(); //对象方法内可以调用类的方法 } public static void go(){ //run(); //编译报错:类的方法内不可以调用对象的方法 } public static void main(String[] args) { //run(); //编译报错:类的方法内不可以调用对象的方法 go(); //类可以直接调用类自己的方法 } }
static代码块 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 /* Filename:Person.java */ package com.oop.demo; public class Person { { //2 :可以在里面赋初值 System.out.println("匿名代码块"); } static{ //1 静态代码块只执行/初始化一次 System.out.println("静态代码块"); } public Person() { //3 System.out.println("构造方法"); } public static void main(String[] args) { Person p1 = new Person(); System.out.println("===="); Person p2 = new Person(); /**运行输出 * 静态代码块 * 匿名代码块 * 构造方法 * ==== * 匿名代码块 * 构造方法 */ } }
外部package的函数导入 1 2 3 4 5 6 7 8 9 10 11 /* Filename:Test.java */ package com.oop.demo; import static java.lang.Math.random; import static java.lang.Math.PI; public class Test { public static void main(String[] args) { System.out.println(Math.random()); //用package去调用random System.out.println(random()); //也可以先import static java.lang.Math.random之后就可以直接调用 System.out.println(PI); } }
final类 用final
修饰的类是没有子类的。
抽象类
abstract
修饰符可以用来修饰方法也可以修饰类,如果修饰方法,那么就是抽象方法;修饰类那就是抽象类
抽象类中可以没有抽象方法,但是有抽象方法的类一定要声明为抽象类。
抽象类不能用new
关键字来创建对象,它是用来让子类继承的。
抽象方法,只有方法的声明,没有方法的实现,它是用来让子类实现的。
子类继承抽象类,那么就必须要实现抽象类里没有实现的抽象方法,否则该子类也要声明为抽象类。
1 2 3 4 5 6 7 8 9 10 11 /* Filename:Action.java */ package com.oop.demo; //抽象(abstract)类,不能被new出来,只能被继承 public abstract class Action { //抽象(abstract)方法如果: // 被"abstract修饰的子类"继承可以不写方法体; // 被"非abstract修饰的子类"继承需要写方法体。 public abstract void doSomething(); //抽象方法目的在于约束子类必须实现 public void go(){ System.out.println("Action.go"); } }
1 2 3 4 5 6 7 8 9 /* Filename:A.java */ package com.oop.demo; public abstract class A extends Action { //因为doSomething()是抽象方法,所以子类(A)必须重写/实现出来,如果子类(A)是抽象类则不用重写/实现 //@Override //public void doSomething() { } //Action.go()不是抽象方法,所以子类(A)可以重写/实现,也可以不重写/实现 }
javaTips:类是单继承,接口可以多继承
思考题:1.抽象类不能new
,那它有构造器吗?2.抽象类存在意义是什么?(游戏角色抽象出来,让子类继承去实现。提高开发效率,可扩展性高)
接口
普通类:只有具体实现
抽象类:具体实现和规范(抽象方法)都有
接口:只有规范(使得约束和实现分离,项目组长约定好接口,员工面向接口编程)
接口就是规范,定义是一组规则,体现了现实世界中“如果你是…则必须能…”的意思。如果你是天使,则必须能飞。如果你是汽车,则必须能跑。
接口本质是契约,就像我们人间的法律一样。制定好后大家都遵守。
OO的精髓,是对对象的抽象,最能体现这一点的就是接口。为什么我们讨论设计模式都只针对具备了抽象能力的语言(如C+++,java,C#等),就是因为设计模式所研究的,实际上就是如何合理去抽象。
1 2 3 4 5 6 7 8 9 10 11 12 13 /* Filename:UserService.java */ package com.humble.oop; public interface UserService { //接口内的方法默认是public abstract,可省略不写 ///*public abstract*/ void run(String name); void add(String name); void delete(String name); void update(String name); void query(String name); //接口内的属性默认是常量 public static final(一般不这样定义使用) /*public static final*/ int AGE=99; }
1 2 3 /* Filename:TimeService.java */ package com.humble.oop; public interface TimeService { void timer(); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 /* Filename:UserServiceImp.java */ package com.humble.oop; //接口都需要有实现类,它不能直接被实例化,因为没构造方法 //如果类 implements 了接口,则必须重写里面所有的方法 //类是多继承(extends)的,接口是多实现(implements)的,或者说,类可以集成实现多个接口(interface) public class UserServiceImp implements UserService,TimeService{ @Override public void add(String name) { } @Override public void delete(String name) { } @Override public void update(String name) { } @Override public void query(String name) { } @Override public void timer() { } }
内部类 内部类就是在一个类内部再定义一个类。比如A类中定义一个B类,那么B类相对A类来说就称为内部类,而A类相对B类来说就是外部类了。
1.成员内部类
2.静态内部类
3.局部内部类
4.匿名内部类
成员内部类 1 2 3 4 5 6 7 8 9 10 11 12 13 /* Filename:Outer.java */ package com.humble.oop; 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 = " + id); } } }
1 2 3 4 5 6 7 8 9 10 11 12 /* Filename:Application.java */ package com.humble.oop; public class Application { public static void main(String[] args) { Outer outer = new Outer(); //通过外部类的'实例'去new 内部类 Outer.Inner inner = outer.new Inner(); inner.in(); //这是内部方法 inner.getID(); //id = 10 } }
静态内部类 1 2 3 4 5 6 7 8 9 10 11 12 13 /* Filename:Outer.java */ package com.humble.oop; public class Outer { private int id = 10; public void out(){ System.out.println("这是外部方法"); } public static class Inner { //静态内部类 public void in(){ System.out.println("这是内部方法"); } //编译报错:Inner在静态区,id属性不在静态区,除非用static修饰id,否则必须实例化才能访问id //public void getID(){ System.out.println("id = " + id); } } }
1 2 3 4 5 6 7 8 9 10 11 /* Filename:Application.java */ package com.humble.oop; public class Application { public static void main(String[] args) { Outer outer = new Outer(); //直接new 外部类里的内部类 Outer.Inner inner = new Outer.Inner(); inner.in(); //这是内部方法 } }
注意:
1 2 3 4 5 6 7 8 9 10 11 /* Filename:outer.java */ package com.humble.oop; public class Outer { } //一个java文件中可以有多个class,但只有一个public class class A{ public static void main(String[] args) { System.out.println("A.main"); } }
局部内部类 1 2 3 4 5 6 7 8 9 10 11 12 13 /* Filename:outer.java */ package com.humble.oop; public class Outer { public void method(){ //局部内部类 class Inner { public void in(){ System.out.println("Inner.in"); } } } }
匿名内部类 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 /* Filename:Test.java */ package com.humble.oop; public class Test { public static void main(String[] args) { //不需要变量保存实例,没有名字初始化类(匿名) new Apple().eat(); //Apple.eat //也是匿名类 UserService userService = new UserService() { @Override //运行的时候才重写/实现接口的方法,也就等于定义了新的类,并且用new实例化了,保存在userService中 public void hello() { System.out.println("Test.hello"); } }; userService.hello();//Test.hello } } class Apple { public void eat(){ System.out.println("Apple.eat"); } } interface UserService{ void hello(); }
异常
运行过程中出现状况:用户输入不合法;打开一个文件可是文件不存在或格式不合法;读数据库却是空的;内存耗光或硬盘满了……
软件程序在运行过程中,非常可能遇到刚刚提到的这些问题,叫做异常(Exception
),翻译是例外,可以理解为例外的情况就是异常情况。
1 2 3 4 5 6 7 /* Filename:Application.java */ package com.humble.oop; public class Application { public static void main(String[] args) { System.out.println(11/0); //Exception in thread "main" java.lang.ArithmeticException: / by zero } }
3种类型的异常:
1.检查性异常,最具代表的检查性异常是用户错误输入引起的异常,这是开发者无法预见的。如:打开一个不存在的文件就会引发异常。这个异常在编译时不能被简单地忽略。
2.运行时异常,最容易被避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。
3.错误,错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如:当栈溢出的错误没办法在编译时就检查到。
Java把异常当作对象处理,并定义一个基类java.lang.Throwable
作为所有异常的超类。在Java API中已经定义了许多异常类,这些异常类分为两大类,错误Error
和异常Exception
Error
Error
类对象由Java虚拟机生成并抛出,大多数错误跟代码编写者所写的无关。
Java虚拟机运行错误(Virtual MachineError),当JVM不再有继续执行操作所需的内存资源时,将出现OutOfMemoryError
。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。
还有发生在虚拟机试图执行应用时,如类定义错误(NoClassDefFoundError
)、链接错误(LinkageError
)。这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且大多数是程序运行时不允许出现这样的情况。
Exception 在Exception
分支中有一个重要的自类RuntimeException
(运行时异常)
ArrayIndexOutOfBoundsException
(数组下标越界)
NullPointerException
(空指针异常)
ArithmeticException
(算术异常)
MissingResourceException
(丢失资源)
ClassNotFoundException
(找不到类)
… 这些是不检查异常,程序中可以选择捕获处理,也可不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
Error
和Exception
的区别:
Error
通常是灾难性、致命性的错误,是程序无法控制和处理的,当出现这些异常时,Java虚拟机(JVM)一般会选择终止线程。
Exception
通常情况下是可以被程序处理的,并且在程序中应该尽可能的去处理这些异常
捕获异常 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 /* Filename:Test.java */ package com.humble.oop; public class Test { public static void main(String[] args) { int a = 1; int b = 0; //try catch 需要有,finally可以没有。finally一般是需要在异常后关闭IO、释放资源的情况写 try{ //try监控区域 System.out.println(a / b); }catch (ArithmeticException e){ //catch 捕获到异常后的处理区 System.out.println("捕获异常,除数不能为0"); }finally { //处理善后区,不管有无捕获异常都会处理, System.out.println("finally"); } } }
捕获多个异常需要从小到大
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 /* Filename:Test.java */ package com.humble.oop; public class Test { public static void main(String[] args) { int a = 1; int b = 0; try{ System.out.println(a / b); } catch (Error e){ System.out.println("Error"); } catch (Exception e){ System.out.println("Exception"); } catch (Throwable e){ System.out.println("Throwable"); } finally { System.out.println("finally"); } } }
IDEAtips:选中代码,按Ctrl+Alt+t
可弹出补全逻辑
抛出异常 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 /* Filename:Test2.java */ package com.humble.oop; public class Test2 { public static void main(String[] args) { try { new Test2().test(1,0); } catch (ArithmeticException e) { e.printStackTrace(); } } //注意 throw 和 throws 两关键字 public void test(int a, int b) throws ArithmeticException{ if (b == 0) { throw new ArithmeticException(); //throw 一般是在方法内处理不了,于是主动往更高级抛出异常 } System.out.println(a / b); } }
自定义异常 使用Java内置的异常类可以描述在编程时出现的大部分异常情况。除此之外,用户还可以自定义异常(只需继承Exception
类即可)
自定义异常类步骤:
1.创建自定义异常类
2.在方法中通过throw
关键字抛出异常对象
3.如果在当前抛出异常的方法中处理异常,则可使用try-catch
语句捕获并处理;否则在方法的声明处通过throws
关键字指明要抛出给方法调用者的异常,继续进行下一步操作
4.在出现异常方法的调用者中捕获并处理异常
IDEAtips:双击Shift
可弹出搜索框
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 /* Filename:MyException.java */ package com.humble.oop; //自定义异常类 public class MyException extends Exception{ //传递数字>10 private int detail; public MyException(int detail) { this.detail = detail; } //toString:异常打印信息 @Override public String toString() { return "MyException{" + "detail=" + detail + '}'; } }
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 /* Filename:Test.java */ package com.humble.oop; public class Test { static void test(int a) throws MyException { System.out.println("a = " + a); if (a > 10){ throw new MyException(a);//抛出 } System.out.println("Test.test"); } public static void main(String[] args) { try { test(11); } catch (MyException e) { System.out.println("e = " + e); } } } /** 运行输出 * a = 11 * e = MyException{detail=11} */
处理运行时异常时,采用逻辑去合理规避同时辅助try-catch
处理
在多重catch块后面,可以加一个catch(Exception)
来处理可能会被遗漏的异常
对于不确定的代码,可以加上try-catch
,处理潜在的异常
尽量去处理异常,切忌只是简单地调用printStackTrace()
去打印输出
具体如何处理异常,要根据不同的业务需求和异常类型去决定
尽量添加finally
语句块去释放占用的资源