JAVA面向对象设计
JAVA面向对象设计
这篇文章主要是小马哥(Apache Dubbo PMC、Spring Cloud Alibaba 项目架构师,通过 SUN Java(SCJP、SCWCD、SCBCD)以及 Oracle OCA 等的认证)讲解的一些架构或者设计的心得,我听完很有收获,于是做了一些笔记。
通用设计-类/接口名设计
举例子:
单名词: String
双名词: ArrayLit
形容词+名词: LinkedList
able结尾:形容词,以“v+able”这种方式构成的形容词,其意义为“能…的”、“可以(被)…的”、 “适合于…的”“值得…的”等。
例如: Executable
通常来说形容词用在抽象类以及接口上,但是也不绝对,例如接口Member
通用设计-可访问性
public:公开
protected:包|子类,不可以用于修饰最外层的class(内置类属于类成员), 语言规范决定的
default:包权限
private:类自己访问,不可以修饰class,属于私有API
接口设计
要求无状态,具体来说
完全抽象(JAVA8之前)
局部抽象(JAVA8+) default修饰是为了方法兼容
单一抽象(JAVA8函数式接口)
通用设计-可继承性
final:final就是最终的意思,意味着不允许再被扩展,也就是final修饰之后就不具备继承性,所以final不可能与abstract同时使用
String为啥会被final修饰,也就是不循允许被扩展?
1)、String value = "Hello"; //这其实是一种语法特性, 根据面向对象的思想所有的对象应该是被new出来
2)、String value2 = new String("Hello"); //这个是合法的写法,但是会被人视作异类
子类实例化的时候会实例化出父类接口与数据,但是能不能访问另说:
1)其实是对象类型常量化,常量化是我们的原生类修,赋值方式,例如int a = 1;
C语言中也有类似支持 char* var = malloc(length(char)*8) var = "hello" 先申请空间,然后再赋值
对于String中很多的方法,如果允许被继承的话也就是方法允许被重写,但是这例如hashcode方法,重写后会有安全性问题,虽然String不可变,但是对象是可以通过反射修改的
String value = "Herllo";
String value2 = "Hello";
// Hello , Hello
System.out.println(value + ","+value2);
char[] chars = "World".toCharArray();
Field valueFiled = String.class.getDeclaredField("value");
valueFiled.setAccessible(true);
valueFiled.set(value2,chars);
// Hello , World
System.out.println(value + ","+value2);
反射涉及安全问题, 那么如何防止反射入侵呢?
可以通过RuntimePermission控制,关于其具体使用可参考连接
简单介绍如下:
在 Java 中,RuntimePermission
类用于表示安全管理器在运行时检查的权限。Java 安全管理器(Security Manager)是 Java 安全体系的一部分,它允许开发者在 Java 应用程序中对特定的操作进行控制和限制,以确保应用程序在安全环境下运行。
RuntimePermission
类用于授予或拒绝应用程序对某些系统资源或操作的访问权限。它可以用于实现应用程序级别的安全策略,以防止未经授权的访问和潜在的安全漏洞。
常见的 RuntimePermission
权限包括:
- createClassLoader:允许应用程序创建类加载器。如果没有此权限,应用程序将无法创建自定义类加载器。
- getClassLoader:允许应用程序获取当前线程的类加载器。如果没有此权限,应用程序将无法获取类加载器。
- setSecurityManager:允许应用程序设置或更改安全管理器。如果没有此权限,应用程序将无法设置自定义的安全管理器。
- setIO:允许应用程序改变标准输入、输出或错误流。如果没有此权限,应用程序将无法重定向这些流。
- modifyThread:允许应用程序更改线程的一些属性,例如优先级、守护状态等。如果没有此权限,应用程序将无法更改线程属性。
- stopThread:允许应用程序终止其他线程。如果没有此权限,应用程序将无法终止其他线程。
- accessDeclaredMembers:允许应用程序使用反射访问类的私有成员。如果没有此权限,应用程序将无法访问类的私有成员。
- setFactory:允许应用程序设置用于创建套接字、服务器套接字和服务器套接字工厂的工厂。如果没有此权限,应用程序将无法设置套接字和服务器套接字的工厂。
类设计
功能组件HashMap
- 接口或者抽象类实现
HashMap <- Abstract <- Map
命名模式
前缀模式: Default Generic Common Basic
后缀:Impl - 抽象类实现
抽象类比较难,他是介入接口与类之间的东西
例如接口的通用实现 模板模式(Template) , JDBCTemplate等,一般来说Template会实现另外一个接口
另外一个就是oprations
数据对象
工具辅助
一般是Utils结尾,
抽象类设计
通常抽象类亦可以是普通类,一般以Abstract或者Base开头,抽象类与接口的区别就是抽象类里面可以有实现方法,但是JAVA8之后接口里面也可以有实现方法,也就是这之后的版本接口是可以替代抽象类的,但是也有部分不能替代,就是抽象类里面可以有自己的属性,也就是状态,但是接口里面没有,尽可能少用状态,还有就是静态方法,接口里面是禁止使用
static修饰方法的
高斯林说过抽象类可以删掉,当时是从C++引进来的
接口
一般是上下游的通讯契约,比如有个系统上游调用下游,那么就涉及一个人约定的问题
-
API:契约 参考Collection
extends Iterable -
RPC:远程调用
一般来说实现了某个接口就需要实现其方法,接口的目的就是强类型约束,例如入参是指定接口那么传递进来的对象就必须实现接口以及其中的方法,目的里面可以直接使用约定的方法,需要注意的是接口传递进来的对象实现接口必须唯一性,否则就会有歧义,例如一个方法入参要求传递进来的是CharSequence, 如果入参是String就会不行,因为String除了实现了CharSequence外还实现了java.io.Serializable,那么就会有歧义,这方面需要唯一性,那么此时可以强转,也就是舍弃其余接口的里面的方法常量定义:在接口里面可以定义常量
todo==> 如何使用 ?
方法:动词
- 接口设计
完全抽象与局部抽象:在JDK8之前,接口里面不含有实现方法,但是8之后就可以有实现的default方法
- 单一抽象:接口里面只有一个抽象方法,主要为了函数式编程
Serializable、Cloneable是一标记接口,标志着这个类可以被克隆,参照代码C03_Cloneable.java里面的例子,当我们创建的类A调用super.clone()方法时需要A实现Cloneable接口,否则的话调用clone方法会报错
AutoCloseable
EventListener
内置类
1、临时数据存储类:
像ThreadLocal里面就有一个内置类ThreadLocalMap,这个Map里面的entity需要重点关注
2、特殊用途的API实现
Collections里面的UnmodifiableCollection
将Collection里面所有的写方法全都屏蔽了调用则报错,通知使用者这个方法是只读的也就是不支持进行修改
3、Builder模式(接口)
java.util.stream.Stream.Builder
Stream(接口)里面有一个内置接口Builder
枚举设计
枚举类
实际是一个final class (可以通过字节码查看)
JAVA枚举(enum)引入之前实现的模拟枚举的实现类
成员用常亮表示,并且类型为当前类型(非懒汉模式,直接加载)
常量有关键字final修饰
非public构造器
基本特性:枚举 VS 枚举类
特性 | 枚举类 | 枚举 |
---|---|---|
类成员结构 | \(\checkmark\) | \(\checkmark\) |
必须是具体类 | \(\times\) | \(\checkmark\) |
必须是final类 | \(\times\) | \(\checkmark\) |
支持继承类 | \(\checkmark\) | \(\times\) |
支持实现接口 | \(\checkmark\) | \(\checkmark\) |
可以被继承 | \(\checkmark\) | \(\times\) |
抽象方法 | \(\checkmark\) | \(\checkmark\) |
系列化 | \(\checkmark\) | \(\checkmark\) |
枚举打破了final类里面可以用abstract方法的界限 !
枚举类里的抽象方法需要被里面的元素实现,例如:
enum Count {
ZHANGSAN("男", "18") {
@Override
public String getName() {
return this.name() + " Hello";
}
},
LISI("男", "19") {
@Override
public String getName() {
return this.name();
}
},
WANGER("女", "20") {
@Override
public String getName() {
return this.name();
}
},
SUNXINGZHE("男", "21") {
@Override
public String getName() {
return this.name();
}
},
;
private String sex;
private String age;
Count(String sex, String age) {
this.sex = sex;
this.age = age;
}
abstract public String getName();
}
枚举线程安全
TimeUnit: 出现抽象方法在枚举里面的应用
public enum TimeUnit {
/**
* Time unit representing one thousandth of a microsecond
*/
NANOSECONDS {
public long toNanos(long d) { return d; }
public long toMicros(long d) { return d/(C1/C0); }
public long toMillis(long d) { return d/(C2/C0); }
public long toSeconds(long d) { return d/(C3/C0); }
public long toMinutes(long d) { return d/(C4/C0); }
public long toHours(long d) { return d/(C5/C0); }
public long toDays(long d) { return d/(C6/C0); }
public long convert(long d, TimeUnit u) { return u.toNanos(d); }
int excessNanos(long d, long m) { return (int)(d - (m*C2)); }
}
泛型类设计
泛型的使用场景
-
编译时强类型检查(强类型区别与弱类型)
-
避免类型强转
相对来说比较恶心, 在集合类经常用,如果没有泛型的支撑,也就是需要再代码里面注意判断对象的类型
ArrayList底层实现是数组,LinkedList底层是链表,所以寻址的话ArrayList性能更好,ArrayList实现了RandomAccess接口,意味着可以随机访问 //todo
什么是随机访问? 随机访问性能高? -
实现通用算法
类似foreach等通用算法
-
泛型运行时擦写问题
简单来说就是下面的代码编译期间会报错,但是运行时其实是一样的,简单来说泛型只是编译时期的辅助
泛型类型
A generic type is a generic class or interface that is parameterized over types
泛型类型是在类型上参数化的泛型类或接口
java中有个Class类在java 5之前泛指所有类型,但是JAVA 5 之后引入了Type类型,二者差异如下:
public final class Class<T> implements java.io.Serializable,
GenericDeclaration,
Type,
AnnotatedElement {
}
package java.lang.reflect;
/**
* Type is the common superinterface for all types in the Java
* programming language. These include raw types, parameterized types,
* array types, type variables and primitive types.
*
* @since 1.5
*/
public interface Type {
/**
* Returns a string describing this type, including information
* about any type parameters.
*
* @implSpec The default implementation calls {@code toString}.
*
* @return a string describing this type
* @since 1.8
*/
default String getTypeName() {
return toString();
}
}
-
调用泛型类型
-
实例化泛型
-
JAVA 7 Diamond 语法
List<String> list = new ArrayList<String>(); //1.7之后呢,java引入了diamond运算符,就有了现在的类型推断 List<String> list = new ArrayList<>();
-
类型参数命名约定
- E:表集合元素(Element)
- V:表数值(Value)
- K:表示键(Key)
- T:表示类型
- R: 结果
泛型有界类型参数
通过前面的内内容可以知道,泛型的目的主要是是限定类型,从而进行避免类型强转以及实现通用算法,这的有界参数类型简单来说就是我们对泛型设置一个范围,例如某某类的子类或者某某类的父类等,例如有个类,只需要限定传入的类型是集合或者数字类型。
public static class Container<E>{
private E element;
public Container(E e){
this.element = e;
}
}
public static void main(String[] args) {
// 可以看到这穿进去的类什么类型都可以
Container<String> stringContainer = new Container<>("hello");
Container<Integer> integerContainer = new Container<Integer>(100);
}
-
单界限
- 限定上界
/*上限:意味着只能是CharSequence的子类(或者实现这个接口)*/ /*意味着只能是CharSequence的子类(或者实现这个接口)*/ public static class ContainerExtends<E extends CharSequence>{ private E element; public ContainerExtends(E e){ this.element = e; } public E getElement() { return element; } public boolean setElement(E element) { this.element = element; return true; } } ContainerExtends<StringBuilder> stringContainerExtends = new ContainerExtends("hello world"); // 这个地方会报错, 原因时java只能在编译时识别构造器里传递进去的类型,此时String 与 StringBuilder都是CharSequence子类,所以编译不报 错, 但是运行时对象类型都被擦除了, 都变成Object类型,执行上面那句不报错,但是执行下面的set的时候,由于E是StringBuilder类型,所以需 要传递进去StringBuilder类型 stringContainerExtends.setElement("Hello");
- 限定下届
一般用super限定泛型的下边界,例如
? super Number
意味着类型可以是Number以及其父类,看接口其实现了java.io.Serializable
以及Objectpublic static void lowerBoundedWildcards(List<? super Number> consumer ){ consumer.forEach( System.out::println ); }
-
多界限
对于我们的泛型除了要求他需要继承某一个类以外还可以要求其实现了某某接口,下面的C是类,但是I1,I2就是接口
public static class Temeplate< T extends C & I1 & I2>{ private T element; public Temeplate(T e) { this.element = e; } public T getElement() { return element; } public void setElement(T element) { this.element = element; } }
-
泛型方法和有界类型参数
public static class C {
}
public static interface I1{
}
public static interface I2{
}
public static class Temeplate<T extends C & I1 & I2>{
}
泛型上边界与下边界的重要特点
在 Java 泛型中,super
和 extends
是两个通配符(Wildcard)用于限定泛型类型的上界和下界。它们在泛型中的使用场景和含义有一些差异。
extends
: 上界通配符。在声明泛型时,使用extends
关键字可以限制泛型参数的类型必须是指定类型或其子类型。在使用extends
时,可以读取数据,但不能写入(添加)数据。
javaCopy codepublic void processList(List<? extends Number> list) {
// 读取数据
Number number = list.get(0);
// 但不能写入数据,下面的语句会报错
// list.add(10);
}
在上面的例子中,processList
方法接收一个类型为 List<? extends Number>
的参数。这意味着传递的列表可以是 List<Number>
、List<Integer>
、List<Double>
等,因为这些类型都是 Number
类型或其子类型。但在方法内部,我们只能读取其中的元素而不能添加新的元素。
super
: 下界通配符。在声明泛型时,使用super
关键字可以限制泛型参数的类型必须是指定类型或其父类型。在使用super
时,可以写入(添加)数据,但不能保证能够正确地读取出数据的确切类型。
javaCopy codepublic void addToList(List<? super Integer> list) {
// 可以添加 Integer 及其子类类型的元素
list.add(10);
list.add(new Integer(20));
// 但不能直接读取元素,下面的语句会报错
// Integer num = list.get(0);
// Object 类型可以接收任何对象
Object obj = list.get(0);
}
在上面的例子中,addToList
方法接收一个类型为 List<? super Integer>
的参数。这意味着传递的列表可以是 List<Integer>
、List<Number>
、List<Object>
等,因为这些类型都是 Integer
类型或其父类型。我们可以向其中添加 Integer 及其子类类型的元素,但不能直接读取元素的确切类型,只能以 Object
类型接收。
总结一下:
extends
用于限定泛型的上界,表示类型必须是指定类型或其子类型,在读取数据时安全,但不能添加数据。super
用于限定泛型的下界,表示类型必须是指定类型或其父类型,在添加数据时安全,但不能保证读取数据的确切类型。
为什么不能添加元素
在泛型中,为了保证类型安全性,Java 引入了通配符(Wildcard)来限定泛型的使用范围。当使用 ? extends T
通配符时,表示泛型参数必须是类型 T
或其子类型,但无法确定具体是哪个子类型。因此,向这样的泛型对象中添加数据是不安全的。
考虑下面这个例子:
javaCopy codepublic void addToList(List<? extends Number> list) {
// 无法确定list的具体类型,假设它是List<Integer>
// 如果我们允许添加数据,则可能导致类型不匹配的问题
list.add(new Integer(10));
}
假设我们传递一个 List<Integer>
给 addToList
方法,这个方法的声明是 addToList(List<? extends Number> list)
,在方法内部,我们无法确定具体的泛型类型是 List<Integer>
,List<Double>
还是其他类型的列表。如果我们允许在方法内添加数据,比如 list.add(new Integer(10))
,则可能导致类型不匹配的问题。因为如果列表实际上是 List<Double>
,那么向其中添加 Integer
类型的元素将会出现错误。
因此,为了保证泛型的类型安全性,在使用 ? extends T
通配符时,编译器禁止向该泛型对象添加数据。
如果确实需要向泛型对象添加数据,可以使用 ? super T
通配符来表示泛型参数必须是类型 T
或其父类型。使用 ? super T
通配符时,可以添加类型 T
及其子类型的数据,但在读取时只能以 Object
类型接收,因为不能确定具体的子类型。
JAVA方法名设计
一般来说类是名词,而方法一般是动词、单一动词、动词+副词
例如:
renderConcurrently :并发渲染
renderSynchronously:同步渲染
Runable里的run
Action里的execute()
Callable里的call
方法参数设计
Arguments : 一般表示方法参数
parameters:通常是指外部媒介传递过来的,请求参数
Effective JAVA 建议不要超过四个参数
Consumer 一个参数
Function BiFunction两个参数
能用可变参数作为方法的参数就用,可以增加程序的扩展性
- 对等方式的单项传输
public void copy(Object source, Object target) {
}
- 非对等方式的单项传输 -- 参数类型非对等
public void add(Collection<Object> collection, Object element) {
collection.add(element);
}
- 多项传输
/**
* @param collection 集合类
* @param elements zero or more elements (0..*)
*/
// 多项传输
public void addAll(Collection<Object> collection, Object... elements) {
for (Object element : elements) {
collection.add(element);
}
}