1 Markdown语法学习
标题:
#加上 空格 后面继续输入内容 回车即形成标题,二级,三级。。。标题依次多一个#即可
字体
Hello,world!
Hello,world! 粗体 两边各加两个*
Hello,world! 斜体 两边各 一个*
Hello,world! 斜体加粗体 两边各三个 *
Hello,world! 划去 两边各2个~
Hello,world!
引用
一个小箭头>,后面一个空格
选择java,改变人生轨迹。
分割线
三个---或者三个***,然后回车
图片
超链接
按Ctrl+左键可以再Markdown里直接打开超链接
列表
1加个.加个空格回车就自定有序了
-
的
-
地方
-
对方
-
地方
减号加个空格出来无序列表
表格
姓名 | 性别 | 年 |
---|---|---|
表头用|分开后就可以直接生成了
|姓名|性别|年,,,年后面再来一个竖线,然后回车.
代码
键盘table上面的点,点三下,后面加上语言名字,如java
2 命令行
打开cmd
-
在任意桌面空白处按住Shift+鼠标右键,点击打开powershell窗口
-
windows+R 输入 cmd 回车
-
资源管理器地址栏前面加上CMD和空格
管理员方式运行
常用的DOS(disk operator system)命令
#D:切换盘符 #切换盘符cd /d 盘名: #dir 查看盘符下所有文件 #切换目录cd 文件名 #返回上一级cd.. #清理屏幕cls #退出终端exit #查看电脑ip ipconfig #打开应用 calc计算器 notepad记事本 mspaint画图 #ping 命令 ping www.baidu.com 鼠标右键在命令行里即是粘贴 #md 文件名 创建目录 #cd>文件名.txt 创建文本文档 #del name.txt 删除文本文档 #rd name 移除目录
3 JAVA介绍与安装
java介绍
JDK: java development kit 开发者工具(包含运行时环境)
JRE: java runtime environment 运行时环境
JVM:java virtual machine 虚拟机
JDK安装与卸载
卸载
-
删除安装目录
-
删除JAVA-HOME
-
删除环境变量中path下关于Java的目录
-
命令行java -version
安装
-
oracle里找到所需版本的JDK下载
-
安装至自己定义的目录里,注意所有目录都不要出现汉字
-
配置环境变量
-
我的电脑-->右键属性
-
环境变量-->"JAVA_HOME"
-
配置path变量
-
-
测试JDK是否安装成功 1. 打开命令行 2. 输入java -version
4 第一个代码
-
新建一个java文件
-
Hello.java
-
后缀为java
-
-
编写代码
public class Hello{ public static void main(String[] args){ System.out.print("Hello,World!"); } }
System.out.print以及System.out.println的区别,后者直接另起一行
-
打开控制台指向该文件目录
-
编译java文件
-
javac Hello.java
-
此时生成 Hello.class文件
-
-
运行
java Hello
5 java程序运行机制
编译型和解释型
编译型可以理解为,将代码一次性翻译好,交给下层语言去运行
解释型可以理解为,需要运行代码时,每次都是一段一段的去解释
java既编译又解释,区分不明显
6 IDE,IDEA
官方网址
7 Java基础语法
三种注释
-
单行注释
-
多行注释
-
文档注释
8 标识符
java所有的组成部分都需要名字.类名,变量名以及方法名都被称为标识符.
包名:多单词组成时所有字母都小写:xxxyyyzzz
类名、接口名:多单词组成时,所有单词的首字母大写:XxxYyyZzz
变量名、方法名:多单词组成时,第一个单词首字母小写,第二个单词开始每个
单词首字母大写:xxxYyyZzz
常量名:所有字母都大写。多单词时每个单词用下划线连接:XXX_YYY_ZZZ
10 数据类型
强类型语言,所有变量先定义再使用.安全性高,速度慢.
Java各整数类型有固定的表数范围和字段长度,不受具体OS的影响,以保证java程序的可移植性。
java的整型常量默认为 int 型,声明long型常量须后加‘l’或‘L’
java程序中变量通常声明为int型,除非不足以表示较大的数,才使用long
类 型 占用存储空间 表数范围
byte 1字节=8bit位 -128 ~ 127
short 2字节 -2 15 ~215 -1
int 4字节 -2 31 ~ 231 -1 (约21亿)
long 8字节 -2 63 ~ 263 -1
java的数据类型分为两大类
-
基本类型(primitive type)
-
数值类型
-
整数类型 byte一个字节 short 2字节 int 4字节 long 8字节
-
浮点类型 float 4字节 double 8字节
-
字符类型
-
-
boolean类型(布尔类型):占1位其值只有true和false两种
-
-
引用类型(reference type)
-
类
-
接口
-
数组
-
char字符
char的单引号里面必须要放一个字符,空格也可以,但是String的双引号之间可以不放任何东西
char也可以不写引号,此时直接写数字如char c1 = 97;此时输出c1为a;(实际开发时很少这么写)
char 型数据用来表示通常意义上“字符”(2字节)
Java中的所有字符都使用Unicode编码,故一个字符可以存储一个字
母,一个汉字,或其他书面语的一个字符。
字符型变量的三种表现形式:
字符常量是用单引号(‘ ’)括起来的单个字符。例如:char c1 = 'a'; char c2
= '中'; char c3 = '9';
Java中还允许使用转义字符‘\’来将其后的字符转变为特殊字符型常量。
例如:char c3 = ‘\n’; // '\n'表示换行符
直接使用 Unicode 值来表示字符型常量:‘\uXXXX’。其中,XXXX代表
一个十六进制整数。如:\u000a 表示 \n。
char类型是可以进行运算的。因为它都对应有Unicode码。
String
-
String不是基本数据类型,属于引用数据类型
-
使用方式与基本数据类型一致。例如:String str = “abcd”;
-
一个字符串可以串接另一个字符串,也可以直接串接其他类型的数据。例如:
-
可以和8种基本数据类型做运算,且运算只能是连接运算.运算结果仍然是String类型
-
String类型如何在判断括号里生成判断条件:
-
someString.equals("需要判断的字符串")
-
str = str + “xyz” ;
int n = 100;
str = str + n;
练习
String str1 = 4; //判断对错:no
String str2 = 3.5f + “”; //判断str2对错:yes
System.out.println(str2); //输出:”3.5”
System.out .println(3+4+“Hello!”); //输出:7Hello!
System.out.println(“Hello!”+3+4); //输出:Hello!34
System.out.println(‘a’+1+“Hello!”); //输出:98Hello!
System.out.println(“Hello”+‘a’+1); //输出:Helloa1
最好完全 避免 使用浮点数进行比较
银行业务怎么表示 用Bigdecimal 数学工具类
汉字也能强制装转换???? unicode表 共2的16次方个 从u0000到uFFFF
char c3 = '\u0061'; //u0061其实是十六进制 转换为二进制是01100001,转换为十进制则是 97 故为a
类型转换
不包含 boolean
自动类型提升
运算中,不同类型的数据先转换为同一类型,然后进行运算
低----------------------------------------------------------->高
byte,short,char-->int-->long-->float-->double
byte,short,char之间做运算结果默认变为int
定义long和float时都必须加上 L和F.否则,前者会当成int类型赋给long,后者会把数字当成double类型赋给float.
s ++和s +=1,s *= 0.1不会改变其数据类型,例如s=127,s *=0.1运算得到的结果为s =12;
强制类型转换
-
不能对布尔值进行转换
-
不能把对象转换为不相干的类型
-
把高容量转为低容量时,强制转换
-
转换的时候可能出现内存溢出,或者精度问题
不同进制使用方法
二进制:0b开头
八进制:0开头
十六进制:0x开头
11 运算符
&&是短路与,||是短路或
int i1 = 127; boolean a = false; if(a ||/*此处或者为&&*/ i1++>10){ System.out.println(i1); }else{ System.out.println(i1); }
&&,如果左边判定为假,右边就不判定了
||如果左边判定为真,右边就不判定了
^ 异或,m= (m^n)^n;
n=(m^n)^m
//JDK 7新特性 数字之间可以插入下划线 不影响数字的表示和计算 int money = 10_0000_0000; int year = 20; long total1 = year * money;//year和money都是int型,在赋值之前的计算中已经溢出 long total2 = year * (long)money;//先对money进行强制转换,再与year乘之后,结果就会变成long System.out.println(total1); System.out.println(total2);
//很多运算,会使用一些工具来操作 double pow = Math.pow(2, 10); //先写Math.pow(2, 3); 然后 alt+enter System.out.println(pow);
位运算符和三元运算符
/* 位运算符 A = 0011 1100 B = 0000 1101 A&B 0000 1100(同为1,则为1) A|B 0011 1101(有1则为1) A^B 0011 0001(异或,不一样,则为1) ~B 1111 0010(取反) <<左移 >>右移 效率极高 */ System.out.println(2<<3); System.out.println("==========================================="); //条件运算符 int a = 10; int b = 20; a +=b; System.out.println(); //字符串连接符 + ,出现+ 转换成String连接起来 System.out.println("a+b="+(a+b)); System.out.println("a+b="+a+b); System.out.println(a+b+"");//双引号在后,则不起作用了,正常输出了a+b=50 System.out.println("==========================================="); //三元运算符(条件运算符 ? : ) 必须掌握!!!!!!!!!! //表达式1和表达式2应是一致的 //三元运算符可以套娃,但不建议这样做 //如果既可以用三元,又可以用if else,建议用三元,因为执行效率高 //x ? y : z --->如果 x == true 则结果为 y 否则为 z int score = 95; char grade; grade = score >90? 'A' : 'B'; System.out.println(grade);
交换变量方式
方法一:(推荐)
中间变量
方法二:(好:不用定义中间变量,坏:相加可能超出范围,有局限性:只能用于赋值类型)
num1 = num1 + num2;
num2 = num1 - num2;
num1 = num1 - num2;
方法三:(使用位运算符)
num1 = num1 ^num2;
num2 = num1^num2;(这一步将num2抵消掉,num1的值给了2)
num1 = num1^num2;(此时的num2为最初的num1)
12 包机制
为了更好的组织类,java提供了包机制,用于区别类名的命名空间.包的本质就是一个文件夹
一般利用公司域名倒置作为包名:如 com.baidu.www
包名语法
package pack1.pack2.pack3;
为了使用某一个包的成员,我们需要在java程序中明确导入该包.使用import
import package1.pack2.pack3.classname;
如果要导入该包里面的所有类,最后用一个* 来包括所有.
import pack1.pack2.pack3.*;
13 阿里巴巴开发手册
网上直接搜
14 JavaDoc
javadoc命令是用来生成自己的API文档的
相关----java se 官方在线文档
参数信息
// /加两个**回车 就是文档注释 //下面是给类加属性 /** * @author 作者名 * @version 版本号 * @since 指明需要最早使用的JDK版本 * @param 参数名 * @return 返回值情况 * @throws 异常抛出情况 */
控制台里生成自己的API文档,并在网页中显示出来
控制台生成javadoc文档
控制台里javadoc -encoding UTF-8 -charset UTF-8 Doc.java
如果目录中有汉字,encoding UTF-8 -charset UTF-8是为了让汉字显示更加正常.
然后在Doc.java目录下发现 index.html
使用IDEA生成javadoc文档
百度
15 Java流程控制
用户交互scanner
实现步骤
-
导包:import.java.util.Scanner;
-
Scanner的实例化
Scanner name = new Scanner(Syetem.in);
-
调用Scanner的相关方法,输入指定数据类型.
使用Next方法输入
io流用完关闭!!!!!!!!!!!
-
一定要读取到有效字符才可以结束输入
-
对输入有效字符之前遇到的空白,next()方法会自动将其去掉
-
只有输入有效字符后才能将其后面的空白作为分隔符或者结束符
-
next()不能得到带有字符的字符串
//通过scanner类的next()与nextLine()方法获取输入的字符串,在读取前 //我们一般需要使用hasNext()与hasNextLine()判断是否还有输入的数据 //创建一个扫描器对象,用于接收键盘数据 Scanner scanner = new Scanner(System.in); System.out.println("使用Next方法输入"); //判断用户有没有输入字符串 if(scanner.hasNext()==true){ //使用next方式接收 String str = scanner.next();//程序会等待用户输入完毕 System.out.println("输出的内容为:"+str); } //凡是属于io流的类如果不关闭会一直占用资源,养成好习惯用完及时关闭 scanner.close();
使用NextLine方法输入
io流用完关闭!!!!!!!!!!!
-
以Enter为结束符,也就是说netLine()方法返回的使输入回车之前的所有字符
-
可以获得空白
Scanner scanner = new Scanner(System.in); System.out.println("shuru"); if(scanner.hasNextLine()){ String str = scanner.nextLine(); System.out.println("shuruwei:"+str);
生成随机数
生成整数区间[a,b]的随机数方法
Math.random();该方法生成的是[0.0,1.0)之间的随机数
故:(int)(Math.random()*(b-a+1)+a)就是[a,b]之间的整型随机数
顺序结构
选择结构
if单选泽
id多选择
if嵌套
switch
-
default表示其他(或者否则)
-
switch的变量类型是
-
byte,short,int,或者char,枚举类型和String类型
-
从Java se 7开始,支持string了,同时,case标签必须为字符串常量或者字面量
-
System.out.println("请输入日子:"); String day = scanner.next(); switch(day){ case "周一": str = "Mon"; break; case "周二": str = "Tus"; break; case "周三": str = "Wes"; break; default: str = "休息"; } System.out.println("今天是"+str);
反编译
反编译 查看源码 javac是编译---->class(字节码文件),------->反编译(IDEA)
查看方法:
打开编译输出目录,将class文件复制到包的打开目录,之后在idea中目录里就可以看到,直接打开
循环结构
while循环
do...while循环
for循环
在java5中引入了一种用于数组的增强型for循环
for(声明语句 : 表达式) { //代码句子 }
声明语句:声明新的局部变量,该变量的类型必须和数组元素的类型匹配,其作用域限定于循环语句块,其值与此时数组元素的值相等
表达式:是要访问的数组名,或者是返回值为数组的方法
break&continue
break强行退出循环
continue用于终止某次循环,即 跳过循环体中尚未执行的语句,接着进行下一次啊是否执行循环的判定
带标签的continue:比如双层for循环,在第一层for前面加上label:,在第二层for里面加上 continue label;遇到该continue时候,会直接将continue作用到label处,即第一个for那里.
16 Java方法详解(可以理解为函数)
方法的特点
-
方法是面向过程的思想,函数是面向过程的思想
-
java方法是语句的集合,在一起执行一个功能
-
包含于类或者对象中
-
在程序中被创建,在其他地方被引用
-
-
设计方法的原则,方法的本意是功能块,就是实现某个功能的语句块的集合.设计方法时,最好保持方法的原子性,就是一个方法只完成一个功能,利于后期额扩展
方法的定义
修饰符 返回值类型 方法名(参数类型 参数名){ ... 方法体 ... return 返回值; }
return除了可以返回结果,还可以返回0 ,此时终止了方法!
修饰符: 可选的,告诉编译器如何调用该方法.定义了该方法的访问类型
返回值类型:方法可能会返回值,return valuetype是方法返回值的类型.有些方法执行所需的操作,但没有返回值,在这种情况下,return valuetype是关键字void
方法名:是方法的实际名称.方法名和参数表共同构成方法签名
参数类型:参数像一个占位符.当方法被调用时,传递值给参数.这个值被称为实参或者变量.参数列表是指方法的参数类型,顺序,和参数的个数.参数是可选的,方法可以不包含任何参数
形式参数:在方法被调用时用于接受外界输入的数据.
实参:调用方法时实际传给方法的数据
方法体:包含具体的语句,定义该方法的功能.
方法调用
-
调用方法:(new)对象名.方法名(实参列表)
-
java支持两种调用方法的方式,根据方法是否返回值来选择
-
当方法返回一个值的时候,方法调用通常被当做一个值
-
如果方法返回值是void,方法调用一定是一条语句
变量的赋值
-
如果变量是基本数据类型,此时赋值的是变量所保存的数据值
-
如果变量是引用数据类型,此时赋值的是变量所保存的数据的地址值
引用类型的变量的值只有两种可能,一是null,二是地址值,引用类型的变量在赋值时,传递的是地址值,即两个引用类型的变量指向堆中同一个对象实体,这样,更改其中一个变量,就相当于同时修改另一个同地址的引用类型变量的值.
引申方法的形参的传递机制,什么是 值传递 什么是 引用传递
-
形参:方法定义时,声明的小括号的参数
-
实参:方法调用时,实际传递给形参的数据
值传递机制,即将实际参数值的副本(复制品)传入方法内,而参数本身不受影响.
-
形参是基本数据类型时,将实参基本数据类型变量的"数据值"传递给形参
-
形参是引用数据类型时,将实参引用数据类型变量的"地址值"传递给形参
public class MethodArgsTest { public static void main(String[] args) { Number test = new Number(); test.m = 10; test.n = 20; MethodArgsTest t = new MethodArgsTest(); t.swap(test);//引用传递 System.out.println("test.m="+test.m+","+"\t"+"test.n="+test.n);//引用传递传递了地址值,更改的时候会更改成功 } //引用传递 public void swap(Number tt){ int temp = tt.m; tt.m = tt.n; tt.n = temp; } } class Number{ int m; int n; }
public class MethodArgsTest { public static void main(String[] args) { Number test = new Number(); test.m = 10; test.n = 20; MethodArgsTest t = new MethodArgsTest(); t.swap(test.m,test.n);//值传递 System.out.println("test.m="+test.m+","+"\t"+"test.n="+test.n);//值传递,只把值传递给了形参,并不能交换改变test.m和test.n的值. } //值传递 public void swap(int a ,int b){ int temp = tt.m; tt.m = tt.n; tt.n = temp; } } class Number{ int m; int n; }
方法的重载
就是在同一个类中,有相同的函数名称,但形参一定不同的函数
"两同一不同"
重载规则
-
在同一个类中
-
方法名称必须相同
-
参数列表必须不同(个数,类型,参数排列顺序不同)
-
方法的返回类型可同可不同
-
仅仅返回类型不同不足以成为方法的重载
实现理论
方法名称相同时,编译器会根据调用方法的参数个数,参数类型等去逐个匹配,已选择对应的方法,如果匹配失败,则编译器报错
命令行传参
可变个数形参
-
在方法声明中,在指定参数类型后加一个省略号...
-
一个方法中只能声明一个可变参数,它必须是方法形参中的最后一个参数,任何普通的参数必须在它之前声明
-
传入的参数个数为0个或者1个或者多个,时,都可以匹配,如果传入多个参数到可变个数形参里面,那么在方法里可以用数组的方式来调用各个值
-
可变个数形参的方法与本类中的其他的方法名相同,形参类型也相同的数组之间不构成重载,换句话说:二者不可共存
-
由4推,传递进去一个数组,也是可以的
-
假如对同一个方法进行重载,方法一形参列表为int型的可变个数形参,方法二形参列表为固定3个int型变量,那么调用这个同名方法时,如果传递的形参个数为三个,就会优先调用形参列表为固定3个int型变量的那个.
-
在后面学习到方法重写时,可变个数形参和数组,在编译和运行时当做一样的!即java把这种情况当做是重写方法的!
public static void main(String[] args) { MethodArgsTest t = new MethodArgsTest(); String b; b = t.greet(3,"Hello","yrf","wmz","cat"); System.out.println(b); } //定义一个方法,为可变个数形参,传递进来一个或多个值的时候,可以直接当做数组来调用各个值; public String greet(int a,String...str){ return str[a]; } 输出结果为cat
递归
-
A方法调用A方法,也就是自己调用自己
-
利用递归可以用简单的程序就解决一些复杂的问题,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大的减少了代码量.递归的能力在于用有限的语句,来定义对象的无限集合.
-
包括两个部分
-
递归头:什么时候不调用自身方法.如果没有头,就将陷入死循环
-
递归体:什么时候需要调用自身方法
-
例如,递归会加大时间复杂度和空间复杂度,应尽量避免,但数字小的计算还是可以的
public class digui { public static void main(String[] args) { System.out.println(f(5)); } public static long f(long n){ if(n==1) { return 1; } else { return n*f(n-1); } } }
17 数组
数组概述
四个基本特点
-
长度是确定的,数组一旦被创建,其大小就是不可改变的
-
其元素必须是相同类型,不允许出现混合类型
-
数组中的元素可以是任何数据类型,包含基本类型和引用类型
-
数组变量属于引用类型,数组也可以看成是对象,数组中的每个元素相当于该对象的成员变量,数组本身就是对象,java中对象是在堆中的,因此数组无论保存原始类型还是其他对象类型,数组对象本身都是在堆中的.
小结
-
数组是相同数据类型的有序集合
-
数组也是对象,数组元素相当于对象的成员变量
-
数组长度是确定的,不可变的
数组声明创建
int[] num = new int[10];
数组的元素是通过索引访问的,索引从0开始
获取数组长度arrays.length
内存分析
堆 :存放new出来的结构,对象和数组,可以被所有的线程共享,不会存放别的对象引用
栈 :存放基本变量类型(包含这个基本类型的具体数值),引用对象的变量(会存放这个引用在堆里面的具体地址)
三种初始化
-
数组的默认初始值
-
整型,默认全为0
-
浮点型,默认全为0.0
-
char型,默认全为ASC表中的0,对应空白,不是字符'0'
-
boolean型,默认全为false
-
String型,默认全为,null空值,而非"null"
-
-
一维数组初始化的两种方式
-
静态初始化
int[] arr = new int[]{1,2,3}
(其中,后面的new int []可以去掉)
-
动态初始化
int[] arr = new int[5];
-
-
二维数组初始化的两种方式
-
静态初始化
int[][] arr = new int[][]{{1,2},{3,4},{5,6}};
同样,new int[][]可以省略
-
动态初始化
两种方式
-
int[][] arr = new int[4][3]
-
int[][] arr = new int[4][]
列可以省去定义数字,行不可以
-
-
-
规定:二维数组分为外层数组的元素,内层数组的元素
int[][] arr = new int[4][3];
外层元素:arr[0],arr[1]等
内层元素:arr[0][0],arr[1][2]等
-
数组元素的默认初始化值
-
针对初始化方式:int[][] arr = new int[4][3];
-
外层初始化值arr[0]是地址值
-
内层初始化值arr[][]与一位数组初始化情况相同
-
-
针对初始化方法:int[][] arr = new int[4][]
-
外层初始化值为null(因为二维还没在堆里建立出来)
-
内层初始化值为:不能调用,否则报错
-
-
数组使用
数组的复制问题
int[] arr1 ={1435,462,3,45,5235,8385,342,4364,47,5}; int[] arr2; arr2 = arr1; for (int i = 0; i < arr2.length; i++) { System.out.println(arr2[i]);
这样不能称作是数组的复制,只是把arr堆中数组的地址给了arr2,也就是说两个变量arr1和arr2在栈中指向堆空间中同一个数组实体.
这叫做两个数组的赋值
int[] arr1 ={1,2,3,4,5}; int[] arr2 ={6,7,8,9,10,11,12,13}; arr2 = arr1; for (int i = 0; i < arr2.length; i++) { System.out.println(arr2[i]); }
实测这样亦可以
数组的复制要通过遍历才对
数组作返回值
public class demo02 { public static void main(String[] args) { int[] arr= {1,2,3,4,5,6,7,8,9,10}; int[] rev =reverse(arr);//定义第二个数组,直接调用reverse方法 for (int i = 0; i < 10; i++) { System.out.print(rev[i]+" "); } } //定义反转数组方法,返回值为数组 public static int[] reverse(int[] arrays){ int[] result = new int[arrays.length]; for (int i = 0 ; i <result.length ; i++) { result[i] = arrays[arrays.length-1-i]; } return result; } }
多维数组
int[][] arrays1 = new int[][]; int[][] arrays2 = {{0,1},{2,3},{4,5},{6,7}}; arrays2.length == 4 arrays2[0].length == 2 //注意理解这两个
Arrays类
-
数组的工具类java.util.Arrays
-
由于数组对象本身并没有什么方法可以供我们调用,但API中提供了一个工具类Arrays供我们时候用,从而可以对数据对象进行一些基本的操作
-
查看JDK帮助文档
-
Arrays类中的方法都是static修饰的静态方法,再使用的时候可以直接使用类名进行调用,而"不用"使用对象来调用(注意事项 "不用" 而不是"不能")
经常使用以下功能
public class arrays { public static void main(String[] args) { int[] arr ={1435,462,3,45,5235,8385,342,4364,47,5}; //打印数组元素, sout 括号里 Arrays.toString() Arrays.sort(arr);//按升序排列 Arrays.fill(arr,9);//用fill全部赋值为9 System.out.println(Arrays.toString(arr));//遍历打印数组值 Arrays.equals(a,b)//比较a和b是否相等,返回boolean值 int c = Arrays.binarySearch(a,3);//二分查找,括号里a为数组,3为目标,key System.out.println(c); } }
-
给数组赋值:通过fill方法
-
对数组排序:通过sort方法,升序
-
比较数组:通过equals方法比较数组中元素值是否相等
-
查找数组元素:通过binarysearch方法能对排序号的数组进行二分查找法操作
Arrays.toString() 打印所有数组元素
Arrays.sort() 对数组进行排序
稀疏数组
-
当一个数组中大部分元素时0,或者为同一值的数组时,可以使用稀疏数组来保存该数组.
-
稀疏数组的处理方式为:
-
记录数组一共有几行几列,有多少个不同值
-
把具有不同值得元素和行列及值记录在一个小规模的数组中,从而缩小程序的规模
-
public class xishushuzu { public static void main(String[] args) { //创建一个二维数组并初始化 int[][] arrays1 = new int[8][8]; arrays1[0][3] = 5; arrays1[0][6] = 2; arrays1[4][6] = 3; arrays1[7][4] = 9; //将数组1打印出来 for (int i = 0; i < arrays1.length; i++) { for (int j = 0; j < arrays1[i].length; j++) { System.out.print(arrays1[i][j]+"\t"); } System.out.print("\n"); } System.out.println("==================================="); int cnt = 0; //数出数组1中有效数字的个数 for (int i = 0; i < arrays1.length; i++) { for (int j = 0; j < arrays1[i].length; j++) { if(arrays1[i][j] != 0){ cnt ++; } } } //cnt+1就是稀疏数组的行数 //定义稀疏数组2 赋第一行的值 int[][] arrays2 = new int[cnt+1][3]; arrays2[0][0] = arrays1.length; arrays2[0][1] = arrays1[0].length; arrays2[0][2] = cnt; cnt = 0; //将有效数字提取出来,放入稀疏数组中 for (int i = 0; i < arrays1.length; i++) { for (int j = 0; j < arrays1[i].length; j++) { if(arrays1[i][j] != 0){ cnt++; arrays2[cnt][0] = i; arrays2[cnt][1] = j; arrays2[cnt][2] = arrays1[i][j]; } } } //打印稀疏数组 for (int i = 0; i < arrays2.length; i++) { for (int j = 0; j < 3; j++) { System.out.print(arrays2[i][j]+"\t"); } System.out.print("\n"); } } }
数组中常见的异常
-
数组角标越界的异常:ArrayIndexOutOfBoundException
-
空指针异常:NullPointerException
//情况一:数组是null,则为空指针 int[] a2 = new int[]{1,2,3}; //a2 = null; System.out.println(a2[0]); //情况二:二维数组,只定义外层,内层还没有创建,则为null,注意不是地址值 int[][] a3 = new int[4][]; System.out.println(a3[0]); //情况三: String[] a4 = new String[]{"aa","bb","cc","dd"}; a4[0] = null; System.out.println(a4[0].toString());
18 面向对象编程思想
核心思想就是oop(面向对象编程)
初识面向对象
学习面向对象的三条主线
-
java类及类的成员:属性,方法,构造器,代码块,内部类
-
面向对象的三大特征:封装,继承,多态,(抽象)
-
其他关键字:this super static final,abstract,interface,package,import
面向对象和面向过程
面向过程强调的是功能行为,以函数为最小单位,考虑怎么做.
面向对象,将功能封装进对象,强调具备了功能的对象,以类/对象为最小单位,考虑谁来做.
什么是面向对象
object-orientd programming,oop
本质就是:以类的方式组织代码,以对象的形式封装数据
抽象
三大特性
1. 封装 2. 继承 3. 多态
从认识论角度考虑就是先有对象后有类.对象是具体的事物,类是抽象的,是对对象的抽象
从代码运行角度考虑是先有类后有对象,类是对象的模板
类及类的成员
一个文件中有多个类,只有与文件名一致(注意是完全一致)的类名,才能声明为:public 内部类不能声明为:public 一个文件中只能有一个public类
类是一种抽象的数据模型,对一类食物整体描述的,不能代表某一个具体的事物
类中有属性和方法
属性 = 成员变量 = field = 域,字段 : 属性对应类中的成员变量
方法 = 成员方法 = Method =函数 : 方法对应类中的成员方法
类与对象使用的步骤
-
创建类,设计类的成员
-
创建类的对象
-
通过"对象.属性"或"对象.方法"的形式调用对象的结构
public class demo01 { //引用传递:对象,本质还是值传递 public static void main(String[] args) { //创建PERSON类的一个对象 PERSON p = new PERSON(); //调用对象的结构:属性或方法 //调用对象属性 p.name = "yrf"; p.age = 26; p.isMale = true; System.out.println(p.age); //调用对象方法 p.eat(); p.sleep(); p.talk(); } public static void change(PERSON Q){ //PERSON Q 是一个对象:指向PERSON p = new PERSON(),这是一个具体的人,可以改变属性 Q.name = "yrf"; } } //定义了一个PERSON类 class PERSON{ //定义属性 String name;//null int age = 25; boolean isMale; //定义方法 public void eat(){ System.out.println("吃饭"); } public void sleep(){ System.out.println("人可以睡觉"); } public void talk(){ System.out.println("人可以讲话"); } }
类与多个对象的关系,创建对象时的内存分析
类中的属性具有一维数组的默认初始化的特点,即类中属性没有赋初始值的时候,就给他
在创建多个对象时,在堆空间中开辟不同的区域,每个对象都独立地拥有一套类的属性(非static的),意味着,如果我们修改一个对象的属性A,则不影响另一个对象属性A的值.
如果new一个新的对象p2,将老的对象p1直接赋值给p2(PERSON p2 = p1,即将p1变量保存的对象地址赋给p3),那么,修改p2的属性A时,连带着修改了p1的属性A.此特征对应于数组的赋值,即在堆空间中,两个变量指向了堆空间中同一个对象实体.
类的成员之一:属性
属性(成员变量)和局部变量(local variable)的相同和不同
-
相同点:
-
定义变量的格式: 数据类型 变量名 = 变量值;
-
先声明,后使用
-
变量都有其对应的作用域
-
-
不同点
属性(成员变量) | 局部变量 | |
---|---|---|
在类中声明的位置不同 | 直接定义在类的一对{}中 | 声明在方法中,方法形参,代码块内,构造器形参,构造器内部变量 |
权限修饰符的不同 | 可以在声明属性时,指明其权限,使用权限修饰符,常用的权限修饰符:private,public,缺省,protected | 不可以使用权限修饰符 |
默认初始化值不同 | 类的属性根据其类型,都有默认初始化值 整型:0 浮点型:0.0 字符型:0 布尔型:false 引用数据类型(类,接口,数组)都为null | 没有默认初始化值(故需显示赋值),特别的,形参在调用时赋值即可 |
在内存中加载的位置 | 加载到堆空间中(非static) | 加载到栈空间中 |
类中方法的声明和使用
方法:描述类应该具有的功能
比如:Math类:sqrt()\random()......
Scanner类:nextXxx().......
Arrays类: sort()\binarySearch()\equals()\toString().....
声明
权限修饰符 返回值类型 方法名(形参列表){
方法体
}
-
权限修饰符:private public 缺省 protected
-
返回值类型:有返回值 无返回值
-
如果方法有返回值,则必须在方法声明时,指定返回值的类型,同时方法中需要使用return关键字返回指定类型的变量,没有返回值时,声明时使用void ,通常不需要return了,但是也可以使用,return;表示结束此方法.
-
定义方法该不该有返回值,
-
看题目要求
-
经验
-
具体问题具体分析
-
-
方法名: 属于标识符,遵循规则和规范
-
形参列表:可以声明多个或0个形参
-
格式:数据类型1 形参1,类型2 形参2,.....
-
该不该定义形参
-
-
-
方法里面可以调用属性或者方法
-
方法中不可以定义方法
方法的调用
-
静态方法:static
直接用类().方法();就可以调用
public class demo01 { public static void main(String[] args) { demo01.add(1,2); } public static int add(int a,int b){ return a+b; } }
-
非静态方法 : 不加static
静态方法和非静态方法的跨类调用????
非静态方法调用时,需要new一个,new一个类中的对象,再用 类().方法(); 来实现调用方法.
public class demo01 { public static void main(String[] args) { demo01 name = new demo01(); demo01().add(); } public int add(int a,int b){ return a+b; } }
-
形参和实参
-
值传递和引用传递
public class demo01 { //引用传递:对象,本质还是值传递 public static void main(String[] args) { PERSON p = new PERSON(); System.out.println(p.name); change(p); System.out.println(p.name); } public static void change(PERSON Q){ //PERSON Q 是一个对象:指向PERSON p = new PERSON(),这是一个具体的人,可以改变属性 Q.name = "yrf"; } } //定义了一个PERSON类,有一个属性:name class PERSON{ String name;//null }
-
this关键字
两种创建对象调用方法的方式
匿名对象的使用:直接创建对象没有署名,然后直接调用方法
new Student1().stateThree(test);
匿名对象只能使用唯一的一次,下次再用不得不再创建一个新对象。 2.没有引用的对象就是内存中的垃圾,会被JVM中垃圾回收机制回收,匿名对象一创建就是垃圾
Student1 t = new Student1(); t.stateThree(test);
经典例题
-
创建一个Student1类
-
在类中添加属性: 学号,年级,分数
-
定义方法:1 返回对象的三个属性
2 输出年级为3 的所有信息
3 使用冒泡排序按分数进行排序并输出
public class Student1 { int number; int state; int score; //返回对象数组中 某一对象的 三个属性 信息 public String info(){ return "学号是:"+number+"\t"+"年级是:"+state+"\t"+"分数是:"+score; } //数组对象中 3 年级 的信息 //在这个方法里,调用了info()方法 //形参里面,使用 Student1[] a 可以直接将new出来的 对象数组test 拿来使用 public void stateThree(Student1[] a){ for (int i = 0; i < a.length; i++) { if(a[i].state == 3){ System.out.println(a[i].info()); } } } //使用冒泡排序按成绩进行排序,并遍历输出 public void scoreSort(Student1[] a) { int a0, a1, a2; for (int i = 0; i < a.length - 1; i++) { for (int j = 0; j < a.length - 1 - i; j++) { if (a[j].score < a[j + 1].score) { Student1 temp = a[j]; a[j] = a[j + 1]; a[j + 1] = temp; } } } for (int i = 0; i < a.length; i++) { System.out.println(a[i].info()); } } }
public class Student1test { public static void main(String[] args) { //定义一个Student1类的对象数组,此时开辟了内存空间,但是具体的元素为null //引用类型的变量的值,要么是null,要么是地址值 Student1[] test = new Student1[20]; //遍历数组,创建对象,同时给数组中每个对象的属性赋值 for (int i = 0; i < test.length; i++) { test[i] = new Student1(); test[i].number = i + 1; test[i].state = (int)(Math.random()*6 + 1); test[i].score = (int)(Math.random()*41 + 60); } //输出所有数组对象的 属性信息 for (int i = 0; i < test.length; i++) { System.out.println(test[i].info()); } System.out.println("===================================="); //输出所有数组对象中 3年级的学生信息,第一种调用方式 new Student1().stateThree(test); System.out.println("===================================="); //第二种调用方式 Student1 t = new Student1(); t.stateThree(test); System.out.println("===================================="); //使用冒泡排序按成绩进行排序,之后遍历输出 new Student1().scoreSort(test); } }
完成一个项目的思路
-
根据问题需要,选择问题所针对的现实世界中的实体
-
从实体中寻找解决问题相关的属性和功能,这些属性和功能就形成了概念世界中的类
-
把抽象的实体用计算机语言来描述,形成计算机世界中类的定义 ,即借助某种程序语言,把类构造乘计算机能够识别和处理的数据结构.
-
将类实例化为计算机世界中的对象,对象时计算机世界解决问题的最终工具
面向对象思想落地实现的规则
-
创建类,设计类的成员
-
创建类的对象
-
通过"对象.属性"或者"对象.方法"调用对象的结构
类的成员之二:方法
面向对象特征之一:封装与隐藏
高内聚:类的内部数据操作自己完成,不允许外部干涉
低耦合:仅对外暴露少量的方法用于使用
隐藏对象内部的复杂性,只对外工卡简单的接口,便于外界调用,从而提高系统的可扩展性,可维护性,通俗的说,把该隐藏的隐藏起来,该暴露的暴露出来.这就是封装性的设计思想
一.问题引入
当我们创建一个类的对象以后,可以通过"对象.属性"来对对象的属性进行赋值.此时,赋值操作只收到属性的数据类型的存储范围的制约,除此之外,无其他条件,但在实际问题中,往往需要给属性赋值加入额外的限制条件,这个限制条件不能再属性声明时体现,只能通过方法进行限制条件的添加,同时我们需要避免用户再使用"对象.属性"的方式对属性进行赋值,只需要将属性加上private
二.如何实现呢
将属性私有化的同时(private),通过public的setXxxx()getXxxx()方法来实现设置和获取此属性的值.
快捷键 alt + insert (setter 和 getter)
三.拓展
以上是封装性的一个体现,其他体现还要:
-
不对外暴露的私有的方法,
-
单例模式
-
...
四.封装性的体现需要权限修饰符来配合
-
java规定的四种权限(从小到大): private 缺省 protected public
修饰符 | 类内部 | 同一个包 | 不同包的子类 | 同一个工程 |
---|---|---|---|---|
private | Yes | |||
缺省 | Yes | Yes | ||
protected | Yes | Yes | Yes | |
public | Yes | Yes | Yes | Yes |
-
对于class的权限修饰只可以用public和缺省
-
四种权限可以用来修饰类及类的内部结构:属性,方法,构造器,内部类
-
具体的,4种权限都可以用来修饰 属性,方法,构造器,内部类
但是类只能用public和缺省来修饰
总结
java提供了四个权限修饰符,体现类及类的内部结构在被调用时的可见性
类的成员之三:构造器(或构造方法)
Person p = new Person(); 其中,这后面的Person()就是构造器,构造器与类 同名
构造器的作用:
-
创建对象
-
构造器可以重载
-
通过形参可以在创建对象的时候给对象的属性进行赋值,初始化对象
Person p = new Person();
Person p = new Person("Tom",15);
如果没有显示的定义类的构造器的话,则系统默认提供一个空参的构造器
一旦我们显式地定义了构造器之后,系统就不在提供空参的构造器了,如果显式定义了带参数的构造器之后,又想去使用空参构造器,则必须显式地将空参构造器给定义出来!
一个类中,至少会有一个构造器
定义构造器的格式:权限修饰符 类名(形参列表){}
public class TriAngleTest { public static void main(String[] args) { TriAngle a = new TriAngle(); System.out.println(a.Square()); a.setBase(4); a.setHeight(6); System.out.println(a.Square()); TriAngle b = new TriAngle(5,6); System.out.println(b.Square()); } }
public class TriAngle { private double base; private double height; public TriAngle(){ } public TriAngle(double ba,double hei){ base = ba; height = hei; } public void setBase(double base) { this.base = base; } public double getBase() { return base; } public void setHeight(double height) { this.height = height; } public double getHeight() { return height; } public double Square(){ return 0.5 * base * height; } }
属性赋值的先后顺序,值的多少从后向前看
-
默认初始化
-
显式初始化
-
构造器中赋值
-
通过"对象.属性"或者"对象.方法"的方式赋值
javabean
所谓的javabean,指的是符合以下标准的java类
-
类是公共的
-
有一个无参的公共的构造器
-
有属性,且有对应的get,set方法
用户可以使用javabean将功能,处理,值,数据库访问和其他任何可以用java代码创造的对象进行打包,并且其他的开发者可以通过呢部的jsp页面,Servlet,其他javabean,applet程序或者应用来使用这些对象,用户可以认为javabean提供了一种随时随地的复制和粘贴的功能
关键字:this的使用
this可以用来修饰属性,方法,构造器,可以理解为当前对象,或者当前正在创造的对象()
-
通常都可以省略掉this,但是如果方法的形参和类的属性同名时,我们必须显式使用"this.变量"的方式,表明此变量是属性,而非形参.
-
构造器中没有return值
-
可以在构造器中使用表明当前正在创造的对象的属性
-
this()直接调构造器!
-
如果一个类中有n个构造器,最多有n-1个构造器中使用了this(),每个构造器只能调一个
-
构造器中不能调用自己
-
this()必须在构造器中的首行!
-
this(),括号里面可以加上形参,代表调用重载的构造器
-
如果定义了多个构造器,同时执行的语句又比较多,那么可以不用重复写这些重复的语句.比如:
-
1.public Person(){ 重复的语句1 重复的语句2 重复的语句3 重复的语句4 重复的语句5 ... } 2.public Person(int age){ this();//这里的this()调用的是 构造器1 this.age = age; } 3.public Person(String name,int age){ this(age);//这里的this(age)调用的是 构造器2 this.age = age; }
典型例子
public class Account { private double balance;//定义账户余额 public Account(double balance){//带传递余额的账户构造器 this.balance = balance; } public double getBalance(){//get余额 return balance; } public void deposit(double amount){//存钱方法 balance += amount; System.out.println("您已存入"+amount+"元,余额为"+balance+"元."); } public void withdraw(double amount){//取钱方法 if(amount > balance){ System.out.println("余额不足!"); return; } balance -= amount; System.out.println("您已取出"+amount+"元,余额为"+balance); } }
public class Customer { private String firstname;//定义客户姓氏 private String lastname;//定义客户名字 private Account acc ;//定义客户的账户acc 为引用类型Account public Customer(String firstname,String lastname){//客户的带姓名的构造器 this.firstname = firstname; this.lastname = lastname; } public String getFirstname(){//get客户名字 return firstname; } public String getLastname(){//get客户姓氏 return lastname; } public void setAcc(Account acc){//set客户账户 this.acc = acc; } public Account getAcc(){//获取客户账户 return acc; } }
public class Bank { private Customer[] customers;//定义客户的 数组对象 private int numofcustomers;//定义客户数目 public Bank(){//bank的构造器,在创建一个bank对象时,自动创建一个 客户对象的数组 customers = new Customer[10]; } public void addCustomer(String firstname,String lastname){//向客户数组里添加一个新客户 Customer cus = new Customer(firstname,lastname);//创建新客户 customers[numofcustomers ++] = cus;//将新客户赋值给数组对应的元素,同时记录客户数量加一 } public int getNumofcustomers(){//获得该银行中客户的数量 return numofcustomers; } public Customer getCustomer(int index){//获得对应数组客户中某一个客户 if(index >= 0 && index < numofcustomers){ return customers[index]; }else { return null; } } }
public class BankTest { public static void main(String[] args) { Bank b1 = new Bank();//创建一个银行对象 b1.addCustomer("yan","rongfu");//使用银行b1调用一个增加客户的方法 b1.getCustomer(0).setAcc(new Account(2000));//设置该银行中客户数组中下标为0的客户的账户 b1.getCustomer(0).getAcc().withdraw(300);//调用该客户的账户并取钱300 double balan = b1.getCustomer(0).getAcc().getBalance();//获取该客户余额 System.out.println(balan); b1.addCustomer("wei","mingzhu");//在这个银行中创建第二个客户 System.out.println(b1.getNumofcustomers()); b1.getCustomer(1).setAcc(new Account(5000)); b1.getCustomer(1).getAcc().withdraw(6000); } }
关键字package 的使用
-
为了更好的对项目中的类进行管理,提供包的概念,
-
使用package声明类或者接口所属的包,声明在源文件的首行
-
包,属于标识符,遵循标识符的命名规则,规范 "见名知意"
-
每点 一次,就代表一层文件目录
-
同一个包下,不能命名相同名字的类,接口
-
不同的包下,可以命名相同名字的类,接口
import关键字的使用
-
在源文件中显式地使用import结构导入指定包下的类,接口
-
声明在包的声明和类的声明之间
-
如需导入多个结构,并列写出即可
-
可以使用XXX.*;来表明导入xxx包下的所有结构
-
如果使用的类或接口是java.lang包下定义的,则可以省略import结构
-
如果在源文件中使用了不同包下的同名的类,则必须至少有一个类需要以全类名的方式调用
-
如果我们使用xxx.*的方式表明调用xxx包下的所有结构,但如果使用的xx子包下的结构,则仍需要显式导入
-
import static 导入指定类或接口中的静态结构的属性或方法
面向对象特征之二:继承(inheritance)
继承的好处
-
减少代码冗余,提高了代码的公用性
-
利于扩展
-
为之后的多态性的使用,提供了前提
继承性的格式
class A extends B{};
A : 子类,派生类,subclass
B : 父类,超类,基类,superclass
-
体现: 一旦子类A继承了父类B以后,子类A中就获取了父类B所有的结构 : 属性,方法
-
注意: 父类中声明为private的属性也继承了,只是由于封装性的影响使得子类不能直接调用父类的结构.调用属性的时候需要用set和get
-
子类继承父类以后,还可以声明自己特有的属性和方法,实现功能的扩展
java中关于继承的一些规定
-
一个类可以有多个子类
-
一个类只能有一个父类--->单继承
-
子父类是相对的概念
-
子类直接继承的父类,成为直接父类,间接继承的父类称为间接父类
-
子类继承父类以后,就继承了直接父类以及所有间接父类中声明的属性和方法
-
如果我们没有显式地声明一个类的父类的话,则此类继承于java.lang.Object类
-
所有的类都直接或间接的继承于Object类
-
意味着,所有的java类都具有Object类中的功能
方法的重写override/overwrite
-
重写:在子类中可以根据对从父类继承来的方法进行改造,也称为方法的重置,覆盖,在程序执行中,子类的方法将覆盖父类的方法.
-
应用,重写以后,创建子类对象之后,通过子类对象调用父类中的同名参数的方法时,实际执行的是子类重写父类的方法
-
面试题:区分方法的重载和重写
-
重写的规定:(注意属性可以在子父类中重复而不覆盖)
-
方法的声明:权限修饰符 返回值类型 方法名(形参列表){
方法体
}
-
约定俗成:子类中叫重写的方法,父类中叫做被重写的方法
-
子类重写的方法的方法名和形参列表与父类被重写的方法名和形参列表相同
-
子类重写的方法的权限修饰符 不小于父类被重写的权限修饰符
子类不能重写父类中声明为private的方法
-
返回值类型:
-
父类方法是void,则子类重写的也是void
-
父类方法是A类型,则子类重写的是A类或A类的子类
-
父类方法是基本数据类型,子类重写的也必须是相同的基本数据类型
-
-
子类重写的方法抛出的异常类型,不大于父类被重写的方法抛出的异常类型
-
子类和父类中同名同参数的方法要么都声明为非static的,要么都声明为static的
-
只有非static的,才叫做方法的重写,如果是static的方法,则不是重写
-
super关键字
父类定义了方法,子类重写了该方法,那么在调用子类对象时,如果想使用父类的该方法,在前面加上super
子父类定义了相同的属性,调用的时候就近原则,加上super时则会调用父类的属性.
super的使用:调用属性和方法
-
可以在子类的方法或构造器中,通过使用"super.属性"或"super.方法"的方式,显式调用父类中声明的属性或方法.但是,通常情况下,我们习惯省略super
-
当子类和父类中定义了重名的属性时,要想在子类中调用父类中声明的属性,则必须显式地使用"super.属性"的方式,表明调用的是父类中声明的属性
-
父类指的是子类向上所有的父类,先找的直接父类,再找间接父类,就近原则
super的使用:调用构造器
-
子类的所有构造器会在首行默认地隐式调用父类中空参的构造器
-
在子类的构造器中可以显式地使用"super(形参列表)"来调用父类中声明的指定的构造器
-
super(形参列表)的使用,必须声明在子类构造器的首行
-
我们在类的构造器中,针对于"this(形参列表)" 和"super(形参列表)"只能够二选一,不能够同时出现
-
在类的多个构造器中,至少有一个类的构造器中使用了"super(形参列表)",调用父类的构造器
子类对象的实例化过程
-
从结果来看,子类继承父类以后,就获取了父类中声明的属性与方法,创建子类的对象,在堆空间中,就会加载所有父类中声明的属性
-
从结果上来看,通过子类的构造器创建子类的对象,子类的构造器自动的调用父类的构造器,以此类推,直到调用了java.lang.Object类中的空参的构造器位为止
-
虽然调用了父类的构造器,但自始至终只创建了一个对象
面向对象特征之三:多态性(Polymorphism)
-
如何理解: 可以理解为一个事物的多种形态
-
何为多态性
-
对象的多态性:父类的引用指向子类的对象(子类的对象赋给父类的引用)相当于以一个更大的范围定义一个具体的对象
-
-
多态的使用:---虚拟方法调用(virtual method invocation)
有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法
编译看左,运行看右
编译看左,运行看右
编译看左,运行看右
-
多态性的使用前提
-
要有类的继承关系
-
子类要有方法的重写
-
-
对象的多态性只适用于方法,不适用于属性
对于属性,编译和运行都看左边
-
注意
-
有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法,的,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法,子类特有的属性和方法不能调用
-
那么如何才能调用子类特有的属性和方法.
使用强制类型转换符(向下转型):Student stu1 = (Student)p1;
子类和父类的类型转换类比于基本数据类型的转换
-
较低的基本数据类型向上转换叫做自动类型提升,较高的基本数据类型乡下转换叫做强制类型转换
-
子类向父类转换叫做多态,父类向子类转型叫做向下转型 此时使用instanceof进行判断,使用强制类型转换符(向下转型):Student stu1 = (Student)p1;
-
使用强转时可能出现ClassCastException的异常,举例来说就是,定义了一个Person引用类型的Man对象,又将该对象强转为Woman,则会产生这个异常
instanceof 关键字的使用
为了避免在强转的时候出现ClassCastException异常,通常在强转的之前使用instanceof进行判断.
a instanceof A:判断a对象是否为类A的实例,如果是,返回true,不是的话返回false并终止向下转型.
Animal ani = new Bird(); if(ani instanceof Cow){ Cow c1 = (Cow)ani; System.out.println("Cow"); }else if(ani instanceof Bird){ Bird b1 = (Bird) ani; System.out.println("Bird"); }
多态性典型例子
public class Example { public static void main(String[] args) { Sub s = new Sub(); System.out.println(s.count);//20 s.print();//20 Base b = s; System.out.println(b == s);//true 引用类型变量是地址值,此时二者指向同一个地址!!! System.out.println(b.count);//10 b.print();//20 } } class Base{ int count = 10 ; public void print(){ System.out.println(this.count); } } class Sub extends Base{ int count = 20; public void print(){ System.out.println(this.count); } }
-
若子类重写了父类的方法,就意味着子类里定义的方法彻底颠覆了父类里的同名方法,系统将不可能把父类的方法转移到子类中,即编译看左,运行看右.
-
对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量,也就是堆空间中二者都存在,只看最终引用的哪个,编译运行都看左边
Object类的使用
-
Object类是所有类的根父类,数组也是类,数组的根父类也是Object类,数组可以调用Object类中的方法
-
Object类中的功能(属性方法)具有通用性
-
Object类只声明了一个空参的构造器
-
getclass();方法获取当前对象的类
"=="和"equals()"的区别及equals()的重写
-
==
-
是运算符,可以使用在基本数据类型变量和引用数据类型变量中
-
如果比较的是基本数据类型,比较两个变量保存的数据是否相等(不一定类型要相同)
如果比较的是引用数据类型,比较的是两个对象的地址值是否相同,即两个引用是否指向同一个对象实体
-
-
equals方法的使用
-
是一个方法,而非运算符
-
只适用于引用类数据类型
-
Object类中的equals的定义: 其实是用的 "==",也就是说和"=="的作用是相同的
-
像String Date File 包装类这些类都重写了Object类中的equals方法,重写了之后,比较的不是两个对象的地址值了,比较的是两个对象"实体内容"是否相同
-
我们自定义的类,如果使用equals的话,也通常想要比较两个对象的实体内容,那么我们要对Object类中的equals()进行重写
重写的原则,比较两个对象的实体内容是否相同.
-
对称性:如果x.equals(y)返回true,则y.equals(x)返回也应该是true
-
自反性,x.equals(x)的返回必须是true
-
传递性:如果x.equals(y)返回true,且y.equals(z)返回也是true,则x.equals(z)返回true
-
一致性:如果x.equals(y)返回true,只要x和y的内容一直不变,不管你重复多少次,返回都是true
-
任何情况下,x.equals(null)返回都是false,x.equals(和x不同类型的对象)返回都是false
下面以String重写equals()的源码为例,注意value是String源码中利用char数组来造字符串
public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String)anObject; int n = value.length; if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.value; int i = 0; while (n-- != 0) { if (v1[i] != v2[i]) return false; i++; } return true; } } return false; }
下面用一个Customer类来展示重写equals()前后的区别,首先是未重写equals():
此时输出为false
public class CustomerTest { public static void main(String[] args) { Customer c1 = new Customer("yrf",26); Customer c2 = new Customer("yrf",26); System.out.println(c1.equals(c2));//此时没有重写equals(),得到的结果为false,因为比较的是两个对象的地址 } } class Customer{ private String name; private int age; public Customer(String name, int age) { this.name = name; this.age = age; } }
然后是重写了equals():
此时输出为true
public class CustomerTest { public static void main(String[] args) { Customer c1 = new Customer("yrf",26); Customer c2 = new Customer("yrf",26); System.out.println(c1.equals(c2));//重写equals(),得到的结果为true,因为利用重写的equals方法完全比较了两个Customer的内容 } } class Customer{ private String name; private int age; public Customer(String name, int age) { this.name = name; this.age = age; } //重写Customer继承Object类的equals()方法,参照String中的重写equals()方法 @Override public boolean equals(Object obj) { if (this == obj) { return true;//调用该方法的Customer和传进来的Customer的地址相同,不用比了,直接返回true } if(obj instanceof Customer){//如果传进来的是Customer类的对象,就继续 Customer cust = (Customer) obj; if(this.age == cust.age && this.name.equals(cust.name)){ return true; } } return false;//如果传进来的就压根不是Customer类的对象,那也不用比了,直接返回false } }
但是,这个可以不用手写!可以alt+insert 自动生成
一般用自动生成的,因为写得更细致
public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Customer customer = (Customer) o; return age == customer.age && name.equals(customer.name); }
-
-
关于new两个相同的String对象和用String定义两个相同引用类型变量时比较相等的情况说明
-
new两个String对象后,两个对象在堆空间中都有各自的空间,如果要用equals()比较两个对象,此时的equals()是重写的方法,比较的是对象的实体内容
-
String两个一样的变量,此时java有一个特性,即数据值相同时,此数据是在 变量池中重复使用的,换句话说,两个String变量指向 变量池 中同一个地址,如果此时采用 "==" 来比较,也是true
toString()方法的使用
-
当我们输出一个对象的引用时,实际上就是调用当前对象的toString()
-
Object类中toString()的定义:
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
-
像String Date File 包装类都重写了Object类中的toString()方法.使得在调用对象的toString()时,返回"实体内容"的信息
-
自定义类也可以重写toString()方法,使得当调用此方法时,返回对象的"实体内容".
-
手写
-
alt + insert
-
包装类(Wrapper)的使用
为了让八大基本数据类型统一到Object类中,使其变为对象,具有类的特征
包装类new出来的对象,注意已经变为引用类型了(没有赋值的话初始值为null)
基本数据类型 | 包装类 | 父类 |
---|---|---|
byte | Byte | Number |
short | Short | Number |
int | Integer | Number |
long | Long | Number |
float | Float | Number |
double | Double | Number |
boolean | Boolean | |
char | Character |
基本类型,包装类和String类之间的转换
-
基本数据类型--->包装类:调用包装类的构造器
int num1 = 10; Integer in1 = new Integer(num1); Integer in2 = new Integer("135"); System.out.println(in1);//10 System.out.println(in2);//135 Float f1 = new Float(1.2f); Float f2 = new Float("1.234"); System.out.println(f1.toString());//1.2 System.out.println(f2);//1.234 Boolean b1 = new Boolean(true); Boolean b2 = new Boolean("true123"); System.out.println(b1);//true System.out.println(b2);//false
-
包装类--->基本数据类型: 调用包装类Xxx的xxxValue()
Integer in1 = new Integer(10); int num1 = in1.intValue(); System.out.println(num1);//10
自动装箱与自动拆箱
int n1 = 20; Integer in1 = n1;//自动装箱 int n2 = in1;//自动拆箱
就是直接把基本数据类型变成了包装类,不需要调用包装类的构造器了
基本类型和包装类 怎么转换为String类型
-
做连接运算,即+" "
int n = 10; String str = n + " ";
-
调用String重载的valueof()
float f1 = 1.34f; String str = String.valueof(f1);
所有的基本数据类型都可以通过调用String.valueof()来实现转换
String类型 怎么转换为基本类型和包装类
调用包装类的parseXxx()方法
String str = "123"; int num = Integer.parseInt(str);//num 为123. String str1 = "true"; boolean b = Boolean.parseBoolean(str1);//b为true
练习:
Object o1 = true? new Integer(1):new Double(2.0); System.out.println(o1); System.out.println(o1.toString());
输出的都是 1.0 因为三元运算符需要在编译时对冒号两边的类型进行统一,Integer自动提升为了Double了
练习:
Integer in1 = new Integer(1); Integer in2 = new Integer(1); System.out.println(in1 == in2);//false Integer in3 = 1; Integer in4 = 1; System.out.println(in3 == in4);//true Integer in5 = 128; Integer in6 = 128; System.out.println(in5 == in6);//false
此题查看源码,Integer中有一个静态IntegerCache,造了数组,保存了范围为-128至127的整数,如果我们使用自动装箱的方式,给Integer赋值的范围在-128至127的范围内时,可以直接使用数组中的元素,就不用再去new了,目的就是为了提高效率
in5 和in6 相当于new了一个对象
关键字:static
-
可以用来修饰:属性,方法,代码块,内部类,,,!!!!!不能修饰构造器
-
变量的分类:按声明的位置的不同
-
成员变量
-
实例变量(不以static修饰)
-
类变量(以static修饰)
-
-
局部变量
-
形参
-
方法局部变量
-
代码块局部变量
-
-
-
使用static修饰属性:静态变量,或类变量
-
属性按是否使用static修饰就分为静态属性 vs 非静态属性(实例变量)
实例变量:我们创建了类的多个对象,每个对象都独立地拥有一套类中的非静态属性,当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值的修改
静态变量:我们创建了类的多个对象,多个对象共享同一个静态变量,当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改过了的
-
static修饰属性的其他说明:
-
静态变量随着类的加载而加载,可以通过 "类.静态变量" 的方式进行调用
-
静态变量的加载要早于对象的创建
-
由于类只加载一次,那么静态变量在内存中也只会存在一份,存在方法区中的 静态域 中.
-
-
-
使用static修饰方法:
-
随着类的加载而加载,可以通过"类.静态方法"的方式调用
-
静态方法中只能调用静态的方法和属性,对于非静态方法,既可以调用非静态的方法和属性,也可以调用静态的方法和属性.
-
静态方法中,不能使用 this 关键字 和 super 关键字
-
-
开发中,如何确定一个属性是否要声明为 static 的?
属性可以被多个对象所共享的,不会随着对象的不同而不同的
类中的常量,也常常声明为static
-
开发中,如何确定一个方法是否要声明为 static 的?
-
操作静态属性的方法,通常设置为static
-
工具类中的方法,习惯上声明为static的,比如: Math Arrays Collections
-
单例设计模式
是在大量的实践中总结和理论化之后优选的代码结构,编程风格,以及解决问题的思考模式,就像是经典的棋谱,不同的棋局,我们用不同的棋谱,免去我们自己再思考和摸索套路.
-
单例设计模式
就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个确切对象实例的方法,如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为private,这样就不能用 new 操作符 在类的外部产生类的对象了,但在类内部仍然可以产生该类的对象.因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的.
-
如何实现单例设计模式
饿汉式 vs 懒汉式
// 单例模式的饿汉式实现 public class SingletonTest1 { public static void main(String[] args) { SingletonTest1 st1 = new SingletonTest1(); Bank bank1 = Bank.getInstance(); Bank bank2 = Bank.getInstance(); bank1.print(); System.out.println(bank1 == bank2);// true } } // 饿汉式 class Bank{ // 1.私有化类的构造器 private Bank(){ } // 2.内部创建类的对象 // 4.要求此对象也必须声明为静态的(因为静态方法调用的结构也必须为静态的) private static Bank instance = new Bank(); // 3.提供公共的静态的方法,返回类的对象,此时,要求----->4 public static Bank getInstance(){ return instance; } public void print(){ System.out.println("这是单例银行"); } }
// 单例模式的懒汉式实现 public class SingletonTest2 { public static void main(String[] args) { Order order1 = Order.getInstance(); Order order2 = Order.getInstance(); System.out.println(order1 == order2); } } // 懒汉式 class Order{ // 1.私有化类的构造器 private Order(){ } // 2.声明当前类对象,没有初始化 // 4.此对象也必须为 static 的 private static Order instance = null; public static Order getInstance(){ if(instance == null){ // 特别注意 此时写得不安全 ,见第3点 懒汉式 坏处 instance = new Order(); } return instance; } }
-
区分 饿汉式 和 懒汉式
-
饿汉式:
坏处 对象加载时间过程
好处 饿汉式是线程安全的
-
懒汉式:
好处 延迟对象创建
坏处 目前(上面)的写法是不安全的
-
-
单例设计模式的优点
单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置,产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方法
-
应用场景
-
网站计数器,否则难以同步
-
应用程序的日志应用
-
数据库连接池
-
项目中,读取配置文件的类
-
Application也是单例的典型应用
-
Windows的任务管理器和回收站
-
main()方法的语法
-
main()方法作为程序入口
-
本身也是个普通的方法,可以在类中调用其他类生成的main方法
public class MainTest { public static void main(String[] args) { Main.main(new String[10]); } } class Main{ public static void main(String[] args) { for (int i = 0; i < args.length; i++) { args[i] = "args" + i; System.out.println(args[i]); } } }
-
main()方法可以作为我们与控制台交互的方式(之前,使用scanner)
类的成员之四:代码块(或初始化块)
-
代码块的作用: 初始化类 对象
-
代码块如果有修饰的话,只能使用 static
-
分类: 静态代码块 vs 非静态代码块
-
静态代码块:
-
内部可以有输出语句
-
随着类的加载而自动执行,自始至终只会执行一次
-
作用: 初始化类的信息
-
如果一个类中定义了多个静态代码块,按照声明的先后顺序执行
-
静态代码块的执行要优先于非静态代码块
-
静态代码块内只能调用静态属性和静态方法,不能调用非静态的结构
-
-
非静态代码块:
-
内部可以有输出语句
-
随着对象的创建而自动执行
-
每创建一个对象,就执行一次
-
作用: 可以在创建对象时,对对象的属性等进行初始化
-
如果一个类中定义了多个非静态代码块,按照声明的先后顺序执行
-
非静态代码块内可以调用静态属性和静态方法,以及非静态的结构
-
非静态代码块的执行优先级 高于 构造器
注意:执行顺序 静态代码块 > 非静态代码块 > 构造器
-
引申:对属性可以赋值的位置(也是执行顺序)
-
默认初始化
-
显示初始化
-
在代码块中赋值
-
构造器中初始化
-
有了对象以后,可以通过"对象.属性"或"对象.方法"的方式,进行赋值
关于main()方法,代码块和构造器的典型例题
public class Father { static{ System.out.println("父类静态代码块"); } { System.out.println("父类非静态代码块"); } public Father() { System.out.println("父类构造器"); } } class Son extends Father{ static{ System.out.println("子类静态代码块"); } { System.out.println("子类非静态代码块"); } public Son() { System.out.println("子类构造器"); } public static void main(String[] args) { System.out.println("11111111111111111111"); System.out.println("********************"); new Son(); System.out.println("********************"); new Father(); System.out.println("********************"); new Son(); } }
-
运行结果为
父类静态代码块 子类静态代码块 11111111111111111111 88888888888888888888 父类非静态代码块 父类构造器 子类非静态代码块 子类构造器 88888888888888888888 父类非静态代码块 父类构造器 88888888888888888888 父类非静态代码块 父类构造器 子类非静态代码块 子类构造器
-
首先认清main()方法,本身也是个方法,运行main()前需要加载其所在类,而此代码恰好将main()方法写在了Son类中,而Son类又是 Father类 的子类,故运行main()方法前,已经加载了 Father 类 和 Son 类,那么打印"111"之前 就执行了 Father 和 Son 的静态代码块,之后才打印 "111" 和"888"
-
new 一个 Son 对象,调用了Son构造器,而Son的构造器中隐含第一句是 super() ,即调用了Father的构造器,又因为非静态代码块优先级高于构造器,故先执行 Father的非静态代码块,而后 Father的构造器,而后Son的非静态代码块 ,最后Son的构造器
关键字: final
-
可以用来修饰的结构: 类,方法,变量
-
用来修饰一个类,此类不能被其他类继承,即"太监类"
比如: String类,System类,StringBuffer类
-
用来修饰方法,表明此方法不能被重写
比如Object类中的getClass();
引申 关键字 native 表明该方法的方法体为底层的c或者c++实现的,具体源码不再展示出来
-
用来修饰变量:此时的变量 就称为是 常量了,注意:定义一个final属性,每个对象的该属性可以不同
final修饰属性,可以考虑赋值的位置有:
-
显式初始化
-
代码块中初始化
-
构造器中初始化
final修饰局部变量:
-
修饰方法中的形参,表明该形参是一个常量,该形参在获得一次值传递后,在这个方法体内部不能
改变其值了
-
-
static final: 用来修饰 属性: 称为全局常量
抽象类与抽象方法:abstract
有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类.抽象的类不能实例化了.
-
abstract可以修饰的结构:类,方法
-
修饰类
-
此类不能实例化
-
抽象类中一定有构造器,便于子类对象实例化的时候调用
-
开发中都会提供抽象类的子类,让子类对象实例化完成相关操作
-
-
修饰方法:
-
抽象方法只有方法的声明,没有方法体
-
包含抽象方法的类一定是一个抽象类,反之,抽象类中可以没有抽象方法
-
若子类重写了父类中所有的抽象方法后,此类方可实例化
-
若子类没有重写父类中的所有抽象方法,则此子类也是一个抽象,需要使用abstract修饰
-
-
abstract使用上的注意点
-
abstract不能修饰属性,构造器等结构
-
abstract不能用来修饰私有方法,静态方法(因为),final的方法,final的类
-
匿名子类和匿名对象
-
匿名对象
new Person();
-
匿名子类
假设Person为抽象类,那么此时不能选择new 一个Person 类的对象,但是可以用下列格式new一个匿名子类的对象
Person p = new Person(){ @Override方法 };
注意此时是在()和; 之间加了一个花括号
-
匿名子类的匿名对象
new Person(){ @Override方法 };
多态的应用:模板方法设计模式(TemplateMethod)
抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展,改造,但子类总体上会保留抽象类的行为方式.
解决问题:
当功能内部一部分实现是确定的,一部分实现是不确定的,这时可以把不确定的部分暴露出去,让子类实现
换句话说,在软件开发中实现一个算法时,整体步骤很固定,通用,这些步骤已经在父类中写好了,但是某些部分易变,易变部分可以抽象出来,供不同子类实现,这就是一种模板模式
接口:Interface
-
一方面,有时必须从几个类中派生出一个子类(即多继承,但java是单继承),有了接口,就可以得到多重继承的效果
-
另一方面,有时必须从几个类中抽取出一些共同的行为特征,而他们之间又没有is-a 的关系,仅仅是具有相同的行为特征而已.例如:鼠标,键盘,打印机,扫描仪,摄像头,充电器,MP3等都支持USB连接.
-
接口就是规范,定义的是一组规则,接口的本质是契约,标准,规范.
-
接口的使用;
-
使用interface 定义
-
java中,接口和 类 是并列的两个结构
-
如何定义接口:定义接口中的成员
-
JDK7及以前只能定义全局常量和抽象方法
-
全局常量: public static final 的(这三个修饰的关键字可以省略掉)
-
抽象方法: public abstract的
-
-
JDK8关于接口的改进: !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-
可以为接口添加静态方法和默认方法.从技术角度来说,这是完全合法的,只是它看起来违反了接口作为一个抽象定义的概念
-
静态方法:使用static关键字修饰,接口中定义的静态方法,只能通过接口直接调用,并执行其方法体.我们经常在相互一起使用的类中使用静态方法,你可以在标准库中找到像Collection/Collections或者Path/Paths这样成对的接口和类
-
默认方法:使用default关键字修饰,
-
可以通过实现类的对象来调用接口中的默认方法,
-
如果实现类重写了接口中的默认方法,调用时即调用的重写后的方法.
-
-
如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的方法,子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法.--->类优先原则
-
如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,那么在实现类没有重写此方法的情况下,报错.--->接口冲突
-
如何在子类(或方法类)的方法中调用父类,接口中被重写的方法
-
调用自己定义的方法: 方法名;
-
调用父类中声明的方法: super.方法名;
-
调用接口中的默认方法: 接口A.super.方法名;
接口B.super.方法名;
-
-
-
-
-
接口中不能定义构造器!意味着接口不可以实例化
-
Java中,接口通过让类实现 (implements(使生效,贯彻)) 的方式来使用
如果实现类覆盖了接口中的所有(注意是 所有 !!!)抽象方法,则此实现类就可以实例化
如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类,需用abstract修饰!!!!!!!.
-
Java类可以实现多个接口,弥补了java单继承性的局限性
格式: class A extends B implements C,D{
}
-
接口与接口之间可以继承,而且是多继承,子接口继承了父接口的所有方法.
-
接口的使用,能够体现多态性,开发中,体会面向接口编程
注意创建接口对象的四种方式
public class USBTest { public static void main(String[] args) { Computer com = new Computer(); // 1. 创建接口的非匿名实现类的非匿名对象 SanDisk sdcard = new SanDisk(); com.tranfer(sdcard);//此处体现了多态,tranfer()方法的形参需要一个USB,SanDisk实现了USB,传到此处可以用 // 2. 创建接口的非匿名实现类的匿名对象 com.tranfer(new SanDisk()); // 3. 创建接口的匿名实现类的非匿名对象 USB mp3 = new USB() { @Override public void start() { System.out.println("mp3启动usb"); } @Override public void stop() { System.out.println("mp3停止usb"); } }; com.tranfer(mp3); // 4. 创建接口的匿名实现类的匿名对象 com.tranfer(new USB() { @Override public void start() { System.out.println("手机启动usb"); } @Override public void stop() { System.out.println("手机停止usb"); } }); } } class Computer{ public void tranfer(USB usb){// 定义一种传输方法,形参实际上规定了传递时需满足USB协议 usb.start(); System.out.println("数据传输的具体细节"); usb.stop(); } } interface USB{// 定义一个接口,其实就是定义一种规范 void start(); void stop(); } class SanDisk implements USB{// 定义SanDisk实现了 USB 接口 @Override public void start() { System.out.println("闪迪优盘启动,连接USB3.0协议"); } @Override public void stop() { System.out.println("闪迪优盘退出,关闭USB3.0协议"); } }
-
面试题: 抽象类 与 接口 有哪些异同
-
相同:不能实例化,都可以被继承
-
不同:抽象类 有构造器,单继承,接口 不能声明构造器,单继承
-
接口例题
接口A定义了X为0
类B定义了X为1
(正常写代码不会这样写)
test类继承B,实现A
public class test extends B implements A { public void pX(){ //System.out.println(x); 编译报错 System.out.println(super.x);// 父类B的 x 的值 为1 System.out.println(A.x);// 接口的 x 的值 为0 } public static void main(String[] args) { new test().pX(); } } interface A { int x = 0; } class B { int x = 1; }
代理设计模式
代理模式是java开发中使用较多的一种设计模式,代理设计就是为其他对象提供一种代理以控制对这个对象的访问
public class NetWorkTest { public static void main(String[] args) { Server server = new Server();// 创建被代理类对象 ProxyServer proxyServer = new ProxyServer(server);// 创建代理类对象,并将被代理类对象传入 proxyServer.browse();// 通过代理类对象调用浏览的方法 } } interface NetWork {// 创建联网接口,有 浏览 方法 public void browse(); } // 被代理类,实现联网接口 class Server implements NetWork { @Override public void browse() {// 此方法定义被代理类要做的事 System.out.println("真实的服务器访问网络"); } } // 代理类,实现联网接口 class ProxyServer implements NetWork { private NetWork work;//定义接口作为代理类的属性!!!!!!!!! public ProxyServer(NetWork work){// 构造器 传入联网方法 this.work = work; } public void check() {// 创建联网前的检查 System.out.println("联网之前的检查工作"); } @Override public void browse() {// 重写浏览方法 check(); work.browse();// 使用代理类的 接口 属性 调用浏览方法,而这个接口属性还是 } }
应用场景:
-
安全代理:屏蔽对真实角色的直接访问
-
远程代理:通过代理类处理远程方法调用
-
延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象
分类:
-
静态代理(静态定义代理类)
-
动态代理(动态生成代理类)
JDK自带的动态代理,需要反射等知识
类的内部成员之五:内部类
-
当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内部类
-
在java中,允许一个类B的定义位于另一个类A的内部,B称为内部类,A称为外部类
-
Inner class一般用在定义它的类或语句块之内,在外部引用它时,必须给出完整的名称
-
Inner class的名字不能与包含它的外部类类名相同.
-
分类:
-
成员内部类(static成员内部类和非static成员内部类)
-
局部内部类(方法内,代码块,不谈修饰符),匿名内部类
-
-
成员内部类:
-
一方面作为外部类的成员
-
调用外部类的结构
-
可以被static修饰
-
可以被4种不同的权限修饰
-
-
另一方面,作为一个类:
-
类内可以正常定义属性,方法,构造器
-
可以被final修饰,表示此类不能被继承
-
可以被abstract修饰,表示该类不能被实例化
-
-
-
关注内部类的如下3个问题
-
如何实例化成员内部类的对象
-
如何在成员内部类中区分调用外部类结构
-
开发当中局部内部类的使用
/* 开发中局部内部类的使用场景 */ // 该方法返回一个实现了Comparable接口的类的对象 public Comparable getComparable(){ //那么首先应创建一个实现了Comparable接口的类:局部内部类,为何把内部类定义在方法中呢,因为该类只在该方法中使用 //方式一:创建一个该接口的有名实现类的有名对象 // class MyComparable implements Comparable{ // @Override // public int compareTo(Object o) { // return 0; // } // } // return new MyComparable(); //方式二:创建一个该接口的匿名实现类的匿名对象 return new Comparable() { @Override public int compareTo(Object o) { return 0; } }; }
/* 内部类的定义和实例化 */ public class InnerClassTest { public static void main(String[] args) { //创建非静态成员内部类Brain的实例 Person.Brain brain = new Person.Brain(); brain.think(); //创建静态成员内部类Bird的实例(非静态的成员内部类),需要先造 Person Person p = new Person(); Person.Bird bird = p.new Bird(); bird.sing(); bird.display("杜鹃"); } } class Person{ String name = "yrf"; int age; public void eat(){ System.out.println("人吃饭"); } //非静态成员内部类 class Bird{ String name = "小燕子"; public Bird(){ } public void sing(){ System.out.println("我是一只鸟"); Person.this.eat();//Person.this. 可以省略,内部类中调用外部类的方法9 } public void display(String name){ System.out.println(name);//方法形参 System.out.println(this.name);//内部类的属性 System.out.println(Person.this.name);//外部类的属性 } } //静态成员内部类 static class Brain{ private String shape; String name; public Brain(){ } public void think(){ System.out.println("大脑的思考过程"); } } //方法中的局部内部类 public void method(){ class AA{ } } //构造器中的局部内部类 public Person(){ class BB{ } } }
-
-
在某个方法A中的局部内部类B的方法C中,如果调用局部内部类所在的方法A中的局部变量的话,要求此局部变量声明为final的,但可不写final,默认写好了
异常处理
异常概述与异常的体系结构
-
异常: 在java中,将程序执行过程中发生的不正常情况称为异常,注意语法错误和逻辑错误不是异常.
-
java程序在执行过程中功能所发生的的异常可以分为两类:
-
Error:
java虚拟机无法解决的严重问题,JVM系统内部错误,资源耗尽等严重情况,比如:StackOverflowError(栈溢出),OutOfMemory(堆溢出),一般不编写针对性的代码进行处理.
-
Exception:
其他因编程错误或偶然的外部因素导致的一般性问题,可以使用针对性的代码进行处理,例如:
-
空指针访问
-
试图读取不存在的文件
-
网络连接中断
-
数组角标越界
-
-
-
捕获错误最理想的是在编译期间,但有的错误只有在运行时才发生,比如:除数为0,数组下标越界等
-
分类
-
编译时异常和运行时异常
-
-
-
异常的体系结构
-
java.lang.Throwable
-
java.lang.Error:一般不编写针对性的代码进行处理
-
java.lang.Exception:可以进行异常的处理
-
编译时异常(checked)
-
IOException
-
FileNotFoundException
-
-
ClassNotFoundException
-
-
运行时异常(unchecked)
-
NullPointerException
-
ArrayIndexoutOfBoundException
-
ClassCastException
-
NumberFormatException
-
InputMismatchException
-
ArithmeticException
-
-
-
-
异常处理机制一
-
编写程序时,经常要在可能出现错误的地方加上检测的代码,如进行x/y运算时,要检测分母为0,数据为空,输入的不是数据而是字符等,过多的if-else分支会导致程序的代码加长,臃肿可读性差,因此采用异常处理机制.
-
java异常处理的方式
-
方式一:try-catch-finally
-
方式二:throws + 异常类型
-
-
异常的处理:抓抛模型
-
过程一:"抛":程序在正常执行的过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象,并将此对象抛出,一旦抛出对象以后,其后的代码就不再执行
-
过程二:"抓":可以理解为异常的处理方式:
-
try-catch-finally
-
throws
-
-
-
try-catch-finally的使用:
try{
//可能出现异常的代码
}catch(异常类型1 变量名1){
//处理异常的方式一
}catch(异常类型2 变量名1){
//处理异常的方式二
}catch(异常类型3 变量名1){
//处理异常的方式三
}......
finally{
//一定会执行的代码
}
-
使用try将可能出现异常的代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,去catch中进行匹配
-
一旦try中的异常对象匹配到某一个catch时,就进入catch中进行异常的处理,一旦处理完成,就跳出当前的try-catch结构(在没有写finally的情况下).继续执行其后的代码
-
多个catch中的异常类型,如果没有子父类关系,则谁在上谁在下无所谓,如果满足子父类关系,则要求子类一定声明在父类的上面,否则报错
-
常用的异常对象处理的方式,用异常变量名调用------e.getMessage()
-
String getMessage()
-
printStackTrace()
-
-
在try结构中声明的变量,再出了try结构以后,就不能再被调用
-
使用try-catch-fianlly处理编译时异常,使得程序在编译时就不再报错,但是运行时仍可能报错,相当于我们使用try-catch-finally将一个编译时可能出现的异常,延迟到运行时出现
-
开发中由于运行时异常比较常见,所以我们通常就不针对运行时异常编写try-catch-finally,针对编译时异常,一定要考虑异常的处理.
-
finally的使用
-
finally是可选的
-
finally中声明的是一定会被执行的代码,即使catch中又出现异常了,或者try,catch中有return语句等情况
-
像数据库连接,输入输出流,网络编程Socket等资源,JVM是不能自动回收的,我们需要自己手动的进行资源的释放,此时资源释放就要放在finall中,比如scanner的close()方法!
-
-
异常处理机制二:throws+异常类型
-
"throws+ 异常类型"写在方法的声明处,指明此方法执行时,可能会抛出的异常类型.一旦当方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足throws后异常类型时,就会被抛出.异常代码后续的代码就不再执行!
-
体会:
try-catch-finally:真正的将异常处理掉了
throws方式只是将异常抛给了方法的调用者,并没有真正将异常处理掉
-
重写方法异常抛出的规则
子类重写的方法抛出的异常类型不大于父类被重写的异常类型
两种机制如何选择
-
如果父类中被重写的方法没有throws方式处理异常,则子类重写的方法也不能够使用throws,意味着如果子类重写的方法中有异常,必须使用try-catch-finally方式处理
-
执行的方法中,先后又调用了另外的几个方法,这几个方法是递进关系执行的(比如上一个方法产生的一些结果要给下一个方法当做参数使用),我们建议这几个方法使用throws的方式进行处理,而执行的方法a可以考虑使用try-catch-finally进行处理
关于异常对象的产生:
-
系统自动生成的异常对象
-
手动生成一个异常对象,并在运行时抛出(throw)
如何自定义异常类
-
继承于现有的异常结构,RuntimeException,Exception
-
提供全局常量:static final long serialVersionUID
-
提供重载的构造器
例题
public class EcmDef { public static void main(String[] args) { try { int a = Integer.parseInt(args[0]);//从命令行输入参数 int b = Integer.parseInt(args[1]); int res = ecm(a,b);//调用除法方法 System.out.println(res); }catch(NumberFormatException e){//数据类型不匹配的异常 System.out.println("数据类型不一致"); }catch (ArrayIndexOutOfBoundsException e){//缺少命令行参数的异常 System.out.println("缺少命令行参数"); }catch (ArithmeticException e){//除数为0的异常 System.out.println("除数为0"); }catch (EcDef ecDef) {//自定义的异常 ecDef.printStackTrace(); } } //除法方法,在定义时即抛出了自定义的除数小于0的异常 public static int ecm(int x,int y) throws EcDef{ if(x < 0 || y < 0){ throw new EcDef("数据不可以为负数!");//如果数据小于0,则抛出异常 } return x / y; } }
自定义的异常类EcDef 如果有数据小于0,则为异常
public class EcDef extends Exception{ static final long serialVersionUID = -3387516993124229948L;//定义全局变量,从Exception源码里直接拿来的 public EcDef(){//无参构造器 } public EcDef(String msg){//有参构造器 super(msg); } }
总结:异常处理5个关键字
-
try :执行可能产生异常的代码
-
catch:捕获异常
-
finally:无论是否发生异常,代码总被执行
-
throw:抛出异常---异常的生成阶段,手动抛出异常对象
-
throws:声明异常---异常的处理方式,声明方法中可能要抛出的各种异常类
面试题:
-
结构相似的
-
final,finally,finalize三者的区别
-
throw和throws
-
Collection和Collections
-
String,StringBuffer,StringBuilder
-
ArrayList,LinkedList
-
HashMap,LinkedHashMap
-
-
结构不相似的
-
抽象类,接口
-
== ,equals()
-
sleep(),wait()
-