java
基础运算
long a = 5645L;
float a = 3.141F
byte char short 结果做运算结果为int型;
float 范围大于long
编码与解码;Unicode 和 utf-8
数组初始化
int [][] arr = new int[4][];
外层初始化为null
内层不能调用 - 报错
java中二维数组是数组的数组,数组空间不是连续分配的,所以不要求二维数组每一维的大小相同。 二维简单数据类型数组的动态初始化如下: int a[ ][ ] = new int[2][ ]; a[0] = new int[3]; a[1] = new int[5]; 对二维复合数据类型的数组,必须首先为最高维分配引用空间,然后再顺次为低维分配空间。而且,必须为每个数组元素单独分配空间,前面不能省略,后面可以省略:int[][] i = new int[6][];
引用类型赋值
Person p1 = p2;
把p2的对象地址给了p1(指向了堆空间同一个对象实体)
堆、栈、方法区
堆存放的是对象实例
栈存放的是局部变量
方法区(Method Area) 存放的是被虚拟机加载的类信息、常量池、静态域、编译后的代码等数据
匿名对象使用
直接用
可变个数形参
在一个方法中,可变形参只能有一个
数据类型 … 变量名
public void show(int i){
System.out.println(i);
}
public void show(String ... s){
System.out.println("123123");
}
如果传入的参数不是show方法中给定的参数(包括无参数的情况)
那就执行
public void show(String ... s){
System.out.println("123123");
}
的内容
public void show(int x, String ... s){
System.out.println(s[0]);
}
但是如果你再重载一个:
public void show(String[] strs){
System.out.println("123123");
}
会报错
因为编译器认为两种方法等价
可以把前者当作后者。
应用例子
在用户查询某个关键字时,不知道查询几个关键字。就可以用可变形参.
基本数据类型的赋值, 传值。
引用数据类型的赋值,传地址。//数组名、类对象…
JavaBean
是一个Java语言写成的可重用组件
满足下列要求的Java类为JavaBean:
1.类是公共的
2.有一个无参公共的构造器
3.有属性和对应get、set方法
package
声明类和接口所属的包,放于首行
没"."一次代表一层文件目录
import 导入指定包下的类、接口
如果在源文件中,使用了不同包的同名类,必须有一个类以全类名的方式显示
例如
java.sql.Date date1 = new java.sql.Date(123456);
如果用了"xxx.*", 但是使用了其子包下的结构,那么必须显示import其子包
多态(前提:继承关系、方法重写)
父类的引用指向子类的对象(上转型)
虚拟方法:编译时看声明,运行时看对象
调用子类重写父类的方法
但是,多态只适用于方法,不适用于属性!!!!!
对于重载而言,重载的方法在编译时就确定了,但是多态在编译时是不确定的。所以重载是静态绑定,多态是动态绑定
对于上转型:不能调用子类特有的属性和方法
用强制类型转换就可以调用(下转型)
instanceof 关键字:判断前面的对象是否是后面的类,或者其子类、实现类的实例。如果是返回true,否则返回false。
a instanceof A;
在下转型时先判断,如果为true则可以转
Person a = new Man(); // a为Man的实例,所以可以Man b = (Man) a;
super
super()必须用在子类构造器首行!
类的构造器中this(形参列表)super(形参列表)只能二选一
== 运算符
对于基础数据类型,比较其值 ,注意不能和布尔类型比较
对于引用数据类型,比较的是其地址
注意:String是引用类型
getClass方法
getClass()方法返回的类型是class类型,可以理解为类的地址。
equals方法
只能用于引用数据类型
String、Data、File、包装类 的equals的方法已经进行了重写
对于自己定义的引用数据类型equals方法是需要手动来重写的(object中是比较的地址)
对于重写,可以参考String对equals的重写
用 == 比较地址;
判断null;
用方法.getClass();
用instanceof 比较是否为实例化对象;
比较值
可以用编译器自动生成equals和hashcode 方法
对于equals重写要遵循几个原则
对称性:
如果x.equals(y)
返回是“true”,那么y.equals(x)
也应该返回是
“true”。
自反性:
x.equals(x)
必须返回是“true”。
传递性:
如果x.equals(y)
返回是“true”,而且y.equals(z)
返回是“true”,那么z.equals(x)
也应该返回是“true”。
一致性:
如果x.equals(y)
返回是“true”,只要x和y内容一直不变,不管重复x.equals(y)
多少次,返回结果都是“true”。
其他:
任何情况下,x.equals(null)
,永远返回是“false”:
x.equals(与x不同类型的对象)
水远返回是“false”。
toString方法
当输出对象的引用时,其实输出的是其toString方法(具体可看源码)
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
'@'后输出的是对象的虚拟地址,非操作系统中的地址,因为java在操作系统之上有一个JVM
String、Data、File、包装类都重写了toString方法
单元测试
eclipse:
1.选中工程 - 右键 - build path - add libraries - JUnit 4
2.创建类:必须为public 、提供公共无参构造器
3.声明单元测试方法:此方法为public且无返回值、形参
4.单元测试需要声明 @Test (并导入包)
5.在方法里面写代码
6.写好代码后,左键双击方法名,右键:run as - JUnit Test
说明:
如果执行结果无异常:绿条
如果执行出现异常:红条
但是在开发中,不会这么麻烦,直接导入包,直接写
IDEA:
https://blog.csdn.net/qq_45560445/article/details/110290513
JUnit4利用JDK5的新特性Annotation,使用注解来定义测试规则。
这里讲一下以下几个常用的注解:
- @Test:把一个方法标记为测试方法
- @Before:每一个测试方法执行前自动调用一次
- @After:每一个测试方法执行完自动调用一次
- @BeforeClass:所有测试方法执行前执行一次,在测试类还没有实例化就已经被加载,所以用static修饰
- @AfterClass:所有测试方法执行完执行一次,在测试类还没有实例化就已经被加载,所以用static修饰
- @Ignore:暂不执行该测试方法
@BeforeClass
public static void setUpBeforeClass() {
System.out.println("BeforeClass");
}
@AfterClass
public static void tearDownAfterClass() {
System.out.println("AfterClass");
}
@Before
public void setUp() {
System.out.println("Before");
}
@After
public void tearDown() {
System.out.println("After");
}
@Test
public void test1() {
System.out.println("test1");
}
@Test
public void test2() {
System.out.println("test2");
}
@Ignore
public void test3() {
System.out.println("test3");
}
包装类(wrapper)
如:
- byte - Byte
- int - Integer
- char - Character
- double = Double
右边是左边的包装类
学会基本类型 、包装类、String类间的转换
基本数据类型 -> 包装类(方法旧了,在JDK5.0有了自动装箱和自动拆箱,后文会有讲述)
int a = 100;
Integer in1 = new Integer(a);
System.out.println(in1);
---
Integer in2 = new Integer("123");
int a = in2.intValue();
System.out.println(in2.intValue());
---
Integer in2 = new Integer("123abc"); //报错!
a = in2.intValue();
System.out.println(in2.intValue());
---
Float f1 = new Float(12.3);
Float f2 = new Float("12.3");
---
Boolean t1 = new Boolean("true123");//不报错! 具体可看源码
System.out.println(t1);
注意:
Boolean b;
System.out.println(b);
结果为null
包装类 -> 基本数据类型(做加减乘除时)
Integer in2 = new Integer("123");
int a = in2.intValue();
.xxxvalue();
但是在JDK5.0时加入了自动装箱和自动拆箱的新特性
自动装箱:
int a = 100;
Integer in1 = a;
自动拆箱:
Integer in1 = new Integer(123);
int a = in1;
包装类、基本数据类型 -> String
//法1 连接运算
int num = 10;
String str = num + ""; //右边运算结果为字符串
//法2 String方法
float f1 = 12.3f;
String str = String.valueOf(f1);
String -> 包装类、基本数据类型
调用parsexxx()方法
String str = "123";
int num = (int)str; // 不可!!
int num = Integer.parseInt(str); // 注意str不要有字母等字符,否则是NumberFormatException异常
String str2 = "true2";
boolean b = Boolean.parseBoolean(str2);
包装类面试题:
第一道
Object obj = true ? new Integer(1) : new Double(2.0);
System.out.println(obj);
Object obj2;
if(true){
obj2 = new Integer(1);
}else{
obj2 = new Double(2.0);
}
System.out.println(obj2);
上下输出结果是否相同?
不同
第一个为1.0
第二个为1
因为三元运算符编译时要求冒号两边类型为同一类型
所以有一个Integer类型的类型提升到Double
两边的类型不相同,那么他们需要对交集类型的自动参考转换。例如如下这段代码
String str = "abc";
StringBuilder strbu = new StringBuilder("def");
boolean boo = true;
CharSequence cs = boo ? str : strbu;
因为String和StringBuilder都实现了CharSequence这个接口。
第二道:
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j);
Integer a = 1;
Integer b = 1;
System.out.println(a == b);
Integer c = 128;
Integer d = 128;
System.out.println(c == d);
false
true
false
对于第三个:
去IntegerCache类看源码
里面有一个cache[]数组存的-128 ~ 127
因为这个范围数字使用频率高,所以提前打表提高了内存中的速度
如果大于了127, 那么会自动给你new一个Integer对象,所以为false
所以对于第二个:
内部定义了 静态 IntegerCache类,其中定义了cache数组 Integer[]类型
保存了-128到127整数。如果用自动装箱,范围在其范围内,可以直接用数组的元素,不用new了。提高了效率
static 关键字
对于一个类,其实是在描述其对象的属性和行为,并没有产生实质上的对象,只有通过new关键字才会产生出对象,这时系统才会分配内存空间给对象。对于有些数据,我们只希望其在内存空间只有一份,例如一个人的种类只能是人,每个人都共享这个变量而不必在每个实例对象中创建一个变量来存放“国家”
public class Main {
public static void main(String[] args) {
Dog d = new Dog();
Dog d2 = new Dog();
d.food = "123";
System.out.println(d2.food);
}
}
class Dog{
int age;
static String food = "shit";
Dog(){};
}
输出结果为123,因此如果在对象中改变其值,那么在所有对象中这个属性的值都将改变
非静态属性:实例变量
静态属性:静态变量 类变量
- 静态变量随着类的加载而加载(静态变量的加载要早于对象的创建, 可以通过 类.静态变量调用)
- 类只会加载一次,静态变量也只会存在一个(缓存在内存中, 存在方法区中(方法区中的静态域))
- 例如:Math.PI; System.out
静态方法
static修饰的方法为静态方法
- 静态方法只能调用静态的方法或属性
- 不能使用this、super关键字(对于调用的静态属性,省略的不是this.变量,而是类.变量)
- 非静态可以调用静态
如何确定是否要修饰static
- 这个属性不会随着对象的不同而不同(不是变量完全不变,而是相对)
- 操作静态属性的方法通常为静态
- 工具类的方法,习惯声明为static。例如:Math、Arrays、Collections
- 类中的常量也会为static :static final int a = 3;
注意事项
在构造器中初始化static变量时,每一个构造器都要赋值
一个属性是静态,那么其set get方法也会是静态,编译器生成方法时会是静态
单例设计模式
设计模式是一种优选的代码结构、编程风格、解决问题的思考方式。
而单例设计模式是:采取一定方法,让整个软件系统,对于某个类只能存在一个对象实例,并且只有一个取得其对象实例的方法。
(1)将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
(2)在该类内部产生一个唯一private static的实例化对象
(3)定义一个公共静态方法返回这个唯一对象。
优点:
减少系统性能的开销(当一个对象要消耗很多资源时,如读取配置、产生其他依赖对象)
两种写法:
饿汉式:
//单例设计模式
class Bank{
//构造方法私有化
private Bank(){
}
//内部创建 private static 对象
private static Bank instance = new Bank();
//公共静态方法返回对象
public static Bank getInstance(){
return instance;
}
}
懒汉式:
class DogFather{
private DogFather(){}
private static DogFather instance = null;
public static DogFather getInstance(){
if(instance == null){
instance = new DogFather();
}
return instance;
}
}
注意:静态方法,静态块或变量属于该类。而构造函数属于该对象,并在使用 new 运算符创建实例时调用。构造函数不是类属性
饿汉式就是太饿了,急了,在开始就把对象造好了
坏处:对象加载时间长
好处:线程安全
懒汉式就是太懒了,在用的时候才开始造对象
坏处:目前写法是线程不安全的 —>到多线程内容再修改(线程锁)
好处:延迟对象的创建
面试写饿汉式,毕竟线程安全,或者写修改后的懒汉式
应用场景
网站计数器:单例模式,否则难以同步
应用程序日志应用:因为共享日志是打开的,所以只能由一个实例操作,否则内容不好追加
数据库连接池:因为数据库连接是一种数据库资源
读取配置文件的类:没必要每次使用配置文件数据都生成一个对象去读
Application 也是典型应用
Windows任务管理器
Windows回收站:整个系统运行过程,回收站一直维护仅有一个实例
main方法详解
- 不一定只能写一个主方法,但是程序只能选择一个入口执行,那么另一个主方法就是普通静态方法了
- 主方法的 public 是权限声明,给JVM一个调用权限
- 主方法内直接调用主类(public修饰的类,一个.java文件只能有一个)的其他方法时,其他方法必须为静态,否则需要new对象来调用
- main方法的String[]参数可以作为和控制台交互方式(用的不多)
- 控制台交互(用的不多)
代码块(初始化块)
类的成员之一
{
}
或
static{
}
静态代码块和非静态代码块
静态代码块随着类的加载而执行(只执行一次)
- 初始化类的信息
- 不一定只有一个,并且根据声明的先后顺序去执行
- 只能调用静态的属性或者方法
非静态代码块随着对象的创建而执行(多次)
- 创建对象时,对对象的属性进行初始化
- 不一定只有一个,并且根据声明的先后顺序去执行
一般不会写多个
class Test{
{
a = 2;
}
int a = 1;
}
最后属性a的初始值为1,所以他们优先级相同
执行顺序:
- 默认初始化
- 显式初始化/代码块赋值(一般代码块都在声明的后面)
- 构造器
- 创建对象后进行的赋值
静态代码块 静态方法区别
- 一般情况下,如果有些代码必须在项目启动的时候就执行的时候,需要使用静态代码块,这种代码是主动执行的;
- 需要在项目启动的时候就初始化,在不创建对象的情况下,其他程序来调用的时候,需要使用静态方法,这种代码是被动执行的. 静态方法在类加载的时候 就已经加载 可以用类名直接调用,比如main方法就必须是静态的 这是程序入口
- 两者的区别就是:静态代码块是自动执行的;
- 静态方法是被调用的时候才执行的.
- 静态块是在java加载的时候只运行一次的,并且是先执行的代码块。
final关键字
可以用来修饰类、方法、变量
修饰类时:该类不能被继承 例如:String类、System类、StringBuffer类
修饰方法时:方法不能被重写 例如:getClass()方法
修饰变量:变量变常量(名字变成大写)
赋值的三个位置:显式初始化(每个对象属性值相同)、代码块中初始化、构造器中初始化(每个对象的这个属性值不同)
在构造器中初始化时,注意:每一个构造器都要赋值
不能用方法给final 变量赋值(对象的加载、与对象的创建, final已经加载了,用方法不能对常量进行修改)
static final 用来修饰:全局常量(接口)
native
代表下面要调用底层的C/C++了,不会用java语言实现
抽象类和抽象方法
父类非常抽象,没有具体的实例,这样的类叫做抽象类
修饰类:
- 类不能实例化对象
- 类中一定要有构造器,便于子类实例化时调用
- 开发中,都会提供抽象类的子类,让子类对象实例化
有抽象方法的类一定是抽象类
抽象类中可以没有抽象方法
修饰方法:
- 抽象方法只方法的声明。没有方法体
- 子类重写了抽象方法后,子类方可实例化
- 如果没有重写所有的抽象方法后,则此类是抽象类,需要用abstract修饰
- abstract不能用来修饰属性、构造器等结构
- 不能用来修饰私有方法、静态方法 、final方法、final类等
- 子父类中的同名同参数的方法要么都声明为 非static(重写),要么都声明为 static(不是重写)
抽象类匿名子类
Person p = new Person(){
@Override
public void eat(){
}
@Override
public void breath(){
}
}
多态的应用:模板方法设计模式
当功能的一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。(抽象出去)这是一种模板模式
abstract class T{
public void spendtime(){
double start = System.currentTimeMillis();
code();
double end = System.currentTimeMillis();
System.out.println((end - start));
}
public abstract void code();
}
class T1 extends T{
@Override
public void code() {
}
}
接口
有时需要从几个类中派生出一个子类,继承他们所有的的属性和方法。
但是java不支持多继承。
也有时,必须从几个类中抽取一些共同的行为特征。比如键盘、鼠标、打印机都支持USB连接
接口的本质是:契约、标准、规范
接口由类去实现
定义接口 :
- 接口中不能写构造器(意味着接口不能被实例化(抽象类有构造器))
- java开发中,类去实现接口(implements)
- 如果实现类实现了接口中所有抽象方法,则此类可以去实例化。如果没有实现,那么此实现类仍然为一个抽象类
- 类可以实现多个接口(extends C implements A, B)先写继承,后写实现。(中间没有逗号)
- 类和类叫继承、类和接口叫实现、接口和接口叫继承(也可以多继承)
- 接口的具体使用体现了多态性
版本特性
- JDK7以前:只能定义全局常量public static final和抽象方法public abstact
- JDK8:除此之外还可以定义静态方法、默认方法( 接口的方法默认为public abstract、定义的就算是变量,其实也省略了public static final )
- 接口定义的静态方法,只能通过接口来调用(有点像工具类)
- 默认方法必须加default,,通过实现类调用(在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性 例如:java8 API中的Collection、List、Comparator 提供了丰富的默认方法)
- 默认方法也可以重写,类似于继承,但是重写时不用加default(有类继承的话是类优先,如果实现两个接口,并且两个接口都有同名同参数的方法,且实现类未重载的话,会报错,所以不想报错必须要重写方法)
- 直接在 子类(实现类) 用接口中的默认方法:A.super.method();
面试题:
-
抽象类和接口有那些异同
同:
- 接口也可以做形参,但是传进来的参数是继承接口的类的实例化(满足多态)
- 接口的主要用途就是被类实现(开发项目往往都是面向接口编程)
- 需要被继承或实现才能发挥他们的作用
异:
- 抽象类可以定义构造器,可以有抽象方法和具体方法,而接口只能有抽象方法,且不能有构造器。
- 抽象类的成员可以是private、默认、protected、public的而接口只能是public和默认的(也是public)
- 抽象类可以有变量、接口只能有常量
- 有抽象方法的类必须是抽象类。但是抽象方法未必有抽象类
- 抽象方法必须被显示定义为public abstract, 接口的方法默认为public abstract 也可显示定义
- 抽象类可以包含Field,方法(普通方法和抽象方法)、构造器、初始化块、内部类、枚举类6种成分。接口里可以有Field,方法,内部类,枚举。
- 抽象类只能单继承,接口支持多继承。
注意:
1)abstract和final不能同时修饰方法;
2)abstract和static不能同时修饰方法;
3)abstract和private不能同时修饰方法
接口的非匿名实现类的匿名对象
接口的匿名实现类的非匿名对象:
USB phone = new USB(){
@Override
public void start(){
System.out.println(123);
}
@Override
public void end(){
System.out.println(123);
}
}
接口的匿名实现类的匿名对象
A.method(new USB(){
@Override
public void start(){
System.out.println(123);
}
@Override
public void end(){
System.out.println(123);
}
});
接口应用:代理模式
为其他对象提供一种代理(proxy)以控制对这个对象的访问
//接口
interface Net {
public void connect();
}
//被代理类
class Server implements Net {
@Override
public void connect() {
System.out.println("服务器连接");
}
}
//代理类
class ProxyServer implements Net {
private Server server;
ProxyServer(Server server) {
this.server = server;
}
public void check() {
System.out.println("检查完毕");
}
@Override
public void connect() {
check();
server.connect();
}
}
应用场景:
安全代理:屏蔽对真实角色的直接访问
远程代理:通过代理类处理远程方法的调用(RMI)
延迟加载:先加载轻量级的代理对象, 真正需要再加载真实对象(比如开发一个大文档查看软件,大文档有大的图片,在打开文件时,不可能将所有图片显示出来。可以使用代理模式,当需要查看图片时,用proxy来进行大图片的打开)
分类:
-
静态代理
-
动态代理
JDK自带的动态代理,需要反射等知识
工厂设计模式
实现了创建者和调用者的分离,即创建对象的具体工程屏蔽隔离起来,提高了灵活性
设计模式和面向对象设计原则都是为了项目更容易的进行拓展和维护,解决方式就是一个分工
核心本质:工厂方法代替new操作、将选择实现类、创建对象统一管理和控制。从而将调用者和实现类解耦
反射机制和配置文件的巧妙结合(Spring中完美体现)
- 简单工厂模式
- 工厂方法模式
- 抽象工厂模式(最为抽象、最具一般性的)
面向对象的设计原则
原则一:(SRP:Single responsibility principle)单一职责原则又称单一功能原则
核心:解耦和增强内聚性(高内聚,低耦合)
描述:类被修改的几率很大,因此应该专注于单一的功能。如果你把多个功能放在同一个类中,功能之间就形成了关联,改变其中一个功能,有可能中止另一个功能,这时就需要新一轮的测试来避免可能出现的问题。
原则二:开闭原则(OCP:Open Closed Principle)
核心思想:对扩展开放,对修改关闭。即在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展。根据开闭原则,在设计一个软件系统模块(类,方法)的时候,应该可以在不修改原有的模块(修改关闭)的基础上,能扩展其功能(扩展开放)。
扩展开放:某模块的功能是可扩展的,则该模块是扩展开放的。软件系统的功能上的可扩展性要求模块是扩展开放的。
修改关闭:某模块被其他模块调用,如果该模块的源代码不允许修改,则该模块修改关闭的。软件系统的功能上的稳定性,持续性要求是修改关的。
原则三:里氏替换原则(LSP:Liskov Substitution Principle)
核心:
1.在任何父类出现的地方都可以用他的子类来替代(子类应当可以替换父类并出现在父类能够出现的任何地方)子类必须完全实现父类的方法。在类中调用其他类是务必要使用父类或接口,如果不能使用父类或接口,则说明类的设计已经违背了LSP原则。
2.子类可以有自己的个性。子类当然可以有自己的行为和外观了,也就是方法和属性
3.覆盖或实现父类的方法时输入参数可以被放大。即子类可以重载父类的方法,但输入参数应比父类方法中的大,这样在子类代替父类的时候,调用的仍然是父类的方法。即以子类中方法的前置条件必须与超类中被覆盖的方法的前置条件相同或者更宽松。
4.覆盖或实现父类的方法时输出结果可以被缩小。
原则四:依赖倒转原则(DIP:Dependence Inversion Principle)
别名:依赖倒置原则或依赖反转原则
核心:要依赖于抽象,不要依赖于具体的实现
1.高层模块不应该依赖低层模块,两者都应该依赖其抽象(抽象类或接口)
2.抽象不应该依赖细节(具体实现)
3.细节(具体实现)应该依赖抽象。
三种实现方式:
1.通过构造函数传递依赖对象
2.通过setter方法传递依赖对象
3.接口声明实现依赖对象
原则五:接口分离原则(ISP:Interface Segregation Principle)
核心思想:
不应该强迫客户程序依赖他们不需要使用的方法。
接口分离原则的意思就是:一个接口不需要提供太多的行为,一个接口应该只提供一种对外的功能,不应该把所有的操作都封装到一个接口当中.
分离接口的两种实现方法:
1.使用委托分离接口。(Separation through Delegation)
2.使用多重继承分离接口。(Separation through Multiple Inheritance)
原则六:合成复用原则(CRP:Composite Reuse Principle)
核心思想:
尽量使用对象组合,而不是继承来达到复用的目的。该原则就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分:新的对象通过向这些对象的委派达到复用已有功能的目的。
复用的种类:
1.继承
2.合成聚合
注:在复用时应优先考虑使用合成聚合而不是继承
原则七:迪米特原则(LOD:Law of Demeter)
又叫最少知识原则
核心思想:
一个对象应当对其他对象有尽可能少的了解,不和陌生人说话。
(类间解耦,低耦合)意思就是降低各个对象之间的耦合,提高系统的可维护性;在模块之间只通过接口来通信,
而不理会模块的内部工作原理,可以使各个模块的耦合成都降到最低,促进软件的复用
注:
-
在类的划分上,应该创建有弱耦合的类;
-
在类的结构设计上,每一个类都应当尽量降低成员的访问权限;
-
在类的设计上,只要有可能,一个类应当设计成不变;
-
在对其他类的引用上,一个对象对其它对象的引用应当降到最低;
-
尽量降低类的访问权限;
-
谨慎使用序列化功能;
-
不要暴露类成员,而应该提供相应的访问器(属性)
UML统一建模语言,java中七种设计原则
UML统一建模语言。
类与类,类和接口,接口和接口之间的关系。
1、实现关系(一个类实现一个接口)
2、泛化关系(一个类继承另一个类)
3、关联(1)、依赖关系:一个类是另一个类的方法局部变量,方法的参数或方法返回值。2)、聚合关系:一个类是另一个类的属性,是整体和部分的关系。3)、组合关系:一个类是另一个类的属性,是整体不可分割的一部分,是强聚合。)
4、单一职责:一个类而言,应该仅有一个引起它变化的原因,永远不要让一个类存在多个改变的理。一个类只应该做和一个任务相关的业务,不应该把过多的业务放在一个类中完成。
迪米特法则:
一个软件实体应当尽可能少的与其他实体发生相互作用。
七种设计原则总结
单一职责原则:一个类只应该做和一个职责相关的事情,不要把过多的业务放在一个类中完成。
迪米特法则:软件实体之间应该做到最少的交互。不要和陌生人说话。调用方只关心他需要使用的方法
接口隔离原则:使用专门的接口,比用统一的接口要好。便于分工,在实现接口时,不应该看到自己不用关心的方法。
开闭原则:软件实体应该对扩展开放,对修改关闭。开闭原则是设计原则的核心原则,其他的设计原则都是开闭原则表现和补充。实现开闭原则的方法就是抽象。
聚合/组合复用原则。多使用聚合/组合达到代码的重用,少使用继承复用。
依赖倒置原则:面向抽象编程,不要面向具体编程。
面向对象七大设计原则
1、 开闭原则
2、 里氏替换原则
3、 单一职责原则
4、 接口隔离原则
5、 依赖倒置原则
6、 迪米特原则
7、组合/聚合复用原则
面试题:
//改错题
interface A{
int x = 9;
}
class B{
int x = 1;
}
class C extends B implements A{
public void p(){
System.out.println(x);
}
public static void main(String [] args){
new C().p();
}
}
p方法没有指明输出的x是哪一个
可以给改成super.x或者A.x
内部类
当一个事务的内部,一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部的事务提供服务,那么这个内部完整的结构最好使用内部类。
-
inner class 一般用在定义它的类或者语句块之内,在外部引用必须给出完整的名称
inner class 的名字不能和外部类名字相同
分类:
-
成员内部类(static 内部类、非static 内部类)
-
是一个类, 可以用final、abstract修饰
-
调用外部属性(非静态)
Person.this.eat();
-
-
局部内部类(不谈修饰符)、匿名内部类
- 方法内
- 代码块内
- 构造器内
关注三个问题:
- 如何实例化内部类的对象
- 如何在内部类区分调用外部类的结构
- 开发中局部内部类的使用
question1:
A a = new A();
A.B b = new A.B();
A.C c = a.new C();
question2:
class A{
Stirng name;
class B{
String name;
public void display(String name){
System.out.println(name);//方法的形参
System.out.println(this.name);//内部类的属性
System.out.println(A.this.name);//外部类的属性
}
}
}
question3:
//返回实现了一个Comparable接口类的对象
public Comparable getComparable(){
class MyComparable implements Comparable{
@Override
public int compareTo(Object o){
return 0;
}
}
return new MyCompareable();
}
//法2
return new comparaTo(Object o){
@Override
public int compareTo(Object o){
return 0;
}
}
内部类注意事项
//方法中的内部类调用方法中的属性
public void menthd(){
int num = 10;
class A{
Sustem.out.println(num);
}
}
此代码合法(在JDK8及以后,num处省略了final)
在JDK8之前需要手动加final
因为一个在栈里一个在堆里
因为外部类生成一个字节码文件、内部类也是一个独立的字节码文件
从语法来说是对的,但是前者的属性的声明周期延续下来可能有些困难
加了final 相当于传了一个副本,只能用不能改
异常
很多问题不是靠代码能避免的,比如输入的格式不对、读取文件是否存在。
异常:将程序执行发生的不正常的情况称为异常。(语法错误和逻辑错误不是异常)
分类:
- Error:Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况:StackOverflowError(栈溢出(例如:递归调用)) 和 OOM(堆溢出(例如:int/integer数组类型new的太大了)。一般不编写针对性的代码进行处理
- Exception:其他因编程错误或者偶然外在因素导致的一般性问题,可以使用针对性的代码进行处理
- 空指针
- 试图读取不存在的文件
- 网络连接中断
- 数组角标越界
对于错误的解决方法:
- 遇到错误就停止运行
- 编写时考虑错误
有的错误只有运行时发生:
除数为0、数组越界
分类:
- 编译时异常(javac.exe)字节码文件
- 运行时异常(java.exe) 内存中加载、运行类
一、 异常的体系结构
java.lang.Throwable
- java.lang.Error :一般不编写针对性代码进行处理
- java.lang.Exception:可以进行异常的处理
- 编译时异常(chekced)
- IOExecption
- FileNotFoundException
- ClassNotFoundException
- IOExecption
- 运行时异常(unchecked)
- NullPointerException
- ArrayIndexOutOfBoundsException
- NumberFormatException
- ArithmeticException
- 编译时异常(chekced)
除了RuntimeException 其他都是编译时异常。(指java.lang.Exception下的异常)
面试题:
常见的异常有哪些? 举例说明、
异常处理机制
- try - catch - finally (自己处理)
- throws + 异常类型(抛出异常,最后抛到main,还是无法处理,java程序就挂了)
- try with resources?
java提供的异常处理的抓抛模型
如果执行中出现异常,会生成一个异常类对象,该异常对象将提交给java运行时系统,这个过程称为抛出异常
异常对象的生成
- 虚拟机自动生成:虚拟机检测到程序出现了问题,如果当前代码没有找到相应的处理程序,就会在后台自动创建对应异常类的实例对象并抛出—自动抛出
- 开发人员手动创建并抛出:Exception exception = new ClassCastException(); —创建好的异常对象不抛出对程序没有任何的影响,和创建一个普通对象一样
过程二:抓的过程 — 可以理解为异常的处理方式
- try - catch - finally
- throws
try - catch - finally的使用(处理编译时异常,但是运行时仍可能报错)
try{
}catch(异常类型 变量名){
}catch(异常类型2 变量名2){
}finally{
一定会执行的代码
}
一旦在try的某个语句出现异常,后面的语句不会执行。
匹配到一个catch时,就进入catch中进行异常处理,处理完成就跳出当前的try - catch结构(前提时没写finally)
如果catch中的异常类型没有子父类关系,声明顺序不一定
如果catch中的异常类型满足子父类关系,要求子类一定声明在父类的上面否则报错
catch中的处理
catch(NumberFormatException e){
...
System.out.println(e.getMessage);
e.printStackTrace();
}
try {}中声明的变量,在出了这个结构后就不能再调用
finally
finally一定会被执行
public int test(){
try{
int a = 10;
int b = 0;
System.out.println(a/b);
//体会加了fianlly 和 不加finally的区别
return 1; // 但是报了异常所以不会返回1,那么我得考虑再catch中return
}catch(ArithmeticException e){
e.printStackTrace();
//看这里
return 2;
}finally{
System.out.println(123);
}
System.out.println(123);
}
如果catch中出现了异常,finally中的语句仍然可以执行(即catch又出现了异常,try中有return、catch中有return的情况)
public static int m(){
int a = 10;
int b = 0;
try{
System.out.println(a / b);
return 0;
}catch(ArithmeticException e){
return 1;
}finally{
System.out.println("finally");
return 2;
}
}
最后的返回值为2
像数据库连接、输入输出流、网络编程Socket等资源,JVM不能自动回收,需要手动进行资源的释放。此时资源的释放必须声明再finally中
对于try catch操作有快捷键的(Ctrl + Alt + T)
try catch结构可以嵌套
开发中运行时异常一般不考虑
编译异常当然时必须考虑的
throws
method() throws + 异常类型
只是让上级去处理异常(谁调用抛给谁)
public void method() throws 异常1, 异常2{
}
上级可以用 try - catch - finally 处理, 可以继续抛给上级
异常后续的代码就不再执行了
重写异常抛出的规则
- 子类重写的方法抛出异常类型不大于父类被重写的方法抛出的异常类型
- 如果父类重写的方法没有throws方式处理异常,子类重写的方法也不能使用throws,意味着如果子类重写的方法有异常,必须使用 try - catch - finally 处理。(多线程中父类有一个run方法,这个方法就没有抛异常,子类只能用try - catch - finally)
- 执行 方法a中,先后又调用了另外几个方法,这几个方法是递进关系执行的。建议这几个方法 throws 的方式处理,然后再a中用 try - catch - finally 处理
关于手动抛出异常
当结果不是我们期望时
public void regist(int id) throws Exception{
throw new Exception("132");
}
throws是处理(传给上级)
throw是抛出
用户自定义异常类
- 继承于现有的异常结构:RuntimeException / Exception
- 提供全局常量:serialVersionUID ()类唯一的标识(IO流时会讲到)
- 提供重载的构造器
- 如果继承于 RuntimeException 不需要在调用的结构 throws ,因为是运行时异常。如果继承的是 Exception 就必须要throws 因为就是编译异常了
class MyExecption extends RuntimeException {
static final long serialVersionUID = -7034825490745766939L;
public MyExecption(){
}
public MyExecption(String msg){
super(msg);
}
}
面试题:
final、finally、finalize 之间是什么关系?
类似:
throw、throws
Collection、Collections
String、StringBuffer、StringBuilder
ArrayList、LinkedList
HashMap、LinkedHashMap
重写和重载
不类似:
抽象类、接口
==、equals()
sleep、wait()